DonatShell
Server IP : 180.180.241.3  /  Your IP : 216.73.216.252
Web Server : Microsoft-IIS/7.5
System : Windows NT NETWORK-NHRC 6.1 build 7601 (Windows Server 2008 R2 Standard Edition Service Pack 1) i586
User : IUSR ( 0)
PHP Version : 5.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /Program Files (x86)/Mozilla Firefox/updated/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME SHELL ]     

Current File : /Program Files (x86)/Mozilla Firefox/updated//omni.ja
PK
!<Y.HHchrome.manifestPK
!<YCJ  Ychrome/chrome.manifestPK
!<xrUVUV%components/components.manifestPK
!<_  >|components/interfaces.xptPK
!<[3NN*chrome/en-US/locale/en-US/alerts/alert.dtdPK
!<qS1+chrome/en-US/locale/en-US/alerts/alert.propertiesPK
!<
9..:S"chrome/en-US/locale/en-US/autoconfig/autoconfig.propertiesPK
!<b4q.q.4$chrome/en-US/locale/en-US/global/AccessFu.propertiesPK
!<t}ee*Schrome/en-US/locale/en-US/global/about.dtdPK
!<v+/I_chrome/en-US/locale/en-US/global/aboutAbout.dtdPK
!<Ft4achrome/en-US/locale/en-US/global/aboutNetworking.dtdPK
!<[GIŨ2pchrome/en-US/locale/en-US/global/aboutProfiles.dtdPK
!<'zII9schrome/en-US/locale/en-US/global/aboutProfiles.propertiesPK
!<m}7*{chrome/en-US/locale/en-US/global/aboutReader.propertiesPK
!<sϜ##0
chrome/en-US/locale/en-US/global/aboutRights.dtdPK
!<uEnn8$chrome/en-US/locale/en-US/global/aboutServiceWorkers.dtdPK
!<Pt>?chrome/en-US/locale/en-US/global/aboutServiceWorkers.propertiesPK
!<Ӥ&918chrome/en-US/locale/en-US/global/aboutSupport.dtdPK
!<#^!!8.chrome/en-US/locale/en-US/global/aboutSupport.propertiesPK
!<O3'
'
3chrome/en-US/locale/en-US/global/aboutTelemetry.dtdPK
!<_ş:chrome/en-US/locale/en-US/global/aboutTelemetry.propertiesPK
!<;èT	T	7	chrome/en-US/locale/en-US/global/aboutUrlClassifier.dtdPK
!<_w

>
	chrome/en-US/locale/en-US/global/aboutUrlClassifier.propertiesPK
!<k2)7#	chrome/en-US/locale/en-US/global/aboutWebrtc.propertiesPK
!<%3._%	chrome/en-US/locale/en-US/global/appPicker.dtdPK
!<P{62'	chrome/en-US/locale/en-US/global/appstrings.propertiesPK
!<KMpp8:7	chrome/en-US/locale/en-US/global/autocomplete.propertiesPK
!<&W))*<	chrome/en-US/locale/en-US/global/brand.dtdPK
!<۞3q=	chrome/en-US/locale/en-US/global/browser.propertiesPK
!<l-990YA	chrome/en-US/locale/en-US/global/charsetMenu.dtdPK
!<?#

7B	chrome/en-US/locale/en-US/global/charsetMenu.propertiesPK
!<z==1P	chrome/en-US/locale/en-US/global/commonDialog.dtdPK
!<Ȥli9`S	chrome/en-US/locale/en-US/global/commonDialogs.propertiesPK
!<'Bł+yY	chrome/en-US/locale/en-US/global/config.dtdPK
!<X.882Da	chrome/en-US/locale/en-US/global/config.propertiesPK
!<PP?c	chrome/en-US/locale/en-US/global/contentAreaCommands.propertiesPK
!<gDRR,yg	chrome/en-US/locale/en-US/global/crashes.dtdPK
!< d883k	chrome/en-US/locale/en-US/global/crashes.propertiesPK
!<\{11/l	chrome/en-US/locale/en-US/global/css.propertiesPK
!<(Gee5s	chrome/en-US/locale/en-US/global/customizeToolbar.dtdPK
!<<+	chrome/en-US/locale/en-US/global/customizeToolbar.propertiesPK
!<GKgff0	chrome/en-US/locale/en-US/global/datetimebox.dtdPK
!<sff38	chrome/en-US/locale/en-US/global/datetimepicker.dtdPK
!<Y:Ugg2	chrome/en-US/locale/en-US/global/dialog.propertiesPK
!<Cl2	chrome/en-US/locale/en-US/global/dialogOverlay.dtdPK
!<d3	chrome/en-US/locale/en-US/global/dom/dom.propertiesPK
!<5\4rF
chrome/en-US/locale/en-US/global/editMenuOverlay.dtdPK
!<Qars6L
chrome/en-US/locale/en-US/global/extensions.propertiesPK
!<i
11;T
chrome/en-US/locale/en-US/global/fallbackMenubar.propertiesPK
!<Ǡ=5V
chrome/en-US/locale/en-US/global/filefield.propertiesPK
!<f6W
chrome/en-US/locale/en-US/global/filepicker.propertiesPK
!<@	*,_
chrome/en-US/locale/en-US/global/findbar.dtdPK
!<5EE3c
chrome/en-US/locale/en-US/global/findbar.propertiesPK
!<XEE/h
chrome/en-US/locale/en-US/global/finddialog.dtdPK
!<A!06l
chrome/en-US/locale/en-US/global/finddialog.propertiesPK
!<[{9zm
chrome/en-US/locale/en-US/global/global-strres.propertiesPK
!<e{+n
chrome/en-US/locale/en-US/global/global.dtdPK
!<&%%/o
chrome/en-US/locale/en-US/global/globalKeys.dtdPK
!<'5mm)oq
chrome/en-US/locale/en-US/global/intl.cssPK
!<T0#s
chrome/en-US/locale/en-US/global/intl.propertiesPK
!<+[80'
chrome/en-US/locale/en-US/global/keys.propertiesPK
!<$++9V
chrome/en-US/locale/en-US/global/languageNames.propertiesPK
!<{HL
L
;ؓ
chrome/en-US/locale/en-US/global/layout/HtmlForm.propertiesPK
!<&乮@}
chrome/en-US/locale/en-US/global/layout/MediaDocument.propertiesPK
!<9ƻ*..=
chrome/en-US/locale/en-US/global/layout/htmlparser.propertiesPK
!<22<f
chrome/en-US/locale/en-US/global/layout/xmlparser.propertiesPK
!<z9
chrome/en-US/locale/en-US/global/layout_errors.propertiesPK
!<qOO9D
chrome/en-US/locale/en-US/global/mathml/mathml.propertiesPK
!<Cң,
chrome/en-US/locale/en-US/global/mozilla.dtdPK
!<1ÿOO3
chrome/en-US/locale/en-US/global/narrate.propertiesPK
!</I5}$}$-|
chrome/en-US/locale/en-US/global/netError.dtdPK
!<ՑC0Dchrome/en-US/locale/en-US/global/netErrorApp.dtdPK
!<Ÿ1$ chrome/en-US/locale/en-US/global/notification.dtdPK
!<>3?#"chrome/en-US/locale/en-US/global/nsWebBrowserPersist.propertiesPK
!<|u?3{)chrome/en-US/locale/en-US/global/plugins.propertiesPK
!<i0/chrome/en-US/locale/en-US/global/preferences.dtdPK
!<ʏ31chrome/en-US/locale/en-US/global/printPageSetup.dtdPK
!<[CC1;chrome/en-US/locale/en-US/global/printPreview.dtdPK
!</9Bchrome/en-US/locale/en-US/global/printPreviewProgress.dtdPK
!<ᗲ2Dchrome/en-US/locale/en-US/global/printProgress.dtdPK
!<Ƒr330%Hchrome/en-US/locale/en-US/global/printdialog.dtdPK
!<m͒37Nchrome/en-US/locale/en-US/global/printdialog.propertiesPK
!<H'i4
Wchrome/en-US/locale/en-US/global/printing.propertiesPK
!<	+4jcchrome/en-US/locale/en-US/global/printjoboptions.dtdPK
!<C--7gchrome/en-US/locale/en-US/global/regionNames.propertiesPK
!<1!zchrome/en-US/locale/en-US/global/resetProfile.dtdPK
!<ZZ8~chrome/en-US/locale/en-US/global/resetProfile.propertiesPK
!<Ȍ9chrome/en-US/locale/en-US/global/search/search.propertiesPK
!<]]

90chrome/en-US/locale/en-US/global/security/caps.propertiesPK
!<)ɱ8chrome/en-US/locale/en-US/global/security/csp.propertiesPK
!<;C#C#=chrome/en-US/locale/en-US/global/security/security.propertiesPK
!<i&rQ3Kchrome/en-US/locale/en-US/global/svg/svg.propertiesPK
!<0chrome/en-US/locale/en-US/global/textcontext.dtdPK
!<^)chrome/en-US/locale/en-US/global/tree.dtdPK
!<M[		2Ichrome/en-US/locale/en-US/global/videocontrols.dtdPK
!<2/chrome/en-US/locale/en-US/global/viewSource.dtdPK
!<{6_chrome/en-US/locale/en-US/global/viewSource.propertiesPK
!<FN..+chrome/en-US/locale/en-US/global/wizard.dtdPK
!<M1ZZ2*
chrome/en-US/locale/en-US/global/wizard.propertiesPK
!<c/chrome/en-US/locale/en-US/global/xbl.propertiesPK
!<)dd4chrome/en-US/locale/en-US/global/xml/prettyprint.dtdPK
!<[PJJ5bchrome/en-US/locale/en-US/global/xslt/xslt.propertiesPK
!<J/chrome/en-US/locale/en-US/global/xul.propertiesPK
!<@OZZC; chrome/en-US/locale/en-US/global-platform/mac/accessible.propertiesPK
!<eGgg='chrome/en-US/locale/en-US/global-platform/mac/intl.propertiesPK
!<E)chrome/en-US/locale/en-US/global-platform/mac/platformKeys.propertiesPK
!<hD,chrome/en-US/locale/en-US/global-platform/unix/accessible.propertiesPK
!<eGgg>P/chrome/en-US/locale/en-US/global-platform/unix/intl.propertiesPK
!<ccF1chrome/en-US/locale/en-US/global-platform/unix/platformKeys.propertiesPK
!<hC3chrome/en-US/locale/en-US/global-platform/win/accessible.propertiesPK
!<eGgg=*6chrome/en-US/locale/en-US/global-platform/win/intl.propertiesPK
!<qj..E7chrome/en-US/locale/en-US/global-platform/win/platformKeys.propertiesPK
!<I;22@}:chrome/en-US/locale/en-US/mozapps/downloads/downloads.propertiesPK
!<u>
Pchrome/en-US/locale/en-US/mozapps/downloads/settingsChange.dtdPK
!</`úWWBRchrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.dtdPK
!<?k	IVchrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.propertiesPK
!<c6[chrome/en-US/locale/en-US/mozapps/extensions/about.dtdPK
!<9Z!!:L]chrome/en-US/locale/en-US/mozapps/extensions/blocklist.dtdPK
!<2LD>D>;bchrome/en-US/locale/en-US/mozapps/extensions/extensions.dtdPK
!<ز
-
-Bbchrome/en-US/locale/en-US/mozapps/extensions/extensions.propertiesPK
!<Gzz9chrome/en-US/locale/en-US/mozapps/extensions/newaddon.dtdPK
!<VE@chrome/en-US/locale/en-US/mozapps/extensions/newaddon.propertiesPK
!<9/zz7chrome/en-US/locale/en-US/mozapps/extensions/update.dtdPK
!<0b>chrome/en-US/locale/en-US/mozapps/extensions/update.propertiesPK
!<Hn~7chrome/en-US/locale/en-US/mozapps/handling/handling.dtdPK
!<p>chrome/en-US/locale/en-US/mozapps/handling/handling.propertiesPK
!<
U:chrome/en-US/locale/en-US/mozapps/preferences/changemp.dtdPK
!<3yyDachrome/en-US/locale/en-US/mozapps/preferences/preferences.propertiesPK
!<l:<chrome/en-US/locale/en-US/mozapps/preferences/removemp.dtdPK
!<,0*77Ahchrome/en-US/locale/en-US/mozapps/profile/createProfileWizard.dtdPK
!<*bb>
chrome/en-US/locale/en-US/mozapps/profile/profileSelection.dtdPK
!<2lkE
chrome/en-US/locale/en-US/mozapps/profile/profileSelection.propertiesPK
!<nL4
chrome/en-US/locale/en-US/mozapps/update/history.dtdPK
!<Ïuu4
chrome/en-US/locale/en-US/mozapps/update/updates.dtdPK
!<]C;k.
chrome/en-US/locale/en-US/mozapps/update/updates.propertiesPK
!<ǺDD@>
chrome/en-US/locale/en-US/mozapps/xpinstall/xpinstallConfirm.dtdPK
!<ֆGAA
chrome/en-US/locale/en-US/mozapps/xpinstall/xpinstallConfirm.propertiesPK
!<Iݪ~	~	0mC
chrome/en-US/locale/en-US/necko/necko.propertiesPK
!<jVBB99M
chrome/en-US/locale/en-US/passwordmgr/passwordManager.dtdPK
!<]b!!<T
chrome/en-US/locale/en-US/passwordmgr/passwordmgr.propertiesPK
!<7&pc'q'q5Mf
chrome/en-US/locale/en-US/pipnss/nsserrors.propertiesPK
!<CL8]<<2
chrome/en-US/locale/en-US/pipnss/pipnss.propertiesPK
!<ero0chrome/en-US/locale/en-US/pippki/certManager.dtdPK
!<r]2)chrome/en-US/locale/en-US/pippki/deviceManager.dtdPK
!<ahD+1chrome/en-US/locale/en-US/pippki/pippki.dtdPK
!<[))2>chrome/en-US/locale/en-US/pippki/pippki.propertiesPK
!<=eSS29ichrome/en-US/locale/en-US/places/places.propertiesPK
!<vd
d
9mchrome/en-US/locale/en-US/pluginproblem/pluginproblem.dtdPK
!<foo2xchrome/en-US/locale/en-US/services/sync.propertiesPK
!<C`/`/V{hyphenation/hyph_af.dicPK
!<O
0<0<hyphenation/hyph_bg.dicPK
!<=MPhyphenation/hyph_ca.dicPK
!<Be::_hyphenation/hyph_cy.dicPK
!<rE{{hyphenation/hyph_da.dicPK
!<z~hyphenation/hyph_de-1901.dicPK
!<|.||k&hyphenation/hyph_de-1996.dicPK
!<FF#[		!:hyphenation/hyph_de-CH.dicPK
!<4OOO
Dhyphenation/hyph_en_US.dicPK
!<@.}}hyphenation/hyph_eo.dicPK
!<mV`hyphenation/hyph_es.dicPK
!<e
!!hyphenation/hyph_et.dicPK
!<1}hyphenation/hyph_fi.dicPK
!<0k6868hyphenation/hyph_fr.dicPK
!<jj]hyphenation/hyph_gl.dicPK
!<T0KK|-hyphenation/hyph_hr.dicPK
!<)&--Khyphenation/hyph_hsb.dicPK
!<;Ȣ`
`
yhyphenation/hyph_hu.dicPK
!<
˘&hyphenation/hyph_ia.dicPK
!<`#⌸&hyphenation/hyph_is.dicPK
!<X/Q		s'hyphenation/hyph_it.dicPK
!<b{6}'hyphenation/hyph_kmr.dicPK
!<_tRR'hyphenation/hyph_la.dicPK
!<NIO'O'*'hyphenation/hyph_lt.dicPK
!<2:F6F6'hyphenation/hyph_mn.dicPK
!<vp
,
,)'hyphenation/hyph_nb.dicPK
!<& h,hyphenation/hyph_nl.dicPK
!<\
,
,)-hyphenation/hyph_nn.dicPK
!<^8$h2hyphenation/hyph_pl.dicPK
!<_N2hyphenation/hyph_pt.dicPK
!<Gbb2hyphenation/hyph_ru.dicPK
!<(K))A4hyphenation/hyph_sh.dicPK
!<5|F5hyphenation/hyph_sl.dicPK
!<G(e5hyphenation/hyph_sv.dicPK
!<PzN|
6hyphenation/hyph_tr.dicPK
!<f}kJJf6hyphenation/hyph_uk.dicPK
!<W
7update.localePK
!<M>XGG7components/ConsoleAPIStorage.jsPK
!< ezz"27components/BrowserElementParent.jsPK
!<}7components/FeedProcessor.jsPK
!<t]%8components/UAOverridesBootstrapper.jsPK
!<m)ם8components/WellKnownOpportunisticUtils.jsPK
!<ss#8components/nsDNSServiceDiscovery.jsPK
!<Ϣ..8components/DownloadLegacy.jsPK
!<;8components/nsCrashMonitor.jsPK
!<\}}a8components/nsSearchService.jsPK
!<KGt((!{;components/nsSearchSuggestions.jsPK
!<;components/nsSidebar.jsPK
!<Ik
k
;components/nsLoginInfo.jsPK
!<
0DDJ;components/nsLoginManager.jsPK
!<e3$;components/nsLoginManagerPrompter.jsPK
!<e%7MCMC<components/storage-json.jsPK
!<Ǜ#=components/crypto-SDR.jsPK
!<%!~:=components/TooltipTextProvider.jsPK
!<F++!eR=components/WebVTTParserWrapper.jsPK
!<?qQcZ=components/nsHelperAppDlg.jsPK
!<<~V//(>components/NetworkGeolocationProvider.jsPK
!<Lxe8>components/EditorUtils.jsPK
!<!!<>components/addonManager.jsPK
!<0
0
^>components/amContentHandler.jsPK
!<"xUl>components/amInstallTrigger.jsPK
!<2R3>components/amWebAPI.jsPK
!<v4TT &>components/nsBlocklistService.jsPK
!<


'?components/nsBlocklistServiceContent.jsPK
!<!Qw77̦?components/nsUpdateService.jsPK
!<4:!Acomponents/nsUpdateServiceStub.jsPK
!<533"Acomponents/nsUpdateTimerManager.jsPK
!<{R QQBcomponents/multiprocessShims.jsPK
!<g2Bcomponents/defaultShims.jsPK
!<S7Bcomponents/simpleServices.jsPK
!<7Xߖ33"GBcomponents/MainProcessSingleton.jsPK
!<T#  %UBcomponents/ContentProcessSingleton.jsPK
!<b%%iBcomponents/nsURLFormatter.jsPK
!<=$	$	#PBcomponents/txEXSLTRegExFunctions.jsPK
!<>-mmBcomponents/nsLivemarkService.jsPK
!<F#39Y9YBcomponents/nsTaggingService.jsPK
!<Z^	W	WcSCcomponents/UnifiedComplete.jsPK
!<%oMM Dcomponents/nsPlacesExpiration.jsPK
!<_okk%2@Ecomponents/PageIconProtocolHandler.jsPK
!<<û

%QEcomponents/PlacesCategoriesStarter.jsPK
!<9M\Ecomponents/ColorAnalyzer.jsPK
!<7(`` jEcomponents/PageThumbsProtocol.jsPK
!<X<x {Ecomponents/mozProtocolHandler.jsPK
!<|ãÁEcomponents/nsDefaultCLH.jsPK
!<-WW"Ecomponents/nsContentPrefService.jsPK
!<RR&4Fcomponents/nsContentDispatchChooser.jsPK
!<MGAA#/@Fcomponents/nsHandlerService-json.jsPK
!<Flvv	Fcomponents/nsHandlerService.jsPK
!<__UGcomponents/nsWebHandlerApp.jsPK
!<0UU UnGcomponents/nsFormAutoComplete.jsPK
!<SJJ 6Gcomponents/FormHistoryStartup.jsPK
!<BtJ%Gcomponents/nsInputListAutoComplete.jsPK
!<{{%Gcomponents/contentAreaDropListener.jsPK
!<_@Gcomponents/nsINIProcessor.jsPK
!<6Hcomponents/nsPrompter.jsPK
!<`˱\Hcomponents/Weave.jsPK
!<ov v >Hcomponents/FxAccountsPush.jsPK
!<̚#+A+AHcomponents/captivedetect.jsPK
!<WMضRIcomponents/TelemetryStartup.jsPK
!<=(!!DIcomponents/XULStore.jsPK
!<Oll5Icomponents/recording-cmdline.jsPK
!<q|BAIcomponents/htmlMenuBuilder.jsPK
!<Fa@8!!!DNIcomponents/NotificationStorage.jsPK
!<BY""oIcomponents/Push.jsPK
!<@==Icomponents/PushComponents.jsPK
!<tt!Icomponents/RemoteWebNavigation.jsPK
!<3k	k	tIcomponents/ProcessSelector.jsPK
!<OH<Icomponents/SlowScriptDebug.jsPK
!<K'Icomponents/PeerConnection.jsPK
!<]dR2R2*Jchrome/marionette/content/accessibility.jsPK
!<iG+#Kchrome/marionette/content/action.jsPK
!<E"gKchrome/marionette/content/addon.jsPK
!<'%%#.Kchrome/marionette/content/assert.jsPK
!<OXrXr!!Kchrome/marionette/content/atom.jsPK
!<ISY0Y0$DMchrome/marionette/content/browser.jsPK
!<*Ϻ$SuMchrome/marionette/content/capture.jsPK
!< ^Z!nMchrome/marionette/content/cert.jsPK
!<)
#Mchrome/marionette/content/cookie.jsPK
!<}22#_Mchrome/marionette/content/driver.jsPK
!<Vl~p~p$vOchrome/marionette/content/element.jsPK
!<3*{;::"Ochrome/marionette/content/error.jsPK
!<Ј\7\7%"Pchrome/marionette/content/evaluate.jsPK
!<_DD"CZPchrome/marionette/content/event.jsPK
!<n''"Pchrome/marionette/content/frame.jsPK
!<$55(#Qchrome/marionette/content/interaction.jsPK
!<'i>!YQchrome/marionette/content/l10n.jsPK
!<.n66)eQchrome/marionette/content/legacyaction.jsPK
!<#AA%*Qchrome/marionette/content/listener.jsPK
!<ӠM!M!$Rchrome/marionette/content/message.jsPK
!<"=Rchrome/marionette/content/modal.jsPK
!<W%Rchrome/marionette/content/navigate.jsPK
!<(k+k+$lRchrome/marionette/content/packets.jsPK
!<m3m3"Rchrome/marionette/content/proxy.jsPK
!<Ou..$ Schrome/marionette/content/reftest.jsPK
!<
=+%OSchrome/marionette/content/reftest.xulPK
!<LjqMM#PSchrome/marionette/content/server.jsPK
!<`8`8$Schrome/marionette/content/session.jsPK
!<e_V)(Schrome/marionette/content/stream-utils.jsPK
!<z?{{"zSchrome/marionette/content/test.xulPK
!<Iյ#5Schrome/marionette/content/test2.xulPK
!<X4Schrome/marionette/content/test_anonymous_content.xulPK
!<ɤ;##)aTchrome/marionette/content/test_dialog.dtdPK
!<y90Tchrome/marionette/content/test_dialog.propertiesPK
!<)Tchrome/marionette/content/test_dialog.xulPK
!<cYI<<0O
Tchrome/marionette/content/test_nested_iframe.xulPK
!<Xjj&Tchrome/marionette/content/transport.jsPK
!<I0x!wTchrome/marionette/content/wait.jsPK
!<Lզ&&dTcomponents/marionette.jsPK
!<e@Tcomponents/nsAsyncShutdown.jsPK
!<?/9{9{(Tcomponents/PresentationControlService.jsPK
!<pt--5GUcomponents/PresentationDataChannelSessionTransport.jsPK
!<&$

tUcomponents/mozIntl.jsPK
!<C(.4.4&+Ucomponents/extension-process-script.jsPK
!<Ph||Umodules/AboutReader.jsmPK
!<>Ye3Vmodules/AddonManager.jsmPK
!<%%-Xmodules/AppConstants.jsmPK
!<N04 SXmodules/AppMenuNotifications.jsmPK
!<d;//'hXmodules/AsyncPrefs.jsmPK
!<l^@@|Xmodules/AsyncShutdown.jsmPK
!<`%%Ymodules/AutoCompletePopup.jsmPK
!<IHIH 'Ymodules/BackgroundPageThumbs.jsmPK
!<s		spYmodules/Battery.jsmPK
!<h		zYmodules/BinarySearch.jsmPK
!<L{JJȄYmodules/BookmarkHTMLUtils.jsmPK
!<lMLLM*Zmodules/BookmarkJSONUtils.jsmPK
!<3̛vvwZmodules/Bookmarks.jsmPK
!<HnWW'[modules/BrowserElementPromptService.jsmPK
!<.oː__DF\modules/BrowserUtils.jsmPK
!<+?
\modules/CanonicalJSON.jsmPK
!<<杢׮\modules/CertUtils.jsmPK
!<!\modules/CharsetMenu.jsmPK
!<DsPP \modules/ChromeManifestParser.jsmPK
!<w>\modules/ClientID.jsmPK
!<f\\[]modules/CloudStorage.jsmPK
!<t9Aq!q!>v]modules/ClusterLib.jsPK
!<Wh]modules/Color.jsmPK
!<ѕW3W3]modules/ColorAnalyzer_worker.jsPK
!<L]modules/ColorConversion.jsPK
!<:b)**~]modules/CommonDialog.jsmPK
!<		^modules/CompatWarning.jsmPK
!<QYYV^modules/Console.jsmPK
!<
j

q^modules/ContentPrefInstance.jsmPK
!<t"qq|^modules/ContentPrefService2.jsmPK
!<#^modules/ContentPrefServiceChild.jsmPK
!< $_modules/ContentPrefServiceParent.jsmPK
!<ByvV_modules/ContentPrefStore.jsmPK
!<S'_modules/ContentPrefUtils.jsmPK
!<%{C
-
-%0_modules/ContextualIdentityService.jsmPK
!<p	~]_modules/CrashManager.jsmPK
!<hh`modules/CrashMonitor.jsmPK
!<

^&`modules/CrashReports.jsmPK
!<Ɩ
1I1IT1`modules/CrashSubmit.jsmPK
!<hzJz`modules/Credentials.jsmPK
!<kd`modules/DNSPacket.jsmPK
!<a44`modules/DNSRecord.jsmPK
!<gk`modules/DNSResourceRecord.jsmPK
!<=^^`modules/DNSTypes.jsmPK
!<_B''T`modules/DOMRequestHelper.jsmPK
!<pU`modules/DataReader.jsmPK
!<T		amodules/DataWriter.jsmPK
!<p1 amodules/DateTimePickerHelper.jsmPK
!<nd\
(('"amodules/DeferredSave.jsmPK
!<@'kR--Jamodules/DeferredTask.jsmPK
!<lf	f	xamodules/Deprecated.jsmPK
!<I{amodules/DownloadCore.jsmPK
!<V**?cmodules/DownloadHistory.jsmPK
!<
cmodules/DownloadIntegration.jsmPK
!<Thhcmodules/DownloadLastDir.jsmPK
!<*>>cmodules/DownloadList.jsmPK
!<wtFdmodules/DownloadPaths.jsmPK
!<=Ym9%dmodules/DownloadStore.jsmPK
!<1
8mm<dmodules/DownloadUIHelper.jsmPK
!<|//NN,\dmodules/DownloadUtils.jsmPK
!<(**dmodules/Downloads.jsmPK
!<%dmodules/EventEmitter.jsmPK
!<d^Kdmodules/Extension.jsmPK
!<n

emodules/ExtensionAPI.jsmPK
!<Xttemodules/ExtensionChild.jsmPK
!<((t
t
'fmodules/ExtensionChildDevToolsUtils.jsmPK
!<iNVVw!fmodules/ExtensionCommon.jsmPK
!<c[[fmodules/ExtensionContent.jsmPK
!<b@.1.1]*gmodules/ExtensionPageChild.jsmPK
!<VVmodules/ExtensionParent.jsmPK
!<XVoG Vhmodules/ExtensionPermissions.jsmPK
!<3Ф**'-hmodules/ExtensionPreferencesManager.jsmPK
!<6''"Xhmodules/ExtensionSearchHandler.jsmPK
!<1::"Ԁhmodules/ExtensionSettingsStore.jsmPK
!<-û[((hmodules/ExtensionStorage.jsmPK
!<?t hmodules/ExtensionStorageSync.jsmPK
!<gXMimodules/ExtensionTabs.jsmPK
!<M3K3Kpqjmodules/ExtensionUtils.jsmPK
!<.eggۼjmodules/FileUtils.jsmPK
!<4\OOujmodules/Finder.jsmPK
!<=^SST$kmodules/FinderHighlighter.jsmPK
!<{z``lmodules/FinderIterator.jsmPK
!<QW%%qlmodules/ForgetAboutSite.jsmPK
!<"XTo8o8ٗlmodules/FormData.jsmPK
!<8t((zlmodules/FormHistory.jsmPK
!<B!!`mmodules/FormLikeFactory.jsmPK
!<\1ummodules/FxAccounts.jsmPK
!<$LPPtnmodules/FxAccountsClient.jsmPK
!<D\\nmodules/FxAccountsCommon.jsPK
!<7oo!omodules/FxAccountsConfig.jsmPK
!<mۘ77&?omodules/FxAccountsOAuthGrantClient.jsmPK
!<X}uuZomodules/FxAccountsProfile.jsmPK
!<41[  #momodules/FxAccountsProfileClient.jsmPK
!<[!]!]omodules/FxAccountsStorage.jsmPK
!<f:KK omodules/FxAccountsWebChannel.jsmPK
!<yߞcc!8pmodules/GCTelemetry.jsmPK
!<{q	q	Ppmodules/GMPExtractorWorker.jsPK
!<TWt=t=eZpmodules/GMPInstallManager.jsmPK
!<+V5 5 pmodules/GMPUtils.jsmPK
!<!!{pmodules/Geometry.jsmPK
!<4

pmodules/HiddenFrame.jsmPK
!<Նvpmodules/History.jsmPK
!<Z-qmodules/Http.jsmPK
!<_sqmodules/ISO8601DateUtils.jsmPK
!<p :qmodules/ImageObjectProcessor.jsmPK
!<''$qmodules/IndexedDB.jsmPK
!<V?rmodules/IndexedDBHelper.jsmPK
!<015L5Lrmodules/InlineSpellChecker.jsmPK
!<=		%grmodules/InlineSpellCheckerContent.jsmPK
!<<ɰ!yrmodules/InsecurePasswordUtils.jsmPK
!<mi))Ƙrmodules/Integration.jsmPK
!<Rʖg//rmodules/JSONFile.jsmPK
!<I^rmodules/KeyValueParser.jsmPK
!<|""!`rmodules/LegacyExtensionsUtils.jsmPK
!<F$Ysmodules/LightweightThemeConsumer.jsmPK
!<JR\d\d#8smodules/LightweightThemeManager.jsmPK
!<;Ѱ5smodules/LoadContextInfo.jsmPK
!<!

smodules/Locale.jsmPK
!<ZO;d;dsmodules/Log.jsmPK
!<P	h	htmodules/LoginHelper.jsmPK
!<2gCDxtmodules/LoginImport.jsmPK
!<tmodules/LoginManagerContent.jsmPK
!<It'#uzumodules/LoginManagerContextMenu.jsmPK
!<njIjIumodules/LoginManagerParent.jsmPK
!<!!:umodules/LoginRecipes.jsmPK
!<<]BB4vmodules/LoginStore.jsmPK
!<܈vmodules/Manifest.jsmPK
!<:\d'vmodules/ManifestFinder.jsmPK
!<u?

w/vmodules/ManifestIcons.jsmPK
!<t9vmodules/ManifestObtainer.jsmPK
!<
""Mvmodules/ManifestProcessor.jsmPK
!<z411Gpvmodules/MatchPattern.jsmPK
!<\O
O
dvmodules/Memory.jsmPK
!<!>nnvmodules/MessageChannel.jsmPK
!<:Wafafwmodules/MulticastDNS.jsmPK
!<	wmodules/NLP.jsmPK
!<F==Lwmodules/NativeMessaging.jsmPK
!<VMM1wmodules/NetUtil.jsmPK
!<m9((xmodules/NewTabUtils.jsmPK
!<95b~((bxmodules/NotificationDB.jsmPK
!<L>ymodules/OSCrypto.jsmPK
!<pDFi%i%rymodules/OSCrypto_win.jsPK
!<8?Ȓ>ymodules/ObjectUtils.jsmPK
!<<8%8%^Tymodules/PageMenu.jsmPK
!<t'z##yymodules/PageMetadata.jsmPK
!<BpI11ymodules/PageThumbUtils.jsmPK
!<o(wwymodules/PageThumbs.jsmPK
!<̹Gzmodules/PageThumbsWorker.jsPK
!<<zmodules/PerfMeasurement.jsmPK
!<JGG#S\zmodules/PerformanceStats-content.jsPK
!<G'>o>oozmodules/PerformanceStats.jsmPK
!<tX*%Szmodules/PerformanceWatcher-content.jsPK
!<S**Mzmodules/PerformanceWatcher.jsmPK
!<S

?{modules/PermissionsUtils.jsmPK
!<WUWU{modules/PlacesBackups.jsmPK
!<x``s{modules/PlacesDBUtils.jsmPK
!<0(|modules/PlacesRemoteTabsAutocompleteProvider.jsmPK
!<BF'F',3%|modules/PlacesSearchAutocompleteProvider.jsmPK
!<JJL|modules/PlacesSyncUtils.jsmPK
!<R}modules/PlacesTransactions.jsmPK
!<4(ť}~modules/PlacesUtils.jsmPK
!<ڈLLŝmodules/PluralForm.jsmPK
!<
LEmodules/PopupNotifications.jsmPK
!<88dmodules/Preferences.jsmPK
!<jODODo݁modules/Prefetcher.jsmPK
!<$N !modules/PrivateBrowsingUtils.jsmPK
!<r9JE.modules/ProfileAge.jsmPK
!<ŅLLQFmodules/Promise-backend.jsPK
!<TQʂmodules/Promise.jsmPK
!<J5

܂modules/PromiseMessage.jsmPK
!<AY}Smodules/PromiseUtils.jsmPK
!<?v//Xmodules/PromiseWorker.jsmPK
!<=&$$modules/ProxyScriptContext.jsmPK
!<tSS<modules/PushCrypto.jsmPK
!<-u22ďmodules/PushDB.jsmPK
!<P#'#'ƒmodules/PushRecord.jsmPK
!<7modules/PushService.jsmPK
!<aa	modules/PushServiceHttp2.jsmPK
!<ʆʆ Amodules/PushServiceWebSocket.jsmPK
!<ziOOImodules/ReaderMode.jsmPK
!<0MMՅmodules/RemoteAddonsChild.jsmPK
!<8lo#modules/RemoteAddonsParent.jsmPK
!<2modules/RemoteController.jsmPK
!<g))rʆmodules/RemoteFinder.jsmPK
!<5Rm?m?Rmodules/RemotePageManager.jsmPK
!<ަ3modules/RemoteSecurityUI.jsmPK
!<lz(z(7modules/RemoteWebProgress.jsmPK
!<*:4 0	0	`modules/ResetProfile.jsmPK
!<
!imodules/ResponsivenessMonitor.jsmPK
!<;

"nmodules/RokuApp.jsmPK
!<K]	G	G]modules/SafeBrowsing.jsmPK
!<?FFχmodules/Schemas.jsmPK
!<

modules/ScrollPosition.jsmPK
!<N!modules/SearchStaticData.jsmPK
!<X6t99&1modules/SearchSuggestionController.jsmPK
!<|0;0;
kmodules/SelectContentHelper.jsmPK
!<GFFwmodules/SelectParentHelper.jsmPK
!<11XwwWmodules/ServiceRequest.jsmPK
!<Cmodules/Services.jsmPK
!<D~vC
modules/SharedPromptUtils.jsmPK
!<	Вymodules/ShimWaiver.jsmPK
!<>++? modules/ShortcutUtils.jsmPK
!<,Bn77"/modules/SimpleServiceDiscovery.jsmPK
!<1~fmodules/Sqlite.jsmPK
!<3Tss2modules/Subprocess.jsmPK
!<6e>DDKmodules/Task.jsmPK
!<luYYmodules/TelemetryArchive.jsmPK
!<ȱGmodules/TelemetryController.jsmPK
!<H)QQ 58modules/TelemetryEnvironment.jsmPK
!<''modules/TelemetryHealthPing.jsmPK
!<(;modules/TelemetryLog.jsmPK
!<z:\?modules/TelemetryModules.jsmPK
!<\!??$hLmodules/TelemetryReportingPolicy.jsmPK
!<Zamodules/TelemetrySend.jsmPK
!<bT#;#;?modules/TelemetrySession.jsmPK
!<;;'{modules/TelemetryStopwatch.jsmPK
!<$//ymodules/TelemetryStorage.jsmPK
!<Q.⼐modules/TelemetryTimestamps.jsmPK
!<mTT0Đmodules/TelemetryUtils.jsmPK
!<G(!modules/ThirdPartyCookieProbe.jsmPK
!<RE9zmodules/Timer.jsmPK
!< S^[[modules/Troubleshoot.jsmPK
!<9•Zmodules/UITelemetry.jsmPK
!<qrmodules/UpdateListener.jsmPK
!<B	smodules/UpdatePing.jsmPK
!<hNԜFF<modules/UpdateTelemetry.jsmPK
!<iкL0L0modules/UpdateUtils.jsmPK
!<	{@modules/UserAgentOverrides.jsmPK
!<&""'modules/UserAgentUpdates.jsmPK
!<Wr 		Jmodules/ValueExtractor.jsmPK
!<WS**Tmodules/ViewSourceBrowser.jsmPK
!<|0g,,;modules/WebChannel.jsmPK
!<PWA-8-8mmodules/WebNavigation.jsmPK
!<9MR//modules/WebNavigationContent.jsPK
!<7modules/WebNavigationFrames.jsmPK
!<7Tp?*%modules/WebRequest.jsmPK
!<a"ڮmodules/WebRequestCommon.jsmPK
!<vÓmodules/WebRequestContent.jsPK
!<S=;;ݓmodules/WebRequestUpload.jsmPK
!<s
s
modules/WindowDraggingUtils.jsmPK
!<>&modules/WindowsRegistry.jsmPK
!<SLL2modules/XPCOMUtils.jsmPK
!<^yymodules/ZipUtils.jsmPK
!<@h$Azz"fmodules/accessibility/AccessFu.jsmPK
!<_|((#Cmodules/accessibility/Constants.jsmPK
!<a[E[E(modules/accessibility/ContentControl.jsmPK
!<󂨗]]&M`modules/accessibility/EventManager.jsmPK
!<C}}"(modules/accessibility/Gestures.jsmPK
!<7⺅);modules/accessibility/OutputGenerator.jsmPK
!<5{{(modules/accessibility/PointerAdapter.jsmPK
!<[7M``&GՖmodules/accessibility/Presentation.jsmPK
!<*f&F0F0#[6modules/accessibility/Traversal.jsmPK
!<npAyAyfmodules/accessibility/Utils.jsmPK
!<>TMM'`modules/addons/APIExtensionBootstrap.jsPK
!<"modules/addons/AddonRepository.jsmPK
!<\SGG1Ҙmodules/addons/AddonRepository_SQLiteMigrator.jsmPK
!<
ܧ modules/addons/AddonSettings.jsmPK
!<I% modules/addons/AddonUpdateChecker.jsmPK
!<ۄJJ'modules/addons/Content.jsPK
!<P@$modules/addons/E10SAddonsRollout.jsmPK
!<=V[[modules/addons/GMPProvider.jsmPK
!<}1	modules/addons/LightweightThemeImageOptimizer.jsmPK
!<HAA!modules/addons/PluginProvider.jsmPK
!<99&_modules/addons/ProductAddonChecker.jsmPK
!<c=YPP/ modules/addons/SpellCheckDictionaryBootstrap.jsPK
!<  'modules/addons/WebExtensionBootstrap.jsPK
!<e\!xx"modules/addons/XPIInstall.jsmPK
!<Wrr'modules/addons/XPIProvider.jsmPK
!<VԘ"Кmodules/addons/XPIProviderUtils.jsPK
!<<=

 )modules/commonjs/dev/debuggee.jsPK
!<a$modules/commonjs/dev/frame-script.jsPK
!<5Ԙ"modules/commonjs/dev/panel/view.jsPK
!< κmodules/commonjs/dev/panel.jsPK
!<=sڠmodules/commonjs/dev/ports.jsPK
!<#modules/commonjs/dev/theme/hooks.jsPK
!<c3xmodules/commonjs/dev/theme.jsPK
!<")
)
modules/commonjs/dev/toolbox.jsPK
!<4;;modules/commonjs/dev/utils.jsPK
!<km11modules/commonjs/dev/volcan.jsPK
!<$modules/commonjs/diffpatcher/diff.jsPK
!<xx%modules/commonjs/diffpatcher/index.jsPK
!< OaZZ%modules/commonjs/diffpatcher/patch.jsPK
!<HR&)modules/commonjs/diffpatcher/rebase.jsPK
!<Vaѯ77+modules/commonjs/diffpatcher/test/common.jsPK
!<(ܦ		)modules/commonjs/diffpatcher/test/diff.jsPK
!<k||*	modules/commonjs/diffpatcher/test/index.jsPK
!<		*modules/commonjs/diffpatcher/test/patch.jsPK
!<,55(modules/commonjs/diffpatcher/test/tap.jsPK
!<TmW`3Xmodules/commonjs/framescript/FrameScriptManager.jsmPK
!<-#

(Mmodules/commonjs/framescript/content.jsmPK
!</!!,t&modules/commonjs/framescript/context-menu.jsPK
!<
	'DHmodules/commonjs/framescript/manager.jsPK
!<cA$\Mmodules/commonjs/framescript/util.jsPK
!<QPmodules/commonjs/index.jsPK
!<B$Qmodules/commonjs/jetpack-id/index.jsPK
!<p!!
Xmodules/commonjs/method/core.jsPK
!<?TT'[ymodules/commonjs/method/test/browser.jsPK
!<_1&{modules/commonjs/method/test/common.jsPK
!<$bxx4֚modules/commonjs/mozilla-toolkit-versioning/index.jsPK
!<q8modules/commonjs/mozilla-toolkit-versioning/lib/utils.jsPK
!< 	 	modules/commonjs/node/os.jsPK
!<$'`modules/commonjs/sdk/addon/bootstrap.jsPK
!<fw$ʥmodules/commonjs/sdk/addon/events.jsPK
!<J@@"Хmodules/commonjs/sdk/addon/host.jsPK
!<,@(

'iҥmodules/commonjs/sdk/addon/installer.jsPK
!<I33%modules/commonjs/sdk/addon/manager.jsPK
!<oj$modules/commonjs/sdk/addon/runner.jsPK
!<0({{$modules/commonjs/sdk/addon/window.jsPK
!<oxmodules/commonjs/sdk/base64.jsPK
!<Bv22&
modules/commonjs/sdk/browser/events.jsPK
!<&\&&!=modules/commonjs/sdk/clipboard.jsPK
!<<_vS	S	*5modules/commonjs/sdk/console/plain-text.jsPK
!<G`
E	E	)>modules/commonjs/sdk/console/traceback.jsPK
!<Vɛ ( (.9Hmodules/commonjs/sdk/content/content-worker.jsPK
!<<|'pmodules/commonjs/sdk/content/content.jsPK
!<t,t,,smodules/commonjs/sdk/content/context-menu.jsPK
!<	ii&Ɵmodules/commonjs/sdk/content/events.jsPK
!<c2V^^)smodules/commonjs/sdk/content/l10n-html.jsPK
!<8&modules/commonjs/sdk/content/loader.jsPK
!<Ns# ¦modules/commonjs/sdk/content/mod.jsPK
!<*ep(xɦmodules/commonjs/sdk/content/page-mod.jsPK
!<>>+tmodules/commonjs/sdk/content/page-worker.jsPK
!<u??.modules/commonjs/sdk/content/sandbox/events.jsPK
!<d99'modules/commonjs/sdk/content/sandbox.jsPK
!<ϱ*3modules/commonjs/sdk/content/tab-events.jsPK
!<&@@);modules/commonjs/sdk/content/thumbnail.jsPK
!<{%(Cmodules/commonjs/sdk/content/utils.jsPK
!<xx,YPmodules/commonjs/sdk/content/worker-child.jsPK
!<ɽ!&cmodules/commonjs/sdk/content/worker.jsPK
!<99,Fwmodules/commonjs/sdk/context-menu/context.jsPK
!<Zƪ33)Ɉmodules/commonjs/sdk/context-menu/core.jsPK
!<,UB,༧modules/commonjs/sdk/context-menu/readers.jsPK
!<oςς$0ȧmodules/commonjs/sdk/context-menu.jsPK
!<+pEE&AKmodules/commonjs/sdk/context-menu@2.jsPK
!<nA'Omodules/commonjs/sdk/core/disposable.jsPK
!<Cd}%gmodules/commonjs/sdk/core/heritage.jsPK
!<&ƀmodules/commonjs/sdk/core/namespace.jsPK
!<J{%놨modules/commonjs/sdk/core/observer.jsPK
!<H

$modules/commonjs/sdk/core/promise.jsPK
!<	&modules/commonjs/sdk/core/reference.jsPK
!<i[=[[,modules/commonjs/sdk/deprecated/api-utils.jsPK
!<jVъ3@modules/commonjs/sdk/deprecated/events/assembler.jsPK
!<"".ƨmodules/commonjs/sdk/deprecated/sync-worker.jsPK
!<pQ1134modules/commonjs/sdk/deprecated/unit-test-finder.jsPK
!<RBB,modules/commonjs/sdk/deprecated/unit-test.jsPK
!<Kkk/Dmodules/commonjs/sdk/deprecated/window-utils.jsPK
!<uUl'Zmodules/commonjs/sdk/dom/events/keys.jsPK
!<gg*(bmodules/commonjs/sdk/dom/events-shimmed.jsPK
!<I"emodules/commonjs/sdk/dom/events.jsPK
!<=o8$modules/commonjs/sdk/event/chrome.jsPK
!<oG+WW"modules/commonjs/sdk/event/core.jsPK
!<o+H

!0modules/commonjs/sdk/event/dom.jsPK
!<U-
-
$imodules/commonjs/sdk/event/target.jsPK
!<Zr#r##غmodules/commonjs/sdk/event/utils.jsPK
!<gT
T
*ީmodules/commonjs/sdk/frame/hidden-frame.jsPK
!<QX'

#'modules/commonjs/sdk/frame/utils.jsPK
!<Rl99Dmodules/commonjs/sdk/fs/path.jsPK
!<^l34modules/commonjs/sdk/hotkeys.jsPK
!<<":modules/commonjs/sdk/indexed-db.jsPK
!<l%Cmodules/commonjs/sdk/input/browser.jsPK
!<.i-\Omodules/commonjs/sdk/input/customizable-ui.jsPK
!< km

#Smodules/commonjs/sdk/input/frame.jsPK
!<V$amodules/commonjs/sdk/input/system.jsPK
!<**!smodules/commonjs/sdk/io/buffer.jsPK
!<P

'modules/commonjs/sdk/io/byte-streams.jsPK
!<-F::䩪modules/commonjs/sdk/io/file.jsPK
!<w==jj[modules/commonjs/sdk/io/fs.jsPK
!</66!G)modules/commonjs/sdk/io/stream.jsPK
!<{Vgg'`modules/commonjs/sdk/io/text-streams.jsPK
!<#=btt(~modules/commonjs/sdk/keyboard/hotkeys.jsPK
!<>"0``)rmodules/commonjs/sdk/keyboard/observer.jsPK
!</LL&modules/commonjs/sdk/keyboard/utils.jsPK
!<J0V9ZZ!modules/commonjs/sdk/l10n/core.jsPK
!<U!Bmodules/commonjs/sdk/l10n/html.jsPK
!<<KN&modules/commonjs/sdk/l10n/json/core.jsPK
!<p[		#modules/commonjs/sdk/l10n/loader.jsPK
!<\E#modules/commonjs/sdk/l10n/locale.jsPK
!<))K̫modules/commonjs/sdk/l10n/plural-rules.jsPK
!<ߨ"\modules/commonjs/sdk/l10n/prefs.jsPK
!<Q	^	^	,/modules/commonjs/sdk/l10n/properties/core.jsPK
!<TUmodules/commonjs/sdk/l10n.jsPK
!<tR	
	
2modules/commonjs/sdk/lang/functional/concurrent.jsPK
!<
W|%|%,Jmodules/commonjs/sdk/lang/functional/core.jsPK
!<	M/<modules/commonjs/sdk/lang/functional/helpers.jsPK
!<#L'w?modules/commonjs/sdk/lang/functional.jsPK
!<kbl"+"+!@Emodules/commonjs/sdk/lang/type.jsPK
!<y5++%pmodules/commonjs/sdk/lang/weak-set.jsPK
!<I **)xmodules/commonjs/sdk/loader/cuddlefish.jsPK
!<׼&modules/commonjs/sdk/loader/sandbox.jsPK
!<fY!Umodules/commonjs/sdk/messaging.jsPK
!<5'AWW",modules/commonjs/sdk/model/core.jsPK
!<(QN
N
Ômodules/commonjs/sdk/net/url.jsPK
!<Nmodules/commonjs/sdk/net/xhr.jsPK
!<YN%:modules/commonjs/sdk/notifications.jsPK
!<%_modules/commonjs/sdk/output/system.jsPK
!<7$._modules/commonjs/sdk/page-mod/match-pattern.jsPK
!<** ^modules/commonjs/sdk/page-mod.jsPK
!<i2ss#٬modules/commonjs/sdk/page-worker.jsPK
!<PLL$zmodules/commonjs/sdk/panel/events.jsPK
!<dҧF9F9#modules/commonjs/sdk/panel/utils.jsPK
!<`44,modules/commonjs/sdk/panel.jsPK
!<	J}|SS'amodules/commonjs/sdk/passwords/utils.jsPK
!<Kę!Gpmodules/commonjs/sdk/passwords.jsPK
!<XH+H+(twmodules/commonjs/sdk/places/bookmarks.jsPK
!<ԕ44'modules/commonjs/sdk/places/contract.jsPK
!<΂%{modules/commonjs/sdk/places/events.jsPK
!<3&Amodules/commonjs/sdk/places/favicon.jsPK
!<~&­modules/commonjs/sdk/places/history.jsPK
!<&nJ2ʭmodules/commonjs/sdk/places/host/host-bookmarks.jsPK
!<
'"".modules/commonjs/sdk/places/host/host-query.jsPK
!<PK-bmodules/commonjs/sdk/places/host/host-tags.jsPK
!<&SS--$modules/commonjs/sdk/places/utils.jsPK
!<!5""&!modules/commonjs/sdk/platform/xpcom.jsPK
!<MK50Cmodules/commonjs/sdk/preferences/event-target.jsPK
!<fxa2Kmodules/commonjs/sdk/preferences/native-options.jsPK
!<Zww+dmodules/commonjs/sdk/preferences/service.jsPK
!<)smodules/commonjs/sdk/preferences/utils.jsPK
!<Yp.ymodules/commonjs/sdk/private-browsing/utils.jsPK
!<Pgg(modules/commonjs/sdk/private-browsing.jsPK
!<[p99#_modules/commonjs/sdk/querystring.jsPK
!<0͈$ْmodules/commonjs/sdk/remote/child.jsPK
!<b66#modules/commonjs/sdk/remote/core.jsPK
!<p>*(*(%modules/commonjs/sdk/remote/parent.jsPK
!<UAj$ܮmodules/commonjs/sdk/remote/utils.jsPK
!<˝yyRmodules/commonjs/sdk/request.jsPK
!<>]99!modules/commonjs/sdk/selection.jsPK
!<@7?8modules/commonjs/sdk/self.jsPK
!<Y+$Amodules/commonjs/sdk/simple-prefs.jsPK
!<$$&Dmodules/commonjs/sdk/simple-storage.jsPK
!<U(`modules/commonjs/sdk/stylesheet/style.jsPK
!<[[(Ehmodules/commonjs/sdk/stylesheet/utils.jsPK
!<!N7omodules/commonjs/sdk/system/child_process/subprocess.jsPK
!<t&##,ꀯmodules/commonjs/sdk/system/child_process.jsPK
!<"*modules/commonjs/sdk/system/environment.jsPK
!<'7PSS-amodules/commonjs/sdk/system/events-shimmed.jsPK
!<!FS3!!%modules/commonjs/sdk/system/events.jsPK
!<ZSS&cïmodules/commonjs/sdk/system/globals.jsPK
!<\-&ȯmodules/commonjs/sdk/system/process.jsPK
!<ii&ϯmodules/commonjs/sdk/system/runtime.jsPK
!<T>

%үmodules/commonjs/sdk/system/unload.jsPK
!<zz&ݯmodules/commonjs/sdk/system/xul-app.jsPK
!<$kk'߯modules/commonjs/sdk/system/xul-app.jsmPK
!<&Lmodules/commonjs/sdk/system.jsPK
!<4p
"'modules/commonjs/sdk/tab/events.jsPK
!<ԦVuu#/modules/commonjs/sdk/tabs/common.jsPK
!<}ʰ##modules/commonjs/sdk/tabs/events.jsPK
!<}tt$'modules/commonjs/sdk/tabs/helpers.jsPK
!<nNN&*modules/commonjs/sdk/tabs/namespace.jsPK
!<%,modules/commonjs/sdk/tabs/observer.jsPK
!<GzAh';modules/commonjs/sdk/tabs/tab-fennec.jsPK
!<0##(Vmodules/commonjs/sdk/tabs/tab-firefox.jsPK
!<Vd0 ,zmodules/commonjs/sdk/tabs/tab.jsPK
!<0)]}modules/commonjs/sdk/tabs/tabs-firefox.jsPK
!<[:4&&"1modules/commonjs/sdk/tabs/utils.jsPK
!<3#smodules/commonjs/sdk/tabs/worker.jsPK
!<Omodules/commonjs/sdk/tabs.jsPK
!<mZ&&&&#modules/commonjs/sdk/test/assert.jsPK
!<gGG$ްmodules/commonjs/sdk/test/harness.jsPK
!<F[["b%modules/commonjs/sdk/test/httpd.jsPK
!<#&modules/commonjs/sdk/test/loader.jsPK
!<fDbb#6modules/commonjs/sdk/test/memory.jsPK
!<qI=		$u8modules/commonjs/sdk/test/options.jsPK
!<,П#<modules/commonjs/sdk/test/runner.jsPK
!<*bv,,"Kmodules/commonjs/sdk/test/utils.jsPK
!<`0ttemodules/commonjs/sdk/test.jsPK
!<*NNvmodules/commonjs/sdk/timers.jsPK
!<DqБ(Dmodules/commonjs/sdk/ui/button/action.jsPK
!<ɕ*modules/commonjs/sdk/ui/button/contract.jsPK
!<,e(Nmodules/commonjs/sdk/ui/button/toggle.jsPK
!<?-modules/commonjs/sdk/ui/button/view/events.jsPK
!<{ww&{modules/commonjs/sdk/ui/button/view.jsPK
!<_j;;$6modules/commonjs/sdk/ui/component.jsPK
!<skgg&ױmodules/commonjs/sdk/ui/frame/model.jsPK
!<AKb``'^modules/commonjs/sdk/ui/frame/view.htmlPK
!<p;%modules/commonjs/sdk/ui/frame/view.jsPK
!<*̀ modules/commonjs/sdk/ui/frame.jsPK
!<Omodules/commonjs/sdk/ui/id.jsPK
!<nn*modules/commonjs/sdk/ui/sidebar/actions.jsPK
!<LEE+modules/commonjs/sdk/ui/sidebar/contract.jsPK
!<H9,D
modules/commonjs/sdk/ui/sidebar/namespace.jsPK
!<766(modules/commonjs/sdk/ui/sidebar/utils.jsPK
!<I=I'modules/commonjs/sdk/ui/sidebar/view.jsPK
!<D(("&modules/commonjs/sdk/ui/sidebar.jsPK
!<?'iNmodules/commonjs/sdk/ui/state/events.jsPK
!<
H CPmodules/commonjs/sdk/ui/state.jsPK
!<Cq(_lmodules/commonjs/sdk/ui/toolbar/model.jsPK
!<I5%%'~modules/commonjs/sdk/ui/toolbar/view.jsPK
!<<h"modules/commonjs/sdk/ui/toolbar.jsPK
!<LN^##Хmodules/commonjs/sdk/ui.jsPK
!<#)$+modules/commonjs/sdk/uri/resource.jsPK
!<7I[77!Gmodules/commonjs/sdk/url/utils.jsPK
!<W""modules/commonjs/sdk/url.jsPK
!<144"Բmodules/commonjs/sdk/util/array.jsPK
!<u~'Gmodules/commonjs/sdk/util/collection.jsPK
!<
q%Gmodules/commonjs/sdk/util/contract.jsPK
!<M&modules/commonjs/sdk/util/deprecate.jsPK
!<bb'modules/commonjs/sdk/util/dispatcher.jsPK
!<\F		!0modules/commonjs/sdk/util/list.jsPK
!<&S*modules/commonjs/sdk/util/match-pattern.jsPK
!<33#Emodules/commonjs/sdk/util/object.jsPK
!< ",modules/commonjs/sdk/util/rules.jsPK
!<zE@@%2modules/commonjs/sdk/util/sequence.jsPK
!<_!smodules/commonjs/sdk/util/uuid.jsPK
!<+!vmodules/commonjs/sdk/view/core.jsPK
!<9ʃ$zmodules/commonjs/sdk/webextension.jsPK
!<oUU&Omodules/commonjs/sdk/window/browser.jsPK
!<0ak
k
%至modules/commonjs/sdk/window/events.jsPK
!<^r&modules/commonjs/sdk/window/helpers.jsPK
!<h(modules/commonjs/sdk/window/namespace.jsPK
!<sko6o6$Mmodules/commonjs/sdk/window/utils.jsPK
!<%')
)
&ҳmodules/commonjs/sdk/windows/fennec.jsPK
!<.<iZss'kݳmodules/commonjs/sdk/windows/firefox.jsPK
!<U
(#modules/commonjs/sdk/windows/observer.jsPK
!<m//+@modules/commonjs/sdk/windows/tabs-fennec.jsPK
!<WTmodules/commonjs/sdk/windows.jsPK
!<Jw$modules/commonjs/sdk/worker/utils.jsPK
!<̭!Hmodules/commonjs/sdk/zip/utils.jsPK
!<S55modules/commonjs/test.jsPK
!<8*3E"modules/commonjs/toolkit/loader.jsPK
!<Np#;modules/commonjs/toolkit/require.jsPK
!<d#

vδmodules/css-selector.jsPK
!<KmUU0ܴmodules/ctypes.jsmPK
!<H3u

ߴmodules/debug.jsPK
!<Y{ޙ--modules/devtools/Console.jsmPK
!<_%eecmodules/devtools/Loader.jsmPK
!<H+modules/devtools/dbg-client.jsmPK
!<o(hh;modules/devtools/dbg-server.jsmPK
!<w!modules/devtools/event-emitter.jsPK
!<_%ee"modules/devtools/shared/Loader.jsmPK
!<8Z
L	modules/jsdebugger.jsmPK
!<Ð7modules/lz4.jsPK
!<ʞd>HHv,modules/lz4_internal.jsPK
!<MbS[""3modules/media/IdpSandbox.jsmPK
!<e++#Vmodules/media/PeerConnectionIdp.jsmPK
!<FG modules/media/RTCStatsReport.jsmPK
!<2c۰##Ȇmodules/microformat-shiv.jsPK
!<?5L.L.#modules/narrate/NarrateControls.jsmPK
!<P|11<ٷmodules/narrate/Narrator.jsmPK
!<W:77Wmodules/narrate/VoiceSelect.jsmPK
!<:ۅ$)modules/nsFormAutoCompleteResult.jsmPK
!<Tg%?modules/osfile/osfile_async_front.jsmPK
!<[2[2%d
modules/osfile/osfile_async_worker.jsPK
!<2 =modules/osfile/osfile_native.jsmPK
!<2{MZZ+;Fmodules/osfile/osfile_shared_allthreads.jsmPK
!<
(>J>J&չmodules/osfile/osfile_shared_front.jsmPK
!<I.I.(` modules/osfile/osfile_win_allthreads.jsmPK
!<:;TJWDWD"Nmodules/osfile/osfile_win_back.jsmPK
!<.n;#modules/osfile/osfile_win_front.jsmPK
!<1	GGZmodules/osfile/ospath.jsmPK
!<˟(`modules/osfile/ospath_unix.jsmPK
!<x[{'{'vmodules/osfile/ospath_win.jsmPK
!<y&&modules/osfile.jsmPK
!<(G^^/modules/presentation/ControllerStateMachine.jsmPK
!<-modules/presentation/ReceiverStateMachine.jsmPK
!<)@+ۻmodules/presentation/StateMachineHelper.jsmPK
!<4tVQLjLj$modules/reader/JSDOMParser.jsPK
!<&jmodules/reader/Readability.jsPK
!<$\4modules/reader/ReaderWorker.jsPK
!<

Dmodules/reader/ReaderWorker.jsmPK
!<y";;modules/reflect.jsmPK
!<^((3(3modules/sdk/bootstrap.jsPK
!<q((Uǽmodules/sdk/system/Startup.jsPK
!<aڳ   νmodules/services-common/async.jsPK
!<Y*DD,modules/services-common/blocklist-clients.jsPK
!<S,4modules/services-common/blocklist-updater.jsPK
!<q00%Qmodules/services-common/hawkclient.jsPK
!<#QL&Ƃmodules/services-common/hawkrequest.jsPK
!<f<,˛modules/services-common/kinto-http-client.jsPK
!<vo`>`>/)modules/services-common/kinto-offline-client.jsPK
!<:S880Jhmodules/services-common/kinto-storage-adapter.jsPK
!<11%kmodules/services-common/logmanager.jsPK
!<BPZ--$modules/services-common/observers.jsPK
!<#
Z
Zmodules/services-common/rest.jsPK
!<donA
A
A,ICmodules/services-common/tokenserverclient.jsPK
!<n9RR+modules/services-common/uptake-telemetry.jsPK
!<C4GG 8modules/services-common/utils.jsPK
!<xcc&modules/services-crypto/WeaveCrypto.jsPK
!<bl``$4modules/services-crypto/jwcrypto.jsmPK
!<"*MM modules/services-crypto/utils.jsPK
!<go%%$]modules/services-sync/SyncedTabs.jsmPK
!<ɕ!modules/services-sync/UIState.jsmPK
!<HmMmM)lmodules/services-sync/addonsreconciler.jsPK
!<;
44# modules/services-sync/addonutils.jsPK
!<*arar(^$modules/services-sync/bookmark_repair.jsPK
!<eHzz+modules/services-sync/bookmark_validator.jsPK
!< q+modules/services-sync/browserid_identity.jsPK
!<>s99*modules/services-sync/collection_repair.jsPK
!<k<ff-jmodules/services-sync/collection_validator.jsPK
!<}
H!H!"modules/services-sync/constants.jsPK
!<t%%modules/services-sync/doctor.jsPK
!<1dd'modules/services-sync/engines/addons.jsPK
!<q*lmodules/services-sync/engines/bookmarks.jsPK
!<nv''(modules/services-sync/engines/clients.jsPK
!<w2\modules/services-sync/engines/extension-storage.jsPK
!<	"LL&Vmodules/services-sync/engines/forms.jsPK
!<7X1X1(modules/services-sync/engines/history.jsPK
!<l:|11*modules/services-sync/engines/passwords.jsPK
!<&modules/services-sync/engines/prefs.jsPK
!<}Qݕ//%n0modules/services-sync/engines/tabs.jsPK
!<m55 F`modules/services-sync/engines.jsPK
!<TjjQmodules/services-sync/keys.jsPK
!<+^bmodules/services-sync/main.jsPK
!<pA!gmodules/services-sync/policies.jsPK
!<XJ<modules/services-sync/record.jsPK
!<jII!7smodules/services-sync/resource.jsPK
!<]%יmodules/services-sync/rest.jsPK
!<cI pmodules/services-sync/service.jsPK
!<S

(modules/services-sync/stages/declined.jsPK
!<*00*modules/services-sync/stages/enginesync.jsPK
!<;::kR
R
modules/services-sync/status.jsPK
!<F0vZZ"bmodules/services-sync/telemetry.jsPK
!<QQ-modules/services-sync/util.jsPK
!<<u1'%~modules/sessionstore/PrivacyLevel.jsmPK
!<6DdLL'Յmodules/sessionstore/SessionHistory.jsmPK
!<oFFmodules/sessionstore/Utils.jsmPK
!<(EۦQQ(Jmodules/subprocess/subprocess_common.jsmPK
!<'8		'65modules/subprocess/subprocess_shared.jsPK
!</z(5(5+y?modules/subprocess/subprocess_shared_win.jsPK
!<@or..%tmodules/subprocess/subprocess_win.jsmPK
!<y)".[modules/subprocess/subprocess_worker_common.jsPK
!<`-FF+wmodules/subprocess/subprocess_worker_win.jsPK
!<YW!!"amodules/third_party/jsesc/jsesc.jsPK
!<Smodules/vtt.jsmPK
!<V\\ modules/workers/PromiseWorker.jsPK
!<Ixxmodules/workers/require.jsPK
!<Ү-k-k*gcomponents/nsUrlClassifierHashCompleter.jsPK
!<ι]](Ecomponents/nsUrlClassifierListManager.jsPK
!<U>> )components/nsUrlClassifierLib.jsPK
!<;UU8components/PrivateBrowsingTrackingProtectionWhitelist.jsPK
!<8ůPcomponents/SecurityReporter.jsPK
!<+55+9chrome/toolkit/content/extensions/dummy.xulPK
!<?g1	>>/chrome/toolkit/content/extensions/ext-alarms.jsPK
!<!e17Bchrome/toolkit/content/extensions/ext-backgroundPage.jsPK
!<Y+Y+8ichrome/toolkit/content/extensions/ext-browser-content.jsPK
!<#}tii8chrome/toolkit/content/extensions/ext-browserSettings.jsPK
!<669 chrome/toolkit/content/extensions/ext-c-backgroundPage.jsPK
!<g4d#chrome/toolkit/content/extensions/ext-c-extension.jsPK
!<i3J)chrome/toolkit/content/extensions/ext-c-identity.jsPK
!<R2'.chrome/toolkit/content/extensions/ext-c-runtime.jsPK
!<"2d@chrome/toolkit/content/extensions/ext-c-storage.jsPK
!<2άڒ/Vchrome/toolkit/content/extensions/ext-c-test.jsPK
!<Q		2ichrome/toolkit/content/extensions/ext-c-toolkit.jsPK
!<=schrome/toolkit/content/extensions/ext-contextualIdentities.jsPK
!<880ichrome/toolkit/content/extensions/ext-cookies.jsPK
!<&ii2chrome/toolkit/content/extensions/ext-downloads.jsPK
!<)92'chrome/toolkit/content/extensions/ext-extension.jsPK
!<1]]4(chrome/toolkit/content/extensions/ext-geolocation.jsPK
!<sT^^--chrome/toolkit/content/extensions/ext-i18n.jsPK
!<j8L152chrome/toolkit/content/extensions/ext-identity.jsPK
!<-sDchrome/toolkit/content/extensions/ext-idle.jsPK
!<
I!!3IPchrome/toolkit/content/extensions/ext-management.jsPK
!<hRx6Frchrome/toolkit/content/extensions/ext-notifications.jsPK
!<0

4%chrome/toolkit/content/extensions/ext-permissions.jsPK
!<YKK0chrome/toolkit/content/extensions/ext-privacy.jsPK
!<LZ
Z
9chrome/toolkit/content/extensions/ext-protocolHandlers.jsPK
!<u.Achrome/toolkit/content/extensions/ext-proxy.jsPK
!<찟0Dchrome/toolkit/content/extensions/ext-runtime.jsPK
!<˱||01chrome/toolkit/content/extensions/ext-storage.jsPK
!<f2!!.chrome/toolkit/content/extensions/ext-theme.jsPK
!<0,0	chrome/toolkit/content/extensions/ext-toolkit.jsPK
!<v(==16chrome/toolkit/content/extensions/ext-topSites.jsPK
!<96$chrome/toolkit/content/extensions/ext-webNavigation.jsPK
!<J3?chrome/toolkit/content/extensions/ext-webRequest.jsPK
!<{5Rchrome/toolkit/content/extensions/schemas/alarms.jsonPK
!<Y?+gchrome/toolkit/content/extensions/schemas/browser_settings.jsonPK
!<D~jchrome/toolkit/content/extensions/schemas/contextual_identities.jsonPK
!<lx226{chrome/toolkit/content/extensions/schemas/cookies.jsonPK
!<LD~mm8®chrome/toolkit/content/extensions/schemas/downloads.jsonPK
!<az445chrome/toolkit/content/extensions/schemas/events.jsonPK
!<2\:Rchrome/toolkit/content/extensions/schemas/experiments.jsonPK
!<Uy8QSchrome/toolkit/content/extensions/schemas/extension.jsonPK
!<qqJpchrome/toolkit/content/extensions/schemas/extension_protocol_handlers.jsonPK
!<&ZZ>[xchrome/toolkit/content/extensions/schemas/extension_types.jsonPK
!<B)3chrome/toolkit/content/extensions/schemas/i18n.jsonPK
!<?sVrr7jchrome/toolkit/content/extensions/schemas/identity.jsonPK
!<(31chrome/toolkit/content/extensions/schemas/idle.jsonPK
!<1!)!)9vchrome/toolkit/content/extensions/schemas/management.jsonPK
!<]Ѫ//7chrome/toolkit/content/extensions/schemas/manifest.jsonPK
!<"pXCchrome/toolkit/content/extensions/schemas/native_host_manifest.jsonPK
!<+33<Zchrome/toolkit/content/extensions/schemas/notifications.jsonPK
!<EQ:Nchrome/toolkit/content/extensions/schemas/permissions.jsonPK
!<Z6x]chrome/toolkit/content/extensions/schemas/privacy.jsonPK
!<T4pmchrome/toolkit/content/extensions/schemas/proxy.jsonPK
!<#`}h}h6ytchrome/toolkit/content/extensions/schemas/runtime.jsonPK
!<ZFu((6Jchrome/toolkit/content/extensions/schemas/storage.jsonPK
!<O63@chrome/toolkit/content/extensions/schemas/test.jsonPK
!<x5549chrome/toolkit/content/extensions/schemas/theme.jsonPK
!<D),,89Schrome/toolkit/content/extensions/schemas/top_sites.jsonPK
!<#PP4[chrome/toolkit/content/extensions/schemas/types.jsonPK
!<8LL=]wchrome/toolkit/content/extensions/schemas/web_navigation.jsonPK
!<2٦:ochrome/toolkit/content/extensions/schemas/web_request.jsonPK
!<YOkz2Ychrome/toolkit/content/gfxsanity/gfxFrameScript.jsPK
!<#-3662'achrome/toolkit/content/gfxsanity/sanityparent.htmlPK
!<-0achrome/toolkit/content/gfxsanity/sanitytest.htmlPK
!<	.bchrome/toolkit/content/gfxsanity/videotest.mp4PK
!<4hchrome/toolkit/content/global/BrowserElementChild.jsPK
!<z(;qchrome/toolkit/content/global/BrowserElementChildPreload.jsPK
!<ZK&&8:@chrome/toolkit/content/global/BrowserElementCopyPaste.jsPK
!<*6Ochrome/toolkit/content/global/TopLevelVideoDocument.jsPK
!<⭝1Vchrome/toolkit/content/global/XPCNativeWrapper.jsPK
!<qyEE&Wchrome/toolkit/content/global/about.jsPK
!< lvv){]chrome/toolkit/content/global/about.xhtmlPK
!<Od+8dchrome/toolkit/content/global/aboutAbout.jsPK
!<A؍.jchrome/toolkit/content/global/aboutAbout.xhtmlPK
!<Mhm+nchrome/toolkit/content/global/aboutCache.jsPK
!<hSa3uchrome/toolkit/content/global/aboutCheckerboard.cssPK
!<y}2xchrome/toolkit/content/global/aboutCheckerboard.jsPK
!<;;5chrome/toolkit/content/global/aboutCheckerboard.xhtmlPK
!<T		-Lchrome/toolkit/content/global/aboutMemory.cssPK
!<

,chrome/toolkit/content/global/aboutMemory.jsPK
!<X0PP/chrome/toolkit/content/global/aboutMemory.xhtmlPK
!<da>;>;0Vchrome/toolkit/content/global/aboutNetworking.jsPK
!<b]))3chrome/toolkit/content/global/aboutNetworking.xhtmlPK
!<9	''1chrome/toolkit/content/global/aboutPerformance.jsPK
!<4Xchrome/toolkit/content/global/aboutPerformance.xhtmlPK
!<@(@(.fchrome/toolkit/content/global/aboutProfiles.jsPK
!<ف2WW1chrome/toolkit/content/global/aboutProfiles.xhtmlPK
!<4m``/chrome/toolkit/content/global/aboutRights.xhtmlPK
!<"^A4Echrome/toolkit/content/global/aboutServiceWorkers.jsPK
!<i!!!7	chrome/toolkit/content/global/aboutServiceWorkers.xhtmlPK
!<Q͞͞-)chrome/toolkit/content/global/aboutSupport.jsPK
!<5V@@0Achrome/toolkit/content/global/aboutSupport.xhtmlPK
!<z0uchrome/toolkit/content/global/aboutTelemetry.cssPK
!<@-11/chrome/toolkit/content/global/aboutTelemetry.jsPK
!<̨}##27chrome/toolkit/content/global/aboutTelemetry.xhtmlPK
!< 4[chrome/toolkit/content/global/aboutUrlClassifier.cssPK
!<E<<3achrome/toolkit/content/global/aboutUrlClassifier.jsPK
!<??6ڞchrome/toolkit/content/global/aboutUrlClassifier.xhtmlPK
!<K
9mchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.cssPK
!<FS~`:Fchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.htmlPK
!<J(\\8Jchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.jsPK
!<	J8Mchrome/toolkit/content/global/accessibility/AccessFu.cssPK
!<7Lchrome/toolkit/content/global/accessibility/clicked.oggPK
!<PUU={6chrome/toolkit/content/global/accessibility/content-script.jsPK
!<D1B+Jchrome/toolkit/content/global/accessibility/virtual_cursor_key.oggPK
!<C[chrome/toolkit/content/global/accessibility/virtual_cursor_move.oggPK
!<	.pqchrome/toolkit/content/global/alerts/alert.cssPK
!<	l0l0-tchrome/toolkit/content/global/alerts/alert.jsPK
!<9n

.]chrome/toolkit/content/global/alerts/alert.xulPK
!<vC*¯chrome/toolkit/content/global/appPicker.jsPK
!<	11+chrome/toolkit/content/global/appPicker.xulPK
!<	].chrome/toolkit/content/global/autocomplete.cssPK
!<;+8chrome/toolkit/content/global/backgroundPageThumbs.xhtmlPK
!<	7>{{<'chrome/toolkit/content/global/backgroundPageThumbsContent.jsPK
!<n{YY7chrome/toolkit/content/global/bindings/autocomplete.xmlPK
!<2rchrome/toolkit/content/global/bindings/browser.xmlPK
!<F661\chrome/toolkit/content/global/bindings/button.xmlPK
!≮=2&chrome/toolkit/content/global/bindings/calendar.jsPK
!<Kg

3chrome/toolkit/content/global/bindings/checkbox.xmlPK
!<CȀLL6Kchrome/toolkit/content/global/bindings/colorpicker.xmlPK
!<Ӷ˱((4lchrome/toolkit/content/global/bindings/datekeeper.jsPK
!<e,e,4o'chrome/toolkit/content/global/bindings/datepicker.jsPK
!<m)tt6&Tchrome/toolkit/content/global/bindings/datetimebox.cssPK
!<r	R776Ychrome/toolkit/content/global/bindings/datetimebox.xmlPK
!<LE9y8chrome/toolkit/content/global/bindings/datetimepicker.xmlPK
!<ttg008chrome/toolkit/content/global/bindings/datetimepopup.xmlPK
!<gVk>>1#chrome/toolkit/content/global/bindings/dialog.xmlPK
!<5_||1cchrome/toolkit/content/global/bindings/editor.xmlPK
!<_8bA3chrome/toolkit/content/global/bindings/expander.xmlPK
!<Mː4؍chrome/toolkit/content/global/bindings/filefield.xmlPK
!<F%2chrome/toolkit/content/global/bindings/findbar.xmlPK
!<dm!!2#bchrome/toolkit/content/global/bindings/general.xmlPK
!<#3Cchrome/toolkit/content/global/bindings/groupbox.xmlPK
!<2.chrome/toolkit/content/global/bindings/listbox.xmlPK
!<Qqz!+!+/$chrome/toolkit/content/global/bindings/menu.xmlPK
!<>wSwS3Jchrome/toolkit/content/global/bindings/menulist.xmlPK
!<u[u[7Zchrome/toolkit/content/global/bindings/notification.xmlPK
!<0v##4$chrome/toolkit/content/global/bindings/numberbox.xmlPK
!<5odod0chrome/toolkit/content/global/bindings/popup.xmlPK
!<:6Gchrome/toolkit/content/global/bindings/preferences.xmlPK
!<MM8Lchrome/toolkit/content/global/bindings/progressmeter.xmlPK
!<9!/B/B0:]chrome/toolkit/content/global/bindings/radio.xmlPK
!<&SS9chrome/toolkit/content/global/bindings/remote-browser.xmlPK
!<z:ff2chrome/toolkit/content/global/bindings/resizer.xmlPK
!<H\UPUP6chrome/toolkit/content/global/bindings/richlistbox.xmlPK
!<\1 1 0}Ichrome/toolkit/content/global/bindings/scale.xmlPK
!<oo4ichrome/toolkit/content/global/bindings/scrollbar.xmlPK
!<ݠݠ4qchrome/toolkit/content/global/bindings/scrollbox.xmlPK
!<6chrome/toolkit/content/global/bindings/spinbuttons.xmlPK
!<B	GG1[!chrome/toolkit/content/global/bindings/spinner.jsPK
!<I__3Pichrome/toolkit/content/global/bindings/splitter.xmlPK
!<$J

7nchrome/toolkit/content/global/bindings/stringbundle.xmlPK
!<wooo1Eychrome/toolkit/content/global/bindings/tabbox.xmlPK
!<V!0909/chrome/toolkit/content/global/bindings/text.xmlPK
!<Fbgbg2,"chrome/toolkit/content/global/bindings/textbox.xmlPK
!<<k!..4މchrome/toolkit/content/global/bindings/timekeeper.jsPK
!<UWS S 4chrome/toolkit/content/global/bindings/timepicker.jsPK
!<)u*O*O2chrome/toolkit/content/global/bindings/toolbar.xmlPK
!<^^8)chrome/toolkit/content/global/bindings/toolbarbutton.xmlPK
!<%/?chrome/toolkit/content/global/bindings/tree.xmlPK
!<e#558chrome/toolkit/content/global/bindings/videocontrols.cssPK
!<@D@D8<!chrome/toolkit/content/global/bindings/videocontrols.xmlPK
!<V`I`I1echrome/toolkit/content/global/bindings/wizard.xmlPK
!<]jUCNCN.chrome/toolkit/content/global/browser-child.jsPK
!<;⻔0chrome/toolkit/content/global/browser-content.jsPK
!<.r.chrome/toolkit/content/global/buildconfig.htmlPK
!<Pu=66.chrome/toolkit/content/global/commonDialog.cssPK
!<
}'		-jchrome/toolkit/content/global/commonDialog.jsPK
!<.chrome/toolkit/content/global/commonDialog.xulPK
!<:QII'chrome/toolkit/content/global/config.jsPK
!<xxq9(
cchrome/toolkit/content/global/config.xulPK
!<3n1uchrome/toolkit/content/global/contentAreaUtils.jsPK
!<!5%%(5chrome/toolkit/content/global/crashes.jsPK
!<+Mchrome/toolkit/content/global/crashes.xhtmlPK
!<-(2sYchrome/toolkit/content/global/customizeToolbar.cssPK
!<rbt`t`1[]chrome/toolkit/content/global/customizeToolbar.jsPK
!<ڽW		2chrome/toolkit/content/global/customizeToolbar.xulPK
!<0ܣY
Y
.Gchrome/toolkit/content/global/datepicker.xhtmlPK
!<ճ4chrome/toolkit/content/global/directionDetector.htmlPK
!<$Zdd0chrome/toolkit/content/global/editMenuOverlay.jsPK
!<zz1chrome/toolkit/content/global/editMenuOverlay.xulPK
!<\83_chrome/toolkit/content/global/filepicker.propertiesPK
!<&}*chrome/toolkit/content/global/findUtils.jsPK
!<>1+ychrome/toolkit/content/global/finddialog.jsPK
!<q,chrome/toolkit/content/global/finddialog.xulPK
!<9'Ax.chrome/toolkit/content/global/globalOverlay.jsPK
!<gOI
I
7_2chrome/toolkit/content/global/gmp-sources/openh264.jsonPK
!<B$Luu:?chrome/toolkit/content/global/gmp-sources/widevinecdm.jsonPK
!<5rr*Hchrome/toolkit/content/global/license.htmlPK
!<	HD1chrome/toolkit/content/global/manifestMessages.jsPK
!<KZ*chrome/toolkit/content/global/menulist.cssPK
!<KDž-xchrome/toolkit/content/global/minimal-xul.cssPK
!<q+ychrome/toolkit/content/global/mozilla.xhtmlPK
!<qv?v?,}chrome/toolkit/content/global/netError.xhtmlPK
!<1*="chrome/toolkit/content/global/notfound.wavPK
!<w=w=6(chrome/toolkit/content/global/platformHTMLBindings.xmlPK
!<S)echrome/toolkit/content/global/plugins.cssPK
!<#닐""*kchrome/toolkit/content/global/plugins.htmlPK
!<I
V?V?/chrome/toolkit/content/global/printPageSetup.jsPK
!<F&))0chrome/toolkit/content/global/printPageSetup.xulPK
!<V"DD6rchrome/toolkit/content/global/printPreviewBindings.xmlPK
!<5o=chrome/toolkit/content/global/printPreviewProgress.jsPK
!<8.

6Nchrome/toolkit/content/global/printPreviewProgress.xulPK
!<!!.3Tchrome/toolkit/content/global/printProgress.jsPK
!<w/Lvchrome/toolkit/content/global/printProgress.xulPK
!<gugu+~chrome/toolkit/content/global/printUtils.jsPK
!<lh
h
0\chrome/toolkit/content/global/process-content.jsPK
!<I		5chrome/toolkit/content/global/reader/aboutReader.htmlPK
!<Ztt3	chrome/toolkit/content/global/reader/aboutReader.jsPK
!<B90
chrome/toolkit/content/global/remote-test-ipc.jsPK
!<{Vޡ__.chrome/toolkit/content/global/resetProfile.cssPK
!<
-]chrome/toolkit/content/global/resetProfile.jsPK
!<=zrr.-chrome/toolkit/content/global/resetProfile.xulPK
!<yvv6chrome/toolkit/content/global/resetProfileProgress.xulPK
!<}*- chrome/toolkit/content/global/selectDialog.jsPK
!<$$.(chrome/toolkit/content/global/selectDialog.xulPK
!<|.+,chrome/toolkit/content/global/simplifyMode.cssPK
!<V,0chrome/toolkit/content/global/tabprompts.cssPK
!<W_P88,3chrome/toolkit/content/global/tabprompts.xmlPK
!<e***0lchrome/toolkit/content/global/test-ipc.xulPK
!<c)1chrome/toolkit/content/global/textbox.cssPK
!<Y##.Jchrome/toolkit/content/global/timepicker.xhtmlPK
!<,b*chrome/toolkit/content/global/treeUtils.jsPK
!<^△bb;chrome/toolkit/content/global/unifiedcomplete-top-urls.jsonPK
!<Q:{chrome/toolkit/content/global/url-classifier/unittests.xulPK
!<	N2chrome/toolkit/content/global/viewPartialSource.jsPK
!<3'chrome/toolkit/content/global/viewPartialSource.xulPK
!<%~%~3chrome/toolkit/content/global/viewSource-content.jsPK
!<LL,nchrome/toolkit/content/global/viewSource.cssPK
!<Vqrr+%pchrome/toolkit/content/global/viewSource.jsPK
!<F++,tchrome/toolkit/content/global/viewSource.xulPK
!<y0VV0
chrome/toolkit/content/global/viewSourceUtils.jsPK
!<0dchrome/toolkit/content/global/viewZoomOverlay.jsPK
!<e))%qchrome/toolkit/content/global/win.xulPK
!<jhh21rchrome/toolkit/content/global/xml/XMLMonoPrint.cssPK
!<r4schrome/toolkit/content/global/xml/XMLPrettyPrint.cssPK
!<Z2ii4Uwchrome/toolkit/content/global/xml/XMLPrettyPrint.xmlPK
!<)\4}chrome/toolkit/content/global/xml/XMLPrettyPrint.xslPK
!<cmm%gchrome/toolkit/content/global/xul.cssPK
!<B%

?pchrome/toolkit/content/mozapps/downloads/unknownContentType.xulPK
!<@T??>	chrome/toolkit/content/mozapps/extensions/OpenH264-license.txtPK
!<f

2>chrome/toolkit/content/mozapps/extensions/about.jsPK
!<a3*chrome/toolkit/content/mozapps/extensions/about.xulPK
!<X7\3chrome/toolkit/content/mozapps/extensions/blocklist.cssPK
!<
2		6b5chrome/toolkit/content/mozapps/extensions/blocklist.jsPK
!<HJ7q?chrome/toolkit/content/mozapps/extensions/blocklist.xmlPK
!<7Gchrome/toolkit/content/mozapps/extensions/blocklist.xulPK
!<1Ochrome/toolkit/content/mozapps/extensions/eula.jsPK
!<Y552Schrome/toolkit/content/mozapps/extensions/eula.xulPK
!<II8=Ychrome/toolkit/content/mozapps/extensions/extensions.cssPK
!<WU7xchrome/toolkit/content/mozapps/extensions/extensions.jsPK
!<uE:E:8mchrome/toolkit/content/mozapps/extensions/extensions.xmlPK
!<;}T&&8chrome/toolkit/content/mozapps/extensions/extensions.xulPK
!<)__6Jchrome/toolkit/content/mozapps/extensions/gmpPrefs.xulPK
!<5^5Kchrome/toolkit/content/mozapps/extensions/newaddon.jsPK
!<yl1	1	6]chrome/toolkit/content/mozapps/extensions/newaddon.xulPK
!<j.rbb9+gchrome/toolkit/content/mozapps/extensions/pluginPrefs.xulPK
!<@E??5kchrome/toolkit/content/mozapps/extensions/setting.xmlPK
!<F>W>W3˫chrome/toolkit/content/mozapps/extensions/update.jsPK
!<'884Zchrome/toolkit/content/mozapps/extensions/update.xulPK
!<rBCC8chrome/toolkit/content/mozapps/extensions/updateinfo.xslPK
!<:`%`%1}$chrome/toolkit/content/mozapps/handling/dialog.jsPK
!<x7992,Jchrome/toolkit/content/mozapps/handling/dialog.xulPK
!<`evv3Rchrome/toolkit/content/mozapps/handling/handler.cssPK
!<ώ((3|Tchrome/toolkit/content/mozapps/handling/handler.xmlPK
!<VE6Xchrome/toolkit/content/mozapps/preferences/changemp.jsPK
!<k=		7tchrome/toolkit/content/mozapps/preferences/changemp.xulPK
!<09*chrome/toolkit/content/mozapps/preferences/fontbuilder.jsPK
!<Š$_6chrome/toolkit/content/mozapps/preferences/removemp.jsPK
!<wK7chrome/toolkit/content/mozapps/preferences/removemp.xulPK
!<_=՝chrome/toolkit/content/mozapps/profile/createProfileWizard.jsPK
!<gb		>chrome/toolkit/content/mozapps/profile/createProfileWizard.xulPK
!<S:Bchrome/toolkit/content/mozapps/profile/profileSelection.jsPK
!<Zy	y	;Cchrome/toolkit/content/mozapps/profile/profileSelection.xulPK
!<*P0chrome/toolkit/content/mozapps/update/history.jsPK
!<ܬHH1#chrome/toolkit/content/mozapps/update/history.xulPK
!<YH+N1chrome/toolkit/content/mozapps/update/updates.cssPK
!<@jqq0chrome/toolkit/content/mozapps/update/updates.jsPK
!<


1ηchrome/toolkit/content/mozapps/update/updates.xmlPK
!<
  1chrome/toolkit/content/mozapps/update/updates.xulPK
!<|LL=/chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.cssPK
!<#<chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.jsPK
!<Xt@@=%chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.xulPK
!<\		:chrome/toolkit/content/mozapps/xpinstall/xpinstallItem.xmlPK
!<8^^5chrome/toolkit/content/passwordmgr/passwordManager.jsPK
!<KBB6pchrome/toolkit/content/passwordmgr/passwordManager.xulPK
!<Ď/ichrome/toolkit/content/passwordmgr/recipes.jsonPK
!<Q-Dchrome/toolkit/content/preferences.propertiesPK
!<w!%1chrome/toolkit/content/providers.jsonPK
!<;FǛ

4Zchrome/toolkit/content/satchel/formSubmitListener.jsPK
!<||2chrome/toolkit/content/xbl-marquee/xbl-marquee.cssPK
!<^^2chrome/toolkit/content/xbl-marquee/xbl-marquee.xmlPK
!<;f$dd.chrome/toolkit/pluginproblem/pluginProblem.xmlPK
!<rݸN5chrome/toolkit/pluginproblem/pluginProblemBinding.cssPK
!</&&5chrome/toolkit/pluginproblem/pluginProblemContent.cssPK
!<Lދ5H*chrome/toolkit/pluginproblem/pluginReplaceBinding.cssPK
!<^]@]@2t-chrome/toolkit/res/accessiblecaret-normal@1.5x.pngPK
!<e]>]>0!nchrome/toolkit/res/accessiblecaret-normal@1x.pngPK
!<V;<)C)C3̬chrome/toolkit/res/accessiblecaret-normal@2.25x.pngPK
!<i4&C&C0Fchrome/toolkit/res/accessiblecaret-normal@2x.pngPK
!<Us\??53chrome/toolkit/res/accessiblecaret-tilt-left@1.5x.pngPK
!<S>>3schrome/toolkit/res/accessiblecaret-tilt-left@1x.pngPK
!<
yBB6Jchrome/toolkit/res/accessiblecaret-tilt-left@2.25x.pngPK
!<C]tAtA3Echrome/toolkit/res/accessiblecaret-tilt-left@2x.pngPK
!<9??6
7chrome/toolkit/res/accessiblecaret-tilt-right@1.5x.pngPK
!<#>>4Fwchrome/toolkit/res/accessiblecaret-tilt-right@1x.pngPK
!<{BB7chrome/toolkit/res/accessiblecaret-tilt-right@2.25x.pngPK
!<B{A{A4chrome/toolkit/res/accessiblecaret-tilt-right@2x.pngPK
!≮99!:chrome/toolkit/res/arrow-left.gifPK
!<]ZI99":chrome/toolkit/res/arrow-right.gifPK
!<i988x;chrome/toolkit/res/arrow.gifPK
!<6R<<";chrome/toolkit/res/arrowd-left.gifPK
!<хEN;;#f<chrome/toolkit/res/arrowd-right.gifPK
!<,4;;<chrome/toolkit/res/arrowd.gifPK
!<#X=chrome/toolkit/res/broken-image.pngPK
!<!E-E-$>chrome/toolkit/res/counterstyles.cssPK
!<C0{{lchrome/toolkit/res/forms.cssPK
!<"%-``$chrome/toolkit/res/hiddenWindow.htmlPK
!<'/GGchrome/toolkit/res/html.cssPK
!<UC$1chrome/toolkit/res/loading-image.pngPK
!<k**3chrome/toolkit/res/mathml.cssPK
!<%uu1^chrome/toolkit/res/noframes.cssPK
!<C>FF_chrome/toolkit/res/noscript.cssPK
!<tN%fachrome/toolkit/res/number-control.cssPK
!<]Y dchrome/toolkit/res/plaintext.cssPK
!<Lw@$@$gchrome/toolkit/res/quirk.cssPK
!<aOg11}chrome/toolkit/res/ua.cssPK
!<!chrome/toolkit/res/viewsource.cssPK
!<٧Z,chrome/toolkit/skin/classic/global/about.cssPK
!<NN1chrome/toolkit/skin/classic/global/aboutCache.cssPK
!<6wchrome/toolkit/skin/classic/global/aboutCacheEntry.cssPK
!<:A2chrome/toolkit/skin/classic/global/aboutMemory.cssPK
!<z
z
2	chrome/toolkit/skin/classic/global/aboutReader.cssPK
!<Gw9chrome/toolkit/skin/classic/global/aboutReaderContent.cssPK
!<L4":chrome/toolkit/skin/classic/global/aboutReaderControls.cssPK
!<<Vss3bchrome/toolkit/skin/classic/global/aboutSupport.cssPK
!<*#
#
:&chrome/toolkit/skin/classic/global/alerts/alert-common.cssPK
!<G3chrome/toolkit/skin/classic/global/alerts/alert.cssPK
!<0chrome/toolkit/skin/classic/global/appPicker.cssPK
!<gp559!chrome/toolkit/skin/classic/global/arrow/arrow-dn-dis.gifPK
!<	b5555"chrome/toolkit/skin/classic/global/arrow/arrow-dn.gifPK
!<//:"chrome/toolkit/skin/classic/global/arrow/arrow-lft-dis.gifPK
!<+<M666D#chrome/toolkit/skin/classic/global/arrow/arrow-lft.gifPK
!<wH66:#chrome/toolkit/skin/classic/global/arrow/arrow-rit-dis.gifPK
!<t8666\$chrome/toolkit/skin/classic/global/arrow/arrow-rit.gifPK
!<qY669$chrome/toolkit/skin/classic/global/arrow/arrow-up-dis.gifPK
!<z"#665s%chrome/toolkit/skin/classic/global/arrow/arrow-up.gifPK
!<>I%chrome/toolkit/skin/classic/global/arrow/panelarrow-horizontal-themed.svgPK
!<bpjB(chrome/toolkit/skin/classic/global/arrow/panelarrow-horizontal.svgPK
!<q'*G**chrome/toolkit/skin/classic/global/arrow/panelarrow-vertical-themed.svgPK
!<ڷ@?,chrome/toolkit/skin/classic/global/arrow/panelarrow-vertical.svgPK
!<r1KK3T.chrome/toolkit/skin/classic/global/autocomplete.cssPK
!<s-<chrome/toolkit/skin/classic/global/button.cssPK
!<ut_/Hchrome/toolkit/skin/classic/global/checkbox.cssPK
!<ͬG2Nchrome/toolkit/skin/classic/global/colorpicker.cssPK
!<b>3
Schrome/toolkit/skin/classic/global/commonDialog.cssPK
!<;vv-qTchrome/toolkit/skin/classic/global/config.cssPK
!<c72Xchrome/toolkit/skin/classic/global/customizeToolbar.cssPK
!<X)  ;CZchrome/toolkit/skin/classic/global/datetimeinputpickers.cssPK
!<x%5b{chrome/toolkit/skin/classic/global/datetimepicker.cssPK
!<Dҙō4schrome/toolkit/skin/classic/global/datetimepopup.cssPK
!<Cp00-Rchrome/toolkit/skin/classic/global/dialog.cssPK
!<<W1><͓chrome/toolkit/skin/classic/global/dirListing/dirListing.cssPK
!<(s<<8chrome/toolkit/skin/classic/global/dirListing/folder.pngPK
!<
X\4;chrome/toolkit/skin/classic/global/dirListing/up.pngPK
!<w21Tchrome/toolkit/skin/classic/global/dropmarker.cssPK
!<}}/echrome/toolkit/skin/classic/global/expander.cssPK
!<`XsM0/chrome/toolkit/skin/classic/global/filefield.cssPK
!<P._chrome/toolkit/skin/classic/global/filters.svgPK
!<:)).[chrome/toolkit/skin/classic/global/findBar.cssPK
!<8ۘ% % -кchrome/toolkit/skin/classic/global/global.cssPK
!<REE5@chrome/toolkit/skin/classic/global/globalBindings.xmlPK
!<ћ/chrome/toolkit/skin/classic/global/groupbox.cssPK
!<jdd2chrome/toolkit/skin/classic/global/icons/Error.pngPK
!<.6tchrome/toolkit/skin/classic/global/icons/Landscape.pngPK
!<>B5chrome/toolkit/skin/classic/global/icons/Portrait.pngPK
!<w:chrome/toolkit/skin/classic/global/icons/Print-preview.pngPK
!<5vchrome/toolkit/skin/classic/global/icons/Question.pngPK
!<؇9	chrome/toolkit/skin/classic/global/icons/Search-close.pngPK
!<\WW4	chrome/toolkit/skin/classic/global/icons/Warning.pngPK
!<1..@G
	chrome/toolkit/skin/classic/global/icons/autocomplete-search.svgPK
!<7
	chrome/toolkit/skin/classic/global/icons/autoscroll.pngPK
!<5__>	chrome/toolkit/skin/classic/global/icons/blacklist_favicon.pngPK
!<?XA<	chrome/toolkit/skin/classic/global/icons/blacklist_large.pngPK
!<XV{{40	chrome/toolkit/skin/classic/global/icons/blocked.svgPK
!<΀}}@Y3	chrome/toolkit/skin/classic/global/icons/calendar-arrow-left.svgPK
!<K&||A45	chrome/toolkit/skin/classic/global/icons/calendar-arrow-right.svgPK
!<"}WW@7	chrome/toolkit/skin/classic/global/icons/close-inverted-win7.pngPK
!<%IIC:	chrome/toolkit/skin/classic/global/icons/close-inverted-win7@2x.pngPK
!<ƉwVV;nB	chrome/toolkit/skin/classic/global/icons/close-inverted.pngPK
!<t >E	chrome/toolkit/skin/classic/global/icons/close-inverted@2x.pngPK
!<٬5ݣ70J	chrome/toolkit/skin/classic/global/icons/close-win7.pngPK
!<J:(N	chrome/toolkit/skin/classic/global/icons/close-win7@2x.pngPK
!<ã<<2oV	chrome/toolkit/skin/classic/global/icons/close.pngPK
!<X5X	chrome/toolkit/skin/classic/global/icons/close@2x.pngPK
!<<<t5]	chrome/toolkit/skin/classic/global/icons/collapse.pngPK
!<s5_	chrome/toolkit/skin/classic/global/icons/error-16.pngPK
!<qU"3c	chrome/toolkit/skin/classic/global/icons/expand.pngPK
!<F|<Id	chrome/toolkit/skin/classic/global/icons/find-next-arrow.svgPK
!<~ʥ@Nf	chrome/toolkit/skin/classic/global/icons/find-previous-arrow.svgPK
!<xr8Qh	chrome/toolkit/skin/classic/global/icons/folder-item.pngPK
!<qt1m	chrome/toolkit/skin/classic/global/icons/info.svgPK
!<n1;o	chrome/toolkit/skin/classic/global/icons/information-16.pngPK
!<Q-
mm8r	chrome/toolkit/skin/classic/global/icons/input-clear.svgPK
!<h)1)14u	chrome/toolkit/skin/classic/global/icons/loading.pngPK
!<1Ù7	chrome/toolkit/skin/classic/global/icons/loading@2x.pngPK
!<1k&~B;D
chrome/toolkit/skin/classic/global/icons/menubutton-dropmarker.svgPK
!<y8!F
chrome/toolkit/skin/classic/global/icons/question-16.pngPK
!<=	Y==8nI
chrome/toolkit/skin/classic/global/icons/question-64.pngPK
!<XR8^
chrome/toolkit/skin/classic/global/icons/resizer-rtl.pngPK
!<J4!_
chrome/toolkit/skin/classic/global/icons/resizer.pngPK
!<B55;8`
chrome/toolkit/skin/classic/global/icons/search-textbox.svgPK
!<K:@mm?b
chrome/toolkit/skin/classic/global/icons/spinner-arrow-down.svgPK
!<+{$mm=d
chrome/toolkit/skin/classic/global/icons/spinner-arrow-up.svgPK
!<887Xf
chrome/toolkit/skin/classic/global/icons/sslWarning.pngPK
!<-	1Ax
chrome/toolkit/skin/classic/global/icons/tabprompts-bgtexture.pngPK
!<~_7
chrome/toolkit/skin/classic/global/icons/warning-16.pngPK
!<4.7D
chrome/toolkit/skin/classic/global/icons/warning-64.pngPK
!<+
+
:
chrome/toolkit/skin/classic/global/icons/warning-large.pngPK
!<2~NN4
chrome/toolkit/skin/classic/global/icons/warning.svgPK
!<9Mܹ;
chrome/toolkit/skin/classic/global/icons/windowControls.pngPK
!<٠?Į
chrome/toolkit/skin/classic/global/in-content/check-partial.svgPK
!<3ؾ7
chrome/toolkit/skin/classic/global/in-content/check.svgPK
!<7,d[d[8Բ
chrome/toolkit/skin/classic/global/in-content/common.cssPK
!<:7!YY:chrome/toolkit/skin/classic/global/in-content/dropdown.svgPK
!<<?chrome/toolkit/skin/classic/global/in-content/help-glyph.svgPK
!<?!

<7chrome/toolkit/skin/classic/global/in-content/info-pages.cssPK
!<K$SS7chrome/toolkit/skin/classic/global/in-content/radio.svgPK
!<Gll.Fchrome/toolkit/skin/classic/global/listbox.cssPK
!<D.**B;chrome/toolkit/skin/classic/global/media/TopLevelImageDocument.cssPK
!<4MB>chrome/toolkit/skin/classic/global/media/TopLevelVideoDocument.cssPK
!<-`=@chrome/toolkit/skin/classic/global/media/audioMutedButton.svgPK
!<Ut2RR?Cchrome/toolkit/skin/classic/global/media/audioNoAudioButton.svgPK
!<Nq?XFchrome/toolkit/skin/classic/global/media/audioUnmutedButton.svgPK
!<'[GlJchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-off.svgPK
!<[YFOchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-on.svgPK
!<Vm?yOyO2&Uchrome/toolkit/skin/classic/global/media/error.pngPK
!<kkBchrome/toolkit/skin/classic/global/media/fullscreenEnterButton.svgPK
!<PWWAchrome/toolkit/skin/classic/global/media/fullscreenExitButton.svgPK
!<c?pchrome/toolkit/skin/classic/global/media/imagedoc-darknoise.pngPK
!<J)@chrome/toolkit/skin/classic/global/media/imagedoc-lightnoise.pngPK
!<8chrome/toolkit/skin/classic/global/media/pauseButton.svgPK
!<z7chrome/toolkit/skin/classic/global/media/playButton.svgPK
!<QQ4Ichrome/toolkit/skin/classic/global/media/stalled.pngPK
!<Qww5chrome/toolkit/skin/classic/global/media/throbber.pngPK
!<7%7%:chrome/toolkit/skin/classic/global/media/videocontrols.cssPK
!<$&Dchrome/toolkit/skin/classic/global/menu/shared-menu-check-active.svgPK
!<16"}}Cchrome/toolkit/skin/classic/global/menu/shared-menu-check-black.svgPK
!<$Cuchrome/toolkit/skin/classic/global/menu/shared-menu-check-hover.svgPK
!<)=_chrome/toolkit/skin/classic/global/menu/shared-menu-check.pngPK
!<r-yy@chrome/toolkit/skin/classic/global/menu/shared-menu-check@2x.pngPK
!<+Vchrome/toolkit/skin/classic/global/menu.cssPK
!<aN_t
t
/ochrome/toolkit/skin/classic/global/menulist.cssPK
!<Mߡ40chrome/toolkit/skin/classic/global/narrate/arrow.svgPK
!<;cb3(chrome/toolkit/skin/classic/global/narrate/back.svgPK
!<3chrome/toolkit/skin/classic/global/narrate/fast.svgPK
!</ree6chrome/toolkit/skin/classic/global/narrate/forward.svgPK
!<CEE3chrome/toolkit/skin/classic/global/narrate/slow.svgPK
!<
!""42chrome/toolkit/skin/classic/global/narrate/start.svgPK
!<^93chrome/toolkit/skin/classic/global/narrate/stop.svgPK
!<vK}.chrome/toolkit/skin/classic/global/narrate.cssPK
!<ޛh))6
chrome/toolkit/skin/classic/global/narrateControls.cssPK
!<tɟW
W
/c
chrome/toolkit/skin/classic/global/netError.cssPK
!<|yy3
chrome/toolkit/skin/classic/global/notification.cssPK
!<b@70.
chrome/toolkit/skin/classic/global/numberbox.cssPK
!<g5521
chrome/toolkit/skin/classic/global/passwordmgr.cssPK
!<5u

,y4
chrome/toolkit/skin/classic/global/popup.cssPK
!<r12B
chrome/toolkit/skin/classic/global/preferences.cssPK
!<_5I
chrome/toolkit/skin/classic/global/printPageSetup.cssPK
!<'-\\3J
chrome/toolkit/skin/classic/global/printPreview.cssPK
!<TǴII4M
chrome/toolkit/skin/classic/global/progressmeter.cssPK
!<J..<4S
chrome/toolkit/skin/classic/global/radio/radio-check-dis.gifPK
!<N..8S
chrome/toolkit/skin/classic/global/radio/radio-check.gifPK
!<v`		,@T
chrome/toolkit/skin/classic/global/radio.cssPK
!<`<^
chrome/toolkit/skin/classic/global/reader/RM-Close-24x24.svgPK
!<&J`
chrome/toolkit/skin/classic/global/reader/RM-Content-Width-Minus-42x16.svgPK
!<?Ic
chrome/toolkit/skin/classic/global/reader/RM-Content-Width-Plus-44x16.svgPK
!<MHg
chrome/toolkit/skin/classic/global/reader/RM-Line-Height-Minus-38x14.svgPK
!<teG~i
chrome/toolkit/skin/classic/global/reader/RM-Line-Height-Plus-38x24.svgPK
!<~<k
chrome/toolkit/skin/classic/global/reader/RM-Minus-24x24.svgPK
!<;m
chrome/toolkit/skin/classic/global/reader/RM-Plus-24x24.svgPK
!<&@Do
chrome/toolkit/skin/classic/global/reader/RM-Type-Controls-24x24.svgPK
!<Ds
chrome/toolkit/skin/classic/global/reader/RM-Type-Controls-Arrow.svgPK
!<sGkk.v
chrome/toolkit/skin/classic/global/resizer.cssPK
!<2z
chrome/toolkit/skin/classic/global/richlistbox.cssPK
!<ki,~
chrome/toolkit/skin/classic/global/scale.cssPK
!<fA		1
chrome/toolkit/skin/classic/global/scrollbars.cssPK
!<
0:0Q
chrome/toolkit/skin/classic/global/scrollbox.cssPK
!<2//2i
chrome/toolkit/skin/classic/global/spinbuttons.cssPK
!<+|;
chrome/toolkit/skin/classic/global/splitter/grip-bottom.gifPK
!<^e9қ
chrome/toolkit/skin/classic/global/splitter/grip-left.gifPK
!<B:Ɯ
chrome/toolkit/skin/classic/global/splitter/grip-right.gifPK
!<[8
chrome/toolkit/skin/classic/global/splitter/grip-top.gifPK
!<Sl/
chrome/toolkit/skin/classic/global/splitter.cssPK
!<Gđ-
chrome/toolkit/skin/classic/global/tabbox.cssPK
!<z>1
chrome/toolkit/skin/classic/global/tabprompts.cssPK
!<f

.
chrome/toolkit/skin/classic/global/textbox.cssPK
!<UU?
chrome/toolkit/skin/classic/global/toolbar/chevron-inverted.pngPK
!<	996[
chrome/toolkit/skin/classic/global/toolbar/chevron.gifPK
!<Q5
chrome/toolkit/skin/classic/global/toolbar/spring.pngPK
!<cl.
chrome/toolkit/skin/classic/global/toolbar.cssPK
!<f4
chrome/toolkit/skin/classic/global/toolbarbutton.cssPK
!<JJ8
chrome/toolkit/skin/classic/global/tree/columnpicker.gifPK
!<VMٶ<
chrome/toolkit/skin/classic/global/tree/sort-asc-classic.pngPK
!<74
chrome/toolkit/skin/classic/global/tree/sort-asc.pngPK
!<+s<
chrome/toolkit/skin/classic/global/tree/sort-dsc-classic.pngPK
!< M4
chrome/toolkit/skin/classic/global/tree/sort-dsc.pngPK
!<FII;
chrome/toolkit/skin/classic/global/tree/twisty-preWin10.svgPK
!<ew¯2
chrome/toolkit/skin/classic/global/tree/twisty.svgPK
!<a$RR+
chrome/toolkit/skin/classic/global/tree.cssPK
!<!!jj-Achrome/toolkit/skin/classic/global/wizard.cssPK
!<Fnn7Fchrome/toolkit/skin/classic/mozapps/aboutNetworking.cssPK
!<L5HOchrome/toolkit/skin/classic/mozapps/aboutProfiles.cssPK
!<0Z%%;=Uchrome/toolkit/skin/classic/mozapps/aboutServiceWorkers.cssPK
!<(AXchrome/toolkit/skin/classic/mozapps/downloads/downloadButtons.pngPK
!<#.hh>6mchrome/toolkit/skin/classic/mozapps/downloads/downloadIcon.pngPK
!<-#m))Drchrome/toolkit/skin/classic/mozapps/downloads/unknownContentType.cssPK
!<óg8uchrome/toolkit/skin/classic/mozapps/extensions/about.cssPK
!<f^__B|chrome/toolkit/skin/classic/mozapps/extensions/alerticon-error.svgPK
!<BX2__Jachrome/toolkit/skin/classic/mozapps/extensions/alerticon-info-negative.svgPK
!<MRuJ(chrome/toolkit/skin/classic/mozapps/extensions/alerticon-info-positive.svgPK
!<o__Dschrome/toolkit/skin/classic/mozapps/extensions/alerticon-warning.svgPK
!<U<4chrome/toolkit/skin/classic/mozapps/extensions/blocklist.cssPK
!<EF[ss9Wchrome/toolkit/skin/classic/mozapps/extensions/cancel.pngPK
!<
bE!chrome/toolkit/skin/classic/mozapps/extensions/category-available.pngPK
!<v)KKD?chrome/toolkit/skin/classic/mozapps/extensions/category-discover.pngPK
!<pBchrome/toolkit/skin/classic/mozapps/extensions/category-legacy.svgPK
!<&Cchrome/toolkit/skin/classic/mozapps/extensions/category-plugins.pngPK
!<砦Bchrome/toolkit/skin/classic/mozapps/extensions/category-recent.pngPK
!<`(
(
BCchrome/toolkit/skin/classic/mozapps/extensions/category-search.pngPK
!<N'C˾chrome/toolkit/skin/classic/mozapps/extensions/category-service.pngPK
!<l],D;chrome/toolkit/skin/classic/mozapps/extensions/dictionaryGeneric.pngPK
!<mڎ..@chrome/toolkit/skin/classic/mozapps/extensions/discover-logo.pngPK
!<UC<<7cchrome/toolkit/skin/classic/mozapps/extensions/eula.cssPK
!<!]66Dchrome/toolkit/skin/classic/mozapps/extensions/experimentGeneric.pngPK
!<CFchrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric-16.pngPK
!<h8kCchrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svgPK
!<y0B V V=chrome/toolkit/skin/classic/mozapps/extensions/extensions.cssPK
!<S8,cchrome/toolkit/skin/classic/mozapps/extensions/heart.pngPK
!<		@ochrome/toolkit/skin/classic/mozapps/extensions/localeGeneric.pngPK
!<7o=;ychrome/toolkit/skin/classic/mozapps/extensions/navigation.pngPK
!<s#;-|chrome/toolkit/skin/classic/mozapps/extensions/newaddon.cssPK
!<'5΍A>chrome/toolkit/skin/classic/mozapps/extensions/rating-not-won.pngPK
!<pB~~=chrome/toolkit/skin/classic/mozapps/extensions/rating-won.pngPK
!<PIq..?chrome/toolkit/skin/classic/mozapps/extensions/themeGeneric.pngPK
!<r 9chrome/toolkit/skin/classic/mozapps/extensions/update.cssPK
!<Ӡ2UU<0chrome/toolkit/skin/classic/mozapps/extensions/utilities.svgPK
!<X<Iuu9ߡchrome/toolkit/skin/classic/mozapps/handling/handling.cssPK
!<l=chrome/toolkit/skin/classic/mozapps/places/defaultFavicon.svgPK
!<n5Echrome/toolkit/skin/classic/mozapps/plugins/contentPluginActivate.pngPK
!<PrDbchrome/toolkit/skin/classic/mozapps/plugins/contentPluginBlocked.pngPK
!<qZZBchrome/toolkit/skin/classic/mozapps/plugins/contentPluginClose.pngPK
!<T0Dhchrome/toolkit/skin/classic/mozapps/plugins/contentPluginCrashed.pngPK
!<+i$Cchrome/toolkit/skin/classic/mozapps/plugins/contentPluginStripe.pngPK
!<#jvc
c
@chrome/toolkit/skin/classic/mozapps/plugins/pluginBlocked-64.pngPK
!<Z|=vchrome/toolkit/skin/classic/mozapps/plugins/pluginBlocked.pngPK
!<	eW@chrome/toolkit/skin/classic/mozapps/plugins/pluginGeneric-16.pngPK
!<:zN:=chrome/toolkit/skin/classic/mozapps/plugins/pluginGeneric.pngPK
!<bll= chrome/toolkit/skin/classic/mozapps/plugins/pluginHelp-16.pngPK
!<=~W2=chrome/toolkit/skin/classic/mozapps/plugins/pluginProblem.cssPK
!<Fl[[@Fchrome/toolkit/skin/classic/mozapps/profile/profileSelection.cssPK
!<[/,NN;chrome/toolkit/skin/classic/mozapps/profile/profileicon.pngPK
!<(>	chrome/toolkit/skin/classic/mozapps/update/downloadButtons.pngPK
!<ЖKn

6chrome/toolkit/skin/classic/mozapps/update/updates.cssPK
!<Jv=()chrome/toolkit/skin/classic/mozapps/viewsource/viewsource.cssPK
!<'TB*chrome/toolkit/skin/classic/mozapps/xpinstall/xpinstallConfirm.cssPK
!<3d
%3chrome/recording/content/recording.jsPK
!<X&9chrome/recording/content/recording.xulPK
!<dPP=greprefs.jsPK
!<M} defaults/autoconfig/prefcalls.jsPK
!<JYj!!defaults/pref/marionette.jsPK
!<w7>/defaults/pref/services-sync.jsPK
!<sC*#*#gres/EditorOverride.cssPK
!<=lv&&!res/contenteditable.cssPK
!<`FFHres/designmode.cssPK
!<3!bJres/ImageDocument.cssPK
!<ÃEۭ0Nres/TopLevelImageDocument.cssPK
!<ЗRres/TopLevelVideoDocument.cssPK
!<Ұ
i::%Tres/table-add-column-after-active.gifPK
!<v7::$nUres/table-add-column-after-hover.gifPK
!<x
::Xres/table-add-column-after.gifPK
!<Z(99&`\res/table-add-column-before-active.gifPK
!<M99%\res/table-add-column-before-hover.gifPK
!</99Y`res/table-add-column-before.gifPK
!<<Ed99"cres/table-add-row-after-active.gifPK
!<K::!Hdres/table-add-row-after-hover.gifPK
!<ؿ::gres/table-add-row-after.gifPK
!<ބw99#4kres/table-add-row-before-active.gifPK
!<99"kres/table-add-row-before-hover.gifPK
!<?499'ores/table-add-row-before.gifPK
!<	$CC"rres/table-remove-column-active.gifPK
!<&II!vres/table-remove-column-hover.gifPK
!<V)IIyres/table-remove-column.gifPK
!<	$CC'}res/table-remove-row-active.gifPK
!<&IIres/table-remove-row-hover.gifPK
!<V)II,res/table-remove-row.gifPK
!<!rZZres/grabber.gifPK
!<8EhEh2res/fonts/mathfont.propertiesPK
!<7(res/fonts/mathfontSTIXGeneral.propertiesPK
!<܉$res/fonts/mathfontUnicode.propertiesPK
!<K*mms$res/dtd/htmlmathml-f.entPK
!<Ukk{res/html/folder.pngPK
!<N
res/language.propertiesPK
!<dQ(%res/entityTables/html40Latin1.propertiesPK
!<aa)res/entityTables/html40Special.propertiesPK
!<ID
D
)	res/entityTables/html40Symbols.propertiesPK
!<
WVoVo$res/entityTables/mathml20.propertiesPK
!<n{		,3res/svg.cssPK
!<d*=chrome/pippki/content/pippki/CAOverlay.xulPK
!<\,Echrome/pippki/content/pippki/MineOverlay.xulPK
!<ٽ(.MQchrome/pippki/content/pippki/OrphanOverlay.xulPK
!<f}((.#Ychrome/pippki/content/pippki/OthersOverlay.xulPK
!<x0b	b	0achrome/pippki/content/pippki/WebSitesOverlay.xulPK
!<-pLWW)Gkchrome/pippki/content/pippki/certDump.xulPK
!<>>+rchrome/pippki/content/pippki/certManager.jsPK
!<b,chrome/pippki/content/pippki/certManager.xulPK
!<ʴ9G1G1*Ychrome/pippki/content/pippki/certViewer.jsPK
!<	;+chrome/pippki/content/pippki/certViewer.xulPK
!<h{C.chrome/pippki/content/pippki/changepassword.jsPK
!<ƯUU/chrome/pippki/content/pippki/changepassword.xulPK
!<b\8!!+chrome/pippki/content/pippki/choosetoken.jsPK
!<J88,chrome/pippki/content/pippki/choosetoken.xulPK
!<ѣ-vchrome/pippki/content/pippki/clientauthask.jsPK
!<5.6chrome/pippki/content/pippki/clientauthask.xulPK
!<R``.=chrome/pippki/content/pippki/createCertInfo.jsPK
!<"/Achrome/pippki/content/pippki/createCertInfo.xulPK
!<ŀQQ*Echrome/pippki/content/pippki/deletecert.jsPK
!< *=^^+lUchrome/pippki/content/pippki/deletecert.xulPK
!<*>377.Zchrome/pippki/content/pippki/device_manager.jsPK
!<Fyw

/chrome/pippki/content/pippki/device_manager.xulPK
!<0g99,6chrome/pippki/content/pippki/downloadcert.jsPK
!<9Z-chrome/pippki/content/pippki/downloadcert.xulPK
!<tiLL*chrome/pippki/content/pippki/editcacert.jsPK
!<IP+zchrome/pippki/content/pippki/editcacert.xulPK
!<uB0B0/chrome/pippki/content/pippki/exceptionDialog.jsPK
!<>0Ychrome/pippki/content/pippki/exceptionDialog.xulPK
!<p!+,chrome/pippki/content/pippki/load_device.jsPK
!<$ܗԠ,g	chrome/pippki/content/pippki/load_device.xulPK
!<4&Qchrome/pippki/content/pippki/pippki.jsPK
!<Q-'chrome/pippki/content/pippki/protectedAuth.jsPK
!<F/::.+chrome/pippki/content/pippki/protectedAuth.xulPK
!<Y#-V0chrome/pippki/content/pippki/resetpassword.jsPK
!<?>.R5chrome/pippki/content/pippki/resetpassword.xulPK
!<sWKK.b:chrome/pippki/content/pippki/setp12password.jsPK
!<*/Ichrome/pippki/content/pippki/setp12password.xulPK
!<9A

0Qchrome/pippki/content/pippki/viewCertDetails.xulPK
!<gUU(_components/CrashService.jsPK
!<HE_#vcomponents/nsTerminatorTelemetry.jsPK
!<B/,/,قcomponents/SanityTest.jsPK--PK
!<Y.HHchrome.manifestmanifest chrome/chrome.manifest
manifest components/components.manifest
PK
!<YCJ  chrome/chrome.manifestlocale alerts en-US en-US/locale/en-US/alerts/
locale autoconfig en-US en-US/locale/en-US/autoconfig/
locale global en-US en-US/locale/en-US/global/
locale global-platform en-US en-US/locale/en-US/global-platform/mac/ os=Darwin
locale global-platform en-US en-US/locale/en-US/global-platform/unix/ os=LikeUnix os=Android
locale global-platform en-US en-US/locale/en-US/global-platform/win/ os=WINNT
locale mozapps en-US en-US/locale/en-US/mozapps/
locale necko en-US en-US/locale/en-US/necko/
locale passwordmgr en-US en-US/locale/en-US/passwordmgr/
locale pipnss en-US en-US/locale/en-US/pipnss/
locale pippki en-US en-US/locale/en-US/pippki/
locale places en-US en-US/locale/en-US/places/
locale pluginproblem en-US en-US/locale/en-US/pluginproblem/
locale weave en-US en-US/locale/en-US/services/
content marionette marionette/content/
content extensions toolkit/content/extensions/
content gfxsanity toolkit/content/gfxsanity/
content global toolkit/content/global/ contentaccessible=yes
content mozapps toolkit/content/mozapps/
content passwordmgr toolkit/content/passwordmgr/
content pluginproblem toolkit/pluginproblem/ contentaccessible=yes
content satchel toolkit/content/satchel/
content xbl-marquee toolkit/content/xbl-marquee/ contentaccessible=yes
skin global classic/1.0 toolkit/skin/classic/global/
skin help classic/1.0 toolkit/skin/classic/help/
skin mozapps classic/1.0 toolkit/skin/classic/mozapps/
content recording recording/content/
content pippki pippki/content/pippki/
resource cloudstorage toolkit/content/
resource gre-resources toolkit/res/
PK
!<xrUVUVcomponents/components.manifestinterfaces interfaces.xpt
component {96cf7855-dfa9-4c6d-8276-f9705b4890f2} ConsoleAPIStorage.js
contract @mozilla.org/consoleAPI-storage;1 {96cf7855-dfa9-4c6d-8276-f9705b4890f2}
component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js
contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060}
component {072a5c3d-30c6-4f07-b87f-9f63d51403f2} FeedProcessor.js
contract @mozilla.org/feed-result;1 {072a5c3d-30c6-4f07-b87f-9f63d51403f2}
component {5d0cfa97-69dd-4e5e-ac84-f253162e8f9a} FeedProcessor.js
contract @mozilla.org/feed;1 {5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}
component {8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f} FeedProcessor.js
contract @mozilla.org/feed-entry;1 {8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}
component {b992ddcd-3899-4320-9909-924b3e72c922} FeedProcessor.js
contract @mozilla.org/feed-textconstruct;1 {b992ddcd-3899-4320-9909-924b3e72c922}
component {414af362-9ad8-4296-898e-62247f25a20e} FeedProcessor.js
contract @mozilla.org/feed-generator;1 {414af362-9ad8-4296-898e-62247f25a20e}
component {95c963b7-20b2-11db-92f6-001422106990} FeedProcessor.js
contract @mozilla.org/feed-person;1 {95c963b7-20b2-11db-92f6-001422106990}
component {26acb1f0-28fc-43bc-867a-a46aabc85dd4} FeedProcessor.js
contract @mozilla.org/feed-processor;1 {26acb1f0-28fc-43bc-867a-a46aabc85dd4}
component {965b0ca8-155b-11e7-93ae-92361f002671} UAOverridesBootstrapper.js process=main
contract @mozilla.org/network/ua-overrides-bootstrapper;1 {965b0ca8-155b-11e7-93ae-92361f002671} process=main
component {b4f96c89-5238-450c-8bda-e12c26f1d150} WellKnownOpportunisticUtils.js
contract @mozilla.org/network/well-known-opportunistic-utils;1 {b4f96c89-5238-450c-8bda-e12c26f1d150}
component {f9346d98-f27a-4e89-b744-493843416480} nsDNSServiceDiscovery.js
contract @mozilla.org/toolkit/components/mdnsresponder/dns-sd;1 {f9346d98-f27a-4e89-b744-493843416480}
component {1b4c85df-cbdd-4bb6-b04e-613caece083c} DownloadLegacy.js
contract @mozilla.org/transfer;1 {1b4c85df-cbdd-4bb6-b04e-613caece083c}
component {5a4ae9b5-f475-48ae-9dce-0b4c1d347884} PageThumbsProtocol.js
contract @mozilla.org/network/protocol;1?name=moz-page-thumb {5a4ae9b5-f475-48ae-9dce-0b4c1d347884}
component {d9d75e86-8f17-4c57-993e-f738f0d86d42} nsCrashMonitor.js
contract @mozilla.org/toolkit/crashmonitor;1 {d9d75e86-8f17-4c57-993e-f738f0d86d42}
category profile-after-change CrashMonitor @mozilla.org/toolkit/crashmonitor;1
component {7319788a-fe93-4db3-9f39-818cf08f4256} nsSearchService.js process=main
contract @mozilla.org/browser/search-service;1 {7319788a-fe93-4db3-9f39-818cf08f4256} process=main
category update-timer nsSearchService @mozilla.org/browser/search-service;1,getService,search-engine-update-timer,browser.search.update.interval,21600
component {aa892eb4-ffbf-477d-9f9a-06c995ae9f27} nsSearchSuggestions.js
contract @mozilla.org/autocomplete/search;1?name=search-autocomplete {aa892eb4-ffbf-477d-9f9a-06c995ae9f27}
component {22117140-9c6e-11d3-aaf1-00805f8a4905} nsSidebar.js
contract @mozilla.org/sidebar;1 {22117140-9c6e-11d3-aaf1-00805f8a4905}
component {cb9e0de8-3598-4ed7-857b-827f011ad5d8} nsLoginManager.js
contract @mozilla.org/login-manager;1 {cb9e0de8-3598-4ed7-857b-827f011ad5d8}
component {749e62f4-60ae-4569-a8a2-de78b649660e} nsLoginManagerPrompter.js
contract @mozilla.org/passwordmanager/authpromptfactory;1 {749e62f4-60ae-4569-a8a2-de78b649660e}
component {8aa66d77-1bbb-45a6-991e-b8f47751c291} nsLoginManagerPrompter.js
contract @mozilla.org/login-manager/prompter;1 {8aa66d77-1bbb-45a6-991e-b8f47751c291}
component {0f2f347c-1e4f-40cc-8efd-792dea70a85e} nsLoginInfo.js
contract @mozilla.org/login-manager/loginInfo;1 {0f2f347c-1e4f-40cc-8efd-792dea70a85e}
component {c00c432d-a0c9-46d7-bef6-9c45b4d07341} storage-json.js
contract @mozilla.org/login-manager/storage/json;1 {c00c432d-a0c9-46d7-bef6-9c45b4d07341}
component {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309} crypto-SDR.js
contract @mozilla.org/login-manager/crypto/SDR;1 {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}
component {f376627f-0bbc-47b8-887e-fc92574cc91f} TooltipTextProvider.js
contract @mozilla.org/embedcomp/default-tooltiptextprovider;1 {f376627f-0bbc-47b8-887e-fc92574cc91f}
component {acf6e493-0092-4b26-b172-241e375c57ab} WebVTTParserWrapper.js
contract @mozilla.org/webvttParserWrapper;1 {acf6e493-0092-4b26-b172-241e375c57ab}
component {F68578EB-6EC2-4169-AE19-8C6243F0ABE1} nsHelperAppDlg.js
contract @mozilla.org/helperapplauncherdialog;1 {F68578EB-6EC2-4169-AE19-8C6243F0ABE1}
component {77DA64D3-7458-4920-9491-86CC9914F904} NetworkGeolocationProvider.js
contract @mozilla.org/geolocation/provider;1 {77DA64D3-7458-4920-9491-86CC9914F904}
contract @mozilla.org/geolocation/mls-provider;1 {77DA64D3-7458-4920-9491-86CC9914F904}
component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js process=main
contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} process=main
category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 process=main
component {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} nsBlocklistServiceContent.js process=content
contract @mozilla.org/extensions/blocklist;1 {e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d} process=content
category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400
component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa}
category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
component {7beb3ba8-6ec3-41b4-b67c-da89b8518922} amContentHandler.js
contract @mozilla.org/uriloader/content-handler;1?type=application/x-xpinstall {7beb3ba8-6ec3-41b4-b67c-da89b8518922}
component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js
contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1}
category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
component {8866d8e3-4ea5-48b7-a891-13ba0ac15235} amWebAPI.js
contract @mozilla.org/addon-web-api/manager;1 {8866d8e3-4ea5-48b7-a891-13ba0ac15235}
component {12e63991-86ac-4dff-bb1a-703495d67d17} EditorUtils.js
contract @mozilla.org/editor-utils;1 {12e63991-86ac-4dff-bb1a-703495d67d17}
component {B3C290A6-3943-4B89-8BBE-C01EB7B3B311} nsUpdateService.js
contract @mozilla.org/updates/update-service;1 {B3C290A6-3943-4B89-8BBE-C01EB7B3B311}
category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,43200,86400
component {093C2356-4843-4C65-8709-D7DBCBBE7DFB} nsUpdateService.js
contract @mozilla.org/updates/update-manager;1 {093C2356-4843-4C65-8709-D7DBCBBE7DFB}
component {898CDC9B-E43F-422F-9CC4-2F6291B415A3} nsUpdateService.js
contract @mozilla.org/updates/update-checker;1 {898CDC9B-E43F-422F-9CC4-2F6291B415A3}
component {27ABA825-35B5-4018-9FDD-F99250A0E722} nsUpdateService.js
contract @mozilla.org/updates/update-prompt;1 {27ABA825-35B5-4018-9FDD-F99250A0E722}
component {e43b0010-04ba-4da6-b523-1f92580bc150} nsUpdateServiceStub.js
contract @mozilla.org/updates/update-service-stub;1 {e43b0010-04ba-4da6-b523-1f92580bc150}
category profile-after-change nsUpdateServiceStub @mozilla.org/updates/update-service-stub;1
component {B322A5C0-A419-484E-96BA-D7182163899F} nsUpdateTimerManager.js
contract @mozilla.org/updates/timer-manager;1 {B322A5C0-A419-484E-96BA-D7182163899F}
category profile-after-change nsUpdateTimerManager @mozilla.org/updates/timer-manager;1
component {1363d5f0-d95e-11e3-9c1a-0800200c9a66} multiprocessShims.js
contract @mozilla.org/addons/multiprocess-shims;1 {1363d5f0-d95e-11e3-9c1a-0800200c9a66}
component {50bc93ce-602a-4bef-bf3a-61fc749c4caf} defaultShims.js
contract @mozilla.org/addons/default-addon-shims;1 {50bc93ce-602a-4bef-bf3a-61fc749c4caf}
component {dfd07380-6083-11e4-9803-0800200c9a66} simpleServices.js
contract @mozilla.org/addons/remote-tag-service;1 {dfd07380-6083-11e4-9803-0800200c9a66}
component {ded150e3-c92e-4077-a396-0dba9953e39f} simpleServices.js
contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.webext.unlocalized&to=text/css {ded150e3-c92e-4077-a396-0dba9953e39f}
category agent-style-sheets pluginGlue-pluginProblem chrome://pluginproblem/content/pluginProblemBinding.css
component {0636a680-45cb-11e4-916c-0800200c9a66} MainProcessSingleton.js process=main
contract @mozilla.org/main-process-singleton;1 {0636a680-45cb-11e4-916c-0800200c9a66} process=main
category app-startup MainProcessSingleton service,@mozilla.org/main-process-singleton;1 process=main
component {ca2a8470-45c7-11e4-916c-0800200c9a66} ContentProcessSingleton.js process=content
contract @mozilla.org/content-process-singleton;1 {ca2a8470-45c7-11e4-916c-0800200c9a66} process=content
category app-startup ContentProcessSingleton service,@mozilla.org/content-process-singleton;1 process=content
component {e6156350-2be8-11db-a98b-0800200c9a66} nsURLFormatter.js
contract @mozilla.org/toolkit/URLFormatterService;1 {e6156350-2be8-11db-a98b-0800200c9a66}
component {18a03189-067b-4978-b4f1-bafe35292ed6} txEXSLTRegExFunctions.js
contract @mozilla.org/exslt/regexp;1 {18a03189-067b-4978-b4f1-bafe35292ed6}
category XSLT-extension-functions http://exslt.org/regular-expressions @mozilla.org/exslt/regexp;1
component {dca61eb5-c7cd-4df1-b0fb-d0722baba251} nsLivemarkService.js
contract @mozilla.org/browser/livemark-service;2 {dca61eb5-c7cd-4df1-b0fb-d0722baba251}
component {bbc23860-2553-479d-8b78-94d9038334f7} nsTaggingService.js
contract @mozilla.org/browser/tagging-service;1 {bbc23860-2553-479d-8b78-94d9038334f7}
component {1dcc23b0-d4cb-11dc-9ad6-479d56d89593} nsTaggingService.js
contract @mozilla.org/autocomplete/search;1?name=places-tag-autocomplete {1dcc23b0-d4cb-11dc-9ad6-479d56d89593}
component {705a423f-2f69-42f3-b9fe-1517e0dee56f} nsPlacesExpiration.js
contract @mozilla.org/places/expiration;1 {705a423f-2f69-42f3-b9fe-1517e0dee56f}
category history-observers nsPlacesExpiration @mozilla.org/places/expiration;1
component {803938d5-e26d-4453-bf46-ad4b26e41114} PlacesCategoriesStarter.js
contract @mozilla.org/places/categoriesStarter;1 {803938d5-e26d-4453-bf46-ad4b26e41114}
category idle-daily PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
category places-init-complete PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
component {d056186c-28a0-494e-aacc-9e433772b143} ColorAnalyzer.js
contract @mozilla.org/places/colorAnalyzer;1 {d056186c-28a0-494e-aacc-9e433772b143}
component {f964a319-397a-4d21-8be6-5cdd1ee3e3ae} UnifiedComplete.js
contract @mozilla.org/autocomplete/search;1?name=unifiedcomplete {f964a319-397a-4d21-8be6-5cdd1ee3e3ae}
component {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32} PageIconProtocolHandler.js
contract @mozilla.org/network/protocol;1?name=page-icon {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}
component {47a45e5f-691e-4799-8686-14f8d3fc0f8c} mozProtocolHandler.js
contract @mozilla.org/network/protocol;1?name=moz {47a45e5f-691e-4799-8686-14f8d3fc0f8c}
component {6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344} nsDefaultCLH.js
contract @mozilla.org/toolkit/default-clh;1 {6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344}
category command-line-handler y-default @mozilla.org/toolkit/default-clh;1
component {e3f772f3-023f-4b32-b074-36cf0fd5d414} nsContentPrefService.js
contract @mozilla.org/content-pref/service;1 {e3f772f3-023f-4b32-b074-36cf0fd5d414}
component {8df290ae-dcaa-4c11-98a5-2429a4dc97bb} nsContentPrefService.js
contract @mozilla.org/content-pref/hostname-grouper;1 {8df290ae-dcaa-4c11-98a5-2429a4dc97bb}
component {e35d5067-95bc-4029-8432-e8f1e431148d} nsContentDispatchChooser.js
contract @mozilla.org/content-dispatch-chooser;1 {e35d5067-95bc-4029-8432-e8f1e431148d}
component {220cc253-b60f-41f6-b9cf-fdcb325f970f} nsHandlerService-json.js
contract @mozilla.org/uriloader/handler-service;1 {220cc253-b60f-41f6-b9cf-fdcb325f970f} process=main
component {32314cc8-22f7-4f7f-a645-1a45453ba6a6} nsHandlerService.js
contract @mozilla.org/uriloader/handler-service-rdf;1 {32314cc8-22f7-4f7f-a645-1a45453ba6a6} process=main
component {8b1ae382-51a9-4972-b930-56977a57919d} nsWebHandlerApp.js
contract @mozilla.org/uriloader/web-handler-app;1 {8b1ae382-51a9-4972-b930-56977a57919d}
component {c11c21b2-71c9-4f87-a0f8-5e13f50495fd} nsFormAutoComplete.js
contract @mozilla.org/satchel/form-autocomplete;1 {c11c21b2-71c9-4f87-a0f8-5e13f50495fd}
component {bf1e01d0-953e-11df-981c-0800200c9a66} nsInputListAutoComplete.js
contract @mozilla.org/satchel/inputlist-autocomplete;1 {bf1e01d0-953e-11df-981c-0800200c9a66}
component {3a0012eb-007f-4bb8-aa81-a07385f77a25} FormHistoryStartup.js
contract @mozilla.org/satchel/form-history-startup;1 {3a0012eb-007f-4bb8-aa81-a07385f77a25}
category profile-after-change formHistoryStartup @mozilla.org/satchel/form-history-startup;1
category idle-daily formHistoryStartup @mozilla.org/satchel/form-history-startup;1
component {1f34bc80-1bc7-11d6-a384-d705dd0746fc} contentAreaDropListener.js
contract @mozilla.org/content/dropped-link-handler;1 {1f34bc80-1bc7-11d6-a384-d705dd0746fc}
component {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} nsINIProcessor.js
contract @mozilla.org/xpcom/ini-processor-factory;1 {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}
component {1c978d25-b37f-43a8-a2d6-0c7a239ead87} nsPrompter.js
contract @mozilla.org/prompter;1 {1c978d25-b37f-43a8-a2d6-0c7a239ead87}
component {6e134924-6c3a-4d86-81ac-69432dd971dc} nsPrompter.js
contract @mozilla.org/network/authprompt-adapter-factory;1 {6e134924-6c3a-4d86-81ac-69432dd971dc}
component {7ad1b327-6dfa-46ec-9234-f2a620ea7e00} nsPrompter.js
contract @mozilla.org/embedcomp/prompt-service;1 {7ad1b327-6dfa-46ec-9234-f2a620ea7e00}
component {74b89fb0-f200-4ae8-a3ec-dd164117f6de} Weave.js
contract @mozilla.org/weave/service;1 {74b89fb0-f200-4ae8-a3ec-dd164117f6de}
component {d28f8a0b-95da-48f4-b712-caf37097be41} Weave.js
contract @mozilla.org/network/protocol/about;1?what=sync-log {d28f8a0b-95da-48f4-b712-caf37097be41}
resource services-sync resource://gre/modules/services-sync/
component {1b7db999-2ecd-4abf-bb95-a726896798ca} FxAccountsPush.js process=main
contract @mozilla.org/fxaccounts/push;1 {1b7db999-2ecd-4abf-bb95-a726896798ca}
category push chrome://fxa-device-update @mozilla.org/fxaccounts/push;1
component {d9cd00ba-aa4d-47b1-8792-b1fe0cd35060} captivedetect.js
contract @mozilla.org/toolkit/captive-detector;1 {d9cd00ba-aa4d-47b1-8792-b1fe0cd35060}
resource services-common resource://gre/modules/services-common/
resource services-crypto resource://gre/modules/services-crypto/
component {117b219f-92fe-4bd2-a21b-95a342a9d474} TelemetryStartup.js
contract @mozilla.org/base/telemetry-startup;1 {117b219f-92fe-4bd2-a21b-95a342a9d474}
category profile-after-change TelemetryStartup @mozilla.org/base/telemetry-startup;1 process=main
component {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea} XULStore.js
contract @mozilla.org/xul/xulstore;1 {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}
component {86FB70EC-90FF-45AD-A1C1-F77D3C1184E9} recording-cmdline.js
contract @mozilla.org/commandlinehandler/general-startup;1?type=recording {86FB70EC-90FF-45AD-A1C1-F77D3C1184E9}
category command-line-handler m-recording @mozilla.org/commandlinehandler/general-startup;1?type=recording
component {51c65f5d-0de5-4edc-9058-60e50cef77f8} htmlMenuBuilder.js
contract @mozilla.org/content/html-menu-builder;1 {51c65f5d-0de5-4edc-9058-60e50cef77f8}
component {37f819b0-0b5c-11e3-8ffd-0800200c9a66} NotificationStorage.js
contract @mozilla.org/notificationStorage;1 {37f819b0-0b5c-11e3-8ffd-0800200c9a66}
component {cde1d019-fad8-4044-b141-65fb4fb7a245} Push.js
contract @mozilla.org/push/PushManager;1 {cde1d019-fad8-4044-b141-65fb4fb7a245}
component {daaa8d73-677e-4233-8acd-2c404bd01658} PushComponents.js
contract @mozilla.org/push/Service;1 {daaa8d73-677e-4233-8acd-2c404bd01658}
category app-startup PushServiceParent @mozilla.org/push/Service;1 process=main
category android-push-service PushServiceParent @mozilla.org/push/Service;1 process=main
component {4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd} RemoteWebNavigation.js process=main
contract @mozilla.org/remote-web-navigation;1 {4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd} process=main
component {c616fcfd-9737-41f1-aa74-cee72a38f91b} ProcessSelector.js
component {2dc08eaf-6eef-4394-b1df-a3a927c1290b} ProcessSelector.js
contract @mozilla.org/ipc/processselector;1 {2dc08eaf-6eef-4394-b1df-a3a927c1290b}
component {e740ddb4-18b4-4aac-8ae1-9b0f4320769d} SlowScriptDebug.js
contract @mozilla.org/dom/slow-script-debug;1 {e740ddb4-18b4-4aac-8ae1-9b0f4320769d}
component {bdc2e533-b308-4708-ac8e-a8bfade6d851} PeerConnection.js
component {d1748d4c-7f6a-4dc5-add6-d55b7678537e} PeerConnection.js
component {02b9970c-433d-4cc2-923d-f7028ac66073} PeerConnection.js
component {1775081b-b62d-4954-8ffe-a067bbf508a7} PeerConnection.js
component {7293e901-2be3-4c02-b4bd-cbef6fc24f78} PeerConnection.js
component {7fe6e18b-0da3-4056-bf3b-440ef3809e06} PeerConnection.js
component {0fb47c47-a205-4583-a9fc-cbadf8c95880} PeerConnection.js
component {4fff5d46-d827-4cd4-a970-8fd53977440e} PeerConnection.js
component {d974b814-8fde-411c-8c45-b86791b81030} PeerConnection.js
component {74b2122d-65a8-4824-aa9e-3d664cb75dc2} PeerConnection.js
component {3610C242-654E-11E6-8EC0-6D1BE389A607} PeerConnection.js
contract @mozilla.org/dom/peerconnection;1 {bdc2e533-b308-4708-ac8e-a8bfade6d851}
contract @mozilla.org/dom/peerconnectionobserver;1 {d1748d4c-7f6a-4dc5-add6-d55b7678537e}
contract @mozilla.org/dom/rtcdtmfsender;1 {3610C242-654E-11E6-8EC0-6D1BE389A607}
contract @mozilla.org/dom/rtcicecandidate;1 {02b9970c-433d-4cc2-923d-f7028ac66073}
contract @mozilla.org/dom/rtcsessiondescription;1 {1775081b-b62d-4954-8ffe-a067bbf508a7}
contract @mozilla.org/dom/peerconnectionmanager;1 {7293e901-2be3-4c02-b4bd-cbef6fc24f78}
contract @mozilla.org/dom/rtcstatsreport;1 {7fe6e18b-0da3-4056-bf3b-440ef3809e06}
contract @mozilla.org/dom/peerconnectionstatic;1 {0fb47c47-a205-4583-a9fc-cbadf8c95880}
contract @mozilla.org/dom/rtpsender;1 {4fff5d46-d827-4cd4-a970-8fd53977440e}
contract @mozilla.org/dom/rtpreceiver;1 {d974b814-8fde-411c-8c45-b86791b81030}
contract @mozilla.org/dom/createofferrequest;1 {74b2122d-65a8-4824-aa9e-3d664cb75dc2}
component {786a1369-dca5-4adc-8486-33d23c88010a} marionette.js
contract @mozilla.org/remote/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
category command-line-handler b-marionette @mozilla.org/remote/marionette;1
category profile-after-change Marionette @mozilla.org/remote/marionette;1
component {35c496de-a115-475d-93b5-ffa3f3ae6fe3} nsAsyncShutdown.js
contract @mozilla.org/async-shutdown-service;1 {35c496de-a115-475d-93b5-ffa3f3ae6fe3}
component {f4079b8b-ede5-4b90-a112-5b415a931deb} PresentationControlService.js
contract @mozilla.org/presentation/control-service;1 {f4079b8b-ede5-4b90-a112-5b415a931deb}
component {dd2bbf2f-3399-4389-8f5f-d382afb8b2d6} PresentationDataChannelSessionTransport.js
contract @mozilla.org/presentation/datachanneltransport;1 {dd2bbf2f-3399-4389-8f5f-d382afb8b2d6}
component {215b2f62-46e2-4004-a3d1-6858e56c20f3} PresentationDataChannelSessionTransport.js
contract @mozilla.org/presentation/datachanneltransportbuilder;1 {215b2f62-46e2-4004-a3d1-6858e56c20f3}
component {35ec195a-e8d0-4300-83af-c8a2cc84b4a3} mozIntl.js
contract @mozilla.org/mozintl;1 {35ec195a-e8d0-4300-83af-c8a2cc84b4a3}
category webextension-scripts toolkit chrome://extensions/content/ext-toolkit.js
category webextension-scripts-content toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-devtools toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-addon toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-schemas events chrome://extensions/content/schemas/events.json
category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
component {21f9819e-4cdf-49f9-85a0-850af91a5058} extension-process-script.js
contract @mozilla.org/webextensions/extension-process-script;1 {21f9819e-4cdf-49f9-85a0-850af91a5058}
component {26a4a019-2827-4a89-a85c-5931a678823a} nsUrlClassifierLib.js
contract @mozilla.org/url-classifier/jslib;1 {26a4a019-2827-4a89-a85c-5931a678823a}
component {ca168834-cc00-48f9-b83c-fd018e58cae3} nsUrlClassifierListManager.js
contract @mozilla.org/url-classifier/listmanager;1 {ca168834-cc00-48f9-b83c-fd018e58cae3}
component {9111de73-9322-4bfc-8b65-2b727f3e6ec8} nsUrlClassifierHashCompleter.js
contract @mozilla.org/url-classifier/hashcompleter;1 {9111de73-9322-4bfc-8b65-2b727f3e6ec8}
component {a319b616-c45d-4037-8d86-01c592b5a9af} PrivateBrowsingTrackingProtectionWhitelist.js
contract @mozilla.org/pbm-tp-whitelist;1 {a319b616-c45d-4037-8d86-01c592b5a9af}
component {8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b} SecurityReporter.js
contract @mozilla.org/securityreporter;1 {8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b}
component {92668367-1b17-4190-86b2-1061b2179744} CrashService.js
contract @mozilla.org/crashservice;1 {92668367-1b17-4190-86b2-1061b2179744}
category profile-after-change CrashService @mozilla.org/crashservice;1
category profile-after-change nsTerminator @mozilla.org/toolkit/shutdown-terminator;1
component {3f78ada1-cba2-442a-82dd-d5fb300ddea7} nsTerminatorTelemetry.js
contract @mozilla.org/toolkit/shutdown-terminator-telemetry;1 {3f78ada1-cba2-442a-82dd-d5fb300ddea7}
category profile-after-change nsTerminatorTelemetry @mozilla.org/toolkit/shutdown-terminator-telemetry;1
component {f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd} SanityTest.js process=main
contract @mozilla.org/sanity-test;1 {f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd} process=main
category profile-after-change SanityTest @mozilla.org/sanity-test;1 process=main
PK
!<_  components/interfaces.xptXPCOM
TypeLib
' $h%?Jdx!8Pf}%4Me$6@Xi|FFNHtz4[38k᝽gIR$ӻ} 7hC$;b/KF=r
7NuVҖئߤ{A5{Xm>]M"G%
9a%aVGyvεEq"<FN/\A@",)G`k	3:p*ӯ$	j	W=$M~2hw	$`/?JPVJ,"}D۷yVA3EJYmb$~Ju7RT~H_\Ko}N<Ek4G]o="ֳJڌá8BI++;ӜXT+,_( b۫ f,,my܆E*4,-?N{%-G8X L{~\&>>8L߃q95a??hM5
@
@ mE5IuZAA*VtC"lqwA=B_{(ӯ~$CPCzNtk9iCC"+LvD4Dge,aEBSYM#~DDD)HW:iqDEmG[gE@ETTFO͓ޕF5F{Lݥ_VpR:LFG/Jf[]G~GyKѵ8RGG:wC@Nbb6H"Hv:O9HIiG都I(IL)=eNy""IkI	f#	CVͧ–HIJ	K2գ|J&L)	K$@jtNS	߯Mɳ>	mWX\
21EDԡCXX
9w	IүsrNeY	Y'
MC7vl!JƈYBYjn,i\HYP{YYbgJO6'ʍsYZcLv+[[yI8,3ZR[[
,GC5[\_~M
Je\\vLR{\]FӑA`]^Ot~OkV6x_(aѴHL͌Cg+cc<
*aԘwOJcMc
UхD𹒨2Kcd
l圭]E 	fHhyd2dM
sc*E(_ dde#
yDI*Dee
&YBǓ/nEej
>QJ-K4ibnPn
ߞuno43>OJn$[Eoko=ıLJ7qyutop`,wB*
JOxqqa4LyH`sAqrFv_r2r ~BԂʚkRrs9`\ԟOss˰jxٗ
#LttTEDntujGJ0o5_v"vxP!Ҏ_)pwwxX!Ҏ_)pw,x1BٚB Xyy
O岍zzs"yz2kHԉӅz]zYA Nf4izz>RwKpYnJJ{"{82Lcꮼ6|@|U )AuSP}G},Ma'erF~6~zc@:hw.W~GDiEnZRR
-NB&J4uqFLr}e@S(_׍Kg4,\jj2o][@'ȍTȽ\-Ny[D2eK#[e\3tj@хEi!K@ ,y[j;)A*fѲƔA̲3<6ظ#[sF؜jPU_vDR9#1bo}vvG)!Mi0
E,WON9>#BS'Qy Ai~eL0LdA}br`hoH>Ca7$kGٗ
#L"Cң)G3;-eECc,y"
n(mI'׵ʝ4i6kg%CDžAհVOΑ2i+AoHݒ#8
NjtRF<s/o O	PD'VoImĢd)ofzI3.^RWD\1,+H1.pC-5K|xke}CvX
@JySmE>2&$;[K{8;&`%5LKk4GiH{S>{!NYn-§3fA~
H'OZOهnKmEAyM/{'١V
Cih(Qu
LG)Wӗy=
8QLO~E;8G?3V%Jȃj+!ǡ@Ɲ@S!X^L
T;A?)s 'ӾdestS[[G@P	.8<bII{- pHO!v(m(-\L&2-H4mBZ.;穼Hx6D(!O:7C]1J[|CFEΒw;j;lMx-5{G(OCG+I$YHBؿc
D&hw(YA:=R5p#CJd@ē‘ZbFJ8(G.6MzV9V@߸jđ[CTM͍b{>S'`@g>N1ƬGHU+{
HbȤ+Mաv³/'KLK5,e4Bʤ 0wT.@̀a͝J"] NeӐ@jnˬU QpN
K=51 pNE>n|v]\| ƚ@l,BxoDs͋ y8IVmnLfD٘ @ĔI/~]!^EVӑZ!a(FqW?!Z(G10]pۼ!oGڿS1'0ߏ"&~G(cW"XeenEfۯ"">%Cr/l}":F"<L#Tb<i#Nc1;CH~#bztzEvcivg0	#"X/^#%}<@3$l.#֊#jVD.92#BFfѿ#?8@W,
7#ޜM
aR$eHCUE$4FבzJ$S8Fc-1C?
G$THx^Ib_{$[NnmXjZ$R%K@6H1Z$)N,(\dш$>3@%nRePy${ClTس$ddBϗU$cC_Rev%8WrOٍL%Kd9ԏ=:%U0OC~
,RM%q˵U
D8uߛU?%{GCĿƿ{%7sCǼ D<Z(A%ޓh^N؄<:&D.E=;Ri'=-lcGZde
	'Q'FmXz4'ԛ(I໪ #,=		'מBC	
(TjO|fj

5(ЄMf 1\
Mc("@	7 f|(>FԘOΚ($vCN

8(\3f@4v9
[
(^G8)8=Mݟ*)oqM-
)WM@zAvS<i)œE`6>%[@)_K(W:*3uƈ@Wt).K*c4NØeVTF:*WH!DC+@@WxJ5z+DWGssL%+y	B9S?4,"@a#),8e#J`{i~<,=&Iנ],tbrDsj#-!Jcr._-	/nFcgk!|-Ll&""->P67KKG0{""-pxQJE`"&-KEI_^))-:P/۫ f)*.*!Kxpb:X-***.C2ڿD+1=*1*.K]sXB.**.F{J꡻gs+,.^>@\-0G.p_RIq	qv
11/'i
`CwPQc2c2/q+RNöpI23J/|ZQG	qJ33/#/A@V4
47/}KTԇ^4H4n/}NTԇ^44/}VTԇ^45l/gքDj56)/JeA*Ln4TK6t:/)X`Ә`$"==6/}H܂7[===d/H B#=k=0AۇHvJ7==0%C[=&s?_=>w0FV2wD̐EG^>>1Fľ* 9K>?G113r,AjE?@l1eL5DƳ7@@19U@-ʔ[AAL1մK˳߽O
[AqA1”:ǒ0ܢB]B1iO-jN0"BC2xM!эC#C>21tYO@6yvCYC2;uNACC2^V7bCKAED
EQ2uͯmBBFF2>	E_o
f/FGF2]!F01rGHM3#KL}˥eHH3jMӘ`$"I@I]4x8uԔ ;IdI4fԕ( ;J0JM54f1Eo JpJ51>JLMsJKt5A(YB<f-EYҾKK5[$C(xLL5KG aLL5yӡPDMM.5-~H~
rMMNN&5WCe
XWNN5ROԊV<e|;NO5tL4DPP6.ÂJ؍괗PTP6n>G}֍hcVWO6jK5G1NWW7kD[JR/X`X7"S#NлzNXX7Vz+KEH;_uXY7u2&B+?YMYz7nbOrfUYY7uӗM[JsZ	ZF8ԎiK۵xbZ^8iME]h,;``8}IǵX'vNla
b8lY?bc09la&6OBvI*cmc:<^J@
d cd:u
XJ_cMdwd:Ы۫ fdd:*Nţ@);deF:DDڞ?Mg6-eoe:H9.oBef:ه_J‡',fjf:yԌIROc
1gWjp;LwvFgsll7;3NȦŹvlVm0<Py~J"f^n9o2<S`HJҼ_/p%p|=;uUIBDO`qq*=yuNA8f[]q9q=qآ@>5bqr=WPD(B{qr6ru=SKpXrr=6<H,	s#t="XMFdvL"uwu=/T7M<&s$uv>X,Cff_waw}>_
EOfwx+>b-@Bd?Fxx>҈S$F37xy7?L_~DO7	Eyhy?ΐG*@>͇]6yz?[LGҸUCzNzc@Kp
@Gzxz@ThM+e*Ǯ{
{,@NLZUZ{]}@ROFF~-~L@eJO!{1DZ@*VFxz@SBť|>HXAF6ݓA(l6Hؿ%AeeNObN'vALחSz8QK!\B*NF=,]SBZ3E_j-;B_sNŚQ)4BBZDopa&A,BXA33Cc\SELNV&)UCqhEH?J(ChMy*̕PDt7A@w!:?K7Dl+KAcN|
D>N@w'2^@NT{DDbzHQ*IDAV`A]waDEBMe%S_EސOȸq?E]B40Cһ3Ehb+Fl5iFElO:؏XEoX)Is[E/PK-ժZ5FAK?MkmhF"mA"]MnF\$Hί2kF7+.@Ay6xA@XG6
WJ]e'9G!.MXgdΚG+`oAgroBQPHIӜ`l%HIӜ`l[H2I2		ͅ;HTtGJbWK\{I,8N+s__UICDbko#I"A~HGzE$i%ܳ<IoOWan`]J*hӓK@JACf&WLJG$@1_jJ9@{4|J07-۩ f
J6J"nP6B8^Kq:
G{KtÚiJcK-nB߮?_&|fK|LL=	KM12#>YKI(W^Kу!ijnL#HMJ5n??L>tbCK#89+uUL{ktK=0-~LXA7M%lھC"i,M@Ր=QM.6N љ{<$Mg2ʝAvMk;<Eb.'M}bG%O9Ϗ+(M4uLX#Ԕ6]MoҎ_)pM8QNB6Yo"ބMjO8`))cWF,^M或Aނ7pJN%Gm$CBnN(&=F\WN5JtK*t^N⌂@N?)gKWP/6-^O
՝:`OxOH;7OfJ2[j'O) KقQ_T.`OﴐUg7 fP	isHöRƒ]|P[.8JS3iP`4վ[cj<7PTiJ|SPlOH}:^PBVyQ4|NWQ1*F18uUwQ$UN#IQڭ:D̶ sV"R)
@ŒlѡR~So)Kj\f"RŀK
<MOT61i5R˸9ncJpplI%Rp;.J'y
k6PR婖ЩN$HS+WpR,L#wR?HtKnS	9H r)<SͼN0EyCSbFkT+f%5BjW*4SkT9	MLftWTD+M-Ś+Y8U0=E4cU^/A݈Nֹ"UՐ=QU*FMCp`4U^Iy?G'ދ_UխkUrG#e	
-Uֶ#J`D@
rUJXN	VJ2vb(@\,VoC)CE:)^WCpOwWu`67 f2Wx$/BGWzLLvu>fO-W%[UM',FWj`:ӌ`'PW٧J].w9Xѓ)Idpi}+{X*FH͐y*XBuyJ.SXIT5FIhX0{HҘ_)}X0{HҘ_)}NY?bMci^Yf vA36YVZ'AJʂ$LPZ1ǡ"ҚWdest"ZZTN=U)t5[ZGpC.Kg狗n[EOrWS[6uO`G6c_G3[9SI;ڵD[QTIdMڏ0d[2YA:|P#[9(ӏ]=8S\͛ZsGI\Z\kGPJY(!"]\_@>Fqp##\^(J
.`6z$$\ZJUOZgW%}%]ς":Fn,%.]z8wN|]]D11]&ZC)C>&2$3A]HO&&b404[]%KEy\}4n5^
}EáZuڏ56^>O{wGʧţ*6U6^s@LL>77s^ʥv`J:zwq77^(
A<D]78_
K8@t/j8&8W_V|5l2A@h>:8f:_C\4N2t%z;O;j_`pi f;y;_bI6 xzt;<_߄Ԁw`<=?`8aIm.t7=z=`DUH1gGQ==`|*,
H@VW>@(`ޙTJc|4cPdAB`cH`b]BBa=ӭYMcPBCsa[=cFK@CCao[H	ӌ`CDa4	HW<nD,Eb Hș~d-FeG'b84?CTJDFGHb\DyHHbj\E1GE
3IIbwYOD>/Z
IJ6b=AF@9JnJb:F\2&B)JK<bnB%йBR:KwKbi@`_bmKLbѶOJL6Lfbu<B8LLcM>}AP1OLMchL,p>KP'NO>cr%dA8:EO}P
dEiK?
$PQ]d=K,D`1bikRR:djPNm*c<`RWRdv7	ӌ`RRdEZeFPfSSKdPXMI:SSeHvMΞTTde+YEב'PUTUeN)mE+}tk QKUUe>n3Kfr)>cVXVeHH WLX"eJKNja=XXe#Oj&^
LYY8fN:2DYMYf!lJtD}!HNY[f8j KǟXd\{\fWFH>Ϧd]^f\
B)&-Oi^_/f?VFl߿_|_g~eBW`0`g/EDXtaag"eOs׊A`7b-bg#]NI\kcbcg?mSAcdWgpcFLWddg%G[hdeYgWrA"!t5ef"gHA	5jfgfg$u@d#ffh~CˠC^ggzh{4MyEggh4Q6C6A0gh:i8O$DbN5ijhuhiN6|%DF,R|K8hhiP(D!obi#i>iGڒ]M'iUijiO.	Lɚ(iqik	QlLm0rX0ijkK<gE{X}jBjkd,DK'﫥jk	l4e~׻k8kjl$NFXP#hkklSH+UllalŲCPun:llmg$FFۙe<|llm"h"mAV0jֻmm;mϐ0Ց
=mbmymtylF
{ʰmmn>&EMJegmnnc@61o_pnHzqdqo+XӾ6K`Hqqo0v7L,&QnqrOoT!LquI-)(irtom_CM	ϣu{v`oFM\G\vvp\BB;̴wx"p;S$@ƞSpxyjpdQKxZyz!phgO
0z6zp`J$d1z{pҴRH͍7kS{N{p;z>K˰٘L{|q5Dь`<|}qiSAa6aն}}:qf*J}M}wrDHw/Yr}~s0eŸn{/Xs2SqәN_oss-ǎCF+9ٵMsEFz
9:tMGDIG&N3rt"JD.uFbSUt%a@QptCזI=C~M"CtU1cAXVtI]Ftimr:u!ĘEδb{؂uvy(NlUuz.G3D3Q!5v21#ڑGA. LvΙɎCH|BIekvW NLe@YpxyvGǗI^t
h$ ^w([LD2[6w,lھC"iw!=M"mwO8%w*`o/KFxeNK`&x3.ClO[!xA@p"-xџG2=է;]y"1CR}o1Ly%&8|E1h
1Ny(ӏ]=zxFEi" WWz%"gey{#Tz0|llھC"i@xzO1Ec]z9ˡ?NߧCbBlzL#QY{E-zHכ.{xrvGdzfm?{pA
C_0aظl{BmI#{{ƺ,H`L2_
|Q,X*F%9uRq!W|XEHduHJa9v}	HCC맧gݦ}ԍHǬ7{Ѭ%~Xn?E!Fid~ O' ft~"^_qןce~U>xM9/K~݂QUHE/,d\4~.F+]ʐH	3gKFc')Jzܖ50JuN[6DIIdwϒNp
hǸTE60U\\-u	Dno>̑w1:K*1G=/K>IsD	a,SRVIDOez_ߧaGf
GjBᨹwrj9XެJ֐
q΁jI.AI9QDB۽:1XvnB⊩_*7
.J gM@s!U3`ZEPCUx.i,Ħxz90]mK޶	f|DDp"}uc_eLE?U>	HrHKnՊsD)P@FaqEYWO$=ĬL
BmX^0X&&@A?F +Ĕ|]@҃M(Nǭކ͉pG[~1Ȑ/!L=6]I;*IiƇCU9D|GS:	~ͮ'e0EPW3
J&vpvF.΀^fEbMȿPb5IXVQm
ψj kf׏Sz%E*~Շ7Kd͒6$	с_~O8\l+*UJDfGNp,$H5H@KXe!_ґ>_JCt
/Ҡ6CԓO2,ocَ܈eV@(ӏ]='DmE0=JKZk.F1L)Aa*O`LqpBۘCNL]WK܉2)!EױM0ݡR~JQoy<Š-w EJ)I[⋊DѥG4Z4dc]KVFk}ʔnLbҗe1|j=!(ʒTz
9@G-#B%䯋.$oӳ_fp=λIE1Nҋ9` iE%Y4BvFFIA*:hrg(kHhه{+>@ytsWl4Z铋3AF@oA]%0C}Fbv~#?o]^2F-UJ*X~ۼ@Qh{a pEHRx1D "w4MNKݛ549bԘhXe27Lыq5ﴌr7GQ
/-‘DOU68A qDR`Bèeǡ(YCwHhTt
O`A񒾌eEFE~YnF^aTSII9Üd4K^팬Ia%mՂ~tMq\H~|a9"HnԎTHu&6v&6uM?}q#Trӻ7͏nG@7חL|AJ{md	%{א;B;&}1FOG<luѐL+Lӎ/Z	MЋۣ%`NA3b&F#kPu7_R GW5T:.ҁ`:8.ҁ`:Lv|LJ/Tđ̩mDHP:@KL!J1E7D=b=Z@	b:TmxO0:q2
M5LO<
X
˒F}qG$QmFO明GG#mxô,ӌ`Ș_K]Hd"	Ki%^Ar*1xbO\3U5Y퓭,+JtlCOEqv,j7l fLFJnOfucRBQ[ydE(+4{.QL+EBl'ʄ[#Җ`V^E+u.OكEiҽK`H=7>Jc16w!d48 0|Ҽ_/yAB㢅-^xVxEk j<ҖT}C(f pjI?!*k2  d eL铻CsLb!!FqH#!
S!"L,C.͠"!"d'~@
y""͗YxD!v5"#W lҘB`$"##ۗAџ#%%|K5&M׭I<] %&Z{5CUTlF&&ҙ@oD	A$6
&&LVO dOd"j''g^$>~''"p⋋ f(&(R;reOADL(g(WΓEEuW"O|)_)o%@\%N*_+)x<M1+,B"HWɆI-d-^HLN14-.p|XoHI&/S0}YۈCжcBЏ12TOe#D2H2Su03446IA44՛C45PBԨ,vA6:6Q1x|,Jش9xj 6f6R@+E676HjUύA776IWL+^~78˝g+Eұ]9L:	2M\'::՞R>|oMwJg;;8AJ]Kg;W;$$KT}Xג;;UnJٳFß<"<o߮ۚKؽX<=u{է<<6=>G#$IE 6>>aXNɶ)>?TCӀ q+!?8?tZ'G
_-J??Ñ6FӲ25?@($e!Iu}[8s@W@zn@)Il¼r@@#`#CI->"T@@ޠ;cAd+|AAv;]ABB~h8B@DnBB(ZtLpE~BB2JKS"l
BC1E%As{(C\Dq_M﷗7{DF
p$ӾY 6gFFYFI]haSGGJt.>D@[bGxGU^pFKRxyCHcHIJWHHCOÓUCOIIeo1K#dwgIIԿB7*sIIࣧ._qJݏ0YIJQvM[Rj]JJvhnO-txKK6w-cI^i1KGKpv[O˅KLLɮ}MM񥒦6!O+3ϭ>N"NvNH8]kNN<QE{qVOOUHAa'z/jOP#L( HPoPQQiFwc*]QRæ`J(S_T)?pӓ3K@TVTϐғ._2WWnbLpWYc}Gƨ%qYYh8G6y_ZZ5m}GCHǮ!j}ZJZb(]%EO?Zu[WFexw[C[g_ȴCPÏ4J8[\r|AM߀>]T_è:+uLhݪ`a$GUQ\1beOj$Bxpngg3H#_6F|AghQR;B=}LhkӍbO>smcnz(pK<}o1oWDL6o6p*p|ӵDpq(ϖAJዾq;qpg,ե;qqժAi,`Xr rRQqExҒrwrfPJ@trrܫoF0J9wLrt=oHAwuDu_꣐JӺ_]unu~DކEO'mS1uu,KiyGgμ)E9uvFcL#ߩvwJ;O|ivPw#wa;rHz8]B5wwꬸ<@DP,>|Wx%xȎ|?9BǬ1cw>xy}Gq"lJUz1zV_M{[}?ұ{{Y$7K5`ʫu|9|ʭ7*K{~
1}R}ICED}~#^!EDC
X~Rd)KER
|q2+1*NO豑,>9PxGMG :,}A$8P(FtA혷.tͥ/G?,h	P^k2F`C'
nPZ۫ f0o!M<8-UIf|MN배Vφ{[@K毶 `7GJ\-ct?@شBi]Ktׯ fm
B[ǵ1D.2,7EWt
}_`1h,LquDh
e(DC1Z`!9BKmRЏ䴩 fѱ৯Y6@f.~>tO}M+v4)ҍPdest~J;;4LjHDj)c(1Bz')sڲHE6m`Q7hmO;C%#JӺ_]:_6	NoB7ѳ}Gۨsmz
9O
F<K-l}O?Zׁe`^L!
?:FC)f䌗DG.ԗEulR8E,&PIH̼ ,-d>HOX0zBuV)LA8)Ŷ{K1JjɯZW$7F~O2#)SF)?B]q^?^OXeAkIu߷|@A3CsҒZ
dѸ49Jݙ0k@TaW (CBFh,M!Z%`TllKڂ{VHeyJ
Lhq?*F̑&Dٸ" SM'gM(̹I}GjePrZw>+0NҸjcȹc?OK&43q㩳G֎ P@c9C\r!ͺ;.Lӈ"_|D%;SYкFc%뺑Y!@
Oi+lC)|ֹ<C@Q#qOɷ(jE/Z0;O5Մ.]y<EQMqKLjf5%;G>Δ0BXCZ}#caYBP1ʰ:ӌ`'/S1sFF%نzYj[ԾJbqצtv<<&bA^}^GON*D1^ďIJhGЈYd®vU=O{	βA*śÇ-)Lu׆c×T"LՅikn=ȅG"CUGt-.ҶoCfLO˙Ծ=_EOZ7i\2zN4枂̝޾نfٕGbGm1vΌ^FJa9aDk$KOrKXЭ9u@49kI7Ց
=ѓѹRaLҎ_)p׿ٸ+
KkT2Ӟ#7@.l}kN}MO}+JiZJŶVoIH ݂EBϺUja^(L}F~;+}/J
ֶ*Phb"2%C.Ý͗{٦铬H9Ib@EJ8:]&~GYfڐSsFb=COU#y!&HG0]3y"!Y@p/vܶWW?DOF'ޗ_ݩMo!-\ߣ˜+9.HB43r°aQNﮩ2/Z`ԙ#THBD@?RCߗmNBhJey)+}n@lbmig\}kB0Q5Dh,Bb$Snf
hQV#UEC鯥N}[OAݰNDxā&-H[7~uĽ:OۚyF$zF@2Mf
`.➖ f F#[XJ.gM.?F|v=Ep~vŊ}@ȆZbpŤsE9Iǣal)>:%Edw|e>AjEd>w
d
gAUݣxboƔYr'C83$wcAt/`WUO}߬lCh*{RǞ	EDZ7 ӏ_=P7fM%"=yGIۤ߈g(^\F!(GD7YȢ*Kcw{FlnGs++1ɩ4Iql%pp4yhCȍOV1tSaCoKfhow+cU)wERf~T؂	0cxML:Y#J*R

kkHH'Q9{|Kyp{ʉ[o@QEQ5ʭOGFYOd~@F=CJ~yEN_^0|O쀥_><f@;ٰɭF[6	c>|p@ygl,0ϪKDHtRcJ瞬q9t̮~KQjf_AUs@mnqBb
2k!^A	nFtմW$`FP:Ӷi}@Z۫ f_q(2*AGhR "/ΪGdKbQ#$t؄-7GgVQu$%x`dBB\%&.LKNʮzP&'pw}BpK4{)1?(I(y}8H.=?v((xHj8L))wc`nI$EMؑ))Vl(IN̞C7rC*
*4\AL6֝*K*QҊZ`*+Ա6qr~'+W,@8ۀI(]ߩ,,LqUӑZ,-тJӘ`$".&.Gщ@ҽdest.l.:n@P.*C..ICYYT./0*o_v{O꿒h>/i/ҪtkM]sN#B-//x@IѶmX00,kc)ROESj0G0ӲFcڦGZ&119!NҽK`H1b1!NҽK`H2q3#ND@ϼ>+35Ա#QHc!
;67ͪG |3j75;$.'qE襰ngk>>\? G̟>>oNFP%?"?taBpDa@@նӿZ'@A"Gܿ٬nQA6A㽀g#K"/OAB-x'b*HPC*BkBv[$HF_:BC[Ol_dCD
\NyPEE)y6A&ê#
@FFB_pJӘ`$"FiF8EHLOVFGvנL'paGI#4K4JJJ[H$	J<JJKsLeN99w]zsKL.םpJӘ`$"LLHDk(fLLEZXyZu[LM9Z|қ_MlMZª|қ_NN^GI!+O
O$2O]H-ոO+OVJOIO{OJFNdOOىF@F,{OO@&AMPHhPPUڇAs&{PPʧ1KMQyQ2Q
\8BJ.R;URT$.ҝdestV&V:6\x`Ndc%?VQVۗE),WFWb䴩 fWuW+$eDX%WW&<WG뵷3XX.܇y"Mϗ[+X?Z܏:S@1i*Q0\]ܼ<MԚ1a]]a"^O^z^4_S# ʶO%ˢ?-_`ݟ~tOa7?`'`Jݼ/.Oʣn`gaôJӘ`$"aa[/`A*O6ahڊ flIlO1<^Gih!K=xEll#{N
2%1%m6mc"7F=
mm(0Z۫ fmm(iV<ICk1ڤЗnoBAVFPaλW,qsOFD,7ZtuVBaOϚG"k
N!u>vލ_MxDxJxwJӘ`$"xx
V5}B񜓁xzU1 ѽ_{o{p+U f{{߬JӘ`$"||=Qέ@ߎzzgK|b|pJӘ`$"||+flھC"i|}*>l^
L9"}[}eD39 f}~iEA.~zēҕBҎ_)p	+ӞH6FiILt8oт4վ[`gln5H`0FS5gJӘ`$"Fm:$zҀ`k:$zҀ`ke侫Icm<`yuMӡtZR̀F
BG;
V60JӘ`$"=baOD_"X4^@JӘ`$"Þ4_E#
UXG)
JӘ`$"LrG^lھC"iDlRPJӘ`$"JӘ`$"*Pj ӌ`ur?K2t6!q@F3tnGUDī!KQr7HjS*b76Ilx=%F圹"H5ovPNE8$
*4 Kƚ>G䮜W1wґLeKB|_&CF<'f^#b
AҞB/"raV7+Ms\`S4No:cWZE-_cndG	8Lu^]ftyAV?Т,m#<Gİc`'i磩T8KJUbk
~HACo/B	Lc#ҪZIUL.<	<<xh?N[Oal8n`kaJSq8KNL)Ɉ'D8"Shz80Ir-AŦ!z,,\=ԐPDyQงKo@s8K@&MCгyAiq&\OS[*U]03Ҏ_)p]13Ҏ_)p/닊]C[<
V.ΆhJ4ϜހIyt8@s0;gֳC0b06A1vyey]q<@|KgW4-@dtu{˭WG1	ebWAؐ#	8>)Q*0NS³Jb\XEwˍNDŽ: gHK2&}ȥ-{Nw;nȾpXnTDk<{v'd5SD
>eɉ/F	K5XL?Ka2jyʏMʜK2WȶLԒ"*]$]5̮kFHؖӇͬn[F4{rK3Olxn.Иw%z5F<,4Sӯp$ҵӬE+Bװ5vG(-MvٔٴpٯF/"oٻp;@FWZ29D54Ga] bڎSJۓeXڡDjA~hiV$/n>J؍Lӱ6n$\C+M1gtF2
H'jšʦݔݳ)`AlF.8^QEkhG	(WLॄ).$2ElE7l7Z)ujLć0myVIoTCm
vbOX&· g6M$Pf;@v-k<NJ`]z-
TNias]Ad:CD[.'*_P-yHy姸l	Czӷ]QHcًAANFd&۸]Oٵ\JuUWq$j_HożִgC	qBygߊ9 foKxIѥoP
^_+C}[dђ'9"(A0^~8pBͥY9ْ+Lu 3պ$5[;I|y(X	QoIlL@z8Dh}XTk/N&sSzJq-<:A1MmXe"NqIܑޥsArtB'H<)QLMo
blhJ戰`nEk+`G¢͸;h`#ZfTI'/Wsm
BhqdBkS
Bj]R-IDہVs4p߱h fBSs3O͐Sl8(opI2i}ӓ3K@&3JӜ`IlAO&%츄M񳜞GWM$?AD=j0UvkL륁}ChyE"l\bۊIі~0}	e JG(f
`וC4zxt
qlDꝝ%iinsIAsyncVerifyRedirectReadyCallbacknsIConsoleReportCollectornsIContentnsIDOMCSSStyleDeclarationnsIDOMCSSStyleSheetnsIDOMCharacterDatansIDOMClientRectnsIDOMDOMCursornsIDOMDOMRequestnsIDOMDataTransfernsIDOMDocumentnsIDOMDocumentFragmentnsIDOMDragEventnsIDOMElementnsIDOMEventnsIDOMGeoPositionErrornsIDOMHTMLCanvasElementnsIDOMHTMLFormElementnsIDOMHTMLInputElementnsIDOMHTMLMenuItemElementnsIDOMKeyEventnsIDOMMediaListnsIDOMNodensIDOMRangensIDOMXULElementnsIDocumentnsIFramensIGlobalObjectnsIInputAvailableCallbacknsINodensIObjectFramensIPaymentActionCallbacknsIPaymentActionRequestnsIPresentationAvailabilityListenernsIPrintSessionnsIProfilerStartParamsnsIScriptContextnsIScriptElementnsIScriptGlobalObjectnsIScrollObservernsISelectionnsIStreamingProtocolMetaDatansIThreadObservernsIWidgetnsIXMLHttpRequestUploadnsIXPCScriptablensPIDOMWindowInnernsPIDOMWindowOuternsSubDocumentFramensISupportsQueryInterfaceAddRefRelease`((nsIFactorycreateInstancelockFactory22`

nsIAutoCompleteSimpleResultListeneronValueRemoved2\
nsIBrowserElementNextPaintListenerrecvNextPaint2mozIStoragePendingStatementcancel2nsIDOMXULTreeElementcolumnsviewviewbodyeditableeditableinputField2	```@`` `
@)
2`nsIChannelEventSinkasyncOnChannelRedirectREDIRECT_TEMPORARYREDIRECT_PERMANENTREDIRECT_INTERNALREDIRECT_STS_UPGRADE2amIAddonManagerStartupreadStartupDatainitializeExtensionsencodeBlobdecodeBlobenumerateZipFilereset2d`t``1D`nsIExpatSinkHandleStartElementHandleEndElementHandleCommentHandleCDataSectionHandleDoctypeDeclHandleCharacterDataHandleProcessingInstructionHandleXMLDeclarationReportError2	&7EX2j~5`
nsIBrowserSearchInitObserveronInitComplete2bnsIProgrammingLanguageUNKNOWNJAVASCRIPT2mozIStorageStatementRow2nsILoadGroupChildparentLoadGroupparentLoadGroupchildLoadGrouprootLoadGroup2`@		`	%`nsIInterfaceRequestorgetInterface2	`nsIImageLoadingContentloadingEnabledloadingEnabledimageBlockingStatusaddNativeObserverremoveNativeObserveraddObserverremoveObservergetRequestsetBlockedRequestcurrentRequestHasSizeframeCreatedframeDestroyedgetRequestTypecurrentURIloadImageWithChannelforceReloadforceImageStatenaturalWidthnaturalHeightonVisibilityChangeUNKNOWN_REQUESTCURRENT_REQUESTPENDING_REQUESTC	`
@	
	` 	C 	C
C
C
,`w
7(
I
 
_
 
l

{w`
`
`j




`
`(


nsIXULRuntimeinSafeModelogConsoleErrorslogConsoleErrorsOSXPCOMABIwidgetToolkitprocessTypeprocessIDuniqueProcessIDremoteTypebrowserTabsRemoteAutostartmaxWebProcessCountmultiprocessBlockPolicyaccessibilityEnabledaccessibleHandlerUsedshouldBlockIncompatJawsis64BitinvalidateCachesOnRestartensureContentProcessreplacedLockTimelastRunCrashIDisReleaseOrBetaisOfficialBrandingdefaultUpdateChanneldistributionIDisOfficialwindowsDLLBlocklistStatusPROCESS_TYPE_DEFAULTPROCESS_TYPE_PLUGINPROCESS_TYPE_CONTENTPROCESS_TYPE_IPDLUNITTESTPROCESS_TYPE_GMPLUGINPROCESS_TYPE_GPUE10S_MULTI_EXPERIMENT2:`
E`
@V
gjs````
```


`

#`

;`

C
]
r`

`

`



`

`

'<Vl}nsINestedEventLoopConditionisDone2`
imgINotificationObservernotifySIZE_AVAILABLEFRAME_UPDATEFRAME_COMPLETELOAD_COMPLETEDECODE_COMPLETEDISCARDUNLOCKED_DRAWIS_ANIMATEDHAS_TRANSPARENCY20w	7FSbp	nsIDeviceSensorDatatypexyzTYPE_ORIENTATIONTYPE_ACCELERATIONTYPE_PROXIMITYTYPE_LINEAR_ACCELERATIONTYPE_GYROSCOPETYPE_LIGHTTYPE_ROTATION_VECTORTYPE_GAME_ROTATION_VECTOR2/`4`	6`	8`	:K]lnsIProfileStartupdirectorydoStartup2W`1ansIDocShellloadURIloadStreaminternalLoadaddStatecreateLoadInfoprepareForNewContentModelsetCurrentURIfirePageHideNotificationpresContextGetPresShelleldestPresShellcontentViewerchromeEventHandlerchromeEventHandlercustomUserAgentcustomUserAgentallowPluginsallowPluginsallowJavascriptallowJavascriptallowMetaRedirectsallowMetaRedirectsallowSubframesallowSubframesallowImagesallowImagesallowMediaallowMediaallowDNSPrefetchallowDNSPrefetchallowWindowControlallowWindowControlallowContentRetargetingallowContentRetargetingallowContentRetargetingOnChildrenallowContentRetargetingOnChildreninheritPrivateBrowsingIdinheritPrivateBrowsingIdgetDocShellEnumeratorappTypeappTypeallowAuthallowAuthzoomzoommarginWidthmarginWidthmarginHeightmarginHeighttabToTreeOwnerbusyFlagsloadTypeloadTypedefaultLoadFlagsdefaultLoadFlagsisBeingDestroyedisExecutingOnLoadHandlerlayoutHistoryStatelayoutHistoryStateshouldSaveLayoutStatesecurityUIsecurityUIsuspendRefreshURIsresumeRefreshURIsbeginRestorefinishRestorerestoringDocumentuseErrorPagesuseErrorPagesdisplayLoadErrorfailedChannelpreviousTransIndexloadedTransIndexhistoryPurgedcurrentDocumentChannelsetChildOffsetisInUnloadchannelIsUnsafehasMixedActiveContentLoadedhasMixedActiveContentBlockedhasMixedDisplayContentLoadedhasMixedDisplayContentBlockedhasTrackingContentBlockedhasTrackingContentLoadedDetachEditorFromWindowisOffScreenBrowserisOffScreenBrowserprintPreviewcanExecuteScriptsisActiveisActiveSetIsPrerenderedisPrerenderedhistoryIDHistoryIDisAppTabisAppTabcreateAboutBlankContentViewerforceCreateAboutBlankContentViewercharsetcharsetgatherCharsetMenuTelemetryforcedCharsetforcedCharsetsetParentCharsetgetParentCharsetrecordProfileTimelineMarkersrecordProfileTimelineMarkersnowpopProfileTimelineMarkersaddWeakPrivacyTransitionObserveraddWeakReflowObserverremoveWeakReflowObservernotifyReflowObserversaddWeakScrollObserverremoveWeakScrollObservernotifyScrollObserversframeTypeframeTypeisMozBrowserisIsolatedMozBrowserElementisInIsolatedMozBrowserElementisInMozBrowserisTopLevelContentDocShellgetSameTypeParentIgnoreBrowserBoundariesgetSameTypeRootTreeItemIgnoreBrowserBoundariesasyncPanZoomEnabledsandboxFlagssandboxFlagsonePermittedSandboxedNavigatoronePermittedSandboxedNavigatorisSandboxedFrommixedContentChannelmixedContentChannelGetAllowMixedContentAndConnectionDatapluginsAllowedInCurrentDocfullscreenAllowedsetFullscreenAllowedorientationLocksetOrientationLockaffectPrivateSessionLifetimeaffectPrivateSessionLifetimemayEnableCharacterEncodingMenueditoreditoreditablehasEditingSessionmakeEditablegetChildSHEntryaddChildSHEntryuseGlobalHistoryuseGlobalHistoryremoveFromSessionHistorycreatedDynamicallycreatedDynamicallygetCurrentSHEntryisCommandEnableddoCommanddoCommandWithParamsIsInvisibleSetInvisibleGetScriptGlobalObjectdeviceSizeIsPageSizedeviceSizeIsPageSizesetOpenergetOpenernotifyJSRunToCompletionStartnotifyJSRunToCompletionStophasLoadedNonBlankURIwindowDraggingAllowedwindowDraggingAllowedcurrentScrollRestorationIsManualcurrentScrollRestorationIsManualgetOriginAttributessetOriginAttributeseditingSessiontabChildGetTabChildGetCommandManagertouchEventsOverridetouchEventsOverrideisOnlyToplevelInTabGroupawaitingLargeAllocuseTrackingProtectionuseTrackingProtectiondispatchLocationChangeEventINTERNAL_LOAD_FLAGS_NONEINTERNAL_LOAD_FLAGS_INHERIT_PRINCIPALINTERNAL_LOAD_FLAGS_DONT_SEND_REFERRERINTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUPINTERNAL_LOAD_FLAGS_FIRST_LOADINTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIERINTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIESINTERNAL_LOAD_FLAGS_IS_SRCDOCINTERNAL_LOAD_FLAGS_NO_OPENERENUMERATE_FORWARDSENUMERATE_BACKWARDSAPP_TYPE_UNKNOWNAPP_TYPE_MAILAPP_TYPE_EDITORBUSY_FLAGS_NONEBUSY_FLAGS_BUSYBUSY_FLAGS_BEFORE_PAGE_LOADBUSY_FLAGS_PAGE_LOADINGLOAD_CMD_NORMALLOAD_CMD_RELOADLOAD_CMD_HISTORYLOAD_CMD_PUSHSTATEFRAME_TYPE_REGULARFRAME_TYPE_BROWSERTOUCHEVENTS_OVERRIDE_DISABLEDTOUCHEVENTS_OVERRIDE_ENABLEDTOUCHEVENTS_OVERRIDE_NONE;
䀒
䀒u
F
@F@
@
`($`4` B`8@U8h@x`
@
`
@
`
@
`
@
`
@
`
@)
4`
@E
V`
@i
|`
@
`
@
`
@	
"`K8`@@H`
@R
\`@af`@r~`@

`
``@`@`
`
`@"5`
K`y@Vyat 
`
`
@
`
``` `7F`
Q`
a`
}`
`
`
`
`
(
`
@2
E`%R`
d`
@m
v`
`(`
@
@'@5(C
(T@@@
e`
@
`	`

		#(9(Rh`@r|`
`
`
`
`
`F`FD`
X`@er`F@F(F
`@@
@
@
(
)`
;
 P `
s`
H
`
`a@a`
`

`uuu
"`
@3
D]`
@p
@u`
`
(
(

('`
@
(
('(1
(N
j`
`
@
`
@
``$`р(-(9܀K`@_s`
`
`
@
  & M y    @!!"!5!I!Z!h!x!!!!!!!""#"6"T"qnsICollationFactoryCreateCollationCreateCollationForLocale2+`+`nsIDocumentStateListenerNotifyDocumentCreatedNotifyDocumentWillBeDestroyedNotifyDocumentStateChanged2,,&,D
extIEventItemtypedatapreventDefault2,,`,nsIWorkerDebuggerManagergetWorkerDebuggerEnumeratoraddListenerremoveListener2,`K-O-
OnsIAccessibleRoleROLE_NOTHINGROLE_TITLEBARROLE_MENUBARROLE_SCROLLBARROLE_GRIPROLE_SOUNDROLE_CURSORROLE_CARETROLE_ALERTROLE_WINDOWROLE_INTERNAL_FRAMEROLE_MENUPOPUPROLE_MENUITEMROLE_TOOLTIPROLE_APPLICATIONROLE_DOCUMENTROLE_PANEROLE_CHARTROLE_DIALOGROLE_BORDERROLE_GROUPINGROLE_SEPARATORROLE_TOOLBARROLE_STATUSBARROLE_TABLEROLE_COLUMNHEADERROLE_ROWHEADERROLE_COLUMNROLE_ROWROLE_CELLROLE_LINKROLE_HELPBALLOONROLE_CHARACTERROLE_LISTROLE_LISTITEMROLE_OUTLINEROLE_OUTLINEITEMROLE_PAGETABROLE_PROPERTYPAGEROLE_INDICATORROLE_GRAPHICROLE_STATICTEXTROLE_TEXT_LEAFROLE_PUSHBUTTONROLE_CHECKBUTTONROLE_RADIOBUTTONROLE_COMBOBOXROLE_DROPLISTROLE_PROGRESSBARROLE_DIALROLE_HOTKEYFIELDROLE_SLIDERROLE_SPINBUTTONROLE_DIAGRAMROLE_ANIMATIONROLE_EQUATIONROLE_BUTTONDROPDOWNROLE_BUTTONMENUROLE_BUTTONDROPDOWNGRIDROLE_WHITESPACEROLE_PAGETABLISTROLE_CLOCKROLE_SPLITBUTTONROLE_IPADDRESSROLE_ACCEL_LABELROLE_ARROWROLE_CANVASROLE_CHECK_MENU_ITEMROLE_COLOR_CHOOSERROLE_DATE_EDITORROLE_DESKTOP_ICONROLE_DESKTOP_FRAMEROLE_DIRECTORY_PANEROLE_FILE_CHOOSERROLE_FONT_CHOOSERROLE_CHROME_WINDOWROLE_GLASS_PANEROLE_HTML_CONTAINERROLE_ICONROLE_LABELROLE_LAYERED_PANEROLE_OPTION_PANEROLE_PASSWORD_TEXTROLE_POPUP_MENUROLE_RADIO_MENU_ITEMROLE_ROOT_PANEROLE_SCROLL_PANEROLE_SPLIT_PANEROLE_TABLE_COLUMN_HEADERROLE_TABLE_ROW_HEADERROLE_TEAR_OFF_MENU_ITEMROLE_TERMINALROLE_TEXT_CONTAINERROLE_TOGGLE_BUTTONROLE_TREE_TABLEROLE_VIEWPORTROLE_HEADERROLE_FOOTERROLE_PARAGRAPHROLE_RULERROLE_AUTOCOMPLETEROLE_EDITBARROLE_ENTRYROLE_CAPTIONROLE_DOCUMENT_FRAMEROLE_HEADINGROLE_PAGEROLE_SECTIONROLE_REDUNDANT_OBJECTROLE_FORMROLE_IMEROLE_APP_ROOTROLE_PARENT_MENUITEMROLE_CALENDARROLE_COMBOBOX_LISTROLE_COMBOBOX_OPTIONROLE_IMAGE_MAPROLE_OPTIONROLE_RICH_OPTIONROLE_LISTBOXROLE_FLAT_EQUATIONROLE_GRID_CELLROLE_EMBEDDED_OBJECTROLE_NOTEROLE_FIGUREROLE_CHECK_RICH_OPTIONROLE_DEFINITION_LISTROLE_TERMROLE_DEFINITIONROLE_KEYROLE_SWITCHROLE_MATHML_MATHROLE_MATHML_IDENTIFIERROLE_MATHML_NUMBERROLE_MATHML_OPERATORROLE_MATHML_TEXTROLE_MATHML_STRING_LITERALROLE_MATHML_GLYPHROLE_MATHML_ROWROLE_MATHML_FRACTIONROLE_MATHML_SQUARE_ROOTROLE_MATHML_ROOTROLE_MATHML_FENCEDROLE_MATHML_ENCLOSEDROLE_MATHML_STYLEROLE_MATHML_SUBROLE_MATHML_SUPROLE_MATHML_SUB_SUPROLE_MATHML_UNDERROLE_MATHML_OVERROLE_MATHML_UNDER_OVERROLE_MATHML_MULTISCRIPTSROLE_MATHML_TABLEROLE_MATHML_LABELED_ROWROLE_MATHML_TABLE_ROWROLE_MATHML_CELLROLE_MATHML_ACTIONROLE_MATHML_ERRORROLE_MATHML_STACKROLE_MATHML_LONG_DIVISIONROLE_MATHML_STACK_GROUPROLE_MATHML_STACK_ROWROLE_MATHML_STACK_CARRIESROLE_MATHML_STACK_CARRYROLE_MATHML_STACK_LINEROLE_RADIO_GROUPROLE_TEXTROLE_DETAILSROLE_SUMMARYROLE_LANDMARKROLE_NAVIGATIONROLE_FOOTNOTEROLE_ARTICLEROLE_REGIONROLE_EDITCOMBOBOX2-Y-f-t-------	-
--.
..".0.:.E.Q.].k.z.......... /!/"/##/0$/A%/N&/`'/o(/|)/*/+/,/-/.///0/102030!40150>60M70[80o90:0;0<0=0>0?0@0A0B1C1 D13E1DF1VG1iH1}I1J1K1L1M1N1O1P1Q2R2#S23T2HU2WV2hW2xX2Y2Z2[2\2]2^3_3`3a3*b39c3Dd3Ve3cf3ng3{h3i3j3k3l3m3n3o3p3q4
r4 s45t4Du4Pv4aw4nx4y4z4{4|4}4~4455
55'5>5Q5f5w555555666,6<6L6`6r666666777)7;7U7m77777778888-8:8FnsICookie2rawHostisSessionexpiryisHttpOnlycreationTimelastAccessed>>`
>`>`
>`>`nsIAccessibleScrollTypeSCROLL_TYPE_TOP_LEFTSCROLL_TYPE_BOTTOM_RIGHTSCROLL_TYPE_TOP_EDGESCROLL_TYPE_BOTTOM_EDGESCROLL_TYPE_LEFT_EDGESCROLL_TYPE_RIGHT_EDGESCROLL_TYPE_ANYWHERE2?*???X?m???nsIPrintProgressopenProgressDialogcloseProgressDialogregisterListenerunregisterListenerdoneInitinggetPrompterprocessCanceledByUserprocessCanceledByUser@【2x@
@1
@E@V@i@u`r@`
@@
nsIFileChannelfile2A%`1nsIApplicationCacheinitAsHandlemanifestURIgroupIDclientIDactiveusageactivatediscardmarkEntryunmarkEntrygetTypesgatherEntriesaddNamespacesgetMatchingNamespaceprofileDirectoryITEM_MANIFESTITEM_EXPLICITITEM_IMPLICITITEM_DYNAMICITEM_FOREIGNITEM_FALLBACKITEM_OPPORTUNISTIC2AQA^`AjArA{`
A`AAAAA`A@@AA`(A`1ABBB$B1B> BL@nsITextScrollscrollByLinesscrollByPages2C^ClnsIXULTreeBuildergetResourceAtIndexgetIndexOfResourceaddObserverremoveObserversort2C`C`CCCnsIEventListenerChangetargetchangedListenerNames2DK`8DR`nsIUpdateTimerManagerregisterTimerunregisterTimer2DwDextISessionStorageeventshassetget2D`dD`
DD`mozIStorageServiceopenAsyncDatabaseopenSpecialDatabaseopenDatabaseopenUnsharedDatabaseopenDatabaseWithFileURLbackupDatabaseFile2ES,Ee`Ey1`E1`E`E11`1nsIIncrementalStreamLoaderObserveronIncrementalDataonStreamComplete2FXU2FjU2nsIFormFillControllerfocusedInputattachToBrowserdetachFromBrowsermarkAsLoginManagerFieldmarkAsAutofillFieldshowPopup2F`FFFFFGG%nsIFTPChannellastModifiedTimelastModifiedTime2G`@GnsIFileMetadatasizelastModifiedgetFileDescriptor2G`G`G`nsIJSONencodeencodeToStreamdecodedecodeFromStreamencodeFromJSValdecodeToJSVal2H*H1p
H@`HG`HXHh`nsIProfileUnlockerunlockATTEMPT_QUITFORCE_QUIT2HHHnsICommandHandlerInitwindowwindow2I>`@IEnsINativeOSFileResultresultdispatchDurationMSexecutionDurationMS2I`I`	I`	nsIAccessibleImagegetImagePositiongetImageSize2I@@I@@nsITreeViewrowCountselectionselectiongetRowPropertiesgetCellPropertiesgetColumnPropertiesisContainerisContainerOpenisContainerEmptyisSeparatorisSortedcanDropdropgetParentIndexhasNextSiblinggetLevelgetImageSrcgetProgressModegetCellValuegetCellTextsetTreetoggleOpenStatecycleHeaderselectionChangedcycleCellisEditableisSelectablesetCellValuesetCellTextperformActionperformActionOnRowperformActionOnCellDROP_BEFOREDROP_ONDROP_AFTERPROGRESS_NORMALPROGRESS_UNDETERMINEDPROGRESS_NONE2 J2`J;`@JEJOJ`JrJ`
J`
J`
J`
J`
J
`
J
J`J`
J`JK`KK%K1K9KIKUKfKp`
K{`
KKKKKKKKKLLnsIEditorselectionfinalizeSelectioninitsetAttributeOrEquivalentremoveAttributeOrEquivalentpostCreatepreDestroyflagsflagscontentsMIMETypecontentsMIMETypeisDocumentEditableisSelectionEditabledocumentrootElementselectionControllerdeleteSelectiondocumentIsEmptydocumentModifieddocumentCharacterSetdocumentCharacterSetresetModificationCountgetModificationCountincrementModificationCounttransactionManagerdoTransactionenableUndonumberOfUndoItemsnumberOfRedoItemsundocanUndoredocanRedobeginTransactionendTransactionbeginPlaceHolderTransactionendPlaceHolderTransactionshouldTxnSetSelectionsetShouldTxnSetSelectiongetInlineSpellCheckersyncRealTimeSpellsetSpellcheckUserOverridecutcanCutcopycanCopycanDeletepastepasteTransferablecanPastecanPasteTransferableselectAllbeginningOfDocumentendOfDocumentsetAttributegetAttributeValueremoveAttributecloneAttributecloneAttributescreateNodeinsertNodesplitNodejoinNodesdeleteNodeoutputsMozDirtymarkNodeDirtyswitchTextDirectionoutputToStringoutputToStreamaddEditorObserverremoveEditorObserveraddEditActionListenerremoveEditActionListeneraddDocumentStateListenerremoveDocumentStateListenerdumpContentTreedebugDumpContentdebugUnitTestsisModifiableNodesuppressDispatchingInputEventsuppressDispatchingInputEventisInEditActionforceCompositionEndgetPreferredIMEStatecomposingeNoneeNextePreviouseNextWordePreviousWordeToBeginningOfLineeToEndOfLineeStripeNoStrip2UN`)NN,]N1
NJ
NfNq
N|`@NN`@NN`
N`
N`N`N`]NO
`
O`
O+@O@OUOl`OO`OO
O`O`OO@
@
OO@
@
PPP&PBP\`
Pr
P
`ȀPP
PP`
PP`
P`
PP.Q`
Q.`
Q%Q/QCQQQ^`
QpQQQ`QQ@QQ Q
QQRRpR$R6RKRaRzHRHRRR@@ R
R`
@S
S,`
S;SO`Sd`
	SnStSzSSSSSSnsIPresentationControlServerListeneronServerReadyonServerStoppedonSessionRequestonTerminateRequestonReconnectRequest2XXX%X6
XInsIThrottledInputChannelthrottleQueuethrottleQueue2X`^@X^nsIDOMXULImageElementsrcsrc2Y@Y#nsINetworkPropertiesisWifidhcpGateway2YW`
Y^`nsIStreamConverterconvertasyncConvertDatajY䀐2`Yj2nsIEmbeddingSiteWindowsetDimensionsgetDimensionssetFocusvisibilityvisibilitytitletitlesiteWindowblurDIM_FLAGS_POSITIONDIM_FLAGS_SIZE_INNERDIM_FLAGS_SIZE_OUTERDIM_FLAGS_IGNORE_XDIM_FLAGS_IGNORE_YDIM_FLAGS_IGNORE_CXDIM_FLAGS_IGNORE_CY2	YZ	@@@@ZZ `
@Z+
Z6`@Z<ZB`ZMZRZeZzZZZ Z@nsIDOMCSSSupportsRulensIDownloadManagerResulthandleResult2[nsIMozBrowserFramereallyIsBrowserisolateddisallowCreateFrameLoaderallowCreateFrameLoadercreateRemoteFrameLoaderinitializeBrowserAPIdestroyBrowserFrameScripts[`
\`
\
\'\>\V\knsINamedname2\nsIDownloadManagerUIshowvisiblegetAttentionREASON_USER_INTERACTEDREASON_NEW_DOWNLOAD2]?
]`
]]]2nsIScriptableDateFormatFormatDateTimeFormatDateFormatTimedateFormatNonedateFormatLongdateFormatShortdateFormatYearMonthdateFormatWeekdaytimeFormatNonetimeFormatSecondstimeFormatNoSecondstimeFormatSecondsForce24HourtimeFormatNoSecondsForce24Hour2]
`]`]`
]]]]^^^!^3^G^dnsISelectionPrivateinterlinePositioninterlinePositionancestorLimiterancestorLimiterstartBatchChangesendBatchChangestoStringWithFormataddSelectionListenerremoveSelectionListenergetTableSelectionTypecanCacheFrameOffsetcanCacheFrameOffsetgetCachedFrameOffsetsetTextRangeStylegetSelectionDirectionsetSelectionDirectiontypeGetRangesForIntervalGetRangesForIntervalArrayscrollIntoViewscrollIntoViewInternalselectionLanguageChangesetColorsresetColorsENDOFPRECEDINGLINESTARTOFNEXTLINETABLESELECTION_NONETABLESELECTION_CELLTABLESELECTION_ROWTABLESELECTION_COLUMNTABLESELECTION_TABLETABLESELECTION_ALLCELLS)_<`
@_N
_``H_p______`_`
H`
` `5(`G(`]
`s``x
@``
`
`
`
```aaa2aFaYaoansIDOMDOMConstructortoString2c3nsIOutputStreamcloseflushwritewriteFromwriteSegmentsisNonBlocking2c]ccci`co`cy`c`
nsIBasicCardResponseDatadatainitDatacdnsIHttpPushListeneronPush2dFnsIAuthInformationflagsrealmauthenticationSchemeusernameusernamepasswordpassworddomaindomainAUTH_HOSTAUTH_PROXYNEED_DOMAINONLY_PASSWORDPREVIOUS_FAILEDCROSS_ORIGIN_SUB_RESOURCE2	dw`d}dd@dd@dd@dddddde	 nsIFilePickerShownCallbackdone2ensISHEntryURIoriginalURIoriginalURIresultPrincipalURIresultPrincipalURIloadReplaceloadReplacetitleisSubFramesetURIreferrerURIreferrerURIreferrerPolicyreferrerPolicycontentViewercontentViewerstickystickywindowStatewindowStategetViewerBoundssetViewerBoundsaddChildShellchildShellAtclearChildShellsrefreshURIListrefreshURIListsyncPresentationStatesetTitlepostDatapostDatalayoutHistoryStatelayoutHistoryStateinitLayoutHistoryStateparentparentloadTypeloadTypeIDIDcacheKeycacheKeysaveLayoutStateFlagsaveLayoutStateFlagexpirationStatusexpirationStatuscontentTypecontentTypeURIWasModifiedURIWasModifiedsetScrollPositiongetScrollPositioncreateclonesetIsSubFramegetAnyContentViewertriggeringPrincipaltriggeringPrincipalprincipalToInheritprincipalToInheritstateDatastateDataforgetEditorDatasetEditorDatahasDetachedEditorisDynamicallyAddedhasDynamicallyAddedChilddocshellIDdocshellIDDocshellIDBFCacheEntryhasBFCacheEntryadoptBFCacheEntryabandonBFCacheEntrysharesDocumentWithisSrcdocEntrysrcdocDatasrcdocDatabaseURIbaseURIscrollRestorationIsManualscrollRestorationIsManualloadedInThisProcessSHistorySHistory2Ue`e`@ff`@f%f8`
@fD
fP`fV`
fafh`@ftf`@ff` @f f`
@f
f`2@f2ffg;g`;gg,`@g;gJg`gi`@grg{`@gg`g`u@gug`@gg`@gg`2@g2g`
@h
h`
@h)
h:@hFhR`
@ha
hph@@h
䀒2
h`uh
h@u` h`@hh`@hi`@i(i%(i6
(iD
iV`
ii`
i`@i(ii`f(if
iuiiu`
i`
j@jj`@j%j-`
@jG
ja`
ju`Hj~nsISearchParseSubmissionResultenginetermstermsOffsettermsLength2no`*nvn|`n`nsISelectionDisplaysetSelectionFlagsgetSelectionFlagsDISPLAY_TEXTDISPLAY_IMAGESDISPLAY_FRAMESDISPLAY_ALL2nn`no
oo(nsIInputListAutoCompleteautoCompleteSearch2o`nsIDocShellTreeOwnercontentShellAddedcontentShellRemovedprimaryContentShelltabParentAddedtabParentRemovedprimaryTabParentsizeShellTogetPrimaryContentSizesetPrimaryContentSizegetRootShellSizesetRootShellSizesetPersistencegetPersistencetabCounthasPrimaryContent2o;
o;o`;o
pp`p0;p<@@pRph@@pyp


p@
@
@
p`p`
nsISyncMessageSendersendSyncMessagesendRpcMessageq`q`nsIExternalProtocolHandlerexternalAppExistsForScheme{r`
nsIControllerCommandisCommandEnabledgetCommandStateParamsdoCommanddoCommandParams2rG2`
rX2rn2rx2nsIConsoleServicelogMessagelogStringMessagegetMessageArrayregisterListenerunregisterListenerreset2rgrrD`gsQs Qs3nsISecretDecoderRingencryptStringdecryptStringchangePasswordlogoutlogoutAndTeardown2ssssstxIFunctionEvaluationContextpositionsizecontextNodestate2t4`t=`tB`tN`2nsINavHistoryObserveronBeginUpdateBatchonEndUpdateBatchonVisitonTitleChangedonFrecencyChangedonManyFrecenciesChangedonDeleteURIonClearHistoryonPageChangedonDeleteVisitsREASON_DELETEDREASON_EXPIREDATTRIBUTE_FAVICON2
ttt
tt
tuuuu+u:uIuXnsIUpdateManagergetUpdateAtupdateCountactiveUpdateactiveUpdatesaveUpdatesrefreshUpdateStatuselevationOptedIncleanupActiveUpdate2v3`v?`vK`@vXvevqvvnsIRDFNodeEqualsNode2w`
nsIRDFDataSourceURIGetSourceGetSourcesGetTargetGetTargetsAssertUnassertChangeMoveHasAssertionAddObserverRemoveObserverArcLabelsInArcLabelsOutGetAllResourcesIsCommandEnabledDoCommandGetAllCmdshasArcInhasArcOutbeginUpdateBatchendUpdateBatch2w=`wA
`wK
`KwV
`w`
`Kwk
wrw{ww
`
wmwmw`Kw`Kw`Kw22`
w22w`Kw`
x`
xx"nsICookieTransactionCallbackcallback2ynsITabSourcegetTabToStreamnotifyStreamStartnotifyStreamStop2z`zz!nsIFeedGeneratoragentagentversionversionuriurizn@ztzz@zz`@znsIEnvironmentsetgetexists2zzz`
nsINetworkLinkServiceisLinkUplinkStatusKnownlinkTypeLINK_TYPE_UNKNOWNLINK_TYPE_ETHERNETLINK_TYPE_USBLINK_TYPE_WIFILINK_TYPE_WIMAXLINK_TYPE_2GLINK_TYPE_3GLINK_TYPE_4G2{8`
{A`
{Q`{Z{l{{{{{{nsIURIContentListeneronStartURIOpendoContentisPreferredcanHandleContentloadCookieloadCookieparentContentListenerparentContentListener2|V`
|e
@j`
|o@`
|{
@`
|`2@|2|`@|imgIScriptedNotificationObserversizeAvailableframeUpdateframeCompleteloadCompletedecodeCompletediscardisAnimatedhasTransparency2}hw}vw}w}w}w}w}w}wextIExtensionidnameenabledversionfirstRunprefsstorageevents2~D~G~L`
~T~\`
~e`:~k`U~s`dnsIApplicationUpdateServicecheckForBackgroundUpdatesbackgroundCheckerselectUpdateaddDownloadListenerremoveDownloadListenerdownloadUpdategetUpdatesDirectorypauseDownloadisDownloadingcanCheckForUpdateselevationRequiredcanApplyUpdatesisOtherInstanceHandlingUpdatescanStageUpdates2~
`р`,@W
f`1z`
`
`
`
`
`
nsIArraylengthqueryElementAtindexOfenumerate2``2``KnsIASN1PrintableItemsetDatagetData$,@@nsIWinTaskbaravailabledefaultGroupIdcreateTaskbarTabPreviewgetTaskbarWindowPreviewgetTaskbarProgressgetOverlayIconControllercreateJumpListBuildersetGroupIdForWindowprepareFullScreenprepareFullScreenHWND2
a`
kzF`F`ـF`F``Ԁ

nsIEventListenerInfotypecapturingallowsUntrustedinSystemEventGrouplistenerObjecttoSource2`
`
`
`nsIGeneratingKeypairInfoDialogsdisplayGeneratingKeypairInfo2}?ˀnsICookiePermissionsetAccesscanAccesscanSetCookieACCESS_DEFAULTACCESS_ALLOWACCESS_DENYACCESS_SESSIONACCESS_ALLOW_FIRST_PARTY_ONLYACCESS_LIMIT_THIRD_PARTY2`L
`
	;
nsITaskbarTabPreviewtitletitleiconiconmoveGetHWNDEnsureRegistration1@`}@}  
nsIPresentationServiceCallbacknotifySuccessnotifyError2nsISHistoryListenerOnHistoryNewEntryOnHistoryGoBackOnHistoryGoForwardOnHistoryReloadOnHistoryGotoIndexOnHistoryPurgeOnHistoryReplaceEntryOnLengthChangedOnIndexChanged2	`
`
`
`
%`
4JZnsINSSErrorsServiceisNSSErrorCodegetXPCOMFromNSSErrorgetErrorMessagegetErrorClassERROR_CLASS_SSL_PROTOCOLERROR_CLASS_BAD_CERTNSS_SEC_ERROR_BASENSS_SEC_ERROR_LIMITNSS_SSL_ERROR_BASENSS_SSL_ERROR_LIMITMOZILLA_PKIX_ERROR_BASEMOZILLA_PKIX_ERROR_LIMIT2`
`,`:Sh{nsIRedirectHistoryEntryprincipalreferrerURIremoteAddress2~``nsIUUIDGeneratorgenerateUUIDgenerateUUIDInPlace2`nsIPerformanceSnapshotgetComponentsDatagetProcessData2/`A`nsIMessageManagerGlobaldumpprivateNoteIntentionalCrashatobbtoaznsIMarionetterunning2`
IPeerConnectionkHintAudiokHintVideokActionNonekActionOfferkActionAnswerkActionPRAnswerkActionRollbackkIceGatheringkIceWaitingkIceCheckingkIceConnectedkIceFailedkNewkNegotiatingkActivekClosingkClosedkDataChannelReliablekDataChannelPartialReliableRexmitkDataChannelPartialReliableTimedkNoErrorkInvalidCandidatekInvalidMediastreamTrackkInvalidStatekInvalidSessionDescriptionkIncompatibleSessionDescriptionkIncompatibleMediaStreamTrackkInternalErrorkMaxErrorType2%0<IWgw:CUn|		nsICacheServicecreateSessionvisitEntriesevictEntriescacheIOTarget2
`À#0`VnsIBrowsersameProcessAsFrameLoaderdropLinksswapBrowserscloseBrowserSWAP_DEFAULTSWAP_KEEP_PERMANENT_KEY2`nsIBrowserSearchServiceinitisInitializedresetToOriginalDefaultEnginehasEngineWithURLaddEngineaddEngineWithDetailsrestoreDefaultEnginesgetEngineByAliasgetEngineByNamegetEnginesgetVisibleEnginesgetDefaultEnginesgetEnginesByExtensionIDmoveEngineremoveEngineoriginalDefaultEnginedefaultEnginedefaultEnginecurrentEnginecurrentEnginegetDefaultEngineInfoparseSubmissionURL2H;M`
[x`

R`*`*D`*D`*D`*D`*&*1*>`*T`*@b*p`*@~*``vnsISubstitutingProtocolHandlersetSubstitutiongetSubstitutionhasSubstitutionresolveURIaddObserverremoveObserver{
`*`
:EQnsIFinalizationWitnessServicemake2`txINodeSetitemitemAsNumberitemAsStringlengthadd2``	
`mozIAsyncHistoryupdatePlacesisURIVisited2v̄
1nsIFrameLoaderdocShelltabParentloadContextloadFrameloadURIsetIsPrerenderedmakePrerenderedLoaderActiveappendPartialSHistoryAndSwaprequestGroupedHistoryNavigationaddProcessChangeBlockingPromisedestroydepthTooGreatupdatePositionAndSizeactivateRemoteFramedeactivateRemoteFramesendCrossProcessMouseEventactivateFrameEventmessageManagersendCrossProcessKeyEventrequestNotifyAfterRemotePaintrequestFrameLoaderCloseprintensureGroupedSHistoryeventModeeventModeclipSubdocumentclipSubdocumentclampScrollPositionclampScrollPositionownerElementchildIDownerIsMozBrowserFramelazyWidthlazyHeightpartialSHistorygroupedSHistoryisDeadEVENT_MODE_NORMAL_DISPATCHEVENT_MODE_DONT_FORWARD_TO_CHILD2%`F``$`2A`2a`
1

`݀
-Kcڀi``@`
@
`
@
```
```,`<`
C^nsIQuotaUsageRequestresultcallbackcallbackcancel9K`R`#@[#dmozIStorageBaseStatementfinalizebindUTF8StringParameterbindStringParameterbindDoubleParameterbindInt32ParameterbindInt64ParameterbindNullParameterbindBlobParameterbindStringAsBlobParameterbindUTF8StringAsBlobParameterbindAdoptedBlobParameterbindParametersnewBindingParamsArrayexecuteAsyncstateescapeStringForLIKEMOZ_STORAGE_STATEMENT_INVALIDMOZ_STORAGE_STATEMENT_READYMOZ_STORAGE_STATEMENT_EXECUTING	&8JdS`S
`6`!nsIGSettingsCollectionsetStringsetBooleansetIntgetStringgetBooleangetIntgetStringList2BL
W^h`
s`z`nsIContentPolicyBaseTYPE_INVALIDTYPE_OTHERTYPE_SCRIPTTYPE_IMAGETYPE_STYLESHEETTYPE_OBJECTTYPE_DOCUMENTTYPE_SUBDOCUMENTTYPE_REFRESHTYPE_XBLTYPE_PINGTYPE_XMLHTTPREQUESTTYPE_DATAREQUESTTYPE_OBJECT_SUBREQUESTTYPE_DTDTYPE_FONTTYPE_MEDIATYPE_WEBSOCKETTYPE_CSP_REPORTTYPE_XSLTTYPE_BEACONTYPE_FETCHTYPE_IMAGESETTYPE_WEB_MANIFESTTYPE_INTERNAL_SCRIPTTYPE_INTERNAL_WORKERTYPE_INTERNAL_SHARED_WORKERTYPE_INTERNAL_EMBEDTYPE_INTERNAL_OBJECTTYPE_INTERNAL_FRAMETYPE_INTERNAL_IFRAMETYPE_INTERNAL_AUDIOTYPE_INTERNAL_VIDEOTYPE_INTERNAL_TRACKTYPE_INTERNAL_XMLHTTPREQUESTTYPE_INTERNAL_EVENTSOURCETYPE_INTERNAL_SERVICE_WORKERTYPE_INTERNAL_SCRIPT_PRELOADTYPE_INTERNAL_IMAGETYPE_INTERNAL_IMAGE_PRELOADTYPE_INTERNAL_STYLESHEETTYPE_INTERNAL_STYLESHEET_PRELOADTYPE_INTERNAL_IMAGE_FAVICONTYPE_INTERNAL_WORKER_IMPORT_SCRIPTSREJECT_REQUESTREJECT_TYPEREJECT_SERVERREJECT_OTHERACCEPT21)9ESdq	z

,>Sh !/"I#f$%&'()	*-<HVcnsIPushSubscriptionCallbackonPushSubscription2<ĀnsISSLStatusProviderSSLStatus2y`nsIRDFInMemoryDataSourceEnsureFastContainment2nsIHashableequalshashCode2`
`nsINotificationStorageputgetgetByIDdeletecanPut2*.2:A`
nsIRDFXMLParserparseAsyncparseString2`jnsIPaymentShowActionResponseacceptStatusmethodNamedatapayerNamepayerEmailpayerPhoneinitisAcceptedq`%05?JUZ`
mozIMozIntlHelperaddGetCalendarInfoaddGetDisplayNamesaddGetLocaleInfoaddPluralRulesConstructoraddDateTimeFormatConstructor2-mozIStorageResultSetgetNextRow2`3nsPISocketTransportServiceinitshutdownsendBufferSizeofflineofflinekeepaliveIdleTimekeepaliveRetryIntervalkeepaliveProbeCount
``
@
``'`nsIAppShellServicecreateTopLevelWindowcreateWindowlessBrowsercreateHiddenWindowdestroyHiddenWindowsetScreenIdhiddenWindowhiddenDOMWindowhiddenPrivateWindowhiddenPrivateDOMWindowapplicationProvidedHiddenWindowregisterTopLevelWindowunregisterTopLevelWindowhasHiddenPrivateWindowstartEventLoopLagTrackingstopEventLoopLagTrackingSIZE_TO_CONTENT2f`f
``f` `f4`K`
kff`
`
nsIToolkitProfileServicestartWithLastProfilestartWithLastProfilestartOfflinestartOfflineprofilesselectedProfileselectedProfiledefaultProfiledefaultProfilegetProfileByNamelockProfilePathcreateProfilecreateDefaultProfileForAppprofileCountflush2`
@
`
@

`K#`@3C`@Ra`r11`1```mozIJSSubScriptLoaderloadSubScriptloadSubScriptWithOptionsprecompileScript2``xnsITLSClientStatuspeerCerttlsVersionUsedcipherNamekeyLengthmacLengthSSL_VERSION_3TLS_VERSION_1TLS_VERSION_1_1TLS_VERSION_1_2TLS_VERSION_1_3TLS_VERSION_UNKNOWN2`$`3>`H`R`n~NetDashboardCallbackonDashboardDataAvailable2,nsIContentPrefCallback2handleResulthandleErrorhandleCompletionCOMPLETE_OKCOMPLETE_ERROR2nJ{nsIHeapAllocatedCallbackcallback2nsIGfxInfoD2DEnabledDWriteEnabledusingGPUProcessDWriteVersioncleartypeParametersContentBackendWebRenderEnabledisHeadlessadapterDescriptionadapterDescription2adapterDriveradapterDriver2adapterVendorIDadapterVendorID2adapterDeviceIDadapterDeviceID2adapterSubsysIDadapterSubsysID2adapterRAMadapterRAM2adapterDriverVersionadapterDriverVersion2adapterDriverDateadapterDriverDate2isGPU2ActivegetMonitorsgetFailureslogFailuregetFeatureStatusgetFeatureSuggestedDriverVersiongetWebGLParameterGetDatagetInfogetFeatureLoggetFeaturesgetActiveCrashGuardscontrolGPUProcessForXPCShellFEATURE_DIRECT2DFEATURE_DIRECT3D_9_LAYERSFEATURE_DIRECT3D_10_LAYERSFEATURE_DIRECT3D_10_1_LAYERSFEATURE_OPENGL_LAYERSFEATURE_WEBGL_OPENGLFEATURE_WEBGL_ANGLEFEATURE_WEBGL_MSAAFEATURE_STAGEFRIGHTFEATURE_WEBRTC_HW_ACCELERATIONFEATURE_DIRECT3D_11_LAYERSFEATURE_HARDWARE_VIDEO_DECODINGFEATURE_DIRECT3D_11_ANGLEFEATURE_WEBRTC_HW_ACCELERATION_ENCODEFEATURE_WEBRTC_HW_ACCELERATION_DECODEFEATURE_CANVAS2D_ACCELERATIONFEATURE_VP8_HW_DECODEFEATURE_VP9_HW_DECODEFEATURE_DX_INTEROP2FEATURE_GPU_PROCESSFEATURE_WEBGL2FEATURE_ADVANCED_LAYERSFEATURE_D3D11_KEYED_MUTEXFEATURE_MAX_VALUEFEATURE_STATUS_OKFEATURE_STATUS_UNKNOWNFEATURE_BLOCKED_DRIVER_VERSIONFEATURE_BLOCKED_DEVICEFEATURE_DISCOURAGEDFEATURE_BLOCKED_OS_VERSIONFEATURE_BLOCKED_MISMATCHED_VERSION2%&`
1`
?`
O]q`
`
"2CNZo`
`@D`(
`(
&`.`<`H`]
`
z/	C
b}
!7Mau';VnsIQuotaManagerServiceinitinitStoragesForPrincipalgetUsagegetUsageForPrincipalclearclearStoragesForPrincipalresetpersistedpersist2	2`7`P#
`Y#
`n`t
````nsIThreadManagernewThreadnewNamedThreadgetThreadFromPRThreadmainThreadcurrentThreaddispatchToMainThreadidleDispatchToMainThreadspinEventLoopUntilspinEventLoopUntilEmptysystemGroupEventTargetDEFAULT_STACK_SIZE2
P`Z`i```B`VnsINamedPipeServiceaddDataObserverremoveDataObserverisOnCurrentThread2`
nsIMIMEInfogetFileExtensionssetFileExtensionsextensionExistsappendExtensionprimaryExtensionprimaryExtensionMIMETypeequalspossibleLocalHandlerslaunchWithFileB
-`??Q`
aq@`
`1nsIClassOfServiceclassFlagsclassFlagsclearClassFlagsaddClassFlagsLeaderFollowerSpeculativeBackgroundUnblockedThrottleableUrgentStartDontThrottle2Q`@\gw @nsIRDFRemoteDataSourceloadedInitRefreshFlushFlushTo2j`
qv
~nsIXULOverlayProvidergetXULOverlaysgetStyleOverlays2`K`KnsIURIFixupcreateExposableURIcreateFixupURIgetFixupURIInfokeywordToURIisDomainWhitelistedFIXUP_FLAG_NONEFIXUP_FLAG_ALLOW_KEYWORD_LOOKUPFIXUP_FLAGS_MAKE_ALTERNATE_URIFIXUP_FLAG_FIX_SCHEME_TYPOS2,`?D`ND`^D`k`
nsICacheSessiondoomEntriesIfExpireddoomEntriesIfExpiredprofileDirectoryprofileDirectoryopenCacheEntryasyncOpenCacheEntryevictEntriesisStorageEnableddoomEntryisPrivateisPrivate2}`
@
`1@1
`
`

`
@
nsIPushSubscriptionendpointpushCountlastPushquotaisSystemSubscriptionp256dhPrivateKeyquotaAppliesisExpiredgetKey2	````
``
`
&D`nsIMemoryheapMinimizeisLowMemoryPlatform2
`
nsIMemoryProfilerstartProfilerstopProfilerresetProfilergetResults2`nsIExternalHelperAppServicedoContentapplyDecodingForExtension2f怒?
?`jp`
nsICryptoHashinitinitWithStringupdateupdateFromStreamfinishMD2MD5SHA1SHA256SHA384SHA5122
	nsIUDPSocketChildlocalPortlocalAddressfilterNamefilterNamesetBackgroundSpinsEventsbindconnectsendsendWithAddrsendWithAddresssendBinaryStreamclosejoinMulticastleaveMulticast2`@	&

V&?$*8nsICookieServicegetCookieStringgetCookieStringFromHttpsetCookieStringsetCookieStringFromHttprunInTransactionBEHAVIOR_ACCEPTBEHAVIOR_REJECT_FOREIGNBEHAVIOR_REJECTBEHAVIOR_LIMIT_FOREIGNACCEPT_NORMALLYACCEPT_SESSIONACCEPT_FOR_N_DAYS2&`6`Nr^rvnsIContentSignatureReceiverCallbackcontextCreated2
mozIVisitInfoCallbackhandleErrorhandleResulthandleCompletionignoreResultsignoreErrors2
&7`
E`
mozIStorageErrorresultmessageERRORINTERNALPERMABORTBUSYLOCKEDNOMEMREADONLYINTERRUPTIOERRCORRUPTFULLCANTOPENEMPTYSCHEMATOOBIGCONSTRAINTMISMATCHMISUSENOLFSAUTHFORMATRANGENOTADB2¢`©±·	

 +4;AFMSnsITLSServerSecurityObserveronHandshakeDone2j3nsIPrefServiceresetPrefsresetUserPrefssavePrefFilegetBranchgetDefaultBranchdirtyreadUserPrefsFromFile2Ġīĺ1```
1nsIFileProtocolHandlernewFileURIgetURLSpecFromFilegetURLSpecFromActualFilegetURLSpecFromDirgetFileFromURLSpecreadURLFile{j1`u1ň1š1ų`11`nsITabChildmessageManagerwebBrowserChromewebBrowserChromesendRequestFocussendGetTabCountenableDisableCommandsremoteSizeShellToremoteDropLinkstabIdbeforeUnloadAddedbeforeUnloadRemoved2=`YL`@] n

 @
(Ə
ƥƷ\`(
(
nsICacheEntryOpenCallbackonCacheEntryCheckonCacheEntryAvailableENTRY_WANTEDRECHECK_AFTER_WRITE_FINISHEDENTRY_NEEDS_REVALIDATIONENTRY_NOT_WANTED2Ǖ#P`ǧ#
PǽnsIAccessibleTextChangeEventstartlengthisInsertedmodifiedText`ȅ`Ȍ`
ȗnsIJumpListBuilderavailableisListCommittedmaxListItemsinitListBuildaddListToBuildabortListBuildcommitListBuilddeleteActiveListJUMPLIST_CATEGORY_TASKSJUMPLIST_CATEGORY_RECENTJUMPLIST_CATEGORY_FREQUENTJUMPLIST_CATEGORY_CUSTOMLIST2``
`
`
`
*9؀I`
ZrɋɦnsIDBusHandlerAppserviceserviceobjectPathobjectPathdBusInterfacedBusInterfacemethodmethodT@\d@oz@ʈʖ@ʝnsILoginManagerCryptoencryptdecryptuiBusyisLoggedIndefaultEncTypeENCTYPE_BASE64ENCTYPE_SDR2!`
(`
3`BQnsIBinaryOutputStreamsetOutputStreamwriteBooleanwrite8write16write32write64writeFloatwriteDoublewriteStringZwriteWStringZwriteUtf8ZwriteByteswriteByteArrayp
p
		"0;FnsIAccessibleHideEventtargetParenttargetNextSiblingtargetPrevSibling`
``nsIPrivateModeCallbackcallback2snsIAccessibleEventeventTypeaccessibleaccessibleDocumentDOMNodeisFromUserInputEVENT_SHOWEVENT_HIDEEVENT_REORDEREVENT_ACTIVE_DECENDENT_CHANGEDEVENT_FOCUSEVENT_STATE_CHANGEEVENT_LOCATION_CHANGEEVENT_NAME_CHANGEEVENT_DESCRIPTION_CHANGEEVENT_VALUE_CHANGEEVENT_HELP_CHANGEEVENT_DEFACTION_CHANGEEVENT_ACTION_CHANGEEVENT_ACCELERATOR_CHANGEEVENT_SELECTIONEVENT_SELECTION_ADDEVENT_SELECTION_REMOVEEVENT_SELECTION_WITHINEVENT_ALERTEVENT_FOREGROUNDEVENT_MENU_STARTEVENT_MENU_ENDEVENT_MENUPOPUP_STARTEVENT_MENUPOPUP_ENDEVENT_CAPTURE_STARTEVENT_CAPTURE_ENDEVENT_MOVESIZE_STARTEVENT_MOVESIZE_ENDEVENT_CONTEXTHELP_STARTEVENT_CONTEXTHELP_ENDEVENT_DRAGDROP_STARTEVENT_DRAGDROP_ENDEVENT_DIALOG_STARTEVENT_DIALOG_ENDEVENT_SCROLLING_STARTEVENT_SCROLLING_ENDEVENT_MINIMIZE_STARTEVENT_MINIMIZE_ENDEVENT_DOCUMENT_LOAD_COMPLETEEVENT_DOCUMENT_RELOADEVENT_DOCUMENT_LOAD_STOPPEDEVENT_DOCUMENT_ATTRIBUTES_CHANGEDEVENT_DOCUMENT_CONTENT_CHANGEDEVENT_PROPERTY_CHANGEDEVENT_PAGE_CHANGEDEVENT_TEXT_ATTRIBUTE_CHANGEDEVENT_TEXT_CARET_MOVEDEVENT_TEXT_CHANGEDEVENT_TEXT_INSERTEDEVENT_TEXT_REMOVEDEVENT_TEXT_UPDATEDEVENT_TEXT_SELECTION_CHANGEDEVENT_VISIBLE_DATA_CHANGEDEVENT_TEXT_COLUMN_CHANGEDEVENT_SECTION_CHANGEDEVENT_TABLE_CAPTION_CHANGEDEVENT_TABLE_MODEL_CHANGEDEVENT_TABLE_SUMMARY_CHANGEDEVENT_TABLE_ROW_DESCRIPTION_CHANGEDEVENT_TABLE_ROW_HEADER_CHANGEDEVENT_TABLE_ROW_INSERTEVENT_TABLE_ROW_DELETEEVENT_TABLE_ROW_REORDEREVENT_TABLE_COLUMN_DESCRIPTION_CHANGEDEVENT_TABLE_COLUMN_HEADER_CHANGEDEVENT_TABLE_COLUMN_INSERTEVENT_TABLE_COLUMN_DELETEEVENT_TABLE_COLUMN_REORDEREVENT_WINDOW_ACTIVATEEVENT_WINDOW_CREATEEVENT_WINDOW_DEACTIVATEEVENT_WINDOW_DESTROYEVENT_WINDOW_MAXIMIZEEVENT_WINDOW_MINIMIZEEVENT_WINDOW_RESIZEEVENT_WINDOW_RESTOREEVENT_HYPERLINK_END_INDEX_CHANGEDEVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGEDEVENT_HYPERLINK_SELECTED_LINK_CHANGEDEVENT_HYPERTEXT_LINK_ACTIVATEDEVENT_HYPERTEXT_LINK_SELECTEDEVENT_HYPERLINK_START_INDEX_CHANGEDEVENT_HYPERTEXT_CHANGEDEVENT_HYPERTEXT_NLINKS_CHANGEDEVENT_OBJECT_ATTRIBUTE_CHANGEDEVENT_VIRTUALCURSOR_CHANGEDEVENT_TEXT_VALUE_CHANGEEVENT_LAST_ENTRY2͞`ͨ`ͳ```
X!-@Vh	΁
ΔΦν
%<HYjyϏϣϷ	4 G!Z"k#Ё$Е%Ъ&н'()*.+M,d-w.є/ѫ0Ѿ1234506J7`8|9Җ:Ҳ;<=>#?;@bAӄBӞCӸDEFGH*I@JVKjLMԡNOPQ.RRSjTՉUըVWXnsIBrowserHistoryremovePageremovePagesremovePagesFromHostremovePagesByTimeframe2Vam
فnsILoginMetaInfoguidguidtimeCreatedtimeCreatedtimeLastUsedtimeLastUsedtimePasswordChangedtimePasswordChangedtimesUsedtimesUsed2
@`@`@!`@5I`@SnsILocalegetCategory2nsITransportSecurityInfosecurityStateerrorMessageerrorCodefailedCertChain2
``%`/`nsIDroppedLinkHandlercanDropLinkdropLinkdropLinksgetTriggeringPrincipal2ۆ
`
ے

ۛ
D`\ۥ
`nsIDocumentEncoderinitnativeInitsetSelectionsetRangesetNodesetNativeNodesetContainerNodesetNativeContainerNodesetCharsetsetWrapColumnmimeTypeencodeToStreamencodeToStringencodeToStringWithContextencodeToStringWithMaxLengthsetNodeFixupOutputSelectionOnlyOutputFormattedOutputRawOutputBodyOnlyOutputPreformattedOutputWrapOutputFormatFlowedOutputAbsoluteLinksOutputEncodeW3CEntitiesOutputCRLineBreakOutputLFLineBreakOutputNoScriptContentOutputNoFramesContentOutputNoFormattingInPreOutputEncodeBasicEntitiesOutputEncodeLatin1EntitiesOutputEncodeHTMLEntitiesOutputPersistNBSPOutputDontRewriteEncodingDeclarationSkipInvisibleContentOutputFormatDelSpOutputDropInvisibleBreakOutputIgnoreMozDirtyOutputNonTextContentAsPlaceholderOutputDontRemoveLineEndingSpacesOutputForPlainTextClipboardCopyOutputRubyAnnotationOutputDisallowLineBreakingRequiresReinitAfterOutput2$/)<EM[l܃܎ܜܥpܴp*4CV a@t݈ݠݲ @"=Vhލޢ޴ @%EZunsIURIRefObjectnodenodeResetGetNextURIRewriteAllURIs2g`@lqw
nsIDownloadtargetFilepercentCompleteamountTransferredsizesourcetargetcancelabledisplayNamestartTimespeedMIMEInfoidguidstatereferrerresumableisPrivatecancelpauseresumeremoveretry\`1``	````r'3`=`	C`L`OT`Z`c`
m`
w~nsIQuotaRequestresultcallbackcallback9``Y@YnsIWorkerDebuggerisClosedisChromeisInitializedparenttypeurlwindowprincipalserviceWorkerIDinitializepostMessageaddListenerremoveListenerTYPE_DEDICATEDTYPE_SHAREDTYPE_SERVICE2
`
`
`
`
```$`4?KDWDfunsICacheMetaDataVisitorvisitMetaDataElement2T`
nsIUrlClassifierHashCompletercomplete2tnsIRandomGeneratorgenerateRandomBytes2`nsIRDFBlobvaluelength``nsITaskbarProgresssetProgressStateSTATE_NO_PROGRESSSTATE_INDETERMINATESTATE_NORMALSTATE_ERRORSTATE_PAUSED2ARdxnsIDirIndextypetypecontentTypecontentTypelocationlocationdescriptiondescriptionsizesizelastModifiedlastModifiedTYPE_UNKNOWNTYPE_DIRECTORYTYPE_FILETYPE_SYMLINK2`@`@`@ `@,8`@=B`@O\ixnsISAXLexicalHandlercommentstartDTDendDTDstartCDATAendCDATAstartEntityendEntity2GOX_jsnsITextInputProcessorCallbackonNotify2`
nsIHangDetailsdurationthreadName2#`,nsIAutoCompleteSimpleResultsetSearchStringsetErrorDescriptionsetDefaultIndexsetSearchResultinsertMatchAtappendMatchgetListenersetListenern~`44nsIComponentRegistrarautoRegisterautoUnregisterregisterFactoryunregisterFactoryregisterFactoryLocationunregisterFactoryLocationisCIDRegisteredisContractIDRegisteredenumerateCIDsenumerateContractIDsCIDToContractIDcontractIDToCID2k1x13311`
`
`K`K%`5`nsIResProtocolHandlernsINavBookmarksServiceplacesRootbookmarksMenuFoldertagsFolderunfiledBookmarksFoldertoolbarFoldermobileFolderinsertBookmarkremoveItemcreateFoldergetRemoveFolderTransactionremoveFolderChildrenmoveIteminsertSeparatorgetIdForItemAtsetItemTitlegetItemTitlesetItemDateAddedgetItemDateAddedsetItemLastModifiedgetItemLastModifiedgetBookmarkURIgetItemIndexsetItemIndexgetItemTypeisBookmarkedgetBookmarkedURIForchangeBookmarkURIgetFolderIdForItemgetBookmarkIdsForURIsetKeywordForBookmarkgetKeywordForBookmarkaddObserverremoveObservergetObserversrunInBatchModeDEFAULT_INDEXTYPE_BOOKMARKTYPE_FOLDERTYPE_SEPARATORTYPE_DYNAMIC_CONTAINERSOURCE_DEFAULTSOURCE_SYNCSOURCE_IMPORTSOURCE_IMPORT_REPLACESOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDRENSYNC_STATUS_UNKNOWNSYNC_STATUS_NEWSYNC_STATUS_NORMAL2#$`/`C`N`e`s``````*`;O`c`r```
``D`	
+:D`G2
Vdr~$4nsIGetUserMediaDevicesSuccessCallbackonSuccess2nsIProtocolProxyChannelFilterapplyFilter2倒7`7nsIUTF8ConverterServiceconvertStringToUTF8convertURISpecToUTF821

EnsIChromeRegistryconvertChromeURLcheckForNewChromewrappersEnabledNONEPARTIALFULL2` 
nsIMediaManagerServiceactiveMediaCaptureWindowsmediaCaptureWindowStatesanitizeDeviceIds25`O@
@
D
D
D
D
gnsPIDNSServiceinitshutdownprefetchEnabledprefetchEnabled`
@
nsIDNSServiceResolveListeneronServiceResolvedonResolveFailed23LELnsIEditingSessioneditorStatusmakeWindowEditablewindowIsEditablegetEditorForWindowsetupEditorOnWindowtearDownEditorOnWindowsetEditorOnControllersdisableJSAndPluginsrestoreJSAndPluginsdetachFromWindowreattachToWindowjsAndPluginsDisabledeEditorOKeEditorCreationInProgresseEditorErrorCantEditMimeTypeeEditorErrorFileNotFoundeEditorErrorCantEditFramesetseEditorErrorUnknown2`【


`
`a〒a"6GX`
mw	nsIDOMFontFaceListitemlength2`5`nsIXPCWrappedJSObjectGetterneverCalled2.`2nsIWindowsRegKeykeykeycloseopencreateopenChildcreateChildchildCountgetChildNamehasChildvalueCountgetValueNamehasValueremoveChildremoveValuegetValueTypereadStringValuereadIntValuereadInt64ValuereadBinaryValuewriteStringValuewriteIntValuewriteInt64ValuewriteBinaryValuestartWatchingstopWatchingisWatchinghasChangedROOT_KEY_CLASSES_ROOTROOT_KEY_CURRENT_USERROOT_KEY_LOCAL_MACHINEACCESS_BASICACCESS_QUERY_VALUEACCESS_SET_VALUEACCESS_CREATE_SUB_KEYACCESS_ENUMERATE_SUB_KEYSACCESS_NOTIFYACCESS_READACCESS_WRITEACCESS_ALLWOW64_32WOW64_64TYPE_NONETYPE_STRINGTYPE_BINARYTYPE_INTTYPE_INT642^`Hbflqx````
``
```!1BP`q
`
`
,FT`mxextIExtensionsCallbackcallback2nsIWebBrowserChrome2setStatusWithContext2nsIDragSessioncanDropcanDroponlyChromeDroponlyChromeDropdragActiondragActiontargetSizetargetSizenumDropItemssourceDocumentsourceNodedataTransferdataTransfergetDataisDataFlavorSupporteduserCancelleddragEventDispatchedToChildProcessupdateDragEffectupdateDragImage27`
@?
G`
@V
e`@p{`H````
@
.`
 1nsIPipeinitinputStreamoutputStream2

!`n-`nsIArrayExtensionsCountGetElementAt|``2nsITelemetryhistogramSnapshotssnapshotSubsessionHistogramslastShutdownDurationfailedProfileLockCountslowSQLdebugSlowSQLwebrtcStatsmaximalNumberOfConcurrentThreadschromeHangscaptureStacksnapshotCapturedStacksgetLoadedModulesthreadHangStatslateWritesregisteredHistogramsgetHistogramByIdkeyedHistogramSnapshotssnapshotSubsessionKeyedHistogramsregisteredKeyedHistogramsgetKeyedHistogramByIdcanRecordBasecanRecordBasecanRecordExtendedcanRecordExtendedisOfficialTelemetrysetHistogramRecordingEnabledasyncFetchTelemetryDatafileIOReportsmsSinceProcessStartmsSystemNowscalarAddscalarSetscalarSetMaximumsnapshotScalarskeyedScalarAddkeyedScalarSetkeyedScalarSetMaximumsnapshotKeyedScalarsclearScalarsflushBatchedChildTelemetryrecordEventsetEventRecordingEnabledsnapshotEventsregisterEventsclearEventsHISTOGRAM_EXPONENTIALHISTOGRAM_LINEARHISTOGRAM_BOOLEANHISTOGRAM_FLAGHISTOGRAM_COUNTHISTOGRAM_CATEGORICALSCALAR_COUNTSCALAR_STRINGSCALAR_BOOLEANDATASET_RELEASE_CHANNEL_OPTOUTDATASET_RELEASE_CHANNEL_OPTIN2-`
`````,`8`Y`er
``2``@```
`@`/`E`
@S
a`
@s
`

n``	`	!
`1@Oe
`z

`*9I_lznsISiteSecurityServiceprocessHeaderNativeprocessHeaderremoveStateisSecureURINativeisSecureURIclearAllclearPreloadsgetKeyPinsForHostnamesetKeyPinssetHSTSPreloadcacheNegativeHSTSResultenumerateHEADER_HSTSHEADER_HPKPHEADER_OMSSuccessERROR_UNKNOWNERROR_UNTRUSTWORTHY_CONNECTIONERROR_COULD_NOT_PARSE_HEADERERROR_NO_MAX_AGEERROR_MULTIPLE_MAX_AGESERROR_INVALID_MAX_AGEERROR_MULTIPLE_INCLUDE_SUBDOMAINSERROR_INVALID_INCLUDE_SUBDOMAINSERROR_INVALID_PINERROR_MULTIPLE_REPORT_URISERROR_PINSET_DOES_NOT_MATCH_CHAINERROR_NO_BACKUP_PINERROR_COULD_NOT_SAVE_STATEERROR_ROOT_NOT_BUILT_INSOURCE_UNKNOWNSOURCE_PRELOAD_LISTSOURCE_ORGANIC_REQUESTSOURCE_HSTS_PRIMING2K
DD
D_
DD
DmyD
D`
D
D`
@@
`


`

`
`K#+9Xu		
$FZ
unsIDNSListeneronLookupComplete2	r	nsIPresentationRespondingListenernotifySessionConnect2	nsIPromptFactorygetPrompt2
+〮`nsILoadContextassociatedWindowtopWindowtopFrameElementnestedFrameIdisContentusePrivateBrowsingusePrivateBrowsinguseRemoteTabsuseTrackingProtectionuseTrackingProtectionSetPrivateBrowsingSetRemoteTabsisInIsolatedMozBrowserElementoriginAttributesGetOriginAttributes2
\`
m`
w`
`
`

`
@


`

`
@




 `
>`(O@
nsISuspendedTypesNONE_SUSPENDEDSUSPENDED_PAUSESUSPENDED_BLOCKSUSPENDED_PAUSE_DISPOSABLESUSPENDED_STOP_DISPOSABLE2'7GbnsIPropertyElementkeykeyvaluevalue2@@nsIBidiKeyboardresetisLangRTLhaveBidiKeyboards2

`

&`
nsIAccessibleEditableTextsetTextContentsinsertTextcopyTextcutTextdeleteTextpasteText2
u




nsIStackFramelanguagelanguageNamefilenamenamelineNumbercolumnNumbersourceLineasyncCauseasyncCallercallerformattedStacknativeSavedFrametoString2
`%.3`>`KVa`m`t`mozIStorageStatementCallbackhandleResulthandleErrorhandleCompletionREASON_FINISHEDREASON_CANCELEDREASON_ERROR2GT̀`qnsITCPDeviceInfoidaddressportcertFingerprint2`nsIMemoryReporterManagerinitregisterStrongReporterregisterStrongAsyncReporterregisterWeakReporterregisterWeakAsyncReporterunregisterStrongReporterunregisterWeakReporterblockRegistrationAndHideExistingReportersunblockRegistrationAndRestoreOriginalReportersregisterStrongReporterEvenIfBlockedgetReportsgetReportsExtendedgetReportsForThisProcessExtendedendReportvsizevsizeMaxContiguousresidentresidentFastresidentPeakresidentUniqueheapAllocatedheapOverheadFractionJSMainRuntimeGCHeapJSMainRuntimeTemporaryPeakJSMainRuntimeCompartmentsSystemJSMainRuntimeCompartmentsUserimagesContentUsedUncompressedstorageSQLitelowMemoryEventsVirtuallowMemoryEventsPhysicalghostWindowspageFaultsHardhasMozMallocUsableSizeisDMDEnabledisDMDRunninggetHeapAllocatedAsyncminimizeMemoryUsagesizeOfTab2&UZqEi42瀒2
t42瀒2

42
瀒2```````` `4`O`o````````
`
(`
5K_
@@@@@@@@	@	nsIEncodedChannelcontentEncodingsapplyConversionapplyConversiondoApplyContentConversions2R`?c`
@s
j@j2nsIFeedPersonnamenameemailemailuriuri@@`@nsIPaymentResponseDatatypeinitGENERAL_RESPONSEBASICCARD_RESPONSE2b`gl}nsITransportopenInputStreamopenOutputStreamclosesetEventSinkOPEN_BLOCKINGOPEN_UNBUFFEREDSTATUS_READINGSTATUS_WRITING2``pހVK+K	nsISAXContentHandlerstartDocumentendDocumentstartElementendElementcharactersprocessingInstructionignorableWhitespacestartPrefixMappingendPrefixMapping2	2nsIRDFInferDataSourcebaseDataSourcebaseDataSource`@nsIMessageListenerreceiveMessage2extIApplicationidnameversionconsolegetExtensionsprefsstorageeventsquitrestart2
DGLT`\j`:p`Ux`d`
`
nsINativeOSFileSuccessCallbackcomplete2 ^nsIChanneloriginalURIoriginalURIURIownerownernotificationCallbacksnotificationCallbackssecurityInfocontentTypecontentTypecontentCharsetcontentCharsetcontentLengthcontentLengthopenopen2asyncOpenasyncOpen2contentDispositioncontentDispositioncontentDispositionFilenamecontentDispositionFilenamecontentDispositionHeaderloadInfoloadInfoisDocumentLOAD_DOCUMENT_URILOAD_RETARGETED_DOCUMENT_URILOAD_REPLACELOAD_INITIAL_DOCUMENT_URILOAD_TARGETEDLOAD_CALL_CONTENT_SNIFFERSLOAD_CLASSIFY_URILOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPELOAD_EXPLICIT_CREDENTIALSLOAD_BYPASS_SERVICE_WORKERDISPOSITION_INLINEDISPOSITION_ATTACHMENTG`@S_`c`2@i2o`?@?`2@@`@``j2j`@-@@[v`@`
 +@=gnsIBadCertListener2notifyCertProblem2q?`
extIPreferencenametypevaluevaluelockedlockedmodifiedbrancheventsreset2
`@`
@
`
`:`dnsIUnicharOutputStreamwritewriteStringflushclose2v`
|`
mozIStorageBindingParamsbindByNamebindUTF8StringByNamebindStringByNamebindDoubleByNamebindInt32ByNamebindInt64ByNamebindNullByNamebindBlobByNamebindStringAsBlobByNamebindUTF8StringAsBlobByNamebindAdoptedBlobByNamebindByIndexbindUTF8StringByIndexbindStringByIndexbindDoubleByIndexbindInt32ByIndexbindInt64ByIndexbindNullByIndexbindBlobByIndexbindStringAsBlobByIndexbindUTF8StringAsBlobByIndexbindAdoptedBlobByIndex2  	 # 3 C R a x     	 !!!!!1!I!ensIDOMCSSMozDocumentRulensIIdentitySignCallbacksignFinished2"nsIContentViewerinitcontainercontainerloadStartloadCompleteloadCompletedisStoppedpermitUnloadinPermitUnloadpermitUnloadInternalbeforeUnloadFiringpageHideclosedestroystopDOMDocumentDOMDocumentgetDocumentgetBoundssetBoundssetBoundsWithFlagspreviousViewerpreviousViewermoveshowhidestickystickyrequestWindowCloseopenclearHistoryEntrysetPageModehistoryEntryisTabModalPromptAllowedisHiddenisHiddenpresShellpresContextsetDocumentInternalfindContainerViewsetNavigationTimingscrollToNodetextZoomtextZoomeffectiveTextZoomfullZoomfullZoomoverrideDPPXoverrideDPPXauthorStyleDisabledauthorStyleDisabledforceCharacterSetforceCharacterSethintCharacterSethintCharacterSethintCharacterSetSourcehintCharacterSetSourcegetContentSizegetContentSizeConstrainedminFontSizeminFontSizeappendSubtreepausePaintingresumePaintingemulateMediumstopEmulatingMediumgetHintCharsetsetHintCharsetgetForceCharsetsetForceCharseteDelayResize2F##
`F@#F(#!
#+#8`
#F`
#P`
#]`
#l
`
#`
#
#u###`@#(#####` H$
 $$$#$(`
@$/
$6`
$I2u$N$`
ڀ$l`u$y`
$`
@$
$`$`$
($($
$%`@%	%`%$`@%-%6`@%C%P`
@%d
%x@%%@%%`@%%@@%@@&`@&!&-&;&I&X&f(&z(&
(&(&
&nsIFinishDumpingCallbackcallback2)2extIEventListenerhandleEvent2)InsIPreloadedStyleSheet2nsINetworkPredictorVerifieronPredictPrefetchonPredictPreconnectonPredictDNS2*M*_*snsIUDPSocketListeneronPacketReceivedonStopListening2*U*UnsIParentalControlsServiceparentalControlsEnabledblockFileDownloadsEnabledisAllowedrequestURIOverriderequestURIOverridesloggingEnabledlogDOWNLOADINSTALL_EXTENSIONINSTALL_APPBROWSESHAREBOOKMARKADD_CONTACTSET_IMAGEMODIFY_ACCOUNTSREMOTE_DEBUGGINGIMPORT_SETTINGSPRIVATE_BROWSINGDATA_CHOICESCLEAR_HISTORYMASTER_PASSWORDGUEST_BROWSINGADVANCED_SETTINGSCAMERA_MICROPHONEBLOCK_LISTTELEMETRYHEALTH_REPORTDEFAULT_THEMEePCLog_URIVisitePCLog_FileDownload2+#`
+;`
+U`
+_?`
+r?`
+`
+
1+++++++++	+
,
,,.
,;,I,Y,h,z,,,,,,nsIObjectLoadingContentactualTypedisplayedTypegetContentTypeForMIMETypebaseURIpluginInstancehasNewFramegetPrintFramepluginDestroyedpluginCrashedreloadactivatedstopPluginInstancesyncStartPluginInstanceasyncStartPluginInstanceinitializeFromChannelsrcURIskipFakePluginsTYPE_LOADINGTYPE_IMAGETYPE_PLUGINTYPE_FAKE_PLUGINTYPE_DOCUMENTTYPE_NULLPLUGIN_ACTIVEPLUGIN_UNSUPPORTEDPLUGIN_ALTERNATEPLUGIN_DISABLEDPLUGIN_BLOCKLISTEDPLUGIN_OUTDATEDPLUGIN_CRASHEDPLUGIN_SUPPRESSEDPLUGIN_USER_DISABLEDPLUGIN_CLICK_TO_PLAYPLUGIN_VULNERABLE_UPDATABLEPLUGIN_VULNERABLE_NO_UPDATE2..`.'`.A`.I`.X.d`.r.
.
.`
.....`////*/6/G/U/_/m////////0	0+
nsISHTransactionsHEntrysHEntryprevprevnextnextpersistpersistcreate2	1`u@1u1`(@1(1`(@1(1`
@1
1u(nsIWindowMediatorListeneronWindowTitleChangeonOpenWindowonCloseWindow22}f2f2fnsIUploadChannel2explicitSetUploadStreamuploadStreamHasHeadersensureUploadStreamIsCloneablecloneUploadStream22䀸
3`
338`nsICaptivePortalDetectorcheckCaptivePortalabortcancelLoginfinishPreparation23333nsICacheEntryDoomCallbackonCacheEntryDoomed24$nsIDirectoryServiceProvider2getFiles4e`KnsIRelativeFilePreffilefilerelativeToKeyrelativeToKey24`1@414@4nsIContextMenuInfomouseEventtargetNodeassociatedLinkimageContainerimageSrcbackgroundImageContainerbackgroundImageSrc25`5`55(`}57`5@`}5Y`nsIPaymentRequesttabIdrequestIdpaymentMethodspaymentDetailspaymentOptionsupdatePaymentDetails25`55`5`~6`Y6~nsIFileappendappendNativenormalizecreateleafNameleafNamenativeLeafNamenativeLeafNamecopyToCopyToNativecopyToFollowingLinkscopyToFollowingLinksNativemoveTomoveToNativerenameTorenameToNativeremovepermissionspermissionspermissionsOfLinkpermissionsOfLinklastModifiedTimelastModifiedTimelastModifiedTimeOfLinklastModifiedTimeOfLinkfileSizefileSizefileSizeOfLinktargetnativeTargetpathnativePathexistsisWritableisReadableisExecutableisHiddenisDirectoryisFileisSymlinkisSpecialcreateUniquecloneequalscontainsparentdirectoryEntriesinitWithPathinitWithNativePathinitWithFilefollowLinksfollowLinksopenNSPRFileDescopenANSIFileDescloaddiskSpaceAvailableappendRelativePathappendRelativeNativePathpersistentDescriptorpersistentDescriptorreveallaunchgetRelativeDescriptorsetRelativeDescriptorgetRelativePathsetRelativePathNORMAL_FILE_TYPEDIRECTORY_TYPEOS_READAHEADDELETE_ON_CLOSE2B6|6666@66H66161616171717)17217A
7H`@7T7``@7r7`@77`@77`@77`778	88`
8 `
8+`
86`
8C`
8L`
8X`
8_`
8i`
8s8`181`
81`
8`18`K88818`
@8
8`9`9`9`9-9@9Y@9n9991919191999@:
nsIXPCComponents_Results2nsIPresentationSessionTransportBuilder2nsIPaymentMethodDatasupportedMethodsdata2==`nsIArrayBufferInputStreamsetData=nsIDNSServiceDiscoveryListeneronDiscoveryStartedonDiscoveryStoppedonServiceFoundonServiceLostonStartDiscoveryFailedonStopDiscoveryFailed2>>>-L><L>J>ansIWindowCreatorcreateChromeWindow2>Ȁ`ȀnsIZipReaderCacheinitgetZipgetZipIfCachedisCachedgetInnerZipgetFd2??1`=?1`=?,1`
?51`=?A1`nsISiteSecurityStatehostnameexpireTimesecurityPropertyStateincludeSubdomainsoriginAttributesSECURITY_PROPERTY_UNSETSECURITY_PROPERTY_SETSECURITY_PROPERTY_KNOCKOUTSECURITY_PROPERTY_NEGATIVE2??`?`?`
?`@@ @6@QnsISerializationHelperserializeToStringdeserializeObject2@@`2nsIDOMCounteridentifierlistStyleseparator2A-A8ABnsIFeedEntrysummarysummarypublishedpublishedcontentcontentenclosuresenclosuresmediaContentmediaContent
A~`@AA@AA`@AA`@AA`@AnsICacheDeviceInfodescriptionusageReportentryCounttotalSizemaximumSize2Bp`B|`B`B`B`nsIDirectoryEnumeratornextFileclose2B`1CnsIMutablemutablemutable2C.`
@C6
nsIWebBrowserPersistDocumentReceiveronDocumentReadyonError2C~CnsIStreamLoaderinitnumBytesReadrequestjCOC`C`nsIHandlerInfotypedescriptiondescriptionpreferredApplicationHandlerpreferredApplicationHandlerpossibleApplicationHandlershasDefaultHandlerdefaultDescriptionlaunchWithURIpreferredActionpreferredActionalwaysAskBeforeHandlingalwaysAskBeforeHandlingsaveToDiskalwaysAskuseHelperApphandleInternallyuseSystemDefault2
DD@D*D6`@DRDn`D`
DD?D`@DD`
@D
E
EE"E/E@nsICategoryManagergetCategoryEntryaddCategoryEntrydeleteCategoryEntrydeleteCategoryenumerateCategoryenumerateCategories2F&`F7

`FH
F\Fk`KF}`KnsIPrintingPromptServiceshowPrintDialogshowProgressshowPageSetupshowPrinterProperties2G〒%ڀG〒%ڀx
@@,@
G"〒ڀxG0】ڀmozIGeckoMediaPluginChromeServiceaddPluginDirectoryremovePluginDirectoryremoveAndDeletePluginDirectoryforgetThisSiteisPersistentStorageAllowedgetStorageDir2GGG
HH$`
H?`1nsIScriptChannelexecutionPolicyexecutionPolicyexecuteAsyncexecuteAsyncNO_EXECUTIONEXECUTE_NORMAL2H`@HH`
@H
HHnsIXPCComponents_ClassesByID2nsIContextMenuListeneronShowContextMenuCONTEXT_NONECONTEXT_LINKCONTEXT_IMAGECONTEXT_DOCUMENTCONTEXT_TEXTCONTEXT_INPUT2I{IIIIIInsICommandHandlerexecquery2JB`JG`nsIDOMNSEditableElementeditorsetUserInput2J`aJnsIScrollablegetDefaultScrollbarPreferencessetDefaultScrollbarPreferencesgetScrollbarVisibilityScrollOrientation_XScrollOrientation_YScrollbar_AutoScrollbar_NeverScrollbar_Always2J`JK@
@
KK0KDKSKcnsIHttpHeaderVisitorvisitHeader2KnsIPrefBranchInternalnsIAuthPromptpromptpromptUsernameAndPasswordpromptPasswordSAVE_PASSWORD_NEVERSAVE_PASSWORD_FOR_SESSIONSAVE_PASSWORD_PERMANENTLY2L+@`
L2`
LL`
L[LoLnsIStreamLoaderObserveronStreamComplete2MA2nsIContentViewerEditclearSelectionselectAllcopySelectioncopyablecopyLinkLocationinLinkcopyImageinImagegetContentscanGetContentssetCommandNodeCOPY_IMAGE_TEXTCOPY_IMAGE_HTMLCOPY_IMAGE_DATACOPY_IMAGE_ALL2McMrM|M`
MM`
MM`
M
M`
MMMNNnsIConsoleListenerobserve2NgnsICacheStorageasyncOpenURIopenTruncateexistsgetCacheIndexEntryAttrsasyncDoomURIasyncEvictStorageasyncVisitStorageOPEN_NORMALLYOPEN_TRUNCATEOPEN_READONLYOPEN_PRIORITYOPEN_BYPASS_IF_BUSYCHECK_MULTITHREADEDOPEN_SECRETLYOPEN_INTERCEPTED2NҀO`#O`
O@
@O4,OA,OSb
OeOsOOOOO O@nsPICommandUpdaterinitcommandStatusChanged2PPinIDOMUtilsgetAllStyleSheetsgetCSSStyleRulesgetRuleLinegetRuleColumngetRelativeRuleLinegetCSSLexergetSelectorCountgetSelectorTextgetSpecificityselectorMatchesElementisInheritedPropertygetCSSPropertyNamesgetCSSValuesForPropertycolorNameToRGBrgbToColorNamecolorToRGBAisValidCSSColorcssPropertyIsValidgetSubpropertiesForCSSPropertycssPropertyIsShorthandcssPropertySupportsTypeisIgnorableWhitespacegetParentForNodegetChildrenForNodegetBindingURLsgetContentStatesetContentStateremoveContentStategetUsedFontFacesgetCSSPseudoElementNamesaddPseudoClassLockremovePseudoClassLockhasPseudoClassLockclearPseudoClassLocksparseStyleSheetscrollElementIntoViewEXCLUDE_SHORTHANDSINCLUDE_ALIASESTYPE_LENGTHTYPE_PERCENTAGETYPE_COLORTYPE_URLTYPE_ANGLETYPE_FREQUENCYTYPE_TIMETYPE_GRADIENTTYPE_TIMING_FUNCTIONTYPE_IMAGE_RECTTYPE_NUMBER2$PD`2Q`Q `Q,`Q:`QN`QZ`QkQ{`Q`
Q`
QD`QD`Q`QQ`R`
R`
R.D`RM`
Rd`
R|`
R
`R
`R`R`R`
R
`
R`S	D`S"
S5SK`
S^StS
SSSSSSSSTTTT4	TD
nsPIPlacesDatabaseDBConnectionasyncExecuteLegacyQueriesshutdownClientconnectionShutdownClient2W`W
~
`6W'`MW6`MnsIDOMXULMenuListElementeditableeditableopenopenlabelcropcropimageimageinputField@
W`
@W
W`
@W
WW@WW@WW`nsIXBLAccessibleaccessibleName2XqnsIWindowDataSourcegetWindowForResource2X`ʀnsIProxiedProtocolHandlernewProxiedChannel2newProxiedChannel{X7`X7`nsIPromptService2promptAuthasyncPromptAuthY_〒s
`
Yj	〒耒2s
`rnsIIdleObservertimeonidleonactive2Y`YYnsITransferinitsetSha256HashsetSignatureInfosetRedirectsZ1r
ZZ(Z9nsISelectionControllersetDisplaySelectiongetDisplaySelectiongetSelectionscrollSelectionIntoViewrepaintSelectionsetCaretEnabledsetCaretReadOnlygetCaretEnabledcaretVisiblesetCaretVisibilityDuringSelectioncharacterMovephysicalMovecharacterExtendForDeletecharacterExtendForBackspacewordMovewordExtendForDeletelineMoveintraLineMovepageMovecompleteScrollcompleteMovescrollPagescrollLinescrollCharacterselectAllcheckVisibilitycheckVisibilityContentSELECTION_NONESELECTION_NORMALSELECTION_SPELLCHECKSELECTION_IME_RAWINPUTSELECTION_IME_SELECTEDRAWTEXTSELECTION_IME_CONVERTEDTEXTSELECTION_IME_SELECTEDCONVERTEDTEXTSELECTION_ACCESSIBILITYSELECTION_FINDSELECTION_URLSECONDARYSELECTION_URLSTRIKEOUTNUM_SELECTIONTYPESSELECTION_ANCHOR_REGIONSELECTION_FOCUS_REGIONSELECTION_WHOLE_SELECTIONNUM_SELECTION_REGIONSSELECTION_OFFSELECTION_HIDDENSELECTION_ONSELECTION_DISABLEDSELECTION_ATTENTIONSCROLL_SYNCHRONOUSSCROLL_FIRST_ANCESTOR_ONLYSCROLL_CENTER_VERTICALLYSCROLL_OVERFLOW_HIDDENSCROLL_FOR_CARET_MOVEMOVE_LEFTMOVE_RIGHTMOVE_UPMOVE_DOWNwZZ`Z`)ZZ[
[
['`
[7`
[D
[f

[t
[[[

[
[

[

[

[
\

\
\
\%
\5\?`
\O`
\f\u\\\\\ ]@](]7]N]e]x]]]]]]^^^*^=^X^q ^@^^^^amIWebInstallPromptconfirm2`nsILoginManagerinitializationPromiseaddLoginremoveLoginmodifyLoginremoveAllLoginsgetAllLoginsgetAllDisabledHostsgetLoginSavingEnabledsetLoginSavingEnabledfindLoginscountLoginsautoCompleteSearchAsyncstopSearchsearchLoginsuiBusyisLoggedIn2a`a0`a9aE2aQaaD`anD`a`
a
a@`a`a"aa@`a`
a`
nsIDirIndexParserlistenerlistenercommentencodingencodingjc`@c
c`c`@c'nsIDOMGetUserMediaErrorCallbackonError2c2mozILivemarkInfoidguidtitleparentIdparentGuidindexdateAddedlastModifiedfeedURIsiteURI2
c`ccc`c`c`c`c`c`d`nsICursorContinueCallbackhandleContinue2dextIEventsaddListenerremoveListener2d"d"nsIContentSecurityManagerperformSecurityCheckisOriginPotentiallyTrustworthy2ej`je'`
nsIScriptableUnescapeHTMLunescapeparseFragment2ee
`nsIConsoleMessagelogLeveltimeStampmessagetoStringdebuginfowarnerror2e`e`e`effffnsICycleCollectorLogSinkopencloseGCLogcloseCCLogfilenameIdentifierfilenameIdentifierprocessIdentifierprocessIdentifiergcLogccLog2	f@@fff@ff`@ff`1f`1nsIWebNavigationcanGoBackcanGoForwardgoBackgoForwardgotoIndexloadURIloadURIWithOptionsreloadstopdocumentcurrentURIreferringURIsessionHistorysessionHistorysetOriginAttributesBeforeLoadingLOAD_FLAGS_MASKLOAD_FLAGS_NONELOAD_FLAGS_IS_REFRESHLOAD_FLAGS_IS_LINKLOAD_FLAGS_BYPASS_HISTORYLOAD_FLAGS_REPLACE_HISTORYLOAD_FLAGS_BYPASS_CACHELOAD_FLAGS_BYPASS_PROXYLOAD_FLAGS_CHARSET_CHANGELOAD_FLAGS_STOP_CONTENTLOAD_FLAGS_FROM_EXTERNALLOAD_FLAGS_ALLOW_MIXED_CONTENTLOAD_FLAGS_FIRST_LOADLOAD_FLAGS_ALLOW_POPUPSLOAD_FLAGS_BYPASS_CLASSIFIERLOAD_FLAGS_FORCE_ALLOW_COOKIESLOAD_FLAGS_DISALLOW_INHERIT_PRINCIPALLOAD_FLAGS_DISALLOW_INHERIT_OWNERLOAD_FLAGS_ERROR_LOAD_CHANGES_RVLOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUPLOAD_FLAGS_FIXUP_SCHEME_TYPOSSTOP_NETWORKSTOP_CONTENTSTOP_ALL2gh`
gr`
gggg䀒䄒g䀒䀒ggg`g`g`g`@ghh!h1hAhW hj@hhhhhii i9@iOigiiiijj/ jMjZjgnsIStreamListeneronDataAvailablel'怒2nsIFeedsubtitlesubtitletypeenclosureCountenclosureCountitemsitemscloudcloudgeneratorgeneratorimageimagetextInputtextInputskipDaysskipDaysskipHoursskipHoursTYPE_FEEDTYPE_AUDIOTYPE_IMAGETYPE_VIDEOl^`@lglp`lu`@ll`@ll`E@lEl`@ll`E@lEl`E@lEl`@ll`@lmmmm%nsIZipWritercommentcommentinQueuefileopengetEntryhasEntryaddEntryDirectoryaddEntryFileaddEntryChanneladdEntryStreamremoveEntryprocessQueueclosealignStoredFilesCOMPRESSION_NONECOMPRESSION_FASTESTCOMPRESSION_DEFAULTCOMPRESSION_BEST2nF@nNnV`
n^`1nc1nh`nq`
nz
n1
n
n
n
n2nnnno
o!	nsIRDFObserveronAssertonUnassertonChangeonMoveonBeginUpdateBatchonEndUpdateBatch2p4p=pHpQpXpknsIFetchTelemetryDataCallbackcomplete2q!nsIUrlClassifierPrefixSetinitsetPrefixesgetPrefixescontainsisEmptyloadFromFilestoreToFile2qSqXqd@`qp`
qy`
q1q1nsIDocumentEncoderNodeFixupfixupNode2r@
`nsIPresentationDeviceProviderlistenerlistenerforceDiscovery2rT`@r]rfnsIPresentationTerminateRequestdevicepresentationIdcontrolChannelisFromReceiver2r`rr`r`
nsITimerinitinitWithCallbackInitHighResolutionWithCallbackcancelinitWithNamedFuncCallbackinitWithNameableFuncCallbackdelaydelaytypetypeclosurecallbacktargettargetallowedEarlyFiringMicrosecondsTYPE_ONE_SHOTTYPE_REPEATING_SLACKTYPE_REPEATING_PRECISETYPE_REPEATING_PRECISE_CAN_SKIPTYPE_REPEATING_SLACK_LOW_PRIORITYTYPE_ONE_SHOT_LOW_PRIORITY2s,xs1wsBwsashss`@ss`@ss`s`ws`V@sVs`sttt-tMtonsIProfileMigratormigrate2uEnsISHistoryInternaladdEntryrootTransactionsetRootDocShellupdateIndexreplaceEntrynotifyOnHistoryReloadevictOutOfRangeContentViewersevictExpiredContentViewerForEntryevictAllContentViewersaddToExpirationTrackerremoveFromExpirationTrackerRemoveDynEntriesRemoveEntries2
uu
u`(uFuuuu`
vv3fvUvlfvf(vR
(v
nsIMessageLooppostIdleTask2wpnsINavHistoryContainerResultNodecontainerOpencontainerOpenstatehasChildrenchildCountgetChildgetChildIndexSTATE_CLOSEDSTATE_LOADINGSTATE_OPENED
w`
@w
w`w`
w`w`
w
`xxxnsIGetHostnameListeneronGotHostnameonGetHostnameFailed2xxnsIParentRedirectingChannelstartRedirectcontinueVerificationcompleteRedirectyyy&
nsIDOMModalContentWindowdialogArgumentsreturnValuereturnValue2y`y`@ynsIScriptableInputStreamcloseinitavailablereadreadBytes2yyy`z`zxpcIJSGetFactoryget2z_`3nsIDOMCSSKeyframesRulenamenamecssRulesappendRuledeleteRulefindRule2z@zz`szzz`ansIInputStreamPumpinitasyncRead{
V{"j2nsIPromptServicealertalertCheckconfirmconfirmCheckconfirmExpromptpromptUsernameAndPasswordpromptPasswordselectBUTTON_POS_0BUTTON_POS_1BUTTON_POS_2BUTTON_TITLE_OKBUTTON_TITLE_CANCELBUTTON_TITLE_YESBUTTON_TITLE_NOBUTTON_TITLE_SAVEBUTTON_TITLE_DONT_SAVEBUTTON_TITLE_REVERTBUTTON_TITLE_IS_STRINGBUTTON_POS_0_DEFAULTBUTTON_POS_1_DEFAULTBUTTON_POS_2_DEFAULTBUTTON_DELAY_ENABLESTD_OK_CANCEL_BUTTONSSTD_YES_NO_BUTTONS2	{n】{t】
{】`
{】
`
{
】
`{】
`
{】
`
{】
`
{】@`
{{{{|| |1|A|S|j|~||||||nsIWebBrowseraddWebBrowserListenerremoveWebBrowserListenercontainerWindowcontainerWindowparentURIContentListenerparentURIContentListenercontentDOMWindowisActiveisActivebinarySetOriginAttributes2
~	~	~`Ȁ@~Ȁ~`@~` `
@)
(2
nsIDOMXULMultiSelectControlElementselTypeselTypecurrentItemcurrentItemcurrentIndexcurrentIndexselectedItemsaddItemToSelectionremoveItemFromSelectiontoggleItemSelectionselectItemselectItemRangeselectAllinvertSelectionclearSelectionselectedCountgetSelectedItem@@`
@
`@!.`<
O
g
{


``
nsIFindfindBackwardsfindBackwardscaseSensitivecaseSensitiveentireWordentireWordFind2`
@
`
@
`
@
`nsIOutputStreamCallbackonOutputStreamReady2pnsIUrlClassifierInfogetCacheInfo2`XnsIHttpActivityObserverobserveActivityisActiveACTIVITY_TYPE_SOCKET_TRANSPORTACTIVITY_TYPE_HTTP_TRANSACTIONACTIVITY_SUBTYPE_REQUEST_HEADERACTIVITY_SUBTYPE_REQUEST_BODY_SENTACTIVITY_SUBTYPE_RESPONSE_STARTACTIVITY_SUBTYPE_RESPONSE_HEADERACTIVITY_SUBTYPE_RESPONSE_COMPLETEACTIVITY_SUBTYPE_TRANSACTION_CLOSE22`
=P]PPPPPnsISSLSocketControlnotificationCallbacksnotificationCallbacksproxyStartSSLStartTLSsetNPNListnegotiatedNPNgetAlpnEarlySelectionearlyDataAccepteddriveHandshakejoinConnectiontestJoinConnectionisAcceptableForHostKEAUsedKEAKeyBitsproviderFlagsSSLVersionUsedSSLVersionOfferedMACAlgorithmUsedclientCertclientCertbypassAuthenticationfailedVerificationKEY_EXCHANGE_UNKNOWNSSL_VERSION_3TLS_VERSION_1TLS_VERSION_1_1TLS_VERSION_1_2TLS_VERSION_1_3SSL_VERSION_UNKNOWNSSL_MAC_UNKNOWNSSL_MAC_NULLSSL_MAC_MD5SSL_MAC_SHASSL_HMAC_MD5SSL_HMAC_SHASSL_HMAC_SHA256SSL_MAC_AEAD2`?@?`
`
,`
?`
S`[`f`t````@`
`
%5EYivmozIVisitInfovisitIdvisitDatetransitionTypereferrerURI2/`7`A`P`nsIPrefetchServiceprefetchURIpreloadURIhasMoreElementscancelPrefetchPreloadURI2
`
nsIDOMStorage2nsILoginManagerPrompterinitbrowserbrowseropeneropenerpromptToSavePasswordpromptToChangePasswordpromptToChangePasswordWithUsernames2Zʀ_`@go`ʀ@vʀ}nsIIOServicegetProtocolHandlergetProtocolFlagsnewURInewFileURInewChannelFromURI2newChannelFromURIWithLoadInfonewChannel2newChannelFromURInewChannelofflineofflineconnectivityallowPortextractSchemehostnameIsLocalIPAddress2N`{a`r`y1```	````
@
`
`
`
nsIAccessibleValuemaximumValueminimumValuecurrentValuecurrentValueminimumIncrement2F`	S`	``	@m	z`	nsIContentPrefObserveronContentPrefSetonContentPrefRemoved2

nsIExceptionmessageresultnamefilenamelineNumbercolumnNumberlocationdatatoString2	5=`DIR`]`j`s`2xnsIClipboardHelpercopyStringToClipboardcopyString2nsIOfflineCacheUpdateServicenumUpdatesgetUpdatescheduleUpdatescheduleAppUpdatescheduleOnDocumentStopcheckForUpdateofflineAppAllowedofflineAppAllowedForURIallowOfflineAppALLOW_NO_WARN2	T`_`hi`hx1`hx`
`
nsIStorageStreaminitgetOutputStreamnewInputStreamlengthlengthwriteInProgress2`p``@`
nsIDownloadObserveronDownloadComplete2h怒21nsITooltipListeneronShowTooltiponHideTooltip2mozIGeckoMediaPluginServicethreadRunPluginCrashCallbackshasPluginForAPIgetGMPVideoDecodergetDecryptingGMPVideoDecodergetGMPVideoEncodergetGMPDecryptorgetNodeId2`
%`
5HexnsIDOMCSSConditionRuleconditionTextconditionTextZ(@6nsIStringInputStreamsetDataadoptDatashareDataSizeOfIncludingThist|(nsIDOMNodeListitemlength2``nsIFTPEventSinkOnFTPControlLog2#
nsISelectionListenernotifySelectionChangedNO_REASONDRAG_REASONMOUSEDOWN_REASONMOUSEUP_REASONKEYPRESS_REASONSELECTALL_REASONCOLLAPSETOSTART_REASONCOLLAPSETOEND_REASONIME_REASON2[)	r| @nsIContentDispatchChooseraskREASON_CANNOT_HANDLE2rB?vnsIContentProcessInfoisAliveprocessIdopenertabCountmessageManager2`
````݀nsIContentSignatureVerifierverifyContentSignaturecreateContextcreateContextWithoutCertChainupdateend2Q`
hvˀ`
nsINullChannel2inISearchObserveronSearchStartonSearchResultonSearchEndonSearchErrorIN_SUCCESSIN_INTERRUPTEDIN_ERROR2`"`1`=`KVensIDateTimeInputAreanotifyInputElementValueChangednotifyMinMaxStepAttrChangedsetValueFromPickerfocusInnerTextBoxblurInnerTextBoxhasBadInputsetPickerStatesetEditAttributeremoveEditAttribute2	!3D`
P
_pnsIPresentationDeviceListeneraddDeviceremoveDeviceupdateDeviceonSessionRequestonTerminateRequestonReconnectRequest2!2
EnsIOfflineCacheUpdateObserverupdateStateChangedapplicationCacheAvailableSTATE_ERRORSTATE_CHECKINGSTATE_NOUPDATESTATE_OBSOLETESTATE_DOWNLOADINGSTATE_ITEMSTARTEDSTATE_ITEMCOMPLETEDSTATE_ITEMPROGRESSSTATE_FINISHED2hP	
%4CUg{
nsITextInputProcessorhasCompositionbeginInputTransactionbeginInputTransactionForTestsstartCompositionsetPendingCompositionStringappendClauseToPendingCompositionsetCaretInPendingCompositionflushPendingCompositioncommitCompositioncommitCompositionWithcancelCompositionkeydownkeyupgetModifierStateshareModifierStateOfATTR_RAW_CLAUSEATTR_SELECTED_RAW_CLAUSEATTR_CONVERTED_CLAUSEATTR_SELECTED_CLAUSEKEY_DEFAULT_PREVENTEDKEY_NON_PRINTABLE_KEYKEY_FORCE_PRINTABLE_KEYKEY_KEEP_KEY_LOCATION_STANDARDKEY_KEEP_KEYCODE_ZEROKEY_DONT_DISPATCH_MODIFIER_KEY_EVENTKEYEVENT_NOT_CONSUMEDKEYDOWN_IS_CONSUMEDKEYPRESS_IS_CONSUMED2%`
4`
J`
h`
y`
`
%`-`
3`
D
Yi& KaunsIControllerContextinitsetCommandContext2!2nsIEditorStyleSheetsreplaceStyleSheetaddStyleSheetreplaceOverrideStyleSheetaddOverrideStyleSheetremoveStyleSheetremoveOverrideStyleSheetenableStyleSheet2:LZt
nsITableEditorinsertTableCellinsertTableColumninsertTableRowdeleteTabledeleteTableCellContentsdeleteTableCelldeleteTableColumndeleteTableRowselectTableCellselectBlockOfCellsselectTableRowselectTableColumnselectTableselectAllTableCellsswitchTableCellHeaderTypejoinTableCellssplitTableCellnormalizeTablegetCellIndexesgetTableSizegetCellAtgetCellDataAtgetFirstRowgetNextRowsetSelectionAfterTableEditgetSelectedOrParentTableElementgetSelectedCellsTypegetFirstSelectedCellgetFirstSelectedCellInTablegetNextSelectedCelleNoSearchePreviousColumnePreviousRow2#
3
E
T`x
`'
6ET@@c@@p`z@@@@@@@@
``
@``@`@@` @`4>NnsIURIFixupInfoconsumerconsumerpreferredURIfixedURIkeywordProviderNamekeywordAsSentfixupChangedProtocolfixupCreatedAlternateURIoriginalInput2	`2@ 2)`6`?Sa`
v`
nsIMemoryInfoDumperdumpMemoryReportsToNamedFiledumpMemoryInfoToTempDirdumpGCAndCCLogsToFiledumpGCAndCCLogsToSink2!2
7

O

|e
hnsISubstitutionObserveronSetSubstitution2nsIContentHandlerhandleContent2?nsIPaymentAddresscountryaddressLineregioncitydependentLocalitypostalCodesortingCodelanguageCodeorganizationrecipientphoneinit2NV`binnsICertVerificationCallbackverifyCertFinished2y
nsIRunnablerun2nsIXSLTProcessorimportStylesheettransformToFragmenttransformToDocumentsetParametergetParameterremoveParameterclearParametersreset2```&6FmozIStorageConnectioncloseclonedefaultPageSizeconnectionReadylastInsertRowIDaffectedRowslastErrorlastErrorStringschemaVersionschemaVersioncreateStatementexecuteSimpleSQLtableExistsindexExiststransactionInProgressbeginTransactionbeginTransactionAscommitTransactionrollbackTransactioncreateTablesetGrowthIncrementenableModulegetQuotaObjectsTRANSACTION_DEFERREDTRANSACTION_IMMEDIATETRANSACTION_EXCLUSIVE
```
```(8`@FT`du`
`
`
@@)>TnsIUpdateCheckListeneronCheckCompleteonError2nsIURLFormatterformatURLformatURLPreftrimSensitiveURLs2nsIResumableChannelresumeAtentityID2LUnsIConverterOutputStreaminitpnsICrashReporterenabledsetEnabledserverURLserverURLminidumpPathminidumpPathgetMinidumpForIDgetExtraFileForIDannotateCrashReportappendAppNotesToCrashReportregisterAppMemorywriteMinidumpForExceptionappendObjCExceptionInfoToAppNotessubmitReportssubmitReportsUpdateCrashEventsDirsaveMemoryReportsetTelemetrySessionId2`

`Ȁ@Ȁ`1@1`1`1#7Se`
@
imgIEncoderinitFromDatastartImageEncodeaddImageFrameendImageEncodegetImageBufferUsedgetImageBufferINPUT_FORMAT_RGBINPUT_FORMAT_RGBAINPUT_FORMAT_HOSTARGBn``->PnsIProtectedAuthThreadloginslotgetTokenName2x`nsIContentURIGroupergroup2SnsIEditorUtilsslurpBlob2}〒ӀimgIToolsdecodeImagedecodeImageDataencodeImageencodeScaledImagegetImgLoaderForDocumentgetImgCacheForDocumentencodeCroppedImagecreateScriptedObserver2䀸`}䀸}}`}```}`(`CnsIAutoCompleteSearchDescriptorsearchTypeclearingAutoFillSearchesAgainSEARCH_TYPE_DELAYEDSEARCH_TYPE_IMMEDIATE2`
`
+?nsIUrlClassifierCallbackhandleEvent2nsIDOMMozBrowserFramemozbrowsermozbrowser2`
@
nsISAXDTDHandlernotationDeclunparsedEntityDecl2nsIDataTypeVTYPE_INT8VTYPE_INT16VTYPE_INT32VTYPE_INT64VTYPE_UINT8VTYPE_UINT16VTYPE_UINT32VTYPE_UINT64VTYPE_FLOATVTYPE_DOUBLEVTYPE_BOOLVTYPE_CHARVTYPE_WCHARVTYPE_VOIDVTYPE_IDVTYPE_DOMSTRINGVTYPE_CHAR_STRVTYPE_WCHAR_STRVTYPE_INTERFACEVTYPE_INTERFACE_ISVTYPE_ARRAYVTYPE_STRING_SIZE_ISVTYPE_WSTRING_SIZE_ISVTYPE_UTF8STRINGVTYPE_CSTRINGVTYPE_ASTRINGVTYPE_EMPTY_ARRAYVTYPE_EMPTY2]ht	

-=M`lnsIFeedResultListenerhandleResult2nsIDOMWindowUtilsimageAnimationModeimageAnimationModedocCharsetIsForcedgetCursorTypegetDocumentMetadataredrawupdateLayerTreelastTransactionIdgetViewportInfogetContentViewerSizesetDisplayPortForElementsetDisplayPortMarginsForElementsetDisplayPortBaseForElementsetResolutiongetResolutionsetResolutionAndScaleTosetRestoreResolutionisResolutionSetisFirstPaintisFirstPaintgetPresShellIdsendMouseEventsendPointerEventsendTouchEventsendMouseEventToWindowsendPointerEventToWindowsendTouchEventToWindowsendWheelEventsendKeyEventsendNativeKeyEventsendNativeMouseEventsendNativeMouseMovesuppressAnimationsendNativeMouseScrollEventsendNativeTouchPointsendNativeTouchTapclearNativeTouchSequenceactivateNativeMenuItemAtforceUpdateNativeMenuAtGetSelectionAsPlaintextfocusgarbageCollectcycleCollectrunNextCollectorTimersendSimpleGestureEventelementFromPointnodesFromRectgetTranslationNodescompareCanvasesisMozAfterPaintPendingsuppressEventHandlingdisableNonTestMouseEventsgetScrollXYgetScrollXYFloatgetScrollbarSizegetBoundsWithoutFlushingneedsFlushgetRootBoundsIMEIsOpenIMEStatusscreenPixelsPerCSSPixelfullZoomdispatchDOMEventViaPresShelldispatchEventToChromeOnlygetClassNamesendContentCommandEventsendQueryContentEventremoteFrameFullscreenChangedremoteFrameFullscreenRevertedhandleFullscreenRequestsexitFullscreensendSelectionSetEventselectAtPointgetVisitedDependentComputedStyleouterWindowIDcurrentInnerWindowIDenterModalStateleaveModalStateisInModalStatesetDesktopModeViewportsuspendTimeoutsresumeTimeoutslayerManagerTypelayerManagerRemoteusingAdvancedLayerssupportsHardwareH264DecodingcurrentAudioBackendcurrentMaxAudioChannelscurrentPreferredChannelLayoutcurrentPreferredSampleRateaudioDevicesstartFrameTimeRecordingstopFrameTimeRecordingdisplayDPIgetOuterWindowWithIdcontainerElementRenderDocumentadvanceTimeAndRefreshrestoreNormalRefreshisTestControllingRefreshesasyncPanZoomEnabledsetAsyncScrollOffsetsetAsyncZoomflushApzRepaintszoomToFocusedInputcomputeAnimationDistancegetAnimationTypeForLonghandgetUnanimatedComputedStylefocusedInputTypegetViewIdleafLayersPartitionWindowcheckAndClearPaintedStateisPartOfOpaqueLayernumberOfAssignedPaintedLayersgetFileIdgetFilePathgetFileReferencesflushPendingFileDeletionsisIncrementalGCEnabledstartPCCountProfilingstopPCCountProfilingpurgePCCountsgetPCCountScriptCountgetPCCountScriptSummarygetPCCountScriptContentspaintingSuppressedpluginssetScrollPositionClampingScrollPortSizedisableDialogsenableDialogsareDialogsEnabledloadSheetloadSheetUsingURIStringaddSheetremoveSheetremoveSheetUsingURIStringisHandlingUserInputmillisSinceLastUserInputallowScriptsToCloseisParentWindowMainWidgetVisibleisNodeDisabledForEventspaintFlashingpaintFlashinggetOMTAStylerequestCompositorPropertysetHandlingUserInputgetContentAPZTestDatagetCompositorAPZTestDatapostRestyleSelfEventmediaSuspendmediaSuspendaudioMutedaudioMutedaudioVolumeaudioVolumexpconnectArgumentaskPermissionrestyleGenerationframesConstructedframesReflowedsetChromeMarginserviceWorkersTestingEnabledserviceWorkersTestingEnabledgetFrameUniformityTestDataenterChaosModeleaveChaosModetriggerDeviceResethasRuleProcessorUsedByMultipleStyleSetsforceUseCounterFlushrespectDisplayPortSuppressionforceReflowInterruptterminateGPUProcessgpuProcessPidisTimeoutTrackingaddManuallyManagedStateremoveManuallyManagedStategetStorageUsagegetDirectionFromTextensureDirtyRootFrameisStyledByServoMODIFIER_ALTMODIFIER_CONTROLMODIFIER_SHIFTMODIFIER_METAMODIFIER_ALTGRAPHMODIFIER_CAPSLOCKMODIFIER_FNMODIFIER_FNLOCKMODIFIER_NUMLOCKMODIFIER_SCROLLLOCKMODIFIER_SYMBOLMODIFIER_SYMBOLLOCKMODIFIER_OSWHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICEWHEEL_EVENT_CAUSED_BY_PIXEL_ONLY_DEVICEWHEEL_EVENT_CAUSED_BY_MOMENTUMWHEEL_EVENT_CUSTOMIZED_BY_USER_PREFSWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZEROWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVEWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVEWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZEROWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVEWHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVEKEY_FLAG_PREVENT_DEFAULTKEY_FLAG_NOT_SYNTHESIZED_FOR_TESTSKEY_FLAG_LOCATION_STANDARDKEY_FLAG_LOCATION_LEFTKEY_FLAG_LOCATION_RIGHTKEY_FLAG_LOCATION_NUMPADMOUSESCROLL_PREFER_WIDGET_AT_POINTMOUSESCROLL_SCROLL_LINESMOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULLTOUCH_HOVERTOUCH_CONTACTTOUCH_REMOVETOUCH_CANCELFLUSH_STYLEFLUSH_LAYOUTFLUSH_DISPLAYIME_STATUS_DISABLEDIME_STATUS_ENABLEDIME_STATUS_PASSWORDIME_STATUS_PLUGINQUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAKQUERY_CONTENT_FLAG_USE_XP_LINE_BREAKQUERY_CONTENT_FLAG_SELECTION_SPELLCHECKQUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUTQUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXTQUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXTQUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXTQUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITYQUERY_CONTENT_FLAG_SELECTION_FINDQUERY_CONTENT_FLAG_SELECTION_URLSECONDARYQUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUTQUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINTQUERY_SELECTED_TEXTQUERY_TEXT_CONTENTQUERY_CARET_RECTQUERY_TEXT_RECTQUERY_EDITOR_RECTQUERY_CHARACTER_AT_POINTQUERY_TEXT_RECT_ARRAYSELECTION_SET_FLAG_USE_NATIVE_LINE_BREAKSELECTION_SET_FLAG_USE_XP_LINE_BREAKSELECTION_SET_FLAG_REVERSESELECT_CHARACTERSELECT_CLUSTERSELECT_WORDSELECT_LINESELECT_BEGINLINESELECT_ENDLINESELECT_PARAGRAPHSELECT_WORDNOSPACEAUDIO_INPUTAUDIO_OUTPUTAGENT_SHEETUSER_SHEETAUTHOR_SHEETDEFAULT_MOUSE_POINTER_IDDEFAULT_PEN_POINTER_IDDEFAULT_TOUCH_POINTER_IDMOUSE_BUTTON_LEFT_BUTTONMOUSE_BUTTON_MIDDLE_BUTTONMOUSE_BUTTON_RIGHT_BUTTONMOUSE_BUTTONS_NO_BUTTONMOUSE_BUTTONS_LEFT_BUTTONMOUSE_BUTTONS_RIGHT_BUTTONMOUSE_BUTTONS_MIDDLE_BUTTONMOUSE_BUTTONS_4TH_BUTTONMOUSE_BUTTONS_5TH_BUTTONMOUSE_BUTTONS_NOT_SPECIFIEDDIRECTION_LTRDIRECTION_RTLDIRECTION_NOT_SET2`@`
.`<P`Wg`y	@	@
@	@	@@@
@@@(=`
M`
@Z
g`v


`



`

`








`

			`
xx0xD
V
			xq	x
xxqq3	J

`[	

`i`}@``



@@
@@
@@``
&`4`
>`H```i
`
8`
`.``
/>`
T`
b```

`
/`
C``t````D```ʀ!`2AWl`
`
`
`	1`B`L`
f`
`
``DDD`
`
!6D`Zr`
``
#(4N`
b`	{`
`
`
@
`

``5`Nc`@p}`
@
`@̀````
@+
H`cr`

`&`
8Pk`{`¥`
^µ @ 0AUeyÅø$O ~@ĭ6Orō Ť@ż8DR_lxƅƓƧƺ	.VǀDZ @BdȎȸ&6Hawɠ)8I\huʁʌʙʲ0Hb}˙˲nsIDOMCSSRuletypecssTextcssTextparentStyleSheetparentRulegetCSSRuleUNKNOWN_RULESTYLE_RULECHARSET_RULEIMPORT_RULEMEDIA_RULEFONT_FACE_RULEPAGE_RULEKEYFRAMES_RULEKEYFRAME_RULEMOZ_KEYFRAMES_RULEMOZ_KEYFRAME_RULENAMESPACE_RULECOUNTER_STYLE_RULESUPPORTS_RULEDOCUMENT_RULEFONT_FEATURE_VALUES_RULE2`@``(!,9DQ]hwفِٞٱ

nsIMessageBroadcasterbroadcastAsyncMessagechildCountgetChildAtreleaseCachedProcesses``рnsIRaceCacheWithNetworktest_triggerNetworktest_delayCacheEntryOpeningBytest_triggerDelayedOpenCacheEntry2uۉۧnsIRDFXMLSourceSerialize2pnsIPermissionManageraddgetAllForURIaddFromPrincipalremoveremoveFromPrincipalremovePermissionremoveAllremoveAllSincetestPermissiontestPermissionFromPrincipaltestPermissionFromWindowtestExactPermissiontestExactPermissionFromPrincipaltestExactPermanentPermissiongetPermissionObjectForURIgetPermissionObjectenumeratorremovePermissionsWithAttributesupdateExpireTimegetPermissionsWithKeysetPermissionsWithKeybroadcastPermissionsForPrincipalToAllContentProcesseswhenPermissionsAvailablehasPreloadPermissionsUNKNOWN_ACTIONALLOW_ACTIONDENY_ACTIONPROMPT_ACTIONEXPIRE_NEVEREXPIRE_SESSIONEXPIRE_TIME2.2`K?PWkۀ|܆ܕ`ܤ`````+
`ۀE
`ۀY`Kd݄
ݕ@ݫ`
&5BN\ixnsIDownloadHistoryaddDownloadremoveAllDownloads2?KnsIStyleSheetServiceloadAndRegisterSheetsheetRegisteredpreloadSheetpreloadSheetAsyncunregisterSheetAGENT_SHEETUSER_SHEETAUTHOR_SHEET2`
`#`nsIDOMMozWakeLockListenercallback2nsIHttpChannelInternaldocumentURIdocumentURIgetRequestVersiongetResponseVersiontakeAllSecurityMessagessetCookiesetupFallbackChannelthirdPartyFlagsthirdPartyFlagsforceAllowThirdPartyCookieforceAllowThirdPartyCookiecanceledchannelIsForDownloadchannelIsForDownloadlocalAddresslocalPortremoteAddressremotePortsetCacheKeysRedirectChainHTTPUpgradeallowSpdyallowSpdyresponseTimeoutEnabledresponseTimeoutEnabledinitialRwininitialRwinapiRedirectToURIallowAltSvcallowAltSvcbeConservativebeConservativelastModifiedTimeforceInterceptedresponseSynthesizedcorsIncludeCredentialscorsIncludeCredentialscorsModecorsModeredirectModeredirectModefetchCacheModefetchCacheModetopWindowURIsetTopWindowURIIfUnknownnetworkInterfaceIdnetworkInterfaceIdproxyURIsetCorsPreflightParametersblockAuthPromptblockAuthPromptintegrityMetadataintegrityMetadataconnectionInfoHashKeylastRedirectFlagslastRedirectFlagsTHIRD_PARTY_FORCE_ALLOWCORS_MODE_SAME_ORIGINCORS_MODE_NO_CORSCORS_MODE_CORSCORS_MODE_NAVIGATEREDIRECT_MODE_FOLLOWREDIRECT_MODE_ERRORREDIRECT_MODE_MANUALFETCH_CACHE_MODE_DEFAULTFETCH_CACHE_MODE_NO_STOREFETCH_CACHE_MODE_RELOADFETCH_CACHE_MODE_NO_CACHEFETCH_CACHE_MODE_FORCE_CACHEFETCH_CACHE_MODE_ONLY_IF_CACHED27`@@@@@
",A`@Qa`
@|
`
`
@
``	 `
@*
4`
@K
b`@nz``
@
`
@
``
`
@
%`@.7`@DQ`@`o`|@`(
`
@
@#9`HK]u0HbnsICacheTestingsuspendCacheIOThreadresumeCacheIOThreadflush2nxnsIPK11TokenDBgetInternalKeyTokenfindTokenByNamelistTokens2`׀`׀`KnsIAsyncShutdownBlockernameblockShutdownstate2EJMX`nsPIEditorTransactiontxnDescription2nsIPaymentItemlabelamountpending2`N`
nsIXPCComponents_utils_Sandbox2xpcIJSModuleLoaderloadedModulesloadedComponents2AD`OD`nsIMultiPartChannelbaseChannelpartIDisLastPart2```
nsIGConfServicegetBoolgetStringgetIntgetFloatgetStringListsetBoolsetStringsetIntsetFloatgetAppForProtocolhandlerRequiresTerminalsetAppForProtocol2`
```
&07@@
R`
jnsIPerformanceStatsServiceisMonitoringCPOWisMonitoringCPOWisMonitoringJankisMonitoringJankisMonitoringPerCompartmentisMonitoringPerCompartmentgetSnapshotjankAlertThresholdjankAlertThresholdanimationJankLevelThresholdanimationJankLevelThresholduserInputDelayThresholduserInputDelayThresholdjankAlertBufferingDelayjankAlertBufferingDelaygetObservableWindow22`
BC
T`
Be
v`
B
``@`@`@.F`@^v`ˀnsIFindServicesearchStringsearchStringreplaceStringreplaceStringfindBackwardsfindBackwardswrapFindwrapFindentireWordentireWordmatchCasematchCase2F@S`@n|`
@
`
@
`
@
`
@
nsISocketProvidernewSocketaddToSocketPROXY_RESOLVES_HOSTANONYMOUS_CONNECTNO_PERMANENT_STORAGEMITM_OKBE_CONSERVATIVE2e7@@2o7@2{nsIStringEnumeratorhasMoregetNext2N`
VnsIAsyncShutdownBarrierclientstatewait2`M`nsIPK11TokentokenNametokenLabeltokenManIDtokenHWVersiontokenFWVersiontokenSerialNumberisLoggedInloginlogoutSimplelogoutAndDropAuthenticatedResourcesresetminimumPasswordLengthneedsUserInitcheckPasswordinitPasswordchangePasswordgetAskPasswordTimesgetAskPasswordTimeoutsetAskPasswordDefaultshasPasswordisHardwareTokenneedsLoginASK_EVERY_TIMEASK_FIRST_TIMEASK_EXPIRE_TIME2
+`
6
<Ims``
`
```
`
`
)8GnsIJumpListCommittedCallbackdone2r
nsILocalCertCallbackhandleResult2nsIScriptSecurityManagercanCreateWrappercanCreateInstancecanGetServicecheckLoadURIFromScriptcheckLoadURIWithPrincipalcheckLoadURIStrWithPrincipalinFileURIWhitelistgetSystemPrincipalgetLoadContextCodebasePrincipalgetDocShellCodebasePrincipalgetCodebasePrincipalcreateCodebasePrincipalcreateCodebasePrincipalFromOrigincreateNullPrincipalcheckSameOriginURIgetChannelResultPrincipalgetChannelResultPrincipalIfNotSandboxedgetChannelURIPrincipalisSystemPrincipalactivateDomainPolicydomainPolicyActiveactivateDomainPolicyInternalcloneDomainPolicypolicyAllowsScriptSTANDARDLOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENTALLOW_CHROMEDISALLOW_INHERIT_PRINCIPALDISALLOW_SCRIPT_OR_DATADISALLOW_SCRIPTDONT_REPORT_ERRORSNO_APP_IDUNKNOWN_APP_IDDEFAULT_USER_CONTEXT_ID22p6S`
f`y`F`````
,`F`n``
``
`(
`


1>YqnsIFeedElementBaseattributesattributesbaseURIbaseURI2`@`@nsIDOMGeoPositionCallbackhandleEvent2nsIIOService2manageOfflineStatusmanageOfflineStatusnewChannelFromURIWithProxyFlags2newChannelFromURIWithProxyFlagsC`
@W
k	``imgIContainerDebugframesNotified2`mozIStorageAsyncStatementnsIJSRAIIHelperdestruct2gnsIServiceWorkerUnregisterCallbackunregisterSucceededunregisterFailed2
nsIPaymentCanMakeActionResponseresultinitq`

mozIDOMWindowProxy2nsIInputStreamcloseavailablereadreadSegmentsisNonBlocking2RX`b`g`t`
nsIHandlerServiceenumeratefillHandlerInfostoreexistsremovegetTypeFromExtension2`KBBB`
BnsIWebBrowserChrome3onBeforeLinkTraversalshouldLoadURIshouldLoadURIInThisProcessreloadInFreshProcessstartPrerenderingDocumentshouldSwitchToPrerenderedDocument
F
`
`
F`
`
nsIFinishReportingCallbackcallback22nsIHttpAuthManagergetAuthIdentitysetAuthIdentityclearAll2

mozISandboxSettingseffectiveContentSandboxLevel2w`nsILoadContextInfoisPrivateisAnonymousoriginAttributesbinaryOriginAttributesPtrNO_APP_IDUNKNOWN_APP_ID2`
`
`(nsIWritableVariantwritablewritablesetAsInt8setAsInt16setAsInt32setAsInt64setAsUint8setAsUint16setAsUint32setAsUint64setAsFloatsetAsDoublesetAsBoolsetAsCharsetAsWCharsetAsIDsetAsAStringsetAsDOMStringsetAsACStringsetAsAUTF8StringsetAsStringsetAsWStringsetAsISupportssetAsInterfacesetAsArraysetAsStringWithSizesetAsWStringWithSizesetAsVoidsetAsEmptysetAsEmptyArraysetFromVariantd`
@m
v	
*8IUb2qnsIAccessibleRelationrelationTypetargetsCountgetTargetgetTargetsRELATION_LABELLED_BYRELATION_LABEL_FORRELATION_DESCRIBED_BYRELATION_DESCRIPTION_FORRELATION_NODE_CHILD_OFRELATION_NODE_PARENT_OFRELATION_CONTROLLED_BYRELATION_CONTROLLER_FORRELATION_FLOWS_TORELATION_FLOWS_FROMRELATION_MEMBER_OFRELATION_SUBWINDOW_OFRELATION_EMBEDSRELATION_EMBEDDED_BYRELATION_POPUP_FORRELATION_PARENT_WINDOW_OFRELATION_DEFAULT_BUTTONRELATION_CONTAINING_DOCUMENTRELATION_CONTAINING_TAB_PANERELATION_CONTAINING_WINDOWRELATION_CONTAINING_APPLICATIONRELATION_DETAILSRELATION_DETAILS_FORRELATION_ERRORMSGRELATION_ERRORMSG_FOR2J`W`d`n`y.@	T
g}
!<\mnsIPrefBranchrootgetPrefTypegetBoolPrefgetBoolPrefXPCOMsetBoolPrefgetFloatPrefgetFloatPrefXPCOMgetCharPrefgetCharPrefXPCOMsetCharPrefgetStringPrefsetStringPrefgetIntPrefgetIntPrefXPCOMsetIntPrefgetComplexValuesetComplexValueclearUserPreflockPrefprefHasUserValueprefIsLockedunlockPrefdeleteBranchgetChildListresetBranchaddObserverremoveObserverPREF_INVALIDPREF_STRINGPREF_INTPREF_BOOL2``
`
`

``'`3`DP^l`w``2`
`
D`x
$x3@ L@UnsICertTreeloadCertsloadCertsFromCachegetCertgetTreeItemdeleteEntryObject`		
`
`C
nsIPresentationControlServicestartServerconnectisCompatibleServercloseportversionididcertFingerprintcertFingerprintlistenerlistener2


`
`


`
`
@

@

`b@
bnsINetworkInfoServicelistNetworkAddressesgetHostname2xnsIContentViewerFileprintableprint2`

ڀnsIExternalURLHandlerServicegetURLHandlerInfoFromOS2F@
`BnsIPKCS11deleteModuleaddModule2nsIBrowserElementAPIdestroyFrameScriptssetFrameLoadersendMouseEventsendTouchEventgoBackgoForwardreloadstopdownloadpurgeHistorygetScreenshotzoomgetCanGoBackgetCanGoForwardgetContentDimensionsfindAllfindNextclearMatchaddNextPaintListenerremoveNextPaintListenerexecuteScriptgetWebManifestFIND_CASE_SENSITIVEFIND_CASE_INSENSITIVEFIND_FORWARDFIND_BACKWARD2




 

'
,`	
5`	
B`	
P
U`	
b`	
r`	



5
5
`	
`	
$nsIU2FTokenisCompatibleVersionisRegisteredregistersign2`
`
@@@@nsIFeedContainerididfieldsfieldstitletitlelinklinklinkslinkscategoriescategoriesrightsrightsauthorsauthorscontributorscontributorsupdatedupdatednormalize>@AD`E@KER`@X^`@ch`@nt`@`@`@`@@nsISearchSubmissionpostDatauri2``nsIDirectoryServiceinitregisterProviderunregisterProvider2',=nsIXULTreeBuilderObservercanDroponDroponToggleOpenStateonCycleHeaderonCycleCellonSelectionChangedonPerformActiononPerformActionOnRowonPerformActionOnCellDROP_BEFOREDROP_ONDROP_AFTER2	
`

&.nsIThreadPRThreadCanInvokeJSCanInvokeJSshutdownhasPendingEventsprocessNextEventasyncShutdownregisterIdlePeriodidleDispatcheventTargetEventTargetSerialEventTargetN``
H
`

`
#1DQ`V(](insICompressConvStatsdecodedDataLength2`nsIDOMGlobalPropertyInitializerinit2N`nsIAccessibleStateChangeEventstateisExtraStateisEnabled``
`
nsITransactiondoTransactionundoTransactionredoTransactionisTransientmerge2`
`
nsITransactionListenerwillDodidDowillUndodidUndowillRedodidRedowillBeginBatchdidBeginBatchwillEndBatchdidEndBatchwillMergedidMerge2e`
lr`
{`
`
`
`

nsICaptivePortalCallbackpreparecomplete2
nsIURIClassifierclassifyclassifyLocalWithTablesasyncClassifyLocalWithTablesclassifyLocal2
V
`
`+HnsIAsyncStreamCopierinitasyncCopy䀒pV



2nsIClipboardOwnerLosingOwnership2.nsINavHistoryBatchCallbackrunBatched2P2nsIRDFPropagatableDataSourcepropagateChangespropagateChanges2`
@
nsISAXXMLReaderbaseURIbaseURIcontentHandlercontentHandlerdtdHandlerdtdHandlererrorHandlererrorHandlerdeclarationHandlerdeclarationHandlerlexicalHandlerlexicalHandlersetFeaturegetFeaturesetPropertygetPropertyparseFromStringparseFromStreamparseAsyncj`@`@`@`@)6`@I\`@kz
`
2`
䀐nsIMIMEServicegetFromTypeAndExtensiongetTypeFromExtensiongetTypeFromURIgetTypeFromFilegetPrimaryExtension2`1nsIGlobalProcessScriptLoaderinitialProcessData`nsIHttpUpgradeListeneronTransportAvailable2nnsITextServicesFilterskip2`
nsIXPCComponents_Exception2nsIX509CertDBfindCertByDBKeyfindCertByEmailAddressimportCertificatesimportEmailCertificateimportUserCertificatedeleteCertificatesetCertTrustsetCertTrustFromStringisCertTrustedimportCertsFromFileimportPKCS12FileexportPKCS12FileconstructX509FromBase64constructX509openSignedAppFileAsyncverifySignedDirectoryAsyncaddCertverifyCertAtTimeverifyCertNowasyncVerifyCertAtTimeclearOCSPCacheaddCertFromBase64getCertsgetEnterpriseRootsUNTRUSTEDTRUSTED_SSLTRUSTED_EMAILTRUSTED_OBJSIGNAppXPCShellRootAddonsPublicRootAddonsStageRootPrivilegedPackageRootDeveloperImportedRootFLAG_LOCAL_ONLYFLAG_MUST_BE_EV2h`x`???逸`
1'181I`a`o11_`@@
`@@
````%1?O_p	
nsIDOMXULSelectControlItemElementdisableddisabledcropcropimageimagelabellabelaccessKeyaccessKeycommandcommandvaluevalueselectedcontrol2!`
@!
!@!"@"
"@""@"&"0@"8"@@"F"L`
"U`@nsIAccessibleDocumentURLtitlemimeTypedocTypeDOMDocumentwindowparentDocumentchildDocumentCountvirtualCursorgetChildDocumentAt2
## #&#/#7`#C`#J`#Y`#l`#z`nsIPartialSHistorycountglobalIndexglobalIndexOffsetownerFrameLoadergroupedSHistoryactiveStateactiveStateonAttachGroupedSHistoryhandleSHistoryUpdateonActiveonDeactiveSTATE_INACTIVESTATE_ACTIVESTATE_PRERENDER2$`$`$)`$;`$L`$\`@$h$t$
$$$$$nsIUploadChannelsetUploadStreamuploadStream2%䀸%`nsISecurityUITelemetryWARNING_ADDON_ASKING_PREVENTEDWARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGHWARNING_CONFIRM_ADDON_INSTALLWARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGHWARNING_CONFIRM_POST_TO_INSECURE_FROM_SECUREWARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE_CLICK_THROUGHWARNING_BAD_CERT_ADD_EXCEPTION_BASEWARNING_BAD_CERT_ADD_EXCEPTION_FLAG_UNTRUSTEDWARNING_BAD_CERT_ADD_EXCEPTION_FLAG_DOMAINWARNING_BAD_CERT_ADD_EXCEPTION_FLAG_TIMEWARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_BASEWARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTEDWARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_DOMAINWARNING_BAD_CERT_CONFIRM_ADD_EXCEPTION_FLAG_TIMEWARNING_GEOLOCATION_REQUESTWARNING_GEOLOCATION_REQUEST_SHARE_LOCATIONWARNING_GEOLOCATION_REQUEST_ALWAYS_SHAREWARNING_GEOLOCATION_REQUEST_NEVER_SHAREWARNING_MALWARE_PAGE_TOPWARNING_MALWARE_PAGE_TOP_WHY_BLOCKEDWARNING_MALWARE_PAGE_TOP_GET_ME_OUT_OF_HEREWARNING_MALWARE_PAGE_TOP_IGNORE_WARNINGWARNING_PHISHING_PAGE_TOPWARNING_PHISHING_PAGE_TOP_WHY_BLOCKEDWARNING_PHISHING_PAGE_TOP_GET_ME_OUT_OF_HEREWARNING_PHISHING_PAGE_TOP_IGNORE_WARNINGWARNING_MALWARE_PAGE_FRAMEWARNING_MALWARE_PAGE_FRAME_WHY_BLOCKEDWARNING_MALWARE_PAGE_FRAME_GET_ME_OUT_OF_HEREWARNING_MALWARE_PAGE_FRAME_IGNORE_WARNINGWARNING_PHISHING_PAGE_FRAMEWARNING_PHISHING_PAGE_FRAME_WHY_BLOCKEDWARNING_PHISHING_PAGE_FRAME_GET_ME_OUT_OF_HEREWARNING_PHISHING_PAGE_FRAME_IGNORE_WARNINGWARNING_BAD_CERT_TOPWARNING_BAD_CERT_TOP_STSWARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTIONWARNING_BAD_CERT_TOP_CLICK_VIEW_CERTWARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTIONWARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HEREWARNING_BAD_CERT_TOP_UNDERSTAND_RISKSWARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASEWARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTEDWARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAINWARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIMEWARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASEWARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTEDWARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAINWARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIMEWARNING_UNWANTED_PAGE_TOPWARNING_UNWANTED_PAGE_TOP_WHY_BLOCKEDWARNING_UNWANTED_PAGE_TOP_GET_ME_OUT_OF_HEREWARNING_UNWANTED_PAGE_TOP_IGNORE_WARNINGWARNING_UNWANTED_PAGE_FRAMEWARNING_UNWANTED_PAGE_FRAME_WHY_BLOCKEDWARNING_UNWANTED_PAGE_FRAME_GET_ME_OUT_OF_HEREWARNING_UNWANTED_PAGE_FRAME_IGNORE_WARNING29%&&1&O&{	&
&''5'`'&''((O.(k/(0(1(4)5)%6)Q7)y8)9):);*<**=*Q>*?*@*A*B+C+GD+\E+uF+G+H+I,J,>L,f,,,T-$-^--\-].
^.7_.``.|a.b.cnsISynthVoiceRegistryaddVoiceremoveVoicenotifyVoicesChangedsetDefaultVoicevoiceCountgetVoiceisDefaultVoiceisLocalVoicegetVoiceLanggetVoiceName2
1<

1%<111E
1U`1`1i`
1x`
11nsIPluginTagdescriptionfilenamefullpathversionnameniceNameblocklistedisEnabledStateLockedactiveblocklistStatedisabledclicktoplayloadedenabledStateenabledStatelastModifiedTimegetMimeTypesgetMimeDescriptionsgetExtensionsSTATE_DISABLEDSTATE_CLICKTOPLAYSTATE_ENABLED2212=2F2O2W2\2e`
2q`
2`
2`2`
2`
2`
2`@22`2D`2D`3D`33!33nsINativeFileWatcherErrorCallbackcomplete24RnsILoginManagerStorageinitializeterminateaddLoginremoveLoginmodifyLoginremoveAllLoginsgetAllLoginssearchLoginsfindLoginscountLoginsuiBusyisLoggedIn24`4`4`44244D`4@`4@`4`4`
5`
nsIStreamTransportServicecreateInputTransportInputAvailablecreateOutputTransport25
`5䀒5p
`nsIExtendedExpatSinkhandleStartDTDhandleStartNamespaceDeclhandleEndNamespaceDeclhandleNotationDeclhandleUnparsedEntityDecl:6j6y666nsIPrinterEnumeratordefaultPrinterNameinitPrintSettingsFromPrinterprinterNameList273`7Fڀ7c`ՀnsICommandLineValidatorvalidate27nsITreeContentViewgetItemAtIndexgetIndexOfItem27`7`nsIEditorSpellCheckCallbackeditorSpellCheckDone28BmozIStorageStatementcloneparameterCountgetParameterNamegetParameterIndexcolumnCountgetColumnNamegetColumnIndexresetexecuteexecuteStepnumEntriesgetTypeOfIndexgetInt32getInt64getDoublegetUTF8StringgetStringgetBlobgetBlobAsStringgetBlobAsUTF8StringgetIsNullgetSharedUTF8StringgetSharedStringgetSharedBlobVALUE_TYPE_NULLVALUE_TYPE_INTEGERVALUE_TYPE_FLOATVALUE_TYPE_TEXTVALUE_TYPE_BLOB8{`8`88`8`88`888`
8`9`9`9`9"`	9,9:9D@@9L9\9p`
9z@p9@p9@p99999nsISafeOutputStreamfinish2;cnsIContentPermissionRequestCallbacknotifyVisibility2;
nsIHangReporthangTypescriptBrowserscriptFileNamepluginNameuserCanceledterminateScriptterminatePluginbeginStartingDebuggerendStartingDebuggerisReportForBrowserSLOW_SCRIPTPLUGIN_HANG2
;`;`;;;<<<+<A<U`
<h<tnsINativeAppSupportstartenablestopquitonLastWindowClosingReOpen2=
`
==`
==$=8nsIAccessiblePivotObserveronPivotChanged2=
nsIFormAutoCompleteObserveronSearchCompletion2=nsICacheEntrykeypersistentfetchCountlastFetchedlastModifiedexpirationTimesetExpirationTimeonStartTimeonStopTimesetNetworkTimesforceValidForisForcedValidopenInputStreamopenOutputStreampredictedDataSizepredictedDataSizesecurityInfosecurityInfostorageDataSizeasyncDoomgetMetaDataElementsetMetaDataElementvisitMetaDatametaDataReadysetValiddiskStorageSizeInKBrecreatedataSizealtDataSizeopenAlternativeOutputStreamopenAlternativeInputStreamloadContextInfoclosemarkValidmaybeMarkValidhasWriteAccessNO_EXPIRATION_TIME2$>>`
>"`>-`>9`>F`>U>g`>s`>~>>`
>`>`p>`@>>`2@>2?	`?,?#`?6?I"?W?e?n`?
`#?`?`?`p?`?`???@
`
@nsIProcessinitkillrunrunAsyncrunwrunwAsyncstartHiddenstartHiddenpidexitValueisRunning2A1AA
Ax
A
Ax
A`
@A
B`B	`B`
nsIAuthPromptAdapterFactorycreateAdapter2BN`>nsIUDPSocketInternalcallListenerOpenedcallListenerConnectedcallListenerClosedcallListenerReceivedDatacallListenerError2CCC5CHCansIDisplayInfoidconnected2C`C`
nsIBufferedInputStreaminitdataDD`mozIPlacesAutoCompleteregisterOpenPageunregisterOpenPagepopulatePreloadedSiteStorageMATCH_ANYWHEREMATCH_BOUNDARY_ANYWHEREMATCH_BOUNDARYMATCH_BEGINNINGMATCH_ANYWHERE_UNMODIFIEDMATCH_BEGINNING_CASE_SENSITIVEBEHAVIOR_HISTORYBEHAVIOR_BOOKMARKBEHAVIOR_TAGBEHAVIOR_TITLEBEHAVIOR_URLBEHAVIOR_TYPEDBEHAVIOR_JAVASCRIPTBEHAVIOR_OPENPAGEBEHAVIOR_RESTRICTBEHAVIOR_SEARCHES2DCDTDgDDDDDDEEE&E3EBEO E^@ErEEnsISearchEnginegetSubmissionaddParamsupportsResponseTypegetIconURLBySizegetIconsspeculativeConnectaliasaliasdescriptionhiddenhiddeniconURInamesearchFormidentifiergetResultDomain2Fu`FF`
FF`FF@FFF`
@F
F`FGGGnsIProxiedChannelproxyInfo2G`7nsIPropertyBag2getPropertyAsInt32getPropertyAsUint32getPropertyAsInt64getPropertyAsUint64getPropertyAsDoublegetPropertyAsAStringgetPropertyAsACStringgetPropertyAsAUTF8StringgetPropertyAsBoolgetPropertyAsInterfacegethasKeyH#`H6`HJ`H]`Hq`	HHHH`
H`H`H`
nsIURIWithPrincipalprincipalprincipalUri2I`I`nsINavHistoryQueryResultNodegetQueriesqueryOptionsfolderItemIdtargetFolderGuidwJD`~J`J`J%nsIJSIDnamenumbervalidequalstoStringinitializegetID2Jv`J{`J`
J/`
J`J JnsIWebNavigationInfoisTypeSupportedUNSUPPORTEDIMAGEPLUGINOTHER2K
i`KK)K/K6nsIStreamListenerTeeinitinitAsyncjKjpKjVpnsIPaymentCompleteActionResponsecompleteStatusinitisCompletedqK`KL`
mozIStorageRowgetResultByIndexgetResultByNameiLE`LV`nsIMemoryReporterCallbackcallback2L2nsIScriptErrorerrorMessagesourceNamesourceLinelineNumbercolumnNumberflagscategoryouterWindowIDinnerWindowIDisFromPrivateWindowstackstackerrorMessageNameerrorMessageNamenotesinitinitWithWindowIDerrorFlagwarningFlagexceptionFlagstrictFlaginfoFlaggLLLL`M`M`M`M$`M2`M@`
MT`@MZM`@MqM`MMMMMMMnsIAnnotationObserveronPageAnnotationSetonItemAnnotationSetonPageAnnotationRemovedonItemAnnotationRemoved2NNOO&nsIProxyInfohostporttypeflagsresolveFlagsusernamepasswordfailoverTimeoutfailoverProxyfailoverProxyTRANSPARENT_PROXY_RESOLVES_HOST2
OO`OO`O`OOO`O`7@O7OnsIWindowWatcheropenWindowregisterNotificationunregisterNotificationgetWindowEnumeratorgetNewPromptergetNewAuthPromptersetWindowCreatorhasWindowCreatorgetChromeForWindowgetWindowByNameactiveWindowactiveWindow2P【2`PxPxP`KP`rP`NP7Q`
Q `ȀQ3`QC`@QPnsIGeolocationUpdateupdatenotifyError2R'R.nsIJARURIJARFileJAREntryJAREntrycloneWithJARFileRa`Ri@RrR{`:nsIBufferedOutputStreaminitdatapRpR`pnsIDNSServiceDiscoverystartDiscoveryregisterServiceresolveService2S6`rS,L`rS<LnsIDOMXULDescriptionElementdisableddisabledcropcropvaluevalue2S`
@S
S`
@S
S@SnsIAuthPrompt2promptAuthasyncPromptAuthLEVEL_NONELEVEL_PW_ENCRYPTEDLEVEL_SECURE2Ts`
T)耒2s`rT9TDTWnsINetAddrfamilyaddressportflowscopeisV4MappedgetNetAddrFAMILY_INETFAMILY_INET6FAMILY_LOCAL2T`TT`T`T`T`
T`TUUnsIAccessibleTableCelltablecolumnIndexrowIndexcolumnExtentrowExtentcolumnHeaderCellsrowHeaderCellsisSelected2U`.U`U`U`U`U`U`U`
nsIRequestContextIDblockingTransactionCountaddBlockingTransactionremoveBlockingTransactionspdyPushCachespdyPushCacheuserAgentOverrideuserAgentOverride2Vj`Vm`VV`V`HVVHVmozIOSPreferencesgetSystemLocalesgetRegionalPrefsLocalessystemLocalegetDateTimePatterndateTimeFormatStyleNonedateTimeFormatStyleShortdateTimeFormatStyleMediumdateTimeFormatStyleLongdateTimeFormatStyleFull2W^D`WoD`WWWWWWX
nsIWebContentHandlerRegistrarregisterContentHandlerregisterProtocolHandler2X2X2nsIProtocolHandlerWithDynamicFlagsgetFlagsForURI2Y)`nsPIExternalAppLauncherdeleteTemporaryFileOnExitdeleteTemporaryPrivateFileWhenPossible2Ye1Y1nsIAppStartupcreateHiddenWindowdestroyHiddenWindowrunenterLastWindowClosingSurvivalAreaexitLastWindowClosingSurvivalAreaautomaticSafeModeNecessaryrestartInSafeModecreateInstanceWithProfiletrackStartupCrashBegintrackStartupCrashEndquitshuttingDownstartingUpdoneStartingUprestartingwasRestartedgetStartupInfointerruptedinterruptedeConsiderQuiteAttemptQuiteForceQuiteRestarteRestarti386eRestartx86_64eRestartNotSameProfile2YYYYZ!ZC`
Z^ZpZ`
ZZZ`
Z`
ZZ`
Z`
Z`[	`
@[
[![/[<[G[P []@[lnsIPopupWindowManagertestPermissionALLOW_POPUPDENY_POPUPALLOW_POPUP_WITH_PREJUDICE2\`\\\inIDeepTreeWalkershowAnonymousContentshowAnonymousContentshowSubDocumentsshowSubDocumentsshowDocumentsAsNodesshowDocumentsAsNodesinitrootwhatToShowfiltercurrentNodecurrentNodeparentNodefirstChildlastChildpreviousSiblingnextSiblingpreviousNodenextNode2]`
@])
]>`
@]O
]``
@]u
]]`]`]`]`@]]`]`]`]`]`]`^`nsIDomainSettypeaddremoveclearcontainscontainsSuperDomain2^`____`
_`
nsIIncrementalDownloadinitURIfinalURIdestinationtotalSizecurrentSizestart_1_`_`_`1_`_`_2nsIPresentationSessionTransportcallbackcallbackselfAddressenableDataNotificationsendsendBinaryMsgsendBlobclose2`P`I@`YI`b`?`n````nsIDNSServiceInfohosthostaddressaddressportportserviceNameserviceNameserviceTypeserviceTypedomainNamedomainNameattributesattributes2a@aa @a(a0`@a5a:@aFaR@a^aj@aua`,@a,nsIWebSocketFrametimeStampfinBitrsvBit1rsvBit2rsvBit3opCodemaskBitmaskpayloadOPCODE_CONTINUATIONOPCODE_TEXTOPCODE_BINARYOPCODE_CLOSEOPCODE_PINGOPCODE_PONG2	b?`	bI`
bP`
bX`
b``
bh`bo`
bw`b|bbbbb	b
mozIAsyncLivemarksaddLivemarkremoveLivemarkgetLivemarkreloadLivemarks2cu`c`c`c
nsIPresentationSessionTransportBuilderListeneronSessionTransportonErrorsendOffersendAnswersendIceCandidateclose2dKd#d+d5d@dQtxIXPathObjectgetResult2(dnsIPushServicepushTopicsubscriptionChangeTopicsubscriptionModifiedTopicsubscribesubscribeWithKeyunsubscribegetSubscriptionclearForDomain2dddeee.[e:eJnsISHContainerchildCountAddChildRemoveChildGetChildAtReplaceChild2e`eueuf
`ufumozIStorageBindingParamsArraynewBindingParamsaddParamslength2f`ff`nsIUnicharLineInputStreamreadLine2f`
nsIEffectiveTLDServicegetPublicSuffixgetBaseDomaingetPublicSuffixFromHostgetBaseDomainFromHostgetNextSubDomain2gg-g;gSginsITraceableChannelsetNewListener2gj`jnsIPaymentShippingOptionidlabelamountselectedselected2hhh!`Nh(`
@h1
nsIUrlClassifierCacheInfotableentries2hh`nsIContentFrameMessageManagercontentdocShelltabEventTargeth`h`Fh`VnsIWebRequestListenerinit2i9jVnsIJumpListSeparatornsIDroppedLinkItemurlnametype2iiimozIExtensionProcessScriptpreloadContentScriptloadContentScriptinitExtensionDocument2i2i2i2nsIInputChannelThrottleQueueinitavailablerecordReadbytesProcessedwrapStream2j_jd`jnjy`j`nnsIGZFileWriterinitinitANSIFileDescwritefinish2j1jjknsIUserInfofullnameemailAddressusernamedomain2kD`kM`kZ`kc`nsIObjectInputStreamreadObjectreadIDgetBufferputBufferk
`2 k@ k k
nsICacheStorageVisitoronCacheStorageInfoonCacheEntryInfoonCacheEntryVisitCompleted2l"1l5
lFnsIDOMCSSMediaRulemedial`nsIContentProcessMessageManagerinitialProcessDatal`nsIFakePluginTaghandlerURIsandboxScriptidm`m*m8`nsIPropertynamevalue2mnms`nsINestedURIinnerURIinnermostURI2m`m`nsIOfflineCacheUpdatestatuspartialisUpgradeupdateDomainmanifestURIsucceededinitinitPartialinitForUpdateCheckaddDynamicURIscheduleaddObserverremoveObservercancelbyteProgress2m`m`
m`
nn`n `
n*1n/n;xnNn\ne
nqnn`mozIStorageValueArraynumEntriesgetTypeOfIndexgetInt32getInt64getDoublegetUTF8StringgetStringgetBlobgetBlobAsStringgetBlobAsUTF8StringgetIsNullgetSharedUTF8StringgetSharedStringgetSharedBlobVALUE_TYPE_NULLVALUE_TYPE_INTEGERVALUE_TYPE_FLOATVALUE_TYPE_TEXTVALUE_TYPE_BLOB2ou`o`o`o`o`	ooo@@ooo`
o@pp
@pp@pp+p;pNp_ponsPILoadGroupInternalOnEndPageLoad2qznsIHTTPIndexBaseURLDataSourceencodingencoding2q`q`q`@qnsIWebPageDescriptorloadPagecurrentDescriptorDISPLAY_AS_SOURCEDISPLAY_NORMAL2r2r`2r.r@nsIXMLHttpRequestchannelresponseXMLresponseTextresponseTyperesponseTyperesponsestatusstatusTextabortgetAllResponseHeadersgetResponseHeaderopensendsetRequestHeadertimeouttimeoutreadyStateoverrideMimeTypemozBackgroundRequestmozBackgroundRequestwithCredentialswithCredentialsinituploadonreadystatechangeonreadystatechangemozAnonmozSystemUNSENTOPENEDHEADERS_RECEIVEDLOADINGDONE2r`r`rr@rr`r`rrrss
ss"s3`@s;sC`sNs_`
@st
s`
@s
ss`-s`Bss`
s`
sssttnsIApplicationCacheChannelloadedFromApplicationCacheinheritApplicationCacheinheritApplicationCachechooseApplicationCachechooseApplicationCachemarkOfflineCacheEntryAsForeignapplicationCacheForWriteapplicationCacheForWriteu`
u`
@u
u`
@u
vv.`P@vGPnsITransportProvidersetListenergetIPCChild2v	v`nsIHttpAuthenticableChannelisSSLproxyMethodIsConnectcancelloadFlagsURIloadGroupnotificationCallbacksrequestMethodserverResponseHeaderproxyChallengesWWWChallengessetProxyCredentialssetWWWCredentialsonAuthAvailableonAuthCancelledcloseStickyConnectionconnectionRestartable+w`
w%`
w:wA`wK`wO`wY`?wow}wwwwww
wx
nsICycleCollectorListenerallTraceswantAllTracesdisableLogdisableLoglogSinklogSinkwantAfterProcessingwantAfterProcessingprocessNextasLogger2
x`qx`
y`
@y
y`h@y%hy-`
@yA
yU`
ya`nsIPresentationTransportBuilderConstructorcreateTransportBuilder2z
`3nsIPersistentPropertiesloadsaveenumerategetStringPropertysetStringPropertyzNzSpzX`KzbztnsIClassifiedChannelsetMatchedInfomatchedListmatchedProvidermatchedPrefix2zzz{
nsINetworkInterceptControllershouldPrepareForInterceptchannelIntercepted2{l
`
{nsIExternalProtocolServiceexternalProtocolHandlerExistsisExposedProtocolgetProtocolHandlerInfogetProtocolHandlerInfoFromOSsetProtocolHandlerDefaultsloadUrlloadURIgetApplicationDescription2{`
{`
|`B|@
`B|;B
|V|^?|fScheduledGCCallbackcallback2}nsISharingHandlerAppshare}4nsISecureBrowserUIinitsetDocShellstate2}`}eF}q`nsICacheInfoChannelcacheTokenFetchCountcacheTokenExpirationTimecacheTokenCachedCharsetcacheTokenCachedCharsetisFromCachecacheKeycacheKeyallowStaleCacheContentallowStaleCacheContentpreferAlternativeDataTypealternativeDataTypeopenAlternativeOutputStream2}`}`}@}~`
~`2@~'2~0`
@~G
~^~x~`pnsIOutputIteratorputElementstepForward2A2LnsIModulegetClassObjectregisterSelfunregisterSelfcanUnload2}a`a1a1a`
nsIIdentityKeyPairkeyTypehexRSAPublicKeyExponenthexRSAPublicKeyModulushexDSAPrimehexDSASubPrimehexDSAGeneratorhexDSAPublicValuesign23JVeunsIPaymentDetailsidtotalItemdisplayItemsshippingOptionsmodifierserrorupdate2`̀``#`-3~nsIServiceWorkerManagerregisterunregistergetRegistrationsgetRegistrationgetReadyPromiseremoveReadyPromisegetRegistrationByPrincipalMaybeStartControllingMaybeStopControllingGetInstallingGetWaitingGetActiveGetDocumentControllerremoveAndPropagategetScopeForUrlgetAllRegistrationspropagateSoftUpdatepropagateUnregistersendNotificationClickEventsendNotificationCloseEventsendPushEventsendPushSubscriptionChangeEventaddListenerremoveListenershouldReportToWindow2`2ု`2`2`2` 
 2
G/`2U/`2`/`2j/`2`ု"BHNH]〸`
nsIToolkitProfilerootDirlocalDirnamenameremovelock2.`16`1?@DI
P@\`nsIPaymentDetailsModifiersupportedMethodstotaladditionalDisplayItemsdata2`̀``nsIUpdateProcessorprocessUpdate25nsIDOMXULCheckboxElementcheckedcheckedcheckStatecheckStateautoCheckautoCheckCHECKSTATE_UNCHECKEDCHECKSTATE_CHECKEDCHECKSTATE_MIXEDo`
@w
`@`
@
nsIWebBrowserPersistDocumentisPrivatedocumentURIbaseURIcontentTypecharacterSettitlereferrercontentDispositionpostDatacacheKeypersistFlagspersistFlagsreadResourceswriteContent2W`
amu```@pXXnsIWebHandlerAppuriTemplateuriTemplate@xpcIJSWeakReferenceget2`mozIDOMWindow2mozIStorageAggregateFunctiononSteponFinal2=iD`nsIThreadPoolshutdownthreadLimitthreadLimitidleThreadLimitidleThreadLimitidleThreadTimeoutidleThreadTimeoutthreadStackSizethreadStackSizelistenerlistenersetNameVy`@`@`@`@`@nsIServiceWorkerInfoscriptSpeccacheNamestatedebuggerhandlesFetchEventsinstalledTimeactivatedTimeredundantTimeattachDebuggerdetachDebuggerSTATE_INSTALLINGSTATE_INSTALLEDSTATE_ACTIVATINGSTATE_ACTIVATEDSTATE_REDUNDANTSTATE_UNKNOWN2
```
```->N_onsIJumpListLinkuriuriuriTitleuriTitleuriHashcompareHash0`@48@AJR`
nsICacheStorageConsumptionObserveronNetworkCacheDiskConsumption2nsISAXXMLFilterparentparent`@nsIEventListenerServicegetListenerInfoForgetEventTargetChainForhasListenersForaddSystemEventListenerremoveSystemEventListeneraddListenerForAllEventsremoveListenerForAllEventsaddListenerChangeListenerremoveListenerChangeListener2	P8D`c8
D`8z8`
8
8
8


8

mozITXTToHTMLConvscanTXTscanHTMLciteLevelTXTfindURLInPlaintextkEntitieskURLskGlyphSubstitutionkStructPhrasef``@`@@%8nsIPropertiesgetsethasundefinegetKeys2`2`
@`nsIHttpChannelAuthProviderinitprocessAuthenticationaddAuthorizationHeaderscheckForSuperfluousAuthdisconnectr<pA
W
onsIDOMChromeWindowwindowStatebrowserDOMWindowbrowserDOMWindowgetAttentiongetAttentionWithCycleCountsetCursormaximizeminimizerestorenotifyDefaultButtonLoadedmessageManagergetGroupMessageManagerbeginWindowMovesetOpenerForInitialContentBrowsertakeOpenerForInitialContentBrowserSTATE_MAXIMIZEDSTATE_MINIMIZEDSTATE_NORMALSTATE_FULLSCREEN2``F@F6@IRZt```nsIURLParserparseURLparseAuthorityparseUserInfoparseServerInfoparsePathparseFilePathparseFileName2@@@@@@
	@@@@@@@@@@@'@@@7@@@@@@A@@@@@@O@@@@nsISocketTransporthostportoriginAttributesoriginAttributesbinaryGetOriginAttributesbinarySetOriginAttributesnetworkInterfaceIdnetworkInterfaceIdgetPeerAddrgetSelfAddrbindgetScriptablePeerAddrgetScriptableSelfAddrsecurityInfosecurityCallbackssecurityCallbacksisAlivegetTimeoutsetTimeoutsetReuseAddrPortconnectionFlagsconnectionFlagsQoSBitsQoSBitsrecvBufferSizerecvBufferSizesendBufferSizesendBufferSizekeepaliveEnabledkeepaliveEnabledsetKeepaliveValssetFastOpenCallbackTIMEOUT_CONNECTTIMEOUT_READ_WRITESTATUS_RESOLVINGSTATUS_RESOLVEDSTATUS_CONNECTING_TOSTATUS_CONNECTED_TOSTATUS_SENDING_TOSTATUS_WAITING_FORSTATUS_RECEIVING_FROMSTATUS_TLS_HANDSHAKE_STARTINGSTATUS_TLS_HANDSHAKE_ENDEDBYPASS_CACHEANONYMOUS_CONNECTDISABLE_IPV6NO_PERMANENT_STORAGEDISABLE_IPV4DISABLE_RFC1918MITM_OKBE_CONSERVATIVE ``B.?`Ys@```?`?`2`?@?`
`&1
B`@Rb`@jr`@`@`
@
K)K9KNKbKtK
KKK
$ 4@<nsIAccessibleApplicationappNameappVersionplatformNameplatformVersion2goznsIXPCComponents_ID2nsIFeedResultbozobozodocdocuriuriversionversionstylesheetstylesheetheadersheadersregisterExtensionPrefix2
`
@
`@`@	@`@$/`@7?nsIUrlClassifierDBServicelookupgetTablessetHashCompleterclearLastResultsbeginUpdatebeginStreamupdateStreamfinishStreamfinishUpdatecancelUpdateresetDatabasereloadDatabaseclearCache2
.?OKWdq~nsISAXLocatorcolumnNumberlineNumberpublicIdsystemId2N`[`fonsIAddonContentPolicyvalidateAddonCSP2nsIServerSocketinitinitSpecialConnectioninitWithAddressinitWithFilenamecloseasyncListenportgetAddressLoopbackOnlyKeepWhenOffline2
1-3?`D`O\nsIDownloadProgressListenerdocumentdocumentonDownloadStateChangeonStateChangeonProgressChangeonSecurityChange2`@
)7HamIAddonManagermapURIToAddonID2`
nsIScriptLoaderObserverscriptAvailablescriptEvaluated2&
/&
nsISHistoryisPartialcountglobalCountglobalIndexOffsetindexglobalIndexrequestedIndexmaxLengthmaxLengthgetEntryAtIndexrestoreToEntryAtIndexPurgeHistoryaddSHistoryListenerremoveSHistoryListenersetPartialSHistoryListenerSHistoryEnumeratorreloadCurrentEntrygetIndexOfEntryonPartialSHistoryActiveonPartialSHistoryDeactiveonAttachGroupedSHistoryVIEWER_WINDOW2x`
```````@
`u"9T`Kgzu`nsIIdleServiceInternalresetIdleTimeOut4nsILocalFile1nsIHttpActivityDistributoraddObserverremoveObserver<HnsIProfileLockdirectorylocalDirectoryreplacedLockTimeunlock2`1`1`nsIDOMGeoPositionErrorCallbackhandleEvent2nsIPresentationSessionListenernotifyStateChangenotifyMessageSTATE_CONNECTINGSTATE_CONNECTEDSTATE_CLOSEDSTATE_TERMINATED2DV
dunsISyncStreamListenerinputStreamj`nsIProcessScriptLoaderloadProcessScriptremoveDelayedProcessScriptgetDelayedProcessScripts2.
@[`nsIFlavorDataProvidergetFlavorData2.@2@nsIINIParsergetSectionsgetKeysgetString2`?`?nsIDOMXULTextBoxElementinputFieldtextLengthmaxLengthmaxLengthsizesizeselectionStartselectionStartselectionEndselectionEndvaluevaluetypetypeselectsetSelectionRangeL`W`b`@lv`@{`@`@@@mozIPersonalDictionaryloadsavewordListcheckaddWordremoveWordignoreWordendSessionaddCorrectionremoveCorrectiongetCorrection2`Հ`
@@nsICycleCollectorHandlernoteRefCountedObjectnoteGCedObjectnoteEdgedescribeRootdescribeGarbage2
mozIMozIntlgetCalendarInfogetDisplayNamesgetLocaleInfocreatePluralRulescreateDateTimeFormat2V`f`v```nsIWebBrowserFocusactivatedeactivatesetFocusAtFirstElementsetFocusAtLastElementfocusedWindowfocusedWindowfocusedElementfocusedElement22H`@Vd`@snsIContextMenuListener2onShowContextMenuCONTEXT_NONECONTEXT_LINKCONTEXT_IMAGECONTEXT_DOCUMENTCONTEXT_TEXTCONTEXT_INPUTCONTEXT_BACKGROUND_IMAGE2/+<IW nsIDOMXULContainerItemElementparentContainer2`nsIApplicationReputationQuerysourceURIreferrerURIsuggestedFileNamefileSizesha256HashsignatureInforedirects2#`-`9K`T_`m`nsIPresentationDataChannelSessionTransportBuilderbuildDataChannelTransportonOfferonAnsweronIceCandidatenotifyDisconnected3O )8nsIGroupedSHistorycountactiveFrameLoaderappendPartialSHistoryhandleSHistoryUpdategotoIndexcloseInactiveFrameLoaderOwnersaddPrerenderingPartialSHistoryactivatePrerenderingcancelPrerendering2	``
`.`2CnsICloneableInputStreamcloneableclone2`
`nsINavHistoryQueryOptionssortingModesortingModesortingAnnotationsortingAnnotationresultTyperesultTypeexcludeItemsexcludeItemsexcludeQueriesexcludeQueriesexcludeReadOnlyFoldersexcludeReadOnlyFoldersexpandQueriesexpandQueriesincludeHiddenincludeHiddenmaxResultsmaxResultsqueryTypequeryTypeasyncEnabledasyncEnabledcloneSORT_BY_NONESORT_BY_TITLE_ASCENDINGSORT_BY_TITLE_DESCENDINGSORT_BY_DATE_ASCENDINGSORT_BY_DATE_DESCENDINGSORT_BY_URI_ASCENDINGSORT_BY_URI_DESCENDINGSORT_BY_VISITCOUNT_ASCENDINGSORT_BY_VISITCOUNT_DESCENDINGSORT_BY_KEYWORD_ASCENDINGSORT_BY_KEYWORD_DESCENDINGSORT_BY_DATEADDED_ASCENDINGSORT_BY_DATEADDED_DESCENDINGSORT_BY_LASTMODIFIED_ASCENDINGSORT_BY_LASTMODIFIED_DESCENDINGSORT_BY_TAGS_ASCENDINGSORT_BY_TAGS_DESCENDINGSORT_BY_ANNOTATION_ASCENDINGSORT_BY_ANNOTATION_DESCENDINGSORT_BY_FRECENCY_ASCENDINGSORT_BY_FRECENCY_DESCENDINGRESULTS_AS_URIRESULTS_AS_VISITRESULTS_AS_FULL_VISITRESULTS_AS_DATE_QUERYRESULTS_AS_SITE_QUERYRESULTS_AS_DATE_SITE_QUERYRESULTS_AS_TAG_QUERYRESULTS_AS_TAG_CONTENTSQUERY_TYPE_HISTORYQUERY_TYPE_BOOKMARKSQUERY_TYPE_UNIFIED2(`@4@@Rd`@oz`
@
`
@
`
@
`
@
`
@

`@#.`@8B`
@O
\` bo7	Q
l
0Ni/BWnsIXULTemplateRuleFiltermatch2R׀`
nsIVariantdataTypegetAsInt8getAsInt16getAsInt32getAsInt64getAsUint8getAsUint16getAsUint32getAsUint64getAsFloatgetAsDoublegetAsBoolgetAsChargetAsWChargetAsIDgetAsAStringgetAsDOMStringgetAsACStringgetAsAUTF8StringgetAsStringgetAsWStringgetAsISupportsgetAsJSValgetAsInterfacegetAsArraygetAsStringWithSizegetAsWStringWithSize2|```````````	`
`
` @*9GX`d`q`2`@` @@@@@`@`mozIAnnotatedResultguiduriitemIdannotationNameannotationValue2
``)`nsIAccessiblePivotpositionpositionrootmodalRootmodalRootstartOffsetendOffsetsetTextRangemoveNextmovePreviousmoveFirstmoveLastmoveNextByTextmovePreviousByTextmoveToPointaddObserverremoveObserverCHAR_BOUNDARYWORD_BOUNDARYLINE_BOUNDARYATTRIBUTE_RANGE_BOUNDARYREASON_NONEREASON_NEXTREASON_PREVREASON_FIRSTREASON_LASTREASON_TEXTREASON_POINT2`@``@``


`


`

`

`

`

`
"

`
.!:!IWesnsIScreenGetRectGetAvailRectGetRectDisplayPixGetAvailRectDisplayPixpixelDepthcolorDepthcontentsScaleFactordefaultCSSScaleFactordpi2	8@@@@@@@@@M@@@@_@@@@v```	`	`nsIDomainPolicyblacklistsuperBlacklistwhitelistsuperWhitelistdeactivatecloneDomainPolicyapplyClone2C`IM`I\`If`Iu(
(
nsIFileStreaminitDEFER_OPEN21nsIReflowObserverreflowreflowInterruptible2B		I		nsIServerSocketListeneronSocketAcceptedonStopListening2nsIKeyObjectFactorykeyFromString2`߀nsIContentProcessProviderprovideProcessNEW_PROCESS2-`<nsIPrintSettingsServiceglobalPrintSettingsnewPrintSettingsdefaultPrinterNameinitPrintSettingsFromPrinterinitPrintSettingsFromPrefssavePrintSettingsToPrefsSerializeToPrintDataDeserializeToPrintSettings2`ڀ`ڀ`ڀڀ
ڀ
ڀ%)ڀnsISeekableStreamseektellsetEOFNS_SEEK_SETNS_SEEK_CURNS_SEEK_END2`nsIGSettingsServicegetCollectionForSchema2V`nsIInputIteratorgetElementstepForwardisEqualToclone2`22`
`2nsIRedirectResultListeneronRedirectResult2	
nsIXPCComponents_UtilsreportErrorSandboxevalInSandboxgetSandboxAddonIdgetSandboxMetadatasetSandboxMetadataimportisModuleLoadedunloadimportGlobalPropertiesgetWeakReferenceforceGCforceCCfinishCCccSlicegetMaxCCSliceTimeSinceClearclearMaxCCTimeforceShrinkingGCschedulePreciseGCschedulePreciseShrinkingGCunlinkGhostWindowsgetJSTestingFunctionscallFunctionWithAsyncStackgetGlobalForObjectisProxyexportFunctioncreateObjectInmakeObjectPropsNormalisDeadWrapperisCrossProcessWrappergetCrossProcessWrapperTagpermitCPOWsInScoperecomputeWrapperssetWantXraysforcePermissiveCOWsforcePrivilegedComponentsForScopegetComponentsForScopedispatchstrictstrictwerrorwerrorstrict_modestrict_modeionionisInAutomationcrashIfNotInAutomationsetGCZealnukeSandboxblockScriptForGlobalunblockScriptForGlobalisXrayWrapperwaiveXraysunwaiveXraysgetClassNamegetDOMClassInfogetIncumbentGlobalgenerateXPCWrappedJSgetWatchdogTimestampgetJSEngineTelemetryValuecloneIntogetWebIDLCallerPrincipalgetObjectPrincipalgetCompartmentLocationsetAddonInterpositionsetAddonCallInterpositionallowCPOWsInAddonnow2EBN`΀V`d`v```
`q`->wPwk~````
```
`
"<Oan‚¤`º`
B
`
B
`
B
`
B
`
%/;Pg`
u`À`Í
`Ú`pê`ý`2````$`7NEd~
Đ`	nsIFormSubmitObservernotifynotifyInvalidSubmit2@
nsIURLdirectorydirectoryfileNamefileNamefileBaseNamefileBaseNamefileExtensionfileExtensiongetCommonBaseSpecgetRelativeSpec
@&@/8@ER@`nȀnsIWebBrowserStreamopenStreamappendToStreamcloseStream2"1nsIFocusManageractiveWindowactiveWindowfocusedWindowfocusedWindowfocusedElementgetLastFocusMethodsetFocusmoveFocusclearFocusgetFocusedElementForWindowmoveCaretToFocuselementIsFocusablewindowRaisedwindowLoweredwindowShownwindowHiddenfireDelayedEventsfocusPluginparentActivatedFLAG_RAISEFLAG_NOSCROLLFLAG_NOSWITCHFRAMEFLAG_NOPARENTFRAMEFLAG_BYMOUSEFLAG_BYKEYFLAG_BYMOVEFOCUSFLAG_SHOWRINGFLAG_BYTOUCHMOVEFOCUS_FORWARDMOVEFOCUS_BACKWARDMOVEFOCUS_FORWARDDOCMOVEFOCUS_BACKWARDDOCMOVEFOCUS_FIRSTMOVEFOCUS_LASTMOVEFOCUS_ROOTMOVEFOCUS_CARETMOVEFOCUS_FIRSTDOCMOVEFOCUS_LASTDOC2y`@Ɇɓ`@ɡɯ`ɾ`〒`
@`
`
.;I
Ubtʀ
ʐʛʩʼ @ %8Mcs˂ˑˡ	˴
nsIKeygenThreadstartKeyGenerationuserCanceled2͎x͡@
nsIToolkitChromeRegistrycheckForOSAccessibilitygetLocalesForPackage`?nsIContentPermissionRequesttypesprincipalwindowelementrequestercancelallow2J`P`Z`a`i`sznsIXPCExceptioninitialize2nsIURILoaderregisterContentListenerunRegisterContentListeneropenURIopenChannelstopIS_CONTENT_PREFERREDDONT_RETARGET22L?T?`j`2eznsIClipboardDragDropHookListaddClipboardDragDropHooksremoveClipboardDragDropHooksgetHookEnumerator2
$A`KnsIUpdateCheckercheckForUpdatesstopCheckingCURRENT_CHECKCURRENT_SESSIONANY_CHECKS2Џ
ПЬкnsIImageDocumentimageIsOverflowingimageIsResizedimageRequestshrinkToFitrestoreImagerestoreImageTotoggleImageSize2`
-`
<`wIUbqnsIProgressEventSinkonProgressonStatus2怒2怒2nsITokenPasswordDialogssetPassword2<?`
nsIServiceWorkerRegistrationInfoListeneronChange2҈nsIHTMLEditoraddDefaultPropertyremoveDefaultPropertyremoveAllDefaultPropertiessetInlinePropertygetInlinePropertygetInlinePropertyWithAttrValueremoveAllInlinePropertiesremoveInlinePropertyincreaseFontSizedecreaseFontSizenodeIsBlockinsertHTMLpasteNoFormattingrebuildDocumentFromSourceinsertHTMLWithContextinsertElementAtSelectionupdateBaseURLselectElementsetCaretAfterElementsetParagraphFormatgetParagraphStategetFontFaceStategetFontColorStategetBackgroundColorStategetHighlightColorStategetListStategetListItemStategetAlignmentgetIndentStatemakeOrChangeListremoveListindentaligngetElementOrParentByTagNamegetSelectedElementgetHeadContentsAsHTMLreplaceHeadContentsWithHTMLcreateElementWithDefaultsinsertLinkAroundSelectionsetBackgroundColorsetBodyAttributegetLinkedObjectsisCSSEnabledisCSSEnabledaddInsertionListenerremoveInsertionListenergetSelectionContainercheckSelectionStateForAnonymousButtonsisAnonymousElementreturnInParagraphCreatesNewParagraphreturnInParagraphCreatesNewParagraphGetActiveEditingHosteLefteCentereRighteJustify24Ү뀹뀹뀹뀹@
@
@
뀹@
@
@
5O뀹duӆ`
Ӓӝӯ

)<@
N@
_@
q@
ԉ@
Ԡ@
@
@
@
ԭ@
@
@
@
Ծ@
@@
@

``2Hd`~՘իռ``
@
`*)Q`
d`
@։
(֮nsIAccessibleHyperLinkstartIndexendIndexvalidanchorCountgetURIgetAnchor2٥`ٰ`ٹ`
ٿ```nsIXPCComponents_Constructor2nsIPrefBranch2nsIBinaryInputStreamsetInputStreamreadBooleanread8read16read32read64readFloatreadDoublereadCStringreadStringreadBytesreadByteArrayreadArrayBuffer
vڅ`
ڑ`ڗ`ڞ`ڥ`ڬ`ڶ`	```nsIPackageKitServiceinstallPackagesPK_INSTALL_PACKAGE_NAMESPK_INSTALL_MIME_TYPESPK_INSTALL_FONTCONFIG_RESOURCESPK_INSTALL_GSTREAMER_RESOURCESPK_INSTALL_METHOD_COUNT2ۭx۽+nsIAddonPolicyServicebaseCSPdefaultCSPgetAddonCSPgetGeneratedBackgroundPageUrladdonHasPermissionaddonMayLoadURIextensionURILoadableByAnyoneextensionURIToAddonId2ܟܧܾܲ`

`
`
nsIFeedProcessorlistenerlistenerparseFromStreamparseFromStringparseAsyncjݲ`@ݻ䀒nsINavHistoryServicedatabaseStatushasHistoryEntriesgetPageTitlemarkPageAsFollowedBookmarkmarkPageAsTypedmarkPageAsFollowedLinkcanAddURIgetNewQuerygetNewQueryOptionsexecuteQueryexecuteQueriesqueryStringToQueriesqueriesToQueryStringaddObserverremoveObservergetObserversrunInBatchModehistoryDisabledclearEmbedVisitsmakeGuidTRANSITION_LINKTRANSITION_TYPEDTRANSITION_BOOKMARKTRANSITION_EMBEDTRANSITION_REDIRECT_PERMANENTTRANSITION_REDIRECT_TEMPORARYTRANSITION_DOWNLOADTRANSITION_FRAMED_LINKTRANSITION_RELOADDATABASE_STATUS_OKDATABASE_STATUS_CREATEDATABASE_STATUS_CORRUPTDATABASE_STATUS_UPGRADEDDATABASE_STATUS_LOCKED2Q```
rޚު`
`~`~`~`@~@@~0
<KD`X2g`
w߈ߑߡ߲'>	PcznsIWebVTTListeneronCueonRegiononParsingError2ms|nsIPKCS11ModulenamelibNamefindSlotByNamelistSlots2``KnsIPerformanceStatstotalUserTimetotalSystemTimetotalCPOWTimeticksgetDurations2/`=`M`[`aD`nsITLSServerConnectionInfosetSecurityObserverserverSocketstatus2΀`3`nsISecurityReporterreportTLSError2.ހnsIRDFXMLSerializerinitaddNameSpace2hm뀯nsIJSXMLHttpRequest2nsITextToSubURIConvertAndEscapeUnEscapeAndConvertunEscapeURIForUIunEscapeNonAsciiURI2nsISAXMutableAttributesaddAttributeclearremoveAttributesetAttributessetAttributesetLocalNamesetQNamesetTypesetURIsetValue
fsynsIHTMLObjectResizerresizedObjectobjectResizingEnabledobjectResizingEnabledshowResizershideResizersrefreshResizersmouseDownmouseUpmouseMoveeTopLefteTopeTopRighteLefteRighteBottomLefteBottomeBottomRight2	n`|`
@
'nsITaskbarPreviewControllerwidthheightthumbnailAspectRatiodrawPreviewdrawThumbnailrequestPreviewrequestThumbnailonCloseonActivateonClick2
```2`
)2`
7FW_`
j=nsIDOMXULLabelElementaccessKeyaccessKeycontrolcontrol=@@#nsIAtomtoStringtoUTF8StringequalsSizeOfIncludingThis2bkx`
(nsIWyciwygChannelwriteToCacheEntrycloseCacheEntrysetSecurityInfosetCharsetAndSourcegetCharsetAndSource2@mozISpellCheckingEnginedictionarydictionarylanguageprovidesPersonalDictionaryprovidesWordUtilsnamecopyrightpersonalDictionarypersonalDictionarygetDictionaryListchecksuggestloadDictionariesFromDiraddDirectoryremoveDirectory2`@``
`
```@@@`
 @@(1@1M1nsIServiceManagergetServicegetServiceByContractIDisServiceInstantiatedisServiceInstantiatedByContractID2$`/`F`
\`
nsIAutoCompleteObserveronSearchResultonUpdateSearchResult2nsIHandlerAppnamenamedetailedDescriptiondetailedDescriptionequalslaunchWithURI20@5:@Nb`
i?mozIStorageAsyncConnectionasyncClosespinningSynchronousCloseasyncClonedatabaseFilecreateAsyncStatementexecuteAsyncexecuteSimpleSQLAsynccreateFunctioncreateAggregateFunctionremoveFunctionsetProgressHandlerremoveProgressHandler2
`1`߀0
`6=
`6SSbzd`d`dnsISearchableInputStreamsearch2
@
@nsIDOMWindowInternalnsIPaymentAbortActionResponseabortStatusinitisSucceededq``
mozIStorageCompletionCallbackcomplete2;2nsIWebBrowserPersistpersistFlagspersistFlagscurrentStateresultprogressListenerprogressListenersaveURIsavePrivacyAwareURIsaveChannelsaveDocumentcancelSavePERSIST_FLAGS_NONEPERSIST_FLAGS_FROM_CACHEPERSIST_FLAGS_BYPASS_CACHEPERSIST_FLAGS_IGNORE_REDIRECTED_DATAPERSIST_FLAGS_IGNORE_IFRAMESPERSIST_FLAGS_NO_CONVERSIONPERSIST_FLAGS_REPLACE_EXISTING_FILESPERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONSPERSIST_FLAGS_FIXUP_ORIGINAL_DOMPERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATIONPERSIST_FLAGS_DONT_FIXUP_LINKSPERSIST_FLAGS_SERIALIZE_OUTPUTPERSIST_FLAGS_DONT_CHANGE_FILENAMESPERSIST_FLAGS_FAIL_ON_BROKEN_LINKSPERSIST_FLAGS_CLEANUP_ON_FAILUREPERSIST_FLAGS_AUTODETECT_APPLY_CONVERSIONPERSIST_FLAGS_APPEND_TO_FILEPERSIST_FLAGS_FORCE_ALLOW_COOKIESPERSIST_STATE_READYPERSIST_STATE_SAVINGPERSIST_STATE_FINISHEDENCODE_FLAGS_SELECTION_ONLYENCODE_FLAGS_FORMATTEDENCODE_FLAGS_RAWENCODE_FLAGS_BODY_ONLYENCODE_FLAGS_PREFORMATTEDENCODE_FLAGS_WRAPENCODE_FLAGS_FORMAT_FLOWEDENCODE_FLAGS_ABSOLUTE_LINKSENCODE_FLAGS_ENCODE_W3C_ENTITIESENCODE_FLAGS_CR_LINEBREAKSENCODE_FLAGS_LF_LINEBREAKSENCODE_FLAGS_NOSCRIPT_CONTENTENCODE_FLAGS_NOFRAMES_CONTENTENCODE_FLAGS_ENCODE_BASIC_ENTITIESENCODE_FLAGS_ENCODE_LATIN1_ENTITIESENCODE_FLAGS_ENCODE_HTML_ENTITIESrn`@{```@2䀐22䀐2
2222%*Ej @:Yx @
'I]r @+Gh @!nsIWebBrowserPersistResourceVisitorvisitResourcevisitDocumentendVisit2snsIAsyncVerifyRedirectCallbackonRedirectVerifyCallback2nsIForwardIteratorgetElementputElementstepForwardisEqualToclone2'`222=I2`
S`2nsIWebVTTParserWrapperloadParserparseflushwatchconvertCueToDOMTreeprocessCues2߀2`22nsITabParentgetChildProcessOffsetuseAsyncPanZoomdocShellIsActivedocShellIsActiveisPrerenderedpreserveLayerssuppressDisplayporttabIdosPidnavigateByKeyhasContentOpenerhasPresentedtransmitPermissionsForPrincipalhasBeforeUnloadstartApzAutoscrollstopApzAutoscroll2X@@n`
~`
@
`


``

`
`
	)`
9LnsICacheListeneronCacheEntryAvailableonCacheEntryDoomed2$:nsIPrintStatusFeedbackshowStatusStringstartMeteorsstopMeteorsshowProgresssetDocShellcloseWindow2FnsIAccessibleSelectableselectedItemsselectedItemCountgetSelectedItemAtisItemSelectedaddItemToSelectionremoveItemFromSelectionselectAllunselectAll2/`=`O`a`
p`
mozIStorageVacuumParticipantexpectedDatabasePageSizedatabaseConnectiononBeginVacuumonEndVacuum2+`D`W`
e
nsISocketProviderServicegetSocketProvider2`ԀnsIURIClassifierCallbackonClassifyComplete2nsICryptoHMACinitupdateupdateFromStreamfinishresetMD5SHA1SHA256SHA384SHA51223߀8?P
W]afmtnsIInputStreamTeesourcesourcesinksinkeventTargeteventTarget`@`p@	p`V@VnsICacheEntryDescriptorsetExpirationTimesetDataSizeopenInputStreamopenOutputStreamcacheElementcacheElementpredictedDataSizepredictedDataSizeaccessGrantedstoragePolicystoragePolicyfilesecurityInfosecurityInfostorageDataSizedoomdoomAndFailPendingRequestsasyncDoommarkValidclosegetMetaDataElementsetMetaDataElementvisitMetaData``p`2@2`@
``@&4`19`2@F2S`ch`nsIIdentityKeyGenCallbackgenerateKeyPairFinished2}nsIAsyncShutdownCompletionCallbackdone2<nsINativeOSFileInternalsServiceread2pnsIHTMLAbsPosEditorselectionContainerAbsolutelyPositionedpositionedElementabsolutePositioningEnabledabsolutePositioningEnabledsnapToGridEnabledsnapToGridEnabledgridSizegridSizeabsolutelyPositionedSelectionContainerabsolutePositionSelectionrelativeChangeZIndexabsolutelyPositionElementsetElementPositiongetElementZIndexsetElementZIndexrelativeChangeElementZIndexshowGrabberOnElementhideGrabberrefreshGrabber2`
``
@
`
@%
7`@@I`p

``
+nsIWeakReferenceQueryReferent2*`nsISupportsWeakReferenceGetWeakReference2e`	nsIHapticFeedbackperformSimpleActionShortPressLongPress2nsISerializablereadwrite2ansINavHistoryResultNodeparentparentResulturitypetitleaccessCounttimeiconindentLevelbookmarkIndexitemIddateAddedlastModifiedtagspageGuidbookmarkGuidvisitIdfromVisitIdvisitTypeRESULT_TYPE_URIRESULT_TYPE_QUERYRESULT_TYPE_FOLDERRESULT_TYPE_SEPARATORRESULT_TYPE_FOLDER_SHORTCUT29`w@`MQ`V\`h`mr`~```````.	nsIAutoCompleteResultsearchStringsearchResultdefaultIndexerrorDescriptionmatchCountgetValueAtgetLabelAtgetCommentAtgetStyleAtgetImageAtgetFinalCompleteValueAtremoveValueAtRESULT_IGNOREDRESULT_FAILURERESULT_NOMATCHRESULT_SUCCESSRESULT_NOMATCH_ONGOINGRESULT_SUCCESS_ONGOING2Vc`p`}`
			%	4	KnsIURIspecspecprePathschemeschemeuserPassuserPassusernameusernamepasswordpasswordhostPorthostPortsetHostAndPorthosthostportportpathpathequalsschemeIscloneresolveasciiSpecasciiHostPortasciiHostoriginCharsetrefrefequalsExceptRefcloneIgnoringRefcloneWithNewRefspecIgnoringRefhasReffilePathfilePathqueryquerydisplayHostdisplayHostPortdisplaySpec2*
 @
%
*
2@
9
@@
I
R@
[
d@
m
v@


@

`@

@

`

`

`




@`
`,`<L`
S@\e@kq}nsIFontEnumeratorEnumerateAllFontsEnumerateFontsHaveFontForgetDefaultFontupdateFontListgetStandardFamilyName2
j@`
|@`
`

`
`

`nsIFilePickerinitappendFiltersappendFilterdefaultStringdefaultStringdefaultExtensiondefaultExtensionfilterIndexfilterIndexdisplayDirectorydisplayDirectorydisplaySpecialDirectorydisplaySpecialDirectoryfilefileURLfilesdomFileOrDirectorydomFileOrDirectoryEnumeratoraddToRecentDocsaddToRecentDocsshowopenmodeokButtonLabelokButtonLabelmodeOpenmodeSavemodeGetFoldermodeOpenMultiplereturnOKreturnCancelreturnReplacefilterAllfilterHTMLfilterTextfilterImagesfilterXMLfilterXULfilterAppsfilterAllowURLsfilterAudiofilterVideo22〹7ER@`n@`@`1@1@`1``K
`2 `K=`
@M
]`btg`l@z
 @)9EnsIMemoryReportercollectReportsKIND_NONHEAPKIND_HEAPKIND_OTHERUNITS_BYTESUNITS_COUNTUNITS_COUNT_CUMULATIVEUNITS_PERCENTAGE242
#-8DPgnsIJARProtocolHandlerJARCache{`8nsIObjectOutputStreamwriteObjectwriteSingleRefObjectwriteCompoundObjectwriteIDgetBufferputBuffer2
$292
M U _
nsIXULChromeRegistryreloadChromegetSelectedLocaleisLocaleRTLrefreshSkinsallowScriptsForPackageallowContentToAccesscanLoadURLRemotelymustLoadURLRemotely
`
	`
 `
5`
H`
nsITXTToHTMLConvsetTitlepreFormatHTMLf
nsIAccessibleTextcaretOffsetcaretOffsetcharacterCountselectionCountgetTextgetTextAfterOffsetgetTextAtOffsetgetTextBeforeOffsetgetCharacterAtOffsetgetTextAttributesdefaultTextAttributesgetCharacterExtentsgetRangeExtentsgetOffsetAtPointgetSelectionBoundssetSelectionBoundsaddSelectionremoveSelectionscrollSubstringToscrollSubstringToPointenclosingRangeselectionRangesvisibleRangesgetRangeByChildgetRangeAtPointTEXT_OFFSET_END_OF_TEXTTEXT_OFFSET_CARETBOUNDARY_CHARBOUNDARY_WORD_STARTBOUNDARY_WORD_ENDBOUNDARY_SENTENCE_STARTBOUNDARY_SENTENCE_ENDBOUNDARY_LINE_STARTBOUNDARY_LINE_END2`@&2`A`PX@@k@@{@@`
@@`s`s@@@@@@@@`@@'4DVm`|````	0FZnsIMenuBuilderopenContaineraddItemForaddSeparatorundoAddSeparatorcloseContainertoJSONStringclick2;I
TarnsIDataSignatureVerifierverifyDataverifySignatureVERIFY_OKVERIFY_ERROR_UNKNOWN_ISSUERVERIFY_ERROR_OTHER2`
@`9nsISecCheckWrapChannelinnerChannel2`nsIHttpEventSinkonRedirect2nsIScriptableBase64EncoderencodeToCStringencodeToString2%nsIWebBrowserChromeFocusfocusNextElementfocusPrevElement2t

nsIFormatConvertergetInputDataFlavorsgetOutputDataFlavorscanConvertconvert2```
2@2@nsIBidirectionalIteratorgetElementputElementstepForwardstepBackwardisEqualToclone2^`2i2t2`
`2nsIRemoteWindowContextopenURI2nsIRDFPurgeableDataSourceMarkSweep22
`
7nsIClientAuthUserDecisionrememberClientAuthCertificaterememberClientAuthCertificate2~`
@
nsIRDFCompositeDataSourceallowNegativeAssertionsallowNegativeAssertionscoalesceDuplicateArcscoalesceDuplicateArcsAddDataSourceRemoveDataSourceGetDataSources`
@
`
@5
KYj`KnsIDebug2isDebugBuildassertionCountisDebuggerAttachedassertionwarningbreakabortrustPanic2`
``
#nsIStringBundleOverridegetStringFromNameenumerateKeysInBundle2`KnsIPresentationControlChannelListeneronOfferonAnsweronIceCandidatenotifyConnectednotifyDisconnectednotifyReconnected2  # , ; K ^nsIQuotaOriginUsageResultusagefileUsagelimit2 ` ` `nsIApplicationCacheNamespaceinititemTypenamespaceSpecdataNAMESPACE_BYPASSNAMESPACE_FALLBACKNAMESPACE_OPPORTUNISTIC2!%!*`!3!A!F!W!jnsIWritablePropertyBagsetPropertydeleteProperty!!nsILocalCertServicegetOrCreateCertremoveCertloginPromptRequired2"52"Eـ"P`
nsISystemProxySettingsmainThreadOnlyPACURIgetProxyForURI2"`
""nsITaggingServicetagURIuntagURIgetURIsForTaggetTagsForURIallTagshasTagsMAX_TAG_LENGTH2###`#*D`#8`#@`
#HdnsIXPCComponents_Classes2nsITransferableinitflavorsTransferableCanExportgetTransferDatagetAnyTransferDataisLargeDataSetflavorsTransferableCanImportsetTransferDataaddDataFlavorremoveDataFlavorconverterconverterisPrivateDataisPrivateDatarequestingPrincipalrequestingPrincipalcontentPolicyTypecontentPolicyTypekFlavorHasDataProvider2##`$@2@$$@2@$7`
$F`$c2$s$$`@$$`
H$
$`H$$`H$%nsIXULStoresetValuehasValuegetValueremoveValuegetIDsEnumeratorgetAttributeEnumerator2&&`
&&&&2`Հ&C`ՀnsIDOMCSSUnknownRule2mozIVisitedStatusCallbackisVisited2&
nsIPerformanceGroupDetailsgroupIdnamewindowIdisSystemprocessIdisContentProcess2'-'5':`'C`
'L`'V`
nsISupportsInterfacePointerdatadatadataIIDdataIIDtoStringG'`2@'2'`@''`nsIApplicationReputationCallbackonComplete2(G
nsIDOMFontFacefromFontGroupfromLanguagePrefsfromSystemFallbacknameCSSFamilyNamerulesrcIndexURIlocalNameformatmetadata2(v`
(`
(`
(((`z(`((((nsIDOMRequestServicecreateRequestcreateCursorfireSuccessfireErrorfireDetailedErrorfireSuccessAsyncfireErrorAsyncfireDone2)t`	)c`)	)	)	2)	)	)nsIAccessibilityServicegetApplicationAccessiblegetAccessibleForgetStringRolegetStringStatesgetStringEventTypegetStringRelationTypegetAccessibleFromCachecreateAccessiblePivotsetLoggingisLogged2
*w`*`**`2***`*`++ `
nsIDOMEventTargetaddEventListeneraddSystemEventListenerremoveEventListenerremoveSystemEventListenerdispatchEventGetTargetForDOMEventGetTargetForEventTargetChainGetEventTargetParentWillHandleEventPostHandleEventDispatchDOMEventGetContextForEventHandlers2+

+

+
,
,`
 ,, ,A,^,s,, ,@%nsIQuotaRequestBaseprincipalresultCode2-x`-`nsIDOMCSSCounterStyleRulenamenamesystemsystemsymbolssymbolsadditiveSymbolsadditiveSymbolsnegativenegativeprefixprefixsuffixsuffixrangerangepadpadspeakAsspeakAsfallbackfallback2-@--@--@--@-.@..@.%.,@.3.:@.@.F@.J.N@.V.^@.gnsIDocShellTreeItemnamenamenameEqualsitemTypeitemTypeItemTypeparentsameTypeParentrootTreeItemsameTypeRootTreeItemfindItemWithNametreeOwnersetTreeOwnerchildCountaddChildremoveChildgetChildAtfindChildWithNamegetDocumentgetWindowtypeChrometypeContenttypeContentWrappertypeChromeWrappertypeAll2/g@/l/q`
/|`@/(//`;/`;/`;/`;/;;
`;/`y/y/`0;0;0`;0"

;;`;(04(0@00J0U0a0t0nsISpeechServicespeakserviceTypeSERVICETYPE_DIRECT_AUDIOSERVICETYPE_INDIRECT_AUDIO211`11nsIZipReaderopenopenInneropenMemoryfileclosetestextractgetEntryhasEntryfindEntriesgetInputStreamgetInputStreamWithSpecgetSigningCertmanifestEntriesCount22U12Z=2d2o`12t2z212`2`
2`?2`2`2`2`nsIRandomAccessIteratorgetElementgetElementAtputElementputElementAtstepForwardstepForwardBystepBackwardstepBackwardByisEqualToclone2
3`23`2323233444$2`
4.`2nsIUTF8StringEnumeratorhasMoregetNext24`
4nsIDOMXULSelectControlElementselectedItemselectedItemselectedIndexselectedIndexvaluevalueappendIteminsertItemAtremoveItemAtitemCountgetIndexOfItemgetItemAtIndex5`
@5
5(`@565D@5J5P`
5[`
5h`
5u`5
`5`
nsIAtomServicegetAtom26I`nsIClearSiteDataCallbackcallback26nsIMIMEHeaderParamgetParametergetParameterHTTPdecodeRFC5987ParamgetParameterInternaldecodeRFC2047HeaderdecodeParameter26
@6
@66@@`6

7
nsIWorkerDebuggerListeneronCloseonErroronMessage2777nsIWritablePropertyBag2setPropertyAsInt32setPropertyAsUint32setPropertyAsInt64setPropertyAsUint64setPropertyAsDoublesetPropertyAsAStringsetPropertyAsACStringsetPropertyAsAUTF8StringsetPropertyAsBoolsetPropertyAsInterface,
788#868J	8^8s88
82nsIBrowserDOMWindowopenURIopenURIInFrameisTabContentWindowcanCloseOPEN_DEFAULTWINDOWOPEN_CURRENTWINDOWOPEN_NEWWINDOWOPEN_NEWTABOPEN_SWITCHTABOPEN_NEWOPEN_EXTERNALOPEN_NO_OPENER29``9h`9w`
9`
99999999nsIConsoleAPIStoragegetEventsrecordEventclearEvents2:`::nsIServiceWorkerManagerListeneronRegisteronUnregister2; ;+nsIPresentationSessionTransportCallbacknotifyTransportReadynotifyTransportClosednotifyData2;;;
nsIContentPrefdomainnamevalue2;;;`mozIDownloadPlatformdownloadDonemapUrlToZoneZONE_MY_COMPUTERZONE_INTRANETZONE_TRUSTEDZONE_INTERNETZONE_RESTRICTED2<71
<D`<Q<b<p<}<mozILivemarkstatusreloadgetNodesForContainerregisterForUpdatesunregisterForUpdatesSTATUS_READYSTATUS_LOADINGSTATUS_FAILEDb=`=
=`=#=6=K=X=gnsIControllerCommandGroupaddCommandToGroupremoveCommandFromGroupisCommandInGroupgetGroupsEnumeratorgetEnumeratorForGroup2==>`
>`K>1`KnsISerialEventTargetVnsIUrlClassifierUpdateObserverupdateUrlRequestedstreamFinishedupdateErrorupdateSuccess2>>>>nsIAccessibleTableChangeEventrowOrColIndexRowsOrColsCount?V`?d`nsIDOMXULRelatedElementgetRelatedElement2?`nsISearchInstallCallbackonSuccessonErrorERROR_UNKNOWN_FAILUREERROR_DUPLICATE_ENGINE2?*??@mozIStorageFunctiononFunctionCall2@ki`nsISiteHSTSState9nsIIncrementalStreamLoaderinitnumBytesReadrequestj@W@`@`nsIEventTargetisOnCurrentThreadInfallibleisOnCurrentThreaddispatchFromCdispatchdelayedDispatchDISPATCH_NORMALDISPATCH_SYNCDISPATCH_AT_END2(A
A2`
ADARA[AkA{AnsIMultiplexInputStreamcountappendStreaminsertStreamremoveStreamgetStreamB`BBB+B8`nsIWebBrowserPersistWriteCompletiononFinish2BpnsIQuotaCallbackonComplete2BnsIDOMCSSGroupingRulecssRulesinsertRuledeleteRule2C`sC`C&nsIParserUtilssanitizeconvertToPlainTextparseFragmentSanitizerAllowCommentsSanitizerAllowStyleSanitizerCidEmbedsOnlySanitizerDropNonCSSPresentationSanitizerDropFormsSanitizerDropMedia2CkCtC
`CCCCCD
 nsIEditorSpellCheckcanSpellCheckInitSpellCheckerGetNextMisspelledWordGetSuggestedWordCheckCurrentWordReplaceWordIgnoreWordAllOccurrencesGetPersonalDictionaryGetPersonalDictionaryWordAddWordToDictionaryRemoveWordFromDictionaryGetDictionaryListGetCurrentDictionarySetCurrentDictionaryUninitSpellCheckersetFilterCheckCurrentWordNoSuggestUpdateCurrentDictionary2D`
Da
D`D`D`
D
E	E"E8`EREfE@@EEEE
E`
EnsIRDFDelegateFactoryCreateDelegate2F`nsITokenDialogsChooseTokendisplayProtectedAuth2G)?@
G5?nsIDOMStorageManagerprecacheStoragecreateStoragegetStoragecloneStoragecheckStoragegetLocalStorageForPrincipal2G`G
`G
`GG`
G
`nsIPushQuotaManagernotificationForOriginShownnotificationForOriginClosed2HwHnsIDOMCSSKeyframeRulekeyTextkeyTextstyle2H@HH`nsIDOMCSSFontFeatureValuesRulefontFamilyfontFamilyvalueTextvalueText2I;@IFIQ@I[IJSDebuggeraddClass2ImozIStorageProgressHandleronProgress2I`
nsIThreadInternalobserverobserveraddObserverremoveObserverpushEventQueuepopEventQueueJ`+@J+J+J%+J4`VJCVnsIBFCacheEntryRemoveFromBFCacheSyncRemoveFromBFCacheAsyncID2JJJ`nsISensitiveInfoHiddenURIgetSensitiveInfoHiddenSpec2KnsIContentSniffergetMIMETypeFromContent2KY怔nsIXULTemplateBuilderrootdatasourcedatasourcedatabaserootResultqueryProcessorrebuildrefreshaddResultremoveResultreplaceResultresultBindingChangedgetResultForIdgetResultForContenthasGeneratedContentaddRuleFiltercreateContentsaddListenerremoveListener2K`K`2@K2K`#K`׀K`KKK׀K׀L׀׀L׀L*`׀L9`׀LM`
LaLo
L~LnsIAccessibleVirtualCursorChangeEventoldAccessibleoldStartOffsetoldEndOffsetreasonM`M`M`M`nsIIDNServiceconvertUTF8toACEconvertACEtoUTF8isACEnormalizeconvertToDisplayIDN2N0NANR`
NXNb@
nsIAsyncStreamCopier2initasyncCopyN䀒pV

N2nsIRefreshURIrefreshURIforceRefreshURIsetupRefreshURIsetupRefreshURIFromHeadercancelRefreshURITimersrefreshPending2O

O*
O:OJOdO{`
nsIAsyncInputStreamcloseWithStatusasyncWaitWAIT_CLOSURE_ONLYOPLVPnsIScriptableRegioninitsetToRegionsetToRectintersectRegionintersectRectunionRegionunionRectsubtractRegionsubtractRectisEmptyisEqualRegiongetBoundingBoxoffsetgetRectscontainsRectregion2PePjoPvPoPPoPPoPP`
Po`
P@@@@PP`Q`
Q`nsIClassInfogetInterfacesgetScriptableHelpercontractIDclassDescriptionclassIDflagsclassIDNoAllocSINGLETONTHREADSAFEMAIN_THREAD_ONLYDOM_OBJECTPLUGIN_OBJECTSINGLETON_CLASSINFOCONTENT_NODERESERVED2Q@`R
`.R!`R,`R=`RE`RK`RZRdRoRRR R@RnsIPaymentActionResponserequestIdtypeNO_TYPECANMAKE_ACTIONSHOW_ACTIONABORT_ACTIONCOMPLETE_ACTIONABORT_SUCCEEDEDABORT_FAILEDPAYMENT_ACCEPTEDPAYMENT_REJECTEDCOMPLETE_SUCCEEDEDCOMPLETE_FAILED2SxS`SSSSSSSSSTTnsIPromptalertalertCheckconfirmconfirmCheckconfirmExpromptpromptPasswordpromptUsernameAndPasswordselectBUTTON_POS_0BUTTON_POS_1BUTTON_POS_2BUTTON_TITLE_OKBUTTON_TITLE_CANCELBUTTON_TITLE_YESBUTTON_TITLE_NOBUTTON_TITLE_SAVEBUTTON_TITLE_DONT_SAVEBUTTON_TITLE_REVERTBUTTON_TITLE_IS_STRINGBUTTON_POS_0_DEFAULTBUTTON_POS_1_DEFAULTBUTTON_POS_2_DEFAULTBUTTON_DELAY_ENABLESTD_OK_CANCEL_BUTTONSSTD_YES_NO_BUTTONS2	TT
T`
T
`
T	
`T
`
T
`
T
`
U@`
UU%U2U?UOUcUtUUUUUUVVV+VAnsIDOMCSSRuleListlengthitem2W`W`nsIBlocklistServiceisAddonBlocklistedgetAddonBlocklistStategetPluginBlocklistStategetAddonBlocklistURLgetAddonBlocklistEntrygetPluginBlocklistURLgetPluginInfoURLSTATE_NOT_BLOCKEDSTATE_SOFTBLOCKEDSTATE_BLOCKEDSTATE_OUTDATEDSTATE_VULNERABLE_UPDATE_AVAILABLESTATE_VULNERABLE_NO_UPDATE2W`
X`X`X2XG`X^XtXXXXXXnsIAlertNotificationImageListeneronImageReadyonImageMissing2Y2wY2nsISecurityEventSinkonSecurityChange2Z$2nsITimerCallbacknotify2Z[snsIAssociatedContentSecuritycountSubRequestsBrokenSecuritycountSubRequestsBrokenSecuritycountSubRequestsNoSecuritycountSubRequestsNoSecurityflush2Z`@ZZ`@Z[nsIPerformanceAlertreasonhighestJankhighestCPOWREASON_SLOWDOWNREASON_JANK_IN_ANIMATIONREASON_JANK_IN_INPUT2[W`[^`[j`[v[[nsIXULBrowserWindowsetJSStatussetOverLinkonBeforeLinkTraversalforceInitialBrowserRemoteforceInitialBrowserNonRemoteshouldLoadURIshowTooltiphideTooltipgetTabCountnavigateAndRestoreByIndex2
\\\ 
\6\P\mF
`
\{\\`\nsIProtocolHandlerschemedefaultPortprotocolFlagsnewURInewChannel2newChannelallowPortURI_STDURI_NORELATIVEURI_NOAUTHALLOWS_PROXYALLOWS_PROXY_HTTPURI_INHERITS_SECURITY_CONTEXTURI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENTURI_LOADABLE_BY_ANYONEURI_DANGEROUS_TO_LOADURI_IS_UI_RESOURCEURI_IS_LOCAL_FILEURI_LOADABLE_BY_SUBSUMERSURI_DOES_NOT_RETURN_DATAURI_IS_LOCAL_RESOURCEURI_OPENING_EXECUTES_SCRIPTURI_NON_PERSISTABLEURI_FORBIDS_COOKIE_ACCESSURI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERMURI_SYNC_LOAD_IS_OKURI_SAFE_TO_LOAD_IN_SECURE_CONTEXTURI_FETCHABLE_BY_ANYONEORIGIN_IS_FULL_SPECURI_SCHEME_NOT_SELF_LINKABLE2]g]n`]z`]`]`]`]`
]]]]]]^ ^:@^Q^g^z^^^^ ^@___C_W_z__ nsINSSVersionNSPR_MinVersionNSS_MinVersionNSSUTIL_MinVersionNSSSSL_MinVersionNSSSMIME_MinVersionNSPR_VersionNSS_VersionNSSUTIL_VersionNSSSSL_VersionNSSSMIME_Version2
aaa*a=aOacapa|aaimgIContainerwidthheightintrinsicSizeintrinsicRatiooptimalImageSizeForDesttypeanimatedgetFramegetFrameAtSizewillDrawOpaqueNowisImageContainerAvailablegetImageContainerdrawstartDecodingstartDecodingWithResultrequestDecodeForSizelockImageunlockImagerequestDiscardrequestRefreshanimationModeanimationModeresetAnimationgetFrameIndexgetOrientationgetFirstFrameDelaysetAnimationStartTimegetImageSpaceInvalidationRectunwrappropagateUseCountersTYPE_RASTERTYPE_VECTORFLAG_NONEFLAG_SYNC_DECODEFLAG_SYNC_DECODE_IF_FASTFLAG_ASYNC_NOTIFYFLAG_DECODE_NO_PREMULTIPLY_ALPHAFLAG_DECODE_NO_COLORSPACE_CONVERSIONFLAG_CLAMPFLAG_HIGH_QUALITY_SCALINGFLAG_WANT_DATA_SURFACEFLAG_BYPASS_SURFACE_CACHEFLAG_FORCE_PRESERVEASPECTRATIO_NONEFLAG_FORCE_UNIFORM_SCALINGDECODE_FLAGS_DEFAULTFRAME_FIRSTFRAME_CURRENTFRAME_MAX_VALUEkNormalAnimModekDontAnimModekLoopOnceAnimMode2b%`b+`b2`b@` bObg`bl`
(bu(b~(b
(b
(b(bb(b
bccc! c0
c?`@cMc[ cj cx c c
 c c(c
ccdddd6dHdid d@dddee#e8eDeReberensISimpleStreamListenerinitjgpmozIAsyncFaviconssetAndFetchFaviconForPagereplaceFaviconDatareplaceFaviconDataFromDataURLgetFaviconURLForPagegetFaviconDataForPagecopyFavicons2g
"`Հggh"h."hD"nsIWebProgressListeneronStateChangeonProgressChangeonLocationChangeonStatusChangeonSecurityChangeSTATE_STARTSTATE_REDIRECTINGSTATE_TRANSFERRINGSTATE_NEGOTIATINGSTATE_STOPSTATE_IS_REQUESTSTATE_IS_DOCUMENTSTATE_IS_NETWORKSTATE_IS_WINDOWSTATE_IS_REDIRECTED_DOCUMENTSTATE_RESTORINGSTATE_IS_INSECURESTATE_IS_BROKENSTATE_IS_SECURESTATE_BLOCKED_MIXED_ACTIVE_CONTENTSTATE_LOADED_MIXED_ACTIVE_CONTENTSTATE_BLOCKED_MIXED_DISPLAY_CONTENTSTATE_LOADED_MIXED_DISPLAY_CONTENTSTATE_BLOCKED_TRACKING_CONTENTSTATE_LOADED_TRACKING_CONTENTSTATE_BLOCKED_UNSAFE_CONTENTSTATE_SECURE_HIGHSTATE_SECURE_MEDSTATE_SECURE_LOWSTATE_IDENTITY_EV_TOPLEVELSTATE_USES_SSL_3STATE_USES_WEAK_CRYPTOSTATE_CERT_USER_OVERRIDDENLOCATION_CHANGE_SAME_DOCUMENTLOCATION_CHANGE_ERROR_PAGE2hii怒i$i3iDiPibiuiiiiiiijjj%j5jX jzjjj j@kk-k>kOkjk{kkknsIDOMXULCommandDispatcherfocusedElementfocusedElementfocusedWindowfocusedWindowaddCommandUpdaterremoveCommandUpdaterupdateCommandsgetControllerForCommandgetControllersadvanceFocusrewindFocusadvanceFocusIntoSubtreesuppressFocusScrollsuppressFocusScrolllockunlock2m~`@mm`@mmmmm`\n`nn"n.nF`
@nZ
nnnsnsIXPCComponentsclassesclassesByIDstackmanagerutilsIDExceptionConstructorreturnCodereturnCodereportErroroB`-oJ`GoV`o\`aod`ƀoj`om`ow`؀o`BoonsISupportsPriorityprioritypriorityadjustPriorityPRIORITY_HIGHESTPRIORITY_HIGHPRIORITY_NORMALPRIORITY_LOWPRIORITY_LOWEST2p>`@pGpPp_ppp~p
pnsIListenerChangeListenerlistenersChanged2qnsIWindowsUIUtilsinTabletModeupdateTabletModeState2qM`
qZnsICookieManagerremoveAllenumeratorsessionEnumeratorremoveremoveNative2qq`Kq`Kq
 q
nsIFullHashMatchtableNamefullHashcacheDuration2r1r;rD`rdfITripleVisitorvisit2r
nsIPlatformInfoplatformVersionplatformBuildID2rrnsITreeSelectiontreetreesinglecountisSelectedselecttimedSelecttoggleSelectrangedSelectclearRangeclearSelectioninvertSelectionselectAllgetRangeCountgetRangeAtinvalidateSelectionadjustSelectionselectEventsSuppressedselectEventsSuppressedcurrentIndexcurrentIndexcurrentColumncurrentColumnshiftSelectPivot2s`@s
s`
s`s`
s*s1s=sJ
sWsbsqss`s@@sss`
@s
s`@tt`@tt,`nsIWindowlessBrowsercloseiuYnsISupportsFloatdatadatatoStringGu`@uu`nsIPresentationDevicePromptpromptDeviceSelection2unsITCPSocketCallbackfireErrorEventfireDataStringEventfireDataArrayEventfireEventupdateReadyStateupdateBufferedAmountBUFFER_SIZE2vv v4vGvQvbvwnsIXULBuilderListenerwillRebuilddidRebuild2vivinsIGeolocationProviderstartupwatchshutdownsetHighAccuracy2w:wB9wHwQ
nsIStreamBufferAccessgetBufferputBufferdisableBufferingenableBufferingunbufferedStream2(w(w
www`2nsIJumpListItemtypeequalsJUMPLIST_ITEM_EMPTYJUMPLIST_ITEM_SEPARATORJUMPLIST_ITEM_LINKJUMPLIST_ITEM_SHORTCUT2x5`x:`
xAxUxmxnsINetworkPredictorpredictpredictNativelearnlearnNativeresetPREDICT_LINKPREDICT_LOADPREDICT_STARTUPLEARN_LOAD_TOPLEVELLEARN_LOAD_SUBRESOURCELEARN_LOAD_REDIRECTLEARN_STARTUP2x$ x$x yyyy!y.y>yRyiy}nsIHelperAppLauncherMIMEInfosourcesuggestedFileNamesaveToDisklaunchWithApplicationsaveDestinationAvailablesetWebProgressListenertargetFiletargetFileIsExecutabletimeDownloadStartedcontentLengthrzF`zO`zVzh1
zs1
z1zz`1z`
z`z`nsISocketTransportServicecreateTransportcreateUnixDomainTransportattachSocketnotifyWhenCanAttachSocket2{7`{1`{{nsISpeechTasksetupsendAudiosendAudioNativedispatchStartdispatchEnddispatchPausedispatchResumedispatchErrordispatchBoundarydispatchMark2
|G|M|W|g|u|||||mozIPlaceInfoplaceIdguidurititlefrecencyvisits2}``}h}m`}q}w`}`nsIFrameLoaderOwnerframeLoaderGetFrameLoadersetIsPrerenderedinternalSetFrameLoader2}`(}}(~
nsICookienamevalueisDomainhostpathisSecureexpiresstatuspolicyoriginAttributesSTATUS_UNKNOWNSTATUS_ACCEPTEDSTATUS_DOWNGRADEDSTATUS_FLAGGEDSTATUS_REJECTEDPOLICY_UNKNOWNPOLICY_NONEPOLICY_NO_CONSENTPOLICY_IMPLICIT_CONSENTPOLICY_EXPLICIT_CONSENTPOLICY_NO_II2
~\~a~g`
~p~u~z`
~`~`~`~`~~~~~~	'?WnsICacheStorageServicememoryCacheStoragediskCacheStoragepinningCacheStorageappCacheStoragesynthesizedCacheStorageclearpurgeFromMemoryioTargetasyncGetDiskConsumptionasyncVisitAllStoragesPURGE_DISK_DATA_ONLYPURGE_DISK_ALLPURGE_EVERYTHING2
I`R\
`Rm`RꀒP`R`R`Vb
nsIPresentationChannelDescriptiontypetcpAddresstcpPortdataChannelSDPTYPE_TCPTYPE_DATACHANNEL2``
`$-nsISiteHPKPStatesha256Keys9`KnsIPrefLocalizedStringdatadatatoStringsetDataWithLength2`@`nsITypeAheadFindinitfindfindAgaingetFoundRangesetDocShellsetSelectionModeAndRepaintcollapseSelectionisRangeVisiblesearchStringcaseSensitivecaseSensitiveentireWordentireWordfoundLinkfoundEditablecurrentWindowFIND_FOUNDFIND_NOTFOUNDFIND_WRAPPEDFIND_PENDING2,F1
`6

`@`NFZu
`
`
@
`
@
```!nsIX509CertListaddCertdeleteCertgetEnumeratorgetRawCertListequals2!,`K(:I`
nsITreeColumnelementcolumnsxwidthidgetIdConstatomindexprimarycyclereditableselectabletypegetNextgetPreviousinvalidateTYPE_TEXTTYPE_CHECKBOXTYPE_PROGRESSMETERTYPE_PASSWORD2````P```
`
`
`
```!/BextIConsolelogopen2'+mozISpellI18NManagergetUtil2^`nsILayoutHistoryStatehasStatesgetKeysgetPresStateaddNewPresStateAddStateGetStateRemoveStateHasStatesSetScrollPositionOnlyResetScrollState2
`
D`@@@
@@


(
((
(
(
(
nsIMutableArrayappendElementremoveElementAtinsertElementAtreplaceElementAtclear2
2
2
nsIUnicharStreamLoaderinitchannelcharsetrawBufferjDI`QYnsIUDPMessagefromAddrdataoutputStreamrawDatagetDataAsTArray2`?`p`(nsIErrorServiceregisterErrorStringBundleunregisterErrorStringBundlegetErrorStringBundle2"<X`nsIPushNotifiernotifyPushnotifyPushWithDatanotifySubscriptionChangenotifySubscriptionModifiednotifyError2nsICollationinitializecompareStringallocateRawSortKeycompareRawSortKeykCollationStrengthDefaultkCollationCaseInsensitiveAsciikCollationAccentInsenstivekCollationCaseSensitivekCollationCaseInSensitive2mx`@@`nsIAutoCompleteInputpopupcontrollerpopupOpenpopupOpendisableAutoCompletedisableAutoCompletecompleteDefaultIndexcompleteDefaultIndexcompleteSelectedIndexcompleteSelectedIndexforceCompleteforceCompleteminResultsForPopupminResultsForPopupmaxRowsmaxRowsshowCommentColumnshowCommentColumnshowImageColumnshowImageColumntimeouttimeoutsearchParamsearchParamsearchCountgetSearchAttextValuetextValuesetTextValueWithReasonselectionStartselectionEndselectTextRangeonSearchBeginonSearchCompleteonTextEnteredonTextRevertedconsumeRollupEventinPrivateContextnoRollupOnCaretMoveuserContextIdTEXTVALUE_REASON_UNKNOWNTEXTVALUE_REASON_COMPLETEDEFAULTTEXTVALUE_REASON_COMPLETESELECTEDTEXTVALUE_REASON_REVERTTEXTVALUE_REASON_ENTERMATCH2(``%`
@
`
@
`
@!
6`
@L
b`
@p
~`@`@`
@
`
@
`@@ `,8@BLc`r``
`
`
`
`
`*KmmozISpellI18NUtillanguagegetRootFormfromRootFormfindNextWordkCheckkSuggest2w`@@@@@@nsITooltipTextProvidergetNodeText2-@@`
nsITaskbarOverlayIconControllersetOverlayIcon2r}nsISlowScriptDebuggerStartupCallbackfinishDebuggerStartup2nsIPresentationDeviceidnametypeestablishControlChanneldisconnectisRequestedUrlSupported2`&`
nsICommandParamsgetValueTypegetBooleanValuegetLongValuegetDoubleValuegetStringValuegetCStringValuegetISupportsValuesetBooleanValuesetLongValuesetDoubleValuesetStringValuesetCStringValuesetISupportsValueremoveValueeNoTypeeBooleanTypeeLongTypeeDoubleTypeeWStringTypeeISupportsTypeeStringType2``
``	``2
	*9I2[go|nsIEditActionListenerWillCreateNodeDidCreateNodeWillInsertNodeDidInsertNodeWillDeleteNodeDidDeleteNodeWillSplitNodeDidSplitNodeWillJoinNodesDidJoinNodesWillInsertTextDidInsertTextWillDeleteTextDidDeleteTextWillDeleteSelectionDidDeleteSelection2	$2?N\ky))nsIDownloadManageraddDownloadgetDownloadgetDownloadByGUIDcancelDownloadremoveDownloadremoveDownloadsByTimeframepauseDownloadresumeDownloadretryDownloadDBConnectionprivateDBConnectioncanCleanUpcanCleanUpPrivatecleanUpcleanUpPrivateactiveDownloadCountactivePrivateDownloadCountactiveDownloadsactivePrivateDownloadsaddListeneraddPrivacyAwareListenerremoveListenerdefaultDownloadsDirectoryuserDownloadsDirectoryDOWNLOAD_TYPE_DOWNLOADDOWNLOAD_NOTSTARTEDDOWNLOAD_DOWNLOADINGDOWNLOAD_FINISHEDDOWNLOAD_FAILEDDOWNLOAD_CANCELEDDOWNLOAD_PAUSEDDOWNLOAD_QUEUEDDOWNLOAD_BLOCKED_PARENTALDOWNLOAD_SCANNINGDOWNLOAD_DIRTYDOWNLOAD_BLOCKED_POLICY2
1r
``i+9HV`c`w`
`
```K`K
%4`1N`1e|%4	nsIPresentationDeviceRequestoriginrequestURLschromeEventHandlerprincipalselectcancel2``8`!nsIDOMXULContainerElementappendIteminsertItemAtremoveItemAtitemCountgetIndexOfItemgetItemAtIndex``````nsIProtocolProxyService2reloadPACasyncResolve2PZV`rnsIDOMGeoPositionCoordslatitudelongitudealtitudeaccuracyaltitudeAccuracyheadingspeed2`	`	`	`	`	`	`	nsISupportsDoubledatadatatoStringGL`	@Q	V`nsIAccessibleHyperTextlinkCountgetLinkAtgetLinkIndexgetLinkIndexAtOffset2``׀``nsINativeFileWatcherServiceaddPathremovePath2&!
.!
nsISaveAsCharsetcharsetInitConvertmask_Fallbackmask_Entitymask_CharsetFallbackattr_FallbackNoneattr_FallbackQuestionMarkattr_FallbackEscapeUattr_FallbackDecimalNCRattr_FallbackHexNCRattr_EntityNoneattr_EntityBeforeCharsetConvattr_EntityAfterCharsetConvattr_CharsetFallbackattr_plainTextDefaultattr_htmlTextDefault2}.>[wnsIUrlClassifierPositiveCacheEntryfullhashexpiry2`nsIContentSecurityPolicygetPolicyGetPolicypolicyCountupgradeInsecureRequestsblockAllMixedContentenforcesFrameAncestorsgetReferrerPolicyappendPolicygetAllowsInlinegetAllowsEvalgetCSPSandboxFlagslogViolationDetailssetRequestContextensureEventTargetrequireSRIForTypepermitsAncestrypermitsshouldLoadtoJSONNO_DIRECTIVEDEFAULT_SRC_DIRECTIVESCRIPT_SRC_DIRECTIVEOBJECT_SRC_DIRECTIVESTYLE_SRC_DIRECTIVEIMG_SRC_DIRECTIVEMEDIA_SRC_DIRECTIVEFRAME_SRC_DIRECTIVEFONT_SRC_DIRECTIVECONNECT_SRC_DIRECTIVEREPORT_URI_DIRECTIVEFRAME_ANCESTORS_DIRECTIVEREFLECTED_XSS_DIRECTIVEBASE_URI_DIRECTIVEFORM_ACTION_DIRECTIVEREFERRER_DIRECTIVEWEB_MANIFEST_SRC_DIRECTIVEUPGRADE_IF_INSECURE_DIRECTIVECHILD_SRC_DIRECTIVEBLOCK_ALL_MIXED_CONTENTREQUIRE_SRI_FORSANDBOX_DIRECTIVEVIOLATION_TYPE_INLINE_SCRIPTVIOLATION_TYPE_EVALVIOLATION_TYPE_INLINE_STYLEVIOLATION_TYPE_NONCE_SCRIPTVIOLATION_TYPE_NONCE_STYLEVIOLATION_TYPE_HASH_SCRIPTVIOLATION_TYPE_HASH_STYLEVIOLATION_TYPE_REQUIRE_SRI_FOR_STYLEVIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT(``
`
`
+@`
=

J
`
Z@
`
h`{V`
F`

`
22`'<Pbv	


#6Qo
&A\v	nsIUnicodeNormalizerNormalizeUnicodeNFDNormalizeUnicodeNFCNormalizeUnicodeNFKDNormalizeUnicodeNFKC2nsIPrivacyTransitionObserverprivateModeChanged2b
nsIWellKnownOpportunisticUtilsverifyvalidmixedlifetime2`
`
`nsIPushErrorReporterreportDeliveryErrorACK_DELIVEREDACK_DECRYPTION_ERRORACK_NOT_DELIVEREDUNSUBSCRIBE_MANUALUNSUBSCRIBE_QUOTA_EXCEEDEDUNSUBSCRIBE_PERMISSION_REVOKEDDELIVERY_UNCAUGHT_EXCEPTIONDELIVERY_UNHANDLED_REJECTIONDELIVERY_INTERNAL_ERROR2		+@RensIDOMCSSStyleRuleselectorTextselectorTextstyle2U@bo`nsICaptivePortalServiceCallbackcomplete2
nsIINIParserWritersetStringwriteFileWRITE_UTF1621nsIASN1SequenceASN1ObjectsASN1ObjectsisValidContainerisValidContainerisExpandedisExpandedG`@S_`
@p
`
@
nsIWindowCreator2createChromeWindow2setScreenId7Ȁ@
`ȀnsIPerformanceObserverobserve2V2ynsIPlaintextEditortextLengthmaxTextLengthmaxTextLengthwrapWidthwrapWidthsetWrapColumnnewlineHandlingnewlineHandlinginsertTextsetTextinsertLineBreakeEditorPlaintextMaskeEditorSingleLineMaskeEditorPasswordMaskeEditorReadonlyMaskeEditorDisabledMaskeEditorFilterInputMaskeEditorMailMaskeEditorEnableWrapHackMaskeEditorWidgetMaskeEditorNoCSSMaskeEditorAllowInteractioneEditorDontEchoPasswordeEditorRightToLefteEditorLeftToRighteEditorSkipSpellCheckeNewlinesPasteIntacteNewlinesPasteToFirsteNewlinesReplaceWithSpaceseNewlinesStripeNewlinesReplaceWithCommaseNewlinesStripSurroundingWhitespace2``@`@`@)?Sg{ @" 5@K`vnsIInlineSpellCheckerspellCheckerinitcleanupenableRealTimeSpellenableRealTimeSpellspellCheckAfterEditorChangespellCheckRangegetMisspelledWordreplaceWordaddWordToDictionaryremoveWordFromDictionaryignoreWordignoreWordsupdateCurrentDictionaryspellCheckPending2%`\2a7
?`
@S
g)`
`
nsIClipboardCommandscanCutSelectioncanCopySelectioncanCopyLinkLocationcanCopyImageLocationcanCopyImageContentscanPastecutSelectioncopySelectioncopyLinkLocationcopyImageLocationcopyImageContentspasteselectAllselectNone2`
`
`
3`
H`
]`
fsnsIDOMWindow2nsIPerformanceObservabletargetaddJankObserverremoveJankObserver2`2ƀƀnsIWebSocketEventServiceaddListenerremoveListenerhasListenerFor2ÀÀ
`
nsIApplicationCacheServicebuildGroupIDForInfobuildGroupIDForSuffixcreateApplicationCachecreateCustomApplicationCachegetApplicationCachegetActiveCachedeactivateGroupevictevictMatchingOriginAttributeschooseApplicationCachecacheOpportunisticallygetGroupsgetGroupsTimeOrdered2
c꨸w`P1`P`P`P`P/PFD`PD`nsIXPCComponents_Interfaces2nsISecurityInfoProvidersecurityInfohasTransferredData2e`2r`
nsIXSLTProcessorPrivateDISABLE_ALL_LOADS2nsIMessageListenerManageraddMessageListenerremoveMessageListeneraddWeakMessageListenerremoveWeakMessageListenermarkForCC2
	6 P
nsIGeneralResponseDatadatainitDatansIPushMessageprincipaldata2``nsISimpleContentPolicyshouldLoadshouldProcess7	
2`B	
2`nsIWindowMediator_44getMostRecentNonPBWindow`nsIMediaDevicetypenameidmediaSourcerawIdscary2
`
nsIPowerManagerServicepowerOffrebootrestartaddWakeLockListenerremoveWakeLockListenergetWakeLockStatenewWakeLock2pyǀǀ`2nsIASN1ObjecttypetypetagtagdisplayNamedisplayNamedisplayValuedisplayValueASN1_END_CONTENTSASN1_BOOLEANASN1_INTEGERASN1_BIT_STRINGASN1_OCTET_STRINGASN1_NULLASN1_OBJECT_IDASN1_ENUMERATEDASN1_UTF8_STRINGASN1_SEQUENCEASN1_SETASN1_PRINTABLE_STRINGASN1_T61_STRINGASN1_IA5_STRINGASN1_UTC_TIMEASN1_GEN_TIMEASN1_VISIBLE_STRINGASN1_UNIVERSAL_STRINGASN1_BMP_STRINGASN1_HIGH_TAG_NUMBERASN1_CONTEXT_SPECIFICASN1_APPLICATIONASN1_PRIVATE23`@8=`@AE@Q]@jw

,<LZh| !"nsIBlocklistPromptprompt2$nsIStandardURLinitsetDefaultPortURLTYPE_STANDARDURLTYPE_AUTHORITYURLTYPE_NO_AUTHORITY?RWfwnsIPermissionprincipaltypecapabilityexpireTypeexpireTimematchesmatchesURI2````
`
$
`
nsICommandManageraddCommandObserverremoveCommandObserverisCommandSupportedisCommandEnabledgetCommandStatedoCommand2xx`
`
〒nsIMessageSendersendAsyncMessageprocessMessageManager{`݀nsISHEntryInternalRemoveFromBFCacheAsyncRemoveFromBFCacheSynclastTouchedlastTouchedgetSharedState2 
 
	`@(!nsIApplicationCacheContainerapplicationCacheapplicationCache2`P@PnsIDocumentLoaderstopcontainerloadGroupdocumentChannel2`2``nsIDirectoryServiceProvidergetFile2K@
`1nsICommandLinelengthgetArgumentfindFlagremoveArgumentshandleFlaghandleFlagWithParamstatepreventDefaultpreventDefaultworkingDirectorywindowContextresolveFileresolveURISTATE_INITIAL_LAUNCHSTATE_REMOTE_AUTOSTATE_REMOTE_EXPLICIT2
y`
`
`

``
@
`1`ʀ`1`3EnsIWifiListeneronChangeonError2+4nsIAutoCompletePopupinputoverrideValueselectedIndexselectedIndexpopupOpenopenAutocompletePopupclosePopupinvalidateselectByINVALIDATE_REASON_NEW_RESULTINVALIDATE_REASON_DELETE2	s`yÇ`@Õã`
í

nsIPushClearResultCallbackonClear2ĪnsIAuthPromptProvidergetAuthPromptPROMPT_NORMALPROMPT_PROXY2`nsICaptivePortalServicerecheckCaptivePortalstatelastCheckedUNKNOWNNOT_CAPTIVEUNLOCKED_PORTALLOCKED_PORTAL2BW`]`iq}ōnsIAuthPromptCallbackonAuthAvailableonAuthCancelled22s2
nsIX509CertemailAddressisBuiltInRootgetEmailAddressescontainsEmailAddresssubjectNamecommonNameorganizationorganizationalUnitsha256Fingerprintsha1FingerprinttokenNameissuerNameserialNumberissuerCommonNameissuerOrganizationissuerOrganizationUnitissuervaliditydbKeydisplayNamecertTypeisSelfSignedgetChainkeyUsagesASN1StructuregetRawDERequalssha256SubjectPublicKeyInfoDigestexportAsCMSgetCertmarkForPermDeletionUNKNOWN_CERTCA_CERTUSER_CERTEMAIL_CERTSERVER_CERTANY_CERTCMS_CHAIN_MODE_CertOnlyCMS_CHAIN_MODE_CertChainCMS_CHAIN_MODE_CertChainWithRoot2IV`
d@`v`
ƋƗƢƯ*A`H`QWc`l`
y`ǂnj`؀ǚ@`Ǥ`
ǫ@`(		*3KdnsICertOverrideServicerememberValidityOverriderememberTemporaryValidityOverrideUsingFingerprinthasMatchingOverridegetValidityOverrideclearValidityOverrideisCertUsedForOverridesERROR_UNTRUSTEDERROR_MISMATCHERROR_TIME2E
^ʐ@@
`
ʤ@@
`
ʸ

`nsIPartialSHistoryListeneronRequestCrossBrowserNavigation2˴nsIPresentationDeviceManagerdeviceAvailableaddDeviceProviderremoveDeviceProviderforceDiscoverygetAvailableDevices2`
q$q9H`nsIAsyncOutputStreamcloseWithStatusasyncWaitWAIT_CLOSURE_ONLYp̲VnsIContentPrefService2getByNamegetByDomainAndNamegetBySubdomainAndNamegetGlobalgetCachedByDomainAndNamegetCachedBySubdomainAndNamegetCachedGlobalsetsetGlobalremoveByDomainAndNameremoveBySubdomainAndNameremoveGlobalremoveByDomainremoveBySubdomainremoveByNameremoveAllDomainsremoveAllDomainsSinceremoveAllGlobalsaddObserverForNameremoveObserverForNameextractDomain2#-@V``JyD`J͕`Jͥͩͳ.DUh~nsIJSCIDcreateInstancegetService/`.`nsIFrameScriptLoaderloadFrameScriptremoveDelayedFrameScriptgetDelayedFrameScripts2m

}Ж`nsIHelperAppLauncherDialogshowpromptForSaveToFileAsyncREASON_CANTHANDLEREASON_SERVERREQUESTREASON_TYPESNIFFED222
!6nsIPropertyBagenumeratorgetProperty2Ѣ`Kѭ`nsIRDFServiceGetResourceGetUnicodeResourceGetAnonymousResourceGetLiteralGetDateLiteralGetIntLiteralgetBlobLiteralIsAnonymousResourceRegisterResourceUnregisterResourceRegisterDataSourceUnregisterDataSourceGetDataSourceGetDataSourceBlocking2````'`6`D`S`
g
xҋ
Ҟҳ``nsIFormAutoCompleteautoCompleteSearchAsyncstopAutoCompleteSearch2Ӳ"imgICacheclearCacheremoveEntryfindEntryPropertiesrespectPrivacyNotificationsclearCacheForControlledDocument2
!-`A(]
nsINavBookmarkObserverskipTagsskipDescendantsOnItemRemovalonBeginUpdateBatchonEndUpdateBatchonItemAddedonItemRemovedonItemChangedonItemVisitedonItemMoved2	`
`
'
3A
O]
nsIAboutModulenewChannelgetURIFlagsURI_SAFE_FOR_UNTRUSTED_CONTENTALLOW_SCRIPTHIDE_FROM_ABOUTABOUTENABLE_INDEXED_DBURI_CAN_LOAD_IN_CHILDURI_MUST_LOAD_IN_CHILDMAKE_UNLINKABLEMAKE_LINKABLE2/`:`Ferև֙֯ @nsITextInputProcessorNotificationtypeoffsettextcollapsedlengthreversedwritingModecausedByCompositioncausedBySelectionEventoccurredDuringCompositionremovedLengthaddedLengthcausedOnlyByCompositionincludingChangesDuringCompositionincludingChangesWithoutComposition2w|`׃׈`
ג`י`
ע׮`
`
`
``
`
%`
G`
nsIMozSAXXMLDeclarationHandlerhandleXMLDeclaration2&
nsIDOMCSSPageRulestyle2b`nsINotificationStorageCallbackhandledone2ٚ
١txIEXSLTRegExFunctionsmatchreplacetest2``
nsIContentFilternotifyOfInsertion2KȀ
@
nsIByteRangeRequestisByteRangeRequeststartRangeendRange2ڤ`
ڷ``nsIContentPrefCallbackonResult2nsILoadContextInfoFactorydefaultprivateanonymouscustomfromLoadContextfromWindow2=`E`M`W
`^
`nʀ
`IPeerConnectionManagerhasActivePeerConnection2`
nsINavHistoryResultsortingModesortingModesortingAnnotationsortingAnnotationsuppressNotificationssuppressNotificationsaddObserverremoveObserverroot2	.`@:F@Xj`
@܀
ܖ
ܢܱ`wnsIXULTemplateQueryProcessorgetDatasourceinitializeForBuildingdonecompileQuerygenerateResultsaddBindingtranslateRefcompareResults2<
i@
`2J2i`ei뀒`2r2׀2`K݂뀒뀹ݍ2`׀ݚ׀׀`mozILocaleServicedefaultLocalegetAppLocalesAsLangTagsgetAppLocalesAsBCP47getRegionalPrefsLocalesnegotiateLanguagesgetAppLocaleAsLangTaggetAppLocaleAsBCP47getRequestedLocalesgetRequestedLocalesetRequestedLocalesgetAvailableLocalesisAppLocaleRTLlangNegStrategyFilteringlangNegStrategyMatchinglangNegStrategyLookup2n|D`ޔD`ީD`D`D`%9D`M`
\uߍnsIUnicharStreamLoaderObserveronDetermineCharsetonStreamComplete222nsIRequestObserverProxyinit2nsIPKCS11SlotnamedescmanIDHWVersionFWVersionstatusgetTokentokenNameSLOT_DISABLEDSLOT_NOT_PRESENTSLOT_UNINITIALIZEDSLOT_NOT_LOGGED_INSLOT_LOGGED_INSLOT_READY2&+06@J`Q`׀ZdrnsILocaleServicenewLocalegetSystemLocalegetApplicationLocalegetLocaleFromAcceptLanguagegetLocaleComponentForUserAgent2c`݀m`݀}`݀`݀nsISoundplayplaySystemSoundbeepinitplayEventSoundEVENT_NEW_MAIL_RECEIVEDEVENT_ALERT_DIALOG_OPENEVENT_CONFIRM_DIALOG_OPENEVENT_PROMPT_DIALOG_OPENEVENT_SELECT_DIALOG_OPENEVENT_MENU_EXECUTEEVENT_MENU_POPUPEVENT_EDITOR_MAX_LEN2Ȁ 05:IaynsINativeFileWatcherSuccessCallbackcomplete2nsIDocCharset2nsILoginInfohostnamehostnameformSubmitURLformSubmitURLhttpRealmhttpRealmusernameusernameusernameFieldusernameFieldpasswordpasswordpasswordFieldpasswordFieldinitequalsmatchesclone2@@@@(1@?M@V_@m{`

`
`nsIBackgroundFileSaverobserverobserversignatureInfosha256HashenableSignatureInfoenableSha256enableAppendsetTargetfinish2	`@`1
nsIAccessibleTextRangestartContainerstartOffsetendContainerendOffsetcontainerembeddedChildrencomparecompareEndPointstextboundsmovemoveStartmoveEndnormalizecropfindTextfindAttraddToSelectionremoveFromSelectionselectscrollIntoViewEndPoint_StartEndPoint_EndFormatUnitWordUnitLineUnitParagraphUnitPageUnitDocumentUnitAnimationStyleAttrAnnotationObjectsAttrAnnotationTypesAttrBackgroundColorAttrBulletStyleAttrCapStyleAttrCaretBidiModeAttrCaretPositionAttrCultureAttrFontNameAttrFontSizeAttrFontWeightAttrForegroundColorAttrHorizontalTextAlignmentAttrIndentationFirstLineAttrIndentationLeadingAttrIndentationTrailingAttrIsActiveAttrIsHiddenAttrIsItalicAttrIsReadOnlyAttrIsSubscriptAttrIsSuperscriptAttrLinkAttrMarginBottomAttrMarginLeadingAttrMarginTopAttrMarginTrailingAttrOutlineStylesAttrOverlineColorAttrOverlineStyleAttrSelectionActiveEndAttrStrikethroughColorAttrStrikethroughStyleAttrStyleIdAttrStyleNameAttrTabsAttrTextFlowDirectionsAttrUnderlineColorAttrUnderlineStyleAttrAlignToTopAlignToBottom2e`t``````
```


`
`292HWdox%7C	P
]l
*<EVhv !"#$'%>&Q'donsIChildChannelconnectParentcompleteRedirectSetup2Tbj2nsIHttpProtocolHandleruserAgentappNameappVersionplatformoscpumiscYnsIListNetworkAddressesListeneronListedNetworkAddressesonListNetworkAddressesFailed2D]nsIWebProgressaddProgressListenerremoveProgressListenerDOMWindowDOMWindowIDinnerDOMWindowIDisTopLevelisLoadingDocumentloadTypetargettargetNOTIFY_STATE_REQUESTNOTIFY_STATE_DOCUMENTNOTIFY_STATE_NETWORKNOTIFY_STATE_WINDOWNOTIFY_STATE_ALLNOTIFY_PROGRESSNOTIFY_STATUSNOTIFY_SECURITYNOTIFY_LOCATIONNOTIFY_REFRESHNOTIFY_ALL2
````
`
``V@&V-BXm @nsIStreamingProtocolListeneronMediaDataAvailableonConnectedonDisconnected2**nsIRoutedSocketTransportServicecreateRoutedTransportf7`nsISpeechTaskCallbackonPauseonResumeonCancelonVolumeChanged2nsIDOMXULLabeledControlElementcropcropimageimagelabellabelaccessKeyaccessKeycommandcommand
*@/4@:@@FL@V`@hnsIHttpChannelrequestMethodrequestMethodreferrerreferrerreferrerPolicysetReferrerWithPolicyprotocolVersiontransferSizedecodedBodySizeencodedBodySizegetRequestHeadersetRequestHeadersetEmptyRequestHeadervisitRequestHeadersvisitNonDefaultRequestHeadersallowPipeliningallowPipeliningallowSTSallowSTSredirectionLimitredirectionLimitresponseStatusresponseStatusTextrequestSucceededisMainDocumentChannelisMainDocumentChannelgetResponseHeadersetResponseHeadervisitResponseHeadersgetOriginalResponseHeadervisitOriginalResponseHeadersisNoStoreResponseisNoCacheResponseisPrivateResponseredirectTorequestContextIDrequestContextIDchannelIdchannelIdtopLevelContentWindowIdtopLevelContentWindowIdisTrackingResourcetopLevelOuterContentWindowIdtopLevelOuterContentWindowIdlogBlockedCORSRequestREFERRER_POLICY_UNSETREFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADEREFERRER_POLICY_NO_REFERRERREFERRER_POLICY_ORIGINREFERRER_POLICY_ORIGIN_WHEN_XORIGINREFERRER_POLICY_UNSAFE_URLREFERRER_POLICY_SAME_ORIGINREFERRER_POLICY_STRICT_ORIGINREFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN-@`@`'=M`Z`j`z
LL`
@
`
@

`@'8`GZ`
k`
@

LLL`
`
+`
=H`HYj`@t~`@`
`@	'RnnsIAlertsDoNotDisturbmanualDoNotDisturbmanualDoNotDisturb2{`
@
nsIPrintSettingsWindeviceNamedeviceNamedriverNamedriverNamedevModedevModeprintableWidthInInchesprintableWidthInInchesprintableHeightInInchesprintableHeightInInchescopyFromNativecopyToNative2`H`H`H`	H#	:`	HR	 j
 y
nsIStructuredCloneContainerinitFromJSValinitFromBase64deserializeToJsvaldeserializeToVariantgetDataAsBase64serializedNBytesformatVersion2
#1@`S`hx``nsITranslationNodeListlengthitemisTranslationRootAtIndex2```
nsITransactionManagerdoTransactionundoTransactionredoTransactionclearclearUndoStackclearRedoStackbeginBatchendBatchnumberOfUndoItemsnumberOfRedoItemsmaxTransactionCountmaxTransactionCountbatchTopUndoremoveTopUndopeekUndoStackpeekRedoStackgetUndoListgetRedoListAddListenerRemoveListener2es2
```@$2`@`N`BZ`BfrnsIPrivateBrowsingTrackingProtectionWhitelistaddToAllowListremoveFromAllowListexistsInAllowList2`
nsIDashboardrequestSocketsrequestHttpConnectionsrequestWebsocketConnectionsrequestDNSInforequestConnectionenableLoggingenableLoggingrequestDNSLookuprequestRcwnStatsgetLogPath2
1@R`
@`
nnsIXPCConstructorclassIDinterfaceIDinitializer20`8`D`nsIRemoteBrowserenableDisableCommands2nsIControllerCommandTablemakeImmutableregisterCommandunregisterCommandfindCommandHandlerisCommandEnabledupdateCommandStatesupportsCommanddoCommanddoCommandParamsgetCommandStategetSupportedCommands2||	`|2`
-2@2`
P2Z2j2z@`nsIFaviconDataCallbackonComplete2NnsIQuotaUsageCallbackonUsageResult2nsILineInputStreamreadLine2`
nsIWebBrowserPrintglobalPrintSettingscurrentPrintSettingscurrentChildDOMWindowdoingPrintdoingPrintPreviewisFramesetDocumentisFramesetFrameSelectedisIFrameSelectedisRangeSelectionprintPreviewNumPagesprintprintPreviewprintPreviewNavigatecancelenumerateDocumentNamesexitPrintPreviewPRINTPREVIEW_GOTO_PAGENUMPRINTPREVIEW_PREV_PAGEPRINTPREVIEW_NEXT_PAGEPRINTPREVIEW_HOMEPRINTPREVIEW_END2`ڀ`ڀ`/`
:`
L`
_`
w`
`
`ڀڀ〒@`6M_nsIApplicationReputationServicequeryReputationVERDICT_SAFEVERDICT_DANGEROUSVERDICT_UNCOMMONVERDICT_POTENTIALLY_UNWANTEDVERDICT_DANGEROUS_HOST2s4nsIAccessibleCoordinateTypeCOORDTYPE_SCREEN_RELATIVECOORDTYPE_WINDOW_RELATIVECOORDTYPE_PARENT_RELATIVE2Ga{nsIBaseWindowinitWindowcreatedestroysetPositionsetPositionDesktopPixgetPositionsetSizegetSizesetPositionAndSizegetPositionAndSizerepaintparentWidgetparentWidgetparentNativeWindowparentNativeWindownativeHandlevisibilityvisibilityenabledenabledmainWidgetunscaledDevicePixelsPerCSSPixeldevicePixelsPerDesktopPixelsetFocustitletitleeRepainteDelayResize2@@

@@0@@@@C
K`HXe`@x`
@
`
@
``	`			`@			#nsITimedChanneltimingEnabledtimingEnabledredirectCountredirectCountchannelCreationasyncOpenlaunchServiceWorkerStartlaunchServiceWorkerStartlaunchServiceWorkerEndlaunchServiceWorkerEnddispatchFetchEventStartdispatchFetchEventStartdispatchFetchEventEnddispatchFetchEventEndhandleFetchEventStarthandleFetchEventStarthandleFetchEventEndhandleFetchEventEnddomainLookupStartdomainLookupEndconnectStartsecureConnectionStartconnectEndrequestStartresponseStartresponseEndredirectStartredirectStartredirectEndredirectEndinitiatorTypeinitiatorTypeallRedirectsSameOriginallRedirectsSameOriginallRedirectsPassTimingAllowCheckallRedirectsPassTimingAllowChecktimingAllowCheckcacheReadStartcacheReadEndchannelCreationTimeasyncOpenTimelaunchServiceWorkerStartTimelaunchServiceWorkerEndTimedispatchFetchEventStartTimedispatchFetchEventEndTimehandleFetchEventStartTimehandleFetchEventEndTimedomainLookupStartTimedomainLookupEndTimeconnectStartTimesecureConnectionStartTimeconnectEndTimerequestStartTimeresponseStartTimeresponseEndTimecacheReadStartTimecacheReadEndTimeredirectStartTimeredirectEndTime2;
{`
@


`@

`
`
`H

`H-`HE]`Hs`H`H````"`-`:`H`T`Hbp`H|H`
H
`
H

`

%`
4`
A`
U`
c`
`
`
`
`
```-`>`X`g`x``````nsIGIOMimeAppidnamecommandexpectsURIssupportedURISchemeslaunchsetAsDefaultForMimeTypesetAsDefaultForFileExtensionssetAsDefaultForURISchemeEXPECTS_URISEXPECTS_PATHSEXPECTS_URIS_FOR_NON_FILES2	GJOW`c`?w~nsIGfxInfoDebugspoofVendorIDspoofDeviceIDspoofDriverVersionspoofOSVersion2nsIPrintProgressParamsdocTitledocTitledocURLdocURL2`@'`@.nsIContentPolicyshouldLoadshouldProcessu22`22`nsIAccessibleTablecaptionsummarycolumnCountrowCountgetCellAtgetCellIndexAtgetColumnIndexAtgetRowIndexAtgetRowAndColumnIndicesAtgetColumnExtentAtgetRowExtentAtgetColumnDescriptiongetRowDescriptionisColumnSelectedisRowSelectedisCellSelectedselectedCellCountselectedColumnCountselectedRowCountselectedCellsgetSelectedCellIndicesgetSelectedColumnIndicesgetSelectedRowIndicesselectRowselectColumnunselectRowunselectColumnisProbablyForLayout2`````*`;`I@@b`t``
`
`
````@`4@`M@`cmz`
nsIDOMXULPopupElementpositionpositionshowPopuphidePopupBEFORE_STARTBEFORE_ENDAFTER_STARTAFTER_ENDSTART_BEFORESTART_AFTEREND_BEFOREEND_AFTEROVERLAPAT_POINTERAFTER_POINTER2@ )3=JUakx	
nsIJumpListShortcutappappiconIndexiconIndexfaviconPageUrifaviconPageUriP`Q@TQX`@bl`@{nsITaskbarPreviewcontrollercontrollertooltiptooltipvisiblevisibleactiveactiveinvalidate2	`@@
`
@
`
@$
+nsILocalCertGetCallbackhandleCert2nsITLSServerSocketserverCertserverCertsetSessionCachesetSessionTicketssetRequestClientCertificatesetCipherSuitessetVersionRangeREQUEST_NEVERREQUEST_FIRST_HANDSHAKEREQUEST_ALWAYSREQUIRE_FIRST_HANDSHAKEREQUIRE_ALWAYS`@


8HXf~nsIIdleServiceidleTimeaddIdleObserverremoveIdleObserver2H`QxaxnsIINIParserFactorycreateINIParser21`nsIPaymentRequestServicegetPaymentRequestByIdenumeratecleanupsetTestingUIServiceremoveActionCallbackrequestPaymentrespondPaymentchangeShippingAddresschangeShippingOption2	`0`Kˀ1 F!UqdznsIPresentationTCPSessionTransportBuilderbuildTCPSenderTransportbuildTCPReceiverTransport3,ODOnsIAccessibleObjectAttributeChangedEventchangedAttribute`nsIBoxObjectelementxyscreenXscreenYwidthheightgetPropertyAsSupportssetPropertyAsSupportsgetPropertysetPropertyremovePropertyparentBoxfirstChildlastChildnextSiblingpreviousSibling2````````224`@L[`e`p`z``extIPreferenceBranchrootalleventshasgetgetValuesetValuereset2ty`}`d`
``nsIWebSocketChanneloriginalURIURInotificationCallbacksnotificationCallbackssecurityInfoloadGrouploadGrouploadInfoloadInfoprotocolprotocolextensionsinitLoadInfoasyncOpenclosesendMsgsendBinaryMsgsendBinaryStreampingIntervalpingIntervalpingTimeoutpingTimeoutserialserialsetServerParametersCLOSE_NORMALCLOSE_GOING_AWAYCLOSE_PROTOCOL_ERRORCLOSE_UNSUPPORTED_DATATYPECLOSE_NO_STATUSCLOSE_ABNORMALCLOSE_INVALID_PAYLOADCLOSE_POLICY_VIOLATIONCLOSE_TOO_LARGECLOSE_EXTENSION_MISSINGCLOSE_INTERNAL_ERRORCLOSE_TLS_FAILED2 #` /` 3`?@ I? _`2 l`@ v `@  @    i2     `@!!
`@!!%`@!,!3o!G!T!e!z!!!!!!"	"nsIClipboardsetDatagetDataemptyClipboardhasDataMatchingFlavorssupportsSelectionClipboardsupportsFindClipboardkSelectionClipboardkGlobalClipboardkFindClipboardkSelectionCache2#.#.##`
#`
$`
$0$D$U$dnsITaskbarPreviewButtontooltiptooltipdismissOnClickdismissOnClickhasBorderhasBorderdisableddisabledimageimagevisiblevisible2%@%%`
@%'
%6`
@%@
%J`
@%S
%\`}@%b}%h`
@%p
nsIUpdatePromptcheckForUpdatesshowUpdateAvailableshowUpdateDownloadedshowUpdateErrorshowUpdateHistoryshowUpdateElevationRequired2&&&/
&D&Tʀ&fnsIAlertNotificationinitnameimageURLtitletexttextClickablecookiedirlangdataprincipalURIinPrivateBrowsingrequireInteractionactionablesourceloadImage2&


&&&&&`
'''''!`'+`'/`
'A`
'T`
'_'fu2`rnsIGlobalHistory2addURIisVisitedsetPageTitle2([

(b`
(lnsIExternalSharingAppServiceshareWithDefaultgetSharingApps2((D`xnsITransactionListnumItemsitemIsBatchgetItemgetDatagetNumChildrenForItemgetChildListForItem2)(`)1`
)=`)ED`2)M`)c`BnsICertTreeItemcerthostPort2)`)mozIColorAnalyzerfindRepresentativeColor2*nsIAddonInterpositioninterposePropertyinterposeCallgetWhitelist2*a`*s`*`nsIObserverServiceaddObserverremoveObservernotifyObserversenumerateObservers2*x
*x*2+`KnsISupportsPrimitivetypeTYPE_IDTYPE_CSTRINGTYPE_STRINGTYPE_PRBOOLTYPE_PRUINT8TYPE_PRUINT16TYPE_PRUINT32TYPE_PRUINT64TYPE_PRTIMETYPE_CHARTYPE_PRINT16TYPE_PRINT32TYPE_PRINT64TYPE_FLOATTYPE_DOUBLETYPE_INTERFACE_POINTER2+l`+q+y+++++++	+
++,
,,,)nsICacheServiceInternallockHeldTime,`	nsIEntityConverterConvertUTF32ToEntityConvertToEntityConvertToEntitiesentityNonehtml40Latin1html40Symbolshtml40Specialtransliteratemathml20html32html40entityW3C2-
`-`-/`	-A-L-Y-g-u----nsISupportsIDdatadatatoStringG.4`@.9.>`nsISimpleEnumeratorhasMoreElementsgetNext2.`
.`2nsIInputStreamCallbackonInputStreamReady2.nnsIAsyncShutdownClientnameaddBlockerremoveBlockerjsclient2/	/ˀ/ˀ/'`nsIPaymentCurrencyAmountcurrencyvalue2//nsIWorkerDebuggerManagerListeneronRegisteronUnregister2//nsIColorPickerShownCallbackupdatedone20 0'nsILocalHandlerAppexecutableexecutableparameterCountclearParametersappendParametergetParameterparameterExists0Z`1@0e10p`0000`
nsIDOMCSSImportRulehrefmediastyleSheet21#1(`1.`nsIRDFContainerDataSourceResourceInitGetCountGetElementsAppendElementRemoveElementInsertElementAtRemoveElementAtIndexOf2
1r`1}`11`1`K11
1
1
`1`nsIRDFContainerUtilsIsOrdinalPropertyIndexToOrdinalResourceOrdinalResourceToIndexIsContainerIsEmptyIsBagIsSeqIsAltMakeBagMakeSeqMakeAltindexOf22`
2`2`2`
2`
2`
2`
2`
2`S2`S2`S3`nsIUDPSocketinitinit2initWithAddresscloseasyncListenconnectlocalAddrportgetAddresssendsendWithAddrsendWithAddresssendBinaryStreamsendBinaryStreamWithAddressjoinMulticastjoinMulticastAddrleaveMulticastleaveMulticastAddrmulticastLoopbackmulticastLoopbackmulticastInterfacemulticastInterfacemulticastInterfaceAddrmulticastInterfaceAddrrecvBufferSizerecvBufferSizesendBufferSizesendBufferSize23

3
4
44%4#4+`?45`4:`4E`4J?`4W`4g4x44444`
@4
4@5
5 `H575N`H5]5l`H5{nsICommandLineHandlerhandlehelpInfo277nsIAnnotationServicesetPageAnnotationsetItemAnnotationsetPageAnnotationStringsetItemAnnotationStringsetPageAnnotationInt32setItemAnnotationInt32setPageAnnotationInt64setItemAnnotationInt64setPageAnnotationDoublesetItemAnnotationDoublegetPageAnnotationgetItemAnnotationgetPageAnnotationStringgetItemAnnotationStringgetPageAnnotationInt32getItemAnnotationInt32getPageAnnotationInt64getItemAnnotationInt64getPageAnnotationDoublegetItemAnnotationDoublegetPageAnnotationInfogetItemAnnotationInfogetPageAnnotationTypegetItemAnnotationTypegetPagesWithAnnotationgetItemsWithAnnotationgetAnnotationsWithNamegetPageAnnotationNamesgetItemAnnotationNamespageHasAnnotationitemHasAnnotationremovePageAnnotationremoveItemAnnotationremovePageAnnotationsremoveItemAnnotationscopyPageAnnotationscopyItemAnnotationsaddObserverremoveObservergetObserversEXPIRE_SESSIONEXPIRE_WEEKSEXPIRE_MONTHSEXPIRE_NEVEREXPIRE_WITH_HISTORYEXPIRE_DAYSTYPE_INT32TYPE_DOUBLETYPE_STRINGTYPE_INT642(7J7\7n777777	8	8*`8<`8N8f8~`8`8`8`8`	8`	9
@@@9 @@@96`9L`9bD`9yD`9D`9D`9D`9`
9`
9::#:9:O
:c
:w6:6:D`6
:::::::;;
;nsIWebBrowserPersistURIMapnumMappedURIsgetURIMappingtargetBaseURI2>2`>@>NnsIPaymentOptionsrequestPayerNamerequestPayerEmailrequestPayerPhonerequestShippingshippingType2>`
>`
>`
>`
>nsIDOMXULButtonElementtypetypedlgTypedlgTypeopenopencheckedcheckedcheckStatecheckStateautoCheckautoCheckgroupgroupCHECKSTATE_UNCHECKEDCHECKSTATE_CHECKEDCHECKSTATE_MIXED?9@?>?C@?K?S`
@?X
?]`
@?e
?m`@?x?`
@?
?@????nsIUnsubscribeResultCallbackonUnsubscribe2@
nsIControllerisCommandEnabledsupportsCommanddoCommandonEvent2@`
@`
@@nsISOCKSSocketInfodestinationAddrdestinationAddrexternalProxyAddrexternalProxyAddrinternalProxyAddrinternalProxyAddr2AI`HAYAi`HA{A`HAnsIUnicharInputStreamreadreadSegmentsreadStringclose2B
`B`B`B'nsIVerifySignedDirectoryCallbackverifySignedDirectoryFinished2BinISearchProcessisActiveresultCountholdResultsholdResultssearchSyncsearchAsyncsearchStopsearchStepgetStringResultAtgetIntResultAtgetUIntResultAt2B`
B`B`
@B
BCCC`
C*C<`CK`nsIComponentManagergetClassObjectgetClassObjectByContractIDcreateInstancecreateInstanceByContractIDaddBootstrappedManifestLocationremoveBootstrappedManifestLocationgetManifestLocations2C`C`D2`D!2`D<1D\1D`nsIUrlListManagergetGethashUrlgetUpdateUrlregisterTableunregisterTableenableUpdatedisableUpdatemaybeToggleUpdateCheckingcheckForUpdatesgetBackOffTime2	EE#E0`
E>ENE[EiE`
E`nsIUrlClassifierCacheEntryprefixexpirymatches2F,F3`F:`nsISupportsCStringdatadatatoStringGF|@FF`nsICacheACCESS_NONEACCESS_READACCESS_WRITEACCESS_READ_WRITESTORE_ANYWHERESTORE_IN_MEMORYSTORE_ON_DISKSTORE_OFFLINENOT_STREAM_BASEDSTREAM_BASEDNON_BLOCKINGBLOCKINGNO_EXPIRATION_TIME
FFFFFGGG!G/G@GMGZGcnsIXULWindowdocShellintrinsicallySizedintrinsicallySizedprimaryContentShellprimaryTabParenttabParentAddedtabParentRemovedgetLiveResizeListenersaddChildWindowremoveChildWindowcentershowModalzLevelzLevelchromeFlagschromeFlagsassumeChromeFlagsAreFrozencreateNewWindowXULBrowserWindowXULBrowserWindowapplyChromeFlagssizeShellToWithLimitnextTabParentIdlowestZloweredZnormalZraisedZhighestZ2G`FH`
@H
H.`;HB`HS
Hb(HsHfHfHf

HH`@HH`@HHH`fI
`z@IzI/(I@
IU`IeImIvI~I	nsIOSFileConstantsServiceinit2JnsISpeculativeConnectspeculativeConnectspeculativeConnect2speculativeAnonymousConnectspeculativeAnonymousConnect22K?K&?K:?KV?nsIWebSocketListeneronStartonStoponMessageAvailableonBinaryMessageAvailableonAcknowledgeonServerClose2K2K2K2K2L2L 2nsISupportsStringdatadatatoStringGL@LL`IPeerConnectionObserver2nsIPresentationSessionRequestdeviceurlpresentationIdcontrolChannel2M`MMM*`nsIStringBundleServicecreateBundlecreateExtensibleBundleformatStatusMessageflushBundles2M`nM`nM`MnsIStringBundleGetStringFromIDGetStringFromNameGetStringFromNameCppformatStringFromIDformatStringFromNameformatStringFromNameCppgetSimpleEnumeration2N`N!`N3`NH`N[`Np`N`KnsIAccessibleRetrieval7nsIQuotaUsageResultoriginpersistedusage2O?OF`
OP`nsINSSU2FTokennsICancelablecancel2OnsIHttpAuthenticatorCallbackonCredsGenerated2O22nsIUrlClassifierHashCompleterCallbackcompletionV2completionV4completionFinished2P(P5PBnsICertificateDialogsconfirmDownloadCACertsetPKCS12FilePasswordgetPKCS12FilePasswordviewCert2P?@`
P?`
P?`
P?nsICookieManager2addaddNativecookieExistscookieExistsNativecountCookiesFromHostgetCookiesFromHostimportCookiesgetCookiesWithOriginAttributesremoveCookiesWithOriginAttributes	QD	


 QH	


QRL`
 Q_L@
Qr`Q`KQ1Q`KQimgIRequestimageimageStatusimageErrorCodeURIcurrentURInotificationObservermimeTypecloneimagePrincipalmultipartCORSModecancelAndForgetObserverstartDecodingstartDecodingWithResultlockImageunlockImagerequestDiscardgetStaticRequestincrementAnimationConsumersdecrementAnimationConsumersboostPrioritySTATUS_NONESTATUS_SIZE_AVAILABLESTATUS_LOAD_COMPLETESTATUS_ERRORSTATUS_FRAME_COMPLETESTATUS_DECODE_COMPLETESTATUS_IS_ANIMATEDSTATUS_HAS_TRANSPARENCYCORS_NONECORS_ANONYMOUSCORS_USE_CREDENTIALSCATEGORY_FRAME_INITCATEGORY_SIZE_QUERYCATEGORY_DISPLAYR`}R`R`R`R`R`CR`RC`wR`R`
S`S
S"(S0
SHSRS^Sm`wS~SSSSSSTTT5 TH@T`TjTyTTTnsIObserverobserve2V22nsIAsyncShutdownServicemakeBarrierprofileBeforeChangeprofileChangeTeardownquitApplicationGrantedsendTelemetrywebWorkersShutdownxpcomWillShutdown2Vi`րVu`MV`MV`MV`MV`MV`MnsIDOMCSSFontFaceRulestyle2W\`nsISlowScriptDebugRemoteCallbackhandleSlowScriptDebug2W8nsIDumpGCAndCCLogsCallbackonDumponFinish2W11
WnsIWinAppHelperuserCanElevate2X`
nsINavHistoryQuerybeginTimebeginTimebeginTimeReferencebeginTimeReferencehasBeginTimeabsoluteBeginTimeendTimeendTimeendTimeReferenceendTimeReferencehasEndTimeabsoluteEndTimesearchTermssearchTermshasSearchTermsminVisitsminVisitsmaxVisitsmaxVisitssetTransitionsgetTransitionstransitionCountonlyBookmarkedonlyBookmarkeddomainIsHostdomainIsHostdomaindomainhasDomainuriurihasUriannotationIsNotannotationIsNotannotationannotationhasAnnotationtagstagstagsAreNottagsAreNotgetFoldersfolderCountsetFolderscloneTIME_RELATIVE_EPOCHTIME_RELATIVE_TODAYTIME_RELATIVE_NOW2-XR`@X\Xf`@XyX`
X`X`@XX`@XX`
X`X@YY`
Y`@Y)Y3`@Y=YGYVD`Ye`Yu`
@Y
Y`
@Y
Y@YY`
Y`@YY`
Y`
@Y
Y@YZ
`
Z`@ZZ"`
@Z-
Z8D`ZC`ZOZZ`~Z`ZtZnsIUpdatePatchtypetypeURLURLfinalURLfinalURLsizesizestatestateselectedselectedserialize2
\@\\@\\@\\`@\\@\\`
@\
\`nsIMIMEInputStreamaddContentLengthaddContentLengthaddHeadervisitHeaderssetDatadata]`
@]
]]L]]`nsICachingChannelcacheTokencacheTokenofflineCacheTokenofflineCacheTokencacheOnlyMetadatacacheOnlyMetadatapinpinforceCacheEntryValidForLOAD_NO_NETWORK_IOLOAD_CHECK_OFFLINE_CACHELOAD_BYPASS_LOCAL_CACHELOAD_BYPASS_LOCAL_CACHE_IF_BUSYLOAD_ONLY_FROM_CACHELOAD_ONLY_IF_MODIFIEDz	^F`2@^Q2^\`2@^n2^`
@^
^`
@^
^^^^_ _(@_=nsIPresentationLocalDevicewindowId`
nsIDOMGeoPositiontimestampcoords2`9``C`nsIServiceWorkerRegistrationInfoprincipalscopescriptSpeclastUpdateTimeinstallingWorkerwaitingWorkeractiveWorkergetWorkerByIDaddListenerremoveListener2
```````````````Հ`ՀnsISupportsPRBooldatadatatoStringGa`
@a
a`nsILoadInfoloadingPrincipalbinaryLoadingPrincipaltriggeringPrincipalbinaryTriggeringPrincipalprincipalToInheritprincipalToInheritbinaryPrincipalToInheritloadingDocumentbinaryLoadingNodesecurityFlagssecurityModeisInThirdPartyContextcookiePolicyforceInheritPrincipalforceInheritPrincipalOverruleOwnerloadingSandboxedaboutBlankInheritsallowChromedisallowScriptdontFollowRedirectsloadErrorPageexternalContentPolicyTypeinternalContentPolicyTypeupgradeInsecureRequestsverifySignedContentverifySignedContentenforceSRIenforceSRIforceInheritPrincipalDroppedinnerWindowIDouterWindowIDparentOuterWindowIDframeOuterWindowIDresetPrincipalToInheritToNullPrincipaloriginAttributesoriginAttributesbinaryGetOriginAttributesbinarySetOriginAttributesenforceSecurityenforceSecurityinitialSecurityCheckDoneinitialSecurityCheckDoneappendRedirectHistoryEntryredirectChainIncludingInternalRedirectsbinaryRedirectChainIncludingInternalRedirectsredirectChainbinaryRedirectChainsetCorsPreflightInfocorsUnsafeHeadersforcePreflightisPreflightforceHSTSPrimingmixedContentWouldBlockisHSTSPrimingisHSTSPrimingisHSTSPrimingUpgradeisHSTSPrimingUpgradesetHSTSPrimingclearHSTSPrimingtaintingmaybeIncreaseTaintingisTopLevelLoadresultPrincipalURIresultPrincipalURIsandboxedLoadingPrincipalSEC_NORMALSEC_REQUIRE_SAME_ORIGIN_DATA_INHERITSSEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKEDSEC_ALLOW_CROSS_ORIGIN_DATA_INHERITSSEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULLSEC_REQUIRE_CORS_DATA_INHERITSSEC_COOKIES_DEFAULTSEC_COOKIES_INCLUDESEC_COOKIES_SAME_ORIGINSEC_COOKIES_OMITSEC_FORCE_INHERIT_PRINCIPALSEC_SANDBOXEDSEC_ABOUT_BLANK_INHERITSSEC_ALLOW_CHROMESEC_DISALLOW_SCRIPTSEC_DONT_FOLLOW_REDIRECTSSEC_LOAD_ERROR_PAGESEC_FORCE_INHERIT_PRINCIPAL_OVERRULE_OWNERTAINTING_BASICTAINTING_CORSTAINTING_OPAQUE2Aa`(ab`(bb.`@bA(bTbm`(b}b`b`b`
b`b`
b`
c`
c`
c*`
c6`
cE`
cY`
cg`(cc`
c`
@c
c`
@c
c`
d`d`d*`d>`dQdx`Bdd`dd`
@d
d`
@e
e 
e;`(ece`(e(e

(ee`
e`
e`
f`
f`
Hf+
f9`
HfN
(fc

(fr
f`ff`
f`@ff`ffg"gJgoggg g@g`hhh-hFhWhkh h@hhhnsIWebProgressListener2onProgressChange64onRefreshAttemptedlalt
`
nsIXULAppInfovendornameIDversionappBuildIDUANamellllllnsIASN1TreeloadASN1StructuregetDisplayData`mB؀mTnsIColorPickerinitopen2m〹mPextIExtensionsallhasget2m`m`
m`nsIAccessibleparentnextSiblingpreviousSiblingfirstChildlastChildchildrenchildCountindexInParentDOMNodeiddocumentrootDocumentlanguagenamevaluedescriptionaccessKeykeyboardShortcutrolegetStatehelpfocusedChildattributesgroupPositiongetChildAtPointgetDeepestChildAtPointgetChildAtgetRelationByTypegetRelationsgetBoundssetSelectedtakeSelectiontakeFocusactionCountgetActionNamegetActionDescriptiondoActionscrollToscrollToPoint2'n`n`n'`n7`nB`nL`nU`n``nn`nvny`n`nnnnnnn`n@@nn`n`sn@@@o`o`o*`o5`oG`oT@@@@o^
ojoxo`ooooonsIPresentationServicestartSessionsendSessionMessagesendSessionBinaryMsgsendSessionBlobcloseSessionterminateSessionreconnectSessionregisterAvailabilityListenerunregisterAvailabilityListenerregisterSessionListenerunregisterSessionListenerregisterRespondingListenerunregisterRespondingListenernotifyReceiverReadyNotifyTransportCloseduntrackSessionInfogetWindowIdBySessionIdupdateWindowIdBySessionIdbuildTransportROLE_CONTROLLERROLE_RECEIVERCLOSED_REASON_ERRORCLOSED_REASON_CLOSEDCLOSED_REASON_WENTAWAY2q	8rqqqqrrr%"rB"raryrrr
rrrs`ss9sHsXsfszsnsINamedPipeDataObserveronDataAvailableonError2uunsIDNSServiceasyncResolveasyncResolveNativecancelAsyncResolvecancelAsyncResolveNativeresolveresolveNativeasyncResolveExtendedasyncResolveExtendedNativecancelAsyncResolveExtendedcancelAsyncResolveExtendedNativegetDNSCacheEntriesmyHostNameRESOLVE_BYPASS_CACHERESOLVE_CANONICAL_NAMERESOLVE_PRIORITY_MEDIUMRESOLVE_PRIORITY_LOWRESOLVE_SPECULATERESOLVE_DISABLE_IPV6RESOLVE_OFFLINERESOLVE_DISABLE_IPV4RESOLVE_ALLOW_NAME_COLLISION2uLV`r uYV@rul uu`	 u@	uV`r uV@ru uvv-	v8vMvdv|vv v@vvnsIAutoCompleteSearchstartSearchstopSearch2x`xlnsISupportsPRUint8datadatatoStringGx`@xx`nsIWindowMediatorgetEnumeratorgetXULWindowEnumeratorgetZOrderDOMWindowEnumeratorgetZOrderXULWindowEnumeratorgetMostRecentWindowgetOuterWindowWithIdgetCurrentInnerWindowWithIdregisterWindowunregisterWindowupdateWindowTimeStampupdateWindowTitlecalculateZPositionsetZPositiongetZLevelsetZLeveladdListenerremoveListenerzLevelTopzLevelBottomzLevelBelow2x`Ky`Ky
`Ky9
`KyV`yj`y`yfyfyfyfyf,@@,`
yffzf`z
fz)z#)z2z<zInsIDOMEventListenerhandleEvent2{nsIPrivateBrowsingChannelsetPrivateisChannelPrivateisPrivateModeOverriden2{
{`
{@
`
nsISupportsPRUint16datadatatoStringG|*`@|/|4`nsIPushDatatextjsonbinary2|n|s`|xD`nsISupportsPRUint32datadatatoStringG|`@||`nsISAXErrorHandlererrorfatalErrorignorableWarning2}}}nsIClipboardDragDropHooksallowStartDragallowDroponCopyOrDragonPasteOrDrop2}u`
}`
}.`
}.`
nsICertBlocklistrevokeCertByIssuerAndSerialrevokeCertBySubjectAndPubKeysaveEntriesisCertRevokedisBlocklistFresh2~~!~>~J	`
~X`
nsIUpdatetypetypenamenamedisplayVersiondisplayVersionappVersionappVersionpreviousAppVersionpreviousAppVersionbuildIDbuildIDdetailsURLdetailsURLserviceURLserviceURLchannelchannelunsupportedunsupportedpromptWaitTimepromptWaitTimeisCompleteUpdateisCompleteUpdateinstallDateinstallDatestatusTextstatusTextselectedPatchstatestateerrorCodeerrorCodeelevationFailureelevationFailurepatchCountgetPatchAtserialize2&~@~~@~~@~~@@%8@@H@S^@it@|`
@
`@`
@
`@@
`@$`@.8`
@I
Z`e`p`nsIRDFLiteralValueGetValueConst`PinICSSValueSearchdocumentdocumentbaseURLbaseURLreturnRelativeURLsreturnRelativeURLsnormalizeChromeURLsnormalizeChromeURLsaddPropertyCriteriatextCriteriatextCriteria`X`@aj`@rz`
@
`
@
`@nsIDOMHTMLPictureElement2nsIWebBrowserFindInFramescurrentSearchFramecurrentSearchFramerootSearchFramerootSearchFramesearchSubframessearchSubframessearchParentFramessearchParentFrames2`@`@`
@
`
@"
nsIParentChannelsetParentListenernotifyTrackingProtectionDisabledsetClassifierMatchedInfonotifyTrackingResourcedeletejnsISupportsPRUint64datadatatoStringGZ`@_d`nsIRDFDateValue`nsIRDFIntValue`nsIDNSRegistrationListeneronServiceRegisteredonServiceUnregisteredonRegistrationFailedonUnregistrationFailedERROR_SERVICE_NOT_RUNNING2LLL/LFnsIUrlClassifierStreamUpdaterdownloadUpdates2
`
nsIAccessibleTraversalRulepreFiltergetMatchRolesmatchFILTER_IGNOREFILTER_MATCHFILTER_IGNORE_SUBTREEPREFILTER_INVISIBLEPREFILTER_OFFSCREENPREFILTER_NOT_FOCUSABLEPREFILTER_ARIA_HIDDENPREFILTER_TRANSPARENT2`@`*`0>KaunsISupportsPRTimedatadatatoStringGO`@TY`nsIWifiAccessPointmacssidrawSSIDsignal2`nsISupportsChardatadatatoStringG`@`nsIQueryContentEventResultoffsettentativeCaretOffsetreversedlefttopwidthheighttextgetCharacterRectsucceedednotFoundtentativeCaretOffsetNotFound2D`K```
i`n`r`x`@@@@`
`
`
nsISupportsPRInt16datadatatoStringG_`@di`nsISAXAttributesgetIndexFromNamegetIndexFromQNamelengthgetLocalNamegetQNamegetTypegetTypeFromNamegetTypeFromQNamegetURIgetValuegetValueFromNamegetValueFromQName2```!2nsISupportsPRInt32datadatatoStringG`@`nsISupportsPRInt64datadatatoStringG=`@BG`nsIFileInputStreaminitDELETE_ON_CLOSECLOSE_ON_EOFREOPEN_ON_REWINDDEFER_OPENSHARE_DELETE1 nsIContentPrefServicegetPrefsetPrefhasPrefhasCachedPrefremovePrefremoveGroupedPrefsremovePrefsByNamegetPrefsgetPrefsByNameaddObserverremoveObservergrouperDBConnection2
/`7?`
G`
U`s`,`,``mozIRepresentativeColorCallbackonComplete2
nsIDeviceSensorshasWindowListeneraddWindowListenerremoveWindowListenerremoveWindowAsListener2`
ʀ%ʀ:ʀnsIDOMNodeFilteracceptNodeFILTER_ACCEPTFILTER_REJECTFILTER_SKIPSHOW_ALLSHOW_ELEMENTSHOW_ATTRIBUTESHOW_TEXTSHOW_CDATA_SECTIONSHOW_ENTITY_REFERENCESHOW_ENTITYSHOW_PROCESSING_INSTRUCTIONSHOW_COMMENTSHOW_DOCUMENTSHOW_DOCUMENT_TYPESHOW_DOCUMENT_FRAGMENTSHOW_NOTATION2`, 8@TaonsIWebBrowserFindfindNextsearchStringsearchStringfindBackwardsfindBackwardswrapFindwrapFindentireWordentireWordmatchCasematchCasesearchFramessearchFrames2
X`
a`@n{`
@
`
@
`
@
`
@
`
@
nsIUrlClassifierUtilsgetKeyForURIgetProvidergetTelemetryProvidergetProtocolVersionconvertThreatTypeToListNamesconvertListNameToThreatTypemakeUpdateRequestV4makeFindFullHashRequestV4parseFindFullHashResponseV42	`4nsISliderListenervalueChangeddragStateChanged2

nsIPresentationControlChannellistenerlistenersendOffersendAnswersendIceCandidatelaunchterminatedisconnectreconnect2	O`&@X&akvimgILoaderloadImageXPCOMloadImageWithChannelXPCOMLOAD_CORS_ANONYMOUSLOAD_CORS_USE_CREDENTIALS2%递C22`w4C2@j`wNbmozIStorageStatementParams2nsIVersionComparatorcompare2`nsIX509CertValiditynotBeforenotBeforeLocalTimenotBeforeLocalDaynotBeforeGMTnotAfternotAfterLocalTimenotAfterLocalDaynotAfterGMT2K`Uhz`nsIJARChannelisUnsafejarFilejarFilezipEntryensureCached$`
-`1@51=`F`
nsIFileOutputStreaminitpreallocateDEFER_OPENp1nsIDocShellLoadInforeferrerreferreroriginalURIoriginalURIresultPrincipalURIresultPrincipalURIresultPrincipalURIIsSomeresultPrincipalURIIsSomeloadReplaceloadReplacetriggeringPrincipaltriggeringPrincipalinheritPrincipalinheritPrincipalprincipalIsExplicitprincipalIsExplicitloadTypeloadTypeSHEntrySHEntrytargettargetpostDataStreampostDataStreamheadersStreamheadersStreamsendReferrersendReferrerreferrerPolicyreferrerPolicyisSrcdocLoadsrcdocDatasrcdocDatasourceDocShellsourceDocShellbaseURIbaseURIloadNormalloadNormalReplaceloadHistoryloadReloadNormalloadReloadBypassCacheloadReloadBypassProxyloadReloadBypassProxyAndCacheloadLinkloadRefreshloadReloadCharsetChangeloadBypassHistoryloadStopContentloadStopContentAndReplaceloadNormalExternalloadNormalBypassCacheloadNormalBypassProxyloadNormalBypassProxyAndCacheloadPushStateloadReplaceBypassCacheloadReloadMixedContentloadNormalAllowMixedContentloadReloadCharsetChangeBypassCacheloadReloadCharsetChangeBypassProxyAndCache2%`@`@ ,`@?R`
@k
`
@
`@`
@
`
@
`@ `u@(u0`@7>`@M\`@jx`
@
`@`
@`F@F`@*;Qg	

-KYpnsIJSIID/nsIOpenURIInFrameParamsreferrerreferrerisPrivatetriggeringPrincipaltriggeringPrincipalopenerOriginAttributes2@`
`@`nsIDocumentLoaderFactorycreateInstancecreateInstanceForDocument2@逸F2@j` O2` nsILocalFileWininitWithCommandLinegetVersionInfoFieldcanonicalPathnativeCanonicalPathfileAttributesWinfileAttributesWinsetShortcutopenNSPRFileDescShareDeleteWFA_SEARCH_INDEXEDWFA_READONLYWFA_READWRITE`@(1114`PcpnsIWebSocketEventListenerwebSocketCreatedwebSocketOpenedwebSocketMessageAvailablewebSocketClosedframeReceivedframeSentTYPE_STRINGTYPE_BLOBTYPE_ARRAYBUFFER2 1A[
kMyMnsIIOUtilinputStreamIsBufferedoutputStreamIsBuffered2(`
>p`
nsIFaviconServicegetFaviconLinkForIconexpireAllFaviconspreferredSizeFromURIaddFailedFaviconremoveFailedFaviconisFailedFavicondefaultFavicondefaultFaviconMimeTypeFAVICON_LOAD_PRIVATEFAVICON_LOAD_NON_PRIVATEMAX_FAVICON_BUFFER_SIZE2```
`"7PnsIScriptErrorNoteerrorMessagesourceNamelineNumbercolumnNumbertoString2`"`/nsIScreenManagerscreenForRectprimaryScreen2``nsIWebBrowserChromesetStatuswebBrowserwebBrowserchromeFlagschromeFlagsdestroyBrowserWindowsizeBrowserToshowAsModalisWindowModalexitModalEventLoopSTATUS_SCRIPTSTATUS_LINKCHROME_DEFAULTCHROME_WINDOW_BORDERSCHROME_WINDOW_CLOSECHROME_WINDOW_RESIZECHROME_MENUBARCHROME_TOOLBARCHROME_LOCATIONBARCHROME_STATUSBARCHROME_PERSONAL_TOOLBARCHROME_SCROLLBARSCHROME_TITLEBARCHROME_EXTRACHROME_WITH_SIZECHROME_WITH_POSITIONCHROME_WINDOW_MINCHROME_WINDOW_POPUPCHROME_PRIVATE_WINDOWCHROME_NON_PRIVATE_WINDOWCHROME_PRIVATE_LIFETIMECHROME_MODAL_CONTENT_WINDOWCHROME_REMOTE_WINDOWCHROME_SUPPRESS_ANIMATIONCHROME_WINDOW_RAISEDCHROME_WINDOW_LOWEREDCHROME_CENTER_SCREENCHROME_DEPENDENTCHROME_MODALCHROME_OPENAS_DIALOGCHROME_OPENAS_CHROMECHROME_ALL2
`@`@&4@`
N ao{ @#5ERc x@1F\q @nsIFileURLfilefile^`1@c1nsIWindowProviderprovideWindow2



@
`nsIPaymentUIServicecanMakePaymentshowPaymentabortPaymentcompletePaymentupdatePayment2`q`q`q`q`qnsIContentViewerContainerembedsetIsPrinting2 2
nsIHTTPHeaderListenernewResponseHeaderstatusLine2nsIInputStreamChannelsetURIcontentStreamcontentStreamsrcdocDatasrcdocDataisSrcdocChannelbaseURIbaseURI207`@ES@^i`
y`@nsIDOMXULControlElementdisableddisabledtabIndextabIndex2`
@
`@nsIProfilerCanProfileStartProfilerStopProfilerIsPausedPauseSamplingResumeSamplingAddMarkerGetProfilegetProfileDatagetProfileDataAsyncgetProfileDataAsArrayBufferdumpProfileToFileAsyncIsActiveGetFeaturesGetAllFeaturesstartParamsGetBufferInfogetElapsedTimesharedLibrariesdumpProfileToFilereceiveShutdownProfile2a`
l	z`
	`	`	`2	`2	`
!@`-@`<`$H@@@V`	e`u(
nsIRDFXMLSinkObserveronBeginLoadonInterruptonResumeonEndLoadonError2ҀҀҀҀҀnsIRDFXMLSinkreadOnlyreadOnlybeginLoadinterruptresumeendLoadaddNameSpaceaddXMLSinkObserverremoveXMLSinkObserver2	=`
@F
OYcjrррnsIEditorBlobListeneronResultonError2&rdfIDataSourcevisitAllSubjectsvisitAllTriples2XimozIPlacesPendingOperationcancel2nsIDragServiceinvokeDragSessioninvokeDragSessionWithImageinvokeDragSessionWithSelectiongetCurrentSessionstartDragSessionendDragSessionfireDragEventAtSourcesuppressunsuppressdragMovedmaybeAddChildProcessDRAGDROP_ACTION_NONEDRAGDROP_ACTION_COPYDRAGDROP_ACTION_MOVEDRAGDROP_ACTION_LINKDRAGDROP_ACTION_UNINITIALIZED2o	o

)

$`6G
Vlu 
@nsIXULTemplateResultisContainerisEmptymayProcessChildrenidresourcetypegetBindingForgetBindingObjectForruleMatchedhasBeenRemoved2
`
`
`
+.`7<먹J`2^2jnsIFeedProgressListenerreportErrorhandleStartFeedhandleFeedAtFirstEntryhandleEntry
(?<nsITaskbarWindowPreviewgetButtonenableCustomDrawingenableCustomDrawingNUM_TOOLBAR_BUTTONS1`=`
@
nsIPrintSettingsSetPrintOptionsGetPrintOptionsGetPrintOptionsBitsSetPrintOptionsBitsGetEffectivePageSizecloneassignprintSessionprintSessionstartPageRangestartPageRangeendPageRangeendPageRangeedgeTopedgeTopedgeLeftedgeLeftedgeBottomedgeBottomedgeRightedgeRightmarginTopmarginTopmarginLeftmarginLeftmarginBottommarginBottommarginRightmarginRightunwriteableMarginTopunwriteableMarginTopunwriteableMarginLeftunwriteableMarginLeftunwriteableMarginBottomunwriteableMarginBottomunwriteableMarginRightunwriteableMarginRightscalingscalingprintBGColorsprintBGColorsprintBGImagesprintBGImagesprintRangeprintRangetitletitledocURLdocURLheaderStrLeftheaderStrLeftheaderStrCenterheaderStrCenterheaderStrRightheaderStrRightfooterStrLeftfooterStrLeftfooterStrCenterfooterStrCenterfooterStrRightfooterStrRighthowToEnableFrameUIhowToEnableFrameUIisCancelledisCancelledprintFrameTypeUsageprintFrameTypeUsageprintFrameTypeprintFrameTypeprintSilentprintSilentshrinkToFitshrinkToFitshowPrintProgressshowPrintProgresspaperNamepaperNamepaperDatapaperDatapaperWidthpaperWidthpaperHeightpaperHeightpaperSizeUnitpaperSizeUnitprintReversedprintReversedprintInColorprintInColororientationorientationnumCopiesnumCopiesprinterNameprinterNameprintToFileprintToFiletoFileNametoFileNameoutputFormatoutputFormatprintPageDelayprintPageDelayresolutionresolutionduplexduplexisInitializedFromPrinterisInitializedFromPrinterisInitializedFromPrefsisInitializedFromPrefsSetMarginInTwipsSetEdgeInTwipsGetMarginInTwipsGetEdgeInTwipsSetupSilentPrintingSetUnwriteableMarginInTwipsGetUnwriteableMarginInTwipsGetPageRangeskInitSaveOddEvenPageskInitSaveHeaderLeftkInitSaveHeaderCenterkInitSaveHeaderRightkInitSaveFooterLeftkInitSaveFooterCenterkInitSaveFooterRightkInitSaveBGColorskInitSaveBGImageskInitSavePaperSizekInitSaveResolutionkInitSaveDuplexkInitSavePaperDatakInitSaveUnwriteableMarginskInitSaveEdgeskInitSaveReversedkInitSaveInColorkInitSaveOrientationkInitSavePrinterNamekInitSavePrintToFilekInitSaveToFileNamekInitSavePageDelaykInitSaveMarginskInitSaveNativeDatakInitSaveShrinkToFitkInitSaveScalingkInitSaveAllkPrintOddPageskPrintEvenPageskEnableSelectionRBkRangeAllPageskRangeSpecifiedPageRangekRangeSelectionkRangeFocusFramekJustLeftkJustCenterkJustRightkUseInternalDefaultkUseSettingWhenPossiblekPaperSizeNativeDatakPaperSizeDefinedkPaperSizeIncheskPaperSizeMillimeterskPortraitOrientationkLandscapeOrientationkNoFrameskFramesAsIskSelectedFramekEachFrameSepkFrameEnableNonekFrameEnableAllkFrameEnableAsIsAndEachkOutputFormatNativekOutputFormatPSkOutputFormatPDF2w)
9`
I`]q@	@	`ڀڀ`#H#`@`@`	@	`	@	`	@	`	@'	1`	@;	E`	@P	[`	@h	u`	@	`	@	`	@	`	@	`	@*	A`	@I	Q`
@_
m`
@{
`@`@`@`@`@`@`@!/`@?O`@^m`@`
@
`@`@`
@
	`
@
!`
@3
E`@OY`@cm`	@x	`	@	`@`
@
`
@
`@`@`@%1`
@=
I`@T_`@ly`@`@`@`
@
`
@
,;L[o7
 4@I[m @/ D@Xk|->HT_s)7HXpnsIAccessibleCaretMoveEventcaretOffsetE`nsIGIOServicegetMimeTypeFromExtensiongetAppForURISchemegetAppForMimeTypecreateAppFromCommandgetDescriptionForMimeTypeshowURIshowURIForInputorgFreedesktopFileManager1ShowItems2pƉ`*Ɯ`*Ʈ`*nsIHTMLInlineTableEditorinlineTableEditingEnabledinlineTableEditingEnabledshowInlineTableEditingUIhideInlineTableEditingUIdoInlineTableEditingActionrefreshInlineTableEditingUI2ǝ`
@Ƿ
nsITransportEventSinkonTransportStatus2ȓnsIKeyObjectinitKeygetKeyObjgetTypeSYM_KEYHMAC2``nsIBackgroundFileSaverObserveronTargetChangeonSaveComplete2F
1U
nsICommandControllergetCommandStateWithParamsdoCommandWithParamsgetSupportedCommands2ɞɸ@`nsIXPCComponentsBaseinterfacesinterfacesByIDresultsisSuccessCode2(`΀3`B`2J`
nsIThreadPoolListeneronThreadCreatedonThreadShuttingDown2ʥʵnsIContentPermissionTypetypeaccessoptions2`nsIProtocolProxyServiceasyncResolvenewProxyInfonewProxyInfoWithAuthgetFailoverForProxyregisterFilterregisterChannelFilterunregisterFilterunregisterChannelFilterproxyConfigTypeRESOLVE_PREFER_SOCKS_PROXYRESOLVE_IGNORE_URI_SCHEMERESOLVE_PREFER_HTTPS_PROXYRESOLVE_ALWAYS_TUNNELPROXYCONFIG_DIRECTPROXYCONFIG_MANUALPROXYCONFIG_PACPROXYCONFIG_WPADPROXYCONFIG_SYSTEM2	M2V`rZ7`7g	7`7|7`7ː˟˵`		#>Tgz̛̊nsIRequestnameisPendingstatuscancelsuspendresumeloadGrouploadGrouploadFlagsloadFlagsLOAD_REQUESTMASKLOAD_NORMALLOAD_BACKGROUNDLOAD_HTML_OBJECT_DATAINHIBIT_CACHINGINHIBIT_PERSISTENT_CACHINGLOAD_BYPASS_CACHELOAD_FROM_CACHEVALIDATE_ALWAYSVALIDATE_NEVERVALIDATE_ONCE_PER_SESSIONLOAD_ANONYMOUSLOAD_FRESH_CONNECTION2
ͷͼ`
``@`@
(8N^y΋ΛΫκ @nsIRedirectChannelRegistrarregisterChannellinkChannelsgetRegisteredChannelgetParentChannelderegisterChannels2```'`8nsIIdentityCryptoServicegenerateKeyPairbase64UrlEncode2бnsILoadGroupgroupObservergroupObserverdefaultLoadRequestdefaultLoadRequestaddRequestremoveRequestrequestsactiveCountnotificationCallbacksnotificationCallbacksrequestContextIDdefaultLoadFlagsdefaultLoadFlagsuserAgentOverrideCacheuserAgentOverrideCache`@
`@.A怒2L怒2Z`Kc`o`?@х?ћ`Ѭ`@ѽ@nsIWebBrowserSetupsetPropertySETUP_ALLOW_PLUGINSSETUP_ALLOW_JAVASCRIPTSETUP_ALLOW_META_REDIRECTSSETUP_ALLOW_SUBFRAMESSETUP_ALLOW_IMAGESSETUP_FOCUS_DOC_BEFORE_CONTENTSETUP_USE_GLOBAL_HISTORYSETUP_IS_CHROME_WRAPPERSETUP_ALLOW_DNS_PREFETCH2	0Cb{ӓnsIAccessibleStatesSTATE_UNAVAILABLESTATE_SELECTEDSTATE_FOCUSEDSTATE_PRESSEDSTATE_CHECKEDSTATE_MIXEDSTATE_READONLYSTATE_HOTTRACKEDSTATE_DEFAULTSTATE_EXPANDEDSTATE_COLLAPSEDSTATE_BUSYSTATE_FLOATINGSTATE_MARQUEEDSTATE_ANIMATEDSTATE_INVISIBLESTATE_OFFSCREENSTATE_SIZEABLESTATE_MOVEABLESTATE_SELFVOICINGSTATE_FOCUSABLESTATE_SELECTABLESTATE_LINKEDSTATE_TRAVERSEDSTATE_MULTISELECTABLESTATE_EXTSELECTABLESTATE_ALERT_LOWSTATE_ALERT_MEDIUMSTATE_ALERT_HIGHSTATE_PROTECTEDSTATE_HASPOPUPSTATE_REQUIREDSTATE_IMPORTANTSTATE_INVALIDSTATE_CHECKABLEEXT_STATE_SUPPORTS_AUTOCOMPLETIONEXT_STATE_DEFUNCTEXT_STATE_SELECTABLE_TEXTEXT_STATE_EDITABLEEXT_STATE_ACTIVEEXT_STATE_MODALEXT_STATE_MULTI_LINEEXT_STATE_HORIZONTALEXT_STATE_OPAQUEEXT_STATE_SINGLE_LINEEXT_STATE_TRANSIENTEXT_STATE_VERTICALEXT_STATE_STALEEXT_STATE_ENABLEDEXT_STATE_SENSITIVEEXT_STATE_EXPANDABLEEXT_STATE_PINNEDEXT_STATE_CURRENT25$6ESao {@ԊԛԩԸ @ />P` q@~Վդո @*8 Hj|ֺ֖֩ @/BR d@x׍מnsIXPCComponents_InterfacesByID2nsIWifiMonitorstartWatchingstopWatching2nsIXULSortServicesortSORT_COMPARECASESORT_INTEGER2,nsIDOMGetUserMediaSuccessCallbackonSuccess2ڄ2nsIStreamConverterServicecanConvertconvertasyncConvertData2ڻ`
䀐2`j2`jnsIDOMBlob2nsIControllersgetControllerForCommandinsertControllerAtremoveControllerAtgetControllerAtappendControllerremoveControllergetControllerIdgetControllerByIdgetControllerCount2	E`\]\p`\ۃ`\ۓ\ۤ\۵\``\`nsIScriptableUnicodeConverterConvertFromUnicodeFinishConvertToUnicodeconvertFromByteArrayconvertToByteArrayconvertToInputStreamcharsetcharsetisInternalisInternal2
܅ܘܟܰD``@`
@
nsITaskbarPreviewCallbackdone2ݮ2
nsIAlertsIconURIshowAlertWithIconURI2?xnsITreeBoxObjectcolumnsviewviewfocusedfocusedtreeBodyrowHeightrowWidthhorizontalPositionselectionRegiongetFirstVisibleRowgetLastVisibleRowgetPageLengthensureRowIsVisibleensureCellIsVisiblescrollToRowscrollByLinesscrollByPagesscrollToCellscrollToColumnscrollToHorizontalPositioninvalidateinvalidateColumninvalidateRowinvalidateCellinvalidateRangeinvalidateColumnRangegetRowAtgetCellAtgetCoordsForCellItemisCellCroppedrowCountChangedbeginUpdateBatchendUpdateBatchclearStyleAndImageCachesremoveImageCacheEntry2$`"``@'`,`
@4
<`E`O`X`k`o{`ގ`ޠ`ޮ
4?P^m}ߓ`ߜ@@ߦ@@@@߻`
nsIEditorObserverEditActionBeforeEditActionCancelEditAction2nsIProtocolProxyFilterapplyFilter2N倒7`7nsIInterceptedChannelresetInterceptionsynthesizeStatussynthesizeHeaderfinishSynthesizedResponsecancelresponseBodychannelsecureUpgradedChannelURIsetChannelInfointernalContentPolicyTypeconsoleReportCollectorSetLaunchServiceWorkerStartSetLaunchServiceWorkerEndSetDispatchFetchEventStartSetDispatchFetchEventEndSetHandleFetchEventStartSetHandleFetchEventEndSetFinishResponseStartSetFinishSynthesizedResponseEndSetChannelResetEndSaveTimeStampssetReleaseHandle2`p``!`;`Rn#6E2nsIWebBrowserPersistablestartPersistence2\@nsICrashServiceaddCrashPROCESS_TYPE_MAINPROCESS_TYPE_CONTENTPROCESS_TYPE_PLUGINPROCESS_TYPE_GMPLUGINPROCESS_TYPE_GPUCRASH_TYPE_CRASHCRASH_TYPE_HANG2`2nsINativeOSFileErrorCallbackcomplete2nsINavHistoryResultObservernodeInsertednodeRemovednodeMovednodeTitleChangednodeURIChangednodeIconChangednodeHistoryDetailsChangednodeTagsChangednodeKeywordChangednodeAnnotationChangednodeDateAddedChangednodeLastModifiedChangedcontainerStateChangedinvalidateContainersortingChangedbatchingresultresult2w
w

ww



8
H
[
q

ww
`@nsIContentPermissionPromptprompt2̀nsISlowScriptDebugactivationHandleractivationHandlerremoteActivationHandlerremoteActivationHandler2=`@Oa`{@y{nsIPrincipalequalsequalsConsideringDomainhashValueURIdomaindomainsubsumessubsumesConsideringDomainsubsumesConsideringDomainIgnoringFPDcheckMayLoadcspcspensureCSPpreloadCspensurePreloadCSPcspJSONoriginAttributesOriginAttributesReforiginoriginNoSuffixoriginSuffixbaseDomainappIdaddonIduserContextIdprivateBrowsingIdisInIsolatedMozBrowserElementisNullPrincipalisCodebasePrincipalisExpandedPrincipalisSystemPrincipal`
`
```H	`
`
3`
X

e`Him`w```(``
``
:`
J`
^`
r`
nsIDialogParamBlockGetIntSetIntSetNumberStringsGetStringSetStringobjectsobjects2`$`.8`@@nsIAlertsServiceshowPersistentNotificationshowAlertshowAlertNotificationcloseAlert2?x?x
x

nsISlowScriptDebugCallbackhandleSlowScriptDebug2pʀnsIContentPermissionRequestergetVisibilityonVisibilityChangeonVisibilityChange2`@nsITreeColumnstreecountlengthgetFirstColumngetLastColumngetPrimaryColumngetSortedColumngetKeyColumngetColumnForgetNamedColumngetColumnAtinvalidateColumnsrestoreNaturalOrder2
%`*`0`7`F`T`e`u````nsINavHistoryResultTreeViewernodeForTreeIndextreeIndexForNodeINDEX_INVISIBLE`

`nsICacheVisitorvisitDevicevisitEntry2=`
`
nsIMozIconURIiconURLiconURLimageSizeimageSizestockIconiconSizeiconStatecontentTypecontentTypefileExtension
G`Ȁ@OȀW`@aku~@nsIDNSRecordcanonicalNamegetNextAddrgetAddressesgetScriptableNextAddrgetNextAddrAsStringhasMorerewindreportUnusable2*8`D@Q`?g{`
nsIPluginHostreloadPluginsgetPluginTagsclearSiteDatasiteHasDatagetPermissionStringForTypegetPermissionStringForTaggetPluginTagForTypegetStateForTypegetBlocklistStateForTyperegisterFakePlugingetFakePluginunregisterFakePluginFLAG_CLEAR_ALLFLAG_CLEAR_CACHEEXCLUDE_NONEEXCLUDE_DISABLEDEXCLUDE_FAKE2D`B,`
8Sm````e`e
nsIClientAuthDialogschooseCertificate2#?@`
nsISSLStatusserverCertcipherNamekeyLengthsecretKeyLengthprotocolVersioncertificateTransparencyStatusisDomainMismatchisNotValidAtThisTimeisUntrustedisExtendedValidationSSL_VERSION_3TLS_VERSION_1TLS_VERSION_1_1TLS_VERSION_1_2TLS_VERSION_1_3CERTIFICATE_TRANSPARENCY_NOT_APPLICABLECERTIFICATE_TRANSPARENCY_POLICY_COMPLIANTCERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTSCERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS2
e`p{`````
`
`
`
	
&6FV~nsIPresentationRequestUIGluesendRequest2`2nsICacheEntryInfoclientIDdeviceIDkeyfetchCountlastFetchedlastModifiedexpirationTimedataSizeisStreamBased2	``!`,`8`E`T`]`
nsIZipEntrycompressionsizerealSizeCRC32isDirectorylastModifiedTimeisSyntheticpermissions2`````
``
!`nsIDirIndexListeneronIndexAvailableonInformationAvailable2怒2怒2nsIDownloaderinitj1nsIRDFResourceValueValueUTF8GetValueConstInitEqualsStringGetDelegateReleaseDelegate&`,6PDI`
V`binIDOMViewrootNoderootNodeshowAnonymousContentshowAnonymousContentshowSubDocumentsshowSubDocumentsshowWhitespaceNodesshowWhitespaceNodesshowAccessibleNodesshowAccessibleNodeswhatToShowwhatToShowgetNodeFromRowIndexgetRowIndexFromNoderebuild2`@`
@

`
@
/`
@C
W`
@k
`@``nsIProtocolProxyCallbackonProxyAvailable2r7nsIUrlClassifierParseFindFullHashCallbackonCompleteHashFoundonResponseParsed2nsIOpenSignedAppFileCallbackopenSignedAppFileFinished2@=nsIConverterInputStreaminitDEFAULT_REPLACEMENT_CHARACTER^䀐nsIAlertsIconDatashowAlertWithIconData2?xnsIFormPOSTActionChannelnsIFeedTextConstructbasebaselanglangtypetypetexttextplainTextcreateDocumentFragment2
I`@NS@X]@bg@lq{`amIAddonPathServicefindAddonIdinsertPathmapURIToAddonId2'2nsIStatusReporterManagerenumerateReportersregisterReporterunregisterReporterinitdumpReports2`K''mozIThirdPartyUtilisThirdPartyURIisThirdPartyWindowisThirdPartyChannelgetBaseDomaingetURIFromWindowgetTopWindowForChannel2`
.ㄒ`
A`
Uc`t`nsIRequestObserveronStartRequestonStopRequest2	怒2怒2nsIEditorMailSupportpasteAsQuotationinsertAsQuotationinsertTextWithQuotationspasteAsCitedQuotationinsertAsCitedQuotationrewrapstripCitesgetEmbeddedObjects2du`
`
`nsINetUtilparseRequestContentTypeparseResponseContentTypeprotocolHasFlagsURIChainHasFlagstoImmutableURInewSimpleNestedURIescapeStringescapeURLunescapeStringextractCharsetFromContentTypeparseAttributePolicyStringESCAPE_ALLESCAPE_XALPHASESCAPE_XPALPHASESCAPE_URL_PATHESCAPE_URL_SCHEMEESCAPE_URL_USERNAMEESCAPE_URL_PASSWORDESCAPE_URL_HOSTESCAPE_URL_DIRECTORYESCAPE_URL_FILE_BASENAMEESCAPE_URL_FILE_EXTENSIONESCAPE_URL_PARAMESCAPE_URL_QUERYESCAPE_URL_REFESCAPE_URL_FILEPATHESCAPE_URL_MINIMALESCAPE_URL_FORCEDESCAPE_URL_ONLY_ASCIIESCAPE_URL_ONLY_NONASCIIESCAPE_URL_COLONESCAPE_URL_SKIP_CONTROL2b@
z@
`
`
``@@`
`6AP`p @$3pGZl@nsINativeFileWatcherCallbackchanged2MnsICacheEntryMetaDataVisitoronMetaDataElement2nsIAudioDeviceInfonamegroupIdvendortypestatepreferredsupportedFormatdefaultFormatmaxChannelsdefaultRatemaxRateminRatemaxLatencyminLatencyTYPE_UNKNOWNTYPE_INPUTTYPE_OUTPUTSTATE_DISABLEDSTATE_UNPLUGGEDSTATE_ENABLEDPREF_NONEPREF_MULTIMEDIAPREF_VOICEPREF_NOTIFICATIONPREF_ALLFMT_S16LEFMT_S16BEFMT_F32LEFMT_F32BE2````````$`,`7`BOZfu  nsIHttpAuthenticatorchallengeReceivedgenerateCredentialsAsyncgenerateCredentialsauthFlagsUSING_INTERNAL_IDENTITYREQUEST_BASEDCONNECTION_BASEDREUSABLE_CREDENTIALSREUSABLE_CHALLENGEIDENTITY_IGNOREDIDENTITY_INCLUDES_DOMAINIDENTITY_ENCRYPTED2	p
22@
	
ps
22@r	7
p
22@`	K`	U	m	{					nsIAutoCompleteControllerinputinputsearchStatusmatchCountstartSearchstopSearchhandleTexthandleEnterhandleEscapehandleStartCompositionhandleEndCompositionhandleTabhandleKeyNavigationhandleDeletegetValueAtgetLabelAtgetCommentAtgetStyleAtgetImageAtgetFinalCompleteValueAtsearchStringsearchStringsetInitiallySelectedIndexresetInternalStateSTATUS_NONESTATUS_SEARCHINGSTATUS_COMPLETE_NO_MATCHSTATUS_COMPLETE_MATCH2
`@

`
`

`

`
`
%<Q[`
o`
|@ 1JnsIPKCS11ModuleDBgetInternalgetInternalFIPSfindModuleByNamefindSlotByNamelistModulescanToggleFIPStoggleFIPSModeisFIPSEnabled2
`
`
`
`
`K
`


`
nsIStatusReporternameprocessdescription2{PK
!<[3NN*chrome/en-US/locale/en-US/alerts/alert.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY     closeAlert.tooltip          "Close this notification">
<!ENTITY     settings.label              "Settings">
PK
!<qS1chrome/en-US/locale/en-US/alerts/alert.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE(closeButton.title): Used as the close button text for web notifications on OS X.
# This should ideally match the string that OS X uses for the close button on alert-type
# notifications. OS X will truncate the value if it's too long.
closeButton.title = Close
# LOCALIZATION NOTE(actionButton.label): Used as the button label to provide more actions on OS X notifications. OS X will truncate this if it's too long.
actionButton.label = …
# LOCALIZATION NOTE(webActions.disableForOrigin.label): %S is replaced
# with the hostname origin of the notification.
webActions.disableForOrigin.label = Disable notifications from %S

# LOCALIZATION NOTE(source.label): Used to show the URL of the site that
# sent the notification (e.g., "via mozilla.org"). "%1$S" is the source host
# and port.
source.label=via %1$S
webActions.settings.label = Notification settings

# LOCALIZATION NOTE(doNotDisturb.label): %S is replaced with the
# brandShortName of the application.
doNotDisturb.label = Do not disturb me until I restart %S
PK
!<
9..:chrome/en-US/locale/en-US/autoconfig/autoconfig.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

readConfigTitle = Configuration Error
readConfigMsg = Failed to read the configuration file. Please contact your system administrator.

autoConfigTitle = AutoConfig Alert
autoConfigMsg = Netscape.cfg/AutoConfig failed. Please contact your system administrator. \n Error: %S failed:

emailPromptTitle = Email Address
emailPromptMsg = Enter your email address
PK
!<b4q.q.4chrome/en-US/locale/en-US/global/AccessFu.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

# Screen reader started/stopped
screenReaderStarted = Screen reader started
screenReaderStopped = Screen reader stopped

# Roles
menubar        =       menu bar
scrollbar      =       scroll bar
grip           =       grip
alert          =       alert
menupopup      =       menu popup
document       =       document
pane           =       pane
dialog         =       dialog
separator      =       separator
toolbar        =       toolbar
statusbar      =       status bar
table          =       table
columnheader   =       column header
rowheader      =       row header
column         =       column
row            =       row
cell           =       cell
link           =       link
list           =       list
listitem       =       list item
outline        =       outline
outlineitem    =       outline item
pagetab        =       tab
propertypage   =       property page
graphic        =       graphic
switch         =       switch
pushbutton     =       button
checkbutton    =       check button
radiobutton    =       radio button
combobox       =       combo box
progressbar    =       progress bar
slider         =       slider
spinbutton     =       spin button
diagram        =       diagram
animation      =       animation
equation       =       equation
buttonmenu     =       button menu
whitespace     =       white space
pagetablist    =       tab list
canvas         =       canvas
checkmenuitem  =       check menu item
label          =       label
passwordtext   =       password text
radiomenuitem  =       radio menu item
textcontainer  =       text container
togglebutton   =       toggle button
treetable      =       tree table
header         =       header
footer         =       footer
paragraph      =       paragraph
entry          =       entry
caption        =       caption
heading        =       heading
section        =       section
form           =       form
comboboxlist   =       combo box list
comboboxoption =       combo box option
imagemap       =       image map
listboxoption  =       option
listbox        =       list box
flatequation   =       flat equation
gridcell       =       gridcell
note           =       note
figure         =       figure
definitionlist =       definition list
term           =       term
definition     =       definition

mathmltable              = math table
mathmlcell               = cell
mathmlenclosed           = enclosed
mathmlfraction           = fraction
mathmlfractionwithoutbar = fraction without bar
mathmlroot               = root
mathmlscripted           = scripted
mathmlsquareroot         = square root

# More sophisticated roles which are not actual numeric roles
textarea       =       text area

base           =       base
close-fence    =       closing fence
denominator    =       denominator
numerator      =       numerator
open-fence     =       opening fence
overscript     =       overscript
presubscript   =       presubscript
presuperscript =       presuperscript
root-index     =       root index
subscript      =       subscript
superscript    =       superscript
underscript    =       underscript

# Text input types
textInputType_date   =       date
textInputType_email  =       e-mail
textInputType_search =       search
textInputType_tel    =       telephone
textInputType_url    =       URL

# More sophisticated object descriptions
headingLevel   =       heading level %S

# more sophisticated list announcement
listStart      =       First item
listEnd        =       Last item
# LOCALIZATION NOTE (listItemsCount): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
listItemsCount =       1 item;#1 items

# LOCALIZATION NOTE: # %1$S is the position of the item n the set.
# %2$S is the total number of such items in the set.
# An expanded example would read "2 of 5".
objItemOfN      =       %1$S of %2$S

# Landmark announcements
banner         =       banner
complementary  =       complementary
contentinfo    =       content info
main           =       main
navigation     =       navigation
search         =       search

# LOCALIZATION NOTE (tblColumnInfo): Semi-colon list of plural forms.
# Number of columns within the table.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
tblColumnInfo = with 1 column;with #1 columns
# LOCALIZATION NOTE (tblRowInfo): Semi-colon list of plural forms.
# Number of rows within the table or grid.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
tblRowInfo = and 1 row;and #1 rows

# table or grid cell information
columnInfo = Column %S
rowInfo = Row %S
spansColumns = spans %S columns
spansRows = spans %S rows

# Invoked actions
jumpAction     =      jumped
pressAction    =      pressed
checkAction    =      checked
uncheckAction  =      unchecked
onAction       =      on
offAction      =      off
selectAction   =      selected
unselectAction =      unselected
openAction     =      opened
closeAction    =      closed
switchAction   =      switched
clickAction    =      clicked
collapseAction =      collapsed
expandAction   =      expanded
activateAction =      activated
cycleAction    =      cycled

# Live regions
# 'hidden' will be spoken when something disappears in a live region.
hidden         =      hidden

# Tab states
tabLoading     =      loading
tabLoaded      =      loaded
tabNew         =      new tab
tabLoadStopped =      loading stopped
tabReload      =      reloading

# Object states
stateChecked     =    checked
stateOn          =    on
stateNotChecked  =    not checked
stateOff         =    off
statePressed     =    pressed
# No string for a not pressed toggle button
stateExpanded    =    expanded
stateCollapsed   =    collapsed
stateUnavailable =    unavailable
stateReadonly    =    readonly
stateRequired    =    required
stateTraversed   =    visited
stateHasPopup    =    has pop up
stateSelected    =    selected

# App modes
editingMode    =      editing
navigationMode =      navigating

# Quick navigation modes
quicknav_Simple      = Default
quicknav_Anchor      = Anchors
quicknav_Button      = Buttons
quicknav_Combobox    = Combo boxes
quicknav_Landmark    = Landmarks
quicknav_Entry       = Entries
quicknav_FormElement = Form elements
quicknav_Graphic     = Images
quicknav_Heading     = Headings
quicknav_ListItem    = List items
quicknav_Link        = Links
quicknav_List        = Lists
quicknav_PageTab     = Page tabs
quicknav_RadioButton = Radio buttons
quicknav_Separator   = Separators
quicknav_Table       = Tables
quicknav_Checkbox    = Check boxes

# MathML menclose notations.
# See developer.mozilla.org/docs/Web/MathML/Element/menclose#attr-notation
notation-longdiv            = long division
notation-actuarial          = actuarial
notation-phasorangle        = phasor angle
notation-radical            = radical
notation-box                = box
notation-roundedbox         = rounded box
notation-circle             = circle
notation-left               = left
notation-right              = right
notation-top                = top
notation-bottom             = bottom
notation-updiagonalstrike   = up diagonal strike
notation-downdiagonalstrike = down diagonal strike
notation-verticalstrike     = vertical strike
notation-horizontalstrike   = horizontal strike
notation-updiagonalarrow    = up diagonal arrow
notation-madruwb            = madruwb

# Shortened role names for braille
menubarAbbr        =       menu bar
scrollbarAbbr      =       scroll bar
gripAbbr           =       grip
alertAbbr          =       alert
menupopupAbbr      =       menu popup
documentAbbr       =       document
paneAbbr           =       pane
dialogAbbr         =       dialog
separatorAbbr      =       separator
toolbarAbbr        =       toolbar
statusbarAbbr      =       status bar
tableAbbr          =       tbl
columnheaderAbbr   =       column header
rowheaderAbbr      =       row header
columnAbbr         =       column
rowAbbr            =       row
cellAbbr           =       cell
linkAbbr           =       lnk
listAbbr           =       list
listitemAbbr       =       list item
outlineAbbr        =       outline
outlineitemAbbr    =       outline item
pagetabAbbr        =       tab
propertypageAbbr   =       property page
graphicAbbr        =       graphic
pushbuttonAbbr     =       btn
checkbuttonAbbr    =       check button
radiobuttonAbbr    =       radio button
comboboxAbbr       =       combo box
progressbarAbbr    =       progress bar
sliderAbbr         =       slider
spinbuttonAbbr     =       spin button
diagramAbbr        =       diagram
animationAbbr      =       animation
equationAbbr       =       equation
buttonmenuAbbr     =       button menu
whitespaceAbbr     =       white space
pagetablistAbbr    =       tab list
canvasAbbr         =       canvas
checkmenuitemAbbr  =       check menu item
labelAbbr          =       label
passwordtextAbbr   =       passwdtxt
radiomenuitemAbbr  =       radio menu item
textcontainerAbbr  =       text container
togglebuttonAbbr   =       toggle button
treetableAbbr      =       tree table
headerAbbr         =       header
footerAbbr         =       footer
paragraphAbbr      =       paragraph
entryAbbr          =       entry
captionAbbr        =       caption
headingAbbr        =       heading
sectionAbbr        =       section
formAbbr           =       form
comboboxlistAbbr   =       combo box list
comboboxoptionAbbr =       combo box option
imagemapAbbr       =       imgmap
listboxoptionAbbr  =       option
listboxAbbr        =       list box
flatequationAbbr   =       flat equation
gridcellAbbr       =       gridcell
noteAbbr           =       note
figureAbbr         =       fig
definitionlistAbbr =       definition list
termAbbr           =       term
definitionAbbr     =       definition
textareaAbbr       =       txtarea

# LOCALIZATION NOTE (tblColumnInfoAbbr): Semi-colon list of plural forms.
# Number of columns within the table.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
tblColumnInfoAbbr = #1c;#1c
# LOCALIZATION NOTE (tblRowInfoAbbr): Semi-colon list of plural forms.
# Number of rows within the table or grid.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
tblRowInfoAbbr = #1r;#1r
cellInfoAbbr = c%Sr%S

stateCheckedAbbr = (x)
stateUncheckedAbbr = ( )
statePressedAbbr = (x)
stateUnpressedAbbr = ( )

mathmlenclosedAbbr           = enclosed
mathmltableAbbr              = tbl
mathmlcellAbbr               = cell
mathmlfractionAbbr           = frac
mathmlfractionwithoutbarAbbr = frac no bar
mathmlrootAbbr               = root
mathmlscriptedAbbr           = scripted
mathmlsquarerootAbbr         = sqrt

baseAbbr           = base
close-fenceAbbr    = close
denominatorAbbr    = den
numeratorAbbr      = num
open-fenceAbbr     = open
overscriptAbbr     = over
presubscriptAbbr   = presub
presuperscriptAbbr = presup
root-indexAbbr     = index
subscriptAbbr      = sub
superscriptAbbr    = sup
underscriptAbbr    = under

notation-longdivAbbr            = longdiv
notation-actuarialAbbr          = act
notation-phasorangleAbbr        = phasang
notation-radicalAbbr            = rad
notation-boxAbbr                = box
notation-roundedboxAbbr         = rndbox
notation-circleAbbr             = circ
notation-leftAbbr               = lft
notation-rightAbbr              = rght
notation-topAbbr                = top
notation-bottomAbbr             = bot
notation-updiagonalstrikeAbbr   = updiagstrike
notation-downdiagonalstrikeAbbr = dwndiagstrike
notation-verticalstrikeAbbr     = vstrike
notation-horizontalstrikeAbbr   = hstrike
notation-updiagonalarrowAbbr    = updiagarrow
notation-madruwbAbbr            = madruwb
PK
!<t}ee*chrome/en-US/locale/en-US/global/about.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY about.version                "version">

<!-- LOCALIZATION NOTE (about.credits.beforeLink): note that there is no space between this phrase and the linked about.credits.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
<!ENTITY about.credits.beforeLink     "See a list of ">
<!ENTITY about.credits.linkTitle      "contributors">
<!-- LOCALIZATION NOTE (about.credits.afterLink): note that there is no space between the linked about.credits.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
<!ENTITY about.credits.afterLink      " to the Mozilla Project.">

<!-- LOCALIZATION NOTE (about.license.beforeTheLink): note that there is no space between this phrase and the linked about.license.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
<!ENTITY about.license.beforeTheLink  "Read the ">
<!ENTITY about.license.linkTitle      "licensing information">
<!-- LOCALIZATION NOTE (about.license.afterTheLink): note that there is no space between the linked about.license.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
<!ENTITY about.license.afterTheLink   " for this product.">

<!-- LOCALIZATION NOTE (about.relnotes.beforeTheLink): note that there is no space between this phrase and the linked about.relnotes.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
<!ENTITY about.relnotes.beforeTheLink "Read the ">
<!ENTITY about.relnotes.linkTitle     "release notes">
<!-- LOCALIZATION NOTE (about.relnotes.afterTheLink): note that there is no space between the linked about.relnotes.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
<!ENTITY about.relnotes.afterTheLink  " for this version.">

<!-- LOCALIZATION NOTE (about.buildconfig.beforeTheLink): note that there is no space between this phrase and the linked about.buildconfig.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
<!ENTITY about.buildconfig.beforeTheLink "See the ">
<!ENTITY about.buildconfig.linkTitle     "build configuration">
<!-- LOCALIZATION NOTE (about.buildconfig.afterTheLink): note that there is no space between the linked about.buildconfig.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
<!ENTITY about.buildconfig.afterTheLink  " used for this version.">

<!ENTITY about.buildIdentifier        "Build identifier: ">
PK
!<v+/chrome/en-US/locale/en-US/global/aboutAbout.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY aboutAbout.title  "About About">
<!ENTITY aboutAbout.note   "This is a list of “about” pages for your convenience.<br/>
                            Some of them might be confusing. Some are for diagnostic purposes only.<br/>
                            And some are omitted because they require query strings.">
PK
!<Ft4chrome/en-US/locale/en-US/global/aboutNetworking.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY aboutNetworking.title                 "About Networking">
<!ENTITY aboutNetworking.warning               "This is very experimental. Do not use without adult supervision.">
<!ENTITY aboutNetworking.showNextTime          "Show this warning next time">
<!ENTITY aboutNetworking.ok                    "OK">
<!ENTITY aboutNetworking.HTTP                  "HTTP">
<!ENTITY aboutNetworking.sockets               "Sockets">
<!ENTITY aboutNetworking.dns                   "DNS">
<!ENTITY aboutNetworking.websockets            "WebSockets">
<!ENTITY aboutNetworking.refresh               "Refresh">
<!ENTITY aboutNetworking.autoRefresh           "Autorefresh every 3 seconds">
<!ENTITY aboutNetworking.hostname              "Hostname">
<!ENTITY aboutNetworking.port                  "Port">
<!ENTITY aboutNetworking.http2                 "HTTP/2">
<!ENTITY aboutNetworking.ssl                   "SSL">
<!ENTITY aboutNetworking.active                "Active">
<!ENTITY aboutNetworking.idle                  "Idle">
<!ENTITY aboutNetworking.host                  "Host">
<!ENTITY aboutNetworking.tcp                   "TCP">
<!ENTITY aboutNetworking.sent                  "Sent">
<!ENTITY aboutNetworking.received              "Received">
<!ENTITY aboutNetworking.family                "Family">
<!ENTITY aboutNetworking.addresses             "Addresses">
<!ENTITY aboutNetworking.expires               "Expires (Seconds)">
<!ENTITY aboutNetworking.messagesSent          "Messages Sent">
<!ENTITY aboutNetworking.messagesReceived      "Messages Received">
<!ENTITY aboutNetworking.bytesSent             "Bytes Sent">
<!ENTITY aboutNetworking.bytesReceived         "Bytes Received">
<!ENTITY aboutNetworking.logging               "Logging">
<!ENTITY aboutNetworking.logTutorial           "See <a href='https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'>HTTP Logging</a> for instructions on how to use this tool.">
<!ENTITY aboutNetworking.currentLogFile        "Current Log File:">
<!ENTITY aboutNetworking.currentLogModules     "Current Log Modules:">
<!ENTITY aboutNetworking.setLogFile            "Set Log File">
<!ENTITY aboutNetworking.setLogModules         "Set Log Modules">
<!ENTITY aboutNetworking.startLogging          "Start Logging">
<!ENTITY aboutNetworking.stopLogging           "Stop Logging">
<!ENTITY aboutNetworking.dnsLookup             "DNS Lookup">
<!ENTITY aboutNetworking.dnsLookupButton       "Resolve">
<!ENTITY aboutNetworking.dnsDomain             "Domain">
<!ENTITY aboutNetworking.dnsLookupTableColumn  "IPs">
<!ENTITY aboutNetworking.rcwn                  "RCWN Stats">
<!ENTITY aboutNetworking.rcwnStatus            "RCWN Status">
<!ENTITY aboutNetworking.rcwnCacheWonCount     "Cache won count">
<!ENTITY aboutNetworking.rcwnNetWonCount       "Net won count">
<!ENTITY aboutNetworking.totalNetworkRequests  "Total network request count">
<!ENTITY aboutNetworking.rcwnOperation         "Cache Operation">
<!ENTITY aboutNetworking.rcwnPerfOpen          "Open">
<!ENTITY aboutNetworking.rcwnPerfRead          "Read">
<!ENTITY aboutNetworking.rcwnPerfWrite         "Write">
<!ENTITY aboutNetworking.rcwnPerfEntryOpen     "Entry Open">
<!ENTITY aboutNetworking.rcwnAvgShort          "Short Average">
<!ENTITY aboutNetworking.rcwnAvgLong           "Long Average">
<!ENTITY aboutNetworking.rcwnStddevLong        "Long Standard Deviation">
<!ENTITY aboutNetworking.rcwnCacheSlow         "Cache slow count">
<!ENTITY aboutNetworking.rcwnCacheNotSlow      "Cache not slow count">
PK
!<[GIŨ2chrome/en-US/locale/en-US/global/aboutProfiles.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY aboutProfiles.title "About Profiles">
<!ENTITY aboutProfiles.subtitle "This page helps you to manage your profiles. Each profile is a separate world which contains separate history, bookmarks, settings and add-ons.">
<!ENTITY aboutProfiles.create "Create a New Profile">
<!ENTITY aboutProfiles.restart.title "Restart">
<!ENTITY aboutProfiles.restart.inSafeMode "Restart with Add-ons Disabled…">
<!ENTITY aboutProfiles.restart.normal "Restart normally…">
PK
!<'zII9chrome/en-US/locale/en-US/global/aboutProfiles.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

name = Profile: %S
isDefault = Default Profile
rootDir = Root Directory
# LOCALIZATION NOTE: localDir is used to show the directory corresponding to
# the main profile directory that exists for the purpose of storing data on the
# local filesystem, including cache files or other data files that may not
# represent critical user data. (e.g., this directory may not be included as
# part of a backup scheme.)
# In case localDIr and rootDir are equal, localDir is not shown.
localDir = Local Directory
currentProfile = This is the profile in use and it cannot be deleted.

rename = Rename
remove = Remove
setAsDefault = Set as default profile
launchProfile = Launch profile in new browser

yes = yes
no = no

renameProfileTitle = Rename Profile
renameProfile = Rename profile %S

invalidProfileNameTitle = Invalid profile name
invalidProfileName = The profile name “%S” is not allowed.

deleteProfileTitle = Delete Profile
deleteProfileConfirm = Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\nYou may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “%S” and cannot be undone.\nWould you like to delete the profile data files?
deleteFiles = Delete Files
dontDeleteFiles = Don’t Delete Files

openDir = Open Directory
# LOCALIZATION NOTE (macOpenDir): This is the Mac-specific variant of openDir.
# This allows us to use the preferred"Finder" terminology on Mac.
macOpenDir = Show in Finder
# LOCALIZATION NOTE (winOpenDir2): This is the Windows-specific variant of
# openDir.
winOpenDir2 = Open Folder
PK
!<m}7chrome/en-US/locale/en-US/global/aboutReader.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#LOCALIZATION NOTE (aboutReader.loading2):
# Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
aboutReader.loading2=Loading…
aboutReader.loadError=Failed to load article from page

aboutReader.colorScheme.light=Light
aboutReader.colorScheme.dark=Dark
aboutReader.colorScheme.sepia=Sepia
aboutReader.colorScheme.auto=Auto

# LOCALIZATION NOTE (aboutReader.estimatedReadTimeValue1): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of minutes it is estimated to take to read the article
# example: `3 minutes`
aboutReader.estimatedReadTimeValue1=#1 minute;#1 minutes

#LOCALIZATION NOTE (aboutReader.estimatedReadingTimeRange1): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# When there is some uncertainty in how long the article will take to read show a range of
# minutes it is expected to take.
# #1 is the number of minutes it is estimated to take to read the article for a fast reader
# #2 is the number of minutes it is estimated to take to read the article for a slow reader
# #2 is the variable used to determine the plural form to use.
# example: `5-8 minutes`
aboutReader.estimatedReadTimeRange1=#1-#2 minute;#1-#2 minutes

# LOCALIZATION NOTE (aboutReader.fontType.serif, aboutReader.fontType.sans-serif):
# These are the styles of typeface that are options in the reader view controls.
aboutReader.fontType.serif=Serif
aboutReader.fontType.sans-serif=Sans-serif

# LOCALIZATION NOTE (aboutReader.fontTypeSample): String used to sample font types.
aboutReader.fontTypeSample=Aa

aboutReader.toolbar.close=Close Reader View
aboutReader.toolbar.typeControls=Type controls

# These are used for the Reader View toolbar button and the menuitem within the
# View menu.
readerView.enter=Enter Reader View
readerView.enter.accesskey=R
readerView.close=Close Reader View
readerView.close.accesskey=R
PK
!<sϜ##0chrome/en-US/locale/en-US/global/aboutRights.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- rights.locale-direction instead of the usual local.dir entity, so RTL can skip translating page. -->
<!ENTITY rights.locale-direction "ltr">
<!ENTITY rights.title "About Your Rights">
<!ENTITY rights.intro "&brandFullName; is free and open source software, built by a community of thousands from all over the world. There are a few things you should know:">

<!-- Note on pointa / pointb / pointc form:
     These points each have an embedded link in the HTML, so each point is
     split into chunks for text before the link, the link text, and the text
     after the link. If a localized grammar doesn't need the before or after
     chunk, it can be left blank.

     Also note the leading/trailing whitespace in strings here, which is
     deliberate for formatting around the embedded links. -->
<!ENTITY rights.intro-point1a "&brandShortName; is made available to you under the terms of the ">
<!ENTITY rights.intro-point1b "Mozilla Public License">
<!ENTITY rights.intro-point1c ". This means you may use, copy and distribute &brandShortName; to others.  You are also welcome to modify the source code of &brandShortName; as you want to meet your needs. The Mozilla Public License also gives you the right to distribute your modified versions.">

<!ENTITY rights.intro-point2-a "You are not granted any trademark rights or licenses to the trademarks of the Mozilla Foundation or any party, including without limitation the Firefox name or logo. Additional information on trademarks may be found ">
<!ENTITY rights.intro-point2-b "here">
<!ENTITY rights.intro-point2-c ".">

<!-- point 2.5 text for official branded builds -->
<!ENTITY rights.intro-point2.5 "Some features in &brandShortName;, such as the Crash Reporter, give you the option to provide feedback to &vendorShortName;. By choosing to submit feedback, you give &vendorShortName; permission to use the feedback to improve its products, to publish the feedback on its websites, and to distribute the feedback.">

<!-- point 3 text for official branded builds -->
<!ENTITY rights2.intro-point3a "How we use your personal information and feedback submitted to &vendorShortName; through &brandShortName; is described in the ">
<!ENTITY rights2.intro-point3b "&brandShortName; Privacy Policy">
<!ENTITY rights.intro-point3c ".">

<!-- point 3 text for unbranded builds -->
<!ENTITY rights.intro-point3-unbranded "Any applicable privacy policies for this product should be listed here.">

<!-- point 4 text for official branded builds -->
<!ENTITY rights2.intro-point4a "Some &brandShortName; features make use of web-based information services, however, we cannot guarantee they are 100&#37; accurate or error-free. More details, including information on how to disable the features that use these services, can be found in the ">
<!ENTITY rights.intro-point4b "service terms">
<!ENTITY rights.intro-point4c ".">

<!-- point 4 text for unbranded builds -->
<!ENTITY rights.intro-point4a-unbranded "If this product incorporates web services, any applicable service terms for the service(s) should be linked to the ">
<!ENTITY rights.intro-point4b-unbranded "Website Services">
<!ENTITY rights.intro-point4c-unbranded " section.">

<!ENTITY rights2.webservices-header "&brandFullName; Web-Based Information Services">

<!-- point 5 -->
<!ENTITY rights.intro-point5 "In order to play back certain types of video content, &brandShortName; downloads certain content decryption modules from third parties.">

<!-- Note that this paragraph references a couple of entities from
     preferences/security.dtd, so that we can refer to text the user sees in
     the UI, without this page being forgotten every time those strings are
     updated.  -->
<!-- intro paragraph for branded builds -->
<!ENTITY rights2.webservices-a "&brandFullName; uses web-based information services (&quot;Services&quot;) to provide some of the features provided for your use with this binary version of &brandShortName; under the terms described below. If you do not want to use one or more of the Services or the terms below are unacceptable, you may disable the feature or Service(s). Instructions on how to disable a particular feature or Service may be found ">
<!ENTITY rights2.webservices-b "here">
<!ENTITY rights3.webservices-c ". Other features and Services can be disabled in the application preferences.">

<!-- safe browsing points for branded builds -->
<!ENTITY rights.safebrowsing-a "SafeBrowsing: ">
<!ENTITY rights.safebrowsing-b "Disabling the Safe Browsing feature is not recommended as it may result in you going to unsafe sites.  If you wish to disable the feature completely, follow these steps:">
<!ENTITY rights.safebrowsing-term1 "Open the application preferences">
<!ENTITY rights.safebrowsing-term2 "Select the Security selection">
<!ENTITY rights2.safebrowsing-term3 "Uncheck the option to &quot;&enableSafeBrowsing.label;&quot;">
<!ENTITY rights.safebrowsing-term4 "Safe Browsing is now disabled">

<!-- location aware browsing points for branded builds -->
<!ENTITY rights.locationawarebrowsing-a "Location Aware Browsing: ">
<!ENTITY rights.locationawarebrowsing-b "is always opt-in.  No location information is ever sent without your permission.  If you wish to disable the feature completely, follow these steps:">
<!ENTITY rights.locationawarebrowsing-term1a "In the URL bar, type ">
<!ENTITY rights.locationawarebrowsing-term1b "about:config">
<!ENTITY rights.locationawarebrowsing-term2 "Type geo.enabled">
<!ENTITY rights.locationawarebrowsing-term3 "Double click on the geo.enabled preference">
<!ENTITY rights.locationawarebrowsing-term4 "Location-Aware Browsing is now disabled">

<!-- intro paragraph for unbranded builds -->
<!ENTITY rights.webservices-unbranded "An overview of the website services the product incorporates, along with instructions on how to disable them, if applicable, should be included here.">

<!-- point 1 text for unbranded builds -->
<!ENTITY rights.webservices-term1-unbranded "Any applicable service terms for this product should be listed here.">

<!-- points 1-7 text for branded builds -->
<!ENTITY rights2.webservices-term1 "&vendorShortName; and its contributors, licensors and partners work to provide the most accurate and up-to-date Services.  However, we cannot guarantee that this information is comprehensive and error-free.  For example, the Safe Browsing Service may not identify some risky sites and may identify some safe sites in error and the Location Aware Service all locations returned by our service providers are estimates only and neither we nor our service providers guarantee the accuracy of the locations provided.">
<!ENTITY rights.webservices-term2 "&vendorShortName; may discontinue or change the Services at its discretion.">
<!ENTITY rights2.webservices-term3 "You are welcome to use these Services with the accompanying version of &brandShortName;, and &vendorShortName; grants you its rights to do so.  &vendorShortName; and its licensors reserve all other rights in the Services.  These terms are not intended to limit any rights granted under open source licenses applicable to &brandShortName; and to corresponding source code versions of &brandShortName;.">
<!ENTITY rights.webservices-term4 "The Services are provided &quot;as-is.&quot;  &vendorShortName;, its contributors, licensors, and distributors, disclaim all warranties, whether express or implied, including without limitation, warranties that the Services are merchantable and fit for your particular purposes.  You bear the entire risk as to selecting the Services for your purposes and as to the quality and performance of the Services. Some jurisdictions do not allow the exclusion or limitation of implied warranties, so this disclaimer may not apply to you.">
<!ENTITY rights.webservices-term5 "Except as required by law, &vendorShortName;, its contributors, licensors, and distributors will not be liable for any indirect, special, incidental, consequential, punitive, or exemplary damages arising out of or in any way relating to the use of &brandShortName; and the Services.  The collective liability under these terms will not exceed $500 (five hundred dollars). Some jurisdictions do not allow the exclusion or limitation of certain damages, so this exclusion and limitation may not apply to you.">
<!ENTITY rights.webservices-term6 "&vendorShortName; may update these terms as necessary from time to time. These terms may not be modified or canceled without &vendorShortName;'s written agreement.">
<!ENTITY rights.webservices-term7 "These terms are governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions. If any portion of these terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these terms and the English language version, the English language version shall control.">
PK
!<uEnn8chrome/en-US/locale/en-US/global/aboutServiceWorkers.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
<!ENTITY aboutServiceWorkers.title                     "About Service Workers">
<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
<!ENTITY aboutServiceWorkers.maintitle                 "Registered Service Workers">
<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
<!ENTITY aboutServiceWorkers.warning_not_enabled       "Service Workers are not enabled.">
<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
<!ENTITY aboutServiceWorkers.warning_no_serviceworkers "No Service Workers registered.">
PK
!<Pt>?chrome/en-US/locale/en-US/global/aboutServiceWorkers.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

title = Origin: %S

# LOCALIZATION NOTE: %1$S is brandShortName, %2$2 is the application ID, and $%$3 is true/false value.
# LOCALIZATION NOTE: the term "InBrowserElement" should not be translated
b2gtitle = %1$S Application ID %2$S - InBrowserElement %3$S

scope = Scope:

scriptSpec = Script Spec:

# LOCALIZATION NOTE: the term "Worker" should not be translated.
currentWorkerURL = Current Worker URL:

activeCacheName = Active Cache Name:

waitingCacheName = Waiting Cache Name:

true = true

false = false

# LOCALIZATION NOTE this term is used as a button label (verb, not noun).
update = Update

unregister = Unregister

waiting = Waiting…

# LOCALIZATION NOTE: the term "Service Worker" should not translated.
unregisterError = Failed to unregister this Service Worker.

pushEndpoint = Push Endpoint:
PK
!<Ӥ&91chrome/en-US/locale/en-US/global/aboutSupport.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY aboutSupport.pageTitle "Troubleshooting Information">

<!-- LOCALIZATION NOTE (aboutSupport.pageSubtitle): don't change the 'supportLink' id. -->
<!ENTITY aboutSupport.pageSubtitle "
  This page contains technical information that might be useful when you’re
  trying to solve a problem. If you are looking for answers to common questions
  about &brandShortName;, check out our <a id='supportLink'>support website</a>.
">

<!ENTITY aboutSupport.crashes.title "Crash Reports">
<!-- LOCALIZATION NOTE (aboutSupport.crashes.id):
This is likely the same like id.heading in crashes.dtd. -->
<!ENTITY aboutSupport.crashes.id "Report ID">
<!ENTITY aboutSupport.crashes.sendDate "Submitted">
<!ENTITY aboutSupport.crashes.allReports "All Crash Reports">
<!ENTITY aboutSupport.crashes.noConfig "This application has not been configured to display crash reports.">

<!ENTITY aboutSupport.extensionsTitle "Extensions">
<!ENTITY aboutSupport.extensionName "Name">
<!ENTITY aboutSupport.extensionEnabled "Enabled">
<!ENTITY aboutSupport.extensionVersion "Version">
<!ENTITY aboutSupport.extensionId "ID">

<!ENTITY aboutSupport.featuresTitle "&brandShortName; Features">
<!ENTITY aboutSupport.featureName "Name">
<!ENTITY aboutSupport.featureVersion "Version">
<!ENTITY aboutSupport.featureId "ID">

<!ENTITY aboutSupport.experimentsTitle "Experimental Features">
<!ENTITY aboutSupport.experimentName "Name">
<!ENTITY aboutSupport.experimentId "ID">
<!ENTITY aboutSupport.experimentDescription "Description">
<!ENTITY aboutSupport.experimentActive "Active">
<!ENTITY aboutSupport.experimentEndDate "End Date">
<!ENTITY aboutSupport.experimentHomepage "Homepage">
<!ENTITY aboutSupport.experimentBranch "Branch">

<!ENTITY aboutSupport.appBasicsTitle "Application Basics">
<!ENTITY aboutSupport.appBasicsName "Name">
<!ENTITY aboutSupport.appBasicsVersion "Version">
<!ENTITY aboutSupport.appBasicsBuildID "Build ID">

<!-- LOCALIZATION NOTE (aboutSupport.appBasicsUpdateChannel, aboutSupport.appBasicsUpdateHistory, aboutSupport.appBasicsShowUpdateHistory):
"Update" is a noun here, not a verb. -->
<!ENTITY aboutSupport.appBasicsUpdateChannel "Update Channel">
<!ENTITY aboutSupport.appBasicsUpdateHistory "Update History">
<!ENTITY aboutSupport.appBasicsShowUpdateHistory "Show Update History">

<!ENTITY aboutSupport.appBasicsProfileDir "Profile Directory">
<!-- LOCALIZATION NOTE (aboutSupport.appBasicsProfileDirWinMac):
This is the Windows- and Mac-specific variant of aboutSupport.appBasicsProfileDir.
Windows/Mac use the term "Folder" instead of "Directory" -->
<!ENTITY aboutSupport.appBasicsProfileDirWinMac "Profile Folder">

<!ENTITY aboutSupport.appBasicsEnabledPlugins "Enabled Plugins">
<!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
<!ENTITY aboutSupport.appBasicsUserAgent "User Agent">
<!ENTITY aboutSupport.appBasicsOS "OS">
<!ENTITY aboutSupport.appBasicsMemoryUse "Memory Use">
<!ENTITY aboutSupport.appBasicsPerformance "Performance">

<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
<!ENTITY aboutSupport.appBasicsServiceWorkers "Registered Service Workers">

<!ENTITY aboutSupport.appBasicsProfiles "Profiles">

<!ENTITY aboutSupport.appBasicsMultiProcessSupport "Multiprocess Windows">

<!ENTITY aboutSupport.appBasicsProcessCount "Web Content Processes">

<!ENTITY aboutSupport.appBasicsKeyGoogle "Google Key">
<!ENTITY aboutSupport.appBasicsKeyMozilla "Mozilla Location Service Key">

<!ENTITY aboutSupport.appBasicsSafeMode "Safe Mode">

<!ENTITY aboutSupport.showDir.label "Open Directory">
<!-- LOCALIZATION NOTE (aboutSupport.showMac.label): This is the Mac-specific
variant of aboutSupport.showDir.label.  This allows us to use the preferred
"Finder" terminology on Mac. -->
<!ENTITY aboutSupport.showMac.label "Show in Finder">
<!-- LOCALIZATION NOTE (aboutSupport.showWin2.label): This is the Windows-specific
variant of aboutSupport.showDir.label. -->
<!ENTITY aboutSupport.showWin2.label "Open Folder">

<!ENTITY aboutSupport.modifiedKeyPrefsTitle "Important Modified Preferences">
<!ENTITY aboutSupport.modifiedPrefsName "Name">
<!ENTITY aboutSupport.modifiedPrefsValue "Value">

<!-- LOCALIZATION NOTE (aboutSupport.userJSTitle, aboutSupport.userJSDescription): user.js is the name of the preference override file being checked. -->
<!ENTITY aboutSupport.userJSTitle "user.js Preferences">
<!ENTITY aboutSupport.userJSDescription "Your profile folder contains a <a id='prefs-user-js-link'>user.js file</a>, which includes preferences that were not created by &brandShortName;.">

<!ENTITY aboutSupport.lockedKeyPrefsTitle "Important Locked Preferences">
<!ENTITY aboutSupport.lockedPrefsName "Name">
<!ENTITY aboutSupport.lockedPrefsValue "Value">

<!ENTITY aboutSupport.graphicsTitle "Graphics">

<!ENTITY aboutSupport.placeDatabaseTitle "Places Database">
<!ENTITY aboutSupport.placeDatabaseIntegrity "Integrity">
<!ENTITY aboutSupport.placeDatabaseVerifyIntegrity "Verify Integrity">

<!ENTITY aboutSupport.jsTitle "JavaScript">
<!ENTITY aboutSupport.jsIncrementalGC "Incremental GC">

<!ENTITY aboutSupport.a11yTitle "Accessibility">
<!ENTITY aboutSupport.a11yActivated "Activated">
<!ENTITY aboutSupport.a11yForceDisabled "Prevent Accessibility">
<!ENTITY aboutSupport.a11yHandlerUsed "Accessible Handler Used">

<!ENTITY aboutSupport.libraryVersionsTitle "Library Versions">

<!ENTITY aboutSupport.installationHistoryTitle "Installation History">
<!ENTITY aboutSupport.updateHistoryTitle "Update History">

<!ENTITY aboutSupport.copyTextToClipboard.label "Copy text to clipboard">
<!ENTITY aboutSupport.copyRawDataToClipboard.label "Copy raw data to clipboard">

<!ENTITY aboutSupport.sandboxTitle "Sandbox">
<!ENTITY aboutSupport.sandboxSyscallLogTitle "Rejected System Calls">
<!ENTITY aboutSupport.sandboxSyscallIndex "#">
<!ENTITY aboutSupport.sandboxSyscallAge "Seconds Ago">
<!ENTITY aboutSupport.sandboxSyscallPID "PID">
<!ENTITY aboutSupport.sandboxSyscallTID "TID">
<!ENTITY aboutSupport.sandboxSyscallProcType "Process Type">
<!ENTITY aboutSupport.sandboxSyscallNumber "Syscall">
<!ENTITY aboutSupport.sandboxSyscallArgs "Arguments">

<!ENTITY aboutSupport.safeModeTitle "Try Safe Mode">
<!ENTITY aboutSupport.restartInSafeMode.label "Restart with Add-ons Disabled…">

<!ENTITY aboutSupport.graphicsFeaturesTitle "Features">
<!ENTITY aboutSupport.graphicsDiagnosticsTitle "Diagnostics">
<!ENTITY aboutSupport.graphicsFailureLogTitle "Failure Log">
<!ENTITY aboutSupport.graphicsGPU1Title "GPU #1">
<!ENTITY aboutSupport.graphicsGPU2Title "GPU #2">
<!ENTITY aboutSupport.graphicsDecisionLogTitle "Decision Log">
<!ENTITY aboutSupport.graphicsCrashGuardsTitle "Crash Guard Disabled Features">
<!ENTITY aboutSupport.graphicsWorkaroundsTitle "Workarounds">

<!ENTITY aboutSupport.mediaTitle "Media">
<!ENTITY aboutSupport.mediaOutputDevicesTitle "Output Devices">
<!ENTITY aboutSupport.mediaInputDevicesTitle "Input Devices">
<!ENTITY aboutSupport.mediaDeviceName "Name">
<!ENTITY aboutSupport.mediaDeviceGroup "Group">
<!ENTITY aboutSupport.mediaDeviceVendor "Vendor">
<!ENTITY aboutSupport.mediaDeviceState "State">
<!ENTITY aboutSupport.mediaDevicePreferred "Preferred">
<!ENTITY aboutSupport.mediaDeviceFormat "Format">
<!ENTITY aboutSupport.mediaDeviceChannels "Channels">
<!ENTITY aboutSupport.mediaDeviceRate "Rate">
<!ENTITY aboutSupport.mediaDeviceLatency "Latency">
PK
!<#^!!8chrome/en-US/locale/en-US/global/aboutSupport.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 (crashesTitle): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of relevant days with crash reports
crashesTitle=Crash Reports for the Last #1 Day;Crash Reports for the Last #1 Days

# LOCALIZATION NOTE (crashesTimeMinutes): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of minutes (between 1 and 59) which have passed since the crash
crashesTimeMinutes=#1 minute ago;#1 minutes ago

# LOCALIZATION NOTE (crashesTimeHours): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of hours (between 1 and 23) which have passed since the crash
crashesTimeHours=#1 hour ago;#1 hours ago

# LOCALIZATION NOTE (crashesTimeDays): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of days (1 or more) which have passed since the crash
crashesTimeDays=#1 day ago;#1 days ago

# LOCALIZATION NOTE (pendingReports): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 number of pending crash reports
pendingReports=All Crash Reports (including #1 pending crash in the given time range);All Crash Reports (including #1 pending crashes in the given time range)

# LOCALIZATION NOTE (rawDataCopied) Text displayed in a mobile "Toast" to user when the
# raw data is successfully copied to the clipboard via button press.
rawDataCopied=Raw data copied to clipboard

# LOCALIZATION NOTE (textCopied) Text displayed in a mobile "Toast" to user when the
# text is successfully copied to the clipboard via button press.
textCopied=Text copied to clipboard

# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
blockedDriver = Blocked for your graphics driver version.

# LOCALIZATION NOTE The %S here is a placeholder, leave unchanged, it will get replaced by the driver version string.
tryNewerDriver = Blocked for your graphics driver version. Try updating your graphics driver to version %S or newer.

# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
blockedGfxCard = Blocked for your graphics card because of unresolved driver issues.

# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
blockedOSVersion = Blocked for your operating system version.

# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
blockedMismatchedVersion = Blocked for your graphics driver version mismatch between registry and DLL.

# LOCALIZATION NOTE In the following strings, "Direct2D", "DirectWrite" and "ClearType"
# are proper nouns and should not be translated. Feel free to leave english strings if
# there are no good translations, these are only used in about:support
clearTypeParameters = ClearType Parameters

compositing = Compositing
hardwareH264 = Hardware H264 Decoding
mainThreadNoOMTC = main thread, no OMTC
yes = Yes
no = No
# LOCALIZATION NOTE The following strings indicate if an API key has been found.
# In some development versions, it's expected for some API keys that they are
# not found.
found = Found
missing = Missing

gpuDescription = Description
gpuVendorID = Vendor ID
gpuDeviceID = Device ID
gpuSubsysID = Subsys ID
gpuDrivers = Drivers
gpuRAM = RAM
gpuDriverVersion = Driver Version
gpuDriverDate = Driver Date
gpuActive = Active
webgl1WSIInfo = WebGL 1 Driver WSI Info
webgl1Renderer = WebGL 1 Driver Renderer
webgl1Version = WebGL 1 Driver Version
webgl1DriverExtensions = WebGL 1 Driver Extensions
webgl1Extensions = WebGL 1 Extensions
webgl2WSIInfo = WebGL 2 Driver WSI Info
webgl2Renderer = WebGL 2 Driver Renderer
webgl2Version = WebGL 2 Driver Version
webgl2DriverExtensions = WebGL 2 Driver Extensions
webgl2Extensions = WebGL 2 Extensions
GPU1 = GPU #1
GPU2 = GPU #2
blocklistedBug = Blocklisted due to known issues
# LOCALIZATION NOTE %1$S will be replaced with a bug number string.
bugLink = bug %1$S
# LOCALIZATION NOTE %1$S will be replaced with an arbitrary identifier
# string that can be searched on DXR/MXR or grepped in the source tree.
unknownFailure = Blocklisted; failure code %1$S
d3d11layersCrashGuard = D3D11 Compositor
d3d11videoCrashGuard = D3D11 Video Decoder
d3d9videoCrashGuard = D3D9 Video Decoder
glcontextCrashGuard = OpenGL
resetOnNextRestart = Reset on Next Restart
gpuProcessKillButton = Terminate GPU Process
gpuDeviceResetButton = Trigger Device Reset

audioBackend = Audio Backend
maxAudioChannels = Max Channels
channelLayout = Preferred Channel Layout
sampleRate = Preferred Sample Rate

minLibVersions = Expected minimum version
loadedLibVersions = Version in use

hasSeccompBPF = Seccomp-BPF (System Call Filtering)
hasSeccompTSync = Seccomp Thread Synchronization
hasUserNamespaces = User Namespaces
hasPrivilegedUserNamespaces = User Namespaces for privileged processes
canSandboxContent = Content Process Sandboxing
canSandboxMedia = Media Plugin Sandboxing
contentSandboxLevel = Content Process Sandbox Level
effectiveContentSandboxLevel = Effective Content Process Sandbox Level
sandboxProcType.content = content
sandboxProcType.mediaPlugin = media plugin

# LOCALIZATION NOTE %1$S and %2$S will be replaced with the number of remote and the total number
# of windows, respectively, while %3$S will be replaced with one of the status strings below,
# which contains a description of the multi-process preference and status.
# Note: multiProcessStatus.3 doesn't exist because status=3 was deprecated.
multiProcessWindows = %1$S/%2$S (%3$S)
multiProcessStatus.0 = Enabled by user
multiProcessStatus.1 = Enabled by default
multiProcessStatus.2 = Disabled
multiProcessStatus.4 = Disabled by accessibility tools
multiProcessStatus.5 = Disabled by lack of graphics hardware acceleration on Mac OS X
multiProcessStatus.6 = Disabled by unsupported text input
multiProcessStatus.7 = Disabled by add-ons
multiProcessStatus.8 = Disabled forcibly
# No longer in use (bug 1296353) but we might bring this back.
multiProcessStatus.9 = Disabled by graphics hardware acceleration on Windows XP
multiProcessStatus.unknown = Unknown status

asyncPanZoom = Asynchronous Pan/Zoom
apzNone = none
wheelEnabled = wheel input enabled
touchEnabled = touch input enabled
dragEnabled = scrollbar drag enabled
keyboardEnabled = keyboard enabled

# LOCALIZATION NOTE %1 will be replaced with the key of a preference.
wheelWarning = async wheel input disabled due to unsupported pref: %S
touchWarning = async touch input disabled due to unsupported pref: %S

# LOCALIZATION NOTE Strings explaining why a feature is or is not available.
disabledByBuild = disabled by build
enabledByDefault = enabled by default
disabledByDefault = disabled by default
enabledByUser = enabled by user
disabledByUser = disabled by user
PK
!<O3'
'
3chrome/en-US/locale/en-US/global/aboutTelemetry.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY aboutTelemetry.pageTitle "Telemetry Data">

<!ENTITY aboutTelemetry.pingDataSource "
Ping data source:
">

<!ENTITY aboutTelemetry.showCurrentPingData "
Current ping data
">

<!ENTITY aboutTelemetry.showArchivedPingData "
Archived ping data
">

<!ENTITY aboutTelemetry.raw "
Raw JSON
">

<!ENTITY aboutTelemetry.showSubsessionData "
Show subsession data
">

<!ENTITY aboutTelemetry.choosePing "
Choose ping:
">

<!ENTITY aboutTelemetry.showNewerPing "
&lt;&lt; Newer ping
">

<!ENTITY aboutTelemetry.showOlderPing "
Older ping &gt;&gt;
">

<!ENTITY aboutTelemetry.archiveWeekHeader "
Week
">

<!ENTITY aboutTelemetry.archivePingType "
Ping Type
">

<!ENTITY aboutTelemetry.archivePingHeader "
Ping
">

<!ENTITY aboutTelemetry.optionGroupToday "
Today
">

<!ENTITY aboutTelemetry.optionGroupYesterday "
Yesterday
">

<!ENTITY aboutTelemetry.optionGroupOlder "
Older
">

<!ENTITY aboutTelemetry.generalDataSection "
  General Data
">

<!ENTITY aboutTelemetry.environmentDataSection "
  Environment Data
">

<!ENTITY aboutTelemetry.telemetryLogSection "
  Telemetry Log
">

<!ENTITY aboutTelemetry.slowSqlSection "
  Slow SQL Statements
">

<!ENTITY aboutTelemetry.chromeHangsSection "
  Browser Hangs
">

<!ENTITY aboutTelemetry.threadHangStatsSection "
  Thread Hangs
">

<!ENTITY aboutTelemetry.capturedStacksSection "
  Captured Stacks
">

<!ENTITY aboutTelemetry.scalarsSection "
  Scalars
">

<!ENTITY aboutTelemetry.keyedScalarsSection "
  Keyed Scalars
">

<!ENTITY aboutTelemetry.histogramsSection "
  Histograms
">

<!ENTITY aboutTelemetry.keyedHistogramsSection "
  Keyed Histograms
">

<!ENTITY aboutTelemetry.eventsSection "
  Events
">

<!ENTITY aboutTelemetry.simpleMeasurementsSection "
  Simple Measurements
">

<!ENTITY aboutTelemetry.addonDetailsSection "
  Add-on Details
">

<!ENTITY aboutTelemetry.lateWritesSection "
  Late Writes
">

<!ENTITY aboutTelemetry.sessionInfoSection "
  Session Information
">

<!ENTITY aboutTelemetry.fullSqlWarning "
  NOTE: Slow SQL debugging is enabled. Full SQL strings may be displayed below but they will not be submitted to Telemetry.
">

<!ENTITY aboutTelemetry.fetchStackSymbols "
  Fetch function names for stacks
">

<!ENTITY aboutTelemetry.hideStackSymbols "
  Show raw stack data
">

<!ENTITY aboutTelemetry.filterText "
  Filter (strings or /regexp/)
">

<!ENTITY aboutTelemetry.payloadChoiceHeader "
  Payload
">
PK
!<_ş:chrome/en-US/locale/en-US/global/aboutTelemetry.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Note to translators:
# - %1$S will be replaced by brandFullName
# - %2$S will be replaced with the value of the toolkit.telemetry.server_owner preference
pageSubtitle = This page shows the information about performance, hardware, usage and customizations collected by Telemetry. This information is submitted to %1$S to help improve %2$S.

# Note to translators:
# - %1$S will be replaced by either telemetryEnabled or telemetryDisabled
# - %2$S will be replaced by either extendedTelemetryEnabled or extendedTelemetryDisabled
homeExplanation = Telemetry is %1$S and extended telemetry is %2$S.

# Note to translators:
# - %1$S will be replaced by a link with pingExplanationLink
# - %2$S will be replaced by the namedPing
pingDetails = Each piece of information is sent bundled into “%1$S”. You are looking at the %2$S ping.

# Note to translators:
# - %1$S will be replaced by the ping timestamp, e.g. "2017/07/08 10:40:46"
# - %2$S will be replaced by the ping name, e.g. "saved-session"
namedPing = %1$S, %2$S

# Note to translators:
# - %1$S will be replaced by a link with pingExplanationLink
# - %2$S will be replaced by currentPing
pingDetailsCurrent = Each piece of information is sent bundled into “%1$S“. You are looking at the %2$S ping.

pingExplanationLink = pings

telemetryEnabled = enabled

telemetryDisabled = disabled

extendedTelemetryEnabled = enabled

extendedTelemetryDisabled = disabled

currentPing = current

# Used as a tooltip for the "current" ping title in the sidebar
currentPingSidebar = current ping

telemetryPingTypeAll = all

telemetryLogTitle = Telemetry Log

telemetryLogHeadingId = Id

telemetryLogHeadingTimestamp = Timestamp

telemetryLogHeadingData = Data

# Note to translators:
# - %1$S will be replaced by the section name from the structure of the ping. More info about it can be found here : http://gecko.readthedocs.io/en/latest/toolkit/components/telemetry/telemetry/data/main-ping.html
filterPlaceholder = Find in %1$S

slowSqlMain = Slow SQL Statements on Main Thread

slowSqlOther = Slow SQL Statements on Helper Threads

slowSqlHits = Hits

slowSqlAverage = Avg. Time (ms)

slowSqlStatement = Statement

# Note to translators:
# - The %1$S will be replaced with the number of the hang
# - The %2$S will be replaced with the duration of the hang
chrome-hangs-title = Hang Report #%1$S (%2$S seconds)

# Note to translators:
# - The %1$S will be replaced with the string key for this stack.
# - The %2$S will be replaced with the number of times this stack was captured.
captured-stacks-title = %1$S (capture count: %2$S)

# Note to translators:
# - The %1$S will be replaced with the number of the late write
late-writes-title = Late Write #%1$S

stackTitle = Stack:

memoryMapTitle = Memory map:

errorFetchingSymbols = An error occurred while fetching symbols. Check that you are connected to the Internet and try again.

histogramSamples = samples

histogramAverage = average

histogramSum = sum

histogramCopy = Copy

keysHeader = Property

namesHeader = Name

valuesHeader = Value

addonTableID = Add-on ID

addonTableDetails = Details

# Note to translators:
# - The %1$S will be replaced with the name of an Add-on Provider (e.g. "XPI", "Plugin")
addonProvider = %1$S Provider

parentPayload = Parent Payload

# Note to translators:
# - The %1$S will be replaced with the number of the child payload (e.g. "1", "2")
childPayloadN = Child Payload %1$S

timestampHeader = timestamp

categoryHeader = category

methodHeader = method

objectHeader = object

extraHeader = extra
PK
!<;èT	T	7chrome/en-US/locale/en-US/global/aboutUrlClassifier.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- LOCALIZATION NOTE the term "url-classifier" should not be translated. -->
<!ENTITY aboutUrlClassifier.pageTitle                   "Information about the url-classifier">
<!ENTITY aboutUrlClassifier.providerTitle               "Provider">
<!ENTITY aboutUrlClassifier.provider                    "Provider">
<!ENTITY aboutUrlClassifier.providerLastUpdateTime      "Last update time">
<!ENTITY aboutUrlClassifier.providerNextUpdateTime      "Next update time">
<!ENTITY aboutUrlClassifier.providerBackOffTime         "Back-off time">
<!ENTITY aboutUrlClassifier.providerLastUpdateStatus    "Last update status">
<!ENTITY aboutUrlClassifier.providerUpdateBtn           "Update">
<!ENTITY aboutUrlClassifier.cacheTitle                  "Cache">
<!ENTITY aboutUrlClassifier.cacheRefreshBtn             "Refresh">
<!ENTITY aboutUrlClassifier.cacheClearBtn               "Clear">
<!ENTITY aboutUrlClassifier.cacheTableName              "Table name">
<!ENTITY aboutUrlClassifier.cacheNCacheEntries          "Number of negative cache entries">
<!ENTITY aboutUrlClassifier.cachePCacheEntries          "Number of positive cache entries">
<!ENTITY aboutUrlClassifier.cacheShowEntries            "Show entries">
<!ENTITY aboutUrlClassifier.cacheEntries                "Cache Entries">
<!ENTITY aboutUrlClassifier.cachePrefix                 "Prefix">
<!ENTITY aboutUrlClassifier.cacheNCacheExpiry           "Negative cache expiry">
<!ENTITY aboutUrlClassifier.cacheFullhash               "Full hash">
<!ENTITY aboutUrlClassifier.cachePCacheExpiry           "Positive cache expiry">
<!ENTITY aboutUrlClassifier.debugTitle                  "Debug">
<!ENTITY aboutUrlClassifier.debugModuleBtn              "Set Log Modules">
<!ENTITY aboutUrlClassifier.debugFileBtn                "Set Log File">
<!ENTITY aboutUrlClassifier.debugJSLogChk               "Set JS Log">
<!ENTITY aboutUrlClassifier.debugSBModules              "Safe Browsing log modules">
<!ENTITY aboutUrlClassifier.debugModules                "Current log modules">
<!ENTITY aboutUrlClassifier.debugSBJSModules            "Safe Browsing JS log">
<!ENTITY aboutUrlClassifier.debugFile                   "Current log file">
PK
!<_w

>chrome/en-US/locale/en-US/global/aboutUrlClassifier.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

TriggerUpdate = Trigger Update

NotAvailable = N/A

DisableSBJSLog = Disable Safe Browsing JS Log

EnableSBJSLog = Enable Safe Browsing JS Log

Enabled = Enabled

Disabled = Disabled

Updating = updating

CannotUpdate = cannot update

success = success

updateError = update error (%S)

downloadError = download error (%S)PK
!<k2)7chrome/en-US/locale/en-US/global/aboutWebrtc.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE (document_title, cannot_retrieve_log):
# The text "WebRTC" is a proper noun and should not be translated.
# It is the general label for the standards based technology. see http://www.webrtc.org
document_title = WebRTC Internals
cannot_retrieve_log = Cannot retrieve WebRTC log data

# LOCALIZATION NOTE (save_page_msg):
# %1$S will be replaced by a full path file name: the target of the SavePage operation.
save_page_msg = page saved to: %1$S

# LOCALIZATION NOTE (save_page_dialog_title): "about:webrtc" is a internal browser URL and should not be
# translated. This string is used as a title for a file save dialog box.
save_page_dialog_title = save about:webrtc as

# LOCALIZATION NOTE (debug_mode_off_state_msg):
# %1$S will be replaced by the full path file name of the debug log.
debug_mode_off_state_msg = trace log can be found at: %1$S

# LOCALIZATION NOTE (debug_mode_on_state_msg):
# %1$S will be replaced by the full path file name of the debug log.
debug_mode_on_state_msg = debug mode active, trace log at: %1$S

# LOCALIZATION NOTE (aec_logging_msg_label, aec_logging_off_state_label,
# aec_logging_on_state_label, aec_logging_on_state_msg):
# AEC is an abbreviation for Acoustic Echo Cancellation.
aec_logging_msg_label = AEC Logging
aec_logging_off_state_label = Start AEC Logging
aec_logging_on_state_label = Stop AEC Logging
aec_logging_on_state_msg = AEC logging active (speak with the caller for a few minutes and then stop the capture)

# LOCALIZATION NOTE (aec_logging_off_state_msg):
# %1$S will be replaced by the full path to the directory containing the captured log files.
# AEC is an abbreviation for Acoustic Echo Cancellation.
aec_logging_off_state_msg = captured log files can be found in: %1$S

# LOCALIZATION NOTE (peer_connection_id_label): "PeerConnection" is a proper noun
# associated with the WebRTC module. "ID" is an abbreviation for Identifier. This string
# should not normally be translated and is used as a data label.
peer_connection_id_label = PeerConnection ID

# LOCALIZATION NOTE (sdp_heading, local_sdp_heading, remote_sdp_heading):
# "SDP" is an abbreviation for Session Description Protocol, an IETF standard.
# See http://wikipedia.org/wiki/Session_Description_Protocol
sdp_heading = SDP
local_sdp_heading = Local SDP
remote_sdp_heading = Remote SDP

# LOCALIZATION NOTE (rtp_stats_heading): "RTP" is an abbreviation for the
# Real-time Transport Protocol, an IETF specification, and should not
# normally be translated. "Stats" is an abbreviation for Statistics.
rtp_stats_heading = RTP Stats

# LOCALIZATION NOTE (ice_state, ice_stats_heading): "ICE" is an abbreviation
# for Interactive Connectivity Establishment, which is an IETF protocol,
# and should not normally be translated. "Stats" is an abbreviation for
# Statistics.
ice_state = ICE State
ice_stats_heading = ICE Stats
ice_restart_count_label = ICE restarts
ice_rollback_count_label = ICE rollbacks
ice_pair_bytes_sent = Bytes sent
ice_pair_bytes_received = Bytes received

# LOCALIZATION NOTE (av_sync_label): "A/V" stands for Audio/Video.
# "sync" is an abbreviation for sychronization. This is used as
# a data label.
av_sync_label = A/V sync

# LOCALIZATION NOTE (jitter_buffer_delay_label): A jitter buffer is an
# element in the processing chain, see http://wikipedia.org/wiki/Jitter
# This is used as a data label.
jitter_buffer_delay_label = Jitter-buffer delay

# LOCALIZATION NOTE (avg_bitrate_label, avg_framerate_label): "Avg." is an abbreviation
# for Average. These are used as data labels.
avg_bitrate_label = Avg. bitrate
avg_framerate_label = Avg. framerate

# LOCALIZATION NOTE (typeLocal, typeRemote): These adjectives are used to label a
# line of statistics collected for a peer connection. The data represents
# either the local or remote end of the connection.
typeLocal = Local
typeRemote = Remote

# LOCALIZATION NOTE (nominated): This adjective is used to label a table column.
# Cells in this column contain the localized javascript string representation of "true"
# or are left blank.
nominated = Nominated

# LOCALIZATION NOTE (selected): This adjective is used to label a table column.
# Cells in this column contain the localized javascript string representation of "true"
# or are left blank. This represents an attribute of an ICE candidate.
selected = Selected

save_page_label = Save Page
debug_mode_msg_label = Debug Mode
debug_mode_off_state_label = Start Debug Mode
debug_mode_on_state_label = Stop Debug Mode
stats_heading = Session Statistics
stats_clear = Clear History
log_heading = Connection Log
log_clear = Clear Log
log_show_msg = show log
log_hide_msg = hide log
connection_closed = closed
local_candidate = Local Candidate
remote_candidate = Remote Candidate
priority = Priority
fold_show_msg = show details
fold_show_hint = click to expand this section
fold_hide_msg = hide details
fold_hide_hint = click to collapse this section
dropped_frames_label = Dropped frames
discarded_packets_label = Discarded packets
decoder_label = Decoder
encoder_label = Encoder
received_label = Received
packets = packets
lost_label = Lost
jitter_label = Jitter
sent_label = Sent

PK
!<%3.chrome/en-US/locale/en-US/global/appPicker.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY NoAppFound.label      "No applications were found for this file type.">
<!ENTITY BrowseButton.label    "Browse…">
<!ENTITY SendMsg.label         "Send this item to:">
PK
!<P{6chrome/en-US/locale/en-US/global/appstrings.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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=The file %S cannot be found. Please check the location and try again.
fileAccessDenied=The file at %S is not readable.
dnsNotFound=%S could not be found. Please check the name and try again.
unknownProtocolFound=One of the following (%S) is not a registered protocol or is not allowed in this context.
connectionFailure=The connection was refused when attempting to contact %S.
netInterrupt=The connection to %S has terminated unexpectedly. Some data may have been transferred.
netTimeout=The operation timed out when attempting to contact %S.
redirectLoop=Redirection limit for this URL exceeded.  Unable to load the requested page.  This may be caused by cookies that are blocked.
confirmRepostPrompt=To display this page, the application must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
resendButton.label=Resend
unknownSocketType=This document cannot be displayed unless you install the Personal Security Manager (PSM). Download and install PSM and try again, or contact your system administrator.
netReset=The document contains no data.
notCached=This document is no longer available.
netOffline=This document cannot be displayed while offline. To go online, uncheck Work Offline from the File menu.
isprinting=The document cannot change while Printing or in Print Preview.
deniedPortAccess=Access to the port number given has been disabled for security reasons.
proxyResolveFailure=The proxy server you have configured could not be found. Please check your proxy settings and try again.
proxyConnectFailure=The connection was refused when attempting to contact the proxy server you have configured. Please check your proxy settings and try again.
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.
sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.
inadequateSecurityError=The website tried to negotiate an inadequate level of security.
PK
!<KMpp8chrome/en-US/locale/en-US/global/autocomplete.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 (searchWithEngine): %S will be replaced with
# the search engine provider's name. This format was chosen because
# the provider can also end with "Search" (e.g.: MSN Search).
searchWithEngine = Search with %S

# LOCALIZATION NOTE (switchToTab2): This is the same as the older switchToTab
# string that it's replacing, except it uses title case, so "Switch" and "Tab"
# are capitalized.
switchToTab2 = Switch to Tab

# LOCALIZATION NOTE (visit): This is shown next to autocomplete entries that are
# simple URLs or sites, which will be visited when the user selects them.
visit = Visit

# LOCALIZATION NOTE (bookmarkKeywordSearch): This is the title of autocomplete
# entries that are bookmark keyword searches.  %1$S will be replaced with the
# domain name of the bookmark, and %2$S will be replaced with the keyword
# search text that the user is typing.  %2$S will not be empty.
bookmarkKeywordSearch = %1$S: %2$S
PK
!<&W))*chrome/en-US/locale/en-US/global/brand.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY % realBrandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%realBrandDTD;
PK
!<۞3chrome/en-US/locale/en-US/global/browser.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

browsewithcaret.checkMsg=Do not show me this dialog box again.
browsewithcaret.checkWindowTitle=Caret Browsing
browsewithcaret.checkLabel=Pressing F7 turns Caret Browsing on or off. This feature places a moveable cursor in web pages, allowing you to select text with the keyboard. Do you want to turn Caret Browsing on?
browsewithcaret.checkButtonLabel=Yes

plainText.wordWrap=Wrap Long Lines

formPostSecureToInsecureWarning.title = Security Warning
formPostSecureToInsecureWarning.message = The information you have entered on this page will be sent over an insecure connection and could be read by a third party.\n\nAre you sure you want to send this information?
formPostSecureToInsecureWarning.continue = Continue
PK
!<l-990chrome/en-US/locale/en-US/global/charsetMenu.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY charsetMenu2.label            "Text Encoding">
<!ENTITY charsetMenu2.accesskey        "c">
PK
!<?#

7chrome/en-US/locale/en-US/global/charsetMenu.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE: The property keys ending with ".key" are for access keys.
# Localizations may add or delete properties where the property key ends with
# ".key" as appropriate for the localization. The code that uses this data can
# deal with the absence of an access key for an item.
#
# For gbk, gbk.bis and gbk.bis.key are used to trigger string changes in
# localizations.
#
# In the en-US version of this file, access keys are given to the following:
# * UTF-8
# * All encodings that are the fallback encoding for some locale in Firefox
# * All encodings that are the fallback encoding for some locale in IE
# * All Japanese encodings
#
# For the items whose property key does not end in ".key" and whose value
# includes "(" U+0028 LEFT PARENTHESIS, the "(" character is significant for
# processing by CharsetMenu.jsm. If your localization does not use ASCII
# parentheses where en-US does in this file, please file a bug to make
# CharsetMenu.jsm also recognize the delimiter your localization uses.
# (When this code was developed, all localizations appeared to use
# U+0028 LEFT PARENTHESIS for this purpose.)

# Auto-Detect (sub)menu
charsetMenuCharsets = Character Encoding
charsetMenuAutodet = Auto-Detect
# 'A' is reserved for Arabic:
charsetMenuAutodet.key = D
charsetMenuAutodet.off = (off)
charsetMenuAutodet.off.key = o
charsetMenuAutodet.ja = Japanese
charsetMenuAutodet.ja.key = J
charsetMenuAutodet.ru = Russian
charsetMenuAutodet.ru.key = R
charsetMenuAutodet.uk = Ukrainian
charsetMenuAutodet.uk.key = U

# Globally-relevant
UTF-8.key        = U
UTF-8            = Unicode
windows-1252.key = W
windows-1252     = Western

# Arabic
windows-1256.key = A
windows-1256     = Arabic (Windows)
ISO-8859-6       = Arabic (ISO)

# Baltic
windows-1257.key = B
windows-1257     = Baltic (Windows)
ISO-8859-4       = Baltic (ISO)

# Central European
windows-1250.key =         E
windows-1250     = Central European (Windows)
ISO-8859-2.key   =       l
ISO-8859-2       = Central European (ISO)

# Chinese, Simplified
gbk.bis.key      =          S
gbk.bis          = Chinese, Simplified

# Chinese, Traditional
Big5.key         =          T
Big5             = Chinese, Traditional

# Cyrillic
windows-1251.key = C
windows-1251     = Cyrillic (Windows)
ISO-8859-5       = Cyrillic (ISO)
KOI8-R           = Cyrillic (KOI8-R)
KOI8-U           = Cyrillic (KOI8-U)
IBM866           = Cyrillic (DOS)

# Greek
windows-1253.key = G
windows-1253     = Greek (Windows)
ISO-8859-7.key   =          O
ISO-8859-7       = Greek (ISO)

# Hebrew
windows-1255.key = H
windows-1255     = Hebrew
# LOCALIZATION NOTE (ISO-8859-8): The value for this item should begin with
# the same word for Hebrew as the value for windows-1255 so that this item
# sorts right after that one in the collation order for your locale.
ISO-8859-8       = Hebrew, Visual

# Japanese
Shift_JIS.key    = J
Shift_JIS        = Japanese (Shift_JIS)
EUC-JP.key       =   p
EUC-JP           = Japanese (EUC-JP)
ISO-2022-JP.key  =     n
ISO-2022-JP      = Japanese (ISO-2022-JP)

# Korean
EUC-KR.key       = K
EUC-KR           = Korean

# Thai
windows-874.key  =    i
windows-874      = Thai

# Turkish
windows-1254.key =   r
windows-1254     = Turkish

# Vietnamese
windows-1258.key = V
windows-1258     = Vietnamese

PK
!<z==1chrome/en-US/locale/en-US/global/commonDialog.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY header.label     "Brief Title"> 
<!ENTITY message.label    "Some sample Text goes here.">
<!ENTITY editfield0.label "User Name:"> 
<!ENTITY editfield1.label "Password:"> 
<!ENTITY checkbox.label   "check">
<!ENTITY copyCmd.label    "Copy">
<!ENTITY copyCmd.accesskey "C">
<!ENTITY selectAllCmd.label "Select All">
<!ENTITY selectAllCmd.accesskey "A">
PK
!<Ȥli9chrome/en-US/locale/en-US/global/commonDialogs.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

Alert=Alert
Confirm=Confirm
ConfirmCheck=Confirm
Prompt=Prompt
PromptUsernameAndPassword2=Authentication Required
PromptPassword2=Password Required
Select=Select
OK=OK
Cancel=Cancel
Yes=&Yes
No=&No
Save=&Save
Revert=&Revert
DontSave=Do&n’t Save
ScriptDlgGenericHeading=[JavaScript Application]
ScriptDlgHeading=The page at %S says:
ScriptDialogLabel=Prevent this page from creating additional dialogs
ScriptDialogPreventTitle=Confirm Dialog Preference
# LOCALIZATION NOTE (EnterLoginForRealm3, EnterLoginForProxy3):
# %1 is an untrusted string provided by a remote server. It could try to
# take advantage of sentence structure in order to mislead the user (see
# bug 244273). %1 should be integrated into the translated sentences as
# little as possible. %2 is the url of the site being accessed.
EnterLoginForRealm3=%2$S is requesting your username and password. The site says: “%1$S”
EnterLoginForProxy3=The proxy %2$S is requesting a username and password. The site says: “%1$S”
EnterUserPasswordFor2=%1$S is requesting your username and password.
EnterUserPasswordForCrossOrigin2=%1$S is requesting your username and password. WARNING: Your password will not be sent to the website you are currently visiting!
EnterPasswordFor=Enter password for %1$S on %2$S
PK
!<'Bł+chrome/en-US/locale/en-US/global/config.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY window.title "about:config">

<!-- about:config warning page -->
<!-- LOCALIZATION NOTE: aboutWarningTitle.label should be attention grabbing and playful -->
<!ENTITY aboutWarningTitle.label "This might void your warranty!">
<!ENTITY aboutWarningText.label "Changing these advanced settings can be harmful to the stability, security, and performance of this application. You should only continue if you are sure of what you are doing.">
<!ENTITY aboutWarningButton2.label "I accept the risk!">
<!ENTITY aboutWarningCheckbox.label "Show this warning next time">

<!ENTITY searchPrefs.label "Search:">
<!ENTITY searchPrefs.accesskey "r">
<!ENTITY focusSearch.key "r">
<!ENTITY focusSearch2.key "f">

<!-- Columns -->
<!ENTITY prefColumn.label "Preference Name">
<!ENTITY lockColumn.label "Status">
<!ENTITY typeColumn.label "Type">
<!ENTITY valueColumn.label "Value">

<!-- Tooltips -->
<!ENTITY prefColumnHeader.tooltip "Click to sort"> 
<!ENTITY columnChooser.tooltip "Click to select columns to display"> 

<!-- Context Menu -->
<!ENTITY copyPref.key "C">
<!ENTITY copyPref.label "Copy">
<!ENTITY copyPref.accesskey "C">
<!ENTITY copyName.label "Copy Name">
<!ENTITY copyName.accesskey "N">
<!ENTITY copyValue.label "Copy Value">
<!ENTITY copyValue.accesskey "V">
<!ENTITY modify.label "Modify">
<!ENTITY modify.accesskey "M">
<!ENTITY toggle.label "Toggle">
<!ENTITY toggle.accesskey "T">
<!ENTITY reset.label "Reset">
<!ENTITY reset.accesskey "R">
<!ENTITY new.label "New">
<!ENTITY new.accesskey "w">
<!ENTITY string.label "String">
<!ENTITY string.accesskey "S">
<!ENTITY integer.label "Integer">
<!ENTITY integer.accesskey "I">
<!ENTITY boolean.label "Boolean">
<!ENTITY boolean.accesskey "B">
PK
!<X.882chrome/en-US/locale/en-US/global/config.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Lock column values
default=default
modified=modified
locked=locked

# Type column values
string=string
int=integer
bool=boolean

# Preference prompts
# %S is replaced by one of the type column values above
new_title=New %S value
new_prompt=Enter the preference name
modify_title=Enter %S value

nan_title=Invalid value
nan_text=The text you entered is not a number.
PK
!<PP?chrome/en-US/locale/en-US/global/contentAreaCommands.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# context menu strings

SaveImageTitle=Save Image
SaveMediaTitle=Save Media
SaveVideoTitle=Save Video
SaveAudioTitle=Save Audio
SaveLinkTitle=Save As
DefaultSaveFileName=index
WebPageCompleteFilter=Web Page, complete
WebPageHTMLOnlyFilter=Web Page, HTML only
WebPageXHTMLOnlyFilter=Web Page, XHTML only
WebPageSVGOnlyFilter=Web Page, SVG only
WebPageXMLOnlyFilter=Web Page, XML only

# LOCALIZATION NOTE (filesFolder):
#    This is the name of the folder that is created parallel to a HTML file 
#    when it is saved "With Images". The %S section is replaced with the
#    leaf name of the file being saved (minus extension).
filesFolder=%S_files
PK
!<gDRR,chrome/en-US/locale/en-US/global/crashes.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY crashReports.title         "Crash Reports">
<!ENTITY crashesUnsubmitted.label   "Unsubmitted Crash Reports">
<!ENTITY crashesSubmitted.label     "Submitted Crash Reports">
<!ENTITY id.heading                 "Report ID">
<!ENTITY dateCrashed.heading        "Date Crashed">
<!ENTITY dateSubmitted.heading      "Date Submitted">
<!ENTITY noReports.label            "No crash reports have been submitted.">
<!ENTITY noConfig.label             "This application has not been configured to display crash reports. The preference <code>breakpad.reportURL</code> must be set.">
<!ENTITY clearAllReports.label      "Remove All Reports">
PK
!< d883chrome/en-US/locale/en-US/global/crashes.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

deleteconfirm.title=Are you sure?
deleteconfirm.description=This will delete all reports and cannot be undone.

PK
!<\{11/chrome/en-US/locale/en-US/global/css.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

MimeNotCss=The stylesheet %1$S was not loaded because its MIME type, “%2$S”, is not “text/css”.
MimeNotCssWarn=The stylesheet %1$S was loaded as CSS even though its MIME type, “%2$S”, is not “text/css”.

PEUnexpEOF2=Unexpected end of file while searching for %1$S.
PEParseRuleWSOnly=Whitespace-only string given to be parsed as rule.
PEDeclDropped=Declaration dropped.
PEDeclSkipped=Skipped to next declaration.
PEUnknownProperty=Unknown property ‘%1$S’.
PEValueParsingError=Error in parsing value for ‘%1$S’.
PEExpectEndValue=Expected end of value but found ‘%1$S’.
PERuleTrailing=Expected end of rule but found ‘%1$S’.
PESkipAtRuleEOF2=end of at-rule
PEUnknownAtRule=Unrecognized at-rule or error parsing at-rule ‘%1$S’.
PECharsetRuleEOF=charset string in @charset rule
PECharsetRuleNotString=Expected charset string but found ‘%1$S’.
PEGatherMediaEOF=end of media list in @import or @media rule
PEGatherMediaNotComma=Expected ‘,’ in media list but found ‘%1$S’.
PEGatherMediaNotIdent=Expected identifier in media list but found ‘%1$S’.
PEGatherMediaReservedMediaType=Found reserved keyword ‘%1$S’ when looking for media type.
PEParseSourceSizeListEOF=length value for matched media condition
PEParseSourceSizeListNotComma=Expected ‘,’ after value but found ‘%1$S’
PEImportNotURI=Expected URI in @import rule but found ‘%1$S’.
PEImportBadURI=Invalid URI in @import rule: ‘%1$S’.
PEImportUnexpected=Found unexpected ‘%1$S’ within @import.
PEGroupRuleEOF2=end of @media, @supports or @-moz-document rule
PEGroupRuleNestedAtRule=%1$S rule not allowed within @media or @-moz-document rule.
PEMozDocRuleBadFunc2=Expected url(), url-prefix(), domain() or regexp() in @-moz-document rule but found ‘%1$S’.
PEMozDocRuleNotURI=Expected URI in @-moz-document rule but found ‘%1$S’.
PEMozDocRuleNotString=Expected string in @-moz-document rule regexp() function but found ‘%1$S’.
PEMozDocRuleEOF=next URI in @-moz-document rule
PEAtNSPrefixEOF=namespace prefix in @namespace rule
PEAtNSURIEOF=namespace URI in @namespace rule
PEAtNSUnexpected=Unexpected token within @namespace: ‘%1$S’.
PEKeyframeNameEOF=name of @keyframes rule.
PEKeyframeBadName=Expected identifier for name of @keyframes rule.
PEKeyframeBrace=Expected opening { of @keyframes rule.
PESkipDeclBraceEOF=closing } of declaration block
PESkipRSBraceEOF=closing } of invalid rule set
PEBadSelectorRSIgnored=Ruleset ignored due to bad selector.
PEBadSelectorKeyframeRuleIgnored=Keyframe rule ignored due to bad selector.
PESelectorListExtraEOF=‘,’ or ‘{’
PESelectorListExtra=Expected ‘,’ or ‘{’ but found ‘%1$S’.
PESelectorGroupNoSelector=Selector expected.
PESelectorGroupExtraCombinator=Dangling combinator.
PECounterStyleNotIdent=Expected identifier for name of @counter-style rule.
PECounterStyleBadName=Name of @counter-style rule can’t be ‘%1$S’.
PECounterStyleBadBlockStart=Expected ‘{’ to begin @counter-style rule but found ‘%1$S’.
PECounterStyleEOF=closing ‘}’ of @counter-style block
PECounterDescExpected=Expected counter descriptor but found ‘%1$S’.
PEUnknownCounterDesc=Unknown descriptor ‘%1$S’ in @counter-style rule.
PECounterExtendsNotIdent=Expected identifier for extends system but found ‘%1$S’.
PECounterASWeight=Each weight in the additive-symbols descriptor must be smaller than the previous weight.
PEClassSelEOF=class name
PEClassSelNotIdent=Expected identifier for class selector but found ‘%1$S’.
PECoordinatePair=Expected coordinate pair but found ‘%1$S’.
PETypeSelEOF=element type
PETypeSelNotType=Expected element name or ‘*’ but found ‘%1$S’.
PEUnknownNamespacePrefix=Unknown namespace prefix ‘%1$S’.
PEAttributeNameEOF=attribute name
PEAttributeNameExpected=Expected identifier for attribute name but found ‘%1$S’.
PEAttributeNameOrNamespaceExpected=Expected attribute name or namespace but found ‘%1$S’.
PEAttSelNoBar=Expected ‘|’ but found ‘%1$S’.
PEAttSelInnerEOF=part of attribute selector
PEAttSelUnexpected=Unexpected token in attribute selector: ‘%1$S’.
PEAttSelValueEOF=attribute value
PEAttSelCloseEOF=‘]’ to end attribute selector
PEAttSelNoClose=Expected ‘]’ to terminate attribute selector but found ‘%1$S’.
PEAttSelBadValue=Expected identifier or string for value in attribute selector but found ‘%1$S’.
PEPseudoSelEOF=name of pseudo-class or pseudo-element
PEPseudoSelBadName=Expected identifier for pseudo-class or pseudo-element but found ‘%1$S’.
PEPseudoSelNonFunc=Function token for non-function pseudo-class or pseudo-element, or the other way around, when reading ‘%1$S’.
PEPseudoSelNotPE=Expected pseudo-element but found ‘%1$S’.
PEPseudoSelDoubleNot=Negation pseudo-class can’t be negated ‘%1$S’.
PEPseudoSelPEInNot=Pseudo-elements can’t be negated ‘%1$S’.
PEPseudoSelNewStyleOnly=This pseudo-element must use the “::” form: ‘%1$S’.
PEPseudoSelEndOrUserActionPC=Expected end of selector or a user action pseudo-class after pseudo-element but found ‘%1$S’.
PEPseudoSelNoUserActionPC=Expected end of selector after pseudo-element that does not support user action pseudo-classes but found ‘%1$S’.
PEPseudoSelMultiplePE=Extra pseudo-element ‘%1$S’.
PEPseudoSelUnknown=Unknown pseudo-class or pseudo-element ‘%1$S’.
PENegationEOF=selector within negation
PENegationBadInner=Malformed simple selector as negation pseudo-class argument ‘%1$S’.
PENegationNoClose=Missing closing ‘)’ in negation pseudo-class ‘%1$S’.
PENegationBadArg=Missing argument in negation pseudo-class ‘%1$S’.
PEPseudoClassArgEOF=argument to pseudo-class selector
PEPseudoClassArgNotIdent=Expected identifier for pseudo-class parameter but found ‘%1$S’.
PEPseudoClassArgNotNth=Expected part of argument to pseudo-class but found ‘%1$S’.
PEPseudoClassNoClose=Missing closing ‘)’ in pseudo-class, found ‘%1$S’ instead.
PEPseudoClassNoArg=Missing argument in pseudo-class ‘%1$S’.
PEPseudoClassNotUserAction=Expected end of selector or a user action pseudo-class after pseudo-element but found pseudo-class ‘%1$S’.
PESelectorEOF=selector
PEBadDeclBlockStart=Expected ‘{’ to begin declaration block but found ‘%1$S’.
PEColorEOF=color
PEColorNotColor=Expected color but found ‘%1$S’.
PEColorComponentEOF=color component
PEExpectedPercent=Expected a percentage but found ‘%1$S’.
PEExpectedInt=Expected an integer but found ‘%1$S’.
PEExpectedNumberOrAngle=Expected a number or an angle but found ‘%1$S’.
PEExpectedNumberOrPercent=Expected a number or a percentage but found ‘%1$S’.
PEColorBadRGBContents=Expected number or percentage in rgb() but found ‘%1$S’.
PEColorComponentBadTerm=Expected ‘%2$S’ but found ‘%1$S’.
PEExpectedComma=Expected ‘,’ but found ‘%1$S’.
PEColorSaturationEOF=saturation
PEColorLightnessEOF=lightness
PEColorOpacityEOF=opacity in color value
PEExpectedNumber=Expected a number but found ‘%1$S’.
PEPositionEOF=<position>
PEExpectedPosition=Expected <position> but found ‘%1$S’.
PEExpectedRadius=Expected radius but found ‘%1$S’.
PEExpectedCloseParen=Expected ‘)’ but found ‘%1$S’.
PEDeclEndEOF=‘;’ or ‘}’ to end declaration
PEParseDeclarationNoColon=Expected ‘:’ but found ‘%1$S’.
PEParseDeclarationDeclExpected=Expected declaration but found ‘%1$S’.
PEEndOfDeclEOF=end of declaration
PEImportantEOF=important
PEExpectedImportant=Expected ‘important’ but found ‘%1$S’.
PEBadDeclEnd=Expected ‘;’ to terminate declaration but found ‘%1$S’.
PEBadDeclOrRuleEnd2=Expected ‘;’ or ‘}’ to terminate declaration but found ‘%1$S’.
PEInaccessibleProperty2=Cannot specify value for internal property.
PECommentEOF=end of comment
SEUnterminatedString=Found unclosed string ‘%1$S’.
PEFontDescExpected=Expected font descriptor but found ‘%1$S’.
PEUnknownFontDesc=Unknown descriptor ‘%1$S’ in @font-face rule.
PEMQExpectedExpressionStart=Expected ‘(’ to start media query expression but found ‘%1$S’.
PEMQExpressionEOF=contents of media query expression
PEMQExpectedFeatureName=Expected media feature name but found ‘%1$S’.
PEMQExpectedFeatureNameEnd=Expected ‘:’ or ‘)’ after media feature name but found ‘%1$S’.
PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
PEMQExpectedFeatureValue=Found invalid value for media feature.
PEBadFontBlockStart=Expected ‘{’ to begin @font-face rule but found ‘%1$S’.
PEBadFontBlockEnd=Expected ‘}’ to end @font-face rule but found ‘%1$S’.
PEAnonBoxNotAlone=Did not expect anonymous box.
PEFFVUnexpectedEOF=Unexpected end of @font-feature-values rule.
PEFFVBlockStart=Expected opening { of @font-feature-values rule but found ‘%1$S’.
PEFFVValueSetStart=Expected opening { of feature value set but found ‘%1$S’.
PEFFVNoFamily=Expected font family list for @font-feature-values rule but found ‘%1$S’.
PEFFVUnexpectedBlockEnd=Expected ‘}’ to end @font-feature-values rule but found ‘%1$S’.
PEFFVUnknownFontVariantPropValue=Unknown font-variant property value ‘%1$S’.
PEFFVExpectedIdent=Expected identifier but found ‘%1$S’.
PEFFVExpectedValue=Expected non-negative integer value but found ‘%1$S’.
PEFFVTooManyValues=Too many values for feature type ‘%1$S’.
PEFFVGenericInFamilyList=Family list cannot contain generic font family name.
PEFFVValueDefinitionTrailing=Expected end of value definition but found ‘%1$S’.
PEBadDirValue=Expected ‘ltr’ or ‘rtl’ in direction selector but found ‘%1$S’.
PESupportsConditionStartEOF2=‘not’, ‘(’, or function
PESupportsConditionInParensEOF=‘)’
PESupportsConditionNotEOF=‘not’
PESupportsWhitespaceRequired=Expected whitespace after ‘not’, ‘and’, or ‘or’.
PESupportsConditionExpectedOpenParenOrFunction=Expected ‘(’ or function while parsing supports condition but found ‘%1$S’.
PESupportsConditionExpectedCloseParen=Expected ‘)’ while parsing supports condition but found ‘%1$S’.
PESupportsConditionExpectedStart2=Expected ‘not’, ‘(’, or function while parsing supports condition but found ‘%1$S’.
PESupportsConditionExpectedNot=Expected ‘not’ while parsing supports condition but found ‘%1$S’.
PESupportsGroupRuleStart=Expected ‘{’ to begin @supports rule but found ‘%1$S’.
PEFilterEOF=filter
PEExpectedNoneOrURL=Expected ‘none’ or URL but found ‘%1$S’.
PEExpectedNoneOrURLOrFilterFunction=Expected ‘none’, URL, or filter function but found ‘%1$S’.
PEExpectedNonnegativeNP=Expected non-negative number or percentage.
PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function.
PEVariableEOF=variable
PEVariableEmpty=Expected variable value but found ‘%1$S’.
# LOCALIZATION NOTE(PEValueWithVariablesParsingErrorInValue): %1$S is replaced
# with the property name and %2$S is replaced with the property value.
PEValueWithVariablesParsingErrorInValue=Error in parsing value for ‘%1$S’ after substituting variables. Generated value was ‘%2$S’.
PEValueWithVariablesFallbackInherit=Falling back to ‘inherit’.
PEValueWithVariablesFallbackInitial=Falling back to ‘initial’.
PEInvalidVariableReference=Property contained reference to invalid variable.
PEInvalidVariableTokenFallback=Found invalid token ‘%1$S’ at top level of variable reference fallback.
PEExpectedVariableNameEOF=identifier for variable name
PEExpectedVariableName=Expected identifier for variable name but found ‘%1$S’.
PEExpectedVariableFallback=Expected variable reference fallback after ‘,’.
PEExpectedVariableCommaOrCloseParen=Expected ‘,’ or ‘)’ after variable name in variable reference but found ‘%1$S’.
PESubgridNotSupported=Support for the ‘subgrid’ keyword of CSS Grid is not enabled.
PEMoreThanOneGridRepeatAutoFillInNameList=Only one repeat(auto-fill, …) is allowed in a name list for a subgrid.
PEMoreThanOneGridRepeatAutoFillFitInTrackList=Only one repeat(auto-fill, …) or repeat(auto-fit, …) is allowed in a track list.
PEMoreThanOneGridRepeatTrackSize=Only one track size is allowed inside repeat(auto-fit/auto-fill, …).

TooLargeDashedRadius=Border radius is too large for ‘dashed’ style (the limit is 100000px). Rendering as solid.
TooLargeDottedRadius=Border radius is too large for ‘dotted’ style (the limit is 100000px). Rendering as solid.
PK
!<(Gee5chrome/en-US/locale/en-US/global/customizeToolbar.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY dialog.title             "Customize Toolbar">
<!ENTITY dialog.dimensions        "width: 92ch; height: 36em;">
<!ENTITY instructions.description "You can add or remove items by dragging to or from the toolbars.">
<!ENTITY show.label               "Show:">
<!ENTITY iconsAndText.label       "Icons and Text">
<!ENTITY icons.label              "Icons">
<!ENTITY text.label               "Text">
<!ENTITY useSmallIcons.label      "Use Small Icons">
<!ENTITY restoreDefaultSet.label  "Restore Default Set">
<!ENTITY addNewToolbar.label      "Add New Toolbar">
<!ENTITY saveChanges.label        "Done">
<!ENTITY undoChanges.label        "Undo Changes">
PK
!<<chrome/en-US/locale/en-US/global/customizeToolbar.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

enterToolbarTitle=New Toolbar
enterToolbarName=Enter a name for this toolbar:
enterToolbarDup=There is already a toolbar with the name “%S”. Please enter a different name.
enterToolbarBlank=You must enter a name to create a new toolbar.
separatorTitle=Separator
springTitle=Flexible Space
spacerTitle=Space
PK
!<GKgff0chrome/en-US/locale/en-US/global/datetimebox.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- Placeholders for input type=time -->

<!ENTITY time.hour.placeholder "--">
<!ENTITY time.minute.placeholder "--">
<!ENTITY time.second.placeholder "--">
<!ENTITY time.millisecond.placeholder "--">
<!ENTITY time.dayperiod.placeholder "--">

<!-- Placeholders for input type=date -->

<!ENTITY date.year.placeholder "yyyy">
<!ENTITY date.month.placeholder "mm">
<!ENTITY date.day.placeholder "dd">
PK
!<sff3chrome/en-US/locale/en-US/global/datetimepicker.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- first day of week to display in datepicker, a value from 0 to 6,
     0 = Sunday, 1 = Monday, etc. -->
<!ENTITY firstdayofweek.default "0">
PK
!<Y:Ugg2chrome/en-US/locale/en-US/global/dialog.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

button-accept=OK
button-cancel=Cancel
button-help=Help
button-disclosure=More Info
accesskey-accept=
accesskey-cancel=
accesskey-help=H
accesskey-disclosure=I
PK
!<Cl2chrome/en-US/locale/en-US/global/dialogOverlay.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- WARNING!!! This file is obsoleted by the dialog.xml widget -->

<!-- OK Cancel Buttons -->
<!ENTITY okButton.label      "OK">
<!ENTITY cancelButton.label  "Cancel">
<!ENTITY helpButton.label    "Help">
PK
!<d3chrome/en-US/locale/en-US/global/dom/dom.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

KillScriptTitle=Warning: Unresponsive script
KillScriptMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete.
KillScriptWithDebugMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
KillScriptLocation=Script: %S
StopScriptButton=Stop script
DebugScriptButton=Debug script
WaitForScriptButton=Continue
DontAskAgain=&Don’t ask me again
JSURLLoadBlockedWarning=Attempt to load a javascript: URL from one host\nin a window displaying content from another host\nwas blocked by the security manager.
WindowCloseBlockedWarning=Scripts may not close windows that were not opened by script.
OnBeforeUnloadTitle=Are you sure?
OnBeforeUnloadMessage=This page is asking you to confirm that you want to leave - data you have entered may not be saved.
OnBeforeUnloadStayButton=Stay on Page
OnBeforeUnloadLeaveButton=Leave Page
UnexpectedCanvasVariantStyle=canvas: an attempt to set strokeStyle or fillStyle to a value that is neither a string, a CanvasGradient, or a CanvasPattern was ignored.
EmptyGetElementByIdParam=Empty string passed to getElementById().
LowMemoryTitle=Warning: Low memory
LowMemoryMessage=A script on this page has been stopped due to a low memory condition.
SpeculationFailed=An unbalanced tree was written using document.write() causing data from the network to be reparsed. For more information https://developer.mozilla.org/en/Optimizing_Your_Pages_for_Speculative_Parsing
DocumentWriteIgnored=A call to document.write() from an asynchronously-loaded external script was ignored.
# LOCALIZATION NOTE (EditorFileDropFailed): Do not translate contenteditable, %S is the error message explaining why the drop failed.
EditorFileDropFailed=Dropping a file into a contenteditable element failed: %S.
FormValidationTextTooLong=Please shorten this text to %S characters or less (you are currently using %S characters).
FormValidationTextTooShort=Please use at least %S characters (you are currently using %S characters).
FormValidationValueMissing=Please fill out this field.
FormValidationCheckboxMissing=Please check this box if you want to proceed.
FormValidationRadioMissing=Please select one of these options.
FormValidationFileMissing=Please select a file.
FormValidationSelectMissing=Please select an item in the list.
FormValidationInvalidEmail=Please enter an email address.
FormValidationInvalidURL=Please enter a URL.
FormValidationInvalidDate =Please enter a valid date.
FormValidationPatternMismatch=Please match the requested format.
# LOCALIZATION NOTE (FormValidationPatternMismatchWithTitle): %S is the (possibly truncated) title attribute value.
FormValidationPatternMismatchWithTitle=Please match the requested format: %S.
# LOCALIZATION NOTE (FormValidationNumberRangeOverflow): %S is a number.
FormValidationNumberRangeOverflow=Please select a value that is no more than %S.
# LOCALIZATION NOTE (FormValidationDateTimeRangeOverflow): %S is a date or a time.
FormValidationDateTimeRangeOverflow=Please select a value that is no later than %S.
# LOCALIZATION NOTE (FormValidationNumberRangeUnderflow): %S is a number.
FormValidationNumberRangeUnderflow=Please select a value that is no less than %S.
# LOCALIZATION NOTE (FormValidationDateTimeRangeUnderflow): %S is a date or a time.
FormValidationDateTimeRangeUnderflow=Please select a value that is no earlier than %S.
# LOCALIZATION NOTE (FormValidationStepMismatch): both %S can be a number, a date or a time.
FormValidationStepMismatch=Please select a valid value. The two nearest valid values are %S and %S.
# LOCALIZATION NOTE (FormValidationStepMismatchOneValue): %S can be a number, a date or a time. This is called instead of FormValidationStepMismatch when the second value is the same as the first.
FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S.
FormValidationBadInputNumber=Please enter a number.
GetAttributeNodeWarning=Use of getAttributeNode() is deprecated. Use getAttribute() instead.
SetAttributeNodeWarning=Use of setAttributeNode() is deprecated. Use setAttribute() instead.
GetAttributeNodeNSWarning=Use of getAttributeNodeNS() is deprecated. Use getAttributeNS() instead.
SetAttributeNodeNSWarning=Use of setAttributeNodeNS() is deprecated. Use setAttributeNS() instead.
RemoveAttributeNodeWarning=Use of removeAttributeNode() is deprecated. Use removeAttribute() instead.
CreateAttributeWarning=Use of document.createAttribute() is deprecated. Use element.setAttribute() instead.
CreateAttributeNSWarning=Use of document.createAttributeNS() is deprecated. Use element.setAttributeNS() instead.
NodeValueWarning=Use of attributes’ nodeValue attribute is deprecated. Use value instead.
TextContentWarning=Use of attributes’ textContent attribute is deprecated. Use value instead.
EnablePrivilegeWarning=Use of enablePrivilege is deprecated.  Please use code that runs with the system principal (e.g. an extension) instead.
nsIJSONDecodeDeprecatedWarning=nsIJSON.decode is deprecated.  Please use JSON.parse instead.
nsIJSONEncodeDeprecatedWarning=nsIJSON.encode is deprecated.  Please use JSON.stringify instead.
nsIDOMWindowInternalWarning=Use of nsIDOMWindowInternal is deprecated. Use nsIDOMWindow instead.
FullscreenDeniedDisabled=Request for fullscreen was denied because Fullscreen API is disabled by user preference.
FullscreenDeniedFocusedPlugin=Request for fullscreen was denied because a windowed plugin is focused.
FullscreenDeniedHidden=Request for fullscreen was denied because the document is no longer visible.
FullscreenDeniedContainerNotAllowed=Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
FullscreenDeniedNotInputDriven=Request for fullscreen was denied because Element.requestFullscreen() was not called from inside a short running user-generated event handler.
FullscreenDeniedNotHTMLSVGOrMathML=Request for fullscreen was denied because requesting element is not <svg>, <math>, or an HTML element.
FullscreenDeniedNotInDocument=Request for fullscreen was denied because requesting element is no longer in its document.
FullscreenDeniedMovedDocument=Request for fullscreen was denied because requesting element has moved document.
FullscreenDeniedLostWindow=Request for fullscreen was denied because we no longer have a window.
FullscreenDeniedSubDocFullscreen=Request for fullscreen was denied because a subdocument of the document requesting fullscreen is already fullscreen.
FullscreenDeniedNotDescendant=Request for fullscreen was denied because requesting element is not a descendant of the current fullscreen element.
FullscreenDeniedNotFocusedTab=Request for fullscreen was denied because requesting element is not in the currently focused tab.
RemovedFullscreenElement=Exited fullscreen because fullscreen element was removed from document.
FocusedWindowedPluginWhileFullscreen=Exited fullscreen because windowed plugin was focused.
PointerLockDeniedDisabled=Request for pointer lock was denied because Pointer Lock API is disabled by user preference.
PointerLockDeniedInUse=Request for pointer lock was denied because the pointer is currently controlled by a different document.
PointerLockDeniedNotInDocument=Request for pointer lock was denied because the requesting element is not in a document.
PointerLockDeniedSandboxed=Request for pointer lock was denied because Pointer Lock API is restricted via sandbox.
PointerLockDeniedHidden=Request for pointer lock was denied because the document is not visible.
PointerLockDeniedNotFocused=Request for pointer lock was denied because the document is not focused.
PointerLockDeniedMovedDocument=Request for pointer lock was denied because the requesting element has moved document.
PointerLockDeniedNotInputDriven=Request for pointer lock was denied because Element.requestPointerLock() was not called from inside a short running user-generated event handler, and the document is not in full screen.
PointerLockDeniedFailedToLock=Request for pointer lock was denied because the browser failed to lock the pointer.
HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode.
InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn’t implement nsIWritablePropertyBag2.
# LOCALIZATION NOTE: %S is the name of the header in question
ForbiddenHeaderWarning=Attempt to set a forbidden header was denied: %S
ResponseTypeSyncXHRWarning=Use of XMLHttpRequest’s responseType attribute is no longer supported in the synchronous mode in window context.
TimeoutSyncXHRWarning=Use of XMLHttpRequest’s timeout attribute is not supported in the synchronous mode in window context.
JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
# LOCALIZATION NOTE: Do not translate AudioBufferSourceNode
MediaBufferSourceNodeResampleOutOfMemory=Insufficient memory to resample the AudioBufferSourceNode for playback.
# LOCALIZATION NOTE: Do not translate decodeAudioData.
MediaDecodeAudioDataUnknownContentType=The buffer passed to decodeAudioData contains an unknown content type.
# LOCALIZATION NOTE: Do not translate decodeAudioData.
MediaDecodeAudioDataUnknownError=An unknown error occurred while processing decodeAudioData.
# LOCALIZATION NOTE: Do not translate decodeAudioData.
MediaDecodeAudioDataInvalidContent=The buffer passed to decodeAudioData contains invalid content which cannot be decoded successfully.
# LOCALIZATION NOTE: Do not translate decodeAudioData.
MediaDecodeAudioDataNoAudio=The buffer passed to decodeAudioData does not contain any audio.
# LOCALIZATION NOTE: Do not translate HTMLMediaElement and createMediaElementSource.
MediaElementAudioSourceNodeCrossOrigin=The HTMLMediaElement passed to createMediaElementSource has a cross-origin resource, the node will output silence.
# LOCALIZATION NOTE: Do not translate MediaStream and createMediaStreamSource.
MediaStreamAudioSourceNodeCrossOrigin=The MediaStream passed to createMediaStreamSource has a cross-origin resource, the node will output silence.
MediaLoadExhaustedCandidates=All candidate resources failed to load. Media load paused.
MediaLoadSourceMissingSrc=<source> element has no “src” attribute. Media resource load failed.
# LOCALIZATION NOTE: %1$S is the Http error code the server returned (e.g. 404, 500, etc), %2$S is the URL of the media resource which failed to load.
MediaLoadHttpError=HTTP load failed with status %1$S. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %S is the URL of the media resource which failed to load.
MediaLoadInvalidURI=Invalid URI. Load of media resource %S failed.
# LOCALIZATION NOTE: %1$S is the media resource's format/codec type (basically equivalent to the file type, e.g. MP4,AVI,WMV,MOV etc), %2$S is the URL of the media resource which failed to load.
MediaLoadUnsupportedTypeAttribute=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %1$S is the "media" attribute value of the <source> element. It is a media query. %2$S is the URL of the media resource which failed to load.
MediaLoadSourceMediaNotMatched=Specified “media” attribute of “%1$S” does not match the environment. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %1$S is the MIME type HTTP header being sent by the web server, %2$S is the URL of the media resource which failed to load.
MediaLoadUnsupportedMimeType=HTTP “Content-Type” of “%1$S” is not supported. Load of media resource %2$S failed.
# LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding.
MediaLoadDecodeError=Media resource %S could not be decoded.
MediaWidevineNoWMF=Trying to play Widevine with no Windows Media Foundation. See https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
MediaWMFNeeded=To play video formats %S, you need to install extra Microsoft software, see https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
MediaPlatformDecoderNotFound=The video on this page can’t be played. Your system may not have the required video codecs for: %S
MediaUnsupportedLibavcodec=The video on this page can’t be played. Your system has an unsupported version of libavcodec
# LOCALIZATION NOTE: %1$S is the URL of the media resource, %2$S is technical information (in English)
MediaDecodeError=Media resource %1$S could not be decoded, error: %2$S
# LOCALIZATION NOTE: %1$S is the URL of the media resource, %2$S is technical information (in English)
MediaDecodeWarning=Media resource %1$S could be decoded, but with error: %2$S
# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
MediaNoDecoders=No decoders for some of the requested formats: %S
MediaCannotInitializePulseAudio=Unable to use PulseAudio
# LOCALIZATION NOTE: Do not translate "MediaRecorder".
MediaRecorderMultiTracksNotSupported=MediaRecorder does not support recording multiple tracks of the same type at this time.
# LOCALIZATION NOTE: %S is the ID of the MediaStreamTrack passed to MediaStream.addTrack(). Do not translate "MediaStreamTrack" and "AudioChannel".
MediaStreamAddTrackDifferentAudioChannel=MediaStreamTrack %S could not be added since it belongs to a different AudioChannel.
# LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
# LOCALIZATION NOTE: %S is the URL of the web page which is not served on HTTPS and thus is not encrypted and considered insecure.
MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
# LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (either an audioCapabilities or a videoCapabilities) that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
# LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (a "codecs" string in the "contentType") that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.
# LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
DOMExceptionCodeWarning=Use of DOMException’s code attribute is deprecated. Use name instead.
# LOCALIZATION NOTE: Do not translate "__exposedProps__"
NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information.
# LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver"
MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead.
# LOCALIZATION NOTE: Do not translate "Components"
ComponentsWarning=The Components object is deprecated. It will soon be removed.
PluginHangUITitle=Warning: Unresponsive plugin
PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete.
PluginHangUIWaitButton=Continue
PluginHangUIStopButton=Stop plugin
PrefixedFullscreenAPIWarning=Prefixed Fullscreen API is deprecated. Please use unprefixed API for fullscreen. For more help https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
# LOCALIZATION NOTE: Do not translate "NodeIterator" or "detach()".
NodeIteratorDetachWarning=Calling detach() on a NodeIterator no longer has an effect.
# LOCALIZATION NOTE: Do not translate "LenientThis" and "this"
LenientThisWarning=Ignoring get or set of property that has [LenientThis] because the “this” object is incorrect.
# LOCALIZATION NOTE: Do not translate "nsIDOMWindowUtils", "getWindowWithOuterId", or "nsIWindowMediator"
GetWindowWithOuterIdWarning=Use of nsIDOMWindowUtils.getOuterWindowWithId() is deprecated.  Instead, use the nsIWindowMediator method of the same name.
# LOCALIZATION NOTE: Do not translate "getPreventDefault" or "defaultPrevented".
GetPreventDefaultWarning=Use of getPreventDefault() is deprecated.  Use defaultPrevented instead.
# LOCALIZATION NOTE: Do not translate "getUserData", "setUserData", "WeakMap", or "element.dataset".
GetSetUserDataWarning=Use of getUserData() or setUserData() is deprecated.  Use WeakMap or element.dataset instead.
# LOCALIZATION NOTE: Do not translate "mozGetAsFile" or "toBlob"
MozGetAsFileWarning=The non-standard mozGetAsFile method is deprecated and will soon be removed.  Use the standard toBlob method instead.
# LOCALIZATION NOTE: Do not translate "captureEvents()" or "addEventListener()"
UseOfCaptureEventsWarning=Use of captureEvents() is deprecated. To upgrade your code, use the DOM 2 addEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.addEventListener
# LOCALIZATION NOTE: Do not translate "releaseEvents()" or "removeEventListener()"
UseOfReleaseEventsWarning=Use of releaseEvents() is deprecated. To upgrade your code, use the DOM 2 removeEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.removeEventListener
# LOCALIZATION NOTE: Do not translate "document.load()" or "XMLHttpRequest"
UseOfDOM3LoadMethodWarning=Use of document.load() is deprecated. To upgrade your code, use the DOM XMLHttpRequest object. For more help https://developer.mozilla.org/en/XMLHttpRequest
# LOCALIZATION NOTE: Do not translate "window.showModalDialog()" or "window.open()"
ShowModalDialogWarning=Use of window.showModalDialog() is deprecated. Use window.open() instead. For more help https://developer.mozilla.org/en-US/docs/Web/API/Window.open
# LOCALIZATION NOTE: Do not translate "window._content" or "window.content"
Window_ContentWarning=window._content is deprecated.  Please use window.content instead.
# LOCALIZATION NOTE: Do not translate "XMLHttpRequest"
SyncXMLHttpRequestWarning=Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help http://xhr.spec.whatwg.org/
ImplicitMetaViewportTagFallback=No meta-viewport tag found. Please explicitly specify one to prevent unexpected behavioural changes in future versions. For more help https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
# LOCALIZATION NOTE: Do not translate "window.controllers/Controllers"
Window_Cc_ontrollersWarning=window.controllers/Controllers is deprecated. Do not use it for UA detection.
ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead.
# LOCALIZATION NOTE: Do not translate "IndexedDB".
IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
# LOCALIZATION NOTE: Do not translate Will-change, %1$S,%2$S are numbers.
IgnoringWillChangeOverBudgetWarning=Will-change memory consumption is too high. Budget limit is the document surface area multiplied by %1$S (%2$S px). Occurrences of will-change over the budget will be ignored.
# LOCALIZATION NOTE: Do not translate "Worker".
HittingMaxWorkersPerDomain2=A Worker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The Worker is now queued and will be started after some of the other workers have completed.
# LOCALIZATION NOTE: Do not translate "setVelocity", "PannerNode", "AudioListener", "speedOfSound" and "dopplerFactor"
PannerNodeDopplerWarning=Use of setVelocity on the PannerNode and AudioListener, and speedOfSound and dopplerFactor on the AudioListener are deprecated and those members will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/AudioListener#Deprecated_features
# LOCALIZATION NOTE: Do not translate "Application Cache API", "AppCache" and "ServiceWorker".
AppCacheWarning=The Application Cache API (AppCache) is deprecated and will be removed at a future date.  Please consider using ServiceWorker for offline support.
# LOCALIZATION NOTE: Do not translate "Worker".
EmptyWorkerSourceWarning=Attempting to create a Worker from an empty source. This is probably unintentional.
WebrtcDeprecatedPrefixWarning=WebRTC interfaces with the “moz” prefix (mozRTCPeerConnection, mozRTCSessionDescription, mozRTCIceCandidate) have been deprecated.
NavigatorGetUserMediaWarning=navigator.mozGetUserMedia has been replaced by navigator.mediaDevices.getUserMedia
# LOCALIZATION NOTE: Do not translate "RTCPeerConnection", "getLocalStreams", "getRemoteStreams", "getSenders" or "getReceivers".
RTCPeerConnectionGetStreamsWarning=RTCPeerConnection.getLocalStreams/getRemoteStreams are deprecated. Use RTCPeerConnection.getSenders/getReceivers instead.
# LOCALIZATION NOTE: Do not translate "ServiceWorker". %S is a URL.
InterceptionFailedWithURL=Failed to load ‘%S’. A ServiceWorker intercepted the request and encountered an unexpected error.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "FetchEvent", "no-cors", "opaque", "Response", or "RequestMode". %1$S is a URL. %2$S is a RequestMode value.
BadOpaqueInterceptionRequestModeWithURL=Failed to load ‘%1$S’. A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while handling a ‘%2$S’ FetchEvent. Opaque Response objects are only valid when the RequestMode is ‘no-cors’.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Error", "Response", "FetchEvent.respondWith()", or "fetch()". %S is a URL.
InterceptedErrorResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". %S is a URL.
InterceptedUsedResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent". %s is a URL.
BadOpaqueRedirectInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while handling a non-navigation FetchEvent.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "RedirectMode" or "follow". %S is a URL.
BadRedirectModeInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed a redirected Response to FetchEvent.respondWith() while RedirectMode is not ‘follow’.
# LOCALIZATION NOTE: Do not translate "ServiceWorker" or "FetchEvent.preventDefault()". %S is a URL.
InterceptionCanceledWithURL=Failed to load ‘%S’. A ServiceWorker canceled the load by calling FetchEvent.preventDefault().
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", or "FetchEvent.respondWith()". %1$S is a URL. %2$S is an error string.
InterceptionRejectedResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that rejected with ‘%2$S’.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", "FetchEvent.respondWith()", or "Response". %1$S is a URL. %2$S is an error string.
InterceptedNonResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value ‘%2$S’.
# LOCALIZATION NOTE: Do not translate "mozImageSmoothingEnabled", or "imageSmoothingEnabled"
PrefixedImageSmoothingEnabledWarning=Use of mozImageSmoothingEnabled is deprecated. Please use the unprefixed imageSmoothingEnabled property instead.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Service-Worker-Allowed" or "HTTP". %1$S and %2$S are URLs.
ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope ‘%1$S’ is not under the max scope allowed ‘%2$S’. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker, %2$S is a stringified numeric HTTP status code like "404" and %3$S is a URL.
ServiceWorkerRegisterNetworkError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Load failed with status %2$S for script ‘%3$S’.
# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker, %2$S is a MIME Media Type like "text/plain" and %3$S is a URL.
ServiceWorkerRegisterMimeTypeError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Bad Content-Type of ‘%2$S’ received for script ‘%3$S’.  Must be ‘text/javascript’, ‘application/x-javascript’, or ‘application/javascript’.
# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker.
ServiceWorkerGraceTimeoutTermination=Terminating ServiceWorker for scope ‘%1$S’ with pending waitUntil/respondWith promises because of grace timeout.
# LOCALIZATION NOTE (ServiceWorkerNoFetchHandler): Do not translate "Fetch".
ServiceWorkerNoFetchHandler=Fetch event handlers must be added during the worker script’s initial evaluation.
ExecCommandCutCopyDeniedNotInputDriven=document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
ManifestShouldBeObject=Manifest should be an object.
ManifestScopeURLInvalid=The scope URL is invalid.
ManifestScopeNotSameOrigin=The scope URL must be same origin as document.
ManifestStartURLOutsideScope=The start URL is outside the scope, so the scope is invalid.
ManifestStartURLInvalid=The start URL is invalid.
ManifestStartURLShouldBeSameOrigin=The start URL must be same origin as document.
# LOCALIZATION NOTE: %1$S is the name of the object whose property is invalid. %2$S is the name of the invalid property. %3$S is the expected type of the property value. E.g. "Expected the manifest's start_url member to be a string."
ManifestInvalidType=Expected the %1$S’s %2$S member to be a %3$S.
# LOCALIZATION NOTE: %1$S is the name of the property whose value is invalid. %2$S is the (invalid) value of the property. E.g. "theme_color: 42 is not a valid CSS color."
ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
PatternAttributeCompileFailure=Unable to check <input pattern='%S'> because the pattern is not a valid regexp: %S
# LOCALIZATION NOTE: Do not translate "postMessage" or DOMWindow. %S values are origins, like https://domain.com:port
TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
# LOCALIZATION NOTE: Do not translate 'YouTube'. %S values are origins, like https://domain.com:port
RewriteYouTubeEmbed=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
# LOCALIZATION NOTE: Do not translate 'YouTube'. %S values are origins, like https://domain.com:port
RewriteYouTubeEmbedPathParams=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Params were unsupported by iframe embeds and converted. Please update page to use iframe instead of embed/object, if possible.
# LOCALIZATION NOTE: This error is reported when the "Encryption" header for an
# incoming push message is missing or invalid. Do not translate "ServiceWorker",
# "Encryption", and "salt". %1$S is the ServiceWorker scope URL.
PushMessageBadEncryptionHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption’ header must include a unique ‘salt‘ parameter for each message. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
# LOCALIZATION NOTE: This error is reported when the "Crypto-Key" header for an
# incoming push message is missing or invalid. Do not translate "ServiceWorker",
# "Crypto-Key", and "dh". %1$S is the ServiceWorker scope URL.
PushMessageBadCryptoKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Crypto-Key‘ header must include a ‘dh‘ parameter containing the app server’s public key. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt because the deprecated
# "Encryption-Key" header for an incoming push message is missing or invalid.
# Do not translate "ServiceWorker", "Encryption-Key", "dh", "Crypto-Key", and
# "Content-Encoding: aesgcm". %1$S is the ServiceWorker scope URL.
PushMessageBadEncryptionKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption-Key’ header must include a ‘dh‘ parameter. This header is deprecated and will soon be removed. Please use ‘Crypto-Key‘ with ‘Content-Encoding: aesgcm‘ instead. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
# because the "Content-Encoding" header is missing or contains an
# unsupported encoding. Do not translate "ServiceWorker", "Content-Encoding",
# "aesgcm", and "aesgcm128". %1$S is the ServiceWorker scope URL.
PushMessageBadEncodingHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Content-Encoding‘ header must be ‘aesgcm‘. ‘aesgcm128‘ is allowed, but deprecated and will soon be removed. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
# because the "dh" parameter is not valid base64url. Do not translate
# "ServiceWorker", "dh", "Crypto-Key", and "base64url". %1$S is the
# ServiceWorker scope URL.
PushMessageBadSenderKey=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘dh‘ parameter in the ‘Crypto-Key‘ header must be the app server’s Diffie-Hellman public key, base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C) and in “uncompressed” or “raw” form (65 bytes before encoding). See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
# because the "salt" parameter is not valid base64url. Do not translate
# "ServiceWorker", "salt", "Encryption", and "base64url". %1$S is the
# ServiceWorker scope URL.
PushMessageBadSalt=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘salt‘ parameter in the ‘Encryption‘ header must be base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C), and be at least 16 bytes before encoding. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
# because the "rs" parameter is not a number, or is less than the pad size.
# Do not translate "ServiceWorker", "rs", or "Encryption". %1$S is the
# ServiceWorker scope URL. %2$S is the minimum value (1 for aesgcm128, 2 for
# aesgcm).
PushMessageBadRecordSize=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘rs‘ parameter of the ‘Encryption‘ header must be between %2$S and 2^36-31, or omitted entirely. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
# because an encrypted record is shorter than the pad size, the pad is larger
# than the record, or any of the padding bytes are non-zero. Do not translate
# "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is the pad size
# (1 for aesgcm128, 2 for aesgcm).
PushMessageBadPaddingError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. A record in the encrypted message was not padded correctly. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
# LOCALIZATION NOTE: This error is reported when push message decryption fails
# and no specific error info is available. Do not translate "ServiceWorker".
# %1$S is the ServiceWorker scope URL.
PushMessageBadCryptoError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. For help with encryption, please see https://developer.mozilla.org/docs/Web/API/Push_API/Using_the_Push_API#Encryption
# LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
# LOCALIZATION NOTE: 'ImageBitmapRenderingContext.transferImageBitmap' and 'ImageBitmapRenderingContext.transferFromImageBitmap' should not be translated
ImageBitmapRenderingContext_TransferImageBitmap=ImageBitmapRenderingContext.transferImageBitmap is deprecated and will be removed soon. Use ImageBitmapRenderingContext.transferFromImageBitmap instead.
ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated.
IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
# LOCALIZATION NOTE: Do not translate ".jpeg"
GenericImageNameJPEG=image.jpeg
# LOCALIZATION NOTE: Do not translate ".gif"
GenericImageNameGIF=image.gif
# LOCALIZATION NOTE: Do not translate ".png"
GenericImageNamePNG=image.png
GenericFileName=file
# LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
LargeAllocationSuccess=This page was loaded in a new process due to a Large-Allocation header.
# LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate GET.
LargeAllocationNonGetRequest=A Large-Allocation header was ignored due to the load being triggered by a non-GET request.
# LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate `window.opener`.
LargeAllocationNotOnlyToplevelInTabGroup=A Large-Allocation header was ignored due to the presence of windows which have a reference to this browsing context through the frame hierarchy or window.opener.
# LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
LargeAllocationNonE10S=A Large-Allocation header was ignored due to the document not being loaded out of process.
GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
# LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name.
LargeAllocationNonWin32=This page would be loaded in a new process due to a Large-Allocation header, however Large-Allocation process creation is disabled on non-Win32 platforms.
# LOCALIZATION NOTE: Do not translate URL.createObjectURL(MediaStream).
URLCreateObjectURL_MediaStreamWarning=URL.createObjectURL(MediaStream) is deprecated and will be removed soon.
# LOCALIZATION NOTE: Do not translate MozAutoGainControl or autoGainControl.
MozAutoGainControlWarning=mozAutoGainControl is deprecated. Use autoGainControl instead.
# LOCALIZATION NOTE: Do not translate mozNoiseSuppression or noiseSuppression.
MozNoiseSuppressionWarning=mozNoiseSuppression is deprecated. Use noiseSuppression instead.
# LOCALIZATION NOTE: Do not translate xml:base.
XMLBaseAttributeWarning=Use of xml:base attribute is deprecated and will be removed soon. Please remove any use of it.
# LOCALIZATION NOTE: %S is the tag name of the element that starts the loop
SVGReferenceLoopWarning=There is an SVG <%S> reference loop in this document, which will prevent the document rendering correctly.
# LOCALIZATION NOTE: %S is the tag name of the element that starts the chain
SVGReferenceChainLengthExceededWarning=There is an SVG <%S> reference chain which is too long in this document, which will prevent the document rendering correctly.
# LOCALIZATION NOTE: Do not translate "<script>".
ScriptSourceEmpty=‘%S’ attribute of <script> element is empty.
# LOCALIZATION NOTE: Do not translate "<script>".
ScriptSourceInvalidUri=‘%S’ attribute of <script> element is not a valid URI: “%S”
# LOCALIZATION NOTE: Do not translate "<script>".
ScriptSourceLoadFailed=Loading failed for the <script> with source “%S”.
# LOCALIZATION NOTE: Do not translate "<script>".
ScriptSourceMalformed=<script> source URI is malformed: “%S”.
# LOCALIZATION NOTE: Do not translate "<script>".
ScriptSourceNotAllowed=<script> source URI is not allowed in this document: “%S”.
# LOCALIZATION NOTE: %1$S is the invalid property value and %2$S is the property name.
InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid according to the syntax for “%2$S”.
PK
!<5\4chrome/en-US/locale/en-US/global/editMenuOverlay.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY editMenu.label            "Edit">
<!ENTITY editMenu.accesskey        "e">
<!ENTITY undoCmd.label             "Undo">
<!ENTITY undoCmd.key               "Z">
<!ENTITY undoCmd.accesskey         "u">
<!ENTITY redoCmd.label             "Redo">
<!ENTITY redoCmd.key               "Y">
<!ENTITY redoCmd.accesskey         "r">
<!ENTITY cutCmd.label              "Cut">
<!ENTITY cutCmd.key                "X">
<!ENTITY cutCmd.accesskey          "t">
<!ENTITY copyCmd.label             "Copy">
<!ENTITY copyCmd.key               "C">
<!ENTITY copyCmd.accesskey         "c">
<!ENTITY pasteCmd.label            "Paste">
<!ENTITY pasteCmd.key              "V">
<!ENTITY pasteCmd.accesskey        "p">
<!ENTITY deleteCmd.label           "Delete">
<!ENTITY deleteCmd.accesskey       "d">
<!ENTITY selectAllCmd.label        "Select All">
<!ENTITY selectAllCmd.key          "A">
<!ENTITY selectAllCmd.accesskey    "a">
<!ENTITY findCmd.label             "Find">
<!ENTITY findCmd.key               "F">
<!ENTITY findCmd.accesskey         "F">
<!ENTITY findAgainCmd.label        "Find Again">
<!ENTITY findAgainCmd.key          "G">
<!ENTITY findAgainCmd.key2         "VK_F3">
<!ENTITY findAgainCmd.accesskey    "g">
<!ENTITY findPreviousCmd.label     "Find Previous">
<!ENTITY findPreviousCmd.accesskey "v">
PK
!<Qars6chrome/en-US/locale/en-US/global/extensions.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

csp.error.missing-directive = Policy is missing a required ‘%S’ directive

#LOCALIZATION NOTE (csp.error.illegal-keyword) %1$S is the name of a CSP directive, such as "script-src". %2$S is the name of a CSP keyword, usually 'unsafe-inline'.
csp.error.illegal-keyword = ‘%1$S’ directive contains a forbidden %2$S keyword

#LOCALIZATION NOTE (csp.error.illegal-protocol) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
csp.error.illegal-protocol = ‘%1$S’ directive contains a forbidden %2$S: protocol source

#LOCALIZATION NOTE (csp.error.missing-host) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
csp.error.missing-host = %2$S: protocol requires a host in ‘%1$S’ directives

#LOCALIZATION NOTE (csp.error.missing-source) %1$S is the name of a CSP directive, such as "script-src". %2$S is the name of a CSP source, usually 'self'.
csp.error.missing-source = ‘%1$S’ must include the source %2$S

#LOCALIZATION NOTE (csp.error.illegal-host-wildcard) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
csp.error.illegal-host-wildcard = %2$S: wildcard sources in ‘%1$S’ directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)

#LOCALIZATION NOTE (uninstall.confirmation.title) %S is the name of the extension which is about to be uninstalled.
uninstall.confirmation.title = Uninstall %S

#LOCALIZATION NOTE (uninstall.confirmation.message) %S is the name of the extension which is about to be uninstalled.
uninstall.confirmation.message = The extension “%S” is requesting to be uninstalled. What would you like to do?

uninstall.confirmation.button-0.label = Uninstall
uninstall.confirmation.button-1.label = Keep Installed

saveaspdf.saveasdialog.title = Save As
PK
!<i
11;chrome/en-US/locale/en-US/global/fallbackMenubar.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# OSX only. Default menu label when there is no xul menubar.

quitMenuitem.label=Quit
quitMenuitem.key=q
PK
!<Ǡ=5chrome/en-US/locale/en-US/global/filefield.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#### Change Action

downloadHelperNoneSelected=None Selected
PK
!<f6chrome/en-US/locale/en-US/global/filepicker.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE: The extensions to which these descriptions refer
#                    now live in toolkit/content/filepicker.properties
allTitle=All Files
htmlTitle=HTML Files
textTitle=Text Files
imageTitle=Image Files
xmlTitle=XML Files
xulTitle=XUL Files
appsTitle=Applications
audioTitle=Audio Files
videoTitle=Video Files

dirTextInputLabel=Directory name:
dirTextInputAccesskey=n

confirmTitle=Confirm
confirmFileReplacing=%S already exists.\nDo you want to replace it?
openButtonLabel=Open
saveButtonLabel=Save
selectFolderButtonLabel=Select
noButtonLabel=No
formatLabel=Format:

errorOpenFileDoesntExistTitle=Error opening %S
errorOpenFileDoesntExistMessage=File %S doesn’t exist
errorDirDoesntExistTitle=Error accessing %S
errorDirDoesntExistMessage=Directory %S doesn’t exist

errorOpeningFileTitle=Error opening %S
openWithoutPermissionMessage_file=File %S is not readable

errorSavingFileTitle=Error saving %S
saveParentIsFileMessage=%S is a file, can’t save %S
saveParentDoesntExistMessage=Path %S doesn’t exist, can’t save %S

saveWithoutPermissionMessage_file=File %S is not writable.
saveWithoutPermissionMessage_dir=Cannot create file. Directory %S is not writable.

errorNewDirDoesExistTitle=Error creating %S
errorNewDirDoesExistMessage=A file named %S already exists, directory cannot be created.

errorCreateNewDirTitle=Error creating %S
errorCreateNewDirMessage=Directory %S could not be created
errorCreateNewDirIsFileMessage=Directory cannot be created, %S is a file
errorCreateNewDirPermissionMessage=Directory cannot be created, %S not writable

promptNewDirTitle=Create new directory
promptNewDirMessage=Directory name:

errorPathProblemTitle=Unknown Error
errorPathProblemMessage=An unknown error occurred (path %S)
PK
!<@	*,chrome/en-US/locale/en-US/global/findbar.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- LOCALIZATION NOTE : FILE This file contains the entities needed to -->
<!-- LOCALIZATION NOTE : FILE use the Find Bar. --> 

<!ENTITY next.tooltip "Find the next occurrence of the phrase">
<!ENTITY previous.tooltip "Find the previous occurrence of the phrase">
<!ENTITY findCloseButton.tooltip "Close find bar">
<!ENTITY highlightAll.label "Highlight All">
<!ENTITY highlightAll.accesskey "l">
<!ENTITY highlightAll.tooltiptext "Highlight all occurrences of the phrase">
<!ENTITY caseSensitive.label "Match Case">
<!ENTITY caseSensitive.accesskey "c">
<!ENTITY caseSensitive.tooltiptext "Search with case sensitivity">
<!ENTITY entireWord.label "Whole Words">
<!ENTITY entireWord.accesskey "w">
<!ENTITY entireWord.tooltiptext "Search whole words only">
PK
!<5EE3chrome/en-US/locale/en-US/global/findbar.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# strings used by the Find bar, split from browser.properties
NotFound=Phrase not found
WrappedToTop=Reached end of page, continued from top
WrappedToBottom=Reached top of page, continued from bottom
NormalFind=Find in page
FastFind=Quick find
FastFindLinks=Quick find (links only)
CaseSensitive=(Case sensitive)
EntireWord=(Whole words only)
# LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is currently selected match and #2 the total amount of matches.
FoundMatches=#1 of #2 match;#1 of #2 matches
# LOCALIZATION NOTE (FoundMatchesCountLimit): Semicolon-separated list of plural
# forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the total amount of matches allowed before counting stops.
FoundMatchesCountLimit=More than #1 match;More than #1 matches
PK
!<XEE/chrome/en-US/locale/en-US/global/finddialog.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from finddialog.xul -->

<!ENTITY findDialog.title "Find in This Page">
<!ENTITY findField.label "Find what:">
<!ENTITY findField.accesskey "n">
<!ENTITY caseSensitiveCheckbox.label "Match case">
<!ENTITY caseSensitiveCheckbox.accesskey "c">
<!ENTITY wrapCheckbox.label "Wrap">
<!ENTITY wrapCheckbox.accesskey "W">
<!ENTITY findButton.label "Find Next">
<!ENTITY findButton.accesskey "F">
<!ENTITY cancelButton.label "Cancel">
<!ENTITY closeButton.label "Close">
<!ENTITY up.label "Up">
<!ENTITY up.accesskey "U">
<!ENTITY down.label "Down">
<!ENTITY down.accesskey "D">
<!ENTITY direction.label "Direction">
PK
!<A!06chrome/en-US/locale/en-US/global/finddialog.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

notFoundWarning=The text you entered was not found.
notFoundTitle=FindPK
!<[{9chrome/en-US/locale/en-US/global/global-strres.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

16389=An unknown error has occurred (%1$S)
PK
!<e{+chrome/en-US/locale/en-US/global/global.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY locale.dir "ltr">
PK
!<&%%/chrome/en-US/locale/en-US/global/globalKeys.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY openHelp.commandkey     "VK_F1">
<!ENTITY openHelpMac.commandkey  "?">
PK
!<'5mm)chrome/en-US/locale/en-US/global/intl.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file contains all localizable skin settings such as 
 *   font, layout, and geometry
 */
window { 
  font: 3mm tahoma,arial,helvetica,sans-serif;
}
PK
!<T0chrome/en-US/locale/en-US/global/intl.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE (general.useragent.locale):
# This is the valid BCP 47 language tag representing your locale.
#
# In most cases, this will simply be your locale code. However, in rare cases
# (such as 'jp-JP-mac'), you may need to modify your locale code in order to
# make it a valid BCP 47 language tag. (If your locale code does not include a
# region subtag, do not include one in the language tag representing your
# locale.)
general.useragent.locale=en-US

# LOCALIZATION NOTE (intl.accept_languages):
# This is a comma-separated list of valid BCP 47 language tags.
#
# Begin with the value of 'general.useragent.locale'. Next, include language
# tags for other languages that you expect most users of your locale to be
# able to speak, so that their browsing experience degrades gracefully if
# content is not available in their primary language.
#
# It is recommended that you include "en-US, en" at the end of the list as a
# last resort. However, if you know that users of your locale would prefer a
# different variety of English, or if they are not likely to understand
# English at all, you may opt to include a different English language tag, or
# to exclude English altogether.
#
# For example, the Breton [br] locale might consider including French and
# British English in their list, since those languages are commonly spoken in
# the same area as Breton:
# intl.accept_languages=br, fr-FR, fr, en-GB, en
intl.accept_languages=en-US, en

# LOCALIZATION NOTE (font.language.group):
# This preference controls the initial setting of the language drop-down menu
# in the Content > Fonts & Colors > Advanced preference panel.
#
# Set it to the value of one of the menuitems in the "selectLangs" menulist in
# http://dxr.mozilla.org/mozilla-central/source/browser/components/preferences/fonts.xul
font.language.group=x-western

# LOCALIZATION NOTE (intl.charset.detector):
# This preference controls the initial setting for the character encoding
# detector. Valid values are ja_parallel_state_machine for Japanese, ruprob
# for Russian and ukprob for Ukrainian and the empty string to turn detection
# off. The value must be empty for locales other than Japanese, Russian and
# Ukrainian.
intl.charset.detector=

# LOCALIZATION NOTE (pluralRule): Pick the appropriate plural rule for your
# language. This will determine how many plural forms of a word you will need
# to provide and in what order.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
pluralRule=1

# LOCALIZATION NOTE (intl.menuitems.alwaysappendaccesskeys, intl.menuitems.insertseparatorbeforeaccesskeys):
# Valid values are: true, false, <empty string>
# Missing preference or empty value equals false.
intl.menuitems.alwaysappendaccesskeys=
intl.menuitems.insertseparatorbeforeaccesskeys=true
PK
!<+[80chrome/en-US/locale/en-US/global/keys.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE : FILE This file contains the application's labels for keys on the keyboard.
#                     If you decide to translate this file, you should translate it based on
#                     the prevelant kind of keyboard for your target user.
# LOCALIZATION NOTE : There are two types of keys, those w/ text on their labels
#                     and those w/ glyphs.
# LOCALIZATION NOTE : VK_<…> represents a key on the keyboard.
#
# For more information please see bugzilla bug 90888.

# F1..F10 should probably not be translated unless there are keyboards that actually have other labels
# F11..F20 might be something else, but are really keyboard specific and not region/language specific
# there are actually two different F11/F12 keys, I don't know which one these labels represent.
# eg, F13..F20 on a sparc keyboard are labeled Props, Again .. Find, Cut
# sparc also has Stop, Again and F11/F12. VK_F11/VK_F12 probably map to Stop/Again
# LOCALIZATION NOTE : BLOCK Do not translate the next block
VK_F1=F1
VK_F2=F2
VK_F3=F3
VK_F4=F4
VK_F5=F5
VK_F6=F6
VK_F7=F7
VK_F8=F8
VK_F9=F9
VK_F10=F10

VK_F11=F11
VK_F12=F12
VK_F13=F13
VK_F14=F14
VK_F15=F15
VK_F16=F16
VK_F17=F17
VK_F18=F18
VK_F19=F19
VK_F20=F20
# LOCALIZATION NOTE : BLOCK end do not translate block

# LOCALIZATION NOTE : BLOCK GLYPHS, DO translate this block
VK_UP=Up Arrow
VK_DOWN=Down Arrow
VK_LEFT=Left Arrow
VK_RIGHT=Right Arrow
VK_PAGE_UP=Page Up
VK_PAGE_DOWN=Page Down
# LOCALIZATION NOTE : BLOCK end GLYPHS

# Enter, backspace, and Tab might have both glyphs and text
# if the keyboards usually have a glyph,
# if there is a meaningful translation,
# or if keyboards are localized
# then translate them or insert the appropriate glyph
# otherwise you should probably just translate the glyph regions

# LOCALIZATION NOTE : BLOCK maybe GLYPHS
VK_RETURN=Return
VK_TAB=Tab
VK_BACK=Backspace
VK_DELETE=Del
# LOCALIZATION NOTE : BLOCK end maybe GLYPHS
# LOCALIZATION NOTE : BLOCK typing state keys
VK_HOME=Home
VK_END=End

VK_ESCAPE=Esc
VK_INSERT=Ins
# LOCALIZATION NOTE : BLOCK end
PK
!<$++9chrome/en-US/locale/en-US/global/languageNames.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

aa = Afar
ab = Abkhazian
ae = Avestan
af = Afrikaans
ak = Akan
am = Amharic
an = Aragonese
ar = Arabic
as = Assamese
ast = Asturian
av = Avaric
ay = Aymara
az = Azerbaijani
ba = Bashkir
be = Belarusian
bg = Bulgarian
bh = Bihari
bi = Bislama
bm = Bambara
bn = Bengali
bo = Tibetan
br = Breton
bs = Bosnian
ca = Catalan
ce = Chechen
ch = Chamorro
co = Corsican
cr = Cree
cs = Czech
csb = Kashubian
cu = Church Slavic
cv = Chuvash
cy = Welsh
da = Danish
de = German
dsb = Lower Sorbian
dv = Divehi
dz = Dzongkha
ee = Ewe
el = Greek
en = English
eo = Esperanto
es = Spanish
et = Estonian
eu = Basque
fa = Persian
ff = Fulah
fi = Finnish
fj = Fijian
fo = Faroese
fr = French
fur = Friulian
fy = Frisian
ga = Irish
gd = Scottish Gaelic
gl = Galician
gn = Guarani
gu = Gujarati
gv = Manx
ha = Hausa
haw = Hawaiian
he = Hebrew
hi = Hindi
hil = Hiligaynon
ho = Hiri Motu
hr = Croatian
hsb = Upper Sorbian
ht = Haitian
hu = Hungarian
hy = Armenian
hz = Herero
ia = Interlingua
id = Indonesian
ie = Interlingue
ig = Igbo
ii = Sichuan Yi
ik = Inupiaq
io = Ido
is = Icelandic
it = Italian
iu = Inuktitut
ja = Japanese
jv = Javanese
ka = Georgian
kg = Kongo
ki = Kikuyu
kj = Kuanyama
kk = Kazakh
kl = Greenlandic
km = Khmer
kn = Kannada
ko = Korean
kok = Konkani
kr = Kanuri
ks = Kashmiri
ku = Kurdish
kv = Komi
kw = Cornish
ky = Kirghiz
la = Latin
lb = Luxembourgish
lg = Ganda
li = Limburgan
ln = Lingala
lo = Lao
lt = Lithuanian
lu = Luba-Katanga
lv = Latvian
mg = Malagasy
mh = Marshallese
mi = Maori
mk = Macedonian
ml = Malayalam
mn = Mongolian
mr = Marathi
ms = Malay
mt = Maltese
my = Burmese
na = Nauru
nb = Norwegian Bokm\u00e5l
nd = Ndebele, North
ne = Nepali
ng = Ndonga
nl = Dutch
nn = Norwegian Nynorsk
no = Norwegian
nr = Ndebele, South
nso = Sotho, Northern
nv = Navajo
ny = Chichewa
oc = Occitan
oj = Ojibwa
om = Oromo
or = Odia
os = Ossetian
pa = Punjabi
pi = Pali
pl = Polish
ps = Pashto
pt = Portuguese
qu = Quechua
rm = Rhaeto-Romanic
rn = Kirundi
ro = Romanian
ru = Russian
rw = Kinyarwanda
sa = Sanskrit
sc = Sardinian
sd = Sindhi
se = Northern Sami
sg = Sango
si = Singhalese
sk = Slovak
sl = Slovenian
sm = Samoan
sn = Shona
so = Somali
son = Songhay
sq = Albanian
sr = Serbian
ss = Siswati
st = Sotho, Southern
su = Sundanese
sv = Swedish
sw = Swahili
ta = Tamil
te = Telugu
tg = Tajik
th = Thai
ti = Tigrinya
tig = Tigre
tk = Turkmen
tl = Tagalog
tlh = Klingon
tn = Tswana
to = Tonga
tr = Turkish
ts = Tsonga
tt = Tatar
tw = Twi
ty = Tahitian
ug = Uighur
uk = Ukrainian
ur = Urdu
uz = Uzbek
ve = Venda
vi = Vietnamese
vo = Volap\u00fck
wa = Walloon
wen = Sorbian
wo = Wolof
xh = Xhosa
yi = Yiddish
yo = Yoruba
za = Zhuang
zh = Chinese
zu = Zulu
PK
!<{HL
L
;chrome/en-US/locale/en-US/global/layout/HtmlForm.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

Reset=Reset
Submit=Submit Query
Browse=Browse…
FileUpload=File Upload
DirectoryUpload=Select Folder to Upload
DirectoryPickerOkButtonLabel=Upload
ForgotPostWarning=Form contains enctype=%S, but does not contain method=post.  Submitting normally with method=GET and no enctype instead.
ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.
# LOCALIZATION NOTE (DefaultFormSubject): %S will be replaced with brandShortName
DefaultFormSubject=Form Post from %S
CannotEncodeAllUnicode=A form was submitted in the %S encoding which cannot encode all Unicode characters, so user input may get corrupted. To avoid this problem, the page should be changed so that the form is submitted in the UTF-8 encoding either by changing the encoding of the page itself to UTF-8 or by specifying accept-charset=utf-8 on the form element.
AllSupportedTypes=All Supported Types
# LOCALIZATION NOTE (NoFileSelected): this string is shown on a
# <input type='file'> when there is no file selected yet.
NoFileSelected=No file selected.
# LOCALIZATION NOTE (NoFilesSelected): this string is shown on a
# <input type='file' multiple> when there is no file selected yet.
NoFilesSelected=No files selected.
# LOCALIZATION NOTE (NoDirSelected): this string is shown on a
# <input type='file' directory/webkitdirectory> when there is no directory
# selected yet.
NoDirSelected=No directory selected.
# LOCALIZATION NOTE (XFilesSelected): this string is shown on a
# <input type='file' multiple> when there are more than one selected file.
# %S will be a number greater or equal to 2.
XFilesSelected=%S files selected.
ColorPicker=Choose a color
# LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is shown at the end of the tooltip text for <input type='file'
# multiple> when there are more than 21 files selected (when we will only list
# the first 20, plus an "and X more" line). #1 represents the number of files
# minus 20 and will always be a number equal to or greater than 2. So the
# singular case will never be used.
AndNMoreFiles=and one more;and #1 more
# LOCALIZATION NOTE (DefaultSummary): this string is shown on a <details> when
# it has no direct <summary> child. Google Chrome should already have this
# string translated.
DefaultSummary=Details
PK
!<&乮@chrome/en-US/locale/en-US/global/layout/MediaDocument.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 (ImageTitleWithDimensions2AndFile): first %S is filename, second %S is type, third %S is width and fourth %S is height
#LOCALIZATION NOTE (ImageTitleWithoutDimensions): first %S is filename, second %S is type
#LOCALIZATION NOTE (ImageTitleWithDimensions2): first %S is type, second %S is width and third %S is height
#LOCALIZATION NOTE (ImageTitleWithNeitherDimensionsNorFile): first %S is type
#LOCALIZATION NOTE (MediaTitleWithFile): first %S is filename, second %S is type
#LOCALIZATION NOTE (MediaTitleWithNoInfo): first %S is type
ImageTitleWithDimensions2AndFile=%S (%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithoutDimensions=%S (%S Image)
ImageTitleWithDimensions2=(%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithNeitherDimensionsNorFile=(%S Image)
MediaTitleWithFile=%S (%S Object)
MediaTitleWithNoInfo=(%S Object)

InvalidImage=The image \u201c%S\u201d cannot be displayed because it contains errors.
ScaledImage=Scaled (%S%%)

TitleWithStatus=%S - %S
PK
!<9ƻ*..=chrome/en-US/locale/en-US/global/layout/htmlparser.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Encoding warnings and errors
EncNoDeclarationFrame=The character encoding of a framed document was not declared. The document may appear different if viewed without the document framing it.
EncNoDeclarationPlain=The character encoding of the plain text document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the file needs to be declared in the transfer protocol or file needs to use a byte order mark as an encoding signature.
EncNoDeclaration=The character encoding of the HTML document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol.
EncLateMetaFrame=The character encoding declaration of the framed HTML document was not found when prescanning the first 1024 bytes of the file. When viewed without the document framing it, the page will reload automatically. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
EncLateMeta=The character encoding declaration of the HTML document was not found when prescanning the first 1024 bytes of the file. When viewed in a differently-configured browser, this page will reload automatically. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
EncLateMetaReload=The page was reloaded, because the character encoding declaration of the HTML document was not found when prescanning the first 1024 bytes of the file. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
EncLateMetaTooLate=The character encoding declaration of document was found too late for it to take effect. The encoding declaration needs to be moved to be within the first 1024 bytes of the file.
EncMetaUnsupported=An unsupported character encoding was declared for the HTML document using a meta tag. The declaration was ignored.
EncProtocolUnsupported=An unsupported character encoding was declared on the transfer protocol level. The declaration was ignored.
EncBomlessUtf16=Detected UTF-16-encoded Basic Latin-only text without a byte order mark and without a transfer protocol-level declaration. Encoding this content in UTF-16 is inefficient and the character encoding should have been declared in any case.
EncMetaUtf16=A meta tag was used to declare the character encoding as UTF-16. This was interpreted as an UTF-8 declaration instead.
EncMetaUserDefined=A meta tag was used to declare the character encoding as x-user-defined. This was interpreted as a windows-1252 declaration instead for compatibility with intentionally mis-encoded legacy fonts. This site should migrate to Unicode.

# The bulk of the messages below are derived from
# https://hg.mozilla.org/projects/htmlparser/file/1f633cef7de7/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java
# which is available under the MIT license.

# Tokenizer errors
errGarbageAfterLtSlash=Garbage after “</”.
errLtSlashGt=Saw “</>”. Probable causes: Unescaped “<” (escape as “&lt;”) or mistyped end tag.
errCharRefLacksSemicolon=Character reference was not terminated by a semicolon.
errNoDigitsInNCR=No digits in numeric character reference.
errGtInSystemId=“>” in system identifier.
errGtInPublicId=“>” in public identifier.
errNamelessDoctype=Nameless doctype.
errConsecutiveHyphens=Consecutive hyphens did not terminate a comment. “--” is not permitted inside a comment, but e.g. “- -” is.
errPrematureEndOfComment=Premature end of comment. Use “-->” to end a comment properly.
errBogusComment=Bogus comment.
errUnquotedAttributeLt=“<” in an unquoted attribute value. Probable cause: Missing “>” immediately before.
errUnquotedAttributeGrave=“`” in an unquoted attribute value. Probable cause: Using the wrong character as a quote.
errUnquotedAttributeQuote=Quote in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.
errUnquotedAttributeEquals=“=” in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.
errSlashNotFollowedByGt=A slash was not immediately followed by “>”.
errNoSpaceBetweenAttributes=No space between attributes.
errUnquotedAttributeStartLt=“<” at the start of an unquoted attribute value. Probable cause: Missing “>” immediately before.
errUnquotedAttributeStartGrave=“`” at the start of an unquoted attribute value. Probable cause: Using the wrong character as a quote.
errUnquotedAttributeStartEquals=“=” at the start of an unquoted attribute value. Probable cause: Stray duplicate equals sign.
errAttributeValueMissing=Attribute value missing.
errBadCharBeforeAttributeNameLt=Saw “<” when expecting an attribute name. Probable cause: Missing “>” immediately before.
errEqualsSignBeforeAttributeName=Saw “=” when expecting an attribute name. Probable cause: Attribute name missing.
errBadCharAfterLt=Bad character after “<”. Probable cause: Unescaped “<”. Try escaping it as “&lt;”.
errLtGt=Saw “<>”. Probable causes: Unescaped “<” (escape as “&lt;”) or mistyped start tag.
errProcessingInstruction=Saw “<?”. Probable cause: Attempt to use an XML processing instruction in HTML. (XML processing instructions are not supported in HTML.)
errUnescapedAmpersandInterpretedAsCharacterReference=The string following “&” was interpreted as a character reference. (“&” probably should have been escaped as “&amp;”.)
errNotSemicolonTerminated=Named character reference was not terminated by a semicolon. (Or “&” should have been escaped as “&amp;”.)
errNoNamedCharacterMatch=“&” did not start a character reference. (“&” probably should have been escaped as “&amp;”.)
errQuoteBeforeAttributeName=Saw a quote when expecting an attribute name. Probable cause: “=” missing immediately before.
errLtInAttributeName=“<” in attribute name. Probable cause: “>” missing immediately before.
errQuoteInAttributeName=Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.
errExpectedPublicId=Expected a public identifier but the doctype ended.
errBogusDoctype=Bogus doctype.
maybeErrAttributesOnEndTag=End tag had attributes.
maybeErrSlashInEndTag=Stray “/” at the end of an end tag.
errNcrNonCharacter=Character reference expands to a non-character.
errNcrSurrogate=Character reference expands to a surrogate.
errNcrControlChar=Character reference expands to a control character.
errNcrCr=A numeric character reference expanded to carriage return.
errNcrInC1Range=A numeric character reference expanded to the C1 controls range.
errEofInPublicId=End of file inside public identifier.
errEofInComment=End of file inside comment.
errEofInDoctype=End of file inside doctype.
errEofInAttributeValue=End of file reached when inside an attribute value. Ignoring tag.
errEofInAttributeName=End of file occurred in an attribute name. Ignoring tag.
errEofWithoutGt=Saw end of file without the previous tag ending with “>”. Ignoring tag.
errEofInTagName=End of file seen when looking for tag name. Ignoring tag.
errEofInEndTag=End of file inside end tag. Ignoring tag.
errEofAfterLt=End of file after “<”.
errNcrOutOfRange=Character reference outside the permissible Unicode range.
errNcrUnassigned=Character reference expands to a permanently unassigned code point.
errDuplicateAttribute=Duplicate attribute.
errEofInSystemId=End of file inside system identifier.
errExpectedSystemId=Expected a system identifier but the doctype ended.
errMissingSpaceBeforeDoctypeName=Missing space before doctype name.
errHyphenHyphenBang=“--!” found in comment.
errNcrZero=Character reference expands to zero.
errNoSpaceBetweenDoctypeSystemKeywordAndQuote=No space between the doctype “SYSTEM” keyword and the quote.
errNoSpaceBetweenPublicAndSystemIds=No space between the doctype public and system identifiers.
errNoSpaceBetweenDoctypePublicKeywordAndQuote=No space between the doctype “PUBLIC” keyword and the quote.

# Tree builder errors
errStrayStartTag2=Stray start tag “%1$S”.
errStrayEndTag=Stray end tag “%1$S”.
errUnclosedElements=End tag “%1$S” seen, but there were open elements.
errUnclosedElementsImplied=End tag “%1$S” implied, but there were open elements.
errUnclosedElementsCell=A table cell was implicitly closed, but there were open elements.
errStrayDoctype=Stray doctype.
errAlmostStandardsDoctype=Almost standards mode doctype. Expected “<!DOCTYPE html>”.
errQuirkyDoctype=Quirky doctype. Expected “<!DOCTYPE html>”.
errNonSpaceInTrailer=Non-space character in page trailer.
errNonSpaceAfterFrameset=Non-space after “frameset”.
errNonSpaceInFrameset=Non-space in “frameset”.
errNonSpaceAfterBody=Non-space character after body.
errNonSpaceInColgroupInFragment=Non-space in “colgroup” when parsing fragment.
errNonSpaceInNoscriptInHead=Non-space character inside “noscript” inside “head”.
errFooBetweenHeadAndBody=“%1$S” element between “head” and “body”.
errStartTagWithoutDoctype=Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
errNoSelectInTableScope=No “select” in table scope.
errStartSelectWhereEndSelectExpected=“select” start tag where end tag expected.
errStartTagWithSelectOpen=“%1$S” start tag with “select” open.
errBadStartTagInHead2=Bad start tag “%1$S” in “head”.
errImage=Saw a start tag “image”.
errFooSeenWhenFooOpen=An “%1$S” start tag seen but an element of the same type was already open.
errHeadingWhenHeadingOpen=Heading cannot be a child of another heading.
errFramesetStart=“frameset” start tag seen.
errNoCellToClose=No cell to close.
errStartTagInTable=Start tag “%1$S” seen in “table”.
errFormWhenFormOpen=Saw a “form” start tag, but there was already an active “form” element. Nested forms are not allowed. Ignoring the tag.
errTableSeenWhileTableOpen=Start tag for “table” seen but the previous “table” is still open.
errStartTagInTableBody=“%1$S” start tag in table body.
errEndTagSeenWithoutDoctype=End tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
errEndTagAfterBody=Saw an end tag after “body” had been closed.
errEndTagSeenWithSelectOpen=“%1$S” end tag with “select” open.
errGarbageInColgroup=Garbage in “colgroup” fragment.
errEndTagBr=End tag “br”.
errNoElementToCloseButEndTagSeen=No “%1$S” element in scope but a “%1$S” end tag seen.
errHtmlStartTagInForeignContext=HTML start tag “%1$S” in a foreign namespace context.
errTableClosedWhileCaptionOpen=“table” closed but “caption” was still open.
errNoTableRowToClose=No table row to close.
errNonSpaceInTable=Misplaced non-space characters inside a table.
errUnclosedChildrenInRuby=Unclosed children in “ruby”.
errStartTagSeenWithoutRuby=Start tag “%1$S” seen without a “ruby” element being open.
errSelfClosing=Self-closing syntax (“/>”) used on a non-void HTML element. Ignoring the slash and treating as a start tag.
errNoCheckUnclosedElementsOnStack=Unclosed elements on stack.
errEndTagDidNotMatchCurrentOpenElement=End tag “%1$S” did not match the name of the current open element (“%2$S”).
errEndTagViolatesNestingRules=End tag “%1$S” violates nesting rules.
errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
PK
!<22<chrome/en-US/locale/en-US/global/layout/xmlparser.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Map Expat error codes to error strings
1 = out of memory
2 = syntax error
3 = no root element found
4 = not well-formed
5 = unclosed token
6 = partial character
7 = mismatched tag
8 = duplicate attribute
9 = junk after document element
10 = illegal parameter entity reference
11 = undefined entity
12 = recursive entity reference
13 = asynchronous entity
14 = reference to invalid character number
15 = reference to binary entity
16 = reference to external entity in attribute
17 = XML or text declaration not at start of entity
18 = unknown encoding
19 = encoding specified in XML declaration is incorrect
20 = unclosed CDATA section
21 = error in processing external entity reference
22 = document is not standalone
23 = unexpected parser state
24 = entity declared in parameter entity
27 = prefix not bound to a namespace
28 = must not undeclare prefix
29 = incomplete markup in parameter entity
30 = XML declaration not well-formed
31 = text declaration not well-formed
32 = illegal character(s) in public id
38 = reserved prefix (xml) must not be undeclared or bound to another namespace name
39 = reserved prefix (xmlns) must not be declared or undeclared
40 = prefix must not be bound to one of the reserved namespace names

# %1$S is replaced by the Expat error string, may be followed by Expected (see below)
# %2$S is replaced by URL
# %3$u is replaced by line number
# %4$u is replaced by column number
XMLParsingError = XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:

# %S is replaced by a tag name.
# This gets appended to the error string if the error is mismatched tag.
Expected = . Expected: </%S>.
PK
!<z9chrome/en-US/locale/en-US/global/layout_errors.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

ImageMapRectBoundsError=The “coords” attribute of the <area shape="rect"> tag is not in the “left,top,right,bottom” format.
ImageMapCircleWrongNumberOfCoords=The “coords” attribute of the <area shape="circle"> tag is not in the “center-x,center-y,radius” format.
ImageMapCircleNegativeRadius=The “coords” attribute of the <area shape="circle"> tag has a negative radius.
ImageMapPolyWrongNumberOfCoords=The “coords” attribute of the <area shape="poly"> tag is not in the “x1,y1,x2,y2 …” format.
ImageMapPolyOddNumberOfCoords=The “coords” attribute of the <area shape="poly"> tag is missing the last “y” coordinate (the correct format is “x1,y1,x2,y2 …”).

TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect.
ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!

## LOCALIZATION NOTE(CompositorAnimationWarningContentTooLargeArea):
## %1$S is an integer value of the area of the frame
## %2$S is an integer value of the area of a limit based on the viewport size
CompositorAnimationWarningContentTooLargeArea=Animation cannot be run on the compositor because the area of the frame (%1$S) is too large relative to the viewport (larger than %2$S)
## LOCALIZATION NOTE(CompositorAnimationWarningContentTooLarge2):
## (%1$S, %2$S) is a pair of integer values of the frame size
## (%3$S, %4$S) is a pair of integer values of a limit based on the viewport size
## (%5$S, %6$S) is a pair of integer values of an absolute limit
CompositorAnimationWarningContentTooLarge2=Animation cannot be run on the compositor because the frame size (%1$S, %2$S) is too large relative to the viewport (larger than (%3$S, %4$S)) or larger than the maximum allowed value (%5$S, %6$S)
## LOCALIZATION NOTE(CompositorAnimationWarningTransformBackfaceVisibilityHidden):
## 'backface-visibility: hidden' is a CSS property, don't translate it.
CompositorAnimationWarningTransformBackfaceVisibilityHidden=Animations of ‘backface-visibility: hidden’ transforms cannot be run on the compositor
## LOCALIZATION NOTE(CompositorAnimationWarningTransformPreserve3D):
## 'transform-style: preserve-3d' is a CSS property, don't translate it.
CompositorAnimationWarningTransformPreserve3D=Animations of ‘transform-style: preserve-3d’ transforms cannot be run on the compositor
## LOCALIZATION NOTE(CompositorAnimationWarningTransformSVG,
##                   CompositorAnimationWarningTransformWithGeometricProperties,
##                   CompositorAnimationWarningTransformWithSyncGeometricAnimations,
##                   CompositorAnimationWarningTransformFrameInactive,
##                   CompositorAnimationWarningOpacityFrameInactive):
## 'transform' and 'opacity' mean CSS property names, don't translate it.
CompositorAnimationWarningTransformSVG=Animations of ‘transform’ on elements with SVG transforms cannot be run on the compositor
CompositorAnimationWarningTransformWithGeometricProperties=Animations of ‘transform’ cannot be run on the compositor when geometric properties are animated on the same element at the same time
CompositorAnimationWarningTransformWithSyncGeometricAnimations=Animation of ‘transform’ cannot be run on the compositor because it should be synchronized with animations of geometric properties that started at the same time
CompositorAnimationWarningTransformFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for ‘transform’ animation
CompositorAnimationWarningOpacityFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for ‘opacity’ animation
CompositorAnimationWarningHasRenderingObserver=Animation cannot be run on the compositor because the element has rendering observers (-moz-element or SVG clipping/masking)
PK
!<qOO9chrome/en-US/locale/en-US/global/mathml/mathml.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

InvalidChild=Invalid markup: <%1$S> is not allowed as a child of <%2$S>.
ChildCountIncorrect=Invalid markup: Incorrect number of children for <%1$S/> tag.
DuplicateMprescripts=Invalid markup: More than one <mprescripts/> in <mmultiscripts/>.
# LOCALIZATION NOTE:  The first child of <mmultiscript/> is the base, that is the element to which scripts are attached.
NoBase=Invalid markup: Expected exactly one Base element in <mmultiscripts/>.  Found none.
SubSupMismatch=Invalid markup: Incomplete subscript/superscript pair in <mmultiscripts/>.

# LOCALIZATION NOTE:  When localizing the single quotes ('), follow the conventions in css.properties for your target locale.
AttributeParsingError=Error in parsing the value ‘%1$S’ for ‘%2$S’ attribute of <%3$S/>.  Attribute ignored.
AttributeParsingErrorNoTag=Error in parsing the value ‘%1$S’ for ‘%2$S’ attribute.  Attribute ignored.
LengthParsingError=Error in parsing MathML attribute value ‘%1$S’ as length.  Attribute ignored.
DeprecatedSupersededBy=‘%1$S’ is deprecated in MathML 3, superseded by ‘%2$S’.
UnitlessValuesAreDeprecated=Unitless values are deprecated in MathML 3.
PK
!<Cң,chrome/en-US/locale/en-US/global/mozilla.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY mozilla.title.15.1
'The Book of Mozilla, 15:1'>

<!ENTITY mozilla.quote.15.1
'The <em>twins</em> of Mammon quarrelled. Their warring plunged the world into a <em>new darkness</em>, and the beast 
abhorred the darkness. So it began to move <em>swiftly</em>, and grew more powerful, and went forth and multiplied. 
And the beasts brought <em>fire</em> and light to the darkness.'>

<!ENTITY mozilla.from.15.1
'from <strong>The Book of Mozilla,</strong> 15:1'>
PK
!<1ÿOO3chrome/en-US/locale/en-US/global/narrate.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Narrate, meaning "read the page out loud". This is the name of the feature
# and it is the label for the popup button.
narrate = Narrate
back = Back
start = Start
stop = Stop
forward = Forward
speed = Speed
selectvoicelabel = Voice:
# Default voice is determined by the language of the document.
defaultvoice = Default

# Voice name and language.
# eg. David (English)
voiceLabel = %S (%S)PK
!</I5}$}$-chrome/en-US/locale/en-US/global/netError.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY loadError.label "Page Load Error">
<!ENTITY retry.label "Try Again">

<!-- Specific error messages -->

<!ENTITY connectionFailure.title "Failed to Connect">
<!ENTITY connectionFailure.longDesc "<p>Though the site seems valid, the browser was unable to establish a connection.</p><ul><li>Could the site be temporarily unavailable? Try again later.</li><li>Are you unable to browse other sites?  Check the computer’s network connection.</li><li>Is your computer or network protected by a firewall or proxy? Incorrect settings can interfere with Web browsing.</li></ul>">

<!ENTITY deniedPortAccess.title "Port Restricted for Security Reasons">
<!ENTITY deniedPortAccess.longDesc "<p>The requested address specified a port (e.g. <q>mozilla.org:80</q> for port 80 on mozilla.org) normally used for purposes <em>other</em> than Web browsing. The browser has canceled the request for your protection and security.</p>">

<!ENTITY dnsNotFound.title "Address Not Found">
<!ENTITY dnsNotFound.longDesc "<p>The browser could not find the host server for the provided address.</p><ul><li>Did you make a mistake when typing the domain? (e.g. <q><strong>ww</strong>.mozilla.org</q> instead of <q><strong>www</strong>.mozilla.org</q>)</li><li>Are you certain this domain address exists?  Its registration may have expired.</li><li>Are you unable to browse other sites?  Check your network connection and DNS server settings.</li><li>Is your computer or network protected by a firewall or proxy?  Incorrect settings can interfere with Web browsing.</li></ul>">

<!ENTITY fileNotFound.title "File Not Found">
<!ENTITY fileNotFound.longDesc "<ul><li>Could the item have been renamed, removed, or relocated?</li><li>Is there a spelling, capitalization, or other typographical error in the address?</li><li>Do you have sufficient access permissions to the requested item?</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 "Cannot Complete Request">
<!ENTITY generic.longDesc "<p>Additional information about this problem or error is currently unavailable.</p>">

<!ENTITY malformedURI.title "Invalid Address">
<!ENTITY malformedURI.longDesc "<p>The provided address is not in a recognized format. Please check the location bar for mistakes and try again.</p>">

<!ENTITY netInterrupt.title "Data Transfer Interrupted">
<!ENTITY netInterrupt.longDesc "<p>The browser connected successfully, but the connection was interrupted while transferring information.  Please try again.</p><ul><li>Are you unable to browse other sites? Check the computer’s network connection.</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">

<!ENTITY notCached.title "Document Expired">
<!ENTITY notCached.longDesc "<p>The requested document is not available in the browser’s cache.</p><ul><li>As a security precaution, the browser 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 "<p>The browser is operating in its offline mode and cannot connect to the requested item.</p><ul><li>Is the computer connected to an active network?</li><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 "<p>The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.</p><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 "Connection Interrupted">
<!ENTITY netReset.longDesc "<p>The network link was interrupted while negotiating a connection. Please try again.</p>">

<!ENTITY netTimeout.title "Network Timeout">
<!ENTITY netTimeout.longDesc "<p>The requested site did not respond to a connection request and the browser has stopped waiting for a reply.</p><ul><li>Could the server be experiencing high demand or a temporary outage?  Try again later.</li><li>Are you unable to browse other sites? Check the computer’s network connection.</li><li>Is your computer or network protected by a firewall or proxy?  Incorrect settings can interfere with Web browsing.</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">

<!ENTITY unknownProtocolFound.title "Unknown Protocol">
<!ENTITY unknownProtocolFound.longDesc "<p>The address specifies a protocol (e.g. <q>wxyz://</q>) the browser does not recognize, so the browser cannot properly connect to the site.</p><ul><li>Are you trying to access multimedia or other non-text services? Check the site for extra requirements.</li><li>Some protocols may require third-party software or plugins before the browser can recognize them.</li></ul>">

<!ENTITY proxyConnectFailure.title "Proxy Server Refused Connection">
<!ENTITY proxyConnectFailure.longDesc "<p>The browser is configured to use a proxy server, but the proxy refused a connection.</p><ul><li>Is the browser’s proxy configuration correct? Check the settings and try again.</li><li>Does the proxy service allow connections from this network?</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">

<!ENTITY proxyResolveFailure.title "Proxy Server Not Found">
<!ENTITY proxyResolveFailure.longDesc "<p>The browser is configured to use a proxy server, but the proxy could not be found.</p><ul><li>Is the browser’s proxy configuration correct? Check the settings and try again.</li><li>Is the computer connected to an active network?</li><li>Still having trouble? Consult your network administrator or Internet provider for assistance.</li></ul>">

<!ENTITY redirectLoop.title "Redirect Loop">
<!ENTITY redirectLoop.longDesc "<p>The browser has stopped trying to retrieve the requested item. The site is redirecting the request in a way that will never complete.</p><ul><li>Have you disabled or blocked cookies required by this site?</li><li><em>NOTE</em>: If accepting the site’s cookies does not resolve the problem, it is likely a server configuration issue and not your computer.</li></ul>">

<!ENTITY unknownSocketType.title "Incorrect Response">
<!ENTITY unknownSocketType.longDesc "<p>The site responded to the network request in an unexpected way and the browser cannot continue.</p>">

<!ENTITY nssFailure2.title "Secure Connection Failed">
<!ENTITY nssFailure2.longDesc2 "<p>The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">

<!ENTITY nssBadCert.title "Secure Connection Failed">
<!ENTITY nssBadCert.longDesc2 "<ul>
<li>This could be a problem with the server’s configuration, or it could be someone trying to impersonate the server.</li>
<li>If you have connected to this server successfully in the past, the error may be temporary, and you can try again later.</li>
</ul>
">

<!ENTITY securityOverride.linkText "Or you can add an exception…">
<!ENTITY securityOverride.warningContent "
<p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
<p>If you still wish to add an exception for this site, you can do so in your advanced encryption settings.</p>
">

<!ENTITY cspBlocked.title "Blocked by Content Security Policy">
<!ENTITY cspBlocked.longDesc "<p>The browser 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 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 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>">
PK
!<ՑC0chrome/en-US/locale/en-US/global/netErrorApp.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This file exists to allow applications to override one or more messages
     from netError.dtd; Applications which want to do this should override
     this file with their own version of netErrorApp.dtd -->

<!-- An example (from Firefox):

<!ENTITY securityOverride.linkText "Or you can add an exception…">
<!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">

<!ENTITY securityOverride.warningContent "
<p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
<p>If you still wish to add an exception for this site, you can do so in your advanced encryption settings.</p>

<button id='getMeOutOfHereButton'>&securityOverride.getMeOutOfHereButton;</button>
<button id='exceptionDialogButton'>&securityOverride.exceptionButtonLabel;</button>
">

-->
PK
!<Ÿ1chrome/en-US/locale/en-US/global/notification.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY closeNotification.tooltip "Close this message">

<!ENTITY checkForUpdates "Check for updates…">

<!ENTITY learnMore "Learn more…">

<!ENTITY defaultButton.label "OK!">
<!ENTITY defaultButton.accesskey "O">
PK
!<>3?chrome/en-US/locale/en-US/global/nsWebBrowserPersist.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

readError=%S could not be saved, because the source file could not be read.\n\nTry again later, or contact the server administrator.
writeError=%S could not be saved, because an unknown error occurred.\n\nTry saving to a different location.
launchError=%S could not be opened, because an unknown error occurred.\n\nTry saving to disk first and then opening the file.
diskFull=There is not enough room on the disk to save %S.\n\nRemove unnecessary files from the disk and try again, or try saving in a different location.
readOnly=%S could not be saved, because the disk, folder, or file is write-protected.\n\nWrite-enable the disk and try again, or try saving in a different location.
accessError=%S could not be saved, because you cannot change the contents of that folder.\n\nChange the folder properties and try again, or try saving in a different location.
SDAccessErrorCardReadOnly=Cannot download file because the SD card is in use.
SDAccessErrorCardMissing=Cannot download file because the SD card is missing.
helperAppNotFound=%S could not be opened, because the associated helper application does not exist. Change the association in your preferences.
noMemory=There is not sufficient memory to complete the action you requested.\n\nQuit some applications and try again.
title=Downloading %S
fileAlreadyExistsError=%S could not be saved, because a file already exists with the same name as the ‘_files’ directory.\n\nTry saving to a different location.
fileNameTooLongError=%S could not be saved, because the file name was too long.\n\nTry saving with a shorter file name.
PK
!<|u?3chrome/en-US/locale/en-US/global/plugins.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 (plugins.properties):
#    Those strings are inserted into an HTML page, so all HTML characters
#    have to be escaped in a way that they show up correctly in HTML!

title_label=About Plugins
installedplugins_label=Installed plugins
nopluginsareinstalled_label=No installed plugins found
findpluginupdates_label=Find updates for installed plugins at
file_label=File:
path_label=Path:
version_label=Version:
state_label=State:
state_enabled=Enabled
state_disabled=Disabled
mimetype_label=MIME Type
description_label=Description
suffixes_label=Suffixes
learn_more_label=Learn More

deprecation_description=Missing something? Some plugins are no longer supported.
deprecation_learn_more=Learn More.

# GMP Plugins
gmp_license_info=License information
gmp_privacy_info=Privacy Information

openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
openH264_description2=This plugin is automatically installed by Mozilla to comply with the WebRTC specification and to enable WebRTC calls with devices that require the H.264 video codec. Visit http://www.openh264.org/ to view the codec source code and learn more about the implementation.

cdm_description=Play back protected web video.

widevine_description=Widevine Content Decryption Module provided by Google Inc.
PK
!<i0chrome/en-US/locale/en-US/global/preferences.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  windowClose.key                         "w">
<!ENTITY  preferencesDefaultTitleMac.title        "Preferences">
<!ENTITY  preferencesDefaultTitleWin.title        "Options">
<!ENTITY  preferencesCloseButton.label            "Close">
<!ENTITY  preferencesCloseButton.accesskey        "C">
PK
!<ʏ3chrome/en-US/locale/en-US/global/printPageSetup.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from printjoboptions.xul -->

<!ENTITY printSetup.title    "Page Setup">

<!ENTITY basic.tab           "Format &amp; Options">

<!ENTITY formatGroup.label   "Format">

<!ENTITY orientation.label   "Orientation:">
<!ENTITY portrait.label      "Portrait">
<!ENTITY portrait.accesskey  "P">
<!ENTITY landscape.label     "Landscape">
<!ENTITY landscape.accesskey "L">

<!ENTITY scale.label         "Scale:">
<!ENTITY scale.accesskey     "S">
<!ENTITY scalePercent        "&#037;">

<!ENTITY shrinkToFit.label   "Shrink to fit Page Width">
<!ENTITY shrinkToFit.accesskey "W">

<!ENTITY optionsGroup.label  "Options">

<!ENTITY printBG.label       "Print Background (colors &amp; images)">
<!ENTITY printBG.accesskey   "B">

<!ENTITY advanced.tab        "Margins &amp; Header/Footer">

<!ENTITY marginGroup.label   "Margins (#1)">
<!ENTITY marginUnits.inches  "inches">
<!ENTITY marginUnits.metric  "millimeters">
<!ENTITY marginTop.label     "Top:">
<!ENTITY marginTop.accesskey "T">
<!ENTITY marginBottom.label  "Bottom:">
<!ENTITY marginBottom.accesskey "B">
<!ENTITY marginLeft.label    "Left:">
<!ENTITY marginLeft.accesskey "L">
<!ENTITY marginRight.label   "Right:">
<!ENTITY marginRight.accesskey "R">

<!ENTITY headerFooter.label  "Headers &amp; Footers">

<!ENTITY hfLeft.label        "Left:">
<!ENTITY hfCenter.label      "Center:">
<!ENTITY hfRight.label       "Right:">
<!ENTITY headerLeft.tip      "Left header">
<!ENTITY headerCenter.tip    "Center header">
<!ENTITY headerRight.tip     "Right header">
<!ENTITY footerLeft.tip      "Left footer">
<!ENTITY footerCenter.tip    "Center footer">
<!ENTITY footerRight.tip     "Right footer">

<!ENTITY hfTitle             "Title">
<!ENTITY hfURL               "URL">
<!ENTITY hfDateAndTime       "Date/Time">
<!ENTITY hfPage              "Page #">
<!ENTITY hfPageAndTotal      "Page # of #">
<!ENTITY hfBlank             "--blank--">
<!ENTITY hfCustom            "Custom…">

<!ENTITY customPrompt.title  "Custom…">
<!ENTITY customPrompt.prompt "Enter your custom header/footer text">
PK
!<[CC1chrome/en-US/locale/en-US/global/printPreview.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY print.label          "Print…">
<!ENTITY print.accesskey      "P">
<!ENTITY pageSetup.label      "Page Setup…">
<!ENTITY pageSetup.accesskey  "u">
<!ENTITY page.label           "Page:">
<!ENTITY page.accesskey       "a">
<!ENTITY of.label             "of">
<!ENTITY scale.label          "Scale:">
<!ENTITY scale.accesskey      "S">
<!ENTITY portrait.label       "Portrait">
<!ENTITY portrait.accesskey   "o">
<!ENTITY landscape.label      "Landscape">
<!ENTITY landscape.accesskey  "L">
<!ENTITY close.label          "Close">
<!ENTITY close.accesskey      "C">
<!ENTITY p30.label            "30&#037;">
<!ENTITY p40.label            "40&#037;">
<!ENTITY p50.label            "50&#037;">
<!ENTITY p60.label            "60&#037;">
<!ENTITY p70.label            "70&#037;">
<!ENTITY p80.label            "80&#037;">
<!ENTITY p90.label            "90&#037;">
<!ENTITY p100.label           "100&#037;">
<!ENTITY p125.label           "125&#037;">
<!ENTITY p150.label           "150&#037;">
<!ENTITY p175.label           "175&#037;">
<!ENTITY p200.label           "200&#037;">
<!ENTITY Custom.label         "Custom…">
<!ENTITY ShrinkToFit.label    "Shrink To Fit">
<!ENTITY customPrompt.title   "Custom Scale…">
<!ENTITY simplifyPage.label   "Simplify Page">
<!ENTITY simplifyPage.accesskey "i">
<!ENTITY simplifyPage.enabled.tooltip  "Change layout for easier reading">
<!ENTITY simplifyPage.disabled.tooltip "This page cannot be automatically simplified">

<!ENTITY homearrow.tooltip    "First page">
<!ENTITY endarrow.tooltip     "Last page">
<!ENTITY nextarrow.tooltip    "Next page">
<!ENTITY previousarrow.tooltip "Previous page">
PK
!</9chrome/en-US/locale/en-US/global/printPreviewProgress.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!--LOCALIZATION NOTE printPreviewProgress.dtd Main UI for Print Preview Progress Dialog -->
<!ENTITY printWindow.title "Print Preview"> 
<!ENTITY title   "Title:"> 
<!ENTITY preparing "Preparing…">
<!ENTITY progress "Progress:">
PK
!<ᗲ2chrome/en-US/locale/en-US/global/printProgress.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!--LOCALIZATION NOTE printProgress.dtd Main UI for Print Progress Dialog -->
<!ENTITY printWindow.title "Printing"> 
<!ENTITY title    "Title:"> 
<!ENTITY progress "Progress:">
<!ENTITY preparing "Preparing…">
<!ENTITY printComplete "Printing is Completed.">

<!ENTITY dialogCancel.label "Cancel"> 
<!ENTITY dialogClose.label "Close">

<!-- LOCALIZATION NOTE (percentPrint):

    This string is used to format the text to the right of the progress
    meter.

    #1 will be replaced by the percentage of the file that has been saved -->
<!ENTITY percentPrint "#1&#037;">
PK
!<Ƒr330chrome/en-US/locale/en-US/global/printdialog.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from printdialog.xul -->

<!ENTITY printButton.label "Print">

<!ENTITY printDialog.title "Print">

<!ENTITY fpDialog.title "Save File">

<!ENTITY fileCheck.label "Print to File">
<!ENTITY fileCheck.accesskey "F">
<!ENTITY propertiesButton.label "Properties…">
<!ENTITY propertiesButton.accesskey "o">
<!ENTITY descText.label "Printer Description:">
<!ENTITY printer.label "Printer">
<!ENTITY printerInput.label "Printer Name:">
<!ENTITY printerInput.accesskey "N">

<!ENTITY printrangeGroup.label "Print Range">
<!ENTITY allpagesRadio.label "All Pages">
<!ENTITY allpagesRadio.accesskey "A">
<!ENTITY rangeRadio.label  "Pages">
<!ENTITY rangeRadio.accesskey  "P">
<!ENTITY frompageInput.label  "from">
<!ENTITY frompageInput.accesskey  "r">
<!ENTITY topageInput.label  "to">
<!ENTITY topageInput.accesskey  "t">
<!ENTITY selectionRadio.label "Selection">
<!ENTITY selectionRadio.accesskey "S">

<!ENTITY copies.label "Copies">
<!ENTITY numCopies.label "Number of copies:">
<!ENTITY numCopies.accesskey "c">

<!ENTITY printframeGroup.label "Print Frames">
<!ENTITY aslaidoutRadio.label "As laid out on the screen">
<!ENTITY aslaidoutRadio.accesskey "u">
<!ENTITY selectedframeRadio.label  "The selected frame">
<!ENTITY selectedframeRadio.accesskey  "m">
<!ENTITY eachframesepRadio.label  "Each frame separately">
<!ENTITY eachframesepRadio.accesskey  "E">
PK
!<m͒37chrome/en-US/locale/en-US/global/printdialog.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# These strings are used in the native GTK, Mac and Windows print dialogs.

# GTK titles:
printTitleGTK=Print
optionsTabLabelGTK=Options
printFramesTitleGTK=Print Frames

# Mac titles:
optionsTitleMac=Options:
appearanceTitleMac=Appearance:
framesTitleMac=Frames:
pageHeadersTitleMac=Page Headers:
pageFootersTitleMac=Page Footers:

# Windows titles:
optionsTitleWindows=Options
printFramesTitleWindows=Print Frames

# TRANSLATOR NOTE: For radio button labels and check button labels, an underscore _
# before a character will turn that character into an accesskey in the GTK dialog.
# e.g. "_As laid out" will make A the accesskey.
# In the Windows labels, use an ampersand (&).
# On Mac, underscores will be stripped.

asLaidOut=_As Laid Out on the Screen
asLaidOutWindows=As &laid out on the screen
selectedFrame=The _Selected Frame
selectedFrameWindows=The selected &frame
separateFrames=Each Frame on Separate _Pages
separateFramesWindows=&Each frame separately
shrinkToFit=Ignore Scaling and S_hrink To Fit Page Width
selectionOnly=Print Selection _Only
printBGOptions=Print Backgrounds
printBGColors=Print Background _Colors
printBGImages=Print Background I_mages
headerFooter=Header and Footer
left=Left
center=Center
right=Right
headerFooterBlank=--blank--
headerFooterTitle=Title
headerFooterURL=URL
headerFooterDate=Date/Time
headerFooterPage=Page #
headerFooterPageTotal=Page # of #
headerFooterCustom=Custom…
customHeaderFooterPrompt=Please enter your custom header/footer text

# These are for the summary view in the Mac dialog:
summaryFramesTitle=Print Frames
summarySelectionOnlyTitle=Print Selection
summaryShrinkToFitTitle=Shrink To Fit
summaryPrintBGColorsTitle=Print BG Colors
summaryPrintBGImagesTitle=Print BG Images
summaryHeaderTitle=Page Headers
summaryFooterTitle=Page Footers
summaryNAValue=N/A
summaryOnValue=On
summaryOffValue=Off
PK
!<H'i4chrome/en-US/locale/en-US/global/printing.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 number formatting
## @page_number The current page number
#LOCALIZATION NOTE (pageofpages): Do not translate %ld in the following line.
# Place the word %ld where the page number and number of pages should be
# The first %ld will receive the the page number
pagenumber=%1$d

# Page number formatting
## @page_number The current page number
## @page_total The total number of pages
#LOCALIZATION NOTE (pageofpages): Do not translate %ld in the following line.
# Place the word %ld where the page number and number of pages should be
# The first %ld will receive the the page number
# the second %ld will receive the total number of pages
pageofpages=%1$d of %2$d

noprinter=No printers available.
PrintToFile=Print To File
noPrintFilename.title=Filename is missing
noPrintFilename.alert=You have selected “Print To File”, and the filename is empty!
fileConfirm.exists=%S already exists.\nDo you want to replace it?
print_error_dialog_title=Printer Error
printpreview_error_dialog_title=Print Preview Error

# Printing error messages.
#LOCALIZATION NOTE: Some of these messages come in pairs, one
# for printing and one for print previewing.  You can remove that
# distinction in your language by removing the entity with the _PP
# suffix; then the entity without a suffix will be used for both.
# You can also add that distinction to any of the messages that don't
# already have it by adding a new entity with a _PP suffix.
#
# For instance, if you delete PERR_GFX_PRINTER_DOC_IS_BUSY_PP, then
# the PERR_GFX_PRINTER_DOC_IS_BUSY message will be used for that error
# condition when print previewing as well as when printing.  If you
# add PERR_FAILURE_PP, then PERR_FAILURE will only be used when
# printing, and PERR_FAILURE_PP will be used under the same conditions
# when print previewing.
#
PERR_FAILURE=An error occurred while printing.

PERR_ABORT=The print job was aborted, or canceled.
PERR_NOT_AVAILABLE=Some printing functionality is not currently available.
PERR_NOT_IMPLEMENTED=Some printing functionality is not implemented yet.
PERR_OUT_OF_MEMORY=There is not enough free memory to print.
PERR_UNEXPECTED=There was an unexpected problem while printing.

PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE=No printers available.
PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE_PP=No printers available, cannot show print preview.
PERR_GFX_PRINTER_NAME_NOT_FOUND=The selected printer could not be found.
PERR_GFX_PRINTER_COULD_NOT_OPEN_FILE=Failed to open output file for print to file.
PERR_GFX_PRINTER_STARTDOC=Printing failed while starting the print job.
PERR_GFX_PRINTER_ENDDOC=Printing failed while completing the print job.
PERR_GFX_PRINTER_STARTPAGE=Printing failed while starting a new page.
PERR_GFX_PRINTER_DOC_IS_BUSY=Cannot print this document yet, it is still being loaded.
PERR_GFX_PRINTER_DOC_IS_BUSY_PP=Cannot print-preview this document yet, it is still being loaded.
PK
!<	+4chrome/en-US/locale/en-US/global/printjoboptions.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from printjoboptions.xul -->

<!ENTITY printJobOptions.title "Printer Properties">

<!ENTITY paperInput.label "Paper Size:">
<!ENTITY paperInput.accesskey "P">

<!ENTITY jobTitleInput.label "Job Title:">
<!ENTITY jobTitleInput.accesskey "J">

<!ENTITY colorGroup.label "Color:">
<!ENTITY grayRadio.label "Grayscale">
<!ENTITY grayRadio.accesskey "G">
<!ENTITY colorRadio.label "Color">
<!ENTITY colorRadio.accesskey "C">

<!ENTITY edgeMarginInput.label "Gap from edge of paper to Margin">
<!ENTITY topInput.label "Top:">
<!ENTITY topInput.accesskey "T">
<!ENTITY bottomInput.label "Bottom:">
<!ENTITY bottomInput.accesskey "B">
<!ENTITY leftInput.label "Left:">
<!ENTITY leftInput.accesskey "L">
<!ENTITY rightInput.label "Right:">
<!ENTITY rightInput.accesskey "R">
PK
!<C--7chrome/en-US/locale/en-US/global/regionNames.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

ad	=	Andorra
ae	=	United Arab Emirates
af	=	Afghanistan
ag	=	Antigua and Barbuda
ai	=	Anguilla
al	=	Albania
am	=	Armenia
ao	=	Angola
aq	=	Antarctica
ar	=	Argentina
as	=	American Samoa
at	=	Austria
au	=	Australia
aw	=	Aruba
az	=	Azerbaijan
ba	=	Bosnia and Herzegovina
bb	=	Barbados
bd	=	Bangladesh
be	=	Belgium
bf	=	Burkina Faso
bg	=	Bulgaria
bh	=	Bahrain
bi	=	Burundi
bj	=	Benin
bl	=	Saint Barthelemy
bm	=	Bermuda
bn	=	Brunei
bo	=	Bolivia
bq	=	Bonaire, Sint Eustatius, and Saba
br	=	Brazil
bs	=	Bahamas, The
bt	=	Bhutan
bv	=	Bouvet Island
bw	=	Botswana
by	=	Belarus
bz	=	Belize
ca	=	Canada
cc	=	Cocos (Keeling) Islands
cd	=	Congo (Kinshasa)
cf	=	Central African Republic
cg	=	Congo (Brazzaville)
ch	=	Switzerland
ci	=	Côte d’Ivoire
ck	=	Cook Islands
cl	=	Chile
cm	=	Cameroon
cn	=	China
co	=	Colombia
cp	=	Clipperton Island
cr	=	Costa Rica
cu	=	Cuba
cv	=	Cabo Verde
cw	=	Curaçao
cx	=	Christmas Island
cy	=	Cyprus
cz	=	Czech Republic
de	=	Germany
dg	=	Diego Garcia
dj	=	Djibouti
dk	=	Denmark
dm	=	Dominica
do	=	Dominican Republic
dz	=	Algeria
ec	=	Ecuador
ee	=	Estonia
eg	=	Egypt
eh	=	Western Sahara
er	=	Eritrea
es	=	Spain
et	=	Ethiopia
fi	=	Finland
fj	=	Fiji
fk	=	Falkland Islands (Islas Malvinas)
fm	=	Micronesia, Federated States of
fo	=	Faroe Islands
fr	=	France
ga	=	Gabon
gb	=	United Kingdom
gd	=	Grenada
ge	=	Georgia
gf	=	French Guiana
gg	=	Guernsey
gh	=	Ghana
gi	=	Gibraltar
gl	=	Greenland
gm	=	Gambia, The
gn	=	Guinea
gp	=	Guadeloupe
gq	=	Equatorial Guinea
gr	=	Greece
gs	=	South Georgia and South Sandwich Islands
gt	=	Guatemala
gu	=	Guam
gw	=	Guinea-Bissau
gy	=	Guyana
hk	=	Hong Kong
hm	=	Heard Island and McDonald Islands
hn	=	Honduras
hr	=	Croatia
ht	=	Haiti
hu	=	Hungary
id	=	Indonesia
ie	=	Ireland
il	=	Israel
im	=	Isle of Man
in	=	India
io	=	British Indian Ocean Territory
iq	=	Iraq
ir	=	Iran
is	=	Iceland
it	=	Italy
je	=	Jersey
jm	=	Jamaica
jo	=	Jordan
jp	=	Japan
ke	=	Kenya
kg	=	Kyrgyzstan
kh	=	Cambodia
ki	=	Kiribati
km	=	Comoros
kn	=	Saint Kitts and Nevis
kp	=	Korea, North
kr	=	Korea, South
kw	=	Kuwait
ky	=	Cayman Islands
kz	=	Kazakhstan
la	=	Laos
lb	=	Lebanon
lc	=	Saint Lucia
li	=	Liechtenstein
lk	=	Sri Lanka
lr	=	Liberia
ls	=	Lesotho
lt	=	Lithuania
lu	=	Luxembourg
lv	=	Latvia
ly	=	Libya
ma	=	Morocco
mc	=	Monaco
md	=	Moldova
me	=	Montenegro
mf	=	Saint Martin
mg	=	Madagascar
mh	=	Marshall Islands
mk	=	Macedonia
ml	=	Mali
mm	=	Burma
mn	=	Mongolia
mo	=	Macau
mp	=	Northern Mariana Islands
mq	=	Martinique
mr	=	Mauritania
ms	=	Montserrat
mt	=	Malta
mu	=	Mauritius
mv	=	Maldives
mw	=	Malawi
mx	=	Mexico
my	=	Malaysia
mz	=	Mozambique
na	=	Namibia
nc	=	New Caledonia
ne	=	Niger
nf	=	Norfolk Island
ng	=	Nigeria
ni	=	Nicaragua
nl	=	Netherlands
no	=	Norway
np	=	Nepal
nr	=	Nauru
nu	=	Niue
nz	=	New Zealand
om	=	Oman
pa	=	Panama
pe	=	Peru
pf	=	French Polynesia
pg	=	Papua New Guinea
ph	=	Philippines
pk	=	Pakistan
pl	=	Poland
pm	=	Saint Pierre and Miquelon
pn	=	Pitcairn Islands
pr	=	Puerto Rico
pt	=	Portugal
pw	=	Palau
py	=	Paraguay
qa	=	Qatar
qm	=	Midway Islands
qs	=	Bassas da India
qu	=	Juan de Nova Island
qw	=	Wake Island
qx	=	Glorioso Islands
qz	=	Akrotiri
re	=	Reunion
ro	=	Romania
rs	=	Serbia
ru	=	Russia
rw	=	Rwanda
sa	=	Saudi Arabia
sb	=	Solomon Islands
sc	=	Seychelles
sd	=	Sudan
se	=	Sweden
sg	=	Singapore
sh	=	Saint Helena, Ascension, and Tristan da Cunha
si	=	Slovenia
sk	=	Slovakia
sl	=	Sierra Leone
sm	=	San Marino
sn	=	Senegal
so	=	Somalia
sr	=	Suriname
ss	=	South Sudan
st	=	Sao Tome and Principe
sv	=	El Salvador
sx	=	Sint Maarten
sy	=	Syria
sz	=	Swaziland
tc	=	Turks and Caicos Islands
td	=	Chad
tf	=	French Southern and Antarctic Lands
tg	=	Togo
th	=	Thailand
tj	=	Tajikistan
tk	=	Tokelau
tl	=	Timor-Leste
tm	=	Turkmenistan
tn	=	Tunisia
to	=	Tonga
tr	=	Turkey
tt	=	Trinidad and Tobago
tv	=	Tuvalu
tw	=	Taiwan
tz	=	Tanzania
ua	=	Ukraine
ug	=	Uganda
us	=	United States
uy	=	Uruguay
uz	=	Uzbekistan
va	=	Vatican City
vc	=	Saint Vincent and the Grenadines
ve	=	Venezuela
vg	=	Virgin Islands, British
vi	=	Virgin Islands, U.S.
vn	=	Vietnam
vu	=	Vanuatu
wf	=	Wallis and Futuna
ws	=	Samoa
xa	=	Ashmore and Cartier Islands
xb	=	Baker Island
xc	=	Coral Sea Islands
xd	=	Dhekelia
xe	=	Europa Island
xg	=	Gaza Strip
xh	=	Howland Island
xj	=	Jan Mayen
xk	=	Kosovo
xl	=	Palmyra Atoll
xm	=	Kingman Reef
xp	=	Paracel Islands
xq	=	Jarvis Island
xr	=	Svalbard
xs	=	Spratly Islands
xt	=	Tromelin Island
xu	=	Johnston Atoll
xv	=	Navassa Island
xw	=	West Bank
ye	=	Yemen
yt	=	Mayotte
za	=	South Africa
zm	=	Zambia
zw	=	Zimbabwe
PK
!<1chrome/en-US/locale/en-US/global/resetProfile.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/.  -->

<!ENTITY refreshProfile.dialog.title         "Refresh &brandShortName;">
<!ENTITY refreshProfile.dialog.description1  "Start fresh to fix problems and restore performance.">
<!ENTITY refreshProfile.dialog.description2  "This will:">
<!ENTITY refreshProfile.dialog.items.label1  "Remove your add-ons and customizations">
<!ENTITY refreshProfile.dialog.items.label2  "Restore your browser settings to their defaults">
<!ENTITY refreshProfile.dialog.button.label  "Refresh &brandShortName;">

<!ENTITY refreshProfile.title                "Give &brandShortName; a tune up">
<!ENTITY refreshProfile.button.label         "Refresh &brandShortName;…">

<!ENTITY refreshProfile.cleaning.description "Almost done…">
PK
!<ZZ8chrome/en-US/locale/en-US/global/resetProfile.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE: These strings are used for profile reset.

# LOCALIZATION NOTE (resetUnusedProfile.message): %S is brandShortName.
resetUnusedProfile.message=It looks like you haven’t started %S in a while. Do you want to clean it up for a fresh, like-new experience? And by the way, welcome back!
# LOCALIZATION NOTE (resetUninstalled.message): %S is brandShortName.
resetUninstalled.message=Looks like you’ve reinstalled %S. Want us to clean it up for a fresh, like-new experience?

# LOCALIZATION NOTE (refreshProfile.resetButton.label): %S is brandShortName.
refreshProfile.resetButton.label=Refresh %S…
refreshProfile.resetButton.accesskey=e
PK
!<Ȍ9chrome/en-US/locale/en-US/global/search/search.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

addEngineConfirmTitle=Add Search Engine
addEngineConfirmation=Add “%S” to the list of engines available in the search bar?\n\nFrom: %S
addEngineAsCurrentText=Make this the c&urrent search engine
addEngineAddButtonLabel=Add

error_loading_engine_title=Download Error
# LOCALIZATION NOTE (error_loading_engine_msg2): %1$S = brandShortName, %2$S = location
error_loading_engine_msg2=%S could not download the search plugin from:\n%S
error_duplicate_engine_msg=%S could not install the search plugin from “%S” because an engine with the same name already exists.

error_invalid_engine_title=Install Error
error_invalid_format_title=Invalid Format
# LOCALIZATION NOTE (error_invalid_engine_msg2): %1$S = brandShortName, %2$S = location (url)
error_invalid_engine_msg2=%1$S could not install the search engine from: %2$S

suggestion_label=Suggestions
PK
!<]]

9chrome/en-US/locale/en-US/global/security/caps.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
CheckLoadURIError = Security Error: Content at %S may not load or link to %S.
CheckSameOriginError = Security Error: Content at %S may not load data from %S.
ExternalDataError = Security Error: Content at %S attempted to load %S, but may not load external data when being used as an image. 

# LOCALIZATION NOTE (GetPropertyDeniedOrigins):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
GetPropertyDeniedOrigins = Permission denied for <%1$S> to get property %2$S.%3$S from <%4$S>.
# LOCALIZATION NOTE (GetPropertyDeniedOriginsSubjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain".
GetPropertyDeniedOriginsSubjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to get property %2$S.%3$S from <%4$S> (document.domain has not been set).
# LOCALIZATION NOTE (GetPropertyDeniedOriginsObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
GetPropertyDeniedOriginsObjectDomain = Permission denied for <%1$S> (document.domain has not been set) to get property %2$S.%3$S from <%4$S> (document.domain=<%5$S>).
# LOCALIZATION NOTE (GetPropertyDeniedOriginsSubjectDomainObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain"
# %6$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
GetPropertyDeniedOriginsSubjectDomainObjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to get property %2$S.%3$S from <%4$S> (document.domain=<%6$S>).

# LOCALIZATION NOTE (SetPropertyDeniedOrigins):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
SetPropertyDeniedOrigins = Permission denied for <%1$S> to set property %2$S.%3$S on <%4$S>.
# LOCALIZATION NOTE (SetPropertyDeniedOriginsSubjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain".
SetPropertyDeniedOriginsSubjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to set property %2$S.%3$S on <%4$S> (document.domain has not been set).
# LOCALIZATION NOTE (SetPropertyDeniedOriginsObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
SetPropertyDeniedOriginsObjectDomain = Permission denied for <%1$S> (document.domain has not been set) to set property %2$S.%3$S on <%4$S> (document.domain=<%5$S>).
# LOCALIZATION NOTE (SetPropertyDeniedOriginsSubjectDomainObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the property of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain"
# %6$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
SetPropertyDeniedOriginsSubjectDomainObjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to set property %2$S.%3$S on <%4$S> (document.domain=<%6$S>).

# LOCALIZATION NOTE (CallMethodDeniedOrigins):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the method of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
CallMethodDeniedOrigins = Permission denied for <%1$S> to call method %2$S.%3$S on <%4$S>.
# LOCALIZATION NOTE (CallMethodDeniedOriginsSubjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the method of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain".
CallMethodDeniedOriginsSubjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to call method %2$S.%3$S on <%4$S> (document.domain has not been set).
# LOCALIZATION NOTE (CallMethodDeniedOriginsObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the method of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
CallMethodDeniedOriginsObjectDomain = Permission denied for <%1$S> (document.domain has not been set) to call method %2$S.%3$S on <%4$S> (document.domain=<%5$S>).
# LOCALIZATION NOTE (CallMethodDeniedOriginsSubjectDomainObjectDomain):
# %1$S is the origin of the script which was denied access.
# %2$S is the type of object it was.
# %3$S is the method of that object that access was denied for.
# %4$S is the origin of the object access was denied to.
# %5$S is the value of document.domain for the script which was denied access;
#      don't translate "document.domain"
# %6$S is the value of document.domain for the object being accessed;
#      don't translate "document.domain".
CallMethodDeniedOriginsSubjectDomainObjectDomain = Permission denied for <%1$S> (document.domain=<%5$S>) to call method %2$S.%3$S on <%4$S> (document.domain=<%6$S>).

GetPropertyDeniedOriginsOnlySubject = Permission denied for <%S> to get property %S.%S
SetPropertyDeniedOriginsOnlySubject = Permission denied for <%S> to set property %S.%S
CallMethodDeniedOriginsOnlySubject = Permission denied for <%S> to call method %S.%S
CreateWrapperDenied = Permission denied to create wrapper for object of class %S
CreateWrapperDeniedForOrigin = Permission denied for <%2$S> to create wrapper for object of class %1$S
ProtocolFlagError = Warning: Protocol handler for ‘%S’ doesn’t advertise a security policy.  While loading of such protocols is allowed for now, this is deprecated.  Please see the documentation in nsIProtocolHandler.idl.
PK
!<)ɱ8chrome/en-US/locale/en-US/global/security/csp.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# CSP Warnings:
# LOCALIZATION NOTE (CSPViolation):
# %1$S is the reason why the resource has not been loaded.
CSPViolation = The page’s settings blocked the loading of a resource: %1$S
# LOCALIZATION NOTE (CSPViolationWithURI):
# %1$S is the directive that has been violated.
# %2$S is the URI of the resource which violated the directive.
CSPViolationWithURI = The page’s settings blocked the loading of a resource at %2$S (“%1$S”).
# LOCALIZATION NOTE (CSPROViolation):
# %1$S is the reason why the resource has not been loaded.
CSPROViolation = A violation occurred for a report-only CSP policy (“%1$S”). The behavior was allowed, and a CSP report was sent.
# LOCALIZATION NOTE (CSPROViolationWithURI):
# %1$S is the directive that has been violated.
# %2$S is the URI of the resource which violated the directive.
CSPROViolationWithURI = The page’s settings observed the loading of a resource at %2$S (“%1$S”). A CSP report is being sent.
# LOCALIZATION NOTE (triedToSendReport):
# %1$S is the URI we attempted to send a report to.
triedToSendReport = Tried to send report to invalid URI: “%1$S”
# LOCALIZATION NOTE (couldNotParseReportURI):
# %1$S is the report URI that could not be parsed
couldNotParseReportURI = couldn’t parse report URI: %1$S
# LOCALIZATION NOTE (couldNotProcessUnknownDirective):
# %1$S is the unknown directive
couldNotProcessUnknownDirective = Couldn’t process unknown directive ‘%1$S’
# LOCALIZATION NOTE (ignoringUnknownOption):
# %1$S is the option that could not be understood
ignoringUnknownOption = Ignoring unknown option %1$S
# LOCALIZATION NOTE (ignoringDuplicateSrc):
# %1$S defines the duplicate src
ignoringDuplicateSrc = Ignoring duplicate source %1$S
# LOCALIZATION NOTE (ignoringSrcFromMetaCSP):
# %1$S defines the ignored src
ignoringSrcFromMetaCSP = Ignoring source ‘%1$S’ (Not supported when delivered via meta element).
# LOCALIZATION NOTE (ignoringSrcWithinScriptStyleSrc):
# %1$S is the ignored src
# script-src and style-src are directive names and should not be localized
ignoringSrcWithinScriptStyleSrc = Ignoring “%1$S” within script-src or style-src: nonce-source or hash-source specified
# LOCALIZATION NOTE (ignoringSrcForStrictDynamic):
# %1$S is the ignored src
# script-src, as well as 'strict-dynamic' should not be localized
ignoringSrcForStrictDynamic = Ignoring “%1$S” within script-src: ‘strict-dynamic’ specified
# LOCALIZATION NOTE (ignoringStrictDynamic):
# %1$S is the ignored src
ignoringStrictDynamic = Ignoring source “%1$S” (Only supported within script-src). 
# LOCALIZATION NOTE (strictDynamicButNoHashOrNonce):
# %1$S is the csp directive that contains 'strict-dynamic'
# 'strict-dynamic' should not be localized
strictDynamicButNoHashOrNonce = Keyword ‘strict-dynamic’ within “%1$S” with no valid nonce or hash might block all scripts from loading
# LOCALIZATION NOTE (reportURInotHttpsOrHttp2):
# %1$S is the ETLD of the report URI that is not HTTP or HTTPS
reportURInotHttpsOrHttp2 = The report URI (%1$S) should be an HTTP or HTTPS URI.
# LOCALIZATION NOTE (reportURInotInReportOnlyHeader):
# %1$S is the ETLD of the page with the policy
reportURInotInReportOnlyHeader = This site (%1$S) has a Report-Only policy without a report URI. CSP will not block and cannot report violations of this policy.
# LOCALIZATION NOTE (failedToParseUnrecognizedSource):
# %1$S is the CSP Source that could not be parsed
failedToParseUnrecognizedSource = Failed to parse unrecognized source %1$S
# LOCALIZATION NOTE (inlineScriptBlocked):
# inline script refers to JavaScript code that is embedded into the HTML document.
inlineScriptBlocked = An attempt to execute inline scripts has been blocked
# LOCALIZATION NOTE (inlineStyleBlocked):
# inline style refers to CSS code that is embedded into the HTML document.
inlineStyleBlocked = An attempt to apply inline style sheets has been blocked
# LOCALIZATION NOTE (scriptFromStringBlocked):
# eval is a name and should not be localized.
scriptFromStringBlocked = An attempt to call JavaScript from a string (by calling a function like eval) has been blocked
# LOCALIZATION NOTE (upgradeInsecureRequest):
# %1$S is the URL of the upgraded request; %2$S is the upgraded scheme.
upgradeInsecureRequest = Upgrading insecure request ‘%1$S’ to use ‘%2$S’
# LOCALIZATION NOTE (ignoreSrcForDirective):
ignoreSrcForDirective = Ignoring srcs for directive ‘%1$S’
# LOCALIZATION NOTE (hostNameMightBeKeyword):
# %1$S is the hostname in question and %2$S is the keyword
hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use ‘%2$S’ (wrapped in single quotes).
# LOCALIZATION NOTE (notSupportingDirective):
# directive is not supported (e.g. 'reflected-xss')
notSupportingDirective = Not supporting directive ‘%1$S’. Directive and values will be ignored.
# LOCALIZATION NOTE (blockAllMixedContent):
# %1$S is the URL of the blocked resource load.
blockAllMixedContent = Blocking insecure request ‘%1$S’.
# LOCALIZATION NOTE (ignoringDirectiveWithNoValues):
# %1$S is the name of a CSP directive that requires additional values (e.g., 'require-sri-for')
ignoringDirectiveWithNoValues = Ignoring ‘%1$S’ since it does not contain any parameters.
# LOCALIZATION NOTE (ignoringReportOnlyDirective):
# %1$S is the directive that is ignored in report-only mode.
ignoringReportOnlyDirective = Ignoring sandbox directive when delivered in a report-only policy ‘%1$S’
# LOCALIZATION NOTE (deprecatedReferrerDirective):
# %1$S is the value of the deprecated Referrer Directive.
deprecatedReferrerDirective = Referrer Directive ‘%1$S’ has been deprecated. Please use the Referrer-Policy header instead.
# LOCALIZATION NOTE (IgnoringSrcBecauseOfDirective):
# %1$S is the name of the src that is ignored.
# %2$S is the name of the directive that causes the src to be ignored.
IgnoringSrcBecauseOfDirective=Ignoring ‘%1$S’ because of ‘%2$S’ directive.

# CSP Errors:
# LOCALIZATION NOTE (couldntParseInvalidSource):
# %1$S is the source that could not be parsed
couldntParseInvalidSource = Couldn’t parse invalid source %1$S
# LOCALIZATION NOTE (couldntParseInvalidHost):
# %1$S is the host that's invalid
couldntParseInvalidHost = Couldn’t parse invalid host %1$S
# LOCALIZATION NOTE (couldntParseScheme):
# %1$S is the string source
couldntParseScheme = Couldn’t parse scheme in %1$S
# LOCALIZATION NOTE (couldntParsePort):
# %1$S is the string source
couldntParsePort = Couldn’t parse port in %1$S
# LOCALIZATION NOTE (duplicateDirective):
# %1$S is the name of the duplicate directive
duplicateDirective = Duplicate %1$S directives detected.  All but the first instance will be ignored.
# LOCALIZATION NOTE (deprecatedDirective):
# %1$S is the name of the deprecated directive, %2$S is the name of the replacement.
deprecatedDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘%2$S’ instead.
# LOCALIZATION NOTE (couldntParseInvalidSandboxFlag):
# %1$S is the option that could not be understood
couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’
PK
!<;C#C#=chrome/en-US/locale/en-US/global/security/security.properties# Mixed Content Blocker
# LOCALIZATION NOTE: "%1$S" is the URI of the blocked mixed content resource
BlockMixedDisplayContent = Blocked loading mixed display content “%1$S”
BlockMixedActiveContent = Blocked loading mixed active content “%1$S”

# CORS
# LOCALIZATION NOTE: Do not translate "Access-Control-Allow-Origin", Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers
CORSDisabled=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS disabled).
CORSRequestNotHttp=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request not http).
CORSMissingAllowOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
CORSAllowOriginNotMatchingOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘%2$S’).
CORSNotSupportingCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘%1$S’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).
CORSMethodNotFound=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’).
CORSMissingAllowCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
CORSPreflightDidNotSucceed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight channel did not succeed).
CORSInvalidAllowMethod=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Methods’).
CORSInvalidAllowHeader=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Headers’).
CORSMissingAllowHeaderFromPreflight=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: missing token ‘%2$S’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).

# LOCALIZATION NOTE: Do not translate "Strict-Transport-Security", "HSTS", "max-age" or "includeSubDomains"
STSUnknownError=Strict-Transport-Security: An unknown error occurred processing the header specified by the site.
STSUntrustworthyConnection=Strict-Transport-Security: The connection to the site is untrustworthy, so the specified header was ignored.
STSCouldNotParseHeader=Strict-Transport-Security: The site specified a header that could not be parsed successfully.
STSNoMaxAge=Strict-Transport-Security: The site specified a header that did not include a ‘max-age’ directive.
STSMultipleMaxAges=Strict-Transport-Security: The site specified a header that included multiple ‘max-age’ directives.
STSInvalidMaxAge=Strict-Transport-Security: The site specified a header that included an invalid ‘max-age’ directive.
STSMultipleIncludeSubdomains=Strict-Transport-Security: The site specified a header that included multiple ‘includeSubDomains’ directives.
STSInvalidIncludeSubdomains=Strict-Transport-Security: The site specified a header that included an invalid ‘includeSubDomains’ directive.
STSCouldNotSaveState=Strict-Transport-Security: An error occurred noting the site as a Strict-Transport-Security host.

# LOCALIZATION NOTE: Do not translate "Public-Key-Pins", "HPKP", "max-age", "report-uri" or "includeSubDomains"
PKPUnknownError=Public-Key-Pins: An unknown error occurred processing the header specified by the site.
PKPUntrustworthyConnection=Public-Key-Pins: The connection to the site is untrustworthy, so the specified header was ignored.
PKPCouldNotParseHeader=Public-Key-Pins: The site specified a header that could not be parsed successfully.
PKPNoMaxAge=Public-Key-Pins: The site specified a header that did not include a ‘max-age’ directive.
PKPMultipleMaxAges=Public-Key-Pins: The site specified a header that included multiple ‘max-age’ directives.
PKPInvalidMaxAge=Public-Key-Pins: The site specified a header that included an invalid ‘max-age’ directive.
PKPMultipleIncludeSubdomains=Public-Key-Pins: The site specified a header that included multiple ‘includeSubDomains’ directives.
PKPInvalidIncludeSubdomains=Public-Key-Pins: The site specified a header that included an invalid ‘includeSubDomains’ directive.
PKPInvalidPin=Public-Key-Pins: The site specified a header that included an invalid pin.
PKPMultipleReportURIs=Public-Key-Pins: The site specified a header that included multiple ‘report-uri’ directives.
PKPPinsetDoesNotMatch=Public-Key-Pins: The site specified a header that did not include a matching pin.
PKPNoBackupPin=Public-Key-Pins: The site specified a header that did not include a backup pin.
PKPCouldNotSaveState=Public-Key-Pins: An error occurred noting the site as a Public-Key-Pins host.
PKPRootNotBuiltIn=Public-Key-Pins: The certificate used by the site was not issued by a certificate in the default root certificate store. To prevent accidental breakage, the specified header was ignored.

# LOCALIZATION NOTE: Do not translate "SHA-1"
SHA1Sig=This site makes use of a SHA-1 Certificate; it’s recommended you use certificates with signature algorithms that use hash functions stronger than SHA-1.
InsecurePasswordsPresentOnPage=Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.
InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
# LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource
LoadingMixedActiveContent2=Loading mixed (insecure) active content “%1$S” on a secure page
LoadingMixedDisplayContent2=Loading mixed (insecure) display content “%1$S” on a secure page
# LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.

# Sub-Resource Integrity
# LOCALIZATION NOTE: Do not translate "script" or "integrity". "%1$S" is the invalid token found in the attribute.
MalformedIntegrityHash=The script element has a malformed hash in its integrity attribute: “%1$S”. The correct format is “<hash algorithm>-<hash value>”.
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
# LOCALIZATION NOTE: Do not translate "integrity". "%1$S" is the type of hash algorithm in use (e.g. "sha256").
IntegrityMismatch=None of the “%1$S” hashes in the integrity attribute match the content of the subresource.
# LOCALIZATION NOTE: "%1$S" is the URI of the sub-resource that cannot be protected using SRI.
IneligibleResource=“%1$S” is not eligible for integrity checks since it’s neither CORS-enabled nor same-origin.
# LOCALIZATION NOTE: Do not translate "integrity". "%1$S" is the invalid hash algorithm found in the attribute.
UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: “%1$S”
# LOCALIZATION NOTE: Do not translate "integrity"
NoValidMetadata=The integrity attribute does not contain any valid metadata.

# LOCALIZATION NOTE: Do not translate "RC4".
WeakCipherSuiteWarning=This site uses the cipher RC4 for encryption, which is deprecated and insecure.

#XCTO: nosniff
# LOCALIZATION NOTE: Do not translate "X-Content-Type-Options: nosniff".
MimeTypeMismatch=The resource from “%1$S” was blocked due to MIME type mismatch (X-Content-Type-Options: nosniff).
# LOCALIZATION NOTE: Do not translate "X-Content-Type-Options" and also do not trasnlate "nosniff".
XCTOHeaderValueMissing=X-Content-Type-Options header warning: value was “%1$S”; did you mean to send “nosniff”?

BlockScriptWithWrongMimeType=Script from “%1$S” was blocked because of a disallowed MIME type.

# LOCALIZATION NOTE: Do not translate "data: URI".
BlockTopLevelDataURINavigation=Navigation to toplevel data: URI not allowed (Blocked loading of: “%1$S”)
PK
!<i&rQ3chrome/en-US/locale/en-US/global/svg/svg.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

AttributeParseWarning=Unexpected value %2$S parsing %1$S attribute.
PK
!<0chrome/en-US/locale/en-US/global/textcontext.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY cutCmd.label "Cut">
<!ENTITY cutCmd.accesskey "t">
<!ENTITY copyCmd.label "Copy">
<!ENTITY copyCmd.accesskey "c">
<!ENTITY pasteCmd.label "Paste">
<!ENTITY pasteCmd.accesskey "p">
<!ENTITY undoCmd.label "Undo">
<!ENTITY undoCmd.accesskey "u">
<!ENTITY selectAllCmd.label "Select All">
<!ENTITY selectAllCmd.accesskey "a">
<!ENTITY deleteCmd.label "Delete">
<!ENTITY deleteCmd.accesskey "d">

<!ENTITY spellAddToDictionary.label "Add to Dictionary">
<!ENTITY spellAddToDictionary.accesskey "o">
<!ENTITY spellUndoAddToDictionary.label "Undo Add To Dictionary">
<!ENTITY spellUndoAddToDictionary.accesskey "n">
<!ENTITY spellCheckToggle.label "Check Spelling">
<!ENTITY spellCheckToggle.accesskey "g">
<!ENTITY spellNoSuggestions.label "(No Spelling Suggestions)">
<!ENTITY spellDictionaries.label "Languages">
<!ENTITY spellDictionaries.accesskey "l">

<!ENTITY searchTextBox.clear.label "Clear">

<!ENTITY fillLoginMenu.label          "Fill Login">
<!ENTITY fillLoginMenu.accesskey      "F">
<!ENTITY fillPasswordMenu.label       "Fill Password">
<!ENTITY fillPasswordMenu.accesskey   "F">
<!ENTITY fillUsernameMenu.label       "Fill Username">
<!ENTITY fillUsernameMenu.accesskey   "F">
<!ENTITY noLoginSuggestions.label     "(No Login Suggestions)">
<!ENTITY viewSavedLogins.label        "View Saved Logins">
PK
!<^)chrome/en-US/locale/en-US/global/tree.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY restoreColumnOrder.label "Restore Column Order">
PK
!<M[		2chrome/en-US/locale/en-US/global/videocontrols.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY playButton.playLabel "Play">
<!ENTITY playButton.pauseLabel "Pause">
<!ENTITY muteButton.muteLabel "Mute">
<!ENTITY muteButton.unmuteLabel "Unmute">
<!ENTITY fullscreenButton.enterfullscreenlabel "Full Screen">
<!ENTITY fullscreenButton.exitfullscreenlabel "Exit Full Screen">
<!ENTITY castingButton.castingLabel "Cast to Screen">
<!ENTITY closedCaption.off "Off">

<!ENTITY stats.media "Media">
<!ENTITY stats.size "Size">
<!ENTITY stats.activity "Activity">
<!ENTITY stats.activityPaused "Paused">
<!ENTITY stats.activityPlaying "Playing">
<!ENTITY stats.activityEnded "Ended">
<!ENTITY stats.activitySeeking "(seeking)">
<!ENTITY stats.volume "Volume">
<!ENTITY stats.framesParsed "Frames parsed">
<!ENTITY stats.framesDecoded "Frames decoded">
<!ENTITY stats.framesPresented "Frames presented">
<!ENTITY stats.framesPainted "Frames painted">

<!ENTITY error.aborted "Video loading stopped.">
<!ENTITY error.network "Video playback aborted due to a network error.">
<!ENTITY error.decode "Video can’t be played because the file is corrupt.">
<!ENTITY error.srcNotSupported "Video format or MIME type is not supported.">
<!ENTITY error.noSource2 "No video with supported format and MIME type found.">
<!ENTITY error.generic "Video playback aborted due to an unknown error.">

<!-- LOCALIZATION NOTE (scrubberScale.nameFormat): the #1 string is the current
media position, and the #2 string is the total duration. For example, when at
the 5 minute mark in a 6 hour long video, #1 would be "5:00" and #2 would be
"6:00:00", result string would be "5:00 of 6:00:00 elapsed".
-->
<!ENTITY scrubberScale.nameFormat "#1 of #2 elapsed">

<!-- LOCALIZATION NOTE (positionAndDuration.nameFormat): the #1 string is the current
media position, and the #2 string is the total duration. For example, when at
the 5 minute mark in a 6 hour long video, #1 would be "5:00" and #2 would be
"6:00:00", result string would be "5:00 / 6:00:00".
Note that #2 is not always available. For example, when at the 5 minute mark in an
unknown duration video, #1 would be "5:00" and the string which is surrounded by
<span> would be deleted, result string would be "5:00".
-->
<!ENTITY positionAndDuration.nameFormat "#1<span> / #2</span>">
PK
!<2/chrome/en-US/locale/en-US/global/viewSource.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from content/viewSource.xul -->

<!-- LOCALIZATION NOTE (mainWindow.title) : DONT_TRANSLATE --> 
<!ENTITY mainWindow.title "&brandFullName;">
<!-- LOCALIZATION NOTE (mainWindow.titlemodifier) : DONT_TRANSLATE --> 
<!ENTITY mainWindow.titlemodifier "&brandFullName;">
<!-- LOCALIZATION NOTE (mainWindow.titlemodifierseparator) : DONT_TRANSLATE -->
<!ENTITY mainWindow.titlemodifierseparator " - ">
<!ENTITY mainWindow.preface "Source of: ">

<!ENTITY fileMenu.label "File">
<!ENTITY fileMenu.accesskey "F">
<!ENTITY savePageCmd.label "Save Page As…">
<!ENTITY savePageCmd.accesskey "A">
<!ENTITY savePageCmd.commandkey "S">
<!ENTITY pageSetupCmd.label "Page Setup…">
<!ENTITY pageSetupCmd.accesskey "u">
<!ENTITY printPreviewCmd.label "Print Preview">
<!ENTITY printPreviewCmd.accesskey "v">
<!ENTITY printCmd.label "Print…">
<!ENTITY printCmd.accesskey "P">
<!ENTITY printCmd.commandkey "P">
<!ENTITY closeCmd.label "Close">
<!ENTITY closeCmd.accesskey "C">
<!ENTITY closeCmd.commandkey "W">

<!-- LOCALIZATION NOTE :
textEnlarge.commandkey3, textReduce.commandkey2 and
textReset.commandkey2 are alternative acceleration keys for zoom.
If shift key is needed with your locale popular keyboard for them,
you can use these alternative items. Otherwise, their values should be empty.  -->

<!ENTITY textEnlarge.commandkey "+">
<!ENTITY textEnlarge.commandkey2 "=">
<!ENTITY textEnlarge.commandkey3 "">
<!ENTITY textReduce.commandkey "-">
<!ENTITY textReduce.commandkey2 "">
<!ENTITY textReset.commandkey "0">
<!ENTITY textReset.commandkey2 "">

<!ENTITY goToLineCmd.label "Go to Line…">
<!ENTITY goToLineCmd.accesskey "G">
<!ENTITY goToLineCmd.commandkey "l">

<!ENTITY viewMenu.label           "View">
<!ENTITY viewMenu.accesskey       "V">
<!ENTITY reloadCmd.label "Reload">
<!ENTITY reloadCmd.accesskey "R">
<!ENTITY reloadCmd.commandkey "r">
<!ENTITY menu_wrapLongLines.title "Wrap Long Lines"> 
<!ENTITY menu_wrapLongLines.accesskey "W">
<!ENTITY menu_highlightSyntax.label "Syntax Highlighting">
<!ENTITY menu_highlightSyntax.accesskey "H">
<!ENTITY menu_textSize.label "Text Size">
<!ENTITY menu_textSize.accesskey "Z">
<!ENTITY menu_textEnlarge.label "Increase">
<!ENTITY menu_textEnlarge.accesskey "I">
<!ENTITY menu_textReduce.label "Decrease">
<!ENTITY menu_textReduce.accesskey "D">
<!ENTITY menu_textReset.label "Normal">
<!ENTITY menu_textReset.accesskey "N">

<!ENTITY findOnCmd.label     "Find in This Page…">
<!ENTITY findOnCmd.accesskey "F">
<!ENTITY findOnCmd.commandkey "f">
<!ENTITY findAgainCmd.label  "Find Again">
<!ENTITY findAgainCmd.accesskey "g">
<!ENTITY findAgainCmd.commandkey "g">
<!ENTITY findAgainCmd.commandkey2 "VK_F3">
<!ENTITY findSelectionCmd.commandkey "e">

<!ENTITY backCmd.label "Back">
<!ENTITY backCmd.accesskey "B">
<!ENTITY forwardCmd.label "Forward">
<!ENTITY forwardCmd.accesskey "F">
<!ENTITY goBackCmd.commandKey "[">
<!ENTITY goForwardCmd.commandKey "]">

<!ENTITY copyLinkCmd.label "Copy Link Location">
<!ENTITY copyLinkCmd.accesskey "L">
<!ENTITY copyEmailCmd.label "Copy Email Address">
<!ENTITY copyEmailCmd.accesskey "E">
PK
!<{6chrome/en-US/locale/en-US/global/viewSource.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

goToLineTitle     = Go to line
goToLineText      = Enter line number
invalidInputTitle = Invalid input
invalidInputText  = The line number entered is invalid.
outOfRangeTitle   = Line not found
outOfRangeText    = The specified line was not found.
statusBarLineCol  = Line %1$S, Col %2$S
viewSelectionSourceTitle = DOM Source of Selection
viewMathMLSourceTitle    = DOM Source of MathML

context_goToLine_label        = Go to Line…
context_goToLine_accesskey    = L
context_wrapLongLines_label   = Wrap Long Lines
context_highlightSyntax_label = Syntax Highlighting
PK
!<FN..+chrome/en-US/locale/en-US/global/wizard.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  button-back-mac.label        "Go Back">
<!ENTITY  button-back-mac.accesskey    "B">
<!ENTITY  button-next-mac.label        "Continue">
<!ENTITY  button-next-mac.accesskey    "C">
<!ENTITY  button-finish-mac.label      "Done">
<!ENTITY  button-cancel-mac.label      "Cancel">

<!ENTITY  button-back-unix.label       "Back">
<!ENTITY  button-back-unix.accesskey   "B">
<!ENTITY  button-next-unix.label       "Next">
<!ENTITY  button-next-unix.accesskey   "N">
<!ENTITY  button-finish-unix.label     "Finish">
<!ENTITY  button-cancel-unix.label     "Cancel">

<!ENTITY  button-back-win.label        "&lt; Back">
<!ENTITY  button-back-win.accesskey    "B">
<!ENTITY  button-next-win.label        "Next &gt;">
<!ENTITY  button-next-win.accesskey    "N">
<!ENTITY  button-finish-win.label      "Finish">
<!ENTITY  button-cancel-win.label      "Cancel">
PK
!<M1ZZ2chrome/en-US/locale/en-US/global/wizard.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

default-first-title=Welcome to the %S
default-last-title=Completing the %S
default-first-title-mac=Introduction
default-last-title-mac=Conclusion
PK
!<c/chrome/en-US/locale/en-US/global/xbl.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

UnexpectedElement=Unexpected <%1$S> element.
# LOCALIZATION NOTE: do not localize key=“%S” modifiers=“%S” id=“%S”
GTK2Conflict2=Key event not available on GTK2: key=“%S” modifiers=“%S” id=“%S”
WinConflict2=Key event not available on some keyboard layouts: key=“%S” modifiers=“%S” id=“%S”
TooDeepBindingRecursion=The XBL binding “%S” is already used by too many ancestor elements; not applying it to prevent infinite recursion.
CircularExtendsBinding=Extending the XBL binding “%S” with “%S” would lead to it extending itself
# LOCALIZATION NOTE: do not localize <handler command="…">
CommandNotInChrome=Use of <handler command="…"> not allowed outside chrome.
MalformedXBL = An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?
InvalidExtendsBinding=Extending “%S” is invalid. In general, do not extend tag names.
MissingIdAttr = An “id” attribute missing on the binding tag.
PK
!<)dd4chrome/en-US/locale/en-US/global/xml/prettyprint.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY xml.nostylesheet "This XML file does not appear to have any style information associated with it. The document tree is shown below.">
PK
!<[PJJ5chrome/en-US/locale/en-US/global/xslt/xslt.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

1  = Parsing an XSLT stylesheet failed.
2  = Parsing an XPath expression failed.
3  = 
4  = XSLT transformation failed.
5  = Invalid XSLT/XPath function.
6  = XSLT Stylesheet (possibly) contains a recursion.
7  = Attribute value illegal in XSLT 1.0.
8  = An XPath expression was expected to return a NodeSet.
9  = XSLT transformation was terminated by <xsl:message>.
10 = A network error occurred loading an XSLT stylesheet:
11 = An XSLT stylesheet does not have an XML mimetype:
12 = An XSLT stylesheet directly or indirectly imports or includes itself:
13 = An XPath function was called with the wrong number of arguments.
14 = An unknown XPath extension function was called.
15 = XPath parse failure: ‘)’ expected:
16 = XPath parse failure: invalid axis:
17 = XPath parse failure: Name or Nodetype test expected:
18 = XPath parse failure: ‘]’ expected:
19 = XPath parse failure: invalid variable name:
20 = XPath parse failure: unexpected end of expression:
21 = XPath parse failure: operator expected:
22 = XPath parse failure: unclosed literal:
23 = XPath parse failure: ‘:’ unexpected:
24 = XPath parse failure: ‘!’ unexpected, negation is not():
25 = XPath parse failure: illegal character found:
26 = XPath parse failure: binary operator expected:
27 = An XSLT stylesheet load was blocked for security reasons.
28 = Evaluating an invalid expression.
29 = Unbalanced curly brace.
30 = Creating an element with an invalid QName.
31 = Variable binding shadows variable binding within the same template.
32 = Call to the key function not allowed.

LoadingError = Error loading stylesheet: %S
TransformError = Error during XSLT transformation: %S
PK
!<J/chrome/en-US/locale/en-US/global/xul.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

MissingOverlay=Failed to load overlay from %1$S.
PINotInProlog=<?%1$S?> processing instruction does not have any effect outside the prolog anymore (see bug 360119).
NeededToWrapXUL=XUL box for %1$S element contained an inline %2$S child, forcing all its children to be wrapped in a block.
NeededToWrapXULInlineBox=XUL box for %1$S element contained an inline %2$S child, forcing all its children to be wrapped in a block.  This can often be fixed by replacing “display: -moz-inline-box” with “display: -moz-inline-box; display: inline-block”.
PK
!<@OZZCchrome/en-US/locale/en-US/global-platform/mac/accessible.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle

# Universal Access API support
# (Mac Only)
# The Role Description for AXWebArea (the web widget). Like in Safari.
htmlContent = HTML Content
# The Role Description for the Tab button.
tab     =       tab
# The Role Description for definition list dl, dt and dd
term    =       term
definition =    definition
# The Role Description for an input type="search" text field
searchTextField = search text field
# The Role Description for WAI-ARIA Landmarks
application =   application
search  =       search
banner  =       banner
navigation =    navigation
complementary = complementary
content =       content
main    =       main
# The (spoken) role description for various WAI-ARIA roles
alert       =      alert
alertDialog =      alert dialog
article     =      article
document    =      document
# The (spoken) role description for the WAI-ARIA figure role
# https://w3c.github.io/aria/core-aam/core-aam.html#role-map-figure
figure     =      figure
# The (spoken) role description for the WAI-ARIA heading role
# https://w3c.github.io/aria/core-aam/core-aam.html#role-map-heading
heading     =      heading
log         =      log
marquee     =      marquee
math        =      math
note        =      note
region      =      region
status      =      application status
timer       =      timer
tooltip     =      tooltip
separator    =      separator
tabPanel     =      tab panel
PK
!<eGgg=chrome/en-US/locale/en-US/global-platform/mac/intl.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE (intl.ellipsis): Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
intl.ellipsis=…
PK
!<Echrome/en-US/locale/en-US/global-platform/mac/platformKeys.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#mac
#this file defines the on screen display names for the various modifier keys
#these are used in XP menus to show keyboard shortcuts

#the shift key - open up arrow symbol (ctrl-e)
VK_SHIFT=\u21e7

#the command key - clover leaf symbol (ctrl-q)
VK_META=\u2318

#the win key - never generated by native key event
VK_WIN=win

#the option/alt key - splitting tracks symbol (ctrl-g)
VK_ALT=\u2325

#the control key. hat symbol (ctrl-f)
VK_CONTROL=\u2303

#the separator character used between modifiers (none on Mac OS)
MODIFIER_SEPARATOR=
PK
!<hDchrome/en-US/locale/en-US/global-platform/unix/accessible.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle
PK
!<eGgg>chrome/en-US/locale/en-US/global-platform/unix/intl.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE (intl.ellipsis): Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
intl.ellipsis=…
PK
!<ccFchrome/en-US/locale/en-US/global-platform/unix/platformKeys.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#default
#this file defines the on screen display names for the various modifier keys
#these are used in XP menus to show keyboard shortcuts

#the shift key
VK_SHIFT=Shift

#the command key
VK_META=Meta

#the win key (Super key and Hyper keys are mapped to DOM Win key)
VK_WIN=Win

#the alt key
VK_ALT=Alt

#the control key
VK_CONTROL=Ctrl

#the separator character used between modifiers 
MODIFIER_SEPARATOR=+
PK
!<hCchrome/en-US/locale/en-US/global-platform/win/accessible.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle
PK
!<eGgg=chrome/en-US/locale/en-US/global-platform/win/intl.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE (intl.ellipsis): Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
intl.ellipsis=…
PK
!<qj..Echrome/en-US/locale/en-US/global-platform/win/platformKeys.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#default
#this file defines the on screen display names for the various modifier keys
#these are used in XP menus to show keyboard shortcuts

#the shift key
VK_SHIFT=Shift

#the command key
VK_META=Meta

#the win key
VK_WIN=Win

#the alt key
VK_ALT=Alt

#the control key
VK_CONTROL=Ctrl

#the separator character used between modifiers 
MODIFIER_SEPARATOR=+
PK
!<I;22@chrome/en-US/locale/en-US/mozapps/downloads/downloads.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 (shortSeconds): Semi-colon list of plural
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# s is the short form for seconds
shortSeconds=s;s

# LOCALIZATION NOTE (shortMinutes): Semi-colon list of plural
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# m is the short form for minutes
shortMinutes=m;m

# LOCALIZATION NOTE (shortHours): Semi-colon list of plural
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# h is the short form for hours
shortHours=h;h

# LOCALIZATION NOTE (shortDays): Semi-colon list of plural
# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# d is the short form for days
shortDays=d;d

downloadErrorAlertTitle=Download Error
downloadErrorGeneric=The download cannot be saved because an unknown error occurred.\n\nPlease try again.

# LOCALIZATION NOTE: we don't have proper plural support in the CPP code; bug 463102
quitCancelDownloadsAlertTitle=Cancel All Downloads?
quitCancelDownloadsAlertMsg=If you exit now, 1 download will be canceled. Are you sure you want to exit?
quitCancelDownloadsAlertMsgMultiple=If you exit now, %S downloads will be canceled. Are you sure you want to exit?
quitCancelDownloadsAlertMsgMac=If you quit now, 1 download will be canceled. Are you sure you want to quit?
quitCancelDownloadsAlertMsgMacMultiple=If you quit now, %S downloads will be canceled. Are you sure you want to quit?
offlineCancelDownloadsAlertTitle=Cancel All Downloads?
offlineCancelDownloadsAlertMsg=If you go offline now, 1 download will be canceled. Are you sure you want to go offline?
offlineCancelDownloadsAlertMsgMultiple=If you go offline now, %S downloads will be canceled. Are you sure you want to go offline?
leavePrivateBrowsingCancelDownloadsAlertTitle=Cancel All Downloads?
leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2=If you close all Private Browsing windows now, 1 download will be canceled. Are you sure you want to leave Private Browsing?
leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2=If you close all Private Browsing windows now, %S downloads will be canceled. Are you sure you want to leave Private Browsing?
cancelDownloadsOKText=Cancel 1 Download
cancelDownloadsOKTextMultiple=Cancel %S Downloads
dontQuitButtonWin=Don’t Exit
dontQuitButtonMac=Don’t Quit
dontGoOfflineButton=Stay Online
dontLeavePrivateBrowsingButton2=Stay in Private Browsing

# LOCALIZATION NOTE (infiniteRate):
# If download speed is a JavaScript Infinity value, this phrase is used
infiniteRate=Really fast

# LOCALIZATION NOTE (statusFormat3): — is the "em dash" (long dash)
# %1$S transfer progress; %2$S rate number; %3$S rate unit; %4$S time left
# example: 4 minutes left — 1.1 of 11.1 GB (2.2 MB/sec)
statusFormat3=%4$S — %1$S (%2$S %3$S/sec)

# LOCALIZATION NOTE (statusFormatInfiniteRate): — is the "em dash" (long dash)
# %1$S transfer progress; %2$S substitute phrase for Infinity speed; %3$S time left
# example: 4 minutes left — 1.1 of 11.1 GB (Really fast)
statusFormatInfiniteRate=%3$S — %1$S (%2$S)

# LOCALIZATION NOTE (statusFormatNoRate): — is the "em dash" (long dash)
# %1$S transfer progress; %2$S time left
# example: 4 minutes left — 1.1 of 11.1 GB
statusFormatNoRate=%2$S — %1$S

bytes=bytes
kilobyte=KB
megabyte=MB
gigabyte=GB

# LOCALIZATION NOTE (transferSameUnits2):
# %1$S progress number; %2$S total number; %3$S total unit
# example: 1.1 of 333 MB
transferSameUnits2=%1$S of %2$S %3$S
# LOCALIZATION NOTE (transferDiffUnits2):
# %1$S progress number; %2$S progress unit; %3$S total number; %4$S total unit
# example: 11.1 MB of 3.3 GB
transferDiffUnits2=%1$S %2$S of %3$S %4$S
# LOCALIZATION NOTE (transferNoTotal2):
# %1$S progress number; %2$S unit
# example: 111 KB
transferNoTotal2=%1$S %2$S

# LOCALIZATION NOTE (timePair3): %1$S time number; %2$S time unit
# example: 1m; 11h
timePair3=%1$S%2$S
# LOCALIZATION NOTE (timeLeftSingle3): %1$S time left
# example: 1m left; 11h left
timeLeftSingle3=%1$S left
# LOCALIZATION NOTE (timeLeftDouble3): %1$S time left; %2$S time left sub units
# example: 11h 2m left; 1d 22h left
timeLeftDouble3=%1$S %2$S left
timeFewSeconds2=A few seconds left
timeUnknown2=Unknown time left

# LOCALIZATION NOTE (doneSize): #1 size number; #2 size unit
doneSize=#1 #2
# LOCALIZATION NOTE (doneScheme): #1 URI scheme like data: jar: about:
doneScheme2=%1$S resource
# LOCALIZATION NOTE (doneFileScheme): Special case of doneScheme for file:
# This is used as an eTLD replacement for local files, so make it lower case
doneFileScheme=local file

# LOCALIZATION NOTE (yesterday): Displayed time for files finished yesterday
yesterday=Yesterday
# LOCALIZATION NOTE (monthDate): #1 month name; #2 date number; e.g., January 22
monthDate2=%1$S %2$S

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

# Desktop folder name for downloaded files
downloadsFolder=Downloads
PK
!<u>chrome/en-US/locale/en-US/mozapps/downloads/settingsChange.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  settingsChangePreferences.label  "Settings can be changed in &brandShortName;'s Preferences.">
<!ENTITY  settingsChangeOptions.label      "Settings can be changed in &brandShortName;'s Options.">
PK
!</`úWWBchrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  intro2.label                "You have chosen to open:">
<!ENTITY  from.label                  "from:">
<!ENTITY  actionQuestion.label        "What should &brandShortName; do with this file?">

<!ENTITY  openWith.label              "Open with">
<!ENTITY  openWith.accesskey          "o">
<!ENTITY  other.label                 "Other…">

<!ENTITY  saveFile.label              "Save File">
<!ENTITY  saveFile.accesskey          "s">

<!ENTITY  rememberChoice.label        "Do this automatically for files like this from now on.">
<!ENTITY  rememberChoice.accesskey    "a">

<!ENTITY  whichIs.label              "which is:">

<!ENTITY  chooseHandlerMac.label      "Choose…">
<!ENTITY  chooseHandlerMac.accesskey  "C">
<!ENTITY  chooseHandler.label         "Browse…">
<!ENTITY  chooseHandler.accesskey     "B">

<!ENTITY  unknownPromptText.label     "Would you like to save this file?">
PK
!<?k	Ichrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.properties# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

title=Opening %S
saveDialogTitle=Enter name of file to save to…
defaultApp=%S (default)
chooseAppFilePickerTitle=Choose Helper Application
badApp=The application you chose (“%S”) could not be found.  Check the file name or choose another application.
badApp.title=Application not found
badPermissions=The file could not be saved because you do not have the proper permissions.  Choose another save directory.
badPermissions.title=Invalid Save Permissions
selectDownloadDir=Select Download Folder
unknownAccept.label=Save File
unknownCancel.label=Cancel
fileType=%S file
# LOCALIZATION NOTE (orderedFileSizeWithType): first %S is type, second %S is size, and third %S is unit  
orderedFileSizeWithType=%1$S (%2$S %3$S)
PK
!<c6chrome/en-US/locale/en-US/mozapps/extensions/about.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  creator.label               "Created By:">
<!ENTITY  developers.label            "Developers:">
<!ENTITY  translators.label           "Translators:">
<!ENTITY  contributors.label          "Contributors:">
<!ENTITY  homepage.label              "Visit Home Page">
PK
!<9Z!!:chrome/en-US/locale/en-US/mozapps/extensions/blocklist.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY blocklist.title             "Add-ons may be causing problems">
<!ENTITY blocklist.style             "width: 45em; height: 30em">
<!ENTITY blocklist.summary           "&brandShortName; has determined that the following add-ons are known to cause stability or security problems:">
<!ENTITY blocklist.softblocked       "For your protection, it is highly recommended that you restart with these add-ons disabled.">
<!ENTITY blocklist.hardblocked       "These add-ons have a high risk of causing stability or security problems and have been blocked, but a restart is required to disable them completely.">
<!ENTITY blocklist.softandhard       "The add-ons that have a high risk of causing stability or security problems have been blocked. The others are lower risk, but it is highly recommended that you restart with them disabled.">
<!ENTITY blocklist.moreinfo          "More information">

<!ENTITY blocklist.accept.label      "Restart &brandShortName;">
<!ENTITY blocklist.accept.accesskey  "R">

<!ENTITY blocklist.blocked.label     "Blocked">
<!ENTITY blocklist.checkbox.label    "Disable">
PK
!<2LD>D>;chrome/en-US/locale/en-US/mozapps/extensions/extensions.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY addons.windowTitle                   "Add-ons Manager">

<!ENTITY search.placeholder                   "Search all add-ons">
<!ENTITY search.buttonlabel                   "Search">
<!-- LOCALIZATION NOTE (search.commandKey):
     The search command key should match findOnCmd.commandkey from browser.dtd -->
<!ENTITY search.commandkey                    "f">

<!ENTITY loading.label                        "Loading…">
<!ENTITY listEmpty.installed.label            "You don’t have any add-ons of this type installed">
<!ENTITY listEmpty.availableUpdates.label     "No updates found">
<!ENTITY listEmpty.recentUpdates.label        "You haven’t recently updated any add-ons">
<!ENTITY listEmpty.findUpdates.label          "Check For Updates">
<!ENTITY listEmpty.search.label               "Could not find any matching add-ons">
<!ENTITY listEmpty.button.label               "Learn more about add-ons">
<!ENTITY installAddonFromFile.label           "Install Add-on From File…">
<!ENTITY installAddonFromFile.accesskey       "I">
<!ENTITY toolsMenu.tooltip                    "Tools for all add-ons">

<!ENTITY getThemes.description                "Looking to personalize your browser?">
<!ENTITY getThemes.learnMore                  "Choose from thousands of themes.">

<!ENTITY cmd.back.tooltip                     "Go back one page">
<!ENTITY cmd.forward.tooltip                  "Go forward one page">

<!ENTITY showUnsignedExtensions.button.label  "Some extensions could not be verified">
<!ENTITY showAllExtensions.button.label       "Show all extensions">
<!ENTITY debugAddons.label                    "Debug Add-ons">
<!ENTITY debugAddons.accesskey                "B">

<!-- global warnings -->
<!ENTITY warning.safemode.label                    "All add-ons have been disabled by safe mode.">
<!ENTITY warning.checkcompatibility.label          "Add-on compatibility checking is disabled. You may have incompatible add-ons.">
<!ENTITY warning.checkcompatibility.enable.label   "Enable">
<!ENTITY warning.checkcompatibility.enable.tooltip "Enable add-on compatibility checking">
<!ENTITY warning.updatesecurity.label              "Add-on update security checking is disabled. You may be compromised by updates.">
<!ENTITY warning.updatesecurity.enable.label       "Enable">
<!ENTITY warning.updatesecurity.enable.tooltip     "Enable add-on update security checking">

<!-- categories / views -->
<!ENTITY view.search.label                    "Search">
<!ENTITY view.discover.label                  "Get Add-ons">
<!ENTITY view.recentUpdates.label             "Recent Updates">
<!ENTITY view.availableUpdates.label          "Available Updates">

<!-- addon updates -->
<!ENTITY updates.checkForUpdates.label        "Check for Updates">
<!ENTITY updates.checkForUpdates.accesskey    "C">
<!ENTITY updates.viewUpdates.label            "View Recent Updates">
<!ENTITY updates.viewUpdates.accesskey        "V">
<!-- LOCALIZATION NOTE (updates.updateAddonsAutomatically.label): This menu item
     is a checkbox that toggles the default global behavior for add-on update
     checking. -->
<!ENTITY updates.updateAddonsAutomatically.label     "Update Add-ons Automatically">
<!ENTITY updates.updateAddonsAutomatically.accesskey "A">
<!-- LOCALIZATION NOTE (updates.resetUpdatesToAutomatic.label, updates.resetUpdatesToManual.label):
     Specific addons can have custom update checking behaviors ("Manually",
     "Automatically", "Use default global behavior"). These menu items reset the
     update checking behavior for all add-ons to the default global behavior
     (which itself is either "Automatically" or "Manually", controlled by the
     updates.updateAddonsAutomatically.label menu item). -->
<!ENTITY updates.resetUpdatesToAutomatic.label       "Reset All Add-ons to Update Automatically">
<!ENTITY updates.resetUpdatesToAutomatic.accesskey   "R">
<!ENTITY updates.resetUpdatesToManual.label          "Reset All Add-ons to Update Manually">
<!ENTITY updates.resetUpdatesToManual.accesskey      "R">
<!ENTITY updates.updating.label               "Updating add-ons">
<!ENTITY updates.installed.label              "Your add-ons have been updated.">
<!ENTITY updates.downloaded.label             "Your add-on updates have been downloaded.">
<!ENTITY updates.restart.label                "Restart now to complete installation">
<!ENTITY updates.noneFound.label              "No updates found">
<!ENTITY updates.manualUpdatesFound.label     "View Available Updates">
<!ENTITY updates.updateSelected.label         "Install Updates">
<!ENTITY updates.updateSelected.tooltip       "Install available updates in this list">

<!-- addon actions -->
<!ENTITY cmd.showDetails.label                "Show More Information">
<!ENTITY cmd.showDetails.accesskey            "S">
<!ENTITY cmd.findUpdates.label                "Find Updates">
<!ENTITY cmd.findUpdates.accesskey            "F">
<!ENTITY cmd.preferencesWin.label             "Options">
<!ENTITY cmd.preferencesWin.accesskey         "O">
<!ENTITY cmd.preferencesUnix.label            "Preferences">
<!ENTITY cmd.preferencesUnix.accesskey        "P">
<!ENTITY cmd.about.label                      "About">
<!ENTITY cmd.about.accesskey                  "A">

<!ENTITY cmd.enableAddon.label                "Enable">
<!ENTITY cmd.enableAddon.accesskey            "E">
<!ENTITY cmd.disableAddon.label               "Disable">
<!ENTITY cmd.disableAddon.accesskey           "D">
<!ENTITY cmd.enableTheme.label                "Wear Theme">
<!ENTITY cmd.enableTheme.accesskey            "W">
<!ENTITY cmd.disableTheme.label               "Stop Wearing Theme">
<!ENTITY cmd.disableTheme.accesskey           "W">
<!ENTITY cmd.askToActivate.label              "Ask to Activate">
<!ENTITY cmd.askToActivate.tooltip            "Ask to use this add-on each time">
<!ENTITY cmd.alwaysActivate.label             "Always Activate">
<!ENTITY cmd.alwaysActivate.tooltip           "Always use this add-on">
<!ENTITY cmd.neverActivate.label              "Never Activate">
<!ENTITY cmd.neverActivate.tooltip            "Never use this add-on">
<!ENTITY cmd.stateMenu.tooltip                "Change when this add-on runs">
<!ENTITY cmd.installAddon.label               "Install">
<!ENTITY cmd.installAddon.accesskey           "I">
<!ENTITY cmd.uninstallAddon.label             "Remove">
<!ENTITY cmd.uninstallAddon.accesskey         "R">
<!ENTITY cmd.showPreferencesWin.label         "Options">
<!ENTITY cmd.showPreferencesWin.tooltip       "Change this add-on’s options">
<!ENTITY cmd.showPreferencesUnix.label        "Preferences">
<!ENTITY cmd.showPreferencesUnix.tooltip      "Change this add-on’s preferences">
<!ENTITY cmd.contribute.label                 "Contribute">
<!ENTITY cmd.contribute.accesskey             "C">
<!ENTITY cmd.contribute.tooltip               "Contribute to the development of this add-on">

<!ENTITY cmd.showReleaseNotes.label           "Show Release Notes">
<!ENTITY cmd.showReleaseNotes.tooltip         "Show the release notes for this update">
<!ENTITY cmd.hideReleaseNotes.label           "Hide Release Notes">
<!ENTITY cmd.hideReleaseNotes.tooltip         "Hide the release notes for this update">
<!ENTITY cmd.findReplacement.label            "Find a Replacement">

<!-- discovery view -->
<!-- LOCALIZATION NOTE (discover.title,discover.description,discover.footer):
     Displayed in the center of the Get Add-ons view, see bug 601143 for mockups. -->
<!ENTITY discover.title                       "What are Add-ons?">
<!ENTITY discover.description2                "Add-ons are applications that let you personalize &brandShortName; with
  extra functionality or style. Try a time-saving sidebar, a weather notifier, or a themed look to make &brandShortName;
  your own.">
<!ENTITY discover.footer                      "When you’re connected to the internet, this pane will feature
  some of the best and most popular add-ons for you to try out.">

<!-- detail view -->
<!ENTITY detail.version.label                 "Version">
<!ENTITY detail.lastupdated.label             "Last Updated">
<!ENTITY detail.creator.label                 "Developer">
<!ENTITY detail.homepage.label                "Homepage">
<!ENTITY detail.numberOfDownloads.label       "Downloads">

<!ENTITY detail.contributions.description     "The developer of this add-on asks that you help support its continued development by making a small contribution.">

<!ENTITY detail.updateType                    "Automatic Updates">
<!ENTITY detail.updateDefault.label           "Default">
<!ENTITY detail.updateDefault.tooltip         "Automatically install updates only if that’s the default">
<!ENTITY detail.updateAutomatic.label         "On">
<!ENTITY detail.updateAutomatic.tooltip       "Automatically install updates">
<!ENTITY detail.updateManual.label            "Off">
<!ENTITY detail.updateManual.tooltip          "Don’t automatically install updates">
<!ENTITY detail.home                          "Homepage">
<!ENTITY detail.repository                    "Add-on Profile">
<!ENTITY detail.size                          "Size">

<!ENTITY detail.checkForUpdates.label         "Check for Updates">
<!ENTITY detail.checkForUpdates.accesskey     "F">
<!ENTITY detail.checkForUpdates.tooltip       "Check for updates for this add-on">
<!ENTITY detail.showPreferencesWin.label      "Options">
<!ENTITY detail.showPreferencesWin.accesskey  "O">
<!ENTITY detail.showPreferencesWin.tooltip    "Change this add-on’s options">
<!ENTITY detail.showPreferencesUnix.label     "Preferences">
<!ENTITY detail.showPreferencesUnix.accesskey "P">
<!ENTITY detail.showPreferencesUnix.tooltip   "Change this add-on’s preferences">


<!-- ratings -->
<!ENTITY rating2.label                        "Rating">

<!-- download/install progress -->
<!ENTITY progress.pause.tooltip               "Pause">
<!ENTITY progress.cancel.tooltip              "Cancel">


<!-- list sorting -->
<!ENTITY sort.name.label                      "Name">
<!ENTITY sort.name.tooltip                    "Sort by name">
<!ENTITY sort.dateUpdated.label               "Last Updated">
<!ENTITY sort.dateUpdated.tooltip             "Sort by date updated">
<!ENTITY sort.relevance.label                 "Best match">
<!ENTITY sort.relevance.tooltip               "Sort by relevance">
<!ENTITY sort.price.label                     "Price">
<!ENTITY sort.price.tooltip                   "Sort by price">

<!ENTITY search.filter2.label                 "Search:">
<!ENTITY search.filter2.installed.label       "My Add-ons">
<!ENTITY search.filter2.installed.tooltip     "Show installed add-ons">
<!ENTITY search.filter2.available.label       "Available Add-ons">
<!ENTITY search.filter2.available.tooltip     "Show add-ons available to install">

<!ENTITY addon.homepage                       "Homepage">
<!ENTITY addon.details.label                  "More">
<!ENTITY addon.details.tooltip                "Show more details about this add-on">
<!ENTITY addon.unknownDate                    "Unknown">
<!-- LOCALIZATION NOTE (addon.legacy.label): This appears in a badge next
     to the add-on name for extensions that are not webextensions, which
     will stop working in Firefox 57. -->
<!ENTITY addon.legacy.label                   "LEGACY">
<!-- LOCALIZATION NOTE (addon.disabled.postfix): This is used in a normal list
     to signify that an add-on is disabled, in the form
     "<Addon name> <1.0> (disabled)" -->
<!ENTITY addon.disabled.postfix               "(disabled)">
<!-- LOCALIZATION NOTE (addon.update.postfix): This is used in the available
     updates list to signify that an item is an update, in the form
     "<Addon name> <1.1> Update". It is fine to use constructs like brackets if
     necessary -->
<!ENTITY addon.update.postfix                 "Update">
<!ENTITY addon.undoAction.label               "Undo">
<!ENTITY addon.undoAction.tooltip             "Undo this action">
<!ENTITY addon.undoRemove.label               "Undo">
<!ENTITY addon.undoRemove.tooltip             "Keep this add-on installed">
<!ENTITY addon.restartNow.label               "Restart now">
<!ENTITY addon.install.label                  "Install">
<!ENTITY addon.install.tooltip                "Install this add-on">
<!ENTITY addon.updateNow.label                "Update Now">
<!ENTITY addon.updateNow.tooltip              "Install the update for this add-on">
<!ENTITY addon.includeUpdate.label            "Include in Update">
<!ENTITY addon.updateAvailable.label          "An update is available">
<!ENTITY addon.checkingForUpdates.label       "Checking for updates…">
<!ENTITY addon.releaseNotes.label             "Release Notes:">
<!ENTITY addon.loadingReleaseNotes.label      "Loading…">
<!ENTITY addon.errorLoadingReleaseNotes.label "Sorry, but there was an error loading the release notes.">

<!ENTITY addon.createdBy.label                "By ">

<!ENTITY eula.title                           "End-User License Agreement">
<!ENTITY eula.width                           "560px">
<!ENTITY eula.height                          "400px">
<!ENTITY eula.accept                          "Accept and Install…">

<!ENTITY settings.path.button.label           "Browse…">

<!-- LOCALIZATION NOTE (experiment.info.label): The strings related to
     experiments are present on the "Experiments" tab of the add-ons manager.
     This tab won't be displayed unless an Experiment add-on is installed.
     Install https://people.mozilla.org/~gszorc/dummy-experiment-addon.xpi
     to cause this tab to appear. -->
<!ENTITY experiment.info.label "What’s this? Telemetry may install and run experiments from time to time.">
<!ENTITY experiment.info.learnmore "Learn More">
<!ENTITY experiment.info.learnmore.accesskey "L">
<!ENTITY experiment.info.changetelemetry "Telemetry Settings">
<!ENTITY experiment.info.changetelemetry.accesskey "T">

<!ENTITY setting.learnmore "Learn More…">

<!ENTITY disabledUnsigned.heading "Some add-ons have been disabled">
<!-- LOCALIZATION NOTE (disabledUnsigned.description.start, disabledUnsigned.description.findAddonsLink, disabledUnsigned.description.end):
     These entities form a sentence, with
     disabledUnsigned.description.findAddonsLink being a link to an external site. -->
<!ENTITY disabledUnsigned.description.start "The following add-ons have not been verified for use in &brandShortName;. You can ">
<!ENTITY disabledUnsigned.description.findAddonsLink "find replacements">
<!ENTITY disabledUnsigned.description.end " or ask the developer to get them verified.">
<!ENTITY disabledUnsigned.learnMore "Learn more about our efforts to help keep you safe online.">
<!-- LOCALIZATION NOTE (disabledUnsigned.devInfo.start, disabledUnsigned.devInfo.linkToManual, disabledUnsigned.devInfo.end):
     These entities form a sentence, with disabledUnsigned.devInfo.linkToManual
     being a link to an external site. -->
<!ENTITY disabledUnsigned.devInfo.start "Developers interested in getting their add-ons verified can continue by reading our ">
<!ENTITY disabledUnsigned.devInfo.linkToManual "manual">
<!ENTITY disabledUnsigned.devInfo.end ".">

<!ENTITY pluginDeprecation.description "Missing something? Some plugins are no longer supported by &brandShortName;.">
<!ENTITY pluginDeprecation.learnMore "Learn More.">

<!ENTITY legacyWarning.description "Missing something? Some extensions are no longer supported by &brandShortName;.">
<!ENTITY legacyWarning.showLegacy "Show legacy extensions">
<!ENTITY legacyExtensions.title "Legacy Extensions">
<!ENTITY legacyExtensions.description "These extensions do not meet current &brandShortName; standards so they have been deactivated.">
<!ENTITY legacyExtensions.learnMore "Learn about the changes to add-ons">
PK
!<ز
-
-Bchrome/en-US/locale/en-US/mozapps/extensions/extensions.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#LOCALIZATION NOTE (aboutWindowTitle) %S is the addon name
aboutWindowTitle=About %S
aboutWindowCloseButton=Close
#LOCALIZATION NOTE (aboutWindowVersionString) %S is the addon version
aboutWindowVersionString=version %S
#LOCALIZATION NOTE (aboutAddon) %S is the addon name
aboutAddon=About %S

#LOCALIZATION NOTE (uninstallNotice) %S is the add-on name
uninstallNotice=%S has been removed.

#LOCALIZATION NOTE (numReviews): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of reviews
numReviews=#1 review;#1 reviews

#LOCALIZATION NOTE (dateUpdated) %S is the date the addon was last updated
dateUpdated=Updated %S

#LOCALIZATION NOTE (notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
notification.incompatible=%1$S is incompatible with %2$S %3$S.
#LOCALIZATION NOTE (notification.unsigned, notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name
notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled.
notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution.
notification.unsigned.link=More Information
#LOCALIZATION NOTE (notification.nonMpcDisabled) %1$S is the add-on name
notification.nonMpcDisabled=%1$S has been disabled since it is not multiprocess compatible.
notification.nonMpcDisabled.link=More Information
#LOCALIZATION NOTE (notification.blocked) %1$S is the add-on name
notification.blocked=%1$S has been disabled due to security or stability issues.
notification.blocked.link=More Information
#LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name
notification.softblocked=%1$S is known to cause security or stability issues.
notification.softblocked.link=More Information
#LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name
notification.outdated=An important update is available for %1$S.
notification.outdated.link=Update Now
#LOCALIZATION NOTE (notification.vulnerableUpdatable) %1$S is the add-on name
notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
notification.vulnerableUpdatable.link=Update Now
#LOCALIZATION NOTE (notification.vulnerableNoUpdate) %1$S is the add-on name
notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
notification.vulnerableNoUpdate.link=More Information
#LOCALIZATION NOTE (notification.enable) %1$S is the add-on name, %2$S is brand name
notification.enable=%1$S will be enabled after you restart %2$S.
#LOCALIZATION NOTE (notification.disable) %1$S is the add-on name, %2$S is brand name
notification.disable=%1$S will be disabled after you restart %2$S.
#LOCALIZATION NOTE (notification.install) %1$S is the add-on name, %2$S is brand name
notification.install=%1$S will be installed after you restart %2$S.
#LOCALIZATION NOTE (notification.uninstall) %1$S is the add-on name, %2$S is brand name
notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (notification.upgrade) %1$S is the add-on name, %2$S is brand name
notification.upgrade=%1$S will be updated after you restart %2$S.
#LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name.
notification.downloadError=There was an error downloading %1$S.
notification.downloadError.retry=Try again
notification.downloadError.retry.tooltip=Try downloading this add-on again
#LOCALIZATION NOTE (notification.installError) %1$S is the add-on name.
notification.installError=There was an error installing %1$S.
notification.installError.retry=Try again
notification.installError.retry.tooltip=Try downloading and installing this add-on again
#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name.
notification.gmpPending=%1$S will be installed shortly.

#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
contributionAmount2=Suggested Contribution: %S

installDownloading=Downloading
installDownloaded=Downloaded
installDownloadFailed=Error downloading
installVerifying=Verifying
installInstalling=Installing
installEnablePending=Restart to enable
installDisablePending=Restart to disable
installFailed=Error installing
installCancelled=Install cancelled

#LOCALIZATION NOTE (details.notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
details.notification.incompatible=%1$S is incompatible with %2$S %3$S.
#LOCALIZATION NOTE (details.notification.unsigned, details.notification.unsignedAndDisabled) %1$S is the add-on name, %2$S is brand name
details.notification.unsignedAndDisabled=%1$S could not be verified for use in %2$S and has been disabled.
details.notification.unsigned=%1$S could not be verified for use in %2$S. Proceed with caution.
details.notification.unsigned.link=More Information
#LOCALIZATION NOTE (details.notification.nonMpcDisabled) %1$S is the add-on name
details.notification.nonMpcDisabled=%1$S has been disabled since it is not multiprocess compatible.
details.notification.nonMpcDisabled.link=More Information
#LOCALIZATION NOTE (details.notification.blocked) %1$S is the add-on name
details.notification.blocked=%1$S has been disabled due to security or stability issues.
details.notification.blocked.link=More Information
#LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name
details.notification.softblocked=%1$S is known to cause security or stability issues.
details.notification.softblocked.link=More Information
#LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name
details.notification.outdated=An important update is available for %1$S.
details.notification.outdated.link=Update Now
#LOCALIZATION NOTE (details.notification.vulnerableUpdatable) %1$S is the add-on name
details.notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
details.notification.vulnerableUpdatable.link=Update Now
#LOCALIZATION NOTE (details.notification.vulnerableNoUpdate) %1$S is the add-on name
details.notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
details.notification.vulnerableNoUpdate.link=More Information
#LOCALIZATION NOTE (details.notification.enable) %1$S is the add-on name, %2$S is brand name
details.notification.enable=%1$S will be enabled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name
details.notification.disable=%1$S will be disabled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name
details.notification.install=%1$S will be installed after you restart %2$S.
#LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name
details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
details.notification.upgrade=%1$S will be updated after you restart %2$S.
#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name
details.notification.gmpPending=%1$S will be installed shortly.

# LOCALIZATION NOTE (details.experiment.time.daysRemaining):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days from now that the experiment will remain active (detail view).
details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining
#LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view).
details.experiment.time.endsToday=Less than a day remaining
# LOCALIZATION NOTE (details.experiment.time.daysPassed):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days since the experiment ran (detail view).
details.experiment.time.daysPassed=#1 day ago;#1 days ago
#LOCALIZATION NOTE (details.experiment.time.endedToday) The experiment ended less than a day ago (detail view).
details.experiment.time.endedToday=Less than a day ago
#LOCALIZATION NOTE (details.experiment.state.active) This experiment is active (detail view).
details.experiment.state.active=Active
#LOCALIZATION NOTE (details.experiment.state.complete) This experiment is complete (it was previously active) (detail view).
details.experiment.state.complete=Complete

# LOCALIZATION NOTE (experiment.time.daysRemaining):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days from now that the experiment will remain active (list view item).
experiment.time.daysRemaining=#1 day remaining;#1 days remaining
#LOCALIZATION NOTE (experiment.time.endsToday) The experiment will end in less than a day (list view item).
experiment.time.endsToday=Less than a day remaining
# LOCALIZATION NOTE (experiment.time.daysPassed):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days since the experiment ran (list view item).
experiment.time.daysPassed=#1 day ago;#1 days ago
#LOCALIZATION NOTE (experiment.time.endedToday) The experiment ended less than a day ago (list view item).
experiment.time.endedToday=Less than a day ago
#LOCALIZATION NOTE (experiment.state.active) This experiment is active (list view item).
experiment.state.active=Active
#LOCALIZATION NOTE (experiment.state.complete) This experiment is complete (it was previously active) (list view item).
experiment.state.complete=Complete

installFromFile.dialogTitle=Select add-on to install
installFromFile.filterName=Add-ons

uninstallAddonTooltip=Uninstall this add-on
uninstallAddonRestartRequiredTooltip=Uninstall this add-on (restart required)
enableAddonTooltip=Enable this add-on
enableAddonRestartRequiredTooltip=Enable this add-on (restart required)
disableAddonTooltip=Disable this add-on
disableAddonRestartRequiredTooltip=Disable this add-on (restart required)

#LOCALIZATION NOTE (showAllSearchResults): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the total number of search results
showAllSearchResults=See one result;See all #1 results

#LOCALIZATION NOTE (addon.purchase.label) displayed on a button in the list
# view, %S is the price of the add-on including currency symbol
addon.purchase.label=Purchase for %S…
addon.purchase.tooltip=Visit the add-ons gallery to purchase this add-on
#LOCALIZATION NOTE (cmd.purchaseAddon.label) displayed on a button in the detail
# view, %S is the price of the add-on including currency symbol
cmd.purchaseAddon.label=Purchase for %S…
cmd.purchaseAddon.accesskey=u

#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA
eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed:

type.extension.name=Extensions
type.theme.name=Appearance
type.locale.name=Languages
type.plugin.name=Plugins
type.dictionary.name=Dictionaries
type.service.name=Services
type.experiment.name=Experiments
type.legacy.name=Legacy Extensions
type.unsupported.name=Unsupported
PK
!<Gzz9chrome/en-US/locale/en-US/mozapps/extensions/newaddon.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY title           "Install Add-on">
<!ENTITY intro           "Another program on your computer would like to modify
                          &brandShortName; with the following add-on:">
<!ENTITY warning         "Install add-ons only from authors whom you trust.">
<!ENTITY allow           "Allow this installation">
<!ENTITY later           "You can always change your mind at any time by going
                          to the Add-ons Manager.">
<!ENTITY continue        "Continue">
<!ENTITY restartMessage  "You must restart &brandShortName; to finish installing this add-on.">
<!ENTITY restartButton   "Restart &brandShortName;">
<!ENTITY cancelButton    "Cancel">
PK
!<VE@chrome/en-US/locale/en-US/mozapps/extensions/newaddon.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#LOCALIZATION NOTE (name) %1$S is the add-on name, %2$S is the add-on version
name=%1$S %2$S
#LOCALIZATION NOTE (author) %S is the author of the add-on
author=By %S
#LOCALIZATION NOTE (location) %S is the path the add-on is installed in
location=Location: %S
PK
!<9/zz7chrome/en-US/locale/en-US/mozapps/extensions/update.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY  updateWizard.title              "&brandShortName; Update">

<!ENTITY  offline.title                   "&brandShortName; is working offline">
<!ENTITY  offline.description             "&brandShortName; needs to go online in order to see if updates
                                           are available for your add-ons to make them compatible with this
                                           version.">
<!ENTITY  offline.toggleOffline.label     "Go online now.">
<!ENTITY  offline.toggleOffline.accesskey "G">

<!ENTITY  mismatch.win.title              "Incompatible Add-ons">
<!ENTITY  mismatch.top.label              "The following add-ons are not compatible with this version of
                                           &brandShortName; and have been disabled:">
<!ENTITY  mismatch.bottom.label           "&brandShortName; can check if there are compatible versions
                                           of these add-ons available.">

<!ENTITY  checking.wizard.title           "Checking for Compatible Add-ons">
<!ENTITY  checking.top.label              "Checking your incompatible add-ons for updates…">
<!ENTITY  checking.status                 "This may take a few minutes…">

<!ENTITY  found.wizard.title               "Found Compatible Add-ons">
<!ENTITY  found.top.label                 "Select the add-ons you would like to install:">
<!ENTITY  found.disabledXPinstall.label   "These updates can’t be installed because software installation is currently
                                           disabled. You can change this setting below.">
<!ENTITY  found.enableXPInstall.label     "Allow websites to install software">
<!ENTITY  found.enableXPInstall.accesskey "A">

<!ENTITY  installing.wizard.title         "Installing Compatible Add-ons">
<!ENTITY  installing.top.label            "Downloading and installing updates to your add-ons…">

<!ENTITY  noupdates.wizard.title          "No Compatible Add-ons Found">
<!ENTITY  noupdates.intro.desc            "&brandShortName; was unable to find updates to your
                                           incompatible add-ons.">
<!ENTITY  noupdates.error.desc            "Some problems were encountered when trying to find updates.">
<!ENTITY  noupdates.checkEnabled.desc     "&brandShortName; will check periodically and inform you
                                           when compatible updates for these add-ons are found.">

<!ENTITY  finished.wizard.title           "Compatible Add-ons Installed">
<!ENTITY  finished.top.label              "&brandShortName; has installed the updates to your add-ons.">
<!ENTITY  finished.checkDisabled.desc     "&brandShortName; can check periodically and inform you
                                           when updates for add-ons are found.">
<!ENTITY  finished.checkEnabled.desc      "&brandShortName; will check periodically and inform you
                                           when updates for add-ons are found.">

<!ENTITY  adminDisabled.wizard.title      "Unable to Check for Updates">
<!ENTITY  adminDisabled.warning.label     "It is not possible to check for updates to incompatible add-ons
                                           because software installation for &brandShortName; has been disabled.
                                           Please contact your System Administrator for assistance.">

<!ENTITY  versioninfo.wizard.title        "Checking Compatibility of Add-ons">
<!ENTITY  versioninfo.top.label           "Checking your add-ons for compatibility with this
                                           version of &brandShortName;.">
<!ENTITY  versioninfo.waiting             "This may take a few minutes…">

<!ENTITY  installerrors.wizard.title      "Problems Installing Updates">
<!ENTITY  installerrors.intro.label       "&brandShortName; encountered problems when updating
                                           some of your add-ons.">

<!-- general strings used by several of the finish pages -->
<!ENTITY  clickFinish.label               "Click Finish to continue starting &brandShortName;.">
<!ENTITY  clickFinish.labelMac            "Click Done to continue starting &brandShortName;.">
<!ENTITY  enableChecking.label            "Allow &brandShortName; to check for updates.">
PK
!<0b>chrome/en-US/locale/en-US/mozapps/extensions/update.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

mismatchCheckNow=Check Now
mismatchCheckNowAccesskey=C
mismatchDontCheck=Don’t Check
mismatchDontCheckAccesskey=D
installButtonText=Install Now
installButtonTextAccesskey=I
nextButtonText=Next >
nextButtonTextAccesskey=N
cancelButtonText=Cancel
cancelButtonTextAccesskey=C
statusPrefix=Finished checking %S
downloadingPrefix=Downloading: %S
installingPrefix=Installing: %S
closeButton=Close
installErrors=%S was unable to install updates for the following add-ons:
checkingErrors=%S was unable to check for updates for the following add-ons:
installErrorItemFormat=%S (%S)
PK
!<Hn~7chrome/en-US/locale/en-US/mozapps/handling/handling.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY window.emWidth "26em">
<!ENTITY window.emHeight "26em">
<!ENTITY ChooseOtherApp.description "Choose other Application">
<!ENTITY ChooseApp.label "Choose…">
<!ENTITY ChooseApp.accessKey "C">
<!ENTITY accept "Open link">
PK
!<p>chrome/en-US/locale/en-US/mozapps/handling/handling.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

protocol.title=Launch Application
protocol.description=This link needs to be opened with an application.
protocol.choices.label=Send to:
protocol.checkbox.label=Remember my choice for %S links.
protocol.checkbox.accesskey=R
protocol.checkbox.extra=This can be changed in %S’s preferences.

choose.application.title=Another Application…
PK
!<
U:chrome/en-US/locale/en-US/mozapps/preferences/changemp.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY setPassword.title                 "Change Master Password">
<!ENTITY setPassword.tokenName.label       "Security Device">
<!ENTITY setPassword.oldPassword.label     "Current password:">
<!ENTITY setPassword.newPassword.label     "Enter new password:">
<!ENTITY setPassword.reenterPassword.label "Re-enter password:">
<!ENTITY setPassword.meter.label           "Password quality meter">
<!ENTITY setPassword.meter.loading         "Loading">
<!ENTITY masterPasswordDescription.label   "A Master Password is used to protect sensitive information like site passwords.  If you create a Master Password you will be asked to enter it once per session when &brandShortName; retrieves saved information protected by the password.">
<!ENTITY masterPasswordWarning.label       "Please make sure you remember the Master Password you have set.  If you forget your Master Password, you will be unable to access any of the information protected by it.">
PK
!<3yyDchrome/en-US/locale/en-US/mozapps/preferences/preferences.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#### Master Password

password_not_set=(not set)
failed_pw_change=Unable to change Master Password.
incorrect_pw=You did not enter the correct current Master Password. Please try again.
pw_change_ok=Master Password successfully changed.
pw_erased_ok=You have deleted your Master Password. 
pw_not_wanted=Warning! You have decided not to use a Master Password.
pw_empty_warning=Your stored web and email passwords, form data, and private keys will not be protected.
pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
pw_change_success_title=Password Change Succeeded
pw_change_failed_title=Password Change Failed
pw_remove_button=Remove
PK
!<l:chrome/en-US/locale/en-US/mozapps/preferences/removemp.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY removePassword.title              "Remove Master Password">
<!ENTITY removeInfo.label                  "You must enter your current password to proceed:">
<!ENTITY removeWarning1.label              "Your Master Password is used to protect sensitive information like site passwords.">
<!ENTITY removeWarning2.label              "If you remove your Master Password your information will not be protected if your computer is compromised.">
<!ENTITY setPassword.oldPassword.label     "Current password:">

PK
!<,0*77Achrome/en-US/locale/en-US/mozapps/profile/createProfileWizard.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY newprofile.title             "Create Profile Wizard">
<!ENTITY window.size                  "width: 45em; height: 32em;">

<!-- First wizard page -->
<!ENTITY profileCreationExplanation_1.text  "&brandShortName; stores information about your settings and preferences in your personal profile.">
<!ENTITY profileCreationExplanation_2.text  "If you are sharing this copy of &brandShortName; with other users, you can use profiles to keep each user’s information separate. To do this, each user should create his or her own profile.">
<!ENTITY profileCreationExplanation_3.text  "If you are the only person using this copy of &brandShortName;, you must have at least one profile. If you would like, you can create multiple profiles for yourself to store different sets of settings and preferences. For example, you may want to have separate profiles for business and personal use.">
<!ENTITY profileCreationExplanation_4.text  "To begin creating your profile, click Next.">
<!ENTITY profileCreationExplanation_4Mac.text  "To begin creating your profile, click Continue.">
<!ENTITY profileCreationExplanation_4Gnome.text  "To begin creating your profile, click Next.">

<!-- Second wizard page -->
<!ENTITY profileCreationIntro.text      "If you create several profiles you can tell them apart by the profile names. You may use the name provided here or use one of your own.">
<!ENTITY profilePrompt.label            "Enter new profile name:">
<!ENTITY profilePrompt.accesskey        "E">
<!ENTITY profileDirectoryExplanation.text   "Your user settings, preferences and other user-related data will be stored in:">
<!ENTITY profileDefaultName             "Default User">
<!ENTITY button.choosefolder.label      "Choose Folder…">
<!ENTITY button.choosefolder.accesskey  "C">
<!ENTITY button.usedefault.label        "Use Default Folder">
<!ENTITY button.usedefault.accesskey    "U">
PK
!<*bb>chrome/en-US/locale/en-US/mozapps/profile/profileSelection.dtd<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
<!--

 This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY            windowtitle.label      "&brandShortName; - Choose User Profile">

<!ENTITY            profilename.label      "Profile Name:">

<!ENTITY            start.label            "Start &brandShortName;">
<!ENTITY            exit.label             "Exit">

<!ENTITY            availprofiles.label    "Available Profiles">

<!ENTITY            newButton.label        "Create Profile…">
<!ENTITY            newButton.accesskey    "C">
<!ENTITY            renameButton.label     "Rename Profile…">
<!ENTITY            renameButton.accesskey "R">
<!ENTITY            deleteButton.label     "Delete Profile…">
<!ENTITY            deleteButton.accesskey "D">

<!-- manager entities -->
<!ENTITY            pmDescription.label    "&brandShortName; stores information about your settings, preferences, and other user items in your user profile.">

<!ENTITY            offlineState.label    "Work offline">
<!ENTITY            offlineState.accesskey "o">

<!ENTITY            useSelected.label       "Use the selected profile without asking at startup">
<!ENTITY            useSelected.accesskey   "s">
PK
!<2lkEchrome/en-US/locale/en-US/mozapps/profile/profileSelection.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# LOCALIZATION NOTE: These strings are used for startup/profile problems and the profile manager.

# Application not responding
# LOCALIZATION NOTE (restartTitle, restartMessageNoUnlocker, restartMessageUnlocker, restartMessageNoUnlockerMac, restartMessageUnlockerMac): Messages displayed when the application is running but is not responding to commands. %S is the application name.
restartTitle=Close %S
restartMessageNoUnlocker=%S is already running, but is not responding. To open a new window, you must first close the existing %S process, or restart your system.
restartMessageUnlocker=%S is already running, but is not responding. The old %S process must be closed to open a new window.
restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.

# Profile manager
# LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder.
profileTooltip=Profile: ‘%S’ - Path: ‘%S’

pleaseSelectTitle=Select Profile
pleaseSelect=Please select a profile to begin %S, or create a new profile.

profileLockedTitle=Profile In Use
profileLocked2=%S cannot use the profile “%S” because it is in use.\n\nTo continue, close the running instance of %S or choose a different profile.

renameProfileTitle=Rename Profile
renameProfilePrompt=Rename the profile “%S” to:

profileNameInvalidTitle=Invalid profile name
profileNameInvalid=The profile name “%S” is not allowed.

chooseFolder=Choose Profile Folder
profileNameEmpty=An empty profile name is not allowed.
invalidChar=The character “%S” is not allowed in profile names. Please choose a different name.

deleteTitle=Delete Profile
deleteProfileConfirm=Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\nYou may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “%S” and cannot be undone.\nWould you like to delete the profile data files?
deleteFiles=Delete Files
dontDeleteFiles=Don’t Delete Files

profileCreationFailed=Profile couldn’t be created. Probably the chosen folder isn’t writable.
profileCreationFailedTitle=Profile Creation failed
profileExists=A profile with this name already exists. Please choose another name.
profileExistsTitle=Profile Exists
profileFinishText=Click Finish to create this new profile.
profileFinishTextMac=Click Done to create this new profile.
profileMissing=Your %S profile cannot be loaded. It may be missing or inaccessible.
profileMissingTitle=Profile Missing

# Profile reset
# LOCALIZATION NOTE (resetBackupDirectory): Directory name for the profile directory backup created during reset. This directory is placed in a location users will see it (ie. their desktop). %S is the application name.
resetBackupDirectory=Old %S Data
PK
!<nL4chrome/en-US/locale/en-US/mozapps/update/history.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!ENTITY  history.title             "Update History">
<!ENTITY  history.intro             "The following updates have been installed:">
<!ENTITY  closebutton.label         "Close">
                                     
<!ENTITY  noupdates.label           "No updates installed yet">

<!ENTITY  name.header               "Update Name">
<!ENTITY  date.header               "Install Date">
<!ENTITY  type.header               "Type">
<!ENTITY  state.header              "State">

PK
!<Ïuu4chrome/en-US/locale/en-US/mozapps/update/updates.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY  updateWizard.title              "Software Update">

<!ENTITY  checking.title                  "Checking for Updates">
<!ENTITY  updateCheck.label               "Looking for newer versions of &brandShortName;…">

<!ENTITY  noupdatesfound.title            "No Updates Found">
<!ENTITY  noupdatesautoenabled.intro      "There are no updates available. &brandShortName; will check
                                           periodically for updates.">
<!ENTITY  noupdatesautodisabled.intro     "There are no updates available. Please check again later or enable
                                           &brandShortName;'s automatic update checking.">

<!ENTITY  manualUpdate.title              "Unable to Update">
<!ENTITY  manualUpdate.desc               "A recommended security and stability update is available, but you do
                                           not have the system permissions required to install it. Please contact your
                                           system administrator, or try again from an account that has permission to
                                           install software on this computer.">
<!ENTITY  manualUpdate.space.desc         "A recommended security and stability update is available, but you do
                                           not have enough space to install it.">
<!ENTITY  manualUpdateGetMsg.label        "You can always get the latest version of &brandShortName; at:">

<!ENTITY  unsupported.title               "System Unsupported">
<!ENTITY  unsupported.label               "Your &brandShortName; is out of date, but the latest version is not
                                           supported on your system. Please upgrade your system, then try again.
                                           You will not see this notice again, but you can">
<!ENTITY  unsupportedLink.label           "learn more.">

<!ENTITY  clickHere.label                 "View more information about this update">

<!ENTITY  evangelism.desc                 "It is strongly recommended that you apply this 
                                           update for &brandShortName; as soon as possible.">

<!ENTITY  downloadPage.title              "Downloading &brandShortName;">
<!ENTITY  downloading.intro               "Downloading the update…">
<!ENTITY  connecting.label                "Connecting to the update server…">
<!ENTITY  verificationFailedText.label    "&brandShortName; was unable to verify the integrity of the 
                                           incremental update it downloaded, so it is now downloading
                                           the complete update package.">

<!ENTITY  viewDetails.tooltip             "View details for this update">

<!ENTITY  details.link                    "Details">

<!ENTITY  error.title                     "Update Failed">

<!ENTITY  error.label                     "There were problems checking for, downloading, or installing this 
                                           update. &brandShortName; could not be updated because:">
                                           
<!ENTITY  errorManual.label               "You can update &brandShortName; manually by visiting this link
                                           and downloading the latest version:">
                                           
<!ENTITY  errorpatching.intro             "The partial Update could not be applied. 
                                           &brandShortName; will try again by downloading a complete Update.">

<!ENTITY  genericBackgroundError.label    "&brandShortName; is unable to determine if there is an update available. Please
                                           make sure that you have the latest version of &brandShortName; from:">

<!ENTITY  finishedPage.title              "Update Ready to Install">
<!ENTITY  finishedPage.text               "The update will be installed the next time &brandShortName; starts. You 
                                           can restart &brandShortName; now, or continue working and restart later.">

<!ENTITY  finishedBackgroundPage.text     "A security and stability update for &brandShortName; has been
                                           downloaded and is ready to be installed.">
<!ENTITY  finishedBackground.name         "Update:">
<!-- LOCALIZATION NOTE (finishedBackground.more): This string describes the button labels defined by restartNowButton and restartLaterButton in updates.properties. -->
<!ENTITY  finishedBackground.more         "The update will be installed the next time &brandShortName; starts. You
                                           can restart &brandShortName; now, or continue working and restart later.">
<!ENTITY  finishedBackground.moreElevated "This update requires administrator privileges. The update will be
                                           installed the next time &brandShortName; starts. You can restart
                                           &brandShortName; now, continue working and restart later, or decline this
                                           update.">

<!ENTITY  update.details.label            "Details">
<!ENTITY  update.installedOn.label        "Installed on:">
<!ENTITY  update.status.label             "Status:">
PK
!<]C;chrome/en-US/locale/en-US/mozapps/update/updates.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 1st %S is the update name and the 2nd %S is the build
# identifier from the local updates.xml for displaying update history
# example: MyApplication (20081022033543)
updateFullName=%S (%S)

# LOCALIZATION NOTE: The 1st %S is brandShortName and 2nd %S is update version
# where update version from the update xml
# example: MyApplication 10.0.5
updateName=%S %S

# LOCALIZATION NOTE: When present
# %1$S is the brandShortName. Ex: MyApplication
# %2$S is the update version - provided by the update xml. Ex: version 10.0.5
# %3$S is the build identifier - provided by the update xml. Ex: 20081022033543
updateNightlyName=%1$S %2$S %3$S nightly
intro_major=Do you want to upgrade to %1$S %2$S now?
intro_minor=A security and stability update for %1$S is available:

updateType_major=New Version
updateType_minor=Security Update

# LOCALIZATION NOTE: When present %S is brandShortName
verificationError=%S could not confirm the integrity of the update package.
resumePausedAfterCloseTitle=Software Update
resumePausedAfterCloseMsg=You have paused downloading this update. Do you want to download the update in the background while you continue to use %S?
updaterIOErrorTitle=Software Update Failed
updaterIOErrorMsg=The update could not be installed. Please make sure there are no other copies of %S running on your computer, and then restart %S to try again.
okButton=OK
okButton.accesskey=O
askLaterButton=Ask Later
askLaterButton.accesskey=A
noThanksButton=No Thanks
noThanksButton.accesskey=N
updateButton_minor=Update %S
updateButton_minor.accesskey=U
updateButton_major=Get the New Version
updateButton_major.accesskey=G
backButton=Back
backButton.accesskey=B
acceptTermsButton=Accept Terms
acceptTermsButton.accesskey=A
# NOTE: The restartLaterButton string is also used in
# mozapps/extensions/content/blocklist.js
restartLaterButton=Restart Later
restartLaterButton.accesskey=L
restartNowButton=Restart %S
restartNowButton.accesskey=R

# LOCALIZATION NOTE: %S is the date the update was installed from the local
# updates.xml for displaying update history
statusSucceededFormat=Installed on: %S

statusFailed=Install Failed
pauseButtonPause=Pause
pauseButtonResume=Resume
hideButton=Hide
hideButton.accesskey=H

applyingUpdate=Applying update…

updatesfound_minor.title=Update Available
updatesfound_major.title=New Version Available

installSuccess=The Update was successfully installed
installPending=Install Pending
patchApplyFailure=The Update could not be installed (patch apply failed)
elevationFailure=You don’t have the permissions necessary to install this update. Please contact your system administrator.

# LOCALIZATION NOTE: %S is the amount downloaded so far
# example: Paused —  879 KB of 2.1 MB
downloadPausedStatus=Paused —  %S

check_error-200=Update XML file malformed (200)
check_error-403=Access denied (403)
check_error-404=Update XML file not found (404)
check_error-500=Internal server error (500)
check_error-2152398849=Failed (unknown reason)
check_error-2152398861=Connection refused
check_error-2152398862=Connection timed out
# NS_ERROR_OFFLINE
check_error-2152398864=Network is offline (go online)
check_error-2152398867=Port not allowed
check_error-2152398868=No data was received (please try again)
check_error-2152398878=Update server not found (check your internet connection)
check_error-2152398890=Proxy server not found (check your internet connection)
# NS_ERROR_DOCUMENT_NOT_CACHED
check_error-2152398918=Network is offline (go online)
check_error-2152398919=Data transfer was interrupted (please try again)
check_error-2152398920=Proxy server connection refused
check_error-2153390069=Server certificate has expired (please adjust your system clock to the correct date and time if it is incorrect)
check_error-verification_failed=The integrity of the update could not be verified
PK
!<ǺDD@chrome/en-US/locale/en-US/mozapps/xpinstall/xpinstallConfirm.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- extracted from institems.xul -->

<!ENTITY dialog.title            "Software Installation">
<!ENTITY dialog.style            "width: 45em">
<!ENTITY warningPrimary.label    "Install add-ons only from authors whom you trust.">
<!ENTITY warningSecondary.label  "Malicious software can damage your computer or violate your privacy.">

<!ENTITY from.label "from:">

PK
!<ֆGchrome/en-US/locale/en-US/mozapps/xpinstall/xpinstallConfirm.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

unverified=(Author not verified)
signed=(%S)

itemWarnIntroMultiple=You have asked to install the following %S items:
itemWarnIntroSingle=You have asked to install the following item:
installButtonDisabledLabel=Install (%S)
installButtonLabel=Install Now
PK
!<Iݪ~	~	0chrome/en-US/locale/en-US/necko/necko.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ResolvingHost=Looking up
#ConnectedTo=Connected to 
#ConnectingTo=Connecting to 
#SendingRequestTo=Sending request to 
#TransferringDataFrom=Transferring data from 

3=Looking up %1$S…
4=Connected to %1$S…
5=Sending request to %1$S…
6=Transferring data from %1$S…
7=Connecting to %1$S…
8=Read %1$S 
9=Wrote %1$S
10=Waiting for %1$S…
11=Looked up %1$S…
12=Performing a TLS handshake to %1$S…
13=The TLS handshake finished for %1$S…

27=Beginning FTP transaction…
28=Finished FTP transaction

UnsupportedFTPServer=The FTP server %1$S is currently unsupported.
RepostFormData=This web page is being redirected to a new location. Would you like to resend the form data you have typed to the new location?

# Directory listing strings
DirTitle=Index of %1$S
DirGoUp=Up to higher level directory
ShowHidden=Show hidden objects
DirColName=Name
DirColSize=Size
DirColMTime=Last Modified
DirFileLabel=File: 

PhishingAuth=You are about to visit “%1$S”. This site may be attempting to trick you into thinking you are visiting a different site. Use extreme caution.
PhishingAuthAccept=I understand and will be very careful
SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.

TrackingUriBlocked=The resource at “%1$S” was blocked because tracking protection is enabled.
UnsafeUriBlocked=The resource at “%1$S” was blocked by Safe Browsing.

# LOCALIZATION NOTE (APIDeprecationWarning):
# %1$S is the deprecated API; %2$S is the API function that should be used.
APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’

# LOCALIZATION NOTE (nsICookieManagerDeprecated): don't localize originAttributes.
# %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
PK
!<jVBB9chrome/en-US/locale/en-US/passwordmgr/passwordManager.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY      savedLogins.title               "Saved Logins">

<!ENTITY      closebutton.label               "Close">
<!ENTITY      closebutton.accesskey           "C">

<!ENTITY      treehead.site.label             "Site">
<!ENTITY      treehead.username.label         "Username">
<!ENTITY      treehead.password.label         "Password">
<!ENTITY      treehead.timeCreated.label         "First Used">
<!ENTITY      treehead.timeLastUsed.label        "Last Used">
<!ENTITY      treehead.timePasswordChanged.label "Last Changed">
<!ENTITY      treehead.timesUsed.label           "Times Used">

<!ENTITY      remove.label                    "Remove">
<!ENTITY      remove.accesskey                "R">

<!ENTITY      addLogin.label                  "Add Login">
<!ENTITY      addLogin.accesskey              "L">

<!ENTITY      import.label                    "Import…">
<!ENTITY      import.accesskey                "I">

<!ENTITY      searchFilter.label              "Search">
<!ENTITY      searchFilter.accesskey          "S">

<!ENTITY      windowClose.key                 "w">
<!ENTITY      focusSearch1.key                "f">
<!ENTITY      focusSearch2.key                "k">

<!ENTITY      copyPasswordCmd.label           "Copy Password">
<!ENTITY      copyPasswordCmd.accesskey       "C">

<!ENTITY      copyUsernameCmd.label           "Copy Username">
<!ENTITY      copyUsernameCmd.accesskey       "U">

<!ENTITY      editPasswordCmd.label           "Edit Password">
<!ENTITY      editPasswordCmd.accesskey       "E">

<!ENTITY      editUsernameCmd.label           "Edit Username">
<!ENTITY      editUsernameCmd.accesskey       "d">
PK
!<]b!!<chrome/en-US/locale/en-US/passwordmgr/passwordmgr.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

rememberValue = Use Password Manager to remember this value.
rememberPassword = Use Password Manager to remember this password.
savePasswordTitle = Confirm
# LOCALIZATION NOTE (saveLoginMsg, saveLoginMsgNoUser):
# %1$S is brandShortName, %2$S is the login's hostname.
saveLoginMsg = Would you like %1$S to save this login for %2$S?
saveLoginMsgNoUser = Would you like %1$S to save this password for %2$S?
saveLoginButtonAllow.label = Save
saveLoginButtonAllow.accesskey = S
saveLoginButtonDeny.label = Don’t Save
saveLoginButtonDeny.accesskey = D
updateLoginMsg = Would you like to update this login?
updateLoginMsgNoUser = Would you like to update this password?
updateLoginButtonText = Update
updateLoginButtonAccessKey = U
updateLoginButtonDeny.label = Don’t Update
updateLoginButtonDeny.accesskey = D
# LOCALIZATION NOTE (rememberPasswordMsg):
# 1st string is the username for the login, 2nd is the login's hostname.
# Note that long usernames may be truncated.
rememberPasswordMsg = Would you like to remember the password for “%1$S” on %2$S?
# LOCALIZATION NOTE (rememberPasswordMsgNoUsername):
# String is the login's hostname.
rememberPasswordMsgNoUsername = Would you like to remember the password on %S?
# LOCALIZATION NOTE (noUsernamePlaceholder):
# This is displayed in place of the username when it is missing.
noUsernamePlaceholder=No username
togglePasswordLabel=Show password
togglePasswordAccessKey2=h
notNowButtonText = &Not Now
notifyBarNotNowButtonText = Not Now
notifyBarNotNowButtonAccessKey = N
neverForSiteButtonText = Ne&ver for This Site
notifyBarNeverRememberButtonText2 = Never Save
notifyBarNeverRememberButtonAccessKey2 = e
rememberButtonText = &Remember
notifyBarRememberPasswordButtonText = Remember Password
notifyBarRememberPasswordButtonAccessKey = R
passwordChangeTitle = Confirm Password Change
# LOCALIZATION NOTE (updatePasswordMsg):
# String is the username for the login.
updatePasswordMsg = Would you like to update the saved password for “%S”?
updatePasswordMsgNoUser = Would you like to update the saved password?
notifyBarUpdateButtonText = Update Password
notifyBarUpdateButtonAccessKey = U
notifyBarDontChangeButtonText = Don’t Change
notifyBarDontChangeButtonAccessKey = D
userSelectText2 = Select which login to update:
hidePasswords=Hide Passwords
hidePasswordsAccessKey=P
showPasswords=Show Passwords
showPasswordsAccessKey=P
noMasterPasswordPrompt=Are you sure you wish to show your passwords?
removeAllPasswordsPrompt=Are you sure you wish to remove all passwords?
removeAllPasswordsTitle=Remove all passwords
removeLoginPrompt=Are you sure you wish to remove this login?
removeLoginTitle=Remove login
loginsDescriptionAll=Logins for the following sites are stored on your computer:
loginsDescriptionFiltered=The following logins match your search:
# LOCALIZATION NOTE (loginHostAge):
# This is used to show the context menu login items with their age.
# 1st string is the username for the login, 2nd is the login's age.
loginHostAge=%1$S (%2$S)
# LOCALIZATION NOTE (noUsername):
# String is used on the context menu when a login doesn't have a username.
noUsername=No username
duplicateLoginTitle=Login already exists
duplicateLogin=A duplicate login already exists.

# LOCALIZATION NOTE (insecureFieldWarningDescription2, insecureFieldWarningDescription3):
# %1$S will contain insecureFieldWarningLearnMore and look like a link to indicate that clicking will open a tab with support information.
insecureFieldWarningDescription2 = This connection is not secure. Logins entered here could be compromised. %1$S
insecureFieldWarningDescription3 = Logins entered here could be compromised. %1$S
insecureFieldWarningLearnMore = Learn More

# LOCALIZATION NOTE (removeAll, removeAllShown):
# removeAll and removeAllShown are both used on the same one button,
# never displayed together and can share the same accesskey.
# When only partial sites are shown as a result of keyword search,
# removeAllShown is displayed as button label.
# removeAll is displayed when no keyword search and all sites are shown.
removeAll.label=Remove All
removeAll.accesskey=A
removeAllShown.label=Remove All Shown
removeAllShown.accesskey=A
PK
!<7&pc'q'q5chrome/en-US/locale/en-US/pipnss/nsserrors.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

SSL_ERROR_EXPORT_ONLY_SERVER=Unable to communicate securely. Peer does not support high-grade encryption.
SSL_ERROR_US_ONLY_SERVER=Unable to communicate securely. Peer requires high-grade encryption which is not supported.
SSL_ERROR_NO_CYPHER_OVERLAP=Cannot communicate securely with peer: no common encryption algorithm(s).
SSL_ERROR_NO_CERTIFICATE=Unable to find the certificate or key necessary for authentication.
SSL_ERROR_BAD_CERTIFICATE=Unable to communicate securely with peer: peers’s certificate was rejected.
SSL_ERROR_BAD_CLIENT=The server has encountered bad data from the client.
SSL_ERROR_BAD_SERVER=The client has encountered bad data from the server.
SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE=Unsupported certificate type.
SSL_ERROR_UNSUPPORTED_VERSION=Peer using unsupported version of security protocol.
SSL_ERROR_WRONG_CERTIFICATE=Client authentication failed: private key in key database does not match public key in certificate database.
SSL_ERROR_BAD_CERT_DOMAIN=Unable to communicate securely with peer: requested domain name does not match the server’s certificate.
SSL_ERROR_POST_WARNING=Unrecognized SSL error code.
SSL_ERROR_SSL2_DISABLED=Peer only supports SSL version 2, which is locally disabled.
SSL_ERROR_BAD_MAC_READ=SSL received a record with an incorrect Message Authentication Code.
SSL_ERROR_BAD_MAC_ALERT=SSL peer reports incorrect Message Authentication Code.
SSL_ERROR_BAD_CERT_ALERT=SSL peer cannot verify your certificate.
SSL_ERROR_REVOKED_CERT_ALERT=SSL peer rejected your certificate as revoked.
SSL_ERROR_EXPIRED_CERT_ALERT=SSL peer rejected your certificate as expired.
SSL_ERROR_SSL_DISABLED=Cannot connect: SSL is disabled.
SSL_ERROR_FORTEZZA_PQG=Cannot connect: SSL peer is in another FORTEZZA domain.
SSL_ERROR_UNKNOWN_CIPHER_SUITE=An unknown SSL cipher suite has been requested.
SSL_ERROR_NO_CIPHERS_SUPPORTED=No cipher suites are present and enabled in this program.
SSL_ERROR_BAD_BLOCK_PADDING=SSL received a record with bad block padding.
SSL_ERROR_RX_RECORD_TOO_LONG=SSL received a record that exceeded the maximum permissible length.
SSL_ERROR_TX_RECORD_TOO_LONG=SSL attempted to send a record that exceeded the maximum permissible length.
SSL_ERROR_RX_MALFORMED_HELLO_REQUEST=SSL received a malformed Hello Request handshake message.
SSL_ERROR_RX_MALFORMED_CLIENT_HELLO=SSL received a malformed Client Hello handshake message.
SSL_ERROR_RX_MALFORMED_SERVER_HELLO=SSL received a malformed Server Hello handshake message.
SSL_ERROR_RX_MALFORMED_CERTIFICATE=SSL received a malformed Certificate handshake message.
SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH=SSL received a malformed Server Key Exchange handshake message.
SSL_ERROR_RX_MALFORMED_CERT_REQUEST=SSL received a malformed Certificate Request handshake message.
SSL_ERROR_RX_MALFORMED_HELLO_DONE=SSL received a malformed Server Hello Done handshake message.
SSL_ERROR_RX_MALFORMED_CERT_VERIFY=SSL received a malformed Certificate Verify handshake message.
SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH=SSL received a malformed Client Key Exchange handshake message.
SSL_ERROR_RX_MALFORMED_FINISHED=SSL received a malformed Finished handshake message.
SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER=SSL received a malformed Change Cipher Spec record.
SSL_ERROR_RX_MALFORMED_ALERT=SSL received a malformed Alert record.
SSL_ERROR_RX_MALFORMED_HANDSHAKE=SSL received a malformed Handshake record.
SSL_ERROR_RX_MALFORMED_APPLICATION_DATA=SSL received a malformed Application Data record.
SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST=SSL received an unexpected Hello Request handshake message.
SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO=SSL received an unexpected Client Hello handshake message.
SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO=SSL received an unexpected Server Hello handshake message.
SSL_ERROR_RX_UNEXPECTED_CERTIFICATE=SSL received an unexpected Certificate handshake message.
SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH=SSL received an unexpected Server Key Exchange handshake message.
SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST=SSL received an unexpected Certificate Request handshake message.
SSL_ERROR_RX_UNEXPECTED_HELLO_DONE=SSL received an unexpected Server Hello Done handshake message.
SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY=SSL received an unexpected Certificate Verify handshake message.
SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH=SSL received an unexpected Client Key Exchange handshake message.
SSL_ERROR_RX_UNEXPECTED_FINISHED=SSL received an unexpected Finished handshake message.
SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER=SSL received an unexpected Change Cipher Spec record.
SSL_ERROR_RX_UNEXPECTED_ALERT=SSL received an unexpected Alert record.
SSL_ERROR_RX_UNEXPECTED_HANDSHAKE=SSL received an unexpected Handshake record.
SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA=SSL received an unexpected Application Data record.
SSL_ERROR_RX_UNKNOWN_RECORD_TYPE=SSL received a record with an unknown content type.
SSL_ERROR_RX_UNKNOWN_HANDSHAKE=SSL received a handshake message with an unknown message type.
SSL_ERROR_RX_UNKNOWN_ALERT=SSL received an alert record with an unknown alert description.
SSL_ERROR_CLOSE_NOTIFY_ALERT=SSL peer has closed this connection.
SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT=SSL peer was not expecting a handshake message it received.
SSL_ERROR_DECOMPRESSION_FAILURE_ALERT=SSL peer was unable to successfully decompress an SSL record it received.
SSL_ERROR_HANDSHAKE_FAILURE_ALERT=SSL peer was unable to negotiate an acceptable set of security parameters.
SSL_ERROR_ILLEGAL_PARAMETER_ALERT=SSL peer rejected a handshake message for unacceptable content.
SSL_ERROR_UNSUPPORTED_CERT_ALERT=SSL peer does not support certificates of the type it received.
SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT=SSL peer had some unspecified issue with the certificate it received.
SSL_ERROR_GENERATE_RANDOM_FAILURE=SSL experienced a failure of its random number generator.
SSL_ERROR_SIGN_HASHES_FAILURE=Unable to digitally sign data required to verify your certificate.
SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE=SSL was unable to extract the public key from the peer’s certificate.
SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE=Unspecified failure while processing SSL Server Key Exchange handshake.
SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE=Unspecified failure while processing SSL Client Key Exchange handshake.
SSL_ERROR_ENCRYPTION_FAILURE=Bulk data encryption algorithm failed in selected cipher suite.
SSL_ERROR_DECRYPTION_FAILURE=Bulk data decryption algorithm failed in selected cipher suite.
SSL_ERROR_SOCKET_WRITE_FAILURE=Attempt to write encrypted data to underlying socket failed.
SSL_ERROR_MD5_DIGEST_FAILURE=MD5 digest function failed.
SSL_ERROR_SHA_DIGEST_FAILURE=SHA-1 digest function failed.
SSL_ERROR_MAC_COMPUTATION_FAILURE=MAC computation failed.
SSL_ERROR_SYM_KEY_CONTEXT_FAILURE=Failure to create Symmetric Key context.
SSL_ERROR_SYM_KEY_UNWRAP_FAILURE=Failure to unwrap the Symmetric key in Client Key Exchange message.
SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED=SSL Server attempted to use domestic-grade public key with export cipher suite.
SSL_ERROR_IV_PARAM_FAILURE=PKCS11 code failed to translate an IV into a param.
SSL_ERROR_INIT_CIPHER_SUITE_FAILURE=Failed to initialize the selected cipher suite.
SSL_ERROR_SESSION_KEY_GEN_FAILURE=Client failed to generate session keys for SSL session.
SSL_ERROR_NO_SERVER_KEY_FOR_ALG=Server has no key for the attempted key exchange algorithm.
SSL_ERROR_TOKEN_INSERTION_REMOVAL=PKCS#11 token was inserted or removed while operation was in progress.
SSL_ERROR_TOKEN_SLOT_NOT_FOUND=No PKCS#11 token could be found to do a required operation.
SSL_ERROR_NO_COMPRESSION_OVERLAP=Cannot communicate securely with peer: no common compression algorithm(s).
SSL_ERROR_HANDSHAKE_NOT_COMPLETED=Cannot initiate another SSL handshake until current handshake is complete.
SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE=Received incorrect handshakes hash values from peer.
SSL_ERROR_CERT_KEA_MISMATCH=The certificate provided cannot be used with the selected key exchange algorithm.
SSL_ERROR_NO_TRUSTED_SSL_CLIENT_CA=No certificate authority is trusted for SSL client authentication.
SSL_ERROR_SESSION_NOT_FOUND=Client’s SSL session ID not found in server’s session cache.
SSL_ERROR_DECRYPTION_FAILED_ALERT=Peer was unable to decrypt an SSL record it received.
SSL_ERROR_RECORD_OVERFLOW_ALERT=Peer received an SSL record that was longer than is permitted.
SSL_ERROR_UNKNOWN_CA_ALERT=Peer does not recognize and trust the CA that issued your certificate.
SSL_ERROR_ACCESS_DENIED_ALERT=Peer received a valid certificate, but access was denied.
SSL_ERROR_DECODE_ERROR_ALERT=Peer could not decode an SSL handshake message.
SSL_ERROR_DECRYPT_ERROR_ALERT=Peer reports failure of signature verification or key exchange.
SSL_ERROR_EXPORT_RESTRICTION_ALERT=Peer reports negotiation not in compliance with export regulations.
SSL_ERROR_PROTOCOL_VERSION_ALERT=Peer reports incompatible or unsupported protocol version.
SSL_ERROR_INSUFFICIENT_SECURITY_ALERT=Server requires ciphers more secure than those supported by client.
SSL_ERROR_INTERNAL_ERROR_ALERT=Peer reports it experienced an internal error.
SSL_ERROR_USER_CANCELED_ALERT=Peer user canceled handshake.
SSL_ERROR_NO_RENEGOTIATION_ALERT=Peer does not permit renegotiation of SSL security parameters.
SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED=SSL server cache not configured and not disabled for this socket.
SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT=SSL peer does not support requested TLS hello extension.
SSL_ERROR_CERTIFICATE_UNOBTAINABLE_ALERT=SSL peer could not obtain your certificate from the supplied URL.
SSL_ERROR_UNRECOGNIZED_NAME_ALERT=SSL peer has no certificate for the requested DNS name.
SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT=SSL peer was unable to get an OCSP response for its certificate.
SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT=SSL peer reported bad certificate hash value.
SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET=SSL received an unexpected New Session Ticket handshake message.
SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET=SSL received a malformed New Session Ticket handshake message.
SSL_ERROR_DECOMPRESSION_FAILURE=SSL received a compressed record that could not be decompressed.
SSL_ERROR_RENEGOTIATION_NOT_ALLOWED=Renegotiation is not allowed on this SSL socket.
SSL_ERROR_UNSAFE_NEGOTIATION=Peer attempted old style (potentially vulnerable) handshake.
SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD=SSL received an unexpected uncompressed record.
SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY=SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.
SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID=SSL received invalid NPN extension data.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2=SSL feature not supported for SSL 2.0 connections.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS=SSL feature not supported for servers.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS=SSL feature not supported for clients.
SSL_ERROR_INVALID_VERSION_RANGE=SSL version range is not valid.
SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION=SSL peer selected a cipher suite disallowed for the selected protocol version.
SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST=SSL received a malformed Hello Verify Request handshake message.
SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST=SSL received an unexpected Hello Verify Request handshake message.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION=SSL feature not supported for the protocol version.
SSL_ERROR_RX_UNEXPECTED_CERT_STATUS=SSL received an unexpected Certificate Status handshake message.
SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM=Unsupported hash algorithm used by TLS peer.
SSL_ERROR_DIGEST_FAILURE=Digest function failed.
SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM=Incorrect signature algorithm specified in a digitally-signed element.
SSL_ERROR_NEXT_PROTOCOL_NO_CALLBACK=The next protocol negotiation extension was enabled, but the callback was cleared prior to being needed.
SSL_ERROR_NEXT_PROTOCOL_NO_PROTOCOL=The server supports no protocols that the client advertises in the ALPN extension.
SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT=The server rejected the handshake because the client downgraded to a lower TLS version than the server supports.
SSL_ERROR_WEAK_SERVER_CERT_KEY=The server certificate included a public key that was too weak.
SSL_ERROR_RX_SHORT_DTLS_READ=Not enough room in buffer for DTLS record.
SSL_ERROR_NO_SUPPORTED_SIGNATURE_ALGORITHM=No supported TLS signature algorithm was configured.
SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM=The peer used an unsupported combination of signature and hash algorithm.
SSL_ERROR_MISSING_EXTENDED_MASTER_SECRET=The peer tried to resume without a correct extended_master_secret extension.
SSL_ERROR_UNEXPECTED_EXTENDED_MASTER_SECRET=The peer tried to resume with an unexpected extended_master_secret extension.
SEC_ERROR_IO=An I/O error occurred during security authorization.
SEC_ERROR_LIBRARY_FAILURE=security library failure.
SEC_ERROR_BAD_DATA=security library: received bad data.
SEC_ERROR_OUTPUT_LEN=security library: output length error.
SEC_ERROR_INPUT_LEN=security library has experienced an input length error.
SEC_ERROR_INVALID_ARGS=security library: invalid arguments.
SEC_ERROR_INVALID_ALGORITHM=security library: invalid algorithm.
SEC_ERROR_INVALID_AVA=security library: invalid AVA.
SEC_ERROR_INVALID_TIME=Improperly formatted time string.
SEC_ERROR_BAD_DER=security library: improperly formatted DER-encoded message.
SEC_ERROR_BAD_SIGNATURE=Peer’s certificate has an invalid signature.
SEC_ERROR_EXPIRED_CERTIFICATE=Peer’s Certificate has expired.
SEC_ERROR_REVOKED_CERTIFICATE=Peer’s Certificate has been revoked.
SEC_ERROR_UNKNOWN_ISSUER=Peer’s Certificate issuer is not recognized.
SEC_ERROR_BAD_KEY=Peer’s public key is invalid.
SEC_ERROR_BAD_PASSWORD=The security password entered is incorrect.
SEC_ERROR_RETRY_PASSWORD=New password entered incorrectly. Please try again.
SEC_ERROR_NO_NODELOCK=security library: no nodelock.
SEC_ERROR_BAD_DATABASE=security library: bad database.
SEC_ERROR_NO_MEMORY=security library: memory allocation failure.
SEC_ERROR_UNTRUSTED_ISSUER=Peer’s certificate issuer has been marked as not trusted by the user.
SEC_ERROR_UNTRUSTED_CERT=Peer’s certificate has been marked as not trusted by the user.
SEC_ERROR_DUPLICATE_CERT=Certificate already exists in your database.
SEC_ERROR_DUPLICATE_CERT_NAME=Downloaded certificate’s name duplicates one already in your database.
SEC_ERROR_ADDING_CERT=Error adding certificate to database.
SEC_ERROR_FILING_KEY=Error refiling the key for this certificate.
SEC_ERROR_NO_KEY=The private key for this certificate cannot be found in key database
SEC_ERROR_CERT_VALID=This certificate is valid.
SEC_ERROR_CERT_NOT_VALID=This certificate is not valid.
SEC_ERROR_CERT_NO_RESPONSE=Cert Library: No Response
SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE=The certificate issuer’s certificate has expired. Check your system date and time.
SEC_ERROR_CRL_EXPIRED=The CRL for the certificate’s issuer has expired. Update it or check your system date and time.
SEC_ERROR_CRL_BAD_SIGNATURE=The CRL for the certificate’s issuer has an invalid signature.
SEC_ERROR_CRL_INVALID=New CRL has an invalid format.
SEC_ERROR_EXTENSION_VALUE_INVALID=Certificate extension value is invalid.
SEC_ERROR_EXTENSION_NOT_FOUND=Certificate extension not found.
SEC_ERROR_CA_CERT_INVALID=Issuer certificate is invalid.
SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID=Certificate path length constraint is invalid.
SEC_ERROR_CERT_USAGES_INVALID=Certificate usages field is invalid.
SEC_INTERNAL_ONLY=**Internal ONLY module**
SEC_ERROR_INVALID_KEY=The key does not support the requested operation.
SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION=Certificate contains unknown critical extension.
SEC_ERROR_OLD_CRL=New CRL is not later than the current one.
SEC_ERROR_NO_EMAIL_CERT=Not encrypted or signed: you do not yet have an email certificate.
SEC_ERROR_NO_RECIPIENT_CERTS_QUERY=Not encrypted: you do not have certificates for each of the recipients.
SEC_ERROR_NOT_A_RECIPIENT=Cannot decrypt: you are not a recipient, or matching certificate and private key not found.
SEC_ERROR_PKCS7_KEYALG_MISMATCH=Cannot decrypt: key encryption algorithm does not match your certificate.
SEC_ERROR_PKCS7_BAD_SIGNATURE=Signature verification failed: no signer found, too many signers found, or improper or corrupted data.
SEC_ERROR_UNSUPPORTED_KEYALG=Unsupported or unknown key algorithm.
SEC_ERROR_DECRYPTION_DISALLOWED=Cannot decrypt: encrypted using a disallowed algorithm or key size.
XP_SEC_FORTEZZA_BAD_CARD=Fortezza card has not been properly initialized. Please remove it and return it to your issuer.
XP_SEC_FORTEZZA_NO_CARD=No Fortezza cards Found
XP_SEC_FORTEZZA_NONE_SELECTED=No Fortezza card selected
XP_SEC_FORTEZZA_MORE_INFO=Please select a personality to get more info on
XP_SEC_FORTEZZA_PERSON_NOT_FOUND=Personality not found
XP_SEC_FORTEZZA_NO_MORE_INFO=No more information on that Personality
XP_SEC_FORTEZZA_BAD_PIN=Invalid Pin
XP_SEC_FORTEZZA_PERSON_ERROR=Couldn’t initialize Fortezza personalities.
SEC_ERROR_NO_KRL=No KRL for this site’s certificate has been found.
SEC_ERROR_KRL_EXPIRED=The KRL for this site’s certificate has expired.
SEC_ERROR_KRL_BAD_SIGNATURE=The KRL for this site’s certificate has an invalid signature.
SEC_ERROR_REVOKED_KEY=The key for this site’s certificate has been revoked.
SEC_ERROR_KRL_INVALID=New KRL has an invalid format.
SEC_ERROR_NEED_RANDOM=security library: need random data.
SEC_ERROR_NO_MODULE=security library: no security module can perform the requested operation.
SEC_ERROR_NO_TOKEN=The security card or token does not exist, needs to be initialized, or has been removed.
SEC_ERROR_READ_ONLY=security library: read-only database.
SEC_ERROR_NO_SLOT_SELECTED=No slot or token was selected.
SEC_ERROR_CERT_NICKNAME_COLLISION=A certificate with the same nickname already exists.
SEC_ERROR_KEY_NICKNAME_COLLISION=A key with the same nickname already exists.
SEC_ERROR_SAFE_NOT_CREATED=error while creating safe object
SEC_ERROR_BAGGAGE_NOT_CREATED=error while creating baggage object
XP_JAVA_REMOVE_PRINCIPAL_ERROR=Couldn’t remove the principal
XP_JAVA_DELETE_PRIVILEGE_ERROR=Couldn’t delete the privilege
XP_JAVA_CERT_NOT_EXISTS_ERROR=This principal doesn’t have a certificate
SEC_ERROR_BAD_EXPORT_ALGORITHM=Required algorithm is not allowed.
SEC_ERROR_EXPORTING_CERTIFICATES=Error attempting to export certificates.
SEC_ERROR_IMPORTING_CERTIFICATES=Error attempting to import certificates.
SEC_ERROR_PKCS12_DECODING_PFX=Unable to import. Decoding error. File not valid.
SEC_ERROR_PKCS12_INVALID_MAC=Unable to import. Invalid MAC. Incorrect password or corrupt file.
SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM=Unable to import. MAC algorithm not supported.
SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE=Unable to import. Only password integrity and privacy modes supported.
SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE=Unable to import. File structure is corrupt.
SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM=Unable to import. Encryption algorithm not supported.
SEC_ERROR_PKCS12_UNSUPPORTED_VERSION=Unable to import. File version not supported.
SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT=Unable to import. Incorrect privacy password.
SEC_ERROR_PKCS12_CERT_COLLISION=Unable to import. Same nickname already exists in database.
SEC_ERROR_USER_CANCELLED=The user pressed cancel.
SEC_ERROR_PKCS12_DUPLICATE_DATA=Not imported, already in database.
SEC_ERROR_MESSAGE_SEND_ABORTED=Message not sent.
SEC_ERROR_INADEQUATE_KEY_USAGE=Certificate key usage inadequate for attempted operation.
SEC_ERROR_INADEQUATE_CERT_TYPE=Certificate type not approved for application.
SEC_ERROR_CERT_ADDR_MISMATCH=Address in signing certificate does not match address in message headers.
SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY=Unable to import. Error attempting to import private key.
SEC_ERROR_PKCS12_IMPORTING_CERT_CHAIN=Unable to import. Error attempting to import certificate chain.
SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME=Unable to export. Unable to locate certificate or key by nickname.
SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY=Unable to export. Private Key could not be located and exported.
SEC_ERROR_PKCS12_UNABLE_TO_WRITE=Unable to export. Unable to write the export file.
SEC_ERROR_PKCS12_UNABLE_TO_READ=Unable to import. Unable to read the import file.
SEC_ERROR_PKCS12_KEY_DATABASE_NOT_INITIALIZED=Unable to export. Key database corrupt or deleted.
SEC_ERROR_KEYGEN_FAIL=Unable to generate public/private key pair.
SEC_ERROR_INVALID_PASSWORD=Password entered is invalid. Please pick a different one.
SEC_ERROR_RETRY_OLD_PASSWORD=Old password entered incorrectly. Please try again.
SEC_ERROR_BAD_NICKNAME=Certificate nickname already in use.
SEC_ERROR_NOT_FORTEZZA_ISSUER=Peer FORTEZZA chain has a non-FORTEZZA Certificate.
SEC_ERROR_CANNOT_MOVE_SENSITIVE_KEY=A sensitive key cannot be moved to the slot where it is needed.
SEC_ERROR_JS_INVALID_MODULE_NAME=Invalid module name.
SEC_ERROR_JS_INVALID_DLL=Invalid module path/filename
SEC_ERROR_JS_ADD_MOD_FAILURE=Unable to add module
SEC_ERROR_JS_DEL_MOD_FAILURE=Unable to delete module
SEC_ERROR_OLD_KRL=New KRL is not later than the current one.
SEC_ERROR_CKL_CONFLICT=New CKL has different issuer than current CKL. Delete current CKL.
SEC_ERROR_CERT_NOT_IN_NAME_SPACE=The Certifying Authority for this certificate is not permitted to issue a certificate with this name.
SEC_ERROR_KRL_NOT_YET_VALID=The key revocation list for this certificate is not yet valid.
SEC_ERROR_CRL_NOT_YET_VALID=The certificate revocation list for this certificate is not yet valid.
SEC_ERROR_UNKNOWN_CERT=The requested certificate could not be found.
SEC_ERROR_UNKNOWN_SIGNER=The signer’s certificate could not be found.
SEC_ERROR_CERT_BAD_ACCESS_LOCATION=The location for the certificate status server has invalid format.
SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE=The OCSP response cannot be fully decoded; it is of an unknown type.
SEC_ERROR_OCSP_BAD_HTTP_RESPONSE=The OCSP server returned unexpected/invalid HTTP data.
SEC_ERROR_OCSP_MALFORMED_REQUEST=The OCSP server found the request to be corrupted or improperly formed.
SEC_ERROR_OCSP_SERVER_ERROR=The OCSP server experienced an internal error.
SEC_ERROR_OCSP_TRY_SERVER_LATER=The OCSP server suggests trying again later.
SEC_ERROR_OCSP_REQUEST_NEEDS_SIG=The OCSP server requires a signature on this request.
SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST=The OCSP server has refused this request as unauthorized.
SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS=The OCSP server returned an unrecognizable status.
SEC_ERROR_OCSP_UNKNOWN_CERT=The OCSP server has no status for the certificate.
SEC_ERROR_OCSP_NOT_ENABLED=You must enable OCSP before performing this operation.
SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER=You must set the OCSP default responder before performing this operation.
SEC_ERROR_OCSP_MALFORMED_RESPONSE=The response from the OCSP server was corrupted or improperly formed.
SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE=The signer of the OCSP response is not authorized to give status for this certificate.
SEC_ERROR_OCSP_FUTURE_RESPONSE=The OCSP response is not yet valid (contains a date in the future).
SEC_ERROR_OCSP_OLD_RESPONSE=The OCSP response contains out-of-date information.
SEC_ERROR_DIGEST_NOT_FOUND=The CMS or PKCS #7 Digest was not found in signed message.
SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE=The CMS or PKCS #7 Message type is unsupported.
SEC_ERROR_MODULE_STUCK=PKCS #11 module could not be removed because it is still in use.
SEC_ERROR_BAD_TEMPLATE=Could not decode ASN.1 data. Specified template was invalid.
SEC_ERROR_CRL_NOT_FOUND=No matching CRL was found.
SEC_ERROR_REUSED_ISSUER_AND_SERIAL=You are attempting to import a cert with the same issuer/serial as an existing cert, but that is not the same cert.
SEC_ERROR_BUSY=NSS could not shutdown. Objects are still in use.
SEC_ERROR_EXTRA_INPUT=DER-encoded message contained extra unused data.
SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE=Unsupported elliptic curve.
SEC_ERROR_UNSUPPORTED_EC_POINT_FORM=Unsupported elliptic curve point form.
SEC_ERROR_UNRECOGNIZED_OID=Unrecognized Object Identifier.
SEC_ERROR_OCSP_INVALID_SIGNING_CERT=Invalid OCSP signing certificate in OCSP response.
SEC_ERROR_REVOKED_CERTIFICATE_CRL=Certificate is revoked in issuer’s certificate revocation list.
SEC_ERROR_REVOKED_CERTIFICATE_OCSP=Issuer’s OCSP responder reports certificate is revoked.
SEC_ERROR_CRL_INVALID_VERSION=Issuer’s Certificate Revocation List has an unknown version number.
SEC_ERROR_CRL_V1_CRITICAL_EXTENSION=Issuer’s V1 Certificate Revocation List has a critical extension.
SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION=Issuer’s V2 Certificate Revocation List has an unknown critical extension.
SEC_ERROR_UNKNOWN_OBJECT_TYPE=Unknown object type specified.
SEC_ERROR_INCOMPATIBLE_PKCS11=PKCS #11 driver violates the spec in an incompatible way.
SEC_ERROR_NO_EVENT=No new slot event is available at this time.
SEC_ERROR_CRL_ALREADY_EXISTS=CRL already exists.
SEC_ERROR_NOT_INITIALIZED=NSS is not initialized.
SEC_ERROR_TOKEN_NOT_LOGGED_IN=The operation failed because the PKCS#11 token is not logged in.
SEC_ERROR_OCSP_RESPONDER_CERT_INVALID=Configured OCSP responder’s certificate is invalid.
SEC_ERROR_OCSP_BAD_SIGNATURE=OCSP response has an invalid signature.
SEC_ERROR_OUT_OF_SEARCH_LIMITS=Cert validation search is out of search limits
SEC_ERROR_INVALID_POLICY_MAPPING=Policy mapping contains anypolicy
SEC_ERROR_POLICY_VALIDATION_FAILED=Cert chain fails policy validation
SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE=Unknown location type in cert AIA extension
SEC_ERROR_BAD_HTTP_RESPONSE=Server returned bad HTTP response
SEC_ERROR_BAD_LDAP_RESPONSE=Server returned bad LDAP response
SEC_ERROR_FAILED_TO_ENCODE_DATA=Failed to encode data with ASN1 encoder
SEC_ERROR_BAD_INFO_ACCESS_LOCATION=Bad information access location in cert extension
SEC_ERROR_LIBPKIX_INTERNAL=Libpkix internal error occurred during cert validation.
SEC_ERROR_PKCS11_GENERAL_ERROR=A PKCS #11 module returned CKR_GENERAL_ERROR, indicating that an unrecoverable error has occurred.
SEC_ERROR_PKCS11_FUNCTION_FAILED=A PKCS #11 module returned CKR_FUNCTION_FAILED, indicating that the requested function could not be performed. Trying the same operation again might succeed.
SEC_ERROR_PKCS11_DEVICE_ERROR=A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.
SEC_ERROR_BAD_INFO_ACCESS_METHOD=Unknown information access method in certificate extension.
SEC_ERROR_CRL_IMPORT_FAILED=Error attempting to import a CRL.
SEC_ERROR_EXPIRED_PASSWORD=The password expired.
SEC_ERROR_LOCKED_PASSWORD=The password is locked.
SEC_ERROR_UNKNOWN_PKCS11_ERROR=Unknown PKCS #11 error.
SEC_ERROR_BAD_CRL_DP_URL=Invalid or unsupported URL in CRL distribution point name.
SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED=The certificate was signed using a signature algorithm that is disabled because it is not secure.
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE=The server uses key pinning (HPKP) but no trusted certificate chain could be constructed that matches the pinset. Key pinning violations cannot be overridden.
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY=The server uses a certificate with a basic constraints extension identifying it as a certificate authority. For a properly-issued certificate, this should not be the case.
MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE=The server presented a certificate with a key size that is too small to establish a secure connection.
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA=An X.509 version 1 certificate that is not a trust anchor was used to issue the server’s certificate. X.509 version 1 certificates are deprecated and should not be used to sign other certificates.
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE=The server presented a certificate that is not yet valid.
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE=A certificate that is not yet valid was used to issue the server’s certificate.
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH=The signature algorithm in the signature field of the certificate does not match the algorithm in its signatureAlgorithm field.
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING=The OCSP response does not include a status for the certificate being verified.
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG=The server presented a certificate that is valid for too long.
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING=A required TLS feature is missing.
MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING=The server presented a certificate that contains an invalid encoding of an integer. Common causes include negative serial numbers, negative RSA moduli, and encodings that are longer than necessary.
MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME=The server presented a certificate with an empty issuer distinguished name.
PK
!<CL8]<<2chrome/en-US/locale/en-US/pipnss/pipnss.properties#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

CertPassPrompt=Please enter the master password for the %S.
# the following strings have special requirements:
# they must fit in a 32 or 64 byte buffer after being translated
# to UTF8.  Note to translator. It's not easy for you to figure
# whether the escaped unicode string you produce will fit in 
# the space allocated.
#
# 64 bytes long after conversion to UTF8
RootCertModuleName=Builtin Roots Module
#
# 32 bytes long after conversion to UTF8
ManufacturerID=Mozilla.org
#
# 32  bytes long after conversion to UTF8
LibraryDescription=PSM Internal Crypto Services
#
# 32 bytes long after conversion to UTF8
TokenDescription=Generic Crypto Services
#
# 32 bytes long after conversion to UTF8
PrivateTokenDescription=Software Security Device
#
# 64 bytes long after conversion to UTF8
SlotDescription=PSM Internal Cryptographic Services
#
# 64 bytes long after conversion to UTF8
PrivateSlotDescription=PSM Private Keys
#
# 32
Fips140TokenDescription=Software Security Device (FIPS)
# 64
Fips140SlotDescription=FIPS 140 Cryptographic, Key and Certificate Services
# 32
InternalToken=Software Security Device
# End of size restriction.
VerifySSLClient=SSL Client Certificate
VerifySSLServer=SSL Server Certificate
VerifySSLCA=SSL Certificate Authority
VerifyEmailSigner=Email Signer Certificate
VerifyEmailRecip=Email Recipient Certificate
VerifyObjSign=Object Signer
HighGrade=High Grade
MediumGrade=Medium Grade
# LOCALIZATION NOTE (nick_template): $1s is the common name from a cert (e.g. "Mozilla"), $2s is the CA name (e.g. VeriSign)
nick_template=%1$s’s %2$s ID
#These are the strings set for the ASN1 objects in a certificate.
CertDumpCertificate=Certificate
CertDumpVersion=Version
# LOCALIZATION NOTE (CertDumpVersionValue): %S is a version number (e.g. "3" in "Version 3")
CertDumpVersionValue=Version %S
CertDumpSerialNo=Serial Number
CertDumpMD2WithRSA=PKCS #1 MD2 With RSA Encryption
CertDumpMD5WithRSA=PKCS #1 MD5 With RSA Encryption
CertDumpSHA1WithRSA=PKCS #1 SHA-1 With RSA Encryption
CertDumpSHA256WithRSA=PKCS #1 SHA-256 With RSA Encryption
CertDumpSHA384WithRSA=PKCS #1 SHA-384 With RSA Encryption
CertDumpSHA512WithRSA=PKCS #1 SHA-512 With RSA Encryption
CertDumpDefOID=Object Identifier (%S)
CertDumpIssuer=Issuer
CertDumpSubject=Subject
CertDumpAVACountry=C
CertDumpAVAState=ST
CertDumpAVALocality=L
CertDumpAVAOrg=O
CertDumpAVAOU=OU
CertDumpAVACN=CN
CertDumpUserID=UID
CertDumpPK9Email=E
CertDumpAVADN=DN
CertDumpAVADC=DC
CertDumpSurname=Surname
CertDumpGivenName=Given Name
CertDumpValidity=Validity
CertDumpNotBefore=Not Before
CertDumpNotAfter=Not After
CertDumpSPKI=Subject Public Key Info
CertDumpSPKIAlg=Subject Public Key Algorithm
CertDumpAlgID=Algorithm Identifier
CertDumpParams=Algorithm Parameters
CertDumpRSAEncr=PKCS #1 RSA Encryption
CertDumpRSAPSSSignature=PKCS #1 RSASSA-PSS Signature
CertDumpRSATemplate=Modulus (%S bits):\n%S\nExponent (%S bits):\n%S
CertDumpECTemplate=Key size: %S bits\nBase point order length: %S bits\nPublic value:\n%S
CertDumpIssuerUniqueID=Issuer Unique ID
CertDumpSubjPubKey=Subject’s Public Key
CertDumpSubjectUniqueID=Subject Unique ID
CertDumpExtensions=Extensions
CertDumpSubjectDirectoryAttr=Certificate Subject Directory Attributes
CertDumpSubjectKeyID=Certificate Subject Key ID
CertDumpKeyUsage=Certificate Key Usage
CertDumpSubjectAltName=Certificate Subject Alt Name
CertDumpIssuerAltName=Certificate Issuer Alt Name
CertDumpBasicConstraints=Certificate Basic Constraints
CertDumpNameConstraints=Certificate Name Constraints
CertDumpCrlDistPoints=CRL Distribution Points
CertDumpCertPolicies=Certificate Policies
CertDumpPolicyMappings=Certificate Policy Mappings
CertDumpPolicyConstraints=Certificate Policy Constraints
CertDumpAuthKeyID=Certificate Authority Key Identifier
CertDumpExtKeyUsage=Extended Key Usage
CertDumpAuthInfoAccess=Authority Information Access
CertDumpAnsiX9DsaSignature=ANSI X9.57 DSA Signature
CertDumpAnsiX9DsaSignatureWithSha1=ANSI X9.57 DSA Signature with SHA1 Digest
CertDumpAnsiX962ECDsaSignatureWithSha1=ANSI X9.62 ECDSA Signature with SHA1
CertDumpAnsiX962ECDsaSignatureWithSha224=ANSI X9.62 ECDSA Signature with SHA224
CertDumpAnsiX962ECDsaSignatureWithSha256=ANSI X9.62 ECDSA Signature with SHA256
CertDumpAnsiX962ECDsaSignatureWithSha384=ANSI X9.62 ECDSA Signature with SHA384
CertDumpAnsiX962ECDsaSignatureWithSha512=ANSI X9.62 ECDSA Signature with SHA512
CertDumpKUSign=Signing
CertDumpKUNonRep=Non-repudiation
CertDumpKUEnc=Key Encipherment
CertDumpKUDEnc=Data Encipherment
CertDumpKUKA=Key Agreement
CertDumpKUCertSign=Certificate Signer
CertDumpKUCRLSigner=CRL Signer
CertDumpCritical=Critical
CertDumpNonCritical=Not Critical
CertDumpSigAlg=Certificate Signature Algorithm
CertDumpCertSig=Certificate Signature Value
CertDumpExtensionFailure=Error: Unable to process extension
CertDumpIsCA=Is a Certificate Authority
CertDumpIsNotCA=Is not a Certificate Authority
CertDumpPathLen=Maximum number of intermediate CAs: %S
CertDumpPathLenUnlimited=unlimited
CertDumpEKU_1_3_6_1_5_5_7_3_1=TLS Web Server Authentication
CertDumpEKU_1_3_6_1_5_5_7_3_2=TLS Web Client Authentication
CertDumpEKU_1_3_6_1_5_5_7_3_3=Code Signing
CertDumpEKU_1_3_6_1_5_5_7_3_4=E-mail protection
CertDumpEKU_1_3_6_1_5_5_7_3_8=Time Stamping
CertDumpEKU_1_3_6_1_5_5_7_3_9=OCSP Signing
CertDumpEKU_1_3_6_1_4_1_311_2_1_21=Microsoft Individual Code Signing
CertDumpEKU_1_3_6_1_4_1_311_2_1_22=Microsoft Commercial Code Signing
CertDumpEKU_1_3_6_1_4_1_311_10_3_1=Microsoft Trust List Signing
CertDumpEKU_1_3_6_1_4_1_311_10_3_2=Microsoft Time Stamping
CertDumpEKU_1_3_6_1_4_1_311_10_3_3=Microsoft Server Gated Crypto
CertDumpEKU_1_3_6_1_4_1_311_10_3_4=Microsoft Encrypting File System
CertDumpEKU_1_3_6_1_4_1_311_10_3_4_1=Microsoft File Recovery
CertDumpEKU_1_3_6_1_4_1_311_10_3_5=Microsoft Windows Hardware Driver Verification
CertDumpEKU_1_3_6_1_4_1_311_10_3_10=Microsoft Qualified Subordination
CertDumpEKU_1_3_6_1_4_1_311_10_3_11=Microsoft Key Recovery
CertDumpEKU_1_3_6_1_4_1_311_10_3_12=Microsoft Document Signing
CertDumpEKU_1_3_6_1_4_1_311_10_3_13=Microsoft Lifetime Signing
CertDumpEKU_1_3_6_1_4_1_311_20_2_2=Microsoft Smart Card Logon
CertDumpEKU_1_3_6_1_4_1_311_21_6=Microsoft Key Recovery Agent
CertDumpMSCerttype=Microsoft Certificate Template Name
CertDumpMSNTPrincipal=Microsoft Principal Name
CertDumpMSCAVersion=Microsoft CA Version
CertDumpMSDomainGUID=Microsoft Domain GUID
CertDumpEKU_2_16_840_1_113730_4_1=Netscape Server Gated Crypto
CertDumpRFC822Name=E-Mail Address
CertDumpDNSName=DNS Name
CertDumpX400Address=X.400 Address
CertDumpDirectoryName=X.500 Name
CertDumpEDIPartyName=EDI Party Name
CertDumpURI=URI
CertDumpIPAddress=IP Address
CertDumpRegisterID=Registered OID
CertDumpKeyID=Key ID
CertDumpVerisignNotices=Verisign User Notices
CertDumpUnused=Unused
CertDumpKeyCompromise=Key Compromise
CertDumpCACompromise=CA Compromise
CertDumpAffiliationChanged=Affiliation Changed
CertDumpSuperseded=Superseded
CertDumpCessation=Cessation of Operation
CertDumpHold=Certificate Hold
CertDumpOCSPResponder=OCSP
CertDumpCAIssuers=CA Issuers
CertDumpCPSPointer=Certification Practice Statement pointer
CertDumpUserNotice=User Notice
CertDumpLogotype=Logotype
CertDumpECPublicKey=Elliptic Curve Public Key
CertDumpECDSAWithSHA1=X9.62 ECDSA Signature with SHA1
CertDumpECprime192v1=ANSI X9.62 elliptic curve prime192v1 (aka secp192r1, NIST P-192)
CertDumpECprime192v2=ANSI X9.62 elliptic curve prime192v2
CertDumpECprime192v3=ANSI X9.62 elliptic curve prime192v3
CertDumpECprime239v1=ANSI X9.62 elliptic curve prime239v1
CertDumpECprime239v2=ANSI X9.62 elliptic curve prime239v2
CertDumpECprime239v3=ANSI X9.62 elliptic curve prime239v3
CertDumpECprime256v1=ANSI X9.62 elliptic curve prime256v1 (aka secp256r1, NIST P-256)
CertDumpECsecp112r1=SECG elliptic curve secp112r1
CertDumpECsecp112r2=SECG elliptic curve secp112r2
CertDumpECsecp128r1=SECG elliptic curve secp128r1
CertDumpECsecp128r2=SECG elliptic curve secp128r2
CertDumpECsecp160k1=SECG elliptic curve secp160k1
CertDumpECsecp160r1=SECG elliptic curve secp160r1
CertDumpECsecp160r2=SECG elliptic curve secp160r2
CertDumpECsecp192k1=SECG elliptic curve secp192k1
CertDumpECsecp224k1=SECG elliptic curve secp224k1
CertDumpECsecp224r1=SECG elliptic curve secp224r1 (aka NIST P-224)
CertDumpECsecp256k1=SECG elliptic curve secp256k1
CertDumpECsecp384r1=SECG elliptic curve secp384r1 (aka NIST P-384)
CertDumpECsecp521r1=SECG elliptic curve secp521r1 (aka NIST P-521)
CertDumpECc2pnb163v1=ANSI X9.62 elliptic curve c2pnb163v1
CertDumpECc2pnb163v2=ANSI X9.62 elliptic curve c2pnb163v2
CertDumpECc2pnb163v3=ANSI X9.62 elliptic curve c2pnb163v3
CertDumpECc2pnb176v1=ANSI X9.62 elliptic curve c2pnb176v1
CertDumpECc2tnb191v1=ANSI X9.62 elliptic curve c2tnb191v1
CertDumpECc2tnb191v2=ANSI X9.62 elliptic curve c2tnb191v2
CertDumpECc2tnb191v3=ANSI X9.62 elliptic curve c2tnb191v3
CertDumpECc2onb191v4=ANSI X9.62 elliptic curve c2onb191v4
CertDumpECc2onb191v5=ANSI X9.62 elliptic curve c2onb191v5
CertDumpECc2pnb208w1=ANSI X9.62 elliptic curve c2pnb208w1
CertDumpECc2tnb239v1=ANSI X9.62 elliptic curve c2tnb239v1
CertDumpECc2tnb239v2=ANSI X9.62 elliptic curve c2tnb239v2
CertDumpECc2tnb239v3=ANSI X9.62 elliptic curve c2tnb239v3
CertDumpECc2onb239v4=ANSI X9.62 elliptic curve c2onb239v4
CertDumpECc2onb239v5=ANSI X9.62 elliptic curve c2onb239v5
CertDumpECc2pnb272w1=ANSI X9.62 elliptic curve c2pnb272w1
CertDumpECc2pnb304w1=ANSI X9.62 elliptic curve c2pnb304w1
CertDumpECc2tnb359v1=ANSI X9.62 elliptic curve c2tnb359v1
CertDumpECc2pnb368w1=ANSI X9.62 elliptic curve c2pnb368w1
CertDumpECc2tnb431r1=ANSI X9.62 elliptic curve c2tnb431r1
CertDumpECsect113r1=SECG elliptic curve sect113r1
CertDumpECsect113r2=SECG elliptic curve sect113r2
CertDumpECsect131r1=SECG elliptic curve sect131r1
CertDumpECsect131r2=SECG elliptic curve sect131r2
CertDumpECsect163k1=SECG elliptic curve sect163k1 (aka NIST K-163)
CertDumpECsect163r1=SECG elliptic curve sect163r1
CertDumpECsect163r2=SECG elliptic curve sect163r2 (aka NIST B-163)
CertDumpECsect193r1=SECG elliptic curve sect193r1
CertDumpECsect193r2=SECG elliptic curve sect193r2
CertDumpECsect233k1=SECG elliptic curve sect233k1 (aka NIST K-233)
CertDumpECsect233r1=SECG elliptic curve sect233r1 (aka NIST B-233)
CertDumpECsect239k1=SECG elliptic curve sect239k1
CertDumpECsect283k1=SECG elliptic curve sect283k1 (aka NIST K-283)
CertDumpECsect283r1=SECG elliptic curve sect283r1 (aka NIST B-283)
CertDumpECsect409k1=SECG elliptic curve sect409k1 (aka NIST K-409)
CertDumpECsect409r1=SECG elliptic curve sect409r1 (aka NIST B-409)
CertDumpECsect571k1=SECG elliptic curve sect571k1 (aka NIST K-571)
CertDumpECsect571r1=SECG elliptic curve sect571r1 (aka NIST B-571)
CertDumpRawBytesHeader=Size: %S Bytes / %S Bits
PK11BadPassword=The password entered was incorrect.
PKCS12DecodeErr=Failed to decode the file.  Either it is not in PKCS #12 format, has been corrupted, or the password you entered was incorrect.
PKCS12UnknownErrRestore=Failed to restore the PKCS #12 file for unknown reasons.
PKCS12UnknownErrBackup=Failed to create the PKCS #12 backup file for unknown reasons.
PKCS12UnknownErr=The PKCS #12 operation failed for unknown reasons.
PKCS12InfoNoSmartcardBackup=It is not possible to back up certificates from a hardware security device such as a smart card.
PKCS12DupData=The certificate and private key already exist on the security device.
AddModuleFailure=Unable to add module
DelModuleWarning=Are you sure you want to delete this security module?
DelModuleError=Unable to delete module
AVATemplate=%S = %S

PSMERR_SSL_Disabled=Can’t connect securely because the SSL protocol has been disabled.
PSMERR_SSL2_Disabled=Can’t connect securely because the site uses an older, insecure version of the SSL protocol.
PSMERR_HostReusedIssuerSerial=You have received an invalid certificate.  Please contact the server administrator or email correspondent and give them the following information:\n\nYour certificate contains the same serial number as another certificate issued by the certificate authority.  Please get a new certificate containing a unique serial number.

SSLConnectionErrorPrefix=An error occurred during a connection to %S.

certErrorIntro=%S uses an invalid security certificate.

certErrorTrust_SelfSigned=The certificate is not trusted because it is self-signed.
certErrorTrust_UnknownIssuer=The certificate is not trusted because the issuer certificate is unknown.
certErrorTrust_UnknownIssuer2=The server might not be sending the appropriate intermediate certificates.
certErrorTrust_UnknownIssuer3=An additional root certificate may need to be imported.
certErrorTrust_CaInvalid=The certificate is not trusted because it was issued by an invalid CA certificate.
certErrorTrust_Issuer=The certificate is not trusted because the issuer certificate is not trusted.
certErrorTrust_SignatureAlgorithmDisabled=The certificate is not trusted because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
certErrorTrust_ExpiredIssuer=The certificate is not trusted because the issuer certificate has expired.
certErrorTrust_Untrusted=The certificate does not come from a trusted source.

certErrorMismatch=The certificate is not valid for the name %S.
# LOCALIZATION NOTE (certErrorMismatchSingle2): Do not translate <a id="cert_domain_link" title="%1$S">%1$S</a>
certErrorMismatchSingle2=The certificate is only valid for <a id="cert_domain_link" title="%1$S">%1$S</a>
certErrorMismatchSinglePlain=The certificate is only valid for %S
certErrorMismatchMultiple=The certificate is only valid for the following names:

# LOCALIZATION NOTE (certErrorExpiredNow): Do not translate %1$S (date+time of expired certificate) or %2$S (current date+time)
certErrorExpiredNow=The certificate expired on %1$S. The current time is %2$S.
# LOCALIZATION NOTE (certErrorNotYetValidNow): Do not translate %1$S (date+time certificate will become valid) or %2$S (current date+time)
certErrorNotYetValidNow=The certificate will not be valid until %1$S. The current time is %2$S.

# LOCALIZATION NOTE (certErrorCodePrefix2): Do not translate <a id="errorCode" title="%1$S">%1$S</a>
certErrorCodePrefix2=Error code: <a id="errorCode" title="%1$S">%1$S</a>

P12DefaultNickname=Imported Certificate
CertUnknown=Unknown
CertNoEmailAddress=(no email address)
CaCertExists=This certificate is already installed as a certificate authority.
NotACACert=This is not a certificate authority certificate, so it can’t be imported into the certificate authority list.
NotImportingUnverifiedCert=This certificate can’t be verified and will not be imported. The certificate issuer might be unknown or untrusted, the certificate might have expired or been revoked, or the certificate might not have been approved.
UserCertIgnoredNoPrivateKey=This personal certificate can’t be installed because you do not own the corresponding private key which was created when the certificate was requested.
UserCertImported=Your personal certificate has been installed. You should keep a backup copy of this certificate.
CertOrgUnknown=(Unknown)
CertNotStored=(Not Stored)
CertExceptionPermanent=Permanent
CertExceptionTemporary=Temporary
PK
!<ero0chrome/en-US/locale/en-US/pippki/certManager.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY certmgr.title                       "Certificate Manager">

<!ENTITY certmgr.tab.mine                     "Your Certificates">
<!ENTITY certmgr.tab.others2                  "People">
<!ENTITY certmgr.tab.websites3                "Servers">
<!ENTITY certmgr.tab.ca                       "Authorities">
<!ENTITY certmgr.tab.orphan2                  "Others">

<!ENTITY certmgr.mine                         "You have certificates from these organizations that identify you:">
<!ENTITY certmgr.others                       "You have certificates on file that identify these people:">
<!ENTITY certmgr.websites2                    "You have certificates on file that identify these servers:">
<!ENTITY certmgr.cas                          "You have certificates on file that identify these certificate authorities:">
<!ENTITY certmgr.orphans                      "You have certificates on file that do not fit in any of the other categories:">

<!ENTITY certmgr.detail.general_tab.title     "General">
<!ENTITY certmgr.detail.general_tab.accesskey "G">
<!ENTITY certmgr.detail.prettyprint_tab.title "Details">
<!ENTITY certmgr.detail.prettyprint_tab.accesskey "D">

<!ENTITY certmgr.pending.label                "Currently verifying certificate…">
<!ENTITY certmgr.subjectinfo.label            "Issued To">
<!ENTITY certmgr.issuerinfo.label             "Issued By">
<!ENTITY certmgr.periodofvalidity.label       "Period of Validity" >
<!ENTITY certmgr.fingerprints.label           "Fingerprints">
<!ENTITY certmgr.certdetail.title             "Certificate Detail">
<!ENTITY certmgr.certdetail.cn                "Common Name (CN)">
<!ENTITY certmgr.certdetail.o                 "Organization (O)">
<!ENTITY certmgr.certdetail.ou                "Organizational Unit (OU)">
<!ENTITY certmgr.certdetail.serialnumber      "Serial Number">
<!ENTITY certmgr.certdetail.sha256fingerprint "SHA-256 Fingerprint">
<!ENTITY certmgr.certdetail.sha1fingerprint   "SHA1 Fingerprint">

<!ENTITY certmgr.editcacert.title             "Edit CA certificate trust settings">
<!ENTITY certmgr.editcert.edittrust           "Edit trust settings:">
<!ENTITY certmgr.editcert.trustssl            "This certificate can identify websites.">
<!ENTITY certmgr.editcert.trustemail          "This certificate can identify mail users.">
<!ENTITY certmgr.editcert.trustobjsign        "This certificate can identify software makers.">

<!ENTITY certmgr.deletecert.title             "Delete Certificate">

<!ENTITY certmgr.certname                     "Certificate Name">
<!ENTITY certmgr.certserver                   "Server">
<!ENTITY certmgr.override_lifetime            "Lifetime">
<!ENTITY certmgr.tokenname                    "Security Device">
<!ENTITY certmgr.begins                       "Begins On">
<!ENTITY certmgr.expires                      "Expires On">
<!ENTITY certmgr.email                        "E-Mail Address">
<!ENTITY certmgr.serial                       "Serial Number">

<!ENTITY certmgr.close.label                  "Close">
<!ENTITY certmgr.close.accesskey              "C">
<!ENTITY certmgr.view2.label                  "View…">
<!ENTITY certmgr.view2.accesskey              "V">
<!ENTITY certmgr.edit3.label                  "Edit Trust…">
<!ENTITY certmgr.edit3.accesskey              "E">
<!ENTITY certmgr.export.label                 "Export…">
<!ENTITY certmgr.export.accesskey             "x">
<!ENTITY certmgr.delete2.label                "Delete…">
<!ENTITY certmgr.delete2.accesskey            "D">
<!ENTITY certmgr.delete_builtin.label         "Delete or Distrust…">
<!ENTITY certmgr.delete_builtin.accesskey     "D">
<!ENTITY certmgr.backup2.label                "Backup…">
<!ENTITY certmgr.backup2.accesskey            "B">
<!ENTITY certmgr.backupall2.label             "Backup All…">
<!ENTITY certmgr.backupall2.accesskey         "k">
<!ENTITY certmgr.restore2.label               "Import…">
<!ENTITY certmgr.restore2.accesskey           "m">
<!ENTITY certmgr.details.label                "Certificate Fields">
<!ENTITY certmgr.details.accesskey            "F">
<!ENTITY certmgr.fields.label                 "Field Value">
<!ENTITY certmgr.fields.accesskey             "V">
<!ENTITY certmgr.hierarchy.label              "Certificate Hierarchy">
<!ENTITY certmgr.hierarchy.accesskey2         "H">
<!ENTITY certmgr.addException.label           "Add Exception…">
<!ENTITY certmgr.addException.accesskey       "x">

<!ENTITY exceptionMgr.title                   "Add Security Exception">
<!ENTITY exceptionMgr.exceptionButton.label   "Confirm Security Exception">
<!ENTITY exceptionMgr.exceptionButton.accesskey "C">
<!ENTITY exceptionMgr.supplementalWarning     "Legitimate banks, stores, and other public sites will not ask you to do this.">
<!ENTITY exceptionMgr.certlocation.caption2   "Server">
<!ENTITY exceptionMgr.certlocation.url        "Location:">
<!ENTITY exceptionMgr.certlocation.download   "Get Certificate">
<!ENTITY exceptionMgr.certlocation.accesskey  "G">
<!ENTITY exceptionMgr.certstatus.caption      "Certificate Status">
<!ENTITY exceptionMgr.certstatus.viewCert     "View…">
<!ENTITY exceptionMgr.certstatus.accesskey    "V">
<!ENTITY exceptionMgr.permanent.label         "Permanently store this exception">
<!ENTITY exceptionMgr.permanent.accesskey     "P">
PK
!<r]2chrome/en-US/locale/en-US/pippki/deviceManager.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY devmgr.title                           "Device Manager">
<!-- LOCALIZATION NOTE (devmgr.style2): This is CSS style for Device Manager
     window size. Don't translate "width" nor "height". Adjust the numbers
     to make window contents fit. -->
<!ENTITY devmgr.style2                          "width: 67em; height: 32em;">

<!ENTITY devmgr.devlist.label                   "Security Modules and Devices">
<!ENTITY devmgr.details.title                   "Details">
<!ENTITY devmgr.details.title2                  "Value">

<!ENTITY devmgr.button.login.label              "Log In">
<!ENTITY devmgr.button.login.accesskey          "n">
<!ENTITY devmgr.button.logout.label             "Log Out">
<!ENTITY devmgr.button.logout.accesskey         "O">
<!ENTITY devmgr.button.changepw.label           "Change Password">
<!ENTITY devmgr.button.changepw.accesskey       "P">
<!ENTITY devmgr.button.load.label               "Load">
<!ENTITY devmgr.button.load.accesskey           "L">
<!ENTITY devmgr.button.unload.label             "Unload">
<!ENTITY devmgr.button.unload.accesskey         "U">
<!ENTITY devmgr.button.fips.accesskey           "F">

<!ENTITY loaddevice.info                        "Enter the information for the module you want to add.">
<!ENTITY loaddevice.modname                     "Module Name:">
<!ENTITY loaddevice.modname.accesskey           "M">
<!ENTITY loaddevice.modname.default             "New PKCS#11 Module">
<!ENTITY loaddevice.filename                    "Module filename:">
<!ENTITY loaddevice.filename.accesskey          "f">
<!ENTITY loaddevice.browse                      "Browse…">
<!ENTITY loaddevice.browse.accesskey            "B">

<!ENTITY loaddevice.title2                      "Load PKCS#11 Device Driver">
PK
!<ahD+chrome/en-US/locale/en-US/pippki/pippki.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- Values for changepassword.xul -->
<!ENTITY setPassword.title  "Change Master Password">
<!ENTITY setPassword.tokenName.label "Security Device">
<!ENTITY setPassword.oldPassword.label "Current password:">
<!ENTITY setPassword.newPassword.label "New password:">
<!ENTITY setPassword.reenterPassword.label "New password (again):">
<!ENTITY setPassword.meter.label "Password quality meter">

<!-- Values for resetpassword.xul -->
<!ENTITY resetPasswordButtonLabel "Reset">
<!ENTITY resetPassword.title  "Reset Master Password">
<!ENTITY resetPassword.text  "If you reset your master password, all your stored web and e-mail passwords, form data, personal certificates, and private keys will be forgotten. Are you sure you want to reset your master password?">

<!-- Downloading a cert -->
<!ENTITY downloadCert.title "Downloading Certificate">
<!ENTITY downloadCert.message1 "You have been asked to trust a new Certificate Authority (CA).">
<!ENTITY downloadCert.trustSSL "Trust this CA to identify websites.">
<!ENTITY downloadCert.trustEmail "Trust this CA to identify email users.">
<!ENTITY downloadCert.trustObjSign "Trust this CA to identify software developers.">
<!ENTITY downloadCert.message3 "Before trusting this CA for any purpose, you should examine its certificate and its policy and procedures (if available).">
<!ENTITY downloadCert.viewCert.label "View">
<!ENTITY downloadCert.viewCert.text "Examine CA certificate">

<!-- Strings for the SSL client auth ask dialog -->
<!ENTITY clientAuthAsk.title "User Identification Request">
<!ENTITY clientAuthAsk.message1 "This site has requested that you identify yourself with a certificate:">
<!ENTITY clientAuthAsk.message2 "Choose a certificate to present as identification:">
<!ENTITY clientAuthAsk.message3 "Details of selected certificate:">

<!ENTITY pkcs12.setpassword.title  "Choose a Certificate Backup Password">
<!ENTITY pkcs12.setpassword.message  "The certificate backup password you set here protects the backup file that you are about to create.  You must set this password to proceed with the backup.">
<!ENTITY pkcs12.setpassword.label1 "Certificate backup password:">
<!ENTITY pkcs12.setpassword.label2 "Certificate backup password (again):">
<!ENTITY pkcs12.setpassword.reminder "Important: If you forget your certificate backup password, you will not be able to restore this backup later.  Please record it in a safe location.">

<!ENTITY chooseToken.title  "Choose Token Dialog">
<!ENTITY chooseToken.message1 "Please choose a token.">

<!-- Strings for the CreateCertInfo dialog  -->
<!ENTITY createCertInfo.title "Generating A Private Key">
<!ENTITY createCertInfo.msg1 "Key Generation in progress… This may take a few minutes….">
<!ENTITY createCertInfo.msg2 "Please wait…">

<!-- Strings for protectedAuth dialog -->
<!ENTITY protectedAuth.title "Protected Token Authentication">
<!ENTITY protectedAuth.msg "Please authenticate to the token. Authentication method depends on the type of your token.">
<!ENTITY protectedAuth.tokenName.label "Token:">
PK
!<[))2chrome/en-US/locale/en-US/pippki/pippki.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

CertPassPrompt=Please enter the Personal Security Password for the PSM Private Keys security device.

# LOCALIZATION NOTE(certWithSerial): Used for semi-uniquely representing a cert.
# %1$S is the serial number of the cert in AA:BB:CC hex format.
certWithSerial=Certificate with serial number: %1$S

# Download Cert dialog
# LOCALIZATION NOTE(newCAMessage1):
# %S is a string representative of the certificate being downloaded/imported.
newCAMessage1=Do you want to trust “%S” for the following purposes?
unnamedCA=Certificate Authority (unnamed)

# For editing cert trust
editTrustCA=The certificate “%S” represents a Certificate Authority.

# For Deleting Certificates
deleteSslCertConfirm3=Are you sure you want to delete these server exceptions?
deleteSslCertImpact3=If you delete a server exception, you restore the usual security checks for that server and require it uses a valid certificate.
deleteSslCertTitle3=Delete Server Certificate Exceptions

deleteUserCertConfirm=Are you sure you want to delete these certificates?
deleteUserCertImpact=If you delete one of your own certificates, you can no longer use it to identify yourself.
deleteUserCertTitle=Delete your Certificates

deleteCaCertConfirm2=You have requested to delete these CA certificates. For built-in certificates all trust will be removed, which has the same effect. Are you sure you want to delete or distrust?
deleteCaCertImpactX2=If you delete or distrust a certificate authority (CA) certificate, this application will no longer trust any certificates issued by that CA.
deleteCaCertTitle2=Delete or Distrust CA Certificates

deleteEmailCertConfirm=Are you sure you want to delete these people’s e-mail certificates?
deleteEmailCertImpactDesc=If you delete a person’s e-mail certificate, you will no longer be able to send encrypted e-mail to that person.
deleteEmailCertTitle=Delete E-Mail Certificates

deleteOrphanCertConfirm=Are you sure you want to delete these certificates?
deleteOrphanCertTitle=Delete Certificates

# PKCS#12 file dialogs
chooseP12RestoreFileDialog2=Certificate File to Import
chooseP12BackupFileDialog=File Name to Backup
file_browse_PKCS12_spec=PKCS12 Files
getPKCS12FilePasswordMessage=Please enter the password that was used to encrypt this certificate backup:

# Cert verification
certVerified=This certificate has been verified for the following uses:
certNotVerified_CertRevoked=Could not verify this certificate because it has been revoked.
certNotVerified_CertExpired=Could not verify this certificate because it has expired.
certNotVerified_CertNotTrusted=Could not verify this certificate because it is not trusted.
certNotVerified_IssuerNotTrusted=Could not verify this certificate because the issuer is not trusted.
certNotVerified_IssuerUnknown=Could not verify this certificate because the issuer is unknown.
certNotVerified_CAInvalid=Could not verify this certificate because the CA certificate is invalid.
certNotVerified_AlgorithmDisabled=Could not verify this certificate because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
certNotVerified_Unknown=Could not verify this certificate for unknown reasons.

# Client auth
clientAuthRemember=Remember this decision
# LOCALIZATION NOTE(clientAuthNickAndSerial): Represents a single cert when the
# user is choosing from a list of certificates.
# %1$S is the nickname of the cert.
# %2$S is the serial number of the cert in AA:BB:CC hex format.
clientAuthNickAndSerial=%1$S [%2$S]
# LOCALIZATION NOTE(clientAuthHostnameAndPort):
# %1$S is the hostname of the server.
# %2$S is the port of the server.
clientAuthHostnameAndPort=%1$S:%2$S
# LOCALIZATION NOTE(clientAuthMessage1): %S is the Organization of the server
# cert.
clientAuthMessage1=Organization: “%S”
# LOCALIZATION NOTE(clientAuthMessage2): %S is the Organization of the issuer
# cert of the server cert.
clientAuthMessage2=Issued Under: “%S”
# LOCALIZATION NOTE(clientAuthIssuedTo): %1$S is the Distinguished Name of the
# currently selected client cert, such as "CN=John Doe,OU=Example" (without
# quotes).
clientAuthIssuedTo=Issued to: %1$S
# LOCALIZATION NOTE(clientAuthSerial): %1$S is the serial number of the selected
# cert in AA:BB:CC hex format.
clientAuthSerial=Serial number: %1$S
# LOCALIZATION NOTE(clientAuthValidityPeriod):
# %1$S is the already localized notBefore date of the selected cert.
# %2$S is the already localized notAfter date of the selected cert.
clientAuthValidityPeriod=Valid from %1$S to %2$S
# LOCALIZATION NOTE(clientAuthKeyUsages): %1$S is a comma separated list of
# already localized key usages the selected cert is valid for.
clientAuthKeyUsages=Key Usages: %1$S
# LOCALIZATION NOTE(clientAuthEmailAddresses): %1$S is a comma separated list of
# e-mail addresses the selected cert is valid for.
clientAuthEmailAddresses=Email addresses: %1$S
# LOCALIZATION NOTE(clientAuthIssuedBy): %1$S is the Distinguished Name of the
# cert which issued the selected cert.
clientAuthIssuedBy=Issued by: %1$S
# LOCALIZATION NOTE(clientAuthStoredOn): %1$S is the name of the PKCS #11 token
# the selected cert is stored on.
clientAuthStoredOn=Stored on: %1$S

# Page Info
pageInfo_NoEncryption=Connection Not Encrypted
pageInfo_Privacy_None1=The website %S does not support encryption for the page you are viewing.
pageInfo_Privacy_None2=Information sent over the Internet without encryption can be seen by other people while it is in transit. 
pageInfo_Privacy_None4=The page you are viewing was not encrypted before being transmitted over the Internet.
# LOCALIZATION NOTE (pageInfo_EncryptionWithBitsAndProtocol and pageInfo_BrokenEncryption):
# %1$S is the name of the encryption standard,
# %2$S is the key size of the cipher.
# %3$S is protocol version like "SSL 3" or "TLS 1.2"
pageInfo_EncryptionWithBitsAndProtocol=Connection Encrypted (%1$S, %2$S bit keys, %3$S)
pageInfo_BrokenEncryption=Broken Encryption (%1$S, %2$S bit keys, %3$S)
pageInfo_Privacy_Encrypted1=The page you are viewing was encrypted before being transmitted over the Internet.
pageInfo_Privacy_Encrypted2=Encryption makes it difficult for unauthorized people to view information traveling between computers. It is therefore unlikely that anyone read this page as it traveled across the network.
pageInfo_MixedContent=Connection Partially Encrypted
pageInfo_MixedContent2=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
pageInfo_WeakCipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website’s behavior.
pageInfo_CertificateTransparency_Compliant=This website complies with the Certificate Transparency policy.

# Cert Viewer
# LOCALIZATION NOTE(certViewerTitle): Title used for the Certificate Viewer.
# %1$S is a string representative of the certificate being viewed.
certViewerTitle=Certificate Viewer: “%1$S”
notPresent=<Not Part Of Certificate>

# Token Manager
password_not_set=(not set)
failed_pw_change=Unable to change Master Password.
incorrect_pw=You did not enter the correct current Master Password. Please try again.
pw_change_ok=Master Password successfully changed.
pw_erased_ok=Warning! You have deleted your Master Password. 
pw_not_wanted=Warning! You have decided not to use a Master Password.
pw_empty_warning=Your stored web and email passwords, form data, and private keys will not be protected.
pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
login_failed=Failed to Login
loadPK11ModuleFilePickerTitle=Choose a PKCS#11 device driver to load
devinfo_modname=Module
devinfo_modpath=Path
devinfo_label=Label
devinfo_manID=Manufacturer
devinfo_serialnum=Serial Number
devinfo_hwversion=HW Version
devinfo_fwversion=FW Version
devinfo_status=Status
devinfo_desc=Description
devinfo_stat_disabled=Disabled
devinfo_stat_notpresent=Not Present
devinfo_stat_uninitialized=Uninitialized
devinfo_stat_notloggedin=Not Logged In
devinfo_stat_loggedin=Logged In
devinfo_stat_ready=Ready
enable_fips=Enable FIPS
disable_fips=Disable FIPS
fips_nonempty_password_required=FIPS mode requires that you have a Master Password set for each security device. Please set the password before trying to enable FIPS mode.
unable_to_toggle_fips=Unable to change the FIPS mode for the security device. It is recommended that you exit and restart this application.

resetPasswordConfirmationTitle=Reset Master Password
resetPasswordConfirmationMessage=Your password has been reset.

# Import certificate(s) file dialog
importEmailCertPrompt=Select File containing somebody’s Email certificate to import
importCACertsPrompt=Select File containing CA certificate(s) to import
file_browse_Certificate_spec=Certificate Files

# Cert export
SaveCertAs=Save Certificate To File
CertFormatBase64=X.509 Certificate (PEM)
CertFormatBase64Chain=X.509 Certificate with chain (PEM)
CertFormatDER=X.509 Certificate (DER)
CertFormatPKCS7=X.509 Certificate (PKCS#7)
CertFormatPKCS7Chain=X.509 Certificate with chain (PKCS#7)
writeFileFailure=File Error
writeFileFailed=Can’t write to file %S:\n%S.
writeFileAccessDenied=Access denied
writeFileIsLocked=File is locked
writeFileNoDeviceSpace=No space left on device
writeFileUnknownError=Unknown error

# Add Security Exception dialog
addExceptionBrandedWarning2=You are about to override how %S identifies this site.
addExceptionInvalidHeader=This site attempts to identify itself with invalid information.
addExceptionDomainMismatchShort=Wrong Site
addExceptionDomainMismatchLong2=The certificate belongs to a different site, which could mean that someone is trying to impersonate this site.
addExceptionExpiredShort=Outdated Information
addExceptionExpiredLong2=The certificate is not currently valid. It may have been stolen or lost, and could be used by someone to impersonate this site.
addExceptionUnverifiedOrBadSignatureShort=Unknown Identity
addExceptionUnverifiedOrBadSignatureLong2=The certificate is not trusted because it hasn’t been verified as issued by a trusted authority using a secure signature.
addExceptionValidShort=Valid Certificate
addExceptionValidLong=This site provides valid, verified identification.  There is no need to add an exception.
addExceptionCheckingShort=Checking Information
addExceptionCheckingLong2=Attempting to identify this site…
addExceptionNoCertShort=No Information Available
addExceptionNoCertLong2=Unable to obtain identification status for this site.
PK
!<=eSS2chrome/en-US/locale/en-US/places/places.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

BookmarksMenuFolderTitle=Bookmarks Menu
BookmarksToolbarFolderTitle=Bookmarks Toolbar
OtherBookmarksFolderTitle=Other Bookmarks
TagsFolderTitle=Tags
MobileBookmarksFolderTitle=Mobile Bookmarks

# LOCALIZATION NOTE (dateName):
# These are used to generate history containers when history is grouped by date
finduri-AgeInDays-is-0=Today
finduri-AgeInDays-is-1=Yesterday
finduri-AgeInDays-is=%S days ago
finduri-AgeInDays-last-is=Last %S days
finduri-AgeInDays-isgreater=Older than %S days
finduri-AgeInMonths-is-0=This month
finduri-AgeInMonths-isgreater=Older than %S months

# LOCALIZATION NOTE (localFiles):
# This is used to generate local files container when history is grouped by site
localhost=(local files)

# LOCALIZATION NOTE
# The string is used for showing file size of each backup in the "fileRestorePopup" popup
# %1$S is the file size
# %2$S is the file size unit
backupFileSizeText=%1$S %2$S
PK
!<vd
d
9chrome/en-US/locale/en-US/pluginproblem/pluginproblem.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- LOCALIZATION NOTE (tapToPlayPlugin): Mobile (used for touch interfaces) only has one type of plugin possible. -->
<!ENTITY pluginActivationWarning                             "This site uses a plugin that may slow &brandShortName;.">
<!ENTITY tapToPlayPlugin                                     "Tap here to activate plugin.">
<!ENTITY clickToActivatePlugin                               "Activate plugin.">
<!ENTITY checkForUpdates                                     "Check for updates…">
<!ENTITY blockedPlugin.label                                 "This plugin has been blocked for your protection.">
<!ENTITY hidePluginBtn.label                                 "Hide plugin">
<!ENTITY managePlugins                                       "Manage plugins…">

<!-- LOCALIZATION NOTE (reloadPlugin.pre): include a trailing space as needed -->
<!-- LOCALIZATION NOTE (reloadPlugin.middle): avoid leading/trailing spaces, this text is a link -->
<!-- LOCALIZATION NOTE (reloadPlugin.post): include a starting space as needed -->
<!ENTITY reloadPlugin.pre                                    "">
<!ENTITY reloadPlugin.middle                                 "Reload the page">
<!ENTITY reloadPlugin.post                                   " to try again.">
<!-- LOCALIZATION NOTE (report.please): This and the other report.* strings should be as short as possible, ideally 2-3 words. -->
<!ENTITY report.please                                       "Send crash report">
<!ENTITY report.submitting                                   "Sending report…">
<!ENTITY report.submitted                                    "Crash report sent.">
<!ENTITY report.disabled                                     "Crash reporting disabled.">
<!ENTITY report.failed                                       "Submission failed.">
<!ENTITY report.unavailable                                  "No report available.">
<!ENTITY report.comment                                      "Add a comment (comments are publicly visible)">
<!ENTITY report.pageURL                                      "Include the page’s URL">

<!ENTITY plugin.file                                         "File">
<!ENTITY plugin.mimeTypes                                    "MIME Types">
<!ENTITY plugin.flashProtectedMode.label                     "Enable Adobe Flash protected mode">
<!ENTITY plugin.enableBlocklists.label                       "Block dangerous and intrusive Flash content">
PK
!<foo2chrome/en-US/locale/en-US/services/sync.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# %1: the user name (Ed), %2: the app name (Firefox), %3: the operating system (Android)
client.name2 = %1$S’s %2$S on %3$S

# %S is the date and time at which the last sync successfully completed
lastSync2.label = Last sync: %S

# signInToSync.description is the tooltip for the Sync buttons when Sync is
# not configured.
signInToSync.description = Sign In To Sync

syncnow.label = Sync Now
syncing2.label = Syncing…
PK
!<C`/`/hyphenation/hyph_af.dicUTF-8
LEFTHYPHENMIN 1
RIGHTHYPHENMIN 2
.aa2n5s4
.aa2
.aan1
.aä7lawa
.a1ä
.aäla1w
.a6b2c-1b2
.a1b2
.ab1c2
.abc2-1
.a4b5la
.ab7sa1lo
.a2b1s2
.ab1sa
.a6b-ja
.ab2-1
.ab-1j
.ac7cra.
.a1c2
.a2c2c2
.a6fa2r1m
.a1fa
.af7ar2m.
.a6fee1t
.a1fe
.af7ee2t.
.a4f5en
.a6fe2t1s2
.afe1t
.af7et2s.
.a6foes2
.a1fo
.af7oe2s.
.a4f5oo2
.a4f5ra
.a1f2r
.af6ro'
.a4f3ro
.af6ro’
.a7fro's2
.a7fro’s2
.af6ro2-1
.a7fro-1h2
.a6fry.
.af3ry
.a2f3s2
.ag6aa2m1
.ag3aa2
.a1ga
.agte6r5
.a2g1t
.a6g5uur1
.a1gu
.aguu2
.a9ha.
.a1h2
.ah7le2rs2
.a2hl
.ah1le
.a3k2w
.a6lee2r
.a1le
.alee2
.al7ee2r.
.alf4
.al7fa1g2r
.al1fa
.al2f1ag
.al5f3r
.al6lda
.a2l1l
.all1d
.a4l3o
.al6oïe
.alo1ï
.a7loïen
.al3p
.al5s1t
.a2l1s2
.al7t6he1a
.al1t
.al2t1h2
.al7t6wee
.al2t1w
.al6zhe
.al1z
.alz1h2
.am2p4s2
.a2m
.a2m1p
.amp2s5w
.a6naër
.a1na
.ana1ë1
.an7aëro
.an6cp2l
.an1c2
.anc1p
.a2n1d4
.an5d2r
.a2ng4
.an5g1l
.an2gs5
.a4n5i1o
.a1ni
.an7thro
.a2n1t
.an2t1h2
.ant2hr
.a3p2r
.a2p
.ap7si3de
.a2p1s2
.ap4si2d
.ap1si
.a5rag
.a1ra
.ara6p.
.ara3p4
.ar7t6hur
.a2r1t
.ar2t1h2
.ar6zbe
.ar1z
.arz1b2
.as7ja2s.
.as2
.a1s1j
.a2sja
.asjas2
.a6sno1g
.as3no
.a1sn
.a6s9o2f.
.a1so
.a5s3ti
.as3t
.a7s4traa2
.ast2r
.a7s6tral
.at6hol
.a1t
.a2t1h2
.a7tho2l.
.a5t4s1j
.a2t1s2
.atte4
.a2t1t
.au7d6rey
.au4d3re
.au1d
.aud2r
.b6aa2n1v2
.b2
.baa2
.baan1
.ba6din
.ba2d
.ba3di
.ba4d5o
.ba7loi.
.ba4lo
.baloi1
.ba7ra2g1w
.ba1ra
.ba2rag
.ba7ri2n1s2
.ba1r2i
.ba6sek
.bas2
.ba1se
.ba7t4ho.
.ba1t
.ba2t1h2
.be7de2k1s2
.be3de
.be1d
.be6kaf
.be1ka
.bek7a2f.
.be5la
.be7lo2l.
.be1lo
.be4lol
.be7s3kos2
.be1s4k2
.bes2
.bes1ko
.be7thel
.be2t4h2
.be1t
.be7thul
.bi7s6ho.
.bis2
.bi2s1h2
.bis3ho
.b1li4
.blus5
.bo7k6erf
.bo2k1
.bo1ke
.bo7k6ies2
.boki2e4
.bo1ki
.bo7k6o2rs2
.bo1ko
.bo7m6aa1t1
.bo4m5aa2
.bo2m
.bo3ma
.b4on
.bo7p6laa2
.bo2p
.bop3l
.bo5ro
.bo7so2r.
.bo2s2
.bo4sor
.bo1so
.bo5s4ta
.bos3t
.bo7tri2t
.bo2t
.bot2r
.bot1r2i
.bo7tswa
.bo2t1s2
.bot2s1w
.bo7u6i2t.
.bou1i
.boui2t
.bout5j
.bou2t
.b4re
.bu6e1no
.bu1e
.bu6lol
.bu1lo
.bu7t6hel
.bu2t
.bu2t1h2
.by6ld2r
.by1
.byl1d
.by6lho
.byl1h2
.by6l1ne
.byl1n
.by6lpi
.byl1p
.by7po2r1t
.by2p
.by3s4
.by6tal
.by2t
.by3ta
.ca7t4hy.
.c2
.ca2t4h2
.ca1t
.ca7y4en1n
.cay1e
.chlo7e.
.c1h2
.c2hl
.ci6rca
.ci1r
.cir1c2
.ci7trus2
.ci2t
.cit2r
.co4s7ta.
.cos3t
.cos4ta
.cos2
.cy6p1r2i
.cy2p
.cy3p2r
.d2
.da7go2n.
.da2g
.da1go
.da2g5s2
.da6ka1t
.da1ka
.da6koo2
.da1ko
.da7t4a1ge
.da4tag
.da1t
.da6tji
.da3t4j
.dat7jie
.da6w1ki
.da1w
.de6k7laa2
.de1k2l
.de6klo
.de6kwe
.de1k2w
.de5la
.de7ro1ga
.de4ro
.dero1g
.de6sal
.des2
.de1sa
.de6sok
.de1so
.de4s1p
.die4p5l
.die1p
.di6j2k1s2
.di1j
.di4si
.dis2
.di7t6hak
.di2t
.di2t1h2
.do4m5a
.do2m
.do4m5o
.dor7ste.
.dor4s1t
.do2rs2
.dr6oef
.d2r
.du2n5s3
.du6p1re
.du1p
.dup2r
.dut5j
.du2t
.dy7spie
.dys2
.dys1p
.e6b9cu.
.e1b2
.eb1c2
.ed5wa
.e2d2w
.e1d
.ed7wi2n.
.eer6sk2
.ee2r
.ee2rs2
.ee4t
.e6fron
.e1f2r
.ef7ro2n.
.eg7go1fo
.e2g1g2
.eg1go
.eg2g1of
.e6indu
.ei2n1d
.ei5s3t
.eis2
.ek4s5k2
.e2k1s2
.ek7so3pa
.ek1so
.ek2s1o2p
.ek7sor1d
.eks7t1r2i
.eks1t
.ekst2r
.eks7tro
.en7dres2
.e2n1d
.en4d3re
.end2r
.e2n1k4
.en5k2l
.e6no2f1t
.e1no
.en7ofta
.en4t5j
.e2n1t
.en7to2p1t
.en4to2p
.ep7s6o2m1s2
.e1p
.e2p1s2
.ep1so
.epso2m
.er4d5a
.er1d
.er6dwo
.er3d1w
.er6f2le
.er6foo2
.er1fo
.er6i2n1v2
.e1r2i
.e2r1n4
.e2r4t4
.er5te
.ert5j
.ert7se.
.er2t1s2
.ert1se
.ert2s5w
.e2s2
.e9s2au
.e1sa
.e4s3k2
.e3so
.e1s3p
.es8p.
.es6pma
.es2p1m
.e1s3t
.es6tco
.est1c2
.es6t1ni
.es2t1n
.es5t2r
.e7t4age.
.e4t3ag
.e1t
.et4a1ge
.et4sn
.e2t1s2
.eu4r5a
.eu7s6ta1c2
.eus3t
.eus2
.ex7odus2
.exo1d
.e6z9ra.
.e1z
.f2
.fo6ch1v2
.fo1c2
.foc1h2
.fo6w1le
.fo1w
.f4r2i
.f2r
.fy6tji
.fy2t
.fy3t2j
.g2
.ga7la1ge
.ga1la
.ga2lag
.ga7l4a1go
.ga6la2p
.ga6loo2
.ga1lo
.g6arbo
.g3ar1b2
.ga6sen
.gas2
.ga1se
.ge7da2r1t
.ge3d
.ge3g2
.ge7ge2k1s2
.ge3ge
.ge7g6u2il
.ge4g3ui
.ge1gu
.ge3kun5
.ge1ku
.geku2n2s6
.ge5la
.ge7m6o2p1p
.ge2m3o2p
.ge1m
.ge7mui2t
.ge7nève2
.genè1v2
.ge7r6o2g1g2
.ge1ro
.gero1g
.ges4
.ge7sja1b2
.ge2s1j
.ge2sja
.ge7sjar
.ge5s4k2
.ge5so
.ge4s7pe.
.ge3s1p
.ge7spe2r.
.ge4sper
.ge7steg
.ge3s1t
.gif3
.gi7g4a1g2r
.gi1ga
.gi2g1ag
.gi6sen
.gis2
.gi1se
.g1ly3
.gly5k
.g6ly2n1a
.g3lyn
.g4oo2
.gou7d6a.
.gou4d3
.gr6äbe
.g2r
.gr1ä
.grä1b2
.g6ruba
.g3ru1b2
.gui7d6o.
.gui4d3o
.gui2d
.han2g5s2
.h2
.ha2ng
.he6blu
.he1b2
.he6gor
.he1go
.he6gra
.he1g2r
.h6ei2n1d
.he4k5o
.he7r6a2k3l
.he2r
.he1r3a
.he6r5en
.he3re
.he6w1le
.he1w
.hi8v.
.hi1v2
.ho6fe1t
.ho1fe
.ho6laa2
.ho1la
.ho6loo2
.ho1lo
.hooi5
.hoo2
.ho7taze
.ho4ta
.ho2t
.hota1z
.ho4t5o
.hy6gro
.hy1g
.hy1g2r
.ic7te1ru
.i1c2
.i2c1t
.i4g1l
.ile7u2s.
.i1le
.ileus2
.i2n1
.i9n8a.
.i1na
.in6a1r2i
.i7narie
.i2n1d4
.in7dwar
.in2d1w
.i2ng4
.in5g2r
.in5g1w
.in6i3ti
.i3ni
.ini2t
.in5k2l
.i2n1k
.in6k3ly
.in5k2n
.in5k2w
.in6o3si
.i1no
.inos2
.i7no3sie
.i2n3s4
.in7snee2
.in4s1ne
.in1sn
.in7t6wyf
.i2n1t
.in2t1w
.i5raa2
.i1r
.i3sa
.is2
.i4s3k2
.i3so
.ja6g1li
.jag1l
.jah7we.
.ja1h2
.ja2h1w
.ja6spa
.jas3p
.jas2
.ja7ta1ga
.ja4tag
.ja1t
.j6ä3ger
.j1ä
.jä1ge
.je7so2p1p
.jes2
.je1so
.je2s1o2p
.jo7do1fo
.jo1d
.jo2dof
.jo7s6a1fa
.jos2
.jo1sa
.jo2s3af
.ju6kos2
.ju1ko
.juk7o2s.
.j6ü6r1ge
.j1ü1
.jür1g
.jy6sel
.jys2
.jy1se
.k2
.kaar4
.kaa2
.ka3de4
.ka2d
.kade1t5
.k4af
.kaï7ro.
.ka1ï
.ka7n6o2n1t
.ka1no
.ka6pla
.ka2p
.kap3l
.ka7p6lak
.k6arbe
.kar1b2
.ka7t6har
.ka1t
.ka2t1h2
.ka7t6hu.
.ka6to1ë
.kat7oë.
.ka6tui
.ke6p1le
.ke1p
.kep2l
.ker6k5a
.ke2r1k
.ker6k5l
.ker4k5r
.ker6sa
.ke2rs2
.ker6sl
.ker6s5p
.ke4s5t
.kes2
.kie6s1t
.ki2e
.kies2
.ki6p1li
.ki2p
.ki3p2l
.ki4r
.kit7se.
.ki2t4s2
.ki2t
.kit1se
.k4la
.k2l
.k6leyn
.k1le
.k3ley
.k4li
.kli4p5
.knik5
.k2n
.k1ni
.kn6o2p1n
.k4no2p
.k1no
.ko6maa2
.ko2m
.ko3ma
.ko6m1af
.k4o2p
.ko3pe4
.ko3pe1r7a
.kop4er
.ko6pla
.kop3l
.ko4p5o
.ko7ra2g.
.ko1ra
.ko2rag
.kor6s1t
.ko2rs2
.kors7te.
.k6ra2k.
.k2r
.kr6ü1ge
.kr1ü1
.kry2t5
.ku7mo2n.
.ku2m
.k4we
.k2w
.k4wo
.ky7oto2p
.kyo2t
.kyo3to
.l'7etji
.l'e1t
.l'et2j
.l’7etji
.l’e1t
.l’et2j
.la6eti
.la1e
.lae1t
.la6kwa
.la2k3w
.la5sa
.las2
.lei5s4
.le4k7oë.
.le1ko
.le3ko1ë
.le6poo2
.le1p
.le5p2r
.le7s6ha1b2
.les2
.le2s1h2
.les3ha
.le6son
.le1so
.le4s1p
.le3s5t
.le6suu2
.le1su
.li1g5e
.li4gi
.li6go2m
.li1go
.li6g1re
.li1g2r
.li7pa1se
.li4pa
.li2p
.lipas2
.l6loy2d
.l1l
.lo6c2hn
.lo1c2
.loc1h2
.lof7ui.
.lo4fui
.lo1fu
.lo6g1lê1
.lo1g
.log1l
.l6on2t1d
.lo2n1t
.lo4s5k2
.los2
.lu6gen
.lu2g1
.lu1ge
.lui5s3l
.luis2
.l6üder
.l1ü1
.lü1d
.m2
.m'7etji
.m'e1t
.m'et2j
.m’7etji
.m’e1t
.m’et2j
.ma6cdo
.ma1c2
.mac1d
.ma6nal
.ma1na
.ma6nur
.ma1nu
.ma7s6tek2
.mas3t
.mas2
.ma7t6hes2
.ma1t
.ma2t1h2
.ma6zda
.ma1z
.maz1d
.mel6k5a
.mel1k
.mel6k5l
.mer6k5l
.me2r1k
.me2s5m
.mes2
.me4s1w
.me6te1m
.me3te
.me1t
.mi6dos2
.mi2d
.mi3do
.mi6rba
.mi1r
.mir1b2
.mi7traa2
.mi2t
.mit2r
.m4ne
.m1n
.mo7d6ja2d
.mo1d
.mo2d1j
.mo7f6la2m
.mof3l
.mo6sin
.mo3si
.mos2
.mo4s1k2
.mu4e
.my6nen
.my1ne
.my6n5in
.my1ni
.my7un2is2
.myu1ni
.n2
.n6aa2n1d
.n3aan1
.naa2
.na5f3l
.n1af
.na6gro
.na1g2r
.na7groe
.na7s6maa2
.na2s3m
.nas2
.nas3ma
.na7s6tor
.nas3t
.na7uu2rs2
.n2au
.nauu2
.nauur1
.ne4k5a
.ne4k5o
.ne6k1r2i
.ne1k2r
.ne3k7rin
.ne6k1ys2
.ne1ky
.ne4s3
.ne7ser1h2
.ne3se
.nes4er
.n6etik
.ne1t
.ne4t5j
.ne6tru
.net2r
.ne6wca
.ne1w
.new1c2
.ne6w1fo
.ne6wla
.ne6wma
.new1m
.ni4e
.ni6jho
.ni1j
.nij1h2
.ni6rva
.ni1r
.nir1v2
.nix7o2n.
.n6ko1sa
.n1k
.n1ko
.n3kos2
.noe1t4
.noe5t3j
.no6gee
.no1g
.no1ge
.no4k
.n6ondu
.n3o2n1d
.nu4l
.ny7lo2n1t
.ny1lo
.oe5k2r
.oe4r
.oe7ral1g
.oe1ra
.oe7ra2ng
.oer7o2s.
.oe1ro
.oeros2
.oe4s3
.o4g1l
.o1g
.oh7ri2gs2
.o1h2
.o2hr
.oh1r2i
.o6kla1h2
.o1k2l
.ok7laho
.o4l6i4eu
.o1li
.o7li4e3ui
.o2m1s4
.o2m
.o2n1
.ond6ui
.o2n1d
.on7d5ui2t
.o6nemo
.o1ne
.o3ne1m
.on6ias2
.o1ni
.oni1a
.o7nia2s.
.o2n3k
.on7pa2r1m
.o2n1p
.o2n3s4
.on6s3he
.on2s1h2
.on6s3se
.on2s2s2
.on6t7ee2r
.o2n1t
.on6t5er
.on4t2r
.ont7ras2
.on4t5r2i
.o9nus2
.o1nu
.on6u2s.
.oon4
.oo2
.oon7de.
.oo2n1d
.o4o2p1
.oo2r5n
.oor1
.oo2r5s4
.oo4s2
.o6pee1t3
.o2p
.o3pe
.opee2
.op7ee2t.
.o6p9e2g.
.o6pein
.o3pei
.o6p5er1d
.op4er
.o4pof
.o3po
.o4p3r
.op5ra
.o2p1s4
.op7smuk
.op2s3m
.ops3mu
.o7ra5gie
.o1ra
.o2rag
.ora3gi
.or6k1ne
.o2r1k
.or1k2n
.o3ro
.orto5
.o2r1t
.o4s1k2
.os2
.os5ko
.os7oo2g.
.o1so
.o4soo1g3
.osoo2
.ot6hel
.o2t
.o4the
.o2t1h2
.o7the2l1l
.ou6doo2
.ou1d
.ou7nôi.
.ounô1
.ounô2i
.ou5t2j
.ou2t
.p2
.pa4d3
.pa7d6ie.
.pa3di
.pa6vlo
.pa1v2
.pe5la
.pel6sk2
.pe2l1s2
.per6s1t
.pe2rs2
.pe4sk2
.pes2
.pe4s1t
.pie6t2j
.pie1t
.pi7la2f.
.p2il
.pi1la
.pi2laf
.pi2t5s2
.pi2t
.p4la
.p2l
.po6dzo
.po1d
.pod1z
.p6oe1fe
.poen4
.p6on2t1w
.po2n1t
.po6sa2d
.pos2
.po1sa
.p4re
.p2r
.pu6ta2d
.pu2t
.pu3ta
.py6paa2
.py2p
.py3pa
.py6pla
.pyp3l
.py6pol
.pyp3o
.py4p5r
.py7t6hon
.py2t
.py2t1h2
.r2
.r'7etji
.r'e1t
.r'et2j
.r’7etji
.r’e1t
.r’et2j
.r6aar1d
.raa2
.ra7da2r1t
.ra2d
.ra3da
.rada4r
.ra6seg
.ras2
.ra1se
.ras7e2g.
.re7au3mu
.re1a
.re2au
.reau2m
.re6gru
.re1g2r
.re2k5s2
.re6mas2
.re1m
.rem7a2s.
.re6mco
.rem1c2
.re4sl
.res2
.rie4t
.r2i
.riet5j
.riet5r
.ri6p1le
.ri2p
.ri3p2l
.roc7ky.
.ro1c2
.ro2ck
.ro6gak
.ro1g
.ro1ga
.ron7d6o.
.ron4d5o
.ro2n1d
.ron2d5s4
.ro5py
.ro2p
.ros5t
.ros2
.ro6t3re
.ro3t2r
.ro2t
.ro6w1li
.ro1w
.ru7k6li4p3
.ru2k3
.ru1k2l
.ruk1li
.ru1k4o
.ru7ko3pe
.ruko2p
.ru7s4taa2
.rus3t
.rus2
.ru6suu2
.ru1su
.ry4k5a
.ry6ste
.ry3s3t
.rys2
.s6aa2n1s2
.s2
.s3aan1
.saa2
.s4af
.s4ag
.sa7g6o3pa
.s4a1go
.sa2g1o2p
.s6akty
.s3a2k1t
.s4a2m
.sa6vlo
.sa1v2
.s4ca
.s1c2
.se6a2n.
.se1a
.see5ra
.see2r
.see7y2s.
.seeys2
.se7k6huk
.se2k1h2
.se6laa2
.se1la
.se6l5o2p
.se1lo
.se7re3ni
.se1re
.se6s1le
.s2es2
.se1sl
.se4s5t
.se6suu2
.se1su
.se6ta2p
.se1t
.se4t2r
.sex5y
.s6fa1le
.s1f2
.s3fa
.s4fi
.s4g2r
.s1g
.s4ha4
.s1h2
.s4he
.s4hi
.s4ho
.s4hu
.s4in
.s2i6nes2
.si1ne
.si7p6ho.
.si2p
.si2p1h2
.si7ra2g.
.si1r
.si2rag
.s4ja
.s1j
.s4ka
.sk2
.s4ke
.s4k2l
.s4ko
.s4k2r
.s4ku
.s4la4g5
.s4ma
.s1m
.s4me
.s4mi
.s4mo
.s4mu
.s6na2g3s2
.s3na4g
.s1na
.s4ne
.so1d4
.so7da1fa
.so2d1af
.so7dwan
.so2d1w
.s2o7i6e2t1s2
.soi1
.soie1t
.so6ko2p
.so1ko
.s4o3me4
.so2m
.s6om2s.
.s3o2m1s2
.s4on
.so6n5eg
.s4o1ne
.s4o2p
.so6pek
.so3pe
.so7p6hok
.so2p1h2
.s2o7ro2s2s2
.soros2
.s4pe
.s1p
.s4p2l
.spo4g
.s6pren
.sp2r
.sp1re
.s4py
.s8ri.
.s1r
.s3r2i
.s4ta
.s1t
.s6te2m1p
.ste1m
.ste7rol
.ste1ro
.ster6ta
.s1te2r1t
.ster6t7j
.s4ti
.s4to
.s4traf5
.st2r
.s6tr4ei
.st1re
.s6tuar
.s3tu1a
.stu4c5
.su7bie1t
.su2b2
.sub5m
.sub5p
.su8e.
.su1e
.s4ui
.su5k2r
.su7t6he2r
.su2t
.su2t1h2
.su7tra.
.sut2r
.s4we
.s1w
.s4wi
.s4wo
.sy1
.sy6lvi
.syl1v2
.sy7n6a1g2r
.sy2n1a
.sy7s4la4g
.sy2s3l
.sys2
.t2
.t6af1sy
.t3a2f1s2
.t4ag
.tee5k
.te6flo
.te1fl
.te7r6a1fi
.te4r5af
.te1ra
.te7r4a1g2r
.te2r3ag
.ter6t2j
.te2r1t
.tert7ji
.te4s5t
.tes2
.te7s6tu1d
.ti6e1ne
.tie6t5j
.tie1t
.ti4k
.ti6ner
.ti1ne
.t6jaai1
.t2j
.t3jaa2
.tjo4k5
.toe7y2s.
.toeys2
.to6kla
.to1k2l
.to7ky7o.
.to6lun
.to1lu
.to7ro2n1t
.tou3
.trap5r
.t2r
.tra2p
.trek5
.t1re
.tre4s2
.trie4
.t1r2i
.tries5
.t4sa
.t1s2
.t2s4h2
.ts6jaa2
.t1s1j
.t2sja
.ty6daa2
.ty2d
.ty3da
.ty6dor
.ty6dra
.tyd3r
.ui6laa2
.u2il
.ui1la
.ui4t3
.ui5ti
.ui5t6ji
.uit3j
.um7hlan
.u2m
.u2m1h2
.um2hl
.u2n2s4
.un5s1t
.u5raa2
.u1ra
.u5tra
.u2t
.ut2r
.va6ka2d
.v2
.va1ka
.va6kei
.va1ke
.va6n1af
.va1na
.va4n5o
.va7raan1
.va1ra
.varaa2
.va6sen
.vas2
.va1se
.va6swa
.va2s3w
.vas7y2s.
.va4s2ys2
.va1sy
.ve7ci1no
.ve2
.ve1c2
.ve7laar
.ve1la
.velaa2
.ve7la1re
.ve7lê2r.
.ve1lê1
.ve7loer
.ve1lo
.ve7lo3me
.velo2m
.ve7me2ng
.ve1m
.ve7r6ema
.ver1
.ve1re
.vere1m
.ve7r6e1na
.ve7r6eve2
.vere1v2
.ve7skaf
.ves2
.ve1sk2
.ves1ka
.ve7to1re
.ve1t
.vlas5
.vo6gin
.vo1g
.vo1gi
.vo6ly1w
.vo1ly
.vo6s1ko
.vo1s1k2
.vos2
.wa7gh1r2i
.wa2g1h2
.wag2hr
.wa4n
.wa7s6mou
.wa2s3m
.was2
.wa6spa
.was3p
.we4b5m
.we1b2
.we4bo
.we6b-o
.web2-1
.week7lan
.wee2k
.wee1k2l
.wee4t5
.we6kuu2
.we1ku
.we4l5a
.we6lin
.we3li
.wel7i2ng
.we6nan
.we1na
.wer4k5l
.we2r1k
.wer6k5r
.we4s5k2
.wes2
.we6soe
.we1so
.we6swa
.we3s1w
.w8hê.
.w1h2
.whê1
.w4hi
.wi6i2d.
.wi1i
.wii2d
.wi2n1s5
.wi4p
.wi4t
.wî9e.
.wy7k6was2
.wy2k3w
.wy7n6a2n1d
.wy2n1a
.wy6ne1t
.wy1ne
.x2
.y6amin
.y1a
.ya2m
.ya3mi
.y6an1ni
.yan1n
.y6asu1d
.yas2
.ya1su
.yk7loon1
.y2k2l
.ykloo2
.ys3
.ys6e1re
.y1se
.ys5la
.y2s3l
.ze5us2
.z6üri1c2
.z1ü1
.zü1r2i
.z4wa
.z1w
.z4wi
aa2
aa2d1
aa4da
aa4de
aa4do
aa4d3r
aaf7emme
aa1fe
aafe1m
aafe2m1m
aaf6sa1t
aa2f1s2
aaf1sa
aag5al
aa1ga
aag7ase1m
aagas2
aaga1se
aag7elas2
aa1ge
aage1la
aa1g3r
aag7ro2n1d
aag4ron
aag5s4l
aa2gs2
aag5s3p
aag5s4t
aag7s6wee
aag2s3w
aai7l4a2g.
aai1
aa2il
aai1la
aai2lag
aak1
aa4ka
aa4ko
aa1k3r
aak7ster
aa2k1s2
aaks1t
aa2k3w
aal1
aal6dys2
aal1d
aald7y2s.
aal5f3e
aal6fpo
aalf1p
aal5s4a
aa2l1s2
aal7sfee
aal2s1f2
aals1fe
aa2m1
aa4ma
aa4me
aa4mo
aam7s6mul
aa2m1s2
aa1m2s1m
aams3mu
aan1
aan6dou
aa2n1d
aan4d6re
aand2r
aan7dren
aan7dros2
aan4dro
aan7g6o2n1s2
aa2ng
aan4g3on
aan1go
aa2n3k4
aan5k2l
5aankon
aan1ko
aan7kry.
aan4kry
aank2r
5aanleg
aa2n1l
aan1le
aan7s3a1ge
aa2n1s2
aan2s3ag
aan1sa
aan6see
aan1se
aans7ee2r
aan6sek
5aans4ig
aan3si
aan7ske1m
aan4s1ke
aan1sk2
aan5sl
aan5sn
aan6so2m
aan1so
aan6son
aan5s1p
aan7tuig
aan4tui
aa2n1t
5aanva
aa2n1v2
aa2p1
aa4pa
aa4po
aap3r
aa4pu
aa1r3a
aar6dan
aar1d
aard7a2ng
aar6d7a4s.
aardas2
aar5de
aar7di2ng
aar4du
aa1r3e
aa1r3i
aar7kwek
aar4k2w
aa2r1k
aar6lbe
aar1l
aarl1b2
aar6l1ka
aarl1k
aar6lva
aarl1v2
aar6lzi
aarl1z
aar6l-o
aarl2-1
aa1r3o
aar7se2r.
aa2rs2
aar1se
aar7se1r2i
aar6si2d
aar1si
aars8tee4k
aar1s1t
aars8te2l1l
aars6ti
5aarta2p
aa2r1t
aar6tin
aar7t6o3mo
aar4t3o2m
aar7t6ry1b2
aar4t3ry
aart2r
aa1r3u
aas3
aa4so
aas7omel
aas4o3me
aaso2m
aa1t1
aa1t7na2g1t
aa2t1n
aat1na
aa4to
aat3r
aat6sef
aa2t1s2
aat1se
aat7sfee
aat2s1f2
aats1fe
aat7sli2m
aat1sl
aat2s1li
aat6slo
aat6s1ly
aat7son1n
aat4son
aat1so
aat6so1w
aat6s4ti
aats1t
aau6wbe
a2au
aau1w
aauw1b2
aä5ron
a1ä
aba6kas2
a1b2
aba1ka
abak7a2s.
a1b2a7ko2m1b2
aba4k3o
aba3ko2m
aba1re4
aba7ster
abas3t
abas2
a2b3d
aber6s1p
abe2rs2
a4b5l2au
a4b5ru1p
3ab3so
a2b1s2
abu7scha
abus2
abus1c2
abusc1h2
ab5wie
a2b1w
ac5qu1e
a1c2
ac1q
acqu2
a2d
a3da
a4d3aa2
ada2m4
adam7pe.
ada2m1p
ada4r
a3de
ades7lan
ade3sla
ades2
ade1sl
ade7smee
ade2s1m
ades4me
a3di
adi6eus2
adi1eu
5a1dju1d
a2d1j
5admin
a2d1m
a3do
a4do1w
a3d2r
5adre2s.
ad1re
adres2
ads7erwe
a2ds2
ad3se
adser1w
ad4sn
ad3s6o2p.
ad2s1o
ad2s3o2p
ad5sor
ads7te1so
ad1s1t
adstes2
ad4su
a3du
ad5ui2t
adu7s6pel
adus3p
adus2
adu5t2j
adu2t
5adve2r1t
a2d1v2
adve2
ad3ver1
a3dy
ady7s6mi2t
adys2
ady2s1m
adys3mi
a1e
ael7atoo2
ae1la
aela1t
ae4l5ei
ae3le
ae4lo
aes5to
ae1s1t
aes2
aes5t2r
aes7tuur1
aestuu2
a1ë1
4afee
a1fe
af5e2k1s2
afel5aa2
afe1la
af4f1re
af1f
af1f2r
af5g4ha
af1g
af2g1h2
a4f5in1r
a1fi
af3l
a4f3of
a1fo
a1f2r
af5raa2
a4f5ra2m
af5ran
a4f3re
3af1r2i
a4f5ri2t
a4f3ro
a5f4ro1d
a4f3ru
af3s4w
a2f1s2
3afva
a2f1v2
afva4l5
ag3aa2
a1ga
a6gaa2n1v2
agaan1
a4gaar
ag5a2d1v2
aga2d
a4g3ak
ag5al1g
ag5a3pi
aga2p
ag5a1re
4age.
a1ge
4age1b2
4age3d
age6ddo
age2d1d
a4gei
4age1m
a4g5er1v2
a3ger
4ages2
a6g5ewen
age1w
agge7u2s.
a2g1g2
ag1ge
aggeus2
a3gi
a5gi2ng
a4g5i2n1s2
agi5s6t2r
agis3t
agis2
ag1l
ag5o2g1g2
a1go
ago1g
ag5or1d
ag5o2r1k
ag5ou4d3
a4g3re
a1g2r
ag5rei
a4g3ru
ag3s1a
a2gs2
ags7ab3no
agsa1b2
agsa2b1n
ag6s5i2n1s2
ag1si
ag5s1ka
ag2s1k2
ags7koe1v2
ags1ko
ag5skol
ag5s3k2r
ag5sky
ags4lo
ag2s3l
ag4sn
ags6oe1p
ag2s3o
ag5s4o2m.
agso2m
ags6oo2m1
agsoo2
ag3s6o2p.
ag2s1o2p
ag5spe
ags3p
ag5s6por
ag1s4t
ags7taal1
ags4taa2
ag5s3ti
ags6waa2
ag2s3w
ags6wee
ag4tu
a2g1t
agt7uu2r.
agtuu2
agtuur1
a4g3ui
a1gu
ag5u1re
ag5uur1
aguu2
ag-7l4a2g.
a2g2-1
ag-2lag
ahe2r4
a1h2
ahe5r3i
a4h2s.
ahs2
ai1
aig6ne.
ai2g1n
aig1ne
aiï5er
ai1ï
ai4l1p
a2il
ain6ste
ains4t
ai2n1s2
ai4p6eis2
ai2p
ai3pe
ai3pei
ai3s4k2
ais2
ai5sla
ai2s3l
ais4p
ais7prys2
ai4s3pry
aisp2r
ais4t
ai3t2j
ai2t
ai3t4r
a4k5aan1
a1ka
akaa2
ak5ar1b2
ake6lee2
a1ke
ake3le
ake6lof
ake1lo
ak5e2s2s2
akes2
ak5i2n1s2
a1ki
akis4
akis7te.
akis3t
5akkoo2
a2k1k
ak1ko
a2k3l
a5k4la2n1k
ak6lee1t
ak1le
aklee2
a2k3n
ako6b1re
a1ko
ako1b2
ak5o2m1s2
a3ko2m
a1k2r
a4k3re
a4k5rig
ak1r2i
ak4s1c2
a2k1s2
ak5s3me
ak2s1m
ak3s1p
a4k3ui
a1ku
a2k2w
ak3we
ak5win
a3ky
a4k1ys2
a1la
ala7ga3di
a2lag
ala4ga
alaga2d
a5la2g1m
al5a2g1t
ala7k6le1d
ala2k3l
alak1le
alan7gaa2
ala2ng
alan1ga
al5dei
al1d
a4lef
a1le
ale6st2r
ale3s1t
ales2
al4f3e
alf6e3ni
al4fen
al4f1h2
al5f4ie
al1fi
al4f1j
alf7olie
al1fo
alfo1li
alf6s1ko
al2f1s2
alf1sk2
alf6s1ni
alf1sn
alf6sta
alfs1t
alfs7tan
al4fu
alf4-1
5algo1r2i
al1g
al1go
alie2n5s2
a1li
ali6gal
ali1ga
ali7g6las2
alig1l
alk7aar1d
al1k
al1ka
alkaa2
alk7laag
al1k2l
alklaa2
alk7oo2n1d
al1ko
alkoo2
alkoon1
alk5s1p
al2k1s2
al4kui
al1ku
alk7wy2k.
al4kwy
al1k2w
5allee2n1
a2l1l
al1le
allee2
alm7eier
al1m
almei1e
alm7lo2n1t
al2m1l
a1lo
al1s4a
a2l1s2
als7agti
al2sag
alsa2g1t
als7g6haa2
al2s1g
als2g1h2
al6skel
al1sk2
als1ke
als7kel1k
al2s5li
al1sl
al4s5oo2
al1so
als7pre1t
als1p
alsp2r
alsp1re
als5waa2
al2s1w
als7wer1w
alt6he1a
al1t
al2t1h2
alt6hus2
alt7ro2t1s2
alt2r
al1tro2t
alt6s2as2
al2t1s2
alt1sa
alt6wee
al2t1w
a1lu
alve5o
al1v2
alve2
a2m
a3ma
ama3k4
aman6t5j
ama2n1t
ama7ri2n1s2
ama1r2i
am5atoo2
ama1t
5ambag
a2m1b2
5amba2s2s2
ambas2
a3me
a4m5e2g1t
ame6sin
ame3si
ames2
a1me5s1m
ame6spo
ame1s1p
a3mi
ami7s4ky1w
ami2s3k2
amis2
amm6a1fu
a2m1m
amm1af
5ammun
a3mo
amp7ar1re
a2m1p
ampa2r1r2
amp7l4a2g.
amp2l
amp2lag
amp7lee2r
amplee4
amp1le
amp7li2g.
ampli4g
amp1li
amp7lu2g.
amplu2g1
amp7omhe
a1m3po2m
am4po2m1h2
amp7seël
am2p1s2
amp1se
ampse1ë
amp7sfee
amp2s1f2
amps1fe
amp7sier
amp1si
amp3sie
amp7staf
amps1t
amp7sta1w
ams7e3sel
a2m1s2
am1se
am1se3se
amses2
am6s5ko2p
am1sk2
ams1ko
ams7le2n1d
am1sl
ams1le
am6sme1t
am3s4me
a1m2s1m
ams7meti
ams6mul
ams3mu
am4s3o
ams7pe2l1s2
ams1p
am6swan
am3s1w
am6swar
ams7wy2n.
a3mu
a4mui
a4m5ui2t
a3my
a4n5a2g1t
a1na
ana7kwal
ana2k2w
a4n5alf
ana6spi
ana3s3p
anas2
an4c2-1
an1c2
and7aa2n1s2
a2n1d
andaa2
andaan1
and7adel
an1da2d
anda3de
an6d5a2k1t
an5dan
and7anal
anda1na
an4da2p
an6da1se
andas2
an6da2t1t
anda1t
and7att2r
an6de2g1t
and7egte
and5e2k1s2
and7emal
ande1m
an6derf
and7er2f.
an6de3te
ande1t
and7etes2
and7eval
ande1v2
an6di2n1w
and7inwa
and6ja.
an2d1j
and6jar
an4d5o2m
an4don
an4d5o2p
and7ou2d.
andou1d
an6d5rak
and2r
an3dra
an4dro
and6ser
an2ds2
and3se
and6s7ko2p
and1sk2
ands1ko
and7spaa2
ands1p
and7sp1re
andsp2r
and7steg
and1s1t
and7s6wee
and2s3w
an7d4wi2ng
an2d1w
and7wy2n.
and4wyn
ane6ron
a1ne
ane1ro
ang7aa2l.
a2ng
an1ga
angaa2
angaal1
ang7ade1m
an4ga2d
anga3de
ang7ghor
an2g1g2
ang2g1h2
ang6hai1
an2g1h2
ang6la2d
ang1l
an5g4li
an6glig
ang7li2g.
ang7li2p.
ang4li2p
ang6nol
an2g1n
ang1no
ang6o2n1s2
an4g3on
an1go
ang7o3r2e.
ango1re
ang7repu
an1g2r
ang1re
angre1p
ang7sa2k1m
an2gs2
ang2s1a
ang6s1ka
ang2s1k2
ang7snee2
ang4sn
angs1ne
ang6s7te.
ang1s1t
ang7ste1m
ang6sur
ang1su
ang7u3r2e.
an1gu
angu1re
anie6t5r
a1ni
anie1t
an5i2n1l
ani5s1f2
an2is2
ani7s4la1w
ani2s3l
an6ka1se
a2n1k
an1ka
an3kas2
ank7ase1m
ank7re1fe
ank2r
ank1re
an4kry
an2k3w
an5o2p1s2
a1no
ano2p
an5o2p1t
an5o2p1v2
an5or1d
a4n5or1g
ano7roei1
ano7stoe
anos3t
anos2
anr6hyn
an1r
anr1h2
ans7aal1w
a2n1s2
an1sa
an3saal1
ansaa2
an4s5a2m
an6s2a2s2s2
ans2as2
ans7as3se
an4s1c2
an4s7ei1la
an1se
anse2il
ans7eu4ra
an6si2n1k
an3si
ans7in2k.
an6sjek
an1s1j
ans7je2k3k
ans7jor1d
an4s1ke
an1sk2
ans5kei
an6s5kin
ans1ki
an6s5ko2p
ans1ko
an6s7kous2
an6s3la1t
an2sl
an3sla
ans7ma3da
an2s1m
ans3ma
ans4ma2d
an4sn
ans7oran
an1so
anso1ra
an4s5pa
ans1p
an6s5per
ans7pe2t.
an4spe1t
an4spo
ans4ti
ans1t
ans7to2il
anstoi1
ant5aan1
a2n1t
antaa2
ant5aar
an6ta2s2s2
antas2
ant7as3so
an4tei
an6te2k1s2
antek2
an1t7eks5t
5anten1n
ant7ete.
ante3te
ante1t
an6ti2n1s2
an4t5jo
ant2j
an4tol
ant7o4pru
an4to2p
antop3r
an1t7re4s1t
ant2r
ant1re
antres2
ant5rin
ant1r2i
ant7ro2b.
antro1b2
ant6s1ki
an2t1s2
ant1sk2
ant6s1ko
an4tui
5antwo
an2t1w
a1ny
a1o
a2p
a3pa
a4p5aan1
apaa2
a4p5a2g1t
ap5a2k1s2
a3pak
a4p5a2r1m
5apar2t1h2
apa2r1t
a3pe
a1pe6no2p
ape1no
a3pi
api6r1fa
api1r
ap3l
a3po
apo6k5aa2
apo1ka
ap5o2n1d
apo5s4ta
a3pos2
apos3t
5appar
a2p1p
ap2r
ap5rol
ap3ru
a4pry
ap5ryk
a5prys2
ap6sa2l1l
a2p1s2
ap1sa
aps7al1li
ap6se1ko
ap1se
aps7iden
ap4si2d
ap1si
apsi3de
ap6s5kof
ap1sk2
aps1ko
ap6s5taa2
aps1t
ap5s4ti
ap6s7toe1t
aps5we
ap2s1w
a3pu
a4pui
a4p5ui2t
a3py
a1ra
ara7g6wan
a2rag
ara2g1w
ara7klee2
ara2k3l
arak1le
ara6ko2p
ara1ko
ara3p4
ara6ppa
ara2p1p
ar6d5a2g1t
ar1d
arda2g
ar6da4s.
ardas2
ar6da2t1m
arda1t
ar5der
ar6deti
arde1t
ar6d5o2p1p
ar3do
ardo2p
3are1a
a1re
aree5s3
areg7swe
are2gs2
areg2s3w
ar3ei
are7kni4p3
are4k2n
are3k1ni
ar5fla
ar2g4h2
ar1g
ar5gha
5argi3te
ar1gi
argi2t
ar4gl
ar1g4o
arie4f
a1r2i
ari6j1ke
ari1j
ar6k5a1na
a2r1k
ar1ka
ar6ki3ni
ar1ki
ark7lee2r
ar1k2l
ark1le
arklee2
ar5klo
ar4k6los2
ark7onvo
ar1ko
arko2n1v2
arko6v.
arko1v2
ark7s4nui
ar2k1s2
ark2s3n
arks1nu
ark5s1p
ar4k2w
ark5wa
arn6avo
a2r1n
ar1na
ar3na1v2
a1ro
aroe4t6j
aroe1t
aroe7tji
aroo5h2
aroo2
aroo2m4
aroo5p1
aroo5s2
ar3o2p
aro6wva
aro1w
arow1v2
ar5rag
a2r1r2
arre4s5t
ar1re
arres2
ar2s5ag
a2rs2
ar1sa
ars7elek
ar1se
ar3sel
arse3le
5arse1na
ar5s4ie
ar1si
ar5s6kou
ar1sk2
ars1ko
ars7kra2p
arsk2r
ar6sk4re
ars7kree
ar6skro
ars7pa2n.
ar1s1p
ar3s4pan
ar4sp2r
ar6stal
ar1s1t
ars7ta2l1l
ars7tee.
ars6-in
ar2s2-1
ar6taas3
a2r1t
artaa2
art7aa2s1v2
ar6tal1b2
art7albu
ar4t5as2
ar4t1c2
ar5te.
ar6t7ee2n1d
artee4n1
ar5te1h2
ar4tei
art6hol
ar2t1h2
art6hur
art6o3mo
ar4t3o2m
art5oo1g3
artoo2
art5oor1
ar4tor
ar4t7ree4k
art2r
art4ree
art1re
art7roe1p
art6ry1b2
ar4t3ry
art7sa2m1b2
ar2t1s2
art4sa2m
art1sa
art6slu
art1sl
art6sp2r
arts1p
art7s6pyn
a1ru
arus6o.
aru2s3o
arus2
a1ry
ary7taal1
ary4ta
ary2t
arytaa2
a2s3ag
as2
a1sa
as5a2p1p
asa2p
as3c2
as4d.
a2s1d
as4d1h2
a4s5e2g1t
a1se
ase6rak
ase1ra
as5e3te
ase1t
a2s3f2
asg6hi2t
a2s1g
as2g1h2
as4hi
a2s1h2
asi7f6reu
a1si
asi2f3r
asif1re
a4sj.
a1s1j
a4sj1m
a2s3k2
a5skool1
as1ko
askoo2
a5sk1r2i
ask2r
as5kru
a2s3l
as5laag
as4laa2
a2s3m
a4smy
a4s1na
a1sn
as3no
as9o2f.
a1so
as5o1gi
aso1g
a4s3oo2
a2s3o2p
as3p
as4por
as3t
a2s4t.
a5s4taa2
a2s4t1h2
as2t6les2
as2t1l
ast1le
a5s4tof
a7s6tra2k.
ast2r
a5s6tran
a5s6troo4
a2s3w
a4s2ys2
a1sy
a4s5ys3t
at5aar
a1t
ataa2
a4tag
ata3s4
ata6s3se
ata2s2s2
atas7se.
ata6wba
ata1w
ataw1b2
a1te5i2t2
5atel1j
ate6r3ar
ate1ra
ate6rer
ate1re
ate6ron
ate1ro
a6tetes2
ate3te
ate1t
a1th7ca2r1t
a2t1h2
ath1c2
a5t4hee
ath7ki2n1p
ath1ki
ath7lo1ne
at2hl
atie6te
atie1t
a2t3j
atk6v-s2
a2t1k
at2k1v2
atkv2-1
5a1tlee1t
a2t1l
at1le
atlee2
5atmos2
a2t1m
at5oo1g3
atoo2
at5ry.
at2r
ats7alma
a2t1s2
at1sa
at4sal1m
a1t6si2n1t
at1si
ats7inte
at4s1j
at6skin
at1sk2
ats1ki
ats6ko2m
ats1ko
at6sko2p
ats7ko2p.
at6s7kri4p
atsk2r
atsk1r2i
at4s5le
at1sl
ats7ly2k1h2
ats1ly
at4s1m
ats6maa2
ats3ma
ats7noo1d
at1sn
at4snoo2
ats1no
at6so2m1w
at1so
atso2m
ats7omwe
at4son
ats5o2n1d
ats7on1ko
atso2n1k
ats7onlu
atso2n1l
at2s5o2p
ats7ower
atso1w
at6s7te2n1d
a1ts1t
ats7trek
atst2r
atst1re
ats8tre3ke
at6stro
at2s3w
at5t4he
a2t1t
at2t1h2
att6hys2
at4tu
atu6maa2
atu2m
atu3ma
2au
aud6rey
au4d3re
au1d
aud2r
au5gra
au2g3r
aug6s3bu
au2g3s2
aug2s1b2
aul6spo
au2l1s2
auls1p
au3p
aure5u
au1re
auri5s4
au1r2i
aus4t
aus2
aus7ti2n.
aus3ti
au5st2r
aus7tu2s.
austus2
aut6ste
au2t
au2t1s2
auts1t
ava6lo2p
a1v2
ava1lo
ave7lo2t1t
ave2
ave1lo
avelo2t
avlo6v.
avlo1v2
3avon
awa7g6las2
a1w
awag1l
awas4
awe5ga
a3weg
awe4r5a
awer6ui
awe1ru
aws6han
a2ws2
aw2s1h2
aws3ha
ax5o1fo
a3y1a
ay4a.
ay5is3t
ayis2
ayn6ar1d
ay2n1a
ayn6o2r.
ay2n1o
a3yo
a3y1w
azoo7k6a.
a1z
azoo2
azook3
azoo4ka
azz7agti
a2z1z
azza2g1t
az4zl
azz7or1ke
azzo2r1k
1ä
1b2
2b.
babak4
1b2a1b2
bab7wiër
ba2b1w
babwi1ë
ba4d5ra
ba2d
ba3d2r
bad5s1p
ba2ds2
ba4kin
ba1ki
ba3k3l
ba4kla
ba6kleu
bak1le
ba4k3o
ba4k3r
ba2k3w
bal6kla
bal1k
bal1k2l
ba4lo
bal7on2t1s2
ba4lo2n1t
bal5or
bal7t6s2as2
bal1t
bal2t1s2
balt1sa
ban4da
ba2n1d
ban6dek
ban4d5r
ban7g6la2d
ba2ng
bang1l
bang7ste
ban2gs2
bang1s1t
bang6s8te.
ban4k5a
ba2n1k
ban6k1re
bank2r
ban4k3w
bar7kaan1
ba2r1k
bar1ka
barkaa2
ba2r4s3
bar5t1h2
ba2r1t
bas7ek4s1t
bas2
ba1se
bas2e2k1s2
bas7g6hi2t
ba2s1g
bas2g1h2
bas7ja2n.
ba1s1j
ba2sja
ba4sn
ba5spe
bas3p
bas7pee2r
ba3s4pee2
bat5aan1
ba1t
bataa2
ba3t4j
2b2b2
b3ba
b3be
b3bi
b3bl
b3bo
b3by1
2b1d
b3de
b3di
b3do
bed7s4laa2
bed4sl
be1d
be2ds2
bed6sta
bed1s1t
beds7taa2
be3d2w
beel6d2r
beel1d
bek7neus2
be1k2n
bek1ne
bek7wi2n1d
be1k2w
bel6a4ga
be1la
be2l3ag
belk6li
bel1k
bel1k2l
bel7k3lik
bel6ldo
be2l1l
bell1d
ber6gaa2
ber1g
ber1ga
berg7aar
ber4gl
ber4g5r
ber7g6rys2
ber6gzi
berg1z
ber3s7pan
be2rs2
ber1s1p
ber6sp2r
bers7p1re
bert6sk2
be2r1t
ber2t1s2
be4s6aan1
bes2
be1sa
besaa2
be1s4k2
be5s1ka
be3sl
be3s1m
be3so
be5son
be5sôr
besô1
be3s4t
be6s5ter
be5s3ti
be6sti1a
bes7tial
bes7trol
best2r
be2t4h2
be1t
be5tha
bet7he2s1d
bethes2
be5ton
be3t1w
2b1f
2b1g
b3ge
bi2d3s2
bi2d
bi4du
bid7u3r2e.
bidu1re
bie6dui
bie1d
bie4g
bie1g5r
bi4jl
bi1j
bin6d1r2i
bi2n1d
bind2r
bio7sfee
bi1o
bio1s2
bio2s1f2
bios1fe
bi4r1c2
bi1r
bi1s4a
bis2
bis6ho.
bi2s1h2
bis3ho
bis7scho
bi2s2s2
biss1c2
bissc1h2
bi3t2r
bi2t
2b1j
b3je
2b1k
b3k2l
b3ko
b3ku
bla4d5a
bla2d
bla6don
bla3do
bla4d5r
bla5so
blas2
4bl2au
blee1m5
b1le
blee2
ble4s2
ble7se2r.
ble3se
bles4er
ble1s5k2
ble6tji
ble1t
blet2j
blet7jie
blê6rfl
b1lê1
bli1k5o
b1li
blix7e2n.
blixe1
blo4k3
blo7k4aal1
blo1ka
blokaa2
blo4m3
blu6se1m
blus2
blu1se
bly7ma1re
b1ly
bly4ma
bly1m
bly3s4
2b1n
b3no
bob7slee2
bo1b2
bo2b1s2
bob1sl
bobs1le
boe6k2il
boe1ki
boe6ko2m
boe1ko
boe6koo2
boer6s1t
boe2rs2
boer5s7te
boe4s5k2
boes2
bog7ghe2r
bo1g
bo2g1g2
bog2g1h2
bog7sku2t
bo2gs2
bog2s1k2
bogs1ku
bo2k1
bo3ka
bok6aak1
bokaa2
bok6a1le
bok6a2s.
bo3kas2
bok6erf
bo1ke
bok6ies2
boki2e4
bo1ki
bo1k3l
bo7k6lee1d
bok1le
boklee2
bok6o2m.
bo1ko
bo3ko2m
bok6o2rs2
bok6os3t
bo3kos2
bo1k3r
bo3k6ra4g
bo4m5aa2
bo2m
bo3ma
bom6aa1t1
bo4m3o
bon6da2m
bo2n1d
bon6dra
bond2r
bond7raa2
bon6tel
bo2n1t
bon4t5r
bo9o2p.
boo2
boo2p1
bop6laa2
bo2p
bop3l
bor6dak
bor1d
bor6des2
bor4g5a
bor1g
bor6g1r2i
bor1g2r
b4o2rs2
bor6saa2
bor1sa
bor4s5k2
bor4s5l
bor4s5t
bo2s2
bos7a3ne1m
bo1sa
bosa1ne
1b2os7ja2m1b2
bo1s1j
bo2sja
bosja2m
bos7pepe
bos3p
bos4pe
bospe1p
bos7taai1
bos3t
bos4ta
bos4taa2
bo5s4t2r
bos7ui2l.
bo1su
bos4u2il
bo5t4ha
bo2t
bo2t1h2
bot6sto
bo2t1s2
bots1t
bou6i2t.
bou1i
boui2t
bou3s4
b3p2r
b1p
br4and5a
bra2n1d
bree4k5
b1re
br4ei
brei5s4
breng1s7t
bre2ng
bren2gs2
brie6k2w
b1r2i
briek7wa
3bro
bro4n
bro4s1k2
bros2
bro6v1ni
bro1v2
bro6w1ni
bro1w
bru4l
4bru1p
br4ü1m
br1ü1
2b1s2
b3se
b3si
b3sk2
b3so
b3s1p
b3s1t
bs4ti
b3su
2b1t
b3te
b3ti
buc7cleu
bu1c2
bu2c2c2
bucc1le
bui2k5s2
bui5t4j
bui2t
bul4t7a2f.
bul1t
bul4t5j
bul6to2p
bult7o2p.
bul6tui
1b2un7s6e2n1b2
bu2n2s3
bun1se
bus6ha2d
bus2
bu2s1h2
bus3ha
bu4s7toer
bus3t
bus4to
but6hel
bu2t
bu2t1h2
buu7r4e2n1d
buu2
buur1
buu1re
2b1v2
b3ve2
b3vi
2b1w
b3we
by1
by3d
by3k
1b2y4l1b2
by4l1t
by3n4a
by3s2
bys6ko2t
by2sk2
bys3ko
by2s4l
bys6tek2
bys3t
bys7te2r.
by4ster
bys6tor
by2s4w
byt7al1ka
by2t
by3ta
bytal1k
byt7eier
by3te
bytei1e
by3t2r
1c2
2c.
ca4es2
ca1e
cam5p1h2
ca2m
ca2m1p
ca3p2r
ca2p
ca3ra
car6lto
car1l
carl1t
caru7s6o.
ca1ru
caru2s3o
carus2
ca2t4h2
ca1t
ca5tha
2c2c2
c3ca
c3c1h2
c3ci
c3co
ce4s1t
ces2
ces5te
2c2h.
c1h2
che6lan
che1la
5chemi
che1m
che6reg
che2r
che3re
che5r4i
che7ry2l.
che1ry
che7s4tyl
che4s3t
ches2
che2sty
che6vvi
che1v3
chev1v2
ch5hoe
ch1h2
ch5lei
c2hl
ch1le
ch5nik
c2hn
ch1ni
cho7ra1ge
cho1ra
cho2rag
ch5si1a
chs2
ch1si
2ch1t
2ck
c3ke
cot7ra2n1d
co2t
cot2r
cove7r6y.
co1v2
cove2
co3ver1
cove1ry
2c1t
c3ta
c3to
c3tu
cus5to
cus3t
cus2
cyp7rian
cy2p
cy3p2r
cy3p4ri1a
cyp1r2i
1d
2d.
4d5aa2n1b2
daa2
daan1
1d5aa2n1d
4d5aa2n3k4
d5aan5sl
daa2n1s2
daar4d5u
daar1d
4da2b1s2
da1b2
2d1af
da2g
da5gas2
da1ga
dag5e1t
da1ge
da5gha
da2g1h2
dag6ha2m
da5gra
da1g2r
dag4s1k2
da2gs2
dag5s3o
dag7ster
dag1s4t
dak7lei.
da2k3l
dak4l4ei
dak1le
dak7oor1h2
da1ko
dakoo2
dakoor1
da4k3r
dak5wa
da2k2w
4d5alar
da1la
4da2m1b2
da2m
dam6plu
da2m1p
damp2l
3dan
dan6k7e2r1k
da2n1k
dan1ke
dan6sak
da2n1s2
dan1sa
dan6s1ko
dan1sk2
dan2s5m
dan4so
dan4s5t
dan4t5r
da2n1t
daph7ne.
da2p
da2p1h2
dap2hn
daph1ne
dap4l
da5pla
4d3a2r1m
4da2r1t
d5arti
da4s.
das2
das7l4a2g.
da2s3l
das4la4g
das7traa2
das3t
dast2r
da3t4j
da1t
2d1b2
dby6lvo
dby1
dbyl1v2
2d1d
dda5k3l
dda3s4
dde6lee2
dde3le
ddel5so
dde2l1s2
dder7aal1
dde1ra
dderaa2
dde6ras2
dder7a2s.
d3d1h2
dd4hi
deba4t
de3ba
de1b2
dee4g
dee1g5r
dee4l
dee7li2g.
dee3li
4d5ee2n1h2
dee2n1
deë7skou
deë4sk2
de1ë
deë1s2
deës1ko
4de2g.
2dei
de3ka
dek6aan1
dekaa2
1de6kla2d
de1k2l
de6k5la1t
de6k4l4ei
dek1le
dek7lei.
de4k1na
de1k2n
6d5eko1no
de1ko
de6krie
de1k2r
dek1r2i
dek7rie1t
4dek1sa
de2k1s2
dek6s1ka
dek1sk2
del7appe
de1la
dela2p
dela2p1p
del5eeu
de3le
delee2
del5e2g1g2
d5ele2k1t
de6l7elek
de1le3le
6d5eleme
dele1m
de6le2ng
del7en1ge
del6fer
del1fe
del5fi
del6fos2
del1fo
delf7o2s.
de4l5oor1
de1lo
deloo2
del7op4er
delo2p
delo3pe
del6ser
de2l1s2
del1se
del4so
1del7sol1d
del3sol
del7s4o1ne
del4s1p
del6st2r
del3s1t
del7st1re
del4t6ag
del1t
del7t3a2g1t
del7weis2
del1w
4de2m1m
de1m
dem6pla
de2m1p
demp2l
den6din
1de2n1d
4d3e2ng
4den1j
den6kar
de2n1k
den1ka
den6kja
denk1j
den4k5l
den4k2r
dens7p1re
de2n1s2
dens1p
densp2r
den4t5j
de2n1t
den6t1r2i
dent2r
deo7p6le4k
de1o
deo2p
deop3l
deop1le
deo7sfee
deo1s2
deo2s1f2
deos1fe
3de1p
der5as3t
de1ra
deras2
de6ree2n1
de1re
der7ee2n.
der7emig
dere1m
der7en2t.
dere2n1t
der7fla4p
de6ri2n.
de1r2i
derm7i2n1s2
de2r1m
der5na
de2r1n
de4ro
de5ro1b2
de5roe
der5of
de5rol
der5on
der5o1w
der5p1s2
der1p
der6sjo
de2rs2
der1s1j
der5s6k2r
der1sk2
der6slu
der1sl
der6spu
der1s1p
der7t6hal
de2r1t
der2t1h2
der6ui2t
de1ru
de5sag
des2
de1sa
des7al1ni
desal1n
des5a2p
de6se2ng
de3se
des7en1ge
de4s5in
de3si
deska1t5
de1sk2
des1ka
de6skor
des1ko
1des7lee1d
de1sl
des1le
deslee2
de4sn
des7of1fe
de1so
de4sof1f
des7ok3si
des2o2k1s2
de4s5on
de4sor
de4spa
de1s1p
des7pa1r2i
des7poë1s2
despo1ë
des7prik
desp2r
desp1r2i
des7taal1
de1s1t
des4taa2
des7te3le
de4s3ti
de5stig
de4s1w
des7weë.
deswe1ë
4d5ete.
de3te
de1t
deten6te
de1te2n1t
de3t1w
4deu1ro
deur5s6w
deu2rs2
2d1f
2d1g
dge5s1p
d1ge
dges2
dg4li
2d1h2
dias4
di1a
dia7stol
dias3t
dia6zvi
dia1z
diaz1v2
dic7kie.
di1c2
di2ck
dic1ki
dicki2e
dic7tio.
di2c1t
dicti1o
die6fal
die1fa
die6kes2
die3ke
5die2n4s2
die4p2l
die1p
die6to2m
die1t
die4t5u
dig6o1fa
di1go
di2g1of
di4g1re
di1g2r
di2gs4
dig7skro
dig2s1k2
digs3k2r
dig7s4o2m.
dig2s3o
digso2m
di6ka2m1p
di1ka
di3ka2m
dik7am2p.
di4k3l
di4k2r
dik7ri2b1s2
dik1r2i
dikri1b2
di4k2w
dik7wy2n.
di4kwy
di4l5al
d2il
di1la
din6gas2
di2ng
din1ga
din4g2r
4d3i2n1l
4di2n1s2
4d3i2n1t
4d3i2n1v2
di6s2a2s2s2
dis2
di1sa
dis2as2
dis7as3si
di4so
di5son
dis6p1ne
dis3p
dis2p1n
dis7quis2
dis1q
disqu2
dit6hak
di2t
di2t1h2
dit7jies2
dit2j
2d1j
dja7d4ji.
1dja2d
d1ja2d1j
d4ji.
d4ji1a
2d1k
dklo4
d1k2l
2d1l
d3la
2d1m
2d1n
doe4k5r
doe6lon
doe1lo
doe6s1ko
does2
doe1sk2
does7ko2p
2dof
4do1gi
do1g
do4l5os2
do1lo
dol6sou
do2l1s2
dol1so
dols7ou.
3do2m
dom6p1li
do2m1p
domp2l
dom6sa2p
do2m1s2
dom1sa
4d5om1se
do1m7sli2m
dom1sl
dom2s1li
dom6s3wê1
dom3s1w
4do2m1t
don4sk2
do2n1s2
4d5oor1d
doo2
door1
4d1o2p1n
do2p
do3p6rof
dop3r
dop6rys2
do3pry
3dor
4d3or1g
dor7othy
doro2t
doro2t1h2
dor4sl
do2rs2
dor4s1t
dos6tel
dos3t
dos2
dou3t
do3y
doy4e
2d1p
d2r
3dra
dra6g3aa2
d2rag
dra1ga
4d5ra2n1d
dra7s6tan
dras3t
dras2
4d3re1ë
d1re
4d3reg
d4re1la
d4re1w
4drif1f
d1r2i
d5ri2g1t
d5ri2s2s2
dris2
4dri1v2
droë7y2s.
dro1ë
droëys2
4d5ro2n1d
d5rooi1
droo2
4d5roos2
dr4o2p
dro6pan
dr4o3pa
dr1o5p1n
dro7sfee
dros2
dro2s1f2
dros1fe
4d3rug
d5rui2m
d5rui2t
4d3rus2
4d3ry.
4d3ry1e
4d3ryk
4dry1m
d4ry1w
2ds2
ds5aa2m1b2
d1sa
dsaa2
dsaa2m1
ds5aar
1d4s3a2d
d5sa1k2r
ds5a2k1s4
ds5an2gs2
d3sa2ng
d4s3ar
ds3as2
d3se
d4s5ee2n1
d4s5ei2s.
dseis2
d4s5e1ko
d3se4l
dse7lee2r
dse3le
dselee2
d4s5e2ng
d4s5e1ra
dser6t2j
dse2r1t
dsert7ji
d4s5er1v2
d5sfee2r
d2s1f2
ds1fe
1ds3i2d
d1si
ds5i2m1p
dsi2m
ds5inde
1dsi2n1d
d4s5i2n1s2
ds5i2n1t
d2s3j
d5skee2
d1sk2
ds1ke
d4skin
ds1ki
1ds5ki2n1d
d4s2kis2
d5sko1le
ds1ko
d4s3ko2m
d5skoo2
d6skraa2
dsk2r
d6skri2t
dsk1r2i
d6skroo2
d4s3le
d1sl
d2s3li
ds6lui1e
d2s3m
d5s4mee
ds3me
ds6moor1
dsmoo2
d4s1na
d1sn
d4s5neu
ds1ne
d4s5noo2
ds1no
ds5no2t
d2s1o
d3soe
d5s4o3me
dso2m
ds3on
d7s6onde.
1dso2n1d
d7s4ondes2
d4s3o2n4t
d4s5oo1g3
dsoo2
d2s3o2p
ds3o1w
d5s4pel
ds1p
d5s2pes2
d3spi
ds5po2p
ds6prek
dsp2r
dsp1re
d7spreker
dspre3ke
ds7pre1ki
d1s1t
ds5taak1
ds4taa2
d4staf
1d5sta2n1d
d4s5te1a
d5stel
d4s5te2n1t
d5ste2r.
ds5te2r1r2
d5s2te2rs2
ds4ti
d3st2r
d3stu
d2s3w
d3sy
2d1t
dter6t2j
d1te2r1t
dtert7ji
du4e2-1
du1e
duns6te
du2n2s3
duns1t
dur6rhe
du2r1r2
durr1h2
dusie5k
dus2
du1si
du3sie
du3s6ka2p
du2s3k2
dus1ka
dus6pel
dus3p
2d1v2
dver5die2n7s9
dve2
d3ver1
1dver1d4
dverdien8s5t
1dve6si2d
dve3si
dves2
dvie4
2d1w
d4waal1
dwaa2
d5waar
5d4wa2ng
dwa6nor
dwa1no
4d3wa2r1m
dwar7s6e.
dwa2r4s3
dwar1se
d3wa1t
d4we2il
5dwer1g
dwer1k5o
d3we2r1k
dwe6tar
dwe1t
d3w2il
d4wi2ng
4dwoo2
d4wyn
dy2s5ag
dys2
dy1sa
dys6mi2t
dy2s1m
dys3mi
dys7tuin
dys3t
dys4tu
2d2-1
1d-r6ho1d
d-r1h2
e1a
eam6s2es2
ea2m
ea2m1s2
eam1se
ea2ng4
eate4
ea1t
eau7mo2n1t
e2au
eau2m
e3ba
e1b2
eb5a3d2r
eba2d
eb9cu.
eb1c2
ebou5t
ebou6t.
eb4re
ebro2n1s5
e3bro
ebro4n
eb5tui
e2b1t
ec5c1le
e1c2
e2c2c2
e4c2hn
ec1h2
ech7tiaa2
e2ch1t
echti1a
eda7g4aa1t1
e1d
eda2g
edag3aa2
eda1ga
eda5go
e3de
ed5eis2
e2dei
eder7a2s.
ede1ra
ederas2
ede7ri2ng
ede1r2i
ede6s5a2p
edes2
ede1sa
ed5of1f
e2dof
edo2rs5
e3dor
e3d4ra
ed2r
ed5re1p
ed1re
ed6s5a2k1s4
e2ds2
ed1sa
eds7kal1m
ed1sk2
eds1ka
ed4sl
eds7la1fe
eds2laf
eds5o2m
ed2s1o
ed3s1p
ed5s4we
ed2s3w
ed5ui2t
e2d2w
e4d5woo2
ed5ys3t
edys2
ed3y1w
ee5a2g1t
ee1a
eed6atu
ee1d
eeda1t
eed5we
ee2d2w
ee4dy
ee2f
eef7laag
ee1fl
eeflaa2
eef7lo3pi
eeflo2p
eef7ra2n1t
ee1f2r
eef7re2k.
eef1re
eeg3l
eeg5ru
ee1g2r
ee2g3s4
eeg6s3di
eeg2s1d
e5ei1la
ee2il
ee2k
eek5a2s2s2
ee1ka
ee3kas2
ee5klaa2
ee1k2l
ee5k2lag
eek5lo
ee1k3n
eek5o1g
ee1ko
eek7oo2rs2
eekoo2
eekoor1
eek7rooi1
ee1k2r
eekroo2
ee1k3w
eek6wal
eel5a2p
ee1la
eel6doo2
eel1d
eel7doos2
eel5ee2n1
ee3le
eelee2
eel7ee2r.
eelee2r
eel5ei
eel7ind2r
ee3li
eeli2n1d
eelin2g7s6
eeli2ng
ee6li2n1s2
eel5i2n1t
ee4l3o
ee5l4o1b2
ee3l6oo1d
eeloo2
eel6oon1
eel5sa
ee2l1s2
eel7s6na4g
eel1sn
eels1na
eem7onde
ee1m
eemo2n1d
eem5ou
ee2n1
5een2d1j
ee2n1d
een6ema
ee1ne
ee3ne1m
ee5n4en
3een1j
ee2n1k4
een5k2l
een7slo2t
ee2n1s2
een2sl
een4s3lo
een5s1m
eens6pa
eens1p
een7swee
een2s3w
een7to2p1p
ee2n1t
een4to2p
ee4ny
eep7e3sel
ee1p
eepe3se
eepes2
eep7lee2r
eep2l
eeplee4
eep1le
eep7loo1g3
eeploo2
ee4po2p
eep7roes2
eep2r
eepr4oe
eep6sa2m
ee2p1s2
eep1sa
ee1p7s4ke1p
eep4sk2
eeps1ke
eeps5ko
eep7skui
eeps1ku
eep6s4ti
eeps1t
ee2r
eer5a2p
ee1ra
ee5re1d
ee1re
eer5ee
eer5e2n1d
eer5in
ee1r2i
ee5ro1b2
ee1ro
eer7oe2s.
eeroes2
eer5o2m
eer5on
ee5ro1w
eer5p1s2
eer1p
eer7skur
ee2rs2
eer1sk2
eers1ku
eer4s7la2m
eer1sl
eer7sme1d
eer2s1m
eers3me
eer6so1w
eer1so
ee1r3u
eer6us3t
eerus2
ee1ry4
eer5ys2
ee2s3
ee4s.
ees6a1la
ee1sa
ees6a2p.
eesa2p
ees6le1p
ee1sl
ees1le
ees5me
ee2s1m
ees7mu2il
ees4mu
ee5s1na
ee1sn
ee5s4ny3
ee3s6o2p.
ee1so
ee2s1o2p
ees6p1re
ee1s1p
eesp2r
ee5s4taa2
ee1s1t
ees6tal
ees4t2r
ee6s3ty2d
ee2sty
ee4s1w
ee5s4y2d
ee1sy
ees6y1fe
eet7appe
ee1t
eeta2p
eeta2p1p
ee4t7ee2n1h2
ee3te
eetee4n1
ee4ti
eet5in
eet7re2k.
eet2r
eet1re
ee6troe
eet7roe1d
eet7ruik
eet7wiel
eet4wi
ee2t1w
eeu3g4
eeu5in
eeus4
eeu7spoe
eeus3p
eeu4spo
eeu5t2j
eeu2t
eeu6u2r.
ee1uu2
eeuur1
ee5ys3t
eeys2
e3êr
eê1
eë5aan1
e1ë
eëaa2
eëk4s5t
eë2k1s2
eël7eier
eë4lei
eë1le
eëlei1e
eël7yste
eë1ly
eëlys3t
eëlys2
eë4na
e3ër
eër7agti
eë2rag
eëra2g1t
eë5ran
eër7ar2m.
eë4ra2r1m
eë5ro1d
eër6s1ke
eë2rs2
eër1sk2
eë4sk2
eë1s2
eët6ste
eë1t
eë2t1s2
eë1ts1t
eëts7te.
e4faf
e1fa
ef5a2f1s2
ef5e2k1s2
e1fe
5effek
ef1f
ef1fe
ef5i2n1h2
e1fi
e1fl
ef5loo2
e4f1ly
e4f5o2m1s2
e1fo
efo2m
ef5ou1e
ef3s1t
e2f1s2
efs6tal
eg5a2m1p
e1ga
ega2m
ega5s4k2
egas2
e3ge
ege6las2
ege1la
egel7a2s.
ege4s7per
ege3s1p
eges2
ege3s4t
ege6vwo
ege1v2
egev1w
e6gin1ko
e1gi
egi2n1k
eg5o2g1g2
e1go
ego1g
e4g5rig
e1g2r
eg1r2i
egs7enti
e2gs2
eg1se
egse2n1t
eg6s5i2n1s2
eg1si
eg6s5i2n1t
eg3s1k2
egs6lo2t
eg2s3l
egs6p1re
egs3p
egsp2r
egs6p1r2i
egs6pyk
eg6s7taal1
eg1s1t
egs4taa2
egte6re
e2g1t
e4g3ui
e1gu
eher6in
e1h2
ehe2r
ehe1r3i
ehe7rin1n
eho6kra
eho1k2r
eib7ni2z.
ei1b2
ei2b1n
eib1ni
eibni1z
eid7ro2k.
ei2d
ei3d2r
eid7saa2m1
ei2d2s2
eid1sa
eidsaa2
eid7sa1lo
eid7si2r1k
eid1si
eidsi1r
eid7skou
eid1sk2
eids1ko
eid7sku.
eids1ku
eid7s6pa.
eids3p
eid7s6pek
eid7ste.
eid1s1t
eid7stoo2
eid7su2g.
eid1su
ei1e
eie7n3aan1
eie1na
eienaa2
eig6h-n
ei2g1h2
eigh2-1
eig7opro
ei2g4o2p
ei1go
eigop3r
eik7aar1d
ei1ka
eikaa2
ei5k1no
ei2k3n
eik7wy2d1t
ei4kwy
ei1k2w
eikwy2d
eil6spa
e2il
ei2l1s2
eils1p
3ei2n1d
ei4n5e1d
ei1ne
ein7ee2d.
einee2
einee1d
ein7glas2
ei2ng
eing1l
ei4non
ei1no
ein7oor1d
einoo2
einoor1
ei4n5o2p
ein7otte
eino2t
eino2t1t
ein6sa2d
ei2n1s2
ein1sa
ein6se1p
ein1se
ei6s7an2gs2
eis2
ei1sa
ei3sa2ng
ei5s3ei
ei1se
ei6s5i2n1d
ei1si
ei5sja
ei2s1j
eis7ka2m1m
ei2s3k2
ei4s1ka
ei4s3ka2m
eis6ka1w
ei5s4kê1
eis6ko2t
eis1ko
eis6laa2
ei2s3l
4ei1so
eis7ouer
ei3sou
eisou1e
eis6pi1r
eis3p
ei5s6tel
eis3t
ei5s6t1re
eist2r
ei2t2
eit7ha2n1d
ei2t1h2
ei3t2j
eit7k4lin
ei2t1k
eit1k2l
eitk1li
eit7nis3p
ei2t1n
eit1ni
eitn2is2
eit7onde
ei3to
ei4to2n1d
ei5tra
eit2r
eits5ko
ei2t1s2
eit1sk2
eit1s5l
eit1s5o
eit7spor
eits1p
eit7stak
ei1ts1t
eit7stra
eitst2r
eit2s5w
eï5mi2t
e1ï
eï2m
eï4na
eïn7k4lin
eï2n1k
eïn4k3li
eïn1k2l
eï4n3o
eï4nu
eï5o1ni
eï4sl
eïs2
eja7s6tas2
e1j
ejas3t
ejas2
ek5aan1
e1ka
ekaa2
ekaars8te
ekaa2rs2
ekaar1s1t
e4k5a2k1s2
e3kan
e4k5a2s1g
e3kas2
e3ke
e4k5ee2n1
ekee2
eke7n6aar
eke1na
ekenaa2
eke6ta2m
eke1t
e3kê1
e4k5i2n1s2
e1ki
e2k3k
e4k5les2
e1k2l
ek1le
e5kleu
e4k3lê1
e4k5lo1g
e3koe
e1ko
eko6mol
e3ko2m
eko3mo
eko2m4s2
ek5om1sl
ekoms5t
5eko1no
ek5ooi1
ekoo2
e3ko2p
eko6pa2p
eko3pa
e4k5o2p1m
e4k5o2p1n
ekor6da
ekor1d
eko7ru2m.
ekor1u
ekor2u2m
ekou6s4t
ekous2
ek5owe
eko1w
e1k2r
ek5ra2d
e4k5rok
e5kro2m
ek5rug
ek6sa2p1p
e2k1s2
ek1sa
eksa2p
5ekse2m1p
ek1se
ekse1m
eks5e1sk2
ekses2
ek6s7in1ge
ek3si
eks4i2ng
eks7lo1gi
ek1sl
ekslo1g
e2k1s7lo2k1s2
ek4slok
ek4s7ou3to
ek1so
ek3sou
eksou2t
ek4s5pi1r
ek2s1p
ek3spi
eks5po
eks6poe
eks6tel
eks1t
ek6sten
ek4s3ti
eks7uu2r.
ek1su
eksuu2
eksuur1
e4k5ui2t
e1ku
e4kwê1
e1k2w
ek5wie
e4k3wu
e1ky
e1la
e6l5aa2n1d
elaa2
elaan1
e4l5aa2n1h2
e6laa2n1v2
e4l5aar1d
el5a2d1m
ela2d
e4l5a2d1v2
e2l3af
e2l3ag
e5l4a2g.
ela7klon
ela2k3l
e4l3al
e5la1r2i
e4l5a2r1m
ela7slan
ela2s3l
elas2
el5a4s3p
eld7adel
el1d
el1da2d
elda3de
el4da2p
el5de.
el6d5e3le
eld7er1fe
eld7evan
elde1v2
eld7olie
eldo1li
eld7onde
el1do2n1d
el1d7smi2d
el2ds2
eld2s3m
elds3mi
e3le
e4l5eien
elei1e
eleis6t
eleis2
elei7sta
e6lelek
e1le3le
e4l5er2t1s2
ele2r1t
ele7se2t1t
ele4se1t
ele3se
eles2
ele6too2
ele1t
el4faa2
el1fa
elf6abr
elfa1b2
elf6ei2t2
elf3ei
el1fe
elf7en-1d
el4fen
elfen2-1
elf5e2r1k
elf6lan
elf3l
elf6les2
elf2le
el4fon
el1fo
elf7on2t1h2
elfo2n1t
elf7on2t1p
el6foo2p1
elfoo2
elf7oo2p1s2
el6foor1
elf7oo2rs2
elf6ron
el2f3r
elf7twyf
elf2t4w
el2f1t
e3li
eling8ste2l1l
eli2ng
elin2gs2
eling1s1t
e4l5i2n1h2
e6lin1ko
eli2n1k
e4l5i2n1v2
elk7na1ge
el1k
el1k2n
elk1na
el2k1s4
el4kwi
el1k2w
el4l1v2
e2l1l
elm5a2g1t
el1m
e1lo
el4o1b2
e4lol
el5o1li
e4l5o2n1d
e4l5o2n1t
e4loor1
eloo2
e4l5o2p1d
elo2p
el6o3pe.
elo3pe
e4l3or
el5p2hi
el1p
el2p1h2
els7an2gs2
e2l1s2
el1sa
el3sa2ng
el5s1fe
el2s1f2
el6si2n1d
el1si
el5s7ind2r
el6skan
el1sk2
els1ka
el6s3ko2m
els1ko
el6s7ko2r1r2
els7kri2t
elsk2r
elsk1r2i
els7lo2f.
el1sl
els4lof
el5s3mi
el2s1m
els7mo1ra
el4s6na4g
el1sn
els1na
els7noo1d
el4snoo2
els1no
els7onde
el1so
elso2n1d
els7oo2r1k
elsoo2
elsoor1
el6stek2
el3s1t
el6s7tran
elst2r
els7wa1re
el2s1w
el5swee
elt7ak3ke
el1t
elta2k1k
e1lu
e4l5ui2t
eluit6j
elui7tji
e3ly
ely6kaa2
ely1ka
em5a2p1p
e1m
ema2p
e5ma1s1j
emas2
eme6lek
eme3le
eme6le1w
eme6ron
eme1ro
eme4s2
eme7si2s.
eme3si
emesis2
e1me2s5m
eme1s5t
e5me2t1f
eme1t
em5eva
eme1v2
e4moef
e3moe
e2m3o2p
e4m5or1g
emp7laag
e2m1p
emp2l
emplaa2
emp6sk2r
em2p1s2
emp1sk2
ems4p
e2m1s2
em5s3p2l
e4n1af
e1na
ena7g6las2
enag1l
e4n5a2g1t
en5a2k1k
e4n5al1t
e4n5a2r1t
ena6spe
ena3s3p
enas2
en6d5a2g1t
e2n1d
enda2g
ende7ro.
ende4ro
end7raak1
end2r
en3dra
endraa2
end7ri2t.
end1r2i
endri2t
ends7oë.
en2ds2
end2s1o
endso1ë
end7s6o2n1s2
ends3on
end7u3r2e.
endu1re
end6wer
en2d1w
e4n5e2il
e1ne
en4en
e4n5e2n1t
5ener1g
ener6t2j
ene2r1t
enert7ji
eng6hor
e2ng
en2g1h2
eng4la
eng1l
eng6lor
e3ni
en4ig
en4i2m
e4n5i2n1h2
e6n5in2k.
eni2n1k
eni7s6o2m1s2
en2is2
eni1so
eniso2m
3enji
en1j
en6k3i2n1h2
e2n1k
en1ki
en3ko2m4
en1ko
enko2m1s5
en2k3w
e5n4o2m1m
e1no
eno2m
eno7ry2n.
en5ou2t
ens7ade1m
e2n1s2
en1sa
ensa2d
ensa3de
en6sa2l1l
ens7a3r2e.
ensa1re
en1s7ei1se
en1se
enseis2
ens7elek
en3s4el
ense3le
ens7elik
ense3li
en5sen
ens5er1v2
en1s7es3se
enses2
ens2e2s2s2
ens6ha1w
en2s1h2
ens3ha
en5s4ie
en3si
ens7in1ga
ens4i2ng
en5sji
en1s1j
ens7koei1
en1sk2
ens1ko
ens7ky1ke
ens7luik
en2sl
en3slu
ens6me1d
en2s1m
ens3me
ens7nu2k.
en1sn
ens1nu
en4son
en1so
ens7onru
enson1r
ens7onva
ens4o2n1v2
en6s3pei
ens1p
ens7pis3t
enspis2
ens7po2t.
en4spo2t
en6spou
ens7pous2
en6s7taak1
ens4taa2
ens1t
en6stak
ens6ta2m
en6s5te1h2
ens6t4ei
ens6tel
ens7te3le
en7s6te2rs2
en7s6te2s.
enstes2
ens6te1t
ens6teu
ens6too2
ens7too2m1
ens7trek
enst2r
enst1re
ens7ui2l.
en1su
ens4u2il
ens7u3r2e.
ensu1re
en5sy.
en1sy
en3t5a2k1t
e2n1t
en6te2k1s2
entek2
ent7inte
en1ti2n1t
ent7ri2f.
ent2r
ent1r2i
ent7ro2k.
ent6sin
en2t1s2
ent1si
ent6son
ent1so
ent6spa
ents1p
ent6wen
en2t1w
e2n3ui
e1nu
enu6lin
enu1li
enu5s3k2
enus2
enu5s4t
e3ny
en-7steg
en2-1
en-s2
en-s1t
e1o
eoe4s2
eo5f1ag
eo1fa
eo3g4n
eo1g
eoi6ste
eoi1
eois4t
eois2
eop6le4k
eo2p
eop3l
eop1le
eo3ro
eo1s2
eos4t
eo3t2r
eo2t
e4p1af
e1p
e4p3ag
epe6loo2
epe1lo
e4p5e2m1m
epe1m
e4p5epi
epe1p
eper5s7te
e3pe2rs2
eper1s1t
5epide1m
epi2d
epi3de
e4p5i2n1t
ep4la
ep2l
e4p5la2p
ep5li2g1g2
epli4g
ep1li
ep5lus2
epoe1t4
epo6nin
epo1ni
e4p5o2p1s2
e1po2p
e4p5rei
ep2r
ep1re
eps7ameu
e2p1s2
ep1sa
ep3sa2m
epsa3me
ep6s5eis2
ep1se
ep4s5i2d
ep1si
ep4s3j
ep4sk2
eps7ka1no
eps1ka
ep5s1ki
eps7kohe
eps1ko
epsko1h2
ep4slu
ep1sl
eps7luik
ep4s6o2m1s2
ep1so
epso2m
ep4s5on
ep4s5p2r
eps1p
eps7waar
ep2s1w
epswaa2
e4p5ui2t
e1ra
er4a.
e4r5aan1
eraa2
e4r5a2f1d
e4r5af1h2
er5af1sk2
era2f1s2
e4r5a2f1t
e4r5a2f1v2
e4r5a2f1w
e2r3ag
era7g4ree4
er4a1g2r
era4g3re
era7k6les2
era2k3l
erak1le
er5a2k1s2
er5a2k1t
e4r5al1b2
er6al2d.
eral1d
e4r5al1t
er5a1na
e5r4anda
era2n1d
e4ra2p1p
era2p
er3ar
era6ser
eras2
era1se
era7ui2t1v2
er2au
eraui2t
erd7ry1le
er1d
erd2r
erd7s6li2p
er2ds2
erd2s3li
erd1sl
erd7t6ree
er2d1t
erdt2r
erdt1re
er3d1w
er4e1b2
e1re
er6ee2n1k4
eree2n1
e4r5ee2rs2
eree2r
e4r5ef1f
e4r5ei1e
e4r5e2il
er4ek
e4r5eks1t
ere2k1s2
er5el1m
e4r5e2m1m
ere1m
e6ren2g1t
ere2ng
e4re3ni
e4ren1j
e4r5er1g
er5er1v2
e3r4es2
e4r5e3sel
ere3se
ere7s4pi1o
ere4spi
ere1s1p
eres6ta
ere1s1t
ere6s2t1p
ere7te2m1m
ere3te
ere1t
erete1m
e5r4ewa
ere1w
e1rê1
er4f1h2
er6f5laa2
erf7lee2n1
er3f2le
erflee2
er6fle4t
erf7le2t1t
er5flo
erf7lu2g.
erflu2g1
erf7lu2s.
erflus2
erf7omhe
er1fo
erfo2m
erfo2m1h2
erf7oo2m.
erfoo2
erfoo2m1
er4f1p
erf7reuk
er4f1re
er1f2r
erf7ruik
erg7aren
er1g
er1ga
erga1re
erg7ly2n.
erg4ly
er4g3lyn
erg7ren1m
er1g2r
erg4ren
erg1re
erg7ry2m1p
er4gry1m
erg6rys2
erg6s3ho
er2g3s2
erg2s1h2
erg7stra
erg1s1t
ergst2r
erg7uit6j
er1gu
er4gui2t
er4i2d
e1r2i
eri5f3r
e3rig
eri4g5a
e4r5i2n1d
e6rin2k.
eri2n1k
e6rin1na
erin1n
e4r5i2n1s2
e4r5i2n1t
e6r5ital
eri2t
eri3ta
eri7t6re1a
erit2r
erit3re
er4k5aan1
e2r1k
er1ka
erkaa2
erk7e3sel
erke4s3
er1ke
erke3se
erk6has2
er2k1h2
er6k7in2k.
er1ki
erki2n1k
er6ki2n1s2
erk7inwy
erki2n1w
er4k1j
er6kla1t
er1k2l
er5k1le
erk7onde
er1ko
erko2n1d
er4k6o2p1n
erko2p
erk6s5on
er2k1s2
erk1so
erk7s2pas2
erk2s1p
erk6sto
erks1t
erk7uu2r1r2
er1ku
erkuu2
erkuur1
er6kwe1ë
er1k2w
erk7weë.
erk7ywer
erky1w
er5lik
er1l
er1li
er5lui
erm4a
e2r1m
er6m7aa2n1h2
ermaa2
ermaan1
erm7af1sl
erm1af
er4ma2f1s2
ern7eiwi
e2r1n
er1ne
ernei1w
ern7kwes2
er2n1k
ern2k2w
er4n1m
er4n1n
ern7oe2s.
er1no
er4noes2
er4n1r
e1ro
e4r5oe1w
er3o1ë
e4r5o2g1g2
ero1g
e3ro1ï
e5ro2k.
e4r5o2k1s2
e4r5o1li
er5o2m.
ero2m
er5o2m1h2
e4r5o2m1s2
er6ona.
ero1na
e4r5oo1g3
eroo2
e5roo2m1
e4r5oo2n1d
eroon1
e5roos2
e4r3o2p
e5r4o3pa
er6opla
erop3l
e5r4o3po
e4r1or
e5r4o1ra
5ero3si
eros2
e4r5o2s2s2
ero7s6t2il
eros3t
eros3ti
e4r5ou1d
er5ou1e
erou6t.
erou3t
erp7an1ke
er1p
erpa2n1k
er6pi2n1h2
erp7inho
erp6lan
erp2l
erp7ruik
erp2r
erp6sig
er2p1s2
erp1si
err6ein
e2r1r2
er1re
ers7as3si
e2rs2
er1sa
ersas2
ers2a2s2s2
er6s5e3li
er1se
er3sel
ers7ete.
erse3te
erse1t
er6s7inda
er1si
ersi2n1d
ers7jean
er1s1j
ersje1a
ers7kai1a
er1sk2
ers1ka
erskai1
er6ska1j
ers7kaju
er7ska1ke
er3skak
er6s7ka1ki
ers7ke3te
ers1ke
erske1t
ers7ki2s2s2
ers1ki
erskis2
er6s7koe1t
ers1ko
ers7koor1
erskoo2
ers7ko2p.
ersko2p
er5sky
ers7le2s2s2
er1sl
ers1le
ersles2
ers7lo1ne
ers7lui2d
ers6mal
er2s1m
ers3ma
er7s6ma3ra
er6sma1t
er4s5o2m
er1so
ers7onvr
ers4o2n1v2
ers6o2p1n
er2s3o2p
ers7ower
erso1w
er6s7pien
er1s1p
ers7pu2t.
erspu2t
ers7scen
er2s2s2
erss1c2
er5ste
er1s1t
ers7te3le
er4s6te1o
ers4ti
er6s7treg
erst2r
erst1re
ers7waar
er2s1w
erswaa2
ert5aan1
e2r1t
ertaa2
er6taa2p1
ert7aa2p.
er5ta2p
ert7a3pe.
erta3pe
er6te2n1d
er5t7en2d.
er5tes2
ert6hal
er2t1h2
ert7ja2k1k
ert2j
er2tja
ert7opin
erto2p
erto3pi
ert7or1re
erto2r1r2
er6tres2
ert2r
ert1re
ert7ro2k.
ert1s5l
er2t1s2
ert7uu2r.
ertuu2
ertuur1
ert6wak
er2t1w
er5twi
e1ru
erug3
er5uin
er5ui2t
e2r3uu2
e1ry
ery7doel
ery2d
ery7s6alf
ery4s3a
erys2
erys6ma
ery2s1m
ery7smaa2
ery7suur1
ery4su
erysuu2
ery7trek
ery2t
eryt2r
eryt1re
e5saa2n.
es2
e1sa
e4s3aan1
esaa2
e5s3a1ge
e2sag
es5a2g1t
es4ak
es5a2l1l
esa6mol
e3sa2m
esa3mo
es4an
es3c2
es4d1h2
e2s1d
e3se
e4s5ee2n1
e4s5epi
ese1p
es4er
e3si
es4i1a
e3s4ie
es4if
esi6gei
esi1ge
e4si2l1l
es2il
e1sin6s5i
es2i2n1s2
es4i2t
e2s1j
e4s5ke.
e1sk2
es1ke
es5k1le
e2s3k2l
es2ko2r6s2
es1ko
eskor1s7t
e5s4ku2t
es1ku
e3sla
e1sl
es5le1m
es1le
es4li2p
e2s1li
e2s1m
es4me
es5me.
es5men
es9mè.
e5smou
es4mu
es5nie
e1sn
es1ni
e4s5noo2
es1no
es4ny3
esoe4t6j
e1so
esoe1t
esoe7tji
eso7f6a1gu
eso1fa
eso2f1ag
e3s4ol
e3s4oo2
e3s4ou
e1s1p
e5spel
e4s5pen
e4sper
e4s5pe1t
es5pi1r
es4p1li
e2s3p2l
es4pra
esp2r
ess6o2p1v2
e2s2s2
es3so
es2s1o2p
e1s1t
e2s4t.
e5s4ta2d
es5tas2
e4s5te.
e4s5te1a
es5te3li
e4s5tes2
este1s5o
est6he2r
e2s2t1h2
es6ti2k.
es3ti
es5toi1
e4s5to1s2
e6strak
est2r
es4t1re
e5s4tuk
e3s1w
es4y2d
e1sy
e4t3ag
e1t
eta7s6tas2
e1tas3t
etas2
e3te
e4t5ei1e
ete5r6aa2
ete1ra
etie4l5
5eti1ke4
et7jie-k
et2j
etjie2-1
etk6y1si
e2t1k
etk1ys2
eto6no2p
eto1no
e4t5o2p1v2
eto2p
e4t5or1d
e4t5ra2m
et2r
e6tre3ke
et1re
e6tre4ko
etre7kor
et5ri2m
et1r2i
ets7fy2n.
e2t1s2
et2s1f2
ets1fy
e1t6ska1t
et1sk2
ets1ka
ets7kato
ets7kous2
ets1ko
et6s7krie
etsk2r
etsk1r2i
et6s5la2p
et1sl
ets6maa2
et2s1m
ets3ma
et4s5o2n3g
et1so
et6spaa2
ets1p
et4sp2r
et6stek2
ets1t
ett6r2e.
e2t1t
ett2r
ett1re
e4t5ui2t1s2
e1tui2t
et5unie
etu1ni
et4wi
e2t1w
et5win
et4wy
e1t5ys3t
etys2
2eu.
eug6rie
eu2g3r
eug1r2i
e3ui2t
euk7inte
eu1ki
euki2n1t
eu1k4l
eu4l7eien
eu1le
eulei1e
eu4loo2
eu1lo
eu5mon
eu2m
eum7ui2t1g
eu3mu
eu4mui
eu4mui2t
eu4na
eun6sla
eu2n2s3
eun2sl
eup7aa2n1d
eu1p
eupaa2
eupaan1
eu4ra
eur5aa2
eur6aal1
eu5ral
eu4ree
eu1re
eur7ee2t.
euree1t
eu6re2g.
eu6re2g1t
eur7eg2t1h2
eu4r7eks1t
eure2k1s2
eur7ele1m
eure3le
eur7s6par
eu2rs2
eur1s1p
eu4sa
eus2
eus7a3pe.
eu4sa2p
eusa3pe
eus7ji2g.
eu1s1j
eus6ko2t
eu2s3k2
eus1ko
eus6ta1c2
eus3t
eute4l
eu2t
eu3te
eu5te1m
eu3t2r
e1uu2
2eu2-1
eva7kwaa2
e1v2
eva2k2w
eva6les2
eva1le
evr6o1re
evr1or
evu6e2s.
evu1e
evues2
ewa2l4s5
e1w
ew4ar
ewee4
ewe7gaan1
e3weg
ewe4ga
ewegaa2
ewe7goe1d
ewe4go
ewe7inde
ew3ei2n1d
e5weis2
ewe6nee2
ewe1ne
ewen8s7tes2
ewen6ste
ewe2n1s2
ewens1t
ewe6r4es2
ewe1re
ewe7s4pan
ewe4s1p
ewes2
ewi2k4s2
ewiks7te
ewiks1t
exy7s6te.
exys3t
exys2
ey2n4o
e5yste
eys3t
eys2
e3y1w
e4z1ka
e1z
ez9ra.
è1r
ê1
êe4ro
êla7flui
ê2laf
êlaf3l
ê4rde
êr1d
êre6loe
ê1re
êre1lo
ê4rhe
êr1h2
1ë
ë1g
ëi3e
ëk4sk2
ë2k1s2
ëk2s3p
ëk5s6pek
ëk4s1t
ë3laa2
ël5a2g1t
ë2lag
ël5al1b2
ël5a2s.
ëlas2
ë4lei
ë1le
ël5e2n1t
ëlf4l
ël5f2le
ë5l4oo2p1
ëloo2
ëls7ku2il
ë2l1s2
ël1sk2
ëls1ku
ë4n5a2g1t
ë1na
ën4t2r
ë2n1t
ënt5re
ëp1re4
ë1p
ëp2r
ë4r5aan1
ëraa2
ë4r5a2f1d
ër5of1f
ëro1g4
ëro3s2
ër5owe
ëro1w
ërs7ke2n1t
ë2rs2
ër1sk2
ërs1ke
ë1ry
ë1s2
2f.
1fa
f5aa2n1b2
faa2
faan1
f3aar
f3a2d
2f1ag
f4a3gi
fa1h7re2n1h2
fa1h2
fa2hr
fah1re
fai6r-n
fai1
fai1r
fair2-1
fak6ste
fa2k1s2
faks1t
faks7te.
fan4t5j
fa2n1t
fan4t2r
fan1t6s5t
fan2t1s2
f3a2p
f3a2r1t
faru6q.
fa1ru
faru1q
2f1b2
2f1d
f5dein
f2dei
fde4s2
fde7sa1ke
fdes4ak
fde1sa
fde7s2e2s2s2
fde3se
fdeses2
fde7skei
fde1sk2
fdes1ke
fde7stor
fde1s1t
fde7stra
fdest2r
fde7sus3t
fde1su
fdesus2
f3d2w
1fe
4fee2n1
3f4ee2s3
f3ei1e
fe4l5a2p
fe1la
fel7ase1m
felas2
fela1se
fel7enti
fe3le
fele2n1t
fe6loon1
fe1lo
feloo2
fel7oo2n1d
fel5s4m
fe2l1s2
fel6spoo2
fels1p
fe4lu
fe4ly
fel5ys2
fer6sk2r
fe2rs2
fer1sk2
fers7kra
fer6s1ku
fers7kui
f5er2t1s2
fe2r1t
fe2s2
fe1s3t
fet7ete.
fe3te
fe1t
fete3te
fete1t
f1f
ffe6las2
f1fe
ffe1la
ffe6re1t
ffe1re
ffe6te1t
ffe3te
ffe1t
ffi6ee2k
f1fi
ffi1ee
ffie2s6m
ffies2
ff5rei
f1f2r
ff1re
f1g
fge7sper
f1ge
fge3s1p
fges2
fg4ha
f2g1h2
fg4li
fg4ly
fgo2d4s5
f1go
fgo1d
1fi
fi3d
fid6ji2-1
fi2d1j
fie7eks1t
fi1ee
fiee2k
fiee2k1s2
fie7l6a1fo
fie1la
fie2l3af
fie7smaa2
fie2s1m
fies2
fies3ma
fie4s5o
fie6tol
fie1t
fi5lag
f2il
fi1la
fil4m5a
fil1m
4fin1r
fi5sto
fis3t
fis2
fi2t4z
fi2t
2f1k
fkom6s1t
f1ko
f3ko2m
fko2m1s2
fkoms7te
fla4p
fla3p5o
f2le
f5lee4s3
flee2
f5le3se
fles2
fle4t
flet5j
flex7o2r.
2f1li
5f4liek
3f4li2t
2f1m
fmo4no
f1n
fn2i4s3
f1ni
1fo
f3of
4fof1f
fok4s5t
fo2k1s2
fol4k3
4fo2m1s2
fo2m
5fon2ds2
fo2n1d
fond6sk2
fond6s1t
fonds7te
f5on2t1b2
fo2n1t
5fonte
f5on2t1l
f5oor1l
foo2
foor1
2fo2p
fo4po
fop7spen
fo2p1s2
fops1p
f5orde
for1d
f3or1g
for7ok3si
foro2k1s2
fo5ro2m
fo3r1u
fos7fee2n1
fos2
fo2s1f2
fos1fe
fout5j
fou2t
fox7hi2l1l
fox2h2
foxh2il
fox7stra
foxs2
foxs1t
foxst2r
fp4sa
f1p
f2p1s2
1f2r
frag6aa2
f2rag
fra1ga
4fra2m
f4ras2
f4ren
f1re
fre4s5k2
fres2
4f3rig
f1r2i
fri6too2
fri2t
fri3to
f4ro1d
fru5ga
f3ry
2f1s2
fs5a2g1t
f1sa
f2sag
f4s5a2n1k
f4s5e1ko
f1se
f4s3kon
f1sk2
fs1ko
fs5lo1g
f1sl
f3s1m
f4s3ma
fs4me
fs4mi
f3s1p
f2s4p2l
f4spro
fsp2r
f4s5tak
fs1t
fs4ti
2f1t
ft1re4
ft2r
ftre5d
ftrek5
1fu
fu4c1h2
fu1c2
fur6o1re
fu1ro
fur1or
fu3s3o
fus2
4fuur1
fuu2
f5uu2r.
2f1v2
fva4l
2f1w
1fy
fyn7gou4d3
fy2n1g2
fyn4gou
fyn1go
2f1ys2
fyt7appe
fy2t
fy3ta
fyta2p
fyta2p1p
fyt7jie.
fy3t2j
2g.
1ga
g4aai1
gaa2
gaam6s7te
gaa2m1
gaa2m1s2
gaams1t
g4aa2n.
gaan1
g5aanbi
gaa2n1b2
g6aan2d1h2
gaa2n1d
g5aa2n1l
g5aa2n1w
g4aa1t1
4g3a2d1d
ga2d
2g1af
g4a1fo
2g1ag
g4a1g2r
ga5g3re
gag6re1p
ga2k4l
ga5kla
gal7af1sk2
ga1la
ga2laf
gala2f1s2
gal7appe
gala2p
gala2p1p
galei5
ga1le
gal7oo2g.
ga1lo
galoo2
galoo1g3
gan6gra
ga2ng
gan1g2r
4g5an1ke
ga2n1k
gans7ke.
gan4s1ke
ga2n1s2
gan1sk2
g3a2p1p
ga2p
ga3ra
4g3ar1b2
4g3a2r1m
ga2r4s3
gars6ti
gar1s1t
gar7stig
g5arti
ga2r1t
ga6sa2r1m
gas2
ga1sa
gas7ar2m.
ga3s6mok
ga2s3m
ga4so
ga5sol
gas6pel
gas3p
gas6t1re
gas3t
gast2r
ga4t2r
ga1t
gat7ruik
gay7n6o2r.
gay2n1o
2g1b2
gby3s4
gby1
2g1d
gde7roof1
gde4ro
gderoo2
gdut7jie
gdu2t
gdut2j
g3d1w
gd4wa
1ge
ge3d
gedi4s2
g5ee2n1h2
gee2n1
gee6tal
gee1t
geet7a2l.
geë1s3
ge1ë
geë6s3ti
geës1t
ge3f
4ge4f1f
ge1f4l
ge1g2
4ge2g.
gege3s5p
1ge3ge
geges2
geg6u2il
ge4g3ui
ge1gu
g3ei1e
geï7mi1g2r
ge1ï
geï2m
ge7k6li2k.
ge1k2l
ge4k3lik
gek1li
4ge2k1s2
ge1k4y
gel6a2g1k
ge1la
ge2l3ag
gel6da2d
gel1d
gel4do
ge5lol
ge1lo
ge5loo2
ge6loon1
gel7oo2n1d
gel6s7te.
ge2l1s2
gel3s1t
gemi7au.
ge1m
gemi1a
gemi2au
gem6o2p1p
ge2m3o2p
3gen
gen4d2r
ge2n1d
gen6dur
gene1s5t
ge1ne
genes2
4ge2ng
ge4oi1
ge1o
gep4a
ge1p
ge5p3ag
geper6s1t
ge3pe2rs2
gepo2n6s2
3ger
ge5ra2p
ge1ra
ger6ar1d
ger3ar
ger5e3te
ge1re
gere1t
ger7iden
ger4i2d
ge1r2i
geri3de
ge6roef
ge1ro
ge4r6o2g1g2
gero1g
ger6spo
ge2rs2
ger1s1p
ger5s1w
ger6ui2t
ge1ru
ge3sa
ges2
ge5s1fe
ge2s1f2
ge5sin
ge3si
ges7ja2g1t
ge2s1j
ge2sja
ges3jag
ge1s4k2
ges7ka3de
ges1ka
geska2d
ge3sl
ge3s4m
ge5s1ne
ge1sn
ge3s1p
ges7per1b2
ge4sper
ge4s7pe2rs2
ge4s7pe2s.
gespes2
ge3s1t
ges6tas2
ges6tig
ges3ti
ge3s4w
ge5tja
ge1t
get2j
ge3t1w
ge3ui
ge5u2m.
geu2m
gev7woe4s2
ge1v2
gev1w
gewen6s7te
ge1w
gewe2n1s2
gewens1t
ge1y
2g1f
g3fl
g2f4li
2g1g2
g3ga
gga7kwee
gga2k2w
ggak3we
gga7s6t1re
ggas3t
ggas2
ggast2r
gga5t4j
gga1t
gge6s3ti
g1ge
gge3s1t
gges2
2g1h2
5g4hoer
gho7g6hok
gho1g
g1h2o2g1h2
gho4l
5g4holf
g4h2s.
ghs2
ghu6moe
ghu3mo
ghu2m
1gi
g5iden
gi2d
gi3de
gie6far
gie1fa
gi4fa
4gi2m1p
gi2m
gin6gaa2
gi2ng
gin1ga
4g5in1r2i
gin1r
gi2p4s2
gi2p
gip7sie1t
gip1si
gip3sie
gis7enti
gis2
gi1se
gise2n1t
gi5tra
gi2t
git2r
git5s1w
gi2t1s2
2g1k
gkaar4
g1ka
gkaa2
5g4la2n1s2
gla4sa
glas2
gla6sel
gla1se
gla6s1ka
gla2s3k2
gla4s5o
g4le.
g1le
5g4le2n.
gli6don
g1li
g3li2d
gli3do
g4lif
gli6gur
gli1gu
4glik
g4li2m
g4li2p
3glis2
g4lo1b2
4glo1d
3g4loe
g4lof
3g4lo2m
g3lo2p
3g4lo2t
g4lo2-1
g3lus2
3g4luu2
g4ly.
g1ly
4g3lyn
2g1m
gma7s6kui
gma2s3k2
gmas2
gmas1ku
2g1n
gn4ee1m5
g1ne
gnee2
gnee1t5
gnie6ko
g1ni
1go
god6sak
go1d
go2ds2
god1sa
go1d6s3i2d
god1si
4g3oef
goe7krui
goe4k2r
4goes2
g5oe2s.
g5oeta
goe1t
goe7t6he.
goe2t1h2
goë7la1ry
go1ë
2g1of
3go1gi
go1g
gol4f5o
4g5olie
go1li
go4mag
go2m
go3ma
4g3o2ng
gon6sto
gons4t
go2n1s2
g3o2n1t
goo5g1l
goo2
goo1g3
4g3oor1
2g1o2p
go5pla
gop3l
3g4o3po
g5orig
go1r2i
go3s3l
gos2
gos7pel1r
gos3p
gos4pe
g5os3se
go2s2s2
go3s4t
gou4d3
goud6a.
gou7d6ief
gou7d6i3ni
gou7d6i2n1k
goud6s.
gou2ds2
gow7rie.
go1w
gow2r
gow1r2i
go9y4a.
goy1a
2g1p
1g2r
g4ra1b2
g5ra2k.
gra4ma
gra2m
gra7ma3do
grama2d
gra4m5o
g4ras2
gra4s5a
gra4se
4g3re1d
g1re
g4ree4
g5ree4k
gree1p5
4g3re1ë
g4ren
gren6s1t
gre2n1s2
g5re3se
gres2
gre4s1p
gre6sur
gre1su
gre6tji
gre1t
gret2j
gret7jie
g4reu
griek6s7t
g1r2i
grie2k1s2
grie6t5j
grie1t
4grig
gri4p
g5ri2t.
gri2t
4g3rok
g4ron
4g5rooi1
groo2
g5rook3
g5roo2m1
g5rowe
gro1w
4grug
g5rui2m
4g3ryk
4gry1m
gry6ste
gry3s3t
grys2
2gs2
g2s1a
g5saa2m1
gsaa2
gs3a2d
gs6ade.
gsa3de
g4s3af
gs5a2g1t
g2sag
gs5a2k1s4
g5sa1la
g5sa1le
g4s5a1na
gs5a2n1t
gs5a2p1p
gsa2p
gs6appe
gs3ar
gs3as2
g4s5ee2n1
g1se
g4s3ef
g4s5ei2s.
gs2eis2
g6sei1se
gs5e1ko
gs5e2k1s2
g5se2k1t
g4s5e3le
g3sel
g4s5e3li
g4s5e1ne
g4s5e4s1t
gses2
g4se1w
gs5ewe
g2s3f2
g5s6fee2r
gs1fe
g4si2d
g1si
gs5i3de
g6simpa
gsi2m
gsi2m1p
gs5inde
gsi2n1d
gs5i3ni
gs5i2n1l
gs5i2n1s2
g2s3j
g2s1k2
g5sk4aal1
gs1ka
gskaa2
gs5ka1b2
g5ska2d
g6ska3pa
g3ska2p
g5ska2t1t
gska1t
g5ske1d
gs1ke
g5sker
g7skeu2r.
gs7keur1d
g3s1ki
g4s5kin
g4s3k2l
g5skof
gs1ko
g5sko1le
g4s3ko2m
g5skoo2
g6s5koor1
gs5ko2r1r2
g5sko2t
g5skou
gs3k2r
g2s3l
g3s4la
g5s4lo2p
g2s3m
g5s4nel
g1sn
gs1ne
g2s3o
g3s4ol
gso6pro
g2s1o2p
gso3p3r
g3s4ou
gs3p
g5s4pel
gs5per
g5s2pes2
g3s4pi
g6spi2l1l
gsp2il
gs4poe
g5s6po1re
g7s2por2t1s2
gspo2r1t
g5s4pru
gsp2r
g1s1t
g6s5taak1
gs4taa2
g4s5ta1b2
gs6tabi
g4stak
g4stal
g4star
g4s5tek2
g6stel1g
g6ste1ra
gs5te1r2i
g6ste2r1r2
gs2te2r6s2
g6ste3se
gstes2
g4s3ti
g5s4ti2g1t
g4stoe
g7s4toe3le
g4s5toer
gs5toe1t
g4s5ton
g4s5tra2d
gst2r
g6strak
g6stran
g6stra2p
g6stri1b2
gst1r2i
g4s5troe
g6s7troon1
gstroo4
g6st2ruu2
gs4tru
g4s3ui
g1su
g2s3w
gs6wer1w
2g1t
gte7ee2n1h2
gteee2n1
gte7l4a3gi
gte1la
gte2l3ag
gte6ras2
gte1ra
gte6rer
gte1re
gte4ro
gte7roer
gte7ro2l.
gtes4
gte7sfee
gte2s1f2
gtes1fe
gte7smee
gte2s1m
gtes4me
gt5u1r2i
1gu
gu2a
gu2e
gu5e1la
guid6o.
gui4d3o
gui2d
gui6r3la
gui1r
guir1l
4gui2t
2g1v2
2g1w
g3ys3t
gys2
2g2-1
1h2
2h.
2ha.
hal4f3
hal6m5ag
hal1m
4ha1lo
hal4s5k2
ha2l1s2
hal6s5t2r
hal3s1t
ham7pa2g1n
ha2m
ha2m1p
ham4pag
ham6sk2r
ha2m1s2
ham1sk2
hams7kra
han6dan
ha2n1d
han7d6ja.
han2d1j
han4d2r
han4du
han6g4li
ha2ng
hang1l
han6gor
han1go
hang2s6l
han2gs2
hang5s6w
han4s5k2
ha2n1s2
han6s1ka
har6do2p
har3do
har1d
hard7o2p.
har6sel
ha2rs2
har1se
har6sol
har1so
har6spa
har1s1p
har6t5aa2
ha2r1t
har4t5j
har4to
har7toe1m
ha4wk
ha1w
haw7s6han
ha2ws2
1h2aw2s1h2
haws3ha
hay6e3li
hay1e
heb7lu2s.
he1b2
heblus2
hee4l
heer8s7te.
hee2r
heer5ste
hee2rs2
heer1s1t
hee3s6e.
hee2s3
hee3se
heg7or1ga
he1go
hegor1g
heg7ra2n1k
he1g2r
heg7s6pyk
he2gs2
hegs3p
5hei2d.
hei2d
heid7ste1m
hei2d2s2
heid1s1t
he4ko
hek7s3aan1
he2k1s2
hek1sa
heksaa2
he4k3w
hel7an1ge
he1la
hela2ng
he2n1d4
hen4so
he2n1s2
he2r
he1r3a
he9r4a.
her6a2k3l
he7r6al2d.
heral1d
he5r4an
he3re
her7egpa
here2g1p
he4r5ek
he4r5e1v2
herf4
her2f1s5
he1r3i
he3r6i2b.
heri1b2
he5r4i2d
he5ro1d
he1ro
he5rol
her5o2n1d
her7on2t1m
hero2n1t
he5ros2
he4r6o2s2s2
he5rou
her5o1w
her7sche
he2rs2
hers1c2
hersc1h2
he1r3u
her5yk
he1ry
he4s1p
hes2
hes7pe1ru
he4sper
he4s3t
hete5r6o
he3te
he1t
heu6paa2
heu1p
he3us2
he1v3
he4vr
hewen7s1t
he1w
hewe2n1s2
hie4r
hier7i2n.
hie1r2i
hie7r6o1ni
hie4r5on
hie1ro
hie7r6o1ny
hi1ë1
hil6lbr
h2il
hi2l1l
hill1b2
5hin2gs2
hi2ng
hing6s5t
hi3pe4
hi2p
hi4r1l
hi1r
hi4s3p
his2
his5pa
hi4v2-1
hi1v2
2hl
h3li
2h1m
h3ma
2hn
hode6sl
ho1d
hodes2
hode3s7la
hoe7kaai1
hoe4kaa2
hoe1ka
hoe6k1ys2
hoe1ky
hoe6spi
hoe1s1p
hoes2
hoe4s5t
hoër7o2p.
ho1ë
hoëro2p
ho4fa
hof5aa2
hof7am2p.
hofa2m
hofa2m1p
hof7ui2t1s2
ho4fui
ho1fu
hofui2t
hog6hok
ho1g
1h2o2g1h2
hoi7s6wer
hoi1
hoi2s3w
hois2
hok7ra2k1k
ho1k2r
hol7aa2r.
ho1la
holaa2
holes5
ho1le
4holf
hol5in
ho1li
hol7oo2g.
ho1lo
holoo2
holoo1g3
4ho2n.
hon6daa2
ho2n1d
hon6da2g
hon6dro
hond2r
hop7la2n1d
ho2p
hop3l
hop4lan
hop7s6maa2
ho2p1s2
hop2s3m
hops3ma
ho3ro
ho2r4s2
hor4t5j
ho2r1t
hos6hol
hos2
1h2o2s1h2
hos3ho
ho4ta
ho2t
hou4t5a
hou2t
hout5j
hou6tol
hou3to
hou6to2m
hou6who
hou1w
houw1h2
2hr
hre6sto
h1re
hre1s1t
hres2
hrie4
h1r2i
hries5
hris5t
hris2
h3te
h1t
h3to
hu9go.
hu2go
hui6daa2
hui2d
hui3da
hui6dui
hui3du
hui6sef
huis2
hui1se
huit6ji
huit3j
hui2t
hui7tjie
huk6hun
1h2u2k1h2
hul4p5a
hul1p
hul6pek
hul6p1le
hulp2l
hul6por
hul6ste
hu2l1s2
hul3s1t
huls7te.
hu3mo
hu2m
hum7oe2s.
hu3moe
humoes2
hu3mu4s5
hu3mu
hute2r6s2
hu2t
hu3te
hut7jie.
hut2j
hut6spo
hu2t1s2
huts1p
hut6ste
hu1ts1t
huts7te.
h5vi2l1l
h1v2
hv2il
2h1w
hyg7ro3ma
hy1g
hy1g2r
hygro2m
hy2s3k2
hys2
hys7ta2ng
hys3t
hys4ta
i1a
iaan6so
iaa2
iaan1
iaa2n1s2
iaan6s1p
iaan6s1t
iaans7te
iam7s4o2n.
ia2m
iam4s3o
ia2m1s2
ia4no2p
ia1no
ias6koo2
ia2s3k2
ias2
ias1ko
ia5spo
ias3p
ia5sta
ias3t
ia5s4t2r
i4baf
i1b2
i4bag
ibou6s.
ibou3s4
ic5ky.
i1c2
i2ck
i2d
i3da
ida7groe
ida2g
ida1g2r
id5a2g1t
idde6ra
i2d1d
i3de
ide7s4nui
ide4sn
ides2
ides1nu
i3dê1
i3di
idia5s4
idi1a
id4ja
i2d1j
i5djan
i3do
i3d2r
i2d2s2
id4s5e1t
id3se
ids7inoe
id1si
idsi1no
id4s1ka
id1sk2
ids7ke2r1m
ids1ke
ids5k2r
id1s3l
ids7la2gs2
ids4la4g
ids7nye.
id1sn
idsny3
ids4ny1e
id2s3o
ids3p
ids6pa.
ids6pek
ids6pie
id3spi
id6sp2il
ids6p1r2i
idsp2r
id6s3pry
ids7taal1
id1s1t
ids4taa2
ids7tee.
ids7te2k1s2
idstek2
ids7te3le
id5stel
ids5ti
ids5toe
id6s7trek
id3st2r
idst1re
ids7u3r2e.
id1su
idsu1re
i3du
id3uu2
iedin2g6s7
ie1d
iedi2ng
ied7io1ne
iedi1o
ied5rol
ied2r
ie2d3w
i1ee
i4eee
ie5ee2n1
ieë7aar1d
ie1ë
ieëaa2
ieë6lys2
ieë1ly
ief7alar
ie1fa
iefa1la
ie5fie
ie1fi
ie4f3r
ief7s6tal
ief3s1t
ie2f1s2
ief7ui2t1g
ie1fu
iefui2t
ie5gla
ieg7lo1ka
ieg7rie1m
ie1g2r
ieg1r2i
ie6gri2t
ieg5s1t
ie2gs2
iek7asyn
ie1ka
ie3kas2
ieka1sy
ie5ke.
ie3ke
ie6k5er1v2
iek7es3se
iekes2
ieke2s2s2
ie5ki2e
ie1ki
ie6klaa2
ie1k2l
iek7laai1
ie4k1ni
ie1k2n
ie6k5o2n1d
ie1ko
iek7op1le
ie3ko2p
iekop3l
ie6ko2p1s2
iek7op1se
iek5o2p1v2
ie4k1re
ie1k2r
iek7revu
iekre1v2
iek7ri2g1t
ie4krig
iek1r2i
iek7wa1re
ie1k2w
ie3k4war
ie4k7wee2s3
ie4k5wi
iek6wos2
ie2k3wo
iek7wy2d.
iekwy2d
ie4laa2
ie1la
iel6a1fo
ie2l3af
ie6le1ne
ie3le
iel7oo2r.
ie1lo
ie4loor1
ieloo2
iel6san
ie2l1s2
iel1sa
iel6s5on
iel1so
ien7anal
ie1na
iena1na
ien7g6lor
ie2ng
ieng1l
ien6kro
ie2n1k
ienk2r
ien7olie
ie1no
ieno1li
ie4n5oo2
ie2n4s2
ien7sa2k1k
ien1sa
iens5or
ien1so
ien7sou2t
ien3sou
ien7s4pan
iens1p
iens5t
ien7s6ta2m
ien7s6tel
ien8s7te3le
ien7s6te1t
ien7s6too2
ien5suu2
ien1su
ie4n5ur
ie1nu
ie6poo1g3
ie1p
iepoo2
iep7oo2g.
ie6proo2
iep2r
iep7rooi1
ier7afma
ie1ra
iera2f1m
ie6ra2f1s2
ie6r7en2g1t
ie1re
iere2ng
ier7eter
iere3te
iere1t
ier7nef1f
ie2r1n
ier1ne
ier7omt2r
ie1ro
iero2m
iero2m1t
ie4r5on
ier6o1ni
ier6o1ny
ier7swee
ie2rs2
iers3we
ier2s1w
ie4s6a2m1p
ies2
ie1sa
ie3sa2m
ie5se.
ie3se
ie6se2n1k
ies7en1ke
ies7en2t1s2
iese2n1t
ies7fer1w
ie2s1f2
ies1fe
ies7ka2f.
ie1sk2
ies1ka
ie6s3kon
ies1ko
ies7ko2p.
iesko2p
ies7kraa2
iesk2r
ie3s6kry
ies7laag
ie3sla
ie1sl
ies4laa2
ie6sle1p
ies1le
ies7lepe
ie2s5li
ies7luik
ies7meu1b2
ie2s1m
ies4me
ies7mooi1
iesmoo2
ie3so
ies7oe2s.
ie4s2oes2
ies5o2n1d
ies7oor1p
ie3s4oo2
iesoor1
ie6s1o2p1n
ie2s1o2p
ies7op1ne
ies7pa1ne
ie1s1p
ie3s4pan
ie4s5per
ies7pli4g
ies4p1li
ie2s3p2l
ie6s7taal1
ie1s1t
ies4taa2
ies6tas2
ies7tee.
ie6ste1h2
ies7te3le
ie4s7te2n1t
ies6tin
ies3ti
ies5u2il
ie1su
ie4s3w
iet7aa2n1s2
ie1t
ietaa2
ietaan1
iet7aar1d
iet7alba
ietal1b2
ie5te.
ie3te
iet7er2t1s2
ie1te2r1t
iet7om1se
ieto2m
ieto2m1s2
ie6t7re3ke
iet2r
iet1re
ie6t7re4ko
iet4sl
ie2t1s2
iet7uie.
ietui1e
ie5t4wi
ie2t1w
ie4tys2
i1eu
i4eu1b2
i4eu1d
i4eug
ieu7g6rie
ieu2g3r
ieug1r2i
i4e3ui
ieu7in2g.
ieui2ng
i4eul
i4eu2m
ieu7s6ko2t
ieu2s3k2
ieus2
ieus1ko
i4e1uu2
i4eu1v2
i4eu1w
ie5wie
ie1w
ie4w2-1
ie5ys3t
ieys2
ie-7k3lik
ie2-1
ie-k4li
ie-1k2l
i3è1r
i1ê1
iël6s1ku
i1ë
ië2l1s2
iël1sk2
iën6tji
ië2n1t
iënt2j
iënt7jie
ië4s3t
ië1s2
i4fei
i1fe
i4fi2m
i1fi
i4fin
if1l
i2f3r
i4f3ui
i1fu
i4g5aan1
i1ga
igaa2
i4ga2p
iga1re4
igare1t5
i4g5e4f1f
i1ge
ige3f
ige6naa2
i3gen
ige1na
ig5e3te
ige1t
ig5i2n1s2
i1gi
ig1l
i3g5loe
i2g4o2p
i1go
ig5o2p1t
ig5or1g
igo7roos2
igoroo2
ig5res2
i1g2r
ig1re
ig5roo2
i4g3ry
ig3s1a
i2gs2
igs6i2n1s2
ig1si
igs7ka2p1s2
ig2s1k2
ig3ska2p
igs1ka
ig7ske2n1d
igs1ke
igs5ko
ig5s1ku
igs6mee
ig2s3m
igs3me
igs6o1na
ig2s3o
ig5soo2
ig4s7poei1
igs3p
igs4poe
ig5s3ti
ig1s1t
ig7stoei1
ig4stoe
igu7er4a.
i1gu
igu2e
igue1ra
ihu6ahu
i1h2
ihu1a
ihua1h2
i1i
ike6roe
i1ke
ike1ro
iket5j
ike1t
ik5k4li
i2k1k
ik1k2l
i2k3n
iko6na1t
i1ko
iko1na
i1k2r
ik6s3a2k1t
i2k1s2
iks4ak
ik1sa
iks7akte
iks7iden
ik3si
iksi2d
iksi3de
ik4sin
iks7inve2
ik4si2n1v2
iks7ju2k.
ik1s1j
ik6s3ko2m
ik1sk2
iks1ko
iks7paar
ik2s1p
ikspaa2
ik4s6pa2d
iks7pa1re
iks6tik
iks3ti
iks1t
iks6tuu2
iks6wel
ik2s3w
ik5wan
i1k2w
i4k3we
i4kwy
2il
i1la
il5a2g1t
i2lag
ila6too2
ila1t
ilbe6s4t
il1b2
ilbes2
ild7agti
il1d
ilda2g
ilda2g1t
ild7s6maa2
il2ds2
ild2s3m
ilds3ma
ild6s3t2j
ild1s1t
ild7te2m1m
il2d1t
ildte1m
ilet5a
i1le
ile1t
ile6tji
ilet2j
ilet7jie
ilf4l
il5f1li
il5g4ha
il1g
il2g1h2
ilinde6
i1li
ili2n1d
illo4w
i2l1l
3illu
il4m5a1t
il1m
ilm7op4er
il2mo2p
ilmo3pe
i1lo
ilo5s1k2
ilos2
ils7ins1p
i2l1s2
il1si
ils2i2n1s2
ils7orde
il1so
il4sor1d
il4sp2r
ils1p
ilt7aa2r.
il1t
iltaa2
i1lu
i2m
i3ma
im5a2g1t
i3me
i4mek
im5e2k1s2
ime7l6aar
ime1la
imelaa2
imen2t6s2
ime2n1t
ime4s2
ime1s5t
i3mê1
i3mi
5immi1g2r
i2m1m
i3mo
i4m3o4p
imo7t6heu
i3mo2t
imo4the
imo2t1h2
5implik
i2m1p
imp2l
imp1li
i3mu
imu6maa2
i1mu2m
imu3ma
in5aar1d
i1na
inaa2
i4n1af
i4n3ag
in5a2k1k
in5ar1g
in4d5aa2
i2n1d
in4das2
inder7as2
inde1ra
5indi1v2
ind6oef
in6doo1g3
indoo2
ind7oo2g1m
in6d5oor1
ind7s4leu
in2ds2
ind4s3le
ind1sl
ind5s3w
3indu
in5dwi
in2d1w
inee7tji
i1ne
inee2
inee1t
ineet2j
ine5ra
2i2n1f2
ing7aa2rs2
i2ng
in1ga
ingaa2
in5gan
in6g7eter
in1ge
inge3te
inge1t
ing6hpa
in2g1h2
ingh1p
in6gi2n1d
in1gi
in6g2i2n1f2
ing7in1f2r
in6g7ins4t
ingi2n1s2
ing6le1b2
ing1l
ing1le
ing6o3pe
in1go
in2g1o2p
ing6op3l
in4g5ou
ing7p6seu
in2g1p
ing2p1s2
ingp1se
in4g5ru
in1g2r
ing7saag
in2gs2
ing2s1a
ingsaa2
ing7sa2p.
ingsa2p
ing7see.
ing4see
ing1se
ing7se1k2r
ing4sek
ing7se1ku
ing7s6fer
ing2s3f2
ings1fe
ing7si2n.
ing4sin
ing1si
ing7sin1j
ing7skal
ing2s1k2
ings1ka
ing7skêr
ingskê1
ing7sku1d
ings1ku
ing7s6le1p
ing2s3l
ings1le
ing7s6ly1m
ings1ly
ing7s6o1fa
ing2s3o
ing7s4o2m.
ingso2m
ing7s6o2m1m
ing6s7p2il
ings3p
ing3s4pi
ings9tel1le
ing1s1t
ingste2l1l
ing7s6tin
ing4s3ti
ing7s6ui1e
ing4s3ui
ing1su
ing7s4u2il
ing7s6wel
ing2s3w
ing7ui2l.
in1gu
ingu2il
i3ni
ini6gaa2
ini1ga
in4ik
i4n5i2n1f2
5inisi1a
in2is2
ini1si
in4k7er2t1s2
i2n1k
in1ke
inke2r1t
ink7laag
in1k2l
inklaa2
ink7le2r.
ink4ler
ink1le
ink7nerf
in2k2n
ink1ne
ink5nu
ink7ogie
in1ko
in4ko1gi
inko1g
in4kol
ink7olie
inko1li
5in3ko2m
ink7ri2ng
ink2r
in3krin
ink1r2i
in6k5rol
ink5s4t
in2k1s2
ink7wi2t.
in2k2w
inkwi2t
5in3na2m
in1n
in1na
5innemi
in1ne
in3ne1m
inne1s6t
innes2
inne7ste
in5o2ng
i1no
ino7s6kaa2
ino1s1k2
ino4s3ka
inos2
in5rag
in1r
5inrig
in1r2i
ins7epou
i2n1s2
in1se
inse1p
in6s5e3te
inse1t
5ins2e2t1s2
in4s1g
ins7ka2p1s2
in5ska2p
in1sk2
ins1ka
ins6kin
ins1ki
in3sl
ins7mol1t
in2s1m
ins7moor1
insmoo2
ins6o2n1d
in1so
5inspek
ins1p
ins7p4rie4
insp2r
insp1r2i
ins4t
5ins3ti
insti7t.
ins1ti2t
ins6ton
ins7twis2
in4s3t1w
inst4wi
int7appe
i2n1t
inta2p
inta2p1p
int5e2s2s2
intes2
in1te6s5t
int6he.
in2t1h2
in1t6ui2t
int6wyf
in2t1w
inu5e.
i1nu
inu1e
5invlo
i2n1v2
i1o
io3p3r
io2p
ior6ubr
ior1u
io3ru1b2
io1s2
io1s4k2
io5s3k2l
ios4p
ios4t
io3t2r
io2t
i2p
i3pa
i4p3ag
i3pe
i3pi
i3p2l
ip4lo
i3po
ipo4s5t
i3pos2
i3p2r
ip1re4
ip4s.
i2p1s2
ips7ko3pi
ip1sk2
ips1ko
i1psko2p
i3pu
i4pui
i3py
i1r
ir4c1h2
ir1c2
ir1ke4
i2r1k
irke1l5o
iro2p4
iro5p3r
iru4s2
i4rwa
ir1w
i2s3ag
is2
i1sa
i5s4a3gi
i4s5a2k1s4
i6s2an2gs2
i3sa2ng
is3ar
i4sar1g
is5as3p
isas2
i4sa1v2
is3c2
i4s3ei
i1se
i4s3e1t
ise5u2m
i5sfee2r
i2s1f2
is1fe
ish7nie.
i2s1h2
is2hn
ish1ni
i4s5i2n1t
i1si
i4s5i2n1v2
i2s1j
i4sj.
is5jan
i2sja
is5joe
i2s3k2
i2s4k.
i4s1ka
i5s6kaaf
iskaa2
is5kan
is4kê1
is5kui
is1ku
i2s3l
is5laa2
i5s4la4g
i2s3m
i5s4mi2t
is3mi
i2s3n
i4s5oes2
i1so
is5o2n1d
is3or
is3p
is4p.
is3t
i2s4t.
i2s4t1h2
isto7pho
ist4o2p
isto2p1h2
i5s4tyn
i2sty
i4s3ui
i1su
i5s4uik
isu6maa2
isu2m
isu3ma
i2s3w
i2t
i3ta
it3ag
ita6tis2
ita1t
i2t3b2
i3te
ite7dwal
ite2d2w
ite1d
ite7glas2
itek7te.
itek2
i1te2k1t
ite6mas2
ite1m
i5t4e2n1h2
it4er
ite5ru
i4t5e3te
ite1t
i3tê1
i3ti
it4in
i4t5i2n1s2
i3to
ito5fa
ito7p4lan
ito2p
itop3l
ito7rowe
itoro1w
it2r
it3re
it3ry
it4s1c2
i2t1s2
it5ser
it1se
its5e3te
itse1t
its7joo1d
it1s1j
itsjoo2
it4s5oo2
it1so
it4s7pe2r1k
its1p
its6tek2
i1ts1t
its7to1r2i
its7uu2r.
it3su
itsuu2
itsuur1
i3tu
itu6saa2
itus2
itu1sa
it5win
i2t1w
i3ty
ity7so2k1k
ity2s3o
itys4ok
itys2
itz7laan1
i2t1z
itzlaa2
i1u
iu2m1
iu4ma
iu4me
iu4mi
iu5m4ie
ium6uur1
iu3mu
iumuu2
iwel6s5k2
i1w
iwe2l1s2
iwe7m6o2s.
iwe4m3o
iwe1m
iwemos2
iwes4
iwe7spor
iwe4s1p
iwe5s3t
1ï
ï2m
ïn5a2k1t
ï1na
ï1n3o
ïns4t
ï2n1s2
ïn5u2n1d
ï1nu
ï4s5la2m
ïs2
ï1sl
ïs3t
1j
3jaa2
jaar6s7k2r
jaa2rs2
jaar1sk2
3ja1c2
ja4c1q
3jag
ja4ga
jan7g6hai1
ja2ng
jan2g1h2
jan7k4na2p
ja2n1k
jan2k2n
jank3na
jap4l
ja2p
ja5pla
3ja1re
3ja1r2i
ja4s3m
jas2
jas7pa2n1t
jas3p
ja3s4pan
jas6tas2
jas3t
ja2z4z
ja1z
jaz7ze1r2i
je4k2n
je4k2r
jek7ra1si
jekras2
je5r3o2p
je1ro
jes7nië.
jes2
je1sn
jes1ni
jesni1ë
je1s4t
je4t3r
je1t
jet6sjn
je2t1s2
1jet1s1j
jeu4g
5jie5k2n
jie6nan
jie1na
jie6nol
jie1no
jie2n5s2
5jieon
jie1o
5jieo2p
jie6s1ka
jies2
jie1sk2
jie7ska2p
jies7ka1t
jie6s1ki
jie6sko2p
jies1ko
jie6s5lo
jie1sl
jie6slu
jie6s4ol
jie3so
jie6son
jie6spa
jie1s1p
jie6s5t2r
jie1s1t
jie6sui
jie1su
jie7s4uik
ji4eu
jin7g6o3pe
ji2ng
jin1go
jin2g1o2p
ji4r1p
ji1r
jo2b4s3
jo1b2
joe7kwee
joe4k3w
joen6sk2
joe2n1s2
5joe2r1n
3jo2ng
jos6a1fa
jos2
jo1sa
jo2s3af
jou7kui2t
jou1k
jou1ku
juit6s1p
jui2t
jui2t1s2
juk7rie1m
juk2r
juk1r2i
3jun
jun6k1re
ju2n1k
junk2r
2k.
1ka
k4aal1
kaa2
k5aa2n1d
kaan1
kaan8s7te.
kaa2n1s2
kaans1t
k5aa2n1w
kaar7se.
kaa2rs2
kaar1se
kaars7te
kaar1s1t
5kaa2r1t
kaar6ti
kaar6t5j
kade6la
ka2d
ka3de
kade6sl
kades2
4k3a2d1v2
5k4afee
ka1fe
kaf6oef
ka1fo
4ka1f2r
4k3a2f1s2
2k1ag
5k4age1m
ka1ge
k4a2g1g2
5kag1ge
k4a1go
k4a1g2r
4ka2k1s2
1kal4k5a
kal1k
kal4k5l
5kal1ko
kal6koo2
kal4k2w
kal4s5p
ka2l1s2
kal4s1t
3ka2m
kam6par
ka2m1p
kam6p1le
kamp2l
ka2m1s4
3ka1na
kane1r5o
ka1ne
3ka2n1k
ka4n6o2n1t
ka1no
kan6s1ko
ka2n1s2
kan1sk2
kan6ste
kans1t
kans7te.
3ka2n1t
kan4t5j
kan4t7o2m.
kanto2m
kan4t5r
ka4pak
ka2p
ka3pa
5kapas2
kap7inte
ka3pi
kapi2n1t
5kapi2t
kap6lak
kap3l
kap7la2t.
ka3p4la1t
ka3po4
ka5p1r2i
kap2r
kap7s2eis2
ka2p1s2
kap1se
kap6sp2r
kaps1p
kap6stek2
kaps1t
5kapte
ka2p1t
3ka2r.
5karak
ka1ra
4k5arbe
kar1b2
k5ar2m.
ka2r1m
ka5roo2
ka1ro
ka4r6oor1
kar4s1t
ka2rs2
k5arti
ka2r1t
kar5to
3kas2
4ka2s1g
kas7laai1
ka2s3l
kas4laa2
kas6maa2
ka2s3m
kas3ma
kas7traa2
kas3t
kast2r
ka5s6tro
5kateg
ka1t
ka6t7etes2
kate3te
kate1t
kat6har
ka2t1h2
kat6hu.
ka4too2
kat7ry2k.
kat2r
ka4tryk
ka2t1s4
kat5s3w
kat7ui2l.
katu2il
kay6a1ku
ka3y1a
2k1b2
kbe6kwi
kbe1k2w
kbout7ji
kbou2t
kbout2j
2k1d
k3de
1ke
kede6lo
ke3de
ke1d
kee2
kee1l5a
4kee2n1
keep6s5t
kee1p
kee2p1s2
keer6so
kee2r
kee2rs2
keer6ste
keer1s1t
keë6laa2
ke1ë
keël7aar
4kef1f
kei6dro
kei2d
kei3d2r
keids7p2r
kei2d2s2
keids3p
4keik
keis4
kei5s3t
4k3e2k1s2
ke4l5ak
ke1la
ke6la1ne
kel7a3ne1m
kel7as3si
kelas2
kela2s2s2
kel7ee2n1h2
ke3le
kelee2
kelee2n1
4k5ele1m
ke6li2n1b2
ke3li
kel7inbr
kel6mag
kel1m
ke4l5ou
ke1lo
kels8onde
ke2l1s2
kel1so
kelso2n1d
kem6a1fa
ke1m
kem1af
4k3e2m1m
ken6aar
ke1na
kenaa2
ken6dra
ke2n1d
kend2r
ken7ee2l.
ke1ne
kenee2
kenee4l
4k3en1j
3ken1m
3ken1n
ke4nou
ke1no
ken7s4o2n.
ken4son
ke2n1s2
ken1so
kep7laai1
kep4la
ke1p
kep2l
keplaa2
kep7le2r.
kep1le
ke4p5lo
kep5sk2
ke2p1s2
ker7ee2n.
ke1re
keree2n1
ke4rel
ker7el2s.
kere2l1s2
ker7flan
3ke2r1k
ker6kal
ker1ka
ker6kin
ker1ki
ker6k5or
ker1ko
ker6ko1w
ker4k2r
ker7kris2
kerk1r2i
ker6kui
ker1ku
kerk7u2il
ker4k5wy
ker1k2w
ker6m7e2ng
ke2r1m
ker6n1af
ke2r1n
ker1na
ker6nei
ker1ne
ker6nen
ker4n5o
ker7oe2s.
ke1ro
keroes2
ke4r5on
ker6pru
ker1p
kerp2r
ker6se1t
ke2rs2
ker1se
ker4sk2
ker7s4k2il
kers1ki
kers7ko2m
kers1ko
ker6slo
ker1sl
ker4sn
ker4so
ker7s4o2n.
ker6s5pi
ker1s1p
ker2s5w
4ker2t1s2
ke2r1t
6k5ervar
ker1v2
kes6e2l.
ke3se
kes2
ke3sel
ke4sn
ke6trol
ke1t
ket2r
ket6s1ka
ke2t1s2
ket1sk2
5ketti
ke2t1t
ke4tu
keu6r1or
keu1ro
3keus2
keut7jie
keu2t
keut2j
key7kleu
key2k2l
keyk1le
key7nooi1
key2n4o
keynoo2
kê4r1b2
kê1
2k1f
2k1g
k1ga7la4ga
k1ga
kga1la
kga2lag
kge5la
k1ge
2k1h2
kha7y6e3li
khay1e
k4hoi1
khu7k6hun
k1h2u2k1h2
1ki
ki2e
kie6dro
kie1d
kied2r
kie7laai1
kie4laa2
kie1la
kie6mas2
kie1m
kiem7a2s.
kie6se2n1t
kie3se
kies2
kie4s5k2
kie7s6kry
kiesk2r
kie1s5l
kie6slo
kies7tan
kie1s1t
kie4ta
kie1t
kieu5s2
ki1eu
ki4k2l
ki4ma
ki2m
4ki2m1m
5kin2d.
ki2n1d
6k5indel
kin5d2r
4k3indu
k3i2n1h2
4ki2n1l
4k5ins4t
ki2n1s2
kio4s2
ki1o
kios7ke.
kio1s4k2
kios1ke
kip7li2ng
ki2p
ki3p2l
kip1li
ki4r1c2
ki1r
3ki2s.
kis2
kis7ob1li
ki1so
kiso1b2
ki5s4po
kis3p
ki5s6te1w
kis3t
ki2t4s2
ki2t
kit1s5k2
2k1k
k2k4ag
k1ka
kka7s6maa2
k3kas2
kka2s3m
kkas3ma
kka5st2r
kkas3t
k3ke
kke6nee2
kke1ne
kker5k2r
k3ke2r1k
kk4li
k1k2l
1k2l
k5la3di
kla2d
4k3la2n1d
k5la2ng
k4la2n1k
klas3
kla3s6e.
kla1se
kla6sin
kla1si
k5leer1d
k1le
klee2
klee2r
4kleg
k5le3ge
k4l4ei
4klel
4k3len
3kle4p
klep7a2s.
klepas2
k4ler
5kle1re
kle5us2
k3lê1
4k3lie1d
k1li
k4lier
4k3lig
4k3lik
5k4li2k1k
kli6ko2p
kli1ko
k4li2m
kli6moe
kli3mo
k4lin
5kli2n1k
k5lin1n
kli4p3
k4lis2
kli7sjee
kli2s1j
4klo1g
klo6kon
klo1ko
k5loo2s.
kloo2
kloos2
k4lou
klu4b5h2
klu1b2
klu6b1re
4klu2g1
2k3ly
2k1m
kman7spo
kma2n1s2
kmans1p
1k2n
k4na2p
k1na
4k3na1v2
5k4ne2g.
k1ne
4knei
4k3ne1m
kne4t
knet5j
3k1ni
kni6kla
kni1k2l
kni4p3
knoe4
k1no
knoe7te.
knoe3te
knoe1t
4k3no2m
k5noo2t1
knoo2
k4no2p
knor7o2s.
knoros2
1ko
kob7re1go
ko1b2
kob1re
3ko1d
4k3oef
3koek
koe6kei
koe3ke
koe4l5o
koe5p2l
koe1p
3koer
koe4s3
koe7s4is3t
koe3si
koesis2
3ko1ë
4koë.
k4o1fi
4ko1gi
ko1g
kok7on2t1h2
ko1ko
koko2n1t
kok6sk2r
ko2k1s2
kok1sk2
ko5lag
ko1la
kol6for
kol1fo
3ko2l1l
3ko1lo
3kol1w
3ko2m
kom7aa2n.
ko3ma
komaa2
komaan1
kom7bi1na
ko2m1b2
4ko2m1g
kom7g6ha2d
kom2g1h2
k5omhu
ko2m1h2
kom7mi2s2s2
ko2m1m
kommis2
kom7s6aal1
ko2m1s2
kom1sa
komsaa2
kom4s1p
kon7atoo2
ko1na
kona1t
4k5on2t1l
ko2n1t
kon5t2r
4k5on2t1s2
4k3oo1g3
koo2
ko4o2p1
5koor1d
koor1
3ko1ö
ko4pag
ko2p
ko3pa
kop7a3pe.
ko3pa2p
kopa3pe
kope7la.
ko3pe
kope1la
kop7la2s.
kop3l
ko3p4las2
4k3oplo
3ko2p1m
4k1o2p1n
5k4op1no
ko4po
6kopper
ko2p1p
kop7ui2t1s2
ko3pu
ko4pui2t
kord7aan1
kor1d
kordaa2
kor6doe
kor3do
kor6foo2
kor1fo
k3or1g
korin2g7s2
ko1r2i
kori2ng
k3o2r1k
ko3ro
3kor1p
kor4s5l
ko2rs2
kor7sten
kor1s1t
kor4t5a
ko2r1t
kor6tji
kort2j
kort7jie
3kos2
kos7ee2t1p
ko3se
kosee1t
1ko4s7in1ko
ko3si
kosi2n1k
kos7juf1f
ko1s1j
ko4sjuf
ko4s1k2
ko5s1ki
1kos5ko
kos5pe
kos3p
kos5taa2
kos3t
kos4ta
ko4t5ak
ko2t
ko3ta
5kotel
ko3te
kous7te.
kou3s4t
kous2
kou5t2j
kou2t
kovi7e6v.
ko1v2
kovie1v2
ko4vk
ko4vs2
k5ower1h2
ko1w
2k1p
kpr4o6pa
kp2r
k3p4ro2p
kp4si
k2p1s2
k2r
k5raa2d1
kraa2
3k2ra4g
kra7ge2rs2
kra1ge
kra3ger
kra1g5o
k5ra2k.
4kra2n1d
5kra2n1k
5kredi
k1re
kre1d
5kree2t.
kree1t
k4reëe
k3re1ë
4k3reg
4k3rek
k3rel
k5re3se
kres2
3kre1t
4kri4f3
k1r2i
4krig
kri4k3
kri6moo2
kri2m
kri3mo
3krin
kri4p
krip7lee4
kri3p2l
krip1le
3kris2
4k3ri1v2
k5roe1t
k5rol1p
5kroon1
kroo2
kr4or
4k3ro1w
4k3ru1b2
3krui
4krui2m
kru6kas2
kru2k3
kru1ka
kruk6s.
kru2k1s2
kru4l
k5rus3p
krus2
kry6fin
kry1fi
kryg1s5t
kry1g
kry2gs2
4k3ryk
kry7ske1t
kry2sk2
krys2
krys1ke
3kry2t
2k1s2
ks6aa2n.
k1sa
k4s3aan1
ksaa2
ks5a2g1t
k2sag
ks4ak
ks5chi
ks1c2
ksc1h2
k4s5ee2n1
k1se
k4s5er1v2
k4s5eti
kse1t
k3si
k6sin1ge
ks4i2ng
ks5i2n1s2
ks6ja1r2i
k1s1j
k2sja
k4skan
k1sk2
ks1ka
ks5kin
ks1ki
k4s3kon
ks1ko
k4s3lê1
k1sl
k2s3li
k5s6maak1
k2s1m
ks3ma
ksmaa2
ks5moo2
k5smou
k2s3n
k5s4no1b2
ks1no
k4so1b2
k1so
ks5o2b1j
k4s5o2n1d
ks5o2n1l
ks5o2p1k
k2s1o2p
k2s1p
k5spek
k5spel
k4s5pen
ks5per
k5s2pes2
k3spi
k4spi1r
k5spra
ksp2r
k5sp1r2i
ks5pur
k6s5taal1
ks4taa2
ks1t
ks5ta2n1t
k6s5teken
kstek2
kste3ke
k6ste2m1p
kste1m
ks5te2n1s2
k6ste1ra
k6s5te1r2i
k6ste2r1r2
kster6t7j
ks1te2r1t
ks5te1t
ks5ti2p
ks3ti
k7stra3do
k4stra2d
kst2r
k6s5tra1h2
k5s6trak
ks5tur
ks5tuu2
k3sty
ks3ui
k1su
k4s5u1re
k2s3w
k5swei
ksyn4
k1sy
2k1t
kte6ra2d
kte1ra
kte6ron
kte1ro
kter6s1p
kte2rs2
ktes4
k4the
k2t1h2
k3ti
k3to
kto6re1v2
kto1re
k3tu
kt4wi
k2t1w
1ku
kud7ak3si
ku1d
kuda2k1s2
3kuik
4kui2m
kuin4
kuins5t
kui2n1s2
4kui2t
kul6der
kul1d
kul6plo
kul1p
kulp2l
kul6poo2
3kul1t
3kun
4k3u1ni
kun6sin
ku2n2s3
kun3si
3ku2rs2
3kus2
ku6s5ee2n1
ku1se
kus7la2ng
ku2s3l
kus7node
ku4s1no
ku1sn
kusno1d
kus7taak1
kus3t
kus4taa2
kut3r
ku2t
kut6slu
ku2t1s2
kut1sl
kuus6te
kuu2
kuus3
kuus3t
kuu7ste.
2k1v2
kvan2g6s2
kva2ng
1k2w
4k5waar
kwaa2
k3wa1e
k4wan
3k4war
kwa7s6kaa2
kwa2s3k2
kwas2
kwas1ka
k4wee2k
4kwee2s3
4k3weg
4k3wer
kwê7laf3l
kwê1
kwê2laf
kwik3
kwi6kwa
k1wi1k2w
3kwis2
2k3wo
3k4wo2t
k3wu
ky4fa
kyk7ui2t.
ky1ku
ky4kui2t
k1ys2
2k2-1
k-5k4li
k-1k2l
2l.
4laa2n1b2
laa2
laan1
4laa2n1h2
4laar1d
laat7slo
laa1t1
laa2t1s2
laat1sl
laat6st2r
laats1t
lad7onde
la2d
la3do
la1do2n1d
la4du
4la2d1v2
2laf
la4fa
l3a2f1d
2lag
l4a2g.
la4ga
la5ga.
la5gas2
l4a3gi
la5gie
l4ag1l
lag7l4a2g.
lag2lag
l4a1go
lag5r2i
la1g2r
lag7s4o3me
la2gs2
lag2s3o
lagso2m
lai6r1go
lai1
lai1r
lair1g
lak7albu
la1ka
lakal1b2
lak6le1d
la2k3l
lak1le
lak7okul
la1ko
lako1ku
lak7oo1re
lakoo2
lakoor1
4l5ak3si
la2k1s2
la2k3w
lak7wa1re
la3k4war
lamb7da.
la2m
la2m1b2
lam2b1d
la4m5oo2
la3mo
lam6p1li
la2m1p
lamp2l
lam6pol
lamp7o1li
lam6s1ko
la2m1s2
lam1sk2
lam6s1le
lam1sl
lam6spe
lams1p
3la2n1d
lan6daa2
land7aar
lan6da2d
lan4d5r
land6sta
lan2ds2
land1s1t
land6s7te
lan6gaa2
la2ng
lan1ga
lan7g6nol
lan2g1n
lang1no
lan4go
lang7ste
lan2gs2
lang1s1t
lang6s8te.
lan6gur
lan1gu
lan4k5a
la2n1k
lan4k5l
lan6ko2p
lan1ko
lank7o3pe
lan4k5r
lan6kwi
lan2k3w
lan4s5k2
la2n1s2
lan4s1p
lan4s5t
lan6taa2
la2n1t
lan7taa1t1
lan4t5j
lan6t1re
lant2r
4lan2t1w
lap3r
la2p
4l3a2r1t
las7elek
las2
la1se
la3sel
lase3le
las6ie.
la1si
la3sie
la4sn
la4so
la5sol
la4s3p
las5pa
late5r6a
la1t
5lawaa2
la1w
lba6spe
l1b2
lbas3p
lbas2
lbe6k1ne
lbe1k2n
lbo6wvi
lbo1w
lbow1v2
lb4re
l4d5a2m1b2
l1d
lda2m
ldan7ha.
l3dan
lda2n1h2
ld5a2p1t
lda2p
l4d5ee2n1
ld5eis2
l2dei
lde6ra1t
lde1ra
lder7o2s.
lde4ro
lderos2
l4d5i2n1s2
l2d3of
ld5oor1
ldoo2
ld6oo2r.
l1d5or1d
l3dor
l4do1w
ld5owe
l5draa2
ld2r
l3dra
l4d3re
ld1s4k2
l2ds2
lds6maa2
ld2s3m
lds3ma
ld3s1o
l1ds6o2n1d
lds3on
ld3s1p
l4d5ui2t
1le
lec5t2r
le1c2
le2c1t
lee2
lee4g3
4lee1p3
lee1r5a
lee2r
leer7eis2
lee1re
5leer1l
lee1r5o
lee2r5s2
lee4s3
lees7tra
lees4t2r
lee1s1t
lee7tjie
lee1t
leet2j
lee7vaar
lee1v2
leevaa2
4l3ef1f
leg7s6lo2t
le2gs2
leg2s3l
4leien
lei1e
lei7gleu
leig1l
leig1le
lei6kaa2
lei1ka
lei6naa2
lei1na
lei6no2t
lei1no
lei7skoo2
lei2s3k2
leis2
leis1ko
lei6spa
leis3p
lei3s7pan
lei7s6pi1r
lei2t5s2
lei2t2
lek7lo3ti
le1k2l
leklo2t
le4k2n
lek6suu2
le2k1s2
lek1su
3le3li
5len2g1t
le2ng
4len1j
len6s4el
le2n1s2
len1se
len6ste
lens1t
lens7te.
len6tji
le2n1t
lent2j
lent7jie
le5pel
le1p
lep5li
lep2l
lep7oo2g.
lepoo2
lepoo1g3
lep7ra2t1w
lep2r
lepra1t
lep5sk2
le2p1s2
lep6szy
leps1z
leps7zy.
le1r4a
5leraa2
ler6ka2m
le2r1k
ler1ka
lerk7a2m1p
lerk5s1p
ler2k1s2
4ler2t1s2
le2r1t
le4see
le3se
les2
le4se1t
les5e3te
les6ha1b2
le2s1h2
les3ha
les7in3sl
le3si
les2i2n1s2
le4s1ke
le1sk2
les7ke2s.
leskes2
les7lie.
le1sl
le2s1li
les7onde
le1so
leso2n1d
le4s5oo2
le3s1t
les7taak1
les4taa2
le4ste
le5stel
les6tin
les3ti
les5tra
lest2r
le2s4ty
les7uu2r.
le1su
lesuu2
lesuur1
les7we3te
le3s1w
leswe1t
l5etan
le1t
le4t1c2
let5e1m
le3te
let7oor1b2
letoo2
letoor1
let7ro2l.
let2r
let6s1ko
le2t1s2
let1sk2
4leuf
le3u4m
leu2n5s3
leu6r7e2g.
leu1re
leu4r5o
leu3te4
leu2t
lew6ein
le1w
6l5ewe1na
4l5ewig
3ley
1lê1
lê4r1w
lf5aan1
l1fa
lfaa2
lfa7stra
lfas3t
lfas2
lfast2r
lf3ei
l1fe
l4fek
lf5e2k1s2
l4fen
l4f3e1v2
lf4ie
l1fi
l4fin
lf5i2ng
lf3l
l5fla4p
lf5onde
l1fo
lfo2n1d
l5fo1ne
lf5on2t1s2
lfo2n1t
l2f3o2p
l2f3r
lf6skar
l2f1s2
lf1sk2
lfs1ka
lfs7ka2r1m
lfs7ko2p.
lfs1ko
lfsko2p
lfs7ku2il
lfs1ku
lfs7nier
lf1sn
lfs1ni
lfs7oo2g.
lf1so
lf4soo1g3
lfsoo2
lf4s7pe2r1k
lf3s1p
lf2t4w
l2f1t
l4f3ui
l1fu
l4f3uu2
l1g
lg4ha
l2g1h2
l4g1li
lgo7la2g1n
l1go
lgo1la
lgo2lag
lg6ordy
lgor1d
lgs6mee
l2gs2
lg2s3m
lgs3me
1li
liat6ji
li1a
lia2t3j
lia1t
lia7tjie
3li2d
lid7on2t1s2
li3do
lido2n1t
3lie1d
1lie6g1li
lie6kwy
lie1k2w
lie5la
lier2s5w
lie2rs2
lie5s4me
lie2s1m
lies2
lie7steg
lie1s1t
lie7s2tys2
lie2sty
lie7s4wak
lie4s3w
4l3i4eu
lig7inte
li1gi
ligi2n1t
lig6las2
lig1l
lig6ny.
li2g1n
lig7om1ge
li1go
ligo2m
ligo2m1g
lig7re1k2l
li1g2r
lig1re
lig7rie1t
lig1r2i
li4gro
lig7s2k1ag
li2gs2
lig2s1k2
ligs1ka
lig7s6o1na
lig2s3o
lig5s4p
lig5s4w
lig7u3r2e.
li1gu
ligu1re
lik7aspa
li1ka
li3kas2
likas3p
4li2k1k
5likke1w
lik3ke
li4k2l
lik7op1si
li1ko
liko2p
liko2p1s2
lik6see
li2k1s2
lik1se
lik6sju
lik1s1j
lik6soo2
lik1so
li4k5wa
li1k2w
lin6gi2d
li2ng
lin1gi
lin6gin
1lin6g1li
ling1l
lin6goo2
lin1go
ling7ooi1
4li2n1h2
lin4k5l
li2n1k
lin4k2r
1l5in1li
li2n1l
4l3in1r
l5ins1p
li2n1s2
lin4t5j
li2n1t
4li2n1v2
li4pa
li2p
li4p3l
lip5la
li5p4lo
li6po2m1l
li3po
li3po2m
lip7om1ly
li4p3r
li2p1s4
lip7soo2m1
lip4s3oo2
lip1so
5lisen
lis2
li1se
l5ite1m
li2t
li3te
liter6t7j
lit4er
li1te2r1t
lit3j
litjie6
lit3r
lit7s4ha.
li2t1s2
lit2s1h2
lits3ha
lit4s1p
li1t4s5t
lit6zdo
li2t1z
litz1d
ljus4
l1j
l1k
l4kaf
l1ka
lka6ti1o
lka1t
lkat7ion
l4k5ee2n1
l1ke
lkee2
lks7emos2
l2k1s2
lk1se
lkse1m
lks7e3pos2
lkse1p
lk6ska2p
lk1sk2
lks1ka
lk4s1ku
lk4sl
lk4son
lk1so
lks7ower
lkso1w
lk5spe
lk2s1p
lk5sp2r
lk6stel
lks1t
lks7te2l1l
lk1te2r6t
l2k1t
lktert7j
lk5u2il
l1ku
lk5wa1t
l1k2w
lk5wi2t
l4kwy
lk5wyf
2l1l
l3la
llat6ji
lla2t3j
lla1t
lla7tjie
llei5s2
l1le
lle7k4no2p
lle4k2n
llek1no
lle6rui
lle1ru
lle6swe
lle3s1w
lles2
lleve7ë.
lle1v2
lleve2
lleve1ë
llo5s1k2
llos2
lls7moor1
l2l1s2
ll2s1m
llsmoo2
l4m1af
l1m
lmo6kal
lmo1ka
lmo4no
l1m3s1m
l2m1s2
l1n
lne4s2
l1ne
lob7ee2n1d
lo1b2
lobee2n1
loe6dal
loe1d
loe6de1t
loe3de
loe7d6ja.
loe2d1j
loe6don
loe4d5r
4loef
loe6gos2
loe1go
loeg7o2s.
loer5s7te
loe2rs2
loer1s1t
loe6sk2r
loes2
loe1sk2
loe4s1t
lof7op1r2i
lo1fo
lo2fo2p
lofop3r
lof6spa
lo2f1s2
lof3s1p
4l3ogig
lo1g
lo1gi
lo1g4o
lo5g1o2p
log7s6o2t.
lo2gs2
log2s3o
logso2t
log4s1t
log7stok
lo4k3l
lok7onde
lo1ko
loko2n1d
lok7s6win
lo2k1s2
lok2s3w
loks3wi
lo5kwi
lo2k3w
l5olie.
lo1li
lomer4
lo2m
lo3me
lome2r1t5
lo2m1s4
4lo2n1d
lon6gaa2
lo2ng
lon1ga
long7aar
lon6spa
lo2n1s2
lons1p
lon6ste
lons4t
lons7te.
4lo2n1t
lon4t5j
3loo1d
loo2
loof2s5w
loof1
loo2f1s2
l4oo2p1
5loo2p1b2
l5oo1re
loor1
5loo2s1h2
loos2
loo7stra
loos3t
loost2r
lop7emme
lo2p
lo3pe
lo4pe2m1m
lope1m
4lop3l
lop6rys2
lop3r
lo3pry
lo3ro
lo5ryn
5lo3se.
lo3se
los2
lo4s1j
lo4s1k2
los7laa1t1
lo2s3l
los4laa2
los5ta
los3t
los7tr2u2m
los4tru
lost2r
los7wi2k1k
lo2s3w
los3wi
los4wik
lo4tak
lo2t
lo3ta
lo1t7rie1t
lot2r
lot1r2i
lot7ruïn
lotru1ï
lot7swan
lo2t1s2
lot2s1w
lo4t5ui
lo3tu
loui7s6a.
lou1i
loui4s3a
louis2
lou3t
lou6w1na
lou1w
lou6w1re
louw2r
lou6wt2j
louw1t
lo4w2r
lo1w
low5ry
lox7er4a.
loxe1
loxe1ra
lö4j1d
l1ö
lö1j
löj6don
lp5aan1
l1p
lpaa2
lpe6nin
lpe3ni
lp4he
l2p1h2
l4pon
lp5o2n1d
l1r
2l1s2
l4s5aar
l1sa
lsaa2
l4sa2d
ls5a2r1m
l4s5as3p
lsas2
l4s5e1ko
l1se
l3se4l
lse5le
lse6mek
lse1m
ls5erva
lser1v2
ls5fei
l2s1f2
ls1fe
lsg6haa2
l2s1g
ls2g1h2
lsi6g5aa2
l1si
lsi1ga
ls5jas2
l1s1j
l2sja
l4s5kin
l1sk2
ls1ki
l4s3kon
ls1ko
l6sko2r1r2
l4sk4re
lsk2r
l4skru
l5s6maak1
l2s1m
ls3ma
lsmaa2
l5s4mee
ls3me
l4snaa2
l1sn
ls1na
ls5o2p1w
l1so
l2s1o2p
ls6ple1t
l2s3p2l
ls1p
lsp1le
l5s4p1li
l4spu
l3s1t
ls4ti
l6s5toeg
ls5waar
l2s1w
lswaa2
ls5we1t
ls5wyn
l4t3ag
l1t
l4t5a2m1p
lta2m
lta7spie
ltas4p
ltas2
lter6sk2
lte2rs2
lt5oo2n1d
l3toon1
ltoo2
l5t4wak
l2t1w
lu4b1h2
lu1b2
lu4bl
lub5le
lub7lo1ka
lublo4k3
lu2g1
lug6e2r.
lu1ge
lu3ger
lu5g4u1b2
lu1gu
3lui.
4l5uie.
lui1e
lui7ma1si
lui4ma
lui2m
luimas2
lui7slan
lui2s3l
luis2
4lui2t
luk5raa2
luk2r
luk7ra1ke
luk6s5pa
lu2k1s2
luk2s1p
l5unie.
lu1ni
lun3s6a.
lu2n2s3
lun1sa
lur6pag
lur1p
lus7moor1
lus2
lu2s1m
lusmoo2
lu3t4h2
lu2t
lut6zpu
lu2t1z
lutz1p
luus6te
luu2
luus3
luus3t
luu7ste.
lva7s6oor1
l1v2
lva4s3oo2
lvas2
lva1so
lve5ti
lve2
lve1t
lwe4r5a
l1w
lwe6rui
lwe1ru
1ly
lyce7u2m.
ly1c2
lyceu2m
ly4fe
ly4fo
lyk7aa2n1t
ly1ka
lykaa2
lykaan1
lyk7lo3pe
ly2k2l
lyklo2p
lyk7lu2g.
lyk4lu
ly4klu2g1
ly4k2n
ly4k3o
lyk6o2n1t
lyk7re3de
lyk2r
lyk1re
lykre1d
lyk5sk2
ly2k1s2
ly4ma
ly1m
lym5ag
lym7ui2n1t
3lyn
lyn6aaf
ly2n1a
lynaa2
ly3p2l
ly2p
ly3s1p
lys2
ly4t7ri2ng
ly2t
lyt2r
lyt1r2i
1m
2m.
mac7do1na
ma1c2
mac1d
made7u2s.
ma2d
ma3de
madeus2
mae4s2
ma1e
m1af
4ma2f1d
m4a1fo
ma5f3ro
ma1f2r
4ma2f1s2
mag6sta
mag1s4t
ma2gs2
ma4h1d
ma1h2
mah5di
mak6lo2t
ma2k3l
ma3k2w
ma5lag
ma1la
mal7t6hus2
mal1t
mal2t1h2
mama2t6j
1ma2m
ma3ma
mama1t
mama7tji
man7d6jar
ma2n1d
man2d1j
ma2n3g4
man7go1na
man4g3on
man1go
man6n-1p
man1n
mann2-1
1man7sal1m
ma2n1s2
man1sa
man7spen
mans1p
man6sp2r
man6s7taa2
mans1t
man6sto
man7u3r2e.
ma1nu
manu1re
map4l
ma2p
ma3ra
mar6kek
ma2r1k
mar1ke
mar6k1le
mar1k2l
1mar6ko2m
mar1ko
mar6kon
mar4k5r
mar6lp2r
mar1l
marl1p
mar4s5k2
ma2rs2
mar4s5t
mar6tro
ma2r1t
mart2r
mary7n4a.
ma1ry
mary2n1a
mas6koo2
ma2s3k2
mas2
mas1ko
ma5s6k1r2i
mask2r
mas6kui
mas1ku
mas6tek2
mas3t
ma5s4t2r
mat6hes2
ma1t
ma2t1h2
mat7t6hys2
ma2t1t
mat2t1h2
may7n6ar1d
may2n1a
2m1b2
mb4re
2m1d
mdo6po1ë
mdo2p
mdo3po
mdop7oë.
md5soo2
m2ds2
md2s1o
m3d1w
md4wa
4meder
me3de
me1d
mee7ko1le
mee2k
mee1ko
mee5k2r
5mee2l.
mee5l4o
mee5ne
mee2n1
mee7reis2
mee2r
mee1re
mee7re3ke
meer4ek
mee5sl
mee2s3
mee7s6p1re
mee1s1p
meesp2r
mees6t7al
mee1s1t
mee6t1re
mee1t
meet2r
me3ga
megas4
mega5s3t
4me2g1t
mei6nee2
mei1ne
mel6aar
me1la
melaa2
mel7ekwa
me3le
mele1k2w
mel6kal
mel1k
mel1ka
mel6kjo
melk1j
mel6kla
mel1k2l
mel6k1na
mel1k2n
mel4k5r
mel2k5s4
mel4k5w
mel7s4pul
me2l1s2
mel4spu
mels1p
mel6too2
mel1t
mem7phis2
me1m
me2m1p
mem2p1h2
memp2hi
men7an2gs2
me1na
mena2ng
men7ei1se
me1ne
meneis2
men7op1ga
me1no
meno2p
meno2p1g
men6sky
me2n1s2
men1sk2
men6s1nu
men1sn
men4s5p
men6s5ta
mens1t
men6tin
me2n1t
men4t5j
men4t5r
me5p2hi
me1p
me2p1h2
me4rak
me1ra
me6ra2s2s2
meras2
mer7as3se
mer5as3t
mer7dein
mer1d
mer2dei
me4rei
me1re
me6re2ng
mer7en1ge
mer7es3se
me3r4es2
mere2s2s2
mering8s9taa2
me1r2i
meri2ng
merin2gs2
mering1s1t
mer6k1li
me2r1k
mer1k2l
mer6k1na
mer1k2n
mer7k6o2p1n
mer1ko
merko2p
mer4k2w
mer7k4war
mer5oes2
me1ro
mer7on2t1h2
mero2n1t
mer7t3re1ë
me2r1t
mert2r
mert1re
me1s4a
mes2
me4sal
me4s5ka
me1sk2
me6s7koor1
mes1ko
meskoo2
me6skor
mes7ko1re
me6skro
mesk2r
mes7kroe
me4sl
me5slu
mes7mo2s2s2
1me2s1m
mesmos2
mes7po2r1t
me1s1p
me6s5tas2
me1s1t
me4s5to
mes7wa1re
me3s1w
me4t5ee
me3te
me1t
met7em2p1s2
mete1m
mete2m1p
meter6so
mete2rs2
meu6las2
meu1la
meul7a2s.
2m1f
mfloer6
2m1g
mga2ng4
m1ga
mgan2gs5
mgeper6
m1ge
mge1p
mge4s7per
mge3s1p
mges2
2m1h2
mh4ei
4mi2d.
mi2d
mid7ose1a
mi3do
mido3se
midos2
4mi2d2s2
mid1s5t
mie6kas2
mie1ka
mie6kwa
mie1k2w
mie6re1t
mie1re
mie4r5y
mie6sk2r
mies2
mie1sk2
mie6taa2
mie1t
mie6tji
miet2j
miet7jie
mig6r2e.
mi1g2r
mig1re
migu7e2l.
mi1gu
migu2e
mih7ra2b.
mi1h2
mi2hr
mihra1b2
mil6taa2
m2il
mil1t
min7g6op3l
mi2ng
min1go
min2g1o2p
4m5ins1p
mi2n1s2
6min3s4tu
mins4t
mi4r1l
mi1r
mi3s1f2
mis2
mis7sê2r.
mi2s2s2
mis1sê1
mis6tk2r
mis3t
mis2t1k
mis6tok
mit7swa.
mi2t
mi2t1s2
mit2s1w
mi4v2-1
mi1v2
2m1k
mkaar4
m1ka
mkaa2
mkom6s1t
m1ko
1m3ko2m
mko2m1s2
mkoms7te
2m1l
m3la
2m1m
mma5s3p
mmas2
mmas6to
mmas3t
mma7stor
mmat6ji
mma2t3j
mma1t
mma7tjie
mme7loor1
mme1lo
mmeloo2
mme6r4es2
mme1re
mme4r5o
mmi7s6tok
mmis3t
mmis2
2m1n
3mo1d
mode4l
mo1d6ja2d
mo2d1j
3moe
moe2d4s2
moe1d
moe6nes2
moe1ne
moe4s1t
moes2
1mof6la2m
mof3l
mok7alba
mo1ka
mokal1b2
mole4s5
mo1le
m5olie.
mo1li
mol4m5a
mol1m
mon6dc1h2
mo2n1d
mond1c2
mon6do2p
4m5on2t1s2
mo2n1t
3moon1
moo2
moor6da
moor1
moor1d
2mo2p
m3op3l
4mor1g
mo3ro
mor6sju
mo2rs2
mor1s1j
mor6spo
mor1s1p
mor4s5t
mo5saa2
mos2
mo1sa
mo2s3f2
mos7fles2
mos3fl
mosf2le
mos7i6nen
mo3si
mosi1ne
mo4s1ke
mo1s1k2
mos7keë.
moske1ë
mo5s4ta
mos3t
3mo2t
mote7u2s.
mo3te
moteus2
mot6heu
mo4the
mo2t1h2
moto1r5a
mo3to
mou5fl
mou7s6li2p
mou2s3l
mous2
mou2s1li
mou6ste
mou3s4t
mous7te.
mou6tek2
mou2t
mou3te
mo9y4a.
moy1a
5mô3r2e.
mô1
mô1re
2m1p
m4pag
mpa7g6ne.
mpa2g1n
mpag1ne
mp5a2g1t
mpe6lys2
mpe3ly
mpen6to
mpe2n1t
mp4he2r
m2p1h2
m4p5o2p1s2
m1po2p
mps7kraa2
m2p1s2
mp1sk2
mpsk2r
mp5s1li
mp1sl
mps7taal1
mps4taa2
mps1t
2m1r
2m1s2
ms5a2p1p
m1sa
msa2p
m4s5ka1t
m1sk2
ms1ka
m4s3kon
ms1ko
ms7kraal1
msk2r
mskraa2
m5slin
m1sl
m2s1li
m3s4me
1m2s1m
m2s3o2p
m1so
m4s5pen
ms1p
m6s5taal1
ms4taa2
ms1t
ms4te
m5steg
m5s3te1o
m3s1w
2m1t
mter6t5j
m1te2r1t
muc7k6len
mu1c2
mu2ck
muc1k2l
muck1le
mues7li.
mu1e
mues2
mue1sl
mue2s1li
muf7s6maa2
mu2f1s2
1muf3s1m
muf4s3ma
mui6les2
mu2il
mui1le
4mui2t
3mul
mum7aa2n1t
1mu2m
mu3ma
mumaa2
mumaan1
mu2n2s4
mun5s1t
mun6tou
mu2n1t
mur4g
mur7gie.
mur1gi
3mus2
2m1v2
mvi6tra
mvi3t2r
mvi2t
2m1w
myl7afs1t
my1la
my2laf
myla2f1s2
myl6sla
my2l1s2
myl1sl
3myn
myn7en2t.
my1ne
myne2n1t
myn7impa
my1ni
1myni2m
myni2m1p
myn7in1ge
myni2ng
m1ys2
2m2-1
2n.
1na
3naal1
naa2
3n4aa2m1
4n3aan1
6n5aardi
naar1d
naar6sk2r
naa2rs2
naar1sk2
n5admi
na2d
na2d1m
4n3a2d1v2
nae6lys2
na1e
nae3ly
nael7ys3t
n1af
n4a1fi
naf6la2d
naf3l
nag6aa2n1d
nag3aa2
na1ga
nagaan1
na6gaa2p1
na4g5a2p
na6ge2m1m
n4age1m
na1ge
nag7emme
nag6las2
nag1l
n4a1go
nag5ron
na1g2r
na2g3s2
4na2g1t
n5agtig
na4gu
nai7s4e2t.
nai1
nai4s3e1t
nais2
nai1se
na5k1li
na2k3l
nak6li4p3
4nalf
4nal1t
3na2m
na3p4l
na2p
na3p2r
nap7roe1t
napr4oe
4n3ar1b2
4n3are1a
na1re
na3s4k2
nas2
nas7kli4p3
na2s3k2l
nask1li
na3s4l
nas6maa2
na2s3m
nas3ma
na3s3p
nas6pel
nas4p2r
na5s4ta
nas3t
nas6ten
nas6tor
na5s4t2r
na5stu
nas6tuu2
na2s4w
na5swe
na5t4ha
na1t
na2t1h2
nat6jie
na2t3j
nat7onde
na4to2n1d
3na1v2
5nawee
na1w
na9y2l.
2n1b2
nba6chs2
nba1c2
nbac1h2
4nche
n1c2
nc1h2
2n1d
nda7ge1s4k2
nda2g
nd4ages2
nda1ge
nd5a2k1t
nd5a2p1p
nda2p
n4d5ar1b2
n4d5a2r1t
n4d5a2s2s2
ndas2
nda7stoe
ndas3t
nde7eier
ndeei1e
n4d5ee2n1
n4d5e2g.
n2d3ei
n4d5ek1sa
nde2k1s2
ndel8s7kor
nde2l1s2
ndel1sk2
ndels1ko
ndel8s7taa2
ndel3s1t
n4d5e2m1m
nde1m
n1de6ra2d
nde1ra
nde6raf
nder7a2f.
nde6r3ar
nde6re1m
nde1re
nde6r7e2n1t
nde6r7e2s2s2
nde3r4es2
nde6ri2m
nde1r2i
nde6r7i2n.
n6deros2
nde4ro
nd5e7ro3si
nde7si2l1l
nde3si
ndes2
ndes2il
nde6zvo
nde1z
ndez1v2
nd5i3de
n1di2d
ndi5go
n4d5i2n1s2
n2d3of
n5do3me
n3do2m
n4d5o2m1t
2n1d5o2n1d
nd5o2n1t
n4d5o2p1b2
ndo2p
ndo5s3t
ndos2
nd5rak
nd2r
n3dra
nd5ra1t
n4d3re
n1d6re2s1d
ndres2
n4d5rif1f
nd1r2i
n4d5ri1v2
nd5roe
nd5rok
nd5ro2t
n6d5rui2m
nds7er6t2j
n2ds2
nd3se
ndse2r1t
nds7ge1ru
nd2s1g
nds3ge
nds3ger
nd6si2n1l
nd1si
nd4s7ken1n
nd1sk2
nds1ke
nds7koor1
nd5skoo2
nds1ko
nd6s7kraa2
ndsk2r
nd6s7laag
nd1sl
nds4laa2
nds6leg
nd4s3le
nd5s6maa2
nd2s3m
nds3ma
nds6o2n1s2
nd2s1o
nds3on
nds7oo2r1n
ndsoo2
ndsoor1
nd5sor
nd6sp1re
nds1p
ndsp2r
nd5spu
nds7taal1
nd1s1t
nds4taa2
nd6stek2
nds7toe1t
nd4s7troe
nd3st2r
nds6wee
nd2s3w
ndt6wis2
n2d1t
nd2t1w
ndu4e
n4d5ui2t
ndu7kraa2
nduk2r
n4d3ys2
1ne
nebe6s4t
ne1b2
nebes2
nec7ticu
ne1c2
ne2c1t
necti1c2
5neder
ne3de
ne1d
nee2
nee4l
nee1l5a
n4ee1m
4n5ee2n1d
nee2n1
4n5ee2n1h2
nee1r5o
nee2r
nee2r5s2
nee6te1w
nee3te
nee1t
neeu3
nee7u6u2r.
nee1uu2
neeuur1
nee7woor1
nee1w
neewoo2
4nef1f
4ne2g.
ne5gla
n3ei1e
4ne2il
4nek2s1p
ne2k1s2
n5ekspe
nel6lma
ne2l1l
nell1m
ne6loon1
ne1lo
neloo2
nel7oo2n1d
nel6spo
ne2l1s2
nels1p
3ne1m
nem6a1fi
nem1af
nen4sl
ne2n1s2
nep7olie
ne1p
ne3pol
nepo1li
3ne2r.
ner6faf
ner1fa
nerf7a2f.
ne4ros2
ne1ro
ner7p6sig
ner1p
ner2p1s2
nerp1si
ner6s1le
ne2rs2
ner1sl
ner5s1w
ners6we
nes7evan
ne3se
nes2
nese1v2
nes6tas2
ne1s1t
ne4ste
net7om1ge
ne1t
neto2m
neto2m1g
ne4t1r2i
net2r
ne5u2m.
neu2m
neu7mo1ko
neu7r6aal1
neu4ra
neur5aa2
neu6sji
neus2
neu1s1j
neu6s1ka
neu2s3k2
neu7stoo2
neus3t
neus4to
neu6to1ë
neu2t
neu3to
neut7oë.
ne4w1t
ne1w
3ne1z
1nê1
2n1f2
n3fl
nfy6ta2p
n1fy
nfy2t
nfy3ta
2ng
n4ga2d
n1ga
n4g3ak
n4g5a2p1t
nga2p
n4g5a1se
ngas2
ng5a2s2s2
n4g5ee2n1
n1ge
n4g3ei
n4g5e2k1s2
nge6r7aa2p1
n3ger
nge1ra
ngeraa2
nge6r5al
nger6d5r
nger1d
nge6re1t
nge1re
nge4s7per
nge3s1p
nges2
ng5eten
nge3te
nge1t
n6geter
n4g5i2m1p
n1gi
ngi2m
n4g5i2n1k
n6gins4t
ngi2n1s2
ng1l
ng6la1su
nglas2
n4go1ë
n1go
ng5oë.
n4g3on
n4g5oo1g3
ngoo2
n4gou
ngp6seu
n2g1p
ng2p1s2
ngp1se
n6g5raa2d1
n1g2r
ngraa2
ng5ran
ng5ra1t
n4g3r2i
ng7s6ade.
n2gs2
ng2s1a
ngs3a2d
ngsa3de
ngs7agi2t
ng2sag
ngs4a3gi
ng7s6appe
ngs5a2p1p
ngsa2p
ng4see
ng1se
ng6s7ei1se
ngseis2
ng4sek
ng6se2r1k
ngs7er1ke
ng6se1ro
ng6ser1v2
ngs6fer
ng2s3f2
ngs1fe
ng6s7impa
ng1si
ngsi2m
ngsi2m1p
ng4sin
ngs5i2n1t
ngs5kan
ng2s1k2
ngs1ka
ng7ska2t.
ngska1t
ng7skoel
ngs1ko
ngs7koe1p
ngs7ko2p.
ngsko2p
ng7s2ko2rs2
ngs7ko2r1t
ngs7kur1w
ngs1ku
ngs7kuur1
ngskuu2
ng6s5laa2
ng2s3l
ng3s4la
ng6sla1b2
ngs7labo
ngs7la3di
ng4s3la2d
ngs6le1p
ngs1le
ng7s6l4oo2p1
ngsloo2
ngs6ly1m
ngs1ly
ng4sn
ng5s1ni
ngs6oe1t
ng2s3o
ngs6o1fa
ngs6o2m1m
ngso2m
ngs7pe1lo
ngs3p
ng5s4pel
ng6s7pi2l1l
ng3s4pi
ngsp2il
ng5s4poe
ng7s6tabi
ng1s1t
ng4s5ta1b2
ngs7ta2l.
ng4stal
ng6ste1h2
ng6st4ei
ngs7teik
ng6s7tel1g
ng6ste1m
ngs7tema
ng7s4te2m1m
ngs7te2m1p
ng6s5ten
ng6s7te3se
ngstes2
ngs6tin
ng4s3ti
ng6stou
ngs7tou.
ng6s7tra2p
ngst2r
ng6s7t2ruu2
ngs4tru
ngs6ui1e
ng4s3ui
ng1su
ngs7u3r2e.
ngsu1re
ngs6wel
ng2s3w
n4g5ui2t
n1gu
2n1h2
1ni
nie6kaa2
nie1ka
nie7knik
1nie4k1ni
nie1k2n
nie6raa2
nie1ra
nier7aar
nier2s5w
nie2rs2
nie6uin
ni1eu
ni4e3ui
nig7aar1d
ni1ga
nigaa2
ni4g5ee
ni1ge
ni4g5ie
ni1gi
niks7py.
ni2k1s2
nik2s1p
nik7wa2r1m
ni1k2w
ni3k4war
4n3i2n1d
4n2i2n1f2
nin6g7e3le
ni2ng
nin1ge
nin6ge1t
4ni2n1h2
4n3i2n1s2
4n3i2n1t
4ni2n1v2
n5invo
n2is2
nis7alma
ni1sa
ni4sal1m
nis6a1ra
nis3ar
ni5see
ni1se
nis5i2d
ni1si
nis7ins1p
nis2i2n1s2
ni4s6o2m1s2
ni1so
niso2m
ni4son
ni3t2r
ni2t
nit7sa1re
ni2t1s2
nit4sar
nit1sa
nje7glas2
n1j
nje7krui
nje4k2r
njie6s1t
njies2
2n1k
nkaar4
n1ka
nkaa2
nk5aar1d
n4k3af
n4kak
n4k5a2k1s2
n4k5ef1f
n1ke
n4kei
nk5ei1e
nke6las2
nke1la
nke6li2t
nke3li
nke6ree
nke1re
nker2s6w
nke2rs2
nker7s3we
n4k3li
n1k2l
n2k2n
nk3na
nk5nes2
nk1ne
nk5neu
n4k3of
n1ko
n5k4o1fi
nk5p4si
n2k1p
nk2p1s2
n4k5rig
nk2r
nk1r2i
nk5rol
n4k5roo2
nk5ros2
nk5ry1e
nks6noe
n2k1s2
nk2s3n
nks1no
nk4s5o2m
nk1so
nk3s1p
nks4t
nk3s4w
n4k5ui2t
n1ku
n2k2w
nk5wa1t
n5kwen
2n1l
n3la
nli4ga
n1li
nlu4s2
n1n
nna6spo
n1na
nna3s3p
nnas2
nna7t6jie
nna2t3j
nna1t
nne6pol
n1ne
nne1p
nne6se1v2
nne3se
nnes2
nn2i4s2
n1ni
1no
4noef
4noes2
noe4s1t
no4g5al
no1g
no1ga
nog7ee2n1s2
no1ge
nogee2n1
no3k5as2
no1ka
no9ko.
no1ko
nok7ri2w1w
no1k2r
nok1r2i
nokri1w
3n4o3ma
no2m
n4o2m1m
nomo7yi.
no3mo
no4n3a
4n3o2n1d
4n3o2n1t
noo6dan
noo2
nood3a
noo1d
n3oo1g3
noor6di
noor1
noor1d
4no2p1b2
no2p
no5pla
nop3l
nop6laa2
4n3oplo
nop7omhu
no3po
no3po2m
no4po2m1h2
no3p6ro1d
nop3r
4nor1g
n5or1ga
nor6kla
no2r1k
nor1k2l
3no2r1m
nor7t6ha2m
no2r1t
nor2t1h2
nos6kaa2
no1s1k2
no4s3ka
nos2
no3s3p
3no3ta
no2t
3no3te
not4r
no5t3re
2n1p
n1r
nroe4t6j
nroe1t
nroe7tji
2n1s2
n4s5aar
n1sa
nsaa2
n2s3ag
ns4an
ns5an2gs2
n3sa2ng
n3s4e.
n1se
n3s4el
nse4p2r
nse1p
nser6to
nse2r1t
nser6t2r
n2s3f2
ns6fee2r
ns1fe
ns5gel
n2s1g
ns3ge
n3si
ns4i1a
n3s4ie
ns4ig
nsi6gar
nsi1ga
n6s7inges2
ns4i2ng
nsin1ge
nsi6t1r2i
nsi3t2r
nsi2t
ns4i1u
n2s3ja
n1s1j
n6s5ka1fe
n1sk2
ns1ka
n5ska2p
n4skar
n4s5kel
ns1ke
ns5kin
ns1ki
n6s2kous2
ns1ko
n4s5kra
nsk2r
n2sl
n3sla
n4s5la2m
n6s5la2n1d
ns3le
n4s3li
n4s3lo
ns6lo2t1g
nslo2t
ns6lo2t1t
n3slu
ns4mee
n2s1m
ns3me
n5s4nar
n1sn
ns1na
n4s1ne
n5s4nel
n4s5noo2
ns1no
n5s4oek
n1so
n4so1ë
ns5oë.
ns5o2n1d
n4s3o2n4t5
n4s5o2n1w
n2s3o2p
ns5par
ns1p
ns5pas2
n4s5per
n6s7port2r
nspo2r1t
n4spo2t
n6s5pra1t
nsp2r
ns6prek
nsp1re
n6staak1
ns4taa2
ns1t
n4staf
ns5te1h2
ns6te2l.
ns6te2l1s2
n5s6te2r.
ns6te2rs2
ns5trak
nst2r
n3s4tu
n4s3t1w
nst6wyf
n4s5ty2d
n2sty
n2s3w
ns6wee2r
n4sy1w
n1sy
n4s5ywe
2n1t
n4t5a2gs2
n4t5a2r1k
n4t5a2r1t
nta5t4j
nta1t
n4t5ei1e
nte6ra2m
nte1ra
nte5s1m
ntes2
nte6s3ti
n1te1s1t
n5t4hon
n2t1h2
nti7k6waa2
nti1k2w
n4t5i2n1v2
n2t3ja
nt2j
n4tjo
n1to7fa2k1t
nto4fa
nt7ok3sie
nto2k1s2
ntok3si
n4t5o1li
nto6na2d
nto1na
n4t5o2n1d
n4to2p
nt5o2p1s2
n4t5or1g
nt7radin
nt2r
ntra2d
ntra3di
n4t5raf
n5tref
nt1re
ntre7kor
ntre4ko
nt5ri2m
nt1r2i
nt5roe
nt5ro2m
nt5ron
n4t5roo2m1
ntroo4
n5troos2
n5trou
nt5ro1w
nt5rui
n4t5ryk
nt3sa
n2t1s2
nts7in1ge
nt1si
nts4i2ng
nt6skan
nt1sk2
nts1ka
nts7ka2n1d
nt4s5le
nt1sl
nt3s4m
nts7onde
nt1so
ntso2n1d
nts5paa2
nts1p
n1t3s1t
n1t5ui2t
ntu4m3
n4t3ys2
1nu
3nu1a
nu4e.
nu1e
nu4e2s.
nues2
2nui
nu2k4w
nu5kwa
nul7s6o2m1s2
nu2l1s2
nul1so
nulso2m
4nu1ni
nu5sk2r
nu2s3k2
nus2
nu6skra
nus4t
nu3t2r
nu2t
4n3uur1
nuu2
2n1v2
nva6lis2
nva1li
2n1w
ny4s3o
nys2
nza6c2s.
n1z
nza1c2
nzacs2
o1a
o4bag
o1b2
ob5a2g1t
o3bo
ob5vor
o2b1v2
oby6nro
oby1
obyn1r
ock7wy2n.
o1c2
o2ck
oc1k2w
oda5g2r
o1d
oda2g
od5ee2n1
ode7lei1e
ode3le
ode7spaa2
ode4spa
ode1s1p
odes2
od5lui
o2d1l
od3o2p
od3re
od2r
ods7ak3ke
o2ds2
od1sa
od3sa2k1k
ods7ki2s1h2
od4s2kis2
od1sk2
ods1ki
od6slak
od1sl
ods7la1ke
ods7lo3pi
od4slo2p
ods5oo2
od2s1o
od3s6o2p.
od2s3o2p
ods6or1g
ods7paar
ods1p
odspaa2
ods7ro2g1g2
od2s1r
ods3ro
odsro1g
od4s6ui2t
od1su
ods6waa2
od2s3w
ods6war
od3s6wy1g
odu4k
oe4d5aa2
oe1d
oe4d5a2g
oe4dei
oe3de
oed7eie.
oedei1e
oed7ette
oede1t
oede2t1t
oe6di2n1d
oed6ja.
oe2d1j
oed7onde
oe1do2n1d
oe4d5oo2
oe4d5o2p
oe4d5or
oe4d5ro
oed2r
oed7s4try
oe2ds2
oed1s1t
oed3st2r
oe4du
oe2d3w
oed7wy2n.
oed4wyn
oe4f5aa2
oe1fa
oe4f5an
3oe1fe
oe4fek
oe4f5lo
oe1fl
oe4f3o
oef7ri2t.
oe1f2r
oef1r2i
oefri2t
oeg7aa2n1d
oe1ga
oegaa2
oegaan1
oeg7laer
oegla1e
oeg7la2m.
oegla2m
oe4go1g
oe1go
oe2g3s2
oeg7ys1ka
oegy2sk2
oegys2
oei1
oeie1n6a
oei1e
oeis4
oei7sker
oei2s3k2
oeis1ke
oe4kaa2
oe1ka
oek7eier
oe3ke
oekei1e
oe6kerf
oek7er2f.
oek7eval
oeke1v2
oe4k3l
oek6la2p
oek7olie
oe1ko
oeko1li
oek7oo2r1t
oekoo2
oekoor1
oe4k2r
oek5rak
oek5re
oek5ro
oek7sten
oe2k1s2
oeks1t
oe4k3w
oe4lei
oe3le
oe5lei1e
oe6le2m1m
oele1m
oel7emme
oel7eter
oele3te
oele1t
oeli2ng6
oe3li
oelin2gs7
oe6li2n1s2
oel7ins4t
oel7onbe
oe1lo
oelo2n1b2
oel7op1r2i
oelo2p
oelop3r
oel6ser
oe2l1s2
oel1se
oel6sk2r
oel1sk2
oel7slik
oel1sl
oel2s1li
oe4nei
oe1ne
oen7ei1ke
oe6ne2r1t
oen7er6t2j
oen7es3se
oenes2
oene2s2s2
oe2n1k4
oen5k2l
oen7knoo2
oen2k2n
oenk1no
oen5k3w
oe4n3o
oens5ko
oe2n1s2
oen1sk2
oen5s1m
oen1s4o
oen7ys3be
oe3ny
oenys2
oeny2s1b2
oep7aa2ng
oe1p
oepaa2
oepaan1
oe4p7ins4t
oepi2n1s2
oe6pi2n1v2
oe4p2l
oe5p4la
oep5li
oe5plo
oep6s5ee
oe2p1s2
oep1se
oep6s5in
oep1si
oep4sl
oe4pu
oera1s5e
oe1ra
oeras2
oe2r3k
oe4r5on
oe1ro
oer5ou
oer6s1ke
oe2rs2
oer1sk2
oer7t6wak
oe2r1t
oer2t1w
oe5s4er
oe3se
oes2
oe5s4ie
oe3si
oe5sje
oe2s1j
oes7kraa2
oe1sk2
oesk2r
oes7li2m1t
oe1sl
oe2s1li
oesli2m
oe4s5lo
oes7medi
oe2s1m
oes4me
oesme1d
oes7pi2l.
oe1s1p
oesp2il
oe4s5po
oes5ter
oe1s1t
oe4s7troe
oest2r
oe4swe
oe3s1w
oet7aa2n1p
oe1t
oetaa2
oetaan1
oe4t5a2m
oet6he.
oe2t1h2
oe4t3j
oe6t5o1li
oe4t5oo2
oe4t5r2i
oet2r
oe4t5ru
oets7kra
oe2t1s2
oet1sk2
oetsk2r
oet6s1na
oet1sn
oet6spe
oets1p
oet6s5te
oe1ts1t
oet6s4ti
oet5wy
oe2t1w
oë5rug
o1ë
of3a1t
o1fa
o4f5ee2n1
o1fe
5offis2
of1f
of1fi
o4f3in
o1fi
of3l
o4fok
o1fo
of5o2k1s2
o4f3o2m
of5p4sa
of1p
of2p1s2
o2f3r
of6sa2n1t
o2f1s2
of1sa
ofs7iden
of1si
ofsi2d
ofsi3de
of6s2i2n1s2
of1s7in3si
of4s5le
of1sl
of4s5oo2
of1so
ofs7paar
of3s1p
ofspaa2
o4fui
o1fu
o1g
og4d.
o2g1d
oge4s5t
o1ge
oges2
ogge6lo
o2g1g2
og1ge
ogge5l7oo2
ogi7faal1
o1gi
ogi4fa
ogifaa2
3ogig
og1l
og4na1t
o2g1n
og1na
ogo7s3te1o
o1go
ogo3s4t
ogos2
o4gry
o1g2r
og5ry1e
og4s.
o2gs2
og4s1g
og4s6i2n1f2
og1si
og5s1ka
og2s1k2
ogs7las3t
og2s3l
og3s4la
og4s3las2
ogs6o2t.
og2s3o
ogso2t
ogs4p
ogs7pa2d.
og4spa2d
ogs7pris2
ogsp2r
ogsp1r2i
ogs6ui2p
og4s3ui
og1su
ogs6wan
og2s3w
oi1
oig6af1f
oi1ga
oi2g1af
oi3k
oi1le4
o2il
oi1s4a
ois2
oi5s3ag
oi5s1ki
oi2s3k2
ois6kuu2
ois1ku
oi5sky
oi5sla
oi2s3l
ois4p
ois4t
ois7teïs2
oiste1ï
ois6wer
oi2s3w
oi3t2j
oi2t
oje4k
o1j
oje1k5l
o4k5aas3
o1ka
okaa2
o4k1ag
o3k4a2m
ok4an
oke4t
o1ke
oket5j
oki2e4
o1ki
o4k5i2n1s2
ok5la1t
o1k2l
ok6lee1d
ok1le
oklee2
o1k3n
oko7se2il
o1ko
o3kos2
oko3se
oko6sol
oko1so
o4kou
o1k2r
o4k3ro
ok4s.
o2k1s2
ok5sig
ok3si
ok6s5i2n1s2
ok4s1j
oks7kraa2
ok1sk2
oksk2r
oks6li2p
ok2s3li
ok1sl
ok3s1p
ok3s1t
oks6win
ok2s3w
oks3wi
o2k3w
ok4win
o1la
ol5a2g1t
o2lag
olf7en2t.
ol4fen
ol1fe
olfe2n1t
ol5fèg
ol5f6la4p
olf3l
olf6s1ku
ol2f1s2
olf1sk2
olf6s4me
olf3s1m
ol4gl
ol1g
olg7onde
ol1go
olgo2n1d
ol4g5or
olg7s6mee
ol2gs2
olg2s3m
olgs3me
5olie1b2
o1li
olie2s6m
olies2
olie7s3ma
5oli1fa
oli7g6ny.
oli2g1n
olk6se1m
ol1k
ol2k1s2
olk1se
olk6se1p
olk6so1w
olk1so
olk6s3ti
olks1t
o1lo
olo5k3w
o4l5oor1
oloo2
olo5s3p
olos2
olp6hta
ol1p
ol2p1h2
olph1t
ol4sar
o2l1s2
ol1sa
ols7a3r2e.
olsa1re
olt6zha
ol1t
ol2t1z
oltz1h2
o1lu
oly4f5o
o1ly
o2m
o3ma
om5a2g1t
oma7pleg
omap4l
oma2p
omap1le
o4m5a2r1m
omat6ji
oma2t3j
oma1t
oma7tjie
o3me
ome4s2
ome5us2
omg6ha2d
o2m1g
om2g1h2
o3mi
om3ka5s4
o2m1k
om1ka
5oml4oo2p1
o2m1l
omloo2
o3mo
omos6fe
omo2s3f2
omos2
omo7sfee
omp7li2gs2
o2m1p
omp2l
ompli4g
omp1li
om4p7oo2r.
ompoo2
ompoor1
om6po2p.
om1po2p
om3s6aal1
o2m1s2
om1sa
omsaa2
om5sla
om1sl
oms6la4g
om4s7pe2r1k
oms1p
5omstan
oms1t
oms6tin
oms3ti
o4n3ag
o1na
o5n4a1ge
o3n4an
ona6s3k2l
ona3s4k2
onas2
ona7s6ten
onas3t
ond7aa2p.
o2n1d
ondaa2
ondaa2p1
ond5a2g1t
onda2g
ond7am2p1t
onda2m
onda2m1p
ond7dwaa2
on2d1d
ond2d1w
4ondes2
onde7u2s.
ondeus2
on4di2d
on4do2m
ond7ro2l.
ond2r
ond5s3le
on2ds2
ond1sl
ond5s1o
ond5s1p
ond6s3p2l
ond7t6wis2
on2d1t
ond2t1w
o4n5ef1f
o1ne
o4n3ei
ong5aan1
o2ng
on1ga
ongaa2
on4g5os2
on1go
o3n4ik
o1ni
o4n2il
onin6g2r
oni2ng
onk7ert7j
o2n1k
on1ke
onke2r1t
on4k1j
on5k1no
on2k2n
onk7om1ge
on1ko
on3ko2m
on4ko2m1g
onk7ru2g1h2
onk2r
onne5s1t
on1n
on1ne
onnes2
o4n5o2k1s2
o1no
ono7sfee
onos2
ono2s1f2
onos1fe
on4s.
o2n1s2
onse4p5
on1se
ons7iden
on3si
onsi2d
onsi3de
ons7kepe
on1sk2
on3s4ke1p
ons1ke
ons7ko1r2i
ons1ko
ons7pa2m1p
ons1p
onspa2m
ons7self
on2s2s2
ons3se
ons3sel
ons4t
on5ste
ons7to2l.
on6t5aar
o2n1t
ontaa2
3on2t1d
ont7el1si
onte2l1s2
5ont1gi
on2t1g
ont5raa2
ont2r
on4t1r2i
ont7ro2l.
on4t5ru
ont7slik
on2t1s2
ont1sl
ont2s1li
3on2t1w
o1ny
ony7okol
onyo1ko
oo2
oo5a2g1t
oo1a
ood3a
oo1d
oo5de1b2
ood5ee
oo5dek
ood5er
ood5e1t
ood5ok
ood7on1ge
oodo2ng
ood3r
ood6san
oo2ds2
ood1sa
oo2d7smoo2
ood2s3m
ood7s6or1g
ood2s1o
ood7spui
oods1p
ood7s6ui2t
ood1su
ood7s6waa2
ood2s3w
ood7s6war
ood5ui
oof1
oof6ser
oo2f1s2
oof1se
oof6si2d
oof1si
oo1g3
oog6e2n1h2
oo1ge
oo3gen
oog6les2
oog1l
oog1le
5oogpu
oo2g1p
oog7s6i2n1f2
oo2gs2
oog1si
oog7s6ui2p
oog4s3ui
oog1su
oog6-lo
oo2g2-1
ooi6spa
ooi1
oois4p
oois2
ook3
oo4ka
ook6s3s1t
oo2k1s2
ook2s2s2
ool1
oo3l6a2n1d
oo1la
ool7snaa2
oo2l1s2
ool1sn
ools1na
oo2m1
oo4ma
oo4me
oo4mo
oon1
oon6a2g.
oo4n3ag
oo1na
oo2ng4
oons6ko
oo2n1s2
oon1sk2
oon5sl
oo2p1
oo4pa
oo4pe
oop7k6lik
oo2p1k
oop1k2l
oopk1li
o4o2p1n
oo4po
oop6swe
oop3s1w
oo2p1s2
oor1
oor6daa2
oor1d
oor7daa2d1
oor6da2p
oor7d6a2r1m
oor6dca
oord1c2
oor6d5o2m
oor3do
oor7d6oo2p1
oo2r4d5oo2
oor7frek
oor4f1re
oor1f2r
oo1r3i
oor7k6lik
oo2r1k
oor1k2l
oork1li
5oorlo1g
oor1l
oo5r6o2t.
ooro2t
4oor2t.
oo2r1t
oor6taa2
oor6t5in
oort7ja.
oort2j
oor2tja
oor6tyl
oort7y2l.
oorve7ë.
oor1v2
oorve2
oorve1ë
oo1s3a
oos2
oo1s3k2
oo4so
oo4s7pe2rs2
oos3p
oos4pe
oo2t1
oo4ta
oot6aai1
ootaa2
oot6e2n.
oo3te
oo4ti
oo4to
oo4t3r
oo4tu
oo3v2
oö5spo
o1ö
oös2
oös1p
o2p
o3pa
o4p1af
op5a2g1t
opa6les2
o3pa1le
o4p3a2m
5opd2rag
o2p1d
opd2r
op3dra
o3pe
o4p5ee2n1
opee2
op9e2g.
5ope3ni
op4er
oper7aar
o3pe1ra
operaa2
ope7ra1ge
ope2r3ag
op6hor1u
o2p1h2
o3pi
opie6le
op4k6lik
o2p1k
op1k2l
opk1li
op3l
op4lan
op6lein
op1le
3oplo
1o2p1n
4op1no
o3po
opo7f4a3gi
opo4fa
opo2f1ag
op5of1f
o4p5o2n1t
opo7sfee
o3pos2
opo2s1f2
opos1fe
5opper1v2
o2p1p
op3r
o5p4ro2t
o4pru
op6sk4re
o2p1s2
op1sk2
opsk2r
ops6maa2
op2s3m
ops3ma
ops7neus2
op1sn
op4sneu
ops1ne
op5son
op1so
op4s5or
op3s1t
op3s1w
ops7we2l1s2
o3pu
o1ra
o4r5a2f1d
or5a2g1t
o2rag
ora6lee2
ora1le
ora6loo2
ora1lo
orat6ji
ora2t3j
ora1t
ora7tjie
ord7ak3ti
or1d
orda2k1t
or4d6a2r1m
or4d7ek1sa
orde2k1s2
ord7ier1l
ord7ins4t
or4di2n1s2
ord5oes2
or3do
or4d5oo2
ord6oo2p1
or6dor1d
or3dor
ord7orde
or4d5r2i
ord2r
or6d7ro3ma
ordro2m
ore7ster
o1re
ore4ste
ore1s1t
ores2
o3rê1
orf7oo2n1d
or1fo
orfoo2
orfoon1
4or2g.
or1g
or3g1h2
or4glo
4or2g1p
org7ri2ng
or1g2r
org1r2i
3ori1ë
o1r2i
orings8ku
ori2ng
orin2gs2
oring2s1k2
or5kaa2
o2r1k
or1ka
orke4s5
or1ke
ork7l4a2g.
or1k2l
ork2lag
or4k6lik
ork1li
ork7ney.
or1k2n
ork1ne
orko2m6s2
or1ko
or3ko2m
orkoms7t
or6maan1
o2r1m
ormaa2
or4m1j
or4n1j
o2r1n
or5o2n1d
or5o2ng
o5ro2t.
oro2t
oro7t4hy.
oro2t1h2
or3p4h2
or1p
orp6s1ki
or2p1s2
orp1sk2
orp4sn
ors7aa2r.
o2rs2
or1sa
orsaa2
or2s5ag
ors7ju2r1k
or1s1j
ors5mo
or2s1m
ors7po2t.
or1s1p
orspo2t
ors7te1r2i
or1s1t
ors7te1ry
or4s3ti
ors7trek
orst2r
orst1re
ort7aa2n.
o2r1t
ortaa2
ortaan1
ort5aar
or3t5a2k1t
or4tar
ort6ha2m
or2t1h2
5orto1d
ort7onde
or4to2n1d
or6t7ro2l.
ort2r
ort7ru2k.
or4tru2k3
ort5s1w
or2t1s2
or1u
o3r4us2
o3ry.
ory4s2
o2s3ag
os2
o1sa
osa7p6ha1t
osa2p
osa2p1h2
os5a3pi
os2as4
osa7t6jie
osa2t3j
osa1t
os5cen
os1c2
o3se
ose7phin
ose1p
ose2p1h2
osep2hi
os2e7p6hus2
o3si
osi6nen
osi1ne
o4sjo
o1s1j
o1s1k2
o4s3ka
os5kee2
os1ke
os5kis2
os1ki
os5koe
os1ko
os5ko2p.
osko2p
os5kor
os5kou
o4s5ko1w
os3k2r
o5sk1r2i
o4s1ku
o2s3l
o2s3m
o2s3n
os5o1li
o1so
o3sol
os5oor1
osoo2
o4sor
o4s5or1d
os3p
os4pe
os5ste
o2s2s2
os3s1t
os3t
o2s4t.
os4ta
os5taf
os5tak
os5tal
os5tar
os4t1d
o2s4t1h2
ost7impe
os3ti
osti2m
osti2m1p
os4t1m
o5s1tra1t4
ost2r
os6tre1v2
ost1re
ost7revo
ost6roo4
os4t1w
os4t2-1
o2s3w
os-7lo2n1d
o2s2-1
o2t
o3ta
ot5a2k1k
otas4
o1ta5s3t
ota7tjie
ota2t4j
ota1t
o3te
otee2k5
ote4s5a
otes2
ote6s1no
ote1sn
ote6sp2r
ote1s1p
o1te4s5t
o4the
o2t1h2
oth7na1ge
ot2hn
oth1na
o3ti
ot3j
o3to
o4t5o2p1m
oto2p
oto6ran
oto1ra
oto6ren
oto1re
otor5o
oto1s4
oto5s1k2
ot3re
ot2r
o3tro
o4t5ryk
ot4s7ei1la
o2t1s2
ot1se
otse2il
ots7ka2r1r2
ot4skar
ot1sk2
ots1ka
ot4s5ko
ot4sl
ots5la
ot4s5po
ots1p
ots7rûe.
ot2s1r
otsrû1
ots7tee.
o1ts1t
ot6stek2
o1ts7toe1t
ot4stu
otte6l5o
o2t1t
o3tu
o4tui
o1tu6se1t
otus2
otu1se
oua6che
ou1a
oua1c2
ouac1h2
oud7agti
ou1d
ouda2g
ouda2g1t
ou6da2k1t
oud7ak3ti
oud6ief
oud6i3ni
oud6i2n1k
oud7oor1g
oudoo2
oudoor1
ou1d7s6ty2d
ou2ds2
oud1s1t
oud2sty
ou4du
oud7ui2t1g
oudui2t
oue6ri1o
ou1e
oue1r2i
ou3g
ou4g1h2
ou4g1l
ou2g4r
ou1i
ou5i2l1l
ou2il
oui3s6a.
oui4s3a
ouis2
ou1k
oul7on2t1l
ou1lo
ou4lo2n1t
ou3m
ou5ny1w
ou5rei
ou1re
ou3s6kak
ou2s3k2
ous2
ous1ka
ous6ken
ous1ke
ou5s1ki
ous6li2p
ou2s3l
ou2s1li
ous6ou1w
ou2s3o
ou3sou
ou3s4p
ous7pa2n.
ou3s4pan
ou3s4t
ous7te2r1t
ou7stiek
ous3ti
ou4stie
ous7ties2
out7aa2r.
ou2t
ou3ta
outaa2
out7ak1sy
outa2k1s2
ou4t5as2
ou1t7eks5t
ou3te
outek2
oute2k1s2
out7emme
ou4te2m1m
oute1m
5outo3ma
ou3to
outo2m
out7omhe
outo2m1h2
ou4t5oo2
out3r
out6ra2p
out6r4ei
out1re
ou4w1b2
ou1w
ouw7re2n1s2
ouw2r
ouw1re
ou4w1v2
ou4-o
ou2-1
ove5re
o1v2
ove2
o3ver1
over6y.
ove1ry
ovie6v.
ovie1v2
ov5ke1t
ov1ke
owe6nal
o1w
owe1na
owen7a2l.
owe6ral
owe1ra
ower7a2l.
ower7kon
o3we2r1k
ower1ko
oy4a.
oy1a
o3y4eu
oy1e
ô1
ô2i
ôi3e
ôre5s1t
ô1re
ôres2
1ö
öjd7onde
ö1j
öj1d
öj1do2n1d
ö1l
1p
2p.
5paaie.
paa2
paai1
4paa2n1v2
paan1
paar7dui
paar4du
paar1d
3pa2d.
pa2d
pa4da
pad6ie.
pa3di
pa4d3r
pad6-eg
pa2d2-1
p1af
pag6ne.
pa2g1n
pag1ne
3pak
pa4ke
pak5es2
pa1ki3
pakke1t5
pa2k1k
pak3ke
pa4ko
pa3k5os2
4p5ak3ti
pa2k1t
3pa1le
pal7es3se
pales2
pale2s2s2
pal5f3r
pal6mol
pal1m
1pa4no2p
pa1no
1pan5s1p
pa2n1s2
pan4t5j
pa2n1t
3pa2p
pa5p1r2i
pap2r
pap7ry2p.
pa4pry
1pa1pry2p
pa2p1s4
pap7sa2f.
pap1sa
pap2s3af
pap7smee
pap2s3m
paps3me
3pa3ra
pa6ra2m1n
para2m
par7am1ne
3pa2r1k
par6kar
par1ka
par4ko
park5r
4pa2r1m
par4sk2
pa2rs2
par6ste
par1s1t
pars7te.
5party
pa2r1t
pa5sja
pas2
pa1s1j
pa4so
p5aspi
1pas3p
3pa2s2s2
5paste
pas3t
pas7ti2l.
pas3ti
past2il
pa4taa2
pa1t
pat7a3r2e.
pata1re
4patel
pa2t4j
pa5tji
3pat2r
pa2t1s4
pat5s1j
pav7lo6v.
pa1v2
pavlo1v2
2p1b2
pbe6koo2
pbe1ko
pbre2ng6
pb1re
2p1d
p3d1w
pd4wa
pd4wi
3pe.
pe4ak
pe1a
pe4ar
3pe1d
pede4r
pe3de
pe5dof
pee2
pee1l5a
pee1l5u
4pee2n1
pee1t3
3pei
4pei1e
p5eien
4p3eis2
pek7ne2k.
pe1k2n
pek1ne
pe4k3r
pel7akt2r
pe1la
pela2k1t
pe4l5oe
pe1lo
pel7oo2n1d
peloo2
peloon1
pels7kra
pe2l1s2
pel1sk2
pelsk2r
pel6tak
pel1t
pel7yste
pe3ly
pelys3t
pelys2
4pe2m1m
pe1m
pe6na2rs2
pe1na
3pen1n
pen7op1sl
pe1no
1peno2p
peno2p1s2
pen6slu
pe2n1s2
pen2sl
pen7s6me1d
pen2s1m
pens3me
1pen6s3o2p
pen1so
4pepi
pe1p
3pe1ra
1pe6raa2p1
peraa2
per7aa2p.
per7admi
pera2d
pera2d1m
per6a1ry
per3ar
per7asi2d
peras2
pera1si
pera5s6t
5per2d.
per1d
per6da2g
per6dry
perd2r
per6dwy
per3d1w
per7en1ke
pe1re
pere2n1k
per5e1s1t
pe3r4es2
3pe1r2i
pe4rok
pe1ro
3pe2rs2
per2s5ag
per1sa
per6se1t
per1se
per6sje
per1s1j
pers7me1d
per2s1m
pers3me
5per1so
per6s7o2n1d
pers8te2l.
per5ste
per1s1t
5pert2j
pe2r1t
1per6to2p
per7t2sja
per2t1s2
pert1s1j
per7uran
pe1ru
peru1ra
3pe2s.
pes2
4pe3si
pe4sl
pe4s5te
pe1s1t
peu6rel
peu1re
2p1f
p3fl
p2f4li
2p1g
pges4
p1ge
1pge5s1p
pg4ly
2p1h2
phe6a2rs2
phe1a
p2hi
phi5s4t
phis2
p3hi2t
pie1k5n
pie6raf
pie1ra
pie6ro2m
pie1ro
pie4ru
1pie7s6a2m1p
pies2
pie1sa
pie3sa2m
pie6s1ka
pie1sk2
pi4k7er2t1s2
pi1ke
pike2r1t
pi2k3s4
6p5inges2
pi2ng
pin1ge
ping6la
ping1l
pin7glas2
pin5k2l
pi2n1k
4pins4t
pi2n1s2
pi3pe4
1pi2p
pipe1t5
pi3s4k2
pis2
pit3j
pi2t
pit4s5k2
pi2t1s2
pi1t4s1t
pits5te
pits7tek2
pit6suu2
pit3su
pit6z1ko
pi2t1z
2p1j
2p1k
pko2m4s5
p1ko
p3ko2m
p2l
pla6kal
pla1ka
pla6kok
pla1ko
5pla2n.
5p4la2n1t
4p3la2p
3p4las2
3p4la1t
pla4t5r
plee4
p1le
plee1t5
p5lei1e
ple4k
5ple2k1s2
4p3le1p
pleu7ra.
pleu4ra
p3lê1
pli4g
p1li
pli4t5e
pli2t
4pli2t1s2
p5loos2
ploo2
p5lo3se
plos2
plu6s1ka
plu2s3k2
plus2
4p3lyn
p1ly
2p1m
2p1n
p4neu2m
p1ne
poe6doe
poe1d
3poei1
poe4s5t
poes2
poe6tol
poe1t
po4fa
pog7re1s1t
po1g
po1g2r
pog1re
pogres2
pog5s1k2
po2gs2
pok5aa2
po1ka
pok7olie
po1ko
poko1li
3pol
3po2m
4po2m1h2
3po1ne
pon7opbr
po1no
po4no2p1b2
1pono2p
pon4s5k2
po2n1s2
pons7te.
pons4t
pon5ste
4po2n1t
5pon2t.
p5on2t1s2
4poo2r.
poo2
poor1
5p4oor2t.
poo2r1t
3poo2t1
poo6tel
poo3te
po4pag
1po2p
po3pa
4po2p1d
pop6le4k
pop3l
pop1le
4po2p1s2
p5or3do
por1d
po3ro
por6tak
po2r1t
6port1so
por2t1s2
por6t5ui
3pos2
po4s1k2
pos6ti2m
pos3t
pos3ti
pos6t1ko
pos2t1k
pos6t1na
pos2t1n
po4t5as4
po2t
po3ta
3po3te
pou6ste
pou3s4t
pous2
2p1p
ppe6las2
ppe1la
ppel7a2s.
ppe6ra2d
p3pe1ra
ppe4ro
ppie6sl
ppies2
ppoo2r6t
ppoo2
ppoor1
ppoort7j
p2r
p4raa2
4praak1
p5raa2m1
5praa1t1
pra6e3si
pra1e
praes2
5pra2k1t
3pren
p1re
3pres2
pre4s5t
4preu
3p4ri1a
p1r2i
p3ri1b2
p4rie4
prie7ë2l.
prie1ë
5pries5
priet5j
prie1t
5p4ri2n1s2
3pri1o
3pro1b2
3pro1d
pr4oe
3prof
3p4ro1g
3pro1j
4p5ro2k.
3p4ro2p
1pro6po2p
pr4o3po
p2ro5p3r
pr4or
3pros2
pro5sa
pro7so3pa
pro1so
1pro2s1o2p
pru4t
3pry
pry4s3t
prys2
2p1s2
p4sa2d
p1sa
p4s5a2k1s4
p4s3a2k1t
5psal2m.
p4sal1m
p4s2as2
1ps5as3p
p4sa1t
p4si2d
p1si
5p4si1gi
5p4si3go
p4s5i2ng
p4s5i2n1t
p1s3j
p5skaa2
p1sk2
ps1ka
p4s5ke1t
ps1ke
ps5kof
ps1ko
p4s3kon
p4s5lo1j
p1sl
p2s3m
p3s4my
p4son
p1so
p5s4o2n.
p4s3oo2
p4sor
p6s7port2r
ps1p
pspo2r1t
p6ste1ra
ps1t
ps4ti
p6s1toe1t
p4ston
p5swar
p2s1w
ps5wer
2p1t
pto1s4
3pu1b2
pue4b2
pu1e
4pui2t
3pun
pun4t5j
pu2n1t
pus7ti3po
pus3t
pus2
pus3ti
1pus4ti2p
put7adde
pu2t
pu3ta
puta2d
puta2d1d
put7emme
pu3te
pu4te2m1m
pute1m
put5ji
put2j
put7rioo2
put2r
put1r2i
putri1o
2p1v2
2p1w
3pyn
3py2p
pyp7aar1d
py3pa
pypaa2
pyp3l
pyp7la2s.
py3p4las2
pyp3o
py4p2r
py2s3k2
pys2
4p5yste
pys3t
py5t4ha
py2t
py2t1h2
pyt6hon
2p2-1
1q
qu2
qua7dri1v2
qu1a
qua2d
qua3d2r
quad1r2i
que6st2r
qu1e
que1s1t
ques2
qui7na2s.
qui4na
quinas2
2r.
raa6min
raa2
raa2m1
raa3mi
4raan1
r6aan2s.
raa2n1s2
4r5aar1d
4ra2b1s2
ra1b2
rac5te
ra1c2
ra2c1t
5radi1o
ra2d
ra3di
4r3a2d1v2
4ra2f1d
ra4fek
ra1fe
4raf1h2
ra4foe
ra1fo
4ra2f1t
ra4fu
raf7ur2n.
rafu2r1n
4ra2f1v2
4ra2f1w
2rag
rag6aal1
rag3aa2
ra1ga
5r4age1b2
ra1ge
ra5gie
ra3gi
ra6gi2n1l
rag7in1li
r4a1g2r
rag7raa2d1
ragraa2
rag7ryer2
ragry1e
rag6sa1b2
rag3s1a
ra2gs2
rag6sak
rag6sin
rag1si
rag7s6oe1p
rag2s3o
ragu5e
ra1gu
rag6wan
ra2g1w
rai7g6ne.
rai1
rai2g1n
raig1ne
rak6les2
ra2k3l
rak1le
rak7op4er
ra1ko
rako2p
rako3pe
rak7wate
ra2k2w
rakwa1t
4ral1b2
ral7ee2r.
ra1le
ralee2
ralee2r
ral7oo2r.
ra1lo
raloo2
raloor1
4ral1t
ram7ar1gi
ra2m
ra3ma
ramar1g
4r5ameu
ra3me
ram6pla
ra2m1p
ramp2l
r4anda
ra2n1d
ran6daa2
ran7da1fe
ran2d1af
ran6d7a2k1k
ran6de1m
ran6de1v2
ran6doe
ran4d2r
ran4g5o
ra2ng
ran1k5l
ra2n1k
ran4k5r
ran6saa2
ra2n1s2
ran1sa
ran6seu
ran1se
ran6sjo
ran1s1j
ran6s1ko
ran1sk2
ran6sor
ran1so
ran4s5p
ran4s5t
ran6ta2d
ra2n1t
ran6te1t
ran4t5j
ran4t2r
4r3a1ny
rapa7da.
ra2p
ra3pa
rapa4da
rapa2d
ra6pa2s.
rapas2
ra5pes2
ra3pe
rap7ewen
rape1w
rap6lo1ï
rap3l
ra4pon
ra3po
rap7on1ge
rapo2ng
rap7pa3ra
r5appar
ra2p1p
rap7re2m.
rap2r
rap1re
rapre1m
rap7ri2g1h2
rap1r2i
r4a1r2i
4ra2r1m
4ra2r1r2
4ra2r1t
r5asi1a
ras2
ra1si
ras6tan
ras3t
ra5s4to
ra5s4t2r
ra4su
ras5ui
ra5t4ho
ra1t
ra2t1h2
4r5ato2m
rat1s5o
ra2t1s2
rbo6lol
r1b2
rbo1lo
rd5agti
r1d
rda2g
rda2g1t
r4d5a3me
rda2m
rd5e2il
r2dei
r6delek
rde3le
rd5e3sel
rde3se
rdes2
rde5s1m
r4d5eu1ro
r3do
r4d5o1li
r4d5o2n1t
r4d5oon1
rdoo2
rdô6n1ne
rdô1
rdôn1n
rd7raais2
rd2r
r3dra
rdraa2
rdraai1
r6dro3ma
rdro2m
rds6li2p
r2ds2
rd2s3li
rd1sl
rd3s1o
rd3s1p
rdt6ree
r2d1t
rdt2r
rdt1re
rdu6s4ol
rdu2s3o
rdus2
rd4wa
r2d1w
r5dwar
r4dwu
r4d5ys3t
rdys2
1re
3r2e.
3reak
re1a
reed5a
ree1d
ree4k
ree3k5e
ree7kier
ree1ki
reeki2e
ree7l6oon1
ree4l3o
reeloo2
4r5ee2n1d
ree2n1
4r5ee2n1h2
4ree2n1v2
ree6pes2
ree1p
ree6p1le
reep2l
ree6pro
reep2r
4ree2rs2
ree2r
ree7s6a1la
ree2s3
ree1sa
ree7stra
rees4t2r
ree1s1t
3re1ë
4reë.
4ref1f
3re2g.
reg7ru2k.
re1g2r
regru2k3
reg6sen
re2gs2
reg1se
reg6s3k2w
reg3s1k2
regs7o2m.
reg2s3o
regso2m
1reg7s6p1re
regs3p
regsp2r
4rei1e
4re2il
rei6nar
rei1na
4r3ei2n1d
rei6noo2
rei1no
rei6ser
reis2
rei1se
rei6sou
r4ei1so
reit7ze.
rei2t2
rei2t1z
re4k2n
rek7naar
rek1na
reknaa2
rek5ne
re1k4r
rek7s6poe
reks5po
re2k1s2
rek2s1p
4reks1t
re4kwa
re1k2w
rel4d
rel7dae.
relda1e
rel7diag
reldi1a
rel7do2p1p
reldo2p
reld7ran
reld2r
rel3dra
rel7duik
rel7oe4s1t
re1lo
reloes2
re4n6a2g1t
re1na
r4e2n1d
5ren2d.
r5en4ig
re3ni
ren7s6ha1w
re2n1s2
ren2s1h2
rens3ha
ren4so
ren4s1p
rens7te.
rens1t
ren6sto
ren6st2r
ren6tak
re2n1t
ren6tcl
rent1c2
ren4t5j
ren4t5r
4rer1g
4r3e2r4t
rert5j
4re3sel
re3se
res2
re4s5ka
re1sk2
res7la2p.
re3sla
re1sl
resla2p
res5lo
re3s4m
res7o3r2e.
re1so
reso1re
re4spi
re1s1p
re4ste
re1s1t
re5stel
re4s3ti
res7toe1t
res7to3ma
re3s4to2m
res7u3r2e.
re1su
resu1re
re1t5a2r1t
re1t
reu6kin
reu1ki
reu4k4l
reu4k5o
re5u2s1g
reus2
r4ewa
re1w
rey5n4o
rê4r1h2
rê1
r4faa2
r1fa
rf5laa2
rf5la1t
r3f2le
r5f4lie
r2f1li
r5flui
rf5o2p1v2
r1fo
r2fo2p
r4f1re
r1f2r
rf5reg
rf4sl
r2f1s2
r1g
r4gak
r1ga
rg5a2k1t
rgek6li
r1ge
rge1k2l
rge7k3lik
r4g5e2ng
r3gen
rgeper6
rge1p
rge6r4i2d
r3ger
rge1r2i
rg4hu
r2g1h2
rg5hu2t
rg4len
rg1le
rg4le1t
rg5loo2
r4g3lu
rg4ly
rgo6w1r2i
r1go
rgo1w
rgow2r
rgrie4
r1g2r
rg1r2i
rg5ros2
r2g3s2
rg4s.
rgs4p
rguit6j
r1gu
r4gui2t
rgui7tji
1r2i
3ri2b.
ri1b2
rib7fi1le
ri2b1f
rib1fi
ribf2il
rie6di1o
rie1d
rie6do2p
rieke1t5
rie3ke
rie6klo
rie1k2l
rie5me
rie1m
1r2ie6p1r2i
rie1p
riep2r
rie5s6e.
rie3se
ries2
rie6sk2r
rie1sk2
rie6taa2
rie1t
ri4fa
rif6ree
ri2f3r
rif1re
rig6s1ka
ri2gs2
rig2s1k2
rig7s6mee
rig2s3m
rigs3me
rig7styf
rig1s1t
rig2sty
ri4k2l
rik6si2d
ri2k1s2
rik3si
rik5s1j
rik7s6pa2d
rik2s1p
ri4k2w
ri2m4s2
ri2m
4r3i2n1f2
rin6gaa2
ri2ng
rin1ga
rin7gaan1
ring7aar
6r7inge3s1t
rin1ge
ringes2
rin7g6le1b2
ring1l
ring1le
rin4g5r
rin6gui
rin1gu
4r3i2n1h2
rin6kar
ri2n1k
rin1ka
4r5in1ko
rin2k5w
rin6kwa
4r3i2n1l
4r3in1r
4ri2n1s2
r5ins4t
rin7t6he.
ri2n1t
rin2t1h2
r3i2n1v2
4rinve2
rio7rye.
ri1o
riory1e
rip4s5t
ri2p
ri2p1s2
ri5s4ko
ri2s3k2
ris2
ri1s4o
ri5son
ri2s5o2p
ris6per
ris3p
rit5ji
ri2t
rit2j
rit7oo2n1d
ri3to
ri3toon1
ritoo2
ri4t6re1a
rit2r
rit3re
ri5tro
1r2it6z1r2i
ri2t1z
5rivie
ri1v2
rix7to2n1t
rix1t
2r1k
r4kaan1
r1ka
rkaa2
r4k5ee2n1
r1ke
rkee2
r4k5eik
rke6la2p
rke1la
rke4s3
rke7s6e2l.
rke3se
rke3sel
r6kin2k.
r1ki
rki2n1k
r4k5i2n1l
rk5lei1e
r1k2l
rk4l4ei
rk1le
r4k3li2d
rk1li
r5kli4p3
r4k5los2
r4k5nei
r1k2n
rk1ne
r4k5o2m1g
r1ko
r3ko2m
rkom6s3ti
rko2m1s2
rkoms1t
r4k5o2p1d
rko2p
r4k5o2p1s2
r4k5ra2n1d
rk2r
rk5re1d
rk1re
rk5ri1b2
rk1r2i
rk5rok
rks6maa2
r2k1s2
rk2s1m
rks3ma
rk5spo
rk2s1p
rk3s6ui2d
rks3ui
rk1su
rk5t4wi
r2k1t
rk2t1w
r4k5ui2t
r1ku
r4k5u1re
rk5wag
r1k2w
r4k5wa1t
rk5wee
r4k5we1t
rk5w2il
rk5win
r4kwy
r1l
r3la
rli4g
r1li
rlo6gja
rlo1g
rlog1j
rloo7p1le
rl4oo2p1
rloo2
rloop3l
rlo6wpa
rlo1w
rlow1p
2r1m
r6maa2n1h2
rmaa2
rmaan1
rma5gô1
rma6gun
rma1gu
rma7k6lo2t
rma2k3l
rma7plaa2
rmap4l
rma2p
rma7raan1
rma3ra
rmaraa2
rme6raa2
rme1ra
rmer7aar
rme5s4a
rmes2
rm2i4l
rmos4
rmo7stro
rmos3t
rmost2r
r4m5ui2t
r1my6ni2m
r3myn
rmy1ni
2r1n
r5nagte
r1na
r4na2g1t
rna6s3p2l
rna3s3p
rnas2
r4n5e3le
r1ne
rne4s2
rne4t5a
rne1t
rne6to2m
rn5oor1
r1no
rnoo2
rn6stig
r2n1s2
rns3ti
rns1t
rns6tin
r3nu
rodu4k5
ro1d
roe6fas2
roe1fa
roef7a2s.
roe4f5l
roe6f1r2i
roe1f2r
roe4ga
roe7glas2
roe4n5a
roe4pa
roe1p
roe4p5l
roe4p5o
roe4s5k2
roes2
roe6sla
roe1sl
roes7la4g
roe4s5t
roe3s5w
roe6taa2
roe1t
4roe1w
rog7ak3ke
ro1g
ro1ga
roga2k1k
4ro2g1g2
ro3g6lis2
rog1l
rog1li
ro5g1na
ro2g1n
ro4k3n
ro1k4r
rok7s6li2p
ro2k1s2
rok2s3li
rok1sl
rok4s5p
ro5kyn
ro4l5aa2
ro1la
rol7gor1d
rol4g5or
rol1g
rol1go
r6olien
ro1li
rol7mo2p1s2
rol1m
rol2mo2p
rom6p7o2p.
ro2m
ro2m1p
rom1po2p
4ro2m1s2
ron7aa2r.
ro1na
ronaa2
ron6da2g
ro2n1d
ron6dak
ron6d7e2r6t7
ron6d5e1t
ron4d5o
rond6o.
ron4d5r
ron2ds4
rond5s3w
ron4du
ron6gaa2
ro2ng
ron1ga
ron6kaa2
ro2n1k
ron1ka
ronker6
ron1ke
ron6ke2r1t
ron4k5l
ron6ko1w
ron1ko
ron4k2r
ronk7wa.
ron2k2w
ron6s1ke
ro2n1s2
ron1sk2
ron6ste
rons4t
rons7te.
ron7stel
ron6s5ti
r5on2t1p
ro2n1t
ron4t5r
ron6tui
4r3on2t1w
roo7dewa
roo2
roo1d
roode1w
roo7dis3t
roodis2
3roof1
4roo1g3
roo7g4ron
roo1g2r
roo7me2n1s2
roo2m1
roo4me
roo7n6a2g.
roon1
roo4n3ag
roo1na
4roo2n1d
roop6la
roo2p1
roop3l
4r3oor1
roo7t6aai1
roo2t1
roo4ta
rootaa2
r4o3pa
ro2p
rop7aa2n1s2
ropaa2
ropaan1
rop7anys2
ropa1ny
ro5pee2
ro3pe
ro4p6ee2n1
4r5open
r4o3pi
r4o3po
r1or
r4o1ra
ro3ro
ros6a1f2r
ros2
ro1sa
ro2s3af
4r5ose1a
ro3se
ros7kie.
ro1s1k2
ros1ki
roski2e
ro3s4p
ro5s4ta
ros3t
ros6t2il
ros3ti
rot6hs1c2
ro2t
ro2t1h2
roths2
ro5ton
ro3to
ro3t2r
rot4sa
ro2t1s2
4rou1d
rou7floe
rou5s3k2
rous2
rou3t
rovi7c2h.
ro1v2
rovi1c2
rovic1h2
rov7ni2k.
rov1ni
ro4w2-1
ro1w
rox9y.
r2ö
rpe4s3
r1p
r4pi2d
rp5i3de
r4p5lik
rp2l
rp1li
r4p5o2p1d
r1po2p
r4p5reg
rp2r
rp1re
rps7idi1o
r2p1s2
rp4si2d
rp1si
rpsi3di
rp4sl
rps5no
rp1sn
rp6sp1r2i
rps1p
rpsp2r
rp4stu
rps1t
2r1r2
rre7g4lo1b2
r1re
rre7n6a2g1t
rre1na
rre4s1t
rres2
rre7st2au
rres5t2r
rri6gin
r1r2i
rri1gi
rron7kaa2
rro2n1k
rron1ka
2rs2
r4s3a2k1t
r1sa
r4s5al1m
r4s3ar
r5scha
rs1c2
rsc1h2
r4s5ei1la
r1se
rse2il
rs2eu2n2s6
r2s3f2
r5s6fee2r
rs1fe
r3s4ie
r1si
rsi7f2lag
rsif1l
r6sinda
rsi2n1d
r4s5in1ko
rsi2n1k
r6sins1p
rs2i2n1s2
r6sins4t
r5sja2m
r1s1j
r2sja
r6ska1ki
r1sk2
r3skak
rs1ka
r5ska2p
r6ska3pa
r6ska3pi
r6s3keus2
rs1ke
r4s5kin
rs1ki
rs6ki2n1k
r6skoe1t
rs1ko
rs6ko2m1m
rs3ko2m
rs6ko1ni
rs3kon
r4skor
2r5s2ko2rs2
r5skou
rs5kri1b2
rsk2r
rsk1r2i
r6skroo2
r4skur
rs1ku
r4s3lê1
r1sl
rs5lis2
r2s1li
r4s5los2
r4s5lyf
rs1ly
rs6maa2d1
r2s1m
rs3ma
rsmaa2
rs6maai1
r5s6maak1
r6smaa1t1
rs6ma2g1t
rs6ma2k.
r6sma1ke
rs6ma3ra
rs6me3de
rs3me
rsme1d
rs4mee
r5smee2r
rs4mel
rsn4ee5m
r1sn
rs1ne
rsnee2
r5snoo2
rs1no
r4sno2t
r4s3o1g
r1so
rsonde6
rso2n1d
rsonder7
r4s5o2n3g
r4s5oo1g3
rsoo2
2r6s5oo2rs2
rsoor1
r2s3o2p
r3s4o2p.
r4s5or1d
r1s1p
r6spaar
rspaa2
r4s5par
r6spien
r5sp1r2i
rsp2r
r1s1t
r6s5ta2l.
r4s2t1b2
r6s3te2g1n
r4steg
rs6ter1p
2r5s6te2rs2
rs6tig1l
rs3ti
rs4tik
rs4tis2
r4s5ti2t
r4s3t2j
r6s5toe1t
r6streg
rst2r
rst1re
r3sty
r2s3un
r1su
rs2u4s2
rs5wa.
r2s1w
rs5wa2p
rs3we
rs6werf
rs5wyk
2r1t
r4t5a2f1d
rt7angel
rta2ng
rtan1ge
2r1t5a2r1t
r6tee2n1d
rtee4n1
rte6loe
rte1lo
rtie4s2
rti2ng4
rti7s6aan1
rtis2
rti1sa
rtisaa2
r4t3o2m
r4t5o2n1t
rt5op3r
rto2p
rt5or1g
r6t5reda
rt2r
rt1re
rtre1d
r4t5reis2
rtr4ei
rt5ren
r4t5rie
rt1r2i
r6tro2l.
r4t3ry
rt6s5aar
r2t1s2
rt1sa
rtsaa2
rts5o2n1d
rt1so
rt4s6pyn
rts1p
r4t5ui2t1s2
r1tui2t
rt4wis2
r2t1w
rt4wyf
3ru1b2
ru4ga
rug6-1sk2
ru2g2-1
rug-s2
rui6lek
ru2il
rui1le
rui6moe
rui2m
rui3mo
5ruimte
rui2m1t
rui6ni1v2
rui3ni
4rui2t5s2
rui2t
ru2k3
ru5k4aa2
ru1ka
ruk6li4p3
ru1k2l
ruk1li
ruk6o2p1p
ru1ko
ruko2p
ruk6-en
ru2k2-1
rul5aa2
ru1la
rul7a3pe.
rula2p
rula3pe
rul7yste
ru1ly
rulys3t
rulys2
r2u2m
rum7g6rok
ru2m1g
rum1g2r
4ru1ni
rup7ly2s.
ru4p1ly
ru1p
rup2l
ruplys2
ru5spi
rus3p
rus2
rus6tak
rus3t
rus6t1ka
rus2t1k
rus6tma
rus2t1m
rus6tvo
rus2t1v2
rus7uu2r.
ru1su
rusuu2
rusuur1
rut7oo2n1d
ru2t
ru3to
ru3toon1
rutoo2
2ruu2
ruus6te
ruus3
ruus3t
ruu7ste.
rwe4g5a
r1w
r3weg
rwe6gei
rwe3ge
rwe6sk2r
rwes2
rwe1sk2
rwi7sje.
rwi2s1j
rwis2
rwoe4s5
rwy6sak
r3wys2
rwy1sa
rwy6see
rwy1se
ry4fa
ryf7in2k.
ry1fi
ryfi2n1k
ry4fo
ryf7ode.
ryfo1d
ryf6sc1h2
ry2f1s2
ryfs1c2
ryf6sin
ryf1si
ryf6s1ka
ryf1sk2
ryg7s6tek2
ry1g
ry2gs2
ryg1s1t
ryg6st2r
ry6kin1r
ry1ki
ry2k3l
ry5k4lu
3ry2m.
ry1m
ryn4s5l
ry2n1s2
ryp7ar2m.
ry2p
ry3pa
ry4pa2r1m
ry5p1le
ryp2l
ryp7lu2s.
ryplus2
ryp7na2g1t
ry2p1n
ryp1na
ry4s3a
rys2
ry3s6alf
ry6si2n1l
ry1si
rys5pi
rys1p
rys5po
ry3s3t
rys4ti
ry4su
ry4ta
ry2t
ry5tra
ryt2r
s2
2s.
s'9ie.
s’9ie.
1sa
3sa.
s4aa2d1
saa2
saa6dui
saa3du
3saak1
3saal1
4s3aan1
4s3aa2p1
4s5aar1d
4s3aas3
4s3a2d1m
sa2d
s5ad1re
sa3d2r
4s3a2d1v2
2s3af
2sag
3s4a2g.
sa4gal
sa1ga
s3a1ge
s4a3gi
s4a2g4n
sa5g1ne
3s4a1go
sag6o3pa
sa2g1o2p
5sagte.
sa2g1t
5s6agte1w
3sa2k.
s5aka2d
sa1ka
3sa1ke
3sa2k1k
sa6k5rok
sa1k2r
s2a6krus2
s2a2k1s4
s3a2k1t
4s5ak3ti
sa6la2m1m
sa1la
sala2m
sal7ammo
4sal1b2
s5albu
3salf
sal6fol
sal1fo
4s3al1g
4sal1m
sal6mei
sal6tro
sal1t
salt2r
3sa2m
4samba
sa2m1b2
same4n
sa3me
4s3a2m1p
sam6swy
s2a2m1s2
sam3s1w
4sa1na
s5anal
san6d5a2g
sa2n1d
san6dak
sand7a2k1k
san6dru
sand2r
3sa2ng
san4g5a
san6g1re
san1g2r
sang7ste
s2an2gs2
sang1s1t
4s5an1ke
sa2n1k
sap6ha1t
sa2p
sa2p1h2
s5appa
sa2p1p
s5appel
sa3p2r
4s3ar1b2
4s3are1a
sa1re
sar7olie
sa1ro
saro1li
4s3a2r1t
4sa1se
s2as2
s5ase1m
4s5aspe
sas3p
sa5sp2r
sat6jie
sa2t3j
sa1t
4s3a2t1l
4s5atta
sa2t1t
sav7lo2n.
sa1v2
2s1b2
s3ba
s3be
s3bi
s3bl
s3bo
s3br
s3bu
s3by1
s3ca
s1c2
s3ci
2s1d
s3da
s3de
s3di
s3do
s3d2r
s3du
s3d1w
sd4wa
s3dy
1se
3se.
s5ee2d.
see1d
see3f
see3k
s5eenhe
see2n1
see2n1h2
see6plo
see1p
seep2l
see5ra2m
see2r
see1ra
seer7a2s.
seeras2
see5rei
see1re
see7roe1t
see1ro
s2ee3s4
see7soo1g3
see3s4oo2
see1so
see5s1w
seë7kran
se1ë
seëk2r
s5egpa
se2g1p
4se2g1t
4s3ei1e
4seik
s5ei2k.
4sei1la
se2il
sei6nan
sei1na
4s3ei2n1d
4sei2s.
seis2
sek6huk
se2k1h2
4sek1sa
s2e2k1s2
s5ek3sa2m
sek4s5k2
sek4s5p
sek4s1t
3sel
sel5aan1
se1la
selaa2
sel7anal
sela1na
se4l5el
se3le
sel4f5a
sel7f6abr
selfa1b2
sel6fer
sel1fe
sel1f5i
sel6fi3d
6s2eli2k1s2
se3li
sel5of
se1lo
sel7oo2r.
se4loor1
seloo2
sel5o2p
sel3s7kak
s2e2l1s2
sel1sk2
sels1ka
sel6slo
sel1sl
sel7spen
sels1p
sel6s7taa2
sel3s1t
se4m5ag
se1m
4s5emal
sem7eks1t
s2eme2k1s2
sen6dan
se2n1d
sen7g6hor
se2ng
sen2g1h2
sen7sor1d
s2e2n1s2
sen1so
sen6st2r
sens1t
s2ep6hus2
se1p
se2p1h2
1ser7af1se
se1ra
s2era2f1s2
4serf
s5er2f.
ser6s5in
s2e2rs2
ser1si
ser6sk2r
ser1sk2
ser6sta
ser1s1t
sers7taa2
ser7s4ta2d
sers7tal
ser6tuu2
se2r1t
s5ervar
ser1v2
se4s6aan1
s2es2
se1sa
sesaa2
4se4s5ka
se1sk2
ses7le2t1t
se1sl
ses1le
sesle1t
se4s5po
se1s1p
se4s1t
se4s5ur
se1su
ses7uu2r.
sesuu2
sesuur1
se4s1w
ses7we3ke
s4e2t.
se1t
sewe4s6t
se1w
s2ewes2
sewe7ste
sey7s4tof
seys3t
seys2
1sê1
2s1f2
s3fa
s3fi
s3fl
s3fo
s3f2r
s3fu
2s1g
s3ga
sga4s5e
sgas2
s3ge
sge4s7per
sge3s1p
sges2
s3gi
s3gl
sg4ly
s3go
s3g2r
s3gu
2s1h2
s3ha
s4ha.
sha7ro2n.
sha1ro
s3he
s3hi
s3ho
sho7s6hol
shos2
s1h2o2s1h2
shos3ho
s3hu
1si
4s5ide1a
si2d
si3de
s5idee.
4s5ide1o
3sie
s2ie7k6wos2
sie1k2w
sie2k3wo
sie2s6li
s2ies2
sie1sl
sies7mee
sie2s1m
sies4me
si5f2le
sif1l
sif6reu
si2f3r
sif1re
4si3go
si4gro
si1g2r
4simp2l
si2m
si2m1p
3si2n.
si5n3ag
si1na
s5indek
si2n1d
5sind2r
4s3indu
sin7enti
si1ne
sine2n1t
sin7e4ste
sine1s1t
sines2
4s3i2n1f2
s4i2ng
sin6gaa2
sin1ga
sing7aan1
6s2inges2
sin1ge
sin7g4le.
sing1l
sing1le
sin6g1re
sin1g2r
s5ingry
4s3i2n1h2
sin6kc1h2
si2n1k
sink1c2
sin4k2l
4sin1ko
s5inlig
si2n1l
sin1li
4s3in1m
4s3in1r
sin6see
s2i2n1s2
sin1se
sin6sin
1sin3si
6sin3slu
sin3sl
sin5sn
4si2n1v2
sip6ho.
si2p
si2p1h2
s4is3t
sis2
sit6a1re
si2t
si3ta
4s5ite1m
si3te
si3t2r
si4t3re
sit7rie1m
sit1r2i
sit6sik
s2i2t1s2
sit1si
sit5sl
3si3tu
siu6mur
si1u
siu2m1
siu3mu
1s1j
2sja
s2je6a2n1s2
sje1a
5sjo1ko
4sjuf
1sk2
2s2k.
5skaaf
s1ka
skaa2
s2kaa2r6s2
6s5kaa2r1t
s5ka1fe
3skak
6skake1b2
ska1ke
s5ka2k1t
4s5kalf
4s3ka2m
4s3ka2n1t
3ska2p
ska6pin
ska3pi
ska6pon
ska3po4
skap5r
4s3ka2r.
4ska1ra
4s3kas2
s5kata
ska1t
ska6tel
ska6ti2t
5skawe
ska1w
3ske1m
s1ke
4s3ken1n
3s4ke1p
ske6p4la
skep2l
ske4p5r
4s3ke2r1k
4s5ke2r1n
ske7smee
ske2s1m
skes4me
skes2
5s2ke2t1s2
ske1t
4ske2t1t
s5kie1m
s1ki
ski2e
ski7klu1b2
ski4k2l
s4k2il
4s5kil1j
ski6lol
ski1lo
s4ki2p
5ski2p.
4s3ki2s.
skis2
2s3k2l
2s3k2n
4s3ko1d
s1ko
4s5koe1ë
5s4koen
4s3koer
sko6kaa2
sko1ka
s1ko4ko
4s3ko2l1l
4s3ko1lo
4s3kol1w
4sko2m1b2
s3ko2m
4sko3mi
4sko2m1p
s3kon
s4ko1ne
4sko2n1f2
4sko2ng
4s2ko2n1s2
4sko2n1t
6s5koor1d
skoo2
skoor1
s4koo2t1
4s3ko1ö
4s3kos2
sko6see
sko3se
5sko2t1t
sko2t
4sko1w
6sk4raan1
sk2r
skraa2
4s3k2ra4g
4skran
sk4re
6skree1t
s5kres2
5s4kri4f3
sk1r2i
4s3krin
5s4kri4p
4s3kris2
4s3krui
3s4kry
4s5kry2t
sku6dak
s1ku
sku1d
4s3kuik
s5kui2p
5skul1d
4s3kul1t
4s3kun
5skurf
4s3kus2
sku6tar
sku2t
sku3ta
2s3k2w
sky7drin
sky2d
skyd2r
skyd1r2i
s4kyf
s4ky1w
1sl
s4laa2
4s3la2d
s4la4g
4sla2m
4s5la2n1d
s4la2ng
s5la2n1t
4s3las2
4s3la1t
s4la1w
4s3le1d
s1le
5s4lee1p3
slee2
4s5lee2r
4s3le1ë
s4leë.
4s3lei
4s3lek
4slel
4sle2ng
sle6tji
sle1t
slet2j
slet7jie
s4leu
4s5leu1e
s5leus2
5sleu2t
4s3le1w
4s3ley
s3lê1
2s1li
sli1b3
5slier
s3li4g
5slin1ge
sli2ng
slo6bee
slo1b2
s4lof
4slo1j
4slok
4s5loon1
sloo2
4s5loos2
4slo2p
slo4t5a
slo2t
s5loter
slo3te
s4lo1w
sl4ö1j
sl1ö
4s3lu2g1
4s3lui.
4s5lui1a
s5lui1h2
5s4lui2t
4s3lus2
4s3ly1e
s1ly
4slyf
sly6mui
sly1m
4s3lyn
sly6p1af
sly2p
sly3pa
4s3lys2
2s1m
s3ma
s4ma2d
5s4ma2r1t
s3me
s5mel1k
5s4mel1t
s3mi
5s4mi2d.
smi2d
s2mi4s2
smi2t4h5
smi2t
3smok
s5mo2l.
s3mon
s3mu
3smy
smy6nin
s3myn
smy1ni
s4my2t
1sn
s5naai1
s1na
snaa2
4s3n4aa2m1
s5naa1t1
4s3na4g
sna1g5e
snag6s.
s2na2g3s2
4s3na2m
sna6pro
sna3p2r
sna2p
s4nar
s3nas2
4sna1t
4s3na1v2
snee7tji
s1ne
snee2
snee1t
sneet2j
4s3nek
3s4nel
4s3nes2
4s3ne1t
4sneu
sni6kwa
s1ni
sni1k2w
sni6t3re
sni3t2r
sni2t
s4no1b2
s1no
5snoe1t
4s3no2m
4snoo2
4s3no2r1m
s4nui
s1nu
sny3
s4ny1e
5snyer2
s2ny6-ys2
sny2-1
1so
4soef
3s4oek
soe6kal
soe1ka
soe6ke1v2
soe3ke
soe6kol
soe1ko
soe4k5u
s4oen
s2oe6nys2
soe3ny
4s2oes2
5soe2t.
soe1t
soe1ts6t
s2oe2t1s2
soet7s5te
sof6a1gu
so1fa
so2f1ag
4sof1f
so3f3r
sof6ree
sof1re
sog4l
so1g
s2oi6e2t1s2
soi1
soie1t
sok7opho
so1ko
soko2p
soko2p1h2
3sol
s5olie.
so1li
sol6lme
so2l1l
soll1m
s2olo5s2
so1lo
s4o2m.
so2m
s4o3me
4s3o2m1g
4s3o2m1s2
4s3o2m1v2
3s4o2n.
s6onde.
so2n1d
s7onde2r1n
3s4o1ne
so6nee2k
sonee2
son7ee2k1h2
son5eg
4so2n3g
s2on7kwas2
so2n1k
son2k2w
so4n5o2p
so1no
1son4so
s2o2n1s2
4s3o2n1t
son7ui2t.
so1nu
so2nui
sonui2t
s4o2n1v2
4so2n1w
4soo1g3
soo2
3s4ool1
4s5oo2n1d
soon1
s2oo2n4s2
4s5oo2r.
soor1
4s5oor1b2
4s5oor1d
4s5oo1re
s5oor1g
4s5oor1l
s5oo2rs2
5s4oor2t.
soo2r1t
s5oort2r
2s1o2p
3so2p.
sop7eks1t
so3pe
s2ope2k1s2
so5p2hi
so2p1h2
sop6hok
s4o3pi
5sopie
s3op3l
3so3po
so3p3r
5s4op4ra
s3o2p1s2
s3o2p1t
s3o2p1v2
4sor1d
5s4or2g.
sor1g
4s5or1ga
sor6gee
sor1ge
sor6gra
sor1g2r
sorg7raa2
s5or1ke
so2r1k
so5r1or
4sorto
so2r1t
so2s4h2
s2os2
so5s3ha
so3t1h2
so2t
sot4ho
so3t2r
3sou
4sou1d
s5oude
sou6spa
sou3s4p
s2ous2
sou4s5t
sou6taa2
sou2t
sou3ta
sou6tak
4sou3to
s5outo2m
so9y4a.
soy1a
s5paal1
s1p
spaa2
5spaan1
4spa2d
4s3pak
3s4pan
spa1n5o
spa6noo2
4s3pa2p
4s3pa2r1k
4s5pa2s.
s2pas2
4spas3t
spa6tar
spa1t
4s3pat2r
4s3pe.
3s4pee2
spe4k5l
spe6k1ne
spe1k2n
spe6lak
spe1la
5speler
spe3le
s4pe2l1l
6spelo2t
spe1lo
4spen
s5pe2n.
4sper1d
4spe2r1k
5sper2m.
spe2r1m
4s3pe2rs2
4s3pe2s.
s2pes2
5s4pe3si
4spe1t
s4peu
4s5piek
s4pi1o
2s3p2l
5s4plee4
sp1le
5s4plin
sp1li
5s4pli2t1s2
spli2t
3s4p1ly
4s3poei1
4s3pol
4s3po2m
spon6s4t
s2po2n1s2
5s4poo2r.
spoo2
spoor1
4s3poo2t1
5spo1re
6sport2r
spo2r1t
4s3pos2
4s3po3te
spo2t
s5po3ti
5s4praak1
sp2r
sp4raa2
6s5pra2k1t
4s5pra1t
spree4k5
sp1re
s5pre1m
4s3pres2
5s4preu
4s3pro1b2
5sproei1
spr4oe
4s3prof
4s3p4ro1g
5s4pron
4s3pros2
5sprui2t
4s3pry
s3p1s2
sp4si
4s3pu1b2
3s4pul
4s3pun
4s3pyn
4s3py2p
2s1r
s3ra
sra4e
s3re
s3r2i
s3ro
s3ru
s3ry
2s2s2
s3sa
s2s4af
s2s4ag
ssa6rol
ssa1ro
ssay7i2s.
ssayis2
s3se
s2s2e6nas2
sse1na
sse4n5i
s2s2e2r4s2
sse5s1t
sses2
s3si
ssie6l7ei
s3sie
ssie3le
s3sk2
ss4ko
s3sl
s3s1m
ss4ma
ss4me
s3sn
s3so
s3s1p
s2s4p2l
s3s1t
ss4ti
s3su
s3s1w
s3sy
2s2t.
s1t
4s3ta.
s4taa2
st5aar1d
5s1taa1t1
3s4ta2d
4s3ta1fe
s2ta6las2
sta1la
stal7a2s.
4s3ta1le
sta6lee2
sta6le1m
4sta1li
5s4tal1t
5s4ta2m.
sta2m
sta4m5o
5s4ta2n.
stand8s7ta
sta2n1d
s2tan2ds2
s1tand1s1t
4s3ta1r2i
3s4ta1t
s5tat2r
4ste1a
s4te2d1d
ste1d
5s4te3de
s4tee4k
5s4tee4n1
4s3tee2r
4steg
ste6g1re
ste1g2r
st4ei
ste6k1li
stek2
ste1k2l
ste6lek
ste3le
s2tel7e2k1s2
6stele1v2
5s6tel1se
s2te2l1s2
stel6t2j
s1tel1t
stelt7ji
5s4te2m.
ste1m
5s4te2m1m
ste6mo2m
ste6nou
ste1no
4s1te2n1t
s5te3ny
4s3te1o
6s5tera2p
ste1ra
ste6r5ei
ste1re
5s1ter2k1t
ste2r1k
4s3te2r1m
ste6ro2m
ste1ro
ster5s1m
s2te2rs2
s2te6rys2
ste1ry
ster7y2s.
ste6s4er
ste3se
s2tes2
ste6sin
s3te3si
ste6s1ka
ste1sk2
ste6s1ki
ste4sl
ste6s3ma
ste2s1m
2s2t1h2
s3ti
4stie
s4ti2g1t
4s5ti1ku
sti6laa2
st2il
sti1la
s4ti3mu
sti2m
stin4g5a
sti2ng
5s4tin2k1h2
sti2n1k
s4ti2p
4sti1r
4s5ti3te
s1ti2t
s3t2j
4s3to.
s5toeg
s5toek
5s4toe2l.
6s5toe5la
s5toen
4stoer
4s3toes2
s5toe1v2
s4tof
sto6fek
sto1fe
sto6fe1m
sto6fen
sto4fo
4s3to1g
sto4ka
sto6k1le
sto1k2l
sto6k1re
sto1k2r
3s4to2m
4s3to1ne
4s3toon1
stoo2
st4o2p
sto6poo2
sto3po
4s2to1s2
s1to4s3t
3s4to2t
s4to1w
s4traa2
st2r
4stra2d
s6tra2k.
4s5tr4ei
st1re
s4tre1w
4s5tr2il
st1r2i
6s2t4ri2n1s2
4stroe
s5tro1g
s1tr6o2n1t
6stroon1
stroo4
6stro3s4p
stros2
4s1tro2t
s5tro2t1s2
4s3trou
4s3tro1v2
s4tru
5s4tru2k3
4s5trus2
3s4try
stu4c2
3s4tu1d
4s3tuig
3s4tuk
stu6kin
stu1ki
stu7kle4p
stu4k2l
stuk1le
stu7stra
s1tus3t
stus2
stust2r
stu4to
s1tu2t
s2tu6tys2
stu3ty
st4wi
s2t1w
2sty
4s3ty2d
5sty2f.
3s4tyl
sty6loo2
sty1lo
1su
su2b2
sub3a
s2ub7gi2d2s2
su2b1g
sub1gi
subgi2d
sub7hoof1
sub1h2
subhoo2
sub7na1si
su2b1n
sub1na
subnas2
sub5oo2
sub7re1ko
sub1re
3sui2d
sui6d1af
sui3da
sui6dei
sui3de
s4uik
s4u2il
sui4p5l
sui2p
sui6pro
sui3p2r
4s3ui2t
s4ui1w
sul6tin
sul1t
s2um7aa2n1s2
su2m
su3ma
sumaa2
sumaan1
2sun
s3u1ni
1su4su
sus2
sut6he2r
su2t
su2t1h2
su9yo.
2s1v2
s3va
s3ve2
s3vi
s3vl
s3vo
s3vr
s3vu
s3vy
svy7kraa2
svyk2r
2s1w
5s4waai1
swaa2
5s4wael
swa1e
s5waen
3s4wak
3s4wa4m
swa3m5a
s5wa2n1d
5s4wa2r1t
s3wa1t
3s4wa1w
6s2wee2f1s2
swee2f
s6wee2f1t
s5wee2k
5s4wee1p
5s4wee1t
4s3weg
5s4weis2
s4wel1g
3s4we4m
5s4we2r1m
swe6t1re
swe1t
swet2r
s3wê1
s3wi
s4wik
4sw2il
s3wo
5s4woeg
swor6s1t
s2wo2rs2
4swo2r1t
s3w2r
s3wu
3swy1g
1sy
5syfer
sy1fe
sy3k
syn6a1g2r
sy2n1a
syn6sin
s2y2n1s2
syn3si
syn5s4m
sy5pla
sy2p
syp2l
sy3sk2
s2ys2
4sys3t
s5y4ster
4sywe
sy1w
s5ywer
2s2-1
1t
2t.
3ta.
taa6na2m
taa2
taan1
taa1na
4taa2n1d
taan5s6f2
taa2n1s2
6taan3si
4taa2n1t
4t5aa2n1v2
3ta2b2b2
ta1b2
4ta2d1m
ta2d
ta4d5ro
ta3d2r
tad6ser
ta2ds2
tad3se
tad4s5i
tad6s1ka
tad1sk2
tad4s5n
tads5p
tad6ste
1tad1s1t
tad6s5to
4ta2f.
4ta2f1d
3ta1fe
tafe4l
4ta4f2r
4t3a2f1s2
4ta2f1v2
4t3a2f1w
t4a1ge
4t5a3gen
4ta2gs2
4t3a2g1t
ta2i4l
tai1
ta5i2n1v2
tai7p6eis2
tai2p
tai3pe
tai3pei
5take.
ta1ke
ta3k6le4p
ta2k3l
tak1le
ta5k1li
ta4k2r
tak7ro2l.
3ta2k1t
tak7wy2n.
ta2k2w
3ta1le
tal7ee2n1h2
talee2
talee2n1
tal7emme
tale1m
tale2m1m
5talig
ta1li
tal6kaa2
tal1k
tal1ka
tal6sor
ta2l1s2
tal1so
ta6mi2n1d
ta2m
ta3mi
tam7inde
1tan6da1t
ta2n1d
tan4d2r
tand7rin
tand1r2i
tand6sto
tan2ds2
1tand1s1t
1tang5s1t
ta2ng
tan2gs2
4t5an1na
tan1n
tan4sk2
ta2n1s2
tap5ro
ta2p
tap2r
3ta1r2i
4ta2r1k
4ta2r1m
t5ar2m.
tar5oo2
ta1ro
t5artik
1ta2r1t
ta4s.
tas2
t5ase1m
ta1se
tas4p
tas6tas2
1tas3t
tat7i3sol
1ta1t
tatis2
tati1so
ta2t4j
tat7jies2
ta4t5ra
tat2r
2t1b2
tba6lun
tba1lu
tby6tei
tby1
1tby2t
tby3te
2t1d
tdy7in2g.
tdyi2ng
teby6s.
te1b2
teby1
teby3s2
3tedo
te1d
tee1k5r
tee2k
tee6lee2
tee3le
tee7l6oo1d
tee4l3o
teeloo2
tee6me1v2
tee1m
tee4mo
tee4n1
4tee2n1h2
3tee2r
tee7raa2d1
tee1ra
teeraa2
tee7re2n1v2
tee1re
tee2s4
tee5sk2
tee5sl
1tee5s1t
tee7s4uik
tee1su
4t3eeu
tef7lo2n.
te1fl
3te2g1n
teg6o1r2i
te1go
teg7re2n.
te1g2r
teg4ren
teg1re
5tehui
te1h2
tei6noo2
tei1no
tek2
5te3ka2m
te1ka
tek7ba2k.
te2k1b2
5teken
te3ke
teke8n7aa2p1
teke1na
tekenaa2
tek7haak1
te2k1h2
tekhaa2
tek7li3mi
te1k2l
tek4li2m
tek1li
5tekor
te1ko
t5ek7se2m1p
te2k1s2
tek1se
tekse1m
tek6sin
tek3si
tek1s5k2
1teks5t
tek7s6tel
tek5vo
te2k1v2
te6laa2p1
te1la
telaa2
tel7aa2p.
te4l5ak
te4l5a2p
tel7dwei
tel1d
tel2d1w
te4l5el
te3le
tel7f6les2
telf3l
telf2le
tel7idee
te3li
te3li2d
teli3de
5telik
tel6lho
te2l1l
tell1h2
tel7oe2s.
te1lo
teloes2
tel7oo2g.
teloo2
teloo1g3
te6loon1
tel7oo2n1d
tel7sme1d
te2l1s2
tel2s1m
tels3me
tem7as3se
te1m
temas2
tema2s2s2
4te2m1m
tem7omva
te1mo2m
temo2m1v2
te6moo1g3
temoo2
tem7oo2g.
5tempo
te2m1p
tena6ge
te1na
5ten2d.
te2n1d
t4e2n1h2
ten6kaa2
te2n1k
ten1ka
ten6koo2
ten1ko
ten7ouer
te1no
tenou1e
ten6s4an
te2n1s2
ten1sa
1ten7slo2t
ten2sl
ten4s3lo
5ten1so
ten4s5u
ten4t5j
1te2n1t
ten4t2r
tent7reg
tent1re
ten7treu
ten7t6wen
ten2t1w
3te1o
te3p4h2
te1p
ter7adel
te1ra
tera2d
tera3de
5te4r5af
ter6a1fi
ter6a1go
te2r3ag
te4rak
ter7akro
tera1k2r
ter7al2s.
tera2l1s2
ter7aman
tera2m
tera3ma
ter7amer
tera3me
te6ra2m1p
ter7am2p.
5tera2p
te4r5a2p1p
ter6ar1g
ter3ar
ter6dro
ter1d
terd2r
terd7roo2
ter5ee2n1
te1re
te4r5el
te4re1m
4ter2f1t
te6ri2n.
te1r2i
te6r7in2k.
teri2n1k
terk7wyn
te2r1k
ter4kwy
ter1k2w
3te2r1m
ter7omra
te1ro
tero2m
tero2m1r
ter5o2n1d
1ter5o2n1t
5te4r1or
ter5o2s.
teros2
te4r5o1w
ter7raan1
te2r1r2
terraa2
5terrei
ter1re
ter7r6ein
5ter1r2i
ter6s2as2
te2rs2
ter1sa
ter6sef
ter1se
ter6seg
ter6sko2p
ter1sk2
ters1ko
ter7s6o2p1n
ter2s3o2p
ter1so
ters6we
ter2s1w
ter7swee
1ter7sys3t
ter1sy
tersys2
ter6ta2p
1te2r1t
te4rui
te1ru
ter7uie.
terui1e
1te6ry3s3t
te1ry
terys2
ter7yste
ter6-in
ter2-1
3te3si
tes2
1tes7ins4t
tes2i2n1s2
tes7loe.
te1sl
tes7ly3di
tes1ly
tesly2d
1te5s7ma2r1t
te2s1m
tes3ma
tes7meto
tes4me
tesme1t
tes7pr4oe
te1s1p
tesp2r
tes7te1r2i
1te1s1t
te3s6tu1d
te5sty
te4s3w
3teti
te1t
teu2n5s4
2t1f
t2f4li
2t1g
t2g4af
t1ga
tge6na2p
t1ge
t3gen
tge1na
tg4li
2t1h2
3t2ha.
t4has2
4thei
3t4hen
ther6aa2
the2r
the1r3a
the7r5aan1
the5ro
3t2hi
4thi2t
1t5hi2t1t
t1h5le1h2
t2hl
th1le
t4ho.
1tho6na1t
tho1na
ths7ch2il
ths2
ths1c2
thsc1h2
t4hy.
1tib7nie1t
ti1b2
ti2b1n
tib1ni
tie4f
tie1f5o
tie6g1r2i
tie1g2r
tie6ka2p
tie1ka
tie4k5l
tie6ko2m
tie1ko
tie4k5r
tie4k5w
tie6r3o1ë
tie1ro
tie7smoo2
tie2s1m
ties2
4ti2f.
4tif1f
4ti2g1m
ti1ke4
ti4kla
ti1k2l
tik7lu2g.
ti4klu2g1
tik5ro
ti1k2r
tik6waa2
ti1k2w
4ti2l.
t2il
til7aa2n.
ti1la
tilaa2
tilaan1
4til1d
4ti2l3s2
4til1t
4t3i2n1d
tin7er2t1s2
ti1ne
1tine2r1t
tin4ga
ti2ng
tin7gaal1
tingaa2
ting7aan1
tin4g7e2g.
tin1ge
tinge1g2
tin4g5r
ting6su
tin2gs2
tings7uu2
4tin2k1h2
ti2n1k
tink7wa.
tin2k2w
4t3i2n1l
4ti2n1v2
5ti3pe.
ti2p
ti3pe
5tipes2
ti4r1p
ti1r
ti4s6aan1
tis2
ti1sa
tisaa2
ti3s1j
ti3t2r
1ti2t
ti3ve5r1
ti1v2
tive2
t2j
2tja
tje6s1ni
tjes2
tje1sn
tjo4k
2t1k
tki6so1b2
t1ki
tkis2
tki1so
tko2m4s5
t1ko
t3ko2m
2t1l
t3la
tla6sin
tlas2
tla1si
t3li
t3lo
1t5lo2n1t
2t1m
tme6s3ti
1tme1s1t
tmes2
2t1n
tne6ywe
t1ne
tne3y1w
tno2t4s5
t1no
1tno2t
3to.
toe7eien
toeei1e
4toef
5toe2f.
toe7g4ly.
toeg1ly
toe7k6la2p
toe4k3l
toe5k2r
4toe2l.
5toe5la
4toe3le
1toe6le1t
toe6lo2p
toe1lo
4toel1t
4toe2p.
toe1p
toe7plei
toe4p2l
toep1le
5toe2r1n
toe6r5ou
toe1ro
5toe1ru
3toes2
toe5sl
toe5s1m
toe7swel
toe4swe
toe3s1w
toë7ro3ti
to1ë
1toëro2t
to4fa
1tof7eks1t
to1fe
tofe2k1s2
tof7emis2
tofe1m
to4f5i1o
to1fi
tof7onde
to1fo
tofo2n1d
3to1g
to4g1l
tok7la2s.
to1k2l
toklas3
1tok7le2t1t
tok1le
tokle1t
to4k5ou
to1ko
t5olie.
to1li
4to2m.
to2m
4to2n1d
3to1ne
ton4g2r
to2ng
ton2g5s2
4to2n1t
t5on2t1l
t5on2t1s2
3toon1
too2
t5op4er
to2p
to3pe
to5p4he
to2p1h2
4to2p1m
top7oor1l
to3po
topoo2
topoor1
4to2p1v2
tor7eien
to1re
to4rei1e
tor7eval
tore1v2
1to6ri2n1t
to1r2i
tor7inte
tor6m5ag
to2r1m
tor6mo2m
to1s2
to3s1f2
tos6tro
1tos3t
tost2r
to3t2r
1to2t
tot6ste
to2t1s2
tots1t
tou3s4
tou7tjie
1tou2t
tout2j
tou6w2s1r
tou1w
tou2ws2
to4wn
to1w
toy7o3ta.
1toyo2t
toyo3ta
2t1p
tpen6sk2
tpe2n1s2
tpie6t2j
1tpie1t
tpiet7ji
tple4k5
tp2l
tp1le
1tpoo2r6t
tpoo2
tpoor1
tpoort7j
tp4sa
t2p1s2
t5psal
t2r
4t5raa2d1
traa2
t5raa2m1
5tradis2
tra2d
tra3di
4traf
tra6fas2
tra1fa
tra1f5o
tra6foo2
5trakta
1tra2k1t
5tran1sa
tra2n1s2
5tran2s3f2
tra6p1af
tra2p
tra3pa
tra6p7a2s.
trapas2
tra6pe1w
tra3pe
tra7p6lo1ï
trap3l
5tra2p1p
1tra1t4
tra5t3j
4tratu
4tre1a
t1re
t3re1c2
t5reda
tre1d
t5re2d1d
t4ree
4tree4k
4t5ree2n1
4t3re1ë
4t3re2g.
t5re1gi
tr4ei
t5rei3ni
tre6i2n1k
4treis2
tre4ka
tre4k5l
tre4ko
tre4k4r
tre1k5w
tre6kwi
t4rer
t5re3se
tres2
1tre4s1t
1tre4t
tre7tal1b2
tret5j
t5reuk
t5rewo
tre1w
5tribu
t1r2i
tri1b2
t5rief
trie6k2l
5tries2
tri5g4l
4tri2ng
tro3bo5
tro1b2
4t5roer
tro6las2
tro1la
trol7a2s.
tro6lin
tro1li
tro6n1af
tro1na
troo4
4troo2m1
tro6s1ki
tro1s1k2
tros2
3trou
3tro1v2
4t3ru1b2
4t3rug
truit6j
1trui2t
trui7tji
4tru2k3
tru7k6o2p1p
tru1ko
truko2p
t2rust5r
1trus3t
trus2
4tryk
2t1s2
1ts5a2g1t
t1sa
t2sag
t4sa2m
t4sar
ts5ar1g
t4s5e2k1s2
t1se
t4s5e2ng
tse6raf
tse1ra
ts2e6rys2
tse1ry
tser7y2s.
t6sin1g2r
t1si
ts4i2ng
t4s5i2n1s2
t4s5i2n1v2
tsi7t6sik
1tsi2t
ts2i2t1s2
tsit1si
ts5ja1e
t1s1j
t2sja
t4skar
t1sk2
ts1ka
ts5kok
ts1ko
t4s3kon
t4skor
t6skrie
tsk2r
tsk1r2i
t6s4kri4p
t4skru
t4s5la2m
t1sl
t5s6maak1
t2s1m
ts3ma
tsmaa2
t5s4mee
ts3me
ts4mel
t4smo
1ts5mo2t
t5smou
1t4s5na1t
t1sn
ts1na
t4s5neu
ts1ne
ts5oon1
t1so
tsoo2
t4s5pas2
ts1p
t4s5pen
1ts5po2t
t4s5pro
tsp2r
t4s5te1a
1ts1t
t5stel
t6ste2n1d
ts4ti
t4s5toer
ts5tron
tst2r
t3stu
t3su
ts5waar
t2s1w
tswaa2
t5swar
t5swen
ts6wi2ng
ts3wi
ts5wyn
2t1t
tta5t4j
tta1t
tte6lo1ë
tte1lo
tte6ral
tte1ra
tte6ra2m
tte7ri2d1g
tter4i2d
tte1r2i
tte4ro
tte7ru2il
tte4rui
tte1ru
tte6slo
ttes2
tte1sl
tte6s5ta
1t1te1s1t
tte5us2
tt4he
t2t1h2
ttin4g5a
tti2ng
tt5uur1
ttuu2
3tu1a
3tu1e
3tuig
3tuin
4tui2t1s2
1tui2t
tu4k2l
tum7aa2n1w
tu2m
tu3ma
tumaa2
tumaan1
tur6k1na
tu2r1k
tur1k2n
4tu2r1t
1tus7aa2r1t
tus2
tu1sa
tusaa2
tus7eter
tu1se
tuse3te
tuse1t
tussen5
tu2s2s2
tus3se
tu5têr
1tu2t
tutê1
2t1v2
tv4li
2t1w
twee5k
twee5l
twee2s4
twerp5o
twer1p
twi6sa2p
twis2
twi1sa
3ty2d
tyd7aa2n1w
ty3da
tydaa2
tydaan1
1tyd7lo2n1t
ty2d1l
tyd7orde
ty3dor
tydor1d
tyd3r
1tyd6sa1t
ty2ds2
tyd1sa
3ty1e
tyl7oor1w
ty1lo
tyloo2
tyloor1
3tyn
tyn7sp1re
tyn4s1p
ty2n1s2
tynsp2r
ty3o
t5y4ster
1tys3t
tys2
2t1z
tze6nel
tze1ne
2t2-1
u1a
ua4e4s2
ua1e
uahu6a.
ua1h2
uahu1a
u4b3ag
u1b2
ubas4
uba7s4la4g
uba2s3l
ub5e1ko
u2b3f
ub5gro
u2b1g
ub1g2r
ub5i2n1t
u3bl
u4blu
ub5lun
ub3or
ub5sc1h2
u2b1s2
ubs1c2
u2b3t
u2b3v2
ub5wyk
u2b1w
uc4k6len
u1c2
u2ck
uc1k2l
uck1le
u4d3ar
u1d
udi6to2p
udi2t
udi3to
u4d3re
ud2r
u4d1r2i
u1ds6me1d
u2ds2
ud2s3m
uds3me
ud5sor
ud2s1o
ud6stoe
ud1s1t
u1d4s6ty2d
ud2sty
u1e
ueb5lo
ue1b2
uer7io1ne
ue1r2i
ueri1o
ue4ron
ue1ro
uer7onde
uero2n1d
ues7tri1a
ue1s1t
ues2
uest2r
uest1r2i
ue5u1ni
u4f1r2i
u1f2r
uf5rin
ufs6maa2
u2f1s2
uf3s1m
uf4s3ma
ug5aan1
u1ga
ugaa2
u4gei
u1ge
uge6s1ka
uge1s4k2
uges2
ug1l
u2go
ugo6mol
ugo2m
ugo3mo
ug3or
u2g3r
u2g3s2
ug2s4k2
ug2s4l
ug4soo2
ug2s3o
ugs4p
ug5ste
ug1s1t
ug4s6tek2
ug5sto
ug5s3ui
ug1su
ug4u1b2
u1gu
ui4da2g
ui2d
ui3da
uid7ar2t1s2
ui4da2r1t
ui4d3o
ui4d2r
uid7ree4k
uid1re
uid7si2m1b2
ui2d2s2
uid1si
uidsi2m
uid7ska1t
uid4s1ka
uid1sk2
uid7skel
uids1ke
uid7s4k2il
uids1ki
uid7slui
uid1s3l
uid5sp2r
uids3p
uids6to
uid1s1t
uid7s5toe
uid7ui2t1s2
ui3du
uidui2t
ui1e
uier2s6w
uie2rs2
uies6ma
uie2s1m
uies2
uie7smaa2
ui4f3a
uif7ee2n1d
ui1fe
ui4fee2n1
uif5le
uif1l
ui4go
ui4g3r
ui2g3s4
ui4k3a
ui4k3l
ui4k3r
uik7sfee
ui2k1s2
uik2s1f2
uiks1fe
uik6sta
uiks1t
uik7ui2t1k
ui1ku
ui4kui2t
ui4k2w
uil7aa2p.
u2il
ui1la
uilaa2
uilaa2p1
uil5e2k1s2
ui1le
uil7e3sel
uile3se
uiles2
ui4l5oo2
ui1lo
uil7tjan
uil1t
uilt2j
uil2tja
ui4ma
ui2m
uim7oe2s.
ui3mo
ui3moe
uimoes2
ui4na
ui5na1e
uin5ar
uin7asyn
uinas2
uina1sy
ui2n1d4
uin7drek
uin4d3re
uind2r
uin7ivoo2
ui3ni
uini1v2
uin7ko1le
uin4kol
ui2n1k
uin1ko
ui4n3o
uin7se2r.
ui2n1s2
uin1se
uin5si
uin6s1ka
uin1sk2
ui4p2l
ui2p
ui4po
uip5oo2
uip7o3r2e.
uipo1re
uip7roes2
ui3p2r
uipr4oe
ui4s3a
uis2
ui2s3j
ui4s3o
uis5ta
uis3t
ui4t3a
ui2t
uit7dein
ui2t1d
uit2dei
ui4t5ee
ui3te
ui5t4er
uit3j
uit3r
uit4s1j
ui2t1s2
uit6-as2
ui2t2-1
uk4aa2
u1ka
ukaar4
uk5loo2
u1k2l
u4kof
u1ko
uk5of1f
u4kor
u5kra1t
uk2r
uk3ry
uks2e4s2
u2k1s2
uk1se
uk4s1ki
uk1sk2
uk2s4m
uks7po2p.
uk2s1p
uks1po2p
uk4s1v2
uk4t1h2
u2k1t
uku7y4a3ma
u1ku
ukuy1a
ukuya2m
u2k3w
u1la
u2l3ag
uld5e2r1k
ul1d
ule6sta
u1le
ule3s1t
ules2
ulê6r-1w
u1lê1
ulêr2-1
ulf6a3pi
ul1fa
ulf3a2p
ul5i2n1s2
u1li
u1lo
ul5oo1g3
uloo2
ulp7ek1sa
ul1p
ulpe2k1s2
ulp7oo2r1t
ulpoo2
ulpoor1
ulp7or1ga
ulpor1g
ul4saa2
u2l1s2
ul1sa
ul4s6o2m1s2
ul1so
ulso2m
ult7in1ge
ul1t
ulti2ng
ult7ui2t.
ul1tui2t
u1lu
u2m
u3ma
um5a2g1t
uma1t4
uma5t3j
u3me
um4g6rok
u2m1g
um1g2r
u3mi
um4ie
um5o2n1d
um4s.
u2m1s2
ums7fel1d
um2s1f2
ums1fe
u3mu
u4mui
u3mu4s2
un5a2r1m
u1na
uner6s1t
u1ne
une2rs2
5uni1fo
u1ni
3uni1v2
unk7reda
u2n1k
unk2r
unk1re
unkre1d
u2n2s3
uns6e2n1b2
un1se
un4si2d
un3si
un5s6ka2p
un1sk2
uns1ka
un5s6kol
uns1ko
un7s6kool1
unskoo2
uns7la2g1g2
un2sl
un3sla
uns4la4g
uns7taal1
uns4taa2
uns1t
un5st2r
un4t7ee2n1h2
u2n1t
untee4n1
un6tin1n
unt7real
unt2r
un4tre1a
unt1re
unt6roe
unts6ko
un2t1s2
unt1sk2
unt5s1w
unug6s.
u1nu
unu2g3s2
u1o
u1pi6lo2p
u1p
up2il
upi1lo
u4p1ly
up2l
u4pon
u1ra
ur5aar
uraa2
ur3af
ur5a2g1t
u2rag
ur3ak
u5ra5s4t
uras2
ur5a2t1l
ura1t
ur5ee2n1
u1re
u4ref
u4r5ef1f
ure5u2m
urf7l4oo2p1
urfloo2
urg6h-s2
ur1g
ur2g1h2
urgh2-1
urg7laag
urglaa2
u4r5i2n1t
u1r2i
urke5s3
u2r1k
ur1ke
urk7nael
ur1k2n
urk1na
urkna1e
u1ro
uro7pe3si
uro2p
uro3pe
uropes2
urp7agti
ur1p
urpa2g1t
urr7hei2m
u2r1r2
urr1h2
urs7agte
u2rs2
ur1sa
ur2sag
ursa2g1t
ur4s5ek
ur1se
urs6fee
ur2s3f2
urs1fe
ur6sloo2
ur1sl
urs7mous2
ur2s1m
ur4s1no
ur1sn
ur4s5oo2
ur1so
ur6s7paar
ur1s1p
urspaa2
ur4s6par
ur4sp2r
urs6wee
urs3we
ur2s1w
urs6wie
urs3wi
ur6t5oor1
u2r1t
urtoo2
ur4to2p
ur4t7roo2m1
urt2r
urtroo4
u1ru
u1ry
u2s3ag
us2
u1sa
usa7l4a2g.
usa1la
usa2lag
u4sa2p
us5ee2n1
u1se
use5s1t
uses2
ush7die.
u2s1h2
ush1d
ush7koal
ush1ko
ushko1a
usie4k
u1si
u3sie
usie1k5l
u4s5i2n1s2
u2s3k2
u2s4k.
us4kok
us1ko
us4k1r2i
usk2r
u2s3l
u3s4lu
u4s1no
u1sn
u2s3o
u3s4ol
us4or
us3p
us4pie
u4spo
us5po2t
usse7u2s.
u2s2s2
us3se
usseus2
us3t
u2s4t.
us3t5a2k1t
usta6v.
usta1v2
us4t1b2
us4t1f
us4t1g
u2s4t1h2
us6ti2n1k
us3ti
us4to
us6trek
ust2r
ust1re
us5tru
us4t1s2
u2s3w
u2t
u3ta
u4t5a2d1m
uta2d
u4t3ag
uta7spek
utas4p
utas2
u3te
ute7li2ng
ute3li
ute7r6a1go
ute1ra
ute2r3ag
u3ti
u3to
u4top3l
uto2p
ut5or1g
ut4rek
ut2r
ut1re
uts7luis2
u2t1s2
ut1sl
ut4sp2r
uts1p
u3tu
u3ty
uu2
uur1
uu1r3i
uur6s5in
uu2rs2
uur1si
uus3
uus6khe
uu2s3k2
uus2k1h2
uut3j
uu2t
uwee2s4
u1w
uwe7s6mi2t
uwe2s1m
uwes2
uwes3mi
u5yste
uys3t
uys2
u3yu
û1
1ü1
1v2
vaar6s1t
vaa2
vaa2rs2
va4kar
va1ka
vak7eie.
va1ke
vakei1e
va4kes2
va4ki
va5ki2e
va4k3o
va4k2r
val7e4ste
va1le
vale3s1t
vales2
val7f4ee2s3
val4f3e
val7fu2n1k
val4fu
val7iso1g
va1li
valis2
vali1so
val4k7oë.
val1k
val1ko
val3ko1ë
val7opto
va1lo
valo2p
valo2p1t
val4s4a
va2l1s2
val6spa
vals1p
val3s7pan
val6spo
val6sp2r
val6s5te
val3s1t
val6s2t1h2
va4n5ee2
va1ne
van7ef1fe
va4nef1f
van6ga2p
va2ng
van1ga
van6g1re
van1g2r
van7ui2t.
va1nu
va2nui
vanui2t
vari5e1t
va1r2i
var6kja
va2r1k
vark1j
var6k1le
var1k2l
var4k5n
var4k5o
var4k5r
vas7en2t.
vas2
va1se
vase2n1t
vas6oor1
va4s3oo2
va1so
va5s7waai1
va2s3w
vaswaa2
ve2
ve3d
vee7ko2ng
vee2k
vee1ko
vee5k2r
vee4l
vee1r5a
vee2r
vee3s4
ve9ga.
ve1ga
veis4
vei5s3t
vel6don
vel1d
vel4d5r
vel5oo2
ve1lo
ve5lo2p
vel5s1m
ve2l1s2
vel7sp1le
vel2s3p2l
vels1p
ve5lu2m
ve1lu
ve3na
ve3ne
ve5nor
ve1no
ven4t5j
ve2n1t
ve3nu
vep7le1gi
ve1p
vep2l
vep1le
3ver1
ve9r4a.
ve1ra
ver1d4
ver5die2n7s8
ve5r4e1b2
ve1re
ve5r4ek
ver6ema
vere1m
ver6e1na
ve5r4e2n1d
ve2r6eve2
vere1v2
ver6flu
ver6fru
ver1f2r
ve3r2i
ve6r5in1n
ve2r1k4
ver5k2l
ver5k2w
ve7r6ona.
ve1ro
vero1na
ver5sa
ve2rs2
vers6ki
ver1sk2
ver7s5kin
vers8ko2p.
vers1ko
versko2p
ver5sl
ver5s1m
vers6mag
vers3ma
ver5s1p
ver7stal
ver1s1t
ver5s4w
vers8waar
verswaa2
ver4t5j
ve2r1t
ver5t1w
ve1s3p
ves2
ve1s3t
ves7taal1
ves4taa2
1v2e5su1v2
ve1su
vet5in
ve1t
vet5ji
vet2j
vet7op1ga
veto2p
veto2p1g
vet3r
ve2t1s4
vet5s1m
vid5s3o
vi2d
vi2d2s2
vie7ri2ng
vie1r2i
vi2g4s2
vi5rag
vi1r
vi4r1g
vi4r2-1
vi4s3ar
vis2
vi1sa
vi4so
vi3s5ol
vis7oo2g.
vi4soo1g3
visoo2
vis5t2r
vis3t
vi3t2r
vi2t
vit7rate
vi1tra1t4
vla2g5s2
v2lag
vla4k
vla7koek
vla1ko
vla6sak
vlas2
vla1sa
vle4k
v1le
vle1k5l
v4lie
v1li
vlie6so
vlies2
voe6r4ek
voe1re
voe6rui
voe1ru
voë4l
vo1ë
voël7oë.
voëlo1ë
vog7inho
vo1g
vo1gi
vogi2n1h2
vo4g2r
vo4lei
vo1le
vol4g5a
vol1g
vol6gon
vol1go
vo4l3o
vol7so2n3g
vo2l1s2
vol1so
vol7ui2t.
vo1lu
vo4lui2t
vol7ywer
vo1ly
voly1w
vond6s7te
vo2n1d
von2ds2
vond1s1t
von6klo
vo2n1k
von1k2l
voo7doo.
voo2
voo1d
voodoo2
voo2r5s4
voor1
vor4s5t
vo2rs2
vor7ster
vou7po2p1s2
vou1p
vou1po2p
vou5t2j
vou2t
vra6gry
v2rag
vr4a1g2r
v4r2e.
v1re
vree6t2j
vree1t
vreet7ji
vrie6s1p
v1r2i
vries2
vri6jze
vri1j
vrij1z
vry7duik
vry2d
vry7ky2k.
vry3s4
vry5s3t
vry7ui2t.
vryui2t
vu4e.
vu1e
vu9yo.
vyf7armi
vy4far
vy1fa
vyfa2r1m
vy3s2
1w
2w.
4wael
wa1e
wae6la1t
wae1la
4wa4e1o
3wa2g.
wag6las2
wag1l
wal6ste
wa2l1s2
wal3s1t
wand6sk2
wa2n1d
wan2ds2
wan6gaa2
wa2ng
wan1ga
wan7in1ge
wa1ni
wani2ng
wa2n1s4
wan5s1m
5wapen
wa2p
wa3pe
3wa2r1m
wa2r4s3
war3s6e.
war1se
4wa2r1t
war6t2hi
war2t1h2
war4t5j
war6to1ë
wart7oë.
war6too2
war4t5r
war6tys2
wa5s4hi
was2
wa2s1h2
was6kaa2
wa2s3k2
was1ka
was5la
wa2s3l
was6mou
wa2s3m
was7pa2n.
was3p
wa3s4pan
wa5st2r
was3t
wate6ra
wa1t
water7aa2
wat5so
wa2t1s2
we4ba2d
we3ba
we1b2
we4b1m
we4b5ru
web7taal1
we2b1t
webtaa2
web7v4lie
we2b1v2
webv1li
3we1d
we4d3r
we5d4ra
we5dry
we4dy
5weef1se
wee2f
wee2f1s2
weeg6s.
wee2g3s4
wee5ran
wee2r
wee1ra
wee5sa
wee2s3
wee7skaa2
wee1sk2
wees1ka
wee5s1ko
wee5sl
wee5s4p
wee5s1t
wee6tru
wee1t
weet2r
3weg
we4ga
weg7do2f.
we2g1d
weg2dof
weg3l
we4go
weg7orde
wegor1d
we4g3r
we2g3s4
weg5s1t
we4gu
4weis2
we4k5ro
we1k2r
wek7uu2r.
we1ku
wekuu2
wekuur1
we4l7aa2n1b2
we1la
welaa2
welaan1
we6lar1g
we4l5ee2
we3le
wel7f6lan
welf3l
wel7f6ron
wel2f3r
we4l5oor1
we1lo
weloo2
we4l5o2p
wel6s1ke
we2l1s2
wel1sk2
we4m3o
we1m
wem6o2s.
wemos2
we4nak
we1na
we4n5as2
wen7ee2n1s2
we1ne
wenee2
wenee2n1
wen6sa2d
we2n1s2
wen1sa
wen6s4an
wen6sar
wen6sei
wen1se
wen6ser
wen6s2es2
wen4sk2
wens5ka
wen7sku1d
wens1ku
wen4s5l
wen4so
wen6sp2r
wens1p
wen6ste
wens1t
wens7tes2
wen4s5u
wer7es3se
we3r4es2
we1re
were2s2s2
wer6fo2m
wer1fo
wer6gar
wer1g
wer1ga
wer6g1re
wer1g2r
we4r6i2n1t
we1r2i
3we2r1k
wer6ka2d
wer1ka
wer6k5af
wer6ke4s3
wer1ke
wer4k2l
werk7laa2
wer7klan
wer7kla2p
wer7klok
wer6ko2m1s2
wer1ko
wer3ko2m
wer6kon
wer7ko1ny
wer6k1re
werk2r
wer6kro
wer6kuu2
wer1ku
1wer4k2w
wer5kwa
wer4ky
we4r3o
wer6p1af
wer1p
wer6pan
wer6plo
werp2l
wer6pon
wer6poo2
wer6por
wer7sme1d
we2rs2
wer2s1m
wers3me
wer7ui2l.
we1ru
weru2il
wes6mi2t
we2s1m
wes2
wes3mi
wes7oewe
we1so
wesoe1w
we4s1p
wes7pemi
wespe1m
we4s3t
we5sta
we2s4t1h2
we5s4t2r
wes7waar
1we3s1w
weswaa2
3we2t.
we1t
wet7re1gu
wet2r
wet1re
wet4s5o
we2t1s2
we1t4s5t
wet7wy1si
wet4wy
1we2t1w
wet3wys2
3wêr
wê1
4wfon
w1fo
wids7tor
wi2d
wi2d2s2
wid1s1t
wie1l5a
wie4t5j
wie1t
wi4g2r
wi4k2l
wi4ko
wiks7te.
wi2k1s2
wiks1t
wil6da2g
w2il
wil1d
wil6sin
wi2l1s2
wil1si
wil4sk2
wind7a4s.
win4das2
wi2n1d
win6del
win6do2p
wind7o2p.
win4d2r
wind7ru2k3
win6k1le
wi2n1k
win1k2l
win4sk2
wi2n1s2
win7sky.
win4s5t
win7s6ton
win6ta2p
wi2n1t
win6tes2
wip7li2g.
wi2p
wi3p2l
wipli4g
wip1li
wip7roos2
wi3p2r
wiproo2
wit5el
wi2t
wi3te
wit7in2k1b2
wi3ti
wit4in
witi2n1k
wit5ji
wit2j
wi4t3o
wit5ro
wit2r
2w1j
wje6tun
wje1t
4woeg
woe4s2
wo4l3a
wol7invo
wo1li
wo4li2n1v2
wol6klo
wol1k
wol1k2l
wo4l3o
woor6d2r
woo2
woor1
woor1d
wo5r2u2m
wor1u
wou6da2g
wou1d
w2r
wree4
w1re
wri6gon
w1r2i
wri1go
2ws2
wur4g2r
wur1g
wu2r4m
wurm5a
2w1w
wwe7r6i2n1t
wwe1r2i
wyd5oo2
wy2d
wyd7ui3te
wydui2t
wyk6was2
1wy2k3w
wyn6a2n1d
wy2n1a
wyn5s4m
wy2n1s2
3wys2
wys7ak3si
wy1sa
wys2a2k1s4
wy2s3k2
wys3p
wys5ta
wys3t
wyt7raak1
wy2t
wyt2r
wytraa2
x'9ie.
x’9ie.
x1a
xe1
x3e1m
xerox7e.
xe1ro
xeroxe1
x2h2
x1i
xys6te.
xys3t
xys2
y1a
y4a3ma
ya2m
ybe6l2il
y1b2
ybe3li
y2d
y3da
y3de
yde2n4s2
ydge3le6
y2d1g
yd1ge
ydg6leu
ydg1le
y3di
y2d3of
yd3re
yd2r
yd3ro
yd4sin
y2ds2
yd1si
yd6ska1t
yd1sk2
yds1ka
yd6s7kri2t
ydsk2r
ydsk1r2i
yds7orde
yd2s1o
y1d4sor1d
y1e
y4en1n
yer2
yer7ho2f.
yer1h2
yer7maa1t1
yerm4a
ye2r1m
yermaa2
ye2r4s2
yer7to2n.
ye2r1t
yer7vi2l1l
yer1v2
yerv2il
yer7voë4l
yervo1ë
yes6a2g1t
yes2
ye1sa
ye2sag
3yeu
yf3aa2
y1fa
y4far
yf5a2s.
yfas2
yf3l
y2f3r
yf5ren
yf1re
yf4sl
y2f1s2
yf4su
y1g
yg4le
yg4li
yg4ly
yg5saa2
y2gs2
yg2s1a
ygs7ka2n1s2
yg2s1k2
ygs1ka
yg5s1ko
ygs5le
yg2s3l
yg4s6tek2
yg1s1t
yg4sto
yg4stu
ykaar4
y1ka
ykaa2
yker6s1t
y1ke
yke2rs2
y4k5i2n1s2
y1ki
y2k2l
yk3li
yk4lu
yk5lui
yk5lus2
yko2m1s4
y1ko
y3ko2m
y4kor
yk3r2i
yk2r
yk4s5a2d
y2k1s2
yk1sa
yk4sk2
yks7ka2n1s2
yk4skan
yks1ka
yk3s1p
yks4t
yk4su
y2k3w
y1la
y1lo
y4loe
yls7laar
y2l1s2
yl1sl
yls4laa2
y2n1a
yn4a.
y4nei
y1ne
y4n5e3te
yne1t
y2n1g2
yn5k3li
y2n1k
yn1k2l
yn5kwa
yn2k2w
y2n1o
y3n4o2m
yns6a2g1t
y2n1s2
yn2s3ag
yn1sa
yn4s5a2m
y2n1s5i2n1s2
yn3si
yn5s1ly
yn2sl
yn2s4m
yns7maan1
yns3ma
ynsmaa2
yn4s5or
yn1so
yn4s1p
yns7paar
ynspaa2
yns4t
y1n3u
yo9yo.
y2p
y3pa
y3pe
y3pi
y5plan
yp2l
yp3li
yp5or1g
y3p2r
y3pu
y1r
y4sa2m
ys2
y1sa
ys4ig
y1si
y4s5i2n1d
y4s5i2n1s2
y2sk2
ys5kar
ys1ka
ys3ko
y5s4koo2
y2s3l
y2s3n
y2s3o
ys4ok
y3s4ou
ys5poe
ys1p
ys3t
ys4ta
y4s5taf
y5s4tel
y4ster
y3s4t2r
ys4tu
ys3ui
y1su
y2s3w
y2t
y3ta
yt3ag
y3te
yt4ha
y2t1h2
y3ti
y3t2j
y3to
y3tu
y3ty
yve7sa2n1t
y1v2
yve2
yves4an
yves2
yve1sa
1z
2z.
4zbur
z1b2
zee7r6us3t
zee2r
zee1r3u
zeerus2
zen7el1le
ze1ne
zene2l1l
zi2c2
zi5ca1t
4zman
z1m
zook6a.
zoo2
zook3
zoo4ka
2z1z
2-1
-e2r4t
-ert5j
-ha-7ha.
-1h2
-ha2-1
-ha-1h2
-k4li
-1k2l
-na6ge1w
-1na
-na1ge
-s4ti
-s2
-s1t
-t4we
-1t
-2t1w
-5twee
.s8a8n8d8a8a8l.
.sa2n1d
.sandaa2
.sandaal1
PK
!<O
0<0<hyphenation/hyph_bg.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1а1
1б1
1в1
1г1
1д1
1е1
1ж1
1з1
1и1
1й1
1к1
1л1
1м1
1н1
1о1
1п1
1р1
1с1
1т1
1у1
1ф1
1х1
1ц1
1ч1
1ш1
1щ1
1ъ1
1ю1
1я1
б4а1
б4е1
б4и1
б4о1
б4у1
б4ъ1
б4ю1
б4я1
в4а1
в4е1
в4и1
в4о1
в4у1
в4ъ1
в4ю1
в4я1
г4а1
г4е1
г4и1
г4о1
г4у1
г4ъ1
г4ю1
г4я1
д4а1
д4е1
д4и1
д4о1
д4у1
д4ъ1
д4ю1
д4я1
ж4а1
ж4е1
ж4и1
ж4о1
ж4у1
ж4ъ1
ж4ю1
ж4я1
з4а1
з4е1
з4и1
з4о1
з4у1
з4ъ1
з4ю1
з4я1
й4а1
й4е1
й4и1
й4о1
й4у1
й4ъ1
й4ю1
й4я1
к4а1
к4е1
к4и1
к4о1
к4у1
к4ъ1
к4ю1
к4я1
л4а1
л4е1
л4и1
л4о1
л4у1
л4ъ1
л4ю1
л4я1
м4а1
м4е1
м4и1
м4о1
м4у1
м4ъ1
м4ю1
м4я1
н4а1
н4е1
н4и1
н4о1
н4у1
н4ъ1
н4ю1
н4я1
п4а1
п4е1
п4и1
п4о1
п4у1
п4ъ1
п4ю1
п4я1
р4а1
р4е1
р4и1
р4о1
р4у1
р4ъ1
р4ю1
р4я1
с4а1
с4е1
с4и1
с4о1
с4у1
с4ъ1
с4ю1
с4я1
т4а1
т4е1
т4и1
т4о1
т4у1
т4ъ1
т4ю1
т4я1
ф4а1
ф4е1
ф4и1
ф4о1
ф4у1
ф4ъ1
ф4ю1
ф4я1
х4а1
х4е1
х4и1
х4о1
х4у1
х4ъ1
х4ю1
х4я1
ц4а1
ц4е1
ц4и1
ц4о1
ц4у1
ц4ъ1
ц4ю1
ц4я1
ч4а1
ч4е1
ч4и1
ч4о1
ч4у1
ч4ъ1
ч4ю1
ч4я1
ш4а1
ш4е1
ш4и1
ш4о1
ш4у1
ш4ъ1
ш4ю1
ш4я1
щ4а1
щ4е1
щ4и1
щ4о1
щ4у1
щ4ъ1
щ4ю1
щ4я1
ь4а1
ь4е1
ь4и1
ь4о1
ь4у1
ь4ъ1
ь4ю1
ь4я1
4б3б4
2б3в2
2б3г2
2б3д2
2б3ж2
2б3з2
2б3й2
2б3к2
2б3л2
2б3м2
2б3н2
2б3п2
2б3р2
2б3с2
2б3т2
2б3ф2
2б3х2
2б3ц2
2б3ч2
2б3ш2
2б3щ2
2в3б2
4в3в4
2в3г2
2в3д2
2в3ж2
2в3з2
2в3й2
2в3к2
2в3л2
2в3м2
2в3н2
2в3п2
2в3р2
2в3с2
2в3т2
2в3ф2
2в3х2
2в3ц2
2в3ч2
2в3ш2
2в3щ2
2г3б2
2г3в2
4г3г4
2г3д2
2г3ж2
2г3з2
2г3й2
2г3к2
2г3л2
2г3м2
2г3н2
2г3п2
2г3р2
2г3с2
2г3т2
2г3ф2
2г3х2
2г3ц2
2г3ч2
2г3ш2
2г3щ2
2д3б2
2д3в2
2д3г2
4д3д4
3д4ж1
2д3з2
2д3й2
2д3к2
2д3л2
2д3м2
2д3н2
2д3п2
2д3р2
2д3с2
2д3т2
2д3ф2
2д3х2
2д3ц2
2д3ч2
2д3ш2
2д3щ2
2ж3б2
2ж3в2
2ж3г2
2ж3д2
4ж3ж4
2ж3з2
2ж3й2
2ж3к2
2ж3л2
2ж3м2
2ж3н2
2ж3п2
2ж3р2
2ж3с2
2ж3т2
2ж3ф2
2ж3х2
2ж3ц2
2ж3ч2
2ж3ш2
2ж3щ2
2з3б2
2з3в2
2з3г2
2з3д2
2з3ж2
4з3з4
2з3й2
2з3к2
2з3л2
2з3м2
2з3н2
2з3п2
2з3р2
2з3с2
2з3т2
2з3ф2
2з3х2
2з3ц2
2з3ч2
2з3ш2
2з3щ2
2й3б2
2й3в2
2й3г2
2й3д2
2й3ж2
2й3з2
4й3й4
2й3к2
2й3л2
2й3м2
2й3н2
2й3п2
2й3р2
2й3с2
2й3т2
2й3ф2
2й3х2
2й3ц2
2й3ч2
2й3ш2
2й3щ2
2к3б2
2к3в2
2к3г2
2к3д2
2к3ж2
2к3з2
2к3й2
4к3к4
2к3л2
2к3м2
2к3н2
2к3п2
2к3р2
2к3с2
2к3т2
2к3ф2
2к3х2
2к3ц2
2к3ч2
2к3ш2
2к3щ2
2л3б2
2л3в2
2л3г2
2л3д2
2л3ж2
2л3з2
2л3й2
2л3к2
4л3л4
2л3м2
2л3н2
2л3п2
2л3р2
2л3с2
2л3т2
2л3ф2
2л3х2
2л3ц2
2л3ч2
2л3ш2
2л3щ2
2м3б2
2м3в2
2м3г2
2м3д2
2м3ж2
2м3з2
2м3й2
2м3к2
2м3л2
4м3м4
2м3н2
2м3п2
2м3р2
2м3с2
2м3т2
2м3ф2
2м3х2
2м3ц2
2м3ч2
2м3ш2
2м3щ2
2н3б2
2н3в2
2н3г2
2н3д2
2н3ж2
2н3з2
2н3й2
2н3к2
2н3л2
2н3м2
4н3н4
2н3п2
2н3р2
2н3с2
2н3т2
2н3ф2
2н3х2
2н3ц2
2н3ч2
2н3ш2
2н3щ2
2п3б2
2п3в2
2п3г2
2п3д2
2п3ж2
2п3з2
2п3й2
2п3к2
2п3л2
2п3м2
2п3н2
4п3п4
2п3р2
2п3с2
2п3т2
2п3ф2
2п3х2
2п3ц2
2п3ч2
2п3ш2
2п3щ2
2р3б2
2р3в2
2р3г2
2р3д2
2р3ж2
2р3з2
2р3й2
2р3к2
2р3л2
2р3м2
2р3н2
2р3п2
4р3р4
2р3с2
2р3т2
2р3ф2
2р3х2
2р3ц2
2р3ч2
2р3ш2
2р3щ2
2с3б2
2с3в2
2с3г2
2с3д2
2с3ж2
2с3з2
2с3й2
2с3к2
2с3л2
2с3м2
2с3н2
2с3п2
2с3р2
4с3с4
2с3т2
2с3ф2
2с3х2
2с3ц2
2с3ч2
2с3ш2
2с3щ2
2т3б2
2т3в2
2т3г2
2т3д2
2т3ж2
2т3з2
2т3й2
2т3к2
2т3л2
2т3м2
2т3н2
2т3п2
2т3р2
2т3с2
4т3т4
2т3ф2
2т3х2
2т3ц2
2т3ч2
2т3ш2
2т3щ2
2ф3б2
2ф3в2
2ф3г2
2ф3д2
2ф3ж2
2ф3з2
2ф3й2
2ф3к2
2ф3л2
2ф3м2
2ф3н2
2ф3п2
2ф3р2
2ф3с2
2ф3т2
4ф3ф4
2ф3х2
2ф3ц2
2ф3ч2
2ф3ш2
2ф3щ2
2х3б2
2х3в2
2х3г2
2х3д2
2х3ж2
2х3з2
2х3й2
2х3к2
2х3л2
2х3м2
2х3н2
2х3п2
2х3р2
2х3с2
2х3т2
2х3ф2
4х3х4
2х3ц2
2х3ч2
2х3ш2
2х3щ2
2ц3б2
2ц3в2
2ц3г2
2ц3д2
2ц3ж2
2ц3з2
2ц3й2
2ц3к2
2ц3л2
2ц3м2
2ц3н2
2ц3п2
2ц3р2
2ц3с2
2ц3т2
2ц3ф2
2ц3х2
4ц3ц4
2ц3ч2
2ц3ш2
2ц3щ2
2ч3б2
2ч3в2
2ч3г2
2ч3д2
2ч3ж2
2ч3з2
2ч3й2
2ч3к2
2ч3л2
2ч3м2
2ч3н2
2ч3п2
2ч3р2
2ч3с2
2ч3т2
2ч3ф2
2ч3х2
2ч3ц2
4ч3ч4
2ч3ш2
2ч3щ2
2ш3б2
2ш3в2
2ш3г2
2ш3д2
2ш3ж2
2ш3з2
2ш3й2
2ш3к2
2ш3л2
2ш3м2
2ш3н2
2ш3п2
2ш3р2
2ш3с2
2ш3т2
2ш3ф2
2ш3х2
2ш3ц2
2ш3ч2
4ш3ш4
2ш3щ2
2щ3б2
2щ3в2
2щ3г2
2щ3д2
2щ3ж2
2щ3з2
2щ3й2
2щ3к2
2щ3л2
2щ3м2
2щ3н2
2щ3п2
2щ3р2
2щ3с2
2щ3т2
2щ3ф2
2щ3х2
2щ3ц2
2щ3ч2
2щ3ш2
4щ3щ4
1а1а1а4
1а1а1
аа1е4
аа1и4
аа1о4
аа1у4
аа1ъ4
аа1ю4
аа1я4
1а1е1а4
а1е1
а1е1е4
ае1и4
ае1о4
ае1у4
ае1ъ4
ае1ю4
ае1я4
1а1и1а4
а1и1
аи1е4
а1и1и4
аи1о4
аи1у4
аи1ъ4
аи1ю4
аи1я4
1а1о1а4
а1о1
ао1е4
ао1и4
а1о1о4
ао1у4
ао1ъ4
ао1ю4
ао1я4
1а1у1а4
а1у1
ау1е4
ау1и4
ау1о4
а1у1у4
ау1ъ4
ау1ю4
ау1я4
1а1ъ1а4
а1ъ1
аъ1е4
аъ1и4
аъ1о4
аъ1у4
а1ъ1ъ4
аъ1ю4
аъ1я4
1а1ю1а4
а1ю1
аю1е4
аю1и4
аю1о4
аю1у4
аю1ъ4
а1ю1ю4
аю1я4
1а1я1а4
а1я1
ая1е4
ая1и4
ая1о4
ая1у4
ая1ъ4
ая1ю4
а1я1я4
е1а1а4
е1а1
1е1а1е4
еа1и4
еа1о4
еа1у4
еа1ъ4
еа1ю4
еа1я4
ее1а4
1е1е1
1е1е1е4
ее1и4
ее1о4
ее1у4
ее1ъ4
ее1ю4
ее1я4
еи1а4
е1и1
1е1и1е4
е1и1и4
еи1о4
еи1у4
еи1ъ4
еи1ю4
еи1я4
ео1а4
е1о1
1е1о1е4
ео1и4
е1о1о4
ео1у4
ео1ъ4
ео1ю4
ео1я4
еу1а4
е1у1
1е1у1е4
еу1и4
еу1о4
е1у1у4
еу1ъ4
еу1ю4
еу1я4
еъ1а4
е1ъ1
1е1ъ1е4
еъ1и4
еъ1о4
еъ1у4
е1ъ1ъ4
еъ1ю4
еъ1я4
ею1а4
е1ю1
1е1ю1е4
ею1и4
ею1о4
ею1у4
ею1ъ4
е1ю1ю4
ею1я4
ея1а4
е1я1
1е1я1е4
ея1и4
ея1о4
ея1у4
ея1ъ4
ея1ю4
е1я1я4
и1а1а4
и1а1
иа1е4
1и1а1и4
иа1о4
иа1у4
иа1ъ4
иа1ю4
иа1я4
ие1а4
и1е1
и1е1е4
1и1е1и4
ие1о4
ие1у4
ие1ъ4
ие1ю4
ие1я4
ии1а4
1и1и1
ии1е4
1и1и1и4
ии1о4
ии1у4
ии1ъ4
ии1ю4
ии1я4
ио1а4
и1о1
ио1е4
1и1о1и4
и1о1о4
ио1у4
ио1ъ4
ио1ю4
ио1я4
иу1а4
и1у1
иу1е4
1и1у1и4
иу1о4
и1у1у4
иу1ъ4
иу1ю4
иу1я4
иъ1а4
и1ъ1
иъ1е4
1и1ъ1и4
иъ1о4
иъ1у4
и1ъ1ъ4
иъ1ю4
иъ1я4
ию1а4
и1ю1
ию1е4
1и1ю1и4
ию1о4
ию1у4
ию1ъ4
и1ю1ю4
ию1я4
ия1а4
и1я1
ия1е4
1и1я1и4
ия1о4
ия1у4
ия1ъ4
ия1ю4
и1я1я4
о1а1а4
о1а1
оа1е4
оа1и4
1о1а1о4
оа1у4
оа1ъ4
оа1ю4
оа1я4
ое1а4
о1е1
о1е1е4
ое1и4
1о1е1о4
ое1у4
ое1ъ4
ое1ю4
ое1я4
ои1а4
о1и1
ои1е4
о1и1и4
1о1и1о4
ои1у4
ои1ъ4
ои1ю4
ои1я4
оо1а4
1о1о1
оо1е4
оо1и4
1о1о1о4
оо1у4
оо1ъ4
оо1ю4
оо1я4
оу1а4
о1у1
оу1е4
оу1и4
1о1у1о4
о1у1у4
оу1ъ4
оу1ю4
оу1я4
оъ1а4
о1ъ1
оъ1е4
оъ1и4
1о1ъ1о4
оъ1у4
о1ъ1ъ4
оъ1ю4
оъ1я4
ою1а4
о1ю1
ою1е4
ою1и4
1о1ю1о4
ою1у4
ою1ъ4
о1ю1ю4
ою1я4
оя1а4
о1я1
оя1е4
оя1и4
1о1я1о4
оя1у4
оя1ъ4
оя1ю4
о1я1я4
у1а1а4
у1а1
уа1е4
уа1и4
уа1о4
1у1а1у4
уа1ъ4
уа1ю4
уа1я4
уе1а4
у1е1
у1е1е4
уе1и4
уе1о4
1у1е1у4
уе1ъ4
уе1ю4
уе1я4
уи1а4
у1и1
уи1е4
у1и1и4
уи1о4
1у1и1у4
уи1ъ4
уи1ю4
уи1я4
уо1а4
у1о1
уо1е4
уо1и4
у1о1о4
1у1о1у4
уо1ъ4
уо1ю4
уо1я4
уу1а4
1у1у1
уу1е4
уу1и4
уу1о4
1у1у1у4
уу1ъ4
уу1ю4
уу1я4
уъ1а4
у1ъ1
уъ1е4
уъ1и4
уъ1о4
1у1ъ1у4
у1ъ1ъ4
уъ1ю4
уъ1я4
ую1а4
у1ю1
ую1е4
ую1и4
ую1о4
1у1ю1у4
ую1ъ4
у1ю1ю4
ую1я4
уя1а4
у1я1
уя1е4
уя1и4
уя1о4
1у1я1у4
уя1ъ4
уя1ю4
у1я1я4
ъ1а1а4
ъ1а1
ъа1е4
ъа1и4
ъа1о4
ъа1у4
1ъ1а1ъ4
ъа1ю4
ъа1я4
ъе1а4
ъ1е1
ъ1е1е4
ъе1и4
ъе1о4
ъе1у4
1ъ1е1ъ4
ъе1ю4
ъе1я4
ъи1а4
ъ1и1
ъи1е4
ъ1и1и4
ъи1о4
ъи1у4
1ъ1и1ъ4
ъи1ю4
ъи1я4
ъо1а4
ъ1о1
ъо1е4
ъо1и4
ъ1о1о4
ъо1у4
1ъ1о1ъ4
ъо1ю4
ъо1я4
ъу1а4
ъ1у1
ъу1е4
ъу1и4
ъу1о4
ъ1у1у4
1ъ1у1ъ4
ъу1ю4
ъу1я4
ъъ1а4
1ъ1ъ1
ъъ1е4
ъъ1и4
ъъ1о4
ъъ1у4
1ъ1ъ1ъ4
ъъ1ю4
ъъ1я4
ъю1а4
ъ1ю1
ъю1е4
ъю1и4
ъю1о4
ъю1у4
1ъ1ю1ъ4
ъ1ю1ю4
ъю1я4
ъя1а4
ъ1я1
ъя1е4
ъя1и4
ъя1о4
ъя1у4
1ъ1я1ъ4
ъя1ю4
ъ1я1я4
ю1а1а4
ю1а1
юа1е4
юа1и4
юа1о4
юа1у4
юа1ъ4
1ю1а1ю4
юа1я4
юе1а4
ю1е1
ю1е1е4
юе1и4
юе1о4
юе1у4
юе1ъ4
1ю1е1ю4
юе1я4
юи1а4
ю1и1
юи1е4
ю1и1и4
юи1о4
юи1у4
юи1ъ4
1ю1и1ю4
юи1я4
юо1а4
ю1о1
юо1е4
юо1и4
ю1о1о4
юо1у4
юо1ъ4
1ю1о1ю4
юо1я4
юу1а4
ю1у1
юу1е4
юу1и4
юу1о4
ю1у1у4
юу1ъ4
1ю1у1ю4
юу1я4
юъ1а4
ю1ъ1
юъ1е4
юъ1и4
юъ1о4
юъ1у4
ю1ъ1ъ4
1ю1ъ1ю4
юъ1я4
юю1а4
1ю1ю1
юю1е4
юю1и4
юю1о4
юю1у4
юю1ъ4
1ю1ю1ю4
юю1я4
юя1а4
ю1я1
юя1е4
юя1и4
юя1о4
юя1у4
юя1ъ4
1ю1я1ю4
ю1я1я4
я1а1а4
я1а1
яа1е4
яа1и4
яа1о4
яа1у4
яа1ъ4
яа1ю4
1я1а1я4
яе1а4
я1е1
я1е1е4
яе1и4
яе1о4
яе1у4
яе1ъ4
яе1ю4
1я1е1я4
яи1а4
я1и1
яи1е4
я1и1и4
яи1о4
яи1у4
яи1ъ4
яи1ю4
1я1и1я4
яо1а4
я1о1
яо1е4
яо1и4
я1о1о4
яо1у4
яо1ъ4
яо1ю4
1я1о1я4
яу1а4
я1у1
яу1е4
яу1и4
яу1о4
я1у1у4
яу1ъ4
яу1ю4
1я1у1я4
яъ1а4
я1ъ1
яъ1е4
яъ1и4
яъ1о4
яъ1у4
я1ъ1ъ4
яъ1ю4
1я1ъ1я4
яю1а4
я1ю1
яю1е4
яю1и4
яю1о4
яю1у4
яю1ъ4
я1ю1ю4
1я1ю1я4
яя1а4
1я1я1
яя1е4
яя1и4
яя1о4
яя1у4
яя1ъ4
яя1ю4
1я1я1я4
й4б3б4
й4б3в2
й4б3г2
й4б3д2
й4б3ж2
й4б3з2
1й4б3й2
й4б3к2
й4б3л2
й4б3м2
й4б3н2
й4б3п2
й4б3р2
й4б3с2
й4б3т2
й4б3ф2
й4б3х2
й4б3ц2
й4б3ч2
й4б3ш2
й4б3щ2
й4в3б2
й4в3в4
й4в3г2
й4в3д2
й4в3ж2
й4в3з2
1й4в3й2
й4в3к2
й4в3л2
й4в3м2
й4в3н2
й4в3п2
й4в3р2
й4в3с2
й4в3т2
й4в3ф2
й4в3х2
й4в3ц2
й4в3ч2
й4в3ш2
й4в3щ2
й4г3б2
й4г3в2
й4г3г4
й4г3д2
й4г3ж2
й4г3з2
1й4г3й2
й4г3к2
й4г3л2
й4г3м2
й4г3н2
й4г3п2
й4г3р2
й4г3с2
й4г3т2
й4г3ф2
й4г3х2
й4г3ц2
й4г3ч2
й4г3ш2
й4г3щ2
й4д3б2
й4д3в2
й4д3г2
й4д3д4
й4д4ж1
й4д3з2
1й4д3й2
й4д3к2
й4д3л2
й4д3м2
й4д3н2
й4д3п2
й4д3р2
й4д3с2
й4д3т2
й4д3ф2
й4д3х2
й4д3ц2
й4д3ч2
й4д3ш2
й4д3щ2
й4ж3б2
й4ж3в2
й4ж3г2
й4ж3д2
й4ж3ж4
й4ж3з2
1й4ж3й2
й4ж3к2
й4ж3л2
й4ж3м2
й4ж3н2
й4ж3п2
й4ж3р2
й4ж3с2
й4ж3т2
й4ж3ф2
й4ж3х2
й4ж3ц2
й4ж3ч2
й4ж3ш2
й4ж3щ2
й4з3б2
й4з3в2
й4з3г2
й4з3д2
й4з3ж2
й4з3з4
1й4з3й2
й4з3к2
й4з3л2
й4з3м2
й4з3н2
й4з3п2
й4з3р2
й4з3с2
й4з3т2
й4з3ф2
й4з3х2
й4з3ц2
й4з3ч2
й4з3ш2
й4з3щ2
й4й3б2
й4й3в2
й4й3г2
й4й3д2
й4й3ж2
й4й3з2
4й4й4й4
й4й3к2
й4й3л2
й4й3м2
й4й3н2
й4й3п2
й4й3р2
й4й3с2
й4й3т2
й4й3ф2
й4й3х2
й4й3ц2
й4й3ч2
й4й3ш2
й4й3щ2
й4к3б2
й4к3в2
й4к3г2
й4к3д2
й4к3ж2
й4к3з2
1й4к3й2
й4к3к4
й4к3л2
й4к3м2
й4к3н2
й4к3п2
й4к3р2
й4к3с2
й4к3т2
й4к3ф2
й4к3х2
й4к3ц2
й4к3ч2
й4к3ш2
й4к3щ2
й4л3б2
й4л3в2
й4л3г2
й4л3д2
й4л3ж2
й4л3з2
1й4л3й2
й4л3к2
й4л3л4
й4л3м2
й4л3н2
й4л3п2
й4л3р2
й4л3с2
й4л3т2
й4л3ф2
й4л3х2
й4л3ц2
й4л3ч2
й4л3ш2
й4л3щ2
й4м3б2
й4м3в2
й4м3г2
й4м3д2
й4м3ж2
й4м3з2
1й4м3й2
й4м3к2
й4м3л2
й4м3м4
й4м3н2
й4м3п2
й4м3р2
й4м3с2
й4м3т2
й4м3ф2
й4м3х2
й4м3ц2
й4м3ч2
й4м3ш2
й4м3щ2
й4н3б2
й4н3в2
й4н3г2
й4н3д2
й4н3ж2
й4н3з2
1й4н3й2
й4н3к2
й4н3л2
й4н3м2
й4н3н4
й4н3п2
й4н3р2
й4н3с2
й4н3т2
й4н3ф2
й4н3х2
й4н3ц2
й4н3ч2
й4н3ш2
й4н3щ2
й4п3б2
й4п3в2
й4п3г2
й4п3д2
й4п3ж2
й4п3з2
1й4п3й2
й4п3к2
й4п3л2
й4п3м2
й4п3н2
й4п3п4
й4п3р2
й4п3с2
й4п3т2
й4п3ф2
й4п3х2
й4п3ц2
й4п3ч2
й4п3ш2
й4п3щ2
й4р3б2
й4р3в2
й4р3г2
й4р3д2
й4р3ж2
й4р3з2
1й4р3й2
й4р3к2
й4р3л2
й4р3м2
й4р3н2
й4р3п2
й4р3р4
й4р3с2
й4р3т2
й4р3ф2
й4р3х2
й4р3ц2
й4р3ч2
й4р3ш2
й4р3щ2
й4с3б2
й4с3в2
й4с3г2
й4с3д2
й4с3ж2
й4с3з2
1й4с3й2
й4с3к2
й4с3л2
й4с3м2
й4с3н2
й4с3п2
й4с3р2
й4с3с4
й4с3т2
й4с3ф2
й4с3х2
й4с3ц2
й4с3ч2
й4с3ш2
й4с3щ2
й4т3б2
й4т3в2
й4т3г2
й4т3д2
й4т3ж2
й4т3з2
1й4т3й2
й4т3к2
й4т3л2
й4т3м2
й4т3н2
й4т3п2
й4т3р2
й4т3с2
й4т3т4
й4т3ф2
й4т3х2
й4т3ц2
й4т3ч2
й4т3ш2
й4т3щ2
й4ф3б2
й4ф3в2
й4ф3г2
й4ф3д2
й4ф3ж2
й4ф3з2
1й4ф3й2
й4ф3к2
й4ф3л2
й4ф3м2
й4ф3н2
й4ф3п2
й4ф3р2
й4ф3с2
й4ф3т2
й4ф3ф4
й4ф3х2
й4ф3ц2
й4ф3ч2
й4ф3ш2
й4ф3щ2
й4х3б2
й4х3в2
й4х3г2
й4х3д2
й4х3ж2
й4х3з2
1й4х3й2
й4х3к2
й4х3л2
й4х3м2
й4х3н2
й4х3п2
й4х3р2
й4х3с2
й4х3т2
й4х3ф2
й4х3х4
й4х3ц2
й4х3ч2
й4х3ш2
й4х3щ2
й4ц3б2
й4ц3в2
й4ц3г2
й4ц3д2
й4ц3ж2
й4ц3з2
1й4ц3й2
й4ц3к2
й4ц3л2
й4ц3м2
й4ц3н2
й4ц3п2
й4ц3р2
й4ц3с2
й4ц3т2
й4ц3ф2
й4ц3х2
й4ц3ц4
й4ц3ч2
й4ц3ш2
й4ц3щ2
й4ч3б2
й4ч3в2
й4ч3г2
й4ч3д2
й4ч3ж2
й4ч3з2
1й4ч3й2
й4ч3к2
й4ч3л2
й4ч3м2
й4ч3н2
й4ч3п2
й4ч3р2
й4ч3с2
й4ч3т2
й4ч3ф2
й4ч3х2
й4ч3ц2
й4ч3ч4
й4ч3ш2
й4ч3щ2
й4ш3б2
й4ш3в2
й4ш3г2
й4ш3д2
й4ш3ж2
й4ш3з2
1й4ш3й2
й4ш3к2
й4ш3л2
й4ш3м2
й4ш3н2
й4ш3п2
й4ш3р2
й4ш3с2
й4ш3т2
й4ш3ф2
й4ш3х2
й4ш3ц2
й4ш3ч2
й4ш3ш4
й4ш3щ2
й4щ3б2
й4щ3в2
й4щ3г2
й4щ3д2
й4щ3ж2
й4щ3з2
1й4щ3й2
й4щ3к2
й4щ3л2
й4щ3м2
й4щ3н2
й4щ3п2
й4щ3р2
й4щ3с2
й4щ3т2
й4щ3ф2
й4щ3х2
й4щ3ц2
й4щ3ч2
й4щ3ш2
й4щ3щ4
б4ь
в4ь
г4ь
д4ь
ж4ь
з4ь
й4ь
к4ь
л4ь
м4ь
н4ь
п4ь
р4ь
с4ь
т4ь
ф4ь
х4ь
ц4ь
ч4ь
ш4ь
щ4ь
ь4ь
.д2з4в2
.д1
.д3з2
.д2ж4р2
.д4ж1
.д2ж4л2
.в2г4л2
.в1
.в3г2
.в2д4л2
.в3д2
.в2г4р2
.в2г4н2
.в2п4л2
.в3п2
.в2к4л2
.в3к2
.в2к4р2
.в2т4р2
.в3т2
.с2г4л2
.с1
.с3г2
.з2д4р2
.з1
.з3д2
.с2г4р2
.с2б4р2
.с3б2
.с2д4р2
.с3д2
.ж2д4р2
.ж1
.ж3д2
.с2к4л2
.с3к2
.с2п4л2
.с3п2
.с2п4р2
.с2т4р2
.с3т2
.с2к4р2
.ш2п4р2
.ш1
.ш3п2
.с2к4в2
.в2з4р2
.в3з2
.в2с4л2
.в3с2
.в2с4м2
.в2с4р2
.с2в4р2
.с3в2
.с2х4л2
.с3х2
.с2х4р2
.х2в4р2
.х1
.х3в2
.в2с4т2
.с2х4в2
.с2м4р2
.с3м2
н4кт.
н2к3т2
н4кс.
н2к3с2
к4ст.
к2с3т2
PK
!<=Mhyphenation/hyph_ca.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1ba
1be
1bi
1bo
1bu
1ca
1ce
1ci
1co
1cu
1da
1de
1di
1do
3du
1fa
1fe
1fi
1fo
1fu
1ga
1ge
1gi
1go
1gu
1ha
1he
1hi
1ho
1hu
1ja
1je
1ji
1jo
1ju
1la
1le
1li
1lo
1lu
1ma
1me
1mi
1mo
1mu
1na
1ne
3ni
1no
1nu
1pa
3pe
3pi
3po
1pu
1qu
1ra
1re
1ri
1ro
1ru
1sa
1se
1si
1so
1su
1ta
1te
1ti
1to
1tu
1va
1ve
1vi
1vo
1vu
1xa
1xe
1xi
1xo
1xu
1za
1ze
1zi
1zo
1zu
1bé
1bí
1bó
1bú
1bà
1bè
1bò
1cé
1cí
1có
1cú
1cà
1cè
1cò
1ço
1ça
1çu
1çó
1çú
1çà
1çò
1dé
1dí
1dó
1dú
1dà
1dè
1dò
1fé
1fí
1fó
1fú
1fà
1fè
1fò
1gé
1gí
1gó
1gú
1gà
1gè
1gò
1gü
1hé
1hí
1hó
1hú
1hà
1hè
1hò
1jé
1jí
1jó
1jú
1jà
1jè
1jò
1lé
1lí
1ló
1lú
1là
1lè
1lò
1mé
1mí
1mó
1mú
1mà
1mè
1mò
1né
1ní
1nó
1nú
1nà
1nè
1nò
1pé
1pí
1pó
1pú
1pà
1pè
1pò
1qü
1ré
1rí
1ró
1rú
1rà
1rè
1rò
1sé
1sí
1só
1sú
1sà
1sè
1sò
1té
1tí
1tó
1tú
1tà
1tè
1tò
1vé
1ví
1vó
1vú
1và
1vè
1vò
1xé
1xí
1xó
1xú
1xà
1xè
1xò
1zé
1zí
1zó
1zú
1zà
1zè
1zò
3l2la
1l2le
1l2li
3l2lo
1l2lu
1b2la
1b2le
1b2li
1b2lo
1b2lu
1b2ra
1b2re
1b2ri
1b2ro
1b2ru
1c2la
1c2le
1c2li
1c2lo
1c2lu
1c2ra
1c2re
1c2ri
1c2ro
1c2ru
1d2ra
1d2re
1d2ri
1d2ro
1d2ru
1f2la
1f2le
1f2li
1f2lo
1f2lu
1f2ra
1f2re
1f2ri
1f2ro
1f2ru
1g2la
1g2le
1g2li
1g2lo
1g2lu
1g2ra
1g2re
1g2ri
1g2ro
1g2ru
1p2la
1p2le
1p2li
1p2lo
1p2lu
3pr
1p2ra
1p2re
1p2ri
1p2ro
1p2ru
1t2ra
1t2re
1t2ri
1t2ro
1t2ru
1n2ya
1n2ye
1n2yi
1n2yo
1n2yu
1l2lé
1l2lí
1l2ló
1l2lú
1l2là
1l2lè
1l2lò
1b2lé
1b2lí
1b2ló
1b2lú
1b2là
1b2lè
1b2lò
1b2ré
1b2rí
1b2ró
1b2rú
1b2rà
1b2rè
1b2rò
1c2lé
1c2lí
1c2ló
1c2lú
1c2là
1c2lè
1c2lò
1c2ré
1c2rí
1c2ró
1c2rú
1c2rà
1c2rè
1c2rò
1d2ré
1d2rí
1d2ró
1d2rú
1d2rà
1d2rè
1d2rò
1f2lé
1f2lí
1f2ló
1f2lú
1f2là
1f2lè
1f2lò
1f2ré
1f2rí
1f2ró
1f2rú
1f2rà
1f2rè
1f2rò
1g2lé
1g2lí
1g2ló
1g2lú
1g2là
1g2lè
1g2lò
1g2ré
1g2rí
1g2ró
1g2rú
1g2rà
1g2rè
1g2rò
1p2lé
1p2lí
1p2ló
1p2lú
1p2là
1p2lè
1p2lò
1p2ré
1p2rí
1p2ró
1p2rú
1p2rà
1p2rè
1p2rò
1t2ré
1t2rí
1t2ró
1t2rú
1t2rà
1t2rè
1t2rò
1n2yé
1n2yí
1n2yó
1n2yú
1n2yà
1n2yè
1n2yò
a1a
a1e
a1o
e1a
e1e
e1o
i1a
i1e
i1o
o1a
o1e
o1o
u1a
u1e
u1o
a1é
a1í
a1ó
a1ú
a1à
a1è
a1ò
a1ï
a1ü
e1é
e1í
e1ó
e1ú
e1à
e1è
e1ò
e1ï
e1ü
i1é
i1í
i1ó
i1ú
i1à
i1è
i1ò
i1ï
i1ü
o1é
o1í
o1ó
o1ú
o1à
o1è
o1ò
o1ï
o1ü
u1é
u1í
u1ó
u1ú
u1à
u1è
u1ò
u1ï
u1ü
é1a
é1e
é1o
é1ï
é1ü
í1a
í1e
í1o
í1ï
í1ü
ó1a
ó1e
ó1o
ó1ï
ó1ü
ú1a
ú1e
ú1o
ú1ï
ú1ü
à1a
à1e
à1o
à1ï
à1ü
è1a
è1e
è1o
è1ï
è1ü
ò1a
ò1e
ò1o
ò1ï
ò1ü
ï1a
ï1e
ï1o
ï1é
ï1í
ï1ó
ï1ú
ï1à
ï1è
ï1ò
ï1i
ü1a
ü1e
ü1o
ü1é
ü1í
ü1ó
ü1ú
ü1à
ü1è
ü1ò
a1i2a
a1i2e
a1i2o
a1i2u
a1u2a
a1u2e
a1u2i
a1u2o
a1u2u
e1i2a
e1i2e
e1i2o
e1i2u
e1u2a
e1u2e
e1u2i
e1u2o
e1u2u
i1i2a
i1i2e
i1i2o
i1i2u
i1u2a
i1u2e
i1u2i
i1u2o
i1u2u
o1i2a
o1i2e
o1i2o
o1i2u
o1u2a
o1u2e
o1u2o
o1u2i
o1u2u
u1i2a
u1i2e
u1i2o
u1i2u
u1u2a
u1u2e
u1u2i
u1u2o
u1u2u
a1i2é
a1i2í
a1i2ó
a1i2ú
a1i2à
a1i2è
a1i2ò
a1u2é
a1u2í
a1u2ó
a1u2ú
a1u2à
a1u2è
a1u2ò
e1i2é
e1i2í
e1i2ó
e1i2ú
e1i2à
e1i2è
e1i2ò
e1u2é
e1u2í
e1u2ó
e1u2ú
e1u2à
e1u2è
e1u2ò
i1i2é
i1i2í
i1i2ó
i1i2ú
i1i2à
i1i2è
i1i2ò
i1u2é
i1u2í
i1u2ó
i1u2ú
i1u2à
i1u2è
i1u2ò
o1i2é
o1i2í
o1i2ó
o1i2ú
o1i2à
o1i2è
o1i2ò
o1u2é
o1u2í
o1u2ó
o1u2ú
o1u2à
o1u2è
o1u2ò
u1i2é
u1i2í
u1i2ó
u1i2ú
u1i2à
u1i2è
u1i2ò
u1u2é
u1u2í
u1u2ó
u1u2ú
u1u2à
u1u2è
u1u2ò
é1i2a
é1i2e
é1i2o
é1i2u
é1u2a
é1u2e
é1u2o
é1u2i
é1u2u
í1i2a
í1i2e
í1i2o
í1i2u
í1u2a
í1u2e
í1u2o
í1u2i
í1u2u
ó1i2a
ó1i2e
ó1i2o
ó1i2u
ó1u2a
ó1u2e
ó1u2o
ó1u2i
ó1u2u
ú1i2a
ú1i2e
ú1i2o
ú1i2u
ú1u2a
ú1u2e
ú1u2o
ú1u2i
ú1u2u
à1i2a
à1i2e
à1i2o
à1i2u
à1u2a
à1u2e
à1u2o
à1u2i
à1u2u
è1i2a
è1i2e
è1i2o
è1i2u
è1u2a
è1u2e
è1u2o
è1u2i
è1u2u
ò1i2a
ò1i2e
ò1i2o
ò1i2u
ò1u2a
ò1u2e
ò1u2o
ò1u2i
ò1u2u
ï1i2a
ï1i2e
ï1i2o
ï1i2é
ï1i2í
ï1i2ó
ï1i2ú
ï1i2à
ï1i2è
ï1i2ò
ï1i2u
ï1u2a
ï1u2e
ï1u2o
ï1u2é
ï1u2í
ï1u2ó
ï1u2ú
ï1u2à
ï1u2è
ï1u2ò
ï1u2i
ï1u2u
ü1i2a
ü1i2e
ü1i2o
ü1i2é
ü1i2í
ü1i2ó
ü1i2ú
ü1i2à
ü1i2è
ü1i2ò
ü1i2u
ü1u2a
ü1u2e
ü1u2o
ü1u2é
ü1u2í
ü1u2ó
ü1u2ú
ü1u2à
ü1u2è
ü1u2ò
ü1u2i
ü1u2u
.hi2a
.hi2e
.hi2o
.hi2u
.hu2a
.hu2e
.hu2i
.hu2o
.i2è
.i2ò
.u2è
.u2ò
.hi2é
.hi2ó
.hi2ú
.hi2à
.hi2è
.hi2ò
.hu2é
.hu2í
.hu2ó
.hu2à
.hu2è
.hu2ò
gu2a
gu2e
gu2i
gu2o
qu2a
qu2e
qu2i
qu2o
gu2é
gu2í
gu2ó
gu2à
gu2è
gu2ò
qu2é
qu2í
qu2ó
qu2à
qu2è
qu2ò
gü2e
gü2é
gü2í
gü2è
gü2i
qü2e
qü2é
qü2í
qü2è
qü2i
a1isme.
ais1me
e3ism
e1isme.
eis1me
i1isme.
iis1me
o1isme.
ois1me
u1isme.
uis1me
a1ista.
ais1ta
e1ista.
eis1ta
i1ista.
iis1ta
o1ista.
ois1ta
u1ista.
uis1ta
a1um.
e1um.
i1um.
o1um.
u1um.
.anti1hi2
.an1ti
.be2n
.be2s
.bi2s
.ca2p
.ce2l
.cla2r
.c2la
.co2ll
.co2n
.co2r
.de2s
.di2s
.e1n3a
.hipe2r
.hi3pe
.hiperm2n
.in3ac
.i1na
.in3ad
.in3ap
.in3es
.i1ne
.i1n3o
.inte2r
.in1te
.ma2l
.mal1t2hus
.malt1hu
.pa2n
.pe2r
.pe3ri
.pos2t
.psa2l
.p1sa
.rebe2s
.re1be
.re2d
.su2b
.su1b3o
.subde2s
.sub1de
.supe2r
.su3pe
.tran2s
.t2ra
g2no
p2si
p2se
p2neu
p1ne
g2nò
p2sí
.ch2
.th2
ein1s2tein
eins1te
ru1t2herford
rut1he
ruther1fo
ni2etz1sc2he
ni1e
3exp
3nef
3nei
3ser
a3ne
a3ri
bi3se
des3ag
de1sa
des3ar
des3av
des3enc
de1se
e3le
e3ri1o
e1ri
e3ris
es3a1co
e1sa
es3af
es3ap
es3arr
es3as
es3int
e1si
ig3n
in3ex
i1ne
n3si
o3ro
qu1i3e
s3emp
s3esp
su1b3a
ui3et
o3g2nò
.c8u9r8i8e.
.cu1ri
.curi1e
.c8u9r8i8e8s.
.g8e8i9s8h8a.
.geis1ha
.g8e8i9s8h8e8s.
.geis1he
.g8o8u8a9c8h8e.
.go1u2a
.gouac1he
.g8o8u8a9c8h8e8s.
.h8i8p9p8y.
.h8i8p9p8i8e8s.
.hip3pi
.hippi1e
.h8o8b9b8y.
.h8o8b9b8i8e8s.
.hob1bi
.hobbi1e
.j8e8e8p.
.je1e
.j8e8e8p8s.
.j8o8u8l8e.
.jou1le
.j8o8u8l8e8s.
.k8l8e8e9n8e8x.
.k1le
.kle1e
.klee1ne
.k8l8e8e9n8e8x8s.
.l8a8r9g8h8e8t9t8i.
.larg1he
.larghet1ti
.l8a8r9g8h8e8t9t8o.
.larghet1to
.l8i8e8d.
.li1e
.l8i8e8d8e8r.
.lie1de
.n8o8s9a8l9t8r8e8s.
.no1sa
.nosal1t2re
.r8o9y8a8l9t8i8e8s.
.royal1ti
.royalti1e
.r8o9y8a8l9t8y.
.v8o8s9a8l9t8r8e8s.
.vo1sa
.vosal1t2re
.w8h8i8s9k8y.
.w1hi
.w8h8i8s9k8i8e8s.
.whiski1e
PK
!<Be::hyphenation/hyph_cy.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ac4t
.ad3ae
.ad1a2
.a2d2d5as1
.add1a2
.add3o
.ad4e2g1
.ad1e
.ad4eny
.a2d1en1
.ad4fer
.ad1f2
.adl4
.ad3r
.a2e3a
.af3a
.af4a2n
.af2f3
.af4l4u
.afl2
.af5lw
.ag3w2
.a2g
.am4le
.am1
.aml2
.am3s2
.an5ad
.a2n
.an1a
.an4g3
.anghen5a
.angh2
.anghen4r2
.an2o
.anrhyd4
.an1r2
.anr1h2
.ansodd4e
.an1s2
.ansodd3
.an5te
.an3w4
.an5we
.ar4bo
.a2r1b
.ar4cha
.ar2ch
.ar5dd2e2l
.a2rd2
.ar2dd
.ared4
.ar1e
.ar4en
.ar4f2f4
.a2r2f
.ar4ge
.a2r1g2
.ar2i2
.ar3we
.ar4wed
.as3g
.as1
.as3t
.aw4e
.ban4as1
.b1a2n
.ban3a
.ban4ed
.ban3e
.bar2a5t4
.b2ar
.bel3y
.b2e2l
.be3t4a
.be2t
.bl2
.bl4e
.b2r2
.br4e
.cal2l5
.c2al
.c2e4n
.c1e
.ch2
.ch4e
.ch4l2
.ch4o
.chollad4
.ch2ol1
.chol2l1
.ch2r2
.chwyn5
.ch2wy
.cl2
.cr2
.cy5we
.c2y1w
.dad3r
.d1a2
.d2ad
.dd2
.ddef2n5
.d2d4i
.ddi5a2n
.dd1ia
.dd2i5dd
.ddi1d
.dd1i3e
.ddill5a2das1
.dd4i2l
.ddill4a
.ddil2l3
.ddill4ad1a2
.ddill5ad1e
.ddill5ad2o
.ddill5ad1w
.dd2in4
.ddiw5e2d2das1
.dd2iw2
.dd4iwe
.ddiwedd1a2
.ddiw5edd3e
.ddiw5edd3o
.ddiw5edd1w
.dd2wl3
.dd1w
.ddy5fala
.dd2y
.dd2yf1
.ddyf2al
.ddy5fal1e
.ddy5falo
.ddy5falw
.ddyl5ad4
.dd2yl
.deallad4
.d2ea
.de2al
.deall3a
.deal2l
.def2n3
.der4w
.det2h5
.d1e2t
.di5a2n
.d1i
.d1ia
.d2i5dd
.di1d
.d1i3e
.d2i3g2y
.dill5a2das1
.dill4a
.d2i2l
.dil2l3
.dill4ad1a2
.dill5ad1e
.dill5ad2o
.dill5ad1w
.d2in4
.diw5e2d2das1
.d2iw2
.d4iwe
.diwedd1a2
.diw5edd3e
.diw5edd3o
.diw5edd1w
.dr4e
.d2wl3
.d1w
.dy5fala
.d2yf1
.dyf2al
.dy5fal1e
.dy5falo
.dy5falw
.dy5f3o
.dyl5ad4
.d2yl
.dyrchafad4
.dyr4ch
.dyrcha4f3a
.eb2
.eb3r
.eb4rw
.ec2
.ed2
.edl4
.edr4
.e2g2
.eg2n3
.el4or
.e2l
.el1o
.els4
.en3as1
.eny5na
.er2
.erfy5na
.e2r2f
.erf2yn
.e2r2n4
.ewy5na
.fadd3
.falch5
.f2al
.fal2c
.fan3a
.fa2n
.farn4ais.
.fa2r2n1
.farn3a
.f2ar
.farna2i
.farna2is1
.f2as2g4
.f1as1
.fas5ge
.f2f2
.ff4a
.f2fer4a
.ffe5r1as1
.f2f2er4e
.ff4o
.ffor5t
.ff2or
.ff4y
.ffydd5
.ffyd2
.ffyn2ad4
.ff2yn
.ffy5nas3
.fig2n5
.f1i
.fis5g4
.f2is1
.fon4e2d1i
.fon1
.f2one
.for4dd4
.f4o2rd2
.for4o
.for4w
.for4y
.fr4i2
.f4ryn4d2
.f2ry
.f3ryn
.fydd5
.fyd2
.fyn5as3
.f2yn
.f2yw3
.gal3e
.g2al2
.gal5o
.gan3l2
.ga2n
.gan5ol1as1
.g4anol1
.gan5ole
.gen5as1
.ge2r5b
.g2eu5d2
.ge2u
.ghwy5na
.gh2
.gh2wy
.g2l2
.glaf5y
.glaf2
.gl4e
.gleid4
.gle2i
.gl4y
.glyn3
.glywad4
.gl4yw1
.gly2wa
.g2od3y
.gof3a
.goffad4wy
.gof4f3a
.gof2f
.goff3ad
.goffad1w
.gollad4
.g4ol2l3
.gol1
.g2r2
.grynho4em1
.gr4yn
.gry2n1h2
.grynho4wn1
.grynh2ow
.gwedd4er
.gw2
.gwedd3e
.gyd3
.gyf5al
.g2yf1
.gyf5arc
.gyf2ar
.gyfer5by3na
.gy4fe
.gyfe2r1b
.gyfer2by
.gyfer5byn1i
.gyfer5byn3n2
.gyff2e2l5
.gyf2f1
.gym3o
.g2ym1
.gyn3a
.gyn5e
.gyn1ef5
.gyth5ru
.gyt2h
.gyth2r
.gy5we
.g2yw
.h2ac4
.h2adl4
.ha4f4a
.h4af3l4
.hag2r3
.h2a2g
.ham4le
.h2am1
.haml2
.han5as1
.ha2n
.han1a
.h2an4g5
.hanghen5a
.hangh2
.han5t
.han5w4
.har5dd2e2l
.h2ar
.ha2rd2
.har2dd
.hared4
.har1e
.har4en
.ha2r3n1
.harn4a
.har3w
.h2as3g
.h1as1
.h2aw4
.h2eb2
.hec2
.hed2
.h2edl4
.h2e4o
.herfy5na
.h2e2r2f
.herf2yn
.her4w
.heur5
.he2u
.hof4r3
.hol4y
.h2ol1
.holyn5
.hw2
.hwn4
.hwyl5u
.h2wy
.h2wyl
.hwyn5a
.hwy4r5
.hwyth4a2u
.h2wyt
.hwyth3a
.hwyt2h
.hyd4
.hydr4
.hy3f2f1
.h2yf1
.hyf4od
.hyf3o
.hy5f2ry
.hy3g1
.hyl4
.hym3e
.hym1
.hym4u
.hy2m4y
.hymy5na
.hymys2g4
.hym5ys1
.hyn2
.hy3n1o
.hy3r2w
.iach4
.i2ac
.iac5h1as1
.iac5he
.iac5hw
.ir3
.ladr3
.ledr4e
.l4edr
.le3na
.l2e3o
.les2t4
.les1
.l2in3
.l1i2
.l2l2
.llaw4e
.ll2aw
.lle5na
.llo5nas1
.llon2a
.llon1
.ll2on4e
.llyth2r5
.llyt2h
.lo3n2a
.lon1
.l2on4e
.ludd3
.l2ud
.lyg2r3
.l2yg1
.lyn3a
.lyth2r5
.lyt2h
.man4ed
.m1
.man3e
.ma2n
.man4t4a
.ma2r4f
.m3ar
.mig2n5
.mis5g4
.m2is2
.mol3
.mon4e2d1i
.mon1
.m2one
.mwyth5a
.m2wyt
.mwyt2h
.mwyth5w
.myn5as3
.m2y
.neilltu4ad
.ne2i
.nei4l2l3
.ne2i2l
.neill2t4
.neill3tu
.neillt2u1a
.nei2s4i
.n2e2is1
.nen3a
.ner4w
.ng2
.ngen5as1
.nghyt5u
.ngh2
.nghy5wa
.ngh2yw
.n2g2l4
.n2g4w2
.ngy4
.ni5a2n
.n1i
.n1ia
.n3i3e
.ni5fe2i
.n2if
.n2ig2
.ni5ga
.ni3g1e
.n2i3gw2
.n2i3g1y
.ni5r4e
.nir1
.ni3w4a
.niw2
.ni2w1l5
.n2o4e
.no4w
.n2wl3
.n2wyn3
.oddefad4
.oddef3a
.od4l
.of3a
.of4o
.of4u
.og2
.og4l4
.ol2
.ol2l3
.ol5yg3a
.ol2yg1
.ol5yge
.olyn3
.or1
.ordd3ad4
.or4dd
.o2rd2
.ordd1a2
.pl4a
.p1
.p2l
.pl4e
.rad3r
.rag3l
.r2a2g1
.ra3n1a
.ra2n
.ran5d4
.r2ew3
.rh1i5a
.r1h2
.rh1i2
.r1ia4
.ri2
.rin4t
.r2in
.rug4l5
.ry3n4a
.ryn4e
.s1as4
.s1
.s1es4
.se2
.s2t2
.sych3
.s2y
.s3yc
.sych5e
.tal3ad4
.t2al
.tan4e
.t1a2n
.t2h2
.th2a2g5
.th4i2
.tho5e
.th1o
.th4r4
.thrad4
.th4u
.torad4
.tor2a
.tr2
.tr4a
.trad4
.tr4o
.tro4en3
.uch2
.wa5r4as1
.w2ar
.war4es1
.war3e
.wedd4er
.wedd3e
.weithiad4
.we2i
.w2eit
.we2it2h1
.weith3i2
.weith1ia
.welad4
.w2e2l
.wel1a
.wen3a
.wes2t4
.wes1
.wn4io
.wn1
.wn1i
.wob2r3
.w1o
.w2o2b
.wyb2r4
.w2yb
.wy3by
.wy4r
.w2yw3
.ydd4
.yd4l
.yf4ar
.yf1
.ym4ad1w
.ym1
.ym3e
.ym4yl
.y2m3y
.ymys2g4
.ym3ys1
.yn4d2
.ys4b2
.ys1
.ys2g4
.y2s4i
.ys4n
.ys4t
a1a
a3ar2
2ab
ab3a
ab4ad
a2b3ed
ab3e2l
ab5in2e
a2b1i
ab2in
abl1
a2b1o
ab4or
ab2r3
a1bu
a4bu.
a4bum1
2ac
ac1a
ac5ad1e
ac1ad
aca2n3
ac4aol1
ac3e2i
ac1e
ace3ne
ac2en
ac5enni.
acen1n2
acenn1i2
ach1
a4ch.
2ach5ac
ach5aw
a5ch2ef
ach3o
ach3r
a4ch5us1
ach2u
a3ch2w2a
achwyn5
ach2wy
achy4
aci5mw
ac1i
ac2im2
acl3
ac3o
ac3t2a
ac3te
4ad3ac
ad1a2
ad3ad
ad5afa
ad3a2r2f2
ad2ar
ada2r4g2
a4dd.
add3ad
add1a2
ad3d2al
ad3dd2
add3e2u1
add5ew
add3f2
ad2d3i
add2o
ad4du
a4dd3un4
a4dd3yc
add2y
add3ys1
2ad1e
ad3e2g1
ad3e2l
ad4el.
ad4ena
a2d1en1
ad4ene
ad4en1i
ad4en3o
ad4enw
a2d3i
2adl
ad3len
ad5lys1
ad2na
ad1n
adnabyd4d4e
ad3n2a3b2
adna2by
adnabydd3
adnabydd1ed4
ad2no
2ad1o
ad3oc
ad3od
ad3of
ad3on1
4a2dr.
ad4r3ed
ad3ri2
adr4od
adr3on1
ad4ru
4ad4un1ia
adun1
adun3i
ad5u4ni4ad3
ad5uro
ad1ur1
adwel4ed.
ad1w
adw2e2l
adwel1e
adwel3ed
ad3wi
ad5wr.
3ad3wys1
ad5wyt
ad1y
ady4n
ad4y2r2n3
2a2ea
ae4ad1a2
ae1ad
ae5an.
ae1a2n3
aedd3
ae3i
a2e2l1
ael4edd1a2
ael1e
ael3ed
aen3
ae3oc
a2eo
ae3og1
aer1
aerw4
aer5w3e
aer5w2y
aes3
aes2t4
aeth5a
ae2t
aet2h1
aeth4r4
a2e1w
ae5wyd
af3ad1w
4af5aid
af1a2i
af4al
af3a2n
afan5e
af4an1n2
4af2ar3
a3f5a2r2n1
af4a2t
4af3a2u
2af3e
2af2f
aff3a
aff3ed
aff3e2i
a2ff3i
aff2l3
aff3w
aff3y
aff4yr
af3i
afl3a
afl2
afl5e2d1i
af4l3u
2af2n1
af3odd3
4afol3
af3on2t
afon1
2afr
af3r2a
af3res1
afre2
af5r2if1
afri2
af4ru
af5rw2y
afr2w
af1u
2af1w
af1y
2a2g
ag1a
ag3ad1
a1g3al2
age3na
age5ne
ag3l2aw
ag2l
agl3o
ag3lu
agl3w
ag3n
ag3od
ag3of
ag4or3i2
ag2or
ag2r
ag3r2i2
ag3ry
ag1u
ag2w2
ag3wa
ag3w2e2l
ag3wn1
ag3wr
ag5yma
ag2ym1
a3g2y4w
a1h2
aha2n3
ahan3ad4
ahan1a
ahedr4
a2i
2aig
2a2i2l1
ailen3
2a2in
4ain2c
2ait
2al
al5ab2r3
al2ab
al3ad
4al3ae
alaf3
4alaid
al1a2i
al3a2n
al5arc
al2ar
al5aso
al1as1
al3c1e
al2c
alch3w
alch1
al5c2wl
alc1w
al4d3i
a2l1d2
al1e
al5e2da2u
al2ed3a2
al3e2i
al3en
al4ena
al4es.
ales1
al2fo
alf2
al3fy
al3i2
al4is1
all3a
al2l
all3e
all3i2
al2l3oc
all3w2
all3y
3al1n
a2l3oc
a2l3od
al4og1ia
al1og3
alog3i
alo3n2a
alon1
al2o3n4e
al3ono
al3or
alp4e
alp1
al1u
4al1w2c
alw3e
4al1wr1
al5ws2t
alws1
al3wy
4alwy.
al1y
2am1
ambl3
am3d2
amda2n5
amd1a2
amel5o
am2e2l
am3er
amgym5r2
amg2
am1g2ym4
amhen4
amh2
amho4b4l4
amh2o2b
amhr2yd4
amh2r
am5l1as1
aml2
am4led
am4lf2
am4l1g2
am5n2if
am2n
amn1i
am4of
am2or2
amor5w
am4p2a
am2p1
a4m3w2a
am5wed
am5wr3i2
amwr2
am5wyd
am3wyt
amyn3
a2m2y
a2n
2an.
an1a
a4n2a3b2
an3adl3
an2ad
a4n1ae
a4naf
an4af3i
a4na2i
an2as1
a1n3a2t4
a4na2u3
a4n2aw
4anco
an2c
an2d1a2
an1d2
an5d4d1a2
andd2
an4dd2y
an2de
an2d1o
an1e
an2ed
an3ed.
an5ed3a2
an5edd.
an5ed1o
a4n3e2g1
a4n2e2l
an3el1i2
an5er.
an5e2wi
a3n2ew
2an1f2
an3fy
2ang1
ang3ad1
an4g1d2
ang3e
an4g1f2
angh2af2f4
angh2
anghelf4
angh2e2l
anghred2ad4
angh2r2
anghr2ed3a2
anghris2t4
anghr2i2
anghr2is1
anghy4
anghyd4
anghydna4
anghyd1n
angh2yf4
anghyf2ar4
anghy4f3a
anghyf2f4
anghyfiawn4
anghyf3i
anghyf1ia
anghyfi2aw
anghym4
anghyn4e
an2g2l4
ang5or
an3if
an1i
an3ig3i
an2ig
4anna2u3
an1n2
an3oc
an3od.
an3odd
an5og.
an1og1
an5og4ae
4anol1
an3ol.
an3om3
an3on2t
anon1
an3or
1an1r2
an5s4ic
an1s2
an2s3i
ans4ie
ans4iw
an5s3iwn1
an4ta
an1t5ac
an5th1e
ant2h2
an2t3rw
an1tr
an1w
an3wes1
4an2wl
anwy4
an3wyd
an2wyn3
an1y
an1y4l3
a1oe
ap3a
ap1
ap4cy
ap5elw
ap2e2l
ap3l
apl4a
ap5os2t
ap2os1
ap5rwn1
ap2r2
ap5ryn
ap5wl2l
apw2
ap2wl
2ar
ar3ab
aradr3
ar2ae3
ar3af.
ar4a5f2f
ar3a2n
ar4an.
aran3a
aran3e
ar2a2t4
ar3aw
ar4b4er
a2r1b
arc3as1
arc2a
arch5en
ar2ch
ar5clo
ar2d1a2
a2rd2
ar2de
ar2d5es1
ar4dr
ardy4n3
ar1dy
ar1e
ar2eb
ar2e2i
ar5eid
ar3eit
ar3fa
a2r2f
arfa2n5
arf5ed
ar5f2e2l3
ar4f2f
ar3fod
ar1i2
ar4i4a2n
a2r1ia
ar2m2
4arn.
a2r2n1
arn5adwy.
arn2ad
arnad3w
ar4n1d2
3ar2n1h2
ar4no
ar4nw
ar4n3y
ar1o
ar4od.
ar4o2d3i
ar2og2l3
arog1
3aror
ar3os1
5ar4os.
ar4p3as1
arp2
ar1p2a
arp3w2
ar3s2y
ar1s2
ar3te
ar4t2h3
ar3ug
ar3ut
aru5w2c
ar2u1w
3arw1a2i
ar2w2a
ar5wch1
ar1w2c
ar2wd2
arw5der
arwd2e
ar3wed
3ar3w1i
arw3n1
ar3wn.
ar3w4n1i
ar3w2y
4arwyn3
ar3y
2as.
as1
2as1as1
as5awr1
as2aw
1as2b2
as5boe
2asd
2ased
ase2
as4en1n2
2asf
2as2g
as5g2al2
asg2l3
asg4od3a2
asg3od
as3gwr
asgw2
asg3wy
2as1ia
a2si
4as3ie
2a2sl2
2asn
as4ny
as4od.
2as3oe
2asr
2as2t
as4t2al
as3t2a2n
ast2a2t5
as3te
as4tl
as4tr
as5tr1us1
ast2w2
as5ty.
as3tyl
astyn3
2as2u
as3ur3
as5wir3
a2s2wi
2aswr
2as2y
as5yn3n2
2a2t
at3ad
at5alf2
at2al
ateb3
at3em1
ath3a
at2h
athl3
ath3o
ath4r3e
ath2r
athr3w
ath4r5yc
ath3w
ath3y
ato2i
at3ol1
a3tô
at3ra2n
atr5oc
at3rod
atro5e
atr5yc
at3wy
atw2
aty3na
aty5ne
a2u
2aul
2aw
a2w1a
aw5art
aw2ar
aw5chw
aw2c
awch1
aw5ddr2
a2wd
aw5dry4
awdr1
aw3ed
aw3e2i
aw3e2l
a2w3es1
a2w3f2
aw3ga
awg1
a2w1i
awl5ed
a2wl
awn3a
awn1
awr1
a2w2r3d2
awy4r3
3áu.
1â
ba2b4i
b2ab
bab5yd
ba2by
b3ac
bach3
badd3
b3ad1w
1bae
2bae2t
b1af
b1a2i
b1a2n
ban3a
ban3e
b4an1n2
ban3o
5bar2ch
b2ar
ba2r4f
bar4lys.
bar1l2
barlys1
ba2r2n3
bar4w2y
b1as1
b2as3g2
b2as2t4
bat4a
b2a2t
b4at2h
b1a2u
bawd4a2
b2aw
ba2wd
bawe5na
b1d
b1ec
2bed
beir4a
be2i
beir1
be4i1w
b1em1
be4n4ae
be3nas1
be5ned
ben2g2l4
ben1s4
bent4
b3ent.
ben3w
benwy5na
ben2wyn
b3ert
b3e2si
bes1
b1g2al4
2b1i
b3ia
bi5a2idd
bia2i
3b2ib1
b3id3
b3ie
3b2ig1
b4in2c
b2in
bin2e
b3io
b3ir1
bis2g4
b2is1
b3it
bl3af
bl5air1
bl1a2i
bla3n1a
bla2n
bla5ne2d1i
bl2an1e
blan2ed
bla5n4es1
2b2l3a2u
bl5a2wd
bl2aw
bl3ec
bl4enni.
blen1n2
blenn1i2
bl2ew3
4bl1wr1
b4lyc
4blyn
bl5yn.
bo4b4l
b2o2b
b1oc
4b3odd
bol3
b1om1
b2on1
b2on4d2
b2r
bra3n1a
bra2n
br3ed
breg3y
bre2g1
br3em1
br4en1n2
br2i2
br4i2l
br3ir1
brod4iae
bro2d3i
brod1ia
brog4
br4wd
br2yf3
b4ryn4d2
b1s2
bse3na
bse2
bse5ne
2bu.
1b2u1a
budd4l2
budd1
bu4lo
3b2u1o
b2w3a
b1w2c
3bwl2l
b2wl
b1wn1
b4wn1s2
bwr1
4bwyd
b3w2yd.
4b3w2yf1
bwyll2t4
b2wyl
bwyl2l
3bwyn
bwy4r3
2by
b3ych.
bych1
byd4d5i4
bydd3
b2yl
3bylla
byl2l
by3na
by3ned
by3nes1
by2r2f4
b4yr1w
3byst.
bys1
bys2t
b2yw3
cabl4en
c2ab
cabl1
c1ad
cad3a2
c2ad3l
c2a2e4a
caethiw4ed
cae2t
caet2h1
caeth3i2
c1af
c3a2i
cal3e
c2al
cal3o
cam4en1n2
c2am1
cam2n4
can3a
ca2n
ca4n1e
canghe5na
c2ang1
cangh2
can3l2
c4an1n2
can5ol1as1
c4anol1
can5ole
c3ant
can4yd
can1y
car4en
c2ar
car1e
car4ped.
carp2
car1pe
c1as1
casg4e
c2as2g
3c2a2t
ca4t3r
c3a2u
c3áu
c1b
cd2
c1e
c3ed
c5ed3ig
ce2d1i
ce2g3
c3e2l
c2en
ce3na
c3ent
cer5by
ce2r1b
c2e2r4f
cer3y
ceu4l
ce2u
c3f2f
3chae
ch3af.
ch4af1b2
ch4af3i
chan3a
cha2n
changhe5na
ch2ang1
changh2
char4en1n2
ch2ar
char1e
chasg4e
ch1as1
ch2as2g
chdr5y
ch1d2
ch3eba
ch2eb
ch3eb1e
ch3e2b1i
ch3eb1o
ch3eb1w
ch3ech1
ch3ed.
ch3e2d1i
5che2in
ch2e2i2
c4helad4
ch2e2l
chel1a
ch3ent
chen3y
ch3er.
ch2e2r4f
ch3es2g
ches1
3ches2t
4chest1io
ches2t1i
4chest5ol1
chest2o
4chest2wa
chestw2
4chestwe
ch3eta
ch2e2t
ch5ig.
ch1i2
ch2ig1
chleid4
chl2
chle2i
chl5ent
chl4en
4ch5myn1i
ch1m2
ch2m2y
4chmynnol1
chmyn3n2
ch2n5e2g1
chn2
ch2o2b3
cho4bl4
ch3odd
chon5ad4
chon1
ch3on2t
chon4y
chra4
ch2r
ch4r2o
4chu.
ch2u
ch4ub1
4chus1
5chwant
ch2w2a
chwa2n
ch3wch1
chw2c
ch2w4f2
ch4w2i
ch3wn.
chwn1
ch3w2yf1
ch2wy
chyd3
chym4a2n
chym1
ch4yn3n2
chys2g3
chys1
ch2ys5o
chyt3u
chy5wa
ch2yw
c1i
c2ib3
cig1
c3in
c2i3od.
c1io
cl2e
cleid4
cle2i
cl2i2
c1l2l
cllon3
cloe4
cl2w
cl4wm1
cl2y4w1
clywad4
cly2wa
cn2
cno4en3
cn2oe
cn4yw
c2o2b1
co4bl4
c1oc
c1od
cod4l
coffad4wy
cof4f3a
cof2f
coff3ad
coffad1w
collad4
col1
col2l1
c1om1
c1on1
con4y
corn4a2n
corn3a
co2r2n1
cos2b3
c2os1
cr3ae
cr2a4m1
3cr2a2t
cred2ad4
cr2ed3a2
cr4e2l
c2r3ie
cri2
cring4
cr2in
cr2of4
crog3
cron4a
cron1
cro5nas1
cr2on4e
cryg3
cryg2l4
cr4yl
cr4ym1
crynho4em1
cry2n1h2
crynho4i
cs3a
cs1
c3s4aw
cs3yn
cs2y
ct2a
c4te2g1
ct4id3
c2t1i
c1to
c3tor3
c3tr
1cu
2cus1
c1w
c3wa
cw4as4t3
cwas1
cw4fa
c2wf2
c2wm3
cwn4ed
cwn1
c3wy
c4wyn3
cwy4r
cy2b3y
c2yb
2c1yc
cych3
cyd3
cyd1l4
cydr4
cy4f3a
c2yf1
cyfer3
cyff2e2l5
cyf2f1
c1yl
cyll5a
cyl2l
cym3
cym4a2n
cym4er1o
cym2er
c1yn
cyn3a
c5yn3a2u3
cyn3e
cyn1ef3
c2yn1y
c2y4se2
cys1
cys2g3
cys5on3
c2yso
cys3t
c2ys3w
cyth5r4ud
cyt2h
cyth2r
c2y1w
cy3wa
cy3wi
cy3wy
d1a2
dach3
d2ac
d3a4ch.
d5achwr
d2ad
dad3u
dad3w
d5adwy
dae5ara
d2a2ea
dae2ar1
dae5ared
daear1e
dae5ar1i2
dae5arw
d2a2e2l1
d4af3e
d4afo
d2a2g1
dag3w2
4d2a2i2l1
da2i
da5i2on1
da1io
d4ait
d4al.
d2al
d4a2l1d2
d4al1n
d4al1r2
d2an3a
da2n
d2an3e
d2an3f2
d2ano
d2an1u
d2an1w
d2an1y
dar3a
d2ar
dar4an3a
dar3a2n
da2r4d2
da2r2f2
d5arne
da2r2n1
dar3w
d5ar2yd
dar3y
2das1
2da2u
2d2aw
da2wd3
d5awd.
d1b
ddad3r
dd1a2
dd2ad
4ddaf
3dd2ang1
dda2n
dd4an1i
dd3ar3a
dd2ar
dd3ar1i2
dd3ar3w
2d2das1
d2d4aw
ddd2
d4dd1a2
ddd4e
dd4d4i4
dd3dr2
dd4du
dd4d2y
dd5dy.
dd3er.
ddet2h5
dd1e2t
ddeuad4
dd1e2u1
dde2u3a
dd4eu1g
dd4ew
dd2f2
dd4f1g
2ddi.
d2d1i
dd4ic
dd4if
ddif3a
dd4ig.
dd2i3g2y
dd4i2l
dd4im2
dd4in1i
dd2in
4ddit
dd1l2
dd5len
2ddo.
dd1o
4ddoc
dd3odd
4dd3om1
4ddon3
dd2or3
ddr2
ddr4a
ddr4e
ddr4i2
ddr4o
ddr4w
dd4ry
d2du
4dd3un1
dd5us.
dd1us1
dd5w2aw
dd1w
dd2w2a
4dd3w2c
dd2we
4ddwn1
5dd2w2r2n1
dd4wyn3
3dd2wyr
dd2y
4dd1yc
d4d4ydd3
dd1yd
dd5yf.
dd2yf1
ddym4d2
dd1ym1
dd4yn
ddy5n2ad
d3dyna
5ddyn1i
4ddynt1
3ddyr
3deb.
deb2r3
d1ec
dech4a
dech1
d1ed
d5edd.
d2edd2f3
def3a
d1e2g1
d5eg3ol.
d2ego
degol1
de1h
dehe2u5
d2e2i2l
de2i
d4e2im2
delf2f5
d2e2l
delf2
d3ella
del2l1
d3elle
d3ell1i2
d3ello
dell2t5
d3ellw2
del3o
d1em1
d3em.
2d1en1
d4eng
d3ent
d2e2o
d2e2r3f
derf2yn5
2d1es1
d3e2si
5d4est2u
des2t
d1e2t
det5a2n
deth4o
det2h1
d1e2u1
deul4
deu4ny
d2eun2
d4eut
d1f2
d3f4ae
df4an1n2
dfa2n
df4ao
d4fa5ol1
df4a2t
df4aw
df2ed5r
d3fe2i
dfe5ne
d5f2e2r2f
d4f4er3y
dff4y
df2f
d2f1i
df4id
df4od
d4f3ol3
df4ry
d2fu
dfwr2
d4fyd2
dfyn3a
df2yn
dfyn4ed.
d1g2
dg2am2
dga4n5e
dga2n
dg4e2i
dg2l2
dgrynho5
dg2r
dgr4yn
dgry2n1h2
dgyf5ar
d3g2yf1
d1h2
dha5ol1
dheg2l5
dh2e2g1
2d1i
di5ach2
d1ia
di2ac
d2i1b2
dibr2yd4
dib2r
di1d
d2id2e
di5d1en1
d4i2d2o
di5d2os3
di4e2t
d1ie
di3e2u
dif4a2n
d2if
di5f4a2t
di3fe
di3ffr
dif2f
di5fl1i2
difl2
di5flo
di5fr2a
di3fw
di5g2ab
dig2e
di5g2e2l
di3gen
dige5na
dige5ne
dig2l4
di5gof
d2igo
di3gry
d2ig2r
d2i3gw2
d2ig2y
di3g2ym4
di3g2ys4
dil4a
d2i2l
dil4e
di5lec
di5les1
dill4a
dil2l3
di3lu
dil2w
di3lys1
d3in.
d2in
di5niw4
din1i
di3nod
din1o
d4in2oe
di3or
d1io
d2ir1
d3ir.
di3r2a
d4i3r2e
di5r2if1
d2iri2
3d4ir3o
di4r3w
di3rym1
dir2y
dis3g
d2is1
di3s2o
dis3t2
di3s2w
di3s2y
4d3it.
d2iw2
d4iw.
di3w4a
d4iwe
di5wen
d3iw4yf1
d1iwy
d1ï
d3l4ad4d3
dladr3
dl3af
d3l2am3
d4l3a2u
dl3ed
d3l4e2i
d4len.
dle3na
dle5ne
d4l3ent
dl3er
d3l2ew
d2lo
d2l3oc
d2l3od
d3lon3
dl2on5e
d1lu
dludd3
dl2ud
d2lw
d2l3yc
d3l4yd.
dlyd2
d3lyn
dl4yr
d3l2yw1
d1m2
dm4ar
dm4er
dm4od
d3m2y
d1n
d3nap1
dn3as1
d3n2aw
dn2eb4
d2n3ed
dn3es1
d2n1i
d3ni.
dno2i
d3n2os3
d2n1w
d2ny
d1o
d3och
d2od.
d2o2d3i
d4od1ia
d2od3r
2doe
do4en3
d2of
dof5ydd1io
dofyd2
dofydd3
dofyd4d1i4
d3ol.
dol1
d4ol2l1
dol4wg1
d3om1
don2a
don1
d2on2e
d3on2t
dor2a
dor4d1a2
do2rd2
dor5w4e
dor1w
d2os3
do2w
2dr.
3dra.
dr3ad.
dr3ad1a2
dr4add3
dr3ad1w
dr3a4f
dr5aid
dr1a2i
dr5a2in
dram4g2
dr2am1
dr4an.
dra2n
dra3n1a
dra3n2e
dr4an1n2
dr3ant
dr5au.
d2r1a2u
dr3c
dr3ed
dr4ed1o
dr3en
d4re2u
dr2ew3
d2r3f
drf2f4
dr4i2a2u
dri2
d2r1ia
d4r3id
d4r1ir1
d4roe
dro3es3
4drog1
dro4g4e
dr3ol3
dr5ol.
dr3on.
dron1
dron3a
dr3on2t
d4r2wg1
dr3wn1
dr3w2yf1
drw2y
dr3yd
dr3yn.
d3r2yw
d1s2
d1ug1
d4un.
dun1
dun3a
d4un1ia
dun3i
d1ur1
d1us1
1dut
du5wch3
d2u1w
du2w2c
d1w
d2w2a
d1w3ad1w
d3wae
dw3af
d3w4ait
dw1a2i
d3w2al
dw3a2n
dw3as1
d2wb3
dwbl4
d3w2c
d2wd2
d4w3e2d1i
d2wen
dwer5y
d4w3id
d2wi
d4w3ir3
d4w1it
dw5mig
d2wm1
dw2m2i
d2w3o
dwr3e
d2w2r2n1
dw2y3b
d4w1yc
dwyn3
dwy4on.
d2wy1o
dwyon1
d2wyr
3dy.
d1yc
d5ych.
dych1
d1yd
d3yd.
4dydd3
d2yd2w
dydw5y
dyf5a2n
d2yf1
dyf2f4
dyf4n3
dyf4od
dyf3o
dyf5odd3
dy5fo2d3i
dyf2r3
dyfrad4
dyfr2a
d3yg.
dyg1
d3yg2l
dy3gy
d2yl
dyl5ad
dy3la2n3
dyl2l3
dy3lu
d1ym1
dym3a2g5
dym5od
3dyna
dy3nas3
dy3nes1
dy3n4od
dyn1o
dy3r2a
dyr2e
dy3ri2
dy5r2yd
dyr1y
2dys.
dys1
4d2ysa
dys4g
dysg5a
4d2yso
2ea
e1ad
e4ad1f2
e4adl
eaf1
e3af.
ea4fa
e4af1g
e1a2i
e1a2n3
e4ang1
e2ar1
ea2r2f2
ear5fo
earn4i
ea2r2n1
e1as1
e1a2u
e3aw
eb3ad
eb5ar.
eb2ar
eb1e
ebl2
eb1o
eb3on2t
eb2on1
ebra5n2e
eb2r
ebra2n
ebr3e
eb4r3i2
ebr3o
eb1w
eb3wy
eb5yl2l
e2by
eb2yl
2ec1a
2ec3e
ech1
ech5od
ech2r4
ech3ry
ech5w2e
ech5wyd
ech2wy
echwy5na
echwy5ne
2eco
ec1on2
eco5no
ec5o2rd2
ecr1
2ect
ec4to
2ec1w
ec3y
2ed3a2
ed4al
edd3ad
edd1a2
edd3al
edd3ar
edd3e
2edd2f2
eddf3a
edd2f5i
eddf3o
eddf3w
eddf3y
4edd1g2
edd3o
e4dd3yc
edd2y
edd3yg1
edd5yla
edd2yl
edd5yled
eddyl1e
edd5ylo
edd5ylw
edd5yn
ed1e
ed3e2g1
ed2e2i
eden5a
e2d1en1
ed3fa
ed1f2
ed3fe
ed3f1i
edf4w
4ed1ia
e2d1i
ed2i4f
ed3ig
ed3i4n
ed3ir1
ed3iw2
3ed1ï
2edl1
ed4lo
4e2dr.
edr3e
edr3o
edr4yd
2ed1w
ed2we
ed2w3en
edw5lw
ed2wl
ed3wy4
ed3y
ed1ym4
2e1e
ef5adwy.
efad1w
ef3a2n
ef5an.
ef3ar3
ef3a2u
ef1e
efer2
eff4e
ef2f
eff3r4
eff5re2
effro4er3
effr2o
effro2e
eff3y
ef3id
ef1i
ef3ig
ef2l3
ef4lo
ef2n1
ef5n2os3
ef1o
ef4od1o
ef2r
efr3e2
ef4ri2
ef4ry
ef4us1
ef1w
efydd2ad5
efyd2
efydd3
efydd1a2
efy3na
ef2yn
efy5ne
e2g1
4eg5an.
ega2n
eg4an3a
eg2ar3
egeir4
ege2i
eg5el2l1
eg2e2l
4egen.
2eg1f2
eg5ig.
eg1i
eg2ig1
egl3a
eg2l
egl3e
egl3o
2ego
eg4on.
egon1
4e1g2os1
eg5os.
egr3a
eg2r
egr3e
egr3i2
egr3o
egr3w
eg3ry
eg4r3yc
eg2u
eg3yr
e1ho
e1hy
e2i
2e1i1a
eiaf3
ei5afr
ei3bre
e2ib
eib2r
eich3
e2ic
eid2al5
e2id1a2
e2idd3
eidd5y
ei5der4
e2ide
eidl2
eid5la
2e2idr
eidr5o
e1i1e
2eig
eigl5ad
eig2l
eig5len1n2
eigl2e
eigl3w
e2i4g2r
3eilad
e2i2l
eil3a
4eiladwy.
eilad1w
eil5ec
eil5e2g1
eil3es1
ei4l2l3
ein2a
e2in
ein2d5i
ein1d2
ein4drw
4ein3f2
eing4a
ein5io
ein1i
4ein1l2
4ein1y
2e1i1o
ei3on2t
ei2on1
eir3y
eir1
2eit
eith5e
e2it2h1
ei1w
e1i3y
2e2l
el1a
el5a2in
el1a2i
el2an5e
ela2n
el4co
el2c
el1e
el3ed
el4en3o
el4er1a
el4er1e
el5f2ar
elf2
el5fed
elgr2i5
el1g2
elg2r
3e2l1h2
el5if2f
el1i2
e1l2if1
4elig
el2l1
ell5ac
ellt4ir1
ell2t
ell2t1i
ell5wy
ellw2
ell3y
el2m3
el5m4yn
el2m2y
el1o
e2l2od
el3odd3
4el1og3
el4oga
el2ri2
el1r2
el4w1i
el3wy
el5yb3ia
el2yb3
ely2b1i
el5yb2r1
e2l3yc
4elyd2
el5yd.
e2l3ydd3
elyn3
el3yna
el5yned
el2yng4
el3yn3n2
el3yr
el3ys1
el4ys2g
el4ys2t
em5a2in
em1
e2ma2i
em4a2t
2em3e
2emo
em4os2
2em2p1
emp2r3
em5ryn
emr2
2em2t
em5t1as4
emt2a
2e2m2y
en5ad1a2
en2ad
e4n3ad3u
e4n1ae
en3af
e4n2a2g
en5ago
en3a2i
en3a2n
e4n3ar3
ena2r4g2
e4n3aw
en5b2yl
en1b2
en2by
en3c
en4ct1
en4cy
2en1d2
en3di4g3
en2d1i
endr4
en3ec
en3ed.
en5edd
en3e2l
en3em1
en3en
en3er
en3es2t
enes1
en3e2u
e4n2ew
enew5y
en5fyd2
en1f2
en2fy
eng3h2
en4g1i
en2g2l3
en5gl4og3
en5ise2
en1i
en2is1
en3it
en3o
en4ol3i2
enol1
4en2t1i
ent4ir1
en3tr
ent4wr1
ent1w2
4enty
en5t2y1a
en5uch1
en1u
en2uc
enw3ad
en2wa
en3w2c
en3wn1
en3wr
en3wyd
en3w2yf1
en3yc
en5ych.
enych3
en5ychase2
enych1as1
en5ych1ia
enych1i2
en4yg1
2eo
e5och.
e1od
e1oe
e4olae
eol1
e4olaid
eol1a2i
e4o2l3a2u
e1om1
e1on1
eor3
ep5ach1
ep1
ep2a
ep2ac
ep3l
er1a
er5a2in
er1a2i
er2c
erc3a
er4ch
4erco
2e2r2d2
er3de
er1d3y
er1e
2e2r2f
er5fa2n
er1fa
er4f5a2u
er3fed
er3f2f
er4fl2
er4fu
er3fyd2
er3g2l
e2r1g2
er2gy
er3i2
er4ic
er4i2l
er2in3
er5ir.
er1ir1
er5it.
er2l2
er5lys1
er4md2
er1m2
er4mw
er4m2y
er3na
e2r2n1
ern4i
er5ni5as1
ern1ia
er5n4yw
ern3y
er1o
4er2o2b
erog4
4erol3
er5ol3i2
er4ony
eron1
er2se2
er1s2
er5se2i
2ert
er2w3a
er4w3e
er4wl
er3wn1
er4wre
er1wr
er3w2y
er4w1yc
er4wydd3
er3yc
er3ydd3
er2yd
er3yg1
er3yl
eryl3e
er4yl2l
er3yn
eryn4a
eryn4e
es3a
es1
es3ba
es2b2
es3e2
es5g2ar
es2g
es4ge
es4g2n
es4g3w2
es4gyn
es3n
es4n2e
es4t3a
es2t
es5t4am1
est3er
2est3f
2estl
est5ol1
est2o
4est2u
es5ty2ll.
estyl2l
esty5na
esty5ne
2es2u
esurad4
esur3
es4yd.
es2y
es3yd
es3yn3
e2t
e1t3ac
et3ad
e3tae
e4t5eg.
ete2g1
eter4
et3er.
et2h1
eth3e
eth3i2
eth4le
ethl2
eth3os1
eth1o
eth4r3
eth3w
et5ir3o
e2t1i
etir1
et1o
et5re.
et5swy
ets2
et1w2
4et1wr1
ety5wy
et2yw
e2u
e2u3a
4eua2u
2eu1b2
2eud2
eu3d1a2
eu3d4e
eu2d4i
2eu1f
eu1g
eul2l4
eu5lys1
2eun2
eu5na2n
eu5n2os3
eu5nyd4d1i4
eunydd3
eu5s2i2l
eus1
eu2si
eus3t
eu4t2h
eu4tu
e2u3w
2ew
e2w1a
e2w3d
ew1e
ew3g1
e2wg2l4
ewg4w2
ew3ir3
e2wi
ew2is3
e2wl1
e2w3o
ew5p2ar
e2wp1
ewp2a
e3w2yd.
e3w2yf1
2ey
e1yc
ey4en
ey1e
1ë
3fa.
f2ab3
fa2b4i
fach3
f2ac
fac4w
fadd2
fad4e2i
f2ad1e
fad4r3
fa2e2l3
f1af
3f2a2g
fa2g4d2
fag2l3
f1a2i
falch4
f2al
fal2c
f4al5on1
f4al1u
f3am1
f4an.
fa2n
fan3d2
fan5edd
fan1e
fan2ed
fan4es1
f3an1f2
fan3o
fant2
3fa1oe
far3a
f2ar
far4ch3
4far2e
f3a2r2f
far4fa
far4l2
3fa2r2n1
farn3a
f3arp2
f3art
f4arwe
f3ar3w2y
f1as1
fas4iw
fa2si
f3a2t
fat4o
fawd4a2
f2aw
fa2wd
3fawr1
f1b2
f1d2
fdd2
f2d1w
fd5wr.
f4eb.
feb2r3
f1ec
fed4n
f2edr
3f2e1i1a
fe2i
3fe1i1e
f2e4i1o
feiriad4u
feir1
fe2iri2
fei4r1ia
feiriad3
fe2it2h3
f2eit
fe4i1w
f4el.
f2e2l
f3el2l1
fel5yn1o
felyn3
f1em1
fe3na
feng3
fent4
fen3tr5
fenw3
fen3y
2fer1a
ferch4er
fer2c
fer4ch
fer2dd4
f2e2r2d2
2f2er1e
2f2er3i2
fer4in3
2f2er1o
f2erw
fer4w5yc
fer3w2y
f4er3y
f1es1
fet2h3
fe2t
f4eth.
f4etha
fe2u1
3f2ey
f2f
ff3ad
ff3ant2
ffa2n
ff4a2t
ff3a2u
ff3ed.
ff5ed3ig
ffe2d1i
ff5e4i1o
ffe2i
ff5el.
ff2e2l
ffen5ed
ff3ent4
ff3er.
3ff2ert
ff3es2u
ff1es1
ffe2t4
2ff1i
ffidl5
ff2id
ff2l2
ff4la
ffl4ac
ff4lo
ff5l1og3
ff5l2os1
ff3n
ff3od
ffod5e
ff4o2d3i
3ffon.
ffon1
ffo3n4a
ff2o3n4e
ff3on2t
ff2or
5ff4or.
ff4os2
ff2r2a
ff2ri2
ff4rod
ffr2o
ff2r2w
4ff2ry
ff3r3yn
ff2t
5ff4u2r2f3
ffur1
ff5w2yf1
ff5yl.
ff1yl
f1g
fg4wr
fgw2
f1h2
fha5ol1
f1i
f4iadae
f1ia
fiad3
fi4ad1a2
2f2ic
fic4e
f2id
f3id.
fig4en.
fig1e
fil3y
f2i2l
f2in3
f4in.
f3in1d2
fin4t
fis2g4
f2is1
f2ï
fl2
fl3ad
flaf4
fl3a2i
flamad4
fl2am3
fla3n1a
fla2n
flan5ed
fl2an1e
f2l1as1
fl2aw4
fl3ec
fl5e2is1
fle2i
f2l3em1
fle3na
fle5ne
f1l4eo
f2l3id
fl1i2
fl4ig
flin3e
fl2in
f2l3ir1
fl4iw
f2l3om1
f3lon1
fl5rw2y
fl1r2
f4l3wr1
f1ly
f5lychw
f2lyc
flych1
f4l4yd2
fl4yf1
flyn3a
flyn3e
f2n
fn3a
fn3d2
f4n3ec
f4n3ed
f4n3em1
f4nen
f3n2if
fn1i
fn3ig
f3n2it2h1
fn5lu.
fn1l2
f4n3oc
f4n3om3
f4n3on1
fn3w
fn2y
f4n3yc
fn3yn
f1oc
fodd3
fod4en1n2
fo2d1en1
f4od1f2
f2odr4
fod3rw
f4odu
f3o4edd3
f1og1
fol3
fol4en1n2
f1om1
f2on4d2
fon1
5f2on1og1
f4ony
f4or.
for4c
f4o2rd2
fo2r3f
f3os2
fo2s4i
fos3o
f3ot1
f4otr
fr2a
f2raf
f2r1a2i
fra3n1a
fra2n
fra5n2ed
fran2e
fras4a2u
f2r1as1
f4r3a2u
f2r3d2
fr2dd2
fre2
f2rec
f4red.
f4re2g1
freg3y
f2rem1
f4ren
f3r2e1o
f2r1er
f2r3f
f2r1h2
f2rid
fri2
fr3id.
f2r1ir1
f4rit
fr2o
f3r2o1a
f5road1w
f4road
f2roc
frod4iae
fro2d3i
frod1ia
fro2e
fro4en3
fro5e2si
froes3
f3ro2i
f2r1om3
f2ron1
f3r2o1o
f2r4ot1
f3row
fro4w2c
fro4wn1
f1ru
fr2w
f2r1w2c
f2ry
f3ryn
f1ta
f3ter
fudd4l2
fudd1
fud3w
fu2l
f1un3
f4u2r2f
fur1
f3wa
f1w2c
f2wd3
f1we
4fwl.
f2wl
f1wn2
f3wr.
fwr5ne
fw2r2n1
f4wy.
f3wyd
fwyll2t4
f2wyl
fwyl2l
fwyn3
f4wyn.
f4wys1
f1yc
fyd2
fydd2ad4
fydd3
fydd1a2
fydd4l2
fydd5y
fyd4l3
f4ydr
fyd3y
3f2yf1
fyf4y
f1yl
f4yl.
f2yn
4fyn.
f3yng1
fyn3o
fyn5od
f2yr
fy3r2a
f3y2rd2
fyr2e
fyrf4y
fy2r2f
fyr4y
fys4t
fys1
fystyr4o
fys1tyr3
f2ys4w
gabl4en
g2ab
gabl1
g5a4ch.
g2ac
gach1
gad1
gad3a2
5gad2ar
g4ad2u
5gaduri2
gad1ur1
g4adwr
gad1w
g1ae
g2a2e3a
g3af.
gaf3a
g2af4r3
g1a2i
1g2al2
gal5ar3a
gal2ar
gal5ar1e
gal5ar1i2
gal5ar1o
gal5ar3w2y
galed5
gal1e
4gal2l
gam4en1n2
g2am1
gam2n4
gan3a
ga2n
gan4d2
ga4n1e
ganghe5na
g2ang1
gangh2
g3ant
4ganwr
gan1w
g3ao
gar3e2g1
g2ar
gar1e
gar4en1n2
g3a2r2f
gar4ge
ga2r1g2
3gart
4garth1ia
gar4t2h3
garth1i2
gar4we
g1as1
5g2ased
gase2
gasg4e
g2as2g
ga4t3r
g2a2t
2g1a2u
4gawe
g2aw
2g1b
gb4er
g1c
2g1d2
gdd2
gddig5
gd2d1i
gdo3r2a
gd1o
gdo5r4e
g2d1w
gd5wr.
g1ec
g1ed
gedd3
g2ed1e
g4edi.
ge2d1i
g4edi1d
g4ed3ir1
g4edit
g2ed1o
g4edu
g4ed1yc
ged3y
ge2g3
g2egy
g2ei.
ge2i
g3eid
g4el.
g2e2l
gell5a
gel2l1
gel3o
g1em1
g2en4d2
g5enni.
gen1n2
genn1i2
gen2r2
g3ent
g4en1u
g3er.
3g4e2r2d2
g2e2r4f
ger3y
g1es1
geu4l
ge2u
g1f2
gfa3n1a
gfa2n
gfa5n1e
gfe5ne
gf2yn3
g3ga
gh2
ghae4
ghan3a
gha2n
ghanghe5na
gh2ang1
ghangh2
ghar4en
gh2ar
ghar1e
ghasg4e
gh1as1
gh2as2g
ghen5i
gh2e2r4f
gh4le
ghl2
ghleid4
ghle2i
gh4ne
ghn2
gh2o2b3
gho4bl4
ghof5r
gh4og1
ghon4y
ghon1
gh2r2
ghra4
ghred4ad3u
ghr2ed3a2
ghred2ad
ghred4in3ia
ghred3i4n
ghre2d1i
ghredin1i
gh2w4f2
ghyd3
ghym4a2n
ghym1
ghys2g3
ghys1
g1i
g2ib3
g4id1a2
g4i5en.
g1ie
g2ig1
3g2i2l
1g2ip1
g3iw
g2l
gl3ac
gl3ad1w
glaf2
gl2an5e
gla2n
g4l3ant3
gl1as3
g5l4as.
g3l2a2t
gl5au.
g2l3a2u
gl2e
g3le.
gl3ech3
gl3e2d1i
g5leisiaso
gle2i
gle2is1
glei2si
gleis1ia
gleisias1
g3l2eo
gl3es1
gl3e2u
gl3f2
gl3ia
gl1i2
g2l3id
g3liw
gl4o2d3i
g2lod
gl4ody
glo4e
gl4of
5gl4oga
gl1og3
glo2i
g4lu.
g4l1w2c
g4l4wm1
g4l3wn1
g4lw2yf1
g2l3yc
g3l4yd.
glyd2
gl4ym3
gl4ys1
gl4yw1
g2n
gn2i
gn3io
g4niw2
g3nï1
gn2o3e
gn2of1
gn2u
gn1w
gn4yw
gobl4
g2o2b
g1oc
goddefad4
goddef3a
go5dd3r2
g2od2y
god5yn
g2oe
go5fa2u
go3fer
goff4a2u
gof4f3a
gof2f
gof4un3
gog2
go3g2a2n3
gog3e
g2og4l4
go5gyn
g3ol.
gol1
g4ole2u5
3g4ol2l3
go4lw
gol5yg3a
gol2yg1
gol5yge
gol5ygwy
golygw2
g3om.
gom1
go3m2e
gon5ad
gon1
g4one
g3on2t
gon4yn
g2or
gor5chy
gor2c
gor2ch
gordd3ad4
gor4dd
go2rd2
gordd1a2
gor2d5i
g4o2r2f
gorn4a2n
gorn3a
go2r2n1
g4orol3
gor1o
gor3t
1g2os1
gosb3a
gos2b2
g3ota
g2ot1
g3ot3e
g3o2t1i
g3oto
g3otw2
g2r
gr2a4m1
gran3a
gra2n
gr4e2l
g2r3f
gr2i2
g4rid4
g2r3ie
gring4
gr2in
g4r3ir1
g4r1it.
gr2o
gr3od
gr4oe
gr5oed3
gr2of4
grog3
gron4a
gron1
gro5nas1
gron4ed
gr2one
gron4es1
gr4ono
grwn5a
grwn1
gr3w1o
gr4wt
gr2w2y
g5r2wydd3
g4ryc
gryg3
gryg2l4
gr4ym1
gr4yn
g1s2
gsym4
gs2y
gub3
gudr4
gu5edd3
gu1e
gu4t3o
gw2
gwa5r4as1
g2wa
gw2ar
gwar4es1
gwar3e
gw4as1
g3w2c
gweithiad4
gwe2i
gw2eit
gwe2it2h1
gweith3i2
gweith1ia
gwelad4
gw2e2l
gwel1a
gwel5e
gwen3a
gwerthad4
gw2ert
gwerth3a
gwert2h
g2wm3
gwn4a
gwn1
gw4n4e
gwob2r3
g2w1o
gw2o2b
g3wr.
g4w2rd2
g5wth.
g2wt
gwt2h1
gwy3by
gw2yb
g3w2yd.
gwydr5
g3w2yf1
gwy4r
g2w2yw3
3g2yb
gy2b3y
g1yc
gych3
g4ycho
gyd4d4f5
gydd3
g2y2d3i
gyd1l4
gyd3r4
g4ydu
g4yd1y
3g2yf1
gyf5a2n
gy4fe
gy4fl2
gy4fr
g3yl3a
3g2yl2c
g3yl1e
g4yl1io
gyl1i2
g3ylo
g3ylw
g2ym1
gym4a2n
gym3u
g2yng3
g2yn1o
g2yr
g4yr1o
g2ys1
g2y4se2
gys2g3
gys5on3
g2yso
gys3t
gys5t2o
3g2yw
gy3wa
gy3wed
gy3wi
gy3wy
hab3yd
h2ab
ha2by
ha2d2d5as1
hadd1a2
hadd3o
had4e2g1
h2ad1e
had4eny
ha2d1en1
h4ad1f2
had4fer
hadl4a
h2adl
had3n
had3r4
h5aeol1
ha2eo
ha4f3a
h4afl2
h4af5ol3
h4afs1
hag3w2
h2a2g
h1a2i
h4a2if
hal3e
h2al
hall3o
hal2l
hal3o
ham4en1n2
h2am1
ham2n4
ham3s2
han3ad
ha2n
han1a
h4a4na2u3
han2c4
han3d2
ha4n1e
han5edd
han2ed
han4er
h4ang3e
h2ang1
hanghen4r2
hangh2
han3ig
han1i
han3l2
han2o
han4od3a2
han5ol1a2i
h4anol1
han5ol1as1
han5ole
han5olw4y
hanrhyd4
h1an1r2
hanr1h2
hansodd4e2i
han1s2
hansodd3
har5adwy.
h2ar
harad1w
har2a3t4
harato4en3
har4bwr1
ha2r1b
har4cha
har2ch
har4fo
ha2r2f
h1as1
h3asf
hast4a
h2as2t
ha4tr
h2a2t
hatr3e
h1a2u
hawe5nas1
h2aw
hawe5ne
ha2wl3
h2â
h1b2
hbl4a
h1d2
hdd2ad3
hdd1a2
h3d1i
hd4ir1
hdo3n2a
hd1o
hdon1
hd2o3n2e
hd4ra
hdr3e
hdr5oc
hdr5od
hdro5ed3
hd4roe
hdr5wyd
hdrw2y
h4dwr
hd1w
h2eb
h3eb.
heb3ra
heb2r
hedd3
hedd4f3o
h2edd2f2
h2ed1e
hed5fo
hed1f2
hed5f4w
h4edi1d
he2d1i
h4ed3ir1
h4edit
h2ed1o
hedr5w2y
h4edu
h4ed3y
h2ef
h2e2g1
heg2n3
h4egy
he4ho
h2e2i2
h4e3i1a
h4e2i2l
heimlad4w
he2im2
heiml3
h4el.
h2e2l
4helad
hel1a
4helaf
4hel1a2i
4hela2n
4hel1as1
h3e2l1d2
2hel1e
4hel1i2
2hel3o
hels4
2helw
4hely
h4el3yd2
h1em1
hen5cy
hen3c
hen4i1d
hen1i
hen1s4
hen3wy
henwy5na
hen2wyn
henwy5ne
heny5na
he3ol1
h2eo
he2r3b
h2er1o
h3e2si
hes1
h2e2t
h3ete
h3et1o
5heuae
he2u
he2u3a
heu4aeth5a
heuae2t
heuaet2h1
h2eu3d2
heu2l
he4wi
h2ew
hewy5nas3
h1f2
hf2i2l4
hf1i
hfonhedd5
hfon1
hfo2n1h2
hf4os2
hf4wy
h1g2
hga2n3
hgap2
hgi5a2i
hg1i
hg1ia
h1i2
hiach4
h1ia
hi2ac
hiac5h1as1
hiac5he
hiac5hw
hi4a4n
h2ib3
hidl3
h2ig1
h2ig3y
hin4t
h2in
hir3
hi4wa
h2ï1
hï4en
hl2
hl4ad
hl5ad1w
hl4am3
hla3n1a
hla2n
h5l4as.
hl1as1
hl3a2si
hl3aso
hl4aw
hl5ech.
hlech3
hl5ed3ig
hle2d1i
hl4edr5
h3lef1
4hl2eit
hle2i
hl4en
hl4e2t
h2l3id
hl1i2
hlon3a
hlon1
hl2on5e
h4lus1
h4l2wm1
h5l4yd.
hlyd2
h2l3ydd3
hlym4u
hlym3
h4lyn
hl3yn.
hlywad4
hl2yw1
hly2wa
h1m2
h3myg1
h2m2y
hmygad4
hmyg3a
h3myn.
hmy3na
hmy5ne
h5myn1i
hn2
h3n2ad
h2n1e2g1
h4n2ew
hn4ie
hn1i
h1nï1
hnod3
h2nol1
hn5ole
hn4yw
ho4ad.
h2o1a
ho4bl
h2o2b
hod4l
ho4dy
ho4en3
hoffad4wy
hof4f3a
hof2f
hoff3ad
hoffad1w
h3og.
hog1
h3o4ga
hog5lu
h2og2l
ho2h2
h2ol1
h3ol.
hol5ud
h2olu
h1om1
h2or
h3or.
hor4c
horn4a2n
horn3a
ho2r2n1
h4os.
h2os1
hos2b3
hos3o
h2ow
hp2
h2r
hr4a3dd
hr3ad1w
hr3af.
hr2a3g1
hr4aid.
hr1a2i
hr3ant
hra2n
h5r2aul
h2r1a2u
hr5ed3ig
hre2d1i
hr3em.
hrem1
h2r3f
hr2i2
h2r3ia
hr3id.
hr4id4a2
h2r3ie
hring4
hr2in
hr3ir1
hrisiad4
hr2is1
hri2si
hris1ia
hr3it
hr3iwy
h2riw
hr2o
h4ro4ad4
h2r2o1a
hr5och.
hroch3
hr3odd3
hrog3
hr3om.
hr1om3
hron4a
hron1
hro5nas1
hr2on4e
hrong5
hr3on2t
hr4ud
hr3wn.
hrwn1
hr5w2yd.
hrw2y
h5rwydd.
hr2wydd3
hr3w2yf1
h4ryc
hryg3
hryg2l4
hry3l
hr4ym1
hrynho4e
hry2n1h2
hrynho4i
hrynho4wn1
hrynh2ow
h4rys1
h1s2
h3sef4
hse2
h2t
h2u
hub5on1
hub1
hudd3
hudd5y
hudr4
hud3w
hud5yl
h4uge
hug4l
hun3ad
hun1
h4un1n2
h3ur.
hur1
h3us.
hus1
h4use2
h4us2t
h4usw
h2w2a
hw4as1
hwbl5e
h2wb
h2wd3
hw2e
hw2edl5
h3we2i
h4wel.
hw2e2l
hwen3
hwen4y
hwe5nychaso
hwen3yc
hwenych3
hwenych1as1
hwe5nyched
hwerw5
hwe5u2g
h2we2u
h2w2i
hwiw5g1
h2wm3
hwn4e
hwn1
h3wr.
h2wy
h4wy.
h4w2y1a
h4wyb2r4
hw2yb
hw4yc
hwyll5t
h2wyl
hwyl2l
hw4ym1
h4wy1o
h5wyol1
hwy4r
hyb4l1
h2yb
hyb4wyl
hyb3w
hy2b3y
hydd4i1d
hydd3
hyd4d1i4
hyd4fo
hyd1f2
h2y2d3i
hyd1l4
hyd4n2aw
hyd1n
hy4f3a
h2yf1
hyfad4
hyf4ae
hyf3a2r5f
hyf2ar
hyfer3
hyff2e2l5
hyf2f1
hyffred4in.
hyffre2
hyffred3i4n
hyffre2d1i
hyf4ia2i
hyf3i
hyf1ia
hy4g3a
hyg1
hygl4o
hyg2l
hygl4w
hyg2r4
hyll3a
hyl2l
hym4ad1w
hym1
hym4an5t
hyma2n
hym5e2l
hym4en.
hym3erad4
hym2er
hymer1a
hym3o
hym2p4
hym3u
hym5yr
hy2m3y
hym5ys1
hyn3a
hyn3e
hyn1ef3
hyn3yc
hyn1y
hyr3a
hyr4dd5
hy2rd2
hy2r2f3
hyr5n4o
hy2r2n3
hyr2w
h2ys4b2
hys1
h2y4se2
hysg5od
hys2g
hys4ig
h2y2si
hys4n
hys5on1i
h2yso
hyson3
hyt4bw
hyt1b
hyth5ru
hyt2h
hyth2r
hyt2u
hytun4deba
hyt1un3
hytun1d2
hy3was1
h2yw
hy2wa
hy3we
hy5wed
hy3wi
hy3wyd
1ia
iab4a
i2ab
iach2
i2ac
iad3
i4ad1a2
5iadaeth.
iadae2t
iadaet2h1
iad4lo
i2adl
5iad5uro
iad1ur1
i3a2e2l1
3iae2t
4iafo
iag3w2
i2a2g
2i2al1
i4al4ae
2i3am2
iam3h2
ia3n1a
ia2n
4ian1d2
ian5d1a2
ia3n2e
4i3ang1
iang4e
ianghen5
iangh2
ian3o
ian3w
2i2ar
i3a2rd2
i3a2r2f
iar4l2
iar3l2l5
iar4s2
i3as2g
ias1
iat5er
i2a2t
i2a2u
iawn2ad4
i2aw
iawn3a
iawn1
2ib
i2b3ed
ib3e2l
iben5y
ib3es1
ibetr4
ibe2t
i2b3i
ib4i2l
ibl3e
ibl3o
ibl3w
ib5og.
ibog1
ib3on1
ibr3a
ib2r
ibr3w
iby4n2ad4
i2by
iby3na
2ic
ic3en
ic1e
ichl4
ic5ied
ic1i
ic1ie
ic1on2
ic5on1i
ic5rw2y
ic2s4i
ics1
ic5siw
ic3t2
2id1a2
id2al4
2idd
i4dda2i
idd1a2
id2d4a2u
i2dde
idd3f4
id2d3i
i4dd2ir1
i4ddod
idd1o
idd3r2
2ide
id2e2l4
ider4
2id1f2
idf4w
2i2d3i
id1i4a
id4lo
id2l3w
2id1m2
2i2d2o
id3og1
i3dola
idol1
i3dole
i3dolo2
i5dolw4y
ido3n2a
idon1
id2o5n2e
i3dor
2idr
idr4a
idr4o
id3rw2y
2idu
2id1w
id2w3a
id1w3ad4w
id4wr
2idy
id3yl
id2ym1
1ie
4iedd
4iedi.
ie2d1i
ied2i4f5
ied4yl
ied3y
2i3ef
i3e2g1
ieg2wydd4
iegw2
2ie2i
i3eid
ie2is4
4ien.
ien4a
ien4c
4i2en1d2
i3ene
2ien1n2
ienw4
i3eny
i3es2g
ies1
2if
if4add2
if4ae
if4al
ifan3a
ifa2n
ifan5e
if4ao
if4ar
if5a2rd2
i4far3e
if4a2t
if5at2h
if4aw
if5b2in
if1b2
if2b1i
i4f1ec
i4fed.
i4fe2d1i
i5f2e4i1o
ife2i
i5fe4i1w
i4f1em1
ife4n
i4fent4
i4fer.
i3f4er3y
i4fe2si
if1es1
i3fe2t
iff2l3
if2f
iff5or
i3ffu2
iffy5na
iff2yn
iffy5ne
if3i
i3f2l1as1
ifl2
if4on.
ifon1
i3fre2
i3f2ry
i1fu
i4f1w2c
i4f1wn2
i4f3wyd
i4fw2yf1
i1fy
i4f1yc
i4f4yl
ify5r2e
if2yr
ig3ad1
ig3af
ig4a2in
ig1a2i
2iga2n
4i3g2ar
ig1e
ig3ed
ig3es1
ig5h2al4
igh2
2ig3i
ig5l4an.
ig2l
igla2n
ig5l4an1n2
ig5l2aw
ig5le2t
igl2e
ig4l3o
ig4ly
ig5lyd2
igl3yn
ig2n1
2igo
ig3odd
ig4ode
ig3oe
ig3om1
2ig2r
i3gre
igref4
i3gr2o
ig3rw
igr2yb4
2igw2
ig5w1a2i
ig2wa
i4g3w2c
i4g3wn1
ig4w2r2n1
2ig1y
igyf2f4
i3g2yf1
ig5yn.
ig4yna
ig4yr
ig2ys4
ig5yso
igysyll2t4
ig2ys3y
igysyl2l
igyt4
i3g2y4w
2i1h2
i2ha2n
ih2a2t4
ih2e4w
2i1i
i3iw
2i2l
il3a
5ilau.
i2l3a2u
il2c2
ild5ir1
i2l1d2
il2d3i
il3ed
il5en.
il2ew4
il1f2
ilf4y
il3i2
il4ip1
il2l3
ill5iw
ill1i2
ill2t4
i2l3oc
i2l3od
il5ofy
il3on1
il2s3
il4s2y
il4t1i
il2t
iludd4
il2ud
il3un3
il1w
il5w1a2i
il2w1a
ilwen3
il4ws1
il3yd2
il3yg4
il3yn.
ily3na
ily5ne
i4lys1ia
il2y2si
ilys1
il5y2wa
il2yw1
2im2
im4b1i
i2m3i
iml3
im4le
2in
in1a
in3ac
in3ad
in3af
in3a2i
in3a2n
in2be
in1b2
inc4e
in2c
in4c1i
inc2o
in4cy
in4da2i
in1d2
ind1a2
in1e
3in4eb1
in3f2
ing5en
in4g3o
in2g3w2
ing5yl
in5gyn
in3ia
in1i
in3i1d
in5i2ew
in3ie
in3ig
iniw4
in4iwe
in1o
in4ode
in4o2d3i
in4ody
in3oed
in2oe
in3on1
i2n3os3
int4a
in4te
in2t3r
in4ty
in3w2
in5w1yc
in1y
1io
3io.
2iod.
i3odde
iod5le
iod5wy
i2od1w
2ioe
2i1of
iog3
4iol2c
iol1
iom3
i2on1
ion3a
ior4c
io2r4f
i4or1w
2i2os1
2i2ot1
2ip1
ip5el2l1
ip2e2l
ip4og1
ir1
ir2a
ir5ag2l
ir2a2g1
ir3a2n
ir4áf
i2r3b
ir2ch3
ir3dy5na
i2rd2
ir1dy
irdy4n
irdy5ne
2i2r3f
2iri2
i4r1ia
i2r3io
i3r2is1
ir4l1i2
ir1l2
ir4l2l
ir3na
i2r2n1
irnad4wy.
irn2ad
irnad3w
ir3no
irn4y
2ir3o
ir3w
ir2w2i
ir4w1o
ir2y
ir3yn
i3r2yw
2is1
isaf4
is3b2
is5e2l1d2
ise2
is2e2l
is2er
is5er.
is4g2am1
is2g
is4ge
isg3o
is3g2r
isg5wyd
isgw2
is3gy
is4la
i2sl2
is5myn
ism2
is2m2y
is2o
is5odd3
is3ol1
is3on3
is2t2
is4t1i
is5t2ol1
ist2o
is2w
is3wn1
is5w2yd.
is4yc
is2y
is4yr
1it.
3it2a
2it2h1
ith3a
ith4a2u
ith3e2g1
ith1e
ith3i2
ith5or
ith1o
ith3w
ith3y
2iw.
iw3ad1w
i2wa
iw3af
i4w2air1
iw1a2i
i3w2al
iw3a2n
iw3as1
3iw2c
iw4ch1
2i2w1d2
iw2d4i
iw5edd.
i4w3e2d1i
iw3eid
iwe2i
iwg4w2
iwg1
2i2w1i
i2w1l2
iwl4e
iwl4i2
iwl4o
iwl4w
iwm4e
i2wm1
iwm2p4
3iwn1
iwn4i
4iwn3l2
i2w3o
i3wre
i3wr2t
iw5ter
i2wt
1iwy
iw4yd
iw4yf1
iwyn3
4iwyr
1iy
2iyd
2i1ym1
iyn3
2i1ys1
ï3ae
ï2i
l2ac
lach3
2lad.
l4ad4d3
lad2m2
l2ad2o
lad3r4w
4laen3
l3af.
5l4af2ar3
l1a2i
l4a2in
l4air1
l4ait
l2am3
l4an.
la2n
lan5c4ed
lan2c
lanc1e
lan5de
lan1d2
landr3
l2an1e
lan4es1
l4an1n2
lan3o
4lant3
lar3a
l2ar
la2r4ia
lar1i2
la2r3n1
l1as1
l4as.
l2as2g4
l2as2t2
las5ta
4lat.
l2a2t
lath2r3
lat2h
lat2s5i
lats2
2l3a2u
law5dde
l2aw
la2wd
lawen3
la2w3l
law3n2o
lawn1
lawr2
law5r1o
law3y
2l1b
lb4a2n
l2c
lch1
lch5io
lch1i2
lch5iw
lch3r
lch5w2yd.
lch2wy
l3co
lc3yn.
lc1yn
2l1d2
ldd2
l2d3i
ld4ir1
ldro3
l3dy5na
ldy5ne
1le.
l2e3a
le4ad.
le1ad
le4ad1a2
leb2r3
lech3
l3ed.
l2edd2f5
l4eddog1
ledd3o
led5fy
led1f2
l2ed3l4
l4edr
lef1
lef3e
lef3y
l2ega
le2g1
leg5ar.
leg2ar3
l2egw2
leg5yr
le5i2a2u
le2i
l2e1i1a
le3id.
le2i3l4
le3ir.
leir1
le3it.
l2eit
le4i1w
l3e2l
2l1em1
l3em.
l2ema
l2en2d2
len5d1i
len5ig
len1i
l3ent
len3y
1l2eo
le3oc
le4on.
le1on1
l3er.
l4e2r1a2u
ler1a
ler5ig3
ler3i2
les4g5e
les1
les2g
l4es3n
let4em1
le2t
le4tr
l4euad
le2u
le2u3a
l4eu1h2
4leuon1
le2u1o
l5euon.
le3wch1
l2ew
lew2c
le3wn1
lew3yn
lf2
lf5air1
lf1a2i
l3fa2n
lfe3ne
lf4fa
lf2f
lff4y
l1f1i
lf5icy
l2f2ic
l1fo
lf5od3a2
l1fr
lf4wy
lf3yd2
lfy5r2e
lf2yr
l1g2
lg4a2n
lgo4f3
2l1h2
l3ha
l3he
l3h1i2
l3ho
l3hw
l1i2
liach3
l1ia
li2ac
4lia2n
lib2r3
l2ib
2lid
l2i3de
1l2if1
li4fr
4l3io.
l1io
li5oed
l2ioe
li5p2al
l2ip1
lip2a
2lir1
l3ir.
lis4g3
l2is1
l3it.
lith4r3
l2it2h1
l4iw.
l2l
2ll.
ll4ad1a2
lladr3
ll5adwy.
llad1w
l4l3ant3
lla2n
ll5a2r3n1
ll2ar
lledr4e
ll4edr
ll4ed3y
l1l2e3o
lles2t4
lles1
lle2u4a
lle2u
ll1f2
llf4y
llin3e
ll1i2
ll2in
ll3odd3
l2lod
llosgad4
ll2os1
llosg3a
llos2g
ll5tyr
ll2t
lludd3
ll2ud
llw2
ll3w1a
llw4e
5ll4yd.
llyd2
llyg2r3
ll2yg1
ll4yn3n2
ll4yr2
ll5yr1o
lm2
l1ma
l4mad
l4maf
l2m3a2i
l2m3as1
l4ma2u
lm3o
lm3w
lm4yn
l2m2y
l1n
2lo.
lob5yn
l2o2b
lo2by
2loc
loch3
2lod
lodd3
lo3ed.
l1og3
l2og2l2
l1ol1
lol2w
lol2yg4
2l1om1
l3om.
lon2a
lon1
l2on3d2
lon4es1
l2one
4l3on2t
l3or.
l4o4r3a2u
lor1a
l4o2r3g2
l4or1y
2l2ot1
lo5yn3n2
lp3a
lp1
l3pu
l1r2
l3r1h2
ls4ig
ls1
l2si
l4syn
ls2y
l2t
lt3ad
l4t5eg.
lte2g1
lt3em1
l5ter1a
l5ter1o
l4t3ia
l2t1i
lt4ig
l4t1io
lt1o
l3tra
ltr4e
l3tu
l4tu.
lt1w2
2lu.
l2ud
ludd5y
ludd1
lud3w
lu4edd3
lu1e
l2un3
l4un.
lur5ig3
lur1
luri2
lust5l
lus1
lus2t
l2w1a
lwadd4
lw4ae
l1w2c
l3wch1
lw3ed
lw3er
l2w3es1
lw4fa
l2wf2
lwfr5e2
l4wg2r
lwg1
l2w1i
l3w4ig1
l1wn1
l3wn.
l2w3o
l1wr1
4lwre
l4w1yc
l4wy2d3i
lwyd4io
l4wyn3
l4wyr
3l4wyt
l2yb3
2lyc
l3ych.
lych1
lyd2
l4yd.
2lydd3
lyd1n3
lydr3
lyf3a
l2yf1
lyf5a2n5
lyf4n3
lyf4r3
5lyfr.
l2yg1
4lygedd3
lyg1ed
4lyg1ia
lyg3i
lym3
l4yn3a2u3
lyng3a
l2yng1
l4yn3y
lyr3a
4lys3a2u
l2ysa
lys1
4lysen.
l2yse2
lys3ga
lys2g
lys3ge4
l4y2sl2
4lysn
4lysr
4lysyn
l2ys3y
l2yw1
m1
m2ab3
ma2b4i
m3ac
mac4w
m4ad3ad
mad1a2
m4adaf
m4ada2i
m4ada2n
m4a2das1
m2a2d3i
mad4r3
m4ad3w2c
mad1w
m4adwn1
m4ad1y
ma2e2l3
maf4l3
m3a2g
2ma2i
m3am1
man3a
ma2n
man3e
m4an1f2
man2o
m3ar
m4ar.
mar4ch3
m4ar1e
m4ar1i2
mar4l2
ma2r2n3
m4aru
mar4w2y
m2as2g2
mas1
mas3ge
m3a2t
mat5e2g1
mat4o
m3aw
mawd4a2
ma2wd
mbarato5
mb2ar
mbar2a2t4
m3b1i
m3by
mca2n3
md2
m4da2i
md1a2
md2an4a
mda2n
mda5n2as1
md2a5n4e
mdd2
mdd2adl4
mdd1a2
mdd2ad
mddef3
mddi4d
md2d1i
m5der.
m4der1a
mdog4aeth1o
md1o
mdog1
mdog1ae
mdogae2t
mdogaet2h1
mdo3n2a
mdon1
md2o5n2e
md4ro3e
mdwy4
md1w
md2yng5
mdy5r2e
4m2ed3a2
4medd1ia
med2d1i
4meddwr
medd1w
4me2d1i
4m3ed1ï
medr3
meg3n4
me2g1
megn2i3
me2it2h3
me2i
m2eit
me4i1w
mel5yn1o
m2e2l
melyn3
men1s4
men2t4e
men3tr5
5m4enty
men5yd
m2er
m3er.
m3erad
mer1a
m4eradwy.
merad1w
m4eraf
m4er1a2i
m4era2n
m4e2r1as1
merch4er
mer2c
mer4ch
mer2dd4
m2e2r2d2
m4er1e
m5eri4ad1a2
mer3i2
me2r1ia
meriad3
m4eroc
mer1o
m4er1om3
m4eron1
m4erw
m4ery
4mes1ia
mes1
me2si
4mesol1
mes2t4
4meswr
4mes2y
me2u1
mfalch1i5a
mfalch4
mf2al
mfal2c
mfalch1i2
mfalch1i5e
mff2l4
mf2f
mfydd4
mfyd2
mg2
mgyf2f4
m3g2yf1
mgyffr5o
m1g2ym4
mgym5e2r1ia
mgym4er3i2
mgym2er
mg2ys2
mh2
mhar5ad
mh2ar
mheir4a
mh2e2i2
mheir1
mhe3na
mhe5ned
mhe5nes1
mhen3t4
mhen5w
mh2e2t2
mhe3ta
m2he2u
mho4b4l
mh2o2b
mhr4a
mh2r
mhr2yf5
mhyd4
mh2y3f1
2mi
m3ias1
m1ia
m3id3
m3ie
mi5ge2i
mig1e
m2in1
min4t
m3io
m3ir1
m2is2
mis2g4
mi2s4i
m3it
m3iw
m3iy
ml2
m2l1as1
ml5blw
m2l1b
m3led
ml2ew3
m3l2in
ml1i2
m5liwia2is1
ml2i2w1i
mliw1ia
mliwia2i
m5liwiase2
mliwias1
m5liw1iwy
mlo3n2a
mlon1
mlon4ed
ml2one
mlyn3
m2n
m3na
mn4as1
m3ne
m4ned
mn5e2d1i
m5ni2a2u
mn1i
mn1ia
m3nï3
m2od
m3odd
mod4ig
mo2d3i
m2od3r
mof5yd2
m3og1
m4on.
mon1
mon3a
m2on4d2
m4onï
mor2
mor3c
mor4dd4
mo2rd2
mordd2iw5
mord2d1i
mor4o
m3os2
mo2s4i
mo5s1iy
m2p1
mpr3a
mp2r2
mpr3o
mpr3w
mp5w1a2i
mpw2
mp2wa
mr2
m2r3a2i
mra3n1a
mra2n
m2r4ed
mreg3y
mre2g1
m4r1ia
mri2
m4r1ie
m4rig3
m4ro4ad
m2r2o1a
mrod4iae
mro2d3i
mrod1ia
mrod4ir1
m2roe
m2roi
m2r2o1o
m2row
m4roy
m4ryn
m4ryn4d2
mr2ys4o
mrys1
ms2
m3sa
m2se2
mse3na
mse5ne
m2so
mstr4
ms2t
m2t
mt2a
mt1as4
m3t2h
m2u
mu4a2n
m2u1a
mudd4l2
mudd1
mud3w
mu2l3
mun3
m3us1
m3w2a
mw3as1
m3wch1
mw2c
m3wi
mwr2
mwr3i2
m3wt
mw2y3b
mwyll2t4
m2wyl
mwyl2l
mwyn3
m5w2yse2
m2wys1
mwyth4ad1w
m2wyt
mwyth3a
mwyt2h
mwyth4af
mwyth4asa2n
mwyth1as1
mwyth4aso
mwyth4asw
mwyth4ec
mwyth3e
m1wyth4em1
mwyth4er
mwyth4i2
mwyth4oc
mwyth3o
mwyth4w
mwyth4y
2m2y
m3yc
mych3
m3yd
myd4d5i4
mydd3
mydr3
myd3y
myf4y
m2yf1
m4yl.
myl3a
m4yl1n
m3ym1
myn4ai.
myn3a2i
m3yr
my2r4as1
myr5asa
myr4e2d1i
myr1e
my2r2f4
m3ys1
m4ysg.
mys2g
m2ys4w
myw3y
m2yw
3n2a3b2
na4bl1
na4b1o
na4ch3
n2ac
n2ad
n3adl
nad4n
nad2na4
n4ad1o
nad3r
nad3u
nad3w
n3adwr
n1ae
nae5ara
n2a2ea
nae2ar1
nae5arw
na2e2l4
n2afa
n5af3a2u
n2af3o
n4af1y
n4a2ic
na2i
n4aig
n4a2in
n4air1
n3al
nan3a
na2n
nan3e
n2an3f2
nap4om1
nap1
n3ar
narllen2ad4
nar1l2
nar3l2l
narll4e
narllen3
n3as2g
nas1
n4asol1
n3as4t
1n2a2t
na2u3
n1b2
nbyd5r
n2by
n2c
nc3a2n
nc5d1es1
ncd2
nc4ed
nc1e
nc2e2i
nc5en.
nc2en
n3ch
nchwiliad4
nch4w2i
nchw2i2l3
nchwil3i2
nchwil1ia
n4c1ia
nc1i
n4cid
n4c1ie
n4c1io
n5c2i3od.
n4cir1
n4cit
n4ciw
n4c1iy
n3cl
ncr1
nct1
n5c2yd.
ncyd3
n5c2yn1y
nc1yn
n1d2
n2d3as1
nd1a2
n2d3aw
ndd2
nd4d1a2
n2d1en2
n4d3ia
n2d1i
nd3ie
n3d2i3f
n3di4g
n3d2i2l
nd3io
nd4ir1
n3d2is1
n3dit
nd3iw2
nd3iy
n3dod
nd1o
n2d3oe
ndo3r2a
ndo5r4e
n2dwr
nd1w
n3dy5na
ndy5ne
n4dys1
neallad4
n2ea
ne2al
neall3a
neal2l
n2eb1
neb3o
n5ebry
neb2r
n2edd2f5
n2ed1e
n4edi1d
ne2d1i
n5ediga
ned3ig
n4ed3ir1
n4edit
n2ed1o
n4edu
n3ed3y
n1ef
nef2n3
n4efy
n1e2g1
neg5in
neg1i
ne3h
n3e2idd3
ne2i
n2e2is1
n2e2l
3nel.
nel5yn3
3n4enty
ner3a
ner4ch5
ner2c
n4e2r1g2
n4er2l2
3n2ert
3nes3e2
nes1
4nes1ia
ne2si
n4es3io
nes4m2
3neso
n2es2t
3nesw
n2es2y
neth5o
ne2t
net2h1
n2e2u
n2eu3d2
n4eu1f
neul4
3n2ew
new5y2ll.
ne2wyl
newyl2l
newyn3
n1f2
nfadd4
nf4am1
nfa2n3
nfan5e
nfan4t2
nfa5ol1
nf4a2t
nf2e2l2
nf2f2
nf4fa
nff4o
nff2yn4
nffyn2ad4
nf4id
nf1i
n4f2i2l
nfod4l
n2fon1
nfon5a
n5fone2d1i
nf2one
nf4ri2
nf4wy
n2fy
n5f2yd.
nfyd2
nf2yd3a2
ng2ad1
ng5adwy.
ngad1w
n4g1a2i
n1g2al4
n3g2am1
n3g2ar
n4g1a2u
ng4dd2y
n2g1d2
ngdd2
ng2e2l4
nghwyn5
ngh2
ngh2wy
n2g1i
n2g2l2
n3g4l4wm1
n4gly
n5gl4ym3
nglyn3
ng2n2
ng3oe
ngof3a
ngol4ed
ngol1
ng3on1
ng2op2
n1g2r
ngr4a
n2gw2
ng4wi
ngwy5nas3
n3g2y3f1
n4gyn
2n1h2
nha3o
nh2ar4
nhaws4
nh2aw
nheb5r
nh2eb
nhe3na
nhe3ne
nhep2
nh4es1
nho3ed
nho5e2si
nhoes3
nho3n4a
nhon1
nh2on4e
nhudd4ed.
nh2u
nhudd3
nhudd1ed
nhu4e
nhyc4
nhyd2
nhyl4
nhym4
n1i
4ni4ad3
n1ia
n5ia2l1d2
n2i2al1
n2i1b
nib4a
nib4e
nibr2yd4
nib2r
ni1d
n2idd4
ni5dde
n2id4e
n3ie
ni4e2t
ni3e2u
n4i2ew
ni3fed
n2if
ni3fe4n
ni4fer3yc
ni3f4er3y
ni3ffr
nif2f
ni3fw
n2ig
n5ig2am1
nige5na
nig1e
4nigiad3
n2ig3i
nig1ia
n5igiad.
n5igi4ad1a2
5nigiad1w
4nigi2on1
nig1io
n5igion.
5nigion2t
4n5igiwr
nig3iw
nig2l4
4n2ig1y
ni3g2ym4
nile1ad4
n2i2l
nil2e3a
nill5a2das1
nil2l3
nill4ad1a2
n5ill1io
nill1i2
ni3lu
ni3lys1
n2in2
ni3n1o
nin4w2
ni3or
n1io
ni3r2a
nir1
nir4e
n2i3ri2
ni4r3w
ni3rym1
nir2y
nis3g
n2is1
ni3s2o
nis3ty
nis2t2
ni3s2w
ni3s2y
nith4e
n2it2h1
niw2
ni2w4a
n3i4w2c
niw5e2d2das1
niwedd1a2
niw5edd3e
niw5edd3o
niw5edd1w
ni2w1l3
niwl5e
niwl5o
niwl5w
ni5ydd3
n1iy
n2iyd
n2ï2i
nï4yc
n1l2
nladr3
nl2in3
nl1i2
nl2l2
nllon4
nl4lw2
n4llyn
n2ly
nly3na
nly3ne
n1m2
nmolad4
nmol1
n1n2
nn4al
nn4ar
nned4
nnet2h4
nne2t
n3n1h2
nn1i2
nn2if4
nn2i4l
nnil2l4
nn1i4o
nn2is4
nni4w2
n5nos4b2
n2n2os3
nn4wy
nny3na
nny5ne
nn4yw
no4ad1a2
n2o1a
n3o2b
n2od.
n2od1o
n2od3r
n2oe
no2e4o
no3er3
3no4e2t
n1of1
nof4e2l
n2ofy
n1og1
nol5e2g1
nol1
nom3
n4o2mi
n5ones1
non1
n2one
n1or
nor2ch4
nor2c
no2r4f
2n2os3
nö5es.
nöes1
np4e2t2
np1
n1r2
nr2e4o
n1s2
n2se2
n3se2i
ns3en
n2s3i
ns4ic
ns4ig
n3s4i2l
ns4iy
ns5iyc
n3s2iyn3
nsy3na
ns2y
nsy3ne
nt3ad
nt5af.
nt1af
nt5aid
nt1a2i
nt4an1a
nt1a2n
nt3aw
n2te
n3te2i
nt3e2l
nt3em1
nt3er.
n3t2ew3
nt2h2
n4t3ia
n2t1i
nt5il.
nt2i2l
nt4in
n3t2is1
nt3oc
nt3od
nt5od.
nt3oe
n4t3or
n1tr
nt1w2
nt3yn
nty3r2a
n1tyr
nty3r4e
n1u
nud2o
nun4i
nun1
nut1
nw3af
n2wa
n3w4ait
nw1a2i
nw3a2n
n3w2ar
nwar4ed.
nwar3e
nw3as1
nwbl4
n2wb
nwb5le
nwd3e
n2wd
n5wedd
n4w3e2d1i
n3we2i
nweithi5a2u
nw2eit
nwe2it2h1
nweith3i2
nweith1ia
nwelad4
nw2e2l
nwel1a
nw2en5d2
nw4ia
n2wi
nw3id
nwir4
nw3ir.
n3w2is1
n2w3o
nwr5e2i
n4wy.
nwyb2od4a2
nw2yb
n4w1yc
n3wyl
n2wyn
n4wyn.
n3wyt
nych3
n2yf2
ny5fala
nyf2al
ny5fal1e
ny5falo
nyf2f4
nyf4n
nyf4o
ny5fod
nyfr3
n2yg1
ny3gy
n1yl
ny3la2n3
ny3lu
nym4a
nym1
ny2m4y
n5ynna2u3
nyn3n2
ny3n4od
nyn1o
ny3ra
nyrchafad4
nyr4ch
nyrcha4f3a
ny3ri2
n1ys1
n4ys.
nys4g
n3yw
2o1a
2o2b
o1b3ae
ob4a2n
ob5ant
o2b3ed
ob3e2l
ob5en.
oben5y
ob5er.
obl3a
obl5ed
ob3ler
obl5es1
obl3o
obl3w
o3b4ly
ob3o
ob2r1
ob3yd
o2by
oc1a
oc5byn
oc1b
oc2by
oc3e
och3a
och5a2n
och5en
ochl3a
ochl2
ochl5es1
ochl3o
ochl3w
och3n2
och4n1i
och3o
och2r3
och3w
och3y
2oc1i
2ocr
2oct
2od3a2
od4ao
od2ar4
odd3a2
oddf5y
odd2f2
odd5i2l
od2d1i
odd2iw3
odd3r2
odd5r4i2
4od2du
odd3y
odd5yd
odd5yn
odeb3
o5deb1a2u
o5debu
od5e2d1i
od1ed
od5eid
ode2i
od3e2l
od3er
o2d3i
odl3a
odl3ec
odl5e2si
odles1
od2l3w
od5of2f
od1o
od2of
2odog1
od4oga
2odr
odr3a
odr5ec
odr5em1
odr3o
odr5w2yd.
odrw2y
od4ry
odr5yc
2od1w
od3w2a
od5wed
od5wen
od3yc
od3yn
od4yn3n2
o1ec
o4edd3
oe2d3i
o3ed3ig
oedl4a
o2edl1
oed5la2n
oed5ra
oe2g3
oel3c
o2e2l
o1em1
oen3
o3ent
oer3
oes3
oesg4o
oes2g
oet5a2n
oe2t
oetr3
2of.
of3ad
of3a2i
ofa2n3
ofan5e
of3ant2
ofa5ol1
of5e2b1i
of3ed
of3e2l
of3en
of4en1n2
of3er.
o4fer2l2
o4f4er3y
of4f3a
of2f
off3ed
off5id
o2ff1i
off3w
ofiad4w
of1i
of1ia
ofiad3
ofl3
of3n
of4n3a
of4n3d2
of4ne
of4n1f2
of1o
of4od1o
ofr3a
of3re2
of4rec
of4red
of4rem1
of4r1er
of5w2yf1
of4yn
ofy3na
ofy3ne
og1
og3a2i
og2a2n3
o4ga4na2u3
ogan3a
o4gan1u
og3as1
og4ed3y
og1ed
og5elyn3
og2e2l
og3er
o3g5e2r2d2
og3es1
2og1f2
og3i
2og2l
ogl3w
ogl3y
2og2n3
3og2o4f
og5oru
og2or
og3r2w2y
og2r
o3gry
og3yd
o3g2yf4
og4yl
og5y2r2n3
og2yr
o1h2
oh2eb3
oher4
o1id
oig1
o1ir1
o1it
ol1
2o2l3a2u
ol4c1e
ol2c
o2l3d2
ol4d1a2
4ole2u
ol3e2u3a
ol4eued
oleu1e
ol5e2u1o
ol4euwr
ole2u3w
ol2ew3
ol3i2
ol2l1
oll3e
oll5ed
ol4l2t
oll5wy
ollw2
olo2
o3l2os1
ol3s1
ol4s2b2
2olu
2ol1wr1
olw4y
ol3wyd
ol5wyn3o
ol4wyn3
ol4yne
ol4yn1i
ol4yn1o
ol4yn1w
2oma
om1
om4a2t
2omb
om2e
o4m5ed3a2
o4m5e2d1i
om5eg.
ome2g1
om3e2i
om3en
om5i2s4i
o2mi
om2is2
2oml2
om4og4
2om2p1
om5pre
omp2r2
on1
on5a4ch.
ona4ch3
on2ac
on5ad3u
on2ad
on3af
o4n3a2i
4on4air1
on3a2n
o4n3a2u3
on5au.
2on1b2
on5c2yf1
on2c
2on1d2
on5di1d
on2d1i
on2d1o
2one
on5edd.
on3e2l
on2es2t3
ones1
2on1f2
on2g2l3
ong2o
on2g3w2
on4g2yr
2on1i
2on1n2
4onn1u
on5of.
on1of1
2on1og1
on2t
4onto
on3w
2o1o
2op1
op3a
op4ao
op5aon1
op2l3
opr5a2i
op2r2
op5ren
or1a
4or2ac
or3ach1
or5aeth.
or2ae
orae2t
oraet2h1
or5aeth5a
or3af
or3a2i
or3a2n
o4r3a2u
or3aw4
o2r3b
or2c
or3chw
or2ch
or4dd
o2rd2
or5dd4yn
ordd2y
or2d3en1
or1de
or5d4in
or2d3i
or4d5y4n
or1dy
or1e
or2eb
o4r4edd
ore5dd2y
4ore2g1
or4egw2
or4e2t
or3fa
o2r2f
orfa5n1a
orfa2n
orfa5n1e
orff4e
orf2f
or3fo
or3f4y
2o2r3g2
or3i2
or3l2
or4m2u
or1m2
or4m2y
orn3a
o2r2n1
or3n2e2l
or1o
or3of
or4o1h2
oron5a
oron1
or3one
or5oni.
or2on3i
or5oni1d
or5onir1
or5onit
or5pws1
orp2
orpw2
4orth.
ort2h
or2t4i
or4t3y
or5u2w2c
or2u1w
or1w
or5wa1h2
or2w2a
orw4e
or4w2e2l
or5wg2l
or2wg1
or1y
or3ydd3
or2yd
2os1
os3a
os4an3a
osa2n
os1b3as1
os2b2
os2b5ed
osb3o
osb3w
os2b3y
os5e1a2i
ose2
os2ea
osg3a
os2g
os3g2l
osg2o5e
os3g2or
osg3wy
osgw2
os5iae
o2si
os1ia
os5i2b3i
os2ib
os2o
os3odd3
os3ol1
os3on3
os3te
os2t
os3tr4
os4t2u
os3w
os3y
2ot1
3ot.
ot3e
ot5es3a
ot1es1
ot2h3
ot2s4i
ots2
ot5s1ia
o2u
o1w2c
owg3
owl5as1
o2wl
owl3e
o1wn1
owt5er
o2wt
o1wy
o1yc
o2yw3
oy4we
ô2r3f
p1
p2a
pab5yd
p2ab
pa2by
2p3ad
2p3af
2p3a2i
2p3a2n
pa3od
par2a3t4
p2ar
par4c
par3w
p2as2t4
pas1
p3a2u
pawe5na
p2aw
2pec
4p5ed3ig
pe2d1i
p2e2i
peir4a
peir1
p5eli.
p2e2l
pel1i2
pel3y
2pem1
pen2g2l4
pen1s4
pen3t2
pen3w
penwy5na
pen2wyn
2per
2pes1
pe2t2
pe3ta
p2h2
pheir4a
ph2e2i2
pheir1
phen3t4
phen5w
ph2e2t2
phe3ta
pho4b4l
ph2o2b
phr4a
ph2r
phr2yf5
p3ia
p2ib1
p3ie
p3io
p3iw
p2l
pla3n1a
pla2n
p4l3a2u
pl5ed3a2
p4lyc
3pl2yg1
po4b4l
p2o2b
pog4y
pog1
pol3
p2r2
p2r3as1
pr2yf3
p4r5yn3n2
p2s1
ps4iw
p2si
pt2
p2ud
p4usr
pus1
pw2
p2wd3
pwr1
p4wy.
pydr3
p2yr
r4abe
r2ab
r4a2b1i
rab5lyd2
rabl1
ra2b3y
rach5wy
r2ac
rach1
r4a4c1i
racs4
r4a4ct
r2ad1a2
r4add
radd5ol1
radd2o
rad4ri2
radwr4i2
rad1w
r2ae
raed4
raedd3ad4
raedd3
raedd1a2
r4aen3
ra5f4an1n2
raf3a2n
ra5fán
r4af2f
r2a2g1
ra4ge
rag3o
ra3gra
rag2r
ra4ha
ra1h2
ra5h1a2u
r1a2i
4ra2idd
ram3od
r2am1
ra5m2or2
ra3m2w
ran4d3
ra2n
ran2e
r4an1f2
ran3o
r4an3od.
ra5phe
rap1
rap2h2
r3ar3
rar4c
2r1as1
r4as4ie
ra2si
r2as3t2
r3at3ao
r2a2t
rat3e
2r1a2u
raw3e
r2aw
5ra2w3es1
3ra2w1i
rawn3
2r1b
r2ba
r3b2ar
r4b1ec
r4b1em1
r4bent4
rb4er
r4bes1
r2bl
r4b1oc
r4b1om1
r4bon2t
rb2on1
r4b1w2c
r4b2wd
r4b1wn1
rbyd3
r2by
rc2a
rc5ad1w
rc1ad
rc5af.
rc1af
r3c2ar
rc3e
rc4er
r2ch
rch3ad
rch3a2n
rch3ar5
rch5eb
r5ch2e2i2
rch3e2t
rch3l2
r3ch1m2
rch3oc
rch3oe
rch3og1
r3ch2u
r3ch2w2a
r3ch4w2i
rch5wyd
rch2wy
r5chwyn
rch3yc
rch2yf4
rchym4
r1cy
2rd2
r1d1a2
r3da2i
rda2n3
rd5au.
r2da2u
r2dd
rdd3ad
rdd1a2
r2d2d5as1
rdd5el2l1
rdd2e2l
rdd5in
rd2d1i
rdd5iwy
rdd2iw2
rdd3o
rdd4od.
r5dd2o2d3i
r3d2d4u
r4ddu.
rdd1w4
r4dd3yc
rdd2y
r5ddychw
rddych1
rddyr4ch5
r3ddyr
r5dd2yw
r1de
r2d3i
rd4in
rd4ir1
r1d1o
r5d2od.
r1dr
rdro3
rdro4ad1a2
rd2r2o1a
rd4road
r3d1w
r1dy
rdy4n
rd3yn.
r2e3a
r3eb1a2i
r3eb1as1
r3eb1e
r3e2b1i
rebl3
r3eb1o
rech3
rec3i
4redd
r5edd.
r4ed1io
re2d1i
r4edol1
red1o
r4edwr
r2ed1w
red4yn.
red3y
re4fa
refn5y
ref2n1
ref3y
r4eg2l
re2g1
r5egl.
r4egog2
r2ego
re5i2a2u
re2i
r2e1i1a
r4e2ic
re5id.
re2idd5
r4eig
r4e2i2l
r4ein1e
re2in
re5ir.
reir1
re5it.
r2eit
re4i1w
r3el2l1
r2e2l
r4e2mi
rem1
r2en4d2
r4eng3
r4en1i
ren3in2
r4ennyd
ren1n2
r2e1o
r1er
r4er4id
rer3i2
rer5in3
restr3
res1
res2t
r4esw
r4e2u3a
re2u
r4e2u1o
r2eu1y
re4wi
r2ew
rew5id
re5wn.
rewn1
rew5yn3n2
2r2f
r1fa
r4f3ad
r4faeth.
rfae2t
rfaet2h1
r4f1af
r4f1a2i
rf4ao
r4f1as1
rf4a2t
r4fa2u
r3f2ed1w
rf2e2l3
rf3en
rf4en3y
r3f4ey
r4ff.
rf2f
r2ff3i
rff3l2
rff3o
r3ffw
rff3y
rf3id
rf1i
r5fil.
rf2i2l
r3fl2
rf3lu
rfod2ad4
rf2od3a2
rf5ol.
rfol3
rf3on1
rfor2
rf5o2rd2
r3fr
r3fu
rf1w
rf5w2is1
rf2wi
rfyn5yc
rf2yn
rfyn1y
rf4yr
r3fys1
2r1g2
r1g2al4
rga2n3
r3ge
rgel4y
rg2e2l
rge3na
rge5ne
rgo4f
r1h2
rhag5e
rh2a2g
rhag3l
rhag3o
rha3n4a
rha2n
rha4n4e
r4h2aw
rh4es1
rhew5y
rh2ew
rh2if3
rh1i2
rho4ec
rhon5a
rhon1
rhost4ir1
rh2os1
rhos2t
rhos2t1i
rhug4l5
rh2u
rh2yf2
rhy3n4a
rhyn4e
ri2
2r1ia
r4iaeth2u
r3iae2t
riaet2h1
riaf3
r4i2a2g
ri5ag2l
r3ia2i
r4i5a2idd
ri5al2l
r2i2al1
ri4a2n
r5iant
r3ias1
r4i2aw
ri5a2wd
rib3e
r2ib
ribl3
rib3w
ri2b3y
ri5ca2n
r2ic
r4id1a2
r2idd3
ridd5y
r4i2d3i
rid4yl2l3a2u
r2idy
rid3yl
ridyl2l3
2r1ie
r2i3e2i
r2if1
rig3
r4igo
r2i3i
rin5d4ir1
r2in
rin1d2
rin2d1i
rin3e
rin2g2l5
r4in1i
r4in1l2
2r1io
r3ioc
ri5od2ad
ri2od3a2
ri5odaf
ri5oda2i
ri5oda2n
ri5odasa2i
rio2das1
ri5odasa2n
ri5odase2
ri5oda2si
ri5odas3oc
ri5odas3om3
ri5odasw
r3iodd
ri3ode
ri3o2d3i
ri5odoc
riod1o
ri5odod
ri5od3om1
ri5odon1
ri5od3w2c
ri2od1w
ri5odwn1
ri5od5wy
ri5ody
r4ioe
r3iom3
ri3ong
ri2on1
r3ion2t
r1ir1
ris4g
r2is1
risg2l3
ris2t3
3r4it2h1
2riw
ri4wa
ri2w3l4
r5iwr.
2r3iy
r1l2
rla3n1a
rla2n
rl2a3n1e
r3l2ew
rl3ia
rl1i2
rl3ie
rl3io
r3l2l
r4ll.
rll4e
rllen3
rl4l4w2
rl5og.
rl1og3
r3lon1
rludd4
rl2ud
r3lw
r2lym3
rlyn3
rl5yn.
r1m2
r2ma
rm4ac
r2m3i
rm4i2l
r2mo
rm4od
r3m2y
2r2n1
r4nai.
rna2i
r4na2u3
rn4es.
rnes1
rn4es3a
r5n2es2t
rng4e
rn3i
rn5iae
rn1ia
rn4i1i
rn5iol1
rn1io
r3n2ï1
r2n4os3
rn3y
rn4yw
2r2o1a
4road
4roa2u
rob3l4
r2o2b
roch3
rochl4
r2o3cr
rodd3
r4odr
rod5rw
ro4ea
roed3
ro4eo
ro3er3
r2of
rof3l4
rofun4ed.
rof1un3
rof3w
r3og.
rog1
r4og1ae
ro4ge
rol3
r1om3
r4o4n3a2u3
ron1
ron2g2l4
rong5lwy
r2on3i
r4os.
r2os1
r4osf
ros2g4
ros3o
2r2ot1
rö5e2d1i
rp2
r1p2a
rp2ar3
r1pe
rp5ech1
r2pec
rp5em.
r2pem1
r2pen
rp5en3t2
rp5er.
r2per
rp5e2si
r2pes1
rp3i
rp3o
rp3wy
rpw2
rp3y
r1r2
r3ra
rr4og1
r1s2
rs4a2i
r4s3a2u
r2s3en
rse2
r2s3i
rs4in
rs5li.
r2sl2
rsl1i2
r2s3t2
r2s2y
r1t2a
r4t1a2u
r4t1ed
r3te2is1
rte2i
r4t1en
r4t1es1
rth3a
rt2h
rth4e2g1
rth1e
r3th2in
rth1i2
rth3l3a
rthl2
rth3o
rth5ol1
rth5ru
rth2r
r5th4ryc
r4th1w
rth5wyon.
rth2wy
rth4wy1o
rthwyon1
rth5ydd5
rt4iy
r2t1i
r1tr
rtr4a
rt5rw2y
r2trw
rt2u
rt3y
rub4a
rub1
r3uc
rudd3
3rudd.
run4i
run1
r1us1
r2w2a
rw3ad
rw3af
r3w4a2g1
r3w4ait
rw1a2i
rwb5a2n
r2wb
rwbl3
r1w2c
r5wden1n2
r2wd
rwd2e
rw2d1en1
rwedd3
r4weddog1
rwedd3o
r4weddol1
r4w3eid
rwe2i
r3w2e2l
r3wer
r2wg1
rw5h2e2l
r2w1h2
rw3he
r2w1i
rw3in
r3wl
r4wn2c
rwn1
rw4n1i
rw4n3o
rwob2r4
r2w1o
rw2o2b
rw3od
rw5o2l3d2
rw2ol1
r1wr
rwr5es1
rwr4iaeth1o
rwri2
rw2r1ia
rwr3iae2t
rwriaet2h1
rw2y
r4w2yb
r5wydden.
r2wydd3
rwyd4de
rwyd2d1en1
rwydd4iad4u
rwyd4d1i4
rwydd1ia
rwyddiad3
r4w2yde
r4w2yd3o
rwydr3
r4wyd1y
3rwym1
rwyn3
r4wys1
3ry.
3rybl1
r2yb
ry3bo
rych5w1a2i
rych1
rych2w2a
r2yd
r4yd.
ry5d2ano
r2yd1a2
ryd2a2n3
rydd4on.
rydd3
ry4ddon3
rydd1o
ryd1l4
ry3f4a
r2yf1
ryf2e
ry3fer
ryf4od
ryf3o
ryl3a
ryl2e
ry4l5it
ryl1i2
rym2r3
rym1
ryn3a
ryn2c4
4ryn1d2
ryn3e
r2yn3f2
ryng5a
r2yng1
4ryn3n2
rynod4
ryn1o
ryno5d1ed
ryno5d3er
ryn3yc
ryn1y
r2ys3b2
rys1
rys5ba
rysg5w2
rys2g
rys4g3y
ry3wa
r2yw
ry2w3i
s1
sach3
s2ac
saf3a
saf3o
san3a
sa2n
san3e
san3o
sar3f5a
s2ar
sa2r2f
sat4a
s2a2t
sat2h4
sathr2aw4
sath2r
s3a2u
sá4it
s2b2
sb3ad4w
s4b1a2i
s3be2t
sb3iw
s2b1i
sb5iyc
sb1iy
s3bl
sb2r5io
sb2r
sbr2i2
sd4or
sd1o
se2
sec4a2n
s2ec1a
sedd3
3sef
se5i2on1
se2i
s2e1i1o
sen5ol1
sen3o
senw3
s4er2c
ser4ch5
s4er3i2
s2e2t
sf4am1
sfedd4
sff4y
sf2f
sf4wy
sf4yr
s2g
s5g4adr
sgad1
sg3ad1w
s3g2am1
sg3a2n
sgar5a
sg2ar
s3g2aw
s3g2ed3a2
sg1ed
s3g2ed1e
s4ged3ig
sge2d1i
s5g2ed1o
s5g2ed1w
sg2e2l4
sg5en.
s3ge3na
sge5ne
s4g1ia
sg1i
s4gl.
sg2l
sgl3a
sgl3o
s3g2n
sg3n2i
sg3od
sg4od.
sgo4g3
sg4ol1
sg3om1
sg3on1
sg5oty
sg2ot1
sg5r2w2y
sg2r
sg5r2yw
s4g3w2c
sgw2
sg3wn1
s4g1yc
s3g2y4f3
sgy3na
s1gy5nes1
2si
s5ial.
s1ia
s2i2al1
s5ial1u
s2i4am2
5s4ian1d2
sia2n
s4i2ar
s3id3
s2id4a2
s3ie
s4ie2t
s2ig
s3ig.
si4ga
s3ig1e
sig2l3
5sigl.
s3ig2r
s5igyn
s2ig1y
sil4f2
s2i2l
s1in1s4
s2in
s3io
s3ir1
s3it
si4wr
s2iyn3
s1iy
2s2ï1
2sl2
s2l4a2u
slo3n2a
slon1
sl2o3n4e
s3ly
sl2yw4
sm2
sm4ar
sm4er
smwyt5h4aso
sm2wyt
smwyth3a
smwyt2h
s1mwyth1as1
s4na2u3
sn2e
sn1e2g2
s2n3i
sn4o2b
s3oc
sodd3
sod4l3
s3oe
sof4l3
2s3og3
s3om3
son3
s4on.
s4o4n3a2u3
son4deba
s2on1d2
son4der
s3one
s4ong
sra3n1a
sra2n
sra5n2e
s2t
s1t3ac
s4t2ad1e
s4t1af
st4am1
st2a2n
s1t3as1
s4t1a2u
st5aw2c
st2aw
s4t1ec
s4t1ed
s4te2i
s4t3em1
s4t1en
s4t1es1
st3f
s5t2ir3o
s2t1i
stir1
stl3o
st5lyt
st2o
s3t4od.
sto3r2a
s3tor
sto3r4e
st4ra
s3tra.
str3ec
s1tr3es1
str3oc
str3ol3
s4tr3w
str3yc
st2u
s1t3ur1
st5us.
stus1
s5t2wy1i
stw2
s1tyr3
s2u
sur3
s3us1
s2w2a
s3w2c
swcr3
s3we
s4wed
sw5edd
swen3
2s2wi
swm2p3
s2wm1
s3w2yf1
swyn3
swy4r
s2y
s3yc
s5ych.
sych1
s3yd
syf4l3
s2yf1
2syg1
syll3a
syl2l
syll2t3
sym4l3
sym1
symud3ad4
sym2u
symud1a2
2s3yn.
syn4fe
s2yn1f2
s3yr
syr2a
syr2e
s3ys1
3syt
s4y2wa
s2yw
1t2ac
tach3
3t2ad4l3
tad3r
t1af
ta4fa
taf4l2
t2a2g3
t1a2i
t3aid
t5aliae
t2al
tal3i2
tal1ia
tal2m3
t1a2n
4tan2c
tan3e
tang5n2
t2ang1
tan3o
tan3w
t3ao
3ta2r4d2
t2ar
ta2r4f
t1as1
t2a2t1
t1a2u
ta2wl3
t2aw
t1b
t3ch
t1ec
t1ed
tedd3
4teg.
te2g1
4teg1io
teg1i
t3e2i2d3i
te2i
teimlad4w
te2im2
teiml3
tel4y
t2e2l
t3em.
tem1
t4e2mi
t1en
t2en4d2
te4ne
t2e2r2f4
terf2yn5
t1es1
t4es3a
tes4io
te2si
tet4a
te2t
3t2ew
4tew.
4tew2c
tew5id
te2wi
t1f
tf2f2
tff4e
tfod4
tfydd4
tfyd2
t1g2
tg4af
tg4a2n
tg4en
tg4er
tgl4a
tg2l
tg2n2
t2g2or
t5gor.
t5go2r1ia
tgor3i2
t5go2r2n1
tg4w2c
tgw2
tg4wy
t3g2y3w
t2h
thal3ad4
th2al
thal4m3
thang5n2
tha2n
th2ang1
th4ar
tha2r4f
th4a2t
that5y
th1e
th4ef
th5el2l1
th2e2l
therf2yn5
th2e2r2f
th2e2t4
thl3a
thl2
thl5ent
thl4en
th5l4e2t
th3n2
th5nod3
th1o
th5o2l3d2
th2ol1
thol4l2t4
thol2l1
th2on4e
thon1
thorad4
th2or
thor1a
thr3ac
th2r
th3red
thr5ent
thrid4
thr2i2
thr2o3f
thr2o
th5r2wf2
thr2yd4
thr2y5d1a2
th3ug
th2u
th3um1
th3un1
th3us1
th1w
th3w2a
th4wl
th3wyd
th2wy
th3w2yf1
thwys2g4
th2wys1
th3ych1
thydd5
th5yma
thym1
thyr4f4a2u
thy2r2f3
thyr1fa
thyr3w
th2y4w
2t1i
t3ia
tid3
t3ie
t3in
ting3
t4in1o
ti2on4
t1io
t4iono
tï5ol.
tïol1
tl3a
tl4ae
tl1e
tl4en.
tl3on1
tl3wy
t3lyd2
t1n2
t3och
t4od.
t3odd
to4ec
to3e2d3i
to4em1
to3e2si
toes3
tof3
t3og3
3to2is1
t2ol1
tol4l2t4
tol2l1
tol3y
t1om1
t3om.
t1on1
t2on4e
t3on2t
3tor
tor2a
tor4c
t3os1
to4w2c
to4wn1
tr2a3c
tr4a3dd
tr3ad1w
tr3af.
tr2a3g1
tra3n1a
tra2n
tra5n2e
tr3ant
3tr2aw
tr3ed
3tr4ef
tre4f5a
tref3l3
4tre2g1
tr3em.
trem1
tr3ent
3tr2ew
tr3id4
tri2
tr5ig.
trig3
tro4ad1a2
t2r2o1a
t4road
tr3odd3
tro5fa
tr2of
tr3ola
trol3
tr3olo2
tr3olw
tron4o
tron1
tr3on2t
2trw
tr4wm1
tr3wn1
tr5w2yd.
trw2y
t5r4wydd3
tr3w2yf1
tr2y3d1a2
tr2yd
tryd4y
tr2y3f1
try3l
tr5yn.
3tr2yw
ts2
ts5ach3
ts2ac
t1se2
t2s3i
3tud
tudr4
1tum1
t1un3
1tur1
t4ur1m2
tw2
t3w1a2i
t2wa
t1w2c
t1wn1
t1wr1
tw2r4n1
3t2w2y1a
t3wyd
3t2wy1e
t3w2yf1
tw4ym1
3t2wy1o
twys2g4
t2wys1
3t2w2yw
t1yc
t1yd
tydd5y
tydd3
ty5g4ar
tyg1
tyg3a
ty3l1i2
tym2p4
tym1
4t3yn.
tyng5ad1
t2yng1
1tyr
tyr2a
tyr4es1
tyr1e
ty3wr1
t2yw
2u1a
u2al3
u2an1d2
ua2n
u4an1e
u3ar
u3aw
ub1
2uc
uch1
uch3e
uch5ed
ud3ad
ud1a2
u5dal1e
ud2al
udd1
udd3a2
udd4e2g1
udd3e2l
udd3f2
ud2d3i
ud5eir1
ude2i
ud3er
u2d3i
ud1l
udr3
ud5r2wydd3
udrw2y
ud2w
ud3wn1
ud3wr
ud3yn
u1e
uedd3
u4estai.
ues4t3a
ues1
ues2t
uest1a2i
u4es4t1a2u
u4est1wr1
uestw2
u4esty
uf5au.
ufa2u
uff4y
uf2f
uf3y
ug3ad1
ug3af
ug3en
ug3i
ugl3a
ug2l
ugl3e
ug3lw
ug2n3
ug1o
ug1u
ug1w2
ug3y
u1h2
u1i
ul3ad
ul3af
u5l4an.
ula2n
u5l4an1n2
u4l3ant3
u5lat2h
ul2a2t
u2l3d2
u2l1e
ul1f2
ul5ig2r
ul1i2
ul2l1
u1lo
u2l3oc
u2l3od
ulon3
ul2on5e
ul1u
ul1w
u2l3yc
u3lyd2
un1
un4ed3y
un5el2l1
un2e2l
un5es.
unes1
un3i
un2ig3
un5od.
un2ol1
un5ol.
2u1o
uog3
u3os3
up2
ur1
urb4w
u2r1b
ur5d2d4u
u2rd2
ur2dd
ur3e
ur5fa2u
u2r2f
ur1fa
ur4fl2
ur2gy
u2r1g2
4urn.
u2r2n1
ur2of4
ur2s3
ur4t3y
ur4ud
u5r2wydd3
urw2y
ur3y
ur4yw
1us.
us1
us4edd1a2
use2
usedd3
us5en1d2
us2g1
4usi.
u2si
us3o
3usr
us3ter
us2t
us3tod
ust2o
us3tr
ut3a
ut1e
uth4r3
ut2h
uth3u
uth4un1
ut3o
utr3
2u1w
u2w2c
uwch3
u1y
2wa
wac5ew
w2ac
wac1e
wadd3
wad2n3
w5adwy.
wad1w
waen4i
waen3
waer2
w2a2g1
w1a2i
w3ai.
w3aid
w2air1
w3a2is1
w4ait
wall2t5
w2al
wal2l
w4an.
wa2n
wan3a
wan3e
wan3o
war5ddr2
w2ar
wa2rd2
war2dd
war3e
wa4r4edd
wa2r5ia
war1i2
war4t2h4
wart5h1a2i
warth3a
wart5h1as1
wart5h1i2
war4t5h1w
war3w
3w4as.
was1
w3ase2
w2as4g
w3a2si
w3aso
w4as4t
w3asw
wat5er
w2a2t
w1a2u
2wb
wbl5es1
w2c
2wca
wc4ed
wc1e
wch1
4wch2u
2wc1i
wc5wl2l
wc1w
wc2wl
wc4yn
2wd
wdd3e2g1
w5dd4ew
wd2e
wd3ed
wde3n4a
w2d1en1
wde5n4e
w2d3i
wd4i1h2
wd3ly
w3d2od.
wd1o
wdr1
wd4ra
wdry4
wd2u
w1eb3
2w1ec
2w3ed.
w4ed3a2
4w2edd2f2
4wed2d1i
4we2d1i
w3ed3ig
we4g1i
we2g1
weg2r4
we2in3
we2i
well5t1i
w2e2l
wel2l1
well2t
wel3o
wel2w5l
2w1em1
wen3a2u3
w2en4d2
2w3ent
wen2wyn5
wen3y
2w3er.
wer4i2
wer5id
w4er1s2
wer4yd
2wes1
4w3e2si
w4es3ir1
w4es3it
5west.
wes2t
west4a2i
wes4t3a
w1e2t
w4et2h1
2we2u
weu2g
w2eun3
2wf2
w1fa
w1fe
wff3a
wf2f
w1f1i
wf4id
w1fo
wfor2
w1fw
wf4wy
w3fy
wg1
2wg.
w5gig.
wg1i
wg2ig1
2wg2l
wg3n
2w1h2
w3he
w3hw
2wi
wi4a2n
w1ia
wib5a2n
w2ib
wibl5a
wib5ol3
widl3
wi4fr
w2if
3wig1
wigl5e
wig2l
w2i2l3
win5gad3a2
w2in
wing2ad1
win5g2ad1e
win5ga2d3i
win5g2ad1o
w4i2one
w1io
wi2on1
wir3
wis2g3
w2is1
w1it
3w2iw.
wiw4e
2wl
3w2lad.
wla2n3
wl4co
wl2c
wl3in
wl1i2
w4l2ip1
wll5yn
wl2l
wl5ws.
wlws1
w2l4yc
2wm1
wm3a
wma2n3
wm4b2r
w2m2i
wm5ia2i
wm1ia
wm5ia2n
wm4wl
wn1
wn5adwy.
wn2ad
wnad3w
wn2ae
2wn1d2
wn3de
wn3d1i
wndr3
wn4e2i
wn4êl
2wn3g
wn2g2l4
wn3in2
wn1i
wn3l2
wn2o
w4n3oc
wn3odd
wn3og1
wn3ol1
w4n3om3
w4n3on1
2wnw
2w1o
w2od
w3od.
w3odd
w2ol1
w3ol.
w3olae
w2or
2wp1
wp3e
wp2l1
wp5w2rd2
wpw2
wpwr1
wr5ae2t
wr2ae
wrb5yn
w2r1b
wr2by
wrc2
wr3c2a
wr4c3e
wr4ch3
wr4c1i
wr5c2wd
wrc1w
2w2rd2
wr5de2i
wr1de
wr3ed
wr4eig3
wre2i
wr5e2si
wres1
w2r3f
wr5fa2u
wr1fa
wr4f1i
4wri.
wri2
wrid3
wr3id.
w2r3ie
wr3l2
wr4l2l
wr3n2a
w2r2n1
wrn4es1
wr3no
wr1o
wr2t
wrt2h3
wr1w
wr4ws1
w5r2wydd3
wrw2y
wr2y4w
ws5bre
ws1
ws2b2
wsb2r
ws3e2
ws3g
ws4g2l
ws4ig
w2si
w2s4og3
ws4ta
ws2t
wst5a2n
ws5ter.
wstr3
ws4us1
ws2u
ws3w2a
2wt
wt3a
wt1a2n3
wt3em1
wt5ery
wt2h1
4wth.
wth3w
wt3od
wt3wy
wtw2
wt3y
2w1w
2w2y1a
wy5al2c
wy2al
4wyb2r1
w2yb
wybr5y
wy3bu
w1yc
wych3
w2yd3a2
2wydd3
wydd4ly
wydd1l2
wydd4yd
wydd2y
wydr3o
2wyd1y
2wy1e
wy3fr
w2yf1
wy3h2
2wy1i
2wyl
wyl4deb
wy2l1d2
wyll3a
wyl2l
wyn5ad.
wyn2ad
4wyn1d2
wyn3e2g1
w2yn3f2
w2yn3g4
wy4n1i
wyn3o
wyn3y
2wy1o
wyr3ad
wy3ra2n
5wyrdd.
wyr4dd3
wy2rd2
wyrl3i2
wyr2l2
2wys1
2wyt
2w2yw
wy3wr1
wy3wy
2wy1y
2y1a
y3ar3
y4ar.
y4a2r2n1
2yb
yb4ac
yb5edd
y2bed
yber4
ybl1
yb3ly
yb2r1
ybr3i2
yb3w
ych1
ycha2n5
ych4anwr
ychan1w
ych5e2i2
ych3r
4ychw2e
ych3wy
ychwy5na
ychwy5ne
ycl3
2yd.
2yd1a2
yd3ad
yd4al
yd2a2n3
y3d2an3a
y3d2an3e
yd3ant
y5d2an1w
y3d2ar
y2d3as1
y2d3a2u
ydd3
ydd5a2n
ydd1a2
yd4de
yd4d2f4
yd4d1i4
ydd4in.
ydd2in
ydd4of
ydd1o
ydd5yn.
ydd2y
ydd4yn
yddy5ne
ydd4ys4g
yddys1
2yde
y3deb
yd3ed
yd4ed2da2u
ydedd1a2
yd3e2i
yd3er
yd4e2u1
yd5ffu
yd1f2
ydf2f
ydfwr3
ydf2yn3
y2d3i
yd1l
yd4ma
yd1m2
yd2ne
yd1n
ydn2e5b4
2yd3o
yd4od.
ydol3
yd4os3
4yd2r1a2u
ydr3ec
ydr3em1
ydr5es3id3
ydres1
ydre2si
yd3r2ew3
yd4ri2
4yd2r1ia
ydr3oc
4ydr3ol3
ydr5w2yd.
ydrw2y
yd5r2wydd3
4ydry
ydr3yc
2yd1w
yd3w2a
yd5wed
ydweithi5ol1
ydwe2i
ydw2eit
ydwe2it2h1
ydweith3i2
ydweith1io
ydwel5e
ydw2e2l
yd3wr
yd1y
yd2y4l
y1e
y3e2l
y4er1a
y4e2r2n1
2yf1
y4f3a2g
yf3a2i
yfan3t2
yfa2n
y4f3ar2e
yf2ar
yf3ar1h2
yfar5w2a
yf3ed3a2
yf3ed1e
yf3e2d1i
yf3ed1o
yf3ed1w
yf3e2i
yf2e2l3
yf5e2r2f
yfe2r3n1
yf5e2si
yf1es1
yf5e2wi
yf2ew
yf2f1
yf4fa
yf5f2ait
yff1a2i
yf5fe2i
y4ff3i
yff5in3
y4ff2l2
yff2r3a
yffro5ed3
yffr2o
yffro2e
yffro5em1
yffro5en3
yffro5w2c
yff3row
yff2r3w
y4ff3ry
yf3i
yfl4ed
yfl2
yflo3e
yf3ne
yf2n
yf3no
yf3n2y
yf3o
yf5od.
yf2og2l4
yf1og1
yf5r2ait
yfr2a
yf2r1a2i
yfra5n2e
yfra2n
yf5r2yw
yf2ry
yf3u
yf5wn3g
yf1wn2
yf3yg1
y4f5yn.
yf2yn
yfy3na
yfy5ne
yf3yng5
yf4yt
yg1
yg3a
yg5ad2u
ygad1
yg4ar
yge2g4
yg4eid
yge2i
yg3i
y3g4i2l
3yg2in
ygl3a
yg2l
ygl3o
ygl3w
ygl3y
yg2n3
yg3o
yg4oe
yg4of
yg2r1
ygrad4
yg5wyd
ygw2
y4g1yc
4ygyd
y1h2
y2he
yh2e3i2
yh2e3w
y1i
y3ie
y2l3ad.
yl5adwy.
ylad1w
yl3af
yla2n3
y4l3ant3
y5law.
yl2aw
2yl2c
ylch3w
ylch1
yl4der1a
y2l1d2
yl1e
yl1f2
y3l2in
yl1i2
y4lit
yl2l5ad.
yl2l
yll3e
4yll1f2
yll5id1a2
yll1i2
yl2lid
yll3o
yll3w2
yll3y
yl5n2os3
yl1n
y2l3oc
y2l3od
yl3on1
yl5or1e
y4lu.
4ylwe
yl3wy
yl1y
ym5ait
ym1
y2ma2i
ym4al
ym5an.
yma2n
yman5t
ymar5w
ym3ar
ymbl2
ym5edr3
ym4er3i2
ym2er
ym5es3u
ymes1
3ymg2
ym3he2u
ymh2
ym2le
yml2
ym2l1i2
yml2o5ne
ymlon1
ym4ol3i2
ymol1
ym3on1
ymp3a
ym2p1
ym4pi
ymp5od
ym3p2r2
ymra5n2e
ymr2
ymra2n
ym4r5ig3
ymri2
ym2ro5e
ym4ru
ym3se2
yms2
ym4um1
ym2u
5ymw2y3b
y2m3y
ymyn5y
ym5yra
ym3yr
ym5yr1e
ym5yri2
ym5yr1o
ym5yr1w
yn4ad1a2
yn2ad
yn3ae
yn3af
yn3a2i
yn3a2n3
ynas3
2yn3a2u3
yn4aw
yn5byn
yn1b2
yn2by
ync5ed
yn2c
ync1e
yn3d4ir1
yn1d2
yn2d1i
yn4eb1
yn3ec
yn3ed.
yn3edd
yn2e2g1
yn3e2i
yn3em1
yn3en
yn3er
y3nes3a
ynes1
y4nes3a2u
2yn1f2
ynfyd3
yn2fy
2yng1
yn4ge
yng5er
yn3g2l2
yni2aw4
yn1i
yn1ia
yni4d
yn3i4f
y3n2ig
y4n5ig1y
yn3i2l3
yn3n2
yn1o
yn5o5ad
yn2o1a
yn5odd
yn4o2d3i
yn4ody
yn3oe
y2n3os4
2yn1r2
ynt1
ynt4a
yn4te
yn4t1i
yn4to
yn4tu
yn4ud
yn1u
yn1w
yn3wa
yn2w4e
yn3wy
yn4wyr
yn1y
yn2yc
ynydd5
y1o
ypl3a
yp1
yp2l
ypl3e
ypl3o
ypl3w
yp3ly
yr3ae
yr3af
yra3n1a
yra2n
yra3n2e
yr3ant
y4r3a2u
yr4ch
yrch3e
yrch3o
yrch3w
yrch3y
yr4dd3
y2rd2
yr5dd1yd
yrdd2y
yr1e
yr2e2i
yr5el.
yr2e2l
yr2en5d2
yrf3e
y2r2f
yr3f2f4
yr4fu
yrf5yd2
y4r1ia
yri2
yr3id
yr2l2
yr3ly
y2r2n3
yr1o
yr5ol.
yrol3
yr2s2
yr1w
yr5way
yr2w2a
yr1y
2ysa
ys1
ys3aw
2ys2b2
ysb5ïw
ys4bl
ysb3yd
ys2by
2yse2
ys5etl
ys2e2t
2ysf
4ysgar.
ys2g
ysg2ar
ys5gar1a2i
ysgar5a
ys5garasa
ys1ga2r1as1
ys5ga2r1ia
ysgar1i2
ys5gar3w2y
ysge4
ysgl4e
ysg2l
ysg5lw
ys4g2n
3ysg2r
ys4gy
2y2si
ys5ni.
ys2n3i
2yso
ys3od
4ysol1
ys5ol.
ys3ta
ys2t
4yste
yst5eb
ys5t1ed
ys3ter
ys4try
yst4w2
ys3u
2ysw
ys4we
y2s4wi
2ys3y
y2s4yg1
yt3ad
yt1e
yth3a
yt2h
yth3e
yth1i3e
yth1i2
yth3l2
yth3o
yth4re
yth2r
ythr5ec
yth3r5ed
ythr5es1
yth5re2u
ythr3o
yth5r2wb
yth4r5yc
yth5ur1
yth2u
yth3w
yth3yn
yt5ir3o
y2t1i
ytir1
yt3o
ytr2
yt3r1as1
yt3s2
ytw5ad
ytw2
yt2wa
yt3wy
yt5ysa
ytys1
2yw
yw4ae
y2wa
y3w4ait
yw1a2i
y1w2c
y3wedd
y5wedd1ia
y4wed2d1i
yw5eg.
ywe2g1
y4w2e2l
yw5en.
y2w3es1
yw1g2
y4w1ia
y2wi
yw3id
y4w1io
y4wir.
ywir3
y1wn1
yw3ol1
y2w1o
y2wr1
ywr4a
ywr5a2in
ywr1a2i
y4w1yc
y3w2yf1
ywy3na
ywy5ne
y1y
PK
!<rE{{hyphenation/hyph_da.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a1e3
.an3k
.a4n1s
.be5la
.be1t
.bi4tr
.de1r3i
.diag1no5
.di1a
.her3
.ho1ved3
.ho5ve
.ne4t5
.om1
.ove4
.po1
.til3
.y5d5r
ab5le
3ab1st
a4bs
a3c
ade5la
a1de
5ad1g
a1e
5af1g
5a4f1l
af3r
af4ri
5af1s4
a4gef
a1ge
a4gi
ag5in
ag5si
3ag1ti
a4gy
a3h
ais5t
a3j
a5ka
a3ke
a5kr
aku5
a3la
a1le
a1li
al3k
4alkv
a1lo
al5si
a4ls
a3lu
a1ly
am4pa
3ana1ly
a3na
a1nal
an4k5r
a3nu
3a4n1v
a5o
a5pe
a3pi
a5po
a1ra
a4r5af
1ar1b
a1re
5arg
a1ri
a3ro
a3sa
a3sc
a1si
a3sk
a3so
3a3sp4
a3ste
a3sti
a1ta1
a1te
a1ti
a4t5in
a1to
ato5v
a5tr
a1tu
a5va
a1ve
a5z
1ba
ba4ti
4bd
1be
be1k
be3ro
be5ru
be1s4
be3tr
1bi
bi5sk
b1j
4b1n
1bo
bo4gr
bo3ra
bo5re
1br4
4bs
bs5k
b3so
b1st
b5t
3bu
bu4s5tr
b5w
1by
by5s
4c1c
1ce
ce5ro
3ch
4ch.
ci4o
ck3
5cy
3da
4d3af
d5an1ta
da4s
d1b
d1d4
1de
de5d
4d3e4lem
de3le
der5e1ri
de1re
de4rig
de1ri
de5sk
d1f
d1g
d3h
1di
di1e
di5l
d3j
d1k
d1l
d1m
4d1n
3do
4dop
d5ov
d1p
4d5re4t1t
5d4re3ve
3drif
3driv
d5ros
d5ru
ds5an
ds5in
d1ski
d4s1m
d4su
d3su5l
ds5vi
d3ta
d1te
dt5o
d5tr
dt5u
1du
dub5
d1v
3dy
e5ad
e3af
e5ag
e3ak
e1al
ea4la
e3an
e5ap
e3at
e3bl
e4bs3
e1ci
ed5ar
e3da
ed1de4
ed1d4
eddel5
e4do
ed5ra
ed3re
ed3rin
ed4str
e3e
3ef1f
e3fr
3e4ft
e3gu
e1h
e3in
ei5s
e3je
e4j5el
e1ka
e3ke
e3kl
4e1ko
e5kr
ek5sa
3eksem
ek1se
3eks1p4
e3ku
e1kv
e5ky
e3lad
el3ak
el3ar
e1las
e3le
e4lek
3elem
e1li
5elim
e3lo
el5sa
e4ls
e5lu
e3ly
e4mad
e1ma
em4p5le
em3pl
em1s
e4n5ak
e3na
e4nan
4en1n
e4no
en3so
e4ns
e5nu
e5ol
e3op
e1or
e3ov
epi3
e1pr
e3ra
e4r3af
e4rag
e4rak
e1re
e4ref
er5e1ge
5erhv
er1h
e1ri
e4ri3b
er1k
ero5d
er5ov
er3s
er5tr
e3rum
er5un
e5ry
e1ta
e1te
etek4s
e1ti
e3tj
e1to
e3tr
e3tu
e1ty
e3um
e3un
3eur
e1va
e3ve
e4v3er1f
e1vi
e5x
1fa
fa4ce
fa3c
fags3
f1b
f1d
1fe
fej4
fejl1
f1f
f1g
f1h
1fi
f1k
3fl
1fo
for1en
fo4ri
f1p
f1s4
4ft
f3ta
f1te
f1ti
f5to
f5tvi
1fu
f1v
3fy
1ga
g3art
g1b
g1d
1ge
4g5enden
ge4nd
gen1de
ger3in
ge1ri
ge3s
g3f
g1g
g1h
1gi
gi4b
gi3st
5gj
g3k
g1l
g1m
3go
4g5om
g5ov
g3p
1gr
gs1a
gsde4len
gs1d
gs1de
gsde3le
g4se
gsha4
g4s3h
g5sla
gs3or
gs1p4
g5s4ti1de
gs3tid
g4str
gs1v
g3ta
g1te
g1ti
g5to
g3tr
g4t4s
g3ud
gun5
g3v
1gy
g5yd
4ha.
heds3
he5s
4het
hi4e
hi4n5
hi3s
ho5ko
ho5ve
4h3t
hun4
hu4nd3
h1vo4
i1a
i3b
i4ble
i1c
i3dr
ids5k
i1el
i1en
i3er
i3et.
if3r
i3gu
i3h
i5i
i5j
i1ka
i1ke
ik1l
i5ko
ik3re
ik5ri
iks5t
ik4tu
ik1t
i3ku
ik3v
i3lag
il3eg
il5ej
il5el
i3li
i4l5id
il3k
i1lo
il5u
i3mu
ind3t
i4nd
5in1f
ings1
i4n3s
in4sv
inter1
in1te
i3nu
i3od
i3og
i5ok
i3ol
ion4
io4ns1
i5o5r
i3o3t
i5pi
i3pli
i5pr
i3re
i3ri
ir5t
i3sc
i3si
i4s1m
is3p4
i1ster
i3sti
i5su1a
i1ta
i1te
i1ti
i3to
i3tr
it5re.
i1tu
i3ty
i1u
i1va
i1ve
i1vi
j3ag
jde4rer
j1de
jde1re
jds1
jek4to
jek1t
4j5en.
j5k
j3le
j3li
jlmeld5
jl1me
jlmel4di
j3r
jre5
ju3s
5kap
k5au
5kav
k5b
ke4l5s
ke3sk
ke5st
ke4t5a
k3h
ki3e
ki3st
k1k
k5lak
k1le
3klu
k4ny
5kod
1kon
ko3ra
3kort
ko3v
1kra
5kry
ks3an
k1si
ks3k
ks1p4
k3ste
k5stu
ks5v
k1t
k4tar
k4ter1h
kti4e
kt5re
k4t5s
3kur
1kus
3kut
k4vo
k4vu
5lab
lad3r
5lag1d
la4g3r
5lam
1lat
l1b
ldiagnos5
l1di
ldi1a
ldiag1no
l3dr
ld3st
1le.
5led
4le3le
le4mo
3len
1ler
1les
4leu
l1f
lfin4
l1fi
lfi4nd5
l3go1
l3h
li4ga
4l5i4n3s
4l3int
li5o
l3j
l1ke
l1ko
l3ky
l1l
l5mu
lo4du
l3op
4l5or
3lov
4l3p
l4ps
l3r
4ls
lses1
l1se
ls5in
l5sj
l1ta
l4taf
l1te
l4t5er1f
l3ti
lt3o
l3tr
l3tu
lu5l
l3ve
l3vi
1ma
m1b
m3d
1me
4m5ej
m3f
m1g
m3h
1mi
mi3k
m5ing
mi4o
mi5sty
m3k
m1l
m1m
mmen5
m1me
m1n
3mo
mo4da
4mop
4m5ov
m1pe
m3pi
m3pl
m1po
m3pr
m1r
mse5s
m1se
ms5in
m5sk
ms3p4
m3ste
ms5v
m3ta
m3te
m3ti
m3tr
m1ud
1mul
mu1li
3my
3na
4nak
1nal
n1b
n1c
4nd
n3dr
nd5si
nd5sk
nd5sp4
1ne
ne5a
ne4da
nemen4
ne1me
nemen1t5e
neo4
n3er1k
n5er1l
ne5sl
ne5st
n1f
n4go
4n1h
1ni
4nim
ni5o
ni3st
n1ke
n1ko
n3kr
n3ku
n5kv
4n1l
n1m
n1n
1no
n3ord
n5p
n3r
4ns
n3si
n1sku
ns3po
nsp4
n1sta
n5sti
n1ta
nta4le
n1te
n1ti
ntia1li4
nti1a
n3to
n1tr
nt4s5t
n4ts
nt4su
n3tu
n3ty
4n1v
3ny
n3z
o3a
o4as
ob3li
o1c
o4din
o1di
od5ri
od5s
od5un
o1du
o1e
of5r
o4gek
o1ge
o4gel
o4g5o
og5re
o1gr
og5sk
o5h
o5in
oi6s5e
o1j
o3ka
o1ke
o3ku
o3la
o3le
o1li
o1lo
o3lu
o5ly
1om1r
on3k
ook5
o3or
o5ov
o3pi
op3l
op3r
op3s
3opta
op1t
4or.
or1an
3or4d1n
ord5s3
o3re.
o3reg
o3rek
o3rer
o3re3s
o3ret
o3ri
3orient
ori1e
ori1en
or5im
o4r5in
or3k
or5o
or3sl
or3st
o3si
o3so
o3t
o1te
o5un
ov4s
3pa
pa5g1h
p5a4n1l
p3d
4pec
3pen
1per
pe3ra
pe5s
pe3u
p3f
4p5h
1pla
p4lan
4p1le.
4p1ler
4p1les
p3m
p3n
5pok
4po3re
3po3t
4p5p4
p4ro
1pro1c
p3sk
p5so
ps4p4
p3st
p1t
1pu
pu5b
p5u1le
p5v
5py3
qu4
4raf
ra5is
4r1ar1b
r1b
r4d5ar
r3da
r3dr
rd4s3
4reks
1rel
re5la
r5ens1s4
re4ns
5re1se
re5spo
resp4
4res1s4
re3st
re5s4u
5re4t1t
r1f
r1gu
r1h
ri1e
ri5la
4ri3mo
r4ing
ring4se4
rings1
rings3o4r
4rin5p
4rint
r3ka
r1ke
r1ki
rk3so
r3ku
r1l
r3mo4
r5mu
r1n
ro1b
ro3p
r3or
r3p
r1r
rre5s
rro4n5
r1sa
r1si
r5skr
r4sk5v
rs4n
r3sp4
r5stu
r5su
r3sv
r5tal
r1te
r4te1li
r1ti
r3to
r4t5or
rt5rat
rt4ra
rt3re
r5tri
r5tro
r4t3s
r5ty
r3ud
run4da
ru4nd
5rut
r3va
r1ve
r3vi
ry4s
s3af
1sam
sa4ma
s3ap
s1ar
1sat
4s1b
s1d
s3dy4
1se
s4ed
5s4er
1se4se
s1f
4s1g4
4s3h
si4bl
si3b
1sig
s5int
5sis
5sit
5si1u
s5ju
4sk.
1skab
1ske
s3kl
sk5s4
5sky
s1le
s1li
slo3
5slu
s5ly
s1m
s4my
4snin
s1ni
s4nit
so5k
5sol
5som.
3som1m
s5oms
5somt
3son
4s1op
sp4
3s4pec
4s1per
3s4pi
s1pl
3sprog.
sp4ro
s5r4
s1s4
4st.
5s4tam
1stan
st5as
3stat
1stav
1ste.
1sted
3stel
5ste3mo
1sten
5step
3ster.
3stes
5stet
5stj
3sto
st5om
1str
s1ud
3sul
s3un
3sur
s3ve
3s4y
1sy1s
5ta.
1tag
tands3
ta4nd
4t3a4n1v
4tb
tede4l
te1de
teds5
3teg
5tekn
teo1
5term
te5ro
4t1f
6t3g
t1h
tialis5t
ti1a
tia1li
3tid
ti4en
ti3st
4t3k
4t1l
tli4s5
t1m
t1n
to5ra
to1re
to3ri
tor4m
4t3p
t4ra
4tres
tro5v
1try
4ts
t3si
ts4pa
tsp4
ts5pr
t3st
t3s5ul
4t1t
t5ud3s
5tur
t5ve
1typ
u1a
5ud1l
ud5r
ud3s
3ud1v
u1e
ue4t5
uge4ri
u1ge
ugs3
u5gu
u3i
u5kl
uk4ta
uk1t
uk4tr
u1la
u1le
u5ly
u5pe
up5l
u5q
u3ra
u3re
u4r3eg
u1rer
u3ro
us5a
u3si
u5ska
u5so
us5v
u1te
u1ti
u1to
ut5r
u4t5s4
5u5v
va5d
3varm
1ved
ve4l5e
ve4reg
ve1re
ve3s
5vet
v5h
vi4l3in
vi3li
1vis
v5j
v5k
vl4
v3le
v5li
v4ls1
1vo
4v5om
v5p
v5re
v3st
v5su
v5t
3vu
y3a
y5dr
y3e
y3ke
y5ki
yk3li
y3ko
yk4s5
y3kv
y5li
y5lo
y5mu
y4ns5
y5o
y1pe
y3pi
y3re
yr3ek
y3ri
y3si
y3ti
y5t3r
y5ve
zi5o
.så3
.æ3r5i
.øv3r
a3tø
a5væ
brød3
5bæ
5drøv
d1stå4
3dæ
3dø
e3læ
e3lø
e3rø
e4r5øn
e5tæ
e5tø
e1væ
e3æ
e5å
3fæ
3fø
fø4r5en
fø1re
gi3ø4
g4sø
g5så
3gæ
3gø1
3gå
i5tæ
i3ø
3kø
3kå
lingeni3ø4
lin1ge
linge1ni
l3væ
5løs
m5tå
1mæ
3mø
3må
n3kæ
n5tæ
3næ
4n5æb
5nø
o5læ
or3ø
o5å
5præ
5pæd
på3
r5kæ
r5tæ
r5tø
r3væ
r5æl
4røn
5rør
3rå1d
r5år
s4kå
3slå
s4næ
5stø
1stå
1sæ
4s5æn
1sø
s5øk
så4r5
ti4ø
3træk.
t4sø
t5så
t3væ
u3læ
3værd
1værk
5vå
y5væ
æb3l
æ3c
æ3e
æ1g5a
æ4gek
æ1ge
æ4g5r
ægs5
æ5i
æ5kv
ælle4
æl1l
æn3dr
æ4nd
æ5o
æ1re
ær4g5r
æ3ri
ær4ma
ær4mo4
ær5s
æ5si
æ3so
æ3ste
æ3ve
ø1de5
ø3e
ø1je
ø3ke
ø3le
øms5
øn3st
ø4ns
øn4t3
ø1re
ø3ri
ør1ne3
ør1n
ør5o
ø1ve
å1d
å1e
å5h
å3l
å3re
års5t
å5sk
å3t
PK
!<zhyphenation/hyph_de-1901.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ab1a
.a1b
.ab3l2
.abo2
.ab3ol
.ab1or
.a2c4k2
.ag4n
.a1g
.ag4r4
.a1g2u
.ai2s
.akt2a
.a4k1t
.al3b2r4
.a2l1b
.a1l2e
.al5l4en
.a4l1l
.al1le
.al4tei
.a4l1t
.al4t3s
.amp2e4
.a2m1p
.am4t4s1
.a4m1t
.a2n3d2
.ande2n6k
.an1de
.and4ri
.and2r4
.a4n1g2
.an3g1li
.ang1l
.an4g4s2
.ang1st3
.a6n3s
.an4si.
.an1si
.ans2p4
.an4t2a1g
.a2n1t
.an3t1h
.a2p1s2
.a1p
.ar2i1e
.a1ri
.ar1k2a
.a2r1k
.ar4m3ac
.a4r1m
.ar1ma
.ar2sc
.a4r1s
.ar4t3ei
.ar3t2e
.a4r1t
.as6se1st
.as1s2e
.a2s1s
.ass2e1s
.a1s2t
.ata1
.a1t
.at4h
.a2u3d
.a2u
.au2f3
.au4f1s2
.a2u2s1
.ausc4h3
.au1sc
.au6ste2s
.au1st
.au3st2e
.a1x2
.äm3
.ä2t2s
.äu3
.be3e2r1b
.b4ee
.be3r1a
.be3r2e
.berg3a
.be4r1g
.ber6ga1b
.ber4g3r4
.bo1ge2
.b2o1g
.bo4s3k2
.bo1s
.bu4s1er
.b2us
.bu1se
.bus5sen
.bu2s1s
.bus1se
.bu7ss4er.
.bus2ser
.c4h2
.d1a1b4
.da2r1
.da4rin
.d2a1ri
.da4r1m1
.d4a4te.
.dat4e2
.d2a1t
.d4a4te2s
.de2al
.d2e1a
.d1e1i
.de4in.
.d2e1o2
.d2e3r4en
.de1re
.der1ma3
.de4r3m2
.der3mas6
.de3s1k2
.de1s
.d2i1en2
.di2e
.do2mo
.d2om
.do1p2e
.d2o2r1f1
.d1ü1b
.ebe2r1
.e1b
.ehe1i
.e1he
.ei3e2
.ei4n1a
.einb2u3s6
.ei2n3b4
.ei4n1e4n6g
.e2i1ne
.ei3nen
.e2i2sp2
.ei1s
.ei2s1t
.ei2t2r4
.e2it
.eke2
.e1k
.el2bi
.e2l1b
.e2m3m2
.e1m
.en1
.en4d3er
.e2n1d
.en1de
.en5d4er.
.en2d3r4
.e4n1n2
.e4n2t3
.epi1
.e1p
.er8bre2c4h1t
.er3b2r4
.e2r1b
.erb1re
.erbr2ec4h
.er3da
.e2r1d
.er4dan
.er4dar
.er4d1ei
.er3de
.er4der
.e1r1e
.ere3c
.e2r1f4
.e1r1i
.e4r1s2
.er8s2t1ein
.er3s2t2e
.er1st
.erste2i
.er8ste2r1b
.er8stritt.
.ers2t2r4
.erst1ri
.erstr2it
.erstri4t1t
.er8stritt6en.
.er4zen4
.e2r1z
.es1p2
.e1s
.e4s2st
.e2s1s
.e1s2t
.e3s2t4e
.e4st2h
.e2t2s
.e1t
.eu1
.e2u3g4
.e2u3t
.eve4r1
.e1v
.e1ve2
.e4x1t4
.fä4s
.fe2i
.fer4no
.fe4rn
.fe4st1a
.fe1s2t
.fe1s
.fi4le.
.fi1le
.fi4len
.fi2s
.flö8s7se.
.f2l2
.f3lö
.flö1s
.flö3s2s
.flös1se
.flö8s7s2en.
.flö8s3s2e1s
.f1s4
.fu2sc
.f2us2
.g2a2t
.gd2
.ge5n1ar
.ge3n1a
.ge3ne
.ge3r2a
.ge3r2e
.ge3s4
.ge1t4
.ge3u
.grif8fe1s
.g2r4
.gr2if
.g1ri
.gri2f1f
.grif1f2e
.gu2s1s1
.g2us
.haf2t3s1
.ha4f1t
.ha4l2s
.ha4u2t1
.ha2u
.he2
.h2e3fe
.he1f
.her3an
.her1a
.h2e3ri
.he6r5i4n1n
.ho4me1t
.ho1m2e
.h2om
.i1a2
.i1m2a
.ima4ge
.ima1g
.i2m5m
.in1
.i1n3e
.i2n1k4
.in1n2e
.i4n1n
.in1u1
.i1r2e3
.i1s2a
.i1s
.jor3
.ka2b5l2
.ka1b
.ka2i
.ka2m1p2
.ka4t3i1o
.k2a1t
.k4a3ti
.ken6num
.ke4n1n
.ke4r3s
.ki4e
.kle2i
.kl2
.k2le
.kn4i4e
.k2n2
.k1ni
.kopf1
.k1s2
.k2us2
.le4ar
.l2e1a
.l2i2f
.li4ve.
.l2i1v
.li1ve2
.lo4g3in
.l2o1g
.lo3ver1
.l1o1v
.lo2ve2
.lö4s3s
.lö1s
.lu4st2r4
.lu1s2t
.l2us
.ma3d
.ma2i
.ma3la
.m2a4st2r4
.ma1s2t
.m1d2
.m4e2e
.me1l2a
.men8s2c4hl2
.m2e6n1s
.men1sc
.mensc4h
.men8s2c2hw
.me4n3t4
.mi2t1
.m1m2
.nä4s5c
.nä3s
.n4i4e
.no1b4
.n2o2c
.no2s
.n4o4t3h
.n2o1t
.nu1l2
.n2u2s2
.ob1a
.o1b
.ob2e2
.oh4r5s
.o2hr
.o1m2a
.oper4
.op2e
.or2a
.o4r1t2
.ort1s3e
.or4ts
.ort4st4
.os5t6a2l1g
.o1s
.o1s2t
.o3st2e2
.ost5e2n1d
.osten8de
.oste6r3e
.ost3r4
.ozo4
.o1z
.p2a4r1e
.p2a
.par3t4h
.pa4r1t
.pf4
.ph4
.po1ka2
.p2o
.p2o1k
.po4st2r4
.po1s4t
.po1s
.p1s2
.ra4s3s
.r2a3s2
.re4b3s2
.re1b
.re3cha
.r2ec4h
.rei4n4t
.r1ein
.r2e1li1
.re3l2i3e
.r2i2as
.ri1a
.richt6e
.r2ic
.ric4h
.ri2c4h1t
.r2o4a2
.ro3m4a
.r2om
.rö2s1c
.rö1s
.ru5s6s2e1s
.r2us
.ru2s1s
.rus1se
.r1ü1b
.rü4ck1er6
.r2üc
.rü2c4k
.rü4s2s
.s2a1li1
.sas2
.sa5s1s2e
.sa2s1s
.sä3s4
.sä5s2s
.sc4h4
.s2e6n3s
.s2e1r2u2
.se2t1
.sh2a2
.s1h
.si2m3p4
.si2te
.s2it
.ski1e
.s1k2
.spas4
.sp2
.sp2a
.s3pä5s4
.s2pä
.spieg2e8lei
.s2pi4e
.spie1g
.spie3ge
.spieg2el
.spiege3le
.st4
.st4o4re
.sucher6
.s2u1c
.su1che
.suc4h
.t4a1l2e
.t3an4k3l2
.t2a2n1k
.ta2to
.t2a1t
.t4e2e
.te2f
.t2e3no
.t1h4
.ti2a
.ti1d1
.ti4me.
.ti1me
.ti4me1s
.t2i2s
.ti3te4
.to4nin1
.t4o3ni
.to4p2l4
.t2o2w
.tr2a3s3
.t2r4
.tra4s1s
.tri3e4s2
.t1ri
.tr2ie
.ts2
.tu3ri
.u1f2e2
.ufer1
.ul4mei
.u1l
.u2l1m
.ul1me
.um3
.u1mo2
.u1n3a2
.un1
.u2n3d
.u3n3e
.u4n3g
.u3n2i2t
.u1ni
.ur1
.u1r2i
.u2r3i6n4s
.ur3o2m
.u1ro
.uro2p
.u4r3s2
.ut2a
.u1t
.ut3r4
.übe4
.ve5n2e
.ve2
.vo4r1
.wa4h4l
.w2a
.wa2s
.we4g5s2
.we1g
.wei4ta
.we2i
.we2it
.wi4e
.wor2
.wort5e2n6
.wo4r1t
.xe3
.ya4l
.zei2t3s
.ze2it
.zi2e
.zin4st
.zi6n1s
2aa
a1a1b
aa2be
aa1c
aa2g2r4
aa1g
4a1a2n
4a2ar
aa2r1a
a2a2r3f4
aa4r1t2
aa1s1t
aa2t4s1
a2a1t
a3a2u
a1ä
a1b
2aba
ab1auf
aba2u
ab1ä
ab2äu
1a2b3d4
a3b1e1b
ab4e1e
ab1e4il2
2a3bel
abe2la
2a3ber
ab1e2r1k
ab1e2r1r
ab1e2r1z
ab3es1s4e
a3be1s
abe2s1s
abe1s2t
ab1eß
2a3be1t
2a3b2e1w
1a2b5f4
3ab1fi
1a2b1g2
1a2b5h2
2a1bi
ab1i6n1s
ab1ir
ab1it
1a2b1k4
ab1l2
1a2bla
ab5la1g
1a2blä
2ab2le
ab4le.
ab3li
ab4lo
3a2b1lö
a2blu
ab3ma3s2
a2b1m
ab1ma
1a4b3n2
2abo
a2bo.
a2b2of
3a2bon
ab3r4
a3bra
a4brä
2a3b1rü
ab1s2a
a4b1s
1ab5sc
ab3s2i
1ab3sp2
ab1s4t4
1absta
ab3s1z
1abtei
a4b1t
ab5te
2abu
ab1ur
2abü
1a2b1w
2a3by1
1a2b3z2
2a1ca
2a2c1c
a1ce1m
2a4ch.
ac4h
ach1a
a1chal
ach3a2u
2a2c2h3b2
a1che
a2ch1e2c
a4ch1ei
a4che2r1f
a4che2r1k
a4ch1er1ö
a4ch3e2r1w
4a2c2h3f4
a1chi
a2c4h3l2
a4c2h3m
a2c2h3n4
a1cho
a3cho.
a2ch1o2b
ach1or
ac1h3ö
ac2h3r4
ach3s1u
a4c4h1s
a4c4h1t
acht5e4r3g2
ach2t1o2
ach8t3r4aum
acht2r4
achtra2u
ach8träume.
ach1trä
achträ2u
achtr2äum
achträu1me
ach8träum2en.
achträum2en
ach6tr2it
acht4ri
a1chu
a2ch1u2f
a2ch3ü
2a2c2h1v
4a2c2h1w
a1ci
ac1in
2ack.
a2c4k
ack2en
ack1mu4
ac2k1m
ackm2us3
ack2se
ac2k1s
ack3s1l2
ack3sta4
ack1st4
a1c4l2
a3co
aco4n4n
2a3cu
a1ç
a1d
2a3da.
a3d2a1b
ad2a1g
a3d4ai4
ada2m
ad3a1m2a
a2d1an
3a4d1a1p
a3d2ar3
4ad2a1v
1a2dä
a2d1c
1a2d1d2
2ade.
a1de
ade2al
ad2e1a
ad2e1fi4
ade1f
a2dein
ad1ei
2aden
ade1r2a
a2d2e1ri
4ade1s2
ad2e3s1p2
ade2s6s2
2a2d1f4
2a2d1h2
4a3di
ad2i3en
adi2e
5a2d1j
2ado
ad2o1b
2a2d3p2
2a2d1q
2ad3rec
ad2r4
ad1re
ad4re1s
ad3ru
2a2d1s2
ad3s1z
a2d2t1
2a1du
2a1e1
ae2b
ae2d
ae2i
a2e1k
a2e1la
a2e1le
a2e2o3
ae2p
3a2er2o
a2e1t
a2e1w
ae2x
a1f1a
a2fak
a2fan
a3f2ar
af4a1t
a2fa2u
2a1fe
a2f1ec
a2f1e4n1t
af1e4r1l
a2f1ex
af2f3l2
a2f1f
af4flu
2a1fi
2af3l2
a2fö
af3ra
af2r2
af3rä
af3re
af3rö
af3s2a
a4f1s
af2sp2
af2t1a
a4f1t
af2tei
af4t3e4r1l
af2t3r4
aft5re
af2tur
a2f3ur
a1fu
a1g
2aga
ag1a1b
ag1a2d
ag1ar
ag1a2u
ag2di
a2gd
ag2d3r4
ag2d1u
age1i
a1ge
age4n1a
a3gen
age4n1e1b
age1ne
a2ge4n1t
a4gentu
ag2er
age4r2al
age1r1a
2age1s
age2sa
age4sel
age1se
age4s2i
ag2e2s3p2
age2s5s
ag3es1se
age6ste1m
age1st2
age3s2t2e
ag3g3l
a4g1g
1agg4r4
3a2git
2a2gl
ag4l2a
a4g1lö
ag2n
ag4ne.
ag1n2e
a2g4nu
a2g3re
ag2r4
a2g3ri
ag4ro
ag2sa2
a4gs
ag4sam
ag3sc
ags3p4
ag6s1p2o
ag4sti
ag1st
ag2s1t2r4
2a4g1t
ag2t1h
a2gu2n1d
a1gu
agun1
2ah.
2a1ha
ah4a1t
2a1he
a2h1e2r3h4
a1h2i
ahi2n3
ahl3a2
a4hl
ah4l1ei
ah1le
ah4l3e2r3h4
ah3ler
ah2l2ö
ahl3s1z
ah4l1s
ah4n1a
a2hn
ah2nä
ahne1r4e2
ah1n2e
ah2n1t2
1ahor
ah1o2s
a2h3ö
ahr1a
a2hr
ah3r2e
ah3re4s3
ah3ri
ahrta4
ah4r1t
ahr4t3ri
ahrt2r4
2a4h2s
ah4t1s
a4h1t
a1hu
a2h1w
a1hy
a2ian3
ai1a
ai2d4s
ai1d
aid1s1t4
ai1e2
a2if2
a2i3g4
a3ik.
a2i1k
a4i3ke
ai3ku
a2i2lo
a1i2n1d
a2i1n4e
a1i4n1g
ain3sp4
ai6n1s
2ai1s
ai2sa
a3i6s4ch.
ai1sc
aisc4h
a2i3s2e
aiso2
ai2s1s2
ais3sen
ais1se
ai4s5s1t
a3iv.
a2i1v
ai1ve3
a3i2v1l2
a3i2v1s
a1j
ajekt4o
aje1k
aj2e4k1t
2ak.
1a2k4a1d
a1ka
2akal
2a3kam
2akar
ak4a1t
1a2k2a1z
2a2k3b4
2a2k3c
2a2k3d2
4a1ke
a2ke1f
ake4n2n
a2keu
2a1ki
2ak3l2
ak4li
4a1ko
2a1k2r4
ak3ra2u
3akro3
2a2k1s
ak3s1h
2akta
a4k1t
2ak4t3b2
ak3te
ak4t1ei
2akt2i1k
ak2t3r4
ak3t4ri
2akt1s4t4
ak2t3s2
2a1ku
a2kun1
4a3kü
1a2k3z2
a1la
2a5la.
ala5c4h2
a2l1af
ala2g
al1a1ge
a3l1al
al1am
al2a1mi5
al3a2m1p
al1a1na
a2l1a4n1g
a2l1a6n1s
al1a2n1z
a2lar
a3l2ar.
a3l2a1re
al2a4r1m
al3a2r1r
ala2s
a2l1a1si
al1a2s1s
2al2a1t
al1a2u
a1l3a2u1g
a1lä
al1äm
alb3ein
a2l1b
alb3ei3s2
al4be2r3h4
al4b3e2r1w
al2b1l2
al2b3li
al2boh
al2b2r4
alb3ru
al4b3s
al2dä
a4l1d
al2d3r4
al3du
2a1le
3a2l1e2b
3a2l1e1f
a4l1eh
a2l1ei
a4l3ein
a2l1el
alen1
al3en2d1s
ale2n1d
a2le4n1g
a2le2p
al1e1p2o
a2l1e2r1f
a2l1e2r3h4
al1e4r1l
3ale4r1m
a2l1e4r3t
3a2l1e2r1z
a2l1e3s1k2
ale1s
ale4t
al1eta
a2l1e3t2h
a2l1e2u
a4leur
3a2lex
alf4r2
a2l1f
3algi
a2l1g
al2g1li
1al3go
2a1li
ali4e1ne
al2ie
al2i1en
al2i2m1b2
a2lim
ali4n3al
ali3n2a
al1i6n1s
a2l1i2n1v2
alk1ar
a2l1k
al1ka
1alkoh
al1ko
al2k3s2
al2l1a1b
a4l1l
al2l3a4r
al2l1a2u
al4lec
al1le
al3le2n1d
all5er1fa
alle2r1f
al3l2e1s
1allgä
al2l1g4
alli5er.
al1li
all2ie
alli7ers.
allie4r1s
al2l1o1b
al1lo
3al2m1b2
a2l1m
2a1lo
a2l1o2b
alo2ga
al2o1g
al1op2e
a2l1o2r1c
a2l1ö
al2ö1s
3al1pe.
a2l1p
alp2e
1al1ph
al3sk4l2
a4l1s
al2s1k2
al2s1um
al3su2n1
al2t1ak
a4l1t
alt3e2i1g
al3ter
al4t3e2r1f
al2tö
al2t3re
alt2r4
al2t1ri
alt3r2ic
al2tro
alt2se
al4ts
alt4stü
alt1st4
a1lu
a1l2uf
a2lum
al1u2m1b2
a2l1ur
4a1ly
al2z1e2r4z
a2l1z
al2z1w2
2am.
2a1m2a
am1a1b4
ama1d2
ama3g
2a3mä
a1m4e
2ame.
a2me1b4
ame2n1
ame1r2a
a2m2e1ri
am2e3ru
a4m4e3s1h
a3me1s
a3me1t
a2me1w
a3mi.
a1mi
a3mie
2a3mir
a3mi1s
ami3t2a
a3mit1
ami3ti
2a2m1l2
am2mac
a2m1m
am1ma
2am2m1al
am2m1ei
am1me
am2min
am1mi
2am2m1l2
am4m2o1d
am1mo
am2m2us
am1mu
amm2u2t
a2mö
amp2fa2
a2m1p
am2pf
am3p2r2
2a2m1s
am4s2c4hl2
amsc4h2
am1sc
1amt.
a4m1t
am2t1a
am2t1ä
am2tel
am4t3e4rn
am2tö
am2t3r4
am2tu
2a1mu
2a3na.
a1na
2a2n1a1b
a3n2a3c
an4a3di3
ana1d
a3n1ak
an1a2l1g
ana4lin
an2a1li
2anam
2anan
2a3nas
an1ä3s
a1nä
1a2n3b4
2anbu
an3c4h
a2n1c
2and.
a2n1d
an3d1ac
and4a4r1t
ande4sc
ande2s
an1de
an2dex
an2d3rü
and2r4
and4sas
an2d1s
and1sa
and6s1pa4s
andsp2
andsp2a
and6s5paß
and2su
2an1du
and1ur
2a1ne
a2n3ec
a3n4ee3
an2ei.
a2n1ei
a4n3e2if
an1e4k
3a4n1e2r1b
an1e2t2h
ane1t
1a2n1f
2an3fi
anf2t3s3
an4f1t
an3f2u
4ang.
a4n1g
1anga
3ange1b
an1ge
an2g1ei
an4g3e2r1f
an3ger
an4g3e4r1l
an4ge2r1w
an4g3e2r1z
2an2g1f4
2an2g1h
2angie
ang1l
an2gl2a
2ango
ang2r4
an2g3ra
4angs.
an4g2s
ang3s1c
ang6s3p2o
angs1p4
1a2n1h2
2a3ni
an2i3d
ani5ers.
an4ie
anie4r1s
3a4nim
a4n1i6n1s
anin1
2an1j
2ank.
a2n1k
an2k1an
an1ka
an2kei
an3kl2
an4k1lö
an2k3no
ank2n2
ank2r4
an2k3ra
an2k3rä
an4k1t4
1a2n3l2
an3ma3s2
a2n1m4
an1ma
2an1mu
2a4n1n
3an3na
3an3nä
an3n2e
a2n1o2d
a1no
a3n2ol
a2n1or
a3no1s
a1nö
1a2n3r2
1ansä
a6n1s
1an1sc
ans2en
an1se
an2seu
2an2s1h
2an2s1k2
an3sk2r4
ans1p2a
ansp4
1ansp2r2
an3s2t2e
an1st
an3s2z
2ant.
a2n1t
an2t3a4r
1antá
1antei
3ante4n3n
an3t2en
an3t4h2e
ant1h
1an3t2h2r2
2an3to
an1ton4
3ant2r4
an1t3rin
ant1ri
an2tro
1an4t3w
2a1nu
a3n2u3s
a1nü
1a4n1w
2an1we1t
2an4z3b4
a2n1z
1anzei
2anze2s
2an2z1g2
an2z1i4n
2an4z1s2
1anzü
2anzw2
an2zwi
2ao
a2o1i4
a1op
a1or
a1o1s
a2o3t2
a3ot.
a1ö
a1p
2ap.
2a3p2a
2ap2e
a2pe1f4
a2pé
a2pf
ap2fa
a3pf2l2
a3phä
a1ph
a2p4h3t2
2a1p3l4
a2p2n
a2p2o1t
a1p2o
3ap1p3l4
a2p1p
ap3pu
2ap2r2
2a3pu
2aq
2ar.
a1ra
a3ra.
ar2a1b
a2r3a4b1t
a1r2a3d2
a2r3al
a3r4a3li
a2r1a4n1g
a2r1a6n1s
a2r1a2n1z
a2r3a2p1p
ar2a1p
2a2r1ar
a2r1a2u
a1rä
1a2r1b
2arb.
4arba
ar2ba2u
ar2bec
2arben
2ar1bi
ar2bl2
2arb2r4
ar2b1re
2ar4b2s2
2ar4b1t
2arb2u
ar2b3un1
1a2r1c
ar2dro
a2r1d
ard2r4
2a1re
a2r2e1a
a4r1e2f1f
are1f
a4re1g
a2reh
ar1e2hr
a2r1ein
a4re1k
a3ren
ar1en4se
are6n1s
are3r2a
ar2e2r1f
a2r1e2r3h4
a2r2e1ri
a2r1e4r1l
are3u
ar2e1w
2a2r1f
arf2r2
ar2f3ra
ar2gl
a4r1g
ar2g1n
2ar1h4
2a1ri
ar2i1a
ar2i3e4n
ar2ie
ari3e2r1d
ari3e4r1g
ari5ers.
arie4r1s
ar1im
a2ri2n3it
ari1ni
a2r1i4n1t
a3r2i3u2
ar2kal
a2r1k
ar1ka
ark3a4m1t
ark2am
ar2k1ar
ark3a2u1e
arka2u
ark3la1g
arkl2
ar2kor
ar1ko
ar4k1ri
ark2r4
ar2k1s4
ark3sa
ark3s1h
ar2le1s
a4r1l
ar1l2e
ar3m2ä
a4r1m
ar4me2r1k
ar1me
ar3m2or
ar1mo
ar2na4n
a4rn
ar1na
ar3n2e
2a1ro
ar1o1b
a2r1o2d
a2r1op
a2r1or
2a2r1r
ar2r3a1d
arre4n3
ar3r2e
ar2r1h4
arr3he
2ar1sa
a4r1s
ar4s2c4h2l2
ar1sc
arsc4h
ar1se3
ar3s2h
2ar1si
ar3t2e
a4r1t
ar2th2e
art1h
art2i
artin2
2arto
ar4t3ram
art2r4
art3re
2ar4ts
2a1ru
ar1u1h
a2r1um
a2rü
2a2r1v
arw2a2
a2r1w
2a1ry
ar2zä2
a2r1z
2arze
1ar2z1t
ar2z1w2
as1a1la
a1sa
asas2
asa3s1s2e
asa2s1s
as3a4u
asa2u2s1
as1ä
a2s1ca
a1sc
a3sche
asc4h
a4schec
a3schi
asch3la
as2c4hl2
a2s4c2h2m
a3s4chu
4a3s2e
a4se1b
as3e2m
a4s1ex
4a2s1h
a4s3h2a
as4hi
asi4n2g
a1si
2a5s2i1s
asi4st
a3skop
a2s1k2
as1ko
a4s3l2
a4s3n4
a1so1
as1o2f
a3s2ol
as1or
as1p2
a4s2ph
a1s2pi
a4s1p2l4
as2p2o
a1s1pu
a4s3s2a
a2s1s
as1s2e
a4s2s3ei
as3sel
as3ser
asser1ma6
asse4r1m
as3s2i
a4s2s1p2
a4s4st
ass1ti
ass1to
as5st2r4
as5st2u
2asta
a1st
a3stä
a4s3te1p
a3st2e
as2ter
2ast2r4
as4tra2u
a4s2t3rä
a2s2t3re
a4str2ol
a2s1tum
ast2u
a3su
a4s1w
as1wa2s
asw2a
3a2sy1l1
a1s2y
a1ße2
aßen3
2a1t
at1a1b
at2a1f
a3t4a1g
a2t1a4k1t
a1tak
ata3l
a3tam
a2t1a2pf
ata1p
at1a2u
a2ta2us
a2t1ä
a2t2c
at2e
4ate.
a2te1b
at3e2i1g
a4t2e1li
4aten
a2te1p
at2e2ru2
4ate2s
a1tex3
at2h
a2t3h2a
4a1th2e1
3a4t4h3l
4a3ti
ating1ma5
ati4n1g
atin2g3m2
3a2t1m2
4atm2us
at3mu
ato4man
at2om
ato1ma
4ator
a2t1o4r1t
a2t1ö
4at2r4
2a1t1r2a4t
a1t3rä
at3re
at3r2om
at2sa
a2ts
at4s2c2hn4
at1sc
atsc4h
at2se
at4se1t
at2si
ats1p2
at3ta
a4t1t
at4t1ak
at2t3a4n1g
at4ta2u
at2tei
at3t4hä
att1h
at2t3rä
att2r4
at2t3s4
at3tu
atu2n1
atz1er
a4t1z
at4ze2r1k2
at4ze2r1w
at2z1i
at2zo
at2z3t2
at2z1w2
a2u
2au.
2a2u1a2
2a2u1b
au2b1li
aub2l2
au2b2lo
4a2uc
auch3t1a
auc4h
au2c4h1t
au2d2r4
a2u1d
2a2u1e
aue2b
au3en.
auen1
au2e1re
au5e2r1ein
au2fa
auf1an
2aufe.
au1fe
2aufeh
auf1er
au4fe2r1k
a2u2f1f4
3au2f3n2
2auft.
au4f1t
2a2u1g
4augeh
au1ge
2au1h
au3ha
au2hu
4a2u1i
au2i1s
2au1j
aule2s
au1l
au1le
au3lü
4aum
au2mal
au1ma
au2m1o
au2m3p2
au2m3s2
4aun1
au3n4a
au3n2e
au2n2i1o
au1ni
au1nu
a4u2n1z
2aup2
aup4ter
au2p1t
aup3te
2au3r2
au2s1ah
a2us
au1sa
ausan8ne.
aus4a4n1n
aus1an
ausan3n2e
a2u2s1a4u
2au1sc
au4s4c2h2m
ausc4h
au4scho
1au4s3d2
au2s3e2r3p4
au1se
au4s3e2r1w
1au6s3f4
1au4s3g2
1au2s1l2
au2so
au2sp2r2
au1sp2
1au4s3r4
au2s1s2
3aus3sa1g
au4s1sa
au3s1se
aus4se.
au8s5sen1de
aus2sen
ausse2n1d
au2sta
au1st
au4stec
au3st2e
aus1te4r6m
au3s3t2i2e
aus3t1ri
aust2r4
1ausü
1au2s1z
a2u3ß
a4u1t
au2t1äu
aute4n4g
aute2n1
au4t3e2r3h4
3auto
2au2ts
2a2u1u2
2au1w
2a2u1x
2a2u1z
auz2w2
2a1ü
2a1v
a3v4a
av4a3t4
4a3vi
a2v2r
a2v2s
2a1w
awi3
awi2e
a1x
ax2a2m
ax1a
a1x2e
ax2i2s
2a1ya
a1yeu
ay1e
ay1si1
ay1s
ay3t2
2a1z
az2a3
az2o
az2u
ä1a
ä1b
ä5be
ä2b3l2
ä4b2s
ä1che
äc4h
äch4e1e
ächen1ma5
äche2n1m4
ächen3mas8
ä1chi
ä2c4h3l2
ä2c2h2r4
äch2sp2
ä4c4h1s
ä1chu
äck2e
ä2c4k
ä1d
ä2da
ä2d1i1a
ä1di
ä2d2r4
ä2d2s
2ä1e
äf2f3l2
ä2f1f
äf3l2
äf3r2
ä4f2s
ä4f1t2
äf2t4s1
ä1g
ä5ge
äge1i
ä2g3l
äg2n
ä2g3r4
äg4ra
ä4g2s2
äg3sc
äg3s1t2r4
äg1st
1ä2gy
äh1a
2ä3he
ä3hi
ähl1a
ä4hl
äh1l2e
äh4l3e4be
äh3le1b
2ä2h1m
äh3n2e
ä2hn
äh3ri
ä2hr
2ä4h2s
2ä4h3t4
ä1hu
ä2h1w
ä1im
ä1is.
äi1s
ä3i6s4ch.
äi1sc
äisc4h
ä1i2s1k2
ä1j
ä1k
ä2k3l2
ä2k3r4
ä1la
älbe2
ä2l1b
äl2bl2
ä5le
äl2l1a
ä4l1l
ä2l2p3
äl4s2c4hl2
ä4l1s
äl1sc
älsc4h
ä1lu
äm2i3en
ä1mi
2ä2m1l2
äm4ma
ä2m1m
ä2m2s
ämt2e
ä4m1t
2än.
än5de
ä2n1d
än2d2r4
2ä1ne
ä3ne2n1
ä2n2f5
2än1ge
ä4n1g
än2gl
än2g2r4
äng3s1e2
än4g2s
2ä3ni
änk2e
ä2n1k
än2k3l2
än2k2r4
än3n4e2
ä4n1n
2ä6n1s
än2s1c
än3s2e3h
än1se
ä1on
ä1p2a
äp2p3l4
ä2p1p
äp2p2r2
äp2s1c
ä2p1s
1äq
ä2r3a2
är4af
är1ä
ä2r1c
4ä1re
ä2r1ei
äre2n
ä2r1e1ne
är2g2r4
ä4r1g
ä2r1i4n1t
ä1ri
är2k3l2
ä2r1k
är4me4n1t
ä4r1m
är1me
är3m2e3s
är1o2
ä1rö
är1se2
ä4r1s
är2se1b
är2si
ärt4e
ä4r1t
är2t1h
är4t4s3
ä2rü
1ä2r1z
är2zw2
ä3s
ä1s4c
ä1s4e
ä3se3g2
äser4ei
äse1re
äs2e4ren
äs2e1r2i
äse3t
ä5si
ä4s1ko
ä2s1k2
äskop2
äskopf3
äs2k2r4
ä4s1l2
ä4s1p2
ä2s2s
ä6s4s1c
äs1s2e
äss3e2r1k
ä4s4s1t
ä1st2
ä3s2t2e
ä4st2r4
ä4s1w
ä1ß
äß1e2r1k
ä1ße
ä2t1a2
ä3te
ät2e1i
ä2t1ein2
äte2n
ä2t2h
ä1ti
ä1to
ä1t1o1b
ät3r4
ät2sa
ä2ts
ät2sä
ät4s2c4hl2
ät1sc
ätsc4h
ät4sc2h2r4
ät2s1i
ät2s3l2
äts1p2
ät2s1t4
ät4s3t2e
ät4sti
ät2t2r4
ä4t1t
ä1tu
ät2zw2
ä4t1z
äu2b2r4
ä2u1b
ä2u1c
äu1de3
ä2u1d
äu3el
ä2u1e
ä2uf
äu1f2e
1ä2u1g
äu4g3l
2äu1l
2äum
äu2ma
äu2m2s1
ä2un1
äu3n2e
äu1nu
2äur
ä2u1s
2ä3us.
äu4s2chä
äu1sc
äusc4h
äu4s4c2h2m
äu3se
ä3u4s3g2
ä3u2s1k2
ä3u2s3n4
äu2sp2
äu3s2s
äu6s1s1c
1ä2uß
äu2t2r4
ä2u1t
4ä1v
1äx
ä1z
â1t
á1n
ba2b1l2
ba1b
2b1a4b1s
bach7t4e
ba4c4h1t
bac4h
bac2k1s4
ba2c4k
b1a2d2r4
ba1d
2b1af
bah2nu
ba2hn
b2ai1s2
ba2ka
ba2k1er
b4a1ke
b2a2k1i
b2ak3l2
b2a1k2r4
ba2kra
3bal
ba1l2a
bal2lä
ba4l1l
bal4le1b
bal1le
bal4leh
bal6le4r1g
bal4l2i1g
bal1li
bal3t1h
ba4l1t
2b1am
ba1n2a
3b2a2n1d
ban2d2r4
b2a3n2e
b1a4n1g
ban2k1a
ba2n1k
ban4kl2
ban2k2r4
2b1a2n3l2
2b1a6n1s
ba2n3t
b1a2n1z
b1a2r3b
bar3de
ba2r1d
ba2rei
b2a1re
ba3r2en
ba4r3n
bar3z1w2
ba2r1z
3bas
ba3s2a
ba2sc
b2a2st2r4
ba1st
b2a2u3g
ba2u
bau3s2k2
ba2us
bau3sp2
ba1yo
3b2äc
bä1c4h
b2är
b2ä4s3
4b1b
b3be
bbe4p
bb3ler
bbl2
bb2le
bb2lö
bb4r2u2c
bb2r4
bb1ru
b4b2s
bbu1
2b1c
2b3d4
3be.
3b2e1a
be3an
be3ar
be3as
3be1b
b2ebe
1bec
be1c4h
be2del
be1d
be1d2e
be1di4
be1eh
b4ee
be2e2r1k
be1e4r1l
be1e3ta
bee1t
3be1f4
be3g2
2b1eier
bei1e
be2i1f4
bei4ge.
be2i1g
bei1ge
be2i1k4
be4il2
bei3la
2b1ei1me
b2ein
be1i2n1d
be1i2n2h2
bei3s2
bei2t2s
be2it
3be1k
3bel
be3las
be1la
be3lec
be1le
b2e3lei
be2l1en
be2le1t
b2e3li
bel3la
be4l1l
bel3li
bel3s1z
be4l1s
be4l3t4
1be1m
bema5s1s2e
b2e1ma
be3mas
bema2s1s
bemas8sen
1ben.
ben3ar
be2n1a
ben3d1or
be2n1d
be3n1ei
be1ne
3be4n3g
be3n2i
be4n3n
ben2se
be6n1s
ben4sp2a
bensp4
ben4sp2r2
ben1st4
ben2su
2ben4t3b2
be4n1t
b2enti
bent4r4
b1en4t1s
2b1en4t3w
ben3u2n1
ben1u
be2n3z2
b2e1o
be1r1a
ber3am
be2ran
be1r2a3s4
b4e3r4ei.
be1re
be4r3e2i1w
be4r1e2r1k
b2ere4s
ber6gan.
be4r1g
berg2an
bergescho1s8
ber1ge
berge3s2c
berge1s
bergesc4h2
bergescho7s1se
bergescho2s1s
b4e3r4in.
b2e1ri
be3r3i2s1s
ber2i1s
berma7s1s2e
be4r1m
ber1ma
ber3mas
berma2s1s
bermas8se.
berma8ssen
ber3na
be4rn
b1er2n1t
be1r1o2p
ber1ö4
ber3st4a
be4r1s
ber1st
be3r1u4m
b2e1ru
3be1s
be1s2a
besä5s4
be2s1er
be1se
be5s1lo
be4s3l2
bes2p2o
b2esp2
bes1s4e
be2s1s
b3es6st.
be4s1st
be4s3s1z
be6s2t1ein
be3s2t2e
be1st
beste2i
be4s3t2ol
be4stor
be3s2ze
be2s1z
3be1t
b1e2ta1p
be3th2a
bet2h
be1ur
3b2e1w
2b1ex
1be1z
2b5f4
bf2al2
b1fa
bf3lö4
bf2l2
bflö1s3
2b1g2
bga2s1
bga4st
b1ge3
bge1s2
2b5h2
bh2u1t2
1bi
bi3ak
bi1a
b2i1b2
bibe2
bie4st2r4
bie3s2
bie1st
bi1k2a
b2i1k
bi2ke.
b4ike
bi2ke1s
3bil
bi1l2a
bi2l1a2u
4b1illu
bi4l1l
bi2lu2
2b1i2n3b4
b2i1n2e
2b1i2n3f
bin3gl
bi4n1g
2b1i4n1t
b2i2o1
bio3d
bi3on2
bi1r2i1
b2i3se
bi1s
b1iso
bi2s2ol
b2i2sp2
bi6s4s1c
bi2s1s
bis3si
bi2st2u
bi1st
bi2stü
b2it.
b2i1ta
b2i1te
bi2tu
bi3t1um
b2i3t2us
b2i1z2
4b1j
bjek4t2o
bje1k
bj2e4k1t
2b1k4
bl2
2bl.
bl1a3b6
b3la1d
b2la2n1c
3bl2a1t
b2la4t1t
2b3l2a1w
b2le
3bl2e2a
b3le1b
2b3le1g
2b3l2e2i1d
b3lein
3ble1m
3ble4n
b3le1s2e
ble1s
ble3s1z
b4le1t
b3le2u
2blic4h
b1li
bl2i1c
3b4li2c4k
b2l2ie
2b3l2i1g
bli4n1g4
b4li1s
b2lit
3bl2i4t1z
b2lo
b4l2oc
b3lo1s2
blo3s1s2e
blo2s1s
blö3s4s
b1lö
blö1s
2b1lu2n1
3bl2u1t
3blü
2b1m
b3mas2
b1ma
4b3n2
b1ni2
b3n2i1s1
b2o4a2
bo5a3s
b1o1b3
bo2b1l2
bo2b2r4
bo1c4h2
b2oc
bo3d2
b2o1e1
bo2ei
2b1of
bo3fe
bo1i1s
b2oi
bo2l1an
b2ol
bo1la
3bon.
bo2n1d1
bon2de
b2o2ne
3bo6n1s
b1op
bo1r2a
bo4rä
bor2d1i
bo2r1d
b2or2d3r4
bo2rei
b4o1re
bo4r2i1g
bo1ri
b1o4r1t
bor2t3r4
bo2sc
bo1s
b2o4s3p2
bote3n4e
b2o1t
b4o3t2h
bot2s3t4
bo2ts
bo2xi
bo1x
bö2b3
2b1öf
b1öl
2b1p2
bpa2g4
bp2a
2b1q
b2r4
2br.
b4ra.
2b3r2a1d
b4rah
b4ra3k
bra4s1s
br2a3s2
bra1st4
3brä
brä4u
2b3re.
b1re
3br2e1a
6b5rechte
br2ec4h
bre2c4h1t
2b3re1f
2b3re1g
b3re2if
3bre1m
2b3re1p
b4rer
2b3rie1m
b1ri
br2ie
bri2er
2b3r2i1g
b4r2i1o
bro1
b3roh
2b3r2ol
b4r2on
b4r2uc
b1ru
br2u4s
bru1st3
bru2t3h
b3r2u1t
3b1rü
brü4s2s
4b1s
b2s1a1d
b1sa
b3s2a2n1d
bs1an
b2s3ar
bsas2
bsa3s1s2e
bsa2s1s
bsa4t2z
b3s2a1t
b3sä
b4s1är
b5sc
bs2ca
b6schan
bsc4h
b6s1che1f
b2s4cu
b3se.
b1se
bs1e2b
b3s2el.
bs1e1le
bse2n1
b3s2en.
bs1e4n1t
bs1er
b4se2r1f
bs3e4r3in
bs2e1ri
b4se4r1s
b3se1s
b3se1t
b3s2i2t
b1si
b4s1l2
b2s1of
bs1op
bso2r
b2sö
b2s2p2l4
bsp2
b3s2pu
b4s1s2
b1s2t
bst1a2b
bs2t3ac
b2s1t1ak
b3stä
bs3t2ät
bst3er
b3st2e
b4s2te4rn
b2s1tip
b3sto
b4s1to3d
b3stö
b3stra
bst2r4
b2s3trä
bs3t4re2u
bst1re
b3stü
b4s2t1üb
b2s1u2n1
4b1t
b3ta
btal3
bt2a4st3r4
b1ta1s2t
b5te
b2t1h
bt4r4
b2ts2
btü1
bu2chi
b2uc
buc4h
b2u2e3
bu2f
bu3li
bu1l
bul2l2a
bu4l1l
2b3u2m1k4
bunde4s
bun1
bu2n1d
bun1de
b2u4r1g
bu3r4i
bur4t4s
bu4r1t2
bu2sa
b2us
bu4s3cha
bu1sc
busc4h
bu4s2c4hl2
bu4s4c2h2m
bu4s2c2hw
bus1er
bu1se
bu2sin
bu1si
bu2s1p2
bu4ss2e1s
bu2s1s
bus1se
bu6s1te4r1m
bu1st
bu3st2e
bu2s1t2r4
bu2s1u
b2ü1c
büge3l3e
bü1g
bü1ge
büg2el
bü3s4
2b1v
2b1w
bwel3
3by1
by3p
by1s2
2b3z2
bze2it1
bzu1
1ca
2c1a1b
ca2c4h
c2a2e3
ca3g4
ca1h
ca4l3t
c4an
c2a2p2e
ca1p
3car
ca4r3n
car1ri1
c2a2r1r
ca3s2a3
ca1s2t
ca3t4h
c2a1t
ca1y2
cä1
cä3s2
2cc
c1ce
c1c4h2
c2d2
c3do
2cec
c2e1co4
ce2d2r4
ce1d
2ce1f
ce1i
2ce1k
1cen
ce4n3g
ce1n1u
1cer
ce1re3
ce1ro
c4e3s1h
ce1s
1ce1t
2ceta
ce1u
1cé
c1f
c4h
4ch.
2cha1b
ch3a2bi
2ch1ak
ch2a2n3b4
3cha2n1c
ch1a4n1g
ch3an1st
cha6n1s
2cha2n1z
1ch2ao
2ch2ar.
1ch4a3ra
3charta
cha4r1t
cha2sc
ch2as
1chato
ch2a1t
2chatu
ch1ä4r1m
ch1ä4s
1châ
2c2h3b2
6c2h1c
2c2h3d4
ch3e4b2en
che3be
che1b
1che1f
3ch2ef.
che4f2er
ch2e1fe
3che4f1s2
4chei
ch1e2im
4ch1e2le1m
che1le
che4ler
4ch1en4t1s
che4n1t
4c2h3en4t3w
cher3a
che3rei
che1re
6cherge1b
che4r1g2
cher1ge
cher6zie
che2r1z
ch1e2s1s
che1s
2cheta
che1t
2ch1e4x
1ché
2c2h3f4
2c2h3g2
2c2h1h2
1c2h1i1a
chi3na
chi2n
4chi2n1d
3chi3n2e1s
ch2i1ne
2ch1i2n3f
2ch1i2n1h2
ch1i6n1s
ch1i4n1t
2ch1i2n1v2
1chi1ru
ch2i2r
2ch1j
2c2h1k4
2c4hl2
ch2le
ch3lein
ch2lu
4c2h2m
2c2hn4
chn4e3r8ei.
ch1n2e
chner3ei
chne1re2
2cho1b
cho2f
ch1o2f1f
ch1oh
ch1o2r1c
2c2h3p2
c2h2r4
2ch1re
ch3re3s1
ch3r1h4
1ch4r2on
4c4h1s
ch4stal
ch1st
2c4h1t
2chuf
2chu1h
2ch1u2n1f
chu2n1
2ch1u2n1t
2ch2ü
2c2h1v
2c2hw
5chy
2ch1z
c2i1c
ci2s
c1j
2c4k
c1k1a
ck3an
cka4r1
c1k1ä
ck1e1he
ck1ei
ck1e4n1t
4cker
cke2r1a
ck2e1re
ck1e2r3h4
ck2e4rn
ck1e2r1r
ck1e1se
cke1s
ck1i1d
ck1im
ck1in
ck3l2
ck3n2
c1k1o2
ck3r4
ck4stro
c2k1s
ck1st4
ckst2r4
ckt2e
c4k1t
ck1um3
ck1up
c4l2
cle1t2
c1le
c1lo1
1clu
c2m2
1co
co1c4h
c2oc
co2d2
co3di
co2f1f4
c2oi2
co1it
co2ke
c2o1k
co2le
c2ol
co3l2o
com4te.
c2om
co4m1t
comte2s4
con3n2e
co4n1n
co2p2e
co1r1a
co2r3d
c4o3re
co1s4
co2te
c2o1t
2cp
c1q
1c4r2
c1re2
cre4me1s
cre1m
cre1me
c1ry2
2c1s2
c2si
4c1t
c1t4e3e
cti2
c3t2i4o
ctur6
3cu
cu2p3
cus1si4
c2us
cu2s1s
1cy
c1z
3da.
d2a1a
2d1a1b
d2ab1ä
da2ben
3d2ab1l2
da2b1re
dab3r4
d2a3b4rü
2d1ac
d2ac.
dach3a
dac4h
dachgescho1s8
da2c2h3g2
dach1ge
dachge3s2c
dachge1s
dac4hgesc4h2
dachgescho7s1se
dachgescho2s1s
da2cho
4d3ach1se
da4c4h1s
d1af
d1a1g
dag2i2o
da4h3l
da1ho
3d4ai2
da1in
d2a1i1s
da1l2a
2d1a2lar
da2l3b2
da3l1ö
d1a4l1t
d1am1ma
da2m1m
2d1am3mä
da1mo3
d2a2m1p
dampf8e2r1f
dam2pf
damp1fe
2d1a4m1t
d2an.
2d1a1na
dan4ce.
da2n1c
2d1a2n3d2
d1a4n1g
2dan1ge
dan4kl2
da2n1k
dan5kla
dan2k1o
dan2k2r4
2d1a6n1s
2d1an4t3w
da2n1t
2d1a4n1w
d2anz.
da2n1z
4danzi
2d1a1p
d2a1ph
4da2p1p
da2r3a
2d1a2r1b2
3d2a4r1l
dar2ma
da4r1m
dar2m1i
d2a2ro
d3a2r1r
d2a4r1s
d1a4r1t
d2a2ru
d2a2r1w
d4a3s2h
dat4e2
d2a1t
da3tei
d4ate4n
4d3a2t3l2
4d3a2t1m2
3d2a2u3e
da2u
2d1au2f
2d1a2us
4dau2s1h
2d1äh
2d1ä4m1t
2d1ä2n1d
2d1ä4n1g
2d1äp
2d1ä2r1z
dä2u
dä3u1s
2d1b4
db2u2c
db2u3s2
2dc
d1c4h
dco4r
d1co
2d1d2
dda4r2m
d3d1h2
d5do
1de
de2a1d
d2e1a
de3as
de3a2t
de3b4
2d1e4b2en
3dec
de1c4h
d4e1e2
2d1e2f1f
de1f
de1g2
de3gl
de1he2
de3ho
2d1e2hr
d1ei
d2e2ic
3d2e1im
dei2n2d
dei6n2s
de2l1a4g
de1la
de4l3a2u1g
dela2u
del1än
d2e1lä
de2l1ec
de1le
de4l1e2i4g
d2elei
de3l1ein
2d1ele1k
2d1e2le1m
2d1el2f3m2
de2l1f
del1le2
de4l1l
del4lei
del2lö
de2l1o1b
d2e1lo
de2lop
de3l1or
de2lö
del4s1an
de4l1s
del1sa
del2s5e
del2so
del2s1p2
de4l3t
dem2ar
de1m
d2e1ma
2d1e2m1p
d2en.
de4n3e2n1d
d2e3nen
de1ne
4d1e2ne4r1g
de4n3g
d2e2n1h2
de2ni
den4k3li
de2n1k
denkl2
4d1en4se1m
de6n1s
den1se
den4sen1
den6s5ta2u
den1st
den3t1h
de4n1t
2d1en4t3w
de1n1u
de1on
d2eo
d1e4pi4so
de1p
de3pi1s
d4er.
der1a2b
der1a
de2r2a1p
der2bl2
de2r1b
2d1er2d1b4
de2r1d
de2r1e2b
de1re
de4r1e2c4k
der3e1di
dere1d
de4r3ei1s
derer3
de3r4e2r1b
de3r4e2r1f
de4r3e1ro
de2r1e4r4t
4d3erhöh
de2r3h4
de2r1h2ö
3d4e3r2ie
d2e1ri
de2r1i2n4f
4d1erklä
de2r1k
derkl2
de4r3m2
4derne2u
de4rn
der3ne
de1ro
de2r1o2p
der1ö4
4d3er3s2a1t
der1s2a
de4r1s
der3ta2u
de4r1t
der6t5e2n6d
derte2n1
dert4ra
dert2r4
d2e3ru
de4ru1h
de4r1u4m
de2s1a
de1s
de3sac
de4sa4g
de4sam
des3an
des1än
de4s2eh
de1se
des1en1
des1e1t
des1in
de1si
des1o
de2sor
d2e2s1p2
de2s5s2
des2t5a4l1t
de1st
de4s2tam
de6sta2n1t
de3stan
de4ste2i
de3s2t2e
de4stit
de3sti
dest5r2a1t
dest2r4
de3st1ri
d2e3stro
de2s1u
dete4n4t
de1t
de3te
det2en
2d1e4t3w
d2e1un1
de1u4r1l
de3u1s4
d1e2x2i1s
de1xi
2d3e4x1p
2d1f4
2d1g2
dg2a4st2r4
dga2s
dga1st
d2ge.
d1ge
dgescho1s8
dge3s2c
dge1s
dgesc4h2
dgescho7s1se
dgescho2s1s
dge3t2a
dge1t
dge4t1e
d3gl
2d1h2
d2hi1s
1di
di4a1b
di1a
di2a1d
di4am4
3d2ic
di1ce
di2e
di3e2d
die4n1e1b
d2i1en
di2e1ne
di3e1ni
di3ens.
die6n1s
die4s3c
die3s2
die1t3
die2t2h
dige4s
d2i1g
di1ge
di1k2a
d2i1k
di4l2s3
2d1i2m1b2
di1n2a
2d1i2n1d
2d1i2n3f
2d1i2n1h2
2d1i2n1it
di1ni
4d3inner
di4n1n
din1ne
2d1i6n1s
2d1i4n1t
di2o1b
d2i1o
di3o6n5s3
dion2
di1p
di4re.
di1r2e
di2ren
di2r2i1s
di1r2i
2d1i4r1l
d2i2sp2
di1s
2d1i4s3r4
di1st2
di2ta
di4te4n3g
di1te
dite4n
di4t3e4r1l
di4t3e4r1m
di4t3e4r1s
di2t3r4
di2t1s
di2tu
d2i5v
d2i1z2
2d1j
2d1k4
4d1l2
d3le
dl2e2r1a
dl2i2f
d1li
d2l3m
d4l3s
2d3m2
4d5n2
d1ni2
d3n2i1s1
d1o1b
d2oba
2dob2e
dob4l2
d2ob2r4
do1chi
d2oc
doc4h
2d1o2f
do4l1l2
d2ol
do2mar
d2om
do1ma
d2o5n4a
d4o3ni1
do2o
2dop2e
2d1opf
d2o2p1p
d2o3r4a
2do2r1c
2do2r1d
dor2f1a
d2o2r1f
dor2fä
dor2f2l2
dor2f2r2
2d1o4r1g
do2r2ie
do1ri
d2o2r1p2
2do4r1t
dor2ta
d2os.
do1s
do2s3s
do1s2t1
do4sta
d4ot6h
d2o1t
do3un1
do2u
d1ö
dö2l1
3d2ör
dö2s1c
dö1s
2d3p2
2d1q
d2r4
3d4ra.
2d3r2a1d
2d3r2a2h1m
d3r2ai
3d4ram
d3ra2n1d
2d3ra1st
dr2a3s2
2d3r4a2uc
dra2u
2d3r2ä1d
d4räh
2d3rät
2d3rä2u
4d3re.
d1re
d4rea.
dr2e1a
d4re3as
3d4re2c4k
2d3re1g
3d4reh
2d3re2ic
d4re2i1v
4dre1m
4d3ren
2d3re1p
4d3rer
4d3r4es.
dre1s
d4re3sc
2d3r1h4
d3ri
d4ri.
3d4ri1a
2d5r2ic
d4ri1d2
d4r2ie
d5rie1g
d4r2if
d4r2i1k
d4ril
d4rin.
3d4ri1sc
dr2i1s
2dri2ß1
3d4r2it
4d5ri1tu
d3ro1b
d3r2oc
2d3ro1d
d4r2oi
dr2om2
2d3r2o1t
d3ro2u
2d3ro1v
d3rö
drö2s1
d5r2u1b
d1ru
3d4r2uc
2d3ru1h
drun1ge3
drun1
dru4n1g
2d5r2u1t
d2r1ü1b
d1rü
2d1s
d2s3a1b
d1sa
d4s1a4m1t
d3sam
d2s1an
ds3as3s2i
dsa2s1s
d2s1a4u2
ds1än
4d4s3b4
d4s1che1f
d1sc
dsc4h
d4schi2n
dsc2h4r4
d2s1e2b
d1se
d2s1e1f
d3sei
d2s2e2i1g
d4sei6n1s
ds1ein
d2s1e4n1g
d2s1e4n1t
d2s1e2r1f
d2s1e2r3h4
d2s1e2r1k
ds1e2r1r
d2s1e2r1z
dse4t
d2s1eta
d3s2h2a
d2s1h
d3sho
d2s1im
d1si
d2s2i2n3f
d3s2kan
d2s1k2
ds1ka
d3sku1l
4d2s1l2
d2s1op
dso2r
ds1o1ri
d2sö
d2s1par
dsp2
dsp2a
ds1pa4s
d2s2pä
ds2p2o
d2s1p4ro1
dsp2r2
ds2pu
d2s1s2
ds3si
d1st4
ds1ta1b
d4s3tä1ti
dst2ät
d4s1t2e2a4
d3st2e
ds2til
ds2tip
d2s1t2i1s
d2s1to3d
ds1u2m1s
d2su2n1
ds2zen
d2s1z
2d1t
dta2d
d3t2e2a4
d2t1h
d4t3hei
d1th2e
dt3ho
dto2
dt3r4
dtran2
d2t5s2
1du
du1a2l1v
d2ua
du1ar
dub3l2
d2u1b
du2b1li
du2f
2d1u1fe
2d1u1h
d2u1i
2d1u2m1b2
2d1u2m3d2
2d1u2m1e
2d1u2m1f4
2d1u4m1g2
2d3u2m1k4
2d1u2m3l2
d2u2m1p
2d1u2m3r2
d1u2m1s
d2ums.
2d1u2m1v
2d1u2n3d
dun1
d1und2a
2d1u2n1f
dun3ke
d2u2n1k
dun2kl2
2d1u2n3r2
2d1u2n1t
d2u1o
du2r2c
2d1u4r1l
2dur1sa
du4r1s
du4s2c2hn4
d2us
du1sc
dusc4h
du4sc2h2r4
du4s2c2hw
2d1üb
3düf
3dün
2d1v2
2d1w
dw2a2
dwe1s2t3
dwe1s
dy1
dy2s1
2d1z
2e1a
e3a2b
eab3l2
ea3der
ea1d
ea1de
ead1li4
ea4d1l2
ea2d2r4
ea2g4
e2a3ga
ea4ge
e2a3gl
ea4k1t2
ea2la
e3a2l1ei
e2a1le
e4al2er.
ealti2
ea4l1t
e2a1m4e
ea1m1o
ea4m3t
ea2na
e2a1no
e3ar.
ea2ra
e3a4re1ne
e2a1re
ea3ren
e3a2r1r
e3a2r1v
e2as
ea2s5s
eat4e2
e2a1t
eater1
e3at2h
ea2t3s2
e3a4t3t4
e3au2f
ea2u
e3a2u1g
eau2s3s2
ea2us
eau3st
e1ä4
e1b
2eba
e3b2ak
2ebe1d
ebe2i
2e3bel
eb2en
eben2s3e
ebe6n1s
ebe4r1t4
2e3be1t
2ebl2
eb3ler
eb2le
eb4le2u
e3b2l2ie
eb1li
eb3lo
eb2lö
2eb2o
ebö2s
2eb2r4
eb3rei
eb1re
eb4ru
e4b2s
eb6sche
eb5sc
ebsc4h
eb1se2
eb1s1i
ebs1o
ebs1p2
ebs3p2a
eb4s3t2ät
eb1s2t
eb3stä
eb4s3t2h
eb4s3ti
eb4s3t2o1t
eb3sto
eb3st2r4
ebs1u
e3bu
ebu2s3s
eb2us
eb2u2t1
2e1ca
e1ce
ech1ä
ec4h
2e3che
e4ch1ei
e6ch5erzi
eche2r1z
e1chi
e2c4h3l2
e4c2h3m
e2c2h3n4
e2cho.
e2ch1o2b
e2c2h3r4
ech3t1a
e2c4h1t
ech3t4ei
e1chu
e2ch1u1h
e2c2h1w
e1ci
eci6a
eck3se
e2c4k
ec2k1s
2ec4k1t
2e1c4l2
2e1co
eco3d2
2e4c1t
e1d
e3d2a
ed2d2r4
e2d1d2
e1d2e
ede2al
ed2e1a
e3de4c
e3d1ei
ede3n2e
eden4se
ede6n1s
eden4s3p4
ede2r
ede4r3t2
ed2i4al
e1di
edi1a
ed3ma3s2
e2d3m2
ed1ma
e3d2o
ed2ö
eds2ä
e2d1s
ed2s1e1s
ed1se
ed2s1o
ed2s1p2
ed2s3t2r4
ed1st4
ed2su
ed2u2s
e1du
e3dy3
4ee
e2e3a2
e2eb2l2
ee1b
ee2ce
ee1c4h
ee2cho
ee1d2e3
ee1d
ee2d3s2
4e4e1e
e1e2f1f
ee1f
e2ef4l2
ee1g2
e1ei
ee1im
eein4se
eei6n1s
ee2i5se
eei1s
ee1l2e
e1e2le1k
ee3len
e1e2m1p
ee1m
e1en
ee2n1a2
ee4n2a1g
e2e1nä
e2e2n1c
ee3ni
e2e1no
ee6n3s
e1e2pi
ee1p
ee1r1a
e1er4b1t
ee2r1b
e1e2r1d
eer3de3
ee3r2e
ee4r3e4n4g
e2eren
e2ere4s1
ee4re1t
e1e2r1k
ee1ro
ee1r1ö
eer2ö1s
ee4r1t2
e1ert2r4
e2e3r2u
e1e2r1z
ee1s2
e4e3s1h
ee2s3k2
ee3ta
ee1t
ee4t4a1t
ee2t2h
ee1u2
eewa4r
ee1w
e2ew2a
e1e2x
e1f
2ef.
2e1fa
e2f1a1d
ef1a1na
ef2ar
e2f3a1t
efä3s4
e1fä
e2f1äu
2e1fe
e3fe.
e2f1e2b
ef1e1m
e2f1e4n1t
ef2er
2eff.
e2f1f
1ef1fi
ef2f3l2
2e1fi
e2f1i2d
e2f1i6n1s
e3fi2s
1efku
e4f1k4
2ef2l2
e3f4lu
2e3f2o
e3fra
ef2r2
ef3r2e1a
ef1re
ef3r2ol
ef3r2om
ef4rü
e4f1s2
ef3s1o2
ef3sp2
ef2t1an
e4f1t
2e1fu
e2f1um
2e1fü
e1g
e2gd4
e3ge
ege4n3a4
e3gen
ege2r1a
ege4s3to
ege1st2
ege1s
ege4st2r4
ege1u
eg1l2a
e2g1lo
e2g1n
eg3ni
egro5s1se
eg2r4
eg4ro1s2
egro2s1s
eg4sal
e4gs
eg2sa
eg3s1e2
eg4sei
eg4s3e4r1
eg4sto
eg1st
eg2t1h
e4g1t
2e1gu
2e1ha
eh1ac4h
e3h2al
eh2a2us
eha2u
2e1hä
ehä4s3
e1he
eh2ec
e2h1e2f1f
ehe1f
eh2el
ehe4n2t3
1e2he1p
e3her
ehe1r1a
e1hi
eh1i4n1t
ehi2n
ehi1s4
eh1lam
e4hl
eh1lä
eh1le2
ehl3ein
eh4le4n1t
eh5l2er
eh2lin
eh1li
eh3lo
ehl2se
eh4l1s
2e2h1m
eh3mu
e1ho
e3h2ol
eh2r1a2
e2hr
ehr1ä
eh2r1ec
eh1re
eh2rei
ehr3e4r1l
ehr6er1l2e
eh3re3s1
eh3ri
eh1ro2
eh2r1o1b
eh2r1of
e4h2s2
eh3se
eh3s1h
eh3si
eh3so
eh3sp2
eh3sta
eh1st
e1hu
e2h1u2n1t
ehu2n1
e1h2ü
eh3üb
e2h1w
e1hy
2ei3a2
4e2i1b
ei2bar
ei2bl2
eib2u2t
ei4b3ute
ei2cho
e2ic
eic4h
e2i1d
ei2d1a
ei3d2an
ei3de
ei4d3e2r1r
2ei4d5n2
ei3dra
e2i2d2r4
ei1e
4e2i1en
eien1ge4
ei3e4n3g
eie4s2
1eif3r2
e2if
ei3g2a
e2i1g
4eige1no
ei1ge
ei3gen
eig2er
2eige1w
ei3gl
1ei2g3n
2eig1ru
eig2r4
2ei4g1t
2ei1gu
eik2ar
e2i1k
ei1ka
ei3k1a2u
ei3k4la
ei2k3l2
e4il
2eil.
ei2lar
ei1la
ei2l1a2u
2e4i2l1b
ei4l3d
ei4l1ein
ei1le
eilen1
ei2l3f4
eil3i6n1s
ei1li
2ei4ln
1eilzu
ei2l1z
ei2m1a4g
ei1ma
eim3a4l1l
ei2mor
e2i1mo
e1i2m1p
eim2p2l4
ei2n1a
ei4na4s
ei2nä
e2in3d2r4
ei2n1d
2ein1du
ei4n1e4n1g
e2i1ne
ei3nen
ei2n1e2u
2ein1f2o
ei2n3f
e1in4fo.
e1in4fo1s
ei4n3g2
e1in4ha1b
ei2n1h2
e1i2nit
ei1ni
ei2n3k
e3in6ka4rn
ein1ka
3ein3k2om
ein1ko
e2i2n1o2
einsas4
ei6n1s
ein1sa
einsa7s1s2e
einsa2s1s
3ein3s2a1t
ein6stal
e1insta
ein1st
ein4s2z
e4in3ver1
ei2n1v2
ein1ve2
e2i3o2
ei1p
eip2f2
2eir
ei3r2e
e1i2r1r
e2is.
ei1s
ei2sa4
ei6schi2n
ei1sc
eisc4h
ei6sch3w2u
eis2c2hw
ei4s3e2r1w
e2i1se
eis2p2e
e2isp2
ei3s2s
ei2st2r4
ei1st
ei2sum
e2it
ei2ta1b
ei1ta
ei2tan
ei2tar
2e4i1tä
ei3te
ei2t1h
ei2tro
ei1t2r4
ei4t1t4
ei2t3um
ei1tu
2e2i3u2
2e1j
e1k
e1k2a
1e2k3d2
e3ke.
e3ken
e3ke1s
e3key
e3k2l2
ek4n2
e1k2o
ek4r4
2e4k1t
ekt4a2n1t
ekt1an
ekt3e2r1f
ekt3e4r3g2
ek4t3e2r1z
ekt2o
ek2u
e3k2w
e1la
e4l3a4ben
el1a1b
el3a1bi
el2a4b1t
e2l1af
ela2h
e2l1ak
e2l1a2m
e2l1a6n1s
el1a2n1z
2e3l2ao
e2l1a1p
e2l1a2r
el3a1ri
e2l1a1si
e2l1a2s1p2
el2a1st
2e1lä
3elbi1s
e2l1b
el1bi
el2da
e4l1d
eld5er1st
el1de
elde4r1s
el4d3e2r1w
el2d3s2
2e3le.
e1le
2e3leh
2elei
e6l5eier.
elei1e
e2l1ein
e3l2e2i1ne
e4lei4n3g2
e2l1el
1e2le1m
e3lem.
e4l1e2m1p
2e3l2en.
e4len1se
ele6n1s
e2l1e4n1t
e3le1p
el1e2r1d
el1e2r1f
e4ler4fa
e2l1e4r1g
el1e2r1k2
el1e4r1l
e4ler4l2a
e4l3er1nä
ele4rn
e2l1e2r1r
2ele1s2
el1e2s3s
e4l1e4ta
ele1t
e3le2u
2e3l2e1v
ele2x
1elf.
e2l1f
el3fe
elf4l2
1el2f3m2
1el4f1t
elgi5er.
e2l1g
elgi5e4r1s
2e1li
e2l1i1d
e3l2ie
el2i2ne
el1i1ta
el3k2l2
e2l1k
el2k2s2
el3la2n
e4l1l
ell2a2u
el2le1b
el1le
ell3ebe
el4l3ein
ell3ei1s
el3ler
el2l2i1c
el1li
el3l2in
ell3sp2
el4l1s
el1m2a
e2l1m
2e4ln
el5na
2e1lo
e2lof
e2l2ol
elon2
el1op2e
e2l1or
elo2ri
el2ö2f
e1lö
el2s1um
e4l1s
el1t2ak
e4l1t
elte2k
el4t3e4n3g
el3t2en
el4te4n1t
3elte4rn
el3te2s
elto2
el2t3r4
el3t1ri
el4t1s2
elt3se
elt3s1k2
2e1lu
e2l1um
e2l1ur
e2l3u1se
el2us
e1lü
e2lya
e1ly
2e2l1z
elz2e
el2zw2a
elz1w2
e1m
2e1ma
e2m1a1d
ema2k
e2m3a2n1f
e2m1a6n1s
3e2ma2n1z
emas8s2e6n1s
e3mas
emas1s2e
ema2s1s
em2d3a2
e2m1d
e3m2en
e1me
emen4t3h
eme4n1t
e6ments2p2
emen4t1s
e2m1e2r1w
1e2me3ti
eme1t
e2m1im
e1mi
emi5na
em1i4n1t
emi3ti
e3mit1
2e2m1m
em2ma1p
em1ma
em2m1a3u
em2m1ei
em1me
e2mop
e1mo
3em2pf
e2m1p
em3p2f2l2
em2p3le
em1p2l4
em2sa
e2m1s
em2sp2r2
emsp2
e4m3t2
1emu1l
e1mu
2e1mü
emü3s
e2n1a
4e3na.
2e3n2a2c
e3na1d
e4n1af
4e3n2ah
e4n1ak
en2a3l2i
4enam
e3n4a1m4e
e4n1a2n1d2
e4n3a4n1g
en3a1re
en1ar
en2a1sc
e3nas
4e3n2a1t
e4n3a4t1t
e3n2a2u1e
en1a2u
e2n1är
e1nä
e2n1äu
enb2u4s3
e2n3b4
en2ce.
e2n1c
en3d2ac
e2n1d
en2dal
ender3mas8
en1de
ende4r3m2
ender1ma
en4d3e2s5s2
ende2s
en2d4o4r1t
end1or
end3r2om2
end2r4
end3si
en2d1s
end3s2p2
end3s1z
end2um
en1du
2e3ne.
e1ne
ene4b2en
ene1b
e2n1ec
e2ne2f1f
en1e1f
e4nein
e2n1ei
e2n1e2l
e2n1e4le
2ene1m
2e3nen
e4n1e4n1t
e5n4ent2r4
4e3n2er.
e2n1e2r1d
e2n1e2r1f
1e2ne4r1g
e4n1e2r3h4
e4n1e2r1k
e2n1e4r1l
e4n3er1mo
ene4r1m
4ene4rn
e2n1e2r1r
e2n1e4r1s
e2n1e4r1t
e2n3e1ru
e2n1e2r1w
e4n1e2r1z
2e3n2e1s
e4n3e2s1s
e2n3f
en1f2a
en2f2u
1eng1a1d
e4n1g
3enga1g
enge3r1a
en3ger
en1ge
en3g2i
en2gl
en3g1lo
1en2g1p2
en4g2s
eng3s1c
eng3s1e2
e3ni.
e1ni
e3n2ic
e2n1i1d
e3n4ie
eni3er.
eni5ers.
enie4r1s
e2n1i4m
e2n1in1
e3n2i1o
2e3n2i1s
e3nit
2en2i3v
en3k2ü
e2n1k
e2n1o2b
e1no
en2o3b4le
e3nob1l2
e2n1of
en1oh
e3n2ol
eno2ma
en2om
en1on
e2n1op
e2n1o2r
eno2s
eno1s2t3
e3n2o1t
e3n2o2w
2e1nö
e2n1ö2d
e4n3r2
en3sac
e6n1s
en1sa
ensas2
ensa5s1s2e
ensa2s1s
en2s1a4u
en5sch4e
en1sc
ensc4h
en2s1e1b
en1se
1ense1m
ensen1
ens3e4n1g
en3s1ka
en2s1k2
en3s2p2o
ensp4
ens2t5a4l1t
en1st
en4s3t2ät
en6s5t2e1s2t
en3st2e
enste2s
2ensto
en6s5tr2ie
enst2r4
enst1ri
e4n1t
en3t4a1g
1en2t3d4
en2te2b
en4te2r1b
en3ter
en3te2s
1ent1fa
en2t1f4
3entga
en2t1g2
en2thi
ent1h
3entla
en2t3l2
1en2t5n4
en4t3r2ol
ent2r4
3entsp2r2
en4t1s
ents2p2
2entü
1en4t3w
4ent1we1t
1en4t3z2
en1u
2e1n2u1t
e1nü
4enwü
e4n1w
e1ny
en4z3e2r1f
e2n1z
en4z3e4r1g
en4z3e2r1k2
enz3e4r1t
e1ñ
2eo
e1o2b1
e1of
eo2fe
e1oh
e4ol
e1on.
e1o2n1d
e1o2n3f2
e1o2n1h2
e1o2n3l2
e1o2n3r2
e1o6n1s
e1op2e
e1opf
e1o2p4t4
e1or
e3or.
e3o2r1b
e3o4r1s2
e3o2r1w
eo1s2
e3os.
eo3u1l
eo2u
e1o1v
e1ö2
e1p
e3p2a
epa2g4
e3p2f4
1e4piso
e3pi1s
ep3le
e1p2l4
1e2p2o1c
e1p2o
ep2p2a
e2p1p
ep4p3l4
ep2p2r2
ept2a
e2p1t
ep2tal
e3pu
ep2u2s
e1q
er1a
e3ra.
e3r4ad.
e1r2a1d
er3a2d3m2
era1f4a
er2af
era1f2r2
era2g
e1r2ai
e2r3a2ic
e2rak
e1r2al
er3a4l1l
era2n3d
e3r2a1ne
e2r3a2n1f
e2r1a2n1h2
e2r3a2n1m4
e1r2a1p
e2r3a2pf
e2r1ar
e3r2a1ri
e1r2a3s2
e2r3a6si
era4s3s
e2ra2ß
e2rat2h
e1r2a1t
e3r4a3ti
e2r3a2t1m2
e1r2a2u1b
era2u
er3a2u1e
e2rau2f
e2r3a2u1g
e1r2a1w
e1r2a1z
e1rä
e2r1äh
e2r1äm
erä4s
erb2e
e2r1b
er3b2r4
erb4sp2
er4b2s
e2r1c
er3c4h3l2
erc4h
er3da
e2r1d
1er2d1b4
er3de
2er3dec
erd3e2r1w
4e3re.
e1re
er1e1b
e3r2ec4h
e4r3e4c4h1s
er1e2c4k
e2re4dit
ere1d
ere1di
e4r1e2f1f
ere1f
e2r1e2h
4e3r2ei.
e2r1e2i1g
e2r1ein
e4r3e2is.
erei1s
ere2l
er1e1l2e
2e3re1m
2eren
e3r4en.
e3r2e2n1a
e4r1en1se
ere6n1s
e4r3en2t1f4
ere4n1t
e4r1en2t5n4
e3r2e2n1z
eren8z7e2n1d
2e3r4er.
e2r3e2r1f
e2r1e2r3h4
e4r1e4r1l
2ere4r2n
e3r2e1ro
er1e2r1r
er1e4r1s
e2r1e4r1t
er1e2r1w
2ere1s
e2r1e2s1s
e2r1e2ß1
er3e4ti
ere1t
e2r1eu1l
ere2u
e2r3e4vi1d
er2e1v
er1f2e
e2r1f
erf4r2
4erfü2r
er1fü
3erge4b3n2
e4r1g
er1ge
erge1b
4erg2e1hä
erg3e4l1s
erg2el
1erg2ol
e2r3h4
1erha1b
e2rha
2erh2ü
2e1ri
e2ri3a1t
eri1a
e3r2i1b
4e3r2ic
4e3r2ie
er2i3e4n3
eri5ers.
erie4r1s
e3r2i3k4
4e3rin.
er1i2n3b4
e2r1i1ni
e2r1i2n1k
e2r1i4n1t
e3r2i1o
er1i1ta
er2it
2erk.
e2r1k
1erklä
erkl2
2erk1li
2erk1re
erk2r4
er4k3t
3erle4b3n2
e4r1l
er1l2e
erle1b
erm2e6n4s
e4r1m
er1me
er3m3e4r1s
ern1o1s
e4rn
er1no
e1ro.
er3o3a2
er1o2b
e2r1of
e1r2o1g
e1r1oh
e1r2o1k
e1r2ol
e1r2om
e3r2on
e4r3o1ny
er1o2p
e4r1o2r
e1ro1s
e1ro2u
e1r2o1w
e1r2o1z
er1ö
erö2d
2er1ök
e2r3p4
er3rä
e2r1r
erri3er
er1ri
err2ie
2er3r2ü
er1s2a
e4r1s
er3se
er5se2n1
er3s2i
er3s1k2
ers3ma3s4
er4s3m2
ers1ma
er5s1mo
er3s3n4
er3s2p4
er3s2t2e
er1st
er3s1z
er1t2ak
e4r1t
er6t3erei
erte1re
er4t3e2r1f
er4t1e4r1s
er2t2ho
ert1h
4erti
ert3i6n1s
ert3s2e
er4ts
2e1ru
eru4f4s1
e3ruf
e2r1u4m
e2r1u2n1d
erun1
er1u6n1s2
e4r3u1z
e2r1ü4b
e1rü
3erwe2c4k
e2r1w
6erwei1s
erwe2i
e1s
e4s3a1b
e1sa
es1a1d
es2an
es3a2n1t
e3s2as
esa3s1s2e
esa2s1s
esas6sen
e4s3ato
e3s2a1t
esä3s4
e1s2äu
2e4s3b4
e3sc
es2ca
es3ca1p
e2s2ce
esc4h2
e4s1co
e4s3cu
e3se.
e1se
es1ebe
ese1b
es3e2hr
e3s2eh
e2s1ein
ese4ler
ese1le
es3eva
es2e1v
2e6s3f4
4e2s1h
es3h2a
es4har
es2hu
esi1er
e1si
e3s4ie
e3s2i1g
e2s1il
es1i1n1i1
e4s3i6n1s
e2s3i4n1t
es2k2a1t
e2s1k2
es1ka
e4s3ke
e4sky
e4s3l2
es4l2o1g
es1lo
2e4s3m2
e4s3n4
es2o4r1t
es2ö
2esp2
e3s2pe1k
esp2e
e3s2por
es1p2o
e3s4pra
esp2r2
es2pu
2e4s3r4
es2s1a4u
e2s1s
e4s1sa
4esse1m
es1se
ess4e3re
ess3e4r3g
es3si
2e4sso
es2sof
es2s1p2a
e4ssp2
es4s1t2e
e4s1st
e2s2t1a4b4b
e1st
esta1b
e2s1t1ak
e3stan
e4s2t1a2r1b
1e2stas
es2ta2u
e3s2t2e
e4st3e4n3g
e4st3e2r3h4
e4st3e2s1s2
e1ste2s
e5st3e1v
e3sti
e4s1tip
estmo6de
e4s2t1m2
est1mo
est3m2o1d
e2s1to3d
est3o1ri
2estro
est2r4
es3t4rop
es2t2u
e3s2tü
es2ty
e2s1um
es1ur
e4s1w
e3s2y
eße3r2e
e1ße
e1t
e3ta.
eta1b4
e2t1am
1eta1p
e3t4a3ri1
et4a1t
e2t1äh
e3te
e4t1ein
et2en
ete2n3d2
et2e2o
ete2r4h2ö
ete2r3h4
eter4t2r4
ete4r1t
et2h
et3hal
e2th2a
e2t3h2ü
e3ti
eti2m
eti2ta
2e3to
e1to2b
e4t1of
2et2r4
e4t3r4aum
etra2u
e2t3rec
et1re
e2tre1s
ets2c2h3w
e2ts
et1sc
etsc4h
et1s2p2
et1s1u
ett1a
e4t1t
et2t1a1b
et4ta2n1z
et2t3a2u
et2tä
et2tei
ette4n1
et2t1h
et2t3r4
et4tro
ett3s1z
et2ts
et2t1um
et2tur
et2tü
etwa4r
e4t3w
etw2a2
2e4t1z
et2z1ä2
et4z3e4n1t
etze4n1
et3ze4s
et2zw2
e2u1a2
eue6re2if
e2u1e
eue1re
eu2e5sc
eue4s
eu2g1a
e2u1g
eu4ge4n1t
eu1ge
eu3gen
eu3g2er
eu4gl2a
eu2g1l
eu4g1s2
euil4
e2ui
eu1in
1euk
eu2kä
e1um
e3um.
e3u2m1b2
e3u2m3l2
e3u2m2s
eums1p2
eum3st
2eun1
eu3n2e
eu4n1ei
e3u4n2g
eu2n2i1o
eu1ni
eun3k1a2
e2u2n1k
e2u1o2
eu1p
e2u1r2e
3e4u3ro
e2u1s4
eu3sp2
eu3s1s
eu1st4
2e2u1t
eut2h
eut6s2c2hn4
eu2ts
eut1sc
eutsc4h
2e2u1x
eu2zw2
e2u1z
e3ü
2e1v
e2ve3la
e1ve2
e2ve4n1t
4ever1
ev2e5r2i
e3vo
e2v2s
e1w
2ew2a
e3w2ä4
e1wä6s3
2ewe
e2we.
e3wi4r
ewi2s
e3wit
e2w2s
2ex.
ex3a1t
ex1a
1e2x1e1m
e1xe
ex1er
e1xi
e2x1in
1ex2i1s
e2x3l2
3e4x1p
2ext.
e4x1t
ex2tin
ex2t1u
2e1xu
2e3xy
ey4n
ey1s2
e1z
e3z2a2
e2z1e4n3n
e3zi
ezi2s
ez2w2
é1b
é1c
é1g
égi2
é1h
é1l
élu2
é1o
é1p
é1r
é1s
é1t2
é1u2
é1v
é1z2
è1c
è1m
è1n
è1r
ê1p
1fa
fa1b4
fa2ben
f3a2b5f4
f2ab3r4
fa4b5s
3fac
fa4che1b
fa1che
fac4h
fa4che2r5f
fa2ch1i
fa2cho
f1ader
fa1d
fa1de
fa2d2r4
f4ah
fa2i1b4
f4a2ke
f2al
fa3l2a
fal2k2l2
fa2l1k
fal6l2e2n1k
fa4l1l
fal1le
fal6l5e2r1k2
fal2li
fal6scha
fa4l1s
fal1sc
falsc4h
fal6s4c2h2m
fal4t2s
fa4l1t
2f1a2n3b4
2f1a2n1f
fan2g2r4
fa4n1g
2f1a2n1k
2f1a2n3l2
f1a2n3p4
2f1a2n3r2
fa6n3s
2f1a4n1w
f1a2n3z
2f1a1p
f2ar
f2ar2b2r4
f1a2r1b
2f3a2r1c
3f2a1ri
3f4a4r1t
2f3a2r1z
fa3s4a
f4a3s1h
f3a1t
fa2to3
2f1auf
fa2u
f3a2u1g
fa2u2s
f1au4s3b4
3f4a1v
fa2x1a
fa1x
1fä
fä1c
fäh2r1u
fä2hr
2f1ä4r1m
fä4s3ser
fä3s
fä2s2s
fäs1s2e
fä2ßer
fä1ß
fä1ße
f1äu
2f1b2
2f1c
2f3d4
fdi2e2
f1di
1fe
featu4
f2e1a
fe2a1t
f2ec4h
2f1e2c4k
fe2d2r4
fe1d
fe2ei
f4ee
fe1e1m
f2ef4l2
fe1f
feh4lei
feh1le2
fe4hl
f4ei1e
2f1ei4n3g2
4f1ei2n1h2
fe1i1ni
2f1ei4n1w
f1ei3s
fek2ta
fe1k
f2e4k1t
fe2l1a
fel2d3r4
fe4l1d
2f1e2le1k
fe1le
fe2l1er
f2e2le1s2
f2e2l1o
fel4soh
fe4l1s
fe4l3t
f2em.
fe1m
f2e2m4m
2fe2m1p
fe2nä
fe4n3g
fe2no
fen3sa
fe6n1s
fen1s2t
f1e4n1t
f2er.
fe1r1a
fer2an
fe4ra4n1g
fe4r3a2n1z
fe2ra2u
fer3de3
fe2r1d
f2e1re
fer2er
fe2r3e2r1z
f1er1fa
fe2r1f
f2erl.
fe4r1l
4ferne2u
fe4rn
fer3ne
fe1ro
f4er3p4a
fe2r3p4
f2ers.
fe4r1s
f2e4r1t
f1e2r1w
fe1s2t
fe1s
fe2st1a
fe4st3e2i
fe3s2t2e
fe2st2r4
2f1eta
fe1t
fe4t2a1g
3fe3te
fet2t3a
fe4t1t
feue1r3e
fe2u1e
feu4ru
3fe1w
f1ex
2f3e4x1p
3fe1z
1fé
2f1f
ff2a1b4
f1fa
ff3ar
ff4a2r1b
ff1a2u
f1f2e
ff4e2e
f2f3e1f
ff3ei
ffe1in
ffe2m
f2f3e1mi
f2f2e4t1z
ffe1t
f2f1ex
2f2f1f4
ff3l2
ff4la
f1f4lä
ff4lo
f3flu
f3f4lü
f3f4rä
ff2r2
ff3ro
ff3rö
ff2sa
f4f1s
ff3sho
ff2s1h
ff2sp2
4f3g2
fge3s
f1ge
2f1h2
1fi
3fi.
fi3a1t
fi1a
fi1e2r2f
fi2k1in
f2i1k
fi3k3l2
fi1k1o2
fi2ko1b
fi2k2r4
fi2l1an
fi1la
fil4auf
fil1a2u
fi4l3d
fi2le1s
fi1le
fi2l1g4
fi3li
fi4lin
fi2l2ip
f2i1na
fi3ni
2f1i4n1t
f2i2o
fi3ol
fi2r
fi3r2a
3fi1s
fi1s4a
fisch3o
fi1sc
fisc4h
fi3so
f2is2p2
fi1t1o2
fi2tor
fi3tu
3f2i1z
2f1j
4f1k4
f2l2
2fl.
f3la1d
f3la1p
1flä
3f4lä1c
2f5lä1d
f3län
2f3läu
2f3le1b
f1le
f4l4e2e
2f3lein
f3ler
f4lé
f3li.
f1li
3f6lim
fl2i4ne
2f5lon
f1lo
1f4lop
flo7s8ses.
flos1s2e
flo1s
flo2s1s
floss2e1s
1f4l2o1t
fl2o2w
f3lö
f4l2uc
1f4l2u1g
flu4ger
flu1ge
flus3se
fl2us
flu2s1s
f4lü
2f3m2
fma2d
f1ma
fma2s2s
f3mas
fma3s1s2e
2f3n2
f3n2i2s
f1ni
1fo
fob2l2
fo1b
2f1of
fo1l2i3
f2ol
f2o2na
f2o1n2e
fo2nu
2f1op
4f3o4r1g
f4o3rin1
fo1ri
3f2o4r1m
for4m3a4g
for1ma
forni7er.
fo4rn
for3ni
forn4ie
for4sta
f2o4r1s2
for1st
for4sti
for4tei
fo4r1t
for2t1h
for2t3r4
for3tu
2f1o2x
1fö
2f1öf
2f1ök
2f1öl
4f1p2
2f1q
f2r2
f4rac
frach6t3r4
fra4c4h1t
frac4h
f5r2a1d
fra4m
f3ra2n1d
f5r2a1p
1f4rän
2f3re.
f1re
f3rec
f3re1d
2f3re1g
fre2i1k2
fr1ein4
f3re1p
f4re2u
2f3r2ic
f1ri
fri3d2
fr2i2e
2f3r2i1g
1fr2i1s
f4ri1sc
fri6s2ter
fri1st
fri3st2e
f3r2oc
1f4r2on
fr2o2na
fro2sc
fro1s
f3r2o1t
f3ru
f3rü
4f1s
f5s2a2m1m
f1sa
f3sam
f2s1an
f2s3ar
f2s1as
f2sauf
fs1a4u
f2sa2us
f2sa4u1t
fsä4
f3sc
f4s1ce
f4schan
fsc4h
f4s1che1f
f2s1e2b
f1se
f4s3e2hr
f3s2eh
f2s1e1m
f2s1e4n1t
f2s1er
fse4t
f2s1eta
fsi2d
f1si
f3s2kie
f2s1k2
f2s1o2
f3span
fsp2
fsp2a
f2s1pas
f2s1ph
f3s2p2l4
f3s2por
fs1p2o
fs1p2r2
f2sp1re
fs2p1ri
f2s1p4ro1
f3s2p1ru
f2s3s4
f2stas
f1st
f4s3tä1ti
fst2ät
f4s1t2ec4h
f3st2e
f4ste2m1p
fste1m
f2s1tip
f2s1t2i1s
fst4r4
f4s3tre1s
fst1re
f4s3tü1te
fst2üt
f2s1ty
f2s1u2n1
f2sü
f3s2y
4f1t
f2ta.
f2ta1b
ft1a2be
ft1af
f2t1al
ft1an
ft1ar
f3t2a1t
f2t1e2h
ft1e2i1g
ft1ei1s
f2t1e4n1t
f2t1e4ti
fte1t
f2t1h
f4t3hei
f1th2e
ft3ho
ft1op
f2t3ro
ft2r4
f2t3rö
f3t4ru
f2ts1
ft2sa4
ft4sa1g
ft4sam
ft1s2c
ft4sch2e
ftsc4h
ft2se4
ft4s3eh
fts3el
ft2si
ft2stä
ft1st4
ft4ster
ft3st2e
ft4ste2s
fts2ti
fttra4
f4t1t
ftt2r4
f2tum
ft1u4r1l
ftw2a4
f4t3w
f4t3z2
1fu
3f2u1g
3f2u1h
f1um
2f1u2n1f
fun1
2f1u2ni
fun2kl2
f2u2n1k
fun2ko
fun2k3r4
2f1u2n1m4
2f1u2n1t
f2ur
fu4re.
fu1re
f2us2
fu3s1se
fu2s1s
fus6sen
fu4s2ser
fu4ss1p2
fu4s4s1t
fu2ß1er
f2uß
fu1ße
3f2u1t
1fü
2f1üb
fü2r
fü3s2
2f1v
2f1w
1fy
2f1z
fz2a2
fzeite4n6
fze2it
fzei3te
fzei8t5e2n1d
fz2ö
fzu3
fzu4ga
fz2u1g
3ga.
2g1a2b5f4
ga1b
ga2b5l2
gab4r4
2g1a2b3z2
ga1c4h
2ga4d1l2
ga1d
2ga2d2r4
g2a1f3l2
ga1k
ga2ka
ga1l2a
g4a1mo
2g1a4m1t
2g1a2n3b4
ga2n3d
g1an2g1a
ga4n1g
4g3ange1b
gan1ge
gan2g2r4
2g1a2n1h2
2g3anku
ga2n1k
2g1a2n3l2
g3anla
3g2a1no
2g1a4n1w
ga1ny
2g1a2r1b
2g1a2r1c
3ga2r1d
2g1a4r1m
g2a3r2o
g1art2i
ga4r1t
g2a3ru
2g1a2r1z
ga2s
ga3sc
ga4s3ei
g4a3s2e
ga4s3e2m
ga3s1p2
ga4sp2e
ga4sp2r2
ga2s5s
ga3s6s2e1s
gas1s2e
gas3tan
g2asta
ga1st
ga4s2t3el
ga3st2e
g2a3st2r4
ga4stra
ga4s2t3re
gas1t2u
gat2a
g2a1t
2g3a2t1m2
g4at4r4
g4a2u1c
ga2u
2g1auf
g2auk
g1a2us
2g1a4u1t
2g1äp
2g1ä2r1z
gä3s2
gä4u
2g3b2
gba2u5s
gba2u
gber2
g1bi2
2g1c
2gd
g1da
g2d1a2u
g2d1er
g1de
gd1in
g1di
g1do
g1d1ö
g1d3r4
g2d3s2
g2d1t4
g1d1u
1ge
g2e3a2
g2eb2a
ge1b
gebe4am
ge3b2e1a
g2eb4r4
ge1c
ge1d4
g4e1e2
ge3ec
ge2e1s2
ge1f4
ge3g2l
ge1g
ge1im
ge2in.
gei6n2s
ge2i4n1t
gei2n2v2
g2e1ir
ge2i1s4
2g1e2i1se2
gei3s1h
gei4sta
gei1st
g2el
ge4l1a2n1z
ge1la
gelb2r4
ge2l1b
gel4b3ra
gel6de4r1s
ge4l1d
gel1de
ge3le
g2e5leh
ge4l3e4r1s
ge4l1e2s3s
g2ele1s2
gell2a
ge4l1l
ge3l1or
g2e1lo
gel1s2t
ge4l1s
gel3s1z
gel3t2a
ge4l1t
ge3l1um
g2e1lu
ge3lü
g2e2l1z2
ge3mi
ge1m
ge1m2u
3gen
ge3n1a
g4e4nam
ge4n1ar
gen4a2u1g
gen1a2u
gen2d2r4
ge2n1d
gen1e1b
ge1ne
ge3n1ec
gen3e2i1d
ge2n1ei
g4en3e4rn
ge4n3g
genma7sse.
ge2n1m4
gen1ma
gen3mas
genmas1s2e
genma2s1s
ge4n3n
gen3s1z
ge6n1s
2g1en2t1f4
ge4n1t
gen3t1h
4g1en4t3w
ge1o2r
g2eo
ge1o2u
ge3p4
ge1r1a
ge2r1a1b
4g3e2r1e2i1g
ge1re
ge4re4n1g
g2eren
ge4re6n4s
ge4r3e4n1t
ger2er
ge2r1i2n4f
g2e1ri
ge3r4i4n1n
ge2r1i4n4t
ge4r1m4
germa2s6s
ger1ma
ger3mas
ger3no
ge4rn
ge1ro
ge1r2ö
ger4sto
ge4r1s
ger1st
g2e3r2u
g1erw2a
ge2r1w
ge3s2c
ge1s
ges3e4l1t
ge1se
ge2s1er
ge3s2i
g2es2p2
ge1s4pi
ge4s1s2t
ge2s1s
ge1st2
ge3t2a
ge1t
2g1eta1p
ge3t4u
ge1u1l
2g1ex
2g1f4
4g1g
gg2a2t
g3ge
gge2ne
g3gen
g2g3l
gg4lo
g2g3n
gg4r4
2g1h
4gh.
gh2e
3g2he1t
3g2hi2e
g4h1l
3g2h2r
g2hu
g2h1w
gi3a1lo
gi1a
g2ial
g2ia2s
gie3g
gi2e1i
gi2el
gi2e1n2e1
g2i1en
gi2gu
g2i1g
gi2me.
gi1me
gi4me1s
gi2me1t
2g1i2n1d
g2i3ne
g2in2ga
gi4n1g
2g1i6n1s
2g3isel
gi1s
g2i1se
gi3t2a
gi4us
g2i3u2
2g1j
4g3k2
4gl.
gl2a
g1l1a1b
3g1la1d
g2la1de
2g1la1g
3gla2n1z
gla4s3ti
gla1st
gla4st2u
3g2l2a2u1b
gla2u
2g1lauf
g1lä1ß
3g4lät
2g1lä2uf
g2l4e
2g3le.
3gl2e1a
2g3le1b
g3lec
g3le1g
2g3leh
4g3lein
glei4t5r4
gl2e2it
g3len
4g5ler
2gle1s
g3le1s2e
g4li1a
g1li
2gl2i3b4
3g2li1d
3g2l2ie
2gl2if
g2l2i1k
4glin
g2l2i2o
2gli1s
4g3li1sc
3g2lit
g2l2i1z
3g2l2o3a2
g1lo
3g2lo1b
g3loc4h
gl2oc
gl2o3g
3g4l2o1k
g2l2om
3g2lop
3g2l2o1t
2g4l1s
2g1lu2
glu3te
gl2u1t
3glü
g2ly
2g1m2
g1n
2gn.
g2n2a
g4na.
2g3n2a2c
g4n2a1t
3g2nä
g1n2e
g3neh
gn2e2t2r4
gne1t
2gne2u
2g4n1g
g2n4ie
g1ni
g2n2if
g4nin1
2g3n2i2s1
g2no1
g3n2o1t
2g2n3p4
2g6n1s
2g2n1t
2gnu
3g2n2um.
g2nü
g2ny
2g2n1z
g2o4a2
go2a3li
2g1of
2g2o1g
2g1oh
g2o1i2
go1l2a
g2ol
2go3n2i1s
g4o3ni
2g1op2e
2g1opf
g2o1r1a
2go2r1d
2go4r1g
go2s
g4o3t2h
g2o1t
got6t5e4r3g2
go4t1t
got3ter
go1y
2g1p2
2g1q
g2r4
g1r4a2bi
gr1a1b
gra2b1l2
2g3ra4d1l2
g1r2a1d
2g3rah
2g3rak
grammen6
gr2a2m1m
gram1me
gram8m7e2n1d
2g3rä2u
2g5re.
g1re
g4re1b
2g3rec
2g3re1d2e
gre1d
g4r4e2e
2g3re2ic
2g3r1ein
g3re2it
g4re1m
2g3re4n1n
gren6z5ei
gr2e2n1z
g4rer
g3re1t
g3r2e1v
2g3r2ic
g1ri
gr2i2e
g3rie1se
grie3s2
3gr2if
2g3r2i1g
2g3ri4n1g
2gr2oc
2groh
gr2on4
g4ro1s2
gro5sse.
gro2s1s
gros1se
gro7ss2en.
gro7ss4er.
gro5ss2e1s
g4roß
gro4u
2g3r2öh
g4ruf
g1ru
2g3r2ui
2g3rum
3g4rup
gru2s2s
gr2us
gru3s1se
2g3r2u1t
2g3r2üc
g1rü
3g4rün
4gs
g2sa
gs1ac
gs1a1d
gs1af
g4s1a4g
g3sah
g4s3a2k
g3sal
g4s1a4l1t
gs3a1m2a
g3sam
g4s1a2m1b2
gs3an
g2s3ar
gs1as
gs3a2u1g
gs1a4u
gs1ä
g4s1ca
g1sc
g4s1ce
gsc4h4
g4s1che1f
gs2chi
gs3c4r2
g2s1e2
g3s2e4il
g3s2el.
gs3e1li
g3s2e4ln
gsen1
g4s3er
gse4t
g4seu
g2s1i
gsi2d
g3s2i1g
g3sil
gs1o2
gs1p4
g3s2pe1k
gsp2e
g3s4pi4e
g1spi
g2s3p2l4
g5s2por
gs1p2o
gs1r2a1t4
g4s3r4
g2s3s2
g3star
g1st
gs1ta2u
gs1tä
g5s2tel
g3st2e
g4ste2m1p
gste1m
gst3e4n1t
g4s1te4r1m
gst3e2r1r
g4s3t2e1s2t
gste2s
gs1t2h2e
g4st1h
g3sti
gs1t2i1s
g3sto
g4s1ton
g4s1tor
gs1t2o1t
gs1t2r4
gst4ra
gst4ri
gs2t3ro1s
g3stu2n1
gst2u
gs1tü
gs2t2üc
gs1u
g3s2y
4g1t
g3te
gti2m
gt4r4
gt2se
g2ts
1gu
gu3am
g2ua
gu1an.
gu1a2n1t
gu1as
gu4d3r4
g2u1d
g2u2e
2gu2e1d
gue1t2
2g1u2f
2g1u1h
gu1i6n1s
g2ui
gu1i4s
3g4u2m1m
2g1u2n1f
gun1
g2ung.
gu4n1g
gun1ge2
4g1unge1w
2g1ungl
g2u6n1s2
2g1u2n1t2
3gur
4g1u4r1l
gur4t3s
gu4r1t2
gu2s3a
g2us
guschi5
gu1sc
gusc4h
gu4s2sp2
gu2s1s
gu4s4st
gu3sti
gu1st
g2u2ß1
g2u2t
gut1a
gu3te
gu4t3e2r3h4
gut3h
2g1üb
gür1
gü3s4t
2g1v
2g1w
2g3z2
3h2aa
h2ab2a
ha1b
hab2e
ha2cho
hac4h
ha2del
ha1d
ha1de
ha4din
h4a3di
h1ad3le
ha4d1l2
haf3f4l2
ha2f1f
haf2t2s1
ha4f1t
hafts3p2
h1ah
h2a1k3l2
2h2al.
hala2n4c
ha1la
ha2l1a2u
hal2ba
ha2l1b
hal4bei
hal2b3r4
2h2a1le
hal2la
ha4l1l
hal4leh
hal1le
hal6le2r1f
h1a2l1p
halt3r4
ha4l1t
h1a4m1t
h2an.
h2a2n1d
h4a4n1n
2h1a2n3r2
2ha2n1t
ha1o2s
h2ao
h1a1p
h2a2p3l4
h2a2p2r2
h4a3ra
2h1a2r1b
h2a2r1d
h1arm.
ha4r1m
har4me.
har1me
har4m2e1s
har2t1h
ha4r1t
h1art2i
h2as
2ha3sa
ha1si1
ha2ß1
ha4t1t2
h2a1t
hau3f4li
ha2u
hauf3l2
2h1au2f3m2
h1au4k1t
hau2sa
ha2us
h2au2sc
hau4sp2a
hau1sp2
hau4s1s2
haus5sen
hau3s1se
hau4s3ti
hau1st
hau4sto
h2aut.
ha4u1t
2h3auto
hau2t2r4
h1ä2f1f
hä4s
hä5s4c
hä6s5ch2en
häsc4h
häu2s1c
hä2u1s
hä3u2sp2
2h3b2
hba2r3a
2h1c
2h3d4
hdan2
2h2e1a
he2a1d
he2a5t
he3be
he1b
he4b1e2i
h2e2bl2
h2e3b2r4
h2e5ch2e
hec4h
he1cho
h1e2c4h1t
he2d2g2
he1d
he3di
he2e3l
h4ee
hee2s2
he2fan
he1f
h2e1fa
he2fä
he2f1ei
h2e1fe
hef3e4r1m
hef2er
2he2f1f
he4f3i4n1g
h2e1fi
h2e2f3l2
he2f2r2
he3f1ri
h2e2fu
h2e3gu
he1g
h1ei1e
h1e2if
h1e2i1g
he2im
he1i2m3p
he2i4mu
he2i1ne2
h1ei2n3k
4he2i3o2
he1i4s3m2
hei1s
he1i4st
hei2t4s1
he2it
h1e2i1w
he2l3a2u
he1la
he2l1ec
he1le
h3e2le1k
he3len
hel3e4r1s
h2e3li
hel4l3a2u
he4l1l
hel4mei
he2l1m
hel1me
h2e3lo
he4lof
he2lö
3he2m1d
he1m
he3mi
3h2e2m1m
4h1e2m1p
h2en.
he4n3a4
he2nä
he2n1e2b
he1ne
hen3e2n1d
h2e3nen
h1e2n3e4r1g
he2ne1t
he4n1g2
2he1ni
he2no
hen3st2
he6n1s
h1en4t1s
he4n1t
2h3en4t3w
he2n3z
4h2e2o
he3on
he3op
he3ph
he1p
her3a2b
her1a
he2r2al
2he1r2a1p
he3r2a3s2
hera2u2
he4r1e2c4k
he1re
4he2r1e2i1g
he4r3ei1s
he2re2l
he4r1e2r1w
h1er2fo
he2r1f
h1er1fü
he4r1g2
he2r1i2n4f
h2e1ri
he6r1in6nu
he3ri4n1n
he2r1i6n4s
herin8ter
he2r1i4n1t
h1erke
he2r1k
h3erla2u
he4r1l
her3l2a
2he4r1m
her3ma3s
her1ma
he3ro
he4r3o4b
h1er1ö
he4r1t2
her3t1h
her2zw2
he2r1z
h1e2ta1p
he1t
heter2
he3te
he3t2h
he3t2i
he3t4s
h2e2u
he2u3g
he3x
he1x2a
he1y2
1hè
2h3f4
hfe4l1l1
h1fe
hfel6ler
hfel1le
h3fi2s
h1fi
2h3g2
hge1t4
h1ge
2h1h2
2hi.
2hi1a
h2i2ac
hi2a4n1g
h2ian
hi1ce
h2ic
hich6ter
hic4h
hi2c4h1t
2hi3d
h2i1de
h1i4di
hi2e
hi3e6n1s
h2i1en
hi4e1r1i
hie4rin3
hif3f4r2
h2if
hi2f1f
hi2k2r4
h2i1k
hi2l3a4
hil2f2r2
hi2l1f2
hi2n
h1in1du
hi2n1d
hi3ne2l
h2i1ne
hi3n2en
h1i2n3f
h1i2n1h2
hi3n2i
hi4n3n2
h2i3no
hin2t1a
hi4n1t
2h2i1o
hi4on2
hi3or
2hip1
hi2ph2
h2i2pi
h2i2r
hi3r2a
2hi3r2e
hi3r2i
hi4rn1
hir4ner
hir3ne
hi3ro
hi4r2s
hi1s2a
hi1s
h2i2se
hi2s1p2a
h2isp2
hi3ti
2h2i3u2
h1j
2h1k4
4hl
h4lac
hla2n
hl1a2n1z
h1las
h1la2ß3
h1l2a1t
h1la4u1t
hla2u
h3lä1d
h1lä3s
h1lä1ß
h1läu
h2l1b4
h4l1d4
h3le1b
h1le
hl4e3e
h5l2en.
hle4n3g
hl2e4n1n
h3ler
hl2e2r1a
hl1e4r1g
h6l3er1nä
hle4rn
hle3run1
hl2e1ru
hl1e2r1w
h4l1e2r1z
h3le1s
h4le1si1
h3lex
h2l1g4
h2l2ie
h1li
h2l2if
hl1i2n1d
h2lip
h2li1s
h3li1st
h2lit
h4l1l2
h2l1m2
hl3ma3s2
hl1ma
h2lo
h3l2oc
hl1of
hl1op
h4lor
hl4o2re
h3l2o1si
hlo1s
h1l2ö
h3l2öc
h2lö1s3
hlö4s2s
hl2s1an
h4l1s
hl1sa
hl2ser
hl1se
hl3sku
hl2s1k2
hl3s1lo
hl2s1l2
hl2sto
hl1st
h4l3t2
h3luf
h3luk
h1lüf
2h1m
h2m1a1b
h1ma
h3ma1g
h3man
h3mar
hma3s1s2e
h3mas
hma2s1s
h4mäc
h3mä
h4mäh
h4mäl
h4mäu
h3me.
h1me
hm4e1e
hme1in
h3m2ei1st
hmei1s2
h3men
hm2e6n2s
hme2r1a
h2mo
h4m2on
h3mö
h2m3p4
hm2s1p2
h2m1s
h2mu
2hn
h2na
hn1a1d
h3nam
hn1an
h2n3d4
h1n2e
hn3e2i1g
h2n1ei
hn3ein
h2ne2l
h3ne4n1
h2ne4p2f4
hne1p
hner3ei
hne1re2
h3ne4r1l
h3n1e2r1z
h2n3ex
h2n2ic
h1ni
h2n1i1d
h2n4ie
hn1im
hn1in1
h2n2ip
h2n3k4
h2nor
h1no
hn3s2k2
h6n1s
hn1s2t
hntra4
h2n1t
hnt2r4
hn4t1s2
h1nu
h2n2uc
h2nu1l
hn1u2n1f
hnu2n1
h3nun1ge
h1n2u4n1g
ho2b1l2
ho1b
h2o2c
hoc4h3
hoc4k3t
ho2c4k
2ho1d
h2o1e4
ho2e1f
h2o4fa
h2o2f3r2
2h2oi
hol1a2u
h2ol
ho1la
4holdy3
ho4l1d
3ho1le
ho2l1ei
ho2l3g4
4ho3lo
ho4lor
3ho4l1s
h3o2ly
3ho2l1z
hol6ze1ne
ho1m2e
h2om
ho2m2e3c
ho2me1d
h2on
ho1no3
2hoo
2hop
ho1r1a
ho2r3d
h1o4r1g
ho4s1ei
ho1s
h2o3se
ho3s1l2
h2o2s1p2
ho3s4se.
ho2s1s
hos1se
ho5s6ses.
hoss2e1s
ho3s1si
ho4sta
ho1s2t
ho2st2r4
2hot.
h2o1t
h4o3t2h
ho4t5li4
ho4t3l2
2ho2t1s2
3ho1v
2h2o2w1
h1o2x
ho1y2
hô1
1h2ö
h2ö2c
h4ör
hö4s
hö1s1c
hös3se
hö3s2s
h3ö1s4t
2h3p2
h1q
2hr
hr1ac
h1r3a1d
h1r2ai
h1r2a1ne
h3r2a1t
h3rä2u
h2r1c
h2r3d
h2rec
h1re
h3r2ec4h
h3re1d
h3re1f
h4r2ei.
hrei4ba
hr4e2i1b
h3re2ic
h4r3e2i1g
h3rel
h3r2en
h3re1p
hr2e4r1g
hr2e2r1k
h6rerle1b
hr1e4r1l
hrer1l2e
h2r2e4r1m
h2r2e2r1z
h3re2s1
hre2t
h2r1eta
h3r2e1v
h2r1f2
h4r1g2
h2ri
h3r2ic
h4ri2c4k
hr2i4e
h3rie4s3l2
hrie3s2
h3rin
h4r2i3n4e
h4r1i2n1h2
h4ri1st
hr2i1s
h2ro1b
h2rof
h3roh
h3r2ol
h4ro1me
hr2om
h4ro1mi
h4r2on
h2r1or
h3ro2u
h2r1r4
hr2s1ac
h4r1s
hr1sa
hr2s3an
hr2s1a4u
hr3sc4h
hr1sc
hr2s1e2n1
hr1se
hr2ser
hr2se1t
hr2s1in
hr1si
hr2s3k2
hr2s1of
hr4stec
hr1st
hr3st2e
hr2su
hr4s1w
hr2ta1b
h4r1t
hr2tan
hr2t1h
hr2t1or
hrt3ri
hrt2r4
hr2tro
hrt2sa
hr4ts
hrt2se
hrt4st2e
hrt1st4
h3ru1h
h1ru
hr1u2m1s
h2rum
h3rü
h4r1üb
h2ry
h2r1z2
4h1s
h2s1ac4h
h1sa
h2s1an
h2s1a4u
hsä4s3
h3sc
h4schan
hsc4h
h2s1ec
h1se
hse4ler
hse1le
h2s1e4r1l
h3s2ex
h2s1i4n1g
h1si
h2s1of
h2s1par
hsp2
hsp2a
h2sper
hsp2e
h2s1ph
h1s2por
hs1p2o
h2sp4rä
hsp2r2
h2s1p4ro1
h2s1s2
hs2t3a4l1t
h1st
hst2an
h4s2t1a2r1b
h2sta2u
h2stäl
h4s1t2e2a4
h3st2e
h5s2tel
hs1t2h2e
h4st1h
h3s1t2i2e
h2stin
h2s1tor
h3stö
h3st2r4
hst3ran
hst3ri
h2st2u
h3stu2n1
h3stü
h2s1u
hs2u4n1g
hsu2n1
h3s2y
4h1t
h2t1a
h3t4akt.
h1tak
hta4k1t
h3tak2t3s2
h3t2al
h4t3a4l1t
h2ta2m
hta4n
ht3a3ne
h3t2a2n1k
h3t2as
h4t3a2s1s
h4ta1s2y
ht3a2t
h2tär
h3te.
ht1ec
h2t1e1f
h2t1eh
h3t2e1ha
h3t2e1hä
hte2he
h2te2if
h4tei2l1z
h1te4il
h2t1eim
h2t1ei1s
h4t3e2lit
ht2e1li
htel1m2a4
hte2l1m
htel3mas5
h2te2m1p
hte1m
h3ten
h4ten2t1f4
hte4n1t
h4t3en4t1s
ht3er1fo
hte2r1f
ht3er1fü
h2t1e2r3h4
ht5erken
hte2r1k
h4terkl2
h4t3er3r2e
hte2r1r
ht3er1sc
hte4r1s
h6t5ersp2a
hter3s2p4
ht3er1st4
h6tersta
ht6er3s2t2e
h2t1e2r1z
h2t1e1se
hte2s
h2t1e2s1s2
h3te1t
h2t1eu
h2t1ex
htgescho1s8
h2t1g2
ht1ge
htge3s2c
htge1s
htgesc4h2
htgescho7s1se
htgescho2s1s
h2t1h
h4t3hei
h1th2e
ht2h2e3u
h4t2ho
h2t1in
ht1ni2
h2t5n4
hto2
h2t3o1ly
ht2ol
h2t1o4r1g
ht3rak
ht2r4
h2t3ra2n1d
h2t3r2a1t
ht6rau1me
h1t3r4aum
htra2u
ht4ri
h2t5rin
h2t3r2ol
h2t3ro1s
ht3rö
h2t1rö1s
h2t3ru
h2t3rü
h4ts
ht3sp1ri
htsp2
htsp2r2
ht4sta1b
ht1st4
hts2ti
ht4s3tur
htst2u
ht4s3tür
h4t1t4
htti2
h1t2u2e
h2t1u4r3s
h4t3z2
h2u2a
hu2b1a
h2u1b
hu2bei
hu2b1en
hu2b3l2
hu4b3r4
hu2bu
hu2h1a
hu1h
hu2h1i
hu4k3t4
hu2l3a
hu1l
hu2lä
hu2l3ei
hu1le
hu4l3e4n1g
hule4n
hu4le4n1t
hu2ler
hu2le2t
hu2l1in
hu1li
hu2lo
hu3ma
h1u2m1s
hu2n1
h1u1na
hu3ni1
h1up.
h1u2p1s
2hur
hu4r1g2
hu3sa
h2us
hu2so
hu4s4sa
hu2s1s
hus3se
hu4s2sp2
hu4s4st
hu2ta1b
h2u1t
hu3t2h
hu2ti
hu4t2t
hut4ze4n1
hu4t1z
hut4z3er
h2ü
h4ü4b1s
h1üb
h3übu
hüh3n2e4
hü2hn
hü2s3s
2h1v
hvi2
hvil4
2hw
h2wa4l1l
hw2a
h1wal
hwe1c
h1w4e2i1b
hwe2i
3hy1g
3hy1p
hy2pe.
hyp2e2
2hy2t2
h1z
hz2o
hz2u1g4
i1a
2ia.
i4aa
i2a1b
iab4l2
2iac
i2af
i2af4l2
i4a3g2
i2ah
i3ai
i2a1j
i2ak
i3ak.
i3a4k1t
2ial
i5al.
ia2l1a4
ia2lä
ia2l3b
ia4l3d
i3a2l1ei
i2a1le
i3ale4n1t
ialen1
i3a2l1e2r1f
i3a2l1e2r3h4
i3a4l3e4r1m
i3a2le4t
i3a4li1a
i2a1li
ia2l1k2
i3a4l1l
ial3la
ia2lor
i2a1lo
ia4l3s
ia4l3t4
ia2lu
ia2l3z2
i2am4
i3am.
i4a1mo
2ian
ia2nal
ia1na
i3a2n1d2
i2a1n2e
i3a4n1n
i2a1no
i3a2n1t
i3a2n1z
i2a1p
ia3p2f
i2a1q
i3ar.
ia2ra
2ias
i2a1sc
i4a3s1h
i2a1si
i2a1s1p2
ia2s5s
ia1st4
i3at.
i2a1t
i3a4ta
i4at2e
i3at4h
1i4at2r4
i3a2ts
i3a2u
i4a3un1
ia2u2s1
2i2a1v
2iä
i1äm
i1är.
i1ä4r1s
i1ät.
i1ä2t1a2
i1ä2t3s4
2i1b
i2b1auf
iba2u
ib2b1li
i4b1b
ibbl2
ib1ei
i2be2i1g
i2bei3s2
ibe4n
ibe2n3a
ib2i2k
i1bi
i3bla
ibl2
i3b2le
ib2o
i2bö
i4brä
ib2r4
ib3ren
ib1re
ib4st2e
i4b1s
ib1s2t
i2b2u2n1k
ibun1
i2b1u2n1t
ibu1s1c
ib2us
ibu2s3s
2ic
i2c1c
ich1a
ic4h
ich1ä
i1che
i4ch1ei
icher3ma3s8
ic2he4r1m
icher1ma
ichgro3
i2c2h3g2
ichg2r4
i1chi
i2chi2n
i2c4h3l2
i3ch2lo
i4c2h3m
ich3mas4
ich1ma
i1cho
i2c2h3r4
ich2t3r4
i2c4h1t
i1chu
i2c2h1w
i1ci
ick2e
i2c4k
ic2k1s2
i1c4l2
i1d
i2d2a1b4
i3dam
id2an
i2d1a2u
1i2d4e1e2
i1de
i2d1ei
id2e1l2ä
ide3s1o
ide1s
id2e3s1p2
1i2d2i1o
i1di
id1ni3
i4d5n2
i2d2ol
1idol.
2i2d2r4
i3d2sc
i2d1s
id2s1p2
i2d1t4
i2dy1
i2e3a4
ie2bä
ie1b
i2e2bl2
ie2b1re
i2eb2r4
ieb4sto
ie4b2s
ieb1s2t
ieb4st2r4
ie1c
ie2cho
iec4h
ie2d2r4
ie1d
i4e1e2
ie2f1ak
ie1f
i2e1fa
ie2f1an
ie2fa2u
ie2f3f4
i2e2f3l2
ie2fro
ief2r2
ie4g3l
ie1g
ie3g4n
ie2g3r4
ie3g4ra
ie4g2s
ieg1s1c
ieg4s1e2
ieg4st
i1ei
i2e2l1a
ie3las
iel3a2u
ie4l3d
ie2l1ec
ie1le
ieler8ge1b
ie2l1e4r1g
ieler1ge
i1e4l1l
ielo4b
i2e1lo
iel3s1z
ie4l1s
iel3ta
ie4l1t
2i1en
i3en.
i3e2n1a
ie2n1a2b
ie4n3a4g
i3e2nä
i3e2n3d
i2e1ne
ien1e1b
ie3ner
ie2n4e2r1f
i1e4n3e4r1g
i3e2n3f
i3e4n3g
ien3ge4f4
ien1ge
i3e2n1h2
i3en1j
i3e2n1k
i3e2n1m4
ien3ma3s4
ien1ma
i3e4n1n
i3e2no
i3e1nö
i3e2n3p4
i3e4n3r2
ien3s2e
ie6n1s
ien2s2k2
ien6stof
i2ensto
ien1st
ien6stop
iens4t2r4
iens1t5rä
ien3s1z
ie1n1u
i3e2n1v2
i3e4n1w
i3e2n1z
i2e1o2
ier3a2
ie2r2a1d
ie2r2a1p
i2e1re
ie3r2er
ie4r3e2r1f
ie4r3e2r1z
i2e3re1s
i3ere2u
i4e1ri
ierin3
ie2r3k4
i1e4rn
i3ern.
i2er5ni
ie2r1ö
ier3s2e
ie4r1s
ier4s3eh
ier3sta
ier1st
ier3te
ie4r1t
ie3s2
i4e4s1h
ie4s1k2
ie2s4s
ie5sse1t
ies1se
ie4s1s1t
i1e4stas
ie1st
ie2t1a
ie1t
ie4t3e2r3h4
ie3te
ie4t3e4r1t
ie2t3ho
iet2h
i2e4t1o
ie2t1ö2
ie2t1ri
i2et2r4
iet2se
ie2ts
i1e4t1t
ie2u2e
i2e1un1
i1ex
2if
if1a1b4
i1fa
if2ar
i2f3a4r1m
if4a1t
if1a2u
i2fec
i1fe
ife2i
if2en
if1e4r1g
if1e2r3h4
if2f4ah
i2f1f
if1fa
if6fe3s2t2e
if1f2e
iffe1s2t
iffe1s
if2f3l2
if3l2
i1f4la
i1f4lä
i1f4lü
if3r2
if4ra
i1fra2u
i1f1re
if4rei
if4rü
if2ta
i4f1t
ift3e2r1k
if2t1op
if2t3ri
ift2r4
ift1s2p2
if2ts1
ift3s1z
2i1g
iga1i
i2g1a4n1g
ig1a4r1t
iga3s
i4gef2ar
i1ge
ige1f4
ig2e1fa
ige4n1a
i3gen
iger1ma3
ige4r1m4
ig1e2r1z
i2g1im
i2gl
ig1lä
ig4n2a
ig1n
i4g2nä
i3g4ne2u
ig1n2e
ig4no1
i3go
ig4ra
ig2r4
ig3rei
ig1re
igro3
ig4sal
i4gs
ig2sa
ig1s1o2
ig4sti
ig1st
ig4s1to
ig2stö
ig4st1re
igs1t2r4
2i1h
i2h1am
i2har
i3he
ih4e1e
ihe4n
i2h3m
i2h3n
i2h3r
i4h2s
ih3sp2
i2h1um
i2h1w
ii2
ii3a4
i1ie
i3i4g
i1im
i1in
i1i4s
i2is.
ii3t
i1j
2i1k
i2k1a4k
i1ka
ik1a4m1t
i2k1a1no
ik1a2n1z
i4kanze
ik1a4r3t
i2k3a4t1t
ik2a1t
i2k1a2u
i2kär2
i1kä
ikbu2
i2k3b4
4ike
i2k1ei
ike2l1
i2k1e2r2e
ik1e2r1f
i4ker6f4ah
iker1fa
i2k1e2r3h4
i2k2e4r2l
i2k1e4t1a
ike1t
i3ki.
ik1in
i2ki2n1d
i2k3l2
i3kla
i3k4lä
i2k2n2
ik3no
ik2o3p4
i1ko
i2k1öl
i1kö
i2k3ra
ik2r4
ik3rä
ik3re
ikro3
ik3s1o2
i2k1s
ik3s2z
ikt2e
i4k1t
ikt3e2r1k
ikt3r4
ik2t1re
i2kun1
i3k2us
i1la
i2l3a1b
i1l1a2d
i2l1ak
i2l3a2m
i2l1a6n1s
i2l1a2s1p2
il1a2u
il4au2f1b2
il3a2us
i2la4u1t
i1lä1
4i2l1b
i2l2c
il2da
i4l1d
il4d3e4n4t
il1de
ild2er
ild1o
il2do2r
il2d3r4
i2l1ec
i1le
i3le2h
il1e1he
il2e2i1d4
il1ein
il1el
i4l1en4t1s
ile4n1t
i2l1e2r1f
i2l1e4r1g
i2l1e2r1r
i2l1f2
il2f3l2
il2f3re
ilf2r2
il4f4s1
il2i1e4n
i1li
il2ie
ilig1a2
i3l2i1g
ili4ga1b
i2l1i2n1d
i2l1ip
i3l2ip.
i3l2i2p1s
2ill.
i4l1l
il3l2a
il4la1d
ill4a2n
il3l2er
il1le
il3l2i
2il4l1s
il2mak
i2l1m
il1ma
il4ma4n1g
il2m3a1t
il2ma2u
il2min
il1mi
2i1lo
i2l1or
il3t2h
i4l1t
i1lu2
i2lum
i3l2us
i2l1v4
il2z1ar
i2l1z
ilza2
ilz3e2r1k2
2im.
i2m1a4n1w
i1ma
i2m1a4r1m
im4a1t
im4a2t2r4
imat5sc
ima2ts
ima4tur
i2me1g
i1me
i2m2e1j
i2me1k
i2m1e1le
i2me2l1f
i2m1e2r1f
i2m1e2r1z
i4m4e3s1h
i3me1s
i2me3ti
ime1t
i2me1w
i2m1i2n3f
i1mi
i2m1i6n1s
im2m1ei4
i2m1m
im1me
im4m3e4n1t
1im1mo
im1ni2
i2m3n2
2i1mo
im1o4r1g
1im1p2o
i2m1p
im2p4s
im3p3se
1im3pu
im2st2r4
i2m1s
im1st
2i4m1t
imtu2
2i1mu
i3n3a2c
i1na
i4na2c4k
i2n1a1d
in2af
in1am
i3na1p
in2a2r1a
in1ar
in2a4r1s
i4n4a4r1t
i3na4s
i2n3a2u2
ina2us1
in1ä3s
i1nä
in2dal
i2n1d
in2dan
in3d1a2u
inde1s4t
inde2s
in1de
1index
in3do
2ind2r4
ind4ri
in3d1rü
1ind2us
in1du
2i1ne
i2n1e2be
ine1b
in1e1he
i2n3ei
i2n1e4n1g
i3nen
inen1ma3
ine2n1m4
inen3mas6
in3erb2e
ine2r1b
i4n1er1bi
in2e2r3h4
i2n1er4l2ö
ine4r1l
i4n1er4t2r4
ine4r1t
i4ne2s1k2
i3n2e1s
in1e2u
in2e3un1
i2n1e2x
i2n3f
1info.
in1f2o
1info1s
2inga
i4n1g
ing1af
in2g1a4g
in2gl
ing3mas4
in2g3m2
ing1ma
ing4sam
in4g2s
ing2sa
ing3s1c
1inha1b
i2n1h2
2in3har
2in3ha2u
4in3he
in2i3d
i1ni
i3n4ie
2in2i1g
ini3k2r4
i3n2i1k
in2ir
2i3n2i1s
in2i3s1e
i3n2i4t1z
i2nit
3inka4rn
i2n1k
in1ka
ink4st2e
in2k1s
ink1st4
inm2a4le
i2n1m4
in1ma
2inn.
i4n1n
in4n3e4r1m
in1ne
2in2n3l2
in2n1o2r
in1no
inn4sta
in6n1s
inn1st
1innta
in2n1t
2i1no
i2n1o2d
in3o4l1s
in2ol
in1or
ino1s4
in2o3t
i1nö
i2n1ö2d
2i2n3p4
2i2n3r2
in3s2am
i6n1s
in1sa
insc4h2
in1sc
in2s1e1b
in1se
2insen
ins3e4r1t
in3skan
in2s1k2
ins1ka
in3sk2r4
1insta
in1st
in4s3t2ät
in3su
1in2s1u2f
in4s3um
in3s2z
i4n1t
1inte3g6
int2h
in3t4r4
in1u
i3n2um
in3u2n1z
inu2n1
invil4
i2n1v2
i1ny
in3zw2
i2n1z
i1ñ
2i1o
i2o3a4
i2o1c
io2d
i2o3d2a
i2o3e2
iof4l2
i2o3h
io2i3d
i2oi
i2o3k4
i3ol.
i2ol
i3om.
i2om
i3o2m1s2
ion2
i3on.
iona2l3a2
i2o1na
io3nal
io2n2a2u
io2n3d
i3o6n4s3
i2ony
i2o1p
io4pf
i3o2p1s
i3o2p3t4
i2or
i3or.
i3o2r1c
iore4n
i4o1re
i3o2r1p2
i3o4r1s2
i3o4r1t
io3s
i2o1s2t
i3ot.
i2o1t
i3o2ts
i2o2u
i2o1v
io2x
i3oz.
i2o1z
i1ö2k
i3ön
i1ös.
iö1s
2ip.
i1p2a
i1p2e
ipen3
i3per
ipf2
ip3fa
i1ph2
2i1pi
ipi3el
ip2i3en
i1p4l4
ip2p3l4
i2p1p
ip3pu
i1p2r2
2i2p1s
2i1pu
2i1q
i1r2a
i3r2a1d
1i2rak
ir2a4s2
i1r2a1t2
i1rä
ir2bl2
i2r1b
i2r1c
i1r2e
i3r4ee
2i3re1k
i3ré
i4r1g2
ir2gl
ir4g4s
ir2he
ir1h4
i1r2i
2i3r2i1g
2i2r1k
ir2k3l2
irli4n
i4r1l
ir3l2i
ir2mak
i4r1m
ir1ma
ir2ma2u
ir4mä
ir2m1ei
ir1me
ir2mum
ir1m2u
ir4m3u2n1t2
ir3mun1
ir2n2a2r
i4rn
ir1na
ir2no
i1ro
1ir2on
i1rö
ir3p4la4
i2r1p2
ir1p2l4
ir4rei
i2r1r
ir3r2e
irr2h4
ir4s2c2h3w
i4r1s
ir1sc
irsc4h
ir3se
ir3s1h
irt4st4
i4r1t
ir4ts
ir2u2s1
i1ru
i1s
i3sac
i1sa
i4s1a4m1t
i3sam
i2s2a1p
is3a1re
i2s1ar
i2s1a4u
i2s1än
2i4s3b4
i2s1ca
i1sc
isch3ar
isc4h
i3s2che
i4s1che1f
i4sch3e4h
i4s4ch3ei
i2s2c4h1l2
isch3le
i2s4c2h2m
is2ch3o1b
is2ch3re
isc2h2r4
isch3ru
i4schw2a
is2c2hw
i6sch1wi4r
i4schwo
isch3w2u
i2s3c4r2
2i1se
i3s4e3e
is2e3h1a4
i3s2eh
ise3hi
ise3i2n3f
is1ein
i4sei4n1t
ise2n1
is2e2n1d
is2e6n3s
i2s1e2r3h4
i2s1e4r1m
is2e1r2u2
i2s1e2s1s
ise1s
i4s3e2t4a1t
ise1t
isi2a
i1si
i2s1i1d
i2s1of
iso6ne2n1d
i3s2on
is2o1ne2
iso3ne2n1
is1op
3i2s2o1t
2isp2
is1p2a
i2spar
is1p2e
is1p2ic
i1spi
is2pit
i1s2por
is1p2o
i2s1p4ro1
isp2r2
i4s3sa
i2s1s
is4s1ac
is4s1a4u
i6s3sc
is4s3che
issc4h
is3se2n1k
is1se
isser3mas8
isse4r1m
isser1ma
i4s3so
is3sp2a
i4ssp2
is3spi
is3s2p2o
i4s2s1t
is3st2a
is4s1t2e
is3sto
is3st2u
is2su
i2sta1b
i1st
i4s2tam
ist2an
i4s1t2e2a4
i3st2e
iste4n
is2ter
ist4ra
ist2r4
ist3re
is1t1rü
i2stur
ist2u
is1tüm
i2s1ty
isu2m3p
i2sü
i1ß
iß1e4r1s
i1ße
i1ta
i2t1ab.
ita1b
i4t1a4b1s
ita2l1a
i2t1a4l1t
i2t1am
i2t1a4n1g
it3a4re
i2t1a4r1t
i3t2a1t
it1a2u
i3t4a2uc
i2t1a1x
4i1tä
it2är
i2t1ä4s
it2ät2
i1te
i2tei
i4t1e2i1g
i4t1ein
2itel
ite2la4
ite4n
it2e6n1s2
i4te1p2o
ite1p
i2tex
i3t2h2r2
it1h
i1ti
i2t1i1d
1itii2
iti4kan
it2i1k
iti1ka
it4i3k2e
i2t1in1
it2i4n1n
i3t2i1s
i3t2i1v
i4t3l2
itmen2
i2t1m2
it1me
i1to
i3t2oc
i2t1of
i1tö
i1t2r4
i3t1ra.
it3r2af
i2t3ran
it3r2a3s2
it3ra2u
it3rä2u
i1trä
it3re
it3r2ic
it1ri
it3r2om
it4r2on
i3t1ru
it3run1
it2sa
i2ts
it4s1a4g
it2s1e4
it2s3er1
it4se1t
its1p2e
itsp2
it4staf
it1st4
it4stec
it3st2e
it4s3te1m
it4s3te2s
it2sti
it4s1t2i2e
it2s2to
it2te1b
i4t1t
it4te2m1p
itte1m
it2t1ri
itt2r4
i1tu
i2t1u1h
i2t1um
i2tu6n1s2
itun1
it1u4r1g
it2u1t4
i1tü
2i4t1z
it2z1ä2
it4z3e4r1g
it2z1w2
2i3u2
ium1
iu1s1t
i2us
i1ü
2i1v
i2v1ak
i2v1a4n1g
i2ve3b
i1ve2
i2v1e4i
iv1e4l1t
ive4n
i2v1e3ne
i2v1e4n1t
i2v1ur
2i1w
iwur2
iw2u
2i1x
i2x1a
ix2e1m
i1xe
i3xi
i4x1t2
2i1z
iz1a1p
iza2
iz1a2u
ize2i3c
ize2n
i2z1e1ne
iz4er
i2z1ir
i2z1o2b
i2zö
i2z1w2
í1l
jah4r3ei
jah3r2e
ja2hr
jah4r4s
ja3l2a
j2a3ne
j2a3ni1
2j2a1t
j2e2a
jea6n2s
je2g
jek4ter
je1k
j2e4k1t
jektor4
jekt2o
jek2t2r4
je3n1a
je2p
je2t1a
je1t
je2t3h
j2e2t3r4
je4t3t
je2t1u2
ji2a
j2i2v
j2o3a3
jo2b1
job3r4
j2o2i
j4o3ni1
jo1r1a
jo2r1d2
jo2sc
jo1s
jou4l
jo2u
j2u
ju2b2l2
j2u1b
ju3gen2
j2u1g
ju1ge
juge2n1d3
ju2k
jun4g5s1
jun1
ju4n1g
ju3ni
j4u1r2o
ju3t2e3
j2u1t
2j1v
1ka
3ka.
k3a2a
k4a3ar
kab2bl2
ka1b
ka4b1b
ka2ben
2k1a2b5h2
2k1a2bla
kab1l2
2k1a2blä
2k3a2bo
ka3b4r4
2k1a4b1s
2k1a4b1t
ka1c
k2a1d
2k3ada
2k3a2d2r4
k2a1f4l2
ka1f2r2
ka4f3t2
k2a1g
ka1in
1ka3ka
kaken4
k4a1ke
2k2a5la.
ka1la
ka2lan
ka3l1ei
k2a1le
ka3l2en.
kalen1
ka4le6n1s
kal3e1ri
1kal2ka
ka2l1k
kal2k2r4
k1a4l1l
k2a1lo5
kal4t2r4
ka4l1t
k3a1m2a
kamp8fe2r1f
ka2m1p
kam2pf
kamp1fe
kan2al
ka1na
k2a4n1a4s
ka2n1a2u
ka2n1d4
2kanda
k2a1n2e
2k1a4n1g
ka2n3k4
2k1a2n3l2
2k3an3na
k2a4n1n
k1a6n1s
k2ans.
6k3ante4n3n
ka2n1t
kan3t2en
k2a3nu
2k1a4n1w
k2anz.
ka2n1z
k2a2o
2k1a2pf
ka1p
3ka1ra
2k1a2r1b
k2a2r1d
k2a4r1g
k2a3r2i
kari3e3s2
kar2ie
k2a2r1k
2k1a4r1m
k2a2r1p3
kar2pf4
k2a4r1s
ka4r3t
k2arta
2k1art2i
k2a1ru2
k2a2r1w
ka1si1
ka2s1p2
ka2s3s
ka3t2an
k2a1t
ka3t4h
k4a2t3r4
kat3se
ka2ts
2ka4t1t
kau2f1o
ka2u
4kauf3r2
kauf4sp2
kau4f1s
k1a2us
ka4u3t2
2k3auto
1kä
k1äh
k1ä2mi
k1än
kär2
kä4s5c
kä3s
kä1s4e3
kä3t2h
2k3b4
kbe1
kbo4n
k3by2
2k3c
2k3d2
kd2a2m1p2
2k1ec
k1e2f1f
ke1f
k2e1fi4
ke3ge2
ke1g
ke2gl
ke2he.
ke1he
keh4r2s
ke2hr
kehrs3o
kehr4st
2k1e2ic
2k1e2i1g
k1ein
ke1i2n2d
2kei2n1h2
2k1e2i1se
kei1s
ke2la
kel1ac
ke3la1g
kel1a2u
k2e2lä
ke2l3b4
2k1e2le1k
ke1le
ke2len
ke2l1er
2ke3le1t
kel1l4e
ke4l1l
kel3s2k2
ke4l1s
k4e4l1t
2k1e2m1p
ke1m
k2en.
ken3a2u
ke2n1a
kenb2u5s4
ke2n3b4
4k3en4ga1g
ke4n1g
2kenlä
ke2n3l2
ke2no
ken2s2k2
ke6n1s
ken5s4t2e
ken1st
ken3s1z
k2ente
ke4n1t
k3en3t2en
ken3t1h
k2ent2r4
2k1en4t1s
k2entu
2k1en4t3w
2k2eo2
ke2p2l4
ke1p
k2er.
k2e2r1c
4kerf4ah
ke2r1f
ker1fa
k4erfam
k3erge1b
ke4r1g
ker1ge
k3er6ge4b3n2
k3e2r4h2ö
ke2r3h4
ke6r1in6nu
k2e1ri
ke3ri4n1n
kerin6st
ke2r1i6n1s
ke2r1i4n4t
ker4ken
ke2r1k
k2er1ko
k2e4r1l
k3er4la2u
ker3l2a
k3er4le1b
ker1l2e
k6erlebe
ker4ne2u
ke4rn
ker3ne
k1ero
k2ers.
ke4r1s
ke2r1z2
ker4zeu
2k1er2zi
k6es.
ke1s
ke2sel
ke1se
ke4t1a
ke1t
ke2t3h
ke2t3s
ke1u1p
keu6s2c4hl2
ke2u1s4
keu1sc
keusc4h
2k1e2x
2k3f4
2k1g2
2k1h4
kh2o3m
ki3a4
ki1c4h
k2ic
2k1i2de
ki1d
k2i3d2r4
ki2el
ki2e2l3o
ki1f4l2
k2if
ki1f4r2
k2i3k4
2ki1l2a
ki3li
k2i3lo
k2i1mi
k2in.
k2i4n1g
2ki2n1h2
k2i1ni
k2i4n1n
k2i3n4o
ki6n3s
2k1in1se
2k1i4n1t
ki3or
k2i1o
kio4s
5kir
k2is2p2
ki1s
ki1st2
2k2i1z
ki3zi
2k3j
2k1k4
kl2
4kl.
4k5la.
k4lar
4k1la1st
k2le
4k3le.
kle3a1ri
kl2e1a
4k3leh
k4l2e2i1d
4k3l2e2it
k3lem.
kle1m
2k3ler
kl2e2r1a
2k3le2u
kle3u1s4
2kl2i1c
k1li
2k3l2i1g
k2lin
k3lip
k2lir
k2li1sc
kli1s
2kli1st
kli2t2s2
4kl2i1z
2k3l2oc
k1lo
kl2o2i3
k4lop
klo3s2
klo1s2t4
k2l2ö1t
k1lö
k1lu
k1luf2
2k1l2üc
2k1ly
2k1m
k3mas2
k1ma
k2n2
3k2n1a1b
k1na
k3ne
k4n1ei
2k5ner
k3no4b1l2
k1no
kno1b
2k5nor
k3nu
3knü
1ko
ko2al
k2o3a2
2k1o4b1j
ko1b
2k1o2fe
ko2f1f4
koh3lu
ko4hl
k2o1i2
ko1l2a
k2ol
ko3le
ko2l2k5
3k2om
ko4mu
k2on
k2o3n2e
ko6n1s4
ko3nu
2kop.
ko1p2e
kop4fen
kop1fe
2ko2p1s
2ko2p1z
ko1r2a
2k1o2r1c
kor6de4r1g
ko2r1d
kor1de
ko3ri
k2o1s
k2o2s1p2
ko3ta
k2o1t
ko2t1s2
kot4t1ak
ko4t1t
2k1o2u
3k2o1w
ko2we
k1o2x
1kö
k1ö2f
k1öl
2k1p2
k1q
k2r4
2k3r2a1d
kr2a4s3
k3ra2ts
k1r2a1t
2k3r4aum
kra2u
k4r2a1z
2k3rät
2k3r2äum
krä2u
2k3re.
k1re
2k3rec
2k3red.
kre1d
2k3re1d2e
2k3re1f
2k3re1g
k3re2ic
kr2e1i2e4
kreier4
k3re2i1h
2k3r1h4
2kr2i1b
k1ri
2k3r2ic
k3rie3s2
kr2ie
2krip
3kr2i1s
3k4r2on
kro4s1s
kro1s
2k3ruf
k1ru
k2r1ü1b
k1rü
2k1s
k4s1a4m1t
k1sa
k3sam
k2s1an
k2s1a4u
ks2än
ksc4h4
k1sc
ks1e2b
k1se
k2s1e1m
k2se4n1t
ks1e4r1l
k2s1e4r1s
k2s1e2r1w
k2s1i1d
k1si
k2s1in
k2s1o2
k3sof
ks1p2a
ksp2
k3sp2e
k1s2por
ks1p2o
ks2pu
k2s1s2
k1st4
k4s3ta2n1z
k3st2a1t4
k4s1t2e2a4
k3st2e
k2s1t2i1s
k2s1tor
k2s1trä
kst2r4
k2s1tum
kst2u
k2s1u
ks2zen
k2s1z
4k1t
k4t1a4b1s
kta1b
k2t1a1d
4k1t1a4k1t
k1tak
k3tal
k2t1am
kt1an
k2t3a2r
kt2a4re
k2t1a2u2
ktä5s
k1t4e3e
kt1ei
k2te2m1p
kte1m
k2te4n1t
k4t3er1fo
kte2r1f
k2t1e2r3h4
kt2e3ru2
k2tex
k2t1h
kt3ho
k2t1i1d
kt1im
k2t1i4n1g
kt1i6n1s
kti4ter
k3ti3te
k2t1of
k3top
kt1op2e
k4t3or3g4a
kt1o4r1g
kt3or2ie
kto1ri
kt4ran
kt2r4
kt3r2a3s2
kt4ro
kt3run1
kt1ru
k2t3s2
kt1s4t4
k4t1t2
k2tu6n1s2
ktun1
k4t3z
k2u1c
ku1h3
2k1u2hr
kul2a
ku1l
ku3l2e
ku3l2i
2ku2l1p
2k3u2m3l2
ku2m2s
k2u3n2a
kun1
ku6n4s2
kun1st3
2k1u2n1t
2k1up.
kur2bl2
ku2r1b
ku2rei
ku1re
kur2i2e
ku1ri
k4u2ro
kur2s2p4
ku4r1s
kur4st
ku4s2c4hl2
k2us
ku1sc
kusc4h
ku2sp2
kus3s2e1s
ku2s1s
kus1se
ku2su
k2u2ß
1kü
2k1üb
k2ü1c
kü4r2s
2k1v
2k1w
2k3z2
kze3l
5la.
l1a1b
l2a3ba
2la4b1b
4l3aben
2l1a2b5f4
2l1a2b1g2
2l1a2b5h2
4la2b1l2
l2ab2o
l2ab3r4
la3b4ra
lab4ri
2la4b1s
3l2abu
2l1a2b1w
la1ce
la2ce.
1la1d
l4a3d2i
l1a4d1l2
2la2d3m2
2l1a2d2r4
3l2a1du
l1a2d1v2
2laf
la2f1a
la4f3t
l2a2ga
la1g
la2g2i1o
la2g2n
lago2
la2g1o1b
lag5s1e2
la4gs
2la1ho
1lai
lai4s1t
l2ai1s
la2ke1s
l4a1ke
l2a2k1i
l2a2k1k4
l2a1k4l2
2l1al
4la4l1l
4la2l1p
l2a1mi
la3min
lam4ma
la2m1m
1lam2m1f4
l2a2m1p
2l1a4m1t
lam4t4s
la4mun1
l2a1mu
l1anal
la1na
la2n1a2u
2l1a2n3b4
3l2a2n1d
lan2d3a2
lan6d5e2r1w
lan1de
lan6d5e2r1z
lan2d3r4
2l1a2n1f
lan2g1l
la4n1g
lan4g3s2
2lan3hä
l1a2n1h2
l2an3he
2l1a2n3l2
4lan1li
2l3a4n1n
l1a2n3p4
2la6n1s
4l1ansä
2l3ant2r4
la2n1t
l2an2zw2
la2n1z
3l2ao
l1a2p2o
la1p
l3ap4p3l4
la2p1p
la2r1an
la1ra
la2r1ei
l2a1re
la4re1ne
la3ren
3l2a4r3g
lar3i1ni
l2a1ri
2l1a4r3t
l3art2i
l2a2ru
la2s3a4u
la1sa
4la4s3d2
l4a5s2e
2l4a2s1h
2la1si
la2so1
2la2s1p2
3las3ser
las1s2e
la2s1s
l2a2sta
la1st
last1o
l2a2st2r4
las3tur
last2u
la2stü
la2ß3
lat2a
l2a1t
la3t2e
la4tel
2l3at2h
la2t3ra
l4at2r4
la2t2s
2lat3t1a
la4t1t
lat4tan
lat4t3in
lat2t3r4
laub4se
la2u
l2a2u1b
lau4b1s
lau2fo
l2au2f1z
1l2a2u1g
2l1au2s1l2
la2us
2l1au4s3r4
2l1au2s1s2
2l3auto
la4u1t
1l2a1w
law2a4
lä1c
2läf
2l1ä2hn
1lä2n1d
lär2m1a
lä4r1m
lä4s5c
lä3s
lä4s3s
4lät
2lä2u1b
2lä2u1c
2lä2u1e
1lä2uf
1là
2l1b
l3bac
l2b1e1d2e
lbe1d
l4beta
l3be1t
l2b1i1d
l1bi
l2b1i6n1s
l3b2l2a1t
lbl2
l3blä
lb3le
l2b1li
l3b2lo
l4b3re.
lb2r4
lb1re
lb3r2it
lb1ri
l4b2s
lb3sa
lb3se
lb4s1k2
lb3sp2
lb4st3e
lb1s2t
lb4sto
lb2u
l2b3u2f
lbu2s3s
lb2us
lbzei2
l2b3z2
2l1c
l3che
lc4h
lcher3ma3s8
lc2he4r1m
lcher1ma
l3chi
l2c4h3l2
lc2h3r4
l4c4h3s
l2ch3ü
l2c2h1w
l3c4l2
l3co
4l1d
l2d3a2b1
l3d2ac
ld3a2c4k
l2d1a2d
ld1a4g
l2d1ak
ld1al
l3dam
ld1a2m1m
l2d3a2n
l2d1a2r
ld3a1ri
l3das
l3d2a1t
ld1a2u
ld1är
l2d1ei
l1de
l2de1le
l3d4er.
ld1e2r3p4
l2d1e2se
lde1s
l2dex
l3d2i2c
l1di
l2d1i1d
l2d1im
ldo2r
ld2o1s
ld2ö2
ld3r4
l2dran
l2d1re
l3d4ru
ld4rü
ld3sa
l2d1s
ld1s2t4
l2d1t4
ld3t1h
l2d1um
l1du
ldy3
ldy2s2
1le
3le.
le2a1d
l2e1a
3l2eba
le1b
lebe6n4s
leb2en
l2e2bl2
2lec
le2chi
lec4h
lecht4e
le2c4h1t
3le1d
4le2d1d2
le3d2e
l4e2e
le3ei
l2e1f2a
le1f
le2g1a2s
le1g
le2ga2u
le2gä
le2gl
leg4r4
3leh
leh3r2e
le2hr
4le4h2s2
4le4h1t
3lei.
lei2b2r4
l4e2i1b
l2e2ic
l2e2i1d
4l1e2i1g
l2ein.
leinbu4
lei2n3b4
leinb2u3s5
l2ei2n1d
l2ein4du
l2e2i1ne
lei6ne2r1b
2lei2n3k
l2ei4n1t
lei6ss5er
lei3s2s
lei1s
leis1se
l4ei1st
lei4ßer
lei1ß
lei1ße
l2e2it
lei2ta
lei8t7er8sc
lei3te
leite4r1s
lekt2a
le1k
l2e4k1t
2lekt2r4
3l2e1la
2l1e2le1k
le1le
le4l3s
3le3me1s
le1m
le1me
le2m1o2
4le2m1p
l2en.
le4na1d
le2n1a
le2nä
4lende1t
le2n1d
len1de
2len1du
le4n3e2n1d
l2e3nen
le1ne
4l1e2ne4r1g
l2e2n3f
le3ni
l2e2n1k
2l1en3ni
le4n1n
l2e2no
l1en4se1m
le6n1s
len1se
len3s1z
l1en4t1s
le4n1t
2l3en4t3w
lent4w2ä4
5l4ent1we1t
4l1en4t3z2
len2zi
le2n1z
le1o1s2
l2eo
2le1p
3le3p2a
3le3p2f4
3lep2r2
l2er.
l2e1r1a
le2ra4g
le2ra2u
le2r1b4
4l3e2r1e2i1g
le1re
le4r3eim
le4r1e4r1s
l1er1fo
le2r1f
l2erf4r2
l2er1fü
3lergeh
le4r1g
ler1ge
l3er3gen
lergescho1s8
lerge3s2c
lerge1s
lergesc4h2
lergescho7s1se
lergescho2s1s
3l4erge1w
2l1ergi
lergro3
lerg2r4
le2r1i6n4s
l2e1ri
le2r1k2
l2er1ka
l2er1ko
1l2er1l2e
le4r1l
le1ro
2l1er2ö
3l2erra
le2r1r
l4ers.
le4r1s
ler3s2k2
le4r3t
6lerwe2r1b
le2r1w
l1e2r1z
l2erza2
le3s2am
le1s
le1sa
le1s2e
2l1esel
l4e3s1h
le1si1
le3s1k2
le2s3s
le3s2t2e3
le1st
4le4s1w
2le3s2y
le2t4a1t
le1t
2le3t2h
2l2e3to
le2u
4le2u1d
2l3e4u3ro
3l2e2u1t
3l2e1v
2le1xe
l1e2x2i1s
le1xi
2lex1z
2l1f
l3f4ah
l1fa
lfä4s3
l1fä
l2f1ec
l1fe
lf4e1e
l4f1ei3s
l3f4lä
lf2l2
lf3lo
l3f4lu
lf3ra4m
lf2r2
lf2t2r4
l4f1t
l1f4u
lf2ur1
l3fü
2l1g
lg2a3t
l2gd4
lge3n2a
l1ge
l3gen
lge3r1a
lgerä2u3
lge1rä
l2ge3ti
lge1t
l3go
lg3re
lg2r4
l3gro
lg4ro3s2
l4g2s
lg4s1t
2l1h2
3l2hi.
1li
3li1a
l2i3ac
li3ak
li3am4
li3ar
l2i3b4
li1bi3
l2i1c
3liche1m
li1che
lic4h
3licher
li3chi
4li2c4k
li3d2a
li1d
li2d2eo
li1de
2l1ido
li4d3s
l2ie
3lie.
lie3be4s
lie1b
li3e1ne
l2i1en
lie4s3c
lie3s2
lie5s1se
lie2s4s
lie4sta
lie1st
3l2i1g
lig4n
li2g1re
lig2r4
li4g1s2
l4i3ke
l2i1k
li2k2r4
lik2sp2
li2k1s
lik4ter
likt2e
li4k1t
li3l
li1l2a
2lim
li3m2a
l1i2m1b2
3l2i1mo
li3n2a
lin3al
2l1in1du
li2n1d
li4ne1d
l2i1ne
li2n1e1f
li2neh
li2ne1p
li3n2e1s
2l1i2n3f
lin4g2s3
li4n1g
2l1i2n1h2
2l1i2n1it
li1ni
2l1in1j
lin2k1a
li2n1k
lin2k2s
li2n2ol
l2i1no
l2ins.
li6n1s
l2in1sa
l2in1sc
2linsp4
2lin1st
2l1i4n1t
l1i2n1v2
2li2n1z
l2i2o
li4om
li3o6n5s3
lion2
li3os.
lio3s
li2p3a
3lis.
li1s
li3s2a
li4s4chu
li1sc
lisc4h
2l1i2s1l2
2l1i2so
l2i2sp2
li2s1s4
2li1ß
li2tal
li1ta
li3te
li1t2h
li2t1s2
lit3s1z
li2tur
li1tu
3l2i3u2
2li3xi
l2i1x
li2za2
l2i1z
lizei3
4l3j
2l1k
lk1a2l1p
l1ka
l3k2an
l3k2ar.
lke4n3t
lk2l2
lk3lo
l3k4lu
lk4ne
lk2n2
lko2r2b1
l1ko
lk4ra
lk2r4
l2k3ro
l2k3ru
l2k2s1
lk3sä
l3k2ü
4l1l
lla2be
ll1a1b
l2la4b1t
ll1a2f1f
l2laf
ll1a4k1t
l3l2al
l2l1a2m
ll3a1m2a
lla2n
ll2a4n1g
ll2a4n1w
ll1a2n1z
l3la1p
ll1a4r1m
ll1a2u
ll4au1fe
l1l3a2u1g
l2l3a2us
l2l1äm
l2l1b4
llc4h4
l2l1c
l4l3d4
l3lec
l1le
ll1ec4h
lle3en
ll4e2e
l2l1e1f
l2le4g1t
lle1g
l2l2e1gu
ll1eim
ll2e1m
l3l2en.
lle4n3a
ll3en4d1l2
lle2n1d
l2l3en1du
lle4n3g
l4l1en4t1s
lle4n1t
l3l2er.
ll2e2r1a
l4l1er1fo
lle2r1f
l6l3er3gen
lle4r1g
ller1ge
l4lergo
ll3er2n1t
lle4rn
ll3ert2r4
lle4r3t
l2l1e2r1z
ll2e1s
l2lex
l2l1g4
l4lie1g
l1li
ll2ie
ll1i2m1p
l2lim
l2l1i2n1d
ll1i6n1s
l2l1k4
l2l5m
l4ln2
ll1o1b
l1lo
l2lob2e
l2l1of
l2l1opf
l2l1o2r
l3l2or.
l3l4o1re
l2l1o2u
l2l1ö2f
l1lö
ll1ö2se
llö1s
ll3s1h
l4l1s
ll3s2k2
ll2sp2r2
llsp2
ll4s3tor
ll1st
l4l3t4
llti2m
ll4t5s2
l1lu2f
l2l1ur
llu1s2t6
ll2us
l2l3z2
2l1m
l2m3a2b
l1ma
l2m1a2r1c
l3mas2
lma3s1s2e
lma2s1s
lm1a2us
lma2u
l2m1c
lm4e2e
l1me
lm3ei6n1s
l2m1e2p
l2m1e2r1z
lm1i2n1d
l1mi
lm1i6n1s
l2m1öl
l1mö
l2m3p
lm2pf4
lm3s2z
l2m1s
l4m3t
4ln
ln1a4r
l1na
ln3a1re
l3n4e
l3ni
l1nu
l1nü
1lo
3l2ob.
lo1b
lo2ber
lob2e
2l1o4b1j
2l1o2b1l2
l2ob2r4
lo3b4ri
l1o2fe
lo1f3l2
l2o1f4r2
lo2ga2u
l2o1g
lo3h2e
2l1o2hr
loi4r
l2oi
3l2o1k
l4o2k3r4
lo1l2a
l2ol
l3o2ly
lo2min
l2om
lo1mi
lo2n1o1
lo2o
2lopf
2l1o2p3t4
lo1r1a
lo4rä
2lo2r1c
l1o2r1d
lo3ren
l4o1re
2l1o4r3g2
3l2or1q
3los.
lo1s
l2o4s2a
3l2o3se
lo4ske
lo3s1k2
lo2s2p2e
l2os1p2
los1s2e
lo2s1s
lo4s2teu
lo1s2t
lo3st2e
lo2s3to
lo2s3t4r4
lo2ßu2
lo2t1a
l2o1t
l4ot4h
lo3th2a
lo3t3hi
lo3t2i4o
2l1o1v
lo2ve2
2lo1x
1lö
lö2b3
2lö1d
l1ö2f
2l3ö1fe
4lög
l1ö2hr
2l1ö4l
4lö1ß
2l1p
l3p2a
lpe2n3
lp2e
lp2f
l2p1ho
l1ph
l3pi4p
l2p3t4
l3pu
2l1q
2l3r2
lra4s1s
lr2a3s2
lra2t4s
l1r2a1t
lr2om2
lrö4
lrö1s3
l3r2u1t4
l1ru
l2r1ü1b
l1rü
4l1s
l3sac
l1sa
l2s1a2d
l3s2al
l4s1a2m1b2
l3sam
l2s1a2n1f
ls1an
l2s2a4n1n
l3s2a1re
l2s1ar
l2s1a4u2
lsä4s3
l4schi2n
l1sc
lsc4h
l4sch1mü
ls4c2h2m
l3se.
l1se
l2s1e2b
l2s1ec
l2s1e1m
ls1e1re
ls1e4r3g
ls1e4r1l
l2s1e4r1s
l2s1e2r1w
l3se1s
l3s1ex
l4sh2a
l2s1h
lsho2
l2s1i2m1p
l1si
ls2l2o1g
l2s1l2
ls1lo
ls3oh1n2e
lso2hn
l4s3ort.
lso4r1t
l3s2pi
lsp2
ls2p2o
l2s1p4ro1
lsp2r2
l3s2pu
l2s3s2
lst2a
l1st
lsta1b6
ls2taf
l4s3tä1ti
lst2ät
l2s1t2i1s
l2stit
ls2t2r4
ls1um
l2su2n1
ls2u3s1
ls2zen
l2s1z
4l1t
l2ta1b
l4t1a4b1s
l3t2a1g4
l1t1ak
l2t1a2m
l3ta2mi
lt3a2n1d
l2t1a4n1g
l3t1a2r1b
l2t1a4r1t
l2t3ato
lt2a1t
l2t1a2u
l3te.
l2t1eh
l2t1ei1s
l2t1e4le1m
lte3le
lt3e1li
l3t2en
lter3a
l3t2e4r3g2
lt4er1ö
l2t1e2s1k2
lte2s
lte3st2r4
lt2e1s2t
l3t2et.
lte1t
lte2t2h
l2t1eu
l2t1h
l4t3hei
l1th2e
lt3ho
l3thu
lt2i1mo4
l2to1b
l2t1of
lt1op
l2t1o2ri
lt2o2w
lt1ö4l
lt1ö1s
l1t3ö1t
lt4rak
lt2r4
ltr2a3l
l3trä
lt3rä2u
lt3re
lt4r2ie
lt1ri
l1t3r2oc
lt3ro1s
l2t3rö
l4ts
lt3sc
lt1sp2a
ltsp2
lt4sta1b
lt1st4
lt4s3t2oc
lts2to
l4t1t2
l2t1u1h
l2t1um
ltu4ran
ltu1ra
ltu2ri
lu1an
l2ua
4l2u4b3
luba2
lu4b1s2
lu2d2r4
l2u1d
lu2e4s
l2u1e
1luf
2l1u1fe
2l2u2f1f
luf2t1a
lu4f1t
luf2t1e
luf2t5r4
lu2g1a
l2u1g
lu2g1e2b
lu1ge
lug3e2r3p4
lu4g3l
lu2go3
lu2g3r4
lug3sa
lu4gs
lu2gu
2l1u1h
lu1id.
l2ui
lui1d
lu1me2
2l1u2m1f4
2l1u2m3l2
l2u2m1p
l1u2m1s
l1u2m1w2
1lu2n1
2l1u1na
2l1u2n1f
2l1u1ni
2l1u2n1t
2l1u4n1w
4l2u2o
lu2pf
2lur
l1u4rn
l1u4r1t2
2lu1se
l2us
lu2sp2
lu4s4s3a
lu2s1s
lu6s2s1c
lus3sen
lus1se
lu4s2s1o
lu4s2s1p2
lu4s4s1t
lu1s2t
lu2st1a
lu2stä
lu2sto
lu3st2r4
lust3re
lu2s1u
4l2u2ß1
lu2t1a
l2u1t
lu2t1e1g2
lut3e4r3g2
lut1o2f
lu2top
lu2t3r4
3l2u1x
2l1üb
5lüd
lü4h1l
lüs3
2l1v
2l3w
2lx
1ly
ly1ar
ly3c2
2ly2m3p2
3lyn
ly3n1o
ly1o
ly1s2
ly3te
ly1t2
ly1u
2l1z
l2z3ac
lza2
l3z2an
lz2e2r1k2
lz1i2n1d
l2z1o2f
l2zö
l2z3t2
l2z1u4fe
lzu3f4
lzu4g4s
lz2u1g
lz1w2
lz2wec
1ma
m2aa2
m1a1b
m2abe
2m1a2b1k4
m2ab4r4
2ma4b1s
2ma4b1t
mach4t2r4
ma4c4h1t
mac4h
ma2ci
ma3da
ma1d
ma2d4r4
m2a4d2s2
ma1f
ma2ge.
ma1g
ma1ge
ma2ge1b
ma2ge1f4
ma2ge1g
ma2ge1k
ma2ge3p4
ma4ges.
m2age1s
ma2ge1t
ma2g2e1v
ma2ge1w
2m1a4g1g
magi5er.
magi5e4r1s
ma3g4n
2m1ago
ma2i4s2e
m2ai1s
2m1a4k1t
mal1ak
ma1la
ma4la4k1t
ma2lan
m2a4l3a1t
ma2l1a2u
ma4l3d
ma3ler
m2a1le
m2a1li1
mal3lo
ma4l1l
2mal4l3t4
ma1lu2
ma2l3u1t
ma2m3m
2m1anal
ma1na
ma2n1a2u
2m1a2n3b4
man4ce.
ma2n1c
ma2n3d2
man3e4r1s
m2a1ne
ma2ne1t
m2a2n1f
2m1ang2r4
ma4n1g
m2a2n1h2
2m1a2n3l2
m4a4n1n
m1a6n1s
m2ans.
2man1sa
2m1ansä
2m1an1sc
2m1an4t3w
ma2n1t
2ma2n1z
ma2or
m2ao
m2a2p1p
ma1p
2m3a2r1b
ma4r3g2
4m2a3r2o
ma2r1o3d
4m2a2r1r
mar6s4c2h2m
ma4r1s
mar1sc
marsc4h
mar6sc2h2r4
m2a3r2u
m3a2r1z
3mas
ma1s2p2a
mas1p2
4m1asp2e
ma3ss2e1s
mas1s2e
ma2s1s
mas6ses.
mas6se1st
ma6sse1t
ma3s2su
ma1s2t
3maß
ma2t1a2b
m2a1t
ma2tan
ma2t4c
ma2tel
mat2e
ma4t3e2r1d
mat3se
ma2ts
mat1s1p2
2m1au2f
ma2u
m4a3un1
2m1au4s3g2
ma2us
m4ay
ma1yo
3mä
m1ä2hn
mä1i2
4m1ä2n1d
m1ä4r1g
mä1t4r4
mäu2s1c
mä2u1s
2m1b2
mb4e2e
mb4l2
m3b4r4
m3by2
2mc
m3c4h
2m1d
md1a
m2d1ä
m2d1ei
m1de
md1s2e
m2d1s
m2d1um
m1du
1me
me1b4
m2e1c
me1di3
me1d
medi2e4
med2i1en3
2me3dy3
me1e1f
m4ee
me1e2n1
mega3
me1g
3meh
2m1e2if
2m1e2i1g
m2e4il
mein4da
mei2n1d
mei1s2
me1i2so
m2ei1st
me3l1a2m
me1la
me2la2u
3me4l1d
me2le1k
me1le
me2ler
mele1t2
2m1elf.
me2l1f
mel2se
me4l1s
me4l3t4
6m3el6te4rn
2m1e2mi
me1m
m2en.
me2n1a2b
me2n1a
me3nal
men3ar
men3a2u
men3ge
me4n1g
men3gl
me3n1o2r
me1no
m2e6n1s
men4s1k2
men2so
men3ta
me4n1t
2m1en2t5n4
4m3entwi
m1en4t3w
m2e1o
2meo2u
2me1ö2
3mer.
me1r1a
me2r3a1p
me4re6n1s
m2eren
me1re
mer2er
4m3ergän
me4r1g
3merin
m2e1ri
me2r1i2n4d
me2r1i4n4t
3me4r1s
merz4en
me2r1z
3me1s
me2sal
me1sa
me4sä
me1s2e
4meser
2m4e3s1h
4m1e4s1sa
me2s1s
mes6s3e4r3g
mes1se
m2e4s2s1o
me4s2s1p2
me3s2t2e2
me1st
me2st2r4
4mesu
3me2ß1
me3t2a
me1t
me3t2h
meu1
2m1ex
1mé
2m1f4
mfi4l
m1fi
4m1g2
2m1h4
1mi
mi2a1d
mi1a
mi3ak
mi1bi1
m2i1b
mi1c4h
m2ic
mi3da
mi1d
mie3d2r4
mie1d
mi2e1i
mie3l
mi2er
mie3r2er4
mi2e1re
mi2e1t
mie4ti
3m2i1g
migescho7
mi1ge
mige3s2c
mige1s
migesc4h2
migescho1s8
mi2kar
m2i1k
mi1ka
mi2ki
mi2ku
3mil
mi3l2a
milc4h1
mi2l2c
mil4che
4mi2l1z
2m1i2m1p
mi3n2en
m2i1ne
min2e2u
m2in2ga
mi4n1g
min4g2s2
mi3ni
3m2i1n2o
mi1n1u
3mir.
mi3r2a
3mi1r2i
3mi4r1s
3mi2r1w
mi2sa
mi1s
mi4scha
mi1sc
misc4h
mi4s2c2hn4
mi4s2c2hw
m2i1se1
mi6s4s1c
mi2s1s
mis4ser
mis1se
mis3si
mi4s4s1t
mi2ß1
3mit1
mi2ta
mi2t1h
mi2t2r4
mi2t3s2
mit5sa
mi3ts1u
mi2tu
4m2i4t1z
2m1j
2m1k4
m3ka
m2k5re.
mk2r4
mk1re
2m1l2
m2l3c
m4l3l
m4l3s
2m1m
m2m1a1b
m1ma
m2m1ak
m2m1al
mm1a4n1g
m2m1a6n1s
m2m1a2n1z
m2m1a2u
m2m1d2
mm1ei
m1me
mm2ei1s3t
mmei1s2
mme4lin
mm2e1li
mme4n1a
m4m1en4t3w
mme4n1t
mme2r1a2
mme4rec
mme1re
mme2s3a
m3me1s
mm1i2n3b4
m1mi
mm1i2n3f
mm1i2n1h2
mm1i6n1s
mm1i4n1t
mmi3sc
mmi1s
m2m3p
m2m1s2
m2mum
m1mu
m3m2un1
mm2ül2
m1mü
2m3n2
m4ne1si
m1ne
m3n2e1s
1mo
m2o3a3
2m1o4b1j
mo1b
3m2o1d
mode3s
mo1de
m2o2d2r4
4mog.
m2o1g
mo2gal
3moh
m2o2i3
mo2k1l2
m2o1k
2mol.
m2ol
3m2om
mo1m2e
3m2on
m2o3ne
mo4n1er
mo6n3s
3mo2o
2m1op2e
2m1o2p3t4
mo1r1a
mo2r1ar
2m1o2r1c
m2or2d3a
mo2r1d
m2or2d2r4
mo2r1er
m4o1re
mo2r1k4
3mo1s
moster4
mo1s2t
mo3st2e
mo2sto
3m2o1t
m1o2x
mo1y
1mö
m2ö2c
4mök
m1öl
2m1p
m2pf
mp4f3e4r1g
mp1fe
mpf3e2r3p4
mpf3e2r1r
mp4f3e2r1z
mp2f2l2
mpf3li
mp2f1or
mp1fo
m3pi
m3pon
m1p2o
mp3t1a
m2p1t
m3pu
2m1q
2m3r2
2m1s
m2s1an
m1sa
ms3a2n1d
ms1as
m3sä
msc4h2
m1sc
m2s1e1f
m1se
ms1e2r1f
ms1e2r1w
ms1i1n1i1
m1si
mso2r
ms1o1ri
m2s2pä
msp2
m2s1pe1d
msp2e
m1s2por
ms1p2o
m2sp2o1t
m2s1p4ro1
msp2r2
ms2pu
m2s3s2
m4s3t2a1g
m1st
m2stal
ms1um
m2sü
4m1t
mt1a1b
m1t1ak
m3tam
mt1ar
mt3a1re
mt1e4l1t
m2t1e2r1f
m2t1e4r3g2
m2t1e4r1l
m2t1e4r1s
m2t1e4r1t
m2t1eta
mte1t
m2t1eu
m2t1h
mt3ho
m2t1im
m2t1i6n1s
mt2i2s
mtmen2
m2t1m2
mt1me
mt1ö1s
mtr2a4s3
mt2r4
m4ts
mt2sa
mt2s1e
mt3s2ka
mt2s1k2
mts1p2
mt1sp2a
m4t1t2
m1t1um
mt1u4r1t2
m4t3z
1mu
m2u1a
2m3u1h
mu3la
mu1l
2mu4l1s
3mun1
m1un2d1a
mu2n1d
4m3u2n1f
4m3unge1b
mu4n1g
mun1ge
mu3ni
m4u2n1k
m1u2n1t2
4m2u2n1z
mu3ra
mu4r1u2f
mu3ru
mu4s1a
m2us
3mu1si
mu2s1o
mu2sp2
mu3s4se.
mu2s1s
mus1se
mu3s4s2e1s
mu3s2t2e
mu1st
mu2s1to
mu2st2r4
mu2su
mu1ße3
m2uß
mu2t1a2u
m2u1t
mu2ts3
mut2st4
1mü
2m1üb
mül4len
m2ül
mü4l1l
mül1le
3mün
mü3s1si
mü2s1s
3m2üt
2m1v
mvo4l1l1
m3v2ol
2m1w2
mw2a2
mwa4r
1my
2m1z
mz2u1g4
1na
3na.
2n1a1b
na2b1ä
4n1a2b1g2
4n1a2b5h2
na2b1l2
na2b3r4
4n3a4b1s
4na4b1t
3n2a2c
nac4h1
na3chen
na1che
na4c4h3s
nacht6ra
na4c4h1t
nacht2r4
4n1a2d1d2
na1d
n2a1de
4na2d2r4
n1af
na1f4r2
3n2a1g
na2ge1m
na1ge
3n2ah
n2a2h1a
n3a2hn
3nai
nai2e2
n1a2i3g4
2n1ak
na2ka
3n4a1ko
n2al.
na2l1a2
na2lä
3n2a4l1d
n4a1le
na4le4n1t
nalen1
na2le4t
nal3l2a
na4l1l
nal1mo2
na2l1m
na2lop
n2a1lo
n1al2ph
na2l1p
n2als.
na4l1s
na4l3t4
na2lu
2n4a1ly
3na1m4e
n2ame2n1
4n1a2mer
na3m4n2
3na1mo
nam2sp2
n2a2m1s
2n1a4m1t
nam4t4s
n1an.
4n1a2na
4n1a2n3b4
n1a2n1d2
4n1a4n1g
2n1a2n1h2
2n2a3ni
4na2n1k
2n1a2n3l2
3n2a4n1n
na3no
n1a2n3p4
2n1a2n3r2
2n1a6n1s
2n3ant2r4
na2n1t
2n1a4n1w
nap2si
na1p
na2p1s
n1ar
5n2ar.
na2r1a
2n1a2r1c
n2a2r1d
4na4r1g
3n2a1ri
n2a2r1k
n2ar1l2e
na4r1l
2na4r1m
n2a2r1p2
4n3a4r1t
n2a3r2u
3nas
n2as.
na4s2c2hw
na1sc
nasc4h
4nas1p2
4n1a2s2y
n3a2sy1l2
3naß
3n2a1t
n4ata
na3t4h
4n3a2t1m2
na2ts1
nat4sa
nat4sc
4na4t1t
n1a2u
4nauf
nauf4f2r2
na2u2f1f4
n3a2u1g
5n4a2u1i
3n2au1l
4nau4s3b4
na2us
4n1au4s3g2
n2au2so
4nau2s1s2
n4au3st2e
nau1st
4nau2s1w
nau3te
na4u1t
navi5er.
n2a1v
n4a3vi
navi2er
navi5e4r1s
1nä
2nä1b
3n2äc
3n2ä1e
n1ä2hn
2n1ä2m
2n1än
2näp
nä4s4c
nä3s
n2ä4s3s
2näu
3n2ä1um
2n3b4
nb2e2in
nbe3n
nbe3r2e
nb2u3s
n3by2
2n1c
n3ce2n3
n4c2h3m
nc4h
2n1d
nd2a1g
n2d1ak
n2d1a2n3l2
nd2a4n1n
n2d1a2n1z
nd2a1t2
n2d1a2u
n2d1c
nde4al.
n1de
nd2e1a
n2d1ei
nde4l1än
nd2e1lä
n4d3en4t1s
nde4n1t
nde4r1o2b
nde1ro
nde2s
nde1s1e
ndi2a3
n1di
n2d1o1b
n2do2b2e
nd1op
nd1or
n2d1ö
n2d3r2a1t
nd2r4
n2d3re
n2d3ro1b
nd3r2ol
nd3ro1s
n2d3rö
n2dr2ui
nd1ru
n4d3run1
nd2so2r
n2d1s
nd2sp2r2
ndsp2
nd4s1ta1b
nd1st4
nds3ta2u
nd3t1h
n2d1t
ndt4r4
n2dü4
ndy3
1ne
3ne.
ne2a1p
n2e1a
ne3a4s
ne3a1t
n2e2bl2
ne1b
2n1e4b3n2
2nec
3n2e1ca
3ne1d
ne2d2e
2n4ee3
ne2e2i4
ne3ein
n1e1f
ne1g4
2ne2he.
ne1he
2nehen2
3n2e2h1m
4n1e2hr
2n1ei
n2e2i1d
4ne2if
3n2ei4g1t
ne2i1g
4n3ei4n3g2
4n3ei2n3k
ne2ke
ne1k
n2e4k3t4
ne2l
3ne1la
ne2l3b
2n1e1le
4nele1k
4n1e2le1m
ne3len
n2e3li
nel4la
ne4l1l
nel2lä
3n2e3l2o
3n2e3lu
n2em.
ne1m
2n1e2m1b2
n1e2mi
2n3e2m1p
2n1e2m1s
3nen
n2en.
ne2n3a2
n2e2n3b4
n2e2n1c
4n1en2d1b4
ne2n1d
4n1en2d1d2
4n1en2d1f4
n1en2d1g2
4n1en2d1h2
4n1en2d1k4
4n1en2d3p2
4n1en2d1t
4n1en2d1w
ne2n1e2b
1ne1ne
ne2n3ei
3n2e3nen1
1ne4ne1ne
4nen2g3b2
ne4n1g
nen4ge.
nen1ge
nen4g2en
4nen4g2s
4nen4g1t
n2e2n1h2
ne2ni
n2en1j
ne2n3k
ne2no
n2e6n1s
nen1s4e
nen3s1k2
5n2en3t2a
ne4n1t
n1en4t3b2
4n1en2t3l2
4n1en2t5n4
5nent2r4
n1en4t1s
4n3en4t3w
4n1en4t3z2
ne2n3u
n2e2n1v2
n2e4n1w
ne2o2b1
n2eo
ne1o1s2
2ne3p2f4
ne1p
2n1e1p2o
ne2po1s
n2er.
ne1r1a
ne2r1a2b
ne3r4al
ne2r3am
ne2ran
ne2r2a1p
ne2ra2u
4ner3be.
nerb2e
ne2r1b
4nerben
n1er1bi
ne1re2
ne2r1e1b
n1e2r1f
4n5er1fo
ner3for4
2ner1fü
3nerg2r4
ne4r1g
n1e2r3h4
2n3e2r1h2ö
3n2e1ri
n1e2r1k
n2er3l2i
ne4r1l
2n1er1l2ö
ner1ma3
ne4r1m
ner3mas4
n1er3mä
ner4mit1
ner1mi
n2ern.
ne4rn
4n1er2n1t
ne1rö1s
ner1ö
n2e2r3p4
3n2ers.
ne4r1s
2n3er1s2a
ner8sch2le
ners2c4h2l2
ner1sc
nersc4h
n2ert.
ne4r1t
n1ert2r4
ne2rup
n2e1ru
n2e2r1v
2n1e2r1z
3n2e1s
n4es.
ne3s2c
ne2sei
ne1se
ne2s2e1v
ne3s1ka
ne2s1k2
nes1o
ne2sor
n2e2s1p2
4n3es3si
ne2s1s
ne2ta1d
ne1t
ne2t1ak
ne2t1an
n1e2ta1p
n1et4a1t
ne2ta2u
ne2t2h
ne2t3h2a
nett4sc
ne4t1t
net2ts
n1e2tu
net2zi
n2e4t1z
ne2u
ne2u1c
ne2u3g
2n1eu1p
neur2
n2e1w
2n1ex
3ne1z
1né
2n1f
nf1ak
n1fa
n1f2ä
nfä4s
n2f1f4
n3fi
nfi4le.
nfi1le
nf4l2
nf5lin
nf1li
nflö1s4
nf3lö
n1f2o
nf4r2
nft2o
n4f1t
nf2t2s3
nft4st4
n2f1u
4n1g
n2g1ac
ng1a1d
n2g1a1k
n2g1a2m
n2g1a2n3d
ng2a2n1f
ng1a2n1z
n2g1äl
n2g3d4
n3ge1f4
n1ge
n2g1ein
n3g2en
nge3n2a
nge6n1s2
n3ger
nge4ram
nge1r1a
n4g3er3se
nge4r1s
ng6e1s
nge1s2t2
nge4zän
nge1z
ngezä2
n4g3g4
nggescho1s8
ng3ge
ngge3s2c
ngge1s
nggesc4h2
nggescho7s1se
nggescho2s1s
ng3hu
n2g1h
n2g1i2d
n3glä3s
n2gl2i1c
ng1li
n2g1lo
n3g2l2oc
n2g1lö
n2g3m2
n2g1n
ng3n2e
ng1or
ng3r2a1t
ng2r4
n2g3r2oc
ng4ro3s2
n4g2s
ng1s1c
ng4s3e4h
ng2s1e2
ngs3p2a
ngs1p4
ng3ts
n4g1t
n2gum
n1gu
2n1h2
n3han
n3har
n3ha2u
n3hä
n3he
nhe2r
n3hu
1ni
3ni1a
nib4l2
n2i1b
nibu2
nich8te4r1s
n2ic
nic4h
ni2c4h1t
n1i1d
3n2id.
ni2de
n2i3d2r4
n4ie
nie3b
ni1el
ni2e3l2a
n2i1e4n
ni3e1ne
ni1ero
nig2a
n2i1g
2n3i2g2el
ni1ge
nig3r4
ni2g1re
3n2i1k
ni2kal
ni1ka
ni2kar
ni3ker
n4ike
ni4k3i4n1g
nik1in
ni3k3l2
nik1ma3
ni2k1m
ni2k2r4
3n2il
n2i1m2o
4n1i2m1p
nin1
3n2in.
n2i1n4a
4n3i2n1d
2ni2n3f
3n2i4n1g4
4n1i2n1h2
ni2n1or
n2i1no
2n1i6n1s
n2ins.
4nin1se
4n1i4n1t
2n1i2n1v2
ni2o1b
n2i1o
ni3o3k4
ni3ol
n2ip
ni3r2a
3n2i1s
ni4s2c2hw
ni1sc
nisc4h
n2i2s1e
ni3se.
n2i2s1p2
ni3spi
ni2s5s2
ni2st2u
ni1st
ni3stu2n1
ni2s1u
2nit
ni1t1h
ni2ti
ni3t4r4
ni2t2s
ni3t1sc
nit4tec
ni4t1t
nitt4sa
nit2ts
ni3tu
n2i3v
3n2i1x
n1j
2n1k
n2k3a1d
n1ka
n2k1ak
n3k2al
n4k3a2l1g
nk2am
n2k1a6n1s
n2k1a2u4s
nka2u
n2k1äh
n1kä
n2k1är2
n4k3er1fa
nke2r1f
nk4e4r1g
n2k1i2n1h2
n2k1i6n3s
nk3len
nkl2
nk2le
nk3le1s
n2kl2ie
nk1li
nk2lo
nk2lu
nk3lu2n1
nk4na
nk2n2
n2k3ne
n2k1o4r1t
n1ko
nk2ö2f
n1kö
n2k1öl
n2k3ro
nk2r4
nk2sal
n2k1s
nk1sa
nks2ei
nk1se
nk3s2z
nk2tak
n4k1t
nk2t1an
nkt1it
nk4top
nk2t1ru
nkt2r4
2n3l2
2n1m4
nm2e6n2s
n1me
4n1n
nna2be
n1na
n2n1a1b
n2nada
nna1d
n4n1a4l1l
n2n1an
n2n1a2u
n3nä
n2n3d
nne4n3g
n1ne
n3nen
n4n1en4t1s
nne4n1t
nn2e2r3h4
nn2e2r1k
nne2r1ö4
n4n3er4w2a
nne2r1w
n2n1e2r2z
nne2s1e
n3n2e1s
n2n2ex
n2n3f
n4n1g4
n3ni
n2n1of
n1no
nn1o2r
nn3se
n6n1s
nn3s2p4
nn2t1h
n2n1t
n2n1uf2
n2n1u2n1f
nnu2n1
n2n1ur
1no
3no.
3nob1l2
no1b
no2bla
n2o3b2le
2n1o4b1s2
n2o1c
2no2d
n2o3d2r4
n1of
2n3o2fe
n3o1le
n2ol
no2le2u
n2on.
3n2o1p2a
3n2or.
nor2a
no2r2a1d
n2o1rak
n2o3r2al
2no2r1c
n2or2d5r4
no2r1d
3n2or1h4
3n2o4r1m
3n2o4r1s2
n1o4r1t
3n2os.
no1s
n2o3s1h
n2o2s3p2
no4s1s
n2o3st2e
no1s2t
nost2r4
2no2s2t3v
no3ta1b
n2o1t
no2tä
no4t3e1i
no2tel
n4o3t3h
no4th2a
no4t3hi
no2t3in
no2t1op
no2t2r4
3no1v
3n2o1w
2n1o2x
3n2o1z
2nö1d
2n1ö2f
4n1ö4l
nö4s3s
nö1s
2n3p4
npa2g4
np2a
n3p2s2y3
n2p1s
2n1q
2n3r2
nra4s3s
nr2a3s2
nrä2u3s
nrä2u
nre3s1z
n1re
nre1s
nrö2s1
6n1s
n2s1a2d
n1sa
n2s1a4l1l
n2sa4n1g
ns1an
n2sa2n1t
n2sa2us
ns1a4u
n3s3a1v
n2s1än
nsä4s3
n2s1ä2u1s
n1säu
ns2ca
n1sc
n6s1che1f
nsc4h
n4schro
nsc2h2r4
nsch7we2r1d2
ns2c2hw
ns1e1b
n1se
ns1e2d
nseh5e1re
n3s2eh
nse3he
nse3her
n3se2n1k
nsen4sp4
6n1s2e6n1s
ns1e4n1t
n2s1e1p
ns1e2r1f
n4s1er1fo
ns1e4r3g
n2s1e2r3h4
n3s2e1ri
n2s1e2r1k
n2s1er1ö
ns1e4r1s
n2s1e2r1w
n2s1e2r1z
nse2t
n2s1eta
n3s1ex
nsfi4l
n6s3f4
ns1fi
n4sho2f
n2s1h
n2si2m1p
n1si
n2s1i1n1i1
nsi2te
n3s2it
nsi2t3r4
ns2kal
n2s1k2
ns1ka
n2s1op
n4s3ort.
nso4r1t
nsp4
nspas2
nsp2a
n2s1p2a1t
n4s1p2e1ri
nsp2e
n2s1ph
n1s2pi
n2s1p2o
n1s3pon
n2sp4rä
nsp2r2
n4s3pr2i4e
nsp1ri
n4s1p4ro1
n2s3s2
n2s2t1ak
n1st
n4s1tat.
n3st2a1t
n4s3tat2e
ns2ta2u
n5s4te.
n3st2e
n4st3e2if
nste2i
n5s4tel
ns4tem.
nste1m
ns4t6en.
n4ste4n1t
ns2ter
ns4t4er.
nst4er1ö
ns4tes.
nste2s
n5s2teu
nst5op1fe
ns2tor
n4s1trac
nst2r4
ns2tum
nst2u
nst2ü
ns2t1ü1b
n2s1ty
ns2um
n2s1u2n1
ns2u4n1g
ns2u2n3r2
n4s3zi
n2s1z
2n1t
n4t3a4b1s
nta1b
n3t2a3c
n3t2al
n2ta3m
n4t1anza2
nta2n1z
n2t2a2r1b
n2t1a2r1k
n2t2a4r1m
nt4a1t
n2t1äm
n2t1äu
n3te.
nte3a2u
n1t2e2a4
nte2b
nt1ebe
n1t4e1e
nte3g6
n2t1eh
n2te2i1g
n3t2en
nt4e1ne
nten6te.
nt4ente
2n1te4n1t
n3ter
nt4e4rn
nt4e4r1s
nt4e4r1t
n2t1e2s1s2
nte2s
n3te1t
nteu3
nt2e3v
nt2her
nt1h
n1th2e
n2t3ho
n3t2h2r2
n3t4hu
nti3k4l2
nt2i1k
n2t1i2n3f
n2t1i2n1h2
nti1ni1
nt2i6n1s
n3tit
n3t4le1m
n2t3l2
nt1l4e
ntmen2
n2t1m2
nt1me
nt1mo2
n3to
nto3m1e2
nt2om
nto6n2s1
n1ton
ntra2s3s
nt2r4
ntr2a3s2
n2t3rec
nt1re
n2t3re2if
n3tre1p
nt4r2i1g
nt1ri
n3t4rop
n2t3rü
n4t1s
nt3sa
nt4s1a4u
nts2o
nts2p2
nt4s3par
ntsp2a
nt1s2t4
nt4s2to
3n4tu.
n1tum2
ntu2ra
ntu4re.
ntu1re
ntu4re1s
n4t3z2
1nu.
1n2u1a
nu3ar
nu3b4i1
n2u1b
1n2uc
1n2u1d
3n2u1e
nu2e4s
nuf2
nu2fe
1n2u1g
2n1u1h
1n2ui
nu3k4
n2um.
2n3u2m1b2
2n1u2m1f4
2n1u4m1g2
3n4u2m1m
2n1u2m3r2
2n1u2m1s
2n3u2m1z
nu2n1
2nu1na
1n2u4n1g
3nung.
n3ungl
2n1u1ni
2n1u2n1t
1n2uo
2nup
2nur
3n2u2s
nu3sc
nu3se
nu3s1l2
nu4s1t
1n2u2ß
1n2u1t
nu2t1a
nu3te
nu2t3r4
1n2u1u2
1n2u1x
1n2u1z
3nü.
2n1ü4b
nü2r1c
3nüs
1n2üt
2n1v2
n3ver1
n1ve2
4n1w
nwei4st
nwe2i
nwei1s
2nx
1ny.
1ny1h
2ny1mu
n1yo
1ny1r
1ny1s
1ny1w
2n1z
n2z1a1d
nza2
n2z1a4g
n2z1an
n2z1a2u
n2z1än
nzä2
n2zär
nz1ec
n4zen1s2e
nze6n1s
n4z1en4t3w
n3ze4n1t
n4z1en4t3z2
nz3erwe
nze2r1w
nzi2ga
n3z2i1g
n2z1i2n1h2
nz1i1ni
n2z1or
nz2öl
nzu4g2s
nz2u1g
n2z1u2r1k
n2z1w2a
nzw2
n2z1w2ä
n2zwö
n2z1w2u
ño1
2o3a2
o4a1bi
oa1b
o4ac
oa3che
oac4h
oa3chi
o4a1d
oa3de
oa4g
o4ah
o4a3i
o4a3ke
o2ak3l2
o4a3la
o4a3mi
o2ar
o2a3s
3o4a3s2e
oa4si
o4a1t
oa3t2e
o5a2u
o1b
o3b2al
2oban
o3bar
2o3b2ä
2o4b1b
ob2e
2o3be.
2o3b2e1a
ob3ein
2o3b4en
obe2n3d4
oben3se
obe6n1s
ober3in4
ob2e1ri
obe4r2i1s
2o3b2e1w
2o3b2i
obi2t
ob3i1te
1o4b1j
ob1l2
o2b3li
2o3b2lo
2o3bo
o2b3re
ob2r4
o3b1ri
o4b1s2
ob3s1h
ob3s1k2
ob2sta
ob1s2t
ob3s1z
2o3bu
obu2s3s
ob2us
2o3bü
2o3by2
2oc
o2c1c
o1ce
och1a
oc4h
o2cha2b
o1che
oche4b
o2ch1ec
o4ch1ei
oche2r4k
o2c4h3l2
o4c2h3m
och1o
oc1h3ö2
oc2h3r4
o2c4h1t2
och3te
o1chu
o2chu2f
o2c2h1w
o1ci
o4ck2er
o2c4k
ock3s1z
oc2k1s
o1c4l2
o3co
o1ç
o1d
o3d2a
od2d2r4
o2d1d2
o3de3b4
o1de
o3de2c
o3d2e1i
odein3
ode2n1
ode1ne2
o3dex
2o3di1a
o1di
o3dir
o3d2i5v
o2don
odo4s
2od2r4
o2d1re
o2d1t4
2o3du
2o1e
oe2b
o2ec
oe2d
oe2h
oe2l
oe2n1
o4e1s
o2e1t
o3et.
o3e2ts
oe2x
o1ë
2o1fa
o3f1ac
of1am
of1a2u
o2f1ei
o1fe
of2en
o3fer
of2f1a
o2f1f
of2f1in
of1fi
of2fi2r
1of3f2i1z
of2f5l2
of2fo
of2f3r2
of4f1s2
of2fu
2o1fi
of3l2
of1la
o1f4lä
of4lö
2o1fo
2o1f2r2
of3ra
of3rä
of4rü
of1s1a
o4f1s
of4sam
of2sp2e
ofsp2
of2s1p2r2
of2s1u
2o4f1t
of2tei
of3t1h
2o1g
o2g1a1b
oga3d
og1a1l2a
og1a4n1g
o2g1ei
o1ge
og2e2l1i
og2el
ogen3mas6
o3gen
oge2n1m4
ogen1ma
o3g1h
ogi2er
og2lo
o3g4n
og2o1i3
o4g2s2
og3sc
og3s1i
og3s1p4
o1ha
o1hä
o1he
o2h1ei1s
o2h1e4r1t2
o2h1e2r1z
o1hi
ohl1a
o4hl
oh3lec
oh1le
ohl1ei
oh3le1m
oh3len
oh3le1p
oh4l1e4r1g
oh3ler
oh4l3e2r3h4
oh4l1e2r1w
oh3lo
ohl1s2e
oh4l1s
oh2lu
3oh4n1g
o2hn
oh2ni
1oh2n1m4
oh2n1o
o1ho
oho2la
oh2ol
o2h1o2p
o2h3ö
ohr1a
o2hr
oh4rin
oh2ri
oh1ro
o4h3t
o1hu
o2h1w
2o1hy
2oi
o1i2d
o3ie
o1im
oim1mu4
oi2m1m
o1in
oi2r
o2i1sc
oi1s
o3i6s4ch.
oisc4h
o2i3se
o1i4s3m2
oi2s1s2
oi4st
2o1j
2o1k
oka2la
o1ka
ok2a1le4
3o2kel
ok2i2o
ok1lä
okl2
ok4n2
4ok2r4
ok2s1p2
o2k1s
o4k1t4
2ol
o1la
o2l1a1b
o2l1ak
ol2ar
ol1auf
ola2u
o1lä
ol4dam
o4l1d
ol4d3r4
ol4e3e
o1le
ol1ei1e
ol1ei1s
oler2
ol1ex
o1lé
ol2fa
o2l1f
ol2f2l2
olf2r2
ol2fra
ol2gl
o2l1g
ol2g2r4
o1l2i
ol2i3k4
oli3tu
ol2k2l2
o2l1k
olk3r4
ol2k1re
ol2la1d
o4l1l
ol2lak
ol2l3a6n1s
olla2n
ol2las
ol2l1a2u
ol2lä1d
ol4l1ec
ol1le
ol2l1ei
ol2l1el
oll5en2d1s
olle2n1d
ol4le2r1k2
oll5erwe
olle2r1w
o3lo
ol2of
olo1p2
ol1o4r1t
ol1s2t
o4l1s
ol2s2t2r4
o1lu
3o1ly
1olym
ol2z1a2
o2l1z
ol4z3e4rn
ol2zin
ol2z1w2
2om
o2m1a1b
o1ma
oma4ner
om2a1ne
om2a4n1w
om1a4r1t
o2m1a2u
o2me1b4
o1me
om2e3c
o2m1ei
o3m2ei1s2
o2mel
o3m2en.
o2me1p
o2m2e1ru
om1e2r1z
o3m2e1s
omi2e1t1
o1mi
o2m1i2n1d
om1i4n1g
o2m1i4n1t
om3ma
o2m1m
om1o4r1g
o1mo
om3pf
o2m1p
o2m1s2
omtu3
o4m1t
o4m1u2n1t2
o1mu
o3mun1
o1my1
2o1na
o2n1a2b
o2n2a1e1
o3nal
on1a1p
o2n1a2r1b
on1ar
on2a2u
on3a2us
2o1nä
onbe3
o2n3b4
2o2n1c
onderer5
o2n1d
on1de
onde1re
2o1ne
o2n1e2i
o3ne2n1
one4n3g
on1e2r1b
o2n1e2r1d
on1e4r1g
on1er1ö
o3ne4t1t
one1t
o2n3f2
on3g2l
o4n1g
ong4r4
on4g3s
4o3ni
on2i3d
o4ni2k2r4
o3n2i1k
o4n1im
o3n3i4n1g4
onin1
o2n3k2
onli4n
o2n3l2
on1li
onl2o2c
on1lo
on3n2an
o4n1n
on1na
on3n2e
o1no1
o3no2d
o2noke
on2o1k
o2n1o2r1c
ono3s
on1s1a
o6n1s
on3sa4g
on2s1e1b
on1se
onse2l
on4s1h
onsi2d
on1si
on2s3l2
ons1p4
on4st2h
on1st
on3t2a
o2n1t
on4t3e2n1d
on3t2en
ont3e2r1w
on3ter
ont2h
on2t3ri
ont2r4
o1nu
2onu3k4
o2n3v2
1ony
o2n3z
o1ñ
oo2k3l2
o2o1k
o1op
o1or
o2o2r3f
oo4s1k2
oo1s
oo2t2r4
o2o1t
2o1ö2
o1p2a
opa1b4
o2p3a1d
op3a4k1t
o1pak
o3pan
o1pec
op2e
o1pei1
o1pe4n
2o4pf.
op2f3a
op3f4ah
op4fe2r1d
op1fe
opf5er3de3
opf2l2
opf3la
op1f4lü
4o1ph2
o3phe
o1pi
opi5a4
opi3er.
o3pier
opi5ers.
opie4r1s
opin2
op5la1g
o1p2l4
o3p4la
o2p3le
op3li
2o3p2o
op4p3l4
o2p1p
op2p2r2
2o1p2r2
1op1si
o2p1s
op3s1z
1o2p3t4
o1q
2or.
or1a
or3a2b
2orak
2or2al
o2r3a2l1m
or4a4l3t
3or2am
or2a2n1d
o2r1a2n1h2
o2r3a2r1b
or1ar
o1r2a3s2
or3a4t1t
o1r2a1t
o3rä
or1ä2n1d
or1ät
or2bar
o2r1b
orb2l2
o2r1c
2or1ca
or2ce
2orda
o2r1d
or2d1am
or2da2u
or4d3e4n3g
or3den
or1de
or2deu
or2d1ir
or1di
or2d1it
1or4d5n2
or2do
2ord2r4
2or2d1s
or2dum
or1du
2or2d1w
4o1re
ore4as
or2e1a
o2r1e2c4k
o2r1e1f
ore2h
o2r1e2i1g
o2r1ein
or1er
o2r1e2r1f
or1et2h
ore1t
2o2r1f
or2f1le
orf2l2
or3g4a
o4r1g
2orge1t
or1ge
or3g2h
2orgi1a
orgi1e
or2gl
or3g2l4e
or2g1n
2or1h4
2o3r2ic
o1ri
4orie.
or2ie
o4rie4n1t
or2i1en
o3rier
4oril
4orin1
2or2it
or1k2a
o2r1k
or2k3ar
or2k3s
2o4r1m
or4m1a6n1s
or1ma
or4me4n1t
or1me
or5ne.
o4rn
or3ne
or3n2o
2o1ro
or2o3n2a
or2on
2o1rö
2or1q
2o2r1r
orr4a
or3r1h4
2o4r1s2
or3s4a
orsch5li
ors2c4h2l2
or1sc
orsc4h
or3s1h
or3si
or3s1z
or2t1ak
o4r1t
or2t1an
or2t1a2u
or2t1är
or2te1f
ort3e2i1g
or4t3e4n1t
orte2n1
or4t3e1re
ort3e2r1f
or2t3e1v
or2th2e
ort1h
ort3i6n1s
or4t3o2f1f
or2t1or
or2tö
or4tra2u
ort2r4
or4t3rä2u
or1trä
ort3re
ort3r2ic
ort1ri
or2t1um
o3ru
o3r2uf
o4r3un1
or2us3
o2r3ü
o2rya
o1ry
o1s
2o3s2a
os3a1d
os4an
o3sche
o1sc
osc4h
2o3se
o3s4e3e
o2s1ei
ose2n
o4s1en4t1s
ose4n1t
2o2s1h
o3s2hi
2o1si
o3s1k2
o4s3ka
o4ski
2os2k4l2
2os2ko
os2lo
o2s1l2
2oso
2os1p2
os2p2e
os3pec
o3s2p2o
os2p2r2
o4s2sa
o2s1s
os5s3a2n1d
oss1an
o4s4s2ä
o6ssel
os1se
o3sse1m
oss3e2n4k
o3sse4n1t
oss3e2n1z
o4s2s3o
os4s2on
o4s2s3p2
o4s4s3t
os2su
o1s2t
o2st1a2b
o3s3t2al.
o4s2t1am
os2t3a4n1g
osta4s
ost1a2u
o4ste2r1d
o3st2e
oste1r3e
ost5er6we
oste2r1w
o4st3h
o2stin
os1t1o1b
o4s3ton.
os1ton
ost3ran
ost2r4
o2s1t3rä
ost3re
ost3r2o1t
o3s2t3uf
ost2u
2osu4
2o3s2y
o3s2ze
o2s1z
o2ß1el
o1ße
o2ß1e2n2k
o2ß1e2n1z
o2ß1e1re
o2ß1e2r1f
o2ß3t
2o1t
ot3a2go
o3t2a1g
o3ta2r1k
o2t1a2u
o1t3a2u1g
o1ta2u4s
o3t2a1x
o2te1b
o3t2e1i
o2t1ei4n
ote2l1a4
ot2e4lei
ote3le
ot4e1m3
ote2m1p2
o2t1e2r1w
4ot2h
o1t4h2e
ot5hel
o2t3hi
ot3ho1s
ot2ho
o2t2h2r2
o2t1i2m
ot2in
o4t3l2
o4t5li2
ot4ol
ot1opf
ot2or
oto2r1a
o3tra
ot2r4
o2t3re
o1t3rin
ot1ri
ot2sa
o2ts
ots1p2
ot2sp2a
ot1s2t4
ot2s3t1ri
otst2r4
ot4te2r1k
o4t1t
ot3ter
ot2t1h
ot2t3r4
o2u
o2u1b4
ou2ce4
o2uc
ou1f4l2
o2u1g2
ou2ge
ou3g1l
o3u1h
ou4le.
ou1l
ou1le
o3um
o3un2d1s
oun1
ou2n1d
ou4n1g5
oun4ge.
oun1ge
oun4g4s2
o4up
2our
our2i2e
ou1ri
our4ne.
ou4rn
our3ne
ou3s2i
o2us
ou1s2t
outu4
o2u1t
2ou1v4
2o1ü
o1v
2ovi
ovi3s2o3
ovi1s
2ovo
2o1w
o3wec
owe2r1
o3wi
o1x
ox2a
o1x2e
1o2xi1d
o2x3l2
o2xu
1o2xy
o1yo
2o1z
o3z2a2
oz2e
ozen4ta
o3ze4n1t
o3zi
ozon1
ó2r1d2
ö1b
öbe2la
ö3bel
öb2e4li
öb2l2
ö2b2le
ö2b3r4
2öc
ö1c4h
ö2c4h1l2
ö2c2h2r4
öch1s2t
ö4c4h1s
öch4st2r4
ö2c4h1t4
ö1d
ö1di3
ö1e
1öf
öf2f3l2
ö2f1f
öf3l2
öge6n4s1
ö1ge
ö3gen
ög3l
ög3r4
ö4g2s
ö1he
öh3l2e
ö4hl
öh3ri
ö2hr
ö4h2s
ö1hu
ö3ig.
ö2i1g
ö1ke
ö2ko
ök3r4
ö2k2s
3öl.
öl1a2
öl1ei
ö1le
öl1e1m
öl2f1ei
ö2l1f
öl1fe
ö2l1im
ö1li
öl1in
öl2k3l2
ö2l1k
öl3la
ö4l1l
öll1ma4
öl2l5m
öll3mas3
öl2n1a4r
ö4ln
öl1na
ö1l1o2
ö4l1s2
öl3sa
öl3s1z
ö2l1u
öl2u4n1g
ö1lu2n1
ölz2w2
ö2l1z
ö2m2s
2ön
ö1n2e
ö3ni
önizi1
ön2i1z
ön1n2e
ö4n1n
ö1nu
öo1
ö1p2e
öpf3l2
ör3a2
ö2r1c
ör2d2r4
ö2r1d
ö2r3ec
ö1re
ö2r1ei
ö2r1e2l
ör2e4r1g
ö2r1e4r1l
ö3r2e2r1z
ör2f3l2
ö2r1f
ör2gl
ö4r1g
ö2r1im
ö1ri
ör2kl2
ö2r1k
örner2
ö4rn
ör3ne
ör1o2
ör1s2e
ö4r1s
ör3s2k2
ört2e
ö4r1t
ör2t2r4
ö1ru4
ö2r1u3ne
örun1
ö1s
ö2sa
ö2scha
ö1sc
ösc4h
ö4s4ch3ei
ö2s2c4hl2
ö2s4c2h3m
ö2s2c2hw
ö2s1ei
ö1se
ös4en
ös4e1s
ö2sp2
ö3s2s
ö6s4s1c
ös3s2e1s
ös1se
ö4s3se1t
ö4s4st
ö1s4t
ö2sta
ös4u
ö1ß
2ö1t
ö2t3a
öte4n3
öt2h
öt2sc
ö2ts
öt2t2r4
ö4t1t
ö1v
ö1w
ö1z
öze3
öze2s4
p2a
1pa.
1p2aa
1pac
pa3da
pa1d
pa2d2r4
pa1f4r2
pa1g4
pa3g1h
pa1ho
1pak
p2a1k4l2
pak2to
pa4k1t
3pa1la
p2al2a3t
1pa1lä
p2a3li
2pa4l1t
pa2n1ar
pa1na
pa2n3d
pan4d1s
pa3n1ei
p2a1ne
pa2ne2u
pa2n3k4
2p1a2n3l2
2p2a4n1n
1pa2no
pan3s1l2
pa6n1s
pa2n1t2
pa2n1z4
1pa1p
papi2
papi2eren8
pa3pier
papi2e1re
papie8r7e2n1d
3pa1ra
pa2r3af
par3a4k1t
1p1a2r1c
pa5re1g
p2a1re
pa5re1k
2par2er
2pa4r1g
parge4l6d
par1ge
parg2el
1park.
pa2r1k
par4k2am
par1ka
par4ka2u
par2kl2
par2k2r4
1p2a1ro
2pa2r1p2
1par2t5n4
pa4r1t
1par1ty
pa2r3z2
pa1s2p2
pa2ßu2
1p2a1t
pa2t4c
pat4e2
p4at4r4
1pa2u
p3auf
pa3u1ni
p4aun1
1pä
3päc
3pä1d
3pär
3pä4s3
pä4t1e2h
pä3te
pä4t3e4n1t
päte2n
pä2t3h
pä2to
pä2t3s
2p1b
pbe1
2p3c
2p1d2
pda2
p2e
1pe.
p2e2a
pea4r
1pe1d
pe2en
p4ee
pe1f4
pei1
2pe2ic
pe1im
pek2t4s2
pe1k
p2e4k1t
2pek2u
3pel
pe2l1a4
pe4l3d
pe2le1t
pe1le
pe2le2x
pe3li4n
p2e1li
pe4l3i2n1k
pel3l4e
pe4l1l
pel3li
1pe1m
pe2n1a4
pe3n2al
pen3da
pe2n1d
p2e4nen
pe1ne
1pe4n1n
pe2n1o
pe6n1s2
3pen1si
1pensu
pe2n3z2
1pe1p
pe1r1a
per2an
1pe4r1l
per4na
pe4rn
3pero
per2ra
pe2r1r
perr3an
per4rä
per6rie1g
per1ri
perr2ie
3pe4r1s
perw2a4
pe2r1w
pe3sa
pe1s
pe2s3s2
3pe1t
1pé
4pf.
p2f1a1b4
p1fa
p2fa1d
p2faf
pf1ai
p2f1ak
pf1a6n3s
p2f2a4r
pf3a1re
p2f1a2u
4p3fe.
p1fe
p2fei
pf1eim
pf1ein
p3fen.
p2f1e4n1t
p3f2er.
pf2e2r1w
p3f2e1s
p2f1f4
p2f1i6n1s
p1fi
p2f3lä
pf2l2
pf3lei
pf1le
pf3l2ie
pf1li
pf3lo
pf3lu
p2for
p1fo
pf3r2
pf1ra
p4f1s2
pf3s1l2
pf3s1z
p4f3t
2p1g
pgra2
pg2r4
1ph
4ph.
ph2a
2phä
2p2h3b2
4p2h3d4
2p1hei
phe2n3d2
phe6n3s
2ph1e4r1s
2p2h3f4
4p2h3g2
phi2ka
ph2i1k
4p2h1k4
p4h2l
2p2h1m
2p2hn
p3hop
2p1h2ö
p2h4r
2p4h1s
p4h3t2
2ph1th2e
ph2t1h
ph2u4s
2p1h2ü
2ph1z
pi2a3
pi3as.
p2ias
pi3c4h3l2
p2ic
pic4h
p4i1d
piegel4e2i8en
pie1g
pie3ge
pieg2el
piege3le
pieg2elei
piegelei1e
pi2el
pi2e2l1a2
3pier
3p2i1k
1pil
pi3le
pil4zer
pi2l1z
p2i1n2e
pin3g2en4
pi4n1g
pin1ge
pin4g3s
3pin1se
pi6n1s
p2i2o
pi3oi
pi3o1nu
pion2
3pip
pi2p2e
pi4pel
pi3r2i
3pirin
3pi1s
4piso
pi1s2t
pi3t2a
pi2t2s
2p2i4t1z
pi2z1in
p2i1z
p1j
2p1k2
pku2
pkur1
1p2l4
2pl.
3p4la
p5la1d
pla4n3g
3plä
2p3le.
p1le
p2le1c
p4le1g
p4le1m
3ple5n4
2p3l2i1g
p1li
p4l2i1k
p4l2i1z
p4lo
2p3lu
2p1m2
p1ma1
2p1n
1p2o
po3b4
p2o1c
3po1d
2p3oh
p2o2i
po3i2d
3po1in
3p2o1k
3p4ol
po2la2u
po1la
po3l2i
po4lor
po3lo
2po2n1d
2po4n1n
po1o2b
po2p3ak
po1p2a
po2p3ar
po1p2e
po2p2l4
p1o3p3t4
p2o1r2al
por1a
po1ra2u
2po4rn
por4tin
po4r1t
por4t3re
port2r4
por4t1ri
p2o3s2e
po1s
po1s4t
po2sta
po2s3t3a1g
po2stä
po4st3e2i
po3st2e
post3ra
post2r4
po3ta
p2o1t
3pote
po2t1u
p2o2w
po3x
pö2b2l2
pö1b
p2ö2c
2p1p
p2p3a2b
pp2a
p2p3a2n3l2
ppe4ler
pp2e
p3pel
ppe1le
ppe2n1
p2p1f4
p2p1h
p3p2ho
p1p3l4
pp5lan
p3p4la
p3p1lä
p2p1le
p2p3ra
pp2r2
p2p3re
p2p1ri
pp3sa
p2p1s
p2p1t2
p2r2
1prak
pr2a4s3
1pr2a1x
p4rä
1p3r2ä1d
1p4rä1g
3p2räm
3prä3s
2p3re.
p1re
2prec
1pre1d
pr4e2e1
1prei
3prei1s
prei4s2s
2p3rer
3p4re1s
1p2re2ß1
pr2i4e
p1ri
2p3r2i1g
1pri2n1z
1p4ro1
3pro1b
2pr2oc
3pro1d
3pr2o1g
3pr2o1j
2pro2s3s
pro1s
3pr2o1t
1prüf
p1rü
2prün
2p1s
4ps.
ps4an
p1sa
p3se
p3s2h
ps1i1d
p1si
p2sö
ps2p2o
psp2
p3s2t2e
p1st
p2st3r4
p2st2u
3p2s2y
ps2ze
p2s1z
2p1t
pt1a
pt2a1b
pt3a2l1b
pt3a1t
p3te
p4t3ec
p4t1ei
pte4l
p4te3le
p4t1e4n1t
pt3erei
pte1re
p4t1e2r1w
p4t1e2r1z
p2t1h
pt1in1
pto3m1e2
pt2om
p4to3s2
pt2o2w
pt1p2o4
p4t3p2
p2t3r4
p2t1s2
p4t1t2
p1t1um
p3tu4n1g
ptun1
pt1u4r3s
p2tü4
3p2ty
p4t3z
1pu
p2u1a
p2u1b4
2p2uc
pu2d2r4
p2u1d
2p1u1h
pul2sp2
pu1l
pu4l1s
2pu2n1d
pun1
pu6n2s2
2p1u2n1t
2pur
3p2u1t
pu2t2s
1püf
2p2ül
pün2
2p1v
2p1w
pwa4r
pw2a
3py1
py3t2
2p1z
qu4
1queu
q2u1e
qui3s
q2ui
1ra.
ra2a1b
r2aa
2r3aa1c
r3aal
r4a3ar
r1a1b
ra2bar
r2aba
rab2bl2
ra4b1b
2r1a2b3d4
2r1a2b5f4
2r1a2b1g2
1r4a1bi
ra2b3r4
2ra4b1s
2ra4b1t
2r3a2b1w
1r2a3by1
ra1ce
2r1a1ce1t
ra4che1b
ra1che
rac4h
ra4chi2n
ra1chi
racht3r4
ra4c4h1t
rach6trä
ra2chu
r2a2c4k
1r2a1d
r4ad.
ra2da2m
2r3a4d1a1p
3r2a2d1f4
3ra4d1l2
r3a2d3r4
ra2d3t1
1r2a1e1
r2af
ra3f3ar
ra1f1a
ra2fer
r2a1fe
ra3ge
ra1g
ra3g2l4e
r2a2gl
ra2g2n
3r2a2h1m
4ra4h1t
r2ai
2ra2ic
rai4l4l
2r3air
1r4a1ke
3r2a1k4l2
ra2k1re
r2a1k2r4
r3a2kro3
2rakti
ra4k1t
3r4a3kü
r2al
r4al.
ra2la4
ral3a1b
r3a2lar
ra2l3b
3r4a4l1d
r2a3le
2ra2l1g
r4a1li
rali5er.
ral2ie
rali5e4r1s
ra2l1k2
ral3la
ra4l1l
ral1l2e
2ral2l1g4
2r3alm.
ra2l1m
r3alp.
ra2l1p
2ralp2e
r4a4l1s
r3a4l3t
r4al2t2h
ra2lu
3r4a1ly
r2a1m4e
ra2mer
1r2a1mi
r2a2m1m
ram4man
ram1ma
ram6m5e4r1s
ram1me
ram4m3u
ram2p3l4
ra2m1p
2r1a4m1t
ram4t4s
r2an.
4ra2n1c
r4anda
ra2n1d
r4an1de
ran4de1p
ran4d3er
4r3a2n1ei
r2a1ne
r4aner
2r1a2n1f
1rangi
ra4n1g
ran4i1e
r2a3ni
ran2k2r4
ra2n1k
2r1a2n3l2
2r1a2n1m4
2r1a2n3p4
2r1a2n3r2
r2ans.
ra6n1s
r2ansp4
ran4s1p2a
2r3ant2r4
ra2n1t
2r3a4n1w
r2a1p
2ra2pf
r1ar
r2a1ra
2r1a2r1b
3rarei
r2a1re
r2a2r3f4
ra2r1in
r2a1ri
r2a2r1k
r2a2r1p2
2r3a2r1z
r2a3s2
r4as.
ra1s4a
ra4s2c4hl2
ra1sc
rasc4h
r4a5s2e
ra5si
ra4s1k2
2r3a4s2ph
ras1p2
ra4s3s2i
ra2s1s
2raß
1r2a1t
r4at.
ra2t1a
ra3ta.
ra3t2e
r3a2t3l2
r4at4r4
rat2st4
ra2ts
2r3at3ta
ra4t1t
4r2au.
ra2u
3raub.
r2a2u1b
4ra2u1d
rau3e2n1
r2a2u1e
2rauf
2r2a2u1g
3r4aum
rau4m3a1g
rau1ma
rau4m2an
rau2mi
3r2au1sc
ra2us
2r1au4s3g2
rau2sp2
2rau2s1s2
rau4sti
rau1st
raus3t2r4
4ra4u1t
r2au2t5s
1r2a1ü
r2a1x
rax2i4s1
rä4c4h4s
räc4h
3r2ä1d
4räf
4rä1g
2räh
2räm
3r2än.
3r2ä3ni
3r2ä6n1s
2r1är
r2är.
rä3r3a2
rä4sa
rä3s
rä4s5c
rä5s1s2e
rä2s2s
rä2st2
3rät1se
rä2ts
rä2u
rä2u2s
räu5sche
räu1sc
räusc4h
4rä2u1t
2r1b
r2b1a1b
r2b1a2de
rba1d
r2bak
rba1l3a
r3bal
rb2a3re
rb1a4r1t
rb1auf
rba2u
r4b1b2
rb1e1c4h
r1bec
r4b2e1lä
r3bel
rb1e4n1t
rbe3r2e
r3b2la
rbl2
rb3la2d
r8b3las3ser
rblas1s2e
rbla2s1s
r4b3la1st
r3blä
r2b3le.
rb2le
rb3ler
rb2lin
rb1li
rb2lö
rb3mas3
r2b1m
rb1ma
rb2o
rb4ri
rb2r4
r4b2s
rb3se
rb4sei
rb3s1ka
rb2s1k2
rbs1o
rb3sp2
rb4stä
rb1s2t
rb3st2r4
rb2u
2rc
r1ce
r1che.
rc4h
r1chen
r1chi
r2c4h3l2
r4c2h3m
rc2h3r4
r4c4h1s2
rch3sp2
rch3st4r4
rch1st
rch3t1a
r2c4h1t
rch6te2r1w
r2c2h1w
r1ci
r1c4l2
r1ç
2r1d
r3d2ac
r2d1af
r2d1ak
r2d1al
rd2a3ni1
rd1a2n1t
rd1a2n1z
r4d1a1p
r2d1ei
r1de
rd2ei.
r2d1e2l1b
r3den
rde2n3d2
rden4gl
rde4n3g
rde3re
rder4er3
rde2r1i6n6s
rd2e1ri
r4d3er2n1t
rde4rn
rd2e3s1p2
rde1s
rdi3a2
r1di
rd2ia4l
r2d1i4n1n
rd1it
r2do2b2e
rd1o1b
r3don
rd1o1s
rdo4s2t1
r2d1ö
rd3r2a1t
rd2r4
rd4ri
rd1rü4
r2d1t4
rd3ta
rd3t1h
rdw2a4
r2d1w
1re
3re.
re3aler
r2e1a
re2a1le
re2a4l1t
re2am
re3as
re3at.
re2a1t
re3a2t3s2
2re1ä4
r2e2b1a
re1b
r2e2b1l2
r2eb2r4
reb3ra
re2bü
r2ec4h
rech3ar
4re4c4h1s
2reck.
re2c4k
2recki
3red.
re1d
4re2d1d2
2redit
re1di
re1el
r4ee
re1er
3r2e1fe
re1f
4re2f1f
3r2ef2l2
3r2e3f2o
3re1g
5reg.
reg2e4l3ä
re3ge
reg2el
re2hac
r2e1ha
re4h3e4n2t3
re1he
re2h1i
re4hl4
re2h1o
r2ei.
rei4bl2
r4e2i1b
r2ei1e
2re2i1g
3r2eige1w
rei1ge
rei3l2a
re4il
rei3l2i
re1i2m2p
r1ein
rei3nec
re2i1ne
4rei4n3g2
r3ei2n3k
4re2i2n3r2
1rein8s7t1re
rei6n1s
rein1st
reinst2r4
re1i2n2v2
reis2ter6
rei1s
rei1st
rei3st2e
reis5tro
rei2st2r4
3re1k
4re2ke
re3la
2r1e2l1b
re1l2e
rel2e1a4
r2e3lei
2re2le1k
2r1e2l1f
r2e3lo
2r1e4l1t
r2e1lu2
r4em.
re1m
r2e1mi
4r3em2pf
re2m1p
4re1mu
r4en.
r2e2n1a
re2n1a2b
re3nal
re2nä
3ren1di
re2n1d
ren3d2r4
re4n3e2n1d
r2e3nen
re1ne
ren4gl
re4n1g
2r1en2g1p2
re2ni
ren4n2e1s
re4n1n
ren1ne
r1en1se
re6n1s
2r1en2t3l2
re4n1t
2r1en4t1s
2r1en4t3w
4r3en4t3z2
r2e2n1z
re3or
r2eo
3rep2e
re1p
3re1p2o
4re2p1p
3r4er.
2r1e2r1b
r2er3b2r4
2r1e2r1d
r2erer
re1re
r1e2r1f
r1e4r1g
r4er3gen
rer1ge
r1e2r1k
4r3erken
r2erki
r1e4r1l
4r3erla2u
rer3l2a
2rer1l2ö
2r1e4r1m
re4r2n
2r1er1nä
4r3er6n1s
4r3er2n1t
r2e1ro
re2r1o2b
r1er1ö
3r2ers.
re4r1s
2r1er1s2a
r2er3se
2rer3s2p4
r1e4r1t
r2erte
2rert2r4
2r1e2r1z
rer5ze
r2erzy
3r4es.
re1s
re2sa
res3an
3re1se
3reso
2re2s1s
res1s2e
res6s5e2r1w
3re1st
res3te1m
re3s2t2e
re2s2t2u
3resu
2re2ß1
re2t2hy
re1t
ret2h
re2u
re2u3g2
2reu1l
re3u1ni
r2eun1
2r1eur
2re3ü
2r3evi1d
r2e1v
r1e1w
rewa4r
r2ew2a
re2wi
2r3e2x1
3re1z
4re3zi
1ré
2r1f
rfal4l4s
r1fa
rf2al
rfa4l1l
rf1ä4l1t
r1fä
rfä4s3
rf2äu
r2f1e4n1t
r1fe
rf2e1s
rfgescho1s8
r4f3g2
rfge3s
rf1ge
rfge3s2c
rfgesc4h2
rfgescho7s1se
rfgescho2s1s
rfi4le.
r1fi
rfi1le
rf3l2i1c
rf2l2
rf1li
rf3lin
rf4lö
r3f4lü
r3for
r1fo
rf4ru
rf2r2
rf4rü
rf2sa
r4f1s
rf2s1ä4
rf2s1i2d
rf1si
rf2s3p2r2
rfsp2
rf2ta
r4f1t
rf3t4r4
r1f2u
4r1g
r2g1a2d
r2g1ah
r2g1a1k
rg2an
rga5ssen
rga2s
rga2s5s
rgas1s2e
rga1s2t
rg2a4st2r4
rge4an
r1ge
rg2e3a2
rg2e2bl2
rge1b
rge4l3er
rg2el
rge3le
rgen4z3w2
r3gen
rge2n1z
rge4r2al
rge1r1a
r2g1e4ta1p
rge3t2a
rge1t
r2g2e3to
r2g3i4sel
rgi1s
rg2i1se
r2glan
rgl2a
rglei4c4h8s7
rg2l4e
rgl2e2ic
rgleic4h
r2gle2u
r2g3l2i1g
rg1li
r2g2no1
rg1n
r2g1o1b
r2g3r2al
rg2r4
r2g3re1g
rg1re
r2gre1s
r2g3re1t
rg3rin
rg1ri
rgro5s1se
rg4ro1s2
rgro2s1s
rg1s1p4
r4gs
rgs2t2r4
rg1st
r1h4
2rh.
2rha
r2ha.
2rhä
3r4he.
3r4hen
r3her
r2h2o1e4
r2h2o2i3
2rh2ol
2r1h2ö
2r4h1s
1ri
ri3am4
ri1a
ri3a1t
rib2bl2
r2i1b
ri4b1b
ri1ce
r2ic
ri1ch1a
ric4h
ri1d2
ri2d2an
2ri2d2ol
r2ie
rieb4s3t
rie1b
rie4b2s
rie2f2r2
rie1f
ri1el
ri2e1ne4
r2i1en
rie2n1u
ri1er.
ri4e1re
ri3e4sti
rie3s2
rie1st
ri1eu
ri2f1a
r2if
ri2f1e2i
ri1fe
ri2fer
ri2f1o
ri2f3r2
rif4ter
ri4f1t
3r2i1g
5rig.
ri4ge1ne
ri1ge
ri3gen
5ri2g1j
ri2g1l
4rig2r4
ri2k3l2
r2i1k
ri4kla
r2i2m1b2
2ri2m1p
ri2m2s
r2i3na
2r1i2n1d
r1in4dex
rin1de
rin4d2i1z2
rin1di
r2i3n4e
ri2n3e1i
2r1i2n3f
rin2f2o
rin2g3l
ri4n1g
rin2g2r4
2r1i2n1h2
2ri2nit
ri1ni
2ri2n1k
3ri4n1n
6r5inne2n1m4
rin1ne
rin3nen
4r3inner
4r1innta
rin2n1t
r1innu
2r1i6n1s
3r4ins.
rin2so
rin2sp4
r4in1s2pi
2ri4n1t
r1in4te3g6
rin4t5r4
2r1i2n1v2
4r1ir
r2i1s
ri1s4a
ri4scho
ri1sc
risc4h
ri4s2c2hw
3ris2i1k
ri1si
ris1mu2
ri4s3m2
ri3so
r2i4s1p2
3ri2s1s
ris3si
ris2t5e4r1s
ris2ter
ri1st
ri3st2e
riste2s4
ri6ste2s1s2
ri2ß1
r2it
r3i2tal
ri1ta
ri3t2i
ri1t4r4
rit2t2r4
ri4t1t
5ri1tu
r2i1x1
1rí
2r1j
2r1k
rk2am
r1ka
r2k1äh
r1kä
r3kla2u
rkl2
r2kli1s
rk1li
rk4lo
rk2lu
rk4n2
r2k5nu
rk3rä2u
rk2r4
r2k3r2e1a
rk1re
r3k1ri
rk3rin
rk2s1e
r2k1s
rk3sen
rk2sp2
rkst4a3ti6
rk1st4
rk3st2a1t4
rk4stec
rk3st2e
rk4s1ti
rk2ta
r4k1t
rk4t3e4n3g
rk4t3e2r1f
rkt3e4r1s
rk6ter1sc
rk4t3e2r1w
rk2tin
rk2t1o2
rk2t3r4
rk3tra
rk1u1h3
rk2um
rku2n1
rk1u1ni
rku2s3s
rk2us
rku4s1t
4r1l
r3l2a
r1l2e
rl2e2a
r3lec
rle2i
rle2st
rle1s
r3le1t
r3l2i
r3l2o
r1l2ö
rlö3s5s
rlö1s
rl2s1p2
r4l1s
rl2s1to
rl1st
r4l3t
rlu4st2r4
rlu1s2t
rl2us
4r1m
r3m2a1g
r1ma
rma2la
r2m1a4l3d
r2m1a2n1z
rm1a2p
r2ma1ph
rma5ssen
r3mas
rmas1s2e
rma2s1s
rmas8s2e6n1s
rm2är
r3mä
r2m3d2
r3me.
r1me
r2m1e1f
r2m2e1o
r3m2e1s
r2mi1de
r1mi
rmi1d
r2m1im
r2m1o2ri
r1mo
rm3sa
r2m1s
rm1s2t
rm3sta
rmt2a
r4m1t
r1m2u
rm3u2m1s
4rn
r2n1a2b
r1na
rna4n
rn2a2n1d2
r2n3a3ni
r2n1a2n1z
rn2a2r
rn3a1re
r3n3a1ri
r2n1a2u
r3näp
r1nä
rn3d4r4
r2n1d
r3ne
rn3e4b2en
rne1b
r4n1e1f
r2n2ei
r4n3e2if
r4n3ei1s
r3ne2n
r4n1e1ne
rn3en1s4e
rn2e6n1s
r4n1e2r1f
r4n1e4r1g
rn4erhi
rn1e2r3h4
r4n1e2r1k
r4n1e4r1t
r5n2e1s
rn2e1t
r4n1ex
r2n3f
r4n1g2
r3ni
r4n1in1
r3no2d
r1no
r2n1op
r2n1or
rn1ö
r1n2ö1t
rn3s2ä
r6n1s
rn3s2p4
rn3s2z
rn3t2e
r2n1t
r1nu
r2n1ur
r1nü
r1ny
ro2bei
ro1b
rob2e
2r1o4b1j
1r2o3bo
2ro4b1s2
ro1c4h
r2oc
3rock.
ro2c4k
r2o3de
ro1d
r2o3e4
ro4h1l
roh3na
ro2hn
3r2o2hr
3r2oi
ro3le
r2ol
rol4la2n
ro4l1l
rol3l4en
rol1le
2r3o1ly
4rom.
r2om
ro2ma1d
ro1ma
ro2mer
ro1me
4ro2m1m
4ro4m1t
r2on
ro4n1e2r1b
r2o1ne
3ro4n1n
ro6n1s2
ron4tan
ron3t2a
ro2n1t
4r1o1ny
ro1p2e
2ro2pf
r4o3ph2
r1or
r2or1a
r2or3al
ro2r2a1t
ro2rei
r4o1re
r2o2r1o
ror3t1h
ro4r1t
r2o3s1h
ro1s
r2o3s2i
ro5s1mo
ro4s3m2
ros6s1an
ro4s2sa
ro2s1s
ro6s1s1c
ro1s4t
ro3sta
ro2st2r4
ro2ßu2
ro4t2a1g
r2o1t
ro3t2e3i
ro2t2ho
r4ot2h
ro2t1ri
rot2r4
ro2t1s2
rot2ta
ro4t1t
ro3t2u
ro3u2n1t
ro2u
roun1
3ro2u1t
rö2b3l2
rö1b
rö2du
rö1d
2r1öf
4rög
1r2öh
r1ök
1r2öl
3rö1mi
4röp
r1ör
r2ös.
rö1s
r2ö1se
2r1p2
r3p4a
rp4e
rpe2re
rpe4r3in
rp2e1ri
rpf4
r2p1li
r1p2l4
r3p2o
rpo4st2r4
rpo1s4t
rpo1s
rp1s1t
r2p1s
r2p3t
r3pu
r1q
2r1r
rr2a1b
rr2ar
rra4s3s
rr2a3s2
r2r1äm
r2r1b2
r2r1c
r3r2e
rre4a1le
rr2e1a
rre4r4s
r4r1e1w
rr2he
rr1h4
rr2i1k2
r1ri
rr2n3a
r4rn
rr2o
r2r3o1b
rr2o3m2
rr2t1h
r4r1t
r3ru
r3r2ü
r2r1ü1b
4r1s
r2s3a1b
r1sa
r2s1a2d
r4s1a2m1p
r3sam
r4s1a4m1t
rs2an
r2s3a4n1g
r2s3a2n3p4
r2s3ar
r3sch2e
r1sc
rsc4h
r6sche4r1l
rs2c4h2l2
rs1ebe
r1se
rse1b
r2s1ein
rse2n1
rs2e2n1d
rse4ne
rs1e1re
rs1er1ö
4r1s1e4r1s
rs1e2r1z
rs1eta
rse1t
r3sho
r2s1h
rs2kal
r2s1k2
rs1ka
rs2kan
rs2kie
rs2ki1s
rs2k4l2
r4s1ko
r4sk2r4
r4sku
r2s3l2
rs4no
r2s3n4
r2s1op
r4s3ort.
rso4r1t
rs2p4
rspa3s2
rsp2a
r2s3ph
r4s3s2
r5s2ta1d
r1st
r4sta2n1t
rs2ta2u
r6st5ei4n3g2
r3st2e
rste2i
rs2t1ein
r6s4ter4b1t
rste2r1b
r4st3e2r4w
r4s2t1h
rst3i4n1g
r2s1tip
r2s1t2o1t
rs2t2r4
rst3ran
r6stra4n1g
rs2t2u
rsü3s4
r3swi
r2s1w
4r1t
r4t1a4b1s
rta1b
r2t1a2l1m
rta4l1s1
r2t1am
r2t1a4n1g
rt2a4n1n
rt1a2n1t
rt1a2n1z
r2t1ar
rt3a4re
r2t3a4t1t
rt2a1t
rt1är
r3te.
r1t4e1e2
rt4e2if
rtei3la
r1te4il
r2te2l1f
rte2n1
r3t6en.
rt3erei
rte1re
r4ter1fa
rte2r1f
r4ter1fo
r4t3e2r3h4
r2t1e2r1k
r4t3er4l2a
rte4r1l
r4t3er1l2e
r4t3er1nä
rte4rn
rter4r2e
rte2r1r
rt1e4r1s
r3te2s2
rte3s1k2
r2thi
rt1h
rt3h2ol
rt2ho
rt2hum
r2t1i1d
r2t1i1ma
r2t1i2n3f
rto1p
rt1or
rto2ri
r2t3rak
rt2r4
rtr2a4s3
r2t3rec
rt1re
r4t3rei1s
rt3ro1s
r4ts
rt4s3eh
rt1se
rt1sp2e
rtsp2
r4t1t4
4r2t1u4r1t2
r4t3z
1ru
r2u1a
ru3a2r3
rube2
r2u1b
rud2e2a
r2u1d
ru1de
ru2d2r4
3ruf
ru2fa
ru4f2s1
4r2u1g
2r1u2hr
ru1h
3ruin
r2ui
ru1i6n1s
ru1i1s
2rum
4r1u2m1f4
ru2mi
4r1u2m3l2
r2ums.
ru2m1s
4r1u2m1z
2r1u1na
run1
2ru2n1d
r1un2d1a
r2un1de
rund3er
run6de4r1l
run6de4r1s
run6de2r1w
2r1u2n1f
2rungl
ru4n1g
2r1u2ni
4r3un2i1o
run2k2r4
r2u2n1k
2r1u2n3l2
2r1u2n1m4
4ru4n1n
4r3u2n1t
2r1u4n1w
ru3p2r2
4r3ur
ru2ra
ru2r1e
5r4u1ro
ru2si
r2us
rus3sen
ru2s1s
rus1se
ru4s2s1p2
ru4s6s3t
3r2u1t
ru2tei
rut3h
ru2t1o2
ru2t3r4
4r2u1z
ru2zw2
1rü
2r1üb
rü1ben
rübe2
rü1c4h
r2üc
rüc2k1s2
rü2c4k
4rü2m1m
rü3s2s
rüs3si
2r1v
rve4n1e
r1ve2
rve5s
r2v2s
2r1w
rwu6n3s2
rw2u
rwun1
4r1x
1ry
ry2c2
2r1z
rz1ac
rza2
rz2an
r2zar
r2z1as
r5ze1ne
rz1e4n1g
r4z3en4t3s
r3ze4n1t
r2z1e2r1f
r2z1e4r1g
r2z1e2r1k2
r2z1e2r1w
rz1i1d
r3z2of
rz1op
rz2ö
rz3te
r2z1t
rz2t1h
rz2t3ro
rzt2r4
rzu1g2u
rz2u1g
r3zw2ä
rzw2
r3z2wec
1sa
3sa.
3s2aa
2s1a1b
sa2be
3s2a3be1t
sa2b1l2
s2a3b2le
sa2b3r4
4sa4b1s
sa2cho2
sac4h
sa4c4h3t
2s1ada
sa1d
s1a2d3m2
2s1a2d2r4
s2a2fe
2s3a2f1f
3s2a1fi
sa1f4r2
3sa1g
sa4ge4n1t
sa1ge
sa3gen
sag4n
4s1a2g2r4
3s2ai
sa3i2k1
sail2
2s1ak
sa2ka
3s2a1ki
3s2a1k2r4
4s3a4k1t
3sal.
sa4l3e2r1b4
s2a1le
sa2l1i1d
s2a1li
s1a4l1l
3s2a1lo
sal2se
sa4l1s
2s1a4l1t
3s2a2l1z
3sam
s3a2m2e1ri
sa1m4e
5sa2m1m
6s1am1ma
4s1a2m3n2
s1a2m1p
sam2to
sa4m1t
s1an
s2an.
2s3a2na
2s3a2n3b4
s2a2n2c
s2a2n1d
s4and.
san4d3ri
sand2r4
3s4ang.
sa4n1g
san4g4s
2s3a2n1h2
3s4a3ni
2s3a2n3l2
2sa2n3p4
2s3a6n1s
s2an4s1k2
4s3ant2r4
sa2n1t
2s3a4n1w
s3a2n1z
2s1a1p
sa2p2o
3s2ap2r2
2s1ar
3s4ar.
3s2a1ra
4s3a2r1b
3s2a2r1d
3s2a1ri
s3a2r1r
3s2a4r1s
4sart2i
sa4r1t
s1a2s1p2
sas6se1st
sas1s2e
sa2s1s
sass2e1s
4s3a2s2y
3s2a1t
sat2a
4s3at2h
4s3a2t3l2
4s3a2t1m2
s4a2t2r4
sa3ts
sat4z3e4n1
sa4t1z
s1a4u
3s2au.
3s4a2uc
3s2a2u1e
2s3au2f1b2
sau2g2r4
s2a2u1g
3s4aum
3s2au3r2
sau1ri1
2s3au4s3b4
sa2us
3s2au1se
2s3a1v
sa2vo
3säc
s3ä2hn
3säl
s1ä4l1t
2s1äm
2s1ä2n1d
2s1är
sä3s3
3s2ät
1säu
2s1ä2uß
4s3b4
sba4n
sbe3r2e
sb2us3
1sc
2sc.
2scam
s1ca
s2ca1p
4s3car
2s1ce
6s4ch.
sc4h
3schaf
2s2ch1ak
s2ch2al
4s3cha2n1c
4sch1a4n1g
5s2cha2n1z
4s1ch2ao
s2cha2u
3s2chä
2s2c2h3b2
2s6c2h1c
2s2c2h3d4
3sche.
sch3ei.
s4chei
4sc4h1e2m1p
sche1m
sch2en
3sche1s
4sch1e2s1s
4s2ch1e4x
4s2c2h3f4
2s2c2h3g2
2s2c2h1h2
schi4e
s4chim
3schi4n1g
schi2n
4s1chi1ru
sch2i2r
3schi1s
2s2c2h1k4
4sch3le.
s2c4hl2
sch2le
6sch3lein
sch6lit
sch1li
2sch3mö
s4c2h2m
2schn.
s2c2hn4
2sch1o2x
3s2c1h2ö
4schöl
4s2c2h3p2
2sch1q
4sch3re.
sc2h2r4
s2ch1re
4sch3rin
sch2ri
sch3r2om
4sch3ro2u
6s4c4h1s2
sch3s1k2
6s2c4h3t
sch2t2a
scht4r4
s4chu
4s2ch1u2n1t
schu2n1
sch2up
5s2ch2ü
2s2c2h1v
4sch1we1t
s2c2hw
sch4wil
2s2ch1z
2sc1j
6s1c4l2
2s1co
3s2cop
3sco4r
s2c4r2
2s2c1s2
2s3cu
4s3d2
sda3m4e
sdi2e1n4e
s1di
sdi2e
sd2i1en
sd4r4
1se
se3at.
s2e1a
se2a1t
sea2u4
2s1e2b2en
se1b
s2eb4r4
2s1echo
sec4h
s1e2c4h1t
2s1e2c4k
3s4ee
se1ec
se2e1i4
see3i1g
seein2
se1er.
se1e1r1ö
2s1e2f1f
se1f
3se1g
se2gal
se2gl
seg4r4
3s2eh
s2e2h1a4
se3he
se4h1ei
se4h2el
se4he2r1k
se3her
se2hi2n
se1hi
se4h1l
seh3re
se2hr
se4h3s2
se4h3t
se2h3üb
se1h2ü
2s1ei.
2s1ei1e
2s1e2i1g
s1ein
5s2ein.
2sei2n3b4
s2ein4du
sei2n1d
se2i3n2e
s2ein4f2o
sei2n3f
2sei4n3g2
2sei2n1h2
4sei2n3k
2sei2n3l2
2sei4n1n
2se2i2n3r2
s4eins.
sei6n1s
4seinsp4
4sein1st
2sei4n1w
4s1ei1s
5s2e2it
3s2e1k
s2el.
se2l1a
se3la1d
sela4g
se3l1a2m
3se2l1b
se2l1ec
se1le
2s1e2le1m
se4l3e4r1l
sel3e4r1s
2s1elf.
se2l1f
s3el2i1x
s2e1li
se2l3ö
s2e4l1s
sel3s1z
sel3t3r4
se4l1t
s4e3ma
se1m
2s1e2m1p
s2en.
se4n2a1g
se2n1a
se2nä
3sende1t
se2n1d
sen1de
4s1en4d1l2
sen3gl
se4n1g
5s2e1ni
3senku
se2n1k
se2no
se4n1o2b
s2e6n1s
s2ent.
se4n1t
2s1en2t1f4
4s3en2t1g2
s2enti
2s1en4t1s
2s1en4t3w
2s1en4t3z2
se2n3u
3senva
se2n1v2
se1o2r
s2eo
4s1e2po1s
se1p
se1p2o
3se1q
s4er.
se2r3a2d
ser1a
se2r3al
se5re1f
se1re
s3e2r1e2i1g
se4r3eim
se4r3e2n1k
s2eren
ser2er
s1er1fo
se2r1f
s2erf4r2
s3er1fü
4serf2ül
se4r3g
s1ergä
s2erg2r4
s1e2r3h4
5s4e3r2ie
s2e1ri
s3erken
se2r1k
s1erkl2
3serl.
se4r1l
s1er1nä
se4rn
2s3er2n1t
se1r2o1t
s3e2r1öf
ser1ö
s2ers.
se4r1s
2ser1s2a
s4ert.
se4r1t
s2e1ru2
se4r1uf
se3r1u4m
se3r1u2n1d
serun1
3s4e2r1v
se2sel
se1s
se1se
2s4e2s1h
se3su
2s1e4ta1p
se1t
se2t4a1t
s1e2t2h
3s2e4t1z
s2e1u2n1
2s1ex
se2xe
4s3e4x1p
se4x3t
6s3f4
sfal6l5er
s1fa
sf2al
sfa4l1l
sfal1le
sf1lo4
sf2l2
4s3g2
sge1s2
s1ge
sgro3
sg2r4
2s1h
4sh.
sh2a
3s2ha.
sha2k
4s3han
4s2h1c
s3h2e
3s2hi.
3s2hi3d
sh2i4r
4s2h1k4
s2h3n
4shof
3s2hop
sh4o4re
3s2h2o2w1
s2h4r
4s4h1s
4s4h1t
4s3h2ü
1si
si2ac4h
si1a
s2iac
si3a4ch.
si2a1d
s2ia4s
2si2a1t
s2i1b4
5s2i1c
2s1i2d2eo
si1d
si1de
s2ido
3s4ie
siege4s
sie1g
sie3ge
si3e1ne
s2i1en
si1e2r1r
s2i1f4
si2g1a
s2i1g
3si2g1h
sig4n
si3gnu
si2g3r4
si2k1a1b
s2i1k
si1ka
si2k1ä
si2k3e4r2l
s4ike
si2ki
si4k3l2
si2k2r4
si2k3s2
si4k3t4
si2ku
3s2i1lo
2s1i2m1m
si3n4a
2s1i2n1d
2s1i2n3f
s2ing1a
si4n1g
sin3g1h
sin3g4l
sin2g2r4
sing3sa
sin4g2s
2s1i2n1h2
si1n1i1
2s1i2n1q
2s1i6n1s
2s1i4n1t
2s1i2n1v2
3s2i1o
3s2i1s
si2sa
si4s4chu
si1sc
sisc4h
s2i2s1e
si2s1o
s2i2s1p2
si2s3s2
si2st2u
si1st
3s2it
si2t1a2u
si1ta
si1t3r4
si2tra
si3tu
3s2i1v
siv1a
si1ve3
si2v2r
1sí
4s3j
2s1k2
4sk.
1ska1la
s1ka
4skam
4ska2n1z
4skas
sk4a4te.
sk2a1t
skat2e
4skate1g2
sk4a4te2s
4s2k3b4
skel1m2a4
ske2l1m
skel3mas5
s2ke1p
3s2ki.
s2k2if
s2k2i1g
3s2k2i3k4
4s5kir
3s2k2i1z
sk4l2
4s3klas
sk4n2
4s3k2om
s1ko
4s3kor
4s3k2o1w
4s1kö
4s2k1s
4s4k3t
3s2ku2l1p
sku1l
sk2us3
2s1l2
4sl.
3s2l1al
4slan
sla2ve2
sl2a1v
s2l2a1w
s2l3b
s5le
s3li
3s4lip
4s4ln
s3lo.
s1lo
slo3b2e
slo1b
s3l2o1e
s3lu
4s3m2
2s3n4
4s1na
s2n1a1b4
sni3er.
s1ni
sn4ie
sni3e4r1s
4s5n2o1t
s1no
4snö
3so.
s2o4a2
2s1o2b
so3e1t
s2o1e
3s2o4f1t
3s2o1g
s1o2he
6s3oh4n1g
so2hn
2s1o2hr
1s2ol
so3la
so4l1ei
so1le
sol4ler
so4l1l
sol1le
2s3o2ly
3s2om
3s2on
son3a2u
s2o1na
s2o1ne2
son5en1de
so3ne2n1
sone2n1d
son3sä
so6n1s
son2s1o
so3o
2s1opf
3s2or.
s2o1r2al
sor1a
s1o2r1c
2s1o2r1d
so2rei
s4o1re
2s1or3g4a
so4r1g
5s2or1ge
2s1o2r2ie
so1ri
s2o2r1o2
3s2o4r1s2
so4ru
3so3s2
s4os.
4s1o1s2t
1so2u
so3u2n1t
soun1
3so1v
4s1o2ve2
3s2o1w
2s1o1x
5s2o1z
s1ö2f
2s1ök
2s1ö2l
s1ö4s
sp2
2sp.
2s1p2aa
sp2a
2s1pak
2s3pa1la
spani7er.
sp2a3ni
span4ie
2s1pa2no
4s1pa1p
2s3pa1ra
1sp2a1re
2s1p2a1ro
3sp2a1ru
spa3s1s2e
spa2s1s
spa3s3s2i
3s2paß
2s1pa2u
s2p2a1z
s2pä
2s3pär
s3pe.
sp2e
4s3pel
4s3pen1si
spe6n1s2
s1pe3p4
s1p2e1ri
4s1pe4r1l
2s3pero
s2pe2r1r
2s3pe4r1s
2s3pe1t
1s2pe1z
2s3pf
2sph2a
s1ph
s4phä
s3phe
1spi
3s2pi4e
4s3pier4
s3p2i2k
2s1pil
3sp2i2o
4s3pi4p
4s3pi1s
2s1p2l4
4s3p4la
4s3plä
3s2p1li
s3p4lu
s3p1n
2s3po1d
s1p2o
2sp2o1g
s2p2o2i
2s3p2o1k
4s3p4ol
1spon
1spor
4s3po1s
s2po4t1t
sp2o1t
4spr.
sp2r2
s2prac
s2pran
2s1pr2a1x
2s3p2räm
sp4rä
4s3prä3s
3s4prec
sp1re
2s1pre1d
2s3p4re1s
2s3pro1b
s1p4ro1
5s2pro2s3s
spro1s
3sp1ru
2s1prüf
sp1rü
3s2prün
2s3p1s
2s4p1t
2spun1
s1pu
2spup
3s2pur
4s3p2u1t
1spü
4s3py1
2s1q
4s3r4
sra2t2s
s1r2a1t
srat4sc
sre1t3
s1re
sr2om2
srö2s
srö1s1c
srü4ck1er6
s1rü
sr2üc
srü2c4k
srü2d
2s1s
6ss.
4s1sa
ss2a3bo
s2s1a1b
ss1a2c4k
ss1a1j
s3sal
s4s1a1la
s4s1a2l1b
s4s3a4m1t
s3sam
s5s2a2n1d
ss1an
s4s3a4n1g
s2sa1no
s4s3a6n1s
ss2a2n1t
s4s3a2n1z
s3sas
ss3a4t1t
s3s2a1t
4s3s2ä
4s4s3b4
6s1sc
4s4s3d2
4ss1ec
s1se
4s3s4ee
4s3se1g
s4s1ega
4s3s2eh
ss2e3h1a4
4ssei
sse3i2n3f
ss1ein
sse3i4n4t
4s3s2e1k
6s3sende1t
sse2n1d
ssen1de
4s3sen1du
ssen3mas6
sse2n1m4
ssen1ma
sse6r5a4t1t
sser1a
sse1r2a1t
s2s1er1ö
4s1s3er3se
sse4r1s
s3sersu
ss2e1s
4sse3sc
3s2s4e2s1h
4sse1t
sse3ta
4sse1z
4s6s3f4
4s4s3g2
4s2s1h
4s5s2i1c
s1si
4s3s4ie
s2s2i1g
s4s1i2n3f
s4sin1st
s2s1i6n1s
s4s1i4n1t
4s3s2i1o
4s3s2it
4s2s1k2
s3ska1la
ss1ka
4s4s3l2
4s4s3m2
4s2s3n4
4sso
ss1o2f1f
ss2oi4
s2s1op
ss1o1ri
4ssp2
s3sp2e
ss2p2o
ssque1t4
s2s1q
ssqu4
ssq2u1e
4s4s3r4
4s2s3s2
4s1st
sst2a
s5s2ta1d
ss2tar
s3stä
s3s1t2e
s4ste.
s5s2tel
s4sten
s4ste2s
s4ste1t
s5s2teu
s4s2t1h
ss2tip
ss1t2i1s
ss2top
ss2tur
sst2u
s3s2tü
4ssum
ss1u2m1s
4ssü
4s4s3v
4s2s1w
4s3s2y
4s2s1z
1st
6st.
3st2aa
2s2t1a4b1b
sta1b
2s2t1a2b5h2
s2t2a1bi
2s2t3a4b1t
2s2t1a2b3z2
s2t2ac
3s2ta1d
4st1ada
4s2t1a2d2r4
3s2ta2f1f
2s3t2a1g
3stah2
2s1tak
2s3t2al.
2s1t4a1le
3st2a3li
2sta2l1k
st1a2l1m
st1a2l1p
3s2tam
st1a2mi
4st1a4m1t
st1a4na
3st2a2n1d
4s4t1a2n1f
4s2t1a2n3l2
4st2a4n1n
2st1a4n1w
4st1anza2
sta2n1z
s2t2ar.
s2ta4r1s
3s2ta4r1t
st1a1si
3st2a1t
3s4t2au.
sta2u
2s2t1auf
2s3t4aum
5st2au3r2
2s1ta2us
3staus.
2s1ta1x
3stä1b
3s2tä1d
2s1tä1g
2s2t1ä4l1t
2st1ä4m1t
s2täm
s2tär
3s2tä4t1t
st2ät
2s1tä2u1s
4s4t3b2
4s2t3c
4s2t3d4
3st2e
s2te3an
s1t2e2a4
4s3te2c2h3n4
s1t2ec4h
4s1t4ee
ste2g2r4
ste1g2
ste2i
5s2te2i1g
4s3te4il
stei4n1a
s2t1ein
s2tel
s3t1e2le1m
ste3le
stel4l3ä
ste4l1l
ste4mar
ste1m
st2e1ma
4st3em2pf
ste2m1p
4st3en2d1s
ste2n1d
st3en2gl
ste4n3g
st4e6n1s
4st3en2t1f4
ste4n1t
4s4t3en4t3w
4st3e2pi
ste1p
ste6r1e4r1s
ste1re
s2te4r1l
s2te4rn
6s3terr2a3s2
ste2r1r
s2te4r1s
4st3e1se
ste2s
4s1te4s2t3s4
1st2e1s2t
s2teu
4s3teuf
4st3e1v
4s1tex
4s2t1f4
2s2t1g2
4st1h
st3ho
3st2i2e
4s1ti2ef.
stie1f
stier1ma5
sti2er
stie4r1m
3st2if
3stim
2st1i2n3b4
2st1i2n3f
2st1i6n1s
s4t2i1o
sti2r
st3i2so
st2i1s
2s2t1j
2s2t3k4
4s4t3l2
4s2t1m2
st3ma3s2
st1ma
2s2t5n4
3st2oc
s1to3d
s2to1de2
2st3om
2s1t2o3p2o
2st1o2r1d
2st1o4r1g
3sto3s2
4s3t4o2u
2s1tö1c4h
st2ö2c
5s2tör
2s1t2ö1t
4s4t3p2
2s2t1q
3s2tr2af
st2r4
2s1tra1g
3strah
4s3t4r2ai
3s2tr2al
4s1tra6n1s
3s2tr2a3s4
3s2t2raß
4s1t3r4aum
stra2u
s2t4räf
s1trä
2s3t4rä1g
s2trän
4s3tr2ä1ne
2s4t5re.
st1re
4st3r2ec4h
s2trec
4s2t3re1d
4s1tre1f
4s2t3re1g
3s2t4re2if
4st3rei1s
st3re4n1n
2s1tre1p
2s1t4re1t
2st3r2e1v
2stri.
st1ri
3s4tri1a
2s1tr2i1b
4st3r2i1g
str2i2k
4stri1si
str2i1s
2s1tr2oc
3s2trof
3s2tr2o1k
st3ro4l1l
str2ol
stro4ma
str2om
s2tro1s
s2trö
3s2truk
st1ru
s2t2rum2
4st3run1
2s3t4rup
4s2t3s4
sts1as2
st1sa
2s4t3t4
st2u
3s2t2u1b
4s1t2uc
3st2u1d
2s1t2u1e
3s2tuf
3s2tu1h
2stuk
2st3u2m3r2
s1tum
stu2m2s
2st3u2m1z
stu2n1
2s1t2un.
2s2t3u2n1f
2st3u1ni
2s1tu6n1s2
2s2t3u2n1t
3stuö
stu3re
st3u4r1l
2s3tu4rn
2st3u4r1t2
4s1tüc4h
st2üc
s2tü2c4k
2s1tür.
2s1tü1re
2s1tü4r1g
2s1tü4r1s
2s2t3v
2s4t3w
2s3ty.
s1ty
2s3ty1s
4s4t3z
1su.
su1an
s2ua
3s2u2b3
su4ba2
4su3b4i
5s2u1c
su2ch1a
suc4h
such4st4
su4c4h1s
2s1u2f
2s1u1h
su1i1s
s2ui
su1it.
sul2a
su1l
su1l2i
su4l1t2
su2mar
su1ma
su2ma2u
3s2u1me
su2m1el
su6m5en4t4s
sum2en
sume4n1t
s3um1fe
s1u2m1f4
3s4u2m1m
su1m1o2
su2mor
s3um1sa
su2m1s
s3um1st
su2n1
sun6de2r3h4
su2n1d
sun1de
su4ne
s1u2n1f
2s1u1ni
4s1u2n1t
3s2up
su2p3p
su2ra
2s1u4r1l
s1u4r1t2
s2u2s1
su3sa
su3s1h
su3si
su2s3s
2s1ü4b
3s2üc
sü2d1
süden2
sü1de
3sün
süs4
sü3s1s2e
sü2s1s
sü3s1si
4s3v
2s1w
s3we
s1weh2
4swi2e
4s1wil
s3wö
s3w2u
1s2y
sy1l1
sy4n3
sy5s
2s1z
4s3za2
4s3zei
s2ze2n1a
5s4ze1ne
4s3ze4n1t
s2ze2s
s2ze2ß1
s3ze1t
s2zi1s
sz2o
4s3zu
4s3zw2
2ß3a2
ß1ä
2ß1b2
ßb2us3
2ß1c
2ß1d4
ßdi2e3
ß1di
1ße
2ß1ec
2ß1e2g
2ß1ei
ße2l1a
ße2le
ße4n3g
ße2ni
ße2no
ß2ers.
ße4r1s
2ßer3se
ße4r3t
ße2s
ße2t
ß1ex
2ß1f
2ß3g2
ßg2e2bl2
ß1ge
ßge1b
2ß1h2
1ßi
ßi2g1a
ß2i1g
2ß1in
ß1j
2ß1k4
2ß1l2
2ß1m
2ß1n2
ß1o2
ß1ö
2ß1p2
2ß1q
ßque1t2
ßqu4
ßq2u1e
4ß3r2
ßr2us3
ß1ru
2ß3s2
2ß1t
ß2t1h
ß2ts2
1ßu2
ß1uf
2ß1um
ß1u1ni
ßun1
ß1ü
2ß1v
2ß1w
2ß1z
2tab.
ta1b
ta2b1an
t2aba
2t1a4b1b
1t2a3bel
2taben
ta4be2n1d
2t1a2b5f4
2t1a2b1g2
2t1a2b5h2
2t1a2b1k4
1t2ab2le
tab1l2
2t3a4b3n2
ta2b3r4
4ta4b1s
2t3a4b1t
t2a2bü
2t1a2b1w
2t1a2b3z2
2t1ac
3t2a3cu
t1ada
ta1d
t4a3di3
2t1a2d2r4
t2a3d2s2
1t2a1f2e
2ta2f1f
t1a4f3g2
t1af4r2
3t2a1g
t2a2ga2
ta2g1e1i
ta1ge
4t3a4ge4n1t
ta3gen
t2a3gl
t3ago
ta4g2s
tag4st
tah2
tah3le
ta4hl
tahl3s1k2
tah4l1s
t2ai
ta3i2k
tai2l
ta1i6n1s
tai4r
ta1ir.
1tak
t3a2ka
t3a2kro3
t2a1k2r4
t2ak2ta
ta4k1t
3t2ak4t3b2
3t2aktu
2t1a2k3z2
3t2al.
ta2la
ta3la2g
ta3lak
t1alb.
ta2l1b
t1al2b1k4
1talb2u
ta4l3d
1t4a1le
ta4le6n1s
talen1
tal4le1g
ta4l1l
tal1le
tal2lö
ta2l1op
t2a1lo
tal2se
ta4l1s
2ta4l1t
2tam
ta2mer
ta1m4e
ta2mi
t1am1p2l4
ta2m1p
t1a4m1t
t1a2na
2t1a2n3b4
t2a2n1d
t2a3ne
4t1a2n1f
2ta4n1g
t2a2n1k
t3an3kl2
2t1a2n3l2
2t1an1me
ta2n1m4
4t3an3na
t2a4n1n
t1a6n1s
t2ans.
4t3an1si
2t3ansp4
t2a2nu
2tanw2a
t1a4n1w
2tanw2ä
t2anz.
ta2n1z
t1anza2
4t1anzei
tan6ze2r3h4
t1anzu
ta3or
t2ao
ta2pe.
ta1p
t2ap2e
ta2pe1s
2ta2pf
t2a2p3l4
2t1a2r1b
ta4re6n1s
t2a1re
ta3ren
ta4r3e1re
3t4a3ri
2ta2r1k
2t1a4r1m
2ta4r1t
t1art2i
t2ar2to
t2a2ru
2t1a2r1z
ta3sa
1ta1sc
t1as1p2
1ta1s2t
1tat.
t2a1t
ta2t1a2b
ta2tan
ta2t1a2u
tat3ei
tat2e
ta2te1m
ta2t3er
ta2t2h
t4a1t3h2e1
t3a2t3l2
t4a2t1m2
ta2t2om
1ta2ts
ta2t1um
2t1auf
ta2u
4tau4f3g2
tau3f4li
tauf3l2
4t3au2f3n2
1t2a2u1g
t1auk
3t4aum
1ta2us
t1au4s3b4
tau6sc2h2r4
t2au1sc
tausc4h
tau6s2c2hw
t2au1se
4t3au4s3g2
t1au2s1k2
2t1au2s1l2
4t3au2s1s2
4t1au2s1w
1ta1x
tax2i3s
tä1c
2tä1d
3t2ä1e
1tä1g
2t1ä2gy
2täh
2t1ä4l1t
2täm
t1ä4m1t
t1än4g2s
tä4n1g
1tä2n1z
t1äp
t2är.
tä2ru
tä4s
t2ät
2tä4t1t
1tä2u1s
2t1ä2uß
2t1äx
1tà
4t3b2
tbe3r2e
tblock5e
tbl2
tb2lo
tb4l2oc
tblo2c4k
tblocken8
tb2us3
2t1c
t3cha
tc4h
t3che
tch2i
t2c4h3l2
t2chu
t2c2h1w
t4c4k
t3c4l2
t3c4r2
2t3d4
tdun2
t1du
1t2e2a4
te3al
te3an
3t4e4b1b
te1b
4t1e2b2en
1t2ec4h
te1cha
3te2c2h3n4
2te2c4k
teck2e
1t4ee
te1e1m
te2en3
te1e2r1w
te2e1s2
2te2f1f
te1f
te1g2
teg3re
teg2r4
2teh
t3eif3r2
te2if
te2i1k4
1te4il
2t1ein
teinb2u3s6
tei2n3b4
tei2n3ec
te2i1ne
t3ein1ge
tei4n3g2
t3einla
tei2n3l2
t3e2is.
tei1s
t3e2i4s3b4
tei3st
t2e4k3t2
te1k
te1la4
te2l3a1b
te2l1ac
te2l1a2u
te2l1b4
te3le
tel1e1b
tele4be
te4l1ec
3tele1f
3tele1g
t2e4l1eh
te4l1ein
t2elei
2t1e2le1m
tel1en
te4l1e2r1d
te4le2u
4t3elf.
te2l1f
te2l1in
t2e1li
te2lit
tel1l2e
te4l1l
te4lo1s2t
t2e1lo
telo1s
te2l1ö
tel3s2k2
te4l1s
tel3ta
te4l1t
tel3t1h
tel3t4r4
te2m1ei
te1m
te1me
te2min
te1mi
2te1mo
te2m1o2r
3temper
te2m1p
temp2e
1tem1p2o
te4m1u
t6en.
te2n3a
te2n1a2b
te4na2d
te4n2a4g
te4nas
te4n1a2u
te2nä
t4e2n3b4
ten3da
te2n1d
4t3en2d1f4
t6en1di
2t1en4d1l2
t6endo
4t3en2d3p2
ten3d4r4
te2n1e2b
te1ne
te2n1e1f
te3n4ei.
te2n1ei
ten3e2i1d
ten3e6n1s
t2e3nen
4t1e2ne4r1g
te2ne1t
te4n3g
t1eng.
tengescho1s8
teng6e1s
ten1ge
tenge3s2c
tengesc4h2
tengescho7s1se
tengescho2s1s
ten4gl2a
ten2gl
t4e2n1h2
te2ni
te4n3in1
t4en1j
t2e2n3l2
t4e2n1m4
te4n3n
t2e1no
ten1s2e
te6n1s
4t1ense1m
t4enta
te4n1t
t3en4t3b2
4t1en2t3d4
t4ente
ten4t3ri
tent2r4
4t3en4t3w
t3en4t3z2
ten6ze2r3h4
te2n1z
ten3zw2
t3e2pi
te1p
t4er.
ter1a2b
ter1a
te2r2a1d
te1r2af
ter3am
te3r2an.
4ter4b2s
te2r1b
4ter4b1t
t3erde.
ter3de
te2r1d
te2r1e2b
te1re
te4r3e2if
te2re2l
ter3e2n1d
t2eren
te4re4n1g
te4r1e2r1k
te2r1e2r4z
4terf2ol
te2r1f
ter1fo
t4erf4r2
4terf2ül
ter1fü
te4r3g2
tergescho1s8
ter1ge
terge3s2c
terge1s
tergesc4h2
tergescho7s1se
tergescho2s1s
6tergrei
terg2r4
terg1re
t6erg1ru
2t1er1gu
2tergü
t4e1ri
te3ri1a
4t1erklä
te2r1k
terkl2
2t1er1l2ö
te4r1l
1te4r1m
ter3ma3s4
ter1ma
ter4mer
ter1me
ter4n3a2r
te4rn
ter1na
4t3erne2u
ter3ne
t4ero
t3er1ö
3terr2a3s2
te2r1r
ter4re.
ter3r2e
1terr2o
t4ers.
te4r1s
t6erscha
ter1sc
tersc4h
ter4ser
ter3se
ter1st4
t4er6st.
t4ersti
t4ers2t2u
tert4a
te4r1t
t2e1ru2
te4r1uf
ter4wäh
te2r1w
terw2ä
6terwe2r1b
ter3za2
te2r1z
4t3er4z3b4
te2s
tes1ac
te1sa
te3ser
te1se
te3si
te3so
t2e3sp2
te4sp2r2
te2s1s2
3tesse.
tes1se
t2e1s2t
tes3t2ät
te4st3e2i
te3s2t2e
te6ste4r6g2
te6ste2r1k
te2ste2s4
1te4s2t3s4
t2et.
te1t
te2t4a1t
4te2t3l2
teu3e1ri
te2u1e
3teuf
t2e1un1
teu2r3a4
te2vi
t2e1v
1tex
te1x1a
t3e2xe
2t1e2xi
4t3e4x1p
3te4x1t
2t1ex1z
2t1f4
tfä4s3
t1fä
tfi2l
t1fi
2t1g2
tger2
t1ge
tgro3
tg2r4
t1h
4th.
2th2a
3t4ha.
t2ha1g
t3hai
t2hak
3t2h2al.
4t3ha2u
2t3hä
4t2h1c
1th2e
t2he.
3t2h2e1a
2the1b
t2hec
2t3hei
t4hein
t2he1k
t2he1m
t4he1ne
t4he1ni
3t4h2e2o
2the2r1r
t2he1s
3the1se
t2h2e2u
1t2hi.
t2h2i1k
2t3hil
2t3him
2t2h1k4
4t4h3l
4t2h3m
2t2h3n
t2ho
2t3h2o2c
t3hof
2t3hoh
t4hol.
th2ol
t3hor
2t3h2o1t
tho2u2
2t3h2ö
2t2h3p2
1t2h2r2
4thrin.
th2ri
th3rin
4th2r1i6n1s
2t4h1s
2th2u1b
4thu2n1
2th2ü
2t2h1v
t2hy
ti2a1d
ti1a
ti3a2m4
t2i1b4
ti1ce
t2ic
ti3c2h3r4
tic4h
tiden2
ti1d
ti1de
ti4de2n1d
ti2d2eo
t2ie
1ti2ef.
tie1f
ti1el
t2i3e4n1
ti2er
tie4rec
ti2e1re
tier3mas6
tie4r1m
tier1ma
1tie2r1r
tie5s1se
tie3s2
tie2s4s
2tieß
ti1e1t
ti1eu
1tif.
t2if
ti1f3r2
ti4g1e2r1z
t2i1g
ti1ge
tihi4
t2i1h
ti2kam
t2i1k
ti1ka
ti2kar
ti2k1in
ti2k3rä
tik2r4
ti2k1s2
ti2lar
ti1la
ti2l1a2u
ti2lei
ti1le
ti2l1el
1ti2l1g
til4le1b
ti4l1l
til1le
til4le1g
ti2lö
ti4l1t4
ti2lu2
ti2ma2g
ti1ma
t2i1mi
tim2m1a
ti2m1m
4t1i2m1p
t2in.
ti3na
t1i2n3b4
4t1i2n1d
t2i3n2e
t1i2n3f
t2in2g1a
ti4n1g
tin2g3l
tin4g3s2
t1i2n1it
ti1ni
2t1in1j
tin2k1l2
ti2n1k
t2ins.
ti6n1s
4t1in1se
2t1i4n1t
ti1n1u
4t1i2n1v2
3t2i1o
ti3or
1tip
3t2ip.
ti3p4l4
3ti2p1p
ti4que.
t2i1q
tiqu4
tiq2u1e
ti1r1h4
t2i1s
ti4scha
ti1sc
tisc4h
tis2c2h3w
ti2sei
t2i1se
t2i2sp2
3ti3te
tium2
t2i3u2
ti2van
t2i1v
ti1ve3
ti2vel
ti4v3e4r1l
tiver1
ti2v1o
ti2v3r
ti2za2
t2i1z
2t1j
2t3k4
2t3l2
t1l4e
3tle1m
tl2e2r3a
4t5li
2t1m2
tmal2
t1ma
tme4n4t3
t1me
tmo4de3s
t1mo
t3m2o1d
tmo1de
t3mu
2t5n4
t3n2e1s4
t1ne
to4a3s
t2o3a2
to5a4t
1to1b
2t1o4b1j
tob2l2
to1c4h
t2oc
3to2c4h1t2
2to2c4k
1to1d
3tod.
to1de2
to2d1er
tode4s1
t2o4d1u
toi4r
t2oi
to3la
t2ol
to1m1e2
t2om
to2men
2to4m1g2
1ton
to2n2a2u
t2o1na
to2neh
t2o1ne
3too
to2pak
to1p2a
to2p2a1t
1t2o3p2o
2t1o2p3t4
to1r1a
to2ra2u
to4rän
to3rä
2to2r1c
t1o2r1d
1to3re.
t4o1re
to2r1el
t1o4r1g
t3or3g4a
to2r3i4n1t
t4orin1
to1ri
t2o2rö
1to4r1t
t1ort.
to3ru
t2o2r1w
to3s2
to4s1k2
to1s2t4
1toten
t2o1t
to2t2ho
t4ot2h
3t4o2u
touil4
to2ui
to3un1
t2ö2c
1tö1c4h
2t1öf
2t1ök
tö4l
3t2ön
t1ö4s4t
tö1s
1t2ö1t
4t3p2
tpf4
2t1q
t2r4
2tr.
1trac
tra3ch1a
trac4h
t3r4ad.
t1r2a1d
tra4de1m
tra1de
tra4f3ar
tr2af
tra1f1a
1tra1g
2t3r2a2h1m
3t4r2ai
1tram
2t3r2a2m1s
3t4r2an.
2tra2n1d
1tra2n1k
t3r2a4n1n
1tra6n1s
t3r4a5s2e
tr2a3s2
t3ra5si
t2r4a4st2r4
tra1st
2t2raß
1t3r4aum
tra2u
tra2us2
1trä
3t4rä1g
2t2räh
3tr2ä1ne
2trä3s
2trä1ß
2trä2u2s
trä2u
2tr1ä2uß
4t5re.
t1re
tre4a1le
tr2e1a
2tre1b
tr2e2b2r4
2trec
t3r2ec4h
t4re2c4k
2t3re1d
3t4r4ee
1tre1f
2t3r2e1fe
3t4re2f1f
2t3r2e3f2o
2t3re1g
t4r2ei.
1t4r4e2i1b
2tre2if
t3re2i1g
2t3re2i1h
t3r1ein
t3rei1s
t3re2i1z
2t3re1k
2t3rel
t4re1m
t4r4en.
1tre2n1d
t3re4n1t
1tre1p
2t3rep2e
2t3re1p2o
t4rep2r2
t4rer
t4r4es.
tre1s
1t4re1t
t2r4e2t3r4
t4re2u
t3r2e1v
2t3re1z
3t4ré
2t3r1h4
1tr2i1b
t1ri
3trieb.
tr2ie
trie1b
3trie4b2s
tri2er
1trin
t3r1i2n1d
2tri4n1g
tri3ni
3tr2i1o
t4rip
t3ri2ß1
1tr2i3u2
tr2i2x1
trizi1
tr2i1z
1tr2oc
t4r2oi
tro2ke
tr2o1k
tro2mi
tr2om
2t3roo
t4rop
3t2ro2pf
2t3roß
t3r2öc
2t1r2öh
trö4s3s
trö1s
2t3r2ö1t
1t4r2u1g
t1ru
2truk
t2rum2
tru2m1s1
2t3ru2n1d
trun1
1t4r2u2n1k
3t4rup
t3r2uß
tru2t3h
t3r2u1t
t4r1üb
t1rü
trü1be2
trü1bu
2t3r2üc
trü4ck1er6
trü2c4k
t4rü1g
3t4rü2m1m
t1ry1
2ts
t4s3a4b
t1sa
t3s2ac
ts1a1d
t2s1ah
ts1al
t4s1a4m1t4
t3sam
t2s1an
t4s3ar
ts1as
tsa3s1s2e
tsa2s1s
t2s1a4u
t1sä
t2s1än
t4schar
t1sc
tsc4h
t3sch2e
t4s1che1f
tsch4li
ts2c4hl2
t4schro
tsc2h2r4
t3s2co4r
t2s1co
t2s1e2b
t1se
t3s3eh
t3se4il
t4sei2n1d
ts1ein
ts1e1m
tse2n1
t2s1e4n1g
t3s2e6n1s
t2s1e4n1t
t2s1er
t4s3es1se
tse1s
tse2s1s
t3se1t
t4s1e2t2h
t2s1i2d
t1si
ts1i1n1i1
t2s1ir
t3ska1la
t2s1k2
ts1ka
ts3k2r4
ts1o
tso2r
t1spal
tsp2
tsp2a
t1span
ts1par
t1s4p2a1re
t1spas
ts2pe1d
tsp2e
t1spe1k
t1s2pi
t1s2pon
ts1p2o
t1s2por
t2s3s2
t1st4
t3s2t2a1t
ts3tä1ti
tst2ät
t4s1t2e2a4
t3st2e
t4s1te1p
t4s1te4r1m
t4s3te2r1r
t3s1t2i2e
t2s3t2i1s
t2stit
ts2to
t3s3t2oc
ts3tor
t4s3t1r2a1d
tst2r4
t2s1trä
t2s1t1ri
ts2tro
t4st4rop
t2s3t1rü
ts1u
1t3s2u2b3
t1sü
4t1t
tt1a1b
tta2be
t2t2ac
t2t1a1d
tta6ge2s5s
t3t2a1g
tt2age1s
tta1ge
t1t1ak
tt2al
tt2a2n1t
t2t1a4r1t
tta3s
tt1ebe
tte1b
tt1e2if
tt1ei1s
tte2la4
tte4l1e1b
tte3le
tte4l1en
tt2e1l1o
t3ter
tt2e4r3g2
tterma8s7s
t1te4r1m
tter3ma3s4
tter1ma
tte4s1ä
tte2s
tt2häu
tt1h
t2t3hä
t2t3ho
ttra2s3s
tt2r4
ttr2a3s2
t3tro
tt3ru
tt3rü
tt2se2n1
t2ts
tt1se
tts1p2
tt4s3te1m
tt1st4
tt3st2e
tt4ster
tt4sti
4t4t1t4
t2t2uc
tt2un1
tu1a2l1m
t2ua
tu3an
1t2uc
tu2chi
tuc4h
1t2u1e
tu2e1re
2tuf
tu1f2e
tu3fen
t3u2fer
2tu1h
tu2i1s
t2ui
t3u2k4r4
tul2a
tu1l
1tum
t2um.
t2u1me
2t3u2m1f4
t3u4m1g2
2t3u2m1k4
2tu2m1p
t3u2m3r2
tum2si
tu2m1s
tum2so
2t3u4m3t2
t3u2m1z
1t2un.
tun1
2t1u1na
2t1u2n1d
2t3u2n1f
t3unga
tu4n1g
tun4g6s1
2t1u2n2if
tu1ni
2t1u2n2i1o
1tu4n1n
1tu6n1s2
2t3u2n1t
t1up.
tu2r1a4g
tu1ra
t2u2rä
tu2r1c
tu3re.
tu1re
tu2rei
tu2r1er
tu2re1s
tu2r1e4t
turin1
tu1ri
1tu4rn
t4u2ro
tu4r3s
tu4ru
tu2sa
t2us
tu4s2c4hl2
tu1sc
tusc4h
tu2so
tu3ta
t2u1t
2t1üb
1tüc4h
t2üc
tüc2k2s
tü2c4k
1tüf
1tür.
tü2r1c
1tü1re
1tü4r1g
1tü4r1s
1tüten
t2üt
tü1te
2tü4t1z
2t3v
4t3w
tw2a2
tw2ä4
twi4e
1ty
3ty.
3ty1p
ty2p2a2
3ty1s
4t1z
t2za4
tz1a1g
tz1al
tz1ar
tz1a2u
tz1ä2
t3ze.
t2z1ec
t2z1ei1e
t2z1ei3s2
tze4n1
tz2e1ne
tz3en4t3s
t3ze4n1t
tz1e4r1l
tz2e4r1s2
t3ze2s
tze1s3t
tzg2el2
t2z1g2
tz1ge
tz1i4n1t
t2z1or
tz2ö
tz2t1h
t2z1t
tz2tin
tz1w2ä
tzw2
tz1wi
tz1w2u
2ua
u3a2b
u1a2c
uad4r4
ua1d
u1ah
u1al.
ua2l1a2u
ua1la
u1a2l1b
u3ale4t
u2a1le
u1a2l1f
u3a2lo
u1a2l3r2
u1a4l1s
u1a4l3t
ua2lu
u1am
u1a6n1s
u3ar.
uar2a2b
ua1ra
u1a4r1s
ua3sa
ua2t2h
u2a1t
u4a3t2i
u3a2u
ua2u2s
u1ay
u1äm
u1än
u1äu
2u1b
u2bec
ub3ein
u3b4i
ubi3os.
ub2i2o1
ubio3s
ub2l2
ub3l2i1c
ub1li
u2b3lu
u2b1op
ub2r4
u3b3rä
u2b3r2it
ub1ri
ub2s1an
u4b1s
ub1sa
ub2s1o
ub2sp2a
ubsp2
ub2us3
u2b1üb
2uc
u2c1c
u1ce4
uce1s3
uch1a
uc4h
u1cha.
uch1ä
u1che
u2ch1ec
u4ch1ei
ucher3ma8s
uc2he4r1m
ucher1ma
u3che1s
u1chi
uch1il
uch1i2n
u2c4h3l2
u4c2h3m
uchma6s1s
uch1ma
uch3mas
u2c2h3n4
u2c2h3r4
uch2so
u4c4h1s
uch4sp2r2
uchsp2
uch1st4
uch4tor
u2c4h1t
uchto2
uch2t3r4
u1chu
u2chum
u2ch3ü
u2c2h1w
u1ci
u4ck2er
u2c4k
u1c4l2
2u1d
u3d2a
ude1r2e
u1de
ude4r1t4
ud2i3en
u1di
udi2e
udi1ti4
u2don
ud3ra
ud2r4
u3d1ru
2u1e
u2e1d
ue2en
u4ee
u2e1g
u2e1la
ue2le
u2e1li4
ue2mi
ue1m
uen1
ue2nä
ue2ner
ue1ne
uen1ge4
ue4n1g
ue2ni
ue2no
uen2sa
ue6n1s
uen2zu
ue2n1z
u2e1p
ue2r3a
ue2r1ä
ue3r1e2i1g
ue1re
u3ere2m1p
u2e3re1m
u3ere4n1t
u2eren
ue4r1e4r1g
ue4r1e2r1k
ue4r3g2
u4e3ri4n1n
u2e1ri
u3e2r1i4n4t
uer3ma6s
ue4r1m
uer1ma
uer3ne
ue4rn
uer4ner
uer3o
u3e2r1r
uer3sc
ue4r1s
ue4r3t2
u3e2r1u4m
u2e1ru
u3e2r1u2n1f
uerun1
u3e4r3u2n1t
ue4s
ue5se
u2e5sp2
ue2ta
ue1t
ue4te1k
ue3te
uf1a1b4
u1fa
u3f4ah
uf1ak
uf3ar
uf1a2u
u2f1ä4s
u1fä
u2f1ä2ß
u2f1ei
u1fe
u2f1e1m
u3fen.
u2f1e4n1t
u2f1e2r3h4
u4fer1l2e
ufe4r1l
uf2e4rn
u2f1eß
2u2f1f
uff4l2
uf2f3ro
uff2r2
uf3l2
u2fo1b
u1fo
ufo2r
uf1o1ri
uf3r2
uf3sä4
u4f1s
uf2s1p2o
ufsp2
uf4s3te1m
uf1st
uf3st2e
uf4ster
uf2t1e1b
u4f1t
uf3ten
uf2t3s2
u2f1um
u1fu
2u1g
u4gab5te
uga1b
uga4b1t
ug1af
ug1a1k
uga4n1g4
u2g1a1p
uga4s
ug1a2u
u2g3d2
u2g1ei
u1ge
ugen1ma3
u3gen
uge2n1m4
ugen3mas6
u2g1e2r1f
u2g1e4r1l
ug3hu
u2g1h
u2g1l
u3g3la1d
ugl2a
ug3lo
u3g2lö
u4g1lu2
u2g3n
ugo3
ug1or
u2gö
u4g3rei1s
ug2r4
ug1re
ug3ro
ug4ro3s2
ug3rüs
ug1rü
ug3sc
u4gs
ug3s1e2
ug3s1i
ugs1ma3
ug4s3m2
ugs3mas4
ug1sp2a
ugs1p4
u2gü
u1h
2uh.
uh1la
u4hl
uh1lä
uh2li
uh1me4
u2h1m
uhr1a
u2hr
uh2rer
uh1re
uh3ri
uh4rin
uh4r1t4
uh2ru
uh4rü
uh1u2n1
u2h1w
2ui
u2i2c
u1ie
ui1e1m
u3i1g
u4i1ge
u1im
u1in.
u1is.
ui1s
u3i6s4ch.
ui1sc
uisc4h
u3i6s4c4h1s2
uisi4n
ui1si
ui2st
u1j
u1k2a
u3käu
u1kä
u1ke
u1ki
u1k2l2
ukle1i
uk2le
u1k4n2
u3ko
u1k2ö
u1k4r4
uk2ta
u4k1t
uk2t1in
uk2t3r4
u1ku
uk2u2s
u1l
ul1am
ul1äm
u2l1b4
ul2d3r4
u4l1d
uld2se
ul2d1s
u2l1el
u1le
ule4n
ul1e2r1f
ul1e2r3h4
ul1e2r1w
ule2sa
ule1s
ule2t
ul1eta
u2lex
u2l1f4
u2l1g4
ul2i2k
u1li
ul1i6n1s
ul3ka
u2l1k
ul2k2n2
ull2a
u4l1l
ul3len
ul1le
ul2l2e1s
ul2lö
ulm3ein
u2l1m
ul1me
ul2o2i
u1lo
ul1or
ul2p1h
u2l1p
ul2sa
u4l1s
ul4sam
ul1s2t
ul2s3z
2ulta
u4l1t
ul3t1h
ul2t1ri
ult2r4
ul4t3s
u2lü
ul2v2r
u2l1v
ulz2w2
u2l1z
u2m3a2k
u1ma
um1a4l1l
um2an
u2m3a2n1z
u2m1a4r1t
u2m1a2us
uma2u
u2ma4u1t
1u2m3d2
um2en
u1me
umen4t4s
ume4n1t
ume1r2a
u2m1e4r1g
u2m1e4r1l
u2m1e2r1w
1u2m1f4
1u4m1g2
um1i6n1s
u1mi
um1ir
1u2m1k4
1u2m3l2
4u2m1m
um1m2a
umpf4li
u2m1p
um2pf
ump2f2l2
um2p3le
um1p2l4
1u2m3r2
3um3s2a1t
u2m1s
um1sa
um2s1a4u
um2ser
um1se
um2sim
um1si
um2s1p2e
umsp2
um4ste1m
um1st
um3st2e
um2su
u4m3t2
u3m2un1
u1mu
u2m1ur
1u2m1z
un1
2un.
2u3na.
u1na
1u2n1a1b
u3n3a2c
un4al
u3n2am
u2n3an
2u3n2as
u3n3a1t
1unda
u2n1d
un4d1a1b
1un2d1d2
un4d1ei
un1de
un4d3e2r1f
und5e2rha
unde2r3h4
1un2d1f4
2un2d1g2
un2di1d
un1di
1un4d5n2
un2d1or
un2d3r4
4unds.
un2d1s
und3sp2
un2d1um
un1du
1un2d1v2
1un2d1z
u3ne
une2b
une2h
un2ei.
u2n1ei
un3ein
une4n2t
u3nen
u3n4e1s2
1unge1t
u4n1g
un1ge
1unge1w
1un3glü
un2g2r4
ung3ra
ung3ri
un4g4s1
un2i1d
u1ni
un3i2de
1u2n2if
u3n2i1k4
un2im
uni2r
2u3n2i1s
un3i2s1l2
u3n2it
3u2n2i3v
2u2n1k
un2k1a2
un2kei
un2k1s2
unk4t1it
un4k1t
unk2t3r4
3unku
un2n3a2d
u4n1n
un1na
un3n2e
uno4r
u1no
un2o1s
1u2n3r2
u6n1s2
2uns.
un3se
1un1si
un3s1k2
un3sp4
unst2r4
un1st
1u2n1t
un3ta
unt4e4ri
un3ter
un3t2r4
un4t3s
2untu
u1nü
un3v2ol2
u2n1v2
unvo4l1l3
1u4n1w
2u2n1z
2uo
u1o2b
u3of
u1op
u1or
u3or.
u3o2r3c
u3o4r1s2
u1os.
uo1s
uote2
u2o1t
u1p2a
u1p2e2
uper1
up2fa
up1f2e
up1f1i
u1pf2l2
u3pi
up2p3l4
u2p1p
up2p2r2
u1p2r2
upt3a2
u2p1t
upt3e4r3g2
up3te
upt1o
u1q
2ur.
u1ra
u2r1a1b
u3r2aba
ura2be
u2r3a2m
u2r1a1na
ur2a2n3b4
u2r1a4n1g
ur2a2n1h2
u2r1a6n5s
u2r1ar
ur3a4ren
ur2a1re
u2r3a4t1t
u1r2a1t
u2r1a2u
2u1rä
ur1än
ur3b2a
u2r1b
urc4h1
u2rc
ur3d2i
u2r1d
u4r1e2f1f
u1re
ure1f
u2re1l2e
ure4n
u4r1e1p
ur1e2r3h4
ur1e2r1w
2u2r1f
ur4f3t
ur2g1ri
u4r1g
urg2r4
ur4gs2
ur2i2c
u1ri
ur1im
ur1i1ni
u2r3i6n1s
u2r1i4n1t
1urla2u
u4r1l
ur3l2a
4u1ro
u3r2ol
u1rö
u2r3p2
2u2r1r
ur2s2an
u4r1s
ur1sa
ur2s1a4u
ur2ser
ur1se
urs2t4r4
ur1st
ur4s1w
urs2ze
ur2s1z
u4r1t2
u3ru
ur2za2
u2r1z
ur2zä2
ur2zi
ur2zo
ur2z1w2
2us
u4saf
u1sa
us4a4n1n
us1an
us5a4r1t
u2s1ar
u1sä
u6sche4n1t
u1sc
usch2en
usc4h
usch5wer
us2c2hw
u2s1ec
u1se
u2s1ei
u3se2i1d
u3se1p
use1r1a
u2se2r3p4
u2s1e1se
use1s
usi3er.
u1si
u3s4ie
usi5ers.
usie4r1s
us3k4l2
u2s1k2
u4s1ko
us3mas2
u4s3m2
us1ma
usma5s1s2e
usma2s1s
u1so
us3oc
u3soh
u3s2ol
u2s1op
u1s1o2u
u1sö
u1sp2
us3pa4r1t
usp2a
u2s1pas
u2s1p2a1t
us1p2e
u3s2pe1k
us1p2ic
u1spi
u3s2p2i1z
u2s1p2o
u1s2por
u2s1pu
u4s3sel
u2s1s
us1se
us2sen
us5sen1de
usse2n1d
us6s2e1ni
ussen3ma7s6
usse2n1m4
ussen1ma
us2ser
us4se2r1f
uss5er1fa
u4s2se1z
u3s2s2i1g
us1si
us2sof
u4sso
u2sta1b
u1st
ust3abe
u3stal
u3s2t2a1t
us2ten
u3st2e
us4t4er.
u4s2t1h
ust2in
u3st2i1s
u2s1tor
u2s1trä
ust2r4
u4str2it
ust1ri
u3s4t4rop
u2s1tur
ust2u
u2s1ty
u1su
us2ur
2uß
u2ß1u2
2u1t
u3ta.
u2t1a4l1t
u2t3a2m
u2t1a1p
u2t1ar
u2tär
u3te.
ut1e1g2
ute4ge
ut1ei.
ut1ei1e
ute2n1
u3t6en.
u2te4n1t
uter4er
ute1re
u4t3er1s2a
ute4r1s
u3t2e2s
u3t2e1t
u2t2e1v
u4t1ex
ut1fi4
u2t1f4
u1t2h2e
ut1h
u2thi
u2t3ho
u2thu
ut3mas2
u2t1m2
ut1ma
utma5s1s2e
utma2s1s
u3to.
uto4ber
u1to1b
utob2e
ut2o3c
u3t2om
ut1opf
u2to2p1s
ut4or
ut3r2e1a
ut2r4
ut1re
ut3rü
ut3s2a
u2ts
ut2s1ä
ut4s2c4hl2
ut1sc
utsc4h
ut4s4c2h2m
ut4s2c1h2ö
ut1sp2
ut2sp2a
ut3te
u4t1t
ut3t4l2
ut2t1s2
utu4re
utu5ru
utz3e4n1g
u4t1z
utze4n1
ut2z1in
ut2zo
ut2z1w2
2u1u2
u1ü2
u1v4
u2ve.
u1ve2
uve3rä
uver1
u1w
2u1x
u1x2e
ux2o
u4x3t
u1ya
2u1z
uz1we
uzw2
u4z3z4
1üb
2ü2b1c
2ü2b3d4
übe2
über3
üb3l2
üb3r4
2üc
ü1che
üc4h
ü2c4h3l2
üch4s1c
ü4c4h1s
ücht4e
ü2c4h1t
ü4ck1er
ü2c4k
ück3e1ri
ück4sp2e
üc2k1s
ücksp2
üd3a4
ü3d2en.
ü1de
üde4n4g
ü3d2e6n1s
üd1o4
üd1ö4
üd3r4
ü2d3s2
üd1sa1
ü2d3t4
üdwe2
ü2d1w
ü2f1a
ü2f1ei
ü1fe
üfer2
ü2f1e4r1g
üf2f3l2
ü2f1f
ü2f1i
üf3l2
üf2to
ü4f1t
ü1g
üge6lei1s
ü1ge
üg2el
üge3le
üg2elei
ü2g3l
ü2g1n
ü4g3s
üh1a
ü1he
ü2h1ei
ü2h1e4n1g2
ü2h1e2r1k
ü2h1e2r1z
üh1i
üh4l1ac
ü4hl
üh1l2e
üh3mo
ü2h1m
üh3n2e
ü2hn
üh3r2e
ü2hr
üh4r3ei.
üh1ro
ühr3ta
üh4r1t
ü4h1s2
üh3st2u
üh1st
ü4h3t
üh4t1h
ü1hu
ü2h1w
ü1k2
2ül
ül1a
ü2l2c
ü1l4e
ül2la
ü4l1l
ül2l1ei
ül1le
ül2lo
ül2lö
ü1lu
ü2me4n1t
ü1me
ü2n1a
ün2da
ü2n1d
ün2d2r4
ü3nen3
ü1ne
ün2fa
ü2n1f
ün2f1ei
ün1fe
ün2f1li
ünf4l2
ün2f4r2
ün2g3l
ü4n1g
ü2n1t2
ü1nu
ün2za2
ü2n1z
ü1p2e
ü1pi
üp2p3l4
ü2p1p
ür1a
ü2r1ei
ü1re
ür2f2l2
ü2r1f
ür2f2r2
ür4g3e4n4g
ü4r1g
ür1ge
ür3gen
ü1r2o1
ür4ster
ü4r1s
ür1st
ür3st2e
ürt2h
ü4r1t
ü1s2a
ü1s4c
ü2s2c4hl2
üsc4h
ü5se
ü3s2e3h
üse3l
ü1sp2
ü6s4s1c
ü2s1s
üs1s2e
ü4s3sel
ü4s4st
üs2su
ü1s4t
ü2sta
üste3ne
ü3st2e
ü2st2r4
ü1su
ü1ß
2üt
ü1ta
ü2t1al
ü1te
ü1ti
üt3r4
ü2t2s1
üt2t2r4
ü4t1t
ü1tu
ü1v
ü1z
2v1a1b
va1c
va4l2s
2va4n1g
2v1a2r1b
vas2
v4a1t
va2t3a4
va2tei
vat2e
va2t3h
vat2i1k2
v4a3ti
va4t1in
vati8ons.
va3t2i1o
vation2
vati3o6n4s3
v4a2t3r4
va2t3s4
va2t1u
va4t3z
2v1a2u
vä1
2v1b
2v1d
1ve2
ve3ar
v2e1a
ve3b
ve3c
ve3d
ve3g
ve3h
ve4i
ve2it2
vei2ts1
ve3la
ve4l1a2u
ve3le
v2e3li
v2e3lo
v2e3ma
ve1m
2ve3mu
ve3nal
ve2n1a
ve2n2c
ve3ne
vene2n4d
v2e3nen
ve3ni
v2e3nö
v2e3o
ver1
ver3a
ve3r2a1d
ve3ra2n3d
ve3r2a3s2
ve2r3b2
ve2r1d2
ve1re2
ve4re1k
ve2r1f4
ve4r1g4
verga2s6
v2e3ri
ve4rin
ve2r3k
verma5s8sen
ve4r1m
ver1ma
ver3mas
vermas1s2e
verma2s1s
ver3sta
ve4r1s
ver1st
ve4r1t2
ver5te
v2e1r3u
ve3s
2ve3sc
2ve1se
v4e4s1h
v2e4s1p2
ve1s4t
ve3ta
ve1t
ve3te1
v2e3t2r4
2ve3ü
v2e3v
ve3x2
2v1f4
2v1g
2v1h
vi3ar
vi1a
vi4a3t
v2i2c
vi2e2h3a
vi2el
vi2er
vie4rec
vi2e1re
vie2w1
v2i1g2
2vii2
vi2l1a
vi4le2h
vi1le
vi2l1in
vi1li
2v1i2m
vi1ma2
vi4na
vi6n2s
2v1i4n1t
vi3sa
vi1s
v2i1se4
vi3s2o
v2i2sp2
vis2u
2v1k
2v1l2
2v1m
2v1n
2v1o1b
vo3ga
v2o1g
vo2gu
3v2ol
vollen4
vo4l1l
vol1le
vol6l5e2n1d
vol2li
2v1op
vo2r1
vor3a
vo2r3d
v4o1r3e
vo4r3g
vo3ri
vo5r2i1g
vormen4
v2o4r1m
vor1me
v2o1rö4
3voy
2v1p
v2r
2v3ra
v3re
v4r4ee
2v3ro
2v1s
v1s2e
v3s2z
2v1t
vu2e1t
v2u1e
2v1u2m1f4
2v1v
2v1w
2v1z
w2a
1w2aa
wab2bl2
wa1b
wa4b1b
wa3che
wac4h
wach6st2u
wa4c4h1s
wach1st
wach4t4r4
wa4c4h1t
waf1f2e2
wa2f1f
waffel3
1wa1g
wa5ge
wa2g3n
wa3go
1wah
wahl5e4n1t
wa4hl
wah1le
wah4ler
wah2li
wa2i2b
1wal
2wa2l1b
wal4da
wa4l1d
2wa2l1m
wal2ta
wa4l1t
wal2to
walt4st4
wal4ts
wa3na
wan4g4s
wa4n1g
wa2p
1w2a1r2e
ware1i
war3t4e
wa4r1t
1was
wa3sa
wa4scha
wa1sc
wasc4h
w4a3s1h
was1s4e
wa2s1s
w2ä
1wäh
1wäl
wäm3
2wä4n1g
1wä3s3
wä5s4c
wä4s2s
2w1b2
wbu2
2w1c
2w1d
w2e2a
w2e2ba
we1b
4we3be1b
w2e2bl2
we4b3s
w4e2e4
wee1d3
w2e2f2l2
we1f
1we1g
we2g1a
we2g3l
we2g3r4
we4g1s2
1weh
we2i
wei4bl2
w4e2i1b
2wei1e
we2i1k4
wei4s4s3p2
wei3s2s
wei1s
wei4t2r4
we2it
wei2t1s
wel6s2c4hl2
we4l1s
wel1sc
welsc4h
wel6sc2h2r4
we4l2t1
wel4t3a4
wel6t5e2n6d
wel3t2en
wel4t3r4
we2n3a4
we3ni
wen4k3ri
we2n1k
wenk2r4
we2r3a
wer2bl2
we2r1b
1werb2u
we2r1d2
5werde6n1s
wer3de
wer3den
1wer1du
werer2
we1re
wer2f2l2
we2r1f
wer4g2el
we4r1g
wer1ge
we4r3i1o
w2e1ri
1w2erk.
we2r1k
wer2ka
1werke
wer2kl2
wer2ku
we2r1ö
wer4sta
we4r1s
wer1st
wer2ta
we4r1t
wer6t5e4r1m
wer2to
1wer4ts
1we1se
we1s
wes2e6n4s3
w2e2s1p2
we1s2t
we2st1a
we4st3e2i
we3s2t2e
we4s2teu
we4sti
we2st1o2
we2stö
we2st3r4
we4s2t2u
1we1t
we2t2s
wet2t3s
we4t1t
2w1ey
2w1g
2w3h
1wi1d
wi2e
wie3l
wi2e1n2e
w2i1en
wie4st
wie3s2
w2i1k2
1wil
wim2ma
wi2m1m
wim4m3u
win4d3ec
wi2n1d
win1de
w2in2d2r4
w2i1n2e
2wi4n1g
win8n7er1sc
wi4n1n
win1ne
winne4r1s
win4num
1wi4r
w2i3s2e
wi1s
w2i2sp2
1wi2s1s
wi3st
wi3t1h
1wit2z1l2
w2i4t1z
2w1k
2w1l
2w1m
2wn
w6n3s
1w2o1c
wo2ch1a
woc4h
wo1che4
1woh
woh2le
wo4hl
1wo2l1f
w2ol
wol4f2s1
wol4la
wo4l1l
wol4ler
wol1le
wor3a
wo2r3i
wor2t3r4
wo4r1t
wo4r3u
w2o1t2
1w2öc
wört2h
wö4r1t
2w1p
w2r
w3ro
2w1s
w3s2k2
2w1t
wti2
w2u
1w2uc
wuch4sc
wuc4h
wu4c4h1s
wu4l1s2
wu1l
wu6n2s2
wun1
4w2ur.
wur2fa
w2u2r1f
1wur1st
wu4r1s
w2us4
1w2u2t1
1wüh
wüs4
2w1w
x1a
1xa.
2xa2b
1x2a1d
1x2a1e1
x2a1f3l2
1x2a1g
xa2m
x2a2n1z
1x2as
xa2u3
xa2us2
2x1b
2xc
x1ce
x1c4h
x1c4l2
4x1d
1xe
x1e4g
2xe1k
xe2l
x2e3lei
xe1le
x1e1m
3x2em.
x2en
xe6n3s2
x2er.
x2e1re
xe4r1s2
3xe1s
2x3eu
2x1f
2x1g
2x1h
x2i1b4
x2i1c
xic4h2
xi1de2
xi1d
xi2d1e1m
x1i2do
xie3l
x2i3g
xil1
xi1l2a
x2i2lo
xi2lu2
xi6n3s2
x2i1s
x2i2s1e
xi2s1o2
xi2s5s
xi3stä
xi1st
xi2su
x1i2tu
x1j
2x1k2
2x2l2
x3lä
x3le
2x1m
2x1n
x1or
4x1p
xpor6ter
x1p2o
xpo4r1t
x1q
2x1r
2x3s2
4x1t
x2t1a
x3ta.
x3t2as
xt1ä
x2tän
x2t1e2d
x2t1ei
x2te4n1t
x2t1e2r2f
x2t3e1v
xt1fi4
x2t1f4
x2t1i4l2l
xtr1a3b4
xt2r4
x2t3ran
x2t1s2
xt1u
x3t2ur
1xu
x2u1a
x1u2n1
x2u2s
2xv
2x1w
2xy
3xy.
3xy1s
x1z
2y1a1b
1yac
y1al.
y1a2m
ya4n2g
y1a2n1k
y1ät
y1b
y1c2
y2chi
yc4h
y3chi1s
y2c2h3n4
y1d4
y1e
y2e1f
ye4n4n
y2e1re
ye1s2
y2es.
ye4st
ye2t2h
ye1t
y1f2
y1g
ygi2
ygie5
yg2l
y1h
y2hr2
y1i4
y1j
y1k2
yke3n
y2k3s2
y1l
y2l3a2m
yl4ante
yla2n1t
y2l3c
y4le.
y1le
yli4n
y1li
yl4o3ni1
y1lo
y2l1u
ym2a2t
y1ma
y2m3p2
ym3pi1
y2n1o
y2no4d
y2n1t2
y1of
y2om2
y4o3n4i
y1o2n1t
y1o1s
y1o2u
y1p
yp2a2
yp3an
yp2e2
y2pf
y3ph
y2p1in
y1p2o3
y4p3s
y1r
y3r2e
y3ri
yri2a
yr2i1e
y3r4o
y2r1r2
y1s
ys2an
y1sa
y1s2c
y1se1
y3s2h
y4s3l2
ys1me3
y4s3m2
ys2p2a
ysp2
y1st4
y2s1u2
y3s2z
y1t2
y2te.
y2te2s
y3to
yu2r
yu1re3
y1v
y1w
y1y
y1z2
za2
2z3a1b
zab3l2
za3ch1a
zac4h
za3chä
z1a1d
2z1af
za3ge
za1g
za3g2r4
3zah
2z3ak
z2a1le3
z2a3li
2z1a4l1l
2z1am
z1an
z2a3ne
2z3a2n1f
3z2a3ni
2z3a2n3l2
za3no
za3ra
2z1a2r1b
2z1a2r1c
z2a3re
z2a3ri
z1a4r1m
z2a3ro
z1art2i
za4r1t
zar2t2r4
2z1a2r1z
z1as
za1st4
2z3a1t3
3z2a2u1b
za2u
z1au2f
z3a2u1g
3z4aun1
zä2
2z1äc
3z2äh
2z1äm
z1ä4r1g
z1ä4r1m
4z3b4
zb1ü1b
zbübe3
2z3c
2z3d2
zdan2
zdä1
zea2u3
z2e1a
zea2us4
2z1e2b2en
ze1b
2zecho
zec4h
2z1e2c4k
z4e1e
2z1e2f1f
ze1f
ze2i1k4
zei3la
ze4il
zei1le4
2z1ein
zeinb2u3s6
zei2n3b4
zei3s2
zei1st4
zei2t1a
ze2it
zeit5e2n1d
zei3te
zeite4n
zei4t3er
zei2t2r4
ze2l1a2
ze2len
ze1le
ze2l1er
ze2l1in
z2e1li
zell2a
ze4l1l
zel4leh
zel1le
zel4l2in
zel1li
zel3s1z
ze4l1s
zel3t2h
ze4l1t
z2e1lu2
2z1e2m1p
ze1m
5zen.
z2e4n3a2c
ze2n1a
ze4n3n
ze2no
zen1s2e
ze6n1s
z1en4se1m
3ze4n1t
zen4t3s
zen4zer
ze2n1z
z2er.
ze2r3a
ze2r1e2b
ze1re
2z1ergä
ze4r1g
4z3erge1b
zer1ge
z3erhal
ze2r3h4
ze2rha
2ze2r1h2ö
ze2r1i4n4t
z2e1ri
ze2r1k2
z2erl.
ze4r1l
2zer1l2ö
z2e4rn
zer4ne1b
zer3ne
zer4n3ei
2z1er1q
ze4r1s2
2z1er1s2a
4z3er3s2t2e
zer1st
zert1a4
ze4r1t
zer4t3a1g
zert4an
zer6te1re
zer4tin
z4erti
zer6tra2u
zert2r4
4zerwe2i
ze2r1w
2z1e2r1z
3z2erza2
ze2s
ze1s1e
ze1s1i
ze3sku
ze2s1k2
zessen4
ze2s1s
zes1se
zes6s5e2n1d
ze4s2sp2
zes1t2r4
ze1st
ze2ß1
z2e2t2r4
ze1t
2zet2ts
ze4t1t
2z1ex
2z1f4
zfä4s3
z1fä
2z1g2
zge1r2a
z1ge
2z1h
z2hen
zh2i2r3
3zi.
zi3a1lo
zi1a
z2ial
zi3ar
z2i2d3r4
zi1d
zi1e2r3h4
zi1es.
zie3s2
3z2i1g
zi1l2e
z2i2m1m
2z1i2m1p
z2i1n2e
zin4er
2z1i2n3f
z1i2n1h2
zi2n1it
zi1ni
zin2sa
zi6n1s
zin4ser
zin1se
4z1in2s1u2f
zin3su
z1i2n1v2
z2i2o3
zi3o1p
z2i2r1k2
zir2k6s
zi3s2z
zi1s
zit2h
2z1j
2z3k4
2z1l2
2z1m2
zm4e2e
z1me
2z3n4
2z1o1b
2z1of
zo2gl
z2o1g
2z1oh
3z2ol
zon4ter
zo2n1t
zo2o
2zop2e
z1or
zo2ri
zor4ne
zo4rn
2z1o2s1z
zo1s
2z3o1t
2z1ö2f
z1öl
2z2ön
2z3p4
2z1q
2z3r2
4z1s2
z3sa
z3s1h
z3s1k2
z3st2r4
z1st
z3s1z
2z1t
z2t1a2u
z4te1he
z2teh
zte3st2r4
zte2s
zt2e1s2t
z3t2her
zt1h
z1th2e
zt3ho
zt1i6n1s
z2t3rec
zt2r4
zt1re
z2t3s2
z2u3a
z2u1b4
z2u4c
z2u1d4
zu1di4
zu2el
z2u1e
zu3f4
zu2g1ar
z2u1g
zu4ge4n1t
zu1ge
zu3gen
zu3g1l
zug1s1t
zu4gs
zug4st2e
zug1un1
zu1gu
2z1u2hr
zu1h
zuh2u
z2u1i
zu3k
2z1um.
zum2en2
zu1me
2z1u2m1f4
2z1u4m1g2
2z1u2m3l2
2z1u2m1s
zu3n2e
zun1
2z1u2n1t
zup2f1i
zu3r2a
z1u2r1k
2z1u4r1l
2z1u4r1s
2z1u4r1t2
z2u3s2
z2u3t2
z2u1z2
2z1üb
zü2r1c
2z1v
zw2
z1wac
zw2a
4z1wah
zwa2n2d1
z2wa4n1g
z1war
2z1was
4z1wäl
zw2ä
2z1we1g
z2we2i1g
zwe2i
z1wei3s
2z1wel
2z1wen
2z1wer
z2we4r1g
2z1we1s
2z1we1t
4z1wi4r
z2wit
2z1wo
z1wör
z1wur
zw2u
2z1wü
4z1z
z3z4a2
zze3s
z3z2o
zz2ö
PK
!<|.||hyphenation/hyph_de-1996.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ab1a
.a1b
.a1bi4
.ab3l2
.abo2
.ab3ol
.ab1or
.ac4k2
.ag4n
.a1g
.ag4r4
.a1g2u
.ai2s
.ak1t2a
.a4k1t
.al3b2r4
.a2l1b
.a1l2e
.al5l4en
.a4l1l
.al1le
.al4tei
.a4l1t
.al1te
.al6t3s
.amp2e4
.a2m1p
.am4t4s3
.a4m1t
.a2n3d2
.ande2n6k
.an1de
.and4ri
.and2r4
.a4n1g2
.an3g1li
.ang1l
.an4g2s4
.angs1t3
.a6n3s
.an4si.
.an1si
.an1s2p4
.ans2t
.an4t2a1g
.a2n1t
.an1ta
.an3t1h
.a1p2o1
.a1p
.a2p1s2
.ar2i1e
.a1ri
.ar1k2a
.a2r1k
.ar4m3ac
.a4r1m
.ar1ma
.ar2sc
.a4r1s
.ar4t3ei
.ar3t2e
.a4r1t
.as3t
.as4ta
.at4h
.a1t
.a2u3d
.a2u
.au2f3
.a2u4s3
.ausc4h3
.au1sc
.a1x4
.äm3
.ä2t2s
.be3e2r1b
.b4ee
.be3r1a
.be3r2e
.berg3a
.be4r1g
.ber6g2a1b
.ber4g3r4
.bo1ge2
.b2o1g
.bo4s3k2
.bu4s1er
.b2us
.bu1se
.by4t2
.by1
.c4h2
.d1a1b4
.da2r1
.da4rin
.d2a1ri
.da4r1m1
.d4a4te.
.da1t4e2
.d2a1t
.d4a4tes
.de2al
.d2e1a
.d1e1i
.de4in.
.d2e1o2
.d2e3r4en
.de1re
.de1s1
.de1s2e
.de3s1k2
.des2t
.di2e1n4e
.di2e
.d2i1en
.do2mo
.d2om
.do1p2e
.d2o2r1f1
.d1ü1b
.dy2s1
.ebe2r1
.e1b
.ehe1i
.e1he
.ei3e2
.ei4n1a
.ei4n1e4n6g
.e2i1ne
.ei3nen
.ei2sp2
.ei4s3t
.ei4t2r4
.e2it
.eke2
.e1k
.el2bi
.e2l1b
.el4b3s
.e2m3m2
.e1m
.en1
.en4d3er
.e2n1d
.en1de
.en5d4er.
.en2d3r4
.en2d3s
.e4n1n2
.en6n1s3
.e4n2t3
.en4tei
.en1te
.en4t2r4
.er8bre2c4h1t
.er3b2r4
.e2r1b
.erb1re
.erbr2ec4h
.er3da
.e2r1d
.er4dan
.er4dar
.er4d1ei
.er3de
.er4der
.e1r1e
.ere3c
.e2r1f4
.e1r1i
.er8s2t1ein
.e4r1s
.ers1t
.ers1t2e
.ers2te2i
.er8stritt.
.er3s4t2r4
.ers3t1ri
.erstr2it
.erstri4t1t
.er8stritt6en.
.erstrit1te
.erstrit3ten
.er4zen4
.e2r1z
.e1s1p2
.es3ta
.es1t
.es5t4e
.e4st2h
.es3to
.e1s5t2r4
.e2t2s
.e1t
.eu1
.e2u3g4
.e2u3t
.eve4r1
.e1v
.e1ve2
.e4x1t4
.fe2i
.fer4no
.fe4rn
.fi3es1t
.fi4le.
.fi1le
.fi4len
.fi2s
.f4l2u1g1
.f2l2
.fo4r2t
.f1s4
.fu2sc
.f2us
.g2a4t
.gd2
.ge5n1ar
.ge3n1a
.ge3ne
.ge3r2a
.ge3r2e
.ge3u
.g2s4
.gu6s1s1
.g2us
.ha2u2t1
.ha2u
.he2
.h2e3fe
.he1f
.her3an
.her1a
.h2e3ri
.he6r5i4n1n
.hi2s
.ho4me1t
.ho1m2e
.h2om
.i1a4
.i1m2a
.ima4ge
.ima1g
.i2m5m
.in1
.i1n3e
.i2n1k4
.in1n2e
.i4n1n
.in1u1
.i1r2e3
.i1s2a
.jor3
.ka2b5l2
.ka1b
.ka2i
.ka2m1p2
.ka4t3i1o
.k2a1t
.k4a3ti
.ki4e
.kle2i
.kl2
.k2le
.kn4i4e
.k2n2
.k1ni
.kopf1
.ks2
.k2us2
.le4ar
.l2e1a
.l2i2f
.li4tu
.li4ve.
.l2i1v
.li1ve2
.lo4g3in
.l2o1g
.lo3ver1
.l1o1v
.lo2ve2
.lus4t2r4
.lu4s1t
.l2us
.ma3d
.ma2i
.ma3la
.ma2s1t
.m1d2
.m4e2e
.me1l2a
.men8s4c2hl2
.m2e6n1s
.men1sc
.mensc4h
.men8s4c2hw
.me4n3t4
.mi4t1
.m1m2
.nä2s1c
.n2e4s
.n4i4e
.no1b4
.n4o4t3h
.n2o1t
.n2u2s2
.o3a3
.ob1a
.o1b
.ob2e2
.oper4
.op2e
.or2a
.o4r1t2
.ort1s3e
.or4ts
.os5t6a2l1g
.os1t
.os2ta
.os1t2e2
.ost5e2n1d
.o4s3ten
.os8ten8de
.oste6r3e
.o2ster
.os2t3r4
.ozo4
.o1z
.ö1d2
.p2a4r1e
.p2a
.par3t4h
.pa4r1t
.pf4
.ph4
.po1ka2
.p2o
.p2o1k
.p4ro1
.p2r2
.p1s2
.r2a2m3s
.re4b3s2
.re1b
.re3cha
.r2ec4h
.rei2n4t
.r1ein
.r2e1li1
.re3l2i3e
.re1s6t2r4
.res1t
.r2i2a1s
.ri1a
.rich1t6e
.r2ic
.ric4h
.ri2c4h1t
.r2o4a2
.ro3m2a
.r2om
.rö2s1
.r1ü1b
.rü6ck1er6
.r2üc
.rüc4k
.rü1cke
.sal2i3e
.s2a1li
.sc4h4
.s1e3c4k
.s2e6n3s
.s2e1r2u2
.se2t1
.sh2a2
.s1h
.si2m3p4
.si4te
.s2it
.ski1e
.s1k2
.spieg2e8lei
.sp2
.s2pi4e
.spie1g
.spie3ge
.spieg2el
.spiege3le
.s1t4
.s3t4o4re
.s2to
.sucher6
.s2u1c
.su1che
.suc4h
.t2ag2e4s2
.t2a1g
.ta1ge
.t4a1l2e
.t3an4k3l2
.t2a2n1k
.ta2to
.t2a1t
.t4e2e
.te2f
.te3no
.te2s
.t2e4s1t
.t1h4
.ti2a
.ti1d1
.ti4me.
.ti1me
.ti4mes
.ti2s
.ti5ta
.ti3t2e4
.to4nin1
.t4o3ni
.to4p2l4
.t2o2w
.tri3es
.t2r4
.t1ri
.tr2ie
.tro2s
.ts2
.tu3ri
.u1f2e2
.ufer1
.ul4mei
.u1l
.u2l1m
.ul1me
.um3
.u1mo2
.u1n3a2
.un1
.u2n3d
.u3n3e
.u4n3g
.u3n2i4t
.u1ni
.u6n3s2
.uns4t
.ur1
.u1r2i
.u2r3i6n4s
.ur3o2m
.u1ro
.uro2p
.u4r3s2
.u1t2a
.u1t
.u1t3r4
.übe4
.ve5n2e
.ve2
.vo4r1
.wa2h4l
.w2a
.wa2s
.wei4ta
.we2i
.we2it
.wi4e
.wor2
.wor3t5e2n6
.wo4r1t
.wor1te
.wor8te2n1d
.wor4tu
.xe3
.ya4l
.z1a2s
.zi2e
.zin4s1t
.zi6n1s
.zwe2
.zw2
2aa
a1a1b
aa2be
aa1c
aa2g2r4
aa1g
4a1a2n
4a2ar
aa2r1a
a2a2r3f4
aa4r1t2
aas5t
aa2t4s3
a2a1t
a3a2u
a1ä
a1b
2aba
ab1auf
aba2u
ab1ä
ab2äu
1a2b3d4
a3b1e1b
ab4e1e
abei1
ab1e4il2
2a3bel
abe2la
a3ber
ab1e2r1k
ab1e2r1r
ab1e2r1z
ab3es3s4e
a3be1s
abe6s1s
2a3be1t
2a3b2e1w
1a2b5f4
3ab1fi
1a2b1g2
1a2b5h2
2a1bi
ab1i6n1s
ab1ir
ab1it
1a2b1k4
ab1l2
1a2bla
ab5la1g
1a2blä
2ab2le
ab4le.
ab3li
ab4lo
3a2b1lö
a2blu
1a4b3n2
a2bo.
a2b2of
1a2bon
2abor
ab3r4
a3bra
a4brä
2a3b1rü
1a4b1s
2abs.
ab1s2a
2ab2s3ar
ab3s2i
ab3sp2
abs2t4
2ab6st.
ab3s1t2e
ab3s1z
1abtei
a4b3t
ab5te
2abu
ab1ur
2abü
1a2b1w
2a3by1
aby4t2
1a2b3z2
2a1ca
2a2c1c
a1ce1m
2a4ch.
ac4h
ach1a
a1chal
ach3a2u
2a2c2h3b2
a1che
a2ch1e2c
a4ch1ei
a4che2r1f
a4che2r1k
a4ch1er1ö
a4ch3e2r1w
4a2c2h3f4
a1chi
a2c2h3l2
a4c2h3m
a2c2h3n4
a1cho
a3cho.
a2ch1o2b
ach1or
ac1h3ö
ac2h3r4
ach3s1u
a4c4hs
a4c4h1t
ach3t5e4r3g2
ach1te
ach2t1o2
ach8t3r4aum
ach1t2r4
achtra2u
ach8träume.
achträ2u
achtr2äum
achträu1me
ach8träum2en.
achträum2en
ach6tr2it
ach3t4ri
a1chu
a2ch1u2f
a4ch3ü
2a2c2h1v
4a4c2h1w
a1ci
ac1in
a1cka4r1
ac4k
ac1k1a
ack2en
a1cke
a2ck1in
a1cki
ack2se
a4c2ks
ack3s1l2
ack3s2ta4
acks1t4
a1c4l2
aco4n4n
a3co
2a3cu
a1ç
a1d
2a3da.
a3d2a1b
ad2a1g
ada2m
ad3a1m2a
a2d1an
3a4d1a1p
a3d2ar3
4ad2a1v
1a2dä
a2d1c
1a2d1d2
2ade.
a1de
ade2al
ad2e1a
ad2e1fi4
ade1f
a2dein
ad1ei
2aden
ade1r2a
a2d2e1ri
4ade1s1
ad2e3s2p2
ade6s4s2
ade5s2t2r4
ades1t
2a2d1f4
2a2d1h2
4a3di
ad2i3en
adi2e
5a2d1j
2ado
ad2o1b
2a2d3p2
2a2d1q
2ad3rec
ad2r4
ad1re
ad4res
ad3ru
2a2d1s2
ad3s1t4
ad3s1z
a2d2t1
ad4te
ad4t3r4
2a1du
2a1e
ae2b
ae2c
ae2d
a2e1k
a2e1la
a2e1le
a2e2o3
ae2p
3a2e1r2o1
aes5t
a2e1t
a2e1w
ae2x
a1f1a
a2fak
a2fan
a3f2ar
af4a1t
a2fa2u
2a1fe
a2f1e2c
a2f1e4n1t
af1e4r1l
a2f1ex
af2f3l2
a2f1f
af4flu
2a1fi
2af3l2
afo1s
a1fo
a2fö
af3ra
af2r2
af3rä
af3re
af3rö
af3s2a
a4f1s
af2sp2
af2t1a
a4f1t
af2tei
af1te
af4t3e4r1l
af2t3r4
af4t5re
af2tur
af1tu
a2f3ur
a1fu
a1g
2aga
ag1a1b
ag1a2d
ag1ar
ag1a2u
ag2di
a2gd
ag2d3r4
ag2d1u
age1i
a1ge
age4n1a
a3gen
age4n1e1b
age1ne
a2ge4n1t
a4gen3tu
ag2er
age4r2al
age1r1a
2ag2e1s2
age2sa
age4s2el
age1se
age4si
ag2e2s3p2
ag3es3se
age6s1s
age4s3ti
ages1t2
ag3g3l
a4g1g
1agg4r4
3a2git
2a2gl
ag4la
a4g1lö
ag2n
ag4ne.
ag1n2e
a2g4nu
a2g3re
ag2r4
a2g3ri
ag4ro
ag1sa2
a4g2s1
ag4sam
ag4se4t
ag1se2
ag1s3p4
ag4s1p2o
ag3s3ta
ags1t
ag3s1t2e
ags4t2o1c
ag3s2to
2a2g1t
ag2t1h
a2gu2n1d
a1gu
agun1
2ah.
2a1ha
ah4a1t
2a1he
a2h1er3h4
ahe1s
a1h2i
ahi2n3
ahl3a2
a2hl
ah4l1ei
ah1le
ah4l3er3h4
ah3ler
ah2l2ö
ahl3s1z
ah4l1s
ah4n1a
a2hn
ahne1r4e2
ah1n2e
ah2n1t2
1ahor
ah1os
a2h3ö
ahr1a
a2hr
ah3r2e
ah3re4s3
ah3ri
ahr1ta4
ah4r1t
ahr6t3ri
ahr1t2r4
2a4hs
ah4t3s
a4h1t
a1hu
a2h1w
a1hy
a2ian3
ai1a
ai2d2s
ai1d
ai1e2
a2i1en3
a2if2
a2i3g4
a3ik.
a2i1k
a4i3ke
ai3ku
a2il
a2i2lo
a1i2n1d
a2i1n4e
a1i4n1g
ain3sp4
ai6n1s
2ais
ai2sa
a3i6s4ch.
ai1sc
aisc4h
a2i3s2e
ai1so2
a3iv.
a2i1v
ai1ve3
a3i2v1l2
a3i2vs
a1j
aje2
ajek1t4o
aje1k
aj2e4k1t
2ak.
1a2k4a1d
a1ka
2akal
2a3kam
2akar
ak4a1t
1a2k2a1z
2a2k3b4
2a2k3c
2a2k3d2
4a1ke
a2ke1f
ake4n2n
a2keu
2a1ki
2ak3l2
ak4li
4a1ko
2a1k2r4
4akra
ak3ra2u
3akro
2a2ks
ak3s1h
2ak1ta
a4k1t
ak5t1an
2ak4t3b2
2ak3t2i1k
ak1ti
ak2t3r4
ak5t4ri
2akt1s1t4
ak2t3s4
2a1ku
a2kun1
4a3kü
1a2k3z2
a1la
2a3la.
al1a1b
ala5c4h2
a2l1af
ala2g
al1a1ge
a3l1al
al1am
al3a1m4e
al2a1mi5
al3a2m1p
al1a1na
a2l1a4n1g
a2l1a6n1s
al1a2n1z
a2lar
a3l2ar.
a3l2a1re
al2a4r1m
al3a2r1r
ala4s
a2l1a1si
al1a6s1s
2al2a1t
al1a2u
a1l3a2u1g
a1lä
al1äm
alb3ein
a2l1b
alb3eis
al4ber3h4
al4b3e2r1w
al2b1l2
al2b3li
al2boh
al2b2r4
alb3ru
al4b3s
al2dä
a4l1d
al2d3r4
ald3s2t4
al2d1s
al3du
2a1le
3a2l1e2b
3a2l1e1f
a4l1eh
a2l1ei
a4l3ein
a2l1el
alen1
al3en2d1s
ale2n1d
a2le4n1g
a2le2p
al1e1p2o
a2l1e2r1f
a2l1er3h4
al1e4r1l
3ale4r1m
a2l1e4r3t
3a2l1e2r1z
a2l1e3s1k2
ale4t
al1e1ta
a2l1e3t2h
a2l1e2u
a4leur
3a2lex
alf4r2
a2l1f
3algi
a2l1g
al2g1li
2a1li
ali4e1ne
al2ie
al2i1en
ali4n3al
ali3n2a
al1i6n1s
a2l1i2n1v2
alk1ar
a2l1k
al1ka
1alkoh
al1ko
al2k3s2
alks4t4
al2la1b
a4l1l
al2l3a4r
al2l1a2u
al3le2n1d
al1le
all5er1fa
alle2r1f
al3l2es
1allgä
al2l1g4
alli5er.
al1li
all2ie
alli7ers.
allie4r1s
al2l1o1b
al1lo
3al2m1b2
a2l1m
2a1lo
a2l1o2b
alo2ga
al2o1g
al1op2e
a2l1o2r1c
a2l1ö
al2ös
3al1pe.
a2l1p
alp2e
1al1ph
al3sk4l2
a4l1s
al2s1k2
al5s6te2r1b
al2s1t2e
als1t
al2ster
al3su2n1
al1su
al2t1ak
a4l1t
al1ta
al3t1a2m
alt3e2i1g
al1te
al4t3e2r1f
al2t3re
al1t2r4
al3t1ri
al4t3r2ic
al2tro
alt2se
al6ts
alt4s2tü
alt1s1t4
a1lu
a1l2uf
a2lum
al1u2m1b2
a2l1ur
4a1ly
al2z1e2r4z
a2l1z
al2z1w2
2am.
2a1m2a
am1a1b4
ama1d2
ama3g
2a3mä
2a1m4e
4ame.
a2me1b4
ame2n1
ame1r2a
a2m2e1ri
am2e3ru
a4m4e3s1h
a3mes
a3me1t
a2me1w
2a2m1f4
a3mi.
a1mi
a3mie
2a3mir
a3mis
ami3ta
a3mit
ami3ti
2a4m1k4
2a4m1l2
2am2m1al
a2m1m
am1ma
am2m1ei
am1me
am2min
am1mi
2am4m1l2
am1mu2
a2mö
amp2fa2
a2m1p
am2pf
am3p2r2
2a2m2s
am3sa
am4s4c2hl2
am3sc
amsc4h2
am3s4t2r4
ams1t
1amt.
a4m1t
am2t1a
am2t1ä
am4tel
am1te
2amte1m
am4t3e4rn
am4tö
am2t3r4
am4t1re
am2tu
2a1mu
2a3na.
a1na
2a2n1a1b
a3n2a3c
an4a3di3
ana1d
a3n1ak
an1a2l1g
ana4lin
an2a1li
2anam
2anan
2a3na1s4
a3n1äs
a1nä
1a2n3b4
2anbu
an3c4h
a2n1c
2and.
a2n1d
an3d1ac
and4a4r1t
ande4l4s
an1de
ande2s1
an2dex
an2d3rü
and2r4
and4sas
an2d1s
and1sa
and6s1pas
and1sp2
andsp2a
and3s1t2e
ands1t4
and2su
2an1du
and1ur2
2a1ne
a2n3e2c
a3n4ee3
an2ei.
a2n1ei
a4n3e2if
an1e4k
3a4n1e2r1b
an1e2t2h
ane1t
1a2n1f
2an3fi
anf2t5s3
an4f1t
an3f2u
4ang.
a4n1g
3ange1b
an1ge
an2g1ei
an4g3e2r1f
an3ger
an4g3e4r1l
an4ge2r1w
an4g3e2r1z
2an2g1f4
2an2g1h
2angie
ang1l
an2gla
2ango
ang2r4
an2g3ra
4angs.
an4g2s1
ang4s3p2o
ang1sp4
1a2n1h4
2a3ni
an2i3d
ani5ers.
an4ie
anie4r1s
3a4nim
a4n1i6n1s
anin1
2an1j
2ank.
a2n1k
an2k1an
an1ka
an2kei
an3kl2
an4k1lö
an2k3no
ank2n2
ank2r4
an2k3ra
an2k3rä
an4k1t4
1a2n3l2
2an1mu
a2n1m4
2a4n1n
3an3na
an2n2a1b
3an1nä
an3n2e
a2n1o2d
a1no
a3n2ol
a2n1or
a3nos
a1nö
2anp2r2
a2n3p4
1a2n3r2
1an1sä
a6n1s
1an1sc
ans2en
an1se
an2seu
2an2s1k2
an3sk2r4
ans1p2a
an1sp4
1ansp2r2
an3s2z
2ant.
a2n1t
an2t3a4r
an1ta
1antá
1antei
an1te
3ante4n3n
an3t2en
an3t4h2e
ant1h
1an3t2h2r2
2an3to
1an1t2r4
ant3rin
an3t1ri
an2tro
1an4t3w
2a1nu
a3n2u3s
a1nü
1a4n1w
2an1we1t
2an4z3b4
a2n1z
1anzei
2an2z1g2
an2z1i4n
2an4z3s2
1anzü
2anzw2
an2zwi
2ao
a2o1i
a1op
a1or
a1os3
a2o3t2
a3ot.
a1ö
a1p
2ap.
2ap2a
2ap2e
a2pe1f4
a2pé
a2pf
ap2fa
a3pf2l2
a3phä
a1ph
a2p4h3t2
2a1p3l4
a2p2n
a2p2o1t
a1p2o
3ap1p3l4
a2p1p
ap3pu
2ap2r2
2a3pu
2aq
2ar.
a1ra
a3ra.
ar2a1b
a2r3a4b3t
ar2a3d2
a2r3al
a3r4a3li
a2r1a4n1g
a2r1a6n1s
a2r1a2n1z
a2r3a2p1p
ar2a1p
2a2r1ar
a2r1a2u
a1rä
1a2r1b
2arb.
4arba
ar2ba2u
ar2be1c
2arben
2ar1bi
ar2bl2
2arb2r4
ar2b1re
2ar4b2s2
2ar4b3t
2arb2u
ar2b3un1
1a2r1c
ar2dro
a2r1d
ard2r4
2a1re
a2r2e1a
a4r1e2f1f
are1f
a4re1g
a2reh
ar1e2hr
a2r1ein
a4re1k
a3ren
ar1en4se
are6n1s
are3r2a
ar2e2r1f
a2r1er3h4
a2r2e1ri
a2r1e4r1l
are3u
ar2e1w
2a2r1f
arf2r2
ar2f3ra
ar2gl
a4r1g
ar2g1n
2ar1h4
2a1ri
ar2i1a
ar2i3e4n
ar2ie
ari3e2r1d
ari3e4r1g
ar1im
a2ri2n3it
ari1ni
a2r1i2n1t
a3r2i3u2
ar2kal
a2r1k
ar1ka
ark3a4m1t
ark2am
ar2k1ar
ark3a2u1e
arka2u
ark3la1g
arkl2
ar2kor
ar1ko
ar4k1ri
ark2r4
ar2k1s4
ark3sa
ark3s1h
ark4t1re
ark2t3r4
ar4k1t
ar2les
a4r1l
ar1l2e
ar3m2ä
a4r1m
ar4me2r1k
ar1me
ar3m2or
ar1mo
ar2na4n
a4rn
ar1na
ar3n2e
2a1ro
ar1o1b
a2r1o2d
a2r1op
a2r1or
2a2r1r
ar2r3a1d
arre4n3
ar3r2e
ar2r1h4
arr3he
2ar1sa
a4r1s
ar4s4c2h2l2
ar1sc
arsc4h
ar1se3
ar3s2h
2ar3si
ar2s1t
ar3s2ta
ar3t2e
a4r1t
ar2th2e
art1h
ar3t2i
artin2
2ar1to
ar4t3ram
ar1t2r4
art3re
2ar4ts
2a1ru
ar1u1h
a2r1um
a2rü
2a2r1v
arw2a2
a2r1w
2a1ry
ar2zä2
a2r1z
2arze
1ar2z1t
ar2z1w2
as1a1la
a1sa
as3a4u
a2s1ä
a2s1ca
a1sc
a3sche
asc4h
a4schec
a3schi
asch3la
as4c2hl2
a2s4c2h2m
a3s4chu
4a1s2e
a2se1b
a2s3e2m
a3ses
4a2s1h
a3s2hi
asi4n2g
a1si
2a3s2is
a4s3ka3s
a2s1k2
as1ka
a3skop
as1ko
a2s1o2f
a1so
as1or
a2sö
a2s1p2
as2ph
as2pi
as2p2o
a3s1pu
as3s2a
a6s1s
as3s2e
as4s3ei
as3s2i
as2s1p2
as2s1t
ass3ti
as3s2t2r4
as3s1t2u
2as3ta
as1t
a1s4tas
as4ta2u
as3t2e
a4s2t1h
as3ti
as3to
a1s4tof
2as2t2r4
ast3rä
as6t3re
a2sü
as1wa2s
a2s1w
as3w2a
3a2sy1l1
a1s4y
a1ß
a1ße2
aßen3
2a1t
a1ta1
at1a1b
at2af
a3t4a1g
a2t1a4k1t
ata3l
a3tam
a2t1a2pf
ata1p
at1a2u
a2ta2us
a2t1ä
a4t2c
a1t2e
4a3te.
a2te1b
at3e2i1g
a2t2e1li
4a3ten
a2te1p
ate4r3s2
at2e2ru2
4a3tes
at2h
a2t3h4a
4ath2e1
3a4t2h3l
4a3ti
ati4l4s
ati2s3t
a3tis
3a4t3m2
4atm4us
at1mu
ato4man
a1to
at2om
ato1ma
4ator
a2t1o4r1t
a2t1ö
4a1t2r4
2a1t1r2a4t
at3rä
at3re
at3r2om
at2sa
a2ts
at4s2c2hn4
at1sc
atsc4h
at2se
at4se1t
at2si
at2s1o
at2s1p2
at3ta
a4t1t
at4t1ak
at2t3a4n1g
at4ta2u
at2tei
at1te
at3t4hä
att1h
at2t3rä
at1t2r4
at2t3s
a3t2u1b
a1tu
atu2n1
a3tü
atz1er
a4t1z
at4ze2r1k2
at4ze2r1w
at2z1in
at2zo
at2z3t2
at2z1w2
a2u
2au.
2a2u1a2
2a2u1b
au2b1li
aub2l2
au2b2lo
4a2uc
auch3t1a
auc4h
au2c4h1t
au2d2r4
a2u1d
2a2u1e
aue2b
au5e2r1ein
au2e1re
aue2s
au2fa
auf1an
2aufe.
au1fe
2aufeh
auf1er
au4fe2r1k
a2u2f1f4
3au2f3n2
2aufs.
au4f1s
2auft.
au4f1t
2a2u1g
4augeh
au1ge
4a2u1i
au2is
2au1j
aule2s
au1l
au1le
au3lü
4aum
au2mal
au1ma
au2m1o
au2m3p2
au2m3s6
4aun1
au3n4a
au3n2e
au2n2i1o
au1ni
au1nu
a4u2n1z
a2u1o
2aup2
aup4ter
au2p1t
aup3te
2au3r2
au2s1ah
a2us
au1sa
ausan8ne.
aus4a4n1n
aus1an
ausan3n2e
a2u2s1a4u
2au1sc
au4s4c2h2m
ausc4h
au4scho
1au4s3d2
au2s3e2r3p4
au1se
au4s3e2r1w
3au4s3f4
1au4s3g2
1au2s1l2
au2so
au2sp2r2
au1sp2
1au4s3r4
au6s3s2
3aus3sa1g
aus1sa
aus4se.
aus1se
au4ste4r6m
aus1t
aus1t2e
au2ster
aus5t1ri
aus3t2r4
1au2sü
1au2s1z
2aut.
a2u1t
au2t1äu
au1tä
2au3te
au4te4n4g
au3te2n1
au4t3er3h4
1au1to1
2au2ts4
2a2u1u2
2au1w
2a2u1x
2a2u1z
auz2w2
2a1ü
2a1v
a3v4a
av4a3t4
4avi
a2v2r
2a1w
awi3e
a1x
ax4a2m
ax1a
a1x2e
2a1ya
a1yeu
ay1e
ays4
ay1si1
ay3t2
2a1z
az2a
az2o
az2u1
ä1a
ä1b
ä2b3l2
ä4b2s
ä1che
äc4h
äch4e1e
ä1chi
ä2c2h3l2
ä2c2h2r4
äch2sp2
ä4c4hs
äch4s1t
ä1chu
ä1c4k
ä1ck2e
ä1d
ä2da
ä2d1i1a
ä1di
ä2d2r4
ä2d2s
2ä1e
äf2f3l2
ä2f1f
äf3l2
äf3r2
ä4f2s
äf2t4s3
ä4f1t
ä1g
äge1i
ä1ge
äg2e3s2
ä2g3l
äg2n
ä2g3r4
äg4ra
äg3s3t2r4
ä4g2s1
ägs1t
1ä2gy
äh1a
2ä3he
ä3hi
ähl1a
ä2hl
äh1l2e
äh4l3e4be
äh3le1b
2ä2h1m
äh3n2e
ä2hn
äh3ri
ä2hr
2ä4hs
2ä4h3t4
ä1hu
ä2h1w
ä1im
ä1is.
ä3i6s4ch.
äi1sc
äisc4h
ä1i2s1k2
ä1j
ä1k
ä2k3l2
ä2k3r4
ä1la
älbe2
ä2l1b
äl2bl2
2ä1le
äl2l1a
ä4l1l
ä2l2p3
äl4s4c2hl2
ä4l1s
äl1sc
älsc4h
ä1lu
äm2i3en
ä1mi
2ä4m1l2
ä2m2s
äm1t2e
ä4m1t
2än.
än5de
ä2n1d
än2d2r4
2ä1ne
ä3ne2n1
ä3n2e1s
ä2n2f5
2än1ge
ä4n1g
än2gl
än2g2r4
äng3se2
än4g2s1
2ä3ni
änk2e
ä2n1k
än2k3l2
än2k2r4
än2k2s
än3n4e2
ä4n1n
2ä6n1s
än2s1c
än3se3h
än1se
ä1on
ä1p2a
äp2p3l4
ä2p1p
äp2p2r2
äp2s1c
ä2p1s
äp4s1t
1äq
ä2r3a2
är4af
är1ä
ä2r1c
4ä1re
ä2r1ei
äre2n
ä2r1e1ne
är2g2r4
ä4r1g
ä2r1i2n1t
ä1ri
är2k3l2
ä2r1k
är2k2s
är4me4n1t
ä4r1m
är1me
är2m2s
är1o2
ä1rö
är1se2
ä4r1s
är4si
är2s1t
är1t4e
ä4r1t
är2t1h
är4t2s3
ä2rü
1ä2r1z
är2zw2
ä5s4e
äse3g2
äser4ei
äse1re
äs2e4ren
ä3s2e1r2i
ä3se3t
äskop2
ä2s1k2
äs1ko
äskopf3
ä3s2k2r4
ä2s1p2
äs6s1c
ä6s1s
äs1s2e
äs4s3e2r1k
äs2s1t
ä4s3t2
äs4t2r4
ä3su
ä1ß
äß1e2r1k
ä1ße
ä4t1a2
ä3te
ät2e1i
ä2t1ein2
ä3te2n
ät2h
ät1o1b
ä1to
ä2t3r4
ät2sa
ä2ts
ät2sä
ät4s4c2hl2
ät1sc
ätsc4h
ät4sc2h2r4
ät2s1i
ät2s3l2
ät2s1p2
ät2s3t4
ät4t2r4
ä4t1t
ät2zw2
ä4t1z
äu2b2r4
ä2u1b
ä2u1c
äu1de3
ä2u1d
äu3el
ä2u1e
ä2uf
äu1f2e
1ä2u1g
äu2g3l
4äu1l
2äum
äu2ma
äu2m4s5
ä2un1
äu3n2e
äu1nu
2äur
2ä3us.
ä2us
äu4s4c2h2m
äu1sc
äusc4h
äu3se
ä3u4s3g2
ä3u2s1k2
ä3u2s3n4
äu2sp2
äus2s1c
äu6s1s
1ä2u1ß
äu2t2r4
ä2u1t
4ä1v
1äx
ä1z
â1t
á1n
ba2b1l2
ba1b
2b1a4b1s
bach7t4e
ba4c4h1t
bac4h
ba4c2ks4
bac4k
b1a2d2r4
ba1d
2b1af
3bah
bah2nu
ba2hn
b2ais2
ba2ka
ba2k1er
b4a1ke
b2a2k1i
b2ak3l2
b2a1k2r4
b4a2kra
3bal
ba1l2a
bal4l3eh
ba4l1l
bal1le
bal6le4r1g
bal3t1h
ba4l1t
2b1am
ba1n2a
3b2a2n1d
ban2d2r4
b2a3n2e
b1a4n1g
ban2k1a
ba2n1k
ban4kl2
ban2k2r4
2b1a2n3l2
2b1a6n1s
ba2n3t
b1a2n1z
b1a2r3b
bar3de
ba2r1d
ba2rei
b2a1re
ba3r2en
ba4r3n
bar3z1w2
ba2r1z
3bas
ba3s2a
ba2sc
ba2s1t
b2a2u3g
ba2u
ba2u1s
bau3s2k2
bau3sp2
ba1yo
3b2ä1c
b2är
b2äs
4b1b
b3be
bben3
bbe6n1s2
bbe4p
bb3ler
bbl2
bb2le
bb2lö
bb4r2u2c
bb2r4
bb1ru
b4b2s
bbu1
2b1c
2b3d4
bde1s1
b1de
3be.
3b2e1a
be3an
be3ar
be3as
3be1b
b2ebe
1be1c
be2del
be1d
be1d2e
be1di4
be1eh
b4ee
be2e2r1k
be1e4r1l
be1e3ta
bee1t
3be1f4
be3g2
2b1eier
bei1e
be2i1f4
bei4ge.
be2i1g
bei1ge
be2i1k4
be4il2
bei3la
2b1ei1me
b2ein
be1i2n1d
be1i2n2h4
bei3sc
be2i1s2e
bei1s3t
bei2t2s
be2it
3be1k
3bel
be3la4s
be1la
be3le2c
be1le
b2e3lei
be2l1en
be2le1t
b2e3li
bel3la
be4l1l
bel3s1z
be4l1s
be4l3t4
1be1m
1ben.
ben3ar
be2n1a
ben3d1or
be2n1d
be3n1ei
be1ne
3be4n3g
be3n2i
be4n3n
ben2se
be6n1s
ben4sp2a
ben3sp4
ben4sp2r2
bens1t4
ben2su
2ben4t3b2
be4n1t
b2en1ti
ben5t4r4
b1en4ts
2b1en4t3w
ben3u2n1
ben1u
be2n3z2
b2e1o
be1r1a
ber3am
be2ran
b4e3r4ei.
be1re
be4r3e2i1w
be4r1e2r1k
b2erer
b2ere4s
ber6gan.
be4r1g
berg2an
b4e3r4in.
b2e1ri
be3r3i6s1s
ber2is
ber3na
be4rn
b1er2n1t
be2r1o2b
be1ro
be3r1o2p
ber3s2t4a
be4r1s
bers1t
be3r1u4m
b2e1ru
3be1s
be1s2a
be2s1er
be1se
be3s1lo
be2s3l2
bes2p2o
b2e1sp2
bes3s4e
be6s1s
b3es6st.
bess1t
bes3s1z
be6s2t1ein
bes1t
bes1t2e
bes2te2i
be4s3t2ol
bes2to
be3s4ze
be2s3z
3be1t
b3e2ta1p
be1ta
be3th4a
bet2h
be1ur
3b2e1w
2b1ex
1be1z
2b5f4
bf2al2
b1fa
2b1g2
b1ge3
bg2e1s4
2b5h2
bh2u1t2
1bi
bi3ak
bi1a
b2i1b2
bibe2
bie6n3s
b2i1en
bie2s
bi1k2a
b2i1k
bi2ke.
b4ike
bi2kes
3bil
bi1l2a
bi2l1a2u
4b1illu
bi4l1l
bi2lu2
2b1i2n3b4
b2i1n2e
2b1i2n3f
bin3gl
bi4n1g
2b1i2n1t
b2i2o1
bio3d
bi3on2
bi1r2i1
b2i3se
b1i1so
bi3s2ol
bi2sp2
bis2s1c
bi6s1s
bi2s5t
b2it.
b2i1t2a
b2i1te
bi2tu
b2i3t2us
b2i1z2
4b1j
bjek4t2o
bje1k
bj2e4k1t
2b1k4
bl2
2bl.
bla3b4
b3la1d
b2la2n1c
3bl2a1t
b2la4t1t
2b3l2a1w
b2le
3bl2e2a
b3le1b
2b3le1g
2b3l2e2i1d
b3lein
3ble1m
3ble4n
b3le1s2e
ble3s3z
b4le1t
b3le2u
2blic4h
b1li
bl2i1c
3b4lic4k
b2l2ie
2b3l2i1g
bli4n1g4
b4lis
b2lit
3bl2i4t1z
b2lo
b4l2oc
b3los
2b1lu2n1
3bl2u1t
3blü
2b1m
4b3n2
b1ni2
b3n2is1
b2o4a2
bo5as
b1o1b3
bo2b1l2
bo2b2r4
b2o2c
bo3c4h2
bo3d2
b2o1e2
bo2e3i
2b1of
bo3fe
bo1is
b2oi
bo2l1an
b2ol
bo1la
3bon.
bo2n1d1
bon2de
b2o2ne
3bo6n1s
b1op
bo1r2a
bo4rä
bor2d1i
bo2r1d
b2or2d3r4
bo2rei
b4o1re
bo4r2i1g
bo1ri
b2o4r2s2
b1o4r1t
bor2t3r4
bo2sc
b2o4s3p2
bote3n4e
b2o1t
bo1te
bo3ten
b4o3t2h
bot2s1t4
bo2ts
bo2xi
bo1x
bö2b3
2b1öf
b1öl
2b1p2
bpa2g4
bp2a
2b1q
b2r4
2br.
b4ra.
2b3r2a1d
b4rah
b4ra3k
bra1s1t4
br2as
3brä
brä4u
2b3re.
b1re
3br2e1a
6b5rech1te
br2ec4h
bre2c4h1t
2b3re1f
2b3re1g
b3re2if
3bre1m
2b3re1p
b4rer
2b3rie1m
b1ri
br2ie
bri2er
2b3r2i1g
b4r2i1o
b3roh
2b3r2ol
b4r2on
b4r2uc
b1ru
br2u4s
bru2s1t3
bru2t3h
b3r2u1t
3b1rü
4b1s
b2s1a1d
b1sa
b3s2a2n1d
bs1an
b2s3ar
b3s2a1t2
b3sä
b4s1är
bs2äu
b5sc
bs2ca
b6schan
bsc4h
b6s1che1f
b2s4cu
b3se.
b1se
bse2b
b3s2el.
bse2n1
b4s1e2r1f
bs3e4r3in
b3s2e1ri
b4s1e4r1s
b3s2es
b3s2i4t
b1si
bs2ku
b2s1k2
b4s1l2
b2s1of
b1so
bso2r
b2sö
b2s2p2l4
b1sp2
b3s2pu
b6s1s2
bs2t
bst1a2b
bs2ta
bs2t3ac
b2st1ak
bs3t2ät
b3s2tä
b2st3er
bs1t2e
b2s3tip
bs1ti
b3s2to
b4s3to3d
b3s2tö
b2s3trä
bs2t2r4
bs3t4re2u
bst1re
bs4t1ri
b3s2tü
b4s4t1üb
b2s1u2n1
b1su
4b3t
btal3
b1ta
bt2as2t3r4
b3tas
btas1t
b5te
b4t1h
btil4
b1ti
b1t4r4
b4ts2
b2tü1
bu2chi
b2uc
buc4h
b2u2e3
bu2f
bu3li
bu1l
bul2l2a
bu4l1l
2b3u4m1k4
bu4n1g4
bun1
b2u4r1g
bu3r4i
bu2sa
b2us
bu4s3cha
bu1sc
busc4h
bu4s4c2hl2
bu4s4c2h2m
bu4s4c2hw
bus1er
bu1se
bu2sin
bu1si
bu2s1p2
bu2s1u
b2ü1c
büge3l3e
bü1g
bü1ge
büg2el
2b1v
2b1w
3by1
by3p
bys2
2b3z2
bze2it1
1ca
2c1a1b
ca2c4h
c2a2e3
ca3g4
ca1h
ca4l3t
3cam
c4an
c2a2p2e
ca1p
3car
ca4r3n
car1ri1
c2a2r1r
ca3s2a3
cas3t
ca3t4h
c2a1t
ca1y2
cä3
cäs2
2cc
c1ce
c1c4h2
c2d2
c3do
2cec
c2e3co4
ce2d2r4
ce1d
2ce1f
ce1i
2ce1k
1cen
ce4n3g
1cer
ce1re3
c4e3s1h
1ce1t
2ce1ta
ce1u
1cé
2c1f
c4h
4ch.
2cha1b
ch3a2bi
cha2c4k
2chaf
2ch1ak
ch2a2n3b4
3cha2n1c
ch1a4n1g
ch3ans1t
cha6n1s
4cha2n1z
1ch2ao
4ch2ar.
1ch4a3ra
3char1ta
cha4r1t
cha2sc
ch2as
3cha1to
ch2a1t
4cha1tu
ch1ä4r1m
ch1äs
1châ
2c2h3b2
2c2h1c
2c2h3d4
ch3e4b2en
che3be
che1b
1che1f
3ch2ef.
che4f2er
ch2e1fe
3ch2e1fi
3che4f1s2
4chei
ch1e2im
4ch1e2le1m
che1le
che4ler
4ch1en4ts
che4n1t
4c2h3en4t3w
cher3a
che3rei
che1re
6cherge1b
che4r1g2
cher1ge
cher6zie
che2r1z
ch1e6s1s
2che1ta
che1t
2ch1e4x
1ché
2c2h3f4
2c2h3g2
2c2h1h2
1c2h1i1a
2ch2ic
chi3na
chi2n
4chi2n1d
3chi3n2es
ch2i1ne
2ch1i2n3f
2ch1i2n1h4
ch1i6n3s2
ch1i2n1t
2ch1i2n1v2
1chi1ru
ch2i2r
2ch1j
2c2h1k4
2c2hl2
ch2le
ch3lein
ch2lu
4c2h2m
2c2hn4
chn4e3r8ei.
ch1n2e
chner3ei
chne1re2
2cho1b
cho2f
ch1o2f1f
ch1oh
ch1o2r1c
2c2h3p2
c2h2r4
4ch1re
ch3re3s1
ch3r1h4
1ch4r2on
4c4hs
ch4sper
ch1sp2
chsp2e
2c4h1t
2chuf
2chu1h
2chum
2ch1u2n1f
chu2n1
2ch1u2n1t
4ch2ü
2c2h1v
4c2hw
1chy
2ch1z
c2i1c
ci2s
c1j
c4k
4ck.
c1k1a
1c3ka.
2cka1c
1ck2a1g
2ckal
2ck3an
cka4r1
2cka2u
c1k1ä
2c2k3b4
2c2k3c
2c2k3d2
1cke
4ck1e2f1f
cke1f
2ckeh
ck1e1he
4ck1ei
4cken1se
cke6n1s
ck1e4n1t
4c2k1en4t3w
cke2r1a
ck2e1re
6ck3erge1b
cke4r1g
cker1ge
ck1er3h4
4ck3e2r4h2ö
4ckerke
cke2r1k
ck2e4rn
2ck1e2ro
2ck1e2r1r
4ckerze
cke2r1z2
2ck1e1se
2c2k1e2x
2c2k3f4
2c2k1g2
2c2k1h4
1cki
2ck1i1d
ck1im
ck1in
3ckis
2c2k1k4
2ck3l2
2c2k1m
2ck3n2
c1k1o2
2c2k1p2
2ck3r4
4c2ks
ck4stro
cks1t4
ck1s2t2r4
2c4k1t
ck1t2e
1cku
2ck1um3
2c2k1u2n1t
ckun1
2ck1up
2c2k1v
2c2k1w
1cky
2c2k3z2
c4l2
cle1t4
c1le
c1lo1
1clu
c2m2
3co
c2o2c
co3c4h
co2d2
co3di
co2f1f4
c2oi2
co1it
co2ke
c2o1k
co2le
c2ol
co3l2o
com4te.
c2om
co4m1t
com1te
com3tes4
con3n2e
co4n1n
co2p2e
co1r1a
co2r3d
c4o3re
cos3t
co4te
c2o1t
cô4
2cp
2c1q
1c4r2
c1re2
cre4me2s
cre1m
cre1me
c1ry2
2cs
c1s2a
c2si
c1s4t2r4
cs1t
4c1t
ct4e3e
c1te
c1ti2
c3t2i4o
ctur6
c1tu
3cu
cu2p3
cus1si4
c2us
cu6s1s
1cy
2c1z
3da.
d2a1a
2d1a1b
3d2ab1ä
da2ben
3d2ab1l2
da2b1re
dab3r4
d2a3b4rü
2d1ac
d2ac.
dach3a
dac4h
da2cho
da4c4h1s
4d3ach1se
d1af
d1a1g
dagi2
da2h3l
da1ho
3d4ai
da1in
d2a1is
da1l2a
2d1a2lar
da2l3b2
da3l1ö
d1a4l1t
d1am1ma
da2m1m
2d1am3mä
da1mo3
d4a2m1p
dampf8e2r1f
dam2pf
damp1fe
2d1a4m1t
d2an.
2d1a1na
dan4ce.
da2n1c
2d1a2n3d2
d1a4n1g
2dan1ge
dan4kl2
da2n1k
dan5kla
dan2k1o
dan2k2r4
2d1a6n1s
2d1an4t3w
da2n1t
2d1a4n1w
d2anz.
da2n1z
4danzi
2d1a1p
d2a1ph
4da2p1p
da2r3a
2d1a2r1b2
3d2a4r1l
dar2ma
da4r1m
dar2m1i
d2a2ro
d3a2r1r
d2a4r3s
d1a4r1t
d2a2ru
d2a2r1w
da1s
d4a3s2h
das4t
da1t2a1
d2a1t
da1t4e2
da3tei
d4a3te4n
4d3a4t3l
4d3a4t3m2
d2a2u3e
da2u
2d1au2f
2dauk
2d1a2us3
4dau2s1h
2d1äh
2d1ä4m1t
2d1ä2n1d
2d1ä4n1g
2d1äp
2d1ä2r1z
dä2u
dä3us
2d1b4
db2u2c
2dc
d1c4h
dco4r
d3co
2d1d2
ddar2
d3d1h2
d5do
1de
de2a1d
d2e1a
de3as
de3a2t
de3b4
2d1e4b2en
3de1c
de4ca.
d2e3ca
de2c1k1a
dec4k
d4e1e4
2d1e2f1f
de1f
de1g2
de3gl
de1he2
de3ho
2d1e2hr
d1ei
d2e2ic
3d2e1im
dei2n2d
dei6n2s
de2l1a4g
de1la
de4l3a2u1g
dela2u
del1än
d2e1lä
de2l1e2c
de1le
de4l1e2i4g
d2elei
de3l1ein
2d1ele1k
2d1e2le1m
2d1el2f3m2
de2l1f
del3le2
de4l1l
del4le1b
del4lei
de2l1o1b
d2e1lo
de2lop
de3l1or
de2lö
del4s1an
de4l1s
del1sa
del5sc
del2s5e
del2so
del2s1p2
del5ster
del2s1t2e
dels1t
de4l3t4
dem2ar
de1m
d2e1ma
2d1e2m1p
d2en.
de2n1d2
de4n3e2n1d
d2e3nen
de1ne
4d1e2ne4r1g
de4n3g
d2e2n1h4
de2ni
den4k3li
de2n1k
denkl2
4d1en4se1m
de6n1s
den1se
den4sen1
den6s5ta2u
dens1t
dens2ta
den3t1h
de4n1t
2d1en4t3w
de1n1u
de1on
d2eo
depi2
de1p
d4er.
der1a2b
der1a
de1r2a1d
de2r2a1p
der2bl2
de2r1b
2d1er2d1b4
de2r1d
de2r1e2b
de1re
de4r1e2c4k
der3e1di
dere1d
de4r3eis
d2erer3
de3r4e2r1b
de3r4e2r1f
de4r3e1ro
de2r1e4r4t
4d3erhöh
der3h4
de2r1h2ö
3d4e3r2ie
d2e1ri
de2r1i2n4f
4d1erklä
de2r1k
derkl2
de4r3m2
4derne2u
de4rn
der3ne
4d3er3s2a1t
der1s2a
de4r1s
der3ta2u
de4r1t
der1ta
der6t5e2n6d
der3te2n1
der1te
dert4ra
der1t2r4
d2e3ru
de4ru1h
de4r1u4m
des1
de2sa
de3sac
desa4g
de4sam
des2äc
de1sä
de2se1b
de1se
de4seh
de2sei
des3e4l1t
des2el
de2sen1
de4se1t
de2sin
de1si
de2sor
de1so
d2e2sp2
de6s3s2
des2t5a4l1t
des1t
des2ta
de2s2to
dest5r2a1t
de1s2t2r4
de4st1re
des4tum
de1s1t2u
de2su
de1t2
dete4n4t
de3te
de3t2en
2d1e4t3w
d2e1un1
de1u4r1l
de3us
d1e2x2is1
de1xi
2d3e4x1p
2d1f4
2d1g2
dga2
d2ge.
d1ge
dge4t1e
dge1t
d3gl
2d1h2
dh2a1s4
d2his
1di
di4a1b
di1a
di2a1d
di4am
3d2ic
di1ce1
di2e
di3e2d
die4n1e1b
d2i1en
di2e1ne
di3e1ni
di3ens.
die6n2s
die2s3c
die1t3
die2t2h
dig2e4s2
d2i1g
di1ge
di1k2a
d2i1k
di4l2s5
2d1i2m1b2
di1n2a
2d1i2n1d
2d1i2n3f
2d1i2n1h4
2d1i2n1it
di1ni
4d3inner
di4n1n
din1ne
2d1i6n1s
2d1i2n1t
di2o1b
d2i1o
di3o6n3s3
dion2
di1p
di4re.
di1r2e
di2ren
di2r2is
di1r2i
2d1i4r1l
di2sp2
2d1i4s3r4
dis3t2
di2s5t2e
di2ta
di4te4n3g
di3te4n
di1te
di4t3e4r1l
di4t3e4r1m
di4t3e4r1s
di2t1h
di4t3r4
di2t3s
di2tu
d2i5v
d2i3z2
2d1j
2d1k4
4d1l2
d3la
d3le
dl2e2r1a
dl2i2f
d1li
d2l3m
d4l3s
2d3m2
4d5n2
d1ni2
d3n2is1
d1o1b
d2oba
2dob2e
dob4l2
d2ob2r4
2d1o2f
do1le4
d2ol
do4l1l2
do2mar
d2om
do1ma
d2o5n4a
don4i1e
d4o3ni
do2o
2dop2e
2d1opf
d2o2p1p
d2o3r4a
2do2r1c
2do2r1d
dor2f1a
d2o2r1f
dor2fä
dor2f2l2
dor2f2r2
2d1o4r1g
do2r2ie
do1ri
d2o2r1p2
2do4r1t
dor2ta
d2os.
do6s3s
dos1t1
d4ot6h
d2o1t
do3un1
do2u
d1ö
dö2l1
3d2ör
dö2s1c
2d3p2
2d1q
d2r4
3d4ra.
2d3r2a1d
dra1g4
2d3r2a2h1m
d3r2ai
3d4ram
d3ra2n1d
2d3ras1t
dr2as
2d3r4a2uc
dra2u
2d3r2ä1d
d4räh
2d3rät
2d3rä2u
4d3re.
d1re
d4rea.
dr2e1a
d4re3as
3d4rec4k
2d3re1g
3d4reh
2d3re2ic
d4re2i1v
4dre1m
4d3ren
2d3re1p
4d3rer
4d3r4es.
d4re1s2c
2d3r1h4
d3ri
d4ri.
3d4ri1a
2d5r2ic
d4ri1d2
d4r2ie
d5rie1g
d4r2if
d4r2i1k
d4ril
d4rin.
3d4ri1sc
dr2is
3d4r2it
4d5ri1tu
d3ro1b
d3r2o1c
2d3ro1d
d4r2oi
2d3r2o1t
d3ro2u
2d3ro1v
d3rö
drö2s1
d5r2u1b
d1ru
3d4r2uc
2d3ru1h
drun1ge3
drun1
dru4n1g
2d5r2u1t
d2r1ü1b
d1rü
drü5cke
dr2üc
drüc4k
2d1s
d4s1a4m1t
d1sa
d3sam
d2s1an
ds3as3s2i
dsa6s1s
d2s1a4u2
ds1än
d1sä
4d4s3b4
d4schi2n
d1sc
dsc4h
d2s1e2b
d1se
d2s1e1f
d3sei
d2s2e2i1g
d4sei6n1s
ds1ein
d2s1e4n1g
d2s1e4n1t
d2s1e2r1f
d2s1er3h4
d2s1e2r1k
ds1e2r1r
d2s1e2r1z
d3se4t
d4s1e1ta
d3s2h2a
d2s1h
d3sho
d2s1im
d1si
d2s2i2n3f
d3s2kan
d2s1k2
ds1ka
d3sku1l
4d2s1l2
d2s1op
d1so
dso2r
ds1o1ri
d2sö
d2s1par
d1sp2
dsp2a
ds1pas
d2s2pä
ds2p2o
d2s1p4ro
dsp2r2
ds2pu
d6s1s4
ds1t4
ds3ta1b
ds2ta
d4s3t2a1g
d4s3tä1ti
d3s2tä
dst2ät
d2s1t2e
d4st2e2a2
d3s2te2i
d3ste4l1l
d1s2tel
d4ste1m
d3s4te4rn
d2ster
ds2ti
ds4til
ds4tip
ds2t2u
ds1u2m1s
d1su
d2su2n1
ds2zen
d2s1z
2d1t
dta2d
d1ta
d5t2e2a2
d1te
d2t1h
d4t3hei
dth2e
d1t3ho
d1to2
d3tö
d1t3r4
dtran2
d2t5s2
d3tü
1du
du1a2l1v
d2ua
du1ar
dub3l2
d2u1b
du2b1li
du2f
2d1u1fe
2d1u1h
d2u1i
2d1u2m1b2
2d1u2m3d2
2d1u2m1e
2d1u2m1f4
2d1u4m1g2
2d3u4m1k4
2d1u4m3l2
d2u2m1p
2d1u2m3r2
d1u2m1s
d2ums.
2d1u2m1v
2d1u2n3d
dun1
d1und2a
2d1u2n1f
du4n1g4
dun3ke
d2u2n1k
dun2kl2
2d1u2n3r2
du6n2s2
2d1u2n1t
d2u1o
dur2
2d1u4r1l
2dur1sa
du4r1s
du4s2c2hn4
d2us
du1sc
dusc4h
du4sc2h2r4
du4s4c2hw
dus3t
2d1üb
3düf
3d2ün
2d1v2
2d1w
dw2a2
dwe4s1t3
dy2s
2d1z
2e1a
e3a2b
eab3l2
ea2c
ea3der
ea1d
ea1de
ead1li4
ea4d1l2
ea2d2r4
ea2g4
e2a3ga
ea4ge
e2a3gl
eak1
ea4k1t2
ea2la
e3a2l1ei
e2a1le
e4al2er.
eal1ti2
ea4l1t
eam3
ea1m1o
ea2na
e2a1no
e3ar.
ea2ra
e3a4re1ne
e2a1re
ea3ren
e3a2r1r
e3a2r1v
e2as
ea6s3s
ea1t4e2
e2a1t
eater1
e3at2h
e4a5t2r4
ea2t3s2
e3a4t5t4
e3au2f
ea2u
e3a2u1g
eau1s1t
ea2us
e1ä2
e1b
2eba
e3b2ak
2ebe1d
ebe2i
2e3bel
eb2en
eben2s3e
ebe6n1s
ebe4r1t4
2e3be1t
2ebl2
eb3ler
eb2le
eb4le2u
e3b2l2ie
eb1li
eb3lo
eb2lö
2eb2o
eb2o1t2
ebö2s
2eb2r4
eb3rei
eb1re
eb4ru
e4b2s1
eb6sche
eb5sc
ebsc4h
eb1se2
ebs3p2a
eb1sp2
eb3s2ta
ebs2t
eb4s3t2ät
eb3s2tä
eb2s3te1m
ebs1t2e
eb4s3t2h
eb3s2t2r4
e3bu
eb2u2t1
2e3ca
e1ce
ech1ä
ec4h
2e3che
e4ch1ei
e6ch5erzi
eche2r1z
e1chi
e2c2h3l2
e4c2h3m
e2c2h3n4
e2cho.
e2ch1o2b
e2c2h3r4
ech3t1a
e2c4h1t
ech3t4ei
ech1te
e1chu
e2ch1u1h
e4c2h1w
e1ci
eci6a
eck3se
ec4k
e4c2ks
2e2c4k1t
2e1c4l2
2e3co
eco3d2
e3c4r2
e2c1s4
2e4c1t
e1d
e3d2a
ed2d2r4
e2d1d2
e1d2e
ede2al
ed2e1a
e3d1ei
ede3n2e
ede6n1s1
eden4se
eden4sp4
ede2r
ede4r3t2
ed2i4al
e1di
edi1a
e3d2o
ed2ö
ed1s2ä
e2d1s
ed2s1es
ed1se
ed2s1o
ed2s1p2
ed2s3t2r4
eds1t4
ed2su
ed2u2s
e1du
e3dy3
4ee
e2e3a2
e2eb2l2
ee1b
ee2ce
ee1c4h
ee2cho
ee2c4k
ee1d2e3
ee1d
ee2d3s2
4e4e1e
e1e2f1f
ee1f
e2ef4l2
ee4f3s2
ee1g2
e1ei
ee1im
eein4se
eei6n1s
ee1l2e
e1e2le1k
ee3len
e1e2m1p
ee1m
e1en
ee2n1a2
ee4n2a1g
e2e1nä
e2e2n1c
ee3ni
e2e1no
ee6n3s
e1e2pi
ee1p
ee1r1a
e1er4b3t
ee2r1b
e1e2r1d
ee3r2e
ee4r3e4n4g
e2eren
e2ere2s
ee4re1t
e1e2r1k
ee1r1ö
eer2ös
ee4r1t2
e1er1t2r4
e2e3r2u
e1e2r1z
ee3s2
ee2s3k2
ee3ta
ee1t
ee4t4a1t
ee2t2h
ee1u2
eewa4r
ee1w
e2ew2a
e1e2x
e1f
2ef.
2e1fa
e2f1a1d
ef1a1na
ef2ar
e2f3a1t
e2f1äu
e1fä
2e1fe
e3fe.
e2f1e2b
ef1e1m
e2f1e4n1t
ef2er
2eff.
e2f1f
1ef1fi
ef2f3l2
2e1fi
e2f1i2d2
e2f1i6n2s
e3fi2s
1efku
e4f1k4
2ef2l2
e3f4lu
2e3f2o
e3fra
ef2r2
ef3r2e1a
ef1re
ef3r2ol
ef3r2om
ef4rü
e4f1s2
ef3s1o2
ef3sp2
ef2t1an
e4f1t
ef1ta
2e1fu
e2f1um
2e1fü
e1g
egas3
e2gd4
e3ge
ege4n3a4
e3gen
ege2r1a
ege4s2t2r4
eg2e1s2
eges1t2
ege1u
e2g1lo
e2g1n
eg3ni
eg4sal
e4g2s1
eg1sa
eg4s3e4r1
eg1se2
eg4s2to
egs1t
eg2t1h
e2g1t
2e1gu
egu4n1g4
egun1
eg2us3
2e1ha
eh1ac4h
e3h2al
eh2a2us
eha2u
2e1hä
e1he
eh2ec
e2h1e2f1f
ehe1f
eh2el
ehe4n6t3
1e2he1p
e3her
ehe1r1a
ehe3s5t2r4
ehes1t
e1hi
eh1i2n1t
ehi2n
eh1lam
e2hl
eh1lä
eh1le2
ehl3ein
eh4le4n1t
eh5l2er
eh2lin
eh1li
eh3lo
ehl2se
eh4l1s
2e2h1m
eh3mu
e1ho
e3h2ol
ehr1a2
e2hr
ehr1ä
eh2r1e2c
eh1re
eh2rei
ehr3e4r1l
ehr6er1l2e
eh3re3s1
eh3ri
eh1ro2
eh2r1o1b
eh2r1of
e4hs2
eh3s1h
eh1s1t2e
ehs1t
2e4h1t
e1hu
e2h1u2n1t
ehu2n1
e1h2ü
eh3üb
e2h1w
e1hy
2ei3a2
4e2i1b
ei2bar
ei2bl2
eib2u4t
ei4b3u3te
ei2cho
e2ic
eic4h
e2i1d
ei2d1a
ei3d2an
ei3de
ei4d3e2r1r
2ei4d5n2
ei3dra
e2i2d2r4
ei1e
4e2i1en3
eien1ge4
ei3e4n3g
1eif3r2
e2if
ei3g2a
e2i1g
4eige1no
ei1ge
ei3gen
eig2er
2eige1w
ei3gl
1ei2g3n
2eig1ru
eig2r4
2ei2g1t
2ei1gu
eik2ar
e2i1k
ei1ka
ei3k1a2u
ei3k4la
ei2k3l2
e4il
2eil.
ei2lar
ei1la
ei2l1a2u
2e6i2l1b
ei4l3d
ei4l1ein
ei1le
eilen1
ei2l3f4
eil3i6n1s
ei1li
2ei4ln
1eilzu1
ei2l1z
ei2m1a4g
ei1ma
eim3a4l1l
ei2mor
e2i1mo
e1i2m1p
eim2p2l4
ei2n1a
ei4na4s
ei4nä
e2in3d2r4
ei2n1d
2ein1du
ei4n1e4n1g
e2i1ne
ei3nen
ei2n1e2u
2ein1f2o
ei2n3f
e1in4fo.
e1in4fo1s
ei4n3g2
e1in4ha1b
ei2n1h4
e1i2nit
ei1ni
ei2n3k
e3in6ka4rn
ein1ka
3ein3k2om
ein1ko
e2i2n1o2
3ein3s2a1t
ei6n1s
ein1sa
ein6stal
e1ins2ta
eins1t
ein4s2z
e4in3ver1
ei2n1v2
ein1ve2
e2i3o2
ei1p
eip2f
2eir
ei3r2e
e1i2r1r
e2is.
ei2sa4
ei6sch3w2u
ei1sc
eisc4h
eis4c2hw
ei4s3e2r1w
e2i1se
eis2p2e
ei1sp2
ei4s4t1h
eis3t
ei1s2to
ei2sum
ei1su
e2it
ei2ta1b
ei1ta
ei2t1an
ei2tar
2e4i1tä
ei3te
ei2t1h
ei2tro
ei1t2r4
ei4t1t4
ei2t3um
ei1tu
2e2i3u2
2e1j
e1k
e1k2a
1e2k3d2
e3ke.
e3ken
e3kes
e3key1
e3k2l2
ek4n2
e1k2o
ek4r4
ek1s4t4
e2ks
2e4k1t
ekt4a2n1t
ekt1an
ek1ta
ekt3e2r1f
ek1te
ek3t3e4r3g2
ek4t3e2r1z
ek1t2o
ek5t1ri
ek1t2r4
ek2u
e3k2w
e1la
e4l3a4ben
ela1b
el3a1bi
el2a4b3t
ela2c
e2l1af
ela2h
e2l1ak
e2l3a2m
el4a1mi
el4a2m1p
e2l1a6n1s
el1a2n1z
2e3l2ao
e2l1a1p
e2l1a2r
el3a1ri
ela4s
e2l1a1si
e2l1a2s1p2
el2a2s1t
2e1lä
3elbis
e2l1b
el1bi
el2da
e4l1d
eld5ers1t
el1de
elde4r1s
el4d3e2r1w
el2d3s2
2e3le.
e1le
el2e1a2
e2le2c
2e3leh
2elei
e6l5eier.
elei1e
e2l1ein
e3l2e2i1ne
e4lei4n3g2
1ele1k
e2l1el
1e2le1m
e3lem.
e4l1e2m1p
2e3l2en.
e4len1se
ele6n1s
e2l1e4n1t
e3le1p
el1e2r1d
el1e2r1f
e4ler4fa
e2l1e4r1g
el1e2r1k2
el1e4r1l
e4ler4l2a
e4l3er1nä
ele4rn
e2l1e2r1r
2eles2
el1e6s1s
e4l1e4ta
ele1t
e3le2u
2e3l2e1v
ele2x
1elf.
e2l1f
el3fe
elf4l2
1el2f3m2
1el4f1t
elgi5er.
e2l1g
elgi5e4r1s
2e1li
e2l1i1d
e3l2ie
el2i2ne
el1i1t2a
el3k2l2
e2l1k
el3la2n
e4l1l
el3le
el5le.
ell3ebe
elle1b
el4l3ein
ell3eis
el3lin
el1li
ell3sp2
el4l1s
el1m2a
e2l1m
2e4ln
el5na
2e1lo
e2lof
e2l2ol
elon2
el1op2e
e2l1or
elo2ri
el2ö2f
e1lö
elö2s
el2s1um
e4l1s
el1su
elte2k
e4l1t
el1te
elt3e4n3g
el3t2en
3elte4rn
el1to2
el2t3r4
elt3s2k2
el6ts
elt3s2p2
2e1lu
e2l1um
e2l1ur
e2l3u1se
el2us
e1lü
e2lya
e1ly
2e2l1z
elz2e
el2zw2a
elz1w2
e1m
2e1ma
e2m1a1d
ema2k
e2m3a2n1f
e2m1a6n1s
3e2ma2n1z
em2d3a2
e2m1d
e3m2en
e1me
emen4t3h
eme4n1t
e6ment1s2p2
emen4ts
e2m1e2r1w
e3me2s
1e2me3ti
eme1t
e2m1im
e1mi
emi5na
em1i2n1t
emi3ti
e3mit
2e2m1m
em2m1a3u
em1ma
em2m1ei
em1me
e2mop
e1mo
3em2pf
e2m1p
em3p2f2l2
em2p3le
em1p2l4
em2sa
e2m1s
em2sp2r2
em1sp2
em2s1t
e4m3t2
1emu1l
e1mu
2e1mü
e2n1a
4e3na.
2e3n2ac
e3na1d
e4n1af
4e3n2ah
e4n1ak
en2a3l2i
4enam
e3n4a1m4e
e4n1a2n1d2
e4n3a4n1g
en3a1re
en1ar
en2a1sc
e3nas
4e3n2a1t
e4n3a4t1t
e3n2a2u1e
en1a2u
e2n1är
e1nä
e2n1äu
en4ce.
e2n1c
en3d2ac
e2n1d
en2dal
en4d3e6s3s2
en1de
endes1
en2d4o4r1t
end1or
end3r2om
end2r4
end3si
en2d1s
end3s2p2
end3s1z
end2um
en1du
2e3ne.
e1ne
ene4b2en
ene1b
e2n1e2c
e2ne2f1f
en1e1f
e4nein
e2n1ei
e2n1e2l
e2n1e4le
2ene1m
2e3nen
e4n1e4n1t
e5n4en1t2r4
4e3n2er.
e2n1e2r1d
e2n1e2r1f
1e2ne4r1g
e4n1er3h4
e4n1e2r1k
e2n1e4r1l
e4n3er1mo
ene4r1m
4ene4rn
e2n1e2r1r
e2n1e4r1s
e2n1e4r1t
e2n3e1ru
e2n1e2r1w
e4n1e2r1z
2e3n2es
e4n3e6s1s
e2n3f
en1f2a
en2f2u
1eng1a1d
e4n1g
3enga1g
enge3r1a
en3ger
en1ge
en3g2i
en2gl
en3g1lo
1en2g1p2
eng3se2
en4g2s1
e3ni.
e1ni
e3n2ic
e2n1i1d
e3n4ie
eni3er.
eni5ers.
enie4r1s
e2n1i4m
e2n1in1
e3n2i1o
2e3n2is
e3nit
2en2i3v
en3k2ü
e2n1k
e2n1o2b
e1no
en2o3b4le
e3nob1l2
e2n1of
en1oh
e3n2ol
eno2ma
en2om
en1on
e2n1op
e2n1o2r
enos1t3
e3n2o1t
e3n2o2w
2e1nö
e2n1ö2d
e4n3r2
en3sac
e6n1s
en1sa
en2s1a4u
en5sch4e
en1sc
ensc4h
en2s1e1b
en1se
ens2el
1ense1m
ensen1
en3s1ka
en2s1k2
en3sp4
en2s2p2o
ens2t5a4l1t
ens1t
ens2ta
en4s3t2ät
en3s2tä
2ens2to
e4n1t
en3t4a1g
en1ta
1en4t3d4
en2te2b
en1te
en4te2r1b
en3ter
1ent1fa
en4t1f4
3entga
en4t1g2
en2thi
ent1h
3entla
en4t3l
1en4t5n4
en4t3r2ol
en1t2r4
3entsp2r2
en4ts
ent1s2p2
2en3tü
1en4t3w
4ent1we1t
1en4t3z2
en1u
2e1n2u1t
e1nü
enü2s1t
e3nüs
4enwü
e4n1w
e1ny
en4z3e2r1f
e2n1z
en4z3e4r1g
en4z3e2r1k2
enz3e4r1t
e1ñ
2eo
e1o2b1
e1of
eo2fe
e1oh
e4ol
e1on.
e1o2n1d
e1o2n3f2
e1o2n1h4
e1o2n3l2
e1o2n3r2
e1o6n1s
e1op2e
e1opf
e1o2p4t4
e1or
e3or.
e3o2r1b
e3o4r1s2
e3o2r1w
eo1s2
e3os.
eo3u1l
eo2u
e1o1v
e1ö2
e1p
e3p2a
epa2g4
e3p2f4
1e4pi1so
e3pis
ep3le
e1p2l4
1e2p2o1c
e1p2o
ep2p2a
e2p1p
ep4p3l4
ep2p2r2
ep1t2a
e2p1t
ep2tal
e3pu
ep2u2s
e1q
er1a
e3ra.
e3r4ad.
er2a1d
er3a2d3m2
era1f4a
er2af
era1f2r2
era2g
e1r2ai
e2r3a2ic
e2rak
e1r2al
er3a4l1l
era2n3d
e3r2a1ne
e2r3a2n1f
e2r1a2n1h4
e2r3a2n1m4
e1r2a1p
e2r3a2pf
e2r1ar
e3r2a1ri
e1r2as
e2r3a4si
e2ra2ß
e2rat2h
e1r2a1t
e3r4a3ti
e2r3a4t3m2
e1r2a2u1b
era2u
er3a2u1e
e2rau2f
e2r3a2u1g
e1r2a1w
e1r2a1z
e1rä
e2r1äh
e2r1äm
erb2e
e2r1b
er3b2r4
erb4sp2
er4b2s
e2r1c
er3c2h3l2
erc4h
er3da
e2r1d
1er2d1b4
er3de
2er3de1c
erd3e2r1w
4e3re.
e1re
er1e1b
e3r2ec4h
e4r3e4c4hs
er1e2c4k
e2re4dit
ere1d
ere1di
e4r1e2f1f
ere1f
e2r1e2h
4e3r2ei.
e2r1e2i1g
e2r1ein
e4r3e2is.
ere2l
er1e1l2e
2e3re1m
2eren
4e3r4en.
e3r2e2n1a
e4r1en1se
ere6n1s
e4r3en4t1f4
ere4n1t
e4r1en4t5n4
e3r2e2n1z
eren8z7e2n1d
2erer
4e3r4er.
e2r3e2r1f
e2r1er3h4
e4r1e4r1l
4ere4r2n
e3r2e1ro
er1e2r1r
er1e4r1s
e2r1e4r1t
er1e2r1w
2eres
e2r1e6s1s
er3e4ti
ere1t
e2r1eu1l
ere2u
e2r3e4vi1d
er2e1v
er1f2e
e2r1f
erf4r2
4erfü2r
er1fü
3erge4b3n2
e4r1g
er1ge
erge1b
4erg2e1hä
erg3e4l4s3
erg2el
1erg2ol
er4g3s1
ergs4t
er3h4
1erha1b
e2rha
2erh2ü
2e1ri
e2ri3a1t
eri1a
e3r2i1b
4e3r2ic
4e3r2ie
er2i3e4n3
e3r2i3k4
4e3rin.
er1i2n3b4
e2r1i1ni
e2r1i2n1k
e2r1i2n1t
e3r2i1o
er1i1ta
er2it
2erk.
e2r1k
1erklä
erkl2
2erk1re
erk2r4
er4k3t
3erle4b3n2
e4r1l
er1l2e
erle1b
erm2e6n4s
e4r1m
er1me
er3m3e4r1s
ern1os
e4rn
er1no
e1ro
e3ro.
er3o3a2
er1o2b
e2r1of
er1oh
e3r2on
e4r3o1ny
e2r1o2p
e4r1o2r
e3ros
e3r2o1w
er1ö
erö2d
2er1ök
e2r3p4
er3rä
e2r1r
2er3r2ü
er1s2a
e4r1s
er3se
er3s2i
er3s1k2
er3s1mo
er2s3m2
er3s3n4
er3s2p4
er3s1z
ert2ak
e4r1t
er1ta
er6t3erei
er1te
erte1re
er4t3e2r1f
er4t1e4r1s
er2t2ho
ert1h
4er1ti
ert3i6n1s
ert1s2e
er4ts
2e1ru
eru4f4s3
e3ruf
e2r1u4m
e2r1u2n1d
erun1
eru4n1g4
er1u6n1s2
e4r3u1z
e2r1ü4b
e1rü
3erwec4k
e2r1w
6erweis
erwe2i
e2s3a1b
e1sa
es2ac4h
e2s3ak
es3a2n1z
es1an
e3s2as
e4s3a1to
e3s2a1t
2e4s3b4
e1s2c
es3ca1p
es1ca
e3s1ce
esc4h2
e3scha
e2s3ein
e1se
es2el
ese4ler
ese1le
es3eva
es2e1v
2e4s3f4
4e2s1h
es2har
esh2a
es2hu
es2i1d
e1si
esi1er
e3s4ie
e2sil
e2s3i2n1t
es2ir
es2k2a1t
e2s1k2
es1ka
e4ske
es3k4l2
es3ku
e4sky
e2s3l2
es4l2o1g
es1lo
2e2s3m2
es2o4r1t
e1so
e3s2o1t
e1s2ö
2e1sp2
e3s2pe1k
esp2e
e3spi
e3s2por
es1p2o
e3s4pra
esp2r2
2e4s3r4
es2s1a4u
e6s1s
es1sa
es3sc
es3se
4esse1m
ess4e3re
ess3e4r3g
2es1so
es2sof
es2s1p2a
es1sp2
es2s1pu
es3s2t2r4
ess1t
es3s1t2u
e2s2t1a4b4b
es1t
es2ta
esta1b
e2st1ak
e1star
e4s2t1a2r1b
1e2s3tas
e1st2a1t
e1s2tec
es1t2e
e3s2tel
es4t3e4n3g
e4s3ten
es4t3er3h4
e2ster
es4t3e6s3s2
e4s3tes
e1s2til
es1ti
e2s3tip
estmo6de
e4s4t3m2
est1mo
est3m2o1d
e2st3o1ri
es2to
e1s2t2r4
es4t1ri
es3t4rop
e1s1t2u
e1s4tü
e2s1um
e1su
es3u2m1s
e2s3w
e3s4y
e2s3z
e1ß
eße3r2e
e1ße
e1t
eta1b4
e1ta
e2t1am
3eta1p
et4a1t
e2t1äh
e1tä
e3te
e4t1ein
e3t2en
ete2n3d2
et2e2o
ete2r4h2ö
eter3h4
eter4t2r4
ete4r1t2
et2h
et3hal
e2th4a
e2t3h2ü
e3ti
eti2m
eti2ta
2e3to
eto2b
e4t1of
eto6n1s4
e3ton
e3tö
2e1t2r4
e4t3r4aum
etra2u
e6t3rec
et1re
e2tres
et4r2i1g
e3t1ri
ets4c2h3w
e2ts
et1sc
etsc4h
et1s2p2
et3s1u
et1t1a
e4t1t
et2t1a1b
et2t3a2u
et2tei
et1te
et3te4n1
et2t1h
et2t3r4
et4tro
ett3s1z
et2ts
et4t1um
et3tu
e3tü
etwa4r
e4t3w
etw2a2
2e4t1z
et2z1ä2
et4z3e4n1t
etze4n1
et3ze4s
et2zw2
e2u1a2
eu3erei
e2u1e
eu2e1re
eue6re2if
eu2e1s2c
eu2ga
e2u1g
eu4ge4n1t
eu1ge
eu3gen
eu3g2er
eu4gla
eu2g1l
eu4g2s4
euil4
e2ui
eu1in
1euk
eu2kä
e1um
e3um.
e3u2m1b2
e3u4m3l2
e3u2m2s
eum4sc
eum1s1p2
eum3s1t
2eun1
eu3n2e
eu4n1ei
e3u4n2g
eu2n2i1o
eu1ni
eun3k1a2
e2u2n1k
e2u1o2
eu1p
e2u1r2e
3e4u3ro
eu3sp2
e2us
eus1t4
eu1s2ta
eu1s2to
eu1s3t2r4
2e2u1t
eut2h
eut6s2c2hn4
eu2ts
eut1sc
eutsc4h
2e2u1x
eu2zw2
e2u1z
e3ü
2e1v
e2ve3la
e1ve2
e2ve4n1t
4ever1
ev2e5r2i
e3vo
e1w
2ew2a
e3w2ä
e1wä2s
2ewe
e2we.
ewin1de3
ewi2n1d
e3wi4r
ewi2s
e3wit
e2w2s
2ex.
ex3a1t
ex1a
1e2x1e1m
e1xe
ex1er
e1xi
e2x1in
1ex2is1
e2x3l2
3e4x1p
2ext.
e4x1t
ex2tin
ex1ti
ex2t1u
2e1xu
2e3xy
ey1
ey4n
eys4
e1z
e3z2a
e2z1e4n3n
e3zi
ezi2s
ez2w2
é1b
é1c
é1g
égi2
é1h
é1l
élu2
é1o
é1p
é1r
é1s
é1t2
é1u2
é1v
é1z2
è1c
è1m
è1n
è1r
ê1p
ê4t
1fa
fa1b4
f1abe
fa2ben
f1a4b5s
3fac
fa4che1b
fa1che
fac4h
fa4che2r5f
fa2ch1i
fa2cho
f1ader
fa1d
fa1de
fa2d2r4
f4ah
fa2i1b4
f4a2ke
f2al
fa3l2a
fal2k2l2
fa2l1k
fal6l5e2r1k2
fa4l1l
fal1le
fal6scha
fa4l1s
fal1sc
falsc4h
fal6s4c2h2m
fal3te
fa4l1t
fal6t2s
2f1a2n3b4
2f1a2n1f
fan2g2r4
fa4n1g
2f1a2n1k
2f1a2n3l2
f1a2n3p4
2f1a2n3r2
fa6n3s
2f1a4n1w
f1a2n3z
2f1a1p
f2ar
f2ar2b2r4
f1a2r1b
2f3a2r1c
3f2a1ri
far4r3s
f2a2r1r
3f4a4r1t
2f3a2r1z
fa3s4a
f4a3s1h
f3a1t
fa2to3
2f1auf
fa2u
f3a2u1g
f1au4s3b4
fa2us
3f4a1v
fa2x1a
fa1x
1fä
fä1c
fäh2r1u
fä2hr
2f1ä4r1m
fä2ßer
fä1ß
fä1ße
f1äu
2f1b2
2f1c
2f3d4
fdi2e2
f1di
1fe
fea1tu4
f2e1a
fe2a1t
fe2c
f2ec4h
2f1ec4k
fe2d2r4
fe1d
fe2ei
f4ee
fe1e1m
f2ef4l2
fe1f
feh4lei
feh1le2
fe2hl
f4ei1e
2f1ei4n3g2
4f1ei2n1h4
fe1i1ni
2f1ei4n1w
f1eis
fek2ta
fe1k
f2e4k1t
fe2l1a
fel2d3r4
fe4l1d
2f1e2le1k
fe1le
fe2l1er
f2e2les2
f2e2l1o
fel4soh
fe4l1s
fel1so
fe4l3t
f2em.
fe1m
f2e2m4m
2fe2m1p
fe2nä
fe4n3g
fe2no
fen3sa
fe6n1s
f1e4n1t
f2er.
fe1r1a
fer2an
fe4ra4n1g
fe4r3a2n1z
fe2ra2u
fer3de3
fe2r1d
f2e1re
f2er2er
fe2r3e2r1z
f1er1fa
fe2r1f
f2erl.
fe4r1l
4ferne2u
fe4rn
fer3ne
f4er3p4a
fe2r3p4
f2ers.
fe4r1s
f2e4r1t
f1e2r1w
fe2s1t
fes2t1a
fes2t3e2i
fes1t2e
2f1e1ta
fe1t
fe4t2a1g
3fe3te
fet2t3a
fe4t1t
feu2e1r3e
fe2u1e
feu4ru
3fe1w
f1ex
2f3e4x1p
3fe1z
1fé
2f1f
ff3ar
f1fa
ff1a2u
f1f2e
ff4e2e
f2f3e1f
ff3ei
ffe1in
ffe2m
f2f3e1mi
ff4en
f2f1ex
2f2f1f4
ff3l2
ff4la
f1f4lä
ff4lo
f3flu
f3f4lü
f3f4rä
ff2r2
ff3ro
ff3rö
f4f2s
ff3sho
ff2s1h
ffs3t
ff3s4t4r4
4f3g2
fg2e3s2
f1ge
2f1h2
1fi
3fi.
fi3a1t
fi1a
fi1d2
f2i1en3
fi1e2r2f
fi2k1in
f2i1k
fi3k3l2
fi1k1o2
fi2ko1b
fi2k2r4
fi2l1an
fi1la
fil4auf
fil1a2u
fi4l3d
fi2les
fi1le
fi2l1g4
fi3li
fi4lin
fi2l2ip
f2i1na
fi3ni
fi6n2s
fin3sp4
2f1i2n1t
f2i2o
fi3ol
fi2r
fi3r2a
3fis
fi1s2a
fisch3o
fi1sc
fisc4h
fi1s2p2
fi2s5t
fi1t1o2
fi2tor
fi3tu
3f2i1z
2f1j
4f1k4
f2l2
2fl.
f3la1d
f3la1p
1flä
3f4lä1c
2f5lä1d
f3län
2f3läu
2f3le1b
f1le
f4l4e2e
2f3lein
f3ler
f4lé
f3li.
f1li
3f6lim
fl2i4ne
2f5lon
f1lo
1f4lop
1f4l2o1t
fl2o2w
f3lö
f4l2uc
1f4l2u1g
flu4ger
flu1ge
f4lü
2f3m2
fma2d
f1ma
2f3n2
f3n2i2s
f1ni
1fo
fob2l2
fo1b
2f1of
fo1l2i3
f2ol
f2o2na
f2o1n2e
fo2nu
2f1op
fo1r1a
4f3o4r1g
f4o3rin1
fo1ri
3f2o4r1m
for4m3a4g
for1ma
forni7er.
fo4rn
for3ni
forn4ie
for4s1t
f2o4r1s2
for4tei
fo4r1t
for1te
for2t1h
for2t3r4
for3tu
2f1o2x
1fö
2f1öf
2f1ök
2f1öl
fö4r2s
4f1p2
2f1q
f2r2
f4rac
frach6t3r4
fra4c4h1t
frac4h
f5r2a1d
fra4m
f3ra2n1d
f5r2a1p
1f4rän
2f3re.
f1re
f3rec
f3re1d
2f3re1g
fre2i1k2
fr1ein4
f3re1p
f4re2u
2f3r2ic
f1ri
fri3d2
fr2i2e
2f3r2i1g
1fr2is
f4ri1sc
f3r2o1c
1f4r2on
fr2o2na
fro2s
f3r2o1t
f3ru
f3rü
4f1s
fs1a4l1l
f1sa
f5s4a2m1m
f3sam
f2s1an
f2s3ar
f2s1as
f2sauf
fs1a4u
f2sa2us
f2sa2u1t
f3sc
f4s1ce
f4schan
fsc4h
f4s1che1f
f2s4co
fs1e2b
f1se
f4s1e2hr
f3seh
f2s1e1m
f2s1e4n1t
f2s1er
f3se4t
f4s1e1ta
f3si
f2si2d
f3s2kie
f2s1k2
f2s1o2
f3span
f1sp2
fsp2a
f2s1pas
fs1pen
fsp2e
f2s1ph
f3s2p2l4
f3s2por
fs1p2o
fs1p2r2
f2sp1re
fs2p1ri
f2s1p4ro
f3s2p1ru
f6s3s4
fs2t
f2s3tas
fs2ta
f4s3tä1ti
f3s2tä
fst2ät
f4st2ec4h
fs1t2e
f3s2te2i
f3s4tel
f3ste4rn
f2ster
f4s3t1h
f2s3tip
fs1ti
f3s2t4r4
f4s3tres
fst1re
f4s3tü1te
f1s2tü
f3st2üt
f2s1u2n1
f1su
f2sü
f3s4y
4f1t
f4ta.
f1ta
f2ta1b
ft1a2be
ft1af
f2t1al
ft1an
ft1ar
f3t2a1t
f2t1e2h
f1te
ft1e2i1g
ft1eis
f4t1e4n1t
f3ten
f4t1e4ti
f3te1t2
f2t1h
f4t3hei
fth2e
f1t3ho
ft1op
f1to
f3tö
f2t3ro
f1t2r4
f2t3rö
f3t4ru
f2t2s1
ft1sa4
ft4sam
ft3s2c
ft4sch2e
ftsc4h
ft1se4
ft4seh
fts3el
ft3s1t4
ft4s3tan
fts2ta
ft4s3tä
fts2ti
ft4s3t1ri
fts2t2r4
f2tum
f1tu
ft1u4r1l
f3tü
ftw2a4
f4t3w
f4t3z2
1fu
3f2u1g
3f2u1h
f1um
2f1u2n1f
fun1
fu4n1g4
2f1u2ni
fun2kl2
f2u2n1k
fun2ko
fun2k3r4
2f1u2n1m4
2f1u2n1t
f2ur
fu4re.
f2u1re
fus2sa
f2us
fu6s1s
fus2s1p2
fus2s1t
fu2ß1er
f2u1ß
fu1ße
3f2u1t
1fü
2f1üb
fü2r
2f1v
2f1w
1fy
2f1z
fz2a
fzei3te4n6
fze2it
fzei3te
fzei8t5e2n1d
fz2ö
fzu3
fzu4ga
fz2u1g
3ga.
2g1a2b5f4
ga1b
ga2b5l2
gab4r4
2g1a2b3z2
ga1c
2ga4d1l2
ga1d
2ga2d2r4
g2a1f3l2
ga3ge
ga1g
5gai
ga1k
ga2ka
ga1l2a
g4a1mo
2g1a4m1t
2g1a2n3b4
ga2n3d
gan2g1a
ga4n1g
4g3ange1b
gan1ge
gan2g2r4
2g1a2n1h4
2g3anku
ga2n1k
2g1a2n3l2
g3anla
3g2a1no
2g1a4n1w
ga1ny
2g1a2r1b
2g1a2r1c
3ga2r1d
2g1a4r1m
g2a3r2o
g1ar3t2i
ga4r1t
g2a3ru
2g1a2r1z
ga2sa
gas3ei
g4a1s2e
ga2si
ga2s1or
ga1so
ga3s1p2
ga4sp2e
ga4sp2r2
ga6s3s
g2as4ta
gas1t
gas5tan
ga4s3t2e
ga1s4t3el
ga1t2a1
g2a1t
2g3a4t3m2
g4a1t4r4
g4a2u1c
ga2u
2g1auf
g2auk
g1a2us
2g1a2u1t
2g1äp
2g1ä2r1z
gäs5
gä4u
2g1b2
gber2
g1bi2
gby4t2
g3by1
2g1c
2gd
g1da
g2d1a2u
g2d1er
g1de
gd1in
g1di
g1do
g1d1ö
g1d3r4
g2d3s2
g2d1t4
g1d1u
1ge
g2e3a2
g2eb2a
ge1b
gebe4am3
ge3b2e1a
g2eb4r4
ge1c
ge1d4
g4e1e2
ge3ec
ge2e3s2
ge1f4
ge3g2l
ge1g
ge1im
ge2in.
gei6n2s
ge2i2n1t
gei2n2v2
g2e1ir
ge2is
2g1e2i1se2
gei3s1h
g2el
ge4l1a2n1z
ge1la
gelb2r4
ge2l1b
gel4b3ra
gel6de4r1s
ge4l1d
gel1de
ge3le
g2e5leh
ge4l3e4r1s
ge4l1e6s1s
g2eles2
gell2a
ge4l1l
ge3l1or
g2e1lo
gels2t
ge4l1s
gel3s1t2e
gel3s1z
gel3t2a
ge4l1t
ge3l1um
g2e1lu
ge3lü
g2e2l1z2
ge3mi
ge1m
ge1m2u
3gen
ge3n1a
g4e4nam
ge4n1ar
gen4a2u1g
gen1a2u
gen2d2r4
ge2n1d
gen1e1b
ge1ne
ge3n1e2c
gen3e2i1d
ge2n1ei
g4en3e4rn
ge4n3g
ge4n3n
gen4sam
ge6n1s
gen1sa
gen3s1z
2g1en4t1f4
ge4n1t
gen3t1h
4g1en4t3w
ge1o2r
g2eo
ge1o2u
ge3p4
ge1r1a
ge2r1a1b
4g3e2r1e2i1g
ge1re
ge4re4n1g
g2eren
ge4re6n4s
ge4r3e4n1t
g2er2er
ge2r1i2n4f
g2e1ri
ge3r4i4n1n
ge2r1i2n4t
ge4r1m4
ger3no
ge4rn
ge1r2ö
ger4s2to
ge4r1s
gers1t
g2e3r2u
g1erw2a
ge2r1w
g2e1s2
ges3auf
ge1sa
ges1a4u
ge3s2c
ges3e4l1t
ges2el
ge1se
ge2s3er
ge3si
ge3s4pi
g2e1sp2
ges3s2t
ge6s1s
ges1t2
ge3s1t2e
ge4s3ter
ge4s3t1h
ge3t2a
ge1t
2g3eta1p
g2e5t2r4
ge3t4u
ge1u1l
ge1ur
2g1ex
2g1f4
4g1g
gg2a4t
g3ge
gge2ne
g3gen
g2g3l
gg4lo
g2g3n
gg4r4
2g1h
4gh.
gh2e
3g2he1t
3g2hi2e
g2h1l
3g2h2r
g2hu
g2h1w
gi3a1lo
gi1a
g2ial
gie3g
gi2e1i
gi2el
gi2e1n2e1
g2i1en
gie1s1t
gi2gu
g2i1g
gi2me.
gi1me
gi4mes
gi2me1t
2g1i2n1d
g2i3ne
g2in2ga
gi4n1g
2g1i6n1s
2g3isel
g2i1se
gi3t2a
gi3tu
gi4us
g2i3u2
2g1j
4g3k2
4gl.
g1la1b
g1lac
3g1la1d
g2la1de
2g1la1g
3gla2n1z
3g2l2a2u1b
gla2u
2g1lauf
3g4lät
2g1lä2uf
g2l4e
2g3le.
3gl2e1a
2g3le1b
g3lec
g3le1g
2g3leh
4g3lein
glei4t5r4
gl2e2it
g3len
4g5ler
2gles
g3le1s2e
g4li1a
g1li
2gl2i3b4
3g2li1d
3g2l2ie
2gl2if
g2l2i1k
4glin
g2l2i2o
2glis
4g3li1sc
3g2lit
g2l2i1z
3g2l2o3a2
g1lo
3g2lo1b
g3loc4h
gl2oc
gl2o3g
3g4l2o1k
g2l2om
3g2lop
3g2l2o1t
2g4l1s
2g1lu
gl2u2t
3glü
g2ly
2g1m2
g1n
2gn.
g2n2a
g4na.
2g3n2ac
g4n2a1t
3g2nä
g1n2e
g3neh
gn2e2t2r4
gne1t
2gne2u
2g4n1g
g2n4ie
g1ni
g2n2if
g4nin1
2g3n2i2s1
3g2no
gno1r
4g3n2o1t
2g2n3p4
2g6n1s
2g2n1t
2gnu
3g2n2um.
g2nü
g2ny
2g2n1z
g2o4a2
go2a3li
2g1of
2g2o1g
2g1oh
g2o1i
go1l2a
g2ol
2go3n2is
g4o3ni
2g1op2e
2g1opf
g2o1r1a
2go2r1d
2go4r1g
go2s1
go3s1t
g4o3t2h
g2o1t
got6t5e4r3g2
go4t1t
got1te
go1y
2g1p2
2g1q
g2r4
g1r4a2bi
gr1a1b
gra2b1l2
2g3ra4d1l2
gr2a1d
2g3rah
2g3rak
grammen6
gr2a2m1m
gram1me
gram8m7e2n1d
2g3rä2u
2g5re.
g1re
g4re1b
2g3rec
2g3re1d2e
gre1d
g4r4e2e
2g3re2ic
2g3r1ein
g3re2it
g4re1m
2g3re4n1n
gren6z5ei
gr2e2n1z
g4rer
g3re1t
g3r2e1v
2g3r2ic
g1ri
gr2i2e
g3rie1se
3gr2if
2g3r2i1g
2g3ri4n1g
2gr2o1c
2groh
gr2on4
g4ros
gros6sel
gro6s1s
gros1se
gro4u
2g3r2öh
g4ruf
g1ru
2g3r2ui
2g3rum
3g4rup
2g3r2u1t
2g3r2üc
g1rü
3g4r2ün
4g2s1
gsa4g
g1sa
g3s2ah
g4s3a2k
g3sal
g4s1a4l1t
gs3a1m2a
g3sam
gs3an
g2s3ar
gs3a2u1g
gs1a4u
g3s2c
g4s1ca
g4s3ce
gsc4h4
g4s1che1f
gs4chi
g4s3co
g4s3c4r2
g1se2
g3s2eh
g3s2e4il
g3s2el.
gs3e1li
g3s2e4ln
gsen1
gs3er
gs5e2r1k
g3se4t
g4se1ta
gsi2d
g1si
g3sil
g4s1l2
g1so2
g1sp4
g3s2pe1k
gsp2e
g3spi
g3s4pi4e
g4spin
g2s3p2l4
g3s2por
gs1p2o
gs1r2a1t4
g4s3r4
gs1rü2
g6s5s4
gs3ta
gs1t
g3stan
g3star
g3s4t4a3ti
gst2a1t
g4s3tä
g5s4täm
g3s2tel
gs1t2e
gst3e4n1t
g4s3ten
gst3e2r1r
g2ster
g3s2teu
gst2h2e
g4st1h
g3s2ti2r
gs1ti
g3s2to
gs3t2o1c
g4st2ol
gs3top
g4s3tor
g3s2tö
gs3t2r4
gst4ra
gs4t1r2a1t
gs3t4ri
gs4t3ros
g3s1t2u
g4stur
g1s3tü
gs4t2üc
g4s1w
g3s4y
2g1t
g3te
gti2m
g1ti
g1t4r4
g2t2s
g3tü
1gu
gu3am
g2ua
gu1an.
gu1a2n1t
gu1as
g2u1c
gu4d3r4
g2u1d
g2u2e
2gu2e1d
gue1t4
2g1u2f
2g1u1h
gu1i6n1s
g2ui
gu1is
3g4u2m1m
2g1u2n1f
gun1
g2ung.
gu4n1g
gun1ge2
4g1unge1w
2g1ungl
g2u6n4s2
2g1u2n1t2
2g1u4r1l
gur4t3s
gu4r1t2
gu2s3a
g2us
guschi5
gu1sc
gusc4h
gus4ser
gu6s1s
gus1se
gus2sp2
gus2s1t
gu4s1t
g2u2t
gu1t1a
gu4t3er3h4
gu3te
gut3h
2g1üb
gür1
güs3
2g1v
2g1w
2g3z2
3h2aa
h2ab2a
ha1b
hab2e
h2a4b1s
ha2cho
hac4h
ha2del
ha1d
ha1de
ha4din
h4a3di
h1ad3le
ha4d1l2
haf3f4l2
ha2f1f
haft4s3p2
ha4f1t
haf2t2s1
h1ah
h2a1k3l2
2h2al.
hala2n4c
ha1la
ha2l1a2u
hal2ba
ha2l1b
hal4bei
hal2b3r4
2h2a1le
hal2la
ha4l1l
hal6le2r1f
hal1le
h1a2l1p
hal2s1t
ha4l1s
hal4t3r4
ha4l1t
h1a4m1t
h2an.
h2a2n1d
han2d3s
h4a4n1n
2h1a2n3r2
2ha2n1t
h1a1p
h2a2p3l4
h2a2p2r2
h4a3ra
2h1a2r1b
h2a2r1d
h1arm.
ha4r1m
har4me.
har1me
har4m2es
har2t1h
ha4r1t
h1ar3t2i
h2as
2ha3sa
ha1si1
ha4t5t2
h2a1t
hau3f4li
ha2u
hauf3l2
2h1au2f3m2
h1au4k1t
hau2sa
ha2us
h2au2sc
hau4sp2a
hau1sp2
hau5s2te2i
haus1t
haus1t2e
hau6te2r1k
h2au3te
ha2u1t
2h1au1to1
hau2t2r4
h1ä2f1f
hä6s5ch2en
hä1sc
häsc4h
häu2s1c
hä2us
hä3u2sp2
2h3b2
hba2r3a
2h1c
2h3d4
hdan2
2h2e1a
he2a1d
he3be
he1b
he4b1e2i
h2e2bl2
h2e3b2r4
h2e5ch2e
hec4h
he1cho
h1e2c4h1t
he3cke
hec4k
he2d2g2
he1d
he3di
he2e3l
h4ee
hee4s2
he2fan
he1f
h2e1fa
he2fä
he2f1ei
h2e1fe
hef3e4r1m
hef2er
2he2f1f
he4f3i4n1g
h2e1fi
h2e2f3l2
he2f2r2
he3f1ri
h2e2fu
h2e3gu
he1g
h1ei1e
h1e2if
h1e2i1g
he2im
he1i2m3p
he2i4mu
he2i1ne2
h1ei2n3k
4he2i3o2
he1i2s3m2
he1is3t
hei2t4s3
he2it
h1e2i1w
he2l3a2u
he1la
he2l1e2c
he1le
h3e2le1k
he3len
hel3e4r1s
h2e3li
hel4l3a2u
he4l1l
hel4mei
he2l1m
hel1me
h2e3lo
he4lof
he2lö
3he2m1d
he1m
he3mi
3h2e2m1m
4h1e2m1p
h2en.
he4n3a4
he2nä
hen2d2s
he2n1d
he2n1e2b
he1ne
hen3e2n1d
h2e3nen
h1e2n3e4r1g
he2ne1t
he4n1g2
2he1ni
he2no
hens1t2
he6n1s
hen5t2r4
he4n1t
h1en4ts
2h3en4t3w
he2n3z
4h2e2o
he3on
he3op
he3ph
he1p
her3a2b
her1a
he2r2al
2he1r2a1p
he3r2as
her4b4s
he2r1b
he4r1e2c4k
he1re
4he2r1e2i1g
he4r3eis
he2re2l
he4r1e2r1w
h2erer
h1er2fo
he2r1f
h1er1fü
he4r1g2
he2r1i2n4f
h2e1ri
he6r1in6nu
he3ri4n1n
he2r1i6n4s
herin8ter
he2r1i2n1t
herin1te
h1erke
he2r1k
h3erla2u
he4r1l
her3l2a
2he4r1m
he3ro
he4r3o4b
h1er1ö
he4r1t2
her3t1h
her2zw2
he2r1z
he1s2ta
hes1t
he2s5t2r4
h3e2ta1p
he1t
he1ta
heter2
he3te
he3t2h
he3t2i
he3t4s
h2e2u
he2u3g
he3x
he1x4a
he1y2
1hè
2h3f4
hfe4l1l1
h1fe
hfel6ler
hfel3le
h3fi2s
h1fi
2h3g2
hge1t4
h1ge
2h1h2
2hi.
2hi1a
h2i2ac
hi2a4n1g
h2ian
hi1ce1
h2ic
hich6ter
hic4h
hi2c4h1t
hich1te
2hi3d
h2i1de
h1i4di
hi2e
hi3e6n2s
h2i1en
hi4e1r1i
hie4rin3
hie4r1s2
hif3f4r2
h2if
hi2f1f
hi2k2r4
h2i1k
hi2l3a4
hil2f2r2
hi2l1f2
hi2n
h1in1du
hi2n1d
hi3ne2l
h2i1ne
hi3n2en
h1i2n3f
h1i2n1h4
hi3n2i
hi4n3n2
h2i3no
hi6n3s2
hin4t1a
hi2n1t
2h2i1o
hi4on2
hi3or
2hip1
hi2ph2
h2i2pi
h2i2r
hi3r2a
2hi3r2e
hi3r2i
hi4rn1
hir4ner
hir3ne
hi3ro
hi4r2s
hi1s2a
h2i2se
hi2s3t
hi1t1h
hi3ti
2h2i3u2
h1j
2h1k4
2hl
h4lac
hla2n
hl1a2n1z
h1las
h1l2a1t
h1la2u1t
hla2u
h3lä1d
h1läs
h1läu
h2l1b4
h4l1d4
h3le1b
h1le
hl4e3e
h5l2en.
hle4n3g
hl2e4n1n
h3ler
hl2e2r1a
hl1e4r1g
h6l3er1nä
hle4rn
hle3run1
hl2e1ru
hl1e2r1w
h4l1e2r1z
h3les
h4le1si1
h3lex
h2l1g4
h2l2ie
h1li
h2l2if
hl1i2n1d
h2lip
h2lis
h3lis3t
h2lit
h4l1l2
h2l1m2
h2lo
h3l2oc
hl1of
hl1op
h4lor
hl4o2re
h3l2o1si
h1l2ö
h3l2ö1c
h2lös
hl2s1an
h4l1s
hl1sa
hl2ser
hl1se
hl3sku
hl2s1k2
hl3s1lo
hl2s1l2
h4l3t2
h3luf
h3luk
h1lüf
2h1m
h2m1a1b
h1ma
h3ma1g
h3man
h3mar
h4mäc
h3mä
h4mäh
h4mäl
h4mäu
h3me.
h1me
hm4e1e
hme1in
h3men
hm2e6n2s
hme2r1a
h2mo
h4m2on
h3mö
h2m3p4
h2m2s
hm3sa
hm1s1p2
h2mu
2hn
h2na
hn1a1d
h3nam
hn1an
h2nä
h2n3d4
h1n2e
hn3e2i1g
h2n1ei
hn3ein
h2ne2l
h3ne4n1
h2ne4p2f4
hne1p
hner3ei
hne1re2
h3ne4r1l
h3n1e2r1z
h2n3ex
h2n2ic
h1ni
h2n1i1d
h2n4ie
hn1im
hn1in1
h2n2ip
h2n3k4
h2nor
h1no
hn3s2k2
h6n1s
hn4ts2
h2n1t
h1nu
h2n2u1c
h2nu1l
hn1u2n1f
hnu2n1
h3nun1ge
h1n2u4n1g4
ho2b1l2
ho1b
ho2c4h3
h2oc
ho2c1k1a
ho1c4k
ho6ck2e4r1l
ho3cke
hock2er
ho2c4k3t
2ho1d
h2o1e4
ho2e1f
h2o4fa
h2o2f3r2
2h2oi
hol1a2u
h2ol
ho1la
4holdy
ho4l1d
3ho1le
ho2l1ei
ho2l3g4
4ho3lo
ho4lor
3ho4l3s
h3o2ly
3ho2l1z
hol6ze1ne
ho1m2e
h2om
ho2m2e3c
ho2me1d
h2on
ho1no3
2hoo
2hop
ho1r1a
ho2r3d
h1o4r1g
ho4s1ei
h2o3se
ho3s1l2
h2o2s1p2
ho4s1t
2hot.
h2o1t
h4o3t2h
ho6t5li4
ho4t3l
2ho2t3s2
3ho1v
2h2o2w1
h1o2x
ho1y2
1h2ö
h2ö2c
hö3c4k
h4ör
hö2s1
h3ö2s1t
2h3p2
h1q
2hr
hr1ac
hr3a1d
h1r2ai
h1r2a1ne
h3rä2u
h2r1c
h2r3d
h2rec
h1re
h3r2ec4h
h3re1d
h3re1f
h4r2ei.
hrei4ba
hr4e2i1b
h3re2ic
h4r3e2i1g
h3rel
h3r2en
h3re1p
hr2e4r1g
hr2e2r1k
h6rerle1b
hr1e4r1l
hrer1l2e
h2r2e4r1m
h2r2e2r1z
h3re2s1
hre2t
h2r1e1ta
h3r2e1v
h2r1f2
h4r1g2
h2ri
h3r2ic
h4ric4k
hr2i4e
h3rie2s3l2
h3rin
h4r2i3n4e
h4r1i2n1h4
h4ri2s3t
hr2is
h2ro1b
h3roh
h3r2ol
h4ro1me
hr2om
h4ro1mi
h4r2on
h2r1or
h3ro2u
h2r1r4
hr2s1ac
h4r1s
hr1sa
hr2s3an
hr2s1a4u
hr3s4c2h2l2
hr1sc
hrsc4h
hr2s1e2n1
hr1se
hr2ser
hr4se1t
hr4s1in
hr3si
hr2s3k2
hr4s1of
hr3so
hr2su
hr4s1w
hr2ta1b
h4r1t
hr1ta
hr2tan
hr2t1h
hr2t1or
hr1to
hr5t3ri
hr1t2r4
hr2tro
hrt2sa
hr4ts
hrt2se
h3ru1h
h1ru
hr1u2m1s
h2rum
h3rü
h4r1üb
h2ry
h2r1z2
4hs
h2s1ac4h
h1sa
h2s1an
h2s1a4u
h4schan
h1sc
hsc4h
h2s1ec
h1se
hse4ler
hse1le
h2s1e4r1l
h3s2ex
h2s1i4n1g
h1si
h2s1of
h1so
h2s1par
h1sp2
hsp2a
h2s1ph
hs2por
hs1p2o
h2sp4rä
hsp2r2
h2s1p4ro
h6s1s2
h1s2ta
hs1t
hs2t3a4l1t
hst2an
h2s3ta2u
h1stec
hs1t2e
h3s2t1ein
hs2te2i
h5ste4l1l
h1s2tel
h3s4te2r1b
h2ster
hst2h2e
h4st1h
h1s2ti
h1s2to
h2stor
h1s4t2r4
hst3ran
hs3t3ri
h1stu2n1
hs1t2u
h2s1u
hs2u4n1g
hsu2n1
4h1t
h2t1a
h3t4akt.
hta4k1t
h3tak2t3s4
h3t2al
h4t3a4l1t
h4t3a2m
hta4n
ht3a3ne
h3t2a2n1k
h3t2as
h4t3a6s1s
h4ta1s4y
ht3a2t
h2tär
h1tä
ht1e2c
h1te
h2t1e1f
h2t1eh
hte2he
h2te2if
h4tei2l1z
h3te4il
h2t1eim
h2t1eis
h4t3e2lit
ht2e1li
h2te2m1p
hte1m
h4ten4t1f4
h3ten
hte4n1t
h4t3en4ts
ht3er1fo
hte2r1f
ht3er1fü
h2t1er3h4
ht5erken
hte2r1k
h4terkl2
h6t3erne2u
hte4rn
hter3ne
h4t3er3r2e
hte2r1r
ht3er1sc
hte4r1s
h6t5ersp2a
hter3s2p4
ht3ers1t4
h6ters2ta
h1t6ers1t2e
h2t1e2r1z
h3te2s
h4t1e1se
h4t1e6s3s2
hte3s2ta
ht2es1t
h2t1eu
h2t1ex
h2t1h
h4t3hei
hth2e
ht2h2e3u
h4t2ho
h2t1in
h1ti
h1to2
h2t3o1ly
ht2ol
h2t1o4r1g
h3töp
h2tö
h4t3rak
h1t2r4
h2t3ra2n1d
h2t3r2a1t
ht6rau1me
ht3r4aum
htra2u
h4tre1f
ht1re
h3t4ri
h4t5rin
h2t3r2ol
h2t3ros
ht3rö
h4t1rös
h2t3ru
h2t3rü
h4ts
ht2s1o
ht2sp2
ht3sp1ri
htsp2r2
ht4sta1b
ht1s1t4
hts2ta
hts2ti
hts4ti2e
ht4s3tur
hts2t2u
ht4s3tür
ht1s2tü
h4t1t4
ht3ti2
h2t1u4r1s
h1tu
h3tü
h4t3z2
hu2b1a
h2u1b
hu2b3ei
hu2b1en
hu2b3l2
hu4b3r4
hu2bu
h2u1c
hu2h1a
hu1h
hu2h1i
hu1ko3
hu4k3t4
hu2l3a
hu1l
hu2lä
hu2l3ei
hu1le
hu4l3e4n1g
hule4n
hu4le4n1t
hu2ler
hu2le2t
hu2l1in
hu1li
hu2lo
hu3m2a
h1u2m1s
hu2n1
h1u1na
hun4g4s1
hu4n1g
hu3ni1
h1up.
h1u2p1s
2hur
hu4r1g2
hu3sa
h2us
hu2so
hus4sa
hu6s1s
hus2sp2
hu2ta1b
h2u1t
hu1ta
hu3t2h
hu2ti
hu4t2t
hut4ze4n1
hu4t1z
hut4z3er
h2ü
h4ü4b1s
h1üb
h3übu
hüh3n2e4
hü2hn
hüs3
2h1v
hvi2
hvil4
2hw
h2wa4l1l
hw2a
h1wal
hwe1c
h1w4e2i1b
hwe2i
3hy1g
3hy1p
hy2pe.
hyp2e2
2hy2t2
h1z
hz2o
hz2u1g4
hzu1
i1a
2ia.
i4aa
i2a1b
iab4l2
2iac
i2af
i2af4l2
i4a3g2
i2ah
i3ai
i2a1j
i2ak
i3ak.
i3a4k1t
2ial
i5al.
ia2l1a4
ia2lä
ia2l3b
ia4l3d
i3a2l1ei
i2a1le
i3ale4n1t
ialen1
i3a2l1e2r1f
i3a2l1er3h4
i3a4l3e4r1m
i3a2le4t
i3a4li1a
i2a1li
ia2l1k2
i3a4l1l
ial3la
ia2lor
i2a1lo
ia4l3t4
ia2lu
ia2l3z2
i2am
i3am.
i4a1mo
2ian
ia2nal
ia1na
i3a2n1d2
i2a1n2e
i3a4n1n
i2a1no
i3a2n1t
i3a2n1z
i2a1p
ia3p2f
i2a1q
i3ar.
ia2ra
2ias
i2a1sc
i4a3s1h
i2a1si
i2a3s1p2
ia6s3s
ias1t4
i3at.
i2a1t
i3a4ta1
i4a1t2e
i3at4h
1i4a1t2r4
i3a2ts
i3a2u
i4a3un1
2i2a1v
2iä
i1äm
iär2
i1är.
i1ä4r1s
i1ät.
i1ä4t1a2
i1ä2t3s4
2i1b
i2b1auf
iba2u
ib2b1li
i4b1b
ibbl2
ib1ei
i2be2i1g
i2beis
ibe1la2
i3bel
ibe4n
ibe2n3a
ib2i2k
i1bi
i3bla
ibl2
i3b2le
ib2o
i2bö
i4brä
ib2r4
ib3ren
ib1re
ib4s1t2e
i4b1s
ibs2t
i2b2u2n1k
ibun1
i2b1u2n1t
ib2u2s1
2ic
i2c1c
ice1
ich1a
ic4h
ich1ä
i1che
i4ch1ei
i1chi
i2chi2n
i2c2h3l2
i3ch2lo
i4c2h3m
i1cho
i2c2h3r4
ich4sp2e
i4c4hs
ich1sp2
ich2t3r4
i2c4h1t
i1chu
i4c2h1w
i1ci
i3ck2e
ic4k
i1c4l2
i1d
i2d2a1b4
i3dam
id2an
i2d1a2u
1i2d4e1e4
i1de
i2d1ei
id2e1l2ä
ide3so
ides1
id2e3sp2
1i2d2i1o
i1di
id1ni3
i4d5n2
i2d2ol
1idol.
2i2d2r4
i3d2sc
i2d1s
id2s1p2
i2d1t4
i2dy
i2e3a4
ie2bä
ie1b
i2e2bl2
ie2b1re
i2eb2r4
ieb4s2to
ie4b2s1
iebs2t
ieb4s2t2r4
ie1c
ie2cho
iec4h
ie2c4k
ie2d2r4
ie1d
i4e1e2
ie2f1ak
ie1f
i2e1fa
ie2f1an
ie2fa2u
ie2f3f4
i2e2f3l2
ie2fro
ief2r2
ie4g5l
ie1g
ie3g4n
ie2g3r4
ie3g4ra
ieg3s3c
ie4g2s1
i1ei
i2e2l1a
ie3la4s
iel3a2u
ie4l3d
ie2l1e2c
ie1le
ieler8ge1b
ie2l1e4r1g
ieler1ge
i1e4l1l
ielo4b
i2e1lo
iel3s1z
ie4l1s
iel3ta
ie4l1t
2i1en
i3en.
i3e2n1a
ie2n1a2b
ie4n3a4g
i3e2nä
i3e2n1d
i2e1ne
ien1e1b
ie3ner
ie2n4e2r1f
i1e4n3e4r1g
i3e2n3f
i3e4n3g
ien3ge4f4
ien1ge
i3e2n1h4
i3en1j
i3e2n1k
i3e2n1m4
i3e4n1n
i3e2no
i3e1nö
i3e2n3p4
i3e4n3r2
ie6n2s
ien3sc
ien3s2e
ien3si
ien2s2k2
ienst5rä
iens1t
iens2t2r4
ien3s1z
ie1n1u
i3e2n1v2
i3e4n1w
i3e2n1z
i2e1o2
ier3a2
ie2r2a1p
i2e1re
i2e3r2er
ie4r3e2r1f
ie4r3e2r1z
i2e3res
i3ere2u
i4e1ri
ierin3
ie2r3k2
i1e4rn
i3ern.
i2er5ni
ie2r1ö
ier4seh
ier3se
ie4r1s
iers2t
ier3s2ta
ier3s1t2e
ier3te
ie4r1t
ie3s2e6n3s4
ie1se
ies2sp2
ie6s1s
ies2s3t
ie1s2ta
ies1t
ie3su
ie2t1a
ie1t
ie4t3er3h4
ie3te
ie4t3e4r1t2
ie2t3ho
iet2h
i2e4t1o
ie4t1ö4
ie3t1ri
i2e1t2r4
iet2se
ie2ts
i1e4t1t
ie2u2e
i2e1un1
i1ex
2if
if2ar
i1fa
i2f3a4r1m
if4a1t
if1a2u
i2fe2c
i1fe
ife2i
if2en
ife6n1s2
if1e4r1g
if1er3h4
if2f3l2
i2f1f
if3l2
i1f4la
i1f4lä
i1f4lü
if3r2
if4ra
i1fra2u
i1f1re
if4rei
if4rü
i4f2s
if3se
if3sp2
if2ta
i4f1t
ift3e2r1k
if1te
if2t1op
if1to
if4t3ri
if1t2r4
ift3s2p2
if2t2s1
ift3s1z
2i1g
i5ga3i
i2g1a4n1g
ig1a4r1t
iga1s4
i4gef2ar
i1ge
ige1f4
ig2e1fa
ige4n1a
i3gen
ig1e2r1z
i2g1im
i2gl
ig1lä
ig4n2a
ig1n
i4g2nä
i3g4ne2u
ig1n2e
i3g4no
i3go
ig4ra
ig2r4
ig3rei
ig1re
ig4sal
i4g2s1
ig1sa
ig3sä
ig4se2
ig3so2
ig3sp2r2
ig1sp4
ig3s2te2i
igs1t
igs1t2e
ig4s2to
ig4s2tö
ig3s3t2r4
ig4st1re
ig3s3tü
igu4n1g4
i1gu
igun1
2i1h
i2h1am
i2har
i3he
ih4e1e
ihe4n
i2h3m
i2h3n
i2h3r
i4hs2
i2h1um
i2h1w
ii2
ii3a4
i1ie
i3i4g
i1im
i1in
i1i4s
i2is.
ii3t
i1j
2i1k
i2k1a4k
i1ka
ik1a4m1t
i2k1a1no
ik1a2n1z
i4kanze
ik1a4r3t
i2k3a4t1t
ik2a1t
i2k1a2u
i2kär2
i1kä
4ike
i2k1ei
ike2l1
i2k1e2r2e
ik1e2r1f
i4ker6f4ah
iker1fa
i2k1er3h4
i2k2e4r2l
i2k1e4t1a
ike1t
i3ki.
ik1in
i2ki2n1d
i2k3l2
i3kla
i3k4lä
i2k2n2
ik3no
ik2o3p4
i1ko
ik2o3s
i2k1öl
i1kö
i2k3ra
ik2r4
ik3rä
ik3re
i2k1s2
ik3s1o2
ik3s1z
ik1t2e
i4k1t
ikt3e2r1k
ik1t3r4
ik2t1re
i2kun1
i3k2us
i1la
i2l3a1b
i1l1a2d
i2l1ak
i2l3a2m
i2l1a6n1s
i2l1a2s1p2
il1a2u
il4au2f1b2
il3a2us
i2la2u1t
i1lä1
6i2l1b
i2l2c
il2da
i4l1d
il4d3e4n4t
il1de
ild2er
ild1o
il2do2r
il2d3r4
i2l1e2c
i1le
i3le2h
il1e1he
il1ein
il1el
i4l1en4ts
ile4n1t
i2l1e2r1f
i2l1e4r1g
i2l1e2r1r
i2l1f2
il2f3l2
il2f3re
ilf2r2
il4f4s3
il2i1e4n
i1li
il2ie
ilig1a2
i3l2i1g
ili4ga1b
i2l1i2n1d
i2l1ip
i3l2ip.
i3l2i2p1s
2ill.
i4l1l
il3l2a
il3l2er
il1le
il3l2i
2il4l1s
il2mak
i2l1m
il1ma
il4ma4n1g
il2m3a1t
il2ma2u
il2min
il1mi
2i1lo
i2l1or
il3t2h
i4l1t
i1lu2
i2lum
ilu4n1g4
i1lu2n1
i3l2us
i2l1v4
il2z1ar
i2l1z
ilz3e2r1k2
2im.
i2m1a4n1w
i1ma
i2m1a4r1m
im4a1t
im4a2t2r4
imat5sc
ima2ts
ima4tur
ima1tu
i2me1g
i1me
i2m2e1j
i2me1k
i2me1le
i2me2l1f
i2m1e2r1f
i2m1e2r1z
i4m4e3s1h
i3mes
i2me3ti
ime1t
i2me1w
i2m1i2n3f
i1mi
i2m1i6n1s
im2m1ei
i2m1m
im1me
im4m3e4n1t
1im1mo
2i1mo
im1o4r1g
1im1p2o
i2m1p
im2p4s
im3p3se
1im3pu
im2s1t
i2m1s
im3s2ta
2i4m1t
im4t3s2
2i1mu
i3n3a2c
i1na
i4nac4k
i2n1a1d
in2af
in3am
i3na1p
in2a2r1a
in1ar
in2a4r1s
i4n4a4r1t
i3na4s
i2n3a2u
i3n1äs
i1nä
in2dal
i2n1d
in2dan
1index
in1de
in3do
2ind2r4
ind4ri
in3d1rü
1ind2us
in1du
2i1ne
i2n1e2be
ine1b
in1e1he
i2n3ei
i2n1e4n1g
i3nen
in3erb2e
ine2r1b
i4n1er1bi
in2er3h4
i2n1er4l2ö
ine4r1l
i4n1er4t2r4
ine4r1t
i4ne2s1k2
i3n2es
in1e2u
in2e3un1
i2n1e2x
i2n3f
1info.
in1f2o
1info1s
2inga
i4n1g
ing1af
in2g1a4g
in2gl
ing4sam
in4g2s1
ing1sa
1inha1b
i2n1h4
2in3har
2in3ha2u
4in3he
in2i3d
i1ni
i3n4ie
2in2i1g
ini3k2r4
i3n2i1k
in2ir
2i3n2is
in2i3s1e
i3n2i4t1z
i2nit
3inka4rn
i2n1k
in1ka
inm2a4le
i2n1m4
in1ma
2inn.
i4n1n
in4n3e4r1m
in1ne
2in2n3l2
in2n1o2r
in1no
inn4s2ta
in6n1s
inns1t
1inn1ta
in2n1t
2i1no
i2n1o2d
in3o4l1s
in2ol
in1or
ino1s4
in2o3t
i1nö
i2n1ö2d
2i2n3p4
2i2n3r2
in3s2am
i6n1s
in1sa
insc4h2
in1sc
in2s1e1b
in1se
2insen
ins3e4r1t
in3skan
in2s1k2
ins1ka
in3sk2r4
1ins2ta
ins1t
in4s3t2ät
in3s2tä
in3s2tel
ins1t2e
in3su
1in2s1u2f
in4s3um
in3s2z
1inte3g6
i2n1t
in1te
int2h
in3t4r4
in5t1ri
in1u
i3n2um
in3u2n1z
inu2n1
invil4
i2n1v2
i1ny
in3zw2
i2n1z
i1ñ
2i1o
i2o1c
io2d
i2o3d2a
i2o3e4
iof4l2
i2o3h
io2i3d
i2oi
i2o3k4
i3ol.
i2ol
i3om.
i2om
i3o2m1s2
ion2
i3on.
iona2l3a2
i2o1na
io3nal
io2n2a2u
io2n3d
i3o6n1s3
ion4s2pi
ion1s1p4
ion4s1t
i2ony
i2o1p
io4pf
i3o2p1s
i3o2p3t4
i2or
i3or.
i3o2r1c
iore4n
i4o1re
i3o2r1p2
i3o4r1s2
i3o4r1t
io3s2
i2os1t
i3ot.
i2o1t
i3o2ts
i2o2u
i2o1v
io2x
i3oz.
i2o1z
i1ö2k
i3ön
i1ös.
2ip.
i1p2a
i1p2e
ipen3
i3per
ip3fa
i1ph2
2i1pi
ipi3el
ip2i3en
i3pi2s
i1p4l4
ip2p3l4
i2p1p
ip3pu
i1p2r2
2i2p1s
2i1pu
2i1q
i1r2a
i3r2a1d
1i2rak
i1r2a1t2
i1rä
ir2bl2
i2r1b
i2r1c
i1r2e
i3r4ee
2i3re1k
2i3ré
ir2gl
i4r1g
ir4g4s1
ir2he
ir1h4
i1r2i
2i3r2i1g
2i2r1k
ir2k3l2
irli4n
i4r1l
ir3l2i
ir2mak
i4r1m
ir1ma
ir2ma2u
ir4mä
ir2m1ei
ir1me
ir2mum
ir1m2u
ir4m3u2n1t2
ir3mun1
ir2n2a2r
i4rn
ir1na
ir2no
i1ro
1ir2on
iro2s
i1rö
ir3p4la4
i2r1p2
ir1p2l4
irr2h4
i2r1r
ir4s4c2h3w
i4r1s
ir1sc
irsc4h
ir3se
ir3s1h
ir2s1t
irt2s1t4
i4r1t
ir4ts
ir2u2s1
i1ru
i3sac
i1sa
i4s1a4m1t
i3sam
i2s2a1p
is3a1re
i2s1ar
i2s1a4u
i2s1än
i1sä
2i4s3b4
i2s1ca
i1sc
isch3ar
isc4h
i3s2che
i4s1che1f
i4sch3e4h
i4s4ch3ei
i4schi2n
i5schi4n1g
i2s4c2h1l2
isch3le
i2s4c2h2m
is2ch3o1b
is4ch3re
isc2h2r4
isch3ru
i4schw2a
is4c2hw
i6sch1wi4r
i4schwo
isch3w2u
i2s3c4r2
2i1se
i3s4e3e
is2e3h1a4
i3seh
ise3hi
ise3i2n3f
is1ein
i4sei2n1t
ise2n1
is2e2n1d
i3s2e6n3s
i2s1er3h4
i2s1e4r1m
is2e1r2u2
i2s1e6s1s
i4s3e2t4a1t
i3se1t
ise1ta
i1s2h2as
i2s1h
ish2a
isi2a
i1si
i2s1i1d
i2s1of
i1so
iso6ne2n1d
i3s2on
is2o1ne2
iso3ne2n3
is1op
3i2s2o1t
is1p2a
i1sp2
i2spar
is1p2e
is1p2ic
is2pit
is2por
is1p2o
i2s1p4ro
isp2r2
is3sa
i6s1s
is4s1ac
is4s1a4u
is4s3che
is1sc
issc4h
is2s1t
is3s2t2a
is3s2to
iss3t2r4
is3s1t2u
is2sum
is1su
is3t
is4ta1b
is2ta
is4tam
ist2an
i1s4t2a1t
i1s4tel
is1t2e
i4s3te4n
i4s3tes3
i3s4teu
i1s4til
is1ti
is4t2o1c
is2to
i1s4tö
is5tör
ist4ra
is2t2r4
ist3re
i1s4tü
isu2m3p
i1su
i2sü
i1ß
iß1e4r1s
i1ße
i4t1ab.
i1ta
ita1b
ita2l1a
i2t1a4l1t
i2t1am
it1an
i3t2an.
it3a4re
i2t1a4r1t
i3t2a1t
it1a2u
i3t4a2uc
i4t1a1x
4i1tä
it2är
i2t1ä2s
it2ät2
i2t1ei
i1te
i4te2i1g
i3t2e4il
i4t1ein
2itel
i3te2la
i3te4n
ite6n3s2
i4te1p2o
ite1p
i2tex
i5t2h2r2
it1h
i2t1i1d
i1ti
1itii2
iti4kan
i3t2i1k
iti1ka
it4i3k2e
i2t1in1
it2i4n1n
i6t3l
itmen2
i4t3m2
it1me
i5t2o1c
i1to
i2t1of
i3tö
it3r2af
i1t2r4
i2t3ran
it3r2as
it3ra2u
it3rä2u
it3re
i4t3r2ic
i3t1ri
it3r2om
i3t4r2on
i3t1ru
it3run1
it2sa
i2ts
its1a4g
it2s1e4
it2s3er1
it2s1o
it2s1p2e
it1sp2
it4staf
it1s1t4
its2ta
it2s2to
it2te1b
i4t1t
it1te
it4t1ri
it1t2r4
itt2s1p2
it2ts
i2t1u1h
i1tu
i2t1um
i2tu6n1s2
itun1
it1u4r1g
it2u1t4
i3tü
2i4t1z
it2z1ä2
it4z3e4r1g
it2z1w2
2i3u2
ium1
i1ü
2i1v
i2v1ak
i2v1a4n1g
i2ve3b
i1ve2
i2v1e4i
iv1e4l1t
ive4n
i2v1e3ne
i2v1e4n1t
i2v1ur
2i1w
iwur2
iw2u
2i1x
i2x1a
ix2e1m
i1xe
i3xi
2i1z
iz1a1p
iz1a2u
ize2i3c
ize2n
i2z1e1ne
iz4er
i2z1ir
i2z1o2b
i2zö
i2z1w2
í1l
ja1c
jah4r3ei
jah3r2e
ja2hr
jah4r4s
ja3l2a
j2a3ne
j2a3ni1
ja1s1t
2j2a1t
j2e2a
jea6n2s
je1c
je2g
jek4ter
je1k
j2e4k1t
jek1te
jektor4
jek1t2o
jek2t2r4
je3n1a
je2p
je4s3t
je2t1a
je1t
je2t3h
j2e2t3r4
je2t3s2
je4t3t
je2t1u2
je3w
ji2a
jit3
j2i2v
j2o3a3
jo2b1
job3r4
j2o2i
j4o3ni1
jo1r1a
jo2r1d2
jo2sc
jou4l
jo2u
j2u
ju2b2l2
j2u1b
ju3gen2
j2u1g
ju1ge
juge2n1d3
ju2k
jun4g3s4
jun1
ju4n1g
ju3ni
j4u1r2o
j2us3
ju3t2e1
j2u1t
2j1v
1ka
3ka.
k3a2a
k4a3ar
kab2bl2
ka1b
ka4b1b
ka2ben
2k1a2b5h2
2k1a2bla
kab1l2
2k1a2blä
2k1a2bo
ka3b4r4
2k1a4b1s
2k1a4b3t
ka1c
k2a1d
2k3ada
2k3a2d2r4
k2a1f4l2
ka1f2r2
ka4f3t2
k2a1g
ka1in
1ka3ka
kaken4
k4a1ke
2k2a3la.
ka1la
ka2lan
ka3l1ei
k2a1le
ka3l2en.
kalen1
ka4le6n1s
kal3e1ri
1kal2ka
ka2l1k
kal2k2r4
k1a4l1l
k2a1lo5
kal4t2r4
ka4l1t
k3a1m2a
kamp8fe2r1f
ka2m1p
kam2pf
kamp1fe
kan2al
ka1na
k2a4n1a4s4
ka2n1a2u
ka2n1d4
2kanda
k2a1n2e
2k1a4n1g
ka2n3k4
2k1a2n3l2
2k3an3na
k2a4n1n
k1a6n1s
k2ans.
6k3ante4n3n
ka2n1t
kan3t2en
kan1te
k2a3nu
2k1a4n1w
k2anz.
ka2n1z
k2a2o
2k1a2pf
ka1p
3ka1ra
2k1a2r1b
k2a2r1d
k2a4r1g
k2a3r2i
kari3es
kar2ie
k2a2r1k
2k1a4r1m
k2a2r1p3
kar2pf4
k2a4r1s
ka4r3t
k2ar1ta
2k1ar3t2i
k2a1ru2
k2a2r1w
3kas
k4a3s2e
ka1si1
ka6s3s
ka2s3t
ka3tan
k2a1t
ka1ta1
ka3t4h
k4a4t3r4
2ka4t1t
kau2f1o
ka2u
4kauf3r2
kauf4sp2
kau4f1s
k1a2us
ka2u3t2
2k1au1to1
1kä
k1äh
k1ä2mi
k1än
kär2
kä2s1c
kä5s4e3
2k3b4
kbo4n
kb2u2s
k3by4
2k3c
2k3d2
kd4a2m1p2
2k1e1c
k1e2f1f
ke1f
k2e1fi4
ke3ge2
ke1g
ke2gl
ke2he.
ke1he
keh4r2s
ke2hr
kehr4s3o
2k1e2ic
2k1e2i1g
k1ein
ke1i2n2d
2kei2n1h4
kei1s
2k1e2i1se
ke2it2
ke2la
kel1a2c
ke3la1g
kel1a2u
k2e2lä
ke2l3b4
2k1e2le1k
ke1le
ke2len
ke2l1er
2ke3le1t
kel3l4e
ke4l1l
kel3s2k2
ke4l1s
k4e4l1t
2k1e2m1p
ke1m
k2en.
ken3a2u
ke2n1a
4k3en4ga1g
ke4n1g
2kenlä
ke2n3l2
ke2no
ken2s2k2
ke6n1s
ken5s2te2i
kens1t
kens1t2e
ken3s1z
k2en1te
ke4n1t
k3en3t2en
ken3t1h
k2en1t2r4
2k1en4ts
k2en3tu
2k1en4t3w
2k2eo2
ke2p2l4
ke1p
k2er.
ke1r2a1d
ker1a
k2e2r1c
4kerf4ah
ke2r1f
ker1fa
k4erfam
k3erge1b
ke4r1g
ker1ge
k3er6ge4b3n2
k3e2r4h2ö
ker3h4
ke6r1in6nu
k2e1ri
ke3ri4n1n
kerin6s1t
ke2r1i6n1s
ke2r1i2n4t
ker4ken
ke2r1k
k2er1ko
k2e4r1l
k3er4la2u
ker3l2a
k3er4le1b
ker1l2e
k6erlebe
ker4ne2u
ke4rn
ker3ne
k1e2ro
k2ers.
ke4r1s
ke2r1z2
ker4zeu
2k1er2zi
k6es.
ke2s2el
ke1se
ke4t1a
ke1t
ke2t3h
ke2t3s
ke1u1p
keu6s4c2hl2
ke2us
keu1sc
keusc4h
2k1e2x
2k3f4
2k1g2
2k1h4
kh2o3m
ki3a4
k2i1c
2k1i2de
ki1d
k2i3d2r4
ki2el
ki2e2l3o
ki1f4l2
k2if
ki1f4r2
k2i3k4
2ki1l2a
ki3li
k2i3lo
k2i1mi
k2in.
k2i4n1g
2ki2n1h4
k2i1ni
k2i4n1n
k2i3n4o3
ki6n3s
2k1in1se
2k1i2n1t
ki3or
k2i1o
kio4s2
3kir
ki1s2p2
kis3t2
kis4to
2k2i1z
ki3zi
2k3j
2k1k4
kl2
4kl.
4k3la.
k4lar
4k1la2s1t
k2le
4k3le.
kle3a1ri
kl2e1a
4k3leh
k4l2e2i1d
4k3l2e2it
k3lem.
kle1m
2k3ler
kl2e2r1a
2k3le2u
kle3us
2kl2i1c
k1li
2k3l2i1g
k2lin
k3lip
k2lir
k2li1sc
2klis3t
kli2t2s2
4kl2i1z
2k3l2oc
k1lo
kl2o2i3
k4lop
klos1t4
klö2s
k1lö
k2l2ö1t
k1lu
k1luf2
klu4n1g4
k1lu2n1
2k1l2üc
2k1ly
2k1m
k2n2
3k2n1a1b
k1na
k3ne
k4n1ei
2k5ner
k3no4b1l2
k1no
kno1b
2k5nor
k3nu
3knü
1ko
ko2al
k2o3a2
2k1o4b1j
ko1b
2k1o2fe
ko2f1f4
koh3lu
ko2hl
k2o1i2
ko1l4a
k2ol
ko3le
ko2l2k5
3k2om
ko4mu
k2on
k2o3n2e
ko6n3s4
ko3nu
2kop.
ko1p2e
kop4fen
ko2p1fe
2ko2p1s
2ko2p1z
ko1r2a
2k1o2r1c
kor6de4r1g
ko2r1d
kor1de
ko3ri
k2os
k2o2s1p2
ko2s1t
ko3ta
k2o1t
ko2t3s2
kot4t1ak
ko4t1t
kot1ta
2k1o2u
3k2o1w
ko2we
k1o2x
1kö
k1ö2f
k1öl
2k1p2
k1q
k2r4
2k3r2a1d
k3ra2ts
k1r2a1t
2k3r4aum
kra2u
k4r2a1z
2k3rät
2k3r2äum
krä2u
2k3re.
k1re
2k3rec
2k3red.
kre1d
2k3re1d2e
2k3re1f
2k3re1g
k3re2ic
kr2e1i2e4
kreier4
k3re2i1h
2k3r1h4
2kr2i1b
k1ri
2k3r2ic
k3ries
kr2ie
2krip
3kr2is
3k4r2on
2k3ruf
k1ru
k2r1ü1b
k1rü
2ks
k4s1a4m1t
k1sa
k3sam
k2s1an
k2s3ar
k2s1a4u
ks2än
k1sä
ksc4h4
k1sc
ks1e2b
k1se
k2s1e1m
k2se4n1t
ks1e4r1l
k2s1e4r1s
k2s1e2r1w
ks3h2a
k2s1h
k2s1i1d
k1si
k2s1in
k2s1o2
k3sof
ks1p2a
k1sp2
k3sp2e
ks2por
ks1p2o
ks2pu
k6s3s2
ks1t4
k1s2ta
k4s3ta2n1z
k3st2a1t4
k1s1t2e
k1s2ti
k1s2to
k2stor
k1s2t2r4
k2strä
k1s1t2u
k2stum
k2s1u
ks2zen
k2s1z
4k1t
k2t1a1d
k1ta
4k1t1a4k1t
k3tal
k2t1am
kt1an
k2t3a2r
kt2a4re
k2t1a2u
ktä3s
k1tä
kt4e3e
k1te
kt1ei
k2te2m1p
kte1m
k2te4n1t
k3ten
k4t3er1fo
kte2r1f
k2t1er3h4
kt2e3ru2
k2tex
k2t1h
k1t3ho
k2t1i1d
k1ti
kt1im
k2t1i4n1g
kt1i6n1s
kti4ter
k3ti3t2e
k2t1of
k1to
k3top
kt1op2e
k4t3or3ga
kt1o4r1g
kt3or2ie
kto1ri
kt4ran
k1t2r4
kt3r2as
k4tre1f
kt1re
kt4ro
ktro1s
kt3run1
kt1ru
k2t3s4
k4t1t2
k2tu6n1s2
k1tu
ktun1
k3tü
k4t3z
k2u1c
ku1h1
2k1u2hr
kul2a
ku1l
ku3l2e
ku3l2i
4ku2l1p
2k3u4m3l2
ku2m2s1
k2u3n2a
kun1
ku4n1g4
ku6n4s4
kuns1t3
2k1u2n1t
2k1up.
kur2bl2
ku2r1b
ku2rei
k2u1re
kur2i2e
ku1ri
kuri4er
k4u2ro
kur2s2p4
ku4r1s
kur2s1t
ku4s4c2hl2
k2us
ku1sc
kusc4h
ku2sp2
kus3t
ku2su
1kü
2k1üb
k2ü1c
kü4r4s
2k1v
2k1w
2k3z2
kze3l
3la.
l2a3ba
la1b
2la4b1b
4l3aben
2l1a2b5f4
2l1a2b1g2
2l1a2b5h2
4l1a2b1l2
lab2o
l2ab3r4
la3b4ra
lab4ri
2l3a4b1s
l1a4b3t
3l2abu
2l1a2b1w
la1ce
la2ce.
1la1d
l4a3d2i
l1a4d1l2
2la2d3m2
2l1a2d2r4
3l2a1du
l1a2d1v2
2laf
la2f1a
la4f3s
la4f3t
l2a2ga
la1g
la2g2i1o
la2g2n
lago2
la2g1o1b
2la1ho
1lai
la2kes
l4a1ke
l2a2k1i
l2a2k1k4
l2a1k4l2
2l1al
4la4l1l
4la2l1p
l2a1mi
la3min
1lam2m1f4
la2m1m
l2a2m1p
2l1a4m1t
lam4t4s1
la4mun1
l2a1mu
l1anal
la1na
la2n1a2u
2l1a2n3b4
3l2a2n1d
lan2d3a2
lan6d5e2r1w
lan1de
lan6d5e2r1z
lan2d3r4
2l1a2n1f
lan2g1l
la4n1g
lan4g3s4
2lan3hä
l1a2n1h4
l2an3he
2l1a2n3l2
4lan1li
2l3a4n1n
l1a2n3p4
2la6n1s
4l1an1sä
2l1an1t2r4
la2n1t
l2an2zw2
la2n1z
3l2ao
l1a2p2o2
la1p
l3ap4p3l4
la2p1p
la2r1an
la1ra
la2r1ei
l2a1re
la4re1ne
la3ren
3l2a4r3g
lar3i1ni
l2a1ri
la4r3s
2l1a4r3t
l3ar3t2i
l2a2ru
la2s3a4u
la1sa
4la4s3d2
l4a3s2e
2l4a2s1h
2la1si
la2so
2la2s1p2
3lasser
las3s2e
la6s1s
la2s1t
las3t1o
la1t2a1
l2a1t
la3t2e
la4tel
2l3at2h
la2t3ra
l4a1t2r4
la2t2s
2lat3t1a
la4t1t
lat4tan
lat4t3in
lat3ti
lat2t3r4
laub4se
la2u
l2a2u1b
lau4b1s
l2auf.
lau2fo
l2au2f1z
1l2a2u1g
2l1au2s1l2
la2us
2l1au4s3r4
2l1au6s3s2
2l1au1to1
la2u1t
1l2a1w
law2a4
lay1
lä1c
1lä1d
2läf
2l1ä2hn
1lä2n1d
lär2m1a
lä4r1m
lä2s1c
4lät
2lä2u1b
2lä2u1c
2lä2u1e
1lä2uf
1là
2l1b
l3bac
l2b1e1d2e
lbe1d
l4be1ta
l3be1t
l2b1i1d
l1bi
l2b1i6n1s
l3b2l2a1t
lbl2
l3blä
lb3le
l2b1li
l3b2lo
l4b3re.
lb2r4
lb1re
lb3r2it
lb1ri
l4b2s
lb3sa
lb3se
lb4s1k2
lb3sp2
lbs6t
lbs1t3e
lb4s2to
lb2u
l2b3u2f
lbzei2
l2b3z2
2l1c
l3che
lc4h
l3chi
l2c2h3l2
lc2h3r4
l4ch3ü
l4c2h1w
l3c4l2
4l1d
l2d3a2b1
l3d2ac
ld3a2c4k
l2d1a2d
ld1a4g
l2d1ak
ld1al
l3dam
ld1a2m1m
l2d3a2n
l2d1a2r
ld3a1ri
l3da1s
l3d2a1t
ld1a2u
ld1är
l2d1ei
l1de
l2de1le
l3d4er.
ld1e2r3p4
l2d1e2se
ldes1
l2dex
l2d1i1d
l1di
l2d1im
ldo2r
ld2os
ld2ö2
ld3r4
l2dran
l2d1re
l3d4ru
ld4rü
ld3sa
l2d1s
ld3s1t4
l2d1t4
ld3t1h
l2d1um
l1du
1le
3le.
le2a1d
l2e1a
lebe6n4s3
le1b
leb2en
l2e2bl2
2lec
le2chi
lec4h
lech1t4e
le2c4h1t
3le1d
4le2d1d2
le3d2e
l4e2e
le3ei
l2e1f2a
le1f
le2g1as3
le1g
le2ga2u
le2gä
le2gl
leg4r4
3leh
leh3r2e
le2hr
4le4hs2
4l2e4h1t
3lei.
lei2b2r4
l4e2i1b
l2e2ic
l2e2i1d
4l1e2i1g
l2ein.
l2ei2n1d
l2ein4du
l2e2i1ne
lei6ne2r1b
2lei2n3k
l2ei2n1t
leis6s5er
lei6s1s
leis1se
l4eis3t
lei4ßer
lei1ß
lei1ße
l2e2it
lei2ta
lei8t7er8sc
lei3te
leite4r1s
lei2t3s2
lek1t2a
le1k
l2e4k1t
2lek1t2r4
3l2e1la
2l1e2le1k
le1le
le4l3s
3le3me2s
le1m
le1me
le2m1o2
4le2m1p
le2m3s
l2en.
le4na1d
le2n1a
le2nä
4lende1t2
le2n1d
len1de
2len1du
le4n3e2n1d
l2e3nen
le1ne
4l1e2ne4r1g
l2e2n3f
le3ni
l2e2n1k
2l1en3ni
le4n1n
l2e2no
l1en4se1m
le6n1s
len1se
len3s1z
l1en4ts
le4n1t
2l3en4t3w
lent4w2ä
5l4ent1we1t
4l1en4t3z2
len2zi
le2n1z
le1o1s2
l2eo
2le1p
3le3p2a
3le3p2f4
leposi1ti8
le1p2o
lep2o1si
lepo3s2it
3lep2r2
l2er.
l2e1r1a
le2ra4g
le2ra2u
le2r1b4
4l3e2r1e2i1g
le1re
le4r3eim
le4r1e4r1s
l2erer
l1er1fo
le2r1f
l2erf4r2
l2er1fü
3lergeh
le4r1g
ler1ge
l3er3gen
3l4erge1w
2l1ergi
le2r1i6n4s
l2e1ri
le2r1k2
l2er1ka
l2er1ko
1l2er1l2e
le4r1l
2l1er2ö
3l2erra
le2r1r
l4ers.
le4r1s
ler3s2k2
lers2t
le4r3t
6lerwe2r1b
le2r1w
l1e2r1z
l2erza
le3s2am
le1sa
le1s2e
2l1es2el
le3ser
l4e3s1h
le1si1
le3s1k2
les2t
les1t2e3
le1s2to
4le2s3w
2le3s4y
le2t4a1t
le1t
le1ta
2le3t2h
2l2e3to
let4tu
le4t1t
le2u
4le2u1d
2l3e4u3ro
3l2e2u1t
3l2e1v
2le1xe
l1e2x2is1
le1xi
2lex1z
2l1f
l3f4ah
l1fa
lfa4n1g3
l2f1e2c
l1fe
lf4e1e
l4f1eis
l3f4lä
lf2l2
lf3lo
l3f4lu
lf3ra4m
lf2r2
lf2t2r4
l4f1t
l1f4u
lf2ur1
l3fü
2l1g
lg2a3t
l2gd4
lge3n2a
l1ge
l3gen
lge3r1a
lgerä2u3
lge1rä
l2ge3ti
lge1t
l3go
lg3re
lg2r4
l3gro
2l1h2
3l2hi.
1li
3li1a
l2i3ac
li3ak
li3am
li3ar
l2ia1s
l2i3b4
li1bi3
l2i1c
3liche1m
li1che
lic4h
3licher
li3chi
4lic4k
li2c1k1a
li3d2a
li1d
li2d2eo
li1de
2l1ido
li4d1s
li3d3sc
l2ie
3lie.
lie3be4s
lie1b
li3e1ne
l2i1en
lie6n3s
lie2s3c
lie2s1t
3l2i1g
lig4n
li2g1re
lig2r4
l4i3ke
l2i1k
li2k2r4
lik2sp2
li2k1s2
lik4ter
lik1t2e
li4k1t
li3l
li1l2a
2lim
li3m2a
3l2i1mo
li3n2a
lin3al
2l1in1du
li2n1d
li2n1e1f
l2i1ne
li2neh
li2ne1p
li3n2es
2l1i2n3f
lin4g2s5
li4n1g
2l1i2n1h4
2l1i2n1it
li1ni
2l1in1j
lin2k1a
li2n1k
lin2k2s
li2n2ol
l2i1no
l2ins.
li6n1s
l2in1sa
l2in1sc
2lin1sp4
2lins1t
2l1i2n1t
l1i2n1v2
2li2n1z
l2i2o
li4om
li3os.
lio3s2
li2p3a
3lis.
li3s2a
li4s4chu
li1sc
lisc4h
2l1i2s1l2
2l1i4so
li2sp2
li6s1s2
li1t2a
li2tal
li3te
lit2h
li2t1s2
lit3s1z
li3tu
3l2i3u2
2li3xi
l2i1x
li2za
l2i1z
lizei3
4l1j
2l1k
lk1a2l1p
l1ka
l3k2an
l3k2ar.
lke4n3t
lk2l2
lk3lo
l3k4lu
lk4ne
lk2n2
lko2r2b1
l1ko
lk4ra
lk2r4
l2k3ro
l2k3ru
l2k2s1
lk3sä
lks3t4
lk4s2tä
l3k2ü
4l1l
l2l1a4b1b
lla1b
ll1a2be
l2l1a4b3t
ll1a2f1f
l2laf
ll1a4k1t
l3l2al
l2l1a2m
ll3a1m2a
lla2n
ll2a4n1w
ll1a2n1z
l3la1p
ll1a4r1m
ll1a2u
l1l3a2u1g
l2la2us
l2l1äm
l2l1b4
llc4h4
l2l1c
l4l3d4
ll1ec4h
l1le
l2lec
lle3en
ll4e2e
l2l1e1f
ll1eim
ll2e1m
l3l2en.
lle4n3a
l2l3en1du
lle2n1d
lle4n3g
l4l1en4ts
lle4n1t
l3l2er.
ll2e2r1a
l4l1er1fo
lle2r1f
l6l3er3gen
lle4r1g
ller1ge
l4lergo
ll3er2n1t
lle4rn
ll3er1t2r4
lle4r3t
l2l1e2r1z
ll2es
l2lex
l2l1g4
ll1i2m1b2
l1li
l2lim
ll1i2m1p
l2l1i2n1d
ll1i6n1s
l2l1k4
4l4l3l2
l2l5m
l4ln2
ll1o1b
l1lo
l2lob2e
l2l1of
l2l1opf
l2l1o2r
l3l2or.
l3l4o1re
l2l1o2u
l3l2o1w
l2l1ö2f
l1lö
ll1ö4se
ll3s1h
l4l1s
ll3s2k2
ll2sp2r2
ll1sp2
l4l5t4
llti2m
ll1ti
ll6t5s2
l1lu2f
l2l1ur
llu4s5t6
ll2us
l2l3z2
2l1m
l2m3a2b
l1ma
l2m1a2r1c
lm1a2us
lma2u
l2m1c
lm4e2e
l1me
lm3ei6n1s
l2m1e2p
l2m1e2r1z
lm1i2n1d
l1mi
lm1i6n1s
l2m1öl
l1mö
l2m3p
lm2pf4
lms2t
l2m1s
lm3s1t2e
lm3s2z
l4m3t
4ln
ln1a4r
l1na
ln3a1re
l2n1d2
l3n4e
l3ni
l1nu
l1nü
1lo
3l2ob.
lo1b
lo2ber
lob2e
2l1o4b1j
2l1o2b1l2
l2ob2r4
lo3b4ri
l1o2fe
lo1f3l2
l2o1f4r2
lo2ga2u
l2o1g
lo3h2e
2l1o2hr
loi4r
l2oi
3l2o1k
l4o2k3r4
lo1l2a
l2ol
l3o2ly
lo2min
l2om
lo1mi
lo2n1o1
lo2o
2lopf
2l1o2p3t4
lo1r1a
lo4rä
2lo2r1c
l1o2r1d
lo3ren
l4o1re
2l1o4r3g2
l2o3ro
3l2or1q
3los.
l2o4s2a
3l2o3se
lo4s3ke
lo3s1k2
lo2s2p2e
l2o1s1p2
los1s2e
lo6s1s
lo4s1t2e
los1t
los3t4r4
lo2ta
l2o1t
l4ot4h
lo3th4a
lo3t3hi
lo3t2i4o
lo1ti
2l1o1v
lo2ve2
2lo1x
1lö
lö2b3
2lö1d
l1ö2f
2l3ö1fe
4lög
l1ö2hr
2l1ö4l3
4lö1ß
2l1p
l3p2a
lpe2n3
lp2e
lp2f
l2p1ho
l1ph
l3pi4p
l2p3t4
l3pu
2l1q
2l3r2
lra2t4s
l1r2a1t
lre1s
l1re
l3r2u1t4
l1ru
l2r1ü1b
l1rü
4l1s
l3sac
l1sa
l2s1a2d
l3s2al
l4s1a2m1b2
l3sam
l2s2a4n1n
ls1an
l3s2a1re
l2s1ar
l2s1a4u
l4schi2n
l1sc
lsc4h
l4sch1mü
ls4c2h2m
l2s1e2b
l1se
l2s1ec
l2s1e1m
ls1e1re
ls1e4r3g
l2s1er3h4
ls1e4r1l
l2s1e4r1s
l2s1e2r1w
l3s1ex
l4sh2a
l2s1h
lsho2
l2s1i2m1p
l1si
ls2l2o1g
l2s1l2
ls1lo
ls3oh1n2e
l1so
lso2hn
l4s3ort.
lso4r1t
l3s2pi
l1sp2
ls2p2o
l2s1p4ro
lsp2r2
l3s2pu
l6s3s2
ls2t2a
ls1t
lsta1b6
ls4taf
l4s3tä1ti
l3s2tä
lst2ät
l2s1t2e
l3stec
l3s2te2i
l3s2tel
l4ste1m
ls6ter3ne
l2ster
lste4rn
ls6ter6n1s
ls2ti2e
ls1ti
l2stit
ls4t2r4
ls2t2u
ls1um
l1su
l2su2n1
ls4u3s1
ls2zen
l2s1z
4l1t
l2ta1b
l1ta
l3t2a1g4
lt1ak
l2t1a2m
l4t3a1m4e
l4t3a2n1d
l2t1a4n1g
l3t1a2r1b
l2t1a4r1t
l2t3a1to
lt2a1t
l2t1a2u
l2t1eh
l1te
l2t1eis
l4t1e4le1m
l3te3le
lt3e1li
l3t2en
l5t6en.
lter3a
l3t2e4r3g2
lt4er1ö
l4t1e4s1k2
l3tes
l4te2t2h
l3te1t2
l2t1eu
l2t1h
l4t3hei
lth2e
l1t3ho
l3thu
lt2i1mo4
l1ti
l2to1b
l1to
l2t1of
lt1op
l2t1o2ri
l3t2o2w
lt1ö4l
l2tö
l3tör
lt1ös
l4t3ö1t
l3tr2a3l
l1t2r4
l3trä
lt3rä2u
lt3re
lt4r2ie
l3t1ri
lt3r2o1c
lt3ros
l2t3rö
l6ts
lt3sc
lt2s1o
lt4sta1b
lt1s1t4
lts2ta
lt4s3t2o1c
lts2to
l4t1t2
l2t1u1h
l1tu
l2t1um
ltu4ran
ltu1ra
ltu2ri
l3tü
lu1an
l2ua
4l2u4b3
luba2
lu4b1s2
lu2d2r4
l2u1d
lu2es
l2u1e
1luf
2l1u1fe
2l2u2f1f
luf2t1a
lu4f1t
luf2t1e
luf2t5r4
lu2g1a
l2u1g
lu2g1e2b
lu1ge
lu4g3l
lu2go3
lu2g3r4
lug3sa
lu4g2s1
lug3sp4
lu2gu
2l1u1h
lu1id.
l2ui
lui1d
lu1me2
2l1u2m1f4
2l1u4m3l2
l2u2m1p
l1u2m1s
l1u2m1w2
1lu2n1
2l1u1na
2l1u2n1f
lung4s3c
lu4n1g
lun4g2s1
2l1u1ni
2l1u2n1t
2l1u4n1w
4l2uo
2lur
l1u4rn
l1u4r1t2
2lu1se
l2us
lu2sp2
lus4s3a
lu6s1s
lus2s1c
luss3er
lus1se
lus6se2r1f
lus6se2r1k
lus6se4r1s
lus2s1o
lus2s1p2
lus2s3t
lus4s2tä
lu4s1t
lus4t1a
lust3re
lus3t2r4
lu2s1u
lu2t1a
l2u1t
lu2tä
lu4t1e1g
lu3te
lu4t3e4r3g2
lut1o2f
lu1to1
lu2top
lu4t3r4
3l2u1x
2l1üb
5l2üd
lü2h1l
2l1v
2l3w
2lx
1ly
ly1ar
ly3c2
2ly2m3p4
3lyn
ly3n1o
ly1o
ly3u
2l1z
l2z3a1c
l3z2an
lz2e2r1k2
lz1i2n1d
l2z1o2f
l2zö
l2z3t2
l2z1u4fe
lzu1
lzu3f4
lz1w2
lz2wec
1ma
m1a1b
m2abe
2m1a2b1k4
m2ab4r4
2m1a4b1s
2ma4b3t
mach4t2r4
ma4c4h1t
mac4h
ma2ci
ma3da
ma1d
ma2d4r4
m2a4d2s2
m2a1e2
ma1f
ma2ge.
ma1g
ma1ge
ma2ge1b
ma2ge1f4
ma2ge1g
ma2ge1k
ma2ge3p4
ma4ges.
m2ag2e1s2
ma2ge1t
ma2g2e1v
ma2ge1w
2m1a4g1g
magi5er.
magi5e4r1s
ma3g4n
2m1ago
ma2i4s2e
m2ais
2m1a4k1t
mal1ak
ma1la
ma4la4k1t
ma2lan
m2a4l3a1t
ma2l1a2u
ma4l3d
ma3ler
m2a1le
mal2i1e
m2a1li
mal3lo
ma4l1l
2mal4l5t4
ma1lu4
ma2l3u1t
ma2m3m
2m1anal
ma1na
ma2n1a2u
2m1a2n3b4
man4ce.
ma2n1c
ma2n3d2
man3e4r1s
m2a1ne
ma2ne1t
m2a2n1f
2m1ang2r4
ma4n1g
m2a2n1h4
2m1a2n3l2
m4a4n1n
2man1sa
ma6n1s
2m1an1sä
2m1an1sc
2m1an4t3w
ma2n1t
2ma2n1z
ma2or
m2ao
m2a2p1p
ma1p
2m1a2r1b
ma4r3g2
4m2a3r2o
ma2r1o3d
4m2a2r1r
mar6s4c2h2m
ma4r1s
mar1sc
marsc4h
mar6sc2h2r4
m2a3r2u
m1a2r1z
3mas
ma3s2p2a
ma2s1p2
4m1asp2e
massen3
mas3s2e
ma6s1s
ma1s4tel
mas3t2e
mas1t
m2a1s4t2r4
3ma1ß
ma2t1a2b
m2a1t
ma1ta1
ma2tan
ma4t4c
ma2tel
ma1t2e
ma4t3e2r1d
ma5t1ri
m4a1t2r4
mat3se
ma2ts
mat3s1p2
2m1au2f
ma2u
m4a3un1
2m1au4s3g2
ma2us
m4ay
ma1yo
3mä
m1ä2hn
mä1i2
4m1ä2n1d
m1ä4r1g
mä3t4r4
mäu2s1c
mä2us
2m1b2
mb4e2e
mb4l2
m3b4r4
m3by4
2mc
m3c4h
2m1d
md1a
m2d1ä
m2d1ei
m1de
md1s2e
m2d1s
m2d1um
m1du
1me
me1b4
m2e1c
me1di3
me1d
medi2e4
med2i1en3
2me3dy3
me1e1f
m4ee
me1e2n1
mega1
me1g
3meh
2m1e2if
2m1e2i1g
m2e4il
mein4da
mei2n1d
me1i4so
3meis3t
me3l3a2m
me1la
me2la2u
3me4l1d
m1e2le1k
me1le
me2ler
mele1t4
2m1elf.
me2l1f
me4l1l2
mel2se
me4l1s
me4l5t4
6m3el6te4rn
mel1te
2m1e2mi
me1m
m2en.
me2n1a2b
me2n1a
me3nal
men3ar
men3a2u
men3ge
me4n1g
men3gl
me3n1o2r
me1no
m2e6n1s
men4s1k2
men2so
men3ta
me4n1t
men6ta2n1z
2m1en4t5n4
4m3entwi
m1en4t3w
m2e1o
2meo2u
2me1ö2
3mer.
me1r1a
me2r3a1p
me4re6n1s
m2eren
me1re
m2er2er
4m3ergän
me4r1g
3merin
m2e1ri
me2r1i2n4d
me2r1i2n4t
me2ro
3me4r1s
merz4en
me2r1z
3mes
me1s1a
me2sal
me4sä
4meser
me1se
2m4e3s1h
4m1es1sa
me6s1s
mes6s3e4r3g
mes3se
m2es2s1o
mes2s1p2
mes2s1t
mes1t2e2
mes1t
me1s2to
4me1su
me3t2a
me1t
me3t2h
meu1
2m1ex
1mé
2m1f4
mfi4l
m1fi
4m1g2
2m1h4
1mi
mi2a1d
mi1a
mi3ak
mi1bi1
m2i1b
m2i1c
mi3da
mi1d
mie3d2r4
mie1d
mi2e1i
mie3l
mie6n3s
m2i1en
mi2er
mi2e3r2er4
mi2e1re
mie2ro
mi4e1t
mie4ti
3m2i1g
mi2kar
m2i1k
mi1ka
mi2ki
mi2ku
3mil
mi3l2a
milc4h1
mi2l2c
mil4che
mil2d4s
mi4l1d
4mi2l1z
2m1i2m1p
minde4s1
mi2n1d
min1de
mi3n2en
m2i1ne
min2e2u
m2in2ga
mi4n1g
min4g3s4
mi3ni
3m2i1n2o
mi1n1u
3mir.
mi3r2a
3mi1r2i
3mi4r1s
3mi2r1w
mi2sa
mi4scha
mi1sc
misc4h
mi4s2c2hn4
mi4s4c2hw
m2i1se1
mis2s1c
mi6s1s
mi2s5t2e
mis3t
3mit
mi2ta
mi2t1h
mi2t2r4
mi2t3s2
mit5sa
mi5t2s1u
mi2t1u
4m2i4t1z
2m1j
4m1k4
m3ka
m2k5re.
mk2r4
mk1re
4m1l2
m2l3c
m4l3l
m4l3s
2m1m
m2m1a1b
m1ma
m2m1ak
m2m1al
mm1a4n1g
m2m1a6n1s
m2m1a2n1z
m2m1a2u
m2m1d2
mm1ei
m1me
mme4lin
mm2e1li
mme4n1a
m4m1en4t3w
mme4n1t
mme2r1a2
mme4rec
mme1re
mme2s1a
m3mes
mm1i2n3b4
m1mi
mm1i2n3f
mm1i2n1h4
mm1i6n1s
mm1i2n1t
mmi3sc
mmi1s4t
2m2m1m2
m2m3p
m2m2s
mm3si
mm3sp2
mm3s2ta
mms1t
mm3s4t2r4
m2mum
m1mu
m3m2un1
mmül2
m1mü
mmü4l1l1
2m3n2
m4ne1si
m1ne
m3n2es
1mo
m2o3a3
2m1o4b1j
mo1b
3m2o1d
mode3s1
mo1de
m2o2d2r4
4mog.
m2o1g
mo2gal
3moh
m2o2i3
mo2k1l2
m2o1k
2mol.
m2ol
3m2om
mo1m2e
3m2on
m2o3ne
mo4n1er
mo6n2s3
mon3su
3mo2o
2m1op2e
2m1o2p3t4
mo1r1a
mo2r1ar
2m1o2r1c
m4or2d3a
mo2r1d
m2or2d2r4
mo2r1er
m4o1re
morge6n5s6
mo4r1g
mor1ge
mor3gen
mo2r1k4
3mos
mos4ta
mos1t
mo2ster4
mos1t2e
3m2o1t
m1o2x
mo1y
1mö
m2ö2c
4mök
m1öl
2m1p
m2pf
mp4f3e4r1g
mp1fe
mpf3e2r3p4
mpf3e2r1r
mp4f3e2r1z
mp2f2l2
mpf3li
mp2f1or
mp1fo
m3pi
m3pon
m1p2o
mp3t1a
m2p1t
m3pu
2m1q
2m3r2
2m1s
m2s1an
m1sa
ms3a2n1d
m4s1a1p
ms1as
m2s1a4u
m3sä
m3sc
msc4h2
m4s3co
m3se
m4s1e1f
ms1e2r1w
m4s1ex
ms1i1n1i
m1si
mso2r
m1so
ms1o1ri
m2s2pä
m1sp2
m2s1pe1d
msp2e
ms2p2o
m2sp2o1t
m2s1p4ro
msp2r2
ms2pu
m6s3s2
m4s3t2a1g
ms1t
ms2ta
m3s2tel
ms1t2e
m3s2ti
m3s2to
ms4t2r4
ms5trä
ms5tren
mst1re
m3s2t2u
m1s4tü
ms1um
m1su
m2sü
m3s4y
4m1t
mt1a1b
m1ta
mt1ak
m3tam
mt1ar
mt3a1re
m3t1e4l1t4
m1te
m2t1e2r1f
m4t1e4r3g2
m2t1e4r1l
m2t1e4r1s
m2t1e4r1t2
m4t1e1ta
m3te1t2
m2t1eu
m2t1h
m1t3ho
m2t1im
m1ti
m2t1i6n1s
m3ti2s
mtmen2
m4t3m2
mt1me
m3tö
mt1ös
m4ts1
mt2sa
mt2se
mt3s2ka
mt2s1k2
mt2sp2r2
mt1sp2
m4t1t2
mt1um
m1tu
mt1u4r1t2
m3tü
m4t3z
1mu
m2u1a
mu3cke
m2uc
muc4k
2m3u1h
mu3la
mu1l
2mu4l1s
3mun1
m1un2d1a
mu2n1d
4m3u2n1f
4m3unge1b
mu4n1g
mun1ge
mu3ni
m4u2n1k
m1u2n1t2
4m2u2n1z
mu3ra
mu4r1u2f
mu3ru
m4us
mu4s1a
3mu1si
mu2s1o
mu2sp2
mus3t
mu2su
mut1a2u
m2u1t
mu1ta
mu2ts3
mut2s1t4
1mü
2m1üb
mül4len
mü4l1l
mül1le
3m2ün
3m2üt
mütter3
mü4t1t
müt1te
2m1v
mvo4l1l1
m3v2ol
2m1w2
mw2a2
mwa4r
mwel4
1my
my4s
2m1z
1na
3na.
2n1a1b
na2b1ä
4n1a2b1g2
4n1a2b5h2
na2b1l2
n2abo
na2b3r4
4n3a4b1s
4na4b3t
3n2ac
na2c4h1
na3chen
na1che
na4c4h3s
nacht6ra
na4c4h1t
nach1t2r4
4n1a2d1d2
na1d
n2a1de
4na2d2r4
n1af
na1f4r2
3n2a1g
na2ge1m
na1ge
3n2ah
n2a2h1a
n3a2hn
3nai
nai2e2
n1a2i3g4
2n1ak
na2ka
3n4a1ko
n2al.
na2l1a2
na2lä
3n2a4l1d
n4a1le
na4le4n1t
nalen1
na2le4t
nal3la
na4l1l
nal1mo2
na2l1m
na2lop
n2a1lo
n1al2ph
na2l1p
n2als.
na4l1s
na4l3t4
na2lu
2n4a1ly
n4am.
3n2a1m4e
n4ame2n1
4n3a2mer
na3m4n2
3na1mo
2n1a4m1t
nam4t4s1
n1an.
4n1a2na
4n1a2n3b4
n1a2n1d2
4n1a4n1g
2n1a2n1h4
2n2a3ni
4na2n1k
2n1a2n3l2
3n2a4n1n
na3no
n1a2n3p4
2n1a2n3r2
2n1a6n1s
2n1an1t2r4
na2n1t
2n1a4n1w
nap2si
na1p
na2p1s
n1ar
5n2ar.
na2r1a
2n1a2r1c
n2a2r1d
4na4r1g
3n2a1ri
n2a2r1k
n2ar1l2e
na4r1l
2na4r1m
n2a2r1p2
4n3a4r1t
n2a3r2u
3nas
n2as.
na4s4c2hw
na1sc
nasc4h
4na2s1p2
4n1a2s4y
n3a2sy1l2
3n2a1t
n4a1ta1
na3t4h
4n3a4t3m2
na2ts1
nat4sa
nat4sc
4na4t1t
n1a2u
4nauf
nauf4f2r2
na2u2f1f4
n3a2u1g
5n4a2u1i
3n2au1l
4nau4s3b4
na2us
4n1au4s3g2
n2au2so
4nau6s3s2
n4aus1t2e
naus1t
4nau2s1w
navi5er.
n2a1v
n4avi
navi2er
navi5e4r1s
1nä
3n2äc
3n2ä1e
n1ä2hn
2n1ä2m
2n1än
nä4r4s5
3näs
nä2sc
n2ä6s1s
2näu
3n2ä1um
2n3b4
nb2e2in
nbe3n
nbe3r2e
n3be1s4
nb2u2s
n3by4
2n1c
n3ce2n3
n4c2h3m
nc4h
n2c4k
2n1d
nd2a1g
n2d1ak
n2d1a2n3l2
nd2a4n1n
n2d1a2n1z
nd2a1t2
nd1a2u
n2d1c
nde4al.
n1de
nd2e1a
n2d1ei
nde4l1än
nd2e1lä
n4d3en4ts
nde4n1t
nde4r1o2b
nde1ro
nder5s1t2e
nde4r1s
nders1t
nde2se
ndes1
ndi2a3
n1di
n2d1o1b
n2do2b2e
nd2o1c
nd1op
nd1or
n2d1ö
n2d3r2a1t
nd2r4
n2d3re
n2d3ro1b
nd3r2ol
nd3ros
n2d3rö
n2dr2ui
nd1ru
n4d3run1
nd2so2r
n2d1s
nd1so
nd2sp2r2
nd1sp2
nd4s3ta1b
nds1t4
nds2ta
nds3ta2u
nd3t1h
n2d1t
nd1t4r4
n2dü4
ndy3
1ne
3ne.
ne2a1p
n2e1a
ne3as
ne3a1t
n2e2bl2
ne1b
2n1e4b3n2
2nec
3n2e3ca
ne1c4k
3ne1d
ne2d2e
2n4ee3
ne2e2i4
ne3ein
n1e1f
ne1g4
2ne2he.
ne1he
2nehen2
3n2e2h1m
4n1e2hr
2n1ei
n2e2i1d
4ne2if
3n2ei2g1t
ne2i1g
4n3ei4n3g2
4n3ei2n3k
ne2ke
ne1k
n2e4k3t4
ne2l
3ne1la
ne2l3b
2n1e1le
4n1ele1k
4n1e2le1m
ne3len
n2e3li
nel4la
ne4l1l
3n2e3l2o
3n2e3lu
n2em.
ne1m
2n1e2m1b2
n1e2mi
2n3e2m1p
2n1e2m1s
3nen
n4en.
ne2n3a2
n2e2n3b4
n2e2n1c
4n1en2d1b4
ne2n1d
4n1en2d1d2
4n1en2d1f4
n1en2d1g2
4n1en2d1h2
4n1en2d1k4
4n1en2d3p2
4n1en2d1t
4n1en2d1w
ne2n1e2b
1ne1ne
ne2n3ei
3n2e3nen1
1ne4ne1ne
4nen2g1b2
ne4n1g
nen4ge.
nen1ge
nen4g2en
4nen4g2s1
4nen2g1t
n2e2n1h4
ne2ni
n2en1j
ne2n3k
ne2no
n2e6n1s
nen1s4e
nen3s1k2
5n2en3t2a
ne4n1t
n1en4t3b2
4n1en4t3l
4n1en4t5n4
5nen1t2r4
n1en4ts
4n3en4t3w
4n1en4t3z2
ne2n3u
n2e2n1v2
n2e4n1w
ne2o2b1
n2eo
ne1o1s2
2ne3p2f4
ne1p
2n1e1p2o
ne2pos
n2er.
ne1r1a
ne2r1a2b
ne3r4al
ne2r3am
ne2ran
ne2r2a1p
ne2ra2u
4ner3be.
nerb2e
ne2r1b
4nerben
n1er1bi
ne1re2
ne2r1e1b
n1e2r1f
4n5er1fo
ner3for4
2ner1fü
3nerg2r4
ne4r1g
n1er3h4
2n3e2r1h2ö
3n2e1ri
n1e2r1k
n2er3l2i
ne4r1l
2n1er1l2ö
n1er3mä
ne4r1m
ner4mit
ner1mi
n2ern.
ne4rn
4n1er2n1t
ne2ro
ne1rös
ner1ö
n2e2r3p4
3n2ers.
ne4r1s
2n3er1s2a
ner8sch2le
ners4c2h2l2
ner1sc
nersc4h
n2ert.
ne4r1t
n1er1t2r4
ne2rup
n2e1ru
n2e2r1v
2n1e2r1z
3n2es
n4es.
ne3s1an
ne1sa
ne1s4c
ne3s1ka
ne2s1k2
ne1s1o
n2e2s1p2
4n3es1si
ne6s1s
ne1s2ta
nes1t
nes3ti
ne2ta1d
ne1t
ne1ta
ne2t1ak
ne2t1an
n3e2ta1p
n1et4a1t
ne2ta2u
ne2t2h
ne2t3h4a
nett4sc
ne4t1t
net2ts
n1e2tu
net2zi
n2e4t1z
ne2u
ne2u1c
ne2u3g
2n1eu1p
neur2
n2e1w
2n1ex
3ne1z
1né
2n1f
nf1ak
n1fa
nfa4l1t4
nf2al
n1f2ä
n2f1f4
n3fi
nfi4le.
nfi1le
nf4l2
nf5lin
nf1li
n1f2o
nfo1s
nf4r2
n4f3s
nf1t2o
n4f1t
nf2t4s3
n2f1u
4n1g
ng2a4b1s
nga1b
n2g1a1c
ng1a1d
n2g1a1k
n2g3a2m
n2g1a2n3d
ng2a2n1f
ng1a2n1z
n2g1äl
n2g3d4
n3ge1f4
n1ge
n2g1ein
n3g2en
nge3n2a
n3ger
nge4ram
nge1r1a
n4g3er3se
nge4r1s
nge4zän
nge1z
ngezä2
n4g3g4
ng3hu
n2g1h
n2g1i2d
n3gläs
n2gl2i1c
ng1li
n2g1lo
n3g2l2oc
n2g1lö
n2g3m2
n2g1n
ng3n2e
ng1or
ng3r2a1t
ng2r4
n2g3r2o1c
ng3s3c
n4g2s1
ng4s3e4h
ng1se2
ngs3p2a
ng1sp4
ngs5t4ri
ngs3t2r4
ngs1t
ng3t2s
n2g1t
n2gum
n1gu
2n1h4
n3han
n3har
n3ha2u
n3hä
n3he
nhe2r
n3hu
1ni
3ni1a
nib4l2
n2i1b
ni4c4h1s
n2ic
nic4h
nich8te4r1s
ni2c4h1t
nich1te
n1i1d
3n2id.
ni2de
n2i3d2r4
n4ie
nie3b
ni1el
ni2e3l2a
n2i1e4n3
ni3e1ne
ni1e1ro
nifes3
n2if
ni1fe
nig2a
n2i1g
2n3i2g2el
ni1ge
nig3r4
ni2g1re
nig4sp4
ni4g2s1
3n2i1k
ni2kal
ni1ka
ni2kar
ni3ker
n4ike
ni4k3i4n1g
nik1in
ni3k3l2
ni2k2r4
3n2il
n2i1m2o
4n1i2m1p
nin1
3n2in.
n2i1n4a
4n3i2n1d
2ni2n3f
3n2i4n1g4
4n1i2n1h4
ni2n1or
n2i1no
2n1i6n1s
n2ins.
4nin1se
4n1i2n1t
2n1i2n1v2
ni2o1b
n2i1o
ni3o3k4
ni3ol
n2ip
ni3r2a
3n2is
ni4s4c2hw
ni1sc
nisc4h
n2i2s1e
ni3se.
ni2s1p2
ni3spi
ni6s3s4
ni2s1u
2nit
ni2ti
ni3t4r4
ni2t4s
ni3t1sc
nit2ts1
ni4t1t
nitt4sa
ni3tu
n2i3v
3n2i1x
n1j
2n1k
n2k3a1d
n1ka
n2k1ak
n3k2al
n4k3a2l1g
nk2am
n2k1a6n1s
n2k1a2us
nka2u
n2k1äh
n1kä
n2k1är2
n2k1e2c
n4k3er1fa
nke2r1f
nk4e4r1g
n2k1i2n1h4
n2k1i6n3s
nk3len
nkl2
nk2le
nk3les
n2kl2ie
nk1li
nk2lo
nk2lu
nk3lu2n1
nk4na
nk2n2
n2k3ne
n2k1o4r1t
n1ko
nk2ö2f
n1kö
n2k1öl
n2k3ro
nk2r4
nk2s1al
n2ks
nk1sa
nks2ei
nk1se
nk3s2z
nk2tak
n4k1t
nk1ta
nk2t1an
nkt1it
nk1ti
nk4top
nk1to
nk2t1ru
nk1t2r4
2n3l2
2n1m4
nm2e6n2s
n1me
4n1n
nna2be
n1na
n2n1a1b
n2nada
nna1d
n4n1a4l1l
n2n1an
n2n1a2u
nne4n3g
n1ne
n3nen
n4n1en4ts
nne4n1t
nn2er3h4
nn2e2r1k
nne2r1ö
n4n3er4w2a
nne2r1w
n2n1e2r2z
n3n2e2s
nne1s1e
nne4s1t
n2n2ex
n2n3f
n4n1g4
n3ni
n2n1of
n1no
nn1o2r
nn3sc
n6n1s
nn3se
nn3s2p4
nn2t1h
n2n1t
n2n1uf2
n2n1u2n1f
nnu2n1
n2n1ur
1no
3no.
3nob1l2
no1b
no2bla
n2o3b2le
2n1o4b2s
n2o1c
2no2d
n2o3d2r4
n1of
2n3o2fe
n3o1le
n2ol
no2le2u
n2on.
3n2o1p2a
3n2or.
nor2a
no2r2a1d
n2o1rak
n2o3r2al
2no2r1c
n2or2d5r4
no2r1d
3n2or1h4
3n2o4r1m
3n2o4r1s2
n1o4r1t
3n2os.
n2o3s1h
n2o2s3p2
n2os1t2e
nos1t
nos2t2r4
2no2s4t3v
no3ta1b
n2o1t
no1ta
no2tä
no4t3e1i
no1te
no2tel
n4o3t3h
no4th4a
no4t3hi
no2t3in
no1ti
no2t1op
no1to
no2t2r4
3no1v
3n2o1w
2n1o2x
3n2o1z
2nö1d
2n1ö2f
4n1ö4l
2n3p4
npa2g4
np2a
n1p4ro1
np2r2
n3p2s4y3
n2p1s
2n1q
2n3r2
nrä2u3s
nrä2u
nre3s3z
n1re
nrö2s1
6n1s
n2s1a2d
n1sa
n2s1a4l1l
n2sa4n1g
ns1an
n2sa2n1t
n2sa2us
ns1a4u
n3s3a1v
n2s1än
n1sä
n2s1ä2us
ns2ca
n1sc
n6s1che1f
nsc4h
n4schro
nsc2h2r4
nsch7we2r1d2
ns4c2hw
ns1e1b
n1se
ns1e2d
nseh5e1re
n3seh
nse3he
nse3her
nsen4sp4
6n3s2e6n1s
ns1e4n1t
n2s1e1p
ns1e2r1f
ns1e4r3g
n2s1er3h4
n2s1e2r1k
n2s1er1ö
ns1e4r1s
n2s1e2r1w
n2s1e2r1z
n3se2t
n4s1e1ta
n3s1ex
nsfi4l
n4s3f4
ns1fi
n4sho2f
n2s1h
n3sil
n1si
n2si2m1p
n2s1i1n1i
nsi4te
n3s2it
nsi2t3r4
ns2kal
n2s1k2
ns1ka
n2s1op
n1so
n4s3ort.
nso4r1t
n1sp4
n4s1p2a1t
nsp2a
n4s1p2e1ri
nsp2e
n4s1ph
n3s2pi
n3s4pi4e
n2s1p2o
ns3pon
n4sp4rä
nsp2r2
n4s3pr2i4e
nsp1ri
n4s1p4ro
ns1rü2
n4s3r4
n6s3s2
n2st1ak
ns1t
ns2ta
n3star
n3st2a1t
n4s3tat.
n4s3ta1t2e
nst3e2if
ns1t2e
ns2te2i
n3st2e2m1m
n2ste1m
ns4te4n1t
n4s3ten
ns6terb2e
n2ster
nste2r1b
n5s6ter3ne
nste4rn
6n5s6ter6n1s
nst4er1ö
ns2ti
nst5o2p1fe
ns2to
ns4tor
n4s3trac
ns2t2r4
n4str2ie
ns3t1ri
ns2t2u
n1s2t2ü
ns4t1ü1b
n2s1ty1
ns2um
n1su
n2s1u2n1
ns2u4n1g
ns4u2n3r2
6n1s4u6n1s2
n3s4y
n4s3zi
n2s1z
2n1t
n4t3a4b1s
n1ta
nta1b
n3t2a3c
n3t2al
n2ta3m
n2t1a4n1g
n4t1anza
nta2n1z
n2t2a2r1b
n4t1a2r1k
n2t2a4r1m
nt4a1t
n4t1äm
n1tä
n2t1äu
nte3a2u
n1te
nt2e2a2
nte2b
nt1ebe
nt4e1e
nte3g6
n2t1eh
n2te2i1g
n3t2en
nt4e1ne
nten6te.
n1t4en1te
2n1te4n1t
n3ter
nt4e4rn
nt4e4r1s
nt4e4r1t2
n4t1e6s3s2
n3tes
nteu3
nt2e3v
nt2her
nt1h
nth2e
n2t3ho
n3t2h2r2
n3t4hu
nt2i3c
n1ti
nti3k4l2
n3t2i1k
n2t1i2n3f
n2t1i2n1h4
nti1ni1
nt2i6n1s
n3ti1t
n5t4le1m
n4t3l
nt1l4e
ntmen2
n4t3m2
nt1me
nt1mo2
n3to
nto3m1e2
nt2om
nto6n2s1
n3ton
n3tö
n4t3rec
n1t2r4
nt1re
n2t3re2if
n5tre1p
nt4r2i1g
n3t1ri
n5t4rop
n2t3rü
n4ts
nt3sa
nt4s1a4u
nt1s2o
nt1s2p2
nt4s3par
ntsp2a
nt1s2t4
nt2s2to
n3tu
3n4tu.
ntum4
ntu2ra
ntu4re.
nt2u1re
ntu4res
n3tü
n4t3z2
1nu.
1n2u1a
nu3ar
nu3b4i1
n2u1b
1n2u1c
1n2u1d
3n2u1e
nu2es
nuf2
nu2fe
1n2u1g
2n1u1h
1n2ui
nu3k4
n2um.
2n3u2m1b2
2n1u2m1f4
2n1u4m1g2
3n4u2m1m
2n1u2m3r2
2n1u2m1s
2n3u2m1z
nu2n1
2nu1na
1n2u4n1g4
3nung.
n3ungl
2n1u1ni
2n1u2n1t
1n2uo
2nup
2nur
3n2u2s
nu3sc
nu3se
nu3s1l2
1n2u1t
nu2ta
nu4t3r4
1n2u1u2
1n2u1x
1n2u1z
3nü.
2n1ü4b
nü2r1c
3nüs
1n2üt
2n1v2
n3ver1
n1ve2
4n1w
1ny.
1ny1h
2ny1mu
n1yo
1ny1r
1nys
1ny1w
2n1z
n2z1a2d
n2z1a4g
n2z1an
n2z1a2u
n2z1än
nzä2
n2zär
nzdi1s
n2z3d2
nz1di
nz1ec
n4zen1s2e
nze6n1s
n4z1en4t3w
n3ze4n1t
n4z1en4t3z2
nz3erwe
nze2r1w
nzi2ga
nz2i1g
nzi4g4s1
nz1i1ni
n2z1or
nz2öl
n4z3s2
n2z1u2r1k
nzu1
n2z1w2a
nzw2
n2z1w2ä
n2zwö
n2z1w2u
ño1
2o3a2
o4a1bi
oa1b
o4ac
oa3che
oac4h
oa3chi
o4a1d
oa3de
oa4g
o4ah
o4a3i
o4a3ke
o2a4k3l2
o4a3la
o4a3mi
o2ar
o2as
3o4a3s2e
o4a1t
o5a2u
o1b
o3b2al
2oban
o3bar
2o3b2ä
2o4b1b
ob2e
2o3be.
2o3b2e1a
ob3ein
2o3b4en
obe2n3d4
oben3se
obe6n1s
ober3in4
ob2e1ri
obe4r2is
2o3b2e1w
2o3b2i
obi4t
ob3i1te
1o4b1j
ob1l2
o2b3li
2o3b2lo
2o3bo
o2b3re
ob2r4
o3b1ri
ob3s2h
o4b1s
ob3s1k2
ob1s2p2
ob2s2ta
obs2t
ob3s1z
2o3bu
ob2u2s
2o3bü
2o3by4
2oc
o3ca
o2c1c
o1ce
och1a
oc4h
o2cha2b
o1che
oche4b
o2ch1ec
o4ch1ei
oche2r4k
o2c2h3l2
o4c2h3m
och1o
oc1h3ö2
oc2h3r4
o4c4h1s
o2c4h1t2
och3te
o1chu
o2chu2f
o4c2h1w
o1ci
o1c4k
o2cka4r1
oc1k1a
o3cke
ock2er
o3cki
o2c1k1o2
ock3s1z
o4c2ks
o1c4l2
o1ç
o1d
o3d2a
od2d2r4
o2d1d2
o3de3b4
o1de
o3d2e1i
odein3
ode2n1
ode1ne4
od2e3sp2
odes1
o3dex
2o3di1a
o1di
o3dir
o3d2i5v
o2don
odo4s
2od2r4
o2d1re
o2d1t4
2o3du
2o1e2
o2ec
oen1
o4e3s
o2e3t
o3et.
o3e2ts
o1ë
2o1fa
o3f1a2c
of1am
of1a2u
o2f1ei
o1fe
of2en
o3fer
of2f1a
o2f1f
of2f1in
of1fi
1of3f2i1z
of2f5l2
of2f3r2
of4f2s2
of2fu
2o1fi
of3l2
of1la
o1f4lä
of4lö
2o1fo
2o1f2r2
of3ra
of3rä
of4rü
of1s1a
o4f1s
of4sam
of2sp2e
of1sp2
of2s1p2r2
of2s1u
2o4f1t
of2tei
of1te
of3t1h
2o1g
o2g1a1b
oga3d
og1a1l2a
og1a4n1g
o2g1ei
o1ge
og2e2l1i
og2el
o3g1h
ogi2er
og2lo
o3g4n
o4g2s2
og3sp4
og1s1t2e
ogs1t
o1ha
o1hä
o1he
o2h1eis
ohe6n3s
o2h1e4r1t2
o2h1e2r1z
o1hi
ohl1a
o2hl
ohl3a2u
oh3lec
oh1le
ohl1ei
oh3le1m
oh3len
oh3le1p
oh4l1e4r1g
oh3ler
oh4l3er3h4
oh4l1e2r1w
oh3lo
ohl1s2e
oh4l1s
oh2lu
3oh4n1g
o2hn
oh2ni
1oh2n1m4
oh2n1o
o1ho
oho2la
oh2ol
o2h1o2p
o2h3ö
ohr1a
o2hr
oh4rin
oh2ri
oh1ro
o4h1s
o4h3t
o1hu
o2h1w
2o1hy
2oi
o1i2d
o3ie
o1im
oim1mu4
oi2m1m
o1in
oi2r
o2i1sc
o3i6s4ch.
oisc4h
o1i2s3m2
oi6s1s2
oi1t1h
2o1j
2o1k
oka2la
o1ka
ok2a1le4
3o2kel
ok2i2o
ok1lä
okl2
ok4n2
4ok2r4
ok2s1p2
o2ks
o4k1t4
2ol
o1la
o2la1b
o2l1ak
ol2ar
ola4r3s2
ol1auf
ola2u
o1lä
ol4dam
o4l1d
ol4d3r4
ol4e3e
o1le
ol1ei1e
ol1eis
oler2
ole3s
ol1ex
o1lé
ol2fa
o2l1f
ol2f2l2
olf2r2
ol2fra
ol2gl
o2l1g
ol2g2r4
o1l2i
ol2i3k4
ol2k2l2
o2l1k
olk3r4
ol2k1re
ol2lak
o4l1l
ol2l3a2u
ol2l1e2c
ol1le
ol2l1ei
ol2lel
oll5en2d1s
olle2n1d
ol4le2r1k2
oll5erwe
olle2r1w
o3lo
ol2of
olo3p2
ol1o4r1t
ol2s4t2r4
o4l1s
ols1t
o1lu
3o1ly
1olym
ol2z1a
o2l1z
ol4z3e4rn
ol2zin
ol2z1w2
2om
o2m1a1b
o1ma
oma4ner
om2a1ne
om2a4n1w
om1a4r1t
o2m1a2u
o2me1b4
o1me
om2e3c
o2m1ei
o3m2eis
o2mel
o3m2en.
o2me1p
o2m2e1ru
om1e2r1z
o3m2es
omi4e1t1
o1mi
o2m1i2n1d
om1i4n1g
o2m1i2n1t
om3ma
o2m1m
om1o4r1g
o1mo
om3pf
o2m1p
o2m1s2
om1tu3
o4m1t
o4m1u2n1t2
o1mu
o3mun1
o1my1
2o1na
o2n1a2b
o2n2a1e
o3nal
on1a1p
o2n1a2r1b
on1ar
on2a2u
on3a2us
2o1nä
onbe3
o2n3b4
2o2n1c
ond2erer5
o2n1d
on1de
onde1re
2o1ne
o2n1e2i
o3ne2n3
on2e6n1s2
on1e2r1b
o2n1e2r1d
on1e4r1g
on1er1ö
o3ne4t1t
one1t
o2n3f2
on3g2l
o4n1g
ong4r4
on4g3s1
4o3ni
on2i3d
o4ni2k2r4
o3n2i1k
o4n1im
o3n3i4n1g4
onin1
o2n3k2
on1li4
o2n3l2
onl2o2c
on1lo
on3n2an
o4n1n
on1na
on3n2e
o1no1
o3no2d
o2noke
on2o1k
o2n1o2r1c
ono3s
on1s1a
o6n1s
onsa4g
on4sam
on2s1e1b
on1se
onse2l
on1si2
on2s3l2
on1s1p4
on4st2h
ons1t
on3t2a
o2n1t
o2n1t3a2n1t
on4t3e2n1d
on3t2en
on1te
ont3e2r1w
on3ter
ont2h
on4t3ri
on1t2r4
on4t3s
o1nu
2onu3k4
o2n3v2
1ony
o2n3z
o1ñ
oof2
oo2k3l2
o2o1k
o1op
o1or
o2o2r3f
oo4s1k2
oo2t2r4
o2o1t
2o1ö2
o1p2a
opa1b4
o2p3a1d
op3a4k1t
o1pak
o3pan
opa5s
o1pec
op2e
o1pei1
o1pe4n
2o4pf.
op2f3a
op3f4ah
o2p1fe
op4fe2r1d
opf5er3de3
opf2l2
opf3la
op1f4lü
4o1ph2
o3phe
o1pi
opi5a4
opi3er.
o3pier
opi5ers.
opie4r1s
opin2
op5la1g
o1p2l4
o3p4la
o2p3le
op3li
2o3p2o
op4p3l4
o2p1p
op2p2r2
2o1p2r2
1op1si
o2p1s
op3s1z
1o2p3t4
o1q
2or.
or1a
or3a2b
o1r2a1d
2orak
2or2al
o2r3a2l1m
or4a4l3t
3oram
or2a2n1d
o2r1a2n1h4
o2r3a2r1b
or1ar
o1r2as
or3a4t1t
o1r2a1t
o3rä
or1ä2n1d
or1ät
or2bar
o2r1b
orb2l2
o2r1c
2or1ca
or2ce
4orda
o2r1d
or2d3am
or2da2u
or4d3e4n3g
or3den
or1de
or2deu
or2d1ir
or1di
or2d1it
1or4d5n2
or2do
2ord2r4
2or2d1s
ord3s2t4
or2dum
or1du
2or2d1w
4o1re
ore4as
or2e1a
o2r1e2c4k
o2r1e1f
ore2h
o2r1e2i1g
o2r1ein
or1er
o2r1e2r1f
or1et2h
ore1t
2o2r1f
or2f1le
orf2l2
or4f3s4
or3ga
o4r1g
2orge1t
or1ge
or3g2h
2orgi1a
orgi1e
or2gl
or3g2l4e
or2g1n
2or1h4
2o3r2ic
o1ri
4orie.
or2ie
o4rie4n1t
or2i1en
o3rier
4oril
4orin1
2or2it
or1k2a
o2r1k
or2k3ar
or2k2s
2o4r1m
or4m1a6n1s
or1ma
or4me4n1t
or1me
or5ne.
o4rn
or3ne
or3n2o1
2o1ro
or2o3n2a
or2on
2o1rö
2or1q
2o2r1r
orr4a
or3r1h4
2o4r1s2
or3s4a
orsch5li
ors4c2h2l2
or1sc
orsc4h
or3s1h
or3s1z
or2t1ak
o4r1t
or1ta
or4t1an
or2t1a2u
or2t1är
or1tä
or2te1f
or1te
ort3e2i1g
or4t3e4n1t
or3te2n1
or4t3e1re
ort3e2r1f
or2t3e1v
or2th2e
ort1h
ort3i6n1s
or1ti
or4t3o2f1f
or1to
or2t1or
or4tö
or4tra2u
or1t2r4
or4t3rä2u
ort3re
or4t3r2ic
or5t1ri
or2t1um
or1tu
o3ru
o3r2uf
o4r3un1
o2r3ü
o2rya
o1ry
2o3s2a
os3a1d
os4an
osa1s
o3sche
o1sc
osc4h
o2s4co
2o3se
o3s4e3e
o2s1ei
ose2n
o4s1en4ts
ose4n1t
2o2s1h
o3s2hi
o3sho
2o1si
o3s1k2
o4s1ka
os3ke
o4ski
2os2k4l2
2os2ko
os2lo
o2s1l2
2o1so
2o1s1p2
os2p2e
os3pec
o3s2p2o
os2sa
o6s1s
oss3a2n1d
oss1an
os4s2ä
os2sei
os1se
os4s3e2n4k
os4s3e2n1z
os2s3o
os4s2on
os2s3p2
os2s3t
ost1a2b
os1t
os2ta
os4t3am
os2t3a4n1g
os3t2a2r1r
os4ta4s
ost1a2u
os4te2i
os1t2e
oste1r3e
o2ster
os6t5er6we
oste2r1w
o4s2t3h
os3til
os1ti
os3to
os4t1o1b
ost3ran
os2t2r4
ost3rä
ost3re
ost3r2o1t
o3s4t3uf
os1t2u
2o1su4
2o3s4y
o3s2ze
o2s1z
o2ß1el
o1ße
o2ß1e2n2k
o2ß1e2n1z
o2ß1e1re
o2ß1e2r1f
2o1t
ot3a2go
o1ta
o3t2a1g
o5ta2r1k
o2t1a2u
ot3a2u1g
o2te1b
o1te
o3t2e1i
o2t1ei4n
o3te2l1a
ot2e4lei
o3te3le
ot4e1m3
ote2m1p2
o2t1e2r1w
o3te2s
4ot2h
ot4h2e
ot5hel
o2t3hi
ot3hos
o1t2ho
o2t2h2r2
o2t1i2m
o1ti
ot2in
o6t5li2
o4t3l
ot4ol
o1to
ot1opf
ot2or
oto2r1a
oto1s
o3tra
o1t2r4
o2t3re
ot3rin
o3t1ri
ot2sa
o2ts
ot3sc
ot1s1p2
ot4sp2a
ots2p2e
ot2sp2r2
ot4te2r1k
o4t1t
ot1te
ot2t1h
ot2t3r4
ot4t1ri
o3tü
o2u
o2u1b4
ou2ce
o2uc
ou1f4l2
o2u1g2
ou2ge
ou3g1l
o3u1h
ou4le.
ou1l
ou1le
o3um
o3un2d1s
oun1
ou2n1d
oun4ge.
ou4n1g
oun1ge
2our
ou1ri4
our4ne.
ou4rn
our3ne
ou3s2i
o2us
ou1tu4
o2u1t
2o2u1v4
2o1ü
o1v
ove3s1
o1ve2
2ovi
ovi3s2o3
2ovo
2o1w
o3wec
owe2r1
o3wi
o1x
ox2a
o1x2e
1o2xi1d
o2x3l2
o2xu
1o2xy
o1yo
oy1s4
2o1z
o3z2a
oz2e
ozen4ta
o3ze4n1t
o3zi
ozon1
ó2r1d2
ö1b
öbe2la
ö3bel
öb2e4li
öb2l2
ö2b2le
ö2b3r4
ö4b2s3
2ö1c
ö2c2h1l2
öc4h
ö2c2h2r4
ö4c4h2s
öchs4t2u
öchs1t
ö2c4h1t4
ö1d
ö1di3
öd2s1t4
ö2d1s
ö1e
1öf
öf2f3l2
ö2f1f
öf3l2
öge6n2s1
ö1ge
ö3gen
ög3l
ög3r4
ö1he
öh3l2e
ö2hl
öh3ri
ö2hr
ö1hu
ö3ig.
ö2i1g
ö1ke
ö2ko3
ök3r4
3öl.
öl1a2
öl1ei
ö1le
öl1e1m
öl4en
öl2f1ei
ö2l1f
öl1fe
ö2l1im
ö1li
öl1in
öl2k3l2
ö2l1k
öl3la
ö4l1l
öl2n1a4r
ö4ln
öl1na
ö1l1o2
ö4l1s2
öl3sa
öl3s1z
ö2l1u
öl2u4n1g
ö1lu2n1
ölz2w2
ö2l1z
ö2m2s
2ön
ö1n2e
ö3ni
önizi1
ön2i1z
ön1n2e
ö4n1n
ö6n2s
ön3sc
ön3sp4
ö1nu
öo1
ö1p2e
öpf3l2
öp4s3t
ö2p1s
ör3a2
ö2r1c
ör2d2r4
ö2r1d
ö2r3ec
ö1re
ö2r1ei
ö2r1e2l
ör2e4r1g
ö2r1e4r1l
ö3r2e2r1z
ör2f3l2
ö2r1f
ör2gl
ö4r1g
ö2r1im
ö1ri
ör2kl2
ö2r1k
örner2
ö4rn
ör3ne
ör1o2
ör1s2e
ö4r1s
ör3s2k2
ör1t2e
ö4r1t
ör2t2r4
ö1ru4
ö2r1u3ne
örun1
ö2sa
ö2scha
ö1sc
ösc4h
ö4s4ch3ei
ö2s4c2hl2
ö2s4c2h3m
ö2s4c2hw
ö2s1ei
ö1se
ö2sp2
ös2s1c
ö6s1s
ös2s1t
ö2s1t
ös3t2e
ö4s2t1h
ös3t2r4
ö3su
ö1ß
2ö1t
ö2t3a
ö3te4n3
ö1te
öt2h
ö2ts2
öt2sc
öt2t2r4
ö4t1t
ö1v
ö1w
ö1z
öze3
özes4
p2a
1pa.
1p2aa
1pac
pa3da
pa1d
pa2d2r4
pa1f4r2
pa1g4
pa3g1h
pa1ho
1pak
p2a1k4l2
pak2to
pa4k1t
3pa1la
p2al2a3t
1pa1lä
p2a3li
2pa4l1t
pa2n1ar
pa1na
pa3n1ei
p2a1ne
pa2ne2u
pa2n3k4
2p1a2n3l2
2p2a4n1n
1pa2no
pan3s1l2
pa6n1s
pa2n1t2
pa2n1z4
1pa1p
papi2
papi2eren8
pa3pier
papi2e1re
papie8r7e2n1d
3pa1ra
pa2r3af
par3a4k1t
1p1a2r1c
pa5re1g
p2a1re
pa5re1k
2par2er
2pa4r1g
parge4l6d
par1ge
parg2el
1park.
pa2r1k
par4k2am
par1ka
par4ka2u
par2kl2
par2k2r4
1p2a1ro
2pa2r1p2
1par4t5n4
pa4r1t
1par1ty1
pa2r3z2
pa3s2p2
pa4s1t
2pa1ß
1p2a1t
pa4t4c
pa1t4e2
p4a5t4r4
1pa2u
p3auf
pa3u1ni
p4aun1
1pä
3pä2c
pä3ck2e
pä1c4k
3pä1d
3pär
3päs
pä4t1e2h
pä3te
pä4t3e4n1t
pä3te2n
pä2t3h
pä2to
pä2t3s4
2p1b
2p3c
2p1d2
pda4
p2e
1pe.
p2e2a
pea4r
pec4h1
1pe1d
pe2en
p4ee
pe1f4
pei1
2pe2ic
pe1im
pek2t4s4
pe1k
p2e4k1t
2pek2u
3pel
pe2l1a4
pe4l3d
pe2le1t
pe1le
pe2le2x
pe3li4n
p2e1li
pe4l3i2n1k
pell2a
pe4l1l
pel3l4e
1pe1m
pe2n1a4
pe3n2al
pen3da
pe2n1d
p2e4nen
pe1ne
1pe4n1n
pe2n1o
3pen1si
pe6n1s
1pen1su
pe2n3z2
1pe1p
pe1r1a
per2an
pe1re2
1pe4r1l
per4na
pe4rn
3pe1ro
pe2r1o2b
per2r1a
pe2r1r
5pe4r1s
perw2a4
pe2r1w
pe3sa
pe6s3s2
pe2s1t
3pe1t
1pé
4pf.
p2fa1b4
p1fa
p2fa1d
p2faf
pf1ai
p2f1ak
pf1a6n3s
p2f2a4r
pf3a1re
p2f1a2u
4p3fe.
p1fe
p2fei
pf1eim
pf1ein
p3fen.
p2f1e4n1t
p3f2er.
pf2e2r1w
p3f2es
p2f1f4
p2f1i6n3s
p1fi
p2f3lä
pf2l2
pf3lei
pf1le
pf3l2ie
pf1li
pf3lo
pf3lu
p2for
p1fo
pf3r2
pf1ra
2p4f1s2
pf3s1l2
pf3s1z
2p4f3t
2p1g
pgra2
pg2r4
1ph
4ph.
ph2a
2phä
2p2h3b2
4p2h3d4
2p1hei
phe2n3d
phe6n3s
2ph1e4r1s
2p2h3f4
4p2h3g2
phi2ka
ph2i1k
4p2h1k4
p2h2l
2p2h1m
2p2hn
p3hop
2p1h2ö
p2h4r
2p4hs
p4h3t2
2phth2e
ph2t1h
ph2u4s
2p1h2ü
2ph1z
pi2a3
p2ias4
pi3as.
pi3c2h3l2
p2ic
pic4h
p4i1d2
piegel4e2i8en3
pie1g
pie3ge
pieg2el
piege3le
pieg2elei
piegelei1e
pi2el
pi2e2l1a2
3pier
3p2i1k
1pil
pi3le
pil4zer
pi2l1z
p2i1n2e
pin3g2en4
pi4n1g
pin1ge
pin4g3s1
3pin1se
pi6n1s
p2i2o
pi3oi
pi3o1nu
pion2
3pip
pi2p2e
pi4pel
pi3r2i
3pirin
3pis
4pi1so
pi3t2a
pi1t1h
pi2t2s
2p2i4t1z
pi2z1in
p2i1z
p1j
2p1k2
pku2
pkur1
1p2l4
2pl.
3p4la
p5la.
p5la1d
pla4n3g
3plä
2p3le.
p1le
p2le1c
p4le1g
p4le1m
3ple5n4
2p3l2i1g
p1li
p4l2i1k
p4l2i1z
p4lo
2p3lu
2p1m2
2p1n
1p2o
po3b4
p2o1c
3po1d
2p3oh
p2o2i
po3i2d
3po1in
3p2o1k
3p4ol
po2la2u
po1la
po3l2i
po4lor
po3lo
2po2n1d
po1o2b
po2p3ak
po1p2a
po2p3ar
po1p2e
po2p2l4
p1o3p3t4
p2o1r2al
por1a
po1ra2u
2po4rn
p2o4r3s2
por4tin
po4r1t
por1ti
por4t3re
por1t2r4
por6t1ri
p2o3s2e
po4s2ta
pos1t
po2s4t3a1g
po4s2tä
po2s3t2e
pos4t3e2i
po2s3to
pos6t2r4
post3ra
po3ta
p2o1t
3po1te
po2t1u
p2o2w
po3x
pö2b2l2
pö1b
p2ö2c
2p1p
p2p3a2b
pp2a
p2p3a2n3l2
ppe4ler
pp2e
p3pel
ppe1le
ppe2n1
p2p1f4
p2p1h
p3p2ho
p1p3l4
pp5lan
p3p4la
p3p1lä
p2p1le
p2p3ra
pp2r2
p2p3re
p2p1ri
pp3sa
p2p1s
p2p1t2
p2r2
1prak
1pr2a1x
p4rä
1p3r2ä1d
1p4rä1g
3p2räm
3präs
2p3re.
p1re
2prec
1pre1d
pr4e2e1
1prei
3preis
2p3rer
3p4res
pr2i4e
p1ri
2p3r2i1g
1pri2n1z
1p4ro
3pro1b
2pr2o1c
3pro1d
3pr2o1g
3pr2o1j
2pro6s1s
pro1s1t
3pr2o1t
1prüf
p1rü
2pr2ün
2p1s
4ps.
ps4an
p1sa
p3se
p3s2h
ps1i1d
p1si
p2sö
ps2p2o
p1sp2
p2s1t
p3s2ta
p3st2e2a2
ps1t2e
p3s2tel
p3s2ti
ps2t3r4
ps2t2u
p3s2tü
3p2s4y
ps2ze
p2s1z
2p1t
p1t1a
pt2a1b
pt3a2l1b
pt3a1t
p3te
p4t3ec
p4t1ei
pte4l
p4te3le
p4t1e4n1t
p3ten
pt3erei
pte1re
p4t1e2r1w
p4t1e2r1z
p2t1h
pt1in1
p1ti
pto3m1e2
p1to
pt2om
p4tos
p3t2o2w
p2t3r4
p2t3s2
p4t1t2
pt1um
p1tu
pt1u4r1s
p2tü4
3p2ty1
p4t3z
1pu
p2u1a
p2u1b4
2p2uc
pu2d2r4
p2u1d
2p1u1h
pul2sp2
pu1l
pu4l1s
2pu2n1d
pun1
pu6n2s2
2p1u2n1t
2pur
pu2s3t
p2us
3p2u1t
pu2t2s
1püf
2pül
p2ün2
2p1v
2p1w
pwa4r
pw2a
3py1
pys4
py3t2
2p1z
qu4
1queu
q2u1e
1ra.
ra2a1b
r2aa
2r3aa1c
r3aal
r4a3ar
r1a1b
ra2bar
r2aba
rab2bl2
ra4b1b
2r1a2b3d4
r2a3ber
2r1a2b5f4
2r1a2b1g2
1r4a1bi
ra2b3r4
2r1a4b1s
2ra4b3t
2r3a2b1w
1r2a3by1
ra1ce
2r1a1ce1t
ra4che1b
ra1che
rac4h
ra4chi2n
ra1chi
rach1t3r4
ra4c4h1t
rach6trä
ra2chu
r2ac4k
r2a1d
r4ad.
ra2da2m
2r3a4d1a1p
3r2a2d1f4
3ra4d1l2
r3a2d3r4
ra2d5t1
1r2a1e
r2af
ra3f3ar
ra1f1a
ra2fer
r2a1fe
ra3ge
ra1g
ra3g2l4e
r2a2gl
ra2g2n
3r2a2h1m
4ra4h1t
r2ai
2ra2ic
rai4l4l
ra2il
2r3air
1r4a1ke
3r2a1k4l2
ra2k1re
r2a1k2r4
r3a2kro
2rak1ti
ra4k1t
3r4a3kü
r2al
r4al.
ra2la2
ral3a1b
rala4g
r3a2lar
ra2l3b
3r4a4l1d
r2a3le
2ra2l1g
r4a1li
rali5er.
ral2ie
rali5e4r1s
ra2l1k2
ral3la
ra4l1l
ral1l2e
2ral2l1g4
2r3alm.
ra2l1m
r3alp.
ra2l1p
2ralp2e
r4a4l1s
r3a4l3t
r4al2t2h
ra2lu
3r4a1ly
ra3ma3s
r2a1m2a
ra2mer
r2a1m4e
1r2a1mi
r2a2m1m
ram4man
ram1ma
ram6m5e4r1s
ram1me
ram4m3u2
ram2p3l4
ra2m1p
2r1a4m1t
ram4t4s1
r2an.
4ra2n1c
r4anda
ra2n1d
r4an1de
ran4de1p
ran4d3er
ran2d3s
4r3a2n1ei
r2a1ne
r4aner
2r1a2n1f
1rangi
ra4n1g
ran4i1e
r2a3ni
ran2k2r4
ra2n1k
2r1a2n3l2
2r1a2n1m4
2r1a2n3p4
2r1a2n3r2
r2ans.
ra6n1s
r2an1sp4
ran4s1p2a
2r1an1t2r4
ra2n1t
2r3a4n1w
r2a1p
2ra2pf
r1ar
r2a1ra
2r1a2r1b
3rarei
r2a1re
r2a2r3f4
ra2r1in
r2a1ri
r2a2r1k
r2a2r1p2
2r3a2r1z
r2as
r4as.
ra1s2a
ra4s4c2hl2
ra1sc
rasc4h
2r3as2ph
ra2s1p2
2ra1ß
1r2a1t
r4at.
ra2t1a1
r3a4t3l
r4a1t4r4
rat2s1t4
ra2ts
2r3at3ta
ra4t1t
4r2au.
ra2u
3raub.
r2a2u1b
4ra2u1d
rau3e2n1
r2a2u1e
2rauf
2r2a2u1g
3r4aum
rau4m3a1g
rau1ma
rau4man
rau2mi
3r2au1sc
ra2us
2r1au4s3g2
rau2sp2
2rau6s5s2
r2au2t5s4
ra2u1t
1r2a1ü
r2a1x
3r2ä1d
4räf
4rä1g
2räh
2räm
3r2än.
3r2ä3ni
3r2ä6n1s
2r1är
r2är.
rä3r3a2
rä2s1c
3rät1se
rä2ts
rä2u
rä2u2s
räu5sche
räu1sc
räusc4h
4rä2u1t
2r1b
r2b1a1b
r2b1a2de
rba1d
r2bak
rba1l3a
r3bal
rb2a3re
rb1a4r1t
rb1auf
rba2u
r4b1b2
rb1ec4h
r1be1c
r4b2e1lä
r3bel
rb1e4n1t
rbe3r2e
r3b2la
rbl2
rb3la2d
r8b3lasser
rblas3s2e
rbla6s1s
r4b3la2s1t
r3blä
r2b3le.
rb2le
rb3ler
rb2lin
rb1li
rb2lö
rb2o
rb4ri
rb2r4
r4b2s
rb3se
rb4sei
rb3s1ka
rb2s1k2
rb1s1o
rb3sp2
rb4s2tä
rbs2t
rb3s2t2r4
rb2u
rby4t2
r3by1
2rc
r1ce
r1che.
rc4h
r1chen
r1chi
r2c2h3l2
r4c2h3m
rc2h3r4
r4c4h1s2
rch3sp2
rch1s4t4r4
rchs1t
rch3t1a
r2c4h1t
rch6te2r1w
rch1te
r4c2h1w
r1ci
r2c4k1
r1c4l2
r1ç
2r1d
r3d2ac
r2d1af
r2d1ak
r2d1al
rd2am
rd2a3ni1
rd1a2n1t
rd1a2n1z
r4d1a1p
r2d1ei
r1de
rd2ei.
r2d1e2l1b
r3den
rde2n3d2
rden4gl
rde4n3g
rde3re
rd2er4er3
rde2r1i6n6s
rd2e1ri
r4d3er2n1t
rde4rn
rd2e3sp2
rdes1
rdi3a2
r1di
rd2ia4l
r2d1i4n1n
rd1it
r2do2b2e
rd1o1b
r3don
rd1os
r2d1ö
rd3r2a1t
rd2r4
rd4ri
r2d1t4
rd3ta
rd3t1h
rdw2a4
r2d1w
1re
3re.
re3aler
r2e1a
re2a1le
re2a4l1t
re2am3
re3as
re3at.
re2a1t
re3a2t3s2
2re1ä2
r2e2b1a
re1b
r2e2b1l2
r2eb2r4
reb3ra
re2bü
r2ec4h
rech3ar
4re4c4hs
2re4ck.
rec4k
2re1cki
3red.
re1d
4re2d1d2
2redit
re1di
re1el
r4ee
re1er
3r2e1fe
re1f
4re2f1f
3r2ef2l2
3r2e3f2o
3re1g
5reg.
reg2e4l3ä
re3ge
reg2el
re2hac
r2e1ha
re4h3e4n6t3
re1he
re2h1i
re2hl4
re2h1o
r2ei.
rei4bl2
r4e2i1b
r2ei1e
2re2i1g
3r2eige1w
rei1ge
rei3l2a
re4il
rei3l2i
re1i2m2p
r1ein
rei3nec
re2i1ne
4rei4n3g2
r3ei2n3k
4re2i2n3r2
1rein8s7t1re
rei6n1s
reins1t
reins2t2r4
re1i2n2v2
rei2ster6
reis3t
reis1t2e
3re1k
4re2ke
re3la
2r1e2l1b
re1l2e
r2e3lei
2r1e2le1k
2r1e2l1f
r2e3lo
2r1e4l1t
r2e1lu2
r4em.
re1m
r2e1mi
4r3em2pf
re2m1p
4re1mu
r4en.
r2e2n1a
re2n1a2b
re3nal
re2nä
3ren1di
re2n1d
ren3d2r4
re4n3e2n1d
r2e3nen
re1ne
ren4gl
re4n1g
2r1en2g1p2
re2ni
r1en1se
re6n1s
2r1en4t3l
re4n1t
2r1en4ts
2r1en4t3w
4r3en4t3z2
r2e2n1z
re3or
r2eo
3rep2e
re1p
3re1p2o
4re2p1p
3r4er.
2r1e2r1b
r2er3b2r4
2r1e2r1d
r1e2r1f
r1e4r1g
r4er3gen
rer1ge
r1e2r1k
4r3erken
r2erki
r1e4r1l
4r3erla2u
rer3l2a
2rer1l2ö
2r1e4r1m
re4r2n
2r1er1nä
4r3er6n1s
4r3er2n1t
r2e1ro
re2r1o2b
r1er1ö
3r2ers.
re4r1s
2r1er1s2a
r2er3se
2rer3s2p4
r1e4r1t
r2er1te
2rer1t2r4
2r1e2r1z
rer5ze
r2erzy
3r4es.
re2sa
3re1se
3re1so
2re6s1s
res3s2e
res6s5e2r1w
3res1t
re1s2ta
re2s2t2u
3re1su
re2t2hy
re1t
ret2h
re2u
re2u3g2
2reu1l
re3u1ni
r2eun1
2r1eur
2re3ü
2r3evi1d
r2e1v
r1e1w
rewa4r
r2ew2a
re2wi
2r3e2x1
3re1z
4re3zi
1ré
2r1f
rf1ä4l1t
r1fä
rf2äu
r2f1e4n1t
r1fe
rf2es
rfi4le.
r1fi
rfi1le
rf3l2i1c
rf2l2
rf1li
rf3lin
rf4lö
r3f4lü
rfol4g4s1
r1fo
rf2ol
rfo2l1g
r3for
rf4ru
rf2r2
rf4rü
rf2sa
r4f1s
rf2s1ä
rf4s1i2d
rf3si
rf2s3p2r2
rf1sp2
rf2s3t
rf2ta
r4f1t
rf3t4r4
r1f2u
4r1g
rg2a1b
r2g1a2d
r2g1ah
r2g1a1k
rg2an
rge4an
r1ge
rg2e3a2
rg2e2bl2
rge1b
rge4l3er
rg2el
rge3le
rgen4z3w2
r3gen
rge2n1z
rge4r2al
rge1r1a
r2g3e4ta1p
rge3t2a
rge1t
r2g2e3to
r2g3i4sel
rg2i1se
r2glan
r2gle2u
rg2l4e
r2g3l2i1g
rg1li
r3g2no
rg1n
r2g1o1b
r2g3r2al
rg2r4
r2g3re1g
rg1re
r2gres
r2g3re1t
rg3rin
rg1ri
rg3sp4
r4g2s1
rgs4t2r4
rgs1t
rg5s2t2u
r1h4
2rh.
2rha
r2ha.
2rhä
3r4he.
3r4hen
r3her
r2h2o1e4
r2h2o2i3
2rh2ol
2r1h2ö
2r4hs
rh2u2s
1ri
ri3am
ri1a
r2ia1s
ri3a1t
rib2bl2
r2i1b
ri4b1b
ri1ce1
r2ic
ri1ch1a
ric4h
ri1d2
ri2d3an
2ri2d2ol
r2ie
rie2f2r2
rie1f
ri1el
ri2e1ne4
r2i1en
rie6n3s
rie2n1u
ri1er.
ri4e1re
ri3ers.
rie4r1s
ri3es1ti
ries1t
ri1eu
ri2f1a
r2if
ri2f1e2i
ri1fe
ri2fer
ri2f1o
ri2f3r2
ri4f3s
rif4ter
ri4f1t
rif1te
3r2i1g
5rig.
ri4ge1ne
ri1ge
ri3gen
5ri2g1j
ri2g1l
4rig2r4
ri2k3l2
r2i1k
ri4kla
r2i2m1b2
2ri2m1p
ri2m2s
rim4sc
r2i3na
2r1i2n1d
r1in4dex
rin1de
rin4d2i3z2
rin1di
r2i3n4e
ri2n3e1i
2r1i2n3f
rin2f2o
rin2g3l
ri4n1g
rin2g2r4
2r1i2n1h4
2ri2nit
ri1ni
2ri2n1k
3ri4n1n
6r5inne2n1m4
rin1ne
rin3nen
4r3inner
4r1inn1ta
rin2n1t
r1innu
2r1i6n1s
3r4ins.
rin4so
rin2sp4
r4in3s2pi
2ri2n1t
r1in4te3g6
rin1te
rin4t5r4
2r1i2n1v2
4r1ir
r2is
ri1s2a
ri4scho
ri1sc
risc4h
ri4s4c2hw
3ris2i1k
ri1si
ri3so
ri4s1p2
3ri6s1s
ri2s3t
ris6t5e4r1s
ris1t2e
ri2ster
r2it
r3i2tal
ri1ta
ri3t2i
ri1t4r4
rit2t2r4
ri4t1t
5ri1tu
r2i1x1
1rí
2r1j
2r1k
rk2am
r1ka
r2k1äh
r1kä
r3kla2u
rkl2
r2klis
rk1li
rk4lo
rk2lu
rk4n2
r2k5nu
rk3rä2u
rk2r4
r2k3r2e1a
rk1re
r3k1ri
rk3rin
rk2s1e
r2ks
rk3shi
rk2s1h
rk2sp2
rk1s1t4
rkst4a3ti6
rk1s2ta
rk3st2a1t4
rk4stec
rk1s1t2e
rk2ta
r4k1t
rk4t3e4n3g
rk1te
rk3ten
rk4t3e2r1f
rkt3e4r1s
rk6ter1sc
rk4t3e2r1w
rk2tin
rk1ti
rk2t1o2
rk2t3r4
rk3tra
rk4t1ri
rk1u1h1
rk2um
rku2n1
rk1u1ni
4r1l
r3l2a
r1l2e
rl2e2a
r3lec
rle2i
r3le1t
r3l2i
rli2s
r3l2o
r1l2ö
rlö6s3s
rl2s1p2
r4l1s
rl3s1t2e
rls1t
rl2s3to
r4l3t
4r1m
r3m2a1g
r1ma
rma2la
r2m1a4l3d
rm1a6n1s
r2m1a2n1z
rm1a2p
r2ma1ph
rm2är
r3mä
r2m3d2
r3me.
r1me
r2m1e1f
r2m2e1o
r3m2es
r2mi1de
r1mi
rmi1d
r2m1im
r2m1o2ri
r1mo
r3mo1s
rm3sa
r2m1s
rm3s2ta
rms1t
rm1t2a
r4m1t
r1m2u
rm3u2m1s
4rn
r2n1a2b
r1na
rna4n
rn2a2n1d2
r2n3a3ni
r2n1a2n1z
rn2a2r
rn3a1re
r3n3a1ri
r2n1a2u
r2n1d4
rn3d2r4
r3ne
rn3e4b2en
rne1b
r4n1e1f
r2n2ei
r4n3e2if
r4n3eis
r3ne2n
r4n1e1ne
rn3en1s4e
rn2e6n1s
r4n1e2r1f
r4n1e4r1g
rn4erhi
rn1er3h4
r4n1e2r1k
r4n1e4r1t
r5n2es
rn2e1t
r4n1ex
r2n3f
r4n1g2
r3ni
r4n1in1
r3no2d
r1no
r2n1op
r2n1or
rn1ö
r1n2ö1t
rn3s2ä
r6n1s
rn3s2p4
rn3s2z
rn3t2e
r2n1t
r1nu
r2n1ur
r1nü
r1ny
ro2bei
ro1b
rob2e
2r1o4b1j
3r2o3bo
2ro4b1s
r2o1c
3ro4ck.
ro1c4k
r2o3de
ro1d
r2o3e4
2rof
ro2h1l
roh3na
ro2hn
3r2o2hr
3r2oi
ro1i2r
ro3le
r2ol
rol4la2n
ro4l1l
rol3l4en
rol1le
ro4l3s
2r3o1ly
4rom.
r2om
ro2ma1d
ro1ma
ro2mer
ro1me
4ro2m1m
4ro4m1t
r2on
ro4n1e2r1b
r2o1ne
3ro4n1n
ro6n1s2
ron4tan
ron3t2a
ro2n1t
4r1o1ny
ro1p2e
r4o3ph2
r1or
r2or1a
r2or3al
ro2r2a1t
ro2rei
r4o1re
r2o2r1o
ror3t1h
ro4r1t
r2o3s1h
r2o3s2i
ro3s1mo
ro2s3m2
ros2s1c
ro6s1s
ro3s2ta
ros1t
ros2t2r4
4roß
ro2ßu
ro4t2a1g
r2o1t
ro1ta
ro3t2e3i
ro1te
ro2t2ho
r4ot2h
ro4t1ri
ro1t2r4
rot1s2o
ro2ts
rot2ta
ro4t1t
ro3t2u
ro3u2n1t
ro2u
roun1
3ro2u1t
2ro1x
rö2b3l2
rö1b
rö2du
rö1d
2r1öf
4rög
1r2öh
r1ök
1r2öl
3rö1mi
4röp
r1ör
r2ös.
r2ö1se
2r1p2
r3p4a
rp4e
rpe2re2
rpe4r3in
rp2e1ri
rpf4
r2p1li
r1p2l4
r3p2o
r1p4ro1
rp2r2
rp2s3t
r2p1s
r2p3t
r3pu
r1q
2r1r
rr2a1b
rr2ar
r2r1äm
r2r1b2
r2r1c
r3r2e
rre4a1le
rr2e1a
rre4r4s
r3re2s1t
r4r1e1w
rr2he
rr1h4
rr2i1k2
r1ri
rr2n3a
r4rn
rr2o
r2r3o1b
rr2o3m
rr2s1t
r4r1s
rr3s2t2u
rr2t1h
r4r1t
r3ru
r3r2ü
r2r1ü1b
4r1s
r2s3a1b
r1sa
r2s1a2d
r4s1a2m1p
r3sam
r4s1a4m1t
rs2an
r2s3a4n1g
rs3a2n3p4
rs3a2n1t
r2s3ar
r3sch2e
r1sc
rsc4h
r6sche4r1l
rs4c2h2l2
r3s4chu
r3s4c2hw
r2s1ein
r1se
rse2n1
rs2e2n1d
rse4ne
rs1e1re
rs1er1ö
4r1s1e4r1s
rs1e2r1z
rs1e1ta
r3se1t
r3sho
r2s1h
r3si
rs2kal
r2s1k2
rs1ka
rs2kan
rs2kie
rs2ki1s
rs2k4l2
r4s1ko
r4sk2r4
r4sku
r2s3l2
rs4no
r2s3n4
r3so
r4s1o2b
r4s1op
r4s3o2r1d
r4s3ort.
rso4r1t
r1s2p4
r2s3ph
r6s3s2
r4sta2n1t
rs1t
rs2ta
rs2tec
rs1t2e
r6st5ei4n3g2
rs2te2i
rs2t1ein
rs4te2m1p
r2ste1m
rs4te2r1b
r2ster
rs4t3e2r4w
r4s2t1h
rs2ti
r3sti2e
r2stin
rst3i4n1g
r2s3tip
r3s2to
rs4to1b
r4st2o1t
r3s2tö
r3s4t2r4
rst3ran
r6stra4n1g
rs2t2u
r3s4tü
r3swi
r2s1w
r3s4y
4r1t
rtal2
r1ta
r2t1a2l1m
rta4l1s1
r2t1am
r2t1a4n1g
rt2a4n1n
rt1a2n1t
rt1a2n1z
r2t1ar
rt3a4re
r2t3a4t1t
rt2a1t
rt1är
r1tä
rt4e1e2
r1te
rt4e2if
rtei3la
r3te4il
rtei1s4
r2te2l1f
r2te1mo
rte1m
r3te2n1
rte6n3s2
rt3erei
rte1re
r4ter1fa
rte2r1f
r4ter1fo
r4t3er3h4
r2t1e2r1k
r4t3er4l2a
rte4r1l
r4t3er1l2e
r4t3er1nä
rte4rn
rter4r2e
rte2r1r
rt1e4r1s
rte3s2k2
r3tes
r2thi
rt1h
rt3h2ol
r1t2ho
rt2hum
r2t1i1d
r1ti
r2t1i1ma
r2t1i2n3f
rto1p
r1to
rt1or
rto2ri
r3tö
r4t3rak
r1t2r4
r4t3rec
rt1re
r4t3reis
r5t1ri
rt3ros
r2t3r2ü2c
rt1rü
r4ts
rt4s1eh
rt1se
rt2s1o
rt2sp2a
rt1sp2
rt2sp2r2
r4t1t4
4r2t1u4r1t2
r1tu
r3tü
r4t3z
1ru
r2u1a
ru3a2r3
rube2
r2u1b
ruch3s1t4
r2uc
ruc4h
ru4c4hs
ru6ck2e4r1l
ru3ck2er
ruc4k
ru1cke
ru2cku
rud2e2a
r2u1d
ru1de
ru2d2r4
3ruf
ru2fa
ru4f2s3
4r2u1g
2r1u2hr
ru1h
3ruin
r2ui
ru1i6n1s
ru1is
2rum
4r1u2m1f4
ru2mi
4r1u4m3l2
r2ums.
ru2m1s
4r1u2m1z
2r1u1na
run1
2ru2n1d
r1un2d1a
r2un1de
rund3er
run6de4r1l
run6de4r1s
run6de2r1w
2r1u2n1f
2rungl
ru4n1g
2r1u2ni
4r3un2i1o
run2k2r4
r2u2n1k
2r1u2n3l2
2r1u2n1m4
4ru4n1n
4r3u2n1t
2r1u4n1w
ru3p2r2
4r3ur
ru2ra
r2u2r1e
5r4u1ro
ru2si
r2us
rus2s1p2
ru6s1s
rus4s1t
ru2s1t
ru3s2ta
3r2u1t
ru4tei
ru3te
rut3h
ru2t1o2
ru2t3r4
4r2u1z
ru2zw2
1rü
2r1üb
rü1ben
rübe2
rü1c4h
r2üc
4rü2m1m
2r1v
rve4n1e
r1ve2
2r1w
rwu6n3s2
rw2u
rwun1
4r1x
1ry
ry2c2
2r1z
rz1a2c
rz2an
r2zar
r2z1as
r5ze1ne
rz1e4n1g
r4z3en4t3s
r3ze4n1t
r2z1e2r1f
r2z1e4r1g
r2z1e2r1k2
r2z1e2r1w
rz1i1d
r3z2of
rz1op
rz2ö
rz3te
r2z1t
rz2t1h
rz2t3ro
rz1t2r4
rzu1g2u
rzu1
rz2u1g
r3zw2ä
rzw2
r3z2wec
1sa
3sa.
3s2aa
2s1a1b
sa2be
3s2a3be1t
sa2b1l2
s2a3b2le
sa2b3r4
4s1a4b1s
sa2cho2
sac4h
sa4c4h3t
2s1ada
sa1d
s1a2d3m2
2s1a2d2r4
3sa1f1a
s2a2fe
2s3a2f1f
3s2a1fi
sa1f4r2
3s2aga
sa1g
sa4ge4n1t
sa1ge
sa3gen
sag4n
sa2g2r4
3s2ai
sa3i2k1
sa2il2
2s1ak
sa2ka
3s2a1ki
3s2a1k2r4
4sa4k1t
3s2al.
sa4l3e2r1b4
s2a1le
sa2l1i1d
s2a1li
3s2a1lo
sal2se
sa4l1s
2s1a4l1t
3s2a2l1z
3sam
s3a2m2e1ri
s2a1m4e
5sa2m1m
6s1am1ma
4s1a2m3n2
s1a2m1p
sam2to
sa4m1t
s1an
s2an.
2s3a2na
s3a2n3b4
s2a2n2c
s2a2n1d
s4and.
san4d3ri
sand2r4
3s4ang.
sa4n1g
2s3a2n1h4
3s2a3ni
2s3a2n3l2
2s3a6n1s
s2an4s1k2
4s3an1t2r4
sa2n1t
2s3a4n1w
2s1a1p
sa2p2o
3s2ap2r2
2s1ar
3s4ar.
3s2a1ra
4s3a2r1b
3s2a2r1d
3s2a1ri
s3a2r1r
3s2a4r1s
4sar3t2i
sa4r1t
s1a2s1p2
4s3a2s4y
3s2a1t
sa1t2a1
4s3at2h
4s3a4t3l
4s3a4t3m2
s4a2t2r4
sa3ts
sat4z3e4n1
sa4t1z
s1a4u
3s2au.
3s4a2uc
3s2a2u1e
2s3au2f1b2
sau2g2r4
s2a2u1g
3s4aum
3s2au3r2
sau1ri1
2s3au4s3b4
sa2us
3s2au1se
s3au2s1w
2s3a1v
sa2vo
1sä
s3ä2hn
3säl
s1ä4l1t
2s1äm
2s1ä2n1d
3s2än1ge
sä4n1g
2s1är
3s2ät
3s4äu1l
2s1ä2u1ß
4s3b4
sba4n
sbe3r2e
1sc
2sc.
2s3cam
s1ca
s2c4an
s2ca1p
2s3car
2s1ce
6s4ch.
sc4h
2s2ch1ak
s4ch2al
4s3cha2n1c
4sch1a4n1g
2s1ch2ao
s4chä
4s2c2h3b2
4s2c2h1c
2s2c2h3d4
3sche.
2s1che1f
sch3ei.
s4chei
4sc4h1e2m1p
sche1m
sch2en
3sches
4sch1e6s1s
4s2ch1e4x
2s2c2h3f4
2s2c2h3g2
2s2c2h1h2
schi4e
s4chim
4s1chi1ru
sch2i2r
3schis
2s2c2h1k4
s4c2hl2
4sch3le.
sch2le
6sch3lein
sch6lit
sch1li
2sch3mö
s4c2h2m
2schn.
s2c2hn4
2sch1o2x
s4c1h2ö
2s2c2h3p2
2sch1q
4sch3re.
sc2h2r4
s4ch1re
4sch3rin
sch2ri
sch3r2om
4sch3ro2u
6s4c4hs
sch1s2e
sch3s2k2
4s2c4h3t
sch2t2a
sch1t4r4
s4chu
4s2ch1u2n1t
schu2n1
sch2up
3s4ch2ü
2s2c2h1v
4sch1we1t
s4c2hw
sch4wil
2s2ch1z
2sc1j
4s3c4l2
2s3co
3s4cop
3sco4r
s2c4r2
2s2cs
2s3cu
4s3d2
sd2a3m4e
sde1s1
s1de
sdi2e1n4e
s1di
sdi2e
sd2i1en
sd4r4
1se
se3at.
s2e1a
se2a1t
2s1e2b2en
se1b
s2eb4r4
2s1echo
sec4h
s1e2c4h1t
2s1e2c4k
3s4ee
se1ec
se2e1i4
see3i1g
seein2
se1er.
se1e1r1ö
2s1e2f1f
se1f
se2gal
se1g
se2gl
seg4r4
3seh
s2e2h1a4
se3he
se4h1ei
se4h2el
se4he2r1k
se3her
se2hi2n
se1hi
se2h1l
seh3re
se2hr
se4h1s2
s2e4h3t
se2h3üb
se1h2ü
2s1ei.
2s1ei1e
2s1e2i1g
s1ein
5s4ein.
2sei2n3b4
s2ein4du
sei2n1d
se2i3n2e
s2ein4f2o
sei2n3f
4sei4n3g2
2sei2n1h4
4sei2n3k
2sei2n3l2
2sei4n1n
4se2i2n3r2
s4eins.
sei6n1s
4sein1sp4
4seins1t
2sei4n1w
4s1eis
3s2e2it
3s2e1k
s2el.
se2l1a
se3la1d
sela4g
se3l3a2m
se2l1e2c
se1le
4s1e2le1m
se4l3e4r1l
sel3e4r1s
2s1elf.
se2l1f
s3el2i1x
s2e1li
se2l3ö
s2e4l1s
sel3s1z
sel3t3r4
se4l1t
s4e3ma
se1m
2s1e2m1p
3s2en.
se4n2a1g
se2n1a
se2nä
2s1en4d1l2
se2n1d
sen3gl
se4n1g
3s2e1ni
3se2n1k
se2no
se4n1o2b
3s2e6n1s
s2ent.
se4n1t
4s1en4t1f4
2s3en4t1g2
s2en1ti
2s1en4ts
2s1en4t3w
2s1en4t3z2
se2n3u
se1o2r
s2eo
4s1e2pos
se1p
se1p2o
3se1q
s4er.
3ser1a
ser3a2d
se2r3al
se5re1f
se1re
s3e2r1e2i1g
6ser1ei2g3n
se4r3eim
se4r3e2n1k
s2eren
s2er2er
2s1er1fo
se2r1f
s2erf4r2
s3er1fü
4serfül
se4r3g
s2erg2r4
s1er3h4
2se2r1h2ö
3s2e1ri
4serken
se2r1k
2s3er2n1t
se4rn
se2r1o2b
se1ro
4s3e2r1öf
ser1ö
s2ers.
se4r1s
2ser1s2a
4ser3seh
1ser3se
s4ert.
se4r1t
s2er1ta
s2e1ru2
se4r1uf
se3r1u4m
se3r1u2n1d
serun1
3s4e2r1v
5ses.
se2s2el
se1se
se1s2ta
ses1t
se3su
3se1t
4s3e4ta1p
se1ta
se2t4a1t
4s1e2t2h
s2e1u2n1
2s1ex
se2xe
4s3e4x1p
se4x3t
1sé
4s3f4
sfal6l5er
s1fa
sf2al
sfa4l1l
sfal1le
sf1lo4
sf2l2
4s3g2
2s1h
sh2a
3s2ha.
sha2k
4s3han
1sh2as
s3hä
s3h2e
3s2hi.
3s2hi3d
sh2i4r
s2h3n
s3h2oc
4shof
3s2hop
sh4o4re
3s2h2o2w1
s3h2ö
s2h4r
1si
si2ac4h
si1a
s2iac
si3a4ch.
si2a1d
2si2a1t
s2i1b4
5s2i1c
2s1i2d2eo
si1d
si1de
s2ido
3s4ie
sieg2e4s2
sie1g
sie3ge
s2i1en3
si3e1ne
si1e2r1r
sie2s
s2i1f4
3s4i1g
si2g1a2
sig4n
si3gnu
si2g3r4
sig4s1t
si4g2s1
si2k1a1b
s2i1k
si1ka
si2k1ä
si2k3e4r2l
s4ike
si2ki
si4k3l2
si2k2r4
si2k3s2
si4k3t4
si2ku
3s2i1lo
2s1i2m1m
si3n4a
2s1i2n1d
2s1i2n3f
s2ing1a
si4n1g
sin3g1h
sin3g4l
sin2g2r4
sing3sa
sin4g2s1
4s1i2n1h4
si1n1i
si3n4i1e
2s1i2n1q
2s1i6n1s
2s1i2n1t
4s1i2n1v2
3s2i1o
sion4
3s2is
si2sa
si4s4chu
si1sc
sisc4h
s2i2s1e
si2s1o
si2s1p2
si6s3s
3s2it
si2t1a2u
si1ta
si1t3r4
si2tra
si3tu
siv1a
s2i1v
si1ve3
si2v2r
1sí
2s1j
2s1k2
4sk.
3ska1la
s1ka
4skam
4ska2n1z
s3kar
4s3kas
sk4a4te.
sk2a1t
ska1t2e
4skate1g
sk4a4tes
4s2k3b4
s4ke1p
3s2ki.
s2k2if
s2k2i1g
3s2k2i3k4
4s3kir
ski1s
3s2k2i1z
sk4l2
4s3klas
sk4n2
4s3k2om
s1ko
4skor
4s3k2o1w
4s1kö
4s2ks
4s4k3t
3s4ku2l1p
sku1l
2s1l2
3s2l1al
4slan
sla2ve2
sl2a1v
s2l2a1w
s3lä
s2l3b
s3le
sle4r3s
s3li
3s4lip
sli4tu
s3lo.
s1lo
slo3b2e
slo1b
s3l2o1e2
2s3m2
2s3n4
4s1na
s2n1a1b4
sni3er.
s1ni
sn4ie
sni3e4r1s
4s5n2o1t
s1no
4snö
1so
3so.
s2o4a2
2s1o2b
s2o1c
so3e3t
s2o1e2
3s2o4f1t
3s2o1g
s1o2he
4s3oh4n1g
so2hn
2s1o2hr
3s2ol
so3la
so4l1ei
so1le
sol4ler
so4l1l
sol1le
4s3o2ly
3s2om
3s2on
son3a2u
s2o1na
s2o1ne2
son5en1de
so3ne2n3
sone2n1d
son3sä
so6n1s
1son2s1o
so3o
2sopf
3s2or.
s2o1r2al
sor1a
s1o2r1c
2s3o2r1d
so2rei
s4o1re
2s1or3ga
so4r1g
5s2or1ge
2s1o2r2ie
so1ri
s2o2r1o2
3s2o4r1s2
so4ru
3sos
s4os.
4s1os1t
so3u2n1t
so2u
soun1
3so1v
4s1o2ve2
3s2o1w
2s1o1x
3s2o1z
1sö
s2ö2c
s1ö2f
2s1ök
2s1ö2l
s1ös
1sp2
2sp.
2s1p2aa
sp2a
4s1pak
2s3pa1la
spani7er.
sp2a3ni
span4ie
4s1pa1p
2s3pa1ra
4s1p2a1ro
3sp2a1ru
3s2pa1ß
4s1pa2u
s2p2a1z
s2pä
3späh
2s3pär
s3pe.
sp2e
2s3pel
4s3pen1si
spe6n1s
s1pe3p4
s1p2e1ri
2s1pe4r1l
2s3pe1ro
s2pe2r1r
4s5pe4r1s
4s3pe1t
3s2pe1z
4s3pf
2sph2a
s1ph
s4phä
s3phe
3s2pi4e
4s3pier4
s3p2i2k
4s1pil
3sp2i2o
4s3pi4p
4s3pis
2s1p2l4
4s3p4la
4s3plä
4sp1le
3s2p1li
s3p4lu
s3p1n
2s3po1d
s1p2o
2sp2o1g
s2p2o2i
2s3p2o1k
4s3p4ol
4s3pos
s2po4t1t
sp2o1t
4spr.
sp2r2
s2prac
s2pran
4s1pr2a1x
2s3p2räm
sp4rä
4s3präs
3s4prec
sp1re
2s1pre1d
2s3p4res
2s3pro1b
s1p4ro
3s2pro6s1s
3sp1ru
4s1prüf
sp1rü
2s3p1s
2s4p1t
2spun1
s1pu
2spup
3s2pur
4s3p2u1t
4s3py1
2s1q
4s3r4
sra2t2s
s1r2a1t
srat4sc
sre1t3
s1re
srö2s1
srück1er6
s1rü
sr2üc
srüc4k
srü1cke
sr2ü2d
6s1s
ssa3bo
s1sa
s2s1a1b
ss1a2c4k
s5saf
s3sa1g
ss1a1j
s3sal
s4s1a1la
s4s1a2l1b
s4s3a4m1t
s3sam
s4s3a4n1g
ss1an
s2sa1no
s4s3a6n1s
ss2a2n1t
s4s3a2n1z
s3sa1s2
ss3a4t1t
s3s2a1t
s3s2ä
s4s1ce
s1sc
s4s3co
ss1ec
s1se
s2s1ega
sse1g
ss2e3h1a4
s3seh
sse3i2n3f
ss1ein
sse3i2n4t
sse6r5a4t1t
s3ser1a
sse1r2a1t
ss1er1ö
s1s3er3se
sse4r1s
s3s2es
sse3ta
s3se1t
s2s3l2
ss1o2f1f
s1so
ss2oi4
s2s1op
ss1o1ri
ss2p2o
s1sp2
s2s1p4ro
ssp2r2
ssque1t4
s2s1q
ssqu4
ssq2u1e
6s6s3s4
ss2t2a
ss1t
s3s2tel
ss1t2e
s4s2t1h
ss2ti
ss4tip
ss2tur
ss1t2u
s3s2tü
ss1u2m1s
s1su
s1t
6st.
s2ta
4s3ta.
3s4t2aa
2s2t1a4b1b
sta1b
s2t2ac
3s4ta1d
3s4ta2f1f
2s3t2a1g
3stah2
2stak
2s3t4a1le
s3t2a3li
2sta2l1k
st1a2l1m
st1a2l1p
st1a2mi
s2tam
4s3tan.
st1a4na
3s4t2a2n1d
2s3t2a3ni
4s3t2a4n1n
2st1a6n1s
2st1a4n1w
s4t2ar.
4s3t4a3ri
s4ta4r1s
st1a1si
s3tas
s3tat.
st2a1t
s4t2au.
sta2u
2s2t1auf
2s3t4aum
3st2au3r2
2sta2us
3staus.
2s3ta1x
3s2tä
4s3tä1g
4s2t1ä4l1t
s4tä2n1d
5s4tä4t1t
st2ät
s3tä2us
2s4t3b2
2s4t3c
2s4t3d4
s1t2e
4s5te.
2st2e2a2
4s3te2c2h3n4
st2ec4h
s2te1d
4st4ee
3s2te1g
ste2g2r4
3s4teh
s2te2i
3ste2i1g
4s3te4il
3stei2l1h2
stei4n1a
s2t1ein
1s2tel
2s5tel.
stel4l3ä
ste4l1l
2s5t2e4ln
2s5te4l1s
2ste1m
4s3tem.
ste4mar
st2e1ma
4s3ten
s5t6en.
s4t3en2d1s
ste2n1d
s4t3en2gl
ste4n3g
st4e6n1s
s4t3en4t1f4
ste4n1t
s2te1p
2ster
6s5t4er.
ste6r1e4r1s
st2erer
ste1re
4ste4r1m
3s2ter2n1c
ste4rn
4s3tes
s4t3e1se
stes6se.
ste6s3s2
stes3se
s1t2e4s1t
2s3te1t2
s4te3ti
3s4te4t1t
3s2teu
1ste2u1e
4s3teuf
st3e1v
4stex
2s4t1f4
2s4t1g2
4st1h
s4t3hä
s4thi
s2t3ho
s2thu
2sti1a
s1ti
2s3t2i1b4
s2t2ic
sti2e
2stie.
s2tie1g4
s2ti1el
2st2i3e4n3
3s2t2if
2s3t2i1g
2s3t2i1k
s2til
3s4tim
s4t1i2n3f
s3ti4n1n
st1i6n1s
2s3t2i1o
1s2ti2r
2s3tis
st1i4so
1s2ti3tu
2s3t2i1v
2s2t1j
2s4t3k4
4s4t3l
4s4t3m2
2s4t5n4
s2to
2s3to.
s3to1b
2s3to3d
4stod.
1stof
s4to2f1f
s4t3om
4s3ton
4s3too
2s3t2o3p2o
2s3t2or.
2s3t4o1re
2st1o4r1g
2sto1ri
s3to4r1t
2s3t2o3se
s3to3s2t4
1s4toß
4s3to3te
st2o1t
4s3t4o2u
2s3t2o1w
2st2o1z
1s2tö
2s3töc4h
st2ö1c
2s3t2ö1t
2s4t3p2
2s2t1q
s2t2r4
2str2a1d
2s3tra1g
1s4trah
4s5t4r2ai
4s3trak
2s3tr2al
4s3tra6n1s
3s4tr2as
5s4t2ra1ß
4st3r4aum
stra2u
4s5t4rä1g
4s3tr2ä1ne
4s5tre1f
st1re
4s3t4r4e2i1b
5s2t4re2if
st3re4n1n
2s4t3r2i1g
s3t1ri
1s4tr2i2k
2s5tr2is
st3ro4l1l
str2ol
stro4ma
str2om
1st1ru
2s3tr2u1a
2st4r2u1g
3s4truk
4st3run1
2s5t4rup
2s4t3s4
2s4t3t4
s1t2u
1s2t2u1b
4s3t2uc
3s4t2u1d
2s3t2u1e
3s4tuf
3s2tu1h
2stu2m2s
stum4sc
2s2t3u4m3t2
stu2n1
2s3t4un.
3s4t1u2n1d
s2t3u1ni
4stu4n1n
2s3tu6n1s2
2s2t3u2n1t
st2u3re
st3u4r1l
2s3tu4rn
2st3u4r1t2
2s3t2us
1s2tü
2s3tüc4h
st2üc
2s3tür.
2s3tü1re
2s3tü4r1g
2s3tü4r3s
3st2üt
2s4t3v
2s4t3w
3s2ty1l
s1ty1
4s4t3z
1su
su1an
s2ua
3s2u2b3
su4ba2
4su3b4i
3s2u1c
su2ch1a
suc4h
such4s1t4
su4c4hs
2s1u2f
2s1u1h
su1is
s2ui
su1it.
sul2a
su1l
su1l2i
su4l1t2
su2mar
su1ma
su2ma2u
3s2u1me
su2mel
su6m5en4t4s
sum2en
sume4n1t
s3um1fe
s1u2m1f4
3s4u2m1m
su1m1o2
su2mor
s3um1sa
su2m1s
s3ums1t
su2n1
3s4un.
sun6der3h4
su2n1d
sun1de
su4ne
s1u2n1f
2s1u1ni
4s1u2n1t
3s2up
su2p3p
su2ra
2s1u4r1l
s1u4r1t2
s4u2s1
su3sa
su3s1h
su3si
su6s3s
3s2u1v4
1sü
2s1ü4b
3s2üc
s2ü2d1
süden2
sü1de
3s2ün
4s3v
2s1w
s3w2a
s3we
s1weh2
4swi2e
4s1wil
1s4y
sy1l1
sy4n3
2s1z
4s3za
4s3zei
s2ze2n1a
3s4ze1ne
4s3ze4n1t
s2zes
4s3ze1t
s2zis
sz2o
4s3zu1
s3zü
4s3zw2
2ß1a2
2ß1b2
2ß1c
2ß1d
1ße
2ß1ec
2ß1e2g
2ß1ei
ße2l1a
ße4n3g
ße2ni
ße2no
ße2ro
ß2ers.
ße4r1s
2ßer3se
ße4r3t
2ß1f
2ß3g2
ßg2e2bl2
ß1ge
ßge1b
2ß1h
1ßi
ßi2g1a2
ß2i1g
ßi4g4s1
2ß1in
ß1j
2ß1k4
2ß1l
ßler3
ß1le
2ß1m
2ß1n2
ß1o2
ßos2
2ß1p2
2ß3r2
2ß1s2
ßs1t2
2ß1t
1ßu
2ß1um
2ß1ü
2ß1v
2ß1w
2ß1z
1ta
3ta.
4t2aa
5t4a1a2n
4tab.
ta1b
ta2b1an
t2aba
2t1a4b1b
3t2a3bel
2taben
ta4be2n1d
2t1a2b5f4
2t1a2b1g2
2t1a2b5h2
2t1a2b1k4
3t2ab2le
tab1l2
2t3a4b3n2
ta2b3r4
4t1a4b1s
2t3a4b3t
t2a2bü
2t1a2b1w
2t1a2b3z2
2t1ac
3t2a3cu
t1ada
ta1d
t4a3di3
2t1a2d2r4
t2a3d2s2
3taf.
3t2a1f2e
4ta2f1f
t1a4f3g2
t1af4r2
3t2a1g
t2a2ga2
ta2g1e1i
ta1ge
4t3a4ge4n1t
ta3gen
4t2a3gl
t3ago
tag4s1t
ta4g2s1
tah2
tah3le
ta2hl
tahl3s1k2
tah4l1s
t2ai
ta3i2k
ta2i2l
ta1i6n1s
tai4r
ta1ir.
t1a2ka
t3a2kro
t2a1k2r4
1t2ak6ta
ta4k1t
3t2ak4t3b2
3t2ak1tu
2t1a2k3z2
3t2al.
ta2la
ta3la2g
ta3lak
t1alb.
ta2l1b
t1al2b1k4
ta4l3d
3t4a1le
ta4le6n1s
talen1
tal2lö
ta4l1l
3t2a1lo
ta2l1op
2ta4l1t
2tam
3t2a1m4e
ta2mer
ta2mi
t1am1p2l4
ta2m1p
t1a4m1t
3tan.
t1a2na
2t1a2n3b4
4t2a2n1d
t2a3ne
4t1a2n1f
2ta4n1g
3t2a3ni
t2a2n1k
t3an3kl2
4t1a2n3l2
2t1an1me
ta2n1m4
4t3an3na
t2a4n1n
t2a1no
t1a6n1s
3t2ans.
4t3an1si
4t3an1sp4
t2a2nu
2tanw2a
t1a4n1w
2tanw2ä
t2anz.
ta2n1z
t1anza
tan6zer3h4
t1anzu1
ta3or
t2ao
ta2pe.
ta1p
t2ap2e
ta2pes
2ta2pf
t2a2p3l4
2t1a2r1b
ta4re6n1s
t2a1re
ta3ren
ta4r3e1re
3t4a3ri
4ta2r1k
2t1a4r1m
2ta4r1t
t1ar3t2i
t2ar2to
t2a2ru
2t1a2r1z
3tas
ta3sa
4t1a2s1p2
ta2t1a2b
t2a1t
1ta1ta1
ta2tan
ta2t1a2u
tat3ei
ta1t2e
ta2te1m
ta2t3er
ta2t2h
t4at3h2e1
t3a4t3l
t4a4t3m2
ta2t2om
ta1to
4ta3t2u1e
ta1tu
ta2t1um
2t1auf
ta2u
4tau4f3g2
tau3f4li
tauf3l2
4t3au2f3n2
t1auk
3t4aum
t1au4s3b4
ta2us
3t2au1sc
tau6sc2h2r4
tausc4h
tau6s4c2hw
t2au1se
4t3au4s3g2
t1au2s1k2
4t1au2s1l2
4t3au6s3s2
4t1au2s1w
3t2a1v
3ta1x
tax2i1s1
1tä
4tä1b
tä1c
4tä1d
3t2ä1e
3tä1g
4t1ä2gy
2täh
2t1ä4l1t
4täm
t1ä4m1t
t1än4g2s1
tä4n1g
3tä2n1z
t1äp
t2är.
tä2ru
tä2s
t2ät
4tä4t1t
2t1ä2u1ß
2t1äx
1tà
4t3b2
tbe3r2e
tblo3ck5e
tbl2
tb2lo
tb4l2oc
tblo1c4k
tblocken8
4t1c
t3cha
tc4h
t3che
tch2i
t2c2h3l2
t2chu
t4c2h1w
t4c4k
t3c4l2
t3c4r2
4t3d4
tdun2
t1du
1te
3te.
t2e2a2
2teak1
te3al
te3an
3t2eba
te1b
3t4e4b1b
4t1e2b2en
t2ec4h
te3cha
3te2c2h3n4
2tec4k
te1ck2e
te2cki
te1e1m
t4ee
te2en3
te1e2r1w
te2e3s2
2te2f1f
te1f
teg3re
te1g
teg2r4
2teh
3t2e1ha
3t2e1hä
3tei.
t3eif3r2
te2if
te2i1k4
3te4il
4teilhe
tei2l1h2
2t1ein
tei2n3e4c
te2i1ne
t3ein1ge
tei4n3g2
t3einla
tei2n3l2
4tei4n1n
t3e2is.
t3e2i4s3b4
t2e4k1t2
te1k
5tel.
3te1la
te2l3a1b
te2l1a2c
te2l1a2u
te2l1b4
3te3le
tel1e1b
tele4be
te4l1e2c
t2e4l1eh
te4l1ein
t2elei
2t1e2le1m
tel1en
te4l1e2r1d
te4le2u
4t3elf.
te2l1f
3te2l1g
te2l1in
t2e1li
te2lit
3te2l1k
tel3l2e
te4l1l
5t2e4ln
te4los1t
t2e1lo
te2l1ö
3te2l1p
5te4l1s
tel3s2k2
3te4l1t4
tel3ta
tel3t1h
3tem.
te1m
te2m1ei
te1me
te2min
te1mi
2tem1me
t2e2m1m
te2m1o2r
te1mo
3temper
te2m1p
temp2e
2t3em2pf
te2m3s
te4m1u
3ten
t6en.
te2n3a
te2n1a2b
te4na2d
te4n2a4g
te4nas
te4n1a2u
te2nä
t4e2n3b4
ten3da
te2n1d
4t3en2d1f4
t6en1di
4t1en4d1l2
t6endo
4t3en2d3p2
ten3d4r4
te2n1e2b
te1ne
te2n1e1f
te3n4ei.
te2n1ei
ten3e2i1d
ten3e6n1s
t2e3nen
4t1e2ne4r1g
te2ne1t
te4n3g
4t1eng.
ten4gla
ten2gl
t4e2n1h4
te2ni
te4n3in1
t4en1j
t4e2n1m4
te4n3n
ten1s2e
te6n1s
4t1ense1m
t4en1ta
te4n1t
t3en4t3b2
4t1en4t3d4
1t4en1te
4t1en4t5n4
ten3t3ri
ten1t2r4
4t3en4t3w
4t3en4t3z2
ten6zer3h4
te2n1z
ten3zw2
t3e2pi
te1p
3t4er.
ter1a2b
ter1a
te1r2af
ter3am
te3r2an.
4ter4b2s
te2r1b
4ter4b3t
3te2r1c
4t3erde.
ter3de
te2r1d
te2r1e2b
te1re
te4r3e2if
te2re2l
ter3e2n1d
t2eren
te4re4n1g
te4r1e2r1k
t2erer
te2r1e2r4z
4terf2ol
te2r1f
ter1fo
t4erf4r2
4terfül
ter1fü
3te4r3g2
6tergrei
terg2r4
terg1re
t6erg1ru
t4e1ri
te3ri1a
4t1erklä
te2r1k
terkl2
2t1er1l2ö
te4r1l
ter4mer
te4r1m
ter1me
3ter1mi
ter4n3a2r
te4rn
ter1na
2ter2n1c
t3erne2u
ter3ne
t4e1ro
t3er1ö
ter4re.
te2r1r
ter3r2e
t4ers.
te4r1s
t6erscha
ter1sc
tersc4h
ter4ser
ter3se
ters1t4
t4er6st.
t4ers2ti
t4ers2t2u
te4r1t2
t2e1ru2
te4r1uf
ter4wäh
te2r1w
terw2ä
6terwe2r1b
ter3za
te2r1z
2t3er4z3b4
3tes
tesa2c
te1sa
te2s1an
tesä2c
te1sä
te2s2el
te1se
te2sp2r2
t2e1sp2
te6s3s2
t2es1t
tes3tan
tes2ta
tes2t3e2i
1tes1t2e
tes6te4r6g2
te2ster
tes6te2r1k
3te4s3tes4
te2su
3te1t2
t2et.
te2t4a1t
te1ta
4tet2h
4te4t3l
teu3e1re
te2u1e
teu3e1ri
3teuf
3te1um
t2e1un1
3te2ur.
teu2r3a
te2vi
t2e1v
te1x1a
2t3e2xe
2t1e2xi
4t3e4x1p
3te4x1t
2t1ex1z
4t1f4
tfi2l
t1fi
4t1g2
tger2
t1ge
t1h
4th.
2th4a
3t4ha.
t2ha1g
t3hai
t2hak
3t2h2al.
4t3ha2u
2t3hä
th2e
1t2he.
3t2h2e1a
2the1b
t2hec
2t3hei
t4hein
t2he1k
t2he1m
1then
t4he1ne
t4he1ni
3t4h2e2o
2the2r1r
t2hes
3the1se
t2h2e2u
1thi
t2h2i1k
2t3hil
2t3him
2t2h1k4
4t2h3l
4t2h3m
2t2h3n
1t2ho
2t3h2oc
t3hof
2t3hoh
t4hol.
th2ol
t3hor
2t3h2o1t
tho2u2
4t3h2ö
2t2h3p2
1t2h2r2
4thrin.
th2ri
th3rin
4th2r1i6n1s
2t4hs
2th2u1b
4thu2n1
2th2ü
2t2h1v
t2hy
1ti
ti2a1d
ti1a
ti3a2m
3t2i1b4
ti1ce1
t2ic
tiden2
ti1d
ti1de
ti4de2n1d2
ti2d2eo
3ti2ef.
tie1f
tie1g4
2tieh
ti1el
t2i3e4n3
3ti2er
tie4rec
ti2e1re
ti1e1t
ti1eu
3tif.
t2if
ti1f3r2
4ti4f1t
3t2i1g
ti4g1e2r1z
ti1ge
3t2i1k
ti2kam
ti1ka
ti2kar
ti2k1in
ti2k3rä
tik2r4
ti2lar
ti1la
ti2l1a2u
ti2lei
ti1le
ti2l1el
3ti2l1g
ti2lö
ti4l3s
ti4l1t4
ti2lu2
ti2ma2g
ti1ma
t2i1mi
tim2m1a
ti2m1m
4t1i2m1p
3t2in.
ti3na
t1i2n3b4
4t1i2n1d
t2i3n2e
t1i2n3f
t2in2g1a
ti4n1g
tin2g3l
tin4g3s1
t1i2n1it
ti1ni
2t1in1j
tin2k1l2
ti2n1k
3t2ins.
ti6n1s
4t1in1se
2t1i2n1t
ti1n1u
4t1i2n1v2
3t2i1o
ti3or
3tip
ti3p4l4
ti4que.
t2i1q
tiqu4
tiq2u1e
ti1r1h4
3tis
ti4scha
ti1sc
tisc4h
tis4c2h3w
ti2sei
t2i1se
ti2sp2
ti1s2ta
tis3t
3ti3t2e
2ti3tu
tium2
t2i3u2
3t2i1v
ti2van
ti1ve3
ti2vel
ti4v3e4r1l
tiver1
ti2v1o
ti2v3r
ti2za
t2i1z
2t1j
4t3k4
4t3l
t1l4e
5tle1m
tl2e2r3a
6t5li
tlu4n1g4
t1lu2n1
4t3m2
tmal2
t1ma
tme4n6t3
t1me
tmo4de3s1
t1mo
t3m2o1d
tmo1de
4t5n4
t3n2es2
t1ne
tne6s4s
1to
3to.
to4as
t2o3a2
to5a1t
4t1o4b1j
to1b
tob2l2
t2o1c
3to2c4h1t2
toc4h
to6ck1e4n1t
to1c4k
to3cke
3to1d
to1de2
4to2d1er
t2o4d1u
toi4r
t2oi
3t2o1k
to3la
t2ol
3to1le
4to2l1z
to1m1e2
t2om
to2men
2to4m1g2
3ton
to2n2a2u
t2o1na
to2neh
t2o1ne
3too
to2pak
to1p2a
to2p2a1t
3t2o3p2o
2t1o2p3t4
3t2or.
to1r1a
to2ra2u
to4rän
to3rä
4to2r1c
t1o2r1d
3t4o1re
to2rel
t1o4r1g
t3or3ga
3t4orin1
to1ri
to2r3i2n1t
t2o2rö
3t2o4r1s2
t1ort.
to4r1t
to3ru
t2o2r1w
to3sc
3t2o3se
to4s1k2
t2o1s2p2
4to6s1s
3tos1t4
to1s2ta
4toß
3to3te
t2o1t
to2t2ho
t4ot2h
3to1t2r4
to2ts2
3t4o2u
touil4
to2ui
to3un1
3t2o1w
2tö
3töc4h
t2ö1c
4t1öf
4t1ök
tö4l
3t2ön
t1ö2s1t
4tö1ß
3t2ö1t
4t3p2
tpf4
2t1q
1t2r4
2tr.
5t1ra.
3trac
tra3ch1a
trac4h
4tra4c1t
t3r4ad.
tr2a1d
tra4de1m
tra1de
tra4f3ar
tr2af
tra1f1a
3tra1g
6t3r2a2h1m
5t4r2ai
3trak
3tr2al
2t3r2a2m2s
3t4r2an.
2tra2n1d
3tra2n1k
t3r2a4n1n
3tra6n1s
t3r4a1s2e
tr2as
t3ra1si
4t2ra1ß
5träc
3t4rä1g
3tr2ä1ne
4träs
4trä1ß
4t5re.
t1re
tre4a1le
tr2e1a
4tre1b
tr2e2b2r4
4trec
t3r2ec4h
t4rec4k
6t3re1d
5t4r4ee
3tre1f
4t3r2e1fe
4t3r2e3f2o
4t3re1g
t4r2ei.
3t4r4e2i1b
4tre2ic
2tre2if
t3re2i1g
2t3re2i1h
t3r1ein
t3reis
6tre2it
t3re2i1z
2t3re1k
6t3rel
t4re1m
t4r4en.
3tre2n1d
4t3ren1di
t3re4n1t
2t3rep2e
tre1p
2t3re1p2o
t4rep2r2
t4rer
t4r4es.
t4re1t
1t2r4e2t3r4
t4re2u
3treu1h
t3r2e1v
2t3re1z
5t4ré
2t3r1h4
3t1ri
4tr2ic
5trie1b
tr2ie
tri4er
5tri4g1g
t3r2i1g
t3r1i2n1d
4tri4n1g
tri3ni
4t3ri4n1n
t4rip
4tri2p1t
tr2i2x1
trizi1
tr2i1z
3tro.
3tr2o3e4
3t4r2oi
tro2ke
tr2o1k
4t4rom.
tr2om
tro2mi
4tro4m1l2
3tr2on
2t3roo
t4rop
3tropf
3troy
t3r2ö1c
2t1r2öh
3trös
2t3r2ö1t
3tr2u1a
t1ru
4truk
t2rum2
tru2m1s1
2t3ru2n1d
trun1
3t4r2u2n1k
5t4rup
tru2t3h
t3r2u1t
t4r1üb
t1rü
trü1be2
trü1bu
2t3r2üc
trück1er6
trüc4k
trü1cke
t4rü1g
t1ry1
2ts
4ts.
t4s1a4b
t1sa
t3s2ac
ts1a1d
t2s1ah
ts1al
t4s1a4m1t4
t3sam
t2s1an
t4s3ar
ts1as
t2s1a4u
t2s1än
t1sä
t3s2cha
t1sc
tsc4h
t4schar
t3sch2e
t4s1che1f
ts4che1m
tsch4li
ts4c2hl2
t4schro
tsc2h2r4
t3s4co4r
t2s3co
t2s1e2b
t1se
t3se4il
t4sei2n1d
ts1ein
ts1e1m
tse2n1
t2s1e4n1g
t2s1e4n1t
t2s1er
t4s3es3se
tse6s1s
t2s1i2d
t1si
ts1i1n1i
t2s1ir
ts3k2r4
t2s1k2
t3s2l1al
t2s1l2
t1s1o
tso2r
t3so2u
t2sö
t3spal
t1sp2
tsp2a
ts1par
ts4p2a1re
t2s2pä
ts2pe1d
tsp2e
t3spe1k
t2s1ph
t3s2pi
t2s1p2o
t3s2pon
t3s2por
t4s1prei
tsp2r2
tsp1re
t6s3s4
t1s1t4
t4s3t2a1g
ts2ta
t2s3tak
ts4tal
ts3tä1ti
t3s2tä
tst2ät
t2s3te1p
ts1t2e
t3s4t4e1ro
t2ster
t2s3tip
ts1ti
t4stit
ts3t2o1c
ts2to
ts3tor
t2s3tr2a1d
ts2t2r4
t4stran
ts3tra2u
t2s3trä
t4st4re2u
tst1re
t2s3t1ri
t4st4rop
t2s3t1rü
ts2t2u
t2s1u
1t3s2u2b3
t3s4y
4t1t
tt1a1b
t1ta
tta2be
t2t2ac
t2t1a1d
tta6ge6s1s
t3t2a1g
tt2ag2e1s2
tta1ge
tt1ak
tt2al
tt2a2n1t
t2t1a4r1t
t3ta1s
tt1ebe
t1te
tte1b
tt1e2if
tt1eis
t3tel
t3te2la
tte4l1e1b
t3te3le
tte4l1en
tt2e1l1o
t3tes1
tte2sa
tte2sä
tt2häu
tt1h
t2t3hä
t2t3ho
t3ti
t3to
tto1s
t3tö
t3tro
t1t2r4
tt3ru
tt3rü
tt2se2n1
t2ts
tt1se
tt2so2r
tt1s1o
tt1s1p2
tt2sp2e
tt2sp2r2
tt2s1ti
tt1s1t4
4t4t1t4
t3tu
tt2un1
t3tü
1tu
tu1a2l1m
t2ua
tu3an
2t2u1b
3t2uc
tu2chi
tuc4h
2t2u1d
3t2u1e
4tuf
tu1f2e
tu3fen
t3u2fer
t2u2f1f3
2tu1h
tu2is
t2ui
2tuk
t3u2k4r4
tul2a
tu1l
t2um.
3t2u1me
2t3u2m1f4
2t3u4m1g2
2t3u4m1k4
2t3u2m3r2
tum2si
tu2m1s
tum2so
tums5t2r4
tums1t
2t3u4m3t2
2t3u2m1z
3t4un.
tun1
2t1u1na
2t1u2n1d
3tu3ne
2t3u2n1f
3tu4n1g
t3unga
tun4g4s5
2t1u2n2if
tu1ni
2t1u2n2i1o
2t3u2n1t
t1up.
tu2r1a4g
tu1ra
t2u2rä
tu2r1c
tu3re.
t2u1re
tu2rei
tu2r1er
tu2res
tu2r1e4t
turin1
tu1ri
3tu4rn
t4u2ro
tu4ru
tu2sa
t2us
tu4s4c2hl2
tu1sc
tusc4h
tu2so
tu3ta
t2u1t
2tü
4t1üb
3tüc4h
t2üc
tü4c2k2s
tüc4k
3tüf
3tüm
3tür.
tü2r1c
3tü1re
3tü4r1g
3tü4r3s
3tü3ten
t2üt
tü1te
4tü4t1z
4t3v
4t3w
tw2a2
twi4e
1ty1
3ty1p
ty2p2a2
tys4
4t1z
t2za4
tz1a1g
tz1al
tz1ar
tz1a2u
tz1ä2
t3ze.
t2z1e2c
t2z1ei1e
t2z1ei3s4
tze4n1
tz2e1ne
tz3en4t3s
t3ze4n1t
tz1e4r1l
tz2e4r1s2
t3ze2s
tzg2el2
t2z1g2
tz1ge
tz1i2n1d
tz1i2n1t
t2z1or
tz2ö
tz2t1h
t2z1t
tz2tin
tz1ti
tz1w2ä
tzw2
tz1wi
tz1w2u
2ua
u1a2b
u1a2c
uad4r4
ua1d
u1al.
ua2l1a2u
ua1la
u1a2l1b
u3ale4t
u2a1le
u1a2l1f
u3a2lo
u1a2l3r2
u1a4l1s
u1a4l1t
ua2lu
u1am
u1a6n1s
u3ar.
uar2a2b
ua1ra
u1a4r1s
ua3sa
ua2t2h
u2a1t
u4a3t2i
u3a2u
u1ay
u1äm
u1äu
2u1b
u2be2c
u3b4i
ubi3os.
ub2i2o1
ubio3s2
ub2l2
ub3l2i1c
ub1li
u2b3lu
u2b1op
ub2r4
u3b3rä
u2b3r2it
ub1ri
ub2s1an
u4b1s
ub1sa
ub2s1o
ub2sp2a
ub1sp2
u2b1üb
2uc
u2c1c
u1ce
uch1a
uc4h
u1cha.
uch1ä
u1che
u2ch1e4c
u4ch1ei
u3ches
u1chi
uch1il
uch1i2n
u2c2h3l2
u4c2h3m
u2c2h3n4
u2c2h3r4
uch2so
u4c4hs
uch4sp2r2
uch1sp2
uchs1t4
uch4tor
u2c4h1t
uch1to2
uch2t3r4
u1chu
u4ch3ü
u4c2h1w
u1ci
u2cke1m
uc4k
u1cke
u4ck1e4n1t
u3ck2er
u2cki
u1c4l2
2u1d
u3d2a
ude6n3s2
u1de
ude1r2e
ude4r1t4
ud2i3en
u1di
udi2e
udi1ti4
u2don
ud3ra
ud2r4
u3d1ru
2u1e
ue2c4k
u2e1d
ue2en
u4ee
u2e1g
u2e1la
ue2le
u2e1li4
ue2mi
ue1m
uen1
ue2nä
ue2ner
ue1ne
uen1ge4
ue4n1g
ue2ni
ue2no
uen2zu1
ue2n1z
u2e1p
ue2r3a
ue2r1ä
u2e1re
u3e2r1e2h
ue3r1e2i1g
u3erer
ue4r1e4r1g
ue4r1e2r1k
u3e2r3e2x1
ue4r3g2
u4e3ri4n1n
u2e1ri
u3e2r1i2n4t
uer3ne
ue4rn
uer4ner
uern3s4t
uer6n1s
ue2r3o
u3e2r1r
uer3sc
ue4r1s
ue4r3t2
u3e2r1u4m
u2e1ru
u3e2r1u2n1f
uerun1
u3e4r3u2n1t
ue2ta
ue1t
ue4te1k
ue3te
u3f4ah
u1fa
uf1ak
uf3ar
u3fas
uf1a2u
u2f1äs
u1fä
u2f1ä2ß
u2f1ei
u1fe
u2f1e1m
u3fen.
u2f1e4n1t
u2f1er3h4
u4fer1l2e
ufe4r1l
uf2e4rn
2u2f1f
uff4l2
uf2f3ro
uff2r2
uf4f2s4
uf3l2
u2fo1b
u1fo
ufo2r
uf1o1ri
uf3r2
uf3sä
u4f1s
uf4sin
uf3si
uf4s1o2
uf2s1p2o
uf1sp2
uf2s3te1m
ufs2t
ufs1t2e
uf2t1e1b
u4f1t
uf1te
uf2t3s2
u2f1um
u1fu
2u1g
u4gab5te
uga1b
uga4b3t
ug1af
ug1a1k
u2g1a1p
uga4s
ug1a2u
u2g3d2
u2g1ei
u1ge
u2g1e2r1f
u2g1e4r1l
ugge4s1t2
u4g1g
ug3ge
ugg2e1s2
ug3hu
u2g1h
u2g1l
u3g3la1d
ug3lo
u3g2lö
u4g1lu
u2g3n
ugo3
ug1or
u2gö
u4g3reis
ug2r4
ug1re
ug3ro
ug3rüs
ug1rü
ug3se2
u4g2s1
ug4s3er
ug3si
ug3sp2a
ug1sp4
ug4sp2r2
ug4s1pu
ug5s3tä
ugs1t
ug3s3t2r4
ug3s3tü
u2gü
u1h
2uh.
uhe3s6
uh1la
u2hl
uh1lä
uh2li
uh1me4
u2h1m
uhr1a
u2hr
uh2rer
uh1re
uh3ri
uh4rin
uh4r1t4
uh2ru
uh4rü
u2h1w
2ui
ui2c4h
u2ic
u1ie
ui1e1m
u3i1g
u4i1ge
u1in.
u1is.
u3i6s4ch.
ui1sc
uisc4h
u3i6s4c4hs
uisi4n
ui1si
ui4s5t
u1j
u1k2a
u3käu
u1kä
u1ke
u1ki
u1k2l2
ukle1i
uk2le
uk4n2
u1k2ö
u1k4r4
uk2ta
u4k1t
uk2t1in
uk1ti
uk2t3r4
u1ku
uk2u2s
u1l
ul1a1b
ul1am
ula2s
ul1äm
u2l1b4
ul2d3r4
u4l1d
uld2se
ul2d1s
u2l1el
u1le
ule4n
ul1e2r1f
ul1er3h4
ul1e2r1w
ule2sa
ule2t
ul1e1ta
u2lex
u2l3f4
u2l1g4
ul2i2k
u1li
ul1i6n1s
ul3ka
u2l1k
ul2k2n2
ull2a
u4l1l
ul2l2es
ul1le
ul4l3s
ulm3ein
u2l1m
ul1me
ul2o2i
u1lo
ul1or
ul2p1h
u2l1p
ul2sa
u4l1s
ul4sam
ul4s2t1h
uls1t
ul2s3z
2ul1ta
u4l1t
ul3t1h
ul4t1ri
ul1t2r4
ul6t3s
u2lü
ul2v2r
u2l1v
ulz2w2
u2l1z
u2m3a2k
u1ma
um1a4l1l
u2m1a2n1z
u2m1a4r1t
u2m1a2us
uma2u
u2ma2u1t
1u2m3d2
um2en
u1me
umen4t4s
ume4n1t
ume1r2a
u2m1e4r1g
u2m1e4r1l
u2m1e2r1w
1u2m1f4
1u4m1g2
um1i6n1s
u1mi
um1ir
1u4m1k4
1u4m3l2
4u2m1m
um1m2a
umpf4li
u2m1p
um2pf
ump2f2l2
um2p3le
um1p2l4
1u2m3r2
3um3s2a1t
u2m1s
um1sa
um4ser
um3se
um2sim
um1si
um2s1p2e
um1sp2
um2su
u4m3t2
u3m2un1
u1mu
u2m1ur
1u2m1z
un1
4un.
2u3na.
u1na
1u2n1a1b
u3n3ac
un4al
u3n2am
u2n3an
2u3n2as
u3n3a1t
1unda
u2n1d
un4d1a1b
1un2d1d2
un4d1ei
un1de
un4d3e2r1f
und5e2rha
under3h4
1un2d1f4
2un2d1g2
un2di1d
un1di
1un4d5n2
un2d1or
un2d3r4
4unds.
un2d1s
und3sp2
und3s1t4
un2d1um
un1du
1un2d1v2
1un2d1z
u3ne
une2b
une2h
un2ei.
u2n1ei
un3ein
une4n2t
u3nen
u3n4es4
1unge1t
u4n1g
un1ge
1unge1w
un2g5h
1un3glü
un2g2r4
ung3ra
ung3ri
ung4sa
un4g2s1
un2i1d
u1ni
un3i2de
1u2n2if
u3n2i1k4
un2im
uni2r
2u3n2is
un3i2s1l2
u3n2it
3u2n2i3v
2u2n1k
un2k1a2
un2kei
un2ks2
unk4t1it
un4k1t
unk1ti
unk2t3r4
3unku
un1na2
u4n1n
un2n3a1d
un3n2e
uno4r
u1no
un2os
1u2n3r2
u6n1s2
2uns.
un3se
1un1si
un3s1k2
un3sp4
uns4t2r4
uns1t
1u2n1t
un3ta
unt4e4ri
un3ter
un1te
un3t2r4
un4t3s
2un3tu
un3v2ol2
u2n1v2
unvo4l1l3
1u4n1w
2u2n1z
2uo
u1o2b
u3of
u3or.
u1o2r3c
u3o4r1s2
uos2
u1os.
uo1te2
u2o1t
u1p2a
u1p2e2
uper1
up2fa
u2p1f2e
u2p1f1i
u3pi
up2p3l4
u2p1p
up2p2r2
u1p2r2
up4t3a2
u2p1t
up3t3e4r3g2
up3te
up1t1o
up4t3r4
u1q
2ur.
u1ra
u2r1a1b
u3r2aba
ura2be
u2r3a2m
u2r1a1na
ur2a2n3b4
u2r1a4n1g
ur2a2n1h4
u2r1a6n5s
u2r1ar
ur3a4ren
ur2a1re
u2r3a4t1t
u1r2a1t
u2r1a2u
2u1rä
ur1än
ur3b2a
u2r1b
urc4h1
u2rc
u2r1d2
ur3di
2u1re
u4r1e2f1f
ure1f
u2re1l2e
ure4n
u4r1e1p
ur1er3h4
ur1e2r1w
2u2r1f
ur4f3t
ur2g1ri
u4r1g
urg2r4
urg4ros4
ur4g3s4
ur2i2c
u1ri
ur1im
ur1i1ni
u2r3i6n1s
u2r1i2n1t
ur2k2s
u2r1k
1urla2u
u4r1l
ur3l2a
4u1ro
u3r2ol
uro1s
u1rö
u2r3p2
ur3sac
u4r1s
ur1sa
ur2s2an
ur2s1a4u
ur2ser
ur1se
ur4sin
ur3si
ur3s4t4r4
urs1t
ur4s1w
ur3s2ze
ur2s1z
u4r1t2
u3ru
u1rü2
ur2za
u2r1z
ur2zä2
ur2zi
ur2zo
ur2z1w2
2us
u4saf
u1sa
us4a4n1n
us1an
u6sche4n1t
u1sc
usch2en
usc4h
usch5wer
us4c2hw
u2s1ec
u1se
u2s1ei
u3se2i1d
u3se1p
u3se1r1a
u2se2r3p4
u2s1e1se
usi3er.
u1si
u3s4ie
usi5ers.
usie4r1s
us3k4l2
u2s1k2
u4s1ko
us3o1c
u1so
u3soh
u2s1op
us1o2u
us3pa4r1t
u1sp2
usp2a
u2s1pas
u2s1p2a1t
us1p2e
u3s2pe1k
us1p2ic
u5s4p2i1z
u2s1p2o
us2por
u2s1pu
us4se1z
u6s1s
us1se
us2sof
us1so
ust3abe
us1t
us2ta
usta1b
u1stal
us3ta2u
u4s2t1h
ust2in
us1ti
us3t2r4
u5s4tr2as
u2s6tr2is
us3t1ri
u1s1t2u
u2stu2n1
u2stur
us2ur
u1su
u2sü
2u1ß
2u1t
u2t1a4l1t
u1ta
u2t3a2m
u2t1a1p
u2t1ar
u2tär
u1tä
u3te
ut1e1g
ute4ge
u3t1ei.
ut1ei1e
u3te2n1
u2te4n1t
ut2er4er
ute1re
u4t3er1s2a
ute4r1s
u3t2es
u3t2e1t2
u4t2e1v
u4t1ex
ut1fi4
u4t1f4
ut2h2e
ut1h
u2thi
u2t3ho
u2thu
u1to1
uto4ber
uto1b
utob2e
ut2o3c
ut1opf
u2to2p1s
ut4or
utos4
u3tö
ut3r2e1a
u1t2r4
ut1re
ut3rü
ut3s2a
u2ts
ut2s1ä
ut4s4c2hl2
ut1sc
utsc4h
ut4s4c2h2m
ut4s4c1h2ö
ut2sp2a
ut1sp2
ut3te
u4t1t
ut5t4l
ut2ts2
ut2u4re
u1tu
utu5ru
u3tü
utz3e4n1g
u4t1z
utze4n1
ut2z1in
ut2zo
ut2z1w2
2u1u2
uu1fe2
u1ü2
2u1v4
u2ve.
u1ve2
uve3rä
uver1
u1w
2u1x
u1x2e
ux2o
u4x3t
u1ya
2u1z
uz1we
uzw2
u4z3z4
1üb
2ü2b1c
2ü2b3d4
übe2
ü1be3c
über3
üb3l2
üb3r4
üb2s3t
ü4b1s
2üc
ü1che
üc4h
ü2c2h3l2
üch2s1c
ü4c4hs
üch1t4e
ü2c4h1t
ü3cken
üc4k
ü1cke
ück1er
ück3e1ri
ü4cke4r1s
ück4sp2e
ü4c2ks
ück1sp2
2üd
üd3a4
ü3d2en.
ü1de
üde4n4g
ü3d2e6n1s
üd1o4
üd3r4
ü2d3s2
üd1sa1
ü2d3t4
üdwes2
ü2d1w
ü2f1a
ü2f1ei
ü1fe
üfer2
ü2f1e4r1g
üf2f3l2
ü2f1f
ü2f1i
üf3l2
üf2to
ü4f1t
ü1g
üge6leis
ü1ge
üg2el
üge3le
üg2elei
ü2g3l
ü2g1n
ü4g3s1
üg4s1t
üh1a
ü1he
ü2h1ei
ü2h1e4n1g2
ü2h1e2r1k
ü2h1e2r1z
üh1i
ühla2
ü2hl
üh4l1ac
üh1l2e
üh3mo
ü2h1m
üh3n2e
ü2hn
üh6n2s
üh3r2e
ü2hr
üh4r3ei.
üh1ro
ühr3ta
üh4r1t
ü4h1s
üh1s2p2
ü4h3t
üh4t1h
ü1hu
ü2h1w
ü1k
ül1a
ü2l2c
ü1l4e
ül2la
ü4l1l
ül2l1ei
ül1le
ül2lo
ül2lö
ü1lu
ü2me4n1t
ü1me
2ün
ü2n1a
ün2da
ü2n1d
ün2d2r4
ün2d3s
ü3nen3
ü1ne
ün2fa
ü2n1f
ün2f1ei
ün1fe
ün2f1li
ünf4l2
ün2f4r2
ün2g3l
ü4n1g
ün6n2s
ü4n1n
ü6n2s
ün3sc
ün3se
ün3sp4
ün3s2t2r4
üns1t
ü2n1t2
ü1nu
ün2za
ü2n1z
ü1p2e
ü1pi
üp2p3l4
ü2p1p
ür1a
ü2r1ei
ü1re
ür2f2l2
ü2r1f
ür2f2r2
ür4g3e4n4g
ü4r1g
ür1ge
ür3gen
ü3r2o1
ü2r1r2
ü4r2s
ür3sc
ür3se
ür3s2p4
ürt2h
ü4r1t
ü1s2a
ü2s4c2hl2
ü1sc
üsc4h
ü3se3h
ü1se
üse3l
üse1s
üs2s1c
ü6s1s
üs1s2e
üs2s1t
ü2s1t
üste3ne
üs1t2e
ü4s3ten
ü1ß
2üt
ü2t1al
ü1ta
ü2t3r4
ü2t2s1
üt2t2r4
ü4t1t
ü1v
ü1z
2v1a1b
va1c
va4l2s
2va4n1g
2v1a2r1b
va1s
v4a1t
va2t3a4
va2tei
va1t2e
va2t3h
va3t2i1k2
v4a3ti
va4t1in
vati8ons.
va3t2i1o
vation2
vati3o6n1s3
v4a2t3r4
va2t3s4
va2t1u
2v1a2u
2v1b
2v1d
1ve2
ve3ar
v2e1a
ve3b
ve3c
ve3d
ve3g
ve3h
ve4i
ve2it4
vei2ts3
ve3la
ve4l1a2u
ve3le
v2e3li
v2e3lo
v2e3ma
ve1m
2ve3mu
ve3nal
ve2n1a
ve2n2c
ve3ne
vene2n4d
v2e3nen
ve3ni
v2e3nö
v2e3o
ver1
ver3a
ve3r2a1d
ve3ra2n3d
ve3r2as
ve2r3b2
ve2r1d2
ve1re2
ve4re1k
ve2r1f4
ve4r1g4
v2e3ri
ve4rin
ve2r3k
ver3s1t
ve4r1s
ve4r1t2
ver5te
v2e1r3u
ves1
2ve3s2c
2ve3s2e
ves3ti
ves1t
ve3ta
ve1t
ve3te1
v2e3t2r4
2ve3ü
v2e3v
ve3x2
2v1f4
2v1g
2v1h
vi3ar
vi1a
vi4a3t
v2i2c
vid3s2t4
vi1d
vi2d1s
vi2e2h3a
vi2el
vi2er
vie4rec
vi2e1re
vie2w1
v2i1g2
2vii2
vi2l1a
vi4le2h
vi1le
vi2l1in
vi1li
2v1i2m
vi1ma2
vi4na
vi6n2s
2v1i2n1t
vi3sa
v2i1se4
vi3s2o
vi2sp2
vi1s2u
2v1k
2v1l2
2v1m
2v1n
2v1o1b
vo3ga
v2o1g
vo2gu
3v2ol
voll1a
vo4l1l
vollen4
vol1le
vol6l5e2n1d
vol2li
2v1op
vo2r1
vor3a
vo2r3d
v4o1r3e
vo4r3g
vo3ri
vo5r2i1g
vormen4
v2o4r1m
vor1me
3voy
v2ö2c
2v1p
v2r
2v3ra
v3re
v4r4ee
2v3ro
2vs
v1s2e
v1s2ta
vs1t
v3s2teu
vs1t2e
v3s2z
2v3t
vu2e1t
v2u1e
2v1u2m1f4
2v1v
2v1w
2v1z
w2a
1w2aa
wab2bl2
wa1b
wa4b1b
wa3che
wac4h
wach6s1t2u
wa4c4hs
wachs1t
wach4t4r4
wa4c4h1t
waf1f2e2
wa2f1f
waffel3
1wa1g
wa5ge
wa2g3n
wa3go
1wah
wahl5e4n1t
wa2hl
wah1le
wah4ler
wah2li
wa2i2b
1wal
2wa2l1b
wal4da
wa4l1d
2wa2l1m
wal2ta
wa4l1t
wal2to
walt4s1t4
wal6ts
wa3na
wande4l4s6
wa2n1d
wan1de
wan4g4s1
wa4n1g
wa2p
1w2a1r2e
ware1i
war3s1t2e
war2s1t
wa4r1s
war3t4e
wa4r1t
1was
wa3sa
wa4scha
wa1sc
wasc4h
w4a3s2e
w4a3s1h
was3s4e
wa6s1s
w2ä
1wäh
1wäl
2wä4n1g
1wäs
wä1s2c
wä3sche
wäsc4h
2w1b2
wbu2
2w1c
2w1d
w2e2a
w2e2ba
we1b
4we3be1b
w2e2bl2
we4b3s1
we3cke.
wec4k
we1cke
we5ck2en.
we3ckes
w4e2e4
wee1d3
w2e2f2l2
we1f
1we1g
we2g1a
we2g3l
we2g3r4
we4g3s4
1weh
we2i
wei4bl2
w4e2i1b
2wei1e
we2i1k4
weis4s3p2
wei6s1s
wei3s2t2r4
weis3t
wei4t2r4
we2it
wel6s4c2hl2
we4l1s
wel1sc
welsc4h
wel6sc2h2r4
we4l2t1
wel4t3a4
wel6t5e2n6d
wel3t2en
wel1te
wel4t3r4
we2n3a4
we3ni
wen4k3ri
we2n1k
wenk2r4
we2r3a
wer2bl2
we2r1b
1werb2u
we2r1d2
5werde6n1s
wer3de
wer3den
1wer1du
w2erer2
we1re
wer2f2l2
we2r1f
wer4g2el
we4r1g
wer1ge
we4r3i1o
w2e1ri
1w2erk.
we2r1k
wer2ka
1werke
wer2kl2
wer2ku
we2r1ö
we4r2s
wer2ta
we4r1t
wer6t5e4r1m
wer1te
wer2to
1wer4ts
1we1se
w2e2s1p2
we4s1t
wes2t1a
wes2t3e2i
wes1t2e
we4s2t1h
wes2t1o2
we1s2t3r4
we1s4t2u
1we1t
we2t2s
wet2t3s
we4t1t
2w1ey1
2w1g
2w3h
wi3c1k1a
w2ic
wic4k
1wi1d
wi2e
wie3l
wi2e1n2e
w2i1en
wie2s1t
w2i1k2
1wil
wim2ma
wi2m1m
wim4m3u
win4d3e4c
wi2n1d
win1de
w2in2d2r4
w2i1n2e
2wi4n1g
win8n7er1sc
wi4n1n
win1ne
winne4r1s
1wi4r
w2i3s2e
wi2sp2
1wi6s1s
wi3t1h
1wit2z1l2
w2i4t1z
2w1k
2w1l
2w1m
2wn
w6n3s
1w2o1c
wo2ch1a
woc4h
wo1che4
1woh
woh2le
wo2hl
1wo2l1f
w2ol
wol4f4s3
wol4ler
wo4l1l
wol1le
wor3a
wo2r3i
wor2t3r4
wo4r1t
wo4r3u
w2o1t2
1w2ö1c
wört2h
wö4r1t
2w1p
w2r
w3ro
2w1s
w3s2k2
ws2t
2w1t
w1ti2
w2u
1w2uc
wu1l2
wul3se
wu4l1s
wu6n2s2
wun1
4w2ur.
wur2fa
w2u2r1f
wu4r2s
1wurs1t
w2us2
wus3t2e
wus1t
1w2u4t1
1wüh
wül2
w2ün3
2w1w
x1a
1xa.
2xa2b
1x2a1d
1x2a1e
x2a1f3l2
1x2a1g
x3a2m
x2a2n1z
1x2as
2x1b
2xc
x1ce
x1c4h
x1c4l2
4x1d
1xe
x1e4g
2xe1k
xe2l
x2e3lei
xe1le
x1e1m
3x2em.
x2en
xe6n3s2
x2er.
x2e1re
xe4r1s2
3xes
2x3eu
2x1f
2x1g
2x1h
x2i1b4
x2i1c
xic4h2
xi1de2
xi1d
xi2d1e1m
x1i2do
xie3l
x2i3g
xil1
xi1l2a
x2i2lo
xi2lu2
xi6n3s2
x2is1
xi1s2c
x2i2se
xi2so2
xi6s3s
xi3s4tä
xis3t
xi2su
x1i2tu
x1j
2x1k2
2x2l2
x3lä
x3le
2x1m
2x1n
x1or
4x1p
xpor6ter
x1p2o
xpo4r1t
xpor1te
x1q
2x1r
2x3s2
4x1t
x2t1a
x3t2as
x1t1ä
x2tän
xtb2lo4
x4t3b2
xtbl2
x2t1e2d
x1te
x2t1ei
x4te4n1t
x3ten
x2t1e2r2f
x2t3e1v
xt1fi4
x4t1f4
x2t1i4l2l
x1ti
xtr1a3b4
x1t2r4
x2t3ran
x2t3s2
x1t1u
x3tur
1xu
x2u1a
x1u2n1
x2u2s
2xv
2x1w
2xy
3xy.
3xys
x1z
2y1a1b
1yac
y1al.
y1a2m
ya4n2g
y1a2n1k
y1ät
y1b
y1c2
y2chi
yc4h
y3chis
y2c2h3n4
y1d4
y1e
y2e1f
ye4n4n
y2e1re
y2es.
y2e1s2p2
ye2t2h
ye1t
y1f2
y1g
ygi2
ygie5
yg2l
y1h
y2hr2
y1i4
y1j
y1k2
yke3n
y2k3s2
y1l
y2l3a2m
yl4an1te
yla2n1t
y2l3c
y4le.
y1le
yli4n
y1li
yl4o3ni1
y1lo
y4l3s2
y2l1u
ym2a4t
y1ma
y2m3p4
ym3pi1
y2n1o
y2no4d
y2n1t2
y1nu
y1of
y2om2
y4o3n4i
y1o2n1t
y1os
y1o2u
y1p
yp2a2
yp3an
yp2e2
y2pf
y3ph
y2p1in
y1p2o3
y4p3s
y1r
y3r2e
y3ri
yri2a
yr2i1e
y3r4o
y2r1r2
ys2an
y1sa
y3s2c
y1se1
y3s2h
y4s3l2
ys1me3
y2s3m2
ys2p2o
y1sp2
ys1p2r2
ys3t4
y1s4ty1
y2s1u2
y3s2z
y1t2
y3te.
y1te
y3tes
y3to1
yu2r
y2u1re3
y1v
y1w
y1y
y1z2
2z3a2b
zab3l2
za1c
z1a2d
za3de
2z1af
za3g2r4
za1g
3zah
2z3a2k
z2a1le3
2z1a4l1l
2z1am
z1an
za2na
2z3a2n1f
3z2a3ni
2z3a2n3l2
2z1a2r1b
2z1a2r1c
z1a4r1m
z1ar3t2i
za4r1t
zar2t2r4
2z1a2r1z
z1as
za1s1t4
2z3a1t3
3z2a2u1b
za2u
z1au2f
z3a2u1g
3z4aun1
zä2
2z1äc
3z2äh
2z1äm
z1ä4r1g
z1ä4r1m
4z3b4
zb1ü1b
zbübe3
2z3c
2z3d2
zdan2
zdä1
2z1e2b2en
ze1b
2zecho
zec4h
2z1ec4k
z4e1e
2z1e2f1f
ze1f
ze2i1k4
zei3la
ze4il
zei1le4
2z1ein
zei3s4
zeis3t4
zei2t1a
ze2it
zeit5e2n1d
zei3te
zei3te4n
zei4t3er
zei2t2r4
ze2l1a2
ze2len
ze1le
ze2l1er
ze2l1in
z2e1li
zell2a
ze4l1l
zel3s1z
ze4l1s
zel3t2h
ze4l1t
z2e1lu2
2z1e2m1p
ze1m
5zen.
z2e4n3ac
ze2n1a
ze4n3n
ze2no
zen1s2e
ze6n1s
z1en4se1m
3ze4n1t
zen4t3s
zen4zer
ze2n1z
z2er.
ze2r3a
ze2r1e2b
ze1re
2z1ergä
ze4r1g
4z3erge1b
zer1ge
z3erhal
zer3h4
ze2rha
2ze2r1h2ö
ze2r1i2n4t
z2e1ri
ze2r1k2
z2erl.
ze4r1l
2zer1l2ö
z2e4rn
zer4ne1b
zer3ne
zer4n3ei
ze2ro
2z1er1q
ze4r1s2
2z1er1s2a
4z3ers1t2e
zers1t
zer1t1a4
ze4r1t
zer4t3a1g
zert4an
zer6te1re
zer1te
zer4tin
z4er1ti
zer6tra2u
zer1t2r4
4zerwe2i
ze2r1w
2z1e2r1z
3z2erza
ze2sä
ze3s2c
ze1s1e
ze1s1i
ze3s3ku
ze2s1k2
z2e2sp2
zessen4
zes3se
ze6s1s
zes6s5e2n1d
zes2sp2
zes2s1t
ze2s3t
ze3s2ta
z2e2t2r4
ze1t
2zet2ts
ze4t1t
2z1ex
2z1f4
2z1g2
zge1r2a
z1ge
2z1h
z2hen
zh2i2r3
zi3a1lo
zi1a
z2ial
zi3ar
z2i2d3r4
zi1d
zi1er3h4
zie4r1s1
zi1es.
zi1l2e
2z1i2m1p
z2i1n2e
zin4er
2z1i2n3f
2z1i2n1h4
zi2n1it
zi1ni
zin2sa
zi6n1s
zin4ser
zin1se
4z1in2s1u2f
zin3su
2z1i2n1v2
z2i2o3
zi3o1p
z2i2r1k2
zir2k6s
zi3s2z
zi1t2h
2z1j
2z3k4
2z1l2
2z1m2
zm4e2e
z1me
2z3n4
2z1o1b
2z1of
zo2gl
z2o1g
2z1oh
3z2ol
zon4ter
zo2n1t
zon1te
zo2o
2zop2e
z1or
zo2ri
zor4ne
zo4rn
2z1o2s1z
2z3o1t
2z1ö2f
z1öl
2z2ön
2z3p4
2z1q
2z3r2
4z1s2
z3sa
z3s1h
z3s1k2
z3s1z
2z1t
z2t1a2u
z1ta
z4te1he
z1te
z2teh
z3t2her
zt1h
zth2e
z1t3ho
zt1i6n1s
z1ti
z3tö
z4t3rec
z1t2r4
zt1re
z2t3s2
z3tü
zu1
z2u3a
z2u1b4
zu4c4h
z2uc
zu3cke
zuc4k
z2u1d4
zu1di4
zu2el
z2u1e
zu3f4
zu2g1ar
z2u1g
zu4ge4n1t
zu1ge
zu3gen
zu3g1l
zug1un1
zu1gu
2z1u2hr
zu1h
zu3k
2z1um.
zum2en2
zu1me
2z1u2m1f4
2z1u4m1g2
2z1u4m3l2
2z1u2m1s
zu3n2e
zun1
zu4n1g4
2z1u2n1t
zu2p2f1i
zu3r2a
z1u2r1k
2z1u4r1l
2z1u4r1s
2z1u4r1t2
z2u3s4
z2u5t2
z2u1z2
2z1üb
zü2r1c
2z1v
zw2
z1wac
zw2a
4z1wah
zwa2n2d1
z2wa4n1g
z1war
2z1was
4z1wäl
zw2ä
2z1we1g
z2we2i1g
zwe2i
z1weis
2z1wel
2z1wen
2z1wer
z2we4r1g
2z1wes
2z1we1t
4z1wi4r
z2wit
2z1wo
z1wör
z1wur
zw2u
2z1wü
4z1z
z3z4a
zzi1s4
z3z2o
zz2ö
PK
!<FF#[		hyphenation/hyph_de-CH.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ab1a
.a1b
.ab3l2
.abo2
.ab3ol
.ab1or
.a2c4k2
.ag4n
.a1g
.ag4r4
.a1g2u
.ai2s
.akt2a
.a4k1t
.al3b2r4
.a2l1b
.a1l2e
.al5l4en
.a4l1l
.al1le
.al4tei
.a4l1t
.al4t3s
.amp2e4
.a2m1p
.am4t4s1
.a4m1t
.a2n3d2
.ande2n6k
.an1de
.and4ri
.and2r4
.a4n1g2
.an3g1li
.ang1l
.an4g4s2
.ang1st3
.a6n3s
.an4si.
.an1si
.ans2p4
.an4t2a1g
.a2n1t
.an3t1h
.a2p1s2
.a1p
.ar2i1e
.a1ri
.ar1k2a
.a2r1k
.ar4m3ac
.a4r1m
.ar1ma
.ar2sc
.a4r1s
.ar4t3ei
.ar3t2e
.a4r1t
.a1s2t
.ata1
.a1t
.at4h
.a2u3d
.a2u
.au2f3
.au4f1s2
.a2u2s1
.ausc4h3
.au1sc
.au6ste2s
.au1st
.au3st2e
.a1x2
.äm3
.ä2t2s
.be3e2r1b
.b4ee
.be3r1a
.be3r2e
.berg3a
.be4r1g
.ber6ga1b
.ber4g3r4
.bo1ge2
.b2o1g
.bo4s3k2
.bo1s
.bu4s1er
.b2us
.bu1se
.c4h2
.d1a1b4
.da2r1
.da4rin
.d2a1ri
.da4r1m1
.d4a4te.
.dat4e2
.d2a1t
.d4a4te2s
.de2al
.d2e1a
.d1e1i
.de4in.
.d2e1o2
.d2e3r4en
.de1re
.de3s1k2
.de1s
.d2i1en2
.di2e
.do2mo
.d2om
.do1p2e
.d2o2r1f1
.d1ü1b
.ebe2r1
.e1b
.ehe1i
.e1he
.ei3e2
.ei4n1a
.ei4n1e4n6g
.e2i1ne
.ei3nen
.e2i2sp2
.ei1s
.ei2s1t
.ei2t2r4
.e2it
.eke2
.e1k
.el2bi
.e2l1b
.e2m3m2
.e1m
.en1
.en4d3er
.e2n1d
.en1de
.en5d4er.
.en2d3r4
.e4n1n2
.e4n2t3
.epi1
.e1p
.er8bre2c4h1t
.er3b2r4
.e2r1b
.erb1re
.erbr2ec4h
.er3da
.e2r1d
.er4dan
.er4dar
.er4d1ei
.er3de
.er4der
.e1r1e
.ere3c
.e2r1f4
.e1r1i
.e4r1s2
.er8s2t1ein
.er3s2t2e
.er1st
.erste2i
.er8ste2r1b
.er8stritt.
.ers2t2r4
.erst1ri
.erstr2it
.erstri4t1t
.er8stritt6en.
.er4zen4
.e2r1z
.es1p2
.e1s
.es2st
.e4s1s
.e1s2t
.e3s2t4e
.e2st2h
.e2t2s
.e1t
.eu1
.e2u3g4
.e2u3t
.eve4r1
.e1v
.e1ve2
.e4x1t4
.fe2i
.fer4no
.fe4rn
.fe4st1a
.fe1s2t
.fe1s
.fi4le.
.fi1le
.fi4len
.fi2s
.f1s2
.fu2sc
.f2us
.g2a2t
.gd2
.ge5n1ar
.ge3n1a
.ge3ne
.ge3r2a
.ge3r2e
.ge3s4
.ge1t4
.ge3u
.grif8fe1s
.g2r4
.gr2if
.g1ri
.gri2f1f
.grif1f2e
.gu4s1s1
.g2us
.haf2t3s1
.ha4f1t
.ha4l2s
.ha2u2t1
.ha2u
.he2
.h2e3fe
.he1f
.her3an
.her1a
.h2e3ri
.he6r5i4n1n
.ho4me1t
.ho1m2e
.h2om
.i1a2
.i1m2a
.ima4ge
.ima1g
.i2m5m
.in1
.i1n3e
.i2n1k4
.in1n2e
.i4n1n
.in1u1
.i1r2e3
.i1s2a
.i1s
.jor3
.ka2b5l2
.ka1b
.ka2i
.ka2m1p2
.ka4t3i1o
.k2a1t
.k4a3ti
.ken6num
.ke4n1n
.ke4r3s
.ki4e
.kle2i
.kl2
.k2le
.kn4i4e
.k2n2
.k1ni
.kopf1
.k1s2
.k2us2
.le4ar
.l2e1a
.l2i2f
.li4ve.
.l2i1v
.li1ve2
.lo4g3in
.l2o1g
.lo3ver1
.l1o1v
.lo2ve2
.lu4st2r4
.lu1s2t
.l2us
.ma3d
.ma2i
.ma3la
.m2a4st2r4
.ma1s2t
.m1d2
.m4e2e
.me1l2a
.men8s2c4hl2
.m2e6n1s
.men1sc
.mensc4h
.men8s2c2hw
.me4n3t4
.mi2t1
.m1m2
.nä2s1c
.n4i4e
.no1b4
.n2o2c
.no2s
.n4o4t3h
.n2o1t
.nu1l2
.n2u2s2
.ob1a
.o1b
.ob2e2
.oh4r5s
.o2hr
.o1m2a
.oper4
.op2e
.or2a
.o4r1t2
.ort1s3e
.or4ts
.ort4st4
.os5t6a2l1g
.o1s
.o1s2t
.o3st2e2
.ost5e2n1d
.osten8de
.oste6r3e
.ost3r4
.ozo4
.o1z
.p2a4r1e
.p2a
.par3t4h
.pa4r1t
.pf4
.ph4
.po1ka2
.p2o
.p2o1k
.po4st2r4
.po1s4t
.po1s
.p1s2
.re4b3s2
.re1b
.re3cha
.r2ec4h
.rei4n4t
.r1ein
.r2e1li1
.re3l2i3e
.r2i2as
.ri1a
.richt6e
.r2ic
.ric4h
.ri2c4h1t
.r2o4a2
.ro3m4a
.r2om
.rö2s1c
.rö1s
.r1ü1b
.rü4ck1er6
.r2üc
.rü2c4k
.s2a1li1
.sc4h4
.s2e6n3s
.s2e1r2u2
.se2t1
.sh2a2
.s1h
.si2m3p4
.si2te
.s2it
.ski1e
.s1k2
.spieg2e8lei
.sp2
.s2pi4e
.spie1g
.spie3ge
.spieg2el
.spiege3le
.st4
.st4o4re
.sucher6
.s2u1c
.su1che
.suc4h
.t4a1l2e
.t3an4k3l2
.t2a2n1k
.ta2to
.t2a1t
.t4e2e
.te2f
.t2e3no
.t1h4
.ti2a
.ti1d1
.ti4me.
.ti1me
.ti4me1s
.t2i2s
.ti3te4
.to4nin1
.t4o3ni
.to4p2l4
.t2o2w
.tri3e4s
.t2r4
.t1ri
.tr2ie
.ts2
.tu3ri
.u1f2e2
.ufer1
.ul4mei
.u1l
.u2l1m
.ul1me
.um3
.u1mo2
.u1n3a2
.un1
.u2n3d
.u3n3e
.u4n3g
.u3n2i2t
.u1ni
.ur1
.u1r2i
.u2r3i6n4s
.ur3o2m
.u1ro
.uro2p
.u4r3s2
.ut2a
.u1t
.ut3r4
.übe4
.ve5n2e
.ve2
.vo4r1
.wa4h4l
.w2a
.wa2s
.we4g3s2
.we1g
.wei4ta
.we2i
.we2it
.wi4e
.wor2
.wort5e2n6
.wo4r1t
.xe3
.ya4l
.zei2t3s
.ze2it
.zi2e
.zin4st
.zi6n1s
2aa
a1a1b
aa2be
aa1c
aa2g2r4
aa1g
4a1a2n
4a2ar
aa2r1a
a2a2r3f4
aa4r1t2
aa1s1t
aa2t4s1
a2a1t
a3a2u
a1ä
a1b
2aba
ab2ai1s4
ab1auf
aba2u
ab1ä
ab2äu
1a2b3d4
a3b1e1b
ab4e1e
ab1e4il2
2a3bel
abe2la
2a3ber
ab1e2r1k
ab1e2r1r
ab1e2r1z
ab3es1s4e
a3be1s
abe4s1s
abe1s2t
ab1eß
2a3be1t
2a3b2e1w
1a2b5f4
3ab1fi
1a2b1g2
1a2b5h2
2a1bi
ab1i6n1s
ab1ir
ab1it
1a2b1k4
ab1l2
1a2bla
ab5la1g
1a2blä
2ab2le
ab4le.
ab3li
ab4lo
3a2b1lö
a2blu
1a4b3n2
2abo
a2bo.
a2b2of
3a2bon
ab3r4
a3bra
a4brä
2a3b1rü
ab1s2a
a4b1s
1ab5sc
ab5s2i
1ab3sp2
ab1s4t4
1absta
ab3s1z
1abtei
a4b1t
ab5te
2abu
ab1ur
2abü
1a2b1w
2a3by1
1a2b3z2
2a1ca
2a2c1c
a1ce1m
2a4ch.
ac4h
ach1a
a1chal
ach3a2u
2a2c2h3b2
a1che
a2ch1e2c
a4ch1ei
a4che2r1f
a4che2r1k
a4ch1er1ö
a4ch3e2r1w
4a2c2h3f4
a1chi
a2c4h3l2
a4c2h3m
a2c2h3n4
a1cho
a3cho.
a2ch1o2b
ach1or
ac1h3ö
ac2h3r4
ach3s1u
a4c4h1s
a4c4h1t
acht5e4r3g2
ach2t1o2
ach8t3r4aum
acht2r4
achtra2u
ach8träume.
ach1trä
achträ2u
achtr2äum
achträu1me
ach8träum2en.
achträum2en
ach6tr2it
acht4ri
a1chu
a2ch1u2f
a2ch3ü
2a2c2h1v
4a2c2h1w
a1ci
ac1in
2ack.
a2c4k
ack2en
ack1mu4
ac2k1m
ackm2us3
ack2se
ac2k1s
ack3s1l2
ack3sta4
ack1st4
a1c4l2
a3co
aco4n4n
2a3cu
a1ç
a1d
2a3da.
a3d2a1b
ad2a1g
a3d4ai4
ada2m
ad3a1m2a
a2d1an
3a4d1a1p
a3d2ar3
4ad2a1v
1a2dä
a2d1c
1a2d1d2
2ade.
a1de
ade2al
ad2e1a
ad2e1fi4
ade1f
a2dein
ad1ei
2aden
ade1r2a
a2d2e1ri
4ade1s2
ad2e3s1p2
ade4s6s2
2a2d1f4
2a2d1h2
4a3di
ad2i3en
adi2e
5a2d1j
2ado
ad2o1b
2a2d3p2
2a2d1q
2ad3rec
ad2r4
ad1re
ad4re1s
ad3ru
2a2d1s2
ad3s1z
a2d2t1
2a1du
2a1e1
ae2b
ae2d
ae2i
a2e1k
a2e1la
a2e1le
a2e2o3
ae2p
3a2er2o
a2e1t
a2e1w
ae2x
a1f1a
a2fak
a2fan
a3f2ar
af4a1t
a2fa2u
2a1fe
a2f1ec
a2f1e4n1t
af1e4r1l
a2f1ex
af2f3l2
a2f1f
af4flu
2a1fi
2af3l2
a2fö
af3ra
af2r2
af3rä
af3re
af3rö
af3s2a
a4f1s
af2sp2
af2t1a
a4f1t
af2tei
af4t3e4r1l
af2t3r4
aft5re
af2tur
a2f3ur
a1fu
a1g
2aga
ag1a1b
ag1a2d
ag1ar
ag1a2u
ag2di
a2gd
ag2d3r4
ag2d1u
age1i
a1ge
age4n1a
a3gen
age4n1e1b
age1ne
a2ge4n1t
a4gentu
ag2er
age4r2al
age1r1a
2age1s
age2sa
age4sel
age1se
age4s2i
ag2e2s3p2
age4s5s
ag3es1se
age6ste1m
age1st2
age3s2t2e
ag3g3l
a4g1g
1agg4r4
3a2git
2a2gl
ag4l2a
a4g1lö
ag2n
ag4ne.
ag1n2e
a2g4nu
a2g3re
ag2r4
a2g3ri
ag4ro
ag2sa2
a4gs
ag4sam
ag3sc
ag4se4t
ag2s1e2
ags3p4
ag6s1p2o
ag4sti
ag1st
ag2s1t2r4
2a2g1t
ag2t1h
a2gu2n1d
a1gu
agun1
2ah.
2a1ha
ah4a1t
2a1he
a2h1e2r3h4
a1h2i
ahi2n3
ahl3a2
a4hl
ah4l1ei
ah1le
ah4l3e2r3h4
ah3ler
ah2l2ö
ahl3s1z
ah4l1s
ah4n1a
a2hn
ah2nä
ahne1r4e2
ah1n2e
ah2n1t2
1ahor
ah1o2s
a2h3ö
ahr1a
a2hr
ah3r2e
ah3re4s3
ah3ri
ahrta4
ah4r1t
ahr4t3ri
ahrt2r4
2a4h2s
ah4t1s
a4h1t
a1hu
a2h1w
a1hy
a2ian3
ai1a
ai2d4s
ai1d
aid1s1t4
ai1e2
a2if2
a2i3g4
a3ik.
a2i1k
a4i3ke
ai3ku
a2i2lo
a1i2n1d
a2i1n4e
a1i4n1g
ain3sp4
ai6n1s
2ai1s
ai2sa
a3i6s4ch.
ai1sc
aisc4h
a2i3s2e
aiso2
ai4s1s2
ais4se.
ais1se
ais5s1t
a3iv.
a2i1v
ai1ve3
a3i2v1l2
a3i2v1s
a1j
ajekt4o
aje1k
aj2e4k1t
2ak.
1a2k4a1d
a1ka
2akal
2a3kam
2akar
ak4a1t
1a2k2a1z
2a2k3b4
2a2k3c
2a2k3d2
4a1ke
a2ke1f
ake4n2n
a2keu
2a1ki
2ak3l2
ak4li
4a1ko
2a1k2r4
ak3ra2u
3akro3
2a2k1s
ak3s1h
2akta
a4k1t
2ak4t3b2
ak3te
ak4t1ei
2akt2i1k
ak2t3r4
ak3t4ri
2akt1s4t4
ak2t3s2
2a1ku
a2kun1
4a3kü
1a2k3z2
a1la
2a5la.
ala5c4h2
a2l1af
ala2g
al1a1ge
a3l1al
al1am
al2a1mi5
al3a2m1p
al1a1na
a2l1a4n1g
a2l1a6n1s
al1a2n1z
a2lar
a3l2ar.
a3l2a1re
al2a4r1m
al3a2r1r
ala2s
a2l1a1si
al1a4s1s
2al2a1t
al1a2u
a1l3a2u1g
a1lä
al1äm
alb3ein
a2l1b
alb3ei1s
al4be2r3h4
al4b3e2r1w
al2b1l2
al2b3li
al2boh
al2b2r4
alb3ru
al4b3s
al2dä
a4l1d
al2d3r4
al3du
2a1le
3a2l1e2b
3a2l1e1f
a4l1eh
a2l1ei
a4l3ein
a2l1el
alen1
al3en2d1s
ale2n1d
a2le4n1g
a2le2p
al1e1p2o
a2l1e2r1f
a2l1e2r3h4
al1e4r1l
3ale4r1m
a2l1e4r3t
3a2l1e2r1z
a2l1e3s1k2
ale1s
ale4t
al1eta
a2l1e3t2h
a2l1e2u
a4leur
3a2lex
alf4r2
a2l1f
3algi
a2l1g
al2g1li
1al3go
2a1li
ali4e1ne
al2ie
al2i1en
al2i2m1b2
a2lim
ali4n3al
ali3n2a
al1i6n1s
a2l1i2n1v2
alk1ar
a2l1k
al1ka
1alkoh
al1ko
al2k3s2
al2l1a1b
a4l1l
al2l3a4r
al2l1a2u
al4lec
al1le
al3le2n1d
all5er1fa
alle2r1f
al3l2e1s
1allgä
al2l1g4
alli5er.
al1li
all2ie
alli7ers.
allie4r1s
al2l1o1b
al1lo
3al2m1b2
a2l1m
2a1lo
a2l1o2b
alo2ga
al2o1g
al1op2e
a2l1o2r1c
a2l1ö
al2ö1s
3al1pe.
a2l1p
alp2e
1al1ph
al3sk4l2
a4l1s
al2s1k2
al2s1um
al3su2n1
al2t1ak
a4l1t
alt3e2i1g
al3ter
al4t3e2r1f
al2tö
al2t3re
alt2r4
al2t1ri
alt3r2ic
al2tro
alt2se
al4ts
alt4stü
alt1st4
a1lu
a1l2uf
a2lum
al1u2m1b2
a2l1ur
4a1ly
al2z1e2r4z
a2l1z
al2z1w2
2am.
2a1m2a
am1a1b4
ama1d2
ama3g
2a3mä
a1m4e
2ame.
a2me1b4
ame2n1
ame1r2a
a2m2e1ri
am2e3ru
a4m4e3s1h
a3me1s
a3me1t
a2me1w
a3mi.
a1mi
a3mie
2a3mir
a3mi1s
ami3t2a
a3mit1
ami3ti
2a2m1l2
am2mac
a2m1m
am1ma
2am2m1al
am2m1ei
am1me
am2min
am1mi
2am2m1l2
am4m2o1d
am1mo
am2m2us
am1mu
amm2u2t
a2mö
amp2fa2
a2m1p
am2pf
am3p2r2
2a2m1s
am4s2c4hl2
amsc4h2
am1sc
1amt.
a4m1t
am2t1a
am2t1ä
am2tel
am4t3e4rn
am2tö
am2t3r4
am2tu
2a1mu
2a3na.
a1na
2a2n1a1b
a3n2a3c
an4a3di3
ana1d
a3n1ak
an1a2l1g
ana4lin
an2a1li
2anam
2anan
2a3nas
an1äs
a1nä
1a2n3b4
2anbu
an3c4h
a2n1c
2and.
a2n1d
an3d1ac
and4a4r1t
ande4sc
ande2s
an1de
an2dex
an2d3rü
and2r4
and4sas
an2d1s
and1sa
and6s1pas
andsp2
andsp2a
and6s5paß
and2su
2an1du
and1ur
2a1ne
a2n3ec
a3n4ee3
an2ei.
a2n1ei
a4n3e2if
an1e4k
3a4n1e2r1b
an1e2t2h
ane1t
1a2n1f
2an3fi
anf2t3s3
an4f1t
an3f2u
4ang.
a4n1g
1anga
3ange1b
an1ge
an2g1ei
an4g3e2r1f
an3ger
an4g3e4r1l
an4ge2r1w
an4g3e2r1z
2an2g1f4
2an2g1h
2angie
ang1l
an2gl2a
2ango
ang2r4
an2g3ra
4angs.
an4g2s
ang3s1c
ang6s3p2o
angs1p4
1a2n1h2
2a3ni
an2i3d
ani5ers.
an4ie
anie4r1s
3a4nim
a4n1i6n1s
anin1
2an1j
2ank.
a2n1k
an2k1an
an1ka
an2kei
an3kl2
an4k1lö
an2k3no
ank2n2
ank2r4
an2k3ra
an2k3rä
an4k1t4
1a2n3l2
2an1mu
a2n1m4
2a4n1n
3an3na
3an3nä
an3n2e
a2n1o2d
a1no
a3n2ol
a2n1or
a3no1s
a1nö
1a2n3r2
1ansä
a6n1s
1an1sc
ans2en
an1se
an2seu
2an2s1h
2an2s1k2
an3sk2r4
ans1p2a
ansp4
1ansp2r2
an3s2t2e
an1st
an3s2z
2ant.
a2n1t
an2t3a4r
1antá
1antei
3ante4n3n
an3t2en
an3t4h2e
ant1h
1an3t2h2r2
2an3to
an1ton4
3ant2r4
an1t3rin
ant1ri
an2tro
1an4t3w
2a1nu
a3n2u3s
a1nü
1a4n1w
2an1we1t
2an4z3b4
a2n1z
1anzei
2anze2s
2an2z1g2
an2z1i4n
2an4z1s2
1anzü
2anzw2
an2zwi
2ao
a2o1i4
a1op
a1or
a1o1s
a2o3t4
a3ot.
a1ö
a1p
2ap.
2a3p2a
2ap2e
a2pe1f4
a2pé
a2pf
ap2fa
a3pf2l2
a3phä
a1ph
a2p4h3t2
2a1p3l4
a2p2n
a2p2o1t
a1p2o
3ap1p3l4
a2p1p
ap3pu
2ap2r2
2a3pu
2aq
2ar.
a1ra
a3ra.
ar2a1b
a2r3a4b1t
a1r2a3d2
a2r3al
a3r4a3li
a2r1a4n1g
a2r1a6n1s
a2r1a2n1z
a2r3a2p1p
ar2a1p
2a2r1ar
a2r1a2u
a1rä
1a2r1b
2arb.
4arba
ar2ba2u
ar2bec
2arben
2ar1bi
ar2bl2
2arb2r4
ar2b1re
2ar4b2s2
2ar4b1t
2arb2u
ar2b3un1
1a2r1c
ar2dro
a2r1d
ard2r4
2a1re
a2r2e1a
a4r1e2f1f
are1f
a4re1g
a2reh
ar1e2hr
a2r1ein
a4re1k
a3ren
ar1en4se
are6n1s
are3r2a
ar2e2r1f
a2r1e2r3h4
a2r2e1ri
a2r1e4r1l
are3u
ar2e1w
2a2r1f
arf2r2
ar2f3ra
ar2gl
a4r1g
ar2g1n
2ar1h4
2a1ri
ar2i1a
ar2i3e4n
ar2ie
ari3e2r1d
ari3e4r1g
ari5ers.
arie4r1s
ar1im
a2ri2n3it
ari1ni
a2r1i4n1t
a3r2i3u2
ar2kal
a2r1k
ar1ka
ark3a4m1t
ark2am
ar2k1ar
ark3a2u1e
arka2u
ark3la1g
arkl2
ar2kor
ar1ko
ar4k1ri
ark2r4
ar2k1s4
ark3sa
ark3s1h
ar2le1s
a4r1l
ar1l2e
ar3m2ä
a4r1m
ar4me2r1k
ar1me
ar3m2or
ar1mo
ar2na4n
a4rn
ar1na
ar3n2e
2a1ro
ar1o1b
a2r1o2d
a2r1op
a2r1or
2a2r1r
ar2r3a1d
arre4n3
ar3r2e
ar2r1h4
arr3he
2ar1sa
a4r1s
ar4s2c4h2l2
ar1sc
arsc4h
ar1se3
ar3s2h
2ar1si
ar3t2e
a4r1t
ar2th2e
art1h
art2i
artin2
2arto
ar4t3ram
art2r4
art3re
2ar4ts
2a1ru
ar1u1h
a2r1um
a2rü
2a2r1v
arw2a2
a2r1w
2a1ry
ar2zä2
a2r1z
2arze
1ar2z1t
ar2z1w2
as1a1la
a1sa
as3a4u
asa2u2s1
as1ä
a2s1ca
a1sc
a3sche
asc4h
a4schec
a3schi
asch3la
as2c4hl2
a2s4c2h2m
a3s4chu
4a3s2e
a4se1b
as3e2m
a4s1ex
4a2s1h
a3s2hi
asi4n2g
a1si
2a5s2i1s
asi4st
a3skop
a2s1k2
as1ko
a1so1
as1o2f
a3s2ol
as1or
as1p2
as2ph
a1s2pi
as2p2o
a1s1pu
as3s2a
a4s1s
as2s2ä
as3s2e
as4s3ei
as3s2i
as2s1p2
as4st
ass1ti
ass1to
as5st2r4
as5st2u
2asta
a1st
a3stä
a4s3te1p
a3st2e
as2ter
2ast2r4
as4tra2u
a4s2t3rä
a2s2t3re
a4str2ol
a2s1tum
ast2u
a3su
as1wa2s
a2s1w
asw2a
3a2sy1l1
a1s2y
a1ße2
aßen3
2a1t
at1a1b
at2a1f
a3t4a1g
a2t1a4k1t
a1tak
ata3l
a3tam
a2t1a2pf
ata1p
at1a2u
a2ta2us
a2t1ä
a2t2c
at2e
4ate.
a2te1b
at3e2i1g
a4t2e1li
4aten
a2te1p
at2e2ru2
4ate2s
a1tex3
at2h
a2t3h2a
4a1th2e1
3a4t4h3l
4a3ti
3a2t1m2
4atm2us
at3mu
ato4man
at2om
ato1ma
4ator
a2t1o4r1t
a2t1ö
4at2r4
2a1t1r2a4t
a1t3rä
at3re
at3r2om
at2sa
a2ts
at4s2c2hn4
at1sc
atsc4h
at2se
at4se1t
at2si
ats1p2
at3ta
a4t1t
at4t1ak
at2t3a4n1g
at4ta2u
at2tei
at3t4hä
att1h
at2t3rä
att2r4
at2t3s4
at3tu
atu2n1
atz1er
a4t1z
at4ze2r1k2
at4ze2r1w
at2z1i
at2zo
at2z3t2
at2z1w2
a2u
2au.
2a2u1a2
2a2u1b
au2b1li
aub2l2
au2b2lo
4a2uc
auch3t1a
auc4h
au2c4h1t
au2d2r4
a2u1d
2a2u1e
aue2b
au3en.
auen1
au2e1re
au5e2r1ein
au2fa
auf1an
2aufe.
au1fe
2aufeh
auf1er
au4fe2r1k
a2u2f1f4
3au2f3n2
2auft.
au4f1t
2a2u1g
4augeh
au1ge
2au1h
au3ha
au2hu
4a2u1i
au2i1s
2au1j
aule2s
au1l
au1le
au3lü
4aum
au2mal
au1ma
au2m1o
au2m3p2
au2m3s2
4aun1
au3n4a
au3n2e
au2n2i1o
au1ni
au1nu
a4u2n1z
2aup2
aup4ter
au2p1t
aup3te
2au3r2
au2s1ah
a2us
au1sa
ausan8ne.
aus4a4n1n
aus1an
ausan3n2e
a2u2s1a4u
2au1sc
au4s4c2h2m
ausc4h
au4scho
1au4s3d2
au2s3e2r3p4
au1se
au4s3e2r1w
1au6s1f4
1au4s3g2
1au2s1l2
au2so
au2sp2r2
au1sp2
1au2s3r4
au4s1s2
3aus3sa1g
aus1sa
aus4se.
aus1se
au2sta
au1st
au4stec
au3st2e
aus1te4r6m
au3s3t2i2e
aus3t1ri
aust2r4
1ausü
1au2s1z
a2u3ß
au2t1äu
a2u1t
aute4n4g
aute2n1
au4t3e2r3h4
3auto
2au2ts
2a2u1u2
2au1w
2a2u1x
2a2u1z
auz2w2
2a1ü
2a1v
a3v4a
av4a3t4
4a3vi
a2v2r
a2v2s
2a1w
awi3
awi2e
a1x
ax2a2m
ax1a
a1x2e
ax2i2s
2a1ya
a1yeu
ay1e
ay1si1
ay1s
ay3t2
2a1z
az2a3
az2o
az2u
ä1a
ä1b
ä2b3l2
ä4b2s
ä1che
äc4h
äch4e1e
ä1chi
ä2c4h3l2
ä2c2h2r4
äch2sp2
ä4c4h1s
ä1chu
äck2e
ä2c4k
ä1d
ä2da
ä2d1i1a
ä1di
ä2d2r4
ä2d2s
2ä1e
äf2f3l2
ä2f1f
äf3l2
äf3r2
ä4f2s
ä4f1t2
äf2t4s1
ä1g
äge1i
ä1ge
ä2g3l
äg2n
ä2g3r4
äg4ra
ä4g2s2
äg3sc
äg3s1t2r4
äg1st
1ä2gy
äh1a
2ä3he
ä3hi
ähl1a
ä4hl
äh1l2e
äh4l3e4be
äh3le1b
2ä2h1m
äh3n2e
ä2hn
äh3ri
ä2hr
2ä4h2s
2ä4h3t4
ä1hu
ä2h1w
ä1im
ä1is.
äi1s
ä3i6s4ch.
äi1sc
äisc4h
ä1i2s1k2
ä1j
ä1k
ä2k3l2
ä2k3r4
ä1la
älbe2
ä2l1b
äl2bl2
äl2l1a
ä4l1l
ä2l2p3
äl4s2c4hl2
ä4l1s
äl1sc
älsc4h
ä1lu
äm2i3en
ä1mi
2ä2m1l2
äm4ma
ä2m1m
ä2m2s
ämt2e
ä4m1t
2än.
än5de
ä2n1d
än2d2r4
2ä1ne
ä3ne2n1
ä2n2f5
2än1ge
ä4n1g
än2gl
än2g2r4
äng3s1e2
än4g2s
2ä3ni
änk2e
ä2n1k
än2k3l2
än2k2r4
än3n4e2
ä4n1n
2ä6n1s
än2s1c
än3s2e3h
än1se
ä1on
ä1p2a
äp2p3l4
ä2p1p
äp2p2r2
äp2s1c
ä2p1s
1äq
ä2r3a2
är4af
är1ä
ä2r1c
4ä1re
ä2r1ei
äre2n
ä2r1e1ne
är2g2r4
ä4r1g
ä2r1i4n1t
ä1ri
är2k3l2
ä2r1k
är4me4n1t
ä4r1m
är1me
är3m2e3s
är1o2
ä1rö
är1se2
ä4r1s
är2se1b
är2si
ärt4e
ä4r1t
är2t1h
är4t4s3
ä2rü
1ä2r1z
är2zw2
ä5s4e
äse3g2
äser4ei
äse1re
äs2e4ren
ä3s2e1r2i
ä3se3t
äskop2
ä2s1k2
äs1ko
äskopf3
ä3s2k2r4
ä1so
äs1p2
äs2s1c
ä4s1s
äs3s2e
äs4s3e2r1k
äs4s1t
ä1st2
ä3s2t2e
ä2st2r4
ä3su
ä1ß
äß1e2r1k
ä1ße
ä2t1a2
ä3te
ät2e1i
ä2t1ein2
äte2n
ä2t2h
ä1ti
ä1to
ä1t1o1b
ät3r4
ät2sa
ä2ts
ät2sä
ät4s2c4hl2
ät1sc
ätsc4h
ät4sc2h2r4
ät2s1i
ät2s3l2
äts1p2
ät2s1t4
ät4s3t2e
ät4sti
ät2t2r4
ä4t1t
ä1tu
ät2zw2
ä4t1z
äu2b2r4
ä2u1b
ä2u1c
äu1de3
ä2u1d
äu3el
ä2u1e
ä2uf
äu1f2e
1ä2u1g
äu2g3l
2äu1l
2äum
äu2ma
äu2m2s1
ä2un1
äu3n2e
äu1nu
2äur
2ä3us.
ä2us
äu4s2chä
äu1sc
äusc4h
äu4s4c2h2m
äu3se
ä3u4s3g2
ä3u2s1k2
ä3u2s3n4
äu2sp2
äus2s1c
äu4s1s
1ä2uß
äu2t2r4
ä2u1t
4ä1v
1äx
ä1z
â1t
á1n
ba2b1l2
ba1b
2b1a4b1s
bach7t4e
ba4c4h1t
bac4h
bac2k1s4
ba2c4k
b1a2d2r4
ba1d
2b1af
bah2nu
ba2hn
b2ai1s2
ba2ka
ba2k1er
b4a1ke
b2a2k1i
b2ak3l2
b2a1k2r4
ba2kra
3bal
ba1l2a
bal2lä
ba4l1l
bal4le1b
bal1le
bal4leh
bal6le4r1g
bal4l2i1g
bal1li
bal3t1h
ba4l1t
2b1am
ba1n2a
3b2a2n1d
ban2d2r4
b2a3n2e
b1a4n1g
ban2k1a
ba2n1k
ban4kl2
ban2k2r4
2b1a2n3l2
2b1a6n1s
ba2n3t
b1a2n1z
b1a2r3b
bar3de
ba2r1d
ba2rei
b2a1re
ba3r2en
ba4r3n
bar3z1w2
ba2r1z
3bas
ba3s2a
ba2sc
b2a2st2r4
ba1st
b2a2u3g
ba2u
bau3s2k2
ba2us
bau3sp2
ba1yo
3b2äc
bä1c4h
b2är
b2äs
4b1b
b3be
bbe4p
bb3ler
bbl2
bb2le
bb2lö
bb4r2u2c
bb2r4
bb1ru
b4b2s
bbu1
2b1c
2b3d4
3be.
3b2e1a
be3an
be3ar
be3as
3be1b
b2ebe
1bec
be1c4h
be2del
be1d
be1d2e
be1di4
be1eh
b4ee
be2e2r1k
be1e4r1l
be1e3ta
bee1t
3be1f4
be3g2
2b1eier
bei1e
be2i1f4
bei4ge.
be2i1g
bei1ge
be2i1k4
be4il2
bei3la
2b1ei1me
b2ein
be1i2n1d
be1i2n2h2
bei3sc
bei1s
be2i1s2e
bei3st
bei2t2s
be2it
3be1k
3bel
be3las
be1la
be3lec
be1le
b2e3lei
be2l1en
be2le1t
b2e3li
bel3la
be4l1l
bel3li
bel3s1z
be4l1s
be4l3t4
1be1m
1ben.
ben3ar
be2n1a
ben3d1or
be2n1d
be3n1ei
be1ne
3be4n3g
be3n2i
be4n3n
ben2se
be6n1s
ben4sp2a
bensp4
ben4sp2r2
ben1st4
ben2su
2ben4t3b2
be4n1t
b2enti
bent4r4
b1en4t1s
2b1en4t3w
ben3u2n1
ben1u
be2n3z2
b2e1o
be1r1a
ber3am
be2ran
b4e3r4ei.
be1re
be4r3e2i1w
be4r1e2r1k
b2ere4s
ber6gan.
be4r1g
berg2an
b4e3r4in.
b2e1ri
be3r3i4s1s
ber2i1s
ber3na
be4rn
b1er2n1t
be1r1o2p
ber1ö4
ber3st4a
be4r1s
ber1st
be3r1u4m
b2e1ru
3be1s
be1s2a
be2s1er
be1se
be3s1lo
be2s1l2
bes2p2o
b2esp2
bes1s4e
be4s1s
b3es6st.
bes1st
bes3s1z
be6s2t1ein
be3s2t2e
be1st
beste2i
be4s3t2ol
be4stor
be3s2ze
be2s1z
3be1t
b1e2ta1p
be3th2a
bet2h
be1ur
3b2e1w
2b1ex
1be1z
2b5f4
bf2al2
b1fa
2b1g2
bga2s1
bga4st
b1ge3
bge1s2
2b5h2
bh2u1t2
1bi
bi3ak
bi1a
b2i1b2
bibe2
bi1k2a
b2i1k
bi2ke.
b4ike
bi2ke1s
3bil
bi1l2a
bi2l1a2u
4b1illu
bi4l1l
bi2lu2
2b1i2n3b4
b2i1n2e
2b1i2n3f
bin3gl
bi4n1g
2b1i4n1t
b2i2o1
bio3d
bi3on2
bi1r2i1
b2i3se
bi1s
b1iso
bi2s2ol
b2i2sp2
bis2s1c
bi4s1s
bi2st2u
bi1st
bi2stü
b2it.
b2i1ta
b2i1te
bi2tu
bi3t1um
b2i3t2us
b2i1z2
4b1j
bjek4t2o
bje1k
bj2e4k1t
2b1k4
bl2
2bl.
bl1a3b6
b3la1d
b2la2n1c
3bl2a1t
b2la4t1t
2b3l2a1w
b2le
3bl2e2a
b3le1b
2b3le1g
2b3l2e2i1d
b3lein
3ble1m
3ble4n
b3le1s2e
ble1s
ble3s1z
b4le1t
b3le2u
2blic4h
b1li
bl2i1c
3b4li2c4k
b2l2ie
2b3l2i1g
bli4n1g4
b4li1s
b2lit
3bl2i4t1z
b2lo
b4l2oc
b3lo1s
2b1lu2n1
3bl2u1t
3blü
2b1m
4b3n2
b1ni2
b3n2i1s1
b2o4a2
bo5a3s
b1o1b3
bo2b1l2
bo2b2r4
bo1c4h2
b2oc
bo3d2
b2o1e1
bo2ei
2b1of
bo3fe
bo1i1s
b2oi
bo2l1an
b2ol
bo1la
3bon.
bo2n1d1
bon2de
b2o2ne
3bo6n1s
b1op
bo1r2a
bo4rä
bor2d1i
bo2r1d
b2or2d3r4
bo2rei
b4o1re
bo4r2i1g
bo1ri
b1o4r1t
bor2t3r4
bo2sc
bo1s
b2o4s3p2
bote3n4e
b2o1t
b4o3t2h
bot2s3t4
bo2ts
bo2xi
bo1x
bö2b3
2b1öf
b1öl
2b1p2
bpa2g4
bp2a
2b1q
b2r4
2br.
b4ra.
2b3r2a1d
b4rah
b4ra3k
bra3s2t4
br2as
3brä
brä4u
2b3re.
b1re
3br2e1a
6b5rechte
br2ec4h
bre2c4h1t
2b3re1f
2b3re1g
b3re2if
3bre1m
2b3re1p
b4rer
2b3rie1m
b1ri
br2ie
bri2er
2b3r2i1g
b4r2i1o
bro1
b3roh
2b3r2ol
b4r2on
b4r2uc
b1ru
br2u4s
bru1st3
bru2t3h
b3r2u1t
3b1rü
4b1s
b2s1a1d
b1sa
b3s2a2n1d
bs1an
b2s3ar
bsa4t2z
b3s2a1t
b3sä
b4s1är
b5sc
bs2ca
b6schan
bsc4h
b6s1che1f
b2s4cu
bs1e2b
b1se
b3s2el.
bs1e1le
bse2n1
bs1e4n1t
bs1er
b4se2r1f
bs3e4r3in
b3s2e1ri
b4se4r1s
b3se1s
b3s2i2t
b1si
b4s1l2
b2s1of
bs1op
bso2r
b2sö
b2s2p2l4
bsp2
b3s2pu
b4s1s2
b1s2t
bst1a2b
bs2t3ac
b2s1t1ak
b3stä
bs3t2ät
bst3er
b3st2e
b4s2te4rn
b2s1tip
b3sto
b4s1to3d
b3stö
b3stra
bst2r4
b2s3trä
bs3t4re2u
bst1re
b3stü
b4s2t1üb
b2s1u2n1
4b1t
b3ta
btal3
bt2a4st3r4
b1ta1s2t
b5te
b2t1h
bt4r4
b2ts2
btü1
bu2chi
b2uc
buc4h
b2u2e3
bu2f
bu3li
bu1l
bul2l2a
bu4l1l
2b3u2m1k4
bunde4s
bun1
bu2n1d
bun1de
b2u4r1g
bu3r4i
bur4t4s
bu4r1t2
bu2sa
b2us
bu4s3cha
bu1sc
busc4h
bu4s2c4hl2
bu4s4c2h2m
bu4s2c2hw
bus1er
bu1se
bu2sin
bu1si
bu2s1p2
bu6s1te4r1m
bu1st
bu3st2e
bu2s1t2r4
bu2s1u
b2ü1c
büge3l3e
bü1g
bü1ge
büg2el
2b1v
2b1w
bwel3
3by1
by3p
by1s2
2b3z2
bze2it1
bzu1
1ca
2c1a1b
ca2c4h
c2a2e3
ca3g4
ca1h
ca4l3t
c4an
c2a2p2e
ca1p
3car
ca4r3n
car1ri1
c2a2r1r
ca3s2a3
ca1s2t
ca3t4h
c2a1t
ca1y2
cä3
cäs2
2cc
c1ce
c1c4h2
c2d2
c3do
2cec
c2e1co4
ce2d2r4
ce1d
2ce1f
ce1i
2ce1k
1cen
ce4n3g
ce1n1u
1cer
ce1re3
ce1ro
c4e3s1h
ce1s
1ce1t
2ceta
ce1u
1cé
c1f
c4h
4ch.
2cha1b
ch3a2bi
2ch1ak
ch2a2n3b4
3cha2n1c
ch1a4n1g
ch3an1st
cha6n1s
2cha2n1z
1ch2ao
2ch2ar.
1ch4a3ra
3charta
cha4r1t
cha2sc
ch2as
1chato
ch2a1t
2chatu
ch1ä4r1m
ch1äs
1châ
2c2h3b2
2c2h1c
2c2h3d4
ch3e4b2en
che3be
che1b
1che1f
3ch2ef.
che4f2er
ch2e1fe
3che4f1s2
4chei
ch1e2im
4ch1e2le1m
che1le
che4ler
4ch1en4t1s
che4n1t
4c2h3en4t3w
cher3a
che3rei
che1re
6cherge1b
che4r1g2
cher1ge
cher6zie
che2r1z
ch1e4s1s
che1s
2cheta
che1t
2ch1e4x
1ché
2c2h3f4
2c2h3g2
2c2h1h2
1c2h1i1a
chi3na
chi2n
4chi2n1d
3chi3n2e1s
ch2i1ne
2ch1i2n3f
2ch1i2n1h2
ch1i6n1s
ch1i4n1t
2ch1i2n1v2
1chi1ru
ch2i2r
2ch1j
2c2h1k4
2c4hl2
ch2le
ch3lein
ch2lu
4c2h2m
2c2hn4
chn4e3r8ei.
ch1n2e
chner3ei
chne1re2
2cho1b
cho2f
ch1o2f1f
ch1oh
ch1o2r1c
2c2h3p2
c2h2r4
2ch1re
ch3re3s1
ch3r1h4
1ch4r2on
4c4h1s
ch4stal
ch1st
2c4h1t
2chuf
2chu1h
2ch1u2n1f
chu2n1
2ch1u2n1t
chus4si
ch2us
chu4s1s
2ch2ü
2c2h1v
2c2hw
1chy
2ch1z
c2i1c
ci2s
c1j
2c4k
c1k1a
ck3an
cka4r1
c1k1ä
ck1e1he
ck1ei
ck1e4n1t
4cker
cke2r1a
ck2e1re
ck1e2r3h4
ck2e4rn
ck1e2r1r
ck1e1se
cke1s
ck1i1d
ck1im
ck1in
ck3l2
ck3n2
c1k1o2
ck3r4
ck4stro
c2k1s
ck1st4
ckst2r4
ckt2e
c4k1t
ck1um3
ck1up
c4l2
cle1t2
c1le
c1lo1
1clu
c2m2
1co
co1c4h
c2oc
co2d2
co3di
co2f1f4
c2oi2
co1it
co2ke
c2o1k
co2le
c2ol
co3l2o
com4te.
c2om
co4m1t
comte2s4
con3n2e
co4n1n
co2p2e
co1r1a
co2r3d
c4o3re
co1s4
co2te
c2o1t
2cp
c1q
1c4r2
c1re2
cre4me1s
cre1m
cre1me
c1ry2
2c1s2
c2si
4c1t
c1t4e3e
cti2
c3t2i4o
ction5
ctur6
3cu
cu2p3
cus1si4
c2us
cu4s1s
1cy
c1z
3da.
d2a1a
2d1a1b
d2ab1ä
da2ben
3d2ab1l2
da2b1re
dab3r4
d2a3b4rü
2d1ac
d2ac.
dach3a
dac4h
da2cho
4d3ach1se
da4c4h1s
d1af
d1a1g
dag2i2o
da4h3l
da1ho
3d4ai2
da1in
d2a1i1s
da1l2a
2d1a2lar
da2l3b2
da3l1ö
d1a4l1t
d1am1ma
da2m1m
2d1am3mä
da1mo3
d2a2m1p
dampf8e2r1f
dam2pf
damp1fe
2d1a4m1t
d2an.
2d1a1na
dan4ce.
da2n1c
2d1a2n3d2
d1a4n1g
2dan1ge
dan4kl2
da2n1k
dan5kla
dan2k1o
dan2k2r4
2d1a6n1s
2d1an4t3w
da2n1t
2d1a4n1w
d2anz.
da2n1z
4danzi
2d1a1p
d2a1ph
4da2p1p
da2r3a
2d1a2r1b2
3d2a4r1l
dar2ma
da4r1m
dar2m1i
d2a2ro
d3a2r1r
d2a4r1s
d1a4r1t
d2a2ru
d2a2r1w
d4a3s2h
dat4e2
d2a1t
da3tei
d4ate4n
4d3a2t3l2
4d3a2t1m2
3d2a2u3e
da2u
2d1au2f
2d1a2us
4dau2s1h
2d1äh
2d1ä4m1t
2d1ä2n1d
2d1ä4n1g
2d1äp
2d1ä2r1z
dä2u
dä3us
2d1b4
db2u2c
2dc
d1c4h
dco4r
d1co
2d1d2
dda4r2m
d3d1h2
d5do
1de
de2a1d
d2e1a
de3as
de3a2t
de3b4
2d1e4b2en
3dec
de1c4h
d4e1e2
2d1e2f1f
de1f
de1g2
de3gl
de1he2
de3ho
2d1e2hr
d1ei
d2e2ic
3d2e1im
dei2n2d
dei6n2s
de2l1a4g
de1la
de4l3a2u1g
dela2u
del1än
d2e1lä
de2l1ec
de1le
de4l1e2i4g
d2elei
de3l1ein
2d1ele1k
2d1e2le1m
2d1el2f3m2
de2l1f
del1le2
de4l1l
del4lei
del2lö
de2l1o1b
d2e1lo
de2lop
de3l1or
de2lö
del4s1an
de4l1s
del1sa
del2s5e
del2so
del2s1p2
de4l3t
dem2ar
de1m
d2e1ma
2d1e2m1p
d2en.
de4n3e2n1d
d2e3nen
de1ne
4d1e2ne4r1g
de4n3g
d2e2n1h2
de2ni
den4k3li
de2n1k
denkl2
4d1en4se1m
de6n1s
den1se
den4sen1
den6s5ta2u
den1st
den3t1h
de4n1t
2d1en4t3w
de1n1u
de1on
d2eo
d1e4pi4so
de1p
de3pi1s
d4er.
der1a2b
der1a
de2r2a1p
der2bl2
de2r1b
2d1er2d1b4
de2r1d
de2r1e2b
de1re
de4r1e2c4k
der3e1di
dere1d
de4r3ei1s
derer3
de3r4e2r1b
de3r4e2r1f
de4r3e1ro
de2r1e4r4t
4d3erhöh
de2r3h4
de2r1h2ö
3d4e3r2ie
d2e1ri
de2r1i2n4f
4d1erklä
de2r1k
derkl2
de4r3m2
4derne2u
de4rn
der3ne
de1ro
de2r1o2p
der1ö4
4d3er3s2a1t
der1s2a
de4r1s
der3ta2u
de4r1t
der6t5e2n6d
derte2n1
dert4ra
dert2r4
d2e3ru
de4ru1h
de4r1u4m
de2s1a
de1s
de3sac
desa4g
de4sam
des3an
des1än
de4s2eh
de1se
des1en1
de4s1e1t
des1in
de1si
des1o
de2sor
d2e2s1p2
de4s5s2
des2t5a4l1t
de1st
de4s2tam
de6sta2n1t
de3stan
de4ste2i
de3s2t2e
de4stit
de3sti
dest5r2a1t
dest2r4
de3st1ri
d2e3stro
de2s1u
dete4n4t
de1t
de3te
det2en
2d1e4t3w
d2e1un1
de1u4r1l
de3us
d1e2x2i1s
de1xi
2d3e4x1p
2d1f4
2d1g2
dg2a4st2r4
dga2s
dga1st
d2ge.
d1ge
dge3t2a
dge1t
dge4t1e
d3gl
2d1h2
d2hi1s
1di
di4a1b
di1a
di2a1d
di4am4
3d2ic
di1ce
di2e
di3e2d
die4n1e1b
d2i1en
di2e1ne
di3e1ni
di3ens.
die6n1s
die4s1c
die1s
die1t3
die2t2h
dige4s
d2i1g
di1ge
di1k2a
d2i1k
di4l2s3
2d1i2m1b2
di1n2a
2d1i2n1d
2d1i2n3f
2d1i2n1h2
2d1i2n1it
di1ni
4d3inner
di4n1n
din1ne
2d1i6n1s
2d1i4n1t
di2o1b
d2i1o
di3o6n5s3
dion2
di1p
di4re.
di1r2e
di2ren
di2r2i1s
di1r2i
2d1i4r1l
d2i2sp2
di1s
2d1i2s3r4
di1st2
di2ta
di4te4n3g
di1te
dite4n
di4t3e4r1l
di4t3e4r1m
di4t3e4r1s
di2t3r4
di2t1s
di2tu
d2i5v
d2i1z2
2d1j
2d1k4
4d1l2
d3le
dl2e2r1a
dl2i2f
d1li
d2l3m
d4l3s
2d3m2
4d5n2
d1ni2
d3n2i1s1
d1o1b
d2oba
2dob2e
dob4l2
d2ob2r4
do1chi
d2oc
doc4h
2d1o2f
do4l1l2
d2ol
do2mar
d2om
do1ma
d2o5n4a
d4o3ni1
do2o
2dop2e
2d1opf
d2o2p1p
d2o3r4a
2do2r1c
2do2r1d
dor2f1a
d2o2r1f
dor2fä
dor2f2l2
dor2f2r2
2d1o4r1g
do2r2ie
do1ri
d2o2r1p2
2do4r1t
dor2ta
d2os.
do1s
do4s3s
do1s2t1
do4sta
d4ot6h
d2o1t
do3un1
do2u
d1ö
dö2l1
3d2ör
dö2s1c
dö1s
2d3p2
2d1q
d2r4
3d4ra.
2d3r2a1d
2d3r2a2h1m
d3r2ai
3d4ram
d3ra2n1d
2d3ra1s2t
dr2as
2d3r4a2uc
dra2u
2d3r2ä1d
d4räh
2d3rät
2d3rä2u
4d3re.
d1re
d4rea.
dr2e1a
d4re3as
3d4re2c4k
2d3re1g
3d4reh
2d3re2ic
d4re2i1v
4dre1m
4d3ren
2d3re1p
4d3rer
4d3r4es.
dre1s
d4re3sc
2d3r1h4
d3ri
d4ri.
3d4ri1a
2d5r2ic
d4ri1d2
d4r2ie
d5rie1g
d4r2if
d4r2i1k
d4ril
d4rin.
3d4ri1sc
dr2i1s
2dri2ß1
3d4r2it
4d5ri1tu
d3ro1b
d3r2oc
2d3ro1d
d4r2oi
dr2om2
2d3r2o1t
d3ro2u
2d3ro1v
d3rö
drö2s1
d5r2u1b
d1ru
3d4r2uc
2d3ru1h
drun1ge3
drun1
dru4n1g
2d5r2u1t
d2r1ü1b
d1rü
2d1s
d2s3a1b
d1sa
d4s1a4m1t
d3sam
d2s1an
ds3as3s2i
dsa4s1s
d2s1a4u2
ds1än
4d4s3b4
d4s1che1f
d1sc
dsc4h
d4schi2n
dsc2h4r4
d2s1e2b
d1se
d2s1e1f
d3sei
d2s2e2i1g
d4sei6n1s
ds1ein
d2s1e4n1g
d2s1e4n1t
d2s1e2r1f
d2s1e2r3h4
d2s1e2r1k
ds1e2r1r
d2s1e2r1z
d3se4t
d4s1eta
d3s2h2a
d2s1h
d3sho
d2s1im
d1si
d2s2i2n3f
d3s2kan
d2s1k2
ds1ka
d3sku1l
4d2s1l2
d2s1op
dso2r
ds1o1ri
d2sö
d2s1par
dsp2
dsp2a
ds1pas
d2s2pä
ds2p2o
d2s1p4ro1
dsp2r2
ds2pu
d4s1s2
d1st4
ds1ta1b
d4s3tä1ti
dst2ät
d4s1t2e2a4
d3st2e
ds2til
ds2tip
d2s1t2i1s
d2s1to3d
ds1u2m1s
d2su2n1
ds2zen
d2s1z
2d1t
dta2d
d3t2e2a4
d2t1h
d4t3hei
d1th2e
dt3ho
dto2
dt3r4
dtran2
d2t5s2
1du
du1a2l1v
d2ua
du1ar
dub3l2
d2u1b
du2b1li
du2f
2d1u1fe
2d1u1h
d2u1i
2d1u2m1b2
2d1u2m3d2
2d1u2m1e
2d1u2m1f4
2d1u4m1g2
2d3u2m1k4
2d1u2m3l2
d2u2m1p
2d1u2m3r2
d1u2m1s
d2ums.
2d1u2m1v
2d1u2n3d
dun1
d1und2a
2d1u2n1f
dun3ke
d2u2n1k
dun2kl2
2d1u2n3r2
2d1u2n1t
d2u1o
du2r2c
2d1u4r1l
2dur1sa
du4r1s
du4s2c2hn4
d2us
du1sc
dusc4h
du4sc2h2r4
du4s2c2hw
2d1üb
3düf
3dün
2d1v2
2d1w
dw2a2
dwe1s2t3
dwe1s
dy1
dy2s1
2d1z
2e1a
e3a2b
eab3l2
ea3der
ea1d
ea1de
ead1li4
ea4d1l2
ea2d2r4
ea2g4
e2a3ga
ea4ge
e2a3gl
ea4k1t2
ea2la
e3a2l1ei
e2a1le
e4al2er.
ealti2
ea4l1t
e2a1m4e
ea1m1o
ea4m3t
ea2na
e2a1no
e3ar.
ea2ra
e3a4re1ne
e2a1re
ea3ren
e3a2r1r
e3a2r1v
e2as
ea4s5s
eat4e2
e2a1t
eater1
e3at2h
ea2t3s2
e3a4t3t4
e3au2f
ea2u
e3a2u1g
eau3st
ea2us
e1ä4
e1b
2eba
e3b2ak
2ebe1d
ebe2i
2e3bel
eb2en
eben2s3e
ebe6n1s
ebe4r1t4
2e3be1t
2ebl2
eb3ler
eb2le
eb4le2u
e3b2l2ie
eb1li
eb3lo
eb2lö
2eb2o
ebö2s
2eb2r4
eb3rei
eb1re
eb4ru
e4b2s
eb6sche
eb5sc
ebsc4h
eb1se2
eb1s1i
ebs1o
ebs1p2
ebs3p2a
eb4s3t2ät
eb1s2t
eb3stä
eb2s3t2h
eb4s3ti
eb4s3t2o1t
eb3sto
eb3st2r4
ebs1u
e3bu
eb2u2t1
2e1ca
e1ce
ech1ä
ec4h
2e3che
e4ch1ei
e6ch5erzi
eche2r1z
e1chi
e2c4h3l2
e4c2h3m
e2c2h3n4
e2cho.
e2ch1o2b
e2c2h3r4
ech3t1a
e2c4h1t
ech3t4ei
e1chu
e2ch1u1h
e2c2h1w
e1ci
eci6a
eck3se
e2c4k
ec2k1s
2ec4k1t
2e1c4l2
2e1co
eco3d2
2e4c1t
e1d
e3d2a
ed2d2r4
e2d1d2
e1d2e
ede2al
ed2e1a
e3de4c
e3d1ei
ede3n2e
eden4se
ede6n1s
eden4s3p4
ede2r
ede4r3t2
ed2i4al
e1di
edi1a
e3d2o
ed2ö
eds2ä
e2d1s
ed2s1e1s
ed1se
ed2s1o
ed2s1p2
ed2s3t2r4
ed1st4
ed2su
ed2u2s
e1du
e3dy3
4ee
e2e3a2
e2eb2l2
ee1b
ee2ce
ee1c4h
ee2cho
ee1d2e3
ee1d
ee2d3s2
4e4e1e
e1e2f1f
ee1f
e2ef4l2
ee1g2
e1ei
ee1im
eein4se
eei6n1s
ee1l2e
e1e2le1k
ee3len
e1e2m1p
ee1m
e1en
ee2n1a2
ee4n2a1g
e2e1nä
e2e2n1c
ee3ni
e2e1no
ee6n3s
e1e2pi
ee1p
ee1r1a
e1er4b1t
ee2r1b
e1e2r1d
eer3de3
ee3r2e
ee4r3e4n4g
e2eren
e2ere4s1
ee4re1t
e1e2r1k
ee1ro
ee1r1ö
eer2ö1s
ee4r1t2
e1ert2r4
e2e3r2u
e1e2r1z
ee1s2
e4e3s1h
ee2s3k2
ee3ta
ee1t
ee4t4a1t
ee2t2h
ee1u2
eewa4r
ee1w
e2ew2a
e1e2x
e1f
2ef.
2e1fa
e2f1a1d
ef1a1na
ef2ar
e2f3a1t
e2f1äu
e1fä
2e1fe
e3fe.
e2f1e2b
ef1e1m
e2f1e4n1t
ef2er
2eff.
e2f1f
1ef1fi
ef2f3l2
2e1fi
e2f1i2d
e2f1i6n1s
e3fi2s
1efku
e4f1k4
2ef2l2
e3f4lu
2e3f2o
e3fra
ef2r2
ef3r2e1a
ef1re
ef3r2ol
ef3r2om
ef4rü
e4f1s2
ef3s1o2
ef3sp2
ef2t1an
e4f1t
2e1fu
e2f1um
2e1fü
e1g
e2gd4
e3ge
ege4n3a4
e3gen
ege2r1a
ege4s3to
ege1st2
ege1s
ege4st2r4
ege1u
eg1l2a
e2g1lo
e2g1n
eg3ni
eg4sal
e4gs
eg2sa
eg4s3e4r1
eg2s1e2
eg4sto
eg1st
eg2t1h
e2g1t
2e1gu
2e1ha
eh1ac4h
e3h2al
eh2a2us
eha2u
2e1hä
e1he
eh2ec
e2h1e2f1f
ehe1f
eh2el
ehe4n2t3
1e2he1p
e3her
ehe1r1a
e1hi
eh1i4n1t
ehi2n
ehi1s4
eh1lam
e4hl
eh1lä
eh1le2
ehl3ein
eh4le4n1t
eh5l2er
eh2lin
eh1li
eh3lo
ehl2se
eh4l1s
2e2h1m
eh3mu
e1ho
e3h2ol
eh2r1a2
e2hr
ehr1ä
eh2r1ec
eh1re
eh2rei
ehr3e4r1l
ehr6er1l2e
eh3re3s1
eh3ri
eh1ro2
eh2r1o1b
eh2r1of
e4h2s2
eh3se
eh3s1h
eh3si
eh3so
eh3sp2
eh3sta
eh1st
e1hu
e2h1u2n1t
ehu2n1
e1h2ü
eh3üb
e2h1w
e1hy
2ei3a2
4e2i1b
ei2bar
ei2bl2
eib2u2t
ei4b3ute
ei2cho
e2ic
eic4h
e2i1d
ei2d1a
ei3d2an
ei3de
ei4d3e2r1r
2ei4d5n2
ei3dra
e2i2d2r4
ei1e
4e2i1en
eien1ge4
ei3e4n3g
eie4s
1eif3r2
e2if
ei3g2a
e2i1g
4eige1no
ei1ge
ei3gen
eig2er
2eige1w
ei3gl
1ei2g3n
2eig1ru
eig2r4
2ei2g1t
2ei1gu
eik2ar
e2i1k
ei1ka
ei3k1a2u
ei3k4la
ei2k3l2
e4il
2eil.
ei2lar
ei1la
ei2l1a2u
2e4i2l1b
ei4l3d
ei4l1ein
ei1le
eilen1
ei2l3f4
eil3i6n1s
ei1li
2ei4ln
1eilzu
ei2l1z
ei2m1a4g
ei1ma
eim3a4l1l
ei2mor
e2i1mo
e1i2m1p
eim2p2l4
ei2n1a
ei4na4s
ei2nä
e2in3d2r4
ei2n1d
2ein1du
ei4n1e4n1g
e2i1ne
ei3nen
ei2n1e2u
2ein1f2o
ei2n3f
e1in4fo.
e1in4fo1s
ei4n3g2
e1in4ha1b
ei2n1h2
e1i2nit
ei1ni
ei2n3k
e3in6ka4rn
ein1ka
3ein3k2om
ein1ko
e2i2n1o2
3ein3s2a1t
ei6n1s
ein1sa
ein6stal
e1insta
ein1st
ein4s2z
e4in3ver1
ei2n1v2
ein1ve2
e2i3o2
ei1p
eip2f2
2eir
ei3r2e
e1i2r1r
e2is.
ei1s
ei2sa4
ei6schi2n
ei1sc
eisc4h
ei6sch3w2u
eis2c2hw
ei4s3e2r1w
e2i1se
eis2p2e
e2isp2
ei2st2r4
ei1st
ei2sum
e2it
ei2ta1b
ei1ta
ei2tan
ei2tar
2e4i1tä
ei3te
ei2t1h
ei2tro
ei1t2r4
ei4t1t4
ei2t3um
ei1tu
2e2i3u2
2e1j
e1k
e1k2a
1e2k3d2
e3ke.
e3ken
e3ke1s
e3key
e3k2l2
ek4n2
e1k2o
ek4r4
2e4k1t
ekt4a2n1t
ekt1an
ekt3e2r1f
ekt3e4r3g2
ek4t3e2r1z
ekt2o
ek2u
e3k2w
e1la
e4l3a4ben
el1a1b
el3a1bi
el2a4b1t
e2l1af
ela2h
e2l1ak
e2l1a2m
e2l1a6n1s
el1a2n1z
2e3l2ao
e2l1a1p
e2l1a2r
el3a1ri
e2l1a1si
e2l1a2s1p2
el2a1st
2e1lä
3elbi1s
e2l1b
el1bi
el2da
e4l1d
eld5er1st
el1de
elde4r1s
el4d3e2r1w
el2d3s2
2e3le.
e1le
2e3leh
2elei
e6l5eier.
elei1e
e2l1ein
e3l2e2i1ne
e4lei4n3g2
e2l1el
1e2le1m
e3lem.
e4l1e2m1p
2e3l2en.
e4len1se
ele6n1s
e2l1e4n1t
e3le1p
el1e2r1d
el1e2r1f
e4ler4fa
e2l1e4r1g
el1e2r1k2
el1e4r1l
e4ler4l2a
e4l3er1nä
ele4rn
e2l1e2r1r
2ele1s2
el1e4s3s
e4l1e4ta
ele1t
e3le2u
2e3l2e1v
ele2x
1elf.
e2l1f
el3fe
elf4l2
1el2f3m2
1el4f1t
elgi5er.
e2l1g
elgi5e4r1s
2e1li
e2l1i1d
e3l2ie
el2i2ne
el1i1ta
el3k2l2
e2l1k
el2k2s2
el3la2n
e4l1l
ell2a2u
el2le1b
el1le
ell3ebe
el4l3ein
ell3ei1s
el3ler
el2l2i1c
el1li
el3l2in
ell3sp2
el4l1s
el1m2a
e2l1m
2e4ln
el5na
2e1lo
e2lof
e2l2ol
elon2
el1op2e
e2l1or
elo2ri
el2ö2f
e1lö
el2s1um
e4l1s
el1t2ak
e4l1t
elte2k
el4t3e4n3g
el3t2en
el4te4n1t
3elte4rn
el3te2s
elto2
el2t3r4
el3t1ri
el4t1s2
elt3s1k2
2e1lu
e2l1um
e2l1ur
e2l3u1se
el2us
e1lü
e2lya
e1ly
2e2l1z
elz2e
el2zw2a
elz1w2
e1m
2e1ma
e2m1a1d
ema2k
e2m3a2n1f
e2m1a6n1s
3e2ma2n1z
em2d3a2
e2m1d
e3m2en
e1me
emen4t3h
eme4n1t
e6ments2p2
emen4t1s
e2m1e2r1w
1e2me3ti
eme1t
e2m1im
e1mi
emi5na
em1i4n1t
emi3ti
e3mit1
2e2m1m
em2ma1p
em1ma
em2m1a3u
em2m1ei
em1me
e2mop
e1mo
3em2pf
e2m1p
em3p2f2l2
em2p3le
em1p2l4
em2sa
e2m1s
em2sp2r2
emsp2
e4m3t2
1emu1l
e1mu
2e1mü
e2n1a
4e3na.
2e3n2a2c
e3na1d
e4n1af
4e3n2ah
e4n1ak
en2a3l2i
4enam
e3n4a1m4e
e4n1a2n1d2
e4n3a4n1g
en3a1re
en1ar
en2a1sc
e3nas
4e3n2a1t
e4n3a4t1t
e3n2a2u1e
en1a2u
e2n1är
e1nä
e2n1äu
en2ce.
e2n1c
en3d2ac
e2n1d
en2dal
en4d3e4s5s2
ende2s
en1de
en2d4o4r1t
end1or
end3r2om2
end2r4
end3si
en2d1s
end3s2p2
end3s1z
end2um
en1du
2e3ne.
e1ne
ene4b2en
ene1b
e2n1ec
e2ne2f1f
en1e1f
e4nein
e2n1ei
e2n1e2l
e2n1e4le
2ene1m
2e3nen
e4n1e4n1t
e5n4ent2r4
4e3n2er.
e2n1e2r1d
e2n1e2r1f
1e2ne4r1g
e4n1e2r3h4
e4n1e2r1k
e2n1e4r1l
e4n3er1mo
ene4r1m
4ene4rn
e2n1e2r1r
e2n1e4r1s
e2n1e4r1t
e2n3e1ru
e2n1e2r1w
e4n1e2r1z
2e3n2e1s
e4n3e4s1s
e2n3f
en1f2a
en2f2u
1eng1a1d
e4n1g
3enga1g
enge3r1a
en3ger
en1ge
en3g2i
en2gl
en3g1lo
1en2g1p2
en4g2s
eng3s1c
eng3s1e2
e3ni.
e1ni
e3n2ic
e2n1i1d
e3n4ie
eni3er.
eni5ers.
enie4r1s
e2n1i4m
e2n1in1
e3n2i1o
2e3n2i1s
e3nit
2en2i3v
en3k2ü
e2n1k
e2n1o2b
e1no
en2o3b4le
e3nob1l2
e2n1of
en1oh
e3n2ol
eno2ma
en2om
en1on
e2n1op
e2n1o2r
eno2s
eno1s2t3
e3n2o1t
e3n2o2w
2e1nö
e2n1ö2d
e4n3r2
en3sac
e6n1s
en1sa
en2s1a4u
en5sch4e
en1sc
ensc4h
en2s1e1b
en1se
1ense1m
ensen1
ens3e4n1g
en3s1ka
en2s1k2
en3s2p2o
ensp4
ens2t5a4l1t
en1st
en4s3t2ät
en6s5t2e1s2t
en3st2e
enste2s
2ensto
en6s5tr2ie
enst2r4
enst1ri
e4n1t
en3t4a1g
1en2t3d4
en2te2b
en4te2r1b
en3ter
en3te2s
1ent1fa
en2t1f4
3entga
en2t1g2
en2thi
ent1h
3entla
en2t3l2
1en2t5n4
en4t3r2ol
ent2r4
3entsp2r2
en4t1s
ents2p2
2entü
1en4t3w
4ent1we1t
1en4t3z2
en1u
2e1n2u1t
e1nü
4enwü
e4n1w
e1ny
en4z3e2r1f
e2n1z
en4z3e4r1g
en4z3e2r1k2
enz3e4r1t
e1ñ
2eo
e1o2b1
e1of
eo2fe
e1oh
e4ol
e1on.
e1o2n1d
e1o2n3f2
e1o2n1h2
e1o2n3l2
e1o2n3r2
e1o6n1s
e1op2e
e1opf
e1o2p4t4
e1or
e3or.
e3o2r1b
e3o4r1s2
e3o2r1w
eo1s2
e3os.
eo3u1l
eo2u
e1o1v
e1ö2
e1p
e3p2a
epa2g4
e3p2f4
1e4piso
e3pi1s
ep3le
e1p2l4
1e2p2o1c
e1p2o
ep2p2a
e2p1p
ep4p3l4
ep2p2r2
ept2a
e2p1t
ep2tal
e3pu
ep2u2s
e1q
er1a
e3ra.
e3r4ad.
e1r2a1d
er3a2d3m2
era1f4a
er2af
era1f2r2
era2g
e1r2ai
e2r3a2ic
e2rak
e1r2al
er3a4l1l
era2n3d
e3r2a1ne
e2r3a2n1f
e2r1a2n1h2
e2r3a2n1m4
e1r2a1p
e2r3a2pf
e2r1ar
e3r2a1ri
e1r2as
e2r3a4si
e2ra2ß
e2rat2h
e1r2a1t
e3r4a3ti
e2r3a2t1m2
e1r2a2u1b
era2u
er3a2u1e
e2rau2f
e2r3a2u1g
e1r2a1w
e1r2a1z
e1rä
e2r1äh
e2r1äm
erä4s
erb2e
e2r1b
er3b2r4
erb4sp2
er4b2s
e2r1c
er3c4h3l2
erc4h
er3da
e2r1d
1er2d1b4
er3de
2er3dec
erd3e2r1w
4e3re.
e1re
er1e1b
e3r2ec4h
e4r3e4c4h1s
er1e2c4k
e2re4dit
ere1d
ere1di
e4r1e2f1f
ere1f
e2r1e2h
4e3r2ei.
e2r1e2i1g
e2r1ein
e4r3e2is.
erei1s
ere2l
er1e1l2e
2e3re1m
2eren
e3r4en.
e3r2e2n1a
e4r1en1se
ere6n1s
e4r3en2t1f4
ere4n1t
e4r1en2t5n4
e3r2e2n1z
eren8z7e2n1d
2e3r4er.
e2r3e2r1f
e2r1e2r3h4
e4r1e4r1l
2ere4r2n
e3r2e1ro
er1e2r1r
er1e4r1s
e2r1e4r1t
er1e2r1w
2ere1s
e2r1e4s1s
e2r1e2ß1
er3e4ti
ere1t
e2r1eu1l
ere2u
e2r3e4vi1d
er2e1v
er1f2e
e2r1f
erf4r2
4erfü2r
er1fü
3erge4b3n2
e4r1g
er1ge
erge1b
4erg2e1hä
erg3e4l1s
erg2el
1erg2ol
e2r3h4
1erha1b
e2rha
2erh2ü
2e1ri
e2ri3a1t
eri1a
e3r2i1b
4e3r2ic
4e3r2ie
er2i3e4n3
eri5ers.
erie4r1s
e3r2i3k4
4e3rin.
er1i2n3b4
e2r1i1ni
e2r1i2n1k
e2r1i4n1t
e3r2i1o
er1i1ta
er2it
2erk.
e2r1k
1erklä
erkl2
2erk1li
2erk1re
erk2r4
er4k3t
3erle4b3n2
e4r1l
er1l2e
erle1b
erm2e6n4s
e4r1m
er1me
er3m3e4r1s
ern1o1s
e4rn
er1no
e1ro.
er3o3a2
er1o2b
e2r1of
e1r2o1g
e1r1oh
e1r2o1k
e1r2ol
e1r2om
e3r2on
e4r3o1ny
er1o2p
e4r1o2r
e1ro1s
e1ro2u
e1r2o1w
e1r2o1z
er1ö
erö2d
2er1ök
e2r3p4
er3rä
e2r1r
erri3er
er1ri
err2ie
2er3r2ü
er1s2a
e4r1s
er3se
er3s2i
er3s1k2
er3s1mo
er2s1m2
er3s3n4
er3s2p4
er3s2t2e
er1st
er3s1z
er1t2ak
e4r1t
er6t3erei
erte1re
er4t3e2r1f
er4t1e4r1s
er2t2ho
ert1h
4erti
ert3i6n1s
ert1s2e
er4ts
2e1ru
eru4f4s1
e3ruf
e2r1u4m
e2r1u2n1d
erun1
er1u6n1s2
e4r3u1z
e2r1ü4b
e1rü
3erwe2c4k
e2r1w
6erwei1s
erwe2i
e1s
e2s3a1b
e1sa
es1a1d
es2an
es3a2n1t
e3s2as
e4s3ato
e3s2a1t
e1s2äu
2e4s3b4
e3sc
es2ca
es3ca1p
e2s2ce
esc4h2
e4s1co
e4s3cu
es1ebe
e1se
ese1b
e3se1g
es3e2hr
e3s2eh
e2s1ein
ese4ler
ese1le
es3eva
es2e1v
2e6s1f4
4e2s1h
es2har
esh2a
es2hu
esi1er
e1si
e3s2ie
e2s1il
es1i1n1i1
e2s3i4n1t
es2k2a1t
e2s1k2
es1ka
e4s3ke
e4sky
es2l2o1g
e2s1l2
es1lo
2e2s1m2
es2o4r1t
es2ö
2esp2
e3s2pe1k
esp2e
e3s2por
es1p2o
e3s4pra
esp2r2
es2pu
2e2s3r4
es2s1a4u
e4s1s
es1sa
4esse1m
es1se
es6senso
es3s2e6n1s
ess4e3re
ess3e4r3g
es4s2it
es1si
2esso
es2sof
es2s1p2a
essp2
es2s1pu
es4s1t2e
es1st
e2s2t1a4b4b
e1st
esta1b
e2s1t1ak
e3stan
e4s2t1a2r1b
1e2stas
es2ta2u
e3s2t2e
e4st3e4n3g
e4st3e2r3h4
e4st3e4s3s2
e1ste2s
e5st3e1v
e3sti
e4s1tip
estmo6de
e4s2t1m2
est1mo
est3m2o1d
e2s1to3d
est3o1ri
2estro
est2r4
es3t4rop
es2t2u
e3s2tü
es2ty
e2s1um
es1ur
e3s2y
eße3r2e
e1ße
e1t
e3ta.
eta1b4
e2t1am
1eta1p
e3t4a3ri1
et4a1t
e2t1äh
e3te
e4t1ein
et2en
ete2n3d2
et2e2o
ete2r4h2ö
ete2r3h4
eter4t2r4
ete4r1t
et2h
et3hal
e2th2a
e2t3h2ü
e3ti
eti2m
eti2ta
2e3to
e1to2b
e4t1of
2et2r4
e4t3r4aum
etra2u
e2t3rec
et1re
e2tre1s
ets2c2h3w
e2ts
et1sc
etsc4h
et1s2p2
et1s1u
ett1a
e4t1t
et2t1a1b
et4ta2n1z
et2t3a2u
et2tä
et2tei
ette4n1
et2t1h
et2t3r4
et4tro
ett3s1z
et2ts
et2t1um
et2tur
et2tü
etwa4r
e4t3w
etw2a2
2e4t1z
et2z1ä2
et4z3e4n1t
etze4n1
et3ze4s
et2zw2
e2u1a2
eue6re2if
e2u1e
eue1re
eu2e5sc
eue4s
eu2g1a
e2u1g
eu4ge4n1t
eu1ge
eu3gen
eu3g2er
eu4gl2a
eu2g1l
eu4g1s2
euil4
e2ui
eu1in
1euk
eu2kä
e1um
e3um.
e3u2m1b2
e3u2m3l2
e3u2m2s
eums1p2
eum3st
2eun1
eu3n2e
eu4n1ei
e3u4n2g
eu2n2i1o
eu1ni
eun3k1a2
e2u2n1k
e2u1o2
eu1p
e2u1r2e
3e4u3ro
eu3sp2
e2us
eu1st4
2e2u1t
eut2h
eut6s2c2hn4
eu2ts
eut1sc
eutsc4h
2e2u1x
eu2zw2
e2u1z
e3ü
2e1v
e2ve3la
e1ve2
e2ve4n1t
4ever1
ev2e5r2i
e3vo
e2v2s
e1w
2ew2a
e3w2ä
e1wä2s
2ewe
e2we.
e3wi4r
ewi2s
e3wit
e2w2s
2ex.
ex3a1t
ex1a
1e2x1e1m
e1xe
ex1er
e1xi
e2x1in
1ex2i1s
e2x3l2
3e4x1p
2ext.
e4x1t
ex2tin
ex2t1u
2e1xu
2e3xy
ey4n
ey1s2
e1z
e3z2a2
e2z1e4n3n
e3zi
ezi2s
ez2w2
é1b
é1c
é1g
égi2
é1h
é1l
élu2
é1o
é1p
é1r
é1s
é1t2
é1u2
é1v
é1z2
è1c
è1m
è1n
è1r
ê1p
1fa
fa1b4
fa2ben
f3a2b5f4
f2ab3r4
fa4b5s
3fac
fa4che1b
fa1che
fac4h
fa4che2r5f
fa2ch1i
fa2cho
f1ader
fa1d
fa1de
fa2d2r4
f4ah
fa2i1b4
f4a2ke
f2al
fa3l2a
fal2k2l2
fa2l1k
fal6l2e2n1k
fa4l1l
fal1le
fal6l5e2r1k2
fal2li
fal6scha
fa4l1s
fal1sc
falsc4h
fal6s4c2h2m
fal4t2s
fa4l1t
2f1a2n3b4
2f1a2n1f
fan2g2r4
fa4n1g
2f1a2n1k
2f1a2n3l2
f1a2n3p4
2f1a2n3r2
fa6n3s
2f1a4n1w
f1a2n3z
2f1a1p
f2ar
f2ar2b2r4
f1a2r1b
2f3a2r1c
3f2a1ri
3f4a4r1t
2f3a2r1z
fa3s4a
f4a3s1h
f3a1t
fa2to3
2f1auf
fa2u
f3a2u1g
fa2u2s
f1au4s3b4
3f4a1v
fa2x1a
fa1x
1fä
fä1c
fäh2r1u
fä2hr
2f1ä4r1m
fä2ßer
fä1ß
fä1ße
f1äu
2f1b2
2f1c
2f3d4
fdi2e2
f1di
1fe
featu4
f2e1a
fe2a1t
f2ec4h
2f1e2c4k
fe2d2r4
fe1d
fe2ei
f4ee
fe1e1m
f2ef4l2
fe1f
feh4lei
feh1le2
fe4hl
f4ei1e
2f1ei4n3g2
4f1ei2n1h2
fe1i1ni
2f1ei4n1w
f1ei3s
fek2ta
fe1k
f2e4k1t
fe2l1a
fel2d3r4
fe4l1d
2f1e2le1k
fe1le
fe2l1er
f2e2le1s2
f2e2l1o
fel4soh
fe4l1s
fe4l3t
f2em.
fe1m
f2e2m4m
2fe2m1p
fe2nä
fe4n3g
fe2no
fen3sa
fe6n1s
fen1s2t
f1e4n1t
f2er.
fe1r1a
fer2an
fe4ra4n1g
fe4r3a2n1z
fe2ra2u
fer3de3
fe2r1d
f2e1re
fer2er
fe2r3e2r1z
f1er1fa
fe2r1f
f2erl.
fe4r1l
4ferne2u
fe4rn
fer3ne
fe1ro
f4er3p4a
fe2r3p4
f2ers.
fe4r1s
f2e4r1t
f1e2r1w
fe1s2t
fe1s
fe2st1a
fe4st3e2i
fe3s2t2e
fe2st2r4
2f1eta
fe1t
fe4t2a1g
3fe3te
fet2t3a
fe4t1t
feue1r3e
fe2u1e
feu4ru
3fe1w
f1ex
2f3e4x1p
3fe1z
1fé
2f1f
ff2a1b4
f1fa
ff3ar
ff4a2r1b
ff1a2u
f1f2e
ff4e2e
f2f3e1f
ff3ei
ffe1in
ffe2m
f2f3e1mi
f2f2e4t1z
ffe1t
f2f1ex
2f2f1f4
ff3l2
ff4la
f1f4lä
ff4lo
f3flu
f3f4lü
f3f4rä
ff2r2
ff3ro
ff3rö
ff2sa
f4f1s
ff3sho
ff2s1h
ff2sp2
4f3g2
fge3s
f1ge
2f1h2
1fi
3fi.
fi3a1t
fi1a
fi1e2r2f
fi2k1in
f2i1k
fi3k3l2
fi1k1o2
fi2ko1b
fi2k2r4
fi2l1an
fi1la
fil4auf
fil1a2u
fi4l3d
fi2le1s
fi1le
fi2l1g4
fi3li
fi4lin
fi2l2ip
f2i1na
fi3ni
2f1i4n1t
f2i2o
fi3ol
fi2r
fi3r2a
3fi1s
fi1s4a
fisch3o
fi1sc
fisc4h
fi3so
f2is2p2
fi1t1o2
fi2tor
fi3tu
3f2i1z
2f1j
4f1k4
f2l2
2fl.
f3la1d
f3la1p
1flä
3f4lä1c
2f5lä1d
f3län
2f3läu
2f3le1b
f1le
f4l4e2e
2f3lein
f3ler
f4lé
f3li.
f1li
3f6lim
fl2i4ne
2f5lon
f1lo
1f4lop
1f4l2o1t
fl2o2w
f3lö
f4l2uc
1f4l2u1g
flu4ger
flu1ge
f4lü
2f3m2
fma2d
f1ma
2f3n2
f3n2i2s
f1ni
1fo
fob2l2
fo1b
2f1of
fo1l2i3
f2ol
f2o2na
f2o1n2e
fo2nu
2f1op
4f3o4r1g
f4o3rin1
fo1ri
3f2o4r1m
for4m3a4g
for1ma
forni7er.
fo4rn
for3ni
forn4ie
for4sta
f2o4r1s2
for1st
for4sti
for4tei
fo4r1t
for2t1h
for2t3r4
for3tu
2f1o2x
1fö
2f1öf
2f1ök
2f1öl
4f1p2
2f1q
f2r2
f4rac
frach6t3r4
fra4c4h1t
frac4h
f5r2a1d
fra4m
f3ra2n1d
f5r2a1p
1f4rän
2f3re.
f1re
f3rec
f3re1d
2f3re1g
fre2i1k2
fr1ein4
f3re1p
f4re2u
2f3r2ic
f1ri
fri3d2
fr2i2e
2f3r2i1g
1fr2i1s
f4ri1sc
fri6s2ter
fri1st
fri3st2e
f3r2oc
1f4r2on
fr2o2na
fro2sc
fro1s
f3r2o1t
f3ru
f3rü
4f1s
fs2a2m1m
f1sa
f3sam
f2s1an
f2s3ar
f2s1as
f2sauf
fs1a4u
f2sa2us
f2sa2u1t
f3sc
f4s1ce
f4schan
fsc4h
f4s1che1f
f2s1e2b
f1se
f4s3e2hr
f3s2eh
f2s1e1m
f2s1e4n1t
f2s1er
f3se4t
f4s1eta
fsi2d
f1si
f3s2kie
f2s1k2
f2s1o2
f3span
fsp2
fsp2a
f2s1pas
f2s1ph
f3s2p2l4
f3s2por
fs1p2o
fs1p2r2
f2sp1re
fs2p1ri
f2s1p4ro1
f1s2p1ru
f4s3s2
f2stas
f1st
f4s3tä1ti
fst2ät
f4s1t2ec4h
f3st2e
f4ste2m1p
fste1m
f2s1tip
f2s1t2i1s
fst4r4
f4s3tre1s
fst1re
f4s3tü1te
fst2üt
f2s1ty
f2s1u2n1
f2sü
f3s2y
4f1t
f2ta.
f2ta1b
ft1a2be
ft1af
f2t1al
ft1an
ft1ar
f3t2a1t
f2t1e2h
ft1e2i1g
ft1ei1s
f2t1e4n1t
f2t1e4ti
fte1t
f2t1h
f4t3hei
f1th2e
ft3ho
ft1op
f2t3ro
ft2r4
f2t3rö
f3t4ru
f2ts1
ft2sa4
ft4sam
ft1s2c
ft4sch2e
ftsc4h
ft2se4
ft4s3eh
fts3el
ft2si
ft2stä
ft1st4
ft4ster
ft3st2e
ft4ste2s
fts2ti
f2tum
ft1u4r1l
ftw2a4
f4t3w
f4t3z2
1fu
3f2u1g
3f2u1h
f1um
2f1u2n1f
fun1
2f1u2ni
fun2kl2
f2u2n1k
fun2ko
fun2k3r4
2f1u2n1m4
2f1u2n1t
f2ur
fu4re.
fu1re
fus2sa
f2us
fu4s1s
fus6se2n1k
fus1se
fus2so
fus2s1p2
fus4s1t
fu2ß1er
f2uß
fu1ße
3f2u1t
1fü
2f1üb
fü2r
2f1v
2f1w
1fy
2f1z
fz2a2
fzeite4n6
fze2it
fzei3te
fzei8t5e2n1d
fz2ö
fzu3
fzu4ga
fz2u1g
3ga.
2g1a2b5f4
ga1b
ga2b5l2
gab4r4
2g1a2b3z2
ga1c4h
2ga4d1l2
ga1d
2ga2d2r4
g2a1f3l2
ga1k
ga2ka
ga1l2a
g4a1mo
2g1a4m1t
2g1a2n3b4
ga2n3d
g1an2g1a
ga4n1g
4g3ange1b
gan1ge
gan2g2r4
2g1a2n1h2
2g3anku
ga2n1k
2g1a2n3l2
g3anla
3g2a1no
2g1a4n1w
ga1ny
2g1a2r1b
2g1a2r1c
3ga2r1d
2g1a4r1m
g2a3r2o
g1art2i
ga4r1t
g2a3ru
2g1a2r1z
ga2s
ga3sc
ga4s3ei
g4a3s2e
ga4s3e2m
ga3s1p2
ga4sp2e
ga4sp2r2
ga4s5s
gas3tan
g2asta
ga1st
ga4s2t3el
ga3st2e
g2a3st2r4
ga4stra
ga4s2t3re
gas1t2u
gat2a
g2a1t
2g3a2t1m2
g4at4r4
g4a2u1c
ga2u
2g1auf
g2auk
g1a2us
2g1a2u1t
2g1äp
2g1ä2r1z
gäs2
gä4u
2g3b2
gba2u5s
gba2u
gber2
g1bi2
2g1c
2gd
g1da
g2d1a2u
g2d1er
g1de
gd1in
g1di
g1do
g1d1ö
g1d3r4
g2d3s2
g2d1t4
g1d1u
1ge
g2e3a2
g2eb2a
ge1b
gebe4am
ge3b2e1a
g2eb4r4
ge1c
ge1d4
g4e1e2
ge3ec
ge2e1s2
ge1f4
ge3g2l
ge1g
ge1im
ge2in.
gei6n2s
ge2i4n1t
gei2n2v2
g2e1ir
ge2i1s
2g1e2i1se2
gei3s1h
gei4sta
gei1st
g2el
ge4l1a2n1z
ge1la
gelb2r4
ge2l1b
gel4b3ra
gel6de4r1s
ge4l1d
gel1de
ge3le
g2e5leh
ge4l3e4r1s
ge4l1e4s3s
g2ele1s2
gell2a
ge4l1l
ge3l1or
g2e1lo
gel1s2t
ge4l1s
gel3s1z
gel3t2a
ge4l1t
ge3l1um
g2e1lu
ge3lü
g2e2l1z2
ge3mi
ge1m
ge1m2u
3gen
ge3n1a
g4e4nam
ge4n1ar
gen4a2u1g
gen1a2u
gen2d2r4
ge2n1d
gen1e1b
ge1ne
ge3n1ec
gen3e2i1d
ge2n1ei
g4en3e4rn
ge4n3g
ge4n3n
gen4sam
ge6n1s
gen1sa
gen3s1z
2g1en2t1f4
ge4n1t
gen3t1h
4g1en4t3w
ge1o2r
g2eo
ge1o2u
ge3p4
ge1r1a
ge2r1a1b
4g3e2r1e2i1g
ge1re
ge4re4n1g
g2eren
ge4re6n4s
ge4r3e4n1t
ger2er
ge2r1i2n4f
g2e1ri
ge3r4i4n1n
ge2r1i4n4t
ge4r1m4
ger3no
ge4rn
ge1ro
ge1r2ö
ger4sto
ge4r1s
ger1st
g2e3r2u
g1erw2a
ge2r1w
ge3s2c
ge1s
ges3e4l1t
ge1se
ge2s1er
ge3s2i
g2es2p2
ge1s4pi
ges1s2t
ge4s1s
ge1st2
ge3t2a
ge1t
2g1eta1p
ge3t4u
ge1u1l
2g1ex
2g1f4
4g1g
gg2a2t
g3ge
gge2ne
g3gen
g2g3l
gg4lo
g2g3n
gg4r4
2g1h
4gh.
gh2e
3g2he1t
3g2hi2e
g4h1l
3g2h2r
g2hu
g2h1w
gi3a1lo
gi1a
g2ial
g2ia2s
gie3g
gi2e1i
gi2el
gi2e1n2e1
g2i1en
gi2gu
g2i1g
gi2me.
gi1me
gi4me1s
gi2me1t
2g1i2n1d
g2i3ne
g2in2ga
gi4n1g
2g1i6n1s
2g3isel
gi1s
g2i1se
gi3t2a
gi4us
g2i3u2
2g1j
4g3k2
4gl.
gl2a
g1l1a1b
3g1la1d
g2la1de
2g1la1g
3gla2n1z
gla4s3ti
gla1st
gla4st2u
3g2l2a2u1b
gla2u
2g1lauf
g1lä1ß
3g4lät
2g1lä2uf
g2l4e
2g3le.
3gl2e1a
2g3le1b
g3lec
g3le1g
2g3leh
4g3lein
glei4t5r4
gl2e2it
g3len
4g5ler
2gle1s
g3le1s2e
g4li1a
g1li
2gl2i3b4
3g2li1d
3g2l2ie
2gl2if
g2l2i1k
4glin
g2l2i2o
2gli1s
4g3li1sc
3g2lit
g2l2i1z
3g2l2o3a2
g1lo
3g2lo1b
g3loc4h
gl2oc
gl2o3g
3g4l2o1k
g2l2om
3g2lop
3g2l2o1t
2g4l1s
2g1lu2
glu3te
gl2u1t
3glü
g2ly
2g1m2
g1n
2gn.
g2n2a
g4na.
2g3n2a2c
g4n2a1t
3g2nä
g1n2e
g3neh
gn2e2t2r4
gne1t
2gne2u
2g4n1g
g2n4ie
g1ni
g2n2if
g4nin1
2g3n2i2s1
g2no1
g3n2o1t
2g2n3p4
2g6n1s
2g2n1t
2gnu
3g2n2um.
g2nü
g2ny
2g2n1z
g2o4a2
go2a3li
2g1of
2g2o1g
2g1oh
g2o1i2
go1l2a
g2ol
2go3n2i1s
g4o3ni
2g1op2e
2g1opf
g2o1r1a
2go2r1d
2go4r1g
go2s
g4o3t2h
g2o1t
got6t5e4r3g2
go4t1t
got3ter
go1y
2g1p2
2g1q
g2r4
g1r4a2bi
gr1a1b
gra2b1l2
2g3ra4d1l2
g1r2a1d
2g3rah
2g3rak
grammen6
gr2a2m1m
gram1me
gram8m7e2n1d
2g3rä2u
2g5re.
g1re
g4re1b
2g3rec
2g3re1d2e
gre1d
g4r4e2e
2g3re2ic
2g3r1ein
g3re2it
g4re1m
2g3re4n1n
gren6z5ei
gr2e2n1z
g4rer
g3re1t
g3r2e1v
2g3r2ic
g1ri
gr2i2e
g3rie1se
grie1s
3gr2if
2g3r2i1g
2g3ri4n1g
2gr2oc
2groh
gr2on4
g4ro1s
gros6sel
gro4s1s
gros1se
gros8s2e1ri
g4roß
gro4u
2g3r2öh
g4ruf
g1ru
2g3r2ui
2g3rum
3g4rup
2g3r2u1t
2g3r2üc
g1rü
3g4rün
4gs
g2sa
gs1ac
gs1a1d
gs1af
gs1a4g
g3sah
g4s3a2k
g3sal
g4s1a4l1t
gs3a1m2a
g3sam
g4s1a2m1b2
gs3an
g2s3ar
gs1as
gs3a2u1g
gs1a4u
gs1ä
g4s1ca
g1sc
g4s1ce
gsc4h4
g4s1che1f
gs2chi
gs3c4r2
g2s1e2
g3s2e4il
g3s2el.
gs3e1li
g3s2e4ln
gsen1
gs3er
g3se4t
g2s1i
gsi2d
g3sil
gs1o2
gs1p4
g3s2pe1k
gsp2e
g3s4pi4e
g1spi
g2s3p2l4
g5s2por
gs1p2o
gs1r2a1t4
g2s3r4
g4s3s2
g3star
g1st
gs1ta2u
gs1tä
g5s2tel
g3st2e
g4ste2m1p
gste1m
gst3e4n1t
g4s1te4r1m
gst3e2r1r
g4s3t2e1s2t
gste2s
gs1t2h2e
g2st1h
g3sti
gs1t2i1s
g3sto
g4s1ton
g4s1tor
gs1t2o1t
gs1t2r4
gst4ra
gst4ri
gs2t3ro1s
g3stu2n1
gst2u
gs1tü
gs2t2üc
gs1u
g3s2y
2g1t
g3te
gti2m
gt4r4
gt4se
g2ts
1gu
gu3am
g2ua
gu1an.
gu1a2n1t
gu1as
gu4d3r4
g2u1d
g2u2e
2gu2e1d
gue1t2
2g1u2f
2g1u1h
gu1i6n1s
g2ui
gu1i4s
3g4u2m1m
2g1u2n1f
gun1
g2ung.
gu4n1g
gun1ge2
4g1unge1w
2g1ungl
g2u6n1s2
2g1u2n1t2
3gur
4g1u4r1l
gur4t3s
gu4r1t2
gu2s3a
g2us
guschi5
gu1sc
gusc4h
gus4s2aa
gu4s1s
gus1sa
gus4sam
gus4ser
gus1se
gus2sp2
gus4st
gu3sti
gu1st
g2u2ß1
g2u2t
gut1a
gu3te
gu4t3e2r3h4
gut3h
2g1üb
gür1
gü3st
2g1v
2g1w
2g3z2
3h2aa
h2ab2a
ha1b
hab2e
ha2cho
hac4h
ha2del
ha1d
ha1de
ha4din
h4a3di
h1ad3le
ha4d1l2
haf3f4l2
ha2f1f
haf2t2s1
ha4f1t
hafts3p2
h1ah
h2a1k3l2
2h2al.
hala2n4c
ha1la
ha2l1a2u
hal2ba
ha2l1b
hal4bei
hal2b3r4
2h2a1le
hal2la
ha4l1l
hal4leh
hal1le
hal6le2r1f
h1a2l1p
halt3r4
ha4l1t
h1a4m1t
h2an.
h2a2n1d
h4a4n1n
2h1a2n3r2
2ha2n1t
ha1o2s
h2ao
h1a1p
h2a2p3l4
h2a2p2r2
h4a3ra
2h1a2r1b
h2a2r1d
h1arm.
ha4r1m
har4me.
har1me
har4m2e1s
har2t1h
ha4r1t
h1art2i
h2as
2ha3sa
ha1si1
ha2ß1
ha4t1t2
h2a1t
hau3f4li
ha2u
hauf3l2
2h1au2f3m2
h1au4k1t
hau2sa
ha2us
h2au2sc
hau4sp2a
hau1sp2
hau4s3ti
hau1st
hau4sto
h2aut.
ha2u1t
2h3auto
hau2t2r4
h1ä2f1f
hä6s5ch2en
hä1sc
häsc4h
häu2s1c
hä2us
hä3u2sp2
2h3b2
hba2r3a
2h1c
2h3d4
hdan2
2h2e1a
he2a1d
he2a5t
he3be
he1b
he4b1e2i
h2e2bl2
h2e3b2r4
h2e5ch2e
hec4h
he1cho
h1e2c4h1t
he2d2g2
he1d
he3di
he2e3l
h4ee
hee2s2
he2fan
he1f
h2e1fa
he2fä
he2f1ei
h2e1fe
hef3e4r1m
hef2er
2he2f1f
he4f3i4n1g
h2e1fi
h2e2f3l2
he2f2r2
he3f1ri
h2e2fu
h2e3gu
he1g
h1ei1e
h1e2if
h1e2i1g
he2im
he1i2m3p
he2i4mu
he2i1ne2
h1ei2n3k
4he2i3o2
he1i4s1m2
hei1s
he1i4st
hei2t4s1
he2it
h1e2i1w
he2l3a2u
he1la
he2l1ec
he1le
h3e2le1k
he3len
hel3e4r1s
h2e3li
hel4l3a2u
he4l1l
hel4mei
he2l1m
hel1me
h2e3lo
he4lof
he2lö
3he2m1d
he1m
he3mi
3h2e2m1m
4h1e2m1p
h2en.
he4n3a4
he2nä
he2n1e2b
he1ne
hen3e2n1d
h2e3nen
h1e2n3e4r1g
he2ne1t
he4n1g2
2he1ni
he2no
hen3st2
he6n1s
h1en4t1s
he4n1t
2h3en4t3w
he2n3z
4h2e2o
he3on
he3op
he3ph
he1p
her3a2b
her1a
he2r2al
2he1r2a1p
he3r2as
hera2u2
he4r1e2c4k
he1re
4he2r1e2i1g
he4r3ei1s
he2re2l
he4r1e2r1w
h1er2fo
he2r1f
h1er1fü
he4r1g2
he2r1i2n4f
h2e1ri
he6r1in6nu
he3ri4n1n
he2r1i6n4s
herin8ter
he2r1i4n1t
h1erke
he2r1k
h3erla2u
he4r1l
her3l2a
2he4r1m
he3ro
he4r3o4b
h1er1ö
he4r1t2
her3t1h
her2zw2
he2r1z
h1e2ta1p
he1t
heter2
he3te
he3t2h
he3t2i
he3t4s
h2e2u
he2u3g
he3x
he1x2a
he1y2
1hè
2h3f4
hfe4l1l1
h1fe
hfel6ler
hfel1le
h3fi2s
h1fi
2h3g2
hge1t4
h1ge
2h1h2
2hi.
2hi1a
h2i2ac
hi2a4n1g
h2ian
hi1ce
h2ic
hich6ter
hic4h
hi2c4h1t
2hi3d
h2i1de
h1i4di
hi2e
hi3e6n1s
h2i1en
hi4e1r1i
hie4rin3
hif3f4r2
h2if
hi2f1f
hi2k2r4
h2i1k
hi2l3a4
hil2f2r2
hi2l1f2
hi2n
h1in1du
hi2n1d
hi3ne2l
h2i1ne
hi3n2en
h1i2n3f
h1i2n1h2
hi3n2i
hi4n3n2
h2i3no
hin2t1a
hi4n1t
2h2i1o
hi4on2
hi3or
2hip1
hi2ph2
h2i2pi
h2i2r
hi3r2a
2hi3r2e
hi3r2i
hi4rn1
hir4ner
hir3ne
hi3ro
hi4r2s
hi1s2a
hi1s
h2i4se
hi2s1p2a
h2isp2
hi3ti
2h2i3u2
h1j
2h1k4
4hl
h4lac
hla2n
hl1a2n1z
h1las
h1la2ß3
h1l2a1t
h1la2u1t
hla2u
h3lä1d
h1läs
h1lä1ß
h1läu
h2l1b4
h4l1d4
h3le1b
h1le
hl4e3e
h5l2en.
hle4n3g
hl2e4n1n
h3ler
hl2e2r1a
hl1e4r1g
h6l3er1nä
hle4rn
hle3run1
hl2e1ru
hl1e2r1w
h4l1e2r1z
h3le1s
h4le1si1
h3lex
h2l1g4
h2l2ie
h1li
h2l2if
hl1i2n1d
h2lip
h2li1s
h3li1st
h2lit
h4l1l2
h2l1m2
h2lo
h3l2oc
hl1of
hl1op
h4lor
hl4o2re
h3l2o1si
hlo1s
h1l2ö
h3l2öc
h2lö1s
hl2s1an
h4l1s
hl1sa
hl2ser
hl1se
hl3sku
hl2s1k2
hl3s1lo
hl2s1l2
hl2sto
hl1st
h4l3t2
h3luf
h3luk
h1lüf
2h1m
h2m1a1b
h1ma
h3ma1g
h3man
h3mar
h4mäc
h3mä
h4mäh
h4mäl
h4mäu
h3me.
h1me
hm4e1e
hme1in
h3m2ei1st
hmei1s
h3men
hm2e6n2s
hme2r1a
h2mo
h4m2on
h3mö
h2m3p4
hm2s1p2
h2m1s
h2mu
2hn
h2na
hn1a1d
h3nam
hn1an
h2n3d4
h1n2e
hn3e2i1g
h2n1ei
hn3ein
h2ne2l
h3ne4n1
h2ne4p2f4
hne1p
hner3ei
hne1re2
h3ne4r1l
h3n1e2r1z
h2n3ex
h2n2ic
h1ni
h2n1i1d
h2n4ie
hn1im
hn1in1
h2n2ip
h2n3k4
h2nor
h1no
hn3s2k2
h6n1s
hn1s2t
hn4t1s2
h2n1t
h1nu
h2n2uc
h2nu1l
hn1u2n1f
hnu2n1
h3nun1ge
h1n2u4n1g
ho2b1l2
ho1b
h2o2c
hoc4h3
hoc4k3t
ho2c4k
2ho1d
h2o1e4
ho2e1f
h2o4fa
h2o2f3r2
2h2oi
hol1a2u
h2ol
ho1la
4holdy3
ho4l1d
3ho1le
ho2l1ei
ho2l3g4
4ho3lo
ho4lor
3ho4l1s
h3o2ly
3ho2l1z
hol6ze1ne
ho1m2e
h2om
ho2m2e3c
ho2me1d
h2on
ho1no3
2hoo
2hop
ho1r1a
ho2r3d
h1o4r1g
ho4s1ei
ho1s
h2o3se
ho3s1l2
h2o2s1p2
ho4sta
ho1s2t
ho2st2r4
2hot.
h2o1t
h4o3t2h
ho4t5li4
ho4t3l2
2ho2t1s2
3ho1v
2h2o2w1
h1o2x
ho1y2
hô1
1h2ö
h2ö2c
h4ör
hö4s
hö1s1c
h3ö1s2t
2h3p2
h1q
2hr
hr1ac
h1r3a1d
h1r2ai
h1r2a1ne
h3r2a1t
h3rä2u
h2r1c
h2r3d
h2rec
h1re
h3r2ec4h
h3re1d
h3re1f
h4r2ei.
hrei4ba
hr4e2i1b
h3re2ic
h4r3e2i1g
h3rel
h3r2en
h3re1p
hr2e4r1g
hr2e2r1k
h6rerle1b
hr1e4r1l
hrer1l2e
h2r2e4r1m
h2r2e2r1z
h3re2s1
hre2t
h2r1eta
h3r2e1v
h2r1f2
h4r1g2
h2ri
h3r2ic
h4ri2c4k
hr2i4e
h3rie2s1l2
hrie1s
h3rin
h4r2i3n4e
h4r1i2n1h2
h4ri1st
hr2i1s
h2ro1b
h2rof
h3roh
h3r2ol
h4ro1me
hr2om
h4ro1mi
h4r2on
h2r1or
h3ro2u
h2r1r4
hr2s1ac
h4r1s
hr1sa
hr2s3an
hr2s1a4u
hr3sc4h
hr1sc
hr2s1e2n1
hr1se
hr2ser
hr4se1t
hr2s1in
hr1si
hr2s3k2
hr2s1of
hr4stec
hr1st
hr3st2e
hr2su
hr4s1w
hr2ta1b
h4r1t
hr2tan
hr2t1h
hr2t1or
hrt3ri
hrt2r4
hr2tro
hrt2sa
hr4ts
hrt2se
hrt4st2e
hrt1st4
h3ru1h
h1ru
hr1u2m1s
h2rum
h3rü
h4r1üb
h2ry
h2r1z2
4h1s
h2s1ac4h
h1sa
h2s1an
h2s1a4u
h3sc
h4schan
hsc4h
h2s1ec
h1se
hse4ler
hse1le
h2s1e4r1l
h3s2ex
h2s1i4n1g
h1si
h2s1of
h2s1par
hsp2
hsp2a
h2sper
hsp2e
h2s1ph
h1s2por
hs1p2o
h2sp4rä
hsp2r2
h2s1p4ro1
h4s1s2
hs2t3a4l1t
h1st
hst2an
h4s2t1a2r1b
h2sta2u
h2stäl
h4s1t2e2a4
h3st2e
h5s2tel
hs1t2h2e
h2st1h
h3s1t2i2e
h2stin
h2s1tor
h3stö
h3st2r4
hst3ran
hst3ri
h2st2u
h3stu2n1
h3stü
h2s1u
hs2u4n1g
hsu2n1
h3s2y
4h1t
h2t1a
h3t4akt.
h1tak
hta4k1t
h3tak2t3s2
h3t2al
h4t3a4l1t
h2ta2m
hta4n
ht3a3ne
h3t2a2n1k
h3t2as
h4t3a4s1s
h4ta1s2y
ht3a2t
h2tär
h3te.
ht1ec
h2t1e1f
h2t1eh
h3t2e1ha
h3t2e1hä
hte2he
h2te2if
h4tei2l1z
h1te4il
h2t1eim
h2t1ei1s
h4t3e2lit
ht2e1li
h2te2m1p
hte1m
h3ten
h4ten2t1f4
hte4n1t
h4t3en4t1s
ht3er1fo
hte2r1f
ht3er1fü
h2t1e2r3h4
ht5erken
hte2r1k
h4terkl2
h4t3er3r2e
hte2r1r
ht3er1sc
hte4r1s
h6t5ersp2a
hter3s2p4
ht3er1st4
h6tersta
ht6er3s2t2e
h2t1e2r1z
h2t1e1se
hte2s
h2t1e4s3s2
h3te1t
h2t1eu
h2t1ex
h2t1h
h4t3hei
h1th2e
ht2h2e3u
h4t2ho
h2t1in
ht1ni2
h2t5n4
hto2
h2t3o1ly
ht2ol
h2t1o4r1g
ht3rak
ht2r4
h2t3ra2n1d
h2t3r2a1t
ht6rau1me
h1t3r4aum
htra2u
ht4ri
h2t5rin
h2t3r2ol
h2t3ro1s
ht3rö
h2t1rö1s
h2t3ru
h2t3rü
h4ts
ht3sp1ri
htsp2
htsp2r2
ht4sta1b
ht1st4
hts2ti
ht4s3tur
htst2u
ht4s3tür
h4t1t4
htti2
h1t2u2e
h2t1u4r3s
h4t3z2
h2u2a
hu2b1a
h2u1b
hu2bei
hu2b1en
hu2b3l2
hu4b3r4
hu2bu
hu2h1a
hu1h
hu2h1i
hu4k3t4
hu2l3a
hu1l
hu2lä
hu2l3ei
hu1le
hu4l3e4n1g
hule4n
hu4le4n1t
hu2ler
hu2le2t
hu2l1in
hu1li
hu2lo
hu3ma
h1u2m1s
hu2n1
h1u1na
hu3ni1
h1up.
h1u2p1s
2hur
hu4r1g2
hu3sa
h2us
hu2so
hus4sa
hu4s1s
hus4s2i1c
hus1si
hus2sp2
hus4st
hu2ta1b
h2u1t
hu3t2h
hu2ti
hu4t2t
hut4ze4n1
hu4t1z
hut4z3er
h2ü
h4ü4b1s
h1üb
h3übu
hüh3n2e4
hü2hn
2h1v
hvi2
hvil4
2hw
h2wa4l1l
hw2a
h1wal
hwe1c
h1w4e2i1b
hwe2i
3hy1g
3hy1p
hy2pe.
hyp2e2
2hy2t2
h1z
hz2o
hz2u1g4
i1a
2ia.
i4aa
i2a1b
iab4l2
2iac
i2af
i2af4l2
i4a3g2
i2ah
i3ai
i2a1j
i2ak
i3ak.
i3a4k1t
2ial
i5al.
ia2l1a4
ia2lä
ia2l3b
ia4l3d
i3a2l1ei
i2a1le
i3ale4n1t
ialen1
i3a2l1e2r1f
i3a2l1e2r3h4
i3a4l3e4r1m
i3a2le4t
i3a4li1a
i2a1li
ia2l1k2
i3a4l1l
ial3la
ia2lor
i2a1lo
ia4l3s
ia4l3t4
ia2lu
ia2l3z2
i2am4
i3am.
i4a1mo
2ian
ia2nal
ia1na
i3a2n1d2
i2a1n2e
i3a4n1n
i2a1no
i3a2n1t
i3a2n1z
i2a1p
ia3p2f
i2a1q
i3ar.
ia2ra
2ias
i2a1sc
i4a3s1h
i2a1si
i2a1s1p2
ia4s5s
ia1st4
i3at.
i2a1t
i3a4ta
i4at2e
i3at4h
1i4at2r4
i3a2ts
i3a2u
i4a3un1
ia2u2s1
2i2a1v
2iä
i1äm
i1är.
i1ä4r1s
i1ät.
i1ä2t1a2
i1ä2t3s4
2i1b
i2b1auf
iba2u
ib2b1li
i4b1b
ibbl2
ib1ei
i2be2i1g
i2bei1s
ibe4n
ibe2n3a
ib2i2k
i1bi
i3bla
ibl2
i3b2le
ib2o
i2bö
i4brä
ib2r4
ib3ren
ib1re
ib4st2e
i4b1s
ib1s2t
i2b2u2n1k
ibun1
i2b1u2n1t
ibu1s1c
ib2us
2ic
i2c1c
ich1a
ic4h
ich1ä
i1che
i4ch1ei
i1chi
i2chi2n
i2c4h3l2
i3ch2lo
i4c2h3m
i1cho
i2c2h3r4
ich2t3r4
i2c4h1t
i1chu
i2c2h1w
i1ci
ick2e
i2c4k
ic2k1s2
i1c4l2
i1d
i2d2a1b4
i3dam
id2an
i2d1a2u
1i2d4e1e2
i1de
i2d1ei
id2e1l2ä
ide3s1o
ide1s
id2e3s1p2
1i2d2i1o
i1di
id1ni3
i4d5n2
i2d2ol
1idol.
2i2d2r4
i3d2sc
i2d1s
id2s1p2
i2d1t4
i2dy1
i2e3a4
ie2bä
ie1b
i2e2bl2
ie2b1re
i2eb2r4
ieb4sto
ie4b2s
ieb1s2t
ieb4st2r4
ie1c
ie2cho
iec4h
ie2d2r4
ie1d
i4e1e2
ie2f1ak
ie1f
i2e1fa
ie2f1an
ie2fa2u
ie2f3f4
i2e2f3l2
ie2fro
ief2r2
ie4g3l
ie1g
ie3g4n
ie2g3r4
ie3g4ra
ie4g2s
ieg1s1c
ieg4st
i1ei
i2e2l1a
ie3las
iel3a2u
ie4l3d
ie2l1ec
ie1le
ieler8ge1b
ie2l1e4r1g
ieler1ge
i1e4l1l
ielo4b
i2e1lo
iel3s1z
ie4l1s
iel3ta
ie4l1t
2i1en
i3en.
i3e2n1a
ie2n1a2b
ie4n3a4g
i3e2nä
i3e2n3d
i2e1ne
ien1e1b
ie3ner
ie2n4e2r1f
i1e4n3e4r1g
i3e2n3f
i3e4n3g
ien3ge4f4
ien1ge
i3e2n1h2
i3en1j
i3e2n1k
i3e2n1m4
i3e4n1n
i3e2no
i3e1nö
i3e2n3p4
i3e4n3r2
ien3s2e
ie6n1s
ien2s2k2
ien6stof
i2ensto
ien1st
ien6stop
iens4t2r4
iens1t5rä
ien3s1z
ie1n1u
i3e2n1v2
i3e4n1w
i3e2n1z
i2e1o2
ier3a2
ie2r2a1d
ie2r2a1p
i2e1re
ie3r2er
ie4r3e2r1f
ie4r3e2r1z
i2e3re1s
i3ere2u
i4e1ri
ierin3
ie2r3k4
i1e4rn
i3ern.
i2er5ni
ie2r1ö
ier3s2e
ie4r1s
ier4s3eh
ier3sta
ier1st
ier3te
ie4r1t
ies2sp2
ie1s
ie4s1s
ies4s1t
ie3su
ie2t1a
ie1t
ie4t3e2r3h4
ie3te
ie4t3e4r1t
ie2t3ho
iet2h
i2e4t1o
ie2t1ö4
ie2t1ri
i2et2r4
iet2se
ie2ts
i1e4t1t
ie2u2e
i2e1un1
i1ex
2if
if1a1b4
i1fa
if2ar
i2f3a4r1m
if4a1t
if1a2u
i2fec
i1fe
ife2i
if2en
if1e4r1g
if1e2r3h4
if2f4ah
i2f1f
if1fa
if6fe3s2t2e
if1f2e
iffe1s2t
iffe1s
if2f3l2
if3l2
i1f4la
i1f4lä
i1f4lü
if3r2
if4ra
i1fra2u
i1f1re
if4rei
if4rü
if2ta
i4f1t
ift3e2r1k
if2t1op
if2t3ri
ift2r4
ift1s2p2
if2ts1
ift3s1z
2i1g
iga1i
i2g1a4n1g
ig1a4r1t
iga3s
i4gef2ar
i1ge
ige1f4
ig2e1fa
ige4n1a
i3gen
ig1e2r1z
i2g1im
i2gl
ig1lä
ig4n2a
ig1n
i4g2nä
i3g4ne2u
ig1n2e
ig4no1
i3go
ig4ra
ig2r4
ig3rei
ig1re
ig4sal
i4gs
ig2sa
ig3s1a4u
ig4s1e2
ig1s1o2
ig4sti
ig1st
ig4s1to
ig2stö
ig4st1re
igs1t2r4
2i1h
i2h1am
i2har
i3he
ih4e1e
ihe4n
i2h3m
i2h3n
i2h3r
i4h2s
ih3sp2
i2h1um
i2h1w
ii2
ii3a4
i1ie
i3i4g
i1im
i1in
i1i4s
i2is.
ii3t
i1j
2i1k
i2k1a4k
i1ka
ik1a4m1t
i2k1a1no
ik1a2n1z
i4kanze
ik1a4r3t
i2k3a4t1t
ik2a1t
i2k1a2u
i2kär2
i1kä
ikbu2
i2k3b4
4ike
i2k1ei
ike2l1
i2k1e2r2e
ik1e2r1f
i4ker6f4ah
iker1fa
i2k1e2r3h4
i2k2e4r2l
i2k1e4t1a
ike1t
i3ki.
ik1in
i2ki2n1d
i2k3l2
i3kla
i3k4lä
i2k2n2
ik3no
ik2o3p4
i1ko
i2k1öl
i1kö
i2k3ra
ik2r4
ik3rä
ik3re
ikro3
ik3s1o2
i2k1s
ik3s2z
ikt2e
i4k1t
ikt3e2r1k
ikt3r4
ik2t1re
i2kun1
i3k2us
i1la
i2l3a1b
i1l1a2d
i2l1ak
i2l3a2m
i2l1a6n1s
i2l1a2s1p2
il1a2u
il4au2f1b2
il3a2us
i2la2u1t
i1lä1
4i2l1b
i2l2c
il2da
i4l1d
il4d3e4n4t
il1de
ild2er
ild1o
il2do2r
il2d3r4
i2l1ec
i1le
i3le2h
il1e1he
il2e2i1d4
il1ein
il1el
i4l1en4t1s
ile4n1t
i2l1e2r1f
i2l1e4r1g
i2l1e2r1r
i2l1f2
il2f3l2
il2f3re
ilf2r2
il4f4s1
il2i1e4n
i1li
il2ie
ilig1a2
i3l2i1g
ili4ga1b
i2l1i2n1d
i2l1ip
i3l2ip.
i3l2i2p1s
2ill.
i4l1l
il3l2a
il4la1d
ill4a2n
il3l2er
il1le
il3l2i
2il4l1s
il2mak
i2l1m
il1ma
il4ma4n1g
il2m3a1t
il2ma2u
il2min
il1mi
2i1lo
i2l1or
il3t2h
i4l1t
i1lu2
i2lum
i3l2us
i2l1v4
il2z1ar
i2l1z
ilza2
ilz3e2r1k2
2im.
i2m1a4n1w
i1ma
i2m1a4r1m
im4a1t
im4a2t2r4
imat5sc
ima2ts
ima4tur
i2me1g
i1me
i2m2e1j
i2me1k
i2m1e1le
i2me2l1f
i2m1e2r1f
i2m1e2r1z
i4m4e3s1h
i3me1s
i2me3ti
ime1t
i2me1w
i2m1i2n3f
i1mi
i2m1i6n1s
im2m1ei4
i2m1m
im1me
im4m3e4n1t
1im1mo
im1ni2
i2m3n2
2i1mo
im1o4r1g
1im1p2o
i2m1p
im2p4s
im3p3se
1im3pu
im2st2r4
i2m1s
im1st
2i4m1t
imtu2
2i1mu
i3n3a2c
i1na
i4na2c4k
i2n1a1d
in2af
in1am
i3na1p
in2a2r1a
in1ar
in2a4r1s
i4n4a4r1t
i3na4s
i2n3a2u2
ina2us1
in1äs
i1nä
in2dal
i2n1d
in2dan
in3d1a2u
inde1s4t
inde2s
in1de
1index
in3do
2ind2r4
ind4ri
in3d1rü
1ind2us
in1du
2i1ne
i2n1e2be
ine1b
in1e1he
i2n3ei
i2n1e4n1g
i3nen
in3erb2e
ine2r1b
i4n1er1bi
in2e2r3h4
i2n1er4l2ö
ine4r1l
i4n1er4t2r4
ine4r1t
i4ne2s1k2
i3n2e1s
in1e2u
in2e3un1
i2n1e2x
i2n3f
1info.
in1f2o
1info1s
2inga
i4n1g
ing1af
in2g1a4g
in2gl
ing4sam
in4g2s
ing2sa
ing3s1c
1inha1b
i2n1h2
2in3har
2in3ha2u
4in3he
in2i3d
i1ni
i3n4ie
2in2i1g
ini3k2r4
i3n2i1k
in2ir
2i3n2i1s
in2i3s1e
i3n2i4t1z
i2nit
3inka4rn
i2n1k
in1ka
ink4st2e
in2k1s
ink1st4
inm2a4le
i2n1m4
in1ma
2inn.
i4n1n
in4n3e4r1m
in1ne
2in2n3l2
in2n1o2r
in1no
inn4sta
in6n1s
inn1st
1innta
in2n1t
2i1no
i2n1o2d
in3o4l1s
in2ol
in1or
ino1s4
in2o3t
i1nö
i2n1ö2d
2i2n3p4
2i2n3r2
in3s2am
i6n1s
in1sa
insc4h2
in1sc
in2s1e1b
in1se
2insen
ins3e4r1t
in3skan
in2s1k2
ins1ka
in3sk2r4
1insta
in1st
in4s3t2ät
in3su
1in2s1u2f
in4s3um
in3s2z
i4n1t
1inte3g6
int2h
in3t4r4
in1u
i3n2um
in3u2n1z
inu2n1
invil4
i2n1v2
i1ny
in3zw2
i2n1z
i1ñ
2i1o
i2o3a4
i2o1c
io2d
i2o3d2a
i2o3e2
iof4l2
i2o3h
io2i3d
i2oi
i2o3k4
i3ol.
i2ol
i3om.
i2om
i3o2m1s2
ion2
i3on.
iona2l3a2
i2o1na
io3nal
io2n2a2u
io2n3d
i3o6n4s3
i2ony
i2o1p
io4pf
i3o2p1s
i3o2p3t4
i2or
i3or.
i3o2r1c
iore4n
i4o1re
i3o2r1p2
i3o4r1s2
i3o4r1t
io3s
i2o1s2t
i3ot.
i2o1t
i3o2ts
i2o2u
i2o1v
io2x
i3oz.
i2o1z
i1ö2k
i3ön
i1ös.
iö1s
2ip.
i1p2a
i1p2e
ipen3
i3per
ipf2
ip3fa
i1ph2
2i1pi
ipi3el
ip2i3en
i1p4l4
ip2p3l4
i2p1p
ip3pu
i1p2r2
2i2p1s
2i1pu
2i1q
i1r2a
i3r2a1d
1i2rak
i1r2a1t2
i1rä
ir2bl2
i2r1b
i2r1c
i1r2e
i3r4ee
2i3re1k
i3ré
i4r1g2
ir2gl
ir4g4s
ir2he
ir1h4
i1r2i
2i3r2i1g
2i2r1k
ir2k3l2
irli4n
i4r1l
ir3l2i
ir2mak
i4r1m
ir1ma
ir2ma2u
ir4mä
ir2m1ei
ir1me
ir2mum
ir1m2u
ir4m3u2n1t2
ir3mun1
ir2n2a2r
i4rn
ir1na
ir2no
i1ro
1ir2on
i1rö
ir3p4la4
i2r1p2
ir1p2l4
ir4rei
i2r1r
ir3r2e
irr2h4
ir4s2c2h3w
i4r1s
ir1sc
irsc4h
ir3s1h
irt4st4
i4r1t
ir4ts
ir2u2s1
i1ru
i1s
i3sac
i1sa
i4s1a4m1t
i3sam
i2s2a1p
is3a1re
i2s1ar
i2s1a4u
i2s1än
2i4s3b4
i2s1ca
i1sc
isch3ar
isc4h
i3s2che
i4s1che1f
i4sch3e4h
i4s4ch3ei
i2s2c4h1l2
isch3le
i2s4c2h2m
is2ch3o1b
is2ch3re
isc2h2r4
isch3ru
i4schw2a
is2c2hw
i6sch1wi4r
i4schwo
isch3w2u
i2s3c4r2
2i1se
i3s4e3e
is2e3h1a4
i3s2eh
ise3hi
ise3i2n3f
is1ein
i4sei4n1t
ise2n1
is2e2n1d
i3s2e6n3s
i2s1e2r3h4
i2s1e4r1m
is2e1r2u2
i2s1e4s1s
ise1s
i4s3e2t4a1t
i3se1t
is2h2as
i2s1h
ish2a
isi2a
i1si
i2s1i1d
i4s1m2
i2s1of
iso6ne2n1d
i3s2on
is2o1ne2
iso3ne2n1
is1op
3i2s2o1t
2isp2
is1p2a
i2spar
is1p2e
is1p2ic
i1spi
is2pit
i1s2por
is1p2o
i2s1p4ro1
isp2r2
is3sa
i4s1s
is4s2aa
is4s1ac
is4s1a4u
is3s2ä
is4s3che
is1sc
issc4h
is2s1t
is3st2a
is4s1t2e
is3sto
is3st2u
is6s2u1c
is2sum
i2sta1b
i1st
i4s2tam
ist2an
i4s1t2e2a4
i3st2e
iste4n
is2ter
ist4ra
ist2r4
ist3re
is1t1rü
i2stur
ist2u
is1tüm
i2s1ty
isu2m3p
i2sü
i1ß
iß1e4r1s
i1ße
i1ta
i2t1ab.
ita1b
i4t1a4b1s
ita2l1a
i2t1a4l1t
i2t1am
i2t1a4n1g
it3a4re
i2t1a4r1t
i3t2a1t
it1a2u
i3t4a2uc
i2t1a1x
4i1tä
it2är
i2t1ä2s2
it2ät2
i1te
i2tei
i4t1e2i1g
i4t1ein
2itel
ite2la4
ite4n
it2e6n1s2
i4te1p2o
ite1p
i2tex
i3t2h2r2
it1h
i1ti
i2t1i1d
1itii2
iti4kan
it2i1k
iti1ka
it4i3k2e
i2t1in1
it2i4n1n
i3t2i1s
i3t2i1v
i4t3l2
itmen2
i2t1m2
it1me
i1to
i3t2oc
i2t1of
i1tö
i1t2r4
i3t1ra.
it3r2af
i2t3ran
it3r2as
it3ra2u
it3rä2u
i1trä
it3re
it3r2ic
it1ri
it3r2om
it4r2on
i3t1ru
it3run1
it2sa
i2ts
its1a4g
it2s1e4
it2s3er1
its1p2e
itsp2
it4staf
it1st4
it4stec
it3st2e
it4s3te1m
it4s3te2s
it2sti
it4s1t2i2e
it2s2to
it2te1b
i4t1t
it4te2m1p
itte1m
it2t1ri
itt2r4
i1tu
i2t1u1h
i2t1um
i2tu6n1s2
itun1
it1u4r1g
it2u1t4
i1tü
2i4t1z
it2z1ä2
it4z3e4r1g
it2z1w2
2i3u2
ium1
iu1s1t
i2us
i1ü
2i1v
i2v1ak
i2v1a4n1g
i2ve3b
i1ve2
i2v1e4i
iv1e4l1t
ive4n
i2v1e3ne
i2v1e4n1t
i2v1ur
2i1w
iwur2
iw2u
2i1x
i2x1a
ix2e1m
i1xe
i3xi
i4x1t2
2i1z
iz1a1p
iza2
iz1a2u
ize2i3c
ize2n
i2z1e1ne
iz4er
i2z1ir
i2z1o2b
i2zö
i2z1w2
í1l
jah4r3ei
jah3r2e
ja2hr
jah4r4s
ja3l2a
j2a3ne
j2a3ni1
2j2a1t
j2e2a
jea6n2s
je2g
jek4ter
je1k
j2e4k1t
jektor4
jekt2o
jek2t2r4
je3n1a
je2p
je2t1a
je1t
je2t3h
j2e2t3r4
je4t3t
je2t1u2
ji2a
j2i2v
j2o3a3
jo2b1
job3r4
j2o2i
j4o3ni1
jo1r1a
jo2r1d2
jo2sc
jo1s
jou4l
jo2u
j2u
ju2b2l2
j2u1b
ju3gen2
j2u1g
ju1ge
juge2n1d3
ju2k
jun4g5s1
jun1
ju4n1g
ju3ni
j4u1r2o
ju3t2e3
j2u1t
2j1v
1ka
3ka.
k3a2a
k4a3ar
kab2bl2
ka1b
ka4b1b
ka2ben
2k1a2b5h2
2k1a2bla
kab1l2
2k1a2blä
2k3a2bo
ka3b4r4
2k1a4b1s
2k1a4b1t
ka1c
k2a1d
2k3ada
2k3a2d2r4
k2a1f4l2
ka1f2r2
ka4f3t2
k2a1g
ka1in
1ka3ka
kaken4
k4a1ke
2k2a5la.
ka1la
ka2lan
ka3l1ei
k2a1le
ka3l2en.
kalen1
ka4le6n1s
kal3e1ri
1kal2ka
ka2l1k
kal2k2r4
k1a4l1l
k2a1lo5
kal4t2r4
ka4l1t
k3a1m2a
kamp8fe2r1f
ka2m1p
kam2pf
kamp1fe
kan2al
ka1na
k2a4n1a4s
ka2n1a2u
ka2n1d4
2kanda
k2a1n2e
2k1a4n1g
ka2n3k4
2k1a2n3l2
2k3an3na
k2a4n1n
k1a6n1s
k2ans.
6k3ante4n3n
ka2n1t
kan3t2en
k2a3nu
2k1a4n1w
k2anz.
ka2n1z
k2a2o
2k1a2pf
ka1p
3ka1ra
2k1a2r1b
k2a2r1d
k2a4r1g
k2a3r2i
kari3e1s
kar2ie
k2a2r1k
2k1a4r1m
k2a2r1p3
kar2pf4
k2a4r1s
ka4r3t
k2arta
2k1art2i
k2a1ru2
k2a2r1w
ka1si1
ka2s1p2
ka4s3s
ka3t2an
k2a1t
ka3t4h
k4a2t3r4
2ka4t1t
kau2f1o
ka2u
4kauf3r2
kauf4sp2
kau4f1s
k1a2us
ka2u3t2
2k3auto
1kä
k1äh
k1ä2mi
k1än
kär2
kä2s1c
kä5s4e3
kä3t2h
2k3b4
kbe1
kbo4n
k3by2
2k3c
2k3d2
kd2a2m1p2
2k1ec
k1e2f1f
ke1f
k2e1fi4
ke3ge2
ke1g
ke2gl
ke2he.
ke1he
keh4r2s
ke2hr
kehrs3o
kehr4st
2k1e2ic
2k1e2i1g
k1ein
ke1i2n2d
2kei2n1h2
2k1e2i1se
kei1s
ke2la
kel1ac
ke3la1g
kel1a2u
k2e2lä
ke2l3b4
2k1e2le1k
ke1le
ke2len
ke2l1er
2ke3le1t
kel1l4e
ke4l1l
kel3s2k2
ke4l1s
k4e4l1t
2k1e2m1p
ke1m
k2en.
ken3a2u
ke2n1a
4k3en4ga1g
ke4n1g
2kenlä
ke2n3l2
ke2no
ken2s2k2
ke6n1s
ken5s4t2e
ken1st
ken3s1z
k2ente
ke4n1t
k3en3t2en
ken3t1h
k2ent2r4
2k1en4t1s
k2entu
2k1en4t3w
2k2eo2
ke2p2l4
ke1p
k2er.
k2e2r1c
4kerf4ah
ke2r1f
ker1fa
k4erfam
k3erge1b
ke4r1g
ker1ge
k3er6ge4b3n2
k3e2r4h2ö
ke2r3h4
ke6r1in6nu
k2e1ri
ke3ri4n1n
kerin6st
ke2r1i6n1s
ke2r1i4n4t
ker4ken
ke2r1k
k2er1ko
k2e4r1l
k3er4la2u
ker3l2a
k3er4le1b
ker1l2e
k6erlebe
ker4ne2u
ke4rn
ker3ne
k1ero
k2ers.
ke4r1s
ke2r1z2
ker4zeu
2k1er2zi
k6es.
ke1s
ke2sel
ke1se
ke4t1a
ke1t
ke2t3h
ke2t3s
ke1u1p
keu6s2c4hl2
ke2us
keu1sc
keusc4h
2k1e2x
2k3f4
2k1g2
2k1h4
kh2o3m
ki3a4
ki1c4h
k2ic
2k1i2de
ki1d
k2i3d2r4
ki2el
ki2e2l3o
ki1f4l2
k2if
ki1f4r2
k2i3k4
2ki1l2a
ki3li
k2i3lo
k2i1mi
k2in.
k2i4n1g
2ki2n1h2
k2i1ni
k2i4n1n
k2i3n4o
ki6n3s
2k1in1se
2k1i4n1t
ki3or
k2i1o
kio4s
3kir
k2is2p2
ki1s
ki1st2
2k2i1z
ki3zi
2k3j
2k1k4
kl2
4kl.
4k5la.
k4lar
4k1la1st
k2le
4k3le.
kle3a1ri
kl2e1a
4k3leh
k4l2e2i1d
4k3l2e2it
k3lem.
kle1m
2k3ler
kl2e2r1a
2k3le2u
kle3us
2kl2i1c
k1li
2k3l2i1g
k2lin
k3lip
k2lir
k2li1sc
kli1s
2kli1st
kli2t2s2
4kl2i1z
2k3l2oc
k1lo
kl2o2i3
k4lop
klo1s2t4
klo1s
k2l2ö1t
k1lö
k1lu
k1luf2
2k1l2üc
2k1ly
2k1m
k2n2
3k2n1a1b
k1na
k3ne
k4n1ei
2k5ner
k3no4b1l2
k1no
kno1b
2k5nor
k3nu
3knü
1ko
ko2al
k2o3a2
2k1o4b1j
ko1b
2k1o2fe
ko2f1f4
koh3lu
ko4hl
k2o1i2
ko1l2a
k2ol
ko3le
ko2l2k5
3k2om
ko4mu
k2on
k2o3n2e
ko6n1s4
ko3nu
2kop.
ko1p2e
kop4fen
kop1fe
2ko2p1s
2ko2p1z
ko1r2a
2k1o2r1c
kor6de4r1g
ko2r1d
kor1de
ko3ri
k2o1s
k2o2s1p2
ko3ta
k2o1t
ko2t1s2
kot4t1ak
ko4t1t
2k1o2u
3k2o1w
ko2we
k1o2x
1kö
k1ö2f
k1öl
2k1p2
k1q
k2r4
2k3r2a1d
k3ra2ts
k1r2a1t
2k3r4aum
kra2u
k4r2a1z
2k3rät
2k3r2äum
krä2u
2k3re.
k1re
2k3rec
2k3red.
kre1d
2k3re1d2e
2k3re1f
2k3re1g
k3re2ic
kr2e1i2e4
kreier4
k3re2i1h
2k3r1h4
2kr2i1b
k1ri
2k3r2ic
k3rie1s
kr2ie
2krip
3kr2i1s
3k4r2on
2k3ruf
k1ru
k2r1ü1b
k1rü
2k1s
k4s1a4m1t
k1sa
k3sam
k2s1an
k2s1a4u
ks2än
ksc4h4
k1sc
ks1e2b
k1se
k2s1e1m
k2se4n1t
ks1e4r1l
k2s1e4r1s
k2s1e2r1w
k2s1i1d
k1si
k2s1in
k2s1o2
k3sof
ks1p2a
ksp2
k3sp2e
k1s2por
ks1p2o
ks2pu
k4s1s2
k1st4
k4s3ta2n1z
k3st2a1t4
k4s1t2e2a4
k3st2e
k2s1t2i1s
k2s1tor
k2s1trä
kst2r4
k2s1tum
kst2u
k2s1u
ks2zen
k2s1z
4k1t
k4t1a4b1s
kta1b
k2t1a1d
4k1t1a4k1t
k1tak
k3tal
k2t1am
kt1an
k2t3a2r
kt2a4re
k2t1a2u2
ktä3s2
k1t4e3e
kt1ei
k2te2m1p
kte1m
k2te4n1t
k4t3er1fo
kte2r1f
k2t1e2r3h4
kt2e3ru2
k2tex
k2t1h
kt3ho
k2t1i1d
kt1im
k2t1i4n1g
kt1i6n1s
kti4ter
k3ti3te
k2t1of
k3top
kt1op2e
k4t3or3g4a
kt1o4r1g
kt3or2ie
kto1ri
kt4ran
kt2r4
kt3r2as
kt4ro
kt3run1
kt1ru
k2t3s2
kt1s4t4
k4t1t2
k2tu6n1s2
ktun1
k4t3z
k2u1c
ku1h3
2k1u2hr
kul2a
ku1l
ku3l2e
ku3l2i
2ku2l1p
2k3u2m3l2
ku2m2s
k2u3n2a
kun1
ku6n4s2
kun1st3
2k1u2n1t
2k1up.
kur2bl2
ku2r1b
ku2rei
ku1re
kur2i2e
ku1ri
k4u2ro
kur2s2p4
ku4r1s
kur4st
ku4s2c4hl2
k2us
ku1sc
kusc4h
ku2sp2
ku2su
k2u2ß
1kü
2k1üb
k2ü1c
kü4r2s
2k1v
2k1w
2k3z2
kze3l
5la.
l1a1b
l2a3ba
2la4b1b
4l3aben
2l1a2b5f4
2l1a2b1g2
2l1a2b5h2
4la2b1l2
l2ab2o
l2ab3r4
la3b4ra
lab4ri
2la4b1s
3l2abu
2l1a2b1w
la1ce
la2ce.
1la1d
l4a3d2i
l1a4d1l2
2la2d3m2
2l1a2d2r4
3l2a1du
l1a2d1v2
2laf
la2f1a
la4f3t
l2a2ga
la1g
la2g2i1o
la2g2n
lago2
la2g1o1b
2la1ho
1lai
lai4s1t
l2ai1s
la2ke1s
l4a1ke
l2a2k1i
l2a2k1k4
l2a1k4l2
2l1al
4la4l1l
4la2l1p
l2a1mi
la3min
lam4ma
la2m1m
1lam2m1f4
l2a2m1p
2l1a4m1t
lam4t4s
la4mun1
l2a1mu
l1anal
la1na
la2n1a2u
2l1a2n3b4
3l2a2n1d
lan2d3a2
lan6d5e2r1w
lan1de
lan6d5e2r1z
lan2d3r4
2l1a2n1f
lan2g1l
la4n1g
lan4g3s2
2lan3hä
l1a2n1h2
l2an3he
2l1a2n3l2
4lan1li
2l3a4n1n
l1a2n3p4
2la6n1s
4l1ansä
2l3ant2r4
la2n1t
l2an2zw2
la2n1z
3l2ao
l1a2p2o
la1p
l3ap4p3l4
la2p1p
la2r1an
la1ra
la2r1ei
l2a1re
la4re1ne
la3ren
3l2a4r3g
lar3i1ni
l2a1ri
2l1a4r3t
l3art2i
l2a2ru
la2s3a4u
la1sa
4la4s3d2
l4a5s2e
2l4a2s1h
2la1si
la2so1
2la2s1p2
3lasser
las3s2e
la4s1s
l2a2sta
la1st
last1o
l2a2st2r4
las3tur
last2u
la2stü
la2ß3
lat2a
l2a1t
la3t2e
la4tel
2l3at2h
la2t3ra
l4at2r4
la2t2s
2lat3t1a
la4t1t
lat4tan
lat4t3in
lat2t3r4
laub4se
la2u
l2a2u1b
lau4b1s
lau2fo
l2au2f1z
1l2a2u1g
2l1au2s1l2
la2us
2l1au2s3r4
2l1au4s1s2
2l3auto
la2u1t
1l2a1w
law2a4
lä1c
2läf
2l1ä2hn
1lä2n1d
lär2m1a
lä4r1m
lä2s1c
4lät
2lä2u1b
2lä2u1c
2lä2u1e
1lä2uf
1là
2l1b
l3bac
l2b1e1d2e
lbe1d
l4beta
l3be1t
l2b1i1d
l1bi
l2b1i6n1s
l3b2l2a1t
lbl2
l3blä
lb3le
l2b1li
l3b2lo
l4b3re.
lb2r4
lb1re
lb3r2it
lb1ri
l4b2s
lb3sa
lb3se
lb4s1k2
lb3sp2
lb4st3e
lb1s2t
lb4sto
lb2u
l2b3u2f
lbzei2
l2b3z2
2l1c
l3che
lc4h
l3chi
l2c4h3l2
lc2h3r4
l4c4h3s
l2ch3ü
l2c2h1w
l3c4l2
l3co
4l1d
l2d3a2b1
l3d2ac
ld3a2c4k
l2d1a2d
ld1a4g
l2d1ak
ld1al
l3dam
ld1a2m1m
l2d3a2n
l2d1a2r
ld3a1ri
l3das
l3d2a1t
ld1a2u
ld1är
l2d1ei
l1de
l2de1le
l3d4er.
ld1e2r3p4
lde5s1a
lde1s
l2d1e2se
l2dex
l3d2i2c
l1di
l2d1i1d
l2d1im
ldo2r
ld2o1s
ld2ö2
ld3r4
l2dran
l2d1re
l3d4ru
ld4rü
ld3sa
l2d1s
ld1s2t4
l2d1t4
ld3t1h
l2d1um
l1du
ldy3
ldy2s2
1le
3le.
le2a1d
l2e1a
3l2eba
le1b
lebe6n4s
leb2en
l2e2bl2
2lec
le2chi
lec4h
lecht4e
le2c4h1t
3le1d
4le2d1d2
le3d2e
l4e2e
le3ei
l2e1f2a
le1f
le2g1a2s
le1g
le2ga2u
le2gä
le2gl
leg4r4
3leh
leh3r2e
le2hr
4le4h2s2
4le4h1t
3lei.
lei2b2r4
l4e2i1b
l2e2ic
l2e2i1d
4l1e2i1g
l2ein.
l2ei2n1d
l2ein4du
l2e2i1ne
lei6ne2r1b
2lei2n3k
l2ei4n1t
leis6s5er
lei1s
lei4s1s
leis1se
l4ei1st
lei4ßer
lei1ß
lei1ße
l2e2it
lei2ta
lei8t7er8sc
lei3te
leite4r1s
lekt2a
le1k
l2e4k1t
2lekt2r4
3l2e1la
2l1e2le1k
le1le
le4l3s
3le3me1s
le1m
le1me
le2m1o2
4le2m1p
l2en.
le4na1d
le2n1a
le2nä
4lende1t
le2n1d
len1de
2len1du
le4n3e2n1d
l2e3nen
le1ne
4l1e2ne4r1g
l2e2n3f
le3ni
l2e2n1k
2l1en3ni
le4n1n
l2e2no
l1en4se1m
le6n1s
len1se
len3s1z
l1en4t1s
le4n1t
2l3en4t3w
lent4w2ä
5l4ent1we1t
4l1en4t3z2
len2zi
le2n1z
le1o1s2
l2eo
2le1p
3le3p2a
3le3p2f4
3lep2r2
l2er.
l2e1r1a
le2ra4g
le2ra2u
le2r1b4
4l3e2r1e2i1g
le1re
le4r3eim
le4r1e4r1s
l1er1fo
le2r1f
l2erf4r2
l2er1fü
3lergeh
le4r1g
ler1ge
l3er3gen
3l4erge1w
2l1ergi
le2r1i6n4s
l2e1ri
le2r1k2
l2er1ka
l2er1ko
1l2er1l2e
le4r1l
le1ro
2l1er2ö
3l2erra
le2r1r
l4ers.
le4r1s
ler3s2k2
le4r3t
6lerwe2r1b
le2r1w
l1e2r1z
l2erza2
le3s2am
le1s
le1sa
le1s2e
2l1esel
le3ser
l4e3s1h
le1si1
le3s1k2
le4s3s
le3s2t2e3
le1st
4le2s1w
2le3s2y
le2t4a1t
le1t
2le3t2h
2l2e3to
le2u
4le2u1d
2l3e4u3ro
3l2e2u1t
3l2e1v
2le1xe
l1e2x2i1s
le1xi
2lex1z
2l1f
l3f4ah
l1fa
l2f1ec
l1fe
lf4e1e
l4f1ei3s
l3f4lä
lf2l2
lf3lo
l3f4lu
lf3ra4m
lf2r2
lf2t2r4
l4f1t
l1f4u
lf2ur1
l3fü
2l1g
lg2a3t
l2gd4
lge3n2a
l1ge
l3gen
lge3r1a
lgerä2u3
lge1rä
l2ge3ti
lge1t
l3go
lg3re
lg2r4
l3gro
l4g2s
lg4s1t
2l1h2
3l2hi.
1li
3li1a
l2i3ac
li3ak
li3am4
li3ar
l2i3b4
li1bi3
l2i1c
3liche1m
li1che
lic4h
3licher
li3chi
4li2c4k
li3d2a
li1d
li2d2eo
li1de
2l1ido
li4d3s
l2ie
3lie.
lie3be4s
lie1b
li3e1ne
l2i1en
lie4s1c
lie1s
3l2i1g
lig4n
li2g1re
lig2r4
li4g1s2
l4i3ke
l2i1k
li2k2r4
lik2sp2
li2k1s
lik4ter
likt2e
li4k1t
li3l
li1l2a
2lim
li3m2a
l1i2m1b2
3l2i1mo
li3n2a
lin3al
2l1in1du
li2n1d
li4ne1d
l2i1ne
li2n1e1f
li2neh
li2ne1p
li3n2e1s
2l1i2n3f
lin4g2s3
li4n1g
2l1i2n1h2
2l1i2n1it
li1ni
2l1in1j
lin2k1a
li2n1k
lin2k2s
li2n2ol
l2i1no
l2ins.
li6n1s
l2in1sa
l2in1sc
2linsp4
2lin1st
2l1i4n1t
l1i2n1v2
2li2n1z
l2i2o
li4om
li3o6n5s3
lion2
li3os.
lio3s
li2p3a
3lis.
li1s
li3s2a
li4s4chu
li1sc
lisc4h
2l1i2s1l2
2l1i2so
l2i2sp2
li4s1s4
2li1ß
li2tal
li1ta
li3te
li1t2h
li2t1s2
lit3s1z
li2tur
li1tu
3l2i3u2
2li3xi
l2i1x
li2za2
l2i1z
lizei3
4l3j
2l1k
lk1a2l1p
l1ka
l3k2an
l3k2ar.
lke4n3t
lk2l2
lk3lo
l3k4lu
lk4ne
lk2n2
lko2r2b1
l1ko
lk4ra
lk2r4
l2k3ro
l2k3ru
l2k2s1
lk3sä
l3k2ü
4l1l
lla2be
ll1a1b
l2la4b1t
ll1a2f1f
l2laf
ll1a4k1t
l3l2al
l2l1a2m
ll3a1m2a
lla2n
ll2a4n1g
ll2a4n1w
ll1a2n1z
l3la1p
ll1a4r1m
ll1a2u
ll4au1fe
l1l3a2u1g
l2l3a2us
l2l1äm
l2l1b4
llc4h4
l2l1c
l4l3d4
l3lec
l1le
ll1ec4h
lle3en
ll4e2e
l2l1e1f
l2le2g1t
lle1g
l2l2e1gu
ll1eim
ll2e1m
l3l2en.
lle4n3a
ll3en4d1l2
lle2n1d
l2l3en1du
lle4n3g
l4l1en4t1s
lle4n1t
l3l2er.
ll2e2r1a
l4l1er1fo
lle2r1f
l6l3er3gen
lle4r1g
ller1ge
l4lergo
ll3er2n1t
lle4rn
ll3ert2r4
lle4r3t
l2l1e2r1z
ll2e1s
l2lex
l2l1g4
l4lie1g
l1li
ll2ie
ll1i2m1p
l2lim
l2l1i2n1d
ll1i6n1s
l2l1k4
l2l5m
l4ln2
ll1o1b
l1lo
l2lob2e
l2l1of
l2l1opf
l2l1o2r
l3l2or.
l3l4o1re
l2l1o2u
l2l1ö2f
l1lö
ll1ö4se
llö1s
ll3s1h
l4l1s
ll3s2k2
ll2sp2r2
llsp2
ll4s3tor
ll1st
l4l3t4
llti2m
ll4t5s2
l1lu2f
l2l1ur
llu1s2t6
ll2us
l2l3z2
2l1m
l2m3a2b
l1ma
l2m1a2r1c
lm1a2us
lma2u
l2m1c
lm4e2e
l1me
lm3ei6n1s
l2m1e2p
l2m1e2r1z
lm1i2n1d
l1mi
lm1i6n1s
l2m1öl
l1mö
l2m3p
lm2pf4
lm3s2z
l2m1s
l4m3t
4ln
ln1a4r
l1na
ln3a1re
l3n4e
l3ni
l1nu
l1nü
1lo
3l2ob.
lo1b
lo2ber
lob2e
2l1o4b1j
2l1o2b1l2
l2ob2r4
lo3b4ri
l1o2fe
lo1f3l2
l2o1f4r2
lo2ga2u
l2o1g
lo3h2e
2l1o2hr
loi4r
l2oi
3l2o1k
l4o2k3r4
lo1l2a
l2ol
l3o2ly
lo2min
l2om
lo1mi
lo2n1o1
lo2o
2lopf
2l1o2p3t4
lo1r1a
lo4rä
2lo2r1c
l1o2r1d
lo3ren
l4o1re
2l1o4r3g2
3l2or1q
3los.
lo1s
l2o4s2a
3l2o3se
lo4ske
lo3s1k2
lo2s2p2e
l2os1p2
los1s2e
lo4s1s
lo4s2teu
lo1s2t
lo3st2e
lo2s3to
lo2s3t4r4
lo2ßu2
lo2t1a
l2o1t
l4ot4h
lo3th2a
lo3t3hi
lo3t2i4o
2l1o1v
lo2ve2
2lo1x
1lö
lö2b3
2lö1d
l1ö2f
2l3ö1fe
4lög
l1ö2hr
2l1ö4l
4lö1ß
2l1p
l3p2a
lpe2n3
lp2e
lp2f
l2p1ho
l1ph
l3pi4p
l2p3t4
l3pu
2l1q
2l3r2
lra2t4s
l1r2a1t
lr2om2
l3r2u1t4
l1ru
l2r1ü1b
l1rü
4l1s
l3sac
l1sa
l2s1a2d
l3s2al
l4s1a2m1b2
l3sam
l2s1a2n1f
ls1an
l2s2a4n1n
l3s2a1re
l2s1ar
l2s1a4u2
l4schi2n
l1sc
lsc4h
l4sch1mü
ls4c2h2m
l2s1e2b
l1se
l2s1ec
l2s1e1m
ls1e1re
ls1e4r3g
ls1e4r1l
l2s1e4r1s
l2s1e2r1w
l3se1s
l3s1ex
l4sh2a
l2s1h
lsho2
l2s1i2m1p
l1si
ls2l2o1g
l2s1l2
ls1lo
ls3oh1n2e
lso2hn
l4s3ort.
lso4r1t
l3s2pi
lsp2
ls2p2o
l2s1p4ro1
lsp2r2
l3s2pu
l4s3s2
lst2a
l1st
lsta1b6
ls2taf
l4s3tä1ti
lst2ät
l2s1t2i1s
l2stit
ls2t2r4
ls1um
l2su2n1
ls2zen
l2s1z
4l1t
l2ta1b
l4t1a4b1s
l3t2a1g4
l1t1ak
l2t1a2m
l3ta2mi
lt3a2n1d
l2t1a4n1g
l3t1a2r1b
l2t1a4r1t
l2t3ato
lt2a1t
l2t1a2u
l3te.
l2t1eh
l2t1ei1s
l2t1e4le1m
lte3le
lt3e1li
l3t2en
lter3a
l3t2e4r3g2
lt4er1ö
l2t1e2s1k2
lte2s
lte3st2r4
lt2e1s2t
l3t2et.
lte1t
lte2t2h
l2t1eu
l2t1h
l4t3hei
l1th2e
lt3ho
l3thu
lt2i1mo4
l2to1b
l2t1of
lt1op
l2t1o2ri
lt2o2w
lt1ö4l
lt1ö1s
l1t3ö1t
lt4rak
lt2r4
ltr2a3l
l3trä
lt3rä2u
lt3re
lt4r2ie
lt1ri
l1t3r2oc
lt3ro1s
l2t3rö
l4ts
lt3sc
lt1sp2a
ltsp2
lt4sta1b
lt1st4
lt4s3t2oc
lts2to
l4t1t2
l2t1u1h
l2t1um
ltu4ran
ltu1ra
ltu2ri
lu1an
l2ua
4l2u4b3
luba2
lu4b1s2
lu2d2r4
l2u1d
lu2e4s
l2u1e
1luf
2l1u1fe
2l2u2f1f
luf2t1a
lu4f1t
luf2t1e
luf2t5r4
lu2g1a
l2u1g
lu2g1e2b
lu1ge
lug3e2r3p4
lu4g3l
lu2go3
lu2g3r4
lug3sa
lu4gs
lu2gu
2l1u1h
lu1id.
l2ui
lui1d
lu1me2
2l1u2m1f4
2l1u2m3l2
l2u2m1p
l1u2m1s
l1u2m1w2
1lu2n1
2l1u1na
2l1u2n1f
2l1u1ni
2l1u2n1t
2l1u4n1w
4l2u2o
lu2pf
2lur
l1u4rn
l1u4r1t2
2lu1se
l2us
lu2sp2
lus4sa
lu4s1s
lus4s2ä
lus2s1c
lus4s2e1k
lus1se
lus6se2r1f
lus6se2r1k
lus4si
lus2s1o
lus2s1p2
lus4s1t
lus6s2u1c
lu1s2t
lu2st1a
lu2stä
lu2sto
lu3st2r4
lust3re
lu2s1u
4l2u2ß1
lu2t1a
l2u1t
lu2t1e1g2
lut3e4r3g2
lut1o2f
lu2top
lu2t3r4
3l2u1x
2l1üb
5lüd
lü4h1l
2l1v
2l3w
2lx
1ly
ly1ar
ly3c2
2ly2m3p2
3lyn
ly3n1o
ly1o
ly1s2
ly3te
ly1t2
ly1u
2l1z
l2z3ac
lza2
l3z2an
lz2e2r1k2
lz1i2n1d
l2z1o2f
l2zö
l2z3t2
l2z1u4fe
lzu3f4
lzu4g4s
lz2u1g
lz1w2
lz2wec
1ma
m2aa2
m1a1b
m2abe
2m1a2b1k4
m2ab4r4
2ma4b1s
2ma4b1t
mach4t2r4
ma4c4h1t
mac4h
ma2ci
ma3da
ma1d
ma2d4r4
m2a4d2s2
ma1f
ma2ge.
ma1g
ma1ge
ma2ge1b
ma2ge1f4
ma2ge1g
ma2ge1k
ma2ge3p4
ma4ges.
m2age1s
ma2ge1t
ma2g2e1v
ma2ge1w
2m1a4g1g
magi5er.
magi5e4r1s
ma3g4n
2m1ago
ma2i4s2e
m2ai1s
2m1a4k1t
mal1ak
ma1la
ma4la4k1t
ma2lan
m2a4l3a1t
ma2l1a2u
ma4l3d
ma3ler
m2a1le
m2a1li1
mal3lo
ma4l1l
2mal4l3t4
ma1lu2
ma2l3u1t
ma2m3m
2m1anal
ma1na
ma2n1a2u
2m1a2n3b4
man4ce.
ma2n1c
ma2n3d2
man3e4r1s
m2a1ne
ma2ne1t
m2a2n1f
2m1ang2r4
ma4n1g
m2a2n1h2
2m1a2n3l2
m4a4n1n
m1a6n1s
m2ans.
2man1sa
2m1ansä
2m1an1sc
2m1an4t3w
ma2n1t
2ma2n1z
ma2or
m2ao
m2a2p1p
ma1p
2m3a2r1b
ma4r3g2
4m2a3r2o
ma2r1o3d
4m2a2r1r
mar6s4c2h2m
ma4r1s
mar1sc
marsc4h
mar6sc2h2r4
m2a3r2u
m3a2r1z
3mas
ma1s2p2a
mas1p2
4m1asp2e
ma1s2t
3maß
ma2t1a2b
m2a1t
ma2tan
ma2t4c
ma2tel
mat2e
ma4t3e2r1d
mat3se
ma2ts
mat1s1p2
2m1au2f
ma2u
m4a3un1
2m1au4s3g2
ma2us
m4ay
ma1yo
3mä
m1ä2hn
mä1i2
4m1ä2n1d
m1ä4r1g
mä1t4r4
mäu2s1c
mä2us
2m1b2
mb4e2e
mb4l2
m3b4r4
m3by2
2mc
m3c4h
2m1d
md1a
m2d1ä
m2d1ei
m1de
md1s2e
m2d1s
m2d1um
m1du
1me
me1b4
m2e1c
me1di3
me1d
medi2e4
med2i1en3
2me3dy3
me1e1f
m4ee
me1e2n1
mega3
me1g
3meh
2m1e2if
2m1e2i1g
m2e4il
mein4da
mei2n1d
me1i2so
mei1s
m2ei1st
me3l1a2m
me1la
me2la2u
3me4l1d
me2le1k
me1le
me2ler
mele1t2
2m1elf.
me2l1f
mel2se
me4l1s
me4l3t4
6m3el6te4rn
2m1e2mi
me1m
m2en.
me2n1a2b
me2n1a
me3nal
men3ar
men3a2u
men3ge
me4n1g
men3gl
me3n1o2r
me1no
m2e6n1s
men4s1k2
men2so
men3ta
me4n1t
2m1en2t5n4
4m3entwi
m1en4t3w
m2e1o
2meo2u
2me1ö2
3mer.
me1r1a
me2r3a1p
me4re6n1s
m2eren
me1re
mer2er
4m3ergän
me4r1g
3merin
m2e1ri
me2r1i2n4d
me2r1i4n4t
3me4r1s
merz4en
me2r1z
3me1s
me2sal
me1sa
me4sä
me1s2e
4meser
2m4e3s1h
4m1es1sa
me4s1s
mes6s3e4r3g
mes1se
mes4s2i1g
mes1si
m2es2s1o
mes2s1p2
me3s2t2e2
me1st
me2st2r4
4mesu
3me2ß1
me3t2a
me1t
me3t2h
meu1
2m1ex
1mé
2m1f4
mfi4l
m1fi
4m1g2
2m1h4
1mi
mi2a1d
mi1a
mi3ak
mi1bi1
m2i1b
mi1c4h
m2ic
mi3da
mi1d
mie3d2r4
mie1d
mi2e1i
mie3l
mi2er
mie3r2er4
mi2e1re
mi2e1t
mie4ti
3m2i1g
mi2kar
m2i1k
mi1ka
mi2ki
mi2ku
3mil
mi3l2a
milc4h1
mi2l2c
mil4che
4mi2l1z
2m1i2m1p
mi3n2en
m2i1ne
min2e2u
m2in2ga
mi4n1g
min4g2s2
mi3ni
3m2i1n2o
mi1n1u
3mir.
mi3r2a
3mi1r2i
3mi4r1s
3mi2r1w
mi2sa
mi1s
mi4scha
mi1sc
misc4h
mi4s2c2hn4
mi4s2c2hw
m2i1se1
mis2s1c
mi4s1s
mis4s1t
mi2ß1
3mit1
mi2ta
mi2t1h
mi2t2r4
mi2t3s2
mit5sa
mi3ts1u
mi2tu
4m2i4t1z
2m1j
2m1k4
m3ka
m2k5re.
mk2r4
mk1re
2m1l2
m2l3c
m4l3l
m4l3s
2m1m
m2m1a1b
m1ma
m2m1ak
m2m1al
mm1a4n1g
m2m1a6n1s
m2m1a2n1z
m2m1a2u
m2m1d2
mm1ei
m1me
mm2ei1s3t
mmei1s
mme4lin
mm2e1li
mme4n1a
m4m1en4t3w
mme4n1t
mme2r1a2
mme4rec
mme1re
mme2s3a
m3me1s
mm1i2n3b4
m1mi
mm1i2n3f
mm1i2n1h2
mm1i6n1s
mm1i4n1t
mmi3sc
mmi1s
m2m3p
m2m1s2
m2mum
m1mu
m3m2un1
mm2ül2
m1mü
2m3n2
m4ne1si
m1ne
m3n2e1s
1mo
m2o3a3
2m1o4b1j
mo1b
3m2o1d
mode3s
mo1de
m2o2d2r4
2mog.
m2o1g
mo2gal
3moh
m2o2i3
mo2k1l2
m2o1k
2mol.
m2ol
3m2om
mo1m2e
3m2on
m2o3ne
mo4n1er
mo6n3s
3mo2o
2m1op2e
2m1o2p3t4
mo1r1a
mo2r1ar
2m1o2r1c
m2or2d3a
mo2r1d
m2or2d2r4
mo2r1er
m4o1re
mo2r1k4
3mo1s
moster4
mo1s2t
mo3st2e
mo2sto
3m2o1t
m1o2x
mo1y
1mö
m2ö2c
4mök
m1öl
2m1p
m2pf
mp4f3e4r1g
mp1fe
mpf3e2r3p4
mpf3e2r1r
mp4f3e2r1z
mp2f2l2
mpf3li
mp2f1or
mp1fo
m3pi
m3pon
m1p2o
mp3t1a
m2p1t
m3pu
2m1q
2m3r2
2m1s
m2s1an
m1sa
ms3a2n1d
ms1as
m3sä
msc4h2
m1sc
m2s1e1f
m1se
ms1e2r1f
ms1e2r1w
ms1i1n1i1
m1si
mso2r
ms1o1ri
m2s2pä
msp2
m2s1pe1d
msp2e
m1s2por
ms1p2o
m2sp2o1t
m2s1p4ro1
msp2r2
ms2pu
m4s3s2
m4s3t2a1g
m1st
m2stal
ms1um
m2sü
m3s2üc
4m1t
mt1a1b
m1t1ak
m3tam
mt1ar
mt3a1re
mt1e4l1t
m2t1e2r1f
m2t1e4r3g2
m2t1e4r1l
m2t1e4r1s
m2t1e4r1t
m2t1eta
mte1t
m2t1eu
m2t1h
mt3ho
m2t1im
m2t1i6n1s
mt2i2s
mtmen2
m2t1m2
mt1me
mt1ö1s
m4ts
mt2sa
mt2s1e
mt3s2ka
mt2s1k2
mts1p2
mt1sp2a
m4t1t2
m1t1um
mt1u4r1t2
m4t3z
1mu
m2u1a
2m3u1h
mu3la
mu1l
2mu4l1s
3mun1
m1un2d1a
mu2n1d
4m3u2n1f
4m3unge1b
mu4n1g
mun1ge
mu3ni
m4u2n1k
m1u2n1t2
4m2u2n1z
mu3ra
mu4r1u2f
mu3ru
mu4s1a
m2us
3mu1si
mu2s1o
mu2sp2
mu3s2t2e
mu1st
mu2s1to
mu2st2r4
mu2su
mu1ße3
m2uß
mu2t1a2u
m2u1t
mu2ts3
mut2st4
1mü
2m1üb
mül4len
m2ül
mü4l1l
mül1le
3mün
3m2üt
2m1v
mvo4l1l1
m3v2ol
2m1w2
mw2a2
mwa4r
1my
2m1z
mz2u1g4
1na
3na.
2n1a1b
na2b1ä
4n1a2b1g2
4n1a2b5h2
na2b1l2
na2b3r4
4n3a4b1s
4na4b1t
3n2a2c
nac4h1
na3chen
na1che
na4c4h3s
nacht6ra
na4c4h1t
nacht2r4
4n1a2d1d2
na1d
n2a1de
4na2d2r4
n1af
na1f4r2
3n2a1g
na2ge1m
na1ge
3n2ah
n2a2h1a
n3a2hn
3nai
nai2e2
n1a2i3g4
2n1ak
na2ka
3n4a1ko
n2al.
na2l1a2
na2lä
3n2a4l1d
n4a1le
na4le4n1t
nalen1
na2le4t
nal3l2a
na4l1l
nal1mo2
na2l1m
na2lop
n2a1lo
n1al2ph
na2l1p
n2als.
na4l1s
na4l3t4
na2lu
2n4a1ly
3na1m4e
n2ame2n1
4n1a2mer
na3m4n2
3na1mo
nam2sp2
n2a2m1s
2n1a4m1t
nam4t4s
n1an.
4n1a2na
4n1a2n3b4
n1a2n1d2
4n1a4n1g
2n1a2n1h2
2n2a3ni
4na2n1k
2n1a2n3l2
3n2a4n1n
na3no
n1a2n3p4
2n1a2n3r2
2n1a6n1s
2n3ant2r4
na2n1t
2n1a4n1w
nap2si
na1p
na2p1s
n1ar
5n2ar.
na2r1a
2n1a2r1c
n2a2r1d
4na4r1g
3n2a1ri
n2a2r1k
n2ar1l2e
na4r1l
2na4r1m
n2a2r1p2
4n3a4r1t
n2a3r2u
3nas
n2as.
na4s2c2hw
na1sc
nasc4h
4nas1p2
4n1a2s2y
n3a2sy1l2
3naß
3n2a1t
n4ata
na3t4h
4n3a2t1m2
na2ts1
nat4sa
nat4sc
4na4t1t
n1a2u
4nauf
nauf4f2r2
na2u2f1f4
n3a2u1g
5n4a2u1i
3n2au1l
4nau4s3b4
na2us
4n1au4s3g2
n2au2so
4nau4s1s2
n4au3st2e
nau1st
4nau2s1w
nau3te
na2u1t
navi5er.
n2a1v
n4a3vi
navi2er
navi5e4r1s
1nä
2nä1b
3n2äc
3n2ä1e
n1ä2hn
2n1ä2m
2n1än
2näp
nä2sc
n2ä4s1s
2näu
3n2ä1um
2n3b4
nb2ai1s4
nb2e2in
nbe3n
nbe3r2e
nb2u2s
n3by2
2n1c
n3ce2n3
n4c2h3m
nc4h
2n1d
nd2a1g
n2d1ak
n2d1a2n3l2
nd2a4n1n
n2d1a2n1z
nd2a1t2
n2d1a2u
n2d1c
nde4al.
n1de
nd2e1a
n2d1ei
nde4l1än
nd2e1lä
n4d3en4t1s
nde4n1t
nde4r1o2b
nde1ro
nde2s
nde1s1e
ndi2a3
n1di
n2d1o1b
n2do2b2e
nd1op
nd1or
n2d1ö
n2d3r2a1t
nd2r4
n2d3re
n2d3ro1b
nd3r2ol
nd3ro1s
n2d3rö
n2dr2ui
nd1ru
n4d3run1
nd2so2r
n2d1s
nd2sp2r2
ndsp2
nd4s1ta1b
nd1st4
nds3ta2u
nd3t1h
n2d1t
ndt4r4
n2dü4
ndy3
1ne
3ne.
ne2a1p
n2e1a
ne3a4s
ne3a1t
n2e2bl2
ne1b
2n1e4b3n2
2nec
3n2e1ca
3ne1d
ne2d2e
2n4ee3
ne2e2i4
ne3ein
n1e1f
ne1g4
2ne2he.
ne1he
2nehen2
3n2e2h1m
4n1e2hr
2n1ei
n2e2i1d
4ne2if
3n2ei2g1t
ne2i1g
4n3ei4n3g2
4n3ei2n3k
ne2ke
ne1k
n2e4k3t4
ne2l
3ne1la
ne2l3b
2n1e1le
4nele1k
4n1e2le1m
ne3len
n2e3li
nel4la
ne4l1l
nel2lä
3n2e3l2o
3n2e3lu
n2em.
ne1m
2n1e2m1b2
n1e2mi
2n3e2m1p
2n1e2m1s
3nen
n2en.
ne2n3a2
n2e2n3b4
n2e2n1c
4n1en2d1b4
ne2n1d
4n1en2d1d2
4n1en2d1f4
n1en2d1g2
4n1en2d1h2
4n1en2d1k4
4n1en2d3p2
4n1en2d1t
4n1en2d1w
ne2n1e2b
1ne1ne
ne2n3ei
3n2e3nen1
1ne4ne1ne
4nen2g3b2
ne4n1g
nen4ge.
nen1ge
nen4g2en
4nen4g2s
4nen2g1t
n2e2n1h2
ne2ni
n2en1j
ne2n3k
ne2no
n2e6n1s
nen1s4e
nen3s1k2
5n2en3t2a
ne4n1t
n1en4t3b2
4n1en2t3l2
4n1en2t5n4
5nent2r4
n1en4t1s
4n3en4t3w
4n1en4t3z2
ne2n3u
n2e2n1v2
n2e4n1w
ne2o2b1
n2eo
ne1o1s2
2ne3p2f4
ne1p
2n1e1p2o
ne2po1s
n2er.
ne1r1a
ne2r1a2b
ne3r4al
ne2r3am
ne2ran
ne2r2a1p
ne2ra2u
4ner3be.
nerb2e
ne2r1b
4nerben
n1er1bi
ne1re2
ne2r1e1b
n1e2r1f
4n5er1fo
ner3for4
2ner1fü
3nerg2r4
ne4r1g
n1e2r3h4
2n3e2r1h2ö
3n2e1ri
n1e2r1k
n2er3l2i
ne4r1l
2n1er1l2ö
n1er3mä
ne4r1m
ner4mit1
ner1mi
n2ern.
ne4rn
4n1er2n1t
ne1rö1s
ner1ö
n2e2r3p4
3n2ers.
ne4r1s
2n3er1s2a
ner8sch2le
ners2c4h2l2
ner1sc
nersc4h
n2ert.
ne4r1t
n1ert2r4
ne2rup
n2e1ru
n2e2r1v
2n1e2r1z
3n2e1s
n4es.
ne3s2c
ne2sei
ne1se
ne2s2e1v
ne3s1ka
ne2s1k2
nes1o
ne2sor
n2e2s1p2
4n3es1si
ne4s1s
ne2ta1d
ne1t
ne2t1ak
ne2t1an
n1e2ta1p
n1et4a1t
ne2ta2u
ne2t2h
ne2t3h2a
nett4sc
ne4t1t
net2ts
n1e2tu
net2zi
n2e4t1z
ne2u
ne2u1c
ne2u3g
2n1eu1p
neur2
n2e1w
2n1ex
3ne1z
1né
2n1f
nf1ak
n1fa
n1f2ä
n2f1f4
n3fi
nfi4le.
nfi1le
nf4l2
nf5lin
nf1li
n1f2o
nf4r2
nft2o
n4f1t
nf2t2s3
nft4st4
n2f1u
4n1g
n2g1ac
ng1a1d
n2g1a1k
n2g1a2m
n2g1a2n3d
ng2a2n1f
ng1a2n1z
n2g1äl
n2g3d4
n3ge1f4
n1ge
n2g1ein
n3g2en
nge3n2a
nge6n1s2
n3ger
nge4ram
nge1r1a
n4g3er3se
nge4r1s
ng6e1s
nge1s2t2
nge4zän
nge1z
ngezä2
n4g3g4
ng3hu
n2g1h
n2g1i2d
n3gläs
n2gl2i1c
ng1li
n2g1lo
n3g2l2oc
n2g1lö
n2g3m2
n2g1n
ng3n2e
ng1or
ng3r2a1t
ng2r4
n2g3r2oc
n4g2s
ng1s1c
ng4s3e4h
ng2s1e2
ngs3p2a
ngs1p4
ng3ts
n2g1t
n2gum
n1gu
2n1h2
n3han
n3har
n3ha2u
n3hä
n3he
nhe2r
n3hu
1ni
3ni1a
nib4l2
n2i1b
nibu2
nich8te4r1s
n2ic
nic4h
ni2c4h1t
n1i1d
3n2id.
ni2de
n2i3d2r4
n4ie
nie3b
ni1el
ni2e3l2a
n2i1e4n
ni3e1ne
ni1ero
nig2a
n2i1g
2n3i2g2el
ni1ge
nig3r4
ni2g1re
3n2i1k
ni2kal
ni1ka
ni2kar
ni3ker
n4ike
ni4k3i4n1g
nik1in
ni3k3l2
ni2k2r4
3n2il
n2i1m2o
4n1i2m1p
nin1
3n2in.
n2i1n4a
4n3i2n1d
2ni2n3f
3n2i4n1g4
4n1i2n1h2
ni2n1or
n2i1no
2n1i6n1s
n2ins.
4nin1se
4n1i4n1t
2n1i2n1v2
ni2o1b
n2i1o
ni3o3k4
ni3ol
n2ip
ni3r2a
3n2i1s
ni4s2c2hw
ni1sc
nisc4h
n2i2s1e
n2i2s1p2
ni3spi
ni4s5s2
ni2st2u
ni1st
ni3stu2n1
ni2s1u
2nit
ni1t1h
ni2ti
ni3t4r4
ni2t2s
ni3t1sc
nit4tec
ni4t1t
nitt4sa
nit2ts
ni3tu
n2i3v
3n2i1x
n1j
2n1k
n2k3a1d
n1ka
n2k1ak
n3k2al
n4k3a2l1g
nk2am
n2k1a6n1s
n2k1a2u4s
nka2u
n2k1äh
n1kä
n2k1är2
n4k3er1fa
nke2r1f
nk4e4r1g
n2k1i2n1h2
n2k1i6n3s
nk3len
nkl2
nk2le
nk3le1s
n2kl2ie
nk1li
nk2lo
nk2lu
nk3lu2n1
nk4na
nk2n2
n2k3ne
n2k1o4r1t
n1ko
nk2ö2f
n1kö
n2k1öl
n2k3ro
nk2r4
nk2sal
n2k1s
nk1sa
nks2ei
nk1se
nk3s2z
nk2tak
n4k1t
nk2t1an
nkt1it
nk4top
nk2t1ru
nkt2r4
2n3l2
2n1m4
nm2e6n2s
n1me
4n1n
nna2be
n1na
n2n1a1b
n2nada
nna1d
n4n1a4l1l
n2n1an
n2n1a2u
n3nä
n2n3d
nne4n3g
n1ne
n3nen
n4n1en4t1s
nne4n1t
nn2e2r3h4
nn2e2r1k
nne2r1ö4
n4n3er4w2a
nne2r1w
n2n1e2r2z
nne2s1e
n3n2e1s
n2n2ex
n2n3f
n4n1g4
n3ni
n2n1of
n1no
nn1o2r
nn3se
n6n1s
nn3s2p4
nn2t1h
n2n1t
n2n1uf2
n2n1u2n1f
nnu2n1
n2n1ur
1no
3no.
3nob1l2
no1b
no2bla
n2o3b2le
2n1o4b1s2
n2o1c
2no2d
n2o3d2r4
n1of
2n3o2fe
n3o1le
n2ol
no2le2u
n2on.
3n2o1p2a
3n2or.
nor2a
no2r2a1d
n2o1rak
n2o3r2al
2no2r1c
n2or2d5r4
no2r1d
3n2or1h4
3n2o4r1m
3n2o4r1s2
n1o4r1t
3n2os.
no1s
n2o3s1h
n2o2s3p2
n2o3st2e
no1s2t
nost2r4
2no2s2t3v
no3ta1b
n2o1t
no2tä
no4t3e1i
no2tel
n4o3t3h
no4th2a
no4t3hi
no2t3in
no2t1op
no2t2r4
3no1v
3n2o1w
2n1o2x
3n2o1z
2nö1d
2n1ö2f
4n1ö4l
2n3p4
npa2g4
np2a
n3p2s2y3
n2p1s
2n1q
2n3r2
nrä2u3s
nrä2u
nre3s1z
n1re
nre1s
nrö2s1
6n1s
n2s1a2d
n1sa
n2s1a4l1l
n2sa4n1g
ns1an
n2sa2n1t
n2sa2us
ns1a4u
n3s3a1v
n2s1än
n2s1ä2us
n1säu
ns2ca
n1sc
n6s1che1f
nsc4h
n4schro
nsc2h2r4
nsch7we2r1d2
ns2c2hw
ns1e1b
n1se
ns1e2d
nseh5e1re
n3s2eh
nse3he
nse3her
nsen4sp4
6n3s2e6n1s
ns1e4n1t
n2s1e1p
ns1e2r1f
ns1e4r3g
n2s1e2r3h4
n2s1e2r1k
n2s1er1ö
ns1e4r1s
n2s1e2r1w
n2s1e2r1z
n3se2t
n4s1eta
n3s1ex
nsfi4l
n6s1f4
ns1fi
n4sho2f
n2s1h
n2si2m1p
n1si
n2s1i1n1i1
nsi2te
n3s2it
nsi2t3r4
ns2kal
n2s1k2
ns1ka
n2s1op
n4s3ort.
nso4r1t
nsp4
n2s1p2a1t
nsp2a
n4s1p2e1ri
nsp2e
n2s1ph
n1s2pi
n2s1p2o
n1s3pon
n2sp4rä
nsp2r2
n4s3pr2i4e
nsp1ri
n4s1p4ro1
n4s3s2
n2s2t1ak
n1st
n4s1tat.
n3st2a1t
n4s3tat2e
ns2ta2u
n5s4te.
n3st2e
n4st3e2if
nste2i
n5s4tel
ns4tem.
nste1m
ns4t6en.
n4ste4n1t
ns2ter
ns4t4er.
nst4er1ö
ns4tes.
nste2s
nst5op1fe
ns2tor
n4s1trac
nst2r4
ns2tum
nst2u
nst2ü
ns2t1ü1b
n2s1ty
ns2um
n2s1u2n1
ns2u4n1g
ns2u2n3r2
n4s3zi
n2s1z
2n1t
n4t3a4b1s
nta1b
n3t2a3c
n3t2al
n2ta3m
n4t1anza2
nta2n1z
n2t2a2r1b
n2t1a2r1k
n2t2a4r1m
nt4a1t
n2t1äm
n2t1äu
n3te.
nte3a2u
n1t2e2a4
nte2b
nt1ebe
n1t4e1e
nte3g6
n2t1eh
n2te2i1g
n3t2en
nt4e1ne
nten6te.
nt4ente
2n1te4n1t
n3ter
nt4e4rn
nt4e4r1s
nt4e4r1t
n2t1e4s3s2
nte2s
n3te1t
nteu3
nt2e3v
nt2her
nt1h
n1th2e
n2t3ho
n3t2h2r2
n3t4hu
nti3k4l2
nt2i1k
n2t1i2n3f
n2t1i2n1h2
nti1ni1
nt2i6n1s
n3tit
n3t4le1m
n2t3l2
nt1l4e
ntmen2
n2t1m2
nt1me
nt1mo2
n3to
nto3m1e2
nt2om
nto6n2s1
n1ton
n2t3rec
nt2r4
nt1re
n2t3re2if
n3tre1p
nt4r2i1g
nt1ri
n3t4rop
n2t3rü
n4t1s
nt3sa
nt4s1a4u
nts2o
nts2p2
nt4s3par
ntsp2a
nt1s2t4
nt4s2to
3n4tu.
n1tum2
ntu2ra
ntu4re.
ntu1re
ntu4re1s
n4t3z2
1nu.
1n2u1a
nu3ar
nu3b4i1
n2u1b
1n2uc
1n2u1d
3n2u1e
nu2e4s
nuf2
nu2fe
1n2u1g
2n1u1h
1n2ui
nu3k4
n2um.
2n3u2m1b2
2n1u2m1f4
2n1u4m1g2
3n4u2m1m
2n1u2m3r2
2n1u2m1s
2n3u2m1z
nu2n1
2nu1na
1n2u4n1g
3nung.
n3ungl
2n1u1ni
2n1u2n1t
1n2uo
2nup
2nur
3n2u2s
nu3sc
nu3se
nu3s1l2
nu4s1t
1n2u2ß
1n2u1t
nu2t1a
nu3te
nu2t3r4
1n2u1u2
1n2u1x
1n2u1z
3nü.
2n1ü4b
nü2r1c
3nüs
1n2üt
2n1v2
n3ver1
n1ve2
4n1w
nwei4st
nwe2i
nwei1s
2nx
1ny.
1ny1h
2ny1mu
n1yo
1ny1r
1ny1s
1ny1w
2n1z
n2z1a1d
nza2
n2z1a4g
n2z1an
n2z1a2u
n2z1än
nzä2
n2zär
nz1ec
n4zen1s2e
nze6n1s
n4z1en4t3w
n3ze4n1t
n4z1en4t3z2
nz3erwe
nze2r1w
nzi2ga
n3z2i1g
n2z1i2n1h2
nz1i1ni
n2z1or
nz2öl
nzu4g2s
nz2u1g
n2z1u2r1k
n2z1w2a
nzw2
n2z1w2ä
n2zwö
n2z1w2u
ño1
2o3a2
o4a1bi
oa1b
o4ac
oa3che
oac4h
oa3chi
o4a1d
oa3de
oa4g
o4ah
o4a3i
o4a3ke
o2ak3l2
o4a3la
o4a3mi
o2ar
o2a3s
3o4a3s2e
oa4si
o4a1t
oa3t2e
o5a2u
o1b
o3b2al
2oban
o3bar
2o3b2ä
2o4b1b
ob2e
2o3be.
2o3b2e1a
ob3ein
2o3b4en
obe2n3d4
oben3se
obe6n1s
ober3in4
ob2e1ri
obe4r2i1s
2o3b2e1w
2o3b2i
obi2t
ob3i1te
1o4b1j
ob1l2
o2b3li
2o3b2lo
2o3bo
o2b3re
ob2r4
o3b1ri
o4b1s2
ob3s1h
ob3s1k2
ob2sta
ob1s2t
ob3s1z
2o3bu
2o3bü
2o3by2
2oc
o2c1c
o1ce
och1a
oc4h
o2cha2b
o1che
oche4b
o2ch1ec
o4ch1ei
oche2r4k
o2c4h3l2
o4c2h3m
och1o
oc1h3ö2
oc2h3r4
o2c4h1t2
och3te
o1chu
o2chu2f
o2c2h1w
o1ci
o4ck2er
o2c4k
ock3s1z
oc2k1s
o1c4l2
o3co
o1ç
o1d
o3d2a
od2d2r4
o2d1d2
o3de3b4
o1de
o3de2c
o3d2e1i
odein3
ode2n1
ode1ne2
o3dex
2o3di1a
o1di
o3dir
o3d2i5v
o2don
odo4s
2od2r4
o2d1re
o2d1t4
2o3du
2o1e
oe2b
o2ec
oe2d
oe2h
oe2l
oe2n1
o4e1s
o2e1t
o3et.
o3e2ts
oe2x
o1ë
2o1fa
o3f1ac
of1am
of1a2u
o2f1ei
o1fe
of2en
o3fer
of2f1a
o2f1f
of2f1in
of1fi
of2fi2r
1of3f2i1z
of2f5l2
of2fo
of2f3r2
of4f1s2
of2fu
2o1fi
of3l2
of1la
o1f4lä
of4lö
2o1fo
2o1f2r2
of3ra
of3rä
of4rü
of1s1a
o4f1s
of4sam
of2sp2e
ofsp2
of2s1p2r2
of2s1u
2o4f1t
of2tei
of3t1h
2o1g
o2g1a1b
oga3d
og1a1l2a
og1a4n1g
o2g1ei
o1ge
og2e2l1i
og2el
o3g1h
ogi2er
og2lo
o3g4n
og2o1i3
o4g2s2
og3sc
og3s1p4
o1ha
o1hä
o1he
o2h1ei1s
o2h1e4r1t2
o2h1e2r1z
o1hi
ohl1a
o4hl
oh3lec
oh1le
ohl1ei
oh3le1m
oh3len
oh3le1p
oh4l1e4r1g
oh3ler
oh4l3e2r3h4
oh4l1e2r1w
oh3lo
ohl1s2e
oh4l1s
oh2lu
3oh4n1g
o2hn
oh2ni
1oh2n1m4
oh2n1o
o1ho
oho2la
oh2ol
o2h1o2p
o2h3ö
ohr1a
o2hr
oh4rin
oh2ri
oh1ro
o4h3t
o1hu
o2h1w
2o1hy
2oi
o1i2d
o3ie
o1im
oim1mu4
oi2m1m
o1in
oi2r
o2i1sc
oi1s
o3i6s4ch.
oisc4h
o2i3se
o1i4s1m2
oi4s1s2
oi4st
2o1j
2o1k
oka2la
o1ka
ok2a1le4
3o2kel
ok2i2o
ok1lä
okl2
ok4n2
4ok2r4
ok2s1p2
o2k1s
o4k1t4
2ol
o1la
o2l1a1b
o2l1ak
ol2ar
ol1auf
ola2u
o1lä
ol4dam
o4l1d
ol4d3r4
ol4e3e
o1le
ol1ei1e
ol1ei1s
oler2
ol1ex
o1lé
ol2fa
o2l1f
ol2f2l2
olf2r2
ol2fra
ol2gl
o2l1g
ol2g2r4
o1l2i
ol2i3k4
oli3tu
ol2k2l2
o2l1k
olk3r4
ol2k1re
ol2la1d
o4l1l
ol2lak
ol2l3a6n1s
olla2n
ol2las
ol2l1a2u
ol2lä1d
ol4l1ec
ol1le
ol2l1ei
ol2l1el
oll5en2d1s
olle2n1d
ol4le2r1k2
oll5erwe
olle2r1w
o3lo
ol2of
olo1p2
ol1o4r1t
ol1s2t
o4l1s
ol2s2t2r4
o1lu
3o1ly
1olym
ol2z1a2
o2l1z
ol4z3e4rn
ol2zin
ol2z1w2
2om
o2m1a1b
o1ma
oma4ner
om2a1ne
om2a4n1w
om1a4r1t
o2m1a2u
o2me1b4
o1me
om2e3c
o2m1ei
o3m2ei1s
o2mel
o3m2en.
o2me1p
o2m2e1ru
om1e2r1z
o3m2e1s
omi2e1t1
o1mi
o2m1i2n1d
om1i4n1g
o2m1i4n1t
om3ma
o2m1m
om1o4r1g
o1mo
om3pf
o2m1p
o2m1s2
omtu3
o4m1t
o4m1u2n1t2
o1mu
o3mun1
o1my1
2o1na
o2n1a2b
o2n2a1e1
o3nal
on1a1p
o2n1a2r1b
on1ar
on2a2u
on3a2us
2o1nä
onbe3
o2n3b4
2o2n1c
onderer5
o2n1d
on1de
onde1re
2o1ne
o2n1e2i
o3ne2n1
one4n3g
on1e2r1b
o2n1e2r1d
on1e4r1g
on1er1ö
o3ne4t1t
one1t
o2n3f2
on3g2l
o4n1g
ong4r4
on4g3s
4o3ni
on2i3d
o4ni2k2r4
o3n2i1k
o4n1im
o3n3i4n1g4
onin1
o2n3k2
onli4n
o2n3l2
on1li
onl2o2c
on1lo
on3n2an
o4n1n
on1na
on3n2e
o1no1
o3no2d
o2noke
on2o1k
o2n1o2r1c
ono3s
on1s1a
o6n1s
onsa4g
on2s1e1b
on1se
onse2l
on4s1h
onsi2d
on1si
on2s3l2
ons1p4
on2st2h
on1st
on3t2a
o2n1t
on4t3e2n1d
on3t2en
ont3e2r1w
on3ter
ont2h
on2t3ri
ont2r4
o1nu
2onu3k4
o2n3v2
1ony
o2n3z
o1ñ
oo2k3l2
o2o1k
o1op
o1or
o2o2r3f
oo4s1k2
oo1s
oo2t2r4
o2o1t
2o1ö2
o1p2a
opa1b4
o2p3a1d
op3a4k1t
o1pak
o3pan
o1pec
op2e
o1pei1
o1pe4n
2o4pf.
op2f3a
op3f4ah
op4fe2r1d
op1fe
opf5er3de3
opf2l2
opf3la
op1f4lü
4o1ph2
o3phe
o1pi
opi5a4
opi3er.
o3pier
opi5ers.
opie4r1s
opin2
op5la1g
o1p2l4
o3p4la
o2p3le
op3li
2o3p2o
op4p3l4
o2p1p
op2p2r2
2o1p2r2
1op1si
o2p1s
op3s1z
1o2p3t4
o1q
2or.
or1a
or3a2b
2orak
2or2al
o2r3a2l1m
or4a4l3t
3or2am
or2a2n1d
o2r1a2n1h2
o2r3a2r1b
or1ar
o1r2as
or3a4t1t
o1r2a1t
o3rä
or1ä2n1d
or1ät
or2bar
o2r1b
orb2l2
o2r1c
2or1ca
or2ce
2orda
o2r1d
or2d1am
or2da2u
or4d3e4n3g
or3den
or1de
or2deu
or2d1ir
or1di
or2d1it
1or4d5n2
or2do
2ord2r4
2or2d1s
or2dum
or1du
2or2d1w
4o1re
ore4as
or2e1a
o2r1e2c4k
o2r1e1f
ore2h
o2r1e2i1g
o2r1ein
or1er
o2r1e2r1f
or1et2h
ore1t
2o2r1f
or2f1le
orf2l2
or3g4a
o4r1g
2orge1t
or1ge
or3g2h
2orgi1a
orgi1e
or2gl
or3g2l4e
or2g1n
2or1h4
2o3r2ic
o1ri
4orie.
or2ie
o4rie4n1t
or2i1en
o3rier
4oril
4orin1
2or2it
or1k2a
o2r1k
or2k3ar
or2k3s
2o4r1m
or4m1a6n1s
or1ma
or4me4n1t
or1me
or5ne.
o4rn
or3ne
or3n2o
2o1ro
or2o3n2a
or2on
2o1rö
2or1q
2o2r1r
orr4a
or3r1h4
2o4r1s2
or3s4a
orsch5li
ors2c4h2l2
or1sc
orsc4h
or3s1h
or3si
or3s1z
or2t1ak
o4r1t
or2t1an
or2t1a2u
or2t1är
or2te1f
ort3e2i1g
or4t3e4n1t
orte2n1
or4t3e1re
ort3e2r1f
or2t3e1v
or2th2e
ort1h
ort3i6n1s
or4t3o2f1f
or2t1or
or2tö
or4tra2u
ort2r4
or4t3rä2u
or1trä
ort3re
ort3r2ic
ort1ri
or2t1um
o3ru
o3r2uf
o4r3un1
o2r3ü
o2rya
o1ry
o1s
2o3s2a
os3a1d
os4an
o3sche
o1sc
osc4h
o2s2co
2o3se
o3s4e3e
o2s1ei
ose2n
o4s1en4t1s
ose4n1t
2o2s1h
o3s2hi
o3sho
2o1si
o3s1k2
o4s1ka
o4ski
2os2k4l2
2os2ko
os2lo
o2s1l2
2oso
2os1p2
os2p2e
os3pec
o3s2p2o
os2p2r2
os2sa
o4s1s
oss3a2n1d
oss1an
os2s2ä
os2se1g
os1se
os2sei
os4s3e2n4k
os4s3e2n1z
os4se2r1b
os2s3o
os4s2on
os2s3p2
os4s3t
o1s2t
o2st1a2b
o3s1t2al.
o4s2t1am
os2t3a4n1g
osta4s
ost1a2u
o4ste2r1d
o3st2e
oste1r3e
ost5er6we
oste2r1w
o2st3h
o2stin
os1t1o1b
o4s3ton.
os1ton
ost3ran
ost2r4
o2s1t3rä
ost3re
ost3r2o1t
o3s2t3uf
ost2u
2osu4
2o3s2y
o3s2ze
o2s1z
o2ß1el
o1ße
o2ß1e2n2k
o2ß1e2n1z
o2ß1e1re
o2ß1e2r1f
o2ß3t
2o1t
ot3a2go
o3t2a1g
o3ta2r1k
o2t1a2u
o1t3a2u1g
o1ta2u4s
o3t2a1x
o2te1b
o3t2e1i
o2t1ei4n
ote2l1a4
ot2e4lei
ote3le
ot4e1m3
ote2m1p2
o2t1e2r1w
4ot2h
o1t4h2e
ot5hel
o2t3hi
ot3ho1s
ot2ho
o2t2h2r2
o2t1i2m
ot2in
o4t3l2
o4t5li2
ot4ol
ot1opf
ot2or
oto2r1a
o3tra
ot2r4
o2t3re
o1t3rin
ot1ri
ot2sa
o2ts
ots1p2
ot2sp2a
ot1s2t4
ot2s3t1ri
otst2r4
ot4te2r1k
o4t1t
ot3ter
ot2t1h
ot2t3r4
o2u
o2u1b4
ou2ce4
o2uc
ou1f4l2
o2u1g2
ou2ge
ou3g1l
o3u1h
ou4le.
ou1l
ou1le
o3um
o3un2d1s
oun1
ou2n1d
ou4n1g5
oun4ge.
oun1ge
oun4g4s2
2our
our2i2e
ou1ri
our4ne.
ou4rn
our3ne
ou3s2i
o2us
ou1s2t
outu4
o2u1t
2ou1v4
2o1ü
o1v
2ovi
ovi3s2o3
ovi1s
2ovo
2o1w
o3wec
owe2r1
o3wi
o1x
ox2a
o1x2e
1o2xi1d
o2x3l2
o2xu
1o2xy
o1yo
2o1z
o3z2a2
oz2e
ozen4ta
o3ze4n1t
o3zi
ozon1
ó2r1d2
ö1b
öbe2la
ö3bel
öb2e4li
öb2l2
ö2b2le
ö2b3r4
2öc
ö1c4h
ö2c4h1l2
ö2c2h2r4
öch1s2t
ö4c4h1s
öch4st2r4
ö2c4h1t4
ö1d
ö1di3
ö1e
1öf
öf2f3l2
ö2f1f
öf3l2
öge6n2s1
ö1ge
ö3gen
ög3l
ög3r4
ö4g2s
ö1he
öh3l2e
ö4hl
öh3ri
ö2hr
ö4h2s
ö1hu
ö3ig.
ö2i1g
ö1ke
ö2ko
ök3r4
ö2k2s
3öl.
öl1a2
öl1ei
ö1le
öl1e1m
öl2f1ei
ö2l1f
öl1fe
ö2l1im
ö1li
öl1in
öl2k3l2
ö2l1k
öl3la
ö4l1l
öl2n1a4r
ö4ln
öl1na
ö1l1o2
ö4l1s2
öl3sa
öl3s1z
ö2l1u
öl2u4n1g
ö1lu2n1
ölz2w2
ö2l1z
ö2m2s
2ön
ö1n2e
ö3ni
önizi1
ön2i1z
ön1n2e
ö4n1n
ö1nu
öo1
ö1p2e
öpf3l2
ör3a2
ö2r1c
ör2d2r4
ö2r1d
ö2r3ec
ö1re
ö2r1ei
ö2r1e2l
ör2e4r1g
ö2r1e4r1l
ö3r2e2r1z
ör2f3l2
ö2r1f
ör2gl
ö4r1g
ö2r1im
ö1ri
ör2kl2
ö2r1k
örner2
ö4rn
ör3ne
ör1o2
ör1s2e
ö4r1s
ör3s2k2
ört2e
ö4r1t
ör2t2r4
ö1ru4
ö2r1u3ne
örun1
ö1s
ö2sa
ö2scha
ö1sc
ösc4h
ö4s4ch3ei
ö2s2c4hl2
ö2s4c2h3m
ö2s2c2hw
ö2s1ei
ö1se
ö2sp2
ös2s1c
ö4s1s
ös4st
ö1s2t
ö2sta
ö1ß
2ö1t
ö2t3a
öte4n3
öt2h
öt2sc
ö2ts
öt2t2r4
ö4t1t
ö1v
ö1w
ö1z
öze3
öze2s4
p2a
1pa.
1p2aa
1pac
pa3da
pa1d
pa2d2r4
pa1f4r2
pa1g4
pa3g1h
pa1ho
1pak
p2a1k4l2
pak2to
pa4k1t
3pa1la
p2al2a3t
1pa1lä
p2a3li
2pa4l1t
pa2n1ar
pa1na
pa2n3d
pan4d1s
pa3n1ei
p2a1ne
pa2ne2u
pa2n3k4
2p1a2n3l2
2p2a4n1n
1pa2no
pan3s1l2
pa6n1s
pa2n1t2
pa2n1z4
1pa1p
papi2
papi2eren8
pa3pier
papi2e1re
papie8r7e2n1d
3pa1ra
pa2r3af
par3a4k1t
1p1a2r1c
pa5re1g
p2a1re
pa5re1k
2par2er
2pa4r1g
parge4l6d
par1ge
parg2el
1park.
pa2r1k
par4k2am
par1ka
par4ka2u
par2kl2
par2k2r4
1p2a1ro
2pa2r1p2
1par2t5n4
pa4r1t
1par1ty
pa2r3z2
pa1s2p2
pa2ßu2
1p2a1t
pa2t4c
pat4e2
p4at4r4
1pa2u
p3auf
pa3u1ni
p4aun1
1pä
3päc
3pä1d
3pär
3päs
pä4t1e2h
pä3te
pä4t3e4n1t
päte2n
pä2t3h
pä2to
pä2t3s
2p1b
pbe1
2p3c
2p1d2
pda2
p2e
1pe.
p2e2a
pea4r
1pe1d
pe2en
p4ee
pe1f4
pei1
2pe2ic
pe1im
pek2t4s2
pe1k
p2e4k1t
2pek2u
3pel
pe2l1a4
pe4l3d
pe2le1t
pe1le
pe2le2x
pe3li4n
p2e1li
pe4l3i2n1k
pel3l4e
pe4l1l
pel3li
1pe1m
pe2n1a4
pe3n2al
pen3da
pe2n1d
p2e4nen
pe1ne
1pe4n1n
pe2n1o
pe6n1s2
3pen1si
1pensu
pe2n3z2
1pe1p
pe1r1a
per2an
1pe4r1l
per4na
pe4rn
3pero
per2ra
pe2r1r
perr3an
per4rä
per6rie1g
per1ri
perr2ie
3pe4r1s
perw2a4
pe2r1w
pe3sa
pe1s
pe4s3s2
3pe1t
1pé
4pf.
p2f1a1b4
p1fa
p2fa1d
p2faf
pf1ai
p2f1ak
pf1a6n3s
p2f2a4r
pf3a1re
p2f1a2u
4p3fe.
p1fe
p2fei
pf1eim
pf1ein
p3fen.
p2f1e4n1t
p3f2er.
pf2e2r1w
p3f2e1s
p2f1f4
p2f1i6n1s
p1fi
p2f3lä
pf2l2
pf3lei
pf1le
pf3l2ie
pf1li
pf3lo
pf3lu
p2for
p1fo
pf3r2
pf1ra
p4f1s2
pf3s1l2
pf3s1z
p4f3t
2p1g
pgra2
pg2r4
1ph
4ph.
ph2a
2phä
2p2h3b2
4p2h3d4
2p1hei
phe2n3d2
phe6n3s
2ph1e4r1s
2p2h3f4
4p2h3g2
phi2ka
ph2i1k
4p2h1k4
p4h2l
2p2h1m
2p2hn
p3hop
2p1h2ö
p2h4r
2p4h1s
p4h3t2
2ph1th2e
ph2t1h
ph2u4s
2p1h2ü
2ph1z
pi2a3
pi3as.
p2ias
pi3c4h3l2
p2ic
pic4h
p4i1d
piegel4e2i8en
pie1g
pie3ge
pieg2el
piege3le
pieg2elei
piegelei1e
pi2el
pi2e2l1a2
3pier
3p2i1k
1pil
pi3le
pil4zer
pi2l1z
p2i1n2e
pin3g2en4
pi4n1g
pin1ge
pin4g3s
3pin1se
pi6n1s
p2i2o
pi3oi
pi3o1nu
pion2
3pip
pi2p2e
pi4pel
pi3r2i
3pirin
3pi1s
4piso
pi1s2t
pi3t2a
pi2t2s
2p2i4t1z
pi2z1in
p2i1z
p1j
2p1k2
pku2
pkur1
1p2l4
2pl.
3p4la
p5la1d
pla4n3g
3plä
2p3le.
p1le
p2le1c
p4le1g
p4le1m
3ple5n4
2p3l2i1g
p1li
p4l2i1k
p4l2i1z
p4lo
2p3lu
2p1m2
p1ma1
2p1n
1p2o
po3b4
p2o1c
3po1d
2p3oh
p2o2i
po3i2d
3po1in
3p2o1k
3p4ol
po2la2u
po1la
po3l2i
po4lor
po3lo
2po2n1d
2po4n1n
po1o2b
po2p3ak
po1p2a
po2p3ar
po1p2e
po2p2l4
p1o3p3t4
p2o1r2al
por1a
po1ra2u
2po4rn
por4tin
po4r1t
por4t3re
port2r4
por4t1ri
p2o3s2e
po1s
po1s4t
po2sta
po2s3t3a1g
po2stä
po4st3e2i
po3st2e
post3ra
post2r4
po3ta
p2o1t
3pote
po2t1u
p2o2w
po3x
pö2b2l2
pö1b
p2ö2c
2p1p
p2p3a2b
pp2a
p2p3a2n3l2
ppe4ler
pp2e
p3pel
ppe1le
ppe2n1
p2p1f4
p2p1h
p3p2ho
p1p3l4
pp5lan
p3p4la
p3p1lä
p2p1le
p2p3ra
pp2r2
p2p3re
p2p1ri
pp3sa
p2p1s
p2p1t2
p2r2
1prak
1pr2a1x
p4rä
1p3r2ä1d
1p4rä1g
3p2räm
3präs
2p3re.
p1re
2prec
1pre1d
pr4e2e1
1prei
3prei1s
2p3rer
3p4re1s
1p2re2ß1
pr2i4e
p1ri
2p3r2i1g
1pri2n1z
1p4ro1
3pro1b
2pr2oc
3pro1d
3pr2o1g
3pr2o1j
2pro4s1s
pro1s
3pr2o1t
1prüf
p1rü
2prün
2p1s
4ps.
ps4an
p1sa
p3se
p3s2h
ps1i1d
p1si
p2sö
ps2p2o
psp2
p3s2t2e
p1st
p2st3r4
p2st2u
3p2s2y
ps2ze
p2s1z
2p1t
pt1a
pt2a1b
pt3a2l1b
pt3a1t
p3te
p4t3ec
p4t1ei
pte4l
p4te3le
p4t1e4n1t
pt3erei
pte1re
p4t1e2r1w
p4t1e2r1z
p2t1h
pt1in1
pto3m1e2
pt2om
p4to1s
pt2o2w
pt1p2o4
p4t3p2
p2t3r4
p2t1s2
p4t1t2
p1t1um
p3tu4n1g
ptun1
pt1u4r3s
p2tü4
3p2ty
p4t3z
1pu
p2u1a
p2u1b4
2p2uc
pu2d2r4
p2u1d
2p1u1h
pul2sp2
pu1l
pu4l1s
2pu2n1d
pun1
pu6n2s2
2p1u2n1t
2pur
3p2u1t
pu2t2s
1püf
2p2ül
pün2
2p1v
2p1w
pwa4r
pw2a
3py1
py3t2
2p1z
qu4
1queu
q2u1e
qui3s
q2ui
1ra.
ra2a1b
r2aa
2r3aa1c
r3aal
r4a3ar
r1a1b
ra2bar
r2aba
rab2bl2
ra4b1b
2r1a2b3d4
2r1a2b5f4
2r1a2b1g2
1r4a1bi
ra2b3r4
2ra4b1s
2ra4b1t
2r3a2b1w
1r2a3by1
ra1ce
2r1a1ce1t
ra4che1b
ra1che
rac4h
ra4chi2n
ra1chi
racht3r4
ra4c4h1t
rach6trä
ra2chu
r2a2c4k
1r2a1d
r4ad.
ra2da2m
2r3a4d1a1p
3r2a2d1f4
3ra4d1l2
r3a2d3r4
ra2d3t1
1r2a1e1
r2af
ra3f3ar
ra1f1a
ra2fer
r2a1fe
ra3ge
ra1g
ra3g2l4e
r2a2gl
ra2g2n
3r2a2h1m
4ra4h1t
r2ai
2ra2ic
rai4l4l
2r3air
1r4a1ke
3r2a1k4l2
ra2k1re
r2a1k2r4
r3a2kro3
2rakti
ra4k1t
3r4a3kü
r2al
r4al.
ra2la4
ral3a1b
r3a2lar
ra2l3b
3r4a4l1d
r2a3le
2ra2l1g
r4a1li
rali5er.
ral2ie
rali5e4r1s
ra2l1k2
ral3la
ra4l1l
ral1l2e
2ral2l1g4
2r3alm.
ra2l1m
r3alp.
ra2l1p
2ralp2e
r4a4l1s
r3a4l3t
r4al2t2h
ra2lu
3r4a1ly
r2a1m4e
ra2mer
1r2a1mi
r2a2m1m
ram4man
ram1ma
ram6m5e4r1s
ram1me
ram4m3u
ram2p3l4
ra2m1p
2r1a4m1t
ram4t4s
r2an.
4ra2n1c
r4anda
ra2n1d
r4an1de
ran4de1p
ran4d3er
4r3a2n1ei
r2a1ne
r4aner
2r1a2n1f
1rangi
ra4n1g
ran4i1e
r2a3ni
ran2k2r4
ra2n1k
2r1a2n3l2
2r1a2n1m4
2r1a2n3p4
2r1a2n3r2
r2ans.
ra6n1s
r2ansp4
ran4s1p2a
2r3ant2r4
ra2n1t
2r3a4n1w
r2a1p
2ra2pf
r1ar
r2a1ra
2r1a2r1b
3rarei
r2a1re
r2a2r3f4
ra2r1in
r2a1ri
r2a2r1k
r2a2r1p2
2r3a2r1z
r2as
r4as.
ra1s4a
ra4s2c4hl2
ra1sc
rasc4h
2r3as2ph
ras1p2
ra1s2t
2raß
1r2a1t
r4at.
ra2t1a
ra3ta.
ra3t2e
r3a2t3l2
r4at4r4
rat2st4
ra2ts
2r3at3ta
ra4t1t
4r2au.
ra2u
3raub.
r2a2u1b
4ra2u1d
rau3e2n1
r2a2u1e
2rauf
2r2a2u1g
3r4aum
rau4m3a1g
rau1ma
rau4m2an
rau2mi
3r2au1sc
ra2us
2r1au4s3g2
rau2sp2
2rau4s5s2
rau4sti
rau1st
raus3t2r4
4ra2u1t
r2au2t5s
1r2a1ü
r2a1x
rax2i4s1
rä4c4h4s
räc4h
3r2ä1d
4räf
4rä1g
2räh
2räm
3r2än.
3r2ä3ni
3r2ä6n1s
2r1är
r2är.
rä3r3a2
rä2s1c
rä2st2
3rät1se
rä2ts
rä2u
rä2u2s
räu5sche
räu1sc
räusc4h
4rä2u1t
2r1b
r2b1a1b
r2b1a2de
rba1d
r2bak
rba1l3a
r3bal
rb2a3re
rb1a4r1t
rb1auf
rba2u
r4b1b2
rb1e1c4h
r1bec
r4b2e1lä
r3bel
rb1e4n1t
rbe3r2e
r3b2la
rbl2
rb3la2d
r8b3lasser
rblas3s2e
rbla4s1s
r4b3la1st
r3blä
r2b3le.
rb2le
rb3ler
rb2lin
rb1li
rb2lö
rb2o
rb4ri
rb2r4
r4b2s
rb3se
rb4sei
rb3s1ka
rb2s1k2
rbs1o
rb3sp2
rb4stä
rb1s2t
rb3st2r4
rb2u
2rc
r1ce
r1che.
rc4h
r1chen
r1chi
r2c4h3l2
r4c2h3m
rc2h3r4
r4c4h1s2
rch3sp2
rch3st4r4
rch1st
rch3t1a
r2c4h1t
rch6te2r1w
r2c2h1w
r1ci
r1c4l2
r1ç
2r1d
r3d2ac
r2d1af
r2d1ak
r2d1al
rd2a3ni1
rd1a2n1t
rd1a2n1z
r4d1a1p
r2d1ei
r1de
rd2ei.
r2d1e2l1b
r3den
rde2n3d2
rden4gl
rde4n3g
rde3re
rder4er3
rde2r1i6n6s
rd2e1ri
r4d3er2n1t
rde4rn
rd2e3s1p2
rde1s
rdi3a2
r1di
rd2ia4l
r2d1i4n1n
rd1it
r2do2b2e
rd1o1b
r3don
rd1o1s
rdo4s2t1
r2d1ö
rd3r2a1t
rd2r4
rd4ri
r2d1t4
rd3ta
rd3t1h
rdw2a4
r2d1w
1re
3re.
re3aler
r2e1a
re2a1le
re2a4l1t
re2am
re3as
re3at.
re2a1t
re3a2t3s2
2re1ä4
r2e2b1a
re1b
r2e2b1l2
r2eb2r4
reb3ra
re2bü
r2ec4h
rech3ar
4re4c4h1s
2reck.
re2c4k
2recki
3red.
re1d
4re2d1d2
2redit
re1di
re1el
r4ee
re1er
3r2e1fe
re1f
4re2f1f
3r2ef2l2
3r2e3f2o
3re1g
5reg.
reg2e4l3ä
re3ge
reg2el
re2hac
r2e1ha
re4h3e4n2t3
re1he
re2h1i
re4hl4
re2h1o
r2ei.
rei4bl2
r4e2i1b
r2ei1e
2re2i1g
3r2eige1w
rei1ge
rei3l2a
re4il
rei3l2i
re1i2m2p
r1ein
rei3nec
re2i1ne
4rei4n3g2
r3ei2n3k
4re2i2n3r2
1rein8s7t1re
rei6n1s
rein1st
reinst2r4
re1i2n2v2
reis2ter6
rei1s
rei1st
rei3st2e
reis5tro
rei2st2r4
3re1k
4re2ke
re3la
2r1e2l1b
re1l2e
rel2e1a4
r2e3lei
2re2le1k
2r1e2l1f
r2e3lo
2r1e4l1t
r2e1lu2
r4em.
re1m
r2e1mi
4r3em2pf
re2m1p
4re1mu
r4en.
r2e2n1a
re2n1a2b
re3nal
re2nä
3ren1di
re2n1d
ren3d2r4
re4n3e2n1d
r2e3nen
re1ne
ren4gl
re4n1g
2r1en2g1p2
re2ni
ren4n2e1s
re4n1n
ren1ne
r1en1se
re6n1s
2r1en2t3l2
re4n1t
2r1en4t1s
2r1en4t3w
4r3en4t3z2
r2e2n1z
re3or
r2eo
3rep2e
re1p
3re1p2o
4re2p1p
3r4er.
2r1e2r1b
r2er3b2r4
2r1e2r1d
r2erer
re1re
r1e2r1f
r1e4r1g
r4er3gen
rer1ge
r1e2r1k
4r3erken
r2erki
r1e4r1l
4r3erla2u
rer3l2a
2rer1l2ö
2r1e4r1m
re4r2n
2r1er1nä
4r3er6n1s
4r3er2n1t
r2e1ro
re2r1o2b
r1er1ö
3r2ers.
re4r1s
2r1er1s2a
r2er3se
2rer3s2p4
r1e4r1t
r2erte
2rert2r4
2r1e2r1z
rer5ze
r2erzy
3r4es.
re1s
re2sa
res3an
3re1se
3reso
2re4s1s
res4s2aa
res1sa
res1s2e
res6s5e2r1w
3re1st
res3te1m
re3s2t2e
re2s2t2u
3resu
2re2ß1
re2t2hy
re1t
ret2h
re2u
re2u3g2
2reu1l
re3u1ni
r2eun1
2r1eur
2re3ü
2r3evi1d
r2e1v
r1e1w
rewa4r
r2ew2a
re2wi
2r3e2x1
3re1z
4re3zi
1ré
2r1f
rfal4l4s
r1fa
rf2al
rfa4l1l
rf1ä4l1t
r1fä
rf2äu
r2f1e4n1t
r1fe
rf2e1s
rfi4le.
r1fi
rfi1le
rf3l2i1c
rf2l2
rf1li
rf3lin
rf4lö
r3f4lü
r3for
r1fo
rf4ru
rf2r2
rf4rü
rf2sa
r4f1s
rf2s1ä
rf2s1i2d
rf1si
rf2s3p2r2
rfsp2
rf2ta
r4f1t
rf3t4r4
r1f2u
4r1g
r2g1a2d
r2g1ah
r2g1a1k
rg2an
rga1s2t
rga2s
rg2a4st2r4
rge4an
r1ge
rg2e3a2
rg2e2bl2
rge1b
rge4l3er
rg2el
rge3le
rgen4z3w2
r3gen
rge2n1z
rge4r2al
rge1r1a
r2g1e4ta1p
rge3t2a
rge1t
r2g2e3to
r2g3i4sel
rgi1s
rg2i1se
r2glan
rgl2a
rglei4c4h8s7
rg2l4e
rgl2e2ic
rgleic4h
r2gle2u
r2g3l2i1g
rg1li
r2g2no1
rg1n
r2g1o1b
r2g3r2al
rg2r4
r2g3re1g
rg1re
r2gre1s
r2g3re1t
rg3rin
rg1ri
rg1s1p4
r4gs
rgs2t2r4
rg1st
r1h4
2rh.
2rha
r2ha.
2rhä
3r4he.
3r4hen
r3her
r2h2o1e4
r2h2o2i3
2rh2ol
2r1h2ö
2r4h1s
1ri
ri3am4
ri1a
ri3a1t
rib2bl2
r2i1b
ri4b1b
ri1ce
r2ic
ri1ch1a
ric4h
ri1d2
ri2d2an
2ri2d2ol
r2ie
rieb4s3t
rie1b
rie4b2s
rie2f2r2
rie1f
ri1el
ri2e1ne4
r2i1en
rie2n1u
ri1er.
ri4e1re
ri3e4sti
rie1s
rie1st
ri1eu
ri2f1a
r2if
ri2f1e2i
ri1fe
ri2fer
ri2f1o
ri2f3r2
rif4ter
ri4f1t
3r2i1g
5rig.
ri4ge1ne
ri1ge
ri3gen
5ri2g1j
ri2g1l
4rig2r4
ri2k3l2
r2i1k
ri4kla
r2i2m1b2
2ri2m1p
ri2m2s
r2i3na
2r1i2n1d
r1in4dex
rin1de
rin4d2i1z2
rin1di
r2i3n4e
ri2n3e1i
2r1i2n3f
rin2f2o
rin2g3l
ri4n1g
rin2g2r4
2r1i2n1h2
2ri2nit
ri1ni
2ri2n1k
3ri4n1n
6r5inne2n1m4
rin1ne
rin3nen
4r3inner
4r1innta
rin2n1t
r1innu
2r1i6n1s
3r4ins.
rin2so
rin2sp4
r4in1s2pi
2ri4n1t
r1in4te3g6
rin4t5r4
2r1i2n1v2
4r1ir
r2i1s
ri1s4a
ri4scho
ri1sc
risc4h
ri4s2c2hw
3ris2i1k
ri1si
ris3mu2
ri4s1m2
ri3so
r2i2s1p2
3ri4s1s
ris3sa
ris4s1an
ris2t5e4r1s
ris2ter
ri1st
ri3st2e
riste2s4
ri6ste4s3s2
ri2ß1
r2it
r3i2tal
ri1ta
ri3t2i
ri1t4r4
rit2t2r4
ri4t1t
5ri1tu
r2i1x1
1rí
2r1j
2r1k
rk2am
r1ka
r2k1äh
r1kä
r3kla2u
rkl2
r2kli1s
rk1li
rk4lo
rk2lu
rk4n2
r2k5nu
rk3rä2u
rk2r4
r2k3r2e1a
rk1re
r3k1ri
rk3rin
rk2s1e
r2k1s
rk2sp2
rkst4a3ti6
rk1st4
rk3st2a1t4
rk4stec
rk3st2e
rk4s1ti
rk2ta
r4k1t
rk4t3e4n3g
rk4t3e2r1f
rkt3e4r1s
rk6ter1sc
rk4t3e2r1w
rk2tin
rk2t1o2
rk2t3r4
rk3tra
rk1u1h3
rk2um
rku2n1
rk1u1ni
rku4s1t
rk2us
4r1l
r3l2a
r1l2e
rl2e2a
r3lec
rle2i
rle2st
rle1s
r3le1t
r3l2i
r3l2o
r1l2ö
rlö4s5s
rlö1s
rl2s1p2
r4l1s
rl2s1to
rl1st
r4l3t
rlu4st2r4
rlu1s2t
rl2us
4r1m
r3m2a1g
r1ma
rma2la
r2m1a4l3d
r2m1a2n1z
rm1a2p
r2ma1ph
rm2är
r3mä
r2m3d2
r3me.
r1me
r2m1e1f
r2m2e1o
r3m2e1s
r2mi1de
r1mi
rmi1d
r2m1im
r2m1o2ri
r1mo
rm3sa
r2m1s
rm1s2t
rm3sta
rmt2a
r4m1t
r1m2u
rm3u2m1s
4rn
r2n1a2b
r1na
rna4n
rn2a2n1d2
r2n3a3ni
r2n1a2n1z
rn2a2r
rn3a1re
r3n3a1ri
r2n1a2u
r3näp
r1nä
rn3d4r4
r2n1d
r3ne
rn3e4b2en
rne1b
r4n1e1f
r2n2ei
r4n3e2if
r4n3ei1s
r3ne2n
r4n1e1ne
rn3en1s4e
rn2e6n1s
r4n1e2r1f
r4n1e4r1g
rn4erhi
rn1e2r3h4
r4n1e2r1k
r4n1e4r1t
r5n2e1s
rn2e1t
r4n1ex
r2n3f
r4n1g2
r3ni
r4n1in1
r3no2d
r1no
r2n1op
r2n1or
rn1ö
r1n2ö1t
rn3s2ä
r6n1s
rn3s2p4
rn3s2z
rn3t2e
r2n1t
r1nu
r2n1ur
r1nü
r1ny
ro2bei
ro1b
rob2e
2r1o4b1j
1r2o3bo
2ro4b1s2
ro1c4h
r2oc
3rock.
ro2c4k
r2o3de
ro1d
r2o3e4
ro4h1l
roh3na
ro2hn
3r2o2hr
3r2oi
ro3le
r2ol
rol4la2n
ro4l1l
rol3l4en
rol1le
2r3o1ly
4rom.
r2om
ro2ma1d
ro1ma
ro2mer
ro1me
4ro2m1m
4ro4m1t
r2on
ro4n1e2r1b
r2o1ne
3ro4n1n
ro6n1s2
ron4tan
ron3t2a
ro2n1t
4r1o1ny
ro1p2e
2ro2pf
r4o3ph2
r1or
r2or1a
r2or3al
ro2r2a1t
ro2rei
r4o1re
r2o2r1o
ror3t1h
ro4r1t
r2o3s1h
ro1s
r2o3s2i
ro3s1mo
ro2s1m2
ros2s1c
ro4s1s
ros4s2ie
ros1si
ro1s4t
ro3sta
ro2st2r4
ro2ßu2
ro4t2a1g
r2o1t
ro3t2e3i
ro2t2ho
r4ot2h
ro2t1ri
rot2r4
ro2t1s2
rot2ta
ro4t1t
ro3t2u
ro3u2n1t
ro2u
roun1
3ro2u1t
rö2b3l2
rö1b
rö2du
rö1d
2r1öf
4rög
1r2öh
r1ök
1r2öl
3rö1mi
4röp
r1ör
r2ös.
rö1s
r2ö1se
2r1p2
r3p4a
rp4e
rpe2re
rpe4r3in
rp2e1ri
rpf4
r2p1li
r1p2l4
r3p2o
rpo4st2r4
rpo1s4t
rpo1s
rp1s1t
r2p1s
r2p3t
r3pu
r1q
2r1r
rr2a1b
rr2ar
r2r1äm
r2r1b2
r2r1c
r3r2e
rre4a1le
rr2e1a
rre4r4s
r4r1e1w
rr2he
rr1h4
rr2i1k2
r1ri
rr2n3a
r4rn
rr2o
r2r3o1b
rr2o3m2
rr2t1h
r4r1t
r3ru
r3r2ü
r2r1ü1b
4r1s
r2s3a1b
r1sa
r2s1a2d
r4s1a2m1p
r3sam
r4s1a4m1t
rs2an
r2s3a4n1g
r2s3a2n3p4
r2s3ar
r3sch2e
r1sc
rsc4h
r6sche4r1l
rs2c4h2l2
rs1ebe
r1se
rse1b
r2s1ein
rse2n1
rs2e2n1d
rse4ne
rs1e1re
rs1er1ö
4r1s1e4r1s
rs1e2r1z
rs1eta
r3se1t
r3sho
r2s1h
rs2kal
r2s1k2
rs1ka
rs2kan
rs2kie
rs2ki1s
rs2k4l2
r4s1ko
r4sk2r4
r4sku
r2s3l2
rs4no
r2s3n4
r2s1op
r4s3ort.
rso4r1t
rs2p4
r2s3ph
r4s3s2
r5s2ta1d
r1st
r4sta2n1t
rs2ta2u
r6st5ei4n3g2
r3st2e
rste2i
rs2t1ein
r6s4ter4b1t
rste2r1b
r4st3e2r4w
r2s2t1h
rst3i4n1g
r2s1tip
r2s1t2o1t
rs2t2r4
rst3ran
r6stra4n1g
rs2t2u
r3swi
r2s1w
4r1t
r4t1a4b1s
rta1b
r2t1a2l1m
rta4l1s1
r2t1am
r2t1a4n1g
rt2a4n1n
rt1a2n1t
rt1a2n1z
r2t1ar
rt3a4re
r2t3a4t1t
rt2a1t
rt1är
r3te.
r1t4e1e2
rt4e2if
rtei3la
r1te4il
r2te2l1f
rte2n1
r3t6en.
rt3erei
rte1re
r4ter1fa
rte2r1f
r4ter1fo
r4t3e2r3h4
r2t1e2r1k
r4t3er4l2a
rte4r1l
r4t3er1l2e
r4t3er1nä
rte4rn
rter4r2e
rte2r1r
rt1e4r1s
r3te2s2
rte3s1k2
r2thi
rt1h
rt3h2ol
rt2ho
rt2hum
r2t1i1d
r2t1i1ma
r2t1i2n3f
rto1p
rt1or
rto2ri
r2t3rak
rt2r4
r2t3rec
rt1re
r4t3rei1s
rt3ro1s
r4ts
rt4s3eh
rt1se
rt1sp2e
rtsp2
r4t1t4
4r2t1u4r1t2
r4t3z
1ru
r2u1a
ru3a2r3
rube2
r2u1b
rud2e2a
r2u1d
ru1de
ru2d2r4
3ruf
ru2fa
ru4f2s1
4r2u1g
2r1u2hr
ru1h
3ruin
r2ui
ru1i6n1s
ru1i1s
2rum
4r1u2m1f4
ru2mi
4r1u2m3l2
r2ums.
ru2m1s
4r1u2m1z
2r1u1na
run1
2ru2n1d
r1un2d1a
r2un1de
rund3er
run6de4r1l
run6de4r1s
run6de2r1w
2r1u2n1f
2rungl
ru4n1g
2r1u2ni
4r3un2i1o
run2k2r4
r2u2n1k
2r1u2n3l2
2r1u2n1m4
4ru4n1n
4r3u2n1t
2r1u4n1w
ru3p2r2
4r3ur
ru2ra
ru2r1e
5r4u1ro
ru2si
r2us
rus2s1p2
ru4s1s
rus6s3t
3r2u1t
ru2tei
rut3h
ru2t1o2
ru2t3r4
4r2u1z
ru2zw2
1rü
2r1üb
rü1ben
rübe2
rü1c4h
r2üc
rüc2k1s2
rü2c4k
4rü2m1m
2r1v
rve4n1e
r1ve2
rve5s
r2v2s
2r1w
rwu6n3s2
rw2u
rwun1
4r1x
1ry
ry2c2
2r1z
rz1ac
rza2
rz2an
r2zar
r2z1as
r5ze1ne
rz1e4n1g
r4z3en4t3s
r3ze4n1t
r2z1e2r1f
r2z1e4r1g
r2z1e2r1k2
r2z1e2r1w
rz1i1d
r3z2of
rz1op
rz2ö
rz3te
r2z1t
rz2t1h
rz2t3ro
rzt2r4
rzu1g2u
rz2u1g
r3zw2ä
rzw2
r3z2wec
1sa
3sa.
3s2aa
2s1a1b
sa2be
3s2a3be1t
sa2b1l2
s2a3b2le
sa2b3r4
4sa4b1s
sa2cho2
sac4h
sa4c4h3t
2s1ada
sa1d
s1a2d3m2
2s1a2d2r4
s2a2fe
2s3a2f1f
3s2a1fi
sa1f4r2
sa4ge4n1t
sa1g
sa1ge
sa3gen
sag4n
s1a2g2r4
3s2ai
sa3i2k1
sail2
2s1ak
sa2ka
3s2a1ki
3s2a1k2r4
4s3a4k1t
3sal.
sa4l3e2r1b4
s2a1le
sa2l1i1d
s2a1li
s1a4l1l
3s2a1lo
sal2se
sa4l1s
2s1a4l1t
3s2a2l1z
3sam
s3a2m2e1ri
sa1m4e
4s1am1ma
sa2m1m
4s1a2m3n2
s1a2m1p
sam2to
sa4m1t
s1an
s2an.
2s3a2na
2s3a2n3b4
s2a2n2c
s2a2n1d
s4and.
san4d3ri
sand2r4
3s4ang.
sa4n1g
san4g4s
2s3a2n1h2
3s4a3ni
2s3a2n3l2
2sa2n3p4
2s3a6n1s
s2an4s1k2
4s3ant2r4
sa2n1t
2s3a4n1w
s3a2n1z
2s1a1p
sa2p2o
3s2ap2r2
2s1ar
3s4ar.
3s2a1ra
4s3a2r1b
3s2a2r1d
3s2a1ri
s3a2r1r
3s2a4r1s
4sart2i
sa4r1t
s1a2s1p2
4s3a2s2y
3s2a1t
sat2a
4s3at2h
4s3a2t3l2
4s3a2t1m2
s4a2t2r4
sa3ts
sat4z3e4n1
sa4t1z
s1a4u
3s2au.
3s4a2uc
3s2a2u1e
2s3au2f1b2
sau2g2r4
s2a2u1g
3s4aum
sau1ri1
s2au3r2
2s3au4s3b4
sa2us
3s2au1se
2s3a1v
sa2vo
3säc
s3ä2hn
3säl
s1ä4l1t
2s1äm
2s1ä2n1d
2s1är
sä2s
3s2ät
1säu
2s1ä2uß
4s3b4
sba4n
sbe3r2e
1sc
2sc.
2scam
s1ca
s2c4an
s2ca1p
4s3car
2s1ce
6s4ch.
sc4h
3schaf
2s2ch1ak
s2ch2al
4s3cha2n1c
4sch1a4n1g
5s2cha2n1z
4s1ch2ao
s2cha2u
3s2chä
2s2c2h3b2
2s2c2h1c
2s2c2h3d4
3sche.
sch3ei.
s4chei
4sc4h1e2m1p
sche1m
sch2en
3sche1s
4sch1e4s1s
4s2ch1e4x
4s2c2h3f4
2s2c2h3g2
2s2c2h1h2
schi4e
s4chim
3schi4n1g
schi2n
4s1chi1ru
sch2i2r
3schi1s
2s2c2h1k4
4sch3le.
s2c4hl2
sch2le
6sch3lein
sch6lit
sch1li
2sch3mö
s4c2h2m
2schn.
s2c2hn4
2sch1o2x
3s2c1h2ö
4schöl
2s2c2h3p2
2sch1q
4sch3re.
sc2h2r4
s2ch1re
4sch3rin
sch2ri
sch3r2om
4sch3ro2u
6s4c4h1s2
sch3s1k2
4s2c4h3t
sch2t2a
scht4r4
s4chu
4s2ch1u2n1t
schu2n1
sch2up
5s2ch2ü
2s2c2h1v
4sch1we1t
s2c2hw
sch4wil
2s2ch1z
2sc1j
6s1c4l2
2s1co
3s2cop
3sco4r
s2c4r2
2s2c1s2
2s3cu
4s3d2
sda3m4e
sdi2e1n4e
s1di
sdi2e
sd2i1en
sd4r4
1se
3se.
se3at.
s2e1a
se2a1t
2s1e2b2en
se1b
s2eb4r4
2s1echo
sec4h
s1e2c4h1t
2s1e2c4k
3s4ee
se1ec
se2e1i4
see3i1g
seein2
se1er.
se1e1r1ö
2s1e2f1f
se1f
se2gal
se1g
se2gl
seg4r4
3s2eh
s2e2h1a4
se3he
se4h1ei
se4h2el
se4he2r1k
se3her
se2hi2n
se1hi
se4h1l
seh3re
se2hr
se4h3s2
se4h3t
se2h3üb
se1h2ü
2s1ei.
2s1ei1e
2s1e2i1g
s1ein
5s2ein.
2sei2n3b4
s2ein4du
sei2n1d
se2i3n2e
s2ein4f2o
sei2n3f
2sei4n3g2
2sei2n1h2
4sei2n3k
2sei2n3l2
2sei4n1n
2se2i2n3r2
s4eins.
sei6n1s
4seinsp4
4sein1st
2sei4n1w
4s1ei1s
3s2e2it
3s2e1k
s2el.
se2l1a
se3la1d
sela4g
se3l1a2m
se2l1ec
se1le
2s1e2le1m
se4l3e4r1l
sel3e4r1s
2s1elf.
se2l1f
s3el2i1x
s2e1li
se2l3ö
s2e4l1s
sel3s1z
sel3t3r4
se4l1t
s4e3ma
se1m
2s1e2m1p
3s2en.
se4n2a1g
se2n1a
se2nä
2s1en4d1l2
se2n1d
sen3gl
se4n1g
5s2e1ni
3se2n1k
se2no
se4n1o2b
3s2e6n1s
s2ent.
se4n1t
2s1en2t1f4
4s3en2t1g2
s2enti
2s1en4t1s
2s1en4t3w
2s1en4t3z2
se2n3u
se1o2r
s2eo
4s1e2po1s
se1p
se1p2o
3se1q
s2er.
se2r3a2d
ser1a
se2r3al
se5re1f
se1re
s3e2r1e2i1g
6ser1ei2g3n
se4r3eim
se4r3e2n1k
s2eren
ser2er
2s1er1fo
se2r1f
s2erf4r2
s3er1fü
4serf2ül
se4r3g
s1ergä
s2erg2r4
s1e2r3h4
2se2r1h2ö
3s2e1ri
4s3erken
se2r1k
s1erkl2
s1er1nä
se4rn
2s3er2n1t
se1r2o1t
2s3e2r1öf
ser1ö
s2ers.
se4r1s
2ser1s2a
4ser3s2eh
1ser3se
s4ert.
se4r1t
s2e1ru2
se4r1uf
se3r1u4m
se3r1u2n1d
serun1
3s4e2r1v
5ses.
se1s
se2sel
se1se
se3su
3se1t
4s1e4ta1p
se2t4a1t
4s1e2t2h
s2e1u2n1
2s1ex
se2xe
4s3e4x1p
se4x3t
6s1f4
sfal6l5er
s1fa
sf2al
sfa4l1l
sfal1le
sf1lo4
sf2l2
s3f2r2
4s3g2
sge1s2
s1ge
2s1h
sh2a
3s2ha.
sha2k
4s3han
sh2e
3s2hi.
3s2hi3d
sh2i4r
s2h3n
4shof
3s2hop
sh4o4re
3s2h2o2w1
s2h4r
1si
si2ac4h
si1a
s2iac
si3a4ch.
si2a1d
s2ia4s
2si2a1t
s2i1b4
3s2i1c
2s1i2d2eo
si1d
si1de
s2ido
3s2ie
siege4s
sie1g
sie3ge
si3e1ne
s2i1en
si1e2r1r
s2i1f4
3s2i1g
si2g1a
sig4n
si3gnu
si2g3r4
si2k1a1b
s2i1k
si1ka
si2k1ä
si2k3e4r2l
s4ike
si2ki
si4k3l2
si2k2r4
si2k3s2
si4k3t4
si2ku
3s2i1lo
2s1i2m1m
si3n4a
2s1i2n1d
2s1i2n3f
s2ing1a
si4n1g
sin3g1h
sin3g4l
sin2g2r4
sing3sa
sin4g2s
2s1i2n1h2
si1n1i1
2s1i2n1q
2s1i6n1s
2s1i4n1t
2s1i2n1v2
3s2i1o
5s2i1s
si2sa
si4s4chu
si1sc
sisc4h
s2i2s1e
si2s1o
s2i2s1p2
si4s3s2
si2st2u
si1st
3s2it
si2t1a2u
si1ta
si1t3r4
si2tra
si3tu
siv1a
s2i1v
si1ve3
si2v2r
1sí
4s1j
2s1k2
4sk.
1ska1la
s1ka
4skam
4ska2n1z
4skas
sk4a4te.
sk2a1t
skat2e
4skate1g2
sk4a4te2s
4s2k3b4
s2ke1p
3s2ki.
s2k2if
s2k2i1g
3s2k2i3k4
4s3kir
3s2k2i1z
sk4l2
4s3klas
sk4n2
4s3k2om
s1ko
4skor
4s3k2o1w
4s1kö
4s2k1s
4s4k3t
3s2ku2l1p
sku1l
2s1l2
3s2l1al
4slan
sla2ve2
sl2a1v
s2l2a1w
s2l3b
s5le
s3li
3s4lip
s3lo.
s1lo
slo3b2e
slo1b
s3l2o1e
2s1m2
s3ma
s3mu
2s3n4
4s1na
s2n1a1b4
sni3er.
s1ni
sn4ie
sni3e4r1s
4s5n2o1t
s1no
4snö
3so.
s2o4a2
2s1o2b
so3e1t
s2o1e
3s2o4f1t
3s2o1g
s1o2he
6s3oh4n1g
so2hn
2s1o2hr
1s2ol
so3la
so4l1ei
so1le
sol4ler
so4l1l
sol1le
2s3o2ly
3s2om
3s2on
son3a2u
s2o1na
s2o1ne2
son5en1de
so3ne2n1
sone2n1d
son3sä
so6n1s
son2s1o
so3o
2s1opf
3s2or.
s2o1r2al
sor1a
s1o2r1c
2s1o2r1d
so2rei
s4o1re
2s1or3g4a
so4r1g
5s2or1ge
2s1o2r2ie
so1ri
s2o2r1o2
3s2o4r1s2
so4ru
3so1s
s4os.
4s1o1s2t
1so2u
so3u2n1t
soun1
3so1v
4s1o2ve2
3s2o1w
2s1o1x
5s2o1z
s1ö2f
2s1ök
2s1ö2l
s1ö4s
sp2
2sp.
2s1p2aa
sp2a
2s1pak
2s3pa1la
spani7er.
sp2a3ni
span4ie
2s1pa2no
2s1pa1p
2s3pa1ra
1sp2a1re
2s1p2a1ro
3sp2a1ru
3s2paß
2s1pa2u
s2p2a1z
s2pä
2s3pär
s3pe.
sp2e
2s3pel
4s3pen1si
spe6n1s2
s1pe3p4
s1p2e1ri
2s1pe4r1l
2s3pero
s2pe2r1r
2s3pe4r1s
2s3pe1t
1s2pe1z
2s3pf
2sph2a
s1ph
s4phä
s3phe
1spi
3s2pi4e
4s3pier4
s3p2i2k
2s1pil
3sp2i2o
4s3pi4p
4s3pi1s
2s1p2l4
4s3p4la
4s3plä
3s2p1li
s3p4lu
s3p1n
2s3po1d
s1p2o
2sp2o1g
s2p2o2i
2s3p2o1k
4s3p4ol
1spon
1spor
4s3po1s
s2po4t1t
sp2o1t
4spr.
sp2r2
s2prac
s2pran
2s1pr2a1x
2s3p2räm
sp4rä
4s3präs
1s4prec
sp1re
2s1pre1d
2s3p4re1s
2s3pro1b
s1p4ro1
5s2pro4s1s
spro1s
1sp1ru
3sprun1
2s1prüf
sp1rü
1s2prün
2s3p1s
2s4p1t
2spun1
s1pu
2spup
3s2pur
4s3p2u1t
1spü
4s3py1
2s1q
2s3r4
sra2t2s
s1r2a1t
srat4sc
sre1t3
s1re
sr2om2
srö2s
srö1s1c
srü4ck1er6
s1rü
sr2üc
srü2c4k
srü2d
4s1s
ss2a3bo
s1sa
s2s1a1b
ss1a2c4k
s3sa1g
ss1a1j
s3sal
s4s1a1la
s4s1a2l1b
s4s3a4m1t
s3sam
s4s3a4n1g
ss1an
s2sa1no
s4s3a6n1s
ss2a2n1t
s4s3a2n1z
s3sas
ss3a4t1t
s3s2a1t
ss2ä
ss1ec
s1se
s2s1ega
sse1g
ss2e3h1a4
s3s2eh
sse3i2n3f
ss1ein
sse3i4n4t
sse6r5a4t1t
sser1a
sse1r2a1t
ss1er1ö
s1s3er3se
sse4r1s
s3s2e1s
sse3ta
s3se1t
s3ska1la
s2s1k2
ss1ka
s2s3l2
ss1o2f1f
ss2oi4
s2s1op
ss1o1ri
ss2p2o
ssp2
s2s1p4ro1
ssp2r2
ssque1t4
s2s1q
ssqu4
ssq2u1e
4s4s3s2
sst2a
s1st
s5s2ta1d
ss2tar
s3stä
s3s1t2e
s4ste.
s5s2tel
s4sten
s4ste2s
s4ste1t
s5s2teu
s2s2t1h
ss2tip
ss1t2i1s
ss2top
ss2tur
sst2u
s3s2tü
ss1u2m1s
1st
6st.
3st2aa
2s2t1a4b1b
sta1b
2s2t1a2b5h2
s2t2a1bi
2s2t3a4b1t
2s2t1a2b3z2
s2t2ac
3s2ta1d
4st1ada
4s2t1a2d2r4
3s2ta2f1f
2s3t2a1g
3stah2
2s1tak
2s1t2al.
2s1t4a1le
3st2a3li
2sta2l1k
st1a2l1m
st1a2l1p
3s2tam
st1a2mi
4st1a4m1t
st1a4na
3st2a2n1d
4s4t1a2n1f
4s2t1a2n3l2
4st2a4n1n
2st1a4n1w
4st1anza2
sta2n1z
s2t2ar.
s2ta4r1s
3s2ta4r1t
st1a1si
3st2a1t
3s4t2au.
sta2u
2s2t1auf
2s3t4aum
5st2au3r2
2s1ta2us
3staus.
2s1ta1x
3stä1b
3s2tä1d
2s1tä1g
2s2t1ä4l1t
2st1ä4m1t
s2täm
s2tär
3s2tä4t1t
st2ät
2s1tä2us
4s4t3b2
2s2t3c
2s2t3d4
3st2e
s2te3an
s1t2e2a4
4s3te2c2h3n4
s1t2ec4h
4s1t4ee
ste2g2r4
ste1g2
ste2i
5s2te2i1g
4s3te4il
stei4n1a
s2t1ein
s2tel
s3t1e2le1m
ste3le
stel4l3ä
ste4l1l
ste4mar
ste1m
st2e1ma
4st3em2pf
ste2m1p
4st3en2d1s
ste2n1d
st3en2gl
ste4n3g
st4e6n1s
4st3en2t1f4
ste4n1t
4s4t3en4t3w
4st3e2pi
ste1p
ste6r1e4r1s
ste1re
s2te4r1l
s2te4rn
6s3terr2as
ste2r1r
s2te4r1s
4st3e1se
ste2s
4s1te4s2t3s4
1st2e1s2t
s2teu
4s3teuf
4st3e1v
4s1tex
4s2t1f4
2s2t1g2
2st1h
st3ho
3st2i2e
4s1ti2ef.
stie1f
3st2if
3stim
2st1i2n3b4
2st1i2n3f
2st1i6n1s
s4t2i1o
sti2r
st3i2so
st2i1s
2s2t1j
2s2t3k4
4s2t3l2
4s2t1m2
2s2t5n4
3st2oc
s1to3d
s2to1de2
2st3om
2s1t2o3p2o
2st1o2r1d
2st1o4r1g
3sto1s
4s3t4o2u
2s1tö1c4h
st2ö2c
5s2tör
2s1t2ö1t
2s4t3p2
2s2t1q
3s2tr2af
st2r4
2s1tra1g
3strah
4s3t4r2ai
3s2tr2al
4s1tra6n1s
3s2tr2as
3s2t2raß
4s1t3r4aum
stra2u
s2t4räf
s1trä
2s3t4rä1g
s2trän
4s3tr2ä1ne
2s4t5re.
st1re
4st3r2ec4h
s2trec
4s2t3re1d
4s1tre1f
4s2t3re1g
3s2t4re2if
4st3rei1s
st3re4n1n
2s1tre1p
2s1t4re1t
2st3r2e1v
2stri.
st1ri
3s4tri1a
2s1tr2i1b
4st3r2i1g
str2i2k
4stri1si
str2i1s
2s1tr2oc
3s2trof
3s2tr2o1k
st3ro4l1l
str2ol
stro4ma
str2om
s2tro1s
s2trö
3s2truk
st1ru
s2t2rum2
4st3run1
2s3t4rup
4s2t3s4
2s4t3t4
st2u
3s2t2u1b
4s1t2uc
3st2u1d
2s1t2u1e
3s2tuf
3s2tu1h
2stuk
2st3u2m3r2
s1tum
stu2m2s
2st3u2m1z
stu2n1
2s1t2un.
2s2t3u2n1f
2st3u1ni
2s1tu6n1s2
2s2t3u2n1t
3stuö
stu3re
st3u4r1l
2s3tu4rn
2st3u4r1t2
4s1tüc4h
st2üc
s2tü2c4k
2s1tür.
2s1tü1re
2s1tü4r1g
2s1tü4r1s
2s2t3v
2s4t3w
2s3ty.
s1ty
2s3ty1s
4s4t3z
1su.
su1an
s2ua
3s2u2b3
su4ba2
4su3b4i
5s2u1c
su2ch1a
suc4h
such4st4
su4c4h1s
2s1u2f
2s1u1h
su1i1s
s2ui
su1it.
sul2a
su1l
su1l2i
su4l1t2
su2mar
su1ma
su2ma2u
3s2u1me
su2m1el
su6m5en4t4s
sum2en
sume4n1t
s3um1fe
s1u2m1f4
3s4u2m1m
su1m1o2
su2mor
s3um1sa
su2m1s
s3um1st
su2n1
sun6de2r3h4
su2n1d
sun1de
su4ne
s1u2n1f
2s1u1ni
4s1u2n1t
3s2up
su2p3p
su2ra
2s1u4r1l
s1u4r1t2
s2u2s1
su3sa
su3s1h
su3si
su4s3s
2s1ü4b
sü2d1
süden2
sü1de
3sün
2s1v
2s1w
s3we
s1weh2
4swi2e
4s1wil
1s2y
sy1l1
sy4n3
sy5s
2s1z
4s3za2
4s3zei
s2ze2n1a
5s4ze1ne
4s3ze4n1t
s2ze2s
s2ze2ß1
s3ze1t
s2zi1s
sz2o
4s3zu
4s3zw2
2ß3a2
ß1ä
2ß1b2
2ß1c
2ß1d4
ßdi2e3
ß1di
1ße
2ß1ec
2ß1e2g
2ß1ei
ße2l1a
ße2le
ße4n3g
ße2ni
ße2no
ß2ers.
ße4r1s
2ßer3se
ße4r3t
ße2s
ße2t
ß1ex
2ß1f
2ß3g2
ßg2e2bl2
ß1ge
ßge1b
2ß1h2
1ßi
ßi2g1a
ß2i1g
2ß1in
ß1j
2ß1k4
2ß1l2
2ß1m
2ß1n2
ß1o2
ß1ö
2ß1p2
2ß1q
ßque1t2
ßqu4
ßq2u1e
2ß3r2
2ß3s2
2ß1t
ß2t1h
ß2ts2
1ßu2
ß1uf
2ß1um
ß1u1ni
ßun1
ß1ü
2ß1v
2ß1w
2ß1z
2tab.
ta1b
ta2b1an
t2aba
2t1a4b1b
1t2a3bel
2taben
ta4be2n1d
2t1a2b5f4
2t1a2b1g2
2t1a2b5h2
2t1a2b1k4
1t2ab2le
tab1l2
2t3a4b3n2
ta2b3r4
4ta4b1s
2t3a4b1t
t2a2bü
2t1a2b1w
2t1a2b3z2
2t1ac
3t2a3cu
t1ada
ta1d
t4a3di3
2t1a2d2r4
t2a3d2s2
1t2a1f2e
2ta2f1f
t1a4f3g2
t1af4r2
3t2a1g
t2a2ga2
ta2g1e1i
ta1ge
4t3a4ge4n1t
ta3gen
t2a3gl
t3ago
ta4g2s
tag4st
tah2
tah3le
ta4hl
tahl3s1k2
tah4l1s
t2ai
ta3i2k
tai2l
ta1i6n1s
tai4r
ta1ir.
1tak
t3a2ka
t3a2kro3
t2a1k2r4
t2ak2ta
ta4k1t
3t2ak4t3b2
3t2aktu
2t1a2k3z2
1t2al.
ta2la
ta3la2g
ta3lak
t1alb.
ta2l1b
t1al2b1k4
1talb2u
ta4l3d
1t4a1le
ta4le6n1s
talen1
tal4le1g
ta4l1l
tal1le
tal2lö
ta2l1op
t2a1lo
tal2se
ta4l1s
2ta4l1t
2tam
ta2mer
ta1m4e
ta2mi
t1am1p2l4
ta2m1p
t1a4m1t
t1a2na
2t1a2n3b4
t2a2n1d
t2a3ne
4t1a2n1f
2ta4n1g
t2a2n1k
t3an3kl2
2t1a2n3l2
2t1an1me
ta2n1m4
4t3an3na
t2a4n1n
t1a6n1s
t2ans.
4t3an1si
2t3ansp4
t2a2nu
2tanw2a
t1a4n1w
2tanw2ä
t2anz.
ta2n1z
t1anza2
4t1anzei
tan6ze2r3h4
t1anzu
ta3or
t2ao
ta2pe.
ta1p
t2ap2e
ta2pe1s
2ta2pf
t2a2p3l4
2t1a2r1b
ta4re6n1s
t2a1re
ta3ren
ta4r3e1re
3t4a3ri
2ta2r1k
2t1a4r1m
2ta4r1t
t1art2i
t2ar2to
t2a2ru
2t1a2r1z
ta3sa
1ta1sc
t1as1p2
1ta1s2t
1tat.
t2a1t
ta2t1a2b
ta2tan
ta2t1a2u
tat3ei
tat2e
ta2te1m
ta2t3er
ta2t2h
t4a1t3h2e1
t3a2t3l2
t4a2t1m2
ta2t2om
1ta2ts
ta2t1um
2t1auf
ta2u
4tau4f3g2
tau3f4li
tauf3l2
4t3au2f3n2
1t2a2u1g
t1auk
3t4aum
1ta2us
t1au4s3b4
tau6sc2h2r4
t2au1sc
tausc4h
tau6s2c2hw
t2au1se
4t3au4s3g2
t1au2s1k2
2t1au2s1l2
4t3au4s1s2
4t1au2s1w
1ta1x
tax2i3s
tä1c
2tä1d
3t2ä1e
1tä1g
2t1ä2gy
2täh
2t1ä4l1t
2täm
t1ä4m1t
t1än4g2s
tä4n1g
1tä2n1z
t1äp
t2är.
tä2ru
tä2s2
t2ät
2tä4t1t
1tä2us
2t1ä2uß
2t1äx
1tà
4t3b2
tbe3r2e
tblock5e
tbl2
tb2lo
tb4l2oc
tblo2c4k
tblocken8
2t1c
t3cha
tc4h
t3che
tch2i
t2c4h3l2
t2chu
t2c2h1w
t4c4k
t3c4l2
t3c4r2
2t3d4
tdun2
t1du
1t2e2a4
te3al
te3an
3t4e4b1b
te1b
4t1e2b2en
1t2ec4h
te1cha
3te2c2h3n4
2te2c4k
teck2e
1t4ee
te1e1m
te2en3
te1e2r1w
te2e1s2
2te2f1f
te1f
te1g2
teg3re
teg2r4
2teh
t3eif3r2
te2if
te2i1k4
1te4il
2t1ein
tei2n3ec
te2i1ne
t3ein1ge
tei4n3g2
t3einla
tei2n3l2
t3e2is.
tei1s
t3e2i4s3b4
tei3st
t2e4k3t2
te1k
te1la4
te2l3a1b
te2l1ac
te2l1a2u
te2l1b4
te3le
tel1e1b
tele4be
te4l1ec
3tele1f
3tele1g
t2e4l1eh
te4l1ein
t2elei
2t1e2le1m
tel1en
te4l1e2r1d
te4le2u
4t3elf.
te2l1f
te2l1in
t2e1li
te2lit
tel1l2e
te4l1l
te4lo1s2t
t2e1lo
telo1s
te2l1ö
tel3s2k2
te4l1s
tel3ta
te4l1t
tel3t1h
tel3t4r4
te2m1ei
te1m
te1me
te2min
te1mi
2te1mo
te2m1o2r
3temper
te2m1p
temp2e
1tem1p2o
te4m1u
t6en.
te2n3a
te2n1a2b
te4na2d
te4n2a4g
te4nas
te4n1a2u
te2nä
t4e2n3b4
ten3da
te2n1d
4t3en2d1f4
t6en1di
2t1en4d1l2
t6endo
4t3en2d3p2
ten3d4r4
te2n1e2b
te1ne
te2n1e1f
te3n4ei.
te2n1ei
ten3e2i1d
ten3e6n1s
t2e3nen
4t1e2ne4r1g
te2ne1t
te4n3g
t1eng.
ten4gl2a
ten2gl
t4e2n1h2
te2ni
te4n3in1
t4en1j
t2e2n3l2
t4e2n1m4
te4n3n
t2e1no
ten1s2e
te6n1s
4t1ense1m
t4enta
te4n1t
t3en4t3b2
4t1en2t3d4
t4ente
ten4t3ri
tent2r4
4t3en4t3w
t3en4t3z2
ten6ze2r3h4
te2n1z
ten3zw2
t3e2pi
te1p
t4er.
ter1a2b
ter1a
te2r2a1d
te1r2af
ter3am
te3r2an.
4ter4b2s
te2r1b
4ter4b1t
t3erde.
ter3de
te2r1d
te2r1e2b
te1re
te4r3e2if
te2re2l
ter3e2n1d
t2eren
te4re4n1g
te4r1e2r1k
te2r1e2r4z
4terf2ol
te2r1f
ter1fo
t4erf4r2
4terf2ül
ter1fü
te4r3g2
6tergrei
terg2r4
terg1re
t6erg1ru
2t1er1gu
2tergü
t4e1ri
te3ri1a
4t1erklä
te2r1k
terkl2
2t1er1l2ö
te4r1l
1te4r1m
ter4mer
ter1me
ter4n3a2r
te4rn
ter1na
4t3erne2u
ter3ne
t4ero
t3er1ö
3terr2as
te2r1r
ter4re.
ter3r2e
1terr2o
t4ers.
te4r1s
t6erscha
ter1sc
tersc4h
ter4ser
ter3se
ter1st4
t4er6st.
t4ersti
t4ers2t2u
tert4a
te4r1t
t2e1ru2
te4r1uf
ter4wäh
te2r1w
terw2ä
6terwe2r1b
ter3za2
te2r1z
4t3er4z3b4
te2s
tes1ac
te1sa
te3ser
te1se
te3si
te3so
t2e3sp2
te4sp2r2
te4s3s2
3tes3se.
tes1se
t2e1s2t
tes3t2ät
te4st3e2i
te3s2t2e
te6ste4r6g2
te6ste2r1k
te2ste2s4
1te4s2t3s4
t2et.
te1t
te2t4a1t
4te2t3l2
teu3e1ri
te2u1e
3teuf
t2e1un1
teu2r3a2
te2vi
t2e1v
1tex
te1x1a
t3e2xe
2t1e2xi
4t3e4x1p
3te4x1t
2t1ex1z
2t1f4
tfi2l
t1fi
2t1g2
tger2
t1ge
t1h
4th.
2th2a
3t4ha.
t2ha1g
t3hai
t2hak
3t2h2al.
4t3ha2u
2t3hä
4t2h1c
1th2e
t2he.
3t2h2e1a
2the1b
t2hec
2t3hei
t4hein
t2he1k
t2he1m
t4he1ne
t4he1ni
3t4h2e2o
2the2r1r
t2he1s
3the1se
t2h2e2u
1t2hi.
t2h2i1k
2t3hil
2t3him
2t2h1k4
4t4h3l
4t2h3m
2t2h3n
t2ho
2t3h2o2c
t3hof
2t3hoh
t4hol.
th2ol
t3hor
2t3h2o1t
tho2u4
2t3h2ö
2t2h3p2
1t2h2r2
4thrin.
th2ri
th3rin
4th2r1i6n1s
2t4h1s
2th2u1b
4thu2n1
2th2ü
2t2h1v
t2hy
ti2a1d
ti1a
ti3a2m4
t2i1b4
ti1ce
t2ic
ti3c2h3r4
tic4h
tiden2
ti1d
ti1de
ti4de2n1d
ti2d2eo
t2ie
1ti2ef.
tie1f
ti1el
t2i3e4n1
ti2er
tie4rec
ti2e1re
1tie2r1r
2tieß
ti1e1t
ti1eu
1tif.
t2if
ti1f3r2
ti4g1e2r1z
t2i1g
ti1ge
tihi4
t2i1h
ti2kam
t2i1k
ti1ka
ti2kar
ti2k1in
ti2k3rä
tik2r4
ti2k1s2
ti2lar
ti1la
ti2l1a2u
ti2lei
ti1le
ti2l1el
1ti2l1g
til4le1b
ti4l1l
til1le
til4le1g
ti2lö
ti4l1t4
ti2lu2
ti2ma2g
ti1ma
t2i1mi
tim2m1a
ti2m1m
4t1i2m1p
t2in.
ti3na
t1i2n3b4
4t1i2n1d
t2i3n2e
t1i2n3f
t2in2g1a
ti4n1g
tin2g3l
tin4g3s2
t1i2n1it
ti1ni
2t1in1j
tin2k1l2
ti2n1k
t2ins.
ti6n1s
4t1in1se
2t1i4n1t
ti1n1u
4t1i2n1v2
3t2i1o
ti3or
1tip
3t2ip.
ti3p4l4
3ti2p1p
ti4que.
t2i1q
tiqu4
tiq2u1e
ti1r1h4
t2i1s
ti4scha
ti1sc
tisc4h
tis2c2h3w
ti2sei
t2i1se
t2i2sp2
3ti3te
tium2
t2i3u2
ti2van
t2i1v
ti1ve3
ti2vel
ti4v3e4r1l
tiver1
ti2v1o
ti2v3r
ti2za2
t2i1z
2t1j
2t3k4
2t3l2
t1l4e
3tle1m
tl2e2r3a
4t5li
2t1m2
tmal2
t1ma
tme4n4t3
t1me
tmo4de3s
t1mo
t3m2o1d
tmo1de
t3mu
2t5n4
t3n2e1s4
t1ne
to4a3s
t2o3a2
to5a4t
1to1b
2t1o4b1j
tob2l2
to1c4h
t2oc
3to2c4h1t2
2to2c4k
1to1d
3tod.
to1de2
to2d1er
tode4s1
t2o4d1u
toi4r
t2oi
to3la
t2ol
to1m1e2
t2om
to2men
2to4m1g2
1ton
to2n2a2u
t2o1na
to2neh
t2o1ne
3too
to2pak
to1p2a
to2p2a1t
1t2o3p2o
2t1o2p3t4
to1r1a
to2ra2u
to4rän
to3rä
2to2r1c
t1o2r1d
1to3re.
t4o1re
to2r1el
t1o4r1g
t3or3g4a
to2r3i4n1t
t4orin1
to1ri
t2o2rö
1to4r1t
t1ort.
to3ru
t2o2r1w
to3sc
to1s
to4s1k2
t2os2p2
tos4seu
to4s1s
tos1se
to1s2t4
1toten
t2o1t
to2t2ho
t4ot2h
3t4o2u
touil4
to2ui
to3un1
t2ö2c
1tö1c4h
2t1öf
2t1ök
tö4l
3t2ön
t1ö4s2t
tö1s
1t2ö1t
4t3p2
tpf4
2t1q
t2r4
2tr.
1trac
tra3ch1a
trac4h
t3r4ad.
t1r2a1d
tra4de1m
tra1de
tra4f3ar
tr2af
tra1f1a
1tra1g
2t3r2a2h1m
3t4r2ai
1tram
2t3r2a2m1s
3t4r2an.
2tra2n1d
1tra2n1k
t3r2a4n1n
1tra6n1s
t3r4a3s2e
tr2as
t3ra1si
2t2raß
1t3r4aum
tra2u
1trä
3t4rä1g
2t2räh
3tr2ä1ne
2träs
2trä1ß
2trä2u2s
trä2u
2tr1ä2uß
4t5re.
t1re
tre4a1le
tr2e1a
2tre1b
tr2e2b2r4
2trec
t3r2ec4h
t4re2c4k
2t3re1d
3t4r4ee
1tre1f
2t3r2e1fe
3t4re2f1f
2t3r2e3f2o
2t3re1g
t4r2ei.
1t4r4e2i1b
2tre2if
t3re2i1g
2t3re2i1h
t3r1ein
t3rei1s
t3re2i1z
2t3re1k
2t3rel
t4re1m
t4r4en.
1tre2n1d
t3re4n1t
1tre1p
2t3rep2e
2t3re1p2o
t4rep2r2
t4rer
t4r4es.
tre1s
1t4re1t
t2r4e2t3r4
t4re2u
t3r2e1v
2t3re1z
3t4ré
2t3r1h4
1tr2i1b
t1ri
3trieb.
tr2ie
trie1b
3trie4b2s
tri2er
1trin
t3r1i2n1d
2tri4n1g
tri3ni
3tr2i1o
t4rip
t3ri2ß1
1tr2i3u2
tr2i2x1
trizi1
tr2i1z
1tr2oc
t4r2oi
tro2ke
tr2o1k
tro2mi
tr2om
2t3roo
t4rop
3t2ro2pf
2t3roß
t3r2öc
2t1r2öh
2t3r2ö1t
1t4r2u1g
t1ru
2truk
t2rum2
tru2m1s1
2t3ru2n1d
trun1
1t4r2u2n1k
3t4rup
t3r2uß
tru2t3h
t3r2u1t
t4r1üb
t1rü
trü1be2
trü1bu
2t3r2üc
trü4ck1er6
trü2c4k
t4rü1g
3t4rü2m1m
t1ry1
2ts
t4s3a4b
t1sa
t3s2ac
ts1a1d
t2s1ah
ts1al
t4s1a4m1t4
t3sam
t2s1an
t4s3ar
ts1as
t2s1a4u
t1sä
t2s1än
t4schar
t1sc
tsc4h
t3sch2e
t4s1che1f
tsch4li
ts2c4hl2
t4schro
tsc2h2r4
t3s2co4r
t2s1co
t2s1e2b
t1se
t3s3eh
t3se4il
t4sei2n1d
ts1ein
ts1e1m
tse2n1
t2s1e4n1g
t2s1e4n1t
t2s1er
t4s3es1se
tse1s
tse4s1s
t2s1i2d
t1si
ts1i1n1i1
t2s1ir
t3ska1la
t2s1k2
ts1ka
ts3k2r4
ts1o
tso2r
t1spal
tsp2
tsp2a
t1span
ts1par
t1s4p2a1re
t1spas
ts2pe1d
tsp2e
t1spe1k
t1s2pi
t1s2pon
ts1p2o
t1s2por
t4s3s2
t1st4
t3s2t2a1t
ts3tä1ti
tst2ät
t4s1t2e2a4
t3st2e
t4s1te1p
t4s1te4r1m
t4s3te2r1r
t3s1t2i2e
t2s3t2i1s
t2stit
ts2to
t3s3t2oc
ts3tor
t4s3t1r2a1d
tst2r4
t2s1trä
t2s1t1ri
ts2tro
t4st4rop
t2s3t1rü
ts1u
1t3s2u2b3
t1sü
4t1t
tt1a1b
tta2be
t2t2ac
t2t1a1d
tta6ge4s5s
t3t2a1g
tt2age1s
tta1ge
t1t1ak
tt2al
tt2a2n1t
t2t1a4r1t
tta3s
tt1ebe
tte1b
tt1e2if
tt1ei1s
tte2la4
tte4l1e1b
tte3le
tte4l1en
tt2e1l1o
t3ter
tt2e4r3g2
tte4s1ä
tte2s
tt2häu
tt1h
t2t3hä
t2t3ho
t3tro
tt2r4
tt3ru
tt3rü
tt2se2n1
t2ts
tt1se
tts1p2
tt4s3te1m
tt1st4
tt3st2e
tt4ster
tt4sti
4t4t1t4
t2t2uc
tt2un1
tu1a2l1m
t2ua
tu3an
1t2uc
tu2chi
tuc4h
1t2u1e
tu2e1re
2tuf
tu1f2e
tu3fen
t3u2fer
2tu1h
tu2i1s
t2ui
t3u2k4r4
tul2a
tu1l
1tum
t2um.
t2u1me
2t3u2m1f4
t3u4m1g2
2t3u2m1k4
2tu2m1p
t3u2m3r2
tum2si
tu2m1s
tum2so
2t3u4m3t2
t3u2m1z
1t2un.
tun1
2t1u1na
2t1u2n1d
2t3u2n1f
t3unga
tu4n1g
tun4g6s1
2t1u2n2if
tu1ni
2t1u2n2i1o
1tu4n1n
1tu6n1s2
2t3u2n1t
t1up.
tu2r1a4g
tu1ra
t2u2rä
tu2r1c
tu3re.
tu1re
tu2rei
tu2r1er
tu2re1s
tu2r1e4t
turin1
tu1ri
1tu4rn
t4u2ro
tu4r3s
tu4ru
tu2sa
t2us
tu4s2c4hl2
tu1sc
tusc4h
tu2so
tu3ta
t2u1t
2t1üb
1tüc4h
t2üc
tüc2k2s
tü2c4k
1tüf
1tür.
tü2r1c
1tü1re
1tü4r1g
1tü4r1s
1tüten
t2üt
tü1te
2tü4t1z
2t3v
4t3w
tw2a2
twi4e
1ty
3ty.
3ty1p
ty2p2a2
3ty1s
4t1z
t2za4
tz1a1g
tz1al
tz1ar
tz1a2u
tz1ä2
t3ze.
t2z1ec
t2z1ei1e
t2z1ei3s2
tze4n1
tz2e1ne
tz3en4t3s
t3ze4n1t
tz1e4r1l
tz2e4r1s2
t3ze2s
tze1s3t
tzg2el2
t2z1g2
tz1ge
tz1i4n1t
t2z1or
tz2ö
tz2t1h
t2z1t
tz2tin
tz1w2ä
tzw2
tz1wi
tz1w2u
2ua
u3a2b
u1a2c
uad4r4
ua1d
u1ah
u1al.
ua2l1a2u
ua1la
u1a2l1b
u3ale4t
u2a1le
u1a2l1f
u3a2lo
u1a2l3r2
u1a4l1s
u1a4l3t
ua2lu
u1am
u1a6n1s
u3ar.
uar2a2b
ua1ra
u1a4r1s
ua3sa
ua2t2h
u2a1t
u4a3t2i
u3a2u
ua2u2s
u1ay
u1äm
u1än
u1äu
2u1b
u2bec
ub3ein
u3b4i
ubi3os.
ub2i2o1
ubio3s
ub2l2
ub3l2i1c
ub1li
u2b3lu
u2b1op
ub2r4
u3b3rä
u2b3r2it
ub1ri
ub2s1an
u4b1s
ub1sa
ub2s1o
ub2sp2a
ubsp2
u2b1üb
2uc
u2c1c
u1ce4
uce1s3
uch1a
uc4h
u1cha.
uch1ä
u1che
u2ch1ec
u4ch1ei
u3che1s
u1chi
uch1il
uch1i2n
u2c4h3l2
u4c2h3m
u2c2h3n4
u2c2h3r4
uch2so
u4c4h1s
uch4sp2r2
uchsp2
uch1st4
uch4tor
u2c4h1t
uchto2
uch2t3r4
u1chu
u2chum
u2ch3ü
u2c2h1w
u1ci
u4ck2er
u2c4k
u1c4l2
2u1d
u3d2a
ude1r2e
u1de
ude4r1t4
ud2i3en
u1di
udi2e
udi1ti4
u2don
ud3ra
ud2r4
u3d1ru
2u1e
u2e1d
ue2en
u4ee
u2e1g
u2e1la
ue2le
u2e1li4
ue2mi
ue1m
uen1
ue2nä
ue2ner
ue1ne
uen1ge4
ue4n1g
ue2ni
ue2no
uen2sa
ue6n1s
uen2zu
ue2n1z
u2e1p
ue2r3a
ue2r1ä
ue3r1e2i1g
ue1re
u3ere2m1p
u2e3re1m
u3ere4n1t
u2eren
ue4r1e4r1g
ue4r1e2r1k
ue4r3g2
u4e3ri4n1n
u2e1ri
u3e2r1i4n4t
uer3ne
ue4rn
uer4ner
uer3o
u3e2r1r
uer3sc
ue4r1s
ue4r3t2
u3e2r1u4m
u2e1ru
u3e2r1u2n1f
uerun1
u3e4r3u2n1t
ue4s
ue5se
u2e5sp2
ue2ta
ue1t
ue4te1k
ue3te
uf1a1b4
u1fa
u3f4ah
uf1ak
uf3ar
uf1a2u
u2f1äs
u1fä
u2f1ä2ß
u2f1ei
u1fe
u2f1e1m
u3fen.
u2f1e4n1t
u2f1e2r3h4
u4fer1l2e
ufe4r1l
uf2e4rn
u2f1eß
2u2f1f
uff4l2
uf2f3ro
uff2r2
uf3l2
u2fo1b
u1fo
ufo2r
uf1o1ri
uf3r2
uf3sä
u4f1s
uf2s1p2o
ufsp2
uf4s3te1m
uf1st
uf3st2e
uf4ster
uf2t1e1b
u4f1t
uf3ten
uf2t3s2
u2f1um
u1fu
2u1g
u4gab5te
uga1b
uga4b1t
ug1af
ug1a1k
uga4n1g4
u2g1a1p
uga4s
ug1a2u
u2g3d2
u2g1ei
u1ge
u2g1e2r1f
u2g1e4r1l
ug3hu
u2g1h
u2g1l
u3g3la1d
ugl2a
ug3lo
u3g2lö
u4g1lu2
u2g3n
ugo3
ug1or
u2gö
u4g3rei1s
ug2r4
ug1re
ug3ro
ug3rüs
ug1rü
ug3s1a4u
u4gs
ug2sa
ug3sc
ug3s1e2
ug4s3er
ug3s1i
ug1sp2a
ugs1p4
u2gü
u1h
2uh.
uh1la
u4hl
uh1lä
uh2li
uh1me4
u2h1m
uhr1a
u2hr
uh2rer
uh1re
uh3ri
uh4rin
uh4r1t4
uh2ru
uh4rü
uh1u2n1
u2h1w
2ui
u2i2c
u1ie
ui1e1m
u3i1g
u4i1ge
u1im
u1in.
u1is.
ui1s
u3i6s4ch.
ui1sc
uisc4h
u3i6s4c4h1s2
uisi4n
ui1si
ui2st
u1j
u1k2a
u3käu
u1kä
u1ke
u1ki
u1k2l2
ukle1i
uk2le
u1k4n2
u3ko
u1k2ö
u1k4r4
uk2ta
u4k1t
uk2t1in
uk2t3r4
u1ku
uk2u2s
u1l
ul1am
ul1äm
u2l1b4
ul2d3r4
u4l1d
uld2se
ul2d1s
u2l1el
u1le
ule4n
ul1e2r1f
ul1e2r3h4
ul1e2r1w
ule2sa
ule1s
ule2t
ul1eta
u2lex
u2l1f4
u2l1g4
ul2i2k
u1li
ul1i6n1s
ul3ka
u2l1k
ul2k2n2
ull2a
u4l1l
ul3len
ul1le
ul2l2e1s
ul2lö
ulm3ein
u2l1m
ul1me
ul2o2i
u1lo
ul1or
ul2p1h
u2l1p
ul2sa
u4l1s
ul4sam
ul1s2t
ul2s3z
2ulta
u4l1t
ul3t1h
ul2t1ri
ult2r4
ul4t3s
u2lü
ul2v2r
u2l1v
ulz2w2
u2l1z
u2m3a2k
u1ma
um1a4l1l
um2an
u2m3a2n1z
u2m1a4r1t
u2m1a2us
uma2u
u2ma2u1t
1u2m3d2
um2en
u1me
umen4t4s
ume4n1t
ume1r2a
u2m1e4r1g
u2m1e4r1l
u2m1e2r1w
1u2m1f4
1u4m1g2
um1i6n1s
u1mi
um1ir
1u2m1k4
1u2m3l2
4u2m1m
um1m2a
umpf4li
u2m1p
um2pf
ump2f2l2
um2p3le
um1p2l4
1u2m3r2
3um3s2a1t
u2m1s
um1sa
um2s1a4u
um2ser
um1se
um2sim
um1si
um2s1p2e
umsp2
um4ste1m
um1st
um3st2e
um2su
u4m3t2
u3m2un1
u1mu
u2m1ur
1u2m1z
un1
2un.
2u3na.
u1na
1u2n1a1b
u3n3a2c
un4al
u3n2am
u2n3an
2u3n2as
u3n3a1t
1unda
u2n1d
un4d1a1b
1un2d1d2
un4d1ei
un1de
un4d3e2r1f
und5e2rha
unde2r3h4
1un2d1f4
2un2d1g2
un2di1d
un1di
1un4d5n2
un2d1or
un2d3r4
4unds.
un2d1s
und3sp2
un2d1um
un1du
1un2d1v2
1un2d1z
u3ne
une2b
une2h
un2ei.
u2n1ei
un3ein
une4n2t
u3nen
u3n4e1s4
1unge1t
u4n1g
un1ge
1unge1w
1un3glü
un2g2r4
ung3ra
ung3ri
un4g4s1
un2i1d
u1ni
un3i2de
1u2n2if
u3n2i1k4
un2im
uni2r
2u3n2i1s
un3i2s1l2
u3n2it
3u2n2i3v
2u2n1k
un2k1a2
un2kei
un2k1s2
unk4t1it
un4k1t
unk2t3r4
3unku
un2n3a2d
u4n1n
un1na
un3n2e
uno4r
u1no
un2o1s
1u2n3r2
u6n1s2
2uns.
un3se
1un1si
un3s1k2
un3sp4
unst2r4
un1st
1u2n1t
un3ta
unt4e4ri
un3ter
un3t2r4
un4t3s
2untu
u1nü
un3v2ol2
u2n1v2
unvo4l1l3
1u4n1w
2u2n1z
2uo
u1o2b
u3of
u1op
u1or
u3or.
u3o2r3c
u3o4r1s2
u1os.
uo1s
uote2
u2o1t
u1p2a
u1p2e2
uper1
up2fa
up1f2e
up1f1i
u1pf2l2
u3pi
up2p3l4
u2p1p
up2p2r2
u1p2r2
upt3a2
u2p1t
upt3e4r3g2
up3te
upt1o
u1q
2ur.
u1ra
u2r1a1b
u3r2aba
ura2be
u2r3a2m
u2r1a1na
ur2a2n3b4
u2r1a4n1g
ur2a2n1h2
u2r1a6n5s
u2r1ar
ur3a4ren
ur2a1re
u2r3a4t1t
u1r2a1t
u2r1a2u
2u1rä
ur1än
ur3b2a
u2r1b
urc4h1
u2rc
ur3d2i
u2r1d
u4r1e2f1f
u1re
ure1f
u2re1l2e
ure4n
u4r1e1p
ur1e2r3h4
ur1e2r1w
2u2r1f
ur4f3t
ur2g1ri
u4r1g
urg2r4
urg4ro1s4
ur4gs2
ur2i2c
u1ri
ur1im
ur1i1ni
u2r3i6n1s
u2r1i4n1t
1urla2u
u4r1l
ur3l2a
4u1ro
u3r2ol
u1rö
u2r3p2
2u2r1r
ur2s2an
u4r1s
ur1sa
ur2s1a4u
ur2ser
ur1se
urs2t4r4
ur1st
ur4s1w
urs2ze
ur2s1z
u4r1t2
u3ru
ur2za2
u2r1z
ur2zä2
ur2zi
ur2zo
ur2z1w2
2us
u4saf
u1sa
us4a4n1n
us1an
u1sä
u6sche4n1t
u1sc
usch2en
usc4h
usch5wer
us2c2hw
u2s1ec
u1se
u2s1ei
u3se2i1d
u3se1p
use1r1a
u2se2r3p4
u2s1e1se
use1s
usi3er.
u1si
u3s2ie
usi5ers.
usie4r1s
us3k4l2
u2s1k2
u4s1ko
u1so
us3oc
u3soh
u3s2ol
u2s1op
u1s1o2u
u1sö
u1sp2
us3pa4r1t
usp2a
u2s1pas
u2s1p2a1t
us1p2e
u3s2pe1k
us1p2ic
u1spi
u3s2p2i1z
u2s1p2o
u1s2por
u2s1pu
us2sac
u4s1s
us1sa
us3s2a1t
us3s3a2u1e
uss1a4u
uss5er1fa
us1se
usse2r1f
us2se1z
us2sir
us1si
us2sof
us2soh
us2sü
u2sta1b
u1st
ust3abe
u3stal
u3s2t2a1t
us2ten
u3st2e
us4t4er.
u2s2t1h
ust2in
u3st2i1s
u2s1tor
u2s1trä
ust2r4
u4str2it
ust1ri
u3s4t4rop
u2s1tur
ust2u
u2s1ty
u1su
us2ur
2uß
u2ß1u2
2u1t
u3ta.
u2t1a4l1t
u2t3a2m
u2t1a1p
u2t1ar
u2tär
u3te.
ut1e1g2
ute4ge
ut1ei.
ut1ei1e
ute2n1
u3t6en.
u2te4n1t
uter4er
ute1re
u4t3er1s2a
ute4r1s
u3t2e2s
u3t2e1t
u2t2e1v
u4t1ex
ut1fi4
u2t1f4
u1t2h2e
ut1h
u2thi
u2t3ho
u2thu
u3to.
uto4ber
u1to1b
utob2e
ut2o3c
u3t2om
ut1opf
u2to2p1s
ut4or
ut3r2e1a
ut2r4
ut1re
ut3rü
ut3s2a
u2ts
ut2s1ä
ut4s2c4hl2
ut1sc
utsc4h
ut4s4c2h2m
ut4s2c1h2ö
ut1sp2
ut2sp2a
ut3te
u4t1t
ut3t4l2
ut2t1s2
utu4re
utu5ru
utz3e4n1g
u4t1z
utze4n1
ut2z1in
ut2zo
ut2z1w2
2u1u2
u1ü2
u1v4
u2ve.
u1ve2
uve3rä
uver1
u1w
2u1x
u1x2e
ux2o
u4x3t
u1ya
2u1z
uz1we
uzw2
u4z3z4
1üb
2ü2b1c
2ü2b3d4
übe2
über3
üb3l2
üb3r4
2üc
ü1che
üc4h
ü2c4h3l2
üch4s1c
ü4c4h1s
ücht4e
ü2c4h1t
ü4ck1er
ü2c4k
ück3e1ri
ück4sp2e
üc2k1s
ücksp2
üd3a4
ü3d2en.
ü1de
üde4n4g
ü3d2e6n1s
üd1o4
üd1ö4
üd3r4
ü2d3s2
üd1sa1
ü2d3t4
üdwe2
ü2d1w
ü2f1a
ü2f1ei
ü1fe
üfer2
ü2f1e4r1g
üf2f3l2
ü2f1f
ü2f1i
üf3l2
üf2to
ü4f1t
ü1g
üge6lei1s
ü1ge
üg2el
üge3le
üg2elei
ü2g3l
ü2g1n
ü4g3s
üh1a
ü1he
ü2h1ei
ü2h1e4n1g2
ü2h1e2r1k
ü2h1e2r1z
üh1i
üh4l1ac
ü4hl
üh1l2e
üh3mo
ü2h1m
üh3n2e
ü2hn
üh3r2e
ü2hr
üh4r3ei.
üh1ro
ühr3ta
üh4r1t
ü4h1s2
üh3st2u
üh1st
ü4h3t
üh4t1h
ü1hu
ü2h1w
ü1k2
2ül
ül1a
ü2l2c
ü1l4e
ül2la
ü4l1l
ül2l1ei
ül1le
ül2lo
ül2lö
ü1lu
ü2me4n1t
ü1me
ü2n1a
ün2da
ü2n1d
ün2d2r4
ü3nen3
ü1ne
ün2fa
ü2n1f
ün2f1ei
ün1fe
ün2f1li
ünf4l2
ün2f4r2
ün2g3l
ü4n1g
ü2n1t2
ü1nu
ün2za2
ü2n1z
ü1p2e
ü1pi
üp2p3l4
ü2p1p
ür1a
ü2r1ei
ü1re
ür2f2l2
ü2r1f
ür2f2r2
ür4g3e4n4g
ü4r1g
ür1ge
ür3gen
ü1r2o1
ür4ster
ü4r1s
ür1st
ür3st2e
ürt2h
ü4r1t
ü1s2a
ü2s2c4hl2
ü1sc
üsc4h
ü3s2e3h
ü1se
üse3l
ü1sp2
üs4sa
ü4s1s
üs2s1c
üs1s2e
üs4st
ü2sta
ü1st
üste3ne
ü3st2e
ü2st2r4
ü1su
ü1ß
2üt
ü1ta
ü2t1al
ü1te
ü1ti
üt3r4
ü2t2s1
üt2t2r4
ü4t1t
ü1tu
ü1v
ü1z
2v1a1b
va1c
va4l2s
2va4n1g
2v1a2r1b
vas2
v4a1t
va2t3a4
va2tei
vat2e
va2t3h
vat2i1k2
v4a3ti
va4t1in
vati8ons.
va3t2i1o
vation2
vati3o6n4s3
v4a2t3r4
va2t3s4
va2t1u
va4t3z
2v1a2u
vä1
2v1b
2v1d
1ve2
ve3ar
v2e1a
ve3b
ve3c
ve3d
ve3g
ve3h
ve4i
ve2it2
vei2ts1
ve3la
ve4l1a2u
ve3le
v2e3li
v2e3lo
v2e3ma
ve1m
2ve3mu
ve3nal
ve2n1a
ve2n2c
ve3ne
vene2n4d
v2e3nen
ve3ni
v2e3nö
v2e3o
ver1
ver3a
ve3r2a1d
ve3ra2n3d
ve3r2as
ve2r3b2
ve2r1d2
ve1re2
ve4re1k
ve2r1f4
ve4r1g4
v2e3ri
ve4rin
ve2r3k
ver3sta
ve4r1s
ver1st
ve4r1t2
ver5te
v2e1r3u
ve3s
2ve3sc
2ve1se
v4e4s1h
v2e4s1p2
ve1s4t
ve3ta
ve1t
ve3te1
v2e3t2r4
2ve3ü
v2e3v
ve3x2
2v1f4
2v1g
2v1h
vi3ar
vi1a
vi4a3t
v2i2c
vi2e2h3a
vi2el
vi2er
vie4rec
vi2e1re
vie2w1
v2i1g2
2vii2
vi2l1a
vi4le2h
vi1le
vi2l1in
vi1li
2v1i2m
vi1ma2
vi4na
vi6n2s
2v1i4n1t
vi3sa
vi1s
v2i1se4
vi3s2o
v2i2sp2
vis2u
2v1k
2v1l2
2v1m
2v1n
2v1o1b
vo3ga
v2o1g
vo2gu
3v2ol
vollen4
vo4l1l
vol1le
vol6l5e2n1d
vol2li
2v1op
vo2r1
vor3a
vo2r3d
v4o1r3e
vo4r3g
vo3ri
vo5r2i1g
vormen4
v2o4r1m
vor1me
v2o1rö4
3voy
2v1p
v2r
2v3ra
v3re
v4r4ee
2v3ro
2v1s
v1s2e
v3s2z
2v1t
vu2e1t
v2u1e
2v1u2m1f4
2v1v
2v1w
2v1z
w2a
1w2aa
wab2bl2
wa1b
wa4b1b
wa3che
wac4h
wach6st2u
wa4c4h1s
wach1st
wach4t4r4
wa4c4h1t
waf1f2e2
wa2f1f
waffel3
1wa1g
wa5ge
wa2g3n
wa3go
1wah
wahl5e4n1t
wa4hl
wah1le
wah4ler
wah2li
wa2i2b
1wal
2wa2l1b
wal4da
wa4l1d
2wa2l1m
wal2ta
wa4l1t
wal2to
walt4st4
wal4ts
wa3na
wan4g4s
wa4n1g
wa2p
1w2a1r2e
ware1i
war3t4e
wa4r1t
1was
wa3sa
wa4scha
wa1sc
wasc4h
w4a3s1h
was3s4e
wa4s1s
w2ä
1wäh
1wäl
wäm3
2wä4n1g
1wäs
wä1s2c
2w1b2
wbu2
2w1c
2w1d
w2e2a
w2e2ba
we1b
4we3be1b
w2e2bl2
we4b3s
w4e2e4
wee1d3
w2e2f2l2
we1f
1we1g
we2g1a
we2g3l
we2g3r4
we4g1s2
1weh
we2i
wei4bl2
w4e2i1b
2wei1e
we2i1k4
weis4s3p2
wei1s
wei4s1s
wei3st2r4
wei1st
wei4t2r4
we2it
wei2t1s
wel6s2c4hl2
we4l1s
wel1sc
welsc4h
wel6sc2h2r4
we4l2t1
wel4t3a4
wel6t5e2n6d
wel3t2en
wel4t3r4
we2n3a4
we3ni
wen4k3ri
we2n1k
wenk2r4
we2r3a
wer2bl2
we2r1b
1werb2u
we2r1d2
5werde6n1s
wer3de
wer3den
1wer1du
werer2
we1re
wer2f2l2
we2r1f
wer4g2el
we4r1g
wer1ge
we4r3i1o
w2e1ri
1w2erk.
we2r1k
wer2ka
1werke
wer2kl2
wer2ku
we2r1ö
wer4sta
we4r1s
wer1st
wer2ta
we4r1t
wer6t5e4r1m
wer2to
1wer4ts
1we1se
we1s
we3s2e6n4s3
w2e2s1p2
we1s2t
we2st1a
we4st3e2i
we3s2t2e
we4s2teu
we4sti
we2st1o2
we2stö
we2st3r4
we4s2t2u
1we1t
we2t2s
wet2t3s
we4t1t
2w1ey
2w1g
2w3h
1wi1d
wi2e
wie3l
wi2e1n2e
w2i1en
wie2st
wie1s
w2i1k2
1wil
wim2ma
wi2m1m
wim4m3u
win4d3ec
wi2n1d
win1de
w2in2d2r4
w2i1n2e
2wi4n1g
win8n7er1sc
wi4n1n
win1ne
winne4r1s
win4num
1wi4r
w2i3s2e
wi1s
w2i2sp2
1wi4s1s
wi3st
wi3t1h
1wit2z1l2
w2i4t1z
2w1k
2w1l
2w1m
2wn
w6n3s
1w2o1c
wo2ch1a
woc4h
wo1che4
1woh
woh2le
wo4hl
1wo2l1f
w2ol
wol4f2s1
wol4la
wo4l1l
wol4ler
wol1le
wor3a
wo2r3i
wor2t3r4
wo4r1t
wo4r3u
w2o1t2
1w2öc
wört2h
wö4r1t
2w1p
w2r
w3ro
2w1s
w3s2k2
2w1t
wti2
w2u
1w2uc
wuch4sc
wuc4h
wu4c4h1s
wu4l1s2
wu1l
wu6n2s2
wun1
4w2ur.
wur2fa
w2u2r1f
1wur1st
wu4r1s
w2us4
1w2u2t1
1wüh
wüs4
2w1w
x1a
1xa.
2xa2b
1x2a1d
1x2a1e1
x2a1f3l2
1x2a1g
xa2m
x2a2n1z
1x2as
2x1b
2xc
x1ce
x1c4h
x1c4l2
4x1d
1xe
x1e4g
2xe1k
xe2l
x2e3lei
xe1le
x1e1m
3x2em.
x2en
xe6n3s2
x2er.
x2e1re
xe4r1s2
3xe1s
2x3eu
2x1f
2x1g
2x1h
x2i1b4
x2i1c
xic4h2
xi1de2
xi1d
xi2d1e1m
x1i2do
xie3l
x2i3g
xil1
xi1l2a
x2i2lo
xi2lu2
xi6n3s2
x2i1s
x2i2s1e
xi2s1o2
xi4s5s
xi3stä
xi1st
xi2su
x1i2tu
x1j
2x1k2
2x2l2
x3lä
x3le
2x1m
2x1n
x1or
4x1p
xpor6ter
x1p2o
xpo4r1t
x1q
2x1r
2x3s2
4x1t
x2t1a
x3ta.
x3t2as
xt1ä
x2tän
x2t1e2d
x2t1ei
x2te4n1t
x2t1e2r2f
x2t3e1v
xt1fi4
x2t1f4
x2t1i4l2l
xtr1a3b4
xt2r4
x2t3ran
x2t1s2
xt1u
x3t2ur
1xu
x2u1a
x1u2n1
x2u2s
2xv
2x1w
2xy
3xy.
3xy1s
x1z
2y1a1b
1yac
y1al.
y1a2m
ya4n2g
y1a2n1k
y1ät
y1b
y1c2
y2chi
yc4h
y3chi1s
y2c2h3n4
y1d4
y1e
y2e1f
ye4n4n
y2e1re
ye1s2
y2es.
ye4st
ye2t2h
ye1t
y1f2
y1g
ygi2
ygie5
yg2l
y1h
y2hr2
y1i4
y1j
y1k2
yke3n
y2k3s2
y1l
y2l3a2m
yl4ante
yla2n1t
y2l3c
y4le.
y1le
yli4n
y1li
yl4o3ni1
y1lo
y2l1u
ym2a2t
y1ma
y2m3p2
ym3pi1
y2n1o
y2no4d
y2n1t2
y1of
y2om2
y4o3n4i
y1o2n1t
y1o1s
y1o2u
y1p
yp2a2
yp3an
yp2e2
y2pf
y3ph
y2p1in
y1p2o3
y4p3s
y1r
y3r2e
y3ri
yri2a
yr2i1e
y3r4o
y2r1r2
y1s
ys2an
y1sa
y1s2c
y1se1
y3s2h
y4s3l2
ys1me3
y2s1m2
ys2p2a
ysp2
y1st4
y2s1u2
y3s2z
y1t2
y2te.
y2te2s
y3to
yu2r
yu1re3
y1v
y1w
y1y
y1z2
za2
2z3a1b
zab3l2
za3ch1a
zac4h
za3chä
z1a1d
2z1af
za3ge
za1g
za3g2r4
3zah
2z3ak
z2a1le3
z2a3li
2z1a4l1l
2z1am
z1an
z2a3ne
2z3a2n1f
3z2a3ni
2z3a2n3l2
za3no
za3ra
2z1a2r1b
2z1a2r1c
z2a3re
z2a3ri
z1a4r1m
z2a3ro
z1art2i
za4r1t
zar2t2r4
2z1a2r1z
z1as
za1st4
2z3a1t3
3z2a2u1b
za2u
z1au2f
z3a2u1g
3z4aun1
zä2
2z1äc
3z2äh
2z1äm
z1ä4r1g
z1ä4r1m
4z3b4
zb1ü1b
zbübe3
2z3c
2z3d2
zdan2
zdä1
2z1e2b2en
ze1b
2zecho
zec4h
2z1e2c4k
z4e1e
2z1e2f1f
ze1f
ze2i1k4
zei3la
ze4il
zei1le4
2z1ein
zei3s2
zei1st4
zei2t1a
ze2it
zeit5e2n1d
zei3te
zeite4n
zei4t3er
zei2t2r4
ze2l1a2
ze2len
ze1le
ze2l1er
ze2l1in
z2e1li
zell2a
ze4l1l
zel4leh
zel1le
zel4l2in
zel1li
zel3s1z
ze4l1s
zel3t2h
ze4l1t
z2e1lu2
2z1e2m1p
ze1m
5zen.
z2e4n3a2c
ze2n1a
ze4n3n
ze2no
zen1s2e
ze6n1s
z1en4se1m
3ze4n1t
zen4t3s
zen4zer
ze2n1z
z2er.
ze2r3a
ze2r1e2b
ze1re
2z1ergä
ze4r1g
4z3erge1b
zer1ge
z3erhal
ze2r3h4
ze2rha
2ze2r1h2ö
ze2r1i4n4t
z2e1ri
ze2r1k2
z2erl.
ze4r1l
2zer1l2ö
z2e4rn
zer4ne1b
zer3ne
zer4n3ei
2z1er1q
ze4r1s2
2z1er1s2a
4z3er3s2t2e
zer1st
zert1a4
ze4r1t
zer4t3a1g
zert4an
zer6te1re
zer4tin
z4erti
zer6tra2u
zert2r4
4zerwe2i
ze2r1w
2z1e2r1z
3z2erza2
ze2s
ze1s1e
ze1s1i
ze3sku
ze2s1k2
zessen4
ze4s1s
zes1se
zes6s5e2n1d
zes4ser
zes2sp2
zes1t2r4
ze1st
ze2ß1
z2e2t2r4
ze1t
2zet2ts
ze4t1t
2z1ex
2z1f4
2z1g2
zge1r2a
z1ge
2z1h
z2hen
zh2i2r3
3zi.
zi3a1lo
zi1a
z2ial
zi3ar
z2i2d3r4
zi1d
zi1e2r3h4
zi1es.
zie1s
3z2i1g
zi1l2e
z2i2m1m
2z1i2m1p
z2i1n2e
zin4er
2z1i2n3f
z1i2n1h2
zi2n1it
zi1ni
zin2sa
zi6n1s
zin4ser
zin1se
4z1in2s1u2f
zin3su
z1i2n1v2
z2i2o3
zi3o1p
z2i2r1k2
zir2k4s
zi3s2z
zi1s
zit2h
2z1j
2z3k4
2z1l2
2z1m2
zm4e2e
z1me
2z3n4
2z1o1b
2z1of
zo2gl
z2o1g
2z1oh
3z2ol
zon4ter
zo2n1t
zo2o
2zop2e
z1or
zo2ri
zor4ne
zo4rn
2z1o2s1z
zo1s
2z3o1t
2z1ö2f
z1öl
2z2ön
2z3p4
2z1q
2z3r2
4z1s2
z3sa
z3s1h
z3s1k2
z3st2r4
z1st
z3s1z
2z1t
z2t1a2u
z4te1he
z2teh
zte3st2r4
zte2s
zt2e1s2t
z3t2her
zt1h
z1th2e
zt3ho
zt1i6n1s
z2t3rec
zt2r4
zt1re
z2t3s2
z2u3a
z2u1b4
z2u4c
z2u1d4
zu1di4
zu2el
z2u1e
zu3f4
zu2g1ar
z2u1g
zu4ge4n1t
zu1ge
zu3gen
zu3g1l
zug1s1t
zu4gs
zug4st2e
zug1un1
zu1gu
2z1u2hr
zu1h
zuh2u
z2u1i
zu3k
2z1um.
zum2en2
zu1me
2z1u2m1f4
2z1u4m1g2
2z1u2m3l2
2z1u2m1s
zu3n2e
zun1
2z1u2n1t
zup2f1i
zu3r2a
z1u2r1k
2z1u4r1l
2z1u4r1s
2z1u4r1t2
z2u3s2
z2u3t2
z2u1z2
2z1üb
zü2r1c
2z1v
zw2
z1wac
zw2a
4z1wah
zwa2n2d1
z2wa4n1g
z1war
2z1was
4z1wäl
zw2ä
2z1we1g
z2we2i1g
zwe2i
z1wei1s
2z1wel
2z1wen
2z1wer
z2we4r1g
2z1we1s
2z1we1t
4z1wi4r
z2wit
2z1wo
z1wör
z1wur
zw2u
2z1wü
4z1z
z3z4a2
zze3s
z3z2o
zz2ö
PK
!<4OOOhyphenation/hyph_en_US.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 3
.a2ch4
.ad4der
.a2d
.ad1d4
.a2f1t
.a2f
.a4l3t
.am5at
.4a1ma
.an5c
.a2n
.2ang4
.an1i5m
.an1t4
.an3te
.anti5s
.ant2i
.a4r5s2
.2a2r
.ar4t2ie4
.ar1ti
.ar4ty
.as3c
.as1p
.a2s1s
.aster5
.a2tom5
.a1to
.au1d
.av4i
.awn4
.ba4g
.ba5na
.ba2n
.bas4e
.ber4
.be5r1a
.be3s1m
.4bes4
.b4e5s2to
.bri2
.but4ti
.bu4t3t2
.cam4pe
.1ca
.ca4m1p
.can5c
.ca2n
.capa5b
.ca1pa
.car5ol
.c2a2r
.ca4t
.ce4la
.2ch4
.chill5i
.ch4il2
.chil1l
.1ci2
.cit5r
.2c1it
.co3e2
.1co
.co4r
.cor5n1er
.corn2e
.de4moi2
.d4em
.de1mo
.de3o
.de3r1a
.de3r1i
.de1s4c
.des2
.dic1t2io5
.3di2c1t
.do4t
.1do
.du4c
.1du
.du4m1b5
.earth5
.ear2t
.e2a2r
.eas3i
.2e1b4
.eer4
.eg2
.e2l5d
.el3em
.enam3
.e1na
.en3g
.e2n3s2
.eq5ui5t
.e1q
.equ2
.eq2ui2
.er4ri
.er1r4
.es3
.4eu3
.eye5
.fes3
.for5mer
.1fo
.fo2r
.for1m
.for2me
.1ga2
.ge2
.gen3t4
.1gen
.ge5o2g
.1geo
.1g2i5a
.gi4b
.go4r
.1go
.hand5i
.ha2n
.h4and
.ha4n5k2
.he2
.hero5i2
.h2ero
.h1es3
.he4t3
.hi3b
.hi3er
.h2ie4
.hon5ey
.ho2n
.hon3o
.hov5
.id4l
.2id
.idol3
.i1do
.im3m
.im5p1i2n
.i4m1p
.im2pi
.in1
.in3ci
.2ine2
.4i4n2k2
.2i2n3s2
.ir5r4
.4ir
.is4i
.ju3r
.la4cy
.la4m
.lat5er
.l4ath5
.le2
.leg5e
.len4
.lep5
.lev1
.l2i4g
.li1g5a
.li2n
.l2i3o
.l1i4t
.ma1g5a5
.1ma
.mal5o
.ma1n5a
.ma2n
.mar5ti
.m2a2r
.me2
.mer3c
.me5ter
.me1te
.m2is1
.mis4t5i
.mon3e
.1mo
.mo2n
.mo3ro
.mo2r
.mu5ta
.1mu
.mu2ta5b
.ni4c
.od2
.od1d5
.of5te
.o2ft
.or5a1to
.o1ra
.or3c
.or1d
.or3t
.os3
.os4tl
.4oth3
.out3
.ou2
.ped5al
.2p2ed
.p2e2d2a
.pe5te
.pe2t
.pe5tit
.p2i4e4
.pio5n4
.3p2i1o
.pi2t
.pre3m
.pr2
.ra4c
.ran4t
.ra2n
.ratio5n1a
.ratio2n4
.ra1t2io
.ree2
.re5mit
.res2
.re5stat
.res2t
.res1ta
.r2i4g
.ri2t5u
.ro4q
.ros5t
.row5d
.ru4d
.3s4c2i3e4
.s1ci
.5se2l2f5
.5se2l4ff
.5se2l2fi
.5se2l2fl2
.sel1l5
.se2n
.se5r2ie4
.ser1i
.s2h2
.si2
.s3ing4
.2s1in
.st4
.sta5b2l2
.s1ta
.s2tab
.s4y2
.1ta4
.te4
.3ten5a2n
.te1na
.th2
.ti2
.til4
.ti1m5o5
.1tim
.ting4
.2t1in
.t4i4n5k2
.to1n4a
.1to
.to2n
.to4p
.top5i
.to2u5s
.tou2
.trib5ut
.tr4ib
.u1n1a
.un3ce
.under5
.un1de
.u2n1e
.u4n5k2
.un5o
.un3u4
.up3
.ure3
.us5a2
.2us
.ven4de
.ve5r1a
.wil5i
.wi2
.wil2
.ye4
4ab.
a5bal
a5ba2n
abe2
ab5erd
ab2i5a
ab5i2t5ab
abi2t
abi1ta
ab5lat
ab2l2
ab5o5l1iz
abol2i
4abr
ab5rog
ab3ul
a4c2a2r
a1ca
ac5ard
ac5aro
a5ceou2
ac1er
a5che4t
a2ch
ache2
4a2ci
a3c2ie4
a2c1in
a3c2io
ac5rob
act5if2
a2c1t
act5i4ff
act5i1fi
act5i3fl2
ac3ul
ac4um
a2d
ad4d1in
ad1d4
ad5er.
2adi
a3d4i3a
ad3i1ca
adi4er
ad2ie4
a3d2io
a3dit
a5di1u
ad4le
ad3ow
a1do
ad5ra2n
a1dr
ad4su
a2d1s2
4a1du
a3du2c
ad5um
ae4r
aer2i4e4
aer1i
a2f
a4ff4
a2fi
a2fl2
a4f1f4
a4gab
a1ga
aga4n
ag5el1l
a1ge4o
4ag4eu
ag1i
4ag4l2
ag1n
a2go
3a3g4o4g
ag3o3ni
ago2n2
a5guer
a2gue
ag5ul
a4gy
a3ha
a3he
a4h4l4
a3ho
ai2
a5i1a
a3ic.
ai5ly
a4i4n
ain5in
a2ini
a2i1n5o
ait5en
a2ite
a1j
ak1en
al5ab
al3a2d
a4l2a2r
4aldi4
a2ld
2ale
al3end
a4lent2i
a1len1t
a5le5o
al1i
al4ia.
al2i1a
al2i4e4
al5lev
al1l
al2le
4allic
all2i
4a2lm
a5log.
a4ly.
a1ly
4a2lys4
5a5lys1t
5alyt
3alyz
4a1ma
a2m5ab
am3ag
ama5ra
am2a2r
am5asc
a4ma3tis
a4m5a1to
am5er1a
am3ic
am5if
am5i4ff
am5i1fi
am5i3fl2
am5i1ly
am1in
am2i4no
a2mo
a5mo2n
amor5i
amo2r
amp5en
a4m1p
a2n
an3age
a1na
3ana1ly
a3n2a2r
an3ar3c
anar4i
a3nati
an2at
4and
ande4s2
an1de
an3dis1
an1dl
an4dow
an1do
a5nee
a3nen
an5e2st.
a1nes
a2nest
a3n4eu
2ang
ang5ie4
an1gl2
a4n1ic
a3nies
an2ie4
an3i3f
an3i4ff
an3i3fi
an3i3fl2
an4ime
an1im
a5nim1i
a5n2ine
an1in
an3i4o
a3n2ip
an3is2h
an3it
a3ni1u
an4kli
a4nk2
an1k1l
5anniz
a4n1n2
ano4
an5ot
an4oth5
an2sa2
a2n1s2
an4s1co
ans4c
an4s1n4
an2sp
ans3po
an4st
an4su2r
an1su
anta2l4
an1t
an1ta
an4t2ie4
ant2i
4an1to
an2tr
an4tw4
an3u1a
an3ul
a5nur
4ao
ap2a2r4
a1pa
ap5at
ap5er3o
a3ph4er
4aphi
a4pilla
apil1l
ap5ill2a2r
ap3i2n
ap3i1ta
a3pi2tu
a2p2l2
apo4c5
ap5o1la
apor5i
a1p4or
apos3t
a1pos
aps5e4s
a2p1s2
ap2se
a3pu
aque5
aqu2
2a2r
ar3a2c1t
a5rade
ara2d
ar5adis1
ar2adi
ar3al
a5rame1te
aram3et
ar2an4g
ara2n
ara3p
ar4at
a5ra1t2io
ar5a1t2iv
a5rau
ar5av4
araw4
arbal4
ar1b
ar4cha2n
ar1c
ar3cha
ar2ch
ar5d2ine
ard2i
ard1in4
ar4dr
ar5eas
a3ree
ar3en1t
a5r2e2ss
ar4fi
ar1f
ar4fi
ar4f4l2
ar4fl2
ar1i
ar5i2al
ar2i3a
ar3i2a2n
a3ri5et
ar2ie4
ar4im
ar5in2at
ar2i1na
ar3i1o
ar2iz
ar2mi
ar1m
ar5o5d
a5roni
aro2n
a3roo2
ar2p
ar3q
arre4
ar1r4
ar4sa2
a4rs2
ar2s2h
4as.
a2s4ab
asa2
as3an1t
asa2n
ashi4
as2h
a5sia.
as2i1a
a3si1b
a3sic
5a5si4t
ask3i
ask2
as4l2
a4soc
a1so
as5ph
as4s2h
a2ss
as3ten
as1t4r
asu1r5a
a1su
asu2r
a2ta
at3ab2l2
a2tab
at5ac
at3alo
ata2l
at5ap
ate5c
at5e2ch
at3e1go
ateg4
at3en.
at3er1a
ater5n
a5ter1na
at3est
at5ev
4ath
ath5em
ath2e
a5the2n
at4ho
ath5om
4ati.
a5t2i1a
a2t5i5b
at1ic
at3if2
at3i4ff
at3i1fi
at3i3fl2
ation5a2r
a1t2io
atio2n
atio1n1a
at3i1tu
a4tog
a1to
a2tom
at5om2iz
a4top
a4tos2
a1tr
at5rop
at4sk2
a4t1s2
at4tag
a4t3t2
at1ta
at5te
at4th
a2tu
at5u1a
a4t5ue
at3ul
at3u1ra
a2ty
au4b
augh3
au3gu
au4l2
aun5d
au3r
au5si1b
a2us
a4ut5en
au1th
a2va
av3ag4
a5va2n
av4e4no
av3er1a
av5ern
av5ery
av1i
avi4er
av2ie4
av3ig
av5oc
a1vor
3away
aw3i2
aw4ly
aws4
ax4i5c
ax3i
ax4id
ay5al
aye4
ays4
azi4er
a2z1i
az2ie4
az2z5i
a4z1z2
5ba.
bad5ger
ba2d
ba4ge
bal1a
ban5dag
ba2n
b4and
ban1d2a
ban4e
ban3i
barbi5
b2a2r
bar1b
bar2i4a
bar1i
bas4si
ba2ss
1bat
ba4z
2b1b
b2be
b3ber
bbi4na
4b1d
4be.
beak4
bea2t3
4be2d
b2e3d2a
be3de
b4e3di
be3gi
be5gu
1bel
be1l2i
be3lo
4be5m
be5n2ig
be5nu
4bes4
be3sp
b2e5st4r
3bet
be1t5iz
be5tr
be3tw4
be3w
be5y1o4
2bf
2b4ff
2b1fi
2bfl2
4b3h
bi2b
b2i4d
3b2ie4
bi5en
bi4er
2b3if
2b3i4ff
2b3i1fi
2b3i3fl2
1bil
bi3l2iz
bil1i
bin2a5r4
bi1na
b4in4d
bi5net
b2ine
bi3o2gr
b2io
bi5ou2
bi2t
3b2i3t2io
bi1ti
bi3tr
3bit5u1a
bi1tu
b5i4tz
b1j
bk4
b2l2
bl4ath5
b4le.
blen4
5ble1sp
bles2
b3lis
b4lo
blun4t
4b1m
4b3n
bne5g
3bod
bod3i
bo4e
bol3ic
bol2i
bom4bi
bo4m1b
bo1n4a
bo2n
bon5at
3boo2
5bor.
4b1o1ra
bor5d
5bore
5bori
5bos4
b5o1ta
b4oth5
bo4to
boun2d3
bou2
4bp
4brit
br4oth3
2b5s2
bsor4
b1so
2bt
b2t4l
b4to
b3tr
buf4fer1
bu4f1f
buffer1
bu4ff
bu4ga
bu3l2i
bu1mi4
bu4n
bunt4i
bun1t
bu3re
bus5ie4
b2us
buss4e
bu2ss
5bust
4bu1ta
3bu1t2io
b4u1t2i
b5u1to
b1v
4b5w
5by.
bys4
1ca
cab3in
ca1b2l2
ca2ch4
ca5den
ca2d
4cag4
2c5ah
ca3lat
cal4la
cal1l
cal2l5in4
call2i
4calo
c4an5d
ca2n
can4e
ca4n4ic
can5is
can3iz
can4ty
can1t
cany4
ca5per
car5om
c2a2r
cast5er
cas5t2ig
cast2i
4cas4y
c4a4th
4ca1t2iv
cav5al
ca2va
c3c
ccha5
c2ch
c3c2i4a
c1ci
ccom1pa5
c1co
cco4m1p
cco2n4
ccou3t
ccou2
2ce.
4ced.
4ce1den
3cei2
5cel.
3cel1l
1cen
3cenc
2cen4e
4ceni
3cen1t
3cep
ce5ram
cer1a
4ce1s4a2
3ces1si
c2e2ss
ces5si5b
ces5t
cet4
c5e4ta
cew4
2ch
4ch.
4ch3ab
5cha4n1ic
cha2n
ch5a5nis
che2
cheap3
4ch4ed
ch5e5lo
3chemi
ch5ene
che2n
ch3er.
ch3e4r1s2
4ch1in
5chi2ne.
ch2ine
ch5i5n2e2ss
chi1nes
5ch2ini
5ch2io
3chit
chi2z
3cho2
ch4ti
1ci
3c2i1a
ci2a5b
ci2a5r
ci5c
4cier
c2ie4
5c4i2f3ic.
ci1fi
5c4i2fic.
ci1fi
4c4i5i4
ci4la
3cil1i
2cim
2cin
c4i1na
3cin2at
cin3em
c2ine
c1ing
c5ing.
5c2i1no
cio2n4
c2io
4cipe4
c2ip
ci3ph
4cip4ic
cip3i
4cis1ta
4cis1t2i
2c1it
ci1t3iz
ci1ti
5ciz
ck1
ck3i
1c4l4
4cl2a2r
c5la5ra1t2io
clar4at
5clare
cle4m
4clic
clim4
c1ly4
c5n
1co
co5ag
c4oa
coe2
2cog
co4gr
coi4
co3inc
col5i
5colo
col3o4r
com5er
co2me
co1n4a
co2n
c4one
con3g
con5t
co3pa
cop3ic
co4p2l2
4cor1b
coro3n
cos4e
cov1
cove4
cow5a
co2z5e
co5z1i
c1q
cras5t
cr2as
5crat.
5crat1ic
cre3a2t
5c2r2ed
4c3re1ta
cre4v2
cri2
cri5f
cri5ff
cri5fi
cri5fl2
c4rin
cr2is4
5cri1ti
cro4p2l2
crop5o
cros4e
cru4d
4c3s2
2c1t
c2ta4b
c1ta
ct5ang
cta2n
c5tan1t
c2te
c3ter
c4t4ic1u
ctim3i
c1tim
ctu4r
c1tu
c4tw4
cud5
c4uf
c4u4ff
c4u1fi
c4u3fl2
c4ui2
cu5i1ty
5cul2i
cul4tis4
cul1ti
cu4lt
3c4ul1tu2
cu2ma
c3ume
cu4mi
3cun
cu3pi
cu5py
cu2r5a4b
cu1ra
cu5r2i3a
1c2us
cus1s4i
cu2ss
3c4ut
cu4t2ie4
c4u1t2i
4c5u1t2iv
4cutr
1cy
c2ze4
1d2a
5da.
2d3a4b
da2ch4
4da2f
4da4ff4
4da2fi
4da2fl2
2dag
da2m2
d2an3g
da2n
dard5
d2a2r
dark5
4dary
3dat
4da1t2iv
4da1to
5dav4
dav5e
5day
d1b
d5c
d1d4
2de.
dea2f5
dea4ff4
dea2fi
dea2fl2
de4b5i2t
d2e1b
de4bo2n
deca2n4
de1ca
de4cil
de1c2i
de5com
de1co
2d1ed
4dee.
de5if
dei2
de5i4ff
de5i1fi
de5i3fl2
del2i4e4
del2i
de4l5i5q
de5lo
d4em
5dem.
3demic
dem5ic.
de5mil
de4mo2n3s2
de1mo
demo2n
demo2r5
1den
de4n2a2r
de1na
d4e3no
denti5f2
den1t
dent2i
denti5ff
denti5fi
denti5fl2
de3nu
de1p
de3pa
depi4
de2pu
d3e1q
d4er1h4
5der3m4
d5ern5iz
de4r5s2
des2
d2es.
de1s2c
de2s5o
des3t2i
d2e3st4r
de4su
de1t
de2to
de1v
de2v3i4l
de1vi
4dey
4d1f
4d4ff
4d1fi
4d1fl2
d4ga
d3ge4t
dg1i
d2gy
d1h2
5di.
1d4i3a
dia5b
d4i4cam
di1ca
d4ice
3di2c1t
3d2id
5di3en
d2ie4
d1if
d1i4ff
d1i1fi
d1i3fl2
di3ge
d2ig
di4la1to
di1la
d1in
1di1na
3di2ne.
d2ine
5d2ini
di5niz
1d2io
dio5g
di4p2l2
d2ip
d4ir2
di1re
dir1t5i
dis1
5disi
d4is3t
d2i1ti
1d2i1v
d1j
d5k2
4d5la
3dle.
3dled
3dles.
dles2
4d3l2e2ss
2d3lo
4d5lu
2d1ly
d1m
4d1n4
1do
3do.
do5de
5doe
2d5of
2d5o4ff
2d5o2fi
2d5ofl2
d4og
do4la
dol2i4
do5lo4r
dom5iz
do3n2at
do2n
do1n1a
doni4
doo3d
doo2
do4p4p
d4or
3dos
4d5out
dou2
do4v
3dox
d1p
1dr
drag5o2n2
dra2go
4dr2ai2
dre4
dre2a5r
5dren
dr4i4b
dril4
dro4p
4drow
5drupli
dru3p2l2
4dry
2d1s2
ds4p
d4sw2
d4s4y
d2th
1du
d1u1a
du2c
d1u3ca
duc5er
4duct.
du2c1t
4duc4t1s2
du5el
du4g
d3ul4e
dum4be
du4m1b
du4n
4dup
du4pe
d1v
d1w
d2y
5dyn
dy4s2e
dys5p
e1a4b
e3a2c1t
ea2d1
ead5ie4
e2adi
ea4ge
ea5ger
ea4l
eal5er
e2ale
eal3ou2
eam3er
e5and
ea2n
ear3a
e2a2r
ear4c
ear5es
ear4ic
ear1i
ear4il
ear5k
ear2t
eart3e
ea5sp
e3a2ss
east3
ea2t
eat5en
eath3i
e4ath
e5at3if2
e5at3i4ff
e5at3i1fi
e5at3i3fl2
e4a3tu
ea2v
eav3en
eav5i
eav5o
2e1b
e4bel.
e1bel
e4be2l1s2
e4ben
e4bi2t
e3br
e4ca2d
e1ca
ecan5c
eca2n
ec1ca5
ec3c
e1ce
ec5es1sa2
ec2e2ss
e1c2i
e4cib
ec5ificat
eci1fi
ecifi1ca
ec5ificat
eci1fi
ecifi1ca
ec5i3f2ie4
ec5i3fie4
ec5i1fy
e2c3im
e2c1i4t
e5c2ite
e4clam
e1c4l4
e4cl2us
e2col
e1co
e4com1m
e4compe
eco4m1p
e4con1c
eco2n
e2cor
ec3o1ra
eco5ro
e1cr
e4crem
ec4ta2n
e2c1t
ec1ta
ec4te
e1cu
e4cul
ec3u1la
2e2d2a
4ed3d4
e4d1er
ede4s2
4edi
e3d4i3a
ed3ib
ed3i1ca
ed3im
ed1it
edi5z
4e1do
e4dol
edo2n2
e4dri
e1dr
e4dul
e1du
ed5u1l4o
ee2c
e4ed3i
ee2f
e1e4ff
ee2fi
ee2fl2
eel3i
ee4ly
ee2m
ee4na
ee4p1
ee2s4
eest4
ee4ty
e5ex
e1f
1e4ff
e1fi
e1fl2
e4f3ere
efer1
1e4f1f
e4fic
e1fi
e4fic
5ef2i1c4i
5efi1ci
efil4
efil4
e3f2i2ne
e2fin
e3fine
e2fin
ef5i5n2ite
ef2ini
efin2it
efi5n2ite
efini
efin2it
3efit
3efit
efor5es
e1fo
efo2r
e4fu4se.
e3fu
ef2us
4egal
e1ga
eger4
eg5ib
eg4ic
eg5ing
e5git5
eg5n
e4go.
e1go
e4gos
eg1ul
e5gur
5e1gy
e1h4
eher4
ei2
e5ic
e2i5d
e2ig2
ei5g4l2
e3i4m1b
e3in3f
e3in4ff
e3in3fi
e3in3fl2
e1ing
e5inst
e2i2n1s2
eir4d
e4ir
e2it3e
e2i3th
e5i1ty
e1j
e4jud
ej5udi
eki4n
ek1i
ek4la
ek1l
e1la
e4la.
e4lac
e3l4an4d
ela2n
e4l5a1t2iv
e4law
elax1a4
e3le2a
el5ebra
el2e1b
ele3br
5elec
e4led
el3e1ga
e5len
e4l1er
e1les2
e2l2f
e2l4ff
e2l2fi
e2l2fl2
el2i
e3libe4
e4l5ic.
el3i1ca
e3lier
el2ie4
el5i3gib
el2ig
el4igi
e5lim
e4l3ing
e3l2io
e2lis
el5is2h
e3l2iv3
4ella
el1l
el4lab
ell4o4
e5loc
el5og
el3op.
el2s2h
e2l1s2
el4ta
e4lt
e5lud
el5ug
e4mac
e1ma
e4mag
e5ma2n
em5a1na
e4m5b
e1me
e2mel
e4met
em3i1ca
em2i4e4
em5igra
em2ig4
emi1gr
em1in2
em5ine
em3i3ni
e4m2is
em5is2h
e5m4i2s1s
em3iz
5emniz
e4m1n
emo4g
e1mo
emo3n2i5o
emo2n
em3pi
e4m1p
e4mul
e1mu
em5u1la
emu3n2
e3my
en5a2mo
e1na
e4nan1t
en2a2n
ench4er
en2ch
enche2
en3dic
e5nea
e5nee
en3em
en5ero
en1er
en5e1si
e1nes
e2n5est
en3etr
e3ne4w
en5i4c3s2
e5n2ie4
e5nil
e3n2i4o
en3is2h
en3it
e5ni1u
5eniz
4e4n1n2
4eno
e4no4g
e4nos
en3ov
en4sw2
e2n1s2
ent5age
en1t
en1ta
4enth1es
enth2e
en3u1a
en5uf
en5u4ff
en5u1fi
en5u3fl2
e3ny.
4e4n3z
e5of
e5o4ff
e5o2fi
e5ofl2
eo2g
e4oi4
e3ol
eop3a2r
eo2pa
e1or
eo3re
eo5rol
eos4
e4ot
eo4to
e5out
eou2
e5ow
e2pa
e3p4ai2
ep5anc
epa2n
e5pel
e3pen1t
ep5e5t2i1t2io
epe2t
epeti1ti
ephe4
e4pli
e1p2l2
e1po
e4prec
epr2
ep5re1ca
e4p2r2ed
ep3re1h4
e3pro
e4prob
ep4s4h
e2p1s2
ep5ti5b
e2p1t
e4pu2t
ep5u1ta
e1q
equi3l
equ2
eq2ui2
e4q3ui3s
er1a
e2ra4b
4er4and
era2n
er3a2r
4er4ati.
2er1b
er4b2l2
er3ch
er1c
er4che2
2e2re.
e3re1a4l
ere5co
ere3in
erei2
er5el.
er3e1mo
er5e1na
er5ence
4erene
er3en1t
ere4q
er5e2ss
er3es2t
eret4
er1h4
er1i
e1r2i3a4
5erick1
e3rien
er2ie4
eri4er
er3in4e
e1r2i1o
4erit
er4i1u
er2i4v
e4ri1va
er3m4
er4nis4
4er3n2it
5erniz
er3no4
2ero
er5ob
e5r2oc
ero4r
er1ou2
e4r1s2
er3set
er2se
ert3er
4er2tl
er3tw4
4eru
eru4t
5erwau
er1w
e1s4a2
e4sa2ge.
e4sages
es2c
e2s1ca
es5ca2n
e3scr
es5cu
e1s2e
e2sec
es5e1cr
e4s5enc
e4sert.
e4ser4t1s2
e4ser1va
4es2h
e3sha
esh5e2n
e1si
e2sic
e2s2id
es5i1den
e4s5ig1n4a
es2ig
e2s5im
e2s4i4n
esis4te
e1sis
e5si4u
e5skin
esk2
esk1i
es4mi
e2s1m
e2sol
e1so
es3olu
e2so2n
es5o1n1a4
e1sp
e2s3per
es5pi1ra
esp4ir
es4pre
espr2
2e2ss
es4si4b
es1si
esta2n4
es1ta
es3t2ig
est2i
es5tim
4es2to
e3sto2n
2est4r
e5stro
estruc5
e2su2r
e1su
es5ur1r4
es4w2
e2ta4b
e1ta
e3ten4d
e3teo
ethod3
et1ic
e5tide
et2id
e2t1in4
et2i4no
e5t4ir
e5t2i1t2io
eti1ti
et5i1t2iv
4e2t1n2
et5o1n1a
e1to
eto2n
e3tra
e3tre
et3ric
et5rif
et5ri4ff
et5ri1fi
et5ri3fl2
et3rog
et5ros
et3u1a
e1tu
et5ym
e1ty
e4t5z
4eu
e5un
e3up
eu3ro
e2us4
eute4
euti5l
e4u1t2i
eu5tr
eva2p5
e1va
e2vas
ev5ast
e5vea
ev3el1l
eve4l3o
e5veng
even4i
ev1er
e5v2er1b
e1vi
ev3id
e2vi4l
e4v1in
e3v2i4v
e5voc
e5vu
e1wa
e4wag
e5wee
e3wh
ewil5
ewi2
ew3in4g
e3wit
1ex3p
5ey1c
5eye.
eys4
1fa
4ff
ffa
fa3b2l2
ffa3b2l2
f4ab3r
ff4ab3r
fa4ce
ffa4ce
4fag
ffag
fa4i4n4
fai2
ffa4i4n4
ffai2
fal2l5e
fal1l
ffal2l5e
ffal1l
4f4a4ma
ff4a4ma
fam5is
ffam5is
5f2a2r
ff2a2r
far5th
ffar5th
fa3ta
ffa3ta
fa3th2e
f4ath
ffa3th2e
ff4ath
4fa1to
ffa1to
fau4lt5
fau4l2
ffau4lt5
ffau4l2
4f5b
ff5b
4fd
ffd
4fe.
ffe.
feas4
ffeas4
fe4ath3
fea2t
ffe4ath3
ffea2t
f2e4b
ff2e4b
4fe1ca
ffe1ca
5fe2c1t
ffe2c1t
2fed
ffed
fe3l2i
ffe3l2i
fe4mo
ffe4mo
fen2d
ffen2d
fen1d5e
ffen1d5e
fer1
ffer1
5fer1r4
ffer1r4
fev4
ffev4
4f1f
f4fes
ffes
f4f2ie4
f1fi
ffie
f5f2in.
f2fin
ffin.
f2f5is
ffis
f4f2ly5
ff4l2
ffly
f2fy
ffy
4fh
ffh
1fi
1fi
f2i3a
fi1a
2f3ic.
2fic.
4f3ical
fi1ca
4fical
fi1ca
f3ica2n
fica2n
4ficate
4ficate
f3i1cen
fi1cen
fi3cer
fi3cer
f2i1c4i
fi1ci
5fi3c2i1a
5fi3c2i1a
5fic2ie4
5fic2ie4
4fi4c3s2
4fi4c3s2
fi3cu
fi3cu
fi5del
f2id
fid
fi5del
fight5
f2ig
fig
fight5
fil5i
fil1i
fil2l5in4
fil1l
fill2i
fil2l5in4
fil1l
fill2i
4fi1ly
4fi1ly
2fin
2fin
5fi1na
5fi1na
f4in2d5
find
f2i2ne
fine
f1in3g
fin3g
f2i4n4n2
fi4n1n2
fis4t2i
fis1t2i
f4l2
fl2
f5l2e2ss
fles2
fles2
fl2e2ss
flin4
flin4
flo3re
flo3re
f2ly5
fly
4fm
ffm
4fn
ffn
1fo
ffo
5fo2n
ffo2n
fon4de
f2ond
ffon4de
ff2ond
fon4t
ffon4t
fo2r
ffo2r
fo5rat
fo1ra
ffo5rat
ffo1ra
for5ay
ffor5ay
fore5t
ffore5t
for4i
ffor4i
for1t5a
ffor1t5a
fos5
ffos5
4f5p
ff5p
fra4t
ffra4t
f5rea
ff5rea
fres5c
ffres5c
fri2
ffri2
fril4
ffril4
frol5
ffrol5
2f3s
ff3s
2ft
fft
f4to
ff4to
f2ty
ff2ty
3fu
ffu
fu5el
ffu5el
4fug
ffug
fu4min
fu1mi
ffu4min
ffu1mi
fu5ne
ffu5ne
fu3ri
ffu3ri
fusi4
f2us
ffusi4
ff2us
fu2s4s
ffu2s4s
4fu1ta
ffu1ta
1fy
1ga
ga2f4
ga4ff4
ga2fi
ga2fl2
5gal.
3gal1i
ga3lo
2gam
ga5met
g5a2mo
gan5is
ga2n
ga3niz
gani5za1
4gano4
gar5n4
g2a2r
ga2ss4
g4ath3
4ga1t2iv
4gaz
g3b
gd4
2ge.
2ged
geez4
gel4in
gel2i
ge5lis
ge5l1iz
4ge1ly
1gen
ge4n2at
ge1na
g5e5niz
4g4eno
4geny
1geo
ge3om
g4ery
5ge1si
geth5
4ge1to
ge4ty
ge4v
4g1g2
g2ge
g3ger
gglu5
ggl2
g1go4
gh3in
gh5out
ghou2
gh4to
5gi.
1g2i4a
gi2a5r
g1ic
5gi3c2i1a
g2i1ci
g4i1co
gien5
g2ie4
5gies.
gil4
g3i1men
3g4in.
g4in5ge
5g4i2n1s2
5g2io
3g4ir
gir4l
g3is1l2
gi4u
5g2iv
3giz
gl2
gla4
gl2ad5i
gla2d
5glas
1gle
gli4b
g3l2ig
3glo
glo3r
g1m
g4my
g1n4a
g4na.
gne4t4t2
g1ni
g2n1in
g4n2i4o
g1no
g4no4n
1go
3go.
gob5
5goe
3g4o4g
go3is
goi2
go2n2
4g3o3n1a
gon5do5
g2ond
go3ni
5goo2
go5riz
gor5ou2
5gos.
gov1
g3p
1gr
4gra1d2a
gra2d
g4r2ai2
gra2n2
5gra4ph.
g5ra3ph4er
5graph1ic
gr4aphi
4g3ra1phy
4gray
gre4n
4gress.
gr2e2ss
4grit
g4ro
gruf4
gru4ff
gru1fi
gru3fl2
gs2
g5ste
gth3
gu4a
3guar2d
gu2a2r
2gue
5gui5t
g2ui2
3gun
3g2us
4gu4t
g3w
1gy
2g5y3n
gy5ra
h3ab4l2
ha2ch4
hae4m
hae4t
h5agu
ha3la
hala3m
ha4m
han4ci
ha2n
han4cy
5hand.
h4and
h2an4g
hang5er
han1g5o
h5a5niz
ha4n4k2
han4te
han1t
ha2p3l2
ha2p5t
ha3ra2n
h2a2r
ha5r2as
har2d
hard3e
har4le4
har1l
harp5en
har2p
har5ter
ha2s5s
haun4
5haz
haz3a1
h1b
1hea2d1
3he2a2r
he4ca2n
he1ca
h5ecat
h4ed
h4e5do5
he3l4i
hel4lis
hel1l
hell2i
hel4ly
h5elo
he4m4p
he2n
he1na4
hen5at
he1o5r
hep5
h4er1a
hera3p
her4ba
h2er1b
here5a
h3ern
h5er1ou2
h2ero
h3ery
h1es
he2s5p
he4t
he2t4ed
h4eu4
h1f
h4ff
h1fi
h1fl2
h1h
hi5a2n
h2i1a
hi4co
high5
h2ig
h4il2
himer4
h4i1na
hion4e
h2io
hio2n
h2i4p
hir4l
h4ir
hi3ro
hir4p
hir4r4
his3el
h4ise
h4i2s4s
hith5er
h2ith
hith2e
h2i2v
4hk
4h1l4
hla2n4
h2lo
hlo3ri
4h1m
hmet4
2h1n
h5odiz
h5o2d1s2
ho4g
ho1ge4
hol5a2r
ho1la
3hol4e
ho4ma
ho2me3
ho1n4a
ho2n
ho5ny
3hood
hoo2
hoo2n4
hor5at
ho1ra
ho5r2is
hort3e
ho5ru
hos4e
ho5sen
hos1p
1ho2us
hou2
house3
hov5el
4h5p
4hr4
hree5
hro5niz
hro2n
hro3po
4h1s2
h4s2h
h4t2a2r
h1ta
ht1en
ht5es
h4ty
hu4g
hu4min
hu1mi
hun5ke
hu4nk2
hun4t
hus3t4
h2us
hu4t
h1w
h4war4t
hw2a2r
hy3pe
hy3ph
hy2s
2i1a
i2al
fi2al
iam4
fiam4
iam5e1te
fiam5e1te
i2a2n
fi2a2n
4ianc
fianc
ian3i
fian3i
4ian4t
fian4t
ia5pe
fia5pe
ia2ss4
fia2ss4
i4a1t2iv
fi4a1t2iv
ia4tric
ia1tr
fia4tric
fia1tr
i4a2tu
fi4a2tu
ibe4
fibe4
ib3er1a
fib3er1a
ib5ert
fib5ert
ib5i1a
fib5i1a
ib3in
fib3in
ib5it.
ibi2t
fib5it.
fibi2t
ib5ite
fib5ite
i1b2l2
fi1b2l2
ib3li
fib3li
i5bo
fi5bo
i1br
fi1br
i2b5ri
fi2b5ri
i5bu4n
fi5bu4n
4icam
i1ca
ficam
5icap
ficap
4ic2a2r
fic2a2r
i4car.
fi4car.
i4cara
fi4cara
icas5
ficas5
i4cay
fi4cay
iccu4
ic3c
ficcu4
fic3c
4iceo
ficeo
4i2ch
fi2ch
2i1ci
i5c2id
fi5c2id
ic5i1na
i2cin
fic5i1na
fi2cin
i2c2ip
fi2c2ip
ic3i1pa
fic3i1pa
i4c1ly4
i1c4l4
fi4c1ly4
fi1c4l4
i2c5oc
i1co
fi2c5oc
fi1co
4i1cr
fi1cr
5icra
ficra
i4cry
fi4cry
ic4te
i2c1t
fic4te
fi2c1t
ic1tu2
fic1tu2
ic4t3u1a
fic4t3u1a
ic3u1la
fic3u1la
ic4um
fic4um
ic5uo
fic5uo
i3cur
fi3cur
2id
i4dai2
i1d2a
fi4dai2
fi1d2a
id5anc
ida2n
fid5anc
fida2n
id5d4
fid5d4
ide3a4l
fide3a4l
ide4s2
fide4s2
i2di
fi2di
id5i2a2n
i1d4i3a
fid5i2a2n
fi1d4i3a
idi4a2r
fidi4a2r
i5d2ie4
fi5d2ie4
i1d3io
fi1d3io
idi5ou2
fidi5ou2
id1it
fid1it
id5i1u
fid5i1u
i3dle
fi3dle
i4dom
i1do
fi4dom
fi1do
id3ow
fid3ow
i4dr
fi4dr
i2du
fi2du
id5uo
fid5uo
2ie4
fie4
ied4e
fied4e
5ie5ga
fie5ga
ie2ld3
fie2ld3
ie1n5a4
fie1n5a4
ien4e
fien4e
i5e4n1n2
fi5e4n1n2
i3ent2i
ien1t
fi3ent2i
fien1t
i1er.
fi1er.
i3es2c
fi3es2c
i1est
fi1est
i3et
fi3et
4if.
fif.
if5ero
ifer1
fif5ero
fifer1
iff5en
i4f1f
iff5en
i4ff
fiff5en
fi4ff
if4fr
iffr
fiffr
4i2f3ic.
i1fi
4i2fic.
i1fi
fi2fic.
1fi1fi
i3f2ie4
i3fie4
fi3fie4
i3f4l2
i3fl2
fi3fl2
4i2ft
fi2ft
2ig
iga5b
i1ga
figa5b
fi1ga
ig3er1a
fig3er1a
ight3i
fight3i
4igi
figi
i3gib
fi3gib
ig3il4
fig3il4
ig3in
fig3in
ig3it
fig3it
i4g4l2
fi4g4l2
i2go
fi2go
ig3or
fig3or
ig5ot
fig5ot
i5gre
i1gr
fi5gre
fi1gr
ig2u5i2
fig2u5i2
ig1ur
fig1ur
i3h
fi3h
4i5i4
fi5i4
i3j
fi3j
4ik
fik
i1la
fi1la
il3a4b
fil3a4b
i4l4ade
ila2d
fi4l4ade
fila2d
i2l5am
fi2l5am
ila5ra
il2a2r
fila5ra
fil2a2r
i3leg
fi3leg
il1er
fil1er
ilev4
filev4
i2l5f
fi2l5f
i2l5ff
fi2l5ff
i2l5fi
1fi2l5fi
i2l5fl2
fi2l5fl2
il1i
il3i1a
fil3i1a
il2ib
fil2ib
il3io
fil3io
il4ist
fil4ist
2il1it
fil1it
il2iz
fil2iz
ill5ab
il1l
fill5ab
4i2l1n2
fi2l1n2
il3o1q
fil3o1q
il4ty
i4lt
fil4ty
fi4lt
il5ur
fil5ur
il3v
fil3v
i4mag
i1ma
fi4mag
fi1ma
im3age
fim3age
ima5ry
im2a2r
fima5ry
fim2a2r
iment2a5r
i1men
i3men1t
imen1ta
fiment2a5r
fi1men
fi3men1t
fimen1ta
4imet
fimet
im1i
fim1i
im5i1d4a
im2id
fim5i1d4a
fim2id
imi5le
fimi5le
i5m2ini
fi5m2ini
4imit
fimit
im4ni
i4m1n
fim4ni
fi4m1n
i3mo2n
i1mo
fi3mo2n
fi1mo
i2mu
fi2mu
im3u1la
fim3u1la
2in.
fin.
i4n3au
i1na
fi4n3au
4inav
finav
incel4
fincel4
in3cer
fin3cer
4ind
in5dling
fin5dling
2ine
i3nee
fi3nee
in4er4a2r
in1er
iner1a
fin4er4a2r
fin1er
finer1a
i5n2e2ss
i1nes
fi5n2e2ss
fi1nes
4in1ga
fin1ga
4inge
finge
in5gen
fin5gen
4ingi
fingi
in5gling
ingl2
fin5gling
fingl2
4in1go
fin1go
4in1gu
fin1gu
2ini
fini
i5ni.
fi5ni.
i4n4i1a
fi4n4i1a
in3i4o
fin3i4o
in1is
fin1is
i5ni4te.
in2it
in2ite
fi5ni4te.
fin2it
fin2ite
5i3n2i1t2io
ini1ti
fi3n2i1t2io
fini1ti
in3i1ty
fin3i1ty
4i4nk2
fi4nk2
4i4n1l
fi4n1l
2i4n1n2
2i1no
fi1no
i4no4c
fi4no4c
ino4s
fino4s
i4not
fi4not
2i2n1s2
fi2n1s2
in3se
fin3se
insu1r5a
in1su
insu2r
finsu1r5a
fin1su
finsu2r
2int.
in1t
fint.
fin1t
2in4th
fin4th
in1u
fin1u
i5n2us
fi5n2us
4iny
finy
2io
fio
4io.
fio.
io1ge4
fio1ge4
io2gr
fio2gr
i1ol
fi1ol
io4m
fio4m
ion3at
io2n
io1n1a
fion3at
fio2n
fio1n1a
ion4ery
ion1er
fion4ery
fion1er
ion3i
fion3i
i2o5ph
fi2o5ph
ior3i
fior3i
i4os
fi4os
i4o5th
fi4o5th
i5oti
fi5oti
io4to
fio4to
i4our
iou2
fi4our
fiou2
2ip
fip
ipe4
fipe4
iphr2as4
ip4hr4
fiphr2as4
fip4hr4
ip3i
fip3i
ip4ic
fip4ic
ip4re4
ipr2
fip4re4
fipr2
ip3ul
fip3ul
i3qua
iqu2
fi3qua
fiqu2
iq5ue1f
fiq5ue1f
iq5u1e4ff
fiq5u1e4ff
iq5ue1fi
1fiq5ue1fi
iq5ue1fl2
fiq5ue1fl2
iq3u2id
iq2ui2
fiq3u2id
fiq2ui2
iq3ui3t
fiq3ui3t
4ir
fir
i1ra
fi1ra
i2ra4b
fi2ra4b
i4rac
fi4rac
ird5e
fird5e
ire4de
i2r2ed
fire4de
fi2r2ed
i4re1f
fi4re1f
i4r1e4ff
fi4r1e4ff
i4re3fi
1fi4re3fi
i4re1fl2
fi4re1fl2
i4rel4
fi4rel4
i4res
fi4res
ir5gi
irg2
fir5gi
firg2
ir1i
fir1i
iri5de
ir2id
firi5de
fir2id
ir4is
fir4is
iri3tu
firi3tu
5i5r2iz
fi5r2iz
ir4min
ir1m
fir4min
fir1m
iro4g
firo4g
5iron.
iro2n
firon.
firo2n
ir5ul
fir5ul
2is.
fis.
is5ag
isa2
fis5ag
fisa2
is3a2r
fis3a2r
isas5
fisas5
2is1c
fis1c
is3ch2
fis3ch2
4ise
fise
is3er
fis3er
3i4s3f
fi4s3f
3i4s4ff
fi4s4ff
3i4s3fi
1fi4s3fi
3i4s3fl2
fi4s3fl2
is5ha2n
is2h
fis5ha2n
fis2h
is3ho2n3
isho4
fis3ho2n3
fisho4
ish5op
fish5op
is3i1b
fis3i1b
is2i4d
fis2i4d
i5sis
fi5sis
is5i1t2iv
isi1ti
fis5i1t2iv
fisi1ti
4is4k2
fis4k2
isla2n4
is1l2
fisla2n4
fis1l2
4is4m1s2
i2s1m
fis4m1s2
fi2s1m
i2so
fi2so
iso5mer
i3som
iso2me
fiso5mer
fi3som
fiso2me
is1p
fis1p
is2pi
fis2pi
is4py
fis4py
4i2s1s
fi2s1s
is4sal
is1sa2
fis4sal
fis1sa2
issen4
fissen4
is4s1e4s
fis4s1e4s
is4ta.
is1ta
fis4ta.
fis1ta
is1te
fis1te
is1t2i
ist4ly
is2tl
fist4ly
fis2tl
4istral
ist4r
is1tra
fistral
fist4r
fis1tra
i2su
fi2su
is5us
fis5us
4i3ta.
i1ta
fi3ta.
fi1ta
ita4bi
i2tab
fita4bi
fi2tab
i4tag
fi4tag
4ita5m
fita5m
i3ta2n
fi3ta2n
i3tat
fi3tat
2ite
fite
it3er1a
fit3er1a
i5ter1i
fi5ter1i
it4es
fit4es
2ith
fith
i1ti
fi1ti
4i1t2i1a
fi1t2i1a
4i2tic
fi2tic
it3i1ca
fit3i1ca
5i5tick1
fi5tick1
i2t3ig
fi2t3ig
it5il1l
fit5il1l
i2tim
fi2tim
2i1t2io
fi1t2io
4itis
fitis
i4ti2s4m
fi4ti2s4m
i2t5o5m
i1to
fi2t5o5m
fi1to
4ito2n
fito2n
i4tram
i1tra
fi4tram
fi1tra
it5ry
fit5ry
4i4t3t2
fi4t3t2
it3u1at
i1tu
itu1a
fit3u1at
fi1tu
fitu1a
i5tud2
fi5tud2
it3ul
fit3ul
4itz.
i4tz
fitz.
fi4tz
i1u
fi1u
2iv
fiv
iv3el1l
fiv3el1l
iv3en.
fiv3en.
i4v3er.
fi4v3er.
i4vers.
ive4r1s2
fi4vers.
five4r1s2
iv5il.
i2vil
fiv5il.
fi2vil
iv5io
fiv5io
iv1it
fiv1it
i5vore
fi5vore
iv3o3ro
fiv3o3ro
i4v3ot
fi4v3ot
4i5w
fi5w
ix4o
fix4o
4iy
fiy
4iz2a2r2
iza1
fiz2a2r2
fiza1
i2z1i4
fi2z1i4
5izon1t
i1zo
izo2n
fizon1t
fi1zo
fizo2n
5ja
jac4q
ja4p
1je
je4r5s2
4jes4t2ie4
jest2i
4jes2ty
jew3
jo4p
5judg
3ka.
k3ab
k5ag
kais4
kai2
kal4
k1b
k2ed
1kee
ke4g
ke5l2i
k3en4d
k1er
kes4
k3e2st.
ke4ty
k3f
k4ff
k3fi
k3fl2
kh4
k1i
5ki.
5k2ic
k4il1l
kilo5
k4im
k4in.
kin4de
k4ind
k5i5n2e2ss
k2ine
ki1nes
kin4g
k2i4p
kis4
k5is2h
kk4
k1l
4k3ley
4k1ly
k1m
k5nes
1k2no
ko5r
kos2h4
k3ou2
kro5n
4k1s2
k4sc
ks4l2
k4s4y
k5t
k1w
lab3ic
flab3ic
l4abo
fl4abo
l4a2ci4
fl4a2ci4
l4ade
la2d
fl4ade
fla2d
la3d2y
fla3d2y
lag4n
flag4n
la2m3o
fla2m3o
3l4and
la2n
fl4and
fla2n
lan4dl
flan4dl
lan5et
flan5et
lan4te
lan1t
flan4te
flan1t
lar4g2
l2a2r
flar4g2
fl2a2r
lar3i
flar3i
las4e
flas4e
la5ta2n
la2ta
fla5ta2n
fla2ta
4latel2i4
flatel2i4
4la1t2iv
fla1t2iv
4lav
flav
la4v4a
fla4v4a
2l1b
fl1b
lbin4
flbin4
4l1c2
fl1c2
lce4
flce4
l3ci
fl3ci
2ld
fld
l2de
fl2de
ld4ere
fld4ere
ld4er1i
fld4er1i
ldi4
fldi4
ld5is1
fld5is1
l3dr
fl3dr
l4dri
fl4dri
le2a
fle2a
le4bi
l2e1b
fle4bi
fl2e1b
le2ft5
le1f
fle2ft5
fle1f
5leg.
fleg.
5le4g1g2
fle4g1g2
le4mat
le1ma
fle4mat
fle1ma
lem5at1ic
flem5at1ic
4len.
flen.
3lenc
flenc
5le2ne.
fle2ne.
1len1t
flen1t
le3ph
fle3ph
le4pr2
fle4pr2
le2ra5b
ler1a
fle2ra5b
fler1a
ler4e
fler4e
3lerg2
flerg2
3l4er1i
fl4er1i
l4ero
fl4ero
les2
le5s1co
les2c
fle5s1co
fles2c
5lesq
flesq
3l2e2ss
5less.
fless.
l3e1va
fl3e1va
lev4er.
lev1er
flev4er.
flev1er
lev4er1a
flev4er1a
lev4e4r1s2
flev4e4r1s2
3ley
fley
4leye
fleye
2lf
flf
2l4ff
fl4ff
2l1fi
fl1fi
2lfl2
fl2fl2
l5fr
fl5fr
4l1g4
fl1g4
l5ga
fl5ga
lg2a2r3
flg2a2r3
l4ges
fl4ges
l1go3
fl1go3
2l3h
fl3h
li4ag
l2i1a
fli4ag
fl2i1a
li2am4
fli2am4
liar5iz
li2a2r
liar1i
fliar5iz
fli2a2r
fliar1i
li4as
fli4as
li4a1to
fli4a1to
li5bi
fli5bi
5lic2io
l2i1ci
flic2io
fl2i1ci
li4cor
li1co
fli4cor
fli1co
4li4c3s2
fli4c3s2
4lict.
li2c1t
flict.
fli2c1t
l4icu
fl4icu
l3i1cy
fl3i1cy
l3i1d2a
l2id
fl3i1d2a
fl2id
lid5er
flid5er
3li2di
fli2di
lif3er1
flif3er1
l4i4f1f
l4i4ff
fl4i4ff
li4f4l2
li4fl2
fl2i4fl2
5ligate
l2ig
li1ga
fligate
fl2ig
fli1ga
3ligh
fligh
li4gra
li1gr
fli4gra
fli1gr
3l4ik
fl4ik
4l4i4l
fl4i4l
lim4b2l2
li4m1b
flim4b2l2
fli4m1b
lim3i
flim3i
li4mo
fli4mo
l4i4m4p
fl4i4m4p
l4i1na
fl4i1na
1l4ine
fl4ine
lin3ea
flin3ea
l2in3i
fl2in3i
link5er
l4i4nk2
flink5er
fl4i4nk2
li5og
l2io
fli5og
fl2io
4l4iq
fl4iq
lis4p
flis4p
l1it
fl1it
l2it.
fl2it.
5lit3i1ca
li1ti
l4i2tic
flit3i1ca
fli1ti
fl4i2tic
l5i5ti4c3s2
fl5i5ti4c3s2
liv3er
l2iv
fliv3er
fl2iv
l1iz
fl1iz
4lj
flj
lka3
flka3
l3kal4
fl3kal4
lka4t
flka4t
l1l
fl1l
l4law
fl4law
l2le
fl2le
l5le2a
fl5le2a
l3lec
fl3lec
l3leg
fl3leg
l3lel
fl3lel
l3le4n
fl3le4n
l3le4t
fl3le4t
ll2i
fll2i
l2lin4
fl2lin4
l5l4i1na
fl5l4i1na
ll4o
fll4o
lloq2ui5
llo1q
lloqu2
flloq2ui5
fllo1q
flloqu2
l2l5out
llou2
fl2l5out
fllou2
l5low
fl5low
2lm
flm
l5met
fl5met
lm3ing
flm3ing
l4mo2d1
l1mo
fl4mo2d1
fl1mo
lmo2n4
flmo2n4
2l1n2
fl1n2
3lo.
flo.
lob5al
flob5al
lo4ci
flo4ci
4lof
flof
4lo4ff
flo4ff
4lo2fi
flo2fi
4lofl2
fl2ofl2
3log1ic
flog1ic
l5o1go
fl5o1go
3logu
flogu
lom3er
lo2me
flom3er
flo2me
5long
lo2n
flong
flo2n
lon4i
flon4i
l3o3niz
fl3o3niz
lood5
loo2
flood5
floo2
5lo4pe.
flo4pe.
lop3i
flop3i
l3o4p1m
fl3o4p1m
lo1ra4
flo1ra4
lo4ra1to
flo4ra1to
lo5r2ie4
flo5r2ie4
lor5ou2
flor5ou2
5los.
flos.
los5et
flos5et
5los5o3phiz
lo2so
los4op
los2oph
flos5o3phiz
flo2so
flos4op
flos2oph
5los5o1phy
flos5o1phy
los4t
flos4t
lo4ta
flo4ta
loun5d
lou2
floun5d
flou2
2lout
flout
4lov
flov
2lp
flp
lpa5b
l1pa
flpa5b
fl1pa
l3pha
fl3pha
l5phi
fl5phi
lp5ing
lpi2n
flp5ing
flpi2n
l3pit
fl3pit
l4p2l2
fl4p2l2
l5pr2
fl5pr2
4l1r
fl1r
2l1s2
fl1s2
l4sc
fl4sc
l2se
fl2se
l4s2ie4
fl4s2ie4
4lt
flt
lt5ag
l1ta
flt5ag
fl1ta
ltane5
lta2n
fltane5
flta2n
l1te
fl1te
lten4
flten4
lter1a4
flter1a4
lth3i
flth3i
l5ties.
lt2ie4
fl5ties.
flt2ie4
ltis4
fltis4
l1tr
fl1tr
l1tu2
fl1tu2
ltu1r3a
fltu1r3a
lu5a
flu5a
lu3br
flu3br
lu2ch4
flu2ch4
lu3ci
flu3ci
lu3en
flu3en
luf4
fluf4
lu4ff
flu4ff
lu1fi
flu1fi
lu3fl2
fl2u3fl2
lu5id
l2ui2
flu5id
fl2ui2
lu4ma
flu4ma
5lu1mi
flu1mi
l5umn.
lu4m1n
fl5umn.
flu4m1n
5lum3n4i1a
flum3n4i1a
lu3o
flu3o
luo3r
fluo3r
4lup
flup
lu2ss4
l2us
flu2ss4
fl2us
lus3te
flus3te
1lut
flut
l5ven
fl5ven
l5vet4
fl5vet4
2l1w
fl1w
1ly
4lya
flya
4ly1b
fly1b
ly5me4
fly5me4
ly3no
fly3no
2lys4
flys4
l5y3s2e
fl5y3s2e
1ma
2mab
ma2ca
ma5ch2ine
ma2ch
ma4ch1in
ma4c4l4
mag5in
mag1i
5mag1n
2mah
ma2id5
mai2
4ma2ld
ma3l2ig
mal1i
ma5lin
mal4l2i
mal1l
mal4ty
ma4lt
5ma3n4i1a
ma2n
man5is
man3iz
4map
ma5ri2ne.
m2a2r
mar1i
mar2in4e
ma5r2iz
mar4ly
mar1l
mar3v
ma5sce
mas4e
mas1t
5mate
m4ath3
ma3tis
4mati3za1
ma1tiz
4m1b
m1ba4t5
m5bil
m4b3ing
mb2i4v
4m5c
4me.
2med
4med.
5me3d4i3a
m4edi
me3d2ie4
m5e5d2y
me2g
mel5o2n
me4l4t
me2m
me1m1o3
1men
me1n4a
men5ac
men4de
4mene
men4i
me2n1s4
men1su5
3men1t
men4te
me5o2n
m5er1sa2
me4r1s2
2mes
3mest2i
me4ta
met3a2l
me1te
me5thi
m4etr
5met3ric
me5tr2ie4
me3try
me4v
4m1f
4m4ff
4m1fi
4m1fl2
2mh
5mi.
m2i3a
mi1d4a
m2id
mid4g
m2ig4
3mil3i1a
mil1i
m5i5l2ie4
m4il1l
mi1n4a
3m4ind
m5i3nee
m2ine
m4ingl2
min5gli
m5ing1ly
min4t
m4in1u
miot4
m2io
m2is
mi4s4er.
m4ise
mis3er
mis5l2
mis4t2i
m5i4stry
mist4r
4m2ith
m2iz
4mk
4m1l
m1m
mma5ry
m1ma
mm2a2r
4m1n
m1n4a
m4n1in
mn4o
1mo
4mocr
5moc5ra1tiz
mo2d1
mo4go
mois2
moi2
mo4i5se
4m2ok
mo5lest
moles2
mo3me
mon5et
mo2n
mon5ge
mo3n4i3a
mon4i2s1m
mon1is
mon4ist
mo3niz
monol4
mo3ny.
mo2r
4mo5ra.
mo1ra
mos2
mo5sey
mo3sp
m4oth3
m5ouf
mou2
m5ou4ff
m5ou1fi
m5ou3fl2
3mo2us
mo2v
4m1p
mpara5
m1pa
mp2a2r
mpa5rab
mp4a4r5i
m3pe2t
mphas4
m2pi
mp2i4a
mp5ies
mp2ie4
m4p1i2n
m5p4ir
mp5is
mpo3ri
m1p4or
mpos5ite
m1pos
m4po2us
mpou2
mpov5
mp4tr
m2p1t
m2py
4m3r
4m1s2
m4s2h
m5si
4mt
1mu
mul2a5r4
mu1la
5mu4lt
mul1ti3
3mum
mun2
4mup
mu4u
4mw
1na
2n1a2b
n4abu
4nac.
na4ca
n5a2c1t
nag5er.
nak4
na4l1i
na5l2i1a
4na4lt
na5mit
n2a2n
nan1ci4
nan4it
na4nk4
nar3c
n2a2r
4nare
nar3i
nar4l
n5ar1m
n4as
nas4c
nas5t2i
n2at
na3ta2l
na2ta
nat5o5m2iz
na2tom
na1to
n2au
nau3se
na2us
3naut
nav4e
4n1b4
nc2a2r5
n1ca
n4ces.
n3cha
n2ch
n5cheo
nche2
n5ch4il2
n3chis
n2c1in
n1ci
n2c4it
ncou1r5a
n1co
ncou2
n1cr
n1cu
n4dai2
n1d2a
n5da2n
n1de
nd5e2st.
ndes2
ndi4b
n5d2if
n5d2i4ff
n5d2i1fi
n5d2i3fl2
n1dit
n3diz
n5du2c
n1du
ndu4r
nd2we
nd1w
2ne.
n3e2a2r
n2e2b
neb3u
ne2c
5neck1
2ned
ne4gat
ne1ga
ne4g5a1t2iv
5nege
ne4la
nel5iz
nel2i
ne5mi
ne4mo
1nen
4nene
3neo
ne4po
ne2q
n1er
ne2ra5b
ner1a
n4er3a2r
n2ere
n4er5i
ner4r4
1nes
2nes.
4ne1sp
2nest
4nes4w2
3net1ic
ne4v
n5eve
ne4w
n3f
n4ff
n3fi
n3fl2
n4gab
n1ga
n3gel
nge4n4e
n1gen
n5gere
n3ger1i
ng5ha
n3gib
ng1in
n5git
n4gla4
ngl2
ngov4
n1go
ng5s2h
ngs2
n1gu
n4gum
n2gy
4n1h4
nha4
nhab3
nhe4
3n4i1a
ni3a2n
ni4ap
ni3ba
ni4b2l2
n2i4d
ni5di
ni4er
n2ie4
ni2fi
ni2fi
ni5ficat
nifi1ca
ni5ficat
nifi1ca
n5i1gr
n2ig
n4ik4
n1im
ni3m2iz
nim1i
n1in
5ni2ne.
n2ine
nin4g
n2i4o
5n2is.
nis4ta
n2it
n4ith
3n2i1t2io
ni1ti
n3itor
ni1to
ni3tr
n1j
4nk2
n5k2ero
nk1er
n3ket
nk3in
nk1i
n1k1l
4n1l
n5m
nme4
nmet4
4n1n2
nne4
nni3al
n3n4i1a
nn2i4v
nob4l2
no3ble
n5o1c4l4
4n3o2d
3noe
4nog
no1ge4
nois5i
noi2
no5l4i
5nol1o1gis
3nomic
n5o5m2iz
no4mo
no3my
no4n
non4ag
no1n1a
non5i
n5oniz
4nop
5nop5o5l2i
no2r5ab
no1ra
no4rary
nor2a2r
4nos2c
nos4e
nos5t
no5ta
1nou2
3noun
nov3el3
nowl3
n1p4
npi4
npre4c
npr2
n1q
n1r
nru4
2n1s2
n2s5ab
nsa2
nsati4
ns4c
n2se
n4s3e4s
ns2id1
ns2ig4
n2s1l2
n2s3m
n4soc
n1so
ns4pe
n5spi
nsta5b2l2
ns1ta
ns2tab
n1t
n2ta4b
n1ta
nte4r3s2
nt2i
n5ti2b
nti4er
nt2ie4
nti2f2
nti4ff
nti2fi
nti3fl2
n3t2ine
n2t1in
n4t3ing
nt2i4p
ntrol5l2i
ntrol1l
n4t4s2
ntu3me
n1tu
n3tum
nu1a
nu4d
nu5en
nuf4fe
nu4f1f
nuffe
nu4ff
n3ui4n
n2ui2
3nu3it
n4um
nu1me
n5u1mi
3nu4n
n3uo
nu3tr
n1v2
n1w4
nym4
nyp4
4nz
n3za1
4oa
oa2d3
o5a5les2
o2ale
oard3
o2a2r
oas4e
oast5e
oat5i
ob3a3b
o5b2a2r
o1be4l
o1bi
o2bin
ob5ing
o3br
ob3ul
o1ce
o2ch4
o3che4t
oche2
ocif3
o1ci
oci4ff
oci1fi
oci3fl2
o4cil
o4clam
o1c4l4
o4cod
o1co
oc3rac
oc5ra1tiz
ocre3
5ocrit
ocri2
octo2r5a
o2c1t
oc1to
oc3u1la
o5cure
od5d1ed
od1d4
od3ic
o1d2i3o
o2do4
od4or3
o4d5uct.
o1du
odu2c
odu2c1t
o4d5uc4t1s2
o4el
o5eng
o3er
oe4ta
o3ev
o2fi
o2fi
of5ite
ofite
of4i4t4t2
ofi4t4t2
o2g5a5r
o1ga
o4g5a1t2iv
o4ga1to
o1ge
o5gene
o1gen
o5geo
o4ger
o3g2ie4
1o1gis
og3it
o4gl2
o5g2ly
3ogniz
og1ni
o4g4ro
o1gr
og2u5i2
1o1gy
2o2g5y3n
o1h2
ohab5
oi2
oic3es
oi3der
o2id
oi4f1f4
oi4ff4
o2ig4
oi5let
o3ing
oint5er
oin1t
o5i2s1m
oi5so2n
oi2so
oist5en
ois1te
oi3ter
o2ite
o5j
2ok
o3ken
ok5ie4
ok1i
o1la
o4la2n
ola2ss4
o2l2d
ol2d1e
ol3er
o3les2c
oles2
o3let
ol4fi
o2lf
o2l4fi
ol2i
o3l2i1a
o3lice
ol5id.
ol2id
o3li4f
o3l4i4ff
o3li4fi
o3li4fl2
o5l4i4l
ol3ing
o5l2io
o5l2is.
ol3is2h
o5l2ite
ol1it
o5l2i1t2io
oli1ti
o5l2iv
oll2i4e4
ol1l
oll2i
ol5o3giz
olo4r
ol5p2l2
o2lp
o4l2t
ol3ub
ol3ume
ol3un
o5l2us
ol2v
o2ly
o2m5ah
o1ma
oma5l
om5a1tiz
om2be
o4m1b
om4b2l2
o2me
om3e1n4a
o1men
om5er2se
ome4r1s2
o4met
om5e3try
om4etr
o3m2i3a
om3ic.
om3i1ca
o5m2id
om1in
o5m2ini
5ommend
om1m
om1men
omo4ge
o1mo
o4mo2n
om3pi
o4m1p
ompro5
ompr2
o2n
o1n1a
on4ac
o3n2a2n
on1c
3oncil
on1ci
2ond
on5do
o3nen
o2n5est
o1nes
on4gu
on1ic
o3n2i4o
on1is
o5ni1u
on3key
o4nk2
on4odi
o4n3o2d
on3o3my
o2n3s2
on5spi4
onspi1r5a
onsp4ir
on1su4
onten4
on1t
on3t4i
onti2f5
onti4ff
onti2fi
onti3fl2
on5um
on1va5
on1v2
oo2
ood5e
ood5i
o2o4k
oop3i
o3ord
oost5
o2pa
o2p2e5d
op1er
3oper1a
4op4erag
2oph
o5pha2n
o5ph4er
op3ing
opi2n
o3pit
o5po2n
o4posi
o1pos
o1pr2
op1u
opy5
o1q
o1ra
o5ra.
o4r3ag
or5al1iz
oral1i
or5an4ge
ora2n
or2ang
ore5a
o5re1a4l
or3ei2
or4e5s2h
or5e2st.
ores2t
orew4
or4gu
org2
4o5r2i3a
or3i1ca
o5ril
or1in
o1r2i1o
or3i1ty
o3ri1u
or2mi
or1m
orn2e
o5rof
o5ro4ff
o5ro2fi
o5rofl2
or3oug
orou2
or5pe
or1p
3orrh4
or1r4
or4se
o4rs2
ors5en
orst4
or3thi
or3thy
or4ty
o5rum
o1ry
os3al
osa2
os2c
os4ce
o3scop
os1co
4oscopi
o5scr
os4i4e4
os5i1t2iv
osi1ti
os3i1to
os3i1ty
o5si4u
os4l2
o2so
o2s4pa
os4po
os2ta
o5stati
os5til
ost2i
os5tit
o4ta2n
o1ta
otele4g
ot3er.
ot5e4r1s2
o4tes
4oth
oth5e1si
oth2e
oth1es
oth3i4
ot3ic.
ot5i1ca
o3tice
o3tif2
o3ti4ff
o3ti1fi
o3ti3fl2
o3tis
oto5s2
o1to
ou2
ou3b2l2
ouch5i
ou2ch
ou5et
ou4l
ounc5er
oun2d
ou5v2
ov4en
over4ne
ove4r3s2
ov4ert
o3vis
o4vi1ti4
o5v4ol
ow3der
ow3el
ow5est3
ow1i2
own5i
o4wo2
oy1a
1pa
pa4ca
pa4ce
pa2c4t
p4a2d
5paga4n
pa1ga
p3agat
p4ai2
pa4i4n4
p4al
pa1n4a
pa2n
pan3el
pan4ty
pan1t
pa3ny
pa1p
pa4pu
para5b2l2
p2a2r
pa2rab
par5age
par5d2i
3pare
par5el
p4a4r1i
par4is
pa2te
pa5ter
5pathic
p4ath
pa5thy
pa4tric
pa1tr
pav4
3pay
4p1b
pd4
4pe.
3pe4a
pear4l
pe2a2r
pe2c
2p2ed
3pede
3p4edi
pe3d4i3a4
ped4ic
p4ee
pee4d
pek4
pe4la
pel2i4e4
pel2i
pe4n2a2n
pe1na
p4enc
pen4th
pen1t
pe5o2n
p4era.
per1a
pera5b2l2
pe2ra4b
p4erag
p4er1i
peri5st
per2is
per4mal
per3m4
per1ma
per2me5
p4ern
p2er3o
per3ti
p4e5ru
per1v
pe2t
pe5ten
pe5tiz
4pf
4p4ff
4p1fi
4pfl2
4pg
4ph.
phar5i
ph2a2r
ph4e3no
phe2n
ph4er
ph4es.
ph1es
ph1ic
5ph2ie4
ph5ing
5phis1t2i
3phiz
p4h2l4
3phob
3phone
pho2n
5phoni
pho4r
4p4h1s2
ph3t
5phu
1phy
p2i3a
pi2a2n4
pi4c2ie4
p2i1ci
pi4cy
p4id
p5i1d2a
pi3de
5pi2di
3piec
p2ie4
pi3en
pi4grap
p2ig
pi1gr
pi3lo
pi2n
p4in.
p4ind4
p4i1no
3p2i1o
pio2n4
p3ith
pi5tha
pi2tu
2p3k2
1p2l2
3pla2n
plas5t
pl2i3a
pli5er
pl2ie4
4pl2ig
pli4n
ploi4
plu4m
plu4m4b
4p1m
2p3n
po4c
5pod.
po5em
po3et5
5po4g
poin2
poi2
5poin1t
poly5t
po2ly
po4ni
po2n
po4p
1p4or
po4ry
1pos
po2s1s
p4ot
po4ta
5poun
pou2
4p1p
ppa5ra
p1pa
pp2a2r
p2pe
p4p2ed
p5pel
p3pen
p3per
p3pe2t
ppo5s2ite
p1pos
pr2
pray4e4
5pre1c2i
pre5co
pre3e2m
pre4f5ac
pre1f
pre1fa
pre4la
pr1e3r4
p3re1s2e
3pr2e2ss
pre5ten
pre3v2
5pr2i4e4
prin4t3
pr2i4s
pri2s3o
p3ro1ca
pr2oc
prof5it
pro2fi
profit
pro2fi
pro3l
pros3e
pro1t
2p1s2
p2se
ps4h
p4si1b
2p1t
p2t5a4b
p1ta
p2te
p2th
p1ti3m
ptu4r
p1tu
p4tw4
pub3
pue4
puf4
pu4ff
pu1fi
pu3fl2
pu4l3c2
pu4m
pu2n
pur4r4
5p2us
pu2t
5pute
put3er
pu3tr
put4t1ed
pu4t3t2
put4t1in
p3w
qu2
qua5v4
2que.
3quer
3quet
2rab
ra3bi
rach4e2
ra2ch
r5a1c4l4
raf5fi
ra2f
ra4f1f4
raffi
ra2f4t
r2ai2
ra4lo
ram3et
r2ami
ra3ne5o
ra2n
ran4ge
r2ang
r4ani
ra5no4
rap3er
3ra1phy
rar5c
r2a2r
rare4
rar5e1f
rar5e4ff
rar5e3fi
rar5e1fl2
4raril
rar1i
r2as
ratio2n4
ra1t2io
rau4t
ra5vai2
ra2va
rav3el
ra5z2ie4
ra2z1i
r1b
r4bab
r4bag
rbi2
r2b3i4f
r2b3i4ff
r2b3i4fi
r2b3i4fl2
r2bin
r5b2ine
rb5ing.
rb4o
r1c
r2ce
r1cen4
r3cha
r2ch
rch4er
rche2
r4ci4b
r1ci
r2c4it
rcum3
r4dal
r1d2a
rd2i
r1d4i4a
rdi4er
rd2ie4
rd1in4
rd3ing
2re.
re1a4l
re3a2n
re5ar1r4
re2a2r
5rea2v
re4aw
r5ebrat
r2e1b
re3br
rec5ol1l
re2col
re1co
re4c5ompe
reco4m1p
re4cre
re1cr
2r2ed
re1de
re3dis1
r4edi
red5it
re4fac
re1f
re1fa
re2fe
re5fer.
refer1
re3fi
re3fi
re4fy
reg3is
re5it
rei2
re1l2i
re5lu
r4en4ta
ren1t
ren4te
re1o
re5pi2n
re4posi
re1po
re1pos
re1pu
r1er4
r4er1i
r2ero4
r4e5ru
r4es.
re4spi
re1sp
res4s5i4b
r2e2ss
res1si
res2t
re5s2ta2l
res1ta
r2e3st4r
re4ter
re4ti4z
re3tri
r4eu2
re5u1t2i
rev2
re4val
re1va
rev3el
r5ev5er.
rev1er
re5ve4r1s2
re5vert
re5vi4l
re1vi
rev5olu
re4wh
r1f
r4ff
r1fi
r1fl2
r3fu4
r4fy
rg2
rg3er
r3get
r3g1ic
rgi4n
rg3ing
r5gis
r5git
r1gl2
rgo4n2
r1go
r3gu
rh4
4rh.
4rhal
r2i3a
ria4b
ri4ag
r4ib
rib3a
ric5as5
ri1ca
r4ice
4r2i1ci
5ri5c2id
ri4c2ie4
r4i1co
rid5er
r2id
ri3enc
r2ie4
ri3en1t
ri1er
ri5et
rig5a2n
r2ig
ri1ga
5r4igi
ril3iz
ril1i
5rima2n
ri1ma
rim5i
3ri1mo
rim4pe
ri4m1p
r2i1na
5rina.
r4in4d
r2in4e
rin4g
r2i1o
5riph
r2ip
riph5e
ri2p2l2
rip5lic
r4iq
r2is
r4is.
r2is4c
r3is2h
ris4p
ri3ta3b
ri1ta
r5ited.
r2ite
ri2t1ed
rit5er.
rit5e4r1s2
r4i2t3ic
ri1ti
ri2tu
rit5ur
riv5el
r2iv
riv3et
riv3i
r3j
r3ket
rk4le
rk1l
rk4lin
r1l
rle4
r2led
r4l2ig
r4lis
rl5is2h
r3lo4
r1m
rma5c
r1ma
r2me
r3men
rm5e4r1s2
rm3ing
r4ming.
r4m2io
r3mit
r4my
r4n2a2r
r1na
r3nel
r4n1er
r5net
r3ney
r5nic
r1nis4
r3n2it
r3n2iv
rno4
r4nou2
r3nu
rob3l2
r2oc
ro3cr
ro4e
ro1fe
ro5fil
ro2fi
ro5fil
ro2fi
r2ok2
ro5k1er
5role.
rom5e1te
ro2me
ro4met
rom4i
ro4m4p
ron4al
ro2n
ro1n1a
ron4e
ro5n4is
ron4ta
ron1t
1room
roo2
5root
ro3pel
rop3ic
ror3i
ro5ro
ro2s5per
ro2s4s
ro4th2e
r4oth
ro4ty
ro4va
rov5el
rox5
r1p
r4pe4a
r5pen1t
rp5er.
r3pe2t
rp4h4
rp3ing
rpi2n
r3po
r1r4
rre4c
rre4f
rr1e4ff
rre4fi
rre4fl2
r4re1o
rre4s2t
rr2i4o
rr2i4v
rro2n4
rros4
rrys4
4rs2
r1sa2
rsa5ti
rs4c
r2se
r3sec
rse4cr
r4s5er.
rs3e4s
r5se5v2
r1s2h
r5sha
r1si
r4si4b
rso2n3
r1so
r1sp
r5sw2
rta2ch4
r1ta
r4tag
r3t2e1b
r3ten4d
r1te5o
r1ti
r2t5i2b
rt2i4d
r4tier
rt2ie4
r3t2ig
rtil3i
rtil4l
r4ti1ly
r4tist
r4t2iv
r3tri
rtr2oph4
rt4s2h4
r4t1s2
ru3a
ru3e4l
ru3en
ru4gl2
ru3i4n
r2ui2
rum3p2l2
ru4m2p
ru2n
ru4nk5
run4ty
run1t
r5usc2
r2us
ru2t1i5n
r4u1t2i
rv4e
rvel4i
r3ven
rv5er.
r5vest
rv4e2s
r3vey
r3vic
r3v2i4v
r3vo
r1w
ry4c
5rynge
ryn5g
ry3t
sa2
2s1ab
5sack1
sac3ri2
s3a2c1t
5sai2
sa4l2a2r4
s4a2l4m
sa5lo
sa4l4t
3sanc
sa2n
san4de
s4and
s1ap
sa5ta
5sa3t2io
sa2t3u
sau4
sa5vor
5saw
4s5b
scan4t5
s1ca
sca2n
sca4p
scav5
s4ced
4s3cei2
s4ces
s2ch2
s4cho2
3s4c2ie4
s1ci
5sc4in4d
s2cin
scle5
s1c4l4
s4cli
scof4
s1co
sco4ff
sco2fi
scofl2
4scopy5
scou1r5a
scou2
s1cu
4s5d
4se.
se4a
seas4
sea5w
se2c3o
3se2c1t
4s4ed
se4d4e
s5edl
se2g
se1g3r
5sei2
se1le
5se2l2f
5se2l4ff
5se2l2fi
5se2l2fl2
5selv
4se1me
se4mol
se1mo
sen5at
se1na
4senc
sen4d
s5e2ned
sen5g
s5en1in
4sen4t1d
sen1t
4sen2tl
se2p3a3
4s1er.
s4er1l
s2er4o
4ser3vo
s1e4s
s4e5s2h
ses5t
5se5um
s4eu
5sev
sev3en
sew4i2
5sex
4s3f
4s4ff
4s3fi
4s3fl2
2s3g
s2h
2sh.
sh1er
5shev
sh1in
sh3io
3sh2i4p
sh2i2v5
sho4
sh5o2l2d
sho2n3
shor4
short5
4sh1w
si1b
s5ic3c
3si2de.
s2id
5side4s2
5si2di
si5diz
4sig1n4a
s2ig
sil4e
4si1ly
2s1in
s2i1na
5si2ne.
s2ine
s3ing
1s2io
5sio2n
sio1n5a
s4i2r
si1r5a
1sis
3s2i1t2io
si1ti
5si1u
1s2iv
5siz
sk2
4ske
s3ket
sk5ine
sk1i
sk5in4g
s1l2
s3lat
s2le
sl2ith5
sl1it
2s1m
s3ma
smal1l3
sma2n3
smel4
s5men
5s4m2ith
smo2l5d4
s1mo
s1n4
1so
so4ce
so2ft3
so4lab
so1la
so2l3d2
so3lic
sol2i
5sol2v
3som
3s4on.
so2n
so1n1a4
son4g
s4op
5soph1ic
s2oph
s5o3phiz
s5o1phy
sor5c
sor5d
4sov
so5vi
2s1pa
5sp4ai2
spa4n
spen4d
2s5peo
2sper
s2phe
3sph4er
spho5
spil4
sp5ing
spi2n
4s3p2i1o
s4p1ly
s1p2l2
s4po2n
s1p4or4
4sp4ot
squal4l
squ2
s1r
2ss
s1sa2
ssas3
s2s5c
s3sel
s5sen5g
s4ses.
ss1e4s
s5set
s1si
s4s2ie4
ssi4er
s4s5i1ly
s4s1l2
ss4li
s4s1n4
sspen4d4
ss2t
ssu1r5a
s1su
ssu2r
ss5w2
2st.
s2tag
s1ta
s2ta2l
stam4i
5st4and
sta2n
s4ta4p
5stat.
s4t1ed
stern5i
s5t2ero
ste2w
ste1w5a
s3th2e
st2i
s4ti.
s5t2i1a
s1tic
5s4tick1
s4t2ie4
s3tif2
s3ti4ff
s3ti1fi
s3ti3fl2
st3ing
s2t1in
5st4ir
s1tle
s2tl
5stock1
s1to
sto2m3a
5stone
sto2n
s4top
3store
st4r
s4tra2d
s1tra
5stra2tu
s4tray
s4tr2id
4stry
4st3w4
s2ty
1su
su1al
su4b3
su2g3
su5is
s2ui2
suit3
s4ul
su2m
su1m3i
su2n
su2r
4sv
sw2
4s1wo2
s4y
4sy1c
3syl
syn5o
sy5rin
1ta
3ta.
2tab
ta5bles2
tab2l2
5tab5o5l1iz
tabol2i
4t4a2ci
ta5do
ta2d
4ta2f4
4ta4ff4
4ta2fi
4ta2fl2
tai5lo
tai2
ta2l
ta5la
tal5en
t2ale
tal3i
4talk
tal4lis
tal1l
tall2i
ta5log
ta5mo
tan4de
ta2n
t4and
1tan1ta3
tan1t
ta5per
ta5p2l2
tar4a
t2a2r
4tar1c
4tare
ta3r2iz
tar1i
tas4e
ta5s4y
4tat1ic
ta4tur
ta2tu
taun4
tav4
2taw
tax4is
tax3i
2t1b
4tc
t4ch
tch5e4t
tche2
4t1d
4te.
te2ad4i
tea2d1
4tea2t
te1ce4
5te2c1t
2t1ed
t4e5di
1tee
teg4
te5ger4
te5gi
3tel.
tel2i4
5te2l1s2
te2ma2
tem3at
3ten2a2n
te1na
3tenc
3tend
4te1nes
1ten1t
ten4tag
ten1ta
1teo
te4p
te5pe
ter3c
5ter3d
1ter1i
ter5ies
ter2ie4
ter3is
teri5za1
5t4er3n2it
ter5v
4tes.
4t2e2ss
t3ess.
teth5e
3t4eu
3tex
4tey
2t1f
2t4ff
2t1fi
2t1fl2
4t1g
2th.
tha2n4
th2e
4thea
th3eas
the5a2t
the3is
thei2
3the4t
th5ic.
th5i1ca
4th4il2
5th4i4nk2
4t4h1l4
th5ode
5thod3ic
4thoo2
thor5it
tho5riz
2t4h1s2
1t2i1a
ti4ab
ti4a1to
2ti2b
4tick1
t4i1co
t4ic1u
5ti2di
t2id
3tien
t2ie4
tif2
ti4ff
ti1fi
ti3fl2
ti5fy
2t2ig
5tigu
til2l5in4
til1l
till2i
1tim
4ti4m1p
tim5ul
ti2mu
2t1in
t2i1na
3ti2ne.
t2ine
3t2ini
1t2io
ti5oc
tion5ee
tio2n
5tiq
ti3sa2
3t4ise
ti2s4m
ti5so
tis4p
5tisti1ca
tis1t2i
tis1tic
ti3tl
ti4u
1t2iv
ti1v4a
1tiz
ti3za1
ti3ze4n
ti2ze
2tl
t5la
tla2n4
3tle.
3tled
3tles.
tles2
t5let.
t5lo
4t1m
tme4
2t1n2
1to
to3b
to5crat
4to2do4
2tof
2to4ff
2to2fi
2tofl2
to2gr
to5ic
toi2
to2ma
to4m4b
to3my
ton4a4l1i
to2n
to1n1a
to3n2at
4tono
4tony
to2ra
to3r2ie4
tor5iz
tos2
5tour
tou2
4tout
to3w2a2r
4t1p
1tra
t2ra3b
tra5ch
tr4a2ci4
tra2c4it
trac4te
tra2c1t
tr2as4
tra5ven
trav5e2s5
tre5f
tr1e5ff
tre5fi
tre5fl2
tre4m
trem5i
5tr2i3a
tri5ces
tr4ice
5tri3c2i1a
t4r2i1ci
4tri4c3s2
2trim
tr2i4v
tro5m4i
tron5i
tro2n
4trony
tro5phe
tr2oph
tro3sp
tro3v
tr2u5i2
tr2us4
4t1s2
t4sc
ts2h4
t4sw2
4t3t2
t4tes
t5to
t1tu4
1tu
tu1a
tu3a2r
tu4b4i
tud2
4tue
4tuf4
4tu4ff
4tu1fi
4tu3fl2
5t2u3i2
3tum
tu4nis
tu1ni
2t3up.
3ture
5turi
tur3is
tur5o
tu5ry
3t2us
4tv
tw4
4t1wa
twis4
twi2
4t1wo2
1ty
4tya
2tyl
type3
ty5ph
4tz
t2z4e
4uab
uac4
ua5na
ua2n
uan4i
uar5an1t
u2a2r
uara2n
uar2d
uar3i
uar3t
u1at
uav4
ub4e
u4bel
u3ber
u4b2ero
u1b4i
u4b5ing
u3b4le.
ub2l2
u3ca
uci4b
u1ci
u2c4it
ucle3
u1c4l4
u3cr
u3cu
u4cy
ud5d4
ud3er
ud5est
udes2
ude1v4
u1dic
ud3ied
ud2ie4
ud3ies
ud5is1
u5dit
u4do2n
u1do
ud4si
u2d1s2
u4du
u4ene
ue2n1s4
uen4te
uen1t
uer4il
uer1i
3u1fa
u3f4l2
u3fl2
ugh3e2n
ug5in
2ui2
uil5iz
uil1i
ui4n
u1ing
uir4m
u4ir
ui1ta4
u2iv3
ui4v4er.
u5j
4uk
u1la
ula5b
u5lati
ul2ch4
u4l1c2
5ulche2
ul3der
u2ld
ul2de
ul4e
u1len
ul4gi
u4l1g4
ul2i
u5l2i1a
ul3ing
ul5is2h
ul4l2a2r
ul1l
ul4li4b
ull2i
ul4lis
4u2l3m
u1l4o
4u2l1s2
uls5e4s
ul2se
ul1ti
u4lt
ul1tra3
ul1tr
4ul1tu2
u3lu
ul5ul
ul5v
u2m5ab
u1ma
um4bi
u4m1b
um4b1ly
umb2l2
u1mi
u4m3ing
umor5o
u1mo
umo2r
u4m2p
un2at4
u1na
u2ne
un4er
u1ni
un4im
u2n1in
un5is2h
un2i3v
u2n3s4
un4sw2
un2t3a4b
un1t
un1ta
un4ter.
un4tes
unu4
un5y
u4n5z
u4o4rs2
u5os
u1ou2
u1pe
upe4r5s2
u5p2i3a
up3ing
upi2n
u3p2l2
u4p3p
upport5
up1p4or
up2t5i2b
u2p1t
up1tu4
u1ra
4ura.
u4rag
u4r2as
ur4be
ur1b
ur1c4
ur1d
ure5a2t
ur4fer1
ur1f
ur4fr
u3rif
u3ri4ff
u3ri1fi
u3ri3fl2
uri4fic
uri1fi
uri4fic
ur1in
u3r2i1o
u1rit
ur3iz
ur2l
url5ing.
ur4no4
uros4
ur4pe
ur1p
ur4pi
urs5er
u4rs2
ur2se
ur5tes
ur3th2e
ur1ti4
ur4t2ie4
u3ru
2us
u5sa2d
usa2
u5sa2n
us4ap
usc2
us3ci
use5a
u5s2i1a
u3sic
us4lin
us1l2
us1p
us5s1l2
u2ss
us5tere
us1t4r
u2su
usu2r4
u2ta4b
u1ta
u3tat
4u4te.
4utel
4uten
uten4i
4u1t2i
uti5l2iz
util1i
u3t2ine
u2t1in
ut3ing
utio1n5a
u1t2io
utio2n
u4tis
5u5tiz
u4t1l
u2t5of
u1to
u2t5o4ff
u2t5o2fi
u2t5ofl2
uto5g
uto5mat1ic
uto2ma
u5to2n
u4tou2
u4t1s4
u3u
uu4m
u1v2
ux1u3
u2z4e
1va
5va.
2v1a4b
vac5il
v4a2ci
vac3u
vag4
va4ge
va5l2i4e4
val1i
val5o
val1u
va5mo
va5niz
va2n
va5pi
var5ied
v2a2r
var1i
var2ie4
3vat
4ve.
4ved
veg3
v3el.
vel3l2i
vel1l
ve4lo
v4e1ly
ven3om
v4eno
v5enue
v4erd
5v2e2re.
v4erel
v3eren
ver5enc
v4eres
ver3ie4
ver1i
vermi4n
ver3m4
3ver2se
ve4r1s2
ver3th
v4e2s
4ves.
ves4te
ve4te
vet3er
ve4ty
vi5al1i
v2i1a
vi2al
5vi2a2n
5vi2de.
v2id
5vi2d1ed
4v3i1den
5vide4s2
5vi2di
v3if
v3i4ff
v3i1fi
v3i3fl2
vi5gn
v2ig
v4ik4
2vil
5v2il1it
vil1i
v3i3l2iz
v1in
4vi4na
v2inc
v4in5d
4ving
vi1o3l
v2io
v3io4r
vi1ou2
v2i4p
vi5ro
v4ir
vis3it
vi3so
vi3su
4vi1ti
vit3r
4vi1ty
3v2iv
5vo.
voi4
3v2ok
vo4la
v5ole
5vo4l2t
3vol2v
vom5i
vo2r5ab
vo1ra
vori4
vo4ry
vo4ta
4vo1tee
4vv4
v4y
w5ab2l2
2wac
wa5ger
wa2g5o
wait5
wai2
w5al.
wam4
war4t
w2a2r
was4t
wa1te
wa5ver
w1b
wea5r2ie4
we2a2r
wear1i
we4ath3
wea2t
we4d4n4
weet3
wee5v
wel4l
w1er
west3
w3ev
whi4
wi2
wil2
wil2l5in4
wil1l
will2i
win4de
w4ind
win4g
w4ir4
3w4ise
w2ith3
wiz5
w4k
wl4es2
wl3in
w4no
1wo2
wom1
wo5v4en
w5p
wra4
wri4
wri1ta4
w3s2h
ws4l2
ws4pe
w5s4t
4wt
wy4
x1a
xac5e
x4a2go
xam3
x4ap
xas5
x3c2
x1e
xe4cu1to
xe1cu
xe3c4ut
x2ed
xer4i
x2e5ro
x1h
xhi2
xh4il5
xhu4
x3i
x2i5a
xi5c
xi5di
x2id
x4ime
xi5m2iz
xim1i
x3o
x4ob
x3p
xp4an4d
x1pa
xpa2n
xpec1to5
xpe2c
xpe2c1t
x2p2e3d
x1t2
x3ti
x1u
xu3a
xx4
y5ac
3y2a2r4
y5at
y1b
y1c
y2ce
yc5er
y3ch
ych4e2
ycom4
y1co
ycot4
y1d
y5ee
y1er
y4er1f
y4er4ff
y4er1fi
y4er1fl2
yes4
ye4t
y5gi
4y3h
y1i
y3la
ylla5b2l2
yl1l
y3lo
y5lu
ymbol5
y4m1b
yme4
ym1pa3
y4m1p
yn3c4hr4
yn2ch
yn5d
yn5g
yn5ic
5ynx
y1o4
yo5d
y4o5g
yom4
yo5net
yo2n
y4o2n3s2
y4os
y4p2ed
yper5
yp3i
y3po
y4po4c
yp2ta
y2p1t
y5pu
yra5m
yr5i3a
y3ro
yr4r4
ys4c
y3s2e
ys3i1ca
y1s3io
3y1sis
y4so
y2ss4
ys1t
ys3ta
ysu2r4
y1su
y3thin
yt3ic
y1w
za1
z5a2b
z2a2r2
4zb
2ze
ze4n
ze4p
z1er
z2e3ro
zet4
2z1i
z4il
z4is
5zl
4zm
1zo
zo4m
zo5ol
zoo2
zte4
4z1z2
z4zy
.as9s8o9c8i8a8te.
.as1so
.asso1ci
.asso3c2i1a
.as9s8o9c8i8a8t8es.
.de8c9l8i9n8a9t8i8on.
.de1c4l4
.decl4i1na
.declin2at
.declina1t2io
.declinatio2n
.ob8l8i8g9a9t8o8ry.
.ob2l2
.obl2ig
.obli1ga
.obliga1to
.obligato1ry
.ph8i8l9a8n9t8h8r8o8p8ic.
.ph4il2
.phi1la
.phila2n
.philan1t
.philant4hr4
.philanthrop3ic
.pr8e8s8e8nt.
.p3re1s2e
.presen1t
.pr8e8s8e8n8ts.
.presen4t4s2
.pr8o8j8e8ct.
.pro5j
.pro1je
.proje2c1t
.pr8o8j8e8c8ts.
.projec4t1s2
.re8c9i9p8r8o8c9i9t8y.
.re1c2i
.rec2ip
.recipr2
.recipr2oc
.re1cipro1ci
.recipro2c1it
.reciproci1ty
.re9c8o8g9n8i9z8a8n8ce.
.re1co
.re2cog
.rec3ogniz
.recog1ni
.recogniza1
.recogniza2n
.re8f9o8r9m8a9t8i8on.
.re1f
.re1fo
.refo2r
.refor1m
.refor1ma
.reforma1t2io
.reformatio2n
.re8t9r8i9b8u9t8i8on.
.re3tri
.retr4ib
.retri3bu1t2io
.retrib4u1t2i
.retributio2n
.ta9b8le.
.2tab
.tab2l2
.ac8a8d9e9m8y.
.a1ca
.aca2d
.acad4em
.acade3my
.ac8a8d9e9m8i8e8s.
.academ2i4e4
.ac9c8u9s8a9t8i8v8e.
.ac3c
.ac1c2us
.accusa2
.accusa1t2iv
.ac8r8o9n8y8m.
.acro2n
.acronym4
.ac8r8y8l9a8m8i8d8e.
.acry3la
.acrylam2id
.ac8r8y8l9a8m8i8d8e8s.
.acrylamide4s2
.ac8r8y8l9a8l8d8e9h8y8d8e.
.acryla2ld
.acrylal2de
.acrylalde1h4
.acrylaldehy1d
.ad8d9a9b8l8e.
.ad1d2a
.ad2d3a4b
.addab2l2
.ad8d9i9b8l8e.
.addi1b2l2
.ad8r8e8n9a9l8i8n8e.
.a1dr
.adre4
.a5dren
.adre1na
.adrena4l1i
.adrena1l4ine
.ae8r8o9s8p8a8c8e.
.ae4r
.a2ero
.aero2s4pa
.aerospa4ce
.af9t8e8r9t8h8o8u8g8h8t.
.afterthou2
.af9t8e8r9t8h8o8u8g8h8t8s.
.afterthough4t1s2
.ag8r8o8n9o9m8i8s8t.
.a1gr
.ag4ro
.agro2n
.agronom2is
.ag8r8o8n9o9m8i8s8t8s.
.agronomis4t1s2
.al9g8e9b8r8a9i9c8a8l9l8y.
.a4l1g4
.alg2e1b
.alge3br
.algebr2ai2
.algebrai1ca
.algebraical1l
.algebraical1ly
.am9p8h8e8t9a9m8i8n8e.
.a4m1p
.amphe4t
.amphe1ta
.amphetam1in
.amphetam2ine
.am9p8h8e8t9a9m8i8n8e8s.
.amphetami1nes
.an9a9l8y8s8e.
.3ana1ly
.a1na
.an4a2lys4
.anal5y3s2e
.an9a9l8y8s8e8d.
.analy4s4ed
.an8a8l8y9s8e8s.
.analys1e4s
.an9i8s8o9t8r8o8p9i8c.
.ani2so
.anisotrop3ic
.an9i8s8o9t8r8o8p9i9c8a8l9l8y.
.anisotropi1ca
.anisotropical1l
.anisotropical1ly
.an9i8s8o8t9r8o9p8i8s8m.
.anisotropi2s1m
.an9i8s8o8t9r8o8p8y.
.anisotropy5
.an8o8m9a8l8y.
.ano4
.anoma5l
.ano1ma
.anoma1ly
.an8o8m9a8l8i8e8s.
.anomal1i
.anomal2i4e4
.an8t8i9d8e8r8i8v9a9t8i8v8e.
.ant2id
.antider1i
.antider2i4v
.antide4ri1va
.antideri3vat
.antider2iva1t2iv
.an8t8i9d8e8r8i8v9a9t8i8v8e8s.
.antiderivativ4e2s
.an8t8i9h8o8l8o9m8o8r9p8h8i8c.
.anti3h
.antiholo1mo
.antiholomo2r
.antiholomor1p
.antiholomorp4h4
.antiholomorph1ic
.an9t8i8n9o9m8y.
.an2t1in
.ant2i1no
.antino3my
.an9t8i8n9o9m8i8e8s.
.antinom2ie4
.an9t8i9n8u9c8l8e8a8r.
.antin1u
.antinucle3
.antinu1c4l4
.antinucle2a
.antinucle2a2r
.an9t8i9n8u9c8l8e9o8n.
.antinucleo2n
.an9t8i9r8e8v9o9l8u9t8i8o8n9a8r8y.
.ant4ir
.antirev2
.antirev5olu
.antirevo1lut
.antirevol4u1t2i
.antirevolutio1n5a
.antirevolu1t2io
.antirevolutio2n
.antirevolution2a2r
.ap8o8t8h9e9o9s8e8s.
.ap4ot
.ap4oth
.apoth2e
.apotheos4
.apotheos1e4s
.ap8o8t8h9e9o9s8i8s.
.apotheo1sis
.ap9p8e8n9d8i8x.
.a4p1p
.ap2pe
.ap3pen
.ar9c8h8i9m8e9d8e8a8n.
.ar1c
.ar2ch
.archi2med
.archimedea2n
.ar9c8h8i9p8e8l9a8g8o.
.arch2i4p
.archipe4
.archipe4la
.archipela2go
.ar9c8h8i9p8e8l9a9g8o8s.
.ar9c8h8i8v8e.
.arch2i2v
.ar9c8h8i8v8e8s.
.archiv4e2s
.ar9c8h8i8v9i8n8g.
.archiv1in
.archi4ving
.ar9c8h8i8v9i8s8t.
.ar9c8h8i8v9i8s8t8s.
.archivis4t1s2
.ar9c8h8e9t8y8p9a8l.
.arche2
.arche4t
.arche1ty
.archety1pa
.archetyp4al
.ar9c8h8e9t8y8p9i9c8a8l.
.archetyp3i
.archetypi1ca
.ar8c9t8a8n9g8e8n8t.
.ar2c1t
.arct5ang
.arc1ta
.arcta2n
.arctan1gen
.arctangen1t
.ar8c9t8a8n9g8e8n8t8s.
.arctangen4t4s2
.as9s8i8g8n9a9b8l8e.
.as1si
.as4sig1n4a
.ass2ig
.assig2n1a2b
.assignab2l2
.as9s8i8g8n9o8r.
.assig1no
.as9s8i8g8n9o8r8s.
.assigno4rs2
.as9s8i8s8t9a8n8t9s8h8i8p.
.as1sis
.assis1ta
.assista2n
.assistan1t
.assistan4t4s2
.assistants2h4
.assistant3sh2i4p
.as9s8i8s8t9a8n8t9s8h8i8p8s.
.assistantshi2p1s2
.as8y8m8p9t8o9m8a8t8i8c.
.as4y
.asy4m1p
.asym2p1t
.asymp1to
.asympto2ma
.asymptomat1ic
.as9y8m8p9t8o8t9i8c.
.as8y8n9c8h8r8o9n8o8u8s.
.asyn3c4hr4
.asyn2ch
.asynchro2n
.asynchro1nou2
.asynchrono2us
.at8h9e8r9o9s8c8l8e9r8o9s8i8s.
.4ath
.ath2e
.ath2ero
.atheros2c
.atheroscle5
.atheros1c4l4
.ath2eroscl4ero
.atherosclero1sis
.at9m8o8s9p8h8e8r8e.
.a4t1m
.at1mo
.atmos2
.atmo3sp
.atmos2phe
.atmo3sph4er
.at9m8o8s9p8h8e8r8e8s.
.at9t8r8i8b9u8t8e8d.
.a4t3t2
.attr4ib
.attribu2t1ed
.at9t8r8i8b9u8t9a8b8l8e.
.attri4bu1ta
.attribu2ta4b
.attributab2l2
.au9t8o9m8a9t8i8o8n.
.au1to
.auto2ma
.automa1t2io
.automatio2n
.au9t8o8m9a9t8o8n.
.au1toma1to
.automato2n
.au9t8o8m9a9t8a.
.automa2ta
.au9t8o9n8u8m9b8e8r9i8n8g.
.au5to2n
.auton5um
.autonu4m1b
.autonumber1i
.autonumberin4g
.au9t8o8n9o9m8o8u8s.
.au4tono
.autono4mo
.autono3mo2us
.autonomou2
.au8t8o9r8o8u8n8d9i8n8g.
.autorou2
.autoroun2d
.autoround1in
.av9o8i8r9d8u9p8o8i8s.
.avoi4
.avo4ir
.avoir1du
.avoir4dup
.avoi2rdupoi2
.ba8n8d9l8e8a8d8e8r.
.b4and
.ban1dl
.bandle2a
.bandlea2d1
.ba8n8d9l8e8a8d8e8r8s.
.bandleade4r5s2
.ba8n8k9r8u8p8t.
.ba4nk2
.bankru2p1t
.ba8n8k9r8u8p8t9c8y.
.bankrup4tc
.bankrupt1cy
.ba8n8k9r8u8p8t9c8i8e8s.
.bankrupt1ci
.bankruptc2ie4
.ba8r9o8n8i8e8s.
.b2a2r
.ba5roni
.baro2n
.baron2ie4
.ba8s8e9l8i8n8e9s8k8i8p.
.basel2i
.base1l4ine
.baseli1nes
.baselinesk2
.baselinesk1i
.baselinesk2i4p
.ba9t8h8y8m9e9t8r8y.
.1bat
.b4ath
.bathyme4
.bathym4etr
.bathyme3try
.ba8t8h8y9s8c8a8p8h8e.
.bathy2s
.bathys4c
.bathysca4p
.bathys1ca
.be8a8n9i8e8s.
.bea2n
.bea3nies
.bean2ie4
.be9h8a8v9i8o8u8r.
.be1h4
.behav1i
.behavi1ou2
.behav2io
.behavi4our
.be9h8a8v9i8o8u8r8s.
.behaviou4rs2
.be8v8i8e8s.
.be1vi
.bev2ie4
.bi8b9l8i9o8g9r8a9p8h8y9s8t8y8l8e.
.bi2b
.bi1b2l2
.bib3li
.bibli5og
.bibl2io
.biblio2gr
.biblio4g3ra1phy
.bibliography2s
.bibliographys1t
.bibliographys2ty
.bibliographys2tyl
.bi9d8i8f9f8e8r9e8n9t8i8a8l.
.b2i4d
.bi2di
.bid1if
.bidi4f1f
.bidiffer1
.bidiffer3en1t
.bidifferent2i
.bidifferen1t2i1a
.bidifferenti2al
.bi9d8i8ff8e8r9e8n9t8i8a8l.
.bid1i4ff
.bidiffer1
.bidiffer3en1t
.bidifferent2i
.bidifferen1t2i1a
.bidifferenti2al
.bi8g9g8e8s8t.
.b2ig
.bi4g1g2
.big2ge
.bi8l8l9a8b8l8e.
.1bil
.bill5ab
.bil1l
.billab2l2
.bi8o9m8a8t8h9e9m8a8t9i8c8s.
.b2io
.bio4m
.bio1ma
.biom4ath3
.biomath5em
.biomath2e
.bio1mathe1ma
.biomathemat1ic
.biomathemati4c3s2
.bi8o9m8e8d9i9c8a8l.
.bio2me
.bio2med
.biom4edi
.biomed3i1ca
.bi8o9m8e8d9i9c8i8n8e.
.biomed2i1ci
.biomedi2cin
.biomedic2ine
.bi8o9r8h8y8t8h8m8s.
.biorh4
.biorhyt4h1m
.biorhyth4m1s2
.bi8t9m8a8p.
.bi2t
.bi4t1m
.bit1ma
.bit4map
.bi8t9m8a8p8s.
.bitma2p1s2
.bl8a8n8d9e8r.
.b2l2
.b3l4and
.bla2n
.blan1de
.bl8a8n8d9e8s8t.
.blande4s2
.bl8i8n8d9e8r.
.bl4ind
.blin1de
.bl8o8n8d8e8s.
.b4lo
.blo2n
.bl2ond
.blon1de
.blondes2
.bl8u8e9p8r8i8n8t.
.bluepr2
.blueprin4t3
.bl8u8e9p8r8i8n8t8s.
.blueprin4t4s2
.bo9l8o8m9e9t8e8r.
.bolo2me
.bolo4met
.bolome1te
.bo8o8k9s8e8l8l9e8r.
.3boo2
.bo2o4k
.boo4k1s2
.booksel1l
.booksel2le
.bo8o8k9s8e8l8l9e8r8s.
.bookselle4r1s2
.bo8o8l9e8a8n.
.boole2a
.boolea2n
.bo8o8l9e8a8n8s.
.boolea2n1s2
.bo8r9n8o9l8o8g9i9c8a8l.
.borno4
.borno3log1ic
.bornologi1ca
.bo8t9u9l8i8s8m.
.bo1tu
.botul2i
.botuli2s1m
.br8u8s8q8u8e8r.
.br2us
.brusqu2
.brus3quer
.bu8f9f8e8r.
.buf4fer1
.bu4f1f
.bu8ff8e8r.
.buffer1
.bu4ff
.bu8f9f8e8r8s.
.buffe4r1s2
.bu8ff8e8r8s.
.buffe4r1s2
.bu8s8i8e8r.
.bus5ie4
.b2us
.bu8s8i8e8s8t.
.busi1est
.bu8s8s8i8n8g.
.bu2ss
.bus1si
.bus2s1in
.buss3ing
.bu8t8t8e8d.
.but2t1ed
.bu8z8z9w8o8r8d.
.bu4z1z2
.buzz1wo2
.bu8z8z9w8o8r8d8s.
.buzzwor2d1s2
.ca9c8o8p8h9o9n8y.
.ca1co
.cac2oph
.cacopho5ny
.cacopho2n
.ca9c8o8p8h9o9n8i8e8s.
.caco5phoni
.cacophon2ie4
.ca8l8l9e8r.
.cal1l
.cal2le
.ca8l8l9e8r8s.
.calle4r1s2
.ca8m9e8r8a9m8e8n.
.cam5er1a
.camera1men
.ca8r8t9w8h8e8e8l.
.cartw4
.ca8r8t9w8h8e8e8l8s.
.cartwhee2l1s2
.ca9t8a8r8r8h8s.
.ca2ta
.cat2a2r
.catar1r4
.catarrh4
.catarr4h1s2
.ca8t9a9s8t8r8o8p8h9i8c.
.catas1t4r
.catastr2oph
.catastroph1ic
.ca8t9a9s8t8r8o8p8h9i9c8a8l8l8y.
.1catastrophi1ca
.catastrophical1l
.catastrophical1ly
.ca8t9e9n8o8i8d.
.cat4eno
.catenoi2
.cateno2id
.ca8t9e9n8o8i8d8s.
.catenoi2d1s2
.ca8u9l8i9f8l8o8w9e8r.
.cau4l2
.caul2i
.cauli4f4l2
.cauliflow1er
.ca8u9l8i9fl8o8w9e8r.
.cauli4fl2
.cauliflow1er
.ch8a8p9a8r9r8a8l.
.chap2a2r4
.cha1pa
.chapar1r4
.ch8a8r9t8r8e8u8s8e.
.ch2a2r
.chartr4eu2
.chartre2us4
.ch8e8m8o9t8h8e8r9a8p8y.
.che2
.che1mo
.chem4oth3
.chemoth2e
.chemoth4er1a
.chemothera3p
.ch8e8m8o9t8h8e8r9a9p8i8e8s.
.chemotherap2ie4
.ch8l8o8r8o9m8e8t8h9a8n8e.
.c4h1l4
.ch2lo
.chloro2me
.chloro4met
.chlorometha2n4
.ch8l8o8r8o9m8e8t8h9a8n8e8s.
.chlorometha1nes
.ch8o9l8e8s9t8e8r8i8c.
.3cho2
.c3hol4e
.choles2
.choles1ter1i
.ci8g9a9r8e8t8t8e.
.c2ig
.ci1ga
.cig2a2r
.cigare4t3t2
.ci8g9a9r8e8t8t8e8s.
.cigaret4tes
.ci8n8q8u8e9f8o8i8l.
.2cin
.cin1q
.cinqu2
.cinque1f
.cinque1fo
.cinquefoi2
.co9a8s8s8o9c8i8a9t8i8v8e.
.c4oa
.coa2ss
.coas1so
.coasso1ci
.coasso3c2i1a
.coassoci4a1t2iv
.co9g8n8a8c.
.2cog
.cog1n4a
.co9g8n8a8c8s.
.cogna4c3s2
.co9k8e8r9n8e8l.
.c2ok
.cok1er
.coker3nel
.co9k8e8r9n8e8l8s.
.cokerne2l1s2
.co8l9l8i8n9e8a9t8i8o8n.
.col1l
.coll2i
.col2lin4
.col1l4ine
.collin3ea
.collinea2t
.collinea1t2io
.collineatio2n
.co8l9u8m8n8s.
.colu4m1n
.colum2n1s2
.co8m9p8a8r9a8n8d.
.co4m1p
.compara5
.com1pa
.comp2a2r
.compara2n
.compar4and
.co8m9p8a8r9a8n8d8s.
.comparan2d1s2
.co8m9p8e8n9d8i8u8m.
.compendi1u
.co8m9p8o9n8e8n8t9w8i8s8e.
.compo2n
.compo3nen
.componen1t
.componentw4
.componentwis4
.componentwi2
.component3w4ise
.co8m8p9t8r8o8l9l8e8r.
.comp4tr
.com2p1t
.comptrol1l
.comptrol2le
.co8m8p9t8r8o8l9l8e8r8s.
.comptrolle4r1s2
.co8n9f8o8r8m9a8b8l8e.
.co2n
.con3f
.con1fo
.confo2r
.confor1m
.confor1ma
.confor2mab
.conformab2l2
.co8n9f8o8r8m9i8s8t.
.confor2mi
.conform2is
.co8n9f8o8r8m9i8s8t8s.
.conformis4t1s2
.co8n9f8o8r8m9i8t8y.
.confor3mit
.conformi1ty
.co8n9g8r8e8s8s.
.con3g
.con1gr
.congr2e2ss
.co8n9g8r8e8s8s8e8s.
.congress1e4s
.co8n9t8r8i8b9u8t8e.
.con5t
.contr4ib
.co8n9t8r8i8b9u8t8e8s.
.co8n9t8r8i8b9u8t8e8d.
.contribu2t1ed
.co9r8e9l8a9t8i8o8n.
.core1la
.corela1t2io
.corelatio2n
.co9r8e9l8a9t8i8o8n8s.
.corelatio2n3s2
.co9r8e9l8i9g8i8o8n9i8s8t.
.core1l2i
.corel2ig
.corel4igi
.coreli5g2io
.coreligion3i
.coreligio2n
.coreligion1is
.co9r8e9l8i9g8i8o8n9i8s8t8s.
.coreligionis4t1s2
.co9r8e9o8p9s8i8s.
.core1o
.coreo2p1s2
.coreop1sis
.co9r8e9s8p8o8n9d8e8n8t.
.core1sp
.cores4po2n
.coresp2ond
.corespon1de
.corespon1den
.coresponden1t
.co9r8e9s8p8o8n9d8e8n8t8s.
.coresponden4t4s2
.co9s8e9c8a8n8t.
.cos4e
.cose1ca
.coseca2n
.cosecan1t
.co9t8a8n9g8e8n8t.
.co4ta2n
.co1ta
.cot2ang
.cotan1gen
.cotangen1t
.co8u8r9s8e8s.
.cou2
.cou4rs2
.cour2se
.cours3e4s
.co9w8o8r8k9e8r.
.co4wo2
.cowork1er
.co9w8o8r8k9e8r8s.
.coworke4r1s2
.cr8a8n8k9c8a8s8e.
.cra2n
.cra4nk2
.crank1ca
.cr8a8n8k9s8h8a8f8t.
.cran4k1s2
.cranks2h
.cranksha2f
.cranksha2ft
.cr8o8c9o9d8i8l8e.
.cr2oc
.cro4cod
.cro1co
.cr8o8c9o9d8i8l8e8s.
.crocodiles2
.cr8o8s8s9h8a8t8c8h.
.cro2s4s
.cross2h
.crossha4tc
.crosshat4ch
.cr8o8s8s9h8a8t8c8h8e8d.
.crosshatche2
.crosshat4ch4ed
.cr8o8s8s9o8v8e8r.
.cros1so
.cros4sov
.cr8y8p9t8o9g8r8a8m.
.cry2p1t
.cryp1to
.crypto2gr
.cr8y8p9t8o9g8r8a8m8s.
.cryptogra4m1s2
.cu8f8f9l8i8n8k.
.c4uf
.cu4f1f
.cuff4l2
.cufflin4
.cuffl4i4nk2
.cu8ffl8i8n8k.
.cuffl4i4nk2
.cu8f8f9l8i8n8k8s.
.cufflin4k1s2
.cu8ffl8i8n8k8s.
.cufflin4k1s2
.cu9n8e8i9f8o8r8m.
.3cun
.cu2ne
.cunei2
.cunei1fo
.cuneifo2r
.cuneifor1m
.cu8s9t8o8m9i8z9a9b8l8e.
.1c2us
.cus1to
.custom2iz
.customiza1
.customiz5a2b
.customizab2l2
.cu8s9t8o8m9i8z8e.
.customi2ze
.cu8s9t8o8m9i8z8e8s.
.cu8s9t8o8m9i8z8e8d.
.da8c8h8s9h8u8n8d.
.1d2a
.da2ch4
.dac4h1s2
.dach4s2h
.da8m9s8e8l9f8l8y.
.da2m2
.da4m1s2
.dam5se2l2f
.damself4l2
.damself2ly5
.da8m9s8e8l9fl8y.
.dam5se2l2fl2
.damselfly
.da8m9s8e8l9f8l8i8e8s.
.damselfl2ie4
.da8m9s8e8l9fl8i8e8s.
.damselfl2ie4
.da8c8t8y8l9o9g8r8a8m.
.da2c1t
.dac1ty
.dac2tyl
.dacty3lo
.dactylo1gr
.da8c8t8y8l9o9g8r8a8p8h.
.da8t8a9b8a8s8e.
.3dat
.da2ta
.da2tab
.da8t8a9b8a8s8e8s.
.databas1e4s
.da8t8a9p8a8t8h.
.dat5ap
.datap5at
.data1pa
.datap4ath
.da8t8a9p8a8t8h8s.
.datapa2t4h1s2
.da8t8e9s8t8a8m8p.
.dat3est
.dates1ta
.datesta4m1p
.da8t8e9s8t8a8m8p8s.
.datestam2p1s2
.de9c8l8a8r9a8b8l8e.
.de4cl2a2r
.decla2rab
.declarab2l2
.de9f8i8n9i9t8i8v8e.
.de1f
.de1fi
.de2fin
.def2ini
.defin2it
.defini1ti
.defini1t2iv
.de9fi8n9i9t8i8v8e.
.de1fi
.de2fin
.defini
.defin2it
.defini1ti
.defini1t2iv
.de9l8e8c9t8a9b8l8e.
.d5elec
.dele2c1t
.delec2ta4b
.delec1ta
.delectab2l2
.de8m8i9s8e8m8i9q8u8a9v8e8r.
.de4m2is
.dem4ise
.demisemi3qua
.demisemiqu2
.demisemiqua5v4
.de8m8i9s8e8m8i9q8u8a9v8e8r8s.
.demisemiquave4r1s2
.de9m8o8c9r8a9t8i8s8m.
.de4mocr
.democrati2s4m
.de8m8o8s.
.demos2
.de9r8i8v9a9t8i8v8e.
.der2i4v
.de4ri1va
.deri3vat
.der2iva1t2iv
.de9r8i8v9a9t8i8v8e8s.
.derivativ4e2s
.di8a9l8e8c9t8i8c.
.1d4i3a
.di2al
.di2ale
.diale2c1t
.di8a9l8e8c9t8i8c8s.
.dialecti4c3s2
.di8a9l8e8c9t8i9c8i8a8n.
.dialect2i1ci
.d2i1alecti3c2i1a
.dialectici2a2n
.di8a9l8e8c9t8i9c8i8a8n8s.
.dialecticia2n1s2
.di9c8h8l8o8r8o9m8e8t8h9a8n8e.
.d4i2ch
.dic4h1l4
.dich2lo
.dichloro2me
.dichloro4met
.dichlorometha2n4
.di8f9f8r8a8c8t.
.d1if
.dif4fr
.di4f1f
.diffra2c1t
.di8ff8r8a8c8t.
.d1i4ff
.diffr
.diffra2c1t
.di8f9f8r8a8c8t8s.
.diffrac4t1s2
.di8ff8r8a8c8t8s.
.diffrac4t1s2
.di8f9f8r8a8c9t8i8o8n.
.diffrac1t2io
.diffractio2n
.di8ff8r8a8c9t8i8o8n.
.diffrac1t2io
.diffractio2n
.di8f9f8r8a8c9t8i8o8n8s.
.diffractio2n3s2
.di8ff8r8a8c9t8i8o8n8s.
.diffractio2n3s2
.di8r8e8r.
.d4ir2
.di1re
.dir1er4
.di8r8e9n8e8s8s.
.dire1nes
.diren2e2ss
.di8s9p8a8r9a8n8d.
.dis1
.dis1p
.di2s1pa
.disp2a2r
.dispara2n
.dispar4and
.di8s9p8a8r9a8n8d8s.
.disparan2d1s2
.di8s9t8r8a8u8g8h8t9l8y.
.d4is3t
.dist4r
.dis1tra
.distraugh3
.distraugh2tl
.distraught1ly
.di8s9t8r8i8b9u8t8e.
.distr4ib
.di8s9t8r8i8b9u8t8e8s.
.di8s9t8r8i8b9u8t8e8d.
.distribu2t1ed
.do8u9b8l8e9s8p8a8c8e.
.dou2
.dou3b2l2
.dou5ble1sp
.doubles2
.double2s1pa
.doublespa4ce
.do8u9b8l8e9s8p8a8c9i8n8g.
.doublesp4a2ci
.doublespa2c1in
.doublespac1ing
.do8l8l9i8s8h.
.dol1l
.doll2i
.dollis2h
.dr8i8f8t9a8g8e.
.1dr
.dr4i2ft
.drif1ta
.dr8i8v9e8r8s.
.dr2iv
.drive4r1s2
.dr8o8m9e9d8a8r8y.
.dro2me
.dro2med
.drom2e2d2a
.drome4dary
.dromed2a2r
.dr8o8m9e9d8a8r8i8e8s.
.dromedar1i
.dromedar2ie4
.du9o8p9o9l8i8s8t.
.duopol2i
.du9o8p9o9l8i8s8t8s.
.duopolis4t1s2
.du9o8p9o8l8y.
.duopo2ly
.dy8s9l8e8x8i8a.
.d2y
.dys1l2
.dys2le
.dyslex3i
.dyslex2i5a
.dy8s9l8e8c9t8i8c.
.dysle2c1t
.ea8s8t9e8n8d9e8r8s.
.east3
.eas3ten
.eas3tend
.easten1de
.eastende4r5s2
.ec8o9n8o8m9i8c8s.
.e1co
.eco2n
.eco3nomic
.economi4c3s2
.ec8o8n9o9m8i8s8t.
.econom2is
.ec8o8n9o9m8i8s8t8s.
.economis4t1s2
.ei9g8e8n9c8l8a8s8s.
.ei2
.e2ig2
.ei1gen
.eigen1c4l4
.eigencla2ss
.ei9g8e8n9c8l8a8s8s8e8s.
.eigenclass1e4s
.ei9g8e8n9v8a8l9u8e.
.eigen1v2
.eigen1va
.eigenval1u
.ei9g8e8n9v8a8l9u8e8s.
.el8e8c8t8r8o9m8e8c8h8a8n9i9c8a8l.
.5elec
.ele2c1t
.electro2me
.electrome2ch
.electrome5cha4n1ic
.electromecha2n
.electromechani1ca
.el8e8c8t8r8o9m8e8c8h8a8n8o9a8c8o8u8s8t8i8c.
.electromechano4
.electromechan4oa
.electromechanoa1co
.electromechanoacou2
.electromechanoaco2us
.electromechanoacoust2i
.electromechanoacous1tic
.el8i8t9i8s8t.
.el2i
.el1it
.eli1ti
.el4itis
.el8i8t9i8s8t8s.
.elitis4t1s2
.en9t8r8e9p8r8e9n8e8u8r.
.en1t
.entrepr2
.entrepren4eu
.en9t8r8e9p8r8e9n8e8u8r9i8a8l.
.entrepreneur2i3a
.entrepreneuri2al
.ep9i9n8e8p8h9r8i8n8e.
.epi2n
.ep2ine
.epinep4hr4
.ep2inephr2in4e
.eq8u8i9v8a8r8i9a8n8t.
.equ2iv3
.equi1va
.equiv2a2r
.equivar1i
.equivar3i2a2n
.equivar2i3a
.equivar4ian4t
.eq8u8i9v8a8r8i9a8n8c8e.
.equivar4ianc
.et8h9a8n8e.
.etha2n4
.et8h9y8l9e8n8e.
.ev8e8r9s8i9b8l8e.
.ev1er
.eve4r1s2
.ever1si
.ever4si4b
.eversi1b2l2
.ev8e8r8t.
.ev8e8r8t8s.
.ever4t1s2
.ev8e8r8t9e8d.
.ever2t1ed
.ev8e8r8t9i8n8g.
.ever1ti
.ever2t1in
.ex9q8u8i8s9i8t8e.
.exqu2
.exq2ui2
.exquis2ite
.ex9t8r8a9o8r9d8i9n8a8r8y.
.ex1t2
.ex1tra
.extr4ao
.extraord2i
.extraord1in4
.extraor1di1na
.extraordin2a2r
.fa8l8l9i8n8g.
.1fa
.fal1l
.fall2i
.fal2lin4
.fe8r8m8i9o8n8s.
.fer1
.fer3m4
.fer4m2io
.fermio2n
.fermio2n3s2
.fi9n8i8t8e9l8y.
.1fi
.2fin
.f2ini
.fin2it
.fin2ite
.finite1ly
.fi9n8i8t8e9l8y.
.1fi
.2fin
.fini
.fin2it
.fin2ite
.finite1ly
.fl8a9g8e8l9l8u8m.
.f4l2
.flag5el1l
.fl8a9g8e8l9l8u8m.
.fl2
.flag5el1l
.fl8a9g8e8l9l8a.
.flag4ella
.fl8a9g8e8l9l8a.
.flag4ella
.fl8a8m9m8a9b8l8e8s.
.flam1m
.flam1ma
.flam2mab
.flammab2l2
.flammables2
.fl8a8m9m8a9b8l8e8s.
.flam1m
.flam1ma
.flam2mab
.flammab2l2
.flammables2
.fl8e8d8g9l8i8n8g.
.fledgl2
.fl8e8d8g9l8i8n8g.
.fledgl2
.fl8o8w9c8h8a8r8t.
.flow2ch
.flowch2a2r
.fl8o8w9c8h8a8r8t.
.flow2ch
.flowch2a2r
.fl8o8w9c8h8a8r8t8s.
.flowchar4t1s2
.fl8o8w9c8h8a8r8t8s.
.flowchar4t1s2
.fl8u8o8r8o9c8a8r9b8o8n.
.flu3o
.fluo3r
.fluor2oc
.fluoro1ca
.fluoroc2a2r
.fluorocar1b
.fluorocarb4o
.fluorocarbo2n
.fl8u8o8r8o9c8a8r9b8o8n.
.flu3o
.fluo3r
.fluor2oc
.fluoro1ca
.fluoroc2a2r
.fluorocar1b
.fluorocarb4o
.fluorocarbo2n
.fo8r9m8i9d8a9b8l8e.
.for2mi
.formi1d4a
.form2id
.formi2d3a4b
.formidab2l2
.fo8r9m8i9d8a9b8l8y.
.formidab1ly
.fo8r9s8y8t8h9i8a.
.fo4rs2
.fors4y
.forsyth2i1a
.fo8r8t8h9r8i8g8h8t.
.fort4hr4
.forthr2ig
.fr8e8e9l8o8a8d8e8r.
.freel4oa
.freeloa2d3
.fr8e8e9l8o8a8d8e8r8s.
.freeloade4r5s2
.fr8i8e8n8d9l8i8e8r.
.fri2
.fr2ie4
.fr2ie4ndl2ie4
.fr8i9v8o8l9i8t8y.
.fr2iv
.frivol2i
.frivol1it
.frivoli1ty
.fr8i9v8o8l9i9t8i8e8s.
.frivoli1ti
.frivolit2ie4
.fr8i8v9o9l8o8u8s.
.frivolou2
.frivolo2us
.ga9l8a8c9t8i8c.
.gala2c1t
.ga8l9a8x8y.
.ga8l9a8x9i8e8s.
.galax3i
.galax2ie4
.ga8s9o8m9e9t8e8r.
.ga1so
.ga3som
.gaso2me
.gaso4met
.gasome1te
.ge9o9d8e8s9i8c.
.geodes2
.geode1si
.geode2sic
.ge9o9d8e8t9i8c.
.geode1t
.geodet1ic
.ge8o9m8e8t9r8i8c.
.ge3om
.geo2me
.geo4met
.geom4etr
.geo5met3ric
.ge8o9m8e8t9r8i8c8s.
.geome4tri4c3s2
.ge9o9s8t8r8o8p8h8i8c.
.geos4
.geost4r
.geostr2oph
.geostroph1ic
.ge8o9t8h8e8r9m8a8l.
.ge4ot
.ge4oth
.geoth2e
.geother3m4
.geother1ma
.ge9o8t9r8o9p8i8s8m.
.geotropi2s1m
.gn8o9m8o8n.
.g1no
.gno4mo
.gno4mo2n
.gn8o9m8o8n8s.
.gnomo2n3s2
.gr8a8n8d9u8n8c8l8e.
.1gr
.gra2n2
.gr4and
.gran1du
.grandu4n
.grandun1c4l4
.gr8a8n8d9u8n8c8l8e8s.
.granduncles2
.gr8i8e8v9a8n8c8e.
.gr2ie4
.grie1va
.grieva2n
.gr8i8e8v9a8n8c8e8s.
.gr8i8e8v9o8u8s.
.grievou2
.grievo2us
.gr8i8e8v9o8u8s9l8y.
.grievous1l2
.grievous1ly
.ha8i8r9s8t8y8l8e.
.hai2
.ha4ir
.hai4rs2
.hairs2ty
.hairs2tyl
.ha8i8r9s8t8y8l8e8s.
.hairstyles2
.ha8i8r9s8t8y8l9i8s8t.
.ha8i8r9s8t8y8l9i8s8t8s.
.hairstylis4t1s2
.ha8l8f9s8p8a8c8e.
.ha2lf
.hal2f3s
.half2s1pa
.halfspa4ce
.ha8l8f9s8p8a8c8e8s.
.ha8l8f9w8a8y.
.ha8r9b8i8n9g8e8r.
.h2a2r
.har1b
.harbi2
.har2bin
.harb4inge
.ha8r9b8i8n9g8e8r8s.
.harbinge4r1s2
.ha8r9l8e9q8u8i8n.
.har4le4
.har1l
.harle1q
.harlequ2
.harleq2ui2
.harlequi4n
.ha8r9l8e9q8u8i8n8s.
.harlequ2i2n1s2
.ha8t8c8h9e8r8i8e8s.
.ha4tc
.hat4ch
.hatche2
.hatcher1i
.hatcher2ie4
.he8m8i9d8e8m8i9s8e8m8i9q8u8a9v8e8r.
.hem2id
.hemid4em
.hemide4m2is
.hemidem4ise
.hemidemisemi3qua
.hemidemisemiqu2
.hemidemisemiqua5v4
.he8m8i9d8e8m8i9s8e8m8i9q8u8a9v8e8r8s.
.hemidemisemiquave4r1s2
.he9m8o9g8l8o9b8i8n.
.hemo4g
.he1mo
.hemo4gl2
.hemo3glo
.hemoglo1bi
.hemoglo2bin
.he9m8o9p8h8i8l9i8a.
.hem2oph
.hemoph4il2
.hemophil1i
.hemophil3i1a
.he9m8o9p8h8i8l9i8a8c.
.he9m8o9p8h8i8l9i8a8c8s.
.hemophilia4c3s2
.he8m8o9r8h8e9o8l9o8g8y.
.hemo2r
.hemorh4
.hemorhe3ol
.hemorheol1o1gy
.he9p8a8t9i8c.
.hep5
.he2pa
.hepat1ic
.he8r9m8a8p8h9r8o9d8i8t8e.
.her3m4
.her1ma
.her4map
.hermap4hr4
.hermaphrod2ite
.he8r9m8a8p8h9r8o9d8i8t9i8c.
.hermaphrod2i1ti
.hermaphrod4i2tic
.he9r8o8e8s.
.hero4e
.he8x8a9d8e8c9i9m8a8l.
.hex1a
.hexa2d
.hexade1c2i
.hexade2c3im
.hexadeci1ma
.ho9l8o9n8o9m8y.
.holo2n
.holon3o3my
.ho9m8e8o9m8o8r9p8h8i8c.
.ho2me3
.homeo1mo
.homeomo2r
.homeomor1p
.homeomorp4h4
.homeomorph1ic
.ho9m8e8o9m8o8r9p8h8i8s8m.
.homeomorphi2s1m
.ho9m8o9t8h8e8t8i8c.
.ho1mo
.hom4oth3
.homoth2e
.homo3the4t
.homothet1ic
.ho8r8s8e9r8a8d9i8s8h.
.hor4se
.ho4rs2
.horser1a
.horsera2d
.horser2adi
.horseradis1
.horseradis2h
.ho8t9b8e8d.
.ho2t1b
.hot4be2d
.ho8t9b8e8d8s.
.hotbe2d1s2
.hy9d8r8o9t8h8e8r9m8a8l.
.hy1d
.hy1dr
.hydro4th2e
.hydr4oth
.hydrother3m4
.hydrother1ma
.hy9p8o9t8h8a8l9a9m8u8s.
.hy3po
.hyp4ot
.hyp4oth
.hypotha3la
.hypothala3m
.hypothala1mu
.hypothalam2us
.id8e8a8l8s.
.ide3a4l
.idea2l1s2
.id8e8o9g8r8a8p8h8s.
.ideo2g
.ideo1gr
.ideogra4p4h1s2
.id8i8o9s8y8n9c8r8a8s8y.
.i2di
.i1d3io
.idi4os
.idios4y
.idiosyn1cr
.idiosyncr2as
.idios4yncras4y
.id8i8o9s8y8n9c8r8a9s8i8e8s.
.idiosyncras2ie4
.id8i8o9s8y8n9c8r8a8t8i8c.
.idiosyn5crat1ic
.id8i8o9s8y8n9c8r8a8t9i9c8a8l9l8y.
.idiosyncrati1ca
.idiosyncratical1l
.idiosyncratical1ly
.ig9n8i8t9e8r.
.2ig
.ig1ni
.ign2it
.ign2ite
.ig9n8i8t9e8r8s.
.ignite4r1s2
.ig9n8i9t8o8r.
.ign3itor
.igni1to
.ig8n8o8r8e9s8p8a8c8e8s.
.ig1no
.ignore1sp
.ignore2s1pa
.ignorespa4ce
.im9p8e8d9a8n8c8e.
.im2p2ed
.imp2e2d2a
.impeda2n
.im9p8e8d9a8n8c8e8s.
.in9d8u9b8i9t8a9b8l8e.
.4ind
.in1du
.indu1b4i
.indubi2t
.indubi1ta
.indubi2tab
.indubitab2l2
.in9f8i8n9i8t8e9l8y.
.in3f
.in1fi
.in2fin
.inf2ini
.infin2it
.infin2ite
.infinite1ly
.in9fi8n9i8t8e9l8y.
.in3fi
.in2fin
.infini
.infin2it
.infin2ite
.infinite1ly
.in9f8i8n9i9t8e8s9i9m8a8l.
.infinit4es
.infinite1si
.infinite2s5im
.infinitesi1ma
.in9fi8n9i9t8e8s9i9m8a8l.
.infinit4es
.infinite1si
.infinite2s5im
.infinitesi1ma
.in9f8r8a9s8t8r8u8c9t8u8r8e.
.infr2as
.infras1t4r
.infrastru2c1t
.infrastructu4r
.infrastruc1tu
.infrastruc3ture
.in9f8r8a9s8t8r8u8c9t8u8r8e8s.
.in9s8t8a8l8l9e8r.
.ins2ta2l
.ins1ta
.instal1l
.instal2le
.in9s8t8a8l8l9e8r8s.
.installe4r1s2
.in9t8e8r9d8i8s9c8i9p8l8i9n8a8r8y.
.in1t
.in5ter3d
.interd2i
.interdis1
.interd2is1c
.interdis1ci
.interdisc2ip
.interdisci1p2l2
.interdiscipli4n
.interdiscipl4i1na
.interdisciplin2a2r
.in9t8e8r9g8a9l8a8c9t8i8c.
.interg2
.inter1ga
.intergala2c1t
.in9u8t8i8l8e.
.in1u
.in4u1t2i
.in9u8t8i8l9i9t8y.
.inutil1i
.inut2il1it
.inutili1ty
.ir9r8e9d8u8c9i8b8l8e.
.ir2r2ed
.irre1du
.irredu2c
.irreduci4b
.irredu1ci
.irreduci1b2l2
.ir9r8e9d8u8c9i8b8l8y.
.irreducib1ly
.ir9r8e8v9o9c8a9b8l8e.
.irrev2
.irre5voc
.irrevo1ca
.irrevoca1b2l2
.is8o8t9r8o8p8y.
.i2so
.isotropy5
.is8o9t8r8o8p9i8c.
.isotrop3ic
.it8i8n9e8r9a8r8y.
.i1ti
.i2t1in
.it2ine
.itin4er4a2r
.itin1er
.itiner1a
.it8i8n9e8r9a8r9i8e8s.
.itinerar1i
.itinerar2ie4
.je9r8e9m8i9a8d8s.
.1je
.jerem2i3a
.jeremia2d
.jeremia2d1s2
.ke8y9n8o8t8e.
.ke8y9n8o8t8e8s.
.keyno4tes
.ke8y9s8t8r8o8k8e.
.keys4
.keys1t
.keyst4r
.keystr2ok2
.ke8y9s8t8r8o8k8e8s.
.keystrokes4
.ki8l8n9i8n8g.
.k1i
.k4i2l1n2
.kiln1in
.kilnin4g
.la8c9i9e8s8t.
.l4a2ci4
.la3c2ie4
.laci1est
.la8m9e8n9t8a9b8l8e.
.la1men
.la3men1t
.lamen2ta4b
.lamen1ta
.lamentab2l2
.la8n8d9s8c8a8p9e8r.
.3l4and
.la2n
.lan2d1s2
.landsca4p
.lands1ca
.landsca5per
.la8n8d9s8c8a8p9e8r8s.
.landscape4r1s2
.la8r9c8e9n8y.
.l2a2r
.lar1c
.lar2ce
.lar1cen4
.la8r9c8e9n9i8s8t.
.lar4ceni
.le8a8f9h8o8p9p8e8r.
.le2a
.lea2f
.lea4fh
.leafho4p1p
.leafhop2pe
.leafhop3per
.le8a8f9h8o8p9p8e8r8s.
.leafhoppe4r1s2
.le8t9t8e8r9s8p8a8c9i8n8g.
.le4t3t2
.lette4r1s2
.letter1sp
.letter2s1pa
.lettersp4a2ci
.letterspa2c1in
.letterspac1ing
.li8f8e9s8p8a8n.
.life1sp
.life2s1pa
.lifespa4n
.li8f8e9s8p8a8n8s.
.lifespa2n1s2
.li8f8e9s8t8y8l8e.
.lifes2ty
.lifes2tyl
.li8f8e9s8t8y8l8e8s.
.lifestyles2
.li8g8h8t9w8e8i8g8h8t.
.3ligh
.lightw4
.lightwei2
.l2ightwe2ig2
.li8m9o8u9s8i8n8e8s.
.li4mo
.li3mo2us
.limou2
.limou2s1in
.limous2ine
.limousi1nes
.li8n8e9b8a8c8k8e8r.
.1l4ine
.lin2e2b
.lineback1
.lineback1er
.li8n8e9s8p8a8c8i8n8g.
.li1nes
.li4ne1sp
.line2s1pa
.linesp4a2ci
.linespa2c1in
.linespac1ing
.li9o8n9e8s8s.
.lio2n
.lio1nes
.lion2e2ss
.li8t8h9o9g8r8a8p8h8e8d.
.l2ith
.litho4g
.litho1gr
.lithograph4ed
.li8t8h9o9g8r8a8p8h8s.
.lithogra4p4h1s2
.lo9b8o8t9o8m8y.
.lobo4to
.loboto3my
.lo9b8o8t9o8m9i8z8e.
.lobotom2iz
.lobotomi2ze
.lo8g8e8s.
.lo1ge
.lo8n8g9e8s8t.
.5long
.lo2n
.lo9q8u8a8c9i8t8y.
.lo1q
.loqu2
.loquac4
.loqu4a2ci
.loqua2c1it
.loquaci1ty
.lo8v8e9s8t8r8u8c8k.
.4lov
.lov4e2s
.lov2est4r
.lovestruc5
.lovestruck1
.ma8c8r8o9e8c8o9n8o8m8i8c8s.
.macro4e
.macroe1co
.macroeco2n
.macroeco3nomic
.macroeconomi4c3s2
.ma8l9a9p8r8o8p9i8s8m.
.malapr2
.malapropi2s1m
.ma8l9a9p8r8o8p9i8s8m8s.
.malaprop4is4m1s2
.ma8n9s8l8a8u8g8h9t8e8r.
.ma2n1s2
.man2s1l2
.manslaugh3
.ma8n9u9s8c8r8i8p8t.
.man2us
.manusc2
.manuscri2
.manuscr2ip
.manuscri2p1t
.ma8r9g8i8n9a8l.
.marg2
.margi4n
.margi1na
.ma8t8h9e9m8a9t8i9c8i8a8n.
.m4ath3
.math5em
.math2e
.1mathe1ma
.mathemat1ic
.mathemat2i1ci
.mathemati3c2i1a
.mathematici2a2n
.ma8t8h9e9m8a9t8i9c8i8a8n8s.
.mathematicia2n1s2
.ma8t8t8e8s.
.mat5te
.ma4t3t2
.mat4tes
.me8d9i8c9a8i8d.
.2med
.m4edi
.med3i1ca
.medicai2
.medica2id
.me8d8i9o8c8r8e.
.me1d2io
.mediocre3
.me8d8i9o8c9r8i9t8i8e8s.
.medi5ocrit
.mediocri2
.medio5cri1ti
.mediocrit2ie4
.me8g8a9l8i8t8h.
.me2g
.m4egal
.me1ga
.me3gal1i
.megal1it
.megal2ith
.me8g8a9l8i8t8h8s.
.megali2t4h1s2
.me8t8a9b8o8l9i8c.
.me4ta
.me2ta4b
.metabol3ic
.metabol2i
.me9t8a8b9o9l8i8s8m.
.metaboli2s1m
.me9t8a8b9o9l8i8s8m8s.
.metabol4is4m1s2
.me9t8a8b9o9l8i8t8e.
.metabo5l2ite
.metabol1it
.me9t8a8b9o9l8i8t8e8s.
.metabolit4es
.me8t8a9l8a8n9g8u8a8g8e.
.met3a2l
.meta5la
.metala2n
.metal2ang
.metalan1gu
.metalangu4a
.me8t8a9l8a8n9g8u8a8g8e8s.
.me8t8a9p8h8o8r9i8c.
.metapho4r
.me8t8h9a8n8e.
.metha2n4
.me9t8r8o8p9o9l8i8s.
.m4etr
.metropol2i
.me9t8r8o8p9o9l8i8s8e8s.
.metropol4ise
.metropolis1e4s
.me8t9r8o9p8o8l9i9t8a8n.
.metropol1it
.metropoli3ta2n
.metropoli1ta
.me8t9r8o9p8o8l9i9t8a8n8s.
.metropolita2n1s2
.mi8c8r8o9e8c8o9n8o8m8i8c8s.
.m4i1cr
.micro4e
.microe1co
.microeco2n
.microeco3nomic
.microeconomi4c3s2
.mi9c8r8o9f8i8c8h8e.
.micro2fi
.microf4i2ch
.microfiche2
.mi9c8r8o9fi8c8h8e.
.micro2fi
.microfi2ch
.microfiche2
.mi9c8r8o9f8i8c8h8e8s.
.microfich1es
.mi9c8r8o9fi8c8h8e8s.
.microfich1es
.mi8c8r8o9o8r8g8a8n9i8s8m.
.microo2
.microorg2
.microor1ga
.microorgan5is
.microorga2n
.microorgani2s1m
.mi8c8r8o9o8r8g8a8n9i8s8m8s.
.microorgan4is4m1s2
.mi8l8l9a8g8e.
.m4il1l
.mi8l9l8i9l8i8t8e8r.
.mill2i
.mil4l4i4l
.millil1i
.mill2il1it
.millil2ite
.mi8m8e8o9g8r8a8p8h8e8d.
.mimeo2g
.mimeo1gr
.mimeograph4ed
.mi8m8e8o9g8r8a8p8h8s.
.mimeogra4p4h1s2
.mi8m9i8c9r8i8e8s.
.mim1i
.mim4i1cr
.mimicri2
.mimicr2ie4
.mi8n9i8s.
.m2ini
.min1is
.mi8n8i9s8y8m9p8o9s8i8u8m.
.minis4y
.minisy4m1p
.minisym1pos
.minisympo5si4u
.mi8n8i9s8y8m9p8o9s8i8a.
.minisympos2i1a
.mi9n8u8t9e8r.
.m4in1u
.mi9n8u8t9e8s8t.
.mi8s9c8h8i8e9v8o8u8s9l8y.
.m2is1c
.mis3ch2
.misch2ie4
.mischievou2
.mischievo2us
.mischievous1l2
.mischievous1ly
.mi9s8e8r8s.
.m4ise
.mis3er
.mise4r1s2
.mi9s8o8g9a9m8y.
.mi2so
.miso1ga
.miso2gam
.mo8d9e8l9l8i8n8g.
.mo2d1
.model1l
.modell2i
.model2lin4
.mo8l9e9c8u8l8e.
.mole1cu
.mole4cul
.molecul4e
.mo8l9e9c8u8l8e8s.
.molecules2
.mo8n9a8r8c8h8s.
.mo1n1a
.monar3c
.mon2a2r
.monar2ch
.monarc4h1s2
.mo8n8e8y9l8e8n9d8e8r.
.moneylen1de
.mo8n8e8y9l8e8n9d8e8r8s.
.moneylende4r5s2
.mo8n8o9c8h8r8o8m8e.
.mono2ch4
.monoc4hr4
.monochro2me
.mo8n8o9e8n9e8r9g8e8t8i8c.
.mo3noe
.monoen1er
.monoenerg2
.monoener3get
.monoenerget1ic
.mo8n9o8i8d.
.monoi2
.mono2id
.mo8n8o9p8o8l8e.
.mo4nop
.mo8n8o9p8o8l8e8s.
.monopoles2
.mo9n8o8p9o8l8y.
.monopo2ly
.mo8n8o9s8p8l8i8n8e.
.monos1p2l2
.monospli4n
.monosp1l4ine
.mo8n8o9s8p8l8i8n8e8s.
.monospli1nes
.mo8n8o9s8t8r8o8f8i8c.
.monos5t
.monost4r
.monostro2fi
.mo8n8o9s8t8r8o8fi8c.
.monostro2fi
.mo9n8o8t9o9n8i8e8s.
.mono1to
.mo2noto2n
.monoton2ie4
.mo9n8o8t9o9n8o8u8s.
.mono4tono
.monoto1nou2
.monotono2us
.mo9r8o8n9i8s8m.
.moro5n4is
.moro2n
.moroni2s1m
.mo8s9q8u8i9t8o.
.mos2
.mosqu2
.mosq2ui2
.mosqui1to
.mo8s9q8u8i9t8o8s.
.mosquitos2
.mo8s9q8u8i9t8o8e8s.
.mu8d9r8o8o8m.
.mu1dr
.mud1room
.mudroo2
.mu8d9r8o8o8m8s.
.mudroo4m1s2
.mu8l9t8i9f8a8c9e8t8e8d.
.5mu4lt
.mul1ti3
.multif2
.multi1fa
.multifa4ce
.multifacet4
.multiface2t1ed
.mu8l9t8i9p8l8i8c9a8b8l8e.
.mult2ip
.multi1p2l2
.multipli1ca
.multiplica1b2l2
.mu8l8t8i9u8s8e8r.
.multi4u
.multi2us
.ne8o9f8i8e8l8d8s.
.3neo
.ne5of
.neo2fi
.neof2ie4
.neofie2ld3
.neofiel2d1s2
.ne8o9fi8e8l8d8s.
.ne5o2fi
.neofie4
.neofie2ld3
.neofiel2d1s2
.ne8o9n8a8z8i.
.neo2n
.neo1n1a
.neona2z1i
.ne8o9n8a8z8i8s.
.neonaz4is
.ne8p8h9e8w8s.
.nephe4
.ne8p8h9r8i8t8e.
.nep4hr4
.nephr2ite
.ne8p8h9r8i8t8i8c.
.nephr4i2t3ic
.nephri1ti
.ne8w9e8s8t.
.ne4w
.newest3
.ne8w8s9l8e8t9t8e8r.
.news4l2
.news2le
.newsle4t3t2
.ne8w8s9l8e8t9t8e8r8s.
.newslette4r1s2
.ni8t8r8o9m8e8t8h9a8n8e.
.n2it
.ni3tr
.nitro2me
.nitro4met
.nitrometha2n4
.no9n8a8m8e.
.no4n
.no1n1a
.no8n9a8r9i8t8h9m8e8t9i8c.
.nonar3i
.non2a2r
.nonar2ith
.nonarit4h1m
.nonarithmet4
.nonarithmet1ic
.no8n9e8m8e8r9g8e8n8c8y.
.none1me
.nonemerg2
.nonemer1gen
.nonemergen1cy
.no8n9e8q8u8i9v8a8r8i9a8n8c8e.
.none2q
.nonequ2
.noneq2ui2
.nonequ2iv3
.nonequi1va
.nonequiv2a2r
.nonequivar1i
.nonequivar3i2a2n
.nonequivar2i3a
.nonequivar4ianc
.no8n8e9t8h8e9l8e8s8s.
.noneth2e
.nonethe1les2
.nonethe3l2e2ss
.no8n9e8u8c8l8i8d9e8a8n.
.non4eu
.noneu1c4l4
.noneucl2id
.noneuclidea2n
.no8n9i8s8o9m8o8r9p8h8i8c.
.non5i
.non1is
.noni2so
.noni3som
.noniso1mo
.nonisomo2r
.nonisomor1p
.nonisomorp4h4
.nonisomorph1ic
.no8n9p8s8e8u8d8o9c8o8m9p8a8c8t.
.non1p4
.non2p1s2
.nonp2se
.nonps4eu
.nonpseu1do
.nonpseudo1co
.nonpseudoco4m1p
.nonpseudocom1pa
.nonpseudocompa2c4t
.no8n9s8m8o8o8t8h.
.no2n3s2
.non2s3m
.nons1mo
.nonsmoo2
.nonsmo4oth
.no8n9u8n8i9f8o8r8m.
.no3nu4n
.nonu1ni
.nonuni1fo
.nonunifo2r
.nonunifor1m
.no8n9u8n8i9f8o8r8m9l8y.
.nonunifor4m1l
.nonuniform1ly
.no8r9e8p9i9n8e8p8h9r8i8n8e.
.nore5pi2n
.norep2ine
.norepinep4hr4
.norep2inephr2in4e
.no8t9w8i8t8h9s8t8a8n8d9i8n8g.
.notw4
.notwi2
.notw2ith3
.notwi2t4h1s2
.notwith5st4and
.notwiths1ta
.notwithsta2n
.notwithstand1in
.nu9c8l8e8o9t8i8d8e.
.nucle3
.nu1c4l4
.nucle4ot
.nucleot2id
.nu9c8l8e8o9t8i8d8e8s.
.nucleotide4s2
.nu8t9c8r8a8c8k9e8r.
.nu4tc
.nutcrack1
.nutcrack1er
.nu8t9c8r8a8c8k9e8r8s.
.nutcracke4r1s2
.oe8r9s8t8e8d8s.
.o3er
.oe4r1s2
.oers4t1ed
.oerste2d1s2
.of8f9l8i8n8e.
.o4f1f
.off4l2
.offlin4
.off1l4ine
.offl8i8n8e.
.offl2ine
.of8f9l8o8a8d.
.offl4oa
.offloa2d3
.offl8o8a8d.
.offl4oa
.offloa2d3
.of8f9l8o8a8d8s.
.offloa2d1s2
.offl8o8a8d8s.
.offloa2d1s2
.of8f9l8o8a8d8e8d.
.offloa2d1ed
.offl8o8a8d8e8d.
.offloa2d1ed
.ol8i9g8o8p9o9l8i8s8t.
.ol2i
.ol2ig
.oli2go
.ol2igopol2i
.ol8i9g8o8p9o9l8i8s8t8s.
.oligopolis4t1s2
.ol8i9g8o8p9o8l8y.
.oligopo2ly
.ol8i9g8o8p9o8l9i8e8s.
.oligopol2ie4
.op9e8r9a8n8d.
.op1er
.3oper1a
.op4er4and
.opera2n
.op9e8r9a8n8d8s.
.operan2d1s2
.or8a8n8g9u8t8a8n.
.ora2n
.or2ang
.oran1gu
.oran4gu4t
.orangu1ta
.ora2nguta2n
.or8a8n8g9u8t8a8n8s.
.oranguta2n1s2
.or9t8h8o9d8o8n9t8i8s8t.
.ortho2do4
.orthodo2n
.orthodon3t4i
.orthodon1t
.or9t8h8o9d8o8n9t8i8s8t8s.
.orthodontis4t1s2
.or9t8h8o9k8e8r9a9t8o8l9o8g8y.
.orth2ok
.orthok1er
.orthoker1a
.orthokera1to
.orthokeratol1o1gy
.or8t8h8o9n8i8t8r8o9t8o8l8u8e8n8e.
.ortho2n
.orthon2it
.orthoni3tr
.orthonitro1to
.orthonitrotolu3en
.orthonitrotolu4ene
.ov8e8r9v8i8e8w.
.overv2ie4
.ov8e8r9v8i8e8w8s.
.ox9i8d9i8c.
.ox3i
.oxi5di
.ox2id
.pa8d9d8i8n8g.
.1pa
.p4a2d
.pad4d1in
.pad1d4
.pa8i8n9l8e8s8s9l8y.
.p4ai2
.pa4i4n4
.pa4i4n1l
.painles2
.pain3l2e2ss
.painles4s1l2
.painless1ly
.pa8l9e8t8t8e.
.p4al
.p2ale
.pale4t3t2
.pa8l9e8t8t8e8s.
.palet4tes
.pa8r9a9b8o8l8a.
.p2a2r
.pa2rab
.parabo1la
.pa8r9a9b8o8l9i8c.
.parabol3ic
.parabol2i
.pa9r8a8b9o9l8o8i8d.
.paraboloi2
.parabolo2id
.pa8r9a9d8i8g8m.
.para2d
.par2adi
.parad2ig
.paradig1m
.pa8r9a9d8i8g8m8s.
.paradig4m1s2
.pa8r8a9c8h8u8t8e.
.para2ch
.parachu4t
.pa8r8a9c8h8u8t8e8s.
.pa8r8a9d8i9m8e8t8h8y8l9b8e8n8z8e8n8e.
.parad4imet
.paradimethy2l1b
.paradimethylb4e4n3z
.paradimethylben2ze
.paradimethylbenze4n
.pa8r8a9f8l8u8o8r8o9t8o8l8u8e8n8e.
.para2f
.paraf4l2
.paraflu3o
.parafluo3r
.parafluoro1to
.parafluorotolu3en
.parafluorotolu4ene
.pa8r8a9fl8u8o8r8o9t8o8l8u8e8n8e.
.para2fl2
.paraflu3o
.parafluo3r
.parafluoro1to
.parafluorotolu3en
.parafluorotolu4ene
.pa8r8a9g8r8a8p8h9e8r.
.para1gr
.parag5ra3ph4er
.pa8r8a9l8e9g8a8l.
.par3al
.par2ale
.paral4egal
.parale1ga
.pa8r9a8l9l8e8l9i8s8m.
.paral1l
.paral2le
.paral3lel
.parallel2i
.paralle2lis
.paralleli2s1m
.pa8r8a9m8a8g9n8e8t9i8s8m.
.par4a1ma
.param3ag
.para5mag1n
.paramagneti2s4m
.pa8r8a9m8e8d8i8c.
.para2med
.param4edi
.pa8r8a9m8e8t8h8y8l9a8n8i8s8o8l8e.
.param3et
.paramethy3la
.paramethyla2n
.paramethylani2so
.pa9r8a8m9e9t8r8i8z8e.
.param4etr
.parametri2ze
.pa8r8a9m8i8l9i9t8a8r8y.
.par2ami
.paramil1i
.param2il1it
.paramili1ta
.p2a2ramilit2a2r
.pa8r8a9m8o8u8n8t.
.para2mo
.paramou2
.paramoun1t
.pa8t8h9o9g8e8n9i8c.
.p4ath
.pat4ho
.patho4g
.patho1ge4
.patho1gen
.pe8e8v9i8s8h.
.p4ee
.pee1vi
.peevis2h
.pe8e8v9i8s8h9n8e8s8s.
.peevis2h1n
.peevish1nes
.peevishn2e2ss
.pe8n9t8a9g8o8n.
.pen1t
.pen1ta
.penta2go
.pentago2n2
.pe8n9t8a9g8o8n8s.
.pentago2n3s2
.pe9t8r8o9l8e9u8m.
.petrol4eu
.ph8e9n8o8m9e9n8o8n.
.ph4e3no
.phe2n
.pheno2me
.pheno1men
.ph4enom4eno
.phenomeno4n
.ph8e8n8y8l9a8l8a9n8i8n8e.
.pheny3la
.phenylala2n
.phenylala5n2ine
.phenylalan1in
.ph8i9l8a8t9e9l8i8s8t.
.phi4latel2i4
.philate2lis
.ph8i9l8a8t9e9l8i8s8t8s.
.philatelis4t1s2
.ph8o9n8e8m8e.
.3phone
.pho2n
.phone1me
.ph8o9n8e8m8e8s.
.phone2mes
.ph8o9n8e9m8i8c.
.phone5mi
.ph8o8s9p8h8o8r9i8c.
.phos1p
.phospho5
.phospho4r
.ph8o9t8o9g8r8a8p8h8s.
.pho1to
.photo2gr
.photogra4p4h1s2
.ph8o9t8o9o8f8f9s8e8t.
.photoo2
.photoo4f1f
.photoof2f3s
.ph8o9t8o9o8ff9s8e8t.
.photoo4ff
.photooff3s
.pi8c9a9d8o8r.
.pi1ca
.pica2d
.pica1do
.picad4or
.pi8c9a9d8o8r8s.
.picado4rs2
.pi8p8e9l8i8n8e.
.p2ip
.pipe4
.pipel2i
.pipe1l4ine
.pi8p8e9l8i8n8e8s.
.pipeli1nes
.pi8p8e9l8i8n9i8n8g.
.pipel2in3i
.pipelin1in
.pipelinin4g
.pi9r8a9n8h8a8s.
.p4ir
.pi1ra
.pira2n
.pira4n1h4
.piranha4
.pl8a8c8a9b8l8e.
.1p2l2
.pla1ca
.placa1b2l2
.pl8a8n8t9h8o8p9p8e8r.
.3pla2n
.plan1t
.plantho4p1p
.planthop2pe
.planthop3per
.pl8a8n8t9h8o8p9p8e8r8s.
.planthoppe4r1s2
.pl8e8a8s9a8n8c8e.
.ple2a
.pleasa2
.plea3sanc
.pleasa2n
.pl8u8g9i8n.
.plug5in
.pl8u8g9i8n8s.
.plu5g4i2n1s2
.po8l9t8e8r9g8e8i8s8t.
.po4l2t
.pol1te
.polterg2
.poltergei2
.po8l8y9e8n8e.
.po2ly
.po8l8y9e8t8h9y8l9e8n8e.
.polye4t
.po9l8y8g9a9m8i8s8t.
.poly1ga
.poly2gam
.polygam2is
.po9l8y8g9a9m8i8s8t8s.
.polygamis4t1s2
.po8l8y8g9o8n9i9z8a9t8i8o8n.
.poly1go
.polygo2n2
.polygo3ni
.polygoniza1
.polygoniza1t2io
.polygo2nizatio2n
.po9l8y8p8h9o9n8o8u8s.
.polypho2n
.polypho1nou2
.polyphono2us
.po8l8y9s8t8y8r8e8n8e.
.po2lys4
.polys1t
.polys2ty
.po8m8e9g8r8a8n9a8t8e.
.po2me
.pome2g
.pome1gr
.pomegra2n2
.pomegra1na
.pomegran2at
.po8r8o9e8l8a8s9t8i8c.
.1p4or
.poro4e
.poro4el
.poroe1la
.poroelast2i
.poroelas1tic
.po8r9o8u8s.
.porou2
.poro2us
.po8r9t8a9b8l8e.
.por1ta
.por2tab
.portab2l2
.po8s8t9a8m9b8l8e.
.1pos
.pos2ta
.posta4m1b
.postamb2l2
.po8s8t9a8m9b8l8e8s.
.postambles2
.po8s8t9h8u9m8o8u8s.
.posthu1mo
.posthu3mo2us
.posthumou2
.po8s8t9s8c8r8i8p8t.
.pos4t1s2
.post4sc
.postscri2
.postscr2ip
.postscri2p1t
.po8s8t9s8c8r8i8p8t8s.
.pos4t1s2crip4t1s2
.po8s9t8u8r9a8l.
.pos1tu
.postu1ra
.pr8e9a8m9b8l8e.
.prea4m1b
.preamb2l2
.pr8e9a8m9b8l8e8s.
.preambles2
.pr8e9l8o8a8d8e8d.
.prel4oa
.preloa2d3
.preloa2d1ed
.pr8e9p8a8r9i8n8g.
.pre2pa
.prep4a4r1i
.prep2a2r
.preparin4g
.pr8e9p8r8i8n8t.
.pr2epr2
.preprin4t3
.pr8e9p8r8i8n8t8s.
.preprin4t4s2
.pr8e9p8r8o8c8e8s9s8o8r.
.pre3pro
.prepr2oc
.prepro1ce
.preproc2e2ss
.preproces1so
.pr8e9p8r8o8c8e8s9s8o8r8s.
.preprocesso4rs2
.pr8e9s8p8l8i8t9t8i8n8g.
.pre1sp
.pres1p2l2
.prespl1it
.prespl4i4t3t2
.presplit2t1in
.pr8e9w8r8a8p.
.prewra4
.pr8e9w8r8a8p8p8e8d.
.prewra4p1p
.prewrap2pe
.prewrap4p2ed
.pr8i8e8s8t9e8s8s8e8s.
.5pr2i4e4
.pri1est
.pries4t2e2ss
.priestess1e4s
.pr8e8t9t8y9p8r8i8n9t8e8r.
.pre4t3t2
.pret1ty
.pr2ettypr2
.prettyprin4t3
.pr8e8t9t8y9p8r8i8n9t8i8n8g.
.prettyprint2i
.prettyprin4t3ing
.prettyprin2t1in
.pr8o9c8e9d8u8r9a8l.
.pr2oc
.pro1ce
.proce1du
.procedu1ra
.pr8o8c8e8s8s.
.proc2e2ss
.pr8o9c8u8r9a8n8c8e.
.procu1ra
.procura2n
.pr8o8g9e9n8i8e8s.
.pro1ge
.pro1gen
.proge5n2ie4
.pr8o8g9e9n8y.
.pro4geny
.pr8o9g8r8a8m9m8a8b8l8e.
.pro1gr
.program1m
.program1ma
.program2mab
.programmab2l2
.pr8o8m9i9n8e8n8t.
.prom4i
.prom1in
.prom2ine
.promi1nen
.prominen1t
.pr8o9m8i8s9c8u9o8u8s.
.prom2is
.prom2is1c
.promis1cu
.promiscu1ou2
.promiscuo2us
.pr8o8m9i8s9s8o8r8y.
.prom4i2s1s
.promis1so
.promisso1ry
.pr8o8m9i8s8e.
.prom4ise
.pr8o8m9i8s8e8s.
.promis1e4s
.pr8o9p8e8l9l8e8r.
.pro3pel
.propel1l
.propel2le
.pr8o9p8e8l9l8e8r8s.
.propelle4r1s2
.pr8o9p8e8l9l8i8n8g.
.propell2i
.propel2lin4
.pr8o9h8i8b9i9t8i8v8e.
.pro1h2
.prohibi2t
.prohibi1ti
.prohibi1t2iv
.pr8o9h8i8b9i9t8i8v8e9l8y.
.prohibitiv4e1ly
.pr8o9s8c8i8u8t9t8o.
.pros2c
.pros1ci
.prosci1u
.prosciu4t3t2
.prosciut5to
.pr8o9t8e8s8t9e8r.
.pro1t
.pro4tes
.pr8o9t8e8s8t9e8r8s.
.proteste4r1s2
.pr8o9t8e8s9t8o8r.
.prot4es2to
.pr8o9t8e8s9t8o8r8s.
.protesto4rs2
.pr8o9t8o9l8a8n9g8u8a8g8e.
.pro1to
.proto1la
.proto4la2n
.protol2ang
.protolan1gu
.protolangu4a
.pr8o9t8o9t8y8p9a8l.
.proto1ty
.prototy1pa
.prototyp4al
.pr8o8v9i8n8c8e.
.prov1in
.prov2inc
.pr8o8v9i8n8c8e8s.
.pr8o9v8i8n9c8i8a8l.
.provin1ci
.provin3c2i1a
.provinci2al
.pr8o8w9e8s8s.
.prow2e2ss
.ps8e8u9d8o9d8i8f9f8e8r9e8n9t8i8a8l.
.2p1s2
.p2se
.ps4eu
.pseu1do
.pseudod1if
.pseudodi4f1f
.pseudodiffer1
.pseudodiffer3en1t
.pseudodifferent2i
.pseudodifferen1t2i1a
.pseudodifferenti2al
.ps8e8u9d8o9d8i8ff8e8r9e8n9t8i8a8l.
.pseudod1i4ff
.pseudodiffer1
.pseudodiffer3en1t
.pseudodifferent2i
.pseudodifferen1t2i1a
.pseudodifferenti2al
.ps8e8u9d8o9f8i9n8i8t8e.
.pseu2d5of
.pseudo2fi
.pseudo2fin
.pseudof2ini
.pseudofin2it
.pseudofin2ite
.ps8e8u9d8o9fi9n8i8t8e.
.pseu2d5o2fi
.pseudo2fin
.pseudofini
.pseudofin2it
.pseudofin2ite
.ps8e8u9d8o9f8i9n8i8t8e9l8y.
.pseudofinite1ly
.ps8e8u9d8o9fi9n8i8t8e9l8y.
.pseudofinite1ly
.ps8e8u9d8o9f8o8r8c8e8s.
.pseudo1fo
.pseudofo2r
.pseudofor1c
.pseudofor2ce
.ps8e8u9d8o8g9r8a9p8h8e8r.
.pseud4og
.pseudo1gr
.pseudog5ra3ph4er
.ps8e8u9d8o9g8r8o8u8p.
.pseudo4g4ro
.pseudogrou2
.ps8e8u9d8o9g8r8o8u8p8s.
.2p1s2eudogrou2p1s2
.ps8e8u9d8o9n8y8m.
.pseu4do2n
.pseudonym4
.ps8e8u9d8o9n8y8m8s.
.pseudony4m1s2
.ps8e8u9d8o9w8o8r8d.
.pseudo4wo2
.ps8e8u9d8o9w8o8r8d8s.
.pseudowor2d1s2
.ps8y9c8h8e9d8e8l9i8c.
.ps4y
.p4sy1c
.psy3ch
.psych4e2
.psy4ch4ed
.psychedel2i
.ps8y8c8h8s.
.psyc4h1s2
.pu9b8e8s9c8e8n8c8e.
.pub3
.pub4e
.pu4bes4
.pubes2c
.pubes1cen
.pubes3cenc
.qu8a8d9d8i8n8g.
.qu2
.qua2d
.quad4d1in
.quad1d4
.qu8a9d8r8a8t9i8c.
.qua1dr
.quadrat1ic
.qu8a9d8r8a8t9i8c8s.
.quadrati4c3s2
.qu8a8d9r8a9t8u8r8e.
.quadra2tu
.quadra3ture
.qu8a8d9r8i9p8l8e8g9i8c.
.quadri2p2l2
.quadr2ip
.quadripleg4ic
.qu8a8i8n8t9e8r.
.quai2
.qua4i4n
.quain1t
.qu8a8i8n8t9e8s8t.
.qu8a9s8i9e8q8u8i8v9a9l8e8n8c8e.
.quas2ie4
.quasie1q
.qu2asiequ2
.quasieq2ui2
.quasiequ2iv3
.quasiequi1va
.quasiequiv2ale
.quasiequiva3lenc
.qu8a9s8i9e8q8u8i8v9a9l8e8n8c8e8s.
.qu8a9s8i9e8q8u8i8v9a9l8e8n8t.
.quasiequiva1len1t
.qu8a9s8i9h8y9p8o9n8o8r9m8a8l.
.quasi3h
.quasihy3po
.quasihypo2n
.quasihyponor1m
.quasihyponor1ma
.qu8a9s8i9r8a8d9i9c8a8l.
.quas4i2r
.quasi1r5a
.quasira2d
.quasir2adi
.quasirad3i1ca
.qu8a9s8i9r8e8s8i8d9u8a8l.
.quasi4res
.quasire1si
.quasire2s2id
.quasiresi2du
.quasiresid1u1a
.qu8a9s8i9s8m8o8o8t8h.
.qua1sis
.quasi2s1m
.quasis1mo
.quasismoo2
.quasismo4oth
.qu8a9s8i9s8t8a9t8i8o8n9a8r8y.
.quasis1ta
.quasistation5a2r
.quasista1t2io
.quasistatio2n
.quasistatio1n1a
.qu8a9s8i9t8o8p8o8s.
.qu5a5si4t
.quasi1to
.quasito1pos
.qu8a9s8i9t8r8i9a8n9g8u9l8a8r.
.quasi5tr2i3a
.quasitri2a2n
.quasitri2ang
.quasitrian1gu
.quasitriangu1la
.quasitriangul2a2r
.qu8a9s8i9t8r8i8v9i8a8l.
.quasitr2i4v
.quasitriv3i
.quasitriv2i1a
.quasitrivi2al
.qu8i8n9t8e8s9s8e8n8c8e.
.q2ui2
.qui4n
.quin1t
.quin4t2e2ss
.quintes4senc
.qu8i8n9t8e8s9s8e8n8c8e8s.
.qu8i8n9t8e8s9s8e8n9t8i8a8l.
.quin1tessen1t
.quintessent2i
.quintessen1t2i1a
.quintessenti2al
.ra8b9b8i8t9r8y.
.2rab
.ra2b1b
.rabbi2t
.rabbi3tr
.rabbit5ry
.ra9d8i9o8g9r8a9p8h8y.
.ra2d
.r2adi
.ra3d2io
.radio5g
.radio2gr
.radio4g3ra1phy
.ra8f8f9i8s8h.
.raf5fi
.ra2f
.ra4f1f4
.raf2f5is
.raffis2h
.ra8ffi8s8h.
.raffi
.raffis
.raffis2h
.ra8f8f9i8s8h9l8y.
.raffis4h1l4
.raffish1ly
.ra8ffi8s8h9l8y.
.raffis4h1l4
.raffish1ly
.ra8m9s8h8a8c8k8l8e.
.ra4m1s2
.ram4s2h
.ramshack1
.ramshack1l
.ra8v9e8n9o8u8s.
.rav4e4no
.rave1nou2
.raveno2us
.re9a8r8r8a8n8g8e9m8e8n8t.
.re5ar1r4
.re2a2r
.rearran4ge
.rearra2n
.rearr2ang
.rearrange1me
.rearrange1men
.rearrange3men1t
.re9a8r8r8a8n8g8e9m8e8n8t8s.
.rearrangemen4t4s2
.re8c9i9p8r8o8c9i9t8i8e8s.
.reciproci1ti
.reciprocit2ie4
.re8c9t8a8n9g8l8e.
.rec4ta2n
.re2c1t
.rect5ang
.rec1ta
.rectan1gl2
.rectan1gle
.re8c9t8a8n9g8l8e8s.
.rectangles2
.re8c9t8a8n9g8u9l8a8r.
.rectan1gu
.rectangu1la
.rectangul2a2r
.re9d8i9r8e8c8t.
.2r2ed
.r4edi
.red4ir2
.redi1re
.redire2c1t
.re9d8i9r8e8c8t9i8o8n.
.redirec1t2io
.redirectio2n
.re9d8u8c9i8b8l8e.
.re1du
.redu2c
.reduci4b
.redu1ci
.reduci1b2l2
.re9e8c8h8o.
.ree2c
.ree2ch
.ree3cho2
.re9p8h8r8a8s8e.
.rep4hr4
.rephr2as
.re9p8h8r8a8s8e8s.
.rephras1e4s
.re9p8h8r8a8s8e8d.
.rephra4s4ed
.re9p8o9s8i9t8i8o8n.
.re4posi
.re1po
.re1pos
.repo3s2i1t2io
.reposi1ti
.repositio2n
.re9p8o9s8i9t8i8o8n8s.
.repositio2n3s2
.re9p8r8i8n8t.
.repr2
.reprin4t3
.re9p8r8i8n8t8s.
.reprin4t4s2
.re9s8t8o8r9a8b8l8e.
.r4es2to
.resto2ra
.resto2rab
.restorab2l2
.re8t8r8o9f8i8t.
.retro2fi
.re8t8r8o9fi8t.
.retro2fi
.re8t8r8o9f8i8t9t8e8d.
.retrof4i4t4t2
.retrofit2t1ed
.re8t8r8o9fi8t9t8e8d.
.retrofi4t4t2
.retrofit2t1ed
.re9u8s9a8b8l8e.
.r4eu2
.re2us4
.reusa2
.reu2s1ab
.reusab2l2
.re9u8s8e.
.re9w8i8r8e.
.rewi2
.rew4ir4
.re9w8r8a8p.
.rewra4
.re9w8r8a8p8p8e8d.
.rewra4p1p
.rewrap2pe
.rewrap4p2ed
.re9w8r8i8t8e.
.rewri4
.rewr2ite
.rh8i9n8o8c9e8r9o8s.
.rh4
.rh2i1no
.rhi4no4c
.rhino1ce
.rhinoc2ero
.ri8g8h8t9e8o8u8s.
.righ1teo
.righteou2
.righteo2us
.ri8g8h8t9e8o8u8s9n8e8s8s.
.righteous1n4
.righteous1nes
.righteousn2e2ss
.ri8n8g9l8e8a8d8e8r.
.rin4g
.ringl2
.rin1gle
.ringle2a
.ringlea2d1
.ri8n8g9l8e8a8d8e8r8s.
.ringleade4r5s2
.ro9b8o8t.
.ro9b8o8t8s.
.robo4t1s2
.ro9b8o8t8i8c.
.ro9b8o8t9i8c8s.
.roboti4c3s2
.ro8u8n8d9t8a8b8l8e.
.rou2
.roun2d
.round1ta
.round2tab
.roundtab2l2
.ro8u8n8d9t8a8b8l8e8s.
.roundta5bles2
.sa8l8e8s9c8l8e8r8k.
.sa2
.s2ale
.sales2
.sales2c
.salescle5
.sales1c4l4
.sa8l8e8s9c8l8e8r8k8s.
.salescler4k1s2
.sa8l8e8s9w8o8m8a8n.
.sales4w2
.sale4s1wo2
.saleswom1
.saleswo1ma
.saleswoma2n
.sa8l8e8s9w8o8m8e8n.
.saleswo2me
.saleswo1men
.sa8l9m8o9n8e8l9l8a.
.s4a2l4m
.salmo2n4
.sal1mo
.salmon4ella
.salmonel1l
.sa8l9t8a9t8i8o8n.
.sa4l4t
.sal1ta
.salta1t2io
.saltatio2n
.sa8r9s8a9p8a8r9i8l9l8a.
.s2a2r
.sa2r4sa2
.sa4rs2
.sars1ap
.s2a2rsap2a2r4
.sarsa1pa
.sarsap4a4r1i
.sarsaparil1l
.sa8u8e8r9k8r8a8u8t.
.sau4
.sauerkrau4t
.sc8a8t9o9l8o8g9i9c8a8l.
.s1ca
.sca1to
.scato3log1ic
.s1catologi1ca
.sc8h8e8d9u8l9i8n8g.
.s2ch2
.sche2
.s4ch4ed
.sche4dul
.sche1du
.schedul2i
.schedul3ing
.sc8h8i8z9o9p8h8r8e8n8i8c.
.schi2z
.schi1zo
.schiz2oph
.schizop4hr4
.sc8h8n8a8u9z8e8r.
.sc2h1n
.sch1na
.schn2au
.schnau2z4e
.schnauz1er
.sc8h8o8o8l9c8h8i8l8d.
.s4cho2
.schoo2
.schoo4l1c2
.s2chool2ch
.schoolch4il2
.schoolchi2ld
.sc8h8o8o8l9c8h8i8l8d9r8e8n.
.schoolchil3dr
.schoolchildre4
.schoolchil5dren
.sc8h8o8o8l9t8e8a8c8h8e8r.
.schoo4l2t
.school1te
.s2chooltea2ch
.schoolteache2
.sc8h8o8o8l9t8e8a8c8h9e8r8s.
.schoolteach3e4r1s2
.sc8r8u9t8i9n8y.
.scru2t1i5n
.scr4u1t2i
.scrut4iny
.sc8y8t8h9i8n8g.
.s1cy
.scy3thin
.se8l8l9e8r.
.sel2le
.se8l8l9e8r8s.
.selle4r1s2
.se8c9r8e9t8a8r9i8a8t.
.se1cr
.se4c3re1ta
.secret2a2r
.secretar1i
.secretar2i3a
.se8c9r8e9t8a8r9i8a8t8s.
.secretaria4t1s2
.se8m9a9p8h8o8r8e.
.se1ma
.se4map
.semapho4r
.se8m9a9p8h8o8r8e8s.
.se9m8e8s9t8e8r.
.4se1me
.se2mes
.se8m8i9d8e8f9i9n8i8t8e.
.sem2id
.semide1f
.semidef5i5n2ite
.semide1fi
.semide2fin
.semidef2ini
.semidefin2it
.se8m8i9d8e8fi9n8i8t8e.
.semide1fi
.semidefi5n2ite
.semide2fin
.semidefini
.semidefin2it
.se8m8i9d8i9r8e8c8t.
.semi2di
.semid4ir2
.semidi1re
.semidire2c1t
.se8m8i9h8o9m8o9t8h8e8t9i8c.
.semi3h
.semiho1mo
.semihom4oth3
.semihomoth2e
.semihomo3the4t
.semihomothet1ic
.se8m8i9r8i8n8g.
.sem4ir
.semir1i
.semirin4g
.se8m8i9r8i8n8g8s.
.semirings2
.se8m8i9s8i8m9p8l8e.
.se4m2is
.semisi4m1p
.semisim1p2l2
.se8m8i9s8k8i8l8l8e8d.
.sem4is4k2
.semisk1i
.semisk4il1l
.semiskil2le
.se8r8o9e8p8i9d8e9m8i9o9l8o8g9i9c8a8l.
.s2er4o
.sero4e
.seroep4id
.seroepi3de
.seroepid4em
.seroepidem2io
.seroepidemi1ol
.seroepidemio3log1ic
.seroepidemiologi1ca
.se8r9v8o9m8e8c8h9a8n8i8s8m.
.4ser3vo
.servo2me
.servome2ch
.servomech5a5nis
.servomecha2n
.servomechani2s1m
.se8r9v8o9m8e8c8h9a8n8i8s8m8s.
.servomechan4is4m1s2
.se8s9q8u8i9p8e9d8a9l8i8a8n.
.s1e4s
.sesqu2
.sesq2ui2
.sesqu2ip
.sesquipe4
.sesqui2p2ed
.sesquip2e2d2a
.sesquipedal1i
.sesquipedal2i1a
.sesquipedali2a2n
.se8t9u8p.
.se1tu
.se8t9u8p8s.
.setu2p1s2
.se9v8e8r8e9l8y.
.5sev
.sev1er
.sev4erel
.severe1ly
.sh8a8p8e9a8b8l8e.
.sha3pe4a
.shape1a4b
.shapeab2l2
.sh8o8e9s8t8r8i8n8g.
.sho4
.sho2est4r
.shoestrin4g
.sh8o8e9s8t8r8i8n8g8s.
.shoestrings2
.si8d8e9s8t8e8p.
.5side4s2
.s2id
.sideste4p
.si8d8e9s8t8e8p8s.
.sideste2p1s2
.si8d8e9s8w8i8p8e.
.sides4w2
.sideswi2
.sidesw2ip
.sideswipe4
.sk8y9s8c8r8a8p8e8r.
.sk2
.skys4c
.skyscrap3er
.sk8y9s8c8r8a8p8e8r8s.
.skyscrape4r1s2
.sm8o8k8e9s8t8a8c8k.
.2s1m
.s1mo
.s4m2ok
.smokes4
.smokes1ta
.smokestack1
.sm8o8k8e9s8t8a8c8k8s.
.smokestac4k1s2
.sn8o8r9k8e8l9i8n8g.
.s1n4
.snorke5l2i
.snorke4l3ing
.so9l8e9n8o8i8d.
.1so
.sol4eno
.solenoi2
.soleno2id
.so9l8e9n8o8i8d8s.
.solenoi2d1s2
.so8l8u8t8e.
.so1lut
.so8l8u8t8e8s.
.so8v9e8r9e8i8g8n.
.4sov
.soverei2
.sovere2ig2
.so8v9e8r9e8i8g8n8s.
.sovereig2n1s2
.sp8a9c8e8s.
.2s1pa
.spa4ce
.sp8e9c8i8o8u8s.
.spe2c
.spe1c2i
.spec2io
.speciou2
.specio2us
.sp8e8l8l9e8r.
.spel1l
.spel2le
.sp8e8l8l9e8r8s.
.spelle4r1s2
.sp8e8l8l9i8n8g.
.spell2i
.spel2lin4
.sp8e9l8u8n8k9e8r.
.spelu4nk2
.spelunk1er
.sp8e8n8d9t8h8r8i8f8t.
.spen4d
.spend2th
.spendt4hr4
.spendthr4i2ft
.sp8h8e8r9o8i8d.
.s2phe
.3sph4er
.sph2ero
.spheroi2
.sphero2id
.sp8h8e8r9o8i8d9a8l.
.spheroi1d2a
.sp8h8i8n9g8e8s.
.sph5ing
.sph4inge
.sp8i8c9i9l8y.
.sp2i1ci
.spici1ly
.sp8i8n9o8r8s.
.spi2n
.sp4i1no
.spino4rs2
.sp8o8k8e8s9w8o8m8a8n.
.sp2ok
.spokes4
.spokes4w2
.spoke4s1wo2
.spokeswom1
.spokeswo1ma
.spokeswoma2n
.sp8o8k8e8s9w8o8m8e8n.
.spokeswo2me
.spokeswo1men
.sp8o8r8t8s9c8a8s8t.
.s1p4or4
.spor4t1s2
.sport4sc
.sports1ca
.sp8o8r8t8s9c8a8s8t9e8r.
.sportscast5er
.sp8o8r9t8i8v8e9l8y.
.spor1ti
.spor4t2iv
.sportiv4e1ly
.sp8o8r8t8s9w8e8a8r.
.sport4sw2
.sportswe2a2r
.sp8o8r8t8s9w8r8i8t8e8r.
.sportswri4
.sportswr2ite
.sp8o8r8t8s9w8r8i8t8e8r8s.
.sportswrit5e4r1s2
.sp8r8i8g8h8t9l8i8e8r.
.spr2
.spr2ig
.sprigh2tl
.sprightl2ie4
.sq8u8e8a9m8i8s8h.
.squ2
.squeam2is
.squeamis2h
.st8a8n8d9a8l8o8n8e.
.5st4and
.sta2n
.stan1d2a
.standalo2n
.st8a8r9t8l8i8n8g.
.st2a2r
.star2tl
.st8a8r9t8l8i8n8g9l8y.
.startlingl2
.startling1ly
.st8a9t8i8s9t8i8c8s.
.statis1t2i
.statis1tic
.statisti4c3s2
.st8e8a8l8t8h9i8l8y.
.stea4l
.stea4lt
.stealth3i
.steal4th4il2
.stealthi1ly
.st8e8e8p8l8e9c8h8a8s8e.
.s1tee
.stee4p1
.stee1p2l2
.steeple2ch
.st8e8r8e8o9g8r8a8p8h9i8c.
.stere1o
.stereo2g
.stereo1gr
.stereo5graph1ic
.stereogr4aphi
.st8o9c8h8a8s9t8i8c.
.s1to
.sto2ch4
.stochast2i
.stochas1tic
.st8r8a8n8g8e9n8e8s8s.
.st4r
.s1tra
.stran4ge
.stra2n
.str2ang
.strange4n4e
.stran1gen
.strange1nes
.strangen2e2ss
.st8r8a8p9h8a8n8g8e8r.
.straph2an4g
.straphang5er
.strapha2n
.st8r8a8t9a9g8e8m.
.stra2ta
.st8r8a8t9a9g8e8m8s.
.stratage4m1s2
.st8r8e8t8c8h9i9e8r.
.stre4tc
.stret4ch
.stretch2ie4
.st8r8i8p9t8e8a8s8e.
.str2ip
.stri2p1t
.strip2te
.st8r8o8n8g9h8o8l8d.
.stro2n
.strongho2l2d
.st8r8o8n8g9e8s8t.
.st8u9p8i8d9e8r.
.s1tu
.stup4id
.stupi3de
.st8u9p8i8d9e8s8t.
.stupide4s2
.su8b9d8i8f9f8e8r9e8n9t8i8a8l.
.1su
.su4b3
.su4b1d
.subd1if
.subdi4f1f
.subdiffer1
.subdiffer3en1t
.subdifferent2i
.subdifferen1t2i1a
.subdifferenti2al
.su8b9d8i8ff8e8r9e8n9t8i8a8l.
.subd1i4ff
.subdiffer1
.subdiffer3en1t
.subdifferent2i
.subdifferen1t2i1a
.subdifferenti2al
.su8b9e8x9p8r8e8s9s8i8o8n.
.sub4e
.sub1ex3p
.subexpr2
.subex3pr2e2ss
.subexpres1si
.subexpres1s2io
.subexpres5sio2n
.su8b9e8x9p8r8e8s9s8i8o8n8s.
.subexpressio2n3s2
.su8m9m8a9b8l8e.
.su2m
.sum1m
.sum1ma
.sum2mab
.summab2l2
.su8p8e8r9e8g8o.
.su1pe
.supere1go
.su8p8e8r9e8g8o8s.
.supere4gos
.su9p8r8e8m9a9c8i8s8t.
.supr2
.supre4mac
.supre1ma
.suprem4a2ci
.su9p8r8e8m9a9c8i8s8t8s.
.supremacis4t1s2
.su8r9v8e8i8l9l8a8n8c8e.
.su2r
.surv4e
.survei2
.surveil1l
.surveilla2n
.sw8i8m9m8i8n8g9l8y.
.sw2
.swi2
.swim1m
.swimm4ingl2
.swimm5ing1ly
.sy8m8p9t8o9m8a8t8i8c.
.sy4m1p
.sym2p1t
.symp1to
.sympto2ma
.symptomat1ic
.sy8n9c8h8r8o9m8e8s8h.
.syn3c4hr4
.syn2ch
.synchro2me
.synchro2mes
.synchrom4es2h
.sy8n9c8h8r8o9n8o8u8s.
.synchro2n
.synchro1nou2
.synchrono2us
.sy8n9c8h8r8o9t8r8o8n.
.synchrotro2n
.ta8f8f9r8a8i8l.
.4ta2f4
.ta4f1f4
.taffr2ai2
.ta8ff9r8a8i8l.
.4ta4ff4
.taffr2ai2
.ta8l8k9a9t8i8v8e.
.ta2l
.4talk
.talka3
.talka4t
.talka1t2iv
.ta9p8e8s9t8r8y.
.tap2est4r
.tape4stry
.ta9p8e8s9t8r8i8e8s.
.tapestr2ie4
.ta8r9p8a8u9l8i8n.
.t2a2r
.tar2p
.tar1pa
.tarpau4l2
.tarpaul2i
.ta8r9p8a8u9l8i8n8s.
.tarpaul2i2n1s2
.te9l8e8g9r8a9p8h8e8r.
.tele1gr
.teleg5ra3ph4er
.te9l8e8g9r8a9p8h8e8r8s.
.telegraphe4r1s2
.te8l8e9k8i9n8e8t9i8c.
.teleki4n
.telek1i
.telek2ine
.teleki3net1ic
.te8l8e9k8i9n8e8t9i8c8s.
.telekineti4c3s2
.te8l8e9r8o9b8o8t9i8c8s.
.te4l1er
.tel4ero
.teler5ob
.teleroboti4c3s2
.te8l8l9e8r.
.tel1l
.tel2le
.te8l8l9e8r8s.
.telle4r1s2
.te8m9p8o9r8a8r9i8l8y.
.te4m1p
.tem1p4or
.tempo1ra
.tempo4raril
.tempor2a2r
.temporar1i
.temporari1ly
.te8n9u8r8e.
.te8s8t9b8e8d.
.tes2t1b
.test4be2d
.te8x8t9w8i8d8t8h.
.3tex
.tex1t2
.textw4
.textwi2
.textw2id
.textwid2th
.th8a8l9a9m8u8s.
.tha3la
.thala3m
.thala1mu
.thalam2us
.th8e8r9m8o9e8l8a8s9t8i8c.
.th2e
.ther3m4
.ther1mo
.thermo4el
.thermoe1la
.thermoelast2i
.thermoelas1tic
.ti8m8e9s8t8a8m8p.
.ti2mes
.times1ta
.timesta4m1p
.ti8m8e9s8t8a8m8p8s.
.timestam2p1s2
.to8o8l9k8i8t.
.too2
.toolk1i
.to8o8l9k8i8t8s.
.toolki4t1s2
.to8p8o9g8r8a8p8h9i9c8a8l.
.to5po4g
.topo1gr
.topo5graph1ic
.topogr4aphi
.topographi1ca
.to8q8u8e8s.
.to1q
.toqu2
.tr8a8i9t8o8r9o8u8s.
.1tra
.tr2ai2
.trai1to
.traitorou2
.traitoro2us
.tr8a8n8s9c8e8i8v8e8r.
.tra2n
.tra2n1s2
.trans4c
.tran4s3cei2
.transce2iv
.tr8a8n8s9c8e8i8v8e8r8s.
.transceive4r1s2
.tr8a8n8s9g8r8e8s8s.
.tran2s3g
.trans1gr
.transgr2e2ss
.tr8a8n8s9v8e8r9s8a8l.
.tran4sv
.transve4r1s2
.transver1sa2
.tr8a8n8s9v8e8r9s8a8l8s.
.transversa2l1s2
.tr8a8n8s9v8e8s9t8i8t8e.
.transv4e2s
.transvest2i
.transvest2ite
.tr8a8n8s9v8e8s9t8i8t8e8s.
.transvestit4es
.tr8a9v8e8r8s9a9b8l8e.
.trave4r1s2
.traver1sa2
.traver2s1ab
.traversab2l2
.tr8a9v8e8r9s8a8l.
.tr8a9v8e8r9s8a8l8s.
.traversa2l1s2
.tr8i9e8t8h8y8l9a8m8i8n8e.
.tri5et
.tr2ie4
.triethy3la
.triethylam1in
.triethylam2ine
.tr8e8a8c8h9e8r8i8e8s.
.trea2ch
.treache2
.treacher1i
.treacher2ie4
.tr8o8u9b8a9d8o8u8r.
.trou2
.trouba2d
.trouba1do
.trou2badou2
.tu8r9k8e8y.
.1tu
.tu8r9k8e8y8s.
.turkeys4
.tu8r8n9a8r8o8u8n8d.
.tur4n2a2r
.tur1na
.turnarou2
.turnaroun2d
.tu8r8n9a8r8o8u8n8d8s.
.turnaroun2d1s2
.ty8p9a8l.
.1ty
.ty1pa
.typ4al
.un9a8t9t8a8c8h8e8d.
.un2at4
.una4t3t2
.unat1ta
.unatta2ch
.unattache2
.unatta4ch4ed
.un9e8r8r9i8n8g9l8y.
.un4er
.uner4r4
.unerrin4g
.unerringl2
.unerring1ly
.un9f8r8i8e8n8d9l8y.
.un3f
.unfri2
.unfr2ie4
.unfrien2d1ly
.un9f8r8i8e8n8d9l8i9e8r.
.unfr2ie4ndl2ie4
.va8g8u8e8r.
.1va
.vag4
.va5guer
.va2gue
.va8u8d8e9v8i8l8l8e.
.vaude1v4
.vaude2v3i4l
.vaude1vi
.vaudevil1l
.vaudevil2le
.vi8c9a8r8s.
.v4ic2a2r
.vi1ca
.vica4rs2
.vi8l9l8a8i8n9e8s8s.
.2vil
.vil1l
.villai2
.villa4i4n
.villa2ine
.villai5n2e2ss
.villai1nes
.vi8s9u8a8l.
.vi3su
.visu1al
.vi8s9u8a8l9l8y.
.visual1l
.visual1ly
.vi9v8i8p9a9r8o8u8s.
.3v2iv
.viv2i4p
.vivi1pa
.vivip2a2r
.viviparou2
.viviparo2us
.vo8i8c8e9p8r8i8n8t.
.voi4
.voi3cep
.voicepr2
.voiceprin4t3
.vs8p8a8c8e.
.v2s1pa
.vspa4ce
.wa8d9d8i8n8g.
.wa2d
.wad4d1in
.wad1d4
.wa8l8l9f8l8o8w8e8r.
.wal1l
.wal2lf
.wallf4l2
.wallflow1er
.wa8l8l9fl8o8w8e8r.
.wal2lfl2
.wallflow1er
.wa8l8l9f8l8o8w9e8r8s.
.wallflowe4r1s2
.wa8l8l9fl8o8w9e8r8s.
.wallflowe4r1s2
.wa8r8m9e8s8t.
.w2a2r
.war1m
.war2me
.war2mes
.wa8s8t8e9w8a8t8e8r.
.was4t
.waste2w
.waste1w5a
.wastewa1te
.wa8v8e9g8u8i8d8e.
.waveg3
.waveg2ui2
.wavegu2id
.wa8v8e9g8u8i8d8e8s.
.waveguide4s2
.wa8v8e9l8e8t.
.wa8v8e9l8e8t8s.
.wavele4t1s2
.we8b9l8i8k8e.
.w2e1b
.web2l2
.web3l4ik
.we8e8k9n8i8g8h8t.
.weekn2ig
.we8e8k9n8i8g8h8t8s.
.weeknigh4t1s2
.wh8e8e8l9c8h8a8i8r.
.whee4l1c2
.wheel2ch
.wheelchai2
.wheelcha4ir
.wh8e8e8l9c8h8a8i8r8s.
.wheelchai4rs2
.wh8i8c8h9e8v8e8r.
.whi4
.wh4i2ch
.whiche2
.whichev1er
.wh8i8t8e9s8i8d8e8d.
.wh2ite
.whit4es
.white1si
.white2s2id
.whitesi2d1ed
.wh8i8t8e9s8p8a8c8e.
.white1sp
.white2s1pa
.whitespa4ce
.wh8i8t8e9s8p8a8c8e8s.
.wi8d8e9s8p8r8e8a8d.
.w2id
.wide4s2
.wide1sp
.wides4pre
.widespr2
.widesprea2d1
.wi8n8g9s8p8a8n.
.win4g
.wings2
.wing2s1pa
.wingspa4n
.wi8n8g9s8p8a8n8s.
.wingspa2n1s2
.wi8n8g9s8p8r8e8a8d.
.wingspr2
.wingsprea2d1
.wi8t8c8h9c8r8a8f8t.
.wi4tc
.wit4ch
.witchcra2f4t
.witchcra2f
.wo8r8d9s8p8a8c9i8n8g.
.1wo2
.wor2d1s2
.words4p
.word2s1pa
.wordsp4a2ci
.wordspa2c1in
.wordspac1ing
.wo8r8k9a8r8o8u8n8d.
.work2a2r
.workarou2
.workaroun2d
.wo8r8k9a8r8o8u8n8d8s.
.workaroun2d1s2
.wo8r8k9h8o8r8s8e.
.workh4
.workhor4se
.workho4rs2
.wo8r8k9h8o8r8s8e8s.
.workhors3e4s
.wr8a8p9a8r8o8u8n8d.
.wra4
.wrap2a2r4
.wra1pa
.wraparou2
.wraparoun2d
.wr8e8t8c8h9e8d.
.wre4tc
.wret4ch
.wretche2
.wret4ch4ed
.wr8e8t8c8h9e8d9l8y.
.wretche2d1ly
.ye8s9t8e8r9y8e8a8r.
.yes4
.yesterye2a2r
.al9g8e9b8r8a8i9s8c8h8e.
.algebra2is1c
.algebrais3ch2
.algebraische2
.al9l8e9g8h8e9n8y.
.al1l
.al2le
.al3leg
.alleghe2n
.ar9k8a8n9s8a8s.
.arka2n
.arkan2sa2
.arka2n1s2
.at8p9a8s8e.
.a4t1p
.at1pa
.at8p9a8s8e8s.
.atpas1e4s
.au8s9t8r8a8l9a8s8i8a8n.
.a2us
.aus1t4r
.aus1tra
.australas2i1a
.australasi2a2n
.au8t8o9m8a8t8i9s8i8e8r9t8e8r.
.automa3tis
.automatis2ie4
.automatisiert3er
.be9d8i8e9n8u8n8g.
.4be2d
.b4e3di
.be5di3en
.bed2ie4
.bedie3nu4n
.be8m8b8o.
.4be5m
.be4m5b
.bi8b9l8i9o9g8r8a9p8h8i9s8c8h8e.
.bibliogr4aphi
.bibliograph2is1c
.bibliographis3ch2
.bibliographische2
.bo8s9t8o8n.
.5bos4
.bos1to
.bosto2n
.br8o8w8n9i8a8n.
.brown5i
.brow3n4i1a
.browni3a2n
.br8u8n8s9w8i8c8k.
.bru2n
.bru2n3s4
.brun4sw2
.brunswi2
.brunswick1
.bu9d8a9p8e8s8t.
.bu1d2a
.ca8r9i8b9b8e8a8n.
.car1i
.car4ib
.cari2b1b
.carib2be
.caribbea2n
.ch8a8r8l8e8s9t8o8n.
.char4le4
.char1l
.charles2
.charl4es2to
.charle3sto2n
.ch8a8r9l8o8t8t8e8s9v8i8l8l8e.
.char3lo4
.charlo4t3t2
.charlot4tes
.charlotte4sv
.charlottes2vil
.charlottesvil1l
.charlottesvil2le
.co9l8u8m9b8i8a.
.colum4bi
.colu4m1b
.columb2i1a
.cz8e8c8h8o9s8l8o9v8a9k8i8a.
.c2ze4
.cze2ch
.cze3cho2
.czechos4l2
.czechos4lov
.czechoslo1va
.czechoslovak1i
.czechoslovak2i1a
.de8l9a9w8a8r8e.
.de1la
.de4law
.delaw2a2r
.di8j8k9s8t8r8a.
.di3j
.dij4k1s2
.dijkst4r
.dijks1tra
.du8a8n8e.
.d1u1a
.dua2n
.dy9n8a9m8i9s8c8h8e.
.5dyn
.dy1na
.dynam2is
.dynam2is1c
.dynamis3ch2
.dynamische2
.en8g9l8i8s8h.
.engl2
.englis2h
.eu8l8e8r9i8a8n.
.eul4e
.eu3l4er1i
.eule1r2i3a4
.euleri2a2n
.ev8a8n9s8t8o8n.
.e1va
.eva2n
.evan4st
.eva2n1s2
.evans1to
.evansto2n
.fe8b9r8u9a8r8y.
.f2e4b
.fe3br
.febru3a
.febru2a2r
.fe8s8t9s8c8h8r8i8f8t.
.fes4t1s2
.fest4sc
.fests2ch2
.festsc4hr4
.festschr4i2ft
.fl8o8r9i9d8a.
.flor2id
.flori1d2a
.fl8o8r9i9d8a.
.flor2id
.flori1d2a
.fl8o8r9i9d9i8a8n.
.flori2di
.florid5i2a2n
.flori1d4i3a
.fl8o8r9i9d9i8a8n.
.flori2di
.florid5i2a2n
.flori1d4i3a
.fo8r9s8c8h8u8n8g8s9i8n9s8t8i9t8u8t.
.fors4c
.fors2ch2
.forschungs2
.forschung2s1in
.forschungs2i2n1s2
.forschungsinst2i
.forschungsinsti1tu
.fr8e8e9b8s8d.
.fre2e1b
.free2b5s2
.freeb4s5d
.fu8n8k9t8s8i8o8n8a8l.
.3fu
.fu4nk2
.funk5t
.funk4t1s2
.funkt1s2io
.funkt5sio2n
.funktsio1n5a
.ga8u8s8s9i8a8n.
.ga2us
.gau2ss
.gaus1si
.gauss2i1a
.gaussi2a2n
.gh8o8s8t9s8c8r8i8p8t.
.ghos4t1s2
.ghost4sc
.ghostscri2
.ghostscr2ip
.ghostscri2p1t
.gh8o8s8t9v8i8e8w.
.ghos4tv
.ghostv2ie4
.gr8a8s8s9m8a8n8n9i8a8n.
.gr2as
.gra2ss
.gras2s1m
.grass3ma
.grassma2n3
.grassma4n1n2
.grassman3n4i1a
.grassma2nni3a2n
.gr8e8i8f8s9w8a8l8d.
.grei2
.grei2f3s
.greifsw2
.greifswa2ld
.gr8o8t8h8e8n9d8i8e8c8k.
.g4ro
.gro4th2e
.gr4oth
.grothe2n
.grothend2ie4
.grothendieck1
.gr8u8n8d9l8e8h9r8e8n.
.gru2n
.grundle1h4
.grundle4hr4
.ha9d8a9m8a8r8d.
.ha2d
.ha1d2a
.hada2m2
.had4a1ma
.hadam2a2r
.ha8i9f8a.
.hai1fa
.ha8m8i8l9t8o8n9i8a8n.
.ha4m
.hami4lt
.hamil1to
.hamilto2n
.hamilto3n4i1a
.hamiltoni3a2n
.he8l9s8i8n8k8i.
.he2l1s2
.hel2s1in
.hels4i4nk2
.helsink1i
.he8r9m8i8t9i8a8n.
.her3mit
.hermi1ti
.herm4i1t2i1a
.hermiti2a2n
.hi8b8b8s.
.hi2b1b
.hib2b5s2
.ho8k9k8a8i9d8o.
.h2ok
.hokk4
.hokkai2
.hokka2id
.hokkai1do
.ja8c9k8o8w9s8k8i.
.5ja
.jack1
.jackowsk2
.jackowsk1i
.ja8n9u9a8r8y.
.ja2n
.jan3u1a
.janu2a2r
.ja9p8a9n8e8s8e.
.ja4p
.ja1pa
.japa2n
.japa1nes
.japane1s2e
.ka8d9o8m9t8s8e8v.
.ka2d
.ka1do
.kado4mt
.kadom4t1s2
.kadomt5sev
.ka8n9s8a8s.
.ka2n
.kan2sa2
.ka2n1s2
.ka8r8l8s9r8u8h8e.
.k2a2r
.kar1l
.kar2l1s2
.karls1r
.ko8r9t8e9w8e8g.
.ko5r
.kr8i8s8h8n8a.
.kr2is
.kr3is2h
.kris2h1n
.krish1na
.kr8i8s8h9n8a9i8s8m.
.krishnai2
.krishnai2s1m
.kr8i8s8h9n8a8n.
.krishn2a2n
.la8n9c8a8s9t8e8r.
.lan1ca
.lancast5er
.le9g8e8n8d8r8e.
.le1gen
.legen1dr
.legendre4
.le8i8c8e8s9t8e8r.
.lei2
.le5ic
.leices5t
.li8p9s8c8h8i8t8z.
.l2ip
.li2p1s2
.lips2ch2
.lips3chit
.lipschi4tz
.li8p9s8c8h8i8t8z9i8a8n.
.lipschit2z1i
.lipschitz2i1a
.lipschitzi2a2n
.lo8j9b8a8n.
.lo5j
.lojba2n
.lo8u9i9s8i9a8n8a.
.lou2
.lo2ui2
.louis2i1a
.louisi2a2n
.louisia1na
.ma8c9o8s.
.ma1co
.ma8n9c8h8e8s9t8e8r.
.man2ch
.manche2
.manch1es
.ma8r9k8o8v9i8a8n.
.marko5vi2a2n
.markov2i1a
.ma8r8k8t9o8b8e8r9d8o8r8f.
.mark5t
.mark1to
.markto3b
.marktober1do
.marktoberd4or
.marktoberdor1f
.ma8s8s9a9c8h8u9s8e8t8t8s.
.ma2ss
.mas1sa2
.massa2ch
.massach2us
.massachuse4t3t2
.massachuset4t1s2
.ma8x9w8e8l8l.
.maxwel4l
.mi9c8r8o9s8o8f8t.
.micro2so
.microso2ft3
.mi8n9n8e9a8p9o9l8i8s.
.m2i4n1n2
.minne4
.minneapol2i
.mi8n9n8e9s8o8t8a.
.min1nes
.minne1so
.minneso1ta
.mo8s9c8o8w.
.mos2c
.mos1co
.na8c8h9r8i8c8h8t8e8n.
.1na
.na2ch
.nac4hr4
.na2chr4i2ch
.nachricht1en
.na8s8h9v8i8l8l8e.
.n4as
.nas2h
.nash2vil
.nashvil1l
.nashvil2le
.ne8t9b8s8d.
.ne2t1b
.net2b5s2
.netb4s5d
.ne8t9s8c8a8p8e.
.ne4t1s2
.net4sc
.netsca4p
.nets1ca
.ni8j9m8e9g8e8n.
.ni3j
.nijme2g
.nijme1gen
.no8e9t8h8e8r9i8a8n.
.3noe
.noeth2e
.noether1i
.noethe1r2i3a4
.noetheri2a2n
.no8o8r8d9w8i8j8k8e8r9h8o8u8t.
.noo2
.no3ord
.noord1w
.noordwi2
.noordwi3j
.noordwijk1er
.noordwijker1h4
.noordwijkerhou2
.no9v8e8m9b8e8r.
.nove4m5b
.op8e8n9b8s8d.
.ope4n1b4
.open2b5s2
.openb4s5d
.op8e8n9o8f8f8i8c8e.
.op4eno
.openo4f1f
.openof1fi
.op8e8n9o8ffi8c8e.
.pa8l8a9t8i8n8o.
.pala2t1in
.palat2i1no
.pa9l8e8r9m8o.
.paler3m4
.paler1mo
.pe9t8r8o8v9s8k8i.
.petro3v
.petrovsk2
.petrovsk1i
.pf8a8f8f9i8a8n.
.4pf
.p1fa
.pfa2f
.pfa4f1f4
.pfaf1fi
.pfaff2i3a
.pfaffi2a2n
.pf8a8ffi8a8n.
.pfaffia2n
.ph8i8l9a9d8e8l9p8h8i8a.
.phi4l4ade
.phila2d
.philade2lp
.philadel5phi
.philadelph2i1a
.ph8i8l9o9s8o8p8h9i9s8c8h8e.
.philo2so
.philos4op
.philos2oph
.philosoph2is1c
.philosophis3ch2
.philosophische2
.po8i8n9c8a8r8e.
.poin2
.poi2
.poinc2a2r5
.poin1ca
.po9t8e8n9t8i8a8l9g8l8e8i9c8h8u8n8g.
.p4ot
.po1ten1t
.potent2i
.poten1t2i1a
.potenti2al
.potentia4l1g4
.potentialgl2
.potential1gle
.potentialglei2
.potentialgle5ic
.potentialgle4i2ch
.ra9d8h8a9k8r8i8s8h9n8a8n.
.rad1h2
.radhakr2is
.radhakr3is2h
.radhakris2h1n
.radhakrish1na
.radhakrishn2a2n
.ra8t8h8s9k8e8l9l8e8r.
.r4ath
.ra2t4h1s2
.rathsk2
.rath4ske
.rathskel1l
.rathskel2le
.ri8e9m8a8n8n9i8a8n.
.r2ie4
.rie5ma2n
.rie1ma
.riema4n1n2
.rieman3n4i1a
.riema2nni3a2n
.ry8d9b8e8r8g.
.ry1d
.ryd1b
.rydberg2
.sc8h8o8t9t8i8s8c8h8e.
.scho4t3t2
.schott2is1c
.s2ch2ottis3ch2
.schottische2
.sc8h8r8o9d8i8n8g9e8r.
.sc4hr4
.schrod1in
.schrod4inge
.sc8h8w8a9b8a9c8h8e8r.
.sch1w
.s2chwaba2ch
.schwabache2
.sc8h8w8a8r8z9s8c8h8i8l8d.
.schw2a2r
.s2ch2warzs2ch2
.schwarzsch4il2
.schwarzschi2ld
.se8p9t8e8m9b8e8r.
.se2p1t
.sep2te
.septe4m5b
.st8o8k8e8s9s8c8h8e.
.st2ok
.stokes4
.stok2e2ss
.stokes2s5c
.stokess2ch2
.stokessche2
.st8u8t8t9g8a8r8t.
.stu4t3t2
.stut4t1g
.stutt1ga
.stuttg2a2r
.su8s9q8u8e9h8a8n9n8a.
.s2us
.susqu2
.susque1h4
.susqueha2n
.susqueha4n1n2
.susquehan1na
.ta8u9b8e8r9i8a8n.
.tau4b
.taub4e
.tau3ber
.tauber1i
.taube1r2i3a4
.tauberi2a2n
.te8c8h9n8i9s8c8h8e.
.te2ch
.tec2h1n
.techn2is1c
.te2chnis3ch2
.technische2
.te8n9n8e8s9s8e8e.
.t4e4n1n2
.tenne4
.ten1nes
.tenn2e2ss
.to9m8a9s8z8e8w9s8k8i.
.to2ma
.tomas2ze
.tomaszewsk2
.tomaszewsk1i
.ty9p8o9g8r8a8p8h8i8q8u8e.
.ty3po
.ty5po4g
.typo1gr
.typogr4aphi
.typographiqu2
.uk8r8a8i8n9i8a8n.
.4uk
.ukr2ai2
.ukra4i4n
.ukra2ini
.ukrai4n4i1a
.ukraini3a2n
.ve8r9a8l8l9g8e9m8e8i8n9e8r8t8e.
.veral1l
.veral4l1g4
.verallge1me
.verallgemei2
.verallgeme2ine
.verallgemein1er
.ve8r9e8i8n9i9g8u8n8g.
.vere3in
.verei2
.vere2ini
.verein2ig
.vereini3gun
.ve8r9t8e8i9l8u8n9g8e8n.
.vertei2
.verteilun1gen
.vi8i8i8t8h.
.v4i5i4
.v4i5i5i4
.vii2ith
.vi8i8t8h.
.vi2ith
.wa8h8r9s8c8h8e8i8n9l8i8c8h9k8e8i8t8s9t8h8e8o9r8i8e.
.wa4hr4
.wah4rs2
.wahrs4c
.wahrs2ch2
.wahrsche2
.wahrschei2
.wahrsche4i4n1l
.wahrs2cheinl4i2ch
.wahrscheinlic4hk
.wahrschei2nlichkei2
.wahrscheinlichkei4t1s2
.wahrscheinlichkeits3th2e
.wahrscheinlichkeitsthe1o5r
.wahrscheinlichkeitstheor2ie4
.we8r9n8e8r.
.w1er
.wer4n1er
.we8r9t8h8e8r9i8a8n.
.werth2e
.werther1i
.werthe1r2i3a4
.wertheri2a2n
.wi8n9c8h8e8s9t8e8r.
.win2ch
.winche2
.winch1es
.wi8r8t9s8c8h8a8f8t.
.w4ir4
.wir4t1s2
.wirt4sc
.wirts2ch2
.wirtscha2f
.wirtscha2ft
.wi8s9s8e8n9s8c8h8a8f8t9l8i8c8h.
.w4i2s1s
.wissen4
.wisse2n1s2
.wissens4c
.wissens2ch2
.wissenscha2f
.wissenscha2ft
.wissenschaf2tl
.wissens2chaftl4i2ch
.xv8i8i8i8t8h.
.xv4i5i4
.xv4i5i5i4
.xvii2ith
.xv8i8i8t8h.
.xvi2ith
.xx8i8i8i8r8d.
.xx4
.xx3i
.xx4i5i4
.xx4i5i5i4
.xxii4ir
.xx8i8i8n8d.
.xxi4ind
.yi8n8g9y8o8n8g.
.y1i
.yin2gy
.yingy1o4
.yingyo2n
.sh8u9x8u8e.
.shux1u3
.ji9s8u8a8n.
.ji2su
.jisua2n
.ze8a9l8a8n8d.
.2ze
.zea4l
.zea3l4and
.zeala2n
.ze8i8t9s8c8h8r8i8f8t.
.zei2
.zei4t1s2
.zeit4sc
.zeits2ch2
.zeitsc4hr4
.zeitschr4i2ft
.affin9i1ty
.affin2it
.affin9ity's
.daffi9est
.daffie
.de9fi9ance
.defi1a
.defi2a2n
.defianc
.de9fi9ance's
.de9fi9an4t
.de9fi9ant1ly
.defian2tl
.defic8i4t1s2
.de4fic
.d5efi1ci
.defi2c1it
.de9fil9ing
.defil4
.defil1i
.fi9ancé
.fi1a
.fi2a2n
.fianc
.fi9ancé's
.fi9ancée
.fi9ancées
.fi9ancés
.fil9i9buster
.fil1i
.fil2ib
.fili5bust
.filib2us
.fil9i9buster's
.fil9i9bus9te2r2ed
.filibus5tere
.fil9i9bus9ter9in4g
.filibus1ter1i
.fil9i9buste4r1s2
.fil9i9gree
.fil2ig
.fili5gre
.fili1gr
.fil9i9gree's
.fil9i9greed
.fil9i9gree9ing
.filigreei2
.fil9i9gree2s4
.fin8der
.find
.fin1de
.fin8der's
.find9e4r5s2
.fin8n1er
.fi4n1n2
.finne4
.fin8ni4er
.finn2ie4
.fin8ni9est
.fis8ticuff3s
.fis1t2i
.fis1tic
.fist4ic1u
.fistic4u4ff
.fluffi9est
.fluffie
.fly9lea2f
.fly
.flyle2a
.fly9leaf's
.fly9leav4e2s
.flylea2v
.fly9sheet
.flys4
.flys2h
.fly9speck1
.flyspe2c
.fly9speck's
.fly9speck2ed
.fly9speck9in4g
.flyspeck3i
.fly9spec4k1s2
.fly9swat9ter
.flysw2
.flyswat5te
.flyswa4t3t2
.fly9swat9te4r1s2
.huffi9est
.huffie
.iffi9est
.iffie
.puffi9est
.puffie
.re9fil9ing
.re3fi
.refil4
.refil1i
.scruffi9est
.scruffie
.spiffi9est
.spiffie
.stuffi9est
.stuffie
.viewfin8der
.v2ie4
.view1fi
.view2fin
.viewfind
.viewfin1de
.viewfin8der's
.viewfind9e4r5s2
.affin9ityffis
.de9fi9anceffis
.fi9ancéffis
.fil9i9busterffis
.fil9i9greeffis
.fin8derffis
.fly9leafffis
.fly9speckffis
.viewfin8derffis
PK
!<@.}}hyphenation/hyph_eo.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
a1
e1
i1
o1
u1
2aj.
2a2jn.
a4j1n
2an.
2as.
2oj.
2o2jn.
o4j1n
2on.
2os.
2us.
a3a1
e3a1
i3a1
o3a1
u3a1
a3e1
e3e1
i3e1
o3e1
u3e1
a3i1
e3i1
i3i1
o3i1
u3i1
a3o1
e3o1
i3o1
o3o1
u3o1
a3u1
e3u1
i3u1
o3u1
u3u1
e2sper
es1p
espe1
4j1n
2jn.
4l1p
2lp.
4l1t
2lt.
4n1k
2nk.
4n1s
2ns.
4n1t
2nt.
s1t
2st.
.bl4
.br4
.dr4
.d1l4
.fl4
.fr4
.gl4
.g1n4
.gr4
.kl4
.k1n4
.kr4
.k1v4
.pl4
.pr4
.p1s4
.p1t4
.s1f4
.s1k4
.skl4
.skr4
.sk1v4
.s1l4
.s1m4
.s1n4
.s1p4
.spl4
.spr4
.s1t4
.str4
.s1v4
.ŝl4
.ŝ1m4
.ŝ1n4
.ŝ1p4
.ŝr4
.ŝpr4
.ŝ1t4
.ŝtr4
.ŝ1v4
.tr4
.vr4
1a2ĉa.
a1ĉa1
1a2ĉ2aj.
1a2ĉ2a2jn.
aĉa4j1n
1a2ĉ2an.
1a2ĉe.
aĉe1
1a2ĉo.
aĉo1
1a2ĉ2oj.
1a2ĉ2o2jn.
aĉo4j1n
1a2ĉ2on.
1a2da.
a1da1
1a2d2aj.
1a2d2a2jn.
ada4j1n
1a2d2an.
1a2de.
ade1
1a2do.
ado1
1a2d2oj.
1a2d2o2jn.
ado4j1n
1a2d2on.
1a2d2as.
1a2di.
adi1
1a2dis.
1a2d2os.
1a2du.
adu1
1a2d2us.
.avok2a3d
.a1
.avo1
.avoka1
.bal2a3d
.ba1
.bala1
ĉokol2a3d
ĉo1
ĉoko1
ĉokola1
.fas2a3d
.fa1
.fasa1
.inv2a3d
.i1
.i4n1v2
.inva1
kamar2a3d
ka1
kam2a3r
kama1
kamara1
.ka3n
.kan2a3d
.ka1
.kana1
limon2a3d
li1
limo1
limona1
persv2a3d
pe1
pe4r1s
pers1v
persva1
.pom2a3d
.po1
.poma1
seren2a3d
se1
sere1
serena1
1a2ĵa.
a1ĵa1
1a2ĵ2aj.
1a2ĵ2a2jn.
aĵa4j1n
1a2ĵ2an.
1a2ĵe.
aĵe1
1a2ĵo.
aĵo1
1a2ĵ2oj.
1a2ĵ2o2jn.
aĵo4j1n
1a2ĵ2on.
.a4l2t
al3tabl
a4l1t
a1lta1
al3t2a3r
al3ter
alte1
al3tru3i1
altru1
1a2na.
a1na1
1a2n2aj.
1a2n2a2jn.
ana4j1n
1a2n2an.
1a2ne.
ane1
1a2no.
ano1
1a2n2oj.
1a2n2o2jn.
ano4j1n
1a2n2on.
1a2n2as.
1a2ni.
ani1
1a2nis.
1a2n2os.
1a2nu.
anu1
1a2n2us.
1a2ni4s1m
1a2n1i2n
afg2a3n
a4f1g
afga1
akomp2a3n
ako1
ako4m1p
akompa1
arg2a3n
a4r1g
arga1
ark2a3n
a4r1k
arka1
ban2a3n
ba1
bana1
but2a3n
bu1
buta1
cig2a3n
ci1
ciga1
ĉambel2a3n
ĉa1
ĉa4m1b
ĉambe1
ĉambela1
ĉamp2a3n
ĉa4m1p
ĉampa1
ĉarlat2a3n
ĉa4r1l
ĉarla1
ĉarlata1
ĉik2a3n
ĉi1
ĉika1
dek2a3n
de1
deka1
dog2a3n
do1
doga1
el2a3n
ela1
faz2a3n
fa1
faza1
font2a3n
fo1
fo4n1t
fonta1
galv2a3n
ga1
ga4l1v
galva1
germ2a3n
ge1
ge4r1m
germa1
ĝentlem2a3n
ĝe1
ĝe4n1t
ĝentle1
ĝentlema1
hisp2a3n
hi1
his1p
hispa1
.ĥa3n
.ĥa1
inf2a3n
i4n1f
infa1
ir2a3n
ira1
jap2a3n
ja1
japa1
kalk2a3n
ka4l1k
kalka1
kank2a3n
ka4n1k
kanka1
kapit2a3n
kapi1
kapita1
kard2a3n
ka4r1d
karda1
karav2a3n
kara1
karava1
kor2a3n
ko1
kora1
.ma3n
.ma1
marcip2a3n
ma1
ma4r1c
marci1
marcipa1
membr2a3n
me1
me4m1b
membra1
met2a3n
meta1
nirv2a3n
ni1
ni4r1v
nirva1
.on2a3n
.o1
.ona1
org2a3n
o4r1g
orga1
.pa3n
.pa1
partiz2a3n
pa1
pa4r1t
parti1
partiza1
pelik2a3n
peli1
pelika1
porcel2a3n
po1
po4r1c
porce1
porcela1
prof2a3n
pro1
profa1
.ra3n
.ra1
rum2a3n
ru1
ruma1
.sa3n
.sa1
4s1l
3s2log2a3n
slo1
sloga1
sopr2a3n
so1
sopra1
stef2a3n
ste1
stefa1
sufrag2a3n
su1
sufra1
sufraga1
sult2a3n
su4l1t
sulta1
ŝam2a3n
ŝa1
ŝama1
teher2a3n
te1
tehe1
tehera1
tir2a3n
ti1
tira1
turb2a3n
tu1
tu4r1b
turba1
.va3n
.va1
vatik2a3n
va1
vati1
vatika1
veter2a3n
ve1
vete1
vetera1
vulk2a3n
vu1
vu4l1k
vulka1
.k1a3n2i3n
.kani1
safr2a3n2i3n
sa1
safra1
safrani1
.t1a3n2i3n
.ta1
.tani1
1an2ta.
a4n1t
a1nta1
1an2t2aj.
1an2t2a2jn.
anta4j1n
1an2t2an.
1an2te.
ante1
1an2to.
anto1
1an2t2oj.
1an2t2o2jn.
anto4j1n
1an2t2on.
adjut2a4n3t
a4d1j2
adju1
adjuta1
.arog2a4n3t
.aro1
.aroga1
.atl2a4n3t
.atla1
.ba4n3t
.brok2a4n3t
.bro1
.broka1
.diam2a4n3t
.di1
.di3a1
.diama1
dilet2a4n3t
di1
dile1
dileta1
disk2a4n3t
dis1k
diska1
elef2a4n3t
e1le1
elefa1
.gal2a4n3t
.ga1
.gala1
.g1i2g2a4n3t
.gi1
.giga1
inf2a4n3t
inst2a4n3t
i4n1s
in1s2t
insta1
konson2a4n3t
ko4n1s
konso1
konsona1
.ka4n3t
konst2a4n3t
kon1s2t
konsta1
k1v
kvadr2a4n3t
kva1
kvadra1
.kv2a4n3t
.kva1
leŭten2a4n3t
le1
le4ŭ1
leŭte1
leŭtena1
mom2a4n3t
mo1
moma1
.okt2a4n3t
.o4k1t
.okta1
.ped2a4n3t
.pe1
.peda1
.pla4n3t
.pla1
tal2a4n3t
ta1
ta1la1
batal3a4n4t
bata1
batala1
1an2t3ar
an3t2ar4k1t
anta4r1k
kan3t2arel
k1an2t3ar
ka4n1t
ka1nta1
kantare1
1an2t1ig
anti1
1an2t1ec
.apu2d1
.apu1
1a2ra.
a1ra1
1a2r2aj.
1a2r2a2jn.
ara4j1n
1a2r2an.
1a2re.
are1
1a2ro.
aro1
1a2r2oj.
1a2r2o2jn.
aro4j1n
1a2r2on.
1a2rig
ari1
1a2riĝ
am2a3r
ama1
barb2a3r
ba4r1b
barba1
baz2a3r
baza1
biz2a3r
bi1
biza1
boj2a3r
bo1
boja1
bulg2a3r
bu4l1g
bulga1
.c1i3d2a3r
.ci1
.cida1
cig2a3r
din2a3r
dina1
dol2a3r
dola1
er2a3r
era1
fanf2a3r
fa4n1f
fanfa1
.f2a3r
form2u3l
form1u2l2a3r
fo4r1m
formu1
formula1
garg2a3r
ga4r1g
garga1
git2a3r
gi1
gita1
.ha3r2
.ha1
hung2a3r
hu1
hu4n1g
hunga1
invent2a3r
i4n1v2
inve1
inve4n1t
inventa1
izob2a3r
izo1
izoba1
jug2u3l2a3r
ju1
jugu1
jugula1
kalend2a3r
kale1
kale4n1d
kalenda1
kap2i3l2a3r
kapila1
kla3r
kla1
komp2a3r
ko4m1p
kompa1
kulin2a3r
ku1
kuli1
kulina1
kvatern2a3r
kvate1
kvate4r1n
kvaterna1
lap2i3d2a3r
la1
lapi1
lapida1
.mol2a3r
.mo1
.mola1
najb4a1r
na1
na4j1b
najba1
om2a3r
oma1
ordin2a3r
o4r1d
ordi1
ordina1
partik2u3l
partik2u3l2a3r
partiku1
partikula1
.pol2a3r
.pola1
pop2u3l2a3r
popu1
popula1
prep2a3r
pre1
prepa1
prim2a3r
pri1
prima1
rad2a3r
ra1
rada1
remp2a3r
re1
re4m1p
rempa1
rip2a3r
ri1
ripa1
sa3m2ov2a3r
samo1
samova1
sanit2a3r
sani1
sanita1
sek2u3l2ar
seku1
sekula1
sekund2a3r
seku4n1d
sekunda1
sing2u3l2ar
si1
si4n1g
singu1
singula1
s1k
skal2a3r
ska1
skala1
.st2a3r
.sta1
sol2i3d2a3r
soli1
solida1
tal2a3r
tat2a3r
tata1
.vel2a3r
.ve1
.vela1
vulg2a3r
vu4l1g
vulga1
1a2ta.
a1ta1
1a2t2aj.
1a2t2a2jn.
ata4j1n
1a2t2an.
1a2te.
ate1
1a2to.
ato1
1a2t2oj.
1a2t2o2jn.
ato4j1n
1a2t2on.
1a2t1ec
adiab2a3t
a1di3a1
adiaba1
agreg2a3t
agre1
agrega1
aŭtom2a3t
a4ŭ1
aŭto1
aŭtoma1
aŭtorit2a3t
aŭtori1
aŭtorita1
.ab2a3t
.aba1
.adekv2a3t
.ade1
.adek1v
.adekva1
advok2a3t
a4d1v
advo1
advoka1
afrik2a3t
afri1
afrika1
.ag2a3t
.aga1
1a2kr
akrob2a3t
akro1
akroba1
.akur2a3t
.aku1
.akura1
alop2a3t
alo1
alopa1
.apar2a3t
.apa1
.apara1
apost2a3t
apo1
apos1t
aposta1
arom2a3t
aroma1
.ba3t
.bl2a3t
.bla1
.brok2a3t
.deb2a3t
.de1
.deba1
delik2a3t
deli1
delika1
diplom2a3t
diplo1
diploma1
.duk2a3t
.du1
.duka1
.et2a3t
.e1
.eta1
fakult2a3t
faku1
faku4l1t
fakulta1
.fr2a3t
.fra1
.gefr2a3t
.ge1
.gefra1
.gla3t
.gla1
.gran2a3t
.gra1
.grana1
.hep2a3t
.he1
.hepa1
kandid2a3t
ka4n1d
kandi1
kandida1
.kar2a3t
.kara1
.kazem2a3t
.kaze1
.kazema1
.klim2a3t
.kli1
.klima1
.kom2a3t
.ko1
.koma1
.komit2a3t
.komi1
.komita1
.komp2a3t
.ko4m1p
.kompa1
konkord2a3t
ko4n1k
konko1
konko4r1d
konkorda1
.konst2a3t
.ko4n1s
.kon1s2t
.konsta1
.krav2a3t
.kra1
.krava1
kro3m2a3t
kro1
kroma1
.kvadr2a3t
.kvadra1
magistr2a3t
magi1
magis1t
magistra1
.magn2a3t
.mag1n
.magna1
majorit2a3t
majo1
majori1
majorita1
malgl2a3t
ma4l1g
malgla1
.ma2l1
.mals2a3t
.ma4l1s2
.malsa1
.mand2a3t
.ma4n1d
.manda1
.mecen2a3t
.me1
.mece1
.mecena1
minorit2a3t
mi1
mino1
minori1
minorita1
.mon2a3t
.mona1
.mu3l
.mul2a3t
.mu1
.mula1
.musk2a3t
.mus1k
.muska1
ordin2a3t
.pal2a3t
.pala1
.pir2a3t
.pi1
.pira1
.prel2a3t
.pre1
.prela1
.prim2a3t
.pri1
.prima1
priorit2a3t
pri3o1
priori1
priorita1
.priv2a3t
.priva1
.rab2a3t
.raba1
.ren2a3t
.re1
.rena1
.ril2a3t
.ri1
.rila1
.sab2a3t
.saba1
.sal2a3t
.sala1
.se2n1
.sen2a3t
.se1
.sena1
sindik2a3t
si4n1d
sindi1
sindika1
.skarl2a3t
.ska1
.ska4r1l
.skarla1
.sold2a3t
.so1
.so4l1d
.solda1
.son2a3t
.sona1
.sp2a3t
.spa1
stigm2a3t
sti1
stig1m
stigma1
substr2a3t
su4b1s
subs1t
substra1
surog2a3t
suro1
suroga1
.tok2a3t
.to1
.toka1
.tom2a3t
.toma1
.ultim2a3t
.u1
.u4l1t
.ulti1
.ultima1
universit2a3t
uni1
unive1
unive4r1s
universi1
universita1
.vulg2a3t
.vu1
.vu4l1g
.vulga1
.ĉe2f1
.ĉe1
.ĉi4a1
.ĉi1
.ĉia4m1
.ĉi4e1
.ĉie4s
.ĉi4o1
.ĉio4m
.ĉi4u1
ĉi3el1
ĉi3e1
4ĉ1j2
3ĉ2jo1
.di2s1
.di3s2e4r1t
.dise1
.di3s2e4r1v
.di3s2ip
.disi1
.di3s2oci1
.diso1
.di3s2a.
.disa1
.di3s2aj.
.di3s2a2jn.
.disa4j1n
.di3s2an.
.di3s2e.
.di3s2en.
.du2m1
dump2i4n3g
du1
du4m1p
dumpi1
.du2on1
.du3o1
1e2bla.
ebla1
1e2bl2aj.
1e2bl2a2jn.
ebla4j1n
1e2bl2an.
1e2ble.
e1ble1
1e2blo.
eblo1
1e2bl2oj.
1e2bl2o2jn.
eblo4j1n
1e2bl2on.
1e2bl2as.
1e2bli.
ebli1
1e2blis.
1e2bl2os.
1e2blu.
eblu1
1e2bl2us.
1e2bl1ec
1e2bl1aĵ
.fe3b1l
.fe1
.me3b1l
1e2ca.
eca1
1e2c2aj.
1e2c2a2jn.
eca4j1n
1e2c2an.
1e2ce.
ece1
1e2co.
eco1
1e2c2oj.
1e2c2o2jn.
eco4j1n
1e2c2on.
.apr2e3c
.apre1
.de3c
.pe3c
s1p
sp2e3c
spe1
1e2ga.
ega1
1e2g2aj.
1e2g2a2jn.
ega4j1n
1e2g2an.
1e2ge.
ege1
1e2go.
ego1
1e2g2oj.
1e2g2o2jn.
ego4j1n
1e2g2on.
1e2g2as.
1e2gi.
egi1
1e2gis.
1e2g2os.
1e2gu.
egu1
1e2g2us.
.del2e3g
.dele1
.fl2e3g
.fle1
kol2e3g
kole1
.om2e3g
.ome1
.prel2e3g
.prele1
.strat2e3g
.stra1
.strate1
.norv2e3g
.no1
.no4r1v
.norve1
1e2ja.
eja1
1e2j2aj.
1e2j2a2jn.
eja4j1n
1e2j2an.
1e2je.
eje1
1e2jo.
ejo1
1e2j2oj.
1e2j2o2jn.
ejo4j1n
1e2j2on.
1e2j1ig
eji1
1e2j1iĝ
.be3j
.be1
.pl2ej
.ple1
malpl2ej
ma4l1p
malple1
.e4k2s1
.ek3s2ci1
.eks1c
ek4s3cit
e4k1s
eks1c
eksci1
ek4s3ciz
.ek3s2id
.eksi1
.ek1i2r
.eki1
.ek1rid
.ekri1
1e2kzem
e4k1z
ekze1
.ekste2r1
.eks1t
.e1kste1
.ekste3ra.
.ekstera1
.ekste3r2aj.
.ekste3r2a2jn.
.ekstera4j1n
.ekste3r2an.
.ekste3re.
.ekstere1
.ekste3ro.
.ekstero1
.ekste3r2oj.
.ekste3r2o2jn.
.ekstero4j1n
.ekste3r2on.
.ekste3r2as.
.ekste3ri.
.eksteri1
.ekste3ris.
.ekste3r2os.
.ekste3ru.
.eksteru1
.ekste3r2us.
.el1i2r
.eli1
.el1a2ĉ2e3t
.ela1
.elaĉe1
.el1uz
.elu1
1e2ma.
ema1
1e2m2aj.
1e2m2a2jn.
ema4j1n
1e2m2an.
1e2me.
e1me1
1e2mo.
emo1
1e2m2oj.
1e2m2o2jn.
emo4j1n
1e2m2on.
1e2m2as.
1e2mi.
emi1
1e2mis.
1e2m2os.
1e2mu.
emu1
1e2m2us.
1e2m1ec
.alpr2e3m
.a4l1p
.alpre1
.anat2e3m
.ana1
.anate1
blasf2e3m
bla1
blas1f
blasfe1
.boh2e3m
.bo1
.bohe1
diad2e3m
di3a1
diade1
.dil2e3m
.dile1
.ed2e3m
.ede1
ekstr2e3m
ek1s2t2r
eks1t
ekstre1
.ektr2e3m
.e4k1t
.ektre1
.ekpr2e3m
.e4k1p
.ekpre1
embl2e3m
e4m1b
emble1
emfiz2e3m
e4m1f
emfi1
emfize1
.e2kz2e3m
.e4k1z
.ekze1
.har2e3m
.hare1
jerusal2e3m
je1
jeru1
jerusa1
jerusale1
krizant2e3m
kri1
kriza1
kriza4n1t
krizante1
.le3m
.le1
.po2e3m
.po3e1
.pr2e3m
.probl2e3m
.pro1
.proble1
.sk2e3m
.ske1
.te3m
.te1
.tand2e3m
.ta4n1d
.tande1
.teor2e3m
.te3o1
.teore1
.tot2e3m
.tote1
.tr2e3m
.tre1
sist2e3m
sis1t
siste1
.en1ir2
.eni1
1en2da.
e4n1d
enda1
1en2d2aj.
1en2d2a2jn.
enda4j1n
1en2d2an.
1en2de.
ende1
1en2d2as.
1en2di.
endi1
1en2dis.
1en2d2os.
endo1
1en2du.
endu1
1en2d2us.
1en2do.
1en2d2on.
.am2e4n3d
.ame1
.at2e4n3d
.ate1
.def2e4n3d
.defe1
.et2e4n3d
.ete1
.kal2e4n3d
.kale1
kresĉ2e4n3d
kre1
kre4s1ĉ
kresĉe1
.me4n3d
.of2e4n3d
.ofe1
.pe4n3d
.pl2e4n3d
pret2e4n3d
pre1te1
rekom2e4n3d
reko1
rekome1
.se4n3d
s1c
sc2e4n3d
sce1
.te4n3d
.ve4n3d
1e2ta.
eta1
1e2t2aj.
1e2t2a2jn.
eta4j1n
1e2t2an.
1e2te.
ete1
1e2to.
eto1
1e2t2oj.
1e2t2o2jn.
eto4j1n
1e2t2on.
1e2ta2as.
eta3a1
1e2tai.
eta3i1
1e2tais.
1e2ta2os.
eta3o1
1e2tau.
eta3u1
1e2ta2us.
a2ĉ2e3t
alfab2e3t
a4l1f
alfa1
alfabe1
alum2e3t
alu1
alume1
amul2e3t
amu1
amule1
.ask2e3t
.as1k
.aske1
.atl2e3t
.atle1
.be3t
bajon2e3t
bajo1
bajone1
bil2e3t
bile1
.bol2e3t
.bole1
bracel2e3t
bra1
brace1
bracele1
brev2e3t
bre1
breve1
.buĝ2e3t
.bu1
.buĝe1
.deb2e3t
.debe1
.dem2e3t
.deme1
diab2e3t
diabe1
elizab2e3t
eli1
eliza1
elizabe1
.enk2e3t
.e4n1k
.enke1
epit2e3t
epi1
epite1
.fo2r1
.form2e3t
.fo1
.fo4r1m
.forme1
.gaz2e3t
.gaze1
.herm2e3t
.he4r1m
.herme1
.ĥe3t
.ĥe1
.ĵak2e3t
.ĵa1
.ĵake1
ĵ2e3t
ĵe1
kabin2e3t
kabi1
kabine1
.kad2e3t
.kade1
kastanj2e3t
kas1t
kasta1
kasta4n1j
kastanje1
.kin2e3t
.ki1
.kine1
klarn2e3t
kla4r1n
klarne1
kloz2e3t
klo1
kloze1
.kom2e3t
.kome1
kompl2e3t
komple1
.konf2e3t
.ko4n1f
.konfe1
kors2e3t
ko4r1s
korse1
korv2e3t
ko4r1v
korve1
kotl2e3t
kotle1
.krik2e3t
.kri1
.krike1
.krok2e3t
.kro1
.kroke1
kvodlib2e3t
kvo1
kvo4d1l
kvodli1
kvodlibe1
.me3t
magn2e3t
mag1n
magne1
marion2e3t
mari1
mari3o1
marione1
.mo3t
.mot2e3t
.mote1
.ne3t
.ne1
.oml2e3t
.o4m1l
.omle1
pamfl2e3t
pa4m1f
pamfle1
parg2e3t
pa4r1g
parge1
.pl2e3t
.po2e3t
.prof2e3t
.profe1
.prol2e3t
.prole1
.rak2e3t
.rake1
.rem2e3t
.reme1
.rip2e3t
.ripe1
sekr2e3t
sekre1
skel2e3t
ske1
skele1
.son2e3t
.sone1
.sov2e3t
.sove1
spag2e3t
spa1
spage1
spin2e3t
spi1
spine1
staf2e3t
sta1
stafe1
.st2i3l
.stil2e3t
.sti1
.stile1
ŝibol2e3t
ŝi1
ŝibo1
ŝibole1
.tib2e3t
.ti1
.tibe1
tual2e3t
tu3a1
tuale1
.ve3t
.veg2e3t
.vege1
.vend2e3t
.vende1
vinj2e3t
vi1
vi4n1j
vinje1
1es2tr
es1t
.fo3r2i4n3t
.fori1
.fo3r2u3m
.foru1
.fo3r2a.
.fora1
.fo3r2aj.
.fo3r2a2jn.
.fora4j1n
.fo3r2an.
.fo3r2e.
.fore1
.fo3r2o.
.fo1ro1
.fo3r2oj.
.fo3r2o2jn.
.foro4j1n
.fo3r2on.
.fo3r2en
.fo3r2as.
.fo3r2i.
.fo3r2is.
.fo3r2os.
.fo3r2u.
.fo3r2us.
.ĝi2s1
.ĝi1
1i2da.
ida1
1i2d2aj.
1i2d2a2jn.
ida4j1n
1i2d2an.
1i2de.
ide1
1i2do.
ido1
1i2d2oj.
1i2d2o2jn.
ido4j1n
1i2d2on.
.abs2i3d
.a4b1s
.absi1
.ac2i3d
.aci1
.akr2i3d
.a2kr
.akri1
.aps2i3d
.ap1s
.apsi1
.ar2i3d
.ari1
.askar2i3d
.aska1
.askari1
.asp2i3d
.as1p
.aspi1
.av2i3d
.avi1
.bi3d
.bi1
.bol2i3d
.boli1
.br2i3d
.bri1
cirkumc2i3d
ci4r1k
cirku1
cirku4m1c
cirkumci1
.dav2i3d
.da1
.davi1
.dec2i3d
.deci1
.eŭkl2i3d
.e4ŭ1
.eŭkli1
.eg2i3d
.egi1
.fi3d
.fi1
.flor2i3d
.flo1
.flori1
.fr2i3d
.fri1
frig2i3d
fri1
frigi1
genoc2i3d
geno1
genoci1
.gv2i3d
.g1v
.gvi1
hibr2i3d
hibri1
hum2i3d
humi1
ĥlam2i3d
ĥla1
ĥlami1
.inc2i3d
.i4n1c
.i1nci1
.ins2i3d
.i4n1s
.insi1
inval2i3d
inva1
invali1
.ji3d
.ji1
kan3t2ar2i3d
kantari1
koinc2i3d
ko3i1
koi4n1c
koinci1
.konf2i3d
.konfi1
krizal2i3d
krizali1
.li3d
.li1
.lib2i3d
.libi1
likv2i3d
lik1v
likvi1
.liv2i3d
.livi1
.luc2i3d
.lu1
.luci1
.madr2i3d
.madri1
morb2i3d
mo4r1b
morbi1
.muc2i3d
.muci1
perf2i3d
pe4r1f
perfi1
piram2i3d
pi1
pira1
pirami1
.prez2i3d
.prezi1
.ri3d
rap2i3d
rapi1
rez2i3d
rezi1
rig2i3d
rigi1
.si3d
.si1
.sol2i3d
.soli1
.str2i3d
.stri1
telev2i3d
tele1
televi1
.tim2i3d
.timi1
.vi3d
.vi1
.val2i3d
.vali1
1i2d1ar
1i2d1i2n
idi1
.pir2i3d2i2n
.pi1ri1
.piridi1
1i2ga.
iga1
1i2g2aj.
1i2g2a2jn.
iga4j1n
1i2g2an.
1i2ge.
ige1
1i2go.
igo1
1i2g2oj.
1i2g2o2jn.
igo4j1n
1i2g2on.
1i2g2as.
1i2gi.
i1gi1
1i2gis.
1i2g2os.
1i2gu.
igu1
1i2g2us.
.br2i3g
.di3g
.fi3g
.in3d3i3g
.i4n1d
.i1ndi1
.inst2i3g
.in1s2t
.insti1
.intr2i3g
.i4n1t
.intri1
.kvadr2i3g
.kvadri1
.li3g
p1f
pfen2i3g
pfe1
pfeni1
.pi3g
.prod2i3g
.prodi1
.ri3g
.rodr2i3g
.ro1
.rodri1
.str2i3g
.ti3g
.nav2i3g
.na1
.navi1
1i2gebl
1i2ga4n1t
1i2gi4n1t
1i2go4n1t
1i2gat
al2i3g2a3tor
ali1
al1i2gat
aliga1
aligato1
l2i3g2a3turo1
l1i2gat
liga1
ligatu1
1i2git
1i2got
1i2g1a2d
br2i3g2a3d
bri1
briga1
1i2g1e2m
1i2g1i2l
1i2ĝa.
iĝa1
1i2ĝ2aj.
1i2ĝ2a2jn.
iĝa4j1n
1i2ĝ2an.
1i2ĝe.
iĝe1
1i2ĝo.
iĝo1
1i2ĝ2oj.
1i2ĝ2o2jn.
iĝo4j1n
1i2ĝ2on.
1i2ĝ2as.
1i2ĝi.
i1ĝi1
1i2ĝis.
1i2ĝ2os.
1i2ĝu.
iĝu1
1i2ĝ2us.
1i2ĝa4n1t
1i2ĝi4n1t
1i2ĝo4n1t
.br2i3ĝ
.negl2i3ĝ
.negli1
.prest2i3ĝ
.pres1t
.presti1
.vert2i3ĝ
.ve4r1t
.verti1
.vest2i3ĝ
.ves1t
.vesti1
1i2ĝ1a2d
1i2ĝ1e2m
1i2la.
ila1
1i2l2aj.
1i2l2a2jn.
ila4j1n
1i2l2an.
1i2le.
ile1
1i2lo.
ilo1
1i2l2oj.
1i2l2o2jn.
ilo4j1n
1i2l2on.
abut2i3l
abu1
abuti1
acet2i3l
ace1
aceti1
.ang2i3l
.a4n1g
.angi1
.apr2i3l
.apri1
.arg2i3l
.a4r1g
.argi1
.az2i3l
.azi1
.bab2i3l
.babi1
.bac2i3l
.baci1
.baz2i3l
.bazi1
.ber2i3l
.beri1
.bi3l
.br2i3l
.braz2i3l
.bra1
.brazi1
.ced2i3l
.ce1
.cedi1
.civ2i3l
.ci1vi1
.ĉi3l
ĉinĉ2i3l
ĉi4n1ĉ
ĉinĉi1
.dakt2i3l
.da4k1t
.dakti1
.deb2i3l
.debi1
.def2i3l
.defi1
.di3s3t
.dist2i3l
.di1sti1
.domic2i3l
.do1
.domi1
.domici1
.dr2i3l
.dri1
.ed2i3l
.edi1
.ekz2i3l
.ekzi1
.eps2i3l
.ep1s
.epsi1
.et2i3l
.eti1
.fi3l
.fac2i3l
.faci1
.fus2i3l
.fu1
.fusi1
.gor2i3l
.go1
.gori1
.gr2i3l
.gri1
.ĝent2i3l
.ĝe1
.ĝe4n1t
.ĝenti1
.hu3m
.hum2i3l
.hu1
.humi1
.jub2i3l
.ju1
.jubi1
.ki3l
.kamar2i3l
.kam2a3r
.kama1
.kamari1
kamom2i3l
kamo1
kamomi1
.komp2i3l
.kompi1
.kons2i3l
.konsi1
krokod2i3l
kroko1
krokodi1
.mi3l
.mi1
.mant2i3l
.ma4n1t
.manti1
.met2i3l
.meti1
mob2i3l
mobi1
.mut2i3l
.muti1
naŭt2i3l
na4ŭ1
naŭti1
of2i3l
ofi1
.osc2i3l
.os1c
.osci1
paskv2i3l
pas1k
pask1v
paskvi1
.ps2i3l
.psi1
p1t
pterodakt2i3l
pte1
ptero1
pteroda1
pteroda4k1t
pterodakti1
.pup2i3l
.pu1
.pupi1
sen2il
seni1
sim2i3l
simi1
.stab2i3l
.stabi1
stenc2i3l
ste4n1c
stenci1
strob2i3l
stro1
strobi1
subt2i3l
su4b1t
subti1
s1v
svah2i3l
sva1
svahi1
trankv2i3l
tra1
tra4n1k
tran2k1v
trankvi1
.ut2i3l
.uti1
.vi3l
.van2i3l
.vani1
.v1i2g2i3l
.vigi1
vodev2i3l
vo1
vode1
vodevi1
volat2i3l
vola1
volati1
1i2l1ar
b2i3l2a4r1d
b1i2l1ar
bila1
dakt2i3l2a4r1b
da1
da4k1t
dakti1
dakt1i2l1ar
daktila1
pl2i3l2a4r1ĝ
pli1
pl1i2l1ar
plila1
f2i3l2a3ri1
fi1
f1i2l1ar
fila1
fri1t2i3l2a3ri1
friti1
frit1i2l1ar
fritila1
f2i3l2a4r1b
1e2str1i2na.
estri1
estrina1
1e2str1i2n2aj.
1e2str1i2n2a2jn.
estrina4j1n
1e2str1i2n2an.
1e2str1i2ne.
estrine1
1e2str1i2no.
estrino1
1e2str1i2n2oj.
1e2str1i2n2o2jn.
estrino4j1n
1e2str1i2n2on.
1i2s2t1i2na.
is1t
i1sti1
istina1
1i2s2t1i2n2aj.
1i2s2t1i2n2a2jn.
istina4j1n
1i2s2t1i2n2an.
1i2s2t1i2ne.
istine1
1i2s2t1i2no.
istino1
1i2s2t1i2n2oj.
1i2s2t1i2n2o2jn.
istino4j1n
1i2s2t1i2n2on.
d2is3t2ingo1
dis1t
disti1
disti4n1g
1u2l1i2na.
uli1
ulina1
1u2l1i2n2aj.
1u2l1i2n2a2jn.
ulina4j1n
1u2l1i2n2an.
1u2l1i2ne.
uline1
1u2l1i2no.
ulino1
1u2l1i2n2oj.
1u2l1i2n2o2jn.
ulino4j1n
1u2l1i2n2on.
ins2ul2i3n
insu1
insuli1
1an2t1i2na.
a1ntina1
1an2t1i2n2aj.
1an2t1i2n2a2jn.
antina4j1n
1an2t1i2n2an.
1an2t1i2ne.
antine1
1an2t1i2no.
antino1
1an2t1i2n2oj.
1an2t1i2n2o2jn.
antino4j1n
1an2t1i2n2on.
adam2a4n3t2i3n
adama1
adama4n1t
adamanti1
a4n3t2i3nom
brig2a4n3t2i3n
br1i2ga4n1t
briganti1
gal2a4n3t2i3n
gala1
gala4n1t
galanti1
.k2a4n3t2i3n
.kanti1
strof2a4n3t2i3n
strofa1
strofa4n1t
strofanti1
1in2da.
i4n1d
inda1
1in2d2aj.
1in2d2a2jn.
inda4j1n
1in2d2an.
1in2de.
inde1
1in2d2as.
1in2di.
i1ndi1
1in2dis.
1in2d2os.
indo1
1in2du.
indu1
1in2d2us.
1in2do.
1in2d2on.
1in2d3ec
1in2d3ig
1in2d3iĝ
.bi4n3d
.hi4n3d
.hi1
.li4n3d
rozal4i4n3d
ro1
roza1
rozali1
.bl4i4n3d
.bli1
.pi4n3d
tamar4i4n3d
tam2a3r
tama1
tamari1
.ŝi4n3d
.ŝi1
.vi4n3d
.in3d2ig1n
in3d2iĝen
indiĝe1
1in2ga.
i4n1g
inga1
1in2g2aj.
1in2g2a2jn.
inga4j1n
1in2g2an.
1in2ge.
inge1
1in2go.
ingo1
1in2g2oj.
1in2g2o2jn.
ingo4j1n
1in2g2on.
1in2gig
ingi1
.di4n3g
.pud2i4n3g
.pudi1
.vik2i4n3g
.viki1
4s1m
3s2mok2i4n3g
smo1
smoki1
ŝil2i4n3g
ŝili1
.kli4n3g
sterl2i4n3g
ste4r1l
sterli1
dom2i4n3g
domi1
men2i4n3g
meni1
salp2i4n3g
sa4l1p
salpi1
.ri4n3g
.far2i4n3g
.fari1
.har2i4n3g
.hari1
.lar2i4n3g
.la1
.lari1
.mer2i4n3g
.meri1
.fr2i4n3g
.sir2i4n3g
.siri1
.kri4n3g
str2i4n3g
stri1
vri4n3g
vri1
.at2i4n3g
.ati1
.mi3t
.mit2i4n3g
.miti1
.est2i4n3g
.es1t
.esti1
.dist2i4n3g
.svi4n3g
.svi1
1in2ta.
i4n1t
inta1
1in2t2aj.
1in2t2a2jn.
inta4j1n
1in2t2an.
1in2te.
inte1
1in2to.
into1
1in2t2oj.
1in2t2o2jn.
into4j1n
1in2t2on.
1in2t3ar
1in2t3ec
1in2t3us
intu1
.abs2i4n3t
.fi4n3t
hiac2i4n3t
hi3a1
hiaci1
.jac2i4n3t
.ja1
.jaci1
.kvi4n3t
.kvi1
labir2i4n3t
labi1
labiri1
.pi4n3t
.pl2i4n3t
.pli1
.ti4n3t
tereb2i4n3t
tere1
terebi1
.inte2r1
.inte1
.inte3r2es
.inte1re1
.inte3r2ez
.inte3r2up1t
.interu1
.inte3ra.
.intera1
.inte3r2aj.
.inte3r2a2jn.
.intera4j1n
.inte3r2an.
.inte3re.
.inte3ro.
.intero1
.inte3r2oj.
.inte3r2o2jn.
.intero4j1n
.inte3r2on.
.inte3r2as.
.inte3ri.
.i1nteri1
.inte3ris.
.inte3r2os.
.inte3ru.
.inte3r2us.
.inte3ren.
1is2ma.
i4s1m
isma1
1is2m2aj.
1is2m2a2jn.
isma4j1n
1is2m2an.
1is2me.
isme1
1is2mo.
ismo1
1is2m2oj.
1is2m2o2jn.
ismo4j1n
1is2m2on.
1i2s2m3ec
.pr2i4s3m
.ri4s3m
.sk2i4s3m
.ski1
.si4s3m
1i2s2ta.
ista1
1i2s2t2aj.
1i2s2t2a2jn.
ista4j1n
1i2s2t2an.
1i2s2te.
iste1
1i2s2to.
isto1
1i2s2t2oj.
1i2s2t2o2jn.
isto4j1n
1i2s2t2on.
1i2s2t3ec
1i2s2t3ar
.amet2i3s3t
.ameti1
antikr2i3s3t
antikri1
aor2i3s3t
aori1
.ar2i3s3t
.bal2i3s3t
.bali1
.bat2i3s3t
.bati1
.ci3s3t
ekz2i3s3t
ekzi1
.gen2i3s3t
.geni1
.gi3s3t
.hi3s3t
ins2i3s3t
insi1
kons2i3s3t
konsi1
.ki3s3t
.kr2i3s3t
.li3s3t
.pi3s3t
pers2i3s3t
persi1
.rez2i3s3t
.rezi1
.sk2i3s3t
.vi3s3t
1i2ta.
ita1
1i2t2aj.
1i2t2a2jn.
ita4j1n
1i2t2an.
1i2te.
ite1
1i2to.
ito1
1i2t2oj.
1i2t2o2jn.
ito4j1n
1i2t2on.
1i2t3ec
p2i3t4eci1
p1i2t3ec
pite1
.ag2i3t
.agi1
akred2i3t
akre1
akredi1
antrac2i3t
antra1
antraci1
.apet2i3t
.ape1
.apeti1
.bi3t
.band2i3t
.ba4n1d
.bandi1
biskv2i3t
bis1k
bisk1v
biskvi1
.br2i3t
.ci3t
.cenob2i3t
.ceno1
.cenobi1
.cirkv2i3t
.ci4r1k
.cirk1v
.cirkvi1
.civ2i3t
.deb2i3t
defic2i3t
defi1
defici1
.d1i3g2i3t
.digi1
dinam2i3t
dinami1
.efr2i3t
.efri1
.ek4s3c2i3t
eksplic2i3t
eks1p
ekspli1
eksplici1
.el2i3t
.emer2i3t
.eme1
.emeri1
.erm2i3t
.e4r1m
.ermi1
.erud2i3t
.eru1
.erudi1
.ev2i3t
.evi1
.fr2i3t
.gamb2i3t
.ga4m1b
.gambi1
.gl2i3t
.gli1
.graf2i3t
.grafi1
.gran2i3t
.grani1
.grav2i3t
.gravi1
hermafrod2i3t
he1
he4r1m
herma1
hermafro1
hermafrodi1
.hez2i3t
.hezi1
hipokr2i3t
hipo1
hipokri1
.im2i3t
.imi1
.inc2i3t
infin2i3t
infi1
infini1
inkogn2i3t
i4n1k
inko1
inkog1n
inkogni1
interm2i3t
inte4r1m
intermi1
.inv2i3t
.invi1
kapac2i3t
kapa1
kapaci1
komprom2i3t
kompro1
kompromi1
.konf2i3t
.kred2i3t
.kre1
.kredi1
.kv2i3t
.kval2i3t
.kvali1
.kviv2i3t
.kvivi1
.li3t
malak2i3t
mala1
malaki1
.margar2i3t
.ma4r1g
.marga1
.margari1
.marm2i3t
.ma4r1m
.marmi1
.med2i3t
.medi1
megal2i3t
m1e2gal
mega1
megali1
.mer2i3t
mil2i3t
mili1
asimil3i4t
asim2i3l
asi1
asimi1
asimili1
.mosk2i3t
.mos1k
.moski1
.neof2i3t
.ne3o1
.neofi1
okcip2i3t
o4k1c
okci1
okcipi1
ol2i3t
oli1
.orb2i3t
.o4r1b
.orbi1
palp2i3t
pa4l1p
palpi1
.paraz2i3t
.para1
.parazi1
.pir2i3t
plebisc2i3t
ple1
plebi1
plebis1c
plebisci1
precip2i3t
preci1
precipi1
prestid2i3g2i3t
pres1t
presti1
prestidi1
prestidigi1
preter2i3t
preteri1
prof2i3t
profi1
prozel2i3t
proze1
prozeli1
.ri3t
rehabil2i3t
reha1
rehabi1
rehabili1
.rekviz2i3t
.rek1v
.rekvi1
.rekvizi1
sanskr2i3t
sa4n1s
sans1k
sanskri1
.satel2i3t
.sate1
.sateli1
sibar2i3t
siba1
sibari1
.sk2i3t
.sp2i3t
.spi1
.spir2i3t
.spiri1
.spl2i3t
.spli1
.spr2i3t
.spri1
stalagm2i3t
sta1la1
stalag1m
stalagmi1
stalakt2i3t
stala4k1t
stalakti1
.stil2i3t
.stili1
.su2b1
.su3b2i3t
.su1
.subi1
.ŝv2i3t
.ŝvi1
.term2i3t
.te4r1m
.termi1
.tra4n2s
.tran3s2i3t
.tra1
.transi1
troglod2i3t
tro1
troglo1
troglodi1
.vi3t
.viz2i3t
.vizi1
.zen2i3t
.ze1
.zeni1
.ke4l2k
.ke1
.kro2m1
.kro3ma.
.kroma1
.kro3m2aj.
.kro3m2a2jn.
.kroma4j1n
.kro3m2an.
.kro3me.
.krome1
kro1m2o1
.ku2n1
.ku1
ku3n2ikl
kuni1
.li4a1
li5an
li3a1
li5as
ma3l2ic
mali1
.malno2v
.ma4l1n
.malno1
.me2m1
me3m2or
memo1
.mi4a1
mi5a4s1m
mi3a1
mi5a4ŭ1
.mi2s1
.mi3s2al
.misa1
mi3s2il
misi1
.mi3s2i3a1
.mi1si1
.mi3s2i3e1
.mi3s2i3i1
.mi3s2i3o1
.mi3s2i3u1
.me2z1
mez2alia4n1c
meza1
mezali5an
mezali1
meza1li3a1
me3z2embri1
me1ze1
meze4m1b
me3z2enter
meze4n1t
mezente1
.me3z2e1re1
.meze1
.mez2o1
me3z2ur
mezu1
.ni4a1
.ni1
4n1j
3nja.
nja1
3nj2aj.
3nj2a2jn.
nja4j1n
3nj2an.
3nje.
nje1
3njo.
njo1
3nj2oj.
3nj2o2jn.
njo4j1n
3nj2on.
.no2v
1o2bla.
obla1
1o2bl2aj.
1o2bl2a2jn.
obla4j1n
1o2bl2an.
1o2ble.
oble1
1o2blo.
o1blo1
1o2bl2oj.
1o2bl2o2jn.
oblo4j1n
1o2bl2on.
1o2bl1ec
gren2o3bl
gre1
greno1
malno3bl
ma4l1n
malno1
.no3bl
.vo3bl
.vo1
du3ona.
du3o1
duona1
du3on2aj.
du3on2a2jn.
duona4j1n
du3on2an.
du3one.
duone1
du3ono.
duo1no1
du3on2oj.
du3on2o2jn.
duono4j1n
du3on2on.
.trio2n
.tri1
.tri3o1
.kvar3o2n
.kvaro1
.kvin3o2n
.kvino1
.ses3o2n
.seso1
.sep3o2n
.sepo1
.ok3o2na.
.o1ko1
.okona1
.ok3o2n2aj.
.ok3o2n2a2jn.
.okona4j1n
.ok3o2n2an.
.ok3o2ne.
.okone1
.ok3o2no.
.o1ko1no1
.ok3o2n2oj.
.ok3o2n2o2jn.
.okono4j1n
.ok3o2n2on.
.naŭo4n
.na4ŭ1
.naŭo1
dek3o2na.
deko1
dekona1
dek3o2n2aj.
dek3o2n2a2jn.
dekona4j1n
dek3o2n2an.
dek3o2ne.
dekone1
dek3o2no.
deko1no1
dek3o2n2oj.
dek3o2n2o2jn.
dekono4j1n
dek3o2n2on.
cent3o2na.
ce1
ce4n1t
cento1
centona1
cent3o2n2aj.
cent3o2n2a2jn.
centona4j1n
cent3o2n2an.
cent3o2ne.
centone1
cent3o2no.
cento1no1
cent3o2n2oj.
cent3o2n2o2jn.
centono4j1n
cent3o2n2on.
mil3o2na.
milo1
milona1
mil3o2n2aj.
mil3o2n2a2jn.
milona4j1n
mil3o2n2an.
mil3o2ne.
milone1
mil3o2no.
milo1no1
mil3o2n2oj.
mil3o2n2o2jn.
milono4j1n
mil3o2n2on.
1on2ta.
o4n1t
onta1
1on2t2aj.
1on2t2a2jn.
onta4j1n
1on2t2an.
1on2te.
onte1
1on2to.
o1nto1
1on2t2oj.
1on2t2o2jn.
onto4j1n
1on2t2on.
disk2o4n3t
disko1
.fo4n3t
.fr2o4n3t
.fro1
.ho4n3t
.ho1
horiz2o4n3t
ho1
hori1
horizo1
.ko4n3t
konfr2o4n3t
ko4n1f
konfro1
.mo4n3t
mastod2o4n3t
mas1t
masto1
mastodo1
.melol2o4n3t
.melo1
.melolo1
.po4n3t
.rak2o4n3t
.rako1
.rem2o4n3t
.remo1
.renk2o4n3t
.re4n1k
.renko1
.sp2o4n3t
.spo1
vol2o4n3t
volo1
duo2pa.
duopa1
duo2p2aj.
duo2p2a2jn.
duopa4j1n
duo2p2an.
duo2pe.
duope1
duo2po.
duo1po1
duo2p2oj.
duo2p2o2jn.
duopo4j1n
duo2p2on.
.trio2p
kvar3o2p
kvaro1
kvin3o2p
kvi1
kvino1
ses3o2p
seso1
sep3o2p
sepo1
.ok3o2pa.
.okopa1
.ok3o2p2aj.
.ok3o2p2a2jn.
.okopa4j1n
.ok3o2p2an.
.ok3o2pe.
.okope1
.ok3o2po.
.o1ko1po1
.ok3o2p2oj.
.ok3o2p2o2jn.
.okopo4j1n
.ok3o2p2on.
naŭo4p
naŭo1
dek3o2pa.
dekopa1
dek3o2p2aj.
dek3o2p2a2jn.
dekopa4j1n
dek3o2p2an.
dek3o2pe.
dekope1
dek3o2po.
deko1po1
dek3o2p2oj.
dek3o2p2o2jn.
dekopo4j1n
dek3o2p2on.
cent3o2pa.
centopa1
cent3o2p2aj.
cent3o2p2a2jn.
centopa4j1n
cent3o2p2an.
cent3o2pe.
centope1
cent3o2po.
cento1po1
cent3o2p2oj.
cent3o2p2o2jn.
centopo4j1n
cent3o2p2on.
mil3o2pa.
milopa1
mil3o2p2aj.
mil3o2p2a2jn.
milopa4j1n
mil3o2p2an.
mil3o2pe.
milope1
mil3o2po.
milo1po1
mil3o2p2oj.
mil3o2p2o2jn.
milopo4j1n
mil3o2p2on.
1o2ta.
ota1
1o2t2aj.
1o2t2a2jn.
ota4j1n
1o2t2an.
1o2te.
ote1
1o2to.
o1to1
1o2t2oj.
1o2t2o2jn.
oto4j1n
1o2t2on.
abrik2o3t
abri1
abriko1
anekd2o3t
ane4k1d
anekdo1
antid2o3t
antido1
asimpt2o3t
asi4m1p
asimp1t
asimpto1
.az2o3t
.azo1
.bo3t
.bal2o3t
.balo1
bankr2o3t
ba4n1k
bankro1
.behem2o3t
.behe1
.behemo1
bergam2o3t
be1
be4r1g
berga1
bergamo1
.b1i3g2o3t
.bigo1
.bisk2o3t
.bis1k
.bisko1
bojk2o3t
bo4j1k
bojko1
.do3t
.dep2o3t
.depo1
.desp2o3t
.des1p
.despo1
.dev2o3t
.devo1
dorl2o3t
do4r1l
dorlo1
.ekz2o3t
.ekzo1
.er2o3t
.ero1
.erg2o3t
.e4r1g
.ergo1
.fo3t
.fag2o3t
.fago1
.fakt2o3t
.fa4k1t
.fakto1
.fl2o3t
fokstr2o3t
fo4k1s
fok1s2t2r
foks1t
fokstro1
.fr2o3t
.go3t
.gav2o3t
.gavo1
.golg2o3t
.go4l1g
.golgo1
.gr2o3t
.gro1
.herod2o3t
.hero1
.herodo1
hipn2o3t
hip1n
hipno1
hotent2o3t
hote1
hote4n1t
hotento1
.jo3t
.jo1
ĵab2o3t
ĵa1
ĵabo1
.ko3t
.kaĉal2o3t
.kaĉa1
.kaĉalo1
kalik2o3t
kali1
kaliko1
.kaml2o3t
.ka4m1l
.kamlo1
.kan2o3t
.kano1
.kap2o3t
.kapo1
.kar2o3t
.karo1
.koj2o3t
.kojo1
.komp2o3t
.kompo1
kompl2o3t
komplo1
kreoz2o3t
kre3o1
kreozo1
.ku3l
.kul2o3t
.kulo1
.kv2o3t
.kvo1
.alikv2o3t
.ali1
.alik1v
.alikvo1
.lo3t
.lo1
.lit2o3t
.lito1
.mar2o3t
.maro1
.mark2o3t
.ma4r1k
.marko1
marm2o3t
ma4r1m
marmo1
.mioz2o3t
.mi3o1
.miozo1
.no3t
nark2o3t
na4r1k
narko1
.po3t
perlam2o3t
pe4r1l
perla1
perlamo1
.pier2o3t
.pi3e1
.piero1
.pil2o3t
.pilo1
.piv2o3t
.pivo1
.pl2o3t
.plo1
poligl2o3t
poli1
poliglo1
.ro3t
.rab2o3t
.rabo1
reding2o3t
redi1
redi4n1g
redingo1
.rob2o3t
.robo1
.sab2o3t
.sabo1
sacerd2o3t
sace1
sace4r1d
sacerdo1
.sk2o3t
.sko1
.skler2o3t
.skle1
.sklero1
.skr2o3t
.skro1
.sp2o3t
.ŝo3t
.ŝo1
ŝevj2o3t
ŝe1
ŝe4v1j2
ŝevjo1
.terak2o3t
.tera1
.terako1
.tr2o3t
.tro1
.trik2o3t
.triko1
.vo3t
.zel2o3t
.zelo1
.po2r
.pos2t1
pos3t2ame4n1t
pos1t
posta1
postame1
pos3t2iljon
posti1
posti4l1j2
postiljo1
pos3t2u3l
postu1
.prete2r1
.prete1
.re2f3l2ig
.refli1
.re2spo4n1d
.res1p
.respo1
.re2spublik
.respu1
.respubli1
.re2storaci1
.res1t
.resto1
.restora1
.re2tro1
.sa2m
sam2a3r
sama1
.sa3m2u3m
.samu1
sa3m2uraj
samu1
samura1
.se3n2a3a1
.se3n2a3o1
sen2at
sena1
sen2eskal
sene1
senes1k
seneska1
.si4a1
.su3b2ute1
.subu1
.supe2r1
.supe1
.supe3ra.
.supera1
.supe3r2aj.
.supe3r2a2jn.
.supera4j1n
.supe3r2an.
.supe3re.
.supere1
.supe3ro.
.supero1
.supe3r2oj.
.supe3r2o2jn.
.supero4j1n
.supe3r2on.
.supe3r2as.
.supe3ri.
.superi1
.supe3ris.
.supe3r2os.
.supe3ru.
.su1peru1
.supe3r2us.
.ŝi4a1
tran3sc2e4n3d
tra4n1s
tran1s2c
transce1
tran3sep1t
transe1
tran3sistor
transi1
transis1t
transisto1
tran3sit
tran3s2pir
trans1p
transpi1
.tu2t1
.tu1
.tu3ta.
.tuta1
.tu3t2aj.
.tu3t2a2jn.
.tuta4j1n
.tu3t2an.
.tu3te.
.tute1
.tu3to.
.tuto1
.tu3t2oj.
.tu3t2o2jn.
.tuto4j1n
.tu3t2on.
1u2ja.
uja1
1u2j2aj.
1u2j2a2jn.
uja4j1n
1u2j2an.
1u2je.
uje1
1u2jo.
ujo1
1u2j2oj.
1u2j2o2jn.
ujo4j1n
1u2j2on.
1u2j1ig
uji1
1u2j1iĝ
.and2u3j
.a4n1d
.andu1
.halel2u3j
.hale1
.halelu1
1u2la.
ula1
1u2l2aj.
1u2l2a2jn.
ula4j1n
1u2l2an.
1u2le.
ule1
1u2lo.
ulo1
1u2l2oj.
1u2l2o2jn.
ulo4j1n
1u2l2on.
1e2m1u2l
e3m2u4l1s2
1u2l3ec
1u2l3ej
.akum2u3l
.akumu1
.ang2u3l
.angu1
.bu3l
.bet2u3l
.betu1
.bru3l
.bru1
.ejak2u3l
.eja1
.ejaku1
.fist2u3l
.fis1t
.fistu1
.fu3l
galin2u3l
gali1
galinu1
.gran2u3l
.granu1
.grat2u3l
.gratu1
.herk2u3l
.he4r1k
.herku1
hierod2u3l
hi3e1
hiero1
hierodu1
homunk2u3l
homu1
homu4n1k
homunku1
.inok2u3l
.ino1
.inoku1
.ins2u3l
.insu1
.instanb2u3l
.insta1
.insta4n1b
.instanbu1
.ju3l
.ĵu3l
.ĵu1
.kab2u3l
.kabu1
kalend2u3l
kalendu1
kalk2u3l
kalku1
kapit2u3l
kapitu1
kaps2u3l
kap1s
kapsu1
koag2u3l
ko3a1
koagu1
.kons2u3l
.konsu1
.kop2u3l
.kopu1
korpusk2u3l
ko4r1p
korpu1
korpus1k
korpusku1
.kum2u3l
.kumu1
.lu3l
.liverp2u3l
.live1
.live4r1p
.liverpu1
.mak2u3l
.maku1
manip2u3l
mani1
manipu1
matrik2u3l
matri1
matriku1
.mod2u3l
.modu1
molek2u3l
mole1
moleku1
.nu3l
.nu1
.neb2u3l
.nebu1
.ok2u3l
.oku1
.pu3l
.paĉ2u3l
.paĉu1
.pust2u3l
.pus1t
.pustu1
.ru3l
.ru1
.reg2u3l
.regu1
retik2u3l
reti1
retiku1
.ruk2u3l
.ruku1
.sim2u3l
.simu1
skrup2u3l
skru1
skrupu1
somnamb2u3l
so4m1n
somna1
somna4m1b
somnambu1
speg2u3l
spegu1
.spek2u3l
.spe1
.speku1
.stim2u3l
.stimu1
.tu3l
.tab2u3l
.tabu1
tarant2u3l
tara1
tara4n1t
tarantu1
.tru3l
.tru1
tuberk2u3l
tube1
tube4r1k
tuberku1
turb2u3l
turbu1
.ul2u3l
.ulu1
.uv2u3l
.uvu1
.vist2u3l
.vistu1
1u2l1ar
2u3l2ari1
2u3l2a4r1d
.pedik2u3l2ar
.pedi1
.pediku1
.pedikula1
1u2ma.
uma1
1u2m2aj.
1u2m2a2jn.
uma4j1n
1u2m2an.
1u2me.
ume1
1u2mo.
umo1
1u2m2oj.
1u2m2o2jn.
umo4j1n
1u2m2on.
1u2m2as.
1u2mi.
umi1
1u2mis.
1u2m2os.
1u2mu.
u1mu1
1u2m2us.
1u2m3ec
.alb2u3m
.a4l1b
.albu1
.bu3m
.opid2u3m
.opi1
.opidu1
referend2u3m
refe1
refere1
refere4n1d
referendu1
.fu3m
parf2u3m
pa4r1f
parfu1
.gu3m
.gu1
.kuk2u3m
.kuku1
.luk2u3m
.luku1
.lu3m
vol2u3m
volu1
.plu3m
.plu1
.stern2u3m
.ste1
.ste4r1n
.sternu1
.pu3m
.ru3m
.ser2u3m
.seru1
.gru3m
.gru1
.kvor2u3m
.kvoru1
.tru3m
.stru3m
.stru1
.su3m
.res2u3m
.resu1
kons2u3m
konsu1
opos2u3m
o1po1
oposu1
.bit2u3m
.bitu1
.kost2u3m
.kos1t
.kostu1
.zu3m
.zu1
.vi4a1
vi5a4n1d
vi3a1
vi5atik
viati1
.vi2c1
.vi3ca.
.vica1
.vi3c2aj.
.vi3c2a2jn.
.vica4j1n
.vi3c2an.
.vi3ce.
.vice1
.vi3co.
.vico1
.vi3c2oj.
.vi3c2o2jn.
.vico4j1n
.vi3c2on.
.vi3c2i3a1
.vi1ci1
.vi3c2i3o1
.vi2r1
vir2us
viru1
vi1r2ule4n1t
virule1
1a2fabl
afa1
1a2gra1
1a2per
ape1
1a2va1r
ava1
4ologi1
olo1
4ografi1
ogra1
2fik
fre2m4d3l
fre1
fre4m1d
4b1c
4b1b2
4b1d
4b1f
4b1g
4b1h4
4b1j2
4b1k
4b1m
4b1n
4b1p
4b1s
4b1s2k
4b1t
4b1v
4b1z
4b1ĉ
4b1ĝ
4b1ĵ
4b1ŝ
4c1b
4c1c2
4c1ĉ
4c1d
4c1f
4c1g
4c1ĝ
4c1h4
4c1j2
4c1ĵ
4c1k
4c1l
4c1m
4c1n
4c1p
4c1r
4c1s
4c1ŝ
4c1t
4c1v
4c1z
4ĉ1b
4ĉ1c
4ĉ1d
4ĉ1f
4ĉ1g
4ĉ1h4
4ĉ1k
4ĉ1l
4ĉ1m
4ĉ1n
4ĉ1p
4ĉ1s
4ĉ1t
4ĉ1v
4ĉ1z
4ĉ1ĉ
4ĉ1ĝ
4ĉ1ĵ
4ĉ1ŝ
4d1b
4d1c
4d1d2
4d1f
4d1g
4d1h4
4d1j2
4d1k
4d1l
4d1m
4d1n
4d1p
4d1s
4d1t
4d1v
4d1z
4d1ĉ
4d1ĝ
4d1ĵ
4d1ŝ
1e2ben
ebe1
1e4d2z
1e2gal
1e2le1g2a4n3t
elega1
4f1b
4f1c
4f1ĉ
4f1d
4f1f2
4f1g
4f1ĝ
4f1h4
4f1j2
4f1ĵ
4f1k
4f1m
4f1n
4f1p
4f1s
4f1ŝ
f1t
4f1v
4f1z
4g1b
4g1c
4g1ĉ
g1d
4g1f
4g1g2
4g1ĝ
4g1h4
4g1j2
4g1ĵ
4g1k
g1m
g1n
4g1p
4g1s
4g1ŝ
4g1t
g1v
4g1z
4ĝ1b
4ĝ1c
4ĝ1d
4ĝ1f
4ĝ1g
4ĝ1h4
4ĝ1j2
4ĝ1k
4ĝ1l
4ĝ1m
4ĝ1n
4ĝ1p
4ĝ1s
4ĝ1t
4ĝ1v
4ĝ1z
4ĝ1ĉ
4ĝ1ĝ
4ĝ1ĵ
4ĝ1ŝ
4h1ĉ
4h1ĝ
4h1ĵ
4h1ŝ
4ĥ1b
4ĥ1c
4ĥ1d
4ĥ1f
4ĥ1g
4ĥ1h4
4ĥ1j2
4ĥ1k
4ĥ1m
4ĥ1n
4ĥ1p
4ĥ1s
4ĥ1t
4ĥ1v
4ĥ1z
4ĥ1ĉ
4ĥ1ĝ
4ĥ1ĵ
4ĥ1ŝ
1i2de4n1t
4j1b
4j1c
4j1d
4j1f
4j1g
4j1h4
4j1k
4j1l
4j1m
4j1p
4j1r
4j1s
4j1t
4j1v
4j1z
4j1ĉ
4j1ĝ
4j1ĵ
4j1ŝ
4ĵ1b
4ĵ1c
4ĵ1d
4ĵ1f
4ĵ1g
4ĵ1h4
4ĵ1j2
4ĵ1k
4ĵ1l
4ĵ1m
4ĵ1n
4ĵ1p
4ĵ1s
4ĵ1t
4ĵ1v
4ĵ1z
4ĵ1ĉ
4ĵ1ĝ
4ĵ1ĵ
4ĵ1ŝ
4k1b
4k1c
4k1d
4k1f
4k1g
4k1h4
4k1j2
4k1m
4k1n
4k1p
4k1s
k1s2t2r
ks1t
4k1t
4k1z
4k1ĉ
4k1ĝ
4k1ĵ
4k1ŝ
4l1b
4l1c
4l1d
4l1f
4l1g
4l1h4
4l1j2
4l1k
4l1l2
4l1m
4l1n
4l1r
4l1s2
4l1v
4l1z
4l1ĉ
4l1ĝ
4l1ĵ
4l5ŝ2
4m1b
4m1c
4m1d
4m1f
4m1g
4m1h4
4m1j2
4m1k
4m1l
4m1m2
4m1n
4m1p
4m1r
4m1s
4m1t
4m1v
4m1z
4m1ĉ
4m1ĝ
4m1ĵ
4m1ŝ
mu4l2t1
mu1
mult2e1
mul2t3eg
mul3ta1
mul3te.
mul3to1
mul4t3obl
4n1b
4n1c
4n1d
4n1f
4n1g
4n1h4
n2k1v
4n1l
n1m
4n1n
4n1p
4n1r
n1s2c
n1s2t
4n1v2
4n1z
4n1ĉ
4n1ĝ
4n1ĵ
4n1ŝ
n4k1c
n2s1f
nor2d1af
no1
no4r1d
norda1
nor2d1am
nor2d1az
nor2d1e4ŭ1
norde1
nor2d1rus
nordru1
nor2d1ge4r1m
nor4d1g
nordge1
1o2be1
1o2kup
oku1
1o2por1tun
opo4r1t
oportu1
4p1b
4p1c
4p1d
4p1g
4p1h4
p1j2
p1k
p1m
p1n
p1s
4p1v
4p1z
4p1ĉ
4p1ĝ
4p1ĵ
p1ŝ
4r1b
4r1c
4r1d
4r1f
4r1g
4r1h4
4r1j2
4r1k
4r1l
4r1m
4r1n
4r1p
4r1s
4r1t
4r1v
4r1z
4r1ĉ
4r1ĝ
4r1ĵ
4r1ŝ
s1b
4s1ĉ
4s1d
s1f
s1g
4s1ĝ
4s1h4
4s1j2
4s1ĵ
3s2lab
sla1
3s2lalom
slalo1
3s2la4n1g
3s2lav
3s2led
sle1
3s2lip
sli1
3s2lo4j1d
3s2lovak
slova1
3s2loven
slove1
3s2lup
slu1
3s2lofo4k1s
slofo1
3s2ma4l1t
sma1
3s2mera4l1d
sme1
smera1
3s2milak
smi1
smila1
3s2mi4r1g
3s2mut
smu1
4s1n
3s2nob
sno1
3s2nuf
snu1
s1r
4s1s
4s1ŝ
1s2tu1d
stu1
4s1z
.su2d1af
.suda1
.su2d1am
.su2d1azi1
.su2d1eŭr
.sude1
.sude4ŭ1
.su2d1rus
.sudru1
4ŝ1b
4ŝ1c
4ŝ1d
4ŝ1f
4ŝ1g
4ŝ1h4
4ŝ1j2
4ŝ1k
ŝ1m
ŝ1n
ŝ1p
4ŝ1s
ŝ1t
ŝ1v
4ŝ1z
4ŝ1ĉ
4ŝ1ĝ
4ŝ1ĵ
4ŝ1ŝ
1ŝ2tel
ŝte1
4t1b
4t1c
4t1d
4t1f
4t1g
4t1h4
4t1j2
4t1k
4t1m
4t1n
4t1p
4t1s
4t1t
4t1v
4t1z
4t1ĉ
4t1ĝ
4t1ĵ
4t1ŝ
4ŭ1
ŭ2s1k
4v1b
4v1c
4v1d
4v1f
4v1g
4v1h4
4v1j2
4v1k
4v1l
4v1m
4v1n
4v1p
4v1s
4v1t
4v1v
4v1z
4v1ĉ
4v1ĝ
4v1ĵ
4v1ŝ
4z1b
4z1c
4z1d
4z1f
4z1g
4z1h4
4z1j2
4z1k
4z1l
z2lot
zlo1
4z1m
4z1n
4z1p
4z1r
4z1s
4z1t
4z1v
4z1ĉ
4z1ĝ
4z1ĵ
4z1ŝ
PK
!<mVhyphenation/hyph_es.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1b
4b.
2b1b
2b1c
2b1d
2b1f
2b1g
2b1h
2b1j
2b1k
2b1m
2b1n
2b1p
2b1q
2b1s
2b1t
2b1v
2b1w
2b1x
2b1y
2b1z
1c
4c.
2c1b
2c1c
2c1d
2c1f
2c1g
2c1j
2c1k
2c1m
2c1n
2c1p
2c1q
2c1s
2c1t
2c1v
2c1w
2c1x
2c1y
2c1z
1d
4d.
2d1b
2d1c
2d1d
2d1f
2d1g
2d1h
2d1j
2d1k
2d1l
2d1m
2d1n
2d1p
2d1q
2d1s
2d1t
2d1v
2d1w
2d1x
2d1y
2d1z
1f
4f.
2f1b
2f1c
2f1d
2f1f
2f1g
2f1h
2f1j
2f1k
2f1m
2f1n
2f1p
2f1q
2f1s
2f1t
2f1v
2f1w
2f1x
2f1y
2f1z
1g
4g.
2g1b
2g1c
2g1d
2g1f
2g1g
2g1h
2g1j
2g1k
2g1m
2g1n
2g1p
2g1q
2g1s
2g1t
2g1v
2g1w
2g1x
2g1y
2g1z
4h.
2h1b
2h1c
2h1d
2h1f
2h1g
2h1h
2h1j
2h1k
2h1l
2h1m
2h1n
2h1p
2h1q
2h1r
2h1s
2h1t
2h1v
2h1w
2h1x
2h1y
2h1z
1j
4j.
2j1b
2j1c
2j1d
2j1f
2j1g
2j1h
2j1j
2j1k
2j1l
2j1m
2j1n
2j1p
2j1q
2j1r
2j1s
2j1t
2j1v
2j1w
2j1x
2j1y
2j1z
1k
4k.
2k1b
2k1c
2k1d
2k1f
2k1g
2k1h
2k1j
2k1k
2k1m
2k1n
2k1p
2k1q
2k1s
2k1t
2k1v
2k1w
2k1x
2k1y
2k1z
1l
4l.
2l1b
2l1c
2l1d
2l1f
2l1g
2l1h
2l1j
2l1k
2l1m
2l1n
2l1p
2l1q
2l1r
2l1s
2l1t
2l1v
2l1w
2l1x
2l1y
2l1z
1m
4m.
2m1b
2m1c
2m1d
2m1f
2m1g
2m1h
2m1j
2m1k
2m1l
2m1m
2m1n
2m1p
2m1q
2m1r
2m1s
2m1t
2m1v
2m1w
2m1x
2m1y
2m1z
1n
4n.
2n1b
2n1c
2n1d
2n1f
2n1g
2n1h
2n1j
2n1k
2n1l
2n1m
2n1n
2n1p
2n1q
2n1r
2n1s
2n1t
2n1v
2n1w
2n1x
2n1y
2n1z
1p
4p.
2p1b
2p1c
2p1d
2p1f
2p1g
2p1h
2p1j
2p1k
2p1m
2p1n
2p1p
2p1q
2p1s
2p1t
2p1v
2p1w
2p1x
2p1y
2p1z
1q
4q.
2q1b
2q1c
2q1d
2q1f
2q1g
2q1h
2q1j
2q1k
2q1l
2q1m
2q1n
2q1p
2q1q
2q1r
2q1s
2q1t
2q1v
2q1w
2q1x
2q1y
2q1z
1r
4r.
2r1b
2r1c
2r1d
2r1f
2r1g
2r1h
2r1j
2r1k
2r1l
2r1m
2r1n
2r1p
2r1q
2r1s
2r1t
2r1v
2r1w
2r1x
2r1y
2r1z
1s
4s.
2s1b
2s1c
2s1d
2s1f
2s1g
2s1h
2s1j
2s1k
2s1l
2s1m
2s1n
2s1p
2s1q
2s1r
2s1s
2s1t
2s1v
2s1w
2s1x
2s1y
2s1z
1t
4t.
2t1b
2t1c
2t1d
2t1f
2t1g
2t1h
2t1j
2t1k
2t1m
2t1n
2t1p
2t1q
2t1s
2t1t
2t1v
2t1w
2t1x
2t1y
2t1z
1v
4v.
2v1b
2v1c
2v1d
2v1f
2v1g
2v1h
2v1j
2v1k
2v1m
2v1n
2v1p
2v1q
2v1s
2v1t
2v1v
2v1w
2v1x
2v1y
2v1z
1w
4w.
2w1b
2w1c
2w1d
2w1f
2w1g
2w1h
2w1j
2w1k
2w1l
2w1m
2w1n
2w1p
2w1q
2w1r
2w1s
2w1t
2w1v
2w1w
2w1x
2w1y
2w1z
1x
4x.
2x1b
2x1c
2x1d
2x1f
2x1g
2x1h
2x1j
2x1k
2x1l
2x1m
2x1n
2x1p
2x1q
2x1r
2x1s
2x1t
2x1v
2x1w
2x1x
2x1y
2x1z
1y
4y.
2y1b
2y1c
2y1d
2y1f
2y1g
2y1h
2y1j
2y1k
2y1l
2y1m
2y1n
2y1p
2y1q
2y1r
2y1s
2y1t
2y1v
2y1w
2y1x
2y1y
2y1z
1z
4z.
2z1b
2z1c
2z1d
2z1f
2z1g
2z1h
2z1j
2z1k
2z1l
2z1m
2z1n
2z1p
2z1q
2z1r
2z1s
2z1t
2z1v
2z1w
2z1x
2z1y
2z1z
1ñ
4ñ.
c4h
4c4h.
2c2h1b
2c2h1c
2c2h1d
2c2h1f
2c2h1g
2c2h1h
2c2h1j
2c2h1k
c2h2l
2c2h1m
2c2h1n
2c2h1p
2c2h1q
c2h2r
2c2h1s
2c2h1t
2c2h1v
2c2h1w
2c2h1x
2c2h1y
2c2h1z
1l4l
4l4l.
2l2l1b
2l2l1c
2l2l1d
2l2l1f
2l2l1g
2l2l1h
2l2l1j
2l2l1k
2l4l4l
2l2l1m
2l2l1n
2l2l1p
2l2l1q
2l2l1r
2l2l1s
2l2l1t
2l2l1v
2l2l1w
2l2l1x
2l2l1y
2l2l1z
b2l
4b4l.
2b2l2b
2b2l2c
2b2l2d
2b2l2f
2b2l2g
2b2l2h
2b2l2j
2b2l2k
2b1l4l
2b2l2m
2b2l2n
2b2l2p
2b2l2q
2b2l2r
2b2l2s
2b2l2t
2b2l2v
2b2l2w
2b2l2x
2b2l2y
2b2l2z
c2l
4c4l.
2c2l2b
2c2l2c
2c2l2d
2c2l2f
2c2l2g
2c2l2h
2c2l2j
2c2l2k
2c1l4l
2c2l2m
2c2l2n
2c2l2p
2c2l2q
2c2l2r
2c2l2s
2c2l2t
2c2l2v
2c2l2w
2c2l2x
2c2l2y
2c2l2z
f2l
4f4l.
2f2l2b
2f2l2c
2f2l2d
2f2l2f
2f2l2g
2f2l2h
2f2l2j
2f2l2k
2f1l4l
2f2l2m
2f2l2n
2f2l2p
2f2l2q
2f2l2r
2f2l2s
2f2l2t
2f2l2v
2f2l2w
2f2l2x
2f2l2y
2f2l2z
g2l
4g4l.
2g2l2b
2g2l2c
2g2l2d
2g2l2f
2g2l2g
2g2l2h
2g2l2j
2g2l2k
2g1l4l
2g2l2m
2g2l2n
2g2l2p
2g2l2q
2g2l2r
2g2l2s
2g2l2t
2g2l2v
2g2l2w
2g2l2x
2g2l2y
2g2l2z
k2l
4k4l.
2k2l2b
2k2l2c
2k2l2d
2k2l2f
2k2l2g
2k2l2h
2k2l2j
2k2l2k
2k1l4l
2k2l2m
2k2l2n
2k2l2p
2k2l2q
2k2l2r
2k2l2s
2k2l2t
2k2l2v
2k2l2w
2k2l2x
2k2l2y
2k2l2z
p2l
4p4l.
2p2l2b
2p2l2c
2p2l2d
2p2l2f
2p2l2g
2p2l2h
2p2l2j
2p2l2k
2p1l4l
2p2l2m
2p2l2n
2p2l2p
2p2l2q
2p2l2r
2p2l2s
2p2l2t
2p2l2v
2p2l2w
2p2l2x
2p2l2y
2p2l2z
v2l
4v4l.
2v2l2b
2v2l2c
2v2l2d
2v2l2f
2v2l2g
2v2l2h
2v2l2j
2v2l2k
2v1l4l
2v2l2m
2v2l2n
2v2l2p
2v2l2q
2v2l2r
2v2l2s
2v2l2t
2v2l2v
2v2l2w
2v2l2x
2v2l2y
2v2l2z
b2r
4b4r.
2b2r2b
2b2r2c
2b2r2d
2b2r2f
2b2r2g
2b2r2h
2b2r2j
2b2r2k
2b2r2l
2b2r2m
2b2r2n
2b2r2p
2b2r2q
2b1r2r
2b2r2s
2b2r2t
2b2r2v
2b2r2w
2b2r2x
2b2r2y
2b2r2z
c2r
4c4r.
2c2r2b
2c2r2c
2c2r2d
2c2r2f
2c2r2g
2c2r2h
2c2r2j
2c2r2k
2c2r2l
2c2r2m
2c2r2n
2c2r2p
2c2r2q
2c1r2r
2c2r2s
2c2r2t
2c2r2v
2c2r2w
2c2r2x
2c2r2y
2c2r2z
d2r
4d4r.
2d2r2b
2d2r2c
2d2r2d
2d2r2f
2d2r2g
2d2r2h
2d2r2j
2d2r2k
2d2r2l
2d2r2m
2d2r2n
2d2r2p
2d2r2q
2d1r2r
2d2r2s
2d2r2t
2d2r2v
2d2r2w
2d2r2x
2d2r2y
2d2r2z
f2r
4f4r.
2f2r2b
2f2r2c
2f2r2d
2f2r2f
2f2r2g
2f2r2h
2f2r2j
2f2r2k
2f2r2l
2f2r2m
2f2r2n
2f2r2p
2f2r2q
2f1r2r
2f2r2s
2f2r2t
2f2r2v
2f2r2w
2f2r2x
2f2r2y
2f2r2z
g2r
4g4r.
2g2r2b
2g2r2c
2g2r2d
2g2r2f
2g2r2g
2g2r2h
2g2r2j
2g2r2k
2g2r2l
2g2r2m
2g2r2n
2g2r2p
2g2r2q
2g1r2r
2g2r2s
2g2r2t
2g2r2v
2g2r2w
2g2r2x
2g2r2y
2g2r2z
k2r
4k4r.
2k2r2b
2k2r2c
2k2r2d
2k2r2f
2k2r2g
2k2r2h
2k2r2j
2k2r2k
2k2r2l
2k2r2m
2k2r2n
2k2r2p
2k2r2q
2k1r2r
2k2r2s
2k2r2t
2k2r2v
2k2r2w
2k2r2x
2k2r2y
2k2r2z
p2r
4p4r.
2p2r2b
2p2r2c
2p2r2d
2p2r2f
2p2r2g
2p2r2h
2p2r2j
2p2r2k
2p2r2l
2p2r2m
2p2r2n
2p2r2p
2p2r2q
2p1r2r
2p2r2s
2p2r2t
2p2r2v
2p2r2w
2p2r2x
2p2r2y
2p2r2z
1r2r
4r4r.
2r2r2b
2r2r2c
2r2r2d
2r2r2f
2r2r2g
2r2r2h
2r2r2j
2r2r2k
2r2r2l
2r2r2m
2r2r2n
2r2r2p
2r2r2q
2r2r2r
2r2r2s
2r2r2t
2r2r2v
2r2r2w
2r2r2x
2r2r2y
2r2r2z
t2r
4t4r.
2t2r2b
2t2r2c
2t2r2d
2t2r2f
2t2r2g
2t2r2h
2t2r2j
2t2r2k
2t2r2l
2t2r2m
2t2r2n
2t2r2p
2t2r2q
2t1r2r
2t2r2s
2t2r2t
2t2r2v
2t2r2w
2t2r2x
2t2r2y
2t2r2z
v2r
4v4r.
2v2r2b
2v2r2c
2v2r2d
2v2r2f
2v2r2g
2v2r2h
2v2r2j
2v2r2k
2v2r2l
2v2r2m
2v2r2n
2v2r2p
2v2r2q
2v1r2r
2v2r2s
2v2r2t
2v2r2v
2v2r2w
2v2r2x
2v2r2y
2v2r2z
2b3p2t
2c3p2t
2d3p2t
2l3p2t
2m3p2t
2n3p2t
2r3p2t
2s3p2t
2t3p2t
2x3p2t
2y3p2t
4p4t.
2b3c2t
2c3c2t
2d3c2t
2l3c2t
2m3c2t
2n3c2t
2r3c2t
2s3c2t
2t3c2t
2x3c2t
2y3c2t
4c4t.
2b3c2n
2c3c2n
2d3c2n
2l3c2n
2m3c2n
2n3c2n
2r3c2n
2s3c2n
2t3c2n
2x3c2n
2y3c2n
4c4n.
2b3p2s
2c3p2s
2d3p2s
2l3p2s
2m3p2s
2n3p2s
2r3p2s
2s3p2s
2t3p2s
2x3p2s
2y3p2s
4p4s.
2b3m2n
2c3m2n
2d3m2n
2l3m2n
2m3m2n
2n3m2n
2r3m2n
2s3m2n
2t3m2n
2x3m2n
2y3m2n
4m4n.
2b3g2n
2c3g2n
2d3g2n
2l3g2n
2m3g2n
2n3g2n
2r3g2n
2s3g2n
2t3g2n
2x3g2n
2y3g2n
4g4n.
2b3f2t
2c3f2t
2d3f2t
2l3f2t
2m3f2t
2n3f2t
2r3f2t
2s3f2t
2t3f2t
2x3f2t
2y3f2t
4f4t.
2b3p2n
2c3p2n
2d3p2n
2l3p2n
2m3p2n
2n3p2n
2r3p2n
2s3p2n
2t3p2n
2x3p2n
2y3p2n
4p4n.
2b3c2z
2c3c2z
2d3c2z
2l3c2z
2m3c2z
2n3c2z
2r3c2z
2s3c2z
2t3c2z
2x3c2z
2y3c2z
4c4z.
2b3t2z
2c3t2z
2d3t2z
2l3t2z
2m3t2z
2n3t2z
2r3t2z
2s3t2z
2t3t2z
2x3t2z
2y3t2z
4t4z.
2b3t2s
2c3t2s
2d3t2s
2l3t2s
2m3t2s
2n3t2s
2r3t2s
2s3t2s
2t3t2s
2x3t2s
2y3t2s
4t4s.
sa2n4c5t
sa1n
sa2n1c
pla2n4c5t
pla1n
pla2n1c
2n4o.
2t2l
4caca4
ca1c
4cago4
ca1g
4caga4
4cag4a4s.
caga1s
4tet4a.
1te1t
4tet4a4s.
teta1s
4puta4
pu1t
4puto4
.hu4mea
.hu1m
.hu4meo
.he4mee
.he1m
4me4o.
4meabl4e.
mea1b
meab2l
4meabl4e4s.
meable1s
4pedo4
pe1d
4culo4
cu1l
3ment4e.
me1n
me2n1t
4i3g4o.
i1g
4e4s.
e1s
4é1s
4e.
4e3m2o4s.
e1m
emo1s
4éi4s.
éi1s
4e4n.
e1n
4í4a.
4í4a4s.
ía1s
4í4a3m2o4s.
ía1m
íamo1s
4íai4s.
íai1s
4í4a4n.
ía1n
4í.
4í4s3t4e.
í1s
í2s1t
4í4s3t4e4s.
íste1s
4í3t4e4s.
í1t
íte1s
4í3m2o4s.
í1m
ímo1s
4ís3tei4s.
ístei1s
4e3r4é.
e1r
4e3r4á4s.
erá1s
4e3ré4s.
er4é1s
4e3rí4s.
erí1s
4e3rá.
4e3r4e3m2o4s.
ere1m
eremo1s
4e3r4éi4s.
eréi1s
4e3rá4n.
erá1n
4i3g4a.
4i3g4a4s.
iga1s
4i3g4á4s.
igá1s
4i3g4a3m2o4s.
iga1m
igamo1s
4i3g4ái4s.
igái1s
4a4i3g4a4n.
ai1g
aiga1n
4e3r4í4a.
4e3r4í4a4s.
ería1s
4e3r4í4a3m2o4s.
ería1m
eríamo1s
4e3r4íai4s.
eríai1s
4e3r4í4a4n.
ería1n
4i3gá3mosm4e.
igá1m
igámo1s
igá1mo2s1m
4i3gá3mosmel4e.
igámosme1l
4i3gá3mosmel4o.
4i3gá3mos3mel4a.
4i3gá3mosmel4e4s.
igámosmele1s
4i3gá3mosmel2o4s.
igámosmelo1s
4i3gá3mos3mel4a4s.
igámosmela1s
4i3gá3mos3t4e.
igámo2s1t
4i3gá3mostel4e.
igámoste1l
4i3gá3mostel4o.
4i3gá3mos3tel4a.
4i3gá3mostel4e4s.
igámostele1s
4i3gá3mostel2o4s.
igámostelo1s
4i3gá3mos3tel4a4s.
igámostela1s
4i3gá3mosl4e.
igámo2s1l
4i3gá3mosl4a.
4i3gá3mosl4o.
4i3gá3mosel4e.
igámose1l
4i3gá3mosel4o.
4i3gá3mosel4a.
4i3gá3mosel4e4s.
igámosele1s
4i3gá3mosel2o4s.
igámoselo1s
4i3gá3mosel4a4s.
igámosela1s
4i3gá3mon2o4s.
igámo1n
igámono1s
4i3gá3monosl4e.
igámono2s1l
4i3gá3monosl4o.
4i3gá3monosl4a.
4i3gá3monosl4e4s.
igámonosle1s
4i3gá3monosl2o4s.
igámonoslo1s
4i3gá3monosl4a4s.
igámonosla1s
4i3gá3mo2o4s.
igámoo1s
4i3gá3moosl4e.
igámoo2s1l
4i3gá3moosl4o.
4i3gá3moosl4a.
4i3gá3moosl4e4s.
igámoosle1s
4i3gá3moosl2o4s.
igámooslo1s
4i3gá3moosl4a4s.
igámoosla1s
4i3gá3mosl4e4s.
igámosle1s
4i3gá3mosl4a4s.
igámosla1s
4i3gá3mosl2o4s.
igámoslo1s
4e4d.
e1d
4é.
4edm4e.
e2d1m
4édmel4e.
é1d
é2d1m
édme1l
4édmel4o.
4éd3mel4a.
4édmel4e4s.
édmele1s
4édmel2o4s.
édmelo1s
4éd3mel4a4s.
édmela1s
4edt4e.
e2d1t
4édtel4e.
é2d1t
édte1l
4édtel4o.
4éd3tel4a.
4édtel4e4s.
édtele1s
4édtel2o4s.
édtelo1s
4éd3tel4a4s.
édtela1s
4edl4e.
e2d1l
4eedl4a.
ee1d
ee2d1l
4edl4o.
4édsel4e.
é2d1s
édse1l
4édsel4o.
4édsel4a.
4édsel4e4s.
édsele1s
4édsel2o4s.
édselo1s
4édsel4a4s.
édsela1s
4edn2o4s.
e2d1n
edno1s
4édnosl4e.
é2d1n
édno1s
édno2s1l
4édnosl4o.
4édnosl4a.
4édnosl4e4s.
édnosle1s
4édnosl2o4s.
édnoslo1s
4édnosl4a4s.
édnosla1s
4e2o4s.
eo1s
4éosl4e.
éo1s
éo2s1l
4éosl4o.
4éosl4a.
4éosl4e4s.
éosle1s
4éosl2o4s.
éoslo1s
4éosl4a4s.
éosla1s
4edl4e4s.
edle1s
4edl4a4s.
edla1s
4edl2o4s.
edlo1s
4e4r.
4erm4e.
e2r1m
4érmel4e.
é1r
é2r1m
érme1l
4érmel4o.
4ér3mel4a.
4érmel4e4s.
érmele1s
4érmel2o4s.
érmelo1s
4ér3mel4a4s.
érmela1s
4ert4e.
e2r1t
4értel4e.
é2r1t
érte1l
4értel4o.
4ér3tel4a.
4értel4e4s.
értele1s
4értel2o4s.
értelo1s
4ér3tel4a4s.
értela1s
4erl4e.
e2r1l
4erl4a.
4erl4o.
4ers4e.
e2r1s
4érsel4e.
é2r1s
érse1l
4érsel4o.
4érsel4a.
4érsel4e4s.
érsele1s
4érsel2o4s.
érselo1s
4érsel4a4s.
érsela1s
4ern2o4s.
e2r1n
erno1s
4érnosl4e.
é2r1n
érno1s
érno2s1l
4érnosl4o.
4érnosl4a.
4érnosl4e4s.
érnosle1s
4érnosl2o4s.
érnoslo1s
4érnosl4a4s.
érnosla1s
4e3r2o4s.
ero1s
4é3rosl4e.
éro1s
éro2s1l
4é3rosl4o.
4é3rosl4a.
4é3rosl4e4s.
érosle1s
4é3rosl2o4s.
éroslo1s
4é3rosl4a4s.
érosla1s
4erl4e4s.
erle1s
4erl4a4s.
erla1s
4erl2o4s.
erlo1s
4í3d4o.
í1d
4í3d4a.
4í3d2o4s.
ído1s
4í3d4a4s.
ída1s
4o.
4a4s.
a1s
4a.
4á4s.
á1s
4a3m2o4s.
a1m
amo1s
4ái4s.
ái1s
4a4n.
a1n
4as3t4e.
a2s1t
4as3t4e4s.
aste1s
4ó.
4at4e4s.
a1t
ate1s
4astei4s.
astei1s
4a3ro4n.
a1r
aro1n
4a3b4a.
a1b
4a3b4a4s.
aba1s
4á3b4a3m2o4s.
á1b
ába1m
ábamo1s
4a3bai4s.
abai1s
4a3b4a4n.
aba1n
4a3r4í4a.
4a3r4í4a4s.
aría1s
4a3r4í4a3m2o4s.
aría1m
aríamo1s
4a3ríai1s
4a3r4í4a4n.
aría1n
4a3r4é.
4a3r4á4s.
ará1s
4a3ré4s.
ar4é1s
4a3rí4s.
arí1s
4a3rá.
4a3r4e3m2o4s.
are1m
aremo1s
4a3r4éi4s.
aréi1s
4a3rá4n.
ará1n
4a3r4a.
4a3r4a4s.
ara1s
4á3r4a3m2o4s.
á1r
ára1m
áramo1s
4a3rai4s.
arai1s
4a3r4a4n.
ara1n
4a3r4e.
4a3r4e4s.
are1s
4á3r4e3m2o4s.
áre1m
áremo1s
4a3rei4s.
arei1s
4a3r4e4n.
are1n
4a3s4e.
4a3s4e4s.
ase1s
4á3s4e3m2o4s.
áse1m
ásemo1s
4a3sei4s.
asei1s
4a3s4e4n.
ase1n
4a4d.
a1d
e5r4a4s.
era1s
e5r4a3m2o4s.
era1m
eramo1s
e5r4ái4s.
erái1s
e5r4a4n.
era1n
e5r4as3t4e.
era2s1t
e5r4as3t4e4s.
eraste1s
e5r4at4e4s.
era1t
erate1s
e5r4astei4s.
erastei1s
e5r4a3ro4n.
e1ra1r
eraro1n
e5r4a3b4a.
era1b
e5r4a3b4a4s.
eraba1s
e5r4á3b4a3m2o4s.
erá1b
erába1m
erábamo1s
e5r4a3bai4s.
erabai1s
e5r4a3b4a4n.
eraba1n
e5r4a3r4í4a.
e5r4a3r4í4a4s.
eraría1s
e5r4a3r4í4a3m2o4s.
eraría1m
eraríamo1s
e5r4a3ríai1s
e5r4a3r4í4a4n.
eraría1n
e5r4a3r4é.
e5r4a3r4á4s.
erará1s
e5r4a3ré4s.
erar4é1s
e5r4a3rí4s.
erarí1s
e5r4a3rá.
e5r4a3r4e3m2o4s.
erare1m
eraremo1s
e5r4a3r4éi4s.
eraréi1s
e5r4a3rá4n.
erará1n
e5r4a3r4a.
e5r4a3r4a4s.
erara1s
e5r4á3r4a3m2o4s.
e1rá1r
erára1m
eráramo1s
e5r4a3rai4s.
erarai1s
e5r4a3r4a4n.
erara1n
e5r4a3r4e.
e5r4a3r4e4s.
erare1s
e5r4á3r4e3m2o4s.
eráre1m
eráremo1s
e5r4a3rei4s.
erarei1s
e5r4a3r4e4n.
erare1n
e5r4a3s4e.
e5r4a3s4e4s.
erase1s
e5r4á3s4e3m2o4s.
eráse1m
erásemo1s
e5r4a3sei4s.
erasei1s
e5r4a3s4e4n.
erase1n
e5r4a4d.
era1d
4adm4e.
a2d1m
4ádmel4e.
á1d
á2d1m
ádme1l
4ádmel4o.
4ád3mel4a.
4ádmel4e4s.
ádmele1s
4ádmel2o4s.
ádmelo1s
4ád3mel4a4s.
ádmela1s
4adt4e.
a2d1t
4ádtel4e.
á2d1t
ádte1l
4ádtel4o.
4ád3tel4a.
4ádtel4e4s.
ádtele1s
4ádtel2o4s.
ádtelo1s
4ád3tel4a4s.
ádtela1s
4adl4e.
a2d1l
4eadl4a.
ea1d
ea2d1l
4adl4o.
4ádsel4e.
á2d1s
ádse1l
4ádsel4o.
4ádsel4a.
4ádsel4e4s.
ádsele1s
4ádsel2o4s.
ádselo1s
4ádsel4a4s.
ádsela1s
4adn2o4s.
a2d1n
adno1s
4ádnosl4e.
á2d1n
ádno1s
ádno2s1l
4ádnosl4o.
4ádnosl4a.
4ádnosl4e4s.
ádnosle1s
4ádnosl2o4s.
ádnoslo1s
4ádnosl4a4s.
ádnosla1s
4a2o4s.
ao1s
4áosl4e.
áo1s
áo2s1l
4áosl4o.
4áosl4a.
4áosl4e4s.
áosle1s
4áosl2o4s.
áoslo1s
4áosl4a4s.
áosla1s
4adl4e4s.
adle1s
4adl4a4s.
adla1s
4adl2o4s.
adlo1s
4a4r.
4a4rm4e.
a2r1m
4á4rmel4e.
á2r1m
árme1l
4á4rmel4o.
4á4r3mel4a.
4á4r3mel4e4s.
ármele1s
4á4r3mel2o4s.
ármelo1s
4á4r3mel4a4s.
ármela1s
4a4r3t4e.
a2r1t
4á4r3tel4e.
á2r1t
árte1l
4á4r3tel4o.
4á4r3tel4a.
4á4r3tel4e4s.
ártele1s
4á4r3tel2o4s.
ártelo1s
4á4r3tel4a4s.
ártela1s
4a4r3l4e.
a2r1l
4a4r3l4a.
4a4r3l4o.
4a4r3s4e.
a2r1s
4á4r3sel4e.
á2r1s
árse1l
4á4r3sel4o.
4á4r3sel4a.
4á4r3sel4e4s.
ársele1s
4á4r3sel2o4s.
árselo1s
4á4r3sel4a4s.
ársela1s
4a4r3n2o4s.
a2r1n
arno1s
4á4r3nosl4e.
á2r1n
árno1s
árno2s1l
4á4r3nosl4o.
4á4r3nosl4a.
4á4r3nosl4e4s.
árnosle1s
4á4r3nosl2o4s.
árnoslo1s
4á4r3nosl4a4s.
árnosla1s
4a3r2o4s.
aro1s
4árosl4e.
áro1s
áro2s1l
4árosl4o.
4árosl4a.
4árosl4e4s.
árosle1s
4árosl2o4s.
ároslo1s
4árosl4a4s.
árosla1s
4a4r3l4e4s.
arle1s
4a4r3l4a4s.
arla1s
4a4r3l2o4s.
arlo1s
4a3d4o.
4a3d4a.
4a3d2o4s.
ado1s
4a3d4a4s.
ada1s
e5r4a3d4o.
e5r4a3d4a.
e5r4a3d2o4s.
erado1s
e5r4a3d4a4s.
erada1s
4ando
a2n1d
4ándol4e.
á1n
á2n1d
ándo1l
4ándol4o.
4ándol4a.
4ándol4e4s.
ándole1s
4ándol2o4s.
ándolo1s
4ándol4a4s.
ándola1s
4ándon2o4s.
ándo1n
ándono1s
4ándo2o4s.
ándoo1s
4ándom4e.
ándo1m
4ándomel4o.
ándome1l
4ándomel4a.
4ándomel4e.
4ándomel2o4s.
ándomelo1s
4ándomel4a4s.
ándomela1s
4ándomel4e4s.
ándomele1s
4ándot4e.
ándo1t
4ándotem4e.
ándote1m
4ándotel4o.
ándote1l
4ándotel4a.
4ándotel4e.
4ándotel2o4s.
ándotelo1s
4ándotel4a4s.
ándotela1s
4ándotel4e4s.
ándotele1s
4ándoten2o4s.
ándote1n
ándoteno1s
4ándos4e.
ándo1s
4ándosem4e.
ándose1m
4ándosel4o.
ándose1l
4ándosel4a.
4ándosel4e.
4ándosel2o4s.
ándoselo1s
4ándosel4a4s.
ándosela1s
4ándosel4e4s.
ándosele1s
4ándosen2o4s.
ándose1n
ándoseno1s
4a3do4r.
ado1r
4a3dor4a.
4a3dor4e4s.
adore1s
4a3dor4a4s.
adora1s
e5r4a3do4r.
e1rado1r
e5r4a3dor4a.
e5r4a3dor4e4s.
eradore1s
e5r4a3dor4a4s.
eradora1s
acto1h
a1c
a2c1t
acto1a2
acto1e2
acto1i2
acto1o2
acto1u2
acto1á2
acto1é2
acto1í2
acto1ó2
acto1ú2
afro1h
a1f
af2r
afro1a2
afro1e2
afro1i2
afro1o2
afro1u2
afro1á2
afro1é2
afro1í2
afro1ó2
afro1ú2
.a2
.an2a2
.a1n
.an2e2
.an2i2
.an2o2
.an2u2
.an2á2
.an2é2
.an2í2
.an2ó2
.an2ú.
ana3lí
ana1l
.aná3li
.aná1l
.ana3li
.ana1l
.an3aero
.anae1r
.an3e2pig2r
.ane1p
.anepi1g
.ane3xa
.ane1x
.ane3xá
.ane3xe
.ane3xé
.ane3xio
.ane3xió
.a2n3h
.ani3m
.ani3ma1d
.ani3má1d
.ani3da1r
.ani1d
.ani3l4l
.ani1l
.ani1ñ
.ani3q
.an3i2so
.ani1s
.an3i2só
.ani3ve1l
.ani1v
.ano5che
.ano1c
.anoc4h
.a1no5di1n
.ano1d
.ano5ma1l2
.ano1m
.ano5na1d
.a1no1n
.anó3ni1m
.anó1n
.anó5ma1l2
.anó1m
.ano5ni1m
.ano5ta
.ano1t
.ano3tá
.anua3l
.anua4l1m
.anu3b2l
.anu1b
.anu3da
.anu1d
.anu3l
asu3b2
aero1h
ae1r
aero1a2
aero1e2
aero1i2
aero1o2
aero1u2
aero1á2
aero1é2
aero1í2
aero1ó2
aero1ú2
anfi1h
a2n1f
anfi1a2
anfi1e2
anfi1i2
anfi1o2
anfi1u2
anfi1á2
anfi1é2
anfi1í2
anfi1ó2
anfi1ú2
anglo1h
a2n1g
ang2l
anglo1a2
anglo1e2
anglo1i2
anglo1o2
anglo1u2
anglo1á2
anglo1é2
anglo1í2
anglo1ó2
anglo1ú2
ante1h
a2n1t
ante1a2
ante1e2
ante1i2
ante1o2
ante1u2
ante1á2
ante1é2
ante1í2
ante1ó2
ante1ú2
.ante2o3je
.ante1o2
.a2n1t
.anteo1j
acante2
aca1n
aca2n1t
4ísm4o.
í2s1m
4ísm2o4s.
ísmo1s
4íst4a.
4íst4a4s.
ísta1s
4íst2i3c4o.
ísti1c
4íst2i3c2o4s.
ístico1s
4íst2i3c4a.
4íst2i3c4a4s.
ística1s
t4e4o3n4e4s.
teo1n
teone1s
mante4a2
ma1n
ma2n1t
e4a3miento
ea1m
eamie1n
eamie2n1t
.anti1h
.anti1a2
.anti1e2
.anti1i2
.anti1o2
.anti1u2
.anti1á2
.anti1é2
.anti1í2
.anti1ó2
.anti1ú2
ti2o3qu
tio1q
ti2o3co
tio1c
archi1h
a2r1c
arc4h
archi1a2
archi1e2
archi1i2
archi1o2
archi1u2
archi1á2
archi1é2
archi1í2
archi1ó2
archi1ú2
auto1h
au1t
auto1a2
auto1e2
auto1i2
auto1o2
auto1u2
auto1á2
auto1é2
auto1í2
auto1ó2
auto1ú2
biblio1h
1bi1b
bib2l
biblio1a2
biblio1e2
biblio1i2
biblio1o2
biblio1u2
biblio1á2
biblio1é2
biblio1í2
biblio1ó2
biblio1ú2
bio1h
bio1a2
bio1e2
bio1i2
bio1o2
bio1u2
bio1á2
bio1é2
bio1í2
bio1ó2
bio1ú2
bi1u2ní
biu1n
cardio1h
ca1r
ca2r1d
cardio1a2
cardio1e2
cardio1i2
cardio1o2
cardio1u2
cardio1á2
cardio1é2
cardio1í2
cardio1ó2
cardio1ú2
cefalo1h
ce1f
cefa1l
cefalo1a2
cefalo1e2
cefalo1i2
cefalo1o2
cefalo1u2
cefalo1á2
cefalo1é2
cefalo1í2
cefalo1ó2
cefalo1ú2
centi1h
ce1n
ce2n1t
centi1a2
centi1e2
centi1i2
centi1o2
centi1u2
centi1á2
centi1é2
centi1í2
centi1ó2
centi1ú2
centi5área
centiá1r
ciclo1h
1ci1c
cic2l
ciclo1a2
ciclo1e2
ciclo1i2
ciclo1o2
ciclo1u2
ciclo1á2
ciclo1é2
ciclo1í2
ciclo1ó2
ciclo1ú2
o4i3de4a.
oi1d
o4i3de4a4s.
oidea1s
o4i3d2a4l.
oida1l
o4i3d2al4e4s.
oidale1s
4o2i3d4e.
4o2i3d4e4s.
oide1s
4i2d2a4l.
i1d
ida1l
4i2d2al4e4s.
idale1s
4i3de4o.
4i3d4e2o4s.
ideo1s
cito1h
ci1t
cito1a2
cito1e2
cito1i2
cito1o2
cito1u2
cito1á2
cito1é2
cito1í2
cito1ó2
cito1ú2
3c2neo1r
cnico1h
1cni1c
cnico1a2
cnico1e2
cnico1i2
cnico1o2
cnico1u2
cnico1á2
cnico1é2
cnico1í2
cnico1ó2
cnico1ú2
.co2a2
.co2e2
.co2i2
.co3o4
.co2u2
.co2á2
.co2é2
.co2í2
.co2ó2
.co2ú2
co4á3gu1l
coá1g
co4acci
1coa1c
1coa2c1c
co4acti
coa2c1t
co4adju
coa1d
coa2d1j
co4a3du1n
co4adyu
coa2d1y
co3age1n
coa1g
co4a3gu1l
1co4a3li1c
coa1l
1co4apta1c
coa1p
coa2p1t
co4a2r1t
coa1r
co4á2r1t
coá1r
1co4e3fi1c
coe1f
1co4e2r1c
coe1r
co4e2r1z
co4e3tá
coe1t
co3exi1s
coe1x
co4imb2r
coi1m
coi2m1b
co4inci
coi1n
1coi2n1c
co4i3to
coi1t
con1imb2r
co3n4imbri
co1n
coni1m
coni2m1b
co4o3pe1r
coo1p
co4o3pé1r
co4o2p1t
co4o2r1d
coo1r
con1u2r1b
conu1r
cripto1h
cri1p
cri2p1t
cripto1a2
cripto1e2
cripto1i2
cripto1o2
cripto1u2
cripto1á2
cripto1é2
cripto1í2
cripto1ó2
cripto1ú2
crono1h
cro1n
crono1a2
crono1e2
crono1i2
crono1o2
crono1u2
crono1á2
crono1é2
crono1í2
crono1ó2
crono1ú2
contra1h
co2n1t
cont2r
contra1a2
contra1e2
contra1i2
contra1o2
contra1u2
contra1á2
contra1é2
contra1í2
contra1ó2
contra1ú2
deca1h
de1c
deca1a2
deca1e2
deca1i2
deca1o2
deca1u2
deca1á2
deca1é2
deca1í2
deca1ó2
deca1ú2
4e3dr4o.
ed2r
4e3dr2o4s.
edro1s
4é3dr2i3c4o.
éd2r
édri1c
4é3dr2i3c2o4s.
édrico1s
4é3dr2i3c4a.
4é3dr2i3c4a4s.
édrica1s
.de2sa2
.de1s
.de2se2
.de2si2
.de2so2
.de2su2
.de2sá2
.de2sé2
.de2sí2
.de2só2
.de2sú2
deca2i3mie2n1t
decai1m
decaimie1n
decimo1
deci1m
3s4a.
3s4a4s.
sa1s
de2s3órde
de1s
desó1r
1desó2r1d
de2s3orde
deso1r
1deso2r1d
de2s3aba2s1t
desa1b
desaba1s
de2s3abo1l4l
desabo1l
de2s3aboto
desabo1t
de2s3ab2r
1desa3bri1d
de2s3abroc4h
desabro1c
de2s3acei1t
desa1c
de2s3acele1r
desace1l
desa3ce2r1t
desace1r
desa3cie2r1t
desacie1r
de2s3acoba1r
desaco1b
1de2s3acomo1d
desaco1m
de2s3aco2m1p
de2s3aco2n1s
desaco1n
de2s3acop2l
desaco1p
de2s3aco1r2r
desaco1r
de2s3acostu1m
desaco1s
desaco2s1t
de2s3aco1t
desa3crali1z
desac2r
desacra1l
de2s3acredi1t
desacre1d
de2s3acti1v
desa2c1t
de2s3acua2r1t
desacua1r
de2s3adere1z
1desa1d
desade1r
1de2s3a1deu1d
de2s3ado1ra1r
desado1r
de2s3adorme1c
desado2r1m
de2s3ado2r1n
de2s3adve2r1t
desa2d1v
desadve1r
de2s3afe1r2r
desa1f
desafe1r
de2s3afi1c
de2s3afi1l
de2s3afi1n
de2s3afo1r
desa3gú
desa1g
desa3ga1r2r
desaga1r
de2s3agraci
desag2r
desagra1c
1de2s3agra1d
de2s3agravi
desagra1v
de2s3a1gre1g
de2s3agru1p
de2s3agu
desa3guisado
desagui1s
desaguisa1d
de2s3ahe1r2r
desahe1r
de2s3ahi1j
de2s3aju2s1t
desa1j
desaju1s
de2s3alaga1r
desa1l
desala1g
de2s3ale2n1t
desale1n
de2s3alfo1m
desa2l1f
de2s3alfo1r
de2s3ali1ñ
desa3li1n
de2s3alie1n
de2s3aline
desa3li1v
de2s3a2l1m
1de2s3almi1d
de2s3alo1j
de2s3a1lqui1l
desa2l1q
de2s3alte1r
desa2l1t
de2s3alumb2r
desalu1m
desalu2m1b
desa3ma1r2r
desa1m
desama1r
desa3mob2l
desamo1b
1de2s3amo2l1d
desamo1l
de2s3amo2r1t
desamo1r
de2s3amueb2l
desamue1b
de2s3ampa
desa2m1p
1de2s3a2n1d
desa1n
de2s3ange1l
desa2n1g
de3sang2r
1de2s3ani1d
de2s3ani1m
de2s3aní1m
1de2s3anu1d
desa3pa1ñ
desa1p
desa3paci1b
desapa1c
de2s3apad2r
desapa1d
de2s3apare
desapa1r
de2s3apare1c
de2s3apari1c
de2s3ape1g
de2s3aperci1b
desape1r
desape2r1c
de2s3ape1s
de2s3apli1c
desap2l
de2s3apo1li1l4l
desapo1l
desapoli1l
de2s3apo1y
1de2s3apre2n1d
desap2r
desapre1n
de2s3apre1t
de2s3aprie1t
de2s3apro1b
de2s3apropi
desapro1p
de2s3aprovec4h
desapro1v
desaprove1c
de2s3arbo1l
desa1r
desa2r1b
de2s3are1n
de2s3a2r1m
des4arme
de2s3arrai1g
desa1r2r
de2s3arreg2l
desarre1g
1de2s3arre2n1d
desarre1n
de2s3arri1m
desa3rro1l4l
desarro1l
de2s3arro1p
de2s3arru1g
de2s3articu1l
desa2r1t
desarti1c
de2s3ase2n1t
de1sa1s
desase1n
de2s3asi2s1t
desasi1s
de2s3a2s1n
desa3sose1g
de1sa1so1s
desa3sosie1g
de2s3ate2n1c
desa1t
desate1n
1de2s3ate2n1d
1de2s3atie2n1d
desatie1n
de2s3a1te2n1t
desa3ti1n
de2s3ato2r1n
desato1r
de2s3atra2n1c
desat2r
desatra1n
de2s3auto1r
desau1t
de2s3avi1s
desa1v
desa3yu1n
desa1y
desa3zó1n
desa1z
desa3zo1n
de2s3emba1l
dese1m
dese2m1b
de2s3embá1l
de2s3emba1r
de2s3embá1r
de2s3emba2r1g
de2s3embo2l1s
desembo1l
de2s3embo1r2r
desembo1r
de2s3embo2s1c
desembo1s
de2s3embo1t
de2s3embra1g
desemb2r
de2s3embrá1g
de2s3embrave
desembra1v
de2s3embráve
desembrá1v
de2s3embro1l4l
desembro1l
de2s3embró1l4l
desembró1l
de2s3embru1j
de2s3embrú1j
de3seme1j
de2s3empa1ñ
dese2m1p
de2s3empá1ñ
de2s3empa1c
de2s3empaque1t
desempa1q
de2s3empaqué1t
de2s3empare1j
desempa1r
de2s3emparé1j
de2s3empare2n1t
desempare1n
de2s3empa1t
de2s3empé
de2s3emped2r
desempe1d
de2s3empe1g
de2s3empeo1r
de2s3empere1z
desempe1r
de2s3empe2r1n
de2s3emple
desemp2l
de2s3empo2l1v
desempo1l
de2s3empot2r
desempo1t
de2s3empo1z
de2s3ena1m
dese1n
de2s3enca1b
dese2n1c
1de2s3enca1d
de2s3enca1j
de2s3encá1j
de2s3enca1l4l
desenca1l
de2s3encá1l4l
desencá1l
de2s3enca1m
de3senca2n1t
desenca1n
de2s3enca1p
de2s3enca1r
de2s3encá1r
de2s3enc4h
de2s3enc2l
de2s3enco
de2s3enc2r
de2s3encu
1de2s3e2n1d
1de3senfa1d
dese2n1f
1de3senfá1d
de2s3enfi
de2s3enfo
de2s3enfó
de3se1nfre1n
desenf2r
1de2s3enfu2n1d
desenfu1n
de2s3enfu1r
de3senga1ñ
dese2n1g
de3sengá1ñ
de2s3enganc4h
desenga1n
desenga2n1c
de2s3enga1r
de2s3enga1s
de2s3engo1m
de2s3engo1z
de2s3engra
deseng2r
de2s3enheb2r
dese2n1h
desenhe1b
de2s3e2n1j
1de2s3enla1d
dese2n1l
de2s3enla1z
de2s3enlo
de2s3e2n1m
de2s3e2n1r
de2s3e2n1s
de2s3enta
dese2n1t
1de3sente2n1d
desente1n
de3se1ntie1n
de3se1ntié1n
de2s3ente1r
de2s3entie1r
de2s3entié1r
de2s3ento
de2s3ent2r
de2s3entu
de2s3e1nvai1n
dese2n1v
de3senvolvi1m
desenvo1l
desen1vo2l1v
de3seo
de2s3e1q
de3s4erci
dese1r
dese2r1c
de3s4e2r1t
de3s4é2r1t
desé1r
de2s3espa
de1se1s
dese2s1p
de3sespe1r
de3sespera1c
de2s3espera2n1z
desespera1n
de2s3estabi1l
dese2s1t
desesta1b
de2s3esti1m
de3side1r
1desi1d
de3sidia
de3sidio
de3sie2r1t
desie1r
de3si2g1n
desi1g
de3sigua1l
de3silusi
desi1l
desilu1s
de2s3imagi1n
desi1m
desima1g
de2s3ima1n
de2s3impo1n
desi2m1p
de2s3impre1s
desimp2r
de2s3ince2n1t
desi1n
desi2n1c
desince1n
de2s3i1ncli1n
desinc2l
de2s3inco2r1p
desinco1r
de2s3incru2s1t
desinc2r
desincru1s
de3sine2n1c
desine1n
de3sinfe1c
desi2n1f
de3su3da1r
1desu1d
de3su3da1s
de3su3da1n
de2s3inf2l
de2s3infla1m
de2s3info2r1m
desinfo1r
de2s3inhi1b
desi2n1h
de2s3inse2c1t
de1si2n1s
desinse1c
de2s3insta1l
desin2s1t
ini3ci
i1n
ini1c
iní3ci
iní1c
de3s4integ2r
desi2n1t
desinte1g
de3s4inte1r
de2s3into1x
de2s3inve1r
desi2n1v
de3siste1n
desi1s
desi2s1t
de3isti
dei1s
dei2s1t
de2s3obede1c
deso1b
desobe1d
de2s3obli1g
desob2l
de2s3obst2r
de1so2b1s
desob2s1t
de3socu1p
deso1c
de2s3odo1r
deso1d
de3sola1c
deso1l
1de3sola1d
de3so1l4l
de2s3ore1j
de2s3orie2n1t
desorie1n
de3sorti1j
deso2r1t
de2s3organi
deso2r1g
desorga1n
de3sue1l4l
desue1l
de3sonce
deso1n
deso2n1c
de2s3ovi
deso1v
de2s3oxi
deso1x
de2s3oye
deso1y
de2s3oyé
de3s4ubsta1n
desu1b
de1su2b1s
desub2s1t
de3s4usta1n
desu1s
desu2s1t
de3s4ose1g
deso1s
de2s3ub4i1c
de2s3uni1r
desu1n
de2s3unie1r
de2s3uni1m
.dieci1o2
.die1c
dodeca1h
1do1d
dode1c
dodeca1a2
dodeca1e2
dodeca1i2
dodeca1o2
dodeca1u2
dodeca1á2
dodeca1é2
dodeca1í2
dodeca1ó2
dodeca1ú2
ecano1h
e1c
eca1n
ecano1a2
ecano1e2
ecano1i2
ecano1o2
ecano1u2
ecano1á2
ecano1é2
ecano1í2
ecano1ó2
ecano1ú2
eco1h
eco1a2
eco1e2
eco1i2
eco1o2
eco1u2
eco1á2
eco1é2
eco1í2
eco1ó2
eco1ú2
ectro1h
e2c1t
ect2r
ectro1a2
ectro1e2
ectro1i2
ectro1o2
ectro1u2
ectro1á2
ectro1é2
ectro1í2
ectro1ó2
ectro1ú2
.en2a2
.e1n
.en2e2
.en2i2
.en2o2
.en2u2
.en2á2
.en2é2
.en2í2
.en2ó2
.en2ú2
.ene3mi2s1t
.ene1m
.enemi1s
.ene3mí2s1t
.enemí1s
.eno3ja1r
.eno1j
.enu3mera
.enu1m
.enume1r
.enu3merá
.enu3mere
4o3lóg2i3c4o.
o1l
oló1g
ológi1c
4o3lóg2i3c4a.
4o3lóg2i3c2o4s.
ológico1s
4o3lóg2i3c4a4s.
ológica1s
4o3lógica3ment4e.
ológica1m
ológicame1n
ológicame2n1t
4o3log4í4a.
o3logía
olo1g
4o3log4í4a4s.
ología1s
4ó3log4o.
ó1l
ólo1g
4ó3log4a.
4ó3log2o4s.
ólogo1s
4ó3log4a4s.
óloga1s
endo1h
e2n1d
endo1a2
endo1e2
endo1i2
endo1o2
endo1u2
endo1á2
endo1é2
endo1í2
endo1ó2
endo1ú2
ento1h
e2n1t
ento1a2
ento1e2
ento1i2
ento1o2
ento1u2
ento1á2
ento1é2
ento1í2
ento1ó2
ento1ú2
4emboca
e2m1b
embo1c
entre1h
ent2r
entre1a2
entre1e2
entre1i2
entre1o2
entre1u2
entre1á2
entre1é2
entre1í2
entre1ó2
entre1ú2
euco1h
eu1c
euco1a2
euco1e2
euco1i2
euco1o2
euco1u2
euco1á2
euco1é2
euco1í2
euco1ó2
euco1ú2
euro1h
eu1r
euro1a2
euro1e2
euro1i2
euro1o2
euro1u2
euro1á2
euro1é2
euro1í2
euro1ó2
euro1ú2
extra1h
e1x
e2x1t
ext2r
extra1a2
extra1e2
extra1i2
extra1o2
extra1u2
extra1á2
extra1é2
extra1í2
extra1ó2
extra1ú2
u4teri
u1t
ute1r
.cau5t
.deu5t
fono1h
fo1n
fono1a2
fono1e2
fono1i2
fono1o2
fono1u2
fono1á2
fono1é2
fono1í2
fono1ó2
fono1ú2
foto1h
fo1t
foto1a2
foto1e2
foto1i2
foto1o2
foto1u2
foto1á2
foto1é2
foto1í2
foto1ó2
foto1ú2
gastro1h
ga1s
ga2s1t
gast2r
gastro1a2
gastro1e2
gastro1i2
gastro1o2
gastro1u2
gastro1á2
gastro1é2
gastro1í2
gastro1ó2
gastro1ú2
geo1h
geo1a2
geo1e2
geo1i2
geo1o2
geo1u2
geo1á2
geo1é2
geo1í2
geo1ó2
geo1ú2
gluco1h
glu1c
gluco1a2
gluco1e2
gluco1i2
gluco1o2
gluco1u2
gluco1á2
gluco1é2
gluco1í2
gluco1ó2
gluco1ú2
hecto1h
he1c
he2c1t
hecto1a2
hecto1e2
hecto1i2
hecto1o2
hecto1u2
hecto1á2
hecto1é2
hecto1í2
hecto1ó2
hecto1ú2
helio1h
he1l
helio1a2
helio1e2
helio1i2
helio1o2
helio1u2
helio1á2
helio1é2
helio1í2
helio1ó2
helio1ú2
hemato1h
he1m
hema1t
hemato1a2
hemato1e2
hemato1i2
hemato1o2
hemato1u2
hemato1á2
hemato1é2
hemato1í2
hemato1ó2
hemato1ú2
hemi1h
hemi1a2
hemi1e2
hemi1i2
hemi2o2
hemi1u2
hemi1á2
hemi1é2
hemi1í2
hemi1ó2
hemi1ú2
hemo1h
hemo1a2
hemo1e2
hemo1i2
hemo1o2
hemo1u2
hemo1á2
hemo1é2
hemo1í2
hemo1ó2
hemo1ú2
2a4l.
a1l
2al4e4s.
ale1s
hexa1h
he1x
hexa1a2
hexa1e2
hexa1i2
hexa1o2
hexa1u2
hexa1á2
hexa1é2
hexa1í2
hexa1ó2
hexa1ú2
hidro1h
hi1d
hid2r
hidro1a2
hidro1e2
hidro1i2
hidro1o2
hidro1u2
hidro1á2
hidro1é2
hidro1í2
hidro1ó2
hidro1ú2
hipe2r3r
hi1p
hipe1r
hipe2r1a2
hipe2r1e2
hipe2r1i2
hipe2r1o2
hipe2r1u2
hipe2r1á2
hipe2r1é2
hipe2r1í2
hipe2r1ó2
hipe2r1ú2
pe3r4e3mia
pe1r
pere1m
histo1h
hi1s
hi2s1t
histo1a2
histo1e2
histo1i2
histo1o2
histo1u2
histo1á2
histo1é2
histo1í2
histo1ó2
histo1ú2
homo1h
ho1m
homo1a2
homo1e2
homo1i2
homo1o2
homo1u2
homo1á2
homo1é2
homo1í2
homo1ó2
homo1ú2
icono1h
i1c
ico1n
icono1a2
icono1e2
icono1i2
icono1o2
icono1u2
icono1á2
icono1é2
icono1í2
icono1ó2
icono1ú2
.i2n2a2
.i1n
.i2n2e2
.i2n2i2
.i2n2o2
.i2n2u2
.i2n2á2
.i2n2é2
.i2n2í2
.i2n2ó2
.i2n2ú2
.in3abo2r1d
.ina1b
.inabo1r
.in3aba2r1c
.inaba1r
.in3ace2n1t
.ina1c
.inace1n
.in3agua2n1t
.ina1g
.inagua1n
.in3ada2p1t
.ina1d
.inada1p
.ina3movi1b
.ina1m
.inamo1v
.in3anali1z
.i1na1n
.inana1l
.ina3ni1c
.in3ani1m
.iná3ni1m
.iná1n
.in3ape1l
.ina1p
.in3apli1c
.inap2l
.in3apre2n1s
.inap2r
.inapre1n
.in3apreci
.inapre1c
.in3arru1g
.ina1r
.ina1r2r
.in3asi2s1t
.ina1s
.inasi1s
.iné3di1t
.iné1d
.in3efi1c
.ine1f
.in3efici
.in3eludi
.ine1l
.inelu1d
.ine3na1r2r
.ine1n
.inena1r
ini3cia
iní3cia
ini3ciá
ini3cie
.re2i2
.rei3na
.rei1n
re3ini3cia
reini3ci
rei1n
reini1c
re3iní3cia
reiní3ci
reiní1c
re3ini3ciá
re3ini3cie
.ini3cuo
.ini1c
.ini3cua
.ino3cuo
.ino1c
.ino3cua
.ino3cula
.inocu1l
.ino3culá
.ino3cule
.inú3ti1l
.inú1t
.inu3tili1z
.inu1t
.inuti1l
infra1h
i2n1f
inf2r
infra1a2
infra1e2
infra1i2
infra1o2
infra1u2
infra1á2
infra1é2
infra1í2
infra1ó2
infra1ú2
.inte2r3r
.i2n1t
.inte1r
.inte2r1a2
.inte2r1e2
.inte2r1i2
.inte2r1o2
.inte2r1u2
.inte2r1á2
.inte2r1é2
.inte2r1í2
.inte2r1ó2
.inte2r1ú2
.in3ter2e3sa
.intere1s
.in3ter2e3se
.in3ter2e3so
.in3ter2e3sá
.in3ter2e3sé
.in3ter2e3só
.de3s4in3ter2e3sa
.de3s4inte1r
.desi1n
.desi2n1t
.de1sintere1s
.de3s4in3ter2e3se
.de3s4in3ter2e3so
.de3s4in3ter2e3sá
.de3s4in3ter2e3sé
.de3s4in3ter2e3só
3te3ri3n
te1r
4te4r5i4nsu
teri2n1s
.in3te3r4ro1g
.in3te3r4ru2p1c
.interru1p
.in3te3r4ru2p1t
.in3te3r4ru2m1p
.interru1m
intra1h
i2n1t
int2r
intra1a2
intra1e2
intra1i2
intra1o2
intra1u2
intra1á2
intra1é2
intra1í2
intra1ó2
intra1ú2
iso1h
i1s
iso1a2
iso1e2
iso1i2
iso1o2
iso1u2
iso1á2
iso1é2
iso1í2
iso1ó2
iso1ú2
kilo1h
ki1l
kilo1a2
kilo1e2
kilo1i2
kilo1o2
kilo1u2
kilo1á2
kilo1é2
kilo1í2
kilo1ó2
kilo1ú2
macro1h
ma1c
mac2r
macro1a2
macro1e2
macro1i2
macro1o2
macro1u2
macro1á2
macro1é2
macro1í2
macro1ó2
macro1ú2
ma1l2
ma4l3h
.ma4l3e4du
.ma1l2
.male1d
ma2l3b
ma2l3c
ma2l3d
ma2l3f
ma2l3g
1ma2l3m
ma2l3p
ma2l3q
ma2l3s
ma2l3t
ma2l3v
bie1n2
bie2n3h
bie2n3v
bie2n3q
bie2n3m
bie2n3t
b4ien3d4o.
bie2n1d
.su2b2i2
.su3b4ie1n2
.su1b
b4ien3d4a4s.
bienda1s
maxi1h
ma1x
maxi1a2
maxi1e2
maxi1i2
maxi1o2
maxi1u2
maxi1á2
maxi1é2
maxi1í2
maxi1ó2
maxi1ú2
megalo1h
me1g
mega1l
megalo1a2
megalo1e2
megalo1i2
megalo1o2
megalo1u2
megalo1á2
megalo1é2
megalo1í2
megalo1ó2
megalo1ú2
mega1h
mega1a2
mega1e2
mega1i2
mega1o2
mega1u2
mega1á2
mega1é2
mega1í2
mega1ó2
mega1ú2
melano1h
me1l
mela1n
melano1a2
melano1e2
melano1i2
melano1o2
melano1u2
melano1á2
melano1é2
melano1í2
melano1ó2
melano1ú2
micro1h
mi1c
mic2r
micro1a2
micro1e2
micro1i2
micro1o2
micro1u2
micro1á2
micro1é2
micro1í2
micro1ó2
micro1ú2
mili1h
mi1l
mili1a2
mili1e2
mili1i2
mili1o2
mili1u2
mili1á2
mili1é2
mili1í2
mili1ó2
mili1ú2
familia3ri
famili6a2
fa1m
famili4a1r
fami1l
i4a5r4e4s.
ia1r
iare1s
amili6a2
ami1l
a3rio
li5área
liá1r
mini1h
mi1n
mini1a2
mini1e2
mini1i2
mini1o2
mini1u2
mini1á2
mini1é2
mini1í2
mini1ó2
mini1ú2
2o4s.
o1s
2o3s4o.
2o3s2o4s.
oso1s
2o3s4a.
2o3s4a4s.
osa1s
2o3sa3ment4e.
osa1m
osame1n
osame2n1t
mini4a5tu1r
minia1t
multi1h
mu1l
mu2l1t
multi1a2
multi1e2
multi1i2
multi1o2
multi1u2
multi1á2
multi1é2
multi1í2
multi1ó2
multi1ú2
miria1h
mi1r
miria1a2
miria1e2
miria1i2
miria1o2
miria1u2
miria1á2
miria1é2
miria1í2
miria1ó2
miria1ú2
mono1h
mo1n
mono1a2
mono1e2
mono1i2
mono1o2
mono1u2
mono1á2
mono1é2
mono1í2
mono1ó2
mono1ú2
2i3c4o.
2i3c2o4s.
ico1s
2i3c4a.
2i3c4a4s.
ica1s
namo1h
na1m
namo1a2
namo1e2
namo1i2
namo1o2
namo1u2
namo1á2
namo1é2
namo1í2
namo1ó2
namo1ú2
necro1h
ne1c
nec2r
necro1a2
necro1e2
necro1i2
necro1o2
necro1u2
necro1á2
necro1é2
necro1í2
necro1ó2
necro1ú2
neo1h
neo1a2
neo1e2
neo1i2
neo1o2
neo1u2
neo1á2
neo1é2
neo1í2
neo1ó2
neo1ú2
neto1h
ne1t
neto1a2
neto1e2
neto1i2
neto1o2
neto1u2
neto1á2
neto1é2
neto1í2
neto1ó2
neto1ú2
norte1h
no1r
no2r1t
norte1a2
norte1e2
norte1i2
norte1o2
norte1u2
norte1á2
norte1é2
norte1í2
norte1ó2
norte1ú2
octo1h
o1c
o2c1t
octo1a2
octo1e2
octo1i2
octo1o2
octo1u2
octo1á2
octo1é2
octo1í2
octo1ó2
octo1ú2
octa1h
octa1a2
octa1e2
octa1i2
octa1o2
octa1u2
octa1á2
octa1é2
octa1í2
octa1ó2
octa1ú2
oligo1h
oli1g
oligo1a2
oligo1e2
oligo1i2
oligo1o2
oligo1u2
oligo1á2
oligo1é2
oligo1í2
oligo1ó2
oligo1ú2
omni1h
o1m
o2m1n
omni1a2
omni1e2
omni1i2
omni1o2
omni1u2
omni1á2
omni1é2
omni1í2
omni1ó2
omni1ú2
i4o.
i2o4s.
io1s
paleo1h
pa1l
paleo1a2
paleo1e2
paleo1i2
paleo1o2
paleo1u2
paleo1á2
paleo1é2
paleo1í2
paleo1ó2
paleo1ú2
para1h
pa1r
para1a2
para1e2
para1i2
para1o2
para1u2
para1á2
para1é2
para1í2
para1ó2
para1ú2
p4a3ra2i4s.
parai1s
aí5s4o.
aí1s
aí5s2o4s.
aíso1s
penta1h
pe1n
pe2n1t
penta1a2
penta1e2
penta1i2
penta1o2
penta1u2
penta1á2
penta1é2
penta1í2
penta1ó2
penta1ú2
piezo1h
pie1z
piezo1a2
piezo1e2
piezo1i2
piezo1o2
piezo1u2
piezo1á2
piezo1é2
piezo1í2
piezo1ó2
piezo1ú2
pluri1h
plu1r
pluri1a2
pluri1e2
pluri1i2
pluri1o2
pluri1u2
pluri1á2
pluri1é2
pluri1í2
pluri1ó2
pluri1ú2
poli1h
po1l
poli1a2
poli1e2
poli1i2
poli1o2
poli1u2
poli1á2
poli1é2
poli1í2
poli1ó2
poli1ú2
poli4u3r
poli4o5mie
polio1m
poli4a2r1q
polia1r
poli4á2r1q
poliá1r
poli4éste
poli4é1s
polié2s1t
poli4and2r
polia1n
polia2n1d
poli4ante1a2
polia2n1t
expoli4
e2x1p
expo1l
.pos2t2a2
.po1s
.po2s1t
.pos2t2e2
.pos2t2i2
.pos2t2o2
.pos2t2u2
.pos2t2á2
.pos2t2é2
.pos2t2í2
.pos2t2ó2
.pos2t2ú2
.pos3ti1n
.pos3tí1n
pos3t4a.
po1s
po2s1t
pos3t4a4s.
posta1s
s3t4e.
s3t4e4s.
ste1s
s3t2a4l.
sta1l
s3t2a3l4e4s.
stale1s
s3ti3ll4a.
sti1l
sti1l4l
s3ti3ll4a4s.
stilla1s
s3ti3ll4ó4n.
stilló1n
s3ti3ll4on4e4s.
stillo1n
stillone1s
.pos3tó3ni
.postó1n
.pos3te2r1g
.poste1r
.pos3te3ri
.pos3ti3go
.posti1g
.pos3ti3la
.posti1l
.pos3ti3ne
.pos3ti3za
.posti1z
.pos3ti3zo
.pos3tu3ra
.postu1r
s3to4r.
sto1r
s3tor4a.
s3tor4a4s.
stora1s
s3tor4e4s.
store1s
.pos3tu3la
.postu1l
.pos3tu3lá
.pos3tu3le
.pos3tu3lé
.post3ele1c
.poste1l
.post3imp2r
.posti1m
.posti2m1p
.post3i2n1d
.post3ope
.posto1p
.post3re1v
.post2r
.pre2a2
.p2r
.pre2e2
.pre2i2
.pre2o2
.pre2u2
.pre2h2
.pre2á2
.pre2é2
.pre2í2
.pre2ó2
.pre2ú2
pre3eli1j
pree1l
pre3eli1g
pre3exi1s
pree1x
pre3emi1n
pree1m
1preo3cu1p
preo1c
1preo2cú1p
pre3olí
preo1l
pre3opi1n
preo1p
.pro2a2
.pro2e2
.pro2i2
.pro2o2
.pro2u2
.pro2h2
.pro2á2
.pro2é2
.pro2í2
.pro2ó2
.pro2ú2
proto1h
pro1t
proto1a2
proto1e2
proto1i2
proto1o2
proto1u2
proto1á2
proto1é2
proto1í2
proto1ó2
proto1ú2
radio1h
ra1d
radio1a2
radio1e2
radio1i2
radio1o2
radio1u2
radio1á2
radio1é2
radio1í2
radio1ó2
radio1ú2
ranco1h
ra1n
ra2n1c
ranco1a2
ranco1e2
ranco1i2
ranco1o2
ranco1u2
ranco1á2
ranco1é2
ranco1í2
ranco1ó2
ranco1ú2
.re2a2
.re3e4
.re2o2
.re2u2
.re2á2
.re2é2
.re2í2
.re2ó2
.re2ú2
ea3ci4o.
ea1c
ea3ci2o4s.
eacio1s
ea3ci4a.
ea3ci4a4s.
eacia1s
.re3ab2r
.rea1b
.re3áb2r
.reá1b
.re3afi2r1m
.rea1f
.reafi1r
.re3afí2r1m
.reafí1r
.re3aju2s1t
.rea1j
.reaju1s
.rea3jú2s1t
.reajú1s
.rea3liza
.rea1l
.reali1z
.rea3lizá
.rea3líza
.realí1z
.re3ali1m
.rea3li2s1m
.reali1s
.rea3li2s1t
.re3ani1m
.rea1n
.re3aní1m
.re3apare1c
.rea1p
.reapa1r
.re3ubica
.reu1b
.reubi1c
.re3ubíca
.reubí1c
.reu3mati
.reu1m
.reuma1t
.reu3máti
.reumá1t
.re3uni1r
.reu1n
.re3uní1r
.re3usa1r
.reu1s
.re3usá1r
.re3utili1z
.reu1t
.reuti1l
.re3utilí1z
rmano1h
rma1n
rmano1a2
rmano1e2
rmano1i2
rmano1o2
rmano1u2
rmano1á2
rmano1é2
rmano1í2
rmano1ó2
rmano1ú2
retro1h
re1t
1ret2r
retro1a2
retro1e2
retro1i2
retro1o2
retro1u2
retro1á2
retro1é2
retro1í2
retro1ó2
retro1ú2
romo1h
ro1m
romo1a2
romo1e2
romo1i2
romo1o2
romo1u2
romo1á2
romo1é2
romo1í2
romo1ó2
romo1ú2
sobre1h
so1b
sob2r
sobre1a2
sobre1e2
sobre1i2
sobre1o2
sobre1u2
sobre1á2
sobre1é2
sobre1í2
sobre1ó2
sobre1ú2
semi1h
se1m
semi1a2
semi1e2
semi1i2
semi2o2
semi1u2
semi1á2
semi1é2
semi1í2
semi1ó2
semi1ú2
i4a.
i4a4s.
ia1s
2óti1c
ó1t
emi2o2
seudo1h
seu1d
seudo1a2
seudo1e2
seudo1i2
seudo1o2
seudo1u2
seudo1á2
seudo1é2
seudo1í2
seudo1ó2
seudo1ú2
o2o4s.
oo1s
.so3a4s
socio1h
so1c
socio1a2
socio1e2
socio1i2
socio1o2
socio1u2
socio1á2
socio1é2
socio1í2
socio1ó2
socio1ú2
a3ri4o.
a3ri2o4s.
ario1s
3logía
lo1g
4ó4n.
ó1n
4on4e4s.
o1n
one1s
4i4e4r.
ie1r
4o2i3c4o.
oi1c
4o2i3c2o4s.
oico1s
4o2i3c4a.
4o2i3c4a4s.
oica1s
.su2b2a2
.su2b2e2
.su2b2o2
.su2b2u2
.su2b2á2
.su2b2é2
.su2b2í2
.su2b2ó2
.su2b2ú2
.sub2i3l4l
.subi1l
.sub2i3mie1n
.subi1m
.sub3í2n1d
.subí1n
.sub3ími
.subí1m
.su4b3ra1y
.sub2r
.sub3aflue
.suba1f
.subaf2l
.sub3a1r2r
.suba1r
.sub3e1nte1n
.sube1n
.sube2n1t
.sub3esti1m
.sube1s
.sube2s1t
.sub3estí1m
.sub3ofici
.subo1f
.subofi1c
.sub3urba
.subu1r
.su1bu2r1b
.sub3alte1r
.suba1l
.suba2l1t
.sub3in2s1p
.subi1n
.subi2n1s
.su3bié1n
.su3bi1r
.su3ba1m
.su3bordi1n
.subo1r
.subo2r1d
.su3bordí1n
.sub3acuá
.suba1c
.sub3espe
.sube2s1p
.sub3esta
.su3burbi
.su4b5rei1n
supe2r3r
su1p
supe1r
supe2r1a2
supe2r1e2
supe2r1i2
supe2r1o2
supe2r1u2
supe2r1á2
supe2r1é2
supe2r1í2
supe2r1ó2
supe2r1ú2
supe3r4a4r
supe3r4á4r
supe3r4á3vi4t.
superá1v
superávi1t
supe3r4á3vi4t4s.
1superávi2t1s
4a3ci4ó4n.
ació1n
4a3ci4on4e4s.
acio1n
acione1s
4e3rio4r.
e1rio1r
4e3rior4e4s.
eriore1s
4e3rior4a.
4e3rior4a4s.
eriora1s
4e3rior3ment4e.
erio2r1m
eriorme1n
eriorme2n1t
4e3riorid4a4d.
eriori1d
eriori1da1d
4e3rioridad4e4s.
erioridade1s
4e3ra3bl4e.
erab2l
4e3ra3bl4e4s.
erable1s
4e3ra3ble3ment4e.
erable1m
erableme1n
erableme2n1t
pe5r4ante
pera1n
pera2n1t
pe1rpon5d6r
1pe2r1p
perpo1n
perpo2n1d
sup6ra
supra1h
sup2r
supra1a2
supra1e2
supra1i2
supra1o2
supra1u2
supra1á2
supra1é2
supra1í2
supra1ó2
supra1ú2
talmo1h
ta1l
ta2l1m
talmo1a2
talmo1e2
talmo1i2
talmo1o2
talmo1u2
talmo1á2
talmo1é2
talmo1í2
talmo1ó2
talmo1ú2
tele1h
te1l
tele1a2
tele1e2
tele1i2
tele1o2
tele1u2
tele1á2
tele1é2
tele1í2
tele1ó2
tele1ú2
4óste4o.
ó1s
ó2s1t
4óst4e2o4s.
ósteo1s
termo1h
te2r1m
termo1a2
termo1e2
termo1i2
termo1o2
termo1u2
termo1á2
termo1é2
termo1í2
termo1ó2
termo1ú2
tetra1h
tet2r
tetra1a2
tetra1e2
tetra1i2
tetra1o2
tetra1u2
tetra1á2
tetra1é2
tetra1í2
tetra1ó2
tetra1ú2
topo1h
to1p
topo1a2
topo1e2
topo1i2
topo1o2
topo1u2
topo1á2
topo1é2
topo1í2
topo1ó2
topo1ú2
tropo1h
tro1p
tropo1a2
tropo1e2
tropo1i2
tropo1o2
tropo1u2
tropo1á2
tropo1é2
tropo1í2
tropo1ó2
tropo1ú2
p4o2i3d4e.
poi1d
p4o2i3d4e4s.
poide1s
ultra1h
u1l
u2l1t
ult2r
ultra1a2
ultra1e2
ultra1i2
ultra1o2
ultra1u2
ultra1á2
ultra1é2
ultra1í2
ultra1ó2
ultra1ú2
xeno1h
xe1n
xeno1a2
xeno1e2
xeno1i2
xeno1o2
xeno1u2
xeno1á2
xeno1é2
xeno1í2
xeno1ó2
xeno1ú2
inter4é1s
inte1r
inte1r4esa1r
intere1s
i1n3te3r4i3n
inter4ino
inte1r4io1r
mili4a1r
mili4a3rio
para4íso
paraí1s
para4ulata
parau1l
paraula1t
super4able
supera1b
superab2l
super4ació1n
supera1c
supe1r4io1r
tran4sa2c1c
tra1n
tra2n1s
transa1c
t1rans4a1r
trans4eúnte
transeú1n
1transeú2n1t
t1rans4ibe1r
transi1b
tra1ns4ició1n
transi1c
trans4ido
transi1d
tra1ns4ige1n
transi1g
t1rans4igi1r
t1rans4isto1r
transi1s
1transi2s1t
1trans4i1t
trans4ita1b
trans4itorio
transito1r
trans4ubsta
transu1b
tran1su2b1s
1transub2s1t
ultra4ísmo
ultraí1s
ultraí2s1m
wa3s4h
wa1s
.bi1anua1l
.bia1n
.bi1au1r
.bien1a2n1d
.bie1n2
.biena1n
.bien1apa
.biena1p
.bien1ave
.biena1v
.bien1e2s1t
.biene1s
.bien1i2n1t
.bieni1n
.bi1o1x
.bi1ó2x
.bi1u1n
.en1acei1t
.ena1c
.en1aci1y
.en1aguac4h
.ena1g
.enagua1c
.en1agua1z
.en1anc4h
.ena1n
.ena2n1c
.en1apa
.ena1p
.en1a2r1b
.ena1r
.en1a2r1t
.en2a1rt2r
.en1e1j
.hepta1e
.he1p
.he2p1t
.intra1o2
.int2r
.intra1u2
.mal1aco1n
.mala1c
.mal1aco1s
.mala1e
.mal1anda2n1t
.mala1n
.mala2n1d
.mala1nda1n
.mal1anda2n1z
.mal1e2s1t
.male1s
.mal1i2n1t
.mali1n
.pa4n1a4meri
.pa1n
.pana1m
.paname1r
.pa4n1euro1p
.paneu1r
.pa4n1afri
.pana1f
.panaf2r
.pa4n1ópti
.panó1p
.panó2p1t
3p2si1c
3p2si1q
.re3a2e1g
.re3a2q
.re3a2z
.re3a2gru1p
.rea1g
.reag2r
.re3i2m
.re3i2n1c
.re3i2n1g
.re3i2n1s
.re3i2n1t
.re3o2b
.re1o1c
.re1o1j
.re3orga
.reo1r
.reo2r1g
.re1u2n1t
.retro1a2
.re1t
.ret2r
.su2d1a2f2r
.su1d
.suda1f
.su2d1a2me
.suda1m
.su2d1e2s1t
.sude1s
1su4d3oe1s
su1d
.sur1a2me
.su1r
.sura1m
.sur1e2s1t
.sure1s
.sur1oe1s
.tele1i2m1p
.tele1i2
.te1l
.telei1m
.tele1o2b1j
.tele1o2
.teleo1b
.tra2s1a
.t2r
.tra1s
.tra2s1o
.tra2s2o1ñ
.tran2s1a2l1p
.tra1n
.tra2n1s
.transa1l
.tran2s1a2n1d
.transa1n
.tran2s1a2t2l
.transa1t
.tran2s1oce
.transo1c
.t1ran2s1u1r
.tri1ó2x
PK
!<e
!!hyphenation/hyph_et.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.aa4
.aas5ta5
.aa2s1t
.ahe4li
.a1he
.a1l4a
.al4g4as
.al1g
.antii2k5
.a2nt
.an3ti
.a2p3l4
.a1p
.ap3r4
.ar2t5r
.a2rt
.au3a
.eba3
.e1b
.ee4
.ee2la4
.e2k3l4
.e2k5r
.gu4a
.hie5
.i1di4
.i1d
.i5ni1m5a
.i1n
.i2ni1m
.i1ni
.ise5e
.i1se
.i2s4o
.ja4e
.j4a
.ja4t
.ka4e
.ka2o
.ke5hi
.k2e
.ki4p5r4
.ki1p
.ku4p2l4
.ku1p
.ku5sa2
.kusee4
.ku1se
.la4p4l4
.la1p
.le4e
.le5hi
.lemmi1k5
.le1m
.le2m1m
.lem1mi
.le5se
.le2s
.lõ4p2p3
.lõ2p
.ma4a
.maa5a
.ma5j
.mi5s1a2
.mi2s
.mo4e
.nek3
.ni4p
.ni3su5
.noa3
.oa3
.o2o4
.pa4p4p
.pa1p
.pea3a2
.pe2a
.raadio5
.raa1d
.raa3di
.re4a
.re2o
.re2p3r4
.re1p
.re5so
.rü5hi
.rü2h
.sa2k5r
.sa1k
.sa2p3r4
.sa1p
.se4a
.s1k4
.skaa5
.s1ka
.so5li
.so2l
.s1p4
.s1t4
.te2o2o4
.t2e
.te2t3r
.te1t
.ti1na5
.ti1n
.toa3
.t2o
.t1s4
.tsi4s
.t1si
.turba5
.tu2r1b
.tus2
.tu3s1a2
.ul4ga
.ul1g
.ulu4k
.u3lu
.u2m2b
.u1m
.uu4
.vaa3l
.va2s4k3
.ve1re3
.ve1r
.õp1pe5
.õ2p
.õ4p1p
.ää4
.õõ4
.öö4
.ühi4s
.ü2h
.ü1hi
.ü3le3e
.ü2le
.ür2
.ü4ü4
aaa5l
a1aas
4aa1b
aa4bi.
5aa5del.
aa1d
aa1de
aadio5a2
aa3di
a1ae
a4ae1r
aa4fe
aa1f
aa4gan
aa1g
aa2h
aa4has
aa3ha
aa1i
aai4g
aa4is
a5a2it
aa2j
aa2la
aa4lae
aala4r
aa4las
aa4leh
aa1le
aa4lek
aa4lel
aal1g4
aali4k2e
aa3li
aali1k
aa2l3k
a5alli1k
aa2l1l
aal1li
a4a2l3t4
aa2l3õ
aa4lü
aa4ma1d
aa1m
aa1ma
aa4man
aa4mee
aa1me
a5amee1r
aa4mö
4aan
aa4na1m
aa1na
aa4na1t
a5an2d1m
aa4n1d
aan4duu
aan1du
aa5ni
aa2nt4
aa4pek
aa1p
aa1pe
aa4pen
aa4ra4b
a4a1ra
aa4ra1f
aa4ra1j
aara4l
a5ar3te1r
aa2rt
aar1t2e
aa2r3õ
aa4rö
aa4rü
aa4sa1b
aa1sa
aa4sai
aa4sar
aa2se4
aa4so
3aas5ta
aa2s1t
a5a4sul
aa1su
aa4su1t
aa4sü
4aa1t
aa4taa
aa3ta
aa4tas
aa4teh
aa1t2e
5aatom
aa3t2o
aa4tõ
aa4tü2h
aa1tü
a1au
aa3š4i5
a4a1š
a1b
4a3ba
a5be
3a2bi1e2
a4bi1j
5a2bs
a1d
a4da1d
a1da
a4deo
a1de
a2de1r
a3di
adu4r
a1du
adu3se
ad4u2s
a4dö
a5ea3li
ae2d
3aed.
2aee
a3ees
ae4f
1aeg.
ae1g
ae4ga4j
ae4gi
3ae2g1n
2a1e2h
4aei
4aek
a3e4k1s
2ael
a5e2la3ni
ael4an
ae1la
ae2le
ae4lo
a1e4lu.
ae2lu
4ae2m
4aen
ae4pi
ae1p
ae2s
a3e1si
4ae2t1t
ae1t
a1f
a4fek
a1g
3a4ge2nt
ag4ia4
a3go
5ag5re1g
a2g1r
ag1re
agud4
a3gu
1a2hi.
a1hi
ah4kar
a4hk
ah1ka
5ah1nu
ah1n
4ai.
a4i2a
5aian
ai4hu
ai4k2e
ai1k
ai4k1l4
a3i2l1m
ai4lu
ai4me.
ai1m
ai1me
ai2mp4
4ain.
ai1n
a5i2n1f
a5i4ni1me
ai2ni1m
ai1ni
4ai2ns
a3in1s4e
ai4p2r4
ai1p
ai2sa
5aisti1n
ai2s1t
ais3ti
ais2t4r
ai4va
ai1v
a1j
a3ja.
aj4a
a4ja1le
5aja1lo
a4ja1lu
1a2ja1m
4a3jo
aju1t4
a1k
akaa4s
a1ka
a4k4aa1t
ak4as
3a2ken.
a1k2e
a3ki
ak4kis
a4k1k
ak1ki
ak4k2r
a3klas
a2k1l4
ak2la
ak4lau
a5k2lii
ak1li
a3krii
a2k2r
ak1ri
ak3ro
4a4k1s
ak4te4l
a2kt
ak1t2e
a2ku.
a1ku
aku4ma1p
ak4u1m
aku1ma
a5kva3li
a2k1v
3a4la4l1d
a1la
ala4mas
a3la1ma
ala1m
a4laü2h
ala1ü2
a4le4le
a1le
a3l4e1t
ale2t4t
5alga1t
al1g
al4ge1d
a3li
ali4san
ali1sa
a4lis1t2e
ali2s1t
5allee.
a2l1l
al1le
al3lee
al4le4h
al4lu1j
al3lu
al2lü
a4lo1b
a1lo
a2l1s4
4a2lt
4alu.
a1lu
a4lud4
1a2lun
alü4h
a1lü
a4lüt
a1m
4am.
a5ma.
a1ma
a4maa1d
a3maa
3a2me2t1n
a1me
am4e1t
am2it4
a1mi
a1m4o
am4pa4l
a2mp
am1pa
5amper.
am1pe
ampe1r
4a3mü
2an.
3a2na1l4ü
a1na
5anatoom
ana1t
ana3t2o
anat2o4o
an4das
a4n1d
an1da
an4deo
an1de
an4do1g
an1do
2a1ne
a4neh
a3ni
a4ni2so
4a2n1j
an4k3r
a2nk
3an1ku
an4nah
a2n1n
an1na
4a1no
anos4
3an3s4a1m
a2ns
an1sa
4an2s1t
3anten
a2nt
an1t2e
5anti1lo
an3ti
a4o4d
ao4he
a3o4ht
ao4j
ao2le
ao2lu
ao2m
a3o1ma
ao4nu
aoo4p
a2o2o
ao2pe
a5ope1ra
aope1r
ao2r
a5oras
ao1ra
a1o2sa
ao4su
a1o2t1s
a1p
a2par
a1pa
5aparaa
ap4a1ra
a5pa2rt
a3pa2r1v
a3pi
a3p1la
a2p2l4
a3p4o
ap4pal
a4p1p
ap1pa
ap4pi1n
ap1pi
ap4pis
ap3ri
ap2r4
ap3ron
ap1ro
a4p1ru
a3pä
4a1ra
ara4k4k
ara1k
a4ra1se
5are2n1g
a1re
3ar4e3tu
are1t
3arh2it
a2r1h
ar1hi
2a1ri
ari4al
a3r4ia
ari4a2p4
4a2r1k
ar4kel
ar1k2e
5arma4s1t
a2r1m
ar1ma
5armee.
ar1me
ar3mee
a4ro4l
a1ro
1ar2s1t
a2rs
5arti4k1k
a2rt
ar3ti
arti1k
3a2rua4
a1ru
3arva1m
a2r1v
5arvestu2s
ar3ve
arv4e2s1t
arves3tu
1arvu
ar4vää
ar3vä
a4sa4las
a1sa
asa1la
a4sa1me
a3s4a1m
a4sau
ase4ma
a1se
ase1m
as5ema.
1a2sen
a4seos
a4se1se
a4se2t1t
ase1t
3a4s4e3tu
as4fä
a2s1f2
1a2si.
a1si
asi4a2la
a3s4ia
asi3an
a4si4n1d
asi1n
asi1p4
5asjan
a2s1j
asj4a
as1kõ4
a2s1k
as4kõl
a5s2laa1v
a2s1l
as1la
a4sok
a1so
as3o1le
a2so2l
aso4p
as4pe1t
a2s1p
as1pe
as3p2l4
as2p4o
a3spor
as4san
a2s1s
as1sa
as2s5t
4as3ti
a2s1t
5ast1me
as2t1m
ast4ra1k
as2t1r
ast1ra
a4su4b
a1su
a4sud
asui4
a4su2s1s
a5suu
a1t
a3ta
a4taž
5ateljee
a1t2e
ate2l1j
atii4v
a3ti
atmi4k
a2t1m
at1mi
5atr2o2o
a2t1r
at1ro
at4ros
at4rus
at1ru
at4s2o2o
a2t1s
at1so
at2s4p4
at4sü
at2t4s
a2t1t
4a3tu
au2a
aua3l
au4ba.
au1b
4aud
au4de.
au1de
a3ui
4au2j
au4k2e
au1k
au3lu3
aulus4
au4pü4
au1p
a5u2r1g
au2s4o
au2su
a3usul
1au3t2o
au1t
aut2o4o4
auto5s2
au2t4r
a1v
av4a
a4va2ns
a3v4ar
a4veh
a3ve
a1õ2
a1ö2
a1ä2
a1ü2
až4ni
a4žn
4a1š
5baa
baa4g
baa4k
baa4s
ba1b4
bae4
ba1g4
ba4he
ba4ju.
ba1j
ba3k4r
ba1k
b4a4k4s
ba2k4v
ba2p4l4
ba1p
bap4r4
ba4rõ
basa4s
ba1sa
bas4ko
ba2s1k
bas4pe
ba2s1p
ba2t4r
ba1t
4bb
2bd
be3a4
be1b2
be1f4
be1g4
be4lü4
be2ra2
be1r
be2r1k4
be1ro4
be4rõ4
be4si
b4e4s1t
bes4tis
bes3ti
be2t4r
be1t
4bf
4bh
b3ha4
bi4a2la
b4ia
bia4v
2bi1e2
bi4en
4bi1nõ
bi1n
bi4p1la
bi1p
bi2p2l4
bi1s4a
bi4si
bi2s4k
2b1j
2b1k
2b1l
b1l4e
b3le1t4
b2lon
b1lo
2bm
2bn
4bo2rt
2bp
2b1r
3b2ri1g
b1ri
bros4
b1ro
2bs
b3so
2bt4
4bu4k1s
bu1k
bu4lõ
3b4u1m
bu1se5
bu4si4
bus4k2r
bu2s1k
2b3v
1da
daa2
d4a5a1b
daa2la4
da4do
da1d
dae4
da1f4
d5ai1n
da4leh
da1le
da4l4u1m
da1lu
da4lus
5da1m
4da2n1n
da4n4u1m
da1nu
dao4
dara1t4
d4a1ra
4da2r1b
dar4de
da4r1d
4da4re
4da1ru
2da2r1v
d3arv.
4d1a2sen
da1se
da4s4u1m
da1su
2d5b2
2dd
1de
dea2
2deaa2
de5a2v
de1d4
2dee.
dee4le
4de1he
de2k3l4
de4k4s
5del.
de4lau
de1la
4de4lek
de1le
4delu1v
de2lu
de4p2r4
de1p
dep5res1si
dep1re
depre2s1s
de1re4
de1r
de4rel
de4re1p
5de1ro
de1ru4
2df
2d5g
2d1h
1di
dia4v
d4ia
di1d4
di1g4
dii4g
dii1na4
dii1n
dii4sa
dii4su
4di2l1m
4di1ni
di1n
di2os
di4p1lo
di1p
di2p2l4
dis4ai
di1sa
di4sar
di4sas
dis4p2r4
di2s1p
di2t4r
d2it
2d1j
2d3k4
dkop4
d1ko
2d1l
dle4v
d1le
dlu2s1t4
d1lu
2d1m
d3mee4
d1me
dmi4k
d1mi
2d1n
1do
do4h
doi4r
2dos
d3o2sa
5do2s1k
2d3p4
2d1r4
5draa5ma.
d1ra
draa1m
draa1ma
drao4
3dreen
d1re
d4rel
d2ren
3dre1na
2d1s4
dsar4
d1sa
2d3t2
1du
du4b
due4
2d5u4k1s
du1k
d4u2s
du4s1a2
du4see
du1se
du2s1k4
2d1v
2dõ
dõ4l
4dä
2dül
dü4li
4dü4r
5dü4ü
4dž
2d2š
3dše1m
d1še
eaa2
ea3a2j
e1aas
ea2b
e3abi
e1ae
ea2hi
e1ai
ea2j
e4a4k4s
ea1k
ea2k4v
ea2la
e3ala.
e1a4lu
ea2me
ea1m
e5am4e1t
e5a2n1n
ea4nu
eao4
ea2pa
ea1p
e1ar
ea4re
ea2s
e4a2s1s
e5as1t2e
ea2s1t
e5asu1t
ea1su
e3au
ea2v
e3ava.
eav4a
e1b
e1d
e1d4a
e3ea3li
e3ees
ee4fe
ee1f
eei4
ee2k4r
ee2la
3eelar
e5ele2kt
ee1le
ee3l5uu
ee2lu
ee4nai
ee1na
ee4nal
ee4na1m
ee4no
ee2nt4
ee4pai
ee1p
ee1pa
e5e4p2it
ee1pi
5eepos
ee1p4o
ee4ral
ee1r
ee1ra
eer5a1p
ee4ros
ee1ro
ee4rot
ee4si1n
ee1si
e5es2i1ne
ee4s5i2s1t
5eeskir
ee2s1k
ees1ki
ee4sü
ee4tõm
ee1t
ee1tõ
ee4tä
e1f
e1g
e3gaa4
ega2s4t
eg4as
e4goi
4e3ha
e2he.
e1he
e2hi
e3hi1n
5ehi4s1t4
1e4h2it
5ehi3ti
eh4ta4
e4ht
4e1hä
ei4dul
ei1d
ei1du
ei4e2
eie5ri
eie1r
ei2ga
ei1g
e3iga.
ei4ha
ei4he
e3i2l1m
ei2lu
ei4me1m
ei1m
ei1me
5ei5ne.
ei1n
e2i1ne
ei4r2o2o
ei1ro
e5i4sa.
ei1sa
ei4si2s1t
ei1si
ei4tau
e2it
ei1ta
ei4tõ
ei4va.
ei1v
e3j
e3ka
e4ka1hi
e3k2e
ek4ka1p
e4k1k
ek1ka
e2k2l4
ek4lo
e3k4o
ek3re
e2k2r
e3krii
ek1ri
4eks.
e4k1s
5eks4am.
ek1sa
ek3s4a1m
3ekse1m
ek1se
eksi3k
ek1si
3ek4s1ka
ek2s1k
eks2p3r4
ek2s1p
5eks2t3r
ek2s1t
ek4tül
e2kt
ek1tü
e3kü
e2laa4v
e1la
e4la2g1r
ela1g
e2la1m
3ela1mu
el4an
5elani1k
e2la3ni
eleis4
e1le
e4le4k1s
5elekt1ro
e4lek2t1r
ele2kt
3ele1me
ele1m
5elevaa3t2o
ele1v
ele3v4a
elev4aa1t
e3li
e4li1d
eli2i4t
el4kus
e2l1k
el1ku
e4l4o1b
e1lo
elo4p
el2s4t
e2l1s
el2t2r
e2lt
el4tü
e2lu
1elu.
e5lu1b
elu5i4
1e4lun
e3lus.
e4lu2s1s
el5uss.
el4ü3h
e1lü
e1m
5emand.
e1ma
ema4n1d
e5ma1t
em1ne3
e2m1n4
emos4
e1mo
emp4r4
e2mp
5e4mu2l1s
e1mu
end4u2s3
e4n1d
en1du
5e4nelas
e1ne
ene1la
e5ne1m
3e2ne2r1g
ene1r
e4ne1se
en4es
e4ne2s1l
en2k5l4
e2nk
en2k4r
en4sal
e2ns
en1sa
en4tos
e2nt
en3t2o
en4tü
eo5a2
eo4j4a
eo1j
e3o4le
eo2lu
eo2ma
eoo4p
e2o2o
eo5p2l4
e3osa.
eo2sa
e3osa1k
e5osa3li
e5osa1v
e4o2s4f2
e3ots.
eo2t1s
e1p
e3p2l4
e4p3li
ep4lu
e1r
e3raa
e1ra
5erakon
era1k
era1ko
5era4l1d
e4rao
erei2
e1re
erek4
5ergu1t
e2r1g
er3gu
erih4
e1ri
eri4uu
e5riu
er4nau
e2r1n
er1na
e4ro4r
e1ro
e2r1p4
er2p2r4
er2s4k
e2rs
er4taa
e2rt
er1ta
er4ta1k
er4tes
er1t2e
e4rud4
e1ru
es1an
e1sa
1e2se.
e1se
e5sek
e5sen
e5sil
e1si
eska4j
e2s1k
e4s1ka
eska4s
es1k2e4
es5kel
es2k3l4
es2k3r
es2k5ö
es4laa
e2s1l
es1la
es3o1le
e1so
e2so2l
es4or
es2p4o
e2s1p
e3spor
es4si2s1t
e2s1s
es1si
4e2s1t
es4tü
e3su
esu1p4
e1t
e3t4a
e2ten
e1t2e
5etendus.
etend4u2s3
ete4n1d
eten1du
etii4s
e3ti
et2ra
e2t1r
et4si1n
e2t1s
et1si
et4s2o2o
et1so
et4sü
et4ta1j
e2t1t
et1ta
1et1t2e
et4t1ru
et2t1r
et4tä
et4tü
4e3tu
e4tõ4d
e1tõ
e4tüm
e1tü
eu4de
e3u2j
e3u2l
e5u2n
eu4p
eu4s
e5u2s1k
e3uu
e1v
e3v4a
e1õ2
e3ö4
e1ä2
e1ü4
e1š
5faa
f4aa4n
4fa4d
2ff
1fi
fi2i4t
4fj
4fk
2f1l
f2lo
5floo1ra
fl2o2o
2f1n
1fo
fo2l1k5
fo4ro
2f1r
f2re
3frees
2fs
2f1t
fta1t4
f1ta
3fu
3gaa
gaa4r
4gaa2s1t
4ga1b
4gae2d
ga1g4
2gah
ga2hi
gai4g
ga5is
ga3k4
5ga1m
ga3o
ga3p
4ga2rt
4ga2r1v
g4as
4ga4su1k
ga1su
5ga3ta
ga1t
ga2t4r
gau4
ga4va.
ga1v
gav4a
ga4van
2g3b
2gd4
gea4
ge4du.
ge1d
ge1du
gek4
ge5k2l4
4gek1se
ge4k1s
ge4lah
ge1la
gela4s
ge4leh
ge1le
ge3lu
ge4lä
ge4nan
ge1na
geok4
ge4or
4geos
4ge2r1g
ge1r
ge4rus
ge1ru
4gf
4gg
2g1h
gi2a2j
g4ia
gii4s
gi3k
4gi2n1f
gi1n
4gi2nt
gio4r
gi3p2l4
gi1p
gi4san
gi1sa
gi4sil
gi1si
gi4sö
gi2t4r
g2it
gi4va1j
gi1v
2g1j
2g1k
2g1l
g4lüt
g1lü
2g1m
2g1n
gne4t4a
g1ne
gne1t
gne4t2e4
4goh
go4le
go4ma
go4p4o
2g1p4
2g1r
g2ran
g1ra
5gran4aa
gra1na
3g2ra1v
g2ru
3gru1p
4g1s4
2g1t2
g3t1r
3gu
gu1b4
gue4
gu5i
gu4nel
gu1ne
gu3o2
gu1p4
gu4sen
gu1se
gu4sä
guu4j
2g1v
2g3õ4
4gä
gü4l
3ha
ha2a4ri
haa4s
hal4lai
ha2l1l
hal1la
5har
ha1sa4
ha2t4r
ha1t
1he
hee4m
hee4s
hei4s
he2k4v
2hel.
he2l1s4
he2t4r
he1t
4h1h
hhe4l
h1he
1hi
h4ia2
hii4s
5hil
5hi1m
hio2
hi4san
hi1sa
hi4sar
hise4l
hi1se
hi4s1t4
hi4sü
5hi2t1s
h2it
h1j
h3jaa4
hj4a
4hk
h4kal
h1ka
h4kas
h4kis
h1ki
h2k3l4
h4ko4
h2k3r
h4k1s4
h2kt4
h4ku2s1k
h1ku
h3kus
h4ku4su
h4kõ
h4kä
h1l
hle4n
h1le
4h1m
h4mo4
h4mü
h1n
hni1ka5
h1ni
hni1k
1ho
ho1m4o
4hp4
h2rs4
4ht
h2ta
hta2j
h5ta1k
ht2a4l
ht5ar
hta4su
h4te1g
h1t2e
hte4n
h3te4r
h4ti1d
h3ti
h4t2o
h4t1r
h2t5s4
h4tö
h4tä
1hu
hu3a
hue4
hui4d
hu2k4l4
hu1k
hu1p4
hu3sa2
2h1v
hva1d4
hva2s1t4
hve4l
h3ve
h4vi1ni
h3vi
hvi1n
h4vo
hvu4s
1hõ
3hö
1hä
3hü
4ia
iaa2
iaa4l
ia5al2a1ne
iaa2la
i3aa2s1t
i5a4bi
ia1b
ia4bo
i1ae
ia2g
ia2h
i3a2i1ne
iai1n
i1a2j
ia4k2r
ia1k
i3a2kt
i1a2la
i5a2l1b
i3al1li
ia2l1l
i1a2lu
ia2me
ia1m
i5a4met.
iam4e1t
ia2na
i5an2d1j
ia4n1d
i5an2d1m
i2a4ne
i5an1ne
ia2n1n
ia2p4
i3a2par
ia1pa
ia5p2r4
ia2re
i3a2r1m
ia4ru
i1a2r1v
ia2s
ias4k2e
ia2s1k
i5aste.
ia2s1t
ias1t2e
i3a1su
ia4tel
ia1t
ia1t2e
ia2t4r
i1au
iau4l
i5a4va1j
ia1v
iav4a
i5ava4l1d
i1b
i4ba1d
i4bau
i1d
5i2deaa2
i1de
idea2
idii4
i1di
id1lu4
i2d1l
ie2
i3ea3li
i1eel
iei4
i1e4k1s
i5e2la3ni
iel4an
ie1la
i1e2lu
i3eos
ie4ri
ie1r
ie2rs4
i3e1si
ie5so
i5e2s1s
i4e4s1t
i3e2ten
ie1t
ie1t2e
ietu4s
i4e3tu
i1f
i1g
i4ga4g4
i5gar
5igat1su
iga1t
iga2t1s
i4ga1v
ig4ra1m
i2g1r
ig1ra
ih4le
ih1l
ih4t2e
i4ht
ih4ti1n
ih3ti
i4i4a
ii4deo
ii1d
ii1de
ii4du.
ii1du
iie3
ii4es
ii4gaa
ii1g
ii4g4a4s
ii4ha
ii1ka4
ii1k
ii4kis
ii1ki
iik4ro
ii2k2r
iik2s4t
ii4k1s
ii2kt4
ii4la1b
ii1l4a
ii4la1d
ii4lae
ii4la2s1t
iila4s
ii4leh
ii1le
ii4lel
ii4le2s
ii2lõ4
ii4lü
ii4mai
ii1m
ii1ma
iima1t4
ii4m4e1t
ii1me
ii4nai
ii1n
ii1na
ii4nau
i5in1de
ii4n1d
i5i2n1f
ii3ni
i5inimen
ii2ni1m
ii4ni1me
iin2it4
ii2nk4
ii1no4
ii4nol
ii4nos
ii4nõ
ii4p2r4
ii1p
ii4ran
ii1ra
ii4rau
ii2rs4
ii4rü
ii4san
ii1sa
iise4l
ii1se
ii4se1v
ii4sik.
ii1si
iisi1k
ii2ta
i2it
ii4teh
ii1t2e
ii3t2o4
ii4tom
ii4tos
iit5re
ii2t1r
ii4tõ
ii4tü
ii4ves
ii1v
ii3ve
ii4ve1t
ii2vo
i1j
i1k
i4ka1lu
i1ka
i4ka4re
i4k4a2r1k
i4kau1t
i4k5e2lu
i1k2e
ike4si
i3klaa
i2k1l4
ik2la
i3klas
ik4le1r
ik1le
i5k2lu1b
ik1lu
ikop4
i1ko
ikos4
i4ko1v
i3k1re
i2k2r
ik3ro
ik4sar
i4k1s
ik1sa
ik2s4k
iks4p4o
ik2s1p
ik3t2r
i2kt
i3ku
iku3a
i1l4a
ila4s
i4l2a1si
il4ia4
i1li
i4li1si
il4kan
i2l1k
il1ka
il4kõ
ille5s
i2l1l
il1le
il1lo4
5ilme.
i2l1m
il1me
il4mot
il1mo
3ilmu1t
il1mu
ilo5g2
i1lo
il2p3l4
i2l1p
il4ti
i2lt
i5lun
i1lu
ilu3sa2
5ilus3tu
ilu2s1t
ilü4h
i1lü
i1m
i4me2lu
i1me
imi4g
i1mi
imi3su4
imi2s
i4mo4l
i1mo
5impe1r
i2mp
im1pe
i1n
ind3a4l
i4n1d
in1da
5inde4k4s
in1de
inde4r
ind4re
in2d1r4
2i1ne
inee4s
i4neh
i4ne4k1s
ini4kü
i1ni
ini1k
i2ni1m
ini4ma4s
i5ni1ma
i4ni1me
5inime1ne
5i4ni2m1l
i4ni1ni
ini1n
inn4a5a
i2n1n
in1na
inni4si
in1ni
3insen
i2ns
in1s4e
1in2s1p
1in2s1t
3inven
i2n1v
in3ve
io4de
i4o1d
io4h
i3o4ht
i5o2h1v
io4j
i5oks.
io4k1s
io2le
i3olek
io2lu
i5olu1k
io4lü
io2ma
i5oman
ioo4da
i2o2o
io4o1d
ioo4ne
ioo1ni3
io2p
i3ope1r
io1pe
i5o2r1k
i1o2sa
i4o2s4f2
io2s4p
io2s4t
ios2t4r
io4su
i3ots.
io2t1s
i3ot1si
io4tü
i1p
i3p1la
i2p2l4
ip4lu
ip4ro
ip2r4
ip4sas
i2p1s
ip1s4a
ip1se4
ip4se1p
ip4sus
ip1su
i4ra4b
i1ra
i4ra1se
ir4dis
i4r1d
ir1di
ir4nõ
i2r1n
i2rui
i1ru
iruu4
isaa4v
i1sa
i3s2aa
isa4j4a
isa1j
i4sa1na
isa4re
i4sa2r1h
i4se1lo
i1se
i3se1m
ise2t4s
ise1t
isi4g
i1si
i2so
3iso3la
i2so2l
i5so1li
is5o2ma
iso4r
i3sot
i3s4pek
i2s1p
is1pe
is3p2l4
i3s4por
is1p4o
is3s2p
i2s1s
5ista4n1d
i2s1t
is1ta
i5s4tiil
is3ti
5ist1me
is2t1m
ist4ru
is2t1r
ist4se
is2t1s
i5stsee
5is3tu3tu
is3tu
istu1t
i3su
i4sõm
i1sõ
i2sü4l
i1sü
isü4r
2it
itaa2
i1ta
itee4l
i1t2e
i3tee
it4ra1k
i2t1r
it1ra
it4ras
it2ru4
it2s3k
i2t1s
it4so
it1t2e1
i2t1t
it2t4s
iu4b
iu2da
iu4gi
iu1g
iu2j
i3uju
iu4l4a
iu4mar
i4u1m
iu1ma
iu4me4
iu2mo
iu4ni
iu4pu
iu1p
iu5sa2
i5uss.
iu2s1s
iu2su
iu3ti4
iu1t
i3uu
i1v
i4va1d
i4va2kt
iva1k
i4va4lus
iva1lu
i4va2nt
i4vau
i4veh
i3ve
ive4l
iviil5
i3vi
i4vü
i1õ2
i1ä4
i1ü2
i1ž
j4a
3jaa
ja1a2j
jaa4l
jaa4r
jaa4s
ja1b4
ja1d4
j2a5el
jae4r
ja1g4
ja3i2
ja5k1ra
ja1k
ja2k2r
5ja1ma
ja1m
5ja1me
jao4h
ja3p4
jas4k2e
ja2s1k
jas4tii
j4as3ti
ja2s1t
ja2t4r
ja1t
jat4su
ja2t1s
jau4b
jau4l
je3lu
3jo
joo4k
j2o2o
jue4
5ju1ma
j4u1m
ju4sei
ju1se
juse4l
5jõ
5jä
1ka
kaa4da
kaa1d
kaa3de
ka5a2j
ka4al
ka4a4ra
kaa4sis
kaa1si
kaa4ta
k4aa1t
4ka2de1r
ka1d
ka1de
4kae2d
4kae1g
kae3lu
k2ael
ka1g4
kahe5i
ka1he
4ka4i2a
2kai1n
ka5i2s1t
kait1se5
ka2it
kai2t1s
ka4ja1v
ka1j
kaj4a
ka4ju.
k5a4lus
ka1lu
ka4p2l4
ka1p
ka2p2r4
4ka2r1m
4kase1t
ka1se
k5a2s1j
ka2t4r
ka1t
2k3b
2kd4
1k2e
ke4el
kee3lu
2ke2hi
4ke4k1s
ke4la1j
ke1la
ke4la1k
ke4lok
ke1lo
4ke2m1b
ke1m
2ken.
5ke1ne
keo2
ke2s2k
ke4s1k3a4
ket5ra
ke1t
ke2t1r
k1et4t2e4
ke2t1t
2kf4
4kg
2k1h4
1ki
kie4
ki2k4r
ki1k
4ki2n1f
ki1n
kio4
ki4p2r4
ki1p
ki1s4a
ki4san
ki4si1k
ki1si
4kis1t2e
ki2s1t
ki2t4r
k2it
kiu4d
kiu3su
2k1j
4k1k
k3k1ra
k2k2r
k4k1s4
kku4ro
k1ku
2k1l4
k2la
k3la.
3klaas
k3lai
3k4la2m1b
kla1m
k3lan
3kla4p1p
kla1p
5klas1si
kla2s1s
k3la1t
5klave1r
kla1v
kla3ve
kle4a2
k1le
k4lee
k2lei
3kle2it
k3le1m
k4li1b
k1li
5klibu
kli1g4
k2lii
3klii1m
5kliis1t2e
klii2s1t
k3li1k
k3li1n
k4li2nk
k2lo
k3loom
kl2o2o
3klo2t1s
k2lu1b
k1lu
k3lus.
klu4sa2
klu4sõ
k4lu1t
2k1m
2k1n
kn4aa4
k1na
k4ni1p
k1ni
1ko
2ko4k1s
k4ol
ko4len
ko1le
koo4sa
k2o2o
4ko4rie2
ko1ri
4kor1pi
ko2r1p
k5osa.
ko2sa
ko4va
ko1v
2k3p4
2k2r
k4r4aan
k1ra
5kraa1na
3k4raa1v
3kra1b
3k4rae
k3rai
3kra2mp
kra1m
4kran
k3rau
5kredii
k1re
kre1d
kre1di
3k4ree1m
4k5re1g
5k4res
k5re1t
4k3ri1p
k1ri
5krist2al
kri2s1t
kris1ta
k4roh
k1ro
3kro2h1v
k4rook
kr2o2o
k3ru1t
k1ru
3kruu
3kru3vi
kru1v
4k3rü
4k1s
k5s1ae
k1sa
k4sai
ksek4
k1se
ksi2k
k1si
ksi1ka4
ks2it4
ks2k4l4
k2s1k
ks5kõ
ksp5lo
k2s1p
ks2p2l4
ks2p2r4
ks2t3r
k2s1t
ks4tü
2kt
kt2ra
k2t1r
1ku
kui4s
4ku4ju1j
ku1j
4ku4j4u1m
kuk3la
ku1k
ku2k1l4
4ku4k1s
ku4l2d
2ku2l1p
kumi4su4
k4u1m
ku1mi
kumi2s
kum1mi5
ku2m1m
3kus
ku4si1v
ku1si
ku2t4r
ku1t
kuu4ni
kuu1se5
4kuž
2k1v
k4van
3k2va2rt
kv4ar
5kvii3tu
k3vi
kvi2it
1kõ
kõ4de.
kõ1d
kõ1de
2kõi
k5õis
4kõ2p
1kö
4kö1d
1kä
käi5s
2käk
4käm
1kü
2kü1he
kü2h
4kü2le
4küll.
kü2l1l
2kž4
4kš
1la
5laa1g
la4a2j
laa2r
la4a1r5a
laa4sa
2laa1v
5laav4a
4la2d1j
la1d
5la1du
lae4r
3lae1v
4la2h1v
lai5ek5
laie2
lai4ga
lai1g
la4ja3p4
la1j
laj4a
la4ja1v
la3k4r
la1k
la2k4v
la4la.
la1la
4la4l1d
la4le2s
la1le
4la2l1s4
la4lus
la1lu
3la1ma
la1m
3la2mp
4la2m1s
la4n4es
l2a1ne
2la3ni
la2p4l4
la1p
4l1a2r1v
4la1rü
la4sas
la1sa
4la2s3b2
la4sei
la1se
la4se1m
4l1a2sen
l2a1si
4la4su1t
la1su
3lau
5l4aud
lau4n
lau5su
4la2v1h
la1v
2l1b
l4bau
lb4lo
l2b1l
4l1d
l4da1la
l1da
l5di2s1t
l1di
l4don
l1do
l4dü
1le
le1a2
3lee
lee2k5l4
1lee4le
leep5r4
lee1p
le3hi
4l1e4h2it
5le4ht
3le3j
4le4k1k
5l4eks.
le4k1s
4lek2t1r
le2kt
lekt1ri5
4le1le
le4lek
5lema.
le1m
le1ma
le4mor
le1mo
len4aa4
le1na
leo2
le3oh
le5o2lu
le4p2l4
le1p
le2s
4l1e2se.
le1se
le3sõ
3le1t
4l1et1t2e
le2t1t
4l1f
lf2o2o4
l1fo
l2f4t
l1g
l4gae
l4gai
l4ga2l1l
l4ga2n1n
l4ga1se
lg4as
lga2s4k
l4ge4le
l4gi4d
l2go
l4gu4j
l3gu
2l1h
1li
li4a2la
l4ia
li5a1m
lia4v
3li1d
li4dan
li1da
li4ga.
li1g
4li4i4a
liie4
5lii1g
lii4na
lii1n
5lik.
li1k
likai4
li1ka
li3k2la
li2k1l4
lik4ro
li2k2r
4li2l1m
4li4lus
li1lu
limi4s
li1m
li1mi
4li2n1f
li1n
4li2ni1m
li1ni
4li2ns
lin4t2e
li2nt
4li2n1v
lio4k
li3p4l4
li1p
li4sah
li1sa
li4si1k
li1si
4li2so2l
li2so
li4su1j
li3su
li4sõ2l
li1sõ
li4sö
li2t4r
l2it
li5tu
2li1že
li1ž
2l1j
l3ja1a
lj4a
lja5os
2l1k
l4k1k4
l4k1li
l2k1l4
lk4lu
l3k2r
2l1l
l5le1r
l1le
lli3t4r
l1li
ll2it
l2lor
l1lo
l4lo2t1s
l2l1p4
l2l1s4
l2lt4
l3lu
l4lää
l1lä
2l1m
lmaa4v
l1ma
l3maa
lma1t4
l4mau
l4me4k1s
l1me
l2mp4
2l1n
1lo
lo2d4r4
l4o1d
lo1f4
lo4gal
lo1g
4lo3ha
4lo2h1v
5loi
lo2k4r
2lo2l
l1o1le
l3oli1j
lo1li
3l4o3lo
lo4man
lo1ma
5loo1g
l2o2o
loo4ra1m
loo1ra
4lo4r1d
4lo2r1n
2lo2sa
l3osa.
2l1p
l3p4laa
l2p2l4
lp1la
lp4le
l4pu1k
l3pu
2l1r
lrih4
l1ri
2l1s
l4s4ka
l2s1k
ls1k2e4
l1s4o
ls1t2e4
l2s1t
2lt
l4tau
l1ta
lti4l
l3ti
l4tok
l3t2o
lt4sel
l2t1s
lt1se
lt4sis
lt1si
l4tu1k
l3tu
l4tõm
l1tõ
l4tää
l1tä
l4tüt
l1tü
1lu
3lua
lu3a2l
lud4
lue4
3lu1g4
4lu4hk
4lui1m
lu4ju1k
lu1j
4luk.
lu1k
3lu4k1k
2lun
lu4ni1n
lu1ni
lu4nio
lu2o3r
lu1o2
lu4rau
lu1ra
lu4see
lu1se
lus4tii
lu2s1t
lus3ti
lu4s4u1m
lu1su
lu4sö
3lu1ta
lu1t
lu2t4r
3luu
2l1v
lvel4
l3ve
1lõ
4lõ1d
4lõie2
5lõi1k
4lõis
4lõ2it
lõ4l
lõ3pe
lõ2p
lõ4p2p
1lö
4löe
1lä
4län
län4gi
lä2n1g
lää1ne5
1lü
lü3h
lü4ma
1ma
3maa
m4aa2b4
ma3abi
maa3la
maa3p
ma4bi
ma1b
ma1e2
ma2g4l
ma1g
5mah1l
4ma2h1v
4m1a2ja1m
ma1j
maj4a
4ma4ju.
ma3k1l4
ma1k
ma2k4v
3mal
ma4la.
ma1la
1mal4ma
ma2l1m
ma4o2m
ma3p2l4
ma1p
4ma2rt
4ma2r1v
ma5se
ma5si1n
ma1si
ma4s1t
4ma4sul
ma1su
ma4sä
4mau1t
2m1b
mba1t4
mbe2r3
mb1lu4
m2b1l
mbo4j
mb5u2sa2
2md4
1me
me1a4
me4an
3mee
mee5la
mee4s
me3hi
4me2k1v
me2rs4
me1r
me2rt4
me5s4o
m4e1t
4met.
4me2t1l4
2me2t1n
met4se
me2t1s
4me2t1t
me5u4
2m1f
4mg4
2m1h
1mi
mia4m
m4ia
mia4r
4mi1de
mi1d
mi4ka1j
mi1k
mi1ka
mi4nah
mi1n
mi1na
4mi2n1f
mi3ni
mi1p4
mi2s
mi1s1a2
mi3si
mi4si4g
mi4sih
4mi4si1k
mi4sil
mi2s3k4
mis4tii
mi2s1t
mis3ti
mis2t4r
mis3t1s
mit1t2e3
m2it
mi2t1t
2m1j
2m3k4
2m1l
2m1m
m4m4aa1t
m1ma
m3maa
mma4s
m1m4e
mme4r
mmi1ka4
m1mi
mmi1k
mmi4kal
m4mor
m1mo
2m1n4
1mo
4moh
mo4ha
m3o4k1s
4m1o2r1g
4mo2r1n
mo3sa
2mp
mpa4l
m1pa
m4pa1p
m2p3l4
mp3r4
2m1r
mruu4
m1ru
2m1s
m1s4o
m2s4p
2m1t2
m5t1s
1mu
mua4
mu5ah
mud4
mue4
mui4m
4mu2l1s
mu1p4
5mus
mut4ra
mu1t
mu2t1r
2m1v
mõ4d
4mõi1g
4mõ2p
3mö
4mö1k
3mä
4mäk
mär4ge4l
mä2r1g
3mü
4mü2h
4mž
1na
n4aa
na5a2h
naa4lu
na4a4r5a
na1d4
n4a5ei
na2g4l
na1g
nah4k2e
na4hk
nai4d
nai4si
na4k1ru
na1k
na2k2r
2na1l4ü
4na4me1r
na1m
na1me
na4mii
na1mi
n5amii1d
nant3s5a
na2nt
nan2t1s
na3o4
na2p4l4
na1p
na4re1t
na1re
4na2r1v
n4as
4na2s3b2
na2s1s4
nat4ra
na1t
na2t1r
nau4b
4nb4
4n1d
n4daas
n1da
ndaa2
n4da1b
n4dae4
nda4ko
nda1k
nda4l
n4dau
ndi1f4
n1di
ndi1p4
n4do4r
n1do
n4du1j
n1du
ndu4sõ
nd4u2s
n4dü
1ne
5ne.
nea2
nee3la
nee4le
nee3lu
nei4v
ne4lah
ne1la
4nelas
ne4lek
ne1le
ne4o4d
ne4p2l4
ne1p
2ne2r1g
ne1r
n4es
ne4tan
ne1t
ne3t4a
ne4tõm
ne1tõ
2n1f
2n1g
n4gai
n4ga2l1l
n4ga1se
ng4as
n4gau4
ng4lü
n2g1l
n4go4r
n2g3r
ngud4
n3gu
ngu2s4t
n2gü
2n1h
1ni
ni3a2p4
n4ia
nia4v
ni1g4
nii4g
nii4tu
ni2it
nikkel5
ni1k
ni4k1k
nik1k2e
4ni2l1m
5ni1ma
ni1m
nima4s
5nim4e1t
ni1me
3ni1mi
ni4mi4g
4ni2m1l
4ni2nt
ni1n
ni4si1d
ni1si
nis4k2e
ni2s1k
4ni2s1l
2n1j
2nk
n4ka4n
n1ka
n2k3r
nk4se
n4k1s
nk2s1t4
2n1l4
2n1m
2n1n
nna3e
n1na
nna5i
nna2k4r
nna1k
n4ne4f
n1ne
nnio4
n1ni
n2nk4
1no
4no1b
n4o1d4
4no4k1s
noo4na
n2o2o
n4or
2n3p4
2n1r
2ns
n4sa2l1p
n1sa
n1s4e
ns2k5r
n2s1k
n3so
nso4r
ns1t2e4
n2s1t
ns4tee
ns4te1m
nstis4
ns3ti
ns2t3r
ns4tu
nsu4s
n1su
2nt
ntaa4r
n1ta
n4teh
n1t2e
nte4l
nti1g4
n3ti
n4ti2so
n4to2t1s
n3t2o
n4t1r
nt3ra
nt3re
nt4sa1b
n2t1s
nt3s2a
nt4sal
nt4san
nt4sar
nt5sü
n2t1t4
n4tü2h
n1tü
n4tük
1nu
nu4da
nui4g
nu2r2k
nu2s4p4
2n1v
1nõ
nõ4l
4nõ1li
1nö
1nä
1nü
4nül
4nž
4nš
oa2
oa4a
o1ae
o5a1ku
oa1k
o3anal
oa1na
o5a1pa
oa1p
oau4b
o1b
oba4s
1o2b1j
4o1d
odaa4
o1da
odaal3
o4deh
o1de
odu4sõ
o1du
od4u2s
o2d4š
oe5a4
oe4fe
oe1f
oe2l
o3e1le
oe4mi
oe1m
oe4mu
o4eo
oe4rah
oe1r
oe1ra
oe4rõ
oe4si
oe2t4r
oe1t
o1f
o2f5r
o1g
og4la
o2g1l
og4li
3oha1ka
o3ha
oha1k
oh5t2o
o4ht
5ohvi2t1s
o2h1v
oh3vi
ohv2it
oi2ta
o2it
oiu5a
o1j
oju2s
oka2s1t4
o1ka
ok1ku5
o4k1k
ok3la
o2k1l4
ok5lii1n
ok2lii
ok1li
okoos4
o1ko
ok2o2o
o2k3r
ok4re
ok4ri
o5kris
ok4ru
o3la
o4lae
ola1g4
ola4las
ola1la
ola4su
ol4do
o4l1d
ole4an
o1le
ole1a2
3olek.
o4le4n1d
oli1g4
o1li
o3lii
o5lii4g
o4li4lu
o3lis
4o2l1j
ol4lae
o2l1l
ol1la
ollis4
ol1li
ol4lü
ol4mai
o2l1m
ol1ma
4o3lo
ol4ta1m
o2lt
ol1ta
ol4tar
1olu.
o1lu
5olukor
olu1k
olu1ko
o4lu4k1s
o1lõ4
o4lä
3oma1du
o1ma
oma1d
om4ba1j
o2m1b
om4ban
o4mok
o1mo
on4a4a
o1na
o4na1j
ona4ko
ona1k
o4neh
o1ne
o4ne1le
on4gos
o2n1g
oni2t4r
o1ni
on2it
oniu4
on3s2o2o4
o2ns
on3so
ont4re
o2nt
on4t1r
o4nuu
o1nu
2o2o
oo1a2
oo4kal
oo1ka
oo2k4r
oo4kää
oo1kä
oo4lau
oo3la
oo4lee
oo1le
oo4li1g4
oo1li
oo4li2s1t
oo3lis
oo4lo1g
o4o3lo
oo4lõ4
oo4lü
oo4nah
oo1na
oo4nar
oo4nau
oon4dee
oo4n1d
oon1de
oo2nk4
oo1no4
oo4noh
oo2ns4
oo2n5t4
o5op3ti
oo2p1t
oo4ra1t
oo1ra
oo4ree
oo1re
oo3sa
oo4sel
o4o1se
oo4tõ
o3pa
op5last.
o2p2l4
op1la
opla2s1t
op4lu
o2p3ra
op2r4
op3ri
o2r3ai
o1ra
o4ral1g
ora4mii
ora1m
ora1mi
o4ra1na
5ora1v
3orde1r
o4r1d
or1de
1o2r1g
or2g4r
o3r4ia4
o1ri
3orju
o2r1j
5orkes1t2e
o2r1k
or1k2e
ork4e2s1t
3or1na
o2r1n
or4nel
or1ne
or4t2al
o2rt
or1ta
or2t3r
oru4s
o1ru
or4võ
o2r1v
o2sa
o4sa1k2e
osa1k
3osa1ko
4o3sau
4o1se
o4se4k1s
4o2s1f2
o2si.
o1si
4osi1n
3os3kus
o2s1k
os1ku
4o2s1l
4o1so
o4sok
o2s1o2l
os4pa
o2s1p
4o2s1s
os4sa1j
os1sa
os4sü
os5taas
o2s1t
os2taa
os1ta
os4te1m
os1t2e
4os3t2o
ost4ru
os2t1r
5osu3ti
o1su
osu1t
4o1sõ
oto5a2
o3t2o
otok4
ot2ra
o2t1r
o3t2ran
3ot3s2a
o2t1s
o5t1sö
ot4ta
o2t1t
ot4teo
ot1t2e
otus4ta
o3tu
otu2s3t2
otü4l
o1tü
ou4b
ou4n
o1v
ovaa4l
ovio4
o3vi
ovol4
o4vu4n
o3õ4
oü2
1pa
paa4ka
paa1k
pa3a2m
paa4tel
p4aa1t
paa1t2e
pa4e
pa4lus
pa1lu
pa2ns4
pa4p2r4
pa1p
5paras
p4a1ra
3p4a2r1k
4par1ta
pa2rt
pa4s1p
4pb
4pd
1pe
pe2a
pea5j
pea2s4
pe4au
pea3v
pe4f
4pek2t1r
pe2kt
pe1la4
4pe4le1v
pe1le
pe2l4t
pe4lü4
p4e1m
pe4ol
5pe4r1d
pe1r
pe4ta1p
pe1t
pe3t4a
peti4k
pe3ti
2pf4
pg4
2ph
1pi
pii4v
pi2k5r
pi1k
pi4n2d
pi1n
pin4gis
pi2n1g
pip3r4
pi1p
pis5ta
pi2s1t
piu4g
2p1j
2p1k
2p2l4
4pla.
p1la
p4laa
p5laa1si
3pl4aa1t
p4la1k
p3la1m
p5la5ne.
pl2a1ne
3pla4s1m
5plasti1k
pl4as3ti
pla2s1t
plek2
p1le
3p4le4k1k
4p3le1m
p3li
5p4li4i4a
p5lii1g4
3plok
p1lo
p3lom
p3lu
3pluus
p3luu
4p1lõ
2p1m
2p1n
p4neu
p1ne
1p4o
5po4e
3pon
3p2o2o
poo4la
poola4v
por1di5
po4r1d
4p1p
ppa4k
p1pa
p4pas
ppe3a
p1pe
ppee4
p2p3l4
p2p3r4
p2p1s4
p4pud
p3pu
p4põ
p4pü
p2r4
2p1ra
3praa
3pra1g
3pra2kt
pra1k
p5rau
p4re1p
p1re
p4re1si
4pri1b
p1ri
2prii
p5ri2it
p4ro1g
p1ro
p4r2o2o
4p4ros
3p4rot
4pr4u1m
p1ru
5pruu
2p1s
p1s4a
p4sor
p1so
3p2sü
2p1t
p4ta1k
p1ta
3pu
pua4
pu4da
pu3ha4
pu2i4t
pu4k4k
pu1k
pul4ti
pu2lt
4pu2rt
pu2s4p4
2p1v
3põ
põ1hi3
p4õi
põi4s
põ1li4
5pö
1pä
pär4gu
pä2r1g
4päs
3pü
pü3he
pü2h
4pž
4pš
1ra
raa2l3a
4raa1le
5raal1ne
raa2l1n
raa4lu
raam4a3tu5
raa1m
raa1ma
raama1t
raa4ta4
r4aa1t
4raa2t1t
ra4a4tu
4raa1v
5ra4a1š
raie3
2rai1n
r3a2i1ne
rai4si
r5a4la.
ra1la
r4a2l4t
ra4lus
ra1lu
3ra1me
ra1m
ran4de1m
ra4n1d
ran1de
ra3o2m
4ra1pa
ra1p
ra4p2l4
4ra2p1t
4ra2r1b
ra4ren
ra1re
4ra2rs
2ra2r1v
ra4sal
ra1sa
4r1a2sen
ra1se
ra4sis
ra1si
ras2t4r
ra2s1t
ra4su1k
ra1su
ra4sul
ra4su1t
5ratas
ra1t
ra3ta
ratiiv5a
ratii4v
ra3ti
rat4sen
ra2t1s
rat1se
rau2a3
r4au2d
rau1d5o
2rau1k
4ra4u1m
4raur
rau4t2e
rau1t
5ra3vi
ra1v
2r1b
r4bae4
4r1d
r4dae4
r1da
r2dar
rde3a2
r1de
rd4re
r2d1r4
r4du1j
r1du
r4dü
1re
5re.
5rea
re1b2
ree4le
ree4ma.
ree1m
ree1ma
ree4man
3ree1r
3ree1t
5re1g
2re2hi
rei4se
re2k3l4
4re4k1s
re4lai
re1la
re4li2i4t
re3li
5re2l1v
re4mää
re1m
re3mä
4re4p1p
re1p
re2p2r4
1rep3re
re4si1n
re1si
re3t2e
re1t
re2t4k2
re4t1r
re2t5s
2r1f
r2f4r
2r1g
r4gae
r2g3ah
rga4re
r4geh
rgi4me
rgi1m
r5g2ra
r2g1r
2r1h
1ri
3r4ia
ri3a1m
ri5a2s1t
ria2s
5ri1d
2ri1g
ri4ga.
3rige
ri2g4r
2rii1b
5riie3
4rii1nu
rii1n
ri4kis
ri1k
ri1ki
ri3k4r
4ri2l1m
3ri1m
ri5o4k1s
rio4r
3ri1p
rip4li
ri2p2l4
4ri4p2p
4ri4sa.
ri1sa
ri4sah
ri4sar
ri4ses
ri1se
4ri2so2l
ri2so
ri4tol
r2it
ri3t2o
ri3t2r
5riu
5ri1v
4riö
2r1j
2r1k
r4ka1d
r1ka
r2k5ai1n
rka4se
r4kek
r1k2e
r2kt4
2r1l
2r1m
r4ma4l1d
r1ma
r3mal
r4me4le
r1me
r4mi1g
r1mi
rmo4ra
r1mo
rmo4s
r2mp4
r2m1s4
2r1n
r4na1d4
r1na
r4neh
r1ne
r2nk4
1ro
roa4
ro3e2
ro4k4k
rol4la
ro2l1l
rol4le
4ro2m1b
2ro1mi
ro4nop
ro1no
roo4ga
r2o2o
roo1g
4ro2sa
ros4p4o
ro2s1p
ro5staa3ti
ro2s1t
ros2taa
ros1ta
rost4aa1t
ros2t4r
4rož
2r1p
rp5re1t
rp2r4
rp1re
2r1r
r3ri
2rs
rsaa4l
r1sa
r3s2aa
r2s4l
r1s4o
r5s2p4o
r2s1p
rs4ta1k
r2s1t
rs1ta
2rt
r4tah
r1ta
r4tos
r3t2o
r2t2r
rt4ra
r4t1ri
rt5r2o2o
rt1ro
rt4sai
r2t1s
rt3s2a
1ru
2rua4
rud4
r4u1j
ru3k2r
ru1k
3ru1ma
r4u1m
4ru2m1b
2ru2m1m
4ru2mp
5rus.
ru4se1t
ru1se
ru4si1n
ru1si
ru4sul
ru1su
ru2t4r
ru1t
ruu1m5a
ru4u1m
4ruur
2r1v
rva4la
rva4lu
r4va2n1n
rva2s5k
rvi2s4k
r3vi
r4voh
r4vok
1rõ
2rõ2l
r5õ1li
rõn4gu
rõ2n1g
4rõr
3rö
4rö1k
röö4pe
röö2p5l4
1rä
4rä4r
1rü
4rük
4rüm
4rü4r
4rž
4rš
1sa
3s2aa
saa5j
saa4lu
s4a4an
4s3aas
3s4a3ba
sa1b
sa4bi.
4sabo
s4a1d
5sa1da
4sa1de
s5a2de1r
2s1ae
s2ae4l
5sa5ga
sa1g
s4age
sa4gu
4sa2h1v
3s4ai.
4sa4i2a
s1ai1n
s4ajan
sa1j
saj4a
sa4ju.
sak4ro
sa1k
sa2k2r
5s4a4k1s
4sa2ku.
sa1ku
sa4kää
sa1kä
4s3a4la.
sa1la
4salas
s4ala1t
5salat.
sa4le1v
sa1le
4sal1la
sa2l1l
4sal3lu
3s4a2lt
sa4l4u1m
sa1lu
5sa2l1v
3s4a1m
4sa2mp
4sa2nk
4s5a2n1n
s4a2nt
4san3ti
s4a1nu
sa4n4u1m
sa2p2r4
sa1p
sa4ris
s2a1ri
s4arv.
sa2r1v
sar4va
sa1se4
3s4as3ti
sa2s1t
sas4tii
sa4su1k
sa1su
sa4s4u1m
3sa1t
4sa2t1l4
s5ava4l1d
sa1v
sav4a
2s3b2
2s3d2
1se
3sea1d
se3a2la
s5ea3li
se5aval
sea2v
seav4a
se4du.
se1d
se1du
4see2la
see4le
s5ee2l1n
see4ma
see1m
4see1pi
see1p
3see1r
see4si
see4t4a4
see1t
4se4fe
se1f
5se1g
se3ga5a4
2seh
3s4e3ha
3se1ho
5se1hu
5s4e1hä
3sei
sei4d
sei4si
3se3ka
se3k2l4
4s5ek2s1p
se4k1s
se4la1j
se1la
4selek
se1le
5se3li
4sel3lu
se2l1l
3se2lt
2se2lu
4se2ns
se5oh
seo4k
se5om
seo4p
seo4r
3se1p
sepa1t4
se1pa
4sere1m
se1r
se1re
4s1e2se.
se1se
se4si.
se1si
4se4si1n
s5es2it
se2s4k
3s4e2s1t
4s5e2ten
se1t
se1t2e
se3t2r
4s1et1t2e
se2t1t
2s1f2
2s1g2
2s1h
1si
3s4ia
sia4h
sie4
5si2g1n
si1g
sig4ra
si2g1r
s5i4ha.
si3ha
sii4g
sii4sa
sii4se
4sii2t1s
si2it
sii4tu
si4ke1t
si1k
si1k2e
sik4o4d
si1ko
sik2s4p
si4k1s
si2kt4
sik4vi
si2k1v
4si2ni1m
si1n
si1ni
s5i4ni1me
4si2ns
sio4le
si4p4ro
si1p
sip2r4
4si2r1r
5si1ru
4si4sa.
si1sa
si3se
s5i4si1k
si1si
si2s2p
sis1se3
si2s1s
s5istu1j
si2s1t
sis3tu
5si3su
2s1j
sja5a4r
sj4a
s3jaa
2s1k
4s1ka
5skaa5la
ska4al
sk4a4no
5skeem.
s1k2e
skee1m
s4ke1la
5ske3le1t
ske1le
s5ken
ski4g
s1ki
s2k2l4
s5klas
sk2la
5skle1r
sk1le
sku1k4
s1ku
3s2ku2l1p
sku4si
s3kus
2s1l
4s1m
sma4sa
s1ma
2s1n
sne4p
s1ne
1so
3sobi
so1b
3soe
s1o4ht
3soi
s5oks.
so4k1s
2so2l
s3olek
so1le
s3oli1j
so1li
5s4o3lo
so3lu1t
so1lu
so2ma
3son
3s2o2o
s2o2o5o
4so4pe
so4p2r4
5sor.
s3o2r1j
3so2rt
2s1os
4sot1su
so2t1s
2s1p
s5pe1r
s1pe
2s1r
2s1s
sse2l4l
s1se
ss2fä
s2s1f2
ssi1n4
s1si
s4si2n1f
s2s1k4
ss4k2e
s2s2p
s5spor
ss1p4o
s2s2t
s5sta1t
ss1ta
ss4ti
ss2t5r
2s4su2s1s
s1su
2s1t
s2taa
s1ta
3staa1d
5sta4a1š
s4tai1n
4s3ta1m
5standa4r1d
s4tan1da
sta4n1d
3s2ta2rt
s4ta3ti
sta1t
5statis
s4te1no
s1t2e
5s2te4p1p
ste1p
ste1t4
s4toh
s3t2o
s4top
s4tot
5strei1k
s2t1r
st2re
st4rei
5strek
5struk3tu
st1ru
stru1k
stru2kt
st4so
s2t1s
s2t1t4
stu2s
s3tu
stu4s5a2
3s4t4uud
s1tä4
s4tär
s4tü2h
s1tü
1su
sua2
su4bar
su1b
su2d4r4
su4du
su5e
su4jul
su1j
4su4k1s
su1k
s5uks.
5sul1g
sul4ga
sun4de4
su4n1d
su4nis
su1ni
su1pe4
su1p
supe5s
su4p2r4
4su2r1b
5sus.
su3s4aa
su2sa2
su4ses
su1se
s5uss.
su2s1s
sus4ti
su2s1t
su4sun
su1su
su2t4r
su1t
su1t4ü
4suu1g
suu4ra
suu3sa3
2s1v
svu2s4t
1sõ
5sõ1da
sõ1d
sõ4de
4s3õh
3sõi1d
4sõie2
2s1õi1g
2s5õis
3sõ2it
sõ2l
2s3õ1li
3sõ2l1m
3sõn
2s1õ2p
5sõr
sõ1s4a4
s4õs
2s3õu
1sö
4sö1d
sö2k
2sön
1sä
4säk
4säm
1sü
4süa
5süd
5sü1g
2sü2h
s3ü1hi
2sük
s3ü4k1s
sü4la
sül5di
sü4l2d
sü3le1m
sü2le
4sü1lo
sü4ra
3süs
3sü4ü
4sž
1ta
3ta.
3taat.
t4aa1t
2tabi
ta1b
4t3ae2d
t3a2i1ne
tai1n
4tais
4ta4ko
ta1k
t2al
4ta4la1j
ta1la
ta4le1v
ta1le
tal4las
ta2l1l
tal1la
ta3lu
ta4lus
3ta1m
5ta1ma
4tan1da
ta4n1d
ta4ot
ta4p1la
ta1p
ta2p2l4
ta4p2r4
4ta4re1t
ta1re
4ta2r1m
2ta2rt
ta4se4r
ta1se
ta4ses
ta4se1t
ta4sis
ta1si
ta4sü
2tau1t
3ta1v
ta4va1j
tav4a
2t1b4
2td2
1t2e
te4aa2
tea4g
tea4h
te3an
te5a2s
3tee
tee5lu
te4e1t
te3hi
tehi4s
te3k2l4
te2k4r
te2k4v
tel4k2e
te2l1k
te4lok
te1lo
t1e3lu.
te2lu
4tepi1v
te1p
te1pi
te3p4l4
2te4p1p
3te1r
5ter.
te5r4a
te4r1d4
4te2r1g
te2rs4
te1s4a
te4si1n
te1si
te2s4k
2t1f
4t1g4
2t1h
thõl4
t1hõ
3ti
t4ia2
ti3e2
tii2k
tii4ma
tii1m
tii4sa
tii4ve
tii1v
ti1k4o
ti1k
ti3k4r
tik4vi
ti2k1v
4ti2l1m
5ti1ma
ti1m
4ti2n1f
ti1n
tin4ga
ti2n1g
ti4sai
ti1sa
tisa4v
ti4si1k
ti1si
ti3tee4
t2it
ti1t2e
2t1j
2t1k2
t3k1l4
t3k2r
2t1l4
t1lu4
2t1m
tmi1ka4
t1mi
tmi1k
tmi2s3
2t1n
3t2o
4to4da
t4o1d
toen4
to1g6
4toks.
to4k1s
toksi5k
tok1si
to4lek
to1le
t1o4lu.
to1lu
to4l4u1m
tonis4
to1ni
t2o4o
too4no4
too4pe
to4rau
to1ra
to2s4p
to2s1t4
2t3p4
2t1r
t4ra1d
t1ra
t2ra1f
3tra1fo
t4ra4h1h
5tra2l1l
t2ran
3tra2ns
t4ra1t2e
tra1t
5t4ra4u1m
t2re
3treen
t4rei
5trei1m
3tre1p
t3ri1g
t1ri
5t2rii1b
5trii1p
5trikoo.
tri1k
tri1ko
trik2o2o
tri4p2l4
t3ri1p
t4r4o2s1s
t1ro
3t2ru2m1m
t1ru
tr4u1m
5t4ru1p
3t2ruu
3t2rü
4trü4ü
2t1s
t3s2a
t3s4aa
tsa5is
t4saju
tsa1j
tsa4lan
tsa1la
3tse4h1h
t1se
t2seh
tse3la
5t4sel3lu
tse2l1l
5tsemen
tse1m
tse1me
4tse1na
5tsen1su
t4se2ns
tsi1g4
t1si
5t1siit1si
2t4sii2t1s
tsi2it
t4si2nk
tsi1n
5tsis1t2e
tsi2s1t
ts4laa
t2s1l
ts1la
t3s2o2o
t1so
t4sor
t2s1p4
t4su2s1s
t1su
2t1t
t4ta1d
t1ta
t3tee2
t1t2e
ttes2
t5tse1m
t2t1s
tt1se
3tu
4tui1m
tu4ra1j
tu1ra
tu2s1a2
tu4s5ee
tu1se
tu2si
tu2s3s
4t5uss.
tu2s3t2
tu5su
tu4sä4
tu2sü4
4tutop
tu1t
tu3t2o
4t4uud
tuu1le5
2t1v
1tõ
5tõ1b
2tõi
tõ4l
4tõ4n
4tõ2p
2t5õun
1tö
4töe
4tö1k
töö5k
töö3p
1tä
täh4t3a
tä4ht
tä5k2e
4täm
1tü
tü5he.
tü2h
tü1he
tü3hi
2t1ž
t2že
5tžek
3tže1m
uaa4
u3aa2s1t
ua2b
u5abi
u1ae
ua4he
uai4k
u3ai1n
ua2j4a
ua1j
u3a2kt
ua1k
ua2l
u3a2l1l
u3am4e1t
ua1m
ua1me
u1an
ua4nu
u5a2par
ua1p
ua1pa
ua2re
ua5ree
u5a2rt
ua2s
u3a2s1t
u1au
u4au4d
ua2v
u5ava4l1d
uav4a
u1b
u4da1lu
u1da
u4deh
u1de
udeo4
udo4r
u1do
u5eal
u3eel
u3e4k1s
ue2ma
ue1m
ueo4
ue4r
u1f
u1g
u4gi4d
ug2ra
u2g1r
u5graa
u4gü
uh4te1m
u4ht
uh1t2e
uh4te4r
uh4ti1n
uh3ti
uh4tis
ui1du5
ui1d
ui2ga
ui1g
ui4h
u3i3ha
u3i2l1m
5uim.
ui1m
ui1na2
ui1n
uin4as5
ui4si
3uisu1t
ui3su
uite4h
u2it
ui1t2e
ui4t2o
ui4va1la
ui1v
ui4ve4l
ui3ve
ui4vo
u1j
u5j4a
u4ju1j
u4j4u1m
3uju1mi
u1k
ukaa4s
u1ka
ukii4g
u1ki
uk1ki5
u4k1k
ukop4
u1ko
u3kraa
u2k2r
uk1ra
u5k1ro
uku4sa2
u1ku
u3kus
uk4vi
u2k1v
u1l4a
ul4deh
u4l1d
ul1de
ul1di4
ul4k2r
u2l1k
ul4len
u2l1l
ul1le
ul4lui
ul3lu
ult1ra5
u2lt
ul2t1r
u3lu
ulu1o4
4u1m
uma1f4
u1ma
u4mau
5umbroh
u2m1b
um2b1r
umb1ro
um4e1t4
u1me
u2m1f4
um4ia4
u1mi
um2it4
um5p4r4
u2mp
un4aa4
u1na
unaal5
un4da1k
u4n1d
un1da
un4dan
un4das
un4do
u4ni.
u1ni
u1o2
uo4h
u2or
u3o2r1g
u1p
u3p1la
u2p2l4
up3le
up1pe4
u4p1p
up4pis
up1pi
uraa4l
u1ra
u4ra1g
ura4ju
ura1j
u4ra1la
u4ra1p
u2r5au1k
ura4v4a
ura1v
3urb.
u2r1b
ur1de3
u4r1d
u5rea4
u1re
u4reos
ure2s4k
u4re2t1t
ure1t
urg4as4
u2r1g
5urge.
u4ri4m4e1t
u1ri
u3ri1m
uri1me
u4ri1ni
uri1n
uri2s4k
urka4v
u2r1k
ur1ka
uro4r
u1ro
u4ru3lu
u1ru
u2sa2
u5sa.
us4a1d4
u4s1ae
u4sa1lu
us5as1t2e
usa2s1t
u3sea
u1se
u5se1g4
u4s5elek
use1le
u4se2t1t
use1t
u3s4e3tu
u5se1v
us4fä
u2s1f2
u2s5g2
u4si4d
u1si
usi4g
u4si4h
us5i4n1d
usi1n
usi4va
usi1v
us3k2r
u2s1k
u2so
us5o2h
us3o1le
u2so2l
uso4r
u2s3p4
u3s4por
us1p4o
usui4
u1su
3usu1li
u4su2s1s
u2sõ
u2sü
u2s3ü2h
u1t
ut4k1l4
u2t1k2
utos2
u3t2o
utot4
ut4ru
u2t1r
ut4so
u2t1s
ut2t4r
u2t1t
utu4s
u3tu
utü4h
u1tü
uu5a2l
4uud
uu4du.
uu1du
uu4ka1k
uu1k
uu1ka
uuk5ri
uu2k2r
uu4mal
u4u1m
uu1ma
uu4mi2s
uu1mi
uu4pü
uu1p
uu4ran
uu1ra
uu4ras
uu4rau
uu4ra1v
uu3sa2
uusa3k
uus4k2e
uu2s1k
u3uss.
uu2s1s
uu4sul
uu1su
4uu1t
uu4tas
uu1ta
uu4tis
uu3ti
uu4tü4h
uu1tü
u5uu1ri
u1v
u4vau
u1õ2
uä2
u3är
u1ü2
u4ž4l
vaa4re4
va4as
4vabi
va1b
vae4r
va1g4
va5he
4va4i2a
4v3ai1n
va2k4v
va1k
va4la.
va1la
va4lai
4val1di
va4l1d
val4ga4
val1g
va4lü
vana5i
va1na
v2a1ne2
4v3an1ku
va2nk
van4ta
va2nt
van4t2e
vap4r4
va1p
v4ar
4va2r1m
va2r4p
2va2rt
4varuh
va1ru
4var3ve
va2r1v
va4sas
va1sa
va2s4k
vas2t4r
va2s1t
va2t4r
va1t
4vatud
v4a3tu
v4au4d
va1v4
4vb
2v1d
3ve
vee3a
vee3k
vee5la
vee3s1p
4ve2hi
vei4sa
ve4ol
veot4
ve4rah
ve1r
ve1ra
ve2s4p
2vf
2vg4
2v1h
3vi
v4ia2
vi1d4
4videa2
vi1de
vi1p4
vi3p2l4
vi2r4k
vi4sa1k
vi1sa
vis4k4o4d
vi2s1k
vis1ko
2v1j
2v1k
2v1l
2v1m
2v1n
voo5lu
v2o2o
2v5p
2v1r
2v1s
2v1t2
v3t1r
vu4sa2
vu4su4
2v1v
3võ
4või1g
4võ4li
4võm
võ2r4k5
3vä
vää4re4
vää4ru
3zo
zook4
z2o2o
zoos4
õ1b
õbe3
õ1d
õea4
õe4l4an
õe1la
õe3lu
õe1t2
õeu4
õ1g
õh4vu
õ2h1v
õ4i4a
õi4b
õie5k
õie2
3õiel
3õi3gu
õi1g
õi4ko
õi1k
õi1se4
õi4su4
õ1j
õk4kõ
õ4k1k
õ4l2d
õ4li1m
õ1li
õ4l2it
õl4ma1d
õ2l1m
õl1ma
3õm2b1l
õ2m1b
õne2t4s
õ1ne
õne1t
õ2p
õ3pa
õpi5e2
õ1pi
õp2pa
õ4p1p
4õp3r4
õran1da5
õ1ra
õra4n1d
õra4s
õr4da
õ4r1d
õr4gaa
õ2r1g
õr4gar
õr4go
4õs
õ1s4a
õ4tü4
õu1de3
õ1v4
õõ5p
õõ1re4
õõ4rel
õõ4ta
öa2
ö1b
ö1d
5ö2dee
ö1de
ödi4k
ö1di
3öe4l1d
öe1p4
öe2t4r
öe1t
ö1g4
ö1h
ö4i4a
öi4g
öi4s
ö1j
ö1k
ö2kon
ö1ko
3öko1no
ö2k4v
ö1l
öli2s
ö1li
öli1s5a
ö1m
öo2
ö2p3au
ö1pa
öp4lu
ö2p2l4
ö4raa
ö1ra
ö4rau
ör4da
ö4r1d
ör4de2l1l
ör1de
ö1r4e
ö4ri4l
ö1ri
ö4ro4
ö4rä
ö4rül
ö1rü
ös4tii
ö2s1t
ös3ti
ö4t2o4
ö4tü4
ö1v
ö3õ
öö3a2
öö3e4
öö5i
3öölan
öö1l
öö1la
öö3o2
öö2t4r
öü4
äa4
ä1b
äbus5
äbu1t4
ä1d
äe3a4
äe1o
ä1g
äg2a4ri
ähe5a
ä1he
äidi4s
äi1d
äi1di
äi1k2e3
äi1k
äi4lo
äi4lu
äi2s
äi1sa4
äi4sõ
äi4tar
ä2it
äi1ta
ä3j
ä2k2e
3äke.
ä5ke1r
ä4kõ
äli2s
ä1li
äl4ko
ä2l1k
5äm2b1l
ä2m1b
ä4nah
ä1na
än4deo
ä4n1d
än1de
äne1t4
ä1ne
än4t2o4
ä2nt
äok4
ä4ra5a
ä1ra
ära3o4
ä4rar
äre4lis
ä1re
äre3li
äre1t4
ärge4l
ä2r1g
ärii4
ä1ri
äri4k1l4
äri1k
äri4sel
äri1se
ä4ris1t2e
äri2s1t
är4kar
ä2r1k
är1ka
ä4ro
5ärr2it
ä2r1r
är3ri
är4ta
ä2rt
är2va
ä2r1v
ä4rõ
äs4ko
ä2s1k
äs4nai
ä2s1n
äs1na
ä2s4p
äs4san
ä2s1s
äs1sa
äs4so
ät4ri
ä2t1r
äu4d
ä1v
ävee4
ä3ve
ää4ki1n
ää1ki
ääo4
ää4ra1se
ää1ra
5ääris.
ää1ri
üaa4
ü1b
üda1me5
ü1da
ü5da1m
ü1g
ü2h
ühe5i4
ü1he
1ühen
3ühik.
ü1hi
ühi1k
ü3h2it
ü1j
ükan4
ü1ka
ük4kas
ü4k1k
ük1ka
ü2k3l4
ü4l2d
ül1d5a2
ü2le
3ülek
ü5lel
üle4mas
üle1m
üle1ma
1üle2s
üle1sä4
ül4ga1v
ül1g
ül4gee
ü4lih
ü1li
ülii4
3üli1ko
üli1k
ü4li1õ2
ül5k1l4
ü2l1k
ül5la
ü2l1l
ül4mei
ü2l1m
ül1me
ülo4r
ü1lo
ü2l1p4
ü2lt4
üma4ra1k
ü1ma
üm4a1ra
üma4ru
ümi4ko
ü1mi
ümi1k
ü5ne
üni4s
ü1ni
üot4
ü2p3l4
ü4p5p4
üp3r4
üp1su4
ü2p1s
ü4r1d4
üri1k4
ü1ri
ü1s4o
üs4p2r4
ü2s1p
üs4t2al
ü2s1t
üs1ta
üs4tas
üta4r
ü1ta
ütee4l
ü1t2e
ü3tee
5ütle1v
ü2t1l4
üt1le
ü4t3r
üt4ru
ü1v
ü4ü
üü2l3a2
üü4le
üü4lo
üü4pe
1ža
4žb
1že
5žee
5že1r
4žf
1ži
4žj
4žk
4žl
4žm
4žn
3žo
4žp
4žt
5žö
5žü
2žž
3ša
4ša1k
4šau
1še
3š4i
ši2s4k
4šk
5šo
4št
3šu4
PK
!<1}hyphenation/hyph_fi.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1ba
1be
1bi
1bo
1bu
1by
1da
1de
1di
1do
1du
1dy
1dä
1dö
1fa
1fe
1fi
1fo
1fu
1fy
1ga
1ge
1gi
1go
1gu
1gy
1gä
1gö
1ha
1he
1hi
1ho
1hu
1hy
1hä
1hö
1ja
1je
1ji
1jo
1ju
1jy
1jä
1jö
1ka
1ke
1ki
1ko
1ku
1ky
1kä
1kö
1la
1le
1li
1lo
1lu
1ly
1lä
1lö
1ma
1me
1mi
1mo
1mu
1my
1mä
1mö
1na
1ne
1ni
1no
1nu
1ny
1nä
1nö
1pa
1pe
1pi
1po
1pu
1py
1pä
1pö
1ra
1re
1ri
1ro
1ru
1ry
1rä
1rö
1sa
1se
1si
1so
1su
1sy
1sä
1sö
1ta
1te
1ti
1to
1tu
1ty
1tä
1tö
1va
1ve
1vi
1vo
1vu
1vy
1vä
1vö
1st2r
ä2y
y1a2
y1o2
o1y
ö2y
u1y2
y1u2
ö3a2
ö3o2
ä3a2
ä3o2
ä1u2
ö1u2
a1ä
a1ö
o1ä
o1ö
u1ä2
u1ö2
ä2ä
ö2ö
ä2ö
ö2ä
aa1i2
aa1e2
aa1o2
aa1u2
ee1a2
ee1i2
ee1u2
ee1y2
ii1a2
ii1e2
ii1o2
uu1a2
uu1e2
uu1o2
uu1i2
e1aa
i1aa
o1aa
u1aa
u1ee
a1uu
i1uu
e1uu
o1uu
ää1i
ää1e
ää3y
i1ä2ä
e1ä2ä
y1ä2ä
iö2ö
a1ei
a1oi
e1ai
i1au
y1ei
ai1a
ai1e
ai1o
ai1u
au1a
au1e
eu1a
ie1a
ie1o
ie1y
io1a2
io1e2
iu1a
iu1e
iu1o
oi1a
oi1e
oi1o
oi1u
o1ui
ou1e
ou1o
ue1a
ui1e
uo1a
uo1u
e1ö2
ö1e2
.ä2
u2s
y1li
yli1o2p
a1li
ali1a2v
sp2l
sp1li
a1lo
alou2s1
keu2s1
r1ta
rtau2s1
soh1je
sa1si
2s1a2sia
a1si
1a2sian
1a2siat
1a2sioi
l2as
so1pi
2s1o2pisk
no1pe
2n1o2pet
sa1lo
2s1a2loi
no1pi
2n1o2pist
2s1o2pist
so1sa
no1sa
al1ke
alkei2s1
pe1ru
peru2s1
si1de
2s1i2dean
se1si
sesi1ty
ne1du
2n1e2du2s
sa1ja
saja1tu
2s1a1se
2s1a1pu
sy1ri
2s1y2rit
.y1di
.ydi2n1
.1su
.suu1ra
2s1y2h1ti
not1to
no1to
2n1oton
nan1to
2n1an1no
na1ja
2n1ai1ka
no1ma
2n1o2mai
ny1li
2n1y2lit
sa1le
2s1a2len
na1le
2n1a2len
asia1ka
1a2siaka2s1
u1lo
ulo2s1
na1jo
2s1a2jo
b1lo
bib3li
b1ri
b1ro
1b2ru
d2r
1d2ra
f2l
1f2la
f1ra
1f2re
g2l
1g2lo
g2r
1g2ra
k2l
k1ra
k1re
1k2ri
1k2v
1k2va
p2l
p2r
1p2ro
c2l
q2v
1q2vi
sc2h
ts2h
ch2r
PK
!<0k6868hyphenation/hyph_fr.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
2'2
2’2
.a4
'a4
’a4
.â4
'â4
’â4
ab2h
.ab3réa
.a1b2r
.ab1ré
'ab3réa
'a1b2r
'ab1ré
’ab3réa
’a1b2r
’ab1ré
ad2h
a1è2d1re
aè1d2r
.ae3s4c2h
'ae3s4c2h
’ae3s4c2h
1alcool
al1co
a2l1al1gi
a1la
.amino1a2c
.a1mi
.ami1no
'amino1a2c
'a1mi
'ami1no
’amino1a2c
’a1mi
’ami1no
.ana3s4t2r
.a1na
'ana3s4t2r
'a1na
’ana3s4t2r
’a1na
1a2nesthé1si
a1ne
anes1t2h
anest1hé
.anti1a2
.an1ti
'anti1a2
'an1ti
’anti1a2
’an1ti
.anti1e2
'anti1e2
’anti1e2
.anti1é2
.anti2en1ne
'anti2en1ne
’anti2en1ne
'anti1é2
’anti1é2
.anti1s2
'anti1s2
’anti1s2
.apo2s3ta
.a1po
'apo2s3ta
'a1po
’apo2s3ta
’a1po
apo2s3t2r
a1po
archi1é2pis
ar1c2h
arc1hi
archié1pi
.as2ta
'as2ta
’as2ta
a2s3t1ro
as1t2r
1ba
1bâ
.bai2se3main
.bai1se
.baise1ma
1be
1bé
1bè
1bê
4be.
4bes.
2bent.
1bi
1bî
.bi1a2c
.bi1a2t
.bi1au
.bio1a2
.bi2s1a2
.bi1u2
1b2l
4b4le.
b1le
4b4les.
2b2lent.
1bo
1bô
1b2r
4b4re.
b1re
4b4res.
2b2rent.
1bu
1bû
1by
1ç
1ca
1câ
ca3ou3t2
1ce
1cé
1cè
1cê
4ce.
4ces.
2cent.
1j
ja3cent.
ja1ce
ac3cent.
ac1ce
é3cent.
é1ce
1mu
munifi3cent.
mu1ni
muni1fi
munifi1ce
1ré
réti3cent.
ré1ti
réti1ce
1p2r
privatdo3cent.
p1ri
pri1va
privat1do
privatdo1ce
inno3cent.
in1no
inno1ce
es3cent.
es1ce
acquies4cent.
ac1q
acquies1ce
is3cent.
is1ce
immis4cent.
im1mi
immis1ce
.c2h4
1c2h
4ch.
2chb
4c4he.
c1he
4c4hes.
2chent.
.chè2vre3feuil1le
.c1hè
.chè1v2r
.chèv1re
.chèvre1fe
.chèvrefeuil2l
2chg
ch2l
4ch4le.
ch1le
4ch4les.
chlo2r3a2c
ch1lo
chlo1ra
chlo2r3é2t
chlo1ré
2chm
2chn
2chp
ch2r
4ch4re.
ch1re
4ch4res.
2chs
2cht
2chw
1ci
1cî
.ci2s1alp
.ci1sa
1c2k
4ck.
2ckb
4c4ke.
c1ke
4c4kes.
2c2kent.
2ckf
2ckg
2c1k3h
2ckp
2cks
2ckt
1c2l
4c4le.
c1le
4c4les.
2c2lent.
1co
1cô
co1acc
co1ac1q
co1a2d
co1ap
co1ar
co1assoc
coas1so
co1assur
coas1su
co1au
co1ax
1cœ
co1é2
co1ef
co1en
co1ex
.con4
.cons4
.contre1s2c
.con1t2r
.cont1re
.cont1re3maît1re
.contre1ma
.contremaî1t2r
co2nurb
co1nu
.co1o2
.co2o3lie
.coo1li
1c2r
4c4re.
c1re
4c4res.
2c2rent.
1cu
1cû
1cy
.cul4
1d2'2
1d2’2
1da
1dâ
.dacryo1a2
.da1c2r
.dac1ry
d1d2h
1de
1dé
1dè
1dê
4de.
4des.
2dent.
déca3dent.
dé1ca
déca1de
é3dent.
é1de
cci3dent.
c1ci
cci1de
inci3dent.
in1ci
inci1de
confi3dent.
con1fi
confi1de
1t2r
tri3dent.
t1ri
tri1de
1di
dissi3dent.
dis1si
dissi1de
chien3dent.
c1hi
chien1de
.ar3dent.
.ar1de
impu3dent.
im1pu
impu1de
pru3dent.
p1ru
pru1de
.dé1a2
.dé1io
.dé1o2
.dé2s
.dé3s2a3c2r
.dé1sa
.dés2a3m
.dé3s2a3tell
.désa1te
.dé3s2as1t2r
.dé3s2c
.dé2s1é2
.dé3s2é3g2r
.dé3s2ensib
.dé1se
.désen1si
.dé3s2ert
.dé3s2exu
.dé2s1i2
.dé3s2i3d
.dé3s2i3g2n
.dé3s2i3li
.dé3s2i3nen
.dési1ne
.dé3s2in1vo
.dé3s2i3r
.dé3s2ist
.dé3s2o3dé
.dé1so
.dé2s1œ
.dé3s2o3l
.dé3s2o3pil
.déso1pi
.dé3s2orm
.dé3s2orp
.dé3s2ou1f2r
.dé3s2p
.dé3s2t
.dé2s1u2n
.dé1su
3d2hal
d1ha
3d2houd
d1ho
1dî
di2s3cop
dis1co
.di1a2cé
.di1a2cid
.dia1ci
.di1ald
.di1a2mi
.di1a2tom
.dia1to
.di1e2n
.di2s3h
2d2lent.
d1le
1do
1dô
1d2r
4d4re.
d1re
4d4res.
2d2rent.
d1s2
1du
1dû
1dy
.dy2s3
.dy2s1a2
.dy2s1i2
.dy2s1o2
.dy2s1u2
.e4
'e4
’e4
.ê4
'ê4
’ê4
.é4
'é4
’é4
.è4
'è4
’è4
éd2hi
1é2drie
é1d2r
éd1ri
1é2drique
édri1q
1é2lec1t2r
é1le
1é2lément
é1lé
élé1me
.e1n1a2
'e1n1a2
’e1n1a2
1é2nerg
é1ne
e2n1i2v2r
e1ni
.e1n1o2
'e1n1o2
’e1n1o2
épi2s3cop
é1pi
épis1co
épi3s4co1pe
e2s3cop
es1co
.eu2r1a2
'eu2r1a2
’eu2r1a2
eu1s2tat
eus1ta
ext1ra1
ex1t2r
extra2c
extra2i
1fa
1fâ
1fe
1fé
1fè
1fê
4fe.
4fes.
2fent.
1fi
1fî
1f2l
4f4le.
f1le
4f4les.
2f2lent.
1fo
1fô
1f2r
4f4re.
f1re
4f4res.
2f2rent.
f1s2
1fu
1fû
1fy
1ga
1gâ
1ge
1gé
1gè
1gê
4ge.
4ges.
2gent.
ré3gent.
ré1ge
entre3gent.
en1t2r
ent1re
entre1ge
indi3gent.
in1di
indi1ge
dili3gent.
di1li
dili1ge
intelli3gent.
in1te
intel1li
intelli1ge
indul3gent.
in1du
indul1ge
1ta
tan3gent.
tan1ge
1ri
rin3gent.
rin1ge
contin3gent.
con1ti
contin1ge
.ar3gent.
.ar1ge
'ar3gent.
'ar1ge
’ar3gent.
’ar1ge
1se
ser3gent.
ser1ge
1te
ter3gent.
ter1ge
résur3gent.
ré1su
résur1ge
1g2ha
1g2he
1g2hi
1g2ho
1g2hy
1gi
1gî
1g2l
4g4le.
g1le
4g4les.
2g2lent.
1g2n
'a2g3nat
'a1g2n
'ag1na
’a2g3nat
’a1g2n
’ag1na
.a2g3nat
.a1g2n
.ag1na
a2g3nos
a1g2n
ag1no
co2g3ni1ti
co1g2n
cog1ni
'i4
'i2g3né
'i1g2n
’i4
’i2g3né
’i1g2n
.i4
.i2g3né
.i1g2n
'i2g3ni
’i2g3ni
.i2g3ni
.ma2g3nici1de
.ma1g2n
.mag1ni
.magni1ci
.ma2g3nificat
.magni1fi
.magnifi1ca
.ma2g3num
.mag1nu
o2g3nomo1ni
o1g2n
og1no
ogno1mo
o2g3no1si
.pro2g3na1t2h
.p2r
.p1ro
.pro1g2n
.prog1na
1pu
pu2g3nab1le
pu1g2n
pug1na
pugna1b2l
pu2g3nac
.sta2g3n
.s1ta
.syn2g3na1t2h
.syn1g2n
.syng1na
1wa
wa2g3n
4g4ne.
g1ne
4g4nes.
2g2nent.
1go
1gô
1g2r
4g4re.
g1re
4g4res.
2g2rent.
1gu
1gû
g1s2
4gue.
4gues.
2guent.
.o4
.on3guent.
.on1gu
'o4
'on3guent.
'on1gu
’o4
’on3guent.
’on1gu
1gy
1ha
1hâ
1he
1hé
1hè
1hê
hémi1é
hé1mi
hémo1p2t
hé1mo
4he.
4hes.
1hi
1hî
1ho
1hô
1hu
1hû
1hy
hype4r1
hype1ra2
hy1pe
hype1re2
hype1ré2
hype1ri2
hype1ro2
hypers2
hype1ru2
hypo1a2
hy1po
hypo1e2
hypo1é2
hypo1i2
hypo1o2
hypo1s2
hypo1u2
.î4
'î4
’î4
i1al1gi
i1arth2r
iar1t2h
i1è2d1re
iè1d2r
il2l
cil3l
rcil4l
r1ci
ucil4l
u1ci
1va
vacil4l
va1ci
gil3l
hil3l
1li
lil3l
l3lion
l1li
1mi
mil3l
mil4let
mil1le
émil4l
é1mi
semil4l
se1mi
rmil4l
r1mi
armil5l
ar1mi
capil3l
ca1pi
1pa
papil3la
pa1pi
papil2l
papil3le
papil3li
papil3lom
papil1lo
pupil3l
pu1pi
1pi
piril3l
pi1ri
1t2h
th2r
thril3l
th1ri
cyril3l
cy1ri
ibril3l
i1b2r
ib1ri
pusil3l
pu1si
.stil3l
.s1ti
distil3l
dis1ti
instil3l
ins1ti
fritil3l
f1ri
fri1ti
boutil3l
bou1ti
vanil3lin
va1ni
vanil2l
vanil1li
vanil3lis
1vi
vil3l
avil4l
a1vi
chevil4l
che1vi
uevil4l
ue1vi
uvil4l
u1vi
xil3l
1informat
in1fo
infor1ma
.i1n1a2
'i1n1a2
’i1n1a2
.in2a3nit
.ina1ni
'in2a3nit
'ina1ni
’in2a3nit
’ina1ni
.in2augur
.inau1gu
'in2augur
'inau1gu
’in2augur
’inau1gu
.i1n1e2
'i1n1e2
’i1n1e2
.i1n1é2
'i1n1é2
’i1n1é2
.in2effab
.inef1fa
'in2effab
'inef1fa
’in2effab
’inef1fa
.in2é3luc1ta
.iné1lu
'in2é3luc1ta
'iné1lu
’in2é3luc1ta
’iné1lu
.in2é3nar1ra
.iné1na
'in2é3nar1ra
'iné1na
’in2é3nar1ra
’iné1na
.in2ept
'in2ept
’in2ept
.in2er
'in2er
’in2er
.in2exo1ra
'in2exo1ra
’in2exo1ra
.i1n1i2
'i1n1i2
’i1n1i2
.in2i3mi1ti
.ini1mi
'in2i3mi1ti
'ini1mi
’in2i3mi1ti
’ini1mi
.in2i3q
'in2i3q
’in2i3q
.in2i3t
'in2i3t
’in2i3t
.i1n1o2
'i1n1o2
’i1n1o2
.in2o3cul
.ino1cu
'in2o3cul
'ino1cu
’in2o3cul
’ino1cu
.in2ond
'in2ond
’in2ond
.in1s2tab
.ins1ta
'in1s2tab
'ins1ta
’in1s2tab
’ins1ta
'inte4r3
'in1te
’inte4r3
’in1te
.inte4r3
.inte1ra2
.in1te
'inte1ra2
’inte1ra2
.inte1re2
'inte1re2
’inte1re2
.inte1ré2
'inte1ré2
’inte1ré2
.inte1ri2
'inte1ri2
’inte1ri2
.inte1ro2
'inte1ro2
’inte1ro2
.inte1ru2
'inte1ru2
’inte1ru2
.inters2
'inters2
’inters2
.i1n1u2
'i1n1u2
’i1n1u2
.in2uit
'in2uit
’in2uit
.in2u3l
'in2u3l
’in2u3l
io1a2ct
i1oxy
i1s2tat
is1ta
2jk
4je.
4jes.
2jent.
1ka
1kâ
1ke
1ké
1kè
1kê
4ke.
4kes.
2kent.
1k2h
4kh.
.k2h4
1ki
1kî
1ko
1kô
1k2r
1ku
1kû
1ky
1la
1lâ
1là
la2w3re
la1w2r
1le
1lé
1lè
1lê
4le.
4les.
2lent.
.ta3lent.
.ta1le
iva3lent.
i1va
iva1le
équiva4lent.
é1q
équi1va
équiva1le
1mo
monova3lent.
mo1no
mono1va
monova1le
1po
polyva3lent.
po1ly
poly1va
polyva1le
1re
re3lent.
re1le
.do3lent.
.do1le
indo3lent.
in1do
indo1le
inso3lent.
in1so
inso1le
1tu
turbu3lent.
tur1bu
turbu1le
1su
succu3lent.
suc1cu
succu1le
fécu3lent.
fé1cu
fécu1le
trucu3lent.
t1ru
tru1cu
trucu1le
opu3lent.
o1pu
opu1le
corpu3lent.
cor1pu
corpu1le
1ru
ru3lent.
ru1le
1s2por
sporu4lent.
s1po
spo1ru
sporu1le
1lî
1lo
1lô
l1s2t
1lu
1lû
1ly
1ma
1mâ
.ma2c3k
.macro1s2c
.ma1c2r
.mac1ro
.ma2l1a2dres
.ma1la
.mala1d2r
.malad1re
.ma2l1a2d1ro
.ma2l1ai1sé
.ma2l1ap
.ma2l1a2v
.ma2l1en
.ma1le
.ma2l1int
.ma1li
.ma2l1oc
.ma1lo
.ma2l1o2d
.ma2r1x
1me
1mé
1mè
1mê
.mé2g1oh
.mé1go
.mé2sa
.mé3san
.mé2s1es
.mé1se
.mé2s1i
.mé2s1u2s
.mé1su
.mé1ta1s2ta
.mé1ta
4me.
4mes.
â2ment.
â1me
da2ment.
da1me
fa2ment.
fa1me
amalga2ment.
a1ma
amal1ga
amalga1me
cla2ment.
c1la
cla1me
1ra
ra2ment.
ra1me
tempéra3ment.
tem1pé
tempé1ra
tempéra1me
ta2ment.
ta1me
testa3ment.
tes1ta
testa1me
1q
qua2ment.
qua1me
è2ment.
è1me
carê2ment.
ca1rê
carê1me
diaphrag2ment.
dia1p2h
diaph2r
diaph1ra
diaphrag1me
1ry
ryth2ment.
ry1t2h
ry2thm
ryth1me
ai2ment.
ai1me
rai3ment.
rai1me
abî2ment.
a1bî
abî1me
éci2ment.
é1ci
éci1me
vidi2ment.
vi1di
vidi1me
subli2ment.
su1b2l
sub1li
subli1me
éli2ment.
é1li
éli1me
reli2ment.
re1li
reli1me
mi2ment.
mi1me
ani2ment.
a1ni
ani1me
1ve
veni2ment.
ve1ni
veni1me
ri2ment.
ri1me
détri3ment.
dé1t2r
dét1ri
détri1me
1nu
nutri3ment.
nu1t2r
nut1ri
nutri1me
inti2ment.
in1ti
inti1me
esti2ment.
es1ti
esti1me
l2ment.
l1me
flam2ment.
f1la
flam1me
gram2ment.
g1ra
gram1me
.gem2ment.
.gem1me
om2ment.
om1me
.com3ment.
.com1me
ô2ment.
ô1me
slalo2ment.
s1la
sla1lo
slalo1me
chro2ment.
ch1ro
chro1me
1to
to2ment.
to1me
ar2ment.
ar1me
.sar3ment.
.sar1me
er2ment.
er1me
antifer3ment.
an1ti
anti1fe
antifer1me
.ser3ment.
.ser1me
fir2ment.
fir1me
or2ment.
or1me
as2ment.
as1me
au2ment.
au1me
écu2ment.
é1cu
écu1me
fu2ment.
fu1me
hu2ment.
hu1me
fichu3ment.
fi1c2h
fic1hu
fichu1me
llu2ment.
l1lu
llu1me
1p2l
plu2ment.
p1lu
plu1me
bou2ment.
bou1me
bru2ment.
b1ru
bru1me
su2ment.
su1me
tu2ment.
tu1me
1mî
.milli1am
.mil3l
.mil1li
1m2né1mo
m1né
1m2nès
m1nè
1m2né1si
1mô
1mœ
.mono1a2
.mo1no
.mono1e2
.mono1é2
.mono1i2
.mono1ï2dé
.mono1o2
.mono1u2
.mono1s2
mon2t3réal
mon1t2r
mont1ré
m1s2
1mû
1my
moye2n1â2g
moye1nâ
1na
1nâ
1ne
1né
1nè
1nê
4ne.
4nes.
2nent.
réma3nent.
ré1ma
réma1ne
imma3nent.
im1ma
imma1ne
1pe
perma3nent.
per1ma
perma1ne
.émi3nent.
.é1mi
.émi1ne
préémi3nent.
p1ré
préé1mi
préémi1ne
proémi3nent.
p1ro
proé1mi
proémi1ne
surémi3nent.
su1ré
suré1mi
surémi1ne
immi3nent.
immi1ne
conti3nent.
conti1ne
perti3nent.
per1ti
perti1ne
absti3nent.
abs1ti
absti1ne
1ni
1nî
1no
1nô
1nœ
.no2n1obs
.no1no
1nû
n3s2at.
n1sa
n3s2ats.
n1x
1ny
'ô4
’ô4
.ô4
o2b3long
o1b2l
ob1lo
1octet
oc1te
o1d2l
o1è2d1re
oè1d2r
o1io1ni
ombud2s3
om1bu
omni1s2
om1ni
o1s2tas
os1ta
o1s2tat
o1s2té1ro
os1té
o1s2tim
os1ti
o1s2tom
os1to
o1s2trad
os1t2r
ost1ra
o1s2tra1tu
o1s2triction
ost1ri
ostric1ti
.oua1ou
'oua1ou
’oua1ou
.ovi1s2c
.o1vi
'ovi1s2c
'o1vi
’ovi1s2c
’o1vi
oxy1a2
1pâ
paléo1é2
pa1lé
.pa2n1a2f
.pa1na
.pa2n1a2mé
.pa2n1a2ra
.pa2n1is
.pa1ni
.pa2n1o2p2h
.pa1no
.pa2n1opt
.pa2r1a2c1he
.pa1ra
.para1c2h
.pa2r1a2c1hè
.para1s2
.pa2r3hé
.pa1r2h
1pé
1pè
1pê
4pe.
4pes.
2pent.
re3pent.
re1pe
.ar3pent.
.ar1pe
'ar3pent.
'ar1pe
’ar3pent.
’ar1pe
ser3pent.
ser1pe
.pen2ta
pe1r3h
pé2nul
pé1nu
.pe4r
.pe1r1a2
.pe1r1e2
.pe1r1é2
.pe1r1i2
.pe1r1o2
.pe1r1u2
pé1r2é2q
pé1ré
.péri1os
.pé1ri
.péri1s2
.péri2s3s
.péri2s3ta
.péri1u2
1p2h
.p2h4
4ph.
.phalan3s2t
.p1ha
.pha1la
4p4he.
p1he
4p4hes.
2phent.
ph2l
4ph4le.
ph1le
4ph4les.
2phn
photo1s2
p1ho
pho1to
ph2r
4ph4re.
ph1re
4ph4res.
2phs
2pht
3ph2ta1lé
ph1ta
3ph2tis
ph1ti
1pî
4p4le.
p1le
4p4les.
2p2lent.
.pluri1a
.p2l
.p1lu
.plu1ri
1p2né
1p2neu
p1ne
1pô
po1ast1re
poas1t2r
poly1a2
poly1e2
poly1é2
poly1è2
poly1i2
poly1o2
poly1s2
poly1u2
.pon2tet
.pon1te
.pos2t3h
.pos2t1in
.pos1ti
.pos2t1o2
.pos2t3r
.post1s2
4p4re.
p1re
4p4res.
2p2rent.
.pré1a2
.p1ré
.pré2a3la
.pré2au
.pré1é2
.pré1e2
.pré1i2
.pré1o2
.pré1u2
.pré1s2
.pro1é2
.pro1s2cé
pro2s3tat
pros1ta
.prou3d2h
1p2sy1c2h
p1sy
.psycho1a2n
.p2sy1c2h
.p1sy
.psyc1ho
1p2tèr
p1tè
1p2tér
p1té
.pud1d2l
1pû
1py
4que.
4ques.
2quent.
é3quent.
élo3quent.
é1lo
élo1q
grandilo3quent.
gran1di
grandi1lo
grandilo1q
1râ
radio1a2
ra1di
1rè
1rê
.ré1a2
.ré2a3le
.ré2a3lis
.réa1li
.ré2a3lit
.ré2aux
.ré1é2
.ré1e2
.ré2el
.ré2er
.ré2èr
.ré1i2
.ré2i3fi
.ré1o2
.re1s2
.re2s3cap
.res1ca
.re2s3ci1si
.res1ci
.re2s3ci1so
.re2s3cou
.res1co
.re2s3c1ri
.res1c2r
.re2s3pect
.res1pe
.re2s3pir
.res1pi
.re2s3plend
.res1p2l
.resp1le
.re2s3pons
.res1po
.re2s3quil
.res1q
.re2s3s
.re2s3t
.re3s4tab
.res1ta
.re3s4tag
.re3s4tand
.re3s4tat
.re3s4tén
.res1té
.re3s4tér
.re3s4tim
.res1ti
.re3s4tip
.re3s4toc
.res1to
.re3s4top
.re3s4t2r
.re4s5trein
.rest1re
.re4s5trict
.rest1ri
.re4s5trin
.re3s4tu
.re3s4ty
.réu2
.ré2uss
.rétro1a2
.ré1t2r
.rét1ro
4re.
4res.
2rent.
.pa3rent.
.pa1re
appa3rent.
ap1pa
appa1re
tran2s3p
transpa3rent.
t1ra
trans1pa
transpa1re
é3rent.
é1re
tor3rent.
tor1re
cur3rent.
cur1re
1r2h
4r4he.
r1he
4r4hes.
2r3heur
2r3hy1d2r
r1hy
1rî
1ro
1rô
1rû
1sa
1sâ
.s2c2h4
1s2ca1p2h
s1ca
1s2clér
s1c2l
sc1lé
1s2cop
s1co
1s2c2h
e2s3c2h
i2s3c1hé
i1s2c2h
i2s3chia
isc1hi
i2s3chio
4s4ch.
4s4c4he.
sc1he
4s4c4hes.
2s2chs
1sé
1sè
1sê
sesqui1a2
ses1q
4se.
4ses.
2sent.
ab3sent.
ab1se
pré3sent.
pré1se
.res3sent.
.res1se
.seu2le
.s2h4
1s2h
4sh.
4s4he.
s1he
4s4hes.
2shent.
2shm
2s3hom
s1ho
2shr
2shs
1si
1sî
1s2lav
1s2lov
s1lo
1so
1sô
1sœ
1s2patia
s1pa
spa1ti
1s2perm
s1pe
1s2phèr
s1p2h
sp1hè
1s2phér
sp1hé
1s2piel
s1pi
1s2piros
spi1ro
1s2tandard
s1ta
stan1da
1s2tein
s1te
stéréo1s2
s1té
sté1ré
1s2tigm
s1ti
1s2to1c2k
s1to
1s2tomos
sto1mo
1s2tro1p2h
s1t2r
st1ro
1s2truc1tu
st1ru
1s2ty1le
s1ty
1sû
.su2b1a2
.su3b2alt
.su2b1é2
.su3b2é3r
.su2b1in
.su1bi
.su2b3limin
.su1b2l
.sub1li
.subli1mi
.su2b3lin
.su2b3lu
sub1s2
.su2b1ur
.su1bu
supe4r1
supe1ro2
su1pe
supers2
.su2r1a2
su3r2ah
su1ra
.su3r2a3t
.su2r1e2
.su3r2eau
.su3r2ell
.su3r2et
.su2r1é2
.su2r3h
.su2r1i2m
.su1ri
.su2r1inf
.su2r1int
.su2r1of
.su1ro
.su2r1ox
1sy
1tâ
1tà
tachy1a2
ta1c2h
tac1hy
tchin3t2
t1c2h
tc1hi
1té
1tè
1tê
télé1e2
té1lé
télé1i2
télé1o2b
télé1o2p
télé1s2
4te.
4tes.
2tent.
.la3tent.
.la1te
.pa3tent.
.pa1te
compé3tent.
com1pé
compé1te
éni3tent.
é1ni
éni1te
mécon3tent.
mé1co
mécon1te
omnipo3tent.
omni1po
omnipo1te
ventripo3tent.
ven1t2r
vent1ri
ventri1po
ventripo1te
équipo3tent.
équi1po
équipo1te
impo3tent.
im1po
impo1te
mit3tent.
mit1te
.t2h4
4th.
4t4he.
t1he
4t4hes.
thermo1s2
ther1mo
2t3heur
2thl
2thm
2thn
4th4re.
th1re
4th4res.
2ths
1ti
1tî
1tô
tran2s1a2
tran3s2act
tran3s2ats
tran2s3h
tran2s1o2
tran2s1u2
4t4re.
t1re
4t4res.
2t2rent.
.tri1a2c
.t2r
.t1ri
.tri1a2n
.tri1a2t
.tri1o2n
t1t2l
1tû
tung2s3
1ty
.u4
'u4
’u4
.û4
'û4
’û4
uni1o2v
u1ni
uni1a2x
u2s3t2r
1vâ
1vé
1vè
1vê
vélo1s2ki
vé1lo
4ve.
4ves.
2vent.
conni3vent.
con1ni
conni1ve
.sou3vent.
.sou1ve
1vî
1vo
1vô
vol2t1amp
vol1ta
1v2r
4v4re.
v1re
4v4res.
2v2rent.
1vu
1vû
1vy
1we
4we.
4wes.
2went.
1wi
1wo
1wu
1w2r
2xent.
.y4
'y4
’y4
y1as1t2h
y1s2tom
ys1to
y1al1gi
1za
1ze
1zé
1zè
4ze.
4zes.
2zent.
privatdo3zent.
privatdo1ze
1zi
1zo
1zu
1zy
PK
!<jjhyphenation/hyph_gl.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
3ï3a
a1a
e1e
o1o
1zo
zo2o
a1á
e1é
o1ó
2ai.
2au.
2ei.
2eu.
2oi.
2ou.
a1ia
a1ie
a1io
a1iá
e1ia
e1ie
e1io
e1iá
o1ia
o1ie
o1io
o1iá
u1ia
u1ie
u1io
u1iá
2ia.
2ie.
2io.
2iá.
1ñ
1ba
1be
1bi
1bo
1bu
1bá
1bé
1bí
1bó
1bú
1ca
1ce
1ci
1co
1cu
1cá
1cé
1cí
1có
1cú
1da
1de
1di
1do
1du
1dá
1dé
1dí
1dó
1dú
1fa
1fe
1fi
1fo
1fu
1fá
1fé
1fí
1fó
1fú
1ga
1ge
1gi
1go
1gu
1gá
1gé
1gí
1gó
1gú
1ha
1he
1hi
1ho
1hu
1há
1hé
1hí
1hó
1hú
1ka
1ke
1ki
1ko
1ku
1ká
1ké
1kí
1kó
1kú
1la
1le
1li
1lo
1lu
1lá
1lé
1lí
1ló
1lú
1ma
1me
1mi
1mo
1mu
1má
1mé
1mí
1mó
1mú
1na
1ne
1ni
1no
1nu
1ná
1né
1ní
1nó
1nú
1pa
1pe
1pi
1po
1pu
1pá
1pé
1pí
1pó
1pú
1ra
1re
1ri
1ro
1ru
1rá
1ré
1rí
1ró
1rú
1sa
1se
1si
1so
1su
1sá
1sé
1sí
1só
1sú
1ta
1te
1ti
1to
1tu
1tá
1té
1tí
1tó
1tú
1va
1ve
1vi
1vo
1vu
1vá
1vé
1ví
1vó
1vú
1xa
1xe
1xi
1xo
1xu
1xá
1xé
1xí
1xó
1xú
1za
1ze
1zi
1zu
1zá
1zé
1zí
1zó
1zú
1qu2
1c2h
2ch.
1g2h
2gh.
2k2h
1b2l
1b2r
1c2l
1c2r
1d2l
1d2r
1f2l
1f2r
1g2l
1g2r
1k2l
1k2r
1p2l
1p2r
2t2l
1t2r
1v2l
1v2r
2bl.
2br.
2cl.
2cr.
2dl.
2dr.
2fl.
2fr.
2gl.
2gr.
2kl.
2kr.
2pl.
2pr.
2tr.
2vl.
2vr.
1l4l
2ll.
1r2r
2rr.
b1p2t
c1p2t
d1p2t
l1p2t
m1p2t
n1p2t
r1p2t
s1p2t
t1p2t
x1p2t
b1c2t
c1c2t
d1c2t
l1c2t
m1c2t
n1c2t
r1c2t
s1c2t
t1c2t
x1c2t
b1c2n
c1c2n
d1c2n
l1c2n
m1c2n
n1c2n
r1c2n
s1c2n
t1c2n
x1c2n
b1p2s
c1p2s
d1p2s
l1p2s
m1p2s
n1p2s
r1p2s
s1p2s
t1p2s
x1p2s
b1m2n
c1m2n
d1m2n
l1m2n
m1m2n
n1m2n
r1m2n
s1m2n
t1m2n
x1m2n
b1g2n
c1g2n
d1g2n
l1g2n
m1g2n
n1g2n
r1g2n
s1g2n
t1g2n
x1g2n
b1f2t
c1f2t
d1f2t
l1f2t
m1f2t
n1f2t
r1f2t
s1f2t
t1f2t
x1f2t
b1p2n
c1p2n
d1p2n
l1p2n
m1p2n
n1p2n
r1p2n
s1p2n
t1p2n
x1p2n
b1c2z
c1c2z
d1c2z
l1c2z
m1c2z
n1c2z
r1c2z
s1c2z
t1c2z
x1c2z
b1t2s
c1t2s
d1t2s
l1t2s
m1t2s
n1t2s
r1t2s
s1t2s
t1t2s
x1t2s
san2c3t
plan2c3t
p1la
2pt.
2ct.
2cn.
2ps.
2mn.
2gn.
2ft.
2pn.
2cz.
2ts.
un2ha
2non.
3mente.
men1te
o2hib
o1hi
alde2h
al1de
4ca1ca4
4ca1go4
4ca1ga4
4cag2as.
4pu1ta4
4pu1to4
4meo.
4mea.
4meable.
mea1b2l
meab1le
4meabl2es.
4pei1do4
acto1a2
ac1to
acto1e2
acto1i2
acto1o2
acto1u2
acto1á2
acto1é2
acto1í2
acto1ó2
acto1ú2
afro1a2
a1f2r
af1ro
afro1e2
afro1i2
afro1o2
afro1u2
afro1á2
afro1é2
afro1í2
afro1ó2
afro1ú2
aero1a2
ae1ro
aero1e2
aero1i2
aero1o2
aero1u2
aero1á2
aero1é2
aero1í2
aero1ó2
aero1ú2
anfi1a2
an1fi
anfi1e2
anfi1i2
anfi1o2
anfi1u2
anfi1á2
anfi1é2
anfi1í2
anfi1ó2
anfi1ú2
anglo1a2
an1g2l
ang1lo
anglo1e2
anglo1i2
anglo1o2
anglo1u2
anglo1á2
anglo1é2
anglo1í2
anglo1ó2
anglo1ú2
.ante1a2
.an1te
.ante1e2
.ante1i2
.ante1o2
.ante1u2
.ante1á2
.ante1é2
.ante1í2
.ante1ó2
.ante1ú2
.anti1a2
.an1ti
.anti1e2
.anti1i2
.anti1o2
.anti1u2
.anti1á2
.anti1é2
.anti1í2
.anti1ó2
.anti1ú2
.arqu1i1a2
.ar1qu2
.arqu1i1e2
.arqui1i2
.arqu1i1o2
.arqui1u2
.arqu1i1á2
.arqui1é2
.arqui1í2
.arqui1ó2
.arqui1ú2
auto1a2
au1to
auto1e2
auto1i2
auto1o2
auto1u2
auto1á2
auto1é2
auto1í2
auto1ó2
auto1ú2
biblio1a2
bi1b2l
bib1li
biblio1e2
biblio1i2
biblio1o2
biblio1u2
biblio1á2
biblio1é2
biblio1í2
biblio1ó2
biblio1ú2
bio1a2
bio1e2
bio1i2
bio1o2
bio1u2
bio1á2
bio1é2
bio1í2
bio1ó2
bio1ú2
cardio1a2
car1di
cardio1e2
cardio1i2
cardio1o2
cardio1u2
cardio1á2
cardio1é2
cardio1í2
cardio1ó2
cardio1ú2
cefalo1a2
ce1fa
cefa1lo
cefalo1e2
cefalo1i2
cefalo1o2
cefalo1u2
cefalo1á2
cefalo1é2
cefalo1í2
cefalo1ó2
cefalo1ú2
ciclo1a2
ci1c2l
cic1lo
ciclo1e2
ciclo1i2
ciclo1o2
ciclo1u2
ciclo1á2
ciclo1é2
ciclo1í2
ciclo1ó2
ciclo1ú2
cito1a2
ci1to
cito1e2
cito1i2
cito1o2
cito1u2
cito1á2
cito1é2
cito1í2
cito1ó2
cito1ú2
.co1a2
.co1e2
.co1i2
.co1o2
.co1u2
.co1é2
.co1í2
.co1ó2
.co1ú2
contra1a2
con1t2r
cont1ra
contra1e2
contra1i2
contra1o2
contra1u2
contra1á2
contra1é2
contra1í2
contra1ó2
contra1ú2
cripto1a2
c1ri
crip1to
cripto1e2
cripto1i2
cripto1o2
cripto1u2
cripto1á2
cripto1é2
cripto1í2
cripto1ó2
cripto1ú2
cromo1a2
c1ro
cro1mo
cromo1e2
cromo1i2
cromo1o2
cromo1u2
cromo1á2
cromo1é2
cromo1í2
cromo1ó2
cromo1ú2
crono1a2
cro1no
crono1e2
crono1i2
crono1o2
crono1u2
crono1á2
crono1é2
crono1í2
crono1ó2
crono1ú2
deca1a2
de1ca
deca1e2
deca1i2
deca1o2
deca1u2
deca1á2
deca1é2
deca1í2
deca1ó2
deca1ú2
.deza1a2
.de1za
.deza1e2
.deza1i2
.deza1o2
.deza1u2
.deza1á2
.deza1é2
.deza1í2
.deza1ó2
.deza1ú2
dinamo1a2
di1na
dina1mo
dinamo1e2
dinamo1i2
dinamo1o2
dinamo1u2
dinamo1á2
dinamo1é2
dinamo1í2
dinamo1ó2
dinamo1ú2
ecano1a2
e1ca
eca1no
ecano1e2
ecano1i2
ecano1o2
ecano1u2
ecano1á2
ecano1é2
ecano1í2
ecano1ó2
ecano1ú2
eco1a2
e1co
eco1e2
eco1i2
eco1o2
eco1u2
eco1á2
eco1é2
eco1í2
eco1ó2
eco1ú2
electro1a2
e1le
elec1t2r
elect1ro
electro1e2
electro1i2
electro1o2
electro1u2
electro1á2
electro1é2
electro1í2
electro1ó2
electro1ú2
endo1a2
en1do
endo1e2
endo1i2
endo1o2
endo1u2
endo1á2
endo1é2
endo1í2
endo1ó2
endo1ú2
ento1a2
en1to
ento1e2
ento1i2
ento1o2
ento1u2
ento1á2
ento1é2
ento1í2
ento1ó2
ento1ú2
entre1a2
en1t2r
ent1re
entre1e2
entre1i2
entre1o2
entre1u2
entre1á2
entre1é2
entre1í2
entre1ó2
entre1ú2
euco1a2
eu1co
euco1e2
euco1i2
euco1o2
euco1u2
euco1á2
euco1é2
euco1í2
euco1ó2
euco1ú2
euro1a2
eu1ro
euro1e2
euro1i2
euro1o2
euro1u2
euro1á2
euro1é2
euro1í2
euro1ó2
euro1ú2
extra1a2
ex1t2r
ext1ra
extra1e2
extra1i2
extra1o2
extra1u2
extra1á2
extra1é2
extra1í2
extra1ó2
extra1ú2
fono1a2
fo1no
fono1e2
fono1i2
fono1o2
fono1u2
fono1á2
fono1é2
fono1í2
fono1ó2
fono1ú2
foto1a2
fo1to
foto1e2
foto1i2
foto1o2
foto1u2
foto1á2
foto1é2
foto1í2
foto1ó2
foto1ú2
franco1a2
f1ra
fran1co
franco1e2
franco1i2
franco1o2
franco1u2
franco1á2
franco1é2
franco1í2
franco1ó2
franco1ú2
gastro1a2
gas1t2r
gast1ro
gastro1e2
gastro1i2
gastro1o2
gastro1u2
gastro1á2
gastro1é2
gastro1í2
gastro1ó2
gastro1ú2
xeo1a2
xeo1e2
xeo1i2
xeo1o2
xeo1u2
xeo1á2
xeo1é2
xeo1í2
xeo1ó2
xeo1ú2
hecto1a2
hec1to
hecto1e2
hecto1i2
hecto1o2
hecto1u2
hecto1á2
hecto1é2
hecto1í2
hecto1ó2
hecto1ú2
helio1a2
he1li
helio1e2
helio1i2
helio1o2
helio1u2
helio1á2
helio1é2
helio1í2
helio1ó2
helio1ú2
hemato1a2
he1ma
hema1to
hemato1e2
hemato1i2
hemato1o2
hemato1u2
hemato1á2
hemato1é2
hemato1í2
hemato1ó2
hemato1ú2
hemi1a2
he1mi
hemi1e2
hemi1i2
hemi1o2
hemi1u2
hemi1á2
hemi1é2
hemi1í2
hemi1ó2
hemi1ú2
hexa1a2
he1xa
hexa1e2
hexa1i2
hexa1o2
hexa1u2
hexa1á2
hexa1é2
hexa1í2
hexa1ó2
hexa1ú2
hidro1a2
hi1d2r
hid1ro
hidro1e2
hidro1i2
hidro1o2
hidro1u2
hidro1á2
hidro1é2
hidro1í2
hidro1ó2
hidro1ú2
hipe2r1a2
hi1pe
hipe2r1e2
hipe2r1i2
hipe2r1o2
hipe2r1u2
hipe2r1á2
hipe2r1é2
hipe2r1í2
hipe2r1ó2
hipe2r1ú2
histo1a2
his1to
histo1e2
histo1i2
histo1o2
histo1u2
histo1á2
histo1é2
histo1í2
histo1ó2
histo1ú2
homeo1a2
ho1me
homeo1e2
homeo1i2
homeo1o2
homeo1u2
homeo1á2
homeo1é2
homeo1í2
homeo1ó2
homeo1ú2
homo1a2
ho1mo
homo1e2
homo1i2
homo1o2
homo1u2
homo1á2
homo1é2
homo1í2
homo1ó2
homo1ú2
ibero1a2
i1be
ibe1ro
ibero1e2
ibero1i2
ibero1o2
ibero1u2
ibero1á2
ibero1é2
ibero1í2
ibero1ó2
ibero1ú2
icono1a2
i1co
ico1no
icono1e2
icono1i2
icono1o2
icono1u2
icono1á2
icono1é2
icono1í2
icono1ó2
icono1ú2
.i1n1a2
.i1n1e2
.i1n1i2
.i1n1o2
.i1n1u2
.i1n1á2
.i1n1é2
.i1n1í2
.i1n1ó2
.i1n1ú2
.indo1a2
.in1do
.indo1e2
.indo1i2
.indo1o2
.indo1u2
.indo1á2
.indo1é2
.indo1í2
.indo1ó2
.indo1ú2
infra1a2
in1f2r
inf1ra
infra1e2
infra1i2
infra1o2
infra1u2
infra1á2
infra1é2
infra1í2
infra1ó2
infra1ú2
.inte2r1a2
.in1te
.inte2r1e2
.inte2r1i2
.inte2r1o2
.inte2r1u2
.inte2r1á2
.inte2r1é2
.inte2r1í2
.inte2r1ó2
.inte2r1ú2
intra1a2
in1t2r
int1ra
intra1e2
intra1i2
intra1o2
intra1u2
intra1á2
intra1é2
intra1í2
intra1ó2
intra1ú2
.iso1a2
.i1so
.iso1e2
.iso1i2
.iso1o2
.iso1u2
.iso1á2
.iso1é2
.iso1í2
.iso1ó2
.iso1ú2
kilo1a2
ki1lo
kilo1e2
kilo1i2
kilo1o2
kilo1u2
kilo1á2
kilo1é2
kilo1í2
kilo1ó2
kilo1ú2
macro1a2
ma1c2r
mac1ro
macro1e2
macro1i2
macro1o2
macro1u2
macro1á2
macro1é2
macro1í2
macro1ó2
macro1ú2
magneto1a2
mag1ne
magne1to
magneto1e2
magneto1i2
magneto1o2
magneto1u2
magneto1á2
magneto1é2
magneto1í2
magneto1ó2
magneto1ú2
maxi1a2
ma1xi
maxi1e2
maxi1i2
maxi1o2
maxi1u2
maxi1á2
maxi1é2
maxi1í2
maxi1ó2
maxi1ú2
mega1a2
me1ga
mega1e2
mega1i2
mega1o2
mega1u2
mega1á2
mega1é2
mega1í2
mega1ó2
mega1ú2
megalo1a2
mega1lo
megalo1e2
megalo1i2
megalo1o2
megalo1u2
megalo1á2
megalo1é2
megalo1í2
megalo1ó2
megalo1ú2
melano1a2
me1la
mela1no
melano1e2
melano1i2
melano1o2
melano1u2
melano1á2
melano1é2
melano1í2
melano1ó2
melano1ú2
micro1a2
mi1c2r
mic1ro
micro1e2
micro1i2
micro1o2
micro1u2
micro1á2
micro1é2
micro1í2
micro1ó2
micro1ú2
mili1a2
mi1li
mili1e2
mili1i2
mili1o2
mili1u2
mili1á2
mili1é2
mili1í2
mili1ó2
mili1ú2
mini1a2
mi1ni
mini1e2
mini1i2
mini1o2
mini1u2
mini1á2
mini1é2
mini1í2
mini1ó2
mini1ú2
multi1a2
mul1ti
multi1e2
multi1i2
multi1o2
multi1u2
multi1á2
multi1é2
multi1í2
multi1ó2
multi1ú2
miria1a2
mi1ri
miria1e2
miria1i2
miria1o2
miria1u2
miria1á2
miria1é2
miria1í2
miria1ó2
miria1ú2
mono1a2
mo1no
mono1e2
mono1i2
mono1o2
mono1u2
mono1á2
mono1é2
mono1í2
mono1ó2
mono1ú2
.nano1a2
.na1no
.nano1e2
.nano1i2
.nano1o2
.nano1u2
.nano1á2
.nano1é2
.nano1í2
.nano1ó2
.nano1ú2
necro1a2
ne1c2r
nec1ro
necro1e2
necro1i2
necro1o2
necro1u2
necro1á2
necro1é2
necro1í2
necro1ó2
necro1ú2
.neo1a2
.neo1e2
.neo1i2
.neo1o2
.neo1u2
.neo1á2
.neo1é2
.neo1í2
.neo1ó2
.neo1ú2
norte1a2
nor1te
norte1e2
norte1i2
norte1o2
norte1u2
norte1á2
norte1é2
norte1í2
norte1ó2
norte1ú2
octo1a2
oc1to
octo1e2
octo1i2
octo1o2
octo1u2
octo1á2
octo1é2
octo1í2
octo1ó2
octo1ú2
octa1a2
oc1ta
octa1e2
octa1i2
octa1o2
octa1u2
octa1á2
octa1é2
octa1í2
octa1ó2
octa1ú2
omni1a2
om1ni
omni1e2
omni1i2
omni1o2
omni1u2
omni1á2
omni1é2
omni1í2
omni1ó2
omni1ú2
paleo1a2
pa1le
paleo1e2
paleo1i2
paleo1o2
paleo1u2
paleo1á2
paleo1é2
paleo1í2
paleo1ó2
paleo1ú2
para1a2
pa1ra
para1e2
para1i2
para1o2
para1u2
para1á2
para1é2
para1í2
para1ó2
para1ú2
penta1a2
pen1ta
penta1e2
penta1i2
penta1o2
penta1u2
penta1á2
penta1é2
penta1í2
penta1ó2
penta1ú2
piezo1a2
pie1zo
piezo1e2
piezo1i2
piezo2o2
piezo1u2
piezo1á2
piezo1é2
piezo1í2
piezo1ó2
piezo1ú2
pluri1a2
p1lu
plu1ri
pluri1e2
pluri1i2
pluri1o2
pluri1u2
pluri1á2
pluri1é2
pluri1í2
pluri1ó2
pluri1ú2
poli1a2
po1li
poli1e2
poli1i2
poli1o2
poli1u2
poli1á2
poli1é2
poli1í2
poli1ó2
poli1ú2
.pos2t1a2
.pos2t1e2
.pos2t1i2
.pos2t1o2
.pos2t1u2
.pos2t1á2
.pos2t1é2
.pos2t1í2
.pos2t1ó2
.pos2t1ú2
.pre1a2
.p2r
.p1re
.pre1e2
.pre1i2
.pre1o2
.pre1u2
.pre1é2
.pre1í2
.pre1ó2
.pre1ú2
.pro1a2
.p1ro
.pro1e2
.pro1i2
.pro1o2
.pro1u2
.pro1á2
.pro1é2
.pro1í2
.pro1ó2
.pro1ú2
proto1a2
p1ro
pro1to
proto1e2
proto1i2
proto1o2
proto1u2
proto1á2
proto1é2
proto1í2
proto1ó2
proto1ú2
pseudo1a2
p1se
pseu1do
pseudo1e2
pseudo1i2
pseudo1o2
pseudo1u2
pseudo1á2
pseudo1é2
pseudo1í2
pseudo1ó2
pseudo1ú2
radio1a2
ra1di
radio1e2
radio1i2
radio1o2
radio1u2
radio1á2
radio1é2
radio1í2
radio1ó2
radio1ú2
.re1a2
.re1e2
.re1i2
.re1o2
.re1u2
.re1á2
.re1é2
.re1í2
.re1ó2
.re1ú2
retro1a2
re1t2r
ret1ro
retro1e2
retro1i2
retro1o2
retro1u2
retro1á2
retro1é2
retro1í2
retro1ó2
retro1ú2
sobre1a2
so1b2r
sob1re
sobre1e2
sobre1i2
sobre1o2
sobre1u2
sobre1á2
sobre1é2
sobre1í2
sobre1ó2
sobre1ú2
semi1a2
se1mi
semi1e2
semi1i2
semi1o2
semi1u2
semi1á2
semi1é2
semi1í2
semi1ó2
semi1ú2
socio1a2
so1ci
socio1e2
socio1i2
socio1o2
socio1u2
socio1á2
socio1é2
socio1í2
socio1ó2
socio1ú2
.su2b1a2
.su2b1e2
.su2b1o2
.su2b1u2
.su2b1á2
.su2b1é2
.su2b1ó2
.su2b1ú2
supe2r1a2
su1pe
supe2r1e2
supe2r1i2
supe2r1o2
supe2r1u2
supe2r1á2
supe2r1é2
supe2r1í2
supe2r1ó2
supe2r1ú2
supra1a2
su1p2r
sup1ra
supra1e2
supra1i2
supra1o2
supra1u2
supra1á2
supra1é2
supra1í2
supra1ó2
supra1ú2
.tele1a2
.te1le
.tele1e2
.tele1i2
.tele1o2
.tele1u2
.tele1á2
.tele1é2
.tele1í2
.tele1ó2
.tele1ú2
termo1a2
ter1mo
termo1e2
termo1i2
termo1o2
termo1u2
termo1á2
termo1é2
termo1í2
termo1ó2
termo1ú2
tetra1a2
te1t2r
tet1ra
tetra1e2
tetra1i2
tetra1o2
tetra1u2
tetra1á2
tetra1é2
tetra1í2
tetra1ó2
tetra1ú2
topo1a2
to1po
topo1e2
topo1i2
topo1o2
topo1u2
topo1á2
topo1é2
topo1í2
topo1ó2
topo1ú2
.tri1a2
.t2r
.t1ri
.tri1e2
.tri1i2
.tri1o2
.tri1u2
.tri1á2
.tri1é2
.tri1í2
.tri1ó2
.tri1ú2
tropo1a2
t1ro
tro1po
tropo1e2
tropo1i2
tropo1o2
tropo1u2
tropo1á2
tropo1é2
tropo1í2
tropo1ó2
tropo1ú2
ultra1a2
ul1t2r
ult1ra
ultra1e2
ultra1i2
ultra1o2
ultra1u2
ultra1á2
ultra1é2
ultra1í2
ultra1ó2
ultra1ú2
xeno1a2
xe1no
xeno1e2
xeno1i2
xeno1o2
xeno1u2
xeno1á2
xeno1é2
xeno1í2
xeno1ó2
xeno1ú2
hipe1r3r
.inte1r3r
supe1r3r
ti2o3qu2
ti2o3co
bi1u2ní
o2i3de
o2i3dal
oi1da
2al.
2a2is.
pe3r2e3mia
pe1re
pere1mi
libero2u3
li1be
libe1ro
atopo2u3
a1to
ato1po
enaxeno2u3
e1na
ena1xe
enaxe1no
2os.
2o3so.
o1so
2o3s2os.
2o3sa.
o1sa
2o3s2as.
2o3sa3mente.
osa1me
osamen1te
2i3co.
2i3c2os.
2i3ca.
i1ca
2i3c2as.
.co2ar
.co2á2
.co2a1bá
.co2acerv
.coa1ce
.co2and1ro
.coan1d2r
.co2a1no
.co2añar
.coa1ñ
.co2año
.co2art
.co2etan
.coe1ta
.co2en1ci
.co2er1ci
.co2in1ci
.co2i1ra
.co2i1ro
.co2i1ta
co2a3gul
coa1gu
co2á3gul
coá1gu
co3a3la.
coa1la
co3a3l2as.
co2a3lescen
coa1le
coales1ce
co2a3lición.
coa1li
coali1ci
co2a3licions.
co2a3na.
coa1na
co2a3n2as.
co2antri1ñ
coan1t2r
coant1ri
co2a3ñadeir
coa1ñ
coaña1de
co2a3tí.
coa1tí
co2a3tís.
co2e3ficien
coe1fi
coefi1ci
co2e3l2ern2os.
coe1le
coeler1no
co2e3llo.
coe1l4l
coel1lo
co2e3lla.
coel1la
co2e3ll2os.
co2e3ll2as.
co2e3lleir
coel1le
co2en1l4l
co2enxía
coen1xí
co2e3sita.
coe1si
coesi1ta
co2e3sit2as.
co2e3tá1ne
coe1tá
co2e3vo.
coe1vo
co2e3va.
coe1va
co2e3v2os.
co2e3v2as.
co2i3da1do
coi1da
co2iei
co1ie
co2imb1ra
coim1b2r
co2imb1rá
co2intre2au.
coin1t2r
coint1re
co2í3ña
coí1ñ
co2i3ña
coi1ñ
co2i3ñei
co2i3pú.
coi1pú
co2i3pús.
co2i3ra.
coi1ra
co2i3r2as.
co2i3ra1za
co2i3ro.
coi1ro
co2i3r2os.
co2i3ta.
coi1ta
co2i3t2as.
co2i3t2a3do.
coita1do
co2i3to.
coi1to
co2i3t2os.
co2i3tel
coi1te
co2i3tío.
coi1tí
co2i3tus.
coi1tu
co2u3c
co2u3lomb
cou1lo
co2u3try
cou1t2r
co2u3qui
cou1qu2
co2u3rel
cou1re
co2u3sa.
cou1sa
co2u3s2as.
co2u3so.
cou1so
co2u3s2os.
co2u3se1lo
cou1se
co2u3tad
cou1ta
co2u3to.
cou1to
co2u3t2os.
co2u3vi1ni
cou1vi
co2u3z
deca2e3ment
decae1me
.de2s1a2
.de2s1e2
.de2s1i2
.de2s1o2
.de2s1u2
.de2s1á2
.de2s1é2
.de2s1í2
.de2s1ó2
.de2s1ú2
3se.
3s2es.
3sa.
3s2as.
de3s2ou1t2r
de1so
3s2e3m2os.
se1mo
3s2e3d2es.
se1de
3s2en.
de3s2a3cra1li
de1sa
desa1c2r
desac1ra
de3s2a3gui1sa
desa1gu
de3s2a3li1ni
desa1li
de3s2a3n1g2r
de3s2a3ñ
de3s2a3rrollis
desa1r2r
desar1ro
desarro1l4l
desarrol1li
de3s2as1t2r
de3s2a3zo
de3s2e3c
de1se
de3s2e3que
dese1qu2
de3s2e3guid
dese1gu
de3s2e3la
de3s2ensib
desen1si
de3s2e3ñ
de3s2ert
de3s2ért
de1sé
de3s2esper
deses1pe
de4s3esperanz
desespe1ra
de3s2e3pér
dese1pé
de3s2e3x
de3s2é3x
de3s2i3der
de1si
desi1de
de3s2ign
de3s2ígn
de1sí
de3s2i3nenc
desi1ne
de3s2in1g2r
de3s2is1te
de3s2is1ti
de3s2o3lac
deso1la
de3s2o3lad
de3s2old
de3s2o3lida1ri
deso1li
desoli1da
de3s2uetud
de1su
desue1tu
de3s2sulf
des1su
.des2abor
.desa1bo
.des2afia
.desa1fi
.des2afía
.desa1fí
.des2afío
.des2air
.des2emboc
.desem1bo
.des2embóc
.desem1bó
.des2empe1ñ
.desem1pe
.des2empé1ñ
.desem1pé
.des2enlac
.desen1la
.des2enlaz
.des2enlác
.desen1lá
.des2enláz
.des2envol
.desen1vo
.des2envól
.desen1vó
.des2idia
.desi1di
.des2o1ra
.in2a3misib.
.ina1mi
.inami1si
.in2a3mov
.ina1mo
.in2a3ne.
.ina1ne
.in2a3nic
.ina1ni
.in2a3nid
.in2á3ni1me
.iná1ni
.in2ant2es.
.inan1te
.in2au
.in2e3dia
.ine1di
.in2é3dit
.iné1di
.in2e3fab
.ine1fa
.in2e3na1r2r
.ine1na
.in2epc
.in2ept
.in2erc
.in2ert
.in2erm
.in2erv
.in2e3siv
.ine1si
.in2e3xo1ra
.ine1xo
.in2i3ci
.in2i3cu
.in2i3mig
.ini1mi
.in2i3mi1za
.in2i3qui
.ini1qu2
.in2o3cen
.ino1ce
.in2o3cui
.ino1cu
.in2o3cuo
.in2o3cul
.in2ó3cul
.inó1cu
.in2o3p2ia.
.ino1pi
.in2o3si1li
.ino1si
.in2o3sit
.in2o3tróp
.ino1t2r
.inot1ró
.in2o3trop
.inot1ro
.in2uit
.in2u3la1se
.inu1la
.in2u3li1na
.inu1li
.in2un1da
.in2u3si1ta
.inu1si
.in2ú3til
.inú1ti
in2o4cular
i1no
ino1cu
inocu1la
.inte3r2és.
.inte3r2e3sa
.inte3r2é3sa
.inte3r2e3sá
.inte3r2e3so
.inte3r2é3so
.inte3r2e3só
.inte3r2ior
.inte3r2i3no.
.interi1no
.inte3r2i3n2os.
.inte3r2i3na.
.interi1na
.inte3r2i3n2as.
.inte3r2i3nid
.interi1ni
.be2n1a2
.be2ne2
.be2n1i2
.be2n1o2
.be2n1u2
.be2n1á2
.be2n1í2
.be2n1ó2
.be2n1ú2
be3n2ign
be1ni
be3n2i3me1rí
beni1me
be3n2i3nés
beni1né
be3n2i3nes
beni1ne
be3n2i3toí1ta
beni1to
.ma2l1a2
.ma2le2
.ma2l1i2
.ma2l1o2
.ma2l1u2
.ma2l1á2
.ma2l1í2
.ma2l1ó2
.ma2l1ú2
.mal2abar
.mala1ba
.mal2abár
.mala1bá
.mal2a1co
.mal2a1có
.mal2armad
.malar1ma
.mal2o1g2r
.mal2u1ra
.mal2a1xa
ma3l2a3cía
ma1la
mala1cí
ma3l2a3citan
mala1ci
malaci1ta
ma3l2a3gue1ñ
mala1gu
ma3l2a1io
ma3l2a1ia
ma3l2andrín
malan1d2r
maland1rí
ma3l2andrin
maland1ri
ma3l2a3qui1ta
mala1qu2
ma3l2ar.
ma3l2a3r2es.
mala1re
ma3l2a3r2ia.
mala1ri
ma3l2a3to.
mala1to
ma3l2a3t2os.
ma3l2a3ta.
mala1ta
ma3l2a3t2as.
ma3l2a3tión
mala1ti
ma3l2aui
ma3l2ea1b2l
ma1le
ma3l2eabil
malea1bi
ma3l2ei1co
ma3l2eolar
maleo1la
ma3l2e3ta.
male1ta
ma3l2e3t2as.
ma3l2e3tín
male1tí
ma3l2e3tei1ro
male1te
ma3l2e1za
ma3l2ia.
ma1li
ma3l2ian
ma3l2i3cia
mali1ci
ma3l2i3cios
ma3l2ign
ma3l2i3ki1ta
mali1ki
ma3l2in1ke
ma3l2ó3fa1go
ma1ló
maló1fa
ma3l2ó3nic
maló1ni
ma3l2o3na1to
ma1lo
malo1na
ma3l2o3nilue
malo1ni
maloni1lu
ma4l3ianq
.mal1educ
.male1du
.mal1encar
.malen1ca
.mal1ensin
.malen1si
.mal1entend
.malen1te
mili2a3rio
milia1ri
mili2a3ria
mini2a3tur
minia1tu
para2u3gas
parau1ga
para2í3so
poli2u3r
poli2o3me
poli2arq
poli2árq
poli2és1te
poli2an1d2r
poli2antea
polian1te
expo1li2
ex1po
.pos3t2a.
.pos3t2as.
.pos3t2al.
.pos3t2a2is.
.pos3t2a3l1lo
.posta1l4l
.pos3t2e.
.pos3t2es.
.pos3t2e3ar.
.pos3t2e3la.
.poste1la
.pos3t2e3l2as.
.pos3t2er.
.pos3t2erg
.pos3t2e3rid
.poste1ri
.pos3t2e3rior
.pos3t2i3go
.pos3t2i3la
.pos3t2illón
.posti1l4l
.postil1ló
.pos3t2ín.
.pos3t2i3te.
.posti1te
.pos3t2i3zo.
.posti1zo
.pos3t2i3z2os.
.pos3t2i3za.
.posti1za
.pos3t2i3z2as.
.pos3t2o.
.pos3t2os.
.pos3t2oi1ro
.pos3t2ó3ni
.pos3t2u3la
.pos3t2u3lo
.pos3t2u3le
.pos3t2u3ra.
.postu1ra
.pos3t2u3r2as.
.pre2amar
.prea1ma
.pre2ar
.pre2á2
.pre2a1bá
pre3as.
p1re
pre3a3da.
prea1da
pre3a3d2as.
pre2á2
pre2a1bá
pre2i3t
pre2o3cup
preo1cu
pre2o3cúp
preo1cú
pro2e3za
pro2í3do
pro2ust
.re2al
.re2a3liz
.rea1li
.re2a3lida1de
.reali1da
.re2in
.re2i3no.
.rei1no
.re2i3nos
.re2i3nan1te
.rei1na
.re2iter
.rei1te
.re2i3xa
.re2os.
.re3as.
.re2u3ma
.re2ú3ma
.re2ún
.re2u1ni
re2u3nión.
reu1ni
re2u3nións.
re2u3nir
re2u3nír1mo
reu1ní
re2u3níren
reuní1re
re2u3n2i3do.
reuni1do
re2u3n2i3d2os.
re2u3n2i3da.
reuni1da
re2u3n2i3d2as.
re2u3nind
re2u3no.
reu1no
re2u3n2es.
reu1ne
re2u3ne.
re2u3n2i3m2os.
reuni1mo
re2u3ní1mo
re2u3n2en.
re2u3nía
re2u3ni3a3m2os.
reunia1mo
re2u3ni3a3d2es.
reunia1de
re2u3n2ín.
re2u3n2i3ch2es.
reuni1c2h
reunic1he
re2u3n2iu.
re2u3n2ist2es.
reunis1te
re2u3níst
re2u3n2i3ron.
reuni1ro
re2u3na.
reu1na
re2u3n2as.
re2u3n2a3m2os.
reuna1mo
re2u3n2a3d2es.
reuna1de
re2u3n2an.
re2u3ni1se
re2u3ní1se
re2u3ni1de
re2u3ní1de
re2u3ná1mo
reu1ná
re2u3ná1de
re2i3nar
rei1na
re2i3n2a3do.
reina1do
re2i3n2a3d2os.
re2i3nand
re2i3na.
re2i3n2as.
re2i3n2a3m2os.
reina1mo
re2i3n2a3d2es.
reina1de
re2i3n2an.
re2i3n2a3ba.
reina1ba
re2i3n2a3b2as.
re2i3n2a3b2a3m2os.
reinaba1mo
re2i3n2á3b2a3m2os.
rei1ná
reiná1ba
reinába1mo
re2i3n2a3b2a3d2es.
reinaba1de
re2i3n2á3b2a3d2es.
reinába1de
re2i3n2ei.
rei1ne
re2i3n2a3ch2es.
reina1c2h
reinac1he
re2i3n2ou.
rei1no
re2i3n2ast2es.
reinas1te
re2i3n2a3ron.
reina1ro
re2i3ne.
re2i3n2es.
re2i3n2e3m2os.
reine1mo
re2i3n2e3d2es.
reine1de
re2i3n2en.
re2i3n2a3se.
reina1se
re2i3n2a3s2es.
re2i3nasemo.
reinase1mo
re2i3na3s2e3d2es.
reinase1de
re2i3ná1se
re2i3n2a3s2en.
re2i3n2a3de.
.su2b3l
.su2b3r
.su2b2i2
.su2b2í2
.sub2eriz
.sube1ri
.sub2or1na
sub3índic
su1bí
subín1di
sub3indic
su1bi
subin1di
sub3indiz
.sub4lev
.sub1le
.sub4lim
.sub1li
su3b2e3la.
su1be
sube1la
su3b2e3l2as.
su3b2é3ri1co
su1bé
subé1ri
su3b2e3rina.
sube1ri
suberi1na
su3b2e3rin2as.
su3b2ero1so
sube1ro
su3b2io1te
su3b2ula1do
su1bu
subu1la
su3b2orno.
su1bo
subor1no
su3b2orn2os.
su3b2urbio
subur1bi
su3b4liminar
su1b2l
sub1li
subli1mi
sublimi1na
su3b4repción
su1b2r
sub1re
subrep1ci
su3b4repti1ci
subrep1ti
tri2a3ga.
t1ri
tria1ga
tri2a3g2as.
tri2al.
tri2a3l2es.
tria1le
tri2angul
trian1gu
tri2á3s2i3co.
triá1si
triási1co
tri2estin
tries1ti
tri2unf
tri2unvir
triun1vi
2a3do.
a1do
2i3do.
i1do
2a3da.
a1da
2i3da.
i1da
2a3d2os.
2i3d2os.
2a3d2as.
2i3d2as.
2ando.
an1do
2indo.
in1do
2ar.
2ir.
2a3r2es.
a1re
2e3r2es.
e1re
2i3r2es.
i1re
2arm2os.
ar1mo
2erm2os.
er1mo
2irm2os.
ir1mo
2ard2es.
ar1de
2erd2es.
er1de
2ird2es.
ir1de
2a3r2en.
2e3r2en.
2i3r2en.
2arme.
ar1me
2erme.
er1me
2irme.
ir1me
2arte.
ar1te
2erte.
er1te
2irte.
ir1te
2arlle.
ar1l4l
arl1le
2erlle.
er1l4l
erl1le
2irlle.
ir1l4l
irl1le
2arn2os.
ar1no
2ern2os.
er1no
2irn2os.
ir1no
2arv2os.
ar1vo
2erv2os.
er1vo
2irv2os.
ir1vo
2arll2es.
2erll2es.
2irll2es.
2a3dor.
2e3dor.
e1do
2i3dor.
2a3dora.
ado1ra
2e3dora.
edo1ra
2i3dora.
ido1ra
2a3dor2es.
ado1re
2e3dor2es.
edo1re
2i3dor2es.
ido1re
2a3doiro.
adoi1ro
2e3doiro.
edoi1ro
2i3doiro.
idoi1ro
2a3doir2os.
2e3doir2os.
2i3doir2os.
2a3doira.
adoi1ra
2e3doira.
edoi1ra
2i3doira.
idoi1ra
2a3doir2as.
2e3doir2as.
2i3doir2as.
2a3deiro.
a1de
adei1ro
2e3deiro.
e1de
edei1ro
2i3deiro.
i1de
idei1ro
2a3deir2os.
2e3deir2os.
2i3deir2os.
2a3deira.
adei1ra
2e3deira.
edei1ra
2i3deira.
idei1ra
2a3deir2as.
2e3deir2as.
2i3deir2as.
2a3lo.
a1lo
2e3lo.
e1lo
2i3lo.
i1lo
2a3l2os.
2e3l2os.
2i3l2os.
2a3la.
a1la
2e3la.
e1la
2i3la.
i1la
2a3l2as.
2e3l2as.
2i3l2as.
2a3r2ei.
2e3r2ei.
2i3r2ei.
2a3rás.
a1rá
2e3rás.
e1rá
2i3rás.
i1rá
2a3rá.
2e3rá.
2i3rá.
2a3r2e3m2os.
are1mo
2e3r2e3m2os.
ere1mo
2i3r2e3m2os.
ire1mo
2a3r2e3d2es.
are1de
2e3r2e3d2es.
ere1de
2i3r2e3d2es.
ire1de
2a3rán.
2e3rán.
2i3rán.
2a3r2ía.
a1rí
2e3r2ía.
e1rí
2i3r2ía.
i1rí
2a3r2í2as.
2e3r2í2as.
2i3r2í2as.
2a3ri2a3m2os.
a1ri
aria1mo
2e3ri2a3m2os.
e1ri
eria1mo
2i3ri2a3m2os.
i1ri
iria1mo
2a3r2í2a3m2os.
aría1mo
2e3r2í2a3m2os.
ería1mo
2i3r2í2a3m2os.
iría1mo
2a3ri2a3d2es.
aria1de
2e3ri2a3d2es.
eria1de
2i3ri2a3d2es.
iria1de
2a3r2í2a3d2es.
aría1de
2e3r2í2a3d2es.
ería1de
2i3r2í2a3d2es.
iría1de
2a3r2í2an.
2e3r2í2an.
2i3r2í2an.
2a3de.
2e3de.
2i3de.
2á3deo.
á1de
2é3deo.
é1de
2í3deo.
í1de
2á3dea.
2é3dea.
2í3dea.
2á3de2os.
2é3de2os.
2í3de2os.
2á3de3as.
2é3de3as.
2í3de3as.
2as.
2a3m2os.
a1mo
2a3d2es.
2an.
2a3ba.
a1ba
2a3b2as.
2a3b2a3m2os.
aba1mo
2á3b2a3m2os.
á1ba
ába1mo
2a3b2a3d2es.
aba1de
2á3b2a3d2es.
ába1de
2a3b2an.
2a3ch2es.
a1c2h
ac1he
2ast2es.
as1te
2a3ron.
a1ro
2es.
2e3m2os.
e1mo
2e3d2es.
2en.
2a3se.
a1se
2a3s2es.
2á3s2e3m2os.
á1se
áse1mo
2á3s2e3d2es.
áse1de
2a3s2en.
o3ar.
o3a3do.
oa1do
o3a3da.
oa1da
o3a3d2os.
o3a3d2as.
o3ando.
oan1do
o3a3r2es.
oa1re
o3arm2os.
oar1mo
o3ard2es.
oar1de
o3a3r2en.
o3arme.
oar1me
o3arte.
oar1te
o3arlle.
oar1l4l
oarl1le
o3arn2os.
oar1no
o3arv2os.
oar1vo
o3arll2es.
o3a3lo.
oa1lo
o3a3l2os.
o3a3la.
oa1la
o3a3l2as.
o3a3de.
oa1de
o3á3deo.
oá1de
o3á3dea.
o3á3de2os.
o3á3de3as.
o3as.
o3a3m2os.
oa1mo
o3a3d2es.
o3an.
o3a3ba.
oa1ba
o3a3b2as.
o3a3b2a3m2os.
oaba1mo
o3á3b2a3m2os.
oá1ba
oába1mo
o3a3b2a3d2es.
oaba1de
o3á3b2a3d2es.
oába1de
o3a3b2an.
o3a3ch2es.
oa1c2h
oac1he
o3ast2es.
oas1te
o3a3ron.
oa1ro
o3es.
o3e3m2os.
oe1mo
o3e3d2es.
oe1de
o3en.
o3a3se.
oa1se
o3a3s2es.
o3á3s2e3m2os.
oá1se
oáse1mo
o3á3s2e3d2es.
oáse1de
o3a3s2en.
e3ar.
e3a3do.
ea1do
e3a3da.
ea1da
e3a3d2os.
e3a3d2as.
e3ando.
ean1do
e3a3r2es.
ea1re
e3arm2os.
ear1mo
e3ard2es.
ear1de
e3a3r2en.
e3arme.
ear1me
e3arte.
ear1te
e3arlle.
ear1l4l
earl1le
e3arn2os.
ear1no
e3arv2os.
ear1vo
e3arll2es.
e3a3lo.
ea1lo
e3a3l2os.
e3a3la.
ea1la
e3a3l2as.
e3a3de.
ea1de
e3á3deo.
eá1de
e3á3dea.
e3á3de2os.
e3á3de3as.
e3as.
e3a3m2os.
ea1mo
e3a3d2es.
e3an.
e3a3ba.
ea1ba
e3a3b2as.
e3a3b2a3m2os.
eaba1mo
e3á3b2a3m2os.
eá1ba
eába1mo
e3a3b2a3d2es.
eaba1de
e3á3b2a3d2es.
eába1de
e3a3b2an.
e3a3ch2es.
ea1c2h
eac1he
e3ast2es.
eas1te
e3a3ron.
ea1ro
e3es.
e3e3m2os.
ee1mo
e3e3d2es.
ee1de
e3en.
e3a3se.
ea1se
e3a3s2es.
e3á3s2e3m2os.
eá1se
eáse1mo
e3á3s2e3d2es.
eáse1de
e3a3s2en.
2i3m2os.
i1mo
2i3d2es.
2ía.
2í2as.
2í2a3m2os.
ía1mo
2í2a3d2es.
ía1de
2í2an.
2ín.
2i3ch2es.
i1c2h
ic1he
2iu.
2ist2es.
is1te
2i3ron.
i1ro
2i3se.
i1se
2i3s2es.
2í3s2e3m2os.
í1se
íse1mo
2í3s2e3d2es.
íse1de
2i3s2en.
í3do
í3da
í3dos
í3das
.su3b2ir.
.su3b2indo.
.subin1do
.su3b2i3do.
.subi1do
.su3b2i3da.
.subi1da
.su3b2i3d2os.
.su3b2i3d2as.
.su3b2i3r2es.
.subi1re
.su3b2irm2os.
.subir1mo
.su3b2ird2es.
.subir1de
.su3b2i3r2en.
.su3bo.
.su3b2es.
.su3be.
.su3b2i3m2os.
.subi1mo
.su3b2i3d2es.
.subi1de
.su3b2en.
.su3b2ía.
.su3b2í2as.
.su3b2i3a3m2os.
.subia1mo
.su3b2í2a3m2os.
.subía1mo
.su3b2i3a3d2es.
.subia1de
.su3b2í2a3d2es.
.subía1de
.su3b2í2an.
.su3b2ín.
.su3b2i3ch2es.
.subi1c2h
.subic1he
.su3b2iu.
.su3b2ist2es.
.subis1te
.su3b2i3ron.
.subi1ro
.su3b2i3r2ei.
.su3b2i3rás.
.subi1rá
.su3b2i3rá.
.su3b2i3r2e3m2os.
.subire1mo
.su3b2i3r2e3d2es.
.subire1de
.su3b2i3rán.
.su3b2i3r2ía.
.subi1rí
.su3b2i3r2í2as.
.su3b2i3ri2a3m2os.
.subi1ri
.subiria1mo
.su3b2i3r2í2a3m2os.
.subiría1mo
.su3b2i3ri2a3d2es.
.subiria1de
.su3b2i3r2í2a3d2es.
.subiría1de
.su3b2i3r2í2an.
.su3ba.
.su3b2as.
.su3b2a3m2os.
.suba1mo
.su3b2a3d2es.
.suba1de
.su3b2an.
.su3b2i3se.
.subi1se
.su3b2i3s2es.
.su3b2í3s2e3m2os.
.subí1se
.subíse1mo
.su3b2í3s2e3d2es.
.subíse1de
.su3b2i3s2en.
.su3b2i3de.
.su3b2í3deo.
.subí1de
.su3b2í3de2os.
.su3b2í3dea.
.su3b2í3de3as.
.su3b2a3dor.
.suba1do
.su3b2a3dora.
.subado1ra
.su3b2a3dor2es.
.subado1re
.su3bador2as.
.supe3r2ar.
.supe2r1a2
.su1pe
.supe3r2ando.
.superan1do
.supe3r2a3do.
.supera1do
.supe3r2a3da.
.supera1da
.supe3r2a3d2os.
.supe3r2a3d2as.
.supe3r2a3r2es.
.supera1re
.supe3r2arm2os.
.superar1mo
.supe3r2ard2es.
.superar1de
.supe3r2a3r2en.
.supe3ra.
.supe3r2as.
.supe3r2a3m2os.
.supera1mo
.supe3r2a3d2es.
.supera1de
.supe3r2an.
.supe3r2a3ba.
.supera1ba
.supe3r2a3b2as.
.supe3r2a3b2a3m2os.
.superaba1mo
.supe3r2á3b2a3m2os.
.supe2r1á2
.superá1ba
.superába1mo
.supe3r2a3b2a3d2es.
.superaba1de
.supe3r2á3b2a3d2es.
.superába1de
.supe3r2a3b2an.
.sup2e3r2ei.
.supe2r1e2
.supe3r2a3ch2es.
.supera1c2h
.superac1he
.supe3r2ou.
.supe2r1o2
.supe3r2ast2es.
.superas1te
.supe3r2a3ron.
.supera1ro
.supe3r2a3r2ei.
.supe3r2a3rás.
.supera1rá
.supe3r2a3rá.
.supe3r2e3r2e3m2os.
.supe1re1re
.superere1mo
.supe3r2e3r2e3d2es.
.superere1de
.supe3r2a3rán.
.supe3r2a3r2ía.
.supera1rí
.supe3r2a3r2í2as.
.supe3r2a3ri2a3m2os.
.supera1ri
.superaria1mo
.supe3r2a3r2í2a3m2os.
.superaría1mo
.supe3r2a3ri2a3d2es.
.superaria1de
.supe3r2a3r2í2a3d2es.
.superaría1de
.supe3r2a3r2í2an.
.supe3re.
.sup2e3r2es.
.sup2e3r2e3m2os.
.supere1mo
.sup2e3r2e3d2es.
.supere1de
.sup2e3r2en.
.supe3r2a3se.
.supera1se
.supe3r2a3s2es.
.supe3r2á3s2e3m2os.
.superá1se
.superáse1mo
.supe3r2á3s2e3d2es.
.superáse1de
.supe3r2a3s2en.
.supe3r2a3de.
.supe3r2á3deo.
.superá1de
.supe3r2á3de2os.
.supe3r2á3dea.
.supe3r2á3de3as.
.supe3r2a3dor.
.supe3r2a3dora.
.supe1rado1ra
.supe3r2a3dor2es.
.superado1re
.supe3rador2as.
.supe3ración.
.supera1ci
supe3r2ior
supe3r2a3b1le
supera1b2l
supe3r2a3bilida1de
supera1bi
superabi1li
superabili1da
a3er.
a3endo.
aen1do
a3e3r2es.
ae1re
a3erm2os.
aer1mo
a3erd2es.
aer1de
a3e3r2en.
a3erme.
aer1me
a3erte.
aer1te
a3erlle.
aer1l4l
aerl1le
a3ern2os.
aer1no
a3erv2os.
aer1vo
a3erll2es.
a3e3lo.
ae1lo
a3e3l2os.
a3e3la.
ae1la
a3e3l2as.
a3e3de.
ae1de
a3é3deo.
aé1de
a3é3dea.
a3é3de2os.
a3é3de3as.
a3e3m2os.
ae1mo
a3e3d2es.
a3eron.
a3e3se.
ae1se
a3e3s2es.
a3e3s2e3m2os.
aese1mo
a3é3s2e3m2os.
aé1se
aése1mo
a3e3s2e3d2es.
aese1de
a3é3s2e3d2es.
aése1de
a3e3s2en.
PK
!<T0KKhyphenation/hyph_hr.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
a1a2
a1e2
a1i2
a1o2
a1u2
e1a2
e1e2
e1i2
e1o2
e1u2
i1a2
i1e2
i1i2
i1o2
i1u2
o1a2
o1e2
o1i2
o1o2
o1u2
u1a2
u1e2
u1i2
u1o2
u1u2
a1ba
a1be
a1bi
a1bo
a1bu
a1ca
a1ce
a1ci
a1co
a1cu
a1ča
a1če
a1či
a1čo
a1ču
a1ća
a1će
a1ći
a1ćo
a1ću
a1da
a1de
a1di
a1do
a1du
a1dža
ad2ž
a1dže
a1dži
a1džo
a1džu
a1đa
a1đe
a1đi
a1đo
a1đu
a1fa
a1fe
a1fi
a1fo
a1fu
a1ga
a1ge
a1gi
a1go
a1gu
a1ha
a1he
a1hi
a1ho
a1hu
a1ja
a1je
a1ji
a1jo
a1ju
a1ka
a1ke
a1ki
a1ko
a1ku
a1la
a1le
a1li
a1lo
a1lu
a1lja
a1lje
a1lji
a1ljo
a1lju
a1ma
a1me
a1mi
a1mo
a1mu
a1na
a1ne
a1ni
a1no
a1nu
a1nja
a1nje
a1nji
a1njo
a1nju
a1pa
a1pe
a1pi
a1po
a1pu
a1ra
a1re
a1ri
a1ro
a1ru
a1sa
a1se
a1si
a1so
a1su
a1ša
a1še
a1ši
a1šo
a1šu
a1ta
a1te
a1ti
a1to
a1tu
a1va
a1ve
a1vi
a1vo
a1vu
a1za
a1ze
a1zi
a1zo
a1zu
a1ža
a1že
a1ži
a1žo
a1žu
e1ba
e1be
e1bi
e1bo
e1bu
e1ca
e1ce
e1ci
e1co
e1cu
e1ča
e1če
e1či
e1čo
e1ču
e1ća
e1će
e1ći
e1ćo
e1ću
e1da
e1de
e1di
e1do
e1du
e1dža
ed2ž
e1dže
e1dži
e1džo
e1džu
e1đa
e1đe
e1đi
e1đo
e1đu
e1fa
e1fe
e1fi
e1fo
e1fu
e1ga
e1ge
e1gi
e1go
e1gu
e1ha
e1he
e1hi
e1ho
e1hu
e1ja
e1je
e1ji
e1jo
e1ju
e1ka
e1ke
e1ki
e1ko
e1ku
e1la
e1le
e1li
e1lo
e1lu
e1lja
e1lje
e1lji
e1ljo
e1lju
e1ma
e1me
e1mi
e1mo
e1mu
e1na
e1ne
e1ni
e1no
e1nu
e1nja
e1nje
e1nji
e1njo
e1nju
e1pa
e1pe
e1pi
e1po
e1pu
e1ra
e1re
e1ri
e1ro
e1ru
e1sa
e1se
e1si
e1so
e1su
e1ša
e1še
e1ši
e1šo
e1šu
e1ta
e1te
e1ti
e1to
e1tu
e1va
e1ve
e1vi
e1vo
e1vu
e1za
e1ze
e1zi
e1zo
e1zu
e1ža
e1že
e1ži
e1žo
e1žu
i1ba
i1be
i1bi
i1bo
i1bu
i1ca
i1ce
i1ci
i1co
i1cu
i1ča
i1če
i1či
i1čo
i1ču
i1ća
i1će
i1ći
i1ćo
i1ću
i1da
i1de
i1di
i1do
i1du
i1dža
id2ž
i1dže
i1dži
i1džo
i1džu
i1đa
i1đe
i1đi
i1đo
i1đu
i1fa
i1fe
i1fi
i1fo
i1fu
i1ga
i1ge
i1gi
i1go
i1gu
i1ha
i1he
i1hi
i1ho
i1hu
i1ja
i1je
i1ji
i1jo
i1ju
i1ka
i1ke
i1ki
i1ko
i1ku
i1la
i1le
i1li
i1lo
i1lu
i1lja
i1lje
i1lji
i1ljo
i1lju
i1ma
i1me
i1mi
i1mo
i1mu
i1na
i1ne
i1ni
i1no
i1nu
i1nja
i1nje
i1nji
i1njo
i1nju
i1pa
i1pe
i1pi
i1po
i1pu
i1ra
i1re
i1ri
i1ro
i1ru
i1sa
i1se
i1si
i1so
i1su
i1ša
i1še
i1ši
i1šo
i1šu
i1ta
i1te
i1ti
i1to
i1tu
i1va
i1ve
i1vi
i1vo
i1vu
i1za
i1ze
i1zi
i1zo
i1zu
i1ža
i1že
i1ži
i1žo
i1žu
o1ba
o1be
o1bi
o1bo
o1bu
o1ca
o1ce
o1ci
o1co
o1cu
o1ča
o1če
o1či
o1čo
o1ču
o1ća
o1će
o1ći
o1ćo
o1ću
o1da
o1de
o1di
o1do
o1du
o1dža
od2ž
o1dže
o1dži
o1džo
o1džu
o1đa
o1đe
o1đi
o1đo
o1đu
o1fa
o1fe
o1fi
o1fo
o1fu
o1ga
o1ge
o1gi
o1go
o1gu
o1ha
o1he
o1hi
o1ho
o1hu
o1ja
o1je
o1ji
o1jo
o1ju
o1ka
o1ke
o1ki
o1ko
o1ku
o1la
o1le
o1li
o1lo
o1lu
o1lja
o1lje
o1lji
o1ljo
o1lju
o1ma
o1me
o1mi
o1mo
o1mu
o1na
o1ne
o1ni
o1no
o1nu
o1nja
o1nje
o1nji
o1njo
o1nju
o1pa
o1pe
o1pi
o1po
o1pu
o1ra
o1re
o1ri
o1ro
o1ru
o1sa
o1se
o1si
o1so
o1su
o1ša
o1še
o1ši
o1šo
o1šu
o1ta
o1te
o1ti
o1to
o1tu
o1va
o1ve
o1vi
o1vo
o1vu
o1za
o1ze
o1zi
o1zo
o1zu
o1ža
o1že
o1ži
o1žo
o1žu
u1ba
u1be
u1bi
u1bo
u1bu
u1ca
u1ce
u1ci
u1co
u1cu
u1ča
u1če
u1či
u1čo
u1ču
u1ća
u1će
u1ći
u1ćo
u1ću
u1da
u1de
u1di
u1do
u1du
u1dža
ud2ž
u1dže
u1dži
u1džo
u1džu
u1đa
u1đe
u1đi
u1đo
u1đu
u1fa
u1fe
u1fi
u1fo
u1fu
u1ga
u1ge
u1gi
u1go
u1gu
u1ha
u1he
u1hi
u1ho
u1hu
u1ja
u1je
u1ji
u1jo
u1ju
u1ka
u1ke
u1ki
u1ko
u1ku
u1la
u1le
u1li
u1lo
u1lu
u1lja
u1lje
u1lji
u1ljo
u1lju
u1ma
u1me
u1mi
u1mo
u1mu
u1na
u1ne
u1ni
u1no
u1nu
u1nja
u1nje
u1nji
u1njo
u1nju
u1pa
u1pe
u1pi
u1po
u1pu
u1ra
u1re
u1ri
u1ro
u1ru
u1sa
u1se
u1si
u1so
u1su
u1ša
u1še
u1ši
u1šo
u1šu
u1ta
u1te
u1ti
u1to
u1tu
u1va
u1ve
u1vi
u1vo
u1vu
u1za
u1ze
u1zi
u1zo
u1zu
u1ža
u1že
u1ži
u1žo
u1žu
b1b
b1c
b1č
b1ć
b1d
b1d2ž
b1đ
b1f
b1g
b1h
1bj
2bj.
b1k
1bl
2bl.
2b1lj
b1m
b1n
b1nj
b1p
1br
2br.
b1s
b1š
b1t
1bv
2bv.
b1z
b1ž
c1b
c1c
c1č
c1ć
c1d
c1d2ž
c1đ
c1f
c1g
c1h
1cj
2cj.
c1k
1cl
2cl.
2c1lj
c1m
c1n
c1nj
c1p
1cr
2cr.
c1s
c1š
c1t
1cv
2cv.
c1z
c1ž
č1b
č1c
č1č
č1ć
č1d
č1d2ž
č1đ
č1f
č1g
č1h
1čj
2čj.
č1k
1čl
2čl.
2č1lj
č1m
č1n
č1nj
č1p
1čr
2čr.
č1s
č1š
č1t
1čv
2čv.
č1z
č1ž
ć1b
ć1c
ć1č
ć1ć
ć1d
ć1d2ž
ć1đ
ć1f
ć1g
ć1h
ć1j
ć1k
1ćl
2ćl.
2ć1lj
ć1m
ć1n
ć1nj
ć1p
1ćr
2ćr.
ć1s
ć1š
ć1t
1ćv
2ćv.
ć1z
ć1ž
d1b
d1c
d1č
d1ć
d1d
d2ž
d1đ
d1f
2d1g
d1h
1dj
2dj.
d1k
2d1l
2d1lj
d1m
d1n
d1nj
d1p
1dr
2dr.
d1s
d1š
d1t
1dv
2dv.
d1z
dž1b
d1ž1c
d1ž1č
d1ž1ć
d1ž1d
d2ž1d2ž
d1ž1đ
d1ž1f
d1ž1g
d1ž1h
1dž1j
2džj.
d1ž1k
1d1žl
2d2žl.
2dž1lj
d1ž1m
dž1n
d2ž1nj
d1ž1p
1d1žr
2d2žr.
d1ž1s
d1ž1š
d1ž1t
1d1žv
2d2žv.
d1ž1z
d1ž1ž
đ1b
đ1c
đ1č
đ1ć
đ1d
đ1d2ž
đ1đ
đ1f
đ1g
đ1h
1đj
2đj.
đ1k
1đl
2đl.
2đ1lj
đ1m
đ1n
đ1nj
đ1p
1đr
2đr.
đ1s
đ1š
đ1t
1đv
2đv.
đ1z
đ1ž
f1b
f1c
f1č
f1ć
f1d
f1d2ž
f1đ
f1f
f1g
f1h
1fj
2fj.
f1k
1fl
2fl.
2f1lj
f1m
f1n
f1nj
f1p
1fr
2fr.
f1s
f1š
f1t
1fv
2fv.
f1z
f1ž
g1b
g1c
g1č
g1ć
g1d
g1d2ž
g1đ
g1f
g1g
g1h
1gj
2gj.
g1k
1gl
2gl.
2g1lj
g1m
g1n
g1nj
g1p
1gr
2gr.
g1s
g1š
g1t
1gv
2gv.
g1z
g1ž
h1b
h1c
h1č
h1ć
h1d
h1d2ž
h1đ
h1f
h1g
h1h
1hj
2hj.
h1k
1hl
2hl.
2h1lj
h1m
h1n
h1nj
h1p
1hr
2hr.
h1s
h1š
h1t
1hv
2hv.
h1z
h1ž
j1b
j1c
j1č
j1ć
j1d
j1d2ž
j1đ
j1f
j1g
j1h
j1j
j1k
j1l
2j1lj
j1m
j1n
j1nj
j1p
j1r
j1s
j1š
j1t
j1v
j1z
j1ž
k1b
k1c
k1č
k1ć
k1d
k1d2ž
k1đ
k1f
k1g
k1h
1kj
2kj.
k1k
k1l
1k2lj
2klj.
k1m
k1n
k1nj
k1p
1kr
2kr.
k1s
k1š
k1t
k1v
k1z
k1ž
l1b
l1c
l1č
l1ć
l1d
l1d2ž
l1đ
l1f
l1g
l1h
l1k
l1l
2l1lj
l1m
l1n
l1nj
l1p
l1r
l1s
l1š
l1t
l1v
l1z
l1ž
lj1b
lj1c
lj1č
lj1ć
lj1d
lj1d2ž
lj1đ
lj1f
lj1g
lj1h
lj1k
1lj1l
2ljl.
l2j1lj
lj1m
lj1n
lj1nj
lj1p
1lj1r
2ljr.
lj1s
lj1š
lj1t
1lj1v
2ljv.
lj1z
lj1ž
m1b
m1c
m1č
m1ć
m1d
m1d2ž
m1đ
m1f
m1g
m1h
1mj
2mj.
m1k
1ml
2ml.
2m1lj
m1m
m1n
m1nj
m1p
1mr
2mr.
m1s
m1š
m1t
1mv
2mv.
m1z
m1ž
n1b
n1c
n1č
n1ć
n1d
n1d2ž
n1đ
n1f
n1g
n1h
2nj.
n1k
1nl
2nl.
2n1lj
n1m
n1n
n1nj
n1p
n1r
n1s
n1š
n1t
1nv
2nv.
n1z
n1ž
nj1b
nj1c
nj1č
nj1ć
nj1d
nj1d2ž
nj1đ
nj1f
nj1g
nj1h
1nj1j
2njj.
nj1k
1nj1l
2njl.
2n2j1lj
nj1m
nj1n
nj1nj
nj1p
1nj1r
2njr.
nj1s
nj1š
nj1t
1nj1v
2njv.
nj1z
nj1ž
p1b
p1c
p1č
p1ć
p1d
p1d2ž
p1đ
p1f
p1g
2p1h
1pj
2pj.
p1k
1pl
2pl.
2p1lj
p1m
p1n
p1nj
p1p
1pr
2pr.
p1s
p1š
p1t
1pv
2pv.
p1z
p1ž
r1b
r1c
r1č
r1ć
r1d
r1d2ž
r1đ
r1f
r1g
r1h
r1j
r1k
r1l
2r1lj
r1m
r1n
r1nj
r1p
r1r
r1s
r1š
r1t
r1v
r1z
r1ž
1sb
2sb.
s1c
1sč
2sč.
1sć
2sć.
1sd
2sd.
1sd2ž
2sdž.
1sđ
2sđ.
1sf
2sf.
1sg
2sg.
1sh
2sh.
1sj
2sj.
1sk
2sk.
s1l
1slj
2slj.
1sm
2sm.
s1n
2sn.
1snj
2s2nj.
s1p
1sr
2sr.
1ss
2ss.
1sš
2sš.
s1t
2st.
1sv
2sv.
1sz
2sz.
1sž
2sž.
1šb
2šb.
š1c
š1č
š1ć
1šd
2šd.
1šd2ž
2šdž.
1šđ
2šđ.
1šf
2šf.
1šg
2šg.
1šh
2šh.
1šj
2šj.
1šk
2šk.
š1l
2š1lj
1šm
2šm.
š1n
2š1nj
1šp
2šp.
1šr
2šr.
1šs
2šs.
1šš
2šš.
1št
2št.
1šv
2šv.
1šz
2šz.
1šž
2šž.
t1b
t1c
t1č
t1ć
t1d
t1d2ž
t1đ
t1f
t1g
t1h
1tj
2tj.
t1k
1tl
2tl.
2t1lj
t1m
t1n
t1nj
t1p
1tr
2tr.
t1s
t1š
t1t
1tv
2tv.
t1z
t1ž
v1b
v1c
v1č
v1ć
v1d
v1d2ž
v1đ
v1f
v1g
v1h
v1j
1v2je
v1k
v1l
2v1lj
v1m
v1n
v1nj
v1p
1vr
v1s
v1š
v1t
v1v
v1z
v1ž
z1b
1zc
2zc.
1zč
2zč.
1zć
2zć.
z1d
1zd2ž
2zdž.
1zđ
2zđ.
1zf
2zf.
z1g
1zh
2zh.
1zj
2zj.
1zk
2zk.
z1l
2z1lj
z1m
z1n
1znj
2z2nj.
1zp
2zp.
z1r
z1s
1zš
2zš.
1zt
2zt.
1zv
2zv.
1zz
2zz.
1zž
2zž.
ž1b
2žb.
1žc
2žc.
1žč
2žč.
1žć
2žć.
1žd
2žd.
1žd2ž
2ždž.
1žđ
2žđ.
1žf
2žf.
1žg
2žg.
1žh
2žh.
ž1j
1žk
2žk.
1žl
2žl.
ž1lj
1žm
2žm.
ž1n
2žn.
2ž1nj
1žp
2žp.
1žr
2žr.
1žs
2žs.
1žš
2žš.
1žt
2žt.
1žv
2žv.
1žz
2žz.
1žž
2žž.
2d1v1j
2d1vr
z1g2nj
zg1n
ć1s2t
d1s2m
j1z2g
r2n1t
r2z1n
m2p1t
2d1v1l
r2t1c
r2n1c
r2č1k
2s1hr
z1v2l
2z1vr
r2k1n
r2c1n
r2h1nj
rh1n
s2p1n
j1s2l
r2d1n
.z1g2
r2dž1b
2š1tv
r2t1s
n2t1s
2s1kr
r2n1k
d1š2k
d1s2p
r2t1m
1r2je
k2s1p
t1s2t
l2m1s
r2h1k
j1s2t
s1v2l
r2p1c
t1k2l
s1k2l
n2t1n
2d1ja
2rt.
z1r2j
d1s2t
n2k1c
r2t1k
r2g1n
r2h1t
.na2j1
.be2z1
.iz1
.is1
.ne2o3
r2t1n
r2v1n
r1s2t
n2s1t
l2f1t
PK
!<)&--hyphenation/hyph_hsb.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
8ě
8ń5
ń6č3
ń7č4a
ń7č4e
ń7č4i
ń7č4u
ń7č4o
8ó
š1
3š2ć
8ź
1k8ř8
1p8ř8
1t8ř8
a3b2a
a3b2e
a3b8ě
a3b2i
a3b2o
a3b2u
a3b2y
a3b2j
a3b2r
a3b2l
a3b2ł
a3c2a
a3c2e
a3c2o
a3c2u
a3c2y
a3c2i
a3ch2a
ac2h
a3ch2e
a3ch2o
a3ch2u
a3ch2y
a3ch2i
a3ć2a
a3ć2e
a3ć2i
a3ć2o
a3ć2u
a3č2a
a3č2e
a3č8ě
a3č2i
a3č2o
a3č2u
a3d2a
a3d2e
a3d2i
a3d2o
a3d2u
a3d2y
a3d2ź2a
ad8ź
a3d2ź2e
a3d2ź8ě
a3d2ź2i
a3d2ź2o
a3d2ź2u
a3d2ź2y
a3dl2
a3dr2
a3f2a
a3f2e
a3f2y
a3f2o
a3f2u
a3f2i
a3f2l
a3f2r2
a1g2r2
a1g2l2
a1g2a
a1g2e
a1g8ě
a1g2o
a1g2u
a1g2i
a1h2a
a1h2e
a1h8ě
a1h2o
a1h2u
a1h2i
aj1
a1j2a
a1j2e
a1j8ě
a1j2i
a1j2o
a1j2u
a1k2a
a1k2e
a1k2i
a1k2u
a1k2o
a1k2r
a1k2l
ał1
a3ł2a
a3ł2e
a3ł2o
a3ł2u
a3ł2y
a3l2a
a3l2e
a3l8ě
a3l2o
a3l2u
a3l2i
a3l2y
a3m2a
a3m2o
a3m2u
a3m2i
a3m2e
a3m8ě
a3m2y
a3m2j
a3n2a1
a3n2e
a3n8ě
a3n2i
a3n2o
a3n2u
a3n2y
a3n4j
a3p2a
a3p2e
a3p8ě
a3p2i
a3p2o1
a3p2u
a3p2y
a3p4j
a3p2l
a3p2r
a3r2a
a3r2e
a3r8ě
a3r2i
a3r2o
a3r2u
a3r2y
a3r4j
a3s2a
a3s2e
a3s2i
a3s2o
a3s2u
a3s2y
as1pek1t
a3s2t
aš1
a3š8a
a3š8e
a3š8ě
a3š8i
a3š8o
a3š8u
a3š2y
a3š2tap
a3t2a
a3t2e
a3t2i
a3t2o
a3t2u
a3t2y
a3t2r
a3t2l
a3w2a
a3w2e
a3w8ě
a3w2i
a2w1n
a3w2o
a3w2u
a3w2y
a3w4j
a3z2a
a3z2e
a3z8ě
a3z2i
a3z2o
a3z2u
a3z2y
a2ž1
a3ž2a
a3ž2e
a3ž8ě
a3ž2i
a3ž2o
a3ž2u
e3b2a
e3b2e
e3b2i
e3b2o
e3b2u
e3b2y
e3b2j
e3b2r
e3b2ł
e3b2l
e3c2a
e3c2e
e3c2o
e3c2u
e3c2y
e3c2i
e3ch2a
ec2h
e3ch2e
e3ch2o
e3ch2u
e3ch2i
e3ć2a
e3ć2e
e3ć2i
e3ć2o
e3ć2u
e3č2a
e3č2e
e3č2i
e3č2o
e3č2u
e3d2a
e3d2e
e3d2i
e3d2o
e3d2u
e3d2y
e3d2ź2a
ed8ź
e3d2ź2e
e3d2ź2i
e3d2ź2o
e3d2ź2u
e3d2ź2y
e3dl2
e3dr2
e3f2a
e3f2e
e3f2y
e3f2o
e3f2u
e3f2i
e3f2l
e3f2r2
e1g2r2
e1g2l2
e1g2a
e1g2e
e1g2o
e1g2u
e1g2i
e1h2a
e1h2o
e1h2u
e1h2i
ej1
e1j2a
e1j2e
e1j2i
e1j2o
e1j2u
e1k2a
e1k2e
e1k2i
e1k2u
e1k2o
e1k2r
e1k2l
eł1
e1ł2a
e1ł2e
e1ł2o
e1ł2u
e1ł2y
e1l2a
e1l2e
e1l2o
e1l2u
e1l2i
e3m2a
e3m2o
e3m2u
e3m2i
e3m2e
e3m2y
e3m2j
e3n2a1
e3n2e
e3n2i
e3n2o
e3n2u
e3n2y
e3n4j
e3p2a
e3p2e
e3p2i
e3p2o1
e3p2u
e3p2y
e3p4j
e3p2l
e3p2r
e3r2a
e3r2e
e3r2i
e3r2o
e3r2u
e3r2y
e3r4j
er2b1s2k
er1b
erb1s
e3s2a
e3s2e
e3s2i
e3s2o
e3s2u
e3s2y
e3s2t
eš1
e3š8a
e3š8e
e3š8i
e3š8o
e3š8u
e3š2y
e3š2tap
e3t2a
e3t2e
e3t2i
e3t2o
e3t2u
e3t2y
e3t2r
e3t2l
e3w2a
e3w2e
e3w2i
e3w2o
e3w2u
e3w2y
e3w4j
e3z2a
e3z2e
e3z2i
e3z2o
e3z2u
e3z2y
e2ž1
e3ž2a
e3ž2e
e3ž2i
e3ž2o
e3ž2u
ě3b2a
ě3b2e
ě3b2i
ě3b2o
ě3b2u
ě3b2y
ě3b2j
ě3b2r
ě3b2l
ě3c2a
ě3c2e
ě3c2o
ě3c2u
ě3c2y
ě3ch2a
ěc2h
ě3ch2e
ě3ch2o
ě3ch2u
ě3ch2i
ě3ć2a
ě3ć2e
ě3ć2i
ě3ć2o
ě3ć2u
ě3č2a
ě3č2e
ě3č2i
ě3č2o
ě3č2u
ě3d2a
ě3d2e
ě3d2i
ě3d2o
ě3d2u
ě3d2y
ě3d2ź2a
ěd8ź
ě3d2ź2e
ě3d2ź2i
ě3d2ź2o
ě3d2ź2u
ě3d2ź2y
ě3dl2
ě3dr2
ě3f2a
ě3f2e
ě3f2y
ě3f2o
ě3f2u
ě3f2i
ě3f2l
ě3f2r2
ě1g2r2
ě1g2l2
ě1g2a
ě1g2e
ě1g2o
ě1g2u
ě1g2i
ě1h2a
ě1h2o
ě1h2u
ě1h2i
ěj1
ě1j2a
ě1j2e
ě1j2i
ě1j2o
ě1j2u
ě1k2a
ě1k2e
ě1k2i
ě1k2u
ě1k2o
ě1k2r
ě1k2l
ě1ł2a
ě1ł2e
ě1ł2o
ě1ł2u
ě1ł2y
ě1l2a
ě1l2e
ě1l2o
ě1l2u
ě1l2i
ě3m2a
ě3m2o
ě3m2u
ě3m2i
ě3m2e
ě3m2y
ě3m2j
ě3n2a1
ě3n2e
ě3n2i
ě3n2o
ě3n2u
ě3n2y
ě3n4j
ě3p2a
ě3p2e
ě3p2i
ě3p2o1
ě3p2u
ě3p2y
ě3p4j
ě3p2l
ě3p2r
ě3r2a
ě3r2e
ě3r2i
ě3r2o
ě3r2u
ě3r2y
ě3r4j
ě3s2a
ě3s2e
ě3s2i
ě3s2o
ě3s2u
ě3s2y
ě3s2t
ěš1
ě3š8a
ě3š8e
ě3š8i
ě3š8o
ě3š8u
ě3š2y
ě3š2tap
ě3t2a
ě3t2e
ě3t2i
ě3t2o
ě3t2u
ě3t2y
ě3t2r
ě3t2l
ě3w2a
ě3w2e
ě3w2i
ě3w2o
ě3w2u
ě3w2y
ě3w4j
ě3z2a
ě3z2e
ě3z2i
ě3z2o
ě3z2u
ě3z2y
ě2ž1
ě3ž2a
ě3ž2e
ě3ž2i
ě3ž2o
ě3ž2u
i3b2a
i3b2e
i3b2i
i3b2o
i3b2u
i3b2y
i3b2j
i3b2r
i3b2l
i3c2a
i3c2e
i3c2o
i3c2u
i3c2y
i3c2i
i3ch2a
ic2h
i3ch2e
i3ch2o
i3ch2u
i3ch2i
i3ć2a
i3ć2e
i3ć2i
i3ć2o
i3ć2u
i3č2a
i3č2e
i3č2i
i3č2o
i3č2u
i3d2a
i3d2e
i3d2i
i3d2o
i3d2u
i3d2y
i3d2ź2a
id8ź
i3d2ź2e
i3d2ź2i
i3d2ź2o
i3d2ź2u
i3d2ź2y
i3dl2
i3dr2
i3f2a
i3f2e
i3f2y
i3f2o
i3f2u
i3f2i
i3f2l
i3f2r2
i1g2r2
i1g2l2
i1g2a
i1g2e
i1g2o
i1g2u
i1g2i
i1h2a
i1h2o
i1h2u
i1h2i
ij1
i1j2a
i1j2e
i1j2i
i1j2o
i1j2u
i1k2a
i1k2e
i1k2i
i1k2u
i1k2o
i1k2r
i1k2l
ił1
i1ł2a
i1ł2e
i1ł2o
i1ł2u
i1ł2y
i1l2a
i1l2e
i1l2o
i1l2u
i1l2i
i3m2a
i3m2o
i3m2u
i3m2i
i3m2e
i3m2y
i3m2j
i3n2a1
i3n2e
i3n2i
i3n2o
i3n2u
i3n2y
i3n4j
i3p2a
i3p2e
i3p2i
i3p2o1
i3p2u
i3p2y
i3p4j
i3p2l
i3p2r
i3r2a
i3r2e
i3r2i
i3r2o
i3r2u
i3r2y
i3r4j
i3s2a
i3s2e
i3s2i
i3s2o
i3s2u
i3s2y
i3s2t
iš1
i3š8a
i3š8e
i3š8i
i3š8o
i3š8u
i3š2y
i3š2tap
i3t2a
i3t2e
i3t2i
i3t2o
i3t2u
i3t2y
i3t2r
i3t2l
i3w2a
i3w2e
i3w2i
i3w2o
i2w1n
i3w2u
i3w2y
i3w4j
i3z2a
i3z2e
i3z2i
i3z2o
i3z2u
i3z2y
i2ž1
i3ž2a
i3ž2e
i3ž2i
i3ž2o
i3ž2u
o3b2a
o3b2e
o3b2i
o3b2o
o3b2u
o3b2y
o3b2j
o3b2r
o3b2l
o3c2a
o3c2e
o3c2o
o3c2u
o3c2y
o3ch2a
oc2h
o3ch2e
o3ch2o
o3ch2u
o3ch2i
o3ć2a
o3ć2e
o3ć2i
o3ć2o
o3ć2u
o3č2a
o3č2e
o3č2i
o3č2o
o3č2u
o3d2a
o3d2e
o3d2i
o3d2o
o3d2u
o3d2y
o3d2ź2a
od8ź
o3d2ź2e
o3d2ź2i
o3d2ź2o
o3d2ź2u
o3d2ź2y
o3dl2
o3dr2
o3f2a
o3f2e
o3f2y
o3f2o
o3f2u
o3f2i
o3f2l
o3f2r2
o1g2r2
o1g2l2
o1g2a
o1g2e
o1g2o
o1g2u
o1g2i
o1h2a
o1h2o
o1h2u
o1h2i
oj1
o1j2a
o1j2e
o1j2i
o1j2o
o1j2u
o1k2a
o1k2e
o1k2i
o1k2u
o1k2o
o1k2r
o1k2l
o1k2ł
oł1
o1ł2a
o1ł2e
o1ł2o
o1ł2u
o1ł2y
o1l2a
o1l2e
o1l2o
o1l2u
o1l2i
o3m2a
o3m2o
o3m2u
o3m2i
o3m2e
o3m8ě
o3m2y
o3m2j
o3n2a1
o3n2e
o3n2i
o3n2o
o3n2u
o3n2y
o3n4j
o3p2a
o3p2e
o3p2i
o3p2o1
o3p2u
o3p2y
o3p4j
o3p2l
o3p2r
o3r2a
o3r2e
o3r2i
o3r2o
o3r2u
o3r2y
o3r4j
o3s2a
o3s2e
o3s2i
o3s2o
o3s2u
o3s2y
o3sled1
o3s2t
oš1
o3š8a
o3š8e
o3š8i
o3š8o
o3š8u
o3š2y
o3š2tap
o3t2a
o3t2e
o3t2i
o3t2o
o3t2u
o3t2y
o3t2r
o3t2l
o3w2a
o3w2e
o3w8ě
o3w2i
o2w1n
o3w2o
o3w2u
o3w2y
o3w4j
o3z2a
o3z2e
o3z2i
o3z2o
o3z2u
o3z2y
o2ž1
o3ž2a
o3ž2e
o3ž2i
o3ž2o
o3ž2u
ó3b2a
ó3b2e
ó3b2i
ó3b2o
ó3b2u
ó3b2y
ó3b2j
ó3b2r
ó3b2l
ó3c2a
ó3c2e
ó3c2o
ó3c2u
ó3c2y
ó3ch2a
óc2h
ó3ch2e
ó3ch2o
ó3ch2u
ó3ch2i
ó3ć2a
ó3ć2e
ó3ć2i
ó3ć2o
ó3ć2u
ó3č2a
ó3č2e
ó3č2i
ó3č2o
ó3č2u
ó3d2a
ó3d2e
ó3d2i
ó3d2o
ó3d2u
ó3d2y
ó3d2ź2a
ód8ź
ó3d2ź2e
ó3d2ź2i
ó3d2ź2o
ó3d2ź2u
ó3d2ź2y
ó3dl2
ó3dr2
ó3f2a
ó3f2e
ó3f2y
ó3f2o
ó3f2u
ó3f2i
ó3f2l
ó3f2r2
ó1g2r2
ó1g2l2
ó1g2a
ó1g2e
ó1g2o
ó1g2u
ó1g2i
ó1h2a
ó1h2o
ó1h2u
ó1h2i
ój1
ó1j2a
ó1j2e
ó1j2i
ó1j2o
ó1j2u
ó1k2a
ó1k2e
ó1k2i
ó1k2u
ó1k2o
ó1k2r
ó1k2l
ół1
ó1ł2a
ó1ł2e
ó1ł2o
ó1ł2u
ó1ł2y
ó1l2a
ó1l2e
ó1l2o
ó1l2u
ó1l2i
ó3m2a
ó3m2o
ó3m2u
ó3m2i
ó3m2e
ó3m2y
ó3m2j
ó3n2a1
ó3n2e
ó3n2i
ó3n2o
ó3n2u
ó3n2y
ó3n4j
ó3p2a
ó3p2e
ó3p2i
ó3p2o1
ó3p2u
ó3p2y
ó3p4j
ó3p2l
ó3p2r
ó3r2a
ó3r2e
ó3r2i
ó3r2o
ó3r2u
ó3r2y
ó3r4j
ó3s2a
ó3s2e
ó3s2i
ó3s2o
ó3s2u
ó3s2y
ó3s2t
óš1
ó3š8a
ó3š8e
ó3š8i
ó3š8o
ó3š8u
ó3š2y
ó3š2tap
ó3t2a
ó3t2e
ó3t2i
ó3t2o
ó3t2u
ó3t2y
ó3t2r
ó3t2l
ó3w2a
ó3w2e
ó3w2i
ó3w2o
ó3w2u
ó3w2y
ó3w4j
ó3z2a
ó3z2e
ó3z2i
ó3z2o
ó3z2u
ó3z2y
ó2ž1
ó3ž2a
ó3ž2e
ó3ž2i
ó3ž2o
ó3ž2u
u3b2a
u3b2e
u3b2i
u3b2o
u3b2u
u3b2y
u3b2j
u3b2r
u3b2l
u3c2a
u3c2e
u3c2o
u3c2u
u3c2y
u3c2i
u3ch2a
uc2h
u3ch2e
u3ch2o
u3ch2u
u3ch2i
u3ć2a
u3ć2e
u3ć2i
u3ć2o
u3ć2u
u3č2a
u3č2e
u3č2i
u3č2o
u3č2u
u3d2a
u3d2e
u3d2i
u3d2o
u3d2u
u3d2y
u3d2ź2a
ud8ź
u3d2ź2e
u3d2ź2i
u3d2ź2o
u3d2ź2u
u3d2ź2y
u3dl2
u3dr2
u3f2a
u3f2e
u3f2y
u3f2o
u3f2u
u3f2i
u3f2l
u3f2r2
u1g2r2
u1g2l2
u1g2a
u1g2e
u1g2o
u1g2u
u1g2i
u1h2a
u1h2o
u1h2u
u1h2i
uj1
u1j2a
u1j2e
u1j2i
u1j2o
u1j2u
u1k2a
u1k2e
u1k2i
u1k2u
u1k2o
u1k2r
u1k2l
uł1
u1ł2a
u1ł2e
u1ł2o
u1ł2u
u1ł2y
u1l2a
u1l2e
u1l2o
u1l2u
u1l2i
u3m2a
u3m2o
u3m2u
u3m2i
u3m2e
u3m2y
u3m2j
u3n2a1
u3n2e
u3n2i
u3n2o
u3n2u
u3n2y
u3n4j
u3p2a
u3p2e
u3p2i
u3p2o1
u3p2u
u3p2y
u3p4j
u3p2l
u3p2r
u3r2a
u3r2e
u3r2i
u3r2o
u3r2u
u3r2y
u3r4j
u3s2a
u3s2e
u3s2i
u3s2o
u3s2u
u3s2y
u3s2t
uš1
u3š8a
u3š8e
u3š8i
u3š8o
u3š8u
u3š2y
u3š2tap
u3t2a
u3t2e
u3t2i
u3t2o
u3t2u
u3t2y
u3t2r
u3t2l
u3w2a
u3w2e
u3w2i
u2w1n
u3w2o
u3w2u
u3w2y
u3w4j
u3z2a
u3z2e
u3z2i
u3z2o
u3z2u
u3z2y
u2ž1
u3ž2a
u3ž2e
u3ž2i
u3ž2o
u3ž2u
y3b2a
y3b2e
y3b2i
y3b2o
y3b2u
y3b2y
y3b2j
y3b2r
y3b2l
y3c2a
y3c2e
y3c2o
y3c2u
y3c2y
y3ć2a
y3ć2e
y3ć2i
y3ć2o
y3ć2u
y3č2a
y3č2e
y3č2i
y3č2o
y3č2u
y3d2a
y3d2e
y3d2i
y3d2o
y3d2u
y3d2y
y3d2ź2a
yd8ź
y3d2ź2e
y3d2ź2i
y3d2ź2o
y3d2ź2u
y3d2ź2y
y3dl2
y3dr2
y3f2a
y3f2e
y3f2y
y3f2o
y3f2u
y3f2i
y3f2l
y3f2r2
y1g2r2
y1g2l2
y1g2a
y1g2e
y1g2o
y1g2u
y1g2i
y1h2a
y1h2o
y1h2u
y1h2i
yj1
y1j2a
y1j2e
y1j2i
y1j2o
y1j2u
y1k2a
y1k2e
y1k2i
y1k2u
y1k2o
y1k2r
y1k2l
ył1
y1ł2a
y1ł2e
y1ł2o
y1ł2u
y1ł2y
y1l2a
y1l2e
y1l2o
y1l2u
y1l2i
y3m2a
y3m2o
y3m2u
y3m2i
y3m2e
y3m2y
y3m2j
y3n2a1
y3n2e
y3n2i
y3n2o
y3n2u
y3n2y
y3n4j
y3p2a
y3p2e
y3p2i
y3p2o1
y3p2u
y3p2y
y3p4j
y3p2l
y3p2r
y3r2a
y3r2e
y3r2i
y3r2o
y3r2u
y3r2y
y3r4j
y3s2a
y3s2e
y3s2i
y3s2o
y3s2u
y3s2y
y3s2t
yš1
y3š8a
y3š8e
y3š8i
y3š8o
y3š8u
y3š2y
y3š2tap
y3t2a
y3t2e
y3t2i
y3t2o
y3t2u
y3t2y
y3t2r
y3t2l
y3w2a
y3w2e
y3w2i
y3w2o
y3w2u
y3w2y
y3w4j
y3z2a
y3z2e
y3z2i
y3z2o
y3z2u
y3z2y
y2ž1
y3ž2a
y3ž2e
y3ž2i
y3ž2o
y3ž2u
.a8b7itu1r
.a3b2i
.abi3t2u
.a8b7itu8r1n
b1b
b1ce
b1cy
b1ć
b1č
b1d
.bjez1
.b2j
.bje3z2e
b2j
b1h
b1k
b1m
b1n
b1p
b1s
b1t
b1w
b1z
b1ž
1ca
1ce
1co
1cu
c2h
.ch2c
.c2h
ch1ć
ch1n
č1n
d1bpo4d3
db1p
dbpo1
4de8ń5
d1n
oł1d2n4j
ołd1n
do1s1po1
do1z2na1
do2z3na.
n4j
nje1do3z4na.
nje3d2o
njedo1z2na1
im1ple
1h2d
1h2lad
2h1ny
2h1nu.
2h1nje
hn4j
1h2romad
hro3m2a
.nje1
.nje1z2h2romad
.n4j
.njezhro3m2a
1ka
k1c
1ku
1ki
k1n
k2nys1k
1ko
ko8n7ju3g
ko3n4j
ko8n7jun8k1
ko8n7ur1b
ko3n2u
k1s
k1t
k2tu.
kuz1ł
kuz1l
2k2st.
3ł2ha
.bo4ł3h
.boł1
.do4ł3h
.doł1
.do4ł3ho3
.po4ł3h
.po1
.poł1
l1n
3ł2ž
.do4łž1n
.do3ł2ž
.po4ł3ž
l1g
l1z
m1n
3m2ru.
3m2rje
mr4j
3m2r8ě
m1s
mys1l
na1
.na4d1
.na1
.nje1na4d1
.nje3n2a1
.na4d2e8ń5
.na3d2e
.nje1na4d2e8ń5
.njena3d2e
.na4d2eš1
.nje1na4d2eš1
2n1d
1ni.
1nja.
1nje.
n1t
n2t1n
nuh1l
p4j
po1
po4d3
po4d4e1
po2m1h
po2m8ń5
praw1
pře1
pře4d3
pře5d4a
pře5d4o
pře5d4i
pře5d4u
pře5d4ra3s2t
pře3dr2
pře5d4ras1ć
pře4d4e3
r1b
ser2b1
ser3b2a
ser3b2e
ser3b2o
ser3b2i
ser3b2y
r1ć
r1č
r1d
r1f
r2dź.
rd8ź
r4j
r1m
r1n
r1p
r1w
ro2z1
ro3z2e1
s1ć
si1gna1l
signa1
m2la2s3ka
z1mor3s3ka
pra2s3ka
plu2s3ka
ći2s3ka
wrje2s3ka
wr4j
1s2k2i.
s1ki
1s2k2a.
s1ka
1s2k2e.
1s2k2eho.
ske1h2o
1s2k2eje.
skej1
ske1j2e
1s2k2ej.
1s2k2aj.
skaj1
1s2k2emu.
ske3m2u
1s2k2u.
s1ku
1s2k2im.
1s2k2imaj.
ski3m2a
skimaj1
1s2k2eju.
ske1j2u
1s2k2imi.
ski3m2i
1s2k2i2ch.
skic2h
1s2c2y.
1s2c2e.
s1ce
s1n
s2tat1n
4st1n
2n1stw
1š2to.
1š2li.
1š2łe.
1š2ła.
1š2łoj.
šłoj1
1š8o
1š2oł.
šoł1
1š2łaj.
šłaj1
1š2łej.
šłej1
1š8a
1š8e
1š8ě
1š8i
1š8u
a2š1n4j
t1d
t1k
ě2t3n
t1m
t1n
t1p
w4j
.wje1l2e3
.w4j
w4n1st
.wo4b2e
wo4b3jas1n
wo3b2j
wo4b3jeć
wo4b3jed1n
wo4b3j8ě
wo4b3jim
.nje1wo4b2e1
.nje3w2o
.wo4t2e3
.njewo4t2e3
wo1zna1
.wu1
wu1s2nu
wus1n
wu1s2n2j2e
wusn4j
wu1s2ny
.za1
.za8ń5
.za2w1k
.za2w3da
ze1z2na1
z1nak
zna1
iz1na.
izna1
iz1ny.
iz1nu.
iz1nje.
izn4j
iz1no
2z1p
2z1s
.W8a8r9s8z8a9w8a.
.Warsza3w2a
.d8o9z8n8a.
.do1z2na1
.n8j8e8j9s8y8m.
.njej1
.n8j8e8j9s8y.
.n8j8e9j8e.
.nje1j2e
.n8j8e8j9s8m8ó8j.
.njejsm8ó
.njejsmój1
.n8j8e8j9s8t8a8j.
.njejstaj1
.n8j8e8j9s8t8e8j.
.njejstej1
.n8j8e8j9s8m8y.
.n8j8e8j9s8ć8e.
.njejs1ć
.n8j8e8j9s8u.
.p8ź8e8d8e.
.p8ź
.pźe3d2e
.p8ź8e9d8e8r8j8e.
.pźede3r4j
.t8k8a8l8c.
.t1k
.t1ka
.n8j8e8ń8d8ź.
.nje8ń5
.njeńd8ź
.z8e9t8n8u.
.zet1n
.z8e9t8n8j8e8š.
.zetn4j
.zetnješ1
.z8e9t8n8j8e.
.z8e9t8n8j8e9m8ó8j.
.zetnjem8ó
.zetnjemój1
.z8e9t8n8j8e9t8a8j.
.zetnje3t2a
.zetnjetaj1
.z8e9t8n8j8e9t8e8j.
.zetnje3t2e
.zetnjetej1
.z8e9t8n8j8e9m8y.
.zetnje3m2y
.z8e9t8n8j8e9ć8e.
.zetnje3ć2e
.w8o8t8e9t8n8u.
.wotet1n
.w8o8t8e9t8n8j8e8š.
.wotetn4j
.wotetnješ1
.w8o8t8e9t8n8j8e.
.w8o8t8e9t8n8j8e9m8ó8j.
.wotetnjem8ó
.wotetnjemój1
.w8o8t8e9t8n8j8e9t8a8j.
.wotetnje3t2a
.wotetnjetaj1
.w8o8t8e9t8n8j8e9t8e8j.
.wotetnje3t2e
.wotetnjetej1
.w8o8t8e9t8n8j8e9m8y.
.wotetnje3m2y
.w8o8t8e9t8n8j8e9ć8e.
.wotetnje3ć2e
.w8o9d8n8j8o.
.wod1n
.wodn4j
.w8o9s8n8j8e.
.wos1n
.wosn4j
.t8a8k9r8j8e8c.
.ta1k2r
.takr4j
PK
!<;Ȣ`
`
hyphenation/hyph_hu.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a2
.ada2t1á1t1a2
.a1da
.ada1tá
.ada2t1e2
.adás1s
.a1dá
.adá2s3z
.ad1y2é.
.ad1yi
.ag1os
.a1go
.ag2ra
.a2gyag1g
.ag2y
.a1gya
.agy1á2ram
.a1gyá
.agyá1ra
.agy1árt
.a1gy1e2
.a1gyo2
.agyon1
.a1gy1ó2
.a1gy1ű2
.akác1c
.a1ká
.aká2c2s
.ak2h
.a1ko2
.ak2t1é2r.
.ak1té
.ak2t1orr
.ak1to
.ak2t1ő2
.aku1p2
.a1ku
.ala2g1
.a1la
.ala1k1a2
.ala2k1ö2
.ala2k1ő2
.ala2pa
.ala2p1á
.al1eg
.a1le
.al1is
.a1li
.al2járn
.al1já
.al1je2
.al2j1el
.alje1l1ö2
.alo2m1
.a1lo
.al1os
.al2t1a2k
.al1ta
.al2t1erj
.al1te
.al2té2n
.al1té
.al2térn
.al2tért
.al2ti2n
.al1ti
.am1ak
.a1ma
.ango2l1ó2
.an1go
.an1th
.anti1s
.an1ti
.apa1p2
.a1pa
.apá2ly1á2z
.a1pá
.ap2ál2y
.apá1lyá
.ar2a2ny1e2
.a1ra
.aran2y
.ara2s1ze
.aras2z
.ar1á2c
.a1rá
.ar2cal
.ar1ca
.arc3c
.ar2c1e2
.ar2cél
.ar1cé
.ar2c3há
.arc2h
.ar2c3hoz
.ar1c1ho
.ar2cin
.ar1ci
.ar2ci1o
.ar2col
.ar1co
.ar2cö
.ar2c3s
.ar1kh
.at2h
.a1z1a2
.az1ám
.a1zá
.a1ze2
.a1z1ó2
.á2
.ács1é2k
.ác2s
.á1c1sé
.ág1árn
.á1gá
.ág1árt
.á1g1ó2
.ágy1a1la
.ág2y
.á1gya
.ágy1as2z
.ágy1árt
.á1gyá
.ágy1á2z
.á1l1a2
.á1l1é
.á1l1i2
.á2l1i2gaz1
.áli2g
.á2l1i1ga
.ál2l1alj
.ál1la
.ál2l1alt
.ál2lin
.ál1li
.ál1o2k
.á1lo
.á1l1ú
.ár1aj
.á1ra
.ár1ak1tá
.ár1a2l
.ára2m1e
.ár1a2p
.ára2s2z
.ár1a1s1zó
.ár1á2c
.á1rá
.ár1ál
.ár1á2z
.ár1d2
.á1re2
.ár1em
.á1ré2
.ár1ir
.á1ri
.ár2nye2l
.árn2y
.ár1nye
.ár1ol
.á1ro
.ár1om
.ár1os
.árrés1s
.ár1ré
.ár1s2
.ár1t2r
.ász1ál
.ás2z
.á1s1zá
.ász1árb
.ász1á2ré
.ász1á2ri
.ász1á2ro
.át1a2d
.á1ta
.át1a2k
.át1alt
.át1a2n
.át1ar
.át1a2s
.át1av
.á1t1á2
.á1t1e2
.á1t1é2
.á1t1i2
.át1ol
.á1to
.át1o2r
.át1o2s
.á1tó2
.át1óh
.át1óv
.á1t1ö2
.á1t1u
.á1t1ü2
.á1t1ű
.b2
.ba2b1a2rá
.b2abar
.ba1ba
.ba2bál
.ba1bá
.ba2b1e2
.ba2bol
.ba1bo
.ba2j1á2rat
.b2ajár
.ba1já
.bajá1ra
.ba2j1e
.ba2k1aszt
.ba1ka
.bakas2z
.ba2kál
.ba1ká
.ba2k1á2ro
.ba1ké2
.ba2k1ö2
.ba2ku2r
.ba1ku
.ba2l1e2g
.ba1le
.ba1ts
.ba2u
.bá2l1ó2
.bá1r1a
.bá1th
.be2a2t.
.be1a
.be1d2
.be1i2
.be1kr
.be1str
.be1szk
.bes2z
.beté2t1e2l
.be1té
.beté1te
.be1tr
.bér2c3sí
.bérc2s
.bé2r1o
.bi2ke
.bi2os
.bi1o
.bi2ot
.bi1ta2
.bi2tag
.bi2t1á2
.bi2tel
.bi1te
.b2i2t1er
.bi2t1orr
.bi1to
.bi2tur
.bi1tu
.bo2g1ó2
.bol2ta
.b2o2ly1ó2
.bol2y
.bo2nav
.bo1na1
.bo2raj
.bo1ra
.bo2ra2n
.bo2ras
.bo2rat
.bo2rác
.bo1rá
.bo2rál
.bo2r1odv
.bo1ro
.bor2sét
.bor1sé
.bort2
.bo2tá
.bra2i
.b1ra
.bu2s1z1e
.bus2z
.c2
.cen2t1á
.cen2t1ó2
.ce2t1e2l
.ce1te
.ce2t1ű
.cé2l1e2
.ci2n1á2
.ci1to1
.c2s2
.csa2k1
.c1sa
.csa2p1á2g
.csa1pá
.csa2t1é2
.cse2l1ő2r
.c1se
.cse1lő
.d2
.dac1c
.da2c3s
.da2i
.dal1an
.da1la
.da2lás
.da1lá
.da2l1e2
.da2l1ék
.da1lé
.d2a2lén
.da2l1í2
.da3lol
.da1lo
.da2l1ó2
.dan1n
.da2u
.den2g1
.dé2la
.dé1re2
.dé2res
.dé2sa2
.di2a1fo
.di1a
.di2a1ka
.di2al
.di2c1sá
.dic2s
.di2ó1má
.di1ó
.d2ó2mor
.dó1mo
.dú2ra
.e2
.eb1eg
.e1be
.eb1ir
.e1bi
.e1b1í
.e1b1u2
.e1g2é
.egres1s
.eg1re
.e1gy1a2
.eg2y
.e1gy1á2
.egy1e2lőr
.e1gye
.egye1lő
.egy1ev
.egy1in
.e1gyi
.e1gy1ö2
.egy1ü2l
.e1gyü
.el1a2k
.e1la
.el1an
.el1ap
.ela2s
.el1ass
.el1aszn
.elas2z
.el1a1s1zo
.el1aszv
.e1l1á2
.el1e2c
.e1le
.el1eger
.ele1ge
.ele2gyá2
.eleg2y
.el1e2h
.el1ejt
.el1e2l
.ele2ma
.ele2má
.ele2meg
.ele1me
.ele2mel
.e1l1eme1le
.el1e2mels
.el1e2melt
.el1e2més
.ele1mé
.el1e2n
.el1e2p
.el1e2r
.el1e2se
.el1e2sés
.ele1sé
.el1esh
.el1e2si
.el1esn
.el1e2ső
.el1ess
.el1es1te
.el1estél
.eles1té
.el1es1tü
.el1e2sü
.el1esv
.el1e2s2z
.el1e2t
.el1e2vet
.ele1ve
.el1e2vez
.el1e1vi
.elé2d
.e1lé
.el1é1de
.el1é2gek
.elé1ge
.el1éh
.el1ékez
.elé1ke
.el1é2le
.elé2n
.el1é1ne
.el1é2p
.el1é2r
.el1é2te2t.
.elé1te
.el1é2v
.el1id
.e1li
.el1ig
.el1i2h
.el1ik
.el1i2mit
.eli1mi
.el1in
.el1ir
.el1i2s
.eli2t1o
.el1itt2a.
.elit1ta
.el1itták
.elit1tá
.el1izz
.e1l1í
.elle1ge2
.el1le
.e1lo2
.el1okk
.el1o1ko
.el1or
.e1l1ó2
.e1lö2
.el1ök
.el1ö1le
.el1ö1lé
.el1ölh
.el1ö1li
.el1öl2j.
.el1öl1je
.el1öl1jé
.el1öl1jü
.el1öl1ne
.el1öln2é.
.elöl1né
.el1ölném
.el1öl1ni
.el1ö1lő
.el1öl2t.
.el1öl1te
.el1öl1té
.el1öltn
.el1öl1tü
.el1ölün
.elö1lü
.el1ölv
.el1öv
.elő1é2
.e1lő
.el1s2
.el1t2
.e1l1u
.e1lü2
.el1ü2l.
.el1ülh
.el1ü1li
.el1ülj
.el1üln
.el1ül1te
.el1ülv
.el2v1el
.el1ve
.el2v1é2g.
.el1vé
.es1er
.e1se
.ese2tel
.ese1te
.es2t1a
.es2t1á
.es2t1é2k2e.
.es1té
.esté1ke
.es2t1é2kek
.et2h
.etilén1g2
.e1ti
.eti1lé
.evés1s
.e1vé
.ex1el
.e1xe
.e1z1á
.ez1e2l
.e1ze
.é2
.édes3s
.é1de
.é1g1a2
.ég1eg
.é1ge
.ég1e2re1i
.ége1re
.ége2s
.ég1es2z
.ég1é2r.
.é1gé
.ég1é2ré
.ég1érn
.égés3s
.égé2s2z
.ég1észb
.ég1é1s1ze
.ég1é1s1zé
.ég1észn
.ég1észr
.é1g1ö2
.é1g1u2
.éh1év
.é1hé
.é1j1a2
.é1j1á2
.éj1el
.é1je
.é1j1u
.é1k1a2
.é1k1á
.ékes1s
.é1ke
.ék1ir
.é1ki
.é1k1o
.é1l1a2
.él2c1i
.éli2k
.é1li
.él1i1ke
.él2v1á2
.ép1es
.é1pe
.ép2pa
.ép2p1el
.ép1pe
.ép2pé
.ép2po
.ér1a2n
.é1ra
.ér2c1e2l
.ér1ce
.ér2c1é2j
.ér1cé
.ér2cék
.ér2c3sí
.érc2s
.ér1e2l
.é1re
.ér1e2s
.éré2s2z
.é1ré
.ér1é1s1zé
.ér1é2t
.é1rü2
.ér1ül
.ér2vad
.ér1va
.ér2val
.ér2v1é2g.
.ér1vé
.ész1a2l
.és2z
.é1s1za
.é1s1z1á
.é1s1ze2
.ész1el
.ész1em
.ész1es
.ész1é2k
.é1s1zé
.észigaz1
.é1s1zi
.é2sz1i2ga
.é1s1z1o
.é1s1z1ő2
.ész2t1örv
.ész1tö
.é1s1zü2
.ész1ü1lő
.év1á2g
.é1vá
.év1es2s2z
.é1ve
.év1é2g
.é1vé
.év1é2k
.f2
.fa2i2t.
.fa1i
.fa2leg
.fa1le
.fa2n1év
.fa1né
.fa2r1ont
.fa1ro
.fas2
.fa1st
.fat2
.fa1tr
.fe2le1me
.fe1le
.fe2l1es2s2z
.fe2lev
.fé2k1e2l
.fé1ke
.fé2m1a2
.fé2m1á2
.fil2mé1ré
.fil1mé
.fin2ge
.fog2ó2s3zá
.fo1gó
.fogó1s2z2
.fol2t1a2
.fö2lő
.fö2lü2l.
.fö1lü
.fölü2l1e2
.g2
.ga2za2n
.ga1za
.gá2t1al
.gá1ta
.gá2te
.gá2z1ó2
.gáz2s2
.gá2z1su
.gáz1z
.gene2a
.ge1ne
.ge2od
.ge1o
.ge2os
.gesz2t1í
.ges2z
.gé2d1
.gé2na2
.gé2ná2
.g2én3n
.gé2pe2lem
.g2épel
.gé1pe
.gépe1le
.gé2p1i2p
.gé1pi
.giga1s
.gi1ga
.gonor1
.g2o1no
.gonorr2
.gó2ce
.gó2la
.gó2lá
.gó2le2l
.gó1le
.gó2l1e2s
.góli2g
.gó1li
.gó2l1i1ga
.gó2li2s
.gő2z1á2
.gő2zen
.gő1ze
.gő2z1ő
.gő2z2s
.gu2i
.h2
.ha2b1e2
.ha2b1ol
.ha1bo
.ha2bor
.ha2b1ő
.ha2b1u
.ha2dal
.ha1da
.ha2d1e2
.ha2d2z
.ha2ik
.ha1i
.ha2j1e2
.ha2jom
.ha1jo
.ha2lác
.ha1lá
.halá2l1ó2
.ha2lárv
.ha2leg
.ha1le
.ha2l1e2l
.h2a2lep
.ha2let
.h2a2l1e2v
.ha2li2s
.ha1li
.han2ga2d
.han1ga
.han2g1e2
.h2a2sor
.ha1so
.has3s2z
.h2a2tag
.ha1ta
.ha2t1at
.ha2t1e2
.ha2told
.ha1to
.há2m1a2
.há2ny1in
.hán2y
.há1nyi
.háro2m1e
.há1ro
.há2t1alj
.há1ta
.há2tus
.há1tu
.há2zol
.há1zo
.há2zó
.he2i
.hé2t1ez
.hé1te
.hé2t1o
.hi2t1a
.hi2tis
.hi1ti
.hodás1
.ho1dá
.hol1l
.hol2t1e2
.ho2ne2
.hő2sá
.i2
.ike2r1i
.i1ke
.ike1rü2
.ike2r1ü1lé
.ikon1s
.i1ko
.ima1s
.i1ma
.im1p2la
.in1ak1tí
.i2nakt
.i1na
.in2gin
.in1gi
.inte2r1a
.in1te
.io2n1a2
.i1o
.io2n1á
.io2ne
.ion3n
.ipa2re
.i1pa
.izo2m1ért
.i2zo1mé
.i1zo
.í2
.íjá2t
.í1já
.íj1á1to
.í1n1e
.írá2s1ág
.í1rá
.írá1sá
.í2rá2s3z
.í1v1a2
.ív1á2r
.í1vá
.ív1ell
.í1ve
.í1z1a
.íz1in
.í1zi
.j2
.ja1ké2
.ja2kér
.ja2ko2v
.ja1ko
.jármű1ká2
.jár1mű
.já2s1zá
.jás2z
.já2s1zó
.je2gy1a2
.jeg2y
.je2gy1á2
.je2l1a
.je2leg
.je1le
.je2lev
.job2b1ol
.job1bo
.jó2dal
.jó1da
.jó2s1e2
.jó2t1á
.k2
.kale2i
.ka1le
.ka2nar
.ka1na
.ka2n1e
.kapolc1
.ka1po
.ka2ra1la
.ka1ra
.ka2ra2s
.ka2r1á2s
.ka1rá
.kar2c3se
.karc2s
.ka1re2
.ka2rem
.ka2rék
.ka1ré
.ka2ring
.ka1ri
.ka2rí
.ka2ró2ra
.ka1ró
.ka2r1ó2rák
.karó1rá
.ka2r1ü
.ka1th
.ka1tz
.ká2r1a2d
.ká1ra
.kás2
.kása3l
.ká1sa
.ke2c1só
.kec2s
.ke2l1á2
.ke2l1e2g
.ke1le
.ke2l1e2ve
.ke1l1ö
.ker2ta
.ké2nét
.ké1né
.ké2p1és
.ké1pé
.ké1ta2
.ké2tab
.ké2tad
.ké2t1ag
.ké2ta1ka
.ké2tal
.ké2tan
.ké2tap
.ké2tas
.ké2tat2
.ké2ta1u
.ké2t1á2
.ké2t1e1le
.ké1te
.ké2t1ez
.ké2t1o2
.ké1ze2
.ké2z1el
.ké2ze2m
.ké2z2s2
.kéz1z
.ki1á2
.ki1g2
.ki1k1rá
.ki1ó2
.ki2ság
.ki1sá
.ki1ű2
.kla2uz
.k1la
.kla1u
.kle2i
.k1le
.kló2re
.k1ló
.ko2rác
.ko1rá
.ko2rí
.ko2sis
.ko1si
.kó2d1a2
.kó2r1e
.kó2r1é2s
.kó1ré
.kö2b1öl
.kö1bö
.kö2d1ő
.kö2zis
.kö1zi
.kö2z1ő
.köz1z
.kr2
.ku1n1a
.kvar3k.
.k2vark
.k1va
.l2
.la2k1a2d
.la1ka
.l2a2kal
.l2a2k1an
.la2kép
.la1ké
.la2kor
.la1ko
.la2kő
.lan2t1e
.lan2t1ó2
.l2a2pal
.la1pa
.la2p1ác
.la1pá
.la2p1á2r
.la2p1e2
.la2p1or
.la1po
.la2p1os2z
.la2pó
.lá2b1e2
.lá2bil
.lá1bi
.lá2bor
.lá1bo
.lán2c3s
.lá2nya2n
.lán2y
.lá1nya
.lá2ny1e2
.lá2p1e
.lá2p1il
.lá1pi2
.lá2z1ó2
.lá2z3s
.le3dé
.le2g1
.le3g2a1li
.le1ga
.le3g2elés
.le1ge
.lege1lé
.le3g2esle2g1
.leges1le
.le3g2esle3g2esle2g1
.legesle1ge
.legesleges1le
.lege2t
.le4gé1ne
.le3gén
.le1gé
.leg3g2
.le3g2y
.lei2d
.le1i
.leí2ro
.le1í
.leí2ród
.leí1ró
.leí2ru
.le1kl
.le1k2r
.lemez1z
.le1me
.le1p2ré
.le1s2m
.le1t2r
.le1ü2
.lé2c3s2
.lé2g1a2
.lé2g1á
.lé1ge2
.lé2g1el
.lé2gér
.lé1gé
.lé2go
.lé2gy1á2
.lég2y
.lé2p1a2
.lé1pü2
.lé2p1ü1lé
.lé2t1a2
.lé2t1á2
.lé2t1elek
.lé1te
.léte1le
.lé2t1e2lé
.lé2t1érd
.lé1té
.lé2tör
.lé1tö2
.lisz2ta
.lis2z
.lisz2t1á2
.lisz2ter
.lisz1te
.lito1s
.li1to
.lo2b1e
.lo1bi2
.lo2bin
.lo2mag
.lo1ma
.lo2mal
.lo2m1á
.lom2b1e2
.lo2me
.lo2m1é2t
.lo1mé
.lon2c1si
.lonc2s
.lófo2g1a2d
.l2ófog
.ló1fo
.lófo1ga
.lő2cs1ö2v
.lőc2s
.lő1c1sö
.lőrés3s
.lő1ré
.ly1o
.l2y
.m2
.ma2e
.m2a2ga2l
.ma1ga
.ma2ga2r
.ma2gál
.ma1gá
.ma2g1e2
.mag1g
.ma1gó2
.ma2gór
.ma2i2n.
.ma1i
.m2a2r1ác
.ma1rá
.ma2r1ing
.ma1ri
.masz2k1or
.mas2z
.masz1ko
.masz2k1ö
.ma1th
.ma2uz
.ma1u
.má2r1is
.má1ri
.me2g1e2
.me2g1é2
.meg1if
.me2gi
.me2g1o2
.me2g1ő2
.me2i
.mel2lá
.me2ny1u2
.men2y
.me2zá
.médi2af
.mé1di
.médi1a
.mé2ná
.mé2no
.mé2s1za2
.més2z
.mé2szet
.mé1s1ze
.mé2s1zi
.mé2s1zo
.mé2zis
.mé1zi
.mi3rá
.mo2lyan
.mol2y
.mo1lya
.mű1i2
.mű1kr
.mű1s
.n2
.na2gya
.nag2y
.na2gyá
.na2gye
.na2gyú2
.n2a2pap
.na1pa
.na2p1ar
.na2pál
.na1pá
.na2p1e
.na2pés
.na1pé
.na2p1o2r
.na1po
.na2pö
.ne2ma
.ne2me2g
.ne1me
.ne2m1el
.ne2min
.ne1mi
.ne2ol
.ne1o
.ne2s1zá
.nes2z
.net2t1a2
.ne2um
.ne1u
.né2gyá
.nég2y
.né2pa2
.né2v1ál
.né1vá
.né2ve2l
.né1ve
.no2r1
.nya2k1á2ro
.n2y
.nya1ká
.nya2k1e2
.nya2k1ö2
.nyol2c1a2n
.nyol1ca
.o2
.o1da1
.ok1a2d
.o1ka
.ok1a2l
.ok1ka2
.ola2j1e2
.o1la
.ola2s1z1ó2
.olas2z
.olda2l1ú2t
.olda1lú
.ol1da
.or2r1aj
.or1ra
.or2r1alt
.or2ran
.or2r1e2
.or1ré2
.or2r1és
.or2v1a2
.or2vá
.or2v1é
.or1vi2
.or2vis
.ot1tr
.ó2
.ólo2m1a2
.ó1lo
.óme3g2
.ó1me
.ó1n1a2
.ó1n1á2
.ó1n1e2
.ón1év
.ó1né
.óvá2s1árt2
.ó1vá
.óvá1sá
.ö2
.ön1d2
.ö1n1e2
.ö1ni2
.ö1n1í
.ön1k2
.ön3n
.ö1n1o
.ö1n1ő
.ön1s
.ö1n1ü
.ös1s1z1a
.ös2s2z
.ös1s1z1á
.ö1te2
.öt1eg
.öt1el
.öt1t2
.öv1e2g
.ö1ve
.öv1e2l
.ö1v1ő2
.ő2
.őa3l
.ő1a
.ői3r
.ő1i
.őr1ab
.ő1ra
.őr1a2n
.őr1e2g
.ő1re
.őr1é2g
.ő1ré
.őr1é2s
.őr1ist
.ő1ri
.ő1r1o
.őr2s1ág
.őr1sá
.ő1r1u2
.őr1üg
.ő1rü
.ő1s1a2
.ő1s1á2
.ős1eg
.ő1se
.ős1e2l
.ős1e2r
.ős1e2s
.ős1é2g
.ő1sé
.ős1ért
.ő1s1í2
.ő1s1o
.ő1s1ó2
.ős1p
.ős1t2r
.ő1s1u2
.ő1s1ú
.ő1s1z1a2
.ős2z
.ő1s1z1á
.ősz1e2g
.ő1s1ze
.ősz1el
.ősz1em
.ős3ze1ne
.ős3ze1né
.ő1z1a2
.őz1eg
.ő1ze
.őz1e2l
.őze2t
.őz1e1te
.őz1ék
.ő1zé
.őz1é1re
.ő1z1ő
.őz3s
.ő1z1u
.p2
.pa2da
.pa2d1á2
.p2a2din
.pa1di
.pa2d1ó2
.pa2i
.pa2pa2r
.pa1pa
.pa2pál
.pa1pá
.pa2pe
.para1f2r
.pa1ra
.par1ke2
.par2ker
.par2ta
.par2tel
.par1te
.par2ter
.pá2c3s
.pán2ta
.pár2t1ö2
.pen3n2y
.pe2r1a2
.pe2r1á
.pe2r1enc
.pe1re
.pe2rok
.pe1ro
.pe2rü2l
.pe1rü
.pe2s
.pia2c3s
.pi1a
.pi1na1
.pin2t1ó
.ple2i
.p1le
.pon1ta2
.pon2te2
.po2rad
.po1ra
.po2ral
.po2ra2n
.po2rác
.po1rá
.po2rál
.po2re2
.po2r1us
.po1ru
.pó2ki2s
.pó1ki
.pó2k1ö
.pó2rás
.pó1rá
.pó2t1e
.pó2t1é
.pre1k2
.p1re
.prés3s
.p1ré
.proto1p2
.p2ro1to
.p1ro
.q2
.r2
.ra2b1as
.ra1ba
.ra2b1á
.ra2be
.r2abic1
.ra1bi
.ra2b1il
.r2a2b1i2z
.ra2bí
.ra2bor
.ra1bo
.ra2bö
.ra2b1ő2
.ra2bú
.r2a2gal
.ra1ga
.ra2j1ö2
.raj1tó2
.raj2t1ór
.rá2cs1á2z
.rác2s
.rá1c1sá
.rá1dr
.rá1fr
.rá1gr
.rá1i2
.rán2c1e
.rán2y2
.rá1sp2r
.rá1s2t2
.rát2
.rá1tk
.rá1tr
.re1k2re
.ren2d1a2
.ren2d1ő2
.re1p2ri
.rep2ro
.re1prod
.rete2k1
.re1te
.ré2m1emb
.ré1me
.ré2mu
.ré3p2a
.ré2sa2
.rés3szer
.rés2s2z2
.rés1s1ze
.ré2s1za
.rés2z
.r2é2s1zá2
.ré2sz1e2le
.ré1s1ze
.ré2szell
.ré2szer
.ré2s1zí
.ré2s1zo
.ré2s1ző
.ré2t1a2
.ré2t1á2
.ré2v1á2
.ré2zá2
.ré2ze2l
.ré1ze
.ré2ze2t
.ré2zis
.ré1zi
.ré2z1o
.réz1z
.ri2z1so
.riz2s
.rizs3z
.rí2má
.ro1c2kos
.roc1ko
.ro1mé2
.ro2m1ét
.ro2min
.ro1mi
.ro2mis
.ros2t1e2
.rug1g
.ru2m1a
.ru2mil
.ru1mi
.rú2de
.s2
.sa2h1a
.saj2te
.s2a2s1or
.sa1so
.s2a2vad
.sa1va
.sa2v1a1ra
.sa2v1ál
.sa1vá
.sa2vá1ri
.s2avár
.sa2v1e2
.sa2v1é2
.sá2r1ó
.sá2rú2
.sás1s
.sá2s3z
.sá2v1a
.sá2vá
.sá2vó
.sc2
.se2bal
.se1ba
.se2b1á2
.se2bel
.se1be
.se2bes2z
.se2b1o
.sé2fi
.s3gr
.si2em
.si1e
.si2ók
.si1ó
.sín3n
.sí2p1ő2
.sí2r1a2
.sí2rát
.sí1rá
.sk2
.so2kél
.so1ké
.so2kil
.so1ki
.so2kis
.so2kol
.so1ko
.so2m1ag
.so1ma
.so2ma2t
.so1n2y2
.so2ral
.so1ra
.so2rál
.so1rá
.sö2r1e2g
.sö1re
.sp2
.spor2t1á2
.s1po
.st2
.s1tar2t1a2
.s1ta
.s2z2
.szaba2d1e
.s1za
.sza1ba
.sza2k1at
.sza1ka
.sza2k1ás
.sza1ká
.sza2k1e
.sza2kö
.sza2k1ü2
.szá2l1ó2
.s1zá
.száz1z
.sze2g1é2r.
.s1ze
.sze1gé
.sze2gí
.sze2i
.sze2m1á
.sze2m1é2r.
.sze1mé
.sze2m1é2ri
.sze2r1ág
.sze1rá
.szer2v1a2d
.szer1va
.szer2v1e2v
.szer1ve
.sze2s1zá
.s2zes2z
.sze2szeg
.s1ze1s1ze
.sze2s1z1ó2
.szé2fa
.s1zé
.szén1n
.szé2t1
.szé1te2
.szín3n
.s1zí
.szk2
.szo2l1ó
.s1zo
.s2zókés2z1
.s1zó
.sz2ókés
.szó1ké
.szó2sza2k
.sz2ó1s1za
.s2zós2z
.sz2ó1s1zö2
.szó2sz1öv
.szt2
.t2
.t2a2gal
.ta1ga
.ta2g1a2r
.ta2g1á
.ta2g1e
.ta2gép
.ta1gé
.tag1g
.ta2g1i2n
.ta1gi
.ta2gö
.ta2nag
.ta1na
.ta2n1as
.ta2nál
.ta1ná
.tan1d2
.ta2n1e2
.ta2n1év
.ta1né
.tané2ve
.ta2nis
.ta1ni
.tan1n
.ta2n1ó2
.tant2
.ta2n1u2s
.ta1nu
.ta3rá
.t2a2t1ál
.ta1tá
.ta2t1ára1i
.t2atár
.tatá1ra
.ta2t1e2
.ta2t1í
.t2a2tor
.ta1to
.t2a2tur
.ta1tu
.tá2l1ó2
.tán2c1se
.tánc2s2
.tá2p1ol
.tá1po
.tár2sor
.tár1so
.tár2s1ö2
.tár2t1e2s
.tár1te
.tár2t1ölt
.tár1tö
.tá2v1a
.tá1vi2
.tá2v1ir
.tbc1
.te2a1ka
.te1a
.te2ar
.te2j1á2
.te2j1e2g
.te1je
.tera1pe2
.te1ra
.ter2v1a2
.ter2v1á2
.tes2ték
.tes1té
.tes2t1öl
.tes1tö2
.tetra1é2
.tet1ra
.te1za2
.té3k
.té2nyem
.tén2y
.té1nye
.té2nyí
.té2ra
.té2rá
.té2ret
.té1re
.té2r1int
.té1ri
.tí2z1ó2
.tí2z2s
.tí1zü2
.to2ká1ro
.to1ká
.tol2le
.topa3u2
.to1pa
.to2r1á2l
.to1rá
.to2r1odv
.to1ro
.tor2z3se
.torz2s
.tó1st
.tó2t1é2r.
.tó1té
.tön2k1a
.tő1a2
.tő1e2
.tő2gya
.tőg2y
.tő2r1éss
.tő1ré
.tő2r1é2s2z
.tra2u
.t1ra
.turnus1s2
.tur1nu
.tus3s2
.tu2s2z
.tu1s3zá
.tú2r1att
.tú1ra
.tű2z1ért
.tű1zé
.tű2z1ő2r
.tű1ző
.tű2z1se
.tűz2s
.tyú2ka
.t2y
.u2
.ugrás1s
.ug1rá
.un1in
.u1ni
.uni2o
.utas1s
.u1ta
.utás4
.u1tá
.u1to2
.utó2d1ö
.u1tó
.ú2
.új1as
.ú1ja
.ú1j1e
.ú1r1a2
.ú1r1á2
.ú1r1e
.úszós2
.ús2z
.ú1s1zó
.úszó1sp
.ú1t1a2
.ú1t1á2
.ú1t1e2
.út1ol
.ú1to
.ú1t1ő
.ú1t1ü2
.ü2
.ü2gy1é2r.
.üg2y
.ü1gyé
.ü1k1a2
.üstö2l
.üs1tö
.üs2t1ö1lő
.ütőkés2z1
.ü1tő
.ütő1ké
.üve2g1e2l
.ü1ve
.üve1ge
.ű2
.ű1r1a2
.ű1r1á2
.ű1r1e2
.űr1é2s
.ű1ré
.űr1é1te
.ű1ri2
.űr1i1ta
.űr1öss
.ű1rö
.űr1s
.űrt2
.v2
.va2d1al
.va1da
.va2dár
.va1dá
.va2dét
.va1dé
.va2d1o2r
.va1do
.va2dóv
.va1dó
.va2d1ö
.va2d3z
.va2gy1i
.vag2y
.va2j1e
.va2k1ak
.va1ka
.v2a2kal
.v2a2k1an
.v2a2kap
.va2k1ár
.va1ká
.va2k1e2
.va2k1ö
.v2a2rak
.va1ra
.va2r1á2s
.va1rá
.va2r1e2
.va2r1ing
.va1ri
.va2sab
.va1sa
.v2a2sar
.va2s1a2tom
.vasa1to
.va2s1á2g
.va1sá
.va2sár1k2
.va2sás
.va2s1e
.va2sék
.va1sé
.va2s1i2r
.va1si
.va2sol
.va1so
.v2a2s1or
.va2só
.vas3s
.v2as1tr
.vast2
.va2s1ü
.va2s3z
.vác2s1
.vá2dá
.vá2d1e2
.vá2ma2
.vá2r1a2dá
.vá1ra
.vá2r1i2s
.vá1ri
.vá2r1ol
.vá1ro
.verés1s
.ve1ré
.ver2s1é2g
.ver1sé
.ver2s1o
.ver2s2z
.vé2g1é2k
.vé1gé
.vé2g1o
.vé2nye2l
.vén2y
.vé1nye
.vé2r1a2
.vé2rá
.vé2r1e2b
.vé1re
.vé2r1eg
.vé2rik
.vé1ri
.vé2r1o
.vé2ró
.vér2t1ék
.vér1té
.vé2ru2
.vé2s1za
.vés2z
.v2é2s1zá
.vé2szer
.vé1s1ze
.vé2s1zí
.vé2s1zo
.vé2s1z1ó2
.vi1g2n
.ví2zed
.ví1ze
.ví2ze2m
.vona2t1út
.vo1na
.vona1tú
.von2z1e2
.w2
.x2
.y2
.z2
.za2be
.za2b1i2n
.za1bi
.za1i2
.za2j1e2
.za2j1ö
.za2jut
.za1ju
.zá2r1a2dá
.zá1ra
.zá2r1e
.zá2r1ó2ra
.zá1ró
.zárta2n
.zár1ta
.zár2t1an2y
.zár2t1é
.zár2t1ö2v
.zár1tö
.ze2i
.zé2t1
.z2s2
.zű2r1ő
2a.
a1a
aa2be
aa2cé
aa2c2h
aa2da
aadás1s
aa1dá
aa2dás2s2z2
aa2dó
aa2du
aa2fo
aa2ga
aa2gi
aa2gó
aa2g2y
aa2já
aa2ka
aa2ká
aa2ko
aa2ku
a2a2l.
aa2la
aala2g1
aa2lá
aal1eg
aa1le
aa2lé
aalma1na2
aal1ma
aalmanac3
aa2lo
aal1os
aa2lu
aa2ma
aa2na
aa2ne
aa2ni
aa2no
a2ans
aa2n2y
aa2pa
aa2pá
aa2po
aa2pu
aa2ra
aa2rá
aa2ré
aa2ri
a2arl
aa2ro
aa2s2z
aa2to
aa2t2y
aa2ur
aa1u
aa2ut
aa2va
a2a1vo
aa2zo
a1á
aá2bé
aá2c2s
aá2fá
aá2ga
aá2gá
aá2gé
aá2gi
aá2go
aá2gu
aá2g2y
aá2hí
aá2ju
a2á2l.
aá2la
aá2lo
aá2po
aá2ra
aá2rá
aá2r1e2
aá2ré
aá2ri
aá2ro
aá2ru
aá2rú
aá2sa
aá2sá
aá2so
aá2só
aá2ta
aá2t1á2
aá2t1e2
aá2té
aá2t1i2
aá2tí
aá2to
aá2t1ö
aá2tu
aá2tú
aá2tü
aá2zá
aá2zó
2abab
a1ba
ab1a1dó
aba2d2z
1a2bajg
ab1ak1k
2abal
2aban
a2ba2nal
aba1na
aba1pr
2abar
aba2rat
aba1ra
a2b1a2ráb
aba1rá
a2b1a1u
2abáb
a1bá
abá2b1u2r
abá1bu
2abád
2abán
a2b1áp
abá2rak
abá1ra
ab1á2ron
abá1ro
a2b1á2rú
2abáz
ab1b2a
1abbah
2abe1a
a1be
abe1á2
a2b1e2b
2abec
ab1e1dé
2abe1é
2abef
2abeh
2abe1i
2abej
a2b1ejt
ab1e1la
ab1e2lá
2abe1le
abe2lem
2abels
ab1els2z
a2b1elt
ab1elv
2abem
ab1emb
a2b1erd
2abe1tá
2abe1te
2abe1té
2abe1to
2abe1tö
2abev
a2b1ex
2abék
a1bé
2abél
2abén
a2b1é2ne
a2b1ép
2abér
a2b1érz
2abé1tá
a2b1é1ve
ab1fl
ab1fr
2abic
a1bi
ab1i2do
2abi1e
ab1i2ke
ab1ik1s
a2b1i2na1i
abi1na
abi2náb
abi1ná
a2b1ing
2abir
a2b1irká1i
abir1ká
a2b1ism
2abit
a2b1i2ta
2abiz
a2b1íj
a1bí
ab1írn
ab1kr
1abla1ká
ab1la
1ablakh
1ablakk
1abl2akos
abla1ko
1ablakr
2ab1lo
a1b2lú
2abog
a1bo
2aboh
2abok
2abolt
ab1ol1tó
2abom
abo2rak
abo1ra
abo2r1as
abo2rin
abo1ri
2abot
a2b1öb
a1bö
abö2l
ab1ö1lő
ab1ölt
a2b1ös
a2b1öt
a2b1ö2z
a2b1őrl
a1bő
ab1pr
ab2rek
ab1re
2ab1ri
a1b2ri1ke
ab2rin
a1b2rit
2ab1ró
ab2rók
ab1st
a2b1urd
a1bu
a2b1ú2r.
a1bú
a2b1úrt
abú2s1á
a2b1ús2z
ab1ü2l
a1bü
ab1üs
ab1üv
a2b1üz
aca2la
a1ca
aca2lá
aca2l1e2
ac2a2lé2t
aca1lé
a2c1a2n
aca2tá
a2c1ág
a1cá
a2c1ál
acci2ó
ac1ci
ac2cö
ac3c1se
ac2c2s
ac3c1sí
ac3c1sü
acc3s2z
a2c2e.
a1ce
ac1e1bé
a2c1eg
ace2l
a2c1e1le
a2c1e1lé
a2c1elh
ac1el1le
a2c1elm
a2c1e1lo
a2c1elv
ac1emb
2acen
ace2ta
a2c1e2v
a2c1ex
a2cé1lá
a1cé
acé1l1e2
a2cé1lé
a2c1ép
a2c1é2re
a2c1ér1te
ac1fr
a2c1há
ac2h
ac3héj
a1c1hé
a1c3hí
a2c3hoz
a1c1ho
a1c3hó
a2c3hö
a2c3hú
ac1i1de
a1ci
a2c1i2gá
a2c1i2gé
a2c1i1ke
2aci2n.
a2c1in1d2
a2c1inf
a2c1ist
a2c1ír
a1cí
ac1ív
ack1a1ro
ac1ka
ac2kál
ac1ká
ac2k1e2v
ac1ke
acké2r
ac1ké
ac2k1é1re
ack1é1te
ac2kil
ac1ki
ac2k1os2z
ac1ko
ac2kös
ac1kö
ac2kű
ac2lu
a2c1op
a1co
ac1ös
a1cö
ac1őr
a1cő
ac1pl
ac1pr
2acsal
ac2s
a1c1sa
acs1al2j.
acsa2p1á2g
acsa1pá
a2cs1atk
acs1áll
a1c1sá
a2csá1ru
acse2c
a1c1se
acs1eg2y
a2c3seml
1a2cséb
a1c1sé
a2cs1ék
2a1c1si
2a1c1sí
2acson
a1c1so
acs1orm
a2cs1öc
a1c1sö
acs1s
ac3st2r
a2csúl
a1c1sú
a2cs1úr
acsü2l
a1c1sü
a2csüt
ac3s1zá
acs2z
ac3s1ze
ac3s1zö
ac1tr
a2c1ud
a1cu
2acu1la
a2c1ut
ac1új
a1cú
ac1üg
a1cü
ac1ür
ac1üz
a1c3za
ac2z
a1c3zá
a1c3ze
ac3z2s
a1c3zu
a2d1a2da
a1da
a2d1a2dá
1a2da2g.
ada2gá
1a2dagb
a2dag3g
1a2dagh
1ada1gi
1a2dagj
1a2dagn
1a2da1go
1a2dagr
1a2dagt
1ada1gu
1a2da1gú
a2d1ak1tá
ada2lap
ada1la
ad2a2l1es
ada1le
1ada1lé
ad2a2lén
ada2l1os2z
ada1lo
ada2l1ó2
ada2l1ú2t
ada1lú
a2d1a1na
a2d1ann
1a2dapt
2adar
ada2ral
ada1ra
ada2re
ada2r1és
ada1ré
ada2r1in
ada1ri
ada2rut
ada1ru
ad2a2tab
ada1ta
ada2tal
ad2a2t1a2n
ada2t1á2r.
ad2atár
ada1tá
ada2t1á2rak
adatá1ra
ada2t1á2ram
ada2t1á2rat
ada2t1á2rá
ada2t1árb
ada2t1árr
ada2t1á2ru
1a2datá1u
ada2t1e2g
ada1te
ada2tel
ada2t1es
ad2até2r
ada1té
ada2t1érd
ada2t1é1rő
1a2d2atin
ada1ti
ada2tint
ad2a2tis
ada2tív
ada1tí
a2datm
ada2t1old
ada1to
ada2t1ö2l
ada1tö
ada2t1ő
a2datv
ad1a1zo
a2d1ág
a1dá
adá1ra2
adá2rak
adá2ris
adá1ri
ad2del
ad1de
ade2g
a1de
ad1e1ge
ad1e1gé
ad1eg2y
ade2i
a2d1ej
adel1ej
ade1le
ad1elh
ad1elm
ad1eln
a2d1e2lő
ad1elr
ad1elt
a2d1emb
ad1e2mé
a2d1eml
a2d1e1mu
ad1eng
ad1e2pe
ad1epr
a2derd
ad1e2rő
ade2ti
a2de1u
a2d1e2v
a2d1ex
adé2kat
a1dé
adé1ka
adé2k1e2
ad2é2kés
adé1ké
adé2kis
adé1ki
adé2kü
adé2kű
ad1é1le
2adémont2
adé1mo
a2d1ép
a2d1érz
adé2s
adé2te
a2d1é2ve
ad1fl
adfo2k1út
ad1fo
adfo1kú
ad1gr
1ad1hé
2adi1a
a1di
adia2n
ad1i2bo
2adid
ad2i2kut
adi1ku
2adin
ad1i2n2a.
adi1na
ad1i2na1i
a2d1ind
a2d1ing
adi2o1g2ra
adi1o
2adip
2adis
2adiv
a2d1í2z
a1dí
ad2ji
adka2na
ad1ka
ad1kr
2adob
a1do
2adoc
a2d1ok1ke
2adol
ad1o1la
1a2do1má
2adomb
2ado1mi
1a2dop
a2d1orc
a2d1org
2adorh
2adorian
ado1ri
adori1a
2adorig
ad1orv
a2d1orz
a2d1os2z
a2dó1gu
a1dó
1a2dó1ku
a2dó1mé
a2dó1rá
1a2dósat
adó1sa
2a3dós2i.
adó1si
ad1ó2vás
adó1vá
1a2dó1zó
a2d1ö2k
a1dö
a2d1ö2l
ad1örd
a2d1ös
adő1r1a
a1dő
adő2rel
adő1re
ad1ő2s
ad1pl
ad1pr
a1d2rac
ad1ra
ad2ram
ad2raz
2ad1rá
ad2rám
2ad1ro
ad2rog
a1d2rót
ad1ró
ad2ruk
ad1ru
ad1sp
ad1st
2adug
a1du
2adum
2adup
ad1u2rá
ad1ús2z
a1dú
adú2t
a2d1ú1to
a2d1üg
a1dü
ad1ü2lé
a2d1üt
ad1üz
a2d1űr
a1dű
ad1űz
ad1yéh
ad1yér
ad3zab
ad2z
a1d1za
ad3zav
ad3zár
a1d1zá
ad3zel
a1d1ze
ad3zón
a1d1zó
a2d3zö
a1d3z1sí
adz2s
a1e
ae2bé
ae2ce
ae2c2s
ae2dé
ae2d2z
ae2ge
ae2gé
ae2g2y
ae2he
ae2ke
ae2ké
ae2la
ae2l1á2
ae2le
ael1ej
ae3len
ael1érh
ae1lé
ae2l1í2
ae2lo
ae2l1ö2
ae2lő
ae2lu
ae2me
ae2mé
ae2mi
ae2mu
ae2ne
ae2pe
ae2pé
ae2pi
ae2po
ae2red
ae1re
ae2ré
aero1s
ae1ro
ae2ró
ae2rő
ae2se
aes1er
ae2sé
ae2si
ae2ső
ae2sü
ae2s2z
ae2ta
ae2tá
ae2te
ae2ti
ae2tű
ae2va
ae2ve
ae2vé
ae2vi
ae2vo
ae2vő
ae2xe
ae2zü
a1é
aé2derv
aé1de
aé2ge
aé2gé
aé2gő
aé2he
aé2je
aé2ke
aé2kí
aé2le
aé2lé
aé2li
aé2lő
aé2lü
aé2lű
aé2ne
aé2pí
aé2pü
aérde2m1
aér1de
aé2ri
aé2te
aé2va
aé2ve
aé2vé
aé2vi
aé2vü
2af2a.
a1fa
2afa2j.
2afa1ja
2afa1já
2afajb
2afa2j1e2
2afajj
2afajn
2afa1jo
2afaj2t.
afajt2
2afa1ju
2afajz
2afak
2afal
2afam
2afa1o
2afar
2afas
afe2l1e2m
a1fe
afe1le
2afék
a1fé
2afé1li
2afé1lő
2afélt
2afén
2afér
2afés
1af1fé
afi2a1p
a1fi
afi1a
afi2as2z
afi2ke
afi2t1a2
afi2t1e2
af2le
a1f2lo
a1f2ló
a1f2lö
a1f2lu
2afoc
a1fo
2afog
2afok
2afol
2afon
2aford
2aforg
2aformác
afor1má
2aformál
2aformá1tu
2aformáz
2afor1mu
2aforr
2afos
2afot
af2rak
af1ra
2a1f2re
af2riz
af1ri
af2rí
2af1ro
af2ron
2a1f2rö
af3tá
afus3s
a1fu
a2g1abl
a1ga
a2g1abr
ag1a2cé
a1g1a1ga
a2g1a2ka
a2g1akk
a2g1akt
2agal
a2g1a2lak
aga1la
a2g1a2lap
a2g1a2lá
a2g1alj
a2g1alm
aga2lom
aga1lo
a2g1alt
ag1ang
ag1a2no
ag1ant
a2gan2y
a2gap
ag1a1pa
ag1a1pá
a2g1arc
a2g1a1ré
a2g1a2ro
a2g1art
aga2tom
aga1to
ag1a2tó
a2g1a2ur
aga1u
ag1aut
a2g1a1va
2agaz
a2g1a2zon
aga1zo
agá2c
a1gá
a2g1ác2s.
agác2s
a2g1á1c1si
ag1á2ga
1a2gák
a3gá1la
a2g1álm
agá2lyan
a3g2ál2y
agá1lya
a2g1áp
a2gá2r.
a2g1á2rad
agá1ra
a2g1á2ra1i
a2g1á2rak
a2g1á2ras
a2g1á2ra2t
a2gá2rá
a2gárb
a2g1árc
a2gá2re2
a2gá1ré
agá2rév
a2gárf
a2gárh
a2gá2ri
a2gárj
a2gárk
a2gárm
a2gárn
ag1ár1ná
a2gá1ro
a2gárr
a2gár1s2
a2gárt
a2g1á2ru
a2g1á2rú
ag1ásv
a2g1á1tá2
a2g1á2t1e2
a2g1át1fe
a2g1áth
a2g1átk
a2g1átm
agá2tol
agá1to
a2g1áts
a2g1á2tü2
a2g1átv
ag1bl
ag2del
ag1de
agdí2j1a2da
ag1dí
agdí1ja
a2g2e.
a1ge
ag1e2c2s
a2g1e2d
a2g1e2g
age2l
a2g1e1la
ag1elb
ag1eld
ag1e1le
ag1e1lé
ag1elf
ag1elh
ag1e1li
ag1elm
ag1eln
a2g1e1lo
a2g1e1lő
ag1elr
ag1els
ag1elt
ag1e1lű
ag1elv
ag1elz
a2g1e2m
ag1eng
a2g1en2y
a2g1e2p
a2g1erd
age2red
age1re
a2g1erk
a2g1e2rő
age2s
a2g1e2v
a2g1ex
a2g1ez
a2g1é2j
a1gé
a2g1é2k.
a2g1ékn
a2g1é2l
agé2né
agé2p1i2p
agé1pi
a2g1é2pül
agé1pü
a2g1é2r.
a2g1é2re
a2g1é2ré
a2g1érh
a2g1é2ri
a2g1érk
a2g1érl
a2g1érm
a2g1ér1te
a2g1ér1té
a2g1érth
a2g1ér1tő
ag1és2z
a2g1é1te
a2g1é1ve
ag1fl
ag1fr
1ag2g.
ag2g1a2t2y
ag1ga
ag2g1em
ag1ge
ag2git
ag1gi
1aggl
1aggod
ag1go
1aggok
ag3gyar
ag2g2y
ag1gya
ag3gye
ag3gyi
ag3gyo
ag3gyü
agi3a
a1gi
ag1i1de
a2g1i2ga
a2g1i1ge
a2g1i2gé
ag1i1ke
a2g1ill
a2g1inf
a2g1ing
a2g1int
a2g1i2o1ni
agi1o
agi2ó
a2g1ip
a2g1i1ro
a2g1ist
agi2s2z
a2gi1ta
ag1i1zé
ag1izm
a2g1íj
a1gí
ag1ín
a2g1ír
ag1ív
a2g1íz
agká2rok
ag1ká
agká1ro
ag1kl
ag1kr
ag2n2e.
ag1ne
a1g2non
ag1no
a2g1ob
a1go
ag1ol1tó
ago2ly1a2
agol2y
2agom
2ago1na
agon3n
ago2n1os2z
ag2o1no
a2g1op
a2g1org
ag1orj
a2g1orn
a2g1orr
a2g1ors
a2g1orv
a2g1otth
agó2rá1i
a1gó
agó1rá
a2g1ö2l
a1gö
ag1önk
a2g1önt
a2g1ö1rö
a2g1örv
a2g1ös
a2g1öt
a2g1ö2v
a2g1ö2z
a2g1őr
a1gő
a2g1ő2s
ag1pl
ag1pr
2a1g2rammj
ag1ra
a1g2rav
2ag1rá
a1g2róf
ag1ró
ag1sk
ag1sp
ag1sr
ag1st
ags2z2
ag1szt2
ag1tr
a2g1und
a1gu
a2guram
agu1ra
agu2rat
ag1u2rá
ag1urn
ag1u2tá
a2g1új
a1gú
a2g1ú2t1a2
a2g1ú1ti
a2g1útt
a2g1ü2l
a1gü
a2g1ür
a2g1üs
a2g1üt
a2g1üv
a2g1üz
ag1űr
a1gű
ag1űz
a2gy1a2c
ag2y
a1gya
a2gyad
agy1a2dó
a2gy1a2gya
agyag2y
a2gyaj
1a2gya2k.
a2gyakb
agya2la
agy1alap
agy1alg
a2gyalj
agy1al1ko
agy1alm
1a2gyam
agy1a1na
a2gy1an2y
a2gy1a1pa
agy1ap1ja
a2gy1ap1já
a2gy1a2pó
a2gy1apr
agya2s2z
a2gy1a1s1zó
a2gyál
a1gyá
agy1árv
a2gy1e2c
a1gye
agy1e2g
a2gy1el
agy1em
agy1est
agy1es2z
a2gyev
a2gy1ez
agy1é2k.
a1gyé
agy1é1ke
agy1ékk
a2gy1é2r.
a2gy1é2re
a2gy1érn
a2gy1érr
agy1érs
a2gyi1ma
a1gyi
agy2nyá
agyn2y
agyo2r
a1gyo
a2gy1o1ro
a2gyorr
a2gy1ö2l
a1gyö
a2gy1ő2r
a1győ
agyu2r
a1gyu
a2gy1u1rá
1a2gy2ú.
a1gyú
1a2gyú1a
a2gyún
agy1ú2r.
agy1ú2s2z
a2gyút
2a1gyű
aha2l1e
a1ha
aha2sábr
aha1sá
ahelyü2kü
a1he
ahel2y
ahe1lyü
ahert2
aher1t1ze
ahé2j1út
a1hé
ahé1jú
a2h1ips
a1hi
ahitköz1
ahit1kö
ah1o2vi
a1ho
ahú2sár
a1hú
ahú1sá
ahús3s
a1i
ai2bo
2ai2de
ai2dom
ai1do
2ai2dő
a2iék
ai1é
ai2ga
ai2gá
ai2ge
ai2gé
ai2g2y
ai2i1a
ai1i
ai2ib
ai2ih
ai2ij
ai2in
ai2ir
ai2it
ai2je
ai1ka2
ai2kab
ai2k1ad
ai2k1al
ai2k1ar
ai2k1as2
ai2k1á
ai2ke2
aik1el
ai1ki2
ai2kik
ai2kis
ai2k1ol
ai1ko
ai2k1os2z
ai2kő
ai2kü
a2ilb
a2i1le
2aill
ail2l2e.
ail1le
ail2lo
ai2m2a.
ai1ma
2ai2má
2ai1mi
ai2mit
2aimp
ai2n2a.
ai1na
ai2na2l
ain1a1la
2ai2nas
ai1ná2
ai2n1á1lo
ai2nár
2aind
ai2ne
ai2né2l
ai1né
2ainf
2aing
ai2n1in
ai1ni
ai2nol
ai1no
2ainp
2ains
2aint
ai2nü
ai2onb
ai1o
ai2onn
ai2o1no
ai2onr
ai2ont
ai2pa
a2i2r.
ai2ram
ai1ra
2ai2rat
2ai2rá
ai2r2e.
ai1re
ai2ré
ai2ri
2ai1ro
ai2rod
a2i2se
ai2si
2aisk
2aism
2aist
2ais2z
ai2s1za
ai2s1zo
2ai2ta
ai2vad
ai1va
ai2var
ai2vás
ai1vá
2ai2vó
ai2zé
ai2zom
ai1zo
a1í
aí2gé
aí2ja
aí2já
aí2ju
aí2ra
aí2rá
aí2ro
aí2ró
aí2ru
aí2té
aí2vá
aí2ve
aí2vé
aí2vi
aí2vó
aí2vü
aí2vű
aí2ze
aí2zé
aí2zü
aí2zű
a2j1a2dó
a1ja
a2j1a1du
aj1a1ga
aj1agr
aja2kol
aja1ko
a2j1a1kó
aja2kú
a2j1a1na
a2j1ant
a2j1an2y
aj1apr
a2j1á1a
a1já
ajá2c
aj1á2go
1a2jánd
1a2jánl
a2j1áp
2ajár
a2j1árb
a2j1árc
a2j1á1re2
a2j1á1ré
a2j1árr
aj1á1sá
aj1ásv
a2j1á2t1e2
ajá2z
aj1á1zá
aj1bl
aj1br
aj2d1alm
aj1da
aj2d1a2lo2m.
ajda1lo
aj2d1a2lomm
aj2d1a2lomn
aj2d1a2lomr
aj2d1a2lomt
a2j1e2c
a1je
a2j1ef
a2j1e1gé
a2j1e1la
aje2le1me
aje1le
a2j1elf
aj1el1lá
a2j1e2l1o2
aj1e1lő
aj1el1vá
aj1el1vo
a2j1e2m
aj1e1ne
aj1enz
a2j1e2r
aj1e2se
a2j1ex
a2j1ez
a2j1é1ge
a1jé
a2j1é1gé
a2j1é2gő
a2j1é2k
a2j1él
a2j1ép
ajé2r
a2j1é1re
a2j1é1te
aj1fl
aj1fr
aj1g2r
a2j1i2d
a1ji
a2j1ij
a2j1ik
a2j1im
a2j1int
a2j1i1o
a2j1ip
a2j1iz
aj1ír
a1jí
aj1íz
aj1kl
1ajk2ú.
aj1kú
ajob1b1o
a1jo
ajo2g1á2s1za
ajo1gá
ajogás2z
a2j1o1la
aj1old
a2j1o2v
a2j1ócskás
a1jó
ajóc2s
ajócs1ká
ajó2sár
ajó1sá
aj1öb
a1jö
a2j1ök
a2j1ör
a2j1öz
aj1őr
a1jő
aj1ő2s
aj1pl
aj1pr
aj1sh
aj1sk
aj1sp
aj2tág
aj1tá
aj2teg
aj1te
aj2t1é2t
aj1té
ajt1or1g2
aj1to
aju2hi
a1ju
a2j1új
a1jú
aj1ús2z
a2j1ú1to
a2j1útr
aj1üg
a1jü
aj1ül
aj1üs
aj1üz
aj1űz
a1jű
ajz1a1ka
aj1za
ajz1atl
aj2ze2r
aj1ze
aj2zí
aj2zü
2akab
a1ka
aka2c1se
akac2s
a2kadag
aka1da
a2k1a2da1ta
a2k1a2datb
a2k1a2datn
a2k1a2da1to
a2k1a2datr
a2k1a2dat1t2
1a2kadál
aka1dá
1a2kadém
aka1dé
a2k1adm
a2k1a1ga
2akal
ak1a2lag
aka1la
a2k1a2lak
aka2la1pú
a2k1aleg
aka1le
a2k1al1ko
2akam
2akan
2aka1o
2akap
a2ka2pád
aka1pá
a2k1app
ak1a2ra1i
aka1ra
a2k1a2ras2z
akara2s
a2k1a2ráb
aka1rá
a2k1a2rák
2akarc
a2karc2h
2akard
ak1a2rén
aka1ré
2aka1ri
2akarr
2aka1sí
2akast2
aka2szaj
akas2z
aka1s1za
a2k1a2szat
ak2a2szel
aka1s1ze
aka2s1zö
a2k1asztr
2aka1ta
ak2a2tab
2aka1te
aka2tel
aka2ter
aka1ti2
aka2tik
aka2tim
ak2a2tin
2aka1u
a2k1a2u1tó
2akav
2akaz
1aká2c.
a1ká
a2k1áf
a2k1ág
aká2l1a
aká2lis
aká1li
a2k1ál1ló
a2k1á2rad
aká1ra
a2k1árb
a2k1árj
a2kárk
aká2rokn
aká1ro
a2k1ár1tó
akárt2
a2k1á2ru
aká2sad
aká1sa
aká2saj
aká2sal
aká2sar
aká2sav
a2ká2sást
aká1sá
akás3s
ak1á2szán
akás2z
aká1s1zá
aká2s1zu
a2k1ászun
aká1t1a
a2k1át1la
ak1bl
akció2s1ű2
ak1ci
akci1ó
a2k2e.
a1ke
ake2c2s
a2k1e2d2z
ak1e2ge
ak1e2gé
a2k1e1la
a2k1e1lá
ake2lem
ake1le
ak1elh
a2k1elj
a2k1e1lo
ake2lők
ake1lő
a2k1elr
a2k1elv
a2k1emb
ak1e2mel
ake1me
a2kerd
ak1e2re1i
ake1re
a2k1e2ró
a2k1e2rő
a2kesp
a2k1est
ak1eszk
akes2z
ak1eszm
a2k1e2te
ak1e2ti
a2k1e2vez
ake1ve
a2k1é2kem
a1ké
aké1ke
ak1é2kes
a2k1é2ké
a2k1ékh
ak1ék1rő
2akém
2akén2y
2aké2p.
2aképb
2aké1pe
2aké1pé
2aképh
2aképk
2aképl
2aképn
2aképpel
akép1pe
2akép1pé
2aképr
2aképt
2aké1pü
2aképz
a2k1érc
2akérd
a2kérdek
akér1de
2aké1re
2aké1ré
a2k1érm
2aké1rő
a2k1é2rő1i
a2k1é2rőj
a2k1érr
a2kérte1ke
akér1te
a2k1értel
a2k1értet
a2k1ér1tő
2akérv
a2k1érz
2akés
a2k1é2s1za2
akés2z
a2k1é2te
2akéts
a2k1étt
2akéz
ak1fl
ak1fr
akgerinc1
ak1ge
akge1ri
ak1gr
2aki1a
a1ki
2akib
2akic
2akid
a2k1i2dé
a2k1i1di
2aki1e2
2aki1é
2akif
a2k1i1ga
a2k1i1gé
2akig2y
2akih
2aki1í
2akij
2akil
a2k1ill
a2k1il2y
2akim
a2k1i2má
a2k1i1mi
2akin
a2k1ind
a2k1ing
a2k1ins
a2k1ion
aki1o
2akir
a2ki2rom
aki1ro
2akis
a2k1i1si
a2k1isk
a2k1ism
a2k1ist2
2akit
2akiv
2akiz
a2k1izm
a2kí1té
a1kí
a2k1í2z
ak2k1a2d
ak1ka
ak2kaj
ak2k1a2la
akk1alk
ak2k1arc
ak2kál
ak1ká
ak2k1á2p
ak2k1ed
ak1ke
akk1ell
ak2kelm
akk1elt
ak2kem
ak2k1e2ró
akke2s
akk1e1se
ak2ket
ak2ko1la
ak1ko
1akkord
akk1ölt
akkö2l
ak1kö
ak2k1ös
ak2kőr
ak1kő
1akk2u.
ak1ku
1akkum
ak2la1u
ak1la
ak2lav
ak2lor
ak1lo
ak2lón
ak1ló
ak2lór
1akna1i
ak1na
1aknáb
ak1ná
1aknáh
1akná1i
1akná1ka
1akná1ko
1aknás
1akná2t.
1aknáv
1aknáz
2ako1a
a1ko
ak1obj
2akoc
2akof
2akokt
akolás3s
ako1lá
a2k1ol1da
a2k1o2l2y
2akom
2akonc
2akond
2akonf
2akong
2akonk
2akons
2akont
2akonv
2akon2y
2akonz2
2ako1o
2akop
a2k1o2pe
ak1o2rat
ako1ra
2akorb
2akord
a2k1org
2ako1ri
a2k1orj
2akorl
2akorm
2ako1ro
2akorp
2akorr
2akor1s2
2akort
2ako1ru
2ako1rú
a2k1orv
2akos
2akó1ni
a1kó
2akó1p2
a2k1ó2rá
a2k1ó2ród
akó1ró
1a2kó1zá
ak1ös2s2z
a1kö
akö2z1é2l
akö1zé
aközre1a3
aköz1re
ak1ő2r.
a1kő
ak1ő2s.
ak1pl
ak1pr
ak1ps
akrá1di2
ak1rá
ak2rát
2ak1re
ak2re1a
2ak1ré
a1k2ré1me
ak2ré1ta
ak2ré1tá
2ak1ri
a1k2rit
2ak1rí
ak2ríz
1akrob
ak1ro
2a1k2rónik
ak1ró
akró1ni
ak1sp
ak1sz2t2
aks2z
ak2t1a1u
ak1ta
aktár2s1a2d
ak1tá
aktár1sa
akt1emb
ak1te
1akt2i.
ak1ti
ak2ti2m
1aktívb
ak1tí
aktí2ve
ak2t1ív2e.
1aktívk
1aktí1vo
1aktív1s
1aktívt
akto2r1ál
ak1to
akto1rá
akt1os2z
ak1t1rá
1aktu1a
ak1tu
1akt2ú.
ak1tú
a2k1udv
a1ku
a2k1ujj
2akun
1a2ku1pu
a2k1úg
a1kú
a2k1új
a2k1úrr
a2k1üg
a1kü
a2k1ü2le
a2k1ü2lé
a2k1üln
a2k1ü2t
a2k1ü2v
ak1ya
a1ky
a2l1abl
a1la
alac1c
ala1ce2
ala1ci2
ala2cit
ala2cor
ala1co
a2la1c1sé
alac2s
ala2c3sö
ala2c1sü
a2ladag
ala1da
a2l1a2da1tá
ala2gál
ala1gá
ala2g1e
ala2gép
ala1gé
ala2gol
ala1go
ala2gya
alag2y
ala2j1a2d
ala1ja
ala2jas
ala2j1e2
a2la2kad
ala1ka
al2a2k1an
a2l1a2kas
ala2kál
ala1ká
ala2k1áp
ala2kes
ala1ke
1a2la1kí
a2l1akn2a.
alak1na
a2laknák
alak1ná
a2laknát
ala2kol
ala1ko
a2l1a2kód
ala1kó
ala2k1öl
ala1kö
a2l1ak1tu
alakt2
1ala1ku
a2laku2l.
a2lakult
al1alg
a2l1alj
a2l1alk
al1all
al1alm
al1a2lo
al1alt
ala2n1e
1a2lan2n2y
a2lan2y
ala2nyal
ala1nya
al2a2ny1e2
ala2p1a2d
ala1pa
ala2pak
al2a2pal
a2lapan
ala2p1á2r
ala1pá
ala2p1e2
ala2pill
ala1pi
ala2pin
ala2pir
1a2lapítv
ala1pí
ala2p1ol
ala1po
ala2por
ala2p1os2z
ala1p1ó2
alap1p
al1a2ra
al1a2rá
al1arg
ala1s2p
a2l1aszp
alas2z
alaszta2l
alasz1ta
alat1an2y
al2atan
ala1ta
ala2t1á1t1a2
ala1tá
ala2t1e2v
ala1te
ala2t1inf
al2atin
ala1ti
ala2tív
ala1tí
ala2t1ol
ala1to
ala1tó2
ala2tór
a2l1attak
alat1ta
1a2lat1ti
ala2t1ü2
al1a1va
ala2zúr
ala1zú
1a2lá1a
a1lá
1a2lá1á
alába2d
alá1ba
alá2b1a1da
a2l1áb1rá
1a2lábúj
alá1bú
alá2dal
alá1da
alá2d1a1p
1a2láf
a2l1á2g.
a2lá1ga
al1á2gá
al1ágb
a2l1ágg
al1ágh
al1á2gi
al1ágk
al1ágn
al1á2go
a2l1ágr
al1ágt
al1á2gú
al1á1gya
alág2y
1a2lá1í
1a2lá1mo
a2l1á2rad
alá1ra
a2lárak
a2lá1rá
alá2rár
a2l1árk
alás1te2
alás2tel
alás2t1é2r.
alás1té
a2l1átd
a2lá1te
a2lá1té
al1áté2p
a2l1átf
alá2ti
a2l1átl
a2l1átm
a2lá1tö
a2látr
a2lá1tú
al1bl
al1br
1album
al1bu
al1d2r
a2l2e.
a1le
2ale1a
2aleb
al1e1bé
2alec
al1e1ce
ale2g1e2lé
ale1ge
al1egés
ale1gé
ale2gés2z1
a2l1egys
aleg2y
a2le1gyü
2aleh
2ale1í2
2alej
ale2k1a
a2l1e1la
a2l1elág
ale1lá
a2l1eld
al1e2led
a1le1le
ale2le2m.
ale2lemb
ale2le1me
ale2lemk
ale2lemm
ale2lemt
a2l1elg
a2l1elh
al1elm
a2l1eln
ale2lők
ale1lő
ale2lőt
a2l1elr
a2l1els
al1el1té
a2l1elv
2alem
a2l1emb
a2l1e2mel
ale1me
al1e2mé
a2l1eml
2alen
a2l1e1ne
2alep
ale2p2e.
ale1pe
a2lerd
a2l1e2re
a2l1erk
a2l1ern
2ales
al1esem
ale1se
a2le1si
ale2sik
ale2tet
ale1te
alet2t1est
alet1te
ale1ü2
2alev
ale2vol
ale1vo
ale2vő1i
ale1vő
2alex
a2l1exp
2aléc
a1lé
2alég
a2l1é2gő
alé2kal
alé1ka
alé2k1an
alé2ka2t
alé2k1em
alé1ke
alé2ker
alé2kes
alé2kor
alé1ko
a2lé2l.
al1é2le2n
alé1le
a2l1é2let
a2l1é1lé
a2l1éll
al1é2lő
a2l1é1lü
2alén
a2léne1ke
alé1ne
a2l1érd
al1érs
a2l1ér1te
a2l1ér1té
a2l1ér1tő
a2l1érz
2alét
alé2tek
alé1te
a2l1é2tel
a2l1étl
a2l1é1vi
1alfás
al1fá
1algásat
al1gá
algá1sa
1algor
al1go
al1gr
2alic
a1li
al1i1de
a2li1dé
al1i2do
al1ifj
a2l1i1gé
2alik
a2l1ill
2alim
a2l1i2má
a2linas
ali1na
ali2nin
ali1ni
alió2ra
ali1ó
al1i2pa
a2l1i1rá
a2l1i1ro
a2l1i2si
a2l1ism
ali1s2po
alis1p2
al1is1te
2ali1te
al1iz1ma
al1íj
a1lí
a2l1í2v
alja2i1ké
al1ja
alja1i
1alja1ka
1aljakb
1alja1ké
1aljak1k2
1alja1ko
1aljas
1aljá1i
al1já
alj1ár1na
al2j1á2ro
1aljb
1aljc
1aljd
1al2j1er
al1je
1aljf
1aljg
1aljh
1alji1a
al1ji
al2jí
1aljj
1aljk
1aljl
1aljm
1aljn
1aljr
1aljs
1aljt
1al1jú
1al2jü
1aljv
1aljz
al2k1a2pó
al1ka
alke1le2
al1ke
1alkím
al1kí
1alkoh
al1ko
1alko1tá
1alkotm
1alko1tó
al1k1re
al1k1ro
1alk2u.
al1ku
1alkud
1alkun
al2l1aj
al1la
al2l1akt2
al2l1akv
alla2l
al2l1a1la
al2lalk
al2la1u
all1áll
al1lá
all1áz2s
al2led
al1le
all1eg2y
all1emb
1allerg
all1e1se
all1est
all1e2vő
all1é2jé
al1lé
al2l1id
al1li
al2lim
all1int
al2li2p
al2l1isk
al2lí
all1ó1ri
al1ló
al2lös
al1lö
al2l1ő2
al2lü
1al1lű
1almád
al1má
2alob
a1lo
a2l1o2be
alogos1s
alo1go
alo2g1ó2
a2l1ok1ta
al2ol2d.
a2l1old
alo1ma2
alo2mad
alo2mak
alo2m1al
alo2m1an
alo2map
alo2mar
alo2mas
alo2mác
alo1má
alo2már
alo2m1át
alo2mer
alo1me
alo2min
alo1mi
alo2mi2s
alo2mit
alom1p2
alo2m1ú
alo2n1á
alon1d2
alon3n
2alop
al1opc
a2l1o2pe
al1o2ra
al1orc
a2l1orn
al1o2ro
a2l1orr
alos3s
a2l1os1tá
a2l1oszl
alos2z
2alov
al2ó2c3se
a1ló
alóc2s
3alógu2s1e2s
aló1gu
alógu2s1e2
alóigaz1
aló1i
al2ói2ga
alói2ko
al1ó2lo
a2l1ónn
aló1ó2
a2ló2ráj
aló1rá
aló2rák
aló2za2n
aló1za
aló2zis
aló1zi
a2l1öb
a1lö
a2l1ö2l
a2l1ön
a2l1ör
a2l1ös
a2l1ö2z
alpe2l
al1pe
alp1e1le
al2piz
al1pi
al1sh
al1sk
al1sl
al1sm
al1sp
als2z2
al1szt2
al2ta1da
al1ta
al2t1alap
alta1la
alt1a1nya
altan2y
alt1elv
al1te
alt1emb
al2t1e2p
al2t1e2v
al2t1é2k
al1té
alté2n
alt1é1ne
alt1ér1ne
al2t1é2rő
al2t1érr
alt1ér1tő
al2t1i2m
al1ti
alti2n
alt1i1na
alti2p
alt1i1pa
al2t1ir
al2t1old
al1to
1alton1k2
al2t1os2z
al2tóc
al1tó
al2tön
al1tö
al1trak
alt1ra
al1tran
al1trav
al2tur
al1tu
al2t1út
al1tú
al2tür
al1tü
1alt1vé
al1t2y
alu1p
a1lu
1a2luss
alu1str
alust2
a2l1új
a1lú
al1ú2r.
al1úrb
al1úrh
al1ú2ri
al1úrk
al1úrn
al1úrr
a2l1ú1té
a2l1úth
a2l1útj
a2l1útn
a2l1útt
al1üg
a1lü
al1ül
al1ün
al1ür
al1üs
al1üt
al1üv
a2l1üz
al1űr
a1lű
al1űz
1alve1o
al1ve
1al1vó
a2ly1ap
al2y
a1lya
a2lyar
a2lyál
a1lyá
a2ly1e2
a2lyév
a1lyé
a2ly1id
a1lyi
a2lyim
a2lyis
a2lyö
a2lyug
a1lyu
a2ly1ü2
am1abb
a1ma
am1abl
2amad
a2m1a2dat
ama1da
am1a2dás
ama1dá
a2m1adm
a2m1a2dó
a2m1a2du
2amag
ama2gát
ama1gá
2amaj
am1ajt
ama2kar
ama1ka
a2m1akt
a2m1akv
a2m1a2lak
ama1la
am1a2lap
a2m1a2l1e
ama2nya
aman2y
ama1ó2
2amap
2amas
am2a2sz1a2k
amas2z
ama1s1za
am2a2szeg
ama1s1ze
ama2sz1em
ama2szél
ama1s1zé
ama2s1zö
ama2tad
ama1ta
am2ata2n
ama2tá1rá
am2atár
ama1tá
ama2tel
ama1te
ama2told
ama1to
ama2t1os2z
ama2t1ó2
a2m1a2u
a2ma1zo
a2m1álm
a1má
a2m1á2ra1i
amá1ra
a2m1á2rak
amá2rá
amát1a2d2ó.
a2m1á2ta
amá2ta1dó
a2m1átk
a2m1átl
a2m1átt
am2b1ag
am1ba
am2bal
am2b1at
am2b1á1s1zá
am1bá
ambás2z
am2b1e2g
am1be
am2b1e2le
am2bep
am2b1e2te
am2b1é2r.
am1bé
1ambu1la
am1bu
am1dr
a2m2e.
a1me
am1e2ce
2ameg
am1e1lá
am1e2lem
ame1le
a2m1e2l1i
am1elj
a2m1elk
a2m1eln
a2m1e1lo
a2m1e2lő
a2m1els
a2m1elt
a2m1elv
a2m1e2me
a2m1eng
2amenn
amens1s
amen2t1á2ro
amen1tá
a2m1erd
a2m1e2rő
a2mesk
2amest
a2m1e2v
a2m1ex
am1ezr
amé2hes
a1mé
amé1he
amé2k
am1é1ké
amé2let
amé1le
a2m1ép
a2m1érd
a2m1értek
amér1te
a2mértel
a2m1étk
a2m1é1vé
1amfo1rá
am1fo
am1fr
am1gr
ami1d1i2
a1mi
ami2g
a2m1i1ga
a2m1i1gá
a2m1i1gé
a2mi1má
a1m1i1mi
am1imp
ami2n2a.
ami1na
ami2na2n
ami2nin
ami1ni
a2m1in1té
ami1ó2
a2m1i1rá
a2m1i1ro
ami1se2
ami2sel
ami2sep
ami2s2z
1a2mit1bo
1a2mit1ha
1a2mitm
1a2mi1tö
1a2mit1ro
1a2mit1rú
1a2mits
1a2mit1tá
1a2mit1tö
a2míg
a1mí
am1kl
am1kr
amme2g
am1me
am2m1eg2y
am2me1ta
am2m1é2t
am1mé
2amod
a1mo
a2m1o2k1e2
a2m1okm
a2m1o2koz
amo1ko
am1o1la
a2m1old
a2m1ol1tá
a2m1op
a2m1or1s2
1a2mort2
a2m1orv
2amos
amos3s
2amot
2amoz
am1ó2ri
a1mó
am1ö2r
a1mö
am1ös
am1öt
am1ö2z
am1ő2r
a1mő
am1ős
am1p1la
am1p2r
1amput
am1pu
am1sk
am1sp
am1sr
am1st
am1t2r
2amun
a1mu
a2mu1ni
amu2riz
amu1ri
amu1sl
a2m1u2tas
amu1ta
a2m1új
a1mú
am1üg
a1mü
am1ü2l
am1üt
am1üz
2anad
a1na
a2n1a2dat
ana1da
a2n1a1du
a2n1a1ga
a2n1a1gá
an1a2gya
anag2y
a2n1ah
2ana1i
1a2nakr
ana2lap
ana1la
1ana1lí
an1alk
an1alm
1a2na1ló
an1a2mo
a2n1a1na
1a2na1ná
an1ann
an1a2n2y
2anap
ana2pa2
a2n1ap2a.
a2n1a1pá
a2n1a2pó
a2n1ap1po
anapp2
an1a2rab
ana1ra
an1a2rá
a2narc
ana2s1z1e2
anas2z
ana2szén
ana1s1zé
an2a2szin
ana1s1zi
an2a2s1z1í2
ana2s1zó
an2a2s1zü
ana2t1e
ana2tö
2anav
a2n1a2va
a2n1az
a2n1ág
a1ná
a2n1ál1mi
a2n1á2lom
aná1lo
a2n1á2p
a2násat
aná1sa
2anát
a2n1á1ta
a2n1átk
a2n1átr
an1br
anca3u
an1ca
an2c1ál
an1cá
an1ce2
an2c2e.
an2c1é2r.
an1cé
an2c3hit
anc2h
an1c1hi
an2c1ho
anci2al
an1ci
anci1a
an2c1ó2
an2csaj
anc2s
an1c1sa
an2csa2r
ancs1ell
an1c1se
ancs1emb
an2cs1en
ancs1e2p
ancs1et
an2csé1ré
an1c1sé
an2cs1ill
an1c1si
an2csiz
an2cs1í2z
an1c1sí
an2cs1or
an1c1so
an2c1sö
an1c1ső2
ancs1t
an2csu2t
an1c1su
an2c1s1ü
an2d1alk
an1da
anda1s
and1atl
and1e2le
an1de
and1elk
an2d1e1lő
and1els
an2derd
an2d1es
1andez
an2dél
an1dé
an2dil
an1di
an2d1ö
an2d1ő2
an2dús
an1dú
an2dün
an1dü
an2dű
an1dy
an2d1zá
and2z
a2n1eb
a1ne
an1edd
an1e2gé
1a2nekd
ane2la
ane2l1á
ane2l1e2l
ane1le
ane2l1emb
ane2lél
ane1lé
ane2lő
2anem
an1e1mu
an1e2re
an1ern
an1err
2ane1u
2anev
a2n1ex
ane2z
an1e2z.
a2n1e1ze
an1e1zé
an1ezt
an1ezz
a2n1é2ké
a1né
a2n1é1le
a2n1é2pí
a2n1é1ri
a2n1érv
a2n1étk
a2n1étt
a2n1évc
a2n1é2vem
ané1ve
an1é2ves
a2n1é2vet
a2né2véb
ané1vé
ané2vén
ané2vét
ané2vév
an1év1ha
ané2vi2g
ané1vi
an1év1ke
an1év1kö
a2n1é1vü
a2névz
anfé2l1é2v
an1fé
anfé1lé
an1fl
an1f2r
anga2d
an1ga
an2g1a1da
an2g1a1do
an2g1a1la
an2g1a2ra
an2g1ass
ang1a1zo
an2g1á2c
an1gá
an2g1áll
angá2r1a2d
angá1ra
ang1á1ta
an2g1átj
an2g1átt
an2g1ed
an1ge
an2g1eg
an2g1elf
an2g1elh
an2g1elj
an2g1ell
an2g1eln
an2g1e1lő
an2g1elt
an2g1elv
an2gem
ang1emb
ang1eng
an2g1e2r
ang1e1se
ang1é1le
an1gé
ang1élv
an2g1é2ne
an2g1é2r.
ang1é1rő
an2g1és
an2gi2m
an1gi
an2giz
an2gí
an2g1os2z
an1go
an2g1ó1ri
an1gó
an2g1öl
an1gö
an2g1ös
an2g1ő2
ang1s2z
an2gü
an2gű
an1gye2
ang2y
angy1el
an2gyék
an1gyé
an1i1de
a1ni
ani2g
a2n1i1ga
a2n1i1gé
a2n1i2ko
a2n1ind
a2n1inf
an1i2on
ani1o
2anip
a2n1i2pa
a2n1i2rá
a2n1i1ro
a2n1i1si
a2n1isk
a2n1ism
a2ni1ta
an1itt
a2n1íg
a1ní
a2n1íj
2anív
a2n1íz
ank1abl
an1ka
an2kaj
an2k1a2k
ank1a1le
an2k1a2n
ank1arc
ank1a1ri
an2k1atl
an2k1a1u
an2kaz
an2k1ál
an1ká
an2k1e2g
an1ke
an2k1ek
an2k1e2l
an2k1e2m
an2k1e2reit
anke1re
ankere1i
an2k1erj
an2k1es
ank1ér1de
an1ké
ank1érem
anké1re
an2kér1te
an2k1ér1té
an2k1i2d
an1ki
an2k1i2p
an1k1lu
an2k1old
an1ko
ank1oszt
ankos2z
an2k1ö2römb
an1kö
ankö1rö
an2kös
an2k1ö2v
an2kőr
an1kő
ank1t2
an2k1ü
an2n2e.
an1ne
an3n2y.
an2n2y
an3nye
an3nyo
a2n1oj
a1no
a2n1ok1ta
a2n1old
1a2no1má
2anor
a2n1o2ro
a2n1orr
a2n1or1s2
2anos
a2n1ott
a2nódd
a1nó
a2nó1do
anó1g2
a2n1ó1ni
a2n1ó1no
a2n1ó2rá
an1ó1ri
a2n1öl
a1nö
a2n1ön
a2n1ör
a2n1ös
a2n1ö2t
an1pl
an1pr
an2s1e2l
an1se
an1s1ka
an2sö
an1s2p
ans3s1ze
ans2s2z
an1s1ta
an2szal
ans2z
an1s1za
an2sz1á2bó
an1s1zá
an2sz1á2h
an2szár
ansz1es
an1s1ze
an2szél
an1s1zé
an2sz1én
an2sz1é2p
an2szil
an1s1zi
an2szin
an2s1zó
ansz1t2
ansz1ü2l
an1s1zü
an2t1abl
an1ta
ant1a1ga
an2t1eg
an1te
1anten
an2t1e2se
ant1es2z
anti1llát
an1ti
antil1lá
an2t1ing
an1t2re
a2n1ud
a1nu
a2n1ug
a2n1uj
2anuk
a2n1u2r
anu2s1zi
anus2z
a2n1u2t
a2n1úg
a1nú
an1ü2g
a1nü
anü2l
a2n1ü1lő
an1ü2z
an1űr
a1nű
an1űz
a2ny1a2dó
an2y
a1nya
anya2g1á2r.
a2nyagár
anya1gá
anya2g1árr
1anyagb
1a2nya1gé
anyag1g
1anyagh
1anyagk
1a2nyagm
1anyagr
1anyagt
1a2nya1gú
a2nyakad
anya1ka
a2ny1a2kas
a2ny1alk
a2ny1all
a2ny1ass
a2ny1aszt
anyas2z
a2ny1a2tom
anya1to
a2nyaz
1a2nyádt
a1nyá
1a2nyáék
anyá1é
any1á1lo
a2ny1á1rá
a2ny1árb
a2ny1árf
a2ny1árk
a2ny1árn
a2ny1á2ro
a2ny1árr
any1á2s2z
any1d
2a1nye
a2ny1e2c
a2ny1ed
a2nyeg
any1e1gé
any1eg2y
a2ny1e2k
a2nye1la
anye2le1me
anye1le
any1elev
a2ny1ell
a2ny1e1lo
a2ny1em
a2ny1en
any1e2r2e.
anye1re
any1e2re1i
any1e2ret
any1e2rén
anye1ré
any1e2rér
any1e2rét
any1e2rév
a2ny1e2rőm
anye1rő
any1e2rőr
any1e2rőt
a2ny1ég
a1nyé
a2nyé1he
a2ny1é2j
a2ny1ék
any1élv
a2ny1é2r.
a2ny1érb
a2ny1érc
a2ny1ér1d2
a2ny1é1re
a2ny1érg
a2ny1érh
a2ny1é2ri
a2ny1érk
a2ny1érm
a2ny1érn
a2nyé1rő
a2ny1érp
a2ny1érr
a2ny1érs
a2ny1ér1te
a2nyér1té
a2ny1ér1tő
a2ny1érv
a2nyé1ve
a2nyé1vé
anyha2j1ón
any1ha
anyha1jó
anyha2j1ó2r
2a1nyi
anyigaz1
a2ny1i2ga
any1ing
a2ny1i1o
2a1nyí
2a1nyo
any1old
a2ny1o2r
any1ó1rá
a1nyó
any1ök
a1nyö
any1ö2r
any1öz
a2ny1ő2
any1s
anyt2
any1tr
a2nyur
a1nyu
2a1nyú
2a1nyü
any1ül
a1o
ao2áz
ao1á
ao2be
ao2c2s
ao2da
ao2dú
ao2ka
ao2ká
ao2la
aolaja2d
aola1ja
aola2j1a1da
ao2mo
ao2pá
ao2pe
ao2ra
ao2ro
ao2so
ao2ut
ao1u
ao2ve
ao2vi
ao2xi
a1ó
aóá2r
aó1á
a2óbar
aó1ba
a2ó1bá
a2ó1bi
a2ó1bo
aó2ce
aó2dá
a2ó1di
a2ó1fá
a2ó1fe
a2ó1fo
a2ó1fő
a2ó1fü
a2ó1hé
a2ó1hi
a2ó1hü
a2óil
aó1i
a2óis
a2ói2v
a2ó1ká
a2ó1ke
aó1k1ré
a2ó1ku
aó2la
a2ó1le
a2ó1lé
a2ó1li
aó2lo
aó2lu
a2ómag
aó1ma
a2ómar
a2ómas2
a2ó1me
a2ó1mi
a2ó1mo
a2ó1né
a2ó1nö
a2ó1nő
aó2rá
a2ó1ré
aó2ri
a2ó1sí
a2ó1sű
a2ó1ta
a2ó1te
a2ó1té
aó2vo
aó2vó
a1ö
aö2bö
aö2c2s
aö2dé
aö2ko
aö2kö
aö2le
aö2lé
aö2lő
aö2ná
aö2rö
aö2ve
aö2vi
aö2vö
aö2zö
a1ő
aő2re
aő2ré
aő2ri
aő2rö
aő2rü
aő2se
aő2sö
aő2s2z
apa2cs1a2v
a1pa
apac2s
apa1c1sa
ap2a2c1s1i
a2p1a2da
a2p1a1ga
a2p1a1já
a2p1akc
2apal
a2pa2lag
apa1la
apa2lak
a2p1alb
a2p1alj
a2p1alt
ap2a2mas
apa1ma
a2p1ant
2apap
apa2pán
apa1pá
ap1a2rán
apa1rá
1apa1sá
a2p1asp
apa2tad
apa1ta
apa2t1a1la
ap2ata2n
apa2t1as
ap2a2tál
apa1tá
apa2t1ö
ap1aut
apa1u
2apav
a2pa1va
ap1a2zo
a2pá1jú
a1pá
a2p1állap
apál1la
a2p1ál1lá
a2p1ál1lo
apán1n
a2p1á2rad
apá1ra
ap1á2ra1i
a2p1á2rak
ap1á2ram
a2p1á2ras
ap1á2rat
a2pá2rá1é
apá1rá
apá2ráh
apá2rán
apá2rár
apá2ráv
apár1ba2
a2p1á2r1e2
a2p1á2ré
a2p1árf
ap1ár1ka
ap1ár1ko
a2p1árn2y
ap1ár1tó
a2p1á1ru
a2p1á2rú
apás1ká2
apá2túr
apá1tú
1a2pá1u
ap1bl
ap1dr
a2p2e.
a1pe
ap1e2dé
a2p1e2g
ap1e2l1a
ap1elb
ap1e2lé
a2p1elf
ap1elg
a2p1elh
ap1elj
ap1elk
a2pell
ap1elm
ap1eln
ap1e1lo
ap1e2lő
ap1elr
a2p1elt
a2p1elv
ape2m
ap1emb
ap1e1me
ap1e1ne
ap1e1ni
ap1e2n2y
ap1e2rő
ape2s
ap1e1se
ap1e1sé
ap1e1ső
a2p1e2v
a2p1ex
a2p1é2he
a1pé
a2p1é1je2
a2p1é2le
a2p1éll
a2p1élm
a2p1é2ne
a2p1é1pü
a2p1é2r.
ap1észl
apés2z
a2p1é1te
a2p1é1ve
ap1fl
ap1f2r
ap1gr
a2p1i1de
a1pi
a2pi1dé
apigaz1
a2p1i2ga
ap1i2ko
ap1ikr
a2p1i2nár
api1ná
a2p1ind
a2p1inj
a2p1ins
ap1i2rat
api1ra
a2p1i2rá
a2p1irk
a2p1ism
a2p1íg
a1pí
a2p1ín
a2pí1té
ap1ív
a2p1íz
ap1kl
ap1kr
ap1kv
ap2laz
ap1la
ap2léd
ap1lé
apmeg1
ap1me
ap1mű1
apműt2
a2p1ob
a1po
ap1o2laj
apo1la
a2p1oltár
apol1tá
a2p1opc
a2p1o2pe
a2p1op1t2
apo2rad
apo1ra
ap1or1cá
ap1or1só
apor2t1ő2
apo2t1ál
apo1tá
a2p1ov
1a2pó1ká
a1pó
ap1ó1lo
ap1ó1ri
a2p1öb
a1pö
ap1öl
ap1ön
ap1örv
a2p1ös
ap1öv
a2p1ö2z
ap1ő2r
a1pő
1appa1rá
ap1pa
ap2p1árn
ap1pá
ap1ph
app1ing
ap1pi
ap1p1la
ap1p1ri
ap1p1ró
ap1p2s
ap1py
ap2res
ap1re
ap2réd
ap1ré
a1p2rém
ap2ré2s.
a1p2rím
ap1rí
2ap1ro
ap2roc
a1p2rod
1apród
ap1ró
1apró1zó
ap2s1i2kon
ap1si
apsi1ko
ap2síz
ap1sí
aps1ork
ap1so
apsz1ert
aps2z
ap1s1ze
ap1szf
apsz2t
aptára2d
ap1tá
aptá1ra
aptá2r1a1da
ap1t2r
apu1á2
a1pu
a2p1udv
apu1p2
apus3s2
a2p1u2tas
apu1ta
a2p1után
apu1tá
a2putc
a2p1új
a1pú
a2p1üd
a1pü
a2p1üg
a2p1ü2l
a2p1ün
a2p1üt
a2p1üv
a2p1üz
ap1űr
a1pű
ap1wh
ara2b1ár
a1ra
ara1bá
ara2b1í2
a2r1ab1la
2arad
ar1a2da1ta
ara1da
ar1a2da1to
ar1a2datr
2arag
ara2g1e
2araj
a2r1ajká1ró
araj1ká
2arak
a2r1a2kol
ara1ko
ara2kóh
ara1kó
ara2kó1i
ara2kós
ara2kót
a2r1ak2t.
a2r1alk
a2r1alm
a2r1a1na
1a2rann
arany1a2gá
ara2nyag
aran2y
ara1nya
ara2nyal
1a2ranyb
1a2ranyh
ar2a2nyí
1a2ranyk
1a2ranyn
1a2ranyr
1a2rany1s
a2ra2p2a.
ara1pa
ar1arc
1aras2z.
aras2z
arasz2t1e
ar1a1s1zú
ara2tal
ara1ta
ara2tel
ara1te
ar2a2tin
ara1ti
ara2t1ő2
ara1tű2
ara2tűr
ar1aul
ara1u
aravas2z1
ara1va
1a2raw
ara2zon
ara1zo
2arác
a1rá
a2r1á2c.
a2r1á2c1si
arác2s
a2rácsom
ará1c1so
a2r1á2g.
a2r1á2g2a.
ará1ga
a2r1á2ga1i
a2r1á2gak
a2rága2n
a2r1á2gat
ará2gáb
ará1gá
ará2gáh
ará2gán
ará2gár
ará2gát
ará2gáv
a2r1ágb
a2r1á1ge
a2r1á2gé
a2r1ágf
a2r1ágg
a2r1ágh
a2r1á2gi
a2r1ágk
a2r1ágl
ará2gon
ará1go
a2r1ágr
a2r1ágs
a2r1ágt
a2r1á2guk
ará1gu
a2r1á2gu2n
a2r1á2gú
a2r1á2g2y
ar1álc
a2r1ál1lá
a2r1ál1lo
2arám
ará2m1e2
ará2nye
arán2y
a2r1á2rak
ará1ra
a2r1á2rá
a2r1árk
a2r1árr
a2r1á2ru
a2r1á1rú
ar1árv
ará2s1ze
arás2z
a2r1á2szo1ki2
ará1s1zo
ará2tal
ará1ta
ará2t1ö
aráz4s
ará2zsál
ará1z4sá
ará1z1si2
arázs3z
ar2c1a2d
ar1ca
arc1ag2y
arc1a1la
arca2n
arc1an2y
ar2car
ar2cat
ar2ceg
ar1ce
ar2c1es
ar2cev
ar2c1é2h
ar1cé
arc1é2l.
arc1élb
arc1éll
arc1élt2
ar2c1é2n
ar2cés
1ar1c1hí
arc2h
arc1ing
ar1ci
ar2c1int
ar2ciz
arcolás1s
ar1co
arco1lá
ar2có
ar2cő
arcs1a1la
arc2s
ar1c1sa
ar2csál
ar1c1sá
arc3se1re
ar1c1se
ar2csip
ar1c1si
ar2c3sor
ar1c1so
ar2cü
ar2cű
ard1a1cé
ar1da
ar2d1alj
ar2d1áll
ar1dá
arde2l
ar1de
ard1e1le
ard1e1lő
ard1elt
ar2d1e2m
ar2d1é2l
ar1dé
ar2d1i1na
ar1di
ar2d1ing
ar2dor
ar1do
ar2dö
ar2d1ur
ar1du
ar2dü
a2r1e2d2z
a1re
ar1egés
are1gé
a2r1e2g2y
are2i
areil2
a2r1e2le
ar1elh
ar1elm
ar1eln
ar1elr
ar1elt
ar1e1lü
a2r1e1lű
ar1elv
a2r1emb
ar1e2mel
are1me
ar1e2més
are1mé
a2re1mu
a2r1e2r
ar1e2se
ar1e2ső
a2r1eszek
ares2z
are1s1ze
a2r1e1s1zé
a2r1e1s1zü
ar1e1tű
ar1e2ve
ar1e2vé
a2rew
aré1é2
a1ré
ar1é2g.
ar1é2ge
aré2k1a2l
aré1ka
aré2kek
aré1ke
ar1é2l.
ar1élt
2arém
aré2nek
aré1ne
aré1p
a2r1é2pü
a2r1é2ri
a2r1é1rő
ar1észj
arés2z
aré1sz2tá
aré1t1ra
ar1f2r
ar1g1ha
2ar2i.
a1ri
2ari1a
ar1i2de
a2ri1dé
a2r1i2ga
a2r1i2mád
ari1má
ar1i2mi
a2r1i2n2a.
ari1na
a2r1i2na1ké
a2r1i2nas
a2r1i2nat
a2r1i2náb
ari1ná
a2r1i2náh
a2r1i2nán
a2r1i2nár
a2r1i2nát
a2r1i2náv
a2r1in1gé
ari2nit
ari1ni
arink2
a2r1in1té
ari2nü
a2r1i2o1no
ari1o
a2r1i2ón
ari1ó
2arip
a2r1isp
a2r1is1te
ari1szf
aris2z
2ariz
ari2zom
ari1zo
ar1í2té
a1rí
ar2j1áz
ar1já
ar2j1er
ar1je
arkas3s
ar1ka
arká2p
ar1ká
arká2s
ar2k1eg
ar1ke
ark1e1lá
ar2kéj
ar1ké
ar2ké1pü
ar2k1érd
ar2k1i2n.
ar1ki
ar2k1i2ont
arki1o
ar2kiz
ar2k1orm
ar1ko
ar2k1o1vi
ar2kud
ar1ku
ar2k1u2s
ar2les
ar1le
ar2m1a2g2y
ar1ma
arma2te
arm1áll
ar1má
ar2m2e.
ar1me
ar2me1o
arme2s
arm1ing
ar1mi
ar2m1is
ar2m1os2z
ar1mo
ar2m1ö
ar2mü
arnis3s
ar1ni
aro1ka2
a1ro
aro2kan
aro2k1á2
aro2k1e2
aro2kin
aro1ki
a2r1ol1da
1a2romát
aro1má
aro2mis
aro1mi
a2r1opt
ar1org
a1r1o2ro
ar1ors
a2r1o1vi
aró1p
a1ró
a2r1ó2rak
aró1ra
a2r1ó2ráj
aró1rá
a2r1ó2rám
aró2s3zár
ar2ó1s1zá
arós2z
aró2vár
aró1vá
ar1ózd
a2r1ö2b
a1rö
a2r1ök
a2r1ö2l
ar1öng
a2r1ör
a2r1ös
a2r1ö2z
ar1őr
a1rő
ar1ős
ar1pl
ar1pr
ar1ry
ar1s1ka
ar1s1rá
ar1s1ta
ar1s1to
ar1st2r
2ar2t.
art1abl
ar1ta
ar2t1a2lap
arta1la
arta2n1á2s
arta1ná
art1aszt
artas2z
ar2t1a2u
ar2t1ál1la
ar1tá
ar2t1e2g
ar1te
art1e2lő
art1emb
art1e2re1i
arte1re
ar2tég
ar1té
ar2t1é2l
ar2t1érp
ar2t1i2n2a.
ar1ti
arti1na
ar2t1i2nát
arti1ná
ar2t1i2náv
art1orz
ar1to
ar2t1ö2v
ar1tö
ar2t1ut
ar1tu
artvis1s
art1vi
ar1t2y2
a2r1uml
a1ru
a2ru1ni
aru2tas
aru1ta
a2r1új
a1rú
a2r1ús2z
ar1útj
ar1útr
a2r1üt
a1rü
ar1üz
ar1ű2z
a1rű
ar2va1la
ar1va
arvas1s
arv1ág2y
ar1vá
ar2v1árh
ar2v1á2ri
2asabl
a1sa
a2s1abr
a2sadag
asa1da
asag2
a2s1a1ga
a2s1agg
as1a2g2y
2asaj
a2s1a2kar
asa1ka
2asal
a2s1alab
asa1la
a2s1a2lap
a2s1alf
a2s1alján
asal1já
a2s1al1ji
a2s1alk
a2s1alm
a2s1a1nó
a2s1ant
a2s1a2n2y
2asap
as1apr
2asar
as1aran
asa1ra
a2s1a2rá
asa2t1ó2
a2s1a2t2y
2asav
asa2vo
a2s1á2c
a1sá
asá2g1ikr
asá1gi
as1áll
2asám
a2s1árnak
asár1na
a2s1á2ro
a2s1árr
a2s1árt2
a2s1á2ru
asás1s
as1ás1vá
a2s1áth
2asá1to
2asáv
as1bl
as1d2r
a2s1e2d
a1se
as1e1ge
a2s1e2g2y
ase2k
as1e1ke
as1e1ké
as1ell
a2s1emb
a2s1e2n
a2serd
as1e2ré
a2s1e2rő
a2s1es
ase2t
as1e1ti
a2s1ez
a2s1é1he
a1sé
a2s1é1ke
a2s1é2l
2asém
a2s1ép
2asér
a2s1é2r.
a2s1érb
a2s1érc
a2s1érd
asé1s2
a2s1étv
as1é2ve
as1fr
as1gl
as1gr
as1i1de
a1si
as1i1do
a2s1i2ga
a2s1i2gá
a2s1inj
a2s1i2o
2asiv
a2s1i1zé
as1i2zo
a2s1íj
a1sí
2asík
2asín
2asír
asír1ta2
asír2tal
a2s1í2v
a2s1í1zü
2as1ká
as1kl
asko2s1a2rá
as1ko
asko1sa
asko2s1á
as2koz
as1k2r
as2luk
as1lu
as2már
as1má
2asodr
a1so
a2s1of
a2s1ok1ke
a2s1okl
a2s1o2kos
aso1ko
2asoks
a2s1ok1ta
asom1or
aso1mo
aso2né
2asor
a2s1ord
a2s1orm
a2s1os2z
a2s1ox
asó1p2
a1só
as1ó2rá
a2s1ö2k
a1sö
a2s1ö2l
a2s1örd
a2s1örv
a2s1ös
a2s1ö2ve
as1őr
a1ső
as1p2l
as2pot
as1po
asp2r
a1s2pu
as1s2p
as3sza1bá
as2s2z
as1s1za
as3szin
as1s1zi
asszí2ve
as1s1zí
assz1ív2e.
assz1ívek
assz1ív1ne
1asszoc
as1s1zo
1asszon2y
as3s1zü
as3s1zű
2as1ta
a1s2tand
2as1tá
a1s2tád
2as1ti
astil2
as2tim
2as1tí
2as1to
as2top
2astr
as1trag
ast1ra
as1trav
a1st2ru
2as1tú
a2s1ud
a1su
2asug
a2s1uj
2asul2y
asu2r
a2s1u1ra
a2s1urn
a2s1u2s2z
a2s1u2tak
asu1ta
a2s1u2tas
a2s1u2tá
a2s1u2tu
a2s1ú2s
a1sú
a2s1ú2t
asú1t1a2
a2s1üd
a1sü
a2s1ü2lő
a2s1üst
a2s1üz
as1űz
a1sű
2aszab
as2z
a1s1za
a2sz1a2d
2aszak
a2s2z1akc
asza2k1e
a2sz1akt
asza2k1ü2
a2sz1alk
1asza1ló
a2sz1asp
asz1ass
asza2t1a2
asza2t1e
a2szath
a2sza1ti
2aszav
a2sz1á2g.
a1s1zá
asz1á2ga
asz1á2gá
asz1ágb
asz1ágg
asz1á2gi
asz1ágk
asz1á2go
asz1ágr
asz1ágt
a2szálc
a2szálm
2aszám
aszá2ra1da
aszá1ra
a2száram
asz1ár1nyé
as2zárn2y
a2szárp
a2szá1ta
a2s2záth
a2sz1átl
a2sz1á2to
a2s2z1áts
2aszed
a1s1ze
2aszeg
as2ze2gé2s2z1
asze1gé
a2sz1eh
2aszek
2aszel
asz1e2lem
asze1le
asz1elj
2aszemc
2asze1me
2asze1mé
2a3szem1p2
a2sze1mu
2as2ze1mü
2asze1mű
2aszen
a2sz1eng
a2sz1e2pi
2asze2r.
2asze1re
a2sz1e2rej
2asze1ré
2aszer1k2
2aszern
a2sz1e1ro
a2s2ze2rő
2aszerr
2a3szer1s
2aszert
2a3szerv
2a3szerz
asze2s
a2sz1e1se
asz1est
2aszez
2aszék
a1s1zé
aszé2k1el
aszé1ke
asz1é2let
aszé1le
asz1élés
as2zé1lé
aszé2n1ég
aszé1né
aszén1n
a2sz1é2re
a2sz1é1ré
2aszét
asz2fér
asz1fé
a2sz1ill
a1s1zi
2aszin
a2sz1inf
a2sz1ing
2aszir
a2sz1ism
asz1ist2
2asziv
a2sz1iz
2a1s1zí
a2sz1ír
a2sz1í2vi
asz2karc
asz1ka
asz2k1áp
asz1ká
asz2kell
asz1ke
asz2kes
2aszob
a1s1zo
2aszoc
2a3szof
aszon1n
aszo2n1o
a2sz1orr
a2sz1ors
a2s2z1os2z
1aszó1a
a1s1zó
asz1ó2dá
2aszót
2aszök
a1s1zö
asz1ölt
a2sz1ön
2aször
asz1öss
a2szöt
2aszöv
2a1s1ző
aszőlő1é2
a3sző1lő
as1z3su
asz2s
asz2tab
asz1ta
asz2t1a1po
asz2tác
asz1tá
asz2táll
asz2t1emb
asz1te
asz2té1ne
asz1té
asz2t1és
asz2t1ing
asz1ti
asz2t1olt
asz1to
asz2t1o1ro
asz2t1orr
asz2tors2
as2z2t1os2z
asz2töv
asz1tö
asz2tős
asz1tő
asz2t1ül
asz1tü
asztvíz1
aszt1ví
2aszur
a1s1zu
1a2szús
a1s1zú
a2sz1útr
2a1s1zü
aszü2g
a2sz1ügg
a2sz1ü2z
2a1s1zű
asz2vit
asz1vi
asz1z
2atab
a1ta
at1ab1la
a2t1a2cé
ata2dat
ata1da
a2t1a2dó
2atag
a2t1a1gya
atag2y
a2taján
ata1já
2atak
a2t1aka1ra
ata1ka
ata2ká2r
ata1ká
ata2ke2l
ata1ke
ata2k1é2pes
at2aké1pe
ata1ké
ata2k1ö2v
ata1kö
a2t1ak1tu
a2t1a2la2g1
ata1la
ata2la2p.
ata2lapb
ata2lapj
ata2la1po
ata2lap1p
ata2lapr
ata2la1pú
ata2lat
a2t1aleg
ata1le
at2a2lik
ata1li
a2t1al1le
a2t1almás
atal1má
2atan
a2t1a2nal
ata1na
ata2nó
a2t1a2nyag
atan2y
ata1nya
at1a2nyás
ata1nyá
2atap
a2t1app
ata2puk
ata1pu
a2ta2pun
2atar
a2t1a2rab
ata1ra
ata2ran
a2t1a2rén
ata1ré
ata1st2
atau2r
ata1u
a2t1a2ut
a2t1a1zo
2atáb
a1tá
a2t1ábr
2atág
a2t1á2g.
a2t1á2ga
a2t1ágr
2atáj
2atál
a2t1ál1lá
a2t1ál1ló
a2t1álm
2atám
atá2nal
atá1na
a2t1á2pol
atá1po
2atár
atá2ra1da
atá1ra
atá2ra1do
atá2ramh
a2táramk
atá2ramn
atá2r1az
atá2rét
atá1ré
atá2ris
atá1ri
a2t1árkár
atár1k2
atár1ká
atársá2g
atár1sá
atár2s1á1gá
a2t1ártás
atár1tá
2atás
atá2s1á2g
atá1sá
atá2s2z
atá2tal
atá1ta
a1tá1tá2
atá2tár
a2t1átb
a2t1átf
a2t1áth
a2t1á1ti
a2t1átj
a2t1átk
a2t1átl
a2t1átr
a2t1áts
a2t1átt
a2t1á1tu
a2t1átv
2atáv
at1bl
at1br
at1cl
at1dr
at1e2gé
a1te
ate2jel
ate1je
ateké2r
ate1ké
ate2ké1re
ate2kó
a2t1e2l1a
a2t1elb
at1elc
a2t1eld
at1e2led
ate1le
at1eleg
at1e2lem
at1e2l1en
a2t1elf
a2t1elh
at1el1já
at1el1kö
at1el1kü
a2t1elm
at1eln
a2t1e2lo
at1e2lőn
ate1lő
ate2lőt
a2t1elr
a2t1els
at1el1ta
at1el1tá
at1el1té
at1el1tü
a2t1e1lu
a2t1e1lű
a2t1elv
a2t1elz
a2t1emb
at1e2mel
ate1me
a2t1e1mé
a2t1eml
a2t1e1mó
a2t1enc
a2t1e1ne
at1e1pi
at1e1po
a2t1er1d2
at1e2rec
ate1re
ate2r1ék
ate1ré
a2t1e2rő
a2t1e2se
a2t1e1sé
a2tesk
ates2t1á
ates2tő2
at1eszm
ates2z
at1e1s1zű
ate2tol
ate1to
a2t1e2z
2atég
a1té
a2té2get
até1ge
a2t1é1he
até2k1a2l
até1ka
até2ke2
a2t1ék2e.
aték1el
2atél
a2t1é2le
a2t1é2lé
a2t1élm
a2t1élv
2atém
até2ne
a2t1éps
2atér
até2rá
a2t1érdek
atér1de
a2t1érin
até1ri
a2t1érl
a2t1érm
a2t1értel
atér1te
a2t1érvek
atér1ve
a2t1érz
at1é1s1za
atés2z
at1észl
2atét
até2tét
até1té
a2t1étv
at1fj
at1fl
at1fr
at1gl
at1gr
a1t2hus
at1hu
2at2i.
a1ti
a2t1iat
ati1a
a2t1i2de
a2t1i1do
ati2g
a2t1i1ge
a2t1i1gé
a2t1ig2y
a2till
at1il1le
at1i2má
at2i3má1dá
at1i2mád
at1i2mi
a2t1imp
2atin
a2t1in2g.
a2t1in1ga
a2t1ingb
a2t1in1ge
ati1nó2
ati2n1ór
at1in1té
at1in1to
2atip
a2t1i2pa
2atir
a2t1i1rá
a2t1i1ro
2atis
a2t1isk
a2t1ism
ati2s2z
a2t1i1s1zo
2atit
a2t1i2ta
a2t1i1zé
a2t1izg
a2tiz1mo
a2t1i2zo
a2t1íg
a1tí
a2t1íj
2atíp
2atír
a2t1í2t
at1í2vek
atí1ve
atí2v1e2l
at1í2vet
atí2vét
atí1vé
a2t1íz
2at1ki
2at1kl
2at1ko
2at1kö
2at1ku
at1kv
at1ojt
a1to
ato1ka2
a2t1o2k1al
ato2koss
ato1ko
a2t1o2koz
a2t1ok1ta
a2t1o2ku
at1ol1dá
a2t1ol1dó
ato2m1á
1a2tom1be
atom1b
ato2mer
ato1me
1a2tomj
a2toml
1a2tomok
ato1mo
1a2tomos
1a2tom1s
1a2to1mú
a2t1opc
a2t1o2pe
a2t1op1t2
2ator
ato2rál
ato1rá
a2t1ord2
a2t1or1g2
ato2rú
a2t1orv
a2t1or1zá
atos3s
ato1sz2f
atos2z
a2t1oszl
ató1p2
a1tó
a2t1ó2rák
ató1rá
ató2rán
ató2ri1á
ató1ri
at2ó1s1tá
ató1s2z
ató2s3zár
at2ó1s1zá
ató2s3ze1né
at2ószen
ató1s1ze
ató1tr
a2t1ö2ko
a1tö
atö2l
a2t1ö1le
a2t1ö1lé
a2t1ö1lő
at1ö2ná
atön2k1a
at1ö2röm
atö1rö
a2t1ös
a2t1öt
atö2v2i.
atö1vi
a2t1ö2z
atő2ra
a1tő
a2t1őrl
a2t1ő2s
atpen1
at1pe
at1pl
at1pr
at1ps
atrac1c
at1ra
a1t2rad
2atraj
2atrak
at2ram1b
a1trap
a1tra1u
a1t2rav
2at1ré
a1t2réf
at2rén
atré2szel
atrés2z
atré1s1ze
a1t2ril
at1ri
at2roj
at1ro
a1t2róg
at1ró
2atrón
a1t2rü
at2sán
at1sá
at1sh
at1sk
at1sl
at1sp
at1st
at1s2v
at3szá2m1é
ats2z
at1s1zá
atszáraz1
atszá1ra
at2t1a1dó
attad2
at1ta
1attakok
atta1ko
1atta1sé
at2t1e2g
at1te
at2tez
att1ing
at1ti
at1tó2
at2t1ór
at1t2re
2atud
a1tu
a2t1udv
a2t1ug
2atul
a2t1und
a2tu1ni
2atur
a2t1u1tá
at1u1tó
a2t1új
a1tú
2atúl
at1ú2s2z
a2t1üg
a1tü
2atük
at1ü2lé
at1ült
2atün
a2t1üst
a2t1ü2v
2atüz
at1üzem
atü1ze
at1űr2i.
a1tű
atű1ri
at1űrl
2atűz
a1u
au2b1in
au1bi
au2bor
au1bo
a2u1dá
au2de
au2ga
a2ug2h
au2go
1a2ukc
aul2l
aul2t1a
aul2ti
a2uma1e
au1ma
a2umaf
a2umak
a2umam
a2umar
a2umav
a2umáb
au1má
a2umád
a2umá1é
a2umáh
a2umá1i
a2umám
a2umán
a2umár
a2umáv
au1ph
au2rad
au1ra
au2r1ikr
au1ri
au2rö
au2s1z1e
aus2z
a2u2t.
au2tad
au1ta
au2tal
au2tam
au2tas
au2tat
2au1tá
au2tál
a2u1ti
1a2uton
au1to
a2u1tó
1autób
1autó1é
1autóh
1autó1i
1autóm
1autón
1autór
1autós
1autót
1autóv
a2utr
a2utt
au2tu
au2zí
au2z2s
a2u1zú
au2z1ü
a1ú
aú2jí
aú2jo
aú2ré
aú2r1i
aú2s2z
aú2ti
aú2to
a1ü
aü2dí
aü2dü
aü2ge
aü2g2y
aü2le
aü2lé
aü2li
aü2lö
aü2lő
aü2lü
aü2nő
aü2re
aü2rí
aü2rö
aü2rü
aü2s2z
aü2te
aü2té
aü2ti
aü2ve
aü2vö
aü2ze2m.
aü1ze
aü2zemb
aü2zemen
aüze1me
aü2zemet
aü2ze1mé
aü2zemh
aü2zemm
aü2zemn
aü2zemr
aü2zen
aü2zé
a1ű
aű2ri
aű2rö
aű2ze
aű2zé
aű2zi
aű2zö
aű2ző
2avad
a1va
2avak
a2v1ak1ti
avak1t2
a2v1anh
1a2vant
a2v1a2nya
avan2y
a2vanz
ava2ra2c
ava1ra
av2a2r1ag
ava2r1e2
ava2rék
ava1ré
ava2s1ze
avas2z
av2a2s1zü
1a2va1tá
1a2va1tó
2avád
a1vá
avá2na2n
avá1na
2avár
avá2r1a2l
avá1ra
avá2ri2a.
avá1ri
avári1a
avá2ria1i
a2v1á2ri1á
a2v1á1ta2
a2v1átt
avá2zal
avá1za
av1bl
av1e2le
a1ve
av1elv
2aves
av1est2
2avet
2avez
avi2c1se
a1vi
avic2s
av1ing
av1kr
a2v1old
a1vo
av1ol1tó
avo1s
a2v1ox
a2v1öm
a1vö
a2v1ös
av1ős
a1vő
av1őz
av1pr
av1sp
av1st
a2v1ut
a1vu
av1ü2l
a1vü
av1ür
av1ü2z
a2wag
a1wa
aw2hi
awhis1ky2
awhiskyk2
a2x1ab
a1xa
a2x1ad
a2x1ak
a2x1al
a2x1an
a2x1av
ax1bl
ax1eg
a1xe
ax1el
ax1inf
a1xi
ax1ing
ax1int
axió2r
axi1ó
axi2se2
ax1ír
a1xí
ax1ös
a1xö
ax1öz
ax1pr
a2x1ut
a1xu
ax1új
a1xú
ax1üz
a1xü
ax1űr
a1xű
a1y2e.
a1yed
a1ye1i
a1yek
ay1e2l
a1yen
a1yes
ayet2
ay1fl
a1y2i.
ay1il
ay1ing
a1yit
ay1ma2
ay1s2t
aza2c2h
a1za
aza2cik
aza1ci
azai2ko
aza1i
azal2t1a
aza1p2
aza1s2
az1áll
a1zá
az1ált
azá2nö
azá2r1ó2ra
azá1ró
azá2s1e
azási2k
azá1si
azá2si1ko
azás3s
az2du
a2z1e2g
a1ze
az1e2le
az1elj
az1elm
az1e1lő
a2z1em
a2z1ex
a2z1ég
a1zé
azé2k1e2
azé2kol
azé1ko
a2z1ér1té
a2z1ing
a1zi
a2z1i2o
a2z1i1rá
a2z1irt
azma1g
az1ma
a2z1ob
a1zo
2azol
azo2nal
azo1na
azo2n1á
azont2
a2z1op
a2z1or
a2z1os2z
azót2
a1zó
azó1tr
a1z2rí
a1z4sé
az2s
a2z3si1ke
a1z1si
a2z3sor
a1z1so
az3sp
a2z3sü
az3s2z
az1ut
a1zu
a2z1új
a1zú
azú2r1é
az1üz
a1zü
2á.
á1a
áa2da
áa2dá
áadás1s
áa2do
áa2dó
áa2du
áa2já
áa2ka
áa2la
áa2lu
áa2ra
áa2s2z
áa2ut
áa1u
áa2va
á1á
áá2c2s
áá2ga
áá2g2y
áá2ju
áá2mu
áá2ra
áá2ru
áá2sá
áá2sí
áá2so
áá2só
áá2su
áá2zo
áá2zu
á2b1a1du
á1ba
á2b1akc
á2b1a2la
á2b1alk
á2b1am1bu
á2b1a2n2y
ába1p
áb1art
ába3se
á2b1á2g
á1bá
áb1áll
á2b1álm
á2b1áp
á2b1á1rá
á2b1árn
á2b1á2ru
á2b1átj
á2b1átl
á2b1átm
á2b1átv
á2b1áz
áb2b1a2d
áb1ba
ábba2l
áb2b1a1la
áb2b1and
áb2b1ár
áb1bá
áb2ben
áb1be
áb2b1e2r
áb2b1é2l
áb1bé
áb2bid
áb1bi
áb2bim
áb2b1i2s
áb2b1i1ta
ább1o2so
áb1bo
ább1oss
áb2bö2r
áb1bö
áb2b1ül
áb1bü
áb2bű
áb1dr
á2b1ed
á1be
á2b1e2g
ábe2l1a
ábe2l1á
ábe2l1eg
ábe1le
ábe2l1el
ábe2l1e2r
ábe2lég
ábe1lé
ábe2l1in
ábe1li
á2bel1nö
á2b1e2m
á2b1e2n
áb1e2ro
áb1e2rő
1á2béc
á1bé
á2b1é2g
áb1é2ke
á2b1éks
á2b1é2les
ábé1le
á2b1élt2
á2b1ép
á2b1é2r.
á2b1é2ri
á2b1ér1s
á2b1ér1te
ábért2
á2b1ér1té
áb1fr
á2b1i2d
á1bi
ábi2g
áb1i1ga
áb1i1gé
á2b1ikr
áb1il1la
á2b1im
ábi2na1i
ábi1na
á2binas
á2b1ind
á2b1ing
á2b1int
á2b1is
áb1izm
áb1izz
á2b1ív
á1bí
áb1kl
áb1kr
ábla1kr
áb1la
ábla1p
á2b1ol1da
á1bo
á2b1op
ábo2raj
ábo1ra
ábo2ra2n
ábo2rál
ábo1rá
ábo2ris
ábo1ri
á2b1os2z
á2b1o2v
áb1öb
á1bö
áb1ö2d
áb1öl
áb1ön
áb1ö2r
áb1ös
áb1öv
áb1őr
á1bő
áb1pr
1ábrá1i
áb1rá
áb2rek
áb1re
áb1sp
áb1st
áb1s2z2
áb1tr
á2b1ujjal
á1bu
ábuj1ja
ábu1s2z2
á2b1ú2r.
á1bú
áb1üg
á1bü
áb1ü2l
áb1üs
áb1üt
áb1üv
á2c1a2g
á1ca
ác1ajt
áca1k2l
ác1akn
á2c1a2la
ác1alm
á2c1a2n2y
á2c1ág
á1cá
á2c1ál
ác3c1se
ác2c2s
ác3c1so
ác1e2l1
á1ce
áce1le2
ác1en
ác1er
á2c1ép
á1cé
á2c1é1re
á2c3ha
ác2h
á2c3há
á1c3he
á2c3hé
ác3hon
á1c1ho
á2c3hu
áci2as
á1ci
áci1a
á2c1il
ác1inc
ác1ing
ácin2til
ácin1ti
áci2ó1sű
áci1ó
á2c1i2s
ác1ív
á1cí
á2c1or
á1co
á2c1os2z
ác1ór
á1có
ác1ö2l
á1cö
á2cs1abl
ác2s
á1c1sa
á2cs1a2g
á2cs1ajt
á2csa1ka
á2cs1akn
ács1alap
ácsa1la
ács1alj
ács1alom
ácsa1lo
ác2s1app
ács1atk
á2csatom
ácsa1to
á2cs1á2c
á1c1sá
á2cs1ál
ác3sárg
á2cs1ár1k2
á2cs1árn
á2csá1ru
á2c2s1á2ta
ácsboz1
ács1bo
á2csef
á1c1se
á2cs1e2g
á2cs1e2l
á2cse1ne
á2csent
á2cser
ácse2t
á2cs1ev
á2cs1é2g
á1c1sé
ácsé2k
ács1é1ki
ács1ékk
ács1ékn
á2csél
á2csép
ács1é2te
ácsi2g
á1c1si
á2c2s1i1ga
á2cs1il1le
á2csi1mi
ács1int
ácsi2p
á2cs1i1pa
ács1isk
á1c1sí2
ács1ír
á2c2sok1ta
á1c1so
1á2csolat
ácso1la
1á2csomk
ács1orr
á2c2sos2z
á2cs1ov
ác3s2ó.
á1c1só
ács1ó2r
ác3sót
ác3sóv
ács1ö2k
á1c1sö
á2cs1ö2l
á2cs1ös
á2csöt
ács1ő2s
á1c1ső
ács1s
á2csuj
á1c1su
á2csut
á2cs1úr
á1c1sú
ács1ü2l
á1c1sü
á2cs1ü2t
á1csy
ács3zen
ács2z
ác1s1ze
á2c1ut
á1cu
ác1úr
á1cú
ác1ül
á1cü
ác1ür
ád1abl
á1da
á2d1a2do
á2d1akc
ád1a2lap
áda1la
á2d1alb
á2d1alj
á2d1alk
ád1a2nya
ádan2y
ád1a2nyá
áda1p
á2d1a2pá
ád1arc
á2d1at2y
á2d1ág
á1dá
á2d1ál
ád1á2s2z
ád1átl
ád1dr
ád3d2z
ád1e2c
á1de
á2d1e2g
á2d1ej
á2d1e2l
áde1le2
ádel1ej
ádel1e1me
á2d1e2m
á2d1ep
áde2r1á
ád1e2rő
ád1e2ti
ádéd2
á1dé
ádé1dr
ádé1g2
á2d1é2ge
ádé1k1ré
ád1é1ne
á2d1ér1te
á2d1érz
ádé1st
ádi2c1se
á1di
ádic2s
á2d1i2d
á2d1i2ga
ád1i2ko
ád1ill
ád1i1mi
á2d1ind
á2d1int
ádi2ódar
ádi1ó
ádió1da
ádióé2r
ádió1é
ádi2óé1ra
ádi2ói2v
ádió1i
ádi2ó1ko
ádi2ó1k2ra
á2d1i2p
ád1ist
ád1ív
á1dí
ád1kl
ád1kr
ádo2ga
á1do
ádo2gá
ádo2ge
ádo2rak
ádo1ra
á2d1os2z
ádós2
á1dó
ádö2b
á1dö
á2d1ös
ád1öv
ád1ö2z
ád1ő2r
á1dő
ád1pr
ád1ps
á1d2rót
ád1ró
ád1st
ád1tr
ád1udv
á1du
á2d1ú2s
á1dú
á2d1üg
á1dü
ád1ü2lé
á2d1üz
á1d3za
ád2z
á2d3zá
á2d3ze
á1d3zú
á1e
áe2c2s
áe2d2z
áe2ge
áe2gé
áe2g2y
áe2le
áe2lő
áe2me
áe2re
áe2rő
áe2se
áe2sé
áe2si
áe2ső
áe2sü
áe2te
áe2ve
áe2vé
áe2vő
á1é
áé2de
áé2ge
áé2gé
áé2gő
áé2he
áé2ke
áé2le
áé2ne
áé2pí
áé2pü
áé2re
áé2ré
áé2ri
áé2rő
áé2rü
áé2te
á2f1a2gya
á1fa
áfag2y
á2f1alg
á2fáb
á1fá
á2f1ág
1á2fák
á2f1áp
1á2fás
á2fát
áf1dr
áf1elm
á1fe
á2f1e2m
á2f1e2t
áf1é2ne
á1fé
áfi2ad
á1fi
áfi1a
áfi2am
á2f1i2d
á2f1im
á2f1in2g.
á2f1or1dá
á1fo
á2f1os2z
áf2rec
áf1re
á1f2rö
áfus3s
á1fu
á2f1ün
á1fü
á2g1abl
á1ga
á2g1abr
ága2c
ág1a1cé
1ágacs1ka
ágac2s
á2gad
ág1a2dá
1ágadd
ág1a2dó
á2g1a2j
á2ga1ka
ág1a2kar
ág1a2kas
1á2gakb
1á2gakh
1á2ga1ki
1á2gakk
1á2gakn
1á2gakr
á2gakt
á2g1a2la
á2g1alj
á2g1all
ág1alt
1á2gam
ága2n
ág1a1na
á2g1ang
1á2gank
ág1ant
á2g1an2y
á2g1a2p
á2g1a2r
ág1asp
á2g1as2z
ága2tol
ága1to
á2g1a1u
1á2ga1za
á2ga2zon
ága1zo
á2g1á2g
á1gá
á2g1áld
ág1álm
ág1á2mu
ágá2nyal
ágán2y
ágá1nya
á2g1áp
á2g1á2rad
ágá1ra
á2g1árb
á2g1á2ré
á2g1árh
á2g1á2ri1a
ágá1ri
ágá2rokb
ágá1ro
á2g1árv
á2g1á2só
ágás3s
ágá2s2z
á2g1á1t1a2
á2g1á1tá2
á2g1á1té
á2g1átf
á2g1áth
á2g1á1ti
á2g1átl
á2g1átm
á2g1átn
ágá2tokk
ágá1to
á2g1átr
á2g1áts
á2g1átt
á2g1á2tü2
á2g1átv
ág1bl
ág1br
ág1d2r
á2g1e2c
á1ge
á2g1e2d
ág1ef
á2g1e2g
á2g1e2l
á2g1e2m
á2ge2n
ág1enc
ág1e1ne
á2g1e2p
áge2ra
áge2rá
áge2r1el
áge1re
á2g1e2s
á2g1e2t
á2g1e2v
á2g1ex
á2g1é2g
á1gé
á2g1é2he
á2g1éj
á2g1é2k
á2g1é2l
á2g1é2ne
á2g1é2pí
ág1é2pü
á2g1é2r.
á2g1é2rá
á2g1é2ré
á2g1é1ri
á2g1érm
á2g1é2rő
á2g1ér1te
á2g1ér1té
á2g1érv
á2g1és
á2g1é1te
á2g1é1ve
á2g1é1vé
ág1fl
ág1fr
ág1g2l
ág1gr
ág3gyar
ág2g2y
ág1gya
ág3gye
ág3gyú
ághá2nyi
ág1há
ághán2y
ági2al
á1gi
ági1a
ági2a1s2z2
ági2d
á2g1i1de
á2g1if
ági2g
á2g1i1ga
á2g1igén
ági2gé
á2g1ill
ág1i1ma
á2g1i2mi
á2g1i2n2a.
ági1na
á2g1inf
á2g1ing
á2g1ins
á2g1int
á2g1i1ri
á2g1i1ro
á2g1ist
á2g1is2z
á2g1i2ta
á2g1iz
á2g1íg
á1gí
á2g1ín
á2g1ír
á2gí1té
á2g1íz
ágká2rok
ág1ká
ágká1ro
ág1kl
ág1kr
ág1kv
á2g1o1á
á1go
á2g1okl
á2g1o2li
ág1ol1tó
á2g1op
á2g1or
á2g1os1ko
ágos3s
á2g1oszl
ágos2z
ágó1dr
á1gó
á2g1ó2rá
á2g1ö2k
á1gö
á2g1ö2l
á2g1önt
ágö2r
ág1ö1rö
á2g1örv
á2g1ös
á2g1öt
á2g1öv
á2g1ö2z
ág1ő2r
á1gő
ág1ő2s
ág1pl
ágport2
ág1po
ágpor1tr
ág1pr
ág1ps
ág1sh
ág1sk
ág1sl
ág1sp
ág1st
ágs2z2
ág1tr
á2g1ud
á1gu
á2g1uj
águ2n
á2g1u1na
á2g1und
á2g1u2ra
á2g1u2rá
á2g1u2t
á2g1új
á1gú
á2g1ús
á2g1útt
ág1üd
á1gü
ág1ü2g
ág1ü2l
ág1ün
ág1üv
ág1üz
ág1ű2r
á1gű
ág1űz
ágya1g
ág2y
á1gya
ágy1alj
ágy1alk
ágy1alm
1á2gyasn
á2gyas1sa
á2gy1á2l
á1gyá
á2gy1á2ram
ágyá1ra
ágyás1s
á2gy1e2
á2gyél
á1gyé
á2gy1é2r.
1ágy1gyű
ágyg2y
á2gyid
á1gyi
á2gyil
á2gyivad
ágyi1va
á2gyob
á1gyo
á2gyos2z
ágy1otth
á2gy1ó2s
á1gyó
ágy1ö2l
á1gyö
ágy1ös
á2gyur
á1gyu
á2gyúh
á1gyú
á2gyút
á2gy1ü2
áh1aj1k2
á1ha
áh1ass
1á2hít
á1hí
á2h1ors
á1ho
á1i
ái2dom
ái1do
ái2dő
ái2ga
ái2gé
ái2g2y
ái2ha
ái2je
ái2má
ái2ram
ái1ra
ái2rá
ái2s2z
ái2ta
ái2vá
ái2vo
ái2zé
á1í
áí2gé
áí2rá
áí2ve
áí2vo
á2j1a1dó
á1ja
á2j1akc
á2j1akv
á2j1a2la
á2j1am1bu
á2j1a1na
áj1ant
á2j1an2y
á2j1ar
á2j1atl
á2j1ax
áj1a1zo
á2j1ág
á1já
á2j1ál
á2j1áp
ájás3s
á2j1á1t1a
á2j1á2t1á2
á2j1átt
áj1bl
áj1br
áj2c2h
á2j1e2c
á1je
áj1e1gé
áj1elm
áj1eln
áj1e1lő
áj1elv
á2j1em
á2j1e2n
á2j1es
áj1e2t
á2j1e2v
áj1ex
á2j1é2g
á1jé
á2j1él
á2j1ép
ájé2r
á2j1é1re
á2j1é1te
áj1fl
áj1fr
á2j1i2d
á1ji
á2j1il
á2j1im
á2j1iz
áj1íj
á1jí
áj1ír
áj1ív
áj1íz
ájk1ell
áj1ke
áj2k1ő2
áj2kü
áj2lad
áj1la
ájl1akt2
áj2l1an
áj2l1as
áj2l1at
áj2li2k
áj1li
áj2lob
áj1lo
áj2nár
áj1ná
áj2nin
áj1ni
áj2nü
á2j1ob
á1jo
á2j1op
á2j1or
á2j1öb
á1jö
áj1ök
áj1öl
á2j1ör
áj1őr
á1jő
áj1ős
áj1pl
áj1pr
áj1sn
áj1sp
áj1st2
áj1t2r
á2j1ud
á1ju
áj1ús2z
á1jú
áj1üg
á1jü
áj1ül
áj1ür
áj1ü2t
áj1üv
áj1üz
áj1űr
á1jű
áj2zab
áj1za
áj2za2j
áj2z3sa
ájz2s
ák1abr
á1ka
á2k1a2d
á2k1aj
á1k1a1ka
á2k1a2la1pí
áka1la
ák1a1le
ák1alj
ák1ant
ák1a1ra
á2k1a2rá
ák1arm
ák1arz
á2k1ass
á2k1atl
á2k1a2u
á2k1á2c
á1ká
á2k1á2l
ák1á1ta
ák1átk
ák1bl
ák1e1bé
á1ke
á2k1e2g
ák1e2le
á2k1elk
á2kelle1ne
ákel1le
á2k1elm
á2k1e2lő
ák1emb
ák1e2rő
á2k1e2vé
á2k1e2vo
á2k1é2l
á1ké
á2k1é2ne
á2k1érm
á2k1ér1te
á2k1ér1tő
á2k1érv
á2k1érz
ák1éss
á2k1é2te
á2k1étk
á2k1étt
á2k1é2ve
ák1fl
ák1fr
á2k1i2d
á1ki
á2k1if
áki2g
á2k1i1ga
á2k1i1gé
ák1ill
á2k1i2m
ák1ing
á2k1int
ák1i1ro
á2k1i2s
áki2t
ák1i1ta
á2k1i2v
ák1ír
á1kí
ák1k2l
ák1k2r
ák2lar
ák1la
á2k1oks
á1ko
á2k1o2la
á2k1old
á2k1o2li
á2k1oll
á2k1o2pe
á2k1orv
ákos3s
á2k1ott
ák1ó1ni
á1kó
ákö2l
á1kö
á2k1ö1lő
ák1ö2r
ák1ő2r
á1kő
ák1pr
ák2rák
ák1rá
á1k2re1á
ák1re
á1k2ris
ák1ri
ák1sp
ák1sr
ák1st
ák1s2z2
ák1t2r
á2k1uj
á1ku
á2ku1ni
áku2r
ák1u1ra
ák1u1tó
ák1új
á1kú
ákú2t
á2k1ú1to
ák1üd
á1kü
á2k1üg
ákü2l
ák1ün
ák1ür
ák1ü2t
ák1űr
á1kű
á1k2vat
ák1va
2ál2a.
á1la
ál1abl
ál1a1cé
á2l1adag
ála1da
á2l1a2dó
ál1a2já
ál1ajt
ála2kar
ála1ka
á2l1akn2a.
álak1na
2álal
ál1alak
ála1la
á2l1alát
ála1lá
ál1alg
á2l1alk
á2l1alm
á2l1a1ne
á2l1ang
á2l1ant
á2l1a2nya
álan2y
á2l1a2nyá1i
ála1nyá
á2l1a2nyán
á2l1a2nyát
á2l1a2nyáv
á2l1a2pos
ála1po
ál1a1pó
ál1a2ra
ál2a2szek
álas2z
ála1s1ze
ál2a2szel
ál2a2sz1ék
ála1s1zé
ála2s1zö
ál2a2s1zű
ála2tat
ála1ta
ála2tet
ála1te
ála2t1é2r.
ál2atér
ála1té
ála2tikr
ála1ti
ála2tint
ál2atin
ál1at1lé
ála2told
ála1to
ála2t1ó2
á2l1at1ti
á2l1a2t2y
ál1aut
ála1u
á2l1ábr
á1lá
ál1á2g.
ál1á2gi
á2l1á2gú
ál1áll
á2l1á2rak
álá1ra
ál1árf
ál1árk
á2l1árn
á2l1á2ro
álá2s3z
á2l1átc
á2l1átk
á2l1átm
á2l1á1tú
á2l1át1vá
ál1bl
ál1br
álca1i2
ál1ca
1áldás
ál1dá
1áldoz
ál1do
ál1d2r
ál1dy
á2l1e2c
á1le
á2l1e2d
á2l1ef
ál1elk
ál1elm
ál1e1lo
ál1e2lő
ál1elr
ál1e1mu
á2l1e2r
ál1esem
ále1se
á2l1e2ső
á2l1es2z
á2l1e2t
ál1ez
á2l1ég
á1lé
á2l1é2he
álé2kal
álé1ka
álé2k1an
álé2k1e2l
álé1ke
álé2kü
ál1é2le
ál1é2lő
á2l1é2ne
á2l1é2r.
á2l1érb
á2l1érd
á2l1érf
á2l1érg
á2l1érh
á2l1é2ri
á2l1érm
á2l1érr
á2l1érs
á2l1ér1tá
á2l1ér1te
á2l1ér1té
á2l1érz
á2l1é2v.
á2l1é2vé
ál1fl
ál1fr
ál1gr
1álha1ja
ál1ha
áli2as
á1li
áli1a
ál1i2bo
áli2d
ál1i1de
ál1i1dé
áli2g
á2l1i1ga
á2l1i1ge
á2l1i1gé
á2l1ill
ál1im1p2
á2l1ind
ál1inf
á2l1i2onb
áli1o
á2l1i2ont
á2l1i2p
ál1i1rá
á2l1i1ro
áli2s1e
áli2s1ék
áli1sé
áli2sis
áli1si
ális3s
ál1is1te
á2l1iz
ál1ín
á1lí
ál1í2r
ál1ít
ál1í2v
álká2rok
ál1ká
álká1ro
ál1k2l
ál1k2r
ál2l1a2dó
ál1la
1állag
ál2laj
ál2l1a2lak
álla1la
1álla1má
álla2m1e
1áll2amot
álla1mo
1állam1ti
ál2l1a2pá
ál2l1a2r
1álla1ta
álla2tas
álla2t1e2
áll2a2t1or
álla1to
álla2t1ö2
1álla1tu
ál2l1á2g
ál1lá
ál2l1á2l
ál2l1árr
állás1s
ál2l1áth
ál2l1átm
ál2led
ál1le
ál2l1e2h
ál2l1ej
áll1é1ké
ál1lé
ál2l1iz
ál1li
1állo1má
ál1lo
ál2lü
1álmaim
ál1ma
álma1i
1álmo2k.
ál1mo
1álmom
1álmo2t.
1álmuk
ál1mu
1álmunkb
ál1obj
á1lo
á2l1o2k2a.
álo1ka
á2l1o2ka1i
1álokaih
1álokain
1álokair
á2l1o2ká
1álokán
1álokát
1álo2ká1u
álo2kok
álo1ko
1álo2konk
álo2kos
á2l1ok1ta
1á2l1o2ku
á2l1ol
álo1ma2
álo2mad
álo2m1al
álo2m1an
álo2mar
álo2mas
álo2m1á
álo2m1e
álo2m1it
álo1mi
álo2mot
álo1mo
á2loms
álo2m1ú
á2l1ont
ál1opc
á2l1o2pe
á2l1or
á2l1os2z
á2l1ox
áló1a2
á1ló
áló1á2
áló1ó2
á2ló2ráj
áló1rá
áló2s1ű2
álót2
ál1öb
á1lö
á2l1öd
á2l1ö2l
á2l1ös
ál1öz
á2l1őr
á1lő
ál1p2l
ál1p2r
ál1p2s
ál1sk
ál1sl
ál1st
ál1trad
ált1ra
ál1t2rak
ál1t2ran
ál1t2re
ál1t1ré
á2l1ug
á1lu
álu2n
á2l1u1na
á2l1u2r
á2l1u2t
á2l1uz
á2l1új
á1lú
á2l1úr
álú2t
ál1útj
ál1útk
ál1útn
á2l1ú1to
á2l1útr
á2l1útt
ál1üg
á1lü
ál1ün
ál1ür
ál1üt
ál1üv
ál1üz
ál1űr
á1lű
ál1űz
ály1a1da
ál2y
á1lya
álya1g2
ály1ant
ály1a1nya
ályan2y
álya1p
ály1ass
á2lyál
á1lyá
á2ly1e2
á2lyéj
á1lyé
á2lyé2l
á2ly1é2ne
á2lyé1re
á2lyé1ve
á2lyi1de
á1lyi
á2lyí
ály1k2
ály1odv
á1lyo
á2lyo2l
á2ly1ó2
á2ly1ö
á2lyő
ály1s
á2lyug
á1lyu
á2ly1ü2
á2ly1ű2
á2m1abl
á1ma
á2m1abr
ám1a2cé
ám1adm
ám1agg
á2m1ajt
á2m1akt
ám1a2lap
áma1la
ám1all
ám1alt
áma1ó2
á2m1app
á2m1arc
áma2sz1ál
ámas2z
áma1s1zá
ám2a2szel
áma1s1ze
áma2szí2v
ám2a1s1zí
áma2sz1odv
áma1s1zo
ám2a2s1z1ü2
ám1atl
á2m1att
á2m1at2y
ám1aud
áma1u
ám1a2zo
ámá2c
á1má
ám1áf
á2m1ág
ám1ál1la
ám1ál1lo
ámán1n
ámán1tr
á2m1á2rak
ámá1ra
á2m1á2ram
á2m1á2ras
á2m1á2rá
á2m1árb
á2m1á2ri
á2m1árn
á2m1á2ro
á2m1árr
á2m1á2ru
ám1ásv
á2m1átb
ám1á2t1e2
á2m1á1ti
á2m1átm
ám1á2zó
ám1bl
ám1dr
á2m1e2b
á1me
á2m1ej
á2m1e1la
ám1e1lá
ám1e2lem
áme1le
ám1e1lé
á2m1elh
á2m1e2l1í2
á2m1elj
á2m1elk
á2m1elm
ám1e1lo
á2m1e2lő
ám1els
ám1elt
ám1e1lü
ám1elv
á2m1e2m
á2m1erd
ám1e2rek
áme1re
á2m1erk
á2m1e2ro
áme2rők
áme1rő
áme2rőt
áme2rőv
ám1e2se
ám1ess
ám1es2z
áme2t
á2m1e1ti
á2m1e1tű
á2m1ev
á2m1ex
á2m1ez
ám1é1ke
á1mé
á2m1é2le
á2m1ép
á2m1érc
á2m1érd
á2m1értel
ámér1te
á2m1étk
ám1fr
ám1gr
ámi2ab
á1mi
ámi1a
ámi2al
ámi2am
á2mi1de
á2m1i1dé
á2m1i2dő
á2m1i1ga
á2m1i1gá
á2m1i1gé
á2m1ill
á2mi1má
á2mimm
á2m1imp
á2m1ind
á2m1inf
á2m1ing
á2m1in1te
á2m1in1té
á2m1inv
á2m1i2pa
á2m1i1rá
á2m1i1ro
á2m1irt
á2m1isk
á2m1ism
ám1i2s1zá
ámis2z
á2m1i2z
ám1íg
á1mí
á2m1íj
á2m1ír
á2m1í2v
ám1íz
ám1kl
ám1kr
ám1kv
ámla3t2
ám1la
á2m1ob
á1mo
á2m1of
á2m1o1ká
á2m1okl
á2m1okm
á2m1ok1ta
ámok1t
ámo2lyas
ámol2y
ámo1lya
á2m1op
ámo2r1á2l
ámo1rá
ámo2ri2s
ámo1ri
ámo2r1odv
ámo1ro
ámo2sas
ámo1sa
ámos3s
á2m1os1to
á2m1os2z
á2m1ov
á2m1ox
ám1ó2r
á1mó
ám1ö2k
á1mö
ám1öl
ám1ön
ám1ör
ám1ös
ám1öt
ám1öv
ám1ö2z
ám1ő2r
á1mő
ám1ő2s
ám1őz
ámpa1p2
ám1pa
ám1p2l
ám1p2r
ám1p2s
ám1sk
ám1sm
ám1sn
ám1sp
ám1st
ám1s2z2
ám1t2r
á2m1ud
á1mu
á2m1ug
á2m1uj
á2m1und
á2mu1ni
á2m1u2r
á2m1u1tá
á2m1új
á1mú
ám1üd
á1mü
ám1üg
ám1ü2l
ám1ür
ám1üt
ám1üv
ám1üz
á3műt
á1mű
ám1ű2z
ámva2s1u2
ám1va
á2n1abl
á1na
á2n1a2cé
án1ac2h
án1a1da
ána1e2
án1afr
án1a1gya
ánag2y
án1ajt
á2n1a1ká
á2n1akc
á2n1akr
á2n1a2la
án1alk
á2n1all
á2n1a1nó
án1a1nya
ánan2y
á2n1a2o
ána2p
án1a1pa
ána1p1i
án1a1ra
á2n1arc
án1as2s2z
ána2t1é2r.
án2atér
ána1té
ána2tol
ána1to
án2a2tor
ána2t1ű
á2n1a2u
án1a1va
á2n1ábr
á1ná
á2n1ág
á2n1áll
án1á2r.
án1á2rad
áná1ra
án1á2ri
án1árm
án1árn
án1á1ro
án1á1ru
án1ásv
áná2t1a
áná2t1á
á2n1á2z
án1bl
án1br
án2cac
án1ca
án2c1ad
án2caj
án2cal
án2c1a2n
án2car
án2c1as
án2cat
án2c1a2u
án2c1ál
án1cá
án2c1á2ro
án2cás
án2c1ed
án1ce
án2c1e2g
ánce2l
ánc1e1le
án2c1elt
án2c1er
án2c1e2s
án2c1et
án2cez
ánc1é1he
án1cé
ánc1é2ne
án2c1é2r.
án2c3h
án2c1ill
án1ci
án2cim
ánci2p
ánc1i1pa
án2c1ir
án2c1i2s
án2c1ó2
án2c1ö2
án2cő
án2cs1an
ánc2s
án1c1sa
ánc3sás
án1c1sá
án2c3seb
án1c1se
áncs1es
ánc3s1po
ánc3s1za
áncs2z
án2cü
án2c2z
áne2d
á1ne
án1e1dé
á2n1ef
á2n1e2g
á2n1e2l
án1emb
án1e2mi
án1eml
án1e2mu
á2n1en
á2n1e2p
án1es
á2n1e2t
án1e2u
á2n1ex
án1ez
á2n1é2d
á1né
á2n1é2g
á2n1é2k.
á2n1ékn
án1éks
á2n1é2l
á2n1é2ne
á2n1é2pí
án1é1pü
á2n1é2r.
á2n1érc
á2n1érd
á2n1é1ré
á2n1érl
án1é1te
á2né2vad
áné1va2
á2n1é1ve
á2n1é2vé
án1f2r
án2gab
án1ga
án2g1a2r
án2g1á1ra
án1gá
ángás1s
án2g1át
án2ged
án1ge
án2g1el
ánge2s
án2g1é2r.
án1gé
án2g1é2s
áng3g
án2g1it
án1gi
án2g1iv
án2gí
án2g1os2z
án1go
án2gőr
án1gő
án2g1us
án1gu
án2gü
1áng2y.
áng2y
án2gyas
án1gya
á2n1i2d
á1ni
á2n1if
á2n1i2ga
án1i1gé
áni2k1a
áni2k1á
áni2ke
án1ill
á2n1im
á2n1ind
án1inn
á2n1i2p
á2nirr
án1irt
án1isk
án1ism
á2n1i2s2z
áni2tá
áni2t1e2
áni2t1í
áni2tol
áni1to
áni2t1or
án1i1zo
án1íg
á1ní
án1ív
án1íz
án2kaj
án1ka
ánk1a1ro
án2kern
án1ke
án1k2li
án1k2lo
án1k1lu
án1k2rá
án3nye
án2n2y
án3nyí
án3nyo
án3nyu
á2n1o2b
á1no
á2n1okir
áno1ki
á2n1ok1ta
á2n1o2ku
án1old
án1o1li
á3nom
áno2n1i2m
áno1ni
á2n1o2r
á2n1oszl
ános2z
án1ott
á2n1ox
án1ó1ri
á1nó
án1ök
á1nö
á2n1öl
án1öm
á2n1ön
á2n1ör
á2n1ös
á2n1öt
án1ö1vö
á2n1őr
á1nő
án1ő2s
á2n1ő2z
án1pl
án1pr
án2ses
án1se
án2s1ér
án1sé
án2sis
án1si
án2si2z
án1s2pe
án1s2pi
ánst2
án1str
áns1üld
án1sü
án1szl
áns2z
ánt1a1cé
án1ta
ánt1a2n2y
án2taz
án2t1á2g
án1tá
ántá2p
án2t1árb
án2t1á1ri
án2t1ed
án1te
ánt1e1ké
ánt1elh
án2tez
ánt1é1ke
án1té
án2tér1te
án2tid
án1ti
án2t1i1pa
ánt1ist
án2t1iz
án2t1ív
án1tí
án2tök
án1tö
án2t1ös
án1t2rak
ánt1ra
án1tran
án2t1ü2l
án1tü
á2n1ud
á1nu
á2n1ug
á2n1uj
á2n1u2r
á2n1u2t
án1úr
á1nú
ánú2t
á2n1útj
á2n1ú1to
á2n1útt
án1ü2g
á1nü
án1ü2l
án1ü2t
án1ü2v
án1ü2z
án1űz
á1nű
á2ny1adás
án2y
á1nya
ánya1dá
á2ny1a2dó
ány1agg
á2ny1akc
ány1alap
ánya1la
ány1alk
ány1all
á1ny1a1nya
án2yan2y
ány1a1nyá
ány1a1pá
ány1a1ra
ány1a1rá
ány1art
á2ny1as2s2z
á2ny1aszt
ányas2z
á2ny1á2l
á1nyá
á2ny1á2ras
ányá1ra
á2ny1á2rá
á2ny1árb
á2ny1árc
á2ny1árf
á2ny1árh
á2ny1árk
á2ny1árn
á2ny1á2ro
á2ny1árr
á2ny1árs
á2ny1árt
á2ny1á2ru
á2ny1á2rú2
á2ny1átl
á2ny1á2z
ány1ed
á1nye
á2ny1e2g
ánye2gyez1
ányeg2y
ánye1gye
ány1el
ánye2le
á2ny1e2m
ány1en
á2ny1e1p
ány1e1ső
ány1et
ány1e2v
á2ny1éd
á1nyé
á2ny1ég
á2nyé1he
á2ny1é2j
á2ny1é2k
á2ny1él
á2ny1é2ne
á2ny1ér2c.
ány1ér1re
á2ny1ér1te
á2ny1ér1té
ányé2r1ü2
ány1ér1vé
á2ny1és
á2nyé1te
á2nyétk
á2ny1étt
á2nyé1ve
ányfé2l1é2v
ány1fé
ányfé1lé
á2ny1id
á1nyi
á2nyi1gé
á2nyi1ke
á2ny1ikr
á2nyirat
ányi1ra
á2nyi1ro
á2nyisk
á2ny1is2z
á2nyi1ta
ány1í2ró
á1nyí
á2ny1oml
á1nyo
á2ny1ont2
á2ny1o2r
á2nyos2z
ány1ök
á1nyö
ány1ö2r
á2ny1ös
ány1öz
á2ny1ő2
ány1tr
á2nyug
á1nyu
á2ny1ur
á2ny1ut
á2nyú1to
á1nyú
á2ny1ü2
á2ny1ű2
án2zál
án1zá
á1o
áo2c2s
áo2ká
áo2ko
áo2mo
áo2pe
áo2so
áo2sza2n
áos2z
áo1s1za
áo2s1z1e
áo2szis
áo1s1zi
áo2sziv
áo2s1zú
á1ó
áó2ha
áó2va
á1ö
áö2le
áö2mö
áö2re
áö2rö
á1ő
áő2s2z
á2p1a2dot
á1pa
ápa1do
ápa1tr
ápa3u2
á2p1ág
á1pá
áp1áll
áp1á2t1a2
áp1dr
áp1eg
á1pe
áp1e2l
áp1e2m
ápe2n
á2p1e2s
áp1e2t
á2p1ég
á1pé
áp1é2te
ápi2ac
á1pi
ápi1a
ápi2av
á2p1im
á2p1inj
áp1int
ápi2t
á2p1i1ta
á2p1ín
á1pí
á2poll
á1po
ápo2r1e2
áp1őr
á1pő
áp2ro
áp1t2r
á2p1ug
á1pu
á2p1u2t
á2p1úr
á1pú
áp1üg
á1pü
áp1üz
á2r1abl
á1ra
á2r1abr
á2r1abs
ár1a2dag
ára1da
á2rada1ta
á2rada1tá
á2radatb
1á2rada1té
á2radatn
á2rada1to
1á2radatr
á2radat1t2
ár1a2gá
á2r1ajk
á2r1a2kad
ára1ka
á2r1a2kas
á2r1akc
á2r1akn2a.
árak1na
á2raknát
árak1ná
á2r1ak2t.
ár1ak1ti
ár1ak1tí
ár1a1lá
á2r1al2j.
á2r1alj2a.
áral1ja
á2raljak
á2r1al1já
á2r1alk
ár1all
á2r1alm
ár1alt
á2r1alv
1á2ram2a.
ára1ma
1á2rama1i
1á2ra1má
ára2mál
ára2m1el
ára1me
ára2m1ér1té
ára1mé
1á2ra1mi
ára2m1in
1á2ram1kö
1á2ramok
ára1mo
1á2ramol
1á2r2amot
1á2rams
1á2ra1mu
1á2ra1mú
ár1a1na
ár1ang
ár1a2no
á2r1ant
ár1a2pá
ár1a2pó
ár1aps
á2r1a2rá
á2r1arc
ár1a2ri
á2r1asp
ára2taj
ára1ta
ára2tal
ára2tav
ár2a2tál
ára1tá
ára2t1inf
ár2atin
ára1ti
á2r1at1lé
ára2t1ü2
á2r1at2y
á2r1ábr
á1rá
árá2c
árá2g
ár1á1ga
ár1ágr
ár1ág2y
ár1á2lo
á2r1á2p
ár1á2r.
á2r1á2rak
árá1ra
á2r1á2rá
á2r1árb
á2r1árf
á2r1á2ri
á2r1árk
á2r1á2ro
á2r1árr
á2r1árt
á2r1á2ru
á2r1á1rú
ár1árv
á2r1á2sás
árá1sá
árá2s1zó
árás2z
á2r1á2ta
árá2t1a2d
á2r1á1tá
ár1átb
á2r1átc
á2r1átd
ár1á2t1e2
á2r1á1té
á2r1átf
á2r1áth
á2r1á2ti
á2r1átj
á2r1átk
á2r1átm
á2r1á2tö
á2r1átr
á2r1áts
á2r1átt
á2r1á1tú2
á2r1átv
ár1bl
1árboc
ár1bo
ár1br
ár2d1ál
ár1dá
árd1ell
ár1de
árd1e1me
ár2d1é2n
ár1dé
ár2d1őr
ár1dő
ár1d1rá
ár2dud
ár1du
áre2á
á1re
ár1e2d
á2r1e2g
ár1ej
á2r1e2l
árelői3r
áre1lő
árelő1i
áre2ma
áre2mél
áre1mé
áre2n
ár1e1ne
ár1eng
á2r1e2r
ár1e2sé
ár1e2ső
ár1e1vé
á2r1ex
ár1ébr
á1ré
ár1é1de
á2r1é2g
ár1é2j.
ár1é2je
ár1éjs
á2r1é2ke
á2r1é2ké
á2r1éks
á2r1é2l
á2r1é2ne
á2r1ép
á2r1é1ré
á2r1é2ri
ár1éss
ár1és2z
á2rétk
á2r1étr
á2r1étt
á2r1étv
á2r1é2v.
áré2vek
áré1ve
á2r1évk
á2r1évr
ár1fl
ár1f2r
árgás1s
ár1gá
ár1gl
ár1g2r
ár2gyárv
árg2y
ár1gyá
ár2gyir
ár1gyi
ár2gyol
ár1gyo
ár2gyó
á2r1i2de
á1ri
á2r1i2dé
á3r2i3dőtl
á2r1i2dő
ár1i2dőt
ár1ifj
ári2g
á2r1i1ga
á2r1i1gá
á2r1i1ge
á2r1ill
á2r1i2má
ár1imb
á2r1i2mi
á2rinas
ári1na
á2r1inc
á2r1ind
á2r1inf
á2r1ing
ár1in3n
á2r1int
á2r1inv
á2ri1o
á2r1i2pa1ro
ári1pa
ári2s1e
ár1is1ko
ár1is1te
ár1i2s1za
áris2z
á2r1i2ta
ár1i2zo
á2r1ír
á1rí
ár1ív
á2r1í2z
árka1k2
ár1ka
1árká1do
ár1ká
ár1k2l
1árkok
ár1ko
ár1k1ré
ár1k2v
árnás3s
ár1ná
árnya2n
árn2y
ár1nya
ár2n2y1an2y
árnye2l
ár1nye
ár2ny1e1le
ár2nye1lő
ár2nyem
ár2nyes
ár2nyok
ár1nyo
ár2ny1ol
ár2nyos
ár2nyö
ár2nyü
ár1odv
á1ro
á2ro2ká
áro2k1e2
á2rok1ha
á2rokm
áro2kol
áro1ko
á2r1o2koz
á2rok1re
1á2roks
á2rok1ta
árok1t2
á2r1ol1da
áro1ma2
áro2maj
áro2mak
áro2m1al
áro2m1as
áro2már
áro1má
áro2m1ok
áro1mo
áro2m1os
ár1opt
á2r1o2r
áro2sas
áro1sa
áro2sál
áro1sá
áros3s
á2r1ostr
á2r1ost2y
á2r1otth
á2r1o2v
áróé2r
á1ró
áró1é
á2r1ó2nét
áró1né
á2r1ó2név
áró1p2
á2r1ó2rak
áró1ra
áró2rák
áró1rá
á2r1ó1ri
áró1s1ká
áró1s2p
ár1ö2b
á1rö
ár1öc
ár1ök
á2r1ö2l
á2r1ön
ár1ör
á2r1ös
ár1öv
á2r1ö2z
ár1ő2r
á1rő
ár1ő2s
1árp2a.
ár1pa
ár1pl
ár1p2r
ár1p2s
ár2s1a1la
ár1sa
árs1as2z
ár2s1ál
ár1sá
ár2sed
ár1se
ár2s1e2l
ár2sem
ár2s1en
ár2ses
ár2s1é2g
ár1sé
ár2sip
ár1si
ár2si1rá
árs1okt
ár1so
ár2s1ol
ár2sóv
ár1só
ár2s1ön
ár1sö
árs3s
ár1s2tab
árs1ta
ár2su2t
ár1su
ársza2ké
árs2z
ár1s1za
ár2s3ze1ne
ár1s1ze
ár2ta1do
ár1ta
ár2t1aj
1árta1lo
árta2n1á2s
árta1ná
árt1a1rá
árt1a2ris
árta1ri
árta3u2
árt1áll
ár1tá
ár2t1árn
ár2t1á2ru
ár2t1e2g
ár1te
árt1elh
árt1e1li
árt1ell
árt1eln
ár2t1e1lő
árt1emb
ár2t1er1k2
árte2s
árt1e1se
árt1esth
árt1e1ti
árt1é1le
ár1té
ár2t1érd
ár2t1ér1te
ár2tid
ár1ti
ár2tif
ár2t1ins
ár2t1int
árt1izg
ár2tít
ár1tí
ár2t1ok1ta
ár1to
ár2top
árt1otth
ár2t1ön
ár1tö
ár2t1ös
ár2t1u1ra
ár1tu
árt1u2s2z
ár2t1ut
1á2r2u.
á1ru
1á2ru1a
áru1á2
1á2ru1b2
1á2ruc
á2rud
1áru1da
1áru1dá
1á2ru1e
1á2ru1é
1á2ruf
1árug2y
1á2ru1i
á2ruj
1áru1já
1á2rum
1á2ru2n.
1á2ru1na
1á2ru1ná
á2r1und
á2ru1ni
1á2ru1o
1á2ru1p2
á2rur
1árur2a.
áru1ra
ár1u1rá
1á2rus
árus3s2
á2rut
1áru2t.
áru2tal
áru1ta
áru2tas
1áru1te
áru1tr
áru2tun
áru1tu
1á2ru1ü
1á2ruv
ár1u2z
1á2rú1é
á1rú
1á2rúk
ár1ú1ré
ár1úrf
ár1ús2z
á2r1ú1ta
á2r1útb
á2r1ú1té
á2r1úth
á2r1ú2ti
á2r1útj
ár1útl
á2r1útn
á2r1ú2to2
á2r1útr
á2r1úts
á2r1útt
ár1ü2g
á1rü
ár1ü2l
ár1ün
ár1ür
ár1ü2s
ár1üt
ár1ü2v
ár1üz
ár1űr
á1rű
ár1ű2z
1árvác
ár1vá
ása2b
á1sa
á2s1abl
á2s1a1bo
ás1a2dat
ása1da
á2s1a2dá
ás1a2do
á2s1a2dó
á2s1a2já
ás1aj1tó
á2s1a2kar
ása1ka
á2s1akc
á2s1akv
ás1a2la
ás1alg
ás1a2li
ás1alj
ás1alk
ás1all
á2s1alm
á2s1alt
á2s1amb
ása2n
á2s1a1na
á2s1a1nó
á2s1ant
á2s1an2y
ás1a2pá
á2s1app
á2s1a1pu
ás1a2ra
ás1a2rán
ása1rá
á2s1arc
á2s1a1ré
á2s1a2ri
ás1art
á2s1arz
ás1asp
á2sass
1á2sa1tá
á2s1atl
á2sa1to
á2s1at2y
á2s1a2u
á2s1a1zo
á2sá1bé
á1sá
á2s1ábr
ásá2ga1i
ásá1ga
á2s1ágb
á2s1á2gi2g
ásá1gi
á2s1ágk
ás1ágn
á2s1á2gú
á2s1ál
á2s1á2p
á2s1á2ras
ásá1ra
á2sá2rét
ásá1ré
ásá1ró2
ásá2rón
1á2sásb
1á2sá1si
1ásásk
ás1ásv
á2s1á2ta
á2s1á1tá
á2s1á1té
á2s1áth
á2s1á1ti
á2s1átj
á2s1átk
á2s1átl
á2s1átr
á2s1áts
á2s1átt
á2s1átv
á2s1á2z
ás1bl
ás1br
áscsa2p1á2
ásc2s
ás1c1sa
ás1d2r
ás1e2b
á1se
á2s1e2d
á2s1ef
ás1e2g2y
á2s1e2l
á2s1e2m
á2s1e2n
ás1e1pi
ás1er1k
á2s1e2rő
á2s1e2s
á2s1e2t
ás1e2v
ás1ex
ás1ez
á2s1é2g
á1sé
á2s1é1he
á2s1éj
á2s1é2k
á2s1é2l
á2s1é2ne
á2s1ép
á2s1é2r.
á2s1érd
á2s1é2re
á2s1é2ré
á2s1é2ri
á2s1érl
á2s1érs
á2s1ér1te
á2s1ér1té
á2s1ér1tő
á2s1érv
á2s1é2s
á2s1é2te
á2s1étk
á2s1étt
á2s1é2ve
ás1fl
ás1fr
ás1gl
ás1gr
á2s1i2d
á1si
á2s1if
ási2g
á2s1i1ga
á2s1i1ge
ási2k1e
á2s1ill
á2s1i1má
á2s1imp
á2s1ind
á2s1inf
á2s1ing
á2s1i1ni
á2s1int
á2s1inv
á2s1i2p
á2s1i2rat
ási1ra
á2s1i2rá
á2s1i1ro
á2s1irt
á2s1isk
á2s1ism
ás1ist2
ás1i2s2z
ás1i1ta
á2s1iz
ás1íg
á1sí
á2s1íj
á2s1íns
ásí2r
ás1í1rá
ás1í1ró
á2s1í2v
á2s1í2z
ás1kl
ás1k2r
ás1kv
1ás1nu
á2s1ob
á1so
áso1da2
áso2d1al
áso2d1an
áso2d1as2
áso2da1u
áso2d1ál
áso1dá
á2s1okl
á2s1okm
á2s1ok1ta
á2s1ol1dó
ás1o1li
á2s1ont
á2s1op
ás1o2rá
á2s1org
á2so1ri
á2s1ork
ás1orr
á2s1ors
á2s1orv
á2s1os2z
á2s1o2v
1á2sób
á1só
1á2só1é
1á2sóg
1á2só1i
1á2sój
1á2sók
1á2són
ásó1p2
á2sór
ás1ó2rá
ás1ó1ri
1ásó1ró
á2sós
ás1ó2sá
1á2sót
á2sóv
á2s1ö2k
á1sö
ás1ö2l
ás1ön
á2s1ö2r
á2s1ös
ás1ö2v
á2s1ö2z
ás1ő2r
á1ső
ás1ő2s
á1spic
ás1pi
ás1p2l
á1s2pór
ás1pó
ásp2r
ás1ps
ás1s2k
ás1s2p
ás1sr
ás1s2t
ás2s2z2
ás3szab
ás1s1za
ás3szag
ás3sza1ka
ás3sza1ké
ás3száj
ás1s1zá
ás3szám
ás3száz
ás3s1ze
ás3szél
ás1s1zé
ás3szf
ás3s1zi
ás3s1zí
ás3sz1k2
ás3szoc
ás1s1zo
ás3szok
ás3szol
ás3szor
ás3s1zó
ás3s1zö
ás3szt2
ás3s1zú
ás3s1zű
ás2teg
ás1te
ást1elem
áste1le
ás2tir
ás1ti
ás2t1ös
ás1tö
ás1t2re
ás1t1ri
ás1t2róf
ást1ró
á2s1ud
á1su
á2s1uj
á2s1u2r
á2s1u2s
ásu2t
ás1u1ta
á2s1u1tá
á2s1u1tó
á2s1u2z
á2s1ú2r.
á1sú
á2s1úrn
á2s1ú2s2z
á2s1ú1ti
á2s1ú1to
ás1üd
á1sü
á2s1üg
ás1üld
ás1ü1le
á2s1ün
á2s1ür
ás1ü2ve
á2s1üz
ás1űr
á1sű
ás1ű2z
á2s3zac
ás2z
á1s1za
á2sz1a2d
ász1a1ga
ász1a1gá
á2sz1ag2y
á2szaj
á2s2z1akc
ász1a2kol
ásza1ko
á2sz1akt
ász1a1le
á2sz1alk
ásza2n
á2sza1na
á2sz1ant
á2sz1an2y
á2sz1ap
ásza2s
ás2z1as2z
ásza2t1e
á2sz1a1u
ás3zavar
ásza1va
ász1ágg
á1s1zá
á2sz1ág2y
ász1á2lo
á2s3záp
ászá2r1as
ászá1ra
ász1á2rih
ászá1ri
á2sz1á2rú
ászás1s
á2szá1ta
á2sz1áz
ász1e2b
á1s1ze
á2sze1bé2
ászeb1é1de
ász1e2béd
ász1e2gé
ász1e2g2y
ász1eln
ász1elv
ász1emb
ás3ze1ne
ás3ze1né
ász1eng
ász1e2p
á2sz1er1d2
á2sz1e2ré
á2sz1e2s
ász1e2t
ász1e2v
ász1ex
á2sz1éj
á1s1zé
á2sz1ékb
á2sz1é2l
á2sz1é2ne
á2sz1ép
á2sz1é1ré
á2sz1é1te
á2sz1étt
á2sz1é1ve
á2szid
á1s1zi
á2szif
á2sz1ill
á2szind
á2sz1ing
ászi2p
á2sz1i1pa
á2s2zi1ro
á2sz1isk
á2sz1ism
ász1ist2
ász1i1ta
á2sziz
á2szír
á1s1zí
ász1k2
1ászká1i
ász1ká
á2szokl
á1s1zo
á2sz1okm
á2sz1ors
á2s2z1os2z
ászó1s2p
á1s1zó
á2sz1ö2b
á1s1zö
ászö2l
ász1ölt
á2sz1ö2r
á2sz1ös
ász1ö1vé
ász1őr
á1s1ző
ász1ő2s
ás1z3sa
ász2s
ás3z1su
ászt2
ász1tr
á2s2zu1ni
á1s1zu
á2szur
á2szut
á2sz1ú2s
á1s1zú
á2sz1ü2g
á1s1zü
á2sz1ün
á2sz1ü2z
ász1z
át1abr
á1ta
át1a2já
át1ajk
át1ajt
át2a1k1ré
á2t1ak2t.
á2t1ak1to
át1alh
á2t1al1ja
á2t1alm
át1als
át1a2lu
át1al1vá
á2t1a2ra
á2t1a1rá
á2t1arc
át1arz
áta2sá
át1aut
áta1u
át1a1zo
átá2c
á1tá
á2t1á2g.
át1ál
átá2p
át1á1po
á2t1á2rad
átá1ra
á2t1á2ra1i
át1áram
á2t1á2rá
á2t1árb
á2t1árn
á2t1á2ro
á2t1árt
á2t1á2ru
1á2t1á2sás
átá1sá
átá2s3z
á2t1átf
á2t1á1tu
át1átv
át1bl
át1br
1át1bu
át1dr
át1e2c
á1te
át1ej
át1ell
át1eln
át1elv
á2t1emb
át1eml
át1eng
áte2rá
áte1ri2
áte2rik
át1e2rő
át1ex
át1é2d
á1té
áté1e2
áté2g
át1é1ge
á2t1é1gé
áté2kaj
áté1ka
áté2k1a2l
áté2kas
áté2ke
áték1em
áték1es
áté2kol
áté1ko
áté2k1ü
át1é2l
áté2p
á2t1érb
á2t1é2ri
á2t1érr
á2t1érz
áté2t1á2
1átfés
át1fé
át1fr
át1gr
át2h2i.
át1hi
át2hi1a
1áthid
áti2ag
á1ti
áti1a
áti2al
áti2d
áti2g
á2t1i1gé
át1ill
á2t1ing
át1i2pa
á2t1i1rá
át1isk
át1ist
á2t1i2ta
á2t1i1zé
á2t1izm
á2t1i2zo
át1íg
á1tí
á2t1í2r
át1í2v
1átkel
át1ke
át1kl
át1k2r
átle2g1
át1le
átle1ge2
átműt2r
át1mű
át1oj
á1to
áto2kol
áto1ko
át1oko2l.
át1o2koz
át1ok1ta
2átolj
2átolt
át1oml
át1ont2
át1op
áto2ra2n
áto1ra
áto2ras
áto2rác
áto1rá
áto2rál
áto2re2
áto2ris
áto1ri
áto2r1ol
áto1ro
át1or1zá
á2t1o2x
átói2ko
á1tó
átó1i
átó1p2
át1ó1ri
át1öb
á1tö
átö2l
át1ö1lé
át1öml
át1ön
át1öt
á2t1ö2v
át1ö2z
át1ő2r
á1tő
át1pl
át1pr
át1ps
át2ril
át1ri
át1sk
át1sl
át1sm
át1sp
át1sr
át1st
1át1s1zű
áts2z
1átte1ki
át1te
át3t2é
át1t2r
á2t1udv
á1tu
á2t1ug
á2t1uh
á2t1uj
átu2min
átu1mi
átu2n
á2t1u2r
átu2s1ze
átus2z
á2t1u2t
át1új
á1tú
á2t1ú2r.
á2t1úrb
á2t1úrh
át1ú1ri
á2t1úrn
á2t1ú2ro
á2t1úrr
á2t1ú2s
á2t1üg
á1tü
át1ül
á2t1üt
á2t1ü2v
át1űr
á1tű
1átvár
át1vá
1átvev
át1ve
á1u
áu2ga
áu2go
áu2ná
áu2no
áu2nó
áu2nu
áu2s2z
áu2ta
áu2tá
á1ú
áú2s2z
á1ü
áü2ge
áü2g2y
áü2le
áü2lé
áü2lö
áü2lő
áü2lü
áü2rí
áü2té
áü2ti
áü2tö
áü2tő
áü2tü
áü2vö
á1ű
áv1adm
á1va
á2v1ajk
áv1ak1t2
áv1alk
áv1alt
áv1asp
áva1st2
áva1s2z2
áva1t2
á2v1a1u
áv1a1zo
áv1áls
á1vá
áv1á1te
áv1átf
á2v1áth
á2v1átj
á2v1átk
á2v1á1tu
áve2gé2s2z1
á1ve
áve1gé
á2v1e2l
áve1l1é
áv1é2de
á1vé
ávé1dr
á2v1é2ri
á2v1ér1te
á2v1ér1té
áv1fr
á2v1i1ga
á1vi
á2v1i1gé
á2v1ind
á2v1inf
á2v1ing
á2v1int
á2v1i2rá
á2v1i1ro
á2v1i2si
áv1isk
áv1ism
áv1izm
áv1i1zo
áv1ír
á1ví
á2v1ob
á1vo
á2v1olv
á2v1op
á2v1os2z
áv1ó1rá
á1vó
áv1ör
á1vö
áv1ös
áv1öv
áv1őr
á1vő
áv1pr
áv1sk
áv1sp
áv1st
áv1tr
á2v1ug
á1vu
á2v1ur
á2v1ú2s2z
á1vú
ávú2t
á2v1ú1ti
á2v1ú1to
áv1üg
á1vü
áv1ü2z
á2z1abl
á1za
á2z1abs
áza2dal
áza1da
áza2d1e2
áza2dott
áza1do
áza2dü
áz1ajt
á2z1akc
á2z1a2kó
á2z1ak2t.
á2z1ak1ta
á2z1ak1tá
á2z1ak1tu
áz1a2lap
áza1la
á2z1a2le
á2z1alk
áza2n
á2z1a1na
á2z1a1no
á2z1an2y
á2z1a2p
áz1a2rá
á2z1arc
á2z1arm
á2z1as2s2z
á2z1aszt
ázas2z
áza2t1a2l
áza1ta
áz2a2tan
áza1te2
áza2t1el
áza2t1é2r.
áz2atér
áza1té
áza2t1érv
áza2tés
áza2tik
áza1ti
áza2tí
á2z1at2y
á2z1a2u
á2z1á2g
á1zá
á2z1á2l
á2z1á2ra1i
ázá1ra
á2z1á2rak
á2z1á2ram
á2z1á2ras
á2z1á2rat
ázá2rár
ázá1rá
ázá2rát
á2z1árb
á2z1árc
á2z1árd
á2z1á2ré
á2z1árf
á2z1á2ri
á2z1árjáb
ázár1já
á2z1árjáv
á2z1ár1ka
ázár1k2
á2z1á2rokk
ázá1ro
á2z1á2rokr
á2z1árp
á2z1ár1rá
á2z1á2runkn
ázá1ru
á2z1á2runkr
ázá3ru2s.
áz1á2rus
ázás3s
á2z1á2ta
á2z1á2t1á
á2z1átb
á2z1á2t1e2
á2z1á1té
á2z1áth
á2z1átr
á2z1áts
á2z1á1tü
á2z1áz
áz1bl
áz1d2r
áz1ef
á1ze
áz1e2g
áz1e2m
áz1ep
áz1e2r
áz1e2s
áz1e2t
áz1ex
áz1e2z
á2z1ég
á1zé
á2z1é2l
á2z1é2p
á2z1é2r.
á2z1é1rő
á2z1ér1te
á2z1ér1té
á2z1ér1tő
á2z1érz
á2z1é2te
á2z1é1ve
á2z1é1vi
áz1fl
áz1fr
áz1g2r
á2z1i2d
á1zi
á2z1i1gé
á2z1i2kon
ázi1ko
á2z1ill
á2z1i1má
á2z1i1mi
áz1imp
á2z1inf
á2z1ing
á2z1inj
á2z1int
á2z1i2par
ázi1pa
á2z1i1rá
á2z1i1ro
ázi2s1e
ázi2sir
ázi1si
ázi2s1í2
ázis3s2
ázi2s1ü
ázi2s1zó
ázis2z
ázi2z
á2z1izm
áz1i1zo
áz1íj
á1zí
áz1í2v
áz1k2l
áz1k2r
á2z1ol1da
á1zo
á2zoltal
ázol1ta
á2z1ol1tó
á2z1oml
á2z1ont
á2z1o2r
á2z1os2z
ázói2ko
á1zó
ázó1i
á2z1ó2l.
áz1ó2rá
á2z1ó1ri
ázó1s2p
ázó1s2z
áz1öb
á1zö
áz1öd
áz1ö2l
áz1öm
áz1ön
áz1ös
áz1ö2t
áz1ö2v
áz1öz
áz1ő2r
á1ző
áz1pl
áz1p2r
á2zsab
áz2s
á1z1sa
á2zs1a2d
á2zs1a2g2
á2zs1ajt
á2zs1akn
ázs1a1la
ázs1alk
ázs1all
á2zs1a1mu
ázs1an2y
á2zsar
ázs1a1rá
á2zsat
á2zs1a1u
á2zs1áll
á1z1sá
á2z3sá2r.
á2z1se
áz3seb
ázs1e2c
ázs1ef
ázs1eg
ázs1e2l
ázs1e2m
ázs1es
á2zséj
á1z1sé
á2zs1é2k
ázs1é1ne
á2zs1é1re
á2zs1é1ri
ázsé2t
á2zs1é1te
á2zsi1a
á1z1si
á2zsi1á
á2zsi1de
ázsi2g
á2z2s1i1ga
á2z2s1i1gá
á2z2si1ge
á2zsimm
á2zs1ing
á2zs1int
á2z2sinv
á2zsi1ó
á2zsip
ázs1isk
á2zs1i1ta
á2zsiz
á2z1s1í2
á2z3sor
á1z1so
áz3sóh
á1z1só
ázs1ó2r
á2z1sö
á2z1s1ő2
ázs1s
á2zs1uj
á1z1su
á2zs1ut
á2z2sú1to
á1z1sú
á2z1sü
ázs1ü2v
á2z3sű
áz3s1zá
ázs2z
ázs3zon
áz3s1zo
áz1t2r
á2z1ud
á1zu
á2z1ug
á2z1uj
á2z1u2r
á2z1ut
á2z1új
á1zú
á2z1úr
á2z1ü2g
á1zü
ázü2l
áz1ür
áz1ü2z
ázy1i
áz3z1se
áz2z2s
2b.
1ba
baa2d
ba1a
ba2b1a2dat
1ba1ba
baba1da
ba2b1ajk
baba1k2
ba2b1a1ra
b2abar
ba2b1arc
ba2b1aszt
babas2z
ba2b1ábr
b2abáb
ba1bá
babá2c
ba2b1á1c1si
babác2s
ba2b1ág
bab1áll
ba2b1á2ro
ba2bátv
ba2b1érc
b2abér
ba1bé
babé2t
bab1é1te
ba2bév
ba2bik
ba1bi
ba2b1i2n2a.
babi1na
ba2bo1la
ba1bo
bab1old
ba2b1ó2r
ba1bó
ba2b1ult
ba1bu
ba2bü
ba2c3hu
bac2h
ba2csor
bac2s
ba1c1so
2b1a2dag
ba1da
ba2das
2b1a2da1to
ba2d1ár
ba1dá
ba2de2g
ba1de
ba2d1e2s
ba2dog
ba1do
2b1a2do1má
ba2dód
ba1dó
ba2dó1i
ba2dój
ba2dók
ba2dót
ba2duj
ba1du
ba2dús
ba1dú
bae2gés2z1
ba1e
bae2gé
bae2r
baé2r
ba1é
ba1fl
ba1f2r
ba2ga1i
ba1ga
ba1g2n
ba1g2r
2b1ag2y.
bag2y
bai2z
ba1i
ba2jag
ba1ja
ba2j1á2ru
b2ajár
ba1já
ba2j1á2to
2baj1kú
ba2j1ó2r
ba1jó
ba2jü
ba2jű
ba2k1a2pó
b2akap
ba1ka
2bakas
ba2kaszt
bakas2z
2ba2kác
ba1ká
bak1á2c2s
bak1áll
2bakc
ba2keg
ba1ke
ba2k1é2r.
ba1ké
ba2k1é1ri
bak1k
ba1k2li
ba1k1lu
ba2k1o2v
ba1ko
b2a1k2ri
bak1t2
2b1ak1tu
baku2r
ba1ku
bak1u1ra
bak1u1rá
ba2ky
2b1a2lan2y
ba1la
2b1a2lál
ba1lá
b2a2l1e2s
ba1le
ba2l1í
bal2lak
bal1la
bal2lan
bal2lál
bal1lá
bal2l1ás
bal2láz
bal1le2
bal2leg
bal2l1el
bal2lem
bal2les
bal2l1é2l
bal1lé
bal2lin
bal1li
bal2lór
bal1ló
bal2té
bal2tiz
bal1ti
ba2lud
ba1lu
2b1amp
2banal
ba1na
2b1a2nat
banás3s
ba1ná
ban2c1e2
banc3s
2b1ang2y
ban2kab
ban1ka
ban2k1a2d
ban2k1a2l
ban2kar
ban1ke2
ban2ker
ban2kép
ban1ké
ban2kérd
ban2kir
ban1ki
ban2kol
ban1ko
ban1ku2
ban2kut
ba2nyó
ban2y
bao2k
ba1o
bao2l
baó2r
ba1ó
ba1p2l
b2a1p1ro
2b1ar2c.
2b1ar1cé
2b1arcn
2b1ar1co
2b1arcr
bar2csad
barc2s
bar1c1sa
bar2csal
bar2csan
bar2c1sö
2b1ar1cú
2b1ar1gu
baro2ma
ba1ro
2b1ar1té
baság1g
ba1sá
ba2seb
ba1se
ba1s1lá
ba1s2m
ba1s1ni
2b1as1pe
ba1s2pó
bas3s1ze
bas2s2z
b2a1s2ta
b2a1s2tá
ba2t1es1ti
ba1te
ba1t2rá
ba1t1re
b2a1t1ré
ba2ud
ba1u
2b1a2ul
bau2r
2b1a2vat
ba1va
ba1yi
1bá
bá2bal
bá1ba
bá2b1ass
bá2bál
bá1bá
bá2b1es
bá1be
bá2bik
bá1bi
bá2bö
bá2b1ü
bá2csor
bác2s
bá1c1so
bá2c1sü
2b1á2ga
b1á2gú
bá2gyal
bág2y
bá1gya
bá2gyar
bá2gyott
bá1gyo
bá2gyö
bá2gyú
bá2j1e2
báj2n1á
bá2j1ó2
bá2j1ö2
báj2t1a2k
báj1ta
bá2jü
bákos1
bá1ko
bá2laj
bá1la
bá2l1ap
bá2l1e2
bá1lé2
bá2l1éj
bá2li2d
bá1li
bá2l1ing
bá2l1i2o
bál2is
2b1ál1lí
2b1ál1lo
2b1állv
2bálm
bá2lö
bá2lü
bá2lyad
bál2y
bá1lya
bá2ly1al
bá2ly1a2n
bá2ly1á2z
bá1lyá
báni2as
bá1ni
báni1a
bán2ré
bánya2i1é
bán2y
bá1nya
bánya1i
bá2po
2b1á2rad
bá1ra
2b1á2ra1i
bá2r1aj
2b1á2ram
bá2rap
bá2ras
2bá2ra2t
2b1á2ráb
bá1rá
bá2rá2g
bá2rár
bá2r1ás
2b1á2rát
bár2das
bár1da
bár2d1á
bár2de
bá2r1e
bá2rén
bá1ré
2b1á2ri1á
bá1ri
bá2r1i2o
bá2r1i2p
bá2rí
2b1árn2y
bá2r1ó2n2é.
bá1ró
báró1né
2b1á2r2u.
bá1ru
2b1á2ruf
2b1á2rug
2b1á2ruh
2b1á2ruj
2b1á2ruk
2b1á2rur
b1ár1u2r2a.
báru1ra
2b1á2rus
2b1á2rut
2b1á2ruv
2bá1rú
bá2rúr
bá2rü
bá2s1á2ré
bá1sá
bá2se
bá2sis
bá1si
bá2sz1ak
bás2z
bá1s1za
bá2sza2n
bá2sza2s
bá2sz1ál
bá1s1zá
bá2sz1á2ru
bá2s1ze
bá2szil
bá1s1zi
bá2szi2p
bá2s1zí
bá2s1zö
bá2s1zü
bá2t1a2k
bá1ta
bá2t1al
bá2t1á2
2b1á2t1e2
bá2tö
bá2tü
bb1a2da
b1ba
bb1add
b2b1a2kas
bba1ka
b2b1alk
b2b1als
b2b1a1lu
b2b1alv
b2b1a2n2y
b2b1ap
b2b1a2ra
bba2t
b2b1a1u
bbá2gyas
b1bá
bbág2y
bbá1gya
b2b1áll
b2b1álm
b2b1áp
bb1árn
bb1á2ru
b2b1á2s
b2b1át
bb1dr
bbe2g
b1be
b2b1e2kén
bbe1ké
b2b1elv
b2b1emb
bb1eng
bb1erj
bb1ern
bb1e2rő
bb1e2rű
b2b1es1té
b2b1etn
b2b1ex
b2b1ég
b1bé
bb1é2l.
bb1é1le
bb1élh
b2b1élj
bb1éln
bb1élt2
bb1é2lű
bb1élv
b2b1ép
bb1érl
b2b1érm
b2b1ér1te
bbért2
b2b1ér1té
bb1érv
b2b1é1vi
bb1fr
bb1i2de
b1bi
b2b1i1ga
bbi2gaz1
bb1il1la
b2b1ind
b2b1int
b2b1inv
bbi2tat
bbi1ta
b2b1it1t2
b2b1í2r
b1bí
b2b1ív
bb1kl
bb1kr
b2b1okt
b1bo
bb1ol1tá
b2b1olv
b2b1op
bb1ott
b2b1ób
b1bó
bb1ó2r
b2b1ö2m
b1bö
bbö2r
bb1ö1rö
b2b1ös
bb1ő1ri
b1bő
bb1őrz
b2b1ő2s
bb1pl
bb1pr
bb1sk
bb1sp
bb1st2
bb1t2r
b2b1ud
b1bu
b2b1u2g
b2b1uj
bbu2r
bb1u1ra
b2b1u2t
bbúgás1
b1bú
bbú1gá
b2b1új
b2b1ú2s
b2b1üg
b1bü
b2b1ür
b2b1üz
bb1ű2z
b1bű
bb2ví
bc3s2z
bc2s
bda2cs1a2pá
b1da
bdac2s
bda1c1sa
bda1d2
bda1p2
bda1s2
bdas2z2
bda1t2
bdé2n
b1dé
bd2rá
bd2ro
bd2ró
1be
be2ac
be1a
bea2d
bea2j
bea2k
bea2l
bea2n
bea2r
bea2s
be2a2t1e
be2a1ti
be2a2tin
be2atk
be2atl
bea2v
beá2j
be1á
beá2s
beá2z
be1bl
be1b2r
be2csar
bec2s
be1c1sa
be2csá2r
be1c1sá
be2csért
be1c1sé
be2cs1é2te
be2dén
be1dé
be2d2z
bee2l
be1e
bee2s
beé2r
be1é
be1fl
be1fr
begés3s
be1gé
be2gés2z1
be1g2r
be2gyel
beg2y
be1gye
b1egyl
bei2g
be1i
bei2s
beí2r
be1í
be2j1elt
be1je
2bejt
2b1e2k2e.
be1ke
beke2c1sa
bekec2s
2b1e2ké1tő
be1ké
be1k2ré
be1k1ri
be1k1ró
be1k2v
be2lál
be1lá
bele1í2
be1le
bel1els
be2lemz
bele1p2r
belet2
bele1tr
be2l1é2k
be1lé
be2l1é2r.
be2l1é2re2n
belé1re
be2l1érr
be2lí
be2lof
be1lo
be2löl
be1lö
2b1e2lő1a
be1lő
be2lőr
bel1p2
2bemel
be1me
2b1eml
b1e2mus
be1mu
be2n1ál
be1ná
be2n1ék
be1né
ben2n1a2
ben2ná
ben2n1e2r
ben1ne
ben2nég
ben1né
be2ny1e2g
ben2y
be1nye
beo2k
be1o
beo2l
beo2m
beó2v
be1ó
beö2r
be1ö
be2p2e.
be1pe
be2ped
2b1e2pé
be1p2l
be1p2r
be2r1ad
be1ra
ber1a1lá
ber1all
bera2n
ber1an2y
be2r1a2p
be2r1a2r
ber1ass
be2r1a2t
be2r1av
be2raz
be3rág
be1rá
ber1áll
b1erde1i
ber1de
2b1er1dő
ber1e1gé
be1re
ber1eg2y
be2r1e2k2e.
bere1ke
bere2k1eg
be2r1e2kék
bere1ké
be2r1ell
be2r1elm
be2relő1dö
bere1lő
be2r1előn
be2r1e2mé
be2r1eml
be2r1e2pé
be2r1e2r
be2r1e2s1zü
beres2z
be2r1e2tet
bere1te
be2rég
be1ré
be2r1ékk
ber1éss
ber1in1gü
be1ri
be2r1ism
be2r1ist
ber1i2s1zo
beris2z
ber1old
be1ro
be2ror
ber1os2z
be2ról
be1ró
be2r1ó2r
be2rő
2b1erőd
ber1ő2s.
ber1ő2se
ber1ő2si
ber1ő2sü
ber3s2
berta2n1á2s
ber1ta
berta1ná
be2r1ub
be1ru
be2r1un
be2rur
be2r1ut
be2r1ü2g
be1rü
berü2l
be2r1üld
be2r1ü1lé
be2r1ült
be2sem
be1se
2b1esél
be1sé
2b1e2sés
be1s1ka
be1s1ká
be1s2l
be1s2m
2b1e2ső
be1s2p2
bes1s1z1a
bes2s2z
bes1s1z1á
be1s2ta
2bes1te
2bes1té
be1s2til
bes1ti
be1s1to
2b1e2szet
bes2z
be1s1ze
2b1e2szét
be1s1zé
b1eszm
besz2t1a2
besz2t1á
bete2g1é2r.
be1te
bete1gé
beté2t1elb
be1té
beté1te
beté2telk
be1t2hi
be1t2ra
be1t1rá
be1t2ré
be1t2ro
beu2g
be1u
beu2t
beü2t
be1ü
be2ve1ző
be1ve
2b1e2vol
be1vo
2b1e2vő
2b1ezr
1bé
2b1ébr
bé2c1sú
béc2s
bé2d1as
bé1da
bé2d1á
2bé1dé
2bédh
2bédj
2bédl
bé2d1o
bé2dö
2bédr
2bé1dü
2bédv
bé2gő
bék1alk
bé1ka
2b1ék1s2z2
bé2l1akt2
bé1la
bé2l1a2n
bé2l1a2p
bé2l1a2r
bé2lá2l
bé1lá
bé2l1á1to
bé2l1á2z
bé2l1ed
bé1le
bé2l1e2g
bé2lek
bé2l1e2r
2b1é2let
bé2l1é2j
bé1lé
bé2liz
bé1li
bé2lí
2bélj
bé2l1o
bé2ló
bé2lö
2b1é2lő
bélt2
bél1tr
bé2lul
bé1lu
bé2lú
bé2ly1e2c
bél2y
bé1lye
bé2l3yen
bé2lyin
bé1lyi
bé2lyö
bé2pí
bé2pü
2b1é2ra1i
bé1ra
bé2r1aj
bé2ral
bé2r1a2n
bé2rap
bé2rar
bé2rá
2b1éráb
2b1éráh
2b1éráv
2b1ér1de
bé2re2b
bé1re
bé2r1ele1me
bére1le
bé2r1e2le1mé
bé2r1e2lemh
bé2r1e2lemk
bé2r1e2lemn
bé2r1e2lemr
bé2r1ell
bé2relm
bé2r1elő1i
bére1lő
bé2r1eng
bére2n
bér1es2s2z
bé2r1es2z
bé2r1id
bé1ri
bé2rir
bé2rí
b1ér1ni
bé2rö
bér1s
bért2
2bértel
bér1te
2b1értés
bér1té
bér1tr
bé2rut
bé1ru
bé2rú
bé2s1z1a2
bés2z
b2é2s1zá
bé2s1z1o
béta1s
bé1ta
2bétel
bé1te
bé1t2h
2b1étk
2b1étt
2b1é2v.
bé2vek
bé1ve
2b1é2ven
2b1é2ves
bé2vet
bé2v2i.
bé1vi
2b1évn
bé2z1sú
béz2s
bfej1els
b1fe
bfe1je
bfe2len
bfe1le
bfé2n
b1fé
bf2la
bf2rá
bf2re
bf2ri
bf2ro
bg2ra
bg2rá
bgyö1kö2
bg2y
b1gyö
bgyö2k1öl
1bi
bia2d
bi1a
bi2a1e
bi2ag
bia2la
bia2v
bi1br
bi1by
bic3s2z
bic2s
bi1da2
bi2d1ad
bi2d1al
bi2deg
bi1de
b2i2del
2b1i2dő
bi2ed
bi1e
bie2l
bi1fr
bi2gaz
bi1ga
2b1i2gáj
bi1gá
2bigén
bi1gé
2b1i2hat
bi1ha
bik1a1la
bi1ka
bi2k1ál
bi1ká
bi2k1em
bi1ke
bi2kik
bi1ki
bi1k1lu
bi2k1ő2
bi1k2ro
bik1s
bil1i1ma
bi1li
bil1int
bilis3s
2bil1lé
2b1il1lő
2b1il1lu
bi2lü
bi2m2a.
bi1ma
2b1i2mi
2b1imp
bi2nab
bi1na
2b1i2nad
bi2naj
2b1i2na2t.
bi2n1árb
bi1ná
2b1in1dá
bi2n1é2te
bi1né
2b1in1ká
bin3n
bi2nü
bi1n2y
bi2o1a
bi1o
bi2o1á
bi2ob
bi2oc
bi2od
bi2o1e
bi2o1é
bi2of
bi2o1ge
bi2ok
bi2ol
bi2om
2b1i2on
bi2or
bió2r
bi1ó
bi2par
bi1pa
bi1p2l
bi1pr
2b1i2ram
bi1ra
2b1i2rat
2b1i2rán
bi1rá
2birká1i
bir1ká
2b1i2ro1dá
bi1ro
2b1irr
2b1ir1tá
2b1ir1tó
bis2hi
2b1i2si
2bism
bi2sö
bi1s2p
bis3s
bi1s2to
bit1a1rá
bi1ta
bi2t1e2g
bi1te
bit1elh
bit1elr
bit1elt
bite2r1a
b2iter
bi2t1ing
bi1ti
bi2t1int
bi2t1i2o
bi2t1on
bi1to
bit1t2
bit1u1ra
bi1tu
bi2t1ut
biú2s
bi1ú
bi2var
bi1va
2b1i2vás
bi1vá
1bí
bí2ja
bíróé2r
bí1ró
bíró1é
bí2ve
bí2vé
bí2vű
2b1í2zü
bkés3s
b1ké
bk2li
bk2lu
bk2ra
bk2rá
bk2re
bk2ré
bk2ri
bk2rí
bk2ro
bk2ró
1b2labl
b1la
blai2k
bla1i
bla2k1a2d
bla1ka
bl2a2kal
bl2a2k1an
bla2k1átm
bla1ká
bla2k1átt
bla2kem
bla1ke
bla2kik
bla1ki
bla2k1ol
bla1ko
bla2kü
bla1p1e
bla1p2l
bla1s2t
blás1s
b1lá
bl2be
ble2r1i
b1le
bles2
ble2t1ak
ble1ta
ble2t1a2n
ble2t1á2
ble2t1e2l
ble1te
ble2ter
ble2tes2z
ble2té2l
ble1té
ble2t1érd
ble2t1étk
bletigaz1
bleti2g
ble1ti
ble2t1i2ga
ble2t1o
ble2tó
ble2t1ö2
ble2tu
ble2tüz
ble1tü
ble1ü2
bleves1s
ble1ve
bl2he
bli2af
b1li
bli1a
bli2as
bli2of
bli1o
b1lja1na
bl1ja
bl2ne
1b2lok
b1lo
blok2k1ö2
blo2n1á
blon3n
b2l1ő1zé
b1lő
bl2re
bl2rő
bl2tő
blu2es2z
b1lu
blu1e
1bo
bo2a1á
bo1a
bo2ab
bo2ad
bo2a1e
bo2af
bo2ah
bo2aj
bo2am
bo2ar
bo2at
bo2av
bo2c1ak
bo1ca
bo2ce
bo2cé
bo2c3h
bo2c1sé
boc2s
bo1dy
bo2e1i
bo1e
bo1fl
bo2g1a2k
bo1ga
bo2g1a2t
bo2g1á2c
bo1gá
bogás1s
bo2g1e
bo2g1os2z
bo1go
bog2ó2s1zá
bo1gó
bogó1s2z2
bo2gyo
bog2y
boka1p
bo1ka
2b1o2kí
b1o2koz
bo1ko
bok2szak
boks2z
bok1s1za
bok2szal
bok2sz1ál
bok1s1zá
bok2szel
bok1s1ze
bok1s1zé2
bok2szél
bok2sz1in
bok1s1zi
bok2s1zó
bok2s1z1ő
2b1ok1ta
2b1o2la2j.
bo1la
bolás1s
bo1lá
2b1ol1dó
2b1o2lim
bo1li
bolo1g2
bo1lo
bol2t1e
bol2t1ö2
bol2t1ü2
2b1olvas
bol1va
bo1na1
bon1a1va
bon2can
bon1ca
bon2c1e
bon2c2h
2bond
bo2n1e2
bo2n1é2r.
bo1né
bo2n1or
bo1no
bon2t1i
bo2nü
bo2og
bo1o
bo2ok
bo2om
bo2ot
bo2pe
bo2r1a2d
bo1ra
bo2r1a1ka
bor1akk
bo2r1akv
bo2r1a2l
bora2n
bor1an2y
bo2rar
bor1as2z
bor1atr
bo2r1a1u
bo2r1av
bo2raz
bor1á2c2s
bo1rá
bo2r1áll
bo2r1áz
bor1d2
bo2re
bor1e2c
bor1e2l
bor1e2r
bor1e2s
bor1f2
borfi2ú1ké
bor1fi
borfi1ú
bo2r1i2ko
bo1ri
bo2r1il
bo2r1ing
bo2r1int
bo2r1isk
bo2r1iss
bo2r1ist
bo2r1itt
bo2r1iz
bor1k2
2b1ornam
bor1na
bo2r1ond
bo1ro
bo2r1ó2r
bo1ró
bo2rö
bo2rő
2b1or2r.
2b1or1rú
bor2s1ep1rű
bor1se
bor2sors
bor1so
bor1st2r
bor2sül
bor1sü
bor2süt
bor1t1re
bor1t1ré
bo2rü
borvíz1
bor1ví
bor2z1á2rak
bor1zá
borzá1ra
bor2z1sa
borz2s
bor2z3se
2b1oszt
bos2z
bo2t1a2g2
bo1ta
bo2t1al
bo2tar
bo2tas
bo2t1a2u
bo2t1ác
bo1tá
bo2tár
bo2t1e2
bo2t1il
bo1ti
bot1inf
bot1int
bo2t1i2p
bo2tí
bo2t1ó2
bo2tö
bo2tur
bo1tu
bo1tú2
bo2túr
bo2tü
bo2tű
bo2u1i
bo1u
bo2ul
bo2ur
bo1ya
bo1yá
bo1yé
bo1yi
bo1yo
bo1yu
bo2za2r
bo1za
bo2zál
bo1zá
bo2z1e2
bo2zid
bo1zi
bo2z1i2p
bo2z1ir
bo2zí
bo2z1old
bo1zo
bo2z3s
bo2zü
bo2zű
1bó
bóa2d
bó1a
bóá2g
bó1á
bóá2r
bó2bé
bó2cal
bó1ca
bó2c2h
bó2cü
bó1fl
bó1k1ré
bóli2a
bó1li
b1ólm
bó1p2l
bó1p2r
bó2r1ad
bó1ra
bó2r1an
bó2rat
2bó1rá
bó2rás
bór1ásv
bó2reg
bó1re
bó2rel
bó2r1in
bó1ri
bó2ri2z
bó2r1ol
bó1ro
bó1ró2
bó2rós
bó2rö
bórt2
bó2rü
bó1s2p
bó1s2z
1bö
bö2c2h
bö2lá
2bölb
böl2c1sü
bölc2s
2b1ö2lér
bö1lé
2böl1hö
2böli2g
bö1li
2bölk
b1öl1kú
2bölr
2b1öl1tö
2böl1tő
bö2lú
bö2lyö1kö
böl2y
bö1lyö
b1öml
bö2ná
2b1önt
bö2ra
bö2r1e
bö2ro
2b1ös2s2z
2b1ötl
2b1öts
bö2ve
1bő
bő2r1a2
bő2r1á2
bő2r1e2g
bő1re
bő2r1e2l
bő2r1em
bő2r1en
bő2r1e2r
bő2r1é2g
bő1ré
bő2rék
bő2r1és
bő2ril
bő1ri
bő2r1ing
bő2rip
bő2r1i2s
bő2riz
bő2r1izg
2bőrl
bő2r1o2
bő2r1öl
bő1rö
bő2rő
bőrren2
bőr1re
bőr1s2
bő2r1u
bő2rú
bő2r1ü2g
bő1rü
bő2r1ü2l
2bő1si
bp2la
bp2lá
bp2le
bp2re
bp2ré
bp2rí
bp2ro
b1p2roj
bp2ró
bra2k1á2
b1ra
bra2kös
bra1kö
bra1p2
1b2rat2y
brá2nag
b1rá
brá1na
brá2nas
brá2n1át
brá1ná
bránt2
brá2sz1ál
brás2z
brá1s1zá
brá2s1ze
b2ric
b1ri
bri2da
bri2dá
bri2der
bri1de
1b2rig
bri2no
bri2ód
bri1ó
bri2óf
bri2óm
bri2tel
bri1te
b2ro1sú
b1ro
bró2m1a
b1ró
bró2me
1b2rum
b1ru
bsé2g1el
b1sé
bsé1ge
b1s2ká
bs2lá
bs2pe
bs2pi
bs2po
bs2ta
bs2tá
bs2ti
bs2tí
bs2tú
bszá2r1a2da
bs2z
b1s1zá
bszá1ra
bsz2f
b1sz2k
bsz2tá
bter1mo1
b1te
btermos2z2
bt2rá
b1t2re
bt2ré
b1t2ri
b1t2ro
b1t2ró
1bu
bu2c2h
2b1udv
bué2r
bu1é
bu2g1i2
bu2il
bu1i
2b1uj2j.
2b1ujj2a.
buj1ja
2b1ujjad
buj2j1a2da
2b1ujja1i
2b1ujjak
2b1ujjam
2b1ujjas
2b1ujjat
2b1uj1já
2b1ujjb
2b1ujjc
2b1ujjd
2b1uj2j1e2
2b1uj1jé
2b1ujjf
2b1ujjg
2b1ujjh
2b1uj1ji
2b1ujjk
2b1ujjl
2b1ujjm
2b1ujjn
2b1uj1jo
2b1ujjp
2b1ujjr
2b1ujjs
2b1ujjt
2b1uj1ju
2b1uj1jú
2b1ujjv
buk2j1e
bu2maj
bu1ma
bu2mel
bu1me
bu2m1i2k
bu1mi
bu2m1i1na
bu2mis
bu2mol
bu1mo
2b1ural
bu1ra
b1urb
2burn
2b1u2rú
bu2se
bu2sin
bu1si
bu2sol
bu1so
bu1s2p
bus3s1ze
bus2s2z
bu2szab
bus2z
bu1s1za
bu2sz1aj
bu2szal
bu2szas
bu2sz1ál
bu1s1zá
bu2sz1á2rak
buszá1ra
bu2sz1árn
busz1en
bu1s1ze
bu2sz1él
bu1s1zé
bu2sz1é2p
bu2szid
bu1s1zi
bu2sz1il
bu2szim
bu2szin
bu2szip
bu2sziz
bu1s1zí2
busz1íj
busz3s
bu2s1zü2
1bú
bú2jí
2bú2r.
2b1ú2ri
2búrt
2bús2z
bú2s1zá
b1ú2ti
b1útm
1bü
bü2dü
bü2ge
bü2g2y
bü2ku
2b1üld
bü2l1é2n
bü1lé
bü2ne
2bü1rü
2b1ü2te
2b1ü2té
b1ü2tő
2b1ü2ve
bü2ze
1bű
bű2na
bű2ná
bű2nel
bű1ne
bű2nem
bű2nes
bű2n1e2t2
bűn1n
bű2no
bű2nó
bű2n1ő2
bű2nu
bű2nű
bű2ri
bű2v1e2
bű2z1a2
bű2z1á
bű2zo
bű2z1ő
bű2z3s
bvá2nya2d
b1vá
bván2y
bvá1nya
bvá2nyí
by2te
2c.
1ca
2c1abl
ca1b2r
ca2cél
ca1cé
ca2c2h
ca2dás
ca1dá
2c1a2dó
ca2es
ca1e
caé2ne2
ca1é
c2a1f1ro
2c1agg
ca2gya
cag2y
ca2gy1a2d
ca2gyu
2c1ajk
2c1a2kad
ca1ka
ca2kác
ca1ká
ca2k1áz
2c1akc
cak2kol
cak1ko
cak2k1ö
c2a1k1ri
cala2g1
ca1la
ca2lan
ca2l1a2s
2c1al1bu
ca2l1es1te
c2ales
ca1le
ca2l1é2l
ca1lé
c2alé2t
cal1é1te
c2a2lim
ca1li
ca2l1ip
cal1os2z
ca1lo
cal1p
cal1s
ca2lü
ca1l2y
ca2nal
ca1na
ca2nar
2c1a2ni
can2ne
caó2r
ca1ó
ca2pó
ca1p2ró
ca1p2s
ca2ran
ca1ra
ca2rán
ca1rá
2c1arc
ca2ris
ca1ri
ca1s2p
2c1as2s2z
cast2
c2a1s1to
c2a1str
2c1aszt
cas2z
ca2ta1u
ca1ta
ca2tem
ca1te
2c1atl
c1a2uk
ca1u
cau2n
ca2vat
ca1va
2c1a2zo
1cá
2c1ábr
cá2ga
cá2gú
cá2g2y
cá2la
c1ál1lá
2c1álm
cá2ne
cá2nét
cá1né
cá2nir
cá1ni
cá3p2a3
2c1á2po
2c1á2rad
cá1ra
2c1á2rak
cá2r1as
cá2ri1a
cá1ri
cá2ri1á
cá2r1i2n
2cárk
2c1árp
2c1á2r2u.
cá1ru
2c1á2ruh
2c1á2rus
cá2sás
cá1sá
2c1á2só
2c1ásv
2c1á2sza1i
cás2z
cá1s1za
2c1á2s1zo
2c1á2t1a2
c1áth
2c1á2t1i2
2c1átm
2c1átr
2c1átt
2c1á2tü
2c1átv
cb2lo
cb2ra
c2c1a2j
c1ca
c2c1ak
cc1alb
cc1a2n2y
c2c1a2r
c2c1a1u
c2c1ág
c1cá
c2c1ál
cc1bl
cc1ef
c1ce
c2c1elm
c2c1ember
ccem1be
c2c1ép
c1cé
c2c1é2r.
c2c1ér1te
c1c3he
cc2h
c1c3hí
c2c3ho
c1c3hő
c1c3hú
c2c1i2m
c1ci
cci2n2a.
cci1na
cc1ing
cci2óv
cci1ó
cc1i2pa
cc1i1ro
c2c1i2z
cc1ír
c1cí
cc1kl
cc1k2r
cc1o1á
c1co
c2c1ov
cc1ön
c1cö
cc1ös
cc1öz
cc1pl
cc1pr
c2c2s
c3csap
c1c1sa
c3csar
ccs1as
c3csat
ccs1ál
c1c1sá
c3csáp
ccs1ás
ccs1átl
ccs1eg
c1c1se
ccs1elem
ccse1le
ccs1ell
ccs1elv
ccs1em1be
c2cs1eml
c3csep
ccs1él
c1c1sé
ccs1ér1té
ccs1iv
c1c1si
c3csop
c1c1so
c2cs1ork
cc3so1ro
ccs1ö2l
c1c1sö
ccs1önt
ccs1s
ccs1ut
c1c1su
c3c1sú
c3c1sű
cc3s1za
ccs2z
cc3s1zá
ccs3zen
cc1s1ze
cc3s1zó
cc1új
c1cú
c2c1üg
c1cü
cc1ür
c1c3zá
cc2z
c1c3ze
cc3z2s
cda2l1é2
c1da
cda2l1i
cde2m1e2ké
c1de
cde1me
cde2m1el
cd2ra
cd2rá
1ce
cea2l
ce1a
ce2at
ce2a1u
ceá2r
ce1á
ce2béd
ce1bé
ce1bl
ce2c2h
ce1c3he
2c1e2d2z
ce2gas
ce1ga
ce2g1é2k
ce1gé
cegés3s
ce2gi1na
ce1gi
ce2gor
ce1go
ce1g2rá
ce2gu
ce2gú
c1eg2y.
ceg2y
c1egyb
ce2gyen
ce1gye
c1e2gyi
c1egym
c1egyr
2c1egys
2c1e2k2e.
ce1ke
ce1kl
2c1e2l1a2d
ce1la
2c1e2lág
ce1lá
cel1ér
ce1lé
2c1elf
2c1el1ha
2c1el1já
2c1e2l1os
ce1lo
c1e2lő1a
ce1lő
2c1e2lődö2t.
celő1dö
2c1e2lőf
2c1e2lő1í
c1e2lő1ő
2c1el1ső
2c1el1tá
c1el1to
2c1el1vá
2c1e2me1lő
ce1me
2c1eml
cenc1c
cen2c1eg
cen1ce
cen2c3s
2c1eng
cen2s1ég
cen1sé
cen2ta1u
cen1ta
ceo2l
ce1o
ceo2r
2c1e2p2e.
ce1pe
2c1e2pi
ce1p2r
cep2s1z1a2
ceps2z
cep2sz1é2p
cep1s1zé
cep2s1zi
cep2t1a2
cep2t1é2r.
cep1té
cep2tim
cep1ti
cep2t1ol
cep1to
2c1e2rej
ce1re
ce2róz
ce1ró
2c1e2rő
cer2t1a2
cer1tá2
cer2tár
cer2teg
cer1te
cer2t1e2l
cer2t1emb
cer2t1est
cer2tél
cer1té
cer2t1én
cer2t1ol
cer1to
cer2t1ö
cer2tu
cer1tü2
cer2t1üz
ce2sem
ce1se
2c1e2sés
ce1sé
2c1e2ső
c1es2t1é2j
ces1té
ces2t1ék
ce1st1ra
ce2t1e2g
ce1te
ce2t1es2s2z
ce2t1es2z
ce2t1é2t
ce1té
2ceth
ce2t1ill
ce1ti
ce2t1i2n
ce2tiz
2cetn
2ceton
ce1to
ce1t2ra
ce2t1us
ce1tu
ce2tűd
ce1tű
cetű2z
ceü2t
ce1ü
ce2vés
ce1vé
ce2vő
2c1e2zer
ce1ze
2c1ezre1de
cez1re
1cé
2c1ébr
cé2dé2l
cé1dé
cé2g1a2
cé2g1á2
cé2g1eg
cé1ge
cé2g1e2l
cé2ge2r
cég1e1re
cég1e1ré
cé2g1esem
cége1se
cé2ge1té
cé2ge1tő
cé2g1ék
cé1gé
cé2gép
2c1é2gés
cég1g
cé2g1i2d
cé1gi
cég1i1ga
cégi2g
cé2gi2gé
cé2gim
cé2gir
cé2g1iz
cé1go2
cé2g1ok
cé2gol
cé2gó
cé2gö
cé2g1u2
cé2gú
cé1ha2
cé2ha2l
cé2han
cé2har
cé2hed
cé1he
c2é2h1e2g
cé2hi2r
cé1hi
cé2hu
cé2hú
2c1éks
cé2la
cél1a2n
cé3lap
cél1a2r
cé2láb
cé1lá
cé2lá2l
cé2l1á2r
cé2l1á2t
cé2l2e.
cé1le
cé2led
cé2leg
cé2le1i
cé2lek
cé2l1e2l
cé2lem
cé2l1emb
cé2le2n
cé2l1er
cé2l1e2s
2c1é2let
2c1é2lez
cé2léb
cé1lé
cé2l1é2k
cé2lénk
cé2lép
cé2lé1re
cé2lés
cé2li2m
cé1li
cé2lin
cé2lir
cé2l1is
cé2liz
cé2lí
cé2ló
cé2l1ö2
cé2l1ő2
célt2
cél1tr
cé1lu2
cé2l1ut
cé2lü
2c1é2lű
cé2pí
cé2pü
cé2rag
cé1ra
2c1érd
2c1é2rés
cé1ré
cé2rin
cé1ri
2c1é2rint
cé2ris
cér1s
2c1ér1té
2c1ért2ő.
cér1tő
cé2rú
2c1érz
cés3s
2c1észh
cés2z
2c1étk
2c1étt
2c1é2v.
2c1é2vad
cé1va
2c1é2v2e.
cé1ve
2c1é2ve1i
2c1é2vek
2c1é2ven
2c1é2ves
2c1é2vet
2c1é2vét
cé1vé
cé2v2i.
cé1vi
2c1évn
2c1é2vü
cf2ló
cf2ra
cf2ri
cf2ro
cg2ra
cg2ri
c2h
1c1ha
c3had
2c3haj
2cham
2c3hang
2c3harc
2charm
2c3ha1tá
2c3hav
1c1há
2c3hám
c3hán2y
c3ház
ch1bl
1c1he
2c3heg
2chev
2c3hez
1c1hé
1c1hi
2c3hib
2c3hi1ó
2c3hitel
chi1te
2c3hitet
c3hi1ú
1c1hí
1c1ho
cho1d2
2c3hor
2c3hoss
1c1hó
1c1hö
1c1hő
ch1pr
ch1sc
ch1sp
1c1hu
chu2r
2c3huz
1c1hú
1c1hü
1c1hű
1ci
ci2a1a
ci1a
ci2a1á
cia1b2
ci2a1ba
ci2a1bo
ci2abr
ci2ac
ci2a1d2
ci2a1e
ci2a1é
cia1f
cia2fag
cia1fa
ci2afr
ci2a1g2
ci2ah
ci2aik
cia1i
ci2a1í
ci2aj
ci2akar
cia1ka
ci2akas
ci2aké2n.
cia1ké
ci2a1kó
ci2a1la
ci2a1lá
ci2a1le
ci2a1lé
ci2a1lo
ci2am
ci2an2y
ci2a1o
ci2a1ó
ci2a1ö
ci2a1ő
ci2a1p2
ci2ar
ci2a1s1za
cias2z
ci2a1s1zá
ci2a1s1zé
ci2a1s1zo
ci2a1s1zó
cia1t2
ci2a1ta
ci2a1tá
ci2a1té
ci2a1to
ci2a1tű
ci2a1u2
ci2a1ú
ci2a1ü
ci2a1ű
ci2av
ci2az
ciá2lan
ci1á
ciá1la2
ciá2nár
ciá1ná
ci2á1ó
2c1i2deg
ci1de
ci2de1o
2c1i2dé
2c1i2dom
ci1do
2c1i2dő
cie2r
ci1e
2c1ifj
2c1i2g2a.
ci1ga
2c1i2gaz
ci2g2e.
ci1ge
ci1g2r
ci2ker
ci1ke
cik1ka2
cik2kaj
cik2kan
cik2k1o
ci1k2la
2c1ik1rá
ci2l1á2t
ci1lá
2c1il1lu
ci2mit
ci1mi
2c1im1pu
ci2n1al
ci1na
ci2n1árt2
ci1ná
cin1d2
ci2n1e2re
c2iner
ci1ne
2cinf
2c1in1ge
2c1ingr
ci2nim
ci1ni
cin2kac
cin1ka
cin2k1a2l
cin2kért
cin1ké
cin2kol
cin1ko
cin2kor1s2
cin2kö
ci2nö
cin2tar
cin1ta
cin2t1es
cin1te
cinus1s2
ci1nu
ci2nü
2c1inv
ci2od
ci1o
ci2of
ci2og
cio1g1ra
ci2o1i
ci2ol
ci3o1lo
2c1i2onn
ci2op
cio2v
ci2ó1a
ci1ó
ci2ó1á
ci2óc
ci2ódar
ció1da
ci2ó1e
ci2óg
ci2ó1í
ci2ókal
ció1ka
ci2ókam
ci2ó1ká
ci2ó1ke
ci2ó1kl
ci2ókom
ció1ko
ci2ókos
ci2ó1ku
ci2ól
ci2ómag
ció1ma
ci2ó1má
ci2ó1né
ci2ó1o
ci2ó1ó
ci2ó1ö
ci2ó1ő
ci2ó1p
ci2ó1sá
ci2ó1se
ció2s1ér
ció1sé
ci2ó1s2ká
ci2ó1s1zo
ció1s2z2
ci2ó1tá
ci2ó1ú
ci2ó1ü
ci2ózón
ció1zó
ciő2r
ci1ő
ci2rat
ci1ra
2c1i2rán
ci1rá
2c1i2rod
ci1ro
2c1irt
ci2s2i.
ci1si
2c1is1ko
2c1ism
2c1isp
ci1stad
cis1ta
ci2s1z1i
cis2z
ci2t1aj
ci1ta
citá2r
ci1tá
cit1á2r.
cit1á1ra
cit1á1ré
cit1á1ro
cit1érr
ci1té
ci2tik
ci1ti
ci2t1ol
ci1to
ci1t2y
ciu1mi2
ci1u
ciu2min
ciu2m1i1o
ciumköz1
cium1kö
ciu2t
2c1i2vad
ci1va
2c1i2vás
ci1vá
1cí
cí2ja
cí2jé
cí2m1a2
cí2m1á
cí2m1e2l
cí1me
cí2m1é2l
cí1mé
cí2mí
cí2mo
cí2mö
cí2mő
cí2mu
cí2rá
cí2ró
cí2vá
cí2ve
cí2vé
cí2z2s
cí2zü
c1ka1ka2
c1ka
c2k1alj
c2k1arc
cka2rom
cka1ro
c2k1ág
c1ká
ck1áll
c2k1árn
c2k1e2g
c1ke
cke1p
1cke2r.
1cker1k2
1ckern
ck2é2p1e2l
c1ké
cké1pe
c2k1é2ré
c2k1érl
c2k1ér1té
ck1fl
ck1fr
ck1ill
c1ki
c2k1íz
c1kí
ck1kl
ck1k2r
ck2lu
c2k1o2la
c1ko
ck1o2pe
c2k1or1ro
c2kor1ru
c2kor1rú
1c2kosak
cko1sa
c2k1o1u
c2k1öb
c1kö
ck1öss
ck2re1á
ck1re
c1k2ri
c1k2rí
ck1sp
c2k1üg
c1kü
ck1ült
c2k1üt
c2k1ü2v
ck1űr
c1kű
ck2va
cli2s
c1li
clu2b1a
c1lu
cme1lo1
c1me
cnya2k
cn2y
c1nya
cnyol2c1a2n
c1nyo
cnyol1ca
1co
co2at
co1a
co2áz
co1á
co2be
co2e1u
co1e
co2kar
co1ka
co2ká
co2ke
co2kél
co1ké
co2ké2p
2c1o2kí
2c1o2laj
co1la
co2l1á1ro
co1lá
2c1ol1da
2c1ol1dá
2c1ol1dó
co2le
co2l1ibr
c2olib
co1li
co2li2m
col1i1ma
co2l1i2n2a.
coli1na
co2l1ind
co2l1ing
co2l1i2nu
co2l1o2r
co1lo
2c1olvad
col1va
2c1olvas
com1ba2
com2bal
com2b1e2
com2biz
com1bi
com2bol
com1bo
com2bó2r
com1bó
com2bö
2c1oml
co2ol
co1o
2c1o2pe
cop2f1ő
co1py
2c1orc
2c1o2ri
2c1orm
c1o2rom
co1ro
2c1or1ro
2c1or1rú
2corv
2c1oskol
cos1ko
co2s1o2ku
c2osok
co1so
cos3s
cos3z2s
cos2z
2c1oszt
co2ul
co1u
co2un
co2uv
co2vi
1có
c1ó2ni
2c1ó2rá
c1ó2ri
có2vó
1cö
c1öl1tö
c1ösv
c1ös2z
c1ötb
c1ö2t1e
c1ö2té
c1ötf
c1öth
c1ö2ti
c1ötk
c1ötm
c1ötn
c1ötr
c1öts
c1ött
c1ö2tü
c1ö2v.
cö2zö
1cő
cő2rü
cp2la
cp2ra
cp2re
cp2ré
cp2ri
cp2ro
cp2ró
c2s
1c1sa
2csabl
2cs1a2dat
csa1da
2cs1a2dá
2cs1a2dó
2cs1akc
csa2lakj
csa1la
csa2la1pú
2c2s1a2lá1í
csa1lá
2csalát
2c2s1alb
2cs1alg
2cs1alk
cs1al1le
2c2s1alm
csa2lomb
csa1lo
cs1amb
2csant
csa2pál
csa1pá
2c3sap1ka
2csap1ká
csa2por
csa1po
2cs1a2pó1ká
csa1pó
2cs1a2pókr
2cs1a2pósab
csapó1sa
2cs1arc
2csarg
2csark
2c2s1arz
2cs1ass
csa2t1ó2r
csa1tó
csava1r1a2
csa1va
cs1a1zo
1c1sá
csá2be
2csáf
2cság
cs1á2gak
csá1ga
cs1á2gu
2cs1á2rad
csá1ra
2cs1á2rak
2c2s1á2ram
2c2s1á2rat
csá2rát
csá1rá
2cs1árn2y
cs1á2ruk
csá1ru
2c1s1á1sá
2c3sá1si
2cs1á2só
2cs1á2t1á2sás
csá1tá
csátá1sá
2c2s1átf
2c2s1átm
2csátr
2c3sá2v.
2c3sá1vo
cs1bl
cs1br
cs1d2r
1c1se
2cse1bé
cs1e2ce
cse2c1sa
csec2s
2cs1e2dé
2cs1ed2z
c2s1eff
cs1e2ges
cse1ge
2c3se1gí
2cs1e2k2e.
cse1ke
2c2self
2cs1el1lá
2cs1e2lő1í
cse1lő
2cs1el1ta
2cs1el1tá
2c2s1el2v.
2c3sel2y
2c2s1ember
csem1be
2cs1e2mel
cse1me
2cseml
2cse1mu
csen2d1ő2
cse2nis
cse1ni
cse2n3yen
csen2y
cse1nye
2cs1enz
cse1p2ré
cse2rál
cse1rá
cse2r1e2ped
csere1p
cse1re
csere1pe
2cse2ró
2c2se2rő
2c2s1e2ső
2c2s1eszm
cses2z
cse2tüz
cse1tü
2c2s1e2vő
2cs1ex
1c1sé
2cség
c3sé2g.
c3ségb
c3ség3g
c3ségh
c3sé1gi
c3ségn
c3ségr
2c2s1é2hes
csé1he
2c2s1éhs
2cs1é2ka
2c2s1éks
2cs1é1le
csé2m1a
2cs1é2nekb
csé1ne
2c2s1é2ne1ke
2cs1é2ne1ké
2cs1é2nek1k2
2cs1é2nekr
2cs1é2neks
2cs1é2nek1t2
2cs1é2ne1kü
2c2s1é2pí
2c2s1é2pü
2cs1ér1de
csé2résk
csé1ré
2c3sér2v.
2c3sérvb
2c3sérvh
2c3sérvr
2c3sérvv
2c2s1érz
csé2s1za
csés2z
csé2tab
csé1ta
2c2s1é2v.
2c2s1é2vek
csé1ve
2c2s1évn
2c2s1évr
2c2s1évv
cs1fr
cs1gl
cs1gr
1c1si
csi1á2
2c2s1i2dő
2c2s1i2gé
2c3si1mí
2c3simog
csi1mo
2csim1po
c2simp
2cs1i2n2a.
csi1na
2c2s1i2nas
2c2s1ind
2cs1inp
2c2s1inv
2cs1i2o1no
csi1o
2c3si1pí
2c3si1ví
2c2s1i2vó
1c1sí
2csí1ki
2c3sírb
2c3sírj
cs1í2ró
2cs1í2v
2csí1ze
2c2s1ízl
cs1ízn
2csízt
c2s1í1zü
cska1s
cs1ka
cskas2z2
cs1kl
cs1kv
c3s2lág
cs1lá
1c1so
2cs1o2á
2cs1obj
cso2k1á
2c3sok2k.
2c2s1o2koz
cso1ko
2c2s1o2laj
cso1la
2c2s1ol1da
2c2s1ol1dá
2cs1old2ó.
c2sol1dó
2cs1oldók
2cs1oldóm
2cs1oldón
2cs1oldór
2cs1oldót
2cs1oldóv
2cs1o1li
2c2s1ol1ló
2c2s1olvas
csol1va
cso2mor
cso1mo
cson2t1a2
2cs1opc
2cs1orc
2cso1ri
2csork
2c3sorv
2cs1oszl
c2sos2z
2cs1oszt
1c1só
cs1ó1dá
csó2kes
csó1ke
csó2k1ö2
2cs1ó1né
1c1sö
2cs1ök1rü
cs1öml
csön3n
2c3sö2r.
c3sö1re
2cs1ös2s2z
2c2s1ö2v.
2cs1ö2zön
csö1zö
1c1ső
3cs2ő.
3csőb
csőé2h
cső1é
2cs1őrz
cső2sz1á2ra
csős2z
cső1s1zá
cső2szé2k
cső1s1zé
cső2s1ző
3csőv
c3s2pek
cs1pe
cs1p2l
csp2r
cs1ps
cs1sl
cs1s2p
cs1s2t
cs3s2z2
cssza2kü2
cs1s1za
c3s2tab
cs1ta
cs2top
cs1to
cst2r
c3st1ru
1c1su
2c2s1udv
2csug
2c3su1ho
c2s1ujj
2cs1u1na
2c2s1u2ni
2c2s1u1ra
2cs1u2rá
cs1u2tas
csu1ta
2cs1u1tá
c2s1u1tó
1c1sú
csú1p2
2cs1útn
1c1sü
2csüd
2c3sü3gé
2c2s1ü2g2y
2cs1ünn
cs1ü2te
2c3süv
2cs1üz
1c1sű
c4s3zac
cs2z
c1s1za
cs3zaj
csza2ké
cs3zam
c3szál
c1s1zá
c3szám
c2s3záp
cs3zát
c3száz
c3sz2c
c3szer
c1s1ze
c3s1zé
c3sz2f
c3s1zi
c3s1zí
c3sz2l
c3szob
c1s1zo
cs3zokn
c3szol
cs3zon
c3szor
cs3zón
c1s1zó
c3s1ző
cs3z2s
csz2t
cs3zug
c1s1zu
c1s3zú
c3s1zü
c3s1zű
c3sz2v
ct2ra
ct2re
ct2ré
ct2ri
ct2ro
ct2rü
1cu
cuc1ci2
cuc2cin
cuko2r1a2
cu1ko
cula2te
cu1la
cu2lü
2c1und
2c1u2no
2c1u2ra
2c1u2tá
1cú
cú2jí
c1ú2r.
c1úrr
c1ú2ti
cú2to
1cü
cü2ge
cü2g2y
2c1ü2lé
cü2lő
c1ünn
cü2re
cü2rí
cü2rü
cü2te
cü2té
cü2tő
cü2ve
cü2ze
1cű
cű2ző
cv2a2ne2m
c1va
cva1ne
cva2né2v
cva1né
cve2név
c1ve
cve1né
cven3n
c2z
1c1za
cza2ib
cza1i
cza2i1é
cza2ih
cza2ik
cza2in
cza2ir
cza2it
cza2iv
1c1zá
1c1ze
2c3zen
1c1zé
c3zéh
1c1zi
1c1zí
1c1zo
1c1zó
1c1zö
1c1ző
1c1zu
1c1zú
1c1zü
1c1zű
1czy
2d.
1da
daa2d
da1a
daát1
da1á
2d1ab1la
da2c1ir
da1ci
da2c2z
da2dag
1da1da
dad1a1la
2dada1to
2d1a2dó
d2a1d1rá
d2a1d1ro
dae2r
da1e
daé2d
da1é
daé2r
da1f2l
da1f2r
da2g1e2l
da1ge
dag3g
2d1ahh
da2i1re
da1i
2d1a2ján
da1já
2d1ajt
2d1a2kad
da1ka
daká2r
da1ká
2d1akko1ra
dak1ko
d1ak1ku
da1kl
d2a1k2ré
d2a1k2ri
2d1ak1tu
dal1a2ga
da1la
dal1ajt
da2lakj
da2l1ak1ta
dalakt2
da2l1ak1tá
d1a2la1ku
da2la1kú
2d1alakz
da2l1a2l
da2lapc
da2lapk
da2lapn
da2lapr
da2l1ap1s2
da2lapt
2d1a2la1pú
da2lar
da2l1as2z
da2latt
da2l1á2g
da1lá
da2l1ál
da2l1á2rak
dalá1ra
da2l1á1rá
da2l1árb
da2l1árn
da2l1árr
dal1á2s2z
da2l1á2ti
dale2l
da1le
dal1e1lá
da1l1e1le
dal1ell
dal1e2sé
d2ales
dalé2ke2
da1lé
da2l1é2l.
da2l1é2ne
d2alén
da2l1é2r.
dal1f2
da2lid
da1li
da2l1i2ko
d2alik
da2l1ikr
d2a2l1i2m
da2l1i2nát
dali1ná
da2lind
da2l1inf
da2l1ing
da2l1inj
da2l1int
da2l1i2nu
da2l1itt
dali2z
dal1i1zo
2d1al1ji
dal3l
2d1al1lo
dalo2m1e
da1lo
dalo1mo2
da2l1or
da2lőr
da1lő
dal1p2
dal1ud
da1lu
da2l1ur
da2l1u2s
da2l1u2t
dalú2t
da1lú
da2l1ú1to
dal1útr
da2lü
2d1amc
2d1amf
2d1a2nal
da1na
2d1ang
d1a2nyag
dan2y
da1nya
2d1a2nyó
dao2k
da1o
daó2r
da1ó
daó2v
2d1a2p2a.
da1pa
2d1a2pa1i
da2pa1ké
da2páb
da1pá
da2pád
da2pá1é
da2páh
da2pá1i
da2pák
da2pám
da2pát
da2páv
2d1apj
da1p2l
da2p2ó.
da1pó
2d1app
d2a1p1ro
da1p2s
2dapt
dara2be
da1ra
da2r1a2dó
d2arad
dar1a1la
da2rant
da2r1a1zo
da2r1á1ta
da1rá
da2r1átf
dar2c1e2
dar2c3h
dar2c1so
darc2s
2d1ar1cú
dar2d1a2l
dar1da
dar2d1á
dar2de2l
dar1de
dar2d1es
dar2d1ó2
da2r1el
da1re
da2r1il
da1ri
darus3s2
da1ru
dar1u1ta
da2r1ü
da2sál
da1sá
da1s2l
da1s1pe
2d1as2s2z
d2a1s2ta
da1szl
das2z
daszt2
dasz2tá
2da1s1zú
da2t1akt
d2atak
da1ta
da2t1akv
da2t1a2la
da2t1alk
dat1a1pu
d2atap
dat1a1rá
d2atar
dat1ass
da2t1att
da2taz
da2t1áll
d2atál
da1tá
da2t1árad
d2atár
datá1ra
datá2ramm
datá2ramr
da2t1á1ta
da2t1á1tá2
da2t1e2lem
da1te
date1le
dat1e2l1é
dat1e1lí
da2t1elk
dat1ell
da2t1e1lő
dat1elt
d2a2t1é2g
da1té
da2tér1te
d2atér
da2t1ér1té
da2t1érth
da2t1érv
da2tid
da1ti
dati2k
da2t1i1ko
da2tim
da2t1inf
d2atin
dat1in1te
dat1ist
d2atis
da2tiz
d2a2t1ír
da1tí
dat1í1ve
dat1k2
2d1atlas
dat1la
da2t1os2z
da1to
da2tóc
da1tó
2datóm
da2t1ó2r
dató2s
dat1ó1sá
dat1t2
da2t1u2t
da1tu
da2tút
da1tú
da2tűr
da1tű
da2tya
dat2y
da2tyá
2d1a2uk
da1u
da2up
2d1a2ur
dau2s
dau2ta
da3u2tó1p2
da2u1tó
daü2t
da1ü
daü2z
2d1a2vat
da1va
2d1avv
da1ye
da1yé
2d1a2z.
da2zál
da1zá
2d1a2zé
da2zok
da1zo
da2zon
1dá
2d1á2bé
2d1ábr
dá2c3ho
dác2h
dá2fá
dá2ga
dá2gá
dá2gú
2d1á2g2y
dá2lál
dá1lá
dá2lár
dá2l1e
2d1ál1lí
dá2lü
dá2ly1a2n
dál2y
dá1lya
dá1lyú2
dá2ly1ús
dá1mu2
dá2m1ut
dá2m1ú
dá2ny1a2d
dán2y
dá1nya
dá2ny1al
dá2ny1a2n
dá2nyaz
dá2nyó
dá2po
2d1á2rad
dá1ra
dá2r1ag
2d1á2ra1i
dá2r1a2j
dá2ral
2d1á2ram
dá2r1a2n
dá2r1a2p
dá2rar
dá2ras
dár1ass
dá2rat
dár1atk
dá2rá
dár1ál
dá2r1e2
dá2réb
dá1ré
2d1á2ri2a.
dá1ri
dári1a
dá2rij
dá2ril
dá2r1i2p
dár1isk
dár1ism
dá2rí
dá2rod
dá1ro
dá2r1ond
dá2r1ot
dá2rö
dá2rő
dár1s2
dárt2
dár1tr
2d1á2r2u.
dá1ru
dá2ruk
dáru2s1á2g.
d1á2rus
dáru1sá
dáru2s1á2ga
dá2rú2
2d1ár2ú.
dá2rü
dá2rű
dá2s1a2d
dá1sa
dá2sal
dá2sar
dá2s1á2g
dá1sá
dá2s1á2rad
dásá1ra
dá2s1árh
dá2s1á2ru
d1á2sás
dá2s1á2t1a2
dá2s1e2
dásfé2l1é2v
dás1fé
dásfé1lé
dá2sim
dá1si
dá2sis
dá2sol
dá1so
dá2sor
dá2só2
dá2s1ór
dá2sö
dá2ső
dást2
dás1tr
dá2su2t
dá1su
dá2s1ü2
dá2szag
dás2z
dá1s1za
dá2sza1ka
dá2szal
dá2szar
dá2szav
dá2sz1ál
dá1s1zá
dá2szár1k2
dá2sz1á2ro
dá2s1ze
dász1el
dász1em
dász1er
dá2széb
dá1s1zé
dá2szi2p
dá1s1zi
dá2szir
dá2szis
dá2s1z1í2
2d1ászká1i
dász1k2
dász1ká
dá2s1z1ö
dá2s1ző
dász3s
dá2sz1us
dá1s1zu
dá2s1z1ú
dá2s1z1ü2
dá2s1z1ű
2d1á2ta
dát1al
2d1á2tá
2d1átd
dá2t1e2
2d1á2té
2d1átf
2d1á2tí
2d1átj
2d1átk
2d1átm
2d1átr
3dá1tu
2d1á2tú
2d1átv
dba2l1
d1ba
db2lo
db2lú
db2ro
db2ró
dc2lu
dcsa2p1á2g
dc2s
d1c1sa
dcsa1pá
dd1elh
d1de
d2d1i2d
d1di
ddí2s
d1dí
d2d1o2d
d1do
dd2rá
dd2ró
d2d2z
d3dz2s
1de
de2a1a
de1a
de2a1á
de2ac
dea2d
de2a1e
de2a1é
de2ag2y
de2ah
de2a1í
de2a1la
de2a1lá
de2a1lo
de2am
dea2n
de2a1o
de2ap
dea1s2z2
de2aszf
de2at
2d1e2bé
ded1ell
de1de
2d1e2dén
de1dé
de2d1ó2v
de1dó
de1d1ra
de2d1ú2
2d1e2d2z
de2ep
de1e
dee2s
deé2r
de1é
2d1eff
de1fr
de2g1a2l
de1ga
de2g1a2n
de2g1ál
de1gá
de2g1e2l
de1ge
degen3n
de2ger
de2g1ék
de1gé
de2g1é1ri
de2gés
deg1éss
de2gés2z1
deg3g
de2giz
de1gi
2de1go
de2gor
de1gö2
de2g1öl
de2gör
de2g1ös
2de2gő
2d1e2gye
deg2y
degyez1
2degz
2d1ehh
deho2g
de1ho
de2if
de1i
dei2g
deí2r
de1í
de2k1a2k
de1ka
de2kaz
de2k1e2g
de1ke
de2kellen
dekel1le
de2kep
dek1e2rő
de2k1es2z
dek1ékb
de1ké
dek1é2ke
de2k1él
de2k1é1ri
de2kér1te
de2k1ér1té
de2k1érv
de2k1érz
de1k1lu
dek1old
de1ko
dek1s
deks2z2
2d1e2l1a2d
de1la
de2lef
de1le
2d1e2leg2y
dele2m1a
dele2má
dele2meg
dele1me
d1e2lemek
dele2mel
de1lem1e1le
dele2mu
2d1e2le1mű
2d1e2lemz
dele2t1a2
2d1e2l1e2te1té
dele1te
2d1e2l1é2k
de1lé
2d1e2lél
delés3s
2d1el1ha
2d1el1ho
2d1elkez1dé
del1ke
2d1elkez1dő
del2lal
del1la
del2l1an
del2l1e2g
del1le
delle2l
del2l1e1le
del2lelk
2d1elle1ná
2d1elle1ne
del2l1ent
del2ler
del2l1é2j
del1lé
del2l1é2k
del2l1in
del1li
del2l1is
del2los
del1lo
del2lór
del1ló
del2lőr
del1lő
2d1el1ma
2d1el1nö
de2l1os
de1lo
2de1lö
de2löl
de2lőad
de1lő
delő1a
2d1e2lő1ka
2d1e2lőrej
delő1re
2d1el1sa
2d1eltet2t.
del1te
2d1eltér
del1té
2d1el2v.
2d1el1vá
2d1elves
del1ve
2d1el1vo
2d1elv2ű.
del1vű
2d1elvű1e
2d1elvűk
2d1elvűn
2d1elvűr
2d1elvűs
2d1elvűt
2d1elvűv
de2mad
de1ma
de2m1a2l
de2maz
de2m1á2l
de1má
de2mez
de1me
de2m1é2rem
de1mé
demé1re
de2m1érm
de2mi2m
de1mi
dem1ing
2demo1i
de1mo
dem1p
de2mus
de1mu
demü2l
de1mü
de2nal
de1na
2d1e2ner
de1ne
denkié2ne
den1ki
denki1é
de2nol
de1no
de2n1ó2
dens1s
de2od
de1o
de2of
de2o1g2
de2oj
de2o1lo
de2om
de2ot
de2p2e.
de1pe
2d1e2pé
de1p2re
de1p1ro
de1p2s
de2rad
de1ra
der1a1ka
de2r1a2la
de2r1a2n
de2r1ar
de2r1á2g
de1rá
de2r1á2r
de2rás
der1ázt
2d1er1dő
dere2c
de1re
2d1e2redm
2d1e2re1je
2d1e2rején
dere1jé
2d1e2rejér
2d1e2rejét
de2r1e2ke1i
dere1ke
der1e2le
der1ell
der1e2lő
der1elt
de2rer
de2r1e2ső
de2r1él
de1ré
de2rid
de1ri
de2r1il
de2r1i2m
de2r1in
de2r1i2p
de2r1i2s
der2nék
der1né
de2r1os2z
de1ro
de2r1ó2r
de1ró
de2rő
d1er2ő.
der1ő2s.
d1erőt
d1erőv
der1sp
de1ru2
de2rut
de2r1ü2g
de1rü
de2r1üld
der1ü1le
der1ültet
derül1te
dervis1s
der1vi
2de2s1a2
2desg
de2sip
de1si
2d1es1kü
2destes
des1te
de1s1to
de2su2r
de1su
de1sú2
de2s1úr
2d1eszm
des2z
de1sz2ta
de2sz2ű.
de1s1zű
de2t1ék
de1té
de2ti1ka
de1ti
de2ti1ká
2d1e2vő
2d1evv
de2xa
de1xi2
de2xin
de2xiz
de2xí
de2x1o
de2xö
2de1za
de2zak
de2zér
de1zé
de2zil
de1zi
de2zin
de2z1or
de1zo
dező1e2
de1ző
dezőkés2z1
dező1ké
2d1ezr
1dé
2d1ébr
dé2dap
dé1da
dé2d1ő
dé2du
dé1fl
dé2g1a2
dé2g1á2
dé2g1e2b
dé1ge
dé2g1eg
dé2gép
dé1gé
dé2g1érk
dé2gés
dég1és2z
dég3g
dég1i1ga
dégi2g
dé1gi
dé2gi2gé
dé2gin
dé2gí
dé2g1ok
dé1go
dé2got
dé2gó
dég1s
dé2g1u2
dé2gú
dé2gű
2d1é2hes
dé1he
2d1éhs
2d1é2j.
2d1éjb
dé2k1ab
dé1ka
dé2kac
dé2k1a2d
dé2k1a2l
dé2k1a2n
dé2k1ap
dé2k1as
dé2k1a1u
dé2kaz
dé2k1ág
dé1ká
dé2k1árt2
dé2kás
dé2k1á1t1a2
dé2k1e2g
dé1ke
déke2l
dé2k1e1le
dék1ell
dék1e1lő
dé2k1elt
dé2k1er
dé2k1es2z
dé2k1e1ti
dé2kez
dé2k1é2j
dé1ké
dé2k1é2k
dé2k1é2l
dé2k1é2r.
dé2k1é1te
dék1is2z
dé1ki
dé2ki1vá
dé2kí
dékkulc2s1
dék1ku
dé2k1old
dé1ko
dé2kop
dé2k1or
dé2k1os2z
dé1kó2
dé2kór
dé2k1ö2v
dé1kö
dé2köz
dé2kő
dék2rém
dék1ré
dé2k1ut
dé1ku
dé2lad
dé1la
dé2lam
dé2l1á2
dé2leg
dé1le
dé2le2l
dél1e1lő
dé2les
dél1est
2d1é2let
dé2li2m
dé1li
dé2li1o
délkö2z1ön
dél1kö
délkö1zö
dé2lo
dé2l1ö2
2d1é2lő
dé2l1u2
dé2lük
dé1lü
dé2lyö
dél2y
dé2m1e2l
dé1me
dé2m1e2m
dé1na2
dén1ac
déná1r1a2
dé1ná
2d1é2ne1ke
dé1ne
dé2ny1el
dén2y
dé1nye
dé2nyid
dé1nyi
dé2nyo
dé2nyö2
dé1p2i
2d1é2pí
2d1é2pü
dé2rag
dé1ra
dé2ral
dé2r1an
dé2rar
dé2ras
dé2rá
dér1d2
dé2reg
dé1re
dé2r1eml
dér1e1sé
dé2r1est
dé2rez
dé2rés
dé1ré
dé2rif
dé1ri
dé2r1ik
dé2rí
dé2rot
dé1ro
dé1ró2
dé2rór
dé2rö
2d1ér1té
2d1érth
dé2r1ú2t
dé1rú
dé1ry
2d1ér1zé
dé1sa2
dé2s1aj
dé2sal
dé2sap
dé2sar
dé2s1az
dé1sá2
dé2s1ár
dé2seg
dé1se
dé2s1e2l
dé2s1e1ti
dé2s1ég
dé1sé
dé2sí
dé2sú
dé2s1ü2t
dé1sü
dész1ak
dés2z
dé1s1za
d2é2s3zá
dé2sz1ék
dé1s1zé
dé2szév
dé2s1z1o
dé2s1zú
dé2t1as
dé1ta
dé2t1e2g
dé1te
dé2t1is
dé1ti
2d1ét1ke
dé2tőr
dé1tő
2d1é2v.
2d1évb
2d1é2v2e.
dé1ve
2d1é2ve1i
2d1é2vek
2d1é2vem
2d1é2ven
2d1é2ve2s.
2d1é2vesb
2d1é2vesek
déve1se
2d1é2vesen
2d1é2vesh
2d1é2ve1si
2d1é2vesk
2d1é2vesn
2d1é2vesr
2d1é2vess
2d1é2vet
2d1évez
2d1é2véb
dé1vé
2d1é2vé1i
2dévén
2dévér
2d1é2vét
2d1é2vév
2d1évf
2d1évh
2d1é2vi
2d1évk
2d1évn
2d1évr
2d1évs
2d1évt
2d1é2vu
2d1é2vü
2d1évv
2d1évz
dfé2nyem
d1fé
dfén2y
dfé1nye
df2lo
df2ló
df2rá
df2re
df2ri
df2ro
df2rö
dgá2zár
d1gá
dgá1zá
dgázát1
dgá2zi
dgá2zó
d2g2e.
d1ge
dg2le
dg2li
dg2ló
dg2ra
dg2rá
dg2ró
d2gyu
dg2y
d2ha1li
d1ha
dhan2g1e2
d3hang
dhé2t1
d1hé
d2h2i.
d1hi
d2hi1é
d2hih
d2hi1i
d2hij
d2hik
d2hir
dhú2s1á2
d1hú
1di
di2a1a
di1a
di2a1á
dia1b
di2a1bá
di2a1bi
di2abr
di2ac
dia1d2
dia3da
1di2a1di
di2a1do
di2a1e
di2a1é
di2a1fa
di2ag2y
di2ah
di2ai2k
dia1i
di2a1í
di2aj
di2akép
dia1ké
di2akol
dia1ko
di2a1la
di2a1lá
di2a1li
di2am
di2a1na
di2a1ná
di2a1ni
di2a1nó
di2a1o
di2a1ó
di2a1ö
di2a1ő
di2a1p2
di2a1ra
di2a1s1za
dias2z
di2aszk
di2a1s1zó
di2a1t2
di2a1u2
di2a1ú
di2a1ü
di2a1ű
di2av
di2az
diá2k1e
di1á
diá2kol
diá1ko
dián3n
di1c2k
di2cs1aj
dic2s
di1c1sa
di2cs1e2r
di1c1se
2d1i2deg
di1de
2d1i2dej
di2de1o
2d1i2dén
di1dé
di2d1i2o
di1di
2d1i2dő
di2e1u
di1e
di1fl
di2g2a.
di1ga
2d1i2gá1ná
di1gá
di2g2e.
di1ge
di2g1e2l
2d1i2gén
di1gé
di2gét
2d1i2jes
di1je
di2kép
di1ké
di1k2l
2d1i2konh
di1ko
di1k2ro
dik1u2ta
d2ikut
di1ku
di1k2v
di2la2n
di1la
dile2m
di1le
dilig2
di1li
di2lö
di2l1ő
di2lü
di1l2y
di2m2a.
di1ma
2d1i2má
di2mit
di1mi
2d1imp
2d1i2na1ka
di1na
2dind
2d1inf
din1ga2
din2gal
2d1in1gá
2d1inger
din1ge
2d1i2nic
di1ni
d2i2n1ing
2d1inj
di2nód
di1nó
di2n1óm
di2n1ó2n
di2n1ó2r
2d1inp
2d1in1té
2d1inv
di2o1a
di1o
di2o1i
di2ok
di2ol
di2o1me
di2ov
di2ó1a
di1ó
di2ó1á
di2ó1e
di2óg
di2ó1í
di2ókam
dió1ka
di2ó1ká
di2ó1kl
di2ókok
dió1ko
di2ó1ku
di2ó1mé
di2ó1o
di2ó1ó
di2ó1ö
di2ó1ő
di2ó1p2
di2ó1rá
di2ó1ri
dió2si2
diós1ik
di3óso2r.
di2ósor
dió1so
di2ós1pe
di2ó1s1zű
dió1s2z2
di2ó1u
di2ó1ú
di2ó1ü
2d1i2pa2r.
di1pa
2d1i2pa1rá
2d1i2parb
2d1i2pa1ri
2d1i2pa1ro
2d1i2rat
di1ra
2d1i2rá
2d1i2rod
di1ro
2d1irt
di2saj
di1sa
2d1i2s2i.
di1si
2d1is1ko
2d1ism
2d1is1te
di2tal
di1ta
dit1a2la
dit1a1rá
di2t1e2g
di1te
dit1t2
di2tü
diu1mé2
di1u
diu2m1én
diu2mil
diu1mi
diú2t
di1ú
di2vad
di1va
2d1i2var
diva2t1a
2d1i2zé
1dí
dí2gé
dí2j1á1to
dí1já
dí2je
dí2jí
dí2jö
dí2jü
2d1í2rá
dí1ri2
dí2rik
2d1í2ró
dí2s1z1a
dís2z
dí2szer
dí1s1ze
dí2s1zö
dítés3s
dí1té
2d1í2v.
2d1í2ve
2dívn
2d1í2vü
2d1í2vű
dí2zi
dí2z2s
dí2zü
dí2zű
dj2eg
d1je
dje2gya
djeg2y
dj1is
d1ji
djo2n1
d1jo
dka2n1á2
d1ka
dki1a2
d1ki
dki1e2
dk2la
dk2li
dk2lo
dk2lu
dk2rá
dk2ré
dk2ri
dk2ro
dk2ró
dk2va
dk2vi
dlás3s
d1lá
dlá2s3z
dle1í2
d1le
dló1g2
d1ló
dlót2
dlő1kr
d1lő
dme2g1ér
d1me
dme1gé
dna2pe
d1na
dné2v1á
d1né
dnö2k1ö2l
d1nö
dnö1kö
1do
do2áz
do1á
do2b1ag
do1ba
do2b1a2l
doba2n
do2b1an2y
do2b1ár
do1bá
do2bát
do1be2
do2b1el
do2b1ill
do1bi
do2bí
2dob1je
do2bo1á
do1bo
do2b1old
do2b1or
do2bö
do2bü
do2bű
2d1o2dú
do2gar
do1ga
do2g1a2s2z1
do2gár
do1gá
dogás1s
do2g1ol
do1go
do2gor
dogos3s
do1g2rá
do2gü
do2kal
do1ka
do2kas
do2káj
do1ká
do2k1ál
do2k1e
do2k1é2l
do1ké
do2ké2p
dok1kö2
dok2k1öb
dok2kő
do2k1ott
do1ko
2d1o2koz
do2kö
dokú2t
do1kú
dok1ú1to
do2kü
2d1o2laj
do1la
dol2a2tar
dola1ta
dola2t1e
dola2t1ör
dola1tö
2d1ol1da
2d1ol1dá
2d1ol1do
2d1ol1dó
2d1oltár
dol1tá
2d1oltás
2d1olvas
dol1va
dom1a2cé
do1ma
do2m1árb
do1má
do2m1á1ré
do2m1árh
do2m1árj
do2m1árk
do2m1árl
do2m1árn
do2m1á2ron
domá1ro
do2m1árr
do2m1ár1tó
dom2ba2l
dom1ba
dom2bel
dom1be
dom2bol
dom1bo
dom2bón
dom1bó
do2me2l
do1me
2do1mí
2doml
do2m1ond
do1mo
do2mő
2do1mú
do2mü
do2n1ad
do1na
dona2l
don1a1la
do2n1a2r
do2n1as
do2n1ál
do1ná
do2n1á1ta
do2n1átj
do2n1áts
do2n1átv
don1d2
do2n1e
donos1s
do1no
do2n1os2z
do2nö
don1s
dont2
don1tr
do2nü
do2nyal
don2y
do1nya
do2nyar
do2nye
do2nyó
2d1o2pe
do1p2l
dor1akn
do1ra
do2r1a2l1
do2r1a2p
do2r1as
do2rat
dor1áll
do1rá
do2r1á2lo
dord2
dor1dr
do2r1e2
do2rid
do1ri
do2r1il
do2r1is
do2r1i1ta
dor1k2
do2r1okl
do1ro
dor1oszt
doros2z
do2rö
do2rő
2d1or1rú
dors2
dor1sp
dor1t1ró
dorú2t
do1rú
do2rü
2d1orvos
dor1vo
do2ug
do1u
do2ut
do2vi
do1ye
1dó
dóa2d
dó1a
dóá2g
dó1á
dóá2r
dó1bl
2dóez
dó1e
dó1fl
dó1f2r
2dó1gá
dó1g2r
dói2g
dó1i
dóí2v
dó1í
dó1k2l
dó1k1ré
dó1k2v
dó2mab
dó1ma
dó2mak
dóm1org
d2ómor
dó1mo
dó2mő
dóó2r
dó1ó
dó1p2l
dó1p2r
dó2rád
dó1rá
d1ó2rák
dó2ri1á
dó1ri
dó2sam
dó1sa
dó2sas
dó2sel
dó1se
3dós2i.
dó1si
dó2si2p
dó2sis
dó2sír
dó1sí
dó1s2ká
dó1s1pe
dó1s1pi
dó1s2rá
dós3s
dó1stáb
d2ós1tá
d2ó1st2r
dó2s1ű2
dós2z2
dó1szf
d2ó1szp
dó1t2r
2d1ó2vó
1dö
d1öbl
dö2ga
dö2gá
dö2g1el
dö1ge
dö2gev
dög3g
dög1na2
dö2go
dö2gó
dög1ö2lő
dö1gö
dö2g1ő
dö2gu
dö2ka
dö2ká
dö2k1e2l
dö1ke
dö2k1e2r
dö2kék
dö1ké
dö2k1é2r.
dö2kí
2d1öntöz
dön1tö
dö2ra
dö2rá
dö2ro
2d1ö2röks
dö1rö
2d1ös2z
d1ötl
döt2tért
döt1té
dö2ve
dö2vi
1dő
dőa2n
dő1a
dőá2g
dő1á
dő1bl
dő1cl
dő1d1ra
dőe2l
dő1e
dőe2r
dőé2l
dő1é
dőé2te
dő1fl
dő1f2r
dőgé2p1e2ké
dő1gé
dőgé1pe
dő1gr
dői2rá2
dő1i
dői2ta
dő1kl
dő1kv
dő2ny1a
dőn2y
dő2nye1le
dő1nye
dő1pl
dő1pr
2d1ő2r1áb
dő1rá
2d1ő2r1á2l
2d1ő2reb
dő1re
dőr1eg2y
dőr1e1le
dőr1e1lő
2d1ő2rem
2d1ő2r2é.
dő1ré
2d1ő2réh
2dőrék
2d1ő2r1é2l
2dőrén
2d1őrh
2d1ő2r1if
dő1ri
2d1ő2ril
2d1ő2r1in
2d1ő2rip
dő2r1is
2dőrok
dő1ro
2d1ő2r1or
2dőros
dő2röd
dő1rö
dő2r1öz
2d1ő2r1őr
dő1rő
2d1ő2r1un
dő1ru
2d1ő2r1u2r
2d1ő2rut
2d1ő2rü2
dőr1üg
dőr1ül
2d1őrz
dő2s1érv
dő1sé
dő1s1ká
dő1s2m
dő1s1ni
dőso2d
dő1so
dős1o1do
dő1s1pe
dő1s2pi
dő1s1pó
dő1s2ta
dő1s1té
dő1st2r
dő1sv
dő1s2z2
dőt1áll
dő1tá
dő1t2r
dp2la
dp2le
dp2lé
dp2ra
dp2re
dp2ré
dp2ri
dp2rí
dp2ro
dp2ró
dp2s2z
dra1ps
d1ra
dravas2z1
dra1va
drág1g
d1rá
drá2sz1ál
drás2z
drá1s1zá
drá2s1ze
drá1ta2
drá2tal
drá2t1e2
drá2t1ér
drá1té
dren2d1ő2
d1re
1d2ress2z.
dres2s2z
1d2resszb
1d2res1s1ze
1d2resszh
1d2ressz1k2
1d2resszr
1d2res1s1zü
dr2é2s1zá
d1ré
drés2z
dro2g1a
d1ro
dro2gá
dro2gen
dro1ge
drogé2n1i2
dro1gé
drog3g
dro2g1ó2
dro2n1a2
dro2nyi
dron2y
dros2z2
dro1szf
dro1t2r
dr2ó2baj
d1ró
dró1ba
dró2t1a2
dró2t1á2
dró2tis
dró1ti
dró2t1ü2
d2ru1i
d1ru
dru2se
dru2si
dság1g
d1sá
dsé2g1el
d1sé
dsé1ge
dsé2gül
d3sé1gü
ds2ká
ds2li
ds2pe
ds2pi
ds2po
ds2rá
ds2ta
ds2tá
ds2tí
dst2r
dsza2ké
ds2z
d1s1za
dszá2las
d1s1zá
dszá1la
dszáraz1
dszá1ra
d1s1z2e
dsze2ra
dsze2r1á
dsze2r1elv
d3szerel
dsze1re
dsze2r1o
dszert2
d1sz2l
d1szn
d1sz2p
d1sz2t2
d1sz2v
dta2g1a2
d1ta
dtalpa2d
dtal1pa
dtal2p1a1da
dtal2p1al
dta2n1á2s
dta1ná
d2t1ékn
d1té
d1t2rá
d1t2ré
d1t2ri
d1t2ro
d1t2róf
dt1ró
d2t1ül
d1tü
1du
du2cem
du1ce
du2c3h
du2cö
du2cü
due2l
du1e
du2gal
du1ga
du2g1ár
du1gá
2d1ugr
2duit
du1i
2d1ujj
dula1k2
du1la
dula2t1í
du2l1e
du2lép
du1lé
du1li2
du2l1im
du2l1in
du2lis
du2lí
du2lö
du2lű
2d1u2ni
2d1u2no
2d1unt
du2ó1a
du1ó
du2ó1á
du2ód
du2óf
du2ól
du2óp
du2ra1i
du1ra
du2rak
du2ral
2d1u2rat
du2ráb
du1rá
du2ráh
du2rát
du2ruk
du1ru
du2sal
du1sa
du2san
du2sar
du2s1as
du2sál
du1sá
du2seg
du1se
du2s1ér1té
du1sé
du2sin
du1si
du2s1iv
du2sol
du1so
du2ső
du2s1ű
2d1u2szod
dus2z
du1s1zo
2d1u2szo1ka
2d1u2szokb
2d1u2szo1ké
2d1u2szokh
2d1u2szokk
2d1u2szokn
2d1u2szo1ko
2d1u2szokr
2d1u2szokt
2d1u2szom
dus3z2s
du2t1i
2d1u2to
du2t2ó.
du1tó
du2tór
du2tu
1dú
dú2ce
dú2c2h
d2ú2c1se
dúc2s
d2ú2c3so
dúc3s2z
dú1dr
dú2j1é2
dú2jí
dú2r1a2c
dú1ra
dú2ral
dú2r1e2
dú2rén
dú1ré
2d1ú2ron
dú1ro
dú2rö
dú2s1zá
dús2z
dú2s1zó
dú2t1a2
dú2té
2d1útj
d1útl
2d1útn
dú2to2n1
dú1to
2d1útr
1dü
1dü2dü
dü2g2y
dü2gy1érn
dü1gyé
dü2ha
dü2há
dü2hel
dü1he
dü2ho
dü2hő
dü2két
dü1ké
dü2lá
2d1üld
dü2lep
dü1le
dülős2
dü1lő
2d1ün1ne
dü2rí
dü2te
dü2té
dü2tő
dü2ve
dü2ze
dü2zé
1dű
dű1pr
d1űrl
dű1s2z
dű1tr
dű2zé
dű2ző
dv2a2raj
d1va
dva1ra
dva2r1e
dva2r1ó2
dvá2nya2n
d1vá
dván2y
dvá1nya
dvá2nyí
dv1á1ta2
dv1á1te
d2v1e1ce
d1ve
dv1e2leg
dve1le
dv1elk
dven2t1í
dve2ra2l
dve1ra
dve2rár
dve1rá
dve2rip
dve1ri
dver1s
d2v1e2sés
dve1sé
dve1s2p
d2v1e2te1té
dve1te
dv1élm
d1vé
d2v1ép
d2v1ér1d2
d2v1é2ri
d2v1ér1té
d2v1érz
dv1fr
dvi2c1sa
d1vi
dvic2s
dvi2c1sá
d2v1i1ga
d2v1i2gaz1
dvitéz1
dvi1té
dv1or
d1vo
dvö2l
d1vö
dv1ö1lő
dv1ős
d1vő
dv1őz
dv1pr
dv1un
d1vu
dv1ú2t
d1vú
d2v1üg
d1vü
d2v1ü2z
d2v1űz
d1vű
dwa1yi
d1wa
dy1as
d1yéb
d1yén
dy1é1tő
d1yév
dy2jé
dy2ke
dyk2k
dyk2n
dyk2t
dy2vé
d2z
1d1za
dza1é2
2d3zaj
dzak2
dza1kr
1d1zá
dzá2r1ó2ra
dzá1ró
dzás1s
d3zás2z
1d1ze
1d1zé
dzé2sa
1d1zi
2d3zil
1d1zí
1d1zo
1d1zó
1d1zö
1d1ző
dző1a2
2dző1bő
2dződ
2dzőj
2dző1né
2dzőr
1d1z1sa
dz2s
1d1z1sá
1d1z1se
2dzs1e2g
2dzs1es2z
1d1z1sé
1d1z1si
2dzsir
2dzs1is
d3zsiv
1d1z1sí
2d1z1so2
dzs1ok
1d1z1só
1d1z1sö
1d1z1ső
dzs1s
1d1z1su
1d1z1sú
2d2zs1új
1d1z1sü
1d1z1sű
1d1zu
1d1zú
1d1zü
1d1zű
2e.
e1a
ea2bál
ea1bá
e2a1bo
e2a1bő
ea2da
ea2dá
eadás1s
ea2dó
ea1d2r
ea2du
eaé2d
ea1é
e2a1fá
e2a1fe
e2a1fi
e2a1fo
e2a1fö
e2a1fő
e2a1fü
e2a1fű
ea2gi
e2a1gó
e2aid
ea1i
e2ail
e2aim
e2aip
e2ais
ea2ja
e2a2k.
ea2kas
ea1ka
e2akat
e2a1ká
e2akb
e2a1ke
e2akép
ea1ké
e2akh
e2a1ki
e2a1kí
e2a1kl
e2a1ko
e2a1kó
e2a1kö
e2a1k2r
e2a1kú
e2a1kü
e2alán
ea1lá
eal1eg
ea1le
ea2lu
e2a2m.
e2a1ma
e2a1má
e2amel
ea1me
e2amer
e2a1mé
e2amin
ea1mi
ea2mo
e2a1mu
e2a1mú
e2a1mű
e2a1ne
e2a1né
e2a1nö
e2ans
ea2nya
ean2y
e2a1pi
e2a1po
e2a1pó
e2a1p2ro
e2a2r.
ea2ran
ea1ra
ea2ras
ea2rat
ea2rá
e2arb
e2a1re
e2arh
e2arj
e2arn
e2a1ró
e2arr
e2a1ru
e2a1rü
e2a1so
e2a1st2
e2a1sü
e2a1sű
e2aszem
eas2z
ea1s1ze
e2a1s1zé
e2a1ta
e2a1tá
e2atc
e2a1te
ea2t1eg
e2a1té2
e2a2tél
ea2t1é2ne
e2atf
e2atg
e2ath
ea2tid
ea1ti
eat1ing
e2atin
e2a2tip
e2a2tir
e2atm
eatmo1s
eat1mo
eatmos2z2
e2atn
e2a1to
e2a2t1or
e2a1tó
e2a1tö
e2a1t2rak
eat1ra
e2at2rón
eat1ró
e2ats
e2a1tu
e2a2tü
e2a1tű
e2atz
e2a2u.
ea1u
ea2ut
e2a2ux
e2a1vi
ea1vy
ea2zo
e1á
eá2bé
eá2c2s
eá2ga
eá2gá
eá2gi
eá2go
eá2gu
eá2g2y
eá2hí
eá2k1e
eá2k1osk
eá1ko
eá1la2
eá2lad
eá2l1ak
eá2lál
eá1lá
eá2l1á2r
eá2l1e2
eá1lé2
eá2lél
eál1fe2
eá2li2d
eá1li
eá2l1in
eá2lir
eá2l1ism
eá2lop
eá1lo
eá2l1ór
eá1ló
eá2lö
eá2mu
eá2nac
eá1na
eá2nal
eá2n1at
eá1ná2
eá2nár
eá2n1át
eá2n1e2
eá2ny1a2l
eán2y
eá1nya
eá2ny1a2n
eá2nyap
eá2nyar
eá2ny1as
eá2nyav
eá2ny1e2
eá2nyén
eá1nyé
eá2ny1é2r.
eá2nyif
eá1nyi
eá2ny1ing
eá2nyis
eá2ny1o2ku
eá1nyo
eá2nyö
eá2po
eá2rad
eá1ra
eá2ram
eá2ras
eá2raz
eá2ru
eá2rú
eá2sa
eá2sá
eá2sí
eá2só
eá2su
eá2s2z
eá1ta2
eá2t1e2
eá2té
eá2tí
eá2tu
eá2tü
e2ba2d
e1ba
eb1a1dó
eb1ad1ta
eb1a1ga
e2b1ajk
e2b1a2la
e2b1alk
eb1ant
eb1a2n2y
eb1atl
e2b1a1u
eb1a2zo
eb1ágg
e1bá
e2b1áp
eb1á1ra
ebe1á2
e1be
e2b1e2he
e2b1ejt
ebe2l1á
e2b1e2lef
ebe1le
ebe2l1e1me
e2b1elhel
ebel1he
e2b1enc
e2b1es1te
e2b1es1té
ebe2szek
ebes2z
ebe1s1ze
e2b1ex
e2b1ég
e1bé
eb1ép
e2b1érd
e2b1ér1té
ebért2
e2b1érz
eb1gr
e2b1id
e1bi
e2b1i2na
e2b1inf
e2b1ing
e2b1i1rá
e2b1i2s
e2b1izz
eb1kl
eb1kr
eb2lat
eb1la
e1b2lú
eb1okt
e1bo
eb1o2la
eb1orv
eb1öb
e1bö
eb1ös
eb2rus
eb1ru
eb1st2
eb1tr
e2b1üg
e1bü
ebü2l
eb1ü1lé
e2b1ür
eb1üz
ec1ajt
e1ca
ec1alk
e2c1a2n
e2c1az
ec1ág
e1cá
e2c1ál
ec1ár
ec1bl
ec2c1a2
ec2c1ér
ec1cé
ec2c3h
ec2c1i
eccs1át
ec2c2s
ec1c1sá
ec3c1so
ec2cú
e2c1eg
e1ce
e2c1e1lo
e2c1elv
2ecen1to
e2cetb
ece2t1o
1e2cets
1e2cett
e2ce1tü
ece2tüz
e2c1e1vé
ec3har
ec2h
e1c1ha
ec3hen
e1c1he
ec3h2i.
e1c1hi
ech1in
1e2c1hó
e1c1h1u
ec2le
ec2lu
e2csad
ec2s
e1c1sa
e2cs1a2la
e2cs1a2n
ecsa2p1á2g
ecsa1pá
e2cs1a2pák
ecs1a1rá
ecsá2r
e1c1sá
ecs1á1rá
ecs1árb
ecs1á1ro
e2cs1á2t
e2cs1é2l
e1c1sé
e2cs1é2r.
ecs1ér1té
e2cs1ol
e1c1so
e2cs1öl
e1c1sö
ecs1s
ecsúszós1
e1c1sú
ec2sús2z
ecsú1s1zó
e2c3sükb
e1c1sü
e2c3süt
ec3s1ze
ecs2z
e2c1ud
e1cu
e1c3zá
ec2z
e1c3ze
e2d1ab
e1da
e2d1a2dá
ed1a2n2y
e2d1a2z
e2d1á2c
e1dá
e2d1ág
e2d1áp
ed1eg2y
e1de
edele1me2
ede1le
e2d1e1lo
e2d1eml
ede2rak
ede1ra
ede2r1ál
ede1rá
ede2rel
ede1re
ede2r1ék
ede1ré
ede2r1o
ede2r1ü2l
ede1rü
ede2s1o
ede2tá
ede2tel
ede1te
ede2t1é2r.
ede1té
e2d1é2j
e1dé
edé2ká
edé2kis
edé1ki
edé2k1o
edé2lyá
edél2y
edé2lyo
1e2dénn
1e2dén2y
e2d1ép
e2d1ér1d2
edé2sa2
edé2so
edés3s
edé2s3z
e2d1é2vén
edé1vé
e2d1é2vér
ed1gr
2edic
e1di
e2di1de
e2d1i2ga
edigaz1
ed1ill
e2d1int
e2d1i1ra
ed1i1ro
e2d1ír
e1dí
e2d1ívn
e2d1íz
ed1old
e1do
ed1orv
e2d1os
e2d1ös
e1dö
e2d1őrs
e1dő
edő2s1ü
ed1pl
ed1pr
ed2ram
ed1ra
e1d2rog
ed1ro
e1d2ró
e2d1üg
e1dü
e2d1üt
e2d1űz
e1dű
ed2v1a2
ed1vá2
ed2v1ár
ed2vát
edv1é2r.
ed1vé
ed2v1öz
ed1vö
edy1i
e2d3zá
ed2z
1e2dző1i
e1d1ző
1e2dzőj
1e2dzőr
1e2dzőv
1edzv
e1e
ee2bé
ee2c2s
ee2d2z
ee2ge
ee2gé
ee2gés2z1
ee2g2y
ee2he
ee2ke
e2e2l.
ee2la
ee2le
e2eléb
ee1lé
ee2léd
e2elg
ee2lo
ee2lő
ee2me
ee3men
ee2mé
ee2mu
e2e2n.
e2enb
e2enj
e2ent
e2enw
ee2n2y
ee2pe
ee2pé
ee2po
ee2re
ee2ro
ee2ró
ee2rő
ee2sé
ee2sü
ee2s2z
ee2te
e2e1t2h
ee2to
ee2ve
ee2vé
ee2vi
ee2vo
ee2vő
ee2zü
e1é
eé2de
eé2ge
eé2gé
eé2gő
eé2gü
eé2he
eé2je
eé2jé
eé2ke
eé2kí
eé2le
eé2lé
eé2li
eé2lő
eélőkés2z1
eélő1ké
eé2lü
eé2lű
eé2me
eé2ne
eé2pí
eépítés1s
eépí1té
eé2pü
eé2ré
eé2ri
eé2rő
eé2rü
eé2s2z
eé2te
eé2ve
eé2vé
eé2vi
eé2vü
efa2x1i2
e1fa
efek2tá
e1fe
efek2t1í2
efenyő1é2
efen2y
efe1nyő
ef2f1in
ef1fi
ef2f1o
e1f2la
efle2x1i2k
ef1le
efle1xi
ef2lu
efo2n1alk
e1fo
efo1na
efo2nik
efo1ni
efor1ma2
efor2m1al
ef1pl
e1f2rá
ef2rö
e2g1a2bá
e1ga
ega2be
eg1abl
e2g1abr
e2g1a2cé
ega2c2s
e2g1a2d
e2g1a2g
ega2i
e2g1a2j
e2g1a2k
e3g2a3ké
ega2lac
ega1la
ega2lak
ega2lan
eg1a2lap
e2g1a2lá
e2g1alh
e2g1alj
e2g1alm
e2g1als
e2g1alt
e2g1a2m
eg1ang
eg1ann
eg1ant
eg1a2n2y
e2g1a2pa
eg1a1pá
ega2po
eg1a2pó
e2g1apr
eg1arc
ega2ri
eg1a2ro
eg1art
e2g1ass
e2g1a2s1za
egas2z
e2g1a2s1zo
e2ga1s1zú
eg1atk
ega1t2r
e3g2at2y
e2g1a2u
eg1a2va
e3g2azol
ega1zo
e2ga2zon
e3g2á1ba
e1gá
e3g2á1bó
eg1á2c2s
e2g1áf
e2g1á2g
eg1áh
e3g2á1i
e3g2á1ja
e3g2á1já
egá2ju
egá2m
e3g2án
e2g1áp
e2g1á2rá
e2g1árb
e3g2árg
e2g1árn
egá2ro
eg1árt
egá2ru
egá2rú
egá2sa
e3g2á2t.
e3g2á1tu
e3g2á1va
egá2zá
egá2zi
egá2zu
eg1bl
eg1br
eg1d2r
e2g1e2bé
e1ge
ege2c1s1ö2
egec2s
eg1ed2z
e2g1e2ge
eg1e1he
e2g1e2kés
ege1ké
e2ge1la
e3geled
ege1le
ege2leg
ege2lej
e2gelekt
eg1e2lemb
e2g1e2le1me
e2g1e2lemn
e2g1e2lemr
e2g1e2lemt
ege2l1e2s
eg1e2lég
ege1lé
eg1e2lér
e2g1elf
e3g2elg
e2g1el1ha
e3g2elit
ege1li
e2g1el1já
e2g1elm
e3g2el1ne
e2g1e1lo
e3g2elő1á
ege1lő
ege2lőb1be
e3g2előd
e3g2előf
e3g2elő1i
e3g2előm
e3gelő1nye
egelőn2y
e2g1el1tá
e3g2eltet
egel1te
e3g2el1tü
e2g1el1vá
e2g1elz
e2g1e2mel
ege1me
e3g2end
e3g2e1nye
egen2y
eg1e1pe
eg1epr
e3ge1rá
e2gerd
1eger2e.
ege1re
e2gered
1e2gerek
e2ge1ré
ege2rén2y
eg1erk
e3gerl
e2g1e2ró
e2g1e2rő
eg2esek
ege1se
e2g1ese1te
e2g1eszk
eges2z
e2ge2tal
ege1ta
eg1e2ve
e2g1ex
e2g1é2g
e1gé
e3g2émb
e2g1é2ne1ke
egé1ne
e2g1é2nek1k2
egé2ny1e2l
eg2én2y
egé1nye
e2g1é2pí
eg1éps
e2gé2r.
e2gérb
egé2r1es
egé1re
e2gé2re2t
egér1e1te
egé2rez
e2gé1ré
egé2rés
e2gérg
e2gé1ri
egé2r2i.
e2gérk
e2gérn
e2g1é2r2ő.
egé1rő
e2gérr
e2gérs
e2g1ér1té
e2g1érth
e2gér1tő
e2gérv
e2gés2z
eg1észl
1egész2s
eg1é1te
eg1fl
eg2gim
eg1gi
eg3gyal
eg2g2y
eg1gya
eg3gyan
eg3gyás
eg1gyá
eg3gyú
e3g2i.
e1gi
e2g1ibr
eg1idd
eg1i2de
e3gi1e
egi2g
e3gi2g.
e2g1i1ga
e2g1i2gé
eg1ij
e2g1i2ko
eg1ikr
e2g1ill
eg1imb
e2gimm
e2g1inf
e2g1ing
eg1i1no
e2g1ins
e3g2i1o
eg1i1ra
e2g1i2ro
eg1iss
eg1ist
egi2s2z
e2g1i2ta
e3g2i1tá
e2g1i1va
e2g1i1vá
e3gi1ve
eg1i1zé
eg1izg
eg1izm
eg1izz
e2g1íg
e1gí
e2g1ín
e2g1í2r
e2g1ív
e2g1íz
eg1kl
eg1kr
eg1kv
e2g1ob
e1go
e2goc
e2g1o2d
e2go1i
e2g1o2k
eg1o2la
e2g1old
eg1olv
e3gol2y
2egom
ego2mi
e2gont
e2g1op
eg1org
e3g2orom
ego1ro
ego2ros
e2g1orr
e2g1orv
e2g1o2s
e2g1ot
e3g2ó.
e1gó
e3g2ób
egó2do
e3g2ój
eg1ó2ra
eg1ó1rá
eg1óv
e2g1öb
e1gö
eg1ök
e2g1önt
eg1ö1rö
e3g2örög
egö2röm
eg1öt
eg1öv
e2g1ö2z
egőkés2z1
e1gő
egő1ké
e3gő1re
eg1ő1ri
eg1ő1rö
egő2s1zi
egős2z
e3g2őz
eg1pl
eg1pr
eg1ps
e1g2rat
eg1ra
e1g2róf
eg1ró
eg1sk
eg1sl
eg1sm
eg1sp
eg1s2t
egs2z2
eg1szt2
eg1tr
e3g2ub
e1gu
eg1ud
e3g2um
eg1u2n
e2g1u2r2a.
egu1ra
e2g1u2ra1i
e2g1u2rak
e2g1u2ras
e2g1u2rat
e2g1u2rá
e3gu1ru
e2g1u2s
e2g1u2t
eg1u2z
eg1új
e1gú
e3g2ún
eg1úr
eg1ús
eg1út
e2g1üd
e1gü
egü2gye
egüg2y
e2g1ü2le
eg1ü2li
e2g1üs
e2g1üt
e2g1üv
e2g1üz
e2g1űz
e1gű
egváro1si2
eg1vá
egvá1ro
e2gy1a2d
eg2y
e1gya
egy1a2g
e2gy1aj
egy1akt
egy1a2la
e2gy1a1rá
e2gy1as
egy1a2t
e2gy1a1u
egy1az
e2gy1ál
e1gyá
egy1á2rá
e2gy1árf
e2gyát
e2gy1eleg
e1gye
egye1le
egy1elf
egy1ell
egy1e1lo
egy1e2lőj
egye1lő
egy1e2lőv
egy1elz
e2gye1ni
1e2gyenl
1e2gyens
egy1eszt
egyes2z
1e2gyezm
egy1éks
e1gyé
1e2gyé1ni
e2gy1é2r.
e2gyip
e1gyi
e2gyis
e2gy1iz
egy1ok
e1gyo
e2gy1ol
egyo2r
e2gy1os
egy1ot
e2gy1ó2r
e1gyó
egy1ö2l
e1gyö
e2gy1ös
e2gy1öz
egy1ő2r
e1győ
e1gyu2
egy1ur
egy1ut
1e2gyüt
e1gyü
1egzis
eg1zi
eha2de
e1ha
ehá2zal
e1há
ehá1za
e2h1el1lá
e1he
ehe2lyes
ehel2y
ehe1lye
ehe2rál
ehe1rá
ehe2rát
ehe1re2
ehe2r1el
ehe2r1em
ehe2ren
ehe2res
ehe2rin
ehe1ri
ehe2rol
ehe1ro
e2h1é2je2
e1hé
ehé2ná
ehé2név
ehé1né
ehé2zá
ehé2zo2
e2h1ors
e1ho
eh1s2z
e1i
ei2áb
ei1á
ei2áh
ei2áj
ei2án
ei2ár
ei2át
ei2áv
e2ibn
ei2de1á
ei1de
e1i2de1i
ei2de1o
ei2dén
ei1dé
ei2dom
ei1do
ei2dő
e2idp
e2ier
ei1e
ei2gá
ei2gé
e2i1g2n
ei2g2y
ei2ha
eil2l2e.
eil1le
ei2m2a.
ei1ma
ei2man
ei2má
ei2mit
ei1mi
e2imk
e2ims
e2imz
ei2na2
ein1ad
ei2n1á2
ei2neg
ei1ne
e2inér
ei1né
e2inét
ei2n1i1ta
ei1ni
ei2nol
ei1no
ei2nő
ein1t2r
ei2nü
ei2on
ei1o
ei2pa
ei2ram
ei1ra
ei2rat
ei2ri
ei2rod
ei1ro
e2i1ró
ei2ta
e2itb
e2itj
e2itn
e2itr
ei2va
ei2vá
ei2vo
ei2vó
ei2zé
e2iz1mi
ei2zo
e1í
eí2gé
eí2já
eí2jú
eí2ra
eí2rá
eí3rás1be
eí3rásil
eírá1si
eí3rásoc
eírá1so
eí3rásonk
eí2r2ó.
eí1ró
eí2ró1a
eí2ró1á2
eí2rób
eí2ródn
eí2róf
eí2róg
eí2róh
eí2ró2i.
eíró1i
eí2róik
eí2róin
eí2róit
eí2ró1í2
eí2ró1ja
eí2ró1já
eí2ró1je
eí2ró1ju
eí2rók
eí2róm
eí2ró2n.
eí2ró1na
eí2ró1ná
eí2rón2y
eí2rór
eí2rót2
eí2róv
eí2té
eí2vá
eí2ve
eí2vé
eí2vi
eí2vo
eí2vó
eí2ze
eí2zü
eí2zű
ej1ab
e1ja
e2j1a2d
ej1a2g
e2jak
ej1ak1k2
ej1a2l
ej1a2n
ej1ar
ej1a1u
ej1a2z
ej1áb
e1já
e2j1á2g
ej1ál
e2j1ár1tó
ej1á2t1e2
ej1átv
ej1bl
ej1br
ejcsa2p1
ejc2s
ej1c1sa
ej1dr
eje2c
e1je
e2j1e1c1se
ejec2s
e2j1ef
e2j1e2ged
eje1ge
e2j1e2gé
e2jekc
e2j1e1la
e2j1elc
e2jele1de
eje1le
e2j1e2lemb
e2j1e2le1me
e2j1e2le1mé
e2j1e2lemn
e2j1elf
e2j1el1ha
e2j1elhel
ejel1he
e2j1e2l1o2
e2j1el1s1zá
ejels2z
e2j1eltér
ejel1té
e2j1e2lu
e2j1eng
e2j1enz
e2j1es2s2z
eje2s2z
e2j1ex
e2j1é2j
e1jé
e2j1é2k
e2j1él
e2j1ép
e2j1é1te
ej1fr
ej1g2r
e2j1i2d
e1ji
e2j1im
ej1i1na
e2j1int
e2j1ip
e2j1iz
ej1íg
e1jí
ej1ív
ej1kl
ej1kv
ej2mok
ej1mo
ej1ol
e1jo
ej1op
ej1óc
e1jó
ej1ón
ejó2sá
ej1ót
ej1óv
e2j1öb
e1jö
e2j1öl
e2j1ö2v2e.
ejö1ve
e2j1őz
e1jő
ej1pl
ej1pr
ej1sp
ej1st2
ej2tad
ej1ta
ej2ta1u
ej2tál
ej1tá
ej2tát
ej2t1elk
ej1te
ej2t1es1te
ej2tev
ejté2r
ej1té
ejt1é1ré
ej2tin
ej1ti
ej2tiz
ej2tos
ej1to
ej2t1ó2r
ej1tó
ej2töd
ej1tö
ej2t1ö2l
ej2tön
ej2tös
ej1t1ra
ej1t1ró
ej2tür
ej1tü
ej1új
e1jú
ej1úr
ej1ú2t
e2j1üg
e1jü
e2j1ür
e2j1ü2t
e2j1üv
e2j1üz
e2k1abl
e1ka
ek1a2cé
ek1a2dá
e2k1a2dó
eka2g2y
ek1a2ja
e2k1ajt2ó.
ekaj1tó
e2k1ajtó1i
e2k1ajtók
e2k1ajtón
e2k1ajtór
e2k1ajtót
ek1a2kar
eka1ka
e2k1alj
e2k1a2lo
ek1alt
e2k1ang
e2k1a1ni
ek1a2nyá
ekan2y
ek1ar2c.
ek1ar1ca
ek1arcr
ek1ar1cú
eka2si2p
eka1si
e2k1a1u
ek1a1zo
e2k1ág
e1ká
ek1ál1lo
ek1álm
e2k1á2rad
eká1ra
eká2ra1i
ek1á2rak
ek1á2ras
e2k1á2rá
e2k1ár1d2
e2k1árf
e2k1árh
e2k1árk
e2k1árm
e2k1árn
e2k1á2ron
eká1ro
e2k1árr
e2k1á2ru
e2k1á2rú
e2k1árv
ek1á2só
ek1á1ta
ek1átd
e2k1áth
ek1átj
e2k1átm
ek1á2to
e2k1átt
ek1bl
ek1br
1ekcém
ek1cé
ekci2óf
ek1ci
ekci1ó
ek1cl
ek1dr
e2k1e1bé
e1ke
e2k1e2d2z
e2k1egg
e2k1e2gye
ekeg2y
e2k1e2ké1bő
eke1ké
e2k1e2kés
e2ke1la
e2k1e2leg
eke1le
e2ke2le1me
ek1el1ha
e2k1elk
e2k1el1lá
e2k1elm
e2k1e1lo
e2k1előn
eke1lő
e2k1elr
ek1el1ta
ekel2t1é2r
ekel1té
e2k1e2ma
e2k1e2mel
eke1me
ek1e2més
eke1mé
ek1e2pi
e2k1estr
eke2sze1le
ekes2z
eke1s1ze
eke2s1zo
e2k1e2vé
e2k1e1vi
1e2kééh
e1ké
eké1é
e2k1ég
1e2kéik
eké1i
ek1ékek
eké1ke
e2k1ékt
ek1é2le
ek1éln
ek1é1lő
e2k1é2pí
e2k1érin
eké1ri
e2k1értékb
ekér1té
eké2rül
eké1rü2
e2k1és2z.
ekés2z
e2k1észh
e2k1észn
eké2tel
eké1te
e2k1étk
e2k1étl
e2k1étt
e2k1é2v2e.
eké1ve
e2k1é2vek
e2k1é2vet
e2k1é1vi
ek1fr
ek1gn
ek1gr
eki1á2
e1ki
e2k1i2ga
ekigaz1
e2k1i2ge
e2k1i1gé
eki1i2
ek1ill
e2k1i1ma
e2ki2már
eki1má
e2k1i1ná
e2k1ind
e2k1isk
e2k1ism
e2k1isp2
ek1i2zo
e2k1íj
e1kí
ek1í2rá
ek1íz
ekka2ró2
ek1ka
ek2k1e1le
ek1ke
ek2k1elf
ekk1os2z
ek1ko
ek1k1ri
ek2kű
eklés3s
ek1lé
ek2lim
ek1li
ek3nő
e2k1o1á
e1ko
ek1obj
e2k1odv
e2k1o2la
ek1ol1ló
e2k1olv
e2k1o2pe
ekor2da
ekor2d1á2
ek1o2rom
eko1ro
ek1or1ra
e2k1orv
ek1otth
e2k1ó2h
e1kó
ek1ó2ra
ek1ó2rá
ekö2k
e1kö
e2k1ö1kö
e2k1ö2lé
ek1ö2lő
e2k1öm
e2k1önk
e2k1önt
ekö2ri
e2k1örv
ek1pl
ek1pr
ek1ps
e1k2ram
ek1ra
e1kré1tá
ek1ré
ek2ris
ek1ri
e1k2rí
ek2róm
ek1ró
ek1sl
ek1sm
ek1sp
ek1st
eksz1al
eks2z
ek1s1za
ekszes1
ek1s1ze
ek2szi2p
ek1s1zi
ek2ta1u
ek1ta
ek2taz
ekt1elk
ek1te
ek2t1es2z
ek2t1érd
ek1té
ek2til
ek1ti
ek2ti2m
ek2t1i2o
ek2t1ok
ek1to
ektus1s2
ek1tu
e2k1ud
e1ku
e2k1u2ra
ek1u1ro
e2k1u1tá
e2k1u1tó
e2k1uz
e2k1új
e1kú
e2k1ú2r.
ekú2t
ek1ú1to
ek1útv
e2k1ünn
e1kü
ekü2t
ek1ü1tő
ek1üzl
e1k2vó
el1a2ba
e1la
el1abl
el1a2bort
ela1bo
e2l1a2c
e2l1a2d
el1agg
el1a2g2y
el1a2j
e2l1a2kad
ela1ka
e2l1a2kas
el1akc
e2l1a2l
el1a2m
el1a1na
elan2di
el1a2ne
el1a2ni
el1ann
ela2n2y
el1a2pa
ela2r
el1a1ra
el1a1rá
el1a1ré
el1a1s1za
elas2z
el1a1s1zi
el1a1s1zó
el1a1s1zu
e2l1a2u
el1a2va
el1a2ve
el1a1zo
elá2bé
e1lá
el1á2g.
e2l1á2ga
el1ágg
el1á2gi
el1ágn
el1á2go
el1á2j
el1áld
el1áll
el1á2lo
elá2m
el1á2mí
el2án
elá2ná2
elá2ne
elá2nó
el1á2p
el1á2r.
el1á2rá
el1árb
el1árc
el1á2re
el1á2ré
el1árf
el1árh
el1árk
el1árn
el1á2ro
el1á1rö
el1árr
el1árt2
el1á2ru
el1á2rú
elá2s
elá1ta2
el1á1t1e2
el1átf
el1átk
el1átl
el1átm
el1átr
el1á1zi
el1á1zo2
el1ázt
el1bl
el1br
2el2d.
el1d2r
ele1b1re
e1le
2ele2d.
1e2ledel
ele1de
el1eff
ele2g1e2lé
ele1ge
el1e2gye1ne
eleg2y
ele1gye
e2le1gyü
el1egz
eleí3ran
ele1í
eleí2ra
ele1k2l
ele2k1os
ele1ko
ele1k1rá
1elektr
e2l1elb
e2l1e2lemb
ele1le
e2l1elm
e2lelőz
ele1lő
el1elr
ele2mad
ele1ma
e2l1ember
elem1be
1elem2e.
ele1me
1e2leme1i
1elemek
ele2mell
1e2leme2m.
1e2lememm
1e2leme2s.
1e2lemesn
ele2mes2z
ele2mélt
ele1mé
ele2mérd
1e2lemük
ele1mü
1e2lemünk
1e2lem1zé
1e2lem1ző
2el2end
e2le2ner
ele1ne
ele2né2l
ele1né
ele1ó2
ele2pal
ele1pa
ele2pa2p
e1le2pe2le
ele1pe
ele2pell
ele2p1ő2r
ele1pő
e2lerd
el1e2red
ele1re
el1e2re1i
el1erj
e2l1ern
e2le2róz
ele1ró
e2le2se1ge
ele1se
ele2sésb
ele1sé
ele2sé1se
ele2sé1sé
ele2sésh
ele2sé1si
ele2sésk
ele2sésn
ele2sésr
ele2sés3s
ele2sést
ele2sé1sü
ele2si2k.
ele1si
e2l1esnén
eles1né
ele2sőb
ele1ső
ele2sős
e2l1essél
eles1sé
ele1s1ta
ele2szek
eles2z
ele1s1ze
ele2t1e2két
ele1te
elete1ké
ele2te1ti
ele2tetn
ele2te1tő
ele2t1ék
ele1té
ele2té2l
e2l1ették
elet1té
ele1ü2
1e2l1e2vők
ele1vő
el1e2xi
e2lébb
e1lé
elé2du
el1é2ges
elé1ge
el1é2get
el1égj
el1égtek
elég1te
el1égv
e2l1é2het
elé1he
elé1ka2
elé2kak
elé2k1an
elé2k1á
elé2k1e2le
elé1ke
elé2kev
elé2ke1ze
elé2ke1zi
elé2kezt
el1é1kí
elé2kö
elé2ku
el1é2l.
el1éld
elé2led
elé1le
el1é2let
e1l1é2lé
el1élh
el1é2li
el1élj
el1éln
el1éls
e2l1élt
el1é2lü
e2l1élv
e2l1é2ne1ke
elé1ne
e2l1é2nekh
e2l1é2red
elé1re
e2l1é2rem
elé2rend
elére2n
e2l1é2rez
elé2ré2t.
elé1ré
elé2r2i.
elé1ri
e2l1érp
e2l1ér1rő
e2lértel
elér1te
e2l1ér1té
el1érth
e2l1ér1tő
e2l1é2rü
e2l1érz
2elésé1tő2
elé1sé
elé2so
2elés2z.
elés2z
2elészel
elé1s1ze
2elészem
2elész1ne
2elész1né
2elé1s1ző
2elé1s1zü
2elészv
elé2tel
elé1te
e2l1é2tes
elé2te1te
e2l1étk
e2l1étt
e2l1é2v.
el1fl
el1f2r
el1gl
1elhap
el1ha
el2ib
e1li
e2l1i2deg
eli1de
el1i1dé
el1i2do
el1i1ga
e2l1i1gé
e2l1ill
e2l1i2ma
e2li1má
e2l1im1p2
e2l1in1to
el1i2on
eli1o
eli1ő2
e2l1i2p
e2l1i1ra
e2l1i2ro
e2l1i2si
e2l1ism
el1iss
el1is1te
eli2tin
eli1ti
eli2tol
eli1to
eli1tu2
el1i2vá
e2l2ix
el1i2zo
e2l1íg
e1lí
el1í2rá
el1í2v
elka2r1á
el1ka
1elkez1dé
el1ke
1elkez1dő
el3ki
el1k2l
el1k2r
el1kv
el2l1a2dá
el1la
el2l1a2dó
ell1alk
el2l1amb
el2lamp
ella1t
el2la1u
el2l1ábr
el1lá
el2l1áll
1ellátá1so
ellá1tá
1ellátá2s1ü2
1ellátm
el2lef
el1le
elle2g1ó2
ell1el1ké
el2lelm
1elle1ná
1ell2enes
elle1ne
1ellenf
1elle1nő
1ellens2
1ellenz
el2lid
el1li
ell1inf
ell1in2g.
ell1int
el2l1or
el1lo
ell1os2z
ell1ó1rá
el1ló
el2lön
el1lö
el2lös
el2l1űr
el1lű
1elmééh
el1mé
elmé1é
1elmél
1elmé1te
1elnép
el1né
1elnök
el1nö
el1obj
e1lo
el1off
el1oj
e2l1o2ká
el1okm
e2l1o2l
el1oml
el1o1mo
el1ont
el1opc
e2l1o2pe
el1o1ro
el1orr
el1os
e2los2z
e2l1ox
eló2ig
e1ló
eló1i
eló2in
e2l1ó2ri
el1öb
e1lö
el1ö2m
e2l1ön
e2l1ör
e2l1ös
e2l1ö2z
2előáp
e1lő
elő1á
2előár
2elő1bé
2elő1bi
2elő1bo
1e2lőde1i
elő1de
elő2d1í2
1e2lődj2e.
előd1je
2elő1do
1e2lődö2t.
elő1dö
előe2r
elő1e
1e2lő1fú
2elő1fü
2elő1fű
1e2lőhív
elő1hí
1e2lő1hű
2előib
elő1i
2előik
2előim
2előiv
2elő1ja
2elők2é.
elő1ké
2előkért
1e2lőké1se
1e2lőkést
2elő1kl
2elő1kő
2előkt
2elő1ku
2elő1kü
2elő1mö
2elő1mű
2elő1na
1e2lőnn
2elő1nö
1e2lőn2y.
előn2y
1e2lőnyb
2előnyer
elő1nye
1e2lőnyh
2elő1nyi
1e2lőnyk
1e2lőnyn
1e2lő1nyö
1e2lőnyr
1e2lőnyt
1e2lő1nyü
2elő1ö
el1ő2r.
1e2lőreg
elő1re
1e2lőreh
1e2lőrej
el1őriz
elő1ri
el1őrl
2elő1ro
e2l1ő1rü
2elő1sá
2elő1so
2elő1sö
elő1s2p
2elő1s1ze
elős2z2
2elő1s1zé
2elő1s1zi
2elő1s1zó
2elő1tü
2elő1ü
2elővis
elő1vi
2előviz
1e2lő2z.
e2lő1ze
2e3lőze1ne
1e2lőzm
el1p2l
el1p1ró
el1sk
el1sl
el1sm
el1sz2t2
els2z
el2t1aj
el1ta
eltára2d
el1tá
eltá1ra
eltá2r1a1da
2elt2e.
el1te
el2t1e2re1i
elte1re
2eltes
2elte2t.
2el1te1te
2elte1té
2elteth
2elte1ti
2eltetj
2eltetn
2eltets
1eltettk
2elte1tü
2eltetv
2eltéb
el1té
2elté2l
2eltét
el1t2ra
el1t1rá
el1t1ré
2eltük
el1tü
e2l1ud
e1lu
el1u2g
elu2n
e2l1und
el1u2r
e2l1u2t
e2l1uz
el1új
e1lú
el1ús
el1ú2t
el1üc
e1lü
e2l1üd
e2l1üg
elü2gy1é2r.
elü2g2y
elü1gyé
elü2gy1érn
elü2kén
elü1ké
e2l1ültet
elül1te
e2l1ür
e2l1üs
e2l1üt
e2l1üv
e2l1üz
el1űrt
e1lű
e2l1űz
elv1a1da
el1va
elv1a2dó
el2v1at
el2v1ára1i
el1vá
elvá1ra
el2v1á2ras
el2v1á2rár
elvá1rá
elv1ás2z
el2vát
el2v1enc
el1ve
el2v1é1gü
el1vé
elv1é1le
el2v1é2r.
el2v1é1ri
el2vik
el1vi
elv1olt
el1vo
el2v1ó2
el2vöd
el1vö
el2vő
el2vú
e2ly1a2
el2y
e2ly1á2
e2ly1e2ké2n.
e1lye
elye1ké
e2ly1el
e2lyer
ely1eszt
elyes2z
ely1é2jé
e1lyé
ely1é2ké
e2ly1é2l
e2lyés
e2ly1i2ko
e1lyi
e2ly1i1ra
ely2kéj
ely1ké
e2ly1o
e2ly1ó
e2lyöm
e1lyö
e2lyön
e2lyös
e2lyő
ely1ul
e1lyu
elyü2l
e1lyü
e2ly1ü1lé
e2m1ab
e1ma
em1a2dat
ema1da
em1a2dás
ema1dá
e2m1a2do
e2m1a2dó
e2m1adt
e2m1a2gi
em1a2ja
e2m1a1já
em1ajk
e2m1ajt
em1a2ka
em1a1ká
e2m1a1ku
em1a2lap
ema1la
e2m1all
em1al1má
e2m1alv
e2m1a1na
1e2ma1ná
e2m1a2n2y
ema2p
em1a1pá
em1apr
em1a2rán
ema1rá
em1as1s1zo
emas2s2z
e2m1atl
e2m1a2u
e2m1a2v
e2m1a2zo
e2m1áb
e1má
emá2l
em1á1la
em1áld
em1á1li
e2m1áp
emá2r
e2m1á1ra
e2m1árn
e2m1á1ro
e2m1á1ru
em1árv
e2m1á1sá
e2m1á2t1a2
e2m1á1te2
e2m1átl
em1átm
e2m1átt
e2m1á1tu
1embarg
em1ba
1embered
em1be
embe1re
1emberf
embe2r1ő2
1ember3s2
emb2len
emb1le
1emb1lé
em1b1re
1emb1ri
em1b1ro
em1dr
e2m1ef
e1me
eme3ger
eme1ge
eme3gi
em1egyet
emeg2y
eme1gye
em1e1gyé
e2m1egz
e2m1e2kés
eme1ké
e2m1e1la
e2m1elb
1e2melet
eme1le
1emel1ke
e2melk
e2m1el1lá
e2m1elm
2e2m1e1lo
1emelőb
eme1lő
1emelő1e
em1előn2y
1emelős
1emelőv
1e2melt2y
e2m1ember
emem1be
e2m1e2mel
eme1me
e2m1e1mu
e2me2ner
eme1ne
emenes1s
em2enes
e2m1e2p
e2m1e2rén2y
eme1ré
e2m1e2rő
eme2sa
e2m1e2se1té
eme1se
e2m1e2sés
eme1sé
e2m1e2szem
emes2z
eme1s1ze
e2m1e2szet
e2m1eszk
e2m1eszm
e2m1e1u
e2m1e2v
eme2z1a
eme2z1á2
eme2z1o
eme2z3s
e2m1ég
e1mé
e2m1é2h2e.
emé1he
e2m1é2hen
e2m1é2hes
e2méhs
e2m1é2j
emé2k
e2m1é1ke
em1ékr
em1é2let
emé1le
e2méne1ke
emé1ne
e2m1ép
e2mértel
emér1te
e2m1érté1ke
emér1té
e2m1érté1ké
e2m1értékn
e2m1észl
emés2z
emé2t1a2
emé2tár
emé1tá
e2m1é2tek
emé1te
emé2tel
e2m1éte2l.
emét1elh
emét1els
em1fl
emfoga2d
em1fo
emfo1ga
emfo2ga1da
em1f2r
em1gr
e2mi1dé
e1mi
e2m1i2dő
emi2g
e2m1i1ga
e2m1i1ge
e2m1i1gé
e2m1iks
emi2m
em1i1ma
e2mi1má
e2m1inf
e2m1ins
e2m1in1te
e2m1i2p
e2m1i1ra
e2mi1rá
e2m1i1ro
e2m1irt
e2m1isk
e2m1ism
e2m1is1te
emi2s1za
emis2z
emi2s1zá
emi2s1zo
em1i1zé
em1izg
e2m1i2zo
e2míg
e1mí
e2m1íj
e2mír
e2m1í2v
em1kl
em1kr
1emlékm
em1lé
1emlékv
emo2k
e1mo
e2m1okl
em1o1ko
e2m1ok1t
em1o1la
e2m1old
em1oll
e2m1olt
e2m1o2p
e2m1ork
e2m1or1s2
e2m1orv
emo1t2
emó2ra
e1mó
em1ó2rá
e2m1öb
e1mö
emö2k
e2m1ö1kö
emö2l
e2m1öld
em1ö1lé
e2m1ön
e2m1ö2r
e2m1ös
em1öv
e2m1ö2z
em1ő2r.
e1mő
em1őrk
em1őrn
em1ő2rö
em1őrr
em1őrt
1empát
em1pá
em1pc
em1p2re
em1p1ré
em1p1ro
em1p1ró
em1p2s
em1sk
em1sm
em1sp
em1s2t
em1t2r
1e2m2u.
e1mu
e2mud
e2mug
e2muj
1e2muk
1e2mulz
1e2mum
em1u1no
e2mur
e2mus2z
em1u2tal
emu1ta
e2m1u2tá
e2mutc
e2m1u1tó
e2m1új
e1mú
em1úr
e2m1út
e2m1üd
e1mü
e2m1üg
e2m1ü2lő
e2m1ünn
e2m1ür
e2m1üt
e2m1üv
e2m1üz
e2m1űz
e1mű
em1zr
e2n1ab
e1na
en1a2do
en1a1gi
e2n1a2j
e2n1a2k
en1a2la
en1alk
en1all
en1alm
e2n1a2n
en1a2pá
ena2p1e
en1a2rá
en1arc
en1as2z
en1atk
en1aut
ena1u
e2n1ábr
e1ná
en1ág
en1áld
en1álm
e2n1á1ra
en1árn
en1á2ro
e2n1á1ru
en1átk
en1átm
en1átv
e2n1áz
en1bl
en2c1a2
en2c1ár
en1cá
en2c3h
en2c1ip
en1ci
en2cí
en2c1ol
en1co
en2c1os
en2c1ö2
en2c3ség
enc2s
en1c1sé
en2c3sor
en1c1so
enc3s2p
enc3s2z
en2cu
en2d1a1dá
en1da
en2d1alk
en2da1no
en2d1áll
en1dá
en2d1árn
en2d1átl
end1é2jé
en1dé
en2d1é2r.
en2d1érr
en2d1ér1tő
en2d1érz
en2d1or
en1do
en2d1ón
en1dó
en2d1ó2r
en2dös
en1dö
en1d2rá
en2dú
en2d1za
end2z
ene1á2
e1ne
e2n1egér
ene1gé
e2n1egg
en1e2lek
ene1le
en1el1já
en1elk
e2n1ell
en1elm
en1eln2y
en1e1lü
en1el1vá
e2n1eml
ene1ó2
ene1p2
2ene1rá
1e2nerg
e2n1ern
e2nerv
2enes
ene1sz1tá
enes2z
ene2tal
ene1ta
ene2tos
ene1to
e2n1ex
ené2k1a
e1né
ené2ke2l
ené1ke
ené1ki2
ené2kis
e2n1é2pí
en1é1pü
e2n1é2r.
e2n1érd
e2n1é1ré
e2n1é1ri
e2n1ér1te
e2n1érv
ené2s1za
enés2z
ené2szer
ené1s1ze
ené2sz1in
ené1s1zi
e2n1étk
e2n1é2ves
ené1ve
ené2vi2g
ené1vi
en1f2l
enflu1o2
enf1lu
en1ga2
en2gan
1enge1dé
en1ge
enge2r1ő2
engés3s
en1gé
eng1g
e2ni1dé
e1ni
enidi2o
eni1di
e2n1i2ga
e2n1i1ge
e2n1i1gé
en1ill
e2ni1má
e2n1i1na
e2n1ind
e2n1inf
e2n1in1te
e2n1inv
e2n1i1ra
e2n1i2rá
en1isk
e2n1ism
eni2s1za
enis2z
eni2szer
eni1s1ze
eni2s1zo
eni2s1z1ó2
e2n1ív
e1ní
en1k2j
en2n1e1me
en1ne
en2nes
enn1é1ge
en1né
enné2k
en2n1é1ke
en2nér
en2nir
en1ni
en2n1ol
en1no
en2nú
en2n1ü2l
en1nü
enny1a2d
en2n2y
en1nya
enny1as
en1ny1á
en3nyer
en1nye
en1ny1í2
en3nyu
e2n1ob
e1no
e2n1of
e2n1o1i
en1old
e2n1olv
eno2ni
en1opt
eno2r1á2
e2n1ost
en1oszt
enos2z
e2n1ox
enó1ta2
e1nó
enó2tal
enó2t1e2
e2n1öb
e1nö
e2n1öl
en1ön
e2n1ös
e2n1ö2t
e3növ
e2n1ő2rü
e1nő
en1pr
en1ry
en2s1ab
en1sa
en2s1a2l
en2s1a2n
en2sas
en2s1el
en1se
en2s1ér1té
en1sé
ens3s1zá
ens2s2z
en2s3zon
ens2z
en1s1zo
en2t1a2c
en1ta
en2ta1da
entad2
ent1ag2y
enta1k2
en2t1a2la
ent1alj
en2t1alk
ent1a2lo
ent1and
en2t1a2n2y
ent1ass
en2t1á2rak
en1tá
entá1ra
en2t1á2rat
en2t1á1rá
en2t1á2rú
en2t1el1mé
en1te
ente2r1a
en2t1es1te
en2t1es1té
en2t1es1ti
ente1t2r
en2t1é2g
en1té
en2tép
en2térm
en2t1é2v2e.
enté1ve
en2ti1gé
en1ti
en2t1i2o
enti2p
ent1i1pa
enti2s2z
en2t1okt
en1to
en2tön
en1tö
en1trad
ent1ra
ent2ran
en2tu2n
en1tu
en2t1u2r
en2t1ü2z
en1tü
en1u1ta
e1nu
en1úr
e1nú
en1út
e2n1űz
e1nű
e2nyab
en2y
e1nya
e2ny1a2d
e2ny1a1e
enya2g
eny1a1ga
eny1a1gá
e2ny1aj
eny1alk
e2ny1a2n
eny1a1ré
e2ny1as
e2ny1at
e2ny1a1u
eny1d2
e2ny1e2d1zé
e1nye
enyed2z
e2nyelm
eny1előn
enye1lő
eny1el1vo
eny1el1vű
e2ny1e2rő
e2ny1e1ve
e2nyé1va
e1nyé
1enyh2i.
eny1hi
eny2h1ős
eny1hő
e2ny1id
e1nyi
e2nying
e2ny1i1ra
e2nyiz
eny1í2ró
e1nyí
e2nyok
e1nyo
e2ny1o2l
e2ny1or
e2ny1os
e2ny1ó2
enyö2k
e1nyö
e2ny1ö1kö
e2nyöt
eny1s
enyt2
eny1tr
eny1út
e1nyú
eny1va2
eny2van
1enyv2e.
eny1ve
eny2v1e2l
enyves1s
en2zal
en1za
1enzim
en1zi
e1o
eo2áz
eo1á
e2o1bo
e2o1de
eo2dú
e2og2ra1fi
eog1ra
e2o1ka
eo2kád
eo1ká
e2okár
e2o1ké
e2o1k2l
e2okon
eo1ko
eo2kos
eo2kö
eo2laj
eo1la
e2o1ló
eo2l2y
e2o1me
e2o1mé
e2o1mi
eo2n1al
eo1na
eo2nan
eon1an2y
eo2natom
eona1to
eo2n1a1u
eo2n1ál
eo1ná
eo2nár
eon1d2
eo2ner
eo1ne
eon1f2
eo2niz
eo1ni
eo2nö
eon1t2r
eo2n1ú
eo2nü
e2o1pa
eo2pe
e2o1p2l
e2o1p1ro
eo2r1a2
eo2r1á2
eo2re2s
eo1re
eo2r1és
eo1ré
eorgi2a
eor1gi
e2orgi2áb
eorgi1á
eori2tá
eo1ri
eor1k2
eo2rö
eor1s2
eo2so
e2ost
e2o1s1za
eos2z
eo1sz2f
e2o1tí
e2o1to
eo1t2r
eo2vi
e1ó
eóa2d
eó1a
e2ó1bö
eó2ce
eó2dá
e2ó1fa
e2ó1he
e2ó1je
e2ókap
eó1ka
e2ókép
eó1ké
e2ókor
eó1ko
e2ó1mi
e2ó1mű
e2ó1né
eó2no
eó1pr
eó2rá
e2ó1re
e2ó1su
e2ó1s1zo
eós2z
e2ó1ta
e2ó1tá
e2ó1te
e2ó1té
e2ó1ti
e2ó1tí
eó1t1ré
eó2vak
eó1va
e2ó1ve
e2ó1vé
e2ó1ví
eó2vo
eó2vó
e1ö
eö2bö
eö2dé
eö2kö
eö2le
eö2lé
eö2li
eö2lö
eö2lő
eö2lü
eö2mö
eö2nö
eö2rö
eö2rü
e2ö2s.
eö2ve
eö2vi
eö2vü
eö2zö
e1ő
eő2re
eő2rö
eő2rü
eő2s2z
e2p1ab
e1pa
e2p1a2da
e2p1a2dá
e2p1a2dó
ep1a2g2y
e2p1a1ka
e2p1akk
e2p1akn
ep1a2lak
epa1la
ep1a2lap
e2p1alj
e2p1alk
epa2lom
epa1lo
e2p1a2n2y
epa2rán
epa1rá
ep1at1ró
ep1aut
epa1u
ep1a2zo
e2p1áb
e1pá
e2p1á2g
ep1ál1lá
e2p1á2ra
epá2s
e2p1á1sa
ep1á2t1a2
ep1átc
e2p1á2t1é
ep1átf
e2p1átm
ep1á2t1o
ep1átt
ep1á2tü
e2p1átv
ep1bl
ep1br
ep1dr
e2p1e2d2z
e1pe
e2p1e2lemr
epe1le
e2p1e2le2t
e2p1el1lá
e2p1e1lo
e2p1e1ne
epe2ral
epe1ra
epe2r1e2c
epe1re
epe2rev
epe2rin
epe1ri
epe2rü2l
epe1rü
e2p1es1ti
epe2s1zá
epes2z
e2pesz1mé
e2p1ex
1e2péd
e1pé
1e2pééb
epé1é
1e2péé1i
1e2péén
e2p1ég
1e2péit
epé1i
e2péj
ep1ékh
ep1é1le
e2p1élm
1e2pénk
e2p1é2r.
ep1fl
ep1i1do
e1pi
e2p1i2ko
e2p1ind
e2p1in1ga
e2p1i2rá
e2p1irt
e2p1ism
e2piz
e2p1íg
e1pí
e2p1íj
e2p1ín
e2p1ív
ep1kl
ep1kr
ep2lag
ep1la
e1p2la1ká
e1p2lan
e1p2lán
ep1lá
e2p1ob
e1po
e2poc2h
ep1okt
e2p1olv
e2p1o2pe
e2p1orn
ep1or1só
1epos2z.
epos2z
1epo1s1za
1epo1s1zá
1eposz2t.
ep1osz1tá
e2p1ö2l
e1pö
e2p1önt
e2p1ös
e2p1öv
e2p1ő1ri
e1pő
ep2pan
ep1pa
ep2pát
ep1pá
ep2p1e2le
ep1pe
ep2p1elh
epp1e2ró
ep1pó2
ep2pór
ep2pö
ep1p1ro
ep1p1ró
e2p3ret
ep1re
e1p2réd
ep1ré
e1p2rin
ep1ri
e1p2roj
ep1ro
e1p2rot
ep1s2k
ep1sp
ep1st
ep2ta2d
ep1ta
ep2t1aj
ep2t1í2v
ep1tí
ep2t1op
ep1to
e2p1ug
e1pu
e2p1u2ta
e2p1u1tó
ep1új
e1pú
ep1út
e2p1üg
e1pü
e2p1üt
e2p1üz
e2p1űz
e1pű
e2r1ab1la
e1ra
er1a2bor
era1bo
e2r1abr
er1abs
era2dat
era1da
e2r1a2dá
e2r1adm
er1a2do
e2r1a2dó
era1dr
er1a2ge
er1agr
e2r1a2ja
e2r1ajk
er1aj1tó
e2r1a2kad
era1ka
e2r1a2kas
e2r1akc
e2r1ak1ti
er1a2la
e2r1alg
e2r1alj
e2r1alk
e2r1a2lo
er1als
e2r1alt
er1alv
er1am1b
er1amp
er1ang2y
er1ann
er1a2nya
eran2y
e2r1a2p2a.
era1pa
e2r1app
er1aps
e2r1a2ro
e2r1asp
era2sz1a2l
eras2z
era1s1za
er2a2szav
era2szárn
era1s1zá
er2a2szel
era1s1ze
era2sz1é2p
era1s1zé
e2r1atk
e2r1atl
era1t2r
er1att
er1aut
era1u
e2r1a2zo
e2r1ábr
e1rá
erá2fé
er1á1ge
er1á2gú
e2r1á2g2y
er1ál1lá
er1ál1lé
er1ál1lo
er1ál1ló
er1állv
e2r1á1po
e2r1á2r.
er1á2rak
erá1ra
e1r1á2rá
e2r1árb
er1á1re
er1á1ré
er1árf
er1árk
e2r1á2ro
e2r1árr
er1árs
e2r1á2ru
e2r1á1rú
er1árv
er1á1sá
e2r1á2s2z
er1á2t1e2
e2r1áth
er1á2ti
e2r1á2tí
e2r1átj
e2r1átk
er1átl
e2r1átm
e2r1átn
e2r1átr
e2r1átt
e2r1átv
erb1i2na
er1bi
er2c1a2l
er1ca
er2car
er2c1ár
er1cá
er2c1át
erc1ell
er1ce
er2c3ho
erc2h
er2ci2d
er1ci
er2c1i1na
er2c1i1ná
er2c1i2pá
er2cis
erc1k2
er1co2
er2cö
er2csad
erc2s
er1c1sa
er2cs1an
ercs1ál
er1c1sá
er2cú
er2c2z
1erdej
er1de
1er1dő
ere1á2
e1re
1e2rede1tű
ere1de
e2redén
ere1dé
1e2redm
ere1e2
e2r1eff
ere2gál
ere1gá
ere2gel
ere1ge
e2r1e2ger
erei2g
ere1i
1e2rején
ere1jé
1e2rejér
ere2k1el
ere1ke
erek1e2s1zű
erekes2z
ere2k1é2j
ere1ké
ere2kot
ere1ko
erek1t
ere2k1ú2s
ere1kú
e2r1e2leg
ere1le
e2r1elér
ere1lé
e2r1ellen
erel1le
er1el1li
e2r1e1lö
e2r1elr
erem1eg2y
ere1me
ere2m1emb
erem1ér1té
ere1mé
ere2m1ut
ere1mu
e2r1enz
ereo1g1ra
ere1o
ere2pan
ere1pa
ere2pas
ere1pá2
ere2p1ál
ere2p1e2sé
ere1pe
ere2pin
ere1pi
ere2pos
ere1po
er1erk
er1ern
er1e1ró
e2r1es1ti
e2r1estj
e2r1estr
e2resz2e.
eres2z
ere1s1ze
ere2s1zí
e2re1s1zü
ere2tál
ere1tá
ere2tát
ere2t1eg
ere1te
ere2t1erj
ere2t1é2r2ő.
ere1té
ereté1rő
ere2t1é2v2e.
ereté1ve
ere1tö2
er1e2vés
ere1vé
e2r1ex
1e2reze2t.
ere1ze
1e2reze1te
1e2rezőkh
ere1ző
eré1be2
e1ré
eré2bes
er1é2g.
er1é2ge
er1é1gé
e2r1é2j.
e2r1éjb
er1éjf
e2r1éjn
e2r1éjs
eré2k1a2
eré2ká
eré2kol
eré1ko
eré2k1ö
e2réne1ke
eré1ne
e2r1é2ne1ké
eré2p1a
eré2pá
e2r1é2re2n
eré1re
e2r1é2ré
e2r1é2ri
eré2sa2
eré2s1elv
eré1se
eré2s1za
erés2z
er2é2s1zá
eré2s1zo
er2é2s1zö
e2r1é2ter
eré1te
e2r1étk
e2r1é2v.
e2r1é2v2e.
eré1ve
e2r1é2vek
e2réven
e2r1é2ves
e2r1é2vet
e2r1é2vén
eré1vé
e2r1é2vé2t
e2r1évf
e2r1évh
e2r1é2vi
e2r1évn
e2r1évr
e2r1évt
e2r1évv
erfé2l1é2v
er1fé
erfé1lé
er1fl
er1f2r
er1gl
e2r1i2deg
e1ri
eri1de
e2ri1dé
e2r1i1do
er1i2du
eri2ga
e2r1i2gá
e2r1i2kon
eri1ko
e2r1i2mi
eri2no
erint2
e2rinteg
erin1te
erin1tr
e2r1i2on
eri1o
eri2os
e2r1i2par
eri1pa
e2r1i1ra
er1i1ró
e2r1isk
er1ism
eri2s1zo
eris2z
e2r1i1ta
e2r1i2zé
e2r1izg
er1íg
e1rí
e2r1í2j.
e2r1í1já
e2r1ír
e2r1í2z
er1k2r
er1k2v
erme2k1a2
er1me
erme2ká2
erme2ke2s2z
erme1ke
erme2ko
erme2köl
erme1kö
erme2s1z1á
ermes2z
er2mind
er1mi
erm1i2si
ern1a1la
er1na
ern1ékn
er1né
ern3n
er2n1ó2d2
er1nó
1er1nyő
ern2y
er1okl
e1ro
e2r1okm
er1ol1da
e2r1o2li
ero2nal
ero1na
e2r1o1pe
e2r1opt
er1orc
er1ord
er1orm
er1orn
e1r1o2ro
er1ors
e2r1orv
erosz2f
eros2z
e2r1o2ve
e2r1o1vi
e2r1óc
e1ró
er1ó2dá
er1ó1lo
er1ó2rá
er1ó1ri
e2r1ö2c
e1rö
erö2k
er1ökl
er1ö2ko
e2r1ö1kö
e2r1ö2l
e2r1ör
e2r1ös
e2r1öt
e2r1ö2z
e2rődd
e1rő
erőé2n
erő1é
1e2rőlt
1e2rő1mé
1erőmh
1e2rő1nö
1e2rő1ö
e2r1ő1ri
er1ő2rö
er1p2l
er1p2s
er2rév
er1ré
er1ry
er2s1a2d
er1sa
ers1alk
er2s1an
er2sat
er2s1á2gi
er1sá
ers1á1ra
ers1eml
er1se
er2sér1te
er1sé
er2s1ér1té
er2s1ér1tő
er2si2d
er1si
er2s1im
er2s1i2n
er1s1ká
er2s1od
er1so
er2s1ol
er2s1ón
er1só
er1spor
ers1po
er1s1rá
er1st1ra
erst2r
er2su2t
er1su
ersz2to
ers2z
er2t1a1i
er1ta
ertá2p
er1tá
ert1á1po
ertára2d
ertá1ra
ertá2r1a1da
er2t1el1ké
er1te
ert1estj
ert1e1s1ze
ertes2z
er2t1é2j
er1té
er2t1ékn
er2t1évén
erté1vé
er2t1é2vév
er2tid
er1ti
er2t1i2m
er2t1í2z
er1tí
er2tos
er1to
ert1ó1rá
er1tó
er2t1ös
er1tö
er2t1öz
er1t2ran
ert1ra
er1trén
ert1ré
ert1s
er2t1út
er1tú
eru1bi2
e1ru
1e2rup
e2r1u2ra
er1u1rá
e1r1u1ru
er1u2s2z
er1u2tá
e2r1új
e1rú
e2r1úr
e2r1ú2s
er1útj
er1útl
er1ú2to2
er1útr
e2r1üd
e1rü
e2r1ügg
er1ügyb
erüg2y
e2r1ügyn
er1ügyr
e2r1ü2led
erü1le
e2r1ür
e2r1üs
e2r1üt
e2r1ü2v
e2r1üz
e2r1ű2z
e1rű
er2v1a1la
er1va
er2v1alt
erva2n
erv1an2y
er2v1á2ru
er1vá
er2vá1sa
er2v1átk
erv1e2lő1ké
er1ve
erve1lő
er2vere1ze
erve1re
er2v1e2s1ze
erves2z
er2vék
er1vé
er2vér1te
er2v1érz
er2vos
er1vo
er2vös
er1vö
er2vú
er1ya
2es2a.
e1sa
e2s1a2b
e2s1a2d
2esait
esa1i
e2s1al1ja
e2s1alm
esa2n
es1an2y
esa2p
es1a1pá
es1arc
es1ass
es1as2z
e2s1a2t2y
e2s1a1u
2esá1bó
e1sá
e2s1á2g
2esán
es1á1ra
es1á2ru
2esát
es1bl
es1br
es2c2h
1es1dő
es1d1ró
1e2sedez
e1se
ese1de
ese1fr
ese2gye
eseg2y
e2s1elm
e2seng
e2s1ep1ri
e2s1erd
eseren2
ese1re
1e2set1tü
2es2é.
e1sé
esé2g1el
esé1ge
1e2séll
1e2sél2y
e2s1ép
e2s1érc
esés3s
e2s1i2d
e1si
esike2t1
esi1ke
e2s1i1na
es1i2pa
e2s1isk
1esítőst
e1sí
esí1tő
es1í2zű
e1s2kat2
es1ka
e1s2kál
es1ká
es2kic
es1ki
1es1kü
es2lat
es1la
eslege2l
es1le
esle1ge
esle2t1o
es2lin
es1li
e1s2lu
e1s2mac
es1ma
es1ná2
2eso2k.
e1so
2eso1ka
2esok1bó
2esokk
2esokr
2eson
e2s1op
2eso2s.
2eso1sa
e2s1os2z
es1ott
e2s1ó2r
e1só
eső1ká2
e1ső
e2sőz
es2pan
es1pa
es2pec
es1pe
es1p2l
e1s2pó
e1s2rá
es1s1tá
es3szab
es2s2z
es1s1za
essz1a1ga
essz1a2r
1es2szenc
es1s1ze
es3s2ze1rű
essz1élet
es1s1zé
esszé1le
ess2z1élt
essz1int
es1s1zi
essz1ok
es1s1zo
es3s1zö
e1s2tab
es1ta
es2taf
es2t1a2l
es2ta1na
es2t1a2n2y
estapolc1
esta1po
es2t1a2ra
est1a1rá
e1s2tat
es2taz
es2t1á2p
es1tá
est1áram
está1ra
es2t1á1ri
es2t1árn
es2t1át
1este1ko
es1te
es2tenz
este2r1a
es2t1es1te
es2t1es1ti
1estéih
es1té
esté1i
1estéj
1estém
1esténk
es2t1é2r.
es2t1é1ri
es2t1érr
es2tér1te
es2t1é2rü
es2t1ill
es1ti
es2t1i1na
es2t1int
es2tip
es2t1i2s2z
es2tiz
es2t1ol
es1to
e1s2top
esto2r
es2t1os
es2t1ó2r
es1tó
es1tö2
es2t1ös
es2t1ő2r.
es1tő
es2t1őrk
es2t1ő1rö
es2t1őrr
es2t1őrt
es2tun
es1tu
es2tú
es2tür
es1tü
es2t1ü2z
e2s1ud
e1su
esu2r
e2s1u1ra
es1u1rá
e2s1u1tá
e2s1ú2r.
e1sú
e2s1úrb
e2s1ú1ré
e2s1úrh
e2s1ú1ri
e2s1úrk
e2s1úrn
e2s1úrp
e2s1úrr
e2s1úrt
e2s1ú2t
e2s1üz
e1sü
e2sza2c
es2z
e1s1za
esz1a1cé
e2szad
e2sz1a2e
esz1ajt
esza2k1é
esz1akn
e2sz1alj
esz1an2y
e2sz1a1ra
e2sz1a1u
e2szá1ru
e1s1zá
e2sz1ás
eszá2t
e2s2z1áts
e2s2ze1ce
e1s1ze
eszeg1ér
esze1gé
e2sz1eg2y
e2sz1ekés
esze1ké
e2sz1e1la
e2sz1e2mel
esze1me
1e2szenc
e2sz1er1d2
e2szev
e2sz1ex
eszé2do
e1s1zé
esz1é2pí
e2sz1é2ri
esz1g2
esz1i2pa
e1s1zi
esz1isk
esz1ist2
e2sz1i1ta
e2sz1iz
eszke2l
esz1ke
1eszm2e.
esz1me
1eszméb
esz1mé
1eszmé1i
1eszméj
1eszmék
1eszmén
1eszmét
1eszmév
e2sz1old
e1s1zo
eszö2l
e1s1zö
esz1ö1lő
e2sz1ön
es3z1sá
esz2s
es1z3se
esz2tab
esz1ta
esz2tad
esz2t1a2gá
esz2taj
esz2t1a1la
esz2t1alj
esz2t1ap
esz2t1árf
esz1tá
esz2t1árn
esz2t1e2v
esz1te
esz2t1é2r.
esz1té
esztés3s
1eszté1ti
esz2tid
esz1ti
esz2t1ol
esz1to
esz2t1ó2r
esz1tó
esz1tö2
esz2t1ö2l
esz2t1ő2r.
esz1tő
esz2tut
esz1tu
esz2t1út
esz1tú
esz2tüz
esz1tü
e2sz1ü2g
e1s1zü
e2sz1ü2z
1e2szűs
e1s1zű
1e2szűt
esz1z
et1abr
e1ta
eta2c
et1a1cé
e2t1a2d
e2t1a2gá
e2t1a2g2y
e2t1a2j
et1a2kas
eta1ka
e2t1akc
et1a1kó
e2t1a1ku
e2ta2la2g1
eta1la
eta2lak
et1a2lás
eta1lá
e2t1alb
et1ald
et1alf
e2t1alg
et1alj
e2t1alk
1e2ta1lo
eta2n1é
e2ta2nyag
etan2y
eta1nya
e2tapr
et1a2ra
e2t1a1rá
e2t1arc
e2t1arz
et1asp
e2t1ass
et1a2s1zá
etas2z
e2t1atk
e2t1a2to
e2t1at2y
e2t1a2u
e2t1a2z
e2t1ábr
e1tá
e2t1á2c2s
e2t1áf
e2t1á2g.
e2t1á2ga
e2t1ágb
e2t1ágg
e2t1ágn
e2t1ágr
e2t1áh
et1á1ju
et1áll
e2t1álm
e2t1á2rad
etá1ra
et1á2r1e2
et1árn2y
etá2ron
etá1ro
et1á2rú
e2t1á2ta
e2t1át1a2d
e2t1áth
et1átl
et1áts
et1á1tu
e2t1átv
et1bl
et1br
et2c2h
et1dr
ete1a2
e1te
ete2g1á
ete2g1e2l
ete1ge
ete2gó
e2t1e2k2e.
ete1ke
e2t1e2ke1i
e2t1e2kek
ete2k1ék
ete1ké
e2t1e2kénk
e2t1e2kés
e2t1elc
et1el1do
ete2le1ge
ete1le
ete2leg2y
e2t1e2lej
e2telemz
e2t1e2le1sé
e2t1elé1ré
ete1lé
etelés1s
e2t1el1ha
e2t1el1há
et1elhel
etel1he
e2t1el1i1ga
ete1li
e2t1el1já
e2t1el1lá
e2tellen
etel1le
e2t1el1me
e2t1elmé2n.
etel1mé
e2t1elmé1ne
e2t1elmét
e2t1elnev
etel1ne
e2tel1nö
e2t1eln2y
e2t1e2lo
ete2lőad
ete1lő
etelő1a
et1el1s1zá
etels2z
e2t1el1tá
e2t1elter
etel1te
et1elté1rí
etel1té
e2t1el1vá
e2t1elvez
etel1ve
e2t1el1vo
e2t1elz
ete2mal
ete1ma
e2t1ember
etem1be
ete2mel
ete1me
e2t1enz
ete1p2
e2t1er1d2
ete2rén
ete1ré
ete1ro1
e2t1e2rő
ete2s1a
e2t1est2e.
etes1te
e2t1esz1té
etes2z
ete2te1té
ete1te
ete2t1é2r.
ete1té
1e2tetése2n
eteté1se
1e2tetésn
ete2tos
ete1to
ete2t1ö
ete1t1ra
e2t1ezr
eté1é2
e1té
e2t1é2g.
e2t1égb
e2t1é2gé
e2t1égg
e2t1é2gi
e2t1égn
eté2k1a2
eté2k1á2
eté2k1e2l
eté1ke
et1é2kí2
eté2kos
eté1ko
e2t1élm
e2t1érc
e2t1é2r2é.
eté1ré
e2t1é2rén
e2t1é2rér
e2t1é2rét
eté2ri1e
eté1ri
e2t1ér1ke
e2t1érm
e2t1é2rős
eté1rő
e2t1értel
etér1te
e2t1érz
eté2sa2
eté2s1ég
eté1sé
eté2so
etés3s
eté2s1za
etés2z
et1észl
et1észr
eté2t1a2
eté2te2r.
eté1te
eté2te1re
eté1t1ra
e2t1é2ven
eté1ve
e2té2ve2s.
e2t1é2vet
e2t1é2véh
eté1vé
eté2véig
etévé1i
e2t1é2vé1ne
eté2vé1tő
e2t1évh
e2t1évt
et1fr
et1gl
et1gr
et2he1i
et1he
eti2d
e1ti
et1i1de
et1i1do
eti2g
e2t1i1ge
e2t1i1gé
e2t1ig2y
e2till
et1i2ma
e2t1i1má
e2t1i2mi
1e2ti1mo
e2t1imp
eti1na1
e2t1ind
e2t1inf
e2t1ins
e2t1in1te
e2t1inv
e2tinz
e2t1i2pa
e2t1i2ra
e2t1i1ri
e2t1i1ro
e2t1i1ró
e2t1ism
e2t1is1te
e2t1i2s1za
etis2z
e2t1i2szon
eti1s1zo
e2t1i2ta
et1i1zé
e2t1izg
e2t1i1zo
e2t1izz
e2t1íg
e1tí
e2t1íj
e2t1ín
e2t1í2r
e2t1í2v
e2t1íz
et1kl
et1k2r
et1kv
1etnol
et1no
et1o1da
e1to
e2t1okm
e2t1okt
e2t1oml
eto2n1a2
eto2nál
eto1ná
eto2n1is
eto1ni
eton1n
e2t1opc
e2t1o2pe
e2t1op1t2
2etor
et1ord2
e2t1or1g2
e2t1orm
et1orom
eto1ro
e2t1ors2
e2t1orv
et1ost
etosz2f
etos2z
et1oszl
et1oszt
e2t1o1u
e2t1ó2c
e1tó
et1ó2ra
et1ó2rá
e2t1ó2v
e2t1ö2ko
e1tö
etö2l
e2t1ö2l.
et1ö1lé
e2t1ö1lő
e2t1ön
e2t1ös
e2t1öt
et1ö2vü
e2t1ö2z
ető1a2
e1tő
etőe2l
ető1e
etőé2b
ető1é
etőfé2l1é2v
ető1fé
etőfé1lé
e2t1ő2r.
e2t1őrb
et1őrc
et1ő2réh
ető1ré
ető2r2i.
ető1ri
et1őrk
e2t1őrl
e2t1őrn
ető2rök
ető1rö
e2t1őrp
e2t1őrr
e2t1őrs
et1ő2rü
ető1s2p
et1pl
et1pr
et1ps
e1trap
et1ra
e1tra1u
e1t2rág
et1rá
e1tréf
et1ré
e1t2ril
et1ri
et1sk
et1sn
et1sp
et1st
et3tad2
et1ta
etta1i2
etta2n1ó2
et2telem
et1te
ette1le
et2t1ing
et1ti
et2tír
et1tí
et1t2rá
et1t1ri
et1ty1á2
et2t2y
e2t1ug
e1tu
et1u1na
et1und
et1u2ra
etu2s2z
e2t1u1tá
et1u1tó
e1t1u1tu
e2t1új
e1tú
e2t1ú2ri
e2t1út
e2t1üd
e1tü
e2t1üg
e2t1üld
e2t1üt
e2t1ü2v
et1ü2zem
etü1ze
e1t3ya
et2y
e1u
eu2bo
eu2ga
eu1k2h
eu2mal
eu1ma
eu2m1e
eu2mim
eu1mi
eu2m1i2p
eu2mis
eu2m1iz
eu2mí
eu2mór
eu1mó
eu2mő
eum1p2
eu2mü
eu2na
eu2ná
eu2ni
eu2no
eu2nó
e2u2r.
eu2rá
eur1áz
eu2r2i.
eu1ri
eu2rig
e2urt
eu2s1zí
eus2z
e2uta1i
eu1ta
eu2tal
e2utan
eu2taz
e2utá1i
eu1tá
e2utá1já
e2utá1ka
e2utákk
e2uták1ná
e2utákr
e2utánk
e2utár
eu2tó
e2uts
eu2z2s
e1ú
eú2jí
eú2s2z
eú2ti
eú2to
e1ü
eü2dü
eü2ge
eü2g2y
eü2le
eü2lé
eü2li
eü2lö
eü2lő
eü2lü
eü2re
eü2rí
eü2rü
eü2s2z
eü2te
eü2tő
eü2tü
eü2ve
eü2vö
eü2ze
e1ű
eű2ri
eű2ze
eű2zé
eű2zi
eű2zö
eű2ző
evá2r1a2l
e1vá
evá1ra
eve2s1zö
e1ve
eves2z
evé2lá
e1vé
evé2l1e2l
evé1le
evé2nye2l
evén2y
evé1nye
evé2r1emb
evé1re
evé2rö
evé2so
evé2s1za
evés2z
ev2é2s1zö
evízi2óét
e1ví
eví1zi
evízi1ó
evízió1é
ex1ab
e1xa
ex1al
ex1ap
ex1áb
e1xá
ex1á2r
e2x1át
ex1bl
ex1br
ex1dr
e2xeg
e1xe
e2x1elm
e2x1el1vá
e2x1er
e2x1ék
e1xé
e2x1él
e2x1ép
e2x1i2dő
e1xi
e2x1i1gé
ex1inf
e2x1ing
e2x1int
ex1izz
e2x1íj
e1xí
e2x1ír
e2x1ob
e1xo
ex1op
ex1ön
e1xö
ex1ör
ex1ös
1expan
ex1pa
ex1sk
ex1sp
ex1st
ex1új
e1xú
e2x1üg
e1xü
e2x1üv
e2x1üz
eza2c
e1za
ez1a1cé
e2z1a2d
e2z1af
ez1ajt
e2z1a2l
e2z1a2n
e2z1arz
e2z1as
ez1aut
eza1u
ez1áll
e1zá
ezá2ma
ez1árb
ez1árr
ez1á2rú
e2z1át
ez1bl
ez2dál
ez1dá
ez1e2g2y
e1ze
e2z1e2kék
eze1ké
e2z1e1la
e2z1e2le1me
eze1le
e2z1elér
eze1lé
e2z1elm
e2z1e1l1ö
e2z1e2mel
eze1me
ezen2t1e2
eze2r1a
eze2rá
eze2red
eze1re
eze2r1el
eze2r1em
eze2r1es
eze2r1o
e2z1e2rő
e2z1es1te
e2z1e2s2z
eze2t1a2
eze2t1á
eze2t1eg
eze1te
eze2t1e2l
eze2t1es2z
eze2t1é2r.
eze1té
eze2t1é2r2ő.
ezeté1rő
eze2t1é2v2e.
ezeté1ve
eze2t1o
eze2tu
e2z1ég
e1zé
e2z1é2j
ezé2ká
ezé2k1o
e2z1él
ezé2r1emb
ezé1re
e2zé2rett
ezé2ru
ezé2sa
ezéskés2z1
ezés1ké
e2z1é2te
ez1fr
ez1gr
ez1i2do
e1zi
ezi2g
e2z1i1gé
e2z1i2ko
e2z1ill
ez1imp
ez1i1ná
ez1ind
ez1inf
ez1int
ezi2o
ez1ion
e2z1i2p
ez1i2r
ezisé2g
ezi1sé
ezi2s1é1gé
e2z1ism
ezi2ta
e2z1í2v
e1zí
ez1k2r
ezkupac1
ez1ku
ezku1pa
ez1kv
e2z1ob
e1zo
ez1old
ezo2nár
ezo1ná
ezon3n
ez1opt
e2z1ox
e2z1ó2l
e1zó
e2z1ó2r
ez1ó2t
ez1ö2b
e1zö
ez1ös
ez1ö2v
ezőe2l
e1ző
ező1e
e2z1ő2ri
e2z1őrl
e2z1őrs
e2z1ő2rü
e2z1őrz
ezős1or1ra
ező1so
ez1pl
ez1p2r
1ezre1de
ez1re
1ezreds
1ezrel
1ezrem
1ezresn
1ez1rű
ez3saj
ez2s
e1z1sa
ez3sap
ez3sát
e1z1sá
ez3sáv
e2z3sé
ezsi1ó2
e1z1si
ez3sl
e2z3sor
e1z1so
ez3s2p
ez3s2ta
ez3st2r
e2z3sü
ez3s2z
ez1t2r
ez1u2r
e1zu
ez1ut
ez1új
e1zú
ez1ú2t
e2z1üg
e1zü
1e2züs
e2z1üt
e2z1ü2z
2é.
é1a
éa2da
éa2dá
éa2do
éa2dó
éa2ga
éa2gi
éa2já
éa2ka
éa2la
éa2l1e
éa2na
éa2n2y
éa2ré
éa2ri
éa2ro
éa2uk
éa1u
é1á
éá2fá
éá2g2y
éá2ju
éá2ra
éá2ro
éá2ru
éá2rú
é2b1ag
é1ba
é2b1a2j
é2b1a2k
é2b1a2l
éba2n
é2b1an2y
é2b1a2v
éb1ál
é1bá
ébá2r
éb2b1á
éb1e1s1zű
é1be
ébes2z
é2b1é2k
é1bé
é2b1él
é2b1ép
ébi1é2
é1bi
éb1isk
éb1i2va
éb1íz
é1bí
éb1kr
éb1pl
éb1pr
1ébres
éb1re
é2b1ug
é1bu
éb1üg
é1bü
éc1a2d
é1ca
éc1aj
éc1a2k
éc1a2l
éc1a2n
éc1ál
é1cá
éc1ár
é2c1e2lem
é1ce
éce1le
é2c1elv
é2c1ember
écem1be
é2c1e2mel
éce1me
éc1gr
é1c3ha
éc2h
é1c3há
é1c3hí
é1c3ho
é2c1i2d
é1ci
é2c1il
éc1i1ma
éc1ob
é1co
éc1os
éc1őr
é1cő
éc1pr
éc3sab
éc2s
é1c1sa
écs1ol
é1c1so
éc3s2z
é2c1u2t
é1cu
é2c1ül
é1cü
éc3z2s
éc2z
é2d1ab
é1da
é2d1a2c
é2d1a2d
é2d1a2g
é2d1a2j
éd1akc
éd1akt
éd1a2ku
éd1alk
é2d1a2n
éd1a1pa
éd1a2pá
é2d1arc
éd1asp
éd1ass
éd1a2ti
éd1at1t2
é2d1ág
é1dá
éd1áp
éd1dr
é2d1e2g
é1de
é2d1ej
é2d1e2k2e.
éde1ke
éde2ké1tő
éde1ké
é2d1e2l1a
é2d1elk
é2d1ell
é2d1e1lo
éd1ember
édem1be
é2d1eml
é2d1enz
é2d1ep
éd1erd
é2dere1i
éde1re
é2derem
é2derg
é2derl
é2der1né
é2d1e2rő
é2der1rá
é2der1rő
é2ders
é2der1tő
1é2de2s1a2
é2desem
éde1se
1é2desg
1é2de1sí
é2d1ég
é1dé
é2d1é2j
é2d1ékb
é2d1é2ké
é2d1ékk
édé2l
é2d1é1le
é2d1élm
é2d1ép
é2d1é2r.
é2d1é2ri
é2d1érs
é2d1ér1te
é2d1ér1tő
é2d1érv
é2d1érz
éd1fr
édi2a1d2
é1di
édi1a
édi2a1ka
édi2al
édi2ar
édi2a1s
édias2z2
é2d1i2d
édi2g
é2d1i1ga
é2d1i1ge
é2d1i1gé
éd1i2ko
éd1ill
é2d1i2m
éd1i2na
é2d1ind
é2d1i1ni
éd1ins
é2d1int
é2d1i2p
é2d1i1ro
é2d1i2z
é2d1ín
é1dí
é2d1ír
éd1ív
éd1kl
éd1ok1ta
é1do
é2d1op
é2d1or
é2d1os2z
éd1ott
éd1ó2r
é1dó
éd1öl
é1dö
éd1ön
é2d1ö2r
éd1öt
éd1öv
éd1öz
é2d1őrm
é1dő
é2d1őrn
édős2
édő1sp
éd1pl
éd1pr
é1d2ram
éd1ra
éd2raz
é1d2rám
éd1rá
éd1sk
éd1sp
éd1sr
éd1st
éd1t2r
é2d1ud
é1du
éd1uj
éd1u1ra
é2d1u2t
é2d1új
é1dú
éd1úr
éd1ú2t
é2d1üd
é1dü
é2d1üg
é2d1üt
é2d1üz
é1d3za
éd2z
é1d3zá
é1d3ze
é1d3zó
é1d3zü
é1e
ée2bé
ée2la
ée2le
ée2lő
ée2me
ée2pi
ée2rő
ée2se
ée2sé
ée2si
ée2ső
ée2s1z1a2
ées2z
ée2s1z1á
ée2szel
ée1s1ze
ée2szép
ée1s1zé
ée2szir
ée1s1zi
ée2szis
éeszt2
éesz1tr
ée2uf
ée1u
ée2vé
ée2vő
é1é
éé2ge
éé2le
éé2pí
éé2ra
éé2te
éf1a1i
é1fa
éf1aj1tó
éfajt2
éf1ing
é1fi
é2f1is
éf1kl
é2f1os
é1fo
ég1abl
é1ga
é2g1abr
ég1a2d
ég1a1ka
ég1akk
ég1akn
ég1alj
ég1am
ég1a2n2y
ég1a2p
é2g1a2r
ég1aut
éga1u
ég1a2v
éga2z
é2g1á2g
é1gá
é2g1ál
ég1áp
ég1árt
é2gát1a2d
égá1ta
ég1á1tá2
ég1á2t1e2
ég1átf
ég1átj
ég1átm
ég1áts
ég1átt
ég1átv
ég1bl
ég1br
ég1d2r
ég1e1ce
é1ge
é2gedén
ége1dé
é2g1ed2z
é2g1e2ge
é2g1ej
é2g1e2kés
ége1ké
é2g1e1la
é2g1elb
ége2leg
ége1le
ég2elek
ége2lemb
é2ge2le1me
ége2lemn
ége2lemt
é2g1elér
ége1lé
é2g1elf
é2g1el1ha
ég1el1há
ége2lin
ége1li
é2g1elis
é2g1el1já
é2g1elk
é2g1el1lá
é2g1ellen
égel1le
é2g1elm
é2gel1nö
ég1eln2y
é2g1e2lö
é2g1e2lő1a
ége1lő
é2g1e2lőm
é2g1e2lőn
ég1e2lőt
é2g1elp
é2g1elr
é2g1el1sa
é2g1el1s1zí
égels2z
ég1el1ta
é2g1el1tá
é2g1el1vá
é2g1elz
é2g1enc
é2g1e2ne
é2g1eng
ége1p
é2g1e1pi
ége2rál
ége1rá
é2g1ere1de
ége1re
ége2r1el
ége1ri2
ége2rim
ége2rin
é2g1e2ró
ég1e2rő
ége2rül
ége1rü
1é2gesd2
é2g1es2s2z
égess2
ége2s1ze
éges2z
é2g1eszk
é2g1eszt
1é2getj
1é2getőh
ége1tő
1é2getőn
1é2getőt
ég1e1va
ég1e2ve
ég1e2vé
é2g1e1vo
é2g1ex
é2g1é2g
é1gé
é2g1é2l
é2g1é2ne1ke
égé1ne
é2g1é2ne1ké
é2gének1k2
é2g1é2nekn
ég1é2pí
é2g1é1pü
é2g1é2ret
égé1re
é2g1é2rez
é2gé2r2é.
égé1ré
é2g1érh
é2g1é2ri
é2g1érl
é2g1érm
é2g1é2r2ő.
égé1rő
é2g1é2rők
égért2
é2g1ér1te
é2g1ér1té
1é2gé1sé
1é2gésn
é2gést
é2g1é1va
ég1fl
ég1fr
ég1g2l
ég1g2r
ég3gyo
ég2g2y
1éghes
ég1he
1ég1hü
égi2as
é1gi
égi1a
ég1i2den
égi1de
égi2g
é2g1ig2a.
égi1ga
égig1ap
égig1as
é2g1igaz
égi1g1á
é2g1ill
é2g1i1má
ég1i1na
é2g1inf
é2g1ing
é2g1inj
é2g1ins
é2g1int
é2g1i1ra
é2g1i1ro
é2g1i2ta
é2g1i1va
é2g1i2zésn
égi1zé
é2g1izg
ég1íg
é1gí
ég1íj
ég1ín
ég1ír
ég1ív
ég1íz
1ég2j.
égki1a2
ég1ki
ég1kl
ég1kr
ég1kv
1égn2e.
ég1ne
1égnék
ég1né
1égnén
1égnét
1ég1ni
é2g1ob
é1go
ég1o1ki2
ég1o1la
ég1old
ég1o1li
ég1oll
ég1olt
é2g1op
é2g1o2r
é2g1os
ég1ott
é2g1o2v
é2g1ox
é2g1óc
é1gó
ég1ó2r
ég1öb
é1gö
é2g1ö2d
é2g1ö2l
ég1ön
égö2r
ég1ö1rö
é2g1ös
é2g1öt
é2göv
é2g1ö2z
1ég2ő.
é1gő
1égők
é2gőr
ég1ő1ri
ég1ő1rö
ég1ő1si
1égőt
1égőv
ég1pl
ég1pr
ég1ps
ég1sk
ég1sp
ég1s2t
égs2z2
1égs2z.
égszáraz1
ég1s1zá
égszá1ra
ég1tr
é2g1ud
é1gu
ég1un
é2g1u2t
ég1úg
é1gú
ég1új
ég1úr
ég1ús
ég1ú2t
é2g1üd
é1gü
é2g1ü2g
é2g1ür
é2g1üs
é2g1üt
é2g1üv
é2g1üz
é2g1űz
é1gű
égve1zé2
ég1ve
é2gy1a2
ég2y
é2gyál
é1gyá
é2gye1dé
é1gye
é2gy1eg
é2gyelek
égye1le
é2gyele1me
égye2m
é2gy1emb
é2gy1e1me
égye2se1ké
égye1se
é2gy1es2z
é2gy1e2v
é2gye1ze
é2gy1ék
é1gyé
é2gyél
égy1é2ne
é1gyi2
é2gyin
é2gyir
é2gy1is
é2gy1iv
é2gyí
é1gyo2
égy1ok
égy1os
égy1ot
é2gy1ó2
égy1ö2l
é1gyö
é2gy1u2
é2gy1ú
é2gy1ü2l
é1gyü
é2gyür
éha2l
é1ha
éh1a1la
éh1an2y
é2h1arc
éh1art
é2h1a1u
é2h1a2v
éh1e1dé
é1he
2éheg
éh1e2gé
éh1e2le
éh1e1lé
é2h1elf
éh1elh
éh1ell
éh1e2lő
éh1elt
éh1elv
é2h1enz
é2h1e2r
é2h1esem
éhe1se
é2h1e2to
é2h1e1vé
é2h1ex
é2he1ze
1é2he1zé
1é2he1ző
1é2hezt
é2he1zü
é2hezv
éh1ég
é1hé
éh1é2k
é2h1é2l
é2h1ép
éh1érb
éh1fl
é2h1ic
é1hi
é2h1if
é2h1i2n
é2h1ip
éhi2r
éh1i1ra
éh1irt
é2hit
éh1i2ta
é2h1iz
éh1ín
é1hí
éh1kr
é2h1od
é1ho
éh1old
éh1öb
é1hö
éh1ö2d
éh1ös
éh1pl
éh1pr
1éh1sé
éh1sk
éh1sp
éh1ud
é1hu
éh1új
é1hú
é2h1üg
é1hü
é2h1ür
é2h1üt
é2h1űr
é1hű
é1i
éi2dő
éi2ga
éi2gé
éi2má
éi2pa
éi2rá
éi2ro
éi2ta
éi2vás
éi1vá
éi2vó
é1í
éí2rá
éí2ro
éí2ró
éí2vá
éí2ze
é2j1ab
é1ja
é2j1a2da
é2j1a2l
é2j1an2y
é2j1a2r
é2j1á2l
é1já
é2j1áp
é2j1árn
éj1eb
é1je
é2j1e2lem
éje1le
é2j1elh
éj1elm
é2j1elv
éj1emb
é2j1es2z
é2j1e2v
é2j1é2g
é1jé
éjé2j
é2j1é2k
é2j1él
é2j1ép
é2j1é2te
1é2j2i.
é1ji
é2j1il
é2j1im
éj1i2n
é2j1ip
é2j1iz
é2j1o2l
é1jo
éj1ó2r
é1jó
éj1öd
é1jö
éj1ön
éj1ör
éj1pl
éj1pr
éj1sp
éj1s2z
éj1ud
é1ju
éj1u2r
éju2t
éj1u1ta
é2j1úr
é1jú
é2jül
é1jü
é2j1űz
é1jű
ék1abl
é1ka
ék1a2cé
éka2dat
éka1da
ék1a2dá
ék1a2dó
ék1ajt
éka2kad
éka1ka
é2k1a1ká
é2k1akk
é2k1akn
éka2lag
éka1la
ék1a2lak
é2k1alg
é2k1alj
é2k1al1ko
é2k1all
é2k1alt
ék1a1lu
ék1amb
é2k1ang
é2k1app
é2k1a2rá
ék1arc
ék1a2ré
ék1arz
é2k1asp
é2k1ass
ék1aszt
ékas2z
ék1a2tom
éka1to
ék1a2ve
ék1a1zo
é2k1ál
é1ká
é2k1á2p
é2k1á2rad
éká1ra
é2k1á2rak
é2k1á2rá
ék1árb
é2k1á2ré
ék1árh
é2k1árk
é2k1árn
é2k1á2ro
ék1árr
é2k1á2ru
ék1á2rú
ék1á2só
ék1ásv
é2k1á1ta
é2k1á2t1á2
é2k1átc
é2k1átd
é2k1á2t1e2
ék1á1té
é2k1átf
é2k1áth
é2k1á1ti
é2k1á2tí
é2k1átk
é2k1átl
é2k1átm
é2k1á2t1ö
é2k1átr
é2k1áts
ék1átt
é2k1á1tu
é2k1átv
é2k1áz
ék1bl
ék1br
ék1dr
ék1e1gé
é1ke
é2k1e2kés
éke1ké
é2k1elb
é2k1e2leg
éke1le
é2k1e2le1me
é2k1e2le1mé
é2ke2lemm
ék1e2lér
éke1lé
é2k1elf
é2k1elk
é2k1el1lá
é2k1ellen
ékel1le
é2k1elm
é2k1e1lo
ék1elp
é2k1el1ta
ék1el1tá
é2k1eltér
ékel1té
é2k1elül
éke1lü
é2k1el1vi
é2k1e2mel
éke1me
ék1eng
éke1p2
é2k1erd
é2k1e2rec
éke1re
é2kered
é2k1e2re1i
é2k1e2rez
é2k1erg
é2k1e2ró
é2k1e2rő
é2k1e2se1te
éke1se
ék1eszk
ékes2z
éke1szl
é2k1e2tet
éke1te
éke2tik
éke1ti
éke2vés
éke1vé
é2k1e2vo
é2k1ex
é2k1ég
é1ké
ék1éjs
é2k1é2pí
é2k1é1pü
é2k1érc
é2k1é2rem
éké1re
é2k1é2re2n
é2k1é2ré
é2k1é2ri
é2k1érk
é2k1érl
é2k1érm
é2k1é2r2ő.
éké1rő
é2k1é2rő1i
é2k1é2rők
é2k1érp
é2k1érr
é2k1ér1te
é2k1ér1té
é2k1ér1tő
é2k1é2rü2
é2k1érv
é2k1érz
2ékés
ékés3s
ék1észl
ékés2z
é2k1étk
é2k1étv
é2k1é2v2e.
éké1ve
é2k1é2vek
é2k1é2vet
é2k1é2véb
éké1vé
é2k1é2vén
é2k1é2vét
é2k1é2vév
é2k1é1vi
ék1fj
ék1fl
ék1fr
ék1g2r
éki1a2
é1ki
éki2d
é2k1i1de
ék1i1dé
ék1i1do
é2k1i1dő
éki2g
é2k1i1ga
é2k1i1ge
é2k1i1gé
ék1i2ko
ék1ikr
é2k1ill
é2k1i1ma
é2k1i1má
ék1i1mi
é2k1ind
é2k1ing
é2k1inh
é2k1inn
é2k1int
é2k1inv
é2k1i2o
é2k1i2p
é2kirán
éki1rá
é2k1i1ro
é2k1isk
é2k1ism
é2k1ist2
éki2s1za
ékis2z
é2k1i2ta
é2k1i2z
ék1íj
é1kí
é2k1ír
é2k1í2v
é2k1í2z
ék2kál
ék1ká
ék1k2l
ék1k2r
ék1kv
é1k2lu
é2k1ob
é1ko
ék1of
é2k1o1ká
é2k1o2laj
éko1la
é2k1ol1da
ék1oltás
ékol1tá
é2k1oml
é2k1opc
ék1o2pe
é2k1org
é2k1orm
é2k1orr
é2k1orz
é2k1os1to
é2k1ott
é2k1o1u
é2k1ox
é2k1óc
é1kó
ék1ó2l
é2k1ón
ék1ó2ra
ék1ó2rá
é2k1ó2v
é2k1ó2z
é2k1ö2b
é1kö
ékö2l
é2k1öm
ék1önt
ék1ö2rö
é2k1ös
é2k1ötl
ék1ö2vö
ék1ö2zö
ék1ő2r
é1kő
ék1ős
ék1pl
ék1pr
ék1ps
é1k2re1á
ék1re
ék2rim
ék1ri
ék1sh
ék1sk
ék1sl
ék1sp
ék1s2r
ék1st
ék1s2z2
ék1t2r
é2k1ud
é1ku
é2k1ug
é2k1uj
é2k1und
é2ku1ni
é2k1u2tac
éku1ta
é2k1u2tak
ék1u2tal
é2k1u2tas
é2k1u1tá
é2k1új
é1kú
ék1ú2r.
ék1úrn
ékú2t
é2k1úth
é2k1ú1ti
é2k1útj
é2k1útn
é2k1ú1to
é2k1útr
é2k1útt
é2kútv
é2k1üd
é1kü
é2k1üg
ék1ü2le
é2k1ünn
é2k1ür
é2k1ü2s
é2k1ü2t
é2k1ü2v
é2k1üz
ék1ű2r.
é1kű
ék1űrb
ék1ű2rö
é2k1űz
él1abl
é1la
é2l1abr
él1a2cé
él1a2da
él1a2do
él1a2dó
él1a2g
é2l1a2j
él1a2ka
él1akc
él1akn
él1a1ko
é2l1a2l
él1amc
él1a2me
él1and
él1a1pó
él1arm
él1asp
él1ass
él1as2z
él1a2to
él1a2u
é2l1a2va
él1a1zo
él1ábr
é1lá
é2l1á2g
élá2l
é2l1áll
é2l1ál1má
é2lálmot
élál1mo
é2l1á1lo
é2l1á2p
él1á2r.
él1á2rá
él1árb
él1árf
él1á2ri
él1árk
é2l1árn
él1á2ro
él1árr
él1á2ru
él1á2rú
él1árv
él1á1t1a2
él1átc
élá1t1e2
é2l1átf
él1áth
él1á2ti
él1átj
é2l1átm
élá2tok
élá1to
él1átr
él1áts
él1átt
él1á1tü
él1átv
él1bl
él1br
élc3s2z
élc2s
él1d2r
éle2b
é1le
é2l1e1bé
é2l1e2d2z
él1eff
él1e2gé
éle2gés2z1
él1e2g2y
éle2k1a2
éle2k1á
éle2k1e2l
éle1ke
éle2kem
éle2ker
éle2k1es
é2l1e2kés
éle1ké
éle2kis
éle1ki
éle2kol
éle1ko
éle2k1on
éle2kot
éle2kó
éle2k1ö2
éle2ku
éle2k1ú
él1e2l1a
é2l1e2leg
éle1le
él1elf
é2l1e1lo
é2l1elő1e
éle1lő
éle2lőj
é2l1e2lők
éle2lőt
é2lemb
é2l1e2mi
é2l1em1p2
éle2n
é2l1e1ne
é2l1eng
é2lenj
é2l1enn
é2l1en2y
é2l1enz
él1e2ró
é2l1e2sés
éle1sé
éle2s1zü
éles2z
éle2s1zű
éle2t1a2
éle2t1á2
éle2t1eg
éle1te
éle2t1e2l
éle2t1e2r
éle2tes2z
éle2t1ék
éle1té
éle2té2l
éle2t1é2r.
éle2t1érd
éle2t1é2r2ő.
életé1rő
éle2té1rü
éle2tés
éle2té1te
éle2té1té
éle2t1étn
éle2t1é2v2e.
életé1ve
éle2t1é2ved
éle2t1é2vén
életé1vé
éle2tik
éle1ti
éle2ti2s2z
éle2t1o
éle2tó
éle1tö2
éle2t1ö2l
éle2t1ör
éle2t1u2
éle2tüz
éle1tü
éle2t1ű2z
éle1tű
é2l1e2vő
é2l1ex
élé2d
é1lé
é2l1é1de
é2l1é2g
é2l1é2hes
élé1he
él1ékek
élé1ke
él1é1kí
é2l1é2l
él1é1pü
é2l1é2r.
é2l1érb
é2l1érd
él1é2rem
élé1re
é2l1é2ret
é2l1é2ré
é2l1érm
é2l1érn
é2l1ér1te
é2l1ér1té
é2l1é2rü
é2l1érz
élé2sa2
élés3s
é2l1é2te
é2l1étk
é2l1é2v.
é2l1é2v1á
é2l1é2ves
élé1ve
é2l1é2vet
é2l1évez
é2l1é2vén
élé1vé
é2l1é2vér
é2l1é2vi
é2l1é1vü
él1f2l
él1f2r
él1g2r
é2l1i2d
é1li
é2l1i1ga
éli2gá
é2l1i2ge
é2l1i1gé
é2l1i2ko
é2l1ill
éli2m
él1i1ma
él1i1mi
él1im1p2
é2l1ind
é2l1inf
é2l1ing
él1int
él1inv
él1inz
él1i2on
éli1o
é2l1i2p
é2l1i1rá
él1i1ro
é2l1ism
éli1s2p2
é2l1i2s1zá
élis2z
él1i2va
é2l1i2vá
él1izg
é2l1izm
é2l1i2zo
él1íj
é1lí
él1í2r
él1í2v
él1íz
él1k2l
él1k2r
él3lyu
él2l2y
él2mat
él1ma
1élmén
él1mé
é2l1ob
é1lo
é2l1okm
é2l1oks
é2l1ol
é2l1o2r
élos3s
é2l1os2z
é2l1óc
é1ló
él1ó2n
é2l1ó2r
él1öb
é1lö
él1öc1
élö2k
él1ö2l
él1ön
él1ör
él1ös
é2l1ö2z
élő1e2
é1lő
él1ő1rü
1é2lősk
él1p2l
él1p2r
élrá1di2
él1rá
élre1pr
él1re
él1sk
él1sp
él1st
él2sz1árnn
éls2z
él1s1zá
él1szt2
éltal2p1al
él1ta
éltal1pa
él1t1rá
él1t1ré
él1t1ri
él1t1ró
é2l1ud
é1lu
é2l1ug
é2l1uj
él1ult
él1u2r
é2l1u2tas
élu1ta
él1u1tó
é2l1új
é1lú
él1úr
é2l1üg
é1lü
él1üll
él1ült
é2l1ür
é2l1üs
é2l1üt
é2l1üv
é2l1üz
él1űz
é1lű
1élve1ző
él1ve
é2ly1ab
él2y
é1lya
é2ly1a2d
é2lyaj
é2ly1a2l
ély1a2n
é2ly1ap
ély1a2r
é2ly1as
ély1a1u
é2ly1av
ély1az
é2ly1á2l
é1lyá
élye2c
é1lye
é2lyef
ély1eg2y
é2lyekés
élye1ké
é2ly1el
é2lyeml
é2lye1ne
é2ly1ent
é2lyer
é2lye1ti
é2ly1é2j
é1lyé
é2ly1ék
é2lyé2l
é2lyés
ély1f2
é2ly1i1ra
é1lyi
ély1k2
é2lyo2l
é1lyo
é2ly1ó
é2lyös
é1lyö
é2ly1öz
é2lyő
ély1s
é1lyú2
é2lyültet
é1lyü
élyül1te
é2lyüt
ém1abr
é1ma
éma1d2
ém1a2da
ém1a2dó
éma1e2
émai2k
éma1i
ém1ajt
ém1akk
ém1all
ém1alm
ém1app
ém1arc
é2m1arm
émas2
éma1sp
émat2
éma1tr
ém1aut
éma1u
ém1a2zo
émá2c
é1má
émá2l
ém1álm
ém1á1lo
é2m1áp
ém1árn
é2m1á1ru
é2m1á2t1a
é2m1átt
ém1b2l
ém1b2r
ém1dr
éme2c
é1me
éme2g
é2m1e2k2e.
éme1ke
é2m1e2kés
éme1ké
ém1e1la
éme2led
éme1le
é2m1elh
é2m1ell
ém1e2lő
ém1els
é2m1elv
ém1e2re
ém1ern
ém1e2rő
éme2s1á
é2m1ese1mé
éme1se
é2m1es2z
éme2ta
éme2tel
éme1te
éme2t1ék
éme1té
éme2to
é2m1e2v
é2m1ex
ém1éks
é1mé
é2m1é2l
é2m1ép
é2m1érc
ém1érd
é2m1é1ri
é2m1érm
é2m1ér1té
é2m1és
é2m1é2te
ém1fl
ém1fr
ém1gr
émi2al
é1mi
émi1a
é2m1i2d
émi2g
é2m1i1gé
é2m1iks
ém1ill
ém1ind
ém1inf
é2m1inv
é2m1i2o
é2m1i2p
ém1i1rá
é2m1irh
é2m1i1ro
ém1isk
ém1ism
émi2s2z
ém1i2ta
ém1i1zo
é2m1ír
é1mí
ém1í2v
ém1íz
ém1kl
ém1kr
émo2nac
é1mo
émo1na
émo2ne
émon1n
émont2
ém1o2p
ém1ost
ém1ox
ém1ó2l
é1mó
ém1ó2r
ém1öb
é1mö
ém1ö2l
ém1ön
ém1ös
ém1öt
ém1öv
ém1ő2r
é1mő
ém1p2l
ém1p2r
ém1sk
ém1sl
ém1sp
ém1s2r
ém1s2t
ém1s2z2
ém1t2r
ém1uj
é1mu
ému2n
ém1u1na
é2m1u2r
é2m1úr
é1mú
é2m1üg
é1mü
é2m1ür
é2m1üt
é2m1üv
é2m1üz
ém1wh
én1abb
é1na
é2n1abl
é2na2dal
éna1da
én1a1dá
én1a2do
én1a1gá
én1agr
én1akc
é2n1akn
én1akt
én1alk
é1n1a1na
én1a1no1
én1ant
éna1p1ré
én1arc
énas2
éna1sp
é2n1ass
én1atm
é2n1a2tom
éna1to
éna1t2r
é2n1a1u
é2n1ág
é1ná
éná2l
én1á1la
én1álc
én1áld
é2n1áll
é2n1á2p
é2n1á2rak
éná1ra
énás1s
én1ásv
én1á1ta
én1átb
én1á2t1e2
én1átk
én1á2t1ö
é2n1átr
én1átt
én1á2tü
é2n1átv
én1ba2
én1bl
én1br
én1d2r
é2n1ef
é1ne
én1e2g2y
1é2ne2k1a2
1é2nekd
é2nek2e.
éne1ke
1é2neke1i
1é2nekek
1é2nekem
éne2ker
1é2nekes
éne2k1é2j
éne1ké
1é2nekf
1é2nekg
éne2kiz
éne1ki
1é2ne1kí
1é2nekj
1é2nek1ka
ének1k2
1é2nekl
éne2kó
éne2k1ö
1é2neks
én1e1la
én1el1g2
én1elh
én1elj
én1ell
én1eln
én1e2lő
én1elp
én1els
én1elt
én1elv
én1eml
éne2n
é2n1e1ne
éne2r1a
éne2r1á2
éne2re1me
éne1re
én1e2ró
é2n1e2s2z
éne2t1a2
éne2t1á2
éne2t1e2l
éne1te
éne2test
éne2to
é2n1e2v
é2n1ex
én1éjb
é1né
én1ékb
én1é2ké
én1é2ki
én1é2kű
éné2l
é2n1é1le
é2n1é1lő
éné2m1a
é2n1éne1ke
éné1ne
é2n1é2r.
é2n1érc
é2n1é1ré
éné2ter
éné1te
2énéz
én1f2r
énfüs1tö2
én1fü
énfüs2t1öl
én1g2r
én1i1do
é1ni
éni2g
é2n1i1gé
én1i1ko
én1ill
én1i1mi
én1i1na
é2n1ind
én1inf
én1inh
én1int
é2n1i2p
én1ism
é2n1i1ta
é2n1i1va
é2n1i2z
én1íj
é1ní
én1ín
é2n1ív
én1k2l
én1k2rá
én1k2ré
én1k1ri
én1mű1
én3nyo
én2n2y
é2n1ol
é1no
én1oml
én1ond
é2n1or
é2n1os2z
én1ot
é2n1ox
én1óc
é1nó
é2n1ó2r
én1ök
é1nö
én1öl
én1ön
én1ör
én1öt
2énöv
é2n1ö2z
én1ő2s
é1nő
én1pe2
én1pl
én1pr
én2sas
én1sa
én1s2p
én1s2t2
én2sú
én1t1ra
én1t1rá
én1t2ri
én1t1ró
é2n1u2t
é1nu
én1út
é1nú
é2n1ü2g
é1nü
é2n1ü1le
é2n1ür
é2n1üs
é2n1ü2v
é2n1ü2z
é2ny1a2
én2y
é2ny1á2
énye2c
é1nye
é2ny1e1ce
é2nye1c1se
ényec2s
é2ny1e2d2z
é2nyef
é2ny1eg2y
é2nyekés
énye1ké
é2nye1la
é2nye1lá
é2ny1ell
é2ny1e1lo
é2ny1el1vá
ény1el1vű
ény1elvv
é2nye1ma
é2ny1enc
é2ny1e1p
é2nyerd
ény1ered
énye1re
é2ny1e2rő
é2ny1e2sett
énye1se
é2ny1e2sés
énye1sé
é2ny1e2ső
é2ny1es2s2z
é2ny1es1té
é2ny1e2s2z
é2nye1ta
é2nye1ti
é2nye1tű
é2ny1ev
é2ny1ég
é1nyé
é2ny1é2j
ényé2k
ény1é1ke
ény1ékh
ény1ékn
ény1ékt
é2ny1é2l
é2ny1é2r.
é2ny1ér1d2
é2nyé1ré
é2ny1é2ri
é2ny1érn
é2nyér1te
é2ny1ér1té
é2ny1é2rü2
é2ny1érv
é2ny1érz
é2ny1és
é2ny1é2te
é2ny1étt
é2nyé1va
é2ny1if
é1nyi
ényi2g
é2ny1i1gé
é2ny1i2ko
é2ny1ing
é2ny1i1ra
é2nyi1ro
é2nyisk
é2nyi1ta
é2nyiz
ényí2r
é1nyí
ény1í1rá
ény1í1ró
é2ny1ok
é1nyo
é2ny1o2l
é2ny1o2r
é2ny1os
é2ny1ó2
é1nyö2
é2nyön
ény1ör
é2ny1ös
ény1öz
é2ny1ő2
ény1s
ényt2
ény1tr
é2nyu
ény1u2r
ény1us
é2nyúj
é1nyú
ény1út
é2ny1ü2lő
é1nyü
é2ny1üs
é2ny1üv
é2nyüz
én1za2
én2z1ad
én2zag
én2zak
én2z1al
én2zar
én2za1u
én2z1ál
én1zá
én2z1ás
én2z1át
én2z1e2r
én1ze
én2z1im
én1zi
én2z1in
én2zis
én2zí
én1zo2
én2z1ol
én2zor
én2zos
én2zö2r
én1zö
én2z1ő
én2z1sa
énz2s
én2z1se
én2zur
én1zu
én2zú
énz1z
é1o
éo2la
éo2pe
éo2s2z
é1ó
éó2ra
éó2ri
é1ö
é1ő
ép1a1do
é1pa
é2p1a2j
ép1alk
épa2n2y
ép1a1po
épa1pr
ép1arc
ép1a1ré
ép1ass
ép1atl
épau2s
épa1u
ép1aut
ép1a2va
é2p1á2g
é1pá
é2p1ál1la
é2p1ál1lo
ép1áp
ép1á2r.
ép1árb
ép1árf
ép1á1ri
ép1á2ro
ép1á2ru
ép1á2rú
ép1átb
ép1átj
ép1átl
ép1átm
ép1áts
ép1á2tü
ép1átv
ép1bl
ép1br
ép1dr
é2peb
é1pe
ép1e2gé
ép1e2g2y
é2p1e2kés
épe1ké
2épel
é2p1e1la
é2p1e1lá
é2p1elb
é2p1elc
é2p1e2lemb
épe1le
é2p1e2le1me
é2p1e2lemr
é2p1elér
épe1lé
é2p1elf
é2p1e2lin
épe1li
é2p1el1já
é2p1elk
ép1el1lá
é2p1ellen
épel1le
é2p1elm
é2p1e1lo
é2p1e2lő1ke
épe1lő
é2p1elr
é2p1eltér
épel1té
é2p1e1lu
épe2n
é2p1e1ne
é2p1e2p
é2p1er
épe2rő
é2p1esem
épe1se
é2p1e2ser
é2p1e2sett
é2p1e2sés
épe1sé
é2p1e2ső
é2p1es1te
é2p1es1té
é2p1es1ti
é2p1estj
épe2s2z
é2p1e1s1ze
é2p1eszk
é2p1e1ta
é2p1e2te1te
épetet2
épe1te
é2pe2te1té
é2p1e1tű
ép1e1va
é2p1ex
é2p1ég
é1pé
é2p1é2k
é2p1é2l
é2p1é2ne1ke
épé1ne
é2p1é2r.
é2p1é1ri
épé2sa
é2p1é1te
ép1fl
ép1gr
é2p1i2d
é1pi
é2p1i2ko
é2p1imp
é2p1ind
ép1ing
é2p1ins
é2p1int
é2p1i2pa1i
épi1pa
é2p1i2ra
é2p1i2rá
é2p1i2ro
é2p1irt
ép1isk
é2p1ism
é2p1ist
é2p1i2ta
é2p1iz
ép1ín
é1pí
é2p1í2r
1é2pítm
é2p1ív
épká2r
ép1ká
ép1kl
ép1kr
ép1kv
éple2t1ö
ép1le
ép1ob
é1po
é2p1ok
ép1old
ép1olv
ép1on
ép1op
ép1orm
ép1ors
ép1os2z
é2p1óc
é1pó
é2p1ó2h
ép1ó2r
ép1öl
é1pö
ép1öm
ép1ön
épö2r
ép1ös
ép1ö2z
ép2p1ek
ép1pe
ép2pí
ép1p2l
ép2p1od
ép1po
ép1p2r
é1p2rog
ép1ro
ép2ró1zá
ép1ró
ép1sh
ép1sk
ép1s2n
ép1sp
ép1s2t
éps2z2
ép1t2r
ép1udv
é1pu
ép1ug
é2p1uj
épu2n
é2p1u1no
é2p1u2r
épu2s
é2p1u2t
ép1új
é1pú
ép1ús
ép1út
é2p1üd
é1pü
é2p1üg
é2p1üld
1é2pü1le
é2p1ür
ép1üs
é2p1üv
é2p1üz
ér1abl
é1ra
ér1abs
é2r1a2d
ér1a2gá
ér1agg
ér1ag2y
ér1a2ja
ér1ajt
é2r1a2ka
é2r1akc
é2r1a2la
ér1a1lá
ér1alg
é2r1alk
ér1alm
ér1alv
ér1am1b
ér1amp
ér1a1ne
ér1a1ni
ér1a2no
ér1ant
ér1a2pá
ér1a2pó
é2r1app
ér1apr
é1r1a2ra
ér1a2rá
ér1arc
ér1asp
ér1ass
ér1a2ti
ér1atk
ér1atl
ér1a2to
éra1t2r
ér1att
é2r1at2y
é2r1a2u
ér1a2va
é2r1a2x
ér1a2zo
é2r1ábr
é1rá
ér1á2g.
é2r1á2ga
ér1ágb
ér1ágg
ér1ág2y
é2r1á2l
ér1á2p
é1r1á1rá
ér1árk
ér1á2ro
ér1árp
ér1árr
é2r1á2ru
ér1á2t1a2
ér1á2t1á2
ér1átc
ér1á2t1e2
ér1á1té
ér1átf
ér1áth
ér1átj
ér1átl
ér1átm
ér1á2tö
ér1átr
ér1á1tú2
ér1á1tü
ér1átv
é2r1áz
1érbán
ér1bá
ér1bl
ér1br
ér2caj
ér1ca
ér2c1a2l
ér2c1a2n
ér2c1as
érc3c
1ércd
ér2ce1dé
ér1ce
ér2c1emb
ér2c1es2z
ér2c3h
ér2cil
ér1ci
ér2c1im
ér2ciz
ér2c1o
ér2có
ér2c1ö
ér2cő
ér2c3seb
érc2s
ér1c1se
ér2c3sis
ér1c1si
ér2cú
ér2c2z
ér2d1am
ér1da
ér2d1á2
1érdek1bő
ér1de
1érdek2e.
érde1ke
1érdeke1i
1érdekel
1érdekl
1érdekt
1érde1kü
1érde1kű
1érde1mé
ér2d1e2rő
érd1es1te
érdés3s
ér1dé
ér2d1i2ná
ér1di
ér2d1iz
ér1d1ra
érdü2l
ér1dü
ér2d1ü1lé
ér2d1ü1lő
ér2d3z
ére2b
é1re
ér1e1ba
é2r1e1be
é2r1e1bé
é2r1ebr
é2r1eff
ére2g1a2
ére2g1á
ére2ged
ére1ge
ére2gel
ére2g1em
ére2gen
ére2g1e2r
ére2gev
ére2g1él
ére1gé
éreg1g
ére2gin
ére1gi
ére2go
ére2g1ö2
é2r1e2g2y
é2r1e2gyez1
ére1gye
é2r1ej
é2r1e2ké2n.
ére1ké
é2r1e1lá
é2r1e2leg
ére1le
é2relemz
ér1e2lég
ére1lé
é2r1elis
ére1li
é2r1elk
é2r1e1lo
é2r1e2lő1a
ére1lő
ére2lőir
érelő1i
é2r1e2lől
é2r1e2lő1me
é2r1e2lőn
ér1elr
ér1el1tá
ére2m1a
ére2má
é2rember
érem1be
ére2m1eg
ére1me
é2r1e2mel
ére2mes
é2r1e2més
ére1mé
ér1e2mis
ére1mi
é2reml
ére2m1o
ére2m1ó
é2re2mu
ére2n
é2r1e1ne
é2r1e2r
ére2ső
é2r1es2ő.
é2r1esőb
é2r1esőj
é2r1esőn
éres1ő2r
é2r1esőt
é2r1es1té
ére2t1a
ére2tá
ére2t1eg
ére1te
ére2t1el
ére2t1é2r.
ére1té
ére2t1é2v2e.
éreté1ve
ér1e2ve
é2r1e2vé
é2r1é2d
é1ré
é2r1é2g
é2r1é2j.
é2r1é2jét
éré1jé
é2r1é2k
é2r1é2l
é2r1é2ne1ke
éré1ne
é2r1ép
é2r1é2ri
éré2s1el
éré1se
éré2s1za
érés2z
ér2é2s1zá
é2ré2ter
éré1te
é2r1étk
é2r1é2v2e.
éré1ve
é2r1évn
é2r1évv
érfi1á2
ér1fi
ér1fl
érfo2g1ó2n.
ér1fo
érfo1gó
érfo2g1ós
érfo1to1
ér1f2r
ér1g2r
éri2al
é1ri
éri1a
éri2c1si
éric2s
é2r1i1do
érié2n
éri1é
ér1ifj
éri2g
é2r1i1ga
é2r1i1ge
é2r1i2ko
é2r1ill
é2r1i2ma
é2r1i2má
é2r1i2mi
é2r1i2na
é2r1ind
é2r1inf
é2r1ing
é2r1inj
é2r1ins
é2rint
ér1inv
é2rinz
é2r1i2o
é2r1i2pa
ér1i1ra
é2r1isk
ér1ism
é2r1ist
é2r1i2s2z
é2r1i2ta
é2r1i1va
é2r1i1vá
é2r1i2z
ér1íg
é1rí
ér1íj
é2r1ín
é2r1ír
é2r1í2v
é2r1í2z
ér2jan
ér1ja
ér1k2l
érkö2z1e2pé
ér1kö
érkö1ze
ér1k2r
ér1k2v
érle2t1ö2l
ér1le
érle1tö
1érmé1é
ér1mé
1érméj
1érmék
1érm2i.
ér1mi
é2r1o2l
é1ro
ér1ont
éro2p
é2r1o1pe
é2r1o2r
ér1ott
ér1o1vi
é2r1o2x
éró2l
é1ró
ér1ó1lo
ér1ó1ri
é2r1ö2b
é1rö
é2r1ö2c
ér1ö2ko
é2r1ö2l
é2r1öm
é2r1ön
é2r1ör
é2r1ös
ér1öt
ér1öv
é2r1ö2z
érő1f2
é1rő
ér1pl
ér1p2r
1érsek
ér1se
ér1s2k
ér1sl
ér1s2p
ér1s2r
ér1s2t
érs2z2
ér1szk
ér2tat
ér1ta
1értekez
ér1te
érte1ke
ér2t1e1la
1értelm
1érte1sí
1értékil
ér1té
érté1ki
1értékm
értés3s
ért1ö2ve
ér1tö
1értőc
ér1tő
1értőg
1értő1ü
ér1t2ran
ért1ra
ér1t1rá
ér1t2ren
ért1re
ér1t2ré
1ér1t2ro
ér1t1ró
ér1u1ga
é1ru
é2r1uj
éru2m1e
éru2n
ér1u1na
é2r1u2r
éru2s1ér
éru1sé
ér1u2s2z
ér1u1ta
ér1u1tá
ér1u2to
é2r1u2z
é2r1új
é1rú
ér1ú2r
ér1ú2s
érú2t
é2r1ü2g
é1rü
érü2k2é.
érü1ké
é2r1ür
é2r1üs
é2r1üt
é2r1ü2v
é2r1üz
é2r1ű2z
é1rű
érv1a2dó
ér1va
érv1a1la
ér2v1a2n
ér2v1á2g2y
ér1vá
1érvel
ér1ve
ér2v1elem
érve1le
ér2v1égb
ér1vé
ér2v1égh
ér2vék
ér2vél
ér2vu
2ér1zá
1érzé2k.
ér1zé
1érzékb
1érzé1ké
1érzékh
1érzékk
1érzékn
1érzékr
1érzékt
1érzé1kü
1érzé1kű
1érzésil
érzé1si
és1abl
é1sa
é2s1a2d
és1ag2y
és1ajt
és1a2ka
és1akl
és1akn
és1a1ko
és1akt
és1a2la
és1alg
és1alk
és1all
és1alt
és1alv
é2s1am
és1a1na
és1ant
és1a2n2y
és1a1pa
és1apr
és1a1ra
és1a2rá
és1arc
és1a1ré
é2s1arm
é2s1arz
és1asp
és1ass
é2s1a2u
ésá2c
é1sá
é2s1ál
és1árt2
ésá2s
é2s1á1ta
é2s1áth
és1átl
és1bl
és1d2r
ése2b
é1se
é2s1e1bé
é2s1ef
és1e2ge
és1e2g2y
é2s1e2k2e.
ése1ke
é2s1e2ke1i
é2s1e2kés
ése1ké
é2s1eks
é2s1ekv
és1e1la
és1e1lá
é2s1elb
és1elk
é2s1elm
és1e1lo
é2s1elr
é2s1el1vé
é2s1elz
é2s1ember
ésem1be
é2s1e2mel
ése1me
é2s1eml
ése2n
é2s1e1ne
é2s1eng
é2s1en2y
é2s1e2p
é2s1e2rő
é2s1es2z
é2s1e1to
é2s1ev
é2s1ex
és1égb
é1sé
é2s1é2géb
ésé1gé
é2s1é2gé1é
és1é2géh
és1é2gén
é2s1é2gév
é2s1égh
é2s1é2gi
é2s1égn
é2s1égv
é2s1é2hen
ésé1he
é2s1é2j
é2s1é2k
é2s1é2l
é2s1ép
é2s1é2r.
é2s1érb
é2s1érc
é2s1érd
é2s1é2rem
ésé1re
é2s1é2re2n
é2s1é2ré
é2s1érh
é2s1érl
é2s1érn
é2s1é2r2ő.
ésé1rő
é2s1é2rők
é2s1é2rőv
é2s1érr
é2s1ér1te
é2s1ér1té
é2s1ér1tő
é2s1é2rü
é2s1érv
és1és2z
é2s1é2te
é2s1é2v2e.
ésé1ve
é2s1é2vet
é2s1é2véb
ésé1vé
é2s1é2vét
é2s1é2vév
és1fl
és1fr
és1gr
é2s1ic
é1si
é2s1id
é3s2idet
ési1de
ési1é2
ési2g
é2s1i1ga
é2s1i1ge
é2s1ikt
é2s1ill
é2s1i1mi
é2s1imp
é2s1ind
é2s1inf
é2s1ing
é2s1int
é2s1inv
é2s1i2p
é2s1i1ra
é2s1i2rá
é2s1i1ro
é2s1i2s
é2s1i2ta
é2s1i2vá
é2s1í2r
é1sí
é2s1ív
és2ka1tu
éskat2
és1ka
és1kl
és1k2r
és1kv
é1s2lág
és1lá
é2s1ob
é1so
és1o1ko
és1okt
é2s1o2l
é2s1om
é2s1op
2ésor
és1org
és1orn
és1orv
é2s1os
é2s1ot
és1o2v
és1óc
é1só
és1ó2l
és1ón
és1ó2r
é2s1ö2l
é1sö
é2s1ön
é2s1ös
é2s1öt
é2s1ö2v
é2s1ö2z
és1p2l
és1p2r
és2p1ri
és1ps
és1s2k
és1s2p
és1s2t
és2s2z2
és3szab
és1s1za
és3szag
és3szak
és3szap
és3s1zá
és4sze2l.
és1s1ze
és3sze1re
és3szé1le
és1s1zé
és3szf
és3s1zi
és3s1zí
és3s1zo
és3s1zó
és3s1zö
és3s1ző
és3szt2
és3s1zú
és3szül
és1s1zü
és3s1zű
ést2r
és1t1ra
és1t1rá
és1t1ré
é1st1ru
és1ud
é1su
és1u2r
é2s1u2t
é2s1ú2t
é1sú
é2s1üg
é1sü
é2s1ünn
é2s1ür
és1ü2té
é2s1üz
ész1abl
és2z
é1s1za
é2sz1a2d
ész1ag2y
é2szaj
észa1ká2
észa2k1áz
é2s2zakc
észa2k1ü2
ész1a1le
é2sz1alg
é2sz1all
észa2n
ész1ant
ész1an2y
észa2p
ész1a1po
é2sz1a2r
é2sz1as
ész1a2to
é2sz1a1u
é2szaz
2é1s1zá
é2száb
é2sz1á2g
észá2l
ész1ál1lá
é2s3záp
ész1áram
észá1ra
é2szá1rá
ész1árán
és2z1árf
ész1ár1k2
é2sz1árn
ész1á2ru
é2szás
é2sz1á2t
ész1á2z
é2sz1e2g2e.
é1s1ze
észe1ge
é2sz1e2ge1i
é2sz1e2g2y
é2sz1ej
észe2k1a2
észe2ká
é2sz1ekés
észe1ké
észe1kö2
észe2köv
é2sz1e1la
é2sz1e2leg
észe1le
é2sz1e2lemb
ész1e2le1me
é2sz1e2le1mé
é2sz1e2lemm
ész1e2lemn
é2sz1elk
ész1előd
észe1lő
é2sz1előr
é2szelőz
é2s2z1ember
észem1be
é2sz1e1mi
é2s3ze1né
é2sz1eng
é2sz1ep
ész1e2rez
észe1re
ész1e2rén
észe1ré
ész1er1kö
észer1k2
é2s2ze2rő
é2sz1e2se1te
észe1se
ész1eszt2
észes2z
észe1ta2
észe2t1ak
észe2t1e2l
észe1te
észe2t1é2r.
észe1té
észe2t1o
észe2t1ör
észe1tö
é2sz1e2ve
é2sz1ex
é2szég
é1s1zé
é2sz1ékt
é2sz1é2l
é2sz1é2p
é2sz1é1ré
é2szé1ró
é2s2zés2z
é2sz1é2te
é2s2z1étk
é2sz1i2d
é1s1zi
é2szif
ész1i2ko
é2sz1ikt
é2sz1ill
é2sz1im
é2szi1na
é2sz1ind
é2sz1inf
é2sz1ing
é2sz1in1té
é2sz1i1o
é2sz1i2p
é2szi1ra
é2s2zi1ro
é2s2zirt
é2sz1i2s
é2sz1i1ta
é2sz1iz
ész1í2j
é1s1zí
é2szír
ész1í2vá
ész1í2vé
ész1í2vó
ész1k2
ész1okt
é1s1zo
észo2l
ész1old
ész1olt
é2szop
é2sz1ors
é2sz1os
é2sz1ot
é2s3zón
é1s1zó
2é1s1zö
és3zöld
ész1ölt
é2sz1ön
é2sz1ös
é2szöt
ész1őrs
é1s1ző
ész1p2
és1z3su
ész2s
és1z3sű
észtés1s
ész1té
ész2tors2
ész1to
észtorz1
ész1t1ra
ész1t1ri
és3zul
é1s1zu
ész1ut
ész1úrn
é1s1zú
é2sz1út
é2sz1ü2g
é1s1zü
é2s2zünn
é2szüt
é2sz1ü2z
é2szűz
é1s1zű
ész1z
ét1ab1la
é1ta
ét1a2do
é2t1a2dó
ét1adt
ét1agg
éta1gr
étai2k
éta1i
é2t1ajk
é2t1ajt
ét1akc
éta1kr
ét1akt
é2t1alj
é2t1alk
ét1als
é2t1alt
é2t1a1ni
é2t1ann
é2t1ant
ét1a1nya
étan2y
ét1a1nyá
é2t1aps
é2t1arc
é2t1arg
ét1aszt
étas2z
état2
ét1atom
éta1to
éta1ü2
ét1a1zo
é2t1ábr
é1tá
é2t1á2g
é2t1áll
ét1á2rad
étá1ra
ét1á2ram
ét1á2rá
é2t1á2ri
é2t1á2rú
ét1árv
ét1á1ti
é2t1átr
é2t1átv
ét1bl
ét1dr
éte1a2
é1te
é2tedén
éte1dé
éte2g1a2
éte2ge1le
éte1ge
éte2g1elv
éte2ger
éte2go
é2t1e2k2e.
éte1ke
é2t1e2ke1i
éte2la
éte2l1á
éte2l1e2l
éte1le
éte2le1mü
éte2l1er
éte2l1ék
éte1lé
éte2l1é2r.
éte2l1é2re2n
ételé1re
éte2lé1ré
é2telfog
étel1fo
é2tellen
étel1le
éte2lo
é2t1e2lől
éte1lő
éte2l1ő2r
é2t1e2mel
éte1me
éte1ra2
éte2rad
éte2r1ag
éte2r1á2
éte2reg
éte1re
éte2rel
éter1mo1
é2t1e2rő
éte2sik
éte1si
é2t1estün
étes1tü
é2t1esv
é2t1eszm
étes2z
ét1e2vi
été2g
é1té
é2t1é2g.
ét1é1ge
é2t1é1gé
é2t1égn
é2t1égt
é2t1é2j
é2t1é2l
é2t1é2re2n
été1re
é2t1é2ré
é2t1é2ri
é2t1é2rőt
été1rő
é2t1ér1te
é2t1ér1té
é2t1é2rü
é2t1érv
é2t1érz
ét1észl
étés2z
é2t1étb
é2t1é2té
é2t1étn
é2t1étr
é2t1é2ves
été1ve
ét1fl
ét1fr
ét1gr
é2t1i2d
é1ti
éti2g
é2t1i1ge
é2t1i1gé
é2t1i2ko
é2t1i2m
é2t1ind
é2t1inf
é2t1int
éti2p
é2t1i1pa
é2t1i1rá
ét1i1ro
étis3s
éti2s2z
é2t1i2vá
ét1i1zé
é2t1í2r
é1tí
é2t1í2v
ét1kl
ét1kr
1ét1kű
ét1kv
étmeg1g2
ét1me
ét1o2ká
é1to
ét1o2ki
ét1okm
ét1o2l
é2to1la
é2tolv
ét1oml
é2t1op
ét1ord2
ét1or1g2
éto2ris
éto1ri
ét1orm
ét1ors2
é2t1óc
é1tó
ét1ó2ha
ét1ó2n.
ét1ó1rá
étö2k
é1tö
étö2l
é2t1ö1mö
é2t1ön
étö2rül
étö1rü
é2t1ös
ét1ö2t
ét1ö2v
é2t1ö2z
ét1ő2r.
é1tő
ét1őrb
ét1ő1ri
ét1őrm
ét1ő1rö
ét1ő1rü
é2t1ő2s
é2t1ő2z
étpen1
ét1pe
ét1pf
ét1pl
ét1pr
é1t2ra1fá
ét1ra
ét2réf
ét1ré
étro2m1é2
ét1ro
ét1sl
ét1sm
ét1sp
ét1st
ét1t2r
ét1udv
é1tu
é2t1ug
é2t1uj
étu2n
ét1u1na
ét1und
é2t1u2r
é2t1u2t
ét1ú2s
é1tú
é2t1üg
é1tü
é2t1ür
ét1üs
é2t1üt
é2t1ü2v
é2t1ü2z
étű2z
é1tű
é2t3ye
ét2y
é1u
éu2ni
éu2ta
éu2tá
éu2z2s
é1ú
é1ü
éü2g2y
éü2le
éü2lé
éü2lö
éü2lő
éü2rü
éü2té
éü2tö
éü2tü
éü2ze
é1ű
é2vad
é1va
1éva2d.
év1a1dó
1évadr
év1ag2y
év1a1já
é2v1a2la
é2va1lá
é2v1alk
é2v1am
é2v1a2n
év1arc
év1a1u
é1v1a1va
é2v1a2z
é2v1ál1lo
é1vá
é2v1á1ta2
év1áth
év1átk
é2v1átr
évá2z
év1á1zá
1évbe1o
év1be
é2v1e2g
é1ve
1é2ve1i
1é2vekt
é2v1e1la
1é2v1elf
1é2v1e1li
é2v1ell
év1eln
é2v1e1lo
é2ve1lő
é2v1els
é2v1elt
é2v1elv
é2v1en1g2
1é2ven3k2
1é2vent
év1e2ri
é2v1e2rő
1é2vesb
1éve1sé
év1e2sés
1évesf
1é2ve1si
1é2vesk
1é2vesr
év1es1té
évest2
é1v1e1ve
év1é2ge
é1vé
év1é2gé
év1égr
év1égt
év1é2r.
év1é2ré
évé2r2i.
évé1ri
év1érr
é2v1ér1te
é2v1ér1té
évé1s1ka
évé1s2p
évé1s1tá
év1gr
évi2g
é1vi
év1i1ga
é2v1i1gé
1é2vih
1é2vik
1é2vim
é2vin
é2v1ind
1évi1ne
1évi1né
év1ing
év1int
év1ism
1é2vi1tő
1é2viv
é2v1ír
é1ví
1évkös
év1kö
év1kr
1évn2y
év1ok
é1vo
év1os
év1ó2r
é1vó
év1ö2r
é1vö
év1ös
év1öt
1é2vőd
é1vő
év1pr
év1sk
év1st
év1s2z
évsza2k1as
év1s1za
évsza1ka
évtá2r1a2d
év1tá
évtá1ra
1évt2ő.
év1tő
év1ur
é1vu
év1új
é1vú
év1ú2t
é2v1üg
é1vü
é2vü2k.
é2vükb
é2vü1ke
é2vükh
é2vü1ki
é2vükn
é2vükt
é2vülj
é2v1üt
é2v1üz
1év1zá
éz1a2d
é1za
éz2a2gal
éza1ga
éza2ge
éz1a2j
éz1akr
é2z1a2l
éza2n
éz1an2y
éz1a2r
éz1as
é2z1a2t
éz1az
é2z1á2g
é1zá
é2z1á2l
éz1ásv
éz1áz
éz1bl
éz1d2r
éze2c
é1ze
é2z1e1ce
é2z1e2dé
éz1e2gé
éz1e2g2y
é2z1e1la
é2z1eld
é2z1e2le
ézel1en
é2z1elf
é2z1elh
é2z1ell
éz1előb
éze1lő
éz1elő1é
éz1előg
éz1elő1i
éz1előj
é2z1e2lő1ké
éz1előn
éz1előp
éz1elős
éz1előt
éz1előv
é2z1els
é2z1elv
é2z1emb
éz2e2n.
éze2n1á
é2z1enz
é2z1e2p
éze2r1a
éze2r1á
éze2r1in
éze1ri
éze2rip
éze2ro
é2z1e2rő
é2z1e2rű
éz1es2z
éze2ta
éze2t1á
éze2t1eg
éze1te
éze2t1e2kéh
ézete1ké
éze2t1el
é2z1e2v
é2z1ex
é2z1é2d
é1zé
é2z1ég
é2z1ék
é2z1é2l
é2z1é2r.
é2z1érc
é2z1é2ri
é2z1érm
é2z1érp
é2z1ér1té
é2z1érz
éz1fr
éz1gé2
éz1g2r
ézi2d
é1zi
éz1i1do
é2z1i1gé
é2z1ill
éz1imp
éz1ind
éz1ing
é2z1int
ézi2o
éz1ion
é2z1i2pa
é2z1i1pá
é2z1i2r
é3z2i3re
é3z2i3rő
ézis3s2
ézi2s1ú
ézi2s1za
ézis2z
ézi2s3zá
éz1i2ta
é2z1i2vá
é2z1izm
éz1i1zo
é2z1izz
éz1í2j
é1zí
éz1ír
éz1í2v
éz1íz
ézkar2c3h
éz1ka
éz1k2l
éz1k2r
éz1o1la
é1zo
éz1old
éz1op
é2z1or
éz1os2z
é2z1ox
éz1öb
é1zö
éz1ö2k
éz1ön
éz1ör
éz1ös
éz1öt
éz1ö2v
éző1a2
é1ző
éz1ő2r.
éz1pl
éz1p2r
éz3saj
éz2s
é1z1sa
éz3sar
éz3seb
é1z1se
éz3ser
éz3sik
é1z1si
éz3sín
é1z1sí
éz3sod
é1z1so
é1z1só2
é2z3sók
ézs1ór
é1z3sö
éz3s1pi
ézs1s
éz3sug
é1z1su
ézs1ú2r
é1z1sú
é2z3sü
éz3s2z
éz1t2r
éz1ug
é1zu
éz1uj
é2z1u2r
éz1ut
é2z1ü2g
é1zü
é2z1ür
é2z1üs
é2z1üt
é2z1ü2z
éz1űr
é1zű
éz3z1sa
éz2z2s
2f.
1fa
faát1
fa1á
fa1b2ro
fa2c1hi
fac2h
fac3h2i.
fa2c3ho
fa1d2r
faegyez1
fa1e
fae2g2y
fae1gye
fae3lek
fae2le
fae2r
faé2d
fa1é
fa1f2r
fa1g2r
fa1gyé2
fag2y
fa2gyék
fa2gyol
fa1gyo
fa2győ
fa2gyúr
fa1gyú
fai2d
fa1i
fai2s
fa2j1a2l
fa1ja
fa2j1az
fa2j1ág
fa1já
fa2j1á2ru
f2ajár
fa2j1egys
fa1je
fajeg2y
fa2jelem
faje1le
fa2j1elk
fa2jelm
fa2j1eln
fa2j1elv
fa2j1es
fa2j1ö
fa2jő
fajt2
faj1tr
faju2r
fa1ju
faj1u1ra
fa2jü
fa1k2l
fa1k2r
fa1k2v
fa2l1a2dá
fa1la
fa2l1a2dó
fa2la1já
fa2l1a2kó
fa2l1a2l
fala2n
fa2l1an2y
fa2lav
fa2l1a1zú
fa2l1ábr
fa1lá
fa2l1ál
fa2l1á2ro
fa2l1áth
fa2l1átv
fal1e2g2y
fa1le
fa2l1elem
fale1le
fa2l1e1lő
fal1e1se
f2ales
fa2l1é1ri
fa1lé
fal1f2
fa2lid
fa1li
fa2l1inj
fa2l1iz
fa2l1í2
fa2l1ol
fa1lo
fa2l1oml
fa2lor
fa2l1os
fal1ó1rá
fa1ló
fa1lö2
fa2lök
fa2l1öv
fa2l1őr
fa1lő
fal1p2
fal2s1í2n.
fal1sí
falta2n
fal1ta
fal2tem
fal1te
fal2ti2p
fal1ti
fal2tüz
fal1tü
fa2lü
fa2n1e2vet
f2anev
fa1ne
fane1ve
fan1évb
fa1né
fan1évr
2f1a2ny2a.
fan2y
fa1nya
faó2r
fa1ó
fa1p2l
fa1p2r
fa2r1a2dá
f2arad
fa1ra
fa2rag2y
f2arag
fa2r1a2kó
f2arak
fa2r1a2l
fa2r1at
fa2r1a2v
fa2r1ácc
f2arác
fa1rá
fa2r1ál
fa2r1á2z
fa2r1em
fa1re
fa2r1e2pe
fa2r1id
fa1ri
fa2r1i1ná
fa2r1ing
fa2r1izm
f2ariz
fa2r1i1zo
far2k1al
far1ka
far2kol
far1ko
far2mál
far1má
far2min
far1mi
far2m1un1ká
far1mu
fa2r1ok1ke
fa1ro
fa2r1os2z
fa2r1ut
fa1ru
fa1s2k
fa1s2p
f2a1s1ta
fa1s1té
fa2sz1í2v.
f2a1s1zí
fas2z
fa1t1ri
fau2r
fa1u
fau2s
f2au2tá
faü2t
fa1ü
faü2z
fa2xe
fa1xi2
fa2x1ik
fa2xin
fa2xí
fa2xö
fa2xú
fa2xü
fa2xű
fa1ya
fa1ye
1fá
f1ábr
fá2c3h
fá2gép
fá1gé
fáj2lak
fáj1la
fáj2lá
fáj2l1e2
fáj2l1í2
fá2ní
fán1ka2
fán2kar
fán2ká
fán2tad2
fán1ta
fán2t1a2g
fán2tan
fán2t1á2
fán2t1e2
fán2tis
fán1ti
fán2tor
fán1to
fán2tö
fá2po
fá2ram
fá1ra
f1á2rá
fás3s
fá2t1a2
fá2t1á
fá2te
fá2t1érd
fá1té
fá2t1i2s
fá1ti
fát1mű1
fá2t1ol
fá1to
fá2t1ü2
fázi2s1ág
fá1zi
fázi1sá
fázi2s3z
fba2l1
f1ba
fc2lu
fd2ró
1fe
fe2a1a
fe1a
fe2a1á
fe2ab
fe2ac
fe2ad
fe2a1e
fe2a1é
fe2af
fe2ag
fe2ah
fe2a1i
fe2aj
fe2am
fe2an
fe2a1p2
fe2ar
fe2as
fe2at
fe2a1ú
fe2av
2f1e2d2z
fe2ed
fe1e
fe2el
fe1f2r
fe2gy1i
feg2y
fe2győ
fei2n1i
fe1i
fe2ja
fe2j1á2
fe2j1eg2y
fe1je
fe2j1elm
fe2j1e2r
fe2j1e2s2z
fe2j1e2v
fe2j1o
fe2j1ös
fe1jö
fej1tr
fe2j1u
fe2jú
fek2t1emb
fek1te
fe2l1a2
fe2l1á2
fele2c
fe1le
fe2l1e2d2z
fe2l1e2g
fe2l1e2h
fe2l1elev
fele1le
fe2l1e2mel
fele1me
fe3lemen
fe3lemet
fe2l1e2més
fele1mé
fe2l1eml
fe2l1eng
fe2l1enn
fe2l1e2re
fe2l1esd
fe2lesed
fele1se
fel1eseg
fe2l1e2sett
fe2l1esés
fele1sé
fe2l1esik
fele1si
fe2l1esk
fe2l1es1ni
fe2l1e2ső
fe2l1essen
feles1se
fe2l1es1te
fe2l1es1tü
fe2l1e2s2z
fe2l1e2tes
fele1te
fe2l1e2tet
fe2l1ettek
felet1te
fe2l1ettél
felet1té
fel1e2ve
fel1e2vé
fel1e1vi
felé2d
fe1lé
fe2l1é1de
fe2l1é2g
fe2l1é2k
fe2l1é2l
fe2l1é2p
fe2l1é2r.
fe2l1é2rek
felé1re
fe2l1ére2n
fe2l1érh
fe2l1é1ri
fe2l1érj
fe2l1érk
fe2l1érn
fe2l1érs
fe2l1ért
fe2l1érv
fe2l1étet
felé1te
fe2l1i2
fe3l2ib
fe2l1í2
fel3l
fe2l1o2
fe2l1ó2
fe2l1ö2
fe2l1őg
fe1lő
fe2l1őr
fel1p2
felt2
fe2l1u2
fe2lú
2f1e2ner
fe1ne
fen2n1a2
fen2ná
fen1ne2
fen2nev
fen1sc
fer1abl
fe1ra
fe2rak
fe2r1áll
fe1rá
fe2r1á2r
fere2g
fe1re
fer1eg2y
fe2r1old
fe1ro
fe2r1olj
fe2r1oln
fe2r1olt
fe2sem
fe1se
fe2s1er
2f1e2sé
feu2m1é2
fe1u
fe2vő
fe2z1é2r.
fe1zé
fe2zó
fe2z2s
1fé
fé1ka2
fé2k1ad
fé2k1ag
fé2k1al
fé2kas
fé2k1a1u
fé2k1á2
fé2k1e2g
fé1ke
fék1ell
fé2k1er
fé2k1ék
fé1ké
fé2k1é2l
fé2k1é2r.
fé2k1o2l
fé1ko
fé2k1or
fé2kön
fé1kö
fé2k1u2
fé2k1ú
fé2l1a2
fé2l1á2j
fé1lá
fé2l1á2r
fé2leg
fé1le
fé2l1ekk
fél1e1lé
fé2l1ell
fé2l1e1lő
féle2m
fé2l1emb
fé2l1e1me
fé2l1e2r
fé2l1e1se
fé2l1es2z
2f1é2let
fé2l1ez
fé2l1é1va
fé1lé
fé2l1i2gaz1
fé2l1i1ga
fé1li
fé2lin
fé2lir
fé2l1is
fé2liv
fé2lí
fé2l1ok
fé1lo
fé2los
fé2ló
fé2l1ö
fé2lőr
fé1lő
félpen1
fél1pe
fél1t2r
fé2l1ú2
fé2mab
fé1ma
fé2ma1d2
fé2m1a2g
fé2m1a2j
fé2m1a2n
fé2m1ap
fé2ma1u
fé2maz
fé2m1á2c
fé1má
fé2m1á2l
fé2m1e2c
fé1me
fé2me1dé
fé2me2ké2t.
féme1ké
fé2m1e2l
fé2m1emb
fé2mer
fé2mez
fé2m1é2k
fé1mé
fémé2r
fé2m1é1ré
fé2m1i2n
fé1mi
fé2m1ir
fé2m1is
fé2mit
fé2m1iz
fé2mí
fé2m1o
fé2mó
fé2mö
fé2m1u
fé1mü2
fé2m1ül
fé2ny1e2g
fén2y
fé1nye
fé2ny1e2l
fé2ny1e2r
fé2ny1is
fé1nyi
fé2ny1í
fé2nyo
fé2nyö2
fé2nyú
fé2pí
fé2pü
2féra1á
fé1ra
2féraf
2féra1i
2féral
2férar
2férád
fé1rá
2férá1é
2férám
fér2cem
fér1ce
fére2g1e2
fé1re
fé2s1orr
f2ésor
fé1so
fész1al
fés2z
fé1s1za
f2é1s1zá2
fész1ár
fé2szeg
fé1s1ze
fé2sz1el
féta3u2
fé1ta
2f1évk
fé2z2s
fé1z1s1e2
fé1z1s1o
ffe3l1i2
f1fe
ffi2ac
f1fi
ffi1a
ffi2ag
ffi2a1ka
ffi2am
ffi2at
ff1li2
ff1sh
1f2fy
fg2ló
1fi
fi2a1a
fi1a
fi2a1á
fi2a1e
fi2a1é
fi2af
fi2ah
fi2a1í
fia2la
fi2a1o
fi2a1ó
fi2a1ö
fia1p
fi2a1pa
fia2ra
fia2rá
fi2a1ré
fi2a1s1zá
fias2z
fi2a1s1zo
fi2a1u
fi2a1ü
fi2a1vá
fi2a1ve
fiá2l
fi1á
fi2c3h
fici2t1á
fi1ci
fi2dan
fi1da
fi2d1á
fi1d1rá
fie2l
fi1e
fie2n
fie2r
fie2s
fie1s1e
fie2t
fi1fl
fi1fr
2f1i2gaz
fi1ga
fi1k2l
fi1k1rá
fi1k1ré
fi1k1ri
fi1k2v
fi2lac
fi1la
fil1a1da
fil1akn
fil1akt2
fi2lal
fi2lam
fi2l1ál
fi1lá
fi2lár
fil1elt
fi1le
fi2l1en
fi2les
fil1es2z
fil1ért
fi1lé
fi2l1i2d
fi1li
fi2l1i2m
fi2l1in
fil2mad
fil1ma
fil2mak
fil2man
fil2m1as
fil2mat
fil2m1á2
fil2mos
fil1mo
fil2mu2
fi2lö
fi2lü
2f1i2má
fimeg1
fi1me
2f1i2nas
fi1na
2find
fin1os2z
fi1no
fi2n1ó
fint2
fin1tr
fi2n1u2
fi2nú
2finx
fio2v
fi1o
fi3ók
fi1ó
fió2k1a2l
fió1ka
fió2kar
fió2k1e2
fió2kép
fió1ké
fió2kol
fió1ko
fi2ó2kö
fió2r
fiő2r
fi1ő
fi1p2l
fi1p2r
fi2rod
fi1ro
fir2s1
fis1arc
fi1sa
fi1s2p
fist2
fi1str
fi2t1a1d2
fi1ta
fit1a2la
fi2t1alj
fi2tág
fi1tá
fit1á2rak
fitá1ra
fitá2t
fi2t1á1ta
fi2t1éh
fi1té
fi2t1érd
fi2tik
fi1ti
fi2t1in
fi2tir
fi2t1i2z
fito1p2
fi1to
f2i2t1os2z
fi2tön
fi1tö
fi2t1őr
fi1tő
fi2t1ur
fi1tu
fiu2min
fi1u
fiu1mi
fiu2t
fi2ú1a
fi1ú
fi2ú1á
fi2ú1e
fi2úf
fi2úg
fi2ú1í
fi2úl
fi2ú1o
fi2ú1ö
fi2ú1ő
fi2úp
fi2ús2z
fi2ú1ti
fi2ú1u
fi2ú1ú
fi2ú1ü
fi2x1ár
fi1xá
fi2x1el
fi1xe
fi2xö
1fí
fí2rá
fír1c1sa2
fírc2s
fí2r1in
fí1ri
f1í2ró
fír1tr
fí2rü
fjú1s2z
f1jú
fki2s1
f1ki
fk2li
fk2lu
fk2ró
f2lak
f1la
f2lan
f2lep
f1le
f2lip
f1li
fli2s1é
flo2n1á
f1lo
flo2né
f2lot
f2luk
f1lu
1fo
fo2am
fo1a
fo2g1á2c
fo1gá
fo2gál
fo2g1árk
fo2g1á1ro
fo2g1e
fog3g
fo2gí
fo2g1or
fo1go
fo2g1os
fo2gö
fo2gur
fo1gu
fo2gü
fohá2szat
fo1há
fohás2z
fohá1s1za
fo2kal
fo1ka
fok1áll
fo1ká
fo2k1árr
fo2k1e2
fok1ing
fo1ki
fo2kí
fok1k2
fo2ko1la2
fo1ko
fo2k1or
folta2n
fol1ta
fol2t1e
fol2t1ö
fo2n1a1da
fo1na
fo2na1do
f2o2nak
fon1alap
fona1la
fona2l1e2
fon1al1ja
fon1al1jo
fona2n
fon1an2y
fo2n1as2z
fo2n1a1u
fo2n1á2r
fo1ná
fonás1s
fo2n1át
fond2
fon1dr
fo2neg
fo1ne
fo2n1e2l1
fo2ner
fo2n1e2s
fo2nék
fo1né
fon1f2
fon1i2ko
fo1ni
fo2n1i2m
fo2nin
fo2n1is
fo2niz
fo2ní
fon1k
fo2n1or
fo1no
fo2n1os2z
fo2nö
fo2nő2
fons2
fon1st
fon2t1i2n
fon1ti
fon1tó2
fon2tón
fo2nü
fo1nya1
fon2y
fo2r1ad
fo1ra
forakés2z1
for2akés
fora1ké
fo2r1a1la
fo2r1a2n
for1a1to
fo2reg
fo1re
fo2r1e2l
fo2r1er
forgás1s
for1gá
fo2r1il
fo1ri
fo2r1i2o
for2m1e2l
for1me
for2m1em
for2me1rő
for2mes
for2m1ér1té
for1mé
for2mil
for1mi
for2m1in
fo2r1ol
fo1ro
fo2rö
2f1or1rú
fors2
for1sp
for2t1ál
for1tá
for2t1e2l
for1te
for2t1érd
for1té
for2tü
fo2rü
foto1s
fo1to
fo2vi
1fó
fó2rá
f2ó1s1za2
fós2z
1fö
fö2la
fö2lá
föl2dök
föl1dö
fö2l1e2
fö3l2e.
2f1ö2lésbel
fö1lé
fölés1be
fö2liv
fö1li
fö2lí
föl1k2
fö2lo
fö2ló
fö2lön
fö1lö
fölt2
föl1tr
fö2lu
fö2lú
fön2n1
fön1ne2
fö2sá
1fő
főa2n
fő1a
főá2g
fő1á
fő1bl
fő1br
fő1d1ra
főe2l
fő1e
főe2r
főe2s
főé2h
fő1é
főé2te
fő1fl
fő1f2r
fő1gl
fő1gn
fő1gr
fői2d
fő1i
főigaz1
fői2ga
fői2ta
főí2t
fő1í
fő1kl
fő1kv
fő2n1ag
fő1na
fő2n1apj
fő1pl
fő1pr
fő2r1aj
fő1ra
fő2rem
fő1re
főre2s
fő2r1ék
fő1ré
fő1ri2
fő2r1is
fő2rül
fő1rü
főr1ü1lé
fő1sl
2fő1sö
fő1s1ta
fő1st2r
fős2z2
fő1szl
fő1szp
fő1szt2
fő1t2r
főü2l
fő1ü
főző1é2
fő1ző
fp2la
fp2ro
fra1s
f1ra
frá2ma
f1rá
frá2má
frá2m1e2
frá2nye
frán2y
f2rás
f2resk
f1re
fré2ná
f1ré
fré2nén
fré1né
1f2ric
f1ri
f3ric2h
f2rig
f3rip
1f2ris
fron2t1a2
f1ro
fron2t1e2
f2röc
f1rö
f2rus
f1ru
fs2ho
fs2po
fs2tí
fst2r
ft1aj
f1ta
fta1kr
ft1alj
ft1bl
f2t1ef
f1te
fte2m
f2t1e1me
f2t1é2r.
f1té
fti2g
f1ti
f2t1i1ge
f2t1ing
ft1ös
f1tö
ft1út
f1tú
f2t1üg
f1tü
ft1üt
f2t1ü2z
1fu
fu1ga1
fura2t1e
fu1ra
fu2s1as
fu1sa
fu2seg
fu1se
fu2sis
fu1si
fu2s1z1á2
fus2z
fu2sz1ol
fu1s1zo
fu2s1zü2
futókés2z1
fu1tó
fut2ókés
futó1ké
futó1s2p
1fú
fúj2tal
fúj1ta
2f1ú2ré
2f1úrr
fú2z2s
1fü
2f1ü2g2y
fü2la
fü2lá
füle2c
fü1le
fü2l1eml
fü2l1e2p
fü2l1e2v
fü2l1é2j
fü1lé
fü2l1é2k
fü2lí
fül3l
fü2lo
2fü1lő
fü2lőt2
fül3t2
fü2ma
fü2má
fü2m1e
fü2mo
fü2mő
fü2mú
2f1ünn
für2t1ő
füs1tü2
füs2t1ü1lé
2f1ü2té
2f1ü2tő
2f1üzl
1fű
fű1dr
fű1fr
fű1pr
fű1sr
fű3sze2r1el
fűs2z
fű1s1ze
fűsze1re
fű1t2r
fű2zá
fű2zis
fű1zi
fű2zo
fű2z1ölt
fű1zö
fváro1sé2
f1vá
fvá1ro
1fy
2g.
1ga
2g1abc
2gabr
2g1abs
ga1by
2g1a2dag
ga1da
g1adap
2g1a2da1ta
2g1a2datb
gadás1s
ga1dá
2g1adm
ga2dog
ga1do
2g1a2do1má
gadói2ko
ga1dó
gadó1i
2g1a2dósat
gadó1sa
2g1a2d2u.
ga1du
2g1a2duk
gae2r
ga1e
2g1aff
ga1fl
ga2g2a.
ga1ga
2g1agg
g2a1g2rá
g1ag2y.
gag2y
ga2gyu
g1a2gyú
2g1aján
ga1já
2g1ajk
2g1ajt
2g1a2kad
ga1ka
ga2kác
ga1ká
2g1akc
g2a3ké
2gakév
ga1kl
2g1akna1i
gak1na
g1ak1tu
g1akv
gala2g1ú
ga1la
ga2lat
2g1a2láf
ga1lá
2g1a2lá1í
ga2láv
2g1alb
2g1alc
gale2g1e2
ga1le
2g1alk
galo2m1e
ga1lo
3g2alop
g1a2los
2g1a2lud
ga1lu
ga2lul
ga2lus2z
2g1al1vó
ga2m1a2gá
g2amag
ga1ma
2g1amc
2g1amp
ga2nab
ga1na
2g1a2nal
2ganc2s
gan2csal
gan1c1sa
gan2cs1ág
gan1c1sá
gan2c1se
gan1d2
3g2a1né
1gan2ga
g1a2nim
ga1ni
ga2nyag
gan2y
ga1nya
ga2ny1at
ganye2l
g2a1nye
ga2nyér
ga1nyé
g1a2nyó
ga2nyö
gaó2r
ga1ó
ga2p2a.
ga1pa
ga2pa1á
ga2pac
ga2pas
ga2pán
ga1pá
ga2pát
ga1p2l
ga2pok
ga1po
ga2p2ó.
ga1pó
ga2pób
ga2pók
ga2pón
ga2póv
2g1app
ga1p2ré
g2a1p1ro
gara2t1í
ga1ra
2g1a2rák
ga1rá
2g1a2rán2y
ga2rén
ga1ré
gar1k2
ga2rő
ga2seg
ga1se
ga2s1is
ga1si
g2a2s1ín
ga1sí
ga1s2pi
ga1sp2r
gas3s1ze
gas2s2z
gas3s1zí
gast2
g2a1s2ta
g2a1s1to
g2a1str
gasz1agg
gas2z
ga1s1za
ga2sz1an
ga2szág
ga1s1zá
ga2szás
ga2sz1e2s
ga1s1ze
gasz2tár
gasz1tá
ga2t1a2d
ga1ta
gat1a1rá
g2atar
ga2tav
g2a2t1ál
ga1tá
ga2t1á2ram
g2atár
gatá1ra
ga2t1eg
ga1te
ga2tep
gaté2s
ga1té
ga1t1hi
ga2tim
ga1ti
gat1old
ga1to
ga2to1mi
g1a2uk
ga1u
ga2ul
ga2us
ga2van
ga1va
ga2var
ga2vat
2g1a2vu
gaza2n
ga1za
gaz1an2y
gaza2te
ga2zál
ga1zá
3g2azd
3g2a1ze
ga2zer
2gazg
2gazí1tá
ga1zí
gazmus1s2
gaz1mu
2gazod
ga1zo
2gazon
ga2zü
1gá
g2á1ba
2g1á2bé
g2á1bó
2g1ábr
3g2á1ci
gács1a2va
gác2s
gá1c1sa
gá2c1se
gá2cs1il
gá1c1si
gá2c1sü
gá1da2
gá2d1al
gá2dá
gá2d1él
gá1dé
gá2d1i1a
gá1di
gá2dis
gá2dő
gá2d1ü
gá2fo
2g1á2g.
gá2gak
gá1ga
gá2gat
gá2gaz
2g1ágg
2g1á2gi
2g1á2gú
2g1á2g2y
gágy1as2z
gá1gya
2g1á2hí
g2á1ja
g2á1já
gá2j1e2
gá2jü
gála1p
gá1la
2g1álc
2g1ál1do
gá2l1es
gá1le
g2á1li
3gális
2g1ál2l.
2g1ál1lí
2g1ál1lu
2g1ál1mo
3g2ál2y
g2á2m.
gá2mal
gá1ma
gá2m1e2
2g1á2mí
gá2mü
gá1na2
gá2nac
gá2nal
gá2na2p
gá2n1at
gá2nás
gá1ná
gán2c1se
gánc2s
gán2c1si
gán2c1s1ö2
gán1d2
gá2n1e
2g1áng2y.
gáng2y
gá2nig
gá1ni
gá2n1i1o
gá2nis
2g1á2niz
gá2nol
gá1no
gá2n1ó2
gán1s2z2
gánt2
gán1tr
gá2n1ú
gá2nü
gá2ny1a2n
gán2y
gá1nya
gá2nyar
gá2ny1as
gá2ny1e2
gá2nyér1d2
gá1nyé
gá2nyérz
gá2nyij
gá1nyi
gány1ká2
gá2nyö
gá2po
gá2rad
gá1ra
gá2r1ag
gá2r1aj
2g1á2ram
gá2r1a2n
gá2rar
gá2ras
gá2ra2t
gár1a1to
gár1att
gá2r1av
2g1á2raz
gá2rá2g
gá1rá
gá2r1ál
gá2r1á2z
gá2re2
gár1em
gár1es
gár1et
gár1é1te
gá1ré
gá2rif
gá1ri
gá2r1i2p
gá2r1isk
gá2r1ism
gá2r1iz
gá2rí
2g1ár1nyé
gárn2y
gá2r1ol
gá1ro
gá2rop
gár1ost
gá2r1os2z
gá1ró2
gá2r1ór
gá2rö
gá2rő
gár1s2
gár1tr
2g1á2r2u.
gá1ru
2g1á2rud
gá2rug
2g1á2ruh
2g1á2ru1lá
2g1á2ru1ló
2g1á2rur
2g1á2rus
gár1u1ta
gá2rut
gár1u1tu
gá2rúr
gá1rú
gá2rút
gá2rü
gá2rű
g2á2s.
gá2s1a2d
gá1sa
gá2sal
2g1á2sa1tá
gá2s1á2g
gá1sá
gá2s1á2rad
gásá1ra
gá2s1árn
2g1á2sás
gá2s1á2to
gá2s1e2
gá2s1im
gá1si
gá2sis
gá2sí
gá2sor
gá1so
gá2só
gá2s1ó2r
gá2sö
gá2ső
gás3s1zé
gás2s2z2
gás3s1zü
gást2
gás1tr
gá2su2t
gá1su
gá2s1ü2
gá2sű
gá2szal
gás2z
gá1s1za
gá2sza2s
gá2szatl
gá2s3zav
gá2sz1ál
gá1s1zá
gá2s1z1e2
gá2szis
gá1s1zi
gá2szit
gá2s1zí
2g1ász1ká
gász1k2
gás2zkés2z1
gász1ké
gá2s2z1ok1ta
gá1s1zo
gá2sz1ól
gá1s1zó
gá2s3zón
gá2szöv
gá1s1zö
gá2s1zü
g2á2t.
2gátad
gá1ta
gá2taj
2gá2tal
gát1a2la
gát1alj
gá1tá2
gá2t1ál
gá2t1ár
gá2tát
2g1át1bo
gá2t1eg
gá1te
gá2t1e2l
gá2t1e2m
gá2ten
gá2t1é2l
gá1té
gáté2r
gá2t1é2r.
gá2t1é1re
2gát1fe
2g1átg
2g1át1ha
gá2til
gá1ti
gá2tis
gá2tiz
2g1á2tí
g1át1mé
gá2t1ol1da
gá1to
gá1tó2
gá2tór
2g1á2t1ö
gá2tő
2g1át1tű
2gá2tü2
g1át1vi
g2á1va
g1á2vó
gá2z1a2d
gá1za
gáz1akn
gáz1akt
gá2z1a2l
gá2zar
gá2z1a2t
gá2z1av
gá2z1á2r.
gá1zá
gá2z1á1rá
gáz1á1re
gá2z1árh
gá2z1ár1k2
gá2z1árn
gá2z1á2ro
gá2z1árr
gá2z1árt
gá2zá1ru
2g1á2zásos
gázá1so
gá2z1e2
gázi2g
gá1zi
gá2z1i1ga
gá2zim
gá2z1i2p
gá2z1i2s
gá2z1i2z
gá2zí
gá2zö
gá2ző
gáz3sp
gáz2s
gáz3s1ta
gáz3s1te
gáz3sug
gá1z1su
gá2z1sú
gá2z3sü
gáz3s2z
gá2zü
gbe1á2
g1be
gbé2lá
g1bé
gbé2rem
gbé1re
gb2la
gb2le
gb2lo
gb2lú
gb2ra
gb2ri
gb2ro
gb2ru
gcsa1pá2
gc2s
g1c1sa
gcsa2p1ág
gda2u
g1da
gd1ell
g1de
gde1p2
g2d1é2k
g1dé
g2d1é2r.
gd1in2a.
g1di
gdi1na
gd1ináb
gdi1ná
gd1ináh
gd1inár
gd1inát
gd1ináv
g1d2rá
gd2re
gd2ro
gd1t2r
g2d3zö
gd2z
1ge
gea2c
ge1a
gea2g
gea2l
geá2r
ge1á
g2e1be
g1e2cet
ge1ce
3g2e1ci
g1ed2d2z
2g1e2dénn
ge1dé
2g1e2dén2y
ge2d1ze
ged2z
ge2d1zé
ge2d1zi
ge2d1ző
2g1eff
2ge1ge
2g1e2gé
3g2egom
ge1go
ge2gye
geg2y
ge2gyé
ge2gyo
2g1e2gyü
ge2hes
ge1he
ge2het
2g1e2hü
2g1ejt
2g1e2k2e.
ge1ke
2g1e2ke1i
2g1e2kek
2g1e2ké1é
ge1ké
ge2kéj
ge2kék
2g1e2ké2s.
ge1k2li
2g1eks
3g2e2l.
g1e2l1a2d
ge1la
ge2lag
3g2elap
ge2las
ge2lál
ge1lá
ge2l1ár
2g1elc2s
3g2el2d.
2g1el1do
2g1el1dö
ge2lef
ge1le
ge2le1gi
g1eleg2y
3g2ele2k.
2ge2le1mé
ge2le1mi
2ge2lemk
2g1e2lemm
2g1e2lemz
gele2n
3g2el2end
ge2l1eng
ge2l1en2y
gel1es1te
ge2lev
ge2lég
ge1lé
3g2elésb
3g2elések
gelé1se
3g2elése2n
3g2elésén
gelé1sé
3g2elésér
3g2elésh
3g2elés2i.
gelé1si
3g2elésk
3g2elés1rő
3g2eléss
3g2elés1tő
3g2elé1sü
2g1elés2z
3g2elés2z.
3g2elé1s1ző
3g2elé1s1zü
3g2elészv
2g1elhel
gel1he
3g2elhes
3g2elhet
3g2el2i.
ge1li
3g2elik
gel1int
3g2el2j.
3g2el1je
3g2el1jé
3g2el1jü
2g1el1kü
2g1elle1nő
gel1le
2g1el1mé
2g1elnev
gel1ne
3g2elnén
gel1né
3g2el1ni
2g1e2l1os
ge1lo
ge2lö
3g2el2ő.
ge1lő
ge2lőbbr
3g2elő1be
3g2elő1bé
3g2elő1bi
3g2elő1bo
3g2elő1bő
3g2előc
3g2elő1e
3g2elő1é
3g2előg
3g2előh
3g2elő1ja
3g2elő1jé
3g2elő1jo
3g2elő1jü
3g2elő1jű
3g2elő2k.
3g2elő1ka
3g2előkb
ge2lőkel
gelő1ke
3g2előker
3g2előket
3g2előkez
3g2elők2é.
gelő1ké
3g2előkén
2g1előkés
3g2előkh
3g2elő1ki
3g2előkk
3g2elő1kl
3g2előkn
3g2elő1kö
3g2elő1kő
3g2előkr
3g2előkt
3g2elő1ku
3g2elő1kü
2g1e2lől
2g1előm
3g2elő2n.
3g2elő1na
3g2elő1ne
3g2elő1né
3g2előnk
3g2elő1nö
3g2elő1nyi
gelőn2y
3g2elő1o
3g2elő1ö
3g2elő1ő
3g2előp
g2előr2e.
gelő1re
3g2előrés
gelő1ré
3g2elő1ro
3g2elő1rő
3g2elő2s.
3g2elő1sá
3g2elő1so
3g2elő1sö
3g2elő1s1za
gelős2z2
3g2elő1s1ze
3g2elő1s1zé
3g2elő1s1zi
3g2elő1s1zó
ge2lő1s1zö
3g2elő2t.
3g2elő1tá
2g1elő1té
3g2elő1tő
3g2elő1tü
3g2elő1ü
3g2elő1vá
3g2elő1ve
3g2elő1vé
3g2elővis
gelő1vi
3g2előviz
2g1e2lőz
g1el1ső
3g2els2z.
gels2z
2g1el1s1zá
2g1el1s1zo
3g2el2t.
3g2elt2e.
gel1te
3g2eltek
3g2eltem
3g2eltes
2g1eltet2t.
2g1eltettn
3g2eltéb
gel1té
3g2elték
3g2elté2l
2g1eltér
3g2eltét
2g1el1to
2g1el1tű
ge2lül
ge1lü
3g2elün
2g1el2v.
2g1elvb
2g1elven
gel1ve
2g1elvét
gel1vé
2g1elvh
2g1elvn
2g1el1vo
2g1elvr
2g1el1vű
2g1elvv
ge2ly1e2g
gel2y
ge1lye
ge2lyid
ge1lyi
ge2man
ge1ma
2g1ember
gem1be
2g1embl
g1e2mel
ge1me
2geme1lé
2ge2melk
ge3mell
2geme1lő
2g1e2més
ge1mé
2g1eml
ge2moc
ge1mo
2g1emp
ge1na2
ge2n1ad
ge2nar
ge2n1as
ge2na1u
ge2n1ál
ge1ná
ge2nár
ge2nát
g2end
ge2n1eg
ge1ne
3g2ene1rá
ge2ne1re
2g1e2nerg
ge2n1es2z
g2enes
ge2n1e2vet
gene1ve
ge2n1e2vez
gené2k
ge1né
ge2n1é1ké
ge2n1ékn
ge2n1ékt
geni2d1
ge1ni
ge2n1in
ge2n1is
gen1k2
g1en1ni
g1en1nü
gen3nya
gen2n2y
ge2nop
ge1no
ge2n1or
ge2n1os
gen2sért
gen1sé
gens3s
gent2
gen1tr
ge2n1ur
ge1nu
g2e1nye
gen2y
2g1enyh
g1enyv
2g1enz
ge2ob
ge1o
ge2oc
ge2of
ge2ok
ge2om
ge2orgi2a
geor1gi
ge2ot
geó2r
ge1ó
ge2ped
ge1pe
ge2per
ge2pés
ge1pé
2ge1pi
ge2pos
ge1po
ge2rab
ge1ra
ge2r1ad
ge2r1aj
ge2ral
ge2r1a2n
ge2r1a2p
ge2r1as
ge2ra1u
ge2ráb
ge1rá
ge2r1á2g
ger1áll
gerá2r
ge2r1á1ra
ge2rát
g1er1de
3g2ereb
ge1re
2g1e2redm
ge2reg
1g1ere1ge
ger1eg2y
2g1e2rej
ge2r1e2lők
gere1lő
ge2r1eml
ge2r1en2y
ge2r1er
ge2res2z
ge2r1e2v
ge2réj
ge1ré
ge2r1ék
ge2rél
ger1é1le
ger1é2lé
ge2r1ép
ge2r1étt
ge2r1i2na
ge1ri
ge2r1ing
ge2r1i2p
ger2is
ge2r1iz
ge2rog
ge1ro
ge2ror
ger1os2z
ge2rot
ge2ró
ge2rö2k
ge1rö
ge2r1ön
ge2rő
g1er2ő.
g1erők
g1erőv
ger1őz
ge1ru2
ge2rur
ge2r1ü2g
ge1rü
ger1üld
ge2r1ü2lé
g2e2s.
ge2s1á
ge1sc
gesd2
g2eseb
ge1se
3g2ese1i
ge2s1e2l
ge2s1emb
g2ese2n.
ge2send
2g1e2s1er
2g1e2setb
2g1e2setet
gese1te
2g1e2se1té
ge2setl
2g1e2setr
2g1e2sett
2g1esél
ge1sé
2g1e2sés
g1e2sik
ge1si
2g1es1kü
gesleg1
ges1le
g1es1ni
2g1e2ső
g2esr
gess2
3g2ess2é.
ges1sé
3g2esség
g2es2t.
2g1es1te
2g1es1té
2g1es1ti
2g1estj
g1es1vé
2g1e2szek
ges2z
ge1s1ze
ge2szes
ge2szet
g1eszl
2g1eszm
ge2t1ak
ge1ta
ge2tal
ge2t1a2n2y
get1ap
ge2t1as
get1e2g2y
ge1te
2g1e2tetőn
gete1tő
ge2t1é2k
ge1té
geté2l
get1é1le
ge2t1é2r.
ge2t1é2rü
ge2t1é2v2e.
geté1ve
ge1tó2
ge2t1ór
getőkés2z1
ge1tő
gető1ké
get1őrt
ge1t2ró
get2t1eb1bé
get1te
ge2tut
ge1tu
ge2t1ü2z
ge1tü
ge2ur
ge1u
geu2s
ge2vic
ge1vi
2ge1vo
ge2vol
2g1e2vő
ge1yé
ge2zo
gező1a2
ge1ző
2g1ezr
1gé
gé2ber
gé1be
2g1ébr
géc3c
gé2d1a2
gé2d1á2
gé2d1e2l
gé1de
gé2d1e2r
gé2d1es2z
gé2dik
gé1di
gé2din
gé2dir
gé2dí
gé2d1o
gé2dö
gé2d1ő2
gé2d1u2
gé2d1ú2
gé1dü2
gé2d1ü1lé
gé2dű
gé2d3z
gé2gi
gé2gő
gé2gü
2g1é2h2e.
gé1he
2g1é2hek
2g1é2hen
2g1é2hes
2g1é2het
2g1éhh
2g1éhs
2g1é2j.
gé2jért
gé1jé
2g1éjh
2g1éjj
2g1éjs
gé2ke1i
gé1ke
g1é2kel
g1é2kes
gékes1s
g1é2kez
2g1éks
gé2lá2l
gé1lá
gé2lel
gé1le
2géles
2g1é2let
2g1é2lez
gé2lén
gé1lé
2g1élm
gélv1vá2
gé2lya
gél2y
gé2lyá
gé2lyeg
gé1lye
gé2lyo
gé2lyú2
gé2m1a2
gé2m1á
g2émb
gé2m1e2g
gé1me
gé2mel
gé2mer
gé2mér
gé1mé
gé2m1o
gé1na2
gé2nab
gé2n1ad
gé2nag
gé2n1al
gé2n1an
gé2n1ar
gé2n1at
gé1ná2
gé2n1ár
gé2nát
3g2énd
gé2neg
gé1ne
2g1é2nekes
géne1ke
2g1é2nekl
gé2n1e2l
gé2n1e2r
gé2n1e2t
gé2n1é2g
gé1né
gé2n1in
gé1ni
gé2ní
g2énn
gé2nó
gé2n1ö
gént2
gé2nú
gé1nü2
g2én2y
gé2ny1e2g
gé1nye
gé2nyid
gé1nyi
gé2nyis
gé2nyí2r
gé1nyí
gé2nyo
gé2nyö2
gé2nyú
3gé2p.
gé2p1a2
gé2p1á2
gé2p1e2g
gé1pe
3g2épel
gé2p1ell
gé2p1e1sé
gé2p1e2s2z
gé2pik
gé1pi
gé2pí
2g1épít
gé2p1o2
gé2pó
gé2pö
gé2pő
3g2épp
géptá2v
gép1tá
gé2p1u
gé2pú
gé2pü1lé
gé1pü
gé1ra2
gé2rag
gé2rak
gé2ral
gé2r1an
gé2rap
gé2rar
gé2ras
gé2rá
2g1ér2c.
2g1érd
gé2r1el
gé1re
gé2r1eml
2géret
2gér2é.
gé1ré
gé2ri1e
gé1ri
2g1é2rin
gé2ris
gér1ká2
2gérl
2gérm
gé2ro
gé2rö
2gér2ő.
gé1rő
2gérők
2gér1te
2gér1té
2gérth
g1ér1ti
g1értj
g1értl
g1ért2ő.
gér1tő
g1értő1e
g1értők
g1értőv
g1érts
g1értv
gé2r1u2
gé2r1ú
2g1ér2v.
2gér1vé
2g1érz
3g2ér1zá
gé2sar
gé1sa
gé2seg
gé1se
gé2s1e2l
gé2s1é2g
gé1sé
gé2sim
gé1si
gé2s1o
gé2só
gés3s1za
gés2s2z2
gé2s1za
gés2z
gés3zav
g2é2s1zá
3gészítő1ü
gé1s1zí
gészí1tő
gé2s1z1o
gé2s1zó
2g1é2tel
gé1te
gé2ter
gé2tet
2g1étk
2g1é2to
2g1étr
2g1étt
2g1é2v.
2g1évb
2g1é2v2e.
gé1ve
2g1é2ved
2g1é2ve1i
2g1é2vek
2g1é2ven
2g1é2vet
gé2véb
gé1vé
2g1é2vén
2g1é2vét
2g1é2vév
2g1évf
2g1é2vi
2g1évj
2g1évk
2g1évn
2g1évr
2g1évs
2g1évt
2g1évv
gé2za2n
gé1za
gé2zőr
gé1ző
géz3s2
gfala2d
g1fa
gfa1la
gfa2l1a1da
gfa2le
gfi2ú1ké
g1fi
gfi1ú
gf2la
gf2lo
gf2ló
gf2lu
gfö2l1ü2le
g1fö
gfö1lü
g1f2ra
gf2rá
g1f2re
gf2rí
g1f2ro
g1f2rö
gf2ru
g2g1a2p
g1ga
gg1arc
gga2s
g2g1a1s1zá
ggas2z
g2g1a1s1zó
gge2lest
g1ge
gge1le
ggés3s
g1gé
gg1i2ta
g1gi
g2g1iz
gg1orv
g1go
ggó2n
g1gó
g2g1ó1ni
ggős2
g1gő
g1g2raf
gg1ra
g1g2ran
g2g1re
gg2ro
gg1sp
g2g1ü2g
g1gü
g2g2y
g3gyak
g1gya
g3gya1lá
g3gyap
g3gya1ra
ggy1a1ro
g3gyat
g3gyár
g1gyá
g3gyáv
ggy1á2z
ggy1e2lő
g1gye
g3gyeng
g3gyep
g3gyer
g3gyé1rü
g1gyé
g3gyil
g1gyi
ggy1ol
g1gyo
g3gyom
g3gyor
g3gyó
g3gyö
g3győ
g3gyu
g4gyút
g1gyú
ggy1ült
g1gyü
g3gyür
g3gyű
gha2d1e2
g1ha
ghajói2ko
gha1jó
ghajó1i
g2ha2m.
g2hamb2
g2ha1me
g2hamh
g2hamm
g2hamn
g2hamr
g2hamt
gha2sábr
gha1sá
g2hi1a
g1hi
1ghy
1gi
gi2a1a
gi1a
gi2a1á
gia1b2
gi2a1ba
gi2a1bá
gi2a1bi
gi2a1bo
gi2ac
gi2a1d2
gi2a1e
gi2a1é
gia1f
gi2afr
gi2a1g2
gi2ah
gi2aim
gia1i
gi2a1í
gi2aj
gi2akar
gia1ka
gi2akas
gi2a1la
gi2a1lá
gi2a1lé
gi2am
gi2a1nó
gi2an2y
gi2a1o
gi2a1ó
gi2a1ö
gi2a1ő
gi2a1p2
gi2ar
gia1s2z2
gi2a1s1za
gi2a1s1zá
gi2aszem
gia1s1ze
gi2a1s1zé
gi2aszf
gi2a1s1zi
gi2a1s1zo
gi2a1s1zó
gi2at
gia1t2r
gi2a1u2
gi2a1ú
gi2a1ü
gi2a1ű
gi2av
gi2az
2gibr
2g1i2de1a
gi1de
2g1i2de1á
2g1i2deg
gi2de1i
2g1i2dej
2g1i2de1o
2g1i2dé
gi2di1ó
gi1di
2g1i2do
2g1i2dő
gi1fl
gig1ad
gi1ga
gig1a1ra
gig1ass
gig1a1u
2gigaz
gig1ál
gi1gá
2g1i2g2e.
gi1ge
gig1e2c
gi2g1eg
gig1eh
gig1eng
gig1enn
gige2r
gig1e1re
gig1e2s
gig1et
gig1e2v
gi2gé
gig1éh
gig1é2l
gi2g1ém
gig1é2ne
gig1é2r
2g1igéz
gig3g
1gi2g1i2
gi2gí
gi2g1o2
gi2g1ó2
gi2g1ö
gi2g1u
gi2gú
gi2g1ü
gi2gű
g1i2g2y
2g1i2ha
2g1ihl
2g1i2jes
gi1je
gi2ker
gi1ke
gi2kes
3gi1le
g1ill
gi2m2a.
gi1ma
gi2man
gi2máb
gi1má
2g1i2mád
gi2már
2g1i2máz
gi2m2e.
gi1me
2g1imp
gina1p2
gi1na
gi2nas
gi2n1á2z
gi1ná
2g1ind
3gi1né
2ginf
2g1inkv
g1in1ná
g1in1ni
g1in1no
g1in1nu
2g1inp
2gins
2g1in1ta
3gintc
2g1in1te
2g1in1té
g1inth
g1in1ti
g1intj
g1int2ő.
gin1tő
g1ints
g1in1tu
g1intv
2g1inv
2g1inz
gi2o1ne
gi1o
2g1io1né
gi2o1no
gi2ot
gi2ó1a
gi1ó
gi2ó1á
gi2óc
gi2ó1e
gi2óf
gi2óg
gi2ó1ká
gi2ókom
gió1ko
gi2ól
gi2ó1p
gi2ó1rá
gi2ó1sp
gi2ó1ta
gi2ó1tá
gi2ó1ú
gi2ó1ü
gi2óz
2g1i2pa
3g2ips
gip2s1za
gips2z
gip2s1zá
gip2s1zo
gi2ram
gi1ra
2g1i2rat
2g1i2rá
gi2rig
gi1ri
2gi1ro
gi2rod
2g1i2rón
gi1ró
2g1irt
g1isc
2g1i2si
2g1isk
2g1isl
2g1ism
2g1isp
2g1istál
gis1tá
2g1is1te
2g1i2s1zá
gis2z
2g1i2s1zo
gi2s1zu
gi2tas
gi1ta
g2i1tá
2gitác
3g2i1ti
3g2it1te
g1it1tu
2g1i2vad
gi1va
2g1i2vás
gi1vá
g1i2vo
2g1i2vó
2g1i2z2é.
gi1zé
gi2zév
2g1i2zo
1gí
gí2gé
gí2ja
gí2já
gí2m1a2
gí2má
gí2né
gí2n2y
gí2ra
gí2rá
gí2re
gí2ri
gí2ro
gí2ró
g1í2tés2z
gí1té
gí2vá
gí2ve
gí2vé
gí2vo
gí2vó
gí2vü
gí2ze
gí2zü
gí2zű
gka2ró2r
g1ka
gka1ró
gk2é2p1e2l
g1ké
gké1pe
gkia3dó1ná
g1ki
gkia2d
gki1a
gkia1dó
gki1á2
gkis1s
gk2la
gk2li
gk2lí
gk2lo
gk2ló
gk2lu
gkö2zén
g1kö
gkö1zé
gkö2z1ér
gkő1vá2
g1kő
gk2ra
gk2rá
gk2re
gk2ré
gk2ri
gk2rí
gk2ro
gk2ró
gk2va
gk2vó
gla1p2l
g1la
gla1s2t
gla3t
glá2s1za
g1lá
glás2z
gle2g1a2
g1le
glege2l
gle1ge
gle2g1e1lé
gle1í2
gle2t1a2
gle2tá
gle2t1el
gle1te
gle2t1é2rőt
gle1té
gleté1rő
gle2t1étn
gle2tos
gle1to
gle1ü2
glé2nyel
g1lé
glén2y
glé1nye
g2lor
g1lo
1g2los
gló1ó2
g1ló
glós2
glő1re2
g1lő
glő2reg
glő2rel
glő2ro
gme2g1a2
g1me
gme2g1e
gme2g1é
gmens1s
gmen2s2z
gmus3s2
g1mu
gna2d
g1na
gn1a1da
g2n1a2j
g2n1alk
gna2pe
g2n1a2r
1g2náb
g1ná
1g2náh
1g2ná1i
g2ná2n.
g2ná1ná
g2ná1ra
gná2s3z
g2ná2t.
g2ná1tó
1g2náv
gn1br
gne2i
g1ne
g2n1e2l
gne2m1e2l
gne1me
gne2sir
gne1si
gnes3s
gne2s1zá
gnes2z
gne1to1
gné2l
g1né
g2n1é1le
gné1v1a2
gné2v1á
gni2g
g1ni
g2n1i1ga
g2n1ing
g2n1i2p
g2n1i2r
g2n1is
gni1t1a2
gni2tel
gni1te
g2n1okt
g1no
g2nol
gn1olt
1g2no1ra
1g2no1re
1g2no1ré
gn1ök
g1nö
gn1ös
gn1pr
gn1s2k
gn1st
gn1t2r
g2núj
g1nú
gn1üg
g1nü
gn1üz
1go
go2áz
go1á
go2be
2g1obj
2g1o2dú
go2et
go1e
g1o2k2a.
go1ka
gok1a2d
g1o2ka1i
2g1o2k1a2l
2g1o2ká
go1ki2
gok1ir
gok1lá2
2g1okm
g1o2kok
go1ko
2g1o2kos
g1o2kot
2g1o2koz
2g1ok1ta
2g1o2laj
go1la
go2l1a2l
2g1ol1da
2g1ol1dá
2g1ol1dó
gol2f1a
gol2f1e
gol2fin
gol1fi
go2lim
go1li
go2l1ó2rá
go1ló
2g1olta1lo
gol1ta
2g1oltár
gol1tá
2g1oltás
2g1olt2ó.
gol1tó
2g1oltv
2g1olvad
gol1va
2g1olvas
go2m1as
go1ma
3g2omb
gom2b1árn
gom1bá
gom2b1árt
gom2b1e2
3g2o1me
2g1oml
go2n1a2to
go1na
go2n1áll
go1ná
gonc3c
gon2d1á2
gon2d1é2r.
gon1dé
gon1di2
gon2dik
gon2doks
gon1do
gon2d1or
gon2dó
go2n1e2
gon2g1a
gon2gál
gon1gá
g2o1no
go2nol
2g1on1to
go2nü
go2nye
gon2y
go2od
go1o
go2pá
2g1o2pe
gor1ass
go1ra
goras2z2
gora1t2
gor1áll
go1rá
2g1orc
go2r1e2l
go1re
2g1o2ri1e
go1ri
2g1orm
go2roz
go1ro
go2rö
2gorz
go2se
go2sö
2gos1to
2g1ostr
g1ost2y
go2sü
go2s3za
gos2z
go2s3zá
go2s1zó
go2u1a
go1u
go2ub
go2ud
go2ur
go2ut
go2vác
go1vá
2g1o2ve
go2xi
go1ya
go1yá
1gó
góa2d
gó1a
góá2g
gó1á
góá2r
gó1bl
gó2c3h
gó2c1ol
gó1co
gócsa1pá2
g2ó1c1sa
góc2s
gócsa2p1ág
gó2cü
2g1ó2dán
gó1dá
gó2div
gó1di
gó1d1ru
gó1f2r
góí2v
gó1í
gó1kl
gó1k1ré
gó2lar
gó1la
góle2l
gó1le
gól1e1lő
gó2l1é2h
gó1lé
gó2lí
gó2lü
2g1ó2nu
góó2r
gó1ó
gó1p2l
gó1p2r
g1ó2rad
gó1ra
g1ó2ras
gó2rá1ka
gó1rá
gó2rár
3g2ó1ro
3g2ó1sa
gó2s1aj
gó1s2ká
gó1s2p
gó1s2rá
gós3s
gó1s2ta
g2ó1s1tá
g2ó1st2r
gó1s2z2
gó2s3zám
g2ó1s1zá
gó2s3záras
gószá1ra
gó2s3zá1rá
gós3zá1rú
gó2ta1u
gó1ta
gó2t1is
gó1ti
gó1t1ré
gó1t1ri
3g2óval
gó1va
2g1ó2vod
gó1vo
2g1ó2vó
gó2vu
3g2ó1zá
3g2ó1zi
3g2ó1zo
3g2ó1zu
1gö
2g1öbl
2g1ö2ko
gö2ku
2g1ö2lá
2g1ölb
g1ö2le
2g1ölr
3g2ömb
göm2b1a
göm1be2
göm2bel
göm2b1er
2g1öml
2g1ö2na
g1ö2ná
gö2ne
gö2nö
2gönt
gö2ra
3g2örb
3g2örc
gör2c1sa
görc2s
gör2c1sá
gör2csel
gör1c1se
gör2c1ső
3g2ör1dí
3g2ör1dü
2g1ö2re
3g2örg
3g2örn
gö2rök
gö1rö
g1ö2rü
2görv
gö1sé2
gö2s1én
2g1ös2s2z
2g1ös2z
2g1ötl
g1ö2v.
g1övb
g1ö2ve
g1ö2vé
g1övh
g1övn
g1ö2vö
g1övr
g1övt
g1ö2vü
g1ö2vű
g1övv
1gő
gőa2n
gő1a
gőá2g
gő1á
gő1br
gőe2l
gő1e
gőe2r
gőé2b
gő1é
gő1fr
gő1gl
1gő2g1ő2
gői2ta
gő1i
gő1kl
gő1kv
gő1nyá2
gőn2y
gő1pl
gő1pr
2g1ő2r.
g1őrb
g1őrh
g2őrit
gő1ri
gő2riz
2g1őrj
g1őrk
2g1őrl
g1őrn
gő2rök
gő1rö
2g1őrr
g1őrs
g1őrt
2g1ő2rü
2g1őrz
3g2őrző1sö
gőr1ző
gő2s1e2p
gő1se
gő2sib
gő1si
gő1s1pi
gő1s1ta
gő2s1ü2v
gő1sü
gő1tr
gőu2t
gő1u
gőü2l
gő1ü
gő2zát
gő1zá
gő2zeg
gő1ze
gő2z1e2k
gő2z1e2l
gő2z1o
gőző2s
gő1ző
gő2z1sö
gőz2s
gő2z3su
gő2z3sű
gő2zú
gpe2c3h
g1pe
g2p1e2lu
gpia2c1i2o
g1pi
gpi1a
gpia1ci
gp2la
gp2lá
gp2le
gp2lu
gpon2ga
g1po
gpo2re2
gp2ra
gp2rá
gp2re
gp2ré
gp2ri
gp2rí
gp2ro
gp2ró
gp2rű
gp2s2z
1graff
g1ra
gra1fo1
gr2a2mad
gra1ma
gr2a2maj
gra2ma2l
gra2m1a2r
gr2a2m1as
gram1a2z
gra2m1á2
gra2m1e2
gra2m1ér1té
gra1mé
gra2mik
gra1mi
gra2m1in
gra2m1is
1g2rammj
gra1mu2
gra2mut
1g2ra1p
1g2ra1ví
grá2c1s1i
g1rá
grác2s
grá1fa2
1g2rá2f1an
1g2ráfb
grá2f1e2
1g2ráf1fe
1g2rá2f1i2d
grá1fi
1g2ráfl
1g2ráft
grá2lát
grá1lá
grá2l1e2
g2rá1ná
grán1d
g2rá1ni
g1rá2rá
grá2r1i2p
grá1ri
grá2s3za
grás2z
gre2e
g1re
gren2d1ő2
g2ril
g1ri
gril2l1a
gri2s1á
gris3s
gri1sü2
gri2süt
g2ríz
g1rí
gró1a2
g1ró
gró1á2
gró2f1a
gró2fú
gró1p
1g2rup
g1ru
gság1g
g1sá
gsé2gel
g1sé
gsé1ge
gs2ho
gs2ka
gs2ká
gs2ko
gsk2r
gs2la
gs2lá
gs2li
gs2má
gs2mi
gs2mu
gs2ná
gso2k1o
g1so
gsors3s
gs2pa
gs2pá
gs2pe
gs2pé
gs2pi
gs2po
gs2pó
gsp2r
gs2rá
g1s2ta
gs2tá
gs2te
gs2té
g1s2ti
g1s2tí
gs1to2
gst2r
g1st1ra
g1st1ru
gs2tu
g1s2tú
gsu1gá2
g1su
gs2vé
gsza2ké
gs2z
g1s1za
gsza2k1ü2
gszála2d
g1s1zá
gszá1la
gszá2l1a1da
gszá2li
gszá2r1a2da
gszá1ra
g1sz2c
gszé2t
g1s1zé
g1sz2f
g1sz2k
g1sz2l
gsz2m
g1sz2p
gszt2
g1sz2tá
gta2g1ar
g1ta
gta1ga
gtag1g
gta2n1ó2
gtára2d
g1tá
gtá1ra
gtá2r1a1da
gter1mo1
g1te
gtermos2z2
gté2rá
g1té
gti2m
g1ti
gt2ra
gt2rá
gt2re
gt2ré
gt2ri
gt2ro
gt2ró
gt2rö
gt2ru
gt2rü
gtű2z1ő2r
g1tű
gtű1ző
1gu
gu2at
gu1a
gu2ay
2g1u2bo
gu2el
gu1e
gu2er2
g1u2ga
3g2ugg
g1u2go
2g1ugr
gu2id
gu1i
gu2in
gu2ir
2g1ujj
gula2te
gu1la
gula2t1í
gu2ná
2g1u2ni
gu2nó
gu2nu
3g2u1rí
gus1abl
gu1sa
gu2sad
gu2s1a2n
gu2sas
gu2sat
gu2s1av
gu2sál
gu1sá
gu2s1e2
gu2s1ér1té
gu1sé
gu2sil
gu1si
gu2sis
gu2s1í
gu2sol
gu1so
gu2sor
gu2s1ó2
gu2sö
gu2ső
gus3s2
gus2s2z2
gust2
gu2sü
gu2s1zá
gus2z
gus3z2s
gu2tac
gu1ta
gu2tak
gu2tal
gu2tam
gu2tan
gu2tas
gu2tat
gu2taz
2g1utc
2g1u2tó
gutó2d1o2ku
gut2ódok
gutó1do
2g1u2tu
gu1ya
1gú
gú2ja
gú2jí
gú2ju
gú2ny1e
gún2y
gú2ny1í2
2g1ú2r.
gú2ré
gú2ri
2g1úrn
gú2ro
2g1ú2s2z
2gú2t1a2
2g1ú2t1á2
2g1útb
2g1útc
2g1útd
2g1ú2t1e2
2g1ú2té
2g1útf
2g1útg
2g1úth
2g1ú2t2i.
gú1ti
2g1ú2ti1a
2g1ú2ti2g
2g1ú2tih
2g1ú2tij
2g1ú2t1i2ko
2g1ú2ti1na
2g1ú2ti1ná
2g1ú2tir
gú2tis
2g1ú2tit
2g1ú2tiv
2g1ú2t1i2z
2g1útj
2g1útk
2g1útl
2g1útm
2g1útn
2g1ú2to
2g1útp
2g1útr
2g1úts
2gútt
2g1útv
2g1útz
gú2zi
1gü
gü2c2s
gü2dé
gü2dí
gü2dü
gü2ge
gü2gy1é2r.
güg2y
gü1gyé
2g1ü2gyi
2g1ügyl
2g1ügyv
2g1üld
gü2len
gü1le
gü2lep
gü2lik
gü1li
gü2löm
gü1lö
2g1ünn
3g2ürc
2g1ü2re
3g2ü1ri
2g1ü2rí
2g1ü2rü
gü2te
gü2té
gü2ti
gü2tö
gü2tő
gü2tü
gü2ve
gü2vö
gü2ze
gü2zé
1gű
2g1ű2r.
2g1űrb
gű1re2
g1űrh
gű2ri
2g1űrj
2g1űrl
2g1űrm
2g1űrn
2g1ű2rö
2g1űrt
gű2ru
gű2rü
gű2ze
gű2zé
gű2zi
gű2zö
gű2ző
gva2s1u2
g1va
gvá2gy1a2da
g1vá
gvág2y
gvá1gya
gvá2nya2n
gván2y
gvá1nya
gvás1s
gverés3s
g1ve
gve1ré
gvezé2rel
gve1zé
gvezé1re
gvé2nye2l
g1vé
gvén2y
gvé1nye
gvi2na
g1vi
gvó1s2
g1vó
g2y
1gya
gy1abl
2gy1a2cé
2gyadag
gya1da
gya2dal
2gy1a2datb
gy1a2dót
gya1dó
2gya2g.
2gyagb
gya2ge
gya2gép
gya1gé
2gyagg
2gyagh
gya2gis
gya1gi
2gyagk
2gyagn
gya2g1ol
gya1go
2gyagt
2gya1gya
g2yag2y
gy1a2gyu
2gy1a1ja
2gy1akc
2gy1aknák
gyak1ná
2gy1ak2t.
2gy1ak1tu
gya2laj
gya1la
gya2l1akt2
gya2la1po
gya2lap1p
gy1alat
2gy1a2lá1í
gya1lá
gya2lel
gya1le
gy2a2lik
gya1li
gyan1ab
gya1na
gya2n1e
2gy1a2nya
gyan2y
gy1a2nyá
gya2pak
gya1pa
gy1a2páh
gya1pá
2gy1a2pá1i
2gy1a2pák
2gy1a2pám
2gy1a2pán
gy1a2pás
2gy1a2pá1tó
2gyap1já
gya2pón
gya1pó
gya2pór
2gy1app
gy1aps
gy1aran
gya1ra
2gy1a2raw
gyard2
2g3yardom
gyar1do
gya2rel
gya1re
gy1ar1gó
3gya1ri
gya2r1ón
gya1ró
gya2r1ó2r
gya2róv
2gya1rú
2gyasak
gya1sa
2gyasat
2gyas1ra
2gy1a2t2y
2gya1zá
1gyá
2gy1ábr
2gy1ág
gy1álc
gy1áll
gy1álm
3gyám
2gy1áp
2gyá2r1e2
2gyárf
2gy1á2rok
gyá1ro
2gy1á2rus
gyá1ru
2gy1á2rú2
gyá2s1zó
gyás2z
2gy1á2ta
2gy1átk
gy1átl
2gy1átv
gy1bl
gy1br
gy1dr
1gye
gye2d1ős
gye1dő
2gy1e2d2z
gy1e2ge
gy1e2gé
1gy1e2gye
gyeg2y
2gy1e2k2e.
gye1ke
2gy1e2kés
gye1ké
2gyeleg
gye1le
g2ye2leg2y
2gy1e2le1mű
2gy1el1nö
2gy1eltér
gyel1té
2gy1el1vá
gy1el1vű
2gyembl
2gy1e2mel
gye1me
gy1eml
2gyenget
gyen1ge
2gy1e2n2y
2gy1enz
gye2pal
gye1pa
2gy1erd
gy1e2red
gye1re
2gy1e2rej
gy1e2res
gye2rén
gye1ré
2gy1ern
2gy1e2ro
2gy1e2rő
2gy1ers
gye2seg
gye1se
2gy1esél
gye1sé
2gyesg
gy1e2ső
2gy1es1te
2gy1estés
gyes1té
2gy1es1ti
2gy1es1tű
gye2s1zü
gyes2z
2gyetem
gye1te
2gy1e2tet
2gyezm
2gy1ezre1de
gyez1re
2gy1ezrel
2gyez2s
1gyé
2gyébr
2gy1ég
2gy1é2hes
gyé1he
2gy1éhs
2gy1é2ji
gy1é2k2e.
gyé1ke
gy1é2kes
2gy1é2le
2gy1élt
gy1élv
3gyém
2gyéne1ke
gyé1ne
2gyé1ni
2gy1ép
2gyé2r.
2gy1ér1d2
2gy1é2rem
gyé1re
2gy1é2rez
gyé2rét
gyé1ré
gy1érg
2gy1érh
2gy1é2ri
3gyé1rí
gy1ér1ke
2gy1érm
2gyérn
2gyérr
2gy1ér1te
2gy1ér1té
gy1ér1tő
2gy1érv
2gy1érz
2gy1é1té
2gyétk
2gy1é2v.
2gy1é2vad
gyé1va
2gy1évb
2gy1é2v2e.
gyé1ve
2gy1é2ve1i
2gy1é2vek
2gy1é2ven
2gy1é2ves
2gy1é2vet
2gy1évh
2gy1é2vi
2gy1évn
2gy1évr
2gy1évt
2gy1é2vü
2gy1évv
gy1fl
gy1f2r
gy1gl
gy1gr
1gyi
2gy1i1de
2gy1i2dé
2gy1i2dő
2gy1i2ga
2gy1i2gá
2gy1i1ge
2gy1i2gé
2gy1igm
gy1i1ha
2gy1i2ker
gyi1ke
2gy1ill
2gy1i2má
2gy1ind
2gy1inf
2gy1ing
2gy1i1pa
2gy1i2rat
gyi1ra
2gy1i2rá
2gy1i1ro
2gy1irt
2gy1ish
gy1isk
2gy1ism
2gy1isn
2gy1i1ta
2gy1i2vó
2gy1izg
2gy1i2zo
1gyí
2gy1íg
2gy1í2r
2gy1ív
gy1íz
gy1kl
gy1k2r
gy1kv
gymás1s
gy1má
1gyo
2gy1o2kos
gyo1ko
2gy1old
2gy1olvas
gyol1va
gyo2m1a2s
gyo1ma
gyo2mi
2gy1o2pe
2gy1orc
gy1orm
2gy1or1só
2gy1orv
gy1o1u
1gyó
2gy1ó2dár
gyó1dá
2gy1ó2dásak
gyódá1sa
gy1ó2rá
2gy1ó2ri
1gyö
2gy1öb
gy1ö1dé
2gy1ökl
2gy1ö2tö2d.
gyö1tö
2gy1ö2tödd
2gy1ö2tö1de
2gy1ö2tö1dé
2gy1ö2töd1ne
2gy1ö2tödöt
gyötö1dö
2gy1ö2tödr
2gy1ö2tö1dü
2gy1ött
2gy1ötv
2gyöv
2gy1ö2zön
gyö1zö
1győ
gy1ő1re
gy1ő1rü
2gy1ő2s
3győz
gypár1ba2
gy1pá
gypen1
gy1pe
gy1pl
gy1pr
gy1ps
gyrövid1
gy1rö
gyrö1vi
gy1sc
gy1sk
gy1sl
gy1sm
gy1sn
gy1sp
gy1sr
gy1s2t
gy2sur
gy1su
gy1t2r
1gyu
2gy1ud
2gy1ug
2gy1uj
2gy1und
2gy1u2ni
2gy1u2ra
2gyu1rá
2gy1u2ru
1gyú
2gyúd
2gyú1é
2gyú1i
2gy1ú2jí
2gy1újr
2gy1újs
2gyúm
2gy1úrb
2gyú1ré
2gy1ú2ri
2gy1úrk
2gy1úrr
gy1ú1ti2
2gyú1ü
1gyü
2gy1üd
2gyüg
3gyü1le
3gyüm
2gy1ünn
2gyü1re
2gy1ü2rü
2gyüs
gy1üst
2gyüt
2gyüv
gy1ü1ve
2gy1üz
1gyű
3gyűl
2gy1ű2r2é.
gyű1ré
2gy1ű2z
gy1zr
gza2tá2p
g1za
gza1tá
gza2t1e
gza2tö
gza2t1ű2
gzá2r1ó2r
g1zá
gzá1ró
gzá2se
gzás3s
gze2t1a2
g1ze
gze2t1á2
gze2t1el
gze1te
gze2tin
gze1ti
gze2t1o
gze2t1ő2
gző1a2
g1ző
2h.
1ha
haa2d
ha1a
3ha2b.
ha2bak
ha1ba
h2a2b1a2l
h2a2b1a2n
ha2b1ág
ha1bá
ha2b1árb
ha2bed
ha1be
ha2b1é2r.
h2abér
ha1bé
ha2bid
ha1bi
hab1ill
ha2b1im
ha2b1i2n2a.
habi1na
ha2b1int
3ha1bo
hab1old
hab1o2ra
hab1orr
ha2b1ost
hab1s2z
habu2r
ha1bu
ha2bü
ha2d1ag
ha1da
had1alk
ha2d1ap
hada2s
ha2d1as2z
ha2d1á2c
ha1dá
ha2d1ál
hadás1s
ha2d1ásv
ha2d1á2s1zo
hadás2z
ha2de2g
ha1de
ha2d1el
ha2dem
ha2dél
ha1dé
h2adi1a2
ha1di
hadi1é2
h2adi2n
ha2d1i1na
ha2dor
ha1do
ha2dos
ha2d1u2r
ha1du
ha2d1ú2r.
ha1dú
ha2d1ú2ré
ha2d1úrh
ha2d1ú2ri
ha2d1úrk
ha2d1úrn
ha2d1úrr
ha2d1úrt
ha2dús
ha2dü
ha2d1za
had2z
ha2d3zá
hae2r
ha1e
ha1fl
ha1f2r
2hago1re
ha1go
ha2if
ha1i
ha2j1á2s
ha1já
ha2j1á2to
ha2já2z
ha2j1in
ha1ji
haj1k2
haj1oml
ha1jo
ha2jö
ha2jő
haj1s
haj1t2r
ha2jü
ha2k1ál
ha1ká
ha1k2li
ha1k2r
h1akt
ha1k2v
ha2l1ac2h
ha1la
ha2l1a2g
ha2l1aj
ha2l1a2l
hala2n
ha2l1an2y
ha2l1a2r
hala2s2z
ha2l1a1s1za
ha2l1a1s1zá
ha2l1a1s1zó
ha2l1at1k2
ha2l1a2tom
hala1to
ha2l1att
3ha1lá
hal1á2c2s
ha2l1á2g
halá2l1e2
ha2l1á2rak
halá1ra
ha2l1á2rá
ha2l1árb
ha2l1á2ro
ha2l1árr
hal1ár1ve
ha2l1á1te
h2a2leb
ha1le
h2a2lec
hal1e2g2y
hale2l
ha1l1e1le
hal1e1lő
h2a2lem
h2a2l1en
hal1e1pe
h2alep
ha2l1e1se
h2ales
ha2l1e2ső
hal1e1te
hal1e2to
hal1e1vo
h2alev
hal1e2vő
ha2lez
ha2l1é2l
ha1lé
ha2l1é2r.
ha2l1é1te
h2alét
ha2l1étk
ha2l1étt
ha2lid
ha1li
h2a2l1ik
h2a2l1i2m
ha2l1ing
ha2l1inv
ha2l1i2o1no
hali1o
ha2l1i2ont
ha2lip
hali2s
hal1is2z
ha2l1iv
ha2l1i2z
ha2lí
hal2k1a2pu
hal1ka
halke2l1
hal1ke
hal2k1e1le2
hal2l1aszt
hal1la
hallas2z
hallás1s
hal1lá
hal2l1á2t
hal2léj
hal1lé
hal3l2y
3halm
halma2z1ó2
hal1ma
ha2l1ol
ha1lo
ha2l1ó2ri
ha1ló
ha2l1ö
ha2l1ő2
hal1p2
ha2l1ug
ha1lu
ha2l1u2s
ha2l1u2t
ha2lü
ha2lű
ha2l3ya
hal2y
ha2m1ál
ha1má
ha2m1árb
hamb2
hame2l
ha1me
ham1e1le
ham1es2z
ha2mez
ham1ism
ha1mi
hamkés2z1
ham1ké
ha2m1os2z
h2amos
ha1mo
3ha1mu
ha2mü
hanás1s
ha1ná
han2c2h
2hanés2z
ha1né
3hang
han2gal
han1ga
han1ge2
han2gen
han2g1es
han2gél
han1gé
hang3g
han2g1ó2
han2gö
han2gut
han1gu
han2t1ó2
ha2nyél
han2y
ha1nyé
haó2r
ha1ó
ha1p2r
hara2g1ó2
h2arag
ha1ra
harang1g
ha2r1ál
ha1rá
har2c1al
har1ca
har2ca2n
harc3c
har2c1e2
har2c3h
ha2rel
ha1re
ha2ret
3har1mó
ha2r1ol
ha1ro
ha2r1os2z
ha2r1ór
ha1ró
ha2rű
3ha1sa
haság1g
ha1sá
ha2s1iz
ha1si
ha2s1ol
ha1so
has1ors
h2asor
ha1s2pe
ha1s2po
ha3s2ú2t
ha1sú
ha2sür
ha1sü
ha2s1ű2
ha1szp
has2z
h2a2t1ab
ha1ta
hat1ag2y
h2atag
ha2t1aj
3ha1tá
határa2d
h2atár
hatá1ra
hatá2r1a1da
ha2t1e2v
ha1te
ha2t1én
ha1té
ha2t1é2v
ha2t1í2v
ha1tí
hat1ol1da
ha1to
ha2t1os2z
ha2t1ó1rá
ha1tó
ha2t1ö2v
ha1tö
ha1t2rá
hatt2
hat1tr
ha2tül
ha1tü
ha2u1e
ha1u
hau2n
hau2s
ha2ut
haü2z
ha1ü
1há
3há1bo
há2g2y
há2jús
há1jú
há1ma2
há2m1al
há2m1á
há2m1e2
há1mi2
há2m1is
há2m1ol
há1mo
há2mö
hán2c1s1e
hánc2s
hán2c1si
há2ny1a2l
hán2y
há1nya
hánya2n
há2ny1ar
hányás1s
há1nyá
há2ny1e2
há2nyö
há1ra2
há2r1ad
há2r1al
há2r1a1u
há1ri2
há2r1iv
3há1rí
hár1k2
hármas1s
hár1ma
háro2m1a2
há1ro
háro2mo
hár2s1al
hár1sa
hár2se
3hárt
há2rü
há1ry
há2sí
hász1a2tom
hás2z
há1s1za
hásza1to
há2s1z1e
há2sziv
há1s1zi
2h1ászk2a.
hász1k2
hász1ka
há2s1ző
há2t1a2dó
há1ta
há2t1a2la
há2t1a2n
há2tar
há2tá2p
há1tá
há2t1e2
há1té2
há2t1é2l
há2t1é2r.
há2t1é1re
há2t1érn
há2t1i2s
há1ti
há2tí
há2t1ol
há1to
há2t1o2r
há1tó2
há2t1ós
há2tö
há2tő
hát1u2s2z
há1tu
há2t1ü
há2tű
hátvé2d1el
hát1vé
hátvé1de
há2z1a2dó
há1za
há2zaj
há2z1a1la
há2z1as2z
há2z1av
há2z1á2p
há1zá
há2z1árn
há2z1á2ru
há2z1e2
há2z1isk
há1zi
há2z1ism
há2z1ist2
há2z1í
ház1okt
há1zo
ház1old
há2zos
ház1otth
há2zö
há2ző
ház2s2
há2z3sa
há2z3se
há2z3sé
há2z3si
ház3sp
ház3st
há2z3sü
ház3s2z
há2z1ü
há1zy
hb2le
1he
he2ad
he1a
he2av
hec2c1emb
hec1ce
hec2c3s
2he1cu
he2dén
he1dé
he2e2s
he1e
he2f1i
he2f1u2
he2g1a2
he2gés2z1
he1gé
he2gy1a
heg2y
he2gyá
he2gyeg
he1gye
hegyes1s
he2gy1o
he2gyó
he2győ
he2gyú
2he2idp
he1i
2h1e2kék
he1ké
3hekt
he2lég
he1lé
helés1s
2helf
hel2f2r
2hel1lá
2hellen
hel1le
he2lyeg
hel2y
he1lye
he2lyeml
he2lyes2z
he2ly1é2j
he1lyé
2he1ma
2h1embl
2h1eml
henés1s
he1né
3heng
he2ny1e2g2e.
hen2y
he1nye
henye1ge
he2nyo
he2ol
he1o
he2rab
he1ra
he2r1a2d
he2r1aj
he2r1a2r
he2ra1u
her1áll
he1rá
her1á1t1a2
her1á1té
her1eng
he1re
here1p
her1e1se
herevíz1
here1ví
her1int2
he1ri
he2rö
her1s2
he2rut
he1ru
he2rű
he2s1a
he2s1á
h1e2sés
he1sé
hes2t1o
3het2y
2heus2z
he1u
2he1vé
he2vés
3he1ví
he2z1á
2hezh
2he1zi
2hezn
2he1ző
2hezz
1hé
1hé2hé
3hé2i.
hé1i
3héit
hé1je2
hé2j1eg
hé2j1el
hé2jö
3hékn
hé2nal
hé1na
hé2nan
hé2nar
hé2nát
hé1ná
hé1ne2
hé2n1el
hé2nem
hé2n1et
hén1é2v.
hé1né
hé2nid
hé1ni
hé2nil
hé2n1is
hén3n
hé2nö
hént2
hé2nu
hé2nü
hé1ph
hé2pü
hé1ra2
hé2rar
hé2r1as
hé2rat
hé2rin
hé1ri
2h1érz
h2é2s1zá
hés2z
hé2szeg
hé1s1ze
hé2s2zes2z
h2é2s1z1ö
hé1ta2
hé2t1ab
hé2t1aj
hé2tal
hé2tar
hé2tál
hé1tá
hé2t1e2l
hé1te
hé2t1e2m
hé2t1es
hé2t1ezres
hétez1re
hé2t1é2v
hé1té
hé2tí
hé2t1o2l
hé1to
hé2tor
hé2t1os
hé1tó2
hé2t1ór
hé2t1ö
hé2tu
hé2t1ü2
hé2v1á
hé2v2e.
hé1ve
hé2vég
hé1vé
hé2v1érz
hé1ze2
hé2z1ek
hé2zi2o
hé1zi
hé2zip
hé1zo2
hé2z3s
hf2ló
1hi
2hi2a.
hi1a
hi2a1a
hi2a1á
hi2abeli1e
hia1be
hiabe1li
hi2ac
hi2ad
hi2a1e
hi2a1é
hi2ag
hi2ah
hi2aj
hi2al
hi2am
hi2ant
hi2ap
hi2ar
hi2at
3hi1da
hidro1s
hid1ro
2hiév
hi1é
hig2a2nye2
hi1ga
higan2y
3hi1gi
2hi1la
2hi2m.
2hi1me
2hi1mé
2h1i2nán
hi1ná
3hinás
2hing
h1in2g.
2hink
h1insp
hi2om
hi1o
hi2pa
2hips
h1ip2s.
h1ip1se
h1ip1sé
h1ipsh
h1ipsr
h1ipss
hipszes1
hips2z
hip1s1ze
2hi1se
hi2s2e.
2hisn
2hi1so
hi2ta
hit1ak
hita2l
hit1a2n
hi2t1á2
hi2t1e2g2y
hi1te
3hitel
hite2l1e2l
hite1le
hi2t1e2le1sé
hi2t1e1lő
hi2t1elv2e.
hitel1ve
hi2t1eng
h2i2t1er
hi2t1es2z
3hitet
hi2tél
hi1té
hi2t1é2r.
hi2t1ér1te
hi2t1érv
hi2t1é2te
hi2t1é2v2e.
hité1ve
hi2t1im
hi1ti
hi2t1int
hitkés2z1
hit1ké
hi2t1o
hi1tó2
hi2t1ón
hi2t1ór
hi1tö2
hi2t1ör
hi2t1őr
hi1tő
hi2t1u2
hi2tú
hi2tül
hi1tü
hi2zo
1hí
hí2da
hí2dá
hí2de
hí2dí
hí2dö
hí2dő
hí2dú
hí2dü
hí2d2z
hí2g1e
hí2jé
hí2m1a2
hí2má
hí2m1el
hí1me
hí2m1emb
hí2mer
hí2mo
hí2mö
hí2mu
hí2r1a2
hí2r1á2
hí2r1ing
hí1ri
hí2rí
hí2r1o
hí2r1ó2
hí2rő
hír1s
hí2r1u
hí2rú
hkas3s
h1ka
hká2r
h1ká
hk2ri
hle2g1e2lé
h1le
hle1ge
hle2t1el
hle1te
hle2tö
1ho
ho2dú
2ho2e1á
ho1e
ho2ef
ho2i1i
ho1i
ho2it
2hokl
ho1la2
ho2l1a1d2
ho2l1al
ho2lam
ho2l1at
ho2l1a1u
ho2l1ál
ho1lá
ho2l1á2r
hol2dá
hol2dem
hol1de
hol2d1ó2r
hol1dó
ho2l1e2
ho2lig
ho1li
ho2l1in
ho2lip
ho2l1i2v
hol1k2
hol2mes
hol1me
hol2nik
hol1ni
ho2lor
ho1lo
ho2l1os2z
ho2lot
ho2l1ó2r
ho1ló
ho2lö
holta2n
hol1ta
holt1an2y
hol1te2
hol2t1el
ho2lü
ho2ly1al
hol2y
ho1lya
3homb
3ho1mo
homo1s
ho2n1a2g
ho1na
ho2n1a2l
ho2n1a2n
ho2n1a2p
ho2n1a1u
ho2n1a2v
ho2n1ál
ho1ná
ho2n1á1t1a2
ho2n1á2to
ho1ne2
ho2neg
ho2n1e2l
ho2n1ik
ho1ni
ho2n1i2m
ho2ní
ho2n1orj
ho1no
ho2n1o2ro
ho2n1orr
ho2n1ó2
ho2nö
ho2nő
ho2nü
hor2d1e2
ho2re
2ho1ry
hos1s1z1e2
hos2s2z
hos1s1zé2
hossz1ék
hos1s1z1ü2
2hos2z
ho1th
ho2us
ho1u
ho2we
ho2zál
ho1zá
ho2z1e
ho1zi2
ho2zü
1hó
hóa2k
hó1a
hóá2g
hó1á
hó1bl
hó2cal
hó1ca
hó2cat
hóc3c
hó2ce2l
hó1ce
hó2c1é2g
hó1cé
hó2c3h
hó2ci2m
hó1ci
hó2có
hó2c1ö
hó2c1sü
hóc2s
hó2c2z
hó2d1a2r2a.
hó1da
hóda1ra
h2ó2d1a2rá
hó2d1á2
hó2dem
hó1de
hó2d1é
hó2d1ó2
hó2dö
hó2dü
hó2d3z
hó2l1ej
hó1le
hó2l1e2ped
hóle1pe
hó2l1e2vet
hóle1ve
hó2nal
hó1na
hón1a1pa
h2ónap
2hó1rá
hó2rár
hó1sh
hó1ví2
hóza2t1e
hó1za
1hö
hö1kö1
höl2gya
hölg2y
höl2gy1á2
höl2gyel
höl1gye
1hő
hőa2n
hő1a
hő1br
hőe2l
hő1e
hőe2m
hőe2r
hőé2n
hő1é
hő1fl
hő1gl
hő1gr
hő1kv
hő1pr
hő1sa2
hő2s1al
hő2sas
hő2s1av
hő2s1el
hő1se
hő2se2p
hő2ses
hő2sim
hő1si
hő2sis
hő1s2pi
hős3s
hő1s2tab
hős1ta
hős1t2r
hő2su2t
hő1su
hő2s1ú
hő2s1ült
hő1sü
hő2s1ü2v
hő1sű2
hő2s1űr
hő1tr
hőü2l
hő1ü
hp2la
hp2ra
hp2ré
hp2ri
hp2ro
hp2ró
hru1s2
h1ru
hs2c2h
hsé2gel
h1sé
hsé1ge
hs2ka
hs2pi
hs2po
hs1s2t
hsza2ké
hs2z
h1s1za
hszá2j1a2da
h1s1zá
hszá1ja
hsz2l
ht1cl
ht1kl
h2t1ol
h1to
ht2rá
h1tref
ht1re
h2t1u2t
h1tu
1hu
hu1hy
2hu1rá
hur2t1e
hur2ti2t
hur1ti
2hu2s.
2hu1si
huszon1
hus2z
hu1s1zo
1hú
hú2gy1a2
húg2y
hú2gye
hú2gyi2
hú2gyú
hú2r1a2
hú2r1á
hú2re
húrt2
húr1tr
hú2s1ak
hú1sa
hú2sal
hú2sa2n
hú2sap
hú2sa2r
hú2s1á2g
hú1sá
hú2s1ál
hú2s1e2
hú2sim
hú1si
hú2sí
hú2sor
hú1so
hú2sö
hú2ső
hús3sza2k1
hús2s2z
hús1s1za
hús3s1zá
hús3s1zé
hú2sü
hú2s3zab
hús2z
hú1s1za
hú2sz1ál
hú1s1zá
h2ú2s1z1e2
h2ú2s1z1í2
hú2szol
hú1s1zo
hú2szos
hú2s1z1ó2
h2ú2s1z1ü2
1hü
hü2g2y
2h1ünn
hü2re
hü2rü
hü2tő
h1ü2vö
1hű
hű2ré
hű2ri
hű2rö
hy1ér
hy1év
hy1ig
2i.
i1a
iaa2d
ia1a
iaát1
ia1á
iaá2ta2
i2abaj
ia1ba
i2aber
ia1be
i2abes
i2a1bí
i2abon
ia1bo
i2abor
i2a1bö
i2a1bő
i2a1bu
i2a1bú
i2a1bü
i2a1bű
ia2c1al
ia1ca
iac3c
i2acet
ia1ce
ia2c1é1lé
ia1cé
ia2c1é2r.
ia2c3h
ia2cid
ia1ci
iac1i2ko
ia2c1im
ia2c1int
i2a1cí
ia2c1or
ia1co
ia2có
ia2cö
ia2cő
ia2c3sé
iac2s
iac3sp
iac3st
iac3s2z
ia2cü
ia2c2z
iadás1s
ia1dá
i2a1de
i2a1dí
ia2dot
ia1do
ia2dó1e
ia1dó
iadó1st
i2a1dö
i2a1dő
i2a1dú
iae2l
ia1e
iae2r
iae2t
iaé2r
ia1é
i2a1fá
i2a1fe
i2a1fi
i2a1fl
i2a1fó
i2a1fö
i2a1fő
i2af1ri
i2a1f1ro
i2a1fu
i2a1fú
i2a1fü
i2a1fű
i2a1gá
i2a1ge
i2a1gé
i2a1gi
i2a1gö
i2a1gő
i2a1gu
ia2gyu
iag2y
i2aig
ia1i
i2aip
i2ai2z
ia2ján
ia1já
i2a1je
i2a1jo
ia2kad
ia1ka
i2a1ká
i2a1ke
i2a1kí
i2a1k2l
i2akód
ia1kó
i2akór
i2a1kö
i2a1kő
i2a1k2re
i2a1k2ré
i2a1k2ri
i2a1k1rí
i2a1ku
i2a1kú
i2a1kü
i2a1kv
ia3lan
ia1la
ia2lat
i2aleg
ia1le
i2alib
ia1li
i2a1lí
ia2lom
ia1lo
i2a1lö
ia2lud
ia1lu
ia2lus
i2al2y
i2a1má
i2a1me
i2a1mó
ia2m1ur
ia1mu
i2a1mú
i2a1mű
ia2nek
ia1ne
i2a1né
i2a1nö
i2a1nő
iao2k
ia1o
iaó2r
ia1ó
ia2pát
ia1pá
i2a1pe
i2a1pé
ia1p2l
i2a1po
ia1p2s
iará1di2
ia1rá
i2a1re
ia2rén
ia1ré
i2a1ro
i2a1ró
i2a1rö
i2a1ru
i2a1rú
i2a1se
i2a1sh
i2a1si
i2a1s2ká
i2a1s2l
i2a1s2m
i2a1só
i2a1sö
i2a1s2p
iast2
i2a1s2ta
i2a1s2tá
i2a1s1ti
i2a1s1to
i2a1str
i2a1sú
i2a1sü
i2a1sű
ia2sz1an
ias2z
ia1s1za
i3as2ze1rű
ia1s1ze
ia2sze2s
ia2szép
ia1s1zé
iasz2k1e2r
iasz1ke
ia2szop
ia1s1zo
i2a1s1zú
i2a1te
i2a1tó
i2a1tö
i2a1tő
i2a1t1ré
ia2t2y
i2aud
ia1u
i2au2r
iau2s
iaü2z
ia1ü
ia2vat
ia1va
i2a1vé
i2a1vi
i2a1ví
i2a1vo
i2a1zá
i2a1ze
ia2zo
i2az2s
i2a1zú
i1á
iá2c2s
iá2ga
iá2gá
iá2ge
iá2gi
iá2go
iá2g2y
iá2hí
iá1ka2
iá2kab
iá2kak
iá2k1al
iá2k1an
iá2k1ap
iá2k1ar
iá2k1as
iá2k1á2
iá2keb
iá1ke
iá2k1el
iá2kem
iá2k1en
iá2k1e2s
iá2k1é2r.
iá1ké
iá2k1érd
iá2kés
iá1ki2
iá2kin
iá2kir
iá2ki2t
iá2kí
iá2kop
iá1ko
iá2k1or
iá2k1os2z
iá2k1ó2
iá2k1ö
iá2kő
iá2ku2r
iá1ku
iá2k1ut
iá2k1ú2
iá2k1ü
iá2kű
iá1la2
iál1a1na
iá2lál
iá1lá
iá2l1ár
iá2l1e2
iá2lim
iá1li
iá2l1in
iá2lop
iá1lo
iá2nar
iá1na
iá2n1as
iá2nem
iá1ne
iá2nir
iá1ni
iá2nis
iá2nö
iánt2
ián1tr
iá2nü
iá2ny1ad
ián2y
iá1nya
iá2ny1a2l
iá2nyan
iá2nyar
iá2ny1e2
iá2nyérz
iá1nyé
iá2nyö
iá2ó1i
iá1ó
iá2po
iá2rad
iá1ra
iá2rak
iá2ram
iár2das
iár1da
iár2d1e
iár2d3z
iá2re
iá2rim
iá1ri
iár1s2
iá2ru
iá3run
iá2rú
iá2sal
iá1sa
iá2sar
iá2s1as
iá2s1á2g
iá1sá
iá2s1ám
iá2sás
iá2s1e2
iá2sikr
iá1si
iá2sí
iá2sor
iá1so
iá2só
iá2sö
iá2ső
iás3s1zo
iás2s2z2
iást2
iás1tr
iá2s1ü2
iá2sű
iás1ví2
iá2szás
iás2z
iá1s1zá
iá2s3ze
iás3z2s
iá2ta
iá2t1e2l
iá1te
iá1ti2
iá2t1ir
iba1d2
i1ba
ibas2
ibat2
iba1u2
iba1ü2
ibá2l1a
i1bá
ibe2lér
i1be
ibe1lé
ibe2r1in
ibe1ri
ibe1s
ibi2o
i1bi
ib2lo
ib2ró
ib2ru
ica1f2
i1ca
ica1g2
ica1kl
ica1k2r
ica2los
ica1lo
ica1pr
ica1t2
icca2l
ic1ca
ic2can
ic2c1á2
ic2c3h
ic2cin
ic1ci
ic2cir
ic2cí
ic2c1o
ic2c1ö
iccse2l
ic2c2s
ic1c1se
iccs1ol
ic1c1so
ic1c1s1ő2
ic2cú
ic2c2z
ic3h2a.
ic2h
i1c1ha
ic3hek
i1c1he
i2chi1ná
i1c1hi
ic3hoz
i1c1ho
i2c3hű
ici2t1a2
i1ci
ici2tá2r
ici1tá
ici2tel
ici1te
ic2i2ter
i1c2lu
i2c1ol
i1co
i2cs1a2d
ic2s
i1c1sa
ics1a2la
icsa2p1á2g
icsa1pá
i2cs1a1u
i2cs1ág
i1c1sá
ics1áll
i2cs1eb
i1c1se
i2cs1e2g
icse2t
i2cs1e1te
i2cs1ev
i2cs1é2g
i1c1sé
i2cs1é2r2é.
icsé1ré
ics1i1pa
i1c1si
ics1s
ics1út
i1c1sú
i2cs1ül
i1c1sü
i2cs1ü2t
ic3s1ze
ics2z
ic1üz
i1cü
i2d1a2j
i1da
id1a1na
id1a2n2y
i2d1a1u
id1áll
i1dá
id1á1ru
i2d1ásv
id1br
1id1do
id3d2z
ide2av
i1de
ide1a
ide2g1á
ide2g1él
ide1gé
ide2g1é2r.
1i2dej
ide1k2v
2idel
id1elj
id1elm
id1e1lo
id1elt
i2d1emb
ide1p2
ide2red
ide1re
i2de1ro
i2de3s1a2
ide1u2
i2d1é2g
i1dé
idé2ke2l
idé1ke
i2dén2y
idér2c3s
2idés2z
1i2déz
id2ge
idi2as
i1di
idi1a
1i2dil
id1ionj
idi1o
id1ionn
i2d1i2o1no
i2d1i2ont
idi2os
idi2ód
idi1ó
i2d1i1ta
idíja2d
i1dí
idí1ja
idí2j1a1da
id1kr
id1old
i1do
i2d1olv
ido2mac
ido1ma
ido2m1an
1i2do1má
1i2do1mo
1i2do1mú
id1ös
i1dö
1i2d2ő.
i1dő
1i2dőb
1i2dőd
1i2dő1é
1i2dőh
1i2dő1i
1i2dők
1i2dőm
i2dő1ne
i2dő1né
i2dőnk
1i2dőp
1i2dőr
1i2dős
idő2so2d
idő1so
idő1s2p
1i2dőt
idő2tál
idő1tá
1i2dőv
i2dőz
id1pr
id2rót
id1ró
i1d2ru
id1st
id1t2r
id1u2t
i1du
id1üg
i1dü
i2d1üz
i2d3zá
id2z
i2d3ze
i2d3zó
i2d1z1s1a
idz2s
i2d1z1s1á
i2dzsen
i1d1z1se
i2d2zsél
i1d1z1sé
i2d1z1s1í2
i1e
ie2be
ie2c2s
ie2d2z
ie2f1a2
ie2fá
ie2f1i
ie2f1ü2
ie2gé
ie2g2y
ie2he
i2eld
ie2lo
ie2lő1a
ie1lő
ie2ma
ien2sá
ien2s1o
iens3s
ie2n2y
ie2pe
ie2r1a2d
ie1ra
ie2rag
ie2r1aj
ie2r1a2k
ie2ral
ie2ram
ie2r1an
ie2ras
ie2r1á
ier1d2
ie2reg
ie1re
ier1eg2y
ie2r1el
ie2r1ember
ierem1be
ie2r1est
ie2r1i2ga
ie1ri
ie2r1in
ie2r1is1te
ie2rí
ie2r1ol
ie1ro
ie2ror
ie2rö
ie2rő
iers2z2
iert2
ier1tr
ie2r1u2
ie2r1ú
ie2r1ü2g
ie1rü
ie2sel
ie1se
ie2sem
ie2sett
ie2sés
ie1sé
ie2sik
ie1si
ie2ső
ie2s2z
ieté1s2z2
ie1té
ietz1
ie2ur
ie1u
ie2ve
ie2vé
ie2vi
ie2vő
i1é
i2é1do
i2é1fe
ié2ge
ié2gé
ié2gő
ié2gü
i2é1ha
ié2hes
ié1he
i2é1hi
i2éil
ié1i
ié2le
ié2lé
ié2li
ié2lő
i2é1me
i2é1mé
i2é1mo
ié2ne1ke
ié1ne
ié2nekh
ié2nek1k2
ié2ne1kü
ié2pí
ié2pü
ié2rek
ié1re
i2ére2n
ié2rez
ié2ré
ié2ri
ié2r2ő.
ié1rő
ié2rők
ié2rőt
ié2rü
i2é1s1zi
iés2z
iéta1s
ié1ta
ié2tel
ié1te
i2é1tö
ié2v2e.
ié1ve
ié2vek
ifa1st
i1fa
ifa1t2
i2f2e.
i1fe
ifenyőé2h
ifen2y
ife1nyő
ifenyő1é
ifi1o2
i1fi
if1ír
i1fí
1if1jí
1if1ju
1ifj2ú.
if1jú
1ifjúb
1ifjú1é
1ifjú1i
1ifjú1ké
1ifjún
1ifjúr
1ifjús
1ifjút
1ifjúv
i1f2la
if2le
if2lo
if2lö
if2lu
ifo1go2
i1fo
ifon1n
i1f2ri
i1f2ro
i1f2rö
if2ru
if2t1a
if2t1á2
if2t1e2l
if1te
if2tin
if1ti
if2tö
if2tú
ig1a2git
i1ga
iga1gi
ig1a2ka
ig1als
ig1alv
ig1an1d2
iga2nyal
igan2y
iga1nya
ig2a1nye2
iga2ny1es
iga2ras
iga1ra
1i2garz
iga1sl
iga2szag
igas2z
iga1s1za
igasz1al
igau2r
iga1u
1i2ga2z.
1iga1zí
ig1á2c2s
i1gá
ig1álm
ig1ásh
i3gás2z
ig1á2t1e2
ig1br
ig1d2r
ig1ed2z
i1ge
i2geg
igek2
ige1kl
ig1e2le
ig1ell
ig1elm
1i2genl
ige2rá
ig1e2rő
ig1e2se
ig1e1si
ige2tál
ige1tá
ige2teg
ige1te
ige2té2l
ige1té
ige2t1o
ige2tőr
ige1tő
ig1e1vi
i2g1ex
1i2géd
i1gé
ig1é2g
ig1é2li
ig1é2lő
ig1é2lü
ig1élv
i2gém
igé2na2
igé2ná2
1i2g2én2y
i3gé1pe
ig1é2pí
ig1é2r.
ig1érj
i2g1érl
ig1érn
ig1é2rü
ig1érv
ig1és2z
1i2gé1ü
ig1fl
ig1g2r
ig1ív
i1gí
ig1íz
ig1kl
ig1kr
ig2lac
ig1la
ig2na2d
ig1na
ig2n1e2g
ig1ne
igne2r
ig2n1os2z
ig1no
ig2nö
ig2nü
igo2rál
i1go
igo1rá
ig1o2s
igó1é2
i1gó
ig1öb
i1gö
ig1ö2k
ig1öl
ig1ö2z
ig1pr
i1g2raf
ig1ra
ig1sk
ig1sl
ig1sm
ig1sp
ig1st
ig1s2z
ig1tr
ig1ug
i1gu
igu2n
igu2t
ig1ús
i1gú
ig1üc
i1gü
ig1üd
ig1ü2g
igü2l
ig1üt
ig1üv
ig1űz
i1gű
i2gy2a.
ig2y
i1gya
i2gyam
i2gyák
i1gyá
i2gyál
i2gy1eg
i1gye
i2gy1e2kéh
igye1ké
i2gy1ékt
i1gyé
i2gy1é2r.
i2gy1ért
iha2re
i1ha
i1i
ii2de
ii2dé
ii2dő
ii2ga
ii2gá
ii2gé
ii2g2y
ii2ha
ii2je
ii2má
ii2mi
ii2pa
ii2ram
ii1ra
ii2rat
ii2rá
ii2ro
ii2s2z
ii2ta
ii2vá
ii2vo
ii2vó
ii2zé
ii2zo
i1í
ií2gé
ií2ra
ií2rá
ií2ro
ií2ró
ií2ru
ií2té
ií2ve
ií2ze
1i2jed
i1je
ije2gy1á2
ijeg2y
1i2jes
ik2abe2j1
i1ka
ika1be
ikabe1já2
i2k1abl
ik1ajt
ika2lak
ika1la
i2k1ang
ika1ó2
ika1p2l
ika1p2r
ika2ró2r
ika1ró
ikas2
ika1sp
ika1t2r
i2k1árk
i1ká
i2k1á1ru
ikás1s
iká2tol
iká1to
ik1dr
ik1e1bé
i1ke
i2k1eg
ik1e1lo
ik1eng
ike2r1a2
ike2r1á2
ike2r1e2d2z
ike1re
ike2r1e2l
ike2r1ev
ike2ris
ike1ri
ike2r1o
ik1e2rő
iker1s2
ike2ru
ike2t1ült
ike1tü
ik1e1vo
i2ké2kekk
i1ké
iké1ke
i2k1é2pí
i2k1é1pü
i2k1érz
ik1fl
ik1fr
i2k1id
i1ki
ik1i2ko
ik1ikr
i2k1ind
ik1ins
i2k1int
i2k1i2o
ik1isk
ikka2l
ik1ka
ik2k1a1la
ikk1an2y
ik2k1a2r
ikk1á1ra
ik1ká
ik2káz
ik2kev
ik1ke
ikk1ér1de
ik1ké
ik2kin
ik1ki
ik2k1i2p
ik2k1ol
ik1ko
ik2k1ó
ik2k1ös
ik1kö
ik2köt
ik2k1ö2z
ik2k1u2
ik2küz
ik1kü
ikla1tr
ik1la
ik2ler
ik1le
ik2lor
ik1lo
i1k2lub
ik1lu
ik2lum
i1knéd
ik1né
iko1ma2
i1ko
iko2m1ar
1i2kon1bá
1i2konén
iko1né
1i2konl
1i2kon1ta
1i2kon1tö
1i2kon1tü
1i2konz2s
ikonz2
ikó1p
i1kó
ik1ó2rá
ik2ó2s3zá
ikós2z
ikő2re2s
i1kő
ikő1re
ik1pl
ik1pr
1ikre1i
ik1re
1ikrek
ik2rém
ik1ré
i1k2róm
ik1ró
i1k2ru
ik1st
ikszind2
iks2z
ik1s1zi
iksz2t2
iktus1s2
ik1tu
iktu2s2z
2ik2u.
i1ku
2ikub
ik1udv
2ikuf
2ikuh
2iku1i
2iku1í
2ikuj
2ikuk
2ikur
iku2sav
iku1sa
2ikut
2ikuv
ik1ü2v
i1kü
i2l1abr
i1la
il1a2ce
il1a2cé
il1a2dó
i1l1a2la
il1ald
i2l1alk
il1amb
ila2n
i2l1a1na
i2l1an2y
ila2pin
ila1pi
ila2pol
ila1po
i2l1a2r
ilá2g1e2
i1lá
ilág3g
ilá2gö
i2l1á2gyo
ilág2y
ilány1fé2
ilán2y
i2l1á2p
i2l1árn
il1á2ro
il1á1ru
il1á2rú
ilá2s2z
i2l1á1s1zo
il1átf
il1átm
il1átr
il1bl
il2c1a2
ilc3c
il2c2h
il2c3sap
ilc2s
il1c1sa
il2c3sik
il1c1si
ilc3s2z
il2c2z
il1e2lem
i1le
ile1le
i2l1emb
i2l1eml
il1exp
ilé2n1á2
i1lé
i2l1é2nekh
ilé1ne
i2l1é2nekn
ilé1sp
il1f2l
il1fr
il1g2r
i2l1icc
i1li
il1i1de
i2l1igáh
ili1gá
i2l1igáj
i2l1igás
i2l1igát
ili1g1ra
i2l1i2ko2n.
ili1ko
i2l1i2konb
i2liko1né
i2l1i2konh
i2l1i2konj
i2l1i2konn
i2l1i2ko1no
i2l1i2konr
i2l1i2kont
i2likonz2
i2l1ill
i2li2m2a.
ili1ma
il1i2mi
il1im1p2
ilin1n
ili2p1á
il1i1ró
il1k2l
il1k2r
illa2g1ó2
il1la
1illatb
1illatr
1illatt
il2l1es1te
il1le
il2l1es1té
1illé1sé
il1lé
illé2t
ill1é1te
ill1étt
il2l1id
il1li
illig2
illi1gr
il2l1ö
1il1lu
1il1lú
ilm1a1da
il1ma
il2m1ag2y
il2m1aj
ilm1a1ka
il2m1a2l
ilm1ank
ilm1an2y
il2m1ap
il2m1arc
ilm1atl
il2m1a2z
il2m1á2l
il1má
ilme2g
il1me
il2m1e1gé
il2m1eg2y
il2m1e2l
il2m1ep
il2m1e2r
il2m1es2z
il2m1é2j
il1mé
il2m1ék
ilmé2l
il2m1é1le
il2m1é2r.
il2m1érd
ilm1é2rést
ilmé1ré
il2mér1te
il2m1ér1té
il2m1és
il2mid
il1mi
il2m1i2k
il2mim
il2mir
il2m1is
il2miz
il2m1í2
il2m1ok
il1mo
il2mol
il2m1or
ilm1os2z
il2m1ó2r
il1mó
il2mö
il2mő
il1mu2
il2m1ut
ilo1g2
i1lo
i2l1or
ilót2
i1ló
iló1tr
il1öb
i1lö
il1ös
il1p2l
il1p2r
il1sh
il1sp
il1s2t
2il1te
ilu1mi2
i1lu
ilumin1
ilus3s
i2l1üg
i1lü
il1ür
il1üv
i2l1üz
ilva1k2
il1va
ima1gl
i1ma
im1akk
ima1ó2
ima1p
2ima2r.
i2marit
ima1ri
1i2mád
i1má
i2mákt
im1dr
imeg1g2
i1me
im1elem
ime1le
ime2m
i2m1e1me
ime2ra
ime2rin
ime1ri
i2m1érd
i1mé
im1inh
i1mi
im1in1té
imi2t1a2
imi2t1á2r
imi1tá
imi2tin
imi1ti
1im1mu
i2m1old
i1mo
i2m1om
im1ó2rá
i1mó
imót2
im1ös
i1mö
1im1pé
1im1pu
1imre1i
im1re
i2m1ür
i1mü
iműt2r
i1mű
i2n1abl
i1na
i2n1a2cé
i2n1a1dá
in1a1de
i2na1do
in1a2já
in1ajt
inaka2r
ina1ka
1i2naka2t.
i2n1akc
i2nakk
i2nakn
i2nakt
i2n1akv
in1ald
i2n1alk
in1all
1i2na2m.
i2n1a2mi2t.
ina1mi
i1n1a1na
ina1p1la
ina2rán
ina1rá
1i2na2s.
ina2sis
ina1si
1i2nasn
ina2tell
ina1te
i2n1ág
i1ná
iná2lad
iná1la
in1á2rak
iná1ra
in1árh
i2ná1ru
i2n1ásv
in1bl
in1br
in2c1a2g
in1ca
in2cal
inca2n
inc1elt
in1ce
in2c1él
in1cé
in2c1hi
inc2h
in2c3ho
in2c1is
in1ci
in2c1os
in1co
in2có
in2c1ö
in2cő
in2cs1an
inc2s
in1c1sa
in2cs1e2r
in1c1se
in2cs1é2j
in1c1sé
in2cs1é2r.
inc3sérv
in2csor
in1c1so
inc3so2r.
in2c1sú
in2c3süt
in1c1sü
inc3s1za
incs2z
in2cú
in2d1ab
in1da
1inda1i
in2d1az
in2deb
in1de
in2d1ed
in2d1e2g
ind1e1kö
in2d1e2m
in2d1ett
1index
in2d1e2z
in2d1ég
in1dé
in2d1én
in2dés
ind1ink
in1di
1indiv
1indít
in1dí
in1dö2
in2d1ör
in2dös
in1d1ra
1indul
in1du
2in2e.
i1ne
i2n1e2dé
i2n1ef
ineg1g
in1e2g2y
ine2ku
i2n1e2l
in1e1mu
i2n1en2y
2iner
i2n1erd
i2n1erj
ine2t1a
ine2t1ér
ine1té
ine2tü2l
ine1tü
i2n1ex
2inéb
i1né
in1é2ge
iné1k1ré
iné2l
i2n1é1le
i2n1élt
i2n1élv
i2n1é2pí
i2n1é2r.
i2n1érd
i2n1é1ré
in1ér1te
iné2tá
iné2te
2inév
1infek
in1fe
1infl
in2g1a2dó
in1ga
in2g1a1la
ing1áll
in1gá
ing1á1rá
ing1á2ré
ing1á2ro
ing1árt
ing1á1ru
ingás3s
1ingec
in1ge
in2g1eg
1inge1i
ing1el1já
ing1elk
in2g1ell
ing1els
in2g1enc
1ingerb
1ingerc
1inge1ré
inge2r1és
1ingerg
1ingerh
1inge1ri
1ingerk
1ingerm
1ingern
1ingerp
1ingerr
1ingers
1ingert
1inge1rü
in2g1e2v
in2g1é2j
in1gé
in2g1ék
in2g1él
ingés3s
ing3g
ing1i2na
in1gi
in2gí
in1g1lo
in2gor
in1go
in2g1öl
in1gö
in2g1ös
ing2rá2d.
ing1rá
ing2rádb
ing2rádd
ing2rádn
ing2rá1do
ing2rádt
1in2g1uj
in1gu
in2g1u2t
in2gú
ing1ült
in1gü
2ini1e
i1ni
2ini1é
i2nigar
ini1ga
i2n1i1ge
in1ik1ra
ini1k1ro
i2n1ill
i2n1i2m2a.
ini1ma
in1i2mi
i2n1ind
2ining
i2n1inh
i2n1i2o
2inir
ini2s1ég
ini1sé
i2n1isk
i2n1ism
i2n1i2tal
ini1ta
2ini1u
i2n1íz
i1ní
1injekc
in1je
ink1a1cé
in1ka
in2k1a2d
in2k1a1to
in2k1árn
in1ká
in2k1es2z
in1ke
ink1ér1té
in1ké
in2ki1o
in1ki
ink1old
in1ko
ink1or1só
inkor1s2
in2kös
in1kö
1inkub
in1ku
1inn2a.
in1na
in2nor
in1no
i2n1ob
i1no
i2n1okl
i2n1old
i2n1olt
i2n1olv
in1org
i2n1ox
in1ó1da
i1nó
in1ó2dá
inó2rá
i2n1öl
i1nö
in1ön
in1őz
i1nő
in1pl
in1pr
in1s2k
in1s2m
1ins1pi
in1spr
1ins1ta
in1s2to
in2t1ak1tu
in1ta
int1ann
int1ára1i
in1tá
intá1ra
int1árak
int1árat
in2t1á2rá
in2t1árf
in2t1á1ri
int1áron
intá1ro
in2t1árr
int1árs2z
in2t1á1ru
int1á2ta
1inte1ge
in1te
1integr
in2t1e2g2y
int1előt
inte1lő
int1el1té
in2t1enn
in2t1e2n2y
1intenz
in2t1e2rez
inte1re
1inter1fé
int1es2s2z
inte2t1ős
inte1tő
in2t1é2j
in1té
in2t1és2z
1inté1zé
1intézk
1intézm
1inté1ző
2intézőc
in1t2hos
int1ho
in1t1hu
in2tid
in1ti
in2ti1gé
int1il1la
in2t1ing
in2t1ip
in2t1i1vá
in2t1i2z
int1oml
in1to
in2t1os2z
in2t1ös
in1tö
intőkés2z1
in1tő
intő1ké
int1ő2r.
int1u1ra
in1tu
intus1s2
in2t1ut
in2tús
in1tú
in2t1út
i2n1ug
i1nu
i2n1uj
in1új
i1nú
in1ú2s
i2n1ú2t
i2n1ü2g
i1nü
in1ült
in1ünn
in1ür
in1üs
i2n1ü2t
i2n1ü2v
i2n1ü2z
in1űr
i1nű
1inven
in1ve
in2xa
1in1zu
i1o
ioá2r
io1á
io1b2r
io2c2s
io1d2r
io2dú
i2o1gá
iog2raf
iog1ra
i2o1g2rá2f.
iog1rá
i2o1g2ráff
i2og2y
io2ik
io1i
io2ká
io2kí
io2ko
io2ku
i3old
io2l1i2v
io1li
iol1k2
iol1okk
io1lo
i2o1ló
i3olv
io2mar
io1ma
io2m1árt
io1má
io2mil
io1mi
io2mö
io2nad
io1na
io2n1a2g
i2o2nak
io2n1a2n
io2n1a2r
io2n1as
io2n1a2t
io2n1av
io2n1át
io1ná
io2neg
io1ne
io2n1e2l
io2ne2n
ionim1
io1ni
io2nin
ion1k2
io2nop
io1no
io2n1oszt
ionos2z
io2nö
ions2
ion1st
ion1t2r
io2pe
io1p2r
io2r1a
io2r1i2ko
io1ri
io2so
io1sz2f
ios2z
i2o1te
io2xidj
io1xi
io2xidt
i1ó
ióa2d
ió1a
ió2ap
ióá2g
ió1á
ióá2r
ióá2t1a2
i2ó1bá
i2óbes
ió1be
i2ó1bé
i2ó1bí
i2ó1bl
i2ó1bo
i2ó1bö
i2ó1bő
i2ó1b2r
i2ó1bu
i2ó1bú
i2ó1bü
i2ó1bű
i2ó1ce
i2ó1cé
i2ó1ci
i3ócsk
ióc2s
i2ó1cu
i2ó1de
i2ó1dé
i2ó1di
i2ó1dí
i2ó1dó
i2ó1dö
i2ó1d1rá
i2ó1dú
i2ó1dü
i2ód2z
i2óég
ió1é
i2óék
i2óél
i2óép
i2óés
i2óé2v.
i2ó1fá
i2ó1fe
i2ó1fi
i2ó1fl
i2ó1fó
i2ó1fö
i2ó1fő
i2ó1f2r
i2ó1fu
i2ó1fü
i2ó1fű
ió1g2r
i2ó1ha
i2ó1há
i2ó1he
i2ó1hé
i2ó1hi
i2ó1hí
i2ó1hó
i2ó1hö
i2ó1hő
i2ó1hu
i2ó1hü
i2ó1hű
iói2g
ió1i
i2óip
i2óis
i2óiz
ióí2v
ió1í
i2ó1je
i2ó1jo
i2ó1jó
ió2kad
ió1ka
ió2kaj
iók1arc
ió2k1aszt
iókas2z
ió2kál
ió1ká
ió2k1e2g
ió1ke
ió2k1i2d
ió1ki
i2ó1kí
iókköz1
iók1kö
iók1old
ió1ko
i2ókort
i2ó1kö
i2ó1kő
ió2küz
ió1kü
i2ó1la
i2ó1lá
i2ó1le
i2ó1lé
i2ó1li
i2ó1lo
i2ó1me
i2ó1mó
i2ó1mu
i2ó1mú
i2ó1mű
i2ó1ne
i2ó1nó
i2ó1nö
ióo2k
ió1o
ióó2r
ió1ó
ió1p2s
ió2rab
ió1ra
i2órag
i2órak
i2óran
i2órap
ió2ras
i2órád
ió1rá
i2ó1re
i2ó1ré
i2óri1a
ió1ri
i2ó1ro
i2ó1rö
i2ó1ru
ió2s1aj
ió1sa
ió2sel
ió1se
ió2s2i.
ió1si
i2ó1s1lá
ió2só
iós3s
i2ó1s2tá
i2ó1st2r
i2ó1sú
ió1s2z2
i2ó1te
i2ó1té
i2ó1ti
i2ó1tí
i2ó1tö
i2ó1tő
i2ó1t2r
i2ó1tu
i2ó1tú
i2ó1tü
i2ó1tű
i2óug
ió1u
i2óun
i2óur
i2óut
i2óvár
ió1vá
i2óvás
i2ó1ve
i2ó1vé
i2ó1vi
i2ó1ví
i2ó1vö
i2ózár
ió1zá
i2ó1ze
i2óz2s
i1ö
iö2kö
iö2le
iö2lé
iö2li
iö2lö
iö2lő
iö2mö
iö2re
iö2rö
iö2rü
iö2tö
iö2ve
iö2zö
i1ő
iő1dr
i2ő1ha
i2ő1ké
i2ő1ku
i2ő1ra
iő2ri2
i2ő1s2p
i2ő1st
i2ő1te
i2ő1té
i2ő1va
i2ő1vá
ipa2c1se
i1pa
ipac2s
1i2pa2r.
1i2par2a.
ipa1ra
ipa2ral
ipa2rál
ipa1rá
1i2parán
1i2parát
1i2parb
ipa2r1en
ipa1re
ipa2r1es
1i2pa1ré
1i2parh
1i2paril
ipa1ri
1i2parin
ipa2ris
1i2parm
1i2parn
i2parok
ipa1ro
i2paron
1i2parr
1i2pars2
i2par1ta
1i2par1tá
1i2par1te
1i2par1tó
1i2pa1ru
ipa1u2
ipánk2
i1pá
ipán1n
ip1átm
i2p1ef
i1pe
ip1e2g2y
i2p1e2lu
i2p1e1sé
i2p1ev
ip1fl
ip2fu
ip1kl
ip1kr
ipo1kl
i1po
ipor2tel
ipor1te
ipor2t1ő
ip1ö2l
i1pö
ip1ös
ip2p1a2d
ip1pa
ip2pa2j
ip2par
ip2pár
ip1pá
ipp1ing
ip1pi
ip2pö
i1p2rof
ip1ro
i1p2rog
i1p2roj
ip2rop
i1p2rot
ipsz1a2l
ips2z
ip1s1za
ipsz1ál
ip1s1zá
ip2sz1emb
ip1s1ze
ip2szip
ip1s1zi
ip2s1z1í2
ipsz1or
ip1s1zo
i2p1ug
i1pu
ira1lo2
i1ra
1i2ra1ma
1i2ra1má
iramis1s
ira1mi
ira2tal
ira1ta
ir2ata2n
ira2t1an2y
ira2t1as
ira2t1at
ir2a2tál
ira1tá
1i2ratb
1i2ratc
1i2ratd
ira2tel
ira1te
1i2ra1té
1i2ratf
1i2ra1ti
2i3ra2till
1i2ratm
ira2t1ol
ira1to
1i2rato2z.
ira2t1ö2
1i2ratp
1i2ratr
1i2ra1tü
irádi2ók
i1rá
irá1di
irádi1ó
irádi2ót
irá2f1a2
irá2g1al
irá1ga
irá2g1ál
irá1gá
irá2g1á2rak
irágá1ra
irá2g1á2ro
irá2g1á1to
irá2gáz
irá2g1e
irág1g
irá2g1ol
irá1go
irá2g1ó2r
irá1gó
irá2gö
irá2l1a
irá2l1e2
1i2ránn
1i2rán2y
irá2nyal
irá1nya
irá2nye2
ir2c2h
i2re1i
i1re
1ir1ga
irin2c
i1ri
iri2zo
irka1s
ir1ka
irkas2z2
ir1ke1
2ir2o.
i1ro
2irob
1i2rod2a.
iro1da
1i2ro1dá
iro1g2r
iro2ka
iro2ká
iro2k1e2
iro2kér
ir2o1ké
iro2l1a
iro2m1a
iros3s
iró2ke
i1ró
i2ró1no
1ir1ri
ir1sa2
ir2s1al
ir2s1á2
ir2sil
ir1si
irs3s
ir2s1ü
ir2s2z
ir1té2
ir2t1él
ir2tiz
ir1ti
ir2t1o2r
ir1to
ir2t1ö2
ir2tür
ir1tü
iru2s1ze
i1ru
irus2z
i2s1abl
i1sa
is1a2da
is1a2dá
is1a2do
i2s1a2g
i2s1a1ja
is1ajk
isa2k1e
isa2kol
isa1ko
is1a2la
i2s1alf
i2s1alj
is1alk
i2s1alm
is1amb
isa2n
i2s1a1na
i2s1a1no
i2s1ant
i2s1an2y
isa2p
is1a1pá
is1a1po
is1a2rá
is1ass
i2s1aut
isa1u
i2s1a2z
i2s1ábr
i1sá
is1ág2g2y
isá2gy1út
i4s1á2g2y
isá1gyú
is1áll
is1á2po
i2s1á2rak
isá1ra
i2s1á2rá
i2s1árb
i2s1árf
i2s1árh
i2s1ár1ké
isár1k2
i2s1árn
isá2ron
isá1ro
i2s1árr
i2s1árt2
i2s1á2ru
i2s1á2rus1
i2s1á2t1a2
i2s1átk
i2s1átl
i2s1átv
is1bl
is1br
is1dr
is1ed2z
i1se
i2s1eff
i2s1e1ge
i2s1e2gér
ise1gé
i2s1e2k2e.
ise1ke
i2s1elf
is1elm
i2s1e1lo
i2s1eml
i2s1enc
i2s1e1ne
is1eng
i2s1e2pi
i2s1ep1ri
i2s1e2rő
is1e2set
ise1se
is1e1sé
isé2ge2l
i1sé
isé1ge
isé2gés
isé1gé
isé2gid
isé1gi
iségkés2z1
i3ségk
iség1ké
is1élv
i2s1é2r.
i2s1é2ri
i2s1ér1té
i2s1é2v2e.
isé1ve
i2s1é2vet
i2s1é2vén
isé1vé
i2s1é2vét
i2s1évh
i2sé2v2i.
isé1vi
isföl2
is1fö
is1fr
is2hin
is1hi
is2his
is1i1do
i1si
isi2g
i2s1i1ga
i2s1ind
i2s1inf
is1int
i2s1inv
isi1ó2
i2si1pa
isi2par
i2s1i1ro
i2s1isten
isist2
isis1te
is1i1ta
is1i2zo
i1s2ka1tu
iskat2
is1ka
is1kl
is1k2r
is1kv
is2lag
is1la
i1s2lat
1ism2e.
is1me
1ismek
1isme1re
1ismérv
is1mé
2is1mo
2is1mű
i2s1ob
i1so
i2s1o2l
i2s1orc
i2s1ord
iso2ros2z
iso1ro
i2s1or1ra
i2s1or1ró
is1orv
i2s1os1ko
i2s1os2z
i2s1ott
is1ó2rá
i1só
i2s1öb
i1sö
i2s1ö2c
is1öl
is1ön
isö2r
i2s1örd
is1ö1re
is1ös
is1ö2v
is1ő2r
i1ső
i1s2pek
is1pe
isp2r
i1s2pur
is1pu
is1s2p
is1s1ta
is1s1tá
issz1e2re1i
is2s2z
is1s1ze
issze1re
issz1e2rek
issz1erem
issz1e2res
is3szig
is1s1zi
is3szil
is3s1zí
is3s1zó
is3s1zö
is3s1ző
is3s1zú
is3s1zű
ista1s
is1ta
istas2z2
1istáp
is1tá
1istenh
is1te
iste2n1o
i1s2til
is1ti
is1trez
ist1re
is1t1ré
is1t1ri
is1t1ro
1istv
i2s1ud
i1su
i2s1ujj
isu2t
is1u1ta
is1u1tá
i2s1új
i1sú
i2s1üg
i1sü
i2s1üst
i2s1ü2tőt
isü1tő
i2s1üz
isva2d1áss
is1va
isva1dá
i1svin
is1vi
isvíz1
is1ví
isza2k1o
is2z
i1s1za
i2sza2p1á2
isza2p1e
is3zá1rá
i1s1zá
isz1ár1k2
is3zárl
is3zá1rú
isz1e2g2y
i1s1ze
i2sz1elv
is3ze1ne
isz1esem
isze1se
i2széj
i1s1zé
isz2fér
isz1fé
i2szi1mi
i1s1zi
i2sz1ing
isz1isk
isz1ist2
isz1kl
isz2k1ö2v
isz1kö
isz2k1ú
isz1öl
i1s1zö
isz1ös
isz3s
isz2ta1ti
isz1ta
isz2t1á2ras
isz1tá
isztá1ra
isz2t1árb
isz2tárt2
isz2t1ékn
isz1té
isz2t1é2r.
isz2t1ill
isz1ti
iszt1i2na1i
iszti1na
isz2t1ö2l
isz1tö
isz2t1ő2r.
isz1tő
isz2tüz
isz1tü
i2sz1ü2g
i1s1zü
i2szüt
i2s3zűrödn
i1s1zű
iszű1rö
it1a2cé
i1ta
ita1d2
i2t1a2dó
ita1g2r
it1a1gya
itag2y
i2t1akk
i2t1akn
1i2ta2l.
ita2lad
ita1la
ita2l1á2rú
ita1lá
i2talb
ita2l1el
ita1le
ita1lé2
i2talh
itali2n
ita1li
ita2li1na
i2talj
i2talk
it1al1lo
1i2talm
1i2taln
i2ta1lo
ita2l1ó2
1i2talr
i2talt2
i2ta1lu
it1a1nó
it1ant
i2t1a1ra
i2t1a1u
it1ác2s.
i1tá
itác2s
i2t1á2g.
it1á2ga
itá1na2
itá2n1at
itá2rak
itá1ra
i2t1á2ram
it1á2ras
it1á2rat
itá2rár
itá1rá
itá2rát
i2t1á2ri1a
itá1ri
itá2ri2g
itá2ris
itá1ró2
itá2r1ór
itá2ruk
itá1ru
itá2rú
itáskés2z1
itás1ké
itá2s3z
itá2tal
itá1ta
it1átf
it1bl
it1br
it1dr
ite2l1a
i1te
ite2l1á
ite2leg
ite1le
i2telemz
ite2leng
ite2le1sé
ite2lex
ite2lél
ite1lé
ite2l1é2r.
ite2linj
ite1li
it1el1já
i2t1ellen
itel1le
it2elmél2y
it1elmél
itel1mé
ite2lo
ite2lőz
ite1lő
i2t1e2mel
ite1me
i2t1eml
i2t1e2p
2iter
i2t1e2rez
ite1re
i2t1e2rő
i2t1e2se1te
ite1se
it1ezr
ité2g
i1té
i2t1é1ge
it1é2le
it1élm
i2t1é2ret
ité1re
i2t1érz
ité2tek
ité1te
i2t1étt
it1fl
it1fr
it1gl
it1gn
it1gr
i1t2hot
it1ho
i2t1id
i1ti
iti2g
itigaz1
i2t1i2ga
i2t1i1gé
it1i2ko
it1ikr
itikus1s
iti1ku
it1ill
it1imp
i2t1ind
i2t1inf
it1i2pa
i2t1i1rá
i2t1i1ro
it1i1ró
it1isk
i2t1ism
it1i1s1za
itis2z
it1i2s1zo
i2t1íg
i1tí
i2t1íz
it1kl
itkos1s
it1ko
it1kr
ito2b
i1to
it1o1be
ito2k1aj
ito1ka
ito2kak
ito2k1ol
ito1ko
ito2n1á2
itop2la
ito2r1as
ito1ra
ito2rál
ito1rá
ito2ril
ito1ri
2itos2z
ito1sz2f
i2t1ov
itó1a2
i1tó
itó1f2
1i2tókán
itó1ká
i2t1ó2né
i2t1ónn
i2t1ónt
itó1p2
it1ó1rá
itós2
itó1sp
itó1st
it1önt
i1tö
i2t1ös
i2t1őrl
i1tő
it1pl
it1pr
it1sp
1ittad2
it1ta
1ittam
itta2n1á2s
itta1ná
itta2n1é
itta2n1ó2
2it1te
it2t1eg
it2t1i2na
it1ti
it2t1ing
it1t1ra
it1t1ró
1ittuk
it1tu
1ittun
it1ty1i
it2t2y
i2t1ug
i1tu
i2t1und
itu2n1i
itu2ral
itu1ra
i2t1u2rán
itu1rá
it1új
i1tú
i2t1üg
i1tü
it1üld
i2t1üst
i2t1üt
i1t2zé
i1t2zi
i1u
iu2ga
iu2go
iu2mab
iu1ma
iu2mac
iu2m1ad
iu2maf
iu2m1ag
iu2mal
iu2m1am
iu2m1a2n
iu2m1a2r
iu2m1as
iu2m1a2t
iu2m1av
iu2maz
iu2m1á2l
iu1má
iu2me2g
iu1me
iu2m1el
iu2m1en
iu2mer
iu2m1es
iu2mez
iu2mél
iu1mé
iu2m1é1ré
iu2m1i2d
iu1mi
ium1ill
iu2m1im
ium1inj
iu2m1i2p
iu2m1is
iu2m1iz
iu2mí
iu2mol
iu1mo
iu2m1or
iu2m1ó2
iu2mö
iu2mő
ium1p2
iu2mü
iu2na
iu2no
iu2ra
iu2rá
iu2ru
ius3s1ze
ius2s2z
iu2ta
iu2tó
iu2tu
iu2z2s
i1ú
i2ú1bá
i2ú1be
i2ú1bé
i2ú1bi
i2ú1bo
i2ú1bö
i2ú1br
i2ú1ci
i2ú1cí
i2ú1dá
i2ú1de
i2ú1do
i2úd2z
i2úél
iú1é
iú1fr
i2ú1ha
i2ú1há
i2ú1he
i2ú1hi
i2ú1hö
i2ú1hő
i2ú1hu
i2ú1hú
i2úif
iú1i
i2ú1je
iú2jí
i2ú1jó
i2ú1ke
i2ú1kí
i2ú1kl
i2ú1kó
i2ú1kö
i2ú1ku
i2ú1me
i2ú1mó
i2ú1mu
i2ú1mű
i2ú1ne
i2ú1né
i2ú1nö
i2ú1re
iú2ri
iú2ro
i2ú1ru
i2ú1se
i2ú1so
i2ú1sp
i2ú1ta
i2ú1tá
i2ú1te
iú2té
i2ú1tí
i2ú1tö
i2ú1tú
i2ú1ve
i2ú1vé
i2ú1vi
i2ú1vo
i2ú1ze
i1ü
iü2c2s
iü2ge
iü2g2y
iü2le
iü2lé
iü2li
iü2lö
iü2lő
iü2lü
iü2re
iü2rí
iü2rü
iü2te
iü2té
iü2ti
iü2tö
iü2tő
iü2tü
iü2ve
iü2vö
iü2ze
i1ű
iű2ze
iű2zé
iű2ző
iv2a2csal
i1va
ivac2s
iva1c1sa
iva2c1s1e
1i2va1dé
iva2r1a1i
iva1ra
iv2a2raj
iva2re
iva2rin
iva1ri
iva2rol
iva1ro
iva2ró
ivar1s2
iv2a2t1a2n
iva1ta
iva2t1e2
iv2a2tin
iva1ti
iva2tol
iva1to
iva2t1ó2
iva2t1ö
1i2vá2s.
i1vá
1i2vásb
1i2vá1sé
1i2vásn
1i2vásr
1i2váss
i2v2e.
i1ve
1ivot
i1vo
ivókés2z1
i1vó
iv2ókés
ivó1ké
ivós2
ivő1é2
i1vő
iv2ré
i2x1ab
i1xa
i2x1ad
i2x1an
i2x1ar
ix1as
i2x1ág
i1xá
ix1bl
i2x1ef
i1xe
i2x1eg
i2x1ex
i2x1ép
i1xé
ix1fr
i2x1im
i1xi
i2x1in
i2x1i1o
i2x1ir
i2x1is
ixi2t
i2x1ob
i1xo
i2x1op
ix1öd
i1xö
ix1ös
ix1őr
i1xő
ix1ős
ix1pl
ix1pr
i2x1új
i1xú
i2x1ül
i1xü
iz1akn
i1za
izala2g1
iza1la
iz1alk
izas2
iza1sp
i2z1árn2y
i1zá
iz1bl
ize2d1á2
i1ze
ize2d1ék
ize1dé
iz1eg2y
i2z1e2lem
ize1le
izele2tel
izele1te
i2z1e2lér
ize1lé
i2z1e2lő1í
ize1lő
i2z1ember
izem1be
izene2g
ize1ne
izen3n
ize2s1á2
i2z1ese1mé
ize1se
i2z1ev
i2zéd
i1zé
i2z1ég
1i2zéj
1i2zék
i2zél
i2z1é2p
1i2zé1sí
iz1fl
1izgal
iz1ga
1izgatot
izga1to
i2z1i1ga
i1zi
i2z1i1gé
iz1inf
iz1int
i2z1i1ro
i2z1isk
i2z1ism
izi1s2p
1izmok
iz1mo
1izmuk
iz1mu
izmus1s2
1iz1mú
iz1okt
i1zo
1i2zo1lá
izo2m1a
izo2mál
izo1má
1i2zom1b2
1i2zo1mé
1i2zomf
1i2zomm
1i2zomn
1i2zomr
1i2zom1s
1i2zomt
1i2zomz
izo1p2
i2zos
izo1sz1ta
izos2z
1i2zo1tó
izó2d1a2
i1zó
iz1ó1rá
iz1pl
iz1pr
i2zs1ad
iz2s
i1z1sa
izsa2ik
izsa1i
i2zsakn
i2zs1all
izs1a1ra
izsa3u2tók
iz2saut
izsa1u
izsa2u1tó
i2z4s1ág
i1z1sá
i2zs1ál
i2zsec
i1z1se
i2zs1em1be
i2zsev
izsé2t
i1z1sé
i2zs1é1te
i2zsil
i1z1si
i2z2s1imp
i2zs1ist2
i2zsi1ta
i2zsiz
i2z3sor
i1z1so
i2z1s1ő
izs1s
iz3st2r
i2zs1ül
i1z1sü
iz3s1ze
izs2z
iztos1s
iz1to
iz1udv
i1zu
izu1ra1
iz1ú2t
i1zú
i2z1ü2g
i1zü
i2z1üt
i2z1ü2z
1iz1zi
1iz1zí
1izzot
iz1zo
2í.
í1a
í1á
íba2l1
í1ba
í2bis
í1bi
íbo2r1as
í1bo
íbo1ra
ícius1s
í1ci
íci1u
íd1a2c
í1da
íd1a2d
íd1a2l
ídala2g1
ída1la
íd1a2n
íd1a2v
íd1a2z
íd1ág
í1dá
íd1ál
íd1á2r
íd1át
íd1bl
íd1e2g
í1de
íd1e2l
íd1e2m
í2d1é2g
í1dé
í2d1él
í2d1ép
í2d1é2r.
í2d1érz
ídi2g
í1di
í2d1i1ga
í2d1i1gé
í2d1in
í2d1i2r
íd1ív
í1dí
í2d1ol
í1do
í2d1om
í2d1os
íd1ös
í1dö
íd1ő2r
í1dő
íd1pr
íd1st
í2d1ud
í1du
í2d1ug
íd1új
í1dú
íd1üg
í1dü
íd1ün
íd1üz
í1d3zá
íd2z
íd3z2s
í1e
í1é
íé2le
í2g1a2g
í1ga
íg1e2p
í1ge
íge2s
í2g2ér
í1gé
í2g1op
í1go
íg1tr
í1gyá2
íg2y
í1i
íi2ro
í1í
í2j1a2dá
í1ja
í2j1a2dó
í2j1a2j
í2j1akc
í2j1a2l
íj1an2y
í2j1a2r
í2j1a2u
í2j1á2c
í1já
í2j1á2g
í2j1á1rá
í2j1á1re2
í2j1árt
í2j1á1ru
1í2jás
íjá2s1ze2
íjás2z
íj1ász1ka
íjász1k2
í2j1á1ta
í2j1á2te
í2j1áth
í2j1átl
íjá2tos
íjá1to
í2j1átt
í2j1á1tu
í2j1átv
í2j1á2z
íj1e2g
í1je
íj1e2l
íje1l2i
íj1e2m
íj1en
íj1e2r
íj1e2s
íj1ép
í1jé
íjé2r
í2j1é1re
íj1fr
í2j1i2d
í1ji
í2j1im
í2j1int
í2j1ir
í2j1i2rá2
íj1íg
í1jí
íj1ín
íj1kr
í2j1o2d
í1jo
í2j1ok
í2j1ol
í2j1os
í2j1ot
íj1ön
í1jö
íj1ös
íj1ö2v
íj1öz
íj1pl
íj1pr
íj1sk
íj1sp
íj1st2
í2j1ug
í1ju
í2j1u2t
í2j1út
í1jú
íj1üg
í1jü
íj1ü2t
í2k1abl
í1ka
í2k1a2c
í2k1a2g
í2k1a2l
í2k1an
ík1ar
íka2s
ík1as2z
ík1a2v
í2k1áb
í1ká
í2k1ág
ík1ál
í2k1á2r
í2k1eg
í1ke
í2k1e2l
ík1em
í2k1es
ík1ev
í2k1é2k
í1ké
í2k1é2r.
í2k1érb
í2k1é2rő
ík1fr
í2k1i2d
í1ki
í2k1ing
í2k1i2r
ík1ín
í1kí
ík1ír
ík1k2r
í1k2lu
ík1oll
í1ko
í2k1op
í2k1orn
í2k1orr
í2k1or1s2
íkö2l
í1kö
í2k1ö2v
ík1pl
ík1pr
ík1sp
ík1s2z2
í2k1ug
í1ku
í2ku1ni
í2k1u2r
í2k1u2t
í2k1ú2t
í1kú
ík1ü2v
í1kü
íl1aj
í1la
íl1a2k
íl1a2l
í2l1á2g
í1lá
ílá1si2
ílá2s1ik
ílás3s
ílá2s3z
í2l1át
íl1bl
íl1br
íl1e2g
í1le
í2l1érz
í1lé
íli2as
í1li
íli1a
í2l1i2m
í2l1i2r
í2l1is
íl1í2r
í1lí
íl1í2v
íl1kr
íl1ös
í1lö
íl1ö2z
íl1st
íl1tá2
íl2t1ár1k2
íl2t1á1ro
íl2t1árt
íl2t1e
íl2t1é2
í2l1u2r
í1lu
ílu2sab
ílu1sa
ílu2se
ílus3s
ílu2s3z
íma2n
í1ma
íma1p
í2m1á2l
í1má
ímá2ris
ímá1ri
ímás1s
ím1bl
ím1b2r
ím1dr
í2m1e2g
í1me
í2m1e2l1í2
í2m1elm
íme2r1a2
íme2r1á
íme2reg
íme1re
ím1e2rő
í2m1esem
íme1se
í2m1e2v
í2m1é2het
í1mé
ímé1he
í2m1é2k
í2m1ép
í2m1é2r.
í2m1é2ré
í2m1é2r2ő.
ímé1rő
í2m1é2rő1i
í2m1érr
í2m1ér1te
í2m1ér1té
í2m1i2d
í1mi
ími2g
í2m1i1ga
í2m1i1gé
í2m1ill
í2m1ind
í2m1inf
í2m1i1ra
í2m1is
í2m1i2v
ím1ír
í1mí
ím1kr
ím1o2k1
í1mo
ím1ol
ím1on
ím1o2p
ím1os
ím1ök
í1mö
ím1ö2l
ím1ös
ím1öt
ím1ő2r
í1mő
ím1p2r
ím1sp
ím1st
ím1u2r
í1mu
ím1u2t
í2m1üg
í1mü
í2m1ünn
í2m1üt
í2n1ab
í1na
ín1a2cé
ína2d
ín1a1da
ín1a1dá
ín1a1do
í2n1a2g
í2n1a2j
í2n1a1ka
í2n1akk
ín1a2la
ín1alj
ín1alk
í1n1a1na
ín1a2n2y
í2n1a2p
í2n1a2rá
í2n1arc
ín1ass
ín1atl
ín1att
í2n1a1u
í2n1az
í2n1ábr
í1ná
í2n1á2c
í2n1ág
í2nálhaj
ínál1ha
í2n1álm
í2n1á2p
ín1á2t1a2
ín1átc
ín1á2t1e2
ín1átf
ín1áth
ín1átl
ín1átm
ín1átt
ín1átv
í2n1á2z
ín1bl
ín1br
ín1d2r
í2n1ef
í1ne
í2n1e2g
í2n1e2k2e.
íne1ke
í2n1elc
í2n1e2le
í2n1elh
í2n1elj
í2n1elk
í2n1ell
í2n1elm
í2n1eln2y
í2n1e2lo
í2n1e2l1ö2
í2n1e2lőh
íne1lő
í2n1elr
í2n1el1tá
í2n1el1to
í2n1el1vá
í2n1e2mel
íne1me
í2n1eml
í2n1e2mu
íne2n
í2n1e1ne
í2nesd
í2n1e2s2z
í2n1e2vé
ín1ég
í1né
íné2l
í2n1é1le
ín1é1lé
ín1é1lő
ín1élt
í2n1é2p
í2n1é2r.
í2n1érd
í2n1é1ri
í2n1érl
í2n1érm
í2n1é2rü
íné2s1za
ínés2z
íné2szer
íné1s1ze
íné2szint
íné1s1zi
íné2s1zo
í2n1é1te
í2n1é2ven
íné1ve
ín1f2r
ín1g2r
íni2g
í1ni
í2n1i1ga
í2n1i1gé
í2n1i2ko
í2n1ill
í2n1ind
í2n1inf
í2n1ing
í2n1int
1í2ni1o
í2n1i2rá
í2n1i1ro
í2n1ism
í2n1i2z
ín1íz
í1ní
ín1k2l
ín1k2r
ín1k2v
ín3nyú
ín2n2y
í2n1ob
í1no
í2n1ol
í2n1op
í2n1or
í2n1os2z
ín1ó2l
í1nó
ín1ön
í1nö
ín1ör
ín1ös
ín1öt
ín1ö2v
ín1ö2z
ín1pl
ín1pr
ín1ps
ín1s2k
ínso2k1
ín1so
ín1s2p
ín1s2t2
ín1s2z2
ín1t2r
ín1ug
í1nu
ín1új
í1nú
í2n1üd
í1nü
í2n1ü2g
í2n1ült
í2n1ü2t
í2n1ü2v
íny1e2c
ín2y
í1nye
íny1e2g
íny1el
íny1e2r
íny1ing
í1nyi
í1o
í1ó
íó2vo
í1ö
í1ő
í2p1a2g
í1pa
í2p1a2n
í2p1álc
í1pá
í2p1él
í1pé
ípés3s
í2p1i2z
í1pi
íp3ro
í2p1uj
í1pu
ípu2sa2n
ípu1sa
ípus3s2
ípus3z
ír1a2dó
í1ra
ír1akc
ír1akn
ír1akt
ír1a2la
ír1alj
í2r1alk
íra1pl
íra1pr
ír1arc
í2r1a2u
í2r1ábr
í1rá
í2r1á2g
í2r1á2p
ír1á2r.
ír1árak
írá1ra
í2r1á2ro
í2r1á2ru
í2r1á2sás
írá1sá
írá2se
íráskés2z1
írás1ké
írás3s
í2rás2z
ír1á2t1a
ír1á2t1e2
í2r1áth
ír1áts
ír1átv
ír1á1za
ír1bl
ír1br
ír1d2r
ír1e2dé
í1re
í2r1e2g
í2r1e2kés
íre1ké
í2r1e2l
íre1l1a
í2r1ember
írem1be
ír1eml
ír1e1mu
íre2n
ír1e1ne
í2r1e2r
í2r1esem
íre1se
í2r1e2ső
í2r1eszk
íres2z
í2r1ev
í2r1é2g
í1ré
í2r1éj
í2r1é2k
í2r1é2l
í2r1ép
í2r1é2ri
í2r1és
í2r1é2te
í2r1évh
ír1f2r
ír1gl
ír1g2r
íri2g
í1ri
í2r1i1ga
ír1i1ko
í2r1ill
í2r1im
ír1ind
í2r1inf
í2r1inj
ír1ins
í2r1int
í2r1i2p
ír1i1ra
í2ris
íri2s1z1á
íris2z
í2r1i2z
ír1ín
í1rí
ír1ír
ír1ív
ír1í2z
ír1k2l
ír1k2r
ír1kv
1írnok
ír1no
í2r1o2b
í1ro
1írog
í2r1okl
í2r1okm
íro2l
í2r1o1la
í2r1old
í2r1olv
í2r1o2r
í2r1os2z
í2r1o2v
í2r1o2x
író1á2
í1ró
í2ródj
í2ró1do
í2ró1dó
í2ródt
í2ró1í
író1p2
író1s2p
író1s2z
írót2
író1tr
ír1ön
í1rö
ír1ör
í2r1ös
í2r1ö2z
í2r1ő2r
í1rő
ír1ő2s
ír1p2l
ír1p2r
ír1sh
ír1s2k
ír1s2p
ír1s2r
ír1s2t
írs2z2
írszt2
ír2t1ag
ír1ta
írt1alap
írta1la
írt1é2te
ír1té
írt1ha2
ír1tran
írt1ra
ír1t1ro
ír2t1u2r
ír1tu
íru2n
í1ru
í2r1und
í2r1u1ni
í2r1u2r
íru2s1e2
írus3s2
í2r1u2t
í2r1új
í1rú
í2r1úr
í2r1ú2s
í2r1útj
í2r1útn
í2r1ú2to2
í2r1útr
í2r1útt
í2r1ü2g
í1rü
írü2l
ír1ür
ír1ü2v
í2r1üz
ír1ű2z
í1rű
ís2po
ís2tí
ísz1aj
ís2z
í1s1za
ísza2k
ísza1k1o
ísz1a2l
ísz1as
ísz1at
ísz1a1u
í2s1z1á2
í2sz1eb
í1s1ze
í2sze1dé
í2sz1e2g
í2sz1e2lem
ísze1le
í2sz1ell
í2szeln
í2sz1e2lő
í2sz1elv
í2sz1emb
í2sz1e2mel
ísze1me
í2s2z1eml
í2sz1er1k2
í2szég
í1s1zé
í2sz1é2l
í2sz1é2p
í2sz1érem
íszé1re
í2sz1ért
í2s2z1étk
ísz1g2
í1s1zi2
í2sz1id
í2sz1in
í2szír
í1s1zí
í2sz1ív
ísz1k2
í2s1z1o2
í2s1z1ó2
ísz1öl
í1s1zö
ísz1ön
í2s1z1ő2
ísz1p2
ísz3s
ísz1tr
í2s1z1u
í2s1z1ú
íszü2l
í1s1zü
í2sz1ü1lé
í2s2zünn
í2szüt
ísz1z
íta3u2
í1ta
ítá2s1á2g
í1tá
ítá1sá
íté2k1a2l
í1té
íté1ka
íté2k1e2l
íté1ke
1í2tél
íté2sa2
íté2s1é2g.
íté1sé
íté2s1é2gé
íté2s1égr
íté2s1za
ítés2z
ít2é2s1zá
íté2s1zo
ítész3s
ító1a2
í1tó
ító1f
ítógé2p1é2s
ító1gé
ítógé1pé
ító1p2
ító1sp
ító1s1ta
ítót2
ító1tr
ít2ré
ítus3s2
í1tu
í1u
í1ú
í1ü
íü2dü
í1ű
í2v1a2d
í1va
ív1a2já
ív1ajk
ív1ajt
í2v1a2la
í2v1alj
í2v1a2na
í2v1ang
í2v1a2nó
í2v1a2n2y
ív1arc
ív1a2ri
í2v1aszt
ívas2z
í2v1ábr
í1vá
í2v1á2g
í2v1ál
ív1á2rad
ívá1ra
í2v1á2ram
í2v1árk
í2v1árn
í2v1árt
í2vá1ru
ívás3s
ívá2s3z
ív1á1ta2
ív1bl
ív1dr
íve2c
í1ve
í2v1e2d2z
í2v1e2g
í2v1ej
í2velek
íve1le
ív1e2lemb
ív1e2le1me
ív1e2le1mé
ív1e2lemr
í2velg
ív1e2lől
íve1lő
í2v1ember
ívem1be
íve2n
í2v1end
í2v1e1ne
íve2r
ív1e1re
ív1e1ré
ív1e1rő
í2v1ese1mé
íve1se
í2v1es2z
í2v1e2tet
íve1te
í2v1é2g
í1vé
í2v1é2k
í2v1é2le
í2v1élm
í2v1é2lő
ív1élt
í2v1ép
í2v1é2r.
í2v1ér1d2
í2v1é2ri
í2v1érr
ív1ér1té
í2v1érv
í2v1érz
í2v1é2te
ív1fr
í2v1i2d
í1vi
ívi2g
í2v1i1ga
ívi2k
ív1i1ke
í2v1ill
í2v1im
í2v1in
í2v1i2p
ív1i1ro
ívi2s1el
ívi1se
ívi2ses
ívis1s
ív1i1va
í2v1i2z
ív1kl
ív1kr
í2v1ol
í1vo
í2v1op
í2v1or
í2v1os2z
í2v1ox
ívó1s2p
í1vó
í2v1öb
í1vö
í2v1ö2r
í2v1ös
ív1öv
í2v1ö2z
ív1pl
ív1pr
ívren2de
ív1re
ív1sk
ív1sp
ív1st
ívs2z2
ív1szk
ív1tr
í2v1ug
í1vu
ív1ult
í2v1ur
í2v1u2t
ív1új
í1vú
ív1út
í2v1üg
í1vü
í2v1ür
í2v1üt
í2v1ü2v
í2v1üz
íz1ab
í1za
íz1a2d
íz1a2g
íz1aj
íz1a2k
íz1a2l
íza2n
íz1ar
íz1a2u
íz1á2g
í1zá
íz1ál
íz1á2p
íz1á2r
ízá1r1ó2
ízás1s
íz1á2t
íz1át1a2d
ízá1ta
ízá1t1á
ízá1t1e2
íz1bl
íz1d2r
í2z1ef
í1ze
í2z1e2g
í2z1ej
í3ze1lá
íze2l1el
íze1le
íze2lö
íze2lő
í2z1emel
íze1me
í2ze1mé
íze2n
í2z1e1ne
í2z1e2r
1í2ze1sí
í2z1e2s2z
í2z1e2ti
í2z1e1u
í2z1e2v
í2z1ex
í2z1e2z
í2z1ég
í1zé
íz1ékk
í2z1é2l
í2z1é2p
í2z1é2r.
í2z1érb
í2z1ér1d2
í2z1érel
ízé1re
í2z1é2re2n
í2z1érh
í2z1é2r1i
í2z1érk
í2z1érm
í2z1érn
í2z1érr
í2z1ér1s
í2z1ér1te
í2z1ér1té
í2z1ér1tő
í2z1érv
í2z1érz
ízé2sa
íz1és2z
í2z1é1vi
íz1fr
íz1gl
í2z1icc
í1zi
ízi2g
í2z1i1gé
íz1i1ko
í2z1ill
í2zi1má
í2z1i1mi
í2z1imp
í2z1ind
í2z1inf
í2z1int
ízióé2r
ízi1ó
ízió1é
ízi2óé1ra
ízi2ó1to
í2z1i2pa
í2z1i1ra
í2z1i1rá
ízi2so
ízi2s1za
ízis2z
ízi2s1zo
ízi2s1zó
í2z1i2ta
í2z1i2vá
íz1í2v
í1zí
íz1íz
íz1kl
íz1k2r
ízo2k
í1zo
íz1on
íz1or
íz1os
íz1ó2rá
í1zó
íz1öb
í1zö
íz1öd
íz1ök
íz1ö2l
íz1öm
íz1ön
íz1ör
íz1ös
íz1ö2v
íz1öz
ízpen1
íz1pe
íz1pf
íz1pl
íz1p2r
í2z1sa2
íz2s
ízs1al
ízs1as
ízs1a1u
í2z4s1ág
í1z1sá
ízsá2r
ízs1á1ro
íz3sáv
íz3seb
í1z1se
í2zs1e2l
ízse2s
í2z1s1e1se
í2zs1in
í1z1si
í2zs1it
í1z3sí
í2z1so
ízs1ok
í2z3sor
í2z1s1ó2
í2z1sö
ízs1s
íz3st2r
í2z3su
í2z1sú
íz3s2z
íz1t2r
í2z1ug
í1zu
í2z1uj
ízu2me2
ízu2m1i
í2z1und
í2z1u2r
í2z1ut
íz1úr
í1zú
íz1ú2t
í2z1ü2g
í1zü
í2zü1le
í2z1ür
í2z1üs
í2z1üt
í2z1üv
í2z1ü2z
í2zű1e
í1zű
í2zűn
1í2zűr
í2zűv
2j.
1ja
j1a2cé
2j1a2dag
ja1da
2j1a2dal
ja2da1tá
2j1a2da1to
2j1adm
2j1a2dom
ja1do
2j1a2dot
ja2dóh
ja1dó
ja2dós
ja2dót
ja2dóv
ja2dóz
ja2dus
ja1du
ja1f2r
ja2g2a.
ja1ga
2j1agg
2j1a2gi
2jakad
ja1ka
ja2ka1dé
ja2k1ál
ja1ká
ja2k1á2p
ja2k1árn
2j1ak1ci
ja2k1ec
ja1ke
ja2k1el
ja2k1e2m
ja2kes
j2ak1é1re
ja1ké
ja2k1é1ri
2jakf
ja2k1i2m.
j2akim
ja1ki
j2a2k1iz
ja2k1í2r
ja1kí
jak1k2
2j1ak1ku
jako2v
ja1ko
jak1o1vi
ja2kók
ja1kó
ja2k1ölts
ja1kö
ja2k1ös
2j1ak1ro
jakt2
2j1ak2t.
2j1ak1ti
2j1ak1tu
2j1alg
2j1alk
2j1all
2j1alm
jam2be
2jam1bu
ja2mes
ja1me
2jan2a.
ja1na
2j2ana2i
ja2nal
ja2nat
2j1a2no
jan2s1ze
jans2z
jan2s1zü
ja2nya
jan2y
ja2ran
ja1ra
2j1a2rá
2j1arc
ja2rom
ja1ro
2j1a2szás
jas2z
ja1s1zá
2j1atk
2j1at1ró
ja2ur
ja1u
ja2u1to
2javat
ja1va
ja2xi
ja2zon
ja1zo
1já
já2ar
já1a
2j1á2bé
2j1ábr
2j1á2g.
2j1á2g2a.
já1ga
2j1á2g1a2d
2j1á2ga1i
2j1á2gak
2j1á2gas
2j1á2gat
2j1á2gaz
2j1á2gá
2j1ágb
2j1ágf
2j1ágg
2j1á2gi
2j1ágk
2j1ágr
2j1á2g2y
jáí2r
já1í
2j1áll
já2n1e
já2nék
já1né
2jánl
já2po
2j1á2ra1da
já1ra
2j1á2ra1i
2j1á2rak
2j1á2ram
já2rar
já2r1av
2j1á2rán
já1rá
járás3s
járá2s3z
2j1á2rát
2j1á2ráv
já1re2
já2r1em
já2res
já2rér
já1ré
2j1á2ri2g
já1ri
já2r1is
2j1ár1ki
2j1ár1ko
2j1árn2y
2j1á2ron
já1ro
já2r1ot
já3ró
já2rő
2j1ártal
jár1ta
2j1ártás
jár1tá
2j1á2ruk
já1ru
2j1á2rus
j1á2rut
2j1árvál
jár1vá
2j1árz
já2s1ad
já1sa
já2sal
já2sa2n
já2s1ág
já1sá
já2s1á2ra1i
jásá1ra
já2s1árak
já2sás
já2s1e2
já2sit
já1si
já2s1í
já2s1ol
já1so
2j1á2só
já2sö
jást2
já2s1ü2
já2sz1al
jás2z
já1s1za
já1s1ze2
já2szis
já1s1zi
já2s2z1ok1ta
já1s1zo
jász1ó1i
já1s1zó
jász1ón
jász1ó2r
ját1a2d2ó.
já1ta
já2ta1dó
ját1a2dó2k.
ját1a2dó2n.
ját1a2dót
2j1á2tal
já2t1á2
2j1átáz
játé2k1e2
já1té
2j1át1fe
2j1át1há
2j1á2ti
2j1á2t1í2r
já1tí
2j1át1lé
2j1átm
ját1os2z
já1to
2j1á2t1ö
2j1át1s1zű
játs2z
2j1á2tü
2j1át1vé
2j1át1vi
já3z2s
jba2l1
j1ba
jb2lo
jb2ri
jb2ró
jbű2n1ü2
j1bű
j1c3ho
jc2h
jcsa1pá2
jc2s
j1c1sa
jcs1s
jdo2na2l
j1do
jdo1na
j1d2rá
j1d2ro
j1d2ru
jdúköz1
j1dú
jdú1kö
1je
jea2l
je1a
jea2n
je2bé
2j1e2dén
je1dé
2j1e2d2z
jee2n
je1e
je1f2r
2jeged
je1ge
je2gés
je1gé
je2gés2z1
je2gyeg
jeg2y
je1gye
je2gyel
jegy1els
jegy1elv
2j1e2gye1sí
je2gy1é2k
je1gyé
je2gy1in
je1gyi
je2gy1o
je2győ
je2gyu2
je2gy1ú
je2gy1ű2rű
je1gyű
j1ek1cé
je1k1ri
jek2t1a2
jek2t1á2r
jek1tá
jek2t1e2l
jek1te
jek2ter
je2l1a2j
je1la
je2l1an
je2l1ap
je2la2r
je2lav
je2laz
je1lá2
je2l1áb
je2lág
je2lál
je2l1ár
je2l1át1
je2láz
jel1e2g2y
je1le
je2l1e2l1e2v
jele1le
je2l1elk
je2l1ell
je2l1e2lő1á
jele1lő
je2l1e2lőb
je2l1els
je2l1emel
jele1me
2jele1mé
je2l1eml
2j1e2lemz
je2l1eng
je2l1e2r
jel1esés
jele1sé
jel1esik
jele1si
je2l1es1ni
je2l1e2ső
jel1es1te
je2l1es2z
je2l1etet
jele1te
jel1e1vo
je2lég
je1lé
je2l1éj
je2lél
je2l1ép
je2lér1te
je2l1i1ge
je1li
je2lim
je2l1i2na
je2l1inf
je2l1ing
je2l1int
je2l1í2
2j1ellen
jel1le
je2l1o2
je2l1öv
je1lö
je2lő1a
je1lő
2j1e2lő1fe
je2lő1í
2j1e2lő1tu
jel1p2
2jel1s1zá
jels2z
2jel1s1zo
2j1eltet2t.
jel1te
2jeltér
jel1té
je2lu
je2lú
2j1em1ba
2j1ember
jem1be
2j1e2mel
je1me
je2mu
je2n1á
jen1d2
je2n1e2k2e.
je1ne
jene1ke
je2n1el
je2ner
j2ene2s
je2n1es2z
je2n1in
je1ni
je2n1o
je2n1ő2re
je1nő
je2n1ős
je2n1ü
je1p2r
2j1erd
je2red
je1re
je2ró
2j1e2rő
je2s1a
je2s1emb
je1se
2j1e2setb
2j1e2sett
je2s1ér
je1sé
je2ső
jest2
je1s1ta
je1str
je2su
je2s1ü2v
je1sü
je2s3za2c
jes2z
je1s1za
je2t1am
je1ta
je2tál
je1tá
jet1ell
je1te
je2t1em
je2t1ék
je1té
je2t1o
je1t1ra
je2tun
je1tu
je2tut
jeu2r
je1u
2j1e2vő
jezőkés2z1
je1ző
jező1ké
1jé
2j1ébr
jé2g1a2
jé2g1á2
jégá2r1a2d
jégá1ra
jé2gec
jé1ge
jé2ged
jé2g1e2k
jé2g1el
jé2g1em
jé2g1erk
jé2g1es
jé2get
jé2gép
jé1gé
jé2g1é2r.
jé2gés
jég3g
jé1gi2
jé2g1id
jé2gi2gé
jégi2g
jé2gim
jé2g1is
jé2g1o
jé2gó
jé2gö
2jé2gő
jé2g1u2
jé2gú
2j1é2hen
jé1he
2j1é2hes
2j1éhs
jé2k1os
jé1ko
jé2kü
2j1é2le
jé2lő
2j1élt
jé2nad
jé1na
jé2n1ess
jé1ne
jé2n1é2g
jé1né
jé2nil
jé1ni
jé2no
jé2nú
jé2pí
jé2pü
2j1é2r.
2j1érb
2j1érd
jé2reg
jé1re
2j1é2re2n
2j1é2ré
2j1érh
2j1é2ri
2j1érk
2j1érl
2j1érm
2j1érn
2j1érr
2j1ér1te
2j1ér1té
2j1ér1tő
2j1é2rü
2j1érv
2j1érz
jé2tel
jé1te
2j1étk
2j1étt
2j1é2v.
2j1évb
jé2v2e.
jé1ve
2j1é2vek
2j1é2vet
jé2vén
jé1vé
jé2vér
jé2vét
j1évh
j1é2vi
j1évk
2j1évn
2j1évr
j1évt
2j1é2vü
j1évv
jfeles1s
j1fe
jfe1le
jf2le
jf2lo
jf2ló
jfölös1s
j1fö
jfö1lö
jf2ra
jf2re
jf2ri
jf2rí
jf2ro
jf2rö
jfu2na
j1fu
j1g2le
j1g2ló
jg2ru
jhá2r1e
j1há
1ji
ji2do
j1i2dő
2j1i2ga
2j1i2gé
ji2je
2j1ikr
2j1ill
ji2ma
ji2má
ji2mi
2j1i2n2a.
ji1na
ji2náb
ji1ná
2j1ind
2j1inf
2j1ing
2j1inj
2j1ins
2j1in1te
2j1in1té
ji2on
ji1o
ji2pa
2j1i2rat
ji1ra
2j1i2rá
2j1i2ro
2j1irr
2j1irt
2j1isk
2j1ism
2j1ist
2j1i2ta
2j1i2vá
2j1i2vó
ji2zé
ji2zo
1jí
jí2gé
jí2rá
jí2ró
jítókés2z1
jí1tó
jít2ókés
jító1ké
jí2ve
jí2vé
jí2vó
jí2ze
jí2zé
jí2zü
jí2zű
j2j1alj
j1ja
j2j1a2z
jjá1s
j1já
jje2le1sé
j1je
jje1le
jje2le1si
jje2lest
jje2l1ül
jje1lü
jj1e2r
j2j1im
j1ji
j2j1iz
jj1ív
j1jí
jj1íz
j2j1ol
j1jo
j2j1os
jj1pr
jj1üg
j1jü
jka1pr
j1ka
jk1ard
jka1u2
j2k1e2g
j1ke
j2k1e2lő
j2k1e2s
j1ké2p1e2ké
j1ké
jké1pe
jki1a2
j1ki
jki1á2
j2k1id
jki1e2
jk2la
jk2lá
j1k2li
j1k2lo
jk1me2
j2k1old
j1ko
jko2r1á2s
jko1rá
jko2r1in
jko1ri
jk1ó1rá
j1kó
jk1pl
jk1pr
j1k2ré
j1k2ri
j1k2rí
jk1st
j2k1u2s
j1ku
jk1üg
j1kü
jk2va
j1k2vó
j2l1abl
j1la
jl1a2da
j2l1a2lap
jla1la
j2l1all
j2l1ar
jla2t1e2
j2l1a1zo
jlás3s
j1lá
jl1á2t1a
jl1átn
jl1bl
jl1e2g2y
j1le
jle1í2
jl1e1lé
jl1ell
jl1eln
jle2t1é2te
jle1té
jlé2cel
j1lé
jlé1ce
jlé2c3s2
j2l1é2v.
jli2k
j1li
jl1i1ko
j2l1ind
j2l1inf
j2l1int
jl1obj
j1lo
j2l1ol
jlo2n1á
jlo2ni
j2l1ös
j1lö
jl1pr
jl1sp
jl1st
jl1t2r
jme2g
j1me
jm1o1ká
j1mo
j2m1old
jna2l1e
j1na
jn2a2lég
jna1lé
jna2lis
jna1li
jn1á2rá
j1ná
jn1á2ru
jné1v1a2
j1né
jn1ing
j1ni
jn1st
jn1ü2g
j1nü
1jo
jo2g1a2d
jo1ga
jo2g1a1ka
jo2gal
jo2g1an
j2o2g1a2z
jo2g1á2c
jo1gá
jo2g1á2l
jo2g1e2
jog3g
jo2g1í
jo2g1ol
jo1go
jo2g1or
jo2g1os2z
jo1gó2
jo2g1óv
jo2gö
jo2gő
jo2gur
jo1gu
jo2gü
2j1o2koz
jo1ko
2j1oks
2j1ok1ta
jo2laj
jo1la
jo2lim
jo1li
jo1ma2
jo2m1ag
jo2m1an
jo2mar
jo2m1as
jo2m1á
jo2m1e2
jo2m1é2t
jo1mé
jo2mév
jom1f2
jo2mij
jo1mi
jo2m1ik
jo2m1is
jo2m1iv
jo2m1ol
jo1mo
jo2m1or
jo2mö
jo2mő
jom1p2
jo2mü
jo2mű
jonc1c
jon2c2h
jon2cil
jon1ci
jon2c3s
2j1ond
jo2ób
jo1ó
jo2pá
jo2pe
jo2r1ing
jo1ri
2j1or2r.
jo2rü
2j1os1to
2j1os2z
jo2u1i
jo1u
jo2uk
jo2ul
jo2ur
jo2ut
jo2va
1jó
jóa2k
jó1a
jóá2g
jó1á
jóá2r
jó1bl
jó1b2r
jó2ce
jó2dad
jó1da
jó2dak
jód1al3l
jó2d1a2n
jóda2t
jó2d1a1to
jó1de2
jó2del
jó2dig
jó1di
jó2diz
j2ó2d1ol
jó1do
jó1dó2
jó2dór
jó2dü
jó2d3z
jó1fl
jó1kl
jó1k1ro
jó2l1e2s
jó1le
2j1ónn
jó1p2r
jó2rák
jó1rá
2j1ó2ri
jó2sal
jó1sa
jó2sas
jó2sál
jó1sá
jós1ár1k2
jó2seg
jó1se
jó2se2m
jó2sén
jó1sé
jó2si2p
jó1si
jó2sis
jó2s1o2do
jó1so
jó2sö
jós3s
jós1üld
j2ósül
jó1sü
jó2s1ün
jó2s3zár
j2ó1s1zá
jós2z
jó1t2r
1jö
jö2dé
jö2ko
jö2lé
2j1önt
jö2re
jö2rö
j1ösv
j1ötv
2j1ö2v.
2jöv2e.
jö1ve
j1ö2vez
2j1ö2vén
jö1vé
jö2zö
1jő
j1őrl
jő2rö
2j1őrt
jő2rü
jő2ze
jpár1ba2
j1pá
jp2la
jp2le
jp2re
jp2ré
jp2ri
jp2ro
jp2ró
jra3d2
j1ra
jra1f
jra1i2
jra1p2
jra1s2
jra1u2
jré2sz1e2l
j1ré
jrés2z
jré1s1ze
jré2s1zí
jsa2v1e
j1sa
jsa2v1é
jsa2vo
jsa2v1ó2n.
jsa1vó
jsa2v1ó2r
jság1g
j1sá
js2ho
js2ká
js2ki
js2ni
js2pe
js2pi
js2po
js2pó
jsp2r
j1s2ta
js2ti
j1s2tí
j1s2to
j1st2ra
j1st2ru
j1s2tú
jszabás1s
js2z
j1s1za
jsza1bá
jszá2l1a2da
j1s1zá
jszá1la
jszáraz1
jszá1ra
jszín3n
j1s1zí
jsz2k
jszt2
j2t1a2da
j1ta
jt1a2do
j2t1a2dó
jt1akn
jt1a2lap
jta1la
jt1alk
j2t1all
j2t1alv
j2t1a2nyag
jtan2y
jta1nya
jta1p2
j2t1a2pá
jt1a2rom
jta1ro
j2t1arz
jta1sp
j2t1atk
j2t1a2ut
jta1u
j2t1ác
j1tá
jt1á1ga
j2t1áll
jtá2ra1i
jtá1ra
j2t1á2rak
j2t1ár1ka
jtár1k2
jtá2s3z
jt1á2t1a
j2t1e2g2y
j1te
j2t1e2k2e.
jte1ke
j2t1e2leg
jte1le
j2t1e2le1me
j2telemz
j2t1elf
j2t1elh
jt1el1já
j2t1ell
j2t1elmél
jtel1mé
j2t1e2lo
j2t1e2lő
j2t1el1p2
j2t1elr
j2t1els
j2t1elv
j2t1eml
j2te2ner
jte1ne
j2t1enz
jt1e2red
jte1re
jt1e2rez
j2t1e2rő
j2t1e2tető1e
jte1te
jtete1tő
jt1e2vés
jte1vé
jt1e1vo
j2t1e2vő
j2t1é2le
j1té
jt1élm
j2t1érl
j2t1érm
jtés3s
jt1fl
jt1fr
j2t1i2d
j1ti
jti2m
j2ti1má
j2t1i1mi
j2t1imp
jt1inf
j2t1ing
jt1int
j2t1i2pa
j2t1ir
jti2s
jt1i1si
j2t1izg
jt1i2zo
j2t1í2v
j1tí
j2t1í2z
jt1kl
jt1kr
j2t1ol1da
j1to
j2t1orjáb
jtor3já
j2t1ors2
jt1os2z
j2t1o2x
jtó1a2
j1tó
jtóé2ra
jtó1é
jtó1p2
jtó1s2po
jtó1s1ta
jt2ó1st2r
jtó1s2z
jtó1tr
jt1ö1dé
j1tö
jt1öng
jt1öss
jtő1a2
j1tő
jtő1e2
jtő1é2
jtő1s2p
jt1pl
jt1pr
j1t2ran
jt1ra
jtrá1di2
jt1rá
j1t2rág
jt2ri
j1t2roj
jt1ro
jt2róf
jt1ró
jt1sp
jt1st
jt1t2r
j2t1udv
j1tu
j2t1und
j2t1u1tá
j2t1új
j1tú
j2t1üg
j1tü
j2t1ü2l
j2t1üt
1ju
ju2ga
ju1go1
ju2had
ju1ha
ju2h1a2k
ju2hal
ju2h1a2n
juha2ra
ju2hat
ju2hál
ju1há
ju2h1e2
ju2h1éj
ju1hé
ju2h1is
ju1hi
ju2hor
ju1ho
ju2hő
ju2hü
2ju1la
2juls
2jural
ju1ra
ju2ru
ju2sz1a2v
jus2z
ju1s1za
ju2szim
ju1s1zi
ju2s1z1í2
ju2tak
ju1ta
2j1u2tál
ju1tá
1jú
jú1fr
jú2jí
jújjá1é3
júj1já
jú1pl
jú1p2r
jú1s2p
jú1s2t
jú2s1zó
jús2z
jú1szv
2j1útb
jú2to2n1
jú1to
2j1útv
1jü
jü2ge
jü2g2y
jü2le
jü2lé
j1ü2lő
j1ült
jü2re
jü2rí
jü2rü
jü2ta
j1ü2tő
jü2ve
jü2ze
1jű
jű2ri
jű2ző
j2z1abl
j1za
j2z1a2d
jza2j
j2z1a1já
jz1ajt
j2za2kad
jza1ka
j2z1akc
j2z1a2l
j2z1a2n2y
j2z1ar
j2z1as
jz2a2tan
jza1ta
j2z1ál
j1zá
j2z1á2rad
jzá1ra
j2z1á2rán
jzá1rá
j2z1e2l
j1ze
j2z1em
jze2r
jz1e1ré
j2z1es
j2z1él
j1zé
j2z1ér1té
jz1fr
jzi2g
j1zi
j2z1i1gé
j2zill
j2z1int
j2z1i2r
j2z1isk
j2z1ism
jz1k2l
jz1k2r
j2z1ob
j1zo
j2z1ok1ta
j2z1ol1da
j2z1olvas
jzol1va
j2z1os2z
j2z1ó2d
j1zó
j2z1ó2r
jzó1s2
j2z1ös
j1zö
j2z1öt
jz1pl
jz1p2r
jz3sab
jz2s
j1z1sa
j2zs1a2l
j2zs1ál
j1z1sá
jzscsa2p1
jzsc2s
jzs1c1sa
j2zs1eg
j1z1se
j2zsen
j2zs1er
j2zs1in
j1z1si
jz3sín
j1z1sí
j2z3sor
j1z1so
j2z1s1ö2
j2z1s1ő
jzs1s
j2zs1u2t
j1z1su
jz3s1za
jzs2z
jz3s1zá
jz3s1ze
j2z1ut
j1zu
jz1ü2g
j1zü
2k.
1ka
kaa2d
ka1a
kaát1
ka1á
kaá2ta2
ka1b2a
2k1abbah
kab1b2a
2k1ab1há
kabi2ná
ka1bi
2k1abla2k.
kab1la
2k1abla1ka
2k1abla1ká
2k1ablakb
2k1ablakh
2k1ablakk
2k1ablakok
kabla1ko
2k1ablakon
2k1abl2akos
2k1ablakot
2k1ablakr
ka1b2le
ka1b1re
2k1abs
ka2cé2l.
ka1cé
ka2c3h
ka2cs1á2g
kac2s
ka1c1sá
ka2cs1ús
ka1c1sú
ka2cü
ka2c2z
k1adap
ka1da
2kadál
ka1dá
kadás1s
2k1a2dot
ka1do
ka2dó1i
ka1dó
ka2dój
2k1a2dó1so
ka2dó1u
ka2dóz
k2a1d2rá
kae2gés2z1
ka1e
kae2gé
kae2l
kae2r
kaé2r
ka1é
ka1f2r
ka2gan
ka1ga
2k1agg
ka1g2r
2k1ag2y.
kag2y
ka2gyu
kai2z
ka1i
2k1a2jánl
ka1já
2k1ajk
2k1ajtóh
kaj1tó
ka2ka1dé
1ka1ka
ka2ka1dó
kakas3s
ka2kác
ka1ká
2k1akc
ka1k2l
ka2k2ó.
ka1kó
k2a1k2ré
k2a1k1ri
2k1ak2t.
2k1ak1ti
ka1k2va
ka2la1pa
ka1la
2k1a2lapítv
kala1pí
2k1alb
kal1eg2y
ka1le
2k1alf
2k1al2j.
2k1al1ji
2k1al1ka
2kal1ko
2k1al1le
2k1al1ti
kama2te
ka1ma
2k1am1bu
kana2g
ka1na
k1a2na1lí
2k1a2nam
k1a2nat
ka2n1e2g
ka1ne
ka2n1el
2kang
ka2nim
ka1ni
ka2ni1o
ka2n1iv
ka2nol
ka1no
ka2nód
ka1nó
2kan1te
ka2nű
2k1a2ny2a.
kan2y
ka1nya
2k1a2nyag
2k1a2nya1i
ka2nyáb
ka1nyá
ka2nyó
ka2ó1a
ka1ó
ka2ó1á
ka2óc
ka2ó1e
ka2óg
ka2ó1ha
ka2ó1í
ka2ó1ko
ka2ókr
ka2óp
ka2ó1ü
ka2óz
2kapád
ka1pá
ka2pá1to
2kapp
ka1p2re
ka1p2ré
2k1ap1rí
ka1p2rof
k2ap1ro
k2a2r1a2d
ka1ra
ka2rakk
k2arak
ka2r1a2kó
kar1a2lap
kara1la
2k1arank
ka2r1a2r
kara2s
2ka2ras2z
2kara2t.
2karatn
2kara1to
ka2ra1u
ka2r1ácsh
k2arác
ka1rá
karác2s
ka2r1ácsn
ka2r1ácst
ka2r1ál
2k1a2rán2y
kar2c1el
kar1ce
kar2dac
kar1da
kar2dál
kar1dá
kar2d2z
ka2rel
ka1re
ka2rev
kar1é1ke
ka1ré
karé2kor
karé1ko
ka2rél
ka2r1é1ne
karfe2l1
kar1fe
ka2rid
ka1ri
ka2r1i2ko
ka2r1ikr
ka2rind
ka2r1in2g.
kar1ingb
kar1in1ge
kar1ing3g
kar1ingr
ka2r1isk
ka2r1ism
k2ari2z
ka2r1i1zo
2k1a2rom2a.
ka1ro
karo1ma
2karo1má
ka2ror
kars2
2k1ar1ti
ka2sag2
ka1sa
kaság1g
ka1sá
ka2sál
ka2sid
ka1si
kasi2p
kas1i1pa
ka2siz
ka2s1ol
ka1so
ka2s1ó2r
ka1só
ka1s2po
kas3s1ze
kas2s2z
kas3s1zé
2k1as1s1zi
2k1asszon2y
kas1s1zo
kas3s1ző
kast2
k2a1str
ka2sür
ka1sü
kasz1ajt
kas2z
ka1s1za
2kaszat
ka2szág
ka1s1zá
kat1a1rá
k2atar
ka1ta
k2a2tél
ka1té
kat1i1ko
ka1ti
kat1int
k2atin
ka2t1i2o
2k1at1ká
ka2tol1da
ka1to
katrá1di2
kat1rá
ka1t2ri
ka2tyá
kat2y
ka2ud
ka1u
2k1a2ul
kau2n
kau2r
kau2s
kau2ta
2k1a2u1to
2k1aut2ó.
ka2u1tó
2k1autób
ka3utóc
2k1autó1é
2k1autóh
2k1autó1i
2k1autój
2k1autók
2k1autóm
2k1autón
2k1autós
2k1autót
2k1autóv
kaü2t
ka1ü
kaü2z
2k1a2vat
ka1va
2k1a2zon
ka1zo
1ká
2ká1bé
2k1ábr
ká2cak
ká1ca
ká2ce
2ká2c3h
ká2có
ká2c3sar
kác2s
ká1c1sa
ká2csat
ká2c1se
ká2cs1é2k
ká1c1sé
ká2csi2p
ká1c1si
ká2csis
ká2c3sor
ká1c1so
ká2c1sö
ká2c1ső
ká2cü
ká2dab
ká1da
ká2dar
ká2d1ác
ká1dá
ká2dát
ká2dil
ká1di
ká2dí
ká2d1ö
ká2d1ü2
ká2d3z
ká2fá
2k1á2g.
ká2ga
2k1ág2a.
2k1ágacs1ka
kága2c
kágac2s
1ká3gacs1ká
2k1ága1i
2k1ágak
2k1ágat
2k1ágaz
2k1á2gá
2k1ágb
2k1á2ge
k1á2gé
2k1ágg
2k1ágh
2k1á2gi
2k1ágk
2k1ágn
2k1á2go
2k1ágr
2k1ágs
2k1ágt
2k1á2gu
2k1á2gú
2k1á2g2y
2k1á2hí
ká2jö
ká2jü
ká2lág
ká1lá
2kálá1i
2kálák
2kálán
2káláv
2káláz
2k1álc
k1ál1do
ká2l1e2
ká2lén
ká1lé
2k1állam1ti
kál1la
2k1állap
2k1állás
kál1lá
kál2l1e2
kál1lé2
kál2lék
2k1ál1lí
2k1állom
kál1lo
ká2lü
ká2lyod
kál2y
ká1lyo
2k1á2mí
2k1á2mu
ká2n1ag
ká1na
ká2n1a2n
ká2n1as
ká2nem
ká1ne
ká2n1is
ká1ni
ká2n1ó2
ká2ny1ag
kán2y
ká1nya
ká2ny1a2l
ká2ny1a2n
ká2nyap
ká2nyar
ká2ny1á2to
ká1nyá
ká2ny1e2
ká2nyis
ká1nyi
ká2nyiv
ká2nyö
2k1á2po1lá
ká1po
ká2po1ló
2ká2rad
ká1ra
ká2r1a2g
2k1á2rakr
ká2rakt
kár1ak1ta
kár1ak1tá
ká2r1a2l
2k1á2ram
ká2r1a2p
ká2rar
ká2ras
kár1ass
2k1á2rat
ká2raz
kár1a1zo
kár1áll
ká1rá
ká2r1á2z
kár1d2
ká2r1e2
kár1é1te
ká1ré
2k1á2ri1á
ká1ri
1k1ár1ká
kárkié2h
kár1ki
kárki1é
ká2r1old
ká1ro
ká2r1os2z
ká2rö
ká2rő
kárp1s1zi2
kár1p2s
kárps2z
kár1p2szic3
kár1s2
kárt2
kár1tr
ká2ruh
ká1ru
ká2rü
kás1ajt
ká1sa
ká2s1ass
ká2s1á2go
ká1sá
ká2s1á2ra1i
kásá1ra
ká2s1árak
ká2s1á2rá
ká2s1árb
ká2s1á2ro
ká2s1árr
ká2s1árv
ká2s1árz
2k1á2sá1sá
2kásást
ká2s1e2
ká2s1i2k
ká1si
ká2sim
ká2sis
ká2sí2r
ká1sí
káská2r
kás1ká
ká2s1ol
ká1so
ká2s1ott
ká2só
ká2sö
ká2ső
kás1p
kást2
kás1tr
ká2su2t
ká1su
ká2s1ü2
2k1á2száb
kás2z
ká1s1zá
ká2szár
kás3zá2r.
2k1á2szát
ká2s1ze
ká2s1zé
ká2szis
ká1s1zi
ká2sziv
2k1á2szom
ká1s1zo
ká2s3zü
ká2tab
ká1ta
2k1átad
ká2taj
ká2tal
kát1a2la
ká2t1a2n
ká2tar
ká2ta1u
ká2t1á2
2k1át1be
ká2t1e2
ká2té2g
ká1té
k1át1fo
ká2tip
ká1ti
ká2t1ir
ká2tis
ká2tí
k1át1ló
2k1át1me
ká2t1os2z
ká1to
ká1tó2
ká2tór
ká2t1ö
ká2tü
k1át1vá
kba2l1
k1ba
kbé2rel
k1bé
kbé1re
kb2la
kb2le
kb2lo
kb2rá
kb2ri
kb2ro
kb2ró
kci2ó1fa
k1ci
kci1ó
kci2ó1si
kci2ós1pe
kc2lu
kcsa2p1á2g
kc2s
k1c1sa
kcsa1pá
kdi2al
k1di
kdi1a
kd2ra
kd2rá
kd2re
kd2ro
kd2ró
kd2ru
1ke
kea2j
ke1a
kea2k
kea2l
kea2n
kea2r
kea2s
keá2l
ke1á
keá2r
keá2t1
2k1e2b.
ke2be1i
ke1be
ke2bek
ke2béd
ke1bé
kecs1alj
kec2s
ke1c1sa
kecs1ón
ke1c1só
kecs1ó2r
ke1d2ra
keegyez1
ke1e
kee2g2y
kee1gye
kee2n
ke2ep
kee2s
2k1eff
ke1fl
ke1f2r
2k1e2gér
ke1gé
ke2gés2z1
2kegyb
keg2y
ke2gyék
ke1gyé
ke2gy1in
ke1gyi
2k1egyl
2kegyn
ke2gyö
2kegyr
2kegys
ke2gyu2
ke2gy1ú
2ke1gyü
2k1e2k2e.
1ke1ke
2k1e2ke1i
2k1e2kek
2k1e2ké1é
ke1ké
2k1e2kék
ke1k2l
ke1k2ré
kek2s1za
keks2z
2k1ekv
2k1e2l1a2d
ke1la
ke2l1an
ke2l1a2t
2k1elav
ke2l1á2g
ke1lá
2k1e2lef
ke1le
ke2lekc
2k1e2lekt
ke2l1e2l
k1e2lemb
2kele1me
2kele1mé
k1e2lemh
2kelemm
2k1e2lemn
k1e2lemr
2k1e2lemz
ke2l1e2p2e.
kele1pe
ke2l1e2pe1i
kele2p1ü2
ke2l1e2sé
ke2l1e1s1ze
keles2z
kele2t1el
kele1te
kele2t1o
2keley
ke2l1é2k
ke1lé
2k1e2lél
kel1érh
kelés3s
kelé2s3z
2k1elhel
kel1he
2k1el1ho
kel1id
ke1li
kel2ig
2k1e2lim
ke2li1o
2k1e2l1ism
2k1e2l1í2
2k1el1já
k1el1lá
2kellát
2k1elle1ná
kel1le
2k1elle1nő
2k1ellent
2k1elnev
kel1ne
2k1eln2y
2k1e2l1os
ke1lo
2ke2lö2l
ke1lö
2k1e2lő1a
ke1lő
kelő1é2
2k1e2lő1fú
ke2lő1hí
2k1e2lő1í
2k1e2lőképz
kelő1ké
2k1e2lőkés
2k1e2lől
ke2lőn2y
2k1e2lő1o
ke2lő1té
2k1e2lőtt
k1e2lőz
k1el1ra
2k1el1sa
2k1el1ső
2k1el1s1zá
kels2z
kel2t1é2r2ő.
kel1té
kelté1rő
2k1el1to
2kel1tű
2kelül
ke1lü
ke2lűr
ke1lű
2k1el2v.
2k1el1vá
2k1elve1i
kel1ve
2k1elvek
2k1elven
2kel1vi
k1elvk
2k1el1vo
2k1elvt
2k1el1vű
ke2lyemb
kel2y
ke1lye
ke2ly1ékn
ke1lyé
kelyköz1
kely1kö
2k1elz
2k1ember
kem1be
2k1embl
2k1embr
2k1eml
2k1emul
ke1mu
ke2nal
ke1na
2k1en1ci
kende2rül
ken1de
kende1rü
ken2d1ék
ken1dé
2k1en1do
2k1e2ner
ke1ne
2k1en1ge
ke2n1ip
ke1ni
ke2nis
ke2n1o
kens3s
kens2z2
2k1enyv
ken2y
keo2l
ke1o
keo2r
keó2d
ke1ó
keó2h
keó2l
keó2r
ke2pik
ke1pi
ke2ral
ke1ra
ke2rap
ke2r1a2r
ke2r1as
ke2ra1u
ke2r1ál
ke1rá
ke2r1ár
ke2rás
ker2c1sá
kerc2s
ker2csell
ker1c1se
ker1c1sí2
ker2csír
2ker1de
2k1er1dő
2k1e2re1ge
ke1re
2k1e2rej
kere2k1eg
1kere1ke
kerekes3s
ker1ell
ker1elv
ke2r1ember
kerem1be
ke2r1er
kere2ta
kere2t1e2l
kere1te
kere2t1ö2
ke2rég
ke1ré
ke2r1é2l
ke2r1ép
ker1é1te
ke2r1id
ke1ri
ker1i2ga
ke2r1ill
ke2rim
ke2r1ind
ke2r1int2
ke2r1inv
ke2ri1o
ker1ist
ke2r1iz
ker1k2
2ker2n1e2l.
ker1ne
ke2r1ol
ke1ro
ke2ror
ke2ród
ke1ró
ke2r1ó2r
ke2rő1de
ke1rő
ke2rő1e
ke2rő1ké
ke2rő1o
ke2rő1sí
ke2rő1te
kers2
ker1sp
ker1st
kers2z2
ker2taj
ker1ta
ker2tá2p
ker1tá
ker2tás
ker2t1eb1bé
ker1te
ker2teg
ker2t1es1te
ker2t1es2z
ker2télv
ker1té
ker2t1é2vét
kerté1vé
ker2tön
ker1tö
ker2töv
ker2t1őr
ker1tő
ker2t1ős
ker1t1ró
ke2rút
ke1rú
kerü2g
ke1rü
2k1ese1mé
ke1se
ke2se1té
2k1e2sett
2k1esél
ke1sé
2k1e2sés
ke2sis
ke1si
2k1esítőst
ke1sí
kesí1tő
2k1es1kü
2k1e2ső
ke1s2po
1kes3szé1ke
kes2s2z
kes1s1zé
kes3s1zó
ke1s1ta
2k1es1te
2k1es1té
2k1es1ti
2k1estj
2k1estk
2k1estn
ke1st1ra
2k1est1re
2k1estt
2k1estün
kes1tü
ke2szaj
kes2z
ke1s1za
ke2szép
ke1s1zé
2k1eszmén
kesz1mé
ke2tál
ke1tá
ke1tc
ke2t1e2kéh
ke1te
kete1ké
2k1e2te1té
ke2ti2d
ke1ti
ke1t2ra
ke1t2ré
ke1t2ri
kettes1s
ket1te
ke2tűd
ke1tű
keu2r
ke1u
keu2s
kevés3s
ke1vé
ke2vid
ke1vi
2k1e2vő
keze2tés
ke1ze
keze1té
kező1a2
ke1ző
kező1e2
kezőkés2z1
kező1ké
2k1ezr
1ké
2k1é2ber
ké1be
2k1ébr
ké2ge
ké2gé
ké2gő
2k1é2hem
ké1he
2k1é2hen
2k1é2hes
2k1é2he1zé
2k1é2hezt
2k1éhs
ké2j1ak
ké1ja
ké2j1e1lé
ké1je
ké2jö
ké2ju2t
ké1ju
2k1é2k2e.
ké1ke
2k1é2ke1i
2kékekk
2kékem
ké2k1e2r
kékes3s
kéke2s3z
ké2kén
ké1ké
ké2k1o2
2k1é2kük
ké1kü
2k1é2kű
ké2lel
ké1le
2kéles
2k1é2letek
kéle1te
2k1é2letet
2k1élm
ké2lőd
ké1lő
2k1é2lől
2k1élr
2k1é2lű
ké2lya
kél2y
ké2ly1es2z
ké1lye
ké2lyid
ké1lyi
ké2ly1ü2l
ké1lyü
ké1ma2
ké2ma1d2
ké2m1al
ké2m1an
ké2map
ké2m1as2
ké2m1á
kémi2as2
ké1mi
kémi1a
ké2min
ké2mis
ké2mí
ké2mo
ké2mö
ké2mő
ké2m1u2
ké1na2
ké2n1al
ké2nan
ké2n1ar
ké2n1at
kéndi1o2
kén1di
2k1é2ne1ke
ké1ne
2k1é2nekh
2k1é2ne1ki
2k1é2nek1k2
2k1é2nekl
ké2n1e2l
ké2n1em
ké2n1er
kén1é1te
ké1né
ké2ni2g
ké1ni
ké2nil
ké2n1ing
ké2no
ké2ny1e2g
kén2y
ké1nye
ké2ny1elv
ké2nyú
ké2p1a2
ké2p1á
képá2r
ké2peg
ké1pe
ké2p1e2lő
k2épel
ké2p1el1té
ké2p1elv
ké2p1e2r
ké2p1ill
ké1pi
ké2pim
ké2p1i2p
ké2pí
2k1épít
ké2p1o
ké2pó
ké2p1ö
ké2pő
kép1s
ké2pu
ké2pú
ké2p1ü2lé
ké1pü
ké2p1ü2lő
ké1ra2
ké2rab
ké2ral
ké2ram
ké2r1an
ké2rar
ké2r1as
ké2rat
ké2r1á2
2k1érde1ke
kér1de
kér1dr
2k1é2retts
ké1re
ké2ro
ké2rö
2k1értelm
kér1te
2kértékb
kér1té
2k1érté1ke
2k1értékh
2k1értékn
2k1értékr
2k1értés
1kértő2i1ké
kér1tő
kértő1i
ké2r1u2
ké2r1ú
ké1rü2
kér1ü1lé
2k1érvek
kér1ve
2k1ér1zé
ké1sa2
ké2san
ké2s1á2
ké2s1é2g
ké1sé
ké2s1o
ké2só
kés3s1za
kés2s2z2
kés3szer
kés1s1ze
kés3szél
kés1s1zé
ké2su
ké2süt
ké1sü
ké2s1za2
kés2z
kész1al
k2é2s1z1á2
ké2szeg
ké1s1ze
késze2l
ké2s2zeml
ké2s2zes2z
ké2széd
ké1s1zé
ké2sz1ék1né
ké2szin
ké1s1zi
ké2szir
ké2s1zo
ké2s1z1ó2
k2é2s1zö
ké2szőr
ké1s1ző
készt2
ké2s1zu
ké2s1z1ú
két1akar
ké1ta
kéta1ka
ké2takn
ké2t1a1kó
két1a1la
két1asp
ké2tál
ké1tá
ké2t1ed
ké1te
ké2t1eg
ké2te2l.
ké2teln
ké2t1e2m
ké2t1ep
2ké2te2r
két1e1re
két1er1k2
két1e1rű
ké2te1sé
ké2t1es1té
ké2t1es2z
ké2t1e2v
ké2t1é2
ké2t1i2
ké2tí
ké2t1o2l
ké1to
ké2t1or
ké1tó2
ké2t1ór
ké2t1ö
ké2tud
ké1tu
ké2t1ü2
2kétv
2k1é2v.
2k1é2vad
ké1va
2k1évb
2k1é2ve1i
ké1ve
2k1é2vem
2k1é2ven
2kévet
2k1évf
2k1évh
2k1é2v2i.
ké1vi
ké2vi2g
2k1é2vin
2k1évk
2k1évn
2k1évr
2k1évs
2k1évt
2k1é2vü
2k1évv
ké2z1a
ké2z1á2
ké2zeg
ké1ze
kéz1elt
kéze2m
kéz1e1me
ké2z1e2r
ké2z1ism
ké1zi
ké2zí
ké2z1o
ké2zö
kéz2s2
ké2z3sé
kéz3st
ké2z1su
ké2z1u2
kfe2l1em
k1fe
kfe1le
kfil2mér
k1fi
kfil1mé
kfi2sar
kfi1sa
kf2jo
kf2la
kf2le
kf2li
kf2lo
kf2ló
kfolyói2k
k1fo
kf2o1lyó
kfol2y
kfolyó1i
kfo2n1ó2
kf2ra
kf2rá
kf2re
kf2ri
kf2rí
kf2ro
kf2rö
kf2ru
kg2la
kg2nó
kg2ra
k1g2rá2f.
kg1rá
kg2ri
kg2rí
kg2ru
khá2t1al
k1há
khá1ta
khelyköz1
k1he
khel2y
khely1kö
1k2hi1a
k1hi
khiá2b
khi1á
k2hil
kh2me
1ki
kia2d
ki1a
ki2a1é
ki2af
kia2g
kia2j
kia2kar
kia1ka
kia2kas
kia2lap
kia1la
kia2lá
kia2n
kia2p
kia2ra
kia2s2z
ki2a1ú
kia2v
kiá2z
ki1á
ki1b2l
ki1b2r
2k1i2de1á
ki1de
2k1i2deg
2k1i2dej
2k1i2dét
ki1dé
2k1i2déz
2ki1di
ki2di1o
2k1i2dőb
ki1dő
2k1i2dők
2k1i2dőm
2k1i2dőn
2k1i2dőt
ki1d1ró
kie2l
ki1e
kie2m
kie2r
ki2ese2t.
kie1se
kie2t
ki2ew
kié2d
ki1é
kié2k
2k1ifj
ki1f2r
2ki1ga
ki2g2a.
2ki1gá
ki2gát
2ki1ge
ki2gén
ki1gé
ki2géz
ki1g2r
ki1k2l
ki2konn
ki1ko
ki2ko1no
ki1k2ré
ki1k2ri
ki1k2v
2k1il1le
2kil2y
ki2lye
2k1i2m2a.
ki1ma
2k1i2ma1i
ki2mak
ki2máb
ki1má
2k1i2mád
ki2má1i
ki2máj
ki2mák
2k1i2mám
ki2mán
2kimár
ki2mát
ki2máv
ki2mit
ki1mi
2k1imp
2k1i2n2a.
ki1na
2k1i2na1i
2k1i2náb
ki1ná
2k1i2nár
kin2c1sa
kinc2s
kin2c1sá
kin2csel
kin1c1se
kin2c1so
2kind
2k1inf
kin2gas
kin1ga
2k1inj
2k1integ
kin1te
kio2l
ki1o
kio2m
2k1i2onn
2k1i2o1no
2kion2t.
ki3óh
ki1ó
2k1i2ó2n.
2kipar
ki1pa
ki1pf
ki1p2la
ki1p1le
ki1p2r
2k1ips
2k1i2rat
ki1ra
2k1i2ránn
ki1rá
2k1i2rán2y
2kirg
2k1irh
2k1irk
2k1i2rod
ki1ro
2kirom
2k1irr
2k1irt
ki2sad
ki1sa
ki2s1aj1tó
ki2s1akn
ki2sal
ki2sa2p
ki2sas
ki2sál
ki1sá
ki2s1e2c
ki1se
ki2s1e1lő
ki2s1emb
ki2s1es
ki2s1é2k
ki1sé
ki2s2i.
ki1si
ki2s1in
ki2s1i1pa
ki2s1i2ra
ki2s1isk
ki1skál
kis1ká
2kism
ki1smár
kis1má
ki1s2min
kis1mi
kis2nyá
kisn2y
kis1o1ko
ki1so
ki2s1oros
kiso1ro
ki2sö2r
ki1sö
ki2ső
kisp2
kis1pl
ki1s2por
kis1po
ki1s2pó
kis3s2z
kist2
ki1s2tar
kis1ta
ki1ste1ri
kis1te
ki1s1tí
ki1stop
kis1to
ki1str
ki1s1tu
ki1t2r
kit2t1er
k2it1te
kit2t1ö
kiu2s
ki1u
kiu2t
kiú2s
ki1ú
kiú2t
kivíz1
ki1ví
2k1i2vó
1kí
2k1í2gé
k1íg2y.
kíg2y
kí2ja
kí2nal
kí1na
kí2n1árb
kí1ná
kí2n1e
kí2né2l
kí1né
kí2n1ó2
kí2rá
2k1í2re
2k1í2ró
2kítm
kítő1a2
kí1tő
kítő1e2
2k1í2v.
2k1ívb
2k1í2ve
2k1í2vé
2k1í2vű
kí2zé
kí2zü
kí2zű
kk1abl
k1ka
k2k1a2da
kk1ajt
k2k1a2kad
kka1ka
k2k1akk
k2ka1lá
k2k1alj
k2k1alm
kka2ró2ra
kka1ró
k2k1a2u
k2k1a1zo
k2k1áll
k1ká
kk1árf
kká2rok
kká1ro
k2k1á2ru
kká2s
k2k1á1sá
k2k1áth
k2k1átl
kk1á1tu
k2k1átv
kk1á2zó
kk1br
kke2c
k1ke
kk1e1c1se
kkec2s
kk1e2d2z
kk1e2gé
kke2l1e2g
kke1le
k2k1e2lem
k2k1el1lő
kke2lő1á
kke1lő
kk1e1mu
kke2ra
kk1erd
kke2ró
k2k1e2rő
kk1e2ti
kk1e2vé
k2k1ég
k1ké
k2k1é2l
k2k1é2pí
k2k1ér1té
k2k1érz
kk1fr
kk1gr
kki1á2
k1ki
kk1i2do
kki1e2
k2k1i2ga
k2k1i1gé
k2kill
k2k1ind
k2k1ing
k2k1ink
k2k1int
k2k1i2par
kki1pa
k2k1i2ro
kk1is1ko
k2k1ism
k2k1í2r
k1kí
k2k1íz
k1k2lí
kk2l2ó.
kk1ló
k1k2lu
kko2laj
k1ko
kko1la
kk1ol1ló
k2k1o2pe
kkor2dá
k2k1org
kko2r1os
kko1ro
kko2r1út
kko1rú
k2k1orz
k2k1oszl
kkos2z
kkó1p2
k1kó
kk1ó2rá
kkö2l
k1kö
kk1ö1lő
k2k1önt
kk1ötl
kk1ő2ri
k1kő
kk1ő2rö
kk1őr1s2
kk1ő1rü
kk1pr
kk2ris
kk1ri
kk2rit
kk2rí
kk1sm
kk1sp
kk1st
kk1t2r
kk1udv
k1ku
kk1urt
k2k1u1tá
k2k1új
k1kú
k2k1üg
k1kü
kk1ü2lő
k2k1ü2t
k2k1ü2v
kk1üzl
kk1űr
k1kű
kk2vó
kla1g
k1la
kla3ko
kl2a1k1ri
kla2p1á2t1a2
kla1pá
k1la1p1la
kla1s2k
kla2u1zá
kla1u
klá1ma2
k1lá
klá2mal
klá2m1an
klá2mas
klá2m1á2
klá2m1á2r1a2d
klámá1ra
klá2m1e2g
klá1me
klá2m1eh
klá2mel
klá2mer
klá2m1é2r.
klá1mé
klá2m1ér1té
klá2m1i2k
klá1mi
klá2min
klá2mis
klá2mol
klá1mo
kle1í2
k1le
kle2t1a2n
kle1ta
kle2tál
kle1tá
kle2t1elv
kle1te
kle2t1e2r
kle2t1e2v
kle2t1é2r.
kle1té
kle2t1ét1be
kle2t1é2té
kle2t1é2v2e.
kleté1ve
kle2tin
kle1ti
k2li1ni
k1li
kli1ó2
kli2pel
kli1pe
k2li1sé
kli2só
1k2lím
k1lí
k2lo1á
k1lo
k2lopf
k2lot2y
kló2rad
k1ló
kló1ra
kló2raj
kló2ra2n
kló2rat
kló2ris
kló1ri
kló2rol
kló1ro
k2lub
k1lu
klu2b1a
klu2bá
klu2b1e
klu2bir
klu1bi
klu2bol
klu1bo
klu2se
klus3s
klu2s3z
kma1b
k1ma
kmai2ko
kma1i
kmas2
kmá2nya2n
k1má
kmán2y
kmá1nya
knag2
k1na
knak2
kna1kr
kni2a2
k1ni
kni1ka2
kni2k1al
kni2k1as2
kni1s2z2
knőc1c
k1nő
kn2ő3r
1ko
ko2áz
ko1á
ko2be
2k1obs
ko2c1ho
koc2h
2k1o2dav
ko1da
2k1o2dú
kogás1s
ko1gá
ko1g2ra
ko2kád
ko1ká
2ko1ke2
ko2k1er
2k1o2k1ir
ko1ki
2k1o2kí
2k1okl
2k1okm
2k1o2kol
1ko1ko
ko2k1o2v
2k1o2koz
kok2s1ze
koks2z
2k1ok1ta
ko2l1ajt
ko1la
kol1á1ro
ko1lá
2k1oldal
kol1da
2k1ol1dá
2k1ol1dó
ko2lid
ko1li
ko2l1i1gá
k1o2lim1p2
ko2l1i2n2a.
koli1na
ko2li1ta
kol2l1ad
kol1la
kol2lál
kol1lá
kol2le2l
kol1le
kol2les
kol2l1ét
kol1lé
ko2lö
2k1oltár
kol1tá
2k1olvas
kol1va
2k1om1bu
ko2mil
ko1mi
kom2p1é2r.
kom1pé
kom2p1ol
kom1po
ko2n1a2d
ko1na
ko2naj
ko2n1a2l
ko2n1a2n
ko2n1ar
ko2n1as
ko2n1a1u
ko2n1ál
ko1ná
2kon1bá
2konbet
kon1be
konc1c
kon1cé2
kon2c2h
kon2c1sá
konc2s
kon2c3so2r.
kon1c1so
kon2c3so1ro
ko2n1e
2konén
ko1né
ko2n1é2r.
2kon1gu
2konhab
kon1ha
ko2nir
ko1ni
2konog
ko1no
ko2n1ol
2ko2n1o2la
ko2nor
2konosn
2konosr
2konost
2kon1pa
2kon1pr
2konrét
kon1ré
2kon1tö
ko2nü
ko2nyag
kon2y
ko1nya
ko2nyal
ko2nye
ko2nyó
konz2
2konz2s
ko2ón
ko1ó
kopá2sé
ko1pá
2kopc
k2o1p1ro
2kop1ti
ko2r1a2d
ko1ra
ko2rag
ko2r1aj
ko2r1a2n
kor1ass
ko2r1aszt
koras2z
ko2r1átl
ko1rá
2k1orc2h
kor2csal
korc2s
kor1c1sa
kor2dad
kor1da
kor2d1ag
kor2d1a2s
kor2dác
kor1dá
kord1d
kor2d1e2l
kor1de
kor2dem
kor2des
k1ordít
kor1dí
kor2d1ö
kor2d3z
ko2r1e2c
ko1re
ko2reg
ko2r1e2l
ko2r1er
ko2r1e2s
kor1f2
korgás1s
kor1gá
2k1o2ri1e
ko1ri
kor1ill
ko2r1ing
ko2r1in1te
kor1isk
ko2r1i2zom
kori1zo
ko2ros2z
ko1ro
ko2r1o2v
ko2r1ó1i
ko1ró
ko2rón
ko2rö
ko2rő
2k1or2r.
2k1orrk
2k1orrt
kor1s2
kor1t2r
ko2r1ú1to2
ko1rú
ko2rü
ko2s1as
ko1sa
ko2sál
ko1sá
ko2se
kos3s1ze
kos2s2z
kos3s1zo
kos3s1zü
2k1ostr
ko2sü
ko2szal
kos2z
ko1s1za
ko2szer
ko1s1ze
ko2szir
ko1s1zi
ko2s1zí2
k2o1s1zo
ko2s1zó
2kosz1tá
kotókés2z1
ko1tó
kot2ókés
kotó1ké
kotó1st
ko2ut
ko1u
2kov2ari1a
ko1va
kova1ri
ko2vi1é
ko1vi
ko2xi
koza2tal
ko1za
koza1ta
kozókés2z1
ko1zó
koz2ókés
kozó1ké
kozóköz1
kozó1kö
2kozó1ö
1kó
kóa2d
kó1a
kó1bl
kóc3c
kó2cem
kó1ce
kó2c2h
kó2d1ab
kó1da
kó2dak
kó2d1a2l
kó2d1a2n
kó2daz
kód1d
kó2d1e2l
kó1de
kó2dem
kó2d1e2r
kó2d1es
kó2d1és
kó1dé
kó2din
kó1di
kó2dö
kó2dü
kó2d1zá
kód2z
kóé2r
kó1é
kó1f2r
kói2g
kó1i
kóí2v
kó1í
kó1k1ré
kó1kv
2k1ó2n2é.
kó1né
kó2nét
2k1ó2n2i.
kó1ni
2k1ó2no
kó2pan
kó1pa
kó2p1as
kó2p1e2l
kó1pe
kó2pir
kó1pi
kó1pó2
kó2p1ór
kó2pü
2k1ó2ra1a
kó1ra
kó2r1a2c
2k1ó2ra1i
kó2r1a2n
kó2ras
kó2ráb
kó1rá
kó2rád
kó2rág
kó2rá1i
k1ó2ráj
kó2rám
kó2rán
kó2rár
kó2rát
kó2ráv
kó2rel
kó1re
kóre2s
kó2réj
kó1ré
kó2r1é1ve
kó2r1ir
kó1ri
kó2r1ok
kó1ro
kóro2ka
kóro2ko
kór1os1to
2kóród
kó1ró
kó2r1ó1dá
kó2rő
2k1ó2sá1gú
kó1sá
k2ó1s1lá
kó2s1os
kó1so
kó1s2pe
kó1s1pi
kó1s2po
k2ó1st2r
kó2s1ü2
kó2s3zá1ra
k2ó1s1zá
kós2z
2k1ó2vó
kó2z1a2m
kó1za
1kö
kö2ba
kö2b1öll
kö1bö
köb1öln
kö2b1ölr
köb1ölt
kö2da
kö2dá
kö2del
kö1de
kö2der
kö2dev
kö2do
kö2dó
kö2du
kö2dú
kö2d2z
2k1ö2ko
2k1ök1rö
köl2csa2l
kölc2s
köl1c1sa
köl2c1sá
köl2csel
köl1c1se
köl2c1s1ő2
2k1ö2lel
kö1le
köles3s
2k1ö2lés2s2z2
kölés3s
kö1lé
kö2lí
kö2lő1e
kö1lő
köl1ta2
köl2tal
köl2t1e2v
köl1te
2k1öng
k1ön1té
k1ön1tő
k1öntv
köny2v1a
kön2y
köny2vá
kö2ra
kö2rá
k1ör1dö
kö2red
kö1re
kö2r1e2g
kö2rék
kö1ré
kö2r1étt
kör1f2
kö2ri2k
kö1ri
kö2r1iz
kö2ro
kö2ró
kö2r1ö2l
kö1rö
kö2r1ő2
kör1p2
kör1s2
kört2
kör1tr
kö2ru
kö2rú
körü2l1et
kö1rü
körü1le
körü2lé
kö2r1ülés
körü2lő
kö2r1ül2ő.
2k1ör2v.
2k1örvb
2k1örvh
2k1örvn
2k1ör1vö
2k1örvr
2k1örvt
2k1ör1vü
2k1ör1vű
2k1örvv
k1öszt
kös2z
2k1öt1lé
k1öt1vö
2k1ö2v.
2k1övb
2k1övh
2k1ö2vig
kö1vi
2k1övr
2k1övv
kö2za
kö2zá
kö2zen
kö1ze
kö2z1é2k
kö1zé
kö2z1í
kö2zo
kö2zó
köző1e2
kö1ző
kö2z3s
kö2zu
kö2zú
kö2z1ü2lé
kö1zü
kö2z1ü2lő
1kő
kőa2n
kő1a
kő1bl
kő2c2h
kő1d1ra
kőe2k
kő1e
kőe2l
kőe2r
kőé2l
kő1é
kőé2ne
kő1f2r
kő1gn
kő1gr
kői2t
kő1i
kő1kr
kő1pl
kő1pr
kőr1a2n
kő1ra
k1ő2re1i
kő1re
kőre2s
kőr1es2z
k1ő2r2i.
kő1ri
kő2ril
kő2rip
kő2riv
k1őrl
kőr1s2
kő2rül
kő1rü
2k1őrz
kő1s2k
kő2sö
kő1s2t
kő1tr
kőu2t
kő1u
kpá1ra2
k1pá
kpá2rad
kpá2r1at
kpá2r1ál
kpá1rá
kpá2r1út
kpá1rú
kp2la
kp2le
kp2lé
kp2lu
kpó2t
k1pó
kp2ra
kp2re
kp2ré
kp2ri
kp2rí
kp2ro
kp2ró
kp2s2z
k2rac2h
k1ra
kra2c1hi
1k2rajc
k2rakk
kra1p
krá2s1z1e
k1rá
krás2z
kren2d1ő2
k1re
k2re1o
k2re1pá
1k2rémh
k1ré
1k2réml
1k2rémr
kré3p2
1k2rist
k1ri
1k2ri1ti
kro1g2
k1ro
1k2roket2t.
kro1ke2
1k2rokod
kro1ko
kro2n1a2
kro2ná
kron1d2
kro2n1e
kro1p2
kro1str
kro1sz2f
kros2z
kró2ma2r
k1ró
kró1ma
kró2m1e
kr2ó2mis
kró1mi
k2ró1mo
1k2rónik
kró1ni
k2rup
k1ru
k2s1a2rá
k1sa
ks1e1lo
k1se
ksé2g1e1le
k1sé
ksé1ge
ksé2gis
ksé1gi
kségü2l
k3sé1gü
ksé2g1ü1lé
k1s2ká
ks2ko
k1s2la
ks2lá
ks2lu
ks2mi
k1s2ni
ks2pa
ks2pe
ks2pi
ks2po
ks2pó
ksp2r
ks2rá
k1s2ta
k1s2tá
ks2ti
k1s2tí
ks2to
k1st2r
k1s2tu
k1s2tú
k2sz1a2d
ks2z
k1s1za
ksza1e2
k2szag2y
k2sz1aj
ksza2ke
ksza2ké
ksza2k1ü2
k2sz1am
ksz1an2y
k2sz1a1ré
k2sz1ág
k1s1zá
kszáraz1
kszá1ra
k2sz1e2g2y
k1s1ze
k2sz1e1la
k2sze1mu
k2sz1eng
k2szev
ksz1élet
k1s1zé
kszé1le
kszé2t1
k2sz1id
k1s1zi
kszi1lá2
kszi2p
ksz1i1pa
ksz1isk
k2szír
k1s1zí
k2sz1old
k1s1zo
k2szol1tó
k2s2z1os2z
kszö2g1ell
k1s1zö
k3szö1ge
kszö2g1elv
kszt2
k2s2zu1ni
k1s1zu
k2szut
k2sz1üg
k1s1zü
k2szüt
k2sz1ü2z
ksz1z
kt1a2da
k1ta
kt1a1já
kt1a2lap
kta1la
k2t1alb
k2t1all
kta2n1á2s
kta1ná
kta2n1ó2
kta1p2
k2t1arm
kt1a2ud
kta1u
kt1a1zo
k2t1ábr
k1tá
kt2á2lal
ktá1la
kt1ál1ló
k2t1álm
ktára2d
ktá1ra
ktá2r1a1da
ktá2r1a1dó
ktá2raj
ktá2r1az
k2t1átf
k2t1átv
k2t1e2g
k1te
k2t1elg
k2t1elh
kt1el1já
k2t1ell
k2t1els
k2t1elv
k2t1enz
kte2rad
kte1ra
kte2ral
kte2rár
kte1rá
kte2rát
kte2reg
kte1re
kte2r1e2l
kte2r1in
kte1ri
kte2ros2z
kte1ro
k2t1esem
kte1se
kté2lő
k1té
k2t1érl
k2tér1mi
k2t1ér1te
k2t1éss
k2t1é2vet
kté1ve
kt1fr
kt1gr
k2t1id
k1ti
kti2g
k2t1i1gé
kt1ill
kti2m
kt1i1ma
k2t1ind
k2t1inf
k2t1i2r
k2t1isk
k2t1ism
k2t1i2vás
kti1vá
k2t1íg
k1tí
k2t1í2r
ktí2v1e2l
ktí1ve
kto1g2ra
k1to
kto2n1á
k2t1o2pe
kto2ras
kto1ra
kto2r1e2
kto2rol
kto1ro
k2t1ös
k1tö
k2t1öt
kt1pr
k1t2ran
kt1ra
k1tra1u
k1tréf
kt1ré
k1t2rén
k1t2ri1ó
kt1ri
ktro2ná
kt1ro
k1t2rón
kt1ró
kt1sp
kt1st
kt1s2z
kt1t2r
ktu2m1e
k1tu
ktu2min
ktu1mi
ktu2sab
ktu1sa
ktu2s1an
ktu2szár
ktus2z
ktu1s1zá
ktu2s1ze
ktu2s1zé
ktü2l
k1tü
k2t1ü1lé
k2t1üt
k2t1ü2v
1ku
kuá2r
ku1á
2k1ucc
kue2l
ku1e
kue2r
kue2s
ku1f2r
2k1u2go
2k1ugr
ku2gya
kug2y
2k1uj2j.
2k1ujj2a.
kuj1ja
2k1ujjad
kuj2j1a2da
2k1ujja1i
2k1ujjak
2k1ujjal
2k1ujjam
2k1ujjas
2k1ujjat
2k1uj1já
2k1ujjb
2k1ujjg
2k1ujjh
2k1uj1ji
2k1ujjk
2k1ujjn
2k1uj1jo
2k1ujjp
2k1ujjr
2k1ujjs
2k1ujjt
2k1uj1ju
2k1uj1jú
kuk2k1ó2
kula1k
ku1la
kul2csal
kulc2s
kul1c1sa
kul2c1se
kulus1s
ku1lu
ku2m1a2d
ku1ma
ku2mal
ku2mü
ku2nad
ku1na
ku2nal
ku2n1ás
ku1ná
ku2ne
ku2n1in
ku1ni
kun3n
ku2nor
ku1no
ku2n1ó2
ku2nü
2k1u2p.
2kupr
2ku1pu
ku2rad
ku1ra
2k1u2ra1i
ku2rak
2kural
ku2ram
ku2rá1u
ku1rá
2k1urn
ku2rol
ku1ro
kuru2c3s
ku1ru
ku2sal
ku1sa
ku2sas
ku2sál
ku1sá
ku2s1e2
ku2sis
ku1si
ku2s1i2z
ku2s1ó2
ku2sö
kus3s1ze
kus2s2z
ku2s1ú
ku2sü
ku2szal
kus2z
ku1s1za
ku2sza2r
kusz1e2s
ku1s1ze
ku2sz1é2l
ku1s1zé
ku2sz1é2p
ku2szét
ku2sz1il
ku1s1zi
ku2szis
ku2s1zü2
2k1u2ta1ló
ku1ta
2k1u2ta1sí
2k1u2taz
ku2tál
ku1tá
2k1u2tán
2k1utc
2k1u2tol
ku1to
2k1u2tód
ku1tó
ku2tó1p2
ku2z2s
1kú
kú2jí
2k1újs
kú2p1a2
kú2p1á
kú2p1e
kú2t1a2
kú2t1á2
kú2t1e
kút1fé2
kútfél1
kú2tos
kú1to
kú2t1ő
2kú2tu
kú2t1ü2
1kü
kü2ge
kü2g2y
2k1ü2lep
kü1le
kü2lo
kül2t1á
2k1ü2reg
kü1re
2k1ü2rí
kü2rü
2k1üst
kü2s1zá
küs2z
kü2s1zí
2k1ütk
küvés3s
kü1vé
2k1ü2vö
2k1ü2ze
1kű
2k1űrh
2k1űrm
kű2ro
kű2r1ős
kű1rő
kű2zé
kű2ző
1k2van
k1va
k2varc
1k2vark
kvá2nyol
k1vá
kván2y
kvá1nyo
kváro1si2
kvá1ro
kvé2se2l
k1vé
kvé1se
kvé2so
kvés3s
1k2vin
k1vi
1k2vitt
k2vóc
k1vó
k2vó1ta
k2vó1tá
kvő1c1sa2
k1vő
kvőc2s
1ky
kyd2n
ky2fajtán
ky1fa
kyfajt2
kyfaj1tá
ky2fajtár
ky2fajtát
ky1i2s
ky1i2v
kyk2k
kyk2ne
ky2se
ky2s1ü2
ky1üz
kza2tal
k1za
kza1ta
kza2t1e2
kza2t1ó2
2l.
1la
laa2d
la1a
laát1
la1á
laá2ta2
2labár
la1bá
3labd
2labr
2l1abs
la2c1ag
la1ca
la2c1aj
la2c1al
la2cem
la1ce
la2c1e1te
la2c3ha
lac2h
la2c1hé
lac1i1ta
la1ci
lac2k1orr
lac1ko
lac1orr
la1co
la2c1ö2
la2cő
la2csalj
l2acsal
lac2s
la1c1sa
la2c3ság
la1c1sá
la2c3sor
la1c1so
lac3sü2l
la1c1sü
la2c3süt
lac3s2z
la2cú
la2cü
2l1a2da1ta
la1da
2l1a2datb
2l1a2dat1k2
2l1a2datn
2l1a2da1to
2l1a2datr
2l1a2dat1t2
2l1a2da1tu
ladás1s
la1dá
2l1adl
2l1a2dog
la1do
2l1a2do1má
3l2a3don
ladói2ko
la1dó
ladó1i
2l1a2duk
la1du
la1dy2
lae2d
la1e
lae2l
lae2r
laé2d
la1é
laé2r
2laff
la1fl
la2gac
la1ga
lag1a2dó
la2g1a2n
lag1ass
la2g1a2t
la2ga1u
la2gav
la2gág
la1gá
lag1áll
la2g1á2r.
la2g1á2rá
la2g1árb
la2g1á2re2
la2g1á2ré
la2g1árf
la2g1árh
la2g1árk
la2g1árm
la2g1árn
la2g1á2ro
la2g1árr
la2g1ár1s2
la2g1árt
la2g1e1re
la1ge
la2gés
la1gé
la2gid
la1gi
la2g1i2k
la2gim
la2gis
la2gí
la2g1ont
la1go
la2g1os2z
la2góc
la1gó
la2g1ó2rá
la2gó1vá
la2gön
la1gö
la2g1ud
la1gu
la2g1ur
la2g1ut
la2g1ü
la2gű
la2gyú
lag2y
la2i1re
la1i
la2jag
la1ja
la2j1a2l
la2jap
la2j1ar
laj1ass
laj1a2s2z
la2j1az
la2j1ál
la1já
2l1a2jánl
la2j1á2r.
l2ajár
la2j1árh
la2j1árn
la2j1árs
la2j1árt
la2jás
la2j1áth
la2j1áts
la2j1elt
la1je
la2jen
la2j1es
la1ji2
la2jin
la2j1is
la2jí
la2j1ol
la1jo
la2j1oml
la2j1or
2lajoz
la2j1ór
la1jó
la2j1ös
la1jö
la2jő
l2ajs
laj1s2z
la2j1u2t
la1ju
la2j1ü2
la2kad
la1ka
lak1alj
l2akal
la2k1alm
la2k1a2lo
lak2a2t1a2n
l2aka1ta
la2k1ác
la1ká
la2k1álk
lak1áll
la2k1árn
lak1ásás
laká1sá
2lak1ci
la2k1eg
la1ke
la2k1e2l
la2k1é2r.
la1ké
la2kér1te
la2k1i1ko
la1ki
la2k1ír
la1kí
lak2k1as
lak1ka
lak2ke2c
lak1ke
lak2kel
lak2kol
lak1ko
2l1akkor
lak2kö2l
lak1kö
la1k1lu
2l1aknáb
lak1ná
2l1aknáh
2l1aknás
la2kob
la1ko
l2a2k1op
l2a2k1orm
la2k1os2z
l2akos
la2k1ov
2lakóz
la1kó
la2k1ös
la1kö
la1k2rém
l2ak1ré
lak2rip
l2ak1ri
la1k2ru
lakt2
2l1ak2t.
2l1aktiv
lak1ti
la2kuj
la1ku
2laku1lá
2lakulg
2lakulh
2lakulj
2lakuln
2laku1lo
2laku1ló
2lakuls
2laku1lu
2lakulv
2lakús
la1kú
la2k1ú2to
2lakúv
2lak1za
la2la2g1
1la1la
l1a2la1ku
2l1a2lá1í
la1lá
lalás1s
2l1alb
2l1alc
2l1alf
2l1aljn
2l1al1ka
2l1alkot
lal1ko
2l1al1ku
2l1al1le
lal2tár
lal1tá
lal2te
2l1alt2i.
lal1ti
2l1alton1k2
lal1to
lal2t1ó2
lal2t1ü2
2l1alt1vé
2l1al1vi
lam1a1da
l2amad
la1ma
la2m1a1ka
la2m1an2y
l2a2m1a2p
la2m1a1rá
la2m1a2z
l2ame2g
la1me
la2m1eg2y
la2m1e1le
lam1emb
la2m1es2z
la2mé2k
la1mé
lam1é1le
la2mé1ne
la2m1érv
la2minv
la1mi
la2m1ism
la2m1ist
la2m1í2
2l1am1mó
la2m1ol
la1mo
lam1org
la2m1os2z
l2amos
la2m1ó2r
la1mó
la2mö
la2m1ur
la1mu
la2m1ü
l2a2n1a2d
la1na
lana2g
la2n1aj
la2nal
1lan1a1la
la2nam
la2nar
la2n1áll
la1ná
la2n1á2r.
la2n1á2ra1i
laná1ra
la2n1á2rak
la2n1á2rá
la2n1árr
la2n1árv
lanás1s
lan2csel
lanc2s
lan1c1se
lan2csem
lan2c1si
lan2csol
lan1c1so
lan2d1ál
lan1dá
lan2d1el
lan1de
lan2d1é2r.
lan1dé
lan2diz
lan1di
la2ner
la1ne
la2nes
la2n1ép
la1né
lan2g1as
lan1ga
lan2gen
lan1ge
lang3g
2l1angin
lan1gi
la2nil
la1ni
l2a2nip
la2nis
2l1ankét
lan1ké
lan1kr
la2n1os2z
l2anos
la1no
lans2
lan2tag
lan1ta
2lantác
lan1tá
la2nü
la2nyag
lan2y
la1nya
1lany1a2la
la2nya1u
2lanyá1i
la1nyá
la2ny1ál
2lanyán
la2nyás
2lanyáv
la2ny1e2lem
l2a1nye
lanye1le
lany1e2rő
la2ny1í2r
l2a1nyí
la2ny1ol
l2a1nyo
la2nyó
l2a2nyü
laó2r
la1ó
2l1a2p2a.
la1pa
lap1akk
lap1akt
1lap1a2la
l2apal
lap1alk
la2p1atm
la2p1att
la2p1a2u
la2paz
la2páb
la1pá
la2p1á2g
la2pák
la2pán
lap1á2rá
lap1árh
la2p1árr
la2pe2l
la1pe
lap1e1le
la2pe2m
la2p1en
la2p1e2s
la2pik
la1pi
1lap1il1la
lap1il1le
la2p1im
la2p1i2n2a.
lapi1na
la2p1ing
la2p1int
la2p1i2p
la2p1i2s
la2p1i1ta
la2p1iz
lap2lat
lap1la
la2p1o2ku
la1po
lap1or1g2
lap1orn
lap1orz
lapos1s
la2p2ó.
la1pó
la2pób
la2pón
la2pór
la2pö
la2pő
2lappar
lap1pa
2lap1rí
lap1s2
2lapún
la1pú
2lapúv
la2pű
la2r2a.
la1ra
la2ran
2l1arc
larc3c
2l1a2rén
la1ré
lar2m1e
lar2min
lar1mi
2l1art
2l1arz
la1s2pa
la1s2pi
la1st1ra
l2astr
la2sz1abl
l2aszab
las2z
la1s1za
la2sz1al2t.
la2szas
2l1a2szat
la2szás
la1s1zá
la2sz1én
la1s1zé
la2szód
la1s1zó
lasz3s
lasz2t1alj
lasz1ta
l1asztam
la2sz1út
la1s1zú
la2t1a2d
la1ta
la2t1aj
lat1alak
lata1la
la2t1alk
la2t1alt
lat1a1rá
l2atar
la2t1a2ro
lat1ar1ti
lat1aszt
latas2z
lat1att
la2ta1u
la2taz
l2a2t1áj
la1tá
lat1áll
l2atál
la2t1árad
l2atár
latá1ra
la2t1á2ra1i
la2t1á2rak
la2t1á2ram
la2t1á2rat
la2t1á2ráb
latá1rá
la2t1á2ráh
la2t1á2rán
la2t1á2ré
la2t1árh
la2t1árn
la2t1á2rok
latá1ro
la2t1árr
la1t1ár1tá
la2t1ár1tó
la2t1á2rú
la2t1árv
lat1á2s2z
l2atás
la2t1á1ta
latdi2al
lat1di
latdi1a
la2t1e2g
la1te
la2t1e2lem
late1le
la2t1ell
la2tep
la2t1er1k2
late2s
la2t1ess
la2te1s1ze
lates2z
la2t1e1ti
la2t1e1to
la2t1e2vő
la2t1é2ke2
la1té
lat1é2ké
lat1ékk
la2t1é2ré
l2atér
la2t1é2rő
la2tér1te
la2t1érv
lat1éss
la2t1i2ko
la1ti
lat1ik1ra
lat1in1te
l2atin
la2t1inv
la2t1ist
l2atis
la2t1iz
l2a2t1ír
la1tí
lat1í1vé
lat1k2
la2toj
la1to
la2t1okm
lat1ol1da
la2toll
la2t1oml
la2t1os2z
la2t1otth
la2t1ó1rá
la1tó
la2t1ó1ri
la2t1ö1vi
la1tö
la2t1ö2vö
la2t1őr
la1tő
2l1atta1ko
lat1ta
latta2n1ó2
2lattv
la2tut
la1tu
la2tül
la1tü
lat1ü1ze
l2atüz
l2atű2z
la1tű
la2t1ű1ző
la2tyá
lat2y
la2ub
la1u
la2uk
la2us
la2u1to
laü2z
la1ü
la2vat
la1va
2l1a2vu
la1yé
la1yig
2lay1rő
lazac1c
la1za
laza2c3s
laz1ma1
2laz1má
2l1a2zon
la1zo
1lá
lá2bar
lá1ba
lábas1s
lá2bál
lá1bá
lá2b1e2l
lá1be
lá2b1i1na
lá1bi
lá2b1i2z
láb1org
lá1bo
láb1orr
lá2bö
lá2bő
2láb1rá
lá2b1u2s
lá1bu
lá2bü
lá1c1sé2
lác2s
lá2csét
lá2cs1il
lá1c1si
lá2csi2p
lá2c1sí2
láda1s
lá1da
lá2d1az
lá2d1e2r
lá1de
lá2det
lá2dim
lá1di
lá2d1or
lá1do
lá2d1ott
lá2d1ó
lá2dül
lá1dü
lá2d2z
lá2gab
lá1ga
lá2ga2c
lág1a2da
lá2gad
lá2g1a2g
lá2g1al
lá2ga2n
lá2gas
lág1ass
lága2t
lá2g1atl
lá2g1a1to
lá2g1att
lá2gál
lá1gá
lág1áll
lá2gám
lá2g1á2rak
lágá1ra
lá2g1á2ro
lá2g1á2to
lá2gép
lá1gé
lá2gi2d
lá1gi
lá2gi2gé
lági2g
lá2gim
lá2g1ott
lá1go
lá2g1ó2
lá2gő
lág1s
lá2gü
lá2gű
2lá1gyá
lág2y
2lá1gyo
lá2has
lá1ha
2lá1he
láí2r
lá1í
lá2lad
lá1la
lá2l1a1ka
l2á2l1al
lála2n
lá2l1an2y
lá2l1ar
lá2la1u
lá2l1ág
1lá1lá
lá2l1á2l
lálás1s
lá2l1átj
2l1áldás
lál1dá
2l1áldoz
lál1do
lá2lel
lá1le
lá2l1est
lá2l1e2v
lá2lél
lá1lé
lá2lim
lá1li
lá2l1is
lá2lí
2l1állam
lál1la
2l1állat
2l1állás
lál1lá
2l1állk
2l1állom
lál1lo
2l1állv
2l1álmaim
lál1ma
lálma1i
2l1álmo2k.
lál1mo
2l1álmom
2l1álmos
2l1álmuk
lál1mu
2l1álmunkb
2l1álokaih
lá2l1o2ka1i
lá1lo
lálo1ka
2l1álokain
2l1álokair
2l1álokán
lá2l1o2ká
2l1álokát
2l1álo2ká1u
lá2l1o2ko
2l1álo2konk
2l1á2l1o2ku
lál1ó1rá
lá1ló
lá1lö2
lá2l1öv
2l1á2l1u2t
lá1lu
lá2l1ú2t
lá1lú
lá2lü
lá2lű
lá2m1a2d
lá1ma
lá2m1aj
lám1ass
lá2m1a1u
lá2m1ál
lá1má
lá2m1á2z
lám1b2
lám1ell
lá1me
lám1e1rő
lá2mes
lá2mék
lá1mé
lá2m1érv
lá2m1int
lá1mi
lám1ist
lá2mí
lá2m1or
lá1mo
lá2mó
lá2mö
lá2mő
lá2m1ú2t.
lá1mú
lá2mü
lá2mű
lá1na2
lá2n1ag
lá2nal
lá2n1an
lá2nar
lá2n1as
lá1ná2
lá2nár
lánc3c
lán2ce2l
lán1ce
lán2c1ég
lán1cé
lán2c1sá
lánc2s
lá2nem
lá1ne
lá2n1er
lá2nép
lá1né
lán2g1at
lán1ga
lán2gál
lán1gá
lán2g1e
lá2nil
lá1ni
lá2n1is
lán2k1e2l
lán1ke
lán2k1ó2ra
lán1kó
lán2k1ö2v
lán1kö
lá3nok
lá1no
lá2nol
lán1sp
lán1s2z
lánt2
lán1tr
lá2nü
lá2nyaj
lán2y
lá1nya
lá2nya1la
lá2nyam
lánya2n
lá2nyar
lá2ny1as
lá2nyav
lá2ny1el
lá1nye
lá2ny1é2r.
lá1nyé
lá2nyó
lá2nyö
lá2pét
lá1pé
lá1pi2
2l1á2pol
lá1po
lá2rad
lá1ra
2l1á2ra1i
lá2ra1ka
l1á2rakb
l1á2rakk
l1á2rakr
lá2r1a2l
2l1á2ram
2l1á2rat
lá2r1av
l1á2raz
2láre2n
lá1re
lá2res
l1árkot
lár1ko
2l1ár1nya
lárn2y
lár1s2
lárt2
lár1tr
2l1á2r2u.
lá1ru
lá2rug
2l1á2ruh
2l1á2ruk
2l1á2rul
2l1á2rus
2l1á2rut
2l1á2ruv
lá2rú1a
lá1rú
lá2rü
lá2s1a2d
lá1sa
lá2s1aj
lá2sal
lá2s1a2r
lá2saz
lá2s1á2g
lá1sá
lá2s1á2ra1i
lásá1ra
lá2s1árak
lá2s1á2rá
lá2s1árb
lá2s1á2ré
lá2s1árh
lá2s1árr
lá2s1árt2
2l1á2sás
lá2s1á2to
lá2se
lásegyez1
lás1e2g2y
láse1gye
lá2sí2r
lá1sí
lá2sott
lá1so
lás1otth
lá1só2
2l1á2s2ó.
lá2s1ór
lá2ső
lás3s1zé
lás2s2z2
lá2su2t
lá1su
lá2sű
lá2sza2s
lás2z
lá1s1za
lás3zav
2lászed
lá1s1ze
lá2szeg
lá2sz1e2m
lá2szen
lá2szi2p
lá1s1zi
lás2zkés2z1
lász1k2
lász1ké
lá1s1zö2
lá2szö2l
lá2szöv
lász3s
lá2s1zü
lá2taj
lá1ta
lá2t1e2re
lá1te
láté2tel
lá1té
láté1te
2l1át1he
lá2t1i2o
lá1ti
2l1á2tí
2lát1kö
lá2t1os2z
lá1to
lá1t2rá
lá1t1ri
2l1át1te
2l1át1tö
2l1át1tű
l1á2t1ú2s
lá1tú
2l1át1vé
l1át1vi
2lá1vi
lá2viz
2l1á2vó
2lá1vu
lá2z1adot
lá1za
láza1do
lá2z1al
lá2z1árh
lá1zá
láz2á2rus
lázá1ru
3lázb
lá2z1el
lá1ze
lá2z1i2s
lá1zi
lá1zo2
lá2z1olt
lá2zsal
láz2s
lá1z1sa
lá2zsan
lá2z1só
láz3s2z
l2b1is
l1bi
lb2li
lb2lo
lb2lú
lb2ra
lb2re
lb2ri
lb2ro
lb2ró
lb2ru
lc1ajt
l1ca
lc1alk
l2c1a2to
l2c1ág
l1cá
l2c1ál
lc3c1si
lc2c2s
lc3c1so
lc3c1sö
l2c1e2le
l1ce
lc1elr
l2c1emb
lc1emel
lce1me
lc1es2z
l2c1e2v
lce2z
l2c1e1ze
l2c1ép
l1cé
lc1é1vi
lc1fr
l1c3ha
lc2h
l1c3há
l1c3ho
l1c3hu
l1c3hú
l1c3hü
l2c1i2d
l1ci
lci2tér
lci1té
lc1ív
l1cí
lc1k2r
l1c2lu
l2c1ost
l1co
l2c1os2z
lc1ó2r
l1có
lc1ök
l1cö
lc1ös
lc1pr
l2cs1a2d
lc2s
l1c1sa
l2csakt
lcs1alap
lcsa1la
l2cs1a2n
lcsa2p1á2g
lcsa1pá
lcs1apr
lcsa2r
lcs1a1ra
lcs1a1ro
l2cs1a2s
l2csaz
l2cs1ág
l1c1sá
l2cs1ál
l2c2s1á2t1a2
l2csá1tá
l2c2s1áth
l2c2s1átj
l2c2sátv
l2csec
l1c1se
l2cs1e2g
l2c3sej
l2c2s1elf
l2cs1elt
l2cs1elv
lcs1emb
l2cs1eml
l2cserd
lcse2r1e2l
lcse1re
l2c2s1e2rő
lc1s1e1se
lcse2t
l2cs1e1te
l2csél
l1c1sé
l2cs1é2rés
lcsé1ré
l2csér1le
l2csér1te
l2cs1ér1té
lcs1ér1ve
l2cs1ér1vé
lcsé2sz1ék
lcsés2z
lcsé1s1zé
l2cs1é2te
l2c2s1étk
l2c2s1é2vét
lcsé1vé
l2c2si1dé
l1c1si
l2csi1mi
l2c2s1inf
l2cs1ing
l2cs1int
l2cs1i2pa
l2c2s1irt
l2cs1isk
l2cs1ism
l2csi1ta
l2cs1í2z
l1c1sí
lcs1k2
l2c2s1okm
l1c1so
lcs1ors
l2cs1ö2l
l1c1sö
lcs1ő2sé
l1c1ső
lcs1s
lc3st2r
l2c3sug
l1c1su
lcs1ült
l1c1sü
lc3s1zo
lcs2z
lc3s1zó
lc1tr
l2c1uj
l1cu
lc1ül
l1cü
lc1üt
l1c3zá
lc2z
l1c3zo
ld1abl
l1da
ld1abr
l2d1a2cé
l2d1a2da1to
lda1da
l2d1a2dá
lda1i2
lda2lag
lda1la
lda2laj
lda2l1e2g
lda1le
lda2le2l
ld2a2les
lda2l1é2l
lda1lé
lda2li2z
lda1li
lda2l1í2
lda2los
lda1lo
ldalt2
l2d1ant
lda2nya
ldan2y
lda2nyá
ld1a1pó
l2d1aran
lda1ra
l2d1arc
l2d1ark
lda2t1as
lda1ta
lda2t1eg
lda1te
ld2a2t1in
lda1ti
lda2tó2s
lda1tó
ld1ág
l1dá
ld1áll
ld1álm
ld1árn
ldás3s
ld1br
ld1eg2y
l1de
l2d1e2kék
lde1ké
l2d1e2kén
l2d1e2ké1tő
l2d1e2l1a
l2d1e2le1me
lde1le
l2d1e2le1mi
l2d1e2lemn
l2d1elér
lde1lé
l2d1elk
l2d1el1lá
l2d1e1lo
l2d1e2lőh
lde1lő
l2d1e2lővez
ldelő1ve
l2d1els
l2d1el1tá
l2d1el1vé
l2d1ember
ldem1be
l2d1e2mel
lde1me
l2d1eml
lde1p2
ld1e1pe
l2d1e2rő
ld1e2vé
l2d1ex
l2d1é2g
l1dé
l2d1é2jér
ldé1jé
ld1élm
l2d1ép
l2d1érc
l2d1é1ré
l2d1é2ri
l2d1ér1te
l2d1érz
l2d1és2z
ldé2ves
ldé1ve
ld1fl
ld1fr
ld1gl
ld1gr
l2d1i2ga
l1di
l2d1i1gé
l2d1ill
l2d1i1mi
l2d1ind
l2d1int
ldi2p
ld1i1pa
l2d1i1s1za
ldis2z
l2d1ín
l1dí
l2d1ír
l2d1íz
ld1kr
ld1kv
ldo2g1as
l1do
ldo1ga
l2d1o1la
l2d1old
l2d1olt
l2d1oml
ld1orc
ld1org
l2d1os2z
l2d1ó2rá
l1dó
l2d1ó1ri
l2d1öb
l1dö
ld1ök1le
l2d1örök
ldö1rö
l2d1öv
ld1ő1ri
l1dő
ld1ősk
ld1pl
ld1pr
ld2rót
ld1ró
ld1sp
ld1st
ld1udv
l1du
ldu2r
ld1u1ra
ldus3s
l2d1u2t
l2d1új
l1dú
l2d1úr
l2d1ú2t
l2d1üg
l1dü
l2d1ü1le
l2d1ür
l2d1üz
l2d1űr
l1dű
l1d3zá
ld2z
l1d3zó
l1d3zu
1le
lea2bá
le1a
lea2d
lea2g
lea2k
lea2l
lea2n
lea2p
le2a1ré
lea2s2z
lea2v
leá2j
le1á
leá2s
leá2z
2l1e2béd
le1bé
le2bin
le1bi
le1b1la
le1b1ra
leb2rek
leb1re
leb2s
2lecc
2l1e2cet
le1ce
2l1ecset
lec2s
le1c1se
l1ed1di
le1d2res
led1re
le1d1ro
lee2s
le1e
leé2r
le1é
lefo1na2
le1fo
lefo2nal
le1fr
2l1eft
le2ga1la
le1ga
lega2r
le2g1a2s
le2g1áll
le1gá
le3g2á1to
le3g2áz
le3geb
le1ge
le2g1e2g
le3g2ele2m.
lege1le
leg1ell
le3g2elő1a
lege1lő
le3g2előj
le3g2elő1ké
le3g2előt
le3ge2lő1ze
le2g1e2lőz
le3g2elv2e.
legel1ve
le3g2el1vé
le2ge1ne
le3g2erj
le3g2e2s.
le3g2eseb
lege1se
le3g2esek
le3g2ese2n.
le3g2esn
le3g2esr
le3gest
le2g1e2s2z
legé2d
le1gé
legé2l
le3gén
legg2
le2gid
le1gi
le2g1is
legmeg1
leg1me
le3g2on
le1go
le2g1óv
le1gó
le3göng
le1gö
le2g1ös
legpec1
leg1pe
legvíz1
leg1ví
le1gyá2
leg2y
2legyb
2legyed
le1gye
l1e2gyel
legy1e2lőr
legye1lő
l1e2gyen1ge
l1e2gyék
le1gyé
le3gyi
2le1gyí
2l1egyl
le3gyo
2legys
l1egy1sé
le3gyú
le3gyű
2l1egyv
le2ic
le1i
lei2g
lei2rá
lei2s
lej2ta
lej2t1á
le2kad
le1ka
le2k1a2p2u.
leka1pu
le2k1ál
le1ká
leká2p
le2k1á2r.
le2k1á1ra
le2k1árb
2l1e2k2e.
le1ke
le2k1eg
lek1els
lek1emb
lek1e1me
lek1erj
lek1e2rő
le2k1ér1tő
le1ké
2l1e2ké2s.
le2k1id
le1ki
le2kij
le2k1ik
lek1ist2
le2kiz
lekkés2z1
lek1ké
le1k1li
lek1olt
le1ko
le2k1orz
le2k1ó2r
le1kó
le2k1ő2
2lekt1ro
2lekt1ró
le2k1ú2t
le1kú
le2küd
le1kü
2l1e2l1a2d
le1la
l1el1do
2l1e2lekt
1le1le
lele2ma
2l1e2leme1i
lele1me
2l1e2lemek
2l1e2lemes
2lelemz
lele2t1a2
lele2tel
lele1te
lele2to
2lelég
le1lé
2l1e2l1ér
lelés1s
lelé2s3z
2l1el1go
2l1el1ha
2l1el1há
2l1elhel
lel1he
l1el1ho
le2lim
le1li
lel1ing
le2l1iz
2l1el1já
2l1el1lá
2l1el1lé
2l1el1lő
2l1elméj
lel1mé
2l1elmés
2l1elnev
lel1ne
2l1el1nö
2l1eln2y
2l1e2l1os
le1lo
2l1e2l1ö2l
le1lö
2l1e2lőél
le1lő
lelő1é
2l1e2lő1fo
le2lő1í
le2lő1s1zű
lelős2z2
l1el1so
l1el1sö
2l1el1s1zá
lels2z
2l1el1ta
l1eltáv
lel1tá
2l1eltér
lel1té
2l1el1to
l1el1tö
2l1el2v.
2l1el1vá
2l1elvét
lel1vé
2l1elvh
l1elvn
2l1elvs
2l1elz
2l1e2me2l.
le1me
2l1e2meld
l1emeled
1leme1le
l1emelek
2le2me1lé
l1emelés
2l1e2melg
le2melh
l1emel1he
2l1e2me1li
2l1e2melj
l1emellek
lemel1le
2l1e2meln
le2me1lő
2l1emel2ő.
2l1emelős
le2mels
le2melt
l1emel2t.
l1emel1té
l1emel1tü
2l1e2me1lü
le2melv
l1emel1ve
lem1erk
le2m1es2s2z
lem1eszt
lemes2z
le2m1e2ti
le2m1é2k
le1mé
1le2m1é1le
lem1él1te
le2m1élv
lem1ér1de
2l1e2méss
le2m1ill
le1mi
le2mind
le2m1ing
le2m1is2z
le2mi1ta
le2m1itt
l1emle1ge
lemle1g2
lem1le
le2mo2k
le1mo
le2mol
le2m1org
le2m1os2z
le2m1ó2r
le1mó
le2mö2l
le1mö
le2m1ő2
lem1p2
le2m1u2r
le1mu
le2n1a2d
le1na
le2nal
le2n1ál
le1ná
le2nát
lenc1c
len2cel
len1ce
len2ce1me
l2end
le2n1e2g
le1ne
le2n1e2l
le2ner
lene2tel
lene1te
lené2k
le1né
le2n1é1ke
le2n1ékk
le2n1ékt
lené2l
len1é1lé
lenés3s
le2n1és2z
le2n1é2v.
len2g1e2l
len1ge
le2nid
le1ni
le2n1ip
le2n1o2k
le1no
le2nol
le2n1or
le2n1óv
le1nó
len1ő1ré
le1nő
len1s2p
len1s1ta
len1t1ra
len1t2ren
lent1re
le1nu2
le2n1ur
le2n1ut
le2n1üg
le1nü
le2n1üt
2l1enyv
len2y
le2o1a
le1o
le2oc
leo2k
le2o1li
leo2m
le2oz
leó2c
le1ó
leö2r
le1ö
le2p1aj
le1pa
lepa2p
le1p1a1pa
lep1ál1la
le1pá
le2p1eg
le1pe
1lepe2le
le2pék
le1pé
le2pél
lep1il1lé
le1pi
le2p1iz
le1p2lo
le2p1os2z
le1po
le2p1ó2d
le1pó
lep2p1elv
lep1pe
le1p2ré1se
lep1ré
le1p2ri
le1p1ro
le1p1ró
le2p1ü2lőh
le1pü
lepü1lő
2l1er1dő
2l1e2redm
le1re
le2re1jé
le2r1ék
le1ré
ler1é1te
le2r1il
le1ri
le2rir
2leróz
le1ró
2l1e2rő
le2s1al
le1sa
le2s1ál
le1sá
l1es1dé
2lese1ge
le1se
le2s1e2kéh
lese1ké
2l1ese1mé
l1e2setb
2l1e2sete1i
lese1te
lese2tel
2l1e2se1té
l1e2seth
l1e2se1ti
l1e2setk
l1e2setn
lese2t1o2
l1e2setr
le2sé1sű
le1sé
le1s2ka
le1s1ki
2l1es1kü
le1s1la
le1s2li
le1s1ma
le1s2mi
le1s2p2
les3s1za
les2s2z
les3s1zá
le1s2tar
les1ta
le1stemp
les1te
2l1esten
l1esté1be
les1té
2lest2i.
les1ti
le1s2til
2l1estj
les2t1o2r
les1to
2lestr
l1estt
2l1esz1kö
les2z
le1sz1to
le2t1ab
le1ta
le2ta2c
let1a2la
let1all
le2t1am
le2t1a1na
le2t1apr
le2t1e2kéb
le1te
lete1ké
le2t1e2la
le2t1elb
let1eleg
lete1le
le2t1elf
let1elk
let1elő1a
lete1lő
le2t1e2lő1ké
let1elr
let1e2mel
lete1me
le2t1eml
le2te1ne
le2t1ent
let1ered
lete1re
le2t1e1ré
le2t1er1k2
le2tes1té
let1eszk
letes2z
2l1e2te1té
2l1e2teth
2l1e2tetj
2l1e2tetv
le2t1éd
le1té
le2t1é2j
leté2l
1let1é1le
let1é1lé
let1élv
le2t1é2nekn
leté1ne
le2t1érb
le2t1é2re2n
leté1re
le2t1é2ri
le2t1érr
le2t1ér1tő
let1é2rül
leté1rü
leté2s2z
let1éter
leté1te
let1é2t2é.
leté1té
leté2ve1de
leté1ve
let1éves
le2t1é2véb
leté1vé
le2t1évé1i
le2t1é2vér
le2t1é2vét
le2t1é2vév
le2ti2d
le1ti
let1ikr
le2t1ill
le2tim
le2t1ing
le2t1ist
leti2s2z
let1i1s1zo
le2ti1vá
le2tod
le1to
le2t1o2k
le2t1on
let1o1ra
l2etor
le2t1ox
le2t1ö2v
le1tö
letőe3d
le1tő
lető1e
le2t1ő1ri
le1traf
let1ra
let2teg
let1te
letü2l
le1tü
le2t1ü1lé
let1ü1zé
2let2y
leu2g
le1u
le2uk
leu2r
leu2t
2leve1ne
le1ve
2leve1ní
2leve1nü
l1e2ve2z.
l1e2vezg
l1e2vezh
l1e2vezn
l1e2vezt
le2ve1zü
l1e2vezv
levél1l
le1vé
le2vo1lú
le1vo
le2xá
le2x1el
le1xe
le2x1e2p
lexkés2z1
lex1ké
le2xö
le1yé
le2zer
le1ze
lező1a2
le1ző
lezőe2r
lező1e
lezőé2n
lező1é
lező1s2p
2l1ezr
1lé
2l1ébr
lé2ca
lé2cá
léc3c
lé2c1ék
lé1cé
lé2c3h
lé1ci2
lé2cim
lé2co
léc2s2
léc3sk
lé2d1as
lé1da
lé2d1el
lé1de
lé2d1emb
lé2dé2l
lé1dé
lé2d1és
lé2dil
lé1di
lé2dos
lé1do
lé2dot
lé2dö
lé2d1ő2
lée2r
lé1e
léé2r
lé1é
lé1f2r
lé2gal
lé1ga
lé2g1e2c
lé1ge
2léged
lé2g1eg
lég1ell
lég1els
lé2g1em
lé2g1e2r
lég1é2r.
lé1gé
2l1é2gés
l1éghet
lég1he
lé2gil
lé1gi
2lé1gí
lé2g1ö
2lé2gő
lé1g2ráf
lég1rá
l1égtem
lég1te
l1égtet
l1égtél
lég1té
lé2gú
2lé1gü
l1é2gün
lé2gyel
lég2y
lé1gye
lé2gyes
lé2gyi2
lé2gyo2
lé2gyö
3légz
léh1as2z
lé1ha
2l1é2h2e.
lé1he
lé2hed
lé2h1em
2léhes
2léhet
2l1é2he1ző
lé2h1é
lé2hol
lé1ho
2léhs
2l1é2j.
2l1é2ji
2l1éjj
2l1éjs
lé2kab
lé1ka
lé2k1a2d
lé2k1a2g
lé2k1aj
lé1k1a1ka
lé2k1a2la
léka2p
lé2k1a1po
lé2k1as
léka2t
lék1a1to
lé2k1a1u
lé2k1av
lé2kaz
lé2k1e2g
lé1ke
lé2k1e1p2
lé2ke1sí
lé2k1e2s2z
lék1e1vé
lé2k1é2k
lé1ké
léké2l
lé2k1é1le
lé2k1élv
lé2k1é2te
lé2ki2d
lé1ki
lé2kít
lé1kí
lékköz1
lék1kö
lék1or1s2
lé1ko
lé2k1os2z
lé2k1ö2l
lé1kö
lé2kör
lé2köz
lé2kő
lé2k1u2r
lé1ku
lékü2l
lé1kü
1lé2k1ü1lé
lé2k1ült
lékve2g
lék1ve
l1élc
2l1é2l2e.
lé1le
2l1é2le1i
2l1é2les
lé2léb
1lé1lé
lé2lén
lé2lér
2l1élm
lélőkés2z1
lé1lő
lélő1ké
2l1élr
lé2lük
lé1lü
2l1é2lű
2l1é2mel
lé1me
lé2nag
lé1na
lé1ná2
lé2neg
lé1ne
lé2nekb
2l1é2nekl
lé2nel
2lén1kí
lé2no
lé2nyö2
lén2y
lé2p1a2l
lé1pa
l2é2p1el
lé1pe
lépés3s
lé1pé
2l1é2pí
lé2pó
2lépül
lé1pü
1lé2pü1lé
lé3r1a2d
lé1ra
lé2ral
lé2rap
lé2ras
lé2rat
lé2rav
lé2r1á
2l1ér2c.
2l1ércb
2l1ér1de
lé2reg
lé1re
lé2r1e2l
lé2r1e2ső
2l1é2re2z.
2l1é2rezv
lé2r1é2j
lé1ré
2l1é2rés
2l1é2rik
lé1ri
lé2ris
lé2rit
lé2rí
2l1ér1ni
2l1ér1nü
lé2r1os2z
lé1ro
lé2rö
2l1é2r2ő.
lé1rő
lé2rőd
2l1é2rő1e
lé2rő1i
2l1é2rőj
2l1é2rők
lé2rőn
2l1é2rőt
2l1é2rőv
2l1értelm
lér1te
2l1érték
lér1té
2l1értj
2l1ért2ő.
lér1tő
lé2rut
lé1ru
2l1érvel
lér1ve
2l1érvén
lér1vé
2l1ér1ze
2l1ér1zé
lé1sa2
lé2s1aj
lé2sak
lé2sal
lé2sar
lé2s1az
lé2s1á2
lé2seg
lé1se
lé2s1e2l
lé2s1e1ti
lé2s1é2g
lé1sé
lé2sés
lé2s1ikr
lé1si
lé2só
lé2s1ő
lés3s1za
lés2s2z2
lés3szer
lés1s1ze
lésü2l
lé1sü
1lé2s1ü1lé
lé2s1ü1lő
lé2s1üt
lész1ék
lés2z
lé1s1zé
2lészést
2l1észl
lés3z1se
lész2s
lé2tag
lé1ta
lé2taz
lé2t1eg
lé1te
lé2te2l.
lé2telek
léte1le
léte2le1ko
lé2t1e2lem
lé2t1e2let
lé2telh
lét1el1ha
lé2telm
lét1e2lo
lé2t1e1lő
lé2t1elv
lé2te1ne
lé2te1sé
2l1é2teth
lé2te1ti
2l1é2tetn
lé2tev
lé2t1é2r.
lé1té
lé2t1é2te
lé2t1is
lé1ti
lé2tít
lé1tí
2létl
lé2t1o2k
lé1to
3l2é1tó
lé2tóh
lé3tól
lé2t1ó2r
lé1tö2
lét1ö1rö
lét2rág
lét1rá
lé2tu2n
lé1tu
2l1é2vad
lé1va
2l1évb
2l1é2v2e.
lé1ve
2l1é2ved
2l1é2ve1i
2l1é2vek
2l1é2v1elf
2l1é2v1e1li
2l1é2vem
2l1é2ven
2l1é2ve2t.
2l1é2véb
lé1vé
2l1é2véh
2l1é2vé1i
2lévén
lév1ért
2l1é2vét
2l1é2vév
2l1évf
2l1évh
2l1é2v2i.
lé1vi
2l1é2vi2g
lé2vir
lé2vis
2l1évk
2l1évl
2l1évm
2l1évn
2l1é2vó
2l1évr
2l1évs
2l1évt
2lé1vü
lé2vük
lé2vün
2l1é2vű
2l1évv
2l1évz
lfa2l1e2
l1fa
lfat2
lfa1tr
lf1aut
lfa1u
lfá2t1i2
l1fá
lf1cl
lf1e1se
l1fe
lfe2t
lf1e1ti
lfé2l1é2v
l1fé
lfé1lé
l2f1élm
lfé2m1e2ké
lfé1me
lfi2d1é
l1fi
lfin3n
lfi2nos
lfi1no
lf1i1ro
lf1isk
lf1kl
lf2lo
lf2ló
lf2lö
lf2lu
l2f1ok1ta
l1fo
l2f1ó2r
l1fó
lf1pr
lf2ri
l1f2rí
l1f2ro
lf2rö
lf2rő
lf1sp
lf1s2z2
l2f1ü1lő
l1fü
lga1p2
l1ga
lgatói2ko
lga1tó
lgató1i
lga1u2
lgá2r1as
l1gá
lgá1ra
lgés3s
l1gé
lgi2as
l1gi
lgi1a
lg2la
lg2lo
lg2ló
lgör2c1so
l1gö
l3g2örc
lgörc2s
l1g2ra
lg2ru
l2gy1ad
lg2y
l1gya
l2gy1ag
l2gyaj
lgy1a1la
lgy1alj
lgy1an2y
l2gy1a2s
l2gy1a1u
l2gyáb
l1gyá
l2gyál
l2gyát
l2gy1e2g
l1gye
l2gye1lá
l2gy1e2le
l2gy1elz
l2gyem1be
lgy1e1ré
l2gy1esem
lgye1se
l2gy1e2v
l2gyél
l1gyé
l2gy1é2r.
l2gy1és
l2gyid
l1gyi
l2gyikr
l2gyip
l2gyis
l2gy1ok
l1gyo
l2gy1ol
l2gyop
l2gy1os
l2gy1ó2r
l1gyó
l2gy1ö2r
l1gyö
l2gy1ös
l2gy1u2t
l1gyu
l2gy1út
l1gyú
lha1i2
l1ha
lhó2n1al
l1hó
lhó1na
1li
li2a1a
li1a
li2a1á
li2a1bi
li2a1bo
lia2cé
li2a1ci
li2ac2s
li2a1cu
li2a1d2
li2a1e
li2a1é
li2ag
li2ah
li2aid
lia1i
li2a1í
li2aj
li2akép
lia1ké
li2a1la
li2a1le
1li2a1li
li2a1mé
li2a1mo
li2a1o
li2a1ó
li2a1ő
li2ap
li2a1ra
li2a1ré
li2a1sá
li2a1so
lia1s2z
li2a1s1za
li2a1s1zá
li2a1s1zé
li2aszf
li2a1s1zi
li2a1t2
li2a1ü
li2av
li2az
li3be
lic3s2z
lic2s
li2cü
li1cy
li2de1á
li1de
2li2deg
li2dén
li1dé
li2dét
2l1i2di
2l1i2dő
li2ec
li1e
lie2d
2lien
lié2d
li1é
lié2k
lié2n
lié2vé
lifé2l1é2v
li1fé
lifé1lé
li2fí
li1fl
li1f2r
l2i2g.
2l1i2gaz
li1ga
ligán1n
li1gá
li2g2e.
li1ge
2l1i2geb
2l1i2gek2
2l1i2gep
li2géb
li1gé
li2géh
li2gé1i
li2géj
li2gék
li2gén
li2gér
li2gés
li2gét
li2gév
li2géz
li2hat
li1ha
2l1ihl
2l1i2ke2r.
li1ke
2l1i2kerb
lik2k1ell
lik1ke
lik2k1elv
lik2k1e2r
lik2kérd
lik1ké
li1k2l
2l1i2konc
li1ko
2l1i2konf
2l1i2kong
2l1i2koni2g
liko1ni
2l1i2konk
2l1i2konl
2l1i2konm
2l1i2konp
2l1i2kons
li1k1ré
2lill
2lim2a.
li1ma
2l1i2mád
li1má
li2mák
li2máv
l1im1bo
li2m2e.
li1me
lime2rá
lim1p2
2lim1po
li1mű1
li2nakr
li1na
li2nal
lin1an2y
lin1a1rá
2l1i2na2s.
lin2c1s1ö2
linc2s
2l1in1du
l2i2n2e.
li1ne
li2neg
linék2
li1né
2l1inger
lin1ge
lin1g1rá
ling2rá1di
2l1ing1ré
l2in1ing
li1ni
lin1inj
lin1kl
lin3n2y
li2nor
li1no
2l1integ
lin1te
2l1intéz
lin1té
li2n1u2s
li1nu
li2n1ut
2l1i2nú
li2oc
li1o
lio2l
li2o1ni
li2ó1ke
li1ó
lió2raj
lió1ra
li2ő1á
li1ő
li2őd
li2ő1e
li2őf
li2őg
li2őm
li2őp
li2p2a.
li1pa
li2pa1i
li2p1aj
li2p1á2r
li1pá
li2pát
li2peg
li1pe
lip1e1le
li2p1elv
li2p1es2z
li2pö
li2p1ő2
li1p1ro
2l1i2ram
li1ra
2l1i2rat
2l1i2rán
li1rá
2l1irh
li2rig
li1ri
li2rod
li1ro
2l1irr
2l1irt
li2sál
li1sá
li2s1el
li1se
lise2s
li2sid
li1si
lis1isk
2l1iskol
lis1ko
l1ism
lis1p2
2l1is1pá
2l1isten
lis1te
2l1istr
li2s1ü2t
li1sü
lis1ü2v
2l1iszl
lis2z
lisz2t1á2z
lisz1tá
li1sztir
lisz1ti
li2t1a2g
li1ta
2l1i2ta2l.
li2ta1la
li2t1alk
li2t1á1ta
li1tá
li2t1e2g
li1te
li2t1e2l
li2t1emb
li2tez
li2t1érd
li1té
li2tér1te
li2til
li1ti
lit1int
li2t1i2o
li2t1is
li2t1okt
li1to
li2t1old
li2tön
li1tö
li1tő2
li2t1őr
l1ittak
lit1ta
l1ittas
l1ittat
li2t1u1ra
li1tu
liú2t
li1ú
2l1i2vad
li1va
l1i2vot
li1vo
l1i2vó
3li2x.
l1i2zél
li1zé
2l1i2zén
2l1i2zé1sí
l1iz1gu
2l1iz1mo
2l1iz1zí
2l1iz1zó
1lí
lí2gé
lí2ja
lí2ju
lí1ma1
2l1íns
l1í2n2y
lí2nyenc1
lí1nye
l1írd
l1írh
l1írj
l1írl
lí2rod
lí1ro
l1í2rog
lí2rok
lí2rom
2l1í2ró
l1írs
l1í2ru
lítés3s
lí1té
lítő1a2
lí1tő
lítő2p3r
2lí2v.
lí1va1
lívak2
2l1í2z.
2l1í2ze
l2í1zi
lízis3s2
2l1ízl
2l1í2zü
l1í2zű
l2j1a2da
l1ja
l2j1e2lő
l1je
l2j1er
ljes1s
ljharc1
lj1ha
l2j1ip
l1ji
l2j1ir
l2j1iz
lj1ír
l1jí
l2j1or
l1jo
l2j1os
lj1pr
lj1sp
lj1üz
l1jü
lka1sl
l1ka
lka2tel
lka1te
lka2t1é
lk2a2tin
lka1ti
lka2t1ó2
lké2p1ell
l1ké
lk2épel
lké1pe
lké2sze2l
lkés2z
lké1s1ze
lkia2k
l1ki
lki1a
lki1á2
l1k2lin
lk1li
lk2lí
l1k2lu
lkö2z1ö2n.
l1kö
lkö1zö
l1k2rá
lk2re1á
lk1re
l1k2rémb
lk1ré
l1k2ri
l1k2rí
lk2rom
lk1ro
l1k2ró
lk1sh
lkukés2z1
l1ku
lku1ké
lk2va
lk2vá
lk2vó
lla2dój
l1la
lla1dó
lla1f2
lla2g1ad
lla1ga
ll2a2gal
lla2g1a2s
lla2gál
lla1gá
lla2gen
lla1ge
lla2gép
lla1gé
llag3g
lla2gol
lla1go
ll1a2ja
ll1a2kad
lla1ka
l2l1akc
l2l1a2kóz
lla1kó
l2l1ak1ti
llakt2
l2l1a2lap
lla1la
l2l1alm
ll2a2mad
lla1ma
lla2mal
lla2mem
lla1me
lla2mél
lla1mé
lla2mor
lla1mo
l2l1a2nal
lla1na
lla2nyer
ll2a1nye
llan2y
lla2ny1ó2
ll2a2pal
lla1pa
ll1aszf
llas2z
llata2l
lla1ta
l1la2ta1la
llat1an2y
ll2atan
ll2a2t1ál
lla1tá
lla2t1á2r.
ll2atár
lla2t1ár1a2d
llatá1ra
lla2t1árb
lla2tés
lla1té
lla2tint
ll2atin
lla1ti
lla2t1olt
lla1to
l2lato1mo
lla2tors2
ll2ator
lla2t1ű
l2l1at2y
l2l1aut
lla1u
l2l1á2ga
l1lá
l2l1ágb
l2l1ágg
ll1ág2y.
llág2y
l2l1á1gyá
l2l1ál1lo
l2l1álm
llá2mag
llá1ma
llá2m1al
llá2m1e2
llá2m1ér1té
llá1mé
llá2mik
llá1mi
llá2mis
llá2m1ut
llá1mu
llán2k1e2
l2l1á2p
llá1ra2
llá2r1ad
llá2rak
llá2r1á2
llá2s1ikr
llá1si
llá2sü
llá2s1za
llás2z
l2l1átd
l2l1átf
l2l1á2ti
l2l1átk
ll1bl
ll1br
ll1cl
ll1d2r
l2l1e2d2z
l1le
ll1eff
lle2ger
lle1ge
lleg1g2
lle2gyé
lleg2y
lle1í2
l2l1e2kés
lle1ké
lle2l1a
lle2lin
lle1li
l2l1ell
l2l1e2lő1a
lle1lő
l2l1e2lőd
lle2m1a2
lle2m1á
lle2m1e2g
lle1me
lle2mel
l2l1e2me1lé
llem1ell
lle2mer
lle2m1él
lle1mé
lle2m1é2r.
lle2m1ér1té
lle2m1o2
lle2mu
lle2na
llen3n
lle2n1ő2r
lle1nő
llens2
lle2r1in
lle1ri
l2l1e2ró
l2l1e2sőt
lle1ső
l2l1estr
lle2tos
lle1to
l2l1e2vet
lle1ve
l2l1ex
l2l1é2het
l1lé
llé1he
l2l1é2jek
llé1je
llé2k1aps
lléka2p
llé1ka
llé1ká2
llé2kár
llé2k1ol
llé1ko
llé2kos
llé2kó
llé2k1ú2t
llé1kú
l2l1é2le2t.
llé1le
l2l1é2letb
l2l1é2le1te
l2l1é2letén
lléle1té
l2l1é2letét
l2l1é2letk
l2l1élt
l2l1élv
llé3ny1a2
llén2y
llé1sp
l2l1é2ter
llé1te
llé1t2o
l2l1é2v.
l2l1é2vén
llé1vé
ll1f2l
ll1fr
ll1g2r
ll1i1de
l1li
l2l1i2ga
l2l1i1gé
l2l1ill
l2l1i2ma
l2l1im1p2
ll1in2a.
lli1na
l2l1ind
ll1in1ga
l2l1ingf
l2l1ingm
l2l1ings
lli2nin
lli1ni
l2l1inv
lli2p
l2l1i1pa2
l2l1i1rá
l2l1i1ro
l2l1i1si
ll1is1ko
l2l1ism
l2l1is1te
lli2ta
l2l1it2a.
l2lital
l2l1i1zé
ll1iz1ma
ll1iz1má
l2l1i2zo
l2l1íg
l1lí
l2l1íj
l2l1í2v
ll1k2l
ll1k2r
ll1kv
l2l1o1á
l1lo
ll1obj
l2l1off
l2l1o2l
ll1o2pe
llos3s
lló1á2
l1ló
lló1gr
lló1ó2
lló2rák
lló1rá
l2ló2rát
lló2ri1á
lló1ri
l2ló1vo
l2l1öb
l1lö
l2l1öl
ll1önt
l2l1ör
ll1öss
ll1ös2z
l2l1ö2z
l2l1ő2r.
l1lő
ll1ő2re1i
llő1re
l2l1ő2rö
l2l1őrt
l2l1ő2si
l2l1ő2z2e.
llő1ze
ll1p2l
ll1p2r
ll2s2i.
ll1si
ll1sk
ll1sp
lls3s
lls2z2
ll1szt2
ll1t1ré
ll1t1ri
ll1t1ró
ll1u2bo
l1lu
l2l1ug
ll1ult
llus1s
l2l1u2t
l2l1ús
l1lú
l2l1ú2to
l2l1üd
l1lü
l2l1üg
l2l1ür
l2l1üt
l2l1üz
l2l1űz
l1lű
llvé2d1e2l
ll1vé
llvé1de
l2l2y
l1ly1ö
l3lyw
lma2kad
l1ma
lma1ka
lm2a1k1ré
l2m1ant
lma1t1rá
l2m1att
lmá2l
l1má
lm1álm
lm1á1lo
lmá2nya2n
lmán2y
lmá1nya
lmá2ny1út
lmá1nyú
lmá2ris
lmá1ri
lm1átk
lm1átt
lm1bl
lm1b2r
lm1cl
lm1dr
lme3gon
l1me
lme2g1o
lme1kl
l2m1elb
lm1e2li
lm1elm
lm1e2lő
l2m1ember
lmem1be
l2m1enc
l2m1eng
lme1ó2
lm1e1pi
l2m1e2se1té
lme1se
l2m1es2s2z
l2m1etn
l2m1e1tű
l2m1ex
l2m1ég
l1mé
l2m1élv
l2m1é2ne1ke
lmé1ne
l2m1ép
l2m1ér1tő
l2m1é1va
lm1gl
lm1gr
l2m1i2dő
l1mi
lmi2g
l2m1i1ga
l2m1i2gaz1
l2m1ind
l2m1inf
l2m1ing
l2m1ins
l2m1in1te
l2m1in1té
l2m1inv
l2m1i2p
lm1i1rá
lm1i1ro
lm1isk
lm1izz
lm1íg
l1mí
lm1ír
lm1íz
lm1kl
lm1kr
lm1old
l1mo
lm1olv
l2m1o2p
lm1or1s2
l2m1ov
l2m1ó1dá
l1mó
lm1ó2rá
lm1ö2k
l1mö
lm1ön
lm1ö2r
lm1ös
lm1öt
lm1ö2z
lm1ő2r
l1mő
lm1p2l
lm1p2r
lm1sk
lm1sl
lm1sn
lm1sp
lm1st
lm1s2z2
lm1t2r
l2m1u2g
l1mu
l2m1u2r
lm1u1tó
l2m1új
l1mú
l2m1üg
l1mü
l2m1ünn
l2m1üz
lnak2
l1na
lna2p1e
lná2ris
l1ná
lná1ri
lné2v1á
l1né
lni2ker
l1ni
lni1ke
lni2s1
1lo
lo2áz
lo1á
lo2b1a2r
lo1ba
2lo1bá
lo2b1á2c
2lo2be
lo2b1iv
lo1bi
lo2b1ó2
2lo1bu
lo1by
lo2c2h
lo1cy
lo2éc
lo1é
lo2é1o
lo2ép
lo2éz
lo2g1a2d
lo1ga
lo2gal
lo2g1ál
lo1gá
logás1s
lo2ge2r
lo1ge
lo2gí
lo2g1or
lo1go
lo2gö
l2o1g2rá2f.
log1rá
l2o1g2ráff
lo2g1ú
lo2gü
lo2ir
lo1i
lo2kab
lo1ka
lo2k1a2d
lo2k1aj
2l1o2kak
lo2kárb
lo1ká
lo2k1á2ro
lo2k1árr
lo2k1á1ru
lo2k1átj
lo2ká1u
lo2k1e2
lo2kék
lo1ké
lo2kid
lo1ki
lok1is
lo2k1i2z
lo2kí
lokka2l
lok1ka
lok2k1a1la
lok2k1el
lok1ke
lok2k1ó2
lok2kös
lok1kö
lok2k1ut
lok1ku
lo2k1ol
lo1ko
lo2konk
lo2kor
2l1o2koz
lo2kü
lo2laj
lo1la
2l1old
2l1o2li
2l1ol1ló
2l1oltár
lol1tá
lom1a1dá
lo1ma
lom1a1ga2
lo2m1ajt
lom1a1ka
lom1a2lap
loma1la
1lom1a2lo
lom1a1rá
lom1ass
lo2m1att
lo2maz
lom1á2c2s
lo1má
lo2má2g
lo2m1ál
lo2m1á1ré
lom1á1ri
lom1árk
lo2m1árn
lo2m1á2ro
lomba2l
lom1ba
lom2ba1la
lom2bav
lo2med
lo1me
lo2me2g
lo2men
lom1erk
lom1e2rő
lo2m1e2s
lo2méd
lo1mé
lo2mék
lo2mél
lom1é2ne
lo2m1é1ri
lo2m1i2d
lo1mi
lo2m1i2k
lo2m1im
lo2mink
lom1int
lomi2s
lom1is2z
lo2miz
lo2m1í2
l1om1ló
lo2m1okoz
lo1mo
lomo1ko
lo2mol
lo2m1o2r
lo2m1os2z
lom1ott
lo2m1ó2
lo2mö
lo2mő
lom1p2l
lo2mü
lo2mű
lo2nab
lo1na
lo2n1a2d
lo2n1a2g
lo2naj
l2o2nak
lo2n1a2l
lo2n1ar
lo2n1as
lo2n1a1u
lo2n1av
lo2n1á2z
lo1ná
lon2c2h
lon2cil
lon1ci
lon2d1ó2
lo2n1e2l
lo1ne
lo2n1ir
lo1ni
lon1k2
lo2nol
lo1no
lo2n1ó2
lo2nö
lon1s2
lon1tr
lo2nü
lo2o1i
lo1o
lo2oj
lo2or
lo2ós
lo1ó
lo2pap
lo1pa
lo2p1ál
lo1pá
lo2pe
lop1e2l
lo2pi2z
lo1pi
lo2pí
lo2p1o2r
lo1po
lo2p1os2z
lo2pö
lop2p1in
lop1pi
lop1t2
2l1op1ti
lo2pü
lor2din
lor1di
lore2t
lo1re
2l1org
2l1orm
2lorn
2l1ors
2l1orv
2l1orz
l2o2s.
l2o1sa
l2osb
lo2se
lo2sü
lo1t2ha
2l1ottl
lo2xá
lo2xi
1ló
lóa2d
ló1a
lóá2r
ló1á
ló1bl
ló1br
lóc3c
ló2cem
ló1ce
ló2c2h
lócsa2p1á2g
l2ó1c1sa
lóc2s
lócsa1pá
2l1ó2dát
ló1dá
ló1d1ro
lófé2l1é2v
ló1fé
lófé1lé
lófi2ú1ké
ló1fi
lófi1ú
ló1fl
lóg1g
ló2gí
ló1g1rá
lóí2v
ló1í
ló1k2l
ló1k1ré
ló2nar
ló1na
2l1ó2n2i.
ló1ni
2lónn
2ló1no
2lónr
2lónt
lópár1ba2
ló1pá
ló1p2l
ló1p2r
ló2rac
ló1ra
l2ór1a1da
ló2r1a2la
lóra2n
lór1an2y
ló2rar
2lórá1i
ló1rá
2lóráj
l1ó2rámr
2lórár
2lórát
ló2rem
ló1re
ló2r1e2s
ló2r1ér
ló1ré
ló2r1ing
ló1ri
lór1ism
ló2rí
lór1old
ló1ro
ló2rü
ló2s1aj
ló1sa
lósa2n
lós1an2y
ló1s1ki
ló1s1la
ló1s1pe
ló1s2po
ló1s2rá
lós3s
ló1s2ta
l2ó1st2r
ló1s2z
ló1t2rá
ló1t1re
ló1t1ré
ló1t1ri
ló1t1ro
2l1ó2vod
ló1vo
l1ó2vó
ló2za1d2
ló1za
ló2z1a2k
ló2zal
lóza2n
lóz1an2y
lóza2t1e
ló2zál
ló1zá
ló2z1es
ló1ze
ló2zim
ló1zi
ló2z1ir
lóz1isk
lóz1ism
ló2zolv
ló1zo
ló2ző
ló2zü
ló2z1ű
1lö
lö2bö
lö2ca
löc3c
lö2c2h
lö2cő
2l1ö2dé
lö2ka
lö2ká
lö2ko
l1öl1dö
löl2t1a
löl2tá
2l1öltés
löl1té
l1ölt1he
löl2to
l1öltöt
löl1tö
l1öltöz
l1öl1tő
2l1öml
lö2möl
lö1mö
lö2na
lö2ná
lön2b1ékét
lön1bé
lönbé1ké
lö2ne
lö2né
lö2n1o
lö2nó
lö2nu
lö2nú
lö2pa
lö2pá
lö2pe
lö2pöl
lö1pö
lö1pü2
lö2p1ü1lé
lö2p1ü1lő
lö2re
lö2rö
lös3s1zá
lös2s2z
2lös1s1ze
lö2sü
lö2s1ze
lös2z
lö2szi2s
lö1s1zi
2l1ötl
löt2ter
löt1te
2l1ötv
2l1ö2v.
2l1övb
l1ö2v2e.
lö1ve
l1ö2vez
2l1övh
2l1övn
l1ö2vön
lö1vö
2l1övr
l1ö2vük
lö1vü
2l1övv
1lő
lőa2c
lő1a
lőa2n
lőá2g
lő1á
lő1bl
lő1br
lő2dad
lő1da
lő2dá
2lőd2e.
lő1de
2lőde1i
lő2del
lő2d1ék
lő1dé
lő2din
lő1di
lő2d1iv
2lődj2e.
lőd1je
2lődjét
lőd1jé
lődköz1
lőd1kö
2lődö1ke
lő1dö
2lődökn
2lődökr
2lődö2t.
lő2d3ze
lőd2z
lőe2l
lő1e
lőe2s
2lőe2sé
lő1fl
lő1f2r
lőgé1pi2
lő1gé
lőgé2p1ip
l1ő2gyel
lőg2y
lő1gye
2lő1hű
lői2ta
lő1i
2lőít
lő1í
2lőké1se
lő1ké
lő1kl
lőko2r1út
lő1ko
lőko1rú
lő1kv
2lőle1ge
lő1le
2lőnn
2lőn2y.
lőn2y
lő2nyal
lő1nya
lő2nyár
lő1nyá
2lőnyb
2lőny2e.
lő1nye
2lőnye1i
lő2ny1el1vi
2lőnyéb
lő1nyé
2lőnyén
2lőnyér
2lőnyét
2lőnyév
2lőnyh
2lőnyk
2lőnyn
2lő1nyö
2lőnyr
2lőnyt
2lő1nyü
lőőr2s1é2g
lő1ő
lőőr1sé
2lőpán
lő1pá
lő1pl
lő1pr
lő1ps
2lőreh
lő1re
2lőrej
lőre3m
2lőret
2lőréb
lő1ré
lő2r1is
lő1ri
lő2rül
lő1rü
2l1őrz
lő2sá2l
lő1sá
lő2sin
lő1si
lő1s2ka
lő1s1ká
lő1s1la
lő1só2
lő2sór
lő2s1ót
lő2ső
lő1s2pi
lő1sp2r
lő1s1rá
lős3s
lős2tar
lős1ta
lő1st2r
lő1sy
lős2z2
lős3zá1ra
lő1s1zá
lős3zárr
lő1szf
l1ő2szít
lő1s1zí
lő1szt2
2lőté2t.
lő1té
2lőto1lá
lő1to
lő1t1re
lőu2t
lő1u
lőü2l
lő1ü
2lővig
lő1vi
2l1ő2zét
lő1zé
2l1ő2zi1é
lő1zi
2lőzl
2lőzm
2l1ő2zük
lő1zü
l2p1a2lag
l1pa
lpa1la
l2p1a2lap
l2p1alát
lpa1lá
l2p1a2láv
l2p1alk
lpan1to1
l2p1an2y
l2p1áll
l1pá
l2p1átm
l2p1áts
lpcsa2p1
lpc2s
lp1c1sa
l2p1eg
l1pe
l2p1e2lu
l2p1e2m
lpen1n
l2p1e2rő
l2p1ég
l1pé
lpé2l
l2p1é1le
lpé2r
l2p1é1ri
l2p1i2d
l1pi
l2p1i2na
lp1izm
lp1i2zo
l2p1ív
l1pí
l1p2lá
l2p1old
l1po
lpo2n
lpo1n1á
lp1p2r
l1prd
l1p2ri
l1p2ro
l1p2rób
lp1ró
lpu2s
l1pu
lp1u1s1zo
lpus2z
lrá2k1e
l1rá
lre1i2
l1re
lre1p2ré
lre1s2z
lre1ü2
lsa2v1a2m
l1sa
lsa1va
lság3g
l1sá
ls1eprik
l1se
l2sep1ri
ls2ho
ls2ka
ls2ká
ls2ki
ls2la
ls2lá
ls2li
ls2ma
ls2mi
lsors1s
l1so
lsóé2r
l1só
lsó1é
lső1s2z2
l1ső
l1s2pa
l1s2pe
l1s2pé
l1s2pi
l1s2po
l1s2pó
l1sp2r
l1s2rá
l1s2ró
l1s2ta
ls2tá
lste2i
ls1te
l1s2ti
l1s2tí
l1s2to
l1st2r
l1s2tu
l1s2tú
lsza2ké
ls2z
l1s1za
lsza2k1ü2
lszá2rú
l1s1zá
l2sz1e2gű
l1s1ze
l1sz2f
l1sz2l
l1sz2p
lszt2
lsz2tá
l1sztr
l1sz2v
lta2gyá
l1ta
ltag2y
lt1a1já
lta2lapb
lta1la
lta2l1á2s
lta1lá
lta2l1á2z
lt2a2len
lta1le
lt2a2l1ev
lta2l1é2
lta2liz
lta1li
ltal1l
lta2lö
l2t1amp
l2t1apr
lt1a1rá
l2t1arc
lta2ri1á
lta1ri
l2t1as2s2z
lt1aszt
ltas2z
l2t1a2u
lt1a1zo
l2t1á2g.
l1tá
lt1á2ga
l2t1ágb
l2t1ágg
l2t1ágn
l2t1ágr
l2t1áll
ltá1na2
ltá2nan
ltá2ra1da
ltá1ra
ltár2s1ág
ltár1sá
lt1á2rur
ltá1ru
ltá2s1á2g
ltá1sá
ltá2tal
ltá1ta
l2t1átr
lt1bl
lt1br
l2t1ell
l1te
l2t1e1lö
l2t1e1mu
lte2rad
lte1ra
l2t1e2reik
lte1re
ltere1i
l2t1e2rő
l2te1ru
lte2t1a2
lte2ték
lte1té
l2t1ék1né
l1té
lté2l
lt1é1le
lt1élm
lt1érc
l2t1érz
lté2sa2
lté2s1é2g
lté1sé
ltés3s
l1té2t1é2
l2té2ve2s.
lté1ve
lt1fl
lt1gl
lt1gr
lt1i1de
l1ti
ltig2
l2t1i2gé
lti1kl
l2t1ill
lt1imp
l2t1ind
l2t1ing
l2t1i2o
l2t1i1si
lt1ism
l2t1ist
l2t1i2ta
l2t1ín
l1tí
l2t1í2r
lt1ít
l2t1í2v
ltí2v1e2l
ltí1ve
l2t1í2z
lt1kr
l2t1oml
l1to
l2t1ord2
l2t1or1g2
l2t1orj
l2t1orr
lt1ors2
ltos3s
l2t1o2x
ltó1p
l1tó
ltó1s2p
ltó1s2z
ltót2
ltö2l
l1tö
l2t1ö1lé
lt1önt
lt1öss
ltő1a2
l1tő
ltőe2l
ltő1e
lt1ő2rö
lt1pl
lt1pr
ltra1s
lt1ra
lt2rág
lt1rá
lt2rén
lt1ré
lt2rik
lt1ri
lt2ril
lt2róf
lt1ró
l1t2rón
lt2róp
ltsé2g1el
lt1sé
ltsé1ge
lt1sl
lt1sp
lt1st
lt1t2r
l2t1udv
l1tu
l2t1u1na
ltu2n1i
lt1u1ra
ltu2s1ze
ltus2z
l2t1u2t
ltú2ri
l1tú
l2t1üg
l1tü
lt1ü2lé
l2t1üst
l2t1ü2v
lt1ü2zem
ltü1ze
1lu
lua2g
lu1a
luá2r
lu1á
lu2b1a2d
lu1ba
lu2bal
luba2n
lu2b1as
lu2bár
lu1bá
lu2b1e2g
lu1be
lube2r
lu2bes
lu2bél
lu1bé
lu2b1in
lu1bi
lu1bó2
lu2bór
lu2bö
lub1t2
lu2bü
2ludj
2ludv
lu1dy
lue2l
lu1e
lu2e1sé
lugas1s
lu1ga
lu2gat
lu1g2l
2l1u2gor
lu1go
2l1ugr
lui2r
lu1i
2l1ujj
lu1kl
lu2k1os
lu1ko
lu2k1o2v
lu2l1inf
lu1li
lu2lí
2lulr
lu2mad
lu1ma
lu2maz
lu2mál
lu1má
lumen1n
lu1me
lu2mer
lu2mes
lu2m1i2k
lu1mi
lu2m1ip
2lu1mí
lum2pe2l
lum1pe
2lund
l1unh
2l1u2ni
l1unj
l1unl
l1unn
l1u2no
l1u2nó
l1unv
lu2rak
lu1ra
lu2ram
lu2rat
2l1u2ru
2l1u2rú
lu2sad
lu1sa
lu2s1a1ka
lu2sakr
lu2sal
lu2s1a2n
lu2s1ág
lu1sá
lu2sál
lu2s1e2r
lu1se
lu2s1ér1té
lu1sé
lu2sim
lu1si
lu2sis
lu2sí2r
lu1sí
luskés2z1
lus1ké
lu2s1ó2
lu1sö2
lu2sör
lu2ső
lus3s1ze
lus2s2z
lus3s1zi
lust2
2l1u2tánz
lu1tá
2l1utc
lu1t2h
lu2tód
lu1tó
lu1t1ra
lu2z2s
1lú
lú2d1a
lú2dá
lú2de
lú2dé2t
lú1dé
lú2d3z
lú2ga
lú2g1á
lú2ge
lúg3g
lú2gi
lú2gol
lú1go
2l1újd
2l1ú2jí
lú2ju
lú2ri
lú2ru
lú2rü
2l1ú2s2z
lú2t2é.
lú1té
2l1útv
lú2z1a2n
lú1za
1lü
lü2c2s
lü2dí
lü2dü
lü2ge
lü2g2y
lü2lá
l1ül1dö
lü2lel
lü1le
lü2l1e2m
l1ülep
lü2lé
lül1ér
2l1ülés
2l1ülhe2t.
lül1he
2l1ülhet1ne
2l1ülhets
lü2lí
2l1üljek
lül1je
2l1ülnek
lül1ne
2l1ülnék
lül1né
2l1ül1ni
2l1ü2lö
2l1ü2lő
2l1üls2z.
lüls2z
2l1ül2t.
l1ült2e.
lül1te
2l1ültek
2l1ültem
2l1ültes
2l1ülte2t.
2l1ül1te1te
2l1ülte1ti
2l1ültetj
2l1ültetl
2l1ültetn
2l1ültets
2l1ültett
2l1ülte1tü
2l1ültetv
2lül1té
l1ülté1i
l1ülték
l1ülté2l
l1ültén
l1ültér
l1ültét
l1ültn
2l1ül1tü
lü2lú
1lü2lü
2l1ülün
2l1ülv2e.
lül1ve
2l1ünn
lü2re
lü2rí
lü2rü
lü2s2z
lü2te
lü2té
lü2ti
lü2tö
lü2tő
lü2tü
lü2ve
lü2vö
lü2ze
lü2zé
1lű
lű2rá
2l1űrb
2l1ű2ri
l1űrl
lű2ze
lű2zé
lű2zi
lű2zö
lű2ző
lű2zü
lva2dat
l1va
lva1da
l2v1adm
lvaj1ak
lva1ja
lva2j1e
l2v1a2kad
lva1ka
l2v1akc
l2v1a2la
l2v1alg
l2v1alk
l2v1ant
l2v1a2n2y
l2v1a2rás
lva1rá
l2v1a2z
lvá2gy1ón
l1vá
lvág2y
lvá1gyó
l2v1állat
lvál1la
l2v1állt
l2v1áp
lvá2rain
lvá1ra
lvára1i
l2v1á2ra2k.
lvá2ras
lvá2rár
lvá1rá
l2v1árn2y
lvá2rol
lvá1ro
l2v1á2rul
lvá1ru
lvás3s
lvá2s1zi
lvás2z
lv1á1ta2
lv1áth
lv1átk
lv1br
l2v1e2d2z
l1ve
lv1egys
lveg2y
l2v1egyv
l2v1e2kéb
lve1ké
l2ve2le1me
lve1le
l2v1elk
l2v1ell
l2v1e2ró
l2v1ex
l2v1é2gés
l1vé
lvé1gé
lvé2gül
lvé1gü
l2v1élm
l2v1é2ne1ke
lvé1ne
l2v1ép
lvé2r2i.
lvé1ri
l2vértel
lvér1te
l2v1ér1té
l2v1é2rü
l2vérzé1si
lvér1zé
lvé2s1za
lvés2z
lv1fr
l2v1i2de
l1vi
l2v1i2do
l2v1i1ga
lv1i1ko
l2v1i2m
l2v1in1té
l2v1i2pa
l2v1i1ro
l2v1irt
l2v1is1ko
l2v1ism
l2v1izm
l2v1i1zo
l2v1í2ve
l1ví
lví1ze2
lvíz1es
lv1kl
lv1kr
l2v1ok
l1vo
l2v1old
l2v1olv
l2v1on1to
l2v1op
l2v1or
l2v1os
lv1ö1dé
l1vö
lvö2l
l2v1ö1lé
l2v1ölt
l2v1ön
l2v1ös
l2v1öv
l2v1ö2z
lv1ő2r
l1vő
lv1ős
lv1pr
lv1ps
lv1sp
lv1st
l2v1ut
l1vu
lv1új
l1vú
l2v1üg
l1vü
l2v1üt
l2y
1lya
lya2dat
lya1da
2ly1adm
2ly1a2dó
2ly1ag2y.
lyag2y
2ly1agyr
2ly1ajt
2ly1a2kas
lya1ka
ly1akc
2ly1ak1ná
2ly1a2kós
lya1kó
2lyakt
ly1a2lat
lya1la
2ly1alb
2ly1alk
2ly1alm
ly1a2lom
lya1lo
ly1alt
ly2a2maj
lya1ma
lya2ma1rá
lya2m1el
lya1me
lya2mem
lya2m1ér1té
lya1mé
2ly1ang
2lya1ni
lya2nyag
lyan2y
lya1nya
ly1a2nyá
ly1a2pán
lya1pá
lya1p2r
2ly1arc
ly2a2sal
lya1sa
ly1as1pe
ly1as2s2z
2ly1atl
lya1t2r
2ly1a2t2y
2lya1zo
1lyá
2ly1á2bé
2ly1ábr
2ly1ág
ly1áld
ly1áll
2ly1áp
2ly1á2rad
lyá1ra
2ly1á2rak
2ly1á2ram
2ly1á2rat
ly1árk
2ly1árn
2ly1árr
2ly1á2ru
lyás3s
lyá2s2z
2ly1á2ta
2ly1átf
2ly1áth
2ly1át1lá
2ly1át1lé
2ly1átm
2ly1áts
2ly1átt
2ly1átv
ly1bl
ly1br
ly1dr
1lye
lye2ga2
ly1e2gye
lyeg2y
lye2gyez1
ly1ej
2ly1e2k2e.
lye1ke
2ly1e2kek
2ly1e2kéb
lye1ké
2ly1e2ké1rő
ly1e2le
ly1elf
ly1elh
ly1ell
ly1elm
ly1e2lő
ly1elr
ly1els
ly1elt
ly1e1lü
ly1elv
ly1elz
lye2m1a
2ly1ember
lyem1be
2ly1e2mel
lye1me
lye2min
lye1mi2
2ly1em1lí
2ly1eng
ly1erk
ly1e2rő
2ly1esd
2ly1ese1mé
lye1se
2ly1e2se1te
2ly1e2se1té
2ly1e2sett
2ly1esél
lye1sé
2ly1e2sés
2ly1es1te
2ly1eszk
lyes2z
2ly1eszm
2ly1e2s1zű
2ly1e2tet
lye1te
2ly1e2vő
2ly1ex
1lyé
2ly1ég
2ly1é2h2e.
lyé1he
2ly1é2hen
2ly1é2het
lyé2l
ly1é1le
ly1élm
2lyéne1ke
lyé1ne
2ly1ép
2ly1é2r.
2ly1érc
2ly1ér1d2
2ly1é2ré
2ly1érm
2ly1é2r2ő.
lyé1rő
2ly1é2rő1i
2ly1é2rők
2ly1érr
2ly1ér1te
2ly1ér1té
2ly1é2rü
2ly1érv
2ly1érz
ly1és2z
2ly1étt
2ly1é2v.
2ly1é2v2e.
lyé1ve
2ly1é2ve1i
2ly1é2vek
2ly1é2ven
2ly1é2vet
2ly1é2vév
lyé1vé
2ly1é2vi
2ly1évr
2ly1évv
ly1fl
lyf1ölt2
ly1fö
ly1gl
ly1gr
1lyi
2ly1i2bo
2ly1i2de1á
lyi1de
2ly1i2deg
2ly1i2den
2lyi1di
2ly1i1do
2ly1i2dő
lyié2h
lyi1é
lyi2g
2ly1i2ga
2ly1i2gá
2ly1i1ge
2ly1i1gé
2ly1i1ha
2ly1ill
ly1i1ma
2ly1i2má
2ly1imp
2ly1ind
2ly1inf
2ly1ing
2ly1i1ni
2ly1int
2ly1inv
2ly1i2p
ly1i2rat
lyi1ra
2ly1i2rá
2ly1i2ri
2ly1i1ro
2ly1irr
2ly1irt
2ly1isk
2ly1ism
2ly1isp
2ly1ist
2ly1i1ta
2lyivad
lyi1va
2ly1i2z
1lyí
2ly1íg
2ly1ín
2ly1í2r
ly1ív
ly2kiz
ly1ki
ly1kl
ly1k2ró
1ly2n.
1lyo
2ly1ob
2ly1o2dú
2ly1of
2ly1o2k1al
lyo1ka2
2ly1okl
2ly1okm
2ly1o2koz
lyo1ko
2ly1ok1ta
lyo2l
ly1o1la
ly1old
ly1oll
ly1olt
ly1olv
lyo2m1a2s
lyo1ma
2ly1op
2ly1o2r
2ly1os2z
2ly1ott
1lyó
ly1ó1dá
lyó2s1á
1lyö
2ly1öb
2ly1ö2l
2ly1ö2r
ly1öss
2ly1öv
1lyő
ly1ő2r
ly1pl
ly1pr
lyrá1di2
ly1rá
lyre1p
ly1re
1ly2s.
ly1sk
ly1sp
lys2t
ly3szá2m1é
lys2z
ly1s1zá
ly2ta2c
ly1ta
ly1t2r
1lyu
2ly1ud
2ly1ugr
2ly1uh
2ly1uj
lyu2ká2s
lyu1ká
2ly1u2ni
2ly1u2r
2ly1u2t
1lyú
2ly1újs
2ly1ú2r.
2ly1úth
2ly1útr
2ly1útt
2ly1útv
1lyü
2ly1üd
2ly1üg
2ly1ünn
2ly1ür
2ly1ü2v
2ly1üz
1lyű
2ly1ű2r.
2ly1űrh
2ly1űrl
1ly1wo
lyze2t1el
ly1ze
lyze1te
lzás1s
l1zá
lze2ta
l1ze
lze2t1e2l
lze1te
lze2t1é2r.
lze1té
lzé2sa
l1zé
lző1a2
l1ző
lző1e2
lző2s1orr
lző1so
lzus3s2
l1zu
lzu2s3z
2m.
1ma
maa2d
ma1a
ma1b1ra
2m1abs
ma2cé2l.
ma1cé
ma2célb
ma2célt2
2madag
ma1da
2m1adap
ma2datb
ma2da1to
ma2dat1t2
madás1s
ma1dá
ma2d1é2v
ma1dé
madókés2z1
ma1dó
mad2ókés
madó1ké
ma2dóz
3m2a3d2z
mae2r
ma1e
maé2r
ma1é
ma1f2ra
ma1f1ri
ma2gac
ma1ga
ma2g1a2da1to
maga1da
ma2g1a2dá
ma2g1a2dó
ma2g1a2du
m2aga2l
mag1a1la
ma2ga2n
ma2g1an2y
maga2r
mag1a1ra
magas1s
ma2g1a1s1za
magas2z
mag1a1zo
m2agaz
ma2gág
ma1gá
mag1áll
ma2g1á2ré
ma2g1árn
ma2g1árr
ma2g1árt
mag1á1ta
ma2g1á1to
ma2geb
ma1ge
ma2g1e2l
2m1aggr
ma2gid
ma1gi
ma2g1i2k
ma2g1í
magkia3dó
mag1ki
magkia2d
magki1a
ma2g1or
ma1go
ma2g1os2z
mag1ó1ra
ma1gó
ma2g1óv
ma2g1u2r
ma1gu
ma2g1ü2
2m1ag2y.
mag2y
2m1agyb
2m1a2gyu
m1ahh
ma2i2l.
ma1i
ma2ilt
mai2z
2m1aj1ta
maj2ti
ma2ka1rá
ma1ka
ma2kác
ma1ká
2m1akc
mak2k1e2
2makkr
2m1ak1ku
ma1k1lu
2m1a2ko2l.
ma1ko
ma1k2rém
m2ak1ré
m2a1k2ri
2mak1ro
m1ak1ti
ma2kus
ma1ku
mala2c3há
ma1la
malac2h
mala2c3s
2malag
mala2g1ú
m1a2la1ku
2m1alakz
2m1a2lan
2m1a2la1pí
malasz2t1a
malas2z
2m1alb
2m1alc
2m1ald
2malg
m1al1go
2m1alj
2m1alk
malo2m1e
ma1lo
mal1os
m1alr
2m1al2t.
mal1th
2m1a2lu
2malv
2m1amp
2m1a2nal
ma1na
ma2nat
2ma1ná
ma2n1eg
ma1ne
2m1an1gi
2m1an1to
2m1antr
2m1a2ny2a.
man2y
ma1nya
ma2nyag
2m1a2nyá
2m1a2nyó
mao2k
ma1o
ma2p2a.
ma1pa
ma2pas
2m1a2pát
ma1pá
ma1p2l
ma1p2re
ma1p2ré
ma1p2ri
m2a1p2ro
2m1aps
2m1a2rán2y
ma1rá
2m1arb
2m1ar2c.
2m1arcc
2m1arc2h
mar1c1ko
2m1ar1cu
2m1ar1cú
2m1a2rén
ma1ré
ma2r1i2si
ma1ri
mar2k1al
mar1ka
mar2k1in
mar1ki
mar2k1ón
mar1kó
mar2k1ó2r
2marm
maro2k1
ma1ro
ma2ro1má
maros1s
marók2
ma1ró
maró1kh
maró1kk
maró1kn
maró1kr
ma1ry
2m1arz
m2a1s2ká
ma1s2pe
ma1sp2r
ma1s2rá
mast2
m2a1s2ta
ma1s1te
m2a1str
masz1eg2y
m2aszeg
mas2z
ma1s1ze
m2asz1e2me
m2a2sz1ét
ma1s1zé
ma2szév
ma2szis
ma1s1zi
maszí2v
m2a1s1zí
masz1í1ve
masz1ös
ma1s1zö
mat1a1da
ma1ta
mat1alap
mata1la
mat1a1nya
m2atan
matan2y
ma2taz
m2a2tág
ma1tá
matá2ra1ka
m2atár
matá1ra
ma2t1á2ras
mat1á2rár
matá1rá
ma2t1árn
ma2t1á2rú
ma2t1áz
ma2t1e2g
ma1te
mat1e1li
ma2t1ell
mat1e1lő
mat1elt
ma2t1e1me
mate2s
m2a2tél
ma1té
ma2t1ér1te
m2atér
mat1fé2
matfél1
matfé1lé2
ma2tid
ma1ti
ma2t1ind
m2atin
ma2t1inf
ma2t1ing
ma2t1int
ma2t1ö1rö
ma1tö
ma1t2ran
mat1ra
mat2rág
mat1rá
mat2tin
mat1ti
ma2t1ut
ma1tu
2m1a2tya
mat2y
ma2tyá
2m1a2uk
ma1u
mau2ra
ma2u1ri
ma2us
mau2ta
2m1a2u1to
2m1a2vat
ma1va
ma2z1a2l
ma1za
ma2zál
ma1zá
ma2z1átl
ma2zel
ma1ze
mazókés2z1
ma1zó
maz2ókés
mazó1ké
ma2z3sű
maz2s
1má
2m1á2bé
2m1ábr
má2cs1as
mác2s
má1c1sa
má2c1s1e2
má2csin
má1c1si
má2csir
má2csis
má2c1só
má2c1sü
má2fá
2m1á2g.
2m1á2ga
2m1á2gá
2m1ágb
2m1á2gé
2m1ágg
má2g2i.
má1gi
2m1á2gi2g
2m1ágk
m1ág1na
2má1go
2má1gó
2m1ágr
má2guk
má1gu
má2gu2n
2m1á2gú
2m1á2g2y
mágya2d
má1gya
mágy1a1da
má2hí
má2jan
má1ja
má2j1árt
má1já
má2j1e2
má1jo2
má2j1ol
májren2
máj1re
má2j1ul
má1ju
má2jü
má1ka2
má2k1al
má2kar
má2k1e2
má2k1é2r.
má1ké
má2kil
má1ki
má2k1ó
má2kő
má2k1ü
m2á2l1a2l
má1la
2m1álar
má2lál
má1lá
2m1álc
má2l1e2
mál1ért
má1lé
2m1állam
mál1la
m1állap
m1állat
2m1ál1lí
m1állom
mál1lo
2m1ál1ma
2m1ál1mo
má2los
má1lo
má2m1as
má1ma
2m1á2mí
má1na2
má2n1as
má2nav
má2nár
má1ná
mánc1c
mán2ce
mán2c1ég
mán1cé
mán2ci2p
mán1ci
mán2c1sé
mánc2s
má2n1e
má2n1in
má1ni
má2nis
má2n1it
má2n1ö2
má2nő
máns3s
mán2tac
mán1ta
mán2tag
mán2t1al
mán2t1as
mán1tá2
mán2t1ék
mán1té
mán2t1öl
mán1tö
má2nú
má2nü
má2ny1a2d
mán2y
má1nya
má2ny1a2g
má2nyaj
má2ny1a1ka
má2ny1a2l
mánya2n
má2n2yan2y
má2nyap
má2nyar
má2nya1u
má2nyav
má2nyaz
má2ny1e2
má2ny1é2r.
má1nyé
má2nyér1d2
má2nyérz
má2ny1í2
má2ny1ó2
má2nyö
má2po
2m1á2rad
má1ra
má2r1a2g
2máram
má2r1a2n
má2ras
már1ass
2m1á2rat
má2r1a1u
má2r1ál
má1rá
má2re2
már1em
má2rés
má1ré
má2r1id
má1ri
má2r1ik
má2r1i2p
má2r1i2si
már1isk
már1is1tá
má2rí
2m1árkok
már1ko
2márol
má1ro
má2rö
má2rő
má2rug
má1ru
m1á2ruh
má2ruk
m1á2rur
má2rü
má2s1a2d
má1sa
má2sal
má2sap
má2s1a2r
má2s1av
má2saz
má2s1á2rá
má1sá
má2s1árn
má2sás
2m1á1sá1sá
2m1ásásn
2m1ásásr
másbe2j1
más1be
másbe1já2
má2s1e2
má2sí2r
má1sí
má2sor
má1so
2m1á2só
má2sö
má2ső
mást2
más1tr
má2s1ü2
más3zav
más2z
má1s1za
2m1á2ta
mát1a2k
2m1á2tá
2m1átc
2m1átd
má1te2
má2ten
2má1té
2m1átf
2m1átg
2m1áth
2m1á2tir
má1ti
2m1á2tí
m1átj
2m1át1ló
2m1átn
má2t1ol
má1to
2m1á2t1ö
2m1átp
2m1át1re
2m1áts
2m1át1te
2m1á2tú
2m1á2tü
2m1átv
má2zal
má1za
má2ze
mázi2a
má1zi
mázi2é
má2z1i2s
má2ző
má2zsal
máz2s
má1z1sa
má2z1s1e
máz3s2z
m2b1akc
m1ba
m2b1ak1k
mba1k2r
mb1akv
m2b1a2lag
mba1la
m2b1alj
m2b1alk
m2b1a2na
m2b1a2nya
mban2y
mba1p
mb1a2var
mba1va
mb1a2zo
mb1á2g2y
m1bá
m2b1áll
m2b1á2ron
mbá1ro
m2b1ár1ró
mbá2száv
mbás2z
mbá1s1zá
m2b1ász1ná
m2b1á2szo1ka
mbá1s1zo
m2b1á2szokk
m2b1á2szo1ko
m2b1á2szokr
m2b1á1ta
m2b1áth
mbe1á2
m1be
mb1e2ce
mbe1k2
mbe2led
mbe1le
mb1e2leg
m2b1ell
mb1elr
mb1els2z
mb1e1pe
mbe2r1a2g
mbe1ra
mbe2ral
mbe2ra2n
mbe2ras
mbe2rá
mbe2reg
mbe1re
mbere2s2z
mber1e1s1zű
mbe2rev
mbe2r1él
mbe1ré
mbe2r1é2s
mbe2ri1má
mbe1ri
mbe2ring
mbe2ris2z
mbe2rol
mbe1ro
mbe2ros
mbert2
mbe2r1ú
m2b1é2g
m1bé
mb1é2ke
m2b1é1kí
m2b1ép
mbé2ress
mbé1re
m2b1ér1té
mbért2
m2b1érz
mb1gr
m2b1i1de
m1bi
mbi2k1al
mbi1ka
mbi2ke
m2b1il1la
m2b1i2n2a.
mbi1na
m2b1i2na1i
m2binam
mb1ind
m2b1inf
m2b1in2g.
mb1inv
m2b1i1pa
m2b1izm
mb1i2zom
mbi1zo
m2b1izz
m2b1ív
m1bí
mb1íz
mb1kl
m1b2lú
mb1l2y
m2b1ob
m1bo
mb1ol1da
mbo2lyak
mbol2y
mbo1lya
m2b1ond
m2b1op
m2b1or1má
m2b1or1mú
m2b1o2roz
mbo1ro
m2b1oszl
mbos2z
mb1ö1le
m1bö
m2b1öv
m2b1ő2si
m1bő
m2b1ő2s2z
mb1pl
mb1pr
mbrá1di2
mb1rá
mb2rev
mb1re
mb2rok
mb1ro
mb2ron
mb1sp
mb1st2
mb1s2z
mb1tr
mbu2s1ze
m1bu
mbus2z
mbu2s1zí2
m2b1u2t
mb1új
m1bú
m2b1üg
m1bü
m2b1ül
m2b1üs
m2b1üt
m2b1üz
mbve1zé2
mb1ve
mc2lu
mcsa2p1á2g
mc2s
m1c1sa
mcsa1pá
mda1b2
m1da
mda1g2
mda1p2
mdi2a1le
m1di
mdi1a
mdi3a1p2
md2ra
md2rá
md2ro
md2ró
1me
mea2l
me1a
mea2n
me2av
me2béd
me1bé
mede2r1e2
me1de
2m1e2dén2y
me1dé
me1d2rá
2m1e2d2z
mee2s
me1e
me2et
2m1eff
me2ga1ba
me1ga
me2g1a1la
me2g1a1lu
me2g1alv
me2g1an
me2g1a2r
mega1s2z2
me2gav
me2g1á
me3gá2m
megá2s
megá2t
megá2z
me2g1eg
me1ge
me2g1el
mege2le
mege2lő
me2ger
me2g1esem
mege1se
meg1eszt
meges2z
me2ge1ta
me2g1e2te
me2gez
me2g1é2l
me1gé
megés3s
me2gés2z1
meg1f
megg2
meg1gr
me2gi
meg1i1gá
megi2g
meg1inn
meg1ir
meg1is
meg1itt
me2g1í
me2g1o
me2g1ó2
me2g1ö2
me2gőr
me1gő
me2g1u2
me2gú
me2g1ü2g
me1gü
me2g1ü2l
2megyez
meg2y
me1gye
2m1egyh
2m1egyl
2m1egys
2m1e2gyüt
me1gyü
me2he1tő
me1he
mei2rá
me1i
me2k1ad
me1ka
me2k1ag
mek1alk
me2k1am
mek1arc
me2k1a2s
me2k1att
me2k1á2l
me1ká
me2k1á2p
me2k1á2r.
me2k1á1ra
me2kát
mek1eg2y
me1ke
mek1ell
me2kep
me2k1ers2
meke2s2z
me2k1e1s1ze
me2keszm
mek1esz1te
me2kev
me2k1ék
me1ké
me2kél
me2k1é2r.
mek1ér1de
me2k1érk
me2k1ér1te
me2k1ér1té
me2k1éss
me2k1é1te
me2ki1do
me1ki
me2kij
mek1i1má
me2k1ing
me2k1int
me2k1i2p
me2k1ist2
me2k1i1ta
me2kír
me1kí
me2kít
mek1k2
mek3lu
me2kob
me1ko
me2k1ok
me2k1old
me2k1olt
me2k1onk
me2kop
meko2r
mek1o1ra
mek1o1ro
mek1ort
me2k1os
mek1ott
me2k1ó2v
me1kó
mek1öl1tö
me1kö
me2kön
mek1öröm
mekö1rö
me2k1öt
me2k1ő
mek1s
meks2z2
mek1t2
me2kuj
me1ku
me2kun
me2kur
me2k1ú2
me2küd
me1kü
me2k1üg
me2k1üld
me2k1ü2lé
me2k1ü2lő
me2küz
m1e2l1a2d
me1la
mel1ak
me2lág
me1lá
me2lál
2melb
2m1e2lef
me1le
me2lekt
mel1e2l
2m1e2le1me
2m1e2lemz
2m1e2les
2melet
mele2t1é2r.
mele1té
2m1e2lég
me1lé
2m1e2l1é2l
2m1e2l1ér
melés3s
2m1elf
2m1el1go
m1el1ha
2m1el1há
2m1elhel
mel1he
me2li2t.
me1li
2me2l1í2
2m1el1já
2melk
m1el1ké
m1el1kí
m1el1kö
mel2lál
mel1lá
m1elle1ne
mel1le
mel2ler
mel2l1é2r.
mel1lé
mel2ling
mel1li
mel2l1iz
mel2lo
mel2lö
mel2l1u2
2m1el1mé
2m1el1nö
me2l1os
me1lo
m1elő1de
me1lő
2m1e2lő1í
m1e2lő1le
m1e2lőtt
2melőz
me2lő1ző
2m1elr
2m1el1s1zá
mels2z
2m1el1ta
2m1el1tá
m1elter
mel1te
2m1eltet2t.
2m1eltettn
2m1eltér
mel1té
2m1el1ti
2melt2y
2m1el2v.
2m1el1vá
2m1elves
mel1ve
2m1elvn
2m1el1vo
me2ly1ék
mel2y
me1lyé
2m1elz
2m1eml
2m1e2mul
me1mu
me2n1a2d
me1na
me2nal
me2nar
me2na1u
me2nát
me1ná
me2n1e2g
me1ne
me2n1el
me2ner
mene2t1á2
mene2tö
menés3s
me1né
2m1enges
men1ge
me2nil
me1ni
me2n1ip
me2ní
me2nö
men2s1é2g
men1sé
men2t1ell
men1te
men2ti2p
men1ti
men2t1is
me2n1u
me2nú
me2nya
men2y
menye2ma
me1nye
me2om
me1o
me2ó1e
me1ó
me1p2h
me2pik
me1pi
me2pos
me1po
me1p2r
mera1p2
me1ra
mer1ass
2m1er1dő
2m1e2rec
me1re
2m1e2redm
mer1eg2y
2m1e2rej
me2r1e2l
me2r1eml
mere2t1a
mere2t1e2l
mere1te
mere2t1é2r.
mere1té
2m1e2rezőkh
mere1ző
me2r1ép
me1ré
me2r1il
me1ri
me2rim
mer1inf
mer1ing
me2r1ip
2m1er1nyő
mern2y
me2r1ol
me1ro
me2ror
me2r1os
me2rov
2meről
me1rő
2merő1sí
mers2
mer1st
mer1t1ró
me2r1ü2g
me1rü
me2sas
me1sa
me2s1emb
me1se
2m1e2semén
mese1mé
mese1s
2mese1té
2m1e2sett
2m1e2sél2y
me1sé
2m1e2sé1sé
2m1e2sésh
2mesésk
2mesésr
2mesés3s
2mesést
2m1e2ső
me1s2po
2m1estb
2m1est2e.
mes1te
2m1estek
2m1es1té
2m1estf
me1s2tó
2m1estr
2m1estün
mes1tü
me2szan
mes2z
me1s1za
2meszem
me1s1ze
me1sz2tá
met1a1nya
me1ta
metan2y
meta1s
metas2z2
me2t1e2g2y
me1te
met1ell
2m1e2te1té
met1ing
me1ti
me2tór
me1tó
me2tú
me2tűd
me1tű
2m1e2ug
me1u
me2uk
meus3s
meu2t
me2vő
me2za2c
me1za
me2z1aj
meza2k
me2za1u
me2zál
me1zá
me2ze1dé
me1ze
me2zeg
me2z1elj
me2z1ell
me2zelőh
meze1lő
me2zer
mez1e1re
me2z1ék
me1zé
me2z1é2r.
me2z1ér1d2
mez1éret
mezé1re
me2z1é2ri
me2z1id
me1zi
me2zim
me2zin
me2zi2o
me2zír
me1zí
me2z1ol
me1zo
me2z1or
me2z1ö
mező1e2
me1ző
me2z1ő2rö
me2z1őss
2m1ezrem
mez1re
m1ezre1se
m1ezresr
m1ezrest
me2z3sa
mez2s
me2z1sá
me2z1sö
me2zu
me2zú
1mé
mé2ber
mé1be
2m1ébr
mé2c1s1a2
méc2s
mé2c1so
mé2g1a
mé2ge
még1eg
mé2gé
mé2gi
még1is
mé2g1o2
mé2gő
mé2gú
mé2h1a2
mé2h1á
m2é2heg
mé1he
mé2hel
mé2hi2r
mé1hi
mé2h1is
mé2h1or
mé1ho
mé2hö
méhren2
méh1re
2m1éh1sé
2m1é2j.
2m1éjb
mé2jes
mé1je
2m1éjs
mé1ka2
mé2k1ad
mé2k1aj
mé2k1ak
mé2k1al
mé2k1an
mé2kar
mé2kaz
mé2k1á2
mé2k1e2g
mé1ke
mék1elh
mék1ell
mék1e2lő
mé2k1e1se
mé2kev
mé2kez
mé2k1é2k
mé1ké
mé2k1é2l
mé2ki2d
mé1ki
mé2ki1rá
mé2kí
mé2k1o
mé2k1ö
mé2kő
mé2k1u2
mé2k1ú
mé2l2e.
mé1le
2m1é2lel
mé2les
mé2lez
2m1é2lén
mé1lé
2m1élm
mé2lya
mél2y
mé2lyá
mé2lyeg
mé1lye
mé2ly1ú2
mé2n1a2r
mé1na
mé2n1a2t
2ménekb
mé1ne
2ménekh
2m1é2nekl
mé2n1el
mé2n1é2k
mé1né
mé2n1és
mé2nid
mé1ni
mé2nin
mé2n1is
mé2niv
mént2
mé2ny1e2g
mén2y
mé1nye
mé2ny1e2l
mé2nyer
mé2nyé2k
mé1nyé
mé2nyim
mé1nyi
mé2nyí2r
mé1nyí
mé2ny1o
mé2nyö2
mé2nyú
ményü2l
mé1nyü
mé2ny1ü1lé
mé2pí
mé2pü
2m1ér2c.
2m1é2retts
mé1re
2m1é2rén
mé1ré
mé2rér
mérés1s
2m1é2rév
2m1é2ri1e
mé1ri
2m1é2rin
2m1ér1ké
2m1érlel
mér1le
mé2r1ón
mé1ró
2mérte1ke
mér1te
2m1értes
2m1értér
mér1té
2m1értés
2m1é2rül
mé1rü
mér2v1a2
2m1érz
mé2s1a2
mé2s1á2
mé2s1e2l
mé1se
mé2s1er
mé2sez
mé2sin
mé1si
mé2sö
més3s1za
més2s2z2
mé1s1za2
més2z
mész1al
mé2sz1á2l
m2é1s1zá
mé2sz1á2ra
mé2szed
mé1s1ze
mé2sz1el
2mészl
mé2s1z1ő
mész3s
2mész1té
2mészth
2mész1ti
2mésztj
2mésztl
2mész1tü
2mésztv
mé2s1z1ü2
mé2t1ad
mé1ta
mé2t1ak
mé2t1a2n
mét1árt
mé1tá
mét1el1ho
mé1te
mét1el1ta
mé2t1e2v
mé2té2g
mé1té
2m1ét1je
2m1étjér
mét1jé
2m1étjév
2m1ét1jü
2m1étk2e.
mét1ke
mé2t1o
mé2tö
mé1tő2
mé2tőr
mé2t1u
mé2t1ű
2m1é2v.
2m1évb
2m1é2v2e.
mé1ve
2m1é2ve1i
2m1é2vek
mé2ven
mé2ves
2m1é2vet
2m1é2véb
mé1vé
mé2vén
2m1évf
2m1évh
2m1é2vi
2m1évk
2m1évn
2m1évr
2m1évs
2m1évt
2m1évv
mé2z1a
mé2z1á2
mé2zeg
mé1ze
mé2zil
mé1zi
mé2zim
mé2zin
méz1ism
mé2zit
mé2zí
mé2z1o
mé2z3s
mé2zu
mé2zű
mfa2l1e2
m1fa
mfa2lom
mfa1lo
mfa1s2
mfé2m1a2
m1fé
mfit2
m1fi
mfi1tr
mf2la
m1f2lo
mf2ló
mf2lu
mfog1adat
m1fo
mfo1ga
mfoga1da
m1f2rak
mf1ra
m1f2ran
mf2rá
m1f2re
m1f2ri
m1f2rí
m1f2ro
m1f2rö
m3g2é2p1e2l
m1gé
mgé1pe
mgé1pi2
mgé2p1ip
mg2li
mg2ló
mgör2c1so
m1gö
m3g2örc
mgörc2s
mg2ra
mg2rá
mha2sábr
m1ha
mha1sá
mhossz1út
m1ho
mhos2s2z
mhos1s1zú
1mi
mi2a1a
mi1a
mi2a1á
mi2a1bo
mi2ac
mi2a1e
mi2a1é
mia1f2
mi2afr
mi2ag
mi2ah
mi2a1í
mi2a1le
mi2a1mé
mi2a1o
mi2a1ó
mi2a1ö
mi2a1ő
mi2a1p
mi2a1ré
mias2
mi2a1s1zá
mias2z
mi2a1s1zé
mi2a1s1zi
mi2a1s1zó
mi2a1tá
mi2a1ti
mi2a1to
mi2a1ü
mi2av
2m1i2áz
mi1á
mi1bl
micsa2p1
mic2s
mi1c1sa
2m1i2de1á
mi1de
2m1i2deg
2m1i2de1i
2m1i2dej
2miden
mi2dent
2m1i2de1o
mi2dén
mi1dé
mi2di1o
mi1di
mi2di1ó
mi2dol
mi1do
2mi1dő
m1i2dőz
mie2l
mi1e
mi2éf
mi1é
mi1fl
mi1fr
2m1i2gaz
mi1ga
2m1i2gén
mi1gé
mi1g1ri
2m1ihl
mii2d
mi1i
mi1k2li
mi1k1lu
mi2ko1no
mi1ko
mi2kont
2miks
mi2lal
mi1la
2mil1le
2m1il1lu
2m1il1lú
mi2m2a.
mi1ma
mi2máh
mi1má
mi2mit
mi1mi
mi2mór
mi1mó
mi2naj
mi1na
2m1i2nam
mina2n
min1an2y
2m1i2nas
mi2n1á1ra
mi1ná
min2c1e2
min2c2h
min2d1a2
2m1in1dá
min2dek
min1de
min2d1er
min2din
min1di
2m1in1dí
2min1du
mi2neg
mi1ne
mine2s
2m1infl
2m1in1fú
min2g1á
2m1in1gé
min1g2h
min1inj
mi1ni
min2k1a2l
min1ka
min2k1an
min2k1as
min2kec
min1ke
min2kó
min2kö
2m1insp
2m1i2nuk
mi1nu
mi2nü
2m1inz
mio2n1a
mi1o
mio2n1á
mio2r
mió2r
mi1ó
mi1p2l
mi1p2r
mi2ram
mi1ra
2m1i2rat
2mi2rán
mi1rá
2mirg
2mirh
miri2gyel
mi1ri
mirig2y
miri1gye
2m1irk
2m1i2rod
mi1ro
2m1i2rom
mi2rón
mi1ró
2m1irr
mi2sal
mi1sa
mis1elv
mi1se
mis1e1pe
mis1ing
mi1si
2misit
mi2s1í2r
mi1sí
mi1s2pi
mis3szab
mis2s2z
mis1s1za
mis3szer
mis1s1ze
mi2s1ü2t
mi1sü
mi2s1ü2v
mi2s1ű
mi2szár
mis2z
mi1s1zá
mis3zá2r.
mi1sz2f
mi2tac
mi1ta
mita2n
mit1an2y
2m1i2tat
2mitác
mi1tá
mit1á2r.
mit1á1rá
mit1árh
mit1á1ri
mit1ár1k2
mit1árn
mit1árr
mit1árt
mit1á2rú
2mit1bo
mit1ing
mi1ti
miti2s
2mit1lá
mi2tök
mi1tö
mi1t1ri
2mit1ro
2mit1rú
mi2tür
mi1tü
miu1mé2
mi1u
miu2min
miu1mi
miu2s
2m1i2vad
mi1va
2m1i2vó
mi2x1i
mi2xö
mi2xő
mi2z2é.
mi1zé
mi2zét
1mí
m1í2gé
mí2ja
mí2já
m1íns
2m1í2rá
2m1í2ró
m1í2v.
mí2vá
m1ívb
mí2vé
m1ívh
m1ívr
m1ívv
mí2zü
mí2zű
mjé2ná
m1jé
mjob1b1o
m1jo
mjo2g1á2s
mjo1gá
m2j1ol
mj1ó1sá
m1jó
mj1ős
m1jő
mka1ró2
m1ka
mka2r1ór
mke1p
m1ke
mki1a2
m1ki
mki1á2
mki1e2
mk2la
mk2li
mk2lí
mk2lo
mk2lu
mkó2rost
m1kó
mkó1ro
mk2ra
mk2rá
mk2re
mk2ré
mk2ri
mk2rí
mk2ro
mk2ró
mk2va
mk2vó
mla1f
m1la
mlapá2r
mla1pá
mla2p1á1ro
mla2p1e2
mla2pin
mla1pi
mla1s2t
mlás3s
m1lá
mlá2s3z
mle1g2
m1le
mle1í2
mle1kn
mle1p1la
mlé2k1a2
m1lé
mlé2k1á
mlé2k1el
mlé1ke
mlé2k1es2t.
mlé2k1é2r.
mlé1ké
mlé2k1ol
mlé1ko
mlé2kos
mlé2kó
mlé2k1ú2
mlé2s1zé
mlés2z
mlo2k1ál
m1lo
mlo1ká
mlo2ké
mlő2s1a2
m1lő
mlő2s1ű2
mmag1g
m1ma
mma1gl
mma1i2
mmas2
m2m1atk
m2m2e.
m1me
mme2g1é
mmifé2l1é2v
m1mi
mmi1fé
mmifé1lé
1mo
mo2be
2m1obj
mo1ci1
2m1off
m2o1g2rá2f.
mog1rá
m2o1g2ráff
mo1g2ráfk
mo1g2ráfn
mo2gy1a2
mog2y
mo1hu2
mo2is
mo1i
mo2kab
mo1ka
mo2k1ad
mo2k1a2k
mo2k1a2l
mo2k1a2n
mo2kar
mo2kád
mo1ká
mo2k1ál
mo2k1á2s
mo2k1e2
mo2k1il
mo1ki
mok1k2
mo2k1ol
mo1ko
mo2k1or
mo2k1ó2
mo2kö
mok1t
2m1oktat
mok1ta
mo1ku2
mo2kur
mo1kú2
mo2kús
mo2k1ú1to
mo2kü
2m1o2laj
mo1la
2m1ol1da
m1ol1dó
2m1o2lim
mo1li
mol1li2
mol2l1in
2m1ol1ló
molói2ko
mo1ló
moló1i
2m1oltár
mol1tá
2m1oltás
2m1olvad
mol1va
2m1olvas
2m1oml
mon1a1cé
mo1na
mo2n1a2d
mo2n1a2l
mo2n1an
mo2n1a2p
mona2r
mo2n1as
mo2n1áll
mo1ná
mo2neg
mo1ne
mo2n1er
mo2n1is
mo1ni
mon2or
mo1no
mo2nö
mons2
mon1sp
mon1t1ré
mo2nü
mo2nű
mo1nyá2
mon2y
mo2nyáz
mo2or
mo1o
2m1opc
2mo1pe
mo2per
mo1p2l
2m1opt
mo2r1ad
mo1ra
mora2n
mor1an2y
morá2la
mo1rá
2m1or1dí
mo2r1e2l
mo1re
mo2re2n
mo2r1e2r
mo2r1est
more2s
mo2rid
mo1ri
2m1o2ri1e
mori2s
mo2r1isk
mo2r1i1s1zo
moris2z
mor1izg
2m1orjai1a
mor1ja
morja1i
2m1orjai1é
2m1orjait
mo2r1ol
mo1ro
mo2r1ont
mo2r1ón
mo1ró
mo2r1ó2r
mo2rós
mo2rö
2m1or2r.
mor1s2
mort2
mor3tá
mor1tr
mo2rü
mo2rű
mo2sál
mo1sá
mo2s1e
2mos1to
2m1ostr
2m1ost2y
mo2sü
mo2szal
mos2z
mo1s1za
mo2szis
mo1s1zi
2m1osz1lo
mo1t2h
2m1ott1ha
mot2to
mo2un
mo1u
mo2us2
mo2vi
mo2xi
mo3z1so
moz2s
1mó
móá2g
mó1á
mó1bl
mó2c2h
mócsa2p1
m2ó1c1sa
móc2s
mócsa1pá2
mó2d1a2l
mó1da
mó2da1u
mó2dák
mó1dá
mó2dár
mó2d1e2
mó2di2p
mó1di
mó2d1o2r
mó1do
mó1dó2
mó2d1ór
mó1du2
módus1
mó1fl
mói2ko
mó1i
mó1k2l
mókus1s
mó1ku
mó2lar
mó1la
mó1p2r
mó2rak
mó1ra
mó2rar
mó2rág
mó1rá
mó2rá1i
mó2ráj
mó2rám
móri2as
mó1ri
móri1a
mó1ró2
mó2r1ón
mó2rö
mó1s2k
mó1t1he
1mö
mö2bö
mö2ko
möl2c1s1a
mölc2s
möl2c1sá
möl2csel
möl1c1se
möl2cs1es
möl2cs1é2r.
möl1c1sé
möl2cs1il
möl1c1si
möl2c1s1o
möl2c1s1ő
mö2le
mö2nu
mö2ro
mö2r1ő
m1ötl
mö2ve
mö2vö
mö2vü
mö2vű
1mő
mőa2n
mő1a
mőá2g
mő1á
mőe2l
mő1e
mőe2r
mőé2l
mő1é
mőé2te
mő1kl
mő1ps
mő2r1á2r
mő1rá
2m1őrh
2m1ő2ri
mő2si
mő2sü
mős3zár
mős2z
mő1s1zá
mőü2l
mő1ü
mő2zi
mpa1dr
m1pa
m2p1akc
m2p1ak1tá
m2p1áll
m1pá
m2p1ár1ko
m2p1átj
m2p1átk
mp2ci
mp1elt
m1pe
mp1fr
m2p1ind
m1pi
mpi2re
mp2lak
mp1la
mplo2mal
mp1lo
mplo1ma
m1p2lu
mpon1d2
m1po
m2p1ord
mpor1ta2
mpor2t1al
mpor2t1á2r
mpor1tá
mpor2t1e2
m2p1osztás
mpos2z
mposz1tá
m2p1ös
m1pö
m1p2ref
mp1re
m1p2rep
m1p2rés
mp1ré
m1prib
mp1ri
m1p2ric
mp2ri1o
m1p2rod
mp1ro
m1p2rof
m1p2rog
m1p2roj
m1p2ro1p
m1p2rot
m1p2rób
mp1ró
m1p2ru
m1p2s1zi
mps2z
m2p1u2ta
m1pu
m2p1u1tó
m2p1üz
m1pü
mra1p
m1ra
mren2d1ő2
m1re
mré2m1
m1ré
msa2vo
m1sa
ms2ka
ms2ká
ms2ki
ms2ko
ms2lá
ms2mi
ms2ni
m2s1ond
m1so
ms2pa
ms2pe
ms2pi
ms2po
ms2pó
ms2rá
ms2ta
ms2tá
ms2te
ms2ti
ms2tí
ms2to
mst2r
ms2tú
msza2ké
ms2z
m1s1za
msza2k1ü2
mszáraz1
m1s1zá
mszá1ra
msz2c
mszé2dem
m1s1zé
mszé1de
m1sz2f
mszín3n
m1s1zí
msz2l
msz2m
m1sz2p
msz2tá
m1sz2v
mta2n1ó2
m1ta
mtára2d
m1tá
mtá1ra
mtá2r1a1da
mtés3s
m1té
mtőkés2z1
m1tő
mtő1ké
mtran2s
mt1ra
mtrans2z1
mt2rá
mt2re
mt2ré
mt2ri
m1t2ró
mt2rö
mt2rü
1mu
2m1udv
2m1ugr
m1ujj
2mulet
mu1le
2mulz
mu2m1ad
mu1ma
mu2m1el
mu1me
mu2mél
mu1mé
mu2m1és
mu2min
mu1mi
mu2m1ir
mu2mis
mu2m1iv
mumkés2z1
mum1ké
mu2m1ó2
mu2mö
mu2mő
mumus1s2
mu1mu
mun1g
mu2nok
mu1no
2mu2r.
mu2ral
mu1ra
mu2ram
mu2rat
mu2rál
mu1rá
mur1izm
mu1ri
mu2r1u2
mu2sal
mu1sa
mu2san
mu2sar
mu2sas
mu2sat
mu2s1á2g
mu1sá
mu2sál
mu2s1e
mu2s1ér1té
mu1sé
mu2sir
mu1si
mu2sor
mu1so
mu2s1ó2
mu2ső
muss2
mus3s1ze
mus2s2z
mus2tá1rá
mus1tá
mus2t1erj
mus1te
mu2szal
mus2z
mu1s1za
mus3zav
mu2szál
mu1s1zá
mu2szás
mu2t1a2g
mu1ta
mu2tal
mut1a2la
2m1uta1lá
2m1u2talv
muta2n
mu2t1an2y
mu2ta1sí
m1u2taz
mu2t1á2ra
mu1tá
mu2t1árb
mu2t1á2ru
2m1u2tás
2m1ut1ca
mu2t1el
mu1te
mu2til
mu1ti
mu2t1in
2m1u2tol
mu1to
2m1u2tód
mu1tó
2m1u2tó1p2
mu2t1ö
mu2tü
1mú
mú2jí
múl2t1e2
múl2tol
múl1to
2m1ú2r.
mú2ri
2m1úrn
2m1ú2s2z
2m1útb
m1úth
2m1ú2ti
2m1útj
2m1útk
2m1útm
2m1útn
2m1ú2to
2m1útr
2m1útt
2m1útv
1mü
mü2dí
mü2dü
mü2g2y
mü2ná
mü2re
mü2rí
mü2rü
mü2te
mü2té
mü2tő
mü1tz
mü2ve
mü2vö
mü2ze
1mű
mű1bl
mű1br
mű1fl
mű1fr
mű1gr
mű1kl
mű1pl
mű1pn
mű1pr
2m1űrl
mű1sp
mű1sú2
mű1s2z
műtőkés2z1
mű1tő
műtő1ké
műves3s
mű1ve
mű2zé
mű2zi
mű2zö
mű2ző
mű2zü
m2v1a2dot
m1va
mva1do
mvágya2d
m1vá
mvág2y
mvá1gya
mvá2gy1a1da
mverés3s
m1ve
mve1ré
mw2hi
mza2t1e
m1za
mzás3s
m1zá
mze2r1o
m1ze
mze2t1a2
mze2t1á2
mze2t1e2g
mze1te
mze2t1el
mze2ter
mze2tes2z
mze2t1é2k
mze1té
mze2t1érd
mze2to
mze2t1ö2
mze2t1ő2
mzé2s1a
m1zé
mzé2so
mzókés2z1
m1zó
mz2ókés
mzó1ké
mzőe2r
m1ző
mző1e
mz2rí
2n.
1na
naa2d
na1a
n1ab1bó
2n1abr
2n1abs
na1cl
2n1a2dag
na1da
2n1a2dás
na1dá
2n1add
na2dek
na1de
2n1adm
2n1a2dó
na1d2re
2n1adt
na2d2u.
na1du
na2dus
na2e1i
na1e
naé2r
na1é
2n1aff
na2g2a.
na1ga
na2gár
na1gá
na2git
na1gi
na2gón
na1gó
n2a1g1rá
na2gy1agg
nag2y
na1gya
na2gy1a2l
na2gyapj
na2gy1as
na2gyav
na2gy1é2k
na1gyé
na1gyú2
nagy1úr
na2gy1út
na2i1re
na1i
na2ji
2n1ajk
2n1a2kad
na1ka
naka1ró2
nak1á2s2z
na1ká
na2k1át
n1ak1ko
na1k1li
na1k1lu
nako2l
na1ko
nak1o1la
2n1a2k2ó.
na1kó
n2a1k2ré
n1ak1ti
2n1a2kus
na1ku
na2k1útn
na1kú
na2l1a2dó
na1la
2n1a2la2g1
na2l1aj
na2l1a2l
1na2la1na
2n1a2la1pa
2n1a2lapd
na2lapr
na2lapt
na2lar
na2lav
na2l1ábr
na1lá
na2lág
na2l1á2l
na2l1á2ro
na2l1á2t1ö
na2l1áts
na2l1e1lá
na1le
na2l1ell
nal1eng
n2alen
nal1ent
nal1é2g.
n2alég
na1lé
na2l1ék
na2l1é1ri
na2lid
na1li
na2l1ing
na2l1i2o
na2l1í2r
na1lí
2nalízisb
nal2í1zi
2nalízi1se
2nalízi1sé
2nalízish
2nalízi1si
2nalízisk
2nalízisn
2nalízisr
2nalízist2
2nalízi1sü
2nal2j.
n1alj2a.
nal1ja
3naljac
n1aljad
n1alja1i
2naljak
n1aljam
n1aljat
n1al1ji
2naljon
nal1jo
2nal1ju
2n1aljz
2n1alkat
nal1ka
n2a2l1ob
na1lo
na2l1ol
n2a2lop
nal1os2z
na2l1ó2r
na1ló
na2l1ő
nalt2
nal1tr
na2lulj
na1lu
na2l1ut
na2lü
na2mer
na1me
2n1a2mit1bo
na1mi
2n1a2mi1te
2n1a2mitg
2n1a2mit1ha
2n1a2mitk
2n1amitl
2n1a2mitm
2n1a2mi1tö
2n1a2mitp
2n1a2mit1ro
2n1a2mit1rú
2n1a2mits
2n1a2mit1tá
2n1a2mit1tö
2n1a1mö
2n1amp
2n1a2nal
na1na
2n1ang
2n1anh
na1no1
nanog2
na2nód
na1nó
2n1a2nyag
nan2y
na1nya
nao2l
na1o
naó2r
na1ó
2nap2a.
na1pa
nap1a1dó
na2p1a2g
na2p1a1la
n2apal
na2p1alk
na1p1a2pa
n2apap
nap1a1pá
nap1a1rá
na2p1as
na2pád
na1pá
na2p1á2g
na2pák
nap1áll
na2pám
na2p1árb
na2p1átm
nape2l
na1pe
na2p1ell
na2pe2m
nap1est
nape2s
na2p1ill
na1pi
na2p1ing
na2p1int
nap1isk
na2pí
nap2lat
nap1la
na2p1o1la
na1po
nap1orm
napos1s
na2p1os1tá
na2p1ott
na2p2ó.
na1pó
na2p1ó2r
napp2
2nap1po
nap1pr
n1ap1rí
2napróz
nap1ró
na2p1u2t
na1pu
na2p1úr
na1pú
2n1a2ra1i
na1ra
2n1a2rann
2n1a2ran2y.
naran2y
2n1a2rany2a.
nara1nya
2n1a2ra1nyá
2n1a2ranyb
2n1a2ranyh
2n1a2ranyk
2n1a2ranyn
2n1a2ranyr
2n1a2rany1s
2n1a2ranyt2
2n1ar2c.
2n1ar1cá
narchi2ab
narc2h
nar1c1hi
narchi1a
2n1ar1co
2n1ar1cu
2n1ar1cú
n1ar1ró
2n1arz
na2sév
na1sé
nas1isk
na1si
2nask
na1s2ka
na1s2rá
nast2
n2a1s2ta
n2a1s2tá
n2a1str
na2sz1an
nas2z
na1s1za
na2sz1árad
na1s1zá
naszá1ra
nas2zkés2z1
nasz1ké
nasz1ü2g
n2a1s1zü
n2a2t1ab
na1ta
na2t1aj
na2t1alk
na2t1alt
nat1a1rá
n2atar
nat1áll
n2atál
na1tá
na2t1á2ré
n2atár
na2t1árn
na2t1eg
na1te
nate3le
na2t1e1lé
nat1el1le
n2a2tél
na1té
nat1ér1ke
n2atér
na2t1érv
na2t1i2m
na1ti
na2t1ing
n2atin
na2t1old
na1to
nat1ors2
n2ator
na2t1os2z
na2t1u2t
na1tu
na2tül
na1tü
n2atű2z
na1tű
2n1a2t2y
na2uc
na1u
na2ul
nau2ra
na2u1rá
nau2s
na2u1to
naü2z
na1ü
na2vart
na1va
na2vat
2n1avv
na1wh
2n1azb
na2zé
2n1a2zo
1ná
2n1á2bé
2nábr
ná2caj
ná1ca
ná2c3h
ná2cí
ná2csal
nác2s
ná1c1sa
ná2csap
ná2cs1as
ná2c1se
nác3sik
ná1c1si
ná2csis
2n1á2csolat
ná1c1so
nácso1la
nác3sor
ná2c1sö
ná2c1sü
nác3s2z
ná2d1a1la
ná1da
ná2da1p
ná2d1a2r
ná2d1as2z
ná2d1a2v
ná2dá
nád1d
ná2d1e2
ná2d1ö
ná2dud
ná1du
ná2d1ü2
ná2d3z
ná2ga
ná2gá
ná2gi
ná2gu
ná2gú
ná2g2y
2n1á2hí
ná2k1é2r.
ná1ké
ná2kol
ná1ko
ná2kü
ná2lab
ná1la
n2á2l1a2l
ná2la1na
n1álar
nála2te
ná2l1az
ná2l1át
ná1lá
ná1le2
ná2l1eg
ná2l1el
ná2lem
ná2les
n2á1lé
ná2l1ép
ná2l1in
ná1li
ná2lir
nál2is
ná2lí
2n1ál2l.
2n1áll2a.
nál1la
2n1állap
2n1állat
2n1ál1lí
2n1állom
nál1lo
nállóköz1
nál1ló
nálló1kö
ná2lü
ná2mí
ná2mu
ná2nad
ná1na
ná2n1al
ná2nar
ná2n1á2r
ná1ná
nán2c1e
ná2n1e2
ná1né2
ná2nét
ná2nin
ná1ni
ná2ní
nán2se
ná2nü
ná2rad
ná1ra
2n1á2ra1ka
2n1á2rakb
2n1á2rakh
2n1á2rakk
2n1á2rakn
2n1á2ra1ko
2n1á2rakr
2n1á2rakt
2n1á2ram
ná2r1a2n
ná2rap
ná2ras
nár1ass
2n1á2ra2t.
ná2r1att
ná2r1av
ná2r1á2c
ná1rá
ná2r1ál
ná2r1e2
ná2r1é1ve
ná1ré
2n1á2ria1i
ná1ri
nári1a
2n1á2ri1á
ná2r1i2p
ná2rí
ná1ró2
ná2r1ór
ná2rő
nár1s2
nárt2
nár1tr
2n1á2rud
ná1ru
ná2rug
2n1á2ru2n.
ná2r1ur
2n1á2rus
ná2ru2t
nár1u1tá
ná2rút
ná1rú
ná2rü
ná2s1as
ná1sa
nás1á1ré
ná1sá
ná2s1á2ru
2n1á2sás
ná2s1e2
ná2s1i2k
ná1si
nást2
nás1tr
ná2sza2n
nás2z
ná1s1za
ná2sza2s
ná2szág
ná1s1zá
ná2szál
ná2s1ze
ná2szén
ná1s1zé
ná2szil
ná1s1zi
ná2szin
ná2szis
2n1ászk2a.
nász1k2
nász1ka
2n1ászoks
ná1s1zo
ná2s1z1ö
ná2s1z1ú
ná2s1z1ü
ná2ta1la
ná1ta
ná2t1a2n
ná2t1ál
ná1tá
nát1ásv
ná2t1e2
2n1át1hi
ná2t1i2o
ná1ti
2n1á2t1ir
2n1á2tí
2n1át1lé
ná2t1ö
2n1át1ru
2n1á2t1ug
ná1tu
2n1á2t1u2t
2n1á2tú
ná2tü
2n1át1vi
nba2ká
n1ba
nba2k1e2
n2b1é2kéb
n1bé
nbé1ké
n2b1é2kén
n2b1é2kér
n2b1é2kév
nb2la
nb2lo
nb2lú
nbo2n1a2
n1bo
nb2ra
n1b2ri
nb2ro
nb2ró
nburg2hi
n1bu
nc1a1cé
n1ca
n2c1a1já
nc1ajt
n2c1akn
n2c1akt
nc1a2la
nc1alj
n2c1alk
nc1alt
nc1alv
nc1a1na
nc1ant
nc1a2nya
ncan2y
nc1a1ri
nc1att
nca2u
n2c1a1va
n2c1ág
n1cá
nc1árb
n2c1árk
n2c1árn
nc1árt
nc1á2sa
nc1á1sá
nc1bl
nc1br
nc3c1si
nc2c2s
nc3c1so
nc3c1sö
nc3c1su
nc1dr
nce1a2
n1ce
nc1egg
n2c1eld
nc1e2lek
nce1le
nc1e2lem
nc1elm
n2c1elv
nc1e2red
nce1re
nc1e1ró
n2c1eszt
nces2z
n2c1etn
n2c1ex
ncé2g1ér
n1cé
ncé1gé
ncé2hes
ncé1he
n2c1ép
n2c1é1vi
nc1fl
nc1fr
nc1gr
n2c3ha
nc2h
n2c3há
n2ch2e.
n1c1he
nc3hel
nc3het
n2c3hé
nc3hi1á
n1c1hi
n1c3hí
nc3hol
n1c1ho
nc3hon
n2c3hoz
n2c3hó
n1c3hö
n2c3hu
n2c3hú
nci2alis
n1ci
nci1a
ncia1li
nci2a1so
n2c1i1ge
n2c1i2gé
n2c1i2ko
nc1i2má
n2c1i2n2a.
nci1na
n2c1in1d2
n2c1inf
n2c1ing
n2c1int
n2c1i1rá
nc1i1ro
n2c1ist
n2c1i2ta
n2c1i2z
nc1íj
n1cí
n2c1ír
n2c1ív
n2c1íz
nc1kl
nc1k1re
n2c1ob
n1co
n2c1ok1ta
n2c1o2li
n2c1orv
n2c1ott
n2c1öl2t.
n1cö
nc1öss
ncö2t
nc1őr
n1cő
nc1pl
nc1pr
n2cs1ab
nc2s
n1c1sa
n2csac
n2csad
n2cs1ag
n2cs1ajt
n2csakt
ncs1alap
ncsa1la
n2cs1alj
n2csam
n2csan
ncsa2p1á2g
ncsa1pá
ncsa2r
ncs1a1ra
n2cs1arg
ncs1a1ro
n2cs1a1u
n2csaz
ncs1ágr
n1c1sá
n2cság
n2cs1ál
n2c3sá2r.
n2cs1á2rá
ncs1árb
n2cs1árn
ncs1árr
n2csá1ru
n2c2sá1ta
n2csá1tá
n2c2sátv
n2c2s1elf
n1c1se
ncs1é2rés
n1c1sé
ncsé1ré
n2csér1te
n2cs1ér1té
n2c3sé1rü
n2c3sé1ta
ncs1i2kon
n1c1si
ncsi1ko
ncs1int
n2cs1i1o
n2csi1pa
n2csi1rá
nc2s1irt
n2cs1ism
n2csi1ta
ncs1izz
n2c3sí1ki
n1c1sí
n2cs1í2rá
n2cs1ízt
n2csob
n1c1so
n2cs1oks
n2c2sok1ta
n2c2sos2z
n2cs1ö2lé
n1c1sö
n2cs1ös
n2cs1öz
n2cs1ő2r.
n1c1ső
ncs1ő1rö
ncs1s
n2csur
n1c1su
ncsu2t
ncs1u1ta
n2csút
n1c1sú
n2csüg
n1c1sü
n2csüt
ncs3zár
ncs2z
nc1s1zá
nc3s1ze
nc3s1zó
nc3s1zö
nc1tr
n2c1ud
n1cu
n2c1ug
nc1uj
n2c1ur
n2c1új
n1cú
nc1út
n2c1üg
n1cü
n2c1üt
nc1üv
n2c1üz
n2c1űr
n1cű
nc1ya
n2c3zá
nc2z
n1c3ze
n2c3zó
n1c3zö
nc3z2s
n1c3zü
nczy1i
n1czy
nd1abl
n1da
nda1br
nd1a2dat
nda1da
nda2dás
nda1dá
nd1add
n2d1a1já
n2d1akc
n2d1akk
nd1akt
n2d1alj
n2d1alr
nd1ann
nd1a1pó
nd1a2rán
nda1rá
nd1arr
ndat1an2y
nd2atan
nda1ta
nd2a2tap
nda2t1eg
nda1te
nd2a2tin
nda1ti
nd2a2tir
nd1a1zo
nd1azt
nd1azz
nd1á2rak
n1dá
ndá1ra
ndás1s
nd1bl
nd1cl
nd1dr
nd1ebb
n1de
n2d1e2kéb
nde1ké
n2d1e2kéin
ndeké1i
nde2köz
nde1kö
n2d1elf
n2d1ellen
ndel1le
n2d1elm
n2d1e1lö
nde2mer
nde1me
nde2mu
nde2m1ü
nde2ná
n2dennek
nden1ne
nde1p2
nde2r1a
nde2rál
nde1rá
nde2ráz
nde2rel
nde1re
nde2ro
n2d1e2rő
n2d1e2sett
nde1se
n2d1e2sés
nde1sé
n2d1e2ső
nde2s1za
ndes2z
n2d1e2s1zü
n2d1e1za
ndé2go
n1dé
ndé2ke2l
ndé1ke
nd1é2kez
ndé2kö
n2d1é1le
nd1élm
n2d1ép
ndé2raj
ndé1ra
n2d1és2s2z2
n2d1észh
ndés2z
n2d1észl
n2d1észr
n2d1é2te
n2d1étt
nd1fr
nd1gr
n2d1i1ga
n1di
n2d1i1ge
n2d1ill
n2d1i2n2a.
ndi1na
n2d1ing
n2d1ins
n2d1i2onj
ndi1o
ndi2ó1é2
ndi1ó
ndi2óf
ndi2óm
n2d1i1s1za
ndis2z
ndí2j1a2da
n1dí
ndí1ja
nd1kl
n2d1o1á
n1do
ndo2k1ú2t
ndo1kú
ndo2rál
ndo1rá
n2d1or1ni
ndo2r1ú
n2d1o1u
n2d1ov
ndóé2r
n1dó
ndó1é
nd1ó2ni
n2d1ó2rá
ndö2b
n1dö
nd1öss
n2d1ö2z
n2d1ő2r.
n1dő
n2d1ő2r1a2
n2d1őrb
n2d1őrc
n2d1őrd
nd1ő2reg
ndő1re
nd1ő2re1i
nd1ő2rek
nd1ő2rel
n2d1ő2rék
ndő1ré
n2d1ő2rén
nd1ő2rér
nd1ő2rét
nd1ő2rév
n2d1őrf
n2d1őrg
nd1ő2rig
ndő1ri
nd1ő2r1is
nd1ő2rit
n2d1őrj
n2d1őrk
n2d1őrl
n2d1őrm
n2d1őrn
n2d1őrok
ndő1ro
n2d1őros
n2d1ő2rö
nd1ő2r1ő2s
ndő1rő
n2d1őrp
n2d1őrr
n2d1őrs
n2d1őrt
n2d1ő1rú
n2d1őrv
nd1pr
nd2raz
nd1ra
n1d2ruk
nd1ru
nd1sl
nd1sp
nd1st
ndszá2m1út
nds2z
nd1s1zá
nd3szá1mú
nd3sze2r1e2l
nd1s1z2e
ndsze1re
ndtár2s1a2d
nd1tá
ndtár1sa
ndu2t
n1du
n2d1u1ta
nd1új
n1dú
ndú2rá
nd1ús2z
n2d1üg
n1dü
nd1ünn
n2d1üz
n2d1ű2r.
n1dű
n2d1űrr
n2d1űrt
n2d1űz
ndy2b
ndy2h
ndy2n
ndy2r
ndy2t
ndy2v
nd3zav
nd2z
n1d1za
nd3zár
n1d1zá
n2d3ze
n2d3zó
n2d3zu
1ne
nea2j
ne1a
nea2k
nea2la
ne2a1lo
nea2n
nea2r
ne2bé
ne1bl
ne1d1ra
ne1d2rá
ned2v1el
ned1ve
ne1dy
2n1e2d1zé
ned2z
2neff
2n1e2ger
ne1ge
2n1e2gé2r.
ne1gé
ne2g1ö
n1eg2y.
neg2y
n1egyb
ne2gyek
ne1gye
ne2gyen
ne2gyes
ne2gyet
ne2gyez
2n1e2gyé
n1egyf
n1egyh
ne2gyig
ne1gyi
n1egyk
n1egym
n1egyn
n1egyr
2n1egys
n1egyt
ne2gyün
ne1gyü
nehéz1
ne1hé
2n1ehh
nei2g
ne1i
neí2r
ne1í
ne2k1aj
ne1ka
ne2k1a2n
ne2kát
ne1ká
ne2k1e2g
ne1ke
nek1e1rő
nek1é2jé
ne1ké
ne2kék
nek1ékn
neki1e2
ne1ki
2nekj
nek1k2
2nek1ka
2nek1ki
ne1k1lu
ne2k1ok
ne1ko
nekö2r
ne1kö
ne1kre1á
nek1re
nek1t2
ne2k1üg
ne1kü
nek1ül1dö
ne2lag
ne1la
ne2l1a2j
ne2l1an
ne2lál
ne1lá
nelá2r
ne2lef
ne1le
ne2leg
n1eleg2y.
neleg2y
ne1l1e2le
nele2ma
nelem1el
nele1me
nel1e2més
nele1mé
nel1eng
ne2ler
ne2l1ép
ne1lé
nel1fé2
nel1g2
2n1el1ha
ne2l1id
ne1li
ne2lim
ne2l1in
n1el1ló
2n1elnev
nel1ne
ne2l1ot
ne1lo
ne2l1ó2
ne2l1ö2
2n1e2lő1a
ne1lő
2n1e2lő1á
ne2lőd
ne2lőf
ne2lő1hí
2n1e2lő1í
2n1e2lő1ké
ne2lől
2n1előn2y
2n1e2lőrej
nelő1re
2n1e2lő1té
2n1eltér
nel1té
ne2l1ül
ne1lü
2n1elz
ne2mak
ne1ma
2nem1ba
2n1ember
nem1be
neme2g
ne1me
nem1eg2y
2neme1le
2n1e2melk
ne2m1es2z
ne2m1é2r.
ne1mé
ne2m1id
ne1mi
nem1is2z
2nems
2nemul
ne1mu
2n1eng
2n1enn
nen2sa
nense2s
nen1se
nen2s1e1se
nens3s
nen2s3z
2n1enyv
nen2y
2n1enz
ne2ob
ne1o
ne2od
ne2of
ne2og
ne2oh
ne2o1ko
ne2o1la
ne2o1li
ne2o1ro
ne2pad
ne1pa
ne2pelm
ne1pe
ne2p1est
ne2pid
ne1pi
ne2p1ó2r
ne1pó
ne2p1ut
ne1pu
ne2r1a2d
ne1ra
ne2r1a2k
ne2r1a2n
ne2r1a2r
ne2r1as
ne2raz
ne2ráb
ne1rá
ner2co2
ne2r1e2g
ne1re
n1e2rej
ne2r1e2k2e.
nere1ke
ne2r1e2l
ner1e2mel
nere1me
ne2r1er
ne2rez
ne2rég
ne1ré
ne2r1él
ne2r1ép
ne2r1étt
ne2r1id
ne1ri
ne2r1i2ga
ne2r1il
ne2r1i2m
ne2r1inf
ne2r1ing
ne2r1int2
ne2ris
ner1k2
ne2r1ol
ne1ro
ne2ror
ne2r1os
ne2ró
ne2r1ön
ne1rö
2n1e2rő
3n2e3rő1kü
ner1s
ner1tr
ne2r1u2
ne2r1üg
ne1rü
ne2s1al1já
ne1sa
ne2sas
ne2s1ál
ne1sá
ne2sár
2n1e2setb
ne1se
2n1e2setr
2n1e2sés
ne1sé
2n1e2ső
nes3s1za
nes2s2z
nes3s1zá
2n1es1té
2n1es1ti
ne2s1ü2v
ne1sü
nesz1ál
nes2z
ne1s1zá
ne2s3zár
ne2sz1él
ne1s1zé
ne2s1z1ű2
ne2t1ab
ne1ta
net1a2la
ne2t1a2n2y
ne2tál
ne1tá
ne2t1át1
ne2t1e2g
ne1te
net1e1lá
net1elm
ne2t1elő1a
nete1lő
ne2t1eml
ne1t1es1te
ne2t1es2z
ne2t1etet
nete1te
ne2t1e1ti
ne2t1é2k
ne1té
ne2t1é2l
ne2t1é2r.
ne2t1érd
ne2t1é1ré
ne2t1é2r2ő.
neté1rő
ne2t1é2rők
ne2t1érr
ne2tér1te
ne2t1ér1tő
ne2t1é2rü
ne2t1és2z
ne2t1é2v2e.
neté1ve
ne2ti2d
ne1ti
ne2t1i2ko
ne2t1int
ne2tip
netké2s1z1ü
net1ké
netkés2z
ne2t1o2k
ne1to
ne2tol
net1old
ne2t1ő2
net2tév
net1té
ne2tun
ne1tu
ne2t1ut
netü2l
ne1tü
net1ü1lé
ne2t1ü2z
2n1e2vő
2n1evv
nexpor2t1ő2
nex1po
ne1yé
2n1e2zer
ne1ze
2n1ezred
nez1re
2n1e2züs
ne1zü
1né
2n1ébr
2nédl
né1f2r
2n1é2g.
2n1é2gek
né1ge
2n1é2ge1té
2n1é2get2ő.
nége1tő
2n1é2getőh
2n1é2getők
2n1é2getőn
2n1é2getőt
2n1é2gé
2n1égj
2n1égn
2n1é2gő
2n1égs
2n1égt
2n1é2gü
né2gyer
nég2y
né1gye
né2gyén
né1gyé
né2gy1o2
né2gyök
né1gyö
2n1é2hes
né1he
2n1éhs
né2kaj
né1ka
né2kak
né2k1a2n
né2kar
né2k1á
néke2l
né1ke
nék1e1le
né2ker
né2kév
né1ké
né2ki2d
né1ki
nék1is2z
2n1é2kí
né2kó
né1kü2
né2kül
n1é2les
né1le
2n1é2let
2n1élm
né2lő1i
né1lő
né2lőn
2néne1ke
né1ne
2n1é2ne1ké
né1pa2
né2pad
né2p1ak
né2p1al
né2p1an
né2pap
né2p1as
né2pa1u
né1pá2
né2p1ál
né2p1ár
né2pát
né2p1áz
né2peg
né1pe
n2é2p1e2l
né2p1e2r
nép1etet2
népe1te
né2p1etn
né2pev
né2p1és
né1pé
nép1f2
2né2pí
né2p1o
né2p1ö
né2pő
nép1s
né2p1u2s
né1pu
né2pú
2népül
né1pü
né1ra2
né2raj
né2r1an
n1érd
2nér1de
2n1é2rem
né1re
né2r2é.
né1ré
né2rés
né2r2i.
né1ri
né2rin
né2rip
né2rö
2n1é2r2ő.
né1rő
2n1ér1té
né2rü
2n1érz
né2s1e2l
né1se
né2s1ég
né1sé
nés3szer
nés2s2z2
nés1s1ze
nész1ak
nés2z
né1s1za
nész1al
n2é2s1z1á
né2szeg
né1s1ze
né2sz1e2l
né2sz1emb
né2s2z1e2s2z
né2sz1ék
né1s1zé
né2szik
né1s1zi
né2s1z1í
nés2zkés2z1
nész1k2
nész1ké
né2s1zó
n2é2s1z1ö2
né2s1zu
né2s1z1ú
né2szül
né1s1zü
né2t1eg
né1te
2n1é2tel
né2t1es
2n1é2tet
né1ti2
né2tir
né2tö
né1t2r
né1va2
név1a1da
né2vad
név1a1dá
né2vaj
né2var
né2vav
né2v1ág
né1vá
2n1év1bú
2n1é2v2e.
né1ve
2n1é2ve1i
2n1é2vek
néve2l
né2v1e1le
né2v1e1lő
2né2vem
2n1é2ven3k2
2n1é2vent
né2v1e2r
né2ves
név1es2z
2né2vet
2névéb
né1vé
né2v1é2l
né2v1é1ri
né2vé1rő
né2v1érz
né2vis
né1vi
2n1évn2y
né2v1o
né2vö
né2vő
né2v1u2
né2vú
né2vün
né1vü
né2za
néziu2m1i2
né1zi
nézi1u
nfe2le1mé
n1fe
nfe1le
n1f2la
n1f2lo
nfluo1r1e2
nf1lu
nflu1o
nfol2ta
n1fo
n1f2rak
nf1ra
n1f2rá
n1f2rek
nf1re
n1f2ri
n1f2rí
n1f2rö
n1f2ru
nfüs2t1ö1lé
n1fü
nfüs1tö
n2g1abl
n1ga
n2g1a2dat
nga1da
n2g1a2dá
ng1a2dó
n2gad2ó.
n2gadó1a
n2gadób
n2gadó1i
n2gadój
n2gadók
n2gadór
n2gadós
n2gadót
n2gadóv
ng1akn
n2g1akr
n2g1akt
nga2lag
nga1la
nga2lak
nga2lap
ng1alát
nga1lá
ng1alel
nga1le
n2g1alg
n2g1alj
n2g1a2n2y
nga1p2
n2g1a2rán
nga1rá
n2g1arc
n2g1art
n2g1arz
n2g1asp
ng1as2s2z
n2g1aszt
ngas2z
nga1tr
n2g1a2u
n2g1a1va
n2ga2zon
nga1zo
ngá2c
n1gá
n2g1á2g
ng1ál1lá
ng1ál1lo
ng1ál1ló
n2g1áp
ng1á2rak
ngá1ra
ng1á2ras
ng1á2ra2t
ngá2rát
ngá1rá
ngá2ráv
ngá2ré
n2g1ár1nya
ngárn2y
n2g1ár1ta
ngá2ruk
ngá1ru
n2g1á2rú
n2g1á2szaib
ngás2z
ngá1s1za
ngásza1i
n2g1á2szair
n2g1á2szait
n2g1á2száv
ngá1s1zá
n2g1á2s1zé
n2g1á2sz2i.
ngá1s1zi
n2g1á2szig
n2g1á2szo1ké
ngá1s1zo
n2g1á2szos
n2g1á1tá2
ng1átc
n2g1á2t1e2
n2g1á2ti
n2g1átk
n2g1átl
n2g1átm
n2g1áts
n2g1átv
n2g1á2zój
ngá1zó
n2g1á2zós
n2g1ázta1to
ngáz1ta
ng1bl
ng1br
ng1d2r
ng1ed2z
n1ge
nge2gés2z1
n2g1e2gé
n2g1e1la
nge2lis
nge1li
nge2r1a
nge2rál
nge1rá
nge2r1e2s2z
nge1re
nger2im
nge1ri
nge2ro
n2g1esem
nge1se
n2g1eszk
nges2z
nge2ti1ka
nge1ti
n2g1ex
n2g1é2g
n1gé
n2gé2les
ngé1le
n2g1é2pí
n2g1érc
n2g1érl
n2g1é2r2ő.
ngé1rő
n2g1ér1té
ngé2sa
n2g1é1te
ng1fr
ng1g2r
ng3gyi
ng2g2y
ng3gyo
n2g1i2d
n1gi
ngi2g
n2g1i1ga
n2g1i1ge
n2g1i2gé
ng1i2ko
n2g1ikr
n2g1ill
ngi2m
ng1i1mi
n2g1inf
n2g1ing
n2g1ins
n2g1i1ro
n2g1izg
ng1íg
n1gí
ng1ír
ng1ív
ng1íz
ng1kl
ng1kr
ng1kv
n1glec
ng1le
ngmeg1
ng1me
n1g2nó
n2g1of
n1go
n2g1op
ngo1ra1
n2g1ord
n2g1org
n2g1ork
n2g1os1to
ng1oszt
ngos2z
n2g1otth
ngó2ri1á
n1gó
ngó1ri
n2g1öb
n1gö
n2g1önt
ngö2r
ng1ö1rö
n2g1örv
n2g1öv
n2g1ö2z
ng1ő1rö
n1gő
ngő2z1ő2s
ngő1ző
ng1pr
ng1ps
n1g2ram
ng1ra
ng2rádih
ng1rá
ngrá1di
ng2rádj
n1g2ráf
ng2run
ng1ru
ng1sh
ng1sk
ng1sp
ng1tr
n2g1ud
n1gu
n2g1ug
n2g1uj
n2g1und
ng1u2ra
n2g1u1ta
n2g1új
n1gú
n2g1útt
n2g1üd
n1gü
n2g1ü2g
ng1ür
ng1üt
n2g1üz
ng1űr
n1gű
n2gy1a2gya
ng2y
n1gya
ngyag2y
ngya2l1ó2
ngy1ass
n2gy1á2l
n1gyá
n2gy1em
n1gye
n2gy1es
n2gyez
n2gy1é2d
n1gyé
ngy1éks
ngy1ékt
n2gy1é2r.
n2gyid
n1gyi
n2gyim
n2gy1ut
n1gyu
n2gy1ü2lő
n1gyü
nha2b1i
n1ha
nhal1k2
nha2sábr
nha1sá
nhá2z1alt
n1há
nhá1za
nhá2zip
nhá1zi
nhá2zol
nhá1zo
nhá2zó
nhá2z3s2
nhe2d3z
n1he
nhe2i
nhú2sá
n1hú
nhús3s
1ni
ni2a1a
ni1a
ni2a1á
ni2a1bo
ni2ac
ni2ad
ni2a1e
ni2a1é
ni2a1fo
nia1g2
ni2ag2y
ni2ah
ni2a1í
ni2aj
ni2a1la
ni2a1lá
ni2am
ni2a1o
ni2a1ó
ni2a1ö
ni2a1ő
ni2a1p
ni2ar
ni2a1s1za
nias2z
ni2a1s1zá
nia1t2
ni2a1to
ni2atr
nia3u
ni2a1ü
ni2av
ni2az
niá2t1a2
ni1á
2n1i2bo
ni1br
ni2c1e2l
ni1ce
ni2c1ha
nic2h
ni2c3he
ni2c3hé
ni2c3ho
ni2d2e.
ni1de
2n1i2deg
2n1i2dő
ni2dü
ni2et
ni1e
2n1ifj
2n1i2gal
ni1ga
2n1i2ga2z.
2n1i2gá
ni2g2e.
ni1ge
ni2géj
ni1gé
2n1i2gén
ni2géz
2nigm
2n1ihl
ni2keb
ni1ke
ni2k1el
ni2k1em
ni2k1ér1té
ni1ké
nikk2
ni1k1lu
ni2konr
ni1ko
2n1ikri2t.
nik1ri
ni2kud
ni1ku
n1il1le
2n1il1lu
2n1i2l2y
2n1i2mád
ni1má
n1i2má1é
2n1imp
2n1i2n2a.
ni1na
ni2nas
ni2n1áll
ni1ná
2nind
2n1in1fo
2n1in1fú
nin2gas
nin1ga
nin2gá
2n1ing2e.
nin1ge
2n1inge1i
nin2g1e2l
nin2g1ó2
nin1g2rá
nin2gu
2n1ingük
nin1gü
ni2n1i2p
ni1ni
ni2nol
ni1no
2n1in1té
2n1i2onb
ni1o
ni2onc
ni2onh
ni2onj
ni2on1k2
2n1i2onn
2n1i2o1no
2n1i2onr
2n1i2ont
ni2ó1a
ni1ó
ni2ód
ni2ó1e
ni2óp
ni2ó1ta
ni2ó1tá
ni2ó1ü
nió1vá2
nip2p1i
ni1pr
ni2rat
ni1ra
2ni1rá
nirés2z1
ni1ré
2n1irg
2n1irh
2n1irk
2n1i2rod
ni1ro
ni2rón
ni1ró
ni2s2i.
ni1si
ni2s1in
nisü2v
ni1sü
nisz1ak
nis2z
ni1s1za
ni2szeg
ni1s1ze
ni2s2zeml
ni2sze1se
ni2sz1é2l
ni1s1zé
ni2szip
ni1s1zi
ni2szis
nisz1okt
ni1s1zo
nisz1ol
2n1iszon
ni2s1zö
ni2s1z1ő
ni2s1zu
ni2t1a2d2
ni1ta
ni2t1ag
ni2t1aj
ni2tal
nit1a2la
ni2t1as
2n1i2tat
nit1ell
ni1te
ni2t1ép
ni1té
ni2t1ér
ni2tim
ni1ti
ni2t1in
ni2tir
ni2to2b
ni1to
nit1old
nit1olt
n2i2t1os2z
ni2tür
ni1tü
niu2m1i2o
ni1u
niu1mi
2n1i2vad
ni1va
2n1i2var
2n1i2vó
ni2xa
ni2xő
ni2zén
ni1zé
2n1izg
2n1iz1má
n1izom
ni1zo
ni2zsol
niz2s
ni1z1so
1ní
ní2gé
ní2ja
ní2ju
níli2a
ní1li
ní2ra2
2n1í2rá
ní2r1é2
ní2r1ot
ní1ro
2n1í2ró
ní2r1ú
2n1í2tél
ní1té
nítő1a2
ní1tő
ní2ve
2n1í2vi
ní2ze
ní2zű
nk1a2dós
n1ka
nka1dó
nkai2k
nka1i
nk1ajt
n2k1akk
n2k1alv
n2k1a1nó
nka2nyá
nkan2y
nka1ó2
nka1p2l
n2k1app
nka2ris
nka1ri
nka1s2k
nka1sp
nka2tom
nka1to
nka1t2r
nk1a1zo
n2k1ág
n1ká
n2k1á2rad
nká1ra
nká2rál
nká1rá
nká2rol
nká1ro
nká2ruk
nká1ru
nká2sad
nká1sa
nká2sal
nká2sav
nkás3s
nká2s3z
nká1ta2
n2k1átj
n2k1átm
n2k1áts
n2k1á1tu
nk1br
nkci2ó1sű
nk1ci
nkci1ó
nk1dr
nk1e2c1se
n1ke
nkec2s
nk1e2d2z
nk1e1la
n2k1el1lá
n2k1el1tá
nke2r1a
nk1ered
nke1re
n2k1e2rő
n2k1e2ti
n2k1e2vé
n2k1é2l
n1ké
nk2é2p1el
nké1pe
nké2p1és
nké1pé
n2k1é2pí
n2k1érc
nk1é2s1zé
nkés2z
nk1g2r
nki1a2
n1ki
nki1á2
n2k1i2ga
n2k1i1gé
n2k1i1mi
n2k1ind
n2k1ing
n2k1int
n2kinz
nk1i2on
nki1o
nki2s1i2
n2k1ism
nk1ká2
nk1k2r
nk2lar
nk1la
n1k2ló
n2k1ob
n1ko
n2k1o1ke2
nkos3s
n2k1oszl
nkos2z
n2k1ox
n2k1ó1né
n1kó
n2k1ó1ni
nkó1p2
n2k1ó2ri
n2k1ö2lé
n1kö
n2k1ö2lő
nk1öss
nk1ötl
nk1őr1s2
n1kő
nk1pl
nk1pr
nk2rac
nk1ra
n1k2ris
nk1ri
n1k2rí
nk2ro1ma
nk1ro
nkron1n
nk1sp
nk1st
nk1s2z2
n2k1ud
n1ku
n2k1u2ra
n2k1u2s
nk1utal
nku1ta
n2k1u1tá
n2k1uz
n2k1új
n1kú
n2k1ús
n2k1üg
n1kü
nlac1
n1la
nla2pa
nla1p1e
nla2p1os2z
nla1po
nla2tal
nla1ta
nl2a2t1a2n
nla2t1e2
nla2t1é2te
nl2atét
nla1té
nlás3s
n1lá
nle2g1á
n1le
nle1í2
nle2t1o
nle1tü2
nle2tüz
nlé2tés
n1lé
nlé1té
nlé2t1é2v2e.
nlété1ve
nme2g1a2
n1me
nme2g1é
nműé2n
n1mű
nmű1é
nműt2
nna2i1é
n1na
nna1i
nnak2
nna1kr
nn1alv
nna1p2ré
nna1s2
nn1áll
n1ná
n2n1eml
n1ne
nne2p1a2
nne2se
nn1ess
nn1es2z
n2n1e2tet
nne1te
n2n1ett
nn1evez
nne1ve
nné2get
n1né
nné1ge
nn1é1ri
n2n1id
n1ni
nn1irt
nn1or1s2
n1no
nnőé2h
n1nő
nnő1é
nnőé2n
nn1sy
n2n1ug
n1nu
nn1ú2s
n1nú
n2n1ü2c
n1nü
nnü2l
nn1ü1lő
nn1ült
nn1ülv
n2n2y
n3nyak
n1nya
n3nya1lá
nny1a2n
n3nyar
nnyá2r
n1nyá
nny1áz
n3nydr
nny1ell
n1nye
n3nye1lő
nny1elt
nny1el1vá
nny1elvez
nnyel1ve
nny1e2sett
nnye1se
nny1e2sés
nnye1sé
nny1e2ső
nny1ég
n1nyé
nny1é2ké
nny1é2ki
nnyié2h
n1nyi
nnyi1é
nnyié2ne
nnyi2g
nny1i1gé
n3ny1jé
nny1old
n1nyo
nny1on
nny1öz
n1nyö
n3nys2t
1no
no1d2rá
2n1o2dú
2no2g.
2nogh
2nogj
2nogn
2no1go
2nogs
2nogt
2nogv
no2ir
no1i
2nokal
no1ka
nok1a1la
no2k1a2r
no2ka2u
no2k1ál
no1ká
no2k1é2l
no1ké
no2ké2p
no2k1ing
no1ki
nok1ist2
nok1k2
2n1ok1ke
2n1o2koz
no1ko
no2kö
no2kő
no1k2ro
nok1s
noks2z2
no2kur
no1ku
no2kúr
no1kú
no2kü
2n1o2la
nol1f2
2n1o2lim
no1li
2n1ol1ló
2n1o2l2y
no2m1a2c
no1ma
nom1p
no1na2
no2n1al
nonc3c
non2c2h
nonc3s2z
nonc2s
no2n1e
non1k2
no2nö
no2nő
non1s2
no1n2y
no2ok
no1o
2n1o2pe
no1p2la
no2r1al
no1ra
no2r1a2t
no2raz
no2r1e2l
no1re
no2r1iv
no1ri
no2rí
2n1or1mo
2n1or1ré
nor1s2
no2rü
2n1or1vo
no2sál
no1sá
no2se
nos2s2z2
nos3s1ze
nos3szf
nos3s1zi
no1s2tab
nos1ta
nosza2u
nos2z
no1s1za
no1sz2f
2noszl
no1t2r
2n1otth
no1t2y
no2u1i
no1u
2n1o2v2i.
no1vi
no2xi
1nó
nóa2k
nó1a
nóá2r
nó1á
nó2ce
nó2c2h
nó2d2a.
nó1da
nó2d1a2n
nó2dák
nó1dá
nó2d1e2s
nó1de
nó2d1is
nó1di
nó1fl
nó1fr
nó1k2l
nó2mac
nó1ma
nó2m1em
nó1me
n2ó2mi2k
nó1mi
nó2m1u2t
nó1mu
nó2mü
nó2nib
nó1ni
nó2non
nó1no
nó1p2r
n1ó2rac
nó1ra
nó2r1ad
n1ó2raf
2n1ó2ra1i
nó2r1a2l
n1ó2rar
n1ó2ras
n1ó2rat
nórá1di2
nó1rá
nó2rás
nó2ri1á
nó1ri
nó2rü
nós1akk
nó1sa
nó2seg
nó1se
nó1sl
nó1s2p
nó1s2rá
nós3s
nó1s2ta
nó1s2z2
nós3ze1ne
n2ószen
nó1s1ze
nós3ze1né
nót1a1la
nó1ta
nó2til
nó1ti
nó1t1rá
nó2vó
1nö
nö2ka
nö2ká
nö2k1e2l
nö1ke
nöke2t
nök1e1ti
nö2k1é2j
nö1ké
nö2k1ék
nö2k1é2l
nö2k1é2r.
nö2k1é1ri
nö2k1ér1té
nö2ko
nö2kó
nö2ku
nö2kú
n1ö2le
n1ö2lé
nö2lő
n1öml
2n1ö2nö
2n1önz
nö2rö
2n1ös2s2z
2n1ö2s2z
nö2te
nö2té
nö2ti
n1ötl
nöt1t2
nö2tü
2n1ö2v.
n1övb
n1ö2v2e.
nö1ve
nö2vön
nö1vö
2n1övr
2n1ö2zön
nö1zö
1nő
nőa2l
nő1a
nőa2n
nőá2g
nő1á
nő1br
nő2ca
nő2c2h
nő2csár
nőc2s
nő1c1sá
nő2csős
nő1c1ső
nő2c1sü
nőe2r
nő1e
nőé2l
nő1é
nőfé2l1é2
nő1fé
nő1kl
nő1pl
nő1pr
2n1ő2r.
2n1ő2r1a2n
nő1ra
2n1ő2r1a2s
2n1őrb
2n1őrc
2n1ő2re1i
nő1re
2n1ő2réh
nő1ré
2n1ő2rén
nő2ré2t.
nő2ré1tő
2n1ő2rév
2n1őrg
2n1őrh
2n1ő2ri
2n1őrk
2n1őrl
2n1őrn
2n1ő2rö
2n1őrr
n1őrs
2n1őrt
2n1ő1rü
nő2rül
nő2rün
2n1őrv
2n1őrz
nő2s1a2l
nő1sa
nő2s1e2l
nő1se
nő2ses
nő2s1í2r
nő1sí
nő2so2k
nő1so
nő1s1pe
nős3s
nő1sz2t2
nős2z
nő1t2r
nőt2tin
nőt1ti
nőu2t
nő1u
nőü2l
nő1ü
npa2dal
n1pa
npa1da
npe2s
n1pe
npes2z1
np2la
np2lá
np2le
np2lé
np2lo
np2lü
npon2t1a2
n1po
npo2r1a
np2ra
np2re
np2ré
np2ri
np2ro
np2ró
np2s2z
npu2t1a
n1pu
npu2t1á2
npu2t1e2
npu2t1i
nrefle2x1í2
n1re
nref1le
nren2da
n2s1a2d
n1sa
n2s1akc
ns1alk
ns1a2rá
ns1ass
n2s1a1u
ns2a2vár
nsa1vá
nsa2v1e2
nsa2vil
nsa1vi
nsa2vol
nsa1vo
n2s1a2z
nság1g
n1sá
ns1áll
n2s1á2rak
nsá1ra
n2s1á1ta
n2s1átv
ns2c2h
nsc3h2e.
ns1c1he
nsc3he1i
ns1c3hé
ns1dr
ns1e2lé
n1se
ns1elm
ns1eln
ns1e1lo
ns1els
ns1elv
n2s1e1ne
n2s1es2z
nsé2gel
n1sé
nsé1ge
nsé2g1éj
nsé1gé
nségü2két
n3sé1gü
nségü1ké
n2s1ék
n2s1é2l
n2s1ép
n2s1é2v2e.
nsé1ve
ns1fr
n2s1i2d
n1si
n2s1imp
n2s1inf
n2s1ing
n2s1i1ró
ns1isk
nsi2z
ns1i1zo
n2s1í2r
n1sí
n2s1í2v
n1s2kál
ns1ká
ns1kl
n2s1ob
n1so
n2s1ol
n2s1op
n2s1os2z
n2s1ott
n2s1ó2r
n1só
n2s1ös
n1sö
ns2pec
ns1pe
ns1p2l
ns2por
ns1po
n1s2rá
ns1st
ns1sy
ns3szer
ns2s2z
ns1s1ze
ns3s1zi
ns3s1zo
ns3s1zö
n1s2tab
ns1ta
n1s2tác
ns1tá
nste2i
ns1te
n1s2tim
ns1ti
ns2top
ns1to
nsu2r
n1su
n2s1u1ra
n2s1u2t
ns1úr
n1sú
n2sz1a2d
ns2z
n1s1za
nsza2k1ü2
nsz1alk
n2sz1a2n
ns3za1rá
n2sz1á2b2a.
n1s1zá
nszá1ba
ns3zá1rá
nsz1á2ru
n2sz1it
n1s1zi
n2sziz
n2sz1omm
n1s1zo
nsz1p2
n2szut
n1s1zu
n2sz1ü2z
n1s1zü
nsz1z
nt1ab1la
n1ta
n2t1abr
nta2cél
nta1cé
ntad2
n2t1a2dó
nt1a2g2a.
nta1ga
n2t1agg
nta2gyu
ntag2y
nta2gyú
ntai2k
nta1i
n2t1ajk
n2t1ajt
n2t1akc
n2t1ak1tá
nt1alát
nta1lá
nt1alel
nta1le
n2t1alf
n1t1an1ta
nt1a2r2a.
nta1ra
nta2ran
n2t1a2rá
n2t1arc
n2t1ark
nta1s2p
n2t1as2s2z
n2t1at2y
nt1a2u1ra
nta1u
nta1ü2
n2t1a1zo
n2t1ábr
n1tá
ntá2c2s
nt1á1c1si
nt1á1c1so
ntá2r1a2d
ntá1ra
n2táram
ntá2ráv
ntá1rá
nt1árn2y
ntá2ruk
ntá1ru
n2t1á2só
n2t1ás1vá
n2t1á1ti
n2t1átl
n2t1átr
n2t1áts
n2t1átv
ntá2z1si
ntáz2s
nt1bl
nt1br
nt1dr
nt1e1be
n1te
n2tedén
nte1dé
nt1e1di
nte3gá
n2t1e1la
n2t1elb
ntele2mé
nte1le
nt1elf
n2t1el1já
n2t1elk
n2t1ellen
ntel1le
n2t1elmél
ntel1mé
n2tel1nö
n2t1e2lo
nte2lő1á
nte1lő
n2t1elr
n2t1el1to
n2t1el1vá
n2t1elz
n2t1ember
ntem1be
n2t1e2mel
nte1me
n2t1eml
n2t1e1mu
n2t1endr
n2t1ent
nte2rál
nte1rá
nte2re1le
nte1re
nte2r1in
nte1ri
nter2v1e2l
nter1ve
n2t1erz
n2t1esth
n2t1eszk
ntes2z
n2t1e1va
nt1e2vet
nte1ve
nt1e2vez
n2t1é2g.
n1té
n2t1é2gé
n2t1é2kek
nté1ke
nté2kes
nté2ké
n2t1éks
n2t1é2le
n2t1é2lés
nté1lé
n2t1élm
n2t1élt
n2t1é2lű
n2t1é2ne1ke
nté1ne
n2t1é1pü
n2t1érin
nté1ri
n2t1ér1mé
n2t1ér1té
n2t1érz
ntés3s
nté2ter
nté1te
n2t1é2ven
nté1ve
n2t1é2vet
n2t1é2véb
nté1vé
n2t1é2vén
n2t1é2vér
n2t1é2vét
n2t1évh
n2t1évk
n2t1évt
nt1fl
nt1fr
nt1gr
nt2hon
nt1ho
ntia2n
n1ti
nti1a
ntia2t
nt1i1do
n2t1i1ge
nti1k2l
ntil2lá2t.
ntil1lá
n2t1il1le
n2t1imp
n2t1in1fo
n2t1in2g.
n2t1in1ga
nti1n2k.
n2t1in1té
nti1ó1
nti2par
nti1pa
n2t1i1rá
n2t1i1ro
n2t1isk
n2t1ism
n2t1is1te
nti2vás
nti1vá
nt1i1zo
n2t1íg
n1tí
n2t1íj
nt1í1rá
n2t1ívb
n2t1í2z
nt1kl
nt1kr
n2t1of
n1to
nto1ka2
n2t1o2k1al
n2t1okl
n2t1ol1da
n2t1ol1dó
n2t1o2l2y
nto2m1e2
n2t1opc
nto2ras
nto1ra
nto2rék
nto1ré
nto2rin
nto1ri
nt1or1má
nt1or1ro
n2t1oszl
ntos2z
n2t1oszt
n2t1otth
ntó1p
n1tó
n2t1ó2rá
n2t1ó2ri
ntót2
ntó1tr
nt1ökl
n1tö
nt1ö2kö
nt1ö2lő
nt1önt
n2t1örd
ntő1a2
n1tő
ntőé2n
ntő1é
nt1őrb
n2t1őrl
nt1őrn
n2t1őz
nt1pl
nt1pr
nt2rans
nt1ra
ntrans2z1
ntranszk2
n1t2réf
nt1ré
n1t2róf
nt1ró
nt1ry
nt1sh
nt1sk
nt1sp
nt1st
nts2z2
nt3szá2m1é
nt1s1zá
nt1szv
nt1t2r
n2t1udv
n1tu
n2t1ug
n2t1uj
ntu2mor
ntu1mo
ntu2n
n2t1u1na
nt1und
ntu1n1i
nt1u2rá
ntu2s1za
ntus2z
n2t1u1tá
n2t1úg
n1tú
n2t1új
ntú2ral
ntú1ra
ntú2ran
nt1ú2s2z
n2t1üg
n1tü
n2t1ü2lő
nt1ült
n2t1üt
n2t1ü2v
n2ty1a2l
nt2y
n1tya
n2ty1a2n
n2tyál
n1tyá
n2ty1e2l
n1tye
n2ty1él
n1tyé
n2ty1ik
n1tyi
n2ty1int
n2ty1iv
n2tyí
n2ty1ő2r
n1tyő
n2tyut
n1tyu
1nu
n1ucc
nu2ga
nu2go
2n1ujj
nu1k2la
nu1k1lu
nu2mü
2n1und
2n1u2ni
2n1u2no
2n1unt
nu2ram
nu1ra
nu2rá
nu2sal
nu1sa
nu2sas
nu2s1av
nu2s1e
nu2s1ér1té
nu1sé
nu2sik
nu1si
nu2sol
nu1so
nu1s2po
nuss2
nus3s1zi
nus2s2z
nu2szab
nus2z
nu1s1za
nu2s3zav
nu2szir
nu1s1zi
nu2s1zí
nu2sz1ol
nu1s1zo
nu2tal
nu1ta
nu2tat
nu2taz
nu2tál
nu1tá
nu2te
1nú
n1újd
nú2jí
2n1újs
núkés2z1
n2úkés
nú1ké
nú1pr
2n1ú2r.
2n1úrb
2n1úrh
2n1úrn
2n1úrr
2n1úrt
2n1ú2s1zá
nús2z
2nútb
2núth
2nútj
2n1útk
2n1útn
2nútr
2n1úts
2nútt
2n1útv
1nü
nü1bl
2n1ü2dí
2n1üdv
nü1fr
2n1ügg
nü1gr
2n1üg2y.
nüg2y
2n1ügyb
2n1ügyc
2n1ü2gy2e.
nü1gye
2n1ü2gye1i
2n1ü2gyek
2n1ü2gyes
2n1ü2gyet
2n1ü2gyé
nü2gy1és
2n1ügyh
2n1ü2gyi
2n1ügyk
2n1ügyl
2n1ügyn
2n1ügyr
2n1üld
nü1pr
nü2rí
nüst2
nü1str
2n1ü2tem
nü1te
nü2tés
nü1té
nü2ti
nü2t2ő.
nü1tő
nü2tők
nü2tős
nü2tü
nü2vö
nü2zé
2n1üzl
1nű
nű2zé
nű2ző
nva2su
n1va
nvágya2d
n1vá
nvág2y
nvá1gya
nvá2gy1a1da
nvá2gy1ón
nvá1gyó
nvá2r1a2l
nvá1ra
n2v1át
nven2ta
n1ve
nvé2d1a
n1vé
nvé2d1ő2r
nvé1dő
n2v1ind
n1vi
nvona2l1út
n1vo
nvo1na
nvona1lú
n2v1os
nv1sk
nx1ar
n1xa
n2y
1nya
2ny1abl
2ny1abr
nya2cél
nya1cé
2ny1adag
nya1da
2nyadás
nya1dá
2nya1dó
nya2dóz
2ny1aff
nya2gar
nya1ga
2nyagáh
nya1gá
2nyagár
2nyagáv
2nyagc
nya2gen
nya1ge
2nya1gi
2nyagj
2nyagm
2nyagos
nya1go
2nyag1ta
2nyaguk
nya1gu
2nya1gú
2ny1a2ján
nya1já
2ny1ajk
2ny1ajt
3nya2k.
nya2k1a1la
ny2akal
nya1ka
nya2ka1ra
2nyakc
nya2kel
nya1ke
nya2k1é2k
nya1ké
ny2a2kiz
nya1ki
2ny1ak2t.
2nyak1tá
2ny1aktb
2nyak1ti
2ny1aktj
2nyak1to
2ny1ak1tu
2ny1a2lag
nya1la
2ny1a2la2k.
2ny1a2lakj
2ny1a2lakk
2ny1a2lakr
2n2y1a2lan2y
nya2lapb
nya2laph
nya2la1po
nya2lap1p
nya2lap1s2
2ny1alás2z
nya1lá
2ny1alb
ny1alép
nya1lé
2ny1alm
ny1al1te
2ny1al1tú
2nyamal
nya1ma
2ny1a2nal
nya1na
2ny1ang
2ny1ant
2nyaot
nya1o
ny1a2pad
nya1pa
nya2pát
nya1pá
2ny1app
nya2rén
nya1ré
2ny1ar1ma
2ny1arz
nya1sp
2nyas2s2z
2nyaszt
nyas2z
2ny1at1ká
nya1t1rá
2ny1a2t2y
2ny1a2uk
nya1u
2ny1a2vat
nya1va
1nyá
2ny1ábr
2nyád1ná
2nyádt
2nyáék
nyá1é
2ny1ág
2nyáld
2ny1ál1lí
nyá2lom
nyá1lo
2nyámék
nyá1mé
2nyám1ná
2nyáp
2ny1á2rad
nyá1ra
2ny1á2ra1i
2ny1á2rak
2ny1á2ram
2nyáras
2ny1á2rat
nyá2ráb
nyá1rá
nyá2rán
nyá2rát
nyá2ráv
2nyárc
2nyá2r1e2
2nyárh
2ny1árj
2nyárk
2nyárp
3ny2ár2t.
2nyá2ru
2nyá2rú2
2nyárv
2ny1á2só
nyá2szak
nyás2z
nyá1s1za
nyá2szár
nyá1s1zá
2ny1á2ta
2ny1á2tá
2ny1á2té
2ny1átf
2ny1áth
2ny1átk
2ny1átm
2ny1átn
2ny1á2t1ö
2ny1átr
2ny1áts
2ny1átt
2ny1á2tü
2ny1átv
ny1bl
ny1br
ny1cv
1nydr2e.
nyd1re
1nye
2nyedén
nye1dé
2nye1d1zé
nyed2z
2ny1eff
2ny1egyl
nyeg2y
2ny1egys
2ny1e2k2e.
nye1ke
2ny1e2ke1i
2ny1e2ké1é
nye1ké
2ny1elb
2ny1elc
2ny1e2lef
nye1le
2nyelemz
2ny1elf
ny1el1ha
2ny1el1já
ny1elk
2ny1el1lá
ny1el1ma
2ny1el1nö
2ny1e2lő1í
nye1lő
2ny1e2lő1ő
2ny1e2lőz
2ny1elr
2ny1el1ső
2ny1el1tá
2ny1eltér
nyel1té
2ny1el1to
2ny1elül
nye1lü
nyel2ve2s2z
nyel1ve
2nyelvev
2ny1ember
nyem1be
2nyembl
2nyembr
2nyemel
nye1me
2ny1e2mit
nye1mi2
2ny1eml
2nyenc
2nye1ne
2ny1eng
nye1p
2ny1er1dő
2ny1e2rej
nye1re
nye2rekl
2ny1erk
2n2y1ern2y
2nyerőm
nye1rő
2ny1ese1mé
nye1se
ny1e2s1er
2ny1e2se1té
2ny1esél
nye1sé
2nyestj
2ny1eszk
nyes2z
2ny1e2tik
nye1ti
2nye1ve
nye2vez
2ny1e2vé
2nye1vo
2ny1e2vő
2ny1ex
2nyezr
2ny1e2züs
nye1zü
1nyé
2ny1ébr
2ny1é2hen
nyé1he
2ny1é2hes
2ny1éhs
ny1é2jek
nyé1je
2ny1é2let
nyé1le
2nyélm
2nyéne1ke
nyé1ne
2ny1é2ne1ké
2ny1é2nekn
2ny1ép
2nyér2c.
2ny1ére2m.
nyé2rem
nyé1re
nyé2r1e2s
2ny1é2rin
nyé1ri
2ny1é2r2ő.
nyé1rő
2ny1é2rő1i
2ny1é2rőt
2ny1érték
nyér1té
nyé1rü2
2ny1ér2v.
2ny1ér1zé
2ny1é2tel
nyé1te
2ny1ét1ke
2ny1étl
2ny1é2v.
2ny1évb
2ny1é2v2e.
nyé1ve
2ny1é2ve1i
2ny1é2vek
2ny1é2vem
2ny1é2ven
2ny1é2ves
2ny1é2vet
2ny1évez
2ny1é2véb
nyé1vé
2ny1é2vér
2ny1é2vét
2ny1é2vév
2ny1évf
2ny1é2vi
2ny1évk
2ny1évm
2ny1évn
2ny1évr
2ny1évs
2ny1évt
2ny1é2vü
2ny1é2vű
2ny1évv
ny1fl
ny1f2r
ny1gl
ny1gr
1nyi
2ny1i2bo
2ny1i2deg
nyi1de
2ny1i2dej
2ny1i2dő
nyié2b
nyi1é
2ny1ifj2ú.
nyif1jú
2ny1ifjúb
2ny1ifjú1é
2ny1ifjú1i
2ny1ifjú1ké
2ny1ifjún
2ny1ifjúr
2ny1ifjús
2ny1ifjút
2ny1ifjúv
2ny1i2ga
2nyi1ha
2ny1ihl
2ny1ill
2ny1i1ma
2ny1i2má
2ny1imb
2ny1imp
2ny1i2nas
nyi1na
2ny1inc
2ny1ind
2ny1inf
ny1in2g.
2ny1inj
2ny1ins
2ny1int
2ny1inv
2ny1i2p
2ny1i2rá
2ny1i2ri
2ny1i2rod
nyi1ro
2ny1irt
2ny1is1ko
2ny1ism
2ny1isp
2ny1ist
2nyivad
nyi1va
2ny1i2vás
nyi1vá
2ny1i2vó
2ny1izn
2ny1izt
1nyí
2ny1íg
2ny1íj
3nyíl
2ny1ín
2ny1ív
2ny1íz
1nyjéb
ny1jé
3ny2k.
nyka2r1ó2ra
ny1ka
nyka1ró
1nyke2t.
ny1ke
1nykk
ny1kl
1nykn
ny1k2r
ny1k2v
1ny2m.
1nyme2t.
ny1me
1nymt
1ny2n.
1nyo
2ny1ob
2ny1o2dú
2ny1of
2ny1ok1ke
2ny1okl
2ny1o2kos
nyo1ko
2ny1o2koz
2ny1ok1ta
2ny1o2laj
nyo1la
nyolc1c
2ny1ol1da
2ny1ol1dá
2ny1ol1dó
ny1ol1ló
2ny1oltár
nyol1tá
2ny1oltás
2ny1olvas
nyol1va
3nyo2m.
3nyoma2t.
nyo1ma
3nyomatk
3nyomatom
nyoma1to
3nyo1mo
3nyomt
2ny1op
ny1orc
ny1orm
ny1ors
ny1orv
2ny1os1ko
2ny1os1to
2ny1oszl
nyos2z
2ny1oszt
2ny1ott
2ny1ov
2ny1ox
1nyó
ny1ó2ni
nyó2rác
nyó1rá
nyó2rán
2ny1ó2ri
nyó2s1ü
1nyö
2ny1öb
2ny1öc
2ny1ö2l
ny1önt
2ny1öv
1nyő
2nyőrs
ny1pl
ny1pr
ny1ps
3ny2s.
ny1sc
3nysek
ny1se
ny1sh
ny1sk
ny1sl
ny1sp
nys2t
1nys2t.
ny1s1ta
ny1s1tá
1nyu
2nyud
2nyuj
2nyu1ká
2ny1uk1rá
3nyul
2ny1u2ni
2ny1u2no
ny1u1rá
2nyut
ny1u2ta
ny1u2tá
1nyú
2ny1újd
2ny1ú2jé
2ny1ú2jí
2ny1újs
3nyúl
nyú2lő
2ny1ú2r.
2ny1úrb
2ny1úrh
2ny1ú2ri
2ny1úrk
2ny1úrn
2ny1ú2ro
2ny1úrr
2ny1ú2s2z
2ny1útb
2ny1ú1té
2ny1úth
2ny1ú1ti2
2ny1útj
2ny1útk
ny1útl
2ny1útm
2ny1útn
2ny1útp
2ny1útr
2ny1útt
2ny1útv
1nyü
2ny1üd
2ny1ü2g
2ny1üld
ny1ü1le
2ny1ünn
2ny1ür
2ny1üt
2ny1ü2ze
1nyű
2ny1ű2r.
2ny1űrb
2ny1ű1ré
2ny1űrh
2ny1ű2ri
2ny1űrj
2ny1űrl
2ny1űrn
2ny1ű2rö
2ny1űrr
2ny1űrt
2ny1ű2zé
2ny1ű2z2ő.
nyű1ző
2ny1ű2zőb
2ny1ű2zően
nyűző1e2
2ny1ű2ző1é
2ny1ű2zőh
2ny1ű2zők
2ny1ű2zőn
2ny1ű2zőr
2ny1ű2zőt
2ny1ű2zőv
ny2vék
ny1vé
ny2v1isk
ny1vi
ny2vó
ny2vös
ny1vö
ny2vő
ny2vú
nyzé2ke
ny1zé
nza2c
n1za
n2z1a1cé
nz1a1dá
nz1a1do
nz1a1dó
nz1a2ga
nz1agg
n2z1aj1ta
n2z1akc
nz1ak1k2
nza1k2o
n2z1akt
nz1ald
n2z1alk
n2z1ang
n2z1a2n2y
n2z1ap1p2
nz1a1ra
nz1a1rá
n2z1arc
nz1a1ri
nz1aut
nza1u
nz1á2g2y
n1zá
nz1áll
n2z1á2rad
nzá1ra
n2z1árn2y
nzá2r1ó2ra
nzá1ró
n2z1á2ru
nzá2s1e2
nz1á1só
nzás3s
nz1á1t1a2
nz1d2r
n2z1e2g
n1ze
n2z1elb
n2ze2le1me
nze1le
n2z1e2lér
nze1lé
n2z1elf
n2z1el1ha
n2z1elis
nze1li
n2z1elk
n2z1el1lá
n2z1ellen
nzel1le
n2z1elm
n2z1eln2y
n2z1e1lo
n2z1e2lő1á
nze1lő
n2z1e2lől
n2z1e2lőt
n2zelőz
n2z1els
n2z1el1ta
n2z1el1tü
n2z1elver
nzel1ve
n2z1el1vé
n2z1el1vo
n2z1ember
nzem1be
n2z1e2mel
nze1me
n2z1e2més
nze1mé
n2z1e1mi
n2z1eml
n2zener
nze1ne
n2z1e2rő
nzer2t1a2
nzer2v1a2d
nzer1va
nzervé2t
nzer1vé
nzer2v1é1te
nzer2vi
nze2su
n2z1eszk
nzes2z
n2z1ez
n2z1ég
n1zé
nzé2k1el
nzé1ke
n2z1é2l
n2z1é2r.
n2z1ér1d2
n2z1é2rem
nzé1re
n2z1érk
n2z1érm
n2z1ér1té
n2z1érv
n2z1érz
n2z1étv
nz1gr
nzi2a
n1zi
n2z1i2ga
n2z1i1gé
n2z1ill
nzi2m1a2
nzi2má
nzi2mi
nzi2n1á2
nzi2n1o
nzi2n1ó2
n2z1i2p
n2z1i1rá
nz1ism
n2z1ist2
nzi2tár
nzi1tá
nzi1te2
nzi2t1el
nzi2ten
nzi2t1í2v2e.
nzi1tí
nzití1ve
n2z1íb
n1zí
nz1íg
nz1ín
nz1kl
nz1kr
n2z1okl
n1zo
nzo2lin
nzo1li
nzo2ló
nzo2n1a
nzo2né
nzo2rin
nzo1ri
n2z1os2z
nzókés2z1
n1zó
nz2ókés
nzó1ké
n2z1ön
n1zö
nzö2r
nz1ö1rö
n2z1ös
n2z1ö2v
n2z1öz
nz1pl
nz1pr
nz3saj
nz2s
n1z1sa
n2z3sár
n1z1sá
n2z3sát
n2zsáv
nz3seg
n1z1se
n2z3ser
nz3sik
n1z1si
n2z3sis
n2z3sod
n1z1so
n2z3sor
n2z3só
nz3s2p
nz3s2t
nz3s2z
nztá2r1a2d
nz1tá
nztá1ra
nz1t2r
n2z1uj
n1zu
nzu2l1a
nzu2mé
nz1u2ra
nzu2sa2n
nzu1sa
nzus3s2
n2z1u2t
nz1új
n1zú
nz1ú2t
n2z1üd
n1zü
n2z1ü2g
nz1üs
nz1üv
n2z1ü2z
nz3z2s
2o.
o1a
oa2cé
oa1fr
o2a1ki
o2a1k2v
o2a1mi
oa2na1li
oa1na
o2a1si
o2a1s1zó
oas2z
o1á
oá2ga
oá2r1a2n
oá1ra
oá2ril
oá1ri
oá2rí
oá2r1ol
oá1ro
oá2z2s
oba1b2
o1ba
oba1d2
o2b1alj
obal2t1a2
oba1p
ob1a2ra
oba1u2
obás3s
o1bá
ob1átm
ob2b1e2g
ob1be
ob2bö
o2b1eg
o1be
ob1e1le
o2b1e2m
o2b1e2rő
o2b1ez
o2b1é2g
o1bé
o2b1érz
obi2ki
o1bi
obi2k1ó2
obi2lin
obi1li
obi2lip
obi1na2
ob1in2a.
obi2n1al
o2b1ing
o2b1i2s
ob1ív
o1bí
1objek
ob1je
ob1kl
1o2bo1a
o1bo
o2b1oll
obo2r1a
obo2rin
obo1ri
obo2r1os
obo1ro
obo2t1á2
obo2tin
obo1ti
obókés2z1
o1bó
ob2ókés
obó1ké
o2b1ó2né
o2b1ó2rá
ob1öt
o1bö
ob1pr
1obst
o2b1ut
o1bu
o2b1ú2s
o1bú
ob1üg
o1bü
ob1ür
ob1üt
ob1űr
o1bű
oca2ké
o1ca
o2c1ág
o1cá
o2c1ál
oc1er
o1ce
oc1é2k
o1cé
o2c3h2i.
oc2h
o1c1hi
oc3hok
o1c1ho
oc3hot
oci3a
o1ci
oci1e2
oci1k2r
oci1ó2
oci1p
oci1s2z2
o1c2kef
oc1ke
oc2k1é2l
oc1ké
ocké2n
ock1é1ne
o1c2kér
o1c2két
o1c2k2i.
oc1ki
oc2ki1a
o1c2kig
o1c2kin
o1c2kit
o1c2kiv
oc2kop
oc1ko
o1c2kosn
o1c2ko1so
o1c2kosr
o1c2koss
oc1pr
o2c3sap
oc2s
o1c1sa
o2cs1ál
o1c1sá
ocsá2s
o2cs1ás2z
o2cs1á2z
o2c1s1e2
oc3sér
o1c1sé
ocsié2ra
o1c1si
ocsi1é
o2cs1ing
ocs1izm
o2c1sí2
oc3sín
o2csop
o1c1so
ocs1s
ocs1t
o2csuj
o1c1su
o2c1s1ü2
oc3s1za
ocs2z
oc1s3zá
oc3s1ze
oc3z2s
oc2z
o2daa2d
o1da
oda1a
oda1b2
o2d1adj
oda1dr
o2d1akk
o2d1alj
oda1p2
odas2
o2d1ass
od1aszt2
odas2z
odat2
oda1tr
od1a2u1tó
oda1u
odáb2
o1dá
od1ál1lá
o2dá1ru
odáskés2z1
odás1ké
odás3s
odá2s3z
1o2dáz
od1ed
o1de
ode2l
odel2l1a
ode2min
ode1mi
od1e2v
o2d1é2g
o1dé
od1é2ne
o2d1ép
o2d1ér1d2
o2d1é2te
o2d1é2ve
od1é2vé
2odéz
od1i1de
o1di
odi2g
o2d1i1ga
o2d1ik1re
odi2l1e
odi2lid
odi1li
odi2lik
odi2l1is
o2d1int
o2d1i1ro
od1isp
od1í2z
o1dí
od1kl
od1o1bo
o1do
o2d1okt
o2d1op
odo2rak
odo1ra
odo2ros2z
odo1ro
od1ö2l
o1dö
od1ö2r
od1ő2r
o1dő
od1pr
o1d2ram
od1ra
o1d2rá1ma
od1rá
od1st
odu2l1a2l
o1du
odu1la
o2d1u2r
1odún
o1dú
od1üg
o1dü
od1ün
od1üz
1odváb
od1vá
o1d3ze
od2z
o1d3zo
o1e
oe2ir
oe1i
oe2le
oe2mu
oe2ne
oe1t2he
oe2ur
oe1u
oe2uv
o1é
o2é1fa
o2é1fá
o2é1fo
o2é1ke
o2é1ki
oé1na2
oé2n1al
oé2n1an
oé2n1ar
oé1ná2
oé2n1ár
oé2nis
oé1ni
o2és2z
o2é1vi
ofi2lad
o1fi
ofi1la
ofi2lak
ofi2l1á
ofi2lel
ofi1le
ofi2lér
ofi1lé
ofi1li2
ofi2l1i1ga
ofi2l1i1gá
ofi2lis
ofi2l1os2z
ofi1lo
ofi2tal
ofi1ta
ofi2t1e2
of2la
of2ló
ofo1na2
o1fo
ofo2n1al
ofo2na2n
ofo2n1á
ofo2n1é2r.
ofo1né
ofon3n
ofo2n1ó2
ofor2m1á2
ofő2r1e
o1fő
ofő1rü2
of2rí
o2g1abr
o1ga
o2g1a2g
oga2kar
oga1ka
o2g1a1ká
o2g1a2la
o2g1a2lá
o2g1alj
og1all
og1alt
og1a1lu
o2g1a2n2y
o2g1ap
o2g1a2ran
oga1ra
og1arc
o2g1a2s2z
og2a2t1a2g
oga1ta
oga2t1e2
og2a2t1i2n
oga1ti
og1a2t2y
2ogaz
o2g1á2g
o1gá
og1áll
og1álm
o2g1áp
o2g1á2rak
ogá1ra
o2g1á2re2
o2g1ár1ja
o2g1árját
ogár1já
o2g1á2rok
ogá1ro
ogá2ros
o2g1á2ru
ogáskés2z1
ogás1ké
o2gá2só
o2g1á1ta
o2g1á1te
o2g1átj
o2g1átk
o2g1átl
o2g1átn
o2g1á2to
o2g1átr
o2g1áts
o2g1átt
o2g1á2tü2
o2g1átv
og1bl
ogdí2j1a2d
og1dí
ogdí1ja
og1dr
o2g1e2d
o1ge
o2g1e2g
oge2gés2z1
o2g1e2gé
o2g1e2l
o2g1em
o2g1e2p
oge2r
og1e1re
og1ern
og1e2rő
oge2s
o2g1e1se
o2g1e2v
o2g1ez
o2g1é2g
o1gé
o2g1é2l
og2én1n
o2g1é2p
o2g1é2r.
o2g1ér1te
o2g1ér1té
o2g1ér1tő
o2g1érv
o2g1és
og1fl
og1fr
og1g2l
o2g1ic
o1gi
o2g1i2d
o2g1if
ogi2g
o2g1i1ga
o2g1i2gé
o2g1ill
o2g1inf
o2g1ing
o2g1ins
o2g1int
o2g1ip
o2g1i2ro
og1i1ta
o2g1íj
o1gí
og1ín
og1ír
og1ív
og1kl
og1kr
o1g2lic
og1li
o1g2na1i
og1na
o2g1odv
o1go
og1org
og1orr
o2g1orz
o2g1oszl
ogos2z
o2g1oszt
o2g1o2v
og1ö2b
o1gö
og1ö2l
og1ö2r
og1ös
og1ő2r
o1gő
og1pl
og1pr
2ogra1fi
og1ra
2o1g2ra1p
2ográ2f.
og1rá
2ográff
o1g2ráf1fa
o1g2ráfh
o1g2ráfj
o1g2ráfr
o1g2rál
og1sk
og1sp
og1s2t
og1tr
og1u2ra
o1gu
og1u2ru
o2g1u2s
o2g1u2t
o2g1új
o1gú
og1ü2g
o1gü
og1ül
og1ür
og1üt
og1üz
og1űz
o1gű
ogy1a2c
og2y
o1gya
ogy1a2p
ogy1i2s.
o1gyi
ogy1os
o1gyo
ogyó1é2
o1gyó
oha2mal
o1ha
oha1ma
oha2me2l
oha1me
oha2mes
oha2mis
oha1mi
ohas2
oha1sp
o2h1ág
o1há
o2h1ál
ohá2nyad
ohán2y
ohá1nya
ohá2nya2n
ohá2r1e
ohá2s1zi
ohás2z
ohá2sz1odv
ohá1s1zo
o2h1á2z
oh1e2c
o1he
o2h1ing
o1hi
oh2ni
o2h1orr
o1ho
oh2ó2c1si
o1hó
ohóc2s
o2h1ó1rá
oh1ö2v
o1hö
o2h1u1rá
o1hu
o1i
oi2a1e
oi1a
oi2af
oi2an
oi2av
oi2á1ba
oi1á
oi1da1
oi2d1ad
oi2dan
oi2dál
oi1dá
oi2d1e2
oi2dol
oi1do
oi2d3z
oilet2
oi1le
oi1na2
oi2n1ad
oi2ne
oi2re
oisel2
oi1se
oi2zo
o1í
ojá2r1as
o1já
ojá1ra
ojás3s
ojás3z
ojek2t1á2
o1je
ojek2t1í2
ojek2t1o2
oj1in
o1ji
oj2t1á1ra
oj1tá
oj2t1orják
oj1to
ojtor3já
ojtó1á2
oj1tó
ok1abl
o1ka
ok1a2cé
o2k1a2dat
oka1da
o2k1a2dá
o2k1a2dó
o2k1a2kar
oka1ka
ok1akv
o2k1alj
o2k1alk
ok1alm
ok1alt
o2k1ang
ok1a1ni
o2k1ant
oka1p2l
o2k1app
ok1a2ra
ok1arc
oka2ris
oka1ri
o2k1asp
o2k1ass
ok1aszf
okas2z
ok1aszt
o2k1att
o2k1at2y
oka2u
ok1aut
o2k1a1va
o2k1ág
o1ká
ok1ájt
o2k1á2rad
oká1ra
o2k1á2rak
oká2rul
oká1ru
o2k1árv
oká2sal
oká1sa
ok1á2só
okás3s
ok1ás2z
o2k1á2t1e2
ok1bl
ok1br
ok1dr
o2k2e.
o1ke
ok1e2b
o2k1e2c
oke2d
ok1e1dé
o2k1e2g
o2k1e2l
o2k1e2m
ok1e1ré
ok1er1k2
o2k1er2n1e2l.
oker1ne
ok1e2rő
o2ke2s
ok1e1sé
o2k1e2v
ok1e2z
o2k1ég
o1ké
ok1é2ke
o1k1é2ké
o2k1é2l.
o2k1é2les
oké1le
ok1é2let
ok1é2lé
ok1éln
ok1élt
o2k1é2ne
oké2p
oké3p1á
ok1é2pí
o2k1é1pü
o2k1é2r.
o2k1érb
o2k1érc
o2k1érd
o2k1érg
o2k1érh
o2k1é2ri
o2k1érm
o2k1érr
ok1ér1tá
o2k1ér1te
o2k1ér1té
ok1ér1tö
o2k1érz
oké1s2
okés2z2
o2k1étk
o2k1étt
o2k1é2ve
ok1fl
ok1fr
ok1gr
o2k1i2de
o1ki
o2k1i2do
o2k1i2ga
okigaz1
o2k1i1gá
o2k1i1gé
o2k1ind
o2k1int
o2k1i2rá
o2k1i2ro
o2k1isk
o2k1ism
o2k1isp2
ok1is1te
okist2
o2k1i2ta
o2k1izm
ok1íj
o1kí
ok1ír
ok1ív
ok1íz
ok2kab
ok1ka
ok2k1a2d
ok2k1aj
ok2k1a1le
okk1alk
ok2k1as
ok2kaz
okk1elh
ok1ke
okk1e1lö
okk1e1lő
okk1elr
1okke1ré
ok2k1es
ok2ké1pü
ok1ké
ok2kid
ok1ki
ok1k1ló
ok2kob
ok1ko
okk1öss
ok1kö
okk1ö2vű
ok1k1ri
ok2kud
ok1ku
ok2k1ur
o1k2lí
ok2lor
ok1lo
o2k1ob
o1ko
oko1la2
oko2lár
oko1lá
o2k1oltás
okol1tá
okon1n
oko2n1ok1s
oko1no
oko2ra
oko2r1á
oko2ril
oko1ri
oko2ris
o2k1or1mú
ok1o2ro
o2k1os2z
o2k1o2v2ari1a
oko1va
okova1ri
o2k1o2vi
o2k1öb
o1kö
o2k1ö2d
ok1ö2k
o2k1öl
o2k1ön
okö2r
o2k1ös
o2k1ö2v
ok1ö2z
ok1ő2r
o1kő
ok1pl
ok1pr
o1k2ris
ok1ri
o1k2róm
ok1ró
ok2sel
ok1se
ok1sp
oksz1alm
oks2z
ok1s1za
ok2szan
ok2sz1es
ok1s1ze
ok2sz1is
ok1s1zi
1oktán
ok1tá
o2k1ud
o1ku
o2k1ug
o2k1uj
ok1u2ra
o2k1u2t
ok1ú2r.
o1kú
ok1úrb
ok1úrh
ok1úrr
ok1ús2z
o2k1útb
o2k1úth
o2k1ú2ti
o2k1útj
o2k1útk
o2k1útn
o2k1útr
o2k1útt
o2k1ú2tu
o2kútv
ok1üg
o1kü
ok1ü2l
ok1ün
ok1ür
ok1ü2t
ok1ü2v
ok1üz
ok1űz
o1kű
o3l2a.
o1la
ol1abl
ola1d2
ola1f2
1o2la2j.
1o2lajb
1o2lajf
1o2lajg
1o2lajh
1o2la1ji2
1o2lajj
1o2lajk
1o2lajm
1o2lajn
1o2lajp
1o2lajr
1o2l2ajs
o2lajt
o2la1ju
1o2la1jú
o2lajv
ola1k2r
o2l1alg
ol1alk
ol1amn
ol1a1nya
olan2y
ola1p2
ola1s2p
ola1s2t
o1la2t1a1la
ola1ta
olat1an2y
ol2atan
ol2a2tál
ola1tá
ola2táp
ola2ték
ola1té
ola2t1inf
ol2atin
ola1ti
ola2t1í2v
ola1tí
ola2t1ol
ola1to
ola2t1orn
ol2ator
ola2t1ö2l
ola1tö
ol2a2tüz
ola1tü
olau2r
ola1u
o2l1áb1rá
o1lá
o2l1á2g
olá2ha
ol1áll
o2l1árb
o2l1árh
o2l1á2ri1a
olá1ri
olá2ri1á
o2l1árk
o2l1árn
olá2rok
olá1ro
olá2ron
o2l1árr
o2l1árt2
o2l1á2ru
olá2s1za
olás2z
o2l1á2ti
o2l1átv
ol2caj
ol1ca
ol2cal
olca2n
olc1an2y
ol2c1e2k
ol1ce
ol2cel
ol2ces
ol2c1év
ol1cé
ol2c3h
olc1i1ko
ol1ci
ol2cí
ol2có
ol2cö
ol2c3sor
olc2s
ol1c1so
ol2c1sű
olc3s2z
ol2cü
ol2c2z
ol2dab
ol1da
1olda2l.
1olda1lá
1oldalb
olda2le
1oldalh
1oldalk
1oldal3l
1oldaln
1olda1lo
1oldalr
1oldalt2
1olda1lu
1olda1lú
ol2d1an2y
ol2dap
olda2tel
olda1te
ol2d1a1u
ol2dál
ol1dá
ol2deg
ol1de
ol2d1e1lé
ol2d1ell
ol2d1elv
old1emb
ol2d1e2r
ol2d1e2s
ol2dev
ol2dez
ol2dés
ol1dé
ol2d1é1ve
ol2di2p
ol1di
ol2d1is
ol2dor
ol1do
1oldós
ol1dó
ol2d1ö2
ol2dud
ol1du
ol2d1u2g
ol2d1ü
ol2dű
ol1e2g
o1le
o2l1e2l
o2l1é2d
o1lé
o2l1é2g
o2l1él
ol1é2r.
ol1érd
ol1é2re
ol1é1ré
ol1érh
ol1é2ri
ol1érj
ol1érl
ol1érn
ol1é1rő
ol1érs
o2l1ér1té
ol1é2rü
ol1érv
o2l1érz
olfa2k
ol1fa
ol2fa1u
ol2f1ár
ol1fá
ol2fes
ol1fe
ol2fe2t
olf1ing
ol1fi
ol2fir
ol2fis
ol1fu2
ol2f1ut
ol2fúj
ol1fú
ol1gl
ol1g2r
2olib
o1li
o2l1i2du
1o2li1ga
oli2gáz
oli1gá
o2l1i1gé
o2l1ill
o2l1i2m2a.
oli1ma
o2l1i2má
1olim1p2
oli2nu
o2l1i2pa
oli1pe2
oli2p1et
o2l1isk
oli2szál
olis2z
oli1s1zá
o2l1í2v.
o1lí
oll1a2g2y
ol1la
ol2l1aj
olla2l
ol2l1a1la
ol2l1alj
ollan2d1ó2
oll1an2y
ol2l1at1k2
oll1att
ol2l1á2g
ol1lá
ol2l1e2c
ol1le
oll1e1ge
oll1e1gé
ol2l1e2g2y
ol2l1e2h
olle2l
ol1l1e1le
ol2l1emb
oll1e1se
ol2l1é2k
ol1lé
ol2l1é1ri
ol2l1inf
ol1li
oll1in1ge
oll1in1gé
oll1in1gi
oll1ingj
oll1ingn
oll1ingr
oll1inj
ol2lins
ol2l1int
oll1isk
ol2lob
ol1lo
ol2lor
ol2l1os2z
ol2l1ö
ol2l1ő2
oll1s
ol2lub
ol1lu
ol2lul
ol2l1u2s
ol2lü
ol3lyu
ol2l2y
oln1i1ke
ol1ni
o2l1o2l
o1lo
o2l1op
olo2r1e
ol1ott
o2l1ox
oló1e3dénn
o1ló
oló1e
olóe2dé
oló1f2
o2l1ó2né
o2l1ónn
o2l1ó2no
o2l1ónr
o2l1ónt
o2ló2rá1i
oló1rá
o2ló2ráj
oló2rák
oló2rán
o2ló2rár
o2ló2rát
ol1ó2ri
olót2
oló1tr
ol1ö2l
o1lö
ol1ör
ol1ös
ol1p2l
ol1p2r
ol1sk
ol2t1aj
ol1ta
1oltalm
1olta1lo
ol2t1a1ri
ol2t1ág
ol1tá
ol2t1á1ta
ol2t1eg
ol1te
ol2t1em
olte2r
ol2t1e1re
olte2s
ol2t1e2v
ol2t1ép
ol1té
ol2t1é2r.
ol2t1érr
ol2t1és
ol2tid
ol1ti
ol2tim
ol2tis
ol2tiz
ol2t1old
ol1to
1oltó1é
ol1tó
1oltóh
1oltó1ké
1oltó1lo
1oltóm
1oltón
oltö2r
ol1tö
ol2t1őr
ol1tő
ol2tür
ol1tü
o2l1ug
o1lu
o2l1ur
o2l1u2tá
ol1ús
o1lú
ol1üg
o1lü
ol1ül
ol1üv
ol1üz
1olvad
ol1va
1olvas
o2lyabr
ol2y
o1lya
oly1a1da
oly1ag2g2y
olya1me2
olya2mes
oly1aszt
olyas2z
o2lyál
o1lyá
o2ly1e2
1o2lyéb
o1lyé
1o2lyéh
1o2lyé1i
o2lyé2l
1o2lyé2n.
1o2lyiér
o1lyi
olyi1é
o2lyim
2o1lyó
o2ly1ö
o2lyő
o2lyug
o1lyu
o2ly1ü2
o2ly1ű2
o2m1abl
o1ma
om1a2dat
oma1da
o2m1adm
o2m1a2dó
o2m1adt
oma1f
oma1ga2
oma2g2a.
oma2g1ad
om2a2ga2l
oma2g1á2
oma2ge
oma1gi2
oma2g1in
o2m1a2gya
omag2y
om1a1gyú
o2m1a1já
o2m1ajk
om2a1k1ré
o2m1akt
o2m1a2lag
oma1la
oma2la1po
oma2lapr
o2m1alm
om1alt
o2m1alv
o2m1amb
om1ang
o2m1ann
om1apad
oma1pa
o2m1app
oma1pr
o2m1a2ra
o2m1arc
o2m1arg
oma2t1árak
om2atár
oma1tá
omatá1ra
o2m1at1ká
o2m1at2y
o2m1a1u
om1a2zo
omá2c2s
o1má
omá2g
om1á1gi
o2m1á1go
omá2nya2n
omán2y
omá1nya
omány1ká2
o2m1áp
o2m1á2ra1i
omá1ra
om1á2rak
o2m1á2ram
om1á2ras
o2m1á2rá
o2m1árd
o2m1á2re2
omá2r2é.
omá1ré
omá2rét
o2m1árg
omá2ri1a
omá1ri
omá2ri1á
o2m1árm
o2m1á2ru
omá2sí
omás3s
omá2s2z
omá1s3zó
o2m1á2t1e2
o2m1á2t1é
o2m1átk
o2m1átm
om1á2t1ol
omá1to
o2m1átr
om2b1a2lo
om1ba
om2bág
om1bá
om2b1eg
om1be
omb1elh
om2b1elt
omb1é1ne
om1bé
om2b1é1ri
omb1ó2n.
om1bó
ombó2r
omb1ó1ra
om1b1ro
om2buj
om1bu
omdi1o2
om1di
om1dr
o2m1e2b
o1me
o2m1e2ce
o2m1e2dé
om1ef
ome2g
ome3g1á
om1e1ge
om1e1gé
o2m1eg2y
ome2l
o2m1e1la
om1e1lá
o2m1elb
o2m1e1le
o2m1e1lé
o2m1elk
om1el1lá
o2m1elm
o2m1eln
o2m1e1lo
o2m1e1lő
o2m1els
om1el1te
o2m1e1lu
o2m1elv
o2m1e2m
om1e1ne
om1eng
om1en2y
om1enz
ome2o
o2mep
om1erd
o2m1e2red
ome1re
o2m1e2re1i
o2m1ern2y
om1e2ro
ome2rő
o2m1er2ő.
o2m1erőb
o2m1erőh
o2m1erőn
o2m1erőr
o2m1erő1sí
omer1őss
o2m1erőt
o2m1erőv
om1e2rű
ome2s
om1e1se
om1e1sé
om1ess
om1est
o2m1e2ti
o2m1etn
o2m1e1tű
o2m1e2v
o2m1ex
ome2z
o2m1e1ze
o2m1ezred
omez1re
o2m1é1be
o1mé
om1é2de
o2m1ég
o2m1é2he
o2méhs
om1é1je
o2m1é2ke
o2m1é2le
om1é1lé
om1é2lő
o2m1ép
o2m1é2r.
o2m1érb
o2m1érc
o2m1é2re
o2m1é2ré
omé2r2i.
omé1ri
o2m1érm
o2m1é2rő
o2m1érr
o2m1ér1te
o2m1ér1té
o2m1ér1tő
o2m1érv
o2m1é2s
omé2tel
omé1te
o2m1étt
o2m1é1ve
om1é1vé
om1f2l
om1gl
om1gr
om1i1de
o1mi
omi1d1i2
o2m1i2dő
omi2g
o2m1i1ga
o2m1i1gé
o2m1iks
o2m1ill
om1i1má
omi1me2
omi2mel
omi2m1é
om1i2n2a.
omi1na
omi2náb
omi1ná
omi2náv
o2m1ind
om1inf
o2m1ing
om1inv
o2m1i2o1no
omi1o
o2m1i2p
o2m1i1rá
o2m1i2ri
o2m1i1ro
o2m1irt
o2m1isk
o2m1ism
o2m1is1te
omi2s1z1á
omis2z
om1i2tal
omi1ta
omi2ta2n
omi2t1ás
omi1tá
omi2t1e
omi2ti2s
omi1ti
om1i1zé
om1i1zo
om1izz
om1íj
o1mí
o2m1ír
om1í2v
om1í2z
om1jó2
om2jő
omká2ro2k.
om1ká
omká1ro
om1kl
om1kr
omlá2b1út
om1lá
omlá1bú
omo2dor
o1mo
omo1do
omo2kas
omo1ka
2omol2y
o2m1ont
o2m1o1pe
omo2ras
omo1ra
omo2re
omo2riz
omo1ri
o2m1or3já
o2m1os1to
omosz2f
omos2z
o2m1oszt
o2m1ox
om1ök
o1mö
om1ö2l
om1ön
om1ö2r
om1ös
om1ö2t
om1öv
om1ö2z
om1ő2r
o1mő
om1ő2s
om2pel
om1pe
om1p1la
om2p1ors
om1po
om2pő
om2p1u2tá
om1pu
om1sk
om1sl
om1sp
om1st
om2t2e.
om1te
omtes2s
om1t2r
o2m1ud
o1mu
o2m1ug
o2m1uj
omu2n
o2m1u1no
o2m1ur
o2m1u2t
o2m1u2z
o2m1új
o1mú
om1üd
o1mü
om1üg
om1ü2l
om1ür
om1üs
om1üt
om1üv
om1üz
om1űr
o1mű
om1űz
om1ya
o2n1abl
o1na
ona2cél
ona1cé
ona2dat
ona1da
ona1dr
on1ads
ona1e2
on1agg
on1a1gi
on1a1gó
on1agyh
onag2y
on1ajn
o2n1ajt
2onak
on1akc
o2n1akk
on1akn
o2n1akt
on2a2len
ona1le
on2a2l1e2s
ona2lint
ona1li
o2n1al2j.
o2n1al1ju
ona2lok
ona1lo
ona2los
1ona1ni
o2n1a2no1
o2n1ant
on1app2
ona1pr
ona1ps
on2a2rác
ona1rá
onará1di2
ona1s2p
on2a2tál
ona1tá
ona1tü2
on2a2tüz
o2n1ábr
o1ná
2onác
o2n1ág
o2n1ál1lo
o2n1állv
o2n1á2p
on1á2rad
oná1ra
o2n1á2rak
on1á2rat
on1árb
o2n1árk
o2n1árn
o2n1á2ro
o2n1árt2
o2n1á2ru
o2n1á2rú
on1árv
o2n1á2s1za
onás2z
o2n1á2szokr
oná1s1zo
o2n1á2t1ál
oná1tá
oná2tás
o2n1átc
o2n1á2t1e2
oná2t1ér
oná1té
o2n1átf
o2n1át1he
o2n1átm
o2n1át1re
on1bl
on1br
on2cal
on1ca
on2c1ál
on1cá
on2c1e2g
on1ce
once2s
on2c1ez
on2c1ék
on1cé
on2c1é2r.
on2c1é1ré
on2c1he
onc2h
on2cid
on1ci
on2c1ikr
onc1ill
on2cös
on1cö
on2c3ság
onc2s
on1c1sá
on2cseg
on1c1se
oncs1emb
on2cs1ég
on1c1sé
on2cs1é2r.
onc3sikk
on1c1si
on2cs1im
on2cú
on2cü
on2d2e.
on1de
ond1i1ko
on1di
on2d1o2kos
on1do
ondo1ko
ond1ok1sá
on2d1os2z
2ondoz
on2d1öl
on1dö
on1e2b
o1ne
o2n1e2d
on1e2ge
on1egg
o2n1e2g2y
on1e2h
one2l
o2n1e1la
o2n1e1le
on1e1l1é
o2n1elh
on1elj
o2n1elm
o2n1eln
o2n1e1lo
on1e1lő
o2n1elr
on1els
on1elt
o2n1elv
o2n1e2m
one2n
o1n1e1ne
on1erj
on1er1k2
on1er1s
one2s
o2n1e1se
ones2s
on1est
o2n1e2v
o2n1ex
2onéb
o1né
oné2d
on1é1de
o2n1é2g
o2n1é2he
on1é2ke
on1éks
o2n1é2l
on1é2ne
o2n1é2p
o2n1ér1be
o2n1érc
o2n1érd
o2n1é1ri
o2n1érl
o2n1érm
o2n1érp
o2n1érs
o2n1ér1te
o2n1érv
o2n1é2v.
o2né2vad
oné1va2
o2n1évb
o2n1é2ve
on1é2vi
o2n1évk
o2n1évn
on1évr
on2g1áll
on1gá
ongás1s
on2g1e2c
on1ge
on2ged
on2g1eg
on2g1e2l
on2gik
on1gi
on1g2ló
on2gü
on2gyad
ong2y
on1gya
on2győ
onhá1ro2
on1há
onhárom1
o2n1i2d
o1ni
oni2g
o2n1i1ga
o2n1i1ge
o2n1i1gé
o2n1ij
on1i2ke
o2n1ill
o2n1inb
o2n1ind
o2n1inf
o2n1ing
o2n1inj
o2n1inn
o2n1inr
o2n1ins
o2n1int
o2n1i2p
o2n1i2rá
o2n1i1ro
o2n1irt
o2n1isk
o2n1ism
on1is1te
oni1ta2
o2n1i2zé
on1i1zo
o2n1izz
on1íg
o1ní
o2n1ín
o2n1ív
on1íz
onk1áll
on1ká
onká2ro2k.
onká1ro
onké2t1
on1ké
on2n2e.
on1ne
on3nyá2r
on2n2y
on1nyá
on3nye
on3nyo
on3nyú
on3nyü
o2n1ob
o1no
on2o1g2rá2f.
onog1rá
ono1g2ráfn
ono1ka2
o2n1o2kal
on1o2kos
ono1ko
o2n1ok1ta
o2n1old
on1oll
on1opt
o2n1oszl
onos2z
ono1sztr
o2n1o2v
o2n1ox
o2n1ó1ri
o1nó
onó2sak
onó1sa
onó2si
on2ó2szen
onó1s2z2
onó1s1ze
on1öb
o1nö
on1öl
on1ön
on1ör
on1ös
on1ö2t
on1ö2v
on1ö2z
on1ő2s
o1nő
on1pl
on1pr
on1ps
2on2s.
onsa2v1a2m
on1sa
onsa1va
on1s2k
on1sl
on1s2m
on1s1pe
on1s1po
on1spr
on1sr
on1s1to
ons2z2
on1szf
on1szt
ont1ag2y
on1ta
on2t1aj
on2t1alk
on2t1a1ra
on2t1atr
on2taz
on2t1áll
on1tá
ont1árv
on2teb
on1te
on2t1ed
on2t1e2g
ont1e1lá
ont1eld
ont1elh
ont1ell
ont1elm
on2teln
on2t1e1lő
ont1elt
ont1elv
ont1emb
onte2s
on2t1e1se
ont1é2ké
on1té
on2tél
on2ti1gé
on1ti
on2t1i2ko
ont1ikr
on2t1i2m
on2t1inf
on2t1int
onti2s2z
ont1i1s1zo
on2t1iz
on2t1í2v
on1tí
on2t1oml
on1to
on2t1ors2
ont1ó1ni
on1tó
1ontóst
on2t1ös
on1tö
on2t1öz
on2tül
on1tü
on2tür
on2t1üz
on2tye
ont2y
o2n1ud
o1nu
o2n1ug
o2n1uj
onu2n
o2n1u1na
o2n1u2r
o2n1u2t
o2n1új
o1nú
o2n1ú2s
onú2t
o2n1útb
on1ú1té
o2n1úth
on1ú1ti
o2n1útj
on1ú1to
o2n1útr
o2n1útt
on1ü2c
o1nü
o2n1ü2g
on1ü2l
o2n1ün
o2n1ür
o2n1ü2t
on1ü2v
o2n1ü2z
on1űr
o1nű
on1űz
o2ny1a1cé
on2y
o1nya
o2ny1a2dó
o2ny1akc
ony1alj
o2ny1alk
ony1alt
o1ny1a1nya
onyan2y
onya1p2
o2ny1a1pó
o2ny1ál
o1nyá
o2ny1árk
o2ny1árn
ony1á1zó
ony1e2c
o1nye
ony1e2g
o2ny1e2l
o2ny1e2m
o2nyen
o2ny1e2r
ony1e2s
o2ny1ég
o1nyé
o2ny1é2j
o2ny1é2k
o2ny1él
o2ny1é2ne
o2ny1é2r.
o2nyé1ré
onygóc1
ony1gó
o2nyi1gé
o1nyi
ony1i1ko
ony1ing
o2nyi1ta
o2nyitók
onyi1tó
o2ny1í2r
o1nyí
ony1old
o1nyo
ony1oml
o2ny1o2r
o2nyos2z
ony1ó2r
o1nyó
o2ny1ö
o2ny1ő2
o2nyug
o1nyu
o2ny1ur
o2ny1ü2
onz1abl
on1za
on2zag
onz1aj1tó
on2zar
on2zág
on1zá
on2z1á2l
on2z1ed
on1ze
on2z1e2l
on2z1e2m
on2z1es
on2z1ék
on1zé
on2z1é1re
on2z1im
on1zi
onz1ing
onz3sel
onz2s
on1z1se
on2zü
o1o
oo2ib
oo1i
oo2in
oo2pe
oo2re
oo2xi
o1ó
o1ö
o1ő
o2p1a2b
o1pa
o2p1a2d
o2pal
op1a2la
o2p1an2y
op1a2po
op1a2r
opa2u
o2p1a2z
o2pál
o1pá
o2p1ám
o2p1á2rat
opá1ra
opáskés2z1
opás1ké
o2p1áth
o2p1átl
o2p1átm
op1bl
op1e2dé
o1pe
op1e2g
op1ejt
op1e1lő
op1em
opera1s
ope1ra
operas2z2
op1erd
op1er1k2
op1e1rő
op1es
op1e2t
o2p1é2l
o1pé
o2p1é2n
op1gr
op1i2ko
o1pi
op1ikr
o2p1im
o2p1ind
o2p1ing
o2p1i2p
o2piram
opi1ra
o2p1i2rá
op1i1si
op1ist
o2p1i2ta
opi2z
op1i1zo
op1izz
op1ív
o1pí
op1kl
op1kr
o2p1ob
o1po
2opol
o2p1orj
o2p1orr
opor2t1a2
opor2t1á2
opor2t1e2
opor2t1érd
opor1té
opor2tö
o2p1or1zó
oposz2f
opos2z
o2p1ov
op1ös
o1pö
op2pé
op2p1is
op1pi
op1py
2op1ro
op2roc
op2rod
op1sl
op1sp
op1sr
op1s2t
o2p1ud
o1pu
o2p1u2r
o2p1u2t
op1új
o1pú
o2p1ús
op1üd
o1pü
op1üg
op1üt
op1üz
o2r1abl
o1ra
o2r1abr
ora2dat
ora1da
o2r1a2dá
o2r1adm
o2r1a2dó
or1aff
or1agg
or1a2gó
ora2kad
ora1ka
ora2kas
o2r1akc
ora1kl
ora2kol
ora1ko
o2r1akt
or1alg
o2r1alj
o2r1alk
o2r1alm
o2r1alt
o2r1alv
or1amp
o2r1a1na
o2r1a1ne
o2r1ank
o2r1a2no
o2r1ant
or1a2nya
oran2y
ora1ó2
o2r1app
o2r1a2rá
o2r1arc
or1a2ri
or1aszk
oras2z
o2r1atk
o2r1atl
or1att
o2r1at2y
o2r1a2zo
o2r1ábr
o1rá
orá2c2s
or1ác2s.
or1á1c1sa
or1á1c1so
o2r1á2g
orá2le
or1ál1ló
o2r1állv
orá2lö
o2r1á2p
o2r1á2r.
o2r1á2rak
orá1ra
o2r1á1rá
o2r1árb
o2r1árk
o2r1á2ro
o2r1árp
o2r1árr
o2r1árt
o2r1á2ru
o2r1á1rú
o2r1árv
o2r1ásv
orá2s1ze
orás2z
o2r1á1ta
o2r1á2t1e2
o2r1átf
o2r1áth
o2r1átj
o2r1átk
o2r1átm
o2r1átr
o2r1áts
o2r1átt
o2r1átv
or1bl
or1br
or2c1a2l
or1ca
or2car
1orcád
or1cá
or2c1há
orc2h
or2c3hé
or2c3ho
or2c3ság
orc2s
or1c1sá
or2c3seb
or1c1se
or2c3sé
or2cú
or2c2z
or2d1a1u
or1da
ord1e1me
or1de
ord1e2ső
or2dex
or2d1ing
or1di
2ore1a
o1re
o3re1á
o2r1e2b
or1ec2s
o2r1e2d
o2r1ef
or1e2ge
or1e2gé
o2r1e2g2y
2ore2k
or1e1ke
or1e1ké
ore2l
or1e1la
or1e1le
or1elh
or1elj
or1elm
or1eln
or1e1lo
or1e1lő
or1elr
or1els
or1elt
or1elv
o2r1e2m
ore2n
or1e1ne
o2r1eng
or1en2y
o2r1ep
or1e1ró
ore2s
or1e1se
or1e1sé
or1e1ső
o2r1ess
o2r1e1s1ze
ores2z
o2r1e2v
or1ez
o2r1é2d
o1ré
o2r1é2g
o2r1é2j
oré2kás
oré1ká
oré2k1e2
or1ék1né
o2r1é2l
o2r1é2m
o2r1é2ne
o2r1ép
o2r1é1ré
o2r1é2te
o2r1étk
o2r1é2v.
o2r1é2ve
o2r1é2vé
o2r1é2vi
o2r1évn
o2r1évr
orfé2l1é2v
or1fé
orfé1lé
orfi2úér
or1fi
orfi1ú
orfiú1é
2orgia1i
or1gi
orgi1a
or1gl
o2r1i1bo
o1ri
o2r1i2de
o2r1i2dé
ori1di2
ori2d1i1o
1o2ri1e
o2r1ifj
o2r1i2ga
o2r1i2gá
o2r1i2g2y
or1i1ha
o2r1i2m2a.
ori1ma
o2r1i2má
o2r1i2n2a.
ori1na
o2rinas
o2r1i2nán
ori1ná
o2r1i2nát
or1i2náv
o2r1ind
o2r1inf
o2r1in2g.
o2r1ingc
o2r1in1gé
o2r1in1gi
o2r1ingn
o2r1ingr
o2r1ings
or1inh
o2r1inj
o2r1ins
orin2t1a2
orin2tel
orin1te
orin2t1ő
ori2og
ori1o
o2r1i2p
o2r1i2si
o2r1ism
or1isp
o2r1is1te
o2r1i2s1zá
oris2z
ori2tan
ori1ta
o2r1i2zé
o2r1íg
o1rí
o2r1í2j
o2r1í2n
o2r1ír
o2r1í2v
o2r1í2z
1orjai1a
or1ja
orja1i
1orjai1é
or3já
or1k2l
orkö2z1e2p
or1kö
orkö1ze
or1k2v
or2m1app
or1ma
orma1t2re
or2m1att
or2m1a1zo
or2m1eb
or1me
orme2g
or2m1eg2y
orm1e1li
orm1elv
orm1erők
orme1rő
orm1es2z
ormé2t
or1mé
or2m1é1te
or2m1os2z
or1mo
or2móv
or1mó
or2m1ö
or2m1ő
or2mü
or2n2e.
or1ne
ornis1s
or1ni
o2r1o2á
o1ro
orogkés2z1
orog1ké
oro2k1á2
oro2kor
oro1ko
o2r1ol1da
o2r1o2li
o2roltól
orol1tó
o2r1ont2ó.
oron1tó
o2r1ontób
o2r1ontó1é
o2r1ontói2g
orontó1i
o2r1ontój
o2r1ontón
o2r1ontór
o2r1ontót2
o2r1ontóv
oro2nya
oron2y
oro2nyo
o2r1o1pe
or1opt
o2r1o2r
o2r1os1ko
o2r1os1to
1o2ro1s1zi
oros2z
1o2roszr
o2r1osz1tá
o2r1o2x
or1ó2i1a
o1ró
oró1i
or1ó2ni
or1ó2rá
or1ó1ri
or1ó2sá
or2óso2r
oró1so
oró2so1ro
or1ö2b
o1rö
or1ö2c
or1ö2l
or1ön
or1ör
or1ös
or1öt
or1öv
or1ö2z
or1ő2r
o1rő
or1ő2s
or1ph
or1pl
or1p2n
or1p2r
orr1abl
or1ra
or2r1a2r
or2r1á1ta
or1rá
1orrb
1orr2i.
or1ri
or2rin
or2riz
1orrk
1orruk
or1ru
1orr2ú.
or1rú
1orrúc
or2s1a2d
or1sa
or2s1ajtók
orsaj1tó
ors1alk
ors1ass
or2s1ál
or1sá
or2sed
or1se
or2s1e2s
or2s1é2ne
or1sé
or2sér1te
ors1é2tát
orsé1tá
or2s1í2r
or1sí
or2sön
or1sö
or2ső
or1s1rá
or1s2tab
ors1ta
ors1ült
or1sü
ors1ü2tő
1ország
ors2z
or1s1zá
orszi2l1
or1s1zi
ors3zó1ná
or1s1zó
or1sz2t
or2t1agg
or1ta
or2t1alm
or2ta1na
orta2n1á2c
orta1ná
or2t1a1ré
or2tág
or1tá
ort1árad
ortá1ra
ort1á2ram
ort1á2rán
ortá1rá
ort1á2rár
ort1ár1be
ort1ár1ná
ort1á2ruk
ortá1ru
or2t1á2rú
or2t1e2g
or1te
ort1ejt
or2t1e1la
ort1e2lem
orte1le
or2t1e1lé
or2t1ell
or2t1elm
or2t1eln
or2t1e1lo
or2t1e1lő
or2t1elt
ort1elv
ort1emb
or2te1ne
orte2r1a
or2t1ess
ort1e1ti
ort1é1le
or1té
ort1é1lé
ort1élt
ort1élv
or2t1érm
or2tid
or1ti
ort1i2ku
or2t1int
or2t1ist
or2t1i1ta
or2tít
or1tí
or2t1okm
or1to
or2t1o2kol
orto1ko
or2t1ok1ta
or2t1old
or2t1orm
or2t1ott
or2t1ó2r
or1tó
or2t1ön
or1tö
or2t1ös
or2t1ö2v
ort1u1ra
or1tu
ort1ú2r.
or1tú
or2t1ü2zér
or1tü
ortü1zé
o2r1ud
o1ru
o2r1u2g
o2r1u2r
o2r1u2t
o2r1útb
o1rú
o2r1úth
o2r1ú2ti
o2r1útj
o2r1útn
o2r1ú2to2n1
orú1to2
o2r1útr
o2r1úts
o2r1útt
or1üd
o1rü
or1ü2g
or1ü2l
or1ün
or1ür
or1ü2s
or1üt
or1ü2v
or1üz
or1űr
o1rű
1or2v.
or2v1a2n
or1va
2or1vá
orv1i1si
or1vi
1orvos
or1vo
or2vő
1orvv
or2z1a2va2r.
or1za
orza1va
or2z1e2c
or1ze
or2zes
or2zsan
orz2s
or1z1sa
orz1z
o2s1abl
o1sa
o2s1a2da
o2s1a2dó
o2s1akc
o2s1a2l
osa2n
o2s1ang
o2s1ant
o2s1an2y
o2s1a2p
os1arc
os1as2s2z
o2s1a2t2y
o2s1a1u
o2s1ábr
o1sá
o2s1á2g
os1áld
o2s1áll
os1á2lom
osá1lo
osá1ra2
osá2rak
osá1ri2
osá2rik
osá2rok
osá1ro
o2s1á1ru
osás1s
o2s1á2t1a
o2s1á1té
o2s1átj
o2s1átk
o2s1átr
os1e2d
o1se
o2s1e2g
o2s1e2l
os1emb
os1e2n
o2s1er
o2s1es
os1e2t
os1ex
os1ez
o2s1é2g
o1sé
o2s1é2l
o2s1ép
o2s1érd
o2s1ér1te
o2s1ér1té
os1gé2
o2s1i1de
o1si
o2s1if
osi2g
o2s1i1ga
os1ikr
o2s1inf
o2s1int
o2s1i2p
o2s1i2rá
o2s1i1ro
o2s1irt
o2s1ism
os1isp
os1is1te
osist2
o2s1i2s2z
o2s1íj
o1sí
o2s1í2r
o2s1í2z
os1kl
os1k1ró
2osok
o1so
o2sonh
o2sonn
o2so1no
1o2sont
o2so1nu
1o2sonv
o2s1o2r
o2s1os2z
osó1p
o1só
os1ök
o1sö
os1ö2l
os1ön
os1ö2v
os1ő2r
o1ső
os1p2l
os1p2r
os1ps
os1s1ta
os1su2
os2s1ur
ossz1áll
os2s2z
os1s1zá
ossz1á2ro
ossz1es
os1s1ze
ossz1íj
os1s1zí
os1s1zó2
os3s1ző
os1t1e2te
os1te
os2tip
os1ti
os2tir
os2tiz
1ostob
os1to
ost1old
os2t1ös
os1tö
2os1tu
os2t1ü2
1os1tya
ost2y
o2s1ud
o1su
o2s1uj
o2s1u2r
o2s1u2t
o2s1ú2r.
o1sú
o2s1ú2s
o2s1üg
o1sü
o2s1ün
o2s1ü2v
o2s1üz
os3za1bá
os2z
o1s1za
o2szaj
osz1a1lá
osz1alk
osz1alt
osza2n
osz1an2y
o2szar
o2sz1a1ré
osza2t1e2
o2sz1ál
o1s1zá
o2sz1ed
o1s1ze
o2sz1e2g
o2sz1e2l
o2s3zen
osz1ep
osz1es
o2szég
o1s1zé
o2széj
o2sz1é2l
o2sz1é2p
o2sz1és
o2szi1ge
o1s1zi
o2sz1ing
osz1ism
osz1ist2
o1s1zí2
osz1ív
o1szkl
o2sz1o2ro
o1s1zo
o2sz1ors
o2sz1orz
os3zón
o1s1zó
osz1ó2r
o2s1zö
os3zöl
osz2t1alm
osz1ta
oszt1an2y
osz2t1a1po
osz2tá2r.
osz1tá
osz2t1árn
osz2ted
osz1te
osz2tell
osz2t1em
oszté2r
osz1té
osz2t1é1ré
osz2t1ív
osz1tí
os2z2t1os2z
osz1to
o2szur
o1s1zu
osz1úr
o1s1zú
o2s1zü
osz1üg
os3zül
ot1abl
o1ta
ot1a2da
otag2
ota1gr
ota1k
ot1a1na
o2t1ant
ot1a2n2y
ota1p2
ot1arc
ot1arz
ot1ass
o2t1a2z
o2t1ábr
o1tá
o2t1á2g
ot1á2rak
otá1ra
o2t1á2rá
o2t1árb
o2t1á2ré
ot1árn
ot1bl
o2teb
o1te
o2t1e2g
ote1l1a
ote2l1á
ote2lel
ote1le
ote2lő
ote2m1á
ote2m1o
ot1e2v
o2t1é2l
o1té
o2t1ép
o2t1é2r.
o2t1ér1té
o2t1érz
ot1fr
ot1gr
oth2r
o2t1id
o1ti
o2t1im
oti1na2
ot1i2n2a.
oti2nar
oti2nár
oti1ná
o2t1ind
otin1ká2
2oti1pa
2oti1pi
2otipn
2otipt
o2t1i1rá
o2t1is
o2t1i1zé
ot1íj
o1tí
ot1ín
ot1kl
ot1k2r
o2t1ob
o1to
oto1gram
otog1ra
ot1o1la
oto1na2
oto2n1á
oto2n1i2n.
oto1ni
oto2nis
oton3n
oto2rak
oto1ra
oto2rál
oto1rá
oto2rár
oto2re2
oto2rim
oto1ri
oto2rin
oto2rol
oto1ro
otosz2f
otos2z
otó1f2
o1tó
2otóp
otó2pan
otó1pa
otó2pas
otó2pin
otó1pi
otó2p1os2z
otó1po
otó2sé1tá
ot2ósét
otó1sé
otós3s
otó1s1ta
otó1s2z
otó2s3zár
ot2ó1s1zá
otót2
otó1tr
ot1ö2l
o1tö
ot1ös
ot1pl
ot1pr
ot1ps
o1t2rag
ot1ra
ot1sp
ot1sr
ot1st
otta1g2
ot1ta
ot2t1é2g
ot1té
1ottl
ot1u1ra
o1tu
ot1u2s2z
o2t1új
o1tú
ot1ú1ri
ot1ü2l
o1tü
ot1üt
ot1ü2z
ot1űr
o1tű
otva1k2
ot1va
o1tya1
ot2y
o1u
ou2ag
ou1a
ou2il
ou1i
ou2le
o1ú
o1ü
o1ű
ova2ga
o1va
ova2g1i2n
ova1gi
ova2r1an
ova1ra
ova2rát
ova1rá
ova2r1el
ova1re
ova2rék
ova1ré
ova2rés
ova2ris
ova1ri
ovas3s
ova1u2
ováb2b1o
o1vá
ová2s1á2rá
ová1sá
ová2si2k
ová1si
ovi1o2
o1vi
ow1el
o1we
1oxidb
o1xi
1oxidr
o2xi1gé
1o2xilc
oy2ce
oza2g
o1za
oza1g1a
o2z1a1gi
o2z1a2l
oza2mal
oza1ma
oza2m1e2
oza2m1ér1té
oza1mé
oza2min
oza1mi
oza2n
o2z1an2y
oza2r
oz1a1ra
oza2tat
oza1ta
oz2a2tál
oza1tá
oza2t1e2
oza2tés
oza1té
oza2t1é2te
oz2atét
oza2t1ill
oza1ti
oza2t1í2
oza2t1ol
oza1to
o2z1a1u
o2z1á2g
o1zá
ozá2ke
oz1áll
o2z1á2ro
o2z1á1ru
ozá2s1e
ozás3s
ozá2s3z
o2z1átl
oz1e2g
o1ze
oz1e2m
oz1en
ozé2k1e2
o1zé
o2z1é2l
o2z1é2p
o2z1é2ré
oz1i2do
o1zi
o2z1i2par
ozi1pa
o2z1i1ro
ozi2s1aj
ozi1sa
ozi1sl
ozi1st2
ozi1s2z2
ozi2t1a2
oz1í2n
o1zí
o2zor
o1zo
oz1ors
o2z1os2z
ozóegyez1
o1zó
ozó1e
ozóe2g2y
ozóe1gye
2ozófi1a
ozó1fi
2ozófiáb
ozófi1á
2ozófiáh
2ozófiá1ja
2ozófiá2n.
2ozófiá1na
2ozófiár
2ozófiá2t.
2ozófiá1tó
2ozófiá1va
oz2ó2tan
ozó1ta
oz1p2r
o2zs1a2l
oz2s
o1z1sa
ozsa2n
ozs1an2y
o2z1s1ő
ozs1s
o2z1sü
oz1ün
o1zü
oz1ü2z
oz1űr
o1zű
ozzá1s2
oz1zá
2ó.
ó1a
óa2cé
óa2dag
óa1da
óa2dá
óa2do
óa2dó
óa2ga
óa2gi
óa2g2y
óa2já
óajtó1i2
óaj1tó
óa2kad
óa1ka
óa2kar
óa2ká
óa2ku
óa2la
óala2g1
óa2lá
óa2l1e
óalma1na2
óal1ma
óalmanac3
óa2lo
óal1os
óa2lu
óa2ma
óa2na
óa2nó
óa2n2y
óa2pá
óa2po
óa2pó
óa2pu
óa2ra
óa2rá
óa2ré
óa2ri
óa2ro
óa2to2
óatom1
óa2t2y
óa2uk
óa1u
óa2ur
óa2ut
óa2va
óa2xi
óa2zo
ó1á
óá2bé
óá2c2s
óá2ga
óá2gi
óá2go
óá2gyú
óág2y
óá2hí
óá2la
óá2lo
óá2po
óá2rá
óá2ru
óá2rú
óá2sa
óá2só
óá2s2z
óá2ta
óát1a2d2ó.
óá2ta1dó
óá2tá
óá2t1e2
óá2té
óá2t1i2
óá2tí
óá2to
óá2t1ö
óá2tu
óá2tú
óá2tü
2óbab
ó1ba
2óbaj
2óbak
2óbal
2óband
2óbank
óba1p2
2óba1rá
2óbark
2óbar1na
óbas2
óba1t2r
óba1u2
2óbec
ó1be
2óbef
2óbeg
2óbeh
2óbej
2óbek
2óbe1le
2óbe1lé
2óbels
2óbem
2óbe1o
2óber
2óbet
2óbev
2óbez
ó2bég
ó1bé
2óbil
ó1bi
2óbi1o
2óbir
2óbit
2óbiz
ó1b2le
ó1b2lo
2óbój
ó1bó
2óbó1lé
ób2rá
ó1b2ro
ób2rók
ób1ró
ó1b2ru
óbuda2ik
ó1bu
óbu1da
óbuda1i
óbuszt2
óbus2z
óbu2s1z1ú
ó2c1aj
ó1ca
ó2c1akr
óc1a2la
óc1alk
ó2c1ar
ó2c1ass
óc1att
ó2c1ál
ó1cá
1ó2ce1á
ó1ce
ó2c1e2g
óce2l
óc1e1le
óc1elm
óc1e1lő
óc1emb
ó2c1e2v
ó2c1ép
ó1cé
ó2c1ét
ó1c3há
óc2h
ó1c3he
ó1c3ho
óci2m
ó1ci
óc1i1mi
óc1ing
ó2c1i2pa
ó2c1is
óc2lu
ó2c1or
ó1co
ó2c1os2z
óc1ó2r
ó1có
óc1pr
2ó1c1sa
óc2s
ó2c3ság
ó1c1sá
ó2cs1á1ru
2ó1c1se
2ó1c1sé
2ó1c1si
ó2csit
2ó1c1sí
2ó1c1so
2ó1c1sö
2ó1c1ső
ócs1p
óc3s2pá
óc3s2z
óc1tr
ó2c1ud
ó1cu
óc1ün
ó1cü
óc1üz
ó1c3za
óc2z
ó1c3ze
ó1c3zu
ó1d1a1da
ó1da
ód1a2dá
óda2j
ód1a1já
ód1akt
2óda2l.
2óda1la
ód1alg
ó2d1am
ód1a1na
2ódarabk
óda1ra
2óda1rá
ód1aut
óda1u
ód1a1zo
ó2d1áf
ó1dá
ó2d1ág
ó2d1ál
ó2d1áp
ó2d1árn
ód1á2ru
ódás3s
ód1bl
ód1br
ó2d1e2d
ó1de
ó2d1e2g
ód1e2lem
óde1le
ó2d1elh
ód1elj
ó2d1ell
ó2d1elm
ó2d1e1lo
ód1e1lő
ód1elr
ó2d1elv
ód1emb
ód1e1me
ód1ep
óde2ra
ód1e2rő
óde2sés
óde1sé
ó2d1e2v
ó2d1ez
ó2d1é2g
ó1dé
ó2d1ép
ó2d1ér1te
ó2d1érz
ód1fr
ód1gl
ó2d1i2d
ó1di
ó2d1i1gé
ó2d1int
ódi2p
ó2d1i1pa
ó2d1i1ro
ódi2s2z
ód1i1zo
ó2d1ír
ó1dí
ód1kl
ód1kr
ód1kv
2ódob
ó1do
ódo2ga
2ódok
2ódol
2ódom
ó2d1op
ó2d1os2z
ó2d1o2x
ódókés2z1
ó1dó
ód2ókés
ódó1ké
ód1ó1rá
ó2d1ö2l
ó1dö
ód1ös
ó2d1öt
ó2d1öv
ód1őr
ó1dő
ód1pr
2ó1d2ram
ód1ra
2ód1rá
ód2rám
ó1d2rog
ód1ro
ó1d2rót
ód1ró
ód2ruk
ód1ru
ód1sp
ód1st
2ódug
ó1du
ódu2r
ó2d1u1ra
ódus3s
ódu2s3z
ó2d1u2t
ó2d1új
ó1dú
ód1ú2r.
ód1üg
ó1dü
ó2d1ür
ód1üv
ód1üz
ód3zár
ód2z
ó1d1zá
ó2d3ze
ó2d3z1so2
ódz2s
ó1e
óe2bé
óe2c2h
óe2c2s
óe2dé
óe2d2z
óe2ge
óe2gé
óe2gés2z1
óe2g2y
óe2ke
óe2ké
óe2l1a2
óe2l1á2
óe2le
óel1en
óe2lé
óel1ér
óe2l2i
óe2l1í2
óe2lo
óe2lő
óe2lü
óembe2r1ék
óem1be
óembe1ré
óe2me
óe2mé
óe2mu
óe2ne
óe2pé
óe2pi
óe2po
óe2re
óe2ré
óe2ró
óe2rő
óe2se
óe2sé
óe2ső
óe2s2z
óe2te
óe2ti
óe2tű
óe2ve
óe2vé
óe2vő
óe2zü
ó1é
óé2be
óé2ge
óé2gé
óé2gő
2óé2he
2óéhs
óé2ke
óé2ké
óé2kí
óé2le
óé2lé
óé2lő
2óé2ne
óé2pí
óé2pü
2óé2r.
2óérd
2óé2re
2óé2ré
óé2ri
óé2rő
2óér1te
2óér1té
2óérz
óé2te
2óétk
óé2ve
óé2vé
óé2vi
óé2vü
2óf2a.
ó1fa
2ófa1a
2ófa1á
2ófab
2ófac
2ófad
2ófa1e
2ófaf
2ófag
2ófah
2ófak
2ófal
2ófa1o
2ófap
2ófar
2ófas
ófa1st
2ófa1t2
2ófa1ü
2ófav
2ófaz
ófe2l1em
ó1fe
ófe1le
ófe2len
ó2f1ev
2ófék
ó1fé
2ófé1lő
2ófélt
2ófén
ó2f1é2r.
ófi2ab
ó1fi
ófi1a
ófi2ad
ófi2ag
ó1f2la
ó1f2lo
óf2ló
óf2lö
óf2lu
2ófoc
ó1fo
2ófog
2ófok
2ófol
2óford
2óforg
2óformác
ófor1má
2óformáj
2óformál
2óforr
2ófos
2ófot
ó2f1ov
ó1f2rak
óf1ra
ó1f2ri
ó1f2rí
ó1f2ro
ó1f2rö
ó2f1ud
ó1fu
ófu2r
óf1u1ra
óf1ú2r.
ó1fú
óf1úrn
óga1p
ó1ga
óg1dr
óge2o
ó1ge
ógé2p1i2p
ó1gé
ógé1pi
óg3g2y
ógi2al
ó1gi
ógi1a
óg1ír
ó1gí
óg2la
óg2le
ógo2ras
ó1go
ógo1ra
ó1g2raf
óg1ra
óg2rán
óg1rá
ógu2sab
ó1gu
ógu1sa
ógu2s3z
ó2gy1el
óg2y
ó1gye
ó2gy1es
ó3gy2i.
ó1gyi
ógy1int
ógyö2k1érb
ó1gyö
ógyö1ké
ógyta2n1á2
ógy1ta
ógyte2a
ógy1te
ógy1ús
ó1gyú
óha2de
ó1ha
2óhal
2óhan
2óhas
2óhat
óháza2d
ó1há
óhá1za
óhá2z1a1da
2óhon
ó1ho
2óhor
2óhos
óhús1s
ó1hú
ó1i
2ói2de
2ói1dé
ói2dén
ói2di
2ói2dom
ói1do
2ói2dő
2ói2ga
2ói2ge
2ói1gé
ói2géb
ói2gé1i
ói2géj
ói2gék
ói2gén
ói2gér
ói2gét
ói2gév
ói2konb
ói1ko
2ói2konj
2ói2konn
2ói2ko1no
ói2konr
ói2kont
ói1le2
2óill
ói2m2a.
ói1ma
2ói2má
ói2mi
2óimp
2ói2n2a.
ói1na
ói2na1i
ói2na1ka
ói2nas
ói2nat
ói2nán
ói1ná
ói2nár
ói2nát
2óind
2óinf
2óing
2ói2ni
2óinj
2óinp
2óint
2óinv
ói2pa
2ói2rat
ói1ra
2ói2rá
2ói2ro
ói2s2z
2ói2ta
ói2tók
ói1tó
ói2vad
ói1va
ói2zé
ói2zo
ó1í
óí2rá
óí2ri
óí2ro
óí2ró
óí2té
óí2vé
óí2vü
óí2vű
óí2ze
óí2zé
óí2zi
óí2zü
óí2zű
ója1g2
ó1ja
2ójaké2n.
ója1ké
ójak2r
2ójam
ója1p2
2ójav
2ójá1rá
ó1já
2ójárm
2ójár2ó.
ójá3ró
2ójá1ru
2ójárv
2ójá1té
2ójáts
óje2gy1á2
ó1je
ójeg2y
2ójut
ó1ju
2ókab
ó1ka
ók1a2da
ók1ajt
ó2k1a1kó
ó2k1alj
ó2k1al1ko
2óka1ló
2ókamp
2ókamr
2óka1pa
2ókapc
2ókaps
2óka2r.
2óka1ra
2ókarr
2ókart
2óka1ta
2óka1te
óka1t2r
ók1aut
óka1u
2ókav
2ókaz
ó2k1áll
ó1ká
ó2k1á2rad
óká1ra
ó2k1á2ri
ó2k1árn
ó2k1á1ru
2óke2d
ó1ke
ó2k1ed2z
ók1e1gé
2ókem
ó2k1emb
2óker
óker1es1te
óke1re
óke2r1ék1né
óke1ré
óke2rig
óke1ri
ó2k1e2rő
óke2r1ü2g
óke1rü
óke2s2z
ók1e1s1ze
óke2t
ók1e1te
2ókev
2ókez
ó2k1é2l
ó1ké
2ókém
2ókén2y
2óké2p.
2óképb
2óké1pe
ók2é2p1e2l
2óké1pé
2óképl
2óképn
2óképpel
ókép1pe
2óképr
2óképt
2óképz
ó2k1é2r.
2ókérd
2óké1re
2óké1ré
2óké1rő
2ókés
ó2k1étt
ó2k1étv
ók1gr
ók2hi1á
ók1hi
2óki1a
ó1ki
2óki1á
2ókic
2óki1e2
2óki1é
2ókif
óki2g
ó2k1i1ga
ó2k1i1gé
2óki1í
2ókij
ók1ill
2ókim
ók1i1mi
2ókinc
2óki1ne
ó2k1int
2ókin2y
2óki1o
ó2k1isk
ó2k1ist2
2ókis2z
2ókit
2ókiv
ók1i2va
ók1k2r
ók2lim
ók1li
ó1k2lí
2ó1k2ló
2ó1k2lu
2ókock
ó1ko
2óko1co
2óko1e
2ókoh
2ókoll
2óko1mé
2ókomf
2ókomp
2ókonc
2ókonf
2ókonj
2ókons
2ókont
2ókon2y
2ókonz2
2óko1o
2ókop
2óko1rá
óko2r1á2s
2ókorb
2óko1re
ó2k1org
2óko1ri
2ókorl
2ókorm
2ókorn
2óko1ro
2ókorr
2ókor1s2
2óko1ru
ó2k1oszl
ókos2z
2ókód
ó1kó
ók1ó2l
2ókór
ó2k1óv
2óköl
ó1kö
ók1ö2lé
ók1ö2lő
ók1örd
ók1ötl
ók1pl
ók1pr
ó1k2rá
ók2re1á
ók1re
2ó1k2rém
ók1ré
ók2rét
2ó1k2ri
ó1k2rí
ók2ron
ók1ro
ók2ros
2ó1k2rón
ók1ró
ók1sk
ók1st
ó2k1ug
ó1ku
ó2kum
2ókup
2ókur
óku2sz1ál
ókus2z
óku1s1zá
óku2s1ze
2ókut
2ókúr
ó1kú
ó2k1üg
ó1kü
2ókül
2óküs
ók1üzl
2ó1k2vó
ól1ajt
ó1la
óla2man
óla1ma
óla2pa
ó2l1a2pál
óla1pá
óla2pol
óla1po
óla2p1os2z
ól1a2rá
ól1a1zo
ólá2b1út
ó1lá
ólá1bú
1ó2lál
ólás3s
ólá2sü
ól1átl
óle1í2
ó1le
ól1e2se
ól1e2sé
ól1esh
ól1esn
ól1ess
ól1est
óle2ta
óle1te2
óle2t1ev
ó2l1érz
ó1lé
ólé2tel
ólé1te
ólé2tés
ólé1té
óli2a1ko
ó1li
óli1a
óli2am
óli2s
ól1i1s1zo
ólis2z
ól1ín
ó1lí
ólo1ma2
ó1lo
ólo2m1al
ólo2m1á
ólo2m1e
ólo2mi2s
ólo1mi
1ó2lomr
ó2l1os2z
óló1á2
ó1ló
óló1sl
óló1sp
ólót2
ó2l1öl
ó1lö
ó2l1ör
ó2l1ös
ó2l1ö2z
ól1p2r
ólu2m1e
ó1lu
ólus3s
ólu2s1zá
ólus2z
ól1üt
ó1lü
ól1üv
ólya2ga
ól2y
ó1lya
ólyag1g
ó1ly2á
óm1abl
ó1ma
ó2m1a2cé
2ómagn
óm1akn
óm1all
2óman
2óma1ra
2ómarc
ómaren2
óma1re
2ómarh
2ómark
ómas2
óma1sp
2ómass
2óma1te
óm1a2to
ó2m1a1u
2ómax
2ómág
ó1má
óm1áll
2ómárk
2ómárt
2ómáz
óm1b2r
óm1e2g2y
ó1me
ó2me2n.
2óméd
ó1mé
2ómél
óm1é1le
ó2m1ép
2ómé1re
2ómé1ré
2ómérg
2ómérk
2ómérn
2ómé1rő
2ómérs
2ómér1té
ómi2ac1
ó1mi
ómi1a
ómi2as2
ó2m1i2b
ómi2g
ó2m1i1gé
2ómi2k
óm1i1ko
2ómin
ó2m1i2o1no
ómi1o
ó2m1i2ont
ó2m1i2p
2ómis
ó2m1isk
ó2m1ist
ómi2s2z
ó2m1i2z
2ómod
ó1mo
2ómog
2ómoh
ó2m1o1la
ó2m1old
2ómond
2ómo1ni
2ómo1no
2ómont
2ómor
2ómos
ó2m1os2z
2ómot
ó2m1ox
2ómoz
óm1öt
ó1mö
óm1őr
ó1mő
óm1pr
óm1üt
ó1mü
óm1üz
óműt2
ó1mű
2ónad
ó1na
2ónag
óna2kás
óna1ká
óna2k1e2
óna2ko2l
óna1ko
ó2n1al2j.
ó2n1aljak
ónal1ja
ón1al1já
ón1al1jo
ó2n1al1ju
2ónap
ó2na2p2a.
óna1pa
óna2pá
ó2n1apá1ba
óna2pe
ón1arc
ó2n1asp
ón1aszt
ónas2z
óna1t2
2ónav
óná2l
ó1ná
ó2n1áll
ó2n1á1lo
ónás1s
ón1br
óne2d
ó1ne
ón1ez
1ó2né1é
ó1né
2óném
ó2n1é2pí
1ó2né1ra2
ón1érc
ó2n1érd
1ó2né1ró
óné2v1á
2ónéz
óni2g
ó1ni
ó2n1i1gé
ó2n1ist
ón1odv
ó1no
1ó2nokul
óno1ku
2ónor
ón1ox
ón1ön
ó1nö
ó2n1ör
ón1öt
ónőé2h
ó1nő
ónő1é
ón1pr
ó2n1u2t
ó1nu
ón1ür
ó1nü
2ó1nya
ón2y
2ó1nye
2ónyil
ó1nyi
2ónyi1tá
2ónyit2ó.
ónyi1tó
2ó1nyí
2ó1nyo
2ó1nyu
2ó1nyú
ó1o
óo2dú
óo2ka
óo2ká
óo2k1i2
óo2ku
óo2la
óo2li
óo2pe
óo2ra
óo2ri
óo2ro
óo2vi
óo2xi
ó1ó
óó2la
óó2lá
óó2li
óó2lo
óó2ra
óó2ri
óó2sá
óó2vá
óó2vo
óó2vó
ó1ö
óö2bö
óö2ko
óö2kö
óö2lé
óö2lő
óö2rö
óö2ve
óö2zö
ó1ő
óő2re
óő2ré
óő2ri
óő2rö
óő2rü
óő2sé
óő2sö
ó2p1a2da
ó1pa
ó2p1alk
óp1a2nal
ópa1na
ó2p1a2no
óp1ant
ó2p1an2y
óp1a2rán
ópa1rá
óp1áll
ó1pá
ópcsa2p1
ópc2s
óp1c1sa
ó2p1ef
ó1pe
ó2p1e2g
óp1e1lo
ó2p1id
ó1pi
ó2p1ind
ó2p1i2o
óp1i2rá
ó1p2lak
óp1la
ó1p2las
ó1p2lu
ó2p1ob
ó1po
ó2p1o2la
ópo2rad
ópo1ra
óp1ó1ra
ó1pó
ó1p2ri
óp2rod
óp1ro
óp2rop
ó1prób
óp1ró
óp1t2r
ópus3s2
ó1pu
ó2p1új
ó1pú
óp1üv
ó1pü
ó2ra1bé
ó1ra
ór1a1ce
2óra1da
óra2dat
ó2raib
óra1i
ó2rai1é
óra2iér
ó2raih
ó2rai1i
ó2raij
ó2raik
óra2i1ké
óra2i1ko
ó2raim
ó2rain
ó2raip
ó2rais
ó2rait
ó2raiv
1ó2ra1je
2óra1jo
2órajz
óra1kv
ó2ra1la
ó2r1alk
ó2ra1mo
ó2r1a1ni
ór1ant
óra1p2l
ór1arc
1óra1re
óra1s2p
ó2r1a2tom
óra1to
1ó2ra1ü
1óráén
ó1rá
órá1é
ór1ágg
ór1á2g2y
1órákh
1ó2rá1ki
ó2r1ál
1ó2rá1mé
1ó2rá1mo
1órámr
ó2r1á2p
órá2se
2órá1ta
ór1átv
ór1br
ór1e1ge
ó1re
ó2r1e2g2y
ó2r1e2le
ór1ell
ór1elm
ó2r1e1lo
ó2r1e2lő
ór1elv
ór1emb
ór1eml
ó2r1e2r
ó2r1e2set
óre1se
ó2r1e2tá
ó2r1e2té
ó2re1zü
ór1é2je
ó1ré
ó2r1é2l
ór1é2ri
óré2vek
óré1ve
ór1fl
órhá2zot
ór1há
órhá1zo
óri2a1ka
ó1ri
óri1a
óri2al
ó2riá1si
óri1á
óriás3s
óri2c2h
ó2r1id
ó2r1int
ó2r1i2onn
óri1o
ó2r1i2o1no
2órip
ó2r1i2pa
ó2r1i2si
ó2r1is1me
2órit
óri2z
ór1i1zo
ór1í2j
ó1rí
ó2r1ír
ór1í2z
ór1kl
órmé2s
ór1mé
ór1o2ká
ó1ro
ó3r2o1ké
ór1o2ki
óro2kok
óro1ko
óro2koz
ór1o2ku
ór1o1kú2
ó2r1os2z
ó2r1o2x
ó2r1ó1da
ó1ró
óró2dáj
óró1dá
órói2ko
óró1i
óró1p
ór1ó1sá
2óróz
ó2r1ö2l
ó1rö
ó2r1öml
ó2r1ös
ó2r1öt
ór1őr
ó1rő
ór1p2r
ór1t1rá
óru2mag
ó1ru
óru1ma
óru2me
ó2r1u2r
óru2sab
óru1sa
óru2sa2n
óru2se
órus3s2
óru2sü
óru2s3z
ó2r1u1ta
ó2r1ú2s
ó1rú
ó2r1út
ór1ün
ó1rü
ór1ü2v
ór1üz
2ósabl
ó1sa
ó2s1a2d
ó2s1a2g
ó2s1alj
ós1alt
ós1amn
ó2s1apr
2ósar
ó2s1arcot
ósar1co
ó2s1asp
ós1ass
ó2s1atl
ó2s1a1u
ósa2vo
ó2s1ábr
ó1sá
ó2s1áf
óság1g
ó2s1áll
ós1á2lo
ó2s1á2ro
ó2s1á1ti
2ósá1to
ós1bl
ós1br
óscsa1p1á2
ósc2s
ós1c1sa
2óseb
ó1se
ós1e2g2y
2ósej
ós1elj
ós1elm
ós1e2lő
ós1els
ós1elv
óse2m
ós1emb
ós1e1mi
ó2s1en
óse2p
ós1e1po
ó2s1e2rő
ó2s1es
ó2s1e2t
ó2s1ez
ó2s1é2l
ó1sé
2ósém
ós1é2ne
ó2s1ép
ó2s1é2r.
ó2s1ér1te
2ósét
ósé2tá1ka
ósé1tá
ó2s1é2te
ó2s1étk
ó2s1étt
ós1fr
ó2s1i2d
ó1si
ósi2g
ó2s1i1ge
ósi2kerb
ósi1ke
2ósikl
ó2s1ind
ó2s1inf
ósi2p
ós1i1pa
ó2s1i1ro
ó2s1isk
ós1ist2
2ósík
ó1sí
ó2s1ính
ós1í2rá
ós1í2ró
ó1s2kat2
ós1ka
ós2kic
ós1ki
óski2s
ós1kl
ós1kv
ós2lag
ós1la
2ós1lá
ó1s2ni
2ósokas
ó1so
óso1ka
2ósok1ko
2ósoks
ós1o1li
2ósor
ó2s1org
ó2s1orj
ó2s1orm
ó2s1ott
ó2s1ov
ó2s1ó2r
ó1só
ó2s1ö2l
ó1sö
ó2s1ös
ó2s1öt
ós1őr
ó1ső
ó1s2pec
ós1pe
2ó1s2pek
2ó1s2pir
ós1pi
ós1pl
ó1s2pu
ós1s2k
ós1s2p
ós3s1ze
ós2s2z
ós3s1zö
2ó1s2tab
ós1ta
ó1s2tad
ó1s2taf
2ó1s2tand
2ó1s2tat
2ós1tá
ós2tább
ó1s2tád
ó1s2tát
ó1s2tég
ós1té
2ós1tí
ós2to1po
ós1to
2óst2r
ós1t1ré
ós1t1ri
ó1st1ru
2ós1tú
2ósug
ó1su
ó2s1u2r
ó2s1u2t
ó2s1ú2r.
ó1sú
ó2s1ú2ri
ó2s1úrn
2ósül
ó1sü
2ósür
ó2s1üs
ó2s1üz
2ó3sű1rí
ó1sű
2ó1s1za
ós2z
ó2s3zac
ósza2k1ü2
ó2sz1a2lap
ósza1la
2ó1s1zá
ószá2gol
ószá1go
2ószed
ó1s1ze
2ószeg
ó2sz1e2gű
2ószek
2ószel
2ószem
2ószen
2ósze2r.
2ósze1re
ósze2r1e2pé
2ószer1k2
2ó3szerv
2ó3szerz
2ószez
2ó1s1zé
ószé2n1é2
2ó1s1zi
ó2szi1ma
2ó1s1zí
2ószk
ósz2l
2ószob
ó1s1zo
2ószoc
2ó3szof
2ószol
2ószon
2ó1s1zó
2ó1s1zö
ó2sz1ös
2ó1s1ző
2ószp
ós3z2s
2ószt
ó1sz2tá
ószt2rá
2ó1s1zú
2ó1s1zü
ó2sz1üg
2ó3szűk
ó1s1zű
2ószű1ré
2ószű1rő
ó2s3zűrt
ósz2v
ó2t1a1dó
ó1ta
2óta2g.
2óta1ga
ót2a2gal
2ótagb
2ótag1g
2óta1gi
2ótagj
2ótagk
2ótagn
2óta1go
2ótags
2óta1gu
ót1ajk
2ótalap
óta1la
2óta1lá
ót1alk
ó2t1alm
2ótan
ót1an1ti
2ótap
2ótar
ótas2
ó2t1ass
2ótat
ót1a2ur
óta1u
2ótax
2ótág
ó1tá
ót1á2ga
ó2t1ágg
2ótáp
ótára2d
ótá1ra
ótá2r1a1da
2ótá1ro
2ótárs
2ótávc
2ótá1vi2
ótá2v1ir
2ótá2ví
2ótávk
ó2t1e2g
ó1te
ó2t1elh
ó2t1e2lő
ó2t1emb
ó2ter1d2
ó2t1é1gé
ó1té
ó2t1é2ké
óté2t1ö2
ót1fr
ó2t1i2d
ó1ti
óti2g
ó2t1i1gé
ót1ill
ó2t1im
ó2t1ing
ót1i2pa
óti2s2z
ó2t1í2r
ó1tí
ó2t1í2v
ó2t1íz
ót1kr
ótlé2ke
ót1lé
2ótoj
ó1to
2ótol
ótol2l1a2d
ótol1la
ót1oml
2óton
2ótor
ó2t1ors2
2ótov
2ót2ó.
ó1tó
2ótón
ó2t1ö2ko
ó1tö
ótő1e2
ó1tő
ót2rad
ót1ra
ót2raf
ót2rak
ót2ran
ót2rén
ót1ré
ót2rik
ót1ri
ót2ril
ót2ri1ó
ót2rom
ót1ro
ót1sl
ót1sp
ótsze2r1ep
óts2z
ót1s1ze
ótsze1re
ótu2s1ze
ó1tu
ótus2z
ót1ü1lé
ó1tü
ót1ü2lő
ó2t1üst
ó2t1ü2v
ótű2z3s
ó1tű
ó1u
óu2bo
óu2ga
óugrás1s
óug1rá
óuj2j1a2da
ó1ujjad
óuj1ja
óu2ni
óu2no
óu2ra
óu2s2z
óu2ta
óu2tá
óu2to
óu2tó
óu2tu
ó1ú
óú2jí
óú2ré
óú2s2z
óú2ti
óú2to
ó1ü
óü2g2y
óü2le
óü2lé
óü2lő
óü2nő
óü2re
óü2rí
óü2rü
óü2s2z
óü2te
óü2té
óü2tő
óü2ve
óü2ze
ó1ű
óű2ző
2óvad
ó1va
2óvag
2óvaj
2óva2k.
2óvaks
2óva1ku
2óva1ló
2óvar
2óvas
ó2vat
2óvág
ó1vá
2óvák
2óvál
2óván
óvá2r1a2l
óvá1ra
óvárosi2h
óvá1ro
óváro1si
ó2vá1sa
1ó2vá1si
ó2vá1so
1ó2vásr
1ó2váss
1ó2vást2
2óváz
óve2r1a
ó1ve
óve1ri2
óve2rip
óv1in
ó1vi
1ó2vod
ó1vo
ó2vom
2óvon
óza1d2
ó1za
óz1a2dá
óz1a2dó
2ózaj
ó2z1akc
óza1kr
óz1akt
óz1a2la
ó2z1arc
óza1s
óza2t1a2l
óza1ta
óz2a2tan
óz2a2tál
óza1tá
óza2tés
óza1té
óza2told
óza1to
óza2t1ü2
2ózav
ó2z1á2g
ó1zá
óz1áll
ó2z1á2ru
ó2z1á2rú
óz1bl
1óz1di
ó2z1e2g
ó1ze
ó2z1el
óz1em
ó2z1e2rő
ó2z1ex
óz1ez
ó2z1é2l
ó1zé
ó2z1é2te
óz1fl
óz1fr
ózhajó1i2
óz1ha
ózha1jó
óz1imp
ó1zi
óz1ing
ó2z1i2p
ózi2s1e2
ózi2sir
ózi1si
ózis3s2
ózi2s3z
ó2z1old
ó1zo
ózo2n1a2
ózo2n1á
ózo2ni
ózós2
ó1zó
ózó1sp
óz1őr
ó1ző
óz1pr
ózsa1k2
óz2s
ó1z1sa
ó2z3ser
ó1z1se
ó2z3sor
ó1z1so
óz3s2z
óz1t2r
2ózuh
ó1zu
ó2z1u2r
ó2z1u2t
óz1ú2s
ó1zú
ó2z1út
óz1ü2g
ó1zü
óz1ül
óz1ü2z
2ö.
ö1a
ö1á
öb1a2n
ö1ba
öb1ál
ö1bá
öb1á2r
öb1át
öb2b1a2
öb2b1á2
öb2b1e2g
öb1be
öbbe2l
öb2b1e1le
öbbe2m
öbb1e1me
öb2b1e2r
öb2b1es2z
öbb1e1ve
öb2bél
öb1bé
öb2bid
öb1bi
öb2b1is
öb2bí
öb2b1ol
öb1bo
öb2b1os
öb2bot
öb2bó
öb2bö
öb1bü2
öb2b1ül
ö2b1ef
ö1be
ö2b1eg
ö2b1e2l
ö2b1e2m
ö2b1e2n
ö2b1er
ö2b1él
ö1bé
ö2b1é2r.
ö2b1ér1té
öbért2
ö2b1érz
öb1fr
ö2b1i2d
ö1bi
ö2b1ing
ö2b1int
1öb1li
1öb1lö
öb1or
ö1bo
öb1ón
ö1bó
ö2böll
ö1bö
1ö2böl1tő
öc1aj
ö1ca
öc1c1s1a2
öc2c2s
öc1c1s1i
ö2c1ép
ö1cé
ö2c1é1ve
ö1c3he
öc2h
ö1c3hö
öci1ó2
ö1ci
öc1őr
ö1cő
ö2c1s1a
öc2s
öcs1éj
ö1c1sé
öcs1ék
ö2cs1é2te
ö2csiz
ö1c1si
öcs1izz
ö1c1s1ó
ö2cs1ö2l
ö1c1sö
öcs1ű2r
ö1c1sű
öc3s2z
öd1a2l
ö1da
öd1a2n
öd1ar
öd1á2l
ö1dá
öd1ár
ö2d1ef
ö1de
öd1ell
ö2d1em
öd1e2vé
ödé2m1o
ö1dé
ödé2sa2
ödés3s
ödé2s3z
ö2d1é2ves
ödé1ve
ö2d1é2vén
ödé1vé
ö2d1é2vér
öd1gr
öd1íz
ö1dí
öd1os
ö1do
öd1óc
ö1dó
öd1ó2r
öd1sp
öd1u2s
ö1du
öd1új
ö1dú
öd1ú2s
ö2d1üv
ö1dü
ö2d1űz
ö1dű
ö1d3zá
öd2z
ödzá1ró2
ö1d3zu
ö1e
ö1é
öfés3s
ö1fé
ög1ab
ö1ga
ög1a2c
ög1a2d
ög1ag
ög1a2k
ög1a2l
ög1a2n
ög1ap
ög1ar
ög1as
ög1a2t
ög1áb
ö1gá
ög1ág
ög1á2l
ög1á2r
ög1át
ög1dr
ö2g1e2g
ö1ge
ö2g1e2ké1i
öge1ké
ög1elb
ö2ge2le1me
öge1le
ö2g1elf
ö2g1el1ha
ö2g1elm
ö2g1e1lo
ö2g1e2mel
öge1me
ö2g1er
ö2g1es2z
ög1e2vé
ö2g1é2g
ö1gé
ög1ékt
ö2g1é2p
ö2g1é2r.
ö2g1érs
ö2g1ér1té
ögés3s
ög1fr
ö2g1id
ö1gi
ö2g1i2m
ö2g1inf
ö2g1ist
ö2g1i1va
ö2g1i2z
ög1ín
ö1gí
ög1ír
ög1ív
ög1kr
ög1o2l
ö1go
ög1op
ög1o2r
ög1os
ög1ó2r
ö1gó
ög1ö1li
ö1gö
ög1ö1lö
ö2g1öv
ög1pr
ögre1p2
ög1re
ög1sk
ög1sp
ög1tr
ög1ud
ö1gu
ög1u2n
ög1u2t
ö2g1üg
ö1gü
ö2g1üs
ö2g1üt
ö2g1üv
ö2g1üz
ö2g1űz
ö1gű
ö1g3ya
ög2y
ö1i
ö1í
öj2tél
öj1té
öj2t1o
ök1ab
ö1ka
ök1a2g
ök1a2k
ök1a2l
ök1a2n
ök1a2p
ök1ar
ök1as
ök1a2t
ök1a1u
ök1a2v
ök1ág
ö1ká
ök1ál
ök1á2p
ök1á2r
ök1át
ök1áz
ök1dr
ö2k1e2d
ö1ke
ö2k1e2g
ö2k1e1ke
öke2l
ök1e1la
ök1e1le
ök1elh
ök1elm
ök1eln
ök1e1lő
ök1elv
ö2k1e2m
öke2né
öken1s
ök1erd
ö2k1e2res2z
öke1re
ö2k1e2rő
ö2k1es
öke2vés
öke1vé
ö2k1ez
ö2k1ég
ö1ké
ö2k1ékn
ök1éks
ö2k1é2les
öké1le
ö2k1ép
öké2r1e2l
öké1re
öké2r1em
ö2k1é2rez
ö2k1é2rés
öké1ré
ökés3s
ö2k1é2te
ö2k1é2v2e.
öké1ve
ö2k1é2vek
ö2k1é2vet
ök1fr
ök1gl
öki2d
ö1ki
ök1i1de
ök1i1do
öki2g
ö2k1i1ga
ö2k1i1gé
ö2k1ikt
ö2k1i2na
ö2k1ind
ö2k1ing
ö2k1int
ö2k1i2o
ö2k1i2p
ö2k1i1ro
ö2k1is
ö2k1iz
ök1íj
ö1kí
ö2k1ír
ök1ív
ök1íz
ök1kl
ök1k2r
1ök1lű
ök1ok
ö1ko
ök1old
1öko1ló
1ökon
ök1o2p
ök1o2ro
ök1orr
ök1or1s2
ök1o2v
ök1ó2r
ö1kó
ök1óv
1ökö2r.
ö1kö
ökö2rö
ökőár1a2d
ö1kő
ökő1á
ökőá2ra
ökő1é2
ök1pr
1ökrös
ök1rö
1ök1rü
1ök1rű
ök1sp
ök1sr
ökszi2l1
öks2z
ök1s1zi
ök1t2r
ök1u2n
ö1ku
ök1u2r
ök1us
ök1u2t
ök1új
ö1kú
ök1úr
ök1út
ö2k1üg
ö1kü
ökü2l
ö2k1ü1lé
ö2k1ült
ö2k1ü2t
ö2k1ü2v
ö2k1üz
öl1a2d
ö1la
öl1ag
öl1a2j
öl1a2k
öl1al
öl1ap
öl1a2r
öl1a1u
öl1a2v
öl1á2g
ö1lá
öl1ál
öl1á2m
öl1á2p
öl1á2r
öl1á2s
öl1át
öl1á2z
öl1br
ölcsa2l
ölc2s
öl1c1sa
ölcs1á2p
öl1c1sá
ölcs1á2r
ölcs1ell
öl1c1se
öl2csev
öl2csid
öl1c1si
öl2csiz
öl2cs1ok
öl1c1so
ölcs1ol
öl2csos
öl2csüg
öl1c1sü
öl2csül
öl2dab
öl1da
öl2d1a2d
öl2d1a2k
öl2d1a2la
öl2d1alj
öl2d1alk
öl2d1a2n
öl2dap
öl2d1as
öl2d1á2
öl2deg
öl1de
öl2de1p2
öl2dev
öl2d1éd
öl1dé
öl2dél
öl2d1ing
öl1di
öl2di2p
öl2d1o2r
öl1do
öl2dos
öl2d1ó2
öl1dő2
öl2dőr
öl2dős
öl1d1ró
öl2du2r
öl1du
öl2d3z
1ö2l1e2b
ö1le
öle2gel
öle1ge
öleg1g2
ö2lel
ö2l1e2r
ö3l2e3sü
öle2t1á2
öle2t1el
öle1te
öle2to
öle2t1u
ö2l1e2v
ölé2d
ö1lé
ö2l1é1de
1ö2lé1é
ö2l1é2g
ö2l1é2kes
ölé1ke
ö2l1é1kí
ö2l1é2l.
ö2l1é2le
ö2l1é2lé
ö2l1élh
ö2l1é2li
ö2l1élj
ö2l1éln
ö2l1éls
ö2l1élt
ö2l1élv
ö2l1é1me
ö2l1é2ne1ke
ölé1ne
ö2l1é2p
ö2l1é2r.
ö2l1é2red
ölé1re
ö2l1é2rek
ö2l1é2rezn
ö2l1é1ré
ö2l1érh
ö2l1é2ri
ö2l1érj
ö2l1érk
ö2l1érl
ö2l1érn
ö2l1érs
ö2l1ér1te
ö2l1ér1té
ö2l1ér1tü
ö2l1é2rü
ö2l1érv
ö2l1érz
ölés3s
ö2l1é2tet
ölé1te
öl1fr
öl1gyá2
ölg2y
öl2gyer
öl1gye
ö2l1i2d
ö1li
öli2g
ö2l1i1ga
ö2l1i1gé
ö2l1ig2y
ö2l1ij
ö2l1il
ö2l1im
ö2l1i2n
ö2l1i2p
ö2l1i2r
ö2l1i2s
ö2l1i2ta
ö2l1itt
ö2l1iz
öl1í2r
ö1lí
öl1í2v
öl1o2c
ö1lo
öl1o2k
öl1ol
öl1or
öl1o2s
öl1ó2v
ö1ló
ölö2ki
ö1lö
ö2l1ökl
ö2l1öl
öl1önt
ö2l1ör
ö2l1ö2v
öl1őrl
ö1lő
öl1ő1rö
ölpár1ba2
öl1pá
öl1p2r
öl1sk
öl1sr
öl1st
öl2t1ad
öl1ta
öl2taj
ölt1a1la
ölt1alj
ölta2n
öl2tid
öl1ti
öl2til
öl2tí
öl1t1ro
öl2tur
öl1tu
öl1u2g
ö1lu
öl1uj
öl1u2s
öl1u2t
öl1új
ö1lú
öl1ús
ö2l1üd
ö1lü
ö2l1üg
ölü2le
ö2l1ül1té
ö2l1ül1tö
ö2l1ül1ve
ö2l1üs
ö2l1üt
ö2l1üv
ö2l1üz
ö2l1űz
ö1lű
ö2ly1a2
öl2y
ö2ly1á
öly1e2g
ö1lye
ö2lyel
öly2föl
öly1fö
öm1a2d
ö1ma
öm1a2g
öm1al
öm1a2n
öm1a2p
öm1ar
öm1a1u
öm1áb
ö1má
öm1ág
öm1áh
öm1ál
öm1áp
öm1á2r
öm1á2t
öm1áz
öm2b1a2c
öm1ba
öm2b1ak
ömba2l
öm2b1a2n
öm2baz
öm2b1á
öm2bec
öm1be
ömb1e1le
öm2b1e2m
öm2b1es
öm2bék
öm1bé
öm2b1i2d
öm1bi
öm2bin
öm2bí
öm2b1os
öm1bo
öm2b1ó2
öm2bú
öm2bür
öm1bü
ö2m1e2b
ö1me
öme2g1a2
öme2g1e2r
öme1ge
öme2ges2z
öme2g1ék
öme1gé
öme2gép
ömeg3g2
öm1eg2y
ö2m1e2l
ö2m1ember
ömem1be
ö2m1emel
öme1me
ö2m1e2r
öme2s
ö2m1e1se
ö2m1es1te
ö2m1ég
ö1mé
ö2méhs
ö2m1é2l
ö2m1é2nekb
ömé1ne
ö2m1é2ne1ke
ö2m1é2ne1ké
ö2m1é2nekh
ö2m1é2nek1k2
ö2m1é2nekr
ömé2ny1ü
ömén2y
ö2m1ép
ö2m1é2r.
ö2m1ér1te
ö2m1ér1té
ömés3s
öm1gr
ömi2g
ö1mi
ö2m1i1gé
ö2m1in
ö2m1i2p
ö2m1i2ta
ö2m1itt
ö2m1izm
ö2m1i2zo
öm1í2z
ö1mí
ömkés2z1
öm1ké
öm1kl
öm1kr
ömlés3s
öm1lé
1ömlöt
öm1lö
öm1o2k
ö1mo
öm1o2l
öm1or
öm1os
öm1ó2d
ö1mó
öm1ó2r
ö2m1önt
ö1mö
öm1p2r
öm1sp
öm1st
öm1tr
öm1u2g
ö1mu
öm1uj
öm1u2t
ö2m1üg
ö1mü
ö2m1ünn
ö2m1üv
ö2m1üz
ö2m1űz
ö1mű
ön1ab
ö1na
ön1a2d
ön1a2g
ön1a2j
ön1a2k
ön1a2l
ön1am
ön1a2n
ön1a2p
ön1ar
ön1as
ön1at
ön1a1u
ön1a2v
ön1az
ön1áb
ö1ná
ön1ág
ön1ál
ön1ám
ön1á2p
ön1á2r
ön1á2t
önát1a2d2ó.
öná1ta
öná2ta1dó
öná1t1é
ön1áz
önbé2ké2t.
ön1bé
önbé1ké
ön1bl
ön2c1ál
ön1cá
ön2c2h
ön2cő
ön2c3ség
önc2s
ön1c1sé
önc3s2z
ön2c2z
ön2dab
ön1da
ön2dap
önde2m
ön1de
ön2d1é2r.
ön1dé
ön2d1érn
ön2d1érr
ön2d1ér1tő
ön2d1or
ön1do
ön2d1ő
ön2d2z
ö2n1eb
ö1ne
ö2n1e2d
ö2n1ef
ö2n1e2g
ö2n1e2l
ö2n1e2m
öne2n
ö1n1e1ne
ö2n1e2r
ö2n1es
ön1e2v
ön1ex
ön1é2g
ö1né
ö2n1éj
ö2n1é2k
ö2n1é2l
ö2n1é2p
ö2n1érd
ön1é1ri
ön1érl
ön1ér1te
ön1érv
önés3s
ön1és2z
ö2n1é1te
ö2n1étt
ö2n1é2v.
ö2n1é2ves
öné1ve
ö2n1é2vet
ö2n1é2vér
öné1vé
ö2n1é2vét
ö2n1évv
önfe2lem
ön1fe
önfe1le
ön1f2r
ön1g2l
ön1g2r
öngy1as
öng2y
ön1gya
ön2gyék
ön1gyé
öngy1ó2r
ön1gyó
2ön1gyö
ön2győ
ö2n1i2d
ö1ni
ön1if
öni2g
ö2n1i1ga
ön1i1ge
ö2n1i1gé
ön1ill
ö2n1im
ö2n1in
ö2n1i2p
ö2n1i2r
ö2n1is
ön1i1ta
ö2n1i2z
ön1íj
ö1ní
ö2n1ír
ö2n1íz
ön2k1ag
ön1ka
ön2k1an2y
önk1á1ru
ön1ká
ön2kát
önk1olt
ön1ko
ön2k1ú
ön1kü2
önmeg1g2
ön1me
önna2k2
ön1na
ön2n1á
önny1a2d
ön2n2y
ön1nya
ön1ny1á
önny1e2d2z
ön1nye
ön3nyú
ön1o2d
ö1no
ön1o2k
ön1op
ön1or
ön1os
ön1ox
ön1ó2c
ö1nó
ön1ó2r
ön1óv
ön1öb
ö1nö
ö2n1ör
ö2n1ö2v
ön1őr
ö1nő
önő2re
ön1őz
ön1pl
ön1pr
ön1ps
önségü2ké
ön1sé
ön3sé1gü
ön1s2p
ön1s2t2
öns2z2
ön2t1ell
ön1te
öntgen1n
önt1ge
öntös3s
ön1tö
1öntöz
ön1t2ra
ön1t2rá
ön1t2ré
ön1ud
ö1nu
ön1un
ön1u2r
ön1u2s
ön1u2t
ön1új
ö1nú
ön1ú2s
ön1út
ön1üd
ö1nü
ö2n1ü2g
ö2n1ür
ö2n1ü2t
ö2n1üz
ö2ny1a2
ön2y
ö2ny1á
öny1d
ö2ny1el
ö1nye
ö2ny1id
ö1nyi
ö2ny1in
ö2nyí
ö2ny1o
ö2nyüz
ö1nyü
öny2vaj
öny1va
öny2v1a2l
öny2van
öny2v1á2r
öny1vá
öny2v1e2g
öny1ve
öny2v1er
öny2v1es2z
öny2vev
öny2v1ég
öny1vé
öny2vél
öny2v1é2r.
öny2v1ill
öny1vi
öny2v1í
öny2v1o
ö1o
ö1ó
ö1ö
ö1ő
öp1aj
ö1pa
öp1a2l
öp1ál
ö1pá
öp1e2l
ö1pe
öpe2nyá2
öpen2y
öp1e2r
ö2p1ép
ö1pé
öp1ö2lő
ö1pö
ör1ab
ö1ra
ör1a2c
ör1a2d
ör1a2g
ör1aj
ör1a2k
ör1a2l
ör1a2n
ör1a2r
ör1as
ör1a2t
ör1a2u
ör1a2x
ör1a2z
ör1áb
ö1rá
ör1á2c
ör1á2g
ör1á2l
ör1á2r
ör1á2s
ör1á2t
ör1br
örcs1ál
örc2s
ör1c1sá
örcs1ell
ör1c1se
örcskés2z1
örcs1ké
ör2csos
ör1c1so
2ör1dí
ör1d2r
2ör1dü
ö2r1e2c
ö1re
ör1e2d2z
ö2r1ef
öre2ga
öre2g1ék
öre1gé
ör1e2g2y
öre2j1á
öre2k1e2s2z
öre1ke
öre1ké2
ö2r1e2l
ö2r1em
ör1enc
ö2r1e2p
ö2r1e2r
ör1e2se
ö2r1e2te1tő
öre1te
ö2r1e2v
ö2r1ex
ö2r1ez
ö2r1é2de
ö1ré
ö2r1é2g
ö2r1é2j.
ör1éks
ö2r1é2l
ö2r1éne1ke
öré1ne
ö2r1ép
ö2r1é1ré
ö2r1é2ri
öré2sel
öré1se
öré2t1e2g
öré1te
ö2r1étv
ö2r1é2v2e.
öré1ve
ö2r1évk
ör2fá
ör2f1év
ör1fé
ör2f1i2p
ör1fi
ör2fis
ör2f1os
ör1fo
ör2főr
ör1fő
ör1g2r
ö2r1i2d
ö1ri
öri2g
ö2r1i1ga
ö2r1i1gá
öri2k
ör1i1ko
ö2r1ill
ö2r1im
ö2r1ind
ö2r1ing
ö2r1inj
ö2r1ink
ö2r1int
ö2r1inv
ö2r1i2p
ö2r1i1ra
ö2r1i2s
ö2r1i2ta
ör1itt
ö2r1i1vá
ör1i2zo
ö2r1ír
ö1rí
ö2r1í2v
ö2r1í2z
ör1k2l
ör2k1öl1tő
ör1kö
ör1k2r
örle2ta
ör1le
ör2l1in
ör1li
örny1a2l
örn2y
ör1nya
örny1a2n
örny1as
örnye2l
ör1nye
örny1e1le
ör2ny1er
ör2nyéj
ör1nyé
ör2nyés
örny1í2r
ör1nyí
ör2nyó
ör1ob
ö1ro
ör1o2k
ör1o2l
ör1op
ör1o2r
ör1os
ör1ó2r
ö1ró
2örög
ö1rö
örö3g2e
1ö2rö1mü
ö2r1önt
ö2r1ör
ö2rös1s1ze
örös2s2z
ö2r1ös2z
örpe1t2
ör1pe
ör1s2p
ör1s2v
örta2r
ör1ta
örtá2v1
ör1tá
ör2t1éks
ör1té
örté2l
ör2t1é1lé
ör2t1é1lő
ört1ér1ne
örté2s2z
ör2tív
ör1tí
ör2t1ok
ör1to
ör2top
ör1ud
ö1ru
ör1uj
ör1u2n
ör1u2r
ör1u2s
ör1u2t
ör1új
ö1rú
ör1úr
ör1ú2t
ö2r1üd
ö1rü
ö2r1ü2g
örü2l1ék
örü1lé
ö2r1ür
ö2r1üs
ö2r1üt
ö2r1ü2v
ö2r1üz
1ör2v.
örva2s
ör1va
1örvb
1örvek
ör1ve
1örvem
1örvet
1örvéb
ör1vé
1örvéh
1örvév
1örvh
1örvn
1örvr
1örvt
1ör1vü
1ör1vű
1örvv
örz4s
ör2z1s1á2
ör2zs1e2l
ör1z1se
ör2zsid
ör1z1si
ör2zsin
ör2zsir
ör2z1s1í2
ör2z1s1o
ör2z1só
ör2z1su
ör2z1sú
ös1a2g
ö1sa
ös1al
ös1ár
ö1sá
ö2s1el
ö1se
öses3s
ö2s1ez
ösi1é2
ö1si
ö2s1i2p
ös1k2r
ös1o2l
ö1so
ös1o2r
összá2r
ös2s2z
ös1s1zá
1összeg
ös1s1ze
össz1emb
1összes
ös3s1zí
ös2t1arc
ös1ta
ö2s1ü2v
ö1sü
ö2s1z1a2
ös2z
ösz1e2r
ö1s1ze
öszi2s
ö1s1zi
ös2z1is2z
ö2s1z1o2
ö2s1z1ő
ösz2t1ell
ösz1te
öt1ab
ö1ta
öt1aj
öt1a2k
öt1a2l
öt1am
öt1as
öt1a2t
öt1áb
ö1tá
öt1ág
öt1ál
öt1ár
öt1á2s
öt1e1ké
ö1te
öt1e2m
öt1ep
öt1es
öte2t1a2
öte2tel
öte1te
öte2u
öt1e2v
öt1e2z
öté1lé2
ö1té
öté2lék
öté2l1o
ö1t1ér1té
öt1érz
ötés3s
öt1é2ves
öté1ve
öt1fl
öt1fr
öt1gr
öt1i2r
ö1ti
öt1í2v
ö1tí
öt1kr
1ötlet
öt1le
ötle2t1á
1öt1lé
öt1ok
ö1to
öt1ol
öt1or
öt1os
öt1ó2r
ö1tó
ö2tödb
ö1tö
1ö2tödd
1ö2töd1ne
1ö2tödöt
ötö1dö
1ö2tödr
1ö2tö1dü
ö2tös
ötő1a2
ö1tő
ötő1e2
ötő1é2
öt1pr
öt1sc
öt1st
öt2t1a2c
öt1ta
öt2tar
öt2t1as
öt2t1ál
öt1tá
öttá2r
ött1er1k2
öt1te
ött1ér1te
öt1té
öt2t1ut
öt1tu
öt1uj
ö1tu
öt1un
öt1u2t
öt1ü2l
ö1tü
ötve2n1ez
öt1ve
ötve1ne
ö1u
ö1ú
ö1ü
ö1ű
öv1ab
ö1va
öv1ak
öv1á2r
ö1vá
öv1e2d2z
ö1ve
öve2g1a2
öveg1és
öve1gé
öveg1g2
öve2go
öv1e2r
öve2t1a2
öve2teg
öve1te
öve2t1é2l
öve1té
öve2to
öve2t1ú
övetü2l
öve1tü
öve2t1ü1lé
ö2v2é.
ö1vé
öv1ég
öv1é2j
övé2nye2l
övén2y
övé1nye
övé2nyer
övé2nyö2
övé2s1za
övés2z
öv2é2s1zá
övé2szer
övé1s1ze
övé2s1zo
öv2é2s1zö
övé2szü2l
övé1s1zü
övi2dá
ö1vi
ö2vih
övis3s
ö2viv
öv1or
ö1vo
öv1ó2d
ö1vó
öv1ölt
ö1vö
övőrés3s
ö1vő
övő1ré
öv1ut
ö1vu
öz1ab
ö1za
öz1a2c
öz1a2d
öz1a2j
öz1a2k
öz1a2l
öz1a2m
öz1a2n
öz1a2p
öz1a2r
öz1at
öz1a1u
öz1az
öz1á2g
ö1zá
öz1ál
öz1á2m
öz1á2p
öz1á2r
öz1á2t
özá1t1é
öz1d2r
ö2z1e2b
ö1ze
ö2z1e2d
öze2gel
öze1ge
ö2z1egés
öze1gé
öze2gé2s2z1
ö2z1e2g2y
ö2z1e1la
öze2le1me
öze1le
ö2z1e2m
öz1eng
öz1ent
ö2z1epr
ö2z1er
ö2z1es
öze2t1é2k
öze1té
öze1tő2
öze2t1őr
ö2z1e2v
ö2z1ég
ö1zé
ö2z1é2je
özé2k1e2l
özé1ke
ö2z1é2le2l
özé1le
ö2z1é2le2t.
ö2z1é2lé
ö2z1élm
ö2z1élt
özé2m
ö2z1é1me
özé2p1a
öz2é2p1el
özé1pe
özé2p1em
özé2pí
ö2z1épít
özé2p1o2
ö2z1é2r.
ö2z1érb
ö2z1ér1d2
ö2z1érh
ö2z1é2ri
özér2t1e2h
özér1te
ö2z1érte1le
ö2z1ér1té
ö2z1ér1tő
ö2z1érv
ö2z1érz
ö2z1étk
öz1fr
özi2g
ö1zi
ö2z1i1ga
özigaz1
ö2z1i1gá
ö2z1i1gé
ö2z1ig2y
ö2z1i2ko
ö2z1ikt
ö2z1ill
ö2z1i2m
ö2z1inf
ö2z1ing
ö2z1inp
ö2z1int
ö2z1i2nú
ö2z1inv
ö2z1i1ra
ö2z1i1rá
ö2z1i2ri
ö2z1i1ro
ö2z1i1ró
öz1is1ko
ö2z1ism
ö2z1isp
ö2z1i2s2z
ö2z1iz
öz1ír
ö1zí
öz1íz
özmeg1g2
öz1me
öz1ob
ö1zo
öz1o2k
öz1ol
öz1op
öz1os
öz1ov
öz1ó2h
ö1zó
öz1ón
1ö2zönt
ö1zö
ö2z1ö2r
ö2z1ös2s2z
ö2z1öv
öző1a2
ö1ző
özőe2r
öző1e
öz1ő2r.
ö2z1őrk
öz1ő2rö
öz1pl
öz1p2r
ö1z3sa
öz2s
ö1z3sá
ö1z3se
ö1z3sé
öz3s2k
ö1z3so
öz3s2p
ö1z3sú
öz3s2z
öz1t2r
öz1ug
ö1zu
öz1u2n
öz1ur
öz1ut
öz1ú2r
ö1zú
öz1út
ö2z1üd
ö1zü
ö2z1ü2g
ö2z1ünn
ö2z1üt
ö2z1üv
ö2z1ü2z
öz3z2s
2ő.
ő1a
őa2da
őa2dá
őadás1s
őadá2s2z
őa2do
őa2dó
őa2du
őa2ga
őa2gá
őa2gi
őa2g2y
őagyag1
őa1gya
őa2ja
őa2já
őa2ka
őa2ká
őa2kó
őa2la
őala2g1
őa2l1e
őa2lo
őa2mő
őa2na
őa2no
őa2nó
őa2nyá
őan2y
őa2pa
ő2apar
őa2pá
őa2po
őa2pó
őa2pu
őa2ra
őa2rá
őa2ri
őa2ro
őa2s2z
őa2to
őa2t2y
őa2ul
őa1u
őa2ur
őa2ut
őautói2k
őa2u1tó
ő1autó1i
őa2va
őa2xi
őa2zo
ő1á
őá2c2s
őá2ga1i
őá1ga
őá2gak
őá2gas
őá2gat
őá2gá
őá2gé
őá2gi
őá2go
őá2gú
őá2g2y
őá2hí
őá2la
őá2lo
őá2mí
őá2po
őá2ra
őá2rá
őá2re2
őár1em
őá2ri
őá2ro
őá2ru
őá2rú
őá2sa
őá2sá
őá2so
őá2só
őá2su
őá2s2z
őá2ta
őá2t1á2
őá2t1e2
őá2té
őá2tí
őá2tü
őá2vó
őba1p
ő1ba
őb2le
őb2lo
őb2ri
őb2ro
őb2ró
őb2ru
őc1ap
ő1ca
őc3c1so
őc2c2s
őc1gr
ő1c3há
őc2h
ő1c3hé
ő1c3hö
őc2lu
ő2cs1a1la
őc2s
ő1c1sa
őcsa2p1á2g
őcsa1pá
ő2cs1é2j
ő1c1sé
ő2cs1é2rü
őcs1őst
ő1c1ső
őcs1s
őc3s2z
ő1d1a2da
ő1da
őd1a2dá
ő2d1a2lap
őda1la
ő2d1a1u
őd1ál
ő1dá
őd1á2z
őde1a2
ő1de
ő2d1e2g
őd1eld
őd1elj
őd1elk
őd1e2lő
őd1els
ő2d1ép
ő1dé
ő2d1ér1te
ő2d1érz
ődé2sa2
ődé2so
ődés3s
ődé2s3z
ő2d1id
ő1di
ődi2g
ő2d1i1ga
ő2d1ind
őd1int
ő2d1isk
ő2d1op
ő1do
őd1ost
ő2d1ö2l
ő1dö
ő2d1öv
őd1ő2r.
ő1dő
őd1ő2ré
őd1őrn
őd1őrr
őd1őrt
ő2d1őst
őd1pr
őd2ram
őd1ra
őd2rap
ő1d2rá
ő1d2res
őd1re
ő1d2rog
őd1ro
ő1d2ró
ő1d2ru
őd1st
őd1t2r
ő2d1üg
ő1dü
ő2d1üz
ő2d3zá
őd2z
ő1e
őe2ce
őe2c2s
őe2dé
őe2d2z
őe2ge
őe2gé
őe2g2y
őe2k2e.
őe1ke
őe2kék
őe1ké
őe2la
őe2l1á2
őe2lek
őe1le
őe2le1mé
őe2lemg
őe2lemh
őe2lemm
őe2lemn
őe2lemr
őe2le1mü
őe2li
őe2lo
őe2lö
őe2lőd
őe1lő
őe2lü
őe2ma
őe2me
őe2mé
őe2mu
őe2ne
őe2pi
őe2po
őe2re
őe2ré
őe2rőd
őe1rő
őe2rő1é
őe2rőh
őe2rő2i.
őerő1i
őe2rők
őe2rőm
őe2rő1rő
őe2rü
őe2sé
őe2si
őe2ső
őe2ta
őe2te
őe2ti
őe2un
őe1u
őe2vé
őe2vi
őe2vo
őe2vő
őe2ze
ő1é
őé2de
őé2et
őé1e
őé2ge
őé2gé
őé2gi
őé2gő
őé2hem
őé1he
őé2hes
őé2ji
őé2ke
őé2ké
őé2kí
őé2lé
őé2lő
őé2lű
őé2nekb
őé1ne
őé2ne1ke
őéne2kest
ő1é2nekes
őé2ne1ké
őé2nek1k2
őé2nekr
őé2pí
őé2pü
őé2rem
őé1re
őé2re2n
őé2rez
őé2ré
őé2ri
őé2tek
őé1te
őé2va
őé2v2e.
őé1ve
őé2vek
őé2ves
őé2vet
őé2véb
őé1vé
őé2vén
őé2vér
őé2vét
őé2vi
őfa2l1a2d
ő1fa
őfa1la
őf2la
őf2le
őf2lo
őf2ló
őf2lö
őf2lu
őfo2kál
ő1fo
őfo1ká
őfo2kér
őfo1ké
őfo2kin
őfo1ki
őf2ra
őf2rá
őf2ri
ő1f2ro
őf2rö
ő3g2é2p1e2l
ő1gé
őgé1pe
őgépü2l
őgé1pü
őgé2p1ü1lé
őgés3s
őg2le
őg2ló
őg2nó
ő2g1ö2l
ő1gö
őg2ra
őg2rá
őg2ri
őg2ró
őgu1ba2
ő1gu
őgy1a2la
őg2y
ő1gya
őgy1art
ő2gyeg
ő1gye
ő2gyel
őgy1e2lő
őgy1elv
őgy1elz
ő2gyin
ő1gyi
ő2gy1ör
ő1gyö
őhan1gá2
ő1ha
ő3hang
őhan2g1á1ra
ő1i
ői2de1a
ői1de
ői2de1á
ői2deg
ői2de1o
ői2dén
ői1dé
ői2do
ői2dő
ői2ga
ői2ge
ői2gé
ői2g2y
ői2ko
ői2ma
ői2má
ői2mi
őim1p2l
ői2nas
ői1na
ői2on
ői1o
ői2pa
ői2ra
ői2rá
ői2ri
ői2ro
ő2i1ru
ői2si
ői2s2z
ői2ta1la
ői1ta
ői2ta1lá
ői2ta1lé2
ői2ta1li
ői2tall
ői2va
ői2vá
ői2vó
ői2zé
ői2zo
ő1í
őí2gé
őí2ja
őí2ra
őí2rá
őí2ro
őí2ró
őí2ru
őí2vá
őí2ve
őí2vé
őí2vi
őí2vükb
őí1vü
őí2vü1ke
őí2vün
őí2vű
őí2ze
őí2zü
őí2zű
ője2gy1á2
ő1je
őjeg2y
őjob2b1ol
ő1jo
őjob1bo
őjob2b1ó
őjogá2s1zi
őjo1gá
őjogás2z
őke1k2
ő1ke
őke1p2
őkes2
őke1sp
őke1st
ők2é2p1el
ő1ké
őké1pe
őké2s1el
őké1se
őki1á2
ő1ki
őki1e2
ők2la
ők2le
ők2li
ők2lí
ők2ló
ők2lu
őkó1ro2
ő1kó
ő1k2ra
ő1k2rá
ő1k2re1á
ők1re
ők2red
ő1k2ré
ő1k2ri
ő1k2rí
ő1k2ro
ő1k2ró
ők2va
őle1í2
ő1le
őlés3s
ő1lé
őlőé2r
ő1lő
őlő1é
őlőt2
őlő1tr
őma2gár
ő1ma
őma1gá
őmag1g
őma2g1ó2
őműé2h
ő1mű
őmű1é
őműé2n
őműt2
ő2n1e2ke
ő1ne
ő2n1ems
őné1ve2
ő1né
őné2v1es
ő2ny1a2d
őn2y
ő1nya
őnya2g
ő2ny1a1ga
őny1a2la
őny1á1ra
ő1nyá
őny1á2ro
ő2nyát
ő2nyef
ő1nye
őnye2lem
őnye1le
ő2ny1elh
ő2ny1ell
ő2ny1e1lo
ő2ny1em
ő2ny1élv
ő1nyé
ő2ny1ő
ő2nyüz
ő1nyü
ő1o
őo2áz
őo1á
őo2be
őo2dú
őo2ká
őo2k1i2
őo2kí
őo2ko
őo2la
őol2a2j1á2r
őola1já
őola2je
őo2pe
őo2rá
őo2ri
őo2ro
őo2so
őo2ve
őo2xi
ő1ó
őó2ce
őó2ha
őó2no
őó2nu
őó2ra
őó2rá
őó2ri
őó2va
őó2vó
ő1ö
őö2bö
őö2dé
őö2ko
őö2kö
őö2lé
őö2lő
őö2na
őö2re
őö2rö
őö2ve
őö2vé
őö2vi
őö2vö
őö2zö
ő1ő
őő2re
őő2ré
őő2ri
őő2rö
őőr2s1égb
őőr1sé
őőr2s1égn
őő2se
őő2si
őpárba2jo
ő1pá
őpár1ba
őp2la
őp2le
őp2lé
őp2ne
őponc1
ő1po
őpo2ral
őpo1ra
őp2re
őp2ré
őprés1s
őp2ri
őp2ro
őp2ró
őp2s2z
őr1a2dó
ő1ra
őr1a2gá
őr1agg
őr1ajk
őraj2t1ól
őraj1tó
őr1akc
ő2r1a2l
őra1l1e
őra2n
őr1an2y
őr1ap
ő2r1a2r
ő2r1a2s
ő2r1at
ő2r1a1u
ő2r1a2z
1ő2r1áb
ő1rá
őrádi2ók
őrá1di
őrádi1ó
őr1ág2y
őrá2k1e
ő2r1á2l
ő2r1á2p
ő2r1á2ru
1őr1bí
1őr1bl
1őr1br
2őre1a
ő1re
őreá2li
őre1á
őre1e2
őre2get
őre1ge
őr1e2gye
őreg2y
őrei2g
őre1i
ő2r1ekc
ő2r1ekh
őre2lem
őre1le
őr1elh
ő2r1ell
őr1e2lő1a
őre1lő
őr1els
őr1elt
ő2r1elv
ő2r1emb
őr1eml
őren2d1ő2
őr1enz
őre1o2
őre1p2rog
őrep1ro
ő2r1e2ső
őre1u2
őre1ü2
ő2r1ex
1ő2r1ezr
1ő2r1é2g.
ő1ré
1ő2r1égn
1ő2r1égt
1ő2r1ékh
őr1ék1né
őr1éks
ő2r1é2l
őr1é2pü
ő2r1é2ri
őré2sa2
őré2s1za
őrés2z
1ő2ré1ü
ő2r1é2v2e.
őré1ve
őr1é2vek
őr1fl
1őr1fő
őr1fr
őr1g2r
ő2r1i2d
ő1ri
ő2r1if
ő2r1i2ga
ő2r1i2gá
őr1i1ha
őr1ill
ő2rim
őr1i1mi
őr1i2na
őr1ind
ő2r1inf
ő2r1int
őr1i2pa
őri2s1á
őr1i1vá
őr1i2z2é.
őri1zé
ő2rizg
őr1izm
őr1i2zo
őr1ír
ő1rí
1őr1jö
őr1k2l
őr1k2r
1őr1lö
ő2r1ok1t2
ő1ro
őr1old
1ő2r1o2li
őr1oll
ő2r1or
ő2r1os2z
ő2r1ó2r
ő1ró
ő3rög
ő1rö
1ő2rö1kü
ő2röl
1ő2rö1mü
őr1öng
ő2r1ör
ő2r1ös1s1ze
őrös2s2z
1ő2r1őr
ő1rő
ő2r1ő2s
1őrősr
ő2r1ő2z
őr1pl
őr1p2r
1őr1p2s
1őr2s.
őr2s1ál
őr1sá
1őr1sí
1őr1sö
őr1s2pe
őr1s1pi
őr1s1rá
őrs3s
őr1s2z2
1őrszen
őr1s1ze
őr2s3zöm
őr1s1zö
1őr1t2r
1ő2r1un
ő1ru
1ő2r1u2r
őr1u1tá
ő2r1új
ő1rú
őr1úr
ő2r1út
ő2r1üd
ő1rü
őr1üld
1ő2rü1le
ő2r1üs
ő2r1üt
ő2r1üz
2őrző1sö
őr1ző
ő2s1ad
ő1sa
ő2s1a2g
ős1ajtób
ősaj1tó
ő2s1ajtók
ősa2n
ős1a1na
ősa2p
ős1arc
ős1ass
ős1a1u
ő2s1áb
ő1sá
ő2s1á2g
ősá2l
ős1áll
ős1á1ra
ős1árv
ős1dr
ős1e2d
ő1se
ő2s1e2ge
ő2s1e2g2y
ős1elm
ős1e2lő
ős1elv
ő2s1e2m
őse2n
ős1e1ne
őse2p
ős1e1pi
ős1e1po
ő2s1e2rej
őse1re
ő2s1e2rő
ős1ess
ős1es2z
ős1etn
ő2s1e2v
ő2s1ez
ősé2g1e2l
ő1sé
ősé1ge
ő2s1é2ger
ősé2gés
ősé1gé
ő2s1ék
ő2s1é2l
ős1ép
ős1fl
ős1fr
ős1gn
ős1gr
ő2s1i2d
ő1si
ő2s1if
ősi2g
ő2s1i1ga
ős1i2ma
ős1i2má
ős1i1mi
ő2s1inf
ős1ing
ős1int
ő2s1i2pa
ős1i2ra
ős1ist2
ős1i2s2z
ő2s1i2z
ősí2ka
ő1sí
ős1í2ró
ős1í2z
ős2kál
ős1ká
ős1kl
ős1k2r
ős1kv
ős2lat
ős1la
ős2nit
ős1ni
ős1ob
ő1so
őso2k
ős1o1ko
ő2s1o2l
ő2s1op
ő2s1org
ő2s1os
ős1óc
ő1só
ős1ó1ri
ő2s1ö2l
ő1sö
ős1önz
ősö2r
ő2s1örd
ős1ö1re
ős1ö1rö
ő2s1örv
ő2s1ö2z
ős1őr
ő1ső
ős1ő2s
ős2pec
ős1pe
ős2pek
ős1p2l
ős2pór
ős1pó
ősp2r
ős2rác
ős1rá
ős1sk
ős1s2p
ős1s2t
ős2s2z2
ős3s1za
ős3s1zá
ős3szeg
ős1s1ze
ős3szek
ős3szell
ős3szem
ős3szen
ős3szer
ős3szes
ős3szék
ős1s1zé
ős3szén
ős3szf
ős3s1zi
ős3s1zí
ős3szl
ős3s1zo
ős3s1zó
ős3s1zö
ős3s1ző
ős3s1zu
ős3s1zü
ős2tad
ős1ta
ős2tat
ő1s2tát
ős1tá
ő1s2te1ri
ős1te
ős2tég
ős1té
ős2til
ős1ti
őst2r
ős1t1re
ős1un
ő1su
ősu2t
ős1u1ta
ő2s1ú2r.
ő1sú
ős1ú2s
ősza2k1e
ős2z
ő1s1za
ősza2k1ü2
ősz1e2lő
ő1s1ze
2őszer1k2
ő2s2ze2rő
ősz1est
őszi2l1i2
ő1s1zi
ősz1ill
ősz1ist2
őszö2l
ő1s1zö
ősz1ö1lé
ősz1ö1lő
ősz1ölt
ős3z1se
ősz2s
ősz3sir
ős1z1si
őszt2
ő2s3zű1rű
ő1s1zű
őter1mo1
ő1te
őtermos2z2
őtes2t1ő2
őtol2l1a2d
ő1to
őtol1la
ő1t2ra
ő1t2ré
ő1t2ri
őt2ro
ő1t2ró
őttes3s
őt1te
őt2tés
őt1té
őtt1int
őt1ti
őt2t1o2
őt2t1u2
őtű1fé2
ő1tű
őtűfél1
ő1u
őu2go
őu2ni
őu2ra
őu2rá
őu2ru
őu2ta
őu2tó
őu2tu
ő1ú
őú2jo
őú2ré
őú2ri
őú2ro
őú2s2z
őú2té
őú2ti
őú2to
ő1ü
őü2dü
őü2ge
őü2g2y
őü2le
őü2re
őü2rí
őü2s2z
őü2te
őü2té
őü2ti
őü2tö
őü2tő
őü2ve
őü2vö
őü2ze
őü2zé
ő1ű
őű2ré
őű2ri
őű2rö
őű2zé
őű2ző
ővas1fé2
ő1va
ővasfél1
ővár1a2l
ő1vá
ővá1ra
őve2r1a
ő1ve
2ővet
ővé2res
ő1vé
ővé1re
ővé2ret
ő2z1abs
ő1za
ő2z1a2d
őz1a2g
ő2z1ak
ő2z1a2l
ő2z1a2t
ő2z1a1u
ő2z1ál
ő1zá
őz1á2t1e2
őz1bl
őzeg1g
ő1ze
őze2g1i
őz1e2g2y
1ő2zekn
őz1e1lo
őz1els
őz1elv
ő2zem
őz1emb
őz1e2mel
őze1me
ő2z1e2r
őze2t1a2
őze2t1eg
őze1te
őze2t1el
őze2ter
őze2t1o
ő2z1é2r.
ő1zé
őzé2rem
őzé1re
ő2z1é2ri
ő2z1érl
őz1ér1té
1ő2zi1é
ő1zi
ő2zi2g
őz1i1gé
ő2z1in
ő2z1iz
őz1k2r
őz1o2k
ő1zo
őz1ol
őz1os
ő2z1ös2s2z
ő1zö
őző1a2
ő1ző
őzőe2l
őző1e
őzőe2r
őz1p2r
őz3saj
őz2s
ő1z1sa
őz3sap
őz3sát
ő1z1sá
őz3sik
ő1z1si
őz3sis
őz3s2t
ő2z3sü
őz3s2z
őz1t2r
őz1út
ő1zú
őz1üg
ő1zü
ő2z1üs
ő2z1ü2z
őz1ű2z
ő1zű
2p.
1pa
2p1abl
pa2cem
pa1ce
pa2c2h
pa1cl
pa2c1sú
pac2s
p1a2dag
pa1da
pad1a1la
pa2d1as
pa2d1á2l
pa1dá
pa1de2
pa2d1el
pa2d1em
p2a2d1id
pa1di
pa2d1i1ga
p1a2dott
pa1do
pa2d2u.
pa1du
pa2dut
pa2dül
pa1dü
pae2r
pa1e
paé2r
pa1é
pa1fl
pa1f2r
pa2g2a.
pa1ga
pai2dé
pa1i
2p1aj1tó
pa2kad
pa1ka
paka2r1ó
pa2k1e2m
pa1ke
pa2k1é2r.
pa1ké
2pak1ku
pa1k1lu
pa2k1ó2
p2a1k2ré
2p1akt2a.
pak1ta
pak2tal
pak2t1e2l
pak1te
pak2t1es
pak2t1e2v
2p1ak1ti
pak2t1o2r.
pak1to
pak2t1orr
pak2tos
2palag
pa1la
pala2g1ú
pa2la1pí
paláza2d
pa1lá
palá1za
palá2z1a1dá
pa2l1é2l
pa1lé
2p1alf
2p1alg
2p1al1le
2p1al1má
pal1ud
pa1lu
pam1ass
p2amas
pa1ma
pa2m1ur
pa1mu
pamu2ta
p2ana2d
pa1na
pa2n1ag
pa2nal
pan1a1la
pa2n1á2r
pa1ná
panás1s
pan1d2
pang1g
pa2n1il
pa1ni
pan1k1ro
p1an2n2y
pa2nol
pa1no
pans2
pans2z2
pan1sz1t2
pa2nü
2p1a2nya
pan2y
p1a2nyu
pa2p1ad
1pa1pa
1pa2p1a2pa
p2apap
papa2r
pap1a1ra
pa2p1aszt
papas2z
pap1áll
pa1pá
pa2p1il
pa1pi
pa2p1i2n2a.
papi1na
pa2p1i2p
pa2p1o2ku
pa1po
pa2pö
pap1p2
pa2p1ur
pa1pu
pa2p1u2t
pa2pú
2par2a.
pa1ra
para2je
p2araj
par1a2la
2p1a2rann
2p1a2ran2y
2pa2r1a2r
pa2r1á1gá
pa1rá
pa2r1ágn
pa2r1á1go
pa2r1ágv
2paráh
2p1a2ráj
par1áll
2parár
2paráv
2parb
2p1ar2c.
2p1ar1ca
2p1arcc
2p1arc2h
2par1ci
2p1ar1co
2p1arct
2p1ar1cu
2p1ar1cú
pa2r1el
pa1re
pa2r1é2l
pa1ré
2parig
pa1ri
2paril
pa2r1ill
par1isk
par2k1a2l
par1ka
par2k1á
par2kel
par1ke
par2k1ing
par1ki
par2kov
par1ko
par2kó
par2kön
par1kö
par2k1ö2v
par2k1ő2
par2k1ü
2paro2s.
pa1ro
2paro1si
2paro1so
pa2rö
pa2rő
2parr
pars2
par2tem
par1te
par2t1ol
par1to
par2t1ő2
pa2r1ü2
pa2rű
past2
pa2sz1alj
pas2z
pa1s1za
pasz1alt
pa2szas
pat1a1da
pa1ta
pata1kö2
p2atak
pa2t1alk
pat1a1nya
p2atan
patan2y
pa2t1a1ra
p2atar
pat1álc
p2atál
pa1tá
pa2t1ár1k2
p2atár
pa2t1e2g
pa1te
pa2t1ell
pate2s
p2a2tél
pa1té
pa2t1é2r.
p2atér
pa2t1érd
pa2t1é1re
pa2t1ér1te
pa2tid
pa1ti
pa2t1int
p2atin
p2a2tir
2p1atk2a.
pat1ka
2p1atkánk
pat1ká
pa2t1old
pa1to
pa2t1olt
2p1a2tom1be
patom1b
pa2t1os2z
pa2t1otth
pa2t1ut
pa1tu
pa2tús
pa1tú
pa1tü2
p2a2t1üz
2p1a2tya
pat2y
2p1a2tyá
2paur
pa1u
pau2ra
p1a2vat
pa1va
pa1wh
2p1a2xi
1pá
2p1á2bé
2p1ábr
2pá1ca
2pá1cá
pá2c1e
pá1cé2
pá2c3h
pá2c3só
pác2s
pá2c1sö
pá2c1sü
2p1á2g.
2p1á2ga
2p1ágg
2p1ág2y.
pág2y
2p1á2gy2a.
pá1gya
2p1á2gyac
2p1á2gyad
pá2gyaib
págya1i
pá2gyaid
pá2gyaih
pá2gyaik
pá2gyaim
2p1á2gyain
pá2gyair
p1á2gyakb
pá2gya1ké
pá2gya1ki
p1á2gya1ko
2p1á2gyakr
pá2gyakt
2p1á2gyal
2p1á2gyam
2p1á2gyan
pá2gyast2
2p1ágyaz
2p1á2gyáb
pá1gyá
2p1á2gy1á2l
2p1á2gyán
pá2gyá1tó
2p1á2gyáv
2p1ágyb
2p1ágyc
2p1á2gy1e2
2p1á2gyé
2p1ágyf
2p1ágy1ga
2p1ágy1go
2p1ágyh
2p1á2gyi
2p1ágyj
2p1ágyk
2p1ágyl
2p1ágym
2p1ágy1ná
2p1á2gyos
pá1gyo
2p1á2gyö
2p1ágyp
2p1ágyr
2p1ágys
2p1ágyt
2p1á2gyu
pá2gyú
2p1á2gy1ü2
2p1ágyv
2p1ágyz
2p1ájt
pá1la2
pá2lab
pá2lac
2p1álar
2p1ál1do
pá2le
p1áll2a.
pál1la
2pállap
2p1ál1lí
2p1állom
pál1lo
2p1állv
2pál1mo
pá2lü
p2ál2y
pálya1s
pá1lya
2pá1mi
2p1á2mí
pá2mu
pá1na2
pá2naf
pá2n1am
pá2n1an
pá2nar
pá2n1as
pá2nár
pá1ná
pá2n1e2
pá2nék
pá1né
pá2nil
pá1ni
pá2nir
pá2nis
pá2ní
pán1k1ré
2pánkt
pá2n1ó2
pá2nö
pá2nő
pán1s2z
pán1te2
pán2tek
pán2t1el
pá2nü
pá2ny1ad
pán2y
pá1nya
pá2ny1a2l
pá2ny1a2n
pá2nyar
pá2nyat
pá2nya1u
pá2nyaz
pá2ny1e2
pá2nyér1d2
pá1nyé
pá2nyim
pá1nyi
pá2nyö
2p1á2po
pár1a1dó
pá1ra
2páras
2párá1é
pá1rá
pár2d1a2
pár2del
pár1de
pá2r1e2
pá2r1i2p
pá1ri
pá2rő
pár1s2
pár2t1ag
pár1ta
2pártal
párt1an2y
pár2ta1ri
pár2tál
pár1tá
pár1te2
pár2t1el
pár2tem
pár2te2s
pár2tet
pár2tél
pár1té
pár2t1é2r.
pár2t1é2te
pár2tiz
pár1ti
pár2tott
pár1to
pár1tö2
pár2tök
pár2t1ő
pár2tus
pár1tu
pár1tü2
pár2t1üz
pá2ruh
pá1ru
pá2ruk
pá2ru2t
pár1u1tu
pá2rún
pá1rú
pá2rü
2p1á2sás
pá1sá
pá2s1e
pá2sir
pá1si
pá2ső
pás3s
pá2s1ü2
2p1ásván
pás1vá
pá2t1a2
pá2tá
pá2tel
pá1te
pá2t1e2m
pá2tis
pá1ti
2p1átk2a.
pát1ka
2p1átkát
pát1ká
2p1átkáv
p1átlag
pát1la
2p1át1ló
pá2t1or
pá1to
2p1átrak
pát1ra
2p1át3t2é
pá2t1uk
pá1tu
pát1úrt
pá1tú
pá2tü
2p1átvét
pát1vé
pba2l1
p1ba
pbé2r1e2l
p1bé
pbé1re
pb2lo
pb2ra
pb2ri
pb2ro
pci2ó1fo
p1ci
pci1ó
pcsa2p1á2g
pc2s
p1c1sa
pcsa1pá
pcső2s1orr
p1c1ső
pcső1so
pda2l1ad
p1da
pda1la
pdal1an
pda2leg
pda1le
pda2le2l
pd2a2l1es
pd2a2lén
pda1lé
pda2l1í2
pd2ra
pd2rá
pd2ro
pd2ró
1pe
pea2p
pe1a
pea2r
pe1ca1
pec3c
pe2c2z
pe2ed
pe1e
2p1eff
pe1fl
2p1e2ge1se
pe1ge
pe2gés2z1
pe1gé
pe1g2r
2p1egz
2p1e2k2e.
pe1ke
2p1e2ke1i
pe1k2ré
2pektr
pe2lál
pe1lá
pe2lár
pe2lekt
pe1le
2pelemb
2p1e2lemek
pele1me
pe2lemet
2p1e2le1mé
2p1e2lemg
2p1e2lemh
2p1e2le1mi
2p1e2lemk
2p1e2lemm
2p1e2lemn
2pelemr
2p1e2lemz
pel1e1ró
pele2t
pel1e1te
2p1el1go
2p1el1ha
2p1elhel
pel1he
2pel1já
2p1elleb
pel1le
2p1elnev
pel1ne
2p1eln2y
pe2l1os
pe1lo
2p1e2lö
2p1e2lő1a
pe1lő
2p1e2lő1á
2p1e2lő1e
pe2lőg
2p1e2lőh
pe2lő1í
2p1e2lő1já
2p1e2lőjeg
pelő1je
2p1e2lől
2p1e2lőz
2p1elren
pel1re
2p1el1sö
2p1el1tá
2p1el1to
2pe1lu
2p1el2v.
2p1el1vá
pel2v1el
pel1ve
2p1elven
2p1elvh
2p1el1vi
2p1el1vo
2p1el1vű
3pel2y
2p1elz
2p1ember
pem1be
2p1e2mel
pe1me
2p1e2més
pe1mé
2p1eml
2p1ems
2p1e2mu
2p1e2ner
pe1ne
2penged
pen1ge
pen3n2y.
pen2n2y
pen3nyb
penny1ér
pen1nyé
pen3nyh
2p1enny2i.
pen1nyi
pen3nyj
pen3nyk
pen3nym
pen3nyn
pen3nyr
pen3nyt
pen3nyv
pe2nya
pen2y
pe1nyá2
pe2ny1e2l
pe1nye
pe2ny1e2r
peo2l
pe1o
peo2p
peó2r
pe1ó
pe2p2e.
pe1pe
2p1e2pé
pe1p2r
pe2r1akt
pe1ra
per1all
pera1p2
perc1c
per2c1el
per1ce
per2c1in
per1ci
2p1er1dő
perec1c
pe1re
pe2r1e2gyez1
pereg2y
pere1gye
p1e2rej
pe2r1elk
pe2r1e2lőf
pere1lő
pe2r1e2lő1ké
pere2mért
pere1mé
per1e1vé
2per1fa
pe2rid
pe1ri
pe2r1il
pe2r1i2na
pe2r1i2ná
pe2r1ind
pe2r1ing
pe2rí
per1k2
p2erl
per1ok1t2
pe1ro
pe2r1os
pe2r1o2x
pe2r1ó2r
pe1ró
pe2rőd
pe1rő
pe2rőf
pe2rő1i
pe2rőm
pe2rőn
pe2rőr
2p1e2rős
pe2rőt
pe2rőv
per1st
pers2z2
pe2rú
pe2r1ü2g
pe1rü
perü2l
per1ü1lő
per1ült
p2erz
pe2s1ebbel
pe1se
peseb1be
pe2s1eb1bő
pe2sési2g
pe1sé
pesé1si
2p1e2sé1sű
pe2ső1i
pe1ső
pe1s2p
2p1esszév
pes2s2z
pes1s1zé
pes2t1a2
pes2t1er
pes1te
pe2szak
pes2z
pe1s1za
pe2sz1ál
pe1s1zá
pesz1ell
pe1s1ze
pe2sz1elv
pesze2m
pe2sze1me
pe2s1zu
pe2sz1ü2l
pe1s1zü
pe2tal
pe1ta
pe2t1a2n
pe1t2á
petet2
pe1te
2pe1te1te
2pete1té
2p1e2te1tő
2p1e2ti1ka
pe1ti
2petim
2p1e2to
pet2t1i
pe2tűd
pe1tű
pe2ug
pe1u
pe2vő
2p1ezr
1pé
pé2c1su
péc2s
pé1dü2
pé2d1ü1lé
2pééb
pé1é
2péé1i
2péén
2p1é2ge
2p1é2gé
pégés3s
2p1é2hen
pé1he
2p1é2hes
2p1é2het
2p1éhs
2p1é2j.
pé1je2
pé2j1eg
2p1é2ji
2p1éjj
pé2k1as
pé1ka
pé2k1a1u
pé2k1á2
pé2k1er
pé1ke
pé2k1ék
pé1ké
pé2k1é2l
péké2t
pé2k1é1te
pé2k1i2n
pé1ki
pé2kis
2p1é2l.
2p1élb
pé2l2e.
pé1le
pé2le1i
pé2lek
péle2l
pé1l1e1le
pél1elme1i
pélel1me
pé2le2n
pé2let
2p1é2lé
2pélm
2p1éln
2p1é2lő
2p1élt
2p1é2lű
2p1élv
2p1é2ne2k1a2
pé1ne
2p1é2nekb
2p1é2nekd
2p1é2ne1ké
2p1é2nekf
2p1é2nekg
2p1é2ne1ki
2p1é2ne1kí
2p1é2nekj
2p1é2nek1k2
2p1é2nekn
2p1é2nekr
2p1é2nek1t2
2p1é2ne1kü
pé2ny1el
pén2y
pé1nye
pén2z1a2
pén2z1á2
pén2z2s
pé2p1i2p
pé1pi
2p1é2pí
2pé1pü
2p1érc
2p1érd
2p1é2ré
2p1érh
pé2ri2g
pé1ri
2p1é2rin
2p1érm
2p1érn
2p1é2r2ő.
pé1rő
2p1érr
2p1ér1te
2p1ér1té
2p1ér1tő
2p1érv
2p1érz
pé2s1aj
pé1sa
pé2sal
pé2s1e2l
pé1se
pé2sés
pé1sé
péskés2z1
pés1ké
pé2so
pés3s1za
pés2s2z2
pé2s1ü2t
pé1sü
pé2s1za
pés2z
pé2s3zaj
pész1ak
pés3zav
p2é2s1z1á2
pé2sz1emb
pé1s1ze
2p1é2szé1né
pé1s1zé
pé2szin
pé1s1zi
pé2s1z1o
p2é2s1z1ö
pész3s
pé2s1zu
pé2s1zú
pé2s1z1ű
pé1ta1
pé2tel
pé1te
2p1étk
2pétl
p1ét1la
2p1étr
2p1étv
2p1é2v.
2p1évb
2p1é2v2e.
pé1ve
2p1é2ve1i
2p1é2vek
pé2ven
pé2ves
2p1é2vet
2p1évf
2p1évh
p1évk
2p1évn
2p1évr
2p1évs
2p1évt
2p1évv
pé2zak
pé1za
pé2z1ár
pé1zá
pé2zel
pé1ze
pé2z1e2m
pé1zi2
pé2zi2d
pé2zin
pé2ziz
pé2zol
pé1zo
pé2z1sa
péz2s
pé2zu
p2f1ép
p1fé
pfi2ú1é
p1fi
pfi1ú
pfi2úkér
pfiú1ké
pf2lo
pf2ló
pf2lu
pf2rá
p1f2re
p1f2ri
p1f2rí
p1f2ro
pf2ru
pf1st
pg2ra
pg2ru
pg2rü
pha2de
p1ha
p2ha2i
1p2hanés2z
pha1né
1p2hed
p1he
phelyü2kü
phel2y
phe1lyü
1p2hi1a2
p1hi
p2hic
1p2hi1la
1p2ho1i
p1ho
phó2i
p1hó
1pi
pi2a1a
pi1a
pi2a1á
pi2a1ba
pia2ce2l
pia1ce
pia2cél
pia1cé
pia2cik
pia1ci
pia2c3se
piac2s
pi2ad
pi2a1e
pi2a1é
pi2ag
pi2ah
pi2aj
pi2aké2n.
pia1ké
pi2al
pi2am
pi2a1o
pi2a1ö
pi2ap
pi2a1ré
pi2a1s1za
pias2z
pi2at
pi2a1ú
pi2a1ü
pi2a1ve
pi2az
2picc
pi2c2e.
pi1ce
2picl
pi1da2
pi2dan
pi2de1a
pi1de
pi2de1á
pi2de1i
pi2den
pi2de1o
2p1i2dom
pi1do
2p1i2dő
2p1i2du
pi2eg
pi1e
pi2er
pi2ég
pi1é
pi1fr
2p1i2ga
2p1i2ge
2p1i2gé
2pi1go
2p1ihl
pi2k1ö
pi1l2i
pilis3s
2pilles
pil1le
2p1illet
2pillés
pil1lé
2p1il1lu
2p1i2l2y
2p1i2má
2p1i2mi
2p1imm
pinak2
pi1na
pina1p
2pind
2p1inf
pin2gas
pin1ga
pin2g1á2r
pin1gá
pin2gelj
pin1ge
pin2gép
pin1gé
pin2gos
pin1go
2p1in1gó
2pinj
2p1inp
pin2tác
pin1tá
pin2t1or
pin1to
pin2tőr
pin1tő
pin1tu2
pin2tur
pi2nü
2p1inv
2p1inz
pion1n
pi1o
pi2ó1a
pi1ó
pi2ó1á
pi2ó1e
pi2óf
pi2óg
pi2ó1ki
pi2ól
pi2ó1ma
pi2ó1mé
pi2ó1o
pi2óp
2pipa1i
pi1pa
2p1i2pa2r.
2p1i2pa1rá
2p1i2parb
2p1i2parh
2p1i2parn
2p1i2parr
2p1irh
2p1i2rod
pi1ro
pisa1u2
pi1sa
2pis1ko
pi2s1op
pi1so
2p1is1te
2p1i1s1za
pis2z
piszkos1s
pisz1ko
pi2t1aj
pi1ta
pi2ta1la
pi2tall
pi2t1a2n
pi2t1á2p
pi1tá
pi1tä
pi1t1he
pitos1s
pi1to
pi2t1ü2
pi2vás
pi1vá
2p1i2zé
1pí
pí2gé
pí2já
pí2né
pí2r1a2
pí2rá
pír1ál
pír1á2r
2p1írás
pí2r1e2
pí2r1in
pí1ri
pí2r1i2s
pí2rí
pí2ro2l
pí1ro
2p1í2ró
pí2r1ö2
pír1s
pírt2
pír1tr
pí2rü
pí2rű
2p1í2tél
pí1té
2pítm
pítő1a2
pí1tő
pítőe2l
pítő1e
pí2ve
pí2ze
pí2zé
pí2zü
pí2zű
pke1p2
p1ke
pke1s2
pkés1s
p1ké
pki1a2
p1ki
pki1á2
pki1e2
pk2la
pk2li
pk2lí
pk2lu
pk2rá
pk2re
pk2ré
pk2ri
pk2ro
pk2ró
pk2va
pk2vó
p2lacc
p1la
pla1k2l
pl2a2pal
pla1pa
plap1áll
pla1pá
pla2p1os2z
pla1po
p2latf
1p2laz1má
ple1í2
p1le
ple2t1a2n
ple1ta
ple2t1e2l
ple1te
plé1é2
p1lé
plé2has
plé1ha
pli2s3zá
p1li
plis2z
plo2m1e
p1lo
plo2mén
plo1mé
plo2m1ol
plo1mo
plót2
p1ló
pló1tr
plő2sa
p1lő
plő1s1tá
plő2szár
plős2z2
plő1s1zá
plu2m1e
p1lu
p2lur
pmás1s
p1má
pmeg1g2
p1me
1po
po2be
po1c1si2
poc2s
po2cs1iz
po2d2a.
po1da
po2d2z
pogás1s
po1gá
po2in
po1i
po1ki2
2p1o2k1ir
po2kí
2p1ok1le
po1k2ló
2p1okm
poko2la2
po1ko
pokol1l
2p1ok1ta
2p1ok1tá
polás1s
po1lá
pol2ca2n
pol1ca
polc3c
pol2c3s
2p1ol1da
poló1á2
po1ló
2poltár
pol1tá
2p1oltás
2p1ol1ti
2p1ol1tó
2p1olvas
pol1va
po2n1a
pon2c3so
ponc2s
po2ne2l
po1ne
pon2g1e
po1ni2
po2niz
pon3n
po2n1os2z
po1no
pon2t1a2d2
pon1ta
pon2tag
pon2t1a2l
pont1an2y
pon2tál
pon1tá
pon2tár
pon1te2
pon2t1el
pon2tem
pon2ten
pon2te2s
pon2ték
pon1té
pon2t1é2r.
pon2t1é1te
pon2ti2s2z
pon1ti
pon2tí
pon2t1ő2
2ponz
po2ol
po1o
po2p1a2
po2p1á2
2popc
2po2pe
pop1el
po1pi2
po2pik
po2p1in
po2p1ir
po2pis
p2o2p1o2l
po1po
pop1p2
pop1s
pops2z2
2pop1t2
po2pú
po2pü
po2r1a2c
po1ra
po2r1ag
po2rakk
por1a1la
por1all
po2ram
pora2n
por1an2y
po2r1a2r
po2rat
por1á2c2s
po1rá
por1áll
por2can
por1ca
por2c3h
por2c3sí
porc2s
po1re2
po2rec
po2re2s
por1f2
por1g2
po2rid
po1ri
2p1o2ri1e
po2rih
po2r1il
po2r1i2m
po2r1in
por1k2
po2rö
po2rő
por2tamb
por1ta
por2t1a1u
por2t1á2rá
por1tá
por2tárb
por2tárn
por2tej
por1te
por2t1em
por2tet
por2tél
por1té
por2t1é2r.
por2t1érk
por2t1i2k
por1ti
por2tiz
por2t1ív
por1tí
por2tur
por1tu
por2t1us2z
por2tut
por2túr
por1tú
por2tű
po2rü
2p1or1vo
pos3s1zo
pos2s2z
pos3s1zö
2postol
pos1to
2p1ost2y
po2sü
po2s1ze
pos2z
po2s1zí2
2p1oszl
posz2tü
po2tab
po1ta
po2tad
po2t1a2l
po2tan
po2t1a2u
po2t1e2l
po1te
po2t1inf
po1ti
po2t1ip
po2t1í2
po2tol
po1to
po2tö
po2tus
po1tu
po2tü
po2vi
1pó
pó2ce
pó1fr
pói2g
pó1i
pó2k1ass
pó1ka
pó2ka1u
pó2k1e2l
pó1ke
p2ó2kem
pó2ke2t
pó2k1id
pó1ki
p2ó2kim
póki2s
p2ók1is2z
p2ó2kiv
pó2k1ös
pó1kö
pó2k1ú
pó2l1an
pó1la
pó2l1á
pó2lom
pó1lo
2p1ó2n2é.
pó1né
pó1p2r
p1ó2rad
pó1ra
pór1á2s2z
pó1rá
pó1re2
pó2reg
pó2rem
p1ó2ri1á
pó1ri
pó2rö
p2ó2s1or
pó1so
pós3s
pó2s1ü2v
pó1sü
pó1ta2
pó2t1ad
pó2tal
p2ó2t1an
póté2r
pó1té
pó2til
pó1ti
pó1tü2
pó2tül
2póün
pó1ü
2p1ó2vó
pó2ze
pó2z3sá
póz2s
1pö
pö2ka
pö2ká
pö2ke2l
pö1ke
pö2ker
pö2ki2d
pö1ki
p1ö2ko
pö2ku
pö2kú
pö2lye
pöl2y
2pönt
pö2ra
p1ör1dö
pö2res
pö1re
3pörg
pö2ro
pö2r1ő
pö2rú
2p1öss
p1ötl
pötty1in
pöt2t2y
pöt1tyi
2p1ötv
pö2ve
1pő
pőa2n
pő1a
pőá2g
pő1á
pő1bl
pő1d1ro
pőe2r
pő1e
pőé2h
pő1é
pőé2l
pő1kl
pő1pl
pő1pr
2p1ő2r.
pő2r2i.
pő1ri
p1őrj
p1őrl
2p1őrn
p1őrs
2p1őrt
p1ő2rü
2p1őrz
pő1s2z
pp1a1da
p1pa
ppa1i2
ppa2j
pp1a1já
pp1akk
pp2a2na2d
ppa1na
ppa2n1ell
ppa1ne
ppan1k2
pp1an2y
pp1arc
ppa1s
pp1áll
p1pá
p2p1árj
p2p1á2rok
ppá1ro
pp1átm
p2p1á2to
pp1átv
p2p1e2g2y
p1pe
p2p1e2kéh
ppe1ké
p2p1e1lo
p2p1elr
ppe2r1a
ppe2ró
p2p1ég
p1pé
p2p1éks
pp2hó
ppin2g1e2
p1pi
pp1íg
p1pí
pp1kl
pp2las
pp1la
pp2lat
pp1o2l2y
p1po
p2p1o2rom
ppo1ro
ppor2t1á2r
ppor1tá
ppor2t1e2
ppor2t1ő2
p2p1ó2l
p1pó
pp1ó1rá
pp1ön
p1pö
pp1öv
pp1ö2z
p1p2ref
pp1re
p1p2rem
p1p2rez
p1p2rém
pp1ré
pp2rin
pp1ri
pp2ri1o
p1p2roc
pp1ro
p1p2rod
p1p2rof
p1p2rog
p1p2roj
p1p2ros
p1p2rot
pp2rób
pp1ró
pp1sp
p2p1úg
p1pú
p2p1ú2r.
pp1ví2
ppvíz1
1p2rax
p1ra
p2re1mi
p1re
1p2re1pa
pressz2b
pres2s2z
2pret
pre1t2á
1p2ré1di
p1ré
p2rép
pré2sa2
pr2é2s3zá
prés2z
p2ric2c2s
p1ri
p2rius
pri1u
p2rizm
prí2m1e2l
p1rí
prí1me
prí2mem
1p2robl
p1ro
1p2ro1ce
p2rof
1pro1fe
p2rog
p2roj
1pro1je
pro2mo
p2ro1pa
1p2ro1te
1p2ro1té
1p2ro1to
1p2ro1vo
1p2ró1bá
p1ró
pró2d1e
pró2d2z
1p2ró1za
pru2s1ág
p1ru
pru1sá
pru2se
1p2rüs
p1rü
p2s1a2d
p1sa
psé2gel
p1sé
psé1ge
p2s1i2ha
p1si
ps1í2ze
p1sí
p1s2ká
p1s2mi
p2s1ón
p1só
p2s1ö2z
p1sö
p1s2pe
ps2pi
p1s2po
ps1pr
ps2rá
ps3s1zó
ps2s2z
p1s2tá
ps2ti
p1s2tí
ps2to
p1st2r
ps2tu
p1s2tú
psza2k1ü2
ps2z
p1s1za
psz1an2y
p2sz1as
psz1atl
p2sz1ág
p1s1zá
p2s3záp
p2szég
p1s1zé
psz2fé
1p2szic
p1s1zi
p2sz1ill
pszis3s2
psz1k2
psz1old
p1s1zo
p2sz1orn
p2sz1ön
p1s1zö
p2sz1ös
psz3s
pta2d
p1ta
pt1a1da
pta2n1á2s
pta1ná
pta2ne
ptá2raj
p1tá
ptá1ra
ptá1ró2
ptá2rór
p2t1eg
p1te
p2t1e2k2e.
pte1ke
p2t1e2lő
p2t1els
pte2rál
pte1rá
pté2ká
p1té
pté2k1el
pté1ke
p2t1érd
pt1i2m2a.
p1ti
pti1ma
p2t1i2n
p2t1i2o
p2t1i2r
p2t1í2r
p1tí
pt1kl
p2t1öt
p1tö
p1t2rag
pt1ra
p1t2ran
p1t2rá
pt2ré
ptu2s1ze
p1tu
ptus2z
ptu2s1zi2
p2t1úrr
p1tú
p2t1üt
p1tü
1pu
pua2d
pu1a
pu2csor
puc2s
pu1c1so
pue2l
pu1e
2p1ugr
2pu1ká
pul2tas
pul1ta
2p1u2ni
2pu1no
2p1u2nó
puo2r
pu1o
pu1pr
pu2rak
pu1ra
pu2ral
pu2sad
pu1sa
pu2sal
pusa2n
pus1an2y
pu2sap
pu2sál
pu1sá
pu2s1átl
pu2s1e2
pu2s1ér1té
pu1sé
pu2sik
pu1si
pu2sis
pu2sí
pu2sol
pu1so
pu2sö
pu2ső
puss2
pus2s2z2
pust2
pu2sü
pu2szag
pus2z
pu1s1za
pu2szal
pu2s3zá2r.
pu1s1zá
pusz1é2p
pu1s1zé
pu2s1zö
pu2tab
pu1ta
pu2t1a2d
pu2tak
puta2n
2p1u2taz
put1ing
pu1ti
pu2tol
pu1to
pu2tód
pu1tó
2p1u2tó1p2
pu1t1rá
1pú
2p1úrf
pú2s2z
2p1útb
pú2té
2p1ú2ti
2p1útj
2p1útk
2p1útn
2p1ú2to
2p1útp
2p1útr
2p1útt
2p1útv
1pü
pü2ge
pü2g2y
pü2kü
2pü1le
püle2t1o2
2p1ünn
pü2re
2p1ü2rí
2p1üst
2p1ü2te
pü2té
2p1ütk
2p1ü2tö
pü2ve
pü2ze
1pű
pű2ző
pvá2r1a2l
p1vá
pvá1ra
pw2hi
pwhis1ky2
py2ba
py2bó
py2do
py2ho
py2ja
py2já
py2ka
py2ké
py2ki
py2ko
py2ma
py2na
py2ná
py2ra
py2ró
py1t2h
py2tó
py2va
py2vá
pze2t1a2
p1ze
pze2t1á2
pze2t1e2r
pze1te
pző1a2
p1ző
pző1e2
pző1é2
2q.
1qa
1qá
1qe
1qé
1qi
1qí
1qo
1qó
1qö
1qő
1qu
qu2a1e
qu1a
qu2at
qu2er
qu1e
qu2ez
que2zi
qu2éb
qu1é
qu2i1e
qu1i
qu2ij
qu2il
qu2in
qu2is
qu2od
qu1o
1qú
1qü
1qű
2r.
1ra
raa2d
ra1a
raáta2d
ra1á
raá2ta
ra2bad
ra1ba
rab1as2z
1rab1á2ra
ra1bá
rab1árb
rab1árr
ra2b1át
r2a2b1e1le
ra1be
ra2b1ell
ra2bid
ra1bi
rabi2g
ra2b1i1ga
ra2b1i1gá
ra2b1im
ra2b1i2n2a.
rabi1na
ra2b1i2ná
ra2bind
ra2b1int
2rab1la
r2a1b1ri
ra2buj
ra1bu
ra2c1aj
ra1ca
rac3c1sa
rac2c2s
ra2cet
ra1ce
ra2c1ha
rac2h
rac3hig
ra1c1hi
ra2c3hok
ra1c1ho
ra2c3hos
ra2c3hot
2r1a2cid
ra1ci
rac2kar
rac1ka
rac2kit
rac1ki
rac2k1ö2
rac2kü
ra2dag
ra1da
r1a2da1lé
rad2a2rad
r2adar
rada1ra
2rada1té
radás1s
ra1dá
ra2d2e.
ra1de
r2a3dí
2radíc
2radm
3r2a3dós2i.
ra1dó
radó1si
2r1a2dóz
r2a1d1rá
ra1d1ru
2r1a2d2u.
ra1du
ra2dus
rae2r
ra1e
raé2derb
ra1é
raé1de
raé2de1re
raé2derh
raé2dern
raé2derr
raé2dert
raé2r
rafikus1s
ra1fi
rafi1ku
ra1fl
2r1a2g2a.
ra1ga
ra2gak
rag1a1lo
r2agal
ra2g1á2c
ra1gá
ra2gá1é
2r1a2gán
rag2de
rag1di2
rag2din
ra2ge2l
ra1ge
ra2g1i2n2a.
ra1gi
ragi1na
2r1ag2y.
rag2y
ra2gyat
ra1gya
2r1agyn
ra2gyon
ra1gyo
2r1agyr
rai2o
ra1i
2rai1zá
2rai1zi
2raizn
2rai1zó
2rai1zu
ra2j1a2d
ra1ja
ra2j1a1u
ra2j1á2ru
r2ajár
ra1já
ra2j1es
ra1je
ra2jin
ra1ji
2rajká1ró
raj1ká
2r1aj1kú
rajob2
ra1jo
raj2t1e2l
raj1te
raj2t1en
raj2zat
raj1za
raj2z1e
raj2z1ón
raj1zó
raj2z3s
2r1a2ka1ra
ra1ka
rak2kép
rak1ké
rak2kis
rak1ki
ra1k2li
ra1k1lo
ra1k1ló
ra1k1lu
2r1aknáb
rak1ná
2r1akná1i
r1a2kóz
ra1kó
r2ak2re
r2a1k2ré
r2a1k2ri
rak2rom
rak1ro
raktus1s2
rak1tu
2r1a2kus
ra1ku
r2alab
ra1la
2r1a2la2g1
2r1a2la1ku
ra2la1pa
r1a2la1pú
ra2lá1zá
ra1lá
ra2lá1zó
2r1alb
2r1alja2i.
ral1ja
ralja1i
2r1al1ji
2ralk
ralla2k
ral1la
ral2l1a1kó
ral2l1at
ral1lá2
ral2lág
ral2lál
ral2lev
ral1le
ralo2m1e
ra1lo
ra2mab
ra1ma
ram1a1da
r2amad
2rama1i
ram1akk
rama2l
ram1a1la
ra2m1an2y
ram1áll
ra1má
ra2m1á2rá
ra2m1árb
ra2m1á2re2
ra2m1árh
ra2m1árn
ra2m1árr
ram1b
ra2m1é1he
ra1mé
ram1i1de
ra1mi
ram1i2ko
ram1ill
ra2m1im
ram1i1na
ram1inf
ram1ing
ram1inj
ra2m1i2p
2rammb
2rammet
ram1me
2ram1mi
2rammj
2rammn
2rammr
2rammt
ra2m1os2z
r2amos
ra1mo
ra2m1ó2r
ra1mó
ra2mö
ra2mő
ra2mü
2r1a2nal
ra1na
2r1a2nat
ranás1s
ra1ná
ran2csal
ranc2s
ran1c1sa
ran2cs1ág
ran1c1sá
ran2c1se
ran2csik
ran1c1si
ran2c1ső2
rancs3z
2ran2d.
ran2dat
ran1da
ran2d1a1u
2randj
ran2d2z
ra2nek
ra1ne
ran2g1a2
ran2g1á
ran2ge
rang1e2l
ran2g1ó2
ran2gye2
rang2y
ra2nil
ra1ni
ra2nim
2ran1ka
ran2szál
rans2z
ran1s1zá
ran2s1ze
ransz1om1b2
ran1s1zo
2ran2y.
ran2y
ra2nyag
ra1nya
rany1a2la
ra2ny1a2n
ra2nyap
ra2ny1ar
r1a2nyáit
ra1nyá
ranyá1i
r1a2nyá1ka2
r1a2nyákh
r1a2nyákk
ra2ny1ál
ra2nyás
r1anyá2s.
2ranyb
ra2ny1e2s
r2a1nye
2ra1nyé
ra2nyél
2ranyh
ra2nyid
r2a1nyi
ra2nyin
ra2nyir
rany1í2r
r2a1nyí
2ranyk
2ranyn
ra2ny1ol
r2a1nyo
2ra2nyó
rany1ó2r
ra2nyö
2ranyr
2rany1s
2ranyt2
r2a2ny1ü
2ran1za
ra2ó1a
ra1ó
ra2ó1á
ra2óf
ra2ó1lá
ra2óm
ra2óp
ra2ós2z
2rap2a.
ra1pa
ra2pák
ra1pá
2rapp
rap2pin
rap1pi
ra1p2re
ra1p1ré
ra1p2ri
r2a1p2ro
2r1a2rann
1ra1ra
2r1a2ran2y.
raran2y
2r1a2rany2a.
rara1nya
2r1a2ra1nyá
2r1a2ranyb
2r1a2ranyn
2r1a2ranyr
2r1a2ranyt2
ra2rat
2r1ar1co
ra2rén
ra1ré
2r1art
2r1arz
r2a1s2ká
ra1s2l
ra1s2pe
ra1s2po
ras1s1z1e
ras2s2z
2r1asszoc
ras1s1zo
rast2
r2a1s2ta
r2a1s1tá
r2a1str
ra2sz1abl
r2aszab
ras2z
ra1s1za
ra2sz1aj
ra2szas
ra2szat
ra2szág
ra1s1zá
ra2sz1árr
rasz1emb
ra1s1ze
ra2sz1étt
r2aszét
ra1s1zé
ra2szób
ra1s1zó
ra2szó1i
ra2szón
ra2szös
ra1s1zö
rasz2t1a2n2y
rasz1ta
rasz2t1el
rasz1te
rasz2t1é2te
rasz1té
rasz2tö
rasz2tü
ra2t1a2d
ra1ta
rat1ajt
rat1a2la
rat1alt
rat1a1rá
r2atar
2r1a2tád
ra1tá
rat1áll
r2atál
ra2t1árad
r2atár
1ratá1ra
ra2t1á2ra1i
ra2t1á2rak
ra2t1árb
ra2t1árv
ratá1t1a2
ratá2vi
r2atáv
ra2t1e2g
ra1te
ra2t1elk
ra2t1ell
2r2atég
ra1té
rat1é1ge
ra2t1érd
r2atér
rat1ér1ke
ra2tér1te
ra2t1érv
rat1ing
r2atin
ra1ti
r2a2t1ír
ra1tí
ra2t1í1ve
2r1at1ká
2rat1lé
2rato2z.
ra1to
ra1t2ran
rat1ra
2r1attr
ra2tür
ra1tü
2rat2y
ra2tya
ra2tyá
ra2tyu
ra2u1di
ra1u
2r1a2uk
ra2u1lá
2raum
rau2n
rau2ta
r2au2tá
ra2u1to
2r1a2u1tó
r1autó1é2
raü2t
ra1ü
raü2z
2r1a2va1tó
ra1va
2ra1vú
ra2xü
ra1ye
ra1yé
raz1ajt
ra1za
raza2n
ra2z1an2y
ra2zel
ra1ze
ra2z1olt
r2azol
ra1zo
ra2zo1no
1rá
rá1b1re
rá2cal
rá1ca
rá2c1e
rá2c3h
rá2c1i2n
rá1ci
ráci2óf
ráci1ó
rá2cö
rá2csa1la
rác2s
rá1c1sa
rá2c3ság
rá1c1sá
rá2csét
rá1c1sé
rá2csir
rá1c1si
rá2cs1is
rá2cú
rá2cü
rá2d1an2y
rá1da
2rádáh
rá1dá
2rád1dá
2rád1ju
2ráf1ha
2ráf1he
2ráf1hi
2rá2f1i2d
rá1fi
2ráf1re
rá2g1ad
rá1ga
2r1á2gam
2r1á2gaz
rág1á2zá
rá1gá
rá2gi1a
rá1gi
rá2gil
rá2gim
rá2gis
2rá1gí
rá2g1os2z
rá1go
rágus4
rá1gu
rá2gü
2rá1gya
rág2y
2rágyáb
rá1gyá
2rágyá1é
2rágyáh
2rágyá1i
2rágyáj
2rágyák
2rágyán
2rágyár
2rágyás
2rágyát
2rágyáv
rá2gyu
ráí2r
rá1í
ráj2k1a2
ráj2kel
ráj1ke
ráj2k1ó2
rá2jü
rá2kát
rá1ká
rá2k1ered
rá1ke
ráke1re
rá2k1e2s
rá1k1la
2rákog
rá1ko
rá2k1os2z
rá2k1ón
rá1kó
rá2k1ó2r
1rá1k1rá
rá1krét
rák1ré
rá2l1ak
rá1la
r2á2l1a2l
rá2la1na
2r1álar
rála2t1e
rá2lál
rá1lá
rál1átl
r1ál1cá
2r1ál1dá
rá2li2d
rá1li
rá2lim
rá2lí
2r1állam
rál1la
2r1állap
2r1állat
2r1ál1lí
2r1állom
rál1lo
2r1ál1ma
rá2lü
rá2lyal
rál2y
rá1lya
rá2ly1a2n
rá2lya1p
rá2ly1as
rá2lyav
rá2lyús
rá1lyú
rá2lyút
rá2mö
rá2nal
rá1na
rá2n1a2n
rá2n1a2t
rá2n1ár
rá1ná
rá2nás
ránc1c
rá2nék
rá1né
rán2gál
rán1gá
rá2niz
rá1ni
rá2ní
rán1kl
rá2nol
rá1no
rá2not
rá2n1ó2
rán2sav
rán1sa
ráns3s
rán2t1ak
rán1ta
rá2nü
rá2ny1a2d
rán2y
rá1nya
rá2nyag
rá2nya1ko
rány1alt
rá2ny1a2n
rá2ny1ap
rá2nyar
rá2nyaz
rá2ny1á2r.
rá1nyá
rá2nyérm
rá1nyé
rá2nyérz
rá2ny1í2r
rá1nyí
rá2nyol
rá1nyo
rá2nyó
rá1p2l
2rá1po
rá2pol
rá1p2r
2r1á2rad
rá1ra
rár1a2dá
2r1á2ra1i
2r1á2ram
2r1á2ras
2r1á2rat
rá2raz
rá2ráb
1rá1rá
rá2ráh
rá2r1ál
rá2rár
rá2rát
rá2ráv
rá2r1em
rá1re
rá2rér
rá1ré
2r1árh
rá2ria1i
rá1ri
rári1a
2r1á2ri1á
rá2rin
2r1árj
2r1árl
2r1árn
rá2r1ol
rá1ro
r1ár1tó
2rá1ru
rá2rul
rá2run
2rá1rú
rá2r2ú.
rá2rút
rá2rúv
rá2s1a2d
rá1sa
rá2s1akn
rá2sal
rása2r
1rás1a1rá
rá2s1as
2r1á2sa1tá
rá2s1a2to
rá2saz
rás1á2ga
rá1sá
rá2s1á2rad
rásá1ra
rá2s1árak
1rá2s1á2rá
rá2s1á2ré
rá2sás
rá2ser
rá1se
rá2sis
rá1si
rá2s1ol
rá1so
2r1á2s2ó.
rá1só
2r1á2sób
2r1á2só1é
2r1á2sóg
2r1á2sóh
2r1á2só1i
2r1á2sój
2r1á2sók
2r1á2són
rás1ó2ra
rá2sór
2r1ásó1ró
2r1á2sós
2r1á2sót
2r1á2sóv
rá2ső
rást2
rás1tr
rá2su2t
rá1su
rá2sza2n
rás2z
rá1s1za
rá2sza2s
rá2sz1á2ru
rá1s1zá
rá2sziv
rá1s1zi
rás3zón
rá1s1zó
rá2tad
rá1ta
rát1a2da
rát1a1la
r1á2t1ál
rá1tá
rá2té2g
rá1té
rá2t1é2l
2ráté2p
rá2t1i2o
rá1ti
rá2tí
2r1át1lé
rá1t1ri
rá1t1ro
rátus1s2
rá1tu
rá1tú2
rá2túr
2r1á2t1ül
rá1tü
rá2zal
rá1za
2rázisb
rá1zi
2rázi1sé
2rázish
2rázisk
2rázisn
2rázisr
2rázis3s2
2rázist2
rázi2s2z
rá2zsal
ráz2s
rá1z1sa
rá1z4sá
rá2z1s1e2
rá1z1sé2
rá2zsén
rá2zsi2a
rá1z1si
rá2zsis
rá2z1só
rá2z1s1ü2
rba2j1e
r1ba
rba2jokt
rba1jo
rba2n1á
rbátyá2t
r1bá
rbát2y
rbá1tyá
rbá2ty1á1to
r2b1ell
r1be
r1b2la
r1b2lo
rb2lú
rboc1c
r1bo
r2b1ol1da
rbo2n1a2
r1b2ra
r1b2rá
rb2ri
rb2ro
r1b2ró
rb2ru
rbu2t1e
r1bu
rc1a1dá
r1ca
rc1ajt
rca2lak
rca1la
r2c1alk
r2c1a1na
rc1a1nya
rcan2y
rca1p
rc1a1ra
rc1a1rá
rc1ass
rc1a2to
r2c1ág
r1cá
r2c1á2l
rc1á2r.
r2c1á1ri
r2c1árn
r2c1á2ro
r2c1árr
r2c1árt
r2c1á2ru
rc3c1si
rc2c2s
rc3c1sí
rc3c1so
rc3c1só
rc3c1sö
rce2g1a2
r1ce
rceg1g
rc1eg2y
rc1e2leg
rce1le
rc1elk
rc1e2lőd
rce1lő
r2c1els
r2c1ember
rcem1be
r2c1e1pe
rc1e2ve
r2c1ex
r2c1ez
rc1é2két
r1cé
rcé1ké
r2c1é1le
r2c1é2lé
r2c1é2li
r2c1élm
r2c1é2lü
r2c1ép
r2c1é2r.
r2c1érb
r2c1é2ri
r2c1érr
rc1és2z
rc1fr
rc1gr
r2c3has
rc2h
r1c1ha
rc3hel
r1c1he
r2chen
r2chig
r1c1hi
r2chom
r1c1ho
r1c3hu
r2c3hú
r2c3hű
rci2d
r1ci
r2c1i1de
r2c1i2ko
r2c1ikr
rc1ill
r2ci1má
r2c1i1mi
rc1i2n2a.
rci1na
rci2náb
rci1ná
rci2ná1é
rc1i2nár
r2c1i2nát
r2c1in1d2
rc1i2o1ni
rci1o
r2c1i2pa
r2c1i1si
r2c1isk
r2c1ist
rc1izm
rc1i2zo
r2c1íj
r1cí
r2c1ív
r1c2k2é.
rc1ké
r1c2kéh
r1c2ké1i
r1c2két
r1c2k2i.
rc1ki
r1c2ki1a
r1c2kig
r1c2kok
rc1ko
rc1k2ré
rc1kv
r1c2lu
r2c1op
r1co
r2c1o2r
r2c1os2z
rc1ón
r1có
rc1ó2r
rc1óv
r2c1öb
r1cö
r2c1ön
r2c1ör
r2c1ös
rc1őr
r1cő
rc1pl
rc1pr
r2cs1abl
rc2s
r1c1sa
rcs1alak
rcsa1la
rcsa2p1á2g
rcsa1pá
r2c3sa1ra
r2csarl
r2c3sáp
r1c1sá
r2c3sá2r.
rcs1á1ru
r2c3sej
r1c1se
r2csele1me
rcse1le
r2c2s1elr
r2c2s1érb
r1c1sé
r2csikr
r1c1si
rcs1írás
r1c1sí
rcsí1rá
rc3s2ká
r2c3so1ra
r1c1so
r2c3so1ro
rcsóna2k1á2
r1c1só
rcsó1na
rcs1ő2s.
r1c1ső
rcs1p
rc3s2pi
rcs1s
rc3s1to
rc3s2z
rc1tr
r2c1ud
r1cu
r2c1ur
r2c1ut
r2c1új
r1cú
rc1üg
r1cü
r2c1ür
r2c1üs
r2c1üt
rc1üv
rc1üz
rc1űz
r1cű
r2c3zá
rc2z
r1c3zó
rc3z2s
r1c3zu
r1c3zú
rda2cél
r1da
rda1cé
rd1a2dat
rda1da
r2d1akc
rda2l1í2
rd1al1ka
rd1al1ko
r2d1a2nya
rdan2y
rda1p
rd1a2rán
rda1rá
r2d1arc
rd1aszt2
rdas2z
r2d1a1zo
rd1ác2s.
r1dá
rdác2s
rd1á2rak
rdá1ra
rd1á2rat
rd1á2ron
rdá1ro
r2d1árv
rdás1s
rd1bl
rd3d2z
r2d1e2g
r1de
rde2k1a2
rde2ker
rde1ke
rde2kék
rde1ké
rde2kol
rde1ko
rde2k1ö2
r2d1e1la
rde2sőt
rde1ső
rd1exp
rdező2p3
rde1ző
r2d1é2g
r1dé
rd1é2lé
rd1élr
rd1élt
rd1é2lü
r2d1ép
r2d1é2r.
r2d1ér1d2
r2d1é2ri
rdé2sa2
rdé2s1za
rdés2z
r2d1é2vé
rd1fr
r2d1i2d
r1di
r2d1i2ga
r2d1i1gé
r2d1i1ma
r2d1i2n2a.
rdi1na
r2d1i2na1i
r2d1i2ná2t.
rdi1ná
rdio2x
rdi1o
rd1i1zo
r2d1íg
r1dí
r2d1íz
rd1kl
r2d1ok1ta
r1do
r2d1old
rd1orr
r2d1os2z
r2d1o1u
rdó1a2
r1dó
rd1ó2rá
rdö2g1öl
r1dö
rdö1gö
r2d1öl
r2d1ös
rd1öv
rdő2s1orr
r1dő
rdő1so
rd1pr
r1d2ram
rd1ra
rd2rap
r1d2raz
rd2rog
rd1ro
r1d2rót
rd1ró
rd1sk
rd1sp
rd1st
rdsza2k1
rds2z
rd1s1za
rd1t2r
rd1udv
r1du
r2d1u1rá
rd1üg
r1dü
r2d1üt
r2d3za
rd2z
r2d3zá
r1d3zo
r1d3zó
r2d3zö
r1d3z1sí
rdz2s
r2d3zú
1re
rea2d
re1a
rea2j
rea2ka
rea2la
rea2r
2reá1lá
re1á
2reá1ló
2r1e2b.
2r1eb1bő
reb1eg2y
re1be
2r1e2béd
re1bé
2r1ebh
2r1ebk
2r1ebm
2r1ebn
2r1ebs
2r1ebv
re2caj
re1ca
re2cal
re2cá
2r1e2cets
re1ce
re2c2h
rec3sar
rec2s
re1c1sa
2r1e2cset
re1c1se
rec3sor
re1c1so
re2c2z
re2dan
re1da
red1elv
re1de
2redend
re2d1e2r
2rede1ti
2rede1tű
2r1e2dén2y
re1dé
re2d1é2ves
redé1ve
re2dir
re1di
re2dor
re1do
2r1edzőb
red2z
re1d1ző
2r1e2dzőj
2r1e2dzők
2r1e2dzőr
2r1e2dzőt
2r1e2dzőv
re2et
re1e
re2ew
2reff
re2gap
re1ga
re2gas
reg1áll
re1gá
re2gár
reg1e2le
re1ge
reg1elh
reg1ell
re2g1elr
reg1elv
reg1eng
reg1e2te1tő
rege1te
reg1e2vé
re2gés2z1
re1gé
re2gid
re1gi
reg1inj
re2giz
re2góc
re1gó
reg2óv
re2gú
regü2l
re1gü
re2g1ü1lé
2r1eg2y.
reg2y
2r1egyb
re2gy2e.
re1gye
re2gyed
re2gyen
2r1e2gyes
re2gyet
2r1e2gyez
2r1e2gyé
2r1egyh
2r1egyk
2r1egyl
2r1egyn
2r1egyr
2r1egys
2r1egyt
re2gyün
re1gyü
r1egyv
rei2rá
re1i
re2ja
2rejé1é
re1jé
2rejű1e
re1jű
2rejűn
2rejűr
2rejűt
re2k1ag
re1ka
re2k1a1rá
re2k1as2z
re2k1á2l
re1ká
re2k1á2p
re2k1á2r.
re2kás
rek1elh
re1ke
rek1elt
rek1e1lü
rek1erd
re2k1e2rő
reke2s1z1á
rekes2z
2r1e2kééh
re1ké
reké1é
re2kéj
re2kék
2r1eké2k.
rek1ékh
re2kél
2r1e2kénk
re2k1é2r.
re2k1érd
re2k1ér1te
2r1e2ké2s.
re2k1éss
re2k1id
re1ki
re2kij
re2kil
re2k1ing
re2k1int
re2k1ip
re2kír
re1kí
rek1k2
re1k1lu
re2k1ok
re1ko
re2k1old
rek1opt
re2k1os
rek1ott
re2kór
re1kó
re2kö2k
re1kö
re2kötl
re2kő
rek1s
reks2z2
re2k1u2s
re1ku
re2küd
re1kü
re2k1üg
rekü2l
re2k1ü1lé
re2k1ü1lő
re2kü2t
2r1e2l1a2d
re1la
2r1e2lág
re1lá
2r1el1bí
2r1el1bo
2r1elc
2r1el1do
2r1e2lef
re1le
2r1elektr
rele2ma
2r1e2lem2e.
rele1me
2r1e2lemed
rele2meg
2r1e2leme1i
2r1e2lemek
2r1e2leme2m.
2r1e2le1me1me
2r1e2lememm
2r1e2lemen
rele2m1er
2r1e2leme2s.
2r1e2leme1se
2r1e2lemesn
2r1e2lemet
re2leme1zé
rele2m1é2r.
rele1mé
2r1e2lemtel
relem1te
2r1e2lemük
rele1mü
2r1e2lemünk
2r1e2lem1zé
2r1e2lem1ző
2r1e2l1eng
2relér
re1lé
2r1elf
2r1el1ha
2r1el1há
2r1elhel
rel1he
2r1el1hú
2r1el1já
2r1el1ké
rel1la2
rel2l1an
2r1el1lá
2r1el1lő
2r1el1ma
2r1elmé1é1é
rel1mé
relmé1é
2r1elmééh
2r1elmé2i.
relmé1i
2r1elmél
2r1elmé1te
2r1elnev
rel1ne
2r1el1nö
2r1eln2y
2r1e2l1os
re1lo
2r1e2lőad
re1lő
relő1a
2r1e2lőde1i
relő1de
2r1e2lődök
relő1dö
2r1e2lőnn
2r1elő1nye
relőn2y
2r1e2lőzm
2r1el1so
2r1el1s1zá
rels2z
2r1el1ta
rel2tár
rel1tá
2r1eltáv
2r1eltet2t.
rel1te
2r1eltettk
2r1elté1ré
rel1té
2r1eltév
2re1lú
2r1e2l1ül
re1lü
2re1lű
2r1el2v.
2r1el1vá
2r1elvb
2relved
rel1ve
2r1elve1i
2r1elvek
2r1elvem
2r1elven
2r1elves
2r1elv2é.
rel1vé
2r1elvé1ne
2r1elvér
2r1elvé1ü
2r1elvév
2r1elvh
2r1el1vi
2r1elvk
2r1elvn
2r1el1vo
2r1elvr
2r1elvs
2r1elvt
2r1el1vü
2r1el1vű
2r1elvv
2r1elz
re2m1a2d
re1ma
re2m1as2z
re2maz
re2m1á2l
re1má
re2mát
2rembar
rem1ba
2r1embe2r.
rem1be
2r1emberb
2r1ember2e.
1rembe1re
2r1embered
2r1embere1i
2r1emberek
2r1emberes
2r1embe1ré
2r1emberh
2r1embe1ri
2r1emberk
2r1embern
2r1emberr
2r1ember3s2
2r1embert2
2r1embe1rü
rem1b1le
r1em1bó
2rembr
re2mel
re1me
rem1e2lem
reme1le
2r1eme1lé
2r1emelg
r1e2melk
rem1előd
reme1lő
rem1els
re2m1eng
re2m1es2z
re2m1é2r.
re1mé
re2m1érm
re2m1é2te
re2migr
remi2g
re1mi
re2m1ór
re1mó
re2mö
re2m1ő2r
re1mő
rem1p2
2r1e2muk
re1mu
2r1e2mul
re2m1ur
2r1e2mus
2r1encik
ren1ci
ren2d1e2r
ren1de
ren2d1ég
ren1dé
ren2déj
ren2dék
ren2d1o
ren2d1ö
ren2d1ő2s
ren1dő
2r1e2ner
re1ne
renés3s
re1né
2r1enged
ren1ge
2re1ní
ren2s1ég
ren1sé
rens3s
re2of
re1o
re2ó1a
re1ó
re2óc
re2ó1e
re2ó1é
re2óf
re2ó1ka
re2ól
re2ó1o
re2óp
re2ó1sá
re2ós2z
re2p1aj
re1pa
rep1a1na
rep1as2z
re2pa1u
re2paz
rep1áll
re1pá
rep1álm
re2pá2s
re2pát
re2p1e2g
re1pe
re2p1elf
re2p1elk
re2p1ell
re2pelm
re2p1eng
re2p1ep
2r1e2pe2r.
re2p1er1k2
2r1e2péd
re1pé
re2p1é2j
2r1e2pénk
2r1e2pés
re2p1i2d
re1pi
rep1il1le
rep1int
re2pok
re1po
2r1epos2z.
repos2z
2r1eposz2t.
rep1osz1tó
re1pö2
re2p1ö1rö
rep1pl
rep1pr
re2pú
2r1erd
re2re1i
1re1re
re2rej
re2res
re2rez
2r1erg
re2róz
re1ró
2r1e2rő
2r1ers
re2sas
re1sa
re1s2c
2r1ese1mé
re1se
res1epr
rese2t1e2l
rese1te
2r1esél
re1sé
2r1es1kü
r1es1si
re2szaj
res2z
re1s1za
2r1e2szet
re1s1ze
resz1ív
re1s1zí
2resz1kö
2reszm
resz1ta2
resz2t1an
resz2tál
resz1tá
resz2t1ár
resz2t1í2v
resz1tí
resz2t1o2r
resz1to
resz2t1os
2resz2ű.
re1s1zű
2r1e2szű1e
re2t1ab
re1ta
re2t1akt
re2tal
ret1a2la
re2tant
re2t1a2n2y
re2tág
re1tá
re2t1á1ra
re2t1á1ro
ret1átf
re2t1elb
re1te
re2t1ell
re2telm
ret1e2mel
rete1me
re2t1er1k2
rete2s1z1á
retes2z
2r1e2teté2s.
rete1té
2r1e2teté1sé
2r1e2tetésn
re2t1é2k
re1té
re2t1él
re2t1érb
re2t1érd
re2ti2d
re1ti
2r1e2ti1ka
re2ti1ká
re2t1ill
r1e2tilt
re2t1ing
re2t1int
re2tis
r1etnik
ret1ni
re2t1ok
re1to
re2t1old
re2t1os
re2tór
re1tó
re2t1ö1rö
re1tö
re2t1ö2v
ret2t1est
ret1te
ret2té1ne
ret1té
ret2t1in
ret1ti
re2t1ut
re1tu
re2t1ült
re1tü
re2tűd
re1tű
re2ud
re1u
re2u1te
re2ve1ző
re1ve
2r1e2vő
2r1exp
2rezetn
re1ze
2reze1tü
2rezor
re1zo
2rezőkh
re1ző
2r1ezr
2r1e2züs
re1zü
1ré
ré2bá
ré2bis
ré1bi
ré2biv
ré2bí
2r1ébres
réb1re
ré2bü
2r1é2des
ré1de
2ré1fá
2r1égb
ré2gét
ré1gé
2r1égn
ré2g1ó2
ré2gő
2régt
2r1é2h.
2r1é2hen
ré1he
2r1é2hes
2r1é2het
2r1éhs
2r1éht
ré2jen
ré1je
2r1é2jet
2r1é2jün
ré1jü
ré2k1ab
ré1ka
ré2kac
ré2k1a2g
ré2k1a2n
ré2k1a1u
rék1á2sá
ré1ká
ré2k1e2b
ré1ke
ré2k1eg
ré2kel
rék1e1le
rék1e1lő
rék1elr
rék1els
ré2k1es2z
ré2k1é1te
ré1ké
ré2kik
ré1ki
ré2kin
ré2kit
rék1o1la
ré1ko
rék1old
ré2kör
ré1kö
ré2k1ö2v
ré2köz
ré2kő
ré1ku2
ré2k1ut
ré2k1ú
ré2le2t.
ré1le
ré2letb
2r1é2le1te
ré2le1té
ré2leth
ré2letn
ré2letr
ré2lett
ré2le1tü
2r1élf
2r1élm
2rél2y
ré2ma1d2
ré1ma
ré2m1a2l
ré2m1an
ré2mar
ré2ma1u
ré2m1á2l
ré1má
ré2már
ré2mel
ré1me
rém1e2le
ré2mer
1ré2m1é2ré
ré1mé
ré2mil
ré1mi
ré2m1ist
ré2mos
ré1mo
ré2mó
ré2mö
2rém1tu
réna1p2
ré1na
2r1é2nekh
ré1ne
2r1é2ne1ki
2r1é2nekl
2r1é2ne1kü
ré2nel
2réner
ré2n1é2j
ré1né
ré2nö
ré2ny1e2l
rén2y
ré1nye
ré2p1ed
ré1pe
ré2peg
ré2pes
ré1pi2
ré2p1ip
2r1é2pí
ré2pol
ré1po
ré2pö
ré2pü
2r1épül
2r1é2r.
2r1érb
2r1érc
2r1érd
2r1é2ret
ré1re
2r1é2rez
ré2r2é.
1ré1ré
ré2réb
ré2rén
ré2rér
ré2ré2s.
ré2ré1se
ré2ré1sé
ré2résh
ré2ré1si
ré2résk
ré2résn
ré2résr
ré2réss
ré2rést
ré2ré1sü
ré2rét
2r1érf
2r1érh
ré2ri1e
ré1ri
ré2ri2g
2r1érk
2r1érl
2r1érm
2r1érn
2r1é2r2ő.
ré1rő
ré2rők
2r1érr
2r1ér1tá
2r1ér1te
2r1ér1té
2r1ér1tí
2r1ér1tő
2r1é2rü
2r1érv
2r1érz
ré1sa2
ré2sal
ré2sar
ré2s1ár
ré1sá
ré2sát
ré2seg
ré1se
rés1ell
ré2s1er
ré2sés
ré1sé
ré2s1ő2
rés3s1za
rés2s2z2
ré2sú
ré2s1ü2t
ré1sü
ré2s1ü2v
rész1a2l
rés2z
ré1s1za
ré2sza2n
rés3zav
rész1á2l
r2é1s1zá
ré2s2z1eml
ré1s1ze
ré2s2z1e2s2z
ré2sz1é2k
ré1s1zé
ré2szin1te
ré1s1zi
rész1í2v
ré1s1zí
ré2szok
ré1s1zo
ré2szo2l
ré2s1zó
rész1ó2r
ré2szőr
ré1s1ző
rész3s
rész1t2r
ré2s1zu
ré2s1zú
részü2l
ré1s1zü
ré2sz1ü1lő
2rétáz
ré1tá
ré2t1eg2y
ré1te
2ré2tel
rét1elm
2réter
ré2t1e2s2z
2r1é2te1tő
ré2t1é2k
ré1té
ré2tiz
ré1ti
ré2t1o2l
ré1to
ré2t1os
1ré1t1ré
ré2tud
ré1tu
ré1vá2
ré2vát
2rév2e.
ré1ve
2r1é2ve1i
rév1e1ké
ré2v1é2l
ré1vé
ré2v1é1ri
2révé2t
ré2v1é1te
2r1é2vév
2révf
2révh
2révt
ré2vú
2révv
ré2z1a2
ré1zá2
ré2z1ár
ré2z1e2g
ré1ze
réze2l
réz1e1lő
ré2zer
ré2zes
réze2t
réz1e1te
ré2zi2d
ré1zi
ré2zin
ré2zi2o
ré2zip
réz1is2z
ré2zos
ré1zo
ré1zó2
ré2z1ór
ré2zö
ré2z1sa
réz2s
ré2z3sá
ré2z3se
ré2z3si
ré2z1sí
ré2z1so
ré2z1su
rf1ál
r1fá
r2f1ép
r1fé
rfé2s1za
rfés2z
rf2é2s1zá2
rf1fl
rfia2n
r1fi
rfi1a
rfi1b2
rfid2
rfi1i2
rfik2
rfi1kr
r2f1ind
rf1isk
rfi1s2z2
rfit2
rfi1tr
rf1kl
rf1kr
rf2la
rf2lo
rf2ló
rf2lu
rf1okt
r1fo
rf1ő1rü
r1fő
rf1pr
r1f2rá
rf2rí
r1f2rö
rf1sp
rf1st
r2f1út
r1fú
rga1k2
r1ga
rgá2csal
r1gá
rgác2s
rgá1c1sa
rgá2csan
rgá2csav
rgá2z1é
rge2o
r1ge
rge2r1a
rgés3s
r1gé
rg2h2a.
rg1ha
rgi2ai1a
r1gi
rgi1a
rgia1i
rgi2a1ko
rgi2al
rgi2a1so
rg2il
rg2la
rg2le
rg2lo
rg2ló
r1g2nó
rg2öl
r1gö
rg1ö3le
rgő1c1sa2
r1gő
rgőc2s
rgő2z1ölt
rgő1zö
rgő2z1ő2s
rgő1ző
r1g2rá
r1g2ru
r2gya1do
rg2y
r1gya
r2gy1aj
r2gyalak
rgya1la
r2gy1alap
r2gy1al1gá
r2gy1alk
r2gy1an2y
r2gyap
r2gyarc
r2gy1as2z
r2gya1u
r2gy1az
r2gyál
r1gyá
r2gy1á2z
r2gy1eg
r1gye
r2gy1e2l
r2gy1enc
r2gy1e2s
r2gy1és
r1gyé
r2gyé1vé
r2gyim
r1gyi
r2gy1int
r2gyip
rgy1i1ra
r2gy1ok
r1gyo
r2gy1öz
r1gyö
r2győr
r1győ
rhajóé2r
r1ha
rha1jó
rhajó1é
rhatá2s1út
r3ha1tá
rh2atás
rhatá1sú
rháza2d
r1há
rhá1za
rhá2z1a1da
rhá2zal
rhá2zip
rhá1zi
rhá2z3s2
rhitköz1
r1hi
rhit1kö
1r2ho2e1á
r1ho
rho1e
1ri
ri2a1a
ri1a
ri2a1á
ria1b
ri2a1ba
ri2a1bá
ri2a1bo
ri2ac
ri2a1e
ri2a1é
ria1f
ri2afag
ria1fa
ri2afr
ri2a1g2
ri2ah
ri2ai2k
ria1i
ri2a1í
ri2aj
ri2aké2n.
ria1ké
ri2a1kó
ri2a1la
ri2a1lá
ri2a1lu
ri2am
ri2a1na
ri2a1o
ri2a1ó
ri2a1ö
ri2a1ő
ri2a1p2
ri2a1rá
ri2a1ré
ri2a1s1za
rias2z
ri2a1s1zá
ri2a1s1zi
ri2a1s1zo
ri2a1té
ri2a1to
ri2a1t2r
ri2a1u
ri2a1ú
ri2a1ü
ri2av
ri2az
2riá1sá
ri1á
2riás2z
r1i2ázós
riá1zó
2ri1bé
ri2bol
ri1bo
2r1ibr
2ri1bü
ri2c1e2l
ri1ce
rics1il
ric2s
ri1c1si
ri2d1a2l
ri1da
ri2dál
ri1dá
rid1d
ri2d2e.
ri1de
2r1i2de1á
ri2de1i
2r1i2dej
r2i2del
ri2de1o
rid1e1re
ri2d1es2z
2r1i2dil
ri1di
2r1i2dom
ri1do
2r1i2dő
ri2du
ri2ed
ri1e
3rie2l
3rier
ri1fl
ri1f2rá
2r1i2g2e.
ri1ge
2r1i2gé
2ri1gi
ri1g1la
2r1igr
ri2har
ri1ha
2r1ihl
ri1hó2
rihón1
2r1i2jes
ri1je
ri1k1lo
ri1k2ló
1ri1k2ri
ri2lal
ri1la
ril2l1e2h
ril1le
ril2lé2t
ril1lé
ril2l1in
ril1li
ril2l1ő
2r1il1lu
2r1il1lú
2ri1ló
2rimá1da
r1i2mád
ri1má
2rimá1dá
2r1imp
ri2n1a2d
ri1na
2r1i2na1i
ri2naj
ri2n1a2l
ri2n1a2n
ri2n1a2r
2r1i2na2s.
2r1i2na1sá
2r1i2nasn
2r1i2na1so
ri2nass
2r1i2nast2
rina1s2z2
ri2na2t.
rin2c1a
rin2cá
rinc3c
rin2c1eg
rin1ce
rin2cel
rin2cer
rin2co
rin2csér
rinc2s
rin1c1sé
rin2c1si
2r1in1de
2r1in1du
ri2neg
ri1ne
2r1infl
rin2gál
rin1gá
rin2gel
rin1ge
2r1ingét
rin1gé
rin2gül
rin1gü
2r1in1ha
2r1i2nic
ri1ni
ri2nil
ri2n1ip
2r1injek
rin1je
rin1k1ré
rin3n
2r1insp
2r1inst
rin2t1ad2
rin1ta
rintá2r
rin1tá
rin2tá1ra
rin2tá1ro
rin2társ
2rinten
rin1te
2rinterf
rinté2r
rin1té
rin2t1é2r.
rin2t1é1ré
1rin2t1é1ri
2rint2ő.
rin1tő
2rintők
ri2nül
ri1nü
ri2o1k2
ri1o
rio2lok
rio1lo
ri2om
2rionn
2rio1no
2riox
ri2ó1a
ri1ó
ri2ó1á
ri2óc
ri2ódar
rió1da
ri2ó1e
ri2óg
ri2ó1ke
ri2ól
ri2ó1má
ri2ó1mé
ri2ó1o
ri2ó1p
ri2ó1ü
2r1i2pa1i
ri1pa
2r1i2pa2r.
2r1ipa1ra
2r1i2pa1rá
2r1i2parb
2r1i2pa1ré
2r1i2parh
2r1i2paril
1ripa1ri
2r1i2parin
2r1i2parn
2r1i2parr
2r1i2pars2
2r1i2par1te
2r1i2par1tó
2r1i2pa1ru
2ripl
ripor2ta
ri1po
2r1i2ram
ri1ra
2r1i2rat
2r1i2rá
2r1irh
2r1i2ri
2r1i2ro
2r1i2rón
ri1ró
2r1irr
2r1irt
ri2s1ar
ri1sa
ri2s1as
ri2s1ál
ri1sá
ri2sáp
ri2s1e2l
ri1se
ri2s1emb
ri2s1e2r
ri2sid
ri1si
ri2si1i
ri2sil
ri2s1is
2r1ismérv
ris1mé
ri2s1o2r.
ri1so
ri2s1ort
2r1is1pá
2ristál
ris1tá
ri2su2t
ri1su
ris1ü1té
ri1sü
ri2s1ü2t2ő.
risü1tő
ri2s1ü2v
2r1i2szák
ris2z
ri1s1zá
ri2t1a2d2
ri1ta
ri2tal
2r1i2ta2l.
2r1i2talb
ri2t1alk
2r1itall
2r1i2taln
2r1i2ta1lo
2r1i2talr
2r1i2talt2
rit1a2n2y
ri2tág
ri1tá
rit1ell
ri1te
ri2t1i2o
ri1ti
ri2t1í
riu2mé1ne
ri1u
riu1mé
riumhid2
rium1hi
riumhidr1
riu2m1i2o
riu1mi
2r1i2vad
ri1va
ri2var
2r1i2vás
ri1vá
2ri1vo
2r1i2vó
ri1xe2
ri2x1el
ri2xí
ri2xö
2ri1za
r1i2zét
ri1zé
2r1iz1mi
2r1iz1mo
2r1izmuk
riz1mu
2r1iz1mú
ri2zsar
riz2s
ri1z1sa
ri2zseb
ri1z1se
ri2zsel
2r1iz1za
2r1iz1zó
1rí
rí2gé
2rí2j.
rí2ja1i
rí1ja
rí2jak
rí2jam
rí2jas
2rí1já
rí2ján
rí2ját
rí2jáv
2r1íjh
2r1íjj
2r1íjl
2r1íjr
2r1íjv
rí2m1a2l
rí1ma
2rí1má
rím1emb
rí1me
rí2mo
rí2mö
rí2né
2r1íns
rí2ra
r1í2rá
r1í2ró
2r1í2tél
rí1té
rítés3s
rítő1a2
rí1tő
rítő1e2
rítőkés2z1
rítő1ké
2r1í2v.
rí1va2
2r1ívb
2r1í2ve
2r1í2vé
2r1ívh
rí2vi
r1í2vü
rí2vű
2r1ívv
rí2za
rí2z1el
rí1ze
rízis3s2
rí1zi
rí2zo
r2j1a1do
r1ja
rj1an2y
r2j1ág
r1já
r2j1áp
rjet2
r1je
r2j1id
r1ji
r2j1ös
r1jö
r2j1u2r
r1ju
rka1b
r1ka
r2k1ang
rka1pr
rkaros3
rka1ro
rka2ró2r
rka1ró
rka2s1ü2v
rka1sü
rka1t2r
r2k1ác2s.
r1ká
rkác2s
rká2c1sá
rká2ne
r2k1á2ri
rkár1om
rká1ro
r2k1á2ru
r2k1e2d2z
r1ke
rk1e2lem
rke1le
rk2elm
r2k1el1tá
rke1p
r2k1erd
rk1ere1dő
rke1re
r2k1e2re1i
r2k1e2rez
r2k1e2rő
rke2s1zö
rkes2z
r2k1ex
rk2é2p1e2l
r1ké
rké1pe
rké2p1éss
rké1pé
r2k1é2r.
r2k1ér1té
r2k1étt
r1k2hé
rk2hón
rk1hó
rki1a2
r1ki
rki1e2
rki2g
r2k1i1ga
rkigaz1
rkilenc1
rki1le
r2k1i1mi
rk1in1ga
r2ki2on2t.
rki1o
rki2sem
rki1se
rki2z1a
rk1i1zo
rk1kl
r1kli1e
rk1li
r1k2lí
r1k2lo
r1k2ló
rk1o2laj
r1ko
rko1la
rko2nya
rkon2y
rko2v2i.
rko1vi
rko2vit
r2k1ó1né
r1kó
rk1ó2rá
rk1ó2ri
r2k1ölté1si
r1kö
rköl1té
r2k1ötl
rkö2ző
rk1ő2r.
r1kő
rk1ő2ré
rk1őrk
rk1őrn
rk1ő2rö
rk1őrr
rk1őr1s2
rk1őrt
rk2rém
rk1ré
r1k2ri
r1k2rí
r1k2rom
rk1ro
rk1sh
rktus1s2
rk1tu
rk1udv
r1ku
r2k1ug
rku2s1ze
rkus2z
r2k1ú2s2z
r1kú
r2k1üd
r1kü
r2k1üg
rk2vó
rlag1g
r1la
rla2g1ol
rla1go
rla2p1a
rla2pál
rla1pá
rla1p1e
rla2pol
rla1po
rla2p1os2z
rl2a1p1ro
rla1s2t
rl2a2t1a2n
rla1ta
rla2t1ó2
rlá2pe
r1lá
rlás3s
rlá2s1út
rlá1sú
rlá2t1e2
rlá2t1é
rlá2tor
rlá1to
rle2g1a2
r1le
rle2g1á
rle2ger
rle1ge
rleg1g2
rle2gigaz1
rlegi2g
rle2g1i1ga
rle1gi
rle1í2
rle2t1á2
rle2t1eg
rle1te
rle2tell
rle2t1e1lő
rle2t1ék
rle1té
rle2té2l
rle2t1é2r.
rle2t1érv
rle1ü2
rlésát1
r1lé
rlé2s1á2
rlé2s1á1ta2
rlés3s
rl1gr
rló1g2
r1ló
rló1ó2
rlót2
rlő1e2
r1lő
rlő1s1ta
r2m1akad
r1ma
rma1ka
rmaké2s1z1ü
rm2akés
rma1ké
rmakés2z
r2m1a2kó
rm2a1k1ré
r2m1alt
r2m1a2nya
rman2y
rma1ó2
rma2t1á2ru
rm2atár
rma1tá
rm2a2t1ur
rma1tu
rma2zon
rma1zo
rmá2lér
r1má
rmá1lé
rmá2nya2n
rmán2y
rmá1nya
r2m1á2ram
rmá1ra
r2m1á2ro
r2má1ru
rm1cl
rme1ge2
r1me
rme2g1er
rme2g1es
rme2g1é
rme3g2ö2
rme2k1an
rme1ka
rme2kar
rme1ká2
rme2k1eg
rme1ke
rme2k1e2l
rme2kérd
rme1ké
rme2ko2r
rme1ko
rme2kot
rme2k1ó2
rmekö2r
rme1kö
rme2kö1rö
r2m1elk
r2m1elm
rme2ran
rme1ra
rme2ras
rme2rin
rme1ri
r2m1e2rő1i
rme1rő
rme2rő2k.
rme2rő1ke
rme2rőkn
r2m1e2rőm
rmert2
rmer1tr
rm1esem
rme1se
rme2t1él
rme1té
rme2tin
rme1ti
rmé2k1e2l
r1mé
rmé1ke
rmé2ker
r2m1é2le
r2m1ép
r2m1é2r.
r2m1ér1te
r2m1étk
r2m1étr
r2m1étt
rmi2g
r1mi
r2m1i1ga
r2m1i1gé
rm1il1la
rminc3s
rm1in1te
r2m1i1rá
rmi2si
r2m1is1ko
r2m1ism
rmi1te2
rmi2t1el
r2m1íg
r1mí
rmjob2
rm1jo
rm1kl
rmo2n1a
r1mo
rmo2nár
rmo1ná
rmo2n1e
rmon1n
r2m1o1pe
r2m1or1s2
rmo1sz2fér
rmos2z
rmosz1fé
rmő2s1zá
r1mő
rmős2z
rm1p2l
rm1p2r
rm1sk
rm1st
rm1s2z2
rm1tr
rm1üg
r1mü
rm1üz
rmű1s
r1mű
rm1ya
rna1b
r1na
rna1f2
rna2gyú2
rnag2y
rnai2k
rna1i
rna1k2r
rna2pe2s
rna1pe
rn2a1p1ro
rnas2
rna1sp
rna1s2z2
rna1t2
rná2c2s
r1ná
r2n1e2l.
r1ne
r2n1elb
r2n1elf
r2n1elh
r2n1elj
r2n1ell
r2n1eln
r2n1elr
r2n1elt
rne2m1is
rne1mi
r2n1est
rne2t1a2
rne2t1e2l1
rne1te
rne1t1o
rnés2
r1né
rné1st
r2n1i2d
r1ni
rni2g
r2n1i1ga
rno2kis
r1no
rno1ki
rnó1d2
r1nó
rnö2ke2t
r1nö
rnö1ke
rnus3s1ze
r1nu
rnuss2
rnus2s2z
r2ny1a2dó
rn2y
r1nya
r2nyakad
rnya1ka
r2nyala1ko
rnya1la
r2ny1alk
r2nyarc
rny1álm
r1nyá
r2ny1ell
r1nye
r2nyelm
r2ny1eln
r2ny1e1lo
rny1el1vo
rny1emb
r2nyerd
r2ny1e2rez
rnye1re
rny1e2ső
rny1ék1né
r1nyé
rny1és2z
r2ny1id
r1nyi
r2nyiz
rnyolc1
r1nyo
rny1old
r2ny1or
rny1ök
r1nyö
r2nyöt
rny1s
r2ny1ur
r1nyu
1ro
ro2ad
ro1a
roa2n
2r1obj
2robl
r1obs
roc2ke
ro1cker
roc2ké2n
roc1ké
ro1c2kok
roc1ko
ro1c2kon
roc2ko2s.
2r1o2dú
rofi2tár
ro1fi
rofi1tá
ro1fl
ro1fr
ro2g1ad
ro1ga
ro2g1ak
ro2gal
ro2g1am
ro2gar
ro2g1á2r
ro1gá
ro2g1áz
rog1enc
ro1ge
ro2ge2r
ro1g2ló
ro2g1o2l
ro1go
ro2gor
ro2g1os2z
ro2gö
3rog1rá
r2o1g2rá2f.
r2o1g2ráff
ro1gu2
ro2gur
ro2gü
3rog2y
ro2he
ro2hö
ro2is
ro1i
3rojt
ro2k2a.
ro1ka
ro2kab
ro2kac
ro2k1a2d
ro2kait
roka1i
ro2k1aj
ro2kak
ro2k1al
rok1an2y
ro2k1a2s
ro2káb
ro1ká
r1o2kád
ro2k1ál
ro2kán
ro2k1á2s
ro1ke2
ro2ke2d
ro2k1en
2roket2t.
r2o1ké
ro2kék
ro2k1é2l
ro2ké2p
ro2kid
ro1ki
ro2k1ing
ro2k1í
2rokod
ro1ko
rok1old
ro2kő
2r1okság
rok1sá
rok1t2
2r1oktat
rok1ta
2r1oktán
rok1tá
ro2k1u2s
ro1ku
ro1kú2
ro2k2ú.
ro2kús
ro2kü
2r1o2laj
ro1la
rola2n
rolás1s
ro1lá
2r1ol1dá
2r1ol1dó
ro2l1i2d
ro1li
roligar2
r1o2li1ga
roligarc3
rol2l1a2d
rol1la
rol2lag
rol2l1akn
rol2lan
rol2lat
rol2leg
rol1le
rol2le2l
rol2lis
rol1li
2r1ol1ló
2r1oltás
rol1tá
2r1olt2ó.
rol1tó
2r1oltób
2r1oltó1é
2r1oltóh
2r1oltó1i
2r1oltó1ké
2r1oltóm
2r1oltón
2r1oltót2
2r1oltóv
2r1oltv
ro2lü
2r1olvad
rol1va
2r1olvas
2r1o2lya1i
rol2y
ro1lya
2r1o2lyáh
ro1lyá
2r1o2lyán
2r1o2lyár
2r1o2lyéb
ro1lyé
2r1o2lyéh
2r1o2lyé1i
2r1o2lyé2n.
2r1o2lyiér
ro1lyi
rolyi1é
2r1o2lyuk
ro1lyu
rom1ajt
ro1ma
rom1akk
rom1a1kó
1ro2m1a2ro
ro2m1ál
ro1má
2romám
ro2m1á2ri
ro2m1árk
ro2m1árn
1ro2m1á2ro
rom1bé2
rom2bén
ro2me2g
ro1me
ro2m1e2l
ro2m1e2r
ro2m1e2s
ro2méj
ro1mé
ro2m1é2ne
rom2f1os
rom1fo
ro2mim
ro1mi
rom1i1ná
rom1ist
ro2mi1ta
ro2miz
ro2mí
romköz1
rom1kö
2r1om1ni
ro2m1o2r
ro1mo
ro2m1ó2r
ro1mó
ro2mö
ro2mő
rom1pr
ro2mü
ron1alj
ro1na
ron1alt2
ron1a1lu
ron1an2y
rona1t2
rona2ut
rona1u
ro2n1á1ta
ro1ná
ro2n1átr
ron2csel
ronc2s
ron1c1se
ron2csem
ro2ne2n
ro1ne
ron1es2z
rone2s
ro2n1é2r.
ro1né
ron2gal
ron1ga
ro2n1i2ko
ro1ni
ro2n1i2m
ro2ní
ro2nop
ro1no
ro2nóc
ro1nó
ro2n1ó2r
ro2nö
ro2nő
ron1s2
ron2tab
ron1ta
ron2tem
ron1te
ron2tev
ron2t1én
ron1té
ron2tin
ron1ti
ron2tí
2rontó1é
ron1tó
2r1ontóh
2rontói2g
rontó1i
2rontós
ro2nü
ro2nyac
ron2y
ro1nya
rony1a2l
ro2nyid
ro1nyi
ro2nyik
ro2nyir
ro2nyó
ron2zab
ron1za
ron2z1al
ron2zer
ron1ze
ron2zin
ron1zi
ron2z1ol
ron1zo
ron2z1or
ron2z1ó2
ronz1z
ro2om
ro1o
ro2os
2r1opc
2ro1pe
ro2per
ro1p2l
ropo1s
ro1po
rop2s
ro2rak
ro1ra
ro2r1a2l
ro2rat
ror1áll
ro1rá
ro1re2
ro2reg
ro2r1e2l
ro2r1e2s
ro2r1in
ro1ri
ro2ros2z
ro1ro
rort2
ror1tr
ro2rü
ro2sar
ro1sa
ros1ass
ro2s1atl
ro2s1av
ro2s1ár1k2
ro1sá
1ro2s1á1ro
ro2sem
ro1se
ro2s1ist2
ro1si
ro2s1ol
ro1so
ro2s1ó2
ro2sö
ro2ső
ros3s1ze
ros2s2z
ros3szék
ros1s1zé
ros2tet
ros1te
2r1ostob
ros1to
ros2tol
2r1os1tya
rost2y
ro2s1út
ro1sú
ro2szal
ros2z
ro1s1za
ro2sza2n
ro2szás
ro1s1zá
2r1osz1lo
ro2s1zó
r1osz1tá
2ro1té
ro1t2he
roto2na2
ro1to
ro1t2ró
2rotth
ro2un
ro1u
ro2us
rova2re
ro1va
rovás1s
ro1vá
2r1o2v2i.
ro1vi
2r1o2vib
2r1o2vik
ro2vis
2r1o2xidb
ro1xi
2r1o2xidr
2r1o2xilc
ro1xy
ro1ya
roza2tal
ro1za
roza1ta
ro2zsa2n
roz2s
ro1z1sa
1ró
róa2d
ró1a
rób1ajt
r2óbaj
ró1ba
2ró1bá
róc3c
ró2c3h
ró2cin
ró1ci
ró2c1ö
ró2c3sá
róc2s
ró2c2z
ró2da1i
ró1da
ró2d1ep
ró1de
ródi2a1d2
ró1di
ródi1a
ró2dő
ró2dü
2r1óé2v.
ró1é
2róé2ve
2r1óé2vé
r2ó2f1ag
ró1fa
2rófe1a
ró1fe
2rófe1á
ró2f1iv
ró1fi
2rófs
ró2fu2r
ró1fu
ró1g2r
rói2g
ró1i
róke2rest
r2óker
ró1ke
róke1re
róke2r1in
róke1ri
ró1k2l
ró1k1ré
2r1ólm
r1ó2lom
ró1lo
ró2m1a2l
ró1ma
róma2r
r2óm1a1ra
ró2mál
ró1má
róme2l
ró1me
r2ó2mél
ró1mé
róm1i2s2z
r2ómis
ró1mi
ró2mí
2rómk
ró2mö
ró2mü
róne2m
ró1ne
2r1ó2néb
ró1né
2r1ó2né1é
2r1ó2néh
2r1ó2nén
2rónér
2rónét
2rónév
2rónj
2rónn
2rónr
rónus3s2
ró1nu
2ró1nú
ró2nü
róo2k
ró1o
róó2r
ró1ó
ró1p2l
ró1p1ro
2r1ó2ra1a
ró1ra
2r1ó2rac
2r1ó2rad
2r1ó2ra1e
2r1ó2raf
2r1ó2rag
2r1ó2ra1je
2r1ó2ram
2r1ó2ran
2r1ó2rap
2r1ó2rar
2r1ó2ras
2r1ó2rat
2r1ó2ra1ü
2r1ó2rav
2r1ó2ráb
ró1rá
2r1ó2rád
2r1ó2rá2é.
rórá1é
2r1ó2ráén
2r1ó2ráév
2r1ó2ráh
2r1ó2rá1i
ró2rá1ja
2r1ó2rá1ka
2r1ó2rákb
2r1ó2rá1ké
2r1ó2rákh
2r1ó2rá1ki
2r1ó2rákk
2r1ó2rákn
2r1ó2rá1ko
2r1ó2rákr
2r1ó2rákt
2r1ó2rá2n.
2r1ó2rá1na
2r1ó2rá1ná
2r1ó2ránk
2r1ó2rán2y
2r1ó2rár
2r1ó2rás
2r1ó2rát
2r1ó2ráv
2r1ó2ri1á
ró1ri
rós1orom
r2ósor
ró1so
róso1ro
ró1s1pi
2rótb
2róth
ró2tip
ró1ti
rót1ist
2rótj
2rótk
2rótm
2rótn
ró2t1ö2v
ró1tö
ró1t1ri
ró2tül
ró1tü
2r1ó2vó
róza1t2
ró1za
ró2z1e
ró2z1in
ró1zi
ró2zú
ró2zü
1rö
2r1öbl
2r1ö2bö2l.
rö1bö
2r1ö2bölb
2r1öbölh
2r1öbö1li
2r1ö2böll
2r1ö2böln
2r1ö2bölr
2r1ö2böl1tő
rö2ga
rö2go
rö2gó
rö2k1é2l
rö1ké
rö2k1érv
2rö1kí
2rök1lé
rö2ko
2r1ö2kör
rö1kö
2rökö1sé
2rökö1sö
2r1ök1rö
rö2lős
rö1lő
rö2lyü
röl2y
rö2ma
rö2má
2röm2e.
rö1me
2römed
rö2meg
2röme1i
2römén
rö1mé
2römét
2römév
rö2mí
2röm1mű
rö2mo
rö2mó
2römöd
rö1mö
2römök
2römöt
rö2mu
2rö1mü
2rö1mű
rön2d1e
rön2k1a2
rön2k1e2
rön2kép
rön1ké
rön2kol
rön1ko
rön2kos
rön2k1öl
rön1kö
rön2k1ü2
rö2p1i2
rö2re
1rö2rö
rö2sa
rö2sá
rö2so
rös3s1za
rös2s2z
2r1ö2v.
2r1övb
2r1ö2ve
r1ö2vé
2r1övh
2r1övn
2r1ö2vö
2r1övr
r1ö2vü
2r1övv
1rő
rőa2n
rő1a
rőát1
rő1á
rőá2ta2
rő1bl
rő1br
rő2dá
rő2d1e2l
rő1de
rő2dos
rő1do
rő2d1ő2r
rő1dő
rőe2l
rő1e
rőe2r
rőe2s
rőé2h
rő1é
rőé2l
rőé2r
rőé2te
rő2f1as
rő1fa
rő1fl
rő2f1ő
rőgé2p1és
rő1gé
rőgé1pé
rői2ta
rő1i
rő1kl
rő1kv
2rőlt
rő1pl
rő1pr
2r1ő2r.
rőr1ak
rő1ra
rőr1eg
rő1re
r1ő2re1i
r1őrh
rőr1in
rő1ri
rő2riz
rőrköz1
rőr1kö
2r1őrl
2r1őrm
2r1őrn
rő2rö
2r1őrp
2r1őrr
2r1őrs
2r1őrt
2r1ő2rü
2r1őrz
rő1sá2
rő2sár
rő2sír
rő1sí
rő1s1ká
rő2ső
rő1s1pe
rő1s2pi
rő1s1ta
rő1st2r
rősü2l
rő1sü
rő2s1ü1lő
rő2s1ült
rő2s1ü2t
rő2s1ű2
rősza2kál
rős2z
rő1s1za
rősza1ká
rőu2t
rő1u
rőü2l
rő1ü
rőva2s1
rő1va
rő2z2i.
rő1zi
rő2zön
rő1zö
rpe1i2
r1pe
rpe2szel
rpes2z
rpe1s1ze
rp2he
r2p1ig
r1pi
rpi1s1á2
rpi2t1a
rpi2t1e2
r2p1i1vá
r1p2la
rp2le
rp2lé
rp2lu
rp2lü
rprecíz1
rp1re
rpre1cí
r1p2ré
r1p2ri
r1p2rí
r1p2ro
r1p2ró
rpu2s1zé
r1pu
rpus2z
r2p1üz
r1pü
r2r1a2lap
r1ra
rra1la
rra2s1za
rras2z
rra2s1zi
rr2ata2n
rra1ta
rrat1an2y
rr2a2t1á2r
rra1tá
rra2t1e
r2r1ábr
r1rá
rrádiók2
rrá1di
rrádi1ó
rrádió1kb
r2r1á2ga
r2r1á2g2y
r2r1árb
rrá2saj
rrá1sa
rrá2sa2r
rrá2ság
rrá1sá
rrá2se
rrás3s
rrá2sü
rrá2s3z
rrá2tal
rrá1ta
r2r1áts
rre2l1i
r1re
rr1emb
rren2d1ő2
r2r1ék
r1ré
rré2r
r2r1é1ri
rré2sem
rré1se
rr1fl
rr1fr
r2r1i2de
r1ri
rr1i2n.
rr1ing
r2r1ir
rri2ta
rr1i2zé
rr1izg
rr1izm
rr1k2r
r2r1o1pe
r1ro
rro2rál
rro1rá
rro2r1os2z
rro1ro
rró1dr
r1ró
rró1p
r2r1öb
r1rö
r2r1ös
rr1pl
rr1pr
rr1sp
rr1t2r
r2r1u2r
r1ru
r2r1ür
r1rü
r2r1üt
rry2n
r2s1a2dó
r1sa
r2s1a2g
r2s1ai1a
rsa1i
rsa2il2
r2s1ak1ku
rs1alan
rsa1la
rsa2lap
r2s1a1le
r2s1alm
r2s1a2lomb
rsa1lo
r2s1a1na
rs1a2n2y
r2s1a2pá
r2s1a2po
rs1a2r2a.
rsa1ra
r2s1arc
r2s1arom
rsa1ro
r2s1arz
rs1att
rsa2v1ar
rsa1va
rsa2v1é2
rsa2v1i
r2s1ax
r2s1a2z
r2s1ábr
r1sá
rság1g
rs1áld
r2s1á2ré
r2s1árn
r2s1árr
r2s1á2ru
r2s1á1té
r2s1á1ti
rs1bl
rs1br
rs2c2h2
r1s1c1he
rs1c3hé
rs1d2r
rs1ed2z
r1se
r2s1ef
r2s1e2gét
rse1gé
r2s1e2g2y
rse2il
rse1i
rse2k1a2
rse2keg
rse1ke
r2s1e2le1me
rse1le
r2s1elf
r2s1elk
r2s1e2lő1a
rse1lő
r2s1e2lől
rs1e2mi
r2s1e2mu
rse2ny1a2
rsen2y
rse2nyeg
rse1nye
rse2ny1e2l
rse2nyer
rse2nyí
rse2nyő
r2s1erd
r2s1er1k
r2s1e2rő
r2s1ess
r2s1es2z
rsé2g1el
r1sé
rsé1ge
r2s1él
r2s1ép
r2s1é2r.
r2s1érd
r2s1érl
r2s1érték
rsér1té
r2s1és
rs1é2te
r2s1étk
r2s1étt
rs1fr
rs1gl
rs1gr
rsi2d
r1si
rs1i1de
rs1i1do
rsi2g
r2s1i1ga
r2s1ikr
r2s1ill
r2simm
r2s1ind
r2s1ing
r2s1int
r2s1i2o
rs1i2pa
r2s1i1ro
r2s1i2si
r2s1isk
r2s1ism
r2s1ist2
rs1írn
r1sí
r2s1í2ró
r2s1írt
r1s2kál
rs1ká
r1skj
rs1kl
rs1k2r
r1s2lu
rs2mink
rs1mi
rs2ni
r2s1ond
r1so
rso1nya2
rson2y
rso2nyal
rso2nyan
rso2nyat
rso2nyé
r2s1op
r2s1ord
r2s1org
r2s1os2z
rs2ó1c1sa2
r1só
rsóc2s
rsócsap1
r2s1ó1dá
r2s1ó1né
rsó2s3zárr
rsó1s2z
rs2ó1s1zá
r2s1öb
r1sö
rs1önt
r2s1örv
r2s1ös
r2s1ö2z
rs1őr
r1ső
rs1pl
rsp2r
r1s2rác
rs1rá
rs1s2t
rs3szag
rs2s2z
rs1s1za
rs3szak
rs3s1zá
rs3sze2m.
rs1s1ze
rs3szemet
rssze1me
rs3sze2r.
rs3szer2ű.
rss2ze1rű
rs3szerűb
rs3szerű1e
rs3szerűn
rs3szerűs
rs3szerűt
rs3szerűv
rs3s1zi
rs3s1zí
rs3s1zó
rs3s1zö
r1s2tand
rs1ta
r1s2tat
r1s2tác
rs1tá
r1s2tát
rs2top
rs1to
rst2r
r1strat2
rst1ra
rs1t1re
rs1t1ré
r1st1ró
r1st1ru
r1s2tú
rsu2r
r1su
r2s1u1ra
rsu2t
r2s1u1ta
rs1u1tá
r2s1ú2r.
r1sú
r2s1ú2ri
r2s1ú2s
r2s1ú1to
r2s1üd
r1sü
rs1üld
r2s1üz
rsz2a2k1a2l
rs2z
r1s1za
rsza1ka
rsza2k1e
rsza2kö
rsza2k1ü2
r2sza1ló
r2s3zam
rszág1g
r1s1zá
rszáraz1
rszá1ra
rs3zárl
r2s3ze1né
r1s1ze
r1sz2f
rsz2lo
rsz2m
rszom2j1a2d
r1s1zo
rszom1ja
rs3z1se
rsz2s
rs3z1si
rsz2ta
rszt1al
rsz2t1árv
rsz1tá
rszt2rá
r2s3zű2r.
r1s1zű
r1sz2v
r2t1ab1la
r1ta
r2t1abr
r2t1a2dat
rta1da
r2t1a2dó
r2t1a2g2a.
rta1ga
rt1agit
rta1gi
r2t1a1já
rt1aj1tó
r2t1a2ka1ra
rta1ka
r2t1akc
rt2a1k1re
rt2a1k2ré
rt1alapj
rta1la
rt1a2láb
rta1lá
r2t1alár
r2t1alb
r2t1alel
rta1le
r2t1alg
r2t1alj
r2t1alk
r2t1alt
r2t1alv
rta2m1ér
rta1mé
r2t1a2nim
rta1ni
rta2n1ó2r
rta1nó
r2t1an1to
r2t1antr
r2t1a2nyag
rtan2y
rta1nya
r2t1app
r2t1a2rán
rta1rá
r2t1arc
rta2rén
rta1ré
rt1a2t2y
r2t1a2u1to
rta1u
r2t1a1zo
r2t1ábr
r1tá
r2t1áf
r2t1á2g.
rt1á2ga
r2t1ágb
r2t1ágg
r2t1ágj
r2t1ágn
r2t1ágr
rt1ágs
rtá2la2d
rtá1la
rtá2la1la
rt2álal
rtá2ra1da
rtá1ra
rtá2raj
rtá2ramr
rt1ári1a
rtá1ri
rt1ári1á
r2t1árin
rt1ár1s1zi
rtárs2z
rt1ár1s1zí
r2t1ár1ta
rtá2ru1ké
rtá1ru
rtá2rukk
rtá2rukn
rtá2rukr
rtá2rukt
r2t1á2rur
r2t1á2rut
rtá2s1ág
rtá1sá
r2t1á2sás
rtá2s3z
r2t1á2t1a
r2t1átl
r2t1áts
r2t1átv
rtá2v1é
rtá1vi2
rt1bl
rt1br
rt1cl
rt1cr
rt1dr
rte1a2
r1te
rt1e2gé
rt1e2gye
rteg2y
rte1i2
r2t1eks
r2t1e2l1a2d
rte1la
r2t1elb
r2t1elf
rt1el1já
rt1elker
rtel1ke
rt1el1kö
rt1el1kü
rtel2la
rtel2l1á2
rt1el1ma
rt1e2lő1a
rte1lő
rte2lől
r2t1elr
r2t1ember
rtem1be
r2t1eml
r2t1enc
r2t1ent
rte1p
rte2rac
rte1ra
rte2rál
rte1rá
rte2r1in
rte1ri
rter1mo1
rtermos2z2
rte2r1os
rte1ro
r2t1e2ró
r2t1e2rő
rte1sp2
r2t1esték
rtes1té
rt1est1né
r2t1estün
rtes1tü
rte2sz2e.
rtes2z
rte1s1ze
rte2sze1i
rte2szek
rte2szem
r2te2szet
r2t1eszk
r2t1eszm
r2t1e2s1zü
r2t1é2d
r1té
rt1é2gi
rté2kaj
rté1ka
rté2k1a2l
rté2k1á1t1a2
rté1ká
rté2k1í2
rté2kos
rté1ko
rté2ku
r2t1é2let
rté1le
rté2lén
rté1lé
rt1é2li
r2t1élm
r2t1é2ne1ke
rté1ne
r2t1é2nek1k2
r2t1é2nekr
r2t1é1pü
r2t1érdem
rtér1de
r2t1é2rem
rté1re
r2t1é2ré
r2t1érin
rté1ri
r2t1é2rit
r2t1értek
rtér1te
r2t1ér1té
r2t1érv
r2t1érz
rté2s1ég
rté1sé
rtéskés2z1
rtés1ké
rtés3s1za
rtés2s2z2
rté2sü2l
rté1sü
rt2é2s1zá
rtés2z
rté2s1zo
rt2é2s1zö
rtés3z1sí
rtész2s
r2t1étk
r2t1étl
r2t1étt
r2t1étv
r2t1é2v2e.
rté1ve
r2t1é2vet
rté2vé1né
rté1vé
r2t1é2vér
r2t1évk
rt1fl
rt1fr
rt1gr
r1t2h2a.
rt1ha
r1t2hág
rt1há
r2t1i2de
r1ti
rt1i1dé
rt1i1di
rt1ifj
r2t1i2gé
r2t1ill
r2ti1má
r2t1i2mi
r2t1imp
r2t1i2náb
rti1ná
r2t1i2ná1é
r2t1i2náh
r2t1i2nán
r2t1ind
r2t1inf
r2t1ing
rti2nik
rti1ni
r2t1i2pa
r2t1i1rá
r2t1i1ro
r2t1i1si
r2t1isk
r2t1ism
r2t1i1s1za
rtis2z
rt1i2tal
rti1ta
r2t1i1zé
rt1izm
r2t1izz
r2t1íg
r1tí
r2t1íj
r2t1ín
rtí2ra
rtí2rá
rt1í2ve
rt1í2vű
rtí2z
r2t1ízl
rt1kl
rt1kr
rt1kv
rt1mű1
rtműt2
r2t1of
r1to
rto2kad
rto1ka
rto2k1a2l
rto2k1ar
r2t1o2li
r2t1oml
rto2n1a2
rto1ná2
rto2nár
rt1o1pá
r2t1o2pe
rt1op1t2
r2t1or1g2
r2t1o2rom
rto1ro
r2t1orr
r2t1ors2
r2t1orv
r2t1os2z
rtóe3re1jü
r1tó
rtó1e
rtóe2re
rtó1p
rtó2rák
rtó1rá
rtó2s1í2n.
rtó1sí
rtó1sp
rtó2s3zár
rt2ó1s1zá
rtós2z
r2t1ökl
r1tö
rt1ököl
rtö1kö
r2t1ö2lő1se
rtö1lő
rtön3n
rt1ö2rök
rtö1rö
rt1öröm
r2t1ös2s2z
rt1ö2vez
rtö1ve
rtőé2h
r1tő
rtő1é
rtőkés2z1
rtő1ké
r2t1őrköd
rtőr1kö
r2t1őrl
r2tőrül
rtő1rü
rt1pl
rt1pr
rt1ps
rt2rad
rt1ra
r1t2raf
r1t2ranz
r1t2rág
rt1rá
r1t2ri1á
rt1ri
r1t2rik
r1t2ril
r1t2ri1ó
r1t2rón
rt1ró
r1t2rü
rt1sk
rt1sl
rt1sp
rt1st
rts2z2
rt1t2r
r2t1udv
r1tu
rtu2k
r2t1u1ká
r2tunik
rtu1ni
r2tu2ra1i
rtu1ra
rtu2ral
rt1u2rá
rtu2s1ze
rtus2z
rt1u2tat
rtu1ta
r2t1u1tá
r2t1új
r1tú
r2t1ú2s
r2t1üg
r1tü
r2t1üld
r2t1ü2lé
rt1ü1lő
r2t1üt
r2t1ü2v
rt1ű2ző
r1tű
r2tying
rt2y
r1tyi
rty2j
rty2s
r1t2zé
r1t2zi
rt2ző
1ru
ru2ac
ru1a
ru2b1i2k
ru1bi
ru2c3h
ru2c2z
2r1udv
rue2l
ru1e
rue2r
ru1fr
2rugar
ru1ga
2r1u2gat
rug3g2y
2r1ugr
ruhás1s
ru1há
2r1ujj
ru1k2l
2ruk1tu
2ruk1tú
rum1agr
ru1ma
ru2maj
ru2mal
ru2maz
ru2m1eng
ru1me
ru2mer
ru2mes
ru2mél
ru1mé
ru2m1i2k
ru1mi
rum1ill
ru2m1i2p
ru2mis
ru2m1iv
ru2miz
ru2mí
ru2mol
ru1mo
ru2m1o2r
ru2mö
ru2mő
ru2mü
2r1unc
run2da
ru2nit
ru1ni
r1u2nok
ru1no
ru2pad
ru1pa
ru2pe
ru2p1il
ru1pi
ru2pü
ru2rad
ru1ra
ru2ra1i
r1u2ral
ru2ras
ru2rat
r1u2raz
ru2rán
ru1rá
ru2rát
2r1urb
ru2ruk
ru1ru
rus1abl
ru1sa
ru2sad
ru2sal
rusa2n
rus1an2y
rusa2r
rus1a1ro
ru2sas
ru2sál
ru1sá
ru2s1iz
ru1si
ru2sí2r
ru1sí
ru2s1ol
ru1so
ru2s1ó2
ru2ső
russ2
rus2s2z2
rust2
rus1t1ra
ru2s1ű2
ru2sz1é2p
rus2z
ru1s1zé
ru2szip
ru1s1zi
r1u2tac
ru1ta
r1u2tak
r1u2tat
r1u2taz
2r1utc
r1u2t2ó.
ru1tó
r1u2tób
r1u2tód
r1u2tó1i
r1u2tój
r1u2tók
r1u2tón
ru2tó1p2
r1u2tór
r1u2tós
r1u2tót
r1u2tóv
1rú
rú1da2
rú2da2d
rú2d1al
rú2d1ar
rú2d1á
rú2de2l
rú1de
rú2din
rú1di
rú2du
rú2dü
rú1dy
rú2d3z
rú1gr
2r1újd
rú2jí
2r1újk
2r1újs
2r1ú2r.
2r1úrb
2r1úrh
2r1ú2ri
2r1úrk
2r1úrn
rú2ro
2r1úrr
2r1úrt
rú2s1zó
rús2z
rú2t1a2l
rú1ta
2r1ú2t1e2
2r1ú2t2é.
rú1té
rú2tér
rú2tit
rú1ti
2r1útk
2r1út1le
2r1útm
rú1to2
2rúts
2r1ú2tü
2r1útv
rú2zsad
rúz2s
rú1z1sa
r2ú2z1se
1rü
rü2dí
rü2dü
rü2gyel
rüg2y
rü1gye
2rügyn
rü2ha
rü2la
rü2lá
rü2led
rü1le
rü2les
rü2lép
rü1lé
rü2lér
rü2lí
rü2l1o
rü2ló
rü1lö2
rü2l1ön
rü2l1öz
rü2l1őg
rü1lő
rü2lu
rü2lú
rü2l1ü2l
rü1lü
2r1ünn
2r1ü2nő
rü2rí
1rü2rü
rü2te
rü2té
rü2tö
rü2tő
rü2vö
rü2ze
rü2zé
1rű
rű1gr
rű1kl
rű1pr
2r1ű2r.
2r1űrh
2r1űrm
rű2rö
2r1űrt
rű2s1orr
rű1so
rűs1or1s
rű1sp
rű1st2r
r2v1a1gá
r1va
r2v1akc
rva2lap
rva1la
r2v1alj
r2v1alk
r2v1a2rán
rva1rá
rva2sáb
rva1sá
rva2sék
rva1sé
rva1su2
r2v1a2s1zó
rvas2z
rva1t2r
rva1vá2
r2v1á1ga
r1vá
rvá2gyi
rvág2y
r2vállam
rvál1la
r2v1ál1lo
rv1állv
rvá2nya2d
rván2y
rvá1nya
rvá2nya2n
rvá2nyú
r2v1áp
rvá2r1a2l
rvá1ra
r2v1á2ram
rváro2s1os
rvá1ro
rváro1so
rv1á2rú2
r2v1á2sás
rvá1sá
r2v1á1ta2
rvá2tors2
rvá1to
r2v1e2b
r1ve
r2v1e2d2z
r2v1e2gye
rveg2y
r2v1e1la
rv1e2led
rve1le
rve2lemb
r2ve2le1me
rv1e2les
r2v1elk
r2v1ell
r2v1e1lo
r2v1e2lő1a
rve1lő
rv1e2lő1á
r2v1e2lőkép
rvelő1ké
r2v1előr
r2velőz
r2v1en1g2
r2v1ep
rve2r1a
rve2r1á2
r2v1e2retn
rve1re
rverés3s
rve1ré
rve2rip
rve1ri
rve2r1o
r2v1e2rő1i
rve1rő
r2v1e2se1te
rveset2
rve1se
r2v1e2sés
rve1sé
r2v1ex
rv1é1ke
r1vé
rv1ékk
rv1ékn
rv1é1kü
rv1é2lel
rvé1le
r2v1é2lé
rvé2ny1e2l
rvén2y
rvé1nye
r2v1ép
r2v1é2rem
rvé1re
r2v1é2r2é.
rvé1ré
r2v1é2rés
r2v1érp
r2v1ér1té
r2v1érv
r2v1é2vet
rvé1ve
r2v1é1vi
rv1fr
r2v1i1do
r1vi
r2v1i1ga
r2v1i2gaz1
r2v1i1gé
r2v1ik
r2v1inf
rv1in1te
r2v1in1té
r2v1i2p
r2virán
rvi1rá
r2v1is1me
rvis3s1ze
rvis2s2z
r2v1ital
rvi1t2a
rvi2z1a2
rvi2z1á2
rvi2z1elv
rvi1ze
rvi1z1o
rvi2zó
rví1ze2
r1ví
rvíz1es
rv1kl
rv1kr
r2v1ob
r1vo
r2v1olv
r2v1op
r2v1or
rv1os2z
r2v1ov
r2v1ó2h
r1vó
r2v1ó1rá
rv1öss
r1vö
rv1ős
r1vő
rv1pr
rv1sk
rv1sp
rv1st
rv1tr
r2v1ub
r1vu
r2v1u2t
r2v1üg
r1vü
rvü2l
r2v1ü1lé
r2v1ü1lő
r2v1ült
r2v1üt
r2v1ü2v
r2v1ü2z
ry2be
ry2bó
ry2bő
ry1é2n
ry2ho
ry2na
ry2ne
ry2ra
ry2re
rys2n
ry2tó
ry2tő
ry2va
ry2ve
r2z1a2dot
r1za
rza1do
r2z1a2la
r2z1alj
r2z1alk
r2z1almás
rzal1má
rza2s2z
r2z1a1s1zó
rza2tal
rza1ta
rza2t1e2
rza2tol
rza1to
r2z1áll
r1zá
rzá2r1ó2r
rzá1ró
rzá2se
rzás1s
r2z1e2be
r1ze
rze1p2
rz1est
rze2tal
rze1ta
rze2t1eg
rze1te
rze2t1e2l
rzetes3s
rze2t1o
rzetü2két
rze1tü
rzetü1ké
rzé1na2
r1zé
rzé2n1al
rzé2nat
rzé2ná
r2z1érm
rzé2so
rzi2ab
r1zi
rzi1a
rzis3s2
r2z1ín
r1zí
rzo2r1ál
r1zo
rzo1rá
rzó2s3zár
r1zó
rz2ó1s1zá
rzós2z
rző1a2
r1ző
rző1e2
rz1p2r
rzs1a1la
rz2s
r1z1sa
rzs1an2y
rzs1a1pa
r2zsaz
r2z3sá2r.
r1z1sá
rz3seben
r1z1se
rzse1be
r2zs1e2g
r2zs1e2r
r2z3ség
r1z1sé
r2zs1ék
r2zs1é2r.
r2zsé1va
r2z2s1i1ga
r1z1si
rz2s1inj
rzs1int
r2zsip
r2zsi1ta
r2zs1or
r1z1so
r2zs1ö2r
r1z1sö
r2z1s1ő2
rzs1s
rzs1u2t
r1z1su
rzs1úr
r1z1sú
r2zs1ült
r1z1sü
rzu2sak
r1zu
rzu1sa
rzu2sa2n
rzus3s2
rzu2s3z
r2z1ut
2s.
1sa
2sa1bo
2sabr
2s1abs
2s1a2cé
s1a2da1lé
sa1da
2s1adap
2s1a2da1ta
2s1a2da1to
2s1ada1tó
sadás1s
sa1dá
2s1adm
sa2dog
sa1do
sa2dóc
sa1dó
sa2dód
sa2dó1é
sa2dóh
sa2dó1i
sa2dój
sa2dóm
sa2dón
sa2dór
sa2dó1u
sa2dóz
sae2r
sa1e
sa1f2r
2sa1ga
sa2gan
sa2g1ál
sa1gá
2sagg
sa1g1ne
2s1a2gó
sa2gő
s2a1g1rá
s1ag2y.
sag2y
s1agyb
s1a2gyo
sa2gyú
3sa2h.
saha2r
sa1ha
sa2hov
sa1ho
sa2ig
sa1i
2sa1ja
sa2jak
sa2jág
sa1já
2saján
sa2j1ö
saj2tal
saj1ta
saj2t1a2n
saj2tar
sajté2r
saj1té
saj2t1é1ré
saj2tor
saj1to
saj2t1ö
saj2t1ü2
s2a2k1ab
sa1ka
2s1a2kad
s2a2k1an
sa2kas
2s1ak1ci
sak2k1a2r
sak1ka
sak2k1as
sak1ke2
sak2k1eg
sak2ker
sak2ke2s
sak2kis
sak1ki
sak2kol
sak1ko
sak2k1orr
sak2k1ó2
2sak1ku
sa1k1lu
2s1akn2a.
sak1na
2s1aknáb
sak1ná
2s1akná1i
2s1aknák
sak1o1la
sa1ko
s2a2k1orm
sa2k1ös
sa1kö
sa1k2ru
2s1ak2t.
2s1ak1ti
2s1ak1tu
sa2k1ug
sa1ku
2s1a2kus
sa1k2va
2salab
sa1la
sala2g1
sala2ka
s1a2la1ku
2s1a2la1pa
sa2la1pí
sa2lapl
sa2lapoz
sala1po
s1a2lapr
sa2lapt
salá2da
sa1lá
2s1a2lá1í
salán1n
salás1s
2s1a2lá1té
2s1alb
s1a2lel
sa1le
s1a2l1é2pí
sa1lé
s2alé2t
sa2l1é1te
2salf
2s1algáh
sal1gá
2s1algá1i
2s1algán
2s1algásat
salgá1sa
2s1alja1i
sal1ja
2s1aljáh
sal1já
2s1aljár
2s1aljá2t.
2s1al1ka
2s1alkot
sal1ko
s1al1ku
2salm
2s1a2lomn
sa1lo
2s1a2lomr
sal2tag
sal1ta
2s1al1te
2s1alth
2s1altit
sal1ti
sa2l1ú
sa2m1an2y
sa1ma
sa2mec
sa1me
sa2m1il
sa1mi
sa2m1í
sa2mol
sa1mo
sa2m1os2z
s2amos
2s1a2mő
sa2nal
sa1na
sa2nat
s2and
2sa1ne
sa2nek
2s1ang2y
sa2nód
sa1nó
sa3nö
s1an1te
sa2ny2a.
san2y
sa1nya
s1a2nyag
sa3nyar
2sa1nyá
saó2r
sa1ó
sa2p1a2dó
sa1pa
sa2p1a2g
sapa2te
sa2pác
sa1pá
sap1á2c2s
sap1áll
sa2p1átm
sa2pá1to
sa2p1el
sa1pe
sa2p1ék
sa1pé
3sap1ka
3sapkáb
sap1ká
3sapkán
3sapkás
3sapkát
3sapkáv
sa2p1os2z
sa1po
s1a2pókr
sa1pó
s1a2pósab
sapó1sa
2sapp
s1ap1rí
s2a1p1ro
2sa1pu
sa2ra1tá
sa1ra
sa2ra1tó
sa2rán2y
sa1rá
sar2c3ho
sarc2h
sar2cin
sar1ci
sa2rén
sa1ré
sa2rit
sa1ri
sar2j1e
sar2k1e2
3sar1ki
sar2kin
sar2kir
sar2k1ö2
3sar1kú
3sar1ló
2sarm
2sarom
sa1ro
2sarz
sa1se2
sa2s1eb
sa1s2m
sa1s2po
sas3s1za
sas2s2z
sas3s1zá
sas3s1ze
2s1as1s1zo
3sast
sa2s1u2t
sa1su
sa2sú
s1aszf
sas2z
2saszt
sat2a2kés
s2atak
sa1ta
sata1ké
sa2t1alj
sa2t1alt
sa2t1a2nyá
s2atan
satan2y
sat1a1pu
s2atap
sa2t1á2ra2k.
s2atár
sa1tá
satá1ra
sa2t1ár1ka
satár1k2
sa2t1érk
s2atér
sa1té
sa2t1érr
sa2t1i2k
sa1ti
sa2t1ing
s2atin
s1at1lé
sat1mo1
satmos2z2
2s1a2tom1bó
sa1to
satom1b
2s1a2to1mo
sa2t1orm
s2ator
sa2t1ó2d
sa1tó
sa2t1ó2né
sa1t2rá
2sat2y
sa2tyá
sa2ul
sa1u
sa2u1rá
2saut
sa2u1to
s1aut2ó.
sa2u1tó
sautóé2r
s1autó1é
sautói2ko
s1autó1i
sa2uv
3sa2v.
3sav2a.
sa1va
sa2v1a2da
s2avad
sa2v1a2l
3saván
sa1vá
sav1á1ra
s2avár
sav1ári1a
savá1ri
sa2v1árt
sa2v1el
sa1ve
sa2v1ér
sa1vé
sav1ill
sa1vi
sa2v1i2n
sa2vő
sa2vü
sa2xi
sa2zon
sa1zo
1sá
sá2b1ak
sá1ba
sá2b1e2l
sá1be
sá2b1e2r
sá2bi2g
sá1bi
sá2b1il
sá2bö
sá2buj
sá1bu
sá2b1ut
sá2bü
1sá2c1sá
sác2s
sá2gab
sá1ga
sá2ga2d
ság1a1da
2s1ágadd
sá2gal
sá2ga2n
sá2gas
2s1ága2s.
ság1ass
sá2gat
2s1á2gaz
sá2g1ál
sá1gá
sá2g1á2rak
ságá1ra
sá2g1árn
sá2g1á2ru
sá2g1e2
sá2gép
sá1gé
sá2g1i2ko
sá1gi
sá2gí
sá2g1os2z
sá1go
sá2g1ó2
sá2g1ö
sá2gő
ság1s
sá2gü
sá2gű
4s1á2g2y
ságy1a2d
sá1gya
sá1ka2
sá2kal
sá2k1an
sá2kar
sá2kát
sá1ká
sá2k1e2
sákköz1
sák1kö
sá2k1ö
sá2kü
s1álc
sá2l1in
sá1li
2s1álm
s1á2lomb
sá1lo
3sá1má
sán2c1é
sán2c3so
sánc2s
sán1k2r
sánt2
sán1th
sá2nü
sá2p1ost
sá1po
sá2rad
sá1ra
sár1a1dá
sár1a2dó
sá2r1ag
sá2raj
2s1á2ra2k.
sá2ra1ka
2s1á2rakb
2s1á2rakh
2s1á2rakk
2s1á2rakn
2s1á2ra1ko
2s1á2rakr
sá2rakt
sá2r1a2l
2s1á2ram
sá2r1a2n
2sá2ras
sár1ass
sár1as2z
2s1á2rat
sá2rá2g
sá1rá
sá2r1ál
sár1d2
sá2r1e2
2sárét
sá1ré
sá2r1év
sá2ri2a.
sá1ri
sári1a
2s1á2ri1á
sár1i1ko
sá2r1i2p
sá2r1i2s
sá2rí
sár1k2
sá2r1os2z
sá1ro
sár1ott
sár1ó1ni
sá1ró
sá2rö
sá2rő
2sár1ro
sár1s2
sárt2
sár1tr
sá2rug
sá1ru
2s1á2ruh
2s1á2rul
2s1á2rus
sá1rú2
2s1á2r2ú.
sá2rü
sá2s1ad
sá1sa
sá2sar
sá2sás
sá1sá
sá2s1e2
sá2sis
sá1si
sáskés2z1
sás1ké
sá2só
2s1ás2ó.
sá2s1ó2r
2s1á2sóv
sá2sö
sá2ső
sás3s2z2
sá2s3za
sás2z
sászá2r1a2d
sá1s1zá
sászá1ra
2s1á2s1zo
2sá1ta
s1átad
sá2t1alj
s1á2t1ál
sá1tá
s1á2t1á2z
2s1átb
2s1átc
2s1átd
2s1á2t1e2
2sá1té
sá2t1é2l
2s1átf
2sáth
2sá1ti
sá2t1ir
2s1á2tí
2sátj
2sátk
2s1átm
s1á2tokt
sá1to
3sátor
sátókés2z1
sá1tó
sát2ókés
sátó1ké
2s1á2t1ö
3sát1ra
3sát1rá
s1át1re
2sáts
2sátt
s1át1tö
2sá1tu
2s1á2tü
2sátv
s1át1ve
s1át1vé
s1át1vi
3sá2v.
sá2v1a2d
sá1va
sáva2l
sá2v1a2r
sá2v1ál
sá1vá
sá2v1á2r
3sávb
sá2v1e2
sá2v1érz
sá1vé
sá2vis
sá1vi
sá2viz
3sávj
sá2v1or
sá1vo
sá2vö
3sávr
3sá1vú
3sávv
sba2l1
s1ba
sbe1á2
s1be
sbér2c2s
s1bé
sb2lo
sb2lú
sb2ra
sb2ri
sb2ro
sb2ró
scar2
s1ca
1schei1e
sc2h
s1c1he
sche1i
sc3hek
sc3hen
sc3het
sc3h2é.
s1c1hé
1schéb2e.
sché1be
sc3héd
1schéé1re
sché1é
1sché1re
sc3hés
sc3h2i.
s1c1hi
sc3hig
1schil
sc2ri
sda2dal
s1da
sda1da
sda1i2
sde1á2
s1de
sde1b2
sde2del
sde1de
sde1kr
sdes2
sde1sp
sde1st
sdesz2t
sdes2z
sdi2a1le
s1di
sdi1a
sd2ra
sd2rá
sd2ro
sd2ru
1se
sea2d
se1a
sea2l
sea2n
seá2l
se1á
seá2r
se2bag
se1ba
se2b1ak
seb1alt
se1bá2
se2b1ág
se2bár
se2b1e2g
se1be
seb1ell
seb1elz
se2b1esh
seb1e1s1ze
sebes2z
s1e2béd
se1bé
3sebés
se2b1ó2
se2bö
se2b1ő2
seb1p
seb1s
se2b1u
se2bú
se2bü2l
se1bü
3seb1zé
secs1a2p
sec2s
se1c1sa
se2csev
1se1c1se
se2cs1ék
se1c1sé
2sedez
se1de
sedél1
se1dé
2s1e2dén2y
se1d1ra
se1d2rá
se2d1zé
sed2z
se2d1ző
see2l
se1e
see2n
2seff
se1f2ra
2s1e2ger
se1ge
3segéd
se1gé
2segér
segés3s
s1e2gés2z1
2segét
3segg
seg2ga
seg2go
3se1gí
se2gyed
seg2y
se1gye
segy1e2l
s1e2gyé
2s1egyh
2s1e1gyü
se1hü2
se2h1üv
seí2r
se1í
sej2t1a2
sej2t1e2n2y
sej1te
sej2ter
sej2té2r
sej1té
sej2t1o
sej2tö
seka2r
se1ka
se2k1ál
se1ká
2s1e2ké2s.
se1ké
sek2k1á
sek2kos
sek1ko
sek2k1ö
se1k1lu
se1k2ra
se1k2ré
se2kur
se1ku
se2kúr
se1kú
sel1ak
se1la
se2lál
se1lá
2selb
s1el1dö
2s1e2lef
se1le
s1e2leg2y
se2l1e2h
se2l1e1ké
s1e2lemek
sele1me
2s1e2le1mé
se2lemk
se2lemm
2s1e2lemz
se2l1e1re
s1e2le1sé
sele2s2z
se2l1e1s1zü
sele2t1a2
sele2t1e2l
sele1te
se2l1e2vé
se2l1e2vő
selés3s
se1lé
2self
2s1el1ha
2s1elhel
sel1he
s1el1ho
sel1id
se1li
se2lis
2s1el1já
s1el1lá
2s1elmél
sel1mé
selnö2k1öl
s1elnök
sel1nö
selnö1kö
2s1eln2y
s1e2l1os
se1lo
2s1e2lö
se2lőad
se1lő
selő1a
s1e2lő1í
2s1e2lőz
2selr
s1el1ta
s1el1tá
2s1el1tö
2s1el2v.
2s1elvek
sel1ve
s1el1vű
2s1elvv
2selz
2sember
sem1be
s1embe2r.
s1emberb
s1embern
2s1embr
2semén
se1mé
2sem1lé
se2nat
se1na
se2n1ál
se1ná
sen2d1a
sen2d1á
s1e2ner
se1ne
senés3s
se1né
2s1enged
sen1ge
sen1ist
se1ni
se2n1or
se1no
s1en1ta
seny1a2g
sen2y
se1nya
seny1ak
se2ny1á2
seny1ell
se1nye
seny1e1re
se2ny1él
se1nyé
se2ny1é2r.
se2nyér1d2
se2nyér1te
2s1enyh2i.
seny1hi
se2nyi1gé
se1nyi
se2nyös
se1nyö
se2nyu
se2nyú
se2ny1ü2l
se1nyü
2s1enyv2e.
seny1ve
s1enz
seó2r
se1ó
se2pid
se1pi
s1e2piz
sep2pa
sep2p1á2
sep2per
sep1pe
2sep1ri
se1p2ro
se1p2ró
se2r1ag2y
se1ra
se2ral
se2ram
se2r1a2n
se2r1a2r
se2r1as
se2ra1u
se2r1á2g
se1rá
ser1áll
se2rár
s1er1de
2s1er1dő
sereg1g
se1re
3seregs
2serej
se2r1e2le2m.
sere1le
se2r1e2lő
se2r1elt
se2r1elv
sere1p
sere2pe1dő
sere1pe
se2r1e2sz2e.
seres2z
sere1s1ze
ser1eszk
sere1t
se2r1észb
se1ré
serés2z
se2r1i2ga
se1ri
se2r1il
seri2n
se2r1i1na
ser1inf
ser1ing
ser1int2
se2ris
se2riz
ser1k
se2r1ol
se1ro
se2ró
s1eróz
2se2rő
s1er2ő.
s1erőb
s1erőd
s1erő1é
s1erőf
s1erők
s1erőm
s1erőn
ser1ő2s.
s1erőt
s1erőv
ser1s
sert2
ser1tr
se2r1ü2g
se1rü
ser1ví2
2s1ese1mé
1se1se
2s1e2se1té
2s1esél
se1sé
2s1e2sés
2s1es1kü
2s1e2ső
se1s2p
ses1s1z1a2
ses2s2z
sesszé2l
ses1s1zé
ses1s1z1o
2s1es1te
se1s1ti
s1eszen
ses2z
se1s1ze
2seszk
s1esz1kö
2s1eszm
se2s1zű
se2t1a2la
se1ta
se2ta1na
se2t1a2n2y
se2tát
se1tá
se2t1e2g
se1te
set1eleg
sete1le
se2t1elk
se2t1elm
2setenk
2sete2s.
2sete1se
2s1e2te1té
2s1e2te1tő
seté2k
se1té
se2t1ékb
se2t1é1ké
se2t1é2l
se2t1énk
se2t1é2r.
se2t1é2ri
se2t1ér1té
se2ti1ka
se1ti
se2ti1ká
se2ti1ke
se2t1ing
se2tol
se1to
set1old
se1tran
set1ra
se1t1ri
se1t2ro
2settk
seü2t
se1ü
2s1e2vés
se1vé
2s1e2vő
se1ye
se1yé
se1yi
s1ezr
1sé
2s1ébr
sé2f1a
sé2f1á
sé2g1a2
sé2g1á2
3ség2e.
sé1ge
sé2g1eg
3sége1i
3ségek
ség1e1le
sége2lem
sé2g1ell
3ségem
sé2g1ent2
sé2ger
ség1erk
sé2g1es2z
sé2ge1té
sé2g1e2ti
sé2gev
ség1é2je
sé1gé
sé2g1é2k
sé2gép
sé2g1é2r.
sé2g1é2ré
sé2g1érn
sé2g1érr
sé2g1ér1tő
ségért2
sé2g1é2rü
sé2g1érv
ség1és2z
ség3g
ség1i1ga
ségi2g
sé1gi
sé2g1i2gé
ség1ist
sé2gí
3ségk
sé1go2
sé2g1ok
sé2gol
sé2g1ó2
sé2gö2r
sé1gö
sé2gő2
sé2g1őr
ség1ős
ség1s
sé2gu
sé2gú
3sé1gü
sé2g1ü2lő
ség1ült
2s1é2h.
2s1é2h2e.
sé1he
2s1é2hek
2s1é2hes
2s1é2het
2s1é2hé
2s1éhh
2s1éhr
2s1éhs
sé2jem
sé1je
s1é2ji
s1éjs
sé2kek
sé1ke
s1ék1ho
2s1ékm
2s1éks
sé2k1ú
sé2lel
sé1le
s1é2let
2séll
2s1élm
sélőkés2z1
sé1lő
sélő1ké
sé2lű
2sél2y
sé2lya
sé2lyeg
sé1lye
3sé1má
sé2mu
s1é2nekb
sé1ne
2s1é2ne1ke
s1é2ne1ké
2s1é2nekh
2s1é2ne1ki
s1é2nek1k2
2s1é2nekl
2s1é2nekn
s1é2nekr
s1é2nek1t2
s1é2ne1kü
2s1é2pí
2s1é2pü
sé2ral
sé1ra
sé2ran
sé2rát
sé1rá
2sérb
2sérc
s1ér1de
sé2ré1é
sé1ré
sé2ri1e
sé1ri
sé2ri2g
s1érlel
sér1le
2sérm
sé3ró
2sérr
s1érté1ke
sér1té
3sérvb
s1érve1i
sér1ve
3sérvem
3sérves
3sérvh
3sérvr
3sérvv
4sé1ry
2s1érz
2sés1di
2sése1ki
sé1se
sé2s1el
2sés2s2z2
2sés1tú
sész1ak
sés2z
sé1s1za
s2é2s1z1á
sé2sz1emb
sé1s1ze
sé2szir
sé1s1zi
sé2s1z1o
sész3s
sét1abl
sé1ta
sé2t1a2d
2sétk
sé2t1o
sé1tő2
sé2t1őr
2sétr
2sétt
2sétv
2s1é2v.
2s1é2vad
sé1va
2s1évb
2sév2e.
sé1ve
2s1é2ved
2s1é2ve1i
2s1é2vek
2s1é2ven
2sévet
2sévéb
sé1vé
2s1é2vé1i
2sévén
2sévét
2sévév
2s1évf
2sévh
2sév2i.
sé1vi
2s1évk
2s1évn
2s1évr
2s1évt
2s1é2vü
2s1évv
sfaá3ga2c
s1fa
sfa1á
sfaá2ga
sfa2gy1al
sfag2y
sfa1gya
sfenyő1é2
s1fe
sfen2y
sfe1nyő
sfé2má
s1fé
sfé2m1e2kéh
sfé1me
sféme1ké
sfé2m1é
sfé2mi
sfiú1é2
s1fi
sfi1ú
sfi2úé1ra
sfi2úét
sf2le
sf2lo
sf2ló
sf2lu
sfo2k1út
s1fo
sfo1kú
sf2ra
sf2rá
sf2re
sf2ri
sf2rí
sf2ro
sf2rö
sga1b
s1ga
sga1d2
sga1p
sga1tr
sge2o
s1ge
sg2le
sg2ló
sg2nó
s1g2ra
sg2rá
sg2ri
sg2ró
shelyü2kü
s1he
shel2y
she1lyü
1s2h2i.
s1hi
1s2hih
1s2hij
1s2hik
shitköz1
shit1kö
s2hop
s1ho
s2horr
3s2how
shú2sár
s1hú
shú1sá
shús1s
1si
si2ac
si1a
sia2d
si2a1é
si3ak
sia2l
sia2n2y
siá2ro
si1á
si2b1á2
sibilis1
si1bi
sibi1li
sidás1s
si1dá
si2de1a
si1de
si2de1á
si2deg
si2de1i
si2den
si2de1o
s2idet
2si1dé
s1i2dén
si2dom
si1do
2s1i2dő
si2du
si2eg
si1e
sie2l
si2en
si2e1u
2s1ifj
2si1ga
s1i2ga1zo
2si1gá
2si1ge
s1i2g2e.
s1i2gek2
s1i2ges
2s1i2gé
sig2n1e
sig2n1ó2
2si1ha
3sihed
si1he
2s1ihl
si2k1a2n
si1ka
sike2r1es2z
si1ke
sike1re
sike2s
si2k1in
si1ki
si2k1ir
si2k1old
si1ko
s1i2konh
s1i2konj
2s1i2konk
s1i2konn
2s1i2ko1no
s1i2konr
2s1i2kont
s1i2ko1nu
sik1orr
si1k1ré
sikus1s
si1ku
2s1i2mád
si1má
si2m1el
si1me
s1i2mit
si1mi
3si1mí
3simog
si1mo
2simp
s1impor
sim1po
si2m1u2t
si1mu
2s1i2nas
si1na
2sind
s1in1du
s2i2ner
si1ne
2sinf
2sin1ga
s1in1gá
s1inget
sin1ge
s1in1gé
s1ingók
sin1gó
si2nic
si1ni
2sinj
2s1in1ku
2s1ins
s1in1te
2sinv
s1in1vá
2s1inz
si2onn
si1o
s1i2o1no
si2ó1a
si1ó
si2ó1á
si2óc
si2ó1da
si2ó1e
si2óg
si2ói2k
sió1i
si2ó1o
si2óp
si2ó1ú
2s1i2pa2r.
si1pa
2sipa1ra
2sipa1rá
2s1i2parb
2s1iparc
2s1i2parh
2s1i2parm
2s1i2parn
2s1i2pa1ro
2s1i2parr
2s1i2par1tá
2s1i2par1tó
2s1i2pa1ru
3si1pí
3sip1ka
3sip1ká
3sipol
si1po
2s1i2ram
si1ra
s1irg
2s1irh
2si1ro
s1i2rod
2s1i2rón
si1ró
2sirt
s1ir1tó
si2sad
si1sa
si2sél
si1sé
si2s1is
si1si
si2s1í2
sis3s
sist2
si2s1ü2
si2s3zab
sis2z
si1s1za
s1i2tal
si1ta
sita2li
si2t2e.
si1te
si2t1i
si2t1ö
sit2tin
sit1ti
3sit2y
siú2t
si1ú
2s1i2vad
si1va
2s1i2var
2sivás
si1vá
3si1ví
2s1i2vó
2si1zé
si2z2é.
si2zéb
s1iz1mo
1sí
sí2gé
sí2ja
sí2ju
sí2ka2s
sí1ka
sí1ke2
sí2k1er
sí2kí
sí2kü
sí1na2
sí2nac
sí2nal
sí2nan
sí2n1á2
sí2nel
sí1ne
sí2nö
2síns
sí2n1ú
sí2r1ad
sí1ra
sí2r1ál
sí1rá
sí2rá2s3z
sír1á2to
sí2red
sí1re
sí2r1e2m
sí2re2n
sí2res
sí2r1e2t
sí2r1én
sí1ré
sí2rir
sí1ri
sí2rí
s1í2ró1a
sí1ró
sí2róf
sí2ról
sí2ró1p2
sí2rö
sír2t1e2v
sír1te
sí2r1ü
sí1sp
sí1st
síté2kol
sí1té
síté1ko
2s1í2tél
sítés3s
sítő1a2
sí1tő
sítő1e2
sí1tr
2s1í2v.
sí2vá
2s1ívb
s1í2vek
sí1ve
s1í2ve2n
s1í2vet
sí2vé
sí2vű
sí2za
sí2z2e.
sí1ze
2s1ízl
s1ízr
2sí1zü
sí2zül
sje2gy1á2
s1je
sjeg2y
ska1o2
s1ka
skapoc2s1
ska1po
ska2r1i2m
ska1ri
skaros3
ska1ro
ska2ró2r
ska1ró
ska1sm
skat2
ska1tr
ska1u2
1s2kálák
s1ká
ská1lá
1skálár
ske1p2
s1ke
ske1s2p
sk2é2p1el
s1ké
ské1pe
ski1á2
s1ki
1s2kicc
ski1e2
sk2jö
sk2la
sk2li
sk2lí
sk2lo
sk2ló
sk2lu
skolás1s
s1ko
sko1lá
s2kor1pi
sko2s1a2ra1i
sko1sa
skosa1ra
skó1p
s1kó
s1k2rá
s1k2ré
s1k2rit
sk1ri
sk2rí
sk2rón
sk1ró
sk2ru
sk2va
sk2vi
sk2vó
sky1ér
s1ky
sky1i
sla1d2
s1la
sla2g1e2
sla1s2p
sla2t1a2l
sla1ta
sl2a2t1a2n
sla2tel
sla1te
sla2tev
sla2tés
sla1té
sl2a2t1é2t
s2lág
s1lá
1slágere1i
slá1ge
sláge1re
slá2nyal
slán2y
slá1nya
slás3s
sle1ga2
s1le
sleg1g2
sle1í2
sle2tal
sle1ta
sle2t1el
sle1te
sle2t1em
sle2té2l
sle1té
sleves1s
sle1ve
slé1i2
s1lé
slé1ke2
slé2kev
slé2k1ol
slé1ko
slés3s
slic2c1elv
s1li
slic1ce
sli2d
sli2ká
1s2liss
slős2
s1lő
s2lus
s1lu
sma1ó2
s1ma
smarc2
smas2
sma1sp
3s2má2r.
s1má
3s2má1ru
sme2g1a2
s1me
sme2g1é
smen2tel
smen1te
1s2mirg
s1mi
sn2a2pal
s1na
sna1pa
sna2p1e
sna2p1or
sna1po
snap2s1z1e2
snaps2z
sne1yi
s1ne
snit2t1elv
s1ni
sn2it1te
snőé2h
s1nő
snő1é
1so
s1obj
so1c2k
sodaé2ne2
so1da
soda1é
soda1g2
so2d1e
so2dén
so1dé
so2dév
so2did
so1di
so2dis
so2dí
so2dob
so1do
so2d1org
so2d1os
so2dö
so2dő
sodrás1s
sod1rá
so2dú
sod1út
so2dü
so2d3z
so2kab
so1ka
so2ka1i
so2k1aj
so2k1a2la
so2k1ap
so2kar
soka2t
so2k1a1to
so2k1ál
so1ká
so2k1árn
so2k1e2
sok1ill
so1ki
so2kí
3sok2k.
sokka2l
sok1ka
sok2k1a1la
sok2k1a2p
2sok1ke
sok2k1el
sok2kir
sok1ki
sok2k1ö2v
sok1kö
sok2k1ü
2sokl
2sokm
so2kok
so1ko
sok1old
2so2kos
2s1o2koz
so2k1ó2
2sok1ta
s1oktat
so2k1ú2
so2kü
2s1o2laj
so1la
sol2a2j1á2r
sola1já
sola2je
sola2tel
sola1te
solás1s
so1lá
2s1ol1da
2s1ol1dá
2sol1dó
s1old2ó.
s1oldók
s1oldóm
s1oldón
s1oldór
s1oldót
s1oldóv
so2lim
so1li
2s1ol1ló
soló1ó2
so1ló
s1oltás
sol1tá
2s1olvas
sol1va
so2lyan
sol2y
so1lya
s2o2lyó
so2m1ad
so1ma
somag1g
so2m1a2l
soma2t
som1a1to
so2m1e2
so2mél
so1mé
so2m1é1te
so2mil
so1mi
so2m1i1ta
so2mí
so2m1o1do
so1mo
som1ort2
so2m1os2z
som1p
so2mú
so2mü
son2c2h
2sond
so2n1e
son2kál
son1ká
son2k1e2
2so1nó
son2tab
son1ta
son2t1a2l
son2t1a2n
son2tar
son2t1á2r
son1tá
son2t1e2
sonté2r
son1té
son2t1é1ré
son2tik
son1ti
son2tip
son2tö
son2tő
2sonv
so2nye
son2y
so2nyis
so1nyi
so2ór
so1ó
so2ós
s1opc
s1o2pe
sor1a1da
so1ra
sor1a1la
sor1áll
so1rá
so2r1átl
3so2r1e2
sor1f2
s1or1gi
so2rid
so1ri
2s1o2ri1e
so2rif
so2rim
so2r1ing
so2ris
3sor1ké
sor1mű1
sorműt2
sor1oszt
so1ro
soros2z
3soro1za
so2r1ó2
so2rö
so2rő
2s1or2r.
s1orrát
sor1rá
2s1orrb
2s1orrn
2s1or1ro
2s1or1rú
3sor2s.
sor2sal
sor1sa
sor2sar
sor2sas
sor2s1e2
sor2sir
sor1si
2sor1só
sor2sü
sor1s2z
sor2szón
sor1s1zó
sor1t1re
sorú2t
so1rú
so2r1ú1to2
so2rü
so2rű
2s1os1to
2s1ostr
s1ost2y
2sos2z
s1oszl
s1oszt
so2ul
so1u
so2ur
so2ve
so2vis
so1vi
so2xi
1só
sóá2g
só1á
sóá2r
sócsa1pá2
s2ó1c1sa
sóc2s
só2dá1i
só1dá
só2dák
só2dáv
sógé2p1e2ké
só1gé
sógé1pe
só1g2r
sói2g
só1i
sóí2v
só1í
só2k1a2d
só1ka
só2kil
só1ki
s2ó2kim
só2kó
só1k1ré
só1mű1
só2n2é.
só1né
só2ni
sóó2r
só1ó
só2ra1i
só1ra
só2ráb
só1rá
só2rá1i
só2ráj
só2rám
só2rán
só2rár
só2rát
só2ri1á
só1ri
só2s1ü2
só1s2z
sótá2ny1ér
só1tá
sótán2y
sótá1nyé
só1t2r
2s1ótv
2s1ó2vó
1sö
sö2bű
sö2ga
sö2gá
sö2g1e2l
sö1ge
sö2g1em
sö2g1ék
sö1gé
sög3g
sö2go
sö2g1ö2lé
sö1gö
sö2g1ölt
sö2gű
sö2ka
sö2k1e2l
sö1ke
sö2ki2d
sö1ki
2s1ö2ko
s1ö2kör
sö1kö
sö2kú
sö2lőj
sö1lő
s1ö2lőz
s1öl2y
sö2na
sö2ná
sön2d1a2
sö2no
sö2n1öl
sö1nö
sö2n1ő
s1öntv
sö2nú
3sö2r.
sö2ra
sö2rá
3sörb
2sörd
sö2r1ed
sö1re
sö2reg
sö2ren
sör1f2
sö2r1iz
sö1ri
3sörk
sö2ro
sö2ró
sö2r1ö2l
sö1rö
sö2r1ő
sör1s
sö2ru
2sörv
s1ös2s2z
s1ösv
s1ös2z
2s1ötl
2s1ötv
2s1ö2v.
2sö1ve
s1övv
s1ö2zön
sö1zö
1ső
sőa2c
ső1a
sőa2l
sőa2n
2sőá2g
ső1á
ső1bl
2sőbok
ső1bo
ső2dad
ső1da
ső2dalk
ső2dá
ső2del
ső1de
ső2din
ső1di
ső2d1ő2r
ső1dő
ső1d1ro
sőe2l
ső1e
sőe2r
sőe2s
ső1gr
ső1kl
ső1pl
ső1pn
ső1pr
ső2r1aj
ső1ra
ső2r1á2csot
ső1rá
sőrác2s
sőrá1c1so
ső2riz
ső1ri
ső2rol
ső1ro
ső2r1ö2l
ső1rö
s1őr1ti
s1őrz
ső2s1av
ső1sa
ső2sim
ső1si
sős1orr2a.
ső1so
sősor1ra
ső2s1or1rá
ső2s1or1ró
ső1s2p
ső1s1ta
ső1st2r
ső1sü2
ső2s1ül
ső2s1üt
ső2szap
sős2z
ső1s1za
ső2sz1áll
ső1s1zá
sőszé2k
ső1s1zé
sősz1é1ké
ső2s1z1í
spa2d1a2
s1pa
1s2pann
s2pa1tu
s2páj
s1pá
spá2n1a2
1s2párg
3s2pe1ci
s1pe
1s2pektr
1s2pe1ku
1s2pék
s1pé
s2p1ér
1spirá1lú
s1pi
spi1rá
spis3s
sp2le2
s1p2lé
sp2lu
sp2ne
1s2pong
s1po
spor1ta2
spor2t1al
1s2por2tág
spor1tá
spor2tár
3s2portb
spor2t1e2
1s2por1té
spor2t1érd
1s2porth
spor2t1i2n
spor1ti
1s2portj
1s2portn
1s2porto1ka
spor1to
1s2portol
1s2porton
1s2portos
3s2portot
spor2t1ö
spor2tő
1s2port1ra
1s2port1ró
1s2port1tó
1s2por1tu
spor2t1ü2
1spórá2k.
s1pó
spó1rá
1spórol
spó1ro
sp2ra
s1prak
3spray
s1p2re
s1p2ré
1spric
sp1ri
s1p2ro
s1p2ró
sp2s2z
1s2raf
s1ra
sra1u2
1s2rá1co
s1rá
sren2d1ő2
s1re
3s2róf
s1ró
srú2de
s1rú
ssab2b1i
s1sa
ssa2vo
sság3g
s1sá
ssé2g1e2l
s1sé
ssé1ge
ssé2g1é2j
ssé1gé
ssé2gid
ssé1gi
s2s1ékt
s2s1i2z
s1si
ss2kál
ss1ká
ss2ko
ss1k2r
ss2lá
sso2m1o
s1so
ss2pó
s1s2rá
ss3s2z
ss2tad
ss1ta
ss2tar
ss2tat
s1s2tí
ss2to
s1st2r
ss2tú
s2s1u1ra
s1su
ssy1ér
ssy1ét
s2s2z
ssza1e2
s1s1za
s2sza1p2r
sszat2
ssza1tr
ssz1á2g
s1s1zá
ssz1á2ram
sszá1ra
ssz1á2ruk
sszá1ru
ss2z1á2s2z
s3szá1za
s3sze1dé
s1s1ze
ssze2g1é2r.
ssze1gé
sszegü2l
ssze1gü
ssze2g1ü1lő
ssz1e1gye
sszeg2y
s3szekv
ssz1e2lő1a
ssze1lő
s3s2zeml
s3szemm
ssze1p2
ssze2reib
ssze1re
sszere1i
ssze2reid
ssze2rein
ssze2re1ké
ssze2rek1t
ssze2re2m.
ssze2remm
ssze2re1se
sszer2t1á2r
sszer1tá
ssze1t2r
s3szé1ki
s1s1zé
ssz1é2lő
s2sz1ér1in
sszé1ri
s3szé1to
s3szi1ge
s1s1zi
s2sz1ing
sszis1s2
sszí2vel
s1s1zí
sszí1ve
ssz1k2
ss2zkés2z1
ssz1ké
s3szob
s1s1zo
ss2z1os2z
ssz1ó1sá
s1s1zó
s3szöc
s1s1zö
s3ször
ssz1ös
s3szöv
ssz1p2
ss3z1si
ssz2s
sszt2
ssz1t1ro
s3szur
s1s1zu
sszus1s2
ssz1ú2r.
s1s1zú
1stabil
s1ta
sta1bi
1s2tadi1o
sta1di
s2t1a2dó
1s2ta1fí
s2t1alj
s2t1alk
s2tand
1stan1da
sta2n1ó2r
sta1nó
stan2s1é
s2t1a2nyag
stan2y
sta1nya
s2tarc
1s2t2ar2t.
star2tas
star1ta
1s2tartos
star1to
1s2tartot
1s2tartt
sta1sl
sta1s2t2
sta1t2r
sta3u
stau2t
s2t1a2x
st1a1zo
3s2tá2b.
s1tá
1s2tábj
1s2tábk
1s2tábn
1s2tábot
stá1bo
s2t1á2g.
st1áld
stán2c1ol
stán1co
stán2s1á2
stá2ri1á
stá1ri
s2t1ár1ka
stár1k2
s2t1ár1ká
st1á2t1a2
st1áts
1s2tá1tu
st1átv
st1br
st1dr
ste2a
s1te
1s2teak
s2t1e1bé
s2tedén
ste1dé
s2t1e2g2y
s2t1e2k2e.
ste1ke
s2t1elf
s2t1elh
s2t1ellen
stel1le
s2t1elm
s2t1e2lo
s2t1e1lö
s2t1e2lő
s2t1elr
s2t1elt
s2t1elv
s2t1ember
stem1be
s2t1e2mel
ste1me
s2t1eml
ste2n1a
ste2n1á
ste2ne2g
ste1ne
ste2n1és
ste1né
sten3n
ste2nos
ste1no
ste2n1ő2
1s2tepp
ste2rad
ste1ra
ste2raj
ste2ral
ste2rav
ste2rác
ste1rá
ste2rál
ste2r1e2le
ste1re
1s2teri1li
ste1ri
ste2r1int2
ste2ris
ste2r1o
s2t1e2rő
ste2s2z
s2t1eszm
ste2u
1stégek
s1té
sté1ge
1s2téget
1s2tégg
1s2tégr
sté2k1a2
sté2ká
stékát1
sté2k1á1ta2
sté2k1el
sté1ke
st1é2ke1sí
st1é2kí2
sté2k1o
sté2ku
s2t1é2le
st1é2lé
s2t1élm
s2t1élt
st1élv
s2t1érc
s2t1é2rem
sté1re
s2t1é2ré
s2t1é2rőt
sté1rő
s2t1ér1té
s2t1érz
stés3s
s2t1észl
stés2z
s2t1étk
s2t1é2v2e.
sté1ve
st1fl
st1fr
st1gr
s2t1i2d
s1ti
sti2g
s2t1i1ge
s2t1i1gé
s2t1i2ma
s2t1i2má
1s2timm
s2t1imp
sti2n2a.
sti1na
s2t1ind
s2tinf
s2t1ing
s2t1inv
s2t1i2o
st1i2pa
st1i1rá
st1i1ró
stis3s
sti2s2z
s2t1i1s1za
s2t1i2vá
st1i2zo
s2t1íj
s1tí
3s2tíl
s2tír
st1í1rá
st1í2v
st1í2z
stká2ro
st1ká
st1kr
st1kv
stola2t
s1to
sto1la
s1to2la1to
sto2p1a
1s2topb
1s2toph
1s2topr
1s2top1t2
sto2rál
sto1rá
sto2rás
sto2re2
sto2ris
sto1ri
st1o2x
3s2tó1la
s1tó
3s2tó1lá
st1ó2rá
1s2tós
stö2k1ölt
s1tö
stö1kö
s2t1önt
s2t1ö1rö
s2t1ös2s2z
stő1a2
s1tő
stő1e2
stőkés2z1
stő1ké
s2t1ő1ra
s2t1őrb
s2t1őrc
s2t1őreg
stő1re
s2t1őre1i
s2t1őr2é.
stő1ré
s2t1őrén
s2t1őrér
s2t1őrév
s2t1őrf
s2t1őrg
s2t1őrh
s2t1ő2ri
s2t1őrl
s2t1őrm
s2t1őrn
s2t1őrp
s2t1őrs
s2t1ő2rü
s2t1őrv
st1pf
st1pl
st1pr
1stran1do
st1ra
1st2rap
1stra1té
strat2
s1trág
st1rá
1st2réb
st1ré
s1t2rén
st2ri1á
st1ri
st2róf
st1ró
1stró1fá
1st2ruc
st1ru
1st2ruk1tú
st1sc
st1st
st1s2z
st1t2r
s2t1udv
s1tu
1stukk
st1u1ni
st1u2ral
stu1ra
3s2túd
s1tú
st1ú2r.
st1út
s2t1üg
s1tü
s2t1ü2lő
s2t1üst
s2t1üt
s2t1ü2v
s2t1ű2r.
s1tű
s2t1ű2ri
s2t1űrn
s2t1űrt
1su
suá2r
su1á
su1bi1
s1udm
2s1udv
3sugá2r.
su1gá
3sugárr
2s1ugr
3su1hi
2sujj
suj2j1a2da
s1ujjad
suj1ja
su2k1a1rá
su1ka
su2ke
su2k1ö
su2k1ü
3summ
su2nal
su1na
2s1und
2s1u2ni
su2no
su1pe2
2su1ra
s1u2rad
su2ra1i
su2rak
su2ral
su2rat
su2rát
su1rá
2surn
2s1u2ru
su2s1zo
sus2z
2s1u2tac
su1ta
2s1u2tad
2sutak
s1u2tal
2s1u2tam
2s1u2tan
sutas1s
2s1u2tat
2s1u2ta1zi
s1u2ta1zó
2s1utc
2su1tó
su2tód
2su1tu
1sú
sú2csal
s2ú1c1sa
súc2s
sú2csat
sú2csem
s2ú1c1se
sú2cser
sú2csip
sú1c1si
súcs1ká2
sú2c1s1ö
sú2c1s1ü2
súcs3z
sú1di2
súi2m
sú1i
s1újh
2s1ú2jí
2s1újs
3súl2y
2sú2r.
2súrb
2sú1ré
2súrh
2sú1ri
2súrk
3súrl
2súrn
2súrp
2súrr
2súrt
sú2sén
sú1sé
sú2só
2sús2z
sús2zó2s3z
sú1s1zó
2s1útb
sút1en
sú1te
2sú1té
s1úth
2sú1ti
sú2ti2g
2s1útj
2s1útk
s1útn
2sú1to
s1ú2to2n1
sú2t1ő
2s1útr
2s1útt
sú1tü2
sút1üz
2s1útv
1sü
sü2d1e
sü2dí
sü2dü
3sü3gé
s1üg2g2y
2s1ü2g2y
3sü2k1a
sü2kü
2sül1dö
sü2led
sü1le
3sültr
sü1lye2
sül2y
sü2lyes
sü2ná
sü1ne2
sü2nev
sü2n1é
2s1ü2nő
sü2rí
2s1ü2rü
2süst
2s1ü2s2z
2sütk
3sütőb
sü1tő
sü2ze
sü2zé
1sű
sű1pr
sű2r1a
3sű1rí
s1ű2ru
sű2ző
sva2s1u2
s1va
svá2gy1ó2n.
s1vá
svág2y
svá1gyó
svá2nya2n
sván2y
svá1nya
svá2r1a2l
svá1ra
sváro1si2
svá1ro
sven1
s1ve
svezető1é2
sve1ze
sveze1tő
své2nye2l
s1vé
svén2y
své1nye
své2tes
své1te
sví1ze2
s1ví
svíz1es
s2vun
s1vu
sw2hi
swhis1ky2
sy2bő
sy1ig
s2z
1s1za
sza1a2
3sz2abáz
sza1bá
3szabd
sz1a2dás
sza1dá
2sz1a2dó
sza1é2
3szaft
3sza2g.
3szagb
3sza1gú
2sz1ag2y.
szag2y
2sza1gya
2s3za2j.
2sza1já
2s3zajj
2s3za1jú
2szak1ci
s2zakc
3szakm
sza2k1ó2r
sza1kó
sza2köz
sza1kö
2szalás
sza1lá
2sz1alb
2sz1alf
2szalg
2szalj
sz1al2j.
sz1aljak
szal1ja
sz1al1jo
2szall
2szaln
2sz1alp
2sz1als
2szal2t.
2sz1al1te
2szal1to
2szal1tu
2sza1ne
sz1antr
2sza1nya
szan2y
2sz1a2nyó
2sza2p.
2sza1pá
2szapb
sza2pe2l
sza1pe
2sza1pé
2szaph
2sza1pi
2szapj
2szapn
2szapr
2szapt
2szarán
sza1rá
3szarb
2s2zarc
2sza1ré
3szart
3szarv
2szasp
s2z1as2s2z
sz1aszt
szas2z
sza2ta1la
sza1ta
3s2zat2y
2szaut
sza1u
szau2ta
3szax
2sz1a2zo
1s1zá
2s2z1ábr
2szá1gó
2szág2y
szá2gyá
szá2gyo
szá2j1e
szá2ke
2szál1do
szá2lin
szá1li
3szá1lú
szá2man
szá1ma
szá2ma2r
3szá1má
szá2mál
3számc
szá2m1e2
3szá1mé
szá2m1ér1té
3szá1mí
3számk
3száml
3számm
3számn
szá2mor
szá1mo
3számt
3szá1mú
3szánd
3szánt
2száp
2sz1á2radd
szá1ra
2szárás
szá1rá
szá2r1e
2s2z1á2ri1á
szá1ri
3szá1rí
3szárm
szá2r1ó2ra
szá1ró
szárt2
2sz1á2r2u.
szá1ru
2sz1á2ruh
2sz1á2rus
2sz1á2s2ó.
szá1só
2sz1átd
2sz1á2t1é
2sz1á2t1i2
2szátl
2s2z1átm
2szá2t1ö
2s2záts
2sz1á1tu
2sz1á2tü
2s2z1átv
sz1bl
sz1br
1szcé1na
sz1cé
sz1cl
sz1d2r
1s1ze
2s2z1e2b.
2sze1bé
2sz1e2c2h
2sze1c1se
szec2s
2s2z1e2d2z
2szeger
sze1ge
3szeg1fű
2szegz
2szeh
3szek3cs2ő.
szekc2s
szek1c1ső
sze2keg
sze1ke
2sz1e2kéit
sze1ké
szeké1i
2szekés
sze2ké2s.
3szekrén
szek1ré
3szek1to
szek1t2
3szel2e.
sze1le
2szelef
2szelemb
2sz1e2lemek
szele1me
2szele1mé
2sz1e2lemk
2szelemm
2sz1e2lemr
2szele1mü
2szelemz
2sz1e2l1e2re
3szelet
3szelén
sze1lé
2sz1elf
2sz1el1go
2sz1el1ha
2sz1el1há
2sz1el1já
2sz1el1kü
2sz1el1lá
3szel1lő
2sz1elm
2s2z1elnev
szel1ne
2sz1el1nö
2sze1lo
2sze1lö
2sz1e2lő1á
sze1lő
sze2lő1dö
2sz1e2lőf
sze2lő1ré
2sz1el1ső
2sz1el1s1zá
szels2z
2sz1el1ta
2sz1el1tá
2s2z1el1tű
2sz1e2lu
2sz1elül
sze1lü
2sz1el2v.
2sz1el1vá
2sz1elvek
szel1ve
2sz1elves
2sz1elvez
sz1el1vi
2sz1elvn
2sz1elvt
2sz1elz
2s2zember
szem1be
3s2zemek
sze1me
3személ
sze1mé
3szem1p2
3szem2ű.
sze1mű
2szenc
2szener
sze1ne
3szen2n2y
3szentm
3s2zenz
sze1o2
2sze1pi
sze2r1á2l
sze1rá
2sz1e2redm
sze1re
sze2r1e2ge
sze2rej
3szerel
sze2r1eszt
szeres2z
3szer1ke
szer1k2
3szer1s
3szer1ta
szer2tá1lo
szer1tá
3szerv
szervíz1
szer1ví
3szerz
2sz1esd
2sz1ese1mé
sze1se
2sz1e2setb
2szese1te
2sz1e2se1té
2sz1e2se1ti
2sz1e2setr
2sz1e2sés
sze1sé
2sz1es1kü
2s2z1e2ső
sze1sp2
2s2zes1te
2s2z1es1té
2szes1ti
2s2z1estj
2szestr
sze2t1e2lő
sze1te
sze2t1é2k
sze1té
2sz1e2ti1ka
sze1ti
sze2ton
sze1to
sze2tőr
sze1tő
2sze1ve
2sz1e2vő
2szexp
3sze1zo
2sz1ezr
1s1zé
2sz1é2ber
szé1be
2s2z1ébr
2sz1é2g.
2sz1é2ge
2sz1é2gő
2sz1égr
2sz1é2h2e.
szé1he
2sz1é2hen
2sz1é2het
2s2z1éhs
2sz1é2ji
3szék2e.
szé1ke
3széked
3széke1i
3székek
3székem
3székes
2sz1é2kez
3székéb
szé1ké
3székév
szé2k1ol
szé1ko
szé2kos
3széks
3szé1kü
3szélek
szé1le
2s2z1é2ne1ke
szé1ne
2sz1é2nekn
szé1p1ró
3szép1s
2sz1é2r.
2sz1érb
2sz1érc
2sz1ér1d2
sz1é2rel
szé1re
2szérem
szé2re2m.
szé2remm
2sz1é2re2n
2szé1ré
szé2r2é.
szé2rén
szé2rér
szé2rét
szé2rév
sz1érf
sz1érg
sz1érh
2sz1érin
szé1ri
sz1érj
2sz1érk
2sz1érl
2sz1érm
sz1érn
2s2z1é2r2ő.
szé1rő
2sz1é2rő1i
2sz1é2rők
2sz1é2rőt
sz1érp
2sz1érr
sz1ér1s
sz1ér1tá
2sz1ér1te
2sz1ér1té
sz1ér1tí
2sz1ér1tő
3szé1ru
2sz1é2rü
2sz1érv
2sz1érz
szé2tel
szé1te
széte2s
2s2z1é2v.
2s2z1é2vad
szé1va
2sz1évb
2s2z1é2v2e.
szé1ve
2sz1é2ve1i
2s2z1é2vek
2sz1é2vet
2sz1é2vén
szé1vé
2sz1é2vét
2sz1é2vév
2sz1évf
2s2zé1vi
2sz1évk
2s2z1évn
2sz1évr
2sz1évs
2sz1évt
2sz1é2vü
1sz2féra1i
sz1fé
szfé1ra
1sz2férá1é
szfé1rá
1szférá1so
sz1fl
sz1fr
sz1gl
1s1zi
szi2ab
szi1a
2szi1de
2sz1i1dé
2s2z1i2dő
2sz1ifj
2sz1i2ga
2sz1i1gé
3szign
szi2k1a2s2
szi1ka
szi2k1e2r
szi1ke
szi2k1ó2
sz1i2mak
szi1ma
2sz1i2má
3szim1b
sz1impr
2sz1im1pu
2s2z1i2nas
szi1na
2szin1de
2szin1dí
2sz1in1du
sz1in1fo
2szing
sz1in1ko
2szinteg
szin1te
2szi1o
2sz1i2rat
szi1ra
2s2zi1rá
2sz1i2ri
2sz1i2rod
s2zi1ro
szi2sí
2sz1i2s1za
s2zis2z
szi2s1zá
3szi1tu
2sz1i2vad
szi1va
2szivás
szi1vá
2s2z1i2vó
sz1izg
2sz1izz
1s1zí
2sz1íg
3szí1ne
2sz1ín1na
szí2vár
szí1vá
2szí1vi
3szí1vű
2sz1íz
szka1pr
sz1ka
1szkarab
szka1ra
szk1arc2h
1sz2kenn
sz1ke
1szlávh
sz1lá
1szlávok
sz2lá1vo
1sz2len2g.
sz1le
1szlengn
1szlengr
szle2t1e2l
szle1te
szle2t1o
1sz2lo1ge
sz1lo
1sz2mok
sz1mo
1sz2nob
sz1no
1s1zo
2sz1obj
2szod2a.
szo1da
2szoda1i
2szodak
2sz1o2dú
3szof
3szo1ká
2sz1ok1ke
2s2z1o2koz
szo1ko
2szok1sö
2s2z1o2laj
szo1la
szo2l1ál
szo1lá
2szol1da
sz1ol1dá
2s2z1ol1dó
2szo2lim
szo1li
2sz1ol1ló
2sz1oltár
szol1tá
2sz1oltás
2s2z1olvad
szol1va
2sz1olvas
2s3zombo2r.
szom1b2
szom1bo
3s2zom1s
szo2nas
szo1na
szo2nár
szo1ná
3szond
2szo1ra
szo2r1ál
szo1rá
2szorm
2szorn
2szors
2szorv
2sz1os1tá
2sz1os1to
2sz1otth
3szov
2sz1ox
1s1zó
2sz1ólm
3szó1ló
2s3zónád
szó1ná
2s3zóná1i
2sz1ó2ni
2sz1ó2nod
szó1no
2sz1ó2rán
szó1rá
2sz1ó2rát
2szó1ri
sz2ó2sík
szó1sí
3szós2z.
s2zós2z
szósza2k
sz2ó1s1za
szó2száll
sz2ó1s1zá
szó2szón
1s1z2ó1s1zó
szó2szü1lő
sz2ó1s1zü
szó1tr
1s1zö
2sz1öb1li
3szö1ge
2s3zöldes
szöl1de
2sz1ö1le
2szö1re
2sz1ö2v.
2sz1ö2ve1i
szö1ve
2sz1öz
1s1ző
sző1a2
szőe2r
sző1e
sző1é2
3sző1lő
sző2ra
sz1ő1si
2szőz
sz1ő1ze
1sz2pí
sz1pl
1sz2ponz
sz1po
szrá1di2
sz1rá
sz3saj
sz2s
s1z1sa
sz3sap
sz3sas
sz3sav
s3zsák
s1z1sá
sz3sán
sz3sár
sz3sás
sz3sát
sz3sáv
sz3seg
s1z1se
s3zsem
s3zsen
sz3sep
sz3ser
s1z3sé
sz3sh
sz3sik
s1z1si
s3zsin
sz3sis
sz3siv
sz3sín
s1z1sí
sz3s2k
sz3sl
sz3sod
s1z1so
sz3sok
s3zsol
s2z3sor
s1z3só
sz3sör
s1z1sö
sz3söv
sz3s2p
sz3s2r
sz3s2t
s3zsúll
s1z1sú
s2zsúl
s1z3sü
s2z3s2z
sz2t1ál1la
sz1tá
sztá2r1a2d
sztá1ra
szt1á2ram
sz2tá2ras
sztá2rat
sz2tá2r1e2
sz2tárf
sz2tárh
1sz2tárj
sz2tárn2y
sztá1ró2
sz2tá1ru
szt1á2ruk
sz2tárv
szte2r1el
sz1te
szte1re
1sz2tere1o
szté2g
sz1té
szt1é1ge
sz2tér1te
sz2t1érv
sz2t1é1té
szt1örök
sz1tö
sztö1rö
sz2t1őrn
sz1tő
1szt2rá1dá
szt1rá
1szt2rájk
sz2tür
sz1tü
1sz2tye
szt2y
1s1zu
szu2b
szu1b1o
szuc1
2szud
sz1udv
2s3zu1go
2sz1ugr
2szuh
2sz1uj
3szu1ká
sz1u2ra
2sz1u2rá
2s2z1u2ta
sz1u1tó
2sz1u2tu
2s3zuz
1s1zú
2szúg
2szúj
sz1úron
szú1ro
2sz1úrr
sz1úr1tó
2szús
2szú1té
2sz1úth
2sz1ú1ti
2sz1útj
2sz1útn
2szú1to
2szútr
2sz1útt
2sz1útv
2szúz
1s1zü
2sz1üd
2szügg
3szüks
2szüld
2sz1ü2led
szü1le
2szülő1se
szü1lő
3szü1ne
3szür1ke
3szürkés
szür1ké
2sz1üs
2sz1üv
1s1zű
3szűk
2szűrödn
szű1rö
1sz2vi2t.
sz1vi
1szvitet
szvi1te
1sz2vitj
1sz2vitn
1szvitt
sz3z2s
2t.
1ta
taa2d
ta1a
taa2l
taát1
ta1á
1taá2ta2
taboz1
ta1bo
ta1b1ra
2t1ab1ro
t2a1b1ró
2t1abs
ta1cl
t1a2da1lé
ta1da
2t1adap
ta2das
1t1a2da1ta
2t1a2datb
2t1a2da1to
ta2da1tu
2t1a2dá
2tadi1o
ta1di
t1adj
t1adl
2t1adm
ta2dod
ta1do
2t1a2dog
2t1a2dot
2ta1dó
t1a2dó1a
ta2dó1á
ta2dób
ta2dód
ta2dóf
ta2dóg
t1a2dóh
t1a2dói2g
tadó1i
t1a2dóik
t1a2dóin
t1a2dóit
ta2dó1í
t1a2dój
t1a2dó1ka
t1a2dó1ké
t1a2dó1ko
t1a2dók1ra
ta2dól
t1a2dó1na
t1a2dó1ná
ta2dóp
t1a2dór
t1a2dó1tó
ta2dó1ü
t1a2dóv
t2a1d1rá
ta1d2re
t2a1d1ro
t1ads
ta2dun
ta1du
t1adv
tae2l
ta1e
tae2r
ta2e1ro
taé2r
ta1é
2ta1fí
ta1f2r
1taf2ta
ta2g1aj
ta1ga
ta2gav
t2aga2z
tag1a1zo
ta2gág
ta1gá
ta2g1ál
ta2gec
ta1ge
ta2g1e2l
ta2g1e2r
ta2g1é2g
ta1gé
2tag1go
2t1aggr
ta2gid
ta1gi
ta2giz
ta2g1os2z
ta1go
ta2g1ott
ta2góc
ta1gó
2t1a2gón
ta2g1ó2r
ta2góv
ta1g2raf
tag1ra
ta1g2ram
tagrá1di2
t2ag1rá
ta2g1u2s
ta1gu
ta2gut
ta2g1ü2
2t1ag2y.
tag2y
ta2gy2a.
ta1gya
ta2gyáb
ta1gyá
ta2gyáh
2t1agyb
2t1agyn
2t1agyr
2t1agyv
ta2i1re
ta1i
tai2rón
tai1ró
tai2z
ta2jé
ta2j1u2s
ta1ju
ta2jús
ta1jú
2t1a2kad
ta1ka
ta2k1ál
ta1ká
taká2r
tak1á1ro
ta2kás
ta2kátk
2t1ak1ce
2t1ak1ci
take2l
ta1ke
tak1e1le
ta2k1é2r.
ta1ké
tak1é2s1ze
t2akés
takés2z
2t1akko1ra
tak1ko
2t1akkord
2t1ak1ku
ta1k2la
ta1k1lu
t1akn2a.
tak1na
ta2k2ó.
ta1kó
ta2k1öb
ta1kö
ta2k1öröm
takö1rö
ta2k1ös
ta1k1rá
tak2re1á
t2ak1re
t2a1k1rí
2t1ak1ro
ta1k2rón
tak1ró
2t1akt2a.
tak1ta
tak2tem
tak1te
2t1akt2i.
tak1ti
2t1aktiv
2t1ak1tí
2t1aktj
taktus1s2
tak1tu
ta2kus
ta1ku
ta1k2va
ta2l1a2da
ta1la
ta2l1a2dá
ta2l1adh
ta2l1adj
ta2l1adn
ta2l1a2do
ta2l1a2dó
ta2l1adt
ta2l1a2du
ta2l1adv
2tala2g1
t1a2la1gu
ta2la1gú
2t1a2lakb
tal1akc
2t1a2la1kí
2t1a2lakj
2t1a2la1ku
2t1alakz
ta2l1a2l
2t1a2lan2y
2t1a2la1pa
2t1a2la1pí
ta2lapk
t1a2lapl
2t1a2laps2z
talap1s2
ta2l1a2r
ta2l1as
tala2te
2t1a2lat1ti
ta2l1a1u
2t1a2lá1á
ta1lá
ta2l1á2g
2t1a2lá1í
ta2l1ál1lo
ta2l1á2rak
talá1ra
talás1s
1ta2l1á1ta
ta2l1átr
tal1ell
ta1le
ta2l1e1lő
ta2l1eng
t2alen
ta2l1e1si
t2ales
t2alé2g
ta1lé
talé2k1e2
ta2l1é2r.
2t1al1ge
ta2l1i2ko
t2alik
ta1li
tal1ikr
tal1im1p2
t2alim
tal1in2a.
tali1na
ta2lip
ta2l1isk
ta2l1í2r
ta1lí
2t1aljas
tal1ja
2t1al1ji
2t1aljz
2t1alkal
tal1ka
2t1alkím
tal1kí
2t1alkoh
tal1ko
2talkot
2taller
tal1le
tal3l2y
ta2l1ol
ta1lo
talo2m1e
ta2l1os2z
ta2l1őr
ta1lő
tal2p1á2ro
tal1pá
tal2pe2l
tal1pe
tal2p1il
tal1pi
tal2pu2s
tal1pu
tal1t2re
ta2lud
ta1lu
2t1a2lulj
ta2l1u2r
ta2l1u2t
ta2lúr
ta1lú
ta2l1ú2t.
ta2lü
ta2lű
2t1alve1o
tal1ve
ta2mal
ta1ma
tam1alm
ta2maz
ta2m1i2d
ta1mi
2t1a2mő
t1am1pa
2t1am1pu
2t1amur
ta1mu
ta2mü
ta2n1aj
ta1na
ta2nal
ta2nan
2t1a2nat
tan1áll
ta1ná
tan1á1lo
tanás1s
tan2del
tan1de
2tandr
ta2n1el
ta1ne
ta2ner
2ta2n1es
ta2n1e2z
ta2n1éj
ta1né
ta2n1é2r.
ta2n1érk
tan1évb
tan1é2vé
tan1é2vi
tan1évm
ta2nid
ta1ni
2ta2nim
tan1ist
tanké2r
tan1ké
tan2ké1re
tan2kés
tan1kó2
tan2k1ó1ra
2tan2n2y
ta2n1os2z
t2anos
ta1no
ta2nód
ta1nó
tan2t2e.
tan1te
tan2t1el
tan2tors2
tan1to
tan1t1rá
ta2nü
ta2nű
2tanyag
tan2y
ta1nya
2tanyád
ta1nyá
2t1a2nyó
tao2l
ta1o
taó2r
ta1ó
2t1a2p2a.
ta1pa
2t1a2pa1i
ta2pa1ké
t1a2pa1sá
2t1a2páb
ta1pá
2t1a2pád
2t1a2pá1é
2t1a2páh
2t1a2pá1i
2t1a2páj
2t1a2pák
2t1a2pám
ta2pá1ra
ta2pá1ró
2t1a2pá1u
2t1a2páv
ta1p2la
ta1p1lé
t1a2p2ó.
ta1pó
2tapp
ta1p2ré
2t1ap1rí
t2a1p2ro
tap2sor
tap1so
taps3s
tap2s1ü2
2tapun
ta1pu
ta2ra1be
ta1ra
ta2ra1i
2t1a2ras
2t1a2rat
2t1a2ráb
ta1rá
tará1di2
2t1a2ráh
2ta2rán
2t1a2rát
2t1a2ráv
2t1ar1bi
2t1ar2c.
2t1arc2h
2t1ar1co
t1ar1cu
2t1ar1cú
ta2r1i2k
ta1ri
ta2ro1má
ta1ro
tar1s2
tar2tab
tar1ta
tar2t1e2l
tar1te
tar2t1em
tar2t1en
tar2t1é2r.
tar1té
tar2tit
tar1ti
tar2told
tar1to
2tartos
2tartr
2tartt
2tasc
ta2sem
ta1se
2tasf
t2a1s2ká
ta1s1lu
ta1s2m
2tas1nő
ta2s1ol
ta1so
2t1as1pi
ta1sp2r
2tas1ru
2tas2s2z
tas3s1zá
tas3szt2
tast2
1t2a1s2ta
2tas1te
t2a1str
2tasv
ta1sy
2t1aszk
tas2z
ta1szl
ta2t1alj
1ta1ta
ta2t1alm
ta2t1aszt
tatas2z
tatá2ra2i.
t2atár
ta1tá
tatá1ra
tatára1i
2t1a2te1i
ta1te
tate2s
2tati1ka
ta1ti
2tati1ká
ta2t1i2n2a.
t2atin
tati1na
ta2t1i2ná
ta2t1ing
t1atlas
tat1la
t1a2to1mo
ta1to
tat1orj
t2ator
ta2t1őr
ta1tő
ta1t2ri
2t1at1ti
tat1u2ra
t2atur
ta1tu
ta2tya
tat2y
ta2tyá
2t1a2uk
ta1u
1tau2ta
t2au2tá
2tau1to
taü2z
ta1ü
2tavan
ta1va
2t1a2va1tá
2t1a2va1tó
ta1wh
ta2zon
ta1zo
1tá
2tá2b.
tá2b1á
tábe2sz1é2l
tá1be
tábes2z
tábe1s1zé
2tá1bé
tá2bi2g
tá1bi
tá2bin
2tábj
2tábk
2tábn
2tábok
tá1bo
2tábon
2tábot
tá2bö
2tábr
t1áb1rá
tá2bü
2táci1u
tá1ci
tá2fa
tá2fá
2tá2g.
tá2g2a.
tá1ga
tá2ga2d
tág1a1da
2t1á2ga1i
tá2gaz
2t1á2gá
2tágb
2t1ágc
2t1á2ge
2t1á2gé
2t1ágf
2tágg
2t1ágh
2t1á2gi
2tágj
2t1ágk
2t1ágm
2tágn
2t1á2go
2tágr
2t1ágt
2t1á2guk
tá1gu
2t1á2gu2n
2t1á2gú
2t1ágv
2t1á2g2y
tá2hí
tá2jal
tá1ja
tá2jaz
tá2j1e2g
tá1je
tá2j1e2l
tá2jí
tá2j1ok
tá1jo
tá2j1ö2
tá2jő
t1á2jul
tá1ju
tá2lab
tá1la
tála2d
tál1a1da
tá2laj
tál1a2lap
t2álal
tála1la
tá2lap
2t1álar
tá2l1ál
tá1lá
tá2l1áth
2t1ál1dá
2t1ál1do
tá1le2
tá2l1eg
tá2l1el
tá2lél
tá1lé
2t1ál2l.
2t1állam
tál1la
2t1állan
2t1állat
2t1állás
tál1lá
2t1állh
2t1ál1lí
2t1állj
2t1álln
2t1ál1lo
2t1álls
2t1állt
2t1ál1lu
t1ál1lú
2t1állv
2t1ál1ma
2t1ál1mi
2t1álmok
tál1mo
2t1á2lomr
tá1lo
tá2lomt
tá2lö
2t1ál1ru
tá2lü
tá2lyab
tál2y
tá1lya
tá2ly1a2c
tá2lyad
tá2ly1a2g2
tá2ly1a2l
tá2ly1a2n
tá2lya1p
tá2ly1at
tá2lya1u
tá2lyátl
tá1lyá
tá2ly1á2z
tá2lyid
tá1lyi
tá2lyir
tá2lyis
táma2s1ze
tá1ma
támas2z
tá2mí
tá2mos
tá1mo
2t1á2mu
tán1alm
tá1na
tá2nár
tá1ná
tánc3c
tán2c1e
tán2céh
tán1cé
tán2cél
tán2cén
tánckés2z1
tánc1ké
tánc2s2
tán2c1sá
tán2csor
tán1c1so
tán1d2
tá2n1e2
tá2ní
tá2n1ó2
tá2nö
2tán1pó
tán2s1e
tá2nü
tá2nű
tá2nyal
tán2y
tá1nya
tá2ny1as
tá2nye
2tánz
tá2p1a2
tá1pá2
tá2pál
tá2p1ár
tá2pát
tá2p1e2
tá2p1il
tá1pi
tá2p1in
tá2p1oll
tá1po
tá2p1os2z
tá2pő
tá2pü
2t1á2ra1dá
tá1ra
tár1a2dot
tára1do
tá2ra1dó
tá2r1a2g
tár1ajt
tá2r1a2l
2t1á2ram2a.
tára1ma
2t1á2ra1má
2t1á2ra1mi
2t1á2raml
2t1á2ramok
tára1mo
2t1á2ramol
2t1á2r2amot
2t1á2ramt
2t1á2ra1mu
2t1á2ra1mú
tára2n
tá2r1an2y
tá2rap
tá2r1as2z
tá2r1att
tá2r1a1u
tá2r1av
tá2rá2g
tá1rá
tá2r1ál
tá2r1á2s2z
tá2r1átl
2t1árboc
tár1bo
tá2r1e2
tá2réd
tá1ré
tá2rés
tár2gyö
tárg2y
tá2r1i2k
tá1ri
tá2r1i2p
tár1isk
tá2r1ism
tá2rí
tár1k2
2t1árkád
tár1ká
2t1árká1na
2tárkár
2t1ár1nyé
tárn2y
tá2r1okm
tá1ro
tá2r1os2z
tá2róc
tá1ró
tár1ó1rá
tá2rö
tá2rő
tár2s1alt
tár1sa
2tártás
tár1tá
tárt1öl1tő
tár1tö
tár1t1ro
2t1á2r2u.
tá1ru
2t1á2ru1a
2t1á2ru1b2
2t1á2ruc
2t1á2rug
2t1á2ruh
2t1á2ru1i
2t1á2ruj
2t1á2ru1na
2t1á2rus
2t1áru2t.
tá2rut
tár1u1ta
2t1á2ru1ü
2t1á2ruv
2t1á2rú1é
tá1rú
tá2rúj
2t1á2rúk
tá2rús
tá2rü
tá2rű
tá2s1a2d
tá1sa
tá2s1aj
tá2sal
tá2s1a2r
tá2saz
tás1á2ga
tá1sá
tá2s1á2ra1i
tásá1ra
tá2s1á2rá
tá2s1á2ré
tá2s1árh
tá2s1árn
tá2s1á2ro
tá2s1árr
tá2s1árt2
tá2sás
2t1ásá1so
tá2s1á2to
tá2s1e2
tá2sis
tá1si
tá2sodv
tá1so
tá2s1ol
tá2sor
tá2só
2t1á2sók
tá2s1ó2r
tá2sö
tá2ső
tás3s
tást2
tás1tr
tá2su2t
tá1su
tá2s1ü2
tá2sű
t1ásván
tás1vá
tá2sz1ak
tás2z
tá1s1za
tá2szal
tás3zav
tá2s3zá
tá2s1ze
tás3zen
2tá1s1zi
2tá1s1zo
tá2szos
tá2s3zó
2tászt2
2t1átad
tá1ta
2t1á2t1ál
tá1tá
2t1átc
2t1átd
2t1á2t1e2
2t1á2t1é
2t1át1fo
2t1átg
2t1át1he
2t1át1hi
tá2t1ir
tá1ti
2t1á2tí
2t1át1je
2t1át1kö
2t1átlag
tát1la
2t1átm
2t1á2t1ol
tá1to
2t1á2t1ö
2t1á2tő
2t1átp
2t1át1re
2t1át1ru
2t1áts2z
2t1át1te
2t1át3t2é
2t1át1tö
2t1át1tű
2t1á2t1u2t
tá1tu
2t1á2tü
2t1át1vi
2t1át1vo
tá2v1a2d
tá1va
tá2vak
táva2l
tá2v1a2n
tá2vas
tá2vaz
tá2v1ál
tá1vá
tá2v1e2
tá2véd
tá1vé
tá2v1érz
tá2v1és
tá2vin
tá1vi
tá2vis
tá2ví
tá2v1or
tá1vo
2t1á2vó
tá2vö
tá2vő
tá2vü
tá2zsal
táz2s
tá1z1sa
tá2zsál
tá1z1sá
tá2z1só
tázs1p
tbal2le2
t1ba
tbe1á2
t1be
tb2la
tb2le
tb2li
tb2lo
tb2lú
tb2ra
tb2re
t1b2ri
tb2ro
tb2ró
tb2ru
tca1k
t1ca
tca1s
tca1t2
tc2lu
tc2re
tcsap1á2g
tc2s
t1c1sa
tcsa1pá
tdíja2d
t1dí
tdí1ja
tdí2j1a1da
td2ra
td2rá
td2re
td2ro
td2ró
td2ru
1te
te2a1a
te1a
te2a1á
te2ab
te2ac
te2a3d
te2a1e
te2a1é
te2ag
te2ah
tea1i2
te2aiv
te2a1í
te2aj
te2a1ku
te2alap
tea1la
te2aláz
tea1lá
te2a1li
te2a1na
te2a1o
te2a1ö
te2a1ő
te2a1pa
te2a1pá
teas2
te2a1sp
te2a1s1za
teas2z
te2a1s1zá
te2a1s1zo
tea1t1ró
te2a1ú
te2a1ü
te2av
te2az
te2ber
te1be
te2béd
te1bé
2t1e2c1hó
tec2h
te2c1sá
tec2s
te2dit
te1di
te2dí
2t1e2d2z
2t1eff
te1f2r
te2gan
te1ga
te2g1a2r
tega2z
teg1a1zo
te2gá
teg1ál
teg1ár
te2g1eg
te1ge
teg1e2lem
tege1le
te2g1ell
te2g1elr
te2ge1ne
2t1eger2e.
tege1re
te2g1ered
te2g1él
te1gé
te2g1é2p
te2gés2z1
teg3g
te2gid
te1gi
te2gis
te2giz
te2g1on
te1go
te2g1ö
te2gú
te2g1ü2g
te1gü
tegü2l
te2g1ü1lé
te2g1ü1lő
2t1eg2y.
teg2y
2t1e2gyes
te1gye
t1e2gyez
t1egyén
te1gyé
2t1egyh
2t1egyl
2t1egys
2t1e2gyüt
te1gyü
tei2g
te1i
tein1s
te2j1a
teje2g
te1je
te2j1ell
te2j1elv
te2j1er
te2jin
te1ji
te2jí
te2jo
te2j1ó
te2j1ös
te1jö
te2jő
te2j1u2
te2jú
te2k1ag
te1ka
te2k1ál
te1ká
te2k1el
te1ke
tek1éret
te1ké
teké1re
te2k1i2p
te1ki
te2kí
te1k1lu
te2k1ok
te1ko
te2k1ös
te1kö
te2k1und
te1ku
te2k1ú2t
te1kú
te2lab
te1la
te2lag
te2l1a2j
te2l1an
te2lap
te2la2r
te2las
te2lav
te2l1á2g
te1lá
te2lál
telá2r
te2l1át
2tel1bü
teleí3rá
te1le
tele1í
2telej
tel1ejt
2t1elektr
tel1e2len
tele1le
te2l1elk
te2l1ell
te2lem1ba
t1e2lem2e.
tele1me
2t1e2leme1i
2t1e2lemek
te2lemes
te2lemén
tele1mé
2t1e2le1mű
tel1esés
tele1sé
te2l1e2ső
1te2l1es1te
tel1es1ti
tele2t1é2r.
tele1té
t1e2lég
te1lé
tel1érét
telé1ré
1te2l1é1te
t1elfo1ga
tel1fo
telié2h
te1li
teli1é
te2l1i1mi
te2lind
te2l1inf
te2l1ing
2t1e2l2ix
te2lír
te1lí
tel2l1eg
tel1le
2t1ellenf
2t1elle1nő
2t1ellenz
2t1ellniv
tell1ni
2telmé1le
t1elmél
tel1mé
te2l1ó2
te2l1öl
te1lö
2telőa2dá
te1lő
telő1a
2t1e2lő1í
2t1e2lőnn
2t1e2lőn2y
te2lőtt
2t1e2lő1tu
te2lővét
telő1vé
tel1őz2i.
telő1zi
tel1p2
2t1el1sa
2t1el1ső
2t1elte1lé
1tel1te
2t1eltet2t.
2t1elté1ré
tel1té
te2lú
telü2k2é.
te1lü
telü1ké
2t1el2v.
2t1elvb
2t1elve1i
tel1ve
2t1elvek
2t1elvet
2t1elvév
tel1vé
2t1elvh
2t1elv2i.
tel1vi
2t1elvil
2t1elvk
2t1elvn
2t1elvr
2t1elvt
2t1el1vü
2t1el1vű
2t1elvv
te2ma2p
te1ma
te2m1as
2t1embl
2t1embr
te2m1e2g
te1me
tem1e2leg
teme1le
2t1eme1lé
2t1e2melk
2te2me1lő
te2melv
te2m1él
te1mé
te2m1é2r.
te2m1é2r2ő.
temé1rő
tem1ér1té
2t1e2més
1te2m1é1te
te2m1étk
te2mid
te1mi
te2migr
temi2g
tem1ill
te2mi2m
tem1ing
te2m1int
te2móc
te1mó
te2m1ó2r
te2m1ő2
2tem1pá
2temp1li
2t1e2mul
te1mu
te2mus
te2mut
temü2l
te1mü
te2m1ü1lé
te2nad
te1na
te2n1a2g
te2nal
te2n1a2r
te2n1as
te2nat
te2na1u
te2n1á2t
te1ná
ten1d2h
tene2g
te1ne
ten1eg2y
te2n1el
te2ner
2t1e2nerg
te2n1es2z
t2enes
te2n1ékt
te1né
te2n1é2v.
te2n1é1vi
2t1enged
ten1ge
te2n1i2p
te1ni
te2n1ol
te1no
te2n1ó2
te2nö
ten2tin
ten1ti
ten2t1í2v
ten1tí
ten1t1ri
te2n1u2
te2nú
te2n1üg
te1nü
te2nünn
2t1enyh
ten2y
t1enyv
te2oc
te1o
te2of
teo2s
2t1e2piz
te1pi
2t1e2pos
te1po
2tepp
tep2p1é2k
tep1pé
ter1abl
te1ra
ter1a2cé
te2r1a1da
ter1a1ka
te2r1a2n
te2r1a2r
te2ra1u
ter1á2c2s
te1rá
te2ráf
te2r1áll
ter1álm
te2r1á2ri
ter1á1ta
ter2c2h
ter1d2
2t1er1dő
2t1e2redm
te1re
te2r1e2d2z
ter1e1gé
ter1eg2y
2t1e2rej
te2r1e2k2e.
tere1ke
2t1e2rekl
te2r1elm
tere1me2
te2r1ent
2tere1o
tere2pa
tere2p1e2l
tere1pe
tere2p1ü2lé
tere1pü
te2r1er
te2r1e2ső
1te2r1es1te
te2r1es1té
te2re1ta
te2r1e1ti
te2retn
te2rég
te1ré
te2r1é2j
ter1é2k1a2
te2ré2l
ter1é1le
ter1élv
ter1g2
ter1i1ko
te1ri
2teri1li
ter1il1la
teri2na
te2r1i2p
te2r1ism
ter1ist
ter1izm
ter1k2
t1erkölc
ter1kö
termés1s
ter1mé
te2r1old
te1ro
te2ror
te2r1ox
te2r1ó2r
te1ró
te2rö2k
te1rö
te2r1ön
te2rő
2t1er2ő.
2t1erőb
2t1erőf
2t1erős
t1erőt
t1erőv
ter1s2
ters2z2
tert2
ter1tr
te2rur
te1ru
te2r1ut
te2r1út
te1rú
te2r1üg
te1rü
te2r1üld
ter2vaj
ter1va
ter2va2n
te2sar
te1sa
te2sár
te1sá
te2sel
te1se
tes1ell
2t1ese1mé
2t1e2setb
2t1e2set2e.
1tese1te
2t1e2sete1i
2t1e2seten
2t1e2setet
2t1e2se1té
2t1e2seth
2t1e2se1ti
2t1e2setn
2t1e2setr
2t1e2sett
te2sél
te1sé
2t1e2séll
2t1e2sél2y
2t1e2sés
te2s1int
te1si
tesí2r
te1sí
te2sírn
te2s1í1ró
te2síz
2t1es1kü
t1es1ni
te2sot
te1so
2t1e2ső
tesp2
2t1esszen
tes2s2z
tes1s1ze
tes2tak
tes1ta
tes2t1áll
tes1tá
testá2r
tes2tá1ra
tes2t1elk
1tes1te
tes2t1ell
tes2t1er
2t1estéj
tes1té
2testék
tes2t1ékn
tes2t1éks
2t1estém
tes2tism
tes1ti
tes2t1o
tes1tő2
tes2t1ő1re
tes2t1ő1ré
tes2tur
tes1tu
te2s1ú
te2s1ü2v
te1sü
2teszet
tes2z
te1s1ze
2t1esz1mé
tesz2t1a2
teszte2r
1tesz1te
tesz2t1e1re
1tesz2t1é2te
tesz1té
tesz2tor
tesz1to
te2t1a2k
te1ta
te2t1a2l
te2ta1na
te2t1a2p
te2tág
te1tá
te2t1ál
tetá2r
te2t1á1ra
te2tát
te2t1e2g
1te1te
te2t1ell
tet1e2lő
tet1elr
te2t1elv
te2te1ne
tete2r
te2t1e1ré
te2t1es2z
te2t1éj
te1té
tet1ékk
te2t1é2l
te2t1é1ri
te2t1érv
te2ti2d
te1ti
2t1e2ti1ka
2t1e2ti1ká
te2tim
te2t1int
tetkés2z1
tet1ké
te2t1olt
te1to
te2t1ot
te2tór
te1tó
te2tur
te1tu
te2t1üz
te1tü
2t1e2tűd
te1tű
te2t1ű2z
tevés3s
te1vé
te2vol
te1vo
te2w1a
2t1exp
2t1e2zer
te1ze
tező1a2
te1ző
t1ezred
tez1re
1té
2t1ébr
té2cő
té2des
té1de
2té2g.
2tégb
2téget
té1ge
2té1gé
té2gép
té2gés
2tégg
2tégj
2t1égk
tég1la1
2tégn
2t1é2gö
2t1é2gő
2tégr
2tégt
té1gü2
té2g1ül
2t1é2h.
2t1é2hek
té1he
2t1é2hen
2t1é2hes
2t1é2het
2t1é2hé
2t1éhs
2t1é2jen
té1je
té2jes
2t1éjr
2t1éjs
2t1éjt
té2kab
té1ka
té2kad
ték1a1da
té2k1a1ka
ték1alk
té2kam
té2k1a2n
té2k1ar
téka2t
té2k1att
té2kaz
té2k1ág
té1ká
té2k1e2c
té1ke
té2ke1dé
té2k1e2g
té2k1e2kéh
téke1ké
té2k1e2lő1á
téke1lő
té2k1elr
té2k1er
té2k1es2z
té2k1e1ti
té2k1é2k
té1ké
té2k1é2l
té2kép
té2k1é2r.
t2é2k1és
tékfé1lé2
ték1fé
tékfé2l1év
té2ki2d
té1ki
té2kik
té2kim
té2ki1rá
té2k1i2s
té2kí2
té2k1o2r
té1ko
té2k1os2z
té1kó2
té2kór
té2k1ö2
té2kő
té2k1u2t
té1ku
tékü2l
té1kü
té1la2
té2lak
té2l1an
té2lap
té2las
t2é1lá
té2láb
té2lá2l
té2lár
té2l1á2t
té2le1i
té1le
té2le2l
té1l1e1le
tél1ell
tél1e1lő
tél1elv
té2l1e2r
té2les
tél1est
té2lez
tél1ékb
té1lé
tél1é1ké
té2lí
té2ló
té2l1ö2
télőkés2z1
té1lő
télő1ké
té1lu2
té2lut
2té2lű
té2lyeg
tél2y
té1lye
télyigaz1
té1lyi
télyi2g
té2ly1i2ga
téma1p
té1ma
té2mé
té2mil
té1mi
té1na2
té2n1an
2t1é2nekb
té1ne
2t1é2nek2e.
téne1ke
2t1é2nekek
2t1é2ne1ké
2t1é2nekl
té2n1in
té1ni
té2n1ö2
té2ny1e2g
tén2y
té1nye
té2ny1e2l
té2nyé2k
té1nyé
tényigaz1
tényi2g
té1nyi
té2ny1i2ga
té2nyim
té2nyo
té2nyö2
tépés3s
té1pé
2t1é2pí
té1p2la
2t1é2pül
té1pü
té2rab
té1ra
té2raj
tér1akt
té2r1a2n
té2rar
té2ras
té2rav
té2r1á2r
té1rá
2térdekb
tér1de
2t1érdekl
2térdekn
2t1érde1kü
2t1érde1kű
tér2d1e2mel
térde1me
tér2d1í2j
tér1dí
té2reg
té1re
té2r1e2l
té2rem
té2r1eml
té2r1eng
tére2n
té2r1e2ső
té2r1es2s2z
té2r1es2z
2t1é2rett
térés1s
té1ré
té2r1és2z
tér1é1te
téri2d
té1ri
té2r1i1de
té2ril
té2rip
té2ris
2térkez
tér1ke
2t1ér1mü
té2r1os
té1ro
té2rö
2t1értes
tér1te
2t1érték
tér1té
2tért2ő.
tér1tő
2t1értők
tér1t2r
2t1értv
té2r1u2
2t1ér2v.
2t1érve1i
tér1ve
2térvek
2t1érvén2y
tér1vé
té1sa2
té2sab
té2sag
té2s1aj
té2sak
té2s1al
té2san
té2sap
té2s1as
té2s1az
té2s1á2
té2s1á1t1a2
té2s1eg
té1se
té2s1e2l
té2s1e2r
té2sés
té1sé
tés1i1ko
té1si
t2é2s1or
té1so
té2só
té2s1ő2
té2su
tésü2l
té1sü
té2s1ü1lé
tés1ü1lő
té2s1üt
tés3zav
tés2z
té1s1za
tész1á2l
t2é1s1zá
tés3zár
té2szeg
té1s1ze
té2sz1emb
tés3ze1ne
té2s2zes2z
té2sz1ék
té1s1zé
tés3z1si
tész2s
té2t1a2l
té1ta
tét1c1sa2
tétc2s
té2t1e2g
té1te
tét1e2lemt
téte1le
tét1elkés
tétel1ké
té2t1el1ve
2t1éte1ri
té2t1er1s2
té2t1e2v
té2t1é2te
té1té
té2tok
té1to
té2t1o2l
té2t1os
té1tó2
té2tón
té2t1ór
2t1é2v.
2t1é2vad
té1va
2t1évb
2t1évc
2t1é2vedb
té1ve
2t1é2ve1i
2t1é2vek
2t1é2vem
2téve2s.
tévé1s2z2
té1vé
2t1évf
2tévh
2t1é2vi
2tévk
2t1évn
2t1évr
2t1évs
2tévt
té2vú
2t1é1vü
té2vü1kö
té2vün
2t1é2vű
2t1évv
té2z2s
tfa2l1aj
t1fa
tfa1la
tfa2la2n
tfa2le
tfa2lom
tfa1lo
tf2jo
tf2la
tf2le
tf2li
tf2lo
tf2ló
tf2ra
tf2rá
tf2re
tf2ri
tf2rí
tf2ro
tf2rö
tf2ru
tgá2zak
t1gá
tgá1za
tgá2zár
tgá1zá
tgá2zé
tge2n1el
t1ge
tge1ne
tge2né
tgen1s
tg2lo
tg2ló
tg2ne
tg2ra
tg2rá
tg2ri
tg2ró
tg2ru
tg2rü
tha2de
t1ha
1t2hago1re
tha1go
t2ha2r.
1t2ha1u
1t2h2e.
t1he
the2i2d1
the1i
1t2he2idp
1t2heus2z
the1u
t2ho1li
t1ho
thon1n
tho1n2y
1t2hos2z
th1sc
1thy
1ti
ti2a1a
ti1a
ti2a1e
ti2a1é
ti2af
ti2ah
ti2aj
tiakés2z1
ti2akés
tia1ké
ti2a1la
ti2am
ti2a1p
ti2a1ta
ti2atl
ti2a1ü
ti2av
ti2c1hi
tic2h
ti2de1a
ti1de
2t1i2de1á
2t1i2deg
ti2de1i
2t1i2dej
2t1i2de1o
ti2dén
ti1dé
2t1i2déz
ti2di1o
ti1di
2t1i2dom
ti1do
2t1i2dő
tie2le
ti1e
tie2n
tié2b
ti1é
ti1fl
2t1i2ga
2t1i2gá
2t1i2g2e.
ti1ge
2t1i2gék
ti1gé
ti1g2lo
ti1g2ra
2t1ihl
ti3ki
ti1k1le
ti1k1lu
ti2konb
ti1ko
ti2ko1no
ti2konr
tiko1s2z2
ti1k2ri
ti2lac
ti1la
ti2lad
ti2la2n
ti2l1ág
ti1lá
tile2g
ti1le
til1eg2y
ti2lex
ti2lim
ti1li
til1ing
ti2l1i2p
ti2lir
til1isk
2t1illa1to
til1la
2t1il1lú
ti2l1ö2
2t1i2l2y
ti2mak
ti1ma
ti2máb
ti1má
2timá1dó
t1i2mád
ti2máj
ti2már
ti2máz
ti2mes
ti1me
2t1im1mu
2t1imre1i
tim1re
2t1i2naka2t.
ti1na
tina1ka
2t1i2nakk
ti2n1akt
ti2n1a2n
tin1a1rá
tina1t2
ti2n1a1u
ti2n1áll
ti1ná
tin1árt2
2t1in1dá
2t1in1de
2t1in1dí
2t1in1du
t2i2n2e.
ti1ne
ti2neg
ti2n1e1ké
ti2nem
2t1infar
tin1fa
2t1infl
2t1infr
tin2gal
tin1ga
tin2gas
tin2g1ár
tin1gá
tin2g1e2l
tin1ge
2t1inge1ni
1tin2g1e1ti
tin2gos
tin1go
tin2g1ó2
2t1i2nic
ti1ni
tini2g
tin1i1ga
ti2n1i2m
tini1s2z2
2t1in1ku
2t1inkv
tin3n
tin1ó2rá
ti1nó
ti2nö
2t1insp
2t1integ
tin1te
2t1intéz
tin1té
ti2n1ut
ti1nu
ti2nű
ti2ol
ti1o
ti2onb
ti2onh
ti2o1ni
ti2onj
2t1i2onn
2t1i2o1no
2t1i2onr
2t1i2ont
tio2x
ti2pad
ti1pa
ti1p2la
tip2pin
tip1pi
tip2po
2t1i2rat
ti1ra
2t1i2rán
ti1rá
2t1irg
ti2rig
ti1ri
2t1irk
2t1i2rod
ti1ro
2tirol
ti2rom
ti2rón
ti1ró
2t1irr
tir2s1
2t1irt
tis2as
ti1sa
ti2s1a2u
ti2s1el
ti1se
2t1i2s2i.
ti1si
ti2sim
ti2sin
ti2s1i2r
2t1i2sis
2t1is1ko
ti2sor
ti1so
2t1istv
tisz2ti1na
tis2z
tisz1ti
ti2t1i2o
ti1ti
ti1t2ri
ti1t2ro
tiu2mé
ti1u
tiu2m1i2
2tivad
ti1va
2t1i2var
2t1i2ván
ti1vá
2t1i2vó
ti2xa
tize2n1
ti1ze
ti2z2é.
ti1zé
2t1i2zéj
2t1i2zék
ti2zér
ti2zét
ti2zév
tizo2m
ti1zo
1tí
tí2gé
tí2ja
tí2já
tí2jú
tí2la
tí2l1ó2
2t1í2ni1o
tí1ni
2t1í2no
2t1ínr
2t1íns
2t1í2n2y
tí2ra2n
tí1ra
tí2rar
tí2r1ál
tí1rá
2t1í2rás
tí2r1e
2t1í2ró
tí2rő
tí2rü
títés3s
tí1té
títő1a2
tí1tő
títő1e2
tí2v1e2c
tí1ve
2t1í2ve1i
tí2vel
tí2ve2r
2t1í2vé1i
tí1vé
tí1vi2
tí2vi2k
tí2vir
tí2viv
tí2v1ó2
tí2vö
tí2vő
tív1s
tí2za
tí2zá
tí2zel
tí1ze
tíz1e1le
tí2z1emb
tí1zi2
tí2zik
tí2z1is
tí2zí
2tízl
tí2z1o
tí2zö
tí2z1sá
tíz2s
tí2zu
tí2zül
tí1zü
tíz1ü1lé
2t1í2zű
tje2gy1á2
t1je
tjeg2y
tjó2t1
t1jó
tka1pr
t1ka
tka2ró2r
tka1ró
tken1de2
t1ke
tké2p1e2kéh
t1ké
tké1pe
tképe1ké
tkia2l
t1ki
tki1a
tki1á2
tki1e2
tki1é2
tk2la
tk2li
tk2lí
tk2ló
tk2lu
tkö2z1él
t1kö
tkö1zé
tkőé2h
t1kő
tkő1é
tk2ra
tk2rá
t1k2reác
tk1re
tkre1á
t1k2ré
t1k2ri
tk2rí
t1k2ro
t1k2ró
tk2ru
tk2va
tk2vi
tk2vó
tla2c3
t1la
tla2g1a2d
tla1ga
tla2g1ar
tla2gas
tla2gál
tla1gá
tla2g1e
tlag3g
tla2g1ó
tla2n1e2
tlan1ká2
tla2nó
tla2pár
tla1pá
tle1í2
t1le
tlen3n
tle2t1a2n
tle1ta
tle2tas
t1le2te1le
tle1te
tle2t1elv
tle2t1é2r.
tle1té
tle2tos
tle1to
tle2tő
tle1ü2
tlé2kal
t1lé
tlé1ka
tlé2kem
tlé1ke
tlé3pe
tlés3s
tlé1t1rá
tló1dr
t1ló
tma2gál
t1ma
tma1gá
tma1k
tmá2nyir
t1má
tmán2y
tmá1nyi
tme2g1e2
t1me
tme2g1é
tme3gif
tme2gi
tmu2sí
t1mu
tmus3s2
tmu2s2z
tna2gya
t1na
tnag2y
tná2d1
t1ná
tne2k1el
t1ne
tne1ke
tne2küld
tne1kü
tne2m1e2r
tne1me
tne2r1a
tne2r1á
tne2s1z1a
tnes2z
tne2s1zi
tne2s1z1ó2
tné1v1a2
t1né
tnómen1
t1nó
tnó1me
tnómenk2
tnö2k1öl
t1nö
tnö1kö
tnőé2n
t1nő
tnő1é
1to
2t1obj
2t1obl
2t1obs
to1c1ki
to2da1a
to1da
to2das2
to2dí
2t1o2dú
2t1odv
2t1off
togás1s
to1gá
t2o1g2rá2f.
tog1rá
t2o1g2ráff
to2il
to1i
to2k1a1ka
to1ka
to2k1a2n
to2k1ap
tok1a1ri
to2k1ál
to1ká
tok1á2rok
toká1ro
to2k1átl
to2k1átm
to2k1átr
to2k1áts
to2k1átt
to2k1átv
to2ker
to1ke
to2k1é2l
to1ké
to2ké2p
to2kid
to1ki
to2k1im
to2k1in
to2k1ip
to2k1iv
to2kí
t1o2ko1zá
to1ko
to2k1ö
to2kő
to1k2ro
tok1s
toks2z2
to2k1ur
to1ku
to2kúr
to1kú
to2kü
to2kű
to2l1a2d2
to1la
2t1o2laj
to2l1akt2
tola2n
to2l1an2y
to2l1a2r
tol1atom
tola1to
tol1ábr
to1lá
2t1olda1lo
tol1da
2t1oldalt2
2t1o2lim
to1li
tol1k2
tol2l1árb
tol1lá
tol2l1á2ré
tol2l1árh
tol2l1árr
tol2leg
tol1le
tol2l1in
tol1li
tol2l1í2
to2l1o2r
to1lo
2t1oltás
tol1tá
to2l1ut
to1lu
2t1olvas
tol1va
to2m1ag
to1ma
to2m1ak
to2m1a2l
to2m1a2n
to2m1ap
to2m1ál
to1má
to2m1á2r
tom1b
2tom1be
to2m1e2l
to1me
to2men
tom1e2rő
to2m1e2s
to2mí
to2m1os2z
to1mo
to2m1ó2
to2mö
tom1p2
tom1s
2tom1tö
2to1mú
to2mü
to2mű
to2nalm
to1na
tona1t2
tona1u2
ton1áll
to1ná
ton1álm
to2n1á2z
to2n1e2l
to1ne
to2n1e2n
to2ner
ton1gr
to2nil
to1ni
ton1k2
to2nol
to1no
to2n1or
to2n1os2z
to2nóc
to1nó
to2nór
to2nö
to2nő
ton1s2
tont2
ton1tr
to2n1ú2t
to1nú
to2nü
to2nű
to2nyal
ton2y
to1nya
to2nye
to2p1at
to1pa
2topc
2to2pe
to2pik
to1pi
to2pi2z
2top2p.
2top1po
2toppr
top1t2
to2pü
to2r1a2d
to1ra
to2r1ag
to2r1aj
to2r1a1ka
to2r1akn
to2r1a1ko
to2r1a2l
tora2n
to2r1an2y
to2r1a2p
to2r1ar
tor1ass
to2rat
to2r1a1u
to2r1av
tor1á2c2s
to1rá
to2r1áll
to2r1álm
tor1á1té
to2r1átl
to2r1á2z
2t1or1cá
tord2
tor1dr
to1re2
to2rec
t2o2re2k
to2r1e2l
to2re2n
to2r1er
tor1f2
tor1g2
2t1organ
tor1ga
2t1o2ri1e
to1ri
to2r1ill
to2r1int
to2r1isk
tor1k2
2tor1nó
to2rop
to1ro
to2ros2z
tor1oszt
to2r1ov
to1ró2
to2rón
to2r1ór
to2rö
to2rő
2t1or2r.
2t1orráv
tor1rá
2t1or1ri
tors2
tor1sc
tor1sk
2t1or1só
tor1sp
tor1st
tort2
tor1tr
to2rü
3t2or1vá
2t1orvos
tor1vo
2t1orvv
to1ry
to2s1as
to1sa
to2s1e
to2sik
to1si
to2s1in
to2sis
tos3s1ze
tos2s2z
1t1os1to
2t1ost2y
to2sü
to2szal
tos2z
to1s1za
to2sza2n
to2s1z1e
tosz1k
2t1oszlo2p.
tosz1lo
tosz1tr
3t2o2t.
2t1otthon
tott1ho
to2ut
to1u
to1va1
2t1o2ve
2t1o2vi
2t1o2vu
2t1oxid
to1xi
2t1o2xig
to1yo
toza2t1al
to1za
toza1ta
1tó
tóá2g2y
tó1á
tóá2r
tóát1
tóá2ta2
tó1bl
tó1b1ra
tó1b1ró
tó1cl
tócsa2p1á2g
t2ó1c1sa
tóc2s
tócsa1pá
tó2da2j
tó1da
tó2d1a2n
tó2d1e1sé
tó1de
tó1d1ro
tó2dúr
tó1dú
tó1fl
tó1gl
tó1g2r
tói2g
tó1i
tóí2v
tó1í
tóká2rok
tó1ká
tóká1ro
tó1k2l
tó1k1re
tó1k1ré
tó1k1ro
tónus3s2
tó1nu
tóo2k
tó1o
tóó2r
tó1ó
tóp1ass
tó1pa
tó2p1e2l
tó1pe
tóp1int
tó1pi
tó1p1ro
tó2ras
tó1ra
tó2ráb
tó1rá
tó2ráh
2t1ó2rá1i
tó2rár
2t1ó2rás
tó2rát
t1ó2ráv
tó2se2p
tó1se
tós1éták
t2ósét
tó1sé
tósé1tá
tós1é2tát
tó2sír
tó1sí
tó1s2kan
tós1ka
tó1skál
tós1ká
tó2s1ol
tó1so
tó1s2por1to
tós1po
tó1s2portt
tó1s2rác
tós1rá
t2ó2s1ü2l
tó1sü
tó2s3ze1ne
t2ószen
tós2z
tó1s1ze
tó1sz2f
t2ó1szk
tó2taj
tó1ta
t2ó2tág
tó1tá
tó2tom
tó1to
tó1t2rá
tó1t2re
tó1t1ro
2t1ó2vod
tó1vo
2t1ó2vó
1tö
töb2bev
töb1be
töb2b1o
2t1öbl
2tödé1é
tö1dé
tö2dém
tö2dí
2töd1né
tö2do
2tödöt
tö1dö
2tö1dü
tö2ka
tö2ká
tö2k1e2v
tö1ke
tö2k1é2r.
tö1ké
tö2k1érd
tö2ki2d
tö1ki
tö2kí
2tö2ko
2t1ö2kör
tö1kö
tökös3s
tö2k1ő
2t1ökrös
tök1rö
tö2ku
2t1ölb
2t1öl1ci
töl2gya
tölg2y
töl2t1á2
t1öltön
1töl1tö
2t1öltöz
töm2b1a
töm1be2
töm2bel
töm2b1o
t1öm1le
2t1öm1lé
t1öm1li
tö2möl
tö1mö
tö2na
tö2ná
tön1d2
tö2ne2n
tö1ne
tö2n1í
tön2kár
tön1ká
tönkés2z1
tön1ké
tö2no
tö2n1ő
tön1s
tö2nu
tö2nú
2t1ö2reg
tö1re
törés3s
tö1ré
2töröks
tö1rö
tör2t1a2
tör2teg
tör1te
tör2t1e2lem
törte1le
tör2ter
tör2térn
tör1té
tör2t1és
tör2t1o2
tör2t1öl
1tör1tö
tör2tös
tör2z1sa
törz4s
tör2zs1ö2l
tör1z1sö
2t1ösv
2t1ös2z
2t1ötl
töt2t1á
2t1ötv
2t1ö2v.
2t1övb
2t1övh
2t1ö2vi1e
tö1vi
2tövig
2t1övj
2t1övk
2t1övn
2t1övr
2t1övv
2t1ö2zön
tö1zö
töző1e2
tö1ző
1tő
tőa2c
tő1a
tőa2l
tőá2g
tő1á
tő1bl
tő1br
tőe2ké
tő1e
tőe2r
tőé2l
tő1é
tő1fl
tő1gr
tőí2t
tő1í
tőí2v
tő1kl
tő1kv
tő1pl
tő1pr
tő1ps
tő2rag
tő1ra
t1ő2r1a2l
2t1ő2r1a2n
tőr1eg2y
tő1re
2t1őrez
2t1ő2rék
tő1ré
2t1ő2ré1ü
tő2rin
tő1ri
tő2r1is
2t1ő2rí
2t1őr1já
2t1őrjel
tőr1je
2t1őr1jö
2tőr1ka
2tőrl
2tő2r1öl
tő1rö
2tőrp
2t1őr1ti
2t1őr1t2r
2t1őrz
tő2s1a2l
tő1sa
2t1ősn2y
tő1s1pe
tő1s2pi
tő1sp2r
tő1s1rá
tő1s1ta
tő1s2tá
tő1s1té
tő1st2r
2tő1sű
tő2s2ű.
tős2z2
tő1szt2
tő1tr
tőü2l
tő1ü
tő1zá2
tőzár1
tpen3n
t1pe
tp2fe
tp2la
tp2lá
tp2lé
tp2lu
tp2ra
tp2re
tp2ré
tp2ri
tp2ro
tp2ró
tp2ru
tp2s2z
tpu2tin
t1pu
tpu1ti
t2ra1fó
t1ra
1t2ra1gé
1tra1gi
tra1k2l
tra1k1ro
tran2d1a2
tran2dá
trans1s
tran2sz1a1i
trans2z
tran1s1za
tran2sz1o2m
tran1s1zo
tra1s2p
trat2
t2ra1ti
tra1tr
1t2rá1gya
t1rá
trág2y
1t2rágyáb
trá1gyá
1t2rágyá1é
1t2rágyáh
1t2rágyá1i
1t2rágyáj
1t2rágyák
1t2rágyán
1t2rágyár
1t2rágyás
1t2rágyát
1t2rágyáv
t2rájk
trán2s2z
trá2nyal
trán2y
trá1nya
trá2t1ér
trá1té
tre2c1sa
t1re
trec2s
tre2c1so
t2re1go
tren2da
tren2d1ő2
1t2re1ní
tré1é2
t1ré
1t2ré1ni
tré1p2
tr2é2s1z1á2
trés2z
tri1g2
t1ri
tri1i2
t2ri1kó
t2rill
1t2ri2ó.
tri1ó
t2riój
t2riók
trol2l1in
t1ro
trol1li
tro2mad
tro1ma
tro2maj
tro1na2
tro2n1ag
tro2nal
tro2nan
tro2ne2s
tro1ne
tron3n
tron1tr
tr2os
tro1sz2f
tros2z
tro1sz2ta
t2r1o2x
tró2de
t1ró
tró2nas
tró1na
tró2n1e
1t2rónj
1t2ró1no
t2rónt
tró1s2z
tró2z1si
tróz2s
t3röm
t1rö
1t2rös
1t2rub
t1ru
tru2mad
tru1ma
1t2rup
tsa2vo
t1sa
ts2c2h
1ts1c1he
tsé2g1éj
t1sé
tsé1gé
ts2ká
ts2ko
ts2la
ts2lá
ts2le
ts2li
ts2má
ts2mi
ts2ni
tsó1i2
t1só
ts2pa
ts2pe
ts2pi
ts2po
ts2pó
tsp2r
ts2rá
t1s2ta
t1s2tá
ts2te
ts2té
ts2ti
ts2tí
ts2to
tst2r
ts2tu
ts2tú
1t2sub
t1su
tsza2ké
ts2z
t1s1za
tsza2k1ü2
tsza2t1e2
tsza2tö
tszé2t
t1s1zé
t1sz2f
t1sz2k
t1sz2l
tszö2g1e2le
t1s1zö
t3szö1ge
t1sz2p
t1sz2t2
ttad2
t1ta
tta2d2ó.
t2ta1dó
ttag1g
tta2g1o2ku
tta1go
tta2n1ér
tta1né
t2t1a1rá
t2t1aszt
ttas2z
tta1t2r
tta1ü2
t2t1a2z
ttán2s1á2g
t1tá
ttán1sá
ttá2v1i2
tte2le1me
t1te
tte1le
t2t1e2lő1a
tte1lő
tte2r1in
tte1ri
t2t1e2rő
tte2s1a2
t2t1é2g.
t1té
t2t1é2le
tté2rak
tté1ra
tté2r1em
tté1re
tté2r1es
tté2r1é2ne
tté1ré
tté2ri2d
tté1ri
ttér1in
tt1éss
tt1é2vér
tté1vé
t2t1i2o
t1ti
t2t1i1ro
t2t1isk
t2tizz
ttí1r1a
t1tí
tt1í1rá
tt1kr
ttornác1
t1to
ttor1ná
t2t1ors2
ttó1dr
t1tó
ttó1p2
t2t1ó2rá
ttó1s2r
ttö2l
t1tö
tt1ö1lő
tt1pr
t1t2raf
tt1ra
t1t2rag
t1t2ran
ttrans2z1
ttranszk2
t1t2rav
t1t2róf
tt1ró
tt2rón
ttsé2gel
tt1sé
ttsé1ge
tt2si
t2t1ug
t1tu
t2t1üd
t1tü
t2t1üt
t2t1ü2v
tt1we2
t2t2y
tty1or
t1tyo
tty1ö2l
t1työ
t1tyü2
tty1ül
ttyülés1
ttyü1lé
1tu
tuá2r
tu1á
tubus1s
tu1bu
tu1ck
tuda2te
tu1da
tuda2t1ö
2tu1dí
tu2ga
tu2go
2t1ugr
tu2hu
tu2in
tu1i
2t1ujj
2tu1ká
tu1lo2
2t1ul1ti
2t1ultr
tu2lü
tu2mab
tu1ma
tu2m1a2d
tu2maj
tu2mal
tu2man
tu2mar
tu2maz
tu2mál
tu1má
tu2me2g
tu1me
tu2m1el
tu2mer
tu2mes
tu2m1i2k
tu1mi
tu2mim
tu2m1inf
tu2m1int
tu2m1ir
tu2mis
tu2miz
tu2mí
tu2mol
tu1mo
tu2mö
tu2mő
tum1p2
tu2mü
tu2nal
tu1na
2t1unc
2t1u2ni1ó
tu1ni
tu2nit
2t1u2no
2t1u2nó
2t1unt
tu1p2r
tu2rac
tu1ra
2tura1i
2t1u2rak
tu2ra1lo
tu2ram
2t1u2ras
tu2ráh
tu1rá
2turán
tu2rár
tur2g1a
tu2run
tu1ru
tus1abl
tu1sa
tu2sar
tu2ság
tu1sá
tu2se
tu2s1ér1té
tu1sé
tu2sis
tu1si
tu2s1í
tu2sor
tu1so
tu2s1ó2
tu2s1ö
tu2ső
tuss2
tus3s1ze
tus2s2z
tus3s1zi
tus3s1zo
tu2sü
tu2szab
tus2z
tu1s1za
tu2sz1a2d
tu2szag
tu2szal
tu2szap
tu2sza2r
tus3zav
tu2szál
tu1s1zá
tusz1em
tu1s1ze
tu2sz1é2l
tu1s1zé
tu2sz1é2p
tu1s1zi2
tu2sz1il
tu2szin
tu2sz1ip
tu2szir
tu2szis
tu2sz1it
tu2s1zí
tusz1k2
tu2szol
tu1s1zo
tu2s3zó
tu2s1zö
tu2s1ző
tus1z3sa
tusz2s
tus3z1se
tuszt2
tusz1tr
tu2s1zü2
2t1u2tad
tu1ta
2t1u2tak
2t1u2tal
2t1u2tam
2t1u2tas
2t1u2taz
2tu1tá
tu2tán
2t1utc
2t1u2to
tu2tód
tu1tó
tu2tó1p2
tu2tót
tu2tun
tu1tu
tu2um
tu1u
2t1u1z1so
tuz2s
1tú
tú2ja
tú2jí
tú2jo
tú2ju
tú2l1a2
tú2l1á2
tú2l1e2
tú2l1é2
tú2list
tú1li
tú2lí
tú2lo2k
tú1lo
tú2lop
tú2l1ó2
tú2lö
tú2l1ő
túl1s
túlt2
tú2lú
tú2lü
tú2lű
túra1s2z
tú1ra
tú2rál
tú1rá
tú2r1e2
tú2r1ér
tú1ré
t2ú2r1és
tú2r1ol
tú1ro
tú2rot
tú2rö
tú2rü
t2ú2s1ze
tús2z
t2ú2s1zö
t2ú2s1zü
tú2tá
2t1útb
tú2t1e2
2t1ú2té
2t1úth
2t1ú2ti
2t1útj
2t1útl
2t1útn
2t1ú2to
2t1útr
2t1útt
2t1útv
1tü
2t1ü2dí
2t1ü2dü
2t1üdv
tü2ge
tü2g2y
tü2l1a
tü2lá
2t1ü2led
tü1le
tü2l1e2m
tü2len
tü2lep
tü2l1e2s
tü2l1e2v
tü2l1ér
tü1lé
tü2lir
tü1li
tü2lí
tü2l1o2
tü2l1ökl
tü1lö
tü2löm
tü2l1ön
tü2l1öz
tü2lők
tü1lő
tü2lőr
tü2lu
tü2lú
tü2ma
tü2mék
tü1mé
tü2m1ő
tüne2tel
tü1ne
tüne1te
2t1ünn
2t1ü2reg
tü1re
2t1ü2res
2t1ü2rí
2t1ü2rü
2tüst
2t1ü2te
2t1ü2té
tü2ti
2t1ütk
tü2tö
2t1ü2tő
tü2vö
2t1ü2zen
tü1ze
2t1üzl
1tű
tűcsa2p1
tűc2s
tű1c1sa
tű2d1al
tű1da
tű2dá
tűe2két
tű1e
tűe1ké
tűé2h
tű1é
tűfé1lé2
tű1fé
tű1gr
tű1kv
tűle1ü2
tű1le
tű1pl
tű1pr
tűrés1s
tű1ré
tű1sp
tű2z1a2
tűzá2r
tű1zá
tű2z1á1ra
tű2zát
tű2ze1ne
tű1ze
tű2zis
tű1zi
tű2zí
tű2zo
tű2z1ös
tű1zö
tűz3seb
tűz2s
tű1z1se
tű2zú
tva2n1e
t1va
tva2né2v
tva1né
tv2a2raj
tva1ra
tvá2nya2d
t1vá
tván2y
tvá1nya
tvá2nyí
tvá2z1al
tvá1za
tvá2zip
tvá1zi
tve2n1e2v
t1ve
tve1ne
tven3n
tven3t
tve1nü2
tve2nül
tve2n3y
tve2raj
tve1ra
tve2ra2l
tve2r1á2
tve2reg
tve1re
tve2r1é2s2z
tve1ré
tve2rint2
tve1ri
tve2rip
tve2r1o
tvers2
tver1st
tve3se
tvé2nye2l
t1vé
tvén2y
tvé1nye
tvé2r1int
tvé1ri
tvé2r1o
tviselő1é2
t1vi
tvi1se
tvise1lő
tvis3s1zá
tvis2s2z
t2ví1ve
t1ví
t2y
1tya
tyai2ko
tya1i
tya1kl
tya1p2
tya1s2z2
tyat2
tya1tr
tya1u2
1tyá
2ty1ág
ty1áld
ty1áll
tyá2ra2n
tyá1ra
tyár1s
tyás3s
1tye
ty1e2g2y
ty1e1la
ty1ell
ty1emb
ty1e2rő
2ty1ex
1tyé
2ty1é1te
1tyi
1tyí
ty1í2r
ty1ív
tynk2
1tyo
tyo2la2n
tyo1la
tyo2r1os
tyo1ro
1tyó
1työ
1tyő
ty1ős
ty2pe
ty1pr
ty2sa
ty1sp
1tyu
2ty1ug
ty1u2tá
1tyú
tyú2kü
1tyü
1tyű
1t2zekn
t1ze
1tzek1rő
1t2ze2n.
t2zenj
1t2zer
1t2zes
tz1ő2r
t1ző
tz3sc
tz2s
2u.
u1a
ua2c2h
ua2da
ua2dá
u2ado2r.
ua1do
u2adorb
u2ado1ré
u2adorn
u2ado1ro
u2adorr
u2adort
ua2dó
ua2es
ua1e
ua2gá
ua2ja
ua2já
ua2la
ua2lá
ua2n2y
ua2s2z
ua2t2y
ua2ut
ua1u
ua1yá
ua1yé
ua1yi
ua1yo
ua2zo
u1á
uá2ga
uá2g2y
uá2po
uá2r1á
uá2r1e
uá2réj
uá1ré
uá2r1is
uá1ri
uá2s2z
uá2t1a2
uá2t1e
ub1a2la
u1ba
ub1alk
u2b1ang
ub1arc
ub1a2ri
ub1á2r.
u1bá
ub1á2ro
ub1dr
u2b1e2b
u1be
u2b1ed
ube2lá
ube2lel
ube1le
ub1e1lö
ub1e2se
ub1est
u2b1éj
u1bé
ub1é1le
u2b1é2n
u2b1ép
u2b1érd
u2b1é2re
u2b1érm
u2b1ér1te
ubért2
u2b1é1ve
ubi2g
u1bi
u2b1i1ga
u2b1i1ge
ubi2ke
u2b1in2a.
ubi1na
u2b1ism
ub1kr
ub1ol1da
u1bo
u2bor1k2
u2b1orv
u2b1os2z
ub1ó1ri
u1bó
ub1öl
u1bö
ub1ös
ub1öv
ub1pl
ub1pr
ub1sl
ub1sp
u2b1ud
u1bu
ubu2s2z
u2b1új
u1bú
ub1üd
u1bü
ub1üg
ub1ü2l
ub1üz
uca2t1á2
u1ca
uc2c1e
uc2c3h
uc2c1i1na
uc1ci
uc2cip
uc2cö
ucc3s2z
uc2c2s
uc2cú
uc2cü
uc2c2z
uc1emb
u1ce
u1c3há
uc2h
u2c3hé
u2c3ho
uciá2r
u1ci
uci1á
uci1p
u1c2kig
uc1ki
u1c2kon
uc1ko
u1c2kot
uc2ky
uc1ö2l
u1cö
uc1pr
ucsa2p1á2
uc2s
u1c1sa
u2c3ság
u1c1sá
u2cs1ál
u2csá1ru
u2cs1e2l
u1c1se
u2cs1id
u1c1si
ucs1s
u2c3sü
uc3s2z
u2c1ug
u1cu
uc1üg
u1cü
u1c3zá
uc2z
u1c3ze
uda2tal
u1da
uda1ta
ud2a2tál
uda1tá
uda2t1á2ram
ud2atár
udatá1ra
uda2t1eg
uda1te
uda2ter
udáskés2z1
u1dá
udás1ké
udás3s
udi2o
u1di
u2d1isk
udo1kr
u1do
udó2se
u1dó
ud2ó2sor
udó1so
u1d2rá
u1d2ro
u1e
ue2bé
ue2d2z
ue2gé
ue2gés2z1
ue2g2y
uel1ér
ue1lé
ue2lő
ue2me
ue2rő
uervíz1
uer1ví
u2e2s.
u2esb
ue2s1e
u2esh
u2e1si
u2esk
u2esn
ue2ső
u2esr
u2est
u2e1su
ue2s3zen
ues2z
ue1s1ze
ue2te
ue2ve
u1é
ué2ge
ué2gé
ué2ké
ué2le
ué2pí
ué2pü
ué2te
ufé2nye
u1fé
ufén2y
u1f2le
uf2ri
uf2ru
uf2tü
ug1ag2y
u1ga
ug1alj
uga1p
ug1a1pa
1u2ga1ro
uga2t1el
uga1te
uga2té2s
uga1té
uga2tol
uga1to
uga2t1ó2r
uga1tó
ug1ág
u1gá
ugá1ra2
ugá2r1ad
ugá2rá
ugá2ros
ugá1ro
ugá2ru
ugá1r1ú
ugás3s
ug1á2s2z
ug1el
u1ge
ug1e2v
ug1é2l
u1gé
ugg2l
ugi2e
u1gi
ug1ing
ug1int
u2g1i1ro
ugi2t
ug1i1ta
ug1i1vá
ug1ír
u1gí
ug1kl
ugo2r1á2
u1go
ugó1sv
u1gó
ug2ó2s3zá
ugó1s2z2
ug1ös
u1gö
ug1pr
ug1sk
2ugu1i
u1gu
u2g1ut
u2g1új
u1gú
ug1üg
u1gü
ug1üz
ug1űr
u1gű
ugya2n
ug2y
u1gya
uh1a2dó
u1ha
uh1alk
uha2r1as
uha1ra
uha1t2r
uh1att2
u2h1á2g
u1há
uh1áll
u2h1á2r.
u2h1árb
u2h1árf
u2h1árh
u2h1árn
u2h1árr
u2h1árv
uhá2szak
uhás2z
uhá1s1za
uh1em
u1he
uh1ex
uh1ind
u1hi
u2h1ing
uh1orz
u1ho
uh1őr
u1hő
uh1pr
uh1tr
u2h2u.
u1hu
u2huj
uh1ujj
uh1üz
u1hü
u1i
ui2de
ui2dő
ui2e1u
ui1e
ui2ga
ui2gé
u2i1gn
ui2pa
ui2rá
ui2zé
u1í
uí2ja
uí2ju
uí2ve
uí2vű
u2j1an
u1ja
1ujj2a.
uj1ja
1ujjad
1ujja1i
1ujjak
1ujjam
1ujjas
1ujjat
uj2j1á2ru
uj1já
1ujjb
1ujjc
1ujjd
1uj2j1e2
1uj1jé
1ujjf
1ujjg
1ujjh
1uj1ji
uj2jí
1ujjk
1ujjl
1ujjm
1ujjn
1ujjp
1ujjr
1ujjs
1ujjt
1uj1ju
1uj1jú
uj2jü
1ujjv
u2j1op
u1jo
uk1abl
u1ka
uka2c3se
ukac2s
u2k1a2lap
uka1la
uka1pl
uka2rán
uka1rá
u2k1arc
uka2szás
ukas2z
uka1s1zá
uka1t2r
uk1áll
u1ká
uká2sar
uká1sa
u2k1á2só
ukás3s
u2k1átm
uk1bl
uke2l
u1ke
uk1e2m
uk1ex
u2k1ég
u1ké
u2k1érz
u2k1i2p
u1ki
uk2k1alt
uk1ka
uk2k1eg
uk1ke
uk2k1em
uk2k1o1la
uk1ko
uk2kö2l
uk1kö
uklás1s
uk1lá
ukl2ó2s3zá
uk1ló
ukló1s2z
u2k1old
u1ko
uko1ra2
uko2ras
uko2r1á2
uko2ril
uko1ri
uko2rin
uko2r1o
ukós2
u1kó
uk1öb
u1kö
uk1pr
u1k2rón
uk1ró
uk1ü2t
u1kü
uk1űr
u1kű
ula2c1s1ö2
u1la
ulac2s
ula1g2
ula2jas
ula1ja
ul1aleg
ula1le
u2l1alj
ula1s2p
ulata2l
ula1ta
u1la2t1a1la
ul2a2t1a2n
ul2a2tál
ula1tá
ula2tem
ula1te
ula2tik
ula1ti
ula2tol
ula1to
u2l1ábr
u1lá
ulá2k1e
ulá2k1ü
u2l1á2ri1a
ulá1ri
ulá2s1i2k
ulá1si
uláskés2z1
ulás1ké
ulás3s
ulá2s1za
ulás2z
ulá2s1ze
ul1bl
ulcs1e2l
ulc2s
ul1c1se
ulcs1es
ul2cs1é2r.
ul1c1sé
ul2csérv
ul2cs1é2vé
ul2c2si1ga
ul1c1si
ul2csip
ul2c1s1í2
ul2csor
ul1c1so
ul2c1s1ö2
ul2c1s1ő
ul2csut
ul1c1su
ul2c1sü
ulcs3z
ule2i
u1le
ule1í2
ule2l
ul1e1lő
ulet2ta
u2l1ex
ulé2kal
u1lé
ulé1ka
ulé2k1e2
ulé2k1ol
ulé1ko
ulé2kut
ulé1ku
ul1é1pü
u2l1ér1té
uli2nar
u1li
uli1na
uli2nin
uli1ni
ul1í2r
u1lí
ul1k2r
ul2lef
ul1le
ul2l1e2l
ul2l1em
ul2l1en
ul2l1ér
ul1lé
ulot2
u1lo
uló1f2
u1ló
ulói2kon
uló1i
ulói1ko
ulókés2z1
ul2ókés
uló1ké
uló1ó2
ulót2
uló2za2n
uló1za
uló2z1á2ra
uló1zá
uló2z3s
ul1öt
u1lö
ul1pr
ul1st
ul2t1aj
ul1ta
ulta2r
ult1as2z
ul2taz
ul2tül
ul1tü
ul1úr
u1lú
ul1üt
u1lü
ul1űr
u1lű
u2lyi
ul2y
u2m1abl
u1ma
um1abr
um1a2cé
um1ac2h
um1a2dat
uma1da
u2m1adm
um1a2do
um1a2dó
2uma1é
um1a1já
u2m1a2ka
umakés2z1
um2akés
uma1ké
u2m1akk
u2m1akt
u2m1a2la
u2m1alg
um1all
um1alt
u2m1a1na
u2m1ank
u2m1a2no
u2m1a2n2y
2uma1o
2uma1p2
u2m1a2rá
um1arc
um1arg
u2m1a2ri
um1a2ro
um1asp
u2m1atl
u2m1a1u
um1a2zo
u2m1ág
u1má
u2m1áll
um1álm
u2m1á2rak
umá1ra
u2m1á2ram
u2m1á2ras
u2m1á2rá
u2m1árf
u2m1árk
u2m1árn
u2m1á2ro
u2m1árr
u2m1árt
u2m1á2ru
u2m1árv
u2m1á2t1a
u2m1á2t1e2
u2m1átm
u2m1á1tu
um1bl
um1b2r
um1dr
u2m1e2d
u1me
u2m1ef
ume2g
um1eg2y
um1e1la
u2m1elb
u2m1e2le
um1e1lé
um1elh
u2m1e2l1í2
um1elj
um1elm
u2m1eln
um1e1lo
um1e2lő
u2m1elt
um1elv
u2m1e2m
ume2n1á
ume2n1ó2
um1e2re
um1erk
um1e2rő
um1e2se
um1ess
um1e2s2z
u2m1e2t
u2m1e2v
u2m1ex
um1ezr
u2m1ég
u1mé
u2méhs
um1é2le
um1élv
u2m1ép
u2m1é2r.
u2m1érc
u2m1érm
u2m1ér1te
u2m1ér1té
u2m1érv
u2m1é2te
um1fl
um1f2r
um1gl
um1gr
umi1a2
u1mi
u2m1i2dő
umig2
umi1gr
um1imp
umi2n2a.
umi1na
u2m1ind
u2m1ing
u2m1inv
um1i2onb
umi1o
um1i2o1né
um1i2onh
u2m1i2onj
um1i2on1k2
u2m1i2onn
u2m1i2o1no
um1i2onr
um1i2ont
u2m1irt
um1isk
umi1sl
um1ism
umi1sp
umi2s1zü
umis2z
umit2
umi1tr
um1i1zé
um1ív
u1mí
um1íz
umké1s1z1e
um1ké
umkés2z
um1kl
um1kr
um1kv
um1na2
u2m1o2koz
u1mo
umo1ko
um1o1la
um1old
um1oll
um1olt
um1olv
u2m1o2p
umo2ra2n
umo1ra
um1o2rat
umo2rál
umo1rá
umo2rin
umo1ri
u2m1os1to
u2m1os2z
u2m1ox
um1ó2rá
u1mó
um1ö2l
u1mö
um1öm
um1ön
um1ö2r
um1ös
um1öt
um1öv
um1ö2z
um1ő2r
u1mő
um1ős
umpe2l
um1pe
ump1e1le
um2p1ing
um1pi
um1p2r
um1sk
um1sp
um1st
um1s2z
um1t2r
u2m1ud
u1mu
u2m1ug
u2mu1ni
umu2r
um1u1ra
u2m1u2t
um1üg
u1mü
um1ü2l
um1ür
um1üs
um1üt
um1üv
um1üz
umva2s
um1va
una1b
u1na
un1a1du
un1akt
u2n1arc
u2n1á2g
u1ná
un2c1s1e
unc2s
un2csiv
un1c1si
un2d2z
un1e2r
u1ne
un1e2t
un1ég
u1né
un2g1a2g
un1ga
un2g1eg
un1ge
un2g1er
ung3g
un2g1ol
un1go
u2n1i2d
u1ni
1u2nif
1u2ni1ku
u2n1il
u2n1in1go
1u2ni1ó
1u2niv
unka1p2
un1ka
unka1s
un2k1eg
un1ke
u2nod
u1no
u2n1orr
un1or1s2
u2not
un1pr
un1s2t2
unta2i
un1ta
u2nun
u1nu
u2n1útj
u1nú
un1ü2l
u1nü
u1o
uo2la
uo2li
uo2r1a2
uo1re2
uo2r1et
uo2r1i2o
uo1ri
uo2xi
u1ó
u2ó1bu
u2ó1ne
uó1p2r
u2ó1ré
uó2ri
u2ó1so
u2ós2z
u2ó1ve
u1ö
uö2ko
uö2kö
uö2rö
uö2zö
u1ő
uő2re
uő2ré
uő2ri
uő2rö
uő2rü
upa1b2
u1pa
up1a1da
upa1pr
upas2
upa1sp
upa1t2r
up1da2
upe2r1a
u1pe
upe2rel
upe1re
upe2r1in
upe1ri
uper1s
up1e2s
upé1p2
u1pé
up2hi
upli2n
up1li
up1üz
u1pü
1u2raim
u1ra
ura1i
1u2ra2k.
1u2rakh
1u2rakk
1u2rakn
1u2rakr
u2ralh
1u2ralk
1u2ralm
1u2ra2m.
ura2m1is
ura1mi
1u2ra1sa
ura1s2p
ur1áll
u1rá
urá1t1a
ur2dar
ur1da
ur2d1e
u2r1ef
u1re
ur2fí
ur2f1ú
1ur1ná
ur2ne
ur1n2é
uro1b2
u1ro
uro1ka2
uro2kan
uro2k1á
uro2ke2
uro2ne
uro1p
uro1t2
ur1öl
u1rö
ur1pi2
ur2t1ag
ur1ta
ur2t1e2t
ur1te
ur2t1e2v
urti2t
ur1ti
urt1i1ta
ur2t1ok1ta
ur1to
uru2c1e
u1ru
2urul
uru1p2
uru2szál
urus2z
uru1s1zá
u2r2ú.
u1rú
us1ab1la
u1sa
us1a2da
us1a2dá
u2s1a2dó
u2s1a2g
u2s1a2j
usa2kar
usa1ka
u2s1akc
u2s1a2la
us1alg
us1alj
us1alk
u2s1alt
us1alv
u2s1a1na
u2s1a1ne
us1ant
us1a1pá
u2s1a2ra
u2s1a2rá
u2s1arc
u2s1arz
u2s1ass
u2s1att
u2s1a2t2y
u2s1a1u
u2s1a2z
u2s1ábr
u1sá
u2s1á2gá
us1ágb
u2s1ágg
us1ágh
u2s1ágr
us1áld
us1áll
u2s1á2p
u2s1á2rad
usá1ra
u2s1á2ra1i
u2s1á2rak
u2s1á2rá
u2s1árb
u2s1árh
u2s1á2ri
u2s1ár1k2
u2s1árn
u2s1á2ro
u2s1árr
u2s1árt2
u2s1á2ru
u2s1á1ta
u2s1áth
u2s1á1ti
u2s1átk
u2s1átt
u2s1á1tu
u2s1átv
us1bl
us1br
us1dr
us1e2c
u1se
us1e2d
u2s1ef
us1e2g2y
u2s1e2l
u2s1e2m
u2s1e2n
us1erd
u2s1e2s
use1t
u2s1e2v
u2s1ex
us1ez
u2s1ég
u1sé
u2s1é1he
u2s1é2k
u2s1é2l
u2s1é2ne
u2s1ép
u2s1érd
u2s1ér1te
u2s1érv
u2s1és
u2s1é2te
u2s1étk
u2s1étt
u2s1é1ve
us1fr
us1gr
u2s1i2d
u1si
usi2g
u2s1i1ga
u2s1i2ko
u2s1ill
u2s1i2ma
u2s1i2má
us1i1mi
u2simm
u2s1imp
u2s1inc
u2s1ind
u2s1inf
u2s1ing
u2s1ink
u2s1int
u2s1inv
u2s1i2p
u2s1i2rat
usi1ra
u2s1i2rá
u2s1i1ro
u2s1irt
u2s1isk
u2s1ism
us1i1ta
u2s1i1zé
us1íg
u1sí
u2s1íj
usí2r
us1í1rá
us1í1ró
u2s1í2v
u1s2kál
us1ká
us1kl
uskói2k
us1kó
uskó1i
us1k2r
us1kv
u2so2dú
u1so
u2s1of
u2s1okl
u2s1okm
u2s1ok1ta
us1o1la
u2s1old
us1o1li
u2s1oml
u2s1ond
u2s1op
u2s1org
u2so1ri
u2s1orr
u2s1ors
u2s1os2z
u2s1ott
us1óc
u1só
us1ó2s
u2s1ö2l
u1sö
u2s1ön
u2s1örd
us1ö2rö
u2s1ös
u2s1öt
us1ö2v
u2s1ö2z
us1ő2r
u1ső
u1s2pec
us1pe
us1pl
us1pn
us1pr
us1ps
2us2s.
us2s2e.
us1se
us1s1ká
us1s1pe
us1s1pi
us1s1ta
us1sy
us3szab
us2s2z
us1s1za
us3szag
us3szak
us3szál
us1s1zá
us3szám
us3szen
us1s1ze
us3s1zé
us3szig
us1s1zi
us3s1zí
us3s1zó
us3s1zö
us3s1ző
us1s1zü2
ussz1ül
us3s1zű
ust1á2rár
us1tá
ustá1rá
us2teg
us1te
us2t1il
us1ti
us1trad
ust1ra
us1t1re
us1t1ré
us1t1ro
u1st1ru
us2t1ül
us1tü
u2s1uj
u1su
usu2s
us1us2z
u2s1u2t
u2s1új
u1sú
u2s1ú1té
us1üd
u1sü
u2s1üg
usü2l
us1ü1lé
u2s1ün
u2s1ür
us1üz
usz1abl
us2z
u1s1za
u2szaj
usz1a2la
usz1alk
usz1alv
u2sz1a2n
us2z1ap1p2
usza2r
usz1a1rá
u2sz1a1ré
usz1a1ro
us2z1as2z
u2sz1a1u
u2sz1á2g
u1s1zá
usz1ál1lá
u2sz1á2p
u2sz1á2rad
uszá1ra
u2s3zára1ko
u2sz1á2ram
usz1ár1je
usz1ásv
u2s2záth
u2sz1á2t1ö
u2sz1e2c
u1s1ze
u2szef
usz1e2ge
usz1e2g2y
usze2k
usz1e1ke
u2sz1e2l
usz1emb
us2z1eml
us3ze1ne
usz1eng
u2sz1er1d2
usz1e2ré
usze2s
u2sz1e2v
u2sz1ex
u2szé1ne
u1s1zé
usz1é1te
usz1é1to
usz1imp
u1s1zi
usz1ind
usz1inj
usz1isk
usz1ism
u2s2z1is2z
uszí2j1a2d
u1s1zí
uszí1ja
u2sz1ír
1u2szo1dá
u1s1zo
u2szo1la
u2sz1old
u2szon2y
u2szop
u2s2zos2z
u2sz1ö2b
u1s1zö
usz1öl
usz1ön
u2sz1ös
usz1p2
uszte2r1a
usz1te
usz1t2ran
uszt1ra
u2sz1u2s
u1s1zu
u2szut
u2sz1útr
u1s1zú
u1s1zü2
usz1üg
u2sz1ül
u2sz1üz
usz1z
ut1abl
u1ta
uta2csel
utac2s
uta1c1se
u2t1a2dó
2utakép
uta1ké
1u2ta1ló
1u2talv
uta1me2
uta2mel
uta2mer
uta1p
ut1a1rá
1u2tasc
uta2se
1u2tasf
1u2tasl
1u2tasv
uta1ü2
2utav
1uta1zi
2utáb
u1tá
2utá1é
2utáib
utá1i
2utáin
2utá1ju
2utákb
2utákt
2utám
2utá1ná
után1n
1u2tánz
2utá1ró
utá2rú
ut1bl
1ut1ca
1ut1cá
u2t1e2g
u1te
ute2r1a
ute2rá
ute2reg
ute1re
ute2rim
ute1ri
ute2ru
utén3n
u1té
ut1fr
uti2k1á2r
u1ti
uti1ká
uti2ke
ut1ill
uti2m
uti2n1e2
uti2nér
uti1né
uti1ni2
uti2ni2g
uti2n1ik
uti2n1ó
utin1s
u2t1i2pa
u2t1isk
ut1kl
ut1ok1ke
u1to
u2t1old
uto2l1é
u2tols
2utoma1ta
uto1ma
uto2rim
uto1ri
2utos
2utot
utó2dal
u1tó
utó1da
u2tó1dá
utó2del
utó1de
utó1p2
utó2s1aj
utó1sa
utós3s
utó1s2to
utó1s2z
utó1tr
utótűz1
utó1tű
ut1pr
ut2rak
ut1ra
ut2ran
ut2rák
ut1rá
ut1sp
ut1st
ut1t2r
ut1üg
u1tü
ut1ü2z
utya1s2
ut2y
u1tya
u1u
uu2m1ag
uu1ma
uu2mal
uu2m1as
uu2mál
uu1má
uu2m1e2
uu2m1é2r
uu1mé
uu2mim
uu1mi
uu2min
uu2mö
uum1p2
uu2mü
uu2ta
uu2tá
uu2z2s
u1ú
u1ü
uü2g2y
uü2rí
uü2té
uü2ve
uü2ze
u1ű
uva2r1a
u1va
uva2r1á2
uva2r1e
uva2rin
uva1ri
uva2szál
uvas2z
uva1s1zá
uva1ta2
uv2a2t1ag
uvi1g2
u1vi
uv2re
uxi2t1a
u1xi
uxi2t1á
uxi2t1e
uza1la2
u1za
uza2lac
uza2lad
uza2la2n
uza2lág
uza1lá
uza2l1át
uza2le2l
uza1le
uza2l1ék
uza1lé
uza1p2
uza1s2
2uzá2l.
u1zá
2uzálb
2uzáll
2uzálr
u2z1id
u1zi
u2z1i2gye
uzig2y
uz1ír
u1zí
uz1ki2
uzó1i2
u1zó
uz3sap
uz2s
u1z1sa
uz3s2z
uz1t2r
uz1ü2g
u1zü
uzü2l
2ú.
ú1a
úa2da
úa2dá
úa2dó
úa2g2y
úa2já
úa2kar
úa1ka
úa2kas
úa2la
úa2lá
úa2n2y
úa2s2z
úa2ud
úa1u
úa2va
ú1á
úá2ga
úá2gá
úá2gi
úá2go
úá2g2y
úá2hí
úá2lo
úá2po
úá2ra
úá2ri
úá2ru
2úbab
ú1ba
2úband
2úbar
úb2lo
2úbód
ú1bó
úb2ri
úb2ro
ú2c1aj
ú1ca
ú2c1a2l
ú2c1a2n
ú2c1a2v
úc1e2t
ú1ce
ú1c3he
úc2h
ú1c3ho
ú2c1i2d
ú1ci
úci2ókép
úci1ó
úció1ké
úc1pr
2ú1c1sa
úc2s
ú2csab
ú2csad
ú2cs1ag
ú2cs1aj
ú2csakt
úcs1a1la
ú2cs1a2n
úcsa2p1á2
ú2cs1a2s
ú2cs1a1u
ú2csaz
ú2cs1á2g
ú1c1sá
ú2cs1ál
ú2cs1á1rá
ú2cs1árf
ú2cs1á1ri
ú2cs1árv
ú2c2sátv
2ú1c1se
ú2cs1eb
ú2cs1e2g
úc3sej
ú2cs1e2l
úcs1emb
ú2cs1en
ú2c2s1e2rő
ú2cs1e2s
ú2cs1él
ú1c1sé
ú2cs1é2r.
ú2csérd
ú2cs1ér1te
ú2cs1ér1té
ú2cs1é2v
ú2cs1id
ú1c1si
ú2csigaz
úc2si1ga
ú2cs1il
ú2csim
ú2c2s1inf
ú2cs1int
úcs1i2pa
ú2csi1rá
ú2cs1is
ú2cs1iz
2ú1c1so
ú2c2sok1ta
ú2c2sos2z
2ú1c1só
ú2cs1ó2r
úcs1öl
ú1c1sö
úcs1ös
úcs1p
úcs1s
úcs1t
úcsús1s
ú1c1sú
úcs1ü2t
ú1c1sü
úc1s3za
úcs2z
ú2d1a2c
ú1da
úda2d
ú1d1a1da
ú2d1a2k
ú2d1a2n
úd1ág
ú1dá
úd1ál
úd1á2r
ú2d1e2g
ú1de
ú2d1ej
úde2l
úd1e1le
úd1elh
úd1e1lő
ú2d1e2m
úde2ra
úde2r1e2c
úde1re
úd1e2rő
úd1e2v
ú2d1ék
ú1dé
ú2d1é2r.
ú2d1érc
údé2t
úd1é1te
ú2d1i2d
ú1di
údi2g
ú2d1i1gé
úd1ing
údi2ódar
údi1ó
údió1da
údi2óz
ú2d1os
ú1do
úd1pr
úd2rá
úd2ro
úd1üv
ú1dü
údy1éh
údy1ét
údy1i
údy2s
ú1d3zá
úd2z
ú1e
úe2bé
úe2gé
úe2gés2z1
úe2g2y
úe2la
úe2le
úe2lo
úe2lö
úe2lő
úe2me
úe2pi
úe2re
úe2ré
úe2rő
úe2rű
úe2s2z
úe2ta
úe2te
úe2ve
úe2vő
ú1é
úé2he
úé2le
úé2lő
2úé2ne
úé2pí
2úé2r.
úé2r1á
úé2re
úé2ri
2úérz
úé2te
úé2ve
úfé1lé2
ú1fé
úfé2l1év
úf2lö
úf2rá
úf2ri
úf2rí
úf2ro
úg1a2d
ú1ga
úgás1s
ú1gá
úg1el
ú1ge
úg1i2v
ú1gi
úg1old
ú1go
úgós2
ú1gó
úg2rá
úgy1ag
úg2y
ú1gya
úgy1el
ú1gye
ú2gy1é2r.
ú1gyé
ú1gyi2
úgy1is
úgy1iv
ú2gy1u2
úgy1út
ú1gyú
ú1i
2úi2de
úi2dő
úi2ga
úi2gé
2úi2ke
2úik1re
2úill
2úi2má
úi2mi
2úing
2úint
úi2pa
úi2rat
úi1ra
úi2rá
2úisk
2úism
2úist
úi2ta
2úi2vad
úi1va
úi2vás
úi1vá
ú1í
úí2rá
úí2ve
úí2vi
úí2vü
2újá1té
ú1já
új1es
ú1je
új1ez
új1é1ve
ú1jé
új1é1vé
új1k2r
1ú2jon
ú1jo
új1or
új1pl
új1ra1
1új1sá
új1ud
ú1ju
ú2jul
2úkab
ú1ka
ú2k1a2g
ú2k1a2j
úk1a2lak
úka1la
2úkalan
ú2k1alk
ú2k1an2y
2úkap
ú2k1a2pó
2úkar
2úka1te
ú2k1atk
ú2k1ál
ú1ká
ú2ká1ru
úke2l
ú1ke
úk1e1le
úk1ell
ú2k1em
úke2s
úke2t
úk1e1te
úk1e2vé
2úkérd
ú1ké
2úké1ré
2úkés
ú2k1éss
ú2k1észr
úkés2z
ú2k1é2te
2úkéz
úki1a2
ú1ki
ú2k1i2d
2úkin
ú2k1is
ú2k1i2t
ú1k2li
úk2lu
2úkol
ú1ko
ú2k1olt
2úkom
2úkonf
2úkong
2úko1rá
2úko1ro
2úkos
úk1ó2l
ú1kó
úkö2l
ú1kö
úk1ö1lő
úk1pr
ú1k2re
úk1t2r
ú2k1ud
ú1ku
ú2k1úr
ú1kú
úkü2l
ú1kü
úk1ü1lő
úk1ült
ú2k1ür
ú2k1ü2t
ú2k1üz
úl1a2d
ú1la
úl1a2ja
úl1a2l
úl1a2m
úla2n
ú2l1an2y
úl1a2ro
ú2l1á2g
ú1lá
ú2l1ál
úl1árn
ú2l1á2s2z
úl1átv
úl1br
úl1d2r
úl1e2d
ú1le
úle2l
ú1l1e1le
úl1ell
ú2l1emb
úl1en
ú2l1e2re
úl1e2s
ú2l1e2vő
úl1ex
ú3l2é.
ú1lé
úl1é2d
úlé2g
ú3l2é3va
úl1fr
úl1gl
ú2l1i2d
ú1li
ú2l1i1gé
ú2l1ij
ú2l1il
ú2li1má
ú2l1ind
ú2l1inf
ú2l1ing
ú2l1inj
ú2l1int
ú2l1inv
ú2l1i2p
ú2l1i1rá
ú2l1isk
ú2l1i2s2z
ú2l1i2ta
ú2l1itt
ú2livás
úli1vá
ú2li1vo
ú2lizga1to
úliz1ga
ú2l1izz
úl1íg
ú1lí
úl1í2v
úl1k2l
úl1kv
úlo2k
ú1lo
úl1o1ko
ú2l1ol
úl1o2pe
ú2l1or
ú2l1os
ú2l1ox
úl1öb
ú1lö
úl1öl
úl1ö2m
úl1ö2n
úl1ör
ú2l1ös
úlövés1s
úlö1vé
úl1ő1rü
ú1lő
úl1p2l
úl1p2r
úl1p2s
úl1sk
úl1sm
úl1sp
úl1s2t
úls2z2
úlsz2tá2
úlszt2
últ1agg
úl1ta
últ1ag2y
úl2t1aj
úl2t1al
úl2t1árn
úl1tá
úl2té2l
úl1té
úl2tér1te
úl2t1és
últ1éves
últé1ve
úl1ti2
úl2tid
úl2t1im
úl2t1in
úl2t1ip
úl2tis
úl2tí
últ1old
úl1to
úl2tös
úl1tö
úl2t1ő2r
úl1tő
úl1t1rá
ú2l1ud
ú1lu
ú2l1u2g
ú2l1ur
ú2l1u2t
ú2l1úr
ú1lú
úl1ús
úl1üg
ú1lü
úl1ül
úl1ün
úl1ür
úl1üt
úl1üv
úl1üz
úl1űz
ú1lű
ú2ly1a2d
úl2y
ú1lya
ú2ly1a2l
ú2ly1an
ú2ly1a2r
ú2ly1a1u
ú2lyál
ú1lyá
ú2ly1átl
ú2ly1e2
ú2lyé2l
ú1lyé
ú2lyés
ú2lyo2l
ú1lyo
ú2ly1ö
ú2lyő
úly1s
ú2lyug
ú1lyu
2úmac
ú1ma
2úmad
2úmag
2úmaj
2úmar
2úmatr
úmi2al
ú1mi
úmi1a
2úmoz
ú1mo
2únac
ú1na
2únap
úna1u2
ú2ny1a2n
ún2y
ú1nya
ú2ny1i2r
ú1nyi
úny1tr
ú1o
úo2ko
úo2ve
ú1ó
úó2ra
úó2rá
úó2sá
úó2vo
ú1ö
úö2le
úö2lé
úö2lő
úö2rö
úö2ve
ú1ő
úő2rö
úp1eg
ú1pe
úpe2l
úp1es
ú2p1in
ú1pi
úp2la
úp2lé
ú2p1or
ú1po
úp1p2l
úp2rí
úp2ro
úraát1
ú1ra
úra1á
2úrab
2úraj
úr1akk
úr1a1lu
ú2r1ant
úr1a2nya
úran2y
úra1p2
úra1szp
úras2z
ú2rattas
úrat1ta
úrau2r
úra1u
ú2r1ábr
ú1rá
úr1áll
ú2r1á2ri
úrás1s
ú2r1átm
úr1br
úr1d2r
úr1e2c
ú1re
úr1e2l
úr1ez
ú2rék
ú1ré
úr1ékk
ú2r1é2l
úr1é2ne
2úrés
úrfé2l1é2v
úr1fé
úrfé1lé
úri2al
ú1ri
úri1a
ú2r1i2d
ú2ri1e
úr1ifj
úri2g
ú2r1i1ga
úri3gé1nyé
ú2r1i2gé
úr1i2g2én2y
úr1i2m
ú2r1inf
úr1ing
ú2r1int
ú2ris
úr1ist
úr1k2r
1úrnőr
úr1nő
úr1ott
ú1ro
úró1p2
ú1ró
úró1sp
úr1ö2c
ú1rö
úr1ö2l
úr1ön
úr1öt
úr1ős
ú1rő
úr1pr
úr1s2k
úr1sn
úr1s2r
úr1s2t
úr1szn
úrs2z
úr1u2t
ú1ru
úr1ü2l
ú1rü
úr1ü2v
2úsabl
ú1sa
ú2s1abr
ú2s1a2d
ú2s1a2j
ús1a2la
úsa2n
ús1an2y
ús1apr
úsa2r
ús1a1rá
ú2s1arc
2úsarj
úsá2gol
ú1sá
úsá1go
ús1áld
ú2s1á2p
ú2s1á2ra1i
úsá1ra
ú2s1á2rak
ú2s1árb
ú2s1á2r1e2
ú2s1á2ro
ú2s1á2ru
ú2s1á2rú2
ú2s1árv
2úsát
ús1átl
ú2s1á2z
ús1dr
ús1e2c
ú1se
ús1e2l
ús1e2v
ús1ex
ú2s1é2g
ú1sé
ú2s1é2l
ús1é2ne
ú2s1é2ré
ú2s1érm
ú2s1ér1té
ú2s1é2tá
ú2s1é2te
ú2s1étr
ús1fr
úsi2g
ú1si
ú2s1i1ga
ú2s1il
ú2s1imp
ú2s1in
ú2s1i2p
ú2s1i2r
ú2s1is
ús1í2z
ú1sí
ús1kl
ús1kv
ú2s1o1la
ú1so
ú2s1old
ús1org
ú2s1orr
ú2s1os2z
ús1ó2h
ú1só
ús1ös
ú1sö
ús1őr
ú1ső
ús2pe
ús1pr
ús1s2p
ús3szag
ús2s2z
ús1s1za
ússza2k1
ús3szav
ús3s1ze
ús3s1zi
ús3s1zí
ús3s1zó
ús3s1zö
ús3s1zú
ús3s1zü
ús2tat
ús1ta
ús1t2r
ú2s1u2ga
ú1su
ú2s1u2t
ús1üg
ú1sü
ús1ün
ús1ü2t
ús1üz
úsvé2t1e
ús1vé
ú2sz1a2d
ús2z
ú1s1za
ú2s2z1akc
ú2sz1á2g
ú1s1zá
úszás1s
2ú1s1ze
úsz1ej
úsz1e2s
úsz1e2v
2ú1s1zé
ú2sz1év
ú2szi1gá
ú1s1zi
2ú1s1zí
úsz1k2
úsz1old
ú1s1zo
ús2z1os2z
ú2szó1e
ú1s1zó
2ú1s1zö
úsz1ös
úsz1p
ús3z1se
úsz2s
ú2s3zú
2ú1s1zü
úsz1ü2g
úsz2ve
út1a2d
ú1ta
út1a2i
ú2t1a2j
út1a2v
ú2t1ál
ú1tá
út1á2ro
út1á2s
ú2t1á2t1
útá2v1i2
út1bl
út1ef
ú1te
ú2t1e2g
út1e1lá
út1ell
út1elz
ú2téh
ú1té
út1é2l
ú2tén
ú2t1ép
út1érd
ú2t1é2ri
út1érz
útfé1lé2
út1fé
út1gr
ú2tiakh
ú1ti
úti1a
ú2tiakn
út1id
úti1e2
ú2ti1é
úti2g
út1i1gé
1ú2tij
1ú2t1i2ko
út1ill
1ú2ti1ná
út1ind
út1inf
út1ing
út1int
út1i2pa
1ú2tir
út1i1rá
út1ism
út1ist
1ú2t1i2z
út1íg
ú1tí
út1íj
út1ív
út1okm
ú1to
ú2t1o1la
út1old
út1oml
úto2n1
út1ont2
út1op
2útor
úto2ra2n
úto1ra
úto2r1as
úto2rál
úto1rá
úto2re2
út1ost
út1os2z
út1ös
ú1tö
útő2r
ú1tő
út1pl
út1pr
ú1t2rag
út1ra
2ú1t2ri
útsá2gi
út1sá
út1st
útu2m1é
ú1tu
útu2r
út1u1rá
ú2t1út
ú1tú
ú2t1üg
ú1tü
1út1vo
ú1u
úu2no
úu2ra
ú1ú
ú1ü
úü2g2y
úü2lé
úü2re
úü2te
úü2ve
úü2vö
úü2ze
ú1ű
úű2ző
2úvál
ú1vá
úv2ár
úvá2rad
úvá1ra
úvá2ra2l
úvá2ris
úvá1ri
úvá2ro2s2z
úvá1ro
úvá2r1ó2
úvá1ru2
úza1e2
ú1za
úza1é2
úzak2
úza1p2
ú2z1arc
úza1t2r
ú2z1ál
ú1zá
ú2zá1ru
úzás1s
úz1i2d
ú1zi
úzi1do2
úzót2
ú1zó
úzó1tr
úz1p2r
ú2zs1a2l
úz2s
ú1z1sa
ú2zsál
ú1z1sá
2ú1z1se
úzs1e2c
2ú1z1si
úz3s2z
úz1t2r
2ü.
ü1a
üa2já
üa2la
ü1á
üá2ga
üá2go
üá2gu
üá2ra
üá2s2z
üb2lo
ücsö2k1
üc2s
ü1c1sö
ü2des
ü1de
1ü2dít
ü1dí
üd1íz
ü2d1ör
ü1dö
üdös3s
üdő1é2
ü1dő
1ü2dül
ü1dü
üd2v1a2
üd2v1el
üd1ve
üd2v1e2s
üd2vél
üd1vé
üd2vid
üd1vi
üd2v1í
üd2vo
1üd1vö
üd2vö2l
üd2vő
üd2vu
üd2vú
ü1e
üe2bé
üe2ge
üe2gé
üe2le
üe2l1é2
üe2me
ü1é
üé2ke
üé2pí
üf2f1ö2
üfö3l1e2
ü1fö
üf2ro
üge1k2
ü1ge
üge3l
üg2ra
ü2gy1a2
üg2y
ü2gy1á
ü2gyef
ü1gye
ügy1e2lemb
ügye1le
ügy1e2lemm
ügy1elf
ü2gy1ell
ügy1elm
ügy1e1lo
ügy1é1jé
ü1gyé
ügy1ékb
ügy1é1ré
ü2gy1érr
ü2gyés
ü2gyil
ü1gyi
1ü2gyin
ügy1int
ügy1i1ra
ü3gyí
1ügy1nö
ügy1os
ü1gyo
ü2gy1ő2
üh1af
ü1ha
üh1at
üh1ás
ü1há
ü2h1e2le
ü1he
ü2h1elf
ü2h1ellen
ühel1le
ü2h1e2lő
üh1elv
ü2h1éh
ü1hé
ü2h1é2r.
ü2h1in
ü1hi
ü2h1it
üh1or
ü1ho
üh1ős
ü1hő
ü1i
üi2gé
üi2ko
ü1í
üí2rá
ük1a2n
ü1ka
üka2p
ük2kal
ük1ka
ük2ká
ük1ke2
ükkel1
ük2ker
ük1ko2
ük2kop
ük1u2n
ü1ku
ül1ab
ü1la
ül1a2d
ül1ag
ül1aj
ül1a2k
ü2l1a2l
ül1a2r
ül1at
ül1a1u
ül1á2c
ü1lá
ül1á2g
ül1ál
ül1á2p
ül1á2r
ül1á2s
ül1br
ül1d2r
ü2l1e1c1se
ü1le
ülec2s
ül1e2d2z
ü2l1e2g
2ülek
ül1ell
ü2l1e2lő1te
üle1lő
ül1eng
ül1enn
ü2l1e2r
ül1e2ső
üle1ta2
üle2tal
üle2t1an
üle2t1as
üle2tav
üle2t1á2
üle2t1eg
üle1te
üle2t1e2l
üle2t1ék
üle1té
üle2t1é2r.
üle2t1érn
üle2t1é2r2ő.
ületé1rő
üle2té1rü
üle2t1é2v2e.
ületé1ve
üle1to2
ül2e2t1or
üle2tos
üle2t1ó2
üle2t1ö2
ület1t2
ül1ett2e.
ület1te
ül1etted
üle2t1u
üle2tüz
üle1tü
ü2l1ex
ü2l1é2g
ü1lé
ülé2k1a2
ülé2k1á2
ülé2k1e2l
ülé1ke
ülé2kev
ülé2kir
ülé1ki
ülé2k1o
ülé2ku
ü2l1él
ül1é2pü
ü2l1é2r.
ü2l1é2ré
ül1é1ri
ül1érj
ül1érn
ül1érs
ü2l1é2rü
ül1érv
ü2l1érz
ülé2sa2
ülé2so
ülés3s
ül2é2s3zá
ülés2z
ül1fr
ü2l1i2d
ü1li
üli2g
ü2l1i1ga
ü2l1ill
ü2l1im
ü2l1int
ül1i1ra
ü2l1itt
ü2l1iz
ül1íg
ü1lí
ül1í2r
ül1í2v
ül2l1a2n
ül1la
ül2l1in
ül1li
ül2l1ö2vü
ül1lö
ül2l1u2
ül3lyu
ül2l2y
ülnö2k1öl
ül1nö
ülnö1kö
ül1o2d
ü1lo
ül1o2l
ül1om
ül1op
ül1or
ül1ó2v
ü1ló
ü2l1öb
ü1lö
ü2l1ö2l
ü2l1ö1mö
ü2l1ör
ü2l1ö2v
ülő1e2
ü1lő
ülőé2l
ülő1é
ülő1sl
ülő1s2p
ülő1s1ta
ülőt2
ül1p2l
ül1p2r
ül1sl
ül1sp
ül1st
ül2t1ad
ül1ta
ültá2r
ül1tá
1ültetl
ül1te
1ültets
ül2t1e2v
ül1t1ra
ül1ud
ü1lu
ül1u2g
ül1u2t
ül1ús
ü1lú
ü2l1üg
ü1lü
ülü2l
ül1ü2lé
ül1ülh
ül1ü1li
ül1ülj
ül1üln
ül1ült
ül1ülv
ü2l1ür
ü2l1üt
ü2l1üv
ü2lyel
ül2y
ü1lye
üly1ess
üly1es2z
üm1a2l
ü1ma
üm1a2n
üm1a2r
üm1á2r
ü1má
üme3gi
ü1me
üm1éks
ü1mé
ü2m1ép
ü2m1érd
üm1fl
üm1fr
ümi2g
ü1mi
ü2m1i1ga
ü2m1il
ü2m1im
ü2m1i2p
ü2m1is
ü2m1iz
üm1kl
üm1kr
üm1o2l
ü1mo
üm1or
üm1os
ü2m1öss
ü1mö
üm1őr
ü1mő
üm1p2r
üm1tr
üm1új
ü1mú
ü2m1ü2l
ü1mü
ü2m1üv
ü2m1üz
ün1ál
ü1ná
ün1á2r
ün1e2l
ü1ne
üne2t1elet
üne1te
ünete1le
üne2tés
üne1té
ün1e1vé
ü2n1é2p
ü1né
ün1id
ü1ni
ü2n1ing
ü2n1irt
1ünnep
ün1ne
ü1o
üo2k1i2
ü1ó
üó2rá
ü1ö
ü1ő
üő2re
üp2ri
üp2ro
ürdés1s
ür1dé
1ü2reg
ü1re
üre2g1a
üre2gá
üreg1g
1ü2res
üre2tö
ü2röm
ü1rö
ür2t1a
ür2t1á
ür2t1e2
ür2tis
ür1ti
ür2t1o2
ürtok1
ür1tü2
ür2tül
1ü2rül
ü1rü
üs2s1a2
üs2s1á
üs2s1eg
üs1se
üs2s1o2
üss1s
üs1sü2
üs2s1ül
üs2t1a2
üs2tá2
üst1ág
üst1ál
üst1ár
üs2t1e2
üst1ég
üs1té
üs2t1ék
üs2tél
üs2t1é2r.
üs2t1é1re
üs2t1érm
üs2t1il
üs1ti
üs2tim
üs2tir
üs2t1is
üs2tí
üs2t1o2
üs2t1ó2
üs2t1ör
üs1tö
üs2t1ö2v
üs2t1őr
üs1tő
üs2t1ős
üs2t1u2
üs2tú
üs1tű2
üs2t1űz
üsz1ál
üs2z
ü1s1zá
ü1s1zi2
ü2sz1iv
üsz1í2v
ü1s1zí
ü2s2z1önt
ü1s1zö
ü2szür
ü1s1zü
üt1ab
ü1ta
üt1aj
1ü2teg
ü1te
1ü2te2m.
üte2m1a2
ü2temb
1ü2te1me
üte2m1el
ü2teméb
üte1mé
üte2mért
üte2min
üte1mi
üté1si2
ü1té
üté2sik
ütés3s
üté2s3z
1üt1kö
ütő1s2p
ü1tő
üt2t1á
üt2t1é2
ütty1e2g
üt2t2y
üt1tye
üt2zi
ü1u
üu2ta
ü1ú
ü1ü
ü1ű
üve2g1a2
ü1ve
üveg3g2
üvezé2r
üve1zé
1ü2ze2m1a2
ü1ze
üze2m1á
üze2meg
üze1me
üze2m1ér1té
üze1mé
üze2m1étk
1ü2ze1mi
üze2m1o
ü2zemt
üze2m1u2
ü2ze1mű
üze2t1a
üze2t1o
üzé2ra
ü1zé
üzé2r1e2l
üzé1re
1üzle2t.
üz1le
1üzletn
2ű.
ű1a
űa2da
űa2dó
űa2g2y
űa2ka
űa2la
űala2g1
űa2lo
űa2na
űa2n2y
űa3nyagoc
űa1nya
űanya1go
űa2or
űa1o
űa2pa
űa2pá
űa2ra
űa2rá
űa2to
űa2ut
űa1u
űa2va
űa2xi
űa2zo
ű1á
űá2bé
űá2ga
űá2gá
űá2g2y
űá2hí
űá2je
űá2lo
űá2po
űá2ra
űá2ri
űá2ro
űá2ru
űá2sí
űá2té
űá2tí
űba2l1
ű1ba
űbé2rel
ű1bé
űbé1re
űb2lo
űb2ró
űcsa1pá2
űc2s
ű1c1sa
űcsa2p1ág
űcs1as
ű2cs1ék
ű1c1sé
űcs1i2pa
ű1c1si
űd1ál
ű1dá
ű2d1e2l
ű1de
ű1d2rá
űd2ro
ű1d2ró
űd1sk
ű1e
űe2c2s
űe2ge
űe2gé
űe2g2y
űe2ké1re
űe1ké
űe2la
űe2le
űe2lé
űe2l1í2
űe2lő
űe2lü
űe2me
űe2pi
űe2po
űe2re
űe2rő
űe2se
űe2sé
űe2ső
űe2te
űe2ti
űe2vé
űe2vő
ű1é
űé2le
űé2lé
űé2nekb
űé1ne
űé2ne1ke
űéne2kest
ű1é2nekes
űé2ne1ké
űé2nekr
űé2pí
űé2te
űfa2j1e2
ű1fa
űf2lo
űfo2g1a2l
ű1fo
űfo1ga
űf2ra
űf2ri
űf2ro
ű2g1ö2lé
ű1gö
ű2g1ö2l2ő.
űgö1lő
űg1ős
ű1gő
űgő2z
űg2ra
űg2rá
űholda2d
ű1ho
űhol1da
űhol2d1a1da
ű1i
űi2do
űi2ga
űi2gé
űi2má
űi2mi
űi2pa
űi2rá
űi2ro
űi2s2z
űi2ta
űi2zé
űi2zo
ű1í
űí2rá
űí2ri
űí2ró
űí2té
űí2ve
űí2zü
űí2zű
űki1a2
ű1ki
űk2la
űk2li
űk2lí
űk2lo
űk2rep
űk1re
ű1k2ré
ű1k2ri
ű1k2ro
ű1k2ró
űk2va
űme2g1
ű1me
űn1al
ű1na
űn1ar
űn1ál
ű1ná
űn1á2r
űn1e2le
ű1ne
űn1elh
űn1e2li
űn1elk
űn1e2lő
űn1elr
űn1elt
űn1eml
űn1e2se
űn1est
űne1t2
ű2n1é2r.
ű1né
ű2n1ér1te
űni2g
ű1ni
ű2n1i1ga
ű2n1in
ű2n1i2p
ű2n1i2r
ű2n1is
űn1me2
űn3n2y
űn1o2k
ű1no
űn1ol
űn1os
űn1ó2r
ű1nó
űn1pr
űn1s2k
űn1s2t
űn1u2n
ű1nu
űn1u2t
ű2n1üs
ű1nü
ű2n1üz
űn1űz
ű1nű
ű1o
űo2dú
űo2ká
űo2ko
űo2la
űo2rá
űo2ro
ű1ó
űó2ce
űó2ra
űó2rá
űó2ri
ű1ö
űö2lő
űö2rö
űö2ve
űö2zö
ű1ő
űő2rö
űp2la
űp2lü
űp2ne
űp2ré
űp2ri
űp2rí
űp2ro
űp2ró
űr1ad
ű1ra
űr1a2l
űr1a2m
űr1a2n
űr1a1u
űr1a2v
űr1áb
ű1rá
űr1ál
űr1ás
űr1áz
űr1eg2y
ű1re
űr1e2l
űre2n
ű2r1e2r
űr1ex
ű2r1é2j
ű1ré
űr1é2l
űr1ép
űrés3szer
űrés2s2z2
űrés1s1ze
űré2s1za
űrés2z
űr2é2s1zá
űré2s1zí
űré2s1zo
űr1fl
űr1id
ű1ri
űri2g
űr1i1ga
ű2r1i2m
űr1int
űr1i2p
űr1ist
ű2r1ír
ű1rí
űr1o2d
ű1ro
ű2r1ol
űr1o2p
űr1or
űros2t1a2
űr1ot
űr1ón
ű1ró
ű2r1ö2l
ű1rö
űr1ör
űrő1f2
ű1rő
űr1pl
űr1pr
űr1p2s
űr1s2p
űr1s2t
űrszt2
űrs2z
űr1t1ra
űr1uj
ű1ru
űr1un
űr1u2t
űr1út
ű1rú
űr1üg
ű1rü
űr1üz
ű2s1aj1tó
ű1sa
űs1ál
ű1sá
űsé2g1el
ű1sé
űsé1ge
ű2s1í2n.
ű1sí
ű2s1í2r
ű1s2ka
ű1s2ká
űso2rad
ű1so
űso1ra
űso2raj
űso2ral
űso2ran
űso2rál
űso1rá
űso2ros2z
űso1ro
űsor1s
űsort2
űs2pe
űs2pi
űs2po
űsp2r
űs2rá
űs3s2z
ű1s2ta
ű1s2tí
ű1s2to
űst2r
űs1t1ro
ű2s1uj
ű1su
ű2s1ü2t
ű1sü
űsze2r1á
űs2z
ű1s1ze
űsze2r1e2le
ű3szerel
űsze1re
űszere2p
űsze2r1e1pé
űsze2r1ült
űsze1rü
űsz2k
űsz2t
űtés3s
ű1té
űtő1a2
ű1tő
űtő1e2
ű1t2ra
ű1t2rá1gá
űt1rá
ű1t2re
űt2ri
ű1t2ro
ű1u
űu2ga
űu2ra
űu2s2z
űu2ta
űu2tá
űu2tu
ű1ú
űú2ri
űú2s2z
űú2ti
űú2to
ű1ü
űü2g2y
űü2lé
űü2rí
űü2te
űü2té
űü2tö
űü2ve
űü2vö
űü2ze
ű1ű
űű2ző
űvé2s1z1a
ű1vé
űvés2z
űv2é2s1z1á
űvé2sz1e2l
űvé1s1ze
űvé2szer
űvé2szint
űvé1s1zi
űvé2s1zo
űvé2s1z1ó2
űv2é2s1zö
űvé2s1zú2
űví2z1
ű1ví
űví1ze2
ű2z1a2b
ű1za
űz1a2d
űz1a2g
űz1ak
űz1a2l
űza1l1e
űz1a2p
űz1a2r
űz1a2s
űz1a2t
űz1a2u
űz1á2g
ű1zá
ű2z1ál
ű2z1árb
ű2z1árj
űz1átl
űz1á1tu
ű2z1ed
ű1ze
ű2z1ef
ű2z1e2l
ű2z1em
ű2z1e2r
űze2s
ű2z1e1se
ű2z1e1sé
ű2z1est
ű2z1es2z
űze2teg
űze1te
űze2tel
ű2z1e2v
ű2z1ég
ű1zé
ű2z1é2l
ű2z1érm
ű2z1ér1té
ű2z1érz
ű2z1és2z
űz1fr
űz1gl
űz1g2r
űzi2g
ű1zi
ű2z1i1ga
űzigaz1
ű2z1i1gé
ű2z1i2m
ű2z1i2r
űz1isk
űz1ism
űz1ist2
űz1i2s2z
ű2z1iz
űz1ín
ű1zí
űz1ír
űz1í2v
űz1kl
űz1kr
űz1o2k
ű1zo
űz1o2l
ű3zom
űz1on
űz1op
űz1or
űz1os
ű2z1óc
ű1zó
ű2z1ó2r
ű2z1ö2kö
ű1zö
űzöl2d1el
űzöl1de
ű2z1ö2v
ű2z1öz
űző1a2
ű1ző
űző1e2
ű2z1ő2r.
űz1ő2re1i
űző1re
ű2z1ő2ri
ű2z1őrk
ű2z1őrm
ű2z1őrn
ű2z1ő2rö
ű2z1őrr
ű2z1őrs
ű2z1őrt
ű2z1őrz
űz1p2r
ű1z3sa
űz2s
ű2zsám
ű1z1sá
ű2z3sár
ű2z3sáv
ű2z3ser
ű1z1se
ű2z3sé
ű2z3só
ű2z3sö
űz3s2p
ű2z3su1ga
ű1z1su
ű1z3sü
ű2z3sű
űz3s2z
űz1t2r
ű2z1ug
ű1zu
űz1úr
ű1zú
űz1út
ű2z1ü2g
ű1zü
ű2z1ül
ű2z1ünn
ű2z1üt
űz3z2s
2v.
1va
vaa2d
va1a
vaát1
va1á
vaá2ta2
2v1abl
va1b1ra
v1abs
vacs1a1la
v2acsal
vac2s
va1c1sa
va2cs1an
va2csap
va2csál
va1c1sá
va2c1s1ü2
vacs3z
2v1a2dag
va1da
va2d1aj
v1a2da1lé
2v1adap
va2d1as2z
v1a2da1ta
v1a2datb
v1a2dat1k2
v1a2da1to
v1a2datr
va2daz
va2d1ál
va1dá
va2de2g
va1de
va2d1e2l
va2den
va2dep
va2d1e2r
va2d1e2t
va2dél
va1dé
vad1é2te
va2dib
va1di
v2a2d1id
va2d1ir
va2d1i2t
va2d1ír
va1dí
v2a2d1ol
va1do
2vadom
va2dóh
va1dó
va2dó1i
v1a2dój
va2dór
v1a2dó1u
va2d1ör
va1dö
va2d1ő2
va2d1ú
va2dű
va2d1za
vad2z
va2d1zá
va2d1ze
vaé2r
va1é
va2g1as
va1ga
va2gav
va2gás
va1gá
va2g1e2
va2g1é2n
va1gé
vag1g
va2gid
va1gi
va1g1le
va2g1o2ku
va1go
va2go1li
vag1ost
va2g1os2z
va2g1u1ra
va1gu
va2gú
2v1a2gyú
vag2y
va2j1ar
va1ja
va2j1á2c
va1já
va2j1á2ro
v2ajár
va2j1e2g
va1je
va2jí
va2j1ol
va1jo
va2j1ó2s
va1jó
va2jö
va2jü
2va2kad
va1ka
vak1a2dá
va2k1aj
vak1akn
vak1a1pá
v2akap
vak1árn
va1ká
va2k1ás
va2k1át
va2keg
va1ke
va2kem
va2k1ér1té
va1ké
v2a2kis
va1ki
va2k1ír
va1kí
va2k1ó2s
va1kó
v2a1k1rí
vak1t2
2vak1ti
2v1ak1tu
2vakup
va1ku
va2k1út
va1kú
va2lac
va1la
v1a2la1ku
va2la2p.
va2la1pí
va2la1pú
vallás1s
val1lá
2v1amp
2v1a2nal
va1na
va2n1e2g
va1ne
v2ane2m
van1e1me
va2n1es
vané2v
va1né
van1é1ve
van1é1vi
2vang
van3n
va2nol
va1no
va2nó
2v1a2nyá
van2y
2v1a2nyó
va1p2l
va2pos
va1po
va1p2r
2v1a2pu
va2r1ab
va1ra
vara2c
var1a1cé
v2a2r1a2d
var1ajt
v2araj
var1akt
v2arak
va2r1al
va2ran2y
var1a2nya
va2r1a2p
va2r1a2r
va2r1as2s2z
va2r1at
va2r1av
va2rág
va1rá
va2r1ál
2v1a2rán2y
va2r1ászn
varás2z
var1á1ta
va2r1átf
va2r1átm
va2r1á2to
va2r1á2zá
var1ca2
var2cag
var2cal
var2ca2n
varc1c
var2c3ho
varc2h
va2r1es
va1re
va2r1e2t
va2rev
va2rég
va1ré
var1é2k.
var1éks
va2r1é2l
va2ré1p
va2r1é1ré
va2rid
va1ri
va2r1i2ko
va2r1ikr
va2r1ill
va2rim
var1inf
var1ink2
va2r1inv
v2a2r1i2p
var1isk
var1ism
va2rí
var2k1an
var1ka
var1k2b
var1k2j
var1k2ká
var1kl
va2r1okm
va1ro
var1old
va2rop
va2ror
va2rön
va1rö
va2rő
vars2
vars2z2
2vartet2t.
var1te
var2t1i2n
var1ti
var2tor
var1to
var1tó2
var2t1ór
va2r1ut
va1ru
va2r1út
va1rú
va2r1ü2
va2rű
v2as1abl
va1sa
va2sag2
v2a2s1aj
vas1arc
v2asar
vas1as2z
va2s1ábr
va1sá
va2s1ál
va2s1árl
va2se2k
va1se
va2s1e2l
va2sem
va2s1e2r
va2se2t
va2s1é2k.
va1sé
vas1ékk
va2s1ékn
vas1ékt
v2asé2r
va2s1é1re
va2s1ér1té
va2s1érv
vasfélé2v
vas1fé
vasfé1lé
va2sid
va1si
va2s1i2n2a.
vasi1na
va2s1ing
va2s1i2s
va2s1iz
va2s1ön
va1sö
va2ső
vas3s1ze
vas2s2z
vas3s1zi
vas3s1ző
vast2
vas1t1ró
v2astr
va2sus
va1su
va2s1ü2t
va1sü
va2sű
v2asz1e2l
vas2z
va1s1ze
va2szis
va1s1zi
va2t1a2d
va1ta
va2t1aj
va2ta1u
v2a2t1ál
va1tá
va2t1á2ru
v2atár
va2t1e2v
va1te
va2t1ék
va1té
v2a2tél
va2t1ér1te
v2atér
va2t1é2te
v2atét
va2t1id
va1ti
va2tim
vat1inf
v2atin
vat1ing
v2a2t1is
v2a2t1ír
va1tí
vat1mo1
vatmos2z2
va2t1okm
va1to
va2t1old
va2t1ö2v
va1tö
va1tő2
va2t1őr
vatt2
2vatta1ko
vat1ta
v2a2t1ur
va1tu
2v1a2uk
va1u
vau2n
vaza2t1e
va1za
2v1a2zon
va1zo
1vá
váb2baj
váb1ba
váb2b1as
váb2b1e2
váb2b1é
váb2bil
váb1bi
váb2bol
váb1bo
váb2bos
váb2bő
váb2bu
2vábr
vá2c3h
vá2cs1a2p
vác2s
vá1c1sa
vá2c1se
vá2cs1é2k
vá1c1sé
vá2csi2p
vá1c1si
vá2cs1is
vá2c1ső
vá2c1sú
vá2c1sü
vács3z
vá2d1al
vá1da
vá2d1a2n
vá1de2
vá2des
vá1dé2
vá2dén
vá2dik
vá1di
vá2d1ir
vá2d1or
vá1do
vá2dö
vá2dü
2v1á2gaz
vá1ga
2v1ágr
vá2gú
vá2gyal
vág2y
vá1gya
vá2gyan
vá2gyö
vá2győ
vá2k1e
vá2k1ü
vá2laj
vá1la
v2á2l1a2l
vála2n
vá2l1an2y
vá2l1ap
vá2lar
vál2a2szin
válas2z
vála1s1zi
vá2l1á2r
vá1lá
vá2l1át
vá2l1e2
vá2lél
vá1lé
vá2li2d
vá1li
vá2lin
vá2lir
vá2l1ism
2v1állás
vál1lá
vál2l1e2
2v1állom
vál1lo
2v1ál1ló
vál2l1ö2
vá2lú
vá2lü
vá1ma2
vá2m1ad
vá2m1aj
vá2m1ak
vá2m1al
vá2m1as
vá2ma1u
vá2m1á2
vám1b2
vá2m1e2
vá2m1é2r.
vá1mé
vá2m1ér1té
vá2mil
vá1mi
vá2m1in
vá2m1ir
vá2mis
vá2mí
vá2m1or
vá1mo
vá2mö
vá2mő
vá1mu2
vá2m1ut
vá2mü
vá2naj
vá1na
vána2n
ván1an2y
vá2n1e
vá2nis
vá1ni
váns3s
vá2n1ú
vá2nü
ványa2d
ván2y
vá1nya
vá2nyaj
vá2ny1a2l
ványa2n
vá2n2yan2y
vá2ny1ap
vá2nyar
vá2ny1as
vá2nya1u
vá2nyaz
vá2ny1e2
vá2ny1é2r.
vá1nyé
vá2nyér1d2
vá2nyérr
vá2nyérv
vá2nyérz
vá2ny1ing
vá1nyi
vá2nyis
vány1í2r
vá1nyí
vá2ny1ó2
vá2nyö
vá2po
vá2r1a2da1to
vá1ra
vára1da
vá2ra1dá
vá2r1a2dó
vá2r1a2du
vára2l
vá2r1a1la
vár1a1le
vá2raml
vára2n
vá2r1an2y
vá2r1ar
vá2r1as2s2z
vá2r1a2s2z
vá2rá2g
vá1rá
vá2r1ál
várd2
vá2r1e2
vár1isk
vá1ri
vár1ism
vár1ist
vá2rí
vár1old
vá1ro
vá2r1oml
vá2r1ont
váro2s2z
vár1oszt
vá2r1ó2né
vá1ró
vá2rö
vá2rő
várs2
vár1sp
vár1sr
vár2t1e2s
vár1te
vár2t1é2r.
vár1té
2v1á2r2u.
vá1ru
vá2ruh
vá2r1u2r
vár1us2z
v1á2rus
vá2r1ut
vá1rú2
vá2rús
vá2rü
vá2s1a2d
vá1sa
vá2sam
vá2saz
vásá2r1a2d
vá1sá
vásá1ra
vá2s1e
vási2k
vá1si
vás1i1ko
vá2sis
vá2sí2r
vá1sí
váskés2z1
vás1ké
vá1só2
vást2
vás1tr
vá2s1ü
vá2sű
vá2sza2n
vás2z
vá1s1za
vá2s3zav
vá2s1z1e
vá2szin
vá1s1zi
vá1ta2
vá2t1al
2v1átd
2v1á2t1e2r
vá1te
2v1át1fe
vá2t1ir
vá1ti
2v1á2tí
v1át1lé
2v1átm
2v1á2t1ö
2v1átp
2v1á2tü
2v1átv
vá2z1alj
vá1za
vá2z1alt
vá2z1a2tom
váza1to
vá2z1a2v
vá2z1e2
vázi1s2
vá1zi
vá2zi2z
vá2zí
vá2zos
vá1zo
vá2zö
vá2ző
vá2z3sa
váz2s
vá2z3sá
vá2z3se
vá2z3sé
vá2z3sö
vá2z3su
váz3s2z
vá2zü
vb2lo
vb2ra
vcsőé2ne
vc2s
v1c1ső
vcső1é
vd2rá
1ve
vea2g
ve1a
vea2l
vea2n
vea2r
veá2l
ve1á
2ve1ce
ve2cet
ve1cl
ve2cseg
vec2s
ve1c1se
2v1e2dén2y
ve1dé
ve2gab
ve1ga
ve2gac
ve2gar
ve2g1as
vega2z
ve2g1á2
ve2g1eg
ve1ge
ve2g1e2kéh
vege1ké
ve2g1ell
vege2lő1ké
vege1lő
ve2g1elr
ve2g1e2r
ve2g1esem
vege1se
ve2g1es2z
ve2g1e2tet
vege1te
ve2g1ék
ve1gé
ve2g1él
ve2g1ép
ve2g1é1ré
ve2g1ér1te
ve2g1érv
vegés3s
ve2gé2s2z
vegg2
veg1gr
ve2gid
ve1gi
ve2gik
ve2gim
ve2gí
ve2gol
ve1go
ve2gor
ve2g1ó2
ve2g1ö2
ve2g1u
ve2gú
ve2gyelem
veg2y
ve1gye
vegye1le
ve2gyemb
ve2gyez
2v1egyl
2vegyv
ve2k1ak
ve1ka
2v1e2k2e.
ve1ke
2v1e2ke1i
2vekéb
ve1ké
ve2ké1bő
2v1e2kék
2v1e2kés
ve1k1lu
2ve1la
2v1e2lef
ve1le
2v1e2lemz
vele2t1e2l
vele1te
2v1e2lég
ve1lé
velés3s
2v1elf
v1elg2y
2v1el1ha
2v1elhel
vel1he
ve2lis
ve1li
2v1e2l1í2
v1el1já
2v1elm
ve2l1os
ve1lo
2v1e2lői2rá
ve1lő
velő1i
2v1e2lő1í
2velőkés
velő1ké
2v1előleg
velő1le
2v1elr
2v1el1sa
2v1el1s1zá
vels2z
2v1el1s1zo
2v1el1tá
2v1elter
vel1te
2v1eltér
vel1té
2v1el1to
2v1el2v.
2v1el1vá
2v1elvek
vel1ve
ve2lyö
vel2y
ve2lyu
2v1elz
2v1em1bó
2v1e2mel
ve1me
2v1eml
2v1e2mu
ve2n1ad
ve1na
ve2na1u
ve2n1á
ve2n1e2g
ve1ne
ve2n1e2l
ve2n1es2z
v2enes
ve2n1é2vi
ve1né
ven1f2
ven1g2
ven3k2
ve2n1o
ve2n1ó2
ve2nö
ven1tü2
ven2t1ül
ven1ü1lé
ve1nü
ve2nyö
ven2y
2v1enz
veó2r
ve1ó
ve2ör
ve1ö
ve2pe
ve1p2r
ve2rab
ve1ra
ve2r1a2c
ve2r1a2d
ve2r1a2g
ve2ra1já
vera2l
vera2n
ve2r1a2r
ve2r1a1u
ve2raz
ve2r1ág
ve1rá
ve2r1ál
2v1er1dő
ver1eg2y
ve1re
ve2r1e2h
ve2r1e2k2e.
vere1ke
ve2rekl
ve2r1e2l
ve2r1eng
ve2r1er
ve2r1e2ső
ve2r1es2z
ve2r1é2l
ve1ré
ver1g2
ve2r1i2ga
ve1ri
ve2r1ill
ve2rim
ve2r1inc
ve2rind
ve2r1inf
ve2r1ing
ver1in1te
verint2
ver1i1pa
ve2ris
ve2r1ol
ve1ro
ve2ror
ve2ró
ver1ó2r
ve2rö
ver1p2
ver2s1al
ver1sa
ver2sár
ver1sá
versé2g
ver1sé
ver2s1é1gé
ver2s1ég3g
ver2s1égn
ver2s1égt
ver2sir
ver1si
ver2s1í2
ver2só
vers3s
ver2s1üt
ver1sü
vers3zár
vers2z
ver1s1zá
vers3zen
ver1s1ze
ver1t1ra
ve2rur
ve1ru
ve2r1ut
ve2rút
ve1rú
ve2r1ü2g
ve1rü
ve2sa2n
ve1sa
ve2sas
ve2s1ál
ve1sá
ve2s1e2ké1tő
ve1se
vese1ké
veset2
2v1e2sett
ve2sip
ve1si
ve2sis
ve2s1íz
ve1sí
ve1s1ká
2v1e2ső
ves3s1za
ves2s2z
ves3szer
ves1s1ze
vest2
ve2s1u2
2v1e2sz2e.
ves2z
ve1s1ze
2v1e2szekn
2v1eszm
2v1e2s1zű
veta2l
ve1ta
ve2tas
ve2tál
ve1tá
ve2t1á2r
vet1eg2y
ve1te
ve2t1e2lől
vete1lő
ve2tég
ve1té
ve2t1é2k
ve2ti2d
ve1ti
2v1e2ti1ka
v1e2tim
ve2t1ing
ve2t1ol
ve1to
ve2t1ö2l
ve1tö
ve2töv
vető1é2
ve1tő
ve2tur
ve1tu
ve2t1ut
ve2t1ű2z
ve1tű
ve2vet
ve1ve
2v1e2vo
vezőe2r
ve1ző
vező1e
vezőkés2z1
vező1ké
vező2sorr
vező1so
vezős1s
2v1ezr
1vé
véá2g
vé1á
2v1ébr
vé2dak
vé1da
véda2l
vé2d1as
vé2dá
vé2d1emb
vé1de
vé2dos
vé1do
vé2dóv
vé1dó
vé2dö
vé2dú
vé2d2z
vé1f2r
vé1ga2
vé2gab
vé2gak
vé2gal
vé2g1á2
vé2g1eg
vé1ge
vé2g1e2le
vé2g1elg
vé2g1elh
vé2g1e1li
vé2g1ell
vé2g1els
vé2g1elv
vég2em
vége2n
vé2g1en2y
vé2g1e1p
vé2g1er
vé2g1esem
vége1se
vé2g1e2se1te
vé2ge1té
vé2ge1tő
vé2g1é2r.
vé1gé
vé2g1é2re2n
végé1re
vé2g1é2ré
vé2g1érr
vég1érv
vég3g
2v1ég1hü
vé2g1id
vé1gi
vé2g1igaz1
végi2g
végi1ga
vé2gim
vé2giz
vé2g1í
végkötés1
vég1kö
végkö1té
vé2gol
vé1go
vé2g1ó2
vé2gö
vé2gú
2v1é2h.
2v1é2h2e.
vé1he
2v1é2hen
2v1é2hes
2v1éhs
vé2ke1i
vé1ke
vé2kít
vé1kí
vé1k2l
vé1k2ré
vé1k2ri
2v1éks
vé2kük
vé1kü
vé2l1a2
vé2láb
vé1lá
vé2lá2l
vé2l1ár
vé2leg
vé1le
vél1ell
vél1els
vél1elt
vé2l1emb
vé2l1e1me
vé2l1e2r
vé2les
vél1ess
vél1est
vé2lev
vé2lez
vé2l1é2k
vé1lé
vé2lin
vé1li
vé2lir
vé2lí
vé2los
vé1lo
vé2lő1i
vé1lő
vé2lős
véltá2v
vél1tá
vé2l1u
vé2lú
vé2lű
véna1p
vé1na
2v1é2nekl
vé1ne
vé2n1emb
vé2ny1eg
vén2y
vé1nye
vénye2l
vény1e1le
vény1els
vé2nyid
vé1nyi
vé2nyis
vé2ny1í
vé2nyú
vényü2l
vé1nyü
vé2ny1ü1lé
vé2ny1ült
2v1é2pí
vé1p2l
vé1p2r
2v1é2pü
vé2ral
vé1ra
vé2r1a2n
vé2rap
vé2rar
vé2raz
vé2r1ár
vé1rá
vé2rát
vér1d2
vé2r1e2l
vé1re
vé2r1eml
véres3s
vér1e1ti
vé2rés
vé1ré
vér1és2z
vér1é1te
vér1ikr
vé1ri
vé2rir
vé2r1o2k
vé1ro
vé2rot
vé2róv
vé1ró
vér1s
vértes1s
vér1te
vér2t1ó2n.
vér1tó
vér2töv
vér1tö
vé1ru2
vé2rug
vé2rut
vé2r1ú
vérü2kü
vé1rü
2v1érze2t.
vér1ze
vé2sak
vé1sa
vé2sal
vé2seg
vé1se
vése2l
vés1e1le
vés1elt
vé2s1er
vé2só
vé1s2pe
vés3szer
vés2s2z2
vés1s1ze
vés2táb
vés1tá
vé2s1ü2v
vé1sü
vés3zav
vés2z
vé1s1za
vész1á2r
v2é1s1zá
vé2szeg
vé1s1ze
vész1ell
vé2sz1ékn
vé1s1zé
vé2s1z1ő2
vész3s
vé2s1zu
vé1s1zú2
vész1úr
vészü2l
vé1s1zü
vész1ü1lé
vét1est
vé1te
2v1é2v.
vé3va
2v1évb
2v1é2ve1i
vé1ve
2v1é2vek
2v1é2ven
2v1é2vé1é
1vé1vé
2v1é2vér
vé2v2i.
vé1vi
2v1évr
2v1évt
2v1é2vü
vfe2l1em
v1fe
vfe1le
vf2ra
vf2re
vf2ri
vf2ro
vge2o
v1ge
vg2ra
vg2rá
vhan1g2a3
v1ha
v3hang
vhez1
v1he
1vi
vi2a1a
vi1a
vi2ab
vi2ac
vi2a1é
vi2ag
via2p
via2szal
vias2z
via1s1za
via2sz1ál
via1s1zá
via2sz1árn
via2sz1em
via1s1ze
via1s1zé2
via2szél
via2s1zö
viá1ra2
vi1á
viá2r1ad
vic2c1a
2vic1ké
vi2csag
vic2s
vi1c1sa
vi2csal
vi2cs1an
vi2cs1as
vi2csál
vi1c1sá
vi2cs1e2l
vi1c1se
vi2cs1é2r.
vi1c1sé
vi2csér1té
vi1c1si2
vi2csim
vi2csip
vi2c1s1ö2
vi2c1sú
vics3z
vi2deg
vi1de
vi2d1es2z
2v1i2déz
vi1dé
2v1i2dő
vi2d2z
vi2ew
vi1e
2v1i2gaz
vi1ga
2v1i2gén
vi1gé
vi1ka2
vi2k1ag
vi2k1el
vi1ke
vi2kon
vi1ko
vi2l1ék
vi1lé
villa1s
vil1la
villas2z2
2v1il1lu
vi2má
2v1imm
vina1t2
vi1na
2vind
vi2n1emb
vi1ne
vi2n1ó
vin2tess
vin1te
vin2tes2z
2vin1té
vin2tos
vin1to
2v1i2on
vi1o
2vi1pa
vi1p2s
2v1i2ra2t.
vi1ra
2v1i2ratot
vira1to
2v1i2rato2z.
2v1i2rod
vi1ro
vi1sa2
vi2sal
vi2s1a1u
vi2s1á2g
vi1sá
vi2ser
vi1se
vi2s1ék
vi1sé
vi2sim
vi1si
vi2s1is
vi2siz
vi2sö
vi2szár
vis2z
vi1s1zá
vi2szok
vi1s1zo
vi1t2a
vi3ta1d2
vitakés2z1
vit2akés
vita1ké
2vital
vite1lő2
vi1te
2vitn
vi2t1ú
vi2zeg
vi1ze
vi2z1ell
vi2z1é2l
vi1zé
vi2zin
vi1zi
vi2zok
vi1zo
vi2zol
vi2zom
vi2z1os
vi2z1sá
viz2s
viz3s2z
vi2zud
vi1zu
vi2zú
1ví
ví1di2
ví2ge
ví2n2y
ví2nyenc1
ví1nye
2v1í2ra
2v1í2rá
2v1í2ró
2vítéle2t.
v1í2tél
ví1té
víté1le
v2í3téletb
2vítéle1te
2v1ívb
vívókés2z1
ví1vó
vív2ókés
vívó1ké
ví2z1a2
ví2zá
ví2zel
ví1ze
víz1e2le
víz1elf
víz1elh
víz1ell
víz1eln
víz1e2lő
víz1elp
víz1els
víz1elt
víz1elv
víze2m
víz1emb
víz1e1me
ví2z1e1mé
ví2zék
ví1zé
ví2zés
ví2z1ing
ví1zi
vízi1sk
ví2z1ist2
ví2z1is2z
ví2zí
ví2z1o
ví2z1ó2
ví2zö
ví2z1ő2
ví2z3s2
ví2zú
ví1zü2
2v1í2zű
víz3z
vjára2t1út
v1já
vjá1ra
vjára1tú
vje2gya
v1je
vjeg2y
vje2tel
vje1te
vje2tu
vkia2l
v1ki
vki1a
vki1á2
vk2li
vk2ló
vk2lu
vk2ri
vk2rí
vk2ró
vla2te
v1la
vle1í2
v1le
vle1ü2
vme2g1
v1me
vna2p1in
v1na
vna1pi
1vo
vo2il
vo1i
vo2ji
vo2kí
2v1okm
voks3s
2v1ok1ta
vola1t
vo1la
2v1ol1dá
2v1old2ó.
vol1dó
vol2t1a2d
vol1ta
vo1me2
vomec3
vona2l1e2
vo1na
vona2t1e
vonás1s
vo1ná
voná2s3z
vo2od
vo1o
vo2pe
vo2r1a2
vo2r1in
vo1ri
2v1orm
vo2s1as
vo1sa
vo2s1av
vo2s1e
vos3s2
vo2s1ú
vo2sü
vo2uc
vo1u
vo2vá
vo2xi
1vó
vóa2k
vó1a
vóá2g
vó1á
vóá2r
vócsa2p1á2
v2ó1c1sa
vóc2s
vó1d2re
vó1f2r
vó1k2l
2v1ónn
vóó2r
vó1ó
vó1p2l
vó1p2r
vó2ran
vó1ra
vó2rák
vó1rá
vó2rán
vó2rát
vó2s1akk
vó1sa
vó2s1a2l
vó2seg
vó1se
vó2s1iv
vó1si
vó2sol
vó1so
vó2s1orr
v2ósor
vó2só
vó2sö
vó2ső
vó1s1pi
vós3s
v2ós1t2r
vó1s2z2
vósza2k1
v2ó1s1za
vó2s3zár
v2ó1s1zá
v2ó2s3zen
vó1s1ze
v2óta1t2
vó1ta
vó1t2r
vóváro1s1u
vó1vá
vóvá1ro
1vö
vö2bö
vö2dém
vö1dé
2v1ö2ko
völ2gya
völg2y
2v1ö2rök
vö1rö
2v1ötl
vö2ve
vö2vé
2v1özön
vö1zö
vöző1e3
vö1ző
vö2z2s
1vő
vőa2n
vő1a
vőá2g
vő1á
vőe2l
vő1e
vőe2r
vőé2j
vő1é
vőé2n
vő1fl
vő1f2r
vői3de1jű
vő1i
vő1i2dej
vői1de
vő1kl
vőo2l
vő1o
vő1pl
vő1pr
v1őrm
v1ő2rü
vő1s2p
vő1s1ta
vő1st2r
vő1t2r
vőü2l
vő1ü
vő2zi
vp2la
vp2ra
vp2re
vp2ré
vp2ri
vp2ro
vp2ró
vp2s2z
vs2ká
vs2ki
vs2ko
vs2lá
vs2pe
vs2ta
vs2ti
vs2tí
vs2to
vst2r
vs2tú
vsz2p
vs2z
v1sz2t
vta2n1á2s
v1ta
vta1ná
vta2n1á2to
vt2anát
vta2n1ó2
vtá2raj
v1tá
vtá1ra
vtá2r1ass
vtá2ris
vtá1ri
vtá1ró2
vtá2r1ór
vtelés1s
v1te
vte1lé
vt2ra
vt2ré
vt2ri
vt2ro
1vu
vu2bo
vu2mal
vu1ma
vu2man
vu2m1e2
vu2mis
vu1mi
vu2mö
2v1u2ni
vu2ra
vu2ru
2v1u2tak
vu1ta
2v1u2tas
2v1u2tat
vu2tá
v1utc
v1u2tó
1vú
vú1fr
v1újd
v1ú2jí
v1újs
vú2r1ad
vú1ra
vú2re
vú1ré2
vú2rén
vú2rö
vú2s1zó
vús2z
v1útn
1vü
vü2g2y
vü2kü
vü2lá
2v1üld
2v1ünn
vü1pr
vü2rí
vü1st
vü1s2z2
vü2te
vü2té
vü2zé
1vű
2v1űrl
vű2ző
vvágy1ób
v1vá
vvág2y
vvá1gyó
vverés3s
v1ve
vve1ré
vzá2r1ó2ra
v1zá
vzá1ró
2w.
1wa
wa2i1i
wa1i
wa2le
war2d1i
wa2re
wa1s2h
wa1ye
wa1yé
1wá
1we
we2b1a2
we2b1á2
we2b1e1le
we1be
we2b1é
we2bir
we1bi
we2b1o
we2b1ú
we2bü
we2ed
we1e
we2ek
we2ig
we1i
we2is
we2le
we2ör
we1ö
1wé
whi2t
w1hi
1wi
wi2c2h
wi2c2z
1wí
1wo
wo2od
wo1o
1wó
1wö
1wő
wri2t
w1ri
ws1to2
1wu
1wú
1wü
1wű
wyo2m1
2x.
1xa
x1a2da
xa2dá
xaé2d
xa1é
xa1fl
xa1k2l
2x1akt
2x1a2la
2x1alg
2x1alj
2x1alk
xa2na
x1a2n2y
x1a2rá
xa2ri
xa2vi
1xá
2x1ábr
xá2g2y
2x1áll
xá2rak
xá1ra
2x1á2ram
xá2ras
2x1á2ro
2x1árr
2x1á2ru
2x1átj
2x1átr
2x1átv
xba2l1
x1ba
xb2la
xb2lo
xb2ra
xd2ró
1xe
x1e2g2y
2x1e2k2e.
xe1ke
xe2l1a
xe2l1á
2x1e2lekt
xe1le
xe2lel
xe2l1emb
2x1e2lemr
xe2l1es2z
xe2l1in
xe1li
xe2l1o
2x1emel
xe1me
2x1eml
2x1e2rő
2x1e2sé
2x1e2zü
1xé
2x1éhs
xé2pí
2x1é2r.
2x1ér1te
2x1ér1té
2x1ér1tő
2x1érz
2x1é2ve1i
xé1ve
xf2re
xha2u
x1ha
1xi
xia2n
xi1a
xi2av
xi2d1a2l
xi1da
2xi1dá
xi2dás
2xi2de
2xi1dé
2xid1gá
xi2d1i2
2xid1já
2xid1jé
2xidl
2xidm
xi2dol
xi1do
xi2dö
2xi2dő
xi2d1őz
xi1d2ro
2xid1ró
2xids
2xi1du
2xidv
2xi2d2z
xi2el
xi1e
2x1i2ga
xig2én1n
xi1gé
2xilc
2x1ill
xi1na2
xi2n1an
2x1ind
xi1ne2
xi2n1et
xi2n1i2
xi2nö
xi2on
xi1o
xi2óc
xi1ó
xi2ó1e
xi2óg
xi2ó1mé
xi2ó1o
xi2ó1ö
xi2óp
xió2rá
2x1i2rá
2x1i2ro
xi2sad
xi1sa
xi2sal
xi1se2
xi2s1el
xi2s1es
xi1s2ká
xis1p
xis3s
xi2s1ü
xi2t1e2g
xi1te
x2i2t1e2r
xi2t1é
xi2t1i2
xi2t1ü2
1xí
xí2ja
xí2já
xí2jo
xí2ju
x1í2rá
xí2ró
2x1ívn
2x1í2vü
2x1ívv
1xo
2x1old
xo2n1a1i
xo1na
xo2n1al
xo2n1e
xo2pe
xo1p2l
2x1os2z
1xó
1xö
xö2dé
xö2rö
xö2zö
1xő
xő2sé
xp2la
xpor2t1a2
x1po
xpor2t1á2r
xpor1tá
xpor2t1e2
xpor2t1érd
xpor1té
xpor2t1ü2
xp2ri
x1p2ro
x1p2ró
xs2ká
xs2pe
xs2ta
xst2r
x2t1e2d
x1te
xti2la
x1ti
xti2lá
xti2le2g
xti1le
xti2lin
xti1li
xti2lis
xti2l1o
xt1ört
x1tö
1xu
xu2sad
xu1sa
xu2sal
xu2s1a2n
xu2s1e
xu2s1ér1té
xu1sé
xu1si2
xu2sil
xu2sim
xu2sin
xu2sit
xu2s1ol
xu1so
xu2s1ó2
xu2s1ö2
xus3s
xust2
xus1tr
xu2su2s
xu1su
xu1sú2
xu2s1út
xu2sü
xu2s1ű
xu2s3z
xu2ta
1xú
1xü
xü2lé
xü2ve
xü2ze
1xű
2y.
yaa2d
ya1a
y1ab1la
y1a2bon
ya1bo
y1ab1ra
y2a1b1ri
y1abs
ya2cé2l.
ya1cé
ya2da1ko
ya1da
y1a2da1lé
y1adap
y1a2da1ta
y1a2datb
ya2da1ti
y1a2da1to
ya2da1tu
yadé2ki
ya1dé
ya2d1é2s
ya2dév
y1ad1mi
ya2dóan
ya1dó
yadó1a
ya2dó1bó
ya2dó1é
y1a2dó1i
y1a2dój
ya2dó1ké
ya2dókt
y1a2dóm
ya2dó1ná
y1a2dór
y1a2dós
y1a2dó1u
yae2r
ya1e
ya2e1ro
yae2t
yaé2r
ya1é
y1aff
ya1f2r
ya2g1a2d
ya1ga
ya2g1ag
ya2g1am
ya2gan
y1aganc
ya2g1a2s
ya2g1atl
ya2g1a1to
ya2ga1u
y2aga2z
yag1a1zo
ya2g1áll
ya1gá
ya2g1á2rá
ya2g1árb
ya2g1á2ré
ya2g1árh
ya2g1á2ri
ya2g1árj
ya2g1árk
ya2g1á2ro
yag1árr2a.
ya2gárr
yagár1ra
ya2g1ár1s2
ya2g1árt
ya2gás
yag1á1t1a2
ya2g1átf
ya2g1átl
ya2g1átr
yag1d2
ya2gec
ya1ge
ya2g1e2l
ya2g1e2s
ya2g1é2g
ya1gé
ya2gék
y1aggr
yag3gyú
yag2g2y
ya2gid
ya1gi
ya2gim
ya2g1i1o
yag1i2s2z
ya2giz
ya2gí
ya2g1os2z
ya1go
ya2g1ó2r
ya1gó
ya2gő
yag1s
ya2gur
ya1gu
ya2g1ut
y1ag2y.
yag2y
y1a2gyat
ya1gya
y1agyb
y1agyf
y1agyk
y1a2gyon
ya1gyo
y1agyr
y1a2jak
ya1ja
y1a2ján
ya1já
y1ajk
y1ajt
y1aka1dá
ya1ka
ya2k1áll
ya1ká
ya2k1átk
ya2k1átm
yak1elm
ya1ke
yak1elt
yak1ékn
ya1ké
ya2k1é2r.
ya2k1é1ri
yak1i2zo
y2akiz
ya1ki
ya1k1lu
y1akn2a.
yak1na
y1aknák
yak1ná
ya1k1né
y1a2ko2l.
ya1ko
ya2k1o1la
ya2k1old
ya2k1ón
ya1kó
y1a2kós
ya2k1örv
ya1kö
ya1k2rém
y2ak1ré
y2a1k1ri
y2a1k1rí
y1ak2t.
y1akták
yak1tá
y1aktb
y1aktiv
yak1ti
y1aktj
y1aktot
yak1to
y1ak1tu
yala2g1ú
ya1la
ya2l1ag2y
yal1ajk
ya2lakb
y1a2lakj
y1a2lakk
y1a2lakok
yala1ko
y1a2l2akom
y1a2lakot
ya2lakt2
y1a2la1ku
y1alakz
ya2l1a2l
y1a2lan2y
ya2la1pa
y1a2la1pí
ya2lap1já
y1a2la1pú
yala2te
ya2l1á1ga
ya1lá
ya2l1ál
yalás1s
y1a2lá1té
ya2l1é2ne
y2alén
ya1lé
ya2lif
ya1li
yal1i2ko
y2alik
ya2l1inv
y1alja1i
yal1ja
y1al1ji
ya2lor
ya1lo
yalókés2z1
ya1ló
yal2ókés
yaló1ké
ya2l1ó1rá
ya2l1ő2
y2al1ta
yal1t2r
y1al1tú
ya2lü
ya2lű
y1a2malg
ya1ma
ya2m1an2y
yam1a2rár
yama1rá
yama2tal
yama1ta
ya2m1árb
ya1má
ya2m1árn
yamászás1
yamás2z
yamá1s1zá
ya2m1á2to
yam1b2
yam1emb
ya1me
yam1esés
yame1sé
yami2k
ya1mi
ya2m1i1ko
ya2mind
ya2m1i2o
ya2m1is
ya2m1os2z
y2amos
ya1mo
ya2m1ó2r
ya1mó
ya2mö
ya2mő
ya2mü
y1a2nal
ya1na
ya2nan
ya2nar
yan1arr
ya2nat
yan1att
ya2n3e.
ya1ne
ya2n1e2g
ya2nek
ya2ner
ya2n1et
ya2ne2z
y1a2ném
ya1né
y1ang
ya2nid
ya1ni
ya2n1in
ya2n1is
ya2nit
y1a2niz
y1an2n2y
yano2d
ya1no
yan1o1da
yan1onn
ya2n2ő.
ya1nő
ya2nyas
yan2y
ya1nya
y1anyj
y1a2nyó
y1a2nyuk
ya1nyu
yaó2r
ya1ó
y1a2p2a.
ya1pa
y1a2pa1i
y1a2pas
y1a2páb
ya1pá
y1a2pád
y1a2pá1é
y1a2pá1i
y1a2pák
y1a2pám
y1a2pá1ra
y1a2pá1ró
y1a2pá2t.
y1a2pá1to
y1a2pá1tó
y1a2páv
y1a2p2i.
ya1pi
y1apjáh
yap1já
y1apjá1i
y1ap1ju
ya1p2l
ya2post
ya1po
yapo2tá
y1a2p2ó.
ya1pó
y1a2pó1é
y1a2póh
y1a2pó1i
y1a2pój
ya2pó1ké
ya2pó1na
y1a2pós
ya2pó2t.
ya2pó1tó
y1a2póv
y1app
ya1p1re
ya1p2ri
y1ap1rí
y2a1p1ro
ya2r1a2dá
y2arad
ya1ra
ya2r1a2dó
ya2r1a2du
ya2ras2z
ya2ránn
ya1rá
y1a2rán2y
ya2r1átv
y1ar2c.
y1ar1ca
y1ar1cá
y1arcb
y1arcc
y1arc2h
y1arck
y1arcn
y1ar1co
y1arcr
y1ar1cu
y1ar1cú
1yar2d.
1yardn
3yardom
yar1do
1yardos
yar1d1rá
yar1ell
ya1re
ya2ro1ma
ya1ro
ya2ror
yar1ó2rá
ya1ró
yar1ó1vá
ya2róz
yar1s2
yas1alj
y2asal
ya1sa
y2a2sap
ya2s1as
yaság1g
ya1sá
ya1sl
ya1s1ne
ya1s2pi
ya1s2po
ya1s2rá
yast2
y2a1s2ta
y2a1s1to
y2a1str
ya1s1vi
yasz2tár
yas2z
yasz1tá
y1a2tád
ya1tá
yaté2k1á1t1a2
ya1té
yaté1ká
y1atk2a.
yat1ka
y1at1ká
ya2tomh
ya1to
y1a2tomm
y1a2to1mo
yat2rág
yat1rá
y2a1t1ré
ya1tróf
yat1ró
y1attr
y1a2t2y
y1a2uk
ya1u
y1a2u1lá
y2au2tá
y1a2u1to
y1a2u1tó
yautói2ko
y1autó1i
yaü2t
ya1ü
yaü2z
y1a2vat
ya1va
y1a2zon
ya1zo
y1a2zúr
ya1zú
y1á2bé
y1ábr
yá2ga
yá2gá
yá2gé
yá2gi
yá2go
yá2gu
yá2gú
yá2g2y
yá2jal
yá1ja
yá2j1e
yá2j1ö
yá2jő
yá1ka2
yá2k1an
yá2k1á
yá2k1e
yá2kü
yálas3s
yá1la
yá2lál
yá1lá
y1ál1do
yá2l1e
y1állam
yál1la
y1állat
y1állás
yál1lá
y1ál1lí
y1ál1lo
y1ál1ló
y1állv
yá2lü
yá1ma2
yá2m1al
yá2m1an
yá2m1ap
yá2m1as
yá1mi2
yá2m1in
yá2mü
yá2n1e
yá2nék
yá1né
yán3n
yá2nö
yánt2
yán1tr
yá2nü
yá1n2y2
y1á2po
yá2r1a2l
yá1ra
y1á2raml
y1á2ra1mo
yára2n
yár1an2y
yá2rap
yá2rar
yá2r1ass
yá2r1av
yá2raz
y1ára1zá
yár1a1zo
yá2r1ál
yá1rá
y1árbev
yár1be
yár1d2
yá2r1e2
y1árem
yá2réj
yá1ré
y1á2ri1á
yá1ri
yá2rim
yá2r1is
y1ár1nya
yárn2y
yá1ró2
yá2rór
yá2rö
yá2rő
y1ár1pá
yár2sé
y2ár2t.
y1ártám
yár1tá
yár1tr
y1á2ruh
yá1ru
y1á2rur
yá2rú2
y1ár2ú.
yá2rü
y1ár1va
y1árver
yár1ve
yá2sal
yá1sa
yá2sas
yá2s1á2g
yá1sá
yá2s1á2rá
yá2s1árt2
y1á2sás
yá2se
yás1ká2
y1á2só
yá2sö
yá2szab
yás2z
yá1s1za
yá2szag
yá2szal
yá2sza2s
yá2sz1ál
yá1s1zá
yá2s1ze
yász1el
yász1em
yás3zen
yá2szét
yá1s1zé
yá2szév
yá2szim
yá1s1zi
yá2szin
yá2szis
yá2szit
yász1ó2d
yá1s1zó
yász1ó2r
yá2s1z1ö2
yá2s1ző
yá2s1z1ü2
y1á2ta
yát1a2l
y1á2tá
y1átb
y1á2t1e2
y1á2té
y1átf
y1áth
y1á2t1i2
y1átk
y1átlag
yát1la
y1át1lá
y1át1lé
y1átm
y1átn
y1á2t1ö
y1átp
y1átr
y1áts
y1átt
y1á2tü
y1átv
ybe1á2
y1be
yb2la
yb2le
yb2lé
yb2li
yb2lo
yb2lú
yb2ra
yb2ri
yb2ro
yb2ró
ycsa2p1á2g
yc2s
y1c1sa
ycsa1pá
ycső1é2
y1c1ső
yc2vi
yd2be
y2desd
y1de
yd2ni
y1d2ra
y1d2rá
yd2ro
yd2ró
y1d2ru
yea2v
ye1a
yeá2r
ye1á
ye1bl
y1ecset
yec2s
ye1c1se
ye2d1á
ye2deg
ye1de
ye2d1es2z
ye2dol
ye1do
ye2d1ó2
ye2d1u2
ye2d1ú
ye2d1ü2lő
ye1dü
yed2vér
yed1vé
yee2s
ye1e
y1eff
ye1ga2
ye2g1a1la
ye2gan
ye2g1az
ye2g1á2
ye2g1el
ye1ge
ye2g1e2red
yege1re
ye2g1él
ye1gé
yeg1ér1be
ye2gérb
y1e2gérr
ye2gés2z1
yeg1észn
yeg3g2
ye2gid
ye1gi
ye2gí
ye2gú
ye2g1üg
ye1gü
ye2gyed
yeg2y
ye1gye
y1e2gyen
y1e2gyes
ye2gyet
ye2gyez
y1e2gyé
y1egyh
y1egyl
y1egys
y1e1gyü
y1egyv
yei2g
ye1i
y1ejt
y1e2k2e.
ye1ke
y1e2ke1i
y1e2kek
y1e2ké1é
ye1ké
ye2kéj
y1e2ké2s.
y1e2ké1se
ye1k2ré
y1e2l1a2d
ye1la
y1e2lág
ye1lá
ye2lál
y1elc
y1e2lef
ye1le
ye2le1ge
ye2l1e2h
y1e2lemek
yele1me
y1e2lemez
y1e2le1mű
y1e2l1e2re
ye2légt
ye1lé
ye2l1é2ké
ye2l1é2r.
yelés3s
yelé2s3z
y1elhal
yel1ha
y1elhel
yel1he
y1el1hú
y1e2l1in
ye1li
ye2liv
y1e2l1í2
y1el1já
y1el1ka
y1elnev
yel1ne
y1el1nö
y1eln2y
ye2l1os
ye1lo
y1e2lö
y1e2lőad
ye1lő
yelő1a
y1e2lő1í
ye2lőtt
y1e2lőz
y1el1sa
y1el1ső
y1eltér
yel1té
y1el1to
y1el1tö
yel2vad
yel1va
yel2v1áll
yel1vá
yel2vás
yel2v1eg
yel1ve
yel2v1e2r
yel2ves2s2z
yelve2s2z
yelv1e1s1ze
y1elve1vé
yel2véd
yel1vé
yel2v1ég
yel2vél
yel2v1érz
yel2v1í
yel2vol
yel1vo
ye2mak
ye1ma
ye2m1a2l
ye2ma2p
yema2r
ye2m1at
ye2má2l
ye1má
y1ember
yem1be
y1e2me1lé
ye1me
y1e2melk
ye2m1er
y1e2més
ye1mé
ye1mi2
yem1ing
ye2m1is
ye2mit
yem1i1ta
ye2miz
y1emlék
yem1lé
y1em1lí
y1em1lő
yem1ost
ye1mo
ye2m1ö
yem1p2
yemu2s
ye1mu
ye2m1us2z
ye2n1á2
yen2c1sa
yenc2s
yen2c3ser
yen1c1se
ye2n1elj
ye1ne
ye2n1eln
ye2n1elv
y1e2ner
yenes3s
y2enes
ye2n1é2l
ye1né
yenfé2l1é2v
yen1fé
yenfé1lé
ye2nid
ye1ni
ye2n1i2p
yen1k2
ye2n1o
ye2n1üg
ye1nü
ye1nyá2
yen2y
y1enz
ye2pag
ye1pa
yep1a1lo
ye2p1a1rá
ye2p1áll
ye1pá
ye2pát
ye2p1e2l
ye1pe
ye2p1é2k
ye1pé
y1e2pik
ye1pi
y1e2piz
ye2pos
ye1po
yep2p1e2r
yep1pe
ye1p1ro
yep1t2
y1er1de
yere2ga
ye1re
yere2ge1te
yere1ge
yereg1g
y1e2rej
yere2k1a
yere2ká
yere2k1e2s2z
yere1ke
yere2ko
yere2k1ö2
y1e2rén2y
ye1ré
y1er1ké
y1er1kö
y1ern2y
y1e2ro
ye2rőé2r
ye1rő
yerő1é
ye2rőig
yerő1i
ye2rő1né
ye2rő1rő
ye2rő1sí
ye2rőss
ye2rő1vá
yer2s1a
y1er1s1zé
yers2z
ye2sá
yes1e1gé
ye1se
yes1eg2y
ye2s1ej
yes1e2ké1tő
yese1ké
y1e2semén
yese1mé
ye2s1er
y1e2setb
y1e2se1té
y1e2se1ti
y1e2setk
y1e2setr
y1e2se1tű
y1esél
ye1sé
ye2sip
ye1si
ye2s1í2r
ye1sí
y1eső1á
ye1ső
ye2ső1be
y1e2sőh
ye2ső1je
ye2ső2k.
ye2sőkh
ye2sőkr
y1e2sőn
y1e2sőr
y1e2sős
ye2ső1vé
ye1s2pe
yes2t1a2
y1estés
yes1té
y1estév
y1est1jü
yes2tőr
yes1tő
y1es1tű
y1eszk
yes2z
y1eszm
y1esztét
yesz1té
y1e2s1zű
y1e2tal
ye1ta
yete2g1é2r.
ye1te
yete1gé
y1e2tet
ye2t1ért
ye1té
y1e2tik
ye1ti
y1etn
y1e2tűd
ye1tű
ye2vet
ye1ve
ye2ve1ző
y1e2vé
y1e2vol
ye1vo
y1e2vő
y1e2zer
ye1ze
yeze2t1ék
yeze1té
yező1a2
ye1ző
yé2b1á
yé2b1é2r.
yé1bé
yé2b1i2
yé2bü
yé2des
yé1de
y1égb
y1é2ge
y1é2gé
y1é2gi
y1é2gő
y1égt
y1é2h2e.
yé1he
y1é2hen
y1é2hes
y1é2het
y1éhs
y1é2j.
y1éjb
yé2je1i
yé1je
yé2jen
yé2jért
yé1jé
y1é2ji
y1éjj
y1éjs
y1éjt
yé2kab
yé1ka
yé2kad
yé2k1a1ka
yé2k1a2l
y1é2kat
yé2k1el
yé1ke
yé2ker
yé2k1é2k
yé1ké
yé2k1é2l
yé2k1é2r.
yé2kik
yé1ki
yé2k1ó2
yé2k1ö
yé2kő
yé2kúr
yé1kú
yé2l1á
yé2l2e.
yé1le
yé2les
y1é2let
y1é2lén
yé1lé
yé2lét
yé2li2m
yé1li
yél3l
yé2lős
yé1lő
y1él1sp
y1é2lű
yé1ná2
yé2n1ár
yé2n1e2k2e.
yé1ne
yéne1ke
yé2nel
yé2n1ikr
yé1ni
yé2no
yé2pí
yé2pü
yé1ra2
yé2r1aj
yé2r1ak
yé2ral
yé2r1an
yé2rar
yé2r1á2
y1ér2c3h
yér1d2
y1érdek
yér1de
yé2reg
yé1re
yé2r1e2l
yé2rem
y1ére2m.
yér1e1me
yé2r1enc
yére2n
yér1e2ső
y1é2ret
yé2rev
y1é2rez
y1é2rés
yé1ré
yé2ril
yé1ri
y1é2rin
y1ér1mü
yé2r1os2z
yé1ro
yé1ró2
yé2r1ór
y1é2r2ő.
yé1rő
y1é2rő1i
y1é2rők
y1é2rőt
y1ér1pa
y1érték
yér1té
yé1ru2
yé2rut
y1ér2v.
y1érvén
yér1vé
y1érvv
y1érzet
yér1ze
y1ér1zé
y1ér1ző
y2é2s1z1á
yés2z
yé2sz1emb
yé1s1ze
yé2s1z1o
yé2s1z1ú
yé2t1a
y1é2tel
yé1te
y1é2ter
yé2tén
yé1té
y1ét1ke
y1étl
y1é2v.
y1é2vad
yé1va
y1évb
y1é2v2e.
yé1ve
y1é2vek
y1é2vem
y1é2ven
y1é2ves
y1é2vet
y1évez
y1é2véb
yé1vé
y1é2vén
y1é2vér
y1é2vét
y1é2vév
y1évf
y1évh
y1é2vi
y1évk
y1évm
y1évn
y1évr
y1évs
y1évt
y1é2vü
y1é2vű
y1évv
yfas2
y1fa
yfé2lá
y1fé
yf1gl
yf2la
yf2le
yf2li
yf2lo
yf2ló
y2fö2l.
y1fö
y2f1ő2sö
y1fő
y1f2ra
yf2ri
yf2rí
y1f2ro
yf2rö
yg2le
yg2ló
ygó1g2
y1gó
ygót2
yg2ra
yg2rá
yg2ri
yg2ró
yg2ru
yg2rü
y2h1a2dó
y1ha
yha1p2
yha1t2r
yhá2zal
y1há
yhá1za
y2h1elv
y1he
y1i2bo
y1i2de1á
yi1de
y1i2deg
y1i2den
y1i2de1o
y1i2dé
y1i2dom
yi1do
y1i2dő
y1i2ga
y1i2gá
y1i2géz
yi1gé
y1igm
y1i2har
yi1ha
yi2hat
y1ihl
y1i2ker
yi1ke
yi2k1érn
yi1ké
yi2kont
yi1ko
yi1k2ri
y1i2m2a.
yi1ma
y1i2má
y1i2mit
yi1mi
y1imp
y1i2nas
yi1na
y1inc
y1ind
y1inf
y1in1ga
yi2nic
yi1ni
y1inj
y1ins
y1inv
yi2o1no
yi1o
yi2par
yi1pa
y1i2ram
yi1ra
y1i2ra2t.
y1i2ra1ta
y1i2ra2t1é
y1i2ratn
y1i2ra1to
yi2ratt
y1i2rá
y1i2ri
y1i2rod
yi1ro
y1irr
y1irt
y1ish
y1is1ko
y1ism
y1isp
y1ist
y1i2s1za
yis2z
yi2szel
yi1s1ze
yi2szon
yi1s1zo
y1i2tal
yi1ta
y1i2tat
y1i2var
yi1va
y1i2vás
yi1vá
y1i2vó
y1i2zé
y1izg
y1izm
yi2zom
yi1zo
yí2gé
yí2ja
yí2já
yí2ju
yí2ka
yí2ká
yí2ke
yí2kí
yí2kol
yí1ko
yí2k1ö
yí2la
yí2l1e2
yí2lí
yí2lö
yí2ral
yí1ra
yí2ráb
yí1rá
yí2r1á2s2z
y1í2rog
yí1ro
yí2rő
yítő1a2
yí1tő
yí2vá
yí2ve
yí2vé
yí2vó
yí2vü
yí2vű
yí2zü
yí2zű
yje2gy1á2
y1je
yjeg2y
y2jév
y1jé
yjob2b1o
y1jo
yka1i2
y1ka
yka1k2
yka2ró2ra
yka1ró
yk2bó
y2k1e2lem
y1ke
yke1le
yk1é2jév
y1ké
yké1jé
yk2é2p1el
yké1pe
yk2ho
yki1á2
y1ki
yki1e2
yk1izm
yk2ka
yk2la
yk2le
yk2li
yk2lí
yk2ló
yk2lu
yk2ná
ykó1ro2
y1kó
yk2ra
y1k2rá
y1k2ri
yk2rí
yk2ro
yk2ró
yk2vó
ylal2ta
y1la
yl2a2t1a2n
yla1ta
yl2a2t1é2t
yla1té
yle1i2
y1le
yle1í2
yle2tal
yle1ta
yle2tá
yló1á2
y1ló
ylót2
yma1d2
y1ma
y2ma2t.
ymá2s1ik
y1má
ymá1si
yme2g1é
y1me
ym2ma
yné2vér
y1né
yné1vé
yn2ka
ynk2r
ynőé2n
y1nő
ynő1é
ynő2i1é
ynő1i
yo2be
y1obj
y1o2dú
yo2gal
yo1ga
yo2gál
yo1gá
yo2g1ár
yo2ge
yog3g
yo1gi2
yo2git
yo2g1iv
yo2gí
yo2g1os2z
yo1go
yo2gö
yo2gü
yo2gű
yo1ka2
y1o2k1al
y1o2ká
y1o2k1ir
yo1ki
y1okm
y1o2kol
yo1ko
y1o2kos
y1o2koz
y1ok1ta
yo2l1a2l
yo1la
yola2n
yol1an2y
yo2l1á1ri
yo1lá
yol2cem
yol1ce
yol1ci2
yol2cik
y1ol1dá
y1ol1dó
y1o2lim
yo1li
y1oltás
yol1tá
yol2te2r
yol1te
yo2lú
yo2lü
yol1ví2
yo2m1a2l
yo1ma
yoma2n
yo2m1an2y
yoma2s
yo2m1a1s1zó
yomas2z
yo2maz
yo2m1ál
yo1má
yo2m1árk
yo2m1á2to
yo2m1á2z
yo2m1e
yo2m1ik
yo1mi
yo2mil
yo2m1is
yo2mí
yo2m1ol
yo1mo
yo2mö
yo2mő
yom1p2
yo2n1a2d
yo1na
yo2nag
yo2naj
yon1a2ka
y2onak
yo2n1a2l
yo2n1am
yo2n1a2n
yo2nap
yo2n1a2r
yo2n1a2s
yo2n1a2t
yo2n1a1u
yo2n1ál
yo1ná
yo2n1á1t1a2
yo2n1á1té
yo2n1átj
yon1átk
yo2n1átr
yo2n1áts
yo2n1átv
yo2n1á2z
yon1d2
yo2n1e2
yo2né2d
yo1né
yo2néh
yo2nék
yo2nén
yon1f2
yo2nim
yo1ni
yo2n1is
yon1itt
yo2niz
yo2ní
yon1k2
yon3n
yo2n1ol
yo1no
yo2nop
yo2n1os2z
yo2nö
yo2n1ő2
yon1s2
yont2
yon1tr
yo2n1ü
y1o2pe
y1opt
yo2rad
yo1ra
yo2r1a2k
yo2r1a2p
y1ord
y1or1gi
y1or1rú
yor2sol
yor1so
y1or1só
yors3s
yo2se
yos3s
y1os1to
y1oszl
yos2z
yos3z2s
y1oszt
yo2ut
yo1u
yo2ve
yo2xi
yóá2g
yó1á
yóá2r
yócsa2p1á2
y2ó1c1sa
yóc2s
yó2dák
yó1dá
y1ó2dár
y1ó2dásak
yódá1sa
yó1f2l
yó1g2r
yó2gyár
yóg2y
yó1gyá
yó2gyi
yó2gyú
yóí2v
yó1í
yóo2k
yó1o
yóó2r
yó1ó
yó1p2r
y1ó2rac
yó1ra
y1ó2rad
yó2rag
yó2ra1i
y1ó2ras
y1ó2ráb
yó1rá
y1ó2rád
y1ó2rá1é
y1ó2ráh
y1ó2rá1i
y1ó2ráj
y1ó2rák
y1ó2rám
y1ó2rár
y1ó2rás
y1ó2rát
y1ó2ráv
y1ó2ri
yó2s1aj
yó1sa
yós1c1sa2
yósc2s
yó2sír
yó1sí
yó2s1ol
yó1so
yó2s1orr
y2ósor
yós1ors
yós3s
y2ósü2l
yó1sü
yó2s1ű2
yö2bö
yö2bű
yö2c2s
yö2dém
yö1dé
yö2ka
yö2ká
yö2ke2l
yö1ke
yö2k1ék
yö1ké
yö2ki2d
yö1ki
yö2kí
yö2ko
yök1os
yö2kó
yö2kör
yö1kö
yö2k1öss
yö2ku
yö2lők
yö1lő
y1öltés
yöl1té
y1öml
y1ö2ná
yön2gya
yöng2y
yön2gyá
yön2gyó
yön2gy1öl
y2ön1gyö
y1önk
y1ön1té
y1önz
yö2ra
y1örd
yö2rit
yö1ri
yö2ro
yö2ru
yö2so
y1ös2s2z
y1ösv
y1öszt
yös2z
y1ötl
y1ö2tö2d.
yö1tö
y1ö2tö1de
y1ö2tö1dé
y1ötp
y1ött
y1ötv
yö2ve
yö2vé
yö2vi
y1ö2vö
y1ö2zön
yö1zö
yőa2n
yő1a
yő1bl
yőe2l
yő1e
yőe2r
yő1fr
yő1pr
yő2ra
y1ő2re1i
yő1re
y1őrg
y1őrl
y1őrp
y1őr1s1zi
yőr1s2z2
y1ő2rül
yő1rü
y1őrz
yő2seg
yő1se
yő1st2r
yős2z2
yő2s3zá2r.
yő1s1zá
yős3z2s
yő1szt2
yőzőkés2z1
yő1ző
yőző1ké
ypen1n
y1pe
ype2te
yp2la
yp2le
yp2lé
yp2lu
ypon1té2
y1po
yp2ra
yp2re
yp2ré
yp2ri
yp2rí
yp2ro
yp2ró
y2p1rő
yp2ru
yp2s2z
yrádi2ón
y1rá
yrá1di
yrádi1ó
yrágás1
yrá1gá
yre1a2
y1re
yren2d1ő2
yre1u2
ysa2van
y1sa
ysa1va
yság1g
y1sá
ys2c2h
ysé2g1e2l
y1sé
ysé1ge
ysé2gés
ysé1gé
ys2ho
ys2ka
ys2ká
ys2ki
ys2la
ys2lá
ys2le
ys2lu
ys2mi
ys2na
ys2ni
ys2pe
ys2pi
ys2po
ys2pó
ysp2r
ys2ra
ys2rá
ys2rő
ys2sa
ys2sá
ys2se
y1s2tí
y1s2to
y1s2tó
y1st2r
y1s2tu
y1s2tú
ys2tü
ysza2k1ü2
ys2z
y1s1za
y2sza1ló
y2száld
y1s1zá
yszáraz1
yszá1ra
ysze2ra
y1s1ze
yszer2v1ó2
y3szerv
y1sz2f
y1sz2k
y1sz2l
y1sz2m
yszö2g1el
y1s1zö
y3szö1ge
y1sz2p
y1sz2t
y1sz2v
yta2c
y1ta
yt1a1cé
yta2n1é
yta2n1ó2
ytá2l1é2
y1tá
y2t1ál1lá
y2t1ál1ló
yt2raf
yt1ra
yt2ran
yt2rap
y1t2rá
y1t2re
y1t2ré
y1t2ri
y1t2ro
yt2rón
yt1ró
yt2rö
y1udv
yu2g1á
yu2ge
y1ugr
yu2g2y
yu2hu
y1ujj
yuj2j1a2da
y1ujjad
yuj1ja
yu2kab
yu1ka
yu2kar
yu2k1a1s1zá
yukas2z
yu2k1ác
yu1ká
yu2kál
yuká2s
yuk1á1sá
yu2k1e
yu2k1ö
yu2kő
yuk1t2
yu2kű
y1und
y1u2ni
y1u2no
yu2rad
yu1ra
yu2ra1i
yu2rak
yu2ral
yu2ram
yu2ras
yu2rat
y1u2rán
yu1rá
y1u2ráv
y1urn
y1u2ru
yu2sál
yu1sá
yus3s
yu2s1zá
yus2z
y1u2s1zo
yu2tak
yu1ta
yu2tal
yu2tam
yu2tat
yu2taz
yu2tál
yu1tá
y1utc
yu2tód
yu1tó
y1u2tu
y1u2tú
yú1bl
yú1br
yú1gr
y1újd
y1ú2jé
y1ú2jí
y1újr
y1újs
yú2ke2l
yú1ke
yú2k1e2s
yú2ke2t
yú2kev
yú2kó
yú2kö2l
yú1kö
yú2l1ag
yú1la
yú2laj
yú2lar
yú2l1á2r
yú1lá
yú2lát
yú2l1eg
yú1le
yú2l1e2l
yú2l1e2t
yú2lin
yú1li
yú2l1is
yúl2tag
yúl1ta
yú2lü
yú2lű
yú1pl
yú1p2r
y1úrb
y1ú2r2é.
yú1ré
y1ú2r1é2l
y1ú2rév
y1úr1ho
y1ú2ri
y1úrk
y1úr1nő
y1úrr
y1úr1tó
yú1s1ta
yú2sü
y1úszt
yús2z
y1útb
yú2t2é.
yú1té
y1úth
yú1ti2
yú2t2i.
yú2ti2g
y1útj
y1útk
y1útm
y1útn
y1ú2to2n1
yú1to
y1útp
y1útr
y1úts
y1útt
y1útv
yü2dü
y1ü2g2y
yü2két
yü1ké
yü2lá
yü2led
yü1le
y1ülte1té
yül1te
y1ünn
yü2rí
y1ü2rü
y1ü2te
y1ü2té
y1ütk
y1ü2tő
y1ü2veg
yü1ve
yü2völ
yü1vö
y1ü2ze
y1ü2zé
y1üzl
yű1bl
yűé2n
yű1é
yű2g1ő
yű1kl
yű1pl
y1űrb
y1ű2r2é.
yű1ré
yű2réb
yű2rén
yű2rön
yű1rö
y1űrr
yű2sá
yű2s1orr2a.
yű1so
yűsor1ra
yűs1s
y1ű2zőb
yű1ző
y1ű2zően
yűző1e2
y1ű2ző1é
y1ű2zőh
y1ű2zők
y1ű2zőn
y1ű2zőr
y1ű2zőt
y1ű2zőv
y2v1abr
y1va
y2v1a2dó
yva2d3z
yva1i2
y2v1akc
y2v1alb
y2v1alk
y2v1ank
y2v1ant
yv1a2nya
yvan2y
y2v1a1po
y2v1arz
yva2s1u2
y2v1atk
y2v1a1u
y2v1a1zo
y2v1ábr
y1vá
yv1ál1lo
yv1állv
yv1á2ra1i
yvá1ra
yv1á2rán
yvá1rá
y2v1á2r1e2
y2v1árj
y2v1árl
yvárosi2b
yvá1ro
yváro1si
yv1ár1re
y2v1á2ru
y2v1árv
y2v1á1ta2
y2v1á1ti
y2v1átt
yv1br
yv1dr
yv1e2gye
y1ve
yveg2y
yv1egys
y2v1e2kéb
yve1ké
y2v1e1la
y2v1e2lá
y2v1ell
y2v1e1lo
y2v1el1ti
y2v1ember
yvem1be
yven3n
y2v1e2p
yve2r1a
yve2r1á2
yve2reg
yve1re
yve2r1és2z
yve1ré
yve2rip
yve1ri
yve2r1o
yvert2
y2v1es1s1zé
yves2s2z
y2v1e1va
y2v1e2vé
yv1e2v2ő.
yve1vő
y2v1ex
yvezé2rel
yve1zé
yvezé1re
yvé2du
y1vé
yv1é1kí
yv1é2let
yvé1le
y2v1élm
y2v1élt
y2v1ép
y2v1ér1d2
y2v1é2ri
y2v1ér1rő
y2v1ér1té
y2v1é2rü
y2v1ér1ze
y2v1é2v2e.
yvé1ve
yv1fr
yv1gr
y2v1i2gaz1
y1vi
yvi1ga
y2v1i1gé
y2v1i2k
y2v1il1le
y2v1im
y2v1ind
y2vint
y2v1i2p
y2v1ism
y2v1ist
y2v1íg
y1ví
y2v1ín
yví2zis
yví1zi
yví2zü2
yv1kl
yv1kr
y2v1old
y1vo
y2v1olv
y2v1or
y2v1os
yv1ó2r
y1vó
yv1öss
y1vö
y2v1ö2z
yv1őr
y1vő
yv1pr
yv1sk
yv1sl
yv1st
yv1s2z2
yv1tr
y2v1ub
y1vu
y2v1ud
y2v1ur
y2v1u2t
yv1új
y1vú
yv1út
y2v1üg
y1vü
y2v1ü2z
yza2t1a2l
y1za
yza1ta
yza2t1e
yza2t1érd
yz2atér
yza1té
yzá2r1ó2ra
y1zá
yzá1ró
yzás3s
yze2t1a2
y1ze
yze2t1á
yze2t1é2r.
yze1té
yze2t1é2r2ő.
yzeté1rő
yze2tés
yze2té1te
yze2t1o
yze2t1ö2l
yze1tö
yzé2k1e2l
y1zé
yzé1ke
yző1a2
y1ző
yz2rí
2z.
1za
3zab2a.
za1ba
zab2b1i2ko
zab1bi
za2b1i2ga
za1bi
za1b1re
z2a1b1ri
z1ab1ro
2zabs
za2ce
za2c1ég
za1cé
za2c1ho
zac2h
zac1ikr
za1ci
za2c1im
za2ci1pa
za2c1iv
za2cí
zac1k2
zac3st
zac2s
za2dad
za1da
2zadag
zad1alj
za2dan
za2da1ta
2z1a2da1to
z1a2datr
zadás1s
za1dá
z2adi2a
za1di
za2d1ír
za1dí
za2dó1me
za1dó
za2dóz
z2a1d2rá
za2d1ut
za1du
za2d1zá
zad2z
za1fl
za1f2r
za2g1a2d
za1ga
za2gak
za2ga2n
za2g1an2y
za2ga1ra
za2g1a2t
za2ga1u
za2gág
za1gá
za2g1ál
za2g1árn
za2g1árt
za2g1áz
za2ge2l
za1ge
za2gés
za1gé
z1ag2g2y
2za1gi
za2gin
z1a2git
za2g1os2z
za1go
zagren2
zag1re
za2g1ü
z1ag2y.
zag2y
z1a2gyak
za1gya
z1a2gyar
z1agyb
za2i1já
za1i
zai2z
3za2j.
3zaj2a.
za1ja
za2j1a2d
za2jan
za2j1átv
za1já
3zajáv
za2jes
za1je
3zajf
za2jin
za1ji
3zajj
zajká2rok
zaj1ká
zajká1ro
3zajol
za1jo
za2j1or
2zaj1ta
2zajt2ó.
zaj1tó
z1ajtók
2zajtót
zaj1tr
3za1jú
za2j1ü2
2zakad
za1ka
za2k1a2da2t.
zaka1da
zakai2ko
zaka1i
za2k1a1ka
zak1alk
z2akal
za2k1alv
za2ka1na
z2akan
za2k1ant
za2ka1ra
z1akarat
z1a2karás
zaka1rá
z1a2ka1ró
z1akarv
za2k1atl
za2k1á2p
za1ká
za2k1á1t1a
za2k1átl
2zakc
z1akci1ó
zak1ci
za2k1e2g
za1ke
za2k1e1le
za2kelm
za2k1eln
za2k1e1lő
za2ket
za2k1é2l
za1ké
za2k1int
z2akin
za1ki
za2k1i2p
zak1i1ro
z2akir
za2k1ír
za1kí
zak1k2
za1k1lu
2z1aknák
zak1ná
zak1oszt
z2akos
za1ko
zakos2z
za2k1o2v
za2kős
za1kő
z2a1k1rí
2zak1tá
2zak1ti
z1aktív
zak1tí
2zak1tú
zaku1p2
za1ku
zaku2r
zak1u1ra
za2k1u1rá
za1kü2
za1k1vó
zal1a1cé
za1la
zala2gá
zalag1g
2z1a2la1kí
za2lakj
z1a2la1ku
zala2n
za2l1an2y
z1a2lapb
za2lapj
zal1átk
za1lá
zale2l
za1le
za1l1e1le
zal1ell
zal1e1lő
z2a2l1e2m
z1al1gá
z1al1ge
za2lid
za1li
z1alkal
zal1ka
z1alkat
z1alkot
zal1ko
2zalmás
zal1má
za2lól
za1ló
zalta2n
zal1ta
zal2tel
zal1te
zal2tem
z1al1ti
z2a2m1a2d
za1ma
z2a2m1aj
zam1a1la
z2a2m1a2p
zama2t1ő2
za2maz
2z1am1bu
za2mem
za1me
zam1ing
za1mi
za2m1is
za2mí
za2m1os2z
z2amos
za1mo
za2mü
z1a2nal
za1na
z1a2nek
za1ne
2zang
z1ang2y
2z1ankét
zan1ké
zan2tes
zan1te
z1antil
zan1ti
za2ny2a.
zan2y
za1nya
z1a2nyag
z1a2nyó
zao2k
za1o
zao2s
zaó2h
za1ó
zaó2v
z1a2p2a.
za1pa
za2pad
za2p1a2g
z1a2pa1i
za2p1ág
za1pá
z1a2pán
za2p1á1ra
z1a2pát
z1a2páv
zape2l
za1pe
zap1e1le
za2pe2m
za2pe2s
za2pí
za1p2lán
zap1lá
z1a2p2ó.
za1pó
z1a2pó1é
z1a2pók
2zap1p2
zappa2ne
zap1pa
zap3rof
z2ap1ro
zap1s2
za2r1a2dá
z2arad
za1ra
za2ran
zar1a2nya
zaran2y
zar1a1pa
za2rar
z1a2rá1i
za1rá
za2r1ál
z1a2rán2y
z1a2rár
2zarc
z1ar2c.
z1ar1ca
z1ar1cá
z1arc2h
z1ar1ci
z1arck
z1ar1co
z1ar1cú
zare2t
za1re
za2r1e1te
za2rev
za2ré1na
za1ré
za2ré1ná
2zarm
za2r1ön
za1rö
za2r1ü
zar2vág
zar1vá
2zarz
za2sem
za1se
z2a1s2ká
za1s2ko
za1s2li
za1s2pó
za1sp2r
z2a2t1ab
za1ta
za2t1a2d
za2t1aj
zat1alap
zata1la
zat1alj
zat1a1na
z2atan
za2t1a2n2y
zat1a1rá
z2atar
zat1att
za2ta1u
za2taz
za2t1áll
z2atál
za1tá
zat1á1lo
zatá2p
zat1á1po
za2t1árad
z2atár
zatá1ra
za2t1á1ré
za2t1á2ru
za2t1e1lé
za1te
zat1elk
za2t1e2ré
za2t1er1k2
z2a2t1é2g
za1té
z2a2tél
zat1i2ko
za1ti
za2tim
za2t1ind
z2atin
za2t1ing
za2tins
za2t1int
za2t1ist
z2atis
za2tiz
zat1k2
2z1atk2a.
zat1ka
z1atlas
zat1la
zatmo1s
zat1mo
zatmos2z2
za2t1okos
za1to
zato1ko
zat1ol1da
2z1a2tomj
za2t1oml
za2t1os2z
za2tök
za1tö
za2t1ön
zat2rok
zat1ro
za1trom
zatt2
zat1tr
za2tu1ra
z2atur
za1tu
za2tül
za1tü
za2tür
2zat2y
za2tya
za2tyá
za2u1la
za1u
za2u1lá
za2u1rá
zau2tak
zau1ta
z1a2u1to
z1a2u1tó
zaü2t
za1ü
zaü2z
za3va1rá
za1va
3zavarb
2zavat
z1a2z.
z1azh
z1a2zo
1zá
2z1ábr
zá2de
zá2g1a2d
zá1ga
zá2ga2k.
zá2ga1ko
zá2g1al
z1á2gas
z1á2gat
zá2gaz
zág1a1zo
zá2g1ál
zá1gá
zá2g1á2rak
zágá1ra
zá2g1á1ru
zá2g1e
zá2gép
zá1gé
zá2gol1ta
zá1go
z1á2gó1i
zá1gó
zá2g1ú2t.
zá1gú
zá2gü
z1ág2y.
zág2y
z1á2gya
zágy1a2da
z1ágyb
z1ágyh
z1ágyn
z1ágyr
z1á2gyu
záí2r
zá1í
zá2j1ék
zá1jé
zá2jí
zá2j1ol
zá1jo
zá2j1ös
zá1jö
zá2jő
zá2jü
zá2jű
zá2kin
zá1ki
zá2k1ö2v
zá1kö
zála2n
zá1la
zá2l1an2y
zál1apr
z1álar
zál1as2s2z
z1ál1ca
z1ál1cá
z1ál1dá
zá2l1e2g
zá1le
zá2l1e2l
zá2l1em
zá2lib
zá1li
zá2li2d
zál1ing
zá2lir
z1állam
zál1la
z1állap
z1ál1ma
z1ál1má
z1ál1mo
zá2lob
zá1lo
zálo2d
zá2l1o1da
zá2los
zá2lü
zá2ly1a2l
zál2y
zá1lya
zá2mac
zá1ma
zá2m1a2d
zá2m1aj
zá2m1a2l
zám1an2y
zá2m1a2p
záma2r
zám1a1rá
zá2maz
zá2m1átl
zá1má
zá2m1á2to
zá2mél
zá1mé
zá2m1é2rő
zá2m1ik
zá1mi
zá2mip
zá2mir
zám1orm
zá1mo
zám1or1s2
zá2mó
zá2mö
zá2mő
zá2m1ut
zá1mu
zá2mü
zá2mű
zá2naj
zá1na
zá2n1as
zá2n1at
zá2n1át
zá1ná
zá2nem
zá1ne
zá2nis
zá1ni
zá2n1it
zán3n
zá2n1os2z
zá1no
zán1sp
zá2nü
zán2y2
zá2p1ad
zá1pa
zá2p1ag
zá2p1or1zó
zá1po
zá1p2r
z1á2radd
zá1ra
zá2r1a2dot
zára1do
zá2r1a2l
z1á2ramk
z1á2raml
zára2n
zá2r1an2y
zá2r1ap
zá2rar
zá2r1a1ti
zá2r1a2to
zá2r1att
zá2r1av
zá2r1ál
zá1rá
3zárá2s.
3zárá1sa
3zárásb
3zárá1si
zárás1s
zá2r1á2s2z
z1árbev
zár1be
3zár1dá
zár2dem
zár1de
zár2d3z
2zá2r1em
zá1re
zá2réb
zá1ré
2zárf
2z1á2ri1á
zá1ri
zá2rid
zá2r1i2k
zá2rim
zá2r1is
zá2r1iz
zár1k2
3zárkák
zár1ká
2zár1kö
3zárlat
zár1la
2zár1nö
2zárn2y
zár2nyál
zár1nyá
zá2r1os2z
zá1ro
3zár2ó.
zá1ró
3záró1a
zá2r1ó1bo
3záród
zár1óév
záró1é
zá2r1ó2rá
zá2rő
2zár1ré
zárs2
zár1sk
zárt1a1nyá
zár1ta
zártan2y
zár2t1e2s
zár1te
zárté2r
zár1té
zár1t1ro
zá2rug
zá1ru
z1á2ruh
z1á2ruj
2zárunkn
2zárunkr
zá2rü
z1árvah
zár1va
zá2s1a2d
zá1sa
zá2saj
zá2sal
zá2s1a2r
zá2s1at
zá2saz
zá2s1á2g
zá1sá
zá2s1á2rad
zásá1ra
zá2s1á2rá
zá2s1á2ré
zá2s1árh
zá2s1árn
zá2s1árt2
z1á2sás
zá2sep
zá1se
zás1ikon
zá1si
zási1ko
zá2sis
zá2s1í
zá2s1ol
zá1so
z1á2s2ó.
zá1só
zá2s1ó2r
z1á2sóv
zá2su2t
zá1su
zá2sü
zá2s3zav
zás2z
zá1s1za
3zászl
zás1z3se
zász2s
z1átad
zá1ta
zát1a2d2ó.
zá2ta1dó
zát1a2dók
zátá2r
zá1tá
2zátb
2z1átc
z1átd
zát1e2m
zá1te
zá2t1é2l
zá1té
2z1átf
2záth
z1át1ha
z1át1he
zá1t2hi
z1á2t1i2
2z1átj
2z1átm
zá1t1ra
zá1t1ro
2záts
2z1átv
zá2z1a1la
zá1za
záza2t
zá2z1a1to
zá2z1e2
zá2zév
zá1zé
zá2z1ol
zá1zo
zá2zos
zá1zó2
zá2zór
zá2zö
zá2zsaj
záz2s
zá1z1sa
zá2zsas
zá1z4sá
zá2zsál
zá2zsol
zá1z1so
zba2ki
z1ba
zbe1á2
z1be
zb2le
zb2lo
zb2lú
zb2ri
zb2ró
zb2rú
zc2lu
zcsa2p1á2g
zc2s
z1c1sa
zcsa1pá
zda1p2
z1da
zdas2
z2d1ass
zd1áll
z1dá
zdés1s
z1dé
zdő1s2p
z1dő
zd2ri
zd1u2r
z1du
1ze
zea2d
ze1a
zea2g
zea2k
zea2l
zea2s
1zeán2s1ze
ze1á
zeáns2z
zeá2r
zeá2z
2z1e2b.
2ze1be
ze2b2e.
ze2be1i
ze2bek
z1e2béd
ze1bé
z1ebf
z1ebh
z1ebk
2z1ebn
ze1b1ru
2ze1ce
z1e2cet
z1e2c2h
z1e2cset
zec2s
ze1c1se
ze2d1á2l.
ze1dá
ze2d1álj
ze2d1áln
ze2d1ált
ze2d1álv
zede2r1e
ze1de
ze2dil
ze1di
ze2d1ó2
2z1e2d2z
zee2s
ze1e
z1eff
ze1fr
ze2g1a2l
ze1ga
ze2gap
ze2gar
ze2g1ál
ze1gá
ze2g1ár
ze2g1eg
ze1ge
zeg1ell
ze2gep
zeget2t1eb
zeget1te
ze2g1érr
ze1gé
ze2gé2s2z
zeg1észb
zeg1é1s1zé
ze2gid
ze1gi
ze2gil
ze2gol
ze1go
ze2gú
ze2gyed
zeg2y
ze1gye
ze2gy1e2l
ze2gye1ne
ze2gyez
z1egyl
z1egys
ze2het
ze1he
ze2i1a
ze1i
zei2g
zei2s
zeí2r
ze1í
zekci2ó1s2z2
zek1ci
zekci1ó
zek1e2g2y
ze1ke
ze2k1ell
z1e2kéit
ze1ké
zeké1i
2zekék
ze2kil
ze1ki
ze1k2lap
zek1la
ze2k1ott
ze1ko
zek1övek
ze1kö
zekö1ve
ze2kő
ze1k2re1á
zek1re
zek1t2
ze2k1ü2lő
ze1kü
z1ekviv
zek1vi
ze2lag
ze1la
ze2la2r
ze2lál
ze1lá
z1el1bo
2z1e2ledel
ze1le
zele1de
z1e2le1fá
ze2le1ji
2zelekt
z1e2lem2e.
zele1me
z1e2lemek
zelem1el
zele2m1é2r.
zele1mé
ze2le1mű
zele2pá
zele2p1el
zele1pe
zele2po
zele2p1ü2l
zele1pü
z1e2l1e2re
ze2l1es2z
zele2tá
zele2ter
zele1te
2z1e2lég
ze1lé
2z1elé1ré
zel1érh
zelés1s
z1el1há
2z1elhel
zel1he
2z1elher
ze2litet
ze1li
zeli1te
z1el1kü
2z1ellá1to
zel1lá
2zellen
zel1le
z1elle1ne
2z1elnev
zel1ne
z1el1nö
ze2lof
ze1lo
z1e2l1os
z1e2lö2l
ze1lö
z1e2l1ön
2z1e2lő1ha
ze1lő
zelő2saj
zelő1sa
z1e2lő1ző
z1el1ső
z1el1tá
2z1eltér
zel1té
2z1el1to
2z1el1tű
z1e2lu
2z1e2lú
z1el2v.
z1elvek
zel1ve
z1elves
2z1elvev
z1elvez
z1elvn
z1elvt
2z1el1vű
z1el1zá
2ze1ma
ze2mak
ze2m1ág
ze1má
ze2m1á2r
ze2mát
2zember
zem1be
z1embe2r.
z1emberb
z1embe1ré
z1emberh
z1emberk
z1embern
z1emberr
z1embert2
z1embl
2zem2e.
ze1me
ze2m1e1gé
2zeme1i
2zemek
ze2m1ell
ze2m1eng
2zeméb
ze1mé
2zeméh
ze2m1é2k
ze2m1é1le
ze2m1é2lé
ze2m1é2lő
ze2m1élt
ze2m1élv
ze2m1é2rét
zemé1ré
2zemés
zemé2te
ze2m1éten
ze2m1étet
ze2m1ét1jé
2zemév
ze2migr
zemi2g
ze1mi
ze2m1ing
ze2m1int
ze2mis
ze2m1iz
ze2m1í
2zemk
2zeml
2zem1mó
ze2mol
ze1mo
ze2m1os2z
ze2m1ó2
ze2m1ő
2zems
z1emul
ze1mu
ze2m1ur
ze2mú
2ze1mü
2zemv
zemvíz1
zem1ví
3zeneir
ze1ne
zene1i
3zene1ka
zenes2z2
z2enes
3zené1é
ze1né
3zené1i
3zenéj
ze2nép
zen1ké2
zenkét1
ze2n1o
zen2tag
zen1ta
zen2tal
zen2t1an
zen2t1as
zente2g
zen1te
zen2t1eg2y
zen2telm
zen2t1e1re
zen2tev
zen2t1é2j
zen1té
zen2t1imr
zen1ti
zen2ti2p
zen2tis
zen2tí
zen2tol
zen1to
zen2t1ó2
zen2t1ö
zentő2s
zen1tő
zen2t1ő1se
2zenz
zeo2k
ze1o
zeo2m
zeö2r
ze1ö
2z1e2pééb
ze1pé
zepé1é
2z1e2péé1i
2z1e2péén
2z1e2péit
zepé1i
2z1e2péj
2z1e2pém
2z1e2pés
z1e2piz
ze1pi
ze1p1la
ze1p1le
2zepr
ze1p2ri
ze1p1ró
ze2r1a2d
ze1ra
ze2ra1já
ze2r1ajt
ze2rakr
ze2r1al
ze2ram
ze2r1a2n
ze2r1a2r
ze2r1as
ze2r1a2t
ze2r1a1u
ze2raz
ze2r1á1ga
ze1rá
ze2r1á2r
ze2rás
ze2r1á2t
zer2bin
zer1bi
zer1d2
z1er1dé
ze2r1eb
ze1re
ze2r1e2ce
ze2re1dé
ze2re1ge
zer1e2g2e.
ze2r1e2gé
ze2r1egg
ze2r1eg2y
z1e2re1je
zer1ejt
z1e2re1jü
z1e2re1jű
ze2r1e2k2e.
zere1ke
z1e2rekl
ze2relődj
zere1lő
ze2r1ember
zerem1be
ze2r1e1mi
ze2r1eng
zere2p1a
ze2r1e2r
ze2r1e2sés
zere1sé
ze2r1es2s2z
ze2r1es1te
ze2r1e2sz2e.
zeres2z
1zere1s1ze
zer1eszk
ze2reszt
ze2r1e1u
ze2r1e2v
ze2rég
ze1ré
ze2r1é2j
ze2r1ék
ze2r1é2l
ze2r1ép
zer1g2
ze2r1i2d
ze1ri
ze2r1i2ga
ze2r1i1ko
ze2r1ill
ze2r1i2m
ze2r1i1na
ze2rind
ze2r1inf
ze2r1ing
ze2r1inj
ze2r1ink
ze2r1intel
zerint2
zerin1te
ze2r1in1té
zer1in1tő
ze2ri1o
ze2r1ism
ze2riz
ze2r1í2j
ze1rí
zer1k2
zer2nal
zer1na
ze2r1ok1t2
ze1ro
ze2r1old
ze2rolv
ze2ror
ze2r1ost
ze2r1os2z
ze2ro1ti
ze2r1ó2r
ze1ró
ze2rö2k
ze1rö
2ze2rő
z1er2ő.
z1erőf
z1erő1i
z1erők
z1eről
z1erőn
z1erős
z1erőt
z1erőv
zer1őz
zer1p2
zer1s
zers2k
zers2p
zers2t
zert1á2lom
zer1tá
zertá1lo
zer2tit
zer1ti
zer2tór
zer1tó
zer1t1ra
zer1tren
zert1re
ze2r1un
ze1ru
ze2rur
ze2rus
ze2r1u2t
ze2r1út
ze1rú
ze2r1ü2g
ze1rü
2ze1rű
zer2v1a2dó
zer1va
zer2va2n
zer2vel
zer1ve
zer2vél
zer1vé
2z1e2sedék
ze1se
zese1dé
ze2s1e2kéh
zese1ké
z1e2setb
z1e2set2e.
zese1te
z1esetes
z1e2setet
z1e2se1té
z1e2se1ti
z1e2setr
2z1e2sé2s.
ze1sé
2z1e2sésb
2z1esésd
2z1e2sés2e.
zesé1se
2z1e2sése1i
2z1e2sések
2z1e2sésem
2z1e2sése2n
2z1e2séses
2z1e2sé1sé
2z1e2sésh
2z1e2sé1si
2z1e2sésk
2z1e2sésn
2z1e2sésr
2z1e2sés3s
2z1e2sést
ze2s1it
ze1si
ze1s2mi
2z1e2ső
zesp2
ze1s2pe
ze1spr
2zes1te
z1estek
z1estem
2z1es1té
z1est2i.
zes1ti
ze1s2til
2z1estj
ze1s1to
z1est1re
z1estt
ze2s1ü2v
ze1sü
ze2s3za2c
zes2z
ze1s1za
ze2szág
ze1s1zá
zesz1e2get
1ze1s1ze
zesze1ge
ze2szel1le
ze2szeng
ze2széh
ze1s1zé
ze2sz1é2le
ze2sz1im
ze1s1zi
ze2szip
2zeszk
z1esz1kö
ze2szol
ze1s1zo
zeszt2
zesz1tr
ze2t1ab
ze1ta
ze2ta2c
ze2t1a1la
ze2t1a2n2y
ze2táj
ze1tá
ze2t1á2l
ze2t1á2p
ze2t1át
zet1e1gé
ze1te
zet1e1lá
ze2t1ell
ze2telm
ze2t1e2lő1á
zete1lő
ze2t1e2lőd
ze2t1elr
zet1e2mel
zete1me
ze2t1eml
ze2te1ne
zet1er1k2
zet1e2ró
zete2s1ég
zete1sé
zetest2
zetes1tr
z1e2te1té
ze2t1e1ti
ze2tetn
ze2t1éd
ze1té
ze2t1é2j
ze2t1é2l
ze2tér1te
ze2t1ér1té
zet1ér1tő
ze2t1é2rü
zeté2s2z
1zet1é1s1ze
zet1éter
zeté1te
ze2ti2d
ze1ti
z1e2ti1ka
ze2t1i2ko
ze2t1ill
ze2t1ing
ze2tít
ze1tí
1zetké1s1z1e
zet1ké
zetkés2z
ze2tom
ze1to
zet1ont2
ze2top
ze2t1ó2r
ze1tó
ze2t1ö2v
ze1tö
zet1ő1ri
ze1tő
zet1ő1rö
zet1őrt
ze2tut
ze1tu
ze2tür
ze1tü
ze2t1ü2z
ze2t2y
zeu2g
ze1u
z1e2ur
zeü2t
ze1ü
z1e2vő
ze2x1id
ze1xi
ze2xim
ze2x1i2p
z1ex1pe
zex2t1ö
ze2xú
z1e2zer
ze1ze
z1ezr
1zé
z1é2ber
zé1be
2z1ébr
zé2dak
zé1da
zé2d1a2l
zé2d1a2r
zé2d1as
zé2dat
zé2d1á2
zé2d1ekét
zé1de
zéde1ké
zé2d1e2lem
zéde1le
zé2delm
zéde2r1
zé2d1esem
zéde1se
zé2d1es2z
zé2d1e1ti
zé2d1és
zé1dé
zé2dik
zé1di
zé2dó
zé2d1ő
zédren2
zéd1re
zé1du2
zé2dur
zé2dú
zé2d3z
zé2fá
z1é2g.
z1égb
z1é2ge
zé2gé
z1é2gi
z1égn
z1é2gő
z1égr
z1égt
z1é2h2e.
zé1he
z1é2hen
2z1é2hes
z1é2het
2z1éhs
2zéid
zé1i
z1é2j.
zé2jen
zé1je
z1é2ji
zéjje2l1e2
zéj1je
z1éjs
2z1é2jül
zé1jü
2z1é2jün
zé2k1a2d
zé1ka
zé2k1a2l
zé2k1a2n
zé2k1ap
zé2k1as
zé2kás
zé1ká
zé2k1eg
zé1ke
zé2k1e2két
zéke1ké
zé2k1elr
zé2ker
z1é2kez
zé2k1ék
zé1ké
zé2k1é2l
z2é2k1és
zék1old
zé1ko
zék1os2z
zé2k1ó2
zé2kör
zé1kö
zé2kúr
zé1kú
zékü2l
zé1kü
zé2k1ü1lé
zé2k1ü1lő
zé1la2
zé2lab
zé2lad
zé2lak
zé2l1an
zé2las
zé2l1á2
2zé2le2l
zé1le
zé1l1e1le
zél1ell
zél1e1lő
zél1elt
zé2l1e2r
2zéle2t.
2z1é2letb
2z1é2let2e.
zéle1te
2z1é2leten
2z1é2le1té
zé2letf
2z1é2leth
2z1é2le1ti
2z1é2letn
2z1é2letp
2z1é2letr
2z1é2lets
2z1é2let1tő
2z1é2le1tü
2z1é2le1tű
2zé1lé
zé2l1ékh
zé2li2m
zé1li
zé2l1ist
zé2lí
2zélm
zé2l1o
zé2l1ö2
2zélős
zé1lő
2zélt
zé2l1u2
zé2lú
zé2lya
zél2y
zé2lyá
zé2ly1esh
zé1lye
zé2lyo
2zé1me
zé2n1árn
zé1ná
zéndi1o2
zén1di
zé2neg
zé1ne
2z1é2nekb
2z1é2ne1ke
z1é2ne1ké
zé2nek1k2
2z1é2nekl
z1é2nekn
zé2nekr
zé2ne1kü
zé2n1e2l
zé2n1e2r
zé1ni2
zé2nik
zé2nil
zé2nim
zé2n1i1o
zé2n1is
zé2ní
zé2nom
zé1no
zé2nö
zé2nő
zént2
zé2n1ü2lő
zé1nü
zé2p1a2g
zé1pa
zé2p1a2l
zé2p1a2n2y
zé2p1a2p
zé2p1a2r
zé2pas
zé2pat
zé2pa1u
zé2pav
zé2p1á2r
zé1pá
zé2pát
zé2peg
zé1pe
zép1ell
z2épel
zé2pez
zép1f2
zé2pin
zé1pi
zé2p1i2p
zé2pir
zé2p1i2s
2zépít
zé1pí
zé1po2
zé2pol
zé2pos
zé2pó
zé2pö
zép1s
zé2pud
zé1pu
zé2p1u2s
zé2pú
2zépül
zé1pü
zé2rab
zé1ra
zé2rag
zé2r1a2l
z1é2ram
zé2r1a2n
zé2rap
zé2rar
zé2r1as
zé2rat
zé2rav
zé2r1á2
zér1d2
zé2r1e2le1mé
zé1re
zére1le
zé2r1e2lemk
zé2r1e2lemn
zé2remb
zé2r1eml
zé2r1es2z
2zérett
zé2rev
zé2ré2s.
zé1ré
zé2résb
zé2ré1se
zé2ré1si
zé2résk
zé2résn
zé2r1id
zé1ri
zé2rim
zé2ris
zé2r1o2k
zé1ro
zé2r1os
3zérós
zé1ró
zé2rö
2z1é2r2ő.
zé1rő
z1é2rő1i
z1é2rők
zé2rős
z1é2rőt
zér1s
zér2taj
zér1ta
2z1értekez
zér1te
zérte1ke
2z1értelm
2z1értetl
2z1érte1tő
2z1érték
zér1té
2z1érthet
zért1he
z1ér1to
zé2rú
zé2sab
zé1sa
zé2san
zé2sar
zé2s1az
zé2s1á2
zé2seg
zé1se
zé2s1e2l
zé2s1e2r
zé2s1e1ti
zé2s1ég
zé1sé
zé2sés
zé2s1i2k
zé1si
zé2sok
zé1so
zé2só
zés3s
zé2s1ü2t
zé1sü
zé2s3za
zés2z
z1észb
1z1é2s1zé
z1észl
zé2s1zó
zés3z2s
zé2t1a2b
zé1ta
zé2t1ad
zé2tag
zé2t1aj
zé2t1a2k
zé2t1a2l
zé2t1ap
zé2t1a2s
zé2t1a1u
zé2t1á2
zéte2n
zé1te
zéte2se
zéte2sü
zé2té2g
zé1té
zé2t1é2n
zé2t1i2
2zétk
zé2t1o2l
zé1to
zé2t1o2m
zé2tor
zé2t1o2s
zé2tu2n
zé1tu
zé2tú
zé2t1ü2
zé2t1ű
2zétv
2z1é2v.
2z1é2vad
zé1va
z1évb
2z1é2v2e.
zé1ve
2z1é2vek
2z1é2ven
2z1é2ves
z1é2vén
zé1vé
z1é2vét
z1é2vév
z1évf
2zé1vi
z1é2v2i.
z1évk
2z1évn
z1évr
z1évs
z1évt
z1é2vü
2z1évv
zfa2l1a2da
z1fa
zfa1la
zfa2l1aj
zfa2l1e2l
zfa1le
zf2a2les
zfe2l1em
z1fe
zfe1le
zfe2len
zfe2re2g
zfe1re
zf2la
zf2le
zf2li
zf2lo
zf2ló
zfo1ra2
z1fo
zfo2rat
zfo2riz
zfo1ri
zföldi1é2
z1fö
zföl1di
zf2ra
zf2rá
zf2re
zf2ri
zf2rí
zf2ro
zf2rö
zgás3s
z1gá
zgá2s3z
zgá2zó
zgé2p1e2két
z1gé
zgé1pe
zgépe1ké
zgé2sa
zgé2sá
zgé2sem
zgé1se
zgés3s
zg2la
zg2ló
zgó1g2
z1gó
zgő2nyá2
z1gő
zgőn2y
zgő2z1á2
zgő2zér
zgő1zé
zgő2z1ő
z1g2ra
z1g2rá2f.
zg1rá
z1g2ráff
z1g2ráfh
z1g2ráfj
z1g2ráfk
z1g2ráfn
z1g2ráfr
zg2ró
z1g2ru
zhán2
z1há
zhá2t1a2d
zhá1ta
zházköz1
zház1kö
1zi
zi2a1a
zi1a
zi2a1á
zi2a1bo
zi2ac
zi2ad
zi2a1e
zi2a1é
zi2ag
zi2ah
zi2a1í
zi2aj
zi2akép
zia1ké
zi2a1kó
zi2al
zi2am
zi2a1o
zi2a1ö
zi2a1p2
zi2ar
zi2a1s1za
zias2z
zi2a1s1zo
zi2a1s1zó
zi2at
zi2a1u2
zi2a1ú
zi2a1ü
zi2av
2z1ibr
2zicc
zi1c2h
z1i2de1á
zi1de
z1i2deg
z1i2de1o
zi2dén
zi1dé
2z1i2dő
zie2l
zi1e
zie2m
zi2é1a
zi1é
zi2é1á
zi2é1ke
zi2ép
zi2é1ta
zi2é1tá
zi2é1ü
z1ifj
z2i2g.
zi2g2a.
zi1ga
2z1i2gazg
2z1i2ga1zo
z1i2gás
zi1gá
zi2géj
zi1gé
2z1i2gén
zi1g2r
2zi1gye
zig2y
zi2k1a2n
zi1ka
zik1ékek
zi1ké
ziké1ke
zi2kik
zi1ki
zi2kim
zi2k1ing
zi1k1lu
z2i2k1u2r
zi1ku
zikus1s
zi2ler
zi1le
zi2l1é2j
zi1lé
zi2l1ék
zi2l1ing
zi1li
zi2l1ü
z1i2m2a.
zi1ma
zi2mak
zi2máb
zi1má
zi2máj
zi2mák
zim1áll
zi2már
zi2mát
zi2máz
zim1b
zi2m1e2g
zi1me
zi2m1e2l
zi2m1é2ré
zi1mé
zi2m1ér1té
zim1ind
zi1mi
zi2m1ip
z1i2mit
zi2mö
zi2n1a2d
zi1na
zi2n1a2l
zi2n1a2n
zi2n1a2r
2z1i2nas
zi2n1a1u
zi2ná1i
zi1ná
zi2n1áll
z1in1du
zin1emb
zi1ne
zi2nég
zi1né
zin2gas
zin1ga
zin2gál
zin1gá
zi2n1i2m
zi1ni
zi2n1in
zi2ní
zin3n
zi2nol
zi1no
zin1s2
zin2t1a2d2
zin1ta
zin2t1a2l
zin2t1a2s
zin2tát
zin1tá
zin2t1á2z
zinte2l
zin1te
zin2t1e1le
zin2te1lő
zin2telt
z1interj
z1interp
z1interv
zin2tom
zin1to
zin2t1ón
zin1tó
zi2n1u2t
zi1nu
zi2nú
zi2ol
zi1o
2z1i2o1no
2z1i2ons2
zi2ot
zi2ó1a
zi1ó
zi2ó1á
zi2óc
zi2ó1e
zi2óg
zi2ó1í
zi2ókam
zió1ka
zi2ókap
zi2ó1ká
zi2ó1ke
zi2ó1kl
zi2ól
zi2ó1má
zi2ó1mé
zi2ó1né
zi2ón2y
zi2ó1o
zi2ó1p2
zi2ó1rá
zi2ó1sá
zi2ó1sé
zi2ó1s2ká
zi2ó1sp
zi2ó1s1zű
zió1s2z2
zi2ó1ta
zi2ó1tá
zi2ó1ú
zi2ó1ü
zi2ó1ű
zi2pa1i
zi1pa
zi2pa1ri
zi2páj
zi1pá
zi1p2l
zi1p2r
z1i2rat
zi1ra
2zi1rá
z1i2rán
z2i3re
2zi1ro
z1i2rod
zi2rón
zi1ró
z2i3rő
2zirt
zir2t1a2
z1ir1tá
zir2to
z1ir1tó
zi2sad
zi1sa
zi2sal
zi2sam
zi2sar
zi2s1a2s
zi2sál
zi1sá
zi2s1á1ro
zi2s1e2g
zi1se
zi2s1e2l
zi2s1er
zise2s
zi2s1e1se
zi2s1es2z
zi2s1ék
zi1sé
zi2sér1te
zis1é1vi
zi2s1il
zi1si
zi2sin
zi2s1is
zi2sit
zi2s1o2v
zi1so
zi2s1ó2
zi2sö
zi2ső
ziss2
zis3s1za
zis2s2z
zis3s1zá
zis3s1zo
zist2
zis1t1ra
zis1t1re
zi2su2t
zi1su
zi2s1ü2v
zi1sü
zi2s3zaj
zis2z
zi1s1za
zis3zav
zis3zón
zi1s1zó
zi2t1áll
zi1tá
zit1á1ru
zi2t1e2g
zi1te
zi2t1elr
zit1eng
zi2tét
zi1té
zi2til
zi1ti
zi2tin
zi2tip
zi2tir
zi2t1or
zi1to
zi1tó2
zi2tór
zi2t1út
zi1tú
ziú2r
zi1ú
z1i2vad
zi1va
2z1i2vó
2z1i2zé
2zizm
z1iz1mo
z1i2zom
zi1zo
1zí
zí2gé
zí2jan
zí1ja
zí2je
zí2jö
zí2jü
zí2nac
zí1na
zí2na2d
zí2na1ku
zí2nal
zí2nan
zí2nas
zí2nat
zí2n1áll
zí1ná
zí2n1ár
zí2nát
zí2n1e2r
zí1ne
zí2né2l
zí1né
zí2ní
z1ín1na
zín3nye
zín2n2y
zí1no2
zí2n1od
zí2n1ok
zí2nos
zí2n1ó2
zí2nö
zí2nu
zí2nú
z1í2n2y
zí2nyenc1
zí1nye
zí2r1a
2z1í2rá
z1í2rog
zí1ro
2z1í2ró
zítő1a2
zí1tő
zítő1e2
zítőkés2z1
zítő1ké
zí2vaj
zí1va
zív1ak1t2
zí2val
zí2var
zí2ve2r
zí1ve
zí2v1ő
zí2vul
zí1vu
zí2vú
zí2ze
zí2zü
zí2zű
zje2gy1á2
z1je
zjeg2y
z2k1abl
z1ka
z2k1alak
zka1la
z2k1alj
z2k1alt
zka1p2l
z2k1a2rád
zka1rá
z2k1a2ráj
z2k1a2rén
zka1ré
zka1ró2
zka2rór
zka1s2k
z2k1állv
z1ká
zk1bl
zk1dr
z2k1egys
z1ke
zkeg2y
z2kenn
zk1eszk
zkes2z
z2k1e1tű
z2k1é2r.
z1ké
z2k1ér1té
z1k2hü
zki1a2
z1ki
zki1á2
zki1e2
zki2g
z2k1i1gé
zki2sé
zk2la
z1k2lá
zk2ler
zk1le
z1k2lí
zk2ló
z1k2lu
z2k1ol1da
z1ko
zkon2t1ár
zkon1tá
z2k1on1tó
zko2r1os
zko1ro
z2k1oszl
zkos2z
z3koszt
z2kókor
z1kó
zkó1ko
zk1ókort
zkó2pá
zkó2z3s
zkö2ze1le
z1kö
zkö1ze
zköz1ell
zkö2z1é2l
zkö1zé
zkőé2h
z1kő
zkő1é
zk1pr
z1k2rak
zk1ra
zk2rá
z1k2re1á
zk1re
z1k2rém
zk1ré
z1k2ré1ta
z1k2ré1tá
zk2rí
z1k2ro
z1k2rón
zk1ró
zkul1tú3
z1ku
zkultúr2
z2k1u2rán
zku1rá
z2k1u2rá2t.
z2k1u1tá
z1k2val
zk1va
zk2vó
zky2b
z1ky
zky2h
zky2j
zky2k
zky2n
zky2r
zky2t
zky2v
zla2tal
z1la
zla1ta
zl2ata2n
zlat1an2y
zlá2m1e
z1lá
zlá2mis
zlá1mi
zlás3s
z2lá2v.
z2lá1ve
z2lá1vé
z2lávk
z2lávn
z2lá1vo
z2lávv
zleg1g2
z1le
zle1i2
zle1í2
z2len2g.
zle2tak
zle1ta
zle2tal
zle2t1a2n
zle2tál
zle1tá
zle2t1á2r
zle2t1eg
zle1te
zle2t1ell
zle2tes2z
zle2té2l
zle1té
zle2t1é2r.
zlet1érv
zle2tés
zle2t1é2té
zle2t1é2v2e.
zleté1ve
zletigaz1
zleti2g
zle1ti
zle2t1i2ga
zle2t1u
zle2tüz
zle1tü
zlé2sí
z1lé
zlé2so
zlés3s
zli1na2
z1li
zli2nan
zli2nin
zli1ni
z2lo1ge
z1lo
zlo2p1as
zlo1pa
zlo2pó
zló1ó2
z1ló
zló2s1orr
zl2ósor
zló1so
zma1pr
z1ma
zmas2
zma1sp
zme1á2
z1me
zme2ge
zm2e3gom
zme2g1o
zmi2n1a2
z1mi
zmi2n1e
zmuskés2z1
z1mu
zmus1ké
zmu2sö
zmu2s3z
zmu2tok
zmu1to
zna2pe2s
z1na
zna1pe
zni1e2
z1ni
zni2ó
z2nob
z1no
zn2ó1st2r
z1nó
znót2
znó1tr
1zo
z1obj
z1o2dú
zo2es
zo1e
z1o2k2a.
zo1ka
z1o2k1a2d
z1o2ka1i
zo2k1ál
zo1ká
zo1ki2
zo2k1ip
z1o2k1ir
z1ok1ke
z1ok1le
2z1o2koz
zo1ko
2zok1ta
zo2lab
zo1la
2z1o2laj
zola1já2
zol2a2j1ár
zola2je
zo2l1a2l
zo2l1as
zol1ábr
zo1lá
zo2l1á1ro
z1oldal
zol1da
2z1oldh
2z1ol1dó
zo2l1e
zol1f2
zol1inf
zo1li
zo2l1is
zo2l1í
z1ol1ló
zo2lö
z1oltás
zol1tá
z1olt2ó.
zol1tó
zol1t2re
zo2lü
2z1olvad
zol1va
zo2m1a2g
zo1ma
zo2mak
zo2m1a2l
zo2m1a2n
zo2map
zo2m1a2s
zom1áll
zo1má
zo2m1á2r
zom1b2
3zombo2r.
zom1bo
2zo1me
zo2me2g
zo2m1e2l
zo2men
2zo1mé
zo2mél
zomé2t
zo2m1é1te
2zomf
2zomg
zo1mi2
zo2min
zo2miz
zom2jó2
2zoml
zo2m1or1s2
zo1mo
zo2m1os
2zo2mö
2zom1p2
2zom1s
2zomz
zo2n1aj
zo1na
z2o2n1ak
zo2n1a2la
zo2n1a1lá
zo2n1alk
zona2n
zo2n1an2y
zo2n1a2r
zon1as2z
zo2n1a1u
zo2náld
zo1ná
zo2n1áll
zo2n1átl
zo1ne2
zo2neg
zo2ne2l
zo2n1er
zo2ne2s
zo2név
zo1né
zo2nin
zo1ni
zo2n1i2o
zo2n1or
zo1no
zono2sé
zo2n1ó2
zo2nö
zon1s2
zon2t1e2s
zon1te
zon2tin
zon1ti
zon1t1re
zo2nü
zo2ny1ad
zon2y
zo1nya
zo2ny1a2l
zo2ny1a2n
zo2nyar
zo2nyav
zo2nye
zo2nyid
zo1nyi
zo2nyij
zo2nyó
zo2ol
zo1o
zo2om
z1o2pál
zo1pá
z1o2pe
zo1p1ho
zo2r1a2d
zo1ra
zo2raf
zo2r1ag
zo2r1aj
zo2r1a2l
zo2ran
zo2r1as
z1o2rat
zo2r1a1u
zo2r1e2
zor1f2
zo2rid
zo1ri
zo2r1il
zor1ing
z1or1má
zo2r1ol
zo1ro
zo2rop
zo2ros2z
zo2r1ó2
zo2rö
z1or2r.
z1or1rú
z1or1só
zor1t2re
zo2rü
2z1os1ko
z1os1tá
z1os1to
zo1sz2f
zos2z
z1oszl
zosz2tat
zosz1ta
2zo1te
2zo1tó
zotó2pa
z2otóp
zo2vi
zo2xi
1zó
zóa2d
zó1a
zóá2g
zó1á
zóá2r
zó1bl
zó1b2r
zó1cl
zó2d1e2l
zó1de
zó1fl
zó1f2r
zó1gl
zógyö2k1ér
zóg2y
zó1gyö
zógyö1ké
zói2g
zó1i
zóí2v
zó1í
zó1ja1
zó1k2l
zó1k1ré
zókupac1
z2ókup
zó1ku
zóku1pa
2zó2l.
z1ólm
zó2lomb
zó1lo
3zónád
zó1ná
3zóná1i
z1ó2ni
2z1ónj
2z1ónn
z1ó2nod
zó1no
2z1ónt
zóó2r
zó1ó
zó1p2l
zó1p2r
z1ó2rad
zó1ra
z1ó2rar
zó2ras2z
zó2rá1i
zó1rá
z1ó2ráj
z1ó2rák
z1ó2rár
zórás1s
z1ó2rát
z1ó2ráv
z1ó2ri1á
zó1ri
zós1í2k.
z2ósík
zó1sí
zó2s1í2n.
zó1s2ká
zó1sl
zó1s2ta
zó1s1té
zós2z1akad
z2ó1s1za
zós2z
zósza1ka
zósz1ál1lí
z2ó1s1zá
zó2sze1ne
z2ószen
zó1s1ze
zó2sz1é2le
z2ó1s1zé
zó2sz1íj
z2ó1s1zí
zósz1ü2l2ő.
z2ó1s1zü
zószü1lő
z2óta2g1a2
zó1ta
zóté2g
zó1té
zót1é1ge
zó1t1ré
z1ó2vó
1zö
zö2bá
zö2bo
zö2bó
z1öcc
zö2dé
zö2dú
zö2ga
zö2gá
zög3g
zö2gil
zö1gi
zö2gí
zö2go
zö1gö2
zö2g1öl
zö2gu
zö2g2y
2z1ökl
2z1ö2ko
2z1ök1rü
3zöldes
zöl1de
3zölds
2z1ö2l1e2b
zö1le
zö2les
2z1ölniv
zöl1ni
z1ölyv
zöl2y
2z1öml
z1ö2na
z1ö2ná
2zönb
2zö1ne
2zö1né
2zö1ni
2zönl
2zön1ne
2zö1nö
2zönr
2zönt
3zörej
zö1re
zör2f1e
zör2f1o
zör2nya
zörn2y
zör2nyá
zör2nye2l
zör1nye
zör2nyes2z
zör2nyék
zör1nyé
zör2nyí
zö2rök
zö1rö
zör2p1a2
zör2pá
2z1örv
2zös2s2z
z1ösv
z1ösz1tö
zös2z
zö2te
2z1ötl
2z1ötv
z1ö2v.
z1ö2ve1i
zö1ve
z1ö2vek
zöve2t1e2l
zöve1te
z1ö2vez
1zö2zö
1ző
zőa2c
ző1a
zőa2l
zőá2g
ző1á
ző1bl
ző1br
zőé2l
ző1é
ző1fl
ző1f2r
zőgé1pi2
ző1gé
zőgé2p1ip
ző1gr
ző1kl
ző1mű1
ző1nyá2
zőn2y
ző2ny1er
ző1nye
ző2ny1é2k
ző1nyé
ző2nyért
ző1pl
ző1pr
ző2ra2n
ző1ra
ző2r1e2c
ző1re
ző2r1e1ge
ző2rel
ző2r1er
2zőrk
2zőrl
z1őr1lő
2zőrm
ző2rol
ző1ro
2ző1rü
ző2r1ü2g
2zőrz
z1őr1zé
1z1őr1ző
ző2sis
ző1si
ző1s1ká
ző1s1pe
ző1s2pi
ző1s1ta
ző1s1ti
ző1st2r
zős2z2
ző1szf
ző1szt2
ző1tr
zőü2l
ző1ü
zpen1n
z1pe
zp2fe
zp2la
zp2lé
zp2lo
zpor2t1e2
z1po
zpor2t1ő2
zpor2t1ú
z1p2ra
z1p2ri
z1p2ro
z1p2ru
zrae3le
z1ra
zra1e
zrádiói2t
z1rá
zrá1di
zrádi1ó
zrádió1i
zre2d1á2
z1re
zre2del
zre1de
zre2d1é2k
zre1dé
zre2d3z
zren2d1ő2
3z2r2í.
z1rí
z2s
1z1sa
zs1ab1la
2zs1a2dat
zsa1da
2zs1a2dó
zsag2
2z3sa2h.
2zsajt
2zs1akc
2zs1alj
2z2s1alm
2zs1alt
z2sa2nyá
zsan2y
zsa2p2a.
zsa1pa
2z3sapk
zsa2rán
zsa1rá
z3sarj
2z3sark
2z3sarl
2z2s1arz
2z2s1aszt
zsas2z
zs1atl
zsa1tr
2z2s1a2t2y
2zs1aut2ó.
z2saut
zsa1u
zsa2u1tó
2zs1autón
zs1a1zo
1z1sá
2zs1ábr
2z3sáf
2z3ság
zs1ág2g2y
zsá2kó
2zs1á1la
2zs1álc
2z2s1álm
2zs1á2p
2zs1á2rak
zsá1ra
2z2s1á2rat
2z3sá1ri
2zsá1ru
2z2s1á2t1e2
2z2s1átk
2zsá1to
2z2s1á2t1ö
2z2s1áts
2zs1á2zós
zsá1zó
zs1bl
zs1br
z3s2c2h
zscsa1pá2
zsc2s
zs1c1sa
zsde1s2
zs1de
zs1dr
1z1se
zse2ba
zse2bi1ro
zse1bi
zse2b1o2
2zsebz
zs1e2ce
zs1e2dé
z3sej
2zsell
zs1elm
2z2s1e2lö
zs1e2lőf
zse1lő
zs1e2lőr
2z2sember
zsem1be
2zs1e2mel
zse1me
zs1enc
zs1e1ne
zse2raj
zse1ra
zse2r1á2
zse2rel
zse1re
zsere2s
zse2r1es2z
zse2r1ék
zse1ré
zse2ri2n
zse1ri
zse2r1o
2z1se1se
zse2s1zü
zses2z
zs1e1vé
2zs1ex
2zsez
1z1sé
z3ség
zsé2ge2l
zsé1ge
2z2s1éhs
2zsél
zs1é1le
2zsép
2zs1érd
2zs1ér1te
2zs1ér1té
3z4sé1ry
2z2s1érz
2zsé1te
2z2s1étk
2z2s1étr
2z2s1étt
2z2s1é2v2e.
zsé1ve
zs1fl
zs1fr
zs1gr
1z1si
2z2s1i2dő
2z2si1gá
2z2s1i2gé
zsi2kor
zsi1ko
2zs1ill
2zs1i2má
zsi2min
zsi1mi
zsi2m1u
2z2s1i2nas
zsi1na
zs1i2pa
z3sipk
2zsirat
zsi1ra
zs1ira1to
2z2s1i1ro
2z2s1irt
2zs1ism
2zsist2
zsi2tat
zsi1ta
2z2s1i2vó
z2s1i1zé
2zs1izm
2zs1i2zo
zs1izz
1z1sí
2z3síb
zs1íj
2z3sík
2z3síp
zsí2r1a2
zsí2rá2
3zsír1bó
3zsí1ré
3zsíros
zsí1ro
3zsírr
3zsírt2
2zs1í2v
2zs1í2z
zs1kl
zs1k2r
1z1so
2z3sof
2z2s1okm
2z2s1ok1ta
2z2s1o2laj
zso1la
zs1o1li
3zsom
2zs1op
2zsor
z3so1ro
2zs1ost2y
2z2s1os2z
1z1só
zs1ó1né
zs1ó1rá
1z1sö
2zs1ö2kör
zsö1kö
2zs1ö2lőz
zsö1lő
2zs1ös
z2s1ö2ve
1z1ső
zs2pir
zs1pi
zs1p2l
zsp2r
zs1s2k
zs1s2p
zs1st
zs3s2z
zs3sze2r1e2l
zs1s1ze
zssze1re
z3s2tad
zs1ta
z3s2tat
z3s2top
zs1to
zst2r
zs1t1ro
z3s2tú
1z1su
2zs1ud
2z3sugá2r.
zsu1gá
2z3sugárr
2z2s1ugr
3zsul
2zsum
zsu1s
zsus2z2
1z1sú
2zs1új
2zsúl
zsú2r1a
1z1sü
2zsüg
2zsült
2zsünn
zs1üs
2zsüt
2zs1üz
1z1sű
zs1wh
zs3zac
zs2z
z1s1za
z3szag
zsza2ké
zsza2k1ü2
z3szav
z2s3záp
z1s1zá
zs3zás
z3szem
z1s1ze
zs3ze1né
z3szer
z3s1zé
zsz2f
z3s1zi
zszind2
z3s1zí
z3sz2k
zsz2l
z3s1zo
z3s1zó
z3s1zö
zsz2p
z2s3z2s
zsz2t2
z3s1zü
z3s1zű
zt1ab1la
z1ta
z2t1a2bor
zta1bo
z2t1a2cé
z2ta2dal
zta1da
zt1a2dat
z2t1a2dó
z2t1ag1re
z2t1a2ká
z2t1akc
z2t1akk
zt1akn
ztaköz1
zta1kö
z2t1akv
zt1a2la1pú
zta1la
ztale2l
zta1le
zta2le1le
z2t1a2nal
zta1na
zta1n2e
z2t1a2n1es
zt1an1ké
zta2n1ó2
z2t1a2nyag
ztan2y
zta1nya
zt1anyak
zt1anyas
z2t1anyád
zta1nyá
zt1anyám
zt1anyá1u
z2t1a1nyu
zt1a2pán
zta1pá
zt1a2pás
zt1a2pát
z2t1a1pó
z2t1app
z2t1aps
zt1a2puk
zta1pu
z2t1a2r2a.
zta1ra
z2t1a2rán
zta1rá
z2t1arc
zta1so2
zta1sp
z2t1as2s2z
z2taszt
ztas2z
z1t1asz1ta
z2tati2g
zta1ti
z2t1atk
z2t1at2y
z2t1a2ut
zta1u
z2t1ábr
z1tá
ztá2c2s
zt1á1c1so
z2t1á2g.
z2t1á2ga
z2tá1li
z2t1álm
ztá2raj
ztá1ra
ztá2r1ass
zt1árem
ztá2r1e2
ztá2ris
ztá1ri
z2t1ár1nö
zt1ár1nya
ztárn2y
z2tároc
ztá1ro
z2tárokk
ztá2r1olt
ztá2r1ó1ra
ztá1ró
ztár1sm
ztár1s2p
ztárt2
zt1ár1ví
ztá2s2z
z2t1á1s1zo
z2t1ászt2
z2t1át1ha
z2t1áts
z2t1átv
ztá1vi2
ztá2v1ir
zt1bl
zt1dr
z2t1e2b
z1te
z2t1ef
z2t1e2gé
z2t1eg2y
z2t1e2k2e.
zte1ke
z2t1e2kés
zte1ké
z2t1e1la
z2t1e1lá
z2t1elb
z2t1e2leg2y
zte1le
z2t1e2le1me
z2t1e2lemm
z2telemz
z2t1elf
z2t1el1já
z2t1ellát
ztel1lá
z2t1elm
z2tel1nö
z2t1e2lo
z2t1e2lőa2dá
zte1lő
ztelő1a
z2t1e2lőc
z2t1elr
z2t1el1tá
z2t1el1to
z2t1ember
ztem1be
z2t1e2mel
zte1me
z2t1eml
z2tent
z1t1en1te
zte2raj
zte1ra
zte2rak
zte2rál
zte1rá
zte2rár
z2ter1de
zter1d2
ztere2ot
z2tere1o
zte1re
zte2re1po
zte2rill
zte1ri
zte2ris
zte2riz
zte2ros2z
zte1ro
z2t1e2rot
z2t1e2ró
z2t1e2rő
zte2r1ü2lé
zte1rü
zte2s2z
z2t1e1s1ze
z2t1e1s1zé
z2t1eszk
z2t1ezr
z2t1é2g.
z1té
z2t1égb
z2t1é2gé
z2t1égr
z2t1é2j
zté2k1e2l
zté1ke
z2t1ék1né
z2t1é2le
z2té1lé
zt1é2lés
z2t1élm
z2t1é2lő
z2t1élt
z2t1é2lű
z2t1é1pü
zté2rá
z2t1érd
z2t1érl
z2t1ér1té
z2t1érz
zté2s2z
z2t1észh
z2t1észn
zté2tét
zté1té
z2t1étk
z2t1é2ve2s.
zté1ve
z2t1évt
zt1fl
zt1fr
zt1gr
z1t2hen
zt1he
ztia2g
z1ti
zti1a
zt1i2den
zti1de
z2t1i1dé
z2ti1di
z2t1ifj
z2t1i2gé
z2t1i2kon
zti1ko
z2t1il1le
z2ti1má
z2t1imp
z2t1ind
z2tin1fe
z2t1in1fo
z2t1in2g.
z2t1in1gá
z2t1ingb
z2t1in1ge
z2t1in1gé
z2t1ing3g
z2tin1gu
z2t1in1te
z2t1in1té
z2t1in1vá
zti2p
z2t1i1pa
z2t1i1rá
z2t1isk
z2t1ism
z2t1is1tá
z2t1is1te
z2tital
zti1ta
z2t1i2tat
z2t1íj
z1tí
z2t1í2r
zt1í2ve1ke
ztí1ve
zt1kl
zt1kr
zt1kv
z2to1i
z1to
z2t1o2koz
zto1ko
z2t1ol1tó
zto2lyag
ztol2y
zto1lya
z2t1o2pe
zto2ras
zto1ra
zto2r1e2
z2t1or1gi
ztor1g2
ztorkés2z1
ztor1k2
ztor1ké
zt1ornam
ztor1na
z2tor1rú
z2tor1vo
z2t1oszl
ztos2z
zt1osz1tá
ztó1á2
z1tó
ztóigaz1
ztói2g
ztó1i
zt2ói2ga
z2t1ó2ni
ztó1p2
ztó1sp
ztó1tr
z2t1ö2ko
z1tö
ztö2l
z2t1ö1le
ztön3n
z2tö2röks
ztö1rö
z2t1öss
zt1ö2vez
ztö1ve
z2t1övig
ztö1vi
z2t1öz
ztő1a2
z1tő
ztőe2l
ztő1e
ztőé2b
ztő1é
z2t1őrh
z2t1őrl
z2t1őrs
zt1ő2se1i
ztő1se
z2t1ő2sö
z2t1ő1sű
zt1pl
zt1pr
zt1ps
z1tranz
zt1ra
zt2rá1dá
zt1rá
z1t2rág
z1tréf
zt1ré
z1t2rén
z1t2ri1kó
zt1ri
z1tril
z1t2ri1ó
z1t2rü
zt1sl
zt1sn
zt1sp
zt1st
zt1s2z
zt1t2r
z2t1udv
z1tu
z2t1ug
z2t1u1ra
z2t1u1ru
ztus3s2
zt1u2tat
ztu1ta
z2t1u1tu
z2t1új
z1tú
z2t1ú2r.
z2t1úrb
z2t1ú2ré
z2t1úrh
z2t1ú2ri
z2t1úrk
z2t1úrn
z2t1ú2ro
z2t1úrr
z2t1úrt
z2t1üg
z1tü
ztü2lé
z2t1ülés
ztül1l
z2t1üt
z2t1ü2v
zt1ü2zem
ztü1ze
z2t1ű2r.
z1tű
z2t1ű2ri
z2tye
zt2y
1zu
zu2b1a
3zubb
zu2b1i
zu2bu
zu2c3s
3zu2g.
zu2gag
zu1ga
zu2ga1p
zu2gá2ru
zu1gá
zu2g1ás
3zugb
zu2g1e2
zu2gé
zu2gin
zu1gi
zu2gi2t
zu2giv
zu2gí
3zu1go
zu2g1or
zu2gö
zu2gü
zu2gű
3zu1ha
zu1le2
zu2le2l
zu2l1enc
zu2l1es
zuli2ná
zu1li
zu2lú
zu1lya2
zul2y
zu2ly1ag
zu2mab
zu1ma
zu2m1a2d
zu2maj
zu2mal
zu2mar
zu2m1as
zu1me2
zu2megyez1
zume2g
zum1eg2y
zume1gye
zu2mel
zu2m1en
zu2mél
zu1mé
zu2m1é2n
zu2m1id
zu1mi
zu2m1in
zu2m1i2p
zu2mí
zumkés2z1
zum1ké
zu2mol
zu1mo
zu2mő
zu2mu2r
zu1mu
zu2mü
2zund
z1un1do
zu2ne
zu2n1é
2zu1ni
zu2n1ö2
zu2ral
zu1ra
zu2ram
zurat2
2z1urn
zu2rú
zu2sad
zu1sa
zus1a1ka
zu2sal
zusa2n
zus1an2y
zu2sas
zu2s1ág
zu1sá
zu2s1e2
zu2s1ér1té
zu1sé
zu2sis
zu1si
zu2s1í
zuskés2z1
zus1ké
zu2s1ol
zu1so
zu2s1ó2
zu2ső
zuss2
zus2s2z2
zus3s1ze
zus3s1zi
zust2
zus1tr
zu2s1ü2
zu2sz1i2k
zus2z
zu1s1zi
zu2szon
zu1s1zo
zu2s3zó
2z1u2ta
z1u2tá
2z1utc
zu2tol
zu1to
zu2t2ó.
zu1tó
zu2tób
zu2tó1i
zu2tój
zu2tón
z1u2tu
1zú
zú1dr
zú1fl
3zú1gá
z1újd
z1ú2jé
z1ú2jí
z1újs
zú2r1e2
zúré2t
zú1ré
z1ú2szás
zús2z
zú1s1zá
zú2t2é.
zú1té
z1ú2tér
z1úth
z1ú1ti
zú2t2i.
zú2tia2k.
zúti1a
3z2útia1ka
zú2tiakr
zú2tib
zú2ti2g
zú2tih
zú2tin
zú2tiv
z1ú2tí
z1útj
z1útn
z1ú2to2n1
zú1to
z1út1ró
z1útt
zú2tü
z1útv
3zú1zá
3zúzd
3zú1zó
1zü
zü2dü
zü2gy1és
züg2y
zü1gyé
z1ügyk
z1ül1dö
z1ü2lep
zü1le
z1ü2lőseb
zü1lő
zülő1se
z1ü2lőset
zü2ni
2zünn
zü2rí
z1ü2rü
z1ü2te
z1ü2té
z1ütk
z1ü2tő
zü2ve
zü2vö
zü2zen
zü1ze
z1üzl
1zű
zű2c1sa
zűc2s
zű2c1sá
zű2csip
zű1c1si
zű2c1s1o
zű2ra
zű2rá
zűrés3s
zű1ré
zű2r1i2z
zű1ri
z1űr1la
zű2ró
zű2ru
zű2z1a2
zű2z1á
zű2ze
zűzé2r
zű1zé
zű2z1é1re
zű2zo
z1ű2ző
zű2z2s
zű2zú
zva2su
z1va
zváro1sé2
z1vá
zvá1ro
zváro2s1om
zváro1so
zve2gya
z1ve
zveg2y
zvé2nye2l
z1vé
zvén2y
zvé1nye
z2vi2t.
z1vi
zvi2tác
zvi1tá
z2vitj
zy1ak
z1yar
zy2be
zy2je
zy2ne
zy2né
zy2re
zy2rő
zy2sa
zy2so
zy2tő
zy2ve
z2z1áll
z1zá
zzá2r1ó2r
zzá1ró
zzát2
zzá1tr
zzi2n1é2
z1zi
zz2le
zz1l2y
z2z2s
z3zsák
z1z1sá
z3zsám
z3z1si
z3z1sí
z3z1so
PK
!<
˘hyphenation/hyph_ia.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ch2
.de4s
.in1
.se4i
a1a
a1b
a2b1l
a3b4lo
a1ca
a1ce
a1ch
a4ch2r
a1co
a1c2r
a1cu
a1d
1a2d1m
1a2d1v
ae1
a1el
ae1ro3
a1g
a4g3e1d
a1ge
1a2g1g
a1h
a1i1c
a1is
a1i1v
a1j
a1k
a1la
a1le
al3e1i
a1lo
a1lu
a1m
3ani1m
a1ni
an4s1p
an1s
a1o
a1p
a1q
a1ra
a1ri
a1ro
a1ru
a1ry
a1s2h
3asth
as1t
a1t
aty1r4
aty1
a1v
a1w
a1z
1ba
2b1b
b3bo
b1c
2b1d
1be
b1h
1bi
bi4s3a
b1j
b1lu
b2ly
b1m
b1n
1bo
b1p
1b2r
2b1s2
b1t
1bu
bu1e
bu1i
b1v
ca1i
2c1c
c1d
4c3en1n2
1ch2r
2ch1s
2ch1t
1chu
1ci
2c1k
1c2l
2c1m
co4c2l
co1c
co3p2
c1q
c2r
c1s2
2c1t
ctro3
ct2r
cu1a
cu1e
cu1i
1cy
cy4ne
cy1r2
c1z
1da
2d1d
1de
de4ru
de1s3e
de4s3o
de4su
2d1g
d1h2e
dia3s4
di1a
di3p4t
di1p
di4sa
di4s3e
di4si
di4so
di4s3u
2d1j
2d1m
1do
do4l3a
do1l
4d3o2s1m
d2r
dro3s4
du1a
du1e
du1i
2d1v
dy2s
e1a
e2au
e1b
e1ca
e1ce
e3ch2e
e3chi
e1co
e1c2r
e1cu
e1d
e1e
e1f
1e2f1f
e1g
e1h
e1i
e1j
e1k
e1la
e1le
e1lo
e2l3o1d
e4l3om
e1lu
e1m
e4m3a1g
e1ma
2en1l
eno3p4
e1no
e1o
eo3g2
eo3p2
e1q
e1ra
e1ri
e1ro
4ero1g
ero3p4
e1ru
er3ur
e1ry
es4e1m
e1se
e2s1t
es3u1e
e1su
e1t
eu1
eu4ce
e1un
e1v
e1w
1fa
2f1f
2f1h
1fi
1f2l
1fo
1f2r
f1t
1fu
1ga
2g1d
1ge
4g3evi
ge1v
2g1g
1gi
4g3i1ma
gi1m
g2l
2g1m
2g1n
1go
1g2r
2g1s2
1gu1
gym2
gy2m1n3
gy4na
gy4r3a
gy1r
2g1z
h2e
hec2
he2c1t3
he3ur
heu1
h3lo1c
2h1m
h1n
ho3g2
ho3p2
ho3rh
h2r
h1s
h1t
i1a
ia4l3a
i1b2
i1c
i1d
i1do3
ido1p4
i1e
i1f
i1g
i1h
i1i
i1k
i1l
i1m
i4ma1d
i1ma
1i2m1b
1in1f
1in1r
in1s2
1in1v
i1o1
io3g2
ios2
io2x
i1p
i1q
i1ra
i1ri
i1ro
iro3p4
ir3ur
is3ac
i1sa
is3as
is3au
is3e1q
i1se
is3es
is3i1l
i1si
is3in
i3s4ph
is1p
i1t
i1u
i1v
i1z
ka4le
ke1
kra1
4l3al1g
4l3ar1c
l1b
2l1c
2l1d
le3ch
le4i1d
le1i
2l1f
l1g
2l1h
1li
2l1k
2l1l
l2l3ur
2l1m
l4m3o1d
l1mo
l1n
2lo1d
l3o1do
4lo1pi
lo1p
2l1p
l1q
2l1s2
2l1t
l4t3un
lu1e
lu1i
2lur
2l1v
1ly
ly3ch1
1ma
2m1b
m1c
1me
me3ch
me4s3e
m1f
1mi
mi3p2
mi1s4i
m1j
m1l
2m1m
mme4n3
m1me
2m1n
mn3a1m
m1na
mn3as
m1no1
mn3o1b
mn3o1p
1mo
4m3o1ny
mo3p2
mo3r1r
mos4p
mo3s4t
2m1p
m3p1s
m1s2
1mu
2m1v
1my
my4r1r
my1r
1na
n1ae1
n3al1g
n3a1ni
n1a2p
n1au
n1b
n1c
2n1d
1ne
n1e1q
n1ex
n1f
n1g
n1h
1ni
n1i1q
ni3s1p
n2i1t
n1j
n1l
n1m
n1n2
1no
nob4l
no1b
n3o3s4p
n1ox
n1q
n1r
n1s
n4s3i1e
n1si
n4sir
n2s1l
ns2t
n1t
n4t3a1h
n4t3a1p
1nu
nu1a
nu1e
nu1i
n1v
1ny
n1z
o1a
o1b
ob3lo
1o2b1s2
o1c
oc3le
o1c2l
o1d
o1e
o1f
o1g
o1h
o1i
o1j
o1l
om4na
o2m1n
o1n2a
o1no1
onos4
on1s2
o1o
o1p
o1q
o1ra
o1ri
o1ro
or4rh
or1r
o1ru
o1s2l
o2s4po
os1p
o1t
o4t3ac
oto3s4
ou1
o1ug
o1v
oy1
o1z
1pa
pan3s
1pe
1ph
1pi
1p2l
pl4a
4pl3o1p
p1n
p2n1a
p2ne
1po
2p1p
p4p3i1a
p1pi
1p2r
2p1s
p4s3o1d
p1so
3p2sy
2p1t
1pu
pu1b2
2pu1e
pu1i
p2yl1
py4lo
qu2
qua4n
4r3al1g
2ra1q
4r3ar1c
r1b
r1c
2r1d
1re
re3ch
re1gi3
re1g
4r3en1n2
re3u1t
reu1
r1f
2r1g
1rhi
r1hu
r3hyd
r1j
r1l
2r1m
r1n
r1p
r1q
r1r
r2r3a1q
r1s2
2r1t
ru1a
ru1e
ru1i
r1v
r1w
ry4s1e
r1z
1sa
s3a1bu
sa1b
4s3a2c1t
2s1af
s3a1g2r
sa1g
s3an1n2
2s1a1p
2s1a1q
s3a2r1g
s3a2r1m
s3a2r1t
2s1b
1s2c
scle4
s1c2l
s1d
1se
2s1f
2s1g
s1h
1si
4s3i1ge
si1g
s3i1ro
s1j
1sk
s1l
s4la1v
2s1m
s1n
1so
so1b2
s3o1be
s3obl
s3o2c1c
so1c
s3o1do
so1d
s3o2r1d
s3o2r1g
s3o2s1s2
2s1ox
s1p
2s1pa
spa4i
2s1p2l
2s1po
s1q
s1r
2s1s2
s3sa
s1t
1su
su1a
su4ba
su4b2r
su1e
su1i
2s1un
2s1v
1sy
4t3al1g
4t3a2m1b
ta1m
4t3a2r1t
2t1d
4t3e1co
2t1f
2t1g
th1l
2t2h1m
ti3s4p
t1l
2t1m
t1mo1
to3s4p
4t3ox1y1
2t1p
t2r
4tr3or
2t1s
2t1t
tu1a
tu1e
tu1i
tu4s3a
ty1
2t1z
u3a1ni
u1as
u1a1v
ub3al
u1ba
ub1l
ub3ro
u1b2r
u1ca
u1ce
uc3e1m
u1ch
u1co
u1c2r
u1cu
u1d
u1el
u1i1b2
u1i1c
u1la
u1le
u1lo
u1o1
u1ra
3ur1go
u2r1g
u1ri
u1ro
u1ru
u2s1t
u1t
u1u
u1v
va1i
vi3ru
2vn
v2r
w2n1
x1a
x1c
x1e
x1h
x1i
x1o
x1p
x1q
x1s2
x1t
x1u
xu1a
x1y1
1x2yl
y1a
y1b
yc1a
y1ce
ych1
y1co
yc4t3a
y2c1t
yd2r1
y1e
y1g
y1h
y1i
2yl
y3lac
yl3a1m
y1le
y1lo
yn3an
y1na
yn3e1g
y1ne
y1o
y1po1
ypos4
y1r
yro1
yros4
y1s1e
y1t
y1u
y1z
ze1
1zi
1zu1
2z1z
.a8l8c9u8n.
.a2l1c
.a8l8c9u9n8i8s9s8i9m8e.
.alcu1ni
.alcuni2s1s2
.alcunis1si
.alcunissi1m
.alcunissi1me
.a8l8c9u8n9m8e8n9t8e.
.alcun1m
.alcun1me
.alcunmen1t
.a9l8i8c9u8n.
.a1li
.ali1c
.a9l8i8c9u9n8i8s9s8i9m8e.
.alicu1ni
.alicuni2s1s2
.alicunis1si
.alicunissi1m
.alicunissi1me
.a9l8i8c9u8n9m8e8n9t8e.
.alicun1m
.alicun1me
.alicunmen1t
.m8o8s9l8e8m.
.mo1s2l
.mosle1m
.q8u8a8l8c9u8n.
.qu2
.qua2l1c
.q8u8a8l8c9u9n8i8s9s8i9m8e.
.qualcu1ni
.qualcuni2s1s2
.qualcunis1si
.qualcunissi1m
.qualcunissi1me
.q8u8a8l8c9u8n9m8e8n9t8e.
.qualcun1m
.qualcun1me
.qualcunmen1t
PK
!<`#⌸hyphenation/hyph_is.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.að3
.að1a4
.aðk2
.aðl4a
.a4f3n
.af3re
.a1fr2
.afr3í
.af1s4
.agn5ú
.a1l3e
.a2l4l
.a4m4s
.an3k2
.an1n4e
.an2n
.ar2
.ar3a4b5
.a2ra
.art1h5
.a1sp3
.as2
.a2um5a
.a4u2
.au4m
.ám4
.án4a
.á2n
.b2a4
.da4
.dam5a
.d2a1m4
.d2a1v3
.dr4
.ds4
.d1u4
.dus2t5
.e1d5ik
.ed1i
.eftir5
.e2f
.eft1i
.eigin5g
.e4i
.e4i2g
.eig1i
.ei4g3i4n
.eink4
.e1i2n
.ekt4
.e4k
.er4m
.e4r
.eyf2
.fa4
.fi4
.fjár3
.fl4
.fla4
.fr4
.frá1
.g1a2
.g1ar4
.g2as5l
.gas2
.gd4
.ge2m5
.gj2a2
.gl2
.gr4
.g1u4
.her5s2k4
.he4
.he4r
.il4
.ingj5
.i2n
.is4m2
.i2s
.ís1l2
.j2a2
.j1ar4
.ka4
.ka1l5e
.k2al2
.ka1m5e
.k2am
.kapí3
.k2ap
.k4art5
.k1ar
.k1as2t3
.kas2
.kn4
.kr4
.kur4
.k1u
.la4
.la1g5e
.l2ag
.leik5sv4
.le4i
.le4ik
.lei4ks
.lí4b5
.lu4
.m2a2
.m2aj5
.mak5r
.m4ak
.mi4
.mj4
.m1u4
.myns2t4
.my4n
.n1a2
.ní5f
.ný5f
.ol4íu.
.ol1íu
.ó1a4
.óð5al
.óð1a
.óf4
.ó2g2
.ók4
.óm4i
.ós2
.p1a4
.pl4
.ra2
.r2am4
.rí4
.róð4
.r4ú2m3
.sam1an5
.s2a2m
.sam2a
.s2k4
.s4k2i4
.skj4
.skr4
.sl2
.s1m4
.s3má3l
.s1má
.sn4
.sna2r5a
.sn1a
.sn1ar
.sp4
.spr4
.s2t4
.s4ta2
.ta4
.t1i2
.t2il3
.tí5f
.tr4
.t1u2
.tví1
.t1v
.ung3l
.unn5us4ta
.unn1u
.un2n
.unn2us
.unnus2t
.úr1
.út1
.ú4t5s
.veg4g5s
.vis2t3
.v2i
.vi2s
.yfir3
.yf1i
.ævi3s
.æv2i
.öfl3
.ögr5
.ö4g
.ör3s
.öt3u
2a3a4
a4a1b
a4a1d4
2a1á2
2a1b
ab4a
3a2b3ís
a5by
abæk4l2
4ac
2a1d4
a5dó
a4d1u
1að
4að1að
að1a
að3al
að3ar
2aðfa
að3f4
4að1fö
að3g
að3i
2aðl
að4li
4að2m2
2aðs1h
3aðu.
að1u
2að1v
2a1e
a5e4i
a4e2s
2af
af3a2ld
af2al
af3arn
af2ar
a3fá
af4fr2
af3g2
a1fj
a3fló
a1f2lu
af5ork4
a1fo
a1fr2
af3r4é
afr4u4
a5fræ
4afs3d4
af1s2k
4afs1so
afss4
af2u
a1f2ul
a1fun
3afur4ð
a2f3ur
2ag
a3g2a4u2
ag1a
ag5ál
agá2
ag5i2s
ag1i
a5gj
agn4a2ra
agn1a
agn1ar
ag2ne
ag5o4t
a1gr
ag4ra
ags4ið
a4gs
ags1i
ag4sp2
ag4stj4
ags2t
ag4ul
a2g1u
ag3æð
a1gæ
4a1h
4ai
a3ið
a4i2n
aí4h
2aj
a3jö
2ak
akaup4s
a1k2a4u2
a5ka2up
a5ká
a1ke
a5k1i2nn1i2n
ak1i2n
akin2n
akin4n1i
a5ki2s4t1u
ak1i2s
akis2t
a1kj
ak5re4i
aks4l
a4ks
4akss
a2k3u
a3kv
a5ky
a5ký
a3kö
2al
a1l2ag
4al2and
al1an
a5landið
aland1i
a5landin1u
aland1i2n
a2las4
alá4
a4le2m
a1le
a2l3e4r
al1f
a4l1g
5al1ge
3al1h
al1i2s4
al4í2n
al3kj
a2lk
al4k5s
al3k2u
al4lí
a2ll
al2l2s
alls2t4
al4lý
4alm1að
a2lm
alm2a
al5man2n
alm1an
al5m2i
a3log
alo4
a5lok
al5op
a5lóð
al4sæt
a2ls
al1sæ
al5up
a2lu
al5ú
a2l3v4
a3ly4
a1læ
a1lö
2am
am3ang
am2a
am1an
a3man2n
am3ari
am1a2r
am3ar4s
am3a4s2
am3dr
a3mí
am3k
a4m2m
amm4i2s
a1mó
am3se
a4ms
ams2i
am1t
1an
3an.
3a4na.
an1a
an3a2da
an2a1d4
4an2ag
2an2al2
4an2a4u2
2a1ná
4and.
and1e
4an4dí
andr4
4an3dö
4a1ne
an5e4i
2ang
an4g1a
ang1d
an4gj
ang5sp1i
ang4sp2
an4gs
an2g3ö4
4an4i1b
a4n1i
4anið
an5í2n
2ank2
ank5l2
ank3v
3an1m4
ann5d
an2n
4ann1f2
4ann1h
ann1k2
4ann1l2
4anns
anns2k4
4an4o
ans2t4
an3st1i
an3tí
3an1u
4anú
4a1ný
4a1næ
4anö
2a3o
2a3ó
2ap
ap3al
ap1a
a3pe4r
a3p2il
ap1i
a3po
a5pre
ap3te
a5p4un
ap1u
a5pör
1ar
3ar.
ar3af2u
a2ra
ar2af
ara5kr
ar2ak
ar3ang
ar1an
4ara4n1i
ar4arp
1a4r3ar
ar2as2
ar5a2st.
ar1as2t
ar3a4u2
2ará4
4a1r4áð
5ar3ák
3ar3ár
3ar1ás
ar1át
3ar1d2
4ar2dí
4arð.
ar4ð
arð4ar5á4
arð1a
arð1ar
4arð1i
4arðs
3arðu.
arð1u
2are
3ar1e2f
4ar4ef.
5ar3e4i2g
are4i
5are1i2n
a3re4k
ar3e2l
ar3e2n
ar5e4r
a3ret
4a1r4é
2arf
3ar1fe
3ar1fé
arfs3k
ar1f1u
ar3gj
ar4græ
ar1gr
ar4gs4
arg3sl
4ar5ið
4ar4if
a4r3i2n
ar4ind
ar5i2st.
ari2s
aris2t
4a5r2i4t
4a1rí
ar3j4
4ark.
ark4i2s
ar5kj
ar3kr4
4ar4ms
ar3mú
4ar4na.
arn1a
4aroð
aro4
4arol
4ar4o4t
2aró
2arp
4ar2ra
arr2i
arr4u
ar4sá4
3ar1se
ar1s2k
ar4sp1i
ar1sp2
ar1s2t4
5ars1tí
ar3s2u
ar5t2il
art1i
artr2
ar5tr1að
art2ra
ar3u
ar4uð
4ar4ug
5aru2m
2ar1ú
a5r2ús
ar3v4
ar3yf
4arý
2aræ
a3ræð
ar5æv
4arö
as2
4a1sa
a2s3an
2a3sá4
4a1se
4a1sé
2as1i
a5sinn1a
as1i2n
asin2n
2a1sj
4a1s2k
2asl
a3sl2ag
asla2
2a1s1m2
4asn
4a1so
4a1só4
2a1sp2
aspí2t4
4ass
as1s4v4
1as2t
4ast2að
as4ta
ast4and
ast1an
4ast2a4u2
4a1ste
4a1sté
4a1st4i2g
ast1i
4as4t1i2n
ast4ing
4astir4ð
4a1s1tí
4a1stj4
a4st3l
4a1sto4
2a1stó
4astrá
ast2r
ast5r4áð
4a3strí
4a3stræ
4aströ
4ast2ul
a2s4t1u
4a3st4un
4a1st2ú
4a3sty
4a1stæ
4a1s4tö
2a1s1u
2a1sv4
4a1s4y
2a3s4ý
4a1sæ
2a1sö
2at
at3að
a3t2ak
a3t2al
at4an1v
at1an
a4t1a4r
a1te
a2t3ey
3at1h
a1t2il
at1i
a3tj
3at1k
atn2s3
a4tó
at2r4a
at1re
3at1ri
at2sp2
a4ts
a5t2un
at1u
a1tú
at4ve
at1v
3atv2i
a1tö
2a4u2
4auð.
auð5li
a2uðl
5a2uð4n
au4g4as2
a2ug
aug1a
au4m
aum5b2a
au4mb
aum5un
a4um1u
aun5dr
a2und
aup5e
a2up
aup3l2
a5up2p1
au4s
au1s2a5m
a2usa
a2u4t
2a1ú
2a1v
av4ar.
av2a2
av1ar
a3vir
av2i
2ay
a5yf
4az
2a1þ
a2þ6ó
2a3æ
4a5ö
á1a
á2ak4
áa5lo4
á2al
á1b
á4bu
á1d4
4áð
áð2s
áð3sn
áð3s4ta
áð1s2t2
áð2u
áð3us
á1e
áf2
á5fá
á5fí4
á3fl
áflæð4
áf5un
áf1u
ág2i
ágl4
ágr2
á4g1s
ág3v
á1h
á1i
ák2a
ák2am4
ák2ap4
ákó4
ál2a3m
ál3arf
á2l1ar
á2l2as2
á5l2a4u2
á3let
á1le
álf3d4
álf5s2k
á2l4m
álm5an
álm2a
álm5u
álp3
ál4se
á2ls
ál5sv2a2
álsv4
ál5ug
á2lu
á5lú
ál3æð
2ám
ámál5a
á1má
á3má2l
ámsl4
á4ms
á2n
án4aro4
án1a
án1ar
án2as2
án4o
á3ný
5á1næ
á3o
4áp
ár2a5g
á2ra
ár3an
á4r3ar4
á5r4é
ár3f4
ár5g
ár5k4
ár1m
árns4
árn5sl
ár2s2t
ár5t
ár1u
ár3v
á5ræ
ás5e2n
á1se
á1s2k
á2ska
á4ske
á3s4l
ás2m2
ás2t2
ást5i
ást5r4áð
ást2r
ást5rík
á3strí
á4st5v
4ás1u4
ás5v2a2
ásv4
át3að
á1te
átr4as2
át2ra
át4t1ar3
5áttin.
átt1i
át4t1i2n
átt3un
átt1u
á1tæ
á1u
á2uk4
á3ve
á3ví
á1væ
á1þ
á5æð
b2a
4ba.
baðk4
b1að
b4a4h
bak5s4l
b2ak
ba4ks
b1a2n
4ban1a
4baní
b1ank5an
b2ank2
4bans
b5ant
bar2b4
b1ar
barð4as2
bar4ð
barð1a
bar2n
bá4s
2b4b
bb3að
bb2a
bb3a2n
bb5ar2n
bb1ar
bb3i2
bb3uð
1be
bes2s4
be2s
b2i2
bið5i
bið5l1i2s
b4ið5r4
b4if1
bilj5
b2il
bi2ll5
4bin2n
b1i2n
b2is2k3
bi2s
b4it
bit2a
4bí1a
2b3ís
2b3íu
3bj
bj1ar2
bj2a
b4l2
bl4íun2n
bl1íu
blí3un
4bn
5bo
3bó4
bó4k3s
b4r2
br5að
b2ra
bra4s2
3bro4
br4u
3brú
4bum4
bur2
3bur4ð
burst5að
bur4s4ta
bur1s2t
bu4s
1bú
búf4
bú3s4ta
b2ús
bús2t
1by
1bý
bæk2i
bæn4a
1bær
b5ættis1m2
bætt1i
bætti2s
1bö
4böki
c4a
ci4
ck4
4da.
d2af4
d2ak2
3dal.
d2al
5da2ls
d2a1m4
d5ang
d1an
d4aní
d4ar2a1d4
d1ar
da2ra
d4ar4að
d3ar4ár
d2ará4
d3arf
d5arp
d4a1s2k4
das2
d2a3sl
4d2a1v
d1b
2dd
4dea
d5e2f
3deg
1de2il
de4i
d1e2n
d5eó
5dep
d1f4
d1g2
d4g1i
d1h
d1i
di5f1u
d4if
d3i2ll
d2il
d4im4
di3m2a
d2i3n2a2v
d1i2n
din1a
d2i1ne
dir3s
dí3d
d5íð
d5ín1u
dí2n
3dís
dív3
3dj4
djarf5a
dj2a
dj1ar
dj2arf
d1k2
d1l2
d2la
d2l3að
d3l2ag
d3l2a4u2
d4lið
d2l3u
d1m
d5olf
d5óð
d3óf
dó5lí
1dó2m
dóm5an
dóm2a
dó5ne
dó4n
3dór
dó2s
dó1s5e
4dq
d5rað3f4
d2ra
dr1að
dr4aðs5
d5rað1s2t2
dra4fn4
dr2af
d2r4a2ra
d4r3ar
3dreg
d5re2ip
dre4i
d5rey
d5rið1a
d3rík
d5roð
dro4
dru4
d4ræt
2ds
d5s2al
d4sjö
d1sj
ds2k2
ds4ko
ds5kun
ds2k1u
ds1l
d5snö
ds1s4
d1s2t4
d4st1i
ds4uð
ds1u
d4söf
d1sö
d1t2
d1u
d4uf4
5duft
d2uk4
d4u4mb
d2u5ræ
d4us4ta
dus2t
d3ú4n
dún4a
dút4
d1v4
4dw
1dy
1dý
d3þ
1dæ
4d4æf
4dær
3dö
ð1a
ð2af4
ð2a3g
ð2ak4
ð4al5bo
ð2al
ða2l1b
ð3a2ld
ðal4i2s4
ð4a1m4
ð3a4n
ðanmeg4
ð3an1m4
ðan1me
ða2r1a
ð1ar
ð3ar3f
ðar3l2
ðar4l1i2s
ðar2li4
ðar3t4
ð3ar1ú
ðat4a
ð2at
ð3a4u2
ð3á2
ð1b
ð1d2
ð1e
ð3f4
ð1g
ð2g1a
ð2g1i
ðg4l
ð4gn
ð2g1u4
ð3g4ul
ð1h
ð1i
ð4i3g
ð4ik4
ð3i2n
ðis5l
ði2s
ðis2t4
ð4is4ta
ðis4ve
ðis1v4
ðj3as4
ðj2a
ð3jó
ð4j1u
ð3ke
ð1kr
ðk2un
ð2k1u
ð3kun2n
ð1kv
ð1lá
ðl2i2n
ðl4is2t
ðl1i2s
ð3lí
ðl4ur
ð2lu
ð1læ
ð1m2
ðnum4
ðn1u
ð3o
ð3ó
ð1p2
ðr5át
ð1re
ð1r4é
ð3r4ik
ð3rík
ð5ró
ðr1u
ð3rú
ð1ræ
ðræði2s5
ðræð1i
ðs5afl
ðs2af
ðs5á4
ð4seg
ð1se
ðs4in2n
ðs1i
ðs1i2n
ð1s2k
ðskr4
ð4skú2
ð2s5kv
ð4skö
ðs4lu
ð2so
ð2s3or
ðss4
ð1s2t2
ð2s1tí
ð3s4tö
ðs4u
ð4sv2i
ðsv4
ð2sæ
ð1t4
ð1u
ð3uð
ð3ug
ð3u2ll
ð2ul
ð5u2ls
ð5un2n
ð3un1u
ð3ur
ð2ur5á
ður3f
ður5g4
ður5s2t
ð1v
ðv5að
ðv2a2
ðv3an2n
ðv1a4n
ðv3arn
ðv1ar
ð1y
ð5ý
ð1þ
e2at4
ebr5e
eb4r2
ed4e
e1d4ik
ed1i
ed3it
eð3a2
eð2al1
eð3il
eð1i
eð4i2s
e2f
e3f2al
ef3i
efj5an
efj4a
e4f4n
4eft.
2ef4t2s5
eg4ge
eg3i
egr5u
eg3u4l
e2g1u
egur4s
e4i
eið5ar
eið1a
eið4s2t2
e4iðs
eif4as2
e4if
ei4ka
e4ik
eik3li
eikl2
ei2k3u
eil3a2g
e2il
eil5ö
e4i2m
eim5u
eing4
e1i2n
1eink2
4ein1o
eist5að
ei2s
eis2t
eis4ta
e4k
ek2i2s
ekj5a
ekn3
e2k3u
ek5ú2
e2l
el5ás
e2l4d
el3eg
e1le
elf3i2n
elf2i
el4gr
e4lg
el1i
4elí
el3í2n
el4ke
e2lk
e2l2l
ell5an
el1l3e
elleg4
el3ó
e2l3r
el1sí4
e2ls
5els2k
el4te
e4lt
elu5s
e2lu
e2m
em4b2a
e4mb
1em1be
em5ens
e1me
eme2n
em3i4
emj3
em4l
em2l2i
empl5
em5u
e2n
4enc
en4g
eng3a
en4ik4
e4n1i
2en2n
enn4t
en4t
ep4h
ep3i
epl5i
epl2
e4pr
ep4t
e4r
er3al
e2ra
er5a4u2
er5á
4er4ð
5ere
er5et
erf5ar
er2fr2
erf3u
erg5l4
er4gr
er3i
er2k
er4la
er1l2
er2l3i4
er4lí
er4m2i
erm5i2n
er5ól
er5skj
ers2k
er1un
er2v
er3ö
eröf4
e2s
es3a
es4b1a2n
es1b
esb2a
es4bu
4e1sj
e5ske
es2k
4e1so
esp3a
e1sp2
es2s
est3að
es2t
es4ta
e1s4t5ö
e3sæ
et4ik4
et1i
et5i2ls
et2il
etn1a4
et5o4
etr5an
et2ra
etr3u
et2ul4
et1u
et2us
ext4u
5ey4r3ar
ey2ra
é3b
é1d
éð2s
é3fe
é1g4
é5ky
é1l
él2a4g
él4a2ra
é2l1ar
é2li
é4lj
é2l3r
ér1
ér4á
ér4r
érs4
ér4un
ér3v
é3s1m2
étt4ug
étt1u
ét4un
ét1u
étur4
f3að
f2a2g
f2a5k
1fa2ll
f2al
fa2l4u
f2a4ná
f1an
f2a5p4
f2ar
f3ar4ð
f4are
f4arg
fark2a5l2
f3ar1l2
f4a1st3e
fas2
f1as2t
fá3f2
f5áns
fá2n
fá3rá
fá3ta
fá3v
f1b4
fd2an
f1dr
f5dú
fð2a4l
fð1a
1fe
f3e2f
f3e1i2n
fe4i
3fe2l
f3end
fe2n
fer2l5i4
fe4r
fer1l2
fer3t
f1ey
1fé
f4fa
f2fo
f1fr2
ff4s2t
ffæ2r4a
f1fæ
f1g2
f2g1a
f2g1i
fg4n
f1h
f3ið
f4ik4
f5il.
f2il
f5ili
f3i2ll
f5i2ls
f5i2l1v
fim3a
f4im
f4i1ne
f1i2n
f3in1u
3f2irð1i
fir4ð
f3irð1u
firg2
f3irn
1firs
3f4is2k
fi2s
fi5so
f3i2st.
fis2t
f3i2s4t1u
fis5v4
fí4
f5íkv
f3ís
fj4a
fj5að1u
fj3að
fj5an2n
fj3an
fj4e
f1k2
fl3and
fl1an
fl4at
3flata
f5lá4t
f4le2n
f1le
f4l5g
1flo4
5fl2ut
f2lu
f2l1v4
4f1lý
1flö
f1m
4fn
fn1g
f5n4í
fn3k2
fn5ok
1fo
4fop
forf4
fork4
1fó
fr2
4f4ra.
f2ra
fr3að
fr2a4m
fram3l
fr5and
fr1an
4f4r3ar
2f4ri.
f4r5i2n
fr4í5m4
f3róf
fru4
fr3uð
1fræ
2f3ræk
4f1ræn
fs3á4
f4se2l
f1se
f4sk2af
fs2k
fsl4
fs3li
fs5n1a
f2so
f2s5or
fsr4
fss4
f4st1i
fs2t
f5s4tæ
fs1v4
f4sæ
ft3að
ft4a4s2k
ft1as2
ft5á4
f3te
f4t2s
fts5l
ft3u
ft4ul
f1u
4fu.
f3uð
3fugl
f2ug
fund4as2
f2und
2f3ur
5furð2ul
fur4ð
furð1u
f2ur5e
furs4
fur3t
4f2us
f5u2st.
fus2t
f3u2s4t1u
f1v4
1fy
f3yrt
f1þ
1fæ
1fö
för4l2
g1a
4ga.
4g3að
gaf4ar
g2af
gagl4
g2ag
3gagn
g2ak4
ga4l2ag
g2al
gal4i2s4
5ga2ls
5ga4lt
gam3al
g2am
gam2a
gam4i
g5and
g1an
1g2ang
gap4a
g2ap
g4ard.
g1ar
g3ar1d2
g2ar4ð
3g4arð.
3garð1a
3g4arð1i
3g4arð4s
5garð3ur
garð1u
g3ar3f
gar4kl2
gar3l2
gar3t
4gas2
g4as.
g4así
g3a2st.
g1as2t
g4a5st2að
gas4ta
1gata
g2at
gat4r
gá2
3gáf2
gá5le
g1b
gd4ans
gd1an
g3de
g4d1u
gd4v4
gð4aro4
gð1a
gð1ar
1ge
3ge4i
2g1e2l
ge2n4
g5end
3gen4g
g3en4t
5g4er4ð
ge4r
ger5í
germ4
gerv5a2
ger2v
get3r
g1f2
ggi2s5
gg1i
ggj2af3
ggj2a
ggj5ar
gg5rá
gg3ræ
ggs4v4
g4gs
gg3ug
g2g1u
gg2v
g4gö4
g1h
g1i
4gi.
4g4ik
gi5kv
3gi2ld
g2il
g5i2ll
5gi4lt
gim4a
g4im
4g3i4n
g2is4a
gi2s
g4i1sp2
g5i1stæ
gis2t
gí2r5a
gís4
gjaf5ar
gj2a
gj2af
gja1f5o
gj2ak4
gj2al4
gj4as4ta
gjas4
gj3as2t
1gjö
g1k4
gk2al4
g1lá
g4leð
g1le
g3leg
gl5é
gl3f2
g3l2it
g5lí2n
gl3ót
gls2k4
g2ls
3glugg
g2lu
gl2ug
glu3s
gl4y4
g3lær
g1m4
gn2ap4
gn1a
gnart4
gn1ar
g4ná
gn3g
g3nó
gn4se
gn5ug
gn1u
g4ný.
g1ný
3gol
g3or
3góð
gór5
gó4ur
gó1u
g1p
g3r2ak4
g2ra
gr4an
gra2s2
gras5s4v4
gr4ass
g2rá
1gre4i
g3rei2s
g5reyð
gr2i
2g4ri.
grið4
g3rík
3gró
3gr2und
3grun2n
g3ræð1i
g2ræn
4gs
gs1á4
g4se4i
g1se
gs2k4
gs4le2n
gs1le
gs2l2u
gs4lö
gs3m2
g2s5or
g1so
gs3s4
gs4s1i
gs1v4
gt3að
g3te
g4t4s
2g1u
g4uð1i
g2uðl4
5g2uðs
guð4só4
g3uð1u
5guf1u
g4uf
gu4lag1i
g2ul
g4u1l2ag
g5ul1i2n
g3u2ll
g3u2ls
gu5mið
g4umi
g3un
gurf4
gur3g
g4us4ta
gus2t
g3ú
gús4t5a
g2ús
gús2t
g1v
g2v2a2
g3v2al
5gyð
g1þ
1gæ
gæð4as2
gæð1a
gæ2l4
gær3
1gö4
h2a4
4ha.
h1að4
haf4sp2
h2af
hand5r4
h1an
hat3r
h2at
há3g
hál4
há3re
há4s4k2i
há1s2k
há5s4ta
hás2t2
h4ás4u4
hát4
há5t1i
hátr4
há1v
he4
he4i2
hell1i2s5
he2l
he2l2l
hen2r
he2n
her2k4
he4r
her3l4
hers2k4
4hersla2
hey5s2t
hey2s
4hg
h4i
h4i2g4
hi4m5b
h4im
h1i2n4
hit4as2
h2it
hit4n
2hí2
h5í1a
hí5b
hj4
h2l2
4hl.
4h2ls
h2lu4
h4n2
hnj2a4
hnjá3l4
hol2l3u
ho2ll
horm5
hó2
hó1m5e
hót3
hr4
4hs
hu4
hug1r
h2ug
hu2g3u
hul5i
h2ul
hundr4
h2und
hú2
h4ú2m3
4húr
hús5k
h2ús
hv4
hv1ar4
hv2a2
hv4ik3
hv2i
5hy
hæf5a
h4æf
hætt4us
hætt1u
hös2t3
hö4s
2ia
i5as2
4i5á
4i1b
i2b4b3
ibl3í
ib4l2
4ic
2id4
i3da
i1de
i3dr
1ið.
4ið3f4
2ið3g
ið5jar4ð
iðj2a
iðj1ar
4iðr
4iðs
ið5s2al
2ið1u
4ie
i3e2f
4if
if5arn
if2ar
if2at4
i2fe2n
i1fe
if4g2
if3i2s
if4t
i4fy
4i2g
ig2a3m
ig1a
igð5u
ig1e2n4
i1ge
ig5rí
2i1h
2ii
i5ið
4ij
4ik
ik5á
ik3i
ik4i2s
ikt5o4
i4kvö
2il
il2a2g
il4a2ra
i2l1ar
i1lá
4i1le
ilf4a
il1f2i
i4l1g4
i5l2ið1u
il5i2n
il3ip
il5ís
ilí4um
il1íu
il4kv
i2lk
il3l2ag
i2ll
ill5an
il3lá
i1lo4
il4sa
i2ls
il1s2k
il3sn
ils2t4
ilæk4
i5lön
4im
i4m4b
imb3u
im3i
im4ið
im4la
im1l
i4m4m
i5mó
im4sv4
i4ms
1i2n
4in2af4
in1a
in3an
4in1ar
inat5r
in2at4
2i3n2a4u2
2in2a2v
inav3í
4i1ná
4ind.
in4g1a
ingj6ar4n1i
ingj2a
ingj1ar
4ingr
ing1v4
2inns
in2n
2inn1u
in1o
4inó
in1s4k
in2sp2
in3s4ta
ins2t
in5u1l
in1u
4i1ný
2i3o
4ió
2ip
i5p2il
ip1i
3ir.
4i2ra
2i1rá
2irð1i
ir4ð
4irðn
4ire
4ir4é
irf2i
4irfs
4ir4i2g2
2i1r2i4t
4i1rí
2irk
ir2k3u
ir3l2
ir1m4
4irnd
4irni.
ir4n1i
4irn1i2n
4irni2s
4iró
irs4á4
ir1s2t4
ir3t2al
irt5i
4irtl
irt4ö
ir3ug
4irú
4irö
i2s
1is.
2isa
4i1s2ag
i5sag1a
i3s2a2m
i2s2as2
3is3d4
2i1se
4ishv4
is1h
3i2s5ins
is1i
is1i2n
4isí4
4i1sj
i3sjó
2is2k
is4ka
isk5e2l
isk4i3m2
is4k2i
isk5inn.
isk1i2n
iskin2n
3is1ko
is2k2u
i2sk1v
5iskö
2i3s4lé
is3læ
5i4sm2a
is1m2
i4s5me
4is4n1i
isp3að
i1sp2
isp1a
isp3u
iss2i
is5sú4
i4st3al
is2t
is4ta
2i1ste
4i1sté
4i1st4i2g
ist1i
i5st4ik
i2st4i2s
is4t5í2n
is1tí
4i1sto4
4i1stó
4isty
4i1stæ
2i1s4tö
is5tök
is1v4
4isv2i
is4v4ið
5is5v1i2n
i3s2væ
2i1sö
2it
it3að
it5an2n
it1an
i5te4i4
4it1h
it4h2a4
it5i2ll
it1i
it2il
itis4m2
iti2s
it2k4a
it1k
itn5e2s
it1ne
it3rí
it4s4tö
i4ts
it1s2t4
it3un
it1u
it4urn
it4ve
it1v
4iu
4iú
4i1v
i2v5ís
4ixs
2i3þ
4iæ
4iö
í1a
4í5ak
í2al3
í5a2ld
í3ali
í2as2
í5at
í5a4u2
í5á
íb4a
í5b1ar
í4be
5í1bú
í5d2ag
íð2s3
íð1s2k4
í3e2l
í3e2n
íet3n
í5ett
í2f
íf3eð
í1fe
í4f3i
íf5rí
ífr2
ífs3k
íf4sp2
í2g
í1g3e
í3g2il
íg1i
í1h
í1i
ík3a4
4ík4an1ar
ík3an1a
ík1an
4ík4a4n1i
ík5i2s
íkk2i
5ík1m2
ík2n
ík1n5e
í4k4s
ík3us
í2k1u
2íl
í2l2ak4
í2l2as2
í4l5ár
íl5f4
í3lí
5íl1má
í2lm
íl3s2k
í2ls
4ím
ím3að
ím2a
ím2a3l4
ím3an
ím4g2
ím5o
ím5t
ím3un
ím1u
í2n
í5ná
í1n5e
ín3ge
ín5t
ín2u3g4
ín1u
ín3ú
í3óp
íóp5íu
4íp
ír5ak
í2ra
íra4s2
í4r5i
ír4sv4
ír3t
í1ræ
ír5ö
í2se
í4s5j
ísl2
3ísle2n
ís1le
ísl5i
ís2m2
í2s2t
ís5te4r
í1ste
ít5að
ít5a2ls
ít2al
ít3i
ítj5
ít3re
ítr5ó
4í4u1b4
í5uð
4í2ul
í2u1m2a
í3un
í2u5p
í3ur
í4u2ra
4í2usa
í2u3t
4í4u3v
ívo4
í5þ
j2a
j3að
j5að3i
3ja4f4n
j2af
ja5kl2
j2ak
j2al2
jal5ið
ja5ló
j4am
j3an
j4ar2am
j1ar
ja2ra
j3ari
jark4i
jarn4ið
jar4n1i
j4aræ
jas4
jas2k4u
j4a1s2k
j3as2t
já2l5as2
jál4f
jálf3a
3járn
jár4u
jáv3
jáv1ar5
jáv2a2
j1e
jend4a
je2n
j4ep
j4e4r
j4et
j1i
jó5b
jó5fr4
jó2s
jó5ug
jó1u
jó3ve
j1u
j4uf4
j2ug2
ju4g5as2
jug1a
j4u4gó
j4u1l2
j2ur4e
5j2urt
jurt4i
j2us4
j3u2st.
jus2t
3jö4fn5
3jök
jö2l3k2
jö4l
jö2l1m
kað4s
k1að
kaf4an
k2af
ka4fr2
ka4g4as2
k2ag
kag1a
k2a4j
kaj5a
kak4l2
k2ak
k2al2
3ka2ld
4kal1f
k4a2ll
3ka4m4b
k2am
ka4m2s5
k3an1a
k1an
k4an2af4
k3ann.
kan2n
k4anó
k4ant
5kanta
4kap1a
k2ap
kark4
k1ar
1k2ar2l2
k4arm
k3arn
kar2r
k4art
kar4v4
k4a1s2k4
kas2
3k4ass
5ka4st2al
k1as2t
kas4ta
k5as4tu.
ka2s4t1u
1k2a4u2
5ka2up
kák5
k2ám3
k1b
k1d4
2k1end
ke2n
3k2en2n
3kerf
ke4r
5ker1l2
ker4m
k1f4
kfal2l2s5
k1fa2ll
kf2al
k3g
kgl4
k1h
k3ið
kið5l
k4i2g4
k5i2ll
k2il
k5ils1i
ki2ls
k4im2
k5ing
k1i2n
k4ip
k1ir
k3ir4ð
5k2irk
k5irn
k4irt
k1i2s
k5i1se
k2is2k4
k3i2st.
kis2t
k4it
ki3te
3k2íl
kís3
kít3u
kj2a4l2
kj2a
kja2r4a
kj1ar
kj2ar5f
kj2u3g2
kj1u
1kjö
3kjör
kk2a5l2
kk5e
kkj1ó
k2kl2
kk1lá4
kk4li
kk3rí
kk4se
k4ks
kk2ul4
k2k1u
kkv5ið
kkv2i
kl2
k2l3að
k4l3an2n
kl1an
klá4
5k2le2f
k1le
kleif5a
kle4i
kle4if
k4le2m
k1l1i2n
3klj
3kl2uk
k2lu
3klú
1klæ
k1m2
k4m1ið.
kn3ar
kn1a
k2ney
k1ne
kn4i5sv2i
k4n1i
kni2s
knis1v4
3knú
1ko
2kob
koff5
ko2f
ko2l5d
ko2l4l
kol5sv4
ko2ls
3kon
4kons
3ko4s
kot4as2
k4o4t
kó3d
kó3m4
kó2r5a
k1ótt
kr2a5l
k2ra
kr2as2
k2rá
k5r4é
kr2i
krif3a
kr4if
1krö
3kröf
4ks
ks4lí
ks4lö
ks4má
ks1m2
ks4n
k1sp4
k4ste
ks2t
k4stó
k4st2r
ksyf4
k1sy
kt3að
kt5e4r
k4tí
k3to2ra4
kto4
k5ty
2k1u
k3uð
k4uð1i
k4uf4
k2uk4
kulegr4
k2ul
k4u1le
kum4
ku4mb5
k5un.
k5u4na.
kun1a
k3un3ar1
k5u4n1i
5kunnátta
k2un1n1á
kun2n
k3un1u
k3ups
k2up
k2ur4ð
kur5k
k2us
k4u5sl
k3u2st.
kus2t
k5ustum2
ku2s4t1u
k4u3sty
kú2
3kú2lu
k3ú4n
kú3re
kút4us
kút1u
3kve2n
4kvé2
4k2vu
kv4un.
kv1un
1k4væ
3kvö
4kvör
k5þ
3kök
5kö2ld
kö4l
5kön2n
5kö4s
2la.
4l4ac
la4d1an
l2a1d4
2l1að
lað4al
lað1a
4l2af
laf4as2
la1f4r2
l3afs
3lagð
l2ag
4la1ge
2l2ak
la5kó
l2a1l2
l3a2ld
la4m4b
l2am
2lan1a
l1an
l2and
3l4and.
3lan2ds
1l2ang
lank5as2
l2ank2
4lan2n
l5an4na.
lann1a
l4anó
lan4t
la3pl4
l2ap
2l1ar
l4ar2am
la2ra
l4are
larg4
l4ari2s
l3arn
l2a3ró
lart4
l4ary
2las2
l2as3i
las3le
l2asl
la5sli
l4ast5að
l1as2t
las4ta
la4t3í2n
l2at
la1tí
lat4u
1l2a4u2
2lau4m
laut5as2
la2u4t
lá2g
lám5a
l2ám
lá1n5e
lá2n
4lár
lá4t
5látum2
lát1u
lá3v
2l1b
2ld
ld3ar
l2d3d
lde2m4
ld3ey
ld4i2g4
ld1i
ldr4as2
ld2ra
ld5r2a4u2
l4dri
ld5ro4
ldr3ó
1le
4le.
2le2f
l1e4f4n
4legn
leif5as2
le4i
le4if
leik3v
le4ik
4le1i2n
4le4k
4le2l
4l2en2n
le2n
4lep
2le4r
le5rí
le1s5e
le2s
2let
l3ex
2ley.
4leyj
2leym
lf4as2
lf5át
lf4dr
lf3f
lf2i
lf5inn.
lf1i2n
lfin2n
l3fj
lf2l
lfla4
l4fó
lf4sp2
lftr4
lft4un
lft3u
4lg
lg2a
lgar4s
lg1ar
lg5as2t
l4gas2
l1gá2
l4ge2s
l1ge
lg2il4
lg1i
lg4is1i
lgi2s
lg3í
lgl4
lgni5s
lg4n1i
l1gr
lg4ú
l1h
4li.
lik4a
l4ik
li5kv
l2il2
li5la
li3li
l2ind
l1i2n
4lings1á4
lin4gs
l3in2n
l5inn.
2l3in1u
4l3ir
l1i2s
l5is.
l4isá4
l3is1i
l2is2k4
l5isr
l4ist1i
lis2t
l5is4tu.
li2s4t1u
li4t4ar
l2it
3litl
l3ía.
lí1a
lí4b
1lí2f
líf5a
lík3k
lím5ug
l4ím
lím1u
4l4íp
1lít
l1íu
l4í4uf
l4í4u1h
l4íutr
lí2u3t
lj3ar
lj2a
lj5ar4ð
1ljó2s
ljós5k
ljós3l
lj3ug2
lj1u
ljur4
lj4ur1u
2lk
lk2ap4
lk4as2
l1ke
l3kr
l3kúr
lkú2
l1kv
l3ky
2ll
l2l3et
l1le
l4ley
ll3f
l4l1g2
ll3ið1a
ll4i3g
lliss4
ll1i2s
l1l2it
l1lít4
l2l3k4
l2l3m
ll1ót
ll1s5tæ
l2ls
lls2t
ll5te
l4lt
ll5ug
l2lu
l2l3v
2lm
lm5ari
lm2a
lm1a2r
lm3ar4s
l4mb4
l3me
l1mó
ln1ar4
ln1a
ln4ið
l4n1i
l5no
lo4
5loð
5loki
4lon
4l1or
5lo4s
lóð3r
ló5gr
ló2g
ló4m2a
lp5t
2l1r
l2ri
l3r2i4t
2ls
lsí4
l2sj4
l5sk1i2n
ls2k
ls4k2i
l4s4kon
ls1ko
ls4nes1i
ls2ne
ls3ne2s
l3st2að
ls2t
ls4ta
ls4t4i2n
lst1i
ls1ve
lsv4
4lt
lt3að
l5t2il
lt1i
l4t4s
lt4ú
l1tæ
2lu
4lu.
l3uð
lugl4
l2ug
luk4i
l2uk
l4u1l4
l1um
l2u1m2a
l1un
3l2und
l3un1u
lu5pe
l2up
l1ur
2l1v
l2v2a2
lv3að
l3v2al
l4víu
ly4
3lyf
1ly4n
l1yr
1lý
2lýf
lýt4a
4lýt1i
2l1þ
1læg
lækj3
lækj1ar5
lækj2a
5lækk
1læt1i
1lö4g
l5ö4l
4löt
m2a
2ma.
m4a1b
m4a1d4
m3að.
m1að
m3að1a
m3að3i
m3aðr
m5aðs
1mað1u
m2af4
m4ag
3mag4n
m4ak
ma2l4as4
m2al
mal3dr
ma2ld
m3al1f
m3a2ll
m4alp
ma4l4t
m2a1m4
4m3an.
m1an
4m3a4na.
man1a
m4a1ná
m3an1b
2m3and2
m3ank2
m3an1l2
mann4as2
man2n
mann1a
3man4n1i
3m4anns
mann5t
2m3ans
man5sa
m3ant
4m3an1u
m1a2r
m3a2ra
m3ar1b4
m3ar4ð
1m4ark
mar4ks5
m3arn
mar3o4
mar4s
mars5m2
m4ar1ú
m1as2
m4a1s2k4
m2a4sp2
m3a2st.
m1as2t
1má
4má.
3má2l
mál3f4
2m2ám
má5m1u
m4á5p
4már
4mb
mb5að3i
mb2a
mb1að
m1b1a2n
mb3i2
mb4ir
mb3un
md2as2
md5as4ta
md1as2t
md4v4
1me
4me.
með3
m5e2f
me4g3i4n3
meg3i
5m2ei2s
me4i
meltr4
me2l
me4lt
m1end
me2n
3m2en2n
m5er5h
me4r
m3er3í
mes4t5a
me2s
mes2t
m1f4
m4fí4
mför4u
m1fö
m1g2
mgl4
m1h
mið3i
m4iðr4
mi3ge
m4i2g
3m4ik
milj3
m2il
mi2l4l
millj3
m3ing
m1i2n
5minj
m3inn.
min2n
min4s
m3in1u
m3ir
m1i2s
m2is3k
mis3lu
mis1m4
2mí
mjó3s2l
mjó2s
m1k
m2ka
mk4arg
mk1ar
mk4as2
mk2i
mk4l2
m1l
m2la
m3l2ag
m3l2a4u2
m2lá
m4l3ár
m2li
m5l2uk
m2lu
4mm
m2m3a
m4m5b
m4mó
mm4sv4
m4ms
mm3u
m4nes2k1u
m1ne
mne2s
mnes2k
2mog
4mok
3mol
mong5
mód3
móð4s
mó5g4
m5ón3í
mó4n
mó3r2a4u2
mó2ra
mó4s
3mót
mp3á
m5pe
mp3i
m3r2a4u2
m2ra
m5rá
m3re
m3rý
m1ræ
4ms
ms5ál
msá4
m2se
ms5e4i
m5s1k2a4u2
ms2k
ms5kj
ms5lá
msn4
mssetr4
ms1se
ms3set
m1sv4
m4sví
mt3að
m4t1i
mt2i2s
m3t2ug
mt1u
mt5un
mt4us
mt2v
m1tö
m1u
4mu.
mu2g4u
m2ug
4m2ul
4mum
m2un
1m2und
m3ung
4mur
m4ur1u
mu5s4ta
mus2t
m3úð
mú2g4u
m1ú4n
mú4s4a
m2ús
m1v4
1my
3mý
mý5m
m1þ
1mæ
1mö
mör4
n1a
4na.
na2da
n2a1d4
na4dí
nað3ar3
n1að
nað1a
n2af4
nafl4
nafl5an
3na4fn
nak4a
n2ak
na4kr
n2al2
na3la
n3a2ld
na3li
na1m2a
n2am
n4an2af4
n1an
nan1a
n5ang
n5an2n
n4ar2ak
n1ar
na2ra
n3ar3f
n4arfi
n4ar1fö
narg4
n4ari2s
nar5m
nar5r1i2n
narr2i
n4ar3u
n4ar1ú
n2at4
n5a2ug
n2a4u2
n3a2uk
na2um5a
nau4m
1ná
ná1g
ná1k
3n2á4m
nán5as2t
ná2n
nán2as2
nán1a
n1b
nbæj4
nd3e4r
nd4is1v4
nd1i
ndi2s
n4dj4
nd3ót
nd3re4k
ndr1u4
nd1ræ
nd4se2n
n2ds
nd1se
nd3ug
nd1u
nd5ul
ndur5g
nd3ú
1ne
neð2s
3n2e2f
4nefl
n4e2m
nem5a
2n1e2n
4n4e4r
nest2r4
ne2s
nes2t
netl4
n5ey1i
n3eyj
né5s2k
n1f2
nfr4
ng2a1m
ng1a
ng5are
ng1ar
n4g2as2
n2ge
ng5e4k
n3ge4r
n3get
ng1ey
n4g4i5k
ng1i
ngil4i
ng2il
ngi5lið
ng5l2ag
ngl2i
ngl5ið
ng5ólfs
ng4ra
ngr5an
n3grí
ngr3u
ng3ræ
ng4sj
n4gs
ng4sp2
ng4ste
ngs2t
ngurs3
n2g1u
ng2u3t
n1h
4n1i
n4ið1i
ni5fr2
n4if
ni3gr
n4i2g
ni3lu
n2il
n4i5m
n4iru
n3isa
ni2s
n4isá4
n2is2k4
ni3skó
nis2m2
nis5s
n2i3ste
nis2t
n4i5stæ
n3í1a
n3ís2k
n1íu
ní4um
njál4
nk2
nk3að
nk5and
nk1an
nk3ans
n5ká
n1ke
nk3i
n5kó
n3kun2n
n2k1u
n5ky
n5kö
n1l2
nli4
n4li2st.
nl1i2s
nlis2t
n1m4
n2n
n1n1á
n1n1e
nn3g2
nnk4i
nnk2
nn4sj
nn5ske
nns2k
nn4sto4
nns2t
nn5stun
nn2s4t1u
nn5tó
nn3ug
nn1u
nn2us
nn3úð
n1or
1n4o4t
n5ólf
5nót4t1i2n
nótt1i
n3p4
n1r
n3r2am
n2ra
n5r2a4u2
n2r4i
n3r2i4t
n3s2ak
n5sát
nsá4
n4seg
n1se
n2s5e2s
ns5i2s
ns1i
ns1í
n3sk2il
ns2k
ns4k2i
n3skír4
ns5kj2a
n5skö
nsn4a
ns5r
n1st4e
ns2t
nt3að
n5tak.
nt2ak
n5taki
n3t2al
n1te
ntge2n5
nt1g
nt1ge
n3t2il
nt1i
n2tí
n4t2s
nt5s2k4
nt5s1m2
nt3ug
nt1u
nt4v
n1tý
n1tö
n1u
n4uf4
n4ugr
n2ug
n2uk2
n2u1l
n2u1m2a
5n2umd
nun4gs3
nur5f
nur4l2
n3u2st.
nus2t
nu5st2að
nus4ta
n4u4sv4
nu5ta
n2ut
3n4ú2m3
n2ú4s
n1v
n5yf
n3yrk
1ný
n3ýg
ný3l
ný5s2k
n1þ
1næ
næl4a
næ2l
3nöf
n3ö2ld
nö4l
n5ön
5obs
oð2a3l
oð1a
oðr4
oðs5l
oð4ug
oð1u
o2f
of3ang
of1an
off4u
o1f3o
of5r2
of4sj
of5s1u
og2a3l4
og1a
og1as2t4
o4gas2
ogs4u
o4gs
ok4as2
o2l2ak4
old3u
o2ld
o4l2g
oll5eg
o2ll
ol1le
ol5l2it
oll4s2t
ol2ls
o2l3m
ol3ó
olt2al4
o4lt
o2m
om3a
o4m4m3
om2u
om3un
on3sv4
on4t
on5tó
on4us
on1u
op2a5p
op1a
op2h3
op5u4
o2ra4
or4d1i
or1d
or1e
or2fe
or2gr
3orí
orl2ag4
or1l2
or2m2a
or4m2i
4orn
or4ne
or4s4ta
or1s2t
ortr4
ort3ug
ort1u
or1u
or3ug
or3v4
or4v4ið
or2v2i
or3y
o4s
os4k3i
os2k
os3m2
os2s
ost5i
os2t
ost5un
o2s4t1u
4o4t
ot3að
ot2a5l4
o4t2am4
ot3ro4
ots4á4
o4ts
ot5un
ot1u
o4u
o3ve
ox4
ó1a
óafl4at4
ó2af
ó5an
óar4s
ó1ar
ób3ak
ób2a
ód4a
óð1i4
óðl4
óð1m4
óð1v4
ó5e
óf3ar
óf4as2
óflu5s
óf2lu
ófr4
ó4f5us
óf1u
ó2fy
ó4fö
ó2g
ó1h
ó1i
ók5lo4
ókl2
ó3kr
ó2k2u
ól2a3m
ó2l2as2
ól5ik
ól4i2s
ól4kv
ó2lk
ól5o2m
ólo4
3óls2k
ó2ls
ó4m3að
óm2a
óm3a2r
óm4bæ
ó4mb
óm2g2
óm4i2s
óm1l4
óm3p
óm3s4t1u
ó4ms
óms2t
óm3u
ó2mö
ó4n
ón3í
ón5kv
ónk2
óp4e
óp2h5
ór4as2
ó2ra
órá4
ór2d
ór4dö
ór5e2s
órf4
órg4
ó4ri
ór4i2s
órík4
órj4
órk4
órm4
órn4o
ór3ó4n
órr4
ór1s4a
ór1u
ós2a5f
ós2ak4
ó3se2m
ó1se
ós3end
óse2n
ó5sk2af
ós2k
ó4sk4as2
ósk5i2n
ós4k2i
ós2l
ósl2a1v3
ósla2
ó2só4
ó1sp4
ós4se
ó4ta
ót3að
ót2ap4
ót5e2f
óti4l4t
ót1i
ót2il
ó5tí
ót4ó
ót2v
ó1u
óu4m5b
óv4a2
ó5v2a4t
óy4
ó5þ
p1a
p2ag4
pa4le
p2al
p2a3m
p1a4n
pan3gó
p2ang
p1a2r
p4ar2at
pa2ra
p4ar4i3f
p4a1r5í
p2art
par5te
p4ar3u
p2ák
p4ál
p2á5m
p4ár
pá2s
p4át
p1b
p5d
p1e2l
1pe4n1i
pe2n
3pers
pe4r
p1f2
p1g2
p1h
p1i
pi2l5ar
p2il
5pi4lt
p4ink2
p1i2n
pist5i
pi2s
pis2t
p2it4
3p4íp
pí2t
pl2
p2l3að
p2la4s2
p4læ
3plö
p1m4
1pok
3pós
p2p
pp5a4ks
pp1a
pp2ak
pp5e
pp3í
ppk4
pp1l2
pp3ó
pp1r
pps2k4
4p2ra
pr2i
p4ri2s
prí4
3prj
1pró
pró5f4as2
pró5m
p1sa
pss4
p1s2t
pt2ú
p1u
puk4i
p2uk
p2u1l
p2u2r4a
3pú
púf4
p1v
qu4
2ra
4ra.
5r2aðs1h
r1að
raf4f
r2af
r3a2ld
r2al
ral4i
r4a2ll
r3a2ls
3r2an2al2
r1an
ran1a
r4ani.
ra4n1i
3r4anns
ran2n
r4anó
4r3ar
r4ar1að
ra2ra
r4ar1ú
r4ary
r4aræ
r2as3i
ras2
ra3t1u
r2at
r4au4m
r2a4u2
1r4áð
ráf4i
ráf2
rá5k4væ
rárs4
r4ása
r1b4
rb2a4
r1d
r2dí
r4ð
rð4ar4á4
rð1a
rð1ar
rð5i2s
rð1i
rð1l2
rð4m1u
rð1m2
rð5rá
rð5s4u
rð3sv4
rð1v4
r4ef.
re2f
r4efs
5reft1s2k4
r2ef4t2s5
1regl
r3e4i2g
re4i
3re4ik
r5en1u
re2n
r5eu
r4ey4n
r4é
r1f2al2
rfa5li
rf4ar
r3fá
r5feð
r1fe
rf1i2s
r1fj
rfjár4
r1f4lö
rfó2g5
r1fó
r1fr2
rfr4u4
rf4s2t
r3f2und
rf1u
rf4ur4ð
r2f3ur
rgj4að
rgj2a
rgj4ar
rg2l4
r5gl4y4
r1gr
r2g2ra
r4grey
r5h
4ri.
4r3ið.
r4i2g2
ri4ga5s2
rig1a
r3i2ld
r2il
4r1i2n
ri1n5e
ringj5ar
ringj2a
r3in1u
rip4s
r2ip
4r3ir
r2i3s2k
ri2s
ris5l2
3risn
rist5að
ris2t
ris4ta
ris4un
ris1u
1r2i4t
rit3l2i
4rí.
4rí2f
rík5i2s3
4r2íl
rí2s2t4
4ríu
4rí5þ
rj3ar
rj2a
rjá4l
rjó3sa
rjó2s
rjósk5a
rjós2k
rk2a1m
rkaup4s
r1k2a4u2
r5ka2up
r3ká
r1ke
rk1e2f
r4ke2l2l
rke2l
r4kelss
rke2ls
rkj2u3s4
rkj1u
rk4se
r4ks
rk1s1m2
rk4sp4
rk4ú2
rk5v2e4i
r3kö
r1l2
r2l3að
r4l5an2n
rl1an
r2li4
rl4ið
rlis5s
rl1i2s
r3l2it
r2l3m
rlo2f4
rlo4
rm4ak4
rm2a
r1m1an
rmá2ls5
r1má
r3má2l
r4mb4
rm2i
r1mið
rm2il4
r5mj
rm1k4
rm1l4
r3móð
rmr4
rn3ar
rn1a
rnar5l2
rn1g
rn5ór
rn5s1í
rn5s1m2
rn4so
r4n1u
r5n4umi
ro4
ro4g4as2
rog1a
ron4
r1or
ró5gr
ró2g
ró4m
ró4sa
ró2s3ó4
5rót1i
r5p2al
rp1a
r3pó
r1pr
r4pr2i
rp2s
rp3s2k
r5py
r4r5ar
r2ra
r1rá
r1re
r1r4é
r1rí
rr2k
r3ró
rr1u
r5rú
r1ræ
r1sa
r4s2af
rs4ár
rsá4
rsegl4
r1se
r3se4r
rs4in2n
rs1i
rs1i2n
r3ske
rs2k
r5sk2il
rs4k2i
rs4l1an
rsla2
rs2má
rs1m2
r1s2t
r2st1i
r4st4v
r3s4tö
rs2u
r5s2und
r4sú4
r1sv4
rt3að
rt2a5g
r3t2ak
rt4a4s2k
rt1as2
r5t4á4
rt5e4r
r2t3ey
rt2hu4
rt1h
r4tík
r1tí
r3tó
r3trö
rt4se
r4ts
rt5sl
rtt4
rt4ur1u
rt1u
rt4ú
r4t1v
rt4ve
r1tæ
rt1öf
r1uð
r4uðun
ruð1u
ruk4i
r2uk
ru5li
r2ul
ru2m
r1ur
r4uss
r3u2st.
rus2t
rut4v
r2ut
r4úð
3r4ú2m
rús2t5
r2ús
r1v
rv3að
rv2a2
r3ve
r2v2i
rv4i3g
rv3ing
rv1i2n
rv4un
r2vu
r3væ
r3yr
1rým
r1þ
1rækt
ræmd5a
1ræn
ræt3i
röf4ug
röf3u
rök1r
rö4l4
2sa.
4s2a1b
2s3að
s4að3f4
1s2ag
4sa1gr
4sagt
sal5at
s2al
4s3a2ld
1s2a2m
sa4m5b
4sa1m1e
sa4m3m
sa4m1s
sa1m5y
2s1an
s4an2ds
2s3ar
s4are
sarg4
s4aro4
s4arp
2s1as2
2s2at
sat4a
2s2a4u2
s3a2uk
4s2a1v
sá4
s3á1b
s5áf2
sá2l1ar5
s1ár
s3ás
s1b
s3d4
1se
2s1e2f
4seld.
se2l
se2l4d
5sem3i4
se2m
5sens
se2n
2se4r
2se2s
3set
sex3
2s1ey
s4eyð
1sé
s5f4
sfl4
sfr4
s1g4
sgl2
s1h
shá4s
s1i
s3ið.
5s4ið1a
s4ið1i
s4if2
si5fi
1s4i2g
sind4ar
s1i2n
sind4as2
5s2inn1u
sin2n
2s5ins
s3ir
s3is2t
si2s
s4is1v4
si3ta
s2it
sí3b4r2
1síð
síðk4
sígl4
sí2g
s3ík
s4í4m
sínk5a
sí2n
sínk2
s3ír
s1ís
s5íu.
sí3v
sí4ve
1sj
s3j2af
sj2a
sj1ar4
s4já
sjó5l
sjó3m4
sjó3s
4sj1u
3sjú
s2k
2sk.
4ska.
4sk1að
skaf4a
sk2af
4sk2al2
2sk1an
1sk2a4p
4skas2
1ská
1ske4i
3ske4mm
ske2m
4ske2n
3skey
s4k2i
2ski.
4sk3ið
sk3in1u
sk1i2n
3sk4ip
2sk1ir
4sk1i2s
3skír
5skjá
4sk4n
3skoð
s1ko
4s3kon
4s3ko4s
1s4k4o4t
1skó
5skó2g
1sk2rá
4skró
3skrú
4s4ks
sk3uri
s2k1u
sk3us2t
sk2us
2skv
1sky
3ský
1skæ
sla2
s4l2a3f
s4l2am
s3lan2ds5
sl2and
sl1an
3s4lé
sl2i2s
s1lí
s4líð
s3lo2f
slo4
s3lok
1sló
slu3s
s2lu
1sly2s
sly4
s3læk
s1m2
4sm2a
smá1s
s1má
smá5v
4s1me
s4me4k
s2mi
1s2mí
smj4
4s1mö
s2ne
3sneið
sne4i
5s4n4e4r
s3ne2s
3sneyd
sn4ið
s4n1i
sn2o
1snú
4s3n4ú2m3
s4ný
snæð5
s1næ
1so
4sod
3son
2sor
s5orð1i
sor4ð
1só4
s4ól
só1l3e
só2l3s
2sóm
s5óm4ag
sóm2a
4sós
1sp2
spí2t3
spj4
4spl2
4s4p2ra
2s1pró
s5p2und
sp1u
sr2an
s2ra
s5r2a4u2
s1rá
s1re
s1r4é
s1rí
s5ro4
s5ræ
s3rö
s3s4á4
s2s3e4r
s1se
ss5í
s1s4k4
ssl2
ss1m4
s2s5or
s1so
s1s2t
s4stir
sst1i
s1sv4
s2t
2st.
s4ta
2sta.
4st2al
4st2ap
5starfi
s4t1ar
st2arf
5starfs
4st2a3æ
1ste
3ste2f
3ste1i2n
ste4i4
5stekk
s1te4k
4stet
1sté
st4he4
st1h
4sti.
st1i
1st4i2g
st4isl
sti2s
3stí2g
s1tí
2s3t4ím
4stíu
1stj4
4stjó4n
5stjór
5stjör
st4jö
4stl
st3le
1sto4
3sto2f
1stó
4stó4n
3stór1
st2r
str5al
st2ra
5stranda
str2an
5str2ang
5str4au4m
str2a4u2
5strá.
1stre
3strí
4s1t4rú
s3try
5strön
st5t
2s4t1u
3st2und
1st2ú
4st1v
3stý
1stæ
2s3tæk
1s4tö
3stöð2
4s1tö4l
5stöng
s1u
s4u3f4
5s2um3a
s2ung
s5up2p1
s2up
s5u2ra
s2ust4i
sus2t
sú4
súln4
s5ú4n
s5úrs
sút5
sv4
4sv2ag
sv2a2
4sv2al
s3v2a4t
s5veð
s4ve2f
s2v2e4i
s3ve4ik
3sve1i2n
5s2vep
4svex
s4við1i
sv2i
sv4ið
5sviði.
s4v4ik
svi2k3u
s5v1i2n
s1vo
s2væ
1svæð
1sy
2s3yf1i
3sy4n
4s3yr
3s4ý
s1þ
1sæ
4sæð
s4æf4
sæ5f1a
3sæj3
3sæ2l
4sæs
1sö
3sö4g
sög2u5s
sö2g1u
2s3ö2ld
sö4l
3söm
2s3ör
t2að
ta2fr2
t2af
1taka
t2ak
5ta2k1end
ta1ke
take2n
t4al.
t2al
tal2a4m
4t2am
ta1m2a
t5am1t
t2a4ná
t1an
3t2ang
4t1ar
t4ar4að
ta2ra
tar5i2s5
tark4
t2arp4
tar5sá4
tar5æv4
t2aræ
t1as2
t4as.
t2a5sl
t2at4
ta4ví
t2a1v
t4á4
5tákn
4tánd
tá2n
t1b
t1d
4tegí
5te2g1u
te4i4
tein5g4
te1i2n
t1ei2s
1te4k
3tekj
tekkj5
t1end
te2n
ten5ó
4tepl2
t3ett
2tey
té4l
t3f2
tfirr4
t1g
t1h
th5ers
the4
the4r
t1i
ti4an
t2ia
ti5k4i2s
t4ik
tik3i
ti2ld4
t2il
4t1i2n
t2irk4
t4iræ
tis2t4
ti2s
t5i2st.
ti3s4ta
tist2il4
tist1i
t2is1v4
1t2it
1tí
4tí.
4tí1a
3tíð
t2í4l
3t4ím
4tí2n
4tít
tív3
t4jö
t1k
tk4a
t4k2i
t4l2af4
t3l2ag
t4lag.
tl2an
t4l5an2n
t1lá
tl2i
tl1ur4
t2lu
t1læ
2t3m4
tn2s
tns2k4
tnskr4
to4
1tog
t3on
3torg
5tor1u
1tóm
tóm3a
tóm5as2
5tón1l2
tó4n
tór1
tór5a4u2
tó2ra
tór4i3s
tó4ri
t1ót
t3p
tr2a2b
t2ra
5traðari
tr1að
trað3ar
trað1a
tr3alí
tr2al
tr2an
tr3an2n
t4r5ar
3trau4s
tr2a4u2
t4rey
1tr4é
tr4i2s
t5ris1i
t5rík3a4
3trj
t5róf
tr3ótt
tr3ug
tr3un1a
tr5u4n1i
1t4rú
1try
t5ryð
t3ræn
3tröð2
4ts
t1sa
ts4in2n
ts1i
ts1i2n
t5sí
t1sj4
t1s2k4
ts1s
t1s2t4
ts4u
t2sy
tt3að
ttak4i
tt2ak
tt2ar5f
t4t1ar
tt5á4
tte5rí
tte4r
tt5ern
t4tí
tt5j
tt1l
tt1or
tto4
tt3ræ
tt3ug
tt1u
tt4ugl4
tt1v4
t1tæ
t1u
4tu.
t4uð1i
tugl4
t2ug
t2uk4
tu3l4ið
t2ul
tum2
tu5m1i2n
t4umi
t2ung
3t4ungl
t2up4
t3ur3e
tur3k
t5urs
t4usa
t4us1u
tutr4
t2ut
túd3
t3úð
tú4l
1tú4n
tún4a
t1úr
tú3s2k
t2ús
t1v
5tveggj
t1veg
t5v4e4r
5týs
t1þ
3tæk
1tök
1tö4l
t5ö2ls
2u1a
u3af
u5a4u2
2u1á4
uáætl4
4u1b4
4uc
4ud4
u1da
u5dá
u3de
u3dó
u3dr
2uð1a
uð4are
uð1ar
uð3k4
2uðl
uð1m4
2uð4n
2uðr
uð5ri2s
uð4se
uð3sv4
uð5sæ
4uð2ul
uð1u
2u5e
4ué
4uf
uf5á
ufd4
u5f2it
u3fj
u1fr2
u3f2ul
uf1u
u5fú
2ug
4ug2al
ug1a
4ug2at
4u1ge
4ug2il
ug1i
4u1gj
4ugla
ug1lj
4ug2lu
ug4n
4ugó
u5gr4an
ug2ra
4ugre
4ugrj
4u3gró
ug3ræ
ug3ta
4u1gö4
4u1h
4ui
u5ið
4uí4
2uj
2uk
uk2ak4
uk4as2
u1ke
u5k1i2nn1i2n
uk1i2n
ukin2n
ukin4n1i
ukk2u3s
uk2k1u
u5kó
u3kv
2ul
3ul.
4u1l2ag
u2l3ar
3u2l1b
4u1le
u5l2ind
ul1i2n
ul4i2s
u1lí
u2l3k2
ul4la
u2ll
4ulln
ul4lt4
4u3lo4
u3ló
ul5sv4
u2ls
ult4i
u4lt
ul3us
u2lu
u2l5v4
u3læ
4u1lö
3um.
2um2a
um4ak4
u1m1an
um4ar1an
um1a2r
um3a2ra
2u1má
umá2l4ar
u3má2l
um4b2a
u4mb
um5bæ
um4bö
2umd
2u1me
um3e1i2n
ume4i
4umi
umj4
um4k2i
um1k
4um1l
um2m4a
u4mm
4u5mo
2umó
4ump
2umr
u4ms4
um3sl
3um2st.
ums2t
um1t4
4um1u
um5un2n
um2un
4umú
2u1my
2u3mý
2u1mæ
2u1mö
un3ar1
un1a
una2r5a
un2as2
4un2at4
2u1n2a4u2
2u1ná
2und
4u1ne
4unk2
2un1n1á
un2n
4unns
2unn1u
unn5ug
4uno
4unó
un2s4an
4unt
1un1u
4unur
4u1næ
4unö
2u3o
uol4
4uó
2up
up2p1
upp5a
upp2al5
upp4i
3ur.
2u2ra
ur2a4f
ur5a4m
ur2an
ur5ann1a
uran2n
2urá
urð4a5r4á4
ur4ð
urð1a
urð1ar
2ure
u1re4k
ur3ey
4u1r4é
urf4a
ur3fl
ur1g4e
ur3gj
u4r5i2n
4u1r2i4t
4urí
u3rík
ur3j4
urk4a
ur1m
ur3ní
4uro4
4uró
ur1s2k4
ur3sn1a
ur4s4ta
ur1s2t
ur4svö
ur1sv4
ur5t2il
urt1i
urt4ir
ur1u
ur4un2n
4urus
ur3v4
ur4vi2s
ur2v2i
4uryk
4urý
2uræ
4urö
2usa
u3s2al
4us4á4
2u1se
4usí
2u1sj
4u1s2k
4usl
2u3s4m2
2usn
4u1so
4u1só4
4u1sp2
u5st2arf
us2t
us4ta
us4t1ar
4ust4á4
4u1ste
2u1sté
2ust1i
2us1tí
4u1sto4
4u1stó
4ust2r
3us4tu.
u2s4t1u
2u1st2ú
4usty
4u3stý
4u1stæ
4u1s4tö
u5s2und
us1u
4u3sv4
4u1sy
2u3s4ý
2u1sö
2ut
ut3að
ut2as2
u3te
u5t2il
ut1i
u3tó
ut4stó
u4ts
ut1s2t4
ut2ú
u1tæ
2u3u
4u5ú
4u3v
2uy
u3yf
2u1þ
4uæ
2u5ö
ú1a
ú2al4
ú3arf
ú1ar
úb3a2n
úb2a
úbli3
úb4l2
úð3ar
úð1a
úð5g
ú3e
úf5ar
úfl2
úf5li
úf5ly4
úfs4á4
ú5gala
úg1a
úg2al
úgó3
ú4gæ
4ú1i
úk1l2
úkr1u
ú4k4s
ú2l5e4r
ú1le
5úl4f
úlf5al
úl4í3
ú4l4íp4
ú3lo4
4ú2m
úm4r
úm4s1i
ú4ms
ú4n
únd4ug
únd1u
ún4gö4
úpl5i
úpl2
úr5e2f
úrít4
úr5sl
úr1t
úr3un
úr5v
ú3ræ
2ús
ú4sa
ús3e4i
ú1se
ús3í
ús4st1i
ús1s2t
ús4sv4
ús1v4
út3e
út2he4
út1h
út4i5f
út1i
út2i2s
útj4
út1l2
út1r
4ú1u
ú1v
ú4v2a2
ú5þ
v2a2
vaðr5
v1að
va5fo
v2af
v4a5h
5vall2ag
v2al
va2ll
v1a4n
var4m2a
v1ar
varp2s3
v2arp
v2ar5ú
var4v4
v3as2t
vas2
v2a4t
3vax
veð5l
ve3fe
ve2f
1veg
v2e4i
3veið
vein4as2
ve1i2n
vein1a
5vei2s
4ve4lg
ve2l
2vep
v4e4r
1v4er4ð
ver3gj
3ver2k
ver2s
vé2
v5és
v3ét5
2v3h
v2i
2vi.
v4ið
5við1ar
við1a
við3l
vil4i
v2il
vi3lið
3viln
vi4lo4
vin3gj
v1i2n
4v2ip
3v2irk
visk5un
vi2s
v2is2k
vis2k2u
3viss
vis4v4
5vita
v2it
vit1k5
vit2n
4v4i1v
4víb
2víð
5vík
3vís1i
vísl3a2
vísl2
ví1v
2vn
vo3k
vol4
vork4
vor4r
4vr1
4vs
2vu
v1uð
v1un
5væg
wa4
win4s4
w1i2n
x5ar
x2as2
x3e
x3f
x5i
xi2s4
xí3
xík4
x5íu
xt5að
x1u4
y5b2a
yð2s
yf5a
yf1i
yfj5að
yfj4a
yft4i2s
yft1i
y1i
yj3ar
yj2a
yj5ó
yk5e
yk3i
yk3s1u
y4ks
y2k3u
yk3v
ylf5i
ylgn4
y4lg
yll5a
y2ll
y2l4v
ym3a
ymp5í
y4n
ynj5ar
ynj2a
ynj3ó
yn4k2
yn4t
yn5u
yp2us
yp1u
yr5e
yr1i
yr3il
y4r3ir3
yrj3
yrkv3a2
yr2l2
yr2s
yr1u
y2s
y1sj3
ys2s
yst3ug
ys2t
y2s4t1u
yt4h
yt2il4
yt1i
yt4k
y3v
ý5a
ý2af5
ý5á
ýð2s
ýfl4
ý3f2lu
ýg4r
ýg4uð
ý2g1u
ý1i
ýj2a5f4
ýj2a
ým4a
ým4k
ýpru4
ýp2s
ýrf4
ýr4i3m
ýr2i5p4
ýr3l2
ý4s1i
ý1s4i2g4
ýs4l
ýs4m2
ý3st4á4
ýs2t
ý5u
ý5ú
ý3v
ý5y
zó4
þ2a
þ2am4
þarf5a
þ1ar
þ2arf
þar4m
4þb
þ2i
þist3i
þi2s
þis2t
þjó4
þol5a
þor3f4
þor3g
þorm4
þ6ó
þ1ól
3þór
þó4r5i
þ2r2
þ2ra4
þrás4
þri2s4
þ4r2íl4
þrí3t
þru4
þ2um5a
þu4mb3
þur2
þ2ús3
þ2v
þver5s2k
þv4e4r
þver2s
æ1b
æðn5
æðni2s5
æð4n1i
æð2s
æð4ug
æð1u
4æf
æf1a
æ4fi
æf3us2t
æf1u
æ4f2us
æg5is2t
æg1i
ægi2s
æ1i
æj3
æj4al2
æj2a
æjark4
æj1ar
æk1a
æk1li
ækl2
æ2k3u
æ2l
æl3an
æ1l3e
æl3us
æ2lu
æm3a
æm4al
æ4mund1u
æm1u
æm2un
æ1m2und
æn2ak4
æn1a
æn4k2
æ2r1a
ær3e
ærgöng5
ær1gö4
ær4if4
ær3is2t
æri2s
æri3s1v4
ær3l2
ær4n
ær2s
ær5un
ær1us
æ5rú
æs4i2s
æs1i
æt5i1se
æt1i
æti2s
ætl5i
æ5u
æv2a5g
æv2a2
æ5v2al
æv3ar
æv4a2ra
æ3ve
æv3i2n
æv2i
öð2
öðl3
öð1m5
öð3un
öð1u
öðv5a4n
öð1v
öðv2a2
öðv3ar
ö1fa
öfl3ó
ö4fn5
öfr3u4
öfr2
öf3u
ö4g
ög3gj
ögg2v3
ög1re
ög2us
ö2g1u
ök5e
ök5rá
ök3s2t
ö4ks
ök3ul
ö2k1u
ökv3a2
ö4l
öl1f4
ö2lk2
öl4u5mi
ö2lu
öl1um
öl5un
ö2l2v
ölv5a4n
öl2v2a2
ö4m4b
öm3u
önd4l2
öng5s1v4
ön4gs
öng4us
ön2g1u
öng4v
öng2v3a2
önk3
önn4l2
ön2n
ön3ug
ön1u
ön5un
ör1e
ör4ge
ör4l1an
ör1l2
örm5un
örm1u
ör3ó
ör3ug
ör1un
ör3und
örus4
ör2v
4örv2a2
örv5al
örv5a4n
örv5ar
ör2v4i
ör1y
ör5æ
ö4s
ösk3ul
ös2k
ös2k1u
ös3u
ö4t4s
ö3t2ug
öt1u
öt2v
ötv3a2
ö5u
PK
!<X/Q		hyphenation/hyph_it.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a3p2n
.a1p
.anti1
.a1n
.a2n1t
.a1nti3m2n
.anti1m
.bio1
.c2
.ca4p3s2
.ca1p
.circu2m1
.ci1r
.ci2r1c
.contro1
.co1n
.co2n1t
.cont2r
.d2
.di2s3cine
.di1s2
.dis1c
.disci1n
.e2x1eu
.e1x
.fra2n2k3
.f2r
.fra1n
.free3
.li3p2sa
.li1p
.li2p1s2
.narco1
.na1r
.na2r1c
.opto1
.o1p
.o2p1t
.orto3p2
.o1r
.o2r1t
.para1
.pa1r
.poli3p2
.po1l
.pre1
.p2r
.p2s2
.re1i2sc2r
.rei1s2
.reis1c
.sha2re3
.s2
.s1h
.sha1r
.tran2s3c
.t2r
.tra1n
.tra2n1s2
.tran2s3d
.tran2s3l
.tra1n2s3n
.tran2s3p
.t1ran2s3r
.tran2s3t
.su2b3lu
.su1b
.sub2l
.su2b3r
.wa2g3n
.wa1g
.we2l2t1
.we1l
2'2
2’2
a1ia
a1ie
a1io
a1iu
a1uo
a1ya
2a2t.
a1t
e1iu
e2w
o1ia
o1ie
o1io
o1iu
1b
2b1b
2b1c
2b1d
2b1f
2b1m
2b1n
2b1p
2b1s2
2b1t
2b1v
b2l
b2r
2b.
2b2'2
2b2’2
1c
2c1b
2c1c
2c1d
2c1f
2c1k
2c1m
2c1n
2c1q
2c1s2
2c1t
2c1z
c2h
2c2h1h
2c2h.
2ch'.
c2h2'2
2ch’.
c2h2’2
2ch''.
ch'2'2
2ch’’.
ch’2’2
2c2h1b
c2h2r
2c2h1n
c2l
c2r
2c.
2c2'2
2c2’2
1d
2d1b
2d1d
2d1g
2d1l
2d1m
2d1n
2d1p
d2r
2d1s2
2d1t
2d1v
2d1w
2d.
2d2'2
2d2’2
1f
2f1b
2f1g
2f1f
2f1n
f2l
f2r
2f1s2
2f1t
2f.
2f2'2
2f2’2
1g
2g1b
2g1d
2g1f
2g1g
g2h
g2l
2g1m
g2n
2g1p
g2r
2g1s2
2g1t
2g1v
2g1w
2g1z
2gh2t
2g.
2g2'2
2g2’2
1h
2h1b
2h1d
2h1h
hi3p2n
hi1p
h2l
2h1m
2h1n
2h1r
2h1v
2h.
2h2'2
2h2’2
1j
2j.
2j2'2
2j2’2
1k
2k1g
2k1f
k2h
2k1k
k2l
2k1m
k2r
2k1s2
2k1t
2k.
2k2'2
2k2’2
1l
2l1b
2l1c
2l1d
2l3f2
2l1g
l2h
l2j
2l1k
2l1l
2l1m
2l1n
2l1p
2l1q
2l1r
2l1s2
2l1t
2l1v
2l1w
2l1z
2l.
2l'.
l2'2
2l’.
l2’2
2l2'2'2
2l2’2’2
1m
2m1b
2m1c
2m1f
2m1l
2m1m
2m1n
2m1p
2m1q
2m1r
2m1s2
2m1t
2m1v
2m1w
2m.
2m2'2
2m2’2
1n
2n1b
2n1c
2n1d
2n1f
2n1g
2n1k
2n1l
2n1m
2n1n
2n1p
2n1q
2n1r
2n1s2
n2s3fe1r
ns1f
2n1t
2n1v
2n1z
1n2g3n
2nhei1t
n1h
2n.
2n2'2
2n2’2
1p
2p1d
p2h
p2l
2p1n
3p2ne
2p1p
p2r
2p1s2
3p2si1c
2p1t
2p1z
2p.
2p2'2
2p2’2
1q
2q1q
2q.
2q2'2
2q2’2
1r
2r1b
2r1c
2r1d
2r1f
r2h
2r1g
2r1k
2r1l
2r1m
2r1n
2r1p
2r1q
2r1r
2r1s2
2r1t
r2t2s3
2r1v
2r1x
2r1w
2r1z
2r.
2r2'2
2r2’2
1s2
2s2h1m
s1h
2s2h.
2s2h2'2
2s2h2’2
2s3s2
s4s3m
2s3p2n
s1p
2s2t1b
s1t
2s2t1c
2s2t1d
2s2t1f
2s2t1g
2s2t1m
2s2t1n
2s2t1p
2s2t2s2
2s2t1t
2s2t1v
2s1z
4s.
4s'.
s2'2
4s’.
s2’2
4s2'2'2
4s2’2’2
1t
2t1b
2t1c
2t1d
2t1f
2t1g
t2h
t2l
2t1m
2t1n
2t1p
t2r
t2s2
3t2sc2h
ts1c
2t1t
t2t3s2
2t1v
2t1w
t2z
2tz1k
t2z2s2
2t.
2t'.
t2'2
2t’.
t2’2
2t2'2'2
2t2’2’2
1v
2v1c
v2l
v2r
2v1v
2v.
2v'.
v2'2
2v’.
v2’2
2v2'2'2
2v2’2’2
1w
w2h
wa2r
2w1y
2w.
2w2'2
2w2’2
1x
2x1b
2x1c
2x1f
2x1h
2x1m
2x1p
2x1t
2x1w
2x.
2x2'2
2x2’2
y1ou
y1i
1z
2z1b
2z1d
2z1l
2z1n
2z1p
2z1t
2z1s2
2z1v
2z1z
2z.
2z'.
z2'2
2z’.
z2’2
2z2'2'2
2z2’2’2
.z2
PK
!<b{6hyphenation/hyph_kmr.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ka6r4a1
.ka1
.ka1r
a1
a1l4a1
a1l
6amît.
a1m
amî1
amî1t
4a1n
ar5a6nî1
a1r
a1r4a1
ar4a1n
3a1v
4av2a1y
ava1
2a1y
1b
b4a1
2b1b
2b1l
2b1r
2b1s
2b1x
1c
2c1b
2c2k
2c1l
1ç
2ç1k
1d
da6vêji1
da1
d3a1v
davê1
davê1j
2d1b
dbû6n4a1
dbû1n
2d1d
dda4
2d1g
2d1r
2d1t
d4y2o
d1y
4dyû
e1
ea2
e4d1y
e1d
e1e2
eê2
e4f1r
e1f
el4a1
e1l
erde1s6
e1r
e2r1d
e1rde1
er6desta1
erde2s1t
e4t1r
e1t
2e1z
ê1
ê2a1
êl3a4v
ê1l
êla1
1f
f1l4
f4lî1
4flû
f4r2o
f1r
2f1s
2f1ş
2f1t
2f1x
1g
2g1b
2g1h
2g1k
g2l
2g1n
2g1r
2g1s
2g1t
1h
2h1b
2h1d
2h1k
2h1m
2h1n
2h1r
2h1s
2h1t
i1
i1i2
il4a1
i1l
i4nê1r
i1n
inê1
ire4h
i1r
ire1
i2s
î1
î2a1
2î1d
îe4t
îe1
î2j
î1l3
î4p1l
î1p
îsti6ye1
î1s
î2s1t
îsti1
îsti1y
1j
6ja6va.
ja1
j3a1v
java1
2j1h
2j1k
2j1m
2j1n
2j1t
1k
2k1b
2k1ç
2k1k
2k1l
2k1m
2k1n
2k1r
2k1s
2k1t
2k1v
2k1w
2k1x
2k1y
1l
6la4mîtê1
la1
la1m
lamî1
lamî1t
2l1b
2l1c
2l1ç
2l1d
l4e1
2l1f
2l1g
2l1h
2l1k
2l1l
2l1m
2l1n
2l1p
2l1q
2l1s
2l1t
2l1v
2l1w
2l1x
2l1y
2l1z
1m
ma4î1
ma1
2m1b
2m1d
2m1f
4mîtê1
mî1
mî1t
2m1m
2m1p2
2m1r
2m1s
2m1w
2m1y
2m1z
1n
n4a1
2n1b
2n1c
2n1ç
2n1d
nê4re1
nê1
nê1r
2n1f
2n1g
2n1h
2n1k
nki4
2n1n
2n1p
2n1s
2n1ş
2n1t
2n1v
2n1x
2n1y
2n2z
2o
o1f2
o2h
o2s
o2w
1p
2p1s
2p1t
1q
2q1p
2q1ş
1r
r4a1
raî4
2r1b
2r1c
2r1ç
2r1d
2r1f
r4fi4
2r1g
2r1h
2r1j
2r1k
2r1l
2r1m
2r1n
2r1p
2r1q
2r1r
2r1s
2r1t
2r1v
2r1w
2r1x
2r1y
2r1z
1s
3sa1
2s1b
3se1
2s1g
3sî1
2s1k
2s1p
2s1r
2s1s
2s1t
s4t3a4v
sta1
st4r
3su1
3sû
2s1y
1ş
4ş3a4v
şa1
2ş1b
2ş1d
şê4l
şê1
2ş1g
2ş1h
2ş1k
2ş1m
2ş1n
2ş1p
2ş1t2
2ş1v
2ş1x
1t
4t3a1v
ta1
2t1g
tge4
2t1k
2t1l
2t1m
2t1n
tnî4
2t1p
t4r4a1
t1r
t4rû
2t3s2
2t1t
2t1x
2t1y
u1
ue2
u2i1
u2k
urandi6
u1r
ur4a1
ur4a1n
ura2n1d
u2ş
1v
2v1b
2v1ç
2v1d
2v1g
2v1h
2v1k
2v1n
2v1r
2v1s
2v1ş
2v1y
1w
2w1c
2w1d
2w1h
2w1k
2w1l
2w1n
2w1r
2w1s
2w1ş
2w1t
1x
x4a1
2x1ç
2x1l
2x1n
2x1t
x4t1r
x2w
1y
2y1b
2y1d
yda4
2y1l
2y1n
2y1r
2y1s
2y1t
2y1v
2y1w
1z
2z1b
2z1d
1z3e4z
ze1
2z1k
2z1m
2z1r
2z1t
2z1y
2z1z
PK
!<_tRRhyphenation/hyph_la.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a2b3l
.a1b
.anti1
.a1n
.a2n1t
.a1nti3m2n
.anti1m
.circu2m1
.ci1r
.ci2r1c
.co2n1iu1n
.co1n
.di2s3cine
.di1s2
.dis1c
.disci1n
.e2x1
.o2b3
.para1i
.pa1r
.para1u
.su2b3lu
.s2
.su1b
.sub2l
.su2b3r
1s2
2s3que.
s1qu2
2s3de2m.
s1d
sde1m
1p
2p1s2
3p2si1c
2p1n
3p2neu
æ1
œ1
a1ia
a1ie
a1io
a1iu
ae1a
ae1o
ae1u
e1iu
io1i
o1ia
o1ie
o1io
o1iu
uo3u
1b
2b1b
2b1d
b2l
2b1m
2b1n
b2r
2b1t
2b1s2
2b.
1c
2c1c
c2h2
c2l
2c1m
2c1n
2cq
c2r
2c1s2
2c1t
2c1z
2c.
1d
2d1d
2d1g
2d1m
d2r
2d1s2
2d1v
2d.
1f
2f1f
f2l
2f1n
f2r
2f1t
2f.
1g
2g1g
2g1d
2g1f
g2l
2g1m
g2n
g2r
2g1s2
2g1v
2g.
1h
2h1p
2h1t
2h.
1j
1k
2k1k
k2h2
1l
2l1b
2l1c
2l1d
2l1f
l3f2t
2l1g
2l1k
2l1l
2l1m
2l1n
2l1p
2lq
2l1r
2l1s2
2l1t
2l1v
2l.
1m
2m1m
2m1b
2m1p
2m1l
2m1n
2mq
2m1r
2m1v
2m.
1n
2n1b
2n1c
2n1d
2n1f
2n1g
2n1l
2n1m
2n1n
2n1p
2nq
2n1r
2n1s2
n2s3m
n2s3f
2n1t
2n1v
2n1x
2n.
p2h
p2l
2p1p
p2r
2p1t
2p1z
2p2h1p
2p2h1t
2p.
1qu2
1r
2r1b
2r1c
2r1d
2r1f
2r1g
r2h
2r1l
2r1m
2r1n
2r1p
2rq
2r1r
2r1s2
2r1t
2r1v
2r1z
2r.
2s3p2h
s1p
2s3s2
2s2t1b
s1t
2s2t1c
2s2t1d
2s2t1f
2s2t1g
2st3l
2s2t1m
2s2t1n
2s2t1p
2s2tq
2s2t1s2
2s2t1t
2s2t1v
2s.
2s2t.
1t
2t1b
2t1c
2t1d
2t1f
2t1g
t2h
t2l
t2r
2t1m
2t1n
2t1p
2tq
2t1t
2t1v
2t.
1v
v2l
v2r
2v1v
1x
2x1t
2x1x
2x.
1z
2z.
a1ua
a1ue
a1ui
a1uo
a1uu
e1ua
e1ue
e1ui
e1uo
e1uu
i1ua
i1ue
i1ui
i1uo
i1uu
o1ua
o1ue
o1ui
o1uo
o1uu
u1ua
u1ue
u1ui
u1uo
u1uu
a2l1ua
a1l
a2l1ue
a2l1ui
a2l1uo
a2l1uu
e2l1ua
e1l
e2l1ue
e2l1ui
e2l1uo
e2l1uu
i2l1ua
i1l
i2l1ue
i2l1ui
i2l1uo
i2l1uu
o2l1ua
o1l
o2l1ue
o2l1ui
o2l1uo
o2l1uu
u2l1ua
u1l
u2l1ue
u2l1ui
u2l1uo
u2l1uu
a2m1ua
a1m
a2m1ue
a2m1ui
a2m1uo
a2m1uu
e2m1ua
e1m
e2m1ue
e2m1ui
e2m1uo
e2m1uu
i2m1ua
i1m
i2m1ue
i2m1ui
i2m1uo
i2m1uu
o2m1ua
o1m
o2m1ue
o2m1ui
o2m1uo
o2m1uu
u2m1ua
u1m
u2m1ue
u2m1ui
u2m1uo
u2m1uu
a2n1ua
a1n
a2n1ue
a2n1ui
a2n1uo
a2n1uu
e2n1ua
e1n
e2n1ue
e2n1ui
e2n1uo
e2n1uu
i2n1ua
i1n
i2n1ue
i2n1ui
i2n1uo
i2n1uu
o2n1ua
o1n
o2n1ue
o2n1ui
o2n1uo
o2n1uu
u2n1ua
u1n
u2n1ue
u2n1ui
u2n1uo
u2n1uu
a2r1ua
a1r
a2r1ue
a2r1ui
a2r1uo
a2r1uu
e2r1ua
e1r
e2r1ue
e2r1ui
e2r1uo
e2r1uu
i2r1ua
i1r
i2r1ue
i2r1ui
i2r1uo
i2r1uu
o2r1ua
o1r
o2r1ue
o2r1ui
o2r1uo
o2r1uu
u2r1ua
u1r
u2r1ue
u2r1ui
u2r1uo
u2r1uu
PK
!<NIO'O'hyphenation/hyph_lt.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a1p1
.ap2i1
.a3p3r
.arbi1
.a1r
.a2r1b
.art2i1
.a4r1t
.a3s3p
.a1s
.a1t1
.at2a1
.a2t3r
.a3š3v
.a1š
.dina1
.d4i1n
.e3k3r
.e1k
.i2š3
.iši2
.kirt2i1
.k4i
.ki1r
.ki4r1t
.nu1
.nusi1
.nu5s4
.pie2č
.p2i
.pi2e
.sa1m1
.sida1
.si3d
.s2k4
.s1t4
.su5k2r
.su1
.su1k
.te3s2
.u1k3
.u2š3
.u1ž1
.į1s4
.šv2e1n1
.š1v
.šv2e
a1a2
a1b
a1c
a2ch
a1d
a4dr2a
a2d1r
a1e
ae2l
a1f
2a1g
a4g2r
ag3r2a
a5g4ri2o
ag3r2i
a5gr2o
a1h
a5i2n1f
a4i1n
ai4s4k2
a4i1s
ai4t1r
a2i3t2
a1j
a1k
a2k2l
ak2vi
a2k1v
a2k2ė3t
a1l
a3li
a5lo1
a3ly
a3lė
a3lū
a1m
a1n
an3k2l
a2n1k
an4s2k2
a4n1s
an4t1r
a4n1t
a2o
a1p
ap1a4k
apa3
ap3ei
ap2e
ap3i2m
ap2i
a3p3l
a3p2r
a4p3s2
a1r
a4r1e2i3t2
a4r1g
ar4i4s
ar2i
a1s
asi1
as4i5s2
a4s2k2
as3k1l
a3s2l
as2mi
a2s1m
as2mu
a5s4n
a4stu
a2s1t
a1t
at3au1g
at2a
ata4u
at2e5i2s3t
ate4i1s
at3i2m
at2i
at2o1
a2t3p4
a4tr2u
a2t1r
at1ė2
atū2ž
a4u
au4k1l
au1k
au4s2k2
au1s
au4s2l
a2u4t3r
au3t2
a1v
a1w
a1y
a1z
a1ą
2a1č
a1ę
a1ė2
a1į
a1š
a3š1n
aš4t1r
a4š3t2
a3š2v
a1ų
a1ū2
a1ž
a2ž2l
ba3c
balt2a1
ba1l
ba4l3t
ba4s2l
ba1s
2b1b
2b1c
4b3d
be3p4
be3s2
besi1
be3t2
2b1f
2b1g
2b1h
b1j
2b1k
2b1l
b2la
b2li1z
3b2lo
b2lu
2b1m
2b1n
2b1p
2b2r
b3r2i
2b1s
2b3t
bu4k
2b1v
2b1w
2b1z
2b1č
2b1š
2b1ž
ca1r4
2c1b
2c1c
2c1d
2c1f
2c1g
3chi
2c1k
2c1l
2c1m
2c1n
2c1p
2c1r
2c1s
2c1t
cu4
cu1k5
2c1v
2c1w
2c1z
2c1č
2c1š
2c1ž
da3b4
2d1b
2d1c
2d1d
de4k
dem2a1
de1m
de4p4r
de3p
de4s2p
de1s
2d1f
2d1g
2d1h
di4p
d4i4s
di3s5k2
2d1j
2d3k
4d3l
2d1m
2d1n
do3r2i
do1r
2d1p
2d1r
dr2o1
dro2b
d2rė
4d5rų
2d1s
2d1t
du2a2
du4k
du5ka
du4s2l
du1s
2d1v
d3va
dvi3a
2d1w
d4z
2d1č
2d1š
d4ž
dži2o3
e1a2
e2a3l
eap2i1
ea1p
ea1t1
e1b2
ebe1
e3b1l
e2b3r
e1c
e2ch
e3d2
ed3r2i
e2d1r
e4dr2o1
ed3rė
e1e
e1f
e1g
eg3r2a
e3g2r
e1h
e1i2e
ei4k3l
ei1k
1e4i1n
ei4s4k2
e4i1s
ei4s2l
2ei2s3t
eist2r2a
eis2t1r
ei2š1
e1j
e1k
e3k2r
e1l
e1m
e3ma5s4
em2a
2e1n
en3k2l
e2n1k
en3k4la
e5no1
e4n1s4
4en3t2a
e4n1t
enu1
e2o
e3o1r
e3o2š
e3p
ep4li
e3p2l
e2p4r
epr2a1
ep3r2i1
e1r
ere3a4
er4i4s
er2i
er2o1
erė2
e1s
es4i5s2
e3s4k2
eska1
e5s3ko
e3s2v
e1t
e1u4
eu1ž3
e1v
e3vi
e1w
e1y
e1z
e1ą
e1č
e1ę
e1ė
e1į4
eį4p3
eį3s3k2
eį1s
eį2t3r
eį2t2
e1š
e3š1n
e3š2v
ešė3
e1ų
e1ū
e1ž
2f1b
2f1c
2f1d
2f1f
2f1g
2f1h
f4i4s5
2f1k
2f1l
2f1m
2f1n
2f1p
2f1r
fr2i1
2f1s
2f1t
2f1v
2f1w
2f1z
2f1č
2f1š
2f1ž
ga1š3
2g1b
2g1c
2g1d
ge4o1
2g1f
2g1g
2g1h
2g1k
2g1l
g2le
g2lo
2g1m
2g1n
3g2nų
2g1p2
3g2r
grai2
gr2a
g3ra1n
5g3re
g4rei
g3r2i
4g4ri2o
g3ro.
gr2o
g4rą
5grį
4g5rų
2g1s
4g1t
3gu
2g1v
2g1w
gyva1
gy1v
2g1z
2g1č
2g1š
2g1ž
2h1b
2h1c
2h1d
2h1f
2h1g
2h1h
hi4b2
2h1k
2h2l
2h1m
h2me
2h1n
2h1p
2h1r
2h1s
2h1t
2h1v
2h1w
2h1z
2h1č
2h1š
2h1ž
i2a
i2a5g4
i3ai1š
ia3k
i3an3tę
ia1n
ia4n1t
i3antė
ia1p4
i3a1r
i3b2
i1c
ice1
i1d
i2d2r
id3rė
i2dė1m
i2e
ie4d3r
ie3d2
ie3g
i3ei
ie3k1l
ie1k
i3e4n1t
i2e1n
ie4p5r
ie3p
ie4s4k2
ie1s
i1f
2i1g
i3g2l
ig3r2u
i3g2r
2i1h
i1i2
i1j
i1k
i3k2n
i2k3r
i1l
ilo1
i1m
1i2m1d
4i1n
in4k1l
i2n1k
5i2n1v
i2o
io4g3r
io1g
io1k2
io4p2l
io1p
i3o1r
i1p
i2p1j
ip3r2u
i2p2r
i1r
ira3s2
ir2a
i3r2i
4i1s
i5sa
i5si
i3s4k2
i3s2l
is4li
i5s4n
i4ste
i2s1t
ist2o1
2i3t2
i2u
i1v
i1w
i1y
i1z
i2ą
i1č
i1ę
i1ė2
i1į
i1š
i3š1n
i4š5t2
i4š1v
išė2
1i2š1š
i2ų
i2ū
i3ž
3ja.
jauna1
ja4u
jau1n
2j1b
2j1c
2j1d
2j1f
2j1g
2j1h
2j1j
2j1k
2j1l
2j1m
2j1n
jo3t3
jo4t1v
2j1p
2j1r
2j1s
2j1t
1ju
2j1v
2j1w
2j1z
2j1č
2j1š
1jū
2j1ž
3ka.
5ka3d
ka1k3
ka4k2l
ka4p2r
ka1p
3ka1r
3ka1s
3kat2i
ka1t
5ka1v
kava1r1
ka3z2
2k1b
2k1c
2k1d
3ke
ke4b3
ker2e1n1
ke1r
2k1f
2k1g
2k1h
k4i
5ki.
5ki2a
3ki3b2
3ki1l
5k2i3t2
2k1k
2k1l
3k2la
k4la1n
4k3le.
k2le1l
4k3lo.
4k3lu.
k2ly
4k3lą
4k3lų
2k1m
2k1n
3ko
2k1p
2k2r
k4ra4u
kr2a
kr4i1s2
kr2i
kri5s1t
k3r2o
k4ro1v
4k3r2u
5k4ru1n
k4ry
k5rą
2k1s
k4s2k2
k2s3l
k4s3p
4k1t
k3t2a
3ku
4ku1b
ku4k
ku4p2r
ku3p
2k1v
k2va
k3vo
k3vė
2k1w
3ky1t
2k1z
5ką
2k1č
2k1š
k3š2ly
k2š1l
kšė3
2k1ž
3la.
lap4s3t2o
la1p
la4p3s2
lap2s1t
2l1b
l2b3r
2l1c
2l1d
3le.
le3c
le4g2r
le1g
4le1č
2l1f
2l1g
lg3s2t
l2g1s
2l1h
3li.
li4a
5li4o
3li2ų
2l1j
2l1k
l3ko1
2l1l
2l1m
2l1n
3lo.
lo1g4
4lo1p
4l1p
lpna1
l4p1n
2l1r
4l1s
l4s2k2
l4s3p
4l3t
3lu.
2lu3p
4l1v
2l1w
3ly1č
2l1z
3lą
2l1č
3lę1s
3lė1m
3l2ė1s
2lė1č
3lį4
4l1š
3lų
2l1ž
m2a
2m3ai1d
3ma1s
ma5s3k4i
ma4s2k2
2m1b
m2b3r
2m1c
2m1d
me3c
me4i1s1
2m1f
2m1g
2m1h
mi4g2l
m2i1g
mi4g2r
mi4n4s
m4i1n
mi4t1r
m2i3t2
2m1k
2m1l
2m1m
2m1n
m2o
2mo1d
2m1p
m4p2l
m3p2r
2m1r
4m1s
2m1t
m3t2a
3mu2o
2m1v
2m1w
2m1z
2m1č
2m1š
2m1ž
na3s2
na3t2
2n1b
2n1c
nc2e1n1
2n1d
n3d2rė
n2d1r
ne1
ne1g4
nei2m
ne4o3
ne3o1r2
nerė3
ne1r
nesi1
ne1s
ne3s2l
ne3s2t
ne3t2
neį2s3t2
ne1į4
neį1s
2n1f
4n1g
n2g3l
n3g4r
2n1h
n4i4s
2n1j
2n1k
n3k3la
n2k1l
n2k3r
n3k4ry
2n1l
2n1m
2n1n
5no
2n1p
2n1r
4n1s
ns4ku
n3s2k2
n2s3l
n4s3p
ns2t3r
n2s1t
4n1t
n3t2a
nt4p2l
n2t3p
n3tr2u
n2t1r
nt2ru2o
nu1a4
nu3b2
nu3g
nu1i
nu1k2
nu4o3
nuo1s2
nu5s4
2n1v
2n1w
2n1z
4n1č
2n1š
2n1ž
o1a
o1b
o1c
o1d
o3d1r
o1e
o2e1t
o1f
o1g
o1h
o1i
o1j
o1k
o3k2r
o1l
ol2e1n1
o1m
om4p2r
o2m1p
o1n
o5no1
o1o2
o1p
o1r
o2ri2e
or2i
or4i4s
or4t2r
o4r1t
o2rę
o1s
o3s2l
o3s3le
os4lo
o3s2v
o3t2
ot2o1
o1v
o1w
o1y
o1z
o1ą
o1č
o1ę
o1ė
o1į
o1š
o3š2v
o1ų
o1ū
o1ž
pa3
pa1d2
pai2l
pai2m
pai2r
3pa1n
pa1p4
p2a1r
parsi1
pa4r1s
pa4r1ė2
pa1s2
pa5s1r
p2a1t2
2p1b
4p1c
4p3d2
p2e
pe1r1
pe2re
pe2r3i1m
per2i
pe4r3s
pe2rė2
2p1f
2p1g
2p1h
p2i
p3ie1š
pi2e
2p3k2
3p2l
p3le
p3li
4p5li4o
p4li2u
p3lo
p4lo1j
p4lu
p4ly
2p1m
4p1n
3po
po4g
poli1
po1l
2p1p
2p2r
p3rai
pr2a
prau2si
pra4u
prau1s
p3ra1š
p3r2i
pr4i1s2
p5ro.
pr2o
p3ro1m
p3ry
4p3rą
p3rė
p3rū
4p1s
psi1
p5s4k2
p4s3ty
p2s1t
p3s2v
4p3t2
3p4u
pusia4u1
pu1s
pusi2a
pu4s2k2
pu4s2l
p2u4t1r
pu3t2
2p1v
2p1w
p2y
2p1z
2p1č
p2ė
4p3š2
4p3ž
r2a
3ra.
ra3b
3ra1c
ra1i1m
rai4tį
ra2i3t2
ra3k2r
ra1k
3ra1l
3ra1m
ra3s4l
ra1s
ra3s1m
rau4ka.
ra4u
rau1k
3ra1v
2r1b
rbo1
r3b4r
2r1c
4r1d
2re1b2
4r1e4i1n
4re2i3t2
3re1l
re4p5
5re1s
re2s3l
3re1t
2r1f
2r1g
2r1h
r2i
3ri.
ri3d
3ri1j
ri3k3r
ri1k
2ri1l
ri2ma.
ri1m
rim2a
2r3i2m1t
3ri2o
ri3p
ri5si1
r4i1s
ri3s1t
riv4i1n1
ri1v
3ri2ą
3ri2ų
2ri2ū
4r1k
rk3ly
r2k1l
r3k4r
4r1l
2r1m
4r1n
r2o
3ro1d2
ro4g2r
ro1g
3ro1j
3ro1k
3ro1n
3ro1p
2r3o2r1g
ro1r
5ro1s
2r1p
r3p4r
2r1r
4r1s
rs4ko.
r3s2k2
rs3ko
r3s4p
4r1t
rti5k4
rt2i
rt4i3s2
r3t2r
r3t2v
r2u
2ru1k
3ru1l
4ru1n
3ruo1s
ru2o
2ru3p
3ru1s
ru4s2k2
4rut2o1
ru3t2
4r1v
2r1w
2r1z
3rą
4rą1s
4r1č
4r1š
r3š2l
r3š2m
5rų
2r1ž
sala1
sa1l
2s1a2m1ž
sa1m
sa4n5t
sa1n
sarka1
sa1r
sa4r1k
2s3b
2s1c
2s3d
s2e
se4k2r
se1k
sena3t4
s2e1n
2s1f
2s1g
2s1h
si3au1k
si2a
sia4u
si3a2v
si3a2š
si3d
si3k4
si3p4
s4i3s2
si5š2v
si1š
3s2k2
4sk.
s5ka.
4s3ke
5s4k2e1n
5sk4i
5s4kle
s2k1l
5s2k4r
5s4ku1b
s3ku
sk3va
s2k1v
sk3vi
5sky
4s5ką
5skę
2s2l
3sle
s3li
s3lo
4s3lu
4s3lū
2s1m
4sme.
4smę
4s3n
so4d1r
so1d
3s2p
s3p2e
4s4p1n
4s3p4u
4s1r
s3r2i
2s1s
2s1t
s2ta1l
st2a
s2t2e1n
4s3t4i1n
st2i
s2to1d
st2o
s2to1j
3s2to1v
st2rai
s2t1r
str2a
s2t2v
4s3tę
4s3tė
4stų
s2tū
su1
su3b1l
su1b
su3d2
su3g2
su3k2l
su1k
su3s2
susi1
su1ž4
2s1v
s2v2e
3s2vy
2s1w
2s1z
są3
4s3č
5sė
2s1š
2s1ž
t2a
2ta1b
ta3k2r
ta1k
ta5s
2ta1t
taur2a1
ta4u
tau1r
2t2a1č
2t1b
2t1c
2t1d
2te1b2
3te1m
te4o
te3t2
2t1f
4t3g
2t1h
t2i
ti4g2r
t2i1g
ti4k3l
ti1k
3t4i1n
2ti1p
4t3j
4t3k
t4k1l
4t3l
4t3m
2t1n
t2o
3toje
to1j
2to3lį4
to1l
to3s2
2to1w
2t3p
t4pj
tp3lū
t3p2l
t2p4r
2t1r
4t3ri2o
tr2i
t2ri1š
4tr2o
4t3rą
4t5rų
4t3s4
tsi1
tskr2i1
t3s2k2
t5s2k4r
4t1t
tu1a4
3tu1r
2t1v
t2vo
4tvė1j
t3vė
2t1w
3ty1d
2t1z
2t1č
3tę
t2ė1m
t3ėmu
t3ėmę
t3ėmė
2t3š2
2t3ž
u1a2
u3ai
u1b
u2b1j
u1c
u1d
u1e2
u1f
u1g
u3g4r
u4g5ri2o
ug3r2i
u1h
u1i2m
u5i1n
ui2r
u1j
u1k
u3k1l
uk2le
u3k2r
u3k1v
u5kų
u1l
u1m
u1n
u2o
u3o1r
uo4s2l
uo1s
u3p
u3p4l
up3r2o
u2p2r
u1r
u4r1k2
ur3k1l
u5r2o1
u4r3s2
u1s
u2s1a1l
u3s2l
us3la
u3s3le
usva1
u2s1v
us3v2e
u3t2
ut2o1
2u2t1r
u1u
u1v
u1w
u1y
u1z
u1ą
u1č
u1ę
u1ė2
u1į
u1š
u3š2l
u3š2n
u3š2v
u1ų
u1ū2
u1ž
uži2m
u3ž1l
u3ž3v
už1ė2
3va.
va1p4
va3t
2v1b
2v1c
2v1d
v2e
2ve3p
3ve1s
2v1f
2v1g
2v1h
viesi2a1
vi2e
vie1s
vi4s5k2
v4i1s
vi4t3r
v2i3t2
2v1j
2v1k
2v1l
2v1m
2v1n
2v1p
2v1r
2v4s
2v1t
2v1v
2v1w
2vyda4u
vy1d
2v1z
3vą
2v1č
3vė
4vė1p
2v1š
2v1ž
2w1b
2w1c
2w1d
2w1f
2w1g
2w1h
2w1k
2w1l
2w1m
2w1n
2w1p
2w1r
2w1s
2w1t
2w1v
2w1w
2w1z
2w1č
2w1š
2w1ž
y1a
y1b
y1c
y1d
y1e
y1f
y1g
ygi2a1
y1h
y1i
y1j
y1k
y4k3l
y1l
y1m
y1n
y1o
y1p
y3r
y1s
y4s2k2
y1t
y1u
y1v
y1w
y1y
y1z
y1ą
y1č
y1ę
y1ė
y1į
y1š
y1ų
y1ū
y1ž
2z1b
2z1c
2z1d
2z1f
2z1g
2z1h
2z1k
2z1l
2z1m
2z1n
2z1p
2z1r
2z1s
2z1t
2z1v
2z1w
2z1z
2z1č
2z1š
2z1ž
ą1a
ą1b
ą1c
ą1d
ą1e
ą1f
ą1g
ą1h
ą1i
ą1j
ą1k
ą1l
ą1m
ą1n
ą1o
ą1p
ą1r
ą1s
ą1t
ą1u
ą1v
ą1w
ą1y
ą1z
ą1ą
ą1č
ą1ę
ą1ė
ą1į
ą1š
ą1ų
ą1ū
ą1ž
2č1b
2č1c
2č1d
če3ko1
če1k
2č1f
2č1g
2č1h
č4i1n1
2č1k
2č1l
2č1m
2č1n
2č1p
2č1r
2č1s
2č1t
2č1v
2č1w
2č1z
2č1č
2č1š
2č1ž
ę1a
ę1b
ę1c
ę1d
ę1e
ę1f
ę1g
ę1h
ę1i
ę1j
ę1k
ę1l
ę1m
ę1n
ę1o
ę1p
ę1r
ę1s
ę1t
ę1u
ę1v
ę1w
ę1y
ę1z
ę1ą
ę1č
ę1ę
ę1ė
ę1į
ę1š
ę1ų
ę1ū
ę1ž
ė1a
ė1b
ė1c
ė1d
ė1e
ė1f
ė1g
ė1h
ė1i
ė1j
ė1k
ė4k3l
2ė3l
ė1m
ė3me
ė1n
ė1o
ė1p
ė1r
2ė1s
ė2s3l
2ė3t
ė4t1r
ė1u
ė1v
ė1w
ė1y
ė1z
ė1ą
ė1č
ė1ę
ė1ė
ė1į
ė1š
ė1ų
ė1ū
ė1ž
į1a
į1b
į1c
į1d2
į2e
į1f
į4g
į1h
į1i
į1j
į2k4
į2l
į2m
į1n
į1o
į4p
į2r
į1s
įsi1
į2s3l
į2s2m
į4s2r
į2s1t2
į2t2
į1u
į2v
į1w
į1y
į1z
į1ą
į1č
į1ę
į1ė2
į1į
į1š
į1ų
į1ū
į1ž
2š1b2
2š1c
2š3d2
š1ei
še2v
2š1f
2š1g4
2š1h
2š5i2s1t
š4i1s
šiu1k1
ši2u
2š1k2
2š1l
š2li1j
š2lu
3šly
2š1m
2š1n
š2ne1
š5no1
šo2r
2š1p4
2š1r
2š3s4
šsi1
šsika1p1
šsi3k4
4š3t2
šu4š
3š1v
š2vi
š4vy1d
2š1w
2š1z
4š3č
šė2j
2š1š
2š1ž
ų1a
ų1b
ų1c
ų1d
ų1e
ų1f
ų1g
ų1h
ų1i
ų1j
ų1k
ų1l
ų1m
ų1n
ų1o
ų1p
ų1r
ų1s
ų1t
ų1u
ų1v
ų1w
ų1y
ų1z
ų1ą
ų1č
ų1ę
ų1ė
ų1į
ų1š
ų1ų
ų1ū
ų1ž
ū1a
ū1b
ū1c
ū1d
ū1e
ū1f
ū1g
ū1h
ū1i
ū1j
ū1k
ū4k3l
ū1l
ū1m
ū1n
ū1o
ū1p
ū1r
ū1s
ū4s3k2
ū2s3l
ū2s3t
ū1t
ū1u
ū1v
ū1w
ū1y
ū1z
ū1ą
ū1č
ū1ę
ū1ė
ū1į
ū1š
ū1ų
ū1ū
ū1ž
ža4n1t4
ža1n
žan4t3s5
2ž3b2
2ž1c
2ž3d2
ž2e1n1
2ž3f4
2ž3g
2ž1h
ži3mu
ži1m
ži2o3
ž1j
2ž3k2
2ž1l
ž2lu
4ž1m
2ž1n
2ž3p
2ž1r
2ž1s
žsi1
ž4s2k2
ž4s5l
ž2s3t
4ž3t2
ž2u
žu3s2
3ž2v
ž4vi
ž3vo
2ž1w
2ž1z
2ž1č
žį1s3
2ž1š
2ž1ž
PK
!<2:F6F6hyphenation/hyph_mn.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1ба
1бе
1бё
1би
1бо
1бө
1бу
1бү
1бы
1бь2ю
1бэ
1бю
1бя
1ва
1ве
1вё
1ви
1во
1вө
1ву
1вү
1вы
1вь2ю
1вэ
1вю
1вя
1га
1ге
1гё
1ги
1го
1гө
1гу
1гү
1гы
1гь2ю
1гэ
1гю
1гя
1да
1де
1дё
1ди
1до
1дө
1ду
1дү
1ды
1дь2ю
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ю
1зэ
1зю
1зя
1ка
1ке
1кё
1ки
1ко
1ку
1кү
1кэ
1кю
1кя
1ла
1ле
1лё
1ли
1ло
1лө
1лу
1лү
1лы
1лэ
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ю
1нэ
1ню
1ня
1па
1пе
1пё
1пи
1по
1пө
1пу
1пү
1пы
1пь2ю
1пэ
1пю
1пя
1ра
1ре
1рё
1ри
1ро
1рө
1ру
1рү
1ры
1рэ
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ю
1тэ
1тю
1тя
1фа
1фе
1фё
1фи
1фо
1фу
1фы
1фэ
1фю
1фя
1ха
1хе
1хё
1хи
1хо
1хө
1ху
1хү
1хы
1хэ
1хю
1хя
1ца
1це
1цё
1ци
1цо
1цө
1цу
1цү
1цы
1цэ
1цю
1ця
1ча
1че
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а1я
2аа
2б3ю4у
2б3ю4ү
2в3ю4у
2в3ю4ү
2г3ю4у
2г3ю4ү
2д3ю4у
2д3ю4ү
2ж3ю4у
2ж3ю4ү
2з3ю4у
2з3ю4ү
2и1е
2й1е
2й1ё
2й1ю
2й1я
2л3ю4у
2л3ю4ү
2м3ю4у
2м3ю4ү
2н3ю4у
2н3ю4ү
2о1е
2о1ё
2о1я
2оо
2ө1е
2өө
2п3ю4у
2п3ю4ү
2р3ю4у
2р3ю4ү
2с3ю4у
2с3ю4ү
2т3ю4у
2т3ю4ү
2у1я
2у3ю
2уу
2үү
2х3ю4у
2х3ю4ү
2ц3ю4у
2ц3ю4ү
2ч3ю4у
2ч3ю4ү
2ш3ю4у
2ш3ю4ү
2ъ3е
2ъ3ё
2ъ3ю
2ъ3я
2ь3е
2ь3ё
2ь3я
2э3е
2ээ
2ю3а
2ю3и
2ю3о
.авто3а4г
.ав1то
.авто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б
.авт2оо
.авто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в
.ан1ти
.анти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с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р
.астр2оо
.астро3с4п
.астро3ш4т
.аэро3д4р
.аэ1ро
.аэро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р
.би2оо
.био3п4л
.био3п4р
.био3с4к
.био3с4п
.био3с4т
.био3с4ф
.био3х4р
.био3э4к
.био3э4л
.био3э4н
.био3э4т
.бь4
.въ4
.вь4
.газо3а4б
.га1зо
.газо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б
.гид1ро
.гидро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к
.гидр2оо
.гидро3п4л
.гидро3п4н
.гидро3с4т
.гидро3с4ф
.гидро3т4р
.гидро3у4д
.гидро3у4з
.гидро3у4р
.гидро3х4л
.гидро3э4к
.гидро3э4л
.гидро3э4н
.гипе2р3а4д
.ги1пе
.гипе1ра
.гипе2р3а4з
.гипе2р3а4к
.гипе2р3а4л
.гипе2р3а4м
.гипе2р3а4р
.гипе2р3а4ц
.гипе2р3а4э
.гипе2р3е4а
.гипе1ре
.гипе2р3е4м
.гипе2р3е4р
.гипе2р3е4с
.гипе2р3и4з
.гипе1ри
.гипе2р3и4м
.гипе2р3и4н
.гипе2р3о4в
.гипе1ро
.гипе2р3о4к
.гипе2р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р
.гипе1ру
.гипер3ф4р
.гипер3х4л
.гипер3х4р
.гипер3э4к
.гипе1рэ
.гипер3э4л
.гипер3э4н
.гипер3э4о
.гипер3э4с
.голо3б4л
.го1ло
.голо3э4д
.гомо3а4з
.го1мо
.гомо3а4к
.гомо3а4т
.гомо3к4л
.гомо3п4л
.гомо3с4п
.гомо3т4р
.гомо3ф4т
.гомо3э4н
.гомо3э4п
.гь4
.ди1а3г4н
.ди4ст5ри
.ди4ст5ро
.диа3д4р
.диа3к4л
.диа3к4р
.диа3с4к
.диа3с4п
.диа3с4т
.диа3т4р
.диа3ф4р
.диазо3т4р
.диа1зо
.диазо3э4т
.диазо3э4ф
.дина3т4р
.ди1на
.дь4
.евро3а4з
.ев1ро
.евро3а4т
.евро3и4е
.евро3к4р
.евро3о4б
.евр2оо
.евро3с4п
.евро3с4т
.изо3а4в
.и1зо
.изо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к
.из2оо
.изо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кт
.и1на
.ин3а4п
.ин3а4у
.ин3б4р
.ин3г4р
.ин3к4л
.ин3к4р
.ин3с4тр
.инте2р3а4к
.ин3те4р
.ин1те
.инте1ра
.интер3г4р
.интер3к4в
.интер3к4р
.интер3п4р
.интер3ф4л
.кило3а4м
.ки1ло
.кило3г4р
.кило3э4л
.кино3а4к
.ки1но
.кино3а4н
.кино3а4п
.кино3а4ф
.кино3б4л
.кино3д4р
.кино3и4с
.кино3к4л
.кино3к4р
.кино3о4п
.кин2оо
.кино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п
.к2оо
.ко3о4р
.ко3п4л
.ко3э4ф
.кь4
.ль4
.макро3а4н
.мак1ро
.макро3а4с
.макро3б4л
.макро3б4р
.макро3г4л
.макро3г4н
.макро3г4р
.макро3и4н
.макро3и4с
.макро3к4л
.макро3к4р
.макро3о4в
.макр2оо
.макро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м
.ме1га
.мега3п4р
.мега3с4к
.мега3с4п
.мега3э4л
.микро3а4в
.мик1ро
.микро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б
.микр2оо
.микро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м
.мил1ли
.милли3г4р
.милли3о4м
.милли3э4к
.моно3а4в
.мо1но
.моно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к
.мон2оо
.моно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р
.мо1то
.мото3к4р
.мото3п4л
.мото3п4р
.мото3с4п
.мото3ш4л
.мь4
.на3у
.нано3а4д
.на1но
.нано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с
.нь4
.орто3а4л
.ор1то
.орто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к
.па1ра
.пар2аа
.пара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р
.па1ро
.поли3а4в
.по1ли
.поли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э4л
.поли3э4н
.поли3э4т
.поли3э4ф
.пре3э4к
.п1ре
.пре3ю4д
.про3а4г
.п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л
.про1то
.прото3х4р
.прото3э4р
.проф3г4р
.пъ4
.пь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к
.рет1ро
.ретро3а4у
.ретро3г4р
.ретро3о4т
.ретр2оо
.ретро3ф4л
.санти3г4р
.сан1ти
.стерео3а4д
.с1те
.сте1ре
.стерео3а4к
.стерео3б4л
.стерео3г4р
.стерео3и4з
.стерео3с4к
.стерео3с4п
.стерео3т4р
.стерео3э4л
.стерео3э4н
.стерео3э4ф
.супер3а4г
.су1пе
.супе1ра
.супер3а4д
.супер3а4к
.супер3а4н
.супер3а4р
.супер3а4э
.супер3г4р
.супер3е4с
.супе1ре
.супер3и4з
.супе1ри
.супер3и4к
.супер3и4м
.супер3и4н
.супер3к4л
.супер3п4р
.супер3с4к
.супер3с4п
.супер3с4т
.супер3х4р
.супер3э4в
.супе1рэ
.супер3э4к
.супер3э4ф
.съ4
.сь4
.ть4
.фь4
.хризо3и4д
.х1ри
.хри1зо
.хризо3п4р
.хризо3с4т
.хь4
.элек3т1ро
.э4лек4т
.э1ле
.элек3тро3а4в
.элек3тро3а4г
.элек3тро3а4к
.элек3тро3а4н
.элек3тро3а4э
.элек3тро3б4л
.элек3тро3и4з
.элек3тро3и4с
.элек3тро3о4б
.электр2оо
.элек3тро3о4в
.элек3тро3о4г
.элек3тро3о4д
.элек3тро3о4к
.элек3тро3о4п
.элек3тро3о4с
.элек3тро3о4т
.элек3тро3о4ф
.элек3тро3о4ч
.элек3тро3с4т
.элек3тро3т4р
.элек3тро3э4к
.элек3тро3э4н
.элек3тро3э4р
3ав1то
3актив
ак1ти
3ап1па
3г4рад
г1ра
3г4рам
3г4раф
3им4пул
им1пу
3ин3ст4ру
3ин3те4р
ин1те
3к4ва
3к4лас
к1ла
3к4ри
3оп3тик
оп1ти
3п4ро
3п3роек
пр2о1е
3с4коп
с1ко
3с4фе
3с4хе
3ск4ла
3ск4ле
3ск4ло
3ск4ля
3ск4ра
3ск4ре
3с3к4ри
3ск4ро
3ск4ру
3ск4ры
3сп4ла
3ст4ра
3ст4ре
3ст4ри
3ст4ро
3ст4рук
ст1ру
3ф4рагм
ф1ра
3х4лор
х1ло
3х4ром
х1ро
3ш2таб
ш1та
3ш2тат
3э4к5ви
3э4ко
3э4лек4т
э1ле
3э4ле1ме
3э4нерг
э1не
3э4нт1ро
3эф1фе
PK
!<vp
,
,hyphenation/hyph_nb.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a4
.aa4
.ab4b4a
.a2b1b
.ab7be
.ab6s5t6
.a6b1s
.ac6tin
.a4c1t
.ac1ti
.ad4e8l4s
.a4del
.a1de
.ad8la.
.a4d1l4
.ad3la
.ad6le.
.ad1le
.ad2r4
.a8d1s2
.a2f7f
.a2f5t4
.af5f6u
.ag6na.
.ag1n
.ag1na
.ag6ne.
.ag1n4e
.a1g4r4
.ai2
.a6k
.ak6ka.
.a2kk
.ak1ka
.ak6ke.
.ak1ke
.ak3kl4
.ak3kr6
.ak6ne.
.a2k1n2
.ak5ne
.ak6r6
.ak8sa.
.ak1sa
.ak4se.
.ak1se
.ak6ta
.a6k1t
.ak4te.
.ak1te
.akte4r
.akte1r5e
.ak5tr4
.akva7
.akv2
.a6l
.albu5e2n
.a2l1b2
.al1b2u
.al1fa3
.a4l1f
.al4ge.
.a2lg
.al1ge
.al5gi
.al6ka.
.a6l1k
.al3ka
.al4ke.
.al1k4e
.al4kom
.al4la.
.a2l1l
.al4le.
.al1le
.al5leg
.al5lo
.al8l5s6
.al5ma.
.a2l1m
.al1ma
.al8me.
.al1me
.al5m6in
.al1mi
.al4na
.a2l1n
.al6s7k
.a8l1s
.als5o
.alt4a
.a2l1t
.al4ta.
.al5tar
.al4te.
.al1te
.alt3o
.al6ve.
.a8lv
.a2m7b
.a6me
.am6ma.
.a2m1m4
.am1ma
.am4me.
.am1me
.a6m4s4
.am6ta
.a2m1t
.a6n
.ana3b
.a1na
.an4da.
.a6nd
.an1da
.an4de.
.an1de
.an5d4ra
.andr4
.an4d5ø
.an3er
.a1ne
.anes5
.an4ga.
.a6ng
.an1ga
.an3g4e
.an4ge.
.an2g6e4s
.an4ja.
.a4n1j
.an4ka.
.a2nk
.an1ka
.an4ke.
.an1ke
.an4la.
.a4n1l
.a4n3n
.an4na.
.an1na
.an7nal
.an1n4e
.an4ne.
.a6n5s8
.an4sa.
.an4se.
.an1se
.a6n1t2
.an4ta.
.an4te.
.an1te
.an5tem
.an5t2e1p
.an4ti.
.an1ti
.an6tin
.an4tis
.an4tiv
.ap8lan
.a1pla
.apl6
.a2p1p6
.a2p8t
.ar1b4i5
.a2r1b8
.ar6de.
.a4rd
.ar1de
.a2re4o7
.a1re
.ar4ge.
.a2r1g
.ar1ge
.ar5g6es
.ari6a
.a1ri
.ar2i8e
.arie5ne
.ari1e4n
.ar5ka
.a2r1k
.ar8ka.
.arlan9
.a2r1l
.ar4me.
.a2r1m
.ar1me
.ar7m2ea2
.ar4na.
.a2r2n
.ar3na
.ar3ne
.ar4ne.
.ar5ne1s2
.ar6ra.
.a2r1r
.ar6r7u
.ar6ta.
.a6r1t
.ar4te.
.ar3te
.ar9ti
.ar6va.
.a2r1v
.arv4a
.ar4ve.
.ar4ve2d1
.ar4ver
.arvi6
.a6s
.a1s8ka
.as4ke.
.a2s1ke
.as5k2e1se
.as2ke1s
.a2s4le.
.as1l4
.as1le
.as6pa.
.asp2
.as1pa
.a4s5s
.ast5ru
.astr4
.a2t4h
.at4ki
.a4t3k2
.at4le.
.a6t3l6
.at1le
.at4na.
.a8t1n
.at3na
.at2o4
.a4t3s
.at1te4
.a6tt
.at1t6r4
.a5ty
.au8de
.a2ud
.a6u6e
.au8ga
.au4ge
.au4ka.
.au1k2a
.au8le.
.au1le
.au4r
.au6sa.
.a4u1sa
.au6se.
.a2u1se
.aus9k
.au4s1p2
.avi2
.a1v7i6d
.av7in1de
.a1vin
.avi6nd
.av4la.
.a6v1l2
.av4le.
.av1le
.a8v7s6
.b6
.ba6by5
.b4a
.b8a6la.
.ba3la
.ba4le.
.b2a1le
.ba4ne.
.ba1ne
.ban4k3l4
.ba2nk
.ba4re.
.ba3re
.ba4ri.
.b2a3ri
.ba8te.
.ba1te
.be4de.
.be1de
.bede4n
.be6d5e1ne
.be4d5et
.be8di.
.be1di
.be5e4d
.be3e2
.be6ke.
.be5ke
.be4la.
.b2e1la
.be9nar
.b2e4na
.be4ne.
.be3ne
.be6ra.
.b4er
.b2e1ra
.be6re.
.be1re
.be4res
.b6er6e6tt
.bere4t
.be8ri.
.be1ri
.be7ska
.b4e1s2
.b6e6sten
.bes1te
.b4e4ta.
.b4et
.be1ta
.be6te.
.b2e3te
.b2e5t4v
.b2i6de.
.b4i
.bi1d
.bi1de
.bi6en
.b2ie
.bi4le.
.bi1le
.bi3let5
.bi6t3r4
.bl4
.b2la4d
.b4le4k3
.b1le
.ble5k6e
.blek4k3f
.bl2e2kk
.blekk3s4t6
.blekk7s
.blekk9s8v4
.bli6ng5
.b1li
.bo8da
.b2o
.b2o1d
.bo8de.
.bo3d4e
.bo8g1s8
.b2og
.bok5
.bo4k4en.
.bo5k6en
.bo1ke
.b4o8la
.b4ol
.b2on2
.bo4na.
.bo1na
.b2o6r6t5
.bor1t6e
.bor8ti8
.bort9r4
.brud8d7s6
.br8
.b4ru
.br2ud
.bru6d1d
.brudds1å7
.bu6da.
.b2u
.bu3da
.b2ud
.bu3d6r4
.bu4et
.bus6sy
.bu4s1s
.by6de.
.by1de
.by8ta.
.by1ta
.bæ2
.b6ø6k5a
.b4ø
.bø8nen
.bø2n
.bø1ne5
.bø8ner
.c4
.ca4en.
.c4a
.c2a4e2
.c2a4r4s5
.ca6se
.c2e6b2
.c2os1
.co4st
.d6
.da4ge.
.da1ge
.da4le.
.d2a1le
.dag1s6e
.da8gs1
.da2m
.da4ne.
.d8a1ne
.d4a4ta5
.dau3s
.deka9r
.d4e1ka
.dek2o7d
.de5k2o
.de4le.
.de1le
.de4l5ei
.d4e8l5s6
.de4mi.
.de1mi
.de3m6o7e
.de1mo
.den5sl4
.d2e6ns
.de4re.
.d2er
.de1re
.de4r5e4t
.de6r3i4
.de4ri.
.de7r4i5v
.de4r5om
.d2ero
.de6s5m
.de1s
.d4e4so
.de4sto
.d4es2t
.de4u
.devi5s
.de1v
.di2a
.di5e4l
.d2ie
.di6er
.di4et.
.di1et
.di9f6t
.d2if
.di4ne.
.d4i1ne
.dings7a2
.din8g2s1
.di6ng
.d6i4sk
.di8s3t
.do4e2n
.d6o1e
.do4er
.do8et
.d2o1me5
.d6o4ra
.do6re.
.d8o1re
.drau4m
.dr4
.ds4
.du4a
.du4en.
.du1e2n
.du4er
.du2ft3
.du4ge.
.du1ge
.du2k3n2
.du4se.
.du1s
.d2u1se
.du4st4
.dy4re.
.dø6la.
.dø1la
.d2ø6r3
.dø7r4a
.dø7r4e
.dø4ve.
.då5ren
.då7ret
.e6
.eb4b4a
.e1b2
.e2b1b
.e1cu6
.ed4da
.e6d1d
.ed4le.
.e4d1l4
.ed1le
.e1fo7
.e1f
.ef4ta
.e8ft
.eft8a5s4
.ef6ter
.ef1te
.eg8de.
.e8g1d
.eg1de
.e1ge2
.ege5l
.eg6ga.
.e4g1g
.eg1ga
.eg4ge.
.eg1ge
.eg4gel
.e6g8la.
.egl2a
.eg4le.
.eg1l6e
.eg6na.
.eg1n
.eg1na
.eg4ne.
.eg1n4e
.eg2o7t8
.e1go
.e8gs4
.eg8se.
.eg1se
.eg5si
.ei2
.e2i1d5a
.e2i5den
.ei1de
.ei4ga.
.ei3ga
.e2ig
.ei7g2er
.ei1g6e
.ei1k5a
.e2ik
.ei4ke.
.ei1ke
.ei3l
.ei3m
.ei8ma.
.e4i1ma
.ei8me.
.ei1me
.ein7as
.e6i1na
.e4i3ni
.ein5og5
.e2i1no
.ein5s6ta
.ei6n1s
.e4in8s7to
.ei9rar
.e2ir
.ei1ra
.ei5res
.ei1re
.ei1r9u
.ei5te
.ei1t9r4
.ekk4o
.e2kk
.ek4le.
.e1kl4
.ek1le
.ek3li
.ek6ne.
.e2k1n2
.ek1ne
.e1k4r6
.ek8sa.
.ek1sa
.ek3se
.ek8se.
.ek4sp2
.ek1s6pi
.eks3t4
.ek5s4ta
.ek8ta
.e6k1t
.ek4te.
.ek1te
.ek7to
.el8da.
.e6ld
.el1da
.el4de.
.el1de
.el4g5r4
.e2lg
.el4i5ne
.e1li
.e6l3k2
.e2l5l
.e8l6s
.els6a
.el6s4k2l4
.els6t
.el6ta.
.e2l1t
.el6te.
.el1te
.el6veg
.e8lv
.e2m3b2
.em1fa9
.e2m1f
.em4ma.
.e2m1m4
.em1ma
.em8me.
.em1me
.em6na.
.e2m1n
.em1na
.e8m5p
.e1n5a
.en4da.
.e6nd
.en1da
.en4de.
.en1de
.end5r4
.en4ga.
.en1g6a
.e6ng
.en6gav
.en3ge
.en4ge.
.en6g5r4
.en8gs6
.en6ka
.e2nk
.en4ke.
.en1ke
.en5og5
.e1no
.en6sa
.e6ns
.en4se.
.en1se
.en5so
.en3sp2
.e6n5t
.en6te.
.en1te
.ent4r4
.en6t5ra
.en4t8re
.e1n3ø4
.e2p6t
.e1p
.e1r8a
.e1r4e
.er4g5r4
.e2r1g
.er4ke
.e2r1k
.er4la.
.e2r1l
.er4le.
.er1le
.er6ma.
.e2r1m
.er1ma
.er4me.
.er3me
.er4mek
.er4na.
.e2rn
.er3na
.er6ta.
.e6r1t
.er4te.
.er1te
.es9ast
.e1sa
.e1sas
.es1k
.e1s3ka
.e2s8ka.
.es4ke.
.e2s1ke
.es5l4
.es8la.
.e1sla
.e2s8le.
.es1le
.e1s3p2
.es6pa.
.es1pa
.e8s9r
.es8sa.
.e4s1s
.es4se.
.es1se
.es4so
.es5ta
.es3te
.e6s6te.
.e4s7tet
.es5ti
.est3r4
.e1s7tu
.et8la.
.e6t3l6
.et8le.
.et1le
.et4na
.e8t1n
.et4ne.
.et1ne
.e4t4s1
.et8sa.
.et5s4e
.et6se.
.et1s4i
.et1te4
.e6tt
.et6ter
.et6ti
.ev8ja
.e1v
.e2vj
.ev4je.
.ev1j2e
.ev4ne.
.e2v1n
.ev1ne
.ex4
.f4
.f2a2e2
.fa8g3s4
.fa4ne.
.fa1ne
.fan3t4o
.fa6n1t
.fe4e2
.fei4l5i
.f4eil
.fe2l
.f2e1la9
.fe2l5l
.fel4ta
.fe2l1t
.fe4ma
.f8e8me
.fem5o6g5
.fe4mo
.fe2m5t
.fer8ro
.f2e2r2r
.fe4r6s
.fe8sl4
.fe8st5
.fes3t6e
.fi4b5
.fi6a
.fi4le.
.fi1le
.fire5o6g5
.f2i2r
.fi1re
.fire3o6
.fi2s6k
.f2jel4
.fj2e
.fla4t5o
.f2l2
.fo4bi.
.f2o3b4i
.fol2
.fo2r
.for3d6ri
.fo4rd
.fordr4
.for7d6ra
.for5en.
.for7e6n
.f8ore
.for6m5s
.fo2r1m
.for7s6o
.f2o4rs
.f2re6e3
.fr2
.fri5e6re
.fri1er
.fr2ie
.fug2
.fu6ge.
.fu1ge
.ful2
.fu8se.
.f2u1se
.fy8s4e
.f2y3s
.fø8rel
.f8ør
.fø1re
.g6
.gaf7
.ga4le.
.g2a1le
.ga8li.
.ga1li
.ga4me
.ga4ne.
.ga1ne
.gan8g5s4
.ga6ng
.gas4
.gas5ta
.g2a2t
.ga4ve.
.g2a1ve
.ga9ve3e2
.g2e2a
.ge1l4e
.ge3ne
.g2en
.ge6ni5
.ge6n5s
.ge4o6
.ge4st
.g6es
.ge5s6tap
.ge9sv2
.gh2a5
.g1h
.gif4t5s
.g2if
.gi8ft
.gi4n
.gi4s
.gis7p2
.g4i8v3a
.gl6
.glo6i
.g1lo9v
.gl2y5s
.glø9se
.g1lø
.gnå7la
.g1n
.g2nå
.g1nål
.go5de1s
.g2o1d
.go1d4e
.g2os7
.gra4v3e6nd
.gr4
.g2rav
.gr2a1ve
.gra1ven
.g2re2i7e
.grun6n5s
.gru4nn
.gru4s5s
.gr4us
.gu4de.
.g2ud
.gu1de
.gu2d5v4
.gu4lat
.gu1la
.gus1
.gu4tu.
.gu1tu
.gø2
.gå1s4e5
.gå2s2
.gå4v
.h4
.hai5s6
.h2a
.hai1
.h4a6ka.
.ha5ka
.ha8ma.
.ha5ma
.hand5s6l4
.h4an8ds
.ha6nd
.handsla9
.ha6v5ak
.ha1v4a
.ha6vi
.he4er.
.he3e2
.he2i7e
.he2i
.he2n
.he1n3i2
.he6r5i6
.h2e6r5o6
.he9r6o1e
.he7r6oi
.he7r6ol
.he9r8os
.he4s4s5
.he4t5
.he4t6s5
.het7s6e
.h2e5t6e
.hi2l4l
.hi4n
.hi6re.
.h2ir
.hi1re
.h2o2d
.h4o
.h6o2e
.ho4re.
.h8or2e1
.ho5ren
.ho7ret
.ho8ta.
.h2ot
.ho1ta
.ho8va.
.ho1v
.hu4di
.h2u4d3
.hus6val
.hus1v2
.hvit3
.hv4
.hvi3t4e
.hy6ra.
.h6y
.hy1ra
.hy4re.
.hø4i5
.hø4re
.hø4va
.hø4ve.
.hø1ve
.høy6s1t5æ
.hø2y1s4
.høys2t
.hå8en.
.h6å
.h4å1e
.håe2n
.hå8er
.hånd5s6l4
.hå6nd
.hån8ds
.i2
.i3a4
.i3bl2
.i1b2
.i4da
.ifø5re
.i3fø
.if8ør
.i6g8la.
.ig1l
.igl2a
.ig4le.
.ig1l6e
.i4her
.i3h
.ik2
.i2k6k
.ik1ke5
.i3k6l4
.i6k5t
.ik8te.
.ik1te
.i5k1v2
.i3la
.i4la.
.i8lan1de
.il6a6nd
.ilbo8da
.i2l1b2
.ilb2o
.ilb2o1d
.il6d3r4
.i6ld
.il6de.
.il1de
.i3leg
.i1le
.i6le1s
.il4ja
.i2lj
.il6je.
.il1j2e
.il6ke.
.il1k4e3
.i6l1k
.il4le.
.i2l1l
.il1le
.il6sk
.i8l1s
.il4te.
.i2l1t
.il1te
.i6me
.imø5te.
.i3mø2
.i7m4øt
.imø1te
.in5ad
.i1na
.in5de
.i6nd
.in8ga.
.i6ng
.in1ga
.in4ge.
.in1ge
.in3gr4
.in6gri
.i2n5k6
.in6ka.
.in1ka
.i4n4n3
.in5n6e
.in4ne.
.in6n7eks
.in2nek
.in6n7e4ts1
.in6n7e6tt
.in4n3i4
.i6n5s
.in7t4es
.i6n1t
.in1te
.io4na
.i2on
.io4nom
.io1no
.i4rer
.i1re
.i2r7k8
.ir8ra.
.i2r1r
.ir6re.
.ir3r6e
.i6sa
.i3sc
.i6se
.is7k6
.isl4a8ga
.is1l4
.is6lam
.i2s4le.
.is1le
.is4let
.is4me.
.is1m
.is1me
.i8s8na.
.is1n
.is1na
.i4s8ne.
.is5ne
.is5pa
.is1p2
.i5s1pe
.i4s3s4
.is6sa.
.is4se.
.is1se
.is5sk
.ist6
.i2s8te.
.i2s1te
.i5s2ted
.i5s2tem
.is7ti
.i6s7tj
.i6s7tr4
.is8ut.
.i2s1u4t1
.i6s5ø
.i5t2i
.i3va
.i4van
.i4var.
.i3ve
.i4vel
.i6ven
.i4v8er.
.i4ve1re
.iv8ra.
.i2v3r8
.iv6re.
.iv1re
.i3ø
.j4
.ja4de.
.ja1de
.j2a6e2
.ja4ne
.ja8se.
.ja7se
.ja4va.
.ja1va
.j2e2a2
.j2e
.je2l
.je2m
.je5r6e
.jer6n5s4
.j2e2rn
.je4ta
.ji4
.jo8en.
.j2o
.j6o1e
.joe2n
.jo4se.
.j2os
.jo3se
.ju6a
.ju6la
.ju2l
.ju1le3
.ju4l4i3
.jø4d2e1p
.j2ø
.jø1de
.k6
.kaf2
.k8a6la.
.ka6li.
.ka1li
.kalve5l8
.ka8lv
.ka5me
.ka3na
.ka4ne.
.ka1ne
.ka4p3r6
.ka4ra.
.ka1ra
.ka5r6a5v
.ka6re.
.ka1re
.ka5rin
.k2a1ri
.ka4te.
.ka1te
.ka5t6h
.kau9k
.ka6va.
.ka1va
.ka4ve.
.k2a1ve
.ke6e2
.kel3
.kier3
.k4ie2
.k6i4na
.ki7ni
.k2i2no3
.k2ir2
.ki4se.
.ki3se
.ki8va.
.k6iv
.k4i1va
.ki6ve.
.ki1ve
.kje3de5
.kj2
.kj2e
.kje4k
.k4je1k7l4
.kj4ø4nn4
.kj2ø
.kle4s
.kl4
.k1le
.kl4i5ne
.k1li
.klo9va
.k1lov
.kly7sa
.kl2ys
.kna7se
.k1n2
.k1na
.kne4p3r6
.k1ne
.k2n2e1p
.ko6da.
.ko5da
.k2o1d
.ko7gr4
.k2og
.k4o4la
.k2o3le
.ko8le.
.k3o6l5j
.ko3pe
.ko6pe.
.kor6s5ed
.k2o4rs
.kor1se
.ko4se.
.k2os
.ko1se
.ko6ta.
.k2ot
.ko1ta
.ko4te.
.ko1te
.ko6ve.
.krin8g5s1
.kr6
.kri6ng
.ks6
.ku5f4l2
.ku4le
.ku8ra.
.ku7ra
.ku4re.
.ku1re
.ku8ta.
.ku1ta
.ku8te.
.ku1te
.kva4r1a
.kv2
.k4v4e9ka
.kve5ke
.ky8la.
.ky1la
.ky4le.
.ky1le
.ky6te.
.ky1te
.kø3s4
.kå6pa
.l6
.la4da.
.l6a1da
.la4de.
.l6a1de
.l4a4ga
.la4ge.
.la1ge
.la5g2er
.la4ma.
.la1ma
.la6ta.
.l4a1ta1
.la4te.
.la1te
.la3tr4
.la4va
.l2a3ve
.la4ve.
.le8ar
.l2ea
.le4da.
.le1da
.le4de.
.le1de
.le4er
.le3e2
.le6e6t
.le2f
.l2ef3l2
.leg4a5ta1
.l2e1ga
.leg2at
.le4ge.
.le1ge
.le4gel
.le8gi.
.le3gi
.lei5er.
.le2i7e
.lei4ve
.le4ke.
.le1k6e
.l2e4k5r6
.le1mu9
.le4ne.
.le1ne
.le6o7
.le7s6a
.le4se.
.l2e1se
.le4sek
.le4se5s2
.le4s5p2
.le2t
.le7ta
.let6tan
.le6tt
.let1ta
.le2u3
.le3va
.le1v
.le4va.
.le4ve.
.le4v4es
.li8a
.l2i4de.
.li1de
.l2i4e4
.li5e1ne
.li1en
.l2i2g
.li1ga3
.l2i2k
.li2k5k6
.li1k3o
.lik3s
.l4i4ma
.l2i2nk6
.li6ra
.l2ir
.li4re.
.li1re
.li4sa
.li4se.
.li1se
.li4ta.
.li1ta
.li4te.
.li1te
.li5t2i
.li4ve.
.li1ve
.li4v5en
.liv8s7u6
.li8v2s3
.liv4s5v2
.l6o4e
.lo6ge.
.l2og
.lo5ge
.lo8gi.
.lo1gi
.lo6g5r4
.lo4i
.lo6na
.l2on
.l2o8o
.lo6ri
.lo8sa.
.lo7sa
.l2os
.lo4se.
.lo1se
.lo6te
.l2ot
.lo4va
.lo3ve
.lo4ve.
.l2u4d
.lu2e
.lu4na
.lu6pa
.lu4pe.
.lu1pe
.lu6ra.
.lu1ra
.lu4re.
.lu1r2e
.lu6se.
.l2u1se
.lu4ta.
.luta3
.lu9ta1s4
.ly4de.
.ly1de
.ly8d3s
.l4y8e
.ly8ge
.ly5s6e
.ly4se.
.l2ys
.ly4sk4
.ly4s5s6
.ly8st3r4
.lys2t
.ly4te.
.ly1te
.ly4ve.
.ly3ve
.ly1v
.lø8de.
.lø1de
.lø6en.
.l4ø1e2
.lø8ne.
.lø1ne
.lø6pa.
.lø1pa
.lø4pe.
.lø1pe
.løv5i
.løy7e6ne
.lø4y1e
.løye4n
.lå8ma
.l6åm
.lå5re
.lå6re.
.lå4te.
.lå1te
.m8
.ma4ge.
.ma1ge
.ma2g9r4
.ma3ka
.ma4ke.
.m2a1ke
.mak6t5at
.ma6k1t
.mak4t5s
.ma4le.
.m2a1le
.ma4li.
.ma1li
.m6a4na.
.ma1na
.ma4ne.
.ma3ne
.ma4ni.
.ma1ni
.ma4ra.
.ma1ra
.ma4re.
.ma1re
.ma4ri.
.m2a1r4i
.ma4sa.
.m4a1sa
.ma4si
.ma6st
.m4a8ta1
.m6at
.ma6t8h
.mat5t8o
.ma6tt
.ma4v
.me4d3
.me6d5ei
.me1de
.me6d4i
.me6d7in
.me6d5r4
.mei5er.
.me2i7e
.mei5et
.me2k5l4
.me6la.
.m2el
.m2e1la
.mel3l6o
.me2l1l
.m2e4l5ø
.me4ne.
.me1ne
.m4e3ri
.me4sk
.me2t6a3
.m4e5ta4l1l
.me3tal
.mes5ti
.me4tri
.m2e1tr4
.mi6kj2
.m2ik
.mi4le.
.mi1le
.mi4me.
.mi1me
.mi4ni.
.mi1ni
.mi4n2ik
.m2i2n4k3
.min5k4e
.mi6s3t4
.m1ne6
.m1n
.mo5d4e
.m2o1d
.mo6er
.m6o1e
.mo4na.
.m2on
.mo1na
.mo8ne.
.mo5ne
.mon4st
.mo6n1s
.mons6t5r4
.mor5d6e
.mo4rd
.mo4re.
.m8o3re
.mor3t6a5
.m2o6r1t
.mo4se.
.m2os
.mo1se
.mo4ta
.m2ot
.mo4tek
.mo1te
.mo4ter
.mo4tr4
.mo5v
.mu8ga
.mu8ge.
.mu1ge
.mu6le.
.mu1le
.mu2l5e6s
.mur7
.mu5r8e
.my8ka
.m6yk
.my4ke.
.my3ke
.mø4re.
.mø1re
.mø5re1s2
.m4ø2t
.må6la.
.m6ål
.må1la
.mål3o
.n8
.nab2o3
.na6ge.
.na3ge
.nak2
.na8ke.
.n2a1ke
.na6n5s
.na4r
.na4sa.
.n4a1sa
.nat2
.na3tr4
.nat6tr4
.na6tt
.ne4de.
.ne1de
.ne4den
.ne4d5i
.ne4d3r4
.ne8d3s4
.ne2i7e
.ne8pa.
.n2e1p
.ne1pa
.ne6pe.
.ne5pe
.ne2s
.ne4t
.ni8ar
.ni6er
.n2ie
.nig4l
.n2ig
.ni4na.
.n6i1na
.ni5o6g5
.n4io
.ni4pa.
.ni1pa
.ni6pe.
.ni1pe
.ni8sa
.ni6se.
.ni1se
.ni1ta9
.n4it
.ni4to
.n4i4va
.no6de.
.n2o1d
.no1d4e
.n6o6e
.no8ka
.no4me.
.n2o1me
.no8mi.
.no1mi
.no4r
.no8se
.n2os
.no8si.
.no1si
.no4va.
.no1v
.nu4e
.ny5a
.ny8sa.
.n2y1s
.ny1sa
.ny6se.
.ny5s4e
.ny4te.
.ny3te
.næ8r9a6st.
.næ1
.næ4re.
.næ1re
.nø6d4d
.nø4re.
.nø1re
.nå4de.
.n6å1d
.nå1de
.nå6le.
.nå1le
.o6
.ob6o5e
.o1b4o
.od4da.
.o1d
.o6d1d
.od1da
.od4de.
.od1de
.od8la.
.o4d1l4
.od3la
.od8le.
.od1le
.off1si6
.o2ff
.of2f1s
.of4te.
.o2ft
.of1te
.of5t2e1b2
.og4
.o2k1k
.ok8ka.
.ok1ka
.ok8ke
.ok4se.
.ok1s4e
.ol8d3s
.o6ld
.ol4ga.
.o2lg
.ol1ga
.o1li5
.ol8la
.o2l1l
.ol8le.
.ol1le
.o2l6m
.om1
.om4ar.
.o1ma
.om6bo.
.o2m1b6
.omb2o
.om4e6n
.o1me
.om4gå.
.o2m5g6
.om1gå
.om4me
.o2m1m4
.o6m5s2
.on4de.
.o6nd
.on1de
.on5de1s
.on8d3s
.on6kl4
.o2n1k
.on6na
.o4nn
.on8ne.
.on1ne
.op5ar
.o1pa
.op4pa
.o2pp
.op4pe
.opp7el
.op4pi
.opp3l6
.oppla8te.
.opp2lat
.oppla1te
.op2p5s6
.o1p7r6
.o2p1t6
.or4da
.o4rd
.or4de.
.or1de
.or4del
.or4dr4
.or8d5s4
.or5g4a
.o2r1g
.or6ka.
.or5ka
.o2r1k
.or4ke.
.or1ke
.or6me.
.o2r1m
.or1me
.or4re.
.o2r1r
.orr6e
.o6r1t6
.or6v4a
.o2r1v
.o1sa5
.os4k
.o4s4s
.o8s4te.
.os1te
.o1t5r4
.ot7t8a
.o6tt
.ot4ta.
.ot4te.
.ot1te
.ot4t4es
.ot3to
.ot4to.
.ove6
.ove2r3
.overe6n6d
.ove1re6
.ove1r3i4
.ove2r5k8
.ov8re
.o2vr8
.o1v5u
.p6
.p8a8la.
.pa1la
.pa4le.
.p2a3le
.pa5n6e
.pa6n7e1u
.pa5n6i
.pa6re.
.pa1re
.pe3do
.pe4ke.
.pe1ke
.pe6k5t
.pel4sj
.p6el
.pe8l1s
.pe4ne.
.pe1ne
.p2e2p
.pep3r6
.pe8sa.
.pe1s
.pe1sa
.pese5t
.p2e3se
.pe4st
.p1h2
.pi6le.
.pi1le
.pi6p
.pi9ra
.p2ir
.pi2s
.ple6n5g6
.pl6
.p1le
.plu4s6s5
.plus7s6e
.plø7se
.p1lø
.po4et
.p6o1e
.p4o3la
.po8la.
.po4le.
.p2o3le
.po4l2og
.p2o1lo
.po2p1
.po2p2e
.po2p3p
.po3p2u
.po8ra
.po6re.
.p8o1r4e
.po4re1s2
.po6st
.p2os
.po8ta.
.po1ta
.p2ot
.po6tek
.po3te
.ps2
.pøn3
.pø6n9s6
.p6å5d6
.r6
.ra6da.
.r6a3d2a
.ra6et
.r2a1e2
.ra4ga.
.r4a1ga
.ra4ge.
.ra1ge
.ra4ja.
.ra1j
.ra4ka
.raks7
.ra2m
.ra4na
.ra4ne.
.r4a1ne
.ran7s6a
.ra6ns
.ra6pa.
.ra1pa
.ra6pe.
.ra1pe
.ra2p4s1
.rap5s4e
.rap5s4o4
.ra4re.
.ra1re
.ra6sa.
.r4a1sa
.r4a7ti
.ra6va.
.ra1va
.ra4ve.
.r2a1ve
.re4al
.r2ea
.re6da.
.re1da
.re4de.
.re1de
.ree6l
.re3e2
.re5ge4l
.re3ge
.re4gi.
.re1gi
.rei2
.rei7de
.rei5er.
.re2i7e
.re4i6n1s
.re6ka.
.r4e1ka
.re4ke.
.re1ke
.re8klar
.re1kl4
.re8k1le
.re4k1li
.r4e6k1n2
.re8le.
.r2e1le
.re4ma.
.re1ma
.re4na.
.r2e3na
.re4ne.
.re1ne
.ren6sk
.re6ns
.re6n4t
.re2p5s
.r2e1p
.re4s9s
.re5s6tan
.rett7s8kri
.re6tt
.ret6ts
.rettskr6
.re4ve.
.re1v
.re4v4e5s
.re2v9n
.ri2d
.r2i8e
.rie5ne
.ri1e4n
.ri6ka.
.r2ik
.ri1k2a
.r4i6ma
.ri4me.
.ri1me
.ri4pa.
.ri1pa
.ri4pe.
.ri1pe
.ri4p5o
.ri4sa.
.ri1sa
.ri4se.
.ri1se
.ris5ko
.ri4s3ø
.ri4ta.
.ri1ta
.ri6te.
.ri3te
.ri6va.
.r4i1va
.ri4ve.
.ri1ve
.ro4a6n
.r2oa
.ro4de.
.r2o1d
.ro1d4e
.ro8di
.r6o4e
.ro8ke.
.ro1ke
.ro4ma.
.r2o1ma
.ro4mi
.r2o6pa
.ro9par
.ro3pe
.ro4pe.
.ro4sa
.r2os
.ro9sar
.ro4se.
.ro1se
.ro4sed
.ro4ta.
.r2ot
.ro1ta
.ro4te.
.ro1te
.ro4tek4
.ro8va
.ro3ve
.ro8ve.
.ru8ga.
.ru1ga
.ru4ge.
.ru1ge
.rug9l6e
.ru4gl
.ru8k2a
.ru4na.
.ru1na
.ru4ne.
.ru1ne
.ru2ne1s6
.ru6n7g
.ru4se.
.r4us
.r2u1se
.ru4sk
.ru8va.
.ru5va
.ru1v
.ru4ve.
.r4y2e
.ry7e7ne
.rye2n
.ry6ke.
.r6yk
.ry2ke
.rød2
.rø8d7s4
.rø8le.
.rø1le
.rø4re.
.rø1re
.rø1v
.rø8va.
.rø1va
.rø4ve.
.rø4y7e
.rå8da.
.r6å1d
.rå1da
.rå4de.
.rå1de
.rå4e2n
.r4å1e
.rå8er.
.rå3er
.r2å5g8
.rå6ka.
.rå1ka
.rå4ke.
.rå1ke
.rå3k4l4
.rå6na.
.rå1na
.rå8sa.
.rå1s2
.rå1sa
.s6
.sa4ge.
.sa1ge
.sag6n
.s8a4la.
.sa1la
.sa4le.
.s2a3le
.sa4me.
.s2am
.sa1me
.sa4mer
.sa6n6d7
.san7d8e
.san7d8i
.san8s7ku
.sa6n1s
.san7s6k
.s4a6u6e
.sch6
.s2e2b2
.se3e2
.se4i
.se4k5l4
.sek4s5o6g5
.sek1so
.se8l4v5
.s2el
.sel4v5i6
.se6na.
.s2e1na
.se4ne.
.s2e1ne
.s2e6n7s
.se4ra.
.s2e3ra
.ser7vel
.se2r1v
.se4te.
.s2e1te
.sha9ke.
.s1h
.sha2k
.sh2a
.sh2a1ke
.si2d
.si6er
.s2ie
.si6ga.
.s2ig
.si1ga
.si4ge.
.si1ge
.sik5k6e
.s2ik
.si2kk
.si8la.
.s2il
.si1la
.si4le.
.si1le
.si4ne.
.s4i1ne
.sin6n6s5
.si4nn
.si4ra.
.s2ir
.si1ra
.si5str4
.s2is
.s4i4va
.si4ve.
.si1ve
.sje4i
.sj2e
.sju5o6g5
.sk4
.ska7ka
.sk2a5ke
.skat4
.sk3ei5d
.s1ke
.s6kei
.ski6n
.sk8in6n1s6
.s2ki4nn
.sko7de.
.sk2o1d
.sko1d4e
.sko4g5u
.s2k2og
.skrit6t9s8
.skr6
.sk2rit
.skri6tt
.skud8d5s6
.s2k2ud
.sku6d1d
.skudds1å7
.sku6m5
.sky6f7la
.s4ky1f
.skyf2l2
.skå5ra
.slim5
.sl4
.s1li
.slot6ts5
.slo6tt4
.sl2ot
.slu9k2a
.slu5ke.
.slu1ke
.slø5se
.s1lø
.sma4s
.s1m
.s1ma
.smas5k
.sm2o9g
.s1mo
.s1må3
.små7k6
.sm6å9l
.små5t4
.s8må6t5t
.s1n4
.s1ne3
.snit6t5s6
.s2n4it
.s1ni
.sni6tt
.snø3k
.s1nø
.s2oa8
.so8d4e
.s2o1d
.som1ma4
.s2o2m5m4
.so1n6a
.s2on
.s4op4pa
.s1o2p
.so2pp
.s8o6r4t5
.so4ta.
.s2ot
.so1ta
.s8p6
.spa5ra
.s1pa
.s4pe6e6
.s1pe
.spi9la
.s3pri5s6
.spr6
.st6
.sta9ka
.s6ta5li
.st2a4t4s1
.ste8d4s
.s1te
.s2ted
.s2t4e4i
.ste6i1n7a8
.ste6i6n7s8
.s4te2m4m4
.s2tem
.stev9na
.ste2v1n
.s2te1v
.sti7me
.s1ti
.sto5ne
.s1t2on
.sto6ra
.strek5s6
.str4
.s4tu8a
.stu9va
.stu1v
.stå2l5l6
.s4un8d5s6
.su6nd
.su4ri
.su5te
.s1u4t1
.sv8
.sva5la
.sva5ra.
.sva1ra
.sva5re
.sv2a4r5s
.sv2e2r8n
.syd5
.sy5d6e
.sy6na.
.s2yn
.sy1na
.sy4ne.
.sy1ne
.sy5ter
.sy2t
.sy1te
.sy3ti
.sy6v5
.sy6v5o6g5
.s4z6
.sæ2
.sær1
.sæ2r3e4g
.s3æ4re
.s6ø8ka
.sø4ke.
.s6ø1ke
.sø8l6v5
.s4øl
.sø1r3a
.sø4y1e5
.søy6e4n
.så5pen
.så2p
.så1p2e
.så3re
.så2r
.t8
.ta8ke.
.t2a1ke
.tak9r6
.tak5s1k
.tak9s8p2
.t8a4la.
.ta4le.
.t2a1le
.ta4le7s6
.tal4li
.ta2l1l
.ta2m5m4
.tan5de
.ta6nd
.ta4p5r6
.ta4ra.
.t6a1ra
.ta4re.
.ta1re
.ta2s
.te4e4
.te6i
.tei9er
.t3e2i7e
.te8ke.
.te1ke
.te4ma.
.te1ma
.te6o6
.te8ne.
.te1ne
.te4se.
.t4es
.t2e1se
.te7si
.te4sta
.te4str4
.t1h2
.ti8a
.tid5r4
.ti8d6s3
.t2i4de.
.ti1de
.t2i4e2
.ti4g3r4
.t2ig
.ti5ki
.t2ik
.t4i4l5
.ti1li6
.ti4med
.ti1me
.ti4na.
.t6i1na
.ti4ne.
.t4i1ne
.ti4p5l6
.ti4s
.to5a2r5m
.t2oa
.to6en.
.t6o1e
.toe2n
.to4er.
.to6et.
.toe4t
.to2kk8
.t6ok
.to6le.
.t2o1le
.to6na.
.t2on
.to5na
.to5o6g5
.t2o3o
.to4ra.
.to1ra
.to4re.
.t8o1re
.to6r2e1b2
.t4o3ro
.tor2s1k6e5
.t2o4rs
.tor4s5v2
.to5r4y1e
.t4ory
.to4str4
.t2o1s
.t2o5t8
.tot8a9la.
.to1ta
.tota4l5a
.to8te.
.to5te
.to6va
.to4ve.
.tr6
.tr8a7c
.t2r2e3b2
.tr2e7p
.tre5o6g5
.tre3o6
.tre6skja
.t2resk
.tre1skj2
.tre5s8ko
.tre3s2p2
.tre5s6t
.ts2
.t1sj2
.tu4en.
.tu1e2n
.tu6na
.tu4ne.
.tu1ne
.tu6ra.
.t2ur
.tu1ra
.tu4re.
.tu1re
.tu8ve.
.tu1v
.t1ve6n
.t1v
.tver6r5a6
.t7ver
.tv2e2r1r
.tv2å7g
.ty6da.
.ty1da
.ty4de.
.ty1de
.ty8e2t
.t4y1e
.ty8re.
.ty4r8s9
.ty5ter
.ty2t
.ty1te
.ty5ti
.tæ4re.
.tæ1re
.tø6v9d
.tø6ve.
.tø9ver
.tø5v4et
.tå5ker
.tå2k
.tå1ke
.tå4le.
.tå1le
.t1å2p9n4
.tå5ren
.t3å7ret
.u5a6
.u1b2
.u6b2e2r1g
.u1be
.ub4er
.u3d2
.u7de
.u3e6
.u4er.
.u1er
.u6e1ra
.u4e1re
.u2f4f4
.uf8sa
.u2f1s
.u4ga
.u5gi
.u6g6la.
.u4gl
.ug1l2a
.ug8n
.u1i
.u1i6m
.u1k6
.u4ka.
.u1k2a
.u6ke
.u7k1n2
.u3le
.u4le.
.u2le8k
.ule6n4d
.u6len1de
.u8le1ne
.u6let
.u4lin
.u3l4i
.ul6ka
.u6l1k
.ul4ke.
.ul1k4e
.ul4la.
.u2l1l
.ul8le.
.ul1le
.ul8ma.
.u2l1m
.ul1ma
.ul4me.
.ul1me
.ul4ne.
.u2l1n
.ul3ne
.u2l5t
.ul6te
.u1lu
.ul4v5i
.u8lv
.u1ly
.u3lå
.u5ma
.u1mu7
.u2n
.unde6ri
.u6nd
.un1de
.und2er
.und5r4
.u1ne8
.u5nek
.u9net
.un4ge.
.u6ng
.un1ge
.u4n2n
.un6n3s
.u3no
.un4se.
.u6ns
.un1se
.u6n4t
.u5nu6
.u1o
.u1p
.u4pi
.up2l6
.u2ra
.u5raf
.ur6an
.u6r4a1ne
.u4r4d4
.ure4n
.u1re
.u4ret.
.u6re4ts1
.u2r2i
.ur4ke
.u2r1k
.ur6na
.u2rn
.ur4ne.
.ur1ne
.u1ro
.u4r1s
.ur8ta
.u6r1t
.ur4te.
.ur1te
.ur2te5m8
.ur6tet
.u5rut
.u3ry
.u5rå
.u1s2
.u4sa.
.u4sas
.u5se
.u3ska
.u5s1n
.u7s1p2
.us3se
.u4s1s
.us6t4
.u7stek
.u2s1te
.u5stel
.usy5r
.u1sy
.u2t
.u2ta4g
.u1t5a1ge
.u3tak
.u4ten.
.ut2en
.u1te
.u4t5e1sk
.u2t4e1s6
.ut6e7sko
.u4ti.
.u3ti
.ut3ka6n6t5
.u4t3k2
.ut4ne.
.u8t1n
.ut1ne
.u5tol
.u2t3o6v
.ut6rer
.ut1r4
.ut3re
.ut6ro1v
.u3t4rø
.ut6se.
.u4t1s4
.ut1se
.ut4si.
.ut1si
.u6t3t4
.u3tu
.v6
.va4da.
.v6a1da
.va4de.
.va1de
.vai4
.va4ke.
.v2a1ke
.va6le1s2
.v2a1le
.val8g5s1
.va2lg
.va4ne.
.v4a1ne
.v4a4n4n5
.van6n5s4
.va6re1ta
.va1re
.var4et
.var2i4e
.v2a1ri
.var5sk
.v2a4rs
.ve8en.
.ve3e2
.vee4n
.ve4ga.
.v2e1ga
.ve6ge.
.ve5ge
.ve4g3i
.ve8g3s4
.ve2i3g4
.v2ei3s
.vei4ta
.ve4l3
.ve5l4ar
.v2e1la
.ve6l5a6r1t
.ve6l5d
.ve4l3e
.ve2l5l
.ve5l4os
.v2e1lo
.ve4l5ov
.v4e4ly
.v2e8læ1
.ve4ne.
.ve1ne
.ve5net
.ve4ra.
.v2e1ra
.ve4ras
.ve6re.
.ve1re
.ver1mo9
.ve2r5m6
.ver1s4t
.ve4rs
.ver5s1te
.ve4sl4
.v4es
.ve4st
.v2e1te5
.v4et
.ve8te.
.ve4ve.
.ve1v
.vi4da.
.v2i1da
.vi5de4
.v2i6de.
.vi4d7å4
.v2i2e
.vi4ka.
.v2ik
.vi1k2a
.vil5l6a5l
.vi2l1l
.vil5l6a5t
.vi6ma.
.v4i1ma
.vi8me.
.vi1me
.vin8g7s1
.v6i6ng
.vi4n5n
.vi4sa.
.vi1sa
.vi4se.
.vi1se
.vi6s2e1ri
.vi4ta.
.vi1ta
.vi4te.
.vi1te
.vi5tr4
.vok6s5
.vok5s6e
.vo6r
.vo6ta.
.v2ot
.vo1ta
.vy4
.vy7e6ne
.v4y1e
.vye2n
.væ4ra
.væ4re.
.væ1re
.v6å4d
.vå4r3
.vå4r2s6
.wa4r
.w2a
.w2i6e
.w4i2
.xe2
.y6
.y1e4
.yn8da.
.y6nd
.yn1da
.yn4de.
.yn1de
.yn6ge.
.y6ng
.yn1ge
.yn8ka.
.y2nk
.yn1ka
.yn6ke.
.yn1ke
.yn6k5v2
.y6ns2
.yp8pa.
.yp7p6a
.y2pp
.yp4pe.
.yp3pe
.yr8ja.
.yr1j
.yr8je.
.yrj2e
.yr8ka
.y2r1k
.yr4ke.
.yr1ke
.yrke4s5
.y6r6t
.ys4
.yster2ie8
.ys2t
.y4s3ter
.ys1te
.yste1ri
.yt9ren
.ytr4
.y6t5t
.yt6te.
.yt1t4e
.y1v6
.z4
.zj8
.æ8
.æ2re4s5a
.æ1re
.ære4st
.æt4te.
.æ6tt
.æt1te
.ø6
.øg8l2a
.øg1
.øg4le.
.øg1l6e
.ø4i5
.ø2i6e
.øko5
.øk6ta
.ø6k1t
.øk4te.
.øk1te
.øl3ed
.ø1le
.ø8l3s6
.ø8l3v6
.øm4me.
.ø2m1m4
.øm1me
.ø4res8t
.ø1re
.øre1s2
.ør4j2e
.ør1j
.ø2r5k
.ør8na.
.ø2rn
.ør3na
.ør4ne.
.ør1n4e
.ør6ski
.ø4rs
.ør9s1m
.ør3s2t
.ør8ta
.ø6r1t
.ørt9an
.ør8te.
.ør1te
.øs2
.øs4t
.ø6v4d
.øve4r5
.øve4r6s
.øv4re.
.ø2v1r8
.øv1re
.øy6de.
.øy1de
.ø4y2e
.øyele8ge.
.øy4e1le
.øye3le1ge
.øy7e6ne
.øye4n
.øy4n
.øy6na
.øy6ra
.øy4re
.øy8rer
.øy4st3r4
.ø2y1s
.øys2t
.å2
.å6e
.å6f
.å6g
.å6k4
.å8l
.å2l6t
.ål8ut.
.ål1u
.å8m
.ån8da.
.å6nd
.ån1da
.ån4de.
.ån1de
.ån8d6s5
.åp6na
.å2p1n4
.års3k
.å4r2s
.å6se
.ås2
.ås3k
.å1s7l4
.ås3m
.å3s1te
.å1st
.ås5v2
.å4t
.åt4te.
.å6t1t
.åt1te
.åtte5o6g5
.åt2te3o6
.å6v
a1ad
4aaf
a3a2ft
aa4g
a1aks
a2ak5v2
aa3la
aa2m
a1a2n
a6an.
aan1s9t6
aa6ns
a7antr4
aa6n1t
a1ap
a2a4ri6
aarie9ne
aari1e4n
aar2ie
a2a2r5n
aa2s
3aa1se
aa5t6h
a1av
a6bab
a1b4a
a6b7av
ab9b8l2
a2b1b
ab4but
ab1b2u
abe4lei
a1be
abe1le
abe2l5t4
abe9na.
ab2e4na
abe1s9ka
ab4e1s2
4abe1v
a5bh
ab2ie6
a1b4i
abi9er
abi9la
a4bi1st
ab1l2
ab9la1ra
ab1lar
a1b4lok
ab6l7u
a5b2o9a
ab2o
abo3b
ab6o3e
a3bo2er
ab4o3kl4
a3bo4rd
5abor1te
ab2o6r1t
ab1r8
ab8re
ab2sl4
a6b1s
abu5e
a1b2u
a4bu1el
a4buf
a6busk
a4bu1te
a4by.
a1by
a4by1b
a4b6yk
aby3r
8ac
a1c4a5
a6ca.
ach2e3a
a2c6k3
a1co
6a1da
a5dal
ad5a6n1t
a4da6r1t
ad9da
a6d1d
a2d2e7b2
a1de
ad2e5i6s
a2dei
a2dek
a4del
a4d5e8lv
a5de6nd
a3de1ne
a2de3o6
a2d2e1p
a5der.
ad2er
ader1le7
ade2r1l
a2de1s
a5de4ser
a2d2e1se
a3desl4
a3d2et.
a3de4ts1
3adfer
a2d1f
ad1fe
1a2d1g4
ad8ge
adi4e1ne
a1di
ad2ie
adi1en
ad1j
1adju
2a4d1l4
1a2d1m
a8d5n
ad2o7a
a1do
a4do2b3
ad7o2pp
a2dop
4ador
a7d6o1ra
ad5raf
adr4
ad7ran
a2dre
a4d7rel
ad5r2e1p
ad3ret
ad3rid
ad1ro
a7drø
ad4s5a2m1t
a8ds
ad1s2am
ad4s1i
adsle6ge.
ads1l4
ads1le
ads3le1ge
ad5s4let
ads5te
adst4
ad5s6tek
ad3str4
ad5un
a1du
1a2d1v
adva5re
2adve
a4dy.
a1dy
a4d5øy
a1dø
2a1e2
4a2ea
a4ed
ae3de
ae1d7r4
ae6k5t
a4el.
a2e5la
a2e3li
a2e1l5o
ae6n5t
a3e1p
aes8ke.
ae2s1ke
aes4t
6a1fa
6afc
a4fe.
a1fe
afei5
af4fan
a2ff
af1fa
af4fei
af1fe
af2fe3s
af7fi.
af1fi
af2fi3d
af5f4u
a1f4i
afia1
afi5a4n
a2fi1b2
a6f5i4nn
afi7re
af2i2r
a4fi1t2i
a5fj2o
a1fj
af9la.
af2l2
af9lar
af3le
af5li
1afri
a4fr2ik
afr2
6a1fo
afo7ra.
afor1a
af8o7r8e
afore5ne
afor7e6n
afra5s
a1fra
af2s1l4
a2f1s
af2t5ei
a2ft
af1te
af4t5e4l
af6t5o2
af4t1s4
af5yr2
a1fy
af8ø4r
a1fø
afø5ri
4a1ga
a2gaa
a5g2a1e2
a4ga4n1f
agan8g7s8
aga6ng
a4ga6r1t
a5g6as.
aga6ve.
ag2a1ve
ag5de
a8g1d
a7g4elen
a1ge
age1le
a2gem
3a4ge6n1t
ag2en
ag2e4r3a
ag2er
a4ge6rek
age1re
age5risk
age4ris
age1ri
a7ge4rs
a5g2eru
ag6e5s2
a4ge1ta
a4gé
ag5gar
a4g1g
ag1ga
ag4gas
ag5ge
ag8g1s2
ag1g7u
ag6gut
a4gi.
a1gi
a2g5id
agi6s
ag1l2a
ag4lem
ag1l6e
ag6le1sa
ag5lå
ag3m6o8e
a2g1m
ag1mo
ag2n5om
ag1n
ag1no
4a1go
ag2o5d8
ag1or
a5g2os
a3g2ot
a4g5ov
ag7ras
a1gr4
ag1re
ag1ri
4a3gru
ag1rå
ag4s3a2m1b
a8gs1
agsa2
ag1s2am3
ags4a6ng
ag3s4ei
ag1se
ag4s2el
ag3sem
ag5s6i6ng
ag2si
ag6sju
ag1sj
ag4ska
ags4kul
ag2sl4
agsmå6la
ags1m
ags1må
agsm6ål
ags3tr4
ag2s3t2v
ag2sy
a6gu.
a1gu
agu3ay
agå8v4a
a1gå
agå1v
agå8ve.
a1h
a5hi
a2h4n5
ah4v4
ai1a4
a2i3e2
ai3er.
ai4is
ai1i
ai5ke.
a2ik
ai1ke
ai5ko
ai9k1v2
a4i5ne
a3i6ng
a1i4nn
a2i4n5o4
ai1ro
a2ir
ai1s4e
ai4s3k
ai2s4k2h
ai8s1m
ais6om
ai4s5s
ai5s4v2
ai5ve
ai5ø
a1j
aja9d
a7j2e
a8je.
ajes7
a4jé
a4ji
a6j1l
6ak.
a1ka
4a1ka.
1a2kad
6akaf
ak3aks
6akan
aka4o5
4akar
a1k6a2r1b8
a4ka4t5r4
ak4au
2a2k1d
2a1ke
a9k2ec
a2ke5h
a3kei5
a6kek
ake1l8e
akel5ei9er
akele2i7e
ak2e5l4i
a4ke4rek
ake1re
a4k2e1rø
ake5s1m
ake1s6p2
a8k2e3te
akhe6n
a4k1h
4a1ki
a6kid
a6k2ik
a6k7i6nd
akis1
2a1kj2
akk6a6nd
a2kk
ak1ka
ak5ke.
ak1ke
ak3ken
akk5er1s2t
akke4rs
ak4kes
ak1ki4
ak5kim
ak4k5is
ak6kj2
ak1ko
akk3ol
ak6k5ri
akkr6
ak1ku
ak4kul
ak4k5v2
2ak1l4
ak3le1v
ak1le
ak6l2ik
ak1li
ak6lus
6a2k1n2
ak5ne
a5k1no
2a1ko
ak5om.
a1kom
a5k6on
a7k2os
ak1o2v
ak4pe6
a6k1p
ak4r4a8sa
akr6
a4k5reg
ak1re1gi4
ak3res
ak3ro.
ak2ro
ak3r6o1e
ak1ru
ak4s5a6nd
ak1sa
ak4sek
ak1se
ak2s2e3l4o
aks2el
ak4s5e8lv
ak4ses
ak7s6id
ak1si
3aksj2e
ak1sj
ak2s1k
ak7sk8u
ak6s1l4
4ak1so
4ak8s9r
aks5ti
ak5stol
aks4tr4
ak6st7ren
4ak1su
ak4tab
a6k1t
ak4tai
ak4tak
akta6le.
ak3t2a1le
akt5a2l1l
ak6tam
ak6ta6ns
ak4tap
ak4tas
ak4tav
ak4teg
ak1te
ak4tek
ak5t8e1me
ak2tem8
4ak2t1h
7akt2ig
ak1ti
ak5tit
aktle6ge.
ak6t3l6
akt1le
akt3le1ge
ak2tr4
ak6t1re
akt3rå
akt5s4la
ak4ts
akts1l4
5aktue
akut2
2akv2
ak3val
ak5øl
a5kå
8a1la.
a2l7adr4
ala4g
al4a9ga
a5la2g1m
alag8ra
ala2g1r4
a5la1h
a1lai
al3a2l1l
a2lal
al3a4me
al3a1na
a3la6nd
a5lan3de.
alan1de
a5l6a3ne
alan5gr4
ala6ng
a4l3a4n1l
a4l3a6n1v
a2la4o
ala5pr6
a5l8ar.
a3la1ra
a5laren
ala1re
al3a2r1k
a7la2r1l
3a4l2a2r1m
a6l7a2r1r
a5l2a4rs
a4l3a6r1t
a7la2r1v
al3a4si
2alat
ala7tr4
4alau
al5auk
a2l1av
ala4va
al4ba.
a2l1b2
al1b4a
alb2o4g
alb2o
albu7er.
al1b2u
albu1er
5album
al3de
a6ld
al7d2er
4al1do
ald3re
aldr4
2a1le
a2le1f
a2leg
a9leg.
alei1e6n
ale2i7e
a9l8e2ik
a5le1k6e
a4leks
a4l2e1li
a2lem
a2l5e4mu
al8en.
a4l3e4n1h2
a4l5en5tr4
ale6n1t
ale4p2os
a2l2e1p
a4le1po
a7le2p1t
a4lered
al8e1re
alere6de.
alere1de
a4l2e5ro
a4l2e1ru
ale1s2
al2e7se
a4lesk
ale6s5kr6
a4lesl4
a6le1su
a4le1ta
a4l2e3te
a2le1u
ale5v
a4leva
a4levi
3alfab
a4l1f
al1fa
2alg.
a2lg
al3g2e1b2
al1ge
al2gu
al4går
al1gå
al3i2l1l
a1li
a2lim
a9l2in.
ali5na.
al6i1na
a4l3i6nd
a4l3i6n1s
a4l3i6n1t
al9ja.
a2lj
al3je.
alj2e
alj5e6n1d
al1jen
al3jer
al1j2o
al4jor
al2j1u
al1j2ø3
al4j5ø6v
al9k2e1ra
a6l1k
al1k4e
alk7s6
all4a4ga
a2l1l
alla6ge.
alla1ge
al4lap
al4l5a6r1t
al5l2e5a
al1le
alle6ge.
al3le1ge
al4lek
al5len.
all5er1s2t
alle4rs
all6e3s4
al4lest
al5let
3alli1a
al1li
al4lid
5all2i5e
all4i9ne
al6li6st.
al4lo3m1
all4sen
al8l1s
all1se
all4s1ti
al6lul
al6løs.
al1lø
al4lø1se
al8løst
al6lå
al6mek
a2l1m
al1me
al4met
4a2l1n
a5lo.
a2l1o4b
a5loi
al1om
a1l2on
al3o1pe
a2lop
a2l7o4rd
alo1ri5
al6o6rit
al3ove
a1lov
alow7
a2l1p2
al3ps
2a8l1s
al7sed
al1se
al9skap
al1ska
al7ska1re
al2sk2ar
al2s1l4
als5lø
al2s1n
al6s1pu
alsp2
als4te
als6ter
6al2su
alt6ak
a2l1t
alta8le.
al3tal
al3t2a1le
al3ted
al1te
al7te1ma
al2tem
5alter3na
alt2e2rn
alte4t
al4t5e1ta
al4t5e3te
al4t3op
a4l5u4k
alul8la
alu2l1l
al5u6nd
alu8re.
alu1r2e
al5va.
a8lv
al1va
alv5aks
alva6k
alvak8se.
alvak1se
al4ve2d1
al5v6er
al8v9e2r8m6
alv4e5s
al9v2es.
alvi8se
al1vi
al1vo
al8v3s2
6a1ly
a2l5y4te
alø5se
a1lø
al3øv
a1lå
al7å6t
a4ma1h
a1ma
ama5is
a2mak
a5m4a1ki
am3aks
a2mal
am5a6ld
a3man
a3mar.
a5ma1ra
a7m6as.
ama1so7
3a4ma1tø
am6at
am4bar
a2m1b
am1b4a
3ambas
am4bat
am5be
a2mei
a1me
am3e2i7e
a3m2eis
a2mek
a4m3eks
amen8de.
ame6nd
amen1de
ame6n4s3
amen6t7a2r1v
ame6n1t
ame4ram
am2e1ra
a4mere6t
ame1re
3a4m4e1ri
4a5merin
a2me5u4
amhu7
a2m1h
2a1mi
am6i7na
am4i7ne
ami2sk3
amis4ku
am4l2e1se
a4m3l
am1le
am6lest
am4mad
a2m1m4
am1ma
6amn.
a2m1n
am4ned
am1ne
a2mo
a3m6o5e
a2m1op
am1or
amo6ve
am4pap
a8m1p
am1pa
am4pa1re
amp5ei
am1pe
am8peria
am3pe1ri
am6per2ie
am2pe1s6
amp7i4nn
am2p3l6
am5p4let
amp1le
am4p4re
ampr6
am4pun
am1pu
am2på
amru4
a2m1r6
am5rå
a6m1s
am7s6k8u
ams4l4
amst6
am4s3tr4
am2s9u4t1
ams2v2
am4s5ve
am2sø
am3ti
a2m1t
am9t2i5da
am3tid
5am2t1m
am7t1v
am6ul
a1mu
am5yr
a1my
a2m5øy
a1mø
6ana.
a1na
anak8te.
a2nak
a8n1a6k1t
anak1te
ana3la
ana4l4f
3a2n6a1ly
8anan
a3n6a1ne
a5na6ng
ana6n1t8
4anar
a6n1a2r1b8
a2n5a6r1t
ana3to
6an7au
anaus7
an9av.
3anb4e1f
a4n1b2
an1be
4and.
a6nd
an4da5m6
an1da
and4a5ta5
an4d6ek
an1de
an5den
ander1le7
and2er
ande2r1l
an9det
6an1do
an4d2os
4an8ds
and4s2el
and1se
a6nd8se6nd
and7slet
ands1l4
ands1le
and5s6tre
andst4
andstr4
a2ned
a1ne
an5e4g1g
a2neg
a4n5e8lv
a2nem
2a3nen
2aner
an2e3ru
a5n2es.
a4nest
a2ne5sv2
an5e6ti1k2a
a4n2e3ti
anet2ik
a5neti5ke
an5e6ti2kk
an5e4tis
a2ne1v
3an5fal
a4n1f
an1fa
anfø5re.
an1fø
anf8ør
anfø1re
an1g4e
a6ng
an4ged4
an4g5e4n1h2
an3g2en
ang5er3me
an3g2er
ange2r1m
an8ges1te
an2g6es
an2ge1st
an4gi.
an1gi
5angiv
an2g5of
an1go
an5g2os
an4g9ra
an1gr4
an4g1re.
an6gres
an4g3ret
an4g3ry
ang4s1m
an8gs1
angs6tro
angstr4
ang5s8t9rå
ang1st5y
ang5sva
angsv2
ang6søy
ang2sø
ang4t5re
an2g1t
angtr4
ang5t6ve
ang2t1v
an4gun
an1gu
an4gå.
an1gå
ania7
a1ni
a6ni1b2
a4ni1sj
4aniv
an4ka1na
a2nk
an1ka
4an1ki
4an5kj2
an4ko2b3
an3ko
an4kop
an4k2os
an2k1r6
ankr6a8na.
ankra1na
ank3re
an4k3u6t
an1ku
8an2kv2
an4kø
an6k5å6
2anla
a4n1l
anla6nd6
anlø9pa
an1lø
an4n5a6ns
a4nn
ann4an
an1na
an6n8e1me
an1ne
an5n4en
an5ner
an5nid
an1ni
a4n4n1i4nn
an4no2m1
an1no
5ann2on
an4no1v
ann4sei
an6ns
ann1se
ann4sid
ann1si
ann6s7kå
ann6sl4
ann3st6
ann5sta
ann4s3u
an4ny
an2nø
a3no.
a1no
a9noa.
an2oa
ano6d2e7b2
an2o1d
ano1d4e
2an2og
a7n4o2r1m
a6n2ot
a2no4v
ano5va
an3o3ve
anri7ke
a2n1r2
anr2ik
ans5a2ft
a6ns
ansa7ka
4ansan
3an1sat
an4sek
an1se
an7s6e6n1t
anseri8e9ne
anser2i7e6
ans2e1ri
anseri1e4n
an4ses
3an3s2ik
an1si
ans5i6n5d
a6ns5i6n1s
an4ski
an3skj2
an2s4k3l4
an2s6kun
an5skø
an6s1lø
ansl4
an4s1n
ans5or
an4s3pi
ansp2
anspor4t7s6
ans1p6o
ansp2o6r1t
an1st6
6anstar
ans4te4
6ans6ti
2an1su
an9s6u6nd
5ans6un1da
1an1sv2
4an2s3ve
6an1sy
an4så
anta8la
a6n1t
an5t6a6nd
an4ta6ns
an3ted
an1te
ant5e1mi
an2tem
5ante4nn
an4tesl4
ant4es
an4ti7k1l4
an1ti
ant2ik
an4tim
an5toi
an4t5o4rd
ant5rab
antr4
an6t7rom
an3t5ryg
an4t5s6
an4tu4l
antus4t
2a1nu
an4us7a
a2n5ut
3an1ven
a6n1v
6anvin
an5vi
6a1ny
a4nya
a2n1æ2
anær8
2a1nø
a2nøk
an7å
a1o
a2o9a
a2og9
a2oi
ao6k6
aon8de.
a2on
ao6nd
aon1de
ao2p
ao4r
a5p2ea
a1pe
a7pé
a1pi
a2pia
a2p1id
a6p2ik
a6pi6n1s
a2p4io
ap2i6r5
api7r6e
api7se
ap1j
a1pla
apl6
a5p6las
ap3li
ap9lo
ap2ly
4apol
a1po
a4p2on
a4p2oo
apo3p
apo5s4ti
apo2st
ap2os
a2p2ot
3ap3par
a2pp
ap1pa
4ap9par.
6appa1re
app7esk
ap1pe
ap2pe1s
ap5p2las
app1l6
ap4p1le
ap5p2li
ap6pri
app1r6
ap3ra
apr6
ap5ren
ap5ret
ap3rin
a2p2s1
apsa4
ap3s2el
ap1se
apse4s
ap8s9l4
a3p1sy
ap4s5ø
8a1pu
a6p5ut
a1py
ap7ø
a3p8ø8l1s
a5på.
a1ra
ara9b4i
a2r5aks
ara6k5t
ar3a2l1t
a4r2a1mi
2aran
a4ranor
ara1no
a4ra6ns
a4ra6n1v
2arar
a4r5a4s1s
ara5te.
ar6a1te
a6r5aug
a4r7au4k
arau9ken
arau1ke
ar7a2v1h
a2rav
1a2r1b8
2ar1b4a
arba8ne.
arba1ne
6arbe1h
ar1be
4arben
6ar3b4et
2ar1b4i
4arbj
6arbl2
2arb2o
arbo8da
arb2o1d
2arbr8
2ar1b2u
2ar1by
2ar5b4ø
4ar1b4å
ar7d2e7b2
a4rd
ar1de
ar6d2e1li
ardfø5re
ar2d1f
ard1fø
ardf8ør
ard3re
ardr4
ard5sta
ar8ds
ardst4
a1re
1a2r2ea
4are1ar
a4r2e1b2
areba4r
are1b4a
a2red
a2re1f
a2rei
are3in
a2rek
a6r5e6k1t
a4r2e9la
a6r7elek
a2r2e1le
a4r6e1li
a6r7e2m1n
a2rem
a5remo.
are1mo
3a2r2e3na
a6r5e6ng
a4re5ni
aren5t4es
are6n1t
aren1te
a2re3o6
a2r2e1p
a6rerel
a1rer
are1re
a6r5er1fa
are2r1f
a6re2r1k
a4r2ero
a6r2e1rø
a5r2es.
are1s8ka
a2resk
ar4et
a7r4eta.
are1ta
a4re1v
ar7e6va
ar3e2v1n
arev6ne.
arev1ne
8a1ré
6a2r1f
ar7g6h
a2r1g
ar5g6i
6ar1gj
arg4l
ar7go
arg5stj
ar8gs1
2a1ri
aria7ne
ari1an
a3ri1b2
ari1b4a9
a2r5idr4
a4rim
a4r3i4nn
a2r3i6n1s
ar4ins9k
a2r3i6n1t
ari3se
ari2s2e4a
a4ri4sto
a2riv
ar4kau
a2r1k
ar3ke
ar4ke5s
ar5kh2a
ar4k1h
3arkit
ar3ki
3ark6iv
ar4k1le
arkl4
ar4k5løf
ark2lø
ar6k2o1d
ark6s1te
ar8ks
ark1st
ark4s1tr4
ark7veg
arkv2
ar4k3øy
arl4a4ga
a2r1l
ar4map
a2r1m
ar1ma
5arm1b4å
ar2m3b
ar4me3di
ar1me
arme7t
ar6m2e1tr4
ar4mi4n1f
ar1mi
armle6ne.
ar4m3l
arm1le
armle1ne
armå6la
arm6ål4
ar1må
2a2r2n
ar4nad
ar3na
ar4nal
arneva7la
ar2ne1v
ar1ne
ar3ni
ar3no
ar3nu4
ar3nå
a1ro.
aro8de.
ar2o1d
aro1d4e
a5rok
a1r2on
ar1op
ar2o6pa
a4r1o4r
a1r2os
ar7ost
a1r2ot
ar3ove
ar6ped
a2r1p
ar1pe
ar9po
arp5ret
arpr6
1arra
a2r1r
ar5re3e2
arr6e
ar7re5sk
arr2i8e7
ar3ri
arrå6da
arr6å1d
2a4rs
arsa6ka
ar1sa
ar3sak
ars5a6n1s
ar3s2el
ar1se
a4r6s5e4rs
ar4s1in
ar1si
ar2s5kam
ar1ska
ar5sk2ar
ars5ke1s
ar2s1ke
ars7kre
ar1skr6
ar4spr6
ar1sp2
ars6t4r4
ar1s2t
ars4vei
ar1sv2
ars1ve
ar1s7æ
arta4la
a6r1t
arta6le.
ar3t2a1le
ar3te
ar7te1le
8ar3te4t2s1
art4ha.
ar4t1h
arth2a
art4has
ar4ti1k2a
ar1ti
art2ik
6ar4tim
4ar8t1n
4arto
ar4top
6art2r4
art9ra
ar6trin
art6s5t4
ar4ts
art1s5ø
6artu
8arty
ar4ty1v
ar1ul
ar1un
ar5u6r
a1r4us
ar5u6t1b2
ar5u8t1n
ar2v4e3s
a2r1v
ar6v2e1te
arv4et
arvi8sa
arvi8se.
arvi1se
a1ry
ar7æ6
arø8ve.
ar1øy
a1rå
arå8de.
ar6å1d
arå1de
a2r7åp
a6r5åt
6as.
4a1sa
asab4
asak4
a4sa5lo
as6an
a4s7aug
as1be2
a8s9b4
a1sc
a2se.
a1se
a2s2e1a4
a2sed
a2seg
a2sek
as4el
ase5le
a2sem
a6senet
as2e1ne
a4se2nk
a7se2ol
a2se3o6
a2se5s
a6se1st
a4s2e1te
a2se3u
as2h
a2s5hu
a4si1b2
a1si
a2sip
a2s2ir
a7sis.
as2is
asis5t
a2siv
as4ja
a1sj
a6sje6tt
asj2e
a2s5jor
asj2o
a2s3k2ar
a1ska
ask6et
a2s1ke
as5k2e1ti
a1sk2i
as5kis
a5skj2
as5ko.
as5k6o3e
a4s5ko1pi
as3kor
as3k2ot
a1skr6
as7k8ra
as3kul
a5s6ku1la
a3s6ku1le
ask9u8t
a2s4kv2
ask5øy
as1l4
a5s4lag
asl4a8ga
as5le1v
as1le
as4lit
as1li
a1so
a4so.
a2s5om
a2s5ov
as4pan
asp2
as1pa
as3pe
as7pis
a1spi
a2s5pl6
as9sa.
a4s1s
as4sab
as4sal
ass5a6ld
ass5a2l1t
as4s2am
as5se.
as1se
as4sed
as4sek
asser2i7e6
ass2e1ri
as4se2r1v
as4ses
as6si4f3r2
as1si
ass2if
as5si6ng
as5s2i3s4
as4s2it
as2sj
as6s1k
assku6le.
as3sku1le
as2s3n
as5so.
ass2o9a
as7sos.
as1s2os
as6s1p2
as2s3t
as4stan
as4st6r4
ass5tru
as2s1v2
as4s2y1s
as1sy
as4søk
as1sø
as6s5å
as1ta
as6tab
a6stan1de
a3s4ta6nd
a4sta3tu
a1stat
a2s5te.
as1te
as3ted
as4teg
as4tek
as4t5e4n1h2
a1stj
a9s2tof
a5st6ok
as2t5ov
ast5ren
astr4
as4tro
as9tua
astu8ve.
astu1v
as2t5ø4v
a1stø
a1su
asu3n
as4u9sa
a1sus
as1va
asv2
a6s4ym
a1sy
asy6n7d
as2yn
a8s7ø4y1e
a1sø
as5å
4at.
4ata1
a5tae1ne
at8a1e2
a1taen
at6af
at4a8ka.
ata1ka
at8a8la.
a4ta6ng
at2a9rar
at6a1ra
a6t7a2r1v
atas4
a5t6as.
atat8
a2tau
at5a2v1h
a2tav
atch5
a2tc
a2t2ea
a1te
a2t2ec
at7e6d1d
a2ted
a2te3e4
a4tei
a2t5ei4d
at3e2ig
ate3in
a4t4e1ka
ate5k8e
ate2k5e7ta
a6t5ek1te
ate6k1t
ate7le
at3e2m1b
a2tem
4aten
a5tene.
ate1ne
a4te1ni
a4t2e1nu
a2te3o6
4ater
a5ter.
a6terat
at2e1ra
a8terek
ate1re
a4t4eril
ate1ri
a2t4es
a4t3e6tt
a2t6e5u
a4té
3atfer
a6t1f
at1fe
at4ha.
a2t1h
ath2a
at8has
4a1ti
atik6ka
at2ik
ati2kk
ati1li5
at4il
ati5n2ea
at4i1ne
ati4r2e1p
a2t2ir
ati1re
ati8sta
a2t3j2o
5atla6n1t
a6t3l6
atl9øy
at1lø
3at2m2os
a2t1m
at1mo
at4nel
a8t1n
at1ne
at2o5a
ato5gr4
at2og
at1oi
a2tom
a6t5op6p1r6
ato2pp
a1tor
a5to3se
at2o1s
a3to1v
a1tra
atr4
a6t5reg
at3ren
at3rer
a4tr2if
at3rin
at5r2ot
a9tru
at5røs
at5røy
2a4ts
at3ser
at1se
at7sj2e
at1sj
at7s6kat
at1ska
at7skj2
ats6kul
at2s3ø
4att.
a6tt
at6tat
at1ta
4at5te.
at1te
at5te6n5s
at3ten
attfø7re
at6t1f
att1fø
attf8ør
at4tid
at1ti
atti4s
att5i3se
at3tit
att1o
att7o6p
at2to6v
at1tr4
at4traf
at4t3re
at6trin
att3s6k
at6ts
att5s8l4
att3sp2
att3s1v2
at6t2y2s
at2t3ø2
a1tu
atu5e4
a2tut
a2t5v
atvi5er.
atv2ie
a3t3w
a1ty
atyr8ke.
aty2r1k
atyr1ke
a5t6y1v
a1tø
atø4r3s
at2ør
atø9se
atøs4
a4t5øy
a3tå.
a7tå1a
a1t4å1e
at5år
at5å4se
atå3s2
at7å2t8
4au.
8a8ua
au9ar.
4au5b
auba6ne.
au1b4a
auba1ne
au3c
au5da.
a2ud
au1da
au9d2et
au1de
au4di.
au1di
4a6ue
au7e2n
au7er
aue5re
au3est
au6e5ta
au1et
au5e6te.
au2e1te
au4gal
au1ga
au4gas
au4gel
au1ge
augele8ge.
auge1le
au1ge3le1ge
5aug2n
3au1gu
au5i
au5k2e1li
auke5l4
au1ke
au5ket
auk5la
aukl4
1a2uk7s6
au5kve
aukv2
au6las
au1la
au4lat
au2l5l
aul8la
au5lu
au9men
a2u1me
a4u6mo
aum5s6k
au6ms1
aum7s4t
a5u6nd
au5r2a1e2
au1ra
aure5s
au1re
au5ret
au5ri
au4r5s6
au1ru
auru4e
a4u1sa
aus9kj2
au6skr6
au1so
au4s5s
au6s8tas
aus8te6tt
au2s1te
au4stet
au2sti6s
aus5ti
aus6t7i3se
aus6t2on8
au5str4
au6s4tri
au1su
au4sun
au2s5ø4
aut6a
au3ta.
au7tar
au5te
1a2uto
au2to5v
au3t4re
aut1r4
a2u4ty
2a4ux1
a1va
a4v5ab
a5v2a1e2
ava7g
6aval
a4v3a8lv
a4v3a6nd
a2v3a6ng
a4v5a4n1l
3a4v4a6n5s
a9var.
av3a6r1t
avar6ta.
4avas
5av1b4i
a2v1b2
1a6v1d
3avdel
av1de
2a1ve
a2ve3d2
a5ve3de.
ave1de
a7v4eil
ave3in
a2vek
a4v2e1la
a4ve1le
a5veleg
ave6ns4
a1ven
a5v8er.
ave7ras
av2e1ra
ave4r5d
a6verei
ave1re
a2v4es
a2ve1v
1av1fa
a2v1f
1a2v1g2
avi4ar
a1via
4a5v2ig
a1v2ik
avi9ke
a1vin
a4v5i6n1t
2a1v2ir
5avi1sa
avi5sa.
3a6vi1se
av5isi6ng
avi1si
avis3t
a5vit
avlu9t
a6v1l2
avlø5se
av1lø
6a2v1n
av7na
av4ne1s
av1ne
a1vo
a4vok
avo3r
avo4v
a2v1r8
av4res
av1re
av5ri
av1sa
a8vs
av5seg
av1se
av1s2i
av3s2k2o7g
av1sk
3avs1ni
av1s2n
av1s2p2
avs2pe9g4
avs2pe
avs4te
av1s2t2
av5su
av1s2v2
1a2v1t
avta9ka
avta1
2a1vu
1avvi
a2v1v
av3ø4l
4a1vå
a6v7å6l
a1w2a
a1wat4
a5we2
awe9ne
a1w4i2
ay2a
ay7a4ne.
ay1an
aya1ne
ay5ar
ay9s8t
a2ys
a5y4t
a5zu
azz3o
a1ø
a7å6
1b4a
ba3a
ba1by5
ba4b2y1s
ba5c2l
b8ac
ba2d
ba4da.
b6a1da
ba7dan
b5a6d1d
ba8de.
ba1de
ba4d2e7b2
ba5d2en.
ba7d2e6ns
ba3di
ba8d1s
4baf
ba2k
b6a5kan7
ba1ka
b4a3kar
bak2a4rs9
ba3ken
b2a1ke
ba4k5e6nd
baken6de.
baken5de
ba3ker
bake3s
ba9ket.
bak9e6tt
b4a3ki
ba8ki.
bak6ke4rs
ba2kk
bak1ke
bak6ko
bak5kr6
4bakr6
bak4re
ba5kri
bak3ro
bak3s1m
bak5sp2
bak5s6ti
bak5s4tr4
ba6k7t6
b2ak1v2
ba3la
bal7ak
ba4le1s2
b2a1le
ba4li.
ba1li
ba2l3j
bal4lag
ba2l1l
bal4lan
bal4led
bal1le
bal4leg
bal4lei
bal4le1v
bal9l2ig
bal1li
bal6lov
bal8l5s6
bal6læ1
ba1lo
ba2l5t
ba1lu
ba1n4a
b6a4na.
b8a5nan
b4a5nar
ban9da
ba6nd
ba4nel
ba1ne
ba4nes
ban6kap
ba2nk
ban1ka
ban4kor
ban3ko
ban2k3u
bantu5
ba6n1t
ba3re
bare6t5t
bar4et
b2a3ri
bari6e7n
bar2ie
bar8k5s
ba2r1k
bar5skr6
b2a4rs
b6ar5t2r4
ba6r1t
ba5ru
ba5sen
ba1se
ba4s2e1ru
ba4set
ba3si
ba2s1k
bas6sak
ba4s1s
bas4san
bas1si4
bas5s2i3s5
bas4so
bas4s3t
bas4s6t6r4
ba2st
ba7s8u
b4a1ta1
2ba1tr4
bat6ti
ba6tt
bau9la
6bav
ba5z
2b1b
b3b4a
b6ba2k
b4b5a2r1b8
b6ba1se
b3be.
b1be
b4b2e1b2
b4be1da
b2b4e1f
b4be3g
b6be1h
b2be6i
bb3e2i7e
bb5e2ig
b2bek
b6b2e2l1o
bbe2l6t3
bbel1te4
b4bem
b9bene.
bbe3ne
b2be3o6
b3b4er
b4b5e2r1f
b4b4e1s6
b7be1ska
b6be1ta
b3b4et
b6beten
bb2e3te
b4b2e1ti
b6b2e1to
b6b2e1tr4
b2be1v
b8b1h
b3b4i
bb2i9e8
b4b5i4nn
b2b1l2
bb7len
bb1le
bb5o1p4
bb2o
bb5rek
bbr8
bbu9ra
b1b2u
bb5ut.
bb5u1te
b2by5
bb4y3e
bb6y1k
b4b4ø
b6b4å
2bc
2b1d
b2dek
b1de
b7den
b4d2e1p
1be
be2au
b2ea
be4bo.
b2e1b2
beb2o
bebo5er.
bebo2er
beb6o1e
bebo9k
be4da.
be1da
be9dar
be3d2er
be1de
bed2i9e8
be1di
be4dre
be1dr4
be2d5red
be8d2s1
bedy9ra
be1dy
be1då3
2be3e2
be5ed
be6e1f
b4e1f
befa5re
b4e1fa
be3far
be3g
b2e1ga9
be4ga.
be4gi.
be1gi
be4g1n
b4e5go
be4g5re.
be1g2r4
be4g5rene.
begre1ne
be4gå.
b4e1gå
be6ha.
be1h
beh2a
beha7g
behe4r4s7
4behu
behå4r
beh6å
be6i
be5ke
be2k3i
bek6kel
b2e2kk
bek1ke
bekke5r
bek4kes
be1k6l4
4beks
be4l3a1b
b2e1la
bel4a9ga
bel5e2i7e
be1le
bel5e2ig
be2l5ein
be4lek
bel6i9na
b2e1li
beli9v
belle5sa
bel3l6e4s
be2l1l
bel1le
bel5let
bel5læ1
b2e2l1o
bel5s4p2
be8l1s
bel7s4t
bel4tag
be2l1t
bel4t4e5s
bel1te
bel4t3ø
bel3u
be8l5v
belæ5re
b2e1læ1
belå7ne
b2e1lå
belå2n
b2e4na
be5nat
be3ne
4bened
be4nest
ben5gu
be6ng
be5ni
ben5s4i
be6ns
ben5skj2
ben5sp2
ben5te
be6n1t
2b2e1p
b4er
4berai
b2e1ra
be7ras
be4r5d
be4r2e1p
be1re
8berest
ber2e5te
bere4t
ber4ga
b2e2r1g
ber4g5en1de
ber1ge
berg2en
berge6nd
ber5g6es
ber1g3j
berg3l
ber1g3o
ber4g3å
be5ri1b2
be1ri
beri5ke
ber2ik
be7ris
ber6kl4
be2r1k
ber5na
b2e2rn
ber5ne
b2e1ro
be4ro.
be3r2o9a
ber3ri6
b2e2r1r
ber5te
be6r1t
ber5ti
b2e1ru
ber4u9sa
be1r4us
berø5v
b2e1rø
b4e1s2
5b2es.
be4se.
b2e1se
be5s4i
4be3s2ik
6be1sj
be2s5ke
be2s6k5n2
be3s1n
bes7ne
4b4e3so
be2so9v
be5s2p2
be4s3s
bes6s7a6
bes6s4el
bes1se
bes7si
bes7te6ns
b6esten
bes1te
be2s6t4e3s4
be5s5t2es.
be6så
3b4et
5bet.
be4tab
be1ta
b2e3te
4betei
be7t4es
beto5ne
b2e1to
be1t2on
be6tra5r4
b2e1tr4
be6t1re.
b8etre
be4t3ri
4b2e1tu
be4ty.
b2e1ty
beva5re
be1v
be6ve.
bevi5se.
bevi1se
be9vo
be2v9r8
6be1å6
beån9
7bé
2b5f
6bg
2bh
bhu1
1b4i
bi5ak
bi6bla
bi1b2
bibl2
bi5ce
bi1d
bi5de3e2
bi1de
bi8dé
bid6r4
bi1du8
bi5el
b2ie
bi6e1le
bi5e2r5v
4bi1fa
b2if
b2i5g
bi3k2a
b2ik
bi2k1k
bik6ki
bi5k4l4
4bi7k1r6
bi6la.
bi1la
bi4las
bi4lau
bi3let
bi1le
bi2l2e1t5r4
bi4lin
bi1li
bi2l3j
bil5la
bi2l1l
bill4a8ga
bil5leg
bil1le
bi2l5m6
bil1o
6bi5m
bi4na6ns
b6i1na
bin2an
bin1go5
bi6ng
bin4gol
b2i7no
4b5i6n1t
b4io7
bi3o8m3s4
4bip
bi4ri5
b2ir
bi7ris.
bir4ken
bi2r1k
bir1ke
bi1ro
bi1s2a
bi3se
b5is1h
bis6hi
bi8s7ke
bi5s4la
bis1l4
8bis1n
bi4s1p2
bi1s4p5i
bis5se
bi4s1s
bi1st
bi5s6ta
bis4t4il
bis1ti
bis5tru
bi2str4
bi3s4v2
bi6ta.
bi1ta
bi4te.
bi1te
bi5t4e1s
bi8ti.
bi1t2i
bi4tre
bi1tr4
bi6tri
bjar3
bj4ek4t5o
bj2e
bje6k1t
bjø6r
bj2ø
2b1k4
b5k1h
bl2
1b2lad
bl6a1d3a
b2la6f3
bl4a8ga
bla2k5r6
bla5me
blan9da
bla6nd
b4la2nk
blan5ke
b4la6n1t
b1lar
b8l2arar
bla1ra
b3lat
bla4u
b4le1f
b1le
ble2i7e5
blei5er
b4lek
b2l4e4k3a
ble5kes
ble1k6e
b2le4mo
b3ler
bl2e7r6a
b6le1sa
bles5s2e9ne
ble4s3s6
bles1se
b4lest
2blet
bli9ke
b1li
bl2ik
bl4i5ma
bl6i5me
bling2s6i
blin8g2s1
bli6ng
bli4s3s
b5lj
blja4
blo6dr4
bl2o1d
1blok
bl2o2m5m4
b6lu
blues3
bl4u9sa5
bly7g6l
blæ5re.
b1læ1
blæ1re
blå5n
b2lå3r
blå7sa
blås2
blå5se.
blå1se
2b1m
6bn
b3ne.
b1ne
b3ner
b7n2es.
b7ni
b2o
boa5s4
b2oa
bobba6ne.
bo2b1b
bob3b4a
bobba1ne
bob5by.
bob2by5
bob9b4y3e
bo2b2l2
bob7la
bo6b1s4
bo3d4e
b2o1d
bo6din
bo1di
bo6d7r4
bo2dø
4boe6f
b6o1e
1boe2n
bo2er
bo4et.
2bo3f6
bo4gel
b2og
bo1ge
bog1n7
bo8g1s1
2b2o1h6
2b4o1j
3bok.
bo2ka
bo6kel
bo1ke
bo4k3et
bok1i
bo2k1k
bo6kop
bo3ko
bo6k5ri
bo1kr6
5bok1s
bok3s2i8da
bok5s4i
boks4p2
bo2ku
bok3ve
bokv2
b4ol
bol5e2i7e
b2o1le
b2o7li
boli7n
bo7lo.
b2o1lo
bo5lo3i
bo4l5o4r
bol5s4p2
bo8l1s
bols8t6
1bo2m1b6
bom4bel
bom1be
b2o2m5m4
b2o6mo
bo6m1s4
bo5nap
b2on
bo1na
bon5ato
4bo4nn
bon5ne
bo6ns4
bo5nus
bo1nu
b2oo6
boom1
bo1p4
bo1ra
bo4ra.
bo6re.
b8ore
bo7r1el
bo3ren
bo3ret
bo4r2e1te
bo6ri.
bo1ri
b4o3ro
bor6t7e1f
b2o6r1t
bor1te
bor6tei
bor4t4es
bor6t7et
bor4ti
bor4t5s6
b4o1s
bose8te.
bo1se
bos2e1te
3bo1ska
bo2s2l4
bos5se
bo4s1s
bo4st1o
1b2ot
bo4ta.
bo1ta
bo4tak
bo4tal
bo6te.
bo1te
2bo7to
4bo1t6r4
4bo2t4v
bou3c
4bo1v
bow4e2
bow1
bo6y
4bp
b7pl6
br8
1b4ra
bra5ka
b2rak5s
bra5se
bred5s1p2
bre8ds
b2re2i7e
br2ei3s
bret7te
bre6tt
b6re1v
brevi9er.
brev2i4e
bre4vin
bri5a
b4r4io
b2ro
bro9ar
br2oa
br2o1s4
3b4ru
bru5ke
b4ruk
br2uk4s3
bru4na
bru7na.
bru5ne1s2
bru1ne
bru5pl6
bru7ren
bru2r
bru1re
br4u5sa
br4us
brus4l4
1bry
bry6n4s1
b4rø
brø5de
br4å1e6
brå9ne
brå5te.
brå1te
6b1s
b1s4e
b5s6e6a5m4
b2s2e1a
b4s5el
b5s6i
bsk4
b4s1li
bsl4
b2s1m
b2s1of
bs2t6
b6s6t7f
b7s6v2
bså5
6b5t
b7t6s
1b2u
bu4ar
4bub
bu3da
b2ud
bud5d6h
bu6d1d
bu3de
4bu3em
bu4en.
bu1e2n
bu9e1ne
bu4er.
bu1er
bue5s
bu1i
bu6is
6bu7ki
buk3l4
bu5la
bu4le.
bu1le
bul3le
bu2l1l
bun7de
bu6nd
b4un8d4s3
bun8ge.
bu6ng
bun1ge
bun4k3r6
b4u2nk
bunnl4a8ga
bu4nn
bun4n1l
bun6n3s
2bu3o
bu1p
bu6ra.
bu1ra
bu4re.
bu1re
4bu3ro
bu3ru
b2u1s6e
bu4se.
bu6s4h5e
bu2s1h
busk7ø
bus6sek
bu4s1s
bus1se
bus4s4el
bus7s6e6n1t
bus6set
busse6te.
buss2e1te
bus6sj
bus4s1n
bus4sp2
bus4s2t
bus2s3v2
bu1st
4bustr4
4bu1sy
bu1ta
bu4tal
bu7tem
bu1te
b5u2t3g2
4b5ut1st4
b1u4t1s4
bu2tu
bu1t1ø
4bu1v
6bu1ø6
2b1v
4b5w
1by
bya2
by5a2l
by9a1re
by7d2e1p
by1de
by4en.
b4y1e
bye2n
by7e6ne
by5e6ns4
by2er
by3e4rs
by4ge.
by1ge
byg3l
3by1i
by1lo
4by1lø
by5n
by1re
by6re.
b2y1s
5bys.
4by1s4e
6by1s6i
6bysp2
bys2t4
by4s1te
4bysv2
by5tar
by1ta
by4te.
by1te
by1tr4
2by1v
bæ5rar
bæ6r4ar.
bæ4re.
bæ1re
bæ5ren
bæ5rer.
bæ1rer
bæ5re1re
bæ5r2es.
bæ5ret.
bæ3ri
bæ4r5i6s
bæ3r1u
1b4ø
b4ø4e2
bø7e6ns
bøf3
bø4ke1s
bø1ke
bø6la.
bø1la
bø6le.
bø1le
bø2n
bø1ne5
bøn6nes
b4ø4nn
bøn1ne
bø1n7o
bø6re.
bø1re
bør4s5k
bø4rs
bør4sp2
bør2s3t
bør2s8ta.
bør2s3v2
bø5ta
b4øt
bø1v
bøy7ar
bø4y1e5
bøy7e6ne
bøye4n
bøy4es2
bøy4e2t
1b4å
bå4de
b6å1d
bå9de1s
b4å6e
bå7e1ne
båe2n
bå4la
bå1re
bå6reg
bå4r2e1p
bå4s3te
bås2
b4å1st
bå6s7ti
bå6t5j
bå6to
båt5r4
1c4a
cab4
c2a4e2
c2a5le
ca5me
ca5mo
ca4pe.
ca1pe
cap1r6
ca6pris
ca3ra
c2a2r5n
ca5ro
car4te.
car3te
ca6r1t
c4a2sa3
ca6set
ca1se
cas2h5
ca5s4t4il
cas1ti
cas5to
ca1t
ca2t1h5
ca4to.
6cb
4cc
c1ci
c2d
c1de6
ceb2o9
c2e1b2
ce3d
ce1i
1cel
ce5le
cel4l6e4s
ce2l1l
cel1le
cel4le1v
3cen.
ce4ned
ce1ne
ce4ne3e2
c4e4nem
ce4ne5s4
ce5n2es.
ce4net
c4e4ne1v
5ce6ns
cen4ti9m
cen3ti
ce6n1t
cen6to
ce5o6
1cer
cerba6ne.
c4e2r1b8
cer1b4a
cerba1ne
ce3re
c2e1ro5
ce6r3t
6c2eru
cest3o
2ch.
ch2a5le
ch2a
3cham4
cha7ne
cha5t
2c2h1b
4che2i
che8l5s2
ch2e7te
chet2
chi4li
4c2h3h
2c2h1m
4c2hn
ch5ne
chom4
ch4o
ch2o5s6
2ch1p
8c6h1s
6c4ht
ch1v4
ci1c
3cid
c2i2e
ci1e8n
ci2e2s5
ci2l5l
c4i3ne
ci5ta
2c6k
c1k5ar.
c1k5a6r1t
ck1en
c1ke
ck3er.
ck4e1re
ck5e4t3
ck5et.
cke8y5
ck1i
c2k1k
ck1o2
ck7r6
ck5s2
cku6
c1kup3
ck9ut
c2l
cla2i4r5
cly4
c1m
cmi1ni4
c1mi
c2oa6
coat5
co6bi1d
c2o3b4i
1c4o4c
2c2o1d
6cof
6c2og
co4la.
c4o1la
co4la1b
co4lak
co4la5r
co2m
c2o5ma
co2m5t2
co6n5os
c2on
co1no
co6n5s
co6n7t
c2o2o
c2o7pa
2cor
co3r6a
c8o1re
co4so
c2os
4cost
co4ve2r
c1pr6
cr8
cras4h
cra2c6k4
cr8ac
c2re3e2
cree4n7
cr2os2
5cru
4c4s
4c1t
c6ta
cty5
1cu
4cu.
cu5la
cu5le
cu5lu
cup1l6
cu6po
c4up1r6
c6u2p7s
cu4på
2cur
cures4
cu1re
cu6t
cy2a
cy6p7
c2ys3
c6z
cæ5
1da
4daa
4dab4er
da1be
8dab2o
d5a6b1s
6d5a4del
da1de
d4a2d1g4
da1dø4
dad2ør5
4dae1f
d2a1e2
2daf
5da2g1b
da4g2e1v
da1ge
4da1gj
dag7l
da6go.
d4a1go
da4g1r4
da6gun
da1gu
4da1gå
4dahe
da1h
5da2hl
da1i
2da1ka
4d2a1ko
4dakr6
dak4se.
dak1se
4d7akt2ig
da6k1t
dak1ti
d2a5kv2
7d6a2l5j
da1la
d8a8la.
da4las
6d4alau
dalbu8er
da2l1b2
dal1b2u
4d5a6ld
da4le1s2
d2a1le
dal6so
d2a8l1s
da1lu
da4ma.
da1ma
da4man
da3mas
da4me.
da1me
da3men
6d5a4m4e1ri
dame3s
dame5t6
da3mo
dam7pe
da8m1p
5dan.
6danal
da1na
d8a1ne
9da1ni
2d1a4n1l
3da4nn
dan5n2e1b2
dan1ne
4dan1no
7da1no
d4ans.
da6ns
dan3sa
dan4s2el
dan1se
dan9s8kan
dan1ska
4d1an1sv2
9dant.
da6n1t
9dan1ti
8d6a1ny
2da3o
2dap
da3pe
d3a2pp
3dar.
5da1ra
2d1a2r1b8
dar8d3s4
da4rd
4d1a2r2ea
da1re
da4res
d5a2r2n
da2ro
dar5os.
da1r2os
d2a4r5s6
dar4ta.
da6r1t
dar6va.
da2r1v
darv4a
dar8ve.
4dasei
da1se
2das2h
6da1si
4das2je1f
da1sj
dasj2e
da4sk
da9sko
6dastr4
6da1su
d4ata5
4da4tal
data6le.
da3t2a1le
datal6i8na
data1li
da4tek
da1te
7d6ato
da3t2o5a
4da5tr4
dat1s6j
d2a4ts
d6a2ud
dau8d7s8
dau5go1
2daut
2dav
d5a2v1b2
d1a6v1l2
d3a8v5s
4da1ø
2d1b2
dba8le.
d1b4a
db2a1le
dba4ne.
dba1ne
db4e8ta.
d1be
d3b4et
dbe1ta
dbe6te.
db2e3te
dbli6ng7
dbl2
db1li
dbo6en.
db2o
d1boe2n
db6o1e
d6by1f
d1by
4d1c
6d1d
d2dad
d1da
ddag4
d4dak
d4d5a2r1m
dd3e2i7e
d1de
d2dei
d2dek
dde4lap
dd2e1la
dd2e4l5o
ddel5s1v2
dd4e8l1s
d4de1mi
dde4r5s
dd2er
d4de1su
dde1s
dd2ie8
d1di
d7dom
d1do
d4d1re
ddr4
dd5run
d2dru
ddsa4
d8ds
dd4sa1la
dd1sal
dd4skap
dd1ska
dds5tab
ddst4
dd3s4te
dds5tr4
d2dy1b
d1dy
d1dø2
dd2ør3
d4d5øy
dd1år
1de
2d2ea
de1a2k3
de2al
d2e7b2
6debar
de1b4a
4debas
3debat
2de1be
4debl2
2deb2o
5debon.
deb2on
2debr8
3debut
de1b2u
2de1by
4de1b4ø
d2e1c
5ded.
2de1da
6d7e6d1d
7de3de.
1de1de
4dedek
4dedel
4de5d6ia
de1di
2de1d4r4
2de1dy
2de3e2
5dee.
de7er
2de1f
d5e2ff
d5e8ft
2deg
6d6e5ge
5de4g1g
de3gl
2de1h
5deha8vs
deh2a
2dei
dei4d
dei4e1ne
de2i7e
dei1e2n
3d8eig.
de2ig
dei8ge.
dei1g6e
5dei3g2en
dei4g5r4
5de2ik
d2e1in
3d2e2ir
de5is.
d2eis
de3ist
7de7it
2de1j
8d4e1ka
2de1ke
2de1ki
2d4e1kj2
5d2e2kk
dek1k3a
dek4kan
dek4kel
dek1ke
de6k6k1v2
de1k2l4
6dekly
5dek1ni
d4e2k1n2
de5k2o
4dek2o1d
4dek6on
4de1k2os
2d2e5k6r6
dek6st
dek3s7ti
deks6tr4
5de6k1t
2de1ku
4de1k2v2
4de1kø
4de1kå
d4el.
6delad
d2e1la
6dela1ge
de6la6ns
4delau
4d4eled
de1le
de4le1f
4delei
del5ei4d
del5e2i7e
4de4lek
de5le1le
de2lel
4d5e2lem
de4lest
6d3e4le1v
4delid
d2e1li
6deli4n1j
de4l5i6n1t
4delis
4deliv
del4lap
de2l1l
del6lei
del1le
del4lek
del6le2r1f
del5ler
del9l2ig
del1li
d4e8l1s
del4s5at
del3se
del3se6s7
del4si
del4s1p6o
delsp2
d4e2l1t
del6tala
del3tal
del3te
del7t2r4
6de3luk
del5ve
de8lv
4d4e1ly
4d2e1læ1
2d2e1lø
delø6pa.
delø1pa
6deløy
2d2e1lå
2de5ma
d3e2m1b
2d8e1me
dem2ie4
de1mi
4demj
dem8na.
de2m1n
dem1na
de4mo.
de1mo
de4m2og
5demok
4demol
demo5no
dem2on
5dem2os
dem5pe
de8m1p
3de2m1r6
5de6ms
4de1mu
2d6e1mø
2de1må
d2en.
4d2e1na
de7n1ak
6d5en6d2en.
de6nd
den1de
de5n2e1b2
de1ne
4dened
6denel
4d4enem
4denet
4d4ene1v
6d5en3gen.
de6ng
den3g2en
den1ge
4de1ni
4d2e1no
de4nom
d2e6ns
den4sin
den3si
den4s3t8
de6n6t5a6n1t
de6n1t
4dent2if
den3ti
4den2tit
dent3o
den4tr4
den4t5s8
den6t5u6
den6tå
4d2e1nu
2d4e7næ1
2de1nø
de2ob
de3o6
de4og1
2de2ol
4deo2pp
deo2p
4de3or
de4ove
4depak
d2e1p
de1pa
4depap
2de1pe
4depi
4dep2l6
6de1pu
d2er
der8am
d2e1ra
de6ra6n1t
de4ra1re
de4ra2r1k
5de7r6ast
der6a5te
de2r3av
de4r5d
4der2ea
de1re
4dered
de4r5e6d1d
de4re1f
4dereg
4der2e1p
6dere1si
der5est
8dere6st.
6deres1te
4d6ere6tt
dere4t
de4r2if
de1ri
d4e4ril
5derin
de4ri1næ1
de2r5k
der5ne
d2e2rn
de5rob
d2ero
4de1r2og
4der2os
de5ro1se
dero8se.
6de7r2ot
der1o4v
der5s6n
de4rs
d6er4sp2
de6r3t
der5un
d2eru
de5rup
6de1r4us
de2r3v
4de1rør
d2e1rø
2d2e1rå
de1s
3d6es.
2de1sa
4desc
2d2e1se
de5seg
des5e4rs
4de1sh4o
des1h
de8s9hop
desi4s5t
de1si
des2is
2de1sj
2desk
9de6sk.
5de2s1ke
4des4le
desl4
2d4e1so
2de1s2p2
7de2s3pl6
6de5s6pr6
d4es2t
5de6st.
5de6s5te.
des1te
des6tem
de5s1ti
4de1stj
4desto
4de3strå
destr4
4de1stu
6de1sty
d8e4s3tå
6de3s2up
de1su
des6v2
2d2e3sy
deså7
d2et.
2de1ta
det4a8ka.
de3tak
deta1ka
d2e3te
4det2ea
6deteg
6de5t2e2k1k
4detel
4deten
4de2t2e1p
det4es7
2d2e1ti
2d2e1tj
4d2e1to
4d2e1t6r4
2d2e1tu
4d2e1ty
4de1tø
6de1tå
2de1u
d1e2ur
5de2us
2de1v
deva8ne.
dev4a1ne
devi9er.
dev2i4e
3dev2ik
d7e2v8n
de5vu
2dey1
4de1ø4
2de1å6
2d1f
dfø6r2arar
d1fø
df8ør
dfø1r6a
dfø5rar
dføra1ra
2d1g4
d4gel
d1ge
d5gi
dgi6n
dgjø6re7n6h2
d1gj
dgj2ø
dgjø1re
d3go
2d1h
dha8v4s
dh2a
dhei6m2s5
dhe2i
d3h4eim
dhu9ga
dhø4r
1di
di6ak
di5a2l3g
dia5li
di1ar
di2a3re
dia1s
dia7sp2
di1as6t
di2a4ts4
dia1t
2di1av
2di1b2
d2id5ri
didr4
di7e2l1l
d2ie
di5en.
di1en
4di5e6nd
di1er
di2e1s4
3di2ff
d2if
dif1fe5
3diful
di1fu
4di3fø
di8g9a6nd
d2ig
di1ga
di4g2at
di3g6e4s5
di1ge
di4g7g
2di1gj
di6g8la.
dig1l
digl2a
dig6le.
dig1l6e
4digren
dig2re
di1gr4
dig5ret
4digru
di2gu
4di3h
4di1i
2di1j
di5k2a
d2ik
di8ka.
di5kem
di1ke
2di1kj2
6di2k1n2
2di1ko
2di1k1r6
dik7v2
2di1li
dil4l6es
di2l1l
dil1le
d4i5ma
6dimed
di1me
6d5i6m2el
4dimes
4dim8et
2di1mo
4d5i8m1p2
4di1må
d6i5na
2d1i6nd
di7nen
d4i1ne
4d5in2g3k2
di6ng
din3gr4
ding8s5en
din8g2s1
ding1se
ding6s5er
din8g9å
di1ni4
di4ni.
4d1i4nn
2d2i1no
2d1i6n1v
4d6i1nø
3di2og
d4io
7diol4
dio3na
di2on
dio4no
di3o1ri
dio5tr4
di2ot
2dip
3di1pl6
4dired
d2ir
di1re
4direg
4d5i4rs
5dis.
di6sc
di6sed
di1se
di1s4i
disi6e5ne
disi1e4n
dis2ie
d6isk
di2s6kam
di1ska
di4skj2
di2s6k7l4
di4sk6o
dis5ko.
dis7k6o3e
dis7ku
di4s5s
dis1t
dis7t2ik
dis1ti
di5s1v2
dis4vi
2di1sy
di4tal
di1ta
di7te
4ditek
dit4tet
di6tt
dit1te
dit4t3r4
di9us.
di1u
di4va.
d4i1va
4di5val
di7van
4di2vek
di1ve
di1v6i
2di1vu
2di1ø4k
di1ø
d1ja
d1je.
dj2e
d1jen
djer5ve
dje2r1v
d7j2es.
d4je9ve
dje1v
d6jingan
dji6ng
djin1ga
d6jingar
d1j2o
dju8la.
dju2l
dju1la
5d8jup
6djupar
dju1pa
d4jø.
dj2ø
2d5k2
dka8ra.
d1ka1ra
dki6
d5kj2
dko2r
dku4le.
dku1le
4d1l4
d3la
dl4a4ga
dlan6d7as
dla6nd
dlan1da
dla6te.
dla1te
d2la4v
d4le1di
d1le
d2le1f
d4l2e1ga
d7legar
d3le3ge
dlei7er.
dle2i7e
d5l4e1ka
d2lel
dlem4st8
dle6ms
d4lenet
dle1ne
d4l2e1ru
dleva8ne.
dle1v
dlev4a1ne
d5li
d3l2i4f
dli5ke
dl2ik
d5lo
dly8se.
dl2ys
dly1s4e
dlø8pa.
d1lø
dlø1pa
d5lå
dlå9re
d2lår
2d1m
dme6la.
d1me
dm2el
d4m2e1la
d3m6o4e
d1mo
dm2o4rs6
dm2o8s
dmø4re.
d1mø
dmø1re
dmå6la.
d1må
dm6ål
dmå1la
8d1n
d1n6a
dno8de.
d1no
dn2o1d
dno1d4e
1do
2do1a4v
d2oa
do2b3
4d2o3b4a
do3b4e
8do9b8lan
do1b2l2
2do3b4å
do4da
d2o1d
do1dø4
dod2ør5
4doe1u
d6o1e
2dof
d3o2ff
d5ofr2
dog6med
d2og
do2g1m
dog1me
dog4me5s
2do1gr4
6do5i
do3ki
4dok4i5ne
dok6kan
do2kk
dok1ka
4d2o2k1n2
d2o5le
4do2led
doli8ne.
d2o1li
dol4i5ne
4doli4n1j
6dolis
4d3o6lj
do2l5l4
4domes
d2o1me
do5mis
do1mi
4do4m3l
d2o4mo
do8m7p2
2d1o2m1r6
dom8sa
do6ms
dom8s5e6
dom6s5i
dom4s3k
doms3l4
domsl4a6ga
dom4so
do4m1u
do4na.
d2on
do1na
do6n5g
4don1ke
do2n1k
6d7on6kl4
don5st
do6ns
do6n5t4
2dop
do4pa.
d2o1pa
do4pe.
do1pe
3do1pi
d6o1ra
do5ran
2d1o4rd
dor4da
d8o1re
dor6ge1st
dor3ge
dorg6e4s
do2r1g
d5or2ie
do1ri
do2r1m8
d4o3ro
5do2r1p
3d4o2r1r
d2o4rs6
dor2s7ke
do1r1u
do7r4y1e
d4ory
5dos.
d2os
do4se.
do1se
do4set
do1sk
6do7s6l4
2do3so
4dosp2
2dost
do3str4
4do1tr4
d2ot
2dov
do5va
d4o5ven
do4ve2r
do2v9n
4do1ø
2d7p8
5dq
dr4
d1ra.
d5raa
d9rad.
d5ra1de
1d6rag
dra5g2en
dra1ge
dra6kes
dr2a1ke
3dra6k1t
1dram
5d6ra6ng
3drap.
5d4ra1pa
3dra1pe
d3ra2pp
d1rar
d6r2arar
dra1ra
d3r1a2r1b8
dra6r4et
dra1re
d5r1a2r1m
d5ra2r1r
d9r4a8sa
9dra6tt
dra2u7ma
2d1re.
5dre1a4s1s
dr2ea
2dred
d5re3de.
dre1de
d5red2er
d2r4e7d6r4
d5re8ds
9dreg.
9drege.
dre3ge
d3re1gj
3d2re2i7e
drei5e2n
drei5er.
drei7e2rn
d5re1j
d6r4e1ka
d7r2e2kk
3dre6k1t
4drel
d7re3l2ig
dr2e1li
4d2rem
d1ren
4d1rer
d3ret.
d3r2e8t1n
d6r2e5t2r4
d3re4ts1
dre6tt4
dret6ts5
5drev4et
dre1v
3dre2v1n
4dria
3d2r2if
2d1r2ig
d2r2ik
d3rik.
dri8ka.
dri1k2a
d3ri1ke
3dri2kk
drik7s6
d9ri6k1t6
5d4ril
2d1rin
dri6ng6
drit7tr4
dri6tt
dri1t5u
dritun8ge.
dri6tun
dritu6ng
dritun1ge
1d2riv
3d4ro4nn
dr2on
dro6pa.
dr2o1pa
dro2p5s
2d1ror
3d4r2os
dro8ta.
dr2ot
dro1ta
2d1rov
dro5va
dro3ve
2dru
3d4ruk
d1rul
d1r4um
d3rup
6d5rut
2dr6yk
dry2kk4
d2r2ys
d4rø6m
drø6re.
d1rør
drø1re
d7r4øt
drø3ve
d1r6å1d
drå4de.
drå1de
3d2råp
8ds
d5sa.
d3s8ab1l2
ds1ad
ds3a6del
dsa1de
d1sag
d5s4a9ga
dsak6se.
dsak1se
d1sal
d7s8a8la.
dsa1la
d4s3a4l1f
d6sa2l1l
d9sa1me
d1s2am
ds1a6n
d1s5a5ne
ds1a2r
d2sas
d4sa6t1f
d1sat
dsbø6n
d8s9b4
ds1b4ø
d5se.
d1se
dse4d
d2s1e1f
d2s1ei
ds7eks
d2s3e1la
ds2el
dsel4s5a
d3se8l1s
dse2m
ds3e1mi
d6s7en9d8a
dse6nd
dsen8de.
dsen1de
d4s3e6ng
d5s2e6ns
d2s3e2p
d7s2er.
d6s1e2r1f
ds5e2r1k
d4s5e4sk
ds5e4st
d2s3e2t6a
dse4te.
ds2e1te
d5s2e1tj
d2s1e1v
d2s1i
d3s2id
d7s2i2da
ds5ide6n1t
d3s2i3den
dsi1de
d4s3idr4
d7s2il
ds5i6nd
d3s2ir
ds3i4s
dsi4ve
d4s3jen
d1sj
dsj2e
d2sj2o
d2s7jor
ds6ju
d4s5kab
d1ska
d4s3kan
d2s3k2ar
d4skat
d1skj2
ds5kjen
dskj2e
d6s5kj4øt
dskj2ø
ds1ko
d5s2k2ot
ds3k2ro
dskr6
d2s1kv2
ds1l4
ds5la.
ds5lan
ds6lem
ds1le
dsl2i6k
ds1li
ds6lo.
d9s6lott.
dslo6tt4
dsl2ot
ds4luk
d1slu
ds3ma
ds1m
d6s1mo
dsmå6la
ds1må
dsm6ål
ds3ne
ds1n
ds5no
ds1o2
d7s4omst
ds1o6ms
d4s5os
d7s2ot
ds1p2
d3s2pek
ds1pe
d3s2p6el
d1s2pi
d9s8py
d4s5s4
dst4
ds3tak
d4s3tal
d3s8tar
d5s4tat
ds6tau
dss8t
d8s9te.
ds1te
ds9te2ik
ds2t4ei
dste6ma.
ds2tem
dste1ma
d6s5te8m1p
d4s5te3o6
d3s4t2ig
ds1ti
d3s4t2ik
d5stil1li
dst4il
dsti2l1l
ds1tj
d3stor
d3stri
dstr4
dstu8na
d5s4tu6nd
d2s3t1v
dst4y8e
d1sty
d4s3ty1v
ds1u2
dsu1re6
d5s6us
d6s7usk
ds1v2
dsva8ne.
dsv4a1ne
d3s4v4et
dsvi6ka.
dsv2ik
dsvi1k2a
d5s6yk
d1sy
d3s2yn
dsy6na
d2s1y2t
d2s1ø
d6sør
ds1å6
4d1t
d3t4a
dt8a6la.
dta4le.
d3t2a1le
dta4s
d3tem
d1te
dte4ma.
dte1ma
d2t4e6ng
dter1le7
dte2r1l
dte4se.
dt4es
dt2e1se
d5t4et
d3ti
dt6j
d7to
d5t4rag
dtr4
d6t1r4oc
d5t4rø
d6t1s2
d3t2v4a
d2t1v
dtø8
d2t6øk
dtø4rs3
dt2ør
dtå4
1du
du2b5b
dub5l2
du1c
du9e1ne
du1e2n
du9e6ns
due5s
4du1fo
3dug
4du1gu
duit6
du6ka.
du1k2a
du2l5l
4d1uly
du6ms2
du4na.
du1na
dun7de
du6nd
4d5u1n6i
d4u6n5s6
dun7sta
7duo
d3u4p5k4
3dur
du6ra.
du1ra
du6re.
du1re
du6rei
dur8ta
du6r1t
du1s
d4u5s4a
du9sem
d2u1se
du5s2i
du2sk
du2s1l4
du2s1p2
du4s1s4
dust4
du2s1v2
2d1ut
du6va.
du1v
du4ve.
2d1v
dv8a6la.
d4va6lel
dv2a1le
dva4ne.
dv4a1ne
dv4e6s
d2ve6va
dve1v
dve8ve.
d7vo
dvo8r
2d1w
1dy
dy9a
2dy1b4a
dy1b
2dy1f
dy5ke.
d6yk
dy2ke
4dykl4
4dykø
2dyl
dy4na.
dy1na
dy4ne.
dy1ne
4dy2nk
dynk6s5
dy3pe1s
dy1p8e
dy2p3r6
dy4ra.
dy1ra
dy5re1b4a
dy2r2e1b2
dyrle8ge.
dy2r1l
dyr1le
dyr3le1ge
dy4r5s4
dy2r5u
dyr5ø
7dys.
d2ys
dy8sa
4dy1s4e
dy6se.
dy4s7s
dyst7r4
dys2t
dy2t
d5z
8dz.
1dæ
dæ4rs4
1dø
4dø.
dø8d2s1
dø3g2r4
døg1
2døk
d5ø2k1n2
dø4ma
dø4pe.
dø1pe
dø4pen
d2ør
dø1r3i
2døs
d6øs.
dø2st3
d4øt3
dø9va
dø3vel
dø1vi
6dø6v1l2
døv8le.
døv1le
2døy.
døya8
døy8g
4d9ø2y1s
1d6å1d
då8d6s5
då8na.
då1na
1dåp
då8ra.
då5ri
d3ås.
dås2
då8sa.
då1sa
då7sem
då1se
d7åt.
2ea
e1a2b
e1ad
ea2d1i
ead5li5
e2a4d1l4
e1af
ea2gu
e1a2k
e4akr6
6ea6k1t
eak6se.
eak1se
e1al.
e5al1le
ea2l1l
eal8le.
ea4lov
e3al1te
ea2l1t
ea2lø
ea4m1
e3a3man
ea1ma
e5a2m1n
e1an
e2an.
ea4ne.
ea1ne
e6a5net
e4a1ni
ea4n7n
ea4nor
ea1no
e6ans.
ea6ns
ean3sl4
e1ap
ea4pe
e1ar
ea2re
ea5rer
ea5r4et
e2a5r4i
ear6ka.
ea2r1k
ear8ma.
ea2r1m
ear1ma
ear4ta.
ea6r1t
ea2s1i
e3asp2
e1a4s1s
e4a7ta1
e5a4tel
ea1te
eate6ren
e4ater
eate1re
ea4t3et
ea2t8h
eat6le
ea6t3l6
e8ato
ea5tri
eatr4
e1a6tt
e1au
eau6ga
eau6ge.
eau1ge
eau8ra.
eau1ra
eau6re
eau5s
ea4u8sa
eau8se.
ea2u1se
e1av
eav8la
ea6v1l2
eav8le.
eav1le
2e1b2
eba3d
e1b4a
eba4ne.
eba1ne
eb3be
e2b1b
eb4be.
ebe4d3e6
e1be
ebei7er.
ebe6i
ebe2i7e
eben6s5k
ebe6ns
ebe4rs6
eb4er
ebe1sku5
eb4e1s2
ebe4t5s1
e3b4et
e2b3h
e5b6lo
ebl2
eb1læ5
ebob3
eb2o
ebo4da
eb2o1d
ebo5e6rs.
ebo2er
eb6o1e
eboe4rs
ebo6kr6
eb4o6la
eb4ol
e6b5s
ebu4e5re
e1b2u
ebu1er
ebu5e2rn
ebu6et
eby4ta
e1by
ebø6n
e1b4ø
2ec
e4c4a
e4c4c
e1ce
e5ch2a
e3co2m
e2dad
e1da
ed5ad.
e5d4ag
eda8g4s5
e2dar
ed3a2r1k
ed3a2r1v
ed7d2e1la
e6d1d
ed1de
ed4dyr
ed1dy
e3de.
e1de
e6d6e6ge
e2deg
ed5eg4n
ed3e2i7e
e2dei
e4deks
ede8le.
ede1le
e4de4n1f
e8d5e6ng
ede6n5t
e6depr6
ed2e1p
ed2e4ra
ed2er
e4d2e1rø
e4desk
ede1s
ede4sl4
ede4s1m
e4des1n
e2de4s5p2
e2de4ta
e6d5e6tt
e8dé
e5d6ia
e1di
edi6a5ne
edi4e1ne
ed2ie
edi1en
e2di6gj
ed2ig
ed4is
edi3s4i
ed6i4s5k
e7div
edle6ge.
e4d1l4
ed3le3ge
ed1le
ed8o2b3
e1do
edok8se.
edok1s4e
e4dol
e2d1op
e2d1ov
e1dr4
ed2ra
ed3reg
e5drev.
edre1v
ed3rom
e6d5ryg
ed7ski
e8ds
ed3skr6
ed3s4la
eds1l4
ed2s1m
ed5t4a
e4d1t
ed3te
edt6r4
edu8a
e1du
ed7va
e2d1v
edvi6s
e3d6yk
e1dy
e4d5y2nk
edyr6ke.
edy2r1k
edyr1ke
e1dæ4
e3d6ø1r
e1dø
e3e2
e2ea7
e2e3b2
e2ed
ee3di
eeg4ga
ee4g1g
eeg6n
e4e5gå
eei4d
ee4i7ni
ee1k1e
eek4te
ee6k1t
e2e3la
e5e4li
eem6na
ee2m1n
ee4n
e5e4n1b2
een3in
ee1ni
ee2n6k
ee6n5t
e3ep2l6
e2e1p
e2e8ra
eer3en
ee1re
ee3ri
e4e2r3l
eer4me.
eer3me
ee2r1m
e6e5s1h
ee1s8ka
ees6ke.
ee2s1ke
e3e6t
eev4ne.
ee1v
ee2v1n
eev1ne
e1f
4e1fa
ef2a8l6s7
e5fal
e4fa1na
e4f3a2nk
e4fa2r1k
e3far
ef2a4r6s5
e2f7e1a4
e1fe
e2f5e1f
ef3fe
e2ff
3effek
ef3fo
4e1fi
e4f3id
e5f6ig
e5f4i2l1m
efi7ren
ef2i2r
efi1re
efisken8
efi2sk
efi8s1ke
4e3fj
2ef2l2
efle6ge.
e2fleg
ef1le
ef3le1ge
e1flå3
e2fn6
efo8bi.
e1fo
ef2o3b4i
efo4no
e3f2on
8efr2
ef2sj
e2f1s
ef2sk
ef2sp2
ef6str4
efs2t
e8ft
ef2ta
efta5r
ef2t5ei
ef1te
ef5ter
eftle8ge.
ef6t5l6
eft1le
eft3le1ge
ef4t1s4
6e1fu
efy4rs5
e1fy
efyr2
e3fæ1
4e1fø
efø5le
eføy9
2e1ga
e3gaf
e6ga1la
e7gam
ega4ve.
eg2a1ve
egde8l
e8g1d
eg1de
eg7de1s
e5ge1do
e1ge
eged4
eg1e2i9e
e2gei
e5ge4let
ege1le
e2gem
e3g8e1me
ege6n5s
eg2en
3e4gensk
e7geom
e2ge3o6
e2g6es2
ege5s2p2
eg9g2en
e4g1g
eg1ge
eg2g3l
eg8g9ut
eg1gu
egi3an
e1gi
eg2i1e2
e4gi1ko
eg2ik
e4gis4p2
e3g6lad
egl2a
e3g4led
eg1l6e
eg7le1sa
e5g4lit
eg1li
eg7ly
e5g6lø
6e2g1m
e9g8nag
eg1n
eg1na
eg4nem
eg1n4e
eg6no
e7g2nå
4e1go
e4go.
7e6goi
e1g2r4
egra6ns5
e7g8rø
eg1s4am3
e8gs1
egsa2
eg1s4ki
egs4ta
e1gø
egøy7e6ne
egøye4n
egø4y1e
4e1gå
egå4v4a
egå1v
egå4ve.
e1h
eh2e2a4
ehe2i9e
ehe2i
eh5er.
e4h5e1re
e4h7e2r5n
ehe4r4s
eh2og5
eh4o
eho1v2
ehy6re.
eh6y
ehø8va
e6hå.
eh6å
2e8ia
e9ia6k1t
ei9a4n
ei7ar.
ei4c
e8id.
ei3d8ar
e2i1da
e3i2d2ea
ei1de
e3ide3o6
ei9d2er
ei5det
5ei2d1f
5ei2d1g4
e6i2do
e1idr4
ei3d4u
e2i7e
ei2e5d
ei8eg
ei1e2n
ei9en.
ei2e1r3a
ei4e6r5t
ei6e1s
ei1f2l2
e2if
8eig.
e2ig
ei3ga
ei1g6e
ei6g2e1v
ei5gi
ei6g2ra
ei1gr4
ei6gu
4ei1i
ei5kaa
e2ik
ei1k2a
ei3ke.
ei1ke
ei6ke3e2
ei6ke5h
ei4kel
ei6ke5ri
ei4ket
ei1k1r6
eik2s3a
eik6se
eik4so
4eil
ei9led
ei1le
eil5e4g1g
e3illu
ei2l1l
ei3lo.
ei9l2oa
ei2l5op
ei5l2os
ei8l5s6
4eim
ei7ma.
e4i1ma
e5i2mag
ei4m5a6l
ei4med
ei1me
ei6mei
ei7men
ei4me5s
ei4m8et
eim9e6tt
ei6mo
8e1i8m1p2
ei4n3al
e6i1na
ei4na6ns
ein2an
ein5a6n1t
ei7na1re
ei6n1d
4e3in1du
ei5ned
e4i1ne
ei7nel
ei4n9f
ein1ga6
ei6ng
ein3gr4
e4i1ni
6ei6n4it
e2i2n3k4
e5inkar
ein1ka
4e1i4nn
ei4nom
e2i1no
ei4no1v
einsi9d
ei6n1s
ein3si
ein4s3l4
eins6o
e4in1spi
e1insp2
e4in7s6to
e6i2n1u4
ei3num
6e1i6n1v
e6i2n3ø
ei5or
e4io
e4ip
ei9pa
ei3pe
ei2p5s
2e2ir
eir9ak
ei1ra
ei7ren
ei1re
ei3ri
ei4r5s
2eis
ei5sar
ei1sa
e4i1se
ei3se.
ei2se5i
ei3sen
eiser2i9e8
eis2e1ri
ei3s4h2a
eis1h
ei4s2il
ei1si
e3i6sk.
e3i8s1ke
ei6s3kj2
eis3ko
ei3s4pe
eis1p2
ei4spi
ei4tek
ei1te
ei4t2e1ra
ei4te3re
ei2to
eit7ta
ei6tt
ei6t7ut
ei1tu
ei3tve
ei2t1v
ei6t7ø
ei4vak
e4i1va
eiva9r
ei9ven
ei1ve
ei3vi
ei8v5s4
e1j
ejo8en.
ej2o
ej6o1e
ejoe2n
eju6la
eju2l
4e1ka
eka1li7
ekal2ie6
eka6m6s5
e1kam
eka6ra.
e1ka1ra
e5ka2v1r8
e2kav
ek3e2i7e
e1ke
e2kei
e4k2ero
e4kes
e2k5e4ta
e6key1
e5k6ho1v
e4k1h
ekh4o
e1ki
e4ki.
4e1kj2
2e2kk
ekke7le
ek1ke
ekk9ist
ek1ki
ek1kj2
ek4kj2ø
ekk3l4
ek5kok
ekko5v
ek6ku
ek4ky6
e1kl4
e8kl6a3ne
e2k6leg
ek1le
e6klen
e2k5let
e3klu
e5k2læ1
ek2lø
4e2k1n2
ek9na.
ek1na
e3k2nek
ek1ne
ekne7s
e3k4ni7p
ek1ni
ek3no
e3k4nok
e3k2nu
e5k2nø
e1k2o
e4ko.
e2k2o7le
ekor8ds8
eko4rd
eko6te.
ek2ot
eko1te
e4kov
2e1kr6
e3k2ra
e3k4red
e7kre1f
e3kren
e4k5ret.
e5k4re4ts1
e5k4re1v
e3k2ri
e3k2ro
ek4ry
e3k4rå
e4k5r6å1d
ek3s4ak
ek1sa
ek4sal
3ek1s2am
ek3s2el
ek1se
3eksem
ek4s2ig
ek1si
eks1k
ek4ska
1eksp2
eks5pe
ek4sta
ek7s6tel
eks1te
ek3s1ti
ek4s4t4il
ekst5o
ek2st3å
ek4sæ
ek6t7a2r1m
e6k1t
ek2t3av
ek3te1f
ek1te
5ek3t2e1p
ek5t4es
ek6test
ek4tid
ek1ti
ekti5m
ek5tiv
4ekto
ekto3ri6a
ekto1ri
ek4t3ra
ektr4
e1ku
eku4le.
eku1le
e1k2v2
ek4val
ek6var.
6ekve
e4k5ve4d2
e4k5v2ik
ek4vin
ek6vis
3e2kviv
6e5kw
e5ky
eky6te.
e2kyt
eky1te
e3kæ
e1kø
e1kå
ekå6pa
2e1la
el4a4ga
e4l3a4g1g
e2l1ak
e5l2a1ke
elak8se.
elak1se
e4l7a6k1t
e2l1al
e8la1me
e4l3a4n1l
e6l5a2r1g
e6l7a2r1r
el7ar1ti
e4la6r1t
e5l6as.
e5la5se
ela4te.
ela1te
el5a1to
el5a6v1l2
e2lav
e6l3a8vs
elbo8da
e2l1b2
elb2o
elb2o1d
el4ch
e2l1c
el5do
e6ld
el4d4rer
eldr4
eld5s1le
el8ds
elds1l4
4eled
e1le
e4le1di
e4le1dr4
e3le3e2
ele8g5d
ele7g2er
e3le1ge
eleg8na
eleg1n
e7l2e2ir
e7l2eis
e5le1k6e
e2lel
e2lem
e5l4em.
e5lem6at
e4le1ma
5elemen
el8e3me
e5lemet
e3le2m1m4
e3le8m1p
4elen
el2e9na
e4l5e4n1h2
ele6ns3
e4lentu
ele6n1t
e5le5pa
e2l2e1p
2e3ler
ele8ra.
el2e1ra
e7lere4t
el8e1re
e6l7e2r1g
ele3sk
e6le1sku
e4le1ta
e4l2e1te
5e8lev.
ele1v
ele4vak
6e5le2v5n
el5fi
e4l1f
el9ga.
e2lg
el1ga
el5ge1le
el1ge
el5gi
el8g3s8
2e1li
e9l2ie
eli5e6rer
elie1re
e4l2if
e4li5g4r4
e3l2ig
e4l3i6nd
elin5es
el4i1ne
e4l1insp2
eli6n1s
eli6o6s
el4io
eli8ta.
eli1ta
eli4tet
eli1te
6e5li6tt
eli6v7en
eli1ve
e2l1j
e6l1k2
el6k5a6l
el3ka
el4ke5s
el1k4e
el4k2e3te
el3k1n2
el5la.
e2l1l
el5l6ar
el4led
el1le
el4leg
ell5e2i7e
el5ler
el3l6e4s
el6l2e1te
elli7ga.
el9l2ig
el1li
elli1ga
el4li1sj
ell7sa
el8l1s
ell5sk4
ell5s4l4
ell5sp2
ell5s1v2
el5l4ur
el4læ1
el5m4o
e2l1m
el5mu
e2l5n
2e1lo
elo6ka.
elo1ka
e2lom
e3l2o2m1m4
el3o2m1n
el3o6ms
e2l1op
e2l1or
e3l2o6r1t
e2l3ost
el2os
el4ot
elo7ve6rs.
e1lov
e6l5ove4rs
elove2r
e4l5o2v1n
el4pet
e2l1p
el1pe
el5p2h
elr2o4s
e2l5r4
el4sei
e8l1s
el1se
els5e2i7e
el3sen
el3se6s3
el3si
el4sj2e
el1sj
el4skal
el1ska
7elska6nd
el4s5kan
els5ka1re
el2sk2ar
el2s1l4
el6s1no
els1n
el4s1pe
elsp2
el4spr6
els4ten
els1te
el4s1ti
el4stj
el2sø
els5øk
el5s6å1p2e
el2s1å
elså2p
elta8le.
e2l1t
el3tal
el3t2a1le
el5te.
el1te
el6teg
el4t2ero
el4t5e6tt
el4t3op
el4tro
elt2r4
elt5rop
el2tu
e3lua
e1lu2e
e4lu4he
elu1h
e3luk
e7l4um
el3u6ng
elun6ge.
elun1ge
e7lup
elu8pe.
elu1pe
e1lur
e7lus
el3u4t5r4
elv3an
e8lv
el1va
el4ve3d1
el4vei
el6ve1re
el2v1r8
5elvs.
el8v1s2
elvæ6re.
el1væ
elvæ1re
4e1ly
ely8et.
el4y5e
elye2t
el5y6nd
2e1læ1
elæ5re3s
elæ1re
el9æ2r3v
e6l5æt
2e1lø
e4l3ø4r
e6l3øy.
e4l5øya
e2l5øyd
e6l3øyn
2e1lå
e2l1år
e5låt
4em.
e2mad
e1ma
em6a1fo5
e2ma1f
ema4ge.
ema1ge
e4ma2gr4
e2ma1h
ema4ke.
em2a1ke
e4m2a1ko
e4m5ak1ti
ema6k1t
e4ma1ku
ema6le.
em2a1le
5e4ma2lj
e4mam
e4ma1na
ema4ni.
ema1ni
e6m2a1nu
e4ma1re
em7a2r8m
e4ma1si
e6masku
e4m4a1ta1
em6at
ema3u4k
e2mau
em2a5ve
e2mav
5emba2l1l
e2m1b
em1b4a
3embed
em1be
emb4e6r5
3em3b4et2
6e2m1d2
8e1me
eme2i9e
em2e4li
em2el
e4me6nd
eme5tri
em2e1tr4
eme6trisk
eme4t3ris
e2m5e4v
e4mi1b2
e1mi
emi5e6r5t
em2ie
e4m2ig
4emin
emi5ni
emini6st
e4minor
em2i1no
e6m2ir
e4mi9sa
e2m7j2e
e5mju
em5le
e4m3l
emle6s
em5me.
e2m1m4
em1me
em5men
em8mi.
em1mi
8em6nd
e2m1n
em4ne.
em1ne
em4ned
em4nem
6em1ni
emo5nol
e1mo
emo2no
em2on
e2m1op
e2m3o4v
em4ped
e8m1p
em1pe
em6pe1ri
em2p3l6
em4p9lane.
em3p4lan
empl6a3ne
em6p2li
em1po3
em4pol
em4p2os
em4se5s
e6ms
em1se
emse8te.
ems2e1te
em4s5ju
em1sj
em4s1l4
em2s2n
em4sor
em1so
em1st8
ems4te
ems3u
emta8la
e2m1t
em3ta
em4t4il
em1ti
e3mug
e1mu
e2mu2k
e5my
em6y4k
6e1mø
emø8r
e5møy
emå4la
e1må
em6ål
emå9lan
emå8l4s
e2m1å8r
2e1na
e2n1ak
en3a6ld
ena5li
en3a2l1l
en5a6nd
e5n6a1ne
e6n7a6ng
e4n3a6n1t
e5n4ar.
en8a1re
e4na2r1r
en3a4s1s
enat6s5p2
en2a4ts
ena2v
en3a1vi
4e4n1b2
enb2e9na
en1be
e6n4d5a6nd
e6nd
en1da
en4d6ek
en1de
en5d4e5l
endelø7se
en2d2e1lø
en5de1mi
en2dem
en3d2er
en4desl4
ende1s
en4d5l4
1endr4
en4d3ro
end3st4
en8ds
en7dø
end2ø4r
endø1r5e
en5e4g1g
e1ne
e2neg
eneg8ga
en3e2i7e
e2nei
en5eks
e2nek
e3ne6k5t
4enem
2e3nen
en5e6ng
3e4n2e2r2g
e4nesk
4ene1v
ene7ven
en1g6a
e6ng
en7gar
en4gem
en1ge
en4ge1ri
en3g2er
eng2e6r5u
en4g1l6e
eng1l
eng4les
en1g4r4
eng3s4e
en8gs1
eng5so
eng5sp2
engs4ti
en4gut
en1gu
en4g5å
4enhj
e4n1h2
e4ni.
e1ni
e4nie2r1k
en2ie
e2n2if
e5n2ir
en4k2e1ra
e2nk
en1ke
en4ke1ri
2en1na
e4nn
en9nal
2en1ne
en4nem
en4ne3s6t
en2ne4s5v2
en1n2i
en1n2o
enn4sj
en6ns
enn5sta
ennst6
en1n7ø
2e1no
e2n3ok
en3og
en3om.
e2n1op
e6n5o4rd
2e6n3p2
en4pås2
en4rem
e2n1r2
en3re
en5sab
e6ns
ens5af
en6s7a6ker
en3s4a3ke
en4sek
en1se
en4s2e1li
ens2el
5en3se2m1b
en2sem
en4ses4
en2se3u
en3si
en3s2i8de.
ensi1de
ensi5e6r5t
ens2ie
en4sim
ensle7g
ensl4
ens1le
ens6le1v
en7s6o2p1t
en2s1o2p
en1st
en6s7tal
ens4ter
ens1te
ens4ti
ens7u6nd
en1su
en2s4ve
ensv2
ens1vi6
en2sø
ens3øk
ens7å
en5ta.
e6n1t
en4tap
en5te.
en1te
en4t2ec
en4teg
en4tel
en3ti
en5t2i1e2
en5t2ig
en5t2ik
en9t4i1ma
en7timen
enti1me
en4to4r
ent5o1ri
ent5rab
entr4
en4tre
ent5ren
en4t5rol
entro1pi5
en4t3rå
en6tu4l
entun6ge.
en2t1u2n
entu6ng
entun1ge
3entus
2e1nu
e2n7ul
e4n5ur
2e6n3v
4en3w
e4ny.
e1ny
e2ny1b
e6nyr
e2n2y1s
e4ny1ta
4e7næ1
enæ8m
enø4k
e1nø
e2n3øv
en5øyd
enøy1
4enå
en1å2s2
en6ås.
e3o6
e2o3a
e6o1b4e
eo2b2l2
e2o5d
eod8de.
eo6d1d
eod1de
e2og1
eo7gr4
e2o1i
e2ok
e3oks
e2ol
e4o7lo
eo6m1s4
eon8de.
e2on
eo6nd
eon1de
eo5ne
e7o2n1k
eon4kl4
e3o6n1t
eo2p
e2o3pa
e2o5pl6
e2o3po3
eop6p5r6
eo2pp
e2o3pr6
eor4da
eo4rd
e8o9re
e2o1ri
eo4rid
eor2i5e6
eo4r2ik
e6o4ris
e4o1ro
e2o1s
eos6l4
e2o1u
2e1p
epa3t
e1pa
e6p2e1no
e1pe
2e2p2e6p
ep2e2r5r
e3pe1s
epe4st
e4pe1ta
e6pe2u
3e2pid
epi6ka.
ep2ik
epi1k2a
epi7k1r6
3e2piso
ep2l6
e8plen
ep1le
ep5le1ne
ep5ler
eple9s6
3eplet
epo6et
e1po
ep6o1e
3e2po1ke
ep2p2s
e2pp
ep6sem
e2ps
ep1se
ep4ses4
ep6s5lu
epsl4
eps1t2
ep2su
e3p2sy
ep6tin
e2p1t
ep5ti
ept6r4
2e1ra
e4r3a2b1b
era8da.
er6a3d2a
e4rael4
er2a1e2
e2raf
e6r3a2ft
e4ra4g
e2r1ak
e3r2a1ke
er2a5k1l4
eral3u
e4ra1ma
er6a6na.
era1na
e5r4an9de.
era6nd
eran1de
e5r4a7ne
er7an1li
e2r5a4n1l
era4no
er5ape.
era1pe
er5aper
e4r5a4pin
era1pi
e4r3a1po
er5ap1pe
era2pp
e5r4ar.
e4r1a2r1g
e5ra1s2a5ne
e4r4a1sa
eras6an
e5rasar
e4rasj2e
era1sj
e4rask
e6ra1so
e4r3a4s1s
er6ast
e4ratek
er6a1te
era7tor
era1to
e4ra1t4r4
er5a6tt
e2rau
e4r5au4k
erau9ken
erau1ke
erau8s
er3av.
e2rav
er3a2v1h
e4r5a6v1l2
e3r6a2v1n
er3a2v6r8
er3a8vs
4e2r1b8
erbi9t2i
er1b4i
erbo8da
erb2o
erb2o1d
erbo8de.
erbo3d4e
er8byl
er1by
er1d2e
e4rd
er4ded
erd2e6n8s3
er4dis
er1di
er1dr4
erd4ra
erd8re
erd4skj2
er8ds
erds3t4
erd4sto
erdsto8ga
erd2s3t2o5g
er3d4v
e1re
e4r3e2ff
ere1f
e4r3e8ft
e4r5e4g2en
ere3ge
e2r3eid
e2r3e2i7e
er4e6ka
ere6ke.
ere1ke
e4r3eks
e6r5ek1te
ere6k1t
e4r3e4le
er3e4lit
er2e1li
e6rel1li
er2e2l1l
e4r3e8l1s
e9r4em.
e2rem
e5re2m1m4
e6r3e2m1n
er5enden
ere6nd
eren1de
e4r3e6ng
e4r5e4n1h2
e3re4o7
er8er.
e1rer
e4r3e2r1f
e5re1ri
e2r3e4s1s
ere4t
e5ret.
e4r3e1ta
er4e2t6h
er2e5to
e7re4ts1
6ere6tt
eret6ts5
e4r3e4va
ere1v
e4r5e4ve
e4r3e2v1n
erev6ne.
erev1ne
e8ré
5erf2a1ri
e2r1f
er1fa
er3far
6er1fi
4erfr2
2e2r1g
erg5e8lv
er1ge
erg2i3f
er1gi
er6g1li
er4go.
er1go
erg5ret
er1gr4
erg5sko
er8gs1
erg5sp2
2e2r1h
erhø6re.
erhø5re
e1ri
e4riad
e4riak
eri2b3b
e2ri1b2
eri3b3l2
e4rice
e2rid
e5rid.
e5ri8d1n
eri4kat
er2ik
eri1k2a
e6ri2k1n2
4eril
e7ri6ma.
e4r4i1ma
e4r3i6nd
e3ri6ng
6eringar
erin1ga
ering6si
erin8g2s1
e4r3i4nn
e2r3i6n1s
e2r3i6n1t
e5rio1ri
e2rior
er4io
e5ris.
e4ri1sa
eri3se
e4riso
e6ris1p2
e5ri6st.
e7rista
e5ri2s1te
e4ri5st4il
eris1ti
e4ri5s6to
e4ri5sø
e4ri3te
e4ri5t6o
e6r7i1vo
er7j2es.
er1j
er2jes
erj2e
4erka
e2r1k
8er1ke
erker2i6e
er3ke5ri
er4ke1sa
er4k2e4se
er4k2e3te
er6k5e6tt
3erk2læ1
erkl4
6er2k1n2
4er5k4o
4erkr6
erlan6d7as
e2r1l
erla6nd
erlan1da
erle6ge.
er1le
er3le1ge
erle7s8t
er3me
e2r1m
ermo8de.
er1mo
er5mo1d4e
erm2o1d
er6m1s
ermå6la.
erm6ål4
er1må
ermå1la
2e2rn
er4nad
er3na
er4ned
er1ne
er5ne1de
er6ne3e2
er4n2e3ro
er4nest
erne1s2
er2no
er3no5b
2ero
e3r2oa
er3o1b2l2
5er2o4b7r8
e1r6o1e
er3o2ff
e1r2og
er5ok7s
e1rol
e2r3o1ly
e1rom
e4r5o2m3k2
er2o5mo
e1r2on
er5o6nd
ero1no5
er1op
e5r2op.
e7r2o6pa
e5r2open
ero1pe
er1o2r
e5r4or.
e3r8o3re
e7ro4sa
er2os
3ero1sj
e4r3os2l4
ero4ta
er2ot
er1ov
2e2r1p
2e2r1r
er3ra
er4r2a1e2
er4rel
err6e
erri1e9n
er3ri
err2ie
6er1sa
e4rs
ersa8ka
er3sak
ers6al
er3s2e1p
er1se
6er1sj
er1sk
er3ska
er2s4ka.
er8sk2aran5
er2sk2ar
ers1ka1ra
er9s6k2arar
er4s5kor
er1sl4
ers4la
ers6led
ers1le
ers4mi
ers1m
er5s1ne
ers1n
6er1sp2
ers5te6n6s
er1s2t
ers1te
ers5ter
er7s2te1v
er1su
4er1sv2
er4sva
ers4ve
er1så
er4s5å2r
2erta
e6r1t
er4ta1re
er4t4e3s4
er1te
er5tia
er1ti
er3ti1b2
ert2i4e2
er5t4il
er4ti4mo7
er4top
ert5rol
ert2r4
4er4ts
ert3sa
ert4y6e
ertå7ren
2eru
e1rua
e5r2ud3
e1rui
e4r1u2k
e3r4um
e4r3u6ng
e4r3u2n6i
e3rup.
er1u8r
eru1re8
e1r4us
er5ut.
e5r4uta
e3ru1t8e
er5u8t1n
er9ut9o
e4r1u4t1s4
er7u6v
erve1le8
e2r1v
6ervi
er8v2s3
ervæ6re.
er1væ
ervæ1re
e1ry4
e2r3y1a4
ery7e7ne
er4y1e
erye2n
e2r1yr
er2ys3
e1ræ
e9r4æ4re
2e1rø
e4r5ø2k1n2
e4r1øko
e2r1ø4l
e4r5øn
e5r6ø4nn
erø8ra
e1rør
e6r5ø4r6s
e4r3øya
e4r5ø4y7e
erøy4n
e4r5øys.
erø2y1s
e6r7øy4s3k
2e1rå
e9rå.
erå4da
er6å1d
erå8de.
erå1de
er4å6e
erå4k
er5å1ke
e4r3å4l
e2r1ån
e2r3å4p
e2r3å4se
erå1s2
erå6t
e6r7å1ta9
e6rå1v
2es.
e1sa
e2s5aa
e2sad
esag6
es4al
e7s8a6la.
esa1la
esa9met
e1s2am
esa1me
e4s3a6n1t
e6s5arab
e1sa1ra
e4s5a2r1v
esb2i9e
e8s9b4
es1b4i
2e1se
es5e2ge
e2s3ei4d
es5eie.
ese2i7e
e2s3e2ig
esei8ge.
esei5g6e
e3sek
e4se1ku
e3se8l5s4
es2el
e3s2en
e5s2er.
e5s2e5r4e
e5s2e1ri
e4s3e2r1k
ese5s2
e3se1st
e4ses1v2
ese5tas
e2se2t6a
ese4te.
es2e1te
e5s2e1tj
e9s4e4ts1
e4se1u
e4sh2a
es1h
es6har
esh9ar.
es4h5er
e6she
e4shi
e1sh4o
e7s8hop
esi6ar
e1si
esi5e6r5t
es2ie
e4s2il
e5si1li
es3i2l1l
e4s3i6n1s
esi6v
esi3st
es2is
e2s3jor
e1sj
esj2o
e5skab
e1ska
es6ked4
e2s1ke
es4ke3e2
es4ker
8eskil
e1ski
e4s1ki6ng
e5sk8in6n1s5
es2ki4nn
e1skj2
e8s9kjer.
eskj2e
es4kjæ
e6skjøn
eskj2ø
e2s5k4n2
e5sko.
e4s7k2oa
e3s2k2ot
e1skr6
e1sku
esku5et
e1s4kue
es3kvi
e2skv2
e5sky
e6s1kyr
e1skø
e7skå
eskå7r
e1sla
esl4
es4lek
es1le
es4let
es7let.
es7le4ts1
es4lit
es1li
e4s5lok
es4løk
es1lø
es4løv
es4m2a1ke
es1m
es1ma
e7s4me6tt
es1me
e8s9na.
es1n
es1na
e4s4ner
es1ne
es2no
es4nu
es2ny
esnæ5re
e2s1næ1
4e1so
e2s1o4b
e2s3o1d
e2s1of
e4s3oks
e2som
e3s2o2m5m4
e4s3o2ri
e1s2p2
espa9ra
es1pa
e4s3p6as
e4s5pe.
es1pe
e4s4ped
es5pe2r1m
e2s3pl6
e4s4p5le
e2s3pol
es1p6o
e3spor
es3pun
es1pu
es4sed
e4s1s
es1se
es4se3e2
es4seg
esse7i
es6sen1di
esse6nd
es4s5e2nk
es4ses
es4s2e1te
ess5e4va
es2se1v
es4s3ja
es1sj
es4sj2e
es4skr6
ess4let
es2sl4
ess1le
es2s3om
es4s2os
es4s1pa
essp2
ess5ti2l3t4
ess2t
ess1ti
esst4il
es4stol
ess5tor
ess9tua
ess5tue
esstu8en.
esstu1e2n
4essu
es2s1v2
es7så
e7s6t2ad
es5t8a1e2
es7ta6k1t
e6s9ta4la
e8s1t8a1ne
e5sta6ns
es5ta6n1t
e2s3tap
e5s4ta6r1t
e3stat
e4s5tato
e4s3tau
e7stav.
es2tav
est5a8vs
e6s5te.
es1te
es4teg
est5e2i7e
es2t4ei
est5e2ig
es4tek
es4tel
e5ste2l1l
es5te8l1s
e4ste1ma
es2tem
e6st8e1me
e4ste1mo
es5te8m1p
6esten
e7s6te6ng
e6s5te2nk
e8s7t8er.
e5s6te2r1k
ester1ne8
est2e2rn
e2st4e3s4
es4test
es4ti5en
es1ti
est2i1e2
e3s2t2if
es4t2ig
e3st2ik
es4ti2kk
5e6s5t4i1ma
e2s4tis
e4stiv
e1stj
es3t6on
e4s3t2og
e3st6ok
e3s4tol
es6to1ne
e6s8t5o4rd
est5o1ri
es5trak
estr4
est5re5ne
est5rer
est9ré
es5t6ri1b2
e5str6yk
e2stry
e3strå
e1stu
e5s4t2ud
es5tus
8e1stå
estå5ren
estå6s2
est7å1se
e1su
e2s5u4t1
esva5re
esv2
e5s4vek
e4s5v2ig
e5s4vi6k1t
esv2ik
e2s3vis
2e3sy
esy4na
es2yn
e4s5y2t
es5øvi
e1sø
e2søv
e8s7ø4y1e
e1ta
4eta.
e4t8a1e2
e2taf
et6a1fo7
e3tak
e6t2a1ko
e3tal
eta3la
et8a4la.
et5a6ld
e3t2a5le
4e5ta2l1l
etal6list
etal1li
etal8l5s6
et2a8l4s7
e2t3a2m1b
e6ta6nd
et2a4nen
e1ta1ne
et4ap
e9ta1pa
e5ta1pe
3etap1pe
eta2pp
e1t8ar.
et8a1re
e4t5a6r1t
5e6tas2je5s4
eta1sj
etasj2e
1e2tat
4e5t4a1ti1
e5tato
e3ta6tt
e4t5aun
e1tau
e8tax
e2t1c
2e1te
e4t3e2i7e
e4t4e1ka
ete4ma.
e2tem
ete1ma
e5teran
et2e1ra
et2e5ru
ete1s5i
et4es
e4tesl4
et8et
e5tet.
e7te4t2s1
4eté
4e2t1h
2e1ti
e3tit
2e1tj
e7tjer
etj2e
e2t3j2ø
6e6t3l6
e2t5m
2e8t1n
et6ne1v
et1ne
etni6ng4
et5ni
etnin8g6s7
2e1to
eto4er
e1t6o1e
e4t5op2p1d4
eto2pp
e4t3o4rd
e4t2ot
2e1tr4
e5t4rad
e7t6rak
8etre
e5t2re3e2
e5tre1f
e4t3ris
e9t8ru.
et5r4um
e4ts1
et9s2ar.
et4sku
etsku8le.
et3sku1le
ets7l4
etsl4a8ga
et2s1næ4
ets1n
et2sø
et4tak
e6tt
et1ta
et4tal
etta6le.
et3t2a1le
ett5a2l1t
et4ta2nk
et8tap
et5te.
et1te
et6tei
et6te2l1t
et4te2nk
et3ten
et4te4nn
ett2e1ra4
etter5at
et5t6e2r1f
5ette2r1k
5ett2e2r5r
et4t5e1sk
ett4es
2et1ti
et6ti5a
ettian8
et4t4il
et2tj
ett2o5a
et4tr4
ett3re
ett4ski
et6ts
ett4s1ti
ett1st4
et3tug
et4t2ur
ettvi5se
et2t1v
et2ty
2e1tu
5e6tui
etu6na
e6t5u1n6i
2e3t2v
e4t3v4a
e5t6va6ng
2e1ty
4e2tz
4e1tæ
etæ3ra
e1tø
e1tå
etå5re
e1u
eu4a
e8uf
eug8l2a
eu4gl
eu4h
eu4ka.
eu1k2a
eu2ke
e4u5kem
eu7kr6
eu2l
eum2
e3u2n
eun4ge.
eu6ng
eun1ge
eu5nu6
e4u1p5a
e6u4p5k4
e2ur
eu6rat
eu1ra
eu1re4
2eu1ri
e3u2rn
eur8na
eur6ne.
eur1ne
euro1
3eu2rop
e3u6r1t
e2us
eu4si
eu1ta
e3u4t1s4
eu6t7t
e4u3z
e1v
evad6r4
ev5ak1ti
e1va6k1t
eva4la
evan6n4s
e1v4a4nn
e4v5anta
eva6n1t
e4v2arar
eva1ra
e4v1a2r1b8
e4v5a2r1k
e6va6t1f
6eve2d1
eve5d6a
eve5del
eve1de
6eveg
e5ve4g1g
ev2ei6s5
6eve4nn
e1ven
eve6n5s
5e4venty
eve6n1t
e4ve1ny
e9v8er.
e5v2es.
ev4es
e5ve1ta
ev4et
e5vev.
eve1v
ev2i4e
evi5e1re
evi5e6r5t
ev2i6n4s
evi4se.
evi1se
evis3t
e2vj
ev2na
e2v1n
ev2n5a4k
ev7nu
ev7o6ms
e2vom
e4v1re
e2vr8
e7v6ri.
ev9rå
e8v1s
evs8v2
evta8la
e2v1t
evta1
ev5t4i4
e2v3un
e1vu
e5vy2r1k
e2vy1
e2vø
ew3a
ey1
eybal4
ey1b
ey1b4a
e1y2d
e6y5k
e1y6ng
e1yr
eyr6ka
ey2r1k
eyr4ke.
eyr1ke
e1y2t
e7y1ti
ey3tr4
e3zi
e1zu
e1æ2
eær3
e1ø4
eør6na.
eø2rn
eør3na
eøy4
eøy9a2n
eøy7e4n
eø4y1e
e1å6
eåk4
eån8da
eå6nd
eå6t8t
é1a
é1b
é1d
éd2ø4r5
é1dø
é5e8
é1f
é1g
é1h
é1i
é1k
é1l
é1m
é4n5e
é5n6et
é5o
é1p
é1r
é2r1j
é1s
é1se2
é1t
é1v
éva8la
évo8re.
év8o1re
é5å
è1r
è2red
è2re1f
è2rek4
è4rener
ère1ne
è2r2e1p
ère3s2
è4rest
è2re1v
è6v4es
ê8lan
ê6ra
ê1re
êr7o
êr9sl4
ê4rs
1fa
4fab2o
f8ac8
fa1ci
fa3de
fa6de.
6f1a2d1m
fa4f
f4a2g1a
fa5g2er
fa1ge
fage4t
fa2g5e1ti
fa2gi
f4a2go
fa2gr4
6f5agro
fag3sk
fa8gs1
fa2g1u
fai5
2fakr6
fakse9t
fak1se
fakta3
fa6k1t
fa1ku
5fal
fa1la
fal6kes
fa6l1k
fal1k4e
fal4k3l4
fal6le4rs
fa2l1l
fal1le
fa2l5m
fa4lo
fal4sk
f2a8l1s
4fa5mo
f6a4na.
fa1na
7fa6n5d
5fa1ne
1fan1fa5
fa4n1f
fan8g4s1
fa6ng
fangs2t7e1v
fangs1t6e
4fa2nk
2f3a4n1l
fa6n5s6
fan7te1sk
fa6n1t
fan1te
fant4es
fan5ti
fan3to
fan4try
fantr4
6f2a1nu
2fap
3far
9f8ar.
fa3ra
fa4ra.
fa4re.
fa1re
fa4res
far4gel
fa2r1g
far1ge
far4g6es
fa4rit
f2a1ri
f2a2r5n
far6skj2
f2a4rs
fa6r1t2
far4vel
fa2r1v
4fa1ry
f4a7sa
fa4s4el
fa1se
fa4s2e1ru
2fas1l4
fast3r4
fa3tal
f4ata1
fa4te.
fa1te
fa6t7e4rs
f4ater
fa1to
fat2r4
fa3t6re
fav5ne
f6a2v1n
fa1vø
2f1b
fba4ne.
f1b4a
fba1ne
2f1d
1fe
3fe.
2f2e1a4
5fea.
fe5a6l
2f2ec
fe4da
3fe1de
fe2dr4
fe3d2ra
fe3d4ri
fe7e1ne
fe3e2
fee4n
2fe1f
2fe3g
2fehj
fe1h
fei5e2n
fe2i7e
fei5er.
feig4de
fe2ig
fei8g3d
fei5g6e
feil5e1s
f4eil
fei1le
fei4li
9fe2in.
7fe4i1ne
fei7ter
fei1te
8fe1j
2f4e1k4a
4fe1kl4
2fe1k2o
4f2e1kr6
fek6t4es
fe6k1t
fek1te
fek2t5e1v
fe5lag
f2e1la
f4e5len
fe1le
f2e5li
fe9l2i7e6
6fel2ik
fel3l6e4s5
fe2l1l
fel1le
fel7læ1
2f2e1lo
fel5ok
fel9ta.
fe2l1t
fel4tek
fel1te
fel4ti
fel4tra
felt2r4
fem5ak
fe1ma
fe2m9b
fem9ne
fe2m1n
fe4mo
fe6m5s
fem4tid
fe2m1t
fem1ti
f6e2m1ø
5fen.
4f2e1na
f1end9r4
fe6nd
4fe1ni
5fe6ns
fentleg5
fe6n1t
fen6t5l6
fent1le
2fe3o6
2f2e3p2
5fer.
fe6ral
f2e1ra
4feram
fe5ras
fer6at
fer4dam
fe4rd
fer1da
fer5d2e
4fer2ea
fe1re
4fereg
fe4rek
fer6en
fe7r8er.
fe1rer
fe3r5e4rs
fer2i6e
fe1ri
feri8e5ne
feri1e4n
7f2e2rn
f2e2r2r
fer2s9k2ar
fer1sk
fer3ska
fe4rs
fer2s7ke
2f2e1ru
3f2es.
2fe1si
2fe1sk
fe2st
fes9t6i
fes8t3r4
fe3s9tum
fe1stu
fe6st9ø
4f2e3sy
f4e6ta.
fe1ta
4fet4ap
fe4te.
f2e1te
4fet2ea
f2e5ti
4fet4il
2f2e1t6r4
fet2t5j
fe6tt
fet6t7s6
4f2e4ty
2fe1u
2fe1v
fe4ven
2fe1ø4
fe6øy.
feøy4
2fe1å6
1fé1r
2ff
f7fa.
f1fa
f4fab
f2f3a2g
f4f3ak
f5fa7l
f6fa4nn
f4f1a2r1b8
f3far
ffa4r6d
ffa7re
f9fas
f6fat
f8f9au
f2f3av
f2f2e1b2
f1fe
f2fe3d
f2fe3e2
f6fe1h
f2fem
f6fer1di
ffe4rd
ffe4rer
ffe1re
f2fes
f4f2e5ti
f4f2e1to
ff5eve
f2fe1v
f2fi1b2
f1fi
f4fice
f2fid
f9fi1en
ff2ie2
f4fi4nn
ff5i6n1t
f2fip
ffi5s4
f2fj
ff3la
ff2l2
ffl4a8ga
f2f9leg
ff1le
ff3li
f2fo
ff4ol
f2f3re
ffr2
ff1ro
ff5sl4
f2f1s
f2f5t4
ffu6r
f1fu
ff5ut
f3fæ1
f4fø
2f3g2
2f1h
1fi
fia7l
fia4n
4fiap
fi4as1
fibi6en
fi1b2
fi1b4i
fib2ie
fi9cen
fi1ci7
fi7di
f2ie2
fi1er
fi7e6re
f2i4f
1fi5fi
fi5fo
fi7f2l2
f6ig
fig4h9t
fi6g1h
fi7g6r4
4fi3h
fi9k2a
f2ik
fika1li7
fikal2ie6
fi7ken
fi1ke
fik6ka
fi2kk
fik6k5n2
fi5ko
fik7sa
4fi1ku
fi4la.
fi1la
fi3li
fil6lel
fi2l1l
fil1le
fil6le1ri
fil4l6es
fil7l2e3ti
fil8m7at
fi2l1m
fil1ma
fi1lo
filt6re
fi2l1t
filt2r4
fil7t3res
fil4tri
4fi1læ1
fi2n5a6r1t
f6i1na
fi6n5d
fi4ne.
f4i1ne
fi5ner
fin2g3r4
fi6ng
6fi4n1j
fin2n5ei
fi4nn
fin1ne
6f3innsa
fin6n1s4
f2i4n3o
fi9nor
2fi6n1s
fin6sle7g
finsl4
fins1le
fin6t4e5s
fi6n1t
fin1te
f6i2nu
7f4io5
f2i2r
fi3ra
fi6ra.
fi9re1ne
fi1re
fi5r2es.
fi3ri
fi5s4a
4fisc
fi6se.
fi1se
fi5se4r6s
fi8si.
fi1si
fi2sk
fi6ska
fis2k5a6d
fis7kal
fisk5a6nd
fi4s6ka6r1t
fi2s3k2ar
fis6k5e6nd
fi8s1ke
fi2s4k3l4
6fi1skol
6fis1ku
fis5ti
6fistu
fiti6me.
fi1t2i
fiti5me
4fi1tr4
fit6t5s
fi6tt
fi7ty
fi4ve.
fi1ve
1fj
f1jeg
fj2e
fjel6le6nd
f2jel
fjel5len
fje2l1l
fjel1le
f5jen
fjer5ne
fj2e2rn
fje4sk
fje2t3
f5ju
fjæ1re5
fjæ4res8
2f5k6
f2l2
f5lag.
f6la4g1g
fl2a7ke
f4las
2fleg
f1le
fle6i
f4lek
f6lel
fle4sl4
f4le6tt
f3le1v
fl4i7ne
f1li
flis7t
fli6t9t8
1flo
flo9ga
f1l2og
f2lok9s4
flo2m3
fl4o6r5o
flo2s6k2l4
fl2os
f6lu
flue3s
flus8
fl5ut
3fly
fl2y3s4
f3løn
f1lø
flø5s
1flå
flå9sa
flås2
flå7se
2f1m
fn6
1fo
2fo.
fo9ar
f2oa
6fo6b1s
2f6o1e
4fof
foi7la
fo8ke.
fo1ke
fo6la.
f4o1la
fol4dr4
f2o6ld
fo4le.
f2o1le
fo5led
fol6k5v2
fo6l1k
fol9ler
fo2l1l
fol1le
fol5l2e1se
fol4l6es
folke5s6
fol1k4e
fol5li
fo4lu
3f2on
fo4na
fo6nd2
fo5ne
fo3n4i2d
fo1ni
fo4nin
fo6n1s2
f1op
for1a
for9dre1v
fo4rd
fordr4
for7dro
for5d6u
fo6re.
f8ore
for9ei
for7e6n
for5e6n1t
fo2r5e4s1s
fore1s2
for4et
for9e8te.
for2e1te
for9e8ten
fo2r9g6
fo2r1h6
fo4r9in
fo1ri
fo2r1k8
for1lo7v
f6o2r1l
forl6o
forlø9pa
for1lø
forlø9se
formo9r
fo2r1m
for1mo
fo2r3n
f4oro6
fo2r5p
for3se
f2o4rs
for3s2i9da
for3si
for3s2i7de.
forsi1de
for3s4m
for1s8t
for9s1te
for1s4v2
forta8le.
f2o6r1t
for3t2a1le
for5t6e
for4t3e2i9e
for7t6i
fort2i5da
for3tid
for3t4v
for3u4l
for1u
fo2r7v6
forva9re
forv4a
for5æ4
f2or5ø4
for9å
fo9se
f2os
fosf8o5re
fo2s1f
1fos5fo
4fo1sj
fo4ta
f2ot
fo6te.
fo1te
fo1to5
fo4t6ok
fo4tom
fo6top
fo4tor
fo4t3s
fots6v2
fo6t3t
4f1ov
3fô
2f3p2
fp6o6e
f1po
fr2
1fra
fra5l
fra4m5e
fr2a6mi
framma8ne.
fra2m1m4
fram1ma
framma3ne
fran3ko5
fra2nk
fran7se
fra6ns
fra7r8
fra1s
fra5se.
fra1se
fra7s2e1p
fras2i8e
fra1si
fra7sk
fras6p2
f2ra7v
fra7v6r8
f2re.
fred8s2el
fre8ds
fred1se
freds5t4
f2re4e3
8freg
f4rek
f2re4m5
fremma8ne.
fre2m1m4
frem1ma
fremma3ne
fre6m1s4
fre7ne
f2re6sk
fre5s7ko.
fres5k6o3e
fre8s9v2
fri5a6re
fri1ar
6f5ri5di
fri4e2r1f
fri1er
fr2ie
f1rin
f4ri1s6ka
fri5s4p2
f4ri5s6t4il
fris1ti
frite8re.
fri3te
frite3re
friti8me.
fri7t2i
friti5me
6f1r4oc
fro7f
fron3t2a9le
fr2on
front6a
fro6n1t
fro8st
fr2os
fr4us1
fruta6le.
fr4uta
fru3tal
fru3t2a1le
f1ryt
f6rø5b
fr1ø7ko
f2r1å9r
fråve4
frå1v
2f1s
fs2h
fsh4o6
f1si2
f5s4ju2k
f1sj
f2sl4
fsle6ge.
fs1le
fs3le1ge
f4s1m
f2s1n
fs7ne8
f2s5ov
fs2t
f2s3tab
f6s5tan
f4s9v2
2ft
f1ta
f3ta.
fta7f
ft1ak
ft8a8la.
f1t3a2lg
f2t1am
f4ta2na
f7ta1ne
f2t3a4n1l
f5t4ar.
ft8as4
f6t1av
f2t2ea
f1te
f3ted
f4te3e4
f2teg
f2tei
f2tek
f6tem
ft2e4na
fte6n3d
f4t3e4n1h2
ften5s6v2
fte6ns
f2te3o6
ft2e4r5a4
f4terin
fte1ri
fte4r5s
f4test
ft4es
f4te1ta
f4t5e6tt
f2t3i6nd
f1ti
f2t5i4s
f6t5l6
f8t7n
f2t1o2
f5t2og
3f1t2on
ft3r4
fts5a2l1t
f4ts
fts3ei
ft1se
fts5e4rs
fts5e2r1v
ft2s1i
fts1k
ft2skaf
ft1ska
ftsl4a6ga
fts1l4
ft5s4lan
ftsle6ge.
fts1le
fts3le1ge
ft3sto
ft1st4
ft6s5top
ft5s6tri
ftstr4
ft5stø
fts1u
ft2s1ø4
f6t5t4
ftta8ka
ft1ta
ft1u
1fu
fu8ga.
fu1ga
fug9le.
fu4gl
fug1l6e
fug6l7eg
fu6le
fu2l1l3
ful9lar
ful1le6
ful9le.
ful4len
fullen8de.
fulle6nd
fullen1de
ful4ler
ful7l6e3s4
fullfø7re
ful4l1f
full1fø
fullf8ør
fu4nn2
fun6ns3
funnsl4a8ga
funnsl4
fu6ra.
fu1ra
fu6re.
fu1re
fu9ret
fu7ro
furu1
fu7s2el
f2u1se
fu6sk
fus6o7
fu4s5t
fu2t4h
fu1tu1
4f1v
fva8la
1fy
fy5la
fyr2
fy8ra.
fy1ra
fy4r5a4b
fy1re
fy4re.
fy2r7k
fyr8ke.
fyr1ke
fy6r5t
f2y3s
fy1sa7
fy8sa.
2fæ1
1fø
fø4da.
fø1da
fø4dek
fø1de
fø5den
fø4de1s
fø5d6es.
fø1f
2føk
fø4le.
fø1le
f8ør
fø1r6a
fø5rar
fø4re.
fø1re
fø4re1s2
fø7re6s7v2
fø8r2e1te
før6et
før6tin
fø6r1t
før1ti
4få
få7ren
få7ret
få5ri
får7u
få7v4a
få1v
1ga
4ga1b4a
ga1b2o
6ga1b4ø
g6a3da
ga4ded
ga1de
4g1a2d1g4
2g1a2d1m
4g5adr4
ga4e5k
g2a1e2
6ga1flo
gaf2l2
g3a2ft
6ga1h
6gak
g1aks
gak8ta.
ga6k1t
gak8te
g2a3kv2
ga1la
g8a4la.
galei5
g2a1le
ga7len
gal4l6e3s4
ga2l1l
gal1le
4gal1li
5ga7lo
ga4ma.
ga1ma
4ga2m1b
ga5mer
ga1me
gamm2e6l5
ga2m1m4
gam1me
6g4and.
ga6nd
6gan5den
gan1de
9ga1ne
gan5g6en
gan1g4e
ga6ng
gan4g5j
4ga2n5k
2g1a4n1l
4ga4nn
4gansa
ga6ns
4g1an1sv2
4g5antre
ga6n1t
gantr4
ga6pa.
ga1pa
ga4pe.
ga1pe
ga4p5l6
ga2p3s2
g8a6p7u
9gar.
ga6raf
ga1ra
ga6r5ak
2g1a2r1b8
5g2arbr8
5g2ar1by
gar4dek
ga4rd
gar1de
7ga1re.
ga1re
4g1a2r2ea
gar5es
gares6ke.
ga2resk
gare2s5ke
g2a1r2i
4gar3ki
ga2r1k
gar3ne
g2a2r2n
gar6ta.
ga6r1t
g5ar3te
gar8te.
g3ar1ti
gart5s6la
gar4ts
garts1l4
gar4un
ga4ryl
ga1ry
ga4sc
ga5s2i
ga2s8ka.
ga1ska
gas8ke.
ga2s1ke
ga6sk2i
4gasp2
gas6s4el
ga4s1s
gas1se
gas5sen
gas7ser
gass5e6tt
gas2s3l4
5gast
gas5te
ga5sto
gas7t6ra
gastr4
gas9tri
g2at
ga4te.
ga1te
ga5te6ns
g4aten
ga2t4e5s
g4a3ti
ga1to
ga3tr4
gat6tap
ga6tt
gat1ta
gau5la.
gau1la
9gav.
2g1a6v1d
6ga4vei
g2a1ve
ga4ve1ri
4g1a2v1g2
g5a6v3k6
2g1a8vs
2g1a2v1t
4ga2v1v
2gaw
4ga1ø
2g1b
gba4ne.
g1b4a
gba1ne
g6b6yk
g1by
2g1c
8g1d
gd5a2l1t
g1da
g6d5au
g2d2e3a
g1de
g2d2e7b2
g3de1b4a
g4de1di
gd2e5lo
g2dem
g4de6nd
g2de5o6
g4d2e1ra
gd2er
g4de5re
g6d2ero
g2de1s
g2det
g3d2et.
gdevi8sa
g2de1v
g2d5op
g1do
gd1or
gdy4d
g1dy
g6d1øy
g1dø
1ge
2g2e1a
geak8ta
ge1a2k
g6ea6k1t
geak6te.
geak1te
gea7ren
ge1ar
gea2re
ge4a2r1k
2g2e1b2
6ge1b4a
4ge5be
3gebri
gebr8
4gebrå
3gebyr
ge1by
ged4
4gedan
ge1da
6gedel
ge1de
ge5d6ia5
ge1di
9ged2om.
ge1do
7ged2o1ma
9ged2o1me
7gedom1me
ged2o2m1m4
6gedo6ms
4ged2os
2ge1dr4
2ge1dy
2ge1dø
2ge3e2
geen8de.
gee4n
gee6nd
geen1de
2ge1f
g5e8ft
3ge3fæ1
2ge1g2
g5e4g1g
g3e2g1n
2ge1h
gehø8ve.
gehø1ve
2gei
g1e2i7e
g4e1in
g2e2i4r3
gei9re
gei8s7p2
g2eis
gei6st5
3geit
gei1t3a
gei2t3o
gei4t3r4
2ge1j
2gek2
ge9kl4
g4e3k1n2
g1eks
6ge6k1t
5gel.
ge4lar
g2e1la
4gelau
ge6le.
ge1le
ge7l2ea
4g4eled
4gelei
gelei5er
gele2i7e
4gelek
4g3e2lem
ge5len.
g4elen
ge5le6ns3
5gelet
gel5e6tt
3g2e1li
4gelid
6geli4g1g
ge3l2ig
4gelit
6geliv
2g2e1lo
ge5lov
7ge8l1s4
gel3se
gel5si
gel6s1k7l4
ge2l5t4
4gelu
ge5lun
gel7ve
ge8lv
4g4e1ly
2g2e1læ1
2g2e1lø
2g2e1lå
5g4em.
2ge1ma
9gema.
2ge1mi
3ge4m3l
4g5e2m1n
gem6na
2ge1mo
9ge2m1r6
3ge6ms
3gem4s5t8
ge3mu
2ge5my
2g6e1mø
2ge1må
g2en
3gen.
g2e2n1a
ge7n6am
gend3s6t4
ge6nd
gen8ds
ge3n2ea
ge1ne
4g4enem
gen5e2r1f
ge5nes
gene4t
ge4n5e3ti
4g4ene1v
gene5ve.
gen8ga.
gen1g6a
ge6ng
gen5g4r4
ge4ni6n
ge1ni
6geniv
ge2n5k
genle6ge.
ge4n1l
gen5le
1gen3le1ge
ge4n3n
gens5l4
ge6ns
gen6sun
gen1su
gen5tr4
ge6n1t
4genum
g2e1nu
4ge1ny
4g4e7næ1
4ge1nø
2ge3o6
5ge2og1
3ge2ol
ge2o1me5
geo5met4
ge5on
6geo2p
ge9o1pe
2g2e1p
g2er
3ger.
ge5ra.
g2e1ra
ge6ral
ge4r5a6n1t
ge9ras
ger5di
ge4rd
4gered
ge1re
4gere1f
4gereg
4gerek
ge6re1ne
4ger2e1p
6ger2e3se
4g6ere6tt
gere4t
ge2r4i5d
ge1ri
4ger2ik
geri8k2a
6geri1ke
ge4rim
ge4ris
ge4rit
ge4riv
gerle9g
ge2r1l
ger1le
g9er8ma.
ge2r1m
ger1ma
g2e2r3n
ger1ne6
4ge3r2oa
g2ero
4ger2om.
ge1rom
4ger2o2m1m4
6ge5r2op.
ger1op
4gero1pe
g2e2r5p
ger4s5af
g6er1sa
ge4rs
5ge5r2ud3
g2eru
ge5rup
ge2r5v
gerø6re.
g2e1rø
ge1rør
gerø1re
ge4r3ø4v
4g2e1rå
g6es
5g2es.
2ge1sa
2g2e1se
5ges2en.
ge3s2en
5ges2e6ns
4ge1si
4ge5sja
ge1sj
4gesj2e
2ge1sk
ge1sl4
ge1slu7
ges4lø
4g4e1so
4ges1pe
ge1s2p2
ge4spr6
ge5spra
2ge1st
3ge6st.
gesta7b6l2
5ge6s5te.
ges1te
ges6t2e2k1k
ges4tek
ges8ti.
ges1ti
ge9s8t5rid
gestr4
gestr2i8de.
gestri1de
6g8e1stå
2ge1su
ges5vik.
gesv2
gesv2ik
2g2e3sy
6ge1sæ
2ge1sø
2ge1så
geså5re
geså2r
5get.
ge5tak
ge1ta
geta6le.
ge3tal
ge3t2a5le
6get4at.
g1e2tat
6geta1te
4g2e3te
5gete.
2g2e1ti
2g2e1tj
2g2e1to
2g2e1tr4
5ge4t5s1
2g2e1tu
2g2e1ty
get4y8e
2ge1tø
2ge1u
g2e1v
2ge7v8a
2geve
4ge1v2ir
gevi6sa
4g5e2v1n
gev6ne.
gev1ne
4ge5vo
5gevå
2gey1
4ge1æ2
2ge1ø4
2ge1å6
1gé
2g1f
gfe2l
g1fe
4g1g
g6g5al
g1ga
gg5ask
g2g1av
g4ge1di
g1ge
gged4
g2g1ei
g4g2e1la
g4ge1le
g2gem
g4g2e1nu
gg2en
g6gerei
gg2er
gge1re
gge4rin
gge1ri
g4g2e1rø
gge8s9b4
gg6es
gge1s5l4
g4ges1m
g6g4e1so
g4ge1s2p2
g6ges6t4io
g2ge1st
gges1ti
g6ge5sv2
g2g2e1v
gg2i1e6
g1gi
g4g2if
g4g5i4m
gg4j2e
g1gj
ggje5s
g2gl
g7glu
g4g1n
g2g1o2
g3go.
g5g2os
gg3rad
g1gr4
ggr6a6d2a
gg5ra5t
gg8re.
gg3red
g4g7rek
gg5s4par
g8gs1
ggsp2
ggs1pa
gg3sto
gg4sy
gg3sø
g7g8ud
g1gu
g4g5u4r
2g1h
ghe8n
ght5e1ne
g4ht
gh1te
gh4to
ghæ8
1gi
3gi.
4giak
gi1ar
4gi1a2r1b8
6gi1av
2gi1b2
gi8c
2gid
gi3de
g2i1e
4giek
3gi1en
gi9e6nd
5gier
4gi1fa
g2if
2gi1fo
4gifr2
4gi1fu
gi6ga.
g2ig
gi1ga
2gi1gr4
2gi3h
2gi1i
5gi1k2a
g2ik
3gi1ke
4gi1ki
3gi2kk
gi5k4r6
4gi1ku
gi5le
4gi5me4s1s
gi1me
gi4m2e1tr4
gim8et
4gi1mi
4gi3mø2
2g1i6nd
6g5inge1ni
gi6ng
gin3g2en
gin1ge
gi4nin
gi1ni
2g1i4nn
gin5n5u4
4g2i1no
4g1i6n1s
4g1i6n1t
2g1i6n1v
4gi5om
g4io
2gi1op
gio4r
2gip
gip5si
gi2ps
6gi1ra
g2ir
gi3re
gi4re.
giro3
gi6rob
5gis.
2gi1sa
gi3se
4gis2el
4gisen
5gisk
6gi1ska
gi6s8lu
gis1l4
gis4lø
4giso
4gis1p2
gi3s4pa
gi5s4pr6
gist2e6ru
gi2s1te
gis5ti
gist5ra
gi2str4
gi5stré
gi6st5rer
4gistu
6gi1sty
gi5ta
4gi5te
gi2t4e4s
gi2t9r4
git5te
gi6tt
2gi1u
g4i7va
4gi5val
4gi1vo
4gi1vu
1gj
2g1jak
g5j2a2r2n
2g1jaz
g2je5f
gj2e
4g3jeg
gje8l1s4
g2jel
gje2n
gjen1op2p3s4
g4j2e1no
gje2n1op
gjen1o2pp
gje6n5s8
6gje6n1t
3gjer
gje7sk
g2je4s3p2
gje8v9ak
g4jev6a
gje1v
2g1job
gj2o
2g1ju
7g6jut
gjø9de
gj2ø
2g3k2
gkly9
gkl4
gl2a
6g1la.
gl6a3de
4glag
gl4a4ga
gla8ge.
gla1ge
g3la6nd
g1lar
g4l5a6r1t
gla2r7v
1g2las
7gla5se
g6la4s1s
6gl6ast
g5la6st.
3glat
g5lau
g1l6e
gle6d2ero
gle5d2er
gle1de
g2le1f
g3le3ge
gleg8ga
gle4g1g
6glei
glei7er.
gle2i7e
g2lek
g7lek.
g5le1k6e
g2lel
3g2le2m1t
4glen
g9len.
g9lene.
gle1ne
g9lenes
g9le6ns
gle9p2l6
g2l2e1p
4gler
gl2e4ra
glere8de.
gl8e1re
glere1de
g9l6es.
gle6se.
gl2e1se
g4le5sk
g6le7s1m
4glet
g2le1v
1g2lid
g1li
gli4del
gli1de
gli7e6n
gl2ie
gli8er
g3l2ig1
3g2lim
gl6i9me
4gl4io
gli6tt4
6g7liv
4g2lj
gl7ja
g2l5l
g4lo.
g2l2oa
5g2lob
5g6l2o1me
1g4lor
glo3ria7
glo1ri
glori6an
glori4e7ne
glor2ie6
glori1e4n
g4l2os
glo5s1te
g2lost
6g1lov
g8l1s4
5glug
g5luk
6glun
gl5u6ng
glun8ge.
glun1ge
gl5ut
g5lyd
3gly1f
gly4se.
gl2ys
gly1s4e
g2løg1
g1lø
glø8pa
2gløs
g6l7øy.
5g4løym
2g1m
g4m2e3te
g1me
g4m2e1tr4
g5mé
gmi1ni6
g1mi
g3m6o4e
g1mo
g3mu
gmu8le.
gmu1le
gmå6la
g1må
gm6ål
g1n
g2n1ak
g1na
gna5lem
gn2a1le
gna4lo
g2nav
g6n1d
gn1dø6
gnd2ør5
g1n4e
g2ne3e2
g4n2e1lo
g6n2ero
g6n2e1rø
gne1s4
gne8se.
g4n2e1se
g4nesk
g4nest
g5net
g6n2e1t4r4
g2ne1v
g4ni1b2
g1ni
g4nid
g6n2ik
gni6ng4
gnin8g6s5
gni4s
g6ni5sk
gni6st
gni3st9r4
g2n5k4
g2nom
g1no
g2n5o6p
gn7o6v
g2n5r2
g6n1s
gn4skr6
gn6s1m
gn4som
gns4pr6
gnsp2
gn4s1ti
gn2s1v2
g6n5t4
gnu5re
g1nu
gnæ6re
g1næ1
g2nå
1go
7go3a6n
g2oa
go1ar
7goar.
2gob
go1b4e5
go4da
g2o1d
go5dal
god5ar
2go6d1d
go4de.
go1d4e
gods9t4
go8ds
2gof
go9ga
g2og
go5ge.
go1ge
go9g8r4
6goi
2gok
gok4se.
gok1s4e
g2o3le
gol6fa
go4l1f
g2o5lo
gol5va
go8lv
gol6var
go4lå
2gom
g7o6ma
6gome4t3ris
g2o1me
go5met
gom2e1tr4
7g2o2m1m4
go4n5a6nd
g2on
go1na
go9ne.
go1ne
go7ni
go5nok
go1no
go6n9s
2g1op
3g6or4a5
go5ra.
go7r2a1e2
go7ras
2go4rd
gor6da
g8o1re
gor2e8a7
2go2r1g
g4o1ri
gor2i5e6
g5o2r1v
gor8v4a
g2o5rø
gos3p2
g2os
4gost
6go1to
g2ot
go1t6r4
4gov
go8ve.
go8vi.
g2o1vi
2g3p6
1gr4
8gr.
4grab
6gr8ac
gra5ce
9gra8d2s3
gra2f5f
g6ra2f5t6
gra4m5
gr4an8d3s4
gra6nd
gran5to
gra6n1t
grant4r4
gra9se.
gra1se
gra9set
gra6sk
gras3t
gra5t
gra8te.
gr6a1te
grati4s
gr4a1ti
gra4u
gra4v3ak
g2rav
gra1va
gr3a7vis
gra1vi
gravta8
g4r1a2v1t
2gr2ea
4g5re8ds
gre4e4n
g2re3e2
2gre1f
g7re1fe
g4r2e1ga
grei1e5n
g2re2i7e
grei6e1ne
6g3r2eis
4grek
2g5rel
g5re6n1t
4g6r5e6pi
gr2e1p
gre2p4s3
g7r2e3se
gres6sak
g2re4s1s
gres7s6e6n1t
gres1se
6g5rest
4gre6tt
4g5ri.
g2ri1b3
4grid
4gr2if
4g1r2ig
gri5s2e1te
gri1se
4grit
gro9ar
gr2oa
g4ro7i
gr2o2m5m4
g4r2on
gro5sk
gr2os
gro3ve
4g5rui
4g3rul
gru6n7g
5g4rup
gr4u7sa
gr4us
grus5t
2g1rut
2gryd
4g5ryg
6g5ry2t1m
grø5de.
grø1de
6g5rør
4g5røv
grø5ve
4grøy
g2r7øy.
6gr6å1d
grå6da
gråk4
grå7n
grå5te.
grå1te
8gs1
gsa2
gs5ake4rs
g3s4a3ke
g7s4al.
g7s2a3le
g5s2alg.
g1sa2lg
g5s4al1ge
g1s2am3
gs3a2m1b
gs7a2m1n
g3s6a2nk
g5s4a4rd
g2sas
g7s6ast
gsbø6n
g8s9b4
gs1b4ø
g1s2ce
g2s5e1f
g1se
g2seg
g2sei
g2sek
g5s2e2k1r6
g7se1k2v2
gse9la.
gs2el
g2s2e1la
gsel4s5a
g3se8l1s
gsel4st
g2sem
gs6en3de.
gse6nd
gsen1de
gsen6ke.
gse2nk
gsen1ke
g5s2e6ns
g7s6e6n1t
g2ser
g5s2er.
g3s2e1ri
gse4st
gse4t
g3s2e1te
g2s3e3ti
g9s4e4ts1
gs4e6tt
g2se2v
gs1fø2
g2s1f
g2si
g5s4id2er
gsi1de
gs5is
gsi2v
gs4jar
g1sj
g3s2je1f
gsj2e
g5sji
g2sj2o
g5s2jå
g2s4ka.
g1ska
g5s2kad
gs4kal
g5ska2l1l
g6s1kam
g4skan
g4ska1pa
g5ska5pe
g4ska1pi
gs7kav
g5s4ki2l1t
g1ski
g7s4kj2
g7s6kjæ
g3s2k2ot
g5skren
gskr6
g7s6kug
g6skv2
gsl4a6ga
gsl4
gsle6ge.
gs1le
gs3le1ge
gs6le1ri
g2sler
g4sluk
g1slu
gs4lun
gsl9ut.
gs4lut
g5s4lyn
gs1ly
gs5med
gs1m
gs1me
g9s6me6r1t
g7s4nel
gs1n
gs1ne
g5s2og
gs9o1pe
g2s1o2p
g5s2pal
gsp2
gs1pa
g3s2p6el
gs1pe
g1s2pi
gs5p2ik
g3spil
gs5pi1le
g6s5p2o4rs
gs1p6o
g3s2pur
gs1pu
g4s3s4
gsse4e2
gs1se
gs5tak
g9s8ta6nd
g5s4ta6ng
g3s8ta1sj
g1stas
g7s4tat
gstatsrå7
gst2a4t2s1
gstat8s9r
gs1t6e
g2s5te.
g5s2te4e4
gs4te2l1l
gs5te1ma
gs2tem
gste6ma.
g7ste2m1t
gs4te1re
g5s7t6e2r1f
g5s4te2r1k
g5s4t2e2rn
g5s4te4rs
g5s4te2r7v
gst7ev3nen
g5ste2v1n
gs2te1v
gstev1ne
g5s4ti.
gs1ti
g3s4t2i1e2
g3s2t2if
g3s4t2ig
g4s5ti1å4
g5s2to.
gs4tol
g5st8ol.
g7st4o1la
g5st2o1le
gs5to2l1l
g4s5trap
gstr4
g5strau
gs4t5r2e3a
g5stre1de
gs4tred
gs4t5rei
g5s4tre1ke
gs6t1ret
gstr2i8de.
g9s8t4rid
gstri1de
g5s4t4rof
g3s2trø
g5s6trå
g5s2tue
g2s4t5ut
gstyr8ka
g1sty
g3s4tyr
gsty2r1k
gs4t2ør
g1stø
g5s4tå
gsu2
g5s2u5g
gs4v4a1ne
gsv2
gs5v2ik
gs7væ
gsy2d
g1sy
g7s4ym
gsy6na
gs2yn
g2sy6t
g2sø
gsø6ki
g7s6øt
gså4
2g1t
g3ta
gt4a8ka.
gta1ka
gta8ke.
gt2a1ke
gt8a6la.
gta4le.
g3t2a1le
g9t6e
gte6ke.
gte1ke
gte6ma.
g2tem
gte1ma
g3ti
gt2i8de.
g3tid
gti1de
g4t5if
gt6re.
gtr4
gtrå8d5s6
gtr6å1d
gt7s1v2
g4ts
g6t5t
gtu8en.
gtu1e2n
gtvek8
g2t1v
gt4y8e
g2t9y2t
1gu
4g5u4b4å
gu2di
g2ud
guds3t4
gu8ds
gu4el
6gug
g5u4g6l
gui4d
guid5ar
gu2i1da
gu4le.
gu1le
4g5u6l1k
gul8ke.
gul1k4e
gul4la
gu2l1l
gu1l5o
gu1l7ø
7gum
gu6n4g
gu2r2g
gu4ri
gur8na
gu2rn
gur6ne.
gur1ne
gu5rua
gu3rue
gu4st
gu1ta
2g1u6t1b2
4g1u2t3d
g5u2te.
gu1te
6g1u2t3g2
g7u6t3l6
4gu8t1n
2g1u4t1s4
gut4tak
gu6tt
gut1ta
gut4t4es
gut1te
4gut1tr4
2g1v
gva4ke.
gv2a1ke
gva8la
gved5li6
gve2d1
gve4d1l4
gvi8ta
6g1w
1gy
g9y8a
gy4da.
gy1da
7gym
gy3ne
gyr6
gy8sa.
g2ys
gy1sa
gy6se.
gy1s4e
gy4te.
gy1te
gy4ve.
gy1v
g5æt
4g5øk
gø4r3s
2g3øv
gøy9a
gøye6r
gø4y1e
gøy5n
1gå
gå6as
gå1a
gå6en.
g4å1e
gåe2n
gå4er.
gå3er
4g5å4k
4g6åm
4g3ån
gån8da
gå6nd
2g1å4p
2g3åre
gå2s2
g4å3st
gå9ven
gå1v
gå4v4et
h2a
ha4a
ha1b4a9
4h2a1e2
ha2el
4ha1f4i
ha2g
h4a3ga
ha4ga.
ha3g2en
ha1ge
hai1
ha7i8s1m
ha5ka
ha4ke.
h2a1ke
ha5ken
ha7k2e1ra
ha4ke5s
ha1la
h8a6la.
hal8d4s7
ha6ld
ha4le.
h2a1le
ha4le5v
hal6lei
ha2l1l
hal1le
hal6lø
hal4s3k
h2a8l1s
hal4so
hal4s3t
ha5lu
ha8l4v5
ha5ma
ha8me.
ha1me
ham6nest
ha2m1n
ham1ne
ha5mo
ham4st6
ha6m1s
ham4s4t7r4
ha5na
han6d5r4
ha6nd
hand7skr6
h4an8ds
han1d5ø
ha4ne.
ha1ne
ha1ni1
hanis4
ha4n2n3
han4ne
han6nel
han5n4en
han5n4o5
han3se
ha6ns
han4sk
ha4pe
ha4re.
ha1re
ha5rei
ha4rel
ha3rem
ha4res
ha2r5k
ha5rov
har7se
h2a4rs
har5tre
h6art2r4
ha6r1t
har4tri
ha4sj
hasj5e
ha2s1l4
ha2s6p7l6
hasp2
has5v2
ha3tar
h4ata1
ha4te.
ha1te
hat6le
ha6t3l6
h4au
hau5ke.
hau1ke
hau6st
hau4t5r4
ha1v4a
ha4va.
ha5van5
ha4ve.
h2a1ve
ha4veg
ha4vei
ha2v4e3s
ha1vi
ha4vo
hav4sl4
ha8vs
h2a6vu6
hav1ø
4h4a1vå
2h1b
hba4ne.
h1b4a
hba1ne
4hc
2he.
h2e2a
he7a6ns
he1an
heat4r4
he6b5n
h2e1b2
he4de.
he1de
hede4r6s5
hed2er
he2d9r4
hef9ta
he1f
he8ft
hef7t4es
hef1te
he3ge
he4ge.
he2g3r4
he2i
h2e8ia3
hei5e2n
he2i7e
3h4eim
he3i6n1s
hei4sk
h2eis
hei4t4s
he2k
hek4sek
hek1se
hek6serin
heks2e1ri
h2e1l1a
he4la.
he4le.
he1le
he4l5ei4
hel6le1su
hel3l6e4s
he2l1l
hel1le
h2e1l3o
he8l2s2
hel3sa
hel4se7
hel3se6s4
hel3sk
hel5sp2
hel5s1te
hel3s1v2
hel4t4es
he2l1t
hel1te
hel9ve
he8lv
he4mak
he1ma
hem5ne
he2m1n
hem3s8k
he6ms
henfø5re
he4n1f
hen1fø
henf8ør
h2e1n5o6
he6n1s2
hen5se
her6a9d2a
h2e1ra
he5re4t
he1re
he4ri.
he1ri
her5j
her3le9ge
he2r1l
her1le
herli9ga
her3l4i
her3l2ig
4h2e2r5n
h2e1ro
he4ro.
he3r2o9a
h2e2r7p
her6rei
h2e2r1r
herr6e
her6re1si
her6ret
her6s4ka.
her1sk
her3ska
he4rs
her8s7ka1re
her2sk2ar
h4er6sv2
her5un4
h2eru
h2e2r3ø
he4se.
h2e1se
he2s2p2
hes7pa
hes5p6el
hes1pe
hes5pen
hes9per
heste5ri
hes1te
he2s4t4e3s4
he6stø
het2
he4te.
h2e1te
h2e5t6i
het4s3a4
he4ts1
het4s5p2
he6t3t
he6va.
he1v
he6v7a2r1m
he4ve.
hev9na.
hev2na
he2v1n
2hf
2hh
h5hu
hi5a1o
hi4ba2k
hi1b2
hi1b4a
hi1e4n
h2ie
hi2et
h2i4f1
hi2ff2
hif9r2
hi6ge.
h2ig
hi1ge
hi1k7e
h2ik
himm2e6l5o
hi2m5m4
him1me
himm2el
hi6n7an
h6i1na
hin4nes
hi4nn
hin1ne
hi6n1s4
hin2s9ke
h4insk
hi4pl6
hi2p3p
hi1ro
h2ir
hi2s1
hi9se.
hi1se
hi3sen
hi5s4i
4hisk
hi1t7o
hi4t5r4
hit5ti
hi6tt
hi8va.
h4i1va
hi4ve.
hi1ve
hi8v1s
hjar4
hj2e4
hj8e1m7e
h2jem
hjor1te5
hj2o
hj2o6r1t
hju6l7
hju7l8e
2hl
h5lan
2h1m
hma8n
h1ma
2hn
h2na
h3ne6n
h1ne
h6n1s
h4o
ho5ar.
h2oa
hob6
ho6da
h2o1d
ho4de.
ho1d4e
ho5den
hod2e3r
ho5der.
ho5dy
ho2f4f3
hof4f3a4
hof4f3e4
hof5f6er
hof4fi
ho8gs6
h2og
hog6s4tr4
hog7s5t6ra
hoi5
h4o2la
ho5lag
ho5lan
ho4le.
h2o1le
ho4lin
h2o1li
ho2l5l
ho4lom
h2o1lo
ho8l1s4
ho4lu
hol7ut
ho4me.
h2o1me
ho4mo.
h2o1mo
ho4m2o1d
ho4m2os
ho5n6o
h2on
h2o4o
ho8pa.
h2o1pa
ho4pe.
ho1pe
ho8pi.
ho1pi
ho5ra
ho6ra.
h8or2e1
h4o1ro
2h2o6r1t
h2o4s
ho1s5a
ho5s2en
ho1se
ho5ser
ho5si5
hou2
ho1v
hove5re6
hove2r
ho4vé
2how1
h1p
2hr
h1ra
h1re
hr4i5ne
hri2s3
6h1s
h5s4e
4ht
h5ter
h1te
hte1re4
h1tr4
h6t5t
h2u4d3
hu4da
hu8d5s6
hudså9re
huds1å6
hudså2r
hu4er
hu6et.
hu1et
hu4ga
hu4ge.
hu1ge
hu8ja.
hu1j
hu6ka.
hu1k2a
hu2k8ra
hukr6
hu4la
hu4le.
hu1le
hu4leg
hu5les
hu4le1v
h2u5ma
hun6de1s
hu6nd
hun1de
hu4n2n3
hu1ru4
h4us1a
hu8sa.
hu5s6a6r
hu6s7a2r1r
husa7r8e
hu4se.
h2u1se
hu4s3ed
hu2s1i
hu2s5j
hu2s1k
hus7m
hu4s5s4
hus1t
hu8str4
hus8t9ran
hu6s4tre
hu6sty
hu6s5u6
hu2sø
hu4va
hu1v
hu4ve.
hu4v4e3s
hv4
hv2a5le
hvas5
hve2r
hv2e1r3a
hvi5l6i
h4v4ir
hvi4ts4
hvo2
hvor5
hvo1r5i6
h6y
hya3
hy2bl2
hy1b
hyd4
h4y2e
hye5ne.
hye2n
hy7e6ne
hye9nes
hyg5gel
hy4g1g
hyg1ge
hy6la.
hy1la
hylde4s7
hy6ld
hyl1de
hy4le.
hy1le
hyr4de1s
hy4rd
hyr1de
h2ys3
hy8sa
hy4se.
hy1s4e
hy2s1j
hys5t
hæ5g6
hær1fø9
h4æ2r1f
hæ4r3s6
h4ø4e2
høf5
hø2g1
høg3ri
hø2g2r4
høg7rø
høk6
hø6le.
hø1le
hø4na
hø4ne.
hø1ne
hø5rar
hø1ra
hø5ren
hø1re
hø5rer
hø4re1s2
hør6sp2
hø4rs
hø2s
høst7a
hø1st5ø4
hø1va
hø1ve
hø1vi
høy5a6
hø6y5k
høy7n
høy7rar
høy1ra
hø2y1s4
høyse6te.
høy1s4e
høys2e1te
h6å
hå7a
hå8le
hå6na.
hå1na
hånd5skr6
hå6nd
hån8ds
hå5nel
hå1ne
hå6pa.
hå1pa
hå4pe.
hå1p2e
hå4p5l6
hå1re
hå1ri
hå4r3s
hår7u
hå8va.
hå1v
håv4a
hå4ve.
ia9al
i1ab1l2
ia1b2o
i2a3de
i1ad1j
ia5d8r4
ia1g2
ia2ge
ia1in
ia1kr6
i1aks
iak8se.
iak1se
ia5ku
i1al.
ial1a
ia2l5ein
i2a1le
iale4t
ia2l5e3ti
ia4l5e6tt
i2a4l1f
ia2l3g
ia4lin
ia1li
i4a6l1k
ia2l3op
ia6lov
i5als.
i2a8l1s
ials4t
i3alt.
ia2l1t
ial1u
ia2lø
i1an.
i2a1na
ia4nal
ian5a6ld
i4a5nar
i7andr4
ia6nd
i5a4ne.
ia1ne
ia8nes
ia7net
i5a2nk
i1a4n3m
ia2no1
i1a6ns
ian3sa
ian3sl4
i1a6n1t
i2a7nø
ia2pa
i3a2pp
i1ar.
iar4do
ia4rd
iar8d5s4
iar4du
i2a1re
i5a2r2ea
ia5r6e8l
i5arn.
i2a2r2n
i9ar6ns
i7ar1ska
i2a4rs
i6a1si
i1as1m
ia4sp2
ia4s3s6
i3as1si
i1ast
i7a6st.
ia1t
ia5te
iat6r4
iaty6ra
ia1ty
i1a6tt
i7auk
i1av
i1b2
iba4ne.
i1b4a
iba1ne
ib5b2o
i2b1b
ib3b2u
i3be1re
i1be
ib4er
ib2e4ro
ibi5er.
i1b4i
ib2ie
ib7lar
ibl2
i2b3le
ib4leg
ib4le5s
i5b2o
ib6o4e
ib2o7n
ib4r8
ib3s2t6
i6b1s
i6b8t
ibya7
i1by
iby9ar.
8ican2arar
i1c4a
ic4anar
ica1na
icana1ra
ice5ne
i1ci
i2c6k1
ick7e3te
ick5e4t3
ic1ke
i1co
i5cy
2i1da
i5dal
i3das
i2d1av
id3del
i6d1d
id1de
iddel5u
id6d2e1p
2ide.
i1de
3ide2al
i2d2ea
i2d2e7b2
i6de1f
i6d6e5ge
i2deg
i7de1ki
i9d4el.
id3e8lv
2iden
i5de6nd
iden5sv2
id2e6ns
5i4dent2if
iden3ti
ide6n1t
5i4den2tit
ideo3v
ide3o6
i6derap
id2er
id2e1ra
i9de5re.
ide1re
i7deren
i9deres
iderl4a8ga
ide2r1l
iderle7g
ider1le
i7d2e2rn
i5de4rs
i6des1m
ide1s
id6gem
i2d1g4
id1ge
id4g6es
idi4en
i1di
id2ie
1i2d4io
i8dj
id7jer
idj2e
id9n6a
i8d1n
1i4dol
i1do
id9ran
idr4
id5reg
4i3dre1v
2idri
i8d2s1
id5s2am
id1se4
id6s2el
id5sim
id2s1i
ids5l4
idsl4a6ga
ids3t4
id4s5tu
i4d2t1
i2d1un
i1du
i2dy
3i2dyl
i3dyr
i3dø
i6d7øy
id7å
2ie
i2e1a2
i2ed
i1e2ff
ie1f
ie4ge
i4e1go4
ie1i
iei6d
2i1e2i7e
i3e2ig
i5e4i1ni
ie1k8l4
i1eks
i2e5l6a
i2e1le
i5e2lem
ie4le1v
i7e6lim
i2e1li
i1e8l1s
i9e2l1t
ielø8pa
i2e1lø
i8e9ma
ie2m7b8
i8e3me
i1en
i2e7na
i2e6nd
ien6d2e1la
ien5d4e5l
ien1de
ie8né
i2e5ni
ie4n7n
i2e5no
i5e6ns
ien4sk
ien4s5v2
ien4t3r4
ie6n1t
i2e5nu
i6e1ny
ie4ran
i2e1ra
i4e4rd
ie6re.
ie1re
ie5reg
ie4r5e6ng
i6ere4t
i2e1ri
i4e4ril
ie4ris
ie4riv
i2e2r1l
i6ero
ie7ro.
ier4ra
i2e2r1r
i6er4sp2
ie4rs
i4er1s3v2
ie6r5t
ie3run
i2eru
ie2r5v
i2e1s
i6es.
ies4c
i2e3se
ie4s5s
ies4ti
i8es6v2
i1et
i2e1ta
i5e4tab1l2
i3e2tat
i2e9te
i4e5té
i8et4re
i2e1tr4
ie1u2
i6e7ve2d1
ie1v
2if
ife4s1
i1fe
if2fa
i2ff
if2i6e2
i1fi
ifj2ø8
i1fj
if3le
if2l2
if4les
ifo2r1m4
i1fo
if1re
ifr2
i8ft
if4tal
if1ta
if4te1re
if1te
ift4e5s
if4t2s
i3fø
2ig
i2g2a1e2
i1ga
i4g3a4nn
i7ga2r1k
ig4a6r1t
iga3ru
iga7te
ig2at
ig3a6tt
i2gav
i8g3d
i6g2e1b2
i1ge
i6ge1di
iged4
i6ged2o2m1m4
ige1do
i6gedo6ms5
i2gem
i3g2en
ig2e6no
i5ge4rs
ig2er
i4g2e1rø
i4ge1s2p2
ig6es
ig3e4ta
ig5e6tt
ig4ged4
i4g1g
ig1ge
igg6es4
ig4gra
ig1gr4
ig8g9s2
i6g1h
i2g1ia
i1gi
i5gi1b2
i4gim
igi2on4
ig4io
ig4je9v6a
i1gj
igj2e
igje1v
ig1l
ig6l6d
igli6se.
ig1li
igli1se
ig5l2oa
ig5ne1u
ig1n
ig1n4e
ig5no
i2gof6
i1go
igo5fr2
i2g1om
igo4no
ig2on
ig2ra
i1gr4
ig2re
i2g1rø
i6g3r6å1d
igrå5t
igs4al
i8gs1
igsa2
ig5s2el
ig1se
ig1s2j
ig5s4ka
ig3s4kr6
igs4mu
igs1m
ig3s4pa
igsp2
igst4
igs4ta
ig4s1t6e
ig5stek
ig7stel
ig5s2tem
igs4tra
igstr4
ig5s4va
igsv2
ig1un
i1gu
ig9ut
i3h
ihen3
iho7le.
ih4o
ih2o1le
i4huk
ihu9la
ihu5le.
ihu1le
i1i
i5in
i7is.
i6i1ta
i1j
2ik
i1k2a
i2kab
i5kabel
ika1be
i2kaf
i2kak
ika6n9d
i2ka1o
i4ka1po
i5kar.
i5ka1ra
ika5re
i4ka2rei
i6k4a1sa
i1kas
i6ka3sp2
i2ke.
i1ke
i2ked4
i2k9ei
i9keleg
ike1le
i5k2e1li
ike5lu
i5ken.
i4k2e1na
i5ke3ne
i9ke6n1s2
i3ker.
i4k2e1ra
i5ke1re.
ike1re
i3ke5ri
ik2e4r5o
i2ke1s2
ike3si
i6kesk
i5k4e1so
ike5su
i5ket.
i5ke4ts2
i1ki
ik4i9ne
i2k5i4nn
iki5st
i1kj2
ik4kaf
i2kk
ik1ka
ik6ka1na
ik4kap
ikk5a2r1v
ik4kas
ik4kat
ik6k5a6tt
ik6k7e6n1t
ik1ke
ik4k2eru
ik8kesk
ik4kest
ik3kj2ø
ikkj2
ik4kjøp
ik4kl4
ikk5la4g
ik2ko
ikk5o4rd
ik4kr6
ikk3re
ikk6s5v2
ikk7s
4ik2ku2
ikk5u6nd
ik1kun
ik2k1v2
ik6ky6
ik1l4
i5k2læ1
i1ko
i2k2oa
i9ko7ar
i2ko2b3
i4k2o1d
iko5d4e
i2ko3f
i4k2og
i4k2o1h6
i2kok
iko5na.
ik6on
iko1na
i2k2o1o
i5k6o4rd
i4k8o1r6e
i2kov
ik1r6
ik4rak
ik5rem
ik5ro1b4e
ik2ro
ikro5b4
ik3r2os
ik2ry
ik2sa
ik4sek
ik1se
iksmå8la
iks1må
iks1m
iksm6ål
ik7s2ot
ik1so
ik5s2p6el
iksp2
iks1pe
iks5ti
iks5to
ik8stu
ik4tav
i6k1t
ik4teg
ik1te
ik2t1r4
iktsl4a8ga
ik4ts
ikts1l4
i1ku
iku6le.
iku1le
i6k7u6t
ik1v2
ik4vin
i1ky
i3kø
i3kå
ikå8pa
i1la
i2l5adr4
i2l7af
i2l3ak
i2l3al
i4la1na
il6a6nd
i4la2r1k
i4l3a6r1t
il5a4s1s
il6as5t6
ila5t
i2l5av
il4dak
i6ld
il1da
il4de5k2o
il1de
il4d2e3te
il7dj
ild3re
ildr4
ildsfa9re
il8ds
ild2s1f
ilds1fa
ilds3far
ild3s4t4
ile1a9r
i1le
i2l2ea
ile8a2re
i3le7e2
i2le1f
ile4ge.
i3le1ge
i4l3eg1n
i2l1ei
i6le2ig
i2lek
i2lel
i4l2e1no
i4l2ero
ile1s
i4l2e1se
i4le5sk
i4lest
ile6t4ri
i2l2e1t2r4
ilet5te
ile6tt
i2l2e4tu
i1lé
ilfø5re
i4l1f
il1fø
ilf8ør
il5ge
i2lg
il1gl
i4lid
i1li
ili5e4rs
il2ie
ili9ga
i3l2ig
i5l2ik
i2l1im
i4l3i6nd
i7l4i1ne
i4li6n1s
i4l3i4r
ili5s6t2ik
ilis5ti
il3ja.
i2lj
il1j2e
il5j2e1se
il4je1s
il1j2o
il1ju
il1k4e3
i6l1k
il5ker
il4kes
il1k4o6s
il5ku
il4la1b
i2l1l
il6lam
illan8da
illa6nd
il6lap
il9lau
il4le3e2
il1le
ille2i9e
il6l7en3d2er
ille6nd
illen1de
il6lesk
ill6es
il4le1v
illi9ga
il9l2ig
il1li
il4lo5m1
il8l3s2
ill5s5kå
illsk4
il1læ4
illæ9re.
illæ1re
il4m5est
i2l1m
il1me
il2m5e4v
il4mi
il6m5s
i1l6o1e
i9l2o1lo9
i4lom
il5o2m1v
i5l2on
il3o2pp
i2lop
i4l1o4r
i5lo3so
il2os
ilo1t3u
il2ot
i4love
i1lov
ilo1w1
ilret4
i2l5r4
il4set8j2e
i8l1s
il1se
ils2e1tj
il3sl4
ilsla7
ils3le7ge
ils1le
ils4mu
il2s1m
ils2p2
ils2t
il5str4
il7su
il3s2v2
ilsva9ra
ilsva5re
ilta9la
i2l1t
il3tal
ilt5re6tt
ilt2r4
ilt1ret
il6t7å
ilu4h
i5luk
il7ul8
il3un
i5lur
i9lus
il1ut
i8l5v6
ilve8d1
ilve4r
i1ly
il1å8
i2lår6
8im.
4i1ma
i2mad
i4maen
im2a1e2
i2mag
i6m5a2kk
i4m2a1ko
im4a2l8n
i2mam
i9man
i2map
i5mar.
ima5s
i4ma1tr4
im6at
i8m1b2
i4me7e2
i1me
i2me1g
i2mek
i6me1lu
im2el
im5e4n1h2
im8et
i9met.
i4me1ta
i4m2e1ti
i6mey1
i8mé
imi9la
i1mi
imi1ni6
i7mj
im4le1v
i4m3l
im1le
im8l9u
i2m5m4
i6m2og
i1mo
imo9l
im5o4rd
imor8da
im7o6v
i8m1p2
5im3pe1ri
im1pe
imp4l6
1im1po
im2p9s
imp1se8
1im1pu
im4re1f
i2m1r6
im4rek
im4res
im9se
i6ms
im2s4k5l4
ims3kr6
im5s4me
im2s1m
im1s4t
im6s2ti
imta8la
i2m1t
im3ta
4i1mu
i3mø2
2in.
6i1na
i4nag
in5a4g1g
i5n6a2kk
i2nak
ina4let
in2a1le
i4na2m
in3a1me
in2an
i9n6a1ne
in5a4nn
i2nap
in5a2pp
i5n4ar.
i5n2a4r5s
i4nask
i4nasp2
i4n5a4s1s
ina4t5ak
in4a1ta1
i6nau
i2n7auk
i4n5a6v1l2
i4n5a2v1r8
ince2
in1c
in7d2er
i6nd
in1de
6ind2ig
in1di
in4d2og
in1do
ind9ra
indr4
in3dru
indr5ø
ind5sk
in8ds
ind3s1p2
ind5s4t4
5industr4
in1du
indu1s
indust4
ind7å
4i1ne
ine8a2re
i2n2ea
ine1ar
i2ned
ine1dy6
in5e4g1g
i2neg
in5eid
i2nei
in3e2i7e
i6ne1le
in2e4li
in7e6ng
i3nen
ine8pa
i2n2e1p
ine8pe.
ine5pe
i7ne1re.
ine1re
i5neren
i9ne5res
i4ne4r2ik
ine1ri
i7n2e2rn
i5ne6r1t
i5n2es.
i4nesk
ine1s8ka
ines8ke.
ine2s1k4e
ines4s2t
ine4s1s
ine5s4ti
i4ne1sø
i2ne1v
8i1né
3infek
i4n1f
in1fe
in4f2os4
in1fo
in4f2ot
in1fu9
4in1fy
in4g5a6ld
i6ng
in1ga
in4gav
in5gebj
in1ge
in2g2e1b2
in6gem
5inge1ni
in3g2en
in5ge1ri
in3g2er
in4g2eru
in3g2e7v
ing5je2n
in1gj
ingj2e
in4g2oa
in1go
in4g2os
in2gr4
ing5r2e1p
ing7ris
in8g2s1
ings5om
ingst8
ings5v2
6in1gu
in2g5ø
i7nia
i1ni
ini9ar.
i2ni1b2
i5n2ie
i4n2if
i2n2ig
i4nil
ini7m
i4n1i4nn
i2nip
i4ni1sa
ini3se
i4ni2ses
5initia
in4it
ini1t2i
i4ni1to
5injek
i4n1j
inj2e
2i2nk
in5kel
in1ke
in4k2ero
in4k3la
inkl4
in4kok
in3ko
i4n5l
inl4a8ga
2inn.
i4nn
in4nal
in1na
in4nem
in1ne
in4n2e1rø
in4ne3si
in4ne3s6t
4innet
in6n7et1te
inne6tt
innfa9s
in4n1f
inn1fa
1in6n3g2
5innhal
in4n1h2
innh2a
3innh4o
2in1ni4
in4ni.
in4n5o2m1
in1no
in5n6ova
inno1v
in6n1s4
6inns.
3innsa
inn5se
inn9sen
inn9s1te
innst6
inn7s3ve
innsv2
1in6n7t4
in4n5u4
4in1næ1
in2nø4
2i1no
in2o5a
i5noar
i4n2o1d
i2nok6
in7oks
i6n1s
4ins.
8insa
in9sa.
in7sal
in5sar
in5se.
in1se
in4sek
in3sen
inseri8e9ne
inser2i7e6
ins2e1ri
inseri1e4n
in3si
ins2is5
6in1sj
in4sja
in3sj2e
4insk
in9s8kas
in1ska
in5skat
inske4t
in2s1ke
in1s4ki
in3skj2
ins5kj4øt
inskj2ø
ins6kor
in2s4k3v2
ins6kø
insle7g
insl4
ins1le
ins8l2e1ga
in3slo
in3s2lø
in5s4ma
ins1m
in3s1o2p
1insp2
in4s1pa
in5s4pl6
ins4p6o
5instal
in8s5te.
ins1te
ins5ten.
i6ns9te6ns
3in3s4ti
in4s4tin
4in3s6to
in5stra
instr4
inst5rel
5in3stru
in6stå
in7sul
in1su
in2sv2
6in3sy
8int.
i6n1t
2in3ta
inta8la
4inte.
in1te
5in7teg
in8t7e1ge
in5t4er
in3t5e4s1s
int4es
in5te4t5
4in5ti
inti5me
int2r4
int6ran4
in5t4rer
in5tres
6i1nu
inu6i
i8n9ul8
in7ut.
i2nut
in9u8te
1i6n1v
6inve1v
iny4i
i1ny
iny4t
6i1nø
in7øk
i7n4øt
in3øv
4io
i2o1a4
i2ob
io4de1re
i2o1d
io1d4e
iod2er
i1o2ff
io5g1n
i2og
io1i
i1oks
iol4
i2o3le
iol7jen
i3o6lj
iolj2e
io3m1u
io4na6ns
i2on
io1na
io4na2r1r
io6n5er1s2t
io3ner
io1ne
ione4rs
io6n3g4
io4nin
io1ni
io4nu
i2o1pl6
i1o2pp
io5ra.
ior8da
io4rd
i8o1re
io4r2ie
io1ri
io4r5in
iorl4a8ga
i6o2r1l
io2s8k3v2
i2os
io1st
iota6le.
i2ot
io1ta
io3t2a1le
io3t4e
io4tra
io1tr4
i5pap
i1pa
ipa4ti.
ipa1t
ip4a1ti
i4pe3e6
i1pe
i4p2e1la9
ip6el
i7p2e6p
i1pi
ip2i1e
ip4i9ne
i4p5in8g2s1
i3pi6ng
i1pl6
i2p1le
ip4p2e1li
i2pp
ip1pe
ipp6el
ippe8l7s6
ipp6lan
ipp1l6
ipplæ8re.
ipplæ5re
ipp1læ1
ip2p1r6
ipp4s3t2
ip2p1s
ippsy8na
ipp1sy
ipps2yn
ipp4s1ø
ip7pun
ip2pu
i1pr6
i6pra
i6p5ru
ipru8te.
ipru5t8e
ips1a
i2ps
ip2s2e4l
ip1se
ips1fø5
ip2s1f
ip2si
ip2s1k
ipsle6ge.
ip2s1le
ipsl4
ips3le1ge
ips1t2
ip6tar
i2p1t
i5py7
i3pø
2ir
i1ra
i9r6a6nd
ir5a2r1k
i9rast
ira6t4r4
ir5de
i4rd
ir8d5s
i1re
i2r3e2i7e
ir6ek
i3re1po
ir2e1p
i3rer
ir2e6r5a
ire7st
i3ret
i8r2e1to
i2r7g
i1ri
iri4a
iri8ka.
ir2ik
iri1k2a
i4r3i6nd
i4r3i4nn
ir5inst
i2ri6n1s
iri9t
ir5ka.
i2r1k
ir4kat
ir5ker.
ir1ke
irke3s6
ir4kest
ir4k1le
irkl4
ir5kv2
irk5ø
ir4mag
i2r1m
ir1ma
ir4mal
ir4mast
ir9mé
i5r2oa
i1r6o1e
i4roi
i1rol
i2r5o2pp
ir2o1s
ir3r6e
i2r1r
ir7sko
i4rs
ir1s2p2
ir3s4t
i6r1t6
ir3ta
irti6g5r4
ir1ti
irt2ig
i1ru
i4r5u4k
ir4u8m
iru7sa.
ir4us
ir4usa
i1rø
i1rå
i4r7å6l
i1sa
i4saks
i4sa4n1b2
i4s3a6ng
is3a4nn
i4s3a6n1s
is3a6n1t
i7sas
is5a2u4d
3is1bry
i8s9b4
isbr8
is6cen
i1sce
isch5
is3co
i5scr8
i2sed
i1se
i4se3e4l
ise3e2
i4se5e4n
ise5e2rn
iseer1
i2s2e3g4
i2sei
is3e2i7e
i2s5e2ig
i3s2e5is
i2sek
i3se6k1t
i4sek1te
i6s7e6l1d
is2el
is4e5li
i2sem
is2e5ne
i4s2e1no
isen3si6
is2e6ns
i5s2er.
i5se4rs
i2ses
is5e4s1s
i4s2e1te
i4set8j2e
is2e1tj
i2se1u
8i1sé
isha7ne
is1h
ish2a
3ishav
is4h5in
ishø9ve
is5hø
isi6e1ne
i1si
isi1e4n
is2ie
i3s5i6ld
is2il
i4s3i6nd
isis5t
is2is
i4s7jun
i1sj
i2s9ka.
i1ska
is3kal
i2s3k2ar
isk9art.
i4s1ka6r1t
isk5ar3te
i8s1ke
is5ke.
is8ke3e2
is4kel
isk5e8l1s4
is2ke5s4
is3kj2e
iskj2
i3skj2o
iskl4a8ga
i2s1k2l4
i6skla
is6k5le
isk3lo
i3s2k2o5g
is7ko1gr4
is3kop
is3k2ot
is1kr6
is6k5rin
is1ku
is4kul
i5sku1la
i2s1kv2
is4kvi
is1l4
i6slu
is4l7ut
is6l9øy.
is1lø
i5smak
is1m
is1ma
is4med
is1me
is3m6o5e
is1mo
i6sm2us
is1mu
is5ne
is1n
is5nu
1isol
6i1s2o6ld
5is2o1mo
i2s5o2m1r6
is2o3pa
i2s1o2p
iso5pe
is2o7pr6
3is2ot
i2s1ov
is1p2
is4pan
is1pa
is5pane.
ispa5ne
is6pis
i1spi
i1s7pru
ispr6
is4ses
i4s1s
is1se
is5si
is5s2is5
is6s7kj2
is4sko
is7skr6
is5sky
is4s4kå
is5sok6
is7sto
iss2t
is6s1ve
issv2
is5svo
is9s4ær
is1sæ
is4søk
is1sø
i5stab
i4s3t2a9le
is5t8a1ne
i2s5tap
i4s5tar
ist5a2v1b2
is2tav
ist7avi
i2s1te
i2s5te.
is4tek
is4t5e6k1t
iste2l5l
iste6ma.
is2tem
iste1ma
ist6en
is5ten.
is5te1ne
is5t8er.
is5t2e2rn
is6t2e1rø
is6té
is4t2ik
is1ti
is5ti2l1b2
ist4il
i2s5tis
is3t6ok
is3tol
i2str4
i8s8t1re.
i4st5ren
i5st2rer
i6st5rer.
i4s2tres
i6st1ret
i2s3try
is3tus
6isu
is5u2f
is5ul
is1un
is1v2
is4vak
is7w
isy8na
i1sy
is2yn
isy5r
i2s5y2t
i2s1øk
i1sø
i4søy
is7åk
is3å2r
i1ta
ita4l3a
ita6l5e4rs
i3t2a1le
ita4lo
it6a4ly
i6ta6ng
i4t1an7s6v2
ita6ns
i1tau4
i4tav
i4t2ec
i1te
i2teg
ite8ke.
ite1ke
i7te5ky
i6t7e2lg
i4te1ni
i2te3o6
ite3re
i4t2ero
ite4r6s5
i4t2e5ru
i2t4e1s
ite6se.
it2e1se
i3te4t6s5
i1té
i1t2i
iti6er.
it2i1e2
i4ti2e1s3
i2t2if
i4ti1kam
it2ik
iti1k2a
i4ti3k1v2
iti5me
i2t4io
i2tip
i2t2i3r
i4ti1sa
i4tisen
iti3se
i4ti1si
i4ti1sj
i4tisko
i4ti3s1p2
i4tist
iti5str4
i2ti1u
itiv4
iti8vs5
it7j2aran
itja1ra
i2t7jer
itj2e
i2t7ji
itma6le.
i2t1m
it1ma
itm2a1le
itne4s4s
i8t1n
it1ne
i1to
it2o9a
i4tom
i4t5o4rd
it2o4s
itostra6
itostr4
i2to8v
i1tr4
i4tra.
i6tr2a1e2
it3rel
it3ren
it5rim
it4r4i8ma
it3rin
i5tris
it7r2o6s
it1sa
i4ts
it4s3ei
it1se
it3ser
it6s4es
it5sku
itsl4a8ga
its1l4
it1s1n
it6s1ti
it1st4
it3sun
it4tag
i6tt
it1ta
it6tak
it4tal
itta9la
it6ta4nn
it6tap
it4t5at
it5ted
it1te
it2t2e4l5o
it4te2nk
it3ten
itt4e5s4
i6tt5e6tt
it4tid
it1ti
it4t4il
it4tj
itt1o
it6tof
it7t2on
it4tr4
itt5skr6
it6ts
itt4sø
it4ty
it7t2y2s
i1tu
itu5e
itær1
i1tæ
i1tø
i5tå9
i1u
iu6a
iu2b
i5u6l
i5um.
ium4f5ar
iu2m1f
ium1fa
i2u2m1i
i4u2m5m4
i4u4mo
i6um1se
iu6ms1
i4um5s4t
i4umsu
iu2n
iu8p
i2ur
i2u5se
4i1va
i4vad
i6va1j
i4v3aks
i5val
iv8a6la.
i4va1na
i4v5a2nk
i4v1a2r1b8
iv2a4r5s6
i4v5a4s1s
iva6t3t
i1ve
i2veg
ive6ge
iv5eg6n
i4vei
iv7e2ig
i2vek
iv2e4ra
ive5ras
ive4r5d
i6vere4t
ive1re
i4vesk
iv4es
i4ves1ti
i6vesv2
i6ve1tø
iv4et
i2ve1v
i1vé
i1vi
iv5i6n2s
iv5is.
iv5i8s9b4
ivle6ge.
i6v1l2
iv9leg
iv1le
iv3le1ge
i2v3n
i1vo
i2v7om
i2v3r8
iv4sal
i8vs
iv2si
iv2sk
iv2sl4
iv2s2n
iv4so
ivs5v2
i2v3un
i1vu
i2v7y1
ivyr8ke.
ivy2r1k
ivyr1ke
i9væ
iv7å2p1n4
i1w2a
iw4i5
i5w2ie6
i5y
iyr8ke.
iy2r1k
iyr1ke
i5za.
i7zas
i4ze
i1zo
i1ø
i1ø4k
i1ør.
iø1r5i
iø4r5s
i4øs
iø3se
i1øy
i1å4
iårs7l4
iå4r2s
iåt4te.
iå6t1t
iåt1te
ja1ak
ja9ar
j6a4da
1j2a1e2
ja7en
5jag.
ja4ga.
j4a1ga
ja7gar
ja4ge.
ja1ge
ja1g6r4
ja4h2a
ja1h
ja4h4v4
ja4j
jak4kel
ja2kk
jak1ke
jak7ke1le
jak4ko
jakk7s4
ja1k8r6
ja8kre
ja6k1t
jak1te
ja7ku
ja3lo
ja1lu
ja2m7b
6jam1b4i
jamhø8ve.
ja2m1h
jamhø1ve
5ja2m1m4
ja6m1s2
jan4gr4
ja6ng
j5a4n1l
ja6ra.
ja1ra
ja6r5ap
ja9rek
ja1re
jar6n3s4
j2a2r2n
jar5ta
ja6r1t
jar5te.
jar3te
jar5tet.
jar7ti
6jarø
ja6rå
ja7se
ja8sk2i
ja5s1ti
j4a5ta1
jau4e5re
j4a6ue
jau7er
ja9vi
1jaz
j1b
2jd
j2e
2j2e1a2
je5a2k
2j2e1b2
je2b9b
je4d2e7b2
je1de
6je4dek
4je1dr4
je2e1p1
je3e2
6jee1v
2je1f
je4f3et
je1fe
j4e3fj
jef5lag
j2ef2l2
je4f3re
j8efr2
jef4sa
je2f1s
jef6s5i2
jef3t4r4
je8ft
je5ge
jeg5ge
je4g1g
6jegl
jeg6les
jeg1l6e
4j4e1go
4je1g2r4
4je1gy
2je1h
je1i
j2e5i8s
4je1j
2j4e1ka
jek6k2eru
j2e2kk
jek1ke
4je1kl4
je4k1li
2je1k2o
jekt3a
je6k1t
jek4tan
jek6t4es
jek1te
jek2t5e1v
jek4t5in
jek1ti
jek4t3r4
jekt1s4t4
jek4ts
4je1kø
2jel
je6le1g2r4
je1le
jelei7er
jele2i7e
je7le1le
je2lel
je4les
jel7ge
je2lg
jel6lag
je2l1l
jell5a6ng
jel5len
jel1le
jel4lo
je2l1t3
jel4t2r4
jel9ut
jel7va.
je8lv
jel1va
2jem
je4mi1a
je1mi
je4mit
je8m1p6
jem5p3l6
jem5se
je6ms
jem4s2ti
jem1st8
2j2e7na
je6n1d
4j1endr4
je3ne.
je1ne
je7ne8s
4jenet
jen5ged4
je6ng
jen1ge
jen8g5s4
2je1ni
je2n3k
4je4n1l
4j2e1no
4j2e1nu
je2n1y4
4j4e7næ1
2je5o6
2j2e1p
je3raf
j2e1ra
je4r5a6n1t
jer4d2e1p
jer1d2e
je4rd
jer8d6s
je3r6e
4jered
6jereg
6jerei
6jere4nn
je5r2ik
je1ri
je6rim
je6ri1næ1
jer4kes
j8er1ke
je2r1k
jer6k5l4
jer4kv2
jer1le7
je2r1l
jer6mu
je2r1m
jer6n5as
j2e2rn
jer3na
jern5sl4
jer6ns
je6r2on
j2ero
j6er4sp2
je4rs
j4er4t5s
je6r1t
4j2e1ru
6j2e1rå
5je2s1f
5jes1h
j8es4kil
je1ski
2je3s2p2
je2s4t2ea
jes1te
je6stim
jes1ti
je3str4
5je2s5u4t1
je1su
je5sve
jesv2
je4t3ag
je1ta
jeta6ka
je3tak
je9t8ar.
je7t6a1ra
je9t8a1re
je4t3ru
j2e1tr4
je4ts2
jet4t5an
je6tt
jet1ta
jet6te2r1m
jet1te
jett3o
jet6t5s6
j2e4t3v
jet4y8e
j2e1ty
2je1u6
4jev6a
je1v
je7v5a2r1k
je6vas
4jeve
jevi4s
jev7na.
jev2na
je2v1n
jev7ne
6je1ø4
j1f
4jg
j1h
4ji.
ji2b5b
ji1b2
j2i7e6
6j2ik
2j3i4n1f
ji7ro
j2ir
ji6sj
4ji1ø
2j1k
j4kap
6j1l
6j1m
6j1n
6jn.
j5ni
j2o
1job
5j4oc
jo4da.
j2o1d
jo1da
jo6dat
jo6dis
jo1di
jo4f2l2
jok9ker
j4ok1ke
jo2kk
jokk9o8
jo2l5t6
j3o2m1r6
j2on2
jo5ne
jo4n7n
jo6ns1
jons5a
jon1se4
jons3p2
jons5t
jon6s1ti
jon4str4
jon1su4
4jop
jo5ra
jor6dek
jo4rd
jor1de
jor6d7e2r3v
jord2er
jor6d7is.
jordi4s
jor1di
jor4d3o
jord3r4
jor8d5s
jords4l4
jor6du
j8o1re
jo4r2ie
jo1ri
jort6a
j2o6r1t
jor5tet
jor1te
jo3r4u
2j2os
jo3se
jo4ses
jo5s2t4ei
jos1te
1jou
jou4r5
j1p
4j1r
2j3s2
j1t
ju8a1re
1jub
ju1b4i3
ju9b2o
ju3de
j2ud
ju2do
ju7d6o1e
jue7ni
ju1e2n
ju4e5re
ju1er
jue9s
jug9l6e
ju4gl
5ju1go1
4ju2k
ju3k2a
ju3ke.
ju1ke
ju3ken
juk9sar
j2uks
juk2sa
ju2l
ju4la.
ju1la
ju5lar
ju5las
ju8l9ei
ju1le
ju4li.
ju3l4i
ju6lid
ju4l2ik
ju2l7i4nn
ju2l1l6
1ju6ng
jun7ge
j6u6n6i
8jup
j6u2p7s
ju6p5å6
ju4ra
ju7ras
3juris
ju1ri
ju9ro
ju3ru
5jury
1jus
ju5so5
ju1ta
j2ut5o
ju6va.
ju1v
ju6vak
ju1å
j5v
jy1
jy8de.
jy1de
jy2p3
jæ5le.
jæ1le
jæ3ra.
jæ9ran
jæ9r2es.
jæ1re
jæ7te.
j4æ1te
j2ø
1jø.
1jøa.
j6ø1a2
jø5b4
1j4ø1e2
2jøe1f
4jøeg
6jøei
2jøek
4jøe6nd
4jøe6ng
4jøe1ta
jø1f
jø3g2r4
jøg1
j4ø9kj2
4jøl
jø4les
jø1le
jøl6ver
jø8lv
jø5me
jøn5ne
j4ø4nn
jø5pa
jør4kel
jø2r1k
jør1ke
jør4sp2
jø4rs
jør2s5v2
jør4tel
jø6r1t
jør1te
jø3ru
jø6r7ut
jø1rø
jø4ses
jø1se
jøst2
jø5ta
j4øt
jø6t7av
jø3te.
jø1te
jøte4t
jø4t5e1ta
jø1tr4
jøt1te4
jø6tt
4jø1v
jø4vel
2jå
jå5ar.
jå1a
jå2ar
jå9ge.
j2åg
jå1ge
jå6la.
jå1la
jå4le.
jå1le
jå7le1s2
jå5let
1ka.
2kaa
5kaa.
5ka4a2n4
k3aa2s
ka3at
6kab4e1s2
ka1be
ka7b2o
4ka1b1r8
4ka1by
k6a3da
6kad2a1le
ka5dal
ka5del
ka1de
ka6d4el.
ka4de1ri
kad2er
4k1a2d1g4
kad2i7e
ka1di
2kadr4
5ka1du
4ka1dy
3kaen
k2a1e2
ka3fe
kaf5fe1re
ka2ff
kaf1fe
2k6a1fo
ka9fr2
2ka2ft
kaf5ta
6ka1fø
2kag
k5a4gi
ka1g2r4
2ka1h
ka5i4s3k
ka5i8s1m
6kaj2o
ka1j
k4a4ka.
ka1ka
ka4ke.
k2a1ke
ka4ked4
ka4kel
ka4kes
ka4ki.
k4a1ki
ka4kis1
6k5ak1sj
4ka6k1t
ka3ku
ka3lam
ka3lan
ka6la6n1t
ka3las
kal8d5s
ka6ld
5kal4d1t
k2a1l2e
3kalen
ka5le5v
5kalis
ka1li
k6a6l1k
5kalky
kal7la
ka2l1l
kal6l2ero
kal1le
kal8l4s
ka1lo
ka2l3p2
kal7s6v2
k2a8l1s
kal4v4e5s
ka8lv
1kam
ka6ma.
ka1ma
ka8me.
ka1me
4k3a4m4e1ri
4kamm2el
ka2m1m4
kam1me
kam4p3i
ka8m1p
kam4pr6
7ka1mu
1ka1na
k6a8na.
4kan1da
ka6nd
kan6da.
5k6an3de.
kan1de
1ka1ne
k2a5ner
ka1n4i
ka5nin
2k1a4n1l
4k1a4n3m
k3an1no
ka4nn
5ka1no
ka4no.
ka8nom
4kansa
ka6ns
6kan1si
3kansl4
1ka6n1t
kan6t7e6nd
kan1te
kan6t9r4
2kap.
5kapad
ka1pa
ka4pak
3kap6as
ka5pe
5kape2l1l
kap6el
4kapen
3ka1pit
ka1pi
1ka2pp
kap3re
kapr6
kap4s3t2
ka2p2s1
1kar.
1ka1ra
4karab
4karam
k2aran5
ka3rav
1k1a2r1b8
4karbe6i
kar1be
4k1a2r2ea
ka1re
ka6rek
ka5rel
1k6a2r1f
5ka2r1g
ka4ri.
k2a1ri
ka6rid
6k3ar3ki
ka2r1k
6k5ar6k1t
1ka2r1m
k2a2r3n
ka3rol
kar3om
kar5pe1s
ka2r1p
kar1pe
kar2p3s
3kar3ri
ka2r1r
7kar1sj
k2a4rs
kar1s4t
1ka6r1t
kar5ti
5karu
4ka1rå
1kas
7k6as.
ka5se4i
ka1se
ka3sek
ka2s5e1v
kas2ju
ka1sj
6ka5s2j2ø
ka4sk
ka1s3ka
kasko5
6kasp2
ka4spi
ka4s1s6
kas6sad
kas4sak
4k3as1si
kas4so
ka4st
8kas5to
kas2t3r4
1k4a7t6a1
ka3ted
ka1te
3kateg
ka3tet
ka1to
ka4t5op
4ka1t4r4
k2a4t7s
kat5ta
ka6tt
kat4tel
kat1te
kat6te2r1m
katt4e5s
kat4t4il
kat1ti
kat4tri
kat1tr4
ka4t5y
k7au4r
2kav
ka7v4es
k2a1ve
k9avis
kav8l9u
ka6v1l2
9kay
6k1b4
2kc
k5ce
kcen4
2k1d
k5d6v
1ke
2k2e1a8
ke9al
2k2e1b2
4kebr8
4kebuk
ke1b2u
4k2ec
ked4
ke5da
4ke5d4ag
4kedal
5ke5dan.
9keda6n1t
ke5de
6ke2dei
4kedem
4kede1s
7ke3d6es.
2ke1di
2ke5dr4
ked8sk
ke8ds
ke1du6
4ke1dy
2ke1dø
2keeg
ke3e2
2keek
2keel
ke4e1p
ke7e2rn
2kee1v
4ke1f
k3e2ff
k5e8ft
2ke1g
k3e4g1g
keg8ga
5kegla2d1h
ke3g6lad
kegl2a
ke5h
3kehet2
4kehj
2kei
k3ei2d
k1e2ig
k4e5il
k4e1im
k6e1in
k2eis2
2ke1j
2kek
ke1k2l4
k4e3k1n2
k1eks
k5e6k4t
k6el.
2k2e1la
ke3l4ak
5ke2l1al
ke6la6ns
ke6lat
kel5eier
ke1le
kele2i7e
kel5e2ig
kel7e2ik
kelei8ke.
kelei1ke
6ke7l2eis
ke2l3e4l
4k5e2lem
4kele6ng
k4elen
4ke5l2e1p
4keles
7k6elet
4kele1v
ke9l2i8e8
k2e1li
keli9en
3ke3l2ig
4ke5lig1n
4keli4n1j
ke4l5i6n1t
4kelis
ke4li1se
5kelit
4keliv
ke2l5l
4kelof
k2e1lo
6kelov.
ke1lov
4kelove
ke8l1s4
kel2s7k2ar
kel1ska
kel5s1l4
kel5sp2
kel5st
kel3s1v2
kelsva9
ke2l2t
kel1t3e
6ke3lua
4ke1lu2e
kel9u1k2a
ke3luk
1kel5u1ke
ke4l3ur
6ke1lu1v
ke8l5v
2k4e1ly
2k2e1læ1
2k2e1lø
kelø8pa.
kelø1pa
6k2e5l4å
kelå7re
ke2l1år
2kem
ke4mit
ke1mi
5kena.
k2e1na
6kenam
ken6ap
ke4nas
5kenat6ts
kena6tt
ken5de
ke6nd
6k5en6den
6k5en4d2er
4k1end5r4
ke3ne
4ke2n2e1b2
4ke2nek
4kenel
4k4enem
6kene6tt
4k4ene1v
3ke4n1f
ke4ni
5k6e4n1h2
4ke5niv
ke2n3k
ke4n7n
kenne4l3
k2en1ne
ke4nom
k2e1no
ke6n1s2
4k5en1se
ken1s6t
ken7te
ke6n1t
ken5t6r4
4k3entu
6ke3ny
6ke1nø
2ke3o6
ke3on
4k2e1p
5kepi.
9kepia
7kep2ie
6kep2l6
kera7l
k2e1ra
4keram
ke4ran
ker5a6n1t
6ke2rau
4kered
ke1re
4kereg
ke2r3ei4d
4ker2e2ir
6ker2eis
4kerek
4ke5rem
kere6n
6kere1ne
ke6r5e6ng
4kere4nn
4ker2e1p
ke4r5e6r1t
ke1rer
ke3res
4ke2r3e4s1s
4kerest
4ke1re1su
4kere4t
5ke5ret.
5ke4r3e1ta
7ke7re4ts1
3ke1ri
ke4r2if
6kerik.
ker2ik
4keri1ke
6keri6k1t6
ke4rim
ke4r4i1ne
6kerin1gi
ke3ri6ng
6kerin1gj
ke4ri1næ1
ke4ris
8ke5ri6st.
8ke7rista
6ke5ri2s1te
3ke2r1m
7ker1op
k2ero
5ker1o2r
4ke3r2os
kero6se.
kero1se
ke3r2ot
k4e6r1t
ker5to
ker3t4v
ker3un
k2eru
4ke1r4us
keru6se.
ker2u1se
kerø6re.
k2e1rø
ke1rør
kerø1re
8ke4r8øya
6ke4r6ø4y7e
2k2e1rå
5k2es.
4ke1s2am
ke1sa
9ke5sam.
7kesd2a1le
ke8s9d6
kes1da
5kese.
k2e1se
k5es2el
ke5s2il
ke1si
4ke3s2it
2ke1sj
k9e2s8ka.
ke1ska
4k5e2s1ke
kes8ke.
4ke1ski
4ke1skj2
6ke7skå
kesle6ge.
kesl4
kes1le
kes3le1ge
ke4s5ped
ke1s2p2
kes1pe
5ke4sp2ot
kes1p6o
5ke4s1pu
5ke6st.
4ke3sta
5ke6s5te.
kes1te
4ke5s2ted
4ke5s2t4ei
kes6tem
4k6e5s4ten
ke3sto
4kestri
kestr4
4ke5stru
4ke1stu
4ke1stø
ke4s5un
ke1su
kes1v2
2k2e3sy
kes1å
2ke1ta
keta6ka
ke3tak
keta6le.
ke3tal
ke3t2a5le
5ket2a8l4s7
k6etar
k2e3te
5kete.
4ketek
7k4e2t1h
2k2e1ti
2k2e1tj
2k2e1to
2k2e1t2r4
ke4ts2
ket6t7e4n1h2
ke6tt
ket3ten
ket1te
ket8t4es
2k2e1tu
2k2e1ty
ket4y6e
4k4e1tæ
ketø2y6s5
ke1tø
ket4øy
2ke1tå
2ke1u2
ke5ur
2ke1v
kev2a
keva8ne.
kev4a1ne
5kevas
3kevel
kevi6sa.
kevi1sa
k1e2vj
kev6ja
kev4je.
kev1j2e
k3e2v1n
kev6ne.
kev1ne
ke3vr8
kevæ8ra
ke1væ
ke2y2t
key1
6ke1æ2
2ke1ø4
2ke1å6
1ké4
ké1t5
2k1f
k5fi
4k5g2
k3ge
k5g1h
k1gu4
4k1h
kh5ak
kh2a
k4har
khe4o6
khju8l8s
khju6l7
k2h6m
ki3a2r
k3ide3o6
ki1de
4k1i4dol
ki1do
ki3dr4
k4ie2
kie5re
ki6el
ki3er.
2k2if
k2i5g4
ki6ka.
k2ik
ki1k2a
kikk8s9
ki2kk
ki1k8l4
ki3k2r6
kik4s
ki8la.
ki1la
3kil1de
ki6ld
ki4le.
ki1le
ki5li
4ki2lj
kil9ja
kil6le5st
ki2l1l
kill6es
kil1le
ki7l2oa
ki4l2og
ki3l2os
ki4lov
2ki2l1t
ki7l1å8
ki8ma.
k4i1ma
ki4me.
ki1me
5ki1mo5
ki4mo9l
2k1i8m1p2
kin9a6nd
k6i1na
kin2an
ki4na2r1k
ki5n6as.
6ki6nd
k4i5ne
ki6ne.
2ki4n1f
1ki6ng
king5a6nd
kin1ga
kin3ge
kin5i6ng
ki1ni
2ki4nn
kinn3s2i8da
kin6n1s4
kinn1si
kinn3s2i8de.
kinnsi1de
kinn4sp2
k2i2no
2ki6n1s
kin3s2i6de.
kin3si
kinsi1de
2ki6n1t
kin9ti8me.
k4in5ti
kinti5me
k6i6nu
2k1i6n1v
k4io4
4kiol4
7ki2os
2kip
ki5pe
ki4p5l6
kip5pe
ki2pp
ki2p2s3
1k2ir
ki5re
ki5ri
4ki4rs
ki3se
ki5sko
ki3s1mu
kis1m
ki1s2p2
ki5s2tav
ki5str4
ki6ten
ki1te
ki7t2i
5ki1to
ki4t2on
4ki1u
k6iv
ki4vek
ki1ve
ki4v5e4n1h2
ki1ven
ki6vi.
ki1vi
ki8v3s
kj2
3k8jan
kjap5pe
kja2pp
6k7j2a2r2n
kj4e7fj
kj2e
k2je1f
kj8e7f6r2
4kje4g1g
3kje6ld
k2jel
kjel7leg
kje2l1l
kjel1le
5kjemas
k2jem
kje1ma
3kje8m1p6
6kje2n3k
kje9n1y4
kje1s
kj2e6t5r4
kje6t5t
6kjor
kj2o
kju5le.
kju2l
kju1le
kju5len
kju9r
3kjæ2r1l
k5jø1di
kj2ø
1kjøp
3kjø1ri
kjø4tr4
kj4øt
2kk
k1ka
k2kab
k2k3a2ft
k2k1ak
k2k3al
k2kam
k4ka4n1b2
kk5a6nd
k8k4and.
k6kan5den
kkan1de
k4k5a6ng
k4ka4nn
k4ka1o
k4k1a2r1b8
k6ka2r1g
k4k7a6r1t
kkar6ta.
k5ka4st
k1kas
k6ka1su
k6ka6tt
k2k1au
k2k1av
k6ke1do
k1ke
kked4
k4ke1du6
k2ke3e2
k2ke5h
k2k1ei
k5k6el.
kke6lei
kke1le
k4k5e4le1v
kke2l3t4
kkelu4
kke4luk
kkel5un
kke6nan
kk2e1na
k5ker.
k4kerei
kke1re
k4kerel
k6kere6n1t
kkere6n
k4ker2ig
k3ke1ri
k4kerit
kke5r6u6nd
kker3un
kk2eru
k4ke1sa
k5kesau
k4k2e1se
k4ke1si
kke3s4l4
k4ke1s2p2
k6ke7s6t2ad
k4ke3sta
k4k2e3te
k5ket8et
k4ke1tø
k4k9g2
k1ki
k6k3i6nd
k2k3i4n1f
k2k9i4nn
k2k3i6n1s
k2k3i6n1t
k2k2ir
k1kja
kkj2
kkjek8
kkj2e
kk5je6n1t
k4k5j2e2rn
kkje7t
kk5ju
2k2k5k
kkla4g
kkl4
kk3lan
kk8lar
kk7lau
kk1lo
kk1ly
kk3læ1
k4k1n2
kk5ne
k3k5ny
kk7s
kk6s5v2
k1k2o1d
k1k6o3e
k4k1of
kko6l2ig
kk2o1li
kk5o4m3l
k1kom
kk7o6ms
k4k9o2m5t2
k6k2o1o
kk1op
k1k2o7s
k4ko9si
k6ko2sk
k4kost
kk3ove
k4kra
kkr6
kk5ram
k4k3rap
kk3r2ea
k4k5rei
kk3ren
kk3res
k4kr2i6k
kk3ro
kkr6o8e
kkr2o4s
kk3ru
kk4r4us
kk7rø
kk7rå
kks6al
kk1sa
kk4sar
kk5sed
kk1se
kk3s4ei
kk4sin
kk1si
kk1sk
kks4kj2
kks4l4
kk3sla
kk7s1le
kk5s1li
kk3s1pe
kksp2
kk1s2t
kk4s5tak
kk2s7t2e1p
kks1te
kks1t4r4
kk8s7va
kk5s4å7
k6k3t2
kk5uk
k4ku1n6i
k1kun
k2k1u6t
kk1v2
k2kvo
k1ky6
kkyl4
k1k5yr
k2k7yt
k2k1ø
k8køl
kkø6r
kkøy6
kk1å2
kl4
6kl.
4k1la.
4k5la2g1t
2klak
kla9ke.
kl2a1ke
kla4m2el
kla1me
3k2las
k7l4a5ti
kla4tr4
kleby7t
k1le
k2l2e1b2
kle1by
3k4le6d1d
k4le4d2e7b2
kle1de
kle4de1s
2kleg
kle2i9e
k7le2ik
k4leiv
k2lem
5k6l4em.
7k4l2e1no
k6ler2ik
kle1ri
kle1s7j
kle5s2no
kles1n
k2le4s2p2
kle5s1ti
kle4s7s8
2klet
8kli1a
k1li
4klie.
kl2ie
4k3l2ig
1k2lim2
3k4li1ni
k5li4n1j
1k2lip
4klit
klo4n7a8l
kl2on
klo1na
1kl2os
2k1lov
k5l6u6k1t
4klun
k4lu6n5t
k5lyd
6k7ly1f
3k4ly6ng
klys2e7te
kl2ys
kly1s4e
k2læ1
klæ5re3s
klæ1re
klø7na
k1lø
k6lør
6kløs
klø9va
klø5ve
6k1lå2n
klå5re
k2lår
2k3m
k5m4a4nn6
k1ma
km6ål2
k1må
2k1n2
3k2nap
k1na
kna5t4re
knatr4
kne4b3l2
k1ne
k2n2e1b2
k4ne1di
5k2ne3e2
kne8se.
k4n2e1se
k4nest
k4n5f
3k4nip
k1ni
knip1pe6
kni2pp
kn4i2t3
3k2niv
kn2o7g
k1no
kn4o6kl4
k2nok
5k2nop
kno9ta
kn2ot
kno5te
3k2nu
4k3num
3k2ny
kn2y4s
k2nø
knø9le
kn3øy1
9koa.
k2oa
4ko1a4k
9koa1ne
ko3a6n
ko7ar
ko2b3
ko3b4o
4k2o3br8
ko5da
k2o1d
4ko6d1d
ko4de.
ko1d4e
3kodek
5koden
2ko1dø
k6o3e
3koe6f
4koek
4koe2l
4ko2es
4koe1v
ko4g5e6ng
k2og
ko1ge
kog2en
kog9g2er
ko4g1g
kog1ge
kog3l
ko4gre
ko1gr4
kogst6
ko8gs1
ko4gu
ko6gå
2koi
koi4e5ne
koi1e4n
ko2ie
ko7i6k
ko1in
2k4o1j
ko8ja.
ko4ka.
ko1ka
ko4kab
ko4ke.
ko1ke
ko6kel
ko1ki
kokk6o
ko2kk
k3okku
4k4ok4l4
4ko3ko
2ko1ku
kol1b4a5
ko2l1b2
2k2o1le
ko4leg
ko4lei
ko6lel
ko4let
ko4le1u
3kol1le
ko2l1l
ko5lo.
k2o1lo
ko2l5p
4ko1ly
2ko1lå
1kom
ko4ma.
k2o1ma
4ko2ma1f
ko4me.
k2o1me
6ko2m5g6
ko4mi.
ko1mi
4komil
4komi8s9b4
ko4mi3se
2k1o2m1r6
4kom1se
ko6ms
kom3sl4
kom5so
kom4st
4ko2m5t2
4ko2m1u
4ko5må
k6on
ko4na.
ko1na
ko6n3d
kon6dr4
kon2e5l6å
ko1ne
4koniv
ko1ni
ko6n3s2
kon6s7v2
k2on6t2on
ko6n1t
ko4nu
1k2o1o
4koom
k2o5pa
ko5pe4rs
ko2per
ko1pe
4k2opet
ko4pi.
ko1pi
ko4pi2p
k2o4p9l6
4k2o3po
kop5per
k4op1pe
ko2pp
2k2o1pr6
ko1ra
ko4ra.
kor6da.
ko4rd
kor1da
kor4d3o
kord5s4ø
kor8ds
kor4du
k8o1r6e
6kore1f
4ko7r1ei9
ko5rem
ko7ret
ko3ri
ko6r2ik
4ko2r1m
ko5rol
k4oro
6kor5pa
ko2r1p
6kor1pe
1k4o2r3r
kor4sal
k2o4rs
kor1sa
kor6s5i4nn
kor3si
kors1in
kor4s5l4
kor6st4r4
kor1s2t
kor4sv2
kor5t4es
k2o6r1t
kor1te
kor6top
kor4t5r4
kor4t5s4
ko5r1u
ko6r2u1me
ko5r4um
1k2os
2ko1sa
ko6sa.
ko4sed
ko1se
ko4sek
5kose1le
kos2el
ko5s2en
ko4ses
ko9si
kosi9d
ko2sk
ko1s2l4
4kos1mu
kos1m
2ko3so
2ko1s2p2
ko6sta
ko1st7as
kos6t5e4rs
kos1te
kost5j
4ko1sø
kot4es7
k2ot
ko1te
ko5t4i
4kot4il
4ko1tr4
ko6tre
ko4t2s
kots5tr4
kot1st4
kot4ti
ko6tt
kot6t4s
kott1s5t4
4ko1ty
2kou4
kout3
kou7t8a
ko1va
kove1r7e6
kove2r
4ko2vr8
1ko8v4s3
4ko1ø
4ko1å
6k1p
kr6
k3rad
1k4raf
5krag
3kr2a2kk
kra7ni6e7n
kra1ni
kran2ie
6kra2nk
3kra6ns
4krap
kra9se
7k8ra4sj
kra4s5s
kra4to
kra4u
1k2rav
4kred.
3kre3di
4k1reg
k1re1gi3
4krei
k2re2i9e
kr2ei5s
6krek
kre4k1li
kre1kl4
2krel
k5r2e9la
4k3re4nn
4kre6n1t
6kre3o6
k5repr6
kr2e1p
5kre2ps
5k2re1sj
4k3re4s1s
kre1ta9
6k7r2e8t1n
k4re4ts1
k3re6tt
2k2ri1b2
4kr2if
7k6r2ig
kri8g6s5
kri9ga
4kr2ik
kri5ke
kri4kj2
krik6ka
kri2kk
1krim
kri4me
3k6ri6n1s
krin6s7t
k2ri2p
1k2ris
7kris2e5ne
kri1se
k2rit
3kri3te
6kriv
k2ro
kro5b4
kro5d4e
kr2o1d
k3r2o1fo
k4rofor
kro2k7k
k6ro1ni
kr2on
k5ro4n1l
kro4nom
kro1no
k4rop
kro2p9n4
k2ro2pp4
k4rost
kr2os
kro9t8e
kr2ot
kroten9
kro3v
kru3i4
kr4um3
kr4u5sa
kr4us
krut1t5i
kru6tt
k6ru3t1r4
2k5ryg
kry7pa
kry5p8e
kry4pi
3kr2ys
krø5ke
6k5rør
krø3v
4kr6å1d
krå8da
kr4å1e6
5kråk
krå5le
k4rål
kr6å9m
krå7ne
k1sa
k3sa.
k2s3ad
ks3a2l1t
ksa5me
k1s2am
k3s2a5ne
k4sa6n1s
k4s3a6n1t
k3s2ar.
k7sa1ra
k6s3a2r1k
k7sas
k1sc
k2sed
k1se
k2se3e2
kse6ge
k2s1ei
k5se6k1t
k5s2e1la
ks2el
k4s3e5le1v
kse1le
ksen5to
k7s6e6n1t
k4s1e2r1f
ks3e2r1k
k5se1rol
k2s2e1ro
k4se1sa
k4ses6k
k4se3s1m
k5s4e1so
kse1s4p2
k5se4s1s
kses6s4el
k1ses1se
kses8ser2ie
ksess2e1ri
kses4sp2
kses4s2t
kse6te.
ks2e1te
ks1fø4
k2s1f
k5sia
k1si
k2s2if
ksi5ko3
k3s2ik
ks3i6nd
k3si6ng
ksi7ni
k2s3i4nn
k4s3i6n1s
k7s4io
ksi3st
ks2is
k4si1t2i
k3s2it
k4s9kab
k1ska
k6s3kal
k6sk4io4
k1ski
ks1kj2
k2s5k2l4
ksko7na
k6s1k6on
ksko5ne
ks1kr6
ksk8u
ks9ku.
ks7ku2a
ks8kut
k2s1k6v2
ks1l4
ksla8ga.
ksl4a1ga
ks5lo
k7s6lu
k8s7lug
k2s5løs
ks1lø
kslø8va
kslø8ve.
kslø5ve
k5s4mak
ks1m
ks1ma
ks4m2el
ks1me
ks1må
k5s4no
ks1n
k7s8nø
k1so
k6s7oa
k6s2o1d
k4s2og
k4so2m1b6
k2s1or
k2s2ot
k2s1ov
k3s2pal
ksp2
ks1pa
ks3pek
ks1pe
ks5p4io
k1spi
ks3p2ir
kspor6t5r4
ks1p6o
ksp2o6r1t
6k6s5p2o4rs
ks1pr6
k8s9r
k3spy
k4s1s6
ks3s1m
k5s6t2ad
k4stak
ks3tal
ks5ta6n1t
ks1v2
k7s8vak
ks6tav
ks4te4da
ks1te
ks2ted
ks4teg
k3s2t4ei
k4stek
ks2t3e1v
kstev6ne.
k5ste2v1n
kstev1ne
ks4t4il
ks1ti
k4stin
ks1tj
ks1tr4
kst6ran
ks4t5rek
k6stren
k6st2rer
ks4tri
ks4tro
k3s9tum
k5stus
k4s3ty1v
k1sty
k2stå
k1su
k6sun
k9s8v2a1ke
ksva8ne.
ksv4a1ne
k6s5veg
k7s6ve6r1t
k5s4vin
ks5w
ksy8na
k1sy
ks2yn
ksy8s7m
k3s2y1s
ks5ær
k1sæ
ksø4ke.
k1sø
k3s6ø1ke
k6s4øl
ks1å
k3så.
k4så2p
kså2r4
ksåt4
6k1t
k5ta.
kta7f6
k4tag
kta6ka
k4t3aks
6k4ta6k1t
kt8a6la.
k4t3a2na
kt6a9na.
k2t3a4n1l
k4t3a4nn
k4ta6n1v
kt3a2pp
k9t4ar.
k4t3a2r1r
k4t3a6r1t
k6ta4s5s4
k5tast
k6t7a6t3l6
kt5a2v1h
k2tav
kt5a6v3k6
k6t3a6v1l2
k3ted
k1te
k4te1da
k6te3e4
k4t4e1fø
k2te1f
kt5e4ge
k2te5i
k6t4e1ka
kte5le
k6t5elsk
kte8l1s
k4t5e4lit
kt2e1li
k2tem8
k6t7e2m1n
k4t3e4n1h2
k9ter.
kteri1e5n
kte1ri
kter2ie
kteri5e7ns
kte4r5s6
k7t2es.
kt4es
k6te1sa
k6t2e1se
kt5e2s1ke
k4te1sk
ktes6ke.
kt5es1ti
k4t5e4ta
k4t2e3te
kt5e2v1n
k2te1v
ktev6ne.
ktev1ne
k5ti1b2
k1ti
k3t4il
k4t3i6n1s
k5t2ir
ktis5t
kt7i6te
k2tit
kti6v7e6nd
kti1ve
kti1ven
k2tja
kt7ju
kt2o9a
kt5o2ff
k2tof
k6togra4m5
kt2og
ktog6ra
kto1gr4
k4t2o1li
k2tom
k2t5o2m1f
k2t1op
k9t4or.
k7to1ra
kt5ord.
k4to4rd
k5t8o1re
ktor2i9e8
kto1ri
kt2o4r7s
k2t1ov
kt3ral
ktr4
k2t1re
kt2ro
kt2r2o5s
k2t1ru
kt4s5a2m1b
k4ts
kt1s2am
kts5ar
kt5sek
kt1se
kt7s2em
kt4s5er
kt6sin
kt1si
kts5k8ra
ktskr6
kt5sku
kt2s4k6v2
ktsle6ge.
kts1l4
kts1le
kts3le1ge
kt6sok
kts1o
kt4s1pa
ktsp2
kt5spre
ktspr6
kt4sta
kt1st4
kts6t2on
kts5top
kt4stå
k6t3t8
ktu9er.
ktu1er
k4tu2k
k4t7u6nd
k2t1ut
kt7y2r1k
ktyr8ke.
ktyr1ke
kt2ør7
ktø1r8e
k6t5øs4
k2t1øv
k2t3år
ku2a
ku9an
ku4be.
ku1be
ku3b1j
2k2ud
ku4dal
ku1da
ku4er.
ku1er
ku2e4r5a
ku4et
6kuf
ku4f3l2
6kug
k5u4gr4
ku7is
kuit6
6kul1di
ku6ld
kuld3r4
ku4led
ku1le
ku4leg
ku6lei
ku4lem
ku5len
ku4let
ku4le1v
ku5l4i
ku6li.
3ku2l1l
1k4u2l1t
ku4man
k2u1ma
kumen6tal
k2u1me
kume6n1t
k2u6mi
ku6m2s1
3ku1mu
1kun
kund5s6l4
k4un8ds
ku6nd
2k1u6ng
kun4ge.
kun1ge
k6u4nn
ku4o
1kup
ku4pe.
ku1pe
kup4p1l6
ku2p1p2
ku7ra
ku4rek
ku1re
ku8r7o
ku7r6op
kur2o8pa
1ku4rs
kur6sk
kur4sp2
kur4s3t
kur2s3v2
ku6r3t
ku5ru6
kuru9ken
ku4ruk6
kuru4ke
kuru7ker
1ku2r1v
kur4v3i
1kus
5kus.
ku7sa.
k4usa
kus5a6k
kus5ar
5k2u1se
ku4s5el
ku4ska
ku7s6pe
kus1p2
kus3t
6k1ut.
ku1ta
4ku5te.
ku1te
k2u5to
ku6t7r4
2k1u4t1s4
1ku1u8
kuøy6
ku1ø6
kv2
kv2a8ke
k4va1li
k6va4rd
kvari6e5n
kv2a1ri
kvar2ie
5k6va6r1t
kva9se
kve3d2
k2ve7e2
k6v4eil
kve4i9se
kv2eis
1k4vel
kv2e5la
4k3ve4rd
5kv2e2rn
kver6n6s8
2kv4es
kves5t
7k6via
4k1vid
3k4v2ie
kvi4e1ne
kvi1en
kvi5er
kvi7l1a
3kvi4nn
kvi5se
7kvist
2kviv
1kvo
k1v2og
6k1vok
k4v2ot
k2vu
4k3væ
k1vå
k7we2
k5wu
ky5a
ky6el
k4y1e
4ky1f
ky4f5le
kyf2l2
2kyg
ky4leg
ky1le
2k3y6ns1
kyn6skjer
kyn2sk
kyn5skj2
kynskj2e
ky2p1
ky4p5r6
1kyr
kyrie5ne
ky1ri
kyri1e4n
kyr2ie
kyr8ke.
ky2r1k
kyr1ke
ky8sa
k2ys
4ky1s4e
ky6se.
kyse8te.
kys2e1te
ky3skr6
kys6sk
ky4s1s
1ky4s2t
kys3t3a
kys4tel
kys1te
2kyt
ky6ta.
ky1ta
ky2t4e3s2
ky1te
k1æt3
1kø.
kø2ar
k6ø1a2
1k4ø1e2
kø4en.
kø2er
6k1øk
kø4le
k1øn
køn6skjer
kø6n2s
køn3skj2
kønskj2e
k3ø2r1r
køs4
kø5se
køy7ar.
k7øyd
køy7e6ne
køye4n
kø4y1e
1k6øyr
4kå1ke
kå3le
kån8da
kå6nd
kå5ne
kå4pe.
kå1p2e
kå7pen
kå9ras
kå1re
kå1ri
kå2t5
k5å6t6t
1la.
2laa
la1b
4la1b4a
la6b1l2
1lab2o
2l3ab2on
4la1by
4lad.
l6a1de
la4dem
4l1a2d1g4
lad2i9e8
la1di
2l1a2d1m
2ladr4
la8d3s4
1l2a1e2
2laei
4lae6n5t
4la3e1p
2laf
la3f2l2
4la4g1g
la1gi4
6lag1n4e
lag1n
l4a2go8
la2g5om
la2g1r4
lag5san
la8gs1
lagsa2
lag4sj
2lah2a
la1h
2la1in
la5ka
la4ke.
l2a1ke
4l2a1kj2
l2a2kk
la2kr6
l7ak1sj
l5ak6s1l4
4la6k1t
lakter2ie6
lak1te
lakte1ri
la5kø
2lal
l1a6l1k
la5mab
la1ma
4la2m1b
la4mes
la1me
la4met
la3mo
l8a8mu
4l3anal
la1na
lan2c
lan6das
la6nd
lan1da
lan6d2e7b2
lan1de
land3r4
l6a3ne
3lane.
4lanet
lan6gel
lan1g4e
la6ng
lan4gem
lan6ge2r1m
lan3g2er
lan4gr4
lan8g3s4
lang4s5e
lan6g5ø6
4la4n1l
4la4nn
l3an1no
la4nor
la1no
6l5an1si
la6ns
lan5ti
la6n1t
4la6n1v
l3an5vi
2la1o
la6pal
la1pa
la7pi
la8pl6
lap5sa4
la2p2s1
lap5sen
lap1se
lap5s4i
1l8ar.
6l7arab
la1ra
2l1a2r1b8
l8a1re.
la1re
4l7a2r2ea
la9red
la5rem
l6aren.
4l3a2r2e3na
lar4e6t
la6r7e1ta
5l6a2r1f
la7ria
l2a1ri
lari1ar
lari6e7n
lar2ie
4l3ar3ki
la2r1k
l2a2r1m
l2a2r3n
6l7a2r1r
4la6r1t
lar6ta.
lar7v4et
la2r1v
la6sc
la5se
las2i9e
la1si
la2sk
7la5skj2
4las1m
la5s1mi
4la4sp2
las6sak
la4s1s
las6s2am
las6sat
las6s4el
las1se
l6ast
4lastr4
las3v2
l4a1ta1
la4t5a6ng
6la2tau
la4teg
la1te
lat6ek
la4t2e1no
l4aten
la2t4e5s4
l4a5ti
la6ti.
4la2t1m
la1to
lat7ra.
la1tra
latr4
lat9ran
lat5ra5r4
lat4t5i4s
la6tt
lat1ti
lat4tra
lat1tr4
latt6u
l8a8u7a
2la2ud
l4a6ue6
4laun
4laur
la2u7se
lau6st
2laut
2lav
l6a4v5al
la1va
la4vel
l2a1ve
l5a2v1h
lav5i4nn
la1vin
6la8vs
7l4a1vå
1law
la6y5
2l1b2
lba3de
l1b4a
lba2d
lba4k
lba5ke.
lb2a1ke
lba4ne.
lba1ne
l5be
lb2e9na
lbe8re.
lb4er
lbe1re
l4b2oa
lb2o
l6bu1h
l1b2u
lb4y4e
l1by
l5b4å
2l1c
l4ce.
6ld
l7da.
l1da
ld5aks
ld5a6k1t
l9da6nd
l7d6a6t5o
lda6t5y
l2d2e7b2
l1de
l5den
l2de5o6
l2d2e1p
l3d2er
l7der.
ld2e1r7a
l4derek
lde1re
l6der2ik
lde1ri
l6d3erk2læ1
lde2r5k
lderkl4
lderle9g
lde2r1l
lder1le
lder5s6te
lde4rs
lder1s2t
lder6s5ti
l4des1h
lde1s
l6destr4
ld4es2t
l9dé
ld6is7k
l1di
ld2o9a
l1do
l8d5oks
l2dol
l4d8o1re
ld5o2v9n
l2dov
l3drak
ldr4
ld3ran
ld5ras
l6d1re.
ld7reg1n
ld5rer.
l4d1rer
l8d1r2es.
ld1ri
l7dry
ld1rø
ld1se8
l8ds
ld4sek
ld4s2el
ld4ses
lds1k
ld4ska
ld1s4kj2
ldsl4a8ga
lds1l4
lds6leg
lds1le
lds4let
ld4s9m
ld5s4om
lds1o2
lds3tr4
ldst4
ld6tus
l4d1t
l1dø2
ldø5d
ldø5l
ld7øy
1le
2l2ea
le7ag
le3a2k
le1al
lea5la
3leas
le5at
2l2e1b2
le4bem
le1be
le6bosta
leb2o
leb4o1s
5lebæ2k
l2ec4
2le1da
5le2dar
led4dø2
le6d1d
4led2e7b2
le1de
le4dem
6led2e1p
le5d2er
le3de1s
2le1do
3le2d1op
5le2d1ov
le4dro
le1dr4
le5dry
le8d1s2
6le1du
4le1dy
2le1dø
4le3då
2le3e2
6leei
6l4e1fa
le1f
lefa6ne.
le5fa1ne
4l1e2ff
2l4e1fi
4l2ef2l2
4lefor
le1fo
4lef2ot
2l8efr2
l1e8ft
6le1fy
4l4e1fø
6legap
l2e1ga
5legas
3le1ge
le4ged4
le9g2en
le4g2e1ra
leg2er
le9geran
le9ge9ras
le4g2ero
le4ge1ta
le4g2e1v
7leg1gi
le4g1g
le3gi
6leg2if
4le1gj
2le1g2l
le4gol
l4e1go
4legre
le1g2r4
le8g3s4
2le1gu
2l4e1gå
2le1h
6lehal
leh2a
6leha2m1r6
6lehan
6leh4au
6leh2a1ve
le6i2do
l6eie.
le2i7e
lei5en1de
lei1e2n
lei2e6nd
lei4e5ne
l6ei8et
lei8g6d
le2ig
lei4g2er
lei1g6e
lei7ger.
lei4get
lei6g4h5
leig6na
leig1n
leig8n4e
2lein
le3i6n1t
lei6r7u
l2e2ir
leis7t
l2eis
l6eit
2le1j
2l4e1ka
le4ka.
6lekan
le1k6e
le3ki
2l4e1kj2
lekk7s
l2e2kk
2le1k2l4
2l4e2k1n2
lek4na
2le1k2o
2l2e1kr6
4l3ek1so
4l1eksp2
lek4t5o4rd
l4ekto
le6k1t
lekt4s5t4
lek4ts
2le1ku
2le1k2v2
4le1kø
4le1kå
2l2e3la
8l9e6ld
2le1le
l3e2lem
4l5e2lg
5le3l2ig
l2e1li
2l2e1lo
3le8l1s
4l3elsk
le1lu
4le3luk
l5e8lv
2l4e1ly
4l2e1læ1
2l2e1lø
lelø6pa
4le1ma
l8e3me
le8me.
4lemet
8leme6trisk
leme5tri
lem2e1tr4
leme4t3ris
8l9e2m1n
lem8na
2le1mo
lem9ped
le8m1p
lem1pe
lem5pe1s2
3le2m1r6
lem4si
le6ms
lem4s5ø
2le2m1t
lem5ti
2le1mu
6le5my
4l6e1mø
2le1må
le4na.
l2e1na
le4nal
4lena2v
len1d8a
le6nd
len9dan
6lenden
len1de
l6endre
l1endr4
4lend1ri
4lened
le1ne
4l4enem
4l4ene1v
l4en8g1d4
le6ng
len4g5r4
len1g9u
le5ni
4leniv
lensa4
le6ns
len4s5ak
4len2sem
len1se
len5ses4
len4sta
len1st
len4tam
le6n1t
len4tr4
len8t9ra
2le1nø
4l4enå
2le3o6
5le2ol
2l2e1p
le5pa
le4p6el
le1pe
le8p9enden
lepen3de
lepe6nd
le2p3j
le8p1la.
lep2l6
1le4p1le
4le1po
4lera1b4a
l2e1ra
4leram
le2r3a2m3b
le4r5d
l8e1re
le9re.
6ler2ea
4lere1f
4lereg
4le9r8ei
4lerek
le5res
4ler4e1so
4le1re1su
4lere4t
4lere1v
9leri.
le1ri
7ler2ie
le6rie1i
le7r6i1na
le4riv
4l5er1næ1
l2e2rn
le5ro.
l2ero
4le1rom
6lero1pe
ler1op
le3r2os
4le3r2ot
7le6rs.
le4rs
l6er1s4p2
ler7te
le6r1t
l2e1ru
4le1r4us
leru8se.
ler2u1se
2le1ry4
5le2r1yr
2l2e1rø
2l2e1rå
5l6es.
le4sab
le1sa
4le1s2am
7lesar
4lesau
2lesc
le5sed
l2e1se
le4seg
4le3sek
le5s2en
le3ser
4leset
4le1sh4o
les1h
le6si.
le1si
4le3s2ig
le7si6ng
le2s5i4nn
4le3s2it
4lesju
le1sj
le7skap
le1ska
le4s3kj2
6le1skol
le6sk9u8t
le1sku
4le1sla
lesl4
2le1s2p2
6les2pal
les1pa
le4s3s6
lesse6ne.
les5s2e1ne
les1se
4lesta
9le2s1ta.
7lestal
le5s2ted
les1te
le5s4teg
le5s4tel
le4stim
les1ti
le3sto
4le1s2trø
lestr4
4le3s4tyr
le1sty
4le1stø
4l8e1stå
4le5s2ug
le1su
les1v2
le5s6vi
4l2e5sy
4le1sø
5lesøy
3let.
le4tab
le1ta
6le3tak
leta8ka
leta8le.
le3tal
1le3t2a5le
l5e4tas
5lete.
l2e1te
6let2ea
4leteg
4letek
le5ten
6lete2nk
6le7t2e1p
le5ter.
4let4es
2l2e3ti
l5e4t2ik
2l2e1tj
2l2e1to
2l2e1t2r4
l8e7tre
le5t4ri
3le4ts1
let6sj
lets8k
let6t2ea
le6tt
let1te
let6tr4
let6t3s4
2l2e1tu
l2e4t2v
2l2e1ty
let4y8e
4le1tø
8le1tå
4leu2l
le1u
2le3u2n
l1e2ur
2leut
le4u1te
2lev.
le1v
le6vad
le6va1lu
l6e4ve2d1
4l6eveg
4levei
4levek
6lev2e2rn
4le4ve1v
le1vi6d
le2v5n
2levo
le2v1r8
4le8v1s
4le1væ
lex1
4le1ø4
2le1å6
5lé5e8
1lé1r
7lét.
lé1t
7lè
5lê
4l1f
l4fa.
l1fa
lf5a6n1t
l5far
l5fe
l2f3f
l5f6ig
l1fi
l5f6in
lf5ja
l1fj
l7fj2e
l6f3nul
lfn6
lf1nu
l4fom
l1fo
l4fut
l1fu
2lg
l6gaf
l1ga
lga8le.
lg2a1le
l4g5a2l1t
l4g3a1na
lg2a5t
lga4ve.
lg2a1ve
l2ged4
l1ge
l4g2e1la
l4ge1le
l2gem
lg2e3n1a
lg2en
lge4r5an
lg2er
lg2e1ra
lge4rap
lge5ri
lger5un
lg2eru
l4ge1ry4
l4ge1sl4
lg6es
l6ge7s2p2
l4g5g
l3gi1e8n
l1gi
lg2i1e
lg1lo
lg5ly
lg5n
l2g3ob
l1go
lg2o2d
l2g1ok
l2g1om
l4g5ov
l2g3re1f
l1gr4
l4gr9øy
lg3s2e
l8gs1
lg2sk
lg5s4kre
lgskr6
lgsle9g
lgsl4
lgs1le
lg5s4tr4
lgu4l
l1gu
lg3un
lg3ur
l1gæ
lg5ø
l6gå.
l1gå
lg5år.
lgå8v4a
lgå1v
lgå8ve.
8l1h
lh4a8ka.
lh2a
lha5ka
lha8v6s5
l5hj
1li
li1a
li4al1a
li2am
li5a6ns
li4as
li1b4a5
li1b2
lib2e5ro
li1be
lib4er
li1b4i5
lib2ie6
lib4y5e
li1by
li4dak
l2i1da
li4ded
li1de
li4do.
li1do
2l1idr4
li4d3t1
li4e1ne
l2ie
li1en
li3er.
li5e6rs.
lie4rs
li2e5s
3l2if
4lifat
li1fa
li2f5f
3l2ig
li4ga.
li1ga
li4g3an
li4gar
lig9a6r1t
li8g6as.
li4g5e4n1h2
li3g2en
li1ge
li4get
5lig1n
li4g3re
li1gr4
lig3se
li8gs1
lig3s4i
lig3sl4
lig3s4p2
lig5s4ti
ligst4
lig5str4
li2gu
4li3h
li5kan
l2ik
li1k2a
li9kar
li7kas
li5ke3e2
li1ke
li3ken
li5kes1å
li2ke1s2
li9ki
lik2k3o
li2kk
li1k2l4
9li2k1n2
liks4t
li5ku
6li1la
8l5i6ld
li3le
lil5le1be
li2l1l
lil1le
lil2l2e1b2
lil5let
li4mar
l4i1ma
li4ma5s
li6ma3te
lim6at
l6i1me
li4me.
4li2m1h
limp3r6
li8m1p2
li2m7r6
l4i1m9u
li4na.
l6i1na
4lina6l
lin6c
linch5
5l4indr4
li6nd
4lin1du
lin8d3s4
l5indu1s
li4ne.
l4i1ne
li3n2e6a
li6nem
2l1i4n1f
lin4g3j
li6ng
ling5l
ling7s2en.
lin8g2s1
ling1se
5lin2g1v
4l5inju
li4n1j
lin5ke1s4
l2i2nk
lin1ke
lin5k1le
linkl4
lin4kv2
2l1i4nn
l4in5net
lin1ne
3l2in1ni4
6lin6n1s4
6l5inntr4
l1in6n7t4
li4nor
l2i1no
l4in1se
li6n1s
5l4insk
4l3inst
4li6n1t
2l1i6n1v
l6i6n5ø6
lio4no
l4io
li2on
lion5sp2
lio6ns
lion5s1v2
li5o6s
2lip
li2pe1s4
li1pe
lip2p4s3
li2pp
li9rar
l2ir
li1ra
li4r6ek
li1re
4lisak
li1sa
li5set
li1se
li2s4k2l4
2liso
4lis1p2
lis6sp2
li4s1s
lis7tan
lis6te2r1k
li2s1te
lis5ti
4lis6t4il
list3o
li6s8tr4
li1s2t7rø
lis4t3u4
li4st5y
listyr8ke.
li3s4tyr
listy2r1k
listyr1ke
li4te3e4
li1te
li4tek
li4ti3a
li1t2i
li4tid
li4t2ig
li4t4il
li4tim
li4ti5st
li4tiv4
lit5j
6li8t1n
li5to
li2t1r4
lit5rer
lit6te1le
li6tt
lit1te
lit6te2r1k
lit6te2r1m
lit6t5s6
li5ty
7li1u
li6va.
l4i1va
li4v3ak
li2v5eg
li1ve
liv2i5e
li1vi
li8v2s3
2lj
l1jan
l1jar
l4j1a2r1b8
lj2a4r5s6
ljas4
l4jed
lj2e
l6je3e2
l2je1i
l2jek
l2je3l
lj9e8lv
l1jen
l3jer.
l4je1s
l5j2es.
ljes4t
l5jet.
l4j2e1te
l5jete.
l6j2e5t6r4
l2je1v
l5jé
l1ji
3l2j2o1d
lj2o
lj6o8e
l4jom
lj5o1ri
3l2j2os
lj5un
l7jur
lj7ut
lj2ø3
l5jøs.
6l1k
l3ka
l5ka6l
lkal2i9e8
lka1li
lk4an
l6k5b4
l1k4e
l3ke.
l2ked4
l5kedal
lke5da
l3ke5de
lkeei4
lke3e2
l6ke5h
l5keleg
lke1le
l3ken
l7ker.
l4k2e1ra
l4ke1ri
l4k5e4rs
l4k2e3ru
l5ke7s6t2ad
l4ke3sta
l5ket.
l5ke4ts2
lk2l4
lk4li
l5k4lu
l6k5nin
l2k1n2
lk1ni
l6ko.
lk2o9ma
l1kom
l2ko9sa
l1k2os
l2k2ot
lkras5
lkr6
l4k5r2i6k
lk4ser
lk1se
l4k3s2h
lk1s4t
lku4le.
lku1le
lku8t
lk9u1te
l6kveg
lkv2
lkå1
2l1l
l5la6a
l2l6a5f
l2lak
l2l1al
l4l2a1mi
l5l6a3ne
l4l3a4n1l
l4l5a1no
lla6ns4
ll5ansk
ll7a1pa
lla2p3s1
ll4as
l4l5a4sp2
l4l5aur
llau6re
ll7a6v1l2
l2lav
ll5a2v1r8
l6l3d2
l2l2e5a
l1le
l5le3a2k
lle8da.
l2le1da
lle4d3r4
l3le3e2
l2le1f
lle5g2e1v
l3le1ge
lle5g2r4
l5leh2a
l2le1h
l5leh4o
l5leh6å
l4l5e2ig
l4le3ki
l6l5ekst
l4l2e1li
l2lem
llen6da.
llen1d8a
lle6nd
l6l5enden
llen1de
l4l1endr4
l6len4d1t
l4le2nk
l5l8e6ns
l4lentu
lle6n1t
l4l2e1nu
l1l5e4p1le
l2l2e1p
llep2l6
l6l5e6r2ik
lle1ri
l4lerob
ll2ero
ll6es
ll4e4so
lles5pr6
l2le1s2p2
l4le5stø
l5let.
l4le1ta
l5le4ts1
l1let6te1le
lle6tt
llet1te
l8l4e1tæ
l2le1u
ll5e1ven
lle1v
l4le7ve1v
l5levå
lle6y1
l2lé.
l2l7g2
ll2i1e
l1li
lli5e4n
l9l2ig
l2lim
l4l3i6nd
l4li6n1s
l4l3i6n1t
l9l8int.
l3lip
l4l2ir
lli6sen
lli1se
l4liv
l2l1j
l6l7k2
l2l5m
llmu7e9ne
ll1mu
llmu2e
llmu1e2n
llmu7e6ns
llmu9e7r
l5lo.
l2lob5
l2l2o1d
l4l3o2ff
llo5id
l2l5ok7s4
llo2m1
llo6m5s6
l2l3op
ll5o2pp
l2l1or
l4lo3so
ll2os
l6lo1te
ll2ot
l2l5p
llra7n
l2l5r4
ll4sak
l8l1s
llsa6me.
ll1s4am
llsa1me
ll4sem
ll1se
lls5e6nd
llsk4
ll2s6k2ar
ll1ska
ll2s5kv2
ll5skå
lls5lag
ll2sl4
ll3s1my
ll2s1m
lls4no
lls1n
lls4te
lls6t2ig
lls1ti
lls7øk
ll1sø
l2l3t4
ll1t6o4e
l8lua
l4lu4e
l4luf
ll7ug
llu4k
l8l7u1k2a
l4l3u1ke
l6l2uks
l5l4um
l4l1un
llun6ge.
llu6ng
llun1ge
llu4pi
l2l1ur
l3lus
l2l1ut
l8l1v4
llva8n
ll1va
lly4se.
ll2ys
lly1s4e
l4løk
l1lø
ll5øks
llø6pa.
llø1pa
l4løve
l6l7øy.
l4l5øya
l4l3ø4y1e
lløy6er
ll5øy6n
l2l1å8r
ll3å2s2
2l1m
l5ma.
l1ma
l4m3a4n3m
l4map
l4mar3ki
lma2r1k
l2m3av
l2m7b
l4m3e8l1s
l1me
lm2el
l4me1lu
lm5e4po
l2m2e1p
l4m5e2r1f
lm4e7ri
lme7s4ti
lm2e5t4r4
l5mil
l1mi
lmi8le.
lmi1le
lm3i6nd
l5mi6ng
lmi1ni6
lmin7ne
lmi4nn
lm3i6n2s
lm5i6n1t
l7mis
l4m5l
l2m5m4
l2m1op
l1mo
l6m1s
lm1s6j
lm5s2p2
l2m3t
lmu4le.
l1mu
lmu1le
l2m3ut
lmyr8ke.
l1my
l4my2r1k
lm5yr1ke
l4mø.
l1mø
l6m5øs
l2m5øy6
lmøya9
lmå6la.
l1må
lm6ål
lmå1la
l2m5å4r
l4m5å1s4
2l1n
l2nab
l1na
l3ne
lni4u
l1ni
l7ny
lo1al
l2oa
7loa1ne
lo3a6n
lo1a4r
5loar.
2lob
lob5by.
lo2b1b
lob2by5
lob9by1an
lobbya2
lob9byar.
lob5b4y3e
lo2b2l2
lo2d3a
l2o1d
lo3d4e
lo4d3ri
lodr4
lod3s4m
lo8ds
lod7s1te
lods8t4
lod7s6v2
lo6d5u
6loe6ng
l6o1e
loe2n
6lo1fj
3l6o2ft
1l2og
lo5ge
lo6g5e8v
lo4g2ir
lo1gi
lo1g2o
lo3g1op
log8res
lo1gr4
lo6g5ro
log5s4a2
lo8gs1
4l2o1h6
lo5id.
lo3i1de
lo1in
3loja
l4o1j
lo1ki
lok4ko
lo2kk
lok6kul
2l4o1k4l4
4lok6on
lo3ko
2lok7s4
lok8se.
lok1s4e
lo1k4v2
lole6ge.
l2o1le
lo3le1ge
2l3o6lj
l2o1lo9
l5omdr4
lo2m1d2
lo4m5in
lo1mi
lom4m2el
l2o2m1m4
lom1me
lomst9r4
lo6ms
lo4nal
l2on
lo1na
6lo6nd
lo5ne
lon4g3r4
lo6ng
lo4n4it
lo1ni
lon7skj2
lons1k4
lo6ns
2lop
l2o3pa
lo4p2ea
lo1pe
l4op5pa
lo2pp
5l4o2p1t
lo1ra
2lo4rd
lor6da.
lor1da
l8o1re
4l1o2r1g
lor4g5l
lor2ie6
lo1ri
l4o1ro
lo7rød
l2orø
lo7sa
l2os
lo6sek
lo1se
lo4ses
lo5se1v
los4k1le
lo2s1k2l4
lo1s2l4
lo7sp2
2lost
lo9t4es
l2ot
lo1te
lo8ti.
lo1ti
lo3to
4lottet
lo6tt
lot1te
lo1un
1lov
lov7a6ld
lo3van
lo9va6nd
lo7v4a1ne
lo3var
lo3ve7d6
l6o1v8er.
love2r
love5re6
l5ov2e2rn
6l5ove4rs
6l5ove6r1t
7l4ovo
lov3sa
lo8vs
2l1p
l7pa
lpa5re
lp6as5
l4pe1do
l1pe
l4pe3e6
l2pei
l2p6el
l5peleg
lpe1le
l3p2e1li
l4pelin
l3pes1m
l2pe1s
l4p2e3ti
l2pe2u
l2p2h
lp2i
lp4i1n3e
lpi5ne.
lp2l6
lpo6et
l1po
lp6o1e
l4put
l1pu
l5q
2l5r4
lr6a8d2a
lradi4u
lra1di
lra6ne.
lr4a1ne
lre4de
lre4i
lr4e8ka
lre8ke.
lre1ke
lre8va
lre1v
l4r4i8ma
lri4ve.
lri1ve
lro8de.
lr2o1d
lro1d4e
lro6pa.
lr2o1pa
lro8sa.
lr2os
lro1sa
lro4se.
lro1se
lro4t5s
lr2ot
lrø6re.
l1rør
lrø1re
lrø5v
lrå8da.
lr6å1d
lrå1da
lrå4de.
lrå1de
8l1s
l2sad
lsag6
ls5a6ld
l1s4am
l7s1a2na
lsan6ke.
lsa2nk
lsan1ke
l4s5a2no
l4s3a6n1s
ls5a2r1k
l4s4at.
l1sat
l4s1cu
l3se.
l1se
l2sed
l2se3e2
l2s3eid
l2s5e2ig
l4s3e1le
ls2el
l4s3e8lv
l2sem
l3s5e2m1b
l4s2e1no
l7s2er.
lse2s
lse1s5ku
lses3l4
lses5pa
lse1s2p2
lse4s5s
lse8s5tr4
lse1st
lse4te.
ls2e1te
l2se1u
l6se1v
l4s1f
l2s1h
l5s2ig
l1si
l5s2ik
l6s5i2l1l
ls2il
l4sim
l5s4i3mu
l4s3i4nn
ls5ja2kk
l1sj
l5s2je1f
lsj2e
ls3je3g
ls3jen
8l6sk.
ls5ka1b4i
l1ska
l4skab
l4s5kan
ls5k2a1ri
l2sk2ar
l4s1k5a6r1t
l6s5ke
l5s2k2if5
l1ski
l2s3ki6ld
ls2ki4nn6
l5sk8in6n1s5
ls1kjed
lskj2
lskj2e
ls3kj2o
l6s1k2l4
lsk3læ1
l4s3k2oa
ls5kor
ls3kov
l4sk8ra
lskr6
l2s8k1s2
l2s1k5un
ls7kva
l2skv2
ls3kvi
l4skå
ls3kåp
lskå6pa
l2sl4
lsl4a4ga
l7slag1n
lsle6ge.
ls1le
ls3le1ge
ls5lo
l5s4luk
l1slu
l6s5løs
ls1lø
l2s1m
l5s2mør
ls1mø
lsmå6la
ls1må
lsm6ål
l2s1nu
ls1n
lsok3
ls5o6nd
l3s2on
lson6de.
lson1de
l2s1or
ls7o6se
l1s2os
l5s2ot
l2s1ov
l2s1pa
lsp2
l9s2peg
ls1pe
l5s2pei
l9s2p6el
l5spi
l4spr6
l5spred
ls3pri
l8s7s
l4s3tak
l4s3tal
l6s1ta2nk
l6s3te.
ls1te
ls4ted
l4steg
l3s2t4e4i
ls4tel
l6s5t2e2r5r
ls6ti.
ls1ti
ls4t2i1e2
ls6t2on
ls5tren
lstr4
l9stri
l2su
l5su2b
l3su2k
ls1un
l2s1v2
ls7v2e1a2
ls5ve3e2
lsve8en.
lsvee4n
l6s5vek
lsve7re
l4sv2ik
ls6vi6nd
l4sør
l1sø
l2s1øy
l2s1å
2l1t
l4ta1a
l4taf
lt3a6k1t
l3tal
lt8a4la.
l4t3a2l1b2
lta6le5v
l3t2a1le
l2t3a4n1l
lta6no
l4t3a6r1t
lta4st
lta8t4es
lta1te
lta4t3o
ltat3r4
lt3a6v3k6
l2tav
l5ta1væ
l6t9b2
l3te1de
l1te
l2ted
l4t4e1ka
lte6ma.
l2tem
lte1ma
l3t4en.
l4t5e4n1h2
lt3epi
l2t2e1p
l4teras
lt2e1ra
l4t5er1s2t
lte4rs
ltesa8me.
lt4es
l2te1sa
lte1s2am
ltesa1me
lti8d6s1
l1ti
l3tid
l4ti3et
lt2i1e2
l2t2if
lt2i6g5
l2t2ik
l4t6i1na
l2t3i6nd
l4t9i4nn
lt3i6n1s
l4ti1vi
l2t1ja
ltle8ge.
l6t3l6
lt1le
lt3le1ge
l8t9n
lto9ar
lt2oa
l3t2og
lt2o4s
lt3ost
lt2r4
l3tra
l6t3reg
l4t3rei
l6tre7k2o
l6t5rel
l6t7rem
l4tre6ns
l2t3res
l5t4rest
l5tre1v
l4t5rit
lt3rol
lt3rom
ltr2o8pa
l5t2r2os
l4t5rød
l5trå
l4t1s2
ltsa8me.
lt1s2am
ltsa1me
lt5s4i
lt7s6t4
lt2s3v2
lts4vi
l6t7t8
l4t3u6nd
ltu4ra
l3t2ur
ltu1r5e6
ltu5r6en
ltu5r6er
ltu4r5s6
ltur5å6
l2tut
l5tv.
l2t1v
ltva8la
ltv4a
l3t8vs
l4t5w
l3ty
l1ty8d
l4t7øl
l2t3øv
lua8r
2lub
lub6ba6nd
lu2b1b
lub3b4a
lu6bri
lu1b4r8
lu5c
lu7e1re
lu1er
1luf
3lu5gar
lu1ga
lu7go1
lui6
luid6er
lui2d
lui1de
1lu1j
4l4uk.
lu4ka.
lu1k2a
4luket
lu1ke
luk6ke1ri
l4u2kk
luk1ke
lu3kr6
5l6u6k1t
4l1uly
l4um
lu6m2el
l2u1me
l4u2mo
lum6sk
lu6ms1
lu4mø
lun5d4r4
lu6nd
lu4ne.
lu1ne
lun4gel
lu6ng
lun1ge
l7u2n6i
lun5ne
lu4nn
3lun1sj
l4u6ns
4lu6n1t
lu6o
l6u2p4s
l6u2p3u
lu1r2e
6lurei
lu5ren
lu5ri
6l5u2rn
lur8na
lur8ne.
lur1ne
lur8ta
lu6r1t
1lus.
l4usa5
lu7sak
lu6s2el
l2u1se
lu6sh7e
lu2s1h
lu2s5k
lus4o5
lus4sid
lu4s1s
lus1si
lus4s3t
lus1t
lus2t3r4
luta3
lu3ta.
lu7tet
lu1te
6l1u6t1f
4l1u2t3g2
lu2t1h
3lu5t6he
lutla9
lu6t3l6
lu2t6m
l2u1to
lu4t5r4
2l1u4t1s4
lut4tal
lu6tt
lut1ta
lut4tap
lut4t5at
6l1u2t1v
1lu1v
lu4va
lu4ve.
lu9ven
lu9ver
8lv
l1va
l4va.
lva6k
l4vak1ti
l1va6k1t
lva6la
l4va4m
l4va1na
lv8a6nd
l7var.
l4v7a1sa
l4v5a4s1s
lve3d4a
lve2d1
lv2e5i6s
l4v2e1la
l4ve1le
l1v4en
lve9ne
l9v8er.
l8ve9rau
lv2e1ra
l4v2e1ru
l4vesk
lv4es
l2ve5sl4
l7v6et.
lv4et
l4ve5str4
l9vé
l1vi
lvi8e1ne
lv2ie
lvi1en
lvi9er
lvi6ka.
lv2ik
lvi1k2a
l4v9im
lvin5g6r4
lv6i6ng
l4v3iro
l1v2ir
lv1j
lvly8se.
l6v1l2
lvl2ys
lvly1s4e
l6v3n
l2v1of
l2v9o6p
lvo8re.
lv8o1re
lv2o4r4s
lv5o4v
l6v5p4
lv7ra8r
l2vr8
lv9ri
l8v1s2
lv5se
lv7s6k
lv1s6l4
lv9ta1
l2v1t
l5v4ø1e2
lv1å
l3v2åg
lvå4p4
lvå6r
l5w
ly1a
2ly1b
ly4d7r4
l4y5e
lyes3
ly4gel
ly1ge
ly5g6l
ly8is
ly1i
2ly1kj2
l6yk
lyk6ke1ri
ly2kk
lyk1ke
ly5ku
ly7kv2
6lykø
ly5l
ly5me
2ly8m3p2
ly2n3a
ly4ne.
ly1ne
ly4n5il
ly1ni
ly6n3s2
2lyo
ly5ok
ly3p8e
ly1r8
ly8ra.
ly1ra
ly6re.
6l7y2r1k
ly4sa.
l2ys
ly1sa
ly4s5a4k
lyse6te.
ly1s4e
lys2e1te
lysk4
lys3kj2
ly2s9k3l4
ly2s1l4
4lysp2
lyst9ra
lys2t
lyst3r4
6ly1sy
ly8ta.
ly1ta
4lytek
ly1te
ly4te1ri
ly2t4e5s2
l5y4tin
ly1ti
ly1tr4
ly3ve
ly1v
l3z
l6z5b4
1læ1
6læd
læ6ra.
læ5rar
læ4r4a2r1m
læ4re.
læ1re
læ8r7e3i
læ3rer
læ4re3s
læ5r2es.
lær6sv2
læ4rs
læ6ta
1lø
lø3de.
lø1de
lø4del
lø5dem
lø4er
l4ø1e2
løk5kj2
l2ø2kk
4l1ø2k1n2
2l1øko
lø4k5r6
l5ø6l
lø9me
lø2na
løns5t
lø6n2s
lø2p6s5
5l4ø4rd
lø2r5k6
lø2r5n
4l5ø2r1r
5løs.
lø2sa
lø5san
lø5ser
lø1se
løs3k
lø4s3s
2l4øt
lø9ta.
lø3te.
lø1te
lø4teg
løva9r
6l7ø6v8d
lø4ve.
lø3ver
lø5v4e1s
6løy.
4løya
2løyd
4lø4y1e
løy8ed
løy5el
2løy1f
løy4g
løy1g5e
4løym
6l5øys.
lø2y1s
løy5ter
løy1te
lå6gal
l2åg
lå1ga
lå6gre
lå1g4r4
lå8gs4
lå4gå
2lå1i
lå1k4
lå6ke.
lå1ke
lå9me
l6åm
lå2m5o
lå2n
6l5å6nd
lå6n2s1
2låp
l1å2p9n4
2lår
lå8ra.
lå9rar
lå5ras
lå3rin
lå1ru
lå6sa.
lås2
lå1sa
lå1sk
lå6sko
lå4s5l4
lå4s1te
l4å1st
lås7ten
lås5ter
lå5su4
lå3te.
lå1te
lå4teg
lå4tek
lå8ti.
lå5ti
4lå6t1t
lå4ve.
lå1v
1ma
2maa
3maa.
ma3ar
2mab
ma5b1r8
ma3che
m8ac
mada5me
m6a1da
ma4del
ma1de
ma3dra
madr4
m6a8d9s
4mae1f
m2a1e2
4maek
7mae1ne
2ma1f
3mafia1
ma1f4i
3mafr2
6mafrå
ma8ga.
m4a1ga
ma4ged4
ma1ge
ma4gel
ma4g6e5s2
3mag1n
ma2gr4
2mahe
ma1h
ma4is
ma4ja
ma1j
2m6ak.
ma5kab
ma1ka
4m1a2kad
ma4kes
m2a1ke
mak7ke
ma2kk
4m2ak1l4
mak6le
ma4k2ot
m2a1ko
mak2r6
ma3kre
mak5r2on
mak2ro
mak4tal
ma6k1t
4m3ak1ti
mak4to
makt1s4t4
mak4ts
ma1ku
2m2a1kv2
ma1la
ma7l2e1b2
m2a1le
ma6le6ng
ma4let
mali9e8n
ma1li
mal2ie
ma2l5l
ma4lov
m2a8l1s4
mal4t5ek
ma2l1t
mal1te
mal3u
ma1l3å
4mamer
ma1me
ma3mo
m4an.
ma3nak
ma1na
m3anal
6manam
4manav
man8ce
man1c
man4dom
m6an1do
ma6nd
man4d2on
ma3ne
m4a4n1f
man5g4a
ma6ng
m6an1g4e
4man1gr4
mania8
ma1ni
2ma4n1l
m3an5le
4ma4n3m
5m4a4nn
man4nem
man1ne
mann6s5l4
man6ns
ma4no
2ma1o
4ma1pa
2mapr6
4m1a2r1b8
ma4r5d6
7ma1re.
ma1re
6mareg
ma3rei
ma7rel
5ma9ren
ma5res
3m2a1r4i
mari8e9ne
mari1e4n
mar2ie
ma3rin
m4arka
ma2r1k
4m3arkit
mar3ki
mar4kv2
ma2r5m
marmo9ra.
mar1mo
mar5mo1ra
m2a2r7n
ma1ro
ma4r1o6p
mar3s4h
m2a4rs
mar7sl4
mar5te
ma6r1t
ma4ry.
ma1ry
ma6rå
4mas4el
ma1se
ma5set
mas2h3
ma4sia
ma1si
ma4s2ik
ma4s2is
6masju
ma1sj
ma1s4k2i
4masko
4masp2
mas4se3e2
ma4s1s
mas1se
mas6set
mas1sø9
7ma6st.
ma5s2tem
mas1te
4masto
4ma5str4
2ma1sy
m6at
m4a1ta1
ma5tad
ma3te
ma4te.
ma6ted
ma4tel
7m4aten
7ma5ter.
m4ater
6mat2e1ra
7matet
6m7at1fe
ma6t1f
4mat4il
m4a1ti
7matil.
ma1to
ma1tr4
4ma1tra
ma4t5ras
ma4t3re
ma6t7rom
m2a4t3s2
mats8l4
mat5ta
ma6tt
m8at7t8r4
2mau
mau4k
mau7l
ma1un
5maur
mau7su
2mav
ma6ve.
m2a1ve
ma5ven
m7a2v1h
m5a6v3k6
ma4ze
ma3zo
2m1b
m4ba1o
m1b4a
mba4r3d
m2b2ea
m1be
m4b2e1b2
mbe9da
m4be1dø
m2b4e1f
m2bek
m4b2e1li
m2bem
m4b2e4na
m4be3o6
m4bereg
mb4er
mbe1re
m4b2e1ro
m3b4et2
mbi6ar
m1b4i
m2bi1b2
m3b4l2
mb6o1e2
mb2o
mbo4e6nd
m1boe2n
mbo5er.
mbo2er
mbo5e4re
mbo5id
mb2o5n
m4bo1p4
mb8o5re
m1b2o9t
mbu7ar.
m1b2u
mbu4ar
mbu4e
mbue7re
mbu1er
mbus5
m1c
m6co
2m1d2
m3de
md7om
m1do
1me
2m2ea2
me5al
mea5m1
2m2e1b2
2m2ec
me2c6k4
4medat
me1da
2me2d1b2
5me3de.
me1de
me4ded
me2d5ei
me7den
me7det
4me2d1f
me3di
4med2ik
4med2ir
2me2d1m
me6dok
me1do
4medom
2me1dr4
me6dret
me8d1s4
4me1du
me6dun
me5d4u1s
me2d5v
7medve
2me1dy
4medå
me7e2
2mee1f
6meek
2meel
4mees
4mee1v
2me1f
m3e2ff
6m2e2ga
me7gal
2me1g2r4
2me1gu
4me1h
me2i7e
mei1e5n
m1e2ig
me3i6ld
m4eil
me3i6n1d
me4i5ni
me7isk
m2eis
4meiso
2me1j
4me1ki
4m4e1kj2
2m2e2kk
me2k1l4
m4e3k1n2
4me1k2o
2m2e5k8r6
4meks
me6k5t
6mek1te
m2el
4m2e1la
me5l4aks
me2l1ak
5mel6a3ne
5melar
me3le
me4le.
4m4eled
4melei
4melek
6m5e6lem
me4l5e6ng
m4elen
4mel2ik
m2e1li
4melis
4melit
4meliv
mel5le
me2l1l
mello6m3
4melok
m2e1lo
4me1lov
mel4si
me8l1s
mel2s3j
melsk4
mel5s4t
mel7t2r4
me2l1t
me1lu
me8l5v
mel1vi6
2m4e1ly
2m2e1læ1
4m2e1lø
4m2e5l4å5
2mem
me6mo.
me1mo
4m5en1g6a
me6ng
me2n5k
menle6ge.
me4n1l
men5le
men3le1ge
m6e4nn
men4ny
me4nom
m2e1no
me4nor
men4si
me6ns
men3s1m
men5s1pl6
mensp2
men5te
me6n1t
men6tek
men4tom
men5tr4
me4nyt
me1ny
2me3o6
me6o1s
2m2e1p
5mer.
me6rab
m2e1ra
4merad
me4ra5l
me4r5a6n1t
mer5di
me4rd
4mered
me1re
4mereg
4merei
4merek
4merel
me6ren1se
mere6ns
me3res
4me2r3e4s1s
mere6t
m4e1ri
4me5ri1b2
meri5ke
mer2ik
5merin
merle7g
me2r1l
mer1le
m2e2r9n
mer5os
m2ero
5me4rs
mer5sk
me3run
m2eru
mer5u6nd
4me1r4us
2me1ry4
m2e2r3ø
2m2e1rå
me4rå4k
5m2es.
2me5sa
4me5s4h
2me1sj
2me1sk
me2s5ke
2me1sl4
mes6le
4m4e3so
2me1s2p2
5me4s1s
7me6st.
4mesta
5me6s5te.
mes1te
mes4ti
6me7sto
mest3r4
me5stro
6me1s2trø
4me3strå
4me1stu
4me1su
2m2e3sy
2me1sø
4metab
me1ta
4me9t4ap
me4tar
m2e3te
4meteg
4metei
4metek
4metel
4me2t2e1p
4met4il
m2e1ti
2m2e1tj
4me1t2on
m2e1to
me4t3ra
m2e1tr4
m8e4tre
met5ren
met7rer
me2t5res
met5r2ik
me4tru
4met6ræ
2m2e1tu
4m2e3t2v
2m2e1ty
2me1tø
4me1tå
2me1u4
7m4e2u3s
2me1v
meva8n
2me1ø4
2me1å6
2mé
2m1f
mfa9ra
m1fa
m3far
mfar8ta
mfa6r1t2
mfav5
m2fek
m1fe
m6f3e4s1s
m4fi.
m1fi
m4fib4r8
mfi1b2
m2fit
m5fr4u5s6
mfr2
m5fun6n8s5
m1fu
mfu4nn2
mfu6se.
mf2u1se
2m5g6
m4ga.
m1ga
m4gi.
m1gi
mgå8v4a
m1gå
mgå1v
mgå8ve.
2m1h
mh2e2a4
m4hu.
1mi
mi1a
5mi6al
mia2n
9mia1ne
7mi1ar.
2mi1av
mi6c
miche6l
8m9idé
2mi1di
mi8d5j
mi8d3s4
mi4d4t1
4mi3e1le
m2ie
mi3er.
mi2e9s8
2m2i5f
5migraf
m2ig
mig2ra
mi1gr4
2mi3h
2mi1i
4mi1j
mi4k6h
m2ik
4mi1kj2
2mi1ko
mik5ro.
mik1r6
mik2ro
mik5r2on
mik5sa
6mi1ku
mi4la.
mi1la
mile6t
mi1le
mi2l7e3ti
mi4le1v
mi7li
4milin
mil6s5v2
mi8l1s
4mi1læ1
2mim
mi8ma.
m4i1ma
5mi1mo
m7i8m1p2
mi4na.
m6i1na
mi5nar
mi4ne.
m4i1ne
mi4n5e4rs
4m3in4n1h2
mi4nn
4m3in6n1s4
6m1in6n7t4
mi6n2s
m1in3sp2
mins4t
m6i3nu
m4i6n1v
mi4n5y
mi7ov
m4io
2mip
4mi1sa
mi4san
mi3se
4mi2sek
4mis2el
9m8i1sé
misha9ge
mis1h
mish2a
misha2g
4mi7si
mi2sk
mis4ko
mi3s4la
mis1l4
mis9le
7mis1m
mis4s5k
mi4s1s
mis4s7p2
mista9k
5mi2s7te
mis4tra
mi2str4
4mistu
2mi1sy
4miså
6mi1ta
mi3te
4mitj
4mi1to
2mi1t6r4
mit6t3s
mi6tt
mi5ur
mi1u
2miv
mi5vå
m6ja.
m6jan
m5jar.
m7j2a2r2n
2mj2e
m7je.
m1ji
m8jingan
mji6ng
mjin1ga
m8jingar
8mj2o
mju7ke
m4ju2k
1mj2ø
mjøs5t2
2m3k2
m4ko.
mk2ro5
mkr6
mku6le.
mku1le
mkå8pa
4m3l
m7la
ml4a6ga
m2le1f
m1le
mlei5er.
mle2i7e
m2lek
m2lel
m2lem
m4l2e1ra
mle6se.
ml2e1se
m4lesk
m6le5s8v2
m4le1ta
mle4ve.
mle1v
m1l6i
ml5ja
m2lj
mly6se.
ml2ys
mly1s4e
mlø6pe.
m1lø
mlø1pe
mløy3
2m1m4
m6mai
m1ma
m4m2a5k1l4
mmal5
mma8le.
mm2a1le
m4mam
mm6an1do5
mma6nd
mman6dol
m5mar
mma3r7in
m3m2a1r4i
mma1r7o
mmatik7ka
mm6at
mm4a1ti
mmat2ik
mmati2kk
m4me1dø
m1me
m2meg
m2mei
m2me3k2
m4me1lu
mm2el
m6me1ni
m4m2e1nu
mme6r5t
mme4run
mm2eru
m2me1s
mmest6
m5met.
m4me1ta
m4m2e1ti
m4me4t3ra
mm2e1tr4
mm8e5t6re
m2me3u4
m3mé
m4mi1a
m1mi
m7mia2n
m2mi1b2
m4mid
mmi1e6n
mm2ie
m2m2ik
mmi5sk
mmi5so
mmi3st
m2m5n
m3mu
m1mø2
mmå8la.
m1må
mm6ål
mmå1la
2m1n
m2ne3e2
m1ne
mn7eid
m2nei
m2ne9l
mn2e4ra
m5n2e1se
m4nesk
m4nesta
mnes9t2i1e2
mnes1ti
m9net.
m4n2e3te
m2ne1v
m6nip
m1ni
mn7sk
m6ns
1mo
3m2oa
mo2ar
4m2o3b4a
5mo1d4e
m2o1d
mo3dem
mo5di
mo6di.
2mo1dy
3m6o1e
mo4en.
moe2n
m1o2ff
mofo6bi.
m2o1fo
mof2o3b4i
mo6gi
m2og
5mogl
mo5go
m5o8gs1
4m2o1h6
2mo1ka
mo8ka.
mo8ke
mo1ki
mo6la.
m4o1la
m2o3le
mo4le.
mo7le6s
4mo2l1t
mo3ly
m5om.
4mo2m1f
2m1o2m1r6
mom4s5ø
mo6ms
mo5ne
m2on
mo4ni1sa
mo1ni
mo2no
mo6n1s
mon4s1te
mo6n5t6
2mop
5mo1ra
mo4ra.
mo4rar
mo7r4ar.
mor5d6e
mo4rd
4mor4d1l4
mor6d5r4
m8o3re
more1s7
m2o1ri
mo4ri.
mo6rid
4m3o4r2ie
mor4kl4
mo2r1k
morl4a8ga
m6o2r1l
mo5rok
m4oro
mo4rom
mor4si
m2o4rs
mor4skj2
mor7sky
mor4sp2
m2o1rø
mo9s2en
m2os
mo1se
mo2s7k
mo3s4o
6mostab
4motap
m2ot
mo1ta
mo4te.
mo1te
mo6te6g6e
mo4t6ei
mo2te7kl4
mo5ter.
mo4t4es
mo5to
4mo1tr4
mot7re
mo4t1s2
6motsa8g1d
motsa2g
motsva5
mots1v2
mo6t7t
mou4r5
3mo3va
mo5w1
8m1p
m4pa1na
m1pa
m4p5anta
mpa6n1t
m4pe3e6
m1pe
m4pel4ot
mp6el
mp2e1lo
m6p2e1na
m6p2e1p
mpera8te.
mpera5t
m4per6a1te
mp2e1ra
mpe5res
mpe1re
m6pe2r3e4s1s
m4p5er1fa
mpe2r1f
mperi6e7n
m3pe1ri
mper2ie
m2pe1s2
mpes6te
mpe4s1ti
m5pe6tt
m2pe5u
m2p3id
m4pinj2e
mpi4n1j
m8p1la.
mpl6
m3p4lan
m2p5le.
mp1le
m6p5lin
mp2li
m6p5n4
m6p5ob
m1po
mp6o1e4
m4poe1ta
m4p2og6
m6pok
m2pop
mp3o2pp
m2p1p8
m4p3rad
mpr6
mp5ret
mp3rop
m1p2ro
mpr2o8pa
m2p1s
mp3sek
mp1se
mps4p2
mp5s6t2
mpun6ge.
m1pu
mpu6ng
mpun1ge
m9pur
mp5ut.
m6p5ys
mpø5
m9på.
m3q
2m1r6
mro8sa.
mr2os
mro1sa
mro6se.
mro1se
mru7te.
mru1t8e
mrø9de
m9r1år
6ms
m5sa.
ms1ak
ms5a6n1t
m1sc
m2se5lu
m1se
ms2el
m9s6ei
m4sem
m4s3e6ng
ms5e4p2l6
m2s2e1p
m4se2r1v
mse5s
m5s6e8t1n
mse6t7ja1re
ms2e1tj
mse8t9jas
ms2i6e
m1si
ms4i6ng
m2s3i4nn
m4s5ja
m1sj
m4s5kab
m1ska
m2ska9k
ms6kin
m1ski
ms1k5i6ng
ms3kor
msk8u
ms3lan
msl4
ms6l2e1ga
ms1le
m8s9lu7a
m1slu
m2s3lu2e
ms4ly
m2s1m
m1s2n
ms9ne
ms5no
m2s3næ1
m1so
6m4s1o6ms
ms3o2ri
m2s1ov
m4s3s2
m4s3tal
m8s7te.
ms1te
m2s7t2ea
ms2ti
ms5ti2l1b2
mst4il
ms3tim
m1sto
m4s5top
m5s6to2pp
m6s8t5o4rd
m8stra6nd
mst4ran
mstr4
m5st5r4a1ne
ms4t5red
ms5tre1f
mst5ren
ms5tr2ik
ms4tru
ms3u4nn
msu9ta
m2s1u4t1
msu7t2en
msu1te
ms1v2
msva9ra
msva5re
m3s4v4et
ms3y6nd
m1sy
ms2yn
msø4ke.
m1sø
m3s6ø1ke
m4s5ør
ms1øy
ms1å
2m1t
m3ta
m6t4b2
mt1be6
m2te3e4
m1te
m2teg
mteks7
m6te7k3v2
m4tel
m6te1stu
mt4es
mti9a
m1ti
m9t2i2da
m3tid
mt2i5e2
m6tien.
mti1en
m6t2if
m2t2ik
m6ti1ni
m4ti1ø8
mtiør6
m5to
mt4r4
mt6ve
m2t1v
mt4vin
mtå5
1mu
mu6a
m1u2b
mu2e
mu3el
mu1e7r
mues1
2mug
mu4g5l
mu4he
mu1h
mu2k
8m9u1k2a
4m1u1ke
mu5la
mu4leg
mu1le
mu2le6s
mu2l1l2
mul8l6s7
mul6tiv
m4u2l1t
mul1ti
4m1uly
7mum
m2u3mi
mu6m2s1
mun2c
mu2ne1s6
mu1ne
4mu6ng
mun6ge.
mun1ge
6m5univ
mu1n6i
m4u2n3k
mun6n5s6
mu4nn
mun4t3r4
mu6n1t
mu6ra.
mu1ra
mu4re.
mu1re
2mu2rn
mu4rs4
6mu6r1t
m2us
mu4se.
m2u1se
mu4ses4
mu4sé
mu2s3k
mus2k2e6l5a
mu2s1ke
must4
mus5tan
2mut
3mu1ta
mu8ta.
mutsa8la
m1u4t1s4
mut7t6r4
mu6tt
2m1v
m1va6k5t
mva6la
mv2a1ri6
mve8g5s4
mv2i7e6
mvi6se.
mvi1se
2mw
1my
my5a
myg4ga
my4g1g
my3ke
m6yk
myk4kes
my2kk
myk1ke
myk3l4
my8kr6
my2ra
my9ran
my9rar
my1re
my4re.
4my2r1k
m5yr1ke
my4r5u
m2ys3
my6sa
my4se.
my1s4e
my4so
my4te.
my1te
myt6t6s5
m4y6tt
5mæ
mæ6la.
mæ1la
1mø
mø2b3l2
mø1b
mø7de1s
mø1de
m4ø6e2
mø9e6ns
møk1k6a
m2ø2kk
m3ø2k1n2
2m1øko
mø6na
mø4ne.
mø1ne
møne9s
mø8nest
6m5ønsk
mø6n2s
mø2o
3mør1ke
mø2r1k
mør4k5r6
mør5s1m
mø4rs
mør3ø
mø5se
7m4øt
mø9ta4s
mø4te1re
mø1te
mø6t9t
2møy
møy9ar
m5øys.
mø2y1s
1må
2må.
må5a
6måe4n1h2
m4å1e
måe2n
m2å7g2
må1k
må4ka.
må1ka
må4ke.
må1ke
må4ke5s
m6ål
må5lar
må1la
må4le.
må1le
må6led
måle3i
målø6pe.
må1lø
målø1pe
må4ne5s6
må1ne
må9n2e1se
må8pa.
må1pa
må4pe.
må1p2e
må7pl6
2mår
måra6r
må1re
må1ro
må1ru
må1s4
må6tak
må1ta
6må6t1f
må5tr4
må3tø
1na
na6a6ns
na1a2n
2nab2o
7na7bort2r4
nab2o6r1t
na6b2ot
4na1by
na4ded
na1de
na8dem
na1di4
n3a2dop
na1do
na8d2s1
4n1a2d1v
8naf
n1a2ff
nafo7r
n6a1fo
na3fr2
na2f7t
na8ga.
n4a1ga
na3ge
4nag2en
na8g3s4
nagså5
na7gø
6na1h
4na2ir
2nak
n6a2kk
nak7ka
nak6ko
5n2ak1l4
na1kr6
n1aks
nak8sa
nak8se.
nak1se
8n1a6k1t
nak6ta.
n2a7kv2
na9la4g
na6la1re
6na7la2r1v
na7leg
n2a1le
na4l3ei
na4lek
na4l5e1po
na2l2e1p
na4le1s2
na4l5e6tt
na4le5v
na2l5g
nal6ge.
nal1ge
na4lil
na1li
nal6lag
na2l1l
nal4løp
nal1lø
na2l3op
n2a8l5s2
na2l3t
na3lur
2n6a1ly
na2l5ø
4na1lå
na3lå5r
5nam.
na7me1re
na1me
na5mes
na5mo9
5na6m1s
2na1mø
9nan.
4na1na
n3anal
n6a1ne
nan1fø8
na4n1f
4n5an1gr4
na6ng
na2n5k
nan6ke.
nan1ke
4n3a4n1l
6n5an5le
nan4ne
na4nn
na4n5o
4nn
nn4an
n4n5a6ns
n1na
nan4sin
na6ns
nan1si
nan4skj2
nan4s5t6
4n6a1ny
na7o
na5pe
na2pe4s
na2p3s4
n4ar.
na4rap
na1ra
2n1a2r1b8
nar5dr4
na4rd
4n1a2r2ea
na1re
na2r7ei
4na5rek
nari4e5n
n2a1ri
nar2ie
7narik4sk
nar2ik
4n3ark6iv
na2r1k
nar3ki
6n5ar2mé
na2r1m
6n5ar1me
nar8ma.
nar1ma
n2a4r5s
nar8s1te
nar1s2t
2na6r1t
nar6ta.
nar5ti
na2r7v
nar5ø
nasa3r
n4a1sa
na4sas
nas7h
8na3s2ik
na1si
na4sk2i
na2s5t4
nas1ta5
8na1su
n4a1ta1
nate8k
na1te
na7tem
4na6t3l6
4na2tom
nator2i5e6
na1tor
nato1ri
na1t8ra
natr4
nat3sp2
n2a4ts
nat6tak
na6tt
nat1ta
nat6t2ea
nat1te
na1ty5
2nauk
naus6p2
na6va.
na1va
4n1a6v1d
6na2v1f
2n1a2v1g2
2n3a2v1h
na1vi
4n5a6v3k6
4na6v1l2
nav4les
nav1le
3n6a2v1n
6na1vo
4na2v1r8
n1a8vs
4n1a2v1t
4n1b2
n5b4a2
nba3d
nba9ser
nba1se
nbe6n4s
n1be
nbo5et
nb2o
nb6o1e
n6buf
n1b2u
n6but1r4
nbyr5
n1by
nbø9le
n1b4ø
nbø6n
n1c
n5c4a
n1cel5
n3che
n4ch3e2i
n6c2ot
n2cy1
6nd
n7daa.
n1da
n4daa
n6da1b4i
n4dad
n2da5f
n7dag4
nda8gs5
n4daks
n3dal
n4da4l1f
n4d5a2l1l
nd2a8l1s3
n4da2r1k
n6d3a6r1t
n8da4sk
n4da5tal
nd4ata5
n6d5d4
n3de.
n1de
n2d2e5a
n2d2e7b2
n2ded
n5d4e1fi
n2de1f
n2d1ei
nd4e1in
nd6ek
n4de1k2l4
n4de5k2o
n5d4e5l
nde4le.
nde1le
n4del2ik
nd2e1li
ndel4sk
nd4e8l1s
ndel4st
n2dem
n5d4em.
nde5mo
n5d2en.
n6dener
nde1ne
n5d2e6ns
n2de5o6
n2d2e1p
n4derab
nd2er
nd2e1ra
n4deras
n4derei
nde1re
n4derim
nde1ri
nd6e2r5k
nderl4a6ga
nde2r1l
n4de1si
nde1s
n4d4es2t
n4de1su
n1dé
ndi4en
n1di
nd2ie
n4d3i6n1t
nd6i6sk
ndito1ri5
ndi1to
nditor2ie6
ndit4t5a
ndi6tt
nd3jer
ndj2e
nd1ju
n2d5k2
nd4lem
n4d1l4
nd1le
nd4l2e1se
nd4lest
nd4le1v
nd2o9a
n1do
n6do2b3
n6d5o2kk
nd4o6m4s3
n2d2oo
n2d1op
n2dor
nd3o2r1g
nd5o2r1m8
n6do1te
nd2ot
nd5r2a1e2
ndr4
n4d3ram
nd3ran
nd6rek
ndr2e4ra
n4d1rer
nd5re3s2en
ndr2e3se
nd5re6tt4
nd1ri
n1dro
nd4sag
n8ds
nds3ak
nd4s5a2m1b
nd1s2am
nds5e8lv
nd1se
nds2el
nds5en1de
ndse6nd
nd4ser
nd4sj2e
nd1sj
nd4skj2
nds7kul
ndsl4a6ga
nds1l4
nds6le6tt
nds1le
nd7s1pu
nds1p2
nd3s4te
ndst4
nds9teg
nds5trek
ndstr4
nd5stry
ndt4a6ka.
n4d1t
nd3t4a
ndta1ka
n6duf
n1du
ndu3is
n4dun
nd3u6ng
ndun6ge.
ndun1ge
n2dup
ndu6s2i
ndu1s
ndu2s7k
n2d7v
n2dyg
n1dy
nd6y1k
n2d2ys
ndy5sp2
ndø5l
n1dø
n2d1øy
1ne
2n2ea
5ne2a1e2
nea4g
ne7a6le
ne5a2l1p2
ne5a8l1s
nea9m1
ne9a8r9an
ne1ar
nea1ra
nea9ren
nea2re
ne1as
6ne7av
2n2e1b2
3ne2b1b
4ne1be
ne4b1le
nebl2
ne2c6k5
n2ec
2ne1da
3nedal
ne6d3d
6n5edd2ik
ned1di
4ned2e7b2
ne1de
4ne2deg
4nedel
5ne2d1g4
ne4di.
ne1di
ned4i4s
2ne1do
ned5o4ve2r7
ne2d1ov
ne1d1r4
ne6dre
5ne8ds
ned3st4
6ne1du
4ne1dø
ne5dår
4neeg
ne3e2
4neei
2neek
5ne4e4r5
2ne1f
n1e2ff
n5e8ft
2neg
5neg4a3ti
n2e1ga
neg2at
ne3gl2a
neg5le1s2p2
neg1l6e
ne1g8r4
6negru
6ne1h
2nei
n2eid
nei8dan
ne2i1da
n5e2ig
n2e1in
n3e4i1ni
5n2e2ir
2ne1j
2nek
ne4ka.
n4e1ka
6ne1k2l4
n4e3k1n2
n2e1k2r6
nek5rin
ne3k2ri
n7ekser
nek1se
ne6k5t
4n2e1la
nel3de
ne6ld
4n4eled
ne1le
6nele4g1g
7neleg1gj
4nelei
nelei5er
nele2i7e
4n3e2lem
6ne5lê
4nelid
n2e1li
4neli1ga
ne3l2ig
4nelis
4nelit
4neliv
nel4lov
ne2l1l
ne8l7s
2ne1lu
4n4e1ly
2n2e1læ1
2n2e1lø
nelø8pa
4nelås2
n2e1lå
2ne1ma
4n3e2m1b
4nem2el
n8e1me
4nemer
6nemes
2ne1mi
nem2ie8
6nemj
ne2m6k2
5ne2m1n
nem5ne.
1nem1ne
nem7ne9l
2ne1mo
2ne1mu
2ne5my
2n6e1mø
2ne1må
3nen
4n2e1na
nen5at.
6n5en6den
ne6nd
nen1de
6n1endr4
1ne1ne4
4nened
4n4enem
ne3nes
4ne2nesl4
4nenet
ne2n5e1ta
4n4ene1v
nen4ga.
nen1g6a
ne6ng
4n3enhe
ne4n1h2
6nenhet2
4ne1ni
nen5se
ne6ns
nen2t5ei
ne6n1t
nen1te
6n3en5tr4
4n2e1nu
4ne1ny
4n4e7næ1
2ne3o6
5neo.
ne5o4r
7ne2o1s
2n2e1p
ne5pe
3n4er.
4nerad
n2e1ra
6nerap
3n4e2r1b8
4ner2ea
ne1re
4nered
nere6de.
nere1de
4nere1f
4nereg
4nerek
4nere6n1t
4ner2e1p
ne5res
4ne2r3e4s1s
4ne1re1su
4nere4t
4nere1v
4ner3far
ne2r1f
ner1fa
4n2e2r2g
ner3ga
ner3ge
4ne3ri1b2
ne1ri
4ner2ik
4nerit
ne4ri9v
5ne2r1l
nerl4a8ga
n3erob
n2ero
6ne1r4oc
ne7rof
ne5rok
4ne1rom
ne5r2ot
ne1r4ov
n6e6rs.
ne4rs
ner7se
ner5s4i
n6er1s4p2
ners8ten
ner1s2t
ners1te
4ne3rul
n2eru
ner5un
2ne1ry4
3ne2r1yr
4ne1rør
n2e1rø
2n2e1rå
2ne1sa
ne4sa.
5nesar
4nesc
4n2e1se
6ne3sek
nes6er
5ne9s4e4ts1
nes5eva
ne2se1v
ne3si
nes5i6nd
4ne3s2it
2ne5sj
ne5s1kam
ne1ska
4ne5sk2ar
ne2s1k4e
4ne1ski
5nes2ki6n1s
4ne1skj2
4ne5sko
4ne1skr6
6ne1sku
2nesl4
ne3s1li
ne7s1lø
nes6mi
nes1m
6nes1n
ne7s1nø
2n4e1so
3nes2ot
2ne1s2p2
nes6s2am
ne4s1s
nes4stu
ness2t
5ne6st.
4ne1stas
nes9t6as.
ne6s9te.
nes1te
4nesto
nes5tor
4ne3str4
4ne1stu
4ne1stø
2ne1su
2ne1sv2
nes8va
6n2e3sy
4ne1sæ
5nesøy
ne1sø
4ne5så
neså5re
neså2r
3net.
2ne1ta
4netaks
ne3tak
neta6le.
ne3tal
ne3t2a5le
n2e3te
5nete.
4n2e3ti
6ne3tid
2n2e1tj
2n2e5to
6netom
2n2e1t4r4
3ne4ts1
net5s4p2
net1t3a4
ne6tt
net6tel
net1te
2n2e1tu
n2e4t2v
4n2e1ty
4ne1tå
netå5ker
netå2k
netå1ke
2ne1u
ne2u1r
ne5us
ne6va.
ne1v
ne4ve.
6ne8ve6n1t
ne1ven
5nev2ik
nevi8sa
3ne2v1n
ne2v1r8
ne4y5t
ney1
2ne1ø4
4ne1å6
1né
4né1b
4né1d
2né1f
6né1h
2né1l
4né1m
6né5o
2né1p
5né1r
4né1v
4né5å
4n1f
nfa2l1l4
n1fa
n5fal
nfal8l1s5
nfa6n5t
n6f2oa
n1fo
n4fob
n4f2o1le
n4fom
n4for1a
nfor9en.
nfor7e6n
nf8ore
nf2os4
nfø5de1s
n1fø
nfø1de
nføy8ed
nfø4y1e
6ng
n8g8ad
n1ga
n4gaf
n6gag
n6g1ak
n6g5and.
nga6nd
n8gan8da
n4g3a2n5k
n4g5a2r1m
n4ga6r1t
ng5art.
n5garta
ngar5u
n2g2at
ng5a1to
nga4ve.
ng2a1ve
ng7avi
n8g1d4
ng4ded
ng1de
ng4del
ng4d2e1p
ng3dr4
n4ge1da
n1ge
nged4
n4g2e1la
n3g2en
n4g2e2n1a
n7ge1ne
6n4g3e6ng
n6g2e1no
n4g2e1nu
n3g2er
n4gere4t
nge1re
nge5run
ng2eru
n2g6es
nge3sl4
n4ge1ta
n2g2e7v
ng1fø4
n2g1f
n4g5g2
n2g5id
n1gi
6ngje6n5g
n1gj
ngje2n
ngj2e
ng1l
n5glem
ng1l6e
ng4lu
ng7n
ng2o4d
n1go
n2g5o6d1d
n7g6o1e
n3g4ok
n8g7o8m
ng9o1me
ng5o6nd
ng2on
ng7o6pe
n2g1op
n2gor
n2g5o4rd
ng3o2r1k
ngos6p2
ng2os
n2g2ot
ng3ra.
n1gr4
n5grad.
ng5ra6nd
n2g7r2ea
ng3reg
ng3ren
n4gre6nd
n4g1rer
n6g5rest
ng3ret
ng3re1v
n4g3rid
ng3rin
ng7ro.
ng9r2oa
ng7r6o1e
n5g6r2os5
n6g3r6å1d
ng8sa.
n8gs1
ngsa2
ngs5e8lv
ng1se
ngs2el
ng4sem
ng4ses
ng2sj
ng2sk
ng7s4kj2
ngs7leg
ngsl4
ngs1le
ng5sløy
ngs1lø
ngsmå6la.
ngs1m
ngs1må
ngsm6ål
ngsmå1la
ngs3ne
ngs1n
ng2s7t2e1p
ngs1t6e
ng9s8t5rid
ngstr4
ngstyr8ke.
ng1sty
ng3s4tyr
ngsty2r1k
ngstyr1ke
ngs9t2ør
ng1stø
ng3u6nd
n1gu
ngu5ru.
ngvi4s
n2g1v
ng5y1e
n1gy
n1gø
n2g1øy
ngå8v4a
n1gå
ngå1v
ngå8ve.
4n1h2
nhat5
nh2a
nhe4t4s1
nhet2
1ni
ni1ak
ni1a2n
nian5d2e1p
nia6nd
nian1de
ni2bl2
ni1b2
4ni1by
4ni1b4å
4nicr8
n8i1da
ni4del
ni1de
ni7de1le
n5idé
ni6do
nid7r4
ni8d2s4
nid5s3t4
4nieg
n2ie
ni1el
7ni3e2n
ni3er.
ni2e5ri
ni7e4rs
ni5e6r5t
4n3i4fr2
n2if
ni5gl2a
n2ig
nig1l
ni9glo
2ni3h
8ni1j
ni3ke
n2ik
2ni1kj2
4ni5ko
2nik1r6
4nik1v2
ni5l4i
5nilu
4nimar
n4i1ma
4nima5s
4ni1mo
2ni8m1p2
ni6n7al
n6i1na
2n1i6nd
4ni4n1f
nin1g3o
ni6ng
nin5gr4
nin8g6s5
ning1se4
nings5t6e
ningst8
2n1i4n1j
n1i4nn
4nin4n1b2
4nin4n1h2
4nin6n1s4
4n1in6n7t4
2n1i6n1s
2n1i6n1t
2n1i6n1v
5nio.
n4io
ni2on2
nio6ns3
4niop
7ni2os
4ni5ov
ni4pet
ni1pe
ni9pet.
ni9pe4t1s6
ni2pl6
nip3li
nip5si
ni2ps
4n2ir
nir7kel
ni2r1k
nir1ke
4ni2sem
ni1se
ni5set
nis5im
ni1si
4ni3skj2
nis4k3o
n1i9sol
ni7s6o5ne
ni3s2on
ni4s1s4
4ni1stas
6ni1stat
nis5t2ik
nis1ti
4nist4il
ni3str4
4ni1sty
4ni1stø
6n6isu
6ni1sy
n4it
ni3ted
ni1te
6nitj
ni4t2og
ni1to
ni4t2o4s
nit4t4r4
ni6tt
nit6t4s3
nitt6sk
4ni1tu
ni3ty
ni2t5z
nitæ1r6e
nitær1
ni1tæ
2niu2b
ni1u
ni4u2m1f
2niut
4n4i1va
2ni1ve
ni4v5ei8
4n5i1vo
ni8v3s2
6ni1å4
4n1j
n6ja1h
njav9
n2jed
nj2e
n5je3de.
nje1de
n4je3e2
n6je1i
n2je1s
n3j2es.
n2jet
n3jet.
nj2e1t6r4
n5je6tt
2nk
n1ka
n2kak
nk3aks
nka2l5l
n2kau
n4ke1do
n1ke
nked4
n4keer
nke3e2
n4ke5h
nk5e2i7e
n2kei
nke8l5s4
nke2l2t3
n5k6e4n1h2
n8k2e1no
n4k5er1fa
nke2r1f
nker4s2t
nke4rs
n4ke1s4
n6ke1tø
n1ki
n4kid
n4k4ie2
nk3ier
n6k5i6nd
n2k7i4n1f
n2k3i4nn
n2k7i6n1t
n5kj2
n2k1k4
n1kla
nkl4
n5k6la6ng
n7k4lis
nk1li
nk3lok
n1klu
nk3ly
n3k2læ1
nk1lå
n3k2nu
n2k1n2
n3k2ny
n3ko
n4kof
nk2o1fi8
nk5oks
n2k2o5le
nkol4la
nko2l1l
n6k2ot
n6kov
n1kr6
n3k2rav
nk1s2
nk4tak
n6k1t
nk6tal
nk4tin
nk1ti
nkt3sk
nk4ts
n1ku
nku4le.
nku1le
nku1ri9
nkur2ie8
n4ku6t
nk9u1te
n1kv2
nk3ve
n7kvit
n6kvo
n9ky
n1kø
n1kå
nkå6pa
4n1l
nlan1d9a
nla6nd
nland6se
nl4an8ds
n5le
nlei7er.
nle2i7e
nle8ma.
n4le1ma
nli4en.
n1li
nl2ie
nli1en
n3lj
nlu4e
nly6de.
nly1de
nly6di
nly4se.
nl2ys
nly1s4e
nlø6pa.
n1lø
nlø1pa
n7lå
4n3m
nma4le.
n1ma
nm2a1le
nm2ik3
n1mi
nmi8l
nmi1ni6
nmo6se.
n1mo
nm2os
nmo1se
nmusi2k7k
n1mu
nm2us
nmu1si
nmu3s2ik
nmå6la.
n1må
nm6ål
nmå1la
n2nad
nn5ad.
nn9a2f7t
n8naf
n4n5a2ir
n6n1ak
n4nala
nn5a2l5g
n4na1me
n4n5a1na
n9n6a1ne
nna8ni
n4n5a2n5k
n5n3a4n1l
n6n7anta
nna6n1t
n9n4ar.
n4n2a1ri
n6na1si
n4nask
n2nat
n2nav
n4n3a6v1l2
nnb6o9e
n4n1b2
nnb2o
nnbu9e
nn1b2u
n6n5d2
nn1dø4
nnd2ør3
n5nea.
n1ne
n2n2ea
n5ne1ap
n5nebar
n2n2e1b2
nne1b4a
nneb4e8r
n4ne1be
n5ne1b2u
n4ne1di
n2ne3e2
n6ne1f
n2n1ei
n3ne1ke
n2nek
n4n2e1lo
n3n4en
nne6nat
n4n2e1na
nn2e4n3o
n9n4er.
n4nerel
nne1re
n4n2ero
n7n2es.
n6ne5sj
n5ne1s4la
n2nesl4
n4ne3st
n5n6et.
n4ne1sø
n4n2e3te
n2ne1v
n2ney1
n6n3g2
nnhø8re.
n4n1h2
nnhø1re
n4nid
n1ni
nn3i1de
nn2i3e
n2nim
n4n7i4n1f
n5nis
nni4sj
nni4s3t6
n2n7k2
nnle6ge.
n4n1l
nn5le
nn3le1ge
nnlø6pe.
nn1lø
nnlø1pe
n4no.
n1no
n2n5of
nno2m1
nn6o6m7s2
nnomsy8na
nnom1sy
nnoms2yn
n2n1op
n2n1o4r
nn7o4rd
n4n3o4ve
nno1v
n2n9r2
nnsa9ka
n6ns
nn4s5a2m1b
nn1s2am
nns5a6nd
nns3ar
nn5seg
nn1se
nn2s9e2i8g
nn4s3em
nn6s5e2nk
nn4s3es
nnse6te.
nn4s2e1te
nn3s2i7da
nn1si
nn4s3in
nn4s5i6s
nn6s5jak
nn1sj
nn4s5kan
nn1ska
nn5s4k1li
nn2s1k2l4
nn2s5o2p
nns3or
nnst6
nn6s5tab
nns5tal
nns5te2l1t
nns4tel
nns1te
nns4ten
nn6s5t2e1p
nns7tin
nns1ti
nns5top
nn6s5tre
nnstr4
nns5tro
nn2s3t1v
nn4s5ul
nn1su
nns3va
nnsv2
nn6s5vo
nnsy8na
nn1sy
nns2yn
nn2sø
nns3øk
nn3så5r
n6n7t4
nnte6se.
nn1te
nnt4es
nnt2e1se
nnto9ga
nnt2og
nnu1i
n1nu
nn6u6ng
n2n1un
n2n1ut
nnvi4s
n6n1v
nn5vi
n2n1yn
n1ny
nny4t
nn7øk
n1nø
nn1øv
nn1å
1no
noa4g
n2oa
no1a4k
5noa1ne
no3a6n
no7ar.
2nob
nobe4l
no1b4e
no4b2l2
nob5le
n2o3b4r8
no5co
n4oc
nodi4e5n
n2o1d
no1di
nod2ie
3n6o1e
4noe6f
4noei
4noek
4noe6n5t
noe2n
2nof
n1o2ff
3n6o3ft
n6og6ra
n2og
no1gr4
no5id
no3in
2nok
no1ki
no1k8r6
nok6se.
nok1s4e
n3ok5s4i
n2o9le
2n3o6lj
no6mid
no1mi
no6m2ik
no4mil
4nom1j
n2o2m9m4
no6m7s2
5n6o1my
no6n1s4
n2on
no6n5t
2n2oo
2nop
n1o2pp
no1r4a
no5ran
no5rar
no5ras
nor4da
no4rd
nor6d5e6nd
nor1de
4n1or8d3n
nor4d5r4
nor8d3s4
nor4dø
no9re3e2
n8ore
no3rek
no3ren
no7ret
no2r5g
3nor3ge
norg6e4s5
n3o4r2ie
no1ri
7n6orit
4no2r1k
nor6kla
norkl4
nor6k1le
n4o2r1m
normlø7se
nor4m3l
norm1lø
n2o4r2s
nor2s6ka.
nor1ska
no3ræ
no4s2el
n2os
no1se
no4ses
nose8te.
nos2e1te
4nosp2
no3stj
nostra4
nostr4
2no1sy
no4ta.
n2ot
no1ta
no4te.
no1te
not6e5i
5notek
no4tel
no4t4es
no4t3s
4no1ty
4nou
no1v
no3ve
4n2o5vi
4n5o2v1n
7nó
6n3p2
n5pe
n7po8ta
n1po
np2ot
n4på1k2
n3q
2n1r2
n9ra
nra8na
n4r4a8sa
n3re
n4r2e1b2
n2re2i7e
n6r4enem
nre1ne
n6re3o6
n6ri.
nro6de.
nr2o1d
nro1d4e
nr2o6t
nry6
nrå8da
nr6å1d
6ns
n1sa.
ns1a4d
n5sag
n5sa2kk
n5sa4k2r6
ns3aks
nsak6se.
nsak1se
6n5ak1ti
ns5akv2
n3sa1la
ns3a6ld
n6s5a2l1p2
n5s2a5ne
n4sa4n1f
n5s6a4nn
n5san1se
6nsa6n1s
ns5an1si
ns5a6n1t
n5saren.
nsa1re
n2s6a2r1m
n6s1a6r1t
n4s4a3ti
n1sat
n1sch
n3sco
nse9a8l
n1se
n2s2e1a
n2sed
ns5e6d5d
ns8e3de
n2se3e2
nse6e3i
nse6er1
n6se1f
n2seg
ns5e2ge
n2sei6d
ns5eie.
nse2i7e
n2s9e2ig
n4seks
n4s3e8lv
ns2el
n2sem
n5s2en.
n5sene.
ns2e1ne
n4sener
n9senes
n4senet
n4se1ni
6n7s2e6ns
n3s2er.
n4s2e3ra
nser2i7e6
ns2e1ri
n4se1sa
n4se1si
n4se3sk
nse3sl4
n4se1s4p2
n4se1st
n6se1su
n4se3s4v2
n4s2e1te
n9s4e4ts1
n2se1u
ns5fr2
n2s1f
n2s1h
ns3h2a
n6s7he
ns5hi
n3s2i5de.
n1si
nsi1de
nsi4e6n1t
nsi1e4n
ns2ie
nsi6er.
nsi6n5d
n3s4i6ng
n2s3i4nn
n4sin1te
n2s1i6n1t
nsi8ra
ns2ir
nsi4s7k
ns2is
ns3jak
n1sj
n4sj3av
n5s2je1f
nsj2e
n6s5jo4rd
n2sjor
nsj2o
n4skan
n1ska
n6s5kauk
ns7ke1le
n2s1ke
ns4k2e3te
n9skim
n1ski
n2s5k2ir
n5skis
n6skja
nskj2
n6skje1le
nskj2e
nsk2jel
ns5kjen
n3skjæ
nsko8g6s1
n3s2k2og
ns5kor
ns3k2ro
nskr6
nsku9et
n1s4kue
n4s6kug
ns5ku5l4i
n5skum
n2s1kv2
n5s6ky.
n1sky
n5s6k4y1e
n6s6ky4s1s
n4sk2ys
n5sla6ng
nsl4
n7sl2arar
nsla1ra
ns5las
nsle6ge.
ns1le
ns3le1ge
n3s4le6k1t
ns6le1ri
n2sler
n2s5lid
ns1li
n5s4lyn
ns1ly
n1slå
ns3mi
ns1m
n4smal
ns1ma
n4s5mo
n4smur
ns1mu
n4sm2us
nsmå6la.
ns1må
nsm6ål
nsmå1la
n5s4nar
ns1n
ns1na
n6s5nes
ns1ne
n1s2o4d
ns1of
n3s2ok
n1sol
n4so6nd
n3s2on
nson6de.
nson1de
n7s6o5ris
nso2ri
n2s1ov
n3so2v1n
nspa9ra
nsp2
ns1pa
ns5pe8l1s
ns2p6el
ns1pe
n5spet
ns1pr6
n6s3pr2os
n8s7p2ro
n7s2pur
ns1pu
n4s7s6
nsse4e2
ns1se
ns6så5
n2s4ta.
n4stak
n4stag
ns5t2a1ke
n3stam
n4s1ta2nk
ns4t5a6r1t
nstar8ta.
ns4tau
n3s6ted
ns1te
n3s4t4ei
ns4tel
nste6ma.
ns2tem
nste1ma
n6s5te2nk
n6s2t2e1p
n5s4t2ik
ns1ti
n4s5ti6l1d4
nst4il
ns7ti4l1f
n6s5ti8l1s
ns4tin
n2s4t3i4s
ns1tj
n5s2to.
n3stru
nstr4
n4s7trøy
n1s2trø
n3s2tue
n6stu2k
n6s5t2ur
n2s4t3ut
n1su
ns1u2k
n4su6ng
ns5va6ng
nsv2
nsva9ra
n2s3ve
n6s9veg
ns5ve4rd
ns5v2e2rn
ns1vi
n5s4vi2l1l
n4svil
ns4vi6nd
n2s9vis
n4s1vu
n3s4væ
nsy4d
n1sy
n2s1y2t
n5søn
n1sø
n4s1øv
n4s5øya
n5så1p2e
nså2p
nså7pen
ns7ås.
nså2s2
6n1t
n5tab
n4ta1gr4
nta4lan
nta2l1l4
nt6a5na.
nta2na
n4t5a6nd
n2t3a4n1l
nt4a6n1v
nt3a2pp
n7ta6r1t
n6t5arvi
nta2r1v
nt6as
nt3a5sia
nta1si
nt4at
nt3avi
n2tav
nt5a2v1r8
nt7a8vs
n2t2ea
n1te
n5teat
n2te3e4
n4te1f
n4te1ge
n4te1gi
n7teg1n
n2tei
nt5e4i1ni
nte5i6n1s
n5t2e5i4s
n4t4e1ka
n7t4e2k1n2
n4t3ek1se
n9teleg
nte1le
n5te3l2ig
nt2e1li
n5te2l1l
n4te8l1s
n9te2l1t
nte4ma.
n2tem
nte1ma
nt3e2m1b
nte4mis
nte1mi
n5ten.
n4t2e7na
n5tene.
nte1ne
n4t3e4n1h2
n5te6ns
n2te3o6
n5te2o1ri
n5te2pp
n2t2e1p
nt4er
n5ter.
n7terek
nte1re
n6terest
nte6risk
nte1ri
nteriø7ra.
nteriø4r3a
nteri1ø
n5te4rs
n7t2es.
nt4es
n4tes2el
nt2e1se
n4te1ta
n4t2e3te
n2t4e3u
nt6e4va
n2te1v
n2t9g2
nti1a
n1ti
n4tia1t
n4tid
n5tiem
nt2i1e2
n4t2ig
n4tikap
nt2ik
nti1k2a
n5ti5ki
n4tik1l4
nti5k1li
n5ti1le
nt4il
nti3lo
n9ti4me.
nti1me
n7times
n4t3i6nd
nti6net
nt4i1ne
n6ti1ni
n2t4io
n2tip
n4ti1sa
n4ti5s1ti
n6t5l6
n8t5n
nt2o3a
n2tob
n2t5o2m1f
nt5o2m1n
n4t3o6m1s
n2t1op
n5t4or.
n5t8o5re
n6to2r1g
n2tou
n2t1ov
nt6ran
ntr4
n4tr2e1p
n5t2rer
n6t5re1si
n2tres
ntres9kja1re
nt2resk
ntre1skj2
n4t3rin
ntrol6li
nt2rol
ntro2l1l
n6trom
n6trul
ntrøy4
nt5skj2
n4ts
nt5s6la
nts1l4
nt1s2t4
n6t3t4
n6t7ub
ntu9e
ntu4l
n2t1u2n
n4t5ur1o
n3t2ur
n2t1ut
ntva8la
n2t1v
ntv4a
nt4y6e
nty4r3s
nt7år.
1nu
4nu.
nu3an
nu4av
nu2ft4
nuf4ts5
2nug
nug6l2a
nu4gl
4nu4h
nui1t8e
nuk5
n4u2l9t8
4nulu
6n1uly
n2u9me
2n1un
nun4ge.
nu6ng
nun1ge
nuo2
6nup
nu3pl6
nu4re
nu5sa.
n4usa
nu5sen
n2u1se
nu7ser
nu2s4k5l4
nus7l4
nu4s1s4
nu6st
nus3ta
2nut
nu7ta
nu3te.
nu1te
n2ute6r
nut5e1ri
n7u6t3l6
nu6u1me
nu1u
6n1v
nva6la
nva6nd5
nver1le9
nve2r1l
nv4es1
n5vi
nvi4et
nv2ie
nvi4ka.
nv2ik
nvi1k2a
nvi5ke
n3vu
nvæ8ra
n1væ
n3w
1ny
ny5ar.
ny9ast
nyas4
2nyd
ny4de.
ny1de
4nye1f
n4y1e
4nyg
ny5ge
5n6yhe
ny1h
ny3ke
n6yk
ny3k4le
nykl4
4ny1ko
ny1lo
2nyn
nyn8da.
ny6nd
nyn1da
nyn4de.
nyn1de
4nyo
4nyp
ny8pa
ny6ra.
ny1ra
ny4re.
ny7re1v
ny4ru
n2y1s
ny5s4e
ny9s6k
nys2t4
nystu4
ny3te
ny3tr4
2ny1v
ny8va.
ny5vak
ny8ve.
4ny5ø
n1z
1næ1
næ8ra.
nær9a6nd
næ8ra6st.
1nø
nø2da
4nø6d3d
nød1de5
nød3sk
nø8ds
nød5sto
nødst4
nø9dun
nø2du
2n4ø1e2
4nøf
nø1f2l2
nø1fr2
nø7g2r4
nøg1
4n1øko
4nøks
nø4le.
nø1le
nø9mo
nø5m1u
nø3p4
nø7ra
nø6red
nø1re
nø1ry
nø3se
nø1sk
nøs4l4
nø7te
n4øt
nø7tr4
n6ø6tt
nøtt6r4
nø6v4d
nø3ver
nøy1
2n1øy.
nøy8a9n
n7øy1h
4nø2y1s
n3øys.
nå6as
nå1a
1n6å1d
nå5ded
nå1de
nå8e2n
n4å1e
nå6et
nå8j
n1å2k4
1nål
nå2la
nå5le1v
nå1le
nå8ma
n6åm
nån6de.
nå6nd
nån1de
n1å4p
nåp8na
nå2p1n4
n1å6r
nå2s2
nå5sa
nå1se4
2oa
o8a5c
o1af
oa4k
oak6ka1na
oa2kk
oak1ka
oak4ku
o1a2l1l
o3a2l1t
o1a2m
o3a6n
o9a6nd
o1a6p
oar6d7e
oa4rd
oar4d5in
oar1di
oa4r5e4g
oa1re
oa4r5e4n1h2
o8a6ré
o2a4r5i
oa2r5m
oa9té
o1au
o1a4v
2o3b4a
obakk8s
oba2k
oba2kk
ob2b4l2
o2b1b
ob4b2o
ob4b5r8
ob4b3u
o1b4e
o4b2ea
ober5e4t
ob4er
obe1re
obe4r4s
obers5ta
ober1s2t
obers5te
o3b4e1s2
2o3b4i
ob2i5e6
1ob1j
o1b2l2
ob5la.
ob3le.
ob1le
ob3len
o2b5li
3ob3l2ig
ob4lo
o1b4o
ob2o9a
2obr8
ob5rar
o1b4ra
ob1re
ob1ri
3obser
o6b1s
ob1s4e
ob5s2t6
2o1b2u
2o1by
obyl5
o5b4ø
o3b4å
4oc
ock5e4rs
o2c6k
oc1ke
o5cy
2o1d
o6dab
o1da
o2da4f
od7a6tt
od2d5ei
o6d1d
od1de
od4del
od4d4es2t
odde1s
od9do
o1d4e
o2d2e7b2
o6d5ei
o4de5k2o
o5de1ku
o2d2e1p
oder1le7
od2er
ode2r1l
o2de1s
o5d2et.
o5de4ts1
odi1e4n
o1di
od2ie
o4d3ig1l
od2ig
o4dj
odko5
o2d5k2
o4d3o4m3l
o1do
o2d3ov
o2d5re
odr4
o4d3rø
od3sk
o8ds
od1s4ka
od3s4p6o
ods1p2
ods8t4
od5s4tol
odu7s
o1du
o4dy.
o1dy
o4dy1b
o2d5øk
o1dø
o5døs
od5øy
o2d1å6
6o1e
oe6f
o4ein
oek6s
oe2l
oe4mu
oe2n
o2e1n5a
o7e1ne
oen8g5d4
oe6ng
oen2g7r4
oen8g3s1
oen1g3u
o5e6ns4
o7ens.
oe6n5t
o3e4re
o6e1rer
oer8ma
oe2r1m
o3er1sk
oe4rs
o2es
oe6sip
oe1si
oe6s2it
oe1s8ka
oes8ke.
oe2s1ke
o4etan
oe1ta
o4e2t2h
o2e5ti
oev6ne.
oe1v
oe2v1n
oev1ne
2o1fa
o2fa.
of4a2g6a
o4fa3ra
o3far
ofa4se
o2fav
o2f2e1b2
o1fe
o4fei
o4f2e1ra
o4fe3st
o1fé
of7f9a6n5d
o2ff
of1fa
of4fek
of1fe
of5fes
of6fia
of1fi
of2f5id
of6fr2
of2fu
of6fy
2o1fi
o4fip
o1f2l2
of5le
2o1fo
o6fra.
ofr2
o1fra
of9ra1s
of1re
6o5fri
of8sa
o2f1s
of8se.
of1se
6o2ft
of4tel
of1te
of4t2s3
2o1fy
2og
o2ga.
o1ga
o4g7a2n5k
o4ga1re
o2g7av
o2ge.
o1ge
o4ged4
o2g1ei
oge7na.
og2en
og2e2n1a
o3get
og4g2e1ra
o4g1g
og1ge
ogg2er
og4g5j
og8g3s4
og6g7u
ogi3a4
o1gi
og2i1e2
o4gie1v
o2g2if
o6g2ig
o4gi1ko
og2ik
o2gil
o2gim
o2gin
o3gi6ng
o2g4i1o
o6gista
o6gi7str4
o2giv
o3g5jer
o1gj
ogj2e
og7l2a
ogly7
og4ned
og1n
og1n4e
5o4g5ni
og6nel
o5g6n2os
og1no
o2go.
o1go
og6ra
o1gr4
og4re.
o4g3reg
o4g3rei
og4rer
o4g7re6tt
o4g3ri
o2g3ryd
o4g5rø
og5rå
ogs4a2
o8gs1
ogs6an
og3s2e
og1sk
og7s4kj2
og5sl4
ogs4le
og1s4p2
ogs5pa
og5s1t6e
og3s1ti
og4s5tj
ogs4to
ogs4tr4
ogs5t6ra
og5stre
og5sy
5og5s6y4v3
ogt6r4
o2g1t
5o4g5åt
o1gå
og1un
o1gu
og5ø
og7å2s2
2o1h6
oh2e5te
ohet2
oh1m9a
o2h1m
o4ho.
oh4o
ohø4
oia4
o1i2d9i
o1idr4
oi4d5t1
oi1e4n
o2ie
oi5er
oi6e1s
o2i5k
oi4la
o1im
o3in.
o1i6ng
o2i2n1o4
o4insp2
oi6n1s
o2i6r
o1is
o2is.
o6i1sa
o2i1se
o4i1si
4o1j
o8je.
oj2e
o4jo.
oj2o
o1ka
o2k7a2ft
ok1ak
oka4n5i6
ok2ar
ok5a2r1k
o3kas
o4k1e1f
o1ke
ok5e8l1s5
ok5e8l5v
o5k6en
o4kesk
o6ke1v
o1kj2
ok4k5a6nd
o2kk
ok1ka
4ok1ke
ok6k2e1ra
ok4ke5s
ok3kj2e
okkj2
ok3ko.
ok5kol
ok4kun
4okl4
o4k8la.
okla5m
ok8le.
ok1le
o1klu
ok7lut
oklå5
2o2k1n2
okn4a8sa
ok1na
o3ko
o8ko.
ok8ol
o4k3o6ms
o1kom
o4k3o2m5t2
oko5pe
ok5o2pp
o4k3o2r1m
o1k2o4s
oko9se
o6kov
o1kr6
o6k5re3o6
o6kret
o4k5ru
oks4al
ok1sa
ok1s4e
ok4sek
oks2e5k1r6
ok4sem
ok7sen
ok6serin
oks2e1ri
ok4ses
oks2e9te
ok5s4i
ok3s2i7da
okst2
oks6ti
ok6s5vi
oks1v2
oks6tr4
ok5ta
o6k1t
3oktan
ok3ti
ok5to
okto4r5i
okt6r4
o1ku
ok5u4k
oku8le.
oku1le
oku6t
ok7u1te
o5kva
okv2
o3kvi
ok5øy
o3kå
8ol.
4o1la
ola6ded
ol6a1de
ol4a8ga
o4l5a4n1l
ol3a6n1t
o9l8ar.
o6lar1be
o2l1a2r1b8
ola5t
olb4er4
o2l1b2
ol5be
olb6o7e
olb2o
olbo7ge.
olb2og
olbo1ge
ol4b4ol
ol1b2u7
2o6ld
ol9dan
ol1da
ol5de.
ol1de
ol4dem
ol4d4es2t
olde1s
ol5det
ol3do
oldo7ve7
ol2dov
ol5dre1v
oldr4
ol3d1ri
old3s4kr6
olds1k
ol8ds
2o1le
o5leaks
o2l2ea
ole3a2k
o6l2e1b2
o2led
o2le1f
ol5e2ig
o2le5in
o2lek
o7le3ki
o2lel
olele6ge.
o2le1le
ole3le1ge
o2lem
o5l4em.
o5le6ms
o7lene.
ole1ne
o9lenes
o4lenet
o4le5ni
o4l2e1no
o6lered
ol8e1re
o4l2e1ru
o2les
o4le5sta
ole5str4
o9let.
o2le1v
o1lé
ol4f5i
o4l1f
ol4fj
ol3g4e
o2lg
ol1g4l
2o1li
o4li1b4a5
oli1b2
oli9e6n
ol2ie
oli7e6r5t
ol4i5ne
3o6lj
olke3s4
o6l1k
ol1k4e
ol4k2e1se
ol4kesk
ol4kest
ol2k3l4
ol9ko
ol6ku
ol5kv2
ol5la.
o2l1l
ol2l3ak
ol4lam
ol7le.
ol1le
ol4led
ol4lel
ol7len
ol4l6es
ol6li4n1j
ol1li
ol4l2og
ol4lo2m1
ol5l2os
ol4lov
oll5ove2r
olls4t
ol8l1s
oll5s1v2
ol2lu
ol4løp
ol1lø
ol6løs
ol6løy
ol2lå
olme5s
o2l1m
ol1me
ol4mest
ol4må
o2l9n
2o1lo
o4lo.
o5lo3a6n
ol2oa
o3l6o1e
o4lof
olo3i
o2l5ok7s4
ol3o6ms
o2l3op
o6l7os.
ol2os
o6l7o6se
o6l5o2s1f
olo5ve
o1lov
ol4ped
o2l1p
ol1pe
ol7so
o8l1s
ols8t
ol5sva
ol2s1v2
ol4s5v2ik
ol3ted
o2l1t
ol1te
ol5t2e1p
ol2t2r4
ol6t7ra
olt5re.
olt5rer
olt5ret
o1lu
olu5l
o4l3u6ng
olun8ge.
olun1ge
o4l5ur
ol5va
o8lv
ol1vo
o1ly
oly7d
ol5ør
o1lø
o1lå
2om.
2o1ma
om3a6ld
o2ma2m
om5a6ng
oma4n2if
oma1ni
o5m4a4nn4
oma6n5t
om3a4rs
omar7ø8
om4as
o2m1av
o2m1b6
om4b4et2
om1be
om4bis
om1b4i
om3b4l2
5omb2o1d
omb2o
1om1b2u
2o1me
o2m1ek
o5m4e1ka
o7menes
ome1ne
o5me4r6s
o6m2e1se
o5met
ome4tak
ome1ta
ome7tar
o2m1e1u4
3omfan
o2m1f
om1fa
3omgre
o2m5g6
om1gr4
8o2m1h
o2mi1a
o1mi
o2mi1b2
om2i3e4
o4miek
o2m2ig
o7mi2kk
om2ik
o7m4i1ne
om7i4nn
om3i6n5s
o4m4io
o2m2ir
o4mi1sj
o4mist
o2mi1u
om1j
2o2m1m4
om6m6at
om1ma
om4med
om1me
om4m2e1tr4
3om1mø2
2o1mo
o4m2o1fo
o2m1op
omo1v
o8m1p2
ompa3t
om1pa
1o2m1r6
2omre
2omro
2omru
6oms.
o6ms
oms3al
om4sek
om1se
3omset
omsk2o9d
omsku9la
omsk8u
om4som
om1so
oms3un
om1s4ø
o2m1t2
3omtal
om3ta
om4t4es
om1te
om3ti
om3t1v
o2m1u
o3m2us
omvæ8re.
o2m1v
om1væ
omvæ1re
6o1my
omyr8ke.
o4my2r1k
om5yr1ke
o2møk
o1mø
omø4r
o2m5øy6
o5må
2on
o2n3ab2o
o1na
o4n8ac
o6nak
o4n3a6ng
o4nap
on3a2pp
o6n1a2r1b8
o4n3a2r1g
o4n3a2r7v
onat5r4
o2n1a4v
o4n5b2
on4dar
o6nd
on1da
onde1r6e
on1de
ond2er
ond3re
ondr4
on4d5ri
ond2s4i
on8ds
o4ne.
o1ne
on5e2i7e
o2nei
o4ne1le
o7ne2l1l
o2nem
o3ner
o4n2eru
one3si6
o4nesk
o4ne3s8t
o4n2e3te
o2ne1v
o4n7f
on7ga.
o6ng
on1ga
on8g3d4
on5ge.
on1ge
on6ged4
on2g6es4
on5gi
on5go.
on1go
on1g2r4
on8gro
on5g9r2os5
ong2s4j
on8gs1
on2gu
on4gy
on4g5ø
o9ni.
o1ni
o2n3i2d
on2i1e
o2nil
on5i6ld
o2nim
oni6mi
o4n3i4n1f
o6n7i4nn
o2n4i1o
o4ni5p
o6niso
o4n3j
o2n1k
on5k6a
3onkel
on1ke
on5k6i
on4k1le
onkl4
on5ku
onle6g
o4n1l
on5le
on5nad
o4nn
on1na
on3ni
on5ny
o4no.
o1no
o5n4or.
o4no4v
on3o3ve
on4s1h
o6ns
on1s1i
ons2i3s
onsi4v
ons1k4
on2s7ke
ons1l4
onsl4a6ga
onstitu2e9ra
ons1ti
ons2tit
onsti1tu
onstitu5e
onstitu1er
onstitue9re
onst5r4um
on3stru
onstr4
ons5u6nd
on1su
ons1v2
on4s3ve
ons1å
ont6a
o6n1t
on3te
on4ted
on5ten
on5ti
on4t6ok
on5tor
ont2r4
on7ul8
o1nu
on5ur
onu4sk
o4n5ø4
o2n6øy.
onøy1
on7å6
2oo
oo8d1s
o2o1d
o1o2ff
oo2k1
oo5k8a
ook5es
oo1ke
o4o1l7a
oo4m5i
o2o6mo
o1o2p
o2o1pa8
oop9an
oo4pe
o2op5en
o2op5et
o6o1pi
o1o4r
oor6da
oo4rd
o2o5s
oo6sp2
o1o4v
2op.
2o1pa
o4pab
o2pak
opa6n9d
o4pa3re
o7paren
o4pa1sj
op6as
o4pau
o3p2ea
o1pe
ope1i
opel6lø
op6el
ope2l1l
2open
o4p2e1na
o9pe6nd
o4pe3net
ope1ne
o7pe6n1t
o2per
6o3per.
3op2e1ra
ope6rar
o7p2ero
o5pe6r1t
2opet
o4pe1ta
4o2p1h
o1pi
o4piek
op2ie
opin8g9s1
o3pi6ng
o4p2ir
2opl6
o1pla
o4p5la6nd
op3li
op9lu
op4na.
o2p1n4
op1na
op4ne
2o3po
2opp.
o2pp
4op1pa
op4p5a6r1t
op4p6as
4op1pe
1op2p1g2
op4pi.
op1pi
op6pia
op4pis
opp3li
opp1l6
3opp1ly
op6p6o1e
op1po
op6p1r6
opp5rop
op1p2ro
opp5u6nd
op2pu
op6p1å2
2o1pr6
op6re
o6p7ru
o4p7rå
op6sa.
o2ps
o8p8si
op2t1r4
o2p1t
o4q
4or.
o6r7a6d1d
o2r3a1dr4
o2rag
or1ak
2oral
or5a6ld
o4r5a2l3g
oral4st
or2a8l1s
or6alt.
ora2l1t
o3r4am
o4ra1na
o3r4a1ne
o4ra6ng
o4ra6ns
or6ap
or3a6tt
o4rau
o4ra3u4k
o2r1a4v
4o2r1b8
orbit5
or1b4i
or7by
4or1c
ord4a9ta5
o4rd
or1da
or3d2ea
or1de
6ordel
or4d7e6p2l6
ord2e1p
or7d2ik
or1di
ordi4s
or4d5i1se
or2d3it
1or8d3n
ordre8gi.
ordr4
ord1re1gi
ordsa6me.
or8ds
ord9sa1me
ord1s2am
ord1s6e
ord3st4
ordy9ra
or1dy
8ore
orea1r8a
or2ea
ore1ar
orea2r8e
o5re1b4ra
o2r2e1b2
orebr8
o8re3di
or1ei
or1el
o4r2e9la
o6re6ld
ore6na.
o2r2e3na
o8re4ned
ore1ne
o6re4net
o2r3e2nk
ore6n3s
ore6o5g6
ore3o6
o6re2r1f
o1rer
o4r2ero
ore1s2
o5re2s3c
ores6te
o4re1ta
or9e8t8n
o6r2e3t2v
o2re1v
2o2r1f
orfa6re.
orfa5re
or1fa
or3far
orf4i7ne
or1fi
orfø9re.
or1fø
orf8ør
orfø1re
1or3g4a
o2r1g
4or5ga.
or3ge
or5g2er
org6e4s
orgi6e5ne
or3gi1e4n
or1gi
org2i1e
org4sk
or8gs1
orha9g
o2r1h
orh2a
orh2a9le
o1ri
o3ria
o2ri5b4
ori4e5ne
ori1e4n
or2ie
5o4rie6n1t
o7ri1et
o2r2ig
ori7k2a
or2ik
o6rim
o4r3i6nd
o2r3i2nk
o4r3i4nn
o2r3i6n1s
oriro8
o4r2i1r
6oris
ori1s4a
6orit
o4ri7t2i
oriti6me.
oriti5me
or5ka
o2r1k
3orkes
or1ke
or5ko
orko6se.
or1k2os
orko1se
or8k7s2
6o2r1l
orla7te
or5le
orm5a6ng
o2r1m
or1ma
or6map
or5m2el
or1me
or4m3un
or1mu
or5mæ9
or4nar
o2rn
or3na
orned5
or1ne
orne6d1r4
or5net
or4nol
or1no
or6n3t
4oro
o3r2oa
o3r6o1e
or3o2ff
o5r2og
o1ro5i
o1r2on
or1op
o4r1or
oror9da
oro4rd
o5ro1sa
or2os
o5r2ot
or3ove
or5o2v1n
or2pe6s
o2r1p
or1pe
4o2r1r
2o4rs
ors5a2l1t
or1sa
or5sen
or1se
or4se2r1k
or2se9t6a
orse7te.
ors2e1te
or3si
or2s5k2ar
or1ska
ors6k5ei4
or2s1ke
or6s8k9l4
or1s4ku
or1s4l4
orsmå8la
orsm6ål4
ors1m
ors1må
or1s1n
or3s2o
orso9na
or3s2o6n5
or7s6o9n8e
or2s1o9v
ors4pa
or1sp2
ors4ten
or1s2t
ors1te
or1su
orsva9re
or1sv2
or3sy
or9sø
2o6r1t
or4t3ak
ort8a8la.
or4t5a6nd
or1ta9pe
or4t5av
orte5i6g
or1te
or4tek
or3tem
or6t5e2r1f
or7t6er
or4t5e2r1m
or5ti1i
or1ti
or7t4il
or5tis
3ort2o1d
or6t6ok
orto9ne
or1t2on
orto4r
ort5o1ri
or4tou
or2t5res
ort2r4
ort5r6å1d
ortå4
or4t3år
or1u
or2u8d
or9u1de
oru4h
o5r4um
oru4t5
or4uta4
or4u6t1f
orva9ra
o2r1v
orv4a
or5veg
or8v3s1
4ory
o7ry1a4
or1yn
o9r4æ4re
2orø
or9ø8k
orø6k8t
o1r5ør
or3ø4v
orø6v8d
or1øy
or5å1s2
2os
o1sa
o4sa5b
o6sad
o2saf
o4s4aku
6o5sau
os1b4i7
o8s9b4
os4e1fi5
o1se
o2se1f
o2seg
o5selei
os2el
ose1le
o2sem
os2en
o6s2e1na
ose5sl4
ose5s1m
oses4sk
ose4s1s
oses6sp2
oses4s2t
o4se1u
osf4a9ta1
o2s1f
os1fa
os2hi
os1h
os7hi2s1
osi6e7ne
o1si
osi1e4n
os2ie
osi5e1re
osi5e6r5t
o2s1i6ng
os6k3ei
o2s1ke
oske2i7e
o4skil
o1ski
osk9lar
o2s1k2l4
o6skla
osk5len
osk1le
o1sko
osko5p
o3skri
oskr6
o2s8k3v2
os2l4
os3le
oslo1
os3l2o1d
o1s5lu
os5lø
os9ma
os1m
os5me
os3mo
o2s1n
o3so
os7ove
o2sov
os6pa.
osp2
os1pa
o4s6pe.
os1pe
os4pil
o1spi
os4por
os1p6o
os4sek
o4s1s
os1se
os3sem
os5s2e6ns
osser2i7e6
oss2e1ri
os2se5v
ossi4s5te
os5s2is4
os1si
os6ski
oss3kj2
os4s5ko
os2s9l4
os6s1pa
ossp2
oss7tro
oss2t
osst6r4
os7sty
os8sv2
ost5adr4
o3s6t2ad
os5tal
o4s3t2a5le
o8s3te.
os1te
os4teg
os4t5e4g1g
os4tek
ost6el
o4s5te3o6
o4sti.
os1ti
o8s3tia
o4st2i1e2
o4stin
o2s5tis
ost1o
os6to6ns
os1t2on
ost5ran
ostr4
ost3re
o3stro
ost5rup
ost7rå
o5stø
os1v2
os5øy
o1sø
os7å2r
2ot
o1ta
o5tad
ota4l5a
ot5a6ld
otal7e5v
o3t2a1le
ota4lov
o9ta1ne
o9t4ar.
ot3a2r1g
o8t9a2r1m
o5tas
o2te7d
o1te
ot6ei
ote5i6n1t
ote1k5i
ote6k7la
o2te1kl4
otekl4a8ga
ote4k1le
ot5e2lem
ote1le
o4te5le1v
otel6lan
ote2l1l
otel6lek
otel1le
otel4li
otel8lø
o4t2e1na
o4t5en1de
ote6nd
oten8de.
o2te3o6
o4teram
ot2e1ra
o4tere4t
ote1re
o6tere1v
o4t2ero
ote4r5s
ot2e5r8u
o4t2e1rå
otes6ter
ot4es
otes1te
ote5sté
ote1t5a
o2t2e1t5o
o3te4t3s6
ot1fø4
o6t1f
oti7e6r5t
o1ti
ot2i1e2
ot2i2k
oti1k2a3
o2t7i6ko
o1tj
o6tja
o2t5jer
otj2e
ot6n2ero
o8t1n
ot1ne
otno7te.
ot1no
otn2ot
otno1te
ot6nå
o1to
o2to.
o4t2o3a
o5toa.
o2tob
oto6en.
o1t6o1e
otoe2n
o6tof
ot3o2ff
o6to1ga
ot2og
o6togra4m5
otog6ra
oto1gr4
o4toi
o2tol
oto5ne
o1t2on
ot2o4ral
oto1ra
o4to4r5d
2o2t2ot
o2tou
o1tr4
ot7red
ot5rer
o4t5re1v
o6t7ri
o6t5rom
o6t5rø
ots5el
o4ts
ot1se
ot6s1h
otshus1væ8
ot2shu
otshus1v2
ot4s3ki
ot2s6o2p
ots1o
otso2pp6
ots5pr6
otsp2
ots5tab
ot1st4
ot7s1te
ots6å
ot6ta2nk
o6tt
ot1ta
ot5tas
ot5teg1n
ot2teg
ot1te
ot6te2nk
ot3ten
ot5t2e1se
ott4es
ot5tin
ot1ti
ot5t2oa
ott4s3k
ot6ts
otts5p6o
ottsp2
otts4ti
ott1st4
ot7tug
ot4typ
o1tu
o6t5ut
o1ty
ot4y8e
o2t1y2t
o1tø
ot7å
oub8
ou2l5l
oun6ge.
ou6ng
oun1ge
ou4r
ou7ri
ou5ro
o4u9sa
out8a
ouve4
ou1v
o5v2a1e2
o1vak
ovan1fø8
ova4n1f
ova1n9o
ov3a6n1v
o7var.
o6v1a2r1b8
ova9re
ov5a6r1t
ov7a2r1v
ove2d3
o1vei
o1vel
ov2e4la
4o1ven
o4ve6nd
o5ven1de
o4ve6nya
ove1ny
ove2r
ov2e1r3a
ove7ra.
ove9ras
o6ver1dr4
ove4rd
ove1re6
ove8r5es
3o6ve2r1f
3ov2e2r1g
o5ver8ks
ove2r1k
5ov2e2r1r
5over3s2ik
ove4rs
over1si
ov6er1s4p2
ove2r9v
o1v4et
6o2v1h
2o1vi
ovi1so3
2ovj
6ov1na
o2v1n
ov4ne1s
ov1ne
2ov1ni
4ovo
o5vo.
o7vom
ov1or
o5v2ot
ov1o2v
ov5sal
o8vs
ov4sek
ov1se
ov4se4n
ov9s8ke
ov1sk
ov4s1le
ov1sl4
ovs1p2
ovs5te
ov1s2t2
ov4s1ti
ov7sun
ovs6y5k
ov1sy
ovta6le.
o2v1t
ovta1
ov3t2a1le
ovve1g7i
o2v1v
ow1
ow2a8
o7was
o1wat2
ow5h
ox3
oy9ar
oys5l4
o2ys
o1y2t
o1za
o3zy
o1ø
o1å
ô6ra
ô2re
ôr5ei
ô1ri
ô4t
ó9sa
ó7t
ó8v
ò9re
1pa
4paa
2p8ac
pa8cen
p5ad.
pa3d2e7b2
pa1de
p4a2d1g4
pa8ds4
2pa1j
6p6ak.
6pa1ka
4p2a1ke
pak4kas
pa2kk
pak1ka
pak4ke4s
pak1ke
pakk7e3s4l4
4pak1ti
pa6k1t
3pa1la
p2a3le
pa7lim
pa1li
pal5in
pa2l1j
pal5lø
pa2l1l
5pa2l1m
4pa1lø
2pam
pa5me
p3anal
pa1na
pa4nap
pan9de
pa6nd
pa5ne
pan8g5s6
pa6ng
pan3ka
pa2nk
9panne.
pa4nn
pan1ne
pan5se
pa6ns
pan5sl4
pant8r4
pa6n1t
4pa6n1v
p6a4ny
2pa1pa
pa8pa.
pa3pe
4pa1po
pap4p1r6
pa2pp
4papr6
pap3ri
pa4ra.
pa1ra
5parad
2p1a2r1b8
4pa2rek
pa1re
4p3a2r2e3na
pa2r5e4s1s
pa2r5g
2p2a1ri
pa4ri.
pa3ris
4par3ki
pa2r1k
par6k7l4
par4kv2
pa2r3m8
pa1ro
4pa2r1r
par8ra.
p1arra
p2a4r9s4
par6tid
pa6r1t
par1ti
par4t2ig
par4tin
par5u
pa2r7v
parvi6
6parø
4pa1rå
p6as
p4a1sa5
pa9se
pase6r5
4pasp2
3pa4s1s
pas5sab
pa4s3t
pas4tar
pas1ta
pas5ti
pas9v2
pa1t
pa3te
pa4tist
p4a1ti
pa6tre
patr4
p8at1ta
pa6tt
pat6tak
2pa1tu
p8a2t6v
pau7k
2pav
pa4ve.
p2a1ve
3pa1vi
2p1b6
pba4ne.
p1b4a
pba1ne
pbo6da
pb2o
pb2o1d
pce6
2p1d4
pde4le.
p1de
pde1le
p1dø2
pdø9d
1pe
2p2ea
pe1a2k3
pe4a3re
pe1ar
4p2e1b2
p2ec3
pe7d6a
5pedas
4pe6d1d
pe3de
ped1fø9
pe2d1f
ped2i9e8
pe1di
pe6d2oa
pe1do
pe6d2on
4pe1dr4
pe4dro
4pe1du
4pe1dy6
2pe1dø
pe7då
pe3e6
pe3er
2pe1f
p5e8ft
2peg
p5e4g1g
peg8ge.
peg1ge
p4e1go4
2pe1h
p4e4il
pei4leg
pei1le
2pe1in
pe6is1m
p2eis
2pe1j
2pek
pe8ka.
p4e1ka
pe1k4l4
pek4t2ro
pe6k1t
pektr4
pekt7r2o5s
8pe1ku
p6el
4p2e1la
pe5l4aks
pe2l1ak
5pe2l1al
pe5lar
pe6l9d
4p4eled
pe1le
pe4le1f
4pelei
pe6l2e1p
4p2e3ler
pe6le1v
6peliv
p2e1li
6pe2l1j
pel4lo
pe2l1l
4pe1lov
p2e1lo
pel5s6e
pe8l1s
pel5s4i
pel7st
4pelu
pe5lun
2p4e1ly
2p2e1lø
6p2e1lå
2pem
p1e2m1b
pe4nan
p2e1na
pe4n3ar
pen3de
pe6nd
6p5en5d2en.
4pener
pe1ne
pe3net
5pe6ng8
pen4gel
pen1ge
pen7g1l
peni4n
pe1ni
4peniv
penly4
pe4n1l
pen7s8a
pe6ns
pensa7k
pen3sa8la
pen5sk
pen4s5l4
pen3s6m
pen5s6o
pen1s4t
pen9sta
pen7s1te
pen7tag
pe6n1t
pen5tr4
6pe1ny
2p4enå
2pe3o6
pe7o6s
2p2e1p
pep5ar.
pe1pa
pe2p7p
pera3a
p2e1ra
4pe4rab
4perad
pe4rai
pe4ral
4perap
pera5t
4per6a1te
4pered
pe1re
4perei
4perek
4pere1s2p2
4perest
4pe1re1su
4pere4t
pe4re1v
3pe1ri
peri5e8ns
peri1e4n
per2ie
4pe5r2ik
peri3s4
4perit
pe2r5k
per6les
pe2r1l
per1le
per5mu
pe2r1m
p2e2r5n
4p2ero
pe3r2os
pero6se.
pero1se
per6re1gj
p2e2r1r
perr6e
per6rei
5perro
pers6m
pe4rs
per4tro
pe6r1t
pert2r4
per4t5rå
perv2i6k
p6ervi
pe2r1v
2pe1ry4
6p2e1rø
4p2e7rå
2pe1s
3p2es.
p2e3se
pe6se.
pe7si
pe2s8ka.
pe1ska
p3e2s1ke
pes4ke.
6pesl4
pes4n
5pe4s1s
3pe6st.
5pe6s5te.
pes1te
pes5t4il
pes1ti
4petab
pe1ta
peta6ka
pe3tak
4pe3tal
4petas
4pe1tau
p2e3te
5pete.
4pe2ted
6petei
6petek
4petel
4pe2tem
p2e3ti
4pe3tid
4pet4il
pe4tim
2p2e1tj
2p2e1to
2p2e1t4r4
pe4t5ru
pe4t1s6
4pe6tt
2p2e1tu
4pe1tø
4pe1tå
pe2u
2pe1v
peva8ne.
pev4a1ne
pe5vi
p5e2v1n
pev6ne.
pev1ne
4pe1ø4
4pe1å6
3pé1r
2p1f
3p6fe4nn
p1fe
p3fo
p5fr2
pfri4
pfø5re
p1fø
pf8ør
2p1g2
p3gjer4
p1gj
pgj2e
2p1h
phav2
ph2a
pha9v4a
pha8vs5
ph2e9te
phet2
phe7va
phe1v
phe5ve
phi5li
7pi3a1ne
7piar
pi4as
7pia1se
4pi1av
4pi1b2
pi6ca.
pi1c4a
2pid
pi8d3s2
pi1e2n
p2ie
6pi2e6nd
pi3er.
pi9e4rs
2p2if
pi9fr2
pig5ge
p2ig
pi4g1g
pig6g9u
p7i6gj
2pi3h
6pi1i
pi4ke5h
p2ik
pi1ke
pi6kel
pik2e5r6o
pi4ké4
8pi2kk
pik3ko
4pik1l4
pi1ku5
pi5la
pi6la.
pi3l2e1p
pi1le
pil4lag
pi2l1l
pill1b4a6
pil2l1b2
pil6led
pil1le
pil4leg
pill2e1ga6
pill2e6ra
pil4l6e5s6
pi1lo
2pim
pi4na.
p6i1na
pi4ne.
p4i1ne
pi9ned
pi4nel
pi2ne4v
3pi6ng
pin6go
pin2g3r4
ping5sk
pin8g2s1
6pin6n1s4
pi4nn
pin4sl4
pi6n1s
p6i2nø
pio6n5an
p4io
pi2on
pio1na
pio6n5s
3pi2p
pi4pi
pip9la
pi1pl6
pi4rar
p2ir
pi1ra
pi4res
pi1re
pi4rut
pi1ru
pis2i9e
pi1si
pi4ski
2piso
pis2s4l4
pi4s1s
pis4sp2
pis4s2t
pis1t
pi2s4t5r4
pis9t8ra.
1pit
pi9ta
pit8a7la.
pita4l3a
pi5té
2pi1t2i
4pitj
4pi1tr4
pi6t5t
2pi1u
2pi5v
4pi1ø
4p5k4
p2ka5v
pka8va
pl6
8pl.
2plad
pla8de.
pl6a1de
p4lak
p4lan.
plan7de
pla6nd
4p3lane.
pl6a3ne
pla6n5g
pla6n5s
plap3
4p1lar
p2las
pl6a4st
p2lat
plat6i1na5
pl4a5ti
2ple.
p1le
2pled
4pl2eis
p2lek
p6l2e5n4u
pl2e5n4a
pl2e6r5u
ple8se.
pl2e1se
4ple1v
p2li
4plit
p3liv
pl4i7va
plo4gj
p1l2og
p4lo8i
p1lok
4p5lov
plu4e
p4luk
plun3
plun6d7ri
plun5d4r4
plu6nd
p1ly
ply5d8
plæ5re
p1læ1
plø6pa
p1lø
p1lå
2p3m
2p1n4
p7ner
p1ne
pne6se.
p4n2e1se
1po
p2o9a
p2o1d8
po6da.
po1da
po4de.
po1d4e
po6em
p6o1e
2pof
po6f7r2
p2og6
4p2o1h6
po5id
2po1ke
po6lan
p4o1la
p2o3le
po4lek
6p3o6lj
pol5li
po2l1l
po6lom
p2o1lo
pol6s4ka
po8l1s
po2lu
2pom
pom6p9u
po8m1p2
po6m5s
4pon.
p2on
po6n5d
pon4g2r4
po6ng
pon8gs6
2po6n1s
pon5sa
pon4s1v2
p2o2p1a
po6pe.
po1pe
p5o2p1n4
po2p1s
2p4or.
po1ra
2p5o4rd
p8o1r4e
po4re1f
po6reg
po8ré
2p1o2r1g
2po1ri
2po2r1k
por6s7v2
p2o4rs
por4to4r
p2o6r1t
por4t5ro
port2r4
por4trå
2po2r1v
po1ræ
p2o1rø
po4se.
p2os
po1se
po4ses
4po1sj
po2st
po4sta
po1s5tas
po5stat
pos3te
4pos4v2
5pot.
p2ot
7po1ta
potak9
po3te
po4te.
po2te1k5l4
po5t6h2a
po2t1h
3po1ti
4po1tr4
4pou
2po4v
pove6
pov2e2r6n
pove2r
pow4
2pp
p2pad
p1pa
p2pak
pp3a6k1t
p2p3a4l
p9pa5ne
pp5an1gr4
ppa6ng
pp5a2nk
p4p3a4n1l
p2pap
p9par.
p4p5a2r1r
p7pa4s3t
pp6as
p4p7a1t
p4pe3e6
p1pe
p2pe5i4
ppe8l5s6
pp6el
p4p2e1na
p8p9en3d2er
ppen3de
ppe6nd
p4p1endr4
ppe9nes
ppe1ne
p2p2e5p8
p4pe1nø
pp2e5ra
p7pe1re.
ppe1re
p4perkl4
ppe2r5k
pp7e6s2en
p2pe1s
pp2e3se
pp9es4n
ppes8ti
p4pe1ta
p4p5e4tas
p4p2e3te
p4p5e6tt
p2pe2u
p1pi
p2p1id
p2p1il
p2p5im
pp3i4n1f
p4p5i4nn
pp7ir
ppir8re.
ppir3r6e
ppi2r1r
p4pis1t
pp1j
p4p5k4
pp1l6
pp5l6a4st
pp2las
p2p3led
pp1le
pp5lei
p4p9le1v
p2p5n4
p2p1of
p1po
p2pol4
pp3o6ld
p2p5om
p2p1op
p2p3o2r1k
p2p1o4v
2p2p5p
pp1r6
pp5rei
pp5rin
p1p4ris
pp7ri4s1s
pp9riv
p8p9ro.
p1p2ro
ppropri6
ppr6o1pr6
pp7r2ot
p2p1s
ppse6te.
pp5s2e1te
pp1se
pp3ska
pp2ska9k
pps2p2
pp9s1pe
pp3s1pl6
pps2t2
pp7sto
p2p7t2
p2pu
pp1u2k
pp5ut
pp3ø4
ppøs8
p6på
pr6
4pr.
4prad
3p2raks
pra7li
2pran
pra6n3s
5pr4at.
5pr6a1te
pra5te.
4pray
5pre1f
prei7er.
p2re2i7e
4pr4ei1i
pr5e2lem
p2r2e1le
1p2rem
pre6n4s
1pres
6p1r2es.
pres6sak
p2re4s1s
p6re1stas
4pre6tt
p3r2if
pri5ke
pr2ik
pri4l3e
4pri6ng
5pr2i1no
3p2r6i6n6s5
3pr6in7s6e
3pr6in7s6i
1pris
pri6s5k
pris3t
2prit
pri9ve
1p2ro
8pro.
6p7r4oc
3prof
4prog.
pr2og
4pro1ge
4progl
4p3roi
p5r2op.
3pr2os
7pro1se
6pru
prun7ge
pru6ng
pr4u5ta
pru5t8e
6p1rør
prør2s5t
prø4rs
prø5s4
5prøv
prø5ve8l1s2
2prøy
4prå
prå8da
pr6å1d
prå1k3i
prå4ko
prå2k5k6
2ps
p1sa.
ps5a6n
p1sc
p3se.
p1se
psei8g6e
p2se2ig
p2sek
p2s2el
p2s5e4ly
p3s2en.
p5s2e6ns
p7s2er.
p5s2e1te
p5s4e4ts1
p2s1h
p6si1b2
p1si
ps5i6n1s
p7s2is
p3s4j2o
p1sj
p4s3kil
p1ski
ps7kjen
pskj2
pskj2e
p2s1ko
p7s6ko.
p3sk2o1d
p5s4k6o3e
p2s1le
psl4
p9s8lo.
pslø8va
ps1lø
pslø6ve.
pslø5ve
p3s4lå
p2s1m
psmå8la.
ps1må
psm6ål
psmå1la
p2s1n
ps4no
ps1o
p3s2o1d
pspi9la
psp2
p1spi
p4s5p1le
p2s1pl6
p3s4pre
pspr6
p8s7p2ro
ps5pu
p4s3s2
pst2
ps7tal
p4s5tem
ps1te
p2s3t1v
p5s6tå
psu4r
p3s4us
ps1v2
p2sva
p2sve
ps4v6i6ng
p4s1væ
ps5w
psy3ke
p1sy
ps6yk
3p2sy1ko
4p3s2y1s
p2s1ø
p3s2øk
psø4ke.
p3s6ø1ke
psøy8
psøy9e6ne
p8sø4y1e
psøye4n
ps1å
2p1t
pt8a8la.
pta4le.
p3t2a1le
pt7a2r1k
p3te
pte6k
pte4ma.
p2tem
pte1ma
pte7re
p5ti
pt2o7g
p4tou
ptus5t
p4tut
1pu
pu4b4r8
5puc
6p3u6dy
p2ud
pu2k
pu7la
pu8le.
pu1le
pu2l1l6
5pum
pu4ma.
p2u1ma
pun6k4t5
p4u2nk
punk5t6e
2pu4nn
2pur
pu4re.
pu1re
pu8r2ea
3pu1ri
3pu2r1k
pur5u
pu2r3v
p4u7sa
pu2s4h
pu4sl7u
pus1l4
pu1ta
pu4ta.
pu5ta1s4
p2u5ter
pu1te
pu5te1v
4p1u2t3g2
2p1u4t1s4
put6tr4
pu6tt
put4tu
2p1u2t1v
6put1ø
2p1v
pver7
pvi4se.
pvi1se
py4dr4
py8o
3py1ra
py1re
py1ro
6p2ys
py6sa.
py1sa
py4se.
py1s4e
pys6t
4p5z
1pæ
pæ4re.
pæ1re
p4ø1kj2
pør4ret
pø2r1r
pørr6e
pø8sa.
pø1sa
pø9ta
p4øt
pø9te
p1ø2v8
5på1b4
på4by.
på1by
på1k2
1pål
på4la.
på1la
på4le.
på1le
p5ån
på3p2e
på1pl6
p1å2p1n4
på1r
1pås2
på7sko
på5s1m
på3t2
6påtå7
1på1v
qa5
qu2
qu9ar.
1que
qu1e7r
4raa2m
4raar
4rabis
ra1b4i
ra1b2o4
4rab1r8
2ra1by
ra3cet
r8ac
ra3ch
5raci
r6a3d2a
4ra2d1f
3rad4io3
ra1di
4rad2ir
4rad1j
2r1a2d1m
2ra1dr4
r3a2dre
ra8d2s3
radvi4
r1a2d1v
ra5e3de
r2a1e2
ra4ed
rael4
4rae2r1k
raf4fer
ra2ff
raf1fe
ra4fi1u
ra1f4i
ra2fj
2r6a1fo
ra5fo.
ra4f2os
2rafr2
ra5fre
6ra2ft
ra4fu
ra6fy
ra7g2e1a
ra1ge
ra5ge3e2
2ragl
2ra1h6
7raid
ra5i6n1t
ra3i4s3k
6r5a2kad
ra1ka
r4a9kar
ra7kel
r2a1ke
ra5k6h2a
ra4k1h
r2a2kk
rak4kel
rak1ke
r6akr6
ra5k2ro
2raks
rak6sa
rak3s4e
rak6se.
rakst6
4rak1ti
ra6k1t
rak2t3r4
r4a5ku
4r2akv2
ral5a6ns
6r5album
ra2l1b2
ral1b2u
5ral8ds
ra6ld
4raled
r2a1le
ra5le3o6
ra2l3g
4r5al1ge
4r5al1go
rali5e6n
ra1li
ral2ie
ra4lin
ra2l1j
ral5le
ra2l1l
ra2l7m
ra5lo6i
ra2l3op
r6a5ly
4ra1lø
4ra5l8å
ra4mag
ra1ma
ra4mas
ra2m3b
6r9am1b4i
rambu9e6ns
rambu4e
ram1b2u
rambu1e2n
6ramed
ra1me
4ra4mer
ram8et.
4ramil
r2a1mi
ra2m1o
ram3pe1ri8
ra8m1p
ram1pe
ram6p3u
ramse8te.
ra6m1s
ram1se
rams2e1te
ramt8a8la.
ra2m1t
ram3ta
ramta6le.
ram3t2a1le
4ra1mu
6ra1my
r4an.
ra4naa
ra1na
6r3anal
r4a5nar
ran9c4s
ran1c
r4an9de.
ra6nd
ran1de
r5an5d4e5l
rand3r4
rand5s6a
r4an8ds
r4a1ne
4ran1fa
ra4n1f
ran5g4e
ra6ng
6ran1gi
rang5st
ran8gs1
rania8
ra1ni
ra6nin
r8an3kv2
ra2nk
2r5a4n1l
r6an1li
2r1a4n3m
r4a4nn
ran6n5e6tt
ran1ne
ran4n5in
ran1ni
ran6n3s4
ran2s7k2ar
ra6ns
ran1ska
4r1an1sv2
ran4t2ik
ra6n1t
ran1ti
r2a5nu
ra2n6ut
4r2a1nø
2ra1o
4ra1pa
ra4p6el
ra1pe
4rapin
ra1pi
ra4p2i6r5
ra4pis
ra6pit
ra1pl6
4ra1po
ra4p2os
4rap1pa
ra2pp
4r5app1l6
3rap1po
2ra1pr6
4ra2p2s1
4r8a1pu
1r4ar.
2r1a2r1b8
7r6arbe1h
rar1be
4r1a2r2ea
ra1re
4rareg
rar5e6l
4ra3r2e1p
rar7e1ta
rar4et
r1a2r1g
6rar1gu
8rarin1na
r2a1ri
ra4r3i4nn
6rarin1ne
rar8ka.
ra2r1k
4r1a2r1m
rar8ma.
rar1ma
6r2a2r2n
4ra2r1r
rar3r6e
r2a4r5s
2r1a6r5t
rar6ta.
ra3rø
4r4a1sa
2ra1sc
ra3s2e1a4
ra1se
4ras4el
ras3h
ras1ka8ra
ra2s3k2ar
ra1ska
ra2s3ke
ra4sk2i
ra7s6ko
ra6s1l4
ras2s4l4
ra4s1s
ras7s3t
4ras1ti
ra5s4t4il
4ra1stj
rast5re
rastr4
6ra1sty
ras7v2
r4a1ta1
ratak9
r6a1te
ra4te.
6ra5teg
8r7a6t2e1li
4rat1fe
ra6t1f
ra1to
ra1t4r4
ra4t5ro
ra4trø
ra5t6røy
rat5tel
ra6tt
rat1te
ratt4e4s
4ratub
ra1tu
ra2t5ut
6ra1ty
7ra2ud
rau8d3s
6raug
rau6ga
rau6ge.
rau1ge
4rau4k
rau4s6s
2rav
6r1a6v1d
ra2v4e5s4
r2a1ve
4r1a2v1g2
ra1vi
r3a4vis
4ra6v1l2
rav8l9u9t
ravlø8pa
rav1lø
ravlø8s
ra2v6r8
4r1a2v1t
ra5vy1
ra3vør
4raw
raz5z6
2ra1ø
raøy4
2r1b8
rba3d
r1b4a
rbe2d
r1be
rbe1de4
rbed5en
rbed5et
rbed9ra
rbe1dr4
rb2ie8
r1b4i
rbi9er
r2b2i5g
r2b2ik
rbi1st6
rbi2s5tr4
rbo8di.
rb2o
rb2o1d
rbo1di
rbo4ni
rb2on
rbo6n7s4
rb8o5re
rbra5s
rbr8
r1b4ra
rbrei6
rbu5e2n
r1b2u
r5b4ø
r1c
4rd
r4dab
r1da
r5dag
rda8g4s5
r5da1h
r4d5ak
rdal4
r4da1la
rda4le.
rd2a1le
rd2a8l1s5
rda4me3s
rda1me
r7da8n9o
r4da6n1t
rd5anta
r4d3a2r1m
r4d3a6r1t
rd5a6t3l6
r4da2t1m
r4d3au
r6d3d2
r6de1di
r1de
rde4e4n
r2de3e2
r3d4e1fi
r2de1f
r6d6e5ge
r2deg
r2d1ei
r9d4e1ka
r4de1k2l4
r4deks
r6d2e1lo
r6de1mi
r7d2e1na
r6d7e6ng
r6d7e4n1h2
rd2e6n4s3
rden1se4
rdenta8le.
rde6n1t
rden3t2a1le
r4de3o6
r6de1po
rd2e1p
r4der2ik
rd2er
rde1ri
r4d5er1s2t
rde4rs
rde6s1m
rde1s
r2d3e4ta
r6d7e6tt
r8dé
rdfes5
r2d1f
rd1fe
rdi3an
r1di
r4di2a1na
r4di1a6ns
r6diau
r4did
rd2i3e2
r2d2if
rdi6gres
rd2ig
rdig2re
rdi1gr4
rdi8g3s4
r4di5k2a
rd2ik
r4dik1l4
r4di1ku
r2dil
r6di1mi
r2d4io
rdi3ov
r4dis1h
r2dit
r2di1u
rd5j2e
r1dju
r2d7m
r8d3n
rd1næ4
r9do2b3
r1do
r4d5o4d
r4dol
rdon8na.
rd2on
rdo4nn
rdon1na
r2d1op
r6dor
r2d3ost
rd2os
r2d1o4v
rdo4ve2r5
r9drad
rdr4
r3drak
rd5ran
rd7ra1ra
rd1rar
rd3ras
r3drei
rd3ret
r5drev.
rdre1v
rd1ri
rd3r2ot
rd3s1ei
r8ds
rd1se
rd8s1ke
rd2s4kv2
rds5tan
rdst4
rd3sto
rd2s3t2o5g
rds7tre
rdstr4
rds7tu
rdsva9r
rds1v2
r4d5t
rdta8ka
rd3t4a
rd5tr4
rd7tø8
rd3u6nd
r1du
rd5ve
r2d1v
rdvi8ka.
rdv2ik
rdvi1k2a
r4dyg
r1dy
rdy5p8e
rdy3re
r6d7y2t
r6d5æ
r7d6ær
r7d6æ5r6e
r2d1øs6
r1dø
rdø4ve.
rd3år
rd7å6s2
1re.
2re1an
r2ea
re7a6r7an
re1ar
rea1ra
rea2r5e
4re1av
2r2e1b2
3redak
re1da
6redam
re3de1f
re1de
re5den
re3de1s
re3di
3red2ig
r1e4d1l4
2re1do
2re1dr4
re4d5ri
red5sku
re8ds
red4s1l4
red3s5la
red7s6led
reds1le
3reduk
re1du
6re1dy
2re1dø
2re3e2
re4el.
re3er
8r4e1fa
re1f
4re2ff
4r4e1fi
2r4e3fj
6ref2os
re1fo
6r8efr2
4re8ft
2r4e1fø
4regar
r2e1ga
reg4a7ta1
reg2at
re3ge
4re2g2e1b2
re5gel
4reg2en
4reg2er
4re4g1g
1re1gi
re4gia
re4gil
reg1l
2re1g2r4
4regub
re1gu
4reg2ud
2r4e1gå
2re1h
2reid
2re2i7e
2r1e2ig
rei8ga.
rei3ga
rei8ge.
rei1g6e
4re2ik
r4ei9l
rei5na.
re6i1na
rei7nas
re5in1de
rei6n1d
rei5ne.
re4i1ne
rei7nes
rein6skj2
rei6n1s
re4insk
re4inva
r6e1i6n1v
rei9ra
r2e2ir
rei5sa
r2eis
rei3si
rei7ska
reis6led
reis1l4
reis1le
re7is1m
re4i7va
rei5ve
2re1j
6rek.
re5ka.
r4e1ka
re5kav
re7ken
re1ke
4r4e1kj2
rekk6an
r2e2kk
rek1ka
rek4k5v2
5reklam
re1kl4
rek4led
rek1le
re5k6l2ir
rek1li
re7k2o
4reko2b3
4re1kom
4rek6on
6re1k2os
4re3k2ra
r2e1kr6
3rekru
r3ek1sa
6r1eksp2
rek4ter
re6k1t
rek1te
4re1ku
r8el.
r2e9la
4relag
9reland6sk.
rel4an8ds
rela6nd
9reland2s1ke
2r2e1le
rele8ge.
re3le1ge
r4e5lei
6relek
r4e7len
7rele1ne
6re2lg
r3elit
r2e1li
4re3l1j
r2e2l1l
rel4lag
rel6la6nd
rel5led
rel1le
rel4le1v
5r4e2l1m
rel5se6s3
re8l1s
rel1se
rel4sk
4re1lu
4re8lv
2r4e5ly
2r2e1læ1
2r2e1lø
4r2e7l6å
re2l1å7r
2rem
re5m4a4nn6
re1ma
r1e2m1b
remi6e1ne
re1mi
rem2ie
remi1en
remi6l
re7mis
rem9ji
remmed5
re2m1m4
rem1me
6re2m1n
rem8na
re8m5p4
re6m1s
rem9t2i9da
re2m1t
rem1ti
rem3tid
4re1mu
8re5my
4re1må
2r2e3na
ren5d4e5l
re6nd
ren1de
4r1endr4
8rened
re1ne
re5neg
re7nei
4re2nek
r3e4nel
4renest
6renet
6ren8g1d4
re6ng
reng5l
4reng7n
reng5st
ren8gs1
re5ni
2re2nk
ren6kl4
r3e4n1l
ren8ne8sl4
r2en1ne
re4nn
ren5ne1s9la
r2e5no
ren5sa
re6ns
r3en4s7s6
6r7en2tit
ren3ti
re6n1t
4r3en5tr4
6rentu
4r2e1nu
5ren1z
6r4e7næ1
4re1nø
re5og1
re3o6
2reo2p
re3o2r
5re2o1u
8repen
r2e1p
re1pe
6r5e6pi
1re1pu
6repus
1rer
6rerad
r2e1ra
6re9ra6ng
4rer6at
re5re.
re1re
4rered
4rere1f
4rereg
4rerei
re4rek
4rere6n1t
4rer2e1p
4rer2e3se
4re1re1su
4rere4t
6rer1fa
re2r1f
4rer2ig
re1ri
4rer2ik
4r3er1næ1
r2e2rn
4re1rol
r2ero
4re1rom
re3r2os
rero6se.
rero1se
re5r2ot
3re4rs
r6er1s4p2
4re3ru1t8e
r2eru
re6r7øy
r2e1rø
2r2e1rå
1r2es.
2re1sa
re2s3c
r2e3se
4res2el
re4sem
4reset
resi7e1re
re1si
res2ie
4resin
2re1sj
2resk
re2s5ke
re6s7kje.
re1skj2
reskj2e
re2s6k2l4
res7k2o1d
re6sky
6re1sl4
re4slu
1res1m
re5s1mo
re3sov
r4e1so
re9s1pe
re1s2p2
4re1spi
4respr6
2re4s1s
res4sal
res4sek
res1se
res4s2it
res1si
res4sj
res6sk
res7s8o6r1t
res4sp2
res4s2t
res4sy
re8s9t8a1ne
5re4s3tau
res6t5e2r7v
res1te
re2s4t4e3s4
res3té
4re3s1ti
res4t4il
re3str4
4restre
7re2s2t1v
4re5sty
4r8e1stå
1re1su
6resu2k
4resun
re3svi
resv2
2r2e3sy
2re1sø
4ret4a1ki
re1ta
re3tak
4re3tal
re4t4ap
4ret2ea
r2e1te
8retek.
6rete1ke
4ret4e2k1n2
6retel
6re5tem
re5ten
4r2e5ti
4r2e1tj
4ret2oa
r2e1to
2r2e5t2r4
ret1s4i
re4ts1
ret4st4
ret7ted
re6tt
ret1te
ret5ter
rett8o
rett6set
ret6ts
rett1se
4r2e5tu
2r2e1ty
2re1tø
5retøya.
ret4øy
retøy5a
7retøye4ts1
retø4y1e
retøye2t
4re7tå
2re1u
re2u6r
4revak
re1v
re5van
reva5re.
reva1re
6r6eveg
4revei
4revel
re6v7e4n1h2
re1ven
re5ver
rev4e5s
r3e4v2ig
re4v5i4nn
re7vom
1re2vy1
re4v5åp
3rew
2re1å6
2r1f
rfa5re
r1fa
r3far
rfat5
rfe8en.
r1fe
rfe3e2
rfee4n
rfe8er
rfe4et.
rfe3e6t
rfei5li
rf4eil
r4f2ik
r1fi
r1flå3
rf2l2
rf6e5m6ø
r2ft2
rf2u8se
r1fu
rfyr4
r1fy
r9fæ1
rfø8r2arar
r1fø
rf8ør
rfø1r6a
rfø5rar
rføra1ra
2r1g
r5ga.
r1ga
rga8le.
rg2a1le
rga8li
rg5a6nd
r7ga4nn
r4g5a6n1v
rg2a3r2i
rg3a6r1t
rga4ve.
rg2a1ve
r6ge1di
r1ge
rged4
r4ge1f
r2gem
rge6n1t4
rg2en
r3ge3o6
r4gerei
rg2er
rge1re
r4gere4t
r4g2e3ru
r4ge1sj
rg6es
r4ge1sl4
r4gesta
r2ge1st
rgi1a
r1gi
r3gi1e4n
rg2i1e
r2g2ik
r2gil
r2gim
r2g4io
r2g2ir
rg3i4ri
rgi7s1l4
r2gi1ø
rg2l6e
rg5le.
rglem5
r4g3len
r4g3ler
r2g1n
r4g2og
r1go
r3gom
r2g2ot
r4g5rab
r1gr4
r2g3r2ea
r2g5rel
rg5re2p4s3
rgr2e1p
rg5rin
r3gru
rg5sc
r8gs1
rgs6kor
rg5s4le
rgsl4
rg1s1n
rg5s6ti2l1l
rgs1ti
rgst4il
rg5sto
rg9stu
rg2sy
rg2u7d
r1gu
rg6ut
rgå9as
r1gå
rgå1a
rgå6v4a
rgå1v
rgå6ve.
2r1h
rhav2
rh2a
rha8vs3
rhju8l8s
rhju6l7
rh2o3d
rh4o
r7hu
rhø5re
rhån8d6s9
rh6å
rhå6nd
rhå9ne
ri1an
4ria4n1f
4ria4n1l
6ri1an1sv2
ri1a6ns
ri1ar
4ri1a2r1b8
4ria2r1r
6riau
2ri1av
ri4a1va
3ri6a1vo
2ri1b2
ri2b3l2
ri8ca.
ri1c4a
ri4co.
ri1co
r2i5da
4rid2a1le
ri5dal
ri5d2er
ri1de
ri5di
2r1idr4
ri4d3t1
4r2i1e2i7e
r2ie
rie1i
4ri3e2ig
4ri1eks
ri1el
6rie6ld
4ri5e2lem
ri2e1le
6ri7e6lim
ri2e1li
riel4la
rie2l1l
ri1e4n
ri3e6nd
4rie4n1h2
4rie6n1t
rien5t4r4
ri1er
ri2e5ra
ri2e5ri
4ri3e2tat
ri1et
ri2e1ta
ri5e1ven
rie1v
2ri1fa
r2if
rifer2i9e8
ri1fe
rife1ri
rif4fi
ri2ff
rifiser1b4a8
ri1fi
rifi1se
rifis4e2r1b8
6ri1fj
1rif2l2
rif5la
2ri1fo
2ri1fu
4ri3fø4
ri4ga.
r2ig
ri1ga
rig4gr4
ri4g1g
ri3gi
4ri1gj
4rig2re
ri1gr4
ri8g2s1
rig6s7t4
2ri3h
2ri1i
2ri1j
5rij.
ri4ka1li
r2ik
ri1k2a
ri5kan
5ri2k1d
ri3ke1s2
ri1ke
ri7ki
rik7ken
ri2kk
rik1ke
rikk5j2
4rik1l4
ri8k9la
2ri7ko
6rik6on
2ri1k2r6
rik4sk
rik4s5u
ri6k1t6
rik4ts3
riku6m
ri1ku
ri3k4v2
4ri3kå
2ri1la
6r5il1de
ri6ld
6riled
ri1le
ri5l1ei
ril6lest
ri2l1l
rill6es
ril1le
2ri5lo
ril4s1n
ri8l1s
2ri1lø
4r4i1ma
ri9mab
ri9mar
6rimes
ri1me
ri5m8et
2ri1mi
ri4mi.
7rimis
ri4m7l
4ri2m5m4
4ri1mo
ri4mor
4r1i8m1p2
4r4i1mu
ri2m9ut
4ri1my
rina5l
r6i1na
ri5n6a2m
4rinas
4r5in1c
4ri6nd
r3in1du
ri4ne3e2
r4i1ne
ri4nes
2r1i4n1f
rin8g7o8m
ri6ng
rin1go
rin2g3r4
ring4sa4
rin8g2s1
rings5ak
ring8s1pa
ringsp2
2r3i4n1j
2r2i2nk
4ri4nn
rin9nes
rin1ne
4rinor
r2i1no
2ri6n1s
r4ins6k
rin6s1m
2ri6n1t
rin4t5j
rin4t2r4
2r1i6n1v
r6i6nø
2ri1of
r4io
6ri3om
2ri1op
2rior
ri2o5s4
rio1t3r4
ri2ot
2rip
4ri1pe
rip2o4s3
ri1po
4r2i1r
4risau
ri1sa
4ri2sed
ri1se
ri2se5i
6ri2sek
4ris2el
ri4s4e5li
4ris1h
5risi1ko3
ri1si
ri3s2ik
2ri1sj
ri6sju
4ri1ska
ri4ski
6ris1ku
4ri1sky
6ri3s6t2ad
4ri5s2ted
ri2s1te
ris5t2ik
ris1ti
4rist4il
ri4sto
ri1s5tof
ri5stun
6ri1stø
ri6stå
4ris1v2
4ri1sy
4ri1sø
ri3te
6ri2te3o6
ri7t2i
rit2i9da
ri3tid
4rit4il
4ritj
ri5t6o
ri5tr4
ri4t1s6
ritt8s7t4
ri6tt
rit6ts
ri6tun
ri1tu
4rity
2riu2n
ri1u
ri2u4r
2riut
4rivar
r4i1va
ri6ve2d1
ri1ve
rive9ge
ri2veg
ri5vei
4rive2r1k
2ri1vi
ri4vi4s
riv5i1se
6ri1vo
4ri1ø4k
ri1ø
riø4r3a
8riøya
ri1øy
4ri1å4
r1j
r6j7am1b4i
rja2m7b
r4je3r6e
rj2e
r2jes
r4j2e1ti
r4j2e1tr4
r4j2e1tu
rju6la
rju2l
2r1k
r4k3a6k1t
r6ka4n1f
r4ka1o
r4kapr6
r6ka1t4r4
r3ke.
r1ke
r2ke5h
rk5e2ik
r2kei
rkei8k2a
r6kek
r4k2e1lo
r4ke3lu
r3ken.
r4kena2v
rk2e1na
r3ke3ne
r4ke4ni
r5ke6n1s2
r3ke5ri
r4ke4ris
r4k2ero
r5ke4rs
r4k2e3ru
r4k2e1rø
rk2e4se
rkeslø7se
rkesl4
rke2sløs
rkes1lø
r9ket.
rk4han
r4k1h
rkh2a
r3ki
rki3d
rk4i3e2
rki4vi
rk6iv
rkjek8
rkj2
rkj2e
r6k5jor
rkj2o
r2k1k2
r5k8led
rkl4
rk1le
rk2li
r4k5lun
rk9lut
rklæ5re
rk2læ1
rk2lø
rklå9ra
rk2lår
r3k2nek
r2k1n2
rk1ne
r5k2n2e1p
rk7nes
r3k1no
r2ko2b3
r4k2o6b5r8
r6k2o1fo
r2k2o1h6
r4k4o1la
r4k2o1li
r4ko1pe
r4ko1ra
r4ko5r1u
r4kos2el
r1k2os
rko1se
r5ko6se1le
r4ko1sj
r6k7ras
rkr6
r4k5rei
r5k6rem
r8ks
rk4sar
rk1sa
r6k6seg
rk1se
rk2s1i
rk4ska
rk1st
rk6s3tal
rk4sten
rks1te
rk4s5ti
rk4s1tj
rk4sto
rk6s5vi
rks1v2
rk5ti
r6k1t
rkti4s
rk5to
rku4le.
rku1le
r6k7u6t
r4k5ve6d2
rkv2
rk9vei
r5k4vel
r4kver
r2k3v4es
rk5v2ik
r4k5øl
r2k3øy
rk9ø2y1s
rkå4k
r4k5å1ke
rkå6pa
rk5å4s2
2r1l
rla4te.
rla1te
r2l2e4a
r1le
r2le1f
r3l2e1p
r4l5e4ri
r6le7sl4
rle4st
r4le1su
r4le1u
r3l4i
rli9ke
rl2ik
rlin8g3s4
rli6ng
rli8ta
rli4te.
rli1te
r3lj
rl6o
r1l2og2
rlo5ve
r1lov
rl4sk
r8l1s
rlu4e
r3ly
rlys7k4
rl2ys
r6l5z
rlø8pa.
r1lø
rlø1pa
r5løy9
2r1m
rma6ge.
r1ma
rma1ge
r4m2a5k4l4
r4m5al1te
rma2l1t
r2m3a4n1l
rm4a6ns
r6ma6n1v
r4ma1re
r4ma2r1r
r2m5av
r2m3b
r2me7g
r1me
r2mek
rme6lap
rm2el
r4m2e1la
rme5ne
r4menet
r6mere1v
rme1re
r4m2e1se
rme9tar
rme1ta
r4mey1
r4m5i1de
r1mi
rmi6e1ne
rm2ie
rmi1en
rmi1ni6
rmin5s4ki
rmi6n2s
rm4insk
r6m5ins4t
r2m5i4v
r2m1j2e
rmlø8pa
r4m3l
rm1lø
r3m6o4e
r1mo
r2mof
r2m1op
rmo7st
rm2os
r8m7p
rm5s6ko
r6ms
rm1sl4
rm1s6n
rm1st
rms5t4il
rms2ti
rm1su
rmta8la
r2m1t
rm3ta
rm3te
rmu7an
r1mu
rmu6a
rmu2e4
rmu7e5ne
rmu1e2n
rmu8la.
rmu5la
rmu6le.
rmu1le
rm5øy.
r1mø
r2møy
r9må.
r1må
rm6ål4
r6m5åp
r9mår
r6m7å1ta
2rn
r3na
r4n1ak
r4n3a6ld
r6n5ap3par
rna2pp
rnap1pa
r4n1a2r1b8
r4n3a6r1t
rnat7r4
r4na2t5v
r6n9a6vis
rna1vi
r4n3a6v1l2
r6n3d
rn1dø4
rnd2ør5
r3ne.
r1ne
r2n2e5a2
r5neb6o1e
r2n2e1b2
rneb2o
r2n2ec
r4ne1f
r2nel
rnele6ge.
rne1le
rne3le1ge
r3ne2l1l
r3ne8l7s
r4n3e6ng
r3nen
r4ne4n1h2
r4nerei
rne1re
r6neris
rne1ri
rn2e3ro
rne1s2
r5n2es.
rne6se.
r4n2e1se
r8ne3si
r4ne3sk
r4nes1m
r2n4e3so
rn5e4tab
r2ne1ta
r4n2e3te
r2ne1v
rne5v1r8
r2né1s
r1né
r6n5g6
r4n1i4nn
r1ni
r2n5k4
r4n3n
r5no.
r1no
r4n2oa
r2no5b
r4n2o1d
r4noi
r6nok
r2nom
rn5o2m1n
rn3o6m7s2
r4n1op
r4n3o2r1k
r2n2os
r4n1o4v
rn7se
r6ns
rn4s3in
rn1si
rn5s1ke
rn3skr6
rn5sla
rnsl4
rns3le7ge
rns1le
rn7s6mi
rns1m
rn6s3o2v1n
rn2s1ov
rn5sp2on
rnsp2
rns1p6o
rn3s4pr6
rn1st
rn4s1ti
rn3te
r6n1t
rn5ti
rn7tr4
rntre4
rn2t4v
r1nu4
r2n5ug
r6n3ut
r7n4øt
r1nø
r4n5øv
rnå8le.
r1nål
rnå1le
rn3å2s2
ro1a4k
r2oa
roa4s
ro5a1si
9roban
r2o3b4a
9robar
1ro1b4e
ro4bed
ro4b4e1f
5roben
ro4b4e1s2
5robøl1gj
ro5b4ø
robø2lg
1r4oc
r3od8ds
r2o1d
ro6d1d
7ro1do
ro5e6nd
r6o1e
roe2n
ro7e6ns4
r2o7fa
ro4fel
ro1fe
ro4fem
roff5ri
rof6fr2
ro2ff
ro7ga.
r2og
ro1ga
rog4a9ta1
rog2at
ro7ge.
ro1ge
rog5ret
ro1gr4
ro6gry
ro8g1s4
ro2gu
1roi
ro4kel
ro1ke
ro5ki
rok6kat
ro2kk
rok1ka
rok6ke1ri
r4ok1ke
rok5kl4
rok4kom
r4o7k6l4
r2o2k5n2
rok7s
rok8se.
rok1s4e
rok5v2
roli7ga
r2o1li
ro3l2ig
4r3o6lj
rol4la1b
ro2l1l
rol4lap
rol4leg
rol1le
rolle8ge.
rol3le1ge
rolle8se.
rol4l6es
roll2e1se
rol4lis
rol1li
rol6ly
ro1lo9v
r2o1lo
2ro1ly
ro6mak
r2o1ma
ro4mal
3ro5m8an
ro4ma3te
rom6at
2ro2m7b6
4r2o3me
rome5d
4ro2m3k2
4r3o2m1n
ro4mor
r2o1mo
2r1o2m1r6
rom5sla
ro6ms
romsl4
r7om1sy
rom1s4ø3
ro2m3t2
ro5ne.
r2on
ro1ne
ro7nim
ro1ni
6ronis1m
6ronista
8roni2s1te
6ronis1ti
4ro4nn
ron4na
ron2o5s
ro1no
ro6n1s4
ronta6le.
ront6a
ro6n1t
ron3t2a1le
ro4pad
r2o1pa
5r2opet
ro1pe
ro4pia
ro1pi
ro8p1la.
r2opl6
ro1pla
2ro2pp
ro9py
ror3a
ror6da.
ro4rd
ror1da
r8o3re
ro1r1u
ro4sat
r2os
ro1sa
ro5s2el
ro1se
ro3s2en
4ros2l4
ros3la
ro4s1m
ro6sov
ro3so
ro1s1p2
ros4s2t
ro4s1s
ros4sy
ro1s7tas
ro3s1ti
ro3str4
ro1s2t7rø
ro1t5ek1te
r2ot
ro1te
rote6k1t
ro5t4es
rote7s6ter
rotes1te
roti7k2a3
rot2i2k
ro1ti
ro4t5o4r5d
ro1to
ro5tu
6ro1ty
roun2
rou6nd3
ro5ut
ro5va.
ro9va9re
ro7vas
ro5vek
r4o7ven
rove5re6
rove2r
rov5s1m
ro8vs
rovve6
ro2v1v
rò6te.
rò1te
2r1p
r5pa
r6p1a2r1b8
r5pe1fo
r1pe
r2pe1f
r4p2e1no
r6pe1nø
r5pesk
r2pe1s
r5pet
rp6j
rp2l6
r2p3lad
rprø5ve
rpr6
r5prøv
r3pu
rpu6n7g
r6p5ut.
r6p5øy
r2på1k2
2r1r
r8raa
r2rag
rra3r
r4raro
r4r3d
rr6e
r4r2e1b2
r7rebart.
rre1b4a
rreba6r1t
r2re1f
rre7i6n1t
r4re1kl4
r5relat
rr2e9la
r4re3o6
r4rep2l6
rr2e1p
r4r2e3ru
r1rer
r2re5sk
r4res1m
r4r4e1so
r4re9s1pe
rre1s2p2
r3re4s1s
rre4st
rres5ta
r4re7s1ti
rre5str4
rre4t6s5
r2re5u
r3ri
rri6ka.
rr2ik
rri1k2a
r6rip
rri5v
r2r3m4
rrmå8la
rrm6ål4
rr1må
rr6o6e
r5rom
rro8sa
rr2os
rro8se.
rro1se
rro8ta
rr2ot
r4r5s2
r2r3un
r2r5v
rrå5de
rr6å1d
4rs
6rs.
r1sa
rs3ab
r2s7ad
r3sak
rsa5ka
r6s1a6k1t
r7s8a6la.
rsa1la
r8s9a6ld
rs3a2l1l
r5s2am
r4s1a2r1r
r4s3a2r1v
r1sc
5s6c2h6l
r8se1di
r1se
rse6g7
r4se1ku
r2s4e1la
rs2el
rs7e6l1d
r4s2e1li
r4s3e8lv
r4s5e4r2ik
rs2e1ri
r3ses
r4se1si
r6se1su
rse4te.
rs2e1te
rs6e6tt
rsett8o
r7sim6
r1si
r2si8m1p7
rs1in
r5s4i6ng
r4si6n1s
rsis5t
rs2is
r7sja
r1sj
r4sj1h
r2sj3or
rsj2o
r3s2kad
r1ska
r6s7kaf
r2s4kam
r3skap
r4s1kar.
r2sk2ar
r4s1kas
r4ski
r5skil
r5sk8in6n1s5
rs2ki4nn
r1skj2
rs5kje6ns
rskj2e
r6s1k2l4
rskla8g
r6skla
rsk5lar
rs4k5le
rs4k3læ1
r5sko.
r5sk6o3e
rsk3op
r4skor
r3s2k2ot
r1skr6
r4s3k8ra
r5s6kriv
r4s3k2ro
r1sku
r5sku.
r5s4kue
rsku7et
rsk5u6nd
r2s1kun
rsk5var
r2skv2
r4s5k2ys
r1sky
rsk5ø
rs4le1f
rsl4
rs1le
r6slei
rs4lek
r8s5les
r5s6lit
rs1li
rs3lok
r4slun
r1slu
rs4m2a1ke
rs1m
rs1ma
rs4mo.
rs1mo
rsm6ål4
rs1må
rs6ne1v
rs1n
rs1ne
r1so
rs2o9a
r4s5o4m3l
r3s2o6n5
rso7n6al
rso1na
r7s6o5n8e
rso5n6i
rso6ns4
r2s1or
r4s5o4rd
r7s6o7ris
rso2ri
r2s1ov
r1sp2
rs4pan
rs1pa
rs6pa1t
r5s2p6el
rs1pe
r4sper
r7s2pe1s
r5spi
rs4por
rs1p6o
r5spred
rspr6
r4spå
r4s3s6
r1s2t
r4s5ta2b1b
rs5ta2nk
r2s3tap
r6s6t7b2
rs4ted
rs1te
rs4tem
rs5te6nd
rste6n6s
rs5t8er.
rstev9na
r5ste2v1n
rs2te1v
r3st4il
rs1ti
r4s5ti4l1f
r4s5ti8l1h
r6s5ti8l1s
r4s5ti8l5v6
r5stis
r4s2tit
r6s4t5k2
rst4r4
rs9t1re.
rs7t2re3e2
r4st3rin
r4s5tro
r5s2trø
r2s4t7ut
rstu9va
rstu1v
rstyg7
r1sty
r3st6ø
r7stå
rs5ukl4
rsu2k
rsu9r
rs4u7sa
r1sus
r4s5u1si
r1sv2
rs8vak
rsva9ra
4rsv2a4r4s5
rs1ve
r3s4vek
rs5vit
rsy4na
r1sy
rs2yn
r2sy3t
r1s4z
rsøks3
r1sø
r8s9ø4y1e
6r1t
r4t5ad
r4t5af
rt4a4ka.
rta1ka
r4ta2na
r2t3a4n1l
rta9pa
r4ta2r1r
6r4t3a6r1t
rt3a8vs
r2tav
r4t2ec
r1te
r4te1da
r2ted
r3te1de
r2te3e4
r4t4e1go
r4te1g2r4
r4t3e2i7e
r4te3in
r4t4e1ka
rte6ke.
rte1ke
r4te1ki
r4te1ku
r4t5e4lit
rt2e1li
rte6ma.
r2tem
rte1ma
r3te8m1p
rten4s5k
rte6ns
rtent1le8
rte6n1t
rten6t5l6
r2te3o6
r7t6er
r5t6e4r5d
r5teres
rte1re
r4teris
rte1ri
r5te2r1k
r4t2e1rå
r4t2e1se
rt4es
r6te1sk
r6tes1ti
r4te1ta
r4t2e3te
r4te1v
r4t1h
rtia6n8d
r1ti
r4ti1a6ns
r4tiar
rti8ar.
rti5en
rt2i1e2
rti6g2ra
rt2ig
rti1gr4
r4ti3k1v2
rt2ik
r4ti1la
rt4il
r4ti1li
r4tilo
r4t6i7na
r2t4io
r2tip
rti7sa
r6ti1s4ka
r4ti1ski
r4t6i9so
r4tis1p2
rti4s3s
r4ti5str4
r3ti1tu
r2tit
r2tiv
rtma6le.
r2t1m
rt1ma
rtm2a1le
r5to.
rt3o2ff
r2tof
r9t2o1fo
r9tok.
rt6ok
r4t3o2m3k2
r4t3o2pp
rt6opp.
r4t3o4rd
r6t7o6s
r2t1o4v
rt2r4
rt8ra
r9t1re.
rt6red
r6t3reg
r4t3rei
r4t5re7k2o
rt5rel
rt5r2e1p
r7t6ri1b2
r4t3ris
r4t5r2os
r2t3rut
r5t6rål
rts3ar
r4ts
rt4seg
rt1se
rts5e6ng
rt2si
rt4s5ja
rt1sj
rt5s1ke
rt3skj2
rt5s4no
rts1n
rt3s4pe
rtsp2
rt4s1ti
rt1st4
rt7s6trek
rtstr4
rt4s5t8øy
rt1stø
rts5un1de
rtsu6nd
r6t3t4
rtu6en.
rtu1e2n
r7tug
r4t3u6nd
r2t1ut
rtu8ve.
rtu1v
rty8da.
r1tyd
rty1da
rty8de.
rty1de
rty4r5s
rty6ra
r2t5y2t
rtæ9ra
r1tæ
r4t5øl
r6t5å3s3
ru3a6nd
6ru1av
r1u6a2v1h
ru8bl2
ru5b2o
ru4di.
r2ud
ru1di
rud4r4
ruds4l4
ru8ds
ru4e1le
ru1el
ru1e4r
rue3s4
ruga8l
ru1ga
rug2a5t
rui3d6
4ruk
ruk4su
r2uks
ruk4t3s
ru6k1t
ru9la
4ru3l4i
6r7u6l1k
rul8ke.
rul1k4e
r2u2l1l
r7u8lv
r4um
rum3al
r2u1ma
rum4p9l6
r2u8m1p
5r2un1de
ru6nd
run5d4e5l
6r3und2er
7r4under.
r5unde4rs
rund3r4
r4un8d3s4
run6ge.
ru6ng
run1ge
4ru2n6i
run5kr6
r4u2nk
r7uly
ru4nøy1
ru3nø
ru2r
ru5ra
ru8ran
ru8rar
ru9rer
ru1re
rur8ta
ru6r1t
r4us
ru2s2h3
6r7u6s2ik
ru1si
ru1s5j
ru4s7lu
rus1l4
ru1s4o
rus5s4el
ru4s1s
rus1se
rus4s2t
ru4s4t3r4
r4uta
ru3tal
rut8a8la.
r7ut6an.
r4ut1be
r1u6t1b2
4r1u2t3d
ru1t8e
ru4te3i
ru4tel
ru9tene.
rut2en
rute1ne
r2u9ter
2r1u2t3g2
r1u4t3k2
r2ut9o
6rut1r4
rut4re
ru6t4rø
rutto5
ru6tt
2r1u2t1v
ru5va
ru1v
ru4ve2d1
ru4veg
ru4vei
ru4vel
ru4ve1re
ru4v4e3s
ru5vi8
ru6v7is
2r1v
rv4a
rva7ka
rva6la
rve3de
rve2d1
rve4den
r4ve5dr4
r4v2e1ga
r4ve1gi
r4ve1g2r4
r4v4eim
rve4i1s7e6
rv2eis
r2ve5kl4
r4v2e1la
rvel9le
rve2l1l
r4v5e6ng
r1ven
r5v8er.
rve5re
r2v4es
r2ve1v
rvi2l9l
rvil4le9d
rvil1le
rv2j
r4v2os
r4vo1v
r3vr8
r8v2s1
r2v5u6ng
r1vu
rvå7r
4r1w
rx1
ry1a4
ry2dr4
ry7fe
ry1f
ry5f2l2
ry5ke.
r6yk
ry2ke
2ry1kl4
ry7le
ryl4l5i8s
ry2l1l
ryl1li
4r5yn4d1l4
ry6nd
ry4ne5s4
ry1ne
ry5n2es.
ry4pa.
ry1pa
2ryr
ry8re
r1y2r1k
ryr4ke.
ryr1ke
ry9ro
ry5rø
rys6sal
r2ys
ry4s1s
ry5ta
ry4tek
ry1te
1ry2t1m
r3ytr4
r4z
ræ7le
8r3æ4re
8r3æ4ren
rær5in
ræ1ri
ræ6r8t
ræ8v
2rø.
rø8ar
r6ø1a2
6rø1b
rø4be.
rø1be
rø4dek
rø1de
rø8d1s
4rø5e1p
r4ø1e2
røf5l2
rø4ke.
rø1ke
rø4k2ero
røk3l4
4rø2k1n2
røk5s4
rø6k7t
røk1v2
2røl
rø6m
rø1m1a
røn5nes
r4ø4nn
røn1ne
rønn5s4a
røn6ns
rønn5sk
røn5sko
rø6n2s
5r6øn2t9g2
rø6n1t1
rø4pe.
rø1pe
røp9l6
1rør
rø4r5d6
r6øren3de.
rø1re
røre6nd
røren1de
rø7r6et
5rø2r1l
rø2r3o
rør4sp2
rø4rs
r8øs.
rø3se
rø5sla
røs1l4
røs5v2
rø8ta
r4øt
røt9a4s
rø1va
rø5ve1de
røve2d1
rø9ve4rs
rø1vi
r4ø5væ
2r1øy.
4røya
røy9ar
røy6ed
rø4y1e
røy7e6ne
røye4n
røy5es2
røy4e2t
5r6ø6yk
3røyr
røy5re
røy8senes
rø2y1s
røy1s4e
røys2e1ne
6røy4s3k
røy4s2t
2rå.
rå7a
4råag
4rå1b4
3rådet
r6å1d
rå1de
rådy9ra
rå1dy
rå9e1ne
r4å1e
råe2n
2rå1f
4r2å5g4
2rå1kj2
råk3re
råkr6
rå2k3u8
råk1v2
4rål
rå2le7s8
rå1le
rå4let
rå5let.
rå5l1u
r5å6nd
rån6da
2råp
2r1år
rårs5k
rå4r2s
rå5ru
rå1s2
4rå8s9b4
2rå1se
rå5si
2rå1sj
r4å1s4t
6rå1ta
råt4a8ka.
råta1ka
rå5tr4
rå5t1u
2saa
5saa.
sa4ba.
sa1b4a
s6a1be
s8ab1l2
sa5bok
sab2o
s3ab2on
sa5by
sa3ce
s8ac
sa4dag
s6a1da
4sadam
sa4de1re
sa1de
sad2er
4s1a2d1m
sa4do
2s1a2d1v
sa4e5d
s2a1e2
3saen
7saer
1sa1fe
5s4a1ga
sa4ga.
sa4gas
sa4g2at
6s3a4ge6n1t
sa1ge
sag2en
6s5a4g1g
6s5a6gi
sag8na
sag1n
sa6go.
s4a1go
sa4g2og
2s1a2gr4
sa8g3s4
sa3ik
sa5ir
sa1is
5s6ak.
sa2ka
3s4a1ka.
4s1a2kad
sa5kai
3sa6k1b4
3s2a2k1d
3s4a3ke
5sa2k1f
1sa4k1h
sakh5e
1s4a1ki
s2a4kj2
sak5kr6
sa2kk
s4ak1ky6
5s2ak1l4
5sa2k3m
4s2a1ko
3sa6k1p
sa1k2r6
5s4aks.
sak4s3i
4s3ak1sj
sak4sp2
5s4ak8s9r
5s4ak1su
2s1a6k1t
sak6ta.
5s4akto
s4aku
4sa7kø
1s4al.
sa1la
4s5a4l2a2r1m
7s8a1la.
5s2a5lat
4sa2l1b2
1s2a3le
sa9let
1sa2lg
s4al1ge
4s5al1go
sal8g6s5
s6a1li
sal4mes
sa2l1m
sal1me
sa5lo
5s2a8l2s3
sals4a
4salter
sa2l1t
sal1te
sa1lu
3sa8lv
sal5ve2d1
2s6a1ly
1s2am
5sam.
sa2ma
sa5ma.
sa3m4an7
sa5mas
sa2m5ei
sa1me
sa4m2el
sa4met
5sa4m3l
sam4le1v
sam1le
5sa2m1m4
sammen5
sam1me
sa4my
4s1a2na
s6a9na.
s4a7nar
sa3nat
san6da.
sa6nd
san1da
san7d8al
5s6an3de.
san1de
sand5r4
sand5s6lo
s4an8ds
sands1l4
sand5st4
san4d5ø
1s2a5ne
4s3a6nek
5sang.
sa6ng
3san1g4e
4s3an1gr4
s7anken
sa2nk
san1ke
2s1a4n1l
s5a4n3m
san5ne
sa4nn
6san1no
sa2no
s5anor
san5os
sa6n1s
5s4ans.
4sansa
5s4ansen
san1se
san7s6k
4s5ans8l4
6s1an1sv2
s8ant.
sa6n1t
san9te
6santr4
4santy
4s1a6n1v
2s1ap
sa2po
1s2ar.
1sa1ra
6sarab
2s1a2r1b8
s4a4rd
9s8a1re.
sa1re
4sareal
s1a2r2ea
4sareg
sa5re1v
3s2a1ri
sa4ri.
sar6ka.
sa2r1k
4s3ar3ki
2s1a2r1m
sar5me
sar8me.
s1a2r1r
2s1a6r1t
sar4ta.
sa4ru
4sa2r1v
s4a1ry
1sas
8sasju
sa1sj
2s1a4sp2
4s1a4s1s
s6ast
4sa1sty
2sa1su
4sa1sy
1sat
s4a1ta1
s4a5ten
sa1te
s4a3ti
2s1a6t3l6
4s3at2m2os
sa2t1m
sat1mo
sa1to
4sa5t6r4
s5atsk
s2a4ts
5s4att.
sa6tt
4s3at1ta
6s5at3ten
sat1te
satt4e4s
5s4au.
sa2u4d
sau5di.
sau1di
5s4a6ue
4saug
sau6ga
s3au1ge
sau6ge.
2s1auk
5saum
3saus
4saut
2s1av
s2a1va
sa4ve.
s2a1ve
5s6a5v6in
s6a2v5n
3s2a1vu
8s9b4
sba4ne.
s1b4a
sba1ne
sb2i6e
s1b4i
sbo4da
sb2o
sb2o1d
sbu6et
s1b2u
s6bug
sbul3
sby8ta
s1by
4s1c4a
1sce
2s1cel
s4ce1ne
s3cer
6s2ch.
8schl.
6s7c2l
4sco.
6s1c4o4c
4sc2os
s4cus
s1cu
8s9d6
sda8g4s5
s1da
sda8m9p
sde6le.
s1de
sde1le
sdu8en.
s1du
sdu1e2n
sdu8er
sdø8v
s1dø
1se
2s2e1a
3sea.
sea4g
se3a6n5d
se1an
se7ansa
sea6ns
sea9re
se1ar
5se4au3sk
se1au
seau5s
2s2e1b2
4s2ec
4se5d4ag
se1da
se6d5d
se3de
5se3de.
5se4d1l4
4se1do
2se1dr4
2se1du
6sedvan5le
sed7va
se2d1v
sedva4n1l
2se1dø
5see.
se3e2
se2e3d
2see1f
2seeg
se6e3i
se3e4l
se5e4n
seer1
5sees
2see1v
2se1f
s1e2ff
4s1e8ft
6s2e1ga
sega6l
se2ge
se6g6es2
se3g2e1v
seg8ga.
se4g1g
seg1ga
9segl.
7se4glet
seg1l6e
3s6e2g1m
4s2e7g8r4
2se1h
2seid
sei8dan
se2i1da
se2i9den
sei1de
sei8e9nes
se2i7e
sei1e2n
seie1ne
s5ei4et
2se2ig
sei6ga.
sei3ga
sei5g6e
se2i5k
3s4e8il
6s4eim
2sein
se3i6n1d
s3e4i1ni
se6i2n3k4
se3i6n1s
se3i6n1t
s2e2i5r
3s2eis
2se1j
5sej.
2s4e5ka
3se1ke
4se1ki
2s4e1kj2
5s2e2kk
sek4kes
sek1ke
2se1k4l4
4s4e3k1n2
2se1k2o
s2e2k1r6
4se3k2ra
4se3k2ri
4se3k2ro
3s4ek1sj
4s1eksp2
sek4st
sek2t3an
se6k1t
sek6te.
sek1te
sekt2e9ra
3s4ek5to
4se5ky
4se1kå
s2el
2s2e1la
se6la.
3se2l1ak
5sel6a3ne
5selar
se4l5a6r1t
s3e4las
se6l5at
se6l1d
se4le.
se1le
4s4e5led
6sel2e1ga
4selei
4s3e2lem
4sele6ng
s4elen
4seles
4s3e4le1v
5s6e2lg
4sel2ik
s2e1li
4selil
4selis
4s3e4lit
sel6løp
se2l1l
sel1lø
2s2e1lo
7selol
se3lom
3se8l1s
sel4sin
sel3si
8s5el6s5ke
sel9s8lag
sel2s1l4
sel4s1p6o
selsp2
se2l5t6
2selu
se6l7u6r
sel4v5a6k
se8lv
sel1va
sel4v3an
selv3e4
sel4ve.
sel4vei4
sel4ver
sel8vin
sel1vi
2s4e1ly
2s2e1læ1
2s2e1lø
selø8pa.
selø1pa
6s2e1lå
2se3ma
3se2m1b
7s6e2m1d2
s8e5me
se4mi.
se1mi
semi5ni6
s4emin
2s1e2m1n
sem4na
9s8em6nd
2se1mo
sem5pe
se8m1p
2se1må
s2en.
6senau
s2e1na
sen9d8a
se6nd
sen6d2e1la
sen5d4e5l
sen1de
6senden
4s1endr4
s2e1ne
4sened
se3neg
4s4enem
8se2ne1sa
6se4n2e1se
sene8se.
s5engas
sen1g6a
se6ng
2s1e4n1h2
se6nin
se1ni
s3en5kj2
se2nk
5se4n3n
s2e6ns
4s5en2sem
1sen1se
sen6s5e6nd
sen4sj
sen3so
7s6e6n1t
sen5t4er
sen1te
8s7en5tr2e1p
sen4tre
sentr4
4senum
s2e1nu
4se1ny
2s4e7næ1
6se1nø
2se3o6
7se2o5d
se3or
2s2e1p
se3pe
seper1so5
sepe4rs
3se2p1t
s2er.
s2e3ra
4seram
5seran
4serap
5seras
6se2rau
se4r5d
s2e5r4e
5se1re.
4ser2ea
4sered
4sere1f
4sereg
4serei
4serek
4serel
4sere4nn
4sere6n1t
4ser2e1p
4ser4e1so
4se2r3e4s1s
4serest
4se1re1su
4sere4t
4sere1v
s1e2r1f
s2e1ri
seri6e5ne
seri1e4n
ser2ie
4ser2ik
4serkj2e
se2r1k
serkj2
5s4erkr6
5s2e2rn
ser7ne1v
ser1ne
2s2e1ro
se4r1op
se4r1o2r
se4r2os
9s6e6r1t
ser4tak
s2erta
ser6tat
ser4t2r4
s2e1ru
4serul
se4r3un
ser4ve2d1
se2r1v
ser4vel
2se1ry4
2s2e5r6ø
2s2e1rå
5s2es.
ses5a6ld
se1sa
ses4al
5se5s2a8l2s3
ses5a2l1t
4sesc
2s2e1se
se4s2e1ne
se3s2en
ses5in
se1si
se3sj
4sesj2e
4ses4ju2k
ses5kal
se1ska
se2s5k2ar
se2s5kv2
ses5lit
sesl4
ses1li
se3s1na
ses1n
5s4e1so
ses3pr6
se1s2p2
ses4s5in
se4s1s
ses1si
se1st
5se6st.
5se6s5te.
ses1te
4sesto
ses5un
se1su
ses1v2
2s2e3sy
4se1sø
ses3å
3s2et.
2se2t6a
3s4eta.
se5t8a1e2
seta8ka
se3tak
se5tar
6set2ea
s2e1te
4seteg
4setei
4setek
se7tel
se4t2e1ra
se5t2ero
4set4es
2s2e3ti
se8ti.
s3e4t2ik
s3e4tis
4setj2e
s2e1tj
5se2t1je.
7s6e6t3l6
5s2e8t1n
2s2e1to
2s2e1t6r4
s4e4ts1
s5ette4rs
se6tt
set1te
2s2e1tu
2s2e3t2v
2s2e1ty
6se1tø
3seum2
se1u
4se3u2n
seure9ne
se2ur
seu1re4
seu2t
2se1v
seva6ne.
sev4a1ne
s8e5var
se6v4d
sevi4sa
s1e2v1n
sev4ne.
sev1ne
se3vr8
3sev2åg
2seyn
sey1
2se1ø4
2se1å6
1sé
2sé1a
6sé1b
4sé5e8
4sé1f
4séj
4sé1k
2sé1l
4sé5o
6sé1p
9sé1r
4sé1s
2sé1v
2s1f
sfa4ne.
s1fa
s5fa1ne
6s1fe
sfe6et.
sfe3e2
sfe3e6t
sfes5
sfisken8
s1fi
sfi2sk
sfi8s1ke
s1flå3
sf2l2
s5fo
sfo8r1a
sfor1lø9
sf6o2r1l
sfra5s
sfr2
s1fra
sfri5e6re
sfri1er
sfr2ie
sfy4rs5
s1fy
sfyr2
3s2fæ1
sfø9ren
s1fø
sf8ør
sfø1re
sfø5rer
sfø5ri
6s9g6
sga4l
s1ga
sga8va
sga4ve.
sg2a1ve
sge6n9s
s1ge
sg2en
s2ge4st
sg6es
sgå4v4a
s1gå
sgå1v
sgå4ve.
s1h
4sh.
sha2k
sh2a
s7hat
s3h4au
6she
sh2e2a4
s5he2i5
7sh6e4r2if
she1ri
s4hi.
s6hip
s4h5isk
shi2s1
4shj
6s7ho2pp
sh4o
3s2h2o6r1t
3s2how1
6s6h1s
2shu
4s5h6y
s5hø
shø8l
shø6va
shø6ve.
shø1ve
s7h6å
1si
sia8l5v6
4sia5m
si7a6ns
4siap
4si1av
si2bl2
si1b2
3s2i2da
3s2i4de.
si1de
3s2i3den
si4de3o6
s4id2er
si5der.
si4d2e3te
2si2do
4s1idr4
sid8ra
4si2dy
4sieg
s2ie
si1el
si1e4n
si5er.
si6eren
sie1re
si2e4s
si3est
6si1fe
s2if
si3f2l2
2si1fo
si4f3r2
2si1fu
4si3fø
3s2ig
si5ge4r4s
si1ge
sig2er
4si1gi
4s3iglo
sig1l
si5gr4
4si3h
3s2ik
si3k2a
si5ke.
si1ke
si4k2h7
sik4ka
si2kk
sikk8artet
sik4k7a6r1t
sikkar3te
sik4k5el
sik1ke
sik4ko
si1ko3
si4kom
si4kop
si4k2os
si4k2ot
sik4t4s3
si6k1t
s2il
5sil.
3si6ld
sil4del
sil1de
sil4d2er
sil4de1s
si2l5j
si6l2k
sil4l6es
si2l1l
sil1le
2si1lø
si6m2el
si1me
4sim8et
2si8m1p2
s4i3mu
4sin1de
si6nd
4s3in1du
si3nek
s4i1ne
2s1i4n1f
sing4s5a4
sin8g2s1
si6ng
7s6in1gu
si4ni
4si4n1j
2si4nn
3s4inn.
7s4in1na
s6inne.
sin1ne
5s4innet
s3in4n1h2
s5in2n7k2
s3in4n1l
4s1in6n7t4
si5nob
s2i1no
sin2s1k5e
si6n1s
s4insk
2s1i6n1t
4s1i6n1v
s6i6nø
4si5ov
s4io
si4pa.
si1pa
si8pe.
si1pe
si6re.
s2ir
si1re
si7ren
si4ri.
si1ri
sir8kl4
si2r1k
s2is
si5s2el
si1se
si4s1e2r1f
si2s5e4v
si6sin
si1si
6sisju
si1sj
si4sk
si8s5ke
si4s1n
si4s5te
si4s1ti
sis3to
4si1sy
3s2it
si5ta
si2t8ji
si6t7ra
si1tr4
si4t5re
si4tri
si4t3s4
sitsva9
sits1v2
sit6te2r1m
si6tt
sit1te
sit4t4e5s4
si4u2m1f
si1u
4siut
5s4i1va
si9van
si6vek
si1ve
si8vi.
si1vi
si9våt
1sj
2sj.
s2ja.
8s5ja9g
4sjam
s4jan
4sj5a4n1l
s7j2a2r2n
2sj3av
6s7jaz
2sj1b
6s2jd
5s4je.
sj2e
sj4e4f3i
s2je1f
sje4fla
sj2ef2l2
sje8f5t
sje3g
sj4ek4t5o
sje6k1t
3s2jel
sje4le1v
sje1le
3s2jen.
5s2je1ne
4s3je6n1t
5s4jer.
s2je5s4
5s4jet.
sj2et4ti
sje6tt
2sj1f
2s4jg
s6jim
2s2j1k
2s6j1l
2s6j1m
2s6j1n
2s1job
sj2o
5sjok
4sjom
9sj2on2
sjo6ns7
2sjor
2s1jou
2sj1p
2s4j1r
2s2j3s2
2s6j1t
1s6j3t6sj
sj4ts
sju1a
6s1jub
6s7jug
sju8la
sju2l
4sjun
4sjur
2s7jus
5s2j2ø
sjø3k6
sjø1p
sjø9rø
sjø1s2
sj4ø3t8
6sk.
1ska
2s1ka.
4skab
ska1be3
s2kad
8sk2a1e2
4s6kag
2skak
5s4kal8a
ska5lar
2s1kam
s4ka4m3l
4s5ka1na
4skan1de
ska6nd
4s1ka1ne
4s5ka1no
6s1ka6n1t
5s6kap.
9s8kapa.
ska1pa
4s3kap6as
5skapen.
ska5pe
s4kapen
6s3ka1pit
ska1pi
4s1ka2pp
2sk2ar
s4k5arab
s1ka1ra
sk7ar1be
s1k1a2r1b8
5ska4rd
4s5ka2r1k
6sk2a4rs
4s1ka6r1t
3s8ka2r1v
2s1kas
4ska1te
8s9kay
4s6k1b4
6s2k1d
2s1ke
s4ke5da
sked4
s6ke1do
s2ke5h
s6kei
skei5er.
ske2i7e
s8k5e4le1v
ske1le
s6kel2ik
sk2e1li
ske2l3t
s4kelu
s3ken.
s4k2e1na
s6kena2v
s4k2e1no
s5ke6n1s2
5ske2p1t
s4k2e1p
s5ker.
s4k2ero
s5ke4rs
s2ke1s
ske3si
skes4m
ske5s1n
s4ket8et
sk2e3te
s3k2e4t3j
s6ke1v
s2key1
2s2k1f
2s4k1h
1ski
5s6ki.
7skia
4skid
5sk4ie2
5s2k2if
5s4k2ik
s6ki2l1l
5s4ki2l1t
ski6net
sk4i5ne
ski4nin
ski1ni
5sk8in6n1s5
s2ki4nn
sk2i2no6
5s4kiol4
sk4io4
5s2kip
2s1k2ir
s4ki5re
6s5ki2r1k
s4k5i4rs
s6kis.
7ski4s1s
4s5kist
5s2k6iv
s6kje.
skj2
skj2e
5skje1ma
sk2jem
6s5kjemas
5s6kje2r4m3
3skjer5m4e
7skje4rs
3s6kjor
skj2o
4s5kjæ2r1l
5skjøn
skj2ø
6s1kjøp
skjø5re1s2
skjø1re
2s2k1k4
2s1k2l4
6skla
s2k5lak
s6k5lan
s5k2las
s2k9leg
sk1le
s6klei
sk3lek
sk5li6ng
sk1li
s4k5lit
s4k5luf
s2k5lyd
2s2k3m
2s2k1n2
s3k2nu
4sk2oa
7s4ko6d1d
sk2o1d
4skof
3s2k2og
2s3koi
1skol
7s4k4o1la
3s2k2o1le
4s3ko2l1l
4s1kom
s6k2o1ma
s5k6o1me
6s1k6on
4s1k2o1o
sk5o4rd
s6korpi
sko2r1p
s7k2o4rs
4sk2o6r1t
2s1k2os
s2k2ot
3sko6tt
sk8ra
skr6
s4k9ra.
5s4kral
s4krat
s2k7re.
4s3kre1f
6s4k1reg
5s8krek
4s3kret
5skre2v1n
skre1v
3s2k2ri1b2
3s4kr2if
4s7k6r2ig
3s4kr2ik
3sk2ri2p
4s3kri3te
sk2rit
3s6kriv
5s4kr2og
sk2ro
s3kr2on
4s3k4rop
sk6r2ud
skr4u3s6
6skry4s1s
s3kr2ys
1s2krå
2sk1s2
2s6k3t4
5s2k2ud
1s4kue
sku4e6nd
sku1e2n
sku4er
skue5re
1s6kuf
5skulan
sku1la
5skular
3sku1le
6s5kulis
sku5l4i
4s3ku2l1l
s4ku2l1p
4s1k4u2l1t
sku6m3s1
2s1kun
s6k5unde2r3v
sku6nd
skun1de
skund2er
4s1kup
8s5ku4rs
8s1ku2r1v
2skv2
sk5va.
3skvad
sk3vas
s6k1ven
sk3ver
sk5vit
3sk2vu
1sky
s6ky5a
s6k4y1e
4s1kyr
sky3re
4sk2ys
6s6ky4s1s
5s6kysk
3s2kyt
6skæ
6s1kø.
6s5k6ø1a2
4s1k4ø1e2
1skå
skå5re
6skå2t5
sl4
sl6a8da
s3l6a1de
sla5ge
sla8ge.
sl2a5ke
s5laks
3s4la6k1t
3s2lal
4s3la6nd
slap5pe
sla2pp
s5l2aran
sla1ra
s5la1re
4s5l6ast
s1lat
sla4te.
sla1te
4s1lau
s5laus
s2la1v
3sl2a1ve
sla4vin
2sle.
s1le
s8le1da
7slega1re
sl2e1ga
5s6legas
s4le3gi
s4leg1n
s6le1g2r4
6slei
slei5er.
sle2i7e
s4leiv
8s5le1k6e
s5l2e2kk
s2lel
s4l8e3me
4sle7ne
s6lener
s6le6n6t3
2sler
8s5les
s6le1sj
s4l4e1so
s6lest
s4le1ta
s5le1v
s4leva
4sley1
s5li1a
s1li
2slid
sli4en.
sl2ie
sli1en
6s3l2ig
sli5ke
sl2ik
s4li2kk
2slin
8s3li4n1j
s4li6n6t5
3s2lip
5s4lit.
3s4li1te
4s5liv
slo2b5b
s2lob
slo6tt4
sl2ot
s6lott.
7s4lottet
slot1te
1slu
8slu7a
2slu2e
slu5es
6s1luf
4slug
sl2uk3s6
sl2u8m4p5
sl4um
slum5p6e
s3lu6nd
s5l4u6ns
s6lup
sl4u7sa5
s4lut
s1ly
sly8et.
sl4y5e
slye2t
1s4lyn
4s5l2ys
sly8t
slæ6r2a1ri
s1læ1
slæ5rar
2sløn
s1lø
8s5løp
slø4pa.
slø1pa
s6lør
2sløs
slø8s5a
3s4lø1si
slø5va
slø5ve
slø5vi
5s2løyd
s4lø4y1e7
9s4lå.
s4l4å1e
s1lå2n
slå5ner
slå1ne
1slåt
s1m
sma6d
s1ma
3s2m6ak.
5s4maken
sm2a1ke
sma9let
sm2a1le
4sman
s4med.
s1me
s4me1de
6s5me3di
s4me2d5k2
smeg5
4smei
sme2k7l4
s4me6k5t
8s5me6ld
sm2el
3s4me2l1l
5s4me2l1t
2s5men
3s4me6r1t
6smes
s6me1si
s6mi1a
s1mi
s4m2ie
smi4e1ne
smi1en
s4m2ig
s6mil.
smi7la
s6mi1le
smi1ni6
s2mit
s3m2o7a
s1mo
smo8de.
s5mo1d4e
sm2o1d
s2mok
6sm2ot
3s2mug
s1mu
6s5mu4g1g
smul2
s6mu5la
s4mu6ld
s6mu1le
5s6mu6r1t
1s2m6y4k
s1my
6smøn
s1mø
1s2mør
smø4r3s
4s7m4øt
5s2må.
s1må
s2må5r
s8må6t1t
s1n
8sna.
s1na
s4nab
7s2nak
4s3nas
6s3nat
4snav
4sne.
s1ne
4s2n2e1b2
4sned
s6ne3e2
s4neg
3s2nei
snei5er
sne2i7e
sne4k2ri
s2nek
sn2e1k2r6
s7ne6k5t
s4nel
2snem
4sner
6snes
s2ne9sa
s2ne1s9v2
4snet
s6n2if
s1ni
sni6g1l
sn2ig
s2n2ik
snik5ko
sni2kk
3s2nil
6s3nin
3s2nip
s4n2ir4
5s2n4it
2s3niv
s8no.
s1no
s6n6o1e
s5no4rd
6s7n2ot
5s6n2ud
s1nu
s3num
s4nur
7s2nut
8sny1h
s1ny
2s1næ1
snæ4re.
snæ1re
5s6nø.
s1nø
snø5d4r4
5s2n4ø1e2
1s2nør
snø1s
snø5vi
so3al
s2oa
so8ar
4so1a4v
2s1ob
so2b2l2
1s2o1d
so4da.
so1da
5s6o1e
s2o1fa1
so4fag
so4fas
6s5o2ff
6s5of1re
sofr2
s6o2ft1
so2ga
s2og
so8gi
so7gl
sog6nem
sog1n
sog1n4e
6s2o1h6
5s4oi4
so5id
5s2o2k1n2
4soks
sok4se.
sok1s4e
7s8ol.
so4la.
s4o1la
so2l5av
1s2o6ld
so4le.
s2o1le
solei5er
sole2i7e
so4le1ne
so4l5f6
1s2o7li
2s3o6lj
s2o2lo
so8lo.
so8l3s2
so2l5t4
so4l4um
so1lu
so4lø
3s2om.
so6me6nd
s2o1me
4s3om1fa
so2m1f
4s1o2m5g6
5s8o2m1h
2s1o2m3k2
4so4m3l
1s2o2m5m4
somma1r5a
som5mar
som1ma
3s6om1me
somme6r5e6
2s1o2m1r6
7somren
s2omre
5som1rer
6s3områ
s1o6ms
som5sl4
s4omst
som5s2ti
4som1sy
1s2o2m1t2
5somt.
6som3ta
3s2on
so4na.
so1na
son7da
so6nd
7s6o1ne
so8n2ea
son8g3s4
so6ng
so4n3o
sons4k4
so6ns
son5st
so2nu
so4ny.
so1ny
2s1o2p
so7pak
s2o1pa
so9par
s2o7pet
so1pe
3s2opp.
so2pp
5s4op1pa
3s4op1pe
sop4pi
8sopp1le
sopp1l6
s2o3pr6
1s4or.
7sora
so3ran
5s4o2r1b8
4s1o4rd
sor4da
1s8o1re
so4rek
7s4or5ga.
s1or3g4a
so2r1g
sor4gl
so2ri
4s3or2ie
7s6oris
so5ri1u
4sor1ke
so2r1k
so2r5n
3s2o4r5s
7s8o6r1t
1s2os
4s5osc
so8se.
so1se
6so2s1f
4so1sj
so4s2l4
so5te
s2ot
so8te.
so4tra
so1tr4
so4ts4
sot5te
so6tt
sot4ti
4sou
2sov
so3va
so4ve.
s4o5ven
sove1r5e6
sove2r
sov4e3s4
5so1v4et
3s2ovj
sp2
4sp.
spa5g6h
s1pa
7s6p6ak.
7s6pa1ka
5s4p2a1ke
8spa2kk
s2pal
s3pa2l1l
5spa2l1t
3s4pa4nn
3s2p2a1ri
4sp6as
5s6pase6r5
spa9se
4spe.
s1pe
s2p2e4a
8spe7d6a
spe9dé
s4pe3e6
1s2pei
s2p6el
spe4leg
spe1le
spe4les
5spelet
3s4pe4nn
s4pe6n1t
s5p2e1p
spe4r5a6nd
sp2e1ra
6s3pe1ri
4spe2r1l
s4p2e2r1r
s3pe4rs
3spe7si
s2pe1s
s4pest
s9pet.
1spi
6s5pi1lo
4s3pi6ng
s2p4io
2s3pi2p
spi7res
sp2ir
spi1re
spi7ri
spi7ro
5s6pi4s1s
2s3piz
2s1pl6
8s9pla6n1t
s4p9lar
4sp1le
s4plin
sp2li
3s4plit
s1p6o
2sp6o1e
spo6et
2spol
5s6p2o3le
6s7p2o1li
s4po1ra
s4p8o1r4e
5s4p2o4rs
spor4t6s5
sp2o6r1t
3s2po2r1v
2s5p2os
4sp2ot
s3po3te
s2po5v
9sprag
spr6
5s2pran
6s3preg
5sprei
s4prek
spre5ke
s3p2rem
5s4pren
6s5pres
5s4pret
s6p2ri6n1t
s5pr4io
8s3pr6in7s6i
s3p2r6i6n6s5
8s3pris
8s7p2ro
6s5prob
s3pr2o1d
4s3prof
5s4prog.
spr2og
5s4pro1ge
5s6pro4s1s
s3pr2os
1s6pru
s4pry
3s2prøy
5s4prå
2s3ps
8spub
s1pu
2spul
3s2pyd
5s2pø
6s3pøl
sp5øy
9spå.
5sp6å1d
8s9r
sr4e8ka
sre8ke.
sre1ke
sr2i8e9
sri8k2a
sr2ik
s4r4i8ma
sri4ve.
sri1ve
s5ro
sr6o4e
sr2o6pa
sro6sa
sr2os
sro6se.
sro1se
sr2o2t
sro9te
sro1t5o
srø1v
srå4da.
sr6å1d
srå1da
sråd2e9r
srå1de
srå6de4rs
srå8ma
sr6åm
4s1s
s7sabel
ss6a1be
s7s8ab1l2
s6s2a1e2
s4sa1j
ssa8ke.
s3s4a3ke
s7s8a6la.
ssa1la
ss5a2l1l
ssa4me.
s1s2am
ssa1me
ss5a8m1p
s5s2a5ne
s7s8ar.
ssari8e9n
s3s2a1ri
ssar2ie
s7s6a2r1m
s4s1a2r1r
ssar8ve.
s4sa2r1v
s1s6as
ssa4u8sa
s3saus
ssau6se.
ssa2u1se
s6se3e6t
s1se
sse3e2
s2seg
sse5ge
ss5e4g1g
ss4el
ss2e5li
s3se8l5s
ss9elv.
sse8lv
ssel5v6å
s2sem
s5s2en.
sse8na.
ss2e1na
s6s5enden
sse6nd
ssen1de
s5s2e1ne
s4se1ni
ssen6ke.
sse2nk
ssen1ke
s4s2e1nu
s6s4enå
s5s2er.
s6serab
ss2e3ra
s6serat
sse7sk
s4se1ski
s4se1st
s7s2et.
s9s4e4ts1
s2se1u
s4sey1
s2s1h
ssi4a
s1si
ssi7e6rer
ss2ie
ssie1re
s4si2ff
ss2if
s6silo
ss2il
s8s9i1me
s6s3i6nd
ss5in4it
ssi4ni
s4s3i4n1j
s2s3i4nn
s4s3i6n1s
s5s2is4
ssis5m
ssi6v7e6nd
ssi1ve
ssi1ven
s5s6ja2r1g
s1sj
ss4ka5pe
s1ska
s2s3k2ar
ss1ka8ra
s4s3kof
s7s2k2og
s4skor
ss6ky.
s1sky
ss6k4y1e
s1s4kå
ss5kål
s7skåp
s2sl4
ss1lo
ss5ly
s2s1m
ss6nar
ss1n
ss1na
ss2no
ss4nø
s4s5nød
ss5nøk
s2sof
s4sok6
ss2o5lo
s2som
ss4o1ma
s7so3ra
s4s1or3g4a
sso2r1g
ss3ove
s2sov
ss6pil
ssp2
s1spi
ss4por
ss1p6o
ss9ri
s8s9r
ss2t
s5s6t2ad
ss4ted
ss1te
ss3tek
sstel6li
sste2l1l
s2s5t2e1p
ss7ti2l1p
ss1ti
sst4il
s7s2tip
sst6r4
ss5tren
ss4trå
ss3tus
s2s3t4v
s2sul
s3sur
ssva7ra
ssv2
ss1ve
ssy6na
s1sy
ss2yn
ssy5r
s8s7ø4y1e
s1sø
s6så
6st.
2s1ta.
sta6b2s3
3s6t2ad
st6a2d3a
sta6d3o
s5t6a1fa
3s2ta5fe
s4ta5f2l2
st3a2ft
s1ta7ge
4s8ta1h
st4a6ka.
sta1ka
5sta2kk
4st2ak1l4
6s1tal.
6sta4la
sta5lak
st5a6ld
4s3t2a1le
sta4le.
5s6ta1li
5sta2l1t
s3t4a1me
3sta2m1m4
st6a5na.
sta2na
3s4ta6nd
6s5t6an3de.
stan1de
s1t8a1ne
s4ta4n1f
s4ta6ng
stan8g5s6
s2t3a4n1l
sta8n9o
s6t5antr4
sta6n1t
st3a6n1v
4s2ta3o4
2stap
s7ta1pa
s4ta2pp
s8tapå
s3t2a1ri
1stas
3s8ta1sj
4s5tast
1stat
4stato
sta5t3op
sta3tu
3st2a1ve
s2tav
2s6t5b2
s2t7c
2s2t3d
2s3te.
s1te
2st2ea
2s4t2e1b2
3sted.
s2ted
s4te1da
5ste4d1t
s2te3e4
2s4te1f
3s6teg.
stega6l
s2t2e1ga
s4te1ge
3s2te2g1h
4s5teg1n
s4te1g2r4
4stegsp2
ste8gs1
5s6teg2s1pl6
ste4her
s4te1h
s2t4ei
stei5er
st3e2i7e
4s3tei2k1n2
ste2ik
ste6i6n6s5
3s4tek.
4s4t4e1ka
s6te1ke
ste4ket
s4te1ki
4s5t4e2k1n2
4s2te1k2o
8vs
v1s2t2
v4s5t4ek8st
vs1te
s4te6k1t
s8te5k3v2
4s2t2e3la
s8te5le.
ste1le
s5teleg
s4te5le1v
s5te3l2ig
st2e1li
5st6e2m1d2
s2tem
3s4te2m1m4
5s6te2m1n
4s4t6e1mø
st2e1n5a
ste4nar
5s4t2e3nen
ste1ne
s4tener
4s5te5nes
3s6te6ng
s4te1ni
6ste2nk
4st2e1no
stens5l4
ste6ns
4ste3o6
s5te2ol
2s2t2e1p
step7per
ste2pp
step1pe
st8er.
8s5t2e1ra
s4teram
s6terest
ste1re
s4tere1v
s7t6e2r1f
s4te2rid
ste1ri
ste7ris
s6te4ri6v
4s3te2r1m
6st2e2r5r
ste5run
st2eru
2st4es
s4t2e1se
ste7s1le
stesl4
4stet
s4te1ta
s2t8e7t6r4
s6te1tø
4steve
s2te1v
4stevi
6s3te6v1l2
5ste2v1n
6s2te1å6
2s6t5f
2s2t3g2
stga6ve.
st1ga
stg2a1ve
stgå8v4a
st1gå
stgå1v
stgå8ve.
2s6t5h
st5he
2s3tia
s1ti
4s2ti1b2
s5ti1be
s9tibl2
3stic
6s5tid
s6tidel
sti1de
sti5en
st2i1e2
s2t2if
3s4ti8ft
s4ti3g2en
st2ig
sti1ge
7s6ti1gi
s4t9i8gj
3s2tig1n
s5ti1j
6s5ti1k2a
st2ik
4s5ti5ke
s4tik1l4
3s6til.
st4il
5s4ti1la
sti4lag
4sti6l1d4
3s2ti1le
sti3le9ge
s6ti6l1k
6sti2l1n
s4tilo
6s5ti8l1s
5s4tilt.
sti2l3t4
6s5tilta
5s4til1te
4sti1me
sti7mer
6sti8mé
3s6t4i1mu
s6t3i6nd
s6ti1ni
4s6t1i4nn
s6t3inst
sti6n1s
s6t1i6n1t
1s2tip
4s3ti2pp
6s3ti2ps
5s2t2ir
2stis
6s7tisk
4s3ti6tt
s2tit
4s2ti1å4
s4tja
5s2t2jel
stj2e
4s3tjen
s6t5je6n1t
3s2tjer
4stju
2s4t3k2
2s6t3l6
2s2t1m
2s8t5n2
s2to.
1s2tof
2s3t2og
4stoks
st6ok
6s5to6k1t
3st2o1li
4s2t3o6lj
s5to2l1l
4stom
s9t8o2m1m4
s4t3o6m1s
6s5to2m1t2
4sto1ni
s1t2on
st3o1pe
st5opp1l6
sto2pp
6s8t5o4rd
sto4re4t
st8o1re
s6to2r1m
st4or3o
4st2o4rs
st5o4s
s1t2ot
sto4t5r4
5s6to1re.
st5ou
5sto1va
s2tov
2s6t3p2
8s8tr.
str4
st3ra.
4s5trad
s8tra2ff
6s3tra1f4i
6strail
st3ral
st4ran
str6a8na.
stra1na
5stra6nd
4st5r4a1ne
6s3t6ra6ns
4s5trap
4stra5r4
st7r6as.
st7ra6st.
5s4traum
s4tr2e3a
s4tred
7s6t4re2ik
s4trei
st5r2eis
7s6t6rek.
s4t5re1kl4
6s3t6re5ni
st5rer.
st2rer
6st3re4rs
6st2r1h
9stria
9s8t4rid
5str2ie
st5ri1ge
str2ig
st3rin
3s4t6rip
5stri3s
6s3tr6o1e
s5t4r2o5g
5s4t4rok
st3rol
6s4t5rom
st3r2on
st7r2op.
st7r2o6pa
s6tro1pe
s6t7rug
9s6t4ruk
st5rum.
str4um
2stry
6s3t2ryg
s7try6k1t
str6yk
4s3t6ræ
1s2trø
6s5t6rø1b
s4t6rø6m
4s2t1rør
4strøs
st5rø3se
s5trøst
4strøy
6str6å1d
2s4t5s6
stsa6me.
st1s2am
stsa1me
stsy8na
st1sy
sts2yn
4s6t7t6
stta6le.
st1ta
st3t2a1le
st3t4r4
s4tua
1s2tub
3s4t2ud
s2tue
s5tu4el
stu4er
stue5re
3s4tum
6s3tu6ng
6s5t4u4n5n
3s8tu6n1t
6s3t2ur
2s4tut
2s2t1v
s3t4ve2d1
1sty
2stya
2s1tyd
3s4t6yk
sty6l
2s5tyn
2s7typ
3s4tyr
sty4rs3
6s3tysk
st2y2s
4sty1v
sty5ve
1stø
2s2t1øk
5s4tøl
6s5t4øm5
5s4tøp
6stø2r1k4
st2ør
5s4t4øt
stø7va.
s2tøv
stø5var
stø3ve
stø5vi
st8øy
1stå
5s4t4å1e
stå5k
4su.
1su4a
su7a8l
1su2b
sub7l2
sub3o
5s2u6b1s
s3u4b4å
1sue
su8er
su2f
5s2ug
su6ga.
su1ga
su4ge.
su1ge
su4g3g
su2h
3sui6
su3is5
sui1t5a
su2k
4s1u1ke
3s4u2k5k
suk3r6
1s2uk5s
su4le.
su1le
3s4u4l1f
su4l2ik
su3l4i
sul4t5r4
s4u2l1t
s1ulu
2s1uly
su1læ5
3s2um
4s3umid
s2u1mi
s4u2mo
su6ms6
5s2und.
su6nd
8s5und2er
sun1de
5s4undet
5s4un1di
sun6d7r4
9s4un8ds
sun4ge.
su6ng
sun1ge
6s7u2n6i
su4o
3s2up
su4pe.
su1pe
su5per3
su3pi9
s4up4r6
su8pre
su4r5d4
su4re.
su1re
4s3u4rei
1s2u2r1f
su4r2ie
su1ri
su4ri1u
surs5k
su4rs
sur4sp2
sur4s1ti
sur1s2t
1sus
su3san
s4usa
su4se.
s2u1se
su6s5es
4su1si
su4s2ik
sus3p2
su4s3s
su2sy
2s1u4t1
sut6ra.
sut1r4
su6t8reg
sut3re
7su1v
8s5u6vi
sv2
6sv.
sva5a
svai5
5sv6ak.
3sv2a1ke
6svaks
sva4la
sva4le1s2
sv2a1le
6svalet
5s6va1li
7s6vam
s4var.
s4var4et
sva1re
s3va2r1m
3s4va6r1t
6s1vas
6s1veg
sve6g7i
s5ve2i7e
3s4v2eis
s5v4e1ka
6s5veks
5s4ve6ns
s1ven
sver8d5s4
sve4rd
sve4re
sve8res
3s4v2e2r1g
5s4ver1j
s5ve2r1k
sver4ki
s4ve2r5m6
6s7ve2r1v
7s4ve1v
s4v2ie
svi6e3l
4sv2ig
svi5ke.
sv2ik
svi1ke
6s7vi4k5g2
4s3viks.
3s4vi6k1t
4s5vik1ti
4svil
s6vin5d4e5l
svi6nd
svin1de
svi4nes
sv4i1ne
svin8g5s4
sv6i6ng
ll6svi6n1t
ll2s1v2
4s1v2ir
2svis
svi4sa.
svi1sa
svi4se
svi5sen
svi5ser
s8viv
svi9ve
4s1vol
5s2vor
sv2o6r7t
s3vr8
3s2vul
s1vu
5s6vu2l1m
s3vy4
svy7e7ne
sv4y1e
svye2n
1s2vø
4svæs
s1væ
2s7vå
1sy
sy4c
sy4de.
sy1de
sy5den
5s4y1e
sy2er
sy8ka
s6yk
sy4ker
sy2ke
sy5ke1re
2sy1ko
sy6k2og
s4ym
sy7me
s2yn
4s5yn4d1l4
sy6nd
sy2nk4
syn6sk
sy6ns1
syn3te
sy6n1t
sy4ra
sy5rar
s6yre
sy4re.
2s1y2r1k
syr4ka
syr4ke.
syr1ke
3s2y1s
sy4s4e
sy2sl4
sy2s3t
sys4t3r4
2sy2t
sy5ten
sy1te
sy5t6h
9s4y6tt
sy2vå
sy1v
s4z
sz3c6z
sz1c
6szt.
s4z1t
1sæ
sæ2l
8s3æ4re
8s3æ4ren
sæ4r1i
sær1le9
sæ2r1l
sæ4r5s8
7s6æ5te4
4sæ6tt
1sø
4sø.
sø2d
s1ø1de
4søf
søg4
4sø1j
9s6øk.
3s6ø1ke
5s4økj2
5s2ø2kk
søk7kj2
3s6øk1na
sø2k1n2
2s1øko
søk4sk
søk6s3e6
5s6øks1m
søk4ta
sø6k1t
3s4øl
sø4la.
sø1la
sø4le.
sø1le
sø5let
3s2øm
sø6må
3s4ø4nn
s1ø6n2s
s4øp
sø4ras
sø1ra
sø4re.
sø1re
sø4r5e6nd
søren6de.
søren1de
sø8r6et
sør9e6tt
7sø2r1l
sø4r3s
sør5ø
3s2øs
4søs.
sø8sa
7s4øt
sø7tast
søta4s
sø7tel
sø1te
sø5t4e1s
2søv
s1øve
3sø2v1n
2s5øy.
4s3øya
6s5øyd
søy8de.
søy1de
8sø4y1e
3s2øyl
6s5øy4n
søy2r
2s7ø2y1s
5s2åg
s3å2ke
5s4ål
så4le.
så1le
6s1ån
sån4da
så6nd
sån6de.
sån1de
så2p
9så1pa
så5pet
så1p2e
6s1å2p1n4
så1p9u
så2r
så6ra
sår9a6st.
så4re.
sår3sk
så4r2s
så1r7ø
så2s2
s7å1sa
s4å8s9b4
s5å1se
sås5k
s4å1s5t
9så1v
såv4a7
ta1a
4tab2o
tab2r8
ta6b2s
ta4bu.
ta1b2u
4ta1b4ø
t8a1c
4tad
t6a2da
ta8d3ei
ta1de
ta6d3e4t
tad5s1pa
ta8ds
tads1p2
tad7s6v2
ta6du
t8a1e2
6taei
4taek
4tael
1taen
4tae6nd
4tae4n1h2
ta6es
2ta5fe
ta2f7f6
5tafis
ta1f4i
4taf2l2
1ta1ge
4ta1gj
8ta1h
ta7is
1t6ak.
t4a4ka.
ta1ka
4tak4au
ta7ken
t2a1ke
ta5ker
tak7kel
ta2kk
tak1ke
tak5k4l4
ta8k9la
t2ak1l4
2takr6
tak4sal
tak1sa
tak6se.
tak1se
tak4si
4t3ak1sj
5takst
tak4tal
ta6k1t
4t7akt2ig
tak1ti
tak4to
t5aktø
ta1ku
t2ak3v2
1tal.
ta9lam
ta3lan
tal5a6ng
ta7las
3t2a1le
ta4lei
tal5e2i7e
ta4lek
ta5ler.
tale7s6
1ta2lg
tal6ge.
tal1ge
7ta2lj
tal4j5e1s
talj2e
tal4led
ta2l1l
tal1le
tal4leg
8t7all2e2r1g
tall7e3s4
tal8lig1n
tal9l2ig
tal1li
tall6s9a
tal8l1s
tal4ly
ta2l3op
6talter
ta2l1t
tal1te
t5alt2e2rn
ta6l9u
ta6lå
2ta2m1b
tamba6ne.
tam1b4a
tamba1ne
t3am1b4i
t4a1me
4tamet
t2a1m4i
2ta3m4o
t6an.
ta2na
4t3anal
t4a3nar
ta5nas
ta5nat
t5andak
ta6nd
tan1da
5t6an3de.
tan1de
t7and4el.
tan5d4e5l
t5ande1le
tand5r4
tan1dø4
tandø1r5e
tand2ør
1ta1ne
ta4nel
ta5nem
4tane6tt
t3an1fa
ta4n1f
tan1fø6
6tan2g1f
ta6ng
6t3an1gr4
1ta2nk
2ta4n1l
t3an5le
2t1a4n3m
3tan4n1l
ta4nn
t5an1no
tan6ns4
tan6sk
ta6ns
tan1s5ka
t5an5sl4
4t5anstr4
tan1st6
4t1an1sv2
8tant4il
ta6n1t
tan1ti
tan4tra
tantr4
6t7antre
t3anve
ta6n1v
2ta3o4
5tap.
ta4pa.
ta1pa
1ta1pe
ta4pe.
3ta1pi
4tapl6
4tap1pa
ta2pp
5tap1pi
1t4ar.
t6a1ra
ta4r3ak
4taram
ta6ra1re
t2arar
2t1a2r1b8
3t2arb2o
5t2ar5b4ø
4t9a2r2e3na
ta1re
6t7ark.
ta2r1k
tar8ka.
4t5ar3ke
4t3ar3ki
4t1arra
ta2r1r
tar9si
t2a4rs
tar7sp2
tar1s6v2
4ta6r1t
tart7est
tar3te
tart4es
t5art2ik
tar1ti
tar7tit
t6ar4t3r4
ta6r5å8k
ta1rå
4t4a1sa
ta3se
6t9a2s1f
t3a4sia
ta1si
tas2i3e
ta4s2if
ta4s2il
ta4s2i5s4
tas4p2
ta4s5s4
6ta7sto
ta7str4
4ta1su
t4a1ta1
ta7t4es
ta1te
tate8se.
tat2e1se
ta2t5e4v
t4a1ti1
tat3op
t2a4t2s1
tat3te
ta6tt
t3at1tr4
1tau
9t4au.
7t4a6ue
4t5aug
2t1a4uk
4taun
tau4ne.
tau1ne
tau4sk
2taut
2tav
6ta1vin
3ta6v1l2
4tav1li
ta9xy2
6t1b2
tba3d
t1b4a
tba2n
t6be.
t1be
tbe6te.
t3b4et
tb2e3te
tb2e6t7r4
tbe6t8ra
tbo6da
tb2o
tb2o1d
tbu8da
t1b2u
tb2ud
tb6y3k
t1by
tb2y4s
tby7te
2tc
t1ce
tcen4
tch5e
t5co
2t3d
tdy5p8e
t1dy
1te
4te1ad
t2ea
4te3ag
2te1a2k
2te9a8l
tea6m1
2te1a2n
2te1ap
2te1ar
te5a2r1b8
tea4s
3teatr4
4te1a6tt
2te1au
2te1av
4t2e1b2
t5e2b1b
tebu8da
te1b2u
teb2ud
4te1cel
t2ec
te1ce
te1co
2ted
6t5ed1di
te6d1d
3te3de.
te1de
4ted2e7b2
te7de1f
4tedek
4tedel
4tedem
te5den
4te1di
5tedil
4te1do
tedo6en.
ted6o1e
tedoe2n
tedo4er
tedo8et.
te4dor
ted4sk
te8ds
ted4s1l4
6te1du
4te1dy
tedy2r9k
4te1dø
te3e4
2te1f
8t4e1fa
4t4e1fi
4t2ef2l2
4te1fo
tefo8r
1t3ef1te
te8ft
3t4e1fø
2t2e1ga
5teg2at
4t3e8g1d
te7g2e1a
te1ge
te9ge3e2
4te4g1g
teg8ga.
teg1ga
2te2g1h
te4gim
te1gi
2te1gj
2tegl
te3gl2a
te9g8li
8tegs.
te8gs1
6teg2s1pl6
tegsp2
6tegsv2
2te1gu
tegvi8
te2g1v
2t4e1gå
5tegå2s2
4te1h
5tei.
2tei4d
t3e2i7e
t3ei3ga
te2ig
tei6ga.
t7ei6ge.
tei1g6e
6tei1gr4
3tei2k1n2
te2ik
tei5le
t4eil
t4e1im
6tei4n9f
te4in1ne
t4e1i4nn
1te5in1te
tei6n1t
t2e5i4s
6teiso
2te1j
te7ken
te1ke
te5ker
4tekil
te1ki
2t4e1kj2
5t2e2k1k
2te1kl4
te5k4la
tek6le3de.
tek1le
tekle1de
tek4li
4te3k2nu
t4e2k1n2
2te1k2o
2t2e1kr6
te7k2ra
te6k5ru
9teks.
4t3ek1sa
t3ek1se
4t1eksp2
t4ek8st
tek8t9r4
te6k1t
tek4t4s
tek6ty
2te1ku
te2k3v2
4te5ky
2te1kø
4te1kå
2t2e3la
te2l6ak
5te4l5ar
3te2lav
8te4le.
te1le
4t4eled
4telei
4telek
4tel8e3me
te2lem
te4le1ne
t4elen
6t2e3ler
te4les
te5le3sk
4te4le1v
te6leva
4telid
t2e1li
4tel2ik
4telil
4telin
te4l2ir
4telis
4telit
4teliv
tel5le1f
te2l1l
tel1le
tel4lei
tel4le1v
tel8lig1n
tel9l2ig
tel1li
tel4lo
2t2e1lo
tel3se
te8l1s
tel3se6s5
t3elsk
tel7s6v2
5teltet
te2l1t
tel1te
2te1lu
7telut
2t4e1ly
6t2e1læ1
2t2e1lø
telø8pa.
telø1pa
2t2e1lå
2tem
3tem2a1e2
te1ma
6teman
te4ma6ns
t5em1b4a
te2m1b
6tem6e4nn
t8e1me
5tem2ik
te1mi
tem9ma
te2m1m4
6te2m1n
tem5ne.
tem1ne
tem5o4rd
te1mo
tem5pe
te8m1p
temp6e8l7
3tem1po3
te6m3s
4t6e1mø
3ten.
te5nab
t2e1na
te4n5al
ten8am
te4nan
te4nat
ten5at.
6t7en1c
6t5en6den
te6nd
ten1de
4t1endr4
4tened
te1ne
te5neg
6t4enem
6t3e4n2e2r2g
5tenes
4tenet
2te6ng
7teng2on
ten1go
t5e4n2ig
te1ni
7ten2ik
5tenis
4teniv
ten9n2o
te4nn
te4nom
t2e1no
te5nor
6ten4o2r1m
4ten2ot
te6no1v
6te7nó
ten3sa
te6ns
ten5sko
ten6slu
tensl4
ten3so
tens5v2
ten4t5in
ten3ti
te6n1t
tentle8ge.
ten6t5l6
tent1le
tent3le1ge
4tenum
t2e1nu
4te1ny
4t4e7næ1
2te1nø
te4n5øks
tenø4k
2teom
te3o6
5te2on
4teo2p
te6ora
4teo4rd
2teov
2t2e1p
te3pa
4tepak
tepa9ra
4tep6as
4te5pe
7tepe3e6
9tepé
tep6pe1re
te2pp
tep1pe
t5e6p1le
tep2l6
4tepr6
4ter8ac
t2e1ra
te7r2a2kk
te2r1ak
6tera2m1m4
te4r7a1pa
4tera2pp
te4ra1re
6te4r4a1sa
4tera1se
7ter6ast
terba6ne.
t4e2r1b8
ter1b4a
terba1ne
t6e4r5d
4tered
te1re
4tere1f
4tereg
4ter2eis
te4rel
8te9r4em.
te2rem
6te5re2m1m4
te4r5e6ng
teren6g6a
6tere4nn
4ter2e1p
te4re2r1k
te1rer
te4r5e3s1ti
4t6ere6tt
tere4t
6terevo
tere1v
3t2e2r5g
3t2e2r1h
te4r2ig
te1ri
4ter2ik
4teri1si
te4riv
ter5j
4terk.
te2r1k
4t8er1ke
4ter4k1h
7t4erkr6
6ter8ks4
4ter6k1t
terl4a6ga
te2r1l
ter3le7ge
ter1le
terle6ve.
terle1v
ter8ma.
te2r1m
ter1ma
ter3no
t2e2rn
ter6n5s
te5ro.
t2ero
te1r6o6e
te3rof
4tero2l1l
te1rol
4te1rom
5te2r1o2m1r6
4te3r2os
4te3r2ot
ter1o6v
t2e2r5r
5te6rs.
te4rs
5tersjø1o
t6er1sj
ter5s2j2ø
ter6s4k2l4
ter1sk
ter7s6ko
ter1s4l4
ters4h
ter3s4m
ter5s6ne
ters1n
t4er5sv2
tersø6ke.
ter1sø
ter3s6ø1ke
3te6r3t
ter9to
ter9ul
t2eru
8te3r4um
te2r7v
tervi6se
t6ervi
2te1ry4
te1r5ør
t2e1rø
4ter4øt
te4r3øv
4ter6å1d
t2e1rå
te4r3å1s2
t4es
5t2es.
2te1sa
5tesar
te7s2en
t2e1se
2te1s1h
4tesid
te1si
4te3s2ik
6te3sin
4te3s2it
4te1sj
6tes2je1f
tesj2e
4te1sk
t9e2s6ka.
te1ska
6te1ski
te5sko
2te1s2m
7tesm4å1e
tes1må
2te1s1n
2t4e3so
6te1s2p2
5te8s9r
3te4s1s
5te6st.
4testaf
te1s5tas
4te3stat
5te6s7te.
1tes1te
4te5s2t4ei
te5s6tiv
tes1ti
5te6s5tid
te3s4t2ik
4te5s4t4il
4te3sto
4te3str4
4te1sty
4te1stø
2te1su
4te1sv2
tes8væ
2t2e3sy
2te3sø
te7så
te8så2r
teså9re
3tet.
teta6ka
te1ta
te3tak
teta6le.
te3tal
te3t2a5le
te4t4ap
1t2e3te
5tete.
tete4e4
4teteg
4tetei
4tetek
4te2tem
2t2e1ti
3tet2ik
3tetis
2t2e1tj
2t2e1to
2t8e1t2r4
3te4t2s1
t2et5ti
te6tt
tet6t3s
2t2e1tu
5tetu.
2t2e3t2v
2t2e1ty
te2t3å
2te1u
teu8k
t1e2ur
3te2us
2te1v
t6eva
te4va1lu
3te6v1l2
tevo6r
te3vr8
2tey6
2te1ø4
2te1å6
4té1a
2té1b
2té1f
2té1h
2té1i
4té1le
té1l
2té1m
2té1se2
té1s
4tést
6t1f
tfe6e2
t1fe
tfe4l
t1flå3
tf2l2
t7fo
2t3g2
t4ga.
t1ga
t2g2e4a
t1ge
t2gei5
t4gi.
t1gi
tgi5r2o1s
tgiro3
tg2ir
tg2re2i9e
t1gr4
t4gå.
t1gå
2t1h
t4hap
th2a
t4h2e2a3
t3h4ei5m
the2i
th2o7li
th4o
th8o3r2e1
3t2hr
thu5le
thu6s
thy5r
th6y
1ti
2tiad
ti3a1g2
2ti1ak
4ti3a2l1l
ti5a8l1s
2tia2m
2ti1ap
ti3a4sp2
4tiau
2ti1av
2ti1b2
2ti2c6k1
3tid
t2i2da
4tidan
ti6d7d
6t3i2de3e2
ti1de
4ti5dem
4t7idé
4tidis
ti1di
4ti1do
4tidr4
2ti1du
tidvi4
ti2d1v
6ti2dy
4ti3dø
t2i1e2
2tie1f
2tie1i
2tiem
4tie4n1h2
ti1en
ti2e5ra
4tie2r1f
4tie2r1k
ti4e6r5t
ti2e1s3
ti3esk
ti3et
4ti2e1ta
4tie6tt
2ti1fa
t2if
tifa8ne.
ti5fa1ne
2ti1fe
4tif2l2
2ti1fo
2ti5f6r2
t8ifrå
4ti8ft
tif5te
2ti1fu
4ti3fø
ti9ge.
t2ig
ti1ge
4ti1gj
2tig1n
3tig5no
ti6g1un
ti1gu
2ti3h
2ti1i
2ti1j
ti3ka.
t2ik
ti1k2a
6tika2r1r
ti5ke
4tiket
4ti5ki
6ti1kj2
tik4kj2
ti2kk
2ti3ko
2ti1k2r6
ti3ku
6tikular
tiku1la
6ti1k4u2l1t
6ti1ky
4ti3kø
t4il
til4a6ga
ti1la
ti6l1d4
3til1de
2ti1le
ti3l1ei
3til5fe
ti4l1f
3ti2lg
ti4l3id
ti1li
4til1k4e3
ti6l1k
3til2k1n2
4tille.
ti2l1l
til1le
4til8l1h
4tillin
til1li
til1lø7
4ti2l1m
7til1næ1
ti2l1n
3tils2t
ti8l1s
ti2l3t4
4til1te
2tilu
2ti1lø
ti7mab
t4i1ma
5tim6at
ti4me.
ti1me
5timed2ie
time3di
ti4m2e1ra
5tim4e1ri
2ti4m3l
4ti1mo7
4tim6o8r
2t1i8m1p2
ti6m7s
2t4i1mu
2ti1my
2ti3mø2
ti6nab
t6i1na
ti2n5a6r1t
4tinaz
2ti6nd
t3in1du
ti4nem
t4i1ne
4t1i4n1f
tin5g6e4s
ti6ng
tin1ge
6ti4n4it
ti1ni
6t5i4n1j
2t2i2nk
7tink1s2
4t1i4nn
6t5in1ne
t2i5no
t4insk5
ti6n1s
4t1insp2
4t3inst
4t1i6n1t
2t1i6n1v
4tinva
4ti1ny
9tiol4
t4io
6ti7om
tio6n9s6
ti2on
4tiop
ti1or
ti1ov
ti3pa
ti6pla
ti1pl6
4ti1pr6
3ti2ps
2t2ir
tira4t
ti1ra
ti4re1f
ti1re
ti1ro
6tisak
ti1sa
4ti1s2am
ti5s6an
ti3se
4ti2s4ei
4ti2sek
4tis2el
4ti2sem
4ti2s2e1p
tise8ra.
tis2e3ra
4ti5se4rs
6ti3s2ig
ti1si
4tis4io
4tisj2e
ti1sj
4tisju
ti1s4ka
4tiskan
4tiskil
ti1ski
tis5kok
6tis1ku
tis5l4
4tis1n
4tiso
6ti5s4prå
tis1p2
tispr6
4ti1stat
tis4ti.
1tis1ti
tis4t4il
ti5stre
ti2str4
4ti1sty
4ti1stø
ti8st8øy
2ti1sy
2tit
ti7ta
ti3te
ti1tj
3ti6t3l6
ti1t4r4
ti4t3s
3ti6tt
4tiu2b
ti1u
2tiut
ti4v5a6nd
t4i1va
tiva9re
ti4v5a6r1t
tiv6is
ti1vi
2ti1vo
tiv5si
ti8vs
tiv5sk
4ti5y
ti5ær.
ti7æ1re
ti9ært.
tiæ6r1t
ti1ø8
2ti1å4
6t3jag
2t1jak
t5j2a2r2n
2t1je.
tj2e
2t1jeg
2t2jel
1tjen
tjen6st
tje6ns
6t2j2e1p
2tjer
t5je1v
2t1ji
5t6jingan
tji6ng
tjin1ga
5t6jingar
2tj2o
t5jo4rd
2t1jub
tju4e
2tjun
6tjur
3t8ju1v
tjæ4res
tjæ1re
2tj2ø
4t2jå
4t3k2
t6kag
tka8ra.
t1ka1ra
tkly9
tkl4
tku6le.
tku1le
tkå8pa
6t3l6
tl4a4ga
tla8te.
tla1te
tle6da.
t1le
t2le1da
t4le1dr4
t6le1f
tlei5er.
tle2i7e
tle8se.
tl2e1se
t2l2e5ti
tli6g1h6
t1li
t3l2ig
tlig4ht5
tli6nes
tl4i1ne
tli4te.
tli1te
tlu4e
t6l7ut
tlø4pa.
t1lø
tlø1pa
t6l7øy.
tlå3ne
tlå2n
t2lå7r
2t1m
tma3d
t1ma
tma8ge.
tma1ge
t6ma1ku
t2mam
t2meg4
t1me
tme3in
t2mek
tmi1ni6
t1mi
tmi6n5s6
t2m2os
t1mo
tmå4la.
t1må
tm6ål
tmå1la
8t1n
t3na
t6ne3e2
t1ne
t4n2e1li
t7ne3l2ig
t4ne4rek
tne1re
tn2e4r5ø
tne8se.
t4n2e1se
t4nesk
t4nest
t5ni
t8no.
t1no
tn5sk
t6ns
tnæ6re
t1næ1
7to3a6n
t2oa
7toar.
toa6t
6to1au
2to1a4v
tob4e6r
to1b4e
t3o2b2l2
6to6b1s
4t4oc
to5da
t2o1d
to9de.
to1d4e
to4d2e1ra
tod2er
3to1do
1t6o1e
2toek
6toe2l
toe4t
2tof
6toff.
to2ff
t3of1re
tofr2
t6o5fri
to2g3at
t2og
to1ga
to7g2en
to1ge
4to4g1g
to6gl
6tog2rav
tog6ra
to1gr4
to4gre
tog3st
to8gs1
to2gu
2t2o1h6
toil3
2to1in
4t4o1j
t6ok
1to3ke
to3ki
2to1kj2
2to2kk
tokk5e6nd
t4ok1ke
t4o3kl4
5tok5s4i
5to6k1t
2to1ku
to4l5a2r1m
t4o1la
5tola5t
to5lel
t2o1le
to2le1le7
5tol2e1ra
toli8ne.
t2o1li
tol4i5ne
2t3o6lj
1to6l3k
7tolki
tol4la1b
to2l1l
tol4lag
tol6leg
tol1le
tol6le6tt
3t2o1lo
tol5t2r4
to2l1t
4to1lå
to9ma.
t2o1ma
2to2m1b6
to5mene.
t2o1me
tome1ne
2to2m1f
4to2m5g6
to3mi
to5mi3se
to7mist
4to2m3k2
tomlø8pa
to4m3l
tom1lø
t8o2m1m4
2t1o2m1r6
4to6m1s
5t6oms.
1t2on
to5na
to4ned
to1ne
to4nel
ton8g5s4
to6ng
to8ni1b2
to1ni
2to1no
ton3sa
to6ns
ton5s4l4
ton5sp2
2to1nu
to4ny.
to1ny
2t2o3o
4t2o1pl6
4top2p1d4
to2pp
6t1op2p1g2
6t5opp1le
topp1l6
4t5opp1læ1
top4po
4top2p7t2
4t2o1pr6
to1ra
to4r1ak
tor5a2l1t
t2oral
to4r5a6ng
to4r5a6n1t
to4r3as
4to4rd
tor8da.
tor1da
t8o1re
6toreg
to4rek
tor7eks
to7rem
6tore6n1t
5to1rer
tore4t
tor5e1te
4t1or3g4a
to2r1g
to2r5i6n1t
to1ri
tor7me
to2r1m
tor6m5s
tor5na
to2rn
tor3s4i
t2o4rs
t2o6r5t8
torvei5
to2r1v
t2o4rø
4torå
t2o1s
4to1sa
4to3se
to2s1h
6to1sj
to2s5ke
to8sku
4tos2p2
2to1sy
4to1ta
t2ot
to5te
to8ti.
to1ti
to5to.
to1to
to4tor
6to1ty
2tov
to1va
to7veg
to5ve4rd
tove2r
tove7re6
t5ove4rs
5tow1
4to1ø
6t3p2
t7pa
tp4i7ne
tpi7pi
t3pi2p
tpo4et
t1po
tp6o1e
t4på1k2
tr4
8tr.
6t9rabat
tra1b4a
3tra1f4i
tr4a8ka.
tra1ka
trak7to
tra6k1t
t5r4a1ne
3t6ra6ns
tran7s1ke
5trap
tra3pe
t4ra5po
tra5r4
5tra1se
tra5se.
tra5se5s
5tra1sé
t2rat
t5ra4te.
tr6a1te
3t2rav
tre4al.
tr2ea
tr2e1c
4t3re8ds
t3r6e1fu
tre1f
6t1reg
t5re1gi
4trei
tre5i6n1t
6t6rek.
4tre1ke
4tre1kl4
4t3rekor
tre7k2o
4tr2e9la
3t6r2e7na
tre5ne
t6re6ng
3t6re5ni
4t3re4nn
tren6sk
tre6ns
t7re1pe
tr2e1p
t5re1pres
trepr6
t2rer
t4r2ero
t4r2e3ru
2tres
6tre1si
tre5sko
t2resk
t6re1s4l4
3tres1n
t5re1su
t1ret
t4re1ta
t6r2e1to
t5ret7ted
tre6tt
tret1te
5t6rettel
4tre1v
tre9va
t6ri1b2
4t1ric
4t4rid
tri7e1i
tr2ie
t5rig.
tr2ig
t4ri2kk
tr2ik
t8r6i1na
t4r4i5ne
4t5ri6ng
3t4ri4nn
7t6r4io
t4rist
tri5t6o
t4ri1u
t2riv
t2ri7vi
5trix
3t4ro.
5tr2oa
tro9ar
3tr6o1e
t4rof
tr2o5g
3t2roi
tro3in
t4rok
t2rol
4t3r2om.
t6ro6nd
tr2on
5t4ro6ng
tro5per
tro1pe
t2r2os
5t6rosk
t4ros3l4
tro1v
t8ru.
t8rua
7trua.
5trued
5tru1et
3trug
tru1i
5tru2k1n2
t4ruk
t3r2u2l1l
2t1run9
t3ru6nd
4tru1pe
tru5sa.
tr4us
tr4usa
7t6rusk
2trut
t5r4uta
t3ru1t8e
t3r6u3ti
tru5v
try3dr4
3t2ryg
tryg5ge
try4g1g
7trykk.
tr6yk
try2kk
5tryk1ke
try7pe.
try1p8e
t6ræ
tr6ø8a2
t5rød.
t5rø4d1t
4trøk
4trø6m
2t1rør
5tr4øt
5t4røya
5trø4y1e5
trøy9e2t
4t3røyr
6trø2y1s
5trøyt
trå8da.
tr6å1d
trå1da
4t3rådet
trå1de
trå4dr4
t6råkl4
t3r4å1s4t
trå1s2
4ts
6ts.
t5sa.
t5sab
tsa2g
tsak6se.
tsak1se
ts1an
ts4a6ng
tsau6r
t1sc
t6sch2a
t6sch5k
t5se.
t1se
tse6d
tse4er1
tse3e2
ts5e2ge
t2sei4d
t2s3e2ig
ts2em
t3s2en.
ts4en1de
tse6nd
t5sen1di
tsen8ka
tse2nk
tsen6ke.
tsen1ke
t5s2e6ns
t4sentu
t7s6e6n1t
t5s2er.
t3s2e1ri
t4s3e2r1k
t2s2e1r3o
t5s6e6rs.
tse4rs
ts4es
t4sesk
ts5e6st
t2s3e2t6a
tse4te.
ts2e1te
t2s1e4v
t2s3f
t3s4fæ1
t7s8hop
ts1h
tsh4o
ts7i2l1l
t1si
ts2il
t2s3i4nn
t4s3i6n1s
t5s2ir
ts5je3g
t1sj
tsj2e
t1sje5t6sj
4tsje4ts2
t2s5jor
tsj2o
5t6sjov.
t7s4ju2k
t4sjur
t5s2kad
t1ska
t4skan
ts6kis
t1ski
t4s5kjed
tskj2
tskj2e
ts6kj2ø
t5skol
t6s3ko2l1l
t7s2k2ot
ts5kren
tskr6
tsk5ru
ts5krø
t2s1k6v2
ts9kvi
ts6ky.
t1sky
t6s7kyn
ts1l4
t7s6lo.
t3s4l2ot
t1s2lu
ts5lø
t5s2mit
ts1m
ts1mi
tsmå6la.
ts1må
tsm6ål
tsmå1la
ts1o
t4s5o4d
t5sok
t5s4omst
ts1o6ms
t5s2pek
tsp2
ts1pe
t3s2p6el
t1s2pi
t2s3pi1ke
tsp2ik
t3s4por
ts1p6o
t4s5s4
t1st4
t2s3ta2b1b
t5s6t2ad
ts3tak
t4s3tal
t5s4ta6nd
t5s4ta6ng
t5star
t5stat
ts6tau
t3s2ted
ts1te
t4steg
t4stek
ts6t2ig
ts1ti
ts4t2ik
t4s6ti6l1k
tst4il
t4stin
t4s2tit
t2s1tj
ts5t2on
t4s5t2o4rs
ts5t2re3e2
tstr4
t4s5tren
t4s5tro
t2s3try
ts6t4rål
t5stu
t2s3t1v
t4su4a
tsu7ge
t5s2ug
tsu4l
ts3u3l4i
ts3u4r
t4s5u1si
t1sus
ts1v2
t3sv2ik
ts1w
t5s6yk
t1sy
t2s5y2t
ts6ø8ka
t1sø
ts5øk1ni
tsø2k1n2
t2s5øv
tsøy8er
t8sø4y1e
ts1å
tsåt8
6tt
t1ta
t3ta.
t2tab
ttaba6ne.
tta1b4a
ttaba1ne
t4t9ab2o
tt5adr4
t4tad
t2taf
tta9fr2
tt3a2ft
tt8a4la.
t6t3a6ld
tta7lel
t3t2a1le
tta2l1l4
t4t3a2na
t5t6a1ne
t4t3a6ng
t4ta6ns
t4ta6n1v
tt5ap1pe
tta2pp
t7t8ar.
tta1re6
t4ta2r1r
t4t5a6r5t
tt6arta
t4ta3se
t4ta1sj
t4tav
tt3avi
tt5a6v3k6
tt5a2v1r8
t3te.
t1te
t2t2ea
t3ted
t4te1da
t5te3de.
tte1de
t8te1dr4
t2te3e4
t4te1f
tt3e2ff
t2teg
t2tei
t2t7ei4d
tte3in
t2tek
tte7k3v2
t4t7e4le1v
tte1le
tteli6te.
t4telit
tt2e1li
tteli1te
t4tem
tte4ma.
tte1ma
t6t5e2m1n
ttem8na
ttem6ne.
ttem1ne
t3ten
t5ten.
t4t2e1na
t5tene.
tte1ne
t4te1ni
tte6n5s
t9tens.
t4t5en1se
t2te3o6
t5ter.
t4terei
tte1re
tter5un
tt2eru
tt2e4r5ø
t4t2e1rå
t4t2e1se
tt4es
t4te1si
ttes6ke.
t4te1sk
tte2s1ke
t4tesl4
t5tet.
t4te1ta
t4t2e3te
t1té
t4t1h
tt5he
tti5a
t1ti
t4tidel
t3tid
tti1de
tt2i3e2
t2t2if
t5t2ig
t4ti4g5j
t2t3i6nd
t5t4i1ne
t7t8i1né
t5ti6ng
t4ti1ni
t4t3i6n1s
t4ti1sj
t6tis1ti
t7ti3te
t2tit
ttle6de.
t6t3l6
tt1le
ttle1de
ttlin5
tt1li
t1to.
t5tofr2
t2tof
tt5om.
t7t1o2m1r6
t2t2on
tton6na
tto4nn
tt3o2pp
t5to1ra
t4t3o4rd
tt8o5re
tt5o4ri
tt3o2r1m
tto5u
ttpar4
t6t3p2
tt7pa
tt6pås2
tt9ra1ka
ttr4
tt5r2a1ke
tt3ram
t5t3rap
tt7rat
t4t3r2e1p
t2t3res
t4t3re1v
tt5rom
t5trop
t5try
t2t1rø
t6ts
tt7saf
tts3ar
tt4s5kan
tt1ska
tts5kj2
ttsl4a6ga
tts1l4
tt5sok
tts1o
tt5sti2l1l
tt1st4
tts1ti
ttst4il
tts5top
tts5tra
ttstr4
tts5t8øy
tt1stø
tt3u4gl
t1tum
tt1un
ttun6ge.
ttu6ng
ttun1ge
tt1ut
tt8u1te
ttva8la
t2t1v
ttv4a
tt5ve
tt6vun
tt1vu
t5tw
t5ty.
tt4y8e
t2t1y6t
t1tæ6
t4t3ø4l
t1t2ør
t2t3øv
tt7øy6r
tt4øy
tt1å4
5tual
4tu4av
tu1b4a3
tu4be.
tu1be
tu6b4å
tu6dem
t2ud
tu1de
5tu1el
tu4er.
tu1er
tug8l2a
tu4gl
tu1in
tu2k
t5u1k2a
t1u1ke
7tu6k1t
3tu1la
1tu1le
tul5l6e3s4
tu2l1l
tul1le
t4u2m5m4
tu6m4s1
3t2un.
4tu6nd
3tu1ne
tun5gesv2
tu6ng
tun2g6es
tun1ge
6t1un8g1d4
tun8g9s1
6t5u1n6i
tu4n4io
5t4u4n5n
9t4u6ns
3t2ur
tur7a6n1t
tu1ra
tu4ras
4tu4rei
tu1re
tu4rek
tu1ri4
tu4rin
tur1o
tu2r3p
tu4r5s4
tu4r3uk6
tu5r4us
tur1ø2
1tus.
t4u1sa
3tu8s9b4
3tu2s1f
tu4sin
tu1si
tu4s1m
tus5o
tus7s6t
tu4s1s
tust6r4
tus5u
2t1u2t3g2
t5ut1j
t5u4t3k2
t1ut1r4
tut6te2nk
tu6tt
tut3ten
tut1te
tut5tor
tut2t5ov
tu8va.
tu1v
2t1v
tv4a
tva8k
tva6ne.
tv4a1ne
3t2va6ng
t4v5a4n1l
8t9var
tve5del
tve2d1
tve1de
3t6veit
4t7ver
tve6r1t5
t2v4et
3t1vet.
7t1ve4ts1
t5v2ik
tvi8ka.
tvi1k2a
t8vi3k2las
tvik1l4
5t4vil.
tvi5l1a
5t4vi1le
t4vi5l4i
t6v6i6ng
tvi4sa.
tvi1sa
tvi4se.
tvi1se
6t7vo
3t2vu6ng
t1vu
6tw2a2
1tyd
tyg9gel
ty4g1g
tyg1ge
tyg5g6es4
6ty1h
tykk7s6
t6yk
ty2kk
ty4med
ty1me
1tyn
ty4ne.
ty1ne
6t5yo
1typ
5t6y1p8e
ty4pe1re
ty4pet
ty4rak
ty1ra
5tyr4a4nn
6tyreg
ty4r2e1p
tyre5st
ty4ri.
ty1ri
tyr2i9e
ty4ri5s
t2y2s
3tysk
tys3t
2ty2t
ty8ta
t3ytel
ty1te
ty5ten
ty4ve.
ty1v
2tz
t5za
5t6zel
t1ze
6t6z5l
1tæ
tæ5l
tær6e1ne
tæ1re
tæ4re4n1h2
tæ1r4er
tær4et
tæ4r5s4
tøf3l2
2t1øk
tø9ket
tø1ke
tøk8ta
tø6k1t
4tøl
t7ø4let
tø1le
5t4øm5
tøn5nes
t4ø4nn
tøn1ne
4tøp
tøp5se
tø2p6s1
t2ør
tø4r3as
tø1ra
tør3in
tø1ri
tø2r1k4
7tørk.
9tørka
5tør1ke
tør5n4e
tø2rn
tør2s7ta.
tør2s2t
tø4rs
tørs7ter
tørs1te
tørs5tin
tørs1ti
tø6r3t
tøs4
tø8sa
4t4øt
tøt5a
tø5ta.
tø3te
tø6tt6
2tøv
tø6v6d
tø7ve1le
t4øy
tøy5a
t9øy2em
tø4y1e
tøy5r
tøy5te
tå4en.
t4å1e
tåe2n
tå2k
tå5ket.
tå1ke
tå5ki
tåk6r6
tå2l3a
tå9la.
tå4leg
tå1le
tå7lel
tå8l5s4
tån8da
tå6nd
tån6de.
tån1de
tå3ne
4tåp
tå6pe.
tå1p2e
t1å2p1n4
tå6re.
tå4r2e1p
t3året
4t5åri
3t8å2rn
2t1å4r2s1
tå3s2
tå2t
t3åtak
tå1ta
tå7ten
tå1te
ua7g
u1ak
uak3s
u1al.
ua7la
ua2l5l
ual3o
ual3u
u3a4ne.
ua1ne
u5a4n1l
u3a6ns
u1ar.
ua4r5d
u4a2r1r
u5a6rs.
u2a4rs
uas2h3
ua3t
u5au8
u1av
1ua2v1h
u4ba.
u1b4a
ub7a2l5t
ub7a6n1t
ub5a2r1k
ub4b2o
u2b1b
ub2br8
ub4b2u
ube8l5s
u1be
u2b5h
ubi6s
u1b4i
ubi1s7t
ub1j
ub7lan3de.
ubl2
ubla6nd
ublan1de
uble3s4
ub1le
u1b4r8
ub5rin
2u6b1s
ubu8e
u1b2u
u4b4å
uch5en
uch5er
u1ci
u2c6k1
u1cu5
2ud
u3da.
u1da
u7d6as.
ud4dag4
u6d1d
ud1da
ud2dr4
udd4s5e
ud8ds
ud2då
u2d2ea9
u1de
ude8a2re
ude1ar
u2d2e7b2
ude2i7e5
u2dei
udei5er
u2dek
ude3lu
u2d2e1p
u6d2ero
ud2er
u2de1s
u3d2et
u6d5e6tt
u5devo
u2de1v
udia2
u1di
udi3an
udi4en
ud2ie
ud4io5
ud7ir
ud7ji
ud5leg
u4d1l4
ud1le
udle6ge.
ud3le3ge
u7do2b3
u1do
u6d6o1e
u2d2ot
udo4ve2r7
u2dov
ud1r4
ud1se4
u8ds
u4d3t
u2d5v
3u4dy
udy9ra
ud5å4s2
u2e5a8
u5ei
u1el
uem8na
ue2m1n
u1e2n
u1end5r4
ue6nd
u7e1ne
uensar4
ue6ns
ue7o6
u1er
u5e6r6ast
u2e1ra
ue4re.
ue1re
ue5ren
ue4rer
ue4res
ue5ri
uer1le7
ue2r1l
u2e2r1m
u2e4se
ue2si
ue4skj2
ues6n
u1et
u2e1t8r4
ueu2l8
ue1u
u4fe.
u1fe
u6f2e1b2
ufe6e2
u2f1f4
uf4f5e2r1m
uf1fe
ufi9l
u1fi
uf4i5ne
u1f2l2
u6f2oa
u1fo
uf2o4r3s
u1fr2
uf1t3a
u2ft
uf4tan
uft5s4la
uf4ts
ufts1l4
uft3sp2
u1fø6
u5gag
u1ga
u5gar
u6g5av
ug1by7
u2g1b
u2gem
u1ge
u5g2en
u3g2er
u4g2e1rø
u2g6e5s4
u9get.
u8ge1ta
u2g2e1v
ug8g3s4
u4g1g
u5gis
u1gi
u4gl
ug1l2a
u5g4lad
ug9l8ar.
ug1lar
ug4le.
ug1l6e
ug9ler.
u4gler
ugle7s
ug5ly
ug7na
ug1n
ug7ne.
ug1n4e
u1go1
u4go.
ug4re
u1gr4
ugrei9er
ug2re2i7e
u4grø
ug5s4i
u8gs1
ugs4k
ug7so
ug3s4pr6
ugsp2
ug5s4va7
ugsv2
uguay7a6ns
u1gu
uguay2a
uguay1an
ug5øy5n
u1h
3uhel
ui8a
u4i6c
ui2d
uid5el
ui1de
uid5en.
u2iden
uid5e6ns
uid5er
uid5in
ui1di
u2i3e6
u4il
ui2l5l
u1im
u4i3ne
u1i6ng
uinns3mi8
uin6n1s4
ui4nn
uinns1m
uinns4m2ig9
u1is1m
uista7
uit3en
ui1te
uit5er
u1i6tt
u1j
u4jas
u8je.
uj2e
u7jå
4uk.
u1k2a
u7kar
uka1re6
u1ka6r5t4
u5kat
u2ke.
u1ke
u4ked4
uke5l4
4u2kem
uke4r5an
uk2e1ra
u4ke4rek
uke1re
u3ke1r5i
ukh4o7
u4k1h
u7ki
u1kj2
4u2kk
uk1k6a
uk7kestal
uk1ke
uk4ke3sta
uk8k9l4
uk4k7n2
ukk7s6
u5k2lem
ukl4
uk1le
uk8lu
ukl7ut
u5k4no
u2k1n2
u1ko
u4k5og
u2kra
ukr6
uk3r4us
2uks
uk2sa
uk4sek
uk1se
uk4s2el
uks2e7te
uk2si
uk2s1k
uk7ski
uk2so
uks5t
uk6s3un
uk1su
uk2sø
uk4t5e4sk
u6k1t
uk1te
ukt4es
ukteslø9se
uktesl4
ukte2sløs
uktes1lø
uk4t3id
uk1ti
uk6tj
uk4t5o1ri
uk2t7r4
ukts6l4
uk4ts
ukt3sp2
ukt9s6v2
ukt4s1ti
ukt1st4
uktu8e9ra
uktu1er
uktue5re
u1ku
uku6e
uku5le
uk5v2ik
ukv2
uk2ys8
uk3ø4
uk5å4
6ul.
u1la
u4la.
ula7d
u6l1af
ul3aks
u4l5a6r1t
ul4det
u6ld
ul1de
u4le1f
u1le
u2lek
u2lel
ulele8ge.
u2le1le
ule3le1ge
u4le5ma
u3len.
u6le5ni
u3le6ns
u5ler.
u5le9re.
ul8e1re
ule3ri
u2l3e2r1l
u4l2ero
u2les
ule3st
u5le6st.
u5le6s5te.
ules1te
u2le1u
u1lé
ul4f5l2
u4l1f
ul4fo
ul4fr2
u3l4i
ul2ie8
uli9en
uli5ke.
ul2ik
uli1ke
3uli4k1h
uli9na.
ul6i1na
ul4i5ne
u7li4n1j
u2l1j
ul5ka
u6l1k
ul4k3v2
ul4lag
u2l1l
ul4lam
ull7a2r1m
ul4leg
ul1le
ull5e4g1g
ulleg8ga
ul6le6ng
ul9l8e1re
ul4l6e3s4
ul8li.
ul1li
ul9l2ig
ullin8g7s1
ulli6ng
ul4l5i4v
ul4l3o6s
ul8l2ot
ul1lo3v
ull5s1le
ul8l1s
ull2sl4
ull3s6m
ulls4t
ull3s1v2
ul2lu4
ul6ly
ul2lø
ul1m4u
u2l1m
u1lo
ulo6i
u2l5op
ulo7va
u1lov
ul4sk
u8l1s
uls4p6o
ulsp2
ul8s1n
ul8s7s8
ul2st
ul8s3t6r4
4u2l1t
ul4teg
ul1te
ul4tek
ul4t4es
ul4t4il
ul1ti
ul4tim
ul4to
ul7tor
ul4t3re
ult2r4
ult3ri
u7lua
u3lue
1uluk
u5lup
u5lus
ul9ut.
u7luta3
u9lu1te
ul7va.
u8lv
ul1va
ul5van
ul4var
ul4ve.
ul4veg
ul4vei
ul4v4es
ul2v3t
ul4v3u
1uly
ulyk5kes
ul6yk
uly2kk
ulyk1ke
ulæ6ra
u1læ1
ulæ5re
ulø3se
u1lø
u4lø4y6e
ul5å6l
2u1ma
uma8ge.
uma1ge
u6ma1re
u2m5au
umau7ken
umau4k
umau1ke
u2m3av
2u2m1b
um5be
4um1c
4u2m1d2
2u1me
u4me6nd
u4m3e4n1h2
umen4t5i
ume6n1t
um2e5ra
2um1fo
u2m1f
2u2m1h
2u1mi
u6mi6d1d
umi1e6n
um2ie
um6i9na
u4mi6n1t
um9ja
2u2m3k2
4u4m3l
4u2m1m4
um3me
um4mil
um1mi
um4mis
u2m3n
um1na8
4u1mo
um2o6g
um5ok
2u8m1p
um7pan
um1pa
um4p1le
umpl6
um4p7ut
um1pu
4u2m1r6
u6ms1
um7se.
um1se
um4sk8u
umsku8le.
um3sku1le
um2s2n
um4sor
um1so
ums3t
6u2m7t
umta8l
um3ta
u2m5ut
u1mu
4umve
u2m1v
umø4r3s
u1mø
u2m7øy
u1må7
2un.
un6a6da
u1na
un5a2l5g
u4n3ap
u9nar
u7nas
u3nat
2und.
u6nd
6un1da
6un3de.
un1de
un4d6ek
un5d4e5l
6unden
5under1sk
und2er
unde4rs
5unde2r3v
4undet
un6d7im
un1di
4un8ds
und5s4i
u2ned
u1ne
u4ne3e2
u9nei.
u2nei
u2nel
unele6ge.
une1le
une3le1ge
u4n5e6ng
u3nen
u4n2e1no
u2ne1s2
u3n2es.
u2ne1v
u3ne2v5n
1un8g1d4
u6ng
ungele8ge.
un1ge
unge1le
un1ge3le1ge
un4gem
un2g1j
un4g5l
un4go
un2g1r4
un9g8ru
ung3s4i
un8gs1
u1n6i
u4ni.
u2nid
1u2n2if
u4nim
u3nin
3uni2on2
un4io
uni4st
4u2nk
un4k5l4
un7ko
un4kr6
un7n1ak
u4nn
un1na
unn5e2r1f
un1ne
un4n5e4rs
un4n5e2r1v
un5nes
un4niv
un1ni
un2no
unn5s1te
unnst6
un6ns
unnta8la
un6n7t4
un4n3y
u4no.
u1no
u2n3os
un2o7t
4u6ns
un6sj5i
un1sj
un3skr6
un5s4p2
un3stek
uns1te
uns4t5e6l
un7s6te2nk
unst3o
un1s2v2
un4tal
u6n1t
un4tam
un4t6as
un3t2r4
un4t5r2e1p
un4tri
untun8ge.
un2t1u2n
untu6ng
untun1ge
u1nu6
u2n5ut
u3ny
u3nø
un5øy6d
unøy1
u7nøyg
u3nå
u2o9a
u1ob
u1om
uo6m7s
u1op
uor8da
uo4rd
u8o1re
u4o5ro
u1ov
u2p3av
u1pa
u3p2ea4
u1pe
u5per
up2e1r3a
upe6ren
upe1re
u4pe1ta
6u2p1f
u3pi
u8pi.
up3i6n1s
up3li
upl6
up6ne1v
u2p1n4
up1ne
upo9pe
u1po
u2p1p2
up4ped
up1pe
up4pla
upp1l6
up6pre
upp1r6
up2p5s
4upr6
u3pra
6u2ps
up6sen
up1se
up2s3k
up2s4ke
ups5pr6
upsp2
6u2p1t
6u1pu
u1på
upåvi6
u1på1v
u1ra
u2rad
u2raf
u6r3a2ft
u2r1ak
ur6a6ly
u4ram
u5r4a1ne
u3ra6ns
uran6t5re
ura6n1t
urantr4
u5r4ar.
u6r7a2r1l
ur3a2r1v
u9r6as.
ur5asp2
ur3a6tt
ur7au
urau9ken
u4rau4k
urau1ke
u2r1av
ur4b2o1d
u2r1b8
urb2o
ur4c
u4rd4
ur3di
ur4d5o
u1re
ure8al
ur2ea
uree1r6e
u2re3e2
ure3er
u4r5e4g1g
u1re1gi6
uregist5
3urein
ure4l
u2r3e1le
u4r2e1li
u7r8e2l1l
u7r8e2l1t
u4r3e8l1s
u4r5e6ng
u7reom
ure3o6
ure7o6s
u4re1su
u4re1ta
u4r2e1te
ur4e5v
2u2r1f
ur4f2e1b2
ur1fe
ur6fed
ur3ge
u2r1g
u1ri
u4ri.
uri6a7ne
uri1an
uri9en.
uri1e4n
ur2ie
uri4e5ne
uri9er.
uri1er
u3r2ik
u3rim
ur4i5ne
u4r3i4nn
u2r3i6n1s
u2r3i6n1t
uri6s1p2
ur4ke5s
u2r1k
ur1ke
ur7k6j2
ur6k7ja
ur8kla
urkl4
ur4k1le
ur6k5n2
ur8k5s
url4a8ga
u2r1l
urle6ge.
ur1le
ur3le1ge
urle8se.
url2e1se
urma7g
u2r1m
ur1ma
ur3n2e5a2
u2rn
ur1ne
ur6n1s2
urnæ6re
ur1næ1
u4ro.
u6r4oc
5ur6o1e
uro6m5s
u2rop
uro7pi
u2r3o2pp
ur1or
ur2o3s4
uro4sta
uro8ta
ur2ot
ur4p6el
u2r1p
ur1pe
ur6p9ut
ur3pu
ur3r6e
u2r1r
ursa8ka
u4rs
ur1sa
ur3sak
ur4sal
ur4s3el
ur1se
ur4s1in
ur1si
ur4skr6
ur4s1pe
ur1sp2
ur5s1te
ur1s2t
ur3s5t4il
urs1ti
ur4stj
ur2sv2
urs3va
ur2s1ø
ur3te3o6
u6r1t
ur1te
ur5ti
ur4to
ur6t5ri
urt2r4
urt1s4t4
ur4ts
urue7re
uru1e4r
u6rug8
ur7u4gl
u4ruk6
uru4ke
uru7k1n2
ur6ul
ur4u1mo7
ur4um
uru6n4g
u7r4u2nk
uru7v
ur4vel
u2r1v
ur8v3s1
u6r2ys
urø2
ur1ød
ur1øy
4usa
u9s2a5ne
us3a6ng
u9s2ar.
u7saren
usa1re
u6s7a2r1k
us7au
us1c
2u1se
u5seal
u2s2e1a
use4al.
u6s7edvan5le
used7va
use2d1v
usedva4n1l
u2s1ei
u2sek
u4se1le
us2el
u2sem
u6s5en1di
use6nd
us7e2r1k
u4s5e2r1m
u6se1si
u4set8j2e
us2e1tj
us3eva
u2se1v
4u6s9g6
u2s1h
u2s3h4a
u2s3h4o
us4id
u1si
us3i6nd
u2s5i4nn
u8s7ja9g
u1sj
u4sji
us5kaf
u1ska
u4s3kan
us1ka8ra
u2sk2ar
us7kat
us5ken
u2s1ke
u6s5kis
u1ski
u2s1k4l4
us6k5le
us5klo
us1ko
u1skr6
us7kru
usk7u6t
u1s4ky
us1l4
usle6ge.
us1le
us3le1ge
u2s5lin
us1li
u4slu
us7mø
us1m
u4s1n
us1ok
uso5l
u1s2o6l5d
us1or
u3so2ri
usove7r6e6
u2sov
usove2r
us1p2
u1s2pin
u1spi
us5sar
u4s1s
us6sat
us4s2jå
us1sj
us4skj2
us4s5kor
us4skur
us2s1l4
us7s1mi
us2s1m
uss7mø
us4s5ti
uss2t
us4st6r4
us2sv2
u6stan1de
u3s4ta6nd
u4stat
us3tav
u2s1te
us3ted
u5s2t4ei
us7ten
us1t9et1te
u4stet
uste6tt
us5ti
u5s6t2ig
us1tj
u1s2t5of
us1t2on8
ust7o4nn
us5tor
us2t5o6v
us8t3ra.
ustr4
us8t4ran
u4s8tra5r4
u6stre
ust5ren
us4tri
us3tro
ust5rød
u1s2trø
u6st5rå
u2s4t7ut
u4stå
us3u6ng
u2sur
us1v2
u5sv2a1le
u7s6va2l1t
usva8n
usvi8ka.
usv2ik
usvi1k2a
6u1sæ
usæ9le
usæ2l
u3s2ø6m
u1sø
us1øy
u1t8a1e2
u2tag
u7ta4g1g
u9tal.
ut3a2l1b2
ut5a6ld
u3ta2l1l
u5tan1de
uta6nd
uta7no
ut6a6n1v
ut3a2r1m
u4t3a6r1t
uta1s4
u5t6as.
u6t5a6sa
u6t5a6se
u2ta1u4
1u6t1b2
ut4ba.
ut1b4a
utbe8d
ut1be
4ut1b4i
ut6by.
ut1by
utch5
u2tc
1u2t3d
4utdat
ut1da
utda7ta.
utd4ata5
u2te.
u1te
u2t2ea
u4te3e4
u4tei
u2tek
ut3eks
u5t4ek8st
ute6ma.
u2tem
ute1ma
u5te2m1m4
u9te2m1t
ut2en
u6t2e1na
u3te2nk
u4t2e1nu
u2te3o6
2uter
u6t2e1rø
u2t4e1s6
u4te1ta
u3te6tt
u2t6e1u
ut6eva9
u2te1v
1u6t1f
utfa7s
ut1fa
utfø5re
ut1fø
utf8ør
1u2t3g2
8utg4å1e
ut1gå
6u3ti
u4t5if
ut4ik2k6u2
ut2ik
uti2kk
uti3ku9
u4ti1li
ut4il
u6ti1ven
uti1ve
ut1j
u5tjen
utj2e
ut6ju
3utlei
u6t3l6
ut1le
utlø5se
ut1lø
utlå7na
utlå2n
utmå5le
u2t1m
ut1må
utm6ål
u8t5ni
u8t1n
1ut1ny
2uto
u4t7o6m1s
ut2o5s
u2to4v
3utpr6
u6t3p2
ut4på
ut1r4
u5t4ra1di
ut3re
u5t6re6ng
u3t2riv
u3t2rol
u3t2r2os
5utru
u5t6rul
ut4rø
ut9rød
1u4t1s4
4u6ts.
4utsei
ut1se
utse8t9ja
uts2e1tj
2ut1sj
ut3sk
5ut1st4
7ut1t6ak.
u6tt
ut1ta
5utt2a1ke
ut4te2r1k
ut1te
utt6eva
ut2te1v
ut4tid
ut1ti
ut4t4il
ut5tor2ea
utt8o5re
ut1tr4
ut4tra
3ut5try
ut6t1s2
u1tu
utu5e
u6t5un
utun8ge.
utu6ng
utun1ge
u2t5ut
1u2t1v
u3t4vil
utvi5se
2u3ty
3u4t6yd
u8ty.
8u2tz
ut1ø
u5t2ør
ut4ø8s4
utøs7t
1u2tø4v
ut5øy5a
ut4øy
ut7å2k
u3tål
ut1ån
ut7år
u1u
u7u1ma
u5u1me
u7ut
u1v
uve5di
uve2d1
uve3in
uve6ris
uve1ri
uv4e3s
u7v2es.
uve1t5ø
uv4et
u6ve1v
u5vi
uv2i6k
uv5ra
u2vr8
uv9sa
u8vs
u5vu
u3vø9re
u1w
4ux1
ux4a4
uy6a
u7yn
u1æ
uær3le9ge
uæ2r1l
uær1le
u1ø6
uøv5
u9åra
u7åre
va3a2m
va8a2n
4vab
6vad1j
vad1r4
vaf3
vaf6r2
va2i5r
vai5s
v4a6ka.
va1ka
v6a7kan
va5ker
v2a1ke
6va4k1h
va8ki.
v4a1ki
4v2a1ko
vak3r6
7v6ak1si
4v3ak1sj
vak5s6t
1va6k1t
vak2t5r4
va1ku
v2ak3v2
va3lan
va4led
v2a1le
va4l9eks
4valel
val4g3r4
va2lg
val2i9e8
va1li
val3la
va2l1l
val6mes
va2l1m
val1me
va6l1o
va1lu
va4lun
4va8lv
va4løy
va1lø
va1l7å
vam8pu
va8m1p
4v3anal
va1na
v4an8d5s
va6nd
v4a1ne
vane5s
va4net
2va6ng
van4gr4
van8g4s7
va8ni.
va1ni
1v4a4nn
van4n4an
van1na
van4niv
van1ni
vann6s1ti
vann3st6
van6ns
va4nom
va1no
v4a6ns
van5sem
van1se
5vansk
van5s6ki
van3s1m
van3ti
va6n1t
van9tr4
v6a4ny
2vap
va5po
va4ra.
va1ra
va6rak
va4ral
va4ram
va4rap
va4re.
va1re
va4reg
va6rem
va4res
var8ka
va2r1k
var5ma
va2r1m
var4me7e2
var1me
var4sa
v2a4rs
var4sk
var4s5ti
var1s2t
var4sv2
varta9la
va6r1t
var6tem
var3te
var4t5i4s
var1ti
4v4a1sa
va4sa.
va9set
va1se
6vasid
va1si
va6s2ie
va4s2if
va4s2ik
va4s2il
va6s2is
va2sk
vass5a
va4s1s
vas4sau
vas4s4el
vas1se
vas6s7ø
4vas1ta
v4at5a1
va3ta.
va6t7e8l
va1te
va2te2r1f
v4ater
vatle6ge.
va6t3l6
vat1le
vat3le1ge
5va8t1n
va1to
va6tr4
va1t7ra
v2a4t1s
4vau
2v1av
va4z
2v1b2
vba4ne.
v1b4a
vba1ne
vb2e2r5g
v1be
vb4er
vbo6da
vb2o
vb2o1d
6v1d
v7de1v
v1de
vdin8g5s4
v1di
vdi6ng
v7do5i
v1do
v7d2on
vd6r4
vdu6en.
v1du
vdu1e2n
vdu6er
v7dø
1ve.
2v2e1a2
ve6ag
ve6ar.
ve1ar
2v2e1b2
2v2ec
ve2d1
ve9d8o2b3
ve1do
vedom5
ve8ds2
ved5s4k
3ve8d5t
ve1dø4
vedå6
2ve3e2
ve4er
2ve1f
ve6g5av
v2e1ga
ve5ge
3ve4g1g
veg8gs4
2ve1gj
4vegl
ve3g1l6e
ve5g6lød
ve5g6lø
veg5n
2v4e1go
6vegre
ve1g2r4
ve4g5re6tt
4vegru
ve4gut
ve1gu
v4e4g5å
2ve1h
1ve2i7e
vei3e2n
4vei2l1l
v4eil
6veit
2ve1j
4v4e1ka
5ve4ka.
ve4ke.
ve1ke
ve4ked4
4veke3e2
2ve1ki
4v4e1kj2
2ve1kl4
vek8la
vek8le
ve4k1li
2ve1k2o
2v2e1kr6
3veks
vek4st
veks4t5r4
1ve6k1t
vek4t5an
v4ek5to
vek4t5r4
2ve1kå
5vel.
ve4l5as
v2e1la
5vel4at.
5vela6t3p2
6veld.
ve6ld
vel4del
vel1de
8vel8ds
4v4eled
ve1le
6velei
7velet
4velis
v2e1li
4velit
6veliv
2v2e1lo
5vel4oc
ve8l1s2
vel3se
vel3se6s5
vel3s1m
vel5sp2
vel5st
4velug
vel5un
ve8l5v
2v4e1ly
2v2e1læ1
2v2e1lø
velø8pa
2vem
ve5ma
1ven
ve8na.
v2e1na
4v4enem
ve1ne
ve5net
ve6n2e3te
4v4ene1v
ve4nin
ve1ni
ven6n5i
ve4nn
venn5le7
ven4n1l
v2e2no
ven6s4ti
ven1st
ve6ns
ven6str4
vent6a
ve6n1t
ven5t6r4
ven4t4s
4venty
2v2e1nu
ve4nus
ve5ny.
ve1ny
4venya
ve4nym
2ve3o6
2v2e1p
1v8er.
ve2r5ak
v2e1ra
ver3al
8ve2rau
ver6de1s
ver1d2e
ve4rd
ver6din
ver1di
ver6d2ir
ver3d7v
ve3re.
ve1re
4vered
4vereg
5ve2r1e2ig
4verek
ve4rel
ve6r7e6ng
4vere4nn
4ver2e1p
ve5rer
ve5r2es.
4v6ere6tt
vere4t
4ve4r2e5tu
verfø5re
ve2r1f
ver1fø
verf8ør
ve9ri.
ve1ri
veri3a
ve4rial
ve7r2ie
4ver2ik
v4er3il
5v6er2k1n2
ve2r1k
ve2r5m6
ver6nal
v2e2rn
ver3na
ver6n6s5
ve9ro.
v2ero
ve3rom
ver8s4ka.
ver1sk
ver3ska
ve4rs
ver5ski
ver7s1p6o
v6er1sp2
vert8a8la.
v2erta
ve6r1t
verta6le.
ver3t2a1le
v2e1ru
5verum.
ve3r4um
6ve1ry4
v2e1rø6
ver3øs4
4v2e1rå
v4es
3v2es.
2ve1s4a
ve5san
4veset
v2e1se
4ves1h
4ve1si
4ve1sj
4ve1ski
2vesl4
ve9s1li
ve1s2m
6v4e3so
4ve1s2p2
ve6s3per
ves1pe
ve5s2ted
ves1te
ves6t5e6nd
v6esten
ves2t5o4v
4vestr4
4ve1stu
4ve1stø
vesva7
vesv2
5vesyn8ds
v2e3sy
ves2yn
vesy6nd
2ve1sø
ve1så
veså5re
veså2r
v4et
1vet.
2ve1ta
8veta1ka
ve3tak
4veteg
v2e1te
4vetek
ve5t4e6s7
6ve2te1v
2v2e7ti
4v2e1tj
2v2e1to
ve4to.
ve1t6o7e
9ve1to3ke
vet6ok
9vetoks.
ve4tor
2v2e1tr4
1ve4ts1
vett5sk
ve6tt
vet6ts
6v2e3t2v
4v2e1ty
vet4y8e
2ve1u
ve8um2
2veva
ve1v
ve6vak
ve5van
ve9var
2vevi
vevi8sa
vev4s1m
ve8v1s
vev4sp2
2ve1ø4
2ve1å6
1vê
2v1f
vfa2l1l4
v1fa
v5fal
vfø7re.
v1fø
vf8ør
vfø1re
2v1g2
v4ga.
v1ga
vga4le.
vg2a1le
vga6li
vga8ve.
vg2a1ve
v9ge
v4gi.
v1gi
vgje4r6s7
v1gj
v3gjer
vgj2e
v1go4
v4gå.
v1gå
2v1h
vhø4re.
vhø1re
1via
vi1an
vi6bl2
vi1b2
vice5s
1vid
vi5de
vide4o7
vi4d5o4
vid3r4
vi8d3s4
vi1el
v2ie
vi5e6nd
vi1en
vi2g4m
v2ig
vi4gu
vi5gø
vi4kat
v2ik
vi1k2a
5vi6k1b4
vi5ked4
vi1ke
7vi4k5g2
vik5ke
vi2kk
vi2k5n2
vi2k3o6
3viks.
vik5sa
vik5s6l4
4vi6k1t
5vik1ti
vi4k5ø4
vi2l1a
vi5la.
vi4le1s4
vi1le
vi4l3in
vi1li
1vi2lj
vil4l5an
vi2l1l
vil4led
vil1le
ville6de.
ville1de
vil4lei
vil2l9e1p
6v5illu
vi2l5m
vil1o
vil4s1ti
vils2t
vi8l1s
vilt7o
vi2l1t
vil6t1ret
vilt2r4
vil4tri
vi2lø
4vim
vi8m7p2
v4i1m7u
4vin4d1l4
vi6nd
5vin1du
vine5st
v4i1ne
v6i6ng
4ving7n
vin5g2om.
vin8g7o8m
vin1go
vin2g3r4
v6in8g9u
vin3na
vi4nn
vin4ned
vin1ne
6vin4n1h2
4v5in4n1l
4v5in6n1s4
4v1in6n7t4
vi6n5o6ve2r
v2i1no
vino1v
vino3ve
v2i6n2s
vin7sja
v6in1sj
vin4t4es
vi6n1t
vin1te
vin5tre
vint2r4
vi8pa.
vi1pa
vi6pe.
vi1pe
vi2p5s
1v2ir
4viro
7vi1ru
vis3ab
vi1sa
vis3ak
vis5a6nd
vi4se3e2
vi1se
vi3sen
vi6se6ng
vi2s1k
visk6re
vis1kr6
vis5m
vi1so
vis3om
vi4s1p2
vis9pa
viss4p2
vi4s1s
vis5ti
vi4tak
vi1ta
vi5t2e1se
vi2t4e1s
vi1te
5vi6t5j
vi4t3o
vi4tr4
vit7ra
vit5re
vit5skr6
vi4ts
vit6t4e6s7
vi6tt
vit1te
vi4t5un
vi1tu
vitun6ge.
vitu6ng
vitun1ge
vi4t5øy4
vi1tø
vi4va.
v4i1va
vi6v4es
vi1ve
vi1vi3
v1ja
v1j2e
vje4t5a
vj2e5t6e
vj2e4t5r4
vje6t3t
vj2e4tu
v1ji
4v5j2o
vju3t
6v3k6
vk4le
vkl4
vk4ler
vku4le.
vku1le
6v1l2
v6la1b
vl4a6ga
v9led
v1le
v9leg
vle2i9e
vl6el
vle4se.
vl2e1se
v5les1n
vle5str4
v6les1v2
vli7v
v1li
v8l1s9
vl4u9sa5
2v1m
vma8le.
v1ma
vm2a1le
vmo8de.
v1mo
v5mo1d4e
vm2o1d
vmo4rd4
vmå8la
v1må
vm6ål
2v1n
v3nad
v1na
vna8ke.
v2nak
vn2a1ke
vn5al
v9nar
vn5dy
v6nd
v2n2e7b2
v1ne
v2ned
vne7de
v4ne1le
v4n2e1li
v4n2e1lo
v2nem
vne5r6e
v4n2ero
vne1s
v4nesta
v6nes1te
v2ne1v
vnor9s8kas
v1no
vn2o4r2s
vnor1ska
vn4s5pa
v6ns
vnsp2
vn1s4t
v6n5t4
vnæ4re.
v1næ1
vnæ1re
vn5ør
v1nø
vo1al
v2oa
vo2ar
6vob
2vof
vof8fan
vo2ff
vof1fa
vo4gu
v2og
voi1
1vok
4vo3ko
vok6s5
voks3k
1vol
v2o1li7
vo4li1tu
vol4t4es
vo2l1t
vol1te
2vom
vo4na
v2on
vo9nal
vo9nar
von5de
vo6nd
vo6ns6
von5s1h
2v1o6p
2vo4rd
vor8da.
vor1da
v8o1re
vo9ren
2v1o2r1g
4v4oro
vors7k
v2o4rs
1v2os
4vo1sj
vos4se
vo4s1s
vo4teg
v2ot
vo1te
vo4tel
6vo1tr4
vo1v
vo4ve.
vo6vi.
v2o1vi
2v1p4
2vr8
3v6rak
v7rar
v1re
v1re1gi3
6vren1ge
vre6ng
4vren1gi
v6ren1gj
v6ren2g1t
vret6t4s
vre6tt
v6ri.
3vrid
7vr2ie
v6rigas
vr2ig
vri1ga
vri6ma.
v4r4i1ma
vri8me.
vri1me
v3ri6ng
v2ri6n5s
vri6ve.
vri1ve
v1ro
v4rob
v4rof
v4rok
v4rop
vro8te.
vr2ot
vro1te
v1ru
v7ry
vrø3ve
v1rå
vrå8da.
vr6å1d
vrå1da
v6sa.
v7s6a1li
v6se.
v1se
v4seg
vs3e4g1g
v3s4el
v4s5e4li
vse4n
v5s2en.
v4s5e6ng
v5s4e6ns
v2ser
v6si.
v1si
vs1in
v1sk
v2s8ke
v6s5kum
v2s1kv2
v1sl4
vs3lan
vs6let
vs1le
v6sl2ik
vs1li
vsmå8la
vs1m
vs1må
vsm6ål
v1s2n
v6s7na
vs3ne
vs3ny
v2s3næ1
v4s3nø
v1s5o1d
vsom5
vs5o6ms4
v7s2on
vs1or
vs2pe
vsp2
vsre6de.
v8s9r
vsre1de
vs7s6t
v4s1s
vs6s5å
vste6ma.
vs2tem
vste1ma
vs3t4il
vs1ti
vs1v2
vsø6ke.
v1sø
v3s6ø1ke
v3s4øl5
v2s5øy
vsøy4er
v8sø4y1e
v8så.
vså7re
vså2r
2v1t
vta1
v4ta.
vt4a8ka.
vta1ka
vta6led
v3t2a1le
vta9len
vta4le5v
vta6s
v5te2pp
v1te
v2t2e1p
v5t4i
vt6i8na
vti8ne.
vt4i1ne
v5t4r4
v3t2v
vt4y8e
1vu
v2u2d1
vu6d5d
vude6n5t
vu1de
vu8ds4
8vué
vu8k
2vul
2vu6ng
vun4ge.
vun1ge
6vu6o
2v1ut
2v1v
vva8la
vve8g9ing.
vve1gi
vvegi6ng
vve6g7in1ga
vven7n6i
v1ven
vve4nn
vvi5ke
vv2ik
vvi6k7l4
vvi8s9an
vvi1sa
vvi4se.
vvi1se
2vy1
vya4
vy7e6ns4
v4y1e
vye2n
1væ
væ1ra
væ9ret
væ1re
væ8ta.
væ5ta
v3ø4d
vø4l
vø7li
vøm4met6
vø2m1m4
vøm1me
3vø1re
vø6re.
v3ør1ke
vø2r1k
vør8na.
vø2rn
vør3na
vø3se.
vø1se
v5øv
4v1øy
vø4y6e
vøy7ene.
vøye4n
vøy7e6ne
vøy9enes
1vå1a
1v4å1e
4våe6nd
våe2n
v4åk
vå8ka.
vå1ka
vå4ke.
vå1ke
vå4ke3ne
vå3ken
vå9kene.
vå4le.
vå1le
1vån
vå5ne
4vå1nu
vå3ren
vå8sa.
vås2
vå1sa
vå5t4es
vå1te
6vå6t1f
våt7å
w2a
wa6l
5wa6ld
wa6l4k
walk5o4
wa2l4l
2wap
war6d7er
wa4rd
war1de
was2h3
1wat
wa3z
w1b
wbo6y5
wb2o
we2
wea1te7
w2ea
w2e7b2
we3g
we5re
wes2
we4s3s
wester6
wes1te
west2e2rn7
wet5
we6tt4
w4i2
wi9ar.
wich3
3wic6z
wi3d
5wi6en
w2ie
wi9er.
w2i5f
wi3ni
wi5ra
w2ir
wi3ren
wi1re
wi5ta
wk3r6
w1l
w1m
w6n5s
wob5
wou6
wout7
ws5
ws6k
wu4rs6
wy2
w1yo
wy1o2r2
wyo2r1k3
w1z
xa6n5t
x7b
x3f
x1ga6
x7h
x1ic
5x6id
x2i5e4
x5k
x1l
x1p6
x3r
x1s2
x7t
x6u
xy2
ya7b
1y8ac
ya5f
ya4h
y1ak
yak6te.
ya6k1t
yak1te
ya2l
y7am
y1an
y6an.
y2a1na
ya4ne.
ya1ne
ya2n7k
yan7sl4
ya6ns
y1a2r1k
y7a2r1r
yas4
ya5si
ya5t
y1av
y1b
yba4ne.
y1b4a
yba1ne
yb4bed
y2b1b
yb1be
yb1de3
y2b1d
yb2o2
ybu6er
y1b2u
ybue7re
ybu7e2rn
ybyg5
y1by
y1c
y8ce.
y8cé
y2co3
y6dab
y1da
y2d5av
4y6d1d
yd6d2e1la
yd1de
yd4d2e1p
y6d7e4n1h2
y1de
y4de3o6
y2d2e1p
y4d2ero
yd2er
y2d1is
y1di
yd6j2e
yd1ji
y1do4
y2d3op
y2d5ov
y1dr4
y4d4r5au
yd1re
y4d5rem
y4dro
y4d4r5ok7s
yd3s1i
y8ds
yds4v2
y2du
yd1un
y1dø4
y4døm
yd2ør5
y2d1øs
4y1e
ye4d8l4
ye6dre
ye1dr4
y2ek
y5eks
y4e4len
ye1le
ye6let
yel4s5j
ye8l1s
yel4sk
yel2s3m
yel6s5t
y2em
ye2n
y7e6ne
y8ener
y2e4n1h2
ye5ni
y2e2nk
y2e4n1l
yenle6ge.
yen5le
yen3le1ge
ye6ns4
y4en1se
y3e6re
ye4rel
y3e2rn
yer8sk
ye4rs
y4er1s2t
yes2
ye5s1m
ye2t
y2e9te
y2e5ty
y2e1v
y1f
yfan9
y1fa
yfje2l1l4
y1fj
yfj2e
yf2jel
y4f1le
yf2l2
yf5le.
y8ga.
y1ga
yga8ve.
yg2a1ve
yg4dal
y8g1d
yg1da
yg4dek
yg1de
yg4del
yg4d2e1p
yg2do
yg2d1r4
yg2d1y
yg2dø
y2ge5i
y1ge
yge6n5s
yg2en
y6gere4t
yg2er
yge1re
y2g6es
y4g2e1v
yg6gam
y4g1g
yg1ga
ygg7a6r1t
ygg6es4
yg1ge
yg5gj
yg2g5l
yg6gr4
yg8g3s2
yg6gu
yg6g5å
y1gi2
y5glo
y2g7m
y1g6r4
y1h
yhes5
y1i
y9in.
yis7t
y1j
6yk
y1ka
ykap3
y6ka1ra
y2ke
y7k6el.
y5k4elen
yke1le
y3k2e1li
y7ke8l1s4
y3ken
y3ker
y4kerel
yke1re
y4k2ero
y5ket.
y9ke4ts2
y1ki
y1kj2
y4kjar
y5kja1re
ykjeva8ne.
ykj2e
yk4jev6a
ykje1v
ykjev4a1ne
yk4ka1na
y2kk
yk1ka
yk5k6e4n1h2
yk1ke
yk5ke1sj
yk4ke6s5ta
yk1kj2
ykk5ni
yk4k1n2
ykk4s5k
ykk7s
yk8k5vi6
ykk1v2
yk4ky6
yk1la
ykl4
yk6lest
yk1le
y1klo
y3k2lub
yk3lus
y5k2læ1
y1ko
y2k2oa
y6k2o1li
y4k2o1mo
y1kom
y5ko1sa
y1k2os
y2k2ot
y5ko5t4i
y1kr6
yk1s
yku6le.
yku1le
yk3var
ykv2
y1la
yl4a8ga
yl4dan
y6ld
yl1da
yl4de2r5k
yl3d2er
yl1de
ylde4s
yld1r4
yld5s6k
yl8ds
yle6ge.
y1le
y3le1ge
y7les
y4le1v
yli6g1h9
y1li
y3l2ig
y6l5k8
yl1k4e2
ylke4s3
ylkesl4a8ga
ylke1sla
ylkesl4
yl4le1v
y2l1l
yl1le
yll5is.
yl1li
y8l3s6
ylst7re
ylstr4
yl5tet
y2l1t
yl1te
y8l2u1se
3yl1v4en
y8lv
y1ly
ylæ7re
y1læ1
y6mei
y1me
y4m2e3te
ym4fe5d6
y2m1f
ym1fe
ym5ja1ra
y4mo.
y1mo
y3m6o5e
y8m3p2
y6m1s
ym8sa.
ym4se
ym8sl4
ym6ta.
y2m1t
ym3ta
ym6te.
ym1te
ymå6la
y1må
ym6ål
y9n8a6nd
y1na
ynde1s8
y6nd
yn1de
yn5det
y2nel
y1ne
yn7e6ld
y4n2e2r3g
yne5s4
yne3si8
y6ne1v
yn5gel
y6ng
yn1ge
yn4gem
yn2gr4
yn4g5ø
yn3ko3
y2nk
yn6k5v2
yn5na
y4nn
yn5ne
yn6n3s4
yn1n4ø
y3no
y4no.
y6ns1
yns6a1re
yn4sem
yn1se
yn9set
yn2si
yn2sk
yns9ket.
yn2s1ke
yn1s7ki
yn5skj2
yn6s7kje1le
ynskj2e
ynsk2jel
yn8s9kjer.
yn4st
yn2su
yn4s5ver
yn2s3ve
ynsv2
yn4tap
y6n1t
yn8teg
yn1te
yn2t5ei
yn4tek
yn6t2e3te
yo4g2at
y2og
yo1ga
yo6gi.
yo1gi
y1om
y2on4
yon5n4en
yo4nn
yon1ne
yo6nu
y1o2p
y1o2r
yor6da
yo4rd
y8o5re
you4
yout5
y1p8e
y2pe.
y4pe3e6
y4p2e1na
y4pe3net
ype1ne
ype4rel
ype1re
y6pe1ta
y1pi
y4pi.
y7pi6ng
yp5i4nn
ypin8na
y1p2l6
y5po
yp7p6a
y2pp
yp3pe
y1pr6
y2p5s8
yp4t6ok
y2p1t
y3pu
y1ra
yra8ka
y4r3a6l
y7r1a2r1b8
y4r5au
y2r5av
yr7da
y4rd
yr5dø
y7real
yr2ea
yre3in
y5r2eis
yrek4
y7r2e2k7k
y3re8l1s
y5re6m1s
y2rem
y1ren
yre9ne
y4re3o6
y6rerel
y1rer
yre1re
y4r2ero
yre5s3c
y5re6st.
yre5s1te
y5re6s5te.
yr6es6ten
y1ret
y4re1ta
y4r2e1te
y4r2e1to
y7re1tø
yr7ga
y2r1g
y1ri
yri6n5g6
yri6ene.
yri1e4n
yr2ie
yrie1ne
y4r2if
yri8ka.
yr2ik
yri1k2a
y4r3i4nn
yris6p2
yr4ke3e2
y2r1k
yr1ke
yrke4s
yr5kj2
y2r3m
y7r6o1e
y3r2o2m1m4
yr1op
y5rosk
yr2os
y2r5r8
yr4san
y4rs
yr1sa
yr7set
yr1se
yr5sk
yr6skj2
yr6skr6
yr2sp2
yrs7tan
yr1s2t
yrs5tar
yrs7te1ne
yrs1te
yrs5te6n6s
yr3s1ti
yr4sv2
yr3te
y6r1t
yr6tek
yr2t3r4
yr3t8ra
yrty8
y1ru
yrul8la.
yr2u2l1l
y6r5ut5r4
y2r3v
y1ry
yr3øk
yrøy4
y1rå1
yr6å1d2
yrå8da
yråd2e7r
yrå1de
yrå6de4rs
y4rå7di
y2r6åm
y6r4å1s4t
yrå1s2
y4råt
y4rå1v
2ys
y1sa
ys6a7ke4rs
y3s4a3ke
y1s4e
yse4b2u
y2s2e1b2
y4sed
y4se3e2
yse6e3i
y2seg
y2sek
ys7ek1te
yse6k1t
y2sem
y4s2e3ra
yse7rid
ys2e1ri
yser2i7e6
y4se3sj
y4sesk
y4se5s6l4
y4se1st
y5ses1te
y6se1su
y9s2et.
y4se1u
y1s6i
ys2i8e
y5s2j1k
y1sj
y7s6kag
y1ska
ys6ka1ra
y2sk2ar
ys7ka1re
ysk9au
y2s2k3l4
ysk5øs4
ys4mi
ys1m
ys4nø
ys1n
y1so
y4s5or
ys2pe
ysp2
ys5pis
y1spi
y5s4p6o
ys6sak
y4s1s
ys6s2am
ys4s3ei
ys1se
ys4s3ek
ys4s5il
ys1si
ys2sj
ys4sku
ys2so
ys2sp2
yss5pe
ys4sta
yss2t
yss5tab
ys4s5ti
ys4su
ys2s5v2
ys2t
ys3ta
y8s1t4ar.
y1s4tat
ys3tel
ys1te
ys3ten
y4s3ter
ysteri1e7n
yste1ri
yster2ie
ys7tesl4
y2st4es
y2s3ti
yst3op
yst3r4
y2s5t6ry
y1s6ty
y1su
ys1ve
ysv2
ys5å2r
y1ta
y4t3a2na
y2te3e4
y1te
y4te1f
y2teg
yt5e4ge
yte3in
y4t4e1ka
y4te1ki
y4t2e1na
y4t2e1no
y2te3o6
y4tere4t
yte1re
yt2e6ro
yte4rs6
yter5s1ke
yter1sk
yt2e5r6ø
y2t4es2
y2tet
y3t2hi
y2t1h
6y5t4il
y1ti
y6tj
yt9ja
y1t5jen
ytj2e
y2t9jer
yt4mei
y2t1m
yt1me
yt4mes
y3to
yt8ra
ytr4
yt4re.
yt5rer
yt1ri
yt4r2ik
y5trå
yt4s3en
y4ts
yt1se
yt3sk
yt5s1v2
4y6tt
yt5t6a
yt1t4e
ytte8r9e6nd
ytte1re
ytt4es6
yt4test
yt8tien.
ytt2i3e2
yt1ti
ytti1en
yttsa6me.
yt6ts
ytt1s2am
yttsa1me
2y3tu
y1ty
yt4y2s
y7tå8
y1u
y6ua
y8ue
yu8g
yu4l
y1v
y4vak
yva8la
y2ve2d1
yve4ri3a
yve1ri
yve4ris
y4v2e1se
yv4es
y6v2e3sy
y6ve1v
yvi8sa
yvi6se.
yvi1se
yv1år
yvå5te
y1w
y5æ
y5ø
y1å2
za5b
3z2a1e2
4zaes
za4g5
za5k6h
za1ni4
zania7
5za5v
z1b4
z1b2u4
z1c
z1d
1ze
z2ea1
ze1b2u9
z2e1b2
z2e1li5
ze6n3s
4z3en1se
4z3e6n1t
z1f
z1g
5zh2a
z3hi
z5hu
5z2ie
1zi1fi
z2if
5zi6ng
z5i6n1t
1zis
6zi1sty
4zi5s4v2
2zi1sy
z1k
4z1l
z1m
5zo.
5z6o1e
zo1f2
zo4no
z2on
z8o5re
6z2os
z1p
z5r
z1s
4z1t
zu3e
z1un
z1v
z1za.
z2z3el
z1ze
æ5by
æ2b4ø
æ5de
æ7di
æ1f
æg6
æ2k
æ3ke
æ5ki
æ8kj2
æk1ja
æk7je.
ækj2e
æk9jer.
æk1ke7
æ2kk
ækkel6
æk6l4
æ1la
æ4le.
æ1le
æ2le3d
æ4l2e1na
æ4le1v
æ2l5j
æls1le9
æ8l1s
æl2sl4
æ4r1ak
æ3r4a1ne
æ4ra6ns
æ4r1a2r1b8
æ6rar1be
ær4ar3te
æ2r1a6r5t
æ4r1at
ærbu5e
æ2r1b8
ær1b2u
æ4r1d4
æ1re
æ2r2ea
æ4red
æ2re1f
æ2reg
æ8r7ei
æ2re2i7e
æ2rek
æ6r7e6ld
ære5ne.
ære1ne
ære7nes
æ2r2e1p
æ5r2e2rn
æ1rer
ære1s2a4m
æ2re1sa
ære6skr6
æ2resk
æ6re4sl4
ære4s2p2
æ4re1ta
æ4retek
ær2e1te
æ9re4ts1
æ2re1v
4æ2r1f
ærg2en5
æ2r1g
ær1ge
ærhø8
æ2r1h
æ1ri
æri6e7ne
æri1e4n
ær2ie
æ4r3il
æ2r3i6n1s
æri6s
ær5is.
4æ2r1k2
ærle6ge.
æ2r1l
ær1le
ær3le1ge
4æ2r1m
ærmå8la
ærm6ål4
ær1må
æ2r3n
ær5ne4
ær4nå
æ2r7o6
ærom5
æro6ms4
æ2r5r
ær6s2el
æ4rs
ær1se
ær4s5il
ær1si
ær1sk
ær7s6no
ærs1n
ær2sp2
æ2r1u
ærut5
æ2r3v
æ2r1ø4
ærø4y7e
ær3å4
æ8se.
æ1se
æ2sj
æ2s1k
æ5ta
4æ1te
æ4te.
æt4r4
æt4ta
æ6tt
æ5va
ævar5
æv4e4s
æ5vi
6ø1a2
øa7re
øau4
ø1b
øbe6le1v
ø1be
øbe1le
øb2e4li
ø2bl2
øb6l9u
øb4r8
ød3ag
ø1da
ø4dak
ø6d3d
ød9de.
ød1de
ø2de.
ø1de
ø4de1de
ø6de1f
ø4d2e1la
ø4dem
ø4de3o6
ø4de1ri
ød2er
ø4d2ero
ø5dj
ød1r4
ø2d4red
ød4rek
ød4r2e1p
ød2sc
ø8ds
ød6s5ek
ød1se
ød2s7ke
øds4ko
ød6sku6
øds4mu
øds1m
ød8t5om
ø4d1t
ød7to
ødt6r4
ø2du
ød3u6nd
ø4d5ur
ø6d5ø
4ø1e2
ø4ed
ø5e1ne
ø5e1p
ø3e6re
ø1fe8
øf3fe
ø2ff
ø1f6j
øf8la.
øf2l2
øf8le.
øf1le
øff2e8l7a
ø5fn6
øft4e5s
ø2ft
øf1te
øg1
ø1g2a
ø6g5ak
øg5al
ø7gar
ø5gas
ø3g2er
ø1ge
øg5gl
ø4g1g
øg8gå
ø7g4j
øglo8ve
ø6g1lov
øgn6s7p2
øg1n
øg6n1s
ø2g2r4
øg5re.
øg5r2es.
ø8gs2
øg4s1t6e
øg4str4
øg3ta5
ø2g1t
4ø1g4u
ø1h
ø4i
ø2i4e
ø5i6ng
ø5isk
ø4it
ø1j
6øk.
6ø1ka
ø5kav
øka8ve.
øk2a1ve
ø6ke5h
ø1ke
ø4k2e1lo
ø6ker6a3d2a
øk2e1ra
ø6kerel
øke1re
ø4k2e5ru
øke1s
ø6k2e1se
ø8kesl4
ø4kest
ø4k2e3te
ø1ki
4økj2
ø5kja1re
ø5kjas
ø1kj2e
2ø2kk
øk5kel
øk1ke
øk5ket
øk4k5l4
økk5r6
øk3lag
økl4
øk3lan
2øk1na
ø2k1n2
1øko
øk5o2pp
ø1kr6
ø8krar
øk8sa.
øk1sa
øk6se.
øk1se
øk4ses
øk4si
øk2so
øks1t6
øks5ti
øku4r
øk5ur.
øk5u1re
øk7ve
økv2
ø7ky
ø1la
ø2la1h
ø6lam
ø6l3d
ø1le
ø4le1di
ø6le1lu
ø4l2ero
ø6le1ta
ø4l2e5te
ø2le1v
øl4g2e1ra
ø2lg
øl1ge
ølg2er
ølge5s6v2
ølg6es
ø2l1j
øl2k4e
ø6l1k
ølke7s
øl4l2e1se
ø2l1l
øll6es
øl1le
øl9l2e3ti
øl6le1v
ø1lo
ø2l7op
ølrå4
ø2l5r4
øl6s2el
ø8l1s
øl1se
øl4s2e5r4e
øl4se3s
øls6t4
øl5s4v2
ø5luk
øl7ut
øl1va6
ø8lv
øl4var
øl4v3ei
øl6vek
øl8vel
ølv7e2r1k
øl4v3in
øl1vi
ølv5o
øl2v7r8
ø1ly
ø4me.
ø1me
ø9met
1øm1fi
ø2m1f
ø5mi
ømi1ni6
øm4med
ø2m1m4
øm1me
øm4m2ero
ømmet6
øm4m2e1tr4
ø2m3op
ø1mo
øm4p6el
ø8m1p
øm1pe
øm7s6p2
ø6ms
øm1st
øm5svi
øms1v2
ø2m1u
ømå8la
ø1må
øm6ål
øn2ad
ø1na
ø6n5al
ø5nas
ø2nem
ø1ne
ø3n2es.
ø9n2e1se
ønhø8re.
ø4n1h2
ønhø1re
4ø4nn
øn4nal
øn1na
øn1ni4
øn4n5it
ønns5al
øn6ns
ønn4s3e
ønn4s5i4d
ønn1si
ønns5kj2e
ønnskj2
ønns5kre
ønnskr6
ønns5ku
ønns3l4
ønns3t6
øn2nø
ø6nom
ø1no
ø6n2s
øn9sa.
øn5sak
øn5s2am
øn5se.
øn1se
øn3ser
øn3skj2
øn8skjer.
ønskj2e
ønst3r4
ønst9ra
ø6n1t1
øn2ta
øn2to
ø1n7u8
ø1o
ø4pe7d6a
ø1pe
ø6pe5i
ø4p2e1nu
ø5p2ero
ø4p2eru
ø2pe1s4
ø4p2e3te
ø4p2e3ti
ø1pi
øp3li
øpl6
ø2p5p
øp6p1l6
ø1pr6
ø2p6s1
øpsa4
øpsl4a8ga
øpsl4
øps8leg
øp2s1le
øp5s1te
øpst2
øp4s5ø
ø1ra
ø2ra.
ø2r1af
ø2r1ak
ø2r1a2m
ø4ra6ns
ø4r3a6n1t
ø4r4a8sa
øra6si
ø4r7au
ør1d4e
ø4rd
ø1re
ø2r2ea
ør7e6d1d
ø6re1du
ø2re1f
ø2reg
ø6rein
ø2rek
øre1k6l4
ør3eks
ør5e6k1t
ørel2e8se
ø2r2e1le
ø9relet
ø4r5e6ng
ø2re5o6
ø2r2e1p
ø6rere4t
ø1rer
øre1re
ø4r2eru
øre1s2
ø5r2es.
ø4r2e3se
ø6re1si
ø7resk2ri1vi
ø2resk
øre1skr6
øre3s6kriv
ø8r4e1so
ø4rest
øre6s7v2
ør6et
ø4re1ta
ø2r1e1u
ø2re1v
ør5fe
ø2r1f
ørg6e5s
ø2r1g
ør1ge
ø1ri
øri8m
ør4jet
ør1j
ørj2e
ør4ke5ri
ø2r1k
ør1ke
ør4kes
ør8k9lag
ørkl4
ør6k5n2
ør4kve
ørkv2
ør4kå
ørl4a8ga
ø2r1l
ør2m9ut
ø2r1m
ør1mu
ør1n4e
ø2rn
ør4ne1re
ør4ne1s4
ør4n3u4
ør4n5ø
ør1o
ø5r6ok
ø1r2os
ø4r3ost
øro4v
ør4rek
ø2r1r
ørr6e
ør4r2e1p
ør3ri4
ør4r5is
ør4råt
ør4sak
ø4rs
ør1sa
ørsa8ka
ørsa6me.
ør5s2am
ørsa1me
ør4sc
ør6ses
ør1se
ør2si
ør5ski
ør4skr6
ørs6le1v
ørsl4
ørs1le
ør4som
ør1so
ør4s1pe
ør1sp2
ør2s2t
ørs9tar
ørs2t4e5i
ørs1te
ørs5te6n6s
ørs5t2ig
ørs1ti
ør3sto
ørs9u
ør2sv2
ørs1å
ør9tar
ø6r1t
ør4tek
ør1te
ør6ti9a
ør1ti
ør4ti1de
ør3tid
ør8tien.
ørti5en
ørt2i1e2
ør6t2if
ør3to
ør4um5
ø6rut
ø2r5v
ør1ø2s
ørø1v
ø2r1åp
ø1sa
øsa6me.
ø1s2am
øsa1me
ø8s2arar
ø1sa1ra
ø1sc
ø2se.
ø1se
ø2sem
øs2e4n5o
øs2e1ri7
øser2ie6
øs5j2o
ø1sj
ø2s7k2ar
ø1ska
øs1l4
øs4lag
ø4slu
øs2me
øs1m
ø1s2p2
ø1s8tas
ø4s5t9ast
øs6teg
øs1te
øs5ter1se
øste4rs
øst9e6tt
ø4stet
østo2
ø1s2t5of
øs2t5ov
øst1r4
øst3re
ø1stø4
ø2s1ø2
4øt
øt3ak
ø9tar
øta4s
ø4t5a1sa
ø2te.
ø1te
ø2t2ea
ø6te1f
ø6te1ge
ø2tei
ø2t5eks
ø2tel
ø5te1la.
ø2t2e3la
ø3ten
ø4te1ni
ø3ter.
øt2e3ru
ø2t4e1s
ø2te7s1n
ø4te1ta
øt7ri
øtr4
øt1sa
ø4ts
øt3s4p2
øt7s6å
øtså9re
øtså2r
øt4t2ero
ø6tt
øt1te
øt6test
øtt4es
øt4t5av
øt1ta
øtt5eks
øt2tek
øt4t5e1ta
øt4t5e3te
øtt5ra
øttr4
øtt7ri
øt6t1s2
øttså7re
øtts1å
øttså2r
øt4t2ur
øt4ty
ø1tu
ø5ty
ø1u2
øug8l2a
øu4gl
øv5aa
øv5a6ns
øve5in
ø4vek
øv2e5no
ø1ven
øveren8
øve1re
øv4e1s
øves4t
øv2e3te
øv4et
ø5v2ik
øv6l7ut
ø6v1l2
øv7ne
ø2v1n
øv6nø
øv9o8
ø2v1r8
øv5r2e1b2
øv1re
ø8v1s
øv9sa
ø5vå
øy3a2l
øy1a2n
7øy6an.
9øy8a6ns
øy4dek
øy1de
øy4de1re
øyd2er
øy1d8i
5øy2e1b2
ø4y1e
øy7e4de
øy2e3e2
øy4e1le
øye4n
øy4e1p
øy4e2r1f
øy1gl
øy3ke.
ø6yk
øy2ke
øyk1s4
øy6k5t
2øyl
øy4led
øy1le
øy4leg
øy5na
øy5ni
øy3o
6øyp
øy3pe.
øy1p8e
øy4p6el
øy4ra.
øy1ra
øy5rer.
øy1rer
øyri6v
øy1ri
øy3rø
ø2y1s
øy5s4ar
øy1sa
øy5s6i
øys2l4
øy4s1m
øy4spi
øysp2
5øysu6nd
øy1su
øy4tei
øy1te
øy4tel
øy4te1re
øy2t4e5s6
øyt2i7da
øy1ti
øy3tid
øy1tr4
øy2t3y6
øy5tø
øy4vei
øy1v
5øyvå
ø1ø2
ø5å6
å1a
åak6ta
åa6k1t
åa4n5
åan8ka.
åa2nk
åan1ka
åan6ke.
åan1ke
å2ar
å5aran
åa1ra
åau4re
å1b4
åba8ne.
å1b4a
åba1ne
åb4er2
å1be
åbo9ta
åb2o
å1b2ot
6å1d
å7dan
å1da
å9dar
å4ded
å1de
å4dek
å4dem
å4de3o6
å4d2e1p
å4de1re
åd2er
å4de1ri
å6d2ero
å2de1s
å7di
åd4ra
ådr4
åd8re
å8ds1
åd2sk
åd3s4la
åds1l4
åd7slo
åd3slå
åd1s2n
åd3s4pe
åds1p2
åd7s1pu
åd5st4
åd6s7te
4å1e
å2e1le
åem8na
åe2m1n
åe2n
å7e6ns
å3er
å3e6re.
åe1re
å1f
å1fe6
åfø5re
å1fø
åf8ør
2åg
å4ga.
å1ga
åg7a6ld
å4ge.
å1ge
å2g7ei
å2g6es
åg1na5
åg1n
åg5n4e
å1g4r4
åg5rin
ågs6k
å8gs1
åg5sl4
åg7sp2
ågs4t
ågu4l
å1gu
åg5ø
å4gå.
å1gå
å1h
å1i
å1j
å1ka
å3kan
å3kar
å4k1a2r1b8
å2k7av
å4ke5h
å1ke
å3ken
å4k2e1na
å5k6e4n1h2
å4ke1re
åke5s
å4kesl4
å5kevi
å2ke1v
å1ki
å6kid
å1kj2
å2k1k
åk5ka
åkk6l4
åk7kr6
åk7lau
åkl4
åklist7
åk1li
åk3læ1
å1ko
åk3o2pp
å1k2o4s
åk6ra
åkr6
åk5røk
åk3rå
åk1s2
åks7l4
å6k3t4
åku8
å5k8ul
åkv8a8la.
åkv2
åkøy8r4ar.
å1k6øyr
åkøy1ra
å2k3å
å1la
å5l6a8m1p
å6l1a2r1b8
å9las
ål5au8
å2l9av
å6l3d
å2le1f
å1le
ål5e4i1ni
å2lein
å2lek
å2lem
å4l5e4n1h2
å4le5ni
åle6ris
åle1ri
å2le1s2
åle9s6u6nd
åle1su
å4l2e1te
å2le1v
ål1gå6
å2lg
å6l4io
å1li
å2l5j
åll4a6ga
å2l1l
ål3or
ål4san
å8l1s
ål4sek
ål1se
ål4ser
ål1s7i
ål2sp2
ål4s1ti
ålsy8na
ål1sy
åls2yn
ål5ti
å2l1t
ål3ti5d
ål1u
ålul8la
ålu2l1l
å1ly
å2l1øy
å1lø
å4lø4y4e
ål5å6k4
ålå8te.
ålå1te
6åm
å6me.
å1me
åm4li.
å4m3l
åm1l6i
åm1om
å1mo
å2m5øy
å1mø
å3nar
å1na
å9nas
ånd3r4
å6nd
ånd4sa
ån8ds
ånd4se
ånd4s1o2
å4ne.
å1ne
åned4
å5ne8ds5
å2ne5i6
å2nel
å4n4e1ly6
å5n2es.
å3net
å4n2e1t4r4
å3ni
å1n5o
å9ny
å3o
å1p2e
åp2e4n3a
åpe4n5i
åpe6n3s
å5per
å6p2ero
å1pi
åp6j
å1pla
åpl6
åp4ne.
å2p1n4
åp1ne
å2p7p
å1pr6
åpra5
å2p2s1
åp6ta
å2p1t
å4på
år3ak
å9ran
å7ra1ra
ård5str4
å4rd
år8ds
årdst4
å2re.
å5rei
å6rel
åre7s
å4rest
år1fi4
å2r1f
å7riv
å2r3k2
år4le.
å2r1l
år1le
2å2rn
år6n5s
år3on
år3op
år1po8
å2r1p
å4r2s
års5af
år1sa
års3el
år1se
år3sem
års3ko
år8sku
årsl4a8ga
årsl4
år3sol
år1so
års3ta
år1s2t
år7s1te
års3ti
år7sto
år5stu
års1u
år1s1v2
år5sy
å4r5u4k
år3un
år5y4
å1rø
å5r8å
ås2
å1sa
ås7au
å4se.
å1se
å4se3e2
å2s5eid
å4sek
å4sem
åser2i7e6
ås2e1ri
åse3s2
å4ses1m
å5s4e1so
å2se5t6a
ås3h
å8si.
å1si
å2s5ka.
å1ska
ås9ke3ne
å2s1ke
å1s8ki
å1skj2
ås7kop
å1skr6
å5sku
å1sl4
ås5ne
ås1n
ås3ni
å5so
å1sp2
å2s7pl6
ås5sa
å4s1s
ås5se
ås3si
åss3k
4å1st
å7s2t4ei
ås1te
åsu4
ås3un
å2s7u4t1
ås7væ
åsv2
ås5øy
å1sø
åså9
å1ta
å4ta.
åtaks5
åta2l
åta9la
å3t2a1le3
åta5le.
åta5len
å4ta6ng
å4ta2r1k
å4t5a2r1m
å2te.
å1te
å2t2ea
å4te3e4
å2t1ei
åte6ke.
åte1ke
å4te3o6
å9ter
å2t4es
5åtfe4rd
å6t1f
åt1fe
å5ti
åt2i7da
å3tid
2å8t1n
å1to
å2t7ov
6å6t3p2
åt1re
åtr4
åtri6pa.
åt2rip
åtri1pa
åtri8pe.
åt4ri1pe
åt1ru
åt5sk
å4ts
å6t1t
åt4ted
åt1te
åt4ti
ått3o
åt4tri
åttr4
åt6ts4
åt6t5æ6
åt1u
åt4un
åtva5ra
å2t1v
åtv4a
å8t9var
åtva7re
åt5ve
å5ty
å2t1øv
å3u
åun8ge.
åu6ng
åun1ge
å1v
åv4a
å2ve7d1
å4veg
åve5l
å4ve1ni
å1ven
å9v8er.
å2ve1v
å8v7s6
å7vy1
åvæ4re.
å1væ
åvæ1re
å1ø8
åøy4
.a8t9t8e8n9d8e.
.at3ten
.atte6nd
.atten1de
.b8e9t8r8e.
.b2e1tr4
.b8etre
PK
!<& hyphenation/hyph_nl.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a4
.aan5
.a4a4
.aar4ts5
.aa4rt
.aa1t5
.a3b5l
.a1b
.acht5e4nd
.a4c2ht
.ach3t4en
.ach1te
.ac5re
.a1c4r2
.a1d4i5
.af3
.af5l2
.a4f5s4
.aftu5r2e
.a4ft
.af1tu
.aftu4r
.al3e4e4
.a1le
.al3f
.alk4
.al5ko
.al4k1o5v2
.al5ma
.a2lm
.al3om
.a1lo
.al4s2t
.a4ls
.ana3s2
.a1na
.a4n3d2
.a3n3e2n
.a1ne
.an3g4l4
.a2ng
.an5th
.a2nt
.a2r5d
.ar5t3r4
.a4rt
.as5h
.as5l4
.a2s3t
.as5tra
.a1s4tr4
.a1s3u
.at4a
.a1t
.ave5n
.a1v2
.av4e
.b4
.be3la
.b4e
.be5ra
.be5r4i
.b2o4s1
.b4o
.c4
.co4o5
.co3r2o
.cus5
.c2u
.d4
.daar5
.da4a4
.da4gi
.da4g5r4
.da2k
.dan2
.de3b4e4
.de1b
.de2k
.de1k5l4
.de2k5s
.den4k5r4
.de4n1k
.de5o3d
.de4o
.de3ro
.de5sta
.de2s
.de3s2t
.di4a
.di4e4p
.d4ie
.d4i3o
.doet3
.d4o4e
.do3v2
.du4w
.e4
.e1de2
.e1d
.ede1l5a
.e4d3w
.ee4n
.e4e
.eer5s1te
.ee2r
.ee4rs
.eer1s4t
.ees2t3
.ee2s3
.eesto4
.ee2t3
.ei3l
.e2i
.ei5sc
.e4is
.ei3s4p
.e4i5t2
.e4l4s5
.e2n5s
.en5t2h
.e2nt
.e1p4a
.ere5s
.e1re
.er2f
.er1f3l2
.er3in
.e3ri
.e4rt4
.er4ts3
.es3
.e1s5c
.es5pe
.e1sp
.e1s5tr4
.es2t
.et4en4
.e3te
.e1t4h
.ets5t4e.
.et4s1te
.e4ts
.ets2t
.eu3
.eus5
.é2
.f4
.fe4l4s
.g4
.g4aa1t5
.ga4a4
.gan4g5s
.ga2ng
.ge1a5v2
.g4e3a
.ge3l4a
.ge5l4e
.gelo5v2
.g4e3l4o
.ge3n4a
.ge2n4a5z
.ge5ne
.ge5no
.ge3ra
.ge5r4e
.ge5r4o
.gerst5a
.ge4rs
.ger1s4t
.ge3s4
.ge5s1k2
.ge5ta
.ge5t4j2
.ge5t4o
.gid4
.go4m
.goo4t3
.g2o4o2
.h2
.hande4l2s5
.ha4nd
.han3de
.her5in
.h4e
.he4r3i
.hit4s5t
.h4it
.hi4ts
.ho4lo
.hou2d5s
.hou1d
.i4
.ide5o
.i1de
.i2j4s
.i2j
.ijs5l4
.ijs3p
.ijs3t
.ik3
.in1
.i4n5d4
.i2n3g4
.in5gr4
.i4n1k2
.in5kr4
.in5kw
.i2n3s4
.in5s2l4
.in5s4t
.in5ta
.i2nt
.i4s5c
.j4
.jor5
.k4
.ka3d
.ka5g
.ka4ta4a4
.ka1t
.ker3k5l4
.ker4k5r4
.ker4k5u
.ker5st4en
.ke4rs
.ker1s4t
.ker3s1te
.ke4s
.koo4t5
.ko4o4
.ko5pe
.ko2p5l2
.k1o3v2
.k4u2n2
.l4
.laat5s1te
.la4a4
.laa1t
.laat2s2t
.laa4ts
.le4b5
.le4g3o
.l4e1g
.le4g3r4
.leid5s2t4
.le2i
.l4eid
.lei2d2s1
.le2n4s3
.le5r4
.le4s3
.le5t4h
.li4n5d
.lof5
.loo4t3
.lo4o2
.l2o4s1
.lu3e
.lui5t4j2
.lu4it
.lu2i
.lu4s
.m4
.ma5d
.ma5ï4
.mee4l5d
.me4e
.mee2l
.me5la
.m2el
.me5ni
.mer4k5l4
.m4e2s
.me4s2t
.met5e4e2
.me3te
.mij4n5i
.mi2j
.m4ijn
.moo4t3
.mo4o2
.mor5st4en
.mor4s4t
.mo4rs
.mor3s1te
.m2o4s
.n4
.naa1t5
.na4a4
.na3d
.na3n
.na3s4
.nee5s3
.ne4e4
.ne2p
.ne1p3a
.ne4s
.ne5te
.ne4t3j2
.neu4t5j2
.ne2u
.nie4t5j2
.n4ie
.noo4t5
.no4o2
.no2s5t
.n2os
.no5v2
.o4
.oe4r5
.o4e
.oe4s5
.oev4e4
.oe1v2
.ol3f
.om1
.om1me3
.o2m1m
.o1n3a
.o4n3d
.on5de4r
.on3de
.o1n1e
.o2n5g
.o1n3i
.o4n5k
.on1o
.o2n1o5v2
.o2n2t3
.on4te4e2
.on3te
.on4t2er
.on4t5s4
.ooi5tj2
.o4o2
.o4oi
.oo4it
.oot5je4s3
.oo4t
.oo4tj2
.op5e4e4
.o1pi5
.op5l2
.op3r4
.o4p5s
.or1g4
.os5
.ov4e4
.o2v2
.p4
.pee5tj2
.p4e4e4
.pee2t
.pe3ri5
.per4s5t4e.
.pe4rs
.per1s4t
.per3s1te
.pie4t5j2
.p4ie
.pit4s5t4e.
.p4it
.pit3s4te
.pi4ts
.pits2t
.poor4t5j2
.po4o2
.p4oo4rt
.po4s3t
.p2os
.pu4it4
.p2u
.pu2i
.pui5t1j2
.pu2t
.r4
.raads5le
.r2a4a4
.r4aad
.ra2a2ds
.raads4l4
.ra4n4d
.r4an1d5a
.re4men
.re1me
.ren4o
.re3n1o5v2
.re5o
.rie4t3
.r4ie
.r4ij5sp
.ri2j
.ri2js
.rin4g5s4
.r4i2ng
.roe5t4j2
.ro4e
.ro4l
.ro4s3t
.r2os
.ro4t3h
.ro5v2
.s4
.sa2p3
.sa5v2
.s3ci3
.s4e4e3
.sek1s5te
.sek4s4t
.se2ks
.se5r4e
.se4t3
.se5v2
.si1de3
.sk4i3s4
.s1k2
.s1k4i
.sne2u3
.s2n4
.s1ne
.sno2
.s2o2k3
.so2ng5
.spoor5tj2
.sp4o
.s2po4o2
.sp4oo4rt
.s2t4
.st4e4m
.s1te
.t4
.ta4ar4t5j2
.ta4a4
.taa4rt
.t4an4da
.ta4nd
.t4e4a
.t4e4f
.tek2
.te3le
.t4el
.te2n5ac
.t4en
.te1na
.te3no
.ten4t5j2
.te2nt
.te3ra
.t2er
.ter4p5a
.ter1p
.te4r5s
.te4s
.ti2n
.ti1n3a
.ti1n3e
.toe5p3r4
.t4o4e
.t2oep
.to4lo
.t2ol
.to4p
.to5v2
.tr4i3s4
.tr4
.ts4
.tsa3
.tui4t5j2
.tu4it
.tu2i
.ty2r
.ty1
.u4
.u2i2
.u4i5s
.u4it1
.uit4je
.ui4t1j2
.u1ke5
.u1r4a
.vaa2t5j2
.v2
.va4a4
.vaa1t
.ven4t5j2
.v4e
.ve2nt
.ve4r3
.ve1s5p
.ve2s3
.ve2t3j2
.vie4r
.v4ie
.v4o4l5s
.w4
.w8a8l8s9t8e.
.wa4ls
.wal1s2t
.wal3s1te
.wee4ko
.we4e
.wee2k
.wee4t3
.we4l3
.wen4s5t
.we2n
.we2ns
.we1s4t5r4
.we2s3
.wes2t
.wi2n4s
.x4e3
.y2
.z4
.ze4s5
.z4e
.z4i4t5
.z2i
.zo4oi5
.zo4o2
4a.
a4a4
4aad
aa1d1a
aa1d1o
aa1d1r4
aad5sa2p
a2a2ds
aad2s1a2
a4a1f5a
4aag
aa1g1a
aa1g3e
aa1g3o
aa2g5r4
aa4gs4
aag3s1a2
aag5s1o4
aag3s3p
aai3l
aa1k1a2
aa1k3e2
aa1k1o
aa2k5r4
aak3sp
a2a2ks
aa1l5a2
aa1l1e
aal5f4o
aalf3o5l
aa1l1i
aal5k
aa2l5m
aa1l1o2
aal3s2l4
aa4ls
aal5so
aal5s4pe
aal1sp
aal5s1te
aal1s2t
aa1l1u
aa1m1a
aa1m3o
aam4sta
aa2ms
aams4t
aam4s1te
aa1n1a
5aande4e
aa4nd
aan3de
aan1d4r4
aa1n1e2
aa2n5g
aa1n5i
3aan1j4
aa4n5k4
3aan3n
aa1n3o
aan3sp
a4a2ns
aans4p4o
aa2nt4
3aan3ta
3aa2n1v2
aa1p1a
a4a1p3i
aap3o2
a4ap3r4
aa1r3a
aar4d5as3
aar2da
aa2r1d
aa1r3e4
aa1r1i
4aa2r1n
aa1r1o2
aar5sp4el
aa4r2s
aar4sp
aar3spe
aar4t5on
a4ar3to
aa4rt
aart4s5l4
aar4ts
aa3r3u
a2a3s3e
aa1s3i
4aa2s2t
a4a1s5tr4
aat3a
aa1t
aa1t5e
aat3h
aa3t3i
aat1o
aat5r4
ab2a2k4s5
a1b
a3ba
a5b2ak
aba4l
aba4t4s
aba1t
ab5eun
a3b4e
ab3ij1z
a3b2i
abi2j
a2bon
ab4o
aboo4t4j2
abo4o2
aboo4t
abo4t4j2
2a3br4
ab3ru
4a4c.
a3cal
a1ca
a3car
4a1ce
ace3s2t
ace2s
4a4c4h.
a3cha
2a1ch4e
4a1chi
ac2h3l
a1cho
a3c2hr
4ac4hs
ach5t3ec
a4c2ht
ach1te
a1chu
ach4uut5
achu4u4
4ack
ac3k3l4
2a1c4l
2a3co
2a1c4r2
ac5res
4acta
a2c1t
4a1c2u
4a4d.
a5d4a.
a1da
ad3ac
ada2d
ada4l
ada2r3
a3das5
2a4d3d4
a5d4e.
a1de
a2d3e2i
ade5r2e
a5de2s
a3de3t4
a5de1ta
ad3e4te
2a2d1h
4a1d4i
adi3al
adi2a
adi4o1c
ad4i2o
adi4od
4a2d3k2
2a4d3l
4a3d4o.
a1do
a3do4o2
2a2d3p
ad3re2i
a1dr4
a2dre
a3d4ri
ad3rol
2a2ds
ad5se2
ad3s1o4
ad1s4t4
ad5sta
ad3u2i
a1du
a2d3w
2a1dy
4ae
ae1ge4
a4e1g
ae5k4
a3e2p
ae3r
ae2s3
ae4s5t
a3eu
a3ë
a4ër
4a1fa
af3a4a4
a2f3ac
af4as
af4a1t
af1d4i
a4fd
af1d2r4
af5d4w
4a1fe
afe4e4
4a1fi
af3l2
4a1fo
a5f4o.
a2fo4e
afo4n4d
a2f3o4p
af5or1g
af4or
af1r4
a4f3s4
af3s2c
af5se2
3afs2l4
3afs2p
af1t4a
a4ft
af5tr4
af3u2i
a1fu
2a3fy1
4a4g.
ag1a2d
a1ga
ag3af
ag3a2m
ag3ar
ag3di
a4gd
a5g4e.
a1ge
agee5t
a3ge4e4
4a5g4e4n.
age4r4s
a4g3ex
a4gil
a1gi
ag3i4nd
a4g3i2ns
agi5ot
ag4i1o
4ag1l4
a2g3of
a1go
a4g3or
a2g4o3v2
a2gr4
a3g4ra
ag5rap
ag3ru
ag3s3l4
a4gs
ag4s1le
ag5s4lu
ags3p
ag3spe
ag3s4pi
ag1s2t
ag3st2a
ag5s4tr4
2a4gt
agu5a
a1gu
a2g3u2i
ag3u4r
a2g3u4u4
2ah
4a1ha
4a5h4e
ahe5r3i
a1hi
a2h3l
a3ho
a2h5r
a2h5t2
a3hu
a3hy
ai5a2
ai4dr4
a4i1e
a1i2j
a4i5k
ai2l3m
ai2lo
a2in
a4i1o4
ai3o3v2
a4i3s4
ai5sc
ai4s5l4
ai5s3n4
ai1so
ai2s3t
ai5tj2
a4it
ai3tr4
a4i3u4
aïn4
a1ï
aï2ns5
aï3s3o4
2a1j
ajaa4r2s5
a1jaar
aja4a4
a1ka2
a2k3af
ak3ag
a4k3ar
a4k3e1d
a1ke
ak3e1mi
ak2e2t
a2k3i2d
a1ki
ak3i4n1k
ak5is
1ak1ko
a4k5k4
4a2k3l4
a2k3n4
ak5ne
ak4ni2
a3kof
a1ko
ak3on
ak3o2p
a2kr4
a3k5ru
2a2ks
ak4so4
ak5spe
ak1s4t
ak5to
a4kt
ak5t4w
a2k3u4
ak1w
ak3wi
a1la
a4l3ach
a2lac
a4l3a2dr4
a3l4ag
a3lal
a5l4apr4
al3a4rt
4a4ld
a1le
a5l4e.
a2l3e4f1f
2al4e1g
a2l3el
ale5r4o
ale5s1te
ale2s
ales3t
ale4tj2
a3lè1
al4fe4n
al1fe
alf3l2
al5fon
al4fo
al1fu4
al2gl4
a2l1g
a3l4ie
a1li
al3i2nt
al4k5e2i
al3ke
al5kle
al1k2l4
al2k3s
al4ku2i
al1ku
al5le
a2l1l
al4mac
a2lm
al1ma
al5me
a1lo
a4l3o2l
alo2n
al3ou
a4l3o4v2
2a4lp
al3s4a2g
a4ls
al3s3an
al3s1c4r2
al1sc
als5j2
al2s2l4
al2s5li
als5m
al4s2n4
al4s3o4o2
al4st4em
al1s2t
al3s1te
al5st4en
als5tou
al3sto
altaar5
a4l1t
alt4a4a4
al3t1ha
al4t3ro
al3tr4
alt4s2t
al4ts
a1lu
a2lu2i
a2l3u4it
al3u4r
alu2s5
4a4m.
a4m3ac
a1ma
am3a1dr4
ama4f
4am4ag
am3a4rt
5am4bt4
a4m1b
amen4t4j2
a1me
a3men
ame2nt
ame4ran
ame1ra
ame5tj2
a2meu
a1m4i
4a2m1m
a2m3o1li
a1mo
a2m3o4v2
3ampè1
a2m1p
am3p2l2
am4ple
am4s2m
a2ms
am4s3o4
am4spr4
am3s2p
ams5t4e.
ams4t
am1s1te
a2m3u2i
a1mu
a3nad
a1na
an3a2l1g
an4a3n
an3ar1c
2a2n1c
4an1da
a4nd
anda4d
and5a4n1k
an4d3e4d
an3de
an4dex
2an2d1j
an4dom
an1do
an5d4ri
an1dr4
and5ro4o2
ands5lo
an2ds
ands4l4
an4d3ul
an1du
a4nem
a1ne
a3ne2n
ane4n3i
4aner
an3es2t
a3nes
ane3us
ane2u
4an4g.
a2ng
an4gan
an1ga
an4ga5p
ange5s2t
an1ge
an3ge1s4
ang5le
ang4l4
an3gr4
ang5s1na
an4g2s
angs3n4
ang1s4te
angs2t
aniet3
a1ni
an4ie
ani2j4
3ani1ma
an5i3on
an4i3o
a4n5i4s3l4
a3n4is
an4i5t
4ani1v2
4an4k.
a4n1k
an4ka4a4
an1ka
anka4n
an4k3a4s
an2k3j
an4klu
an1k2l4
ank3of
an1ko
an2k3r4
a1no
an3och
a1noc
a4n3oor
ano4o2
an3ork
a1n2o3s
a1no3t4
a4n3ou
ano5v2
4a2ns
an3s3an
an2s3c4r2
an1sc
an4s4e4g
an3se
an4ser1v2
an4sid
an3si
an2so4
ans5or
ans4pi
ans5pir
an1s4t
an4s5t4e.
an3s1te
an5stru
an1s4tr4
an4tac
a2nt
an3ta
an5t4e4n
an3te
an3th
2an3ti
ant5s2l4
an4ts
an4t3w
4a1nu
a5nuf
a2n3u2i
a2n3u4r
a4n3u4u4
anze5s3
a2n1z
anz4e
2a1o
ao4g
ao2l
a4om
a2op2
ao4r5t
a3os
aos3p
ao2s5t
4a4p.
a1pa
a4pak
a4pa4s3
ap3a4s.
ap3a4s3s4
a1pe
ap5et4en
ape3te
4a1pi
apij4t5j2
api2j
a2p3ij1z
ap1j
2ap2l2
ap3le
ap3li
ap3lo
a1plu
apon5
ap3o4o2
apo3p
apo5s4ta
a1p2os
apo2s3t
ap3o4v2
1ap5pa
a4p3p
4appen
ap5pe
4apr4
ap3ra
a3pre
a4prem
a5p4r4is
ap3ru
ap2sa
a2ps
ap4si
ap2s3l4
ap3s2n4
ap4st4e.
ap1s4t
ap3s1te
2a4p1t
ap3t3j2
2a1p2u
a2q
4a4r.
a1ra
araa2t5j2
ar2a4a4
araa1t
a4r3a4p3p
ara3s4
ar2da
a2r1d
ard3ac
ard3ak
ar3do4
ar4d3om
ar4d3op
ar4d3o3v2
ar2d1r4
ar4dra
ar2d3re
ar4du
ar2d3w
a1re
5a2r4ea
a3r4e1g
a3rem
ar4en
are4no
are3s1p
a3re1v2
ar3g1h
ar1g
ar2gl4
a1ri
arie4tj2
ar4ie
ari2j3s
ari2j
a4r3i2ns
ark2
ar2k3ac
ar1ka
ar3k4l4
ar4map
ar1m
ar1ma
ar1m3u
a1ro
a2r3o3b
ar3o1ge
a3r2ok
aro4ko
a2r3oo4g
aro4o2
a2r1o2p
a3rot
arpi4
ar1p
a4r2s
ar5s4ch2
ar1sc
ar3s2c4r2
ar3s2e
ar5s4e4e
ar3si
ar1s3l4
ar4sla
ars5m
ar3s1ni
ar1s2n4
ar4so
ar4sp
ar5sp4o
ars3ta
ar1s4t
ars5t2al
ar4s5tek
ar3s1te
ar4s4tr4
ar4su
art4a4a4
a4rt
ar1ta
ar4t3ak
ar4tan
art5a4n1k
ar4tap
ar3tar
4ar1te
ar4te2i
ar2th
ar5t4i2j
ar3ti
4ar4tj2
art5je4s5v2
artje4s3
4ar3to
ar5to2f
art5o4ge
art5oo4g
arto4o2
ar4t3o4v2
ar2t3r4
ar4tro
ar2t5ru
art4s3l4
ar4ts
art5s1te
arts2t
a3ru
ar3u2i
4ar1w
arwe3s3
a1ry
4asa
as3ad
as4ag
a2s3ak
as1a2p
a2sc
a4s5ce
2a3se
a4sec
a4s4e1g
ase1r5a
ase5tj2
ase4t
asev4e4
ase1v2
as5ha
as1h
as4i4s1
a1si
a4sj2
as5ja
as3ji
a2s3k2
as5ka
as5k4i
as3l4
as4lu
as3m
as5mi
as3n4
as4ne
as4ni
4aso
as3o3b
as2o2l
aso4r
as1p
a2s3p4l2
a4s5q
as5sa
a4s3s4
4ass1m
3as5su
a2s2t
4as3ta
a4st4a.
as5tag
a2s4tas
as4ta1t
as3te
a3stek
a3st4em
as5t4en
as3tè3
asting5s3p
as3ti
asti2ng
astin4g2s
as1to
a2s3t2o3b
as4t3op
4a1s4tr4
ast5rem
as5tr4o.
a1s4tu
a1t
ata4ar4t5j2
ata4a4
ataa4rt
a2t1ac
at3a4de
a4tad
a2t3a4f.
at3a4n1k
a3ta3s
2atek
a1te
a5te2l1l
at4el
at4e2n
ate3no
aten4t5r4
a3te2nt
ate4r5ad
at2er
ater5s2l4
ate4rs
at4eu
2a4t3g2
a2t3hu
ati5ni
a3ti
a2t3j2
at4je
atje4s5
at5je4s3b
at5jes1h
at5jes5m
at5je1sp
2a4t3m
2a4t3n
a2to4o2
at3oo4g
ato2s5f
a3t2os4
ato3s3t
at3rac
atr4
at3re2i
at3ri1b
at4ro4e
at5ru
at4s3a2
a4ts
at4s3ec
at3se
at1si4
at4s3id
at2s3l4
at4slo
ats5m
ats3n4
at4s1ne
ats3pr4
at2s2t
at4sta4a4
at3sta
at4s5tak
at4st4e.
at3s1te
at5st4en
at5sti2j
at1s4ti
ats5t2ol
at1sto
ats5t4o4p.
ats4top
ats5trek
at1s4tr4
at4t3u4
a4t3t
a2t3u2i
a4t3w
au1a4
au3ch
a4uc
au3co
au5de
au1d
au2d4j
1aug
au3na
au2n3t
a4up2
aur4
au5r2e
aure3u
4aus
au3so2
au4s5p
au3sto
aus2t
au3t4
4au4t.
1au1to
auto3p
2au4ts3
auw3a
4a4u1z
a4ü
ava2s2t4
a1v2
ave3c
av4e
ave4e4
ave4n3i
aven5s2p
ave2ns
aver3a
ave2r
ave3re
ave3r4u
4a3vi
a2vo
1a4von
a5vo4o2
a5vor
4avy3
2a1w
ax4is4
ay2a
4a3z4if
a1z
az2i
ä3h
äm1me3
ä2m1m
ä3r
1b
4b.
3ba
baar5s1te
ba4a4
baa4r2s
baar1s4t
ba4ar5tj2
baa4rt
ba4da
bad3a2r3
ba4d3r4
b2a2d3s
ba3g4h
b4a3g1l4
5b2ak
ba4k3o4
bak4sp
b2a2ks
ba3l4an
ba1la
ba4lar
bal3d1w
b4a4ld
ba1le4
bal3e1v2
ba3li3ë
ba1li
bal4k2l4
ba3lo
ba4ls4
bal3s1f
ba4me
ba5n2a
ban4k3a
ba4n1k
ban4k2l4
ban4k3o
ban4k3r4
ban4k3w
b4a3sa
ba4s2t
ba2tr4
ba1t
ba3tro
4b1b
bbe4l5ag
b3b4e
bbe1la
bbe4l5e4e4
bbe3le
bbe2n
bbe1n3a
4b1c
4b1d4
b5de
bdi5a
b1di
3b4e
b4e1a
be3as
be2au
be3ch
be5d1we
be1d
be4d2w
be5d1wi
be5d1wo
be4e4
bee2t1
b4e5g
be4i1e4
be2i
be4i3s
bei5tj2
b4e4it2
be5ki
be3k4l4
be1kw
be3lar
be1la
be5l4as
bel5dr4
be4ld
be3le
be4l3ec
be4lex
bel5f
b2e3li
be4l5i2nt
bel3k
b4e1l4o
be3lo5v2
bel3sc
be4ls
bel3sp
be4l1t4
beme2n4s
be1me
be3men
be3ne2p
be1ne
be5n4o
be5ot
be3o
be1ra
bere5s4
be1re
ber4g5af
ber1g
ber1ga
ber4g5et
ber1ge
ber4g2l4
ber4gr4
be3r4i
be1r4o
ber1o5v2
be3ru
be3ry
be1s4
be2s5ac
be4s1h
be4s1je
besj2
be3so
be5sp
be4s5s4
be4s5t4e.
bes2t
bes1te
be4s5te4n.
bes3t4en
be5st4ie
bes3ti
bet2
be3t4h
be5ton
be1to
bet5ren
be1tr4
be4t1w
be5t2wi
be3u4nd
beu4r4s
4b3f
2b1g
4b3h
3b2i
bi2d3s1
bi2du
bi2e4li
b4ie
bi4en
bie4t3j2
b4ij5d
bi2j
bij3f
bij3g4
bi2j5k4
bij1p
bi2j1s2
bi1k4a
b4ik
5bil
bi3lo
bi4l3s2
bin4dr4
bi4nd
bin4s4t
bi2ns
bin4t3j2
bi2nt
1bi5o5b
b4i1o
bi3ok
bi5om
bi3o5s2o
bi2o4s
bi5ow
bir3
bi4s3t
b4is
bis5tro4o2
bi3s4tr4
bi1tr4
b4it
bit4se
bi4ts
bit4s3p
4b1j
4b1k
3b4l
blad5i2j
bl4a1d4i
2b5lap
b5le1d
ble2s3
ble5spe
ble4sp
ble2t3
b5lid
b1li
b3li2j2s4
bli2j
blij5s3te
bl4ijs2t
bl4i2k
4b5l4oi
blo2k5l4
bl2ok
bloo4t5j2
blo4o2
bloo4t
blu2s
2b1m
4b1n
b4o
bo4d3ec
bo1de
bo1dy3
boe4g3a
bo4e
bo4e1g
boe4k2n4
boe4ko
boes4
boe3s2t
boet5s2t
boe4ts
bo3f4l2
b2o2k
bok3an
bo1ka
bokje5
bok1j
bok4s4t4
bo2ks
bolk4
b4o2m3a4
b4o2m3o
bo5na
bo4nd2
bon2d4s5
3bo1ne
bo3no
bon4t3j2
bo2nt
bon4t5o4
boo4t3j2
bo4o2
boo4t
boot4s5t4e.
boo4ts
boot3s1te
boots2t
bo3p2
bor4sta
bo4rs
bor1s4t
bor3st5o
bor4s4t5r4
b2o4s
bos3a
bo5s1co
b4o3s2c
bo5si
bo5s2o
bos5p
bos5to
bo2s3t
bo4t3j2
bo4to
b2ot3r4
bot4sp
bo4ts
bot4s2t
bo2tu
bou5ta
bou2w5s
bo3v2
bov4e4
4b1p
3br4
bra2a2d5s
br2a4a4
br4aad
br4an4da
bra4nd
br4a5s4tr4
bra2s2t
bre4i5s4
bre2i
brie4t
br4ie
brie5tj4e.
brie3tj2
bri4l
bro2n
br2o1n3o4
bru2l
4b1s4
b2s5a
b5sc
b3si
bsi3d
bs5je
bsj2
b2s5la
bs2l4
b2s5m
b4s5s4
b4sti2j
bs2t
bs3ti
4bt4
b3ta
b1tr4
b4ts5
3b4u
bui4t4j2
bu2i
bu4it
bul4k
bu4lu
bune5t
bu1ne
1b5ur1b
bu5ri
bu4s5c
bus3o2
bu1t4a
bu2t3j2
bu2to
bu4t4s
but3s5te
but2s2t
buur4tj2
bu4u4
buu4rt
4b1v2
2b3w
by3
4b1z
4c.
1ca
3c4a.
ca3b
ca1ch
5ca1da
ca3do
ca3dr4
c4ae3
ca3g2
ca2l4l3
ca3lo
came5r
ca1me
ca3na
ca2nt4
ca2of
c2a1o
c4a1pr4
ca4p3ra
ca5pri
ca3ra
ca3r4u
c2a5se
ca3s2p
ca2s3t
c4a1s5tr4
ca3ta
ca1t
cat4e4n
ca1te
ca3t4h
cau3
cau4s2t
c4aus
ca3v2
2c1b
4c1c
c1ca3
cce2s5
c1ce
c4d
c5do
1ce
3ce1d
ce4e4
3cee2l
3cel
ce4l3d
ce3le2s5
ce1le
c2e5li
cel5k
c4e4l3o
2ce3n4a
2ce1ne
ce3no
5ce2nt
cen4t3j2
ce3o4
ce3ra
ce2r2n
ce5ro
cer4t3r4
ce4rt
ce2s
ce3s2a
ce5sc
ce3s2h
ce3sta
ces2t
ce3s4ti
cesu5r
ce3su
ce3ta
ce4t3j2
ce1to4
cet3og
cet3o4o2
1cé
c3g
4c4h.
3cha1ï
5ch2a1o
3chas
1chau
5chauf
2chc
1che2f
ch4e
5che4f.
5che4f1s
5che1mi
5che1q
che5r3i
che3ru
5ches
che3us
1ché
5c2hir2
4ch1n
2c2h1p
5chr4o3mo
c2hr
4c2ht
4c4h1w
1chy
3ci
ci5a1b
ci1a
ci3am
ci2e3k
c4ie
cie4r4s5
ci1e2u
5ci2j
5cil
c4i5le
ci2l3m
4ci4nd
c4i3o
ci5om
5cir
c4i3t2
ci5ta
c3j
c2k3a
c4k3e1d
c1ke
c2k3ef
cke5re
c5k4et
c2k3i2d
c1ki
c2k3l4
ck4le
c2k3n4
c2k3o4
c4k3r4
ck5s2e2
c2ks
ck3so4
ck5s4t
c3ky3
1c4l
cl4a2n
cle3u
5clu
2c1n
1co
co3ad
c4oa
co3d
c4o4i
coin5
c2o3k4
co3la
5com
5co4nd
co2n1g
2co1no
5co2ns
3co2n5t4
2co4o2
2co1p2
3c2opa
4co1pi
cor4dr4
co2r1d
co4rel
co1re
co5ri
cor2o
5co2r5r
co4rs4
co3r1u
c4o5s2c
c2os
co5se
co5sp
co3th
c2o3tr4
5coun
2cout
co5v2
c3p4
1c4r2
3cras
cre5d
2crip
3cr4is
cro5f
cr2o5k
cro4o3
cro5v2
cr2u4s5
c3so
c3sp
c3s1te
cs2t
2c1t
2c1t3a2c1t
c2tac
c4t3ad
c4t5c
ctee5t
c1te
cte4e2
ct4e2n3
c2t1h
c2t3j2
c4t3o2f
c3t2ol
c2t1on
ct4or
ct3rap
ctr4
c4t3re
ct3s2l4
c4ts
ct3sp
1c2u
cu5d4
cu3en
cu3és
c4u4i5s
cu2i
cu4i2t
cui1t5e
c2u3k4
cula5p
cu1la
cu3ra
5cu4r3s
cus3o2
c3w
1cy
1ç
ç2a4o
4d.
1da
3d4a.
3d4aag
da4a4
d4aal
d3aap
daa1r5e4
5daa1t
4dab4o
da1b
2d3a4c1c
d4a4ce
da5den
da1de
4da1dr4
3d4ae
2d1af
3dag
da2g3a4
da3ge
da4g3e3d4
da4g3e4t
da4g3on
da1go
da4g3r4
dag4s3t
da4gs
da2gu
3dai
da3ï
da3ke
da4ker
2da4k5k4
da4k1r4
4da1la
d3alar
d3a2l1c
da3le
4dalf
da3li
2da2lm
da2l3u
d4am
da1m4a
da5m3ac
d3a4ma1t
d2a5me4
dam4es3
dam4p2l2
da2m1p
2da2na
dan3as2
dan1k3l4
da4n1k
da3noo4t5
da1no
dano4o2
dan4si
d4a2ns
dan4s3m
dan4s3p
dan4s4t
dan1s5ta
4d3an4t3w
da2nt
2d1ap
4d3a2pe
5d2a1p2u
da2r3a
d3ar1b
3da1re
3da1ri
dar4mo
dar1m
darm5on
3da1ro
da4r3s
dar5s4t
3das3
5d4asa
da3s4tu
da2s2t
3d4a1t
da3ta
da2t5j2
4d5a4t3l
4d5a4t3m
da2t3r4
5daue
4d1au3t4
3dauw
2d1b
dbe2i5
d3b4e
dbou4w5i
db4o
2d5c
4d3d4
dda4gs4
d1da
d3dag
ddag5s3p
dde4l5e1v2
d1de
dde1le
dde2n
dde1n5a
ddera4
dder5al
dde1r2e4
dder5e4e
dde4r5ep
dder3o
ddi3a
d1di
d5dle2s
d4d3l
d5do
d2do3p
1de
3d4e.
de2al
d4ea
de1ch
d4e5den
de1d
de1de
5d4edir
de1di
de4d4it
de4e4g3
de4e
dee4l
de2e3l3i
4d3ee2n
dee4r
4d3e4f1f
d4e3g
4d5e4g.
4d5e4g3g4
2d5e1gy
2de2i
d3e4i1e
d3eig
d3eil
d1e4is
d3ei5w
5dek
de3ke
dek3lu
de1k2l4
dek3w
del4a4a4
de1la
del5da
de4ld
del5dr4
del5ee2k
de1le
dele4e4
4d3e4lek
4d3e2lem
de4le1v2
4d3e4l4it
d2e1li
del3k
de4l2s
del4s3e
del3s3i
del4so
4d3e4mai
d2e1ma
2de2m1h
5de1mi
dem5o4nd
de1mo
d2e4n.
de2n4ac
de1na
den5a1te
de3na1t
de4n3e2i
de1ne
de2n3e4p
den3e1v2
4d3en4gt
de2ng
den4k5of
de4n1k
den1ko
de4noc
den3o4r
den3s1h
de2ns
den5s4tr4
dens4t
de3nu
5den3vl
de2n1v2
de4o
de5o1fo
de5ol
deo4li
deo3v2
de3ra1b
de4r3ad
der3a4g
de3rak
de3ram
de3ran
de3rap
de3ras
de4r5a4s.
de4r5a4s3s4
de1r2e
1der5e1de
dere1d
de4r5e4gd
de1r4e1g
de4r3e2i
de4r3em
de5re4n
de4rep
de4ret
de5ri2j
de3ri
de4r3im
der3k4
der3on
de2r3o4r
4d3er2os
der4s3a
de4rs
der4s5om
der1so
der5s1te
der1s4t
der5sto
der5stra
der3s4tr4
der5t2h
de4rt
4d3er4ts
der5t4w
de2r3u
de3r4up
de2s
de3sa1v2
des3m
de1s3n4
de1s3p
de3spe
de5s4p4el
de4s1p4l2
des5s1m
de4s3s4
de3s2t
de3s5tak
de5s4t2al
de4s3te
de4s3ti
de5s4t4ic
des5top
de3t4
4d3e4tap
de1ta
de5t1w
deu4r3o4
de3u4s.
deu4tj2
dev4e4
de1v2
2dex
4d1exa
4d1ex1p
3dè1
2d1f
2d3g
d4gaf
d1ga
dge3la
d1ge
dge2t
dge1t4o4
dget5on
dget5o3v2
dge4tr4
dg4l4
2d1h
d5h4e
dhee2r4
dhe4e
3d4h4i.
1di
di2a
di5ae
di4ak
di4a1n4o
di3an
dia3s4
di4atr4
di3a1t
5dich
d4ic
di4do
die2f
d4ie
die4r3o
di3e4s5r
die3s2t
die2t
die1t3r4
di1e2u
3dig
di2g4a
dig5a4a4
di3ge2s5
di3ge
dij2k3r4
di2j
di2jk
di3j3o4
2d3ij1z
di2k3o4
d4ik
5dil
2d3i2m1p
di5n2a
2d3i4nd
2d1i2n3f
3d4in4g.
di2ng
4d5ingel
din1ge
4d3in1j4
4d3in1ko
di4n1k
2d5i2n3r
2d3i2ns
4d3i2nt
dint4el5
din5te
2d3i2n1v2
2d3i2n1w
2d3i2n1z
d4i2o
di5ofon
dio3f
di4o1fo
di4ol
di4o1ne
di3on
di4o1ni
di2o4s
di4o5s3c
2d3i2ro
2d3i2r5r
3d4i4s
dis5a2g
di1sa
di5se
di5si
dis4kr4
di4s1k2
dis5p
di2s3t
di3s5tr4
di3th
d4it
di4t3j2
dit3r4
5di1v2
2d1j
2d3k2
4d3l
d5l4e.
dli4n
d1li
dlo4t4s
2d1m
2d3n2
d5ne
d3n4i3s
d1ni
1do
3d4o.
d4o3a
2d3o4b1j
d2o3b
4d3o4b1s4
3d4o4e
5do4e.
doe5d
4doef
d5oe1fe
5doek
5doen
5doet
4d5oe1v2
3d4oi
d4o1le
2do2li
d4o3lin
dol2k5s
5d4o4l5s
3d4o4m.
5do1m2i
d4o4m3o4
d3o2m1r
dom4s2n4
do2ms
5do1mu
d3o2m1v2
4d3o2m1z
5do4n.
d4o1na
5do1ne
do5ni
5d4on3n
5do3n4o
do3nu
do5ny
5do2n1z
2dop
d2o3pa
d3o2p1b4
d3o2p3d2
do3p4e4e4
5d4op1j
4d1op3l2
3do1po
d3o4ps
d3o2p5z
4d5or1g
d3o4ri3ë
do1ri
d3ork
dors5m
do4rs
do3sp
d2os
do3s4ta
do2s3t
do4t3j2
5dou
2do2v2
dove4r5s
dov4e
dove2r
3d4o3vl
3d4o3vo
2d3p
dpren4
dpr4
1dr4
3dra
5dr4a.
d3raam
dr2a4a4
d3raap
d4rac
d5r4a3ce
5drach
d3r4a4d.
d3ra1d4a
5draf
5d4rag
d4ra1ma
d3ra1me
4d3ra4nd
4drap
4dras
4d3ra1z
2dre
4d1rec
d5re3co
d1re1d
d2re4e
4d3ree4k
4dr4e4nd
d4re4s3s4
4dret
3d2re1v2
5drev4e
d3r4ic
dries4
dr4ie
5d2r4if
dri5g4a
dr4ig
d3r4ijd
dri2j
d3ri2jk
d3rijm
d3ri2js
5d4rin
3dr4is
4d3r4it
4d3roe2i
dro4e
d3r4oer
5d2rog
4d3r2ok
d3r4o5ma
d3ro4nd
3d2roo4g
dro4o2
4dro2o4s
5drop
2drou
2d3ro5v2
2dro1z
dru4g4s
dru2g
d3r4uim
dru2i
d3ru4it
5d4r2u4k
4d3r2u4s
2ds
d2s1a2
d4s1a4a4
d1sa4b
d3s2al
d3s4a3te
d1sa1t
d3s4ch2
d1sc
d5s2chi
d3se2
ds3e1co
d4s3e1d
d4s5e4e
d4sef
d4se2i
d2s3e4is
d4s3elf
ds2el
ds2e4li
d5s4en
d4s4es
d4se4t
d2s1h
ds3ho
d2s1i2
d4s5id
dsi1g5a
d5s2ig
ds2im
ds4i2ng4
ds5i4s
d4s3j2
ds4jo
d4s5j4on
ds4l4
d1sla
d4s5las
d4s5l4ic
ds1li
d4s5l4ie
ds5li2m
d3slin
d2s1m
ds4mak
ds1ma
d3s4mi2j
ds1mi
ds5mo
ds3n4
ds4ne
ds5ne2u
d3snu
ds1o4
ds3o3b
ds3om
d4son
d3s2o4o2
d2s3op
d4sp4a
d5s4pan
ds5pa3ti
dspa1t
d5spec
d5s4p4el
d4s3pet
d1spi
d4s3p4l2
d5s2po4e
dsp4o
d5sp2ok
d5spor
d4s5s4
ds2t4
d1sta
d5s4taa1t
dsta4a4
d4sta1b
ds4tak
d4s3t2al
ds4tan
d3s4ta1t
d5s4ta1v2
d3s1te
ds4t4e.
d5ste4e2
d4stek
ds4t2er
d4ste2r5r
d4st4e1v2
d4s3th
d3s4ti
d4st4it
d1sto
ds5tram
d1s4tr4
ds5tre4k5k4
d2s5ty1
d2su4
ds3u1r2e
ds3u4u4
d1s4y
2dt
d1ta
dta4ar4t5j2
dta4a4
dtaa4rt
d1th
d2tj2
d1to
d1tr4
d1tu
1du
2du1ca
d4uc
5due
du3en
du3et
5duid
du2i
5du4if
5d4u4ik
d3uil
2du4it
4dui4t.
d3ui4t3d2
5dui1te
4d1ui4t3g2
d3ui4t1v2
5dui3v2
du4n
du1n5i
du2o
du4ol
3d4urf
3d4ur1v2
5du1s
du2t3j2
du5we2n
du1we
2d1v2
dvaa1t5
dva4a4
dve4e3
dv4e
dve5na
dvie4s5
d3vi
dv4ie
2dw
d3wac
d3was
d3wa1t
d1we
3d2we2i
d3wek
d3wet
d3we1z
d1wi
4d1wo
d3wor
d3wr
1dy
4d3y1o
dy4s4p
dys3
dy2s4t
2d1z
4e.
4ea
e3a4a4
e1a1b
ea3b4o
e3ac
ea4ca
ea2c5t
e1ad
ea3da
e5adem
ea1de
ea3do
e2a2d3s2
ead5s1h
e1af
e1ag
e3ai
ea4k3o4
e1al
ea3la
e3a1li
e4a4ls
ea5m4i
e3an
e4a4n.
ea2ng3
e4a2n4s
e5ap
e4a3pr4
e3a2q
e1ar
ear2c
e1as
e2a2sc
e2a5s4e
ease5t
e4a3so
e1a1t
e4a4t.
ea4t3s
eau3s4t
e4aus
e1a1v2
e3b4o
e1b
ebot4s5t4e.
ebot4s2t
ebo4ts
ebot3s1te
e5br4
3ec4d
e3ce
e1ch4e
e1chi
ech4t5ec
e4c2ht
ech1te
echt4s5o
ech4ts
e3chu
4eck
ec5le
e1c4l
4ecor
e1co
4e2c1t
ec3ta
ec4ta4a4
3ec1z
e1d
e3d4ag
e1da
e3d4am
e3d4an
e4d4as3
ed4e3a
e1de
ed3e4i.
e2de2i
ede5le
edem4
ede5n4ac
ede1na
ede5o
ed4er
e4d5er2ns4
ede2r1n
ede5ro2g
edi3al
e1di
edi2a
edi3am
e5d4ie
4edir
edoe5tj2
e1do
e3d4o4e
e5doet
e3d4o4o2
ed3o2p1v2
e2dop
edor3s5te
edo4rs
edor1s4t
e2d3o2v2
e3d2r4
ed3rod
ed3rol
e2d1s
ed5se2
ed2s4l4
ed4s1o4
ed5sp
ed3su4
e2d3u4it
e1du
edu2i
e4d2w
e5dwan
e4e
e4ea4
ee5b
ee5ca
ee5ch4e
ee2d3a
ee1d
eed4ac
ee4d5as3
ee5de
ee5do
eed3ru
ee3d2r4
eed3s1i2
ee2d1s
ee4d3w
ee2f
ee3fa
eef3ac
e4e3fi
ee1f3l2
ee1f3r4
ee4gap
e4e1g
ee1ga
ee2g3l4
ee3i
ee2k
ee3ka
ee5ka4a4
eek3ak
eek5a2l1l
ee1k1e
ee5k2et
ee3ki
ee3k2l4
ee4k3lo
ee1k3n4
eek3re
ee1kr4
ee3kri
eek3ro
eek5s4t
ee2ks
eek3w
ee2l
ee1l3a
ee3lad
ee4l4a4s.
eel5d4u
ee4ld
ee3le
e4el4e4e4
e2e3li
ee5li2j
eel5k4
ee3l2o3b
e4e1lo
eel3og4
eelo4ge
e4e3lu4
eel3ur
eel3u4u4
4eem
e2e1ma4
ee2n
ee1n3a
eena4r
ee1n3e2
ee2n5g
ee3ni
ee3n5ie
ee4n5k
ee5o2
ee2pa
eep3an
ee3p2l2
ee1po4
ee4p3re
ee1pr4
eep3ru
ee2r
eer1a
ee1r3a4a4
ee4rad
eera4l
ee3ram
ee3ran
ee3re
e4e4re4e
ee5re2i
ee4r3i
ee5r4ic
eer5k
eer3o2g
eer5oom
eero4o2
ee3rot
eer5ston
ee4rs
eer1s4t
eer3sto
eer5s4tr4
ee2s3
ee5s4ch2
ee1sc
ee4s5em
ee3s4e
ees5e4t
ee3sj2
ees5lo
ee3s2l4
ee3s2n4
ee3s4p
ee3s5p4l2
ees5pot
eesp4o
ees5t4en
ees2t
ees1te
ee3stu
ee2t
eet5a4a4
ee1ta
ee3t2al
ee3tan
ee5te
ee1t5h
ee3tj2
eet1na4
ee4t3n
ee3to
eet3og
eeto4ge
eet3o4o2
eeto4r
ee3tr4
ee4tro
eet5r2ok
eet3sp
ee4ts
eet5s1te
eets2t
ee5v2
ee5z
e5ën3
e3ë
e5ër
e2f3ad
e1fa
efa4z
efde5l
e4fd
ef1de
ef3do
ef3e2i
e1fe
e5f4er
4e1fi
efie4t
e5f4ie
efie3t5j2
ef3i2ns
e3f4i4s5
e1f2l2
ef3li
ef3lo4o2
e3flu
ef3om
e1fo
e3fo4o4
e2f3o4p
e1fr4
ef3ri2j
e5fron
ef3s1f
e4f1s
4e1g
ega4s4
e1ga
eg3a4s.
ega5s3k2
e2g3e4b1b
e1ge
ege1b
e4ge4c
e4g3eig
ege2i
egel5e4i.
ege3l4e
egele2i
ege4l5o3v2
e5g4e3l4o
ege4net
e3ge3ne
ege4n5of
ege1no
ege4ra
eger5e2ng
ege1re
ege4r2o
eger5on
e3g4i
e4g3ij1z
e2gi2j
egip4
egis1te4
e3g4is
egi2s3t
e2gl4
e4g4o.
e1go
eg3or1g
e2g2os1
eg3ou1d
e5graf
e1gr4
e3gra
e4g3s4
eg5s1le
egs3l4
eg5s1o4
e2g3u4r
e1gu
egut4
e4g3u4u4
e1h4
e5ha
ehe4is5
eh4e
ehe4i
eh4it4
e2i
ei5a
4eid
ei3do
eid4sc
ei2d2s1
e4i1e
4eien
eie2n5s
eie5re
e4i3f4
ei3gl4
4ei2g1n
e3i2j
ei3k4l4
e4ik
ei3k4n4
ei5kr4
ei2ks4
4ei4l.
eil5a2nt
ei1la
eil4an
4ei4ld4
eil5d3r4
4e4i3le
ei4le1v2
ei2l5m
ei2l3o
ei4n3a1b
ei1na
ei3n4ac
ein4do
ei4nd
eind5o4o2
ein4d3r4
ein5gr4
ei2ng
ei4n5k
ei2no
ein5s2l4
ei2ns
e4i3o
ei2sa
e4is
ei5sha
ei4s1h
ei3s4la
eis3l4
ei3slo
eis4p
ei3s4ta
ei2s3t
4e4it2
ei4to4o2
ei1to
ei4t4s3
eit1s5c
eits5n4
eit4s5t4e.
eit3s4te
eits2t
eit5st4en
eit1s5tr4
ei3v4e4
ei1v2
4e4i1z
e1j2
e3je
ek3aan
e1ka
eka4a4
eka4ar4t5j2
e5kaa4rt
ekaa1t4
ek3a4f.
e2kaf
e4k3a4g
ek3a4l.
e4k3a4l1t
e5kam
e4k3a2ng
ek4e4e
e1ke
ek1e2i
e3kem
e5ke4r.
e5ke4rs
eke2s3
ekes4t
eke1s5tr4
e3k2et
ek5et2er
eke3te
e5k4ic
e1ki
e4kil
e5k4is
ekla4m
e1k2l4
ekla1m5a
ek3le1v2
e5k4lim
ek1li
ek5lo2o4s
eklo4o2
ek4ni2
e1k2n4
e3ko
e4k3o3b
e5kof
ek3o1li
e4k3o2p5z
e5kor
e4k5o4s.
e3k4o4s3
e4k5o4s3s4
e5kran
e1kr4
e3kra
ek3ro1z
ek3s4e2
e2ks
ek4s5er1v2
ek5s3e4t
ek4s4tr4
eks4t
eks5tra
ek5t4e
e4kt
ek3to
e1ku4
e2k3u4it
eku2i
ek3u2r
ek1u4u4
ekwet5s1te
e5kwe4ts
ekwet4s2t
e2k3win
e1la
e2l3aan
ela4a4
e4l5aa4nd
e2l1ac
el4a1de
e2l3a2d1j
e4l3a2d1m
e4l3a2dr4
e4l3a2d1v2
e2l1a4f
e2l1al
e3l4an
e4l5a2na
e3lap
e5l4a4p.
e4la4p3p
el3ar1b
el3ar1c
e2l3ar1m
el3a4rt
e4l3a4s.
el3a2si
e4l3as1p
e4l3a4s3s4
e2l1au
e4lau3t4
e3l4a1z
e4l5az2i
el4dec
e4ld
el1de
el4dr4
el4du
e1le
e3l4e.
e2l3eeu
ele4e4
e2l5e4f1f
e5l4eid
ele2i
e4l5eier
ele4i1e
e4l3eig
el3e4i5s
e4lel
3e2lem
el3e2m1p
e5l4en
e3ler
e3le5r4a
ele4r4s
el3er1v2
e3le2s
eles4t
e4l3e1ta
ele4t3r4
e4l3e2t3u
e4l3ex1c
e3lé
elfi4d
el1fi
el1f2l2
el4f3s4
el3gu
e2l1g
2e1li
e5l4ie
e5l4ig
eli5k1w
el4ik
e2l3i2m1p
e4l3i4nd
e3li2ng
e4l5in4kt
eli4n1k
e4l5i2n1z
3e2lix
el4ke4e
el3ke
el2k3s
el4k3u4r
el1ku
el4kw
4e1lo
e5l2oep
elo4e
e4l3oes
e3l2ok
e2l3o2l
el3o2ms
e4l5o4nd
e2l5o2nt
e3lo4o2
e5l4oo4d
e5lo2o4s
el3o4ps
e4l5o4p1t
e4l5o2p1v2
e2l3o2r
e4l5or1g
elo4t4j2
e5lou
e2l3o4v4e
elo3v2
e5lo5z
el1p4o
e4lp
el4ps
el4s5em
e4ls
el3se
el4s3k2
el5s4me1d
els1m
els1me
el5twe
e4l1t
el4t1w
4e1lu
e2l3u4it
elu2i
elu2k2s5
el2uk
2e1ma
e4ma1na
ema3sc
ema5to
ema1t
e5mee2s5
e1me
eme4e
emen3s5te
e3men
eme2ns
emens4t
eme4r4s
em4es3
emie4tj2
e1mi
em4ie
e5m2ok
e1mo
e2m3o1li
e2m3op
em3or1g
emor5st4en
emor4s4t
emo4rs
emor3s1te
e4mo4v2
em3sa
e2ms
em5s2c
em4s1li
em1s2l4
em4s2m
em1s4t
em3su
e2m3u4it
e1mu
emu2i
e5mut4
e2n3aap
e1na
ena4a4
e3naar
e4n3aas
e2n1ac
e5n4a4c1c
e4n5af
e2n1ak
e2nal
en3a4l.
en3a4ls
e4n3a4m1b
e3nam
en4a1me
e2nan
e4n3a2ng
e2n1a2p
e5na1ri
e4n3a4r2s
e2n3a2s2
ena1s3p
e3na1t
ena4t4el
ena1te
e4n3a4t3t
e2n1a3v2
e2n3a2z
en3ci4
e2n1c
3ency.
en1cy
en3da
e4nd
en5da4a4
end5a1m4a
end4am
5ender5t4i
en3de
ende4rt
en3d4o
en3dr4
en5d4rek
en2d1re
e2n3e2c
e1ne
ene1de4
ene1d
e3ne4e4
e4n3ee1d
enee5t
en5e4g.
en4e3g2
en5e4g3g4
en3e2la
e4n3elf
en3e1ma
e3nem
e4n3e2n5t
ene2n
e2ne2p
en3e1po
e5n2e5re
5e2ner1g
e4ner1v2
en3e1ta
en3e3te
ene4t4en
e3ne2u
4e2n3f
en5ga
e2ng
en3g4l4
en4g5le
en3g4r4
en5g4ri
en4g2s4
eng5se4
eng3s1m
e3n4ie
e1ni
e5n4ijd
eni2j
e2n3im
e4nin2ga4
eni2ng
e4n3i4n1k
e3ni1v2
e4n3i4vo
en3k2a
e4n1k
e4n3och
e1noc
en3o4f1f
e4n3o2li
e2n1on
e4n3oor
eno4o2
e3noo4t5
e2n1o2p
e3no4r.
e2n3o2r1d
e1n2o3s
en3ou
e2n1o3v2
3en1q
en5s1ce
e2ns
en1sc
en4se2i
en3se
ens5ein
ensek5
3ens4em
ens4fe
en2s1f
en4sin
en3si
en5slak
en1s2l4
en4s3on
en1so
en1s2p
en4s5pot
ens1p4o
en5stan
ens4t
en1sta
en5st4en
en3s1te
en4st5ij3v2
en1s3ti
ensti2j
en4stin
en4stu4r
en1stu
en3su
en4tac
e2nt
en3ta
en5te4e2
en3te
en5t3e2i
ente5re
ent2er
en4ter1v2
3en3tè3
en1t2h
en5tom
en3to
en1t4r4
en3tre
ent5rol
ent4s2l4
en4ts
ents3m
ent4s3p
en4t1w
e1nu
e4n1u2i
e2nu2n
e2n3u4r
e4n3u4u4
5env4e1lo
e2n1v2
env4e
en5vel
e1ny4
e3o
eo3d
eo1do3
e5o4e
eoes3
e5o4f1f
eo3f1r4
e4o3k4
e5on
eo5ni
e5o4o2
e2o3pa
eo3pe
eo3p3l2
eop4la
eo3p2r4
e5o4ps
eo2r5d
e5or1g
e5o1ri
eo3ro
e2o3s4
eo5s3t
e4ot
eo5te
e5o3t4h
e1pa
e3pa4a4
ep3aak
ep3ac
e4paf
e3pa4k
e4p5a1ke
e3pal
e3pap
e4p3a4p3p
e3par
ep3as1p
e1pa4s3
e1pe
e5p4e.
e4p5ee2n
ep4e4e4
e5per
eper4s5t4e.
e3pe4rs
eper1s4t
eper3s1te
e1pi
3e2pid
ep3i2js
epi2j
e2p3ij1z
ep5in4gr4
e3p4i2ng
e2p3i2ns
epi4t4s
ep4it
epit3s5te
epits2t
ep1j
e1p2l2
ep3le1d
e4p3lod
e5plo4e
ep3lus
e1po
e4p5o4ge
epoor4t5j2
epo4o2
ep4oo4rt
epoo4t4j2
epoo4t3
3e4po4s.
e1p2os
e3pot
epou4
e1pr4
ep4ra
e3pri
ep5ro1de
e1p2rod
eprot4
e2p2s
ep4s5e4e
e4p3se
ep4ser
ep1s3l4
eps5n4
ep1s3p
eps3ta
ep1s4t
eps5ta4a4
eps5t2al
eps5to
ep1s4tr4
eps5tro
ep4tak
e4p1t
ep2t3j2
ep4tr4
ept3ra
ep5tro
ep3u4it
e1p2u
epu2i
4equ1a
e1q
equ4
e3r4a.
e1r2a4a4
e5r4aad
e4raa4k.
e2r3aan
er5aa2n1p
e4ra4a4p.
e5raa1t
e4r1ac
e5r4a4c.
e5r4a3ce
e5r2a3co
e3rad
e5r4a4d.
er3a1do
er3af
e3r4a4f1f
era4gen
era1ge
e1rai
e4r3a2l1l
er3a1ma
e4r3a1na
e5r4an1da
era4nd
e5ra1ne
e5r2a3p2l2
er3ar1c
e3r4a5re
e3ra1ri
e1ra1t4
er3a4tr4
er3az2i
era1z
er3d2a
e2r1d
er3d4i
er3d4o
er3d2r4
er4d5ui4t.
er3du
er2du4it
erdu2i
er3d4w
e1re
e4r5e5a1t
er4ea
4erec
e4r5e4di3t2i
ere1d
ere1di
ered4it
e2r3ee2n
ere4e
e5ree2p
e4r5ee4rs
eree2r
er3ee2t
er4ef
e2r5e4f1f
e4r5e4g.
e1r4e1g
e4r3e4gd
e4r5e4g3g4
e4r5e4gt
e4r3e4i1e
ere2i
er3eig
e4r3eil
e4r5ei4nd
ere3k3l4
er3el5k
e4r3e2m1m
e2r3e2m1p
e3r4e4nd
e5rendel
eren3de
ere4ne
eren5e3g2
er5e5n4e4n.
erene2n
e3re2n3m4
e3re2nt
er5en1t2h
e5r4en3to
er3en5t1w
ere3o
ere4og
er3e1pi
e4r3e2q
er3e3ri
e3re4s.
er3e2s1k2
e3re4s3s4
ere4s3t
ere4t3j2
e2r3e4t3n
e4r3e4ts
e4r5ex
erg2l4
er1g
e3ri
eri5a1b
eri1a
e5r4if
e5r4ig
eri1g5a
er4ijl
eri2j
er3i2js
e4rij4s.
e4r3ij3v2
e4r3ij1z
e5r4ik
e4r5i4nd
e4r3i1ni
e4r5in4kt
eri4n1k
e4r3i2ns
e4r3i2nt
e5r4i1o
e5r4is
erkee4r4s5
er1ke
erkee4r
erke4e
er3k2n4
er3m4i
er1m
er5mo
er5nu
e2r1n
e1r4o.
e3r2o3b
er4oc
e4r3oe1d
ero4e
e4r3oef
e5r2oep
eroe5t4j2
e4r3oe1v2
er3of
ero2g
e3r2ok
e1ro2l
e5r4o4l.
e2r3o1li
e5ro2l1l
er3om
er1on
e3r2o4n.
e3r2o1ne
e4r3o2n1v2
e2r3oo4g
ero4o2
e2r3oor
e5ro2o4s
e4r3op
er2o3p3a
ero5pen
ero1pe
e2r3or
er1o3v2
e4r3oxi
ero4x
e3ro1z
e3rö
er4plu
er1p
erp2l2
errie5tj2
e2r5r
errie4t
err4ie
er3s2c4r2
e4rs
er1sc
er4sj4
er5s4lag
er1s2l4
er5s4pan
er1sp
ersp4a
ers4pot
ersp4o
er5st4em
er1s4t
er3s1te
er5te
e4rt
er3t2h
er5t4i
er5t4o
er3tr4
ert5se
er4ts
ert2s5l4
er3t4u
er4t4w
e1ru
e3r2u2b
e3ru2g5
e2ru2i
er3u4it
erui5t4j2
e2run
e3ru2ns
e4r3u4r
e3r2u4s
e4r5u4u4
3ervar
er1v2
3er2wt
er1w
e4s1af
e4s3a2g
e3sam
e5san
es3a2p
es3a2r5r
e3sa3s
e3s2co1p2
e1sc
e2s1co
e3s2c4r2
e3s4e
e5sec
e4s5ee2n
es4e4e
e5s2el
es5e1ne
es4en
e4s5e2ng
e4s5ex
es2fe
e2s1f
e4s5h4e
es1h
e4shi
e3sid
e1si
e3s4ie
es1in
e4sir
e2s5j4e.
esj2
es1je
es5je4s3
e3s4jo
e4s5j4on
e4s3ka
e2s1k2
es5kr4
e3s2l4
es4la
e5s4l4a.
e5s4lag
es3lak
e2s5la1t
es4le
e4s5l4e1g
es2m
es4mu2i
es1mu
e5smui4l.
e1s2n4
e3s4ne
e1so
e3s2ol
e3s4o4o2
e4s5oo4r.
e2s1o4p
es3o1re
e1sp
e2s5pa4s5
esp4a
es4p4el
espit5s4te
esp4it
espi4ts
espits2t
e3s1p4l2
e4spr4ie
espr4
esp5riem
es4s1m
e4s3s4
e3s4tak
es2t
e3s4t2al
e3s4tap
es4tar
es5tat4ie
esta1t
esta3ti
e4s3t4e.
es1te
e4s4t4ea
es4tee2l
este4e2
est5e4i.
este2i
e4ste1ka
es5te5kam
e3s4t4em
e4s5te1mo
es3t4en
e4ste4n.
es5te2n1b
es3t2er
estere5o
este1re
e4s5te4s
es4tet
e3steu
es4t4ic
es3ti
e4st4ie
e3stot
e4s5tr4a.
e1s4tr4
es5t2rac
es5trak
e5stral
est5rap
e4s5tre2i
est4sc
e4s4t1s4
es4tur
e1stu
e3s4ty1
e3su
esu4r
e3s4y
e1ta
e3t4a.
et3aan
eta4a4
e2t3ac
e4t3ad
e4t3a2f1z
3e2tag
e3tak
e5ta4k.
e4t4a2na
e5ta4nd
e2tap
e4ta4p3p
e5ta1t
e4tau
e2ta1v2
e3te
e5t4ea
et3e1di
ete1d
e5tek
4et4el
e5te4l.
e4t5el3f
e5te4ls4
e2t5e4m1b
et4em
e4t5e2m1m
eten3s5u4
et4en
ete2ns
eten5tj2
e3te2nt
ete5r4a
et2er
ete3ro
eters5la
ete4rs
eter1s2l4
eter5s4m
e5tes
e1th
e2t3ha
et3hor
e2t5hu
e4t5i4d4
e3ti
e5t4ie
e4t3i2n1c
e4ti1q
e5t4is
e4t1ja
e1tj2
e1to
e5toc
e3t4o4e
e5toe1v2
e3t2ol
eto4p
et3ope
et3op3l2
e4t3or3k
eto3s3f
e3t2os4
e1tr4
e2t3rec
e4t5res
e3tro4e
e5tron
e5tro4o2
et4r2os4
e4t3ru
et4s2l4
e4ts
et3s5lap
et5s4lu
ets3n4
et4s3o4o2
et3spe
ets3pr4
et3s2p4u
et4s1te
ets2t
ets5tek
et5st4en
et5s4ti
et4s4u4
et5s3u5r
et5s3u4u4
e1tu
etui5t4j2
e2tu4it
etu2i
etu4r
et3we
e4t1w
et2wi
1eu1a4
1e4uc
eud4i5o
eu1d
eu1di
eu5d1r4
eu3e
eug4d3r4
eu4gd
eu3g2r4
eu4ler
eu1le
eu4li
e1um
e3u4m.
e2u2m1d
eu2na
eu2n3t
1eu1o
eu2po
e4up
eu4rad
eu1ra
eu4rec
eu1r2e
eu3re4n
eu4res
eu4ri2j
eu1ri
e4ur5k
euro5v2
eu1ro
eur4sta
eu4rs
eur1s4t
eurs5ta4a4
eur4s5t4e.
eur3s1te
eur4s5tr4
eur4su
eu5s4ch2
eu4sc
eu2s4p
eu3sp4a
eu4s2t
eu5s4tr4
eu3tj2
eu1tr4
e3u4u4
2euw
eu4w1a
eu5win
eu1wi
euw4s4tr4
eu2ws
euw1s4t
eva4ar5tj2
e1v2
eva4a4
evaa4rt
eva4l4s
eva1ri5
ev4e4lo
ev4e
e5vel
evel5op
eve5n4a4a4
eve4na
4eve2r
eve3r1a
4e1w
e5wa
e5we
ewe2n4s
ewe2n
ewens5t4e.
ewens4t
ewen3s1te
ewe1s4t5r4
ewe2s3
ewes2t
ew2h
e5wi
ewo3v2
4ex.
2ex3a4a4
ex3af4
4ex1co
ex1c
3ex4e1g
ex4e
3exem
4exi
ex3in
ex5op
1ex1p
e3y4o
eys4
ey3s2t
e5za
e1z
e3ze4e
ez4e
4e3zen
eze4r2s5
e3zo
e2z3z4
é3a
é1d
éde4e4
é1de
é1di3
é1g
é3ge4e5
é1ge
é3h
é3j
é3n
é3p
é3r
é1t
è1
4èc
è2l
è2s
è5t
èta5
ê1
ê2p
ê3per
ê5t
3ë
4ë.
ë2b
ë3c
ë3d
ëe2
ëen3
ë3j
ë1l
5ën
ën3ce3
ë2n1c
ë1n4e
ë2ns2
ën5sc
ë2nt2
ën5th
ën5t1w
ë3p
ë1ra
ë1re
ë1ri
ë1ro
ëro1g2
ër2o3s
ë2s
ë3si
ës3t
ë1t
ë4t4s
ët3s3te
ëts2t
ëv4e5
ë1v2
ëven4
4ëzu
ë1z
4f.
1fa
f3aa2n1b
fa4a4
f4aa1t
3fa1b
fa2b4o
f3a4c1c
f4a1ce4
f1ach
2fad
2f1af
fa3g
fa4l3s
fa3m
f3a2ng
fa2nt2
fan4t3j2
fan4t4s5
2f3a2p
f4ar1m
3f2a5se
fa2to
fa1t
fa3v2
4f1b
fbe5d2w
f3b4e
fbe1d
f1c
4fd
f3da
f3da4g
f5dan
fd1ar
f5de4k
f1de
fde1k3l4
fde4s3
fde3s5e
fde3s5l4
fde5s3m
fde3s5t
f2d3in
f1di
fd3of
f1do
fdor3s5te
fdo4rs
fdor1s4t
f3d4ra
f1dr4
f3d4ru
fd5se2
f2ds
fd3s1i2
fd3s1o4
fd3sp
f4d2w
f4d3wo
1fe
f4e2a
fec4tr4
f4e2c1t
fe1de3
fe1d
fe4del
f3ee2n
fe4e
5fee2s3
fee1s4t5r4
fees2t
fel5dr4
fe4ld
fe4l3e4e4
fe1le
3f2e1li
fe4lom
f4e1lo
fe4l3op
fel3sp
fe4ls
fe3no
f4er
fe3ra1b
fe3ran
fe4r3et
fe1re
fe3r3om
fe3r1on
3fes3
fe4t3j2
fetu5r
fe1tu
2f3ex
1fé
3fè1
3fê1
4f1f
f5fe
f5fi
f4f1s2
ff3s1h
ff3si
f3fu
f3g2
f1ge3
fge5r4
fge5t
4f5h
1fi
fi5ac
fi1a
fi4al
fi3am
fi3a5pa
fi2a1p4
fi3apo
fia4s
3fi1b
fi1ch
f4ic
5f4ie
5fig
f3i2js
fi2j
2f1ij1z
fik4s4t
f4ik
fi2ks
3f2il
fil4m3a
fi2l2m
film5on
fil4mo
fi3lo
4fi4nd
3fi1ni
f3in1j4
4fi4n1k
2fi2n3r
f4i3o
fi4r
f4i4s
fi5se
f5iso
f1j
fje4s5
4f1k4
f3ke
f2l2
4f3la4a4
f2laf
f4lam
f3le2i
flen4s4t
fle2ns
flens5t4e.
flen3s1te
f4le2s
fle2t
fle1t3j2
4fle1v2
f4lex
f3le1z
2fl4ie
f1li
2fli2j
f4l4ik
f4lip
f4l4it
f3l2ok
3f4lor
flu4t3
4f1m
f1n
1fo
3f2o3b
5foc
foe5d
fo4e
foe5ta
2f3of
5f2ok
2fo2ms
fo5na
fond5en
fo4nd
fon3de
fonds5l4
fon2ds
fon5e2ng
fo1ne
fone2n
fo1no
4fo2nt
fon5te
fo4o4
fo4oi5
f3oom
5f4oon
2fo4p
fo4p5s4
f4or
3fo5re
fo5ri
5for1m
for4t3j2
fo4rt
fo1r1u
fo3t
2f3ou1d
4f1o2v2
3fö
4f5p4
fper4s5t4e.
f3pe4rs
fper1s4t
fper3s1te
fpit4s5t4e.
fp4it
fpit3s4te
fpi4ts
fpits2t
fr4
f4raa4k.
fr2a4a4
fraam5
5f2rac
f3rad
f2ras
5f2rau
f1rec
f3rek
5f4re1q
frie4s
fr4ie
frie4t
frie3t5j2
f4r4ik
f4rod
4f3rol
f4ro3lo
f3r4o5ma
fr2u4s3
4f1s
f2sa4
fs3ad
fs3an
fs3ar
f3sc
f5s4ch2
f4s1c4r2
f3se2
f4s3ec
f4s5e4e
f4se2i
f4s3e1th
fse4t
fs4fe
f2s1f
f2s1h
f4s5h4e
f2si
f3s4ie
fs3im
fs1in
f5sla4a4
fs2l4
f5s4lac
f5s4lag
f3s3lap
fs2m
fs3ma
fs4mi
fs3mo
fs3mu
f2s1o4
fs3o3b
fs3om
f3s4o4o2
fs2p
fs4pre
fspr4
fs4t
f2s3t3as
f3s1te
f4s5tec
f5ste2l1l
fst4el
fst4e4m3
f4ste2r5r
fst2er
f3s3ti
f5st4if
f3sto
f4s3t3oc
f4ston
f3s4tr4
f3stu
f3s4y
4ft
f1ta
f2t1ac
ft4a4k3l4
fta4p
ft3a4rt
fter5s1h
f1te
ft2er
fte4rs
ft3h
f1to
f5to4nd
f4to2nt
f1tr4
ft2s3l4
f4ts
ft4s1m
fts3n4
ft4so
fts3p
f1tu
ftu4r
1fu
2fu4it
fu2i
fu4ma
fum3ac
3f2un
fu1r4o
3fus
2fu4u4
4f1v2
fva2
fval3
4f1w4
3fy1
2f1z
fzet5
fz4e
4g.
1ga
3g4a.
ga4ar5tj2
ga4a4
gaa4rt
g4aa1t
2g1ac
4g3a2d1m
g4a4f.
g3a4fd
ga3f1r4
4g3a4f3s4
4g3a4f1w4
2g3a4h
4ga4l.
ga3la
ga4l3ap
ga5ler
ga1le
ga4l3s
4ga4m1b
g4a3m4i
3gan
ga4n5d
5ga1ne
gan4s5t
g4a2ns
g2a3p2l2
3g4a4r.
4g3ar1b
ga3re
g1ar1m
3ga4r2s
2g3a4rt
g4ar5tj2
ga4s
ga2s5c
ga1s3i
ga5s4l4a.
gas3l4
ga3s1li
ga5slo
g4as3o
gas3p
g4a1s4tr4
ga2s2t
gas5tra
gast5rol
3ga1t
ga2t5j2
ga4t3s
4gau3t4
ga5v4e
ga1v2
g1a2vo
2g5b
2g1c
4gd
g5dac
g1da
g5dag
gd3a4rt
g3d4a1t
gd5a1te
g3de
g4d3elf
g5de4r.
gd3er1v2
g4d3id
g1di
gd3im
g2din
g3dr4
g5dru
gd3s1a2
g2ds
gd5sp
g3du
1ge
3g4e.
g4e3a
gea3dr4
ge1ad
gea5na
ge3an
ge3a3q
ge4a1ri
ge1ar
ge5au
4g3e4b.
ge1b
2ge4b1b
ge3c
ge3d4
gedi3a
ge1di
ge4d4it
ge5d2r4
ge5d2w
3ge4e4
gee1s4t5r4
gee2s3
gees2t
gee1t3a
gee2t
ge3f4
2g3e4f1f
g4e5g4
ge3ge4s4
ge1ge
4geig
ge2i
2g3e4ik
gei4l5a
5g4e4it2
gei4t3j2
ge3k4a
ge3ke
ge5ki
ge5k4l4
ge3kr4
gek4s4t
ge2ks
ge1k4u4
ge3k4w
ge3l1au
ge1la
gel4d3a4
ge4ld
ge3l4e
4g3e4lem
gel5f
gel5k
5g4e3l4o
gel5si
ge4ls
gel3s2l4
gel3sp
gel5s1te
gel1s2t
g2e5ma
4ge4m1b
4g3e2m1f
ge5mo
2g3e2m1p
ge2ms3
ge3m4u
g4e4n.
ge3n1ak
ge1na
ge2n4a2z
3ge3ne
ge4n3e1d
ge4ne4nd
gene2n
4g3en4gt
ge2ng
3ge1ni
ge4n5k
ge1no
ge4n4of
ge4nog
gen5s4fe
ge2ns
gen2s1f
gen5ston
gens4t
gen1sto
gen5stu
gen4stu5r
5ge2n1w
ge5om
ge3o
geo5pe
geor5k4e5
ge5o3s4
ge5ot
ge5p4
ge1ra
ger5aal
ge1r2a4a4
ge4r5a4a4p.
ge4r3a4l
gera4p
ger5a1pe
ger5a4s.
ge5r4e1g
ge1re
ge3rem
ge5r4e4n.
ge3r4i
ge4r5i1ni
ge1r2o
ger4of
ge5ro2l
ger5sl4an
ge4rs
ger1s2l4
ger4s1li
gers5li2j
ger4sp
4g3er4ts
ge4rt
ge3r4u
3ge1s4
ge3sa
ge3sc
ge5s4e
ge3si
4ge3s1k2
ge5s2l4
ge3s2n4
ge3so
ge5spe4nd
ge1sp
ge4s3pen
ge5sper
ge5sp4o
ge5stan
ges2t
ge4s5t4e.
ges1te
ge4s5te4n.
ges3t4en
ge3s4tr4
ge5s1w
ge3ta
get4a4a4
ge5tam
ge2th
ge5t4i
ge3t4j2
ge1t4o
ge3tr4
ge5tra
ge5tro
ge5t3ru
ge5ts2j2
ge4ts
ge5tu
ge5t4w
ge3u2i
5g4e1v2
4gex
5g4e1z
1gé
gé1di4
gé1d
3gè1
4g1f
gfijn5s1te
g1fi
gfi2j
gf4ijn
gfijn1s4t
gfij2ns
4g3g4
g5ge
gge3la
gge4r5on
gge1r2o
gges5ti
g3ge1s4
gges2t
g4g5h
g5gi
ggin4g2s5
g5gi2ng
g5gl4
2g1h
g2het
gh4e
g2ht4
gh5te
g2hum
1gi
gid3s5te
gi2d2s1
gids2t4
gie5ra
g4ie
gie4r4s
gi1e2u
g4i2f
gif5r4
gi3g4a
5gige1re
gi3ge
5gig1s1te
gi4gs4
gigs2t
2gi2j
g3i2js
4gij1z
gi2m
gi3na
4g3i2n1b
4g3i2n3f
g5in1fe
g5infr4
5gi2ng
2g3i4n3h
gi1n3o
2gi2n3r
gi4o1c
g4i1o
gi2od
gi4onet
gi3on
gio1ne
gi2or
gip4s4t
gi4ps
5gir
3g4is
4g1j
4g1k
gl4
g5la1b
3glai
1gla4s
gl2a3s3e4
g5la1t
3g4l4a1z
3gl4e.
g5lee2r
gle4e4
glee5t
g3len
2g5lep
4g5ler
g3le2s
3gle4t
gle1t3j2
g5le1v2
g5li3ce
g1li
gl4ic
g5lich
3gli3ë
g2l4if
g5li2j2s
gli2j
g2lim
3g4l4i1o
g2l2o3b
3glof
g5log4
3glom
4g3lon
g3l4oon
glo4o2
g3lop
3g2l2os
g5lo5z
3g2ly
4g1m
gmaa2t5j2
g1ma
gma4a4
gmaa1t
2g1n
g3na
g1n4e
gn4e5g2
g3ne5m
gne4t3j2
gnie4tj2
g1ni
gn4ie
4gnu
1go
3g4o.
3g4o2a
3g2o3b
2goc
g1och
go4d3a
go2d4s3
gods5t4
4goef
go4e
g4oe1r
2gof
go3f2r4
g4og
4g2o1h
g2o2k
5go4m.
g4o2ma
g3o2m3l
4g3o2m1z
go4n3a1z
go1na
2g3o2ng
go5no
2g1o2nt
g2o4o2
2g3oor
3goo4t
2g1op
g2o3pa
g4op1r4
g4o1ra
4go4re
go5r4e.
5g4o1ri
go4r2s
g2os1
g2o3tr4
gou4d5e4e
gou1d
gou1de
2g3o2v2
2g5p
g3pes3
1gr4
3gra
5gr4a.
graa2t5j2
gr2a4a4
graa1t
g5rak
gra2m
g4r4a4m.
gra1m3a
g3r2a2m1p
gra4s3
5gra1v2
2g3rec
2g3re1d
5gre1di
g5re1du
g3ree4k
gre4e
g3ree2l
g4ree2p
g3re4is
gre2i
4g3rek
2g3rem
gre2n4s
gre4s
g4re2u
g3re1v2
5gri1a
grie4t5j2
gr4ie
g5r4ijd
gri2j
g5ri2jk
g5rijm
g5r4i2ng
5g4r4is
gri4t5s
gr4it
2g3ri1v2
groe2t5j2
gro4e
grof5
g3r2ok
g3ro2ok
gro4o2
g3room
groo4t5j2
groo4t
2grou
gro5v2
2g3ru2g
g3r4uim
gru2i
g3r4up
4gs
gs1a2
gsa4g
gs5alar
gs2al
g3sa3la
gs3a4l1t
g2sc
g3se4
gs3e1co
g4s3e1d
g4s5ee2n
gs4e4e
gs3e2i
gs4en
gs5e1ne
gs3er1v2
gs3e4t
gs3e1v2
g4s5h4e
gs1h
g2s1i2
g3s4ie
gs5i4s
gs1j2
g3s4k4e.
g2s1k2
gs1ke
gs3l4
gs4la
gs5l4aag
gsla4a4
gs5lam
g4s5las
gs1le
g3s4lep
g4s4leu
g2s5l4ie
gs1li
gs4lin
g5sli2ng
gs4lo
g4s5log4
gs5l2ok
gs5lon
gs4lu
g4s5ma
gs1m
gs3n4
g4s1na
g3s4ni2j
gs1ni
g4s1o4
g5s2ol
g5s4o4m.
gs2om
gs5o2ns
g2s3op
gs3p
gs5pa4nd
gsp4a
gs4pan
g3spec
g3s4p4el
g3s4pet
gs4pi
g3s4p4ie
g3spil
g5spi4n.
g5spin3n
gs5pir
g4s5pol
gsp4o
g3s4pon
g4s5ps
gs5q
gs5sc
g4s3s4
gst2a
gs2t
gs5t4aal
gsta4a4
g4st5aa2n5g
g2s5t1ac
g5s4tad
g5s4tan
g4s4t3ap
g5sta1t
g1s1te
g5s4t4e.
g5s4te1d
g5ste4e2
g3ste2i
gs3tek
g5st4el
g3st4en
g3st2er
g5ste4r.
gs5te2r5r
g5ste4rs
g4s3th
g5s4t4ic
gs3ti
g3s4tig
g3s5tij1g
gsti2j
g5s4to2f
g5s4top
g5stor
gs4t3o4v2
g4s3tra
g1s4tr4
g4s5t4rad
gs5trak
gst5ram
gs5trap
g5stra1t
gst5res
g4s5tro4e
gs5tron
g4stru
g5str4uc
g3stu
g2s5ty1
g2s1u4
gsve4r3
g4s5v2
gsv4e
g4s5w
g5s4y
4gt
g1ta
g2t3ap
g3te
gte3ro
gt2er
g3tes4
gte3s2t
g1to
g3tr4
g1tu
1gu
5gu.
3gue
gu4eu
2gu4it
gu2i
gu4ni
gu2s3
gut4s2t
gu4ts
gut4s5t4e.
gut3s1te
4g1v2
g5vo
4g1w
g5wa
1gy
4gy1p
2g1z
4h.
haam4s5ta
ha4a4
haa2ms
haams4t
haar5s3l4
haa4r2s
haar5sp
haar3s5te
haar1s4t
ha4ar5tj2
haa4rt
haat4s5t4e.
haa1t
haat2s2t
haa4ts
haat3s1te
h3a4fd
haf4t3u
ha4ft
ha3g
ha5ge
hal2f1
5ha4ls
hal4sto
hal1s2t
5ha4l1z
2ha2m1p
4ha4n.
han4dr4
ha4nd
hand5s4l4
han2ds
han3ga
ha2ng
hang5l4
han4g5s
han4s3l4
h4a2ns
han3so4
han4s4t
ha2p2s
ha4p4se
har4ta
ha4rt
hart4e5l
h4ar1te
h4ar4t3j2
h4ar4t3o4
har5tre
har2t3r4
hart5s3l4
har4ts
ha2t5j2
ha1t
ha2t3r4
ha4t3s
ha3v2
4ha3v4e.
hav4e
4h1b
2hd
h4e
2h4ea
he2ar
3hech
he3co
4he4e.
he4e
he4e3g4
hee4k
hee3k3a
hee3k5l4
he4e4l3o
hee2l
hee2p4s
heep1s5c
heers5tak
hee2r
hee4rs
heer1s4t
hee5sto
hee2s3
hees2t
hee5tje4s3
hee2t
hee3tj2
he2f
he4i
heids5p
h4eid
hei2d2s1
he4is4
hei5tj2
h4e4it2
he2k3a
he2k2l4
hek4s4t
he2ks
heks5t4e.
hek1s1te
hek5st4en
hek3w
he3le
he4l3e4e4
h2e3li
hel4m3a
he2lm
h4e1lo4
hel4p3a
he4lp
hel3s1m
he4ls
he5mo
he5ne
hen4kr4
he4n1k
he3n4o
4he5o
he4pi2j
he1pi
he2p3l2
he2pr4
he1ra
he1r4a4a4
he4r3ad
he3r4au
he4r3i
herm5e2ng
her1m
her1me
her3men
he3r2os
her1o5v2
her4p5a4a4
her1p
her3pa
3her1s4t
he4rs
he4rt4
hert3s5te
her4ts
herts2t
he2ru
he5s4e
he2sp
he2s5t
hets5t4e.
het4s1te
he4ts
hets2t
heu5le
2h3f
4h5g
h3h
hi5d
hie4f3
h4ie
hiels3ge1s5
hie4ls
hiel4s5g
hiels1ge
hie4r3
hie5ren
hie1re
hie3r5u
hie4t5o4
hie4tr4
hie4t5s
hij4s2l4
hi2j
hi2js
hi2k4s5
h4ik
hi3k1w
hi2l3m
him4p2l2
hi2m1p
him4p2r4
hi4n5d
h3i2ns
hin4t3j2
hi2nt
hi2p5l2
2hir2
his5p
h4is
hi3tr4
h4it
hit4s2t
hi4ts
hit4s5t4e.
hit3s4te
hit5st4en
h3j
2hl
h3la
h4lag
h3lep
h3loc
2h2m
h3ma
h3me
h4mer
h1n
h2na
hno3
2h4o.
h4o3a
ho1a3n
hoboo4t4
h2o3b
hob4o
hobo4o2
ho3ch
hoe4ker
ho4e
hoe1ke
hoe4s
hoe3s5l4
hoe3t
ho2f
ho4f5d
ho1f3e
ho3g2
ho2ka
h2ok
h4o5mo
hon3dr4
ho4nd
hon2d4s
ho2n3g
ho1ni4
ho1no
hoo1l3e2
ho4o2
hoo4l
4hoom
h4oo4rt4
hoor5tr4
2hoo4t
h2o3pa
ho1pe
ho2p3o
hop3r4
hop4s4tr4
ho4ps
hop1s4t
hor5de
ho2r1d
5hor3l
ho3ro
hor4s4t
ho4rs
hor4s5t4e.
hor3s1te
hor5st4en
hor4t3j2
ho4rt
ho3r1u
ho3sa
h2os
ho4t3j2
h2o3tr4
ho4t3re
hot4s2t
ho4ts
hot4s5t4e.
hot3s1te
ho3v2
2h4o4w
ho3w3o
2h1p
hpi4
2hr
hra4b
h4re
h5r4ea
hri4
hr2o2k
hro1k3o
hroo4t3
hro4o2
4hs
h3sa
h3sp
h3s2t
2ht
h4t1a2
h2t3ac
h3t2al
ht3a1la
h5t4a2ns
h3t4e.
h1te
h4t3ec
h4t4e1co
h2t3e4e2
h2t4ef
h2t3e2i
ht5em
h3t4en
h4te2n5t
ht5en4t1w
hter3a
ht2er
hte4r5o
h4t3e2s1k2
h3tes
h4t4e1v2
ht5ev4e
h5tevo
h2t3ex
h2t5h
h4t3i2nt
h3ti
h2t1j2
ht1o4
ht5oef
ht4o4e
ht5op
h4t1r4
ht5ro4o2
ht4sa2p
h4ts
ht3se4
ht4ser
ht2si
ht4s2l4
ht5sla
ht5slot
ht3s2me
hts1m
ht5s4mi2j
hts1mi
ht4s3o
ht3spe
ht2s3p4l2
ht3spr4
hts5t4aal
ht3sta
hts2t
htsta4a4
ht4s5tak
ht4s5tek
ht3s1te
ht4s4ti
ht4s5to1re
ht1sto
hts5tre4k5k4
ht1s4tr4
ht1u2
h4t3w
hu4ba
h2u2b
3hu4i1z
hu2i
hul4der
hu4ld
hul1de
hu4r4t5
hu2t3j2
hut4s5t4e.
hut2s2t
hu4ts
hut3s1te
huu4r5s
hu4u4
4h1w
hy4la
3hy1p
hypo3
4i.
i1a
i3a4a4
i4a1b
i5a3b2i
i4ac
i3a1dy
i4ae
i5a4e.
i2a3f4
i2a3g2
i3a2gr4
i3ai
i5a4k.
i3a1ke4
ia4kem
i4a3k3l4
ia3kr4
i3a4l.
i4a3la
i3a1li
i2am
i5a4m.
i3a1m4i
i3an
ia1n4o
i2a3o
i2a1p4
ia5pa
i5a1pi
ia3sc
i2a5se
i4a3so
ia4s5p4o
ias1p
i4a3s3ta
ia2s2t
i3a1t
ia3t2h
i5atri
iatr4
iav4e4
ia1v2
i5ble
i1b
i3b4l
iboo4t4
ib4o
ibo4o2
4ic
i3ce
5i4ce1pa
i1cha
i1ch4e
ichee4t
iche4e
i1chi
i1cho
i3c2hr
ic2k5l4
ic2os4
i1co
ic4t3op
i2c1t
ict4s5c
ic4ts
i3d4am
i1da
idde4r5a4
i4d3d4
id1de
id4e3a
i1de
i4de4e.
ide4e
ider4sp
ide4rs
ider4s4t
ide2s4
idi3a
i1di
idi5a1b
i2d4i5o
id4mak
i2d1m
id1ma
i3d2ok
i1do
i2dr4
id3ran
i3dra
id3ru
i2d2s1
id4s3a2
id4ser
id3se2
id2s5i2
id4s5j2
ids5l4
id4s1m
ids5ma
id5s4me4e
ids1me
id4s3o4
id1s3ta
ids2t4
ids5tak
id4s5tek
id3s1te
id4st4em
id4s4ti
id1s5tr4
id3u4r
i1du
id3u4u4
idu3w
i2d3w
4ie
i4e1a2
ie4d3ac
ie1d
ie1da
ie3de
ie4dro
ie3d2r4
ie4d3w
i1e4e4
ie3ë2
i4e3fi
ie2f2l2
ie3fle
ie3fon
ie1fo
ie4fr4
ie4ga4s4
i4e1g
ie1ga
ie3ge
ie4g5i2ns
ie3g4i
i2ek
iek3e4v2
ie1ke
ie4k2l4
iek3li
ie5klu
ie2k2n4
iek5o4nd
ie3ko
iek4s5n4
ie2ks
iek4sp
ie2ku4
ie3kwa
ie5l4an
ie1la
ie5lap
iel5do
ie4ld
iel5d4r4
ie1l4e
iel5e4i.
iele2i
iel5k
iel3sc
ie4ls
i2e3ma
ie4m3o4v2
ie1mo
ien4dr4
ie4nd
ien3i2j
ie1ni
i3en3n
i5en3n4e.
ien5n2e
ien3s4m
ie2ns
ien5s2p
ien4sta
iens4t
ien4st5o
ien4s4tr4
ien4st5u4r
ien1stu
ie3o4
i4ep
ie5pen
ie1pe
iepiet5
ie1pi
iep4ie
iep5oo4g
ie1po
iepo4o2
iepou5
iep5rel
ie1pr4
ie3pr2o4s
ie2p3s4
iep5s4t
iep5tr4
ie4p1t
ie4pu2i
ie1p2u
ie5r4ad
ier3a4l
ie3ram
ie3rap
ier3as
ie4ra1t4
ier5e4l.
ie1re
ier5e4ls
ie5r4e4n.
ie5r4i2ng
ie3ri
ierk4
ie3r2o
ie4r3of
ier4s2l4
ie4rs
ier5slu
ie3ru
ie2r4u2i
ie3s1f
ie2si
ie4s2l4
ie5s4le
ies3li
ies3m
ie2s3n4
ie2so4
ie4s3p4l2
ie1sp
ie3sta
ies2t
ie4s5t4e.
ies1te
ie5st4el
ies5te1re
ies3t2er
ie3sto
ie4ta4a4
ie1ta
ie5t2al
iet5a2nt
ie5t4en
ie3te
ie3tj2
ie3to4
ie4t3og
ie4to4o2
ie4to4p
ie4tor
ieto5re
ie4t3o3v2
ie5tro4e
ie1tr4
iets5t4e.
iet4s1te
ie4ts
iets2t
iet3u4r
ie1tu
iet3u4u4
ie3t2wi
ie4t1w
i3e1ty1
ie2u
ie2u3k
i1eur
ieu5r4e
i1eus
ieu3s4p
i1e4u1z
ie3v2
ie3z
ieze1l5a
iez4e
i3és
i1é1t
i1è1
i4ëg
i3ë
i4ëva
ië1v2
4if
if3a4a4
i1fa
i2f3ad
if3l2
if3r4
if4ra
if4ta4a4
i4ft
if1ta
if4tar
if4tre
if1tr4
iftu5r
if1tu
if3u2i
i1fu
i1g4a
ig3a4a4
i2g5ac
i5gal
i4g5a1v2
i3ge
i3ge2s4
i4g3e3s1k2
i2g3i2j
i1gi
i4gi4nd
ig4i3o
ig5no
i2g1n
i3g4om
i1go
i2g4op
i4gs4
ig3s1k2
ig3s3l4
ig3s3p
ig3sto
igs2t
ig3un
i1gu
i1h
i3i
i5ie
ii2n
i5is
i2j
4i4j.
ij5a
ija4d
4ijd
4ije
ij3ef
ij3e2i
ij3el4
ij5e4n3
ij1er
ij3i
4ijn
ij3o4
i3j4ou
4ij4s1o4
i2js
4ijsp
4ijs2t
ij5te
ij4tr4
i1j5u
4ijvo
ij3v2
4ijzo
ij1z
4ik
ik3aar
i1ka
ika4a4
i4kam
i3ke
i2k3ef
ike4ra
ik2et3
i2ki2j
i1ki
i3k2l4
ik3la
i4k3lo
i4k3lu
i2k4n4
i4k5na
ik5o2g
i1ko
i3kom
i2ko4o4
iko2p
ik3o1pe
i2k3o2r1d
i4kr4
ik3re
ik3ri
ik3ro
ik5s2e2
i2ks
ik5si
ik3s4l4
iks3n4
ik3s3no
ik3sp
ik4s3p4a
ik1s4t
ik5sta
iks5t4e.
ik1s1te
ik1w
i3k5w4ar
i1la
i3l4a.
il4a4a4
i2l5aan
i2l3ac
il4a2c1t
il3ad
i2l3af
i3lak
i2l3al
i5la4nd
il4an
il2da
i4ld
il4d3r4
il2ds4
4i3le
i3l3ee2n
ile4e4
ile3l
i4l3er1v2
ile4t
ile2t5r4
ile3u
il3e4v4e
ile1v2
ilevin4
ile3vi
i4l3e2z
i3lé
il5f
i3li
ili4e5g
il4ie
ilie5t
il3i4n1k
il1k4l4
il2k3s2
illa3s
i2l1l
il5la
1illu
i2l2m
il3me2
il4min
il3mi
il4mo
i1lo
ilo4ge
ilog4
i4l3o4nd
i3lo4o2
i5l4oon
il3oor
i2l1or
ilo4re
i2lo4v4e
ilo3v2
il3s2h
i4ls
ils5j2
il4s4ti
il1s2t
il2th
i4l1t
i1lu
4i4m.
i2m4ag
i1ma
i4ma5go
im5au
ime4e4
i1me
im3ee2n
i4m3em
im3e2n1c
i3men
i2m3ex
4i2m1f
i2m3of
i1mo
i2m3op
im3or1g
im5pa
i2m1p
im4s3o4o2
i2ms
imso4
im1s4t
i3mu
i2n1ac
i1na
i2nau
ind4a4a4
i4nd
in1da
in4de1ne
in3de
ind3sc
in2ds
ind5s1te
inds2t4
1in1du
in3e4de
i1ne
ine1d
in3e1di
i4n3ee1d
ine4e4
inek4
ine3o2
ine4t4s
i5ne2u
1i2n3f
in2ga4
i2ng
ing3a4a4
ing3ag
ing3al
3in3gan
ing5lo
ing4l4
in2go
in4gr4
ing4s2t
in4g2s
4in4i.
i1ni
i3n4ie
ini5on
in4i3o
ini5s3l4
i3n4is
ini5s1ta
ini2s3t
4ink2j
i4n1k
in2k2n4
3inkom
in1ko
in4kri
in1kr4
3inno
in3n
i1no
i3noc
i3no3d
in4o2g
in1on
ino5pe
ino3s4t
i1n2os
in3o3v2
1in5ri
i2n3r
4in4s.
i2ns
in5s4ch2
in1sc
in5se
in3s2l4
in3s1mi
ins3m
in3so
in1sp
in5s1p4o
in5st4en
ins4t
in3s1te
in5swi
in4s1w
in4t3ap
i2nt
in3ta
in5te
in3tes5
in3th
1in1t4r4
i1nu
i5n4uut3
i4nu4u4
4i1o
i4o5a
ioas5
i2o5b
i3o1c
i3o1de
ioes3
io4e
io3f
io3g2
i3ol
i5o4l.
i5o3len
io1le
i5olus
i4o1lu
i3on
ionee2l4
io1ne
ione4e4
i5o2ng
io2n4s3
ion3s5c
i5o4o2
i2op4
i2o3pa
io3p1r4
i3o4p1t
io3ra
i3o1ri
io3r1u
i2o4s
i3o4s.
i4o3s3c
i3o5se
i3o5s3f
io5s1h
io5si
i5o5s4i.
io5s2o
io5sp
io5s4t
i5o5su
i3o5s4y
i5othek
io1th
ioth4e
i3o3ti
io4t3j2
i5o5tore2ns
io1to
io5t4or
ioto1re
i2o3tr4
i2o3v2
i3o4x
i2o1z
i1pa
i2p1ac
ip3af
i3pap
i1pe
i4per1w
ipe4t3j2
i1pi
ip1j
i1p2l2
ip3lu
i1po
ipo4g
i1pr4
i2pri
ip3ru
i4ps
i4p3se4
ip4si
ip4s3le
ip1s2l4
ips5t4e.
ip1s4t
ip3s1te
ip5st4en
i3ra
ira3k
i1r2e
ires4
ire3s3t
i3ré
i1ri
ir2k4s
i1ro
iro3p
iro5v2
i4r2s
ir4sc
ir3sp
ir5s1te
ir1s4t
ir1t3r4
i4rt
i1ru
4is
i1sa
i2s1a4a4
i4s3ad
is3a2g
is3a2p
i2s1ar
i2s3as
i4sc
i5sch4a
i3s4ch2
i5sc2hr
is5col
i2s1co
i5s2co4o2
i5scope
is2co1p2
ise2d
i3se
i4s3e2i
is3e2l1l
is2el
is5e2ng
is4en
i4s3er1v2
ise3s2t
is4es
ise3t3j2
ise4t
is4fe4e
i2s1f
is1fe
is4f4er
i4s1h
is5ho
isi2d
i1si
i2si2j
i2s3im
is3ja
isj2
i4s1k2
is3ka
is3ke
is3l4
i3s5lag
i4s5las
is5le
i4s5m
i4s3n4
i3s5ne1d
is1ne
i1s5ni2j
is1ni
is5no
5is2ol
i4so4o2
is4oor
is2o3s4
i2s4ot
is3o4t3t
is3p
i2s5pa4s5
isp4a
is2pi
i2s5p4l2
is5q
is5sa
i4s3s4
is5so
i2s3t
is1ta
i3s4tak
is4t3ap
i4s5tas
is4ta1t
is5te2r1d
is1te
ist2er
is5te1re
i4s4th
is1to
is4t5o2ng
i3s4tr4
is5tri
i5str4o.
i3s4ty1
i5su2m
i1su
i5s4y
4it
i1ta
i2t3ac
i4ta5d
i4t3ee4n
i1te
ite4e2
i3t4en
i3t2er
ite5re2i
ite1re
i3tes4
ite3s2t
ite4t
it3h4ie
it1ho
i2t1hu
i3t2i
itie5s2t
i5ties
it4ie
i4tj2
i1to
it5oef
it4o4e
it3oo4g
ito4o2
i3t2ou
i4to4v2
itper5s4t
i4t3p4
it3pe4rs
it3re1d
itr4
it1ru
it3s1je
i4ts
its2j2
it3s1li
it1s2l4
it3s1op
it1sp
it3s4te
its2t
it4st4e.
it4to4o2
i4t3t
i3tu
i4t3w
4i3u2
iu4m
iu1m3a4
i2u1m3e
iu1m3o
iu3r
i3v4e
i1v2
ive2n5s
ive3re
ive2r
i5w
iwi2
iw4ie2
iw4it3
4i1z
i3z4e
ize3t
î3
ît4
1ï
2ï.
ï5a
ï1c
ï1d
ïe4n3
ïe5n4e4n.
ïe1ne
ïene2n
ï2n3a
ïns5m
ï2ns
ïn3sp
ïn3u
ï3n4u4r
ï3o
ï3ri
ï3ro
4ï4s.
ïs3a
ï4sc
ï5sch4e
ï3s4ch2
ïs3l4
ï3so
ïs3t
ï1t
ï5z
4j.
1jaar
ja4a4
ja4ar5tj2
jaa4rt
ja3b
2jaf
1jag
jage4r4s5
ja1ge
ja3k3n4
ja3m4i
jan4s3l4
j4a2ns
jan4s4t
j2a3p2l2
ja1po
1jar
ja1re4
1jas3
jas5p
3j2a1w
ja1z4
j3b
jba4l
j3ba
jb2e4l3i
j3b4e
j1c
j1da2
j2d3a4a4
jd3an
j4d3ar
j2d3e4e
j1de
jde4n3e
jde2n4s
jden1s5p
j4d3er1v2
jde2s4
jde3s3p
jde5s2t
jdi3a
j1di
j2do4
j3dom
jd5on
j2d3op
j3dr4
j4d3re
j4d1ri
j4d3ro
j4d3ru
jd5se2i
j2ds
jd3se2
jd3sp4o
jd1s2t4
j2d3u
j2d3w
j3d4wan
j4ea4
3je3ba
je1b
je3ch
jec4ta
j4e2c1t
2j1e4e
jel4
je3la
j1en
je2na2
je3n4o
5jep
jepiet5
je1pi
jep4ie
je3ro
je4rs4
jer3sp
je4s3
3jesa
5jes2al
je5s4ch2
je1sc
3jes1k2n4
je2s1k2
je3s5l4
jes5m
je1so2
jes5p4a
je1sp
jes4pr4
3je4s5r
je1s5tr4
jes2t
5jesvo
je4s5v2
3jeswa
je4s1w
3jeswi
je2t
jet3er
je3te
jeto4v2
je1to
jet5s2t
je4ts
5jeu
3je3v4r2
je1v2
2j4e1w
j3ex
j2f1a
j2f3e2i
j1fe
j2f1en5
j4f3i2j
j1fi
j4f3i4n1k
jf3l2
j3f4la1t
jf5le
j2f3o4
jf3r4
j3f4ra
j3f4ro
j4f2s
jf2s3a4
jf4sc
jf4s3er
jf3se2
jf2s5f
jfs3l4
jfs5m
jfs3n4
jfs3p
jfs5p4a
jf3s4t
jf4sta
jfs5tak
jf5stan
jf4st4el
jf3s1te
jf4s3ti
jf4s5to
j4ft2
jf5ti
jf5t1w
j1g
j3ge
jger5s2l4
jge4rs
j2g3l4
jg4s5e4
j4gs
jg3s3n4
jg2s2t
jg3s4te
j3h
j4if3
j3ig
ji2n3g
ji5t2j2
j4it
j3j
2jk
j3ka
j4ka4a4
jk5aa2r1d
j4kar
jk3ar1b
j4kau
j4ka1v2
j2ki2j
j1ki
j2k4l4
j3kla4a4
jk5lak
jk5la2p1
jk5las
j4kle
j5k4le1d
jk5le2s
jk5li
j3klon
jk5lop
j4k5l4uc
j2k1na
j1k2n4
j2k3of
j1ko
j4k3o4l
j2k3on
j2ko4p
j4k3o2p1b4
jk3o1pe
jk3o2p3l2
j3ko4ps
j2kr4
j4kra
jk3r2a4a4
j5kran
jk3re
jk3ro
j4k5ru
jk3slo
j2ks
jks2l4
jk2s3p4l2
jk4sta
jks4t
jks5taak
jksta4a4
jks5t4aal
jks5tak
jk5stan
j2k3u2i
j1ku
jk3w
j3k4was
j1la
j3la4a4
j4l5a2na
jl4an
j1le
j2l3ef
j2l3el
jl5f
jl3i4n1k
j1li
j1lo
j2lo4e
j3lu
j2m3af
j1ma
j5m4ar
j3mi
j2m3op
j1mo
j2m3s
j2n1a4
j4na4a4
j2n5ac
j3n2a5g4
jn3ak
j3n2am
jna5me
j3n4an
jn5d2r4
j4nd
j2nef
j1ne
jne4n
j4n3erk
j4n3er1v2
jn3g4l4
j2ng
j4n3im
j1ni
j4n3i4n1k
j4n3k4
j2n1o4
jn4si
j2ns
jn2s3l4
jns5lac
jn3slu
jns5or
jn1so
jn2sp
jn2s3p4l2
jn1s4t
jn4st4e.
jn3s1te
j2nt4
jn3tr4
joet3
jo4e
4j4oi
jo1l4e
jo5li2j
jo1li
j3om
1j4on
jo1ne2
j3op
jo3pe
jo3ra
jo3r1u
j4ou
1jour
jou5r2e
joy3
j3pa
j4p3ac
j4p3ar1m
j1pe
j2p3em
jp3i2j
j1pin
j3p4i5o
jp1j
j1pla
jp2l2
jp3li
j1po
j2p3or
j4pre
jpr4
jp3ri
jp3r2ok
j2ps4
j3r
jra2a2ds5
jr2a4a4
jr4aad
2js
js1a
j4sef
j3se
j4s3e1la
js2el
j5s2e1li
j4s5em
j4s3e4r
j2s1i
js5in
j3s4ir
js4le
js2l4
js3le4e4
js3li
j2s5l4ie
js4me
js1m
js5m2el
js5met
js3n4
j4s1o4
j5so4e
js3ol
js3pac
jsp4a
js3par
j3spe
j2s3p4l2
j4sp4o
js3po4o2
jspoor4t5j2
jsp4oo4rt
j5spor
j1sta
js2t
j4star
j2s3te
j3ste4e2
j3s4tek
j3s4t4el
j5s4te2ng
jst4en
j4s3th
js4ti2j
js3ti
j5s4to4nd
j4s4to4o2
js3tou
jst5ran
j1s4tr4
j5str2ok
j2su
j3s4y
j3t4aal
jta4a4
jt3aar
j2t1ac
j1tag
j3tak
j3tan
j3t4e.
j1te
jt1h
j3t4o4e
jt3o4p1t
j3tr4
jt3ra
j5tre1d
j5tre4e
jt3re2i
j5trek
jt3ri
j5tr2ok
jt3rot
j4t1s
j1tu
1j4u
ju3d
4jum
jus3
juv4e5
ju1v2
j3v2
jve2n
jv4e
jve4r4s
jve2r
jver1s5p
jve3t
jvie5s3
j3vi
jv4ie
j1w
jze4r5o
j1z
jz4e
4k.
1ka
k3aa2n1b
ka4a4
k3aa2n3l
5kaa4rt
kaart5je4s3
ka4ar4tj2
kaat4s5t4e.
kaa1t
kaat2s2t
kaa4ts
kaat3s1te
ka3b4e2
ka1b
ka3b4o
2k1ac
ka3de4t5
ka1de
4k3a2d1m
ka3do
k3a2d1v2
2kaf
k3a4fd
k4a4f1f
ka3f3l2
3k4a4ft
ka4ga
k3a4gen
ka1ge
k3ah
ka3i
2k3a2l1b4
ka3le
5kalf
kal4f4s5
ka3l4i
kal2k
1kal1k3a
4ka4l1t
5kal1v2
3kam
4ka4m1b
ka3men4
ka1me
kame4re
kam4pa
ka2m1p
kam4p2l2
kam4p2r4
ka5na4a4
ka1na
ka4n5d
4ka2ng
kan4s2l4
k4a2ns
kan4s4t
kan4t3j2
ka2nt
k2a1o3
5k4a4p.
ka3pe
k2ap3l2
ka1po
4k1ap5pa
ka4p3p
k4a3pr4
ka2p3s
k3ar1c
k4a3ro
ka4rt4
4k3ar3ti
kar3t3r4
ka4s
ka2s5c
4k3a1si
kas1t3o4
ka2s2t
k4a3s4tr4
kast5ra
ka5stro
kas3u4r
ka1su
kat5aal
ka1t
kata4a4
ka4t5a4le
kat2al
ka4tan
ka3ti4
ka4t5i1o
ka2t5j2
k3a4t3l
kato4
ka4t3og
ka5tr4
ka4t3s
2k1au3t4
2ka2vo
ka1v2
2k3b
2k1c
k3ca
2k5d
kdi3a
k1di
1ke
k4e1b
2k3ec
ke4di
ke1d
2k3ee2n
ke4e
kee4p5l2
kee4r
kee4r4s
keer3s5to
keer1s4t
2kef
4ke4f1f
k4e4i.
ke2i
k4e4i1e
k2eil
ke4i3s4
k4e4i5t2
ke4lap
ke1la
kel5da
ke4ld
kel5dr4
ke5lel
ke1le
4k3e2lem
kel5f
ke4l5i2nt
k2e1li
ke4lom
k4e1lo
ke4l3op
kel3sp
ke4ls
5k4e1ma
2ke2m1m
2ke2m1p
ke4n3an
ke1na
ke4nau
ken4e4i.
ke1ne
ke4n1e2i
ke5ne2n
ke4n5k
ke2n1o
kens5p4o
ken1s2p
ke2ns
kepie5t
ke1pi
kep4ie
4k3e4q
ke3ram
ke4r5e4n1k
ke1re
ker3k2l4
ker4kle
ker4k2n4
ker4k3r4
ker4ku
ker4kw
ker4n3a
ke2r1n
ker4no
ker3o4
ke3r2os
ker4s4m
ke4rs
ker5spe
ker1sp
ker4spr4
ker4sta
ker1s4t
ker5s3t4e.
ker3s1te
ker4s3ti
4k3er4ts
ke4rt
4kerva
ker1v2
4k3er2wt
ker1w
ke2s
ke3s4p
ke3sta
kes2t
kes5t4en
kes1te
ke3sto
ke5str2a4a4
ke1s4tr4
k2et
5k4et4el
ke3te
ke2t3j2
ke3to
ke2t3r4
kets5t4e.
ket4s1te
ke4ts
kets2t
kettin4g5s
ke4t3t
ket3ti
ketti2ng
4k3e2tu
ke4t3w
3k2eu
keviet5
ke1v2
ke3vi
kev4ie
ke4vl
4k1ex
2k3e2z
2k1f
2k3g
2k1h4
k3ho
khou2d5s
khou1d
1ki
2ki2d
4kie1d
k4ie
kie4sp
kie4s4t
kie5s1te
kie4tj2
kiez4e4
kie3z
2ki3ë
kij2k5l4
ki2j
ki2jk
k3i2js
4kij3v2
4k1ij1z
ki3lo
kilo5v2
ki3na
4ki2n1b
4k5indel
ki4nd
kin3de
kinds5t4e.
kind5s1te
kin2ds
kinds2t4
4k1in1du
kin3e2n
ki1ne
5ki2ng
kings5l4
kin4g2s
2k3i4n3h
ki3n4ie4
ki1ni
k3in1ko
ki4n1k
4k1i2n3r
2k1i2ns
2k3i2nt
4k3i2n1v2
k4i3o
ki2p3l2
ki5se
k4is
ki3s4p
ki4t4s
k4it
kit3s5te
kits2t
k1j
2k3ja
k3j4e1w
k3jo
2k3j4u
4k5k4
kke5n1e2i
k1ke
kke1ne
kke4r4s
kkers5t4en
kker1s4t
kker3s1te
kke3s2t
kke2s
1k2l4
5k2lac
k3l4a1d4i
kla2p1
k4las
5kla4s.
5kla4s3s4
k3la2s2t
k3la4t.
kla1t
k3la4t3t
3k4la3v2
3k4le1d
5kle1di
5klee1d
kle4e4
k5lee4r.
klee2r
4k5l4e1g
5klem
4k5len
k3le4r.
4k3le1ra
k3le4rs
k3le2s
5k4le4u
k5l4ic
k1li
4k3lid
k3l4ig
2k3li2j
4k3li2j2s
k4lim
kli4me
3k4lin
k5l2o3b
4klod
3kl2ok
5klo4k.
k5lo1ka
k3lo1ke
k3l4oo4d
klo4o2
5kl4oof
k3lo1pe
5kl2os
klot4s5t4e.
klo4ts
klot3s1te
klots2t
2k5lo5z
4kl4uc
4klui1h
klu2i
2k1m
k3ma
1k2n4
4k3nam
k1na
k4nap
3k4nar
5knec
k1ne
k5nem
k1ni2
5kni4e.
kn4ie
knip1
4k5ni1v2
3knol
k3no3te
k1not
2k3num
1ko
ko4b4l
k2o3b
k4oc
2k5o2c3t4
4k1oef
ko4e
5koek
koe4k2e4t
koe1ke
koer4s5p
k4oer
koe4rs
koes3
koe3tj2
koets5t4e.
koet4s1te
koe4ts
koets2t
ko1ge4
5ko5gr4
3k4ok
1ko5ko
ko1l2e2
ko3len3
2ko2lm
5ko3lo
ko4ly
k4o2m3a
4ko2m5g
ko2m5p
k3om3s2l4
ko2ms
kom4s4tr4
koms4t
4k3o2m1z
kon1ge4
ko2ng
k4o1ni
k3on4t3b
ko2nt
kon4t3j2
kon4t3r4
ko4o4
2k1oo4g
kooi5tj2
ko4oi
koo4it
koo4t3
koo4t4j2
k2o3pa
4ko2p1b4
4k3o2p3d2
ko1pe
ko5pe4n.
4ko2p1g
3ko5pi
5k4op1j
ko2p3l2
2ko4ps
4ko2p5z
2ko2r1d
kor5do
2k1or1g
2k3ork
kor4s5t4e.
ko4rs
kor1s4t
kor3s1te
kor4ta
ko4rt
kor4t3o4
kor4tr4
ko3r1u
3k4o4s3
4k3o4s.
ko4s4j2
ko5sje1re
kos1je
ko1sjer
ko3s2o4
4ko4s3s4
kot4s2t
ko4ts
kot4s5t4e.
kot3s1te
4k1o2v2
4k3o4x
2k3p
kp4i3s
k4plam
kp2l2
kpren4
kpr4
1kr4
3kra
k5r4aad
kr2a4a4
kra2a2ds5
kra4b
4k5rad
k5ra4nd
2k1r4ea
2k3rec
4k3re1de
kre1d
k4re4e4
k5ree2p
kree2t3
k3r4ef
k2r4e1g
2k3rel
2k1r4ic
k3ri2jk
kri2j
k3rijp
krij4t
krij1t5j2
k4r4it
k5ri4t3m
kroe2t5j2
kro4e
2krol
k4ron
k4ro2n3t
5k4r4oon
kro4o2
kr2o3p3a
kro4to
2krou
k3ro5v2
3k4ru
k5r2u2b
5kr4u4is
kru2i
kru4l
kru1l5a
2ks
k3s2al
k4s3a2lm
ks3an
ks3a2p
ks1ar
ks3as
k3s2e2
k5sec
ks3e1d
k4s5e4i.
kse2i
ks3ep
k4ser1v2
ks3e4t
kse3v2
ksges5t
k4s5g
ks1ge
ks3ge1s4
k4si
k5sil
ks1in
k5s4i4s
k5s4it
ks1j2
k1sla
ks2l4
k4s3la1b
k4sl4an
ks3le
ks3li
k4s1mo
ks1m
ks3na
ks2n4
ks3no
ks3nu
kso4
ks3om
k5so2ng
k2s3p4a
ks5pa4nd
ks4pan
k4spar
k1spe
k3spi
ks3po4o2
ksp4o
k5spor
ks3pot
ks3pru
kspr4
k3s2p4u
k4s5s4
ks4t
k1sta
k5staan
ksta4a4
k5s4taa1t
k1s1te
k4s5tec
k4s4t3e1d
k3st4en
k4s5te2nt
kst2e4r
kster5a
k4ste2r5r
k4s3th
k3s3ti
k3sto
ks5ton
k5s4to4o2
k4s4top
k5stot
ks5trek
k1s4tr4
ks3tri
k3s3tu3e
k1stu
ks2t5u4it
kstu2i
k1s4y
4kt
k1ta
kt3aan
kta4a4
k3taar
ktaa1t5
k2t3ac
kt3a4rt
k3te
k3te2c
k4t3e1co
k4tex
kt1h
k5ti2j
k3ti
kt3im
kt3in
k5t4it
k1t3j2
k1to
kt3om
kto4p
kt4or
kt5o2r1d
k4t5or1g
kt5o1ri
kt3o4v2
k1tr4
kt3res
k5tro2l1l
kt4r2o3s
k3tu
1ku
ku5b4e
k2u2b
ku4i2f
ku2i
2ku4it
k2u5k
k2u5me
3k4u2n
4k5u1ni
5ku2ns
ku2r
ku3ra
ku3r2e
ku4r3s
3ku2s
kut3
2kû
2k1v2
k3v4e
kve2n4t3
5k4waal
kwa4a4
2k3wac
k2w2ad
k1w4ag
5k2wal
5k4wam
3k4w4ar
k5wa1re
4kwa1t
k3wee2r
kwe4e
2k3w4e1g
k1we2i
5kwel
kwen4s4t
kwe2n
kwe2ns
kwens5t4e.
kwen3s1te
4k1wer
5k2we2s3
kwe1s5tr4
kwes2t
5kwe4ts
k2w4ie
k3wij1z
kw4i2j
k4w4i4k
2k3wil
2kwin
k3wi4nd
4k1wo
ky3
2k1z
4l.
2laan
la4a4
4laa4nd
l3aa4n3h
laa5r3e4
la4ar5tj2
laa4rt
laat5sta
laa1t
laat2s2t
laa4ts
l3a2bon
la1b
lab4o
2lac
la4ca
5l4a4c4h.
la4cha
5l2a1ch4e
lach5te
la4c2ht
lach4t4s
l4a3ci
la2d5a
la4de3t4
la1de
2la2d1j
4la2d1m
la2d3o
4la2dr4
l2a2d5s
la2du
4la2d1v2
3l4ae3
2laf
l4a2fa
la3f3l2
l4a1fo2
4l3a4f3s4
la2g3a
la4ge2nt
la1ge
la2go
la2g3r4
la4gs4
lag5s1a2
la2k3a2
la4ki
la3kr4
2lal
3l4a4ld
la1l4o
lam4p3j
la2m1p
lam4p5l2
lam4po4
lam4s3p
la2ms
l4an
4la2na
la2n3ac
3la4nd
l4an4da
land5a4a4
lan4d5o4o2
lan1do
lan4d3r4
lands5t4e.
lan2ds
lands2t4
land3s1te
la4n3ec
la1ne
lanel5
5lan3g4e.
la2ng
lan1ge
lang5l4
lang5s3p
lan4g2s
lang5st2a
langs2t
lan4k3a
la4n1k
lan4k3l4
lan4k3w
4lan3n
la4nor
la1no
l4a2n2s
lan1s3l4
lan4s4t
lan4t3j2
la2nt
lap3ac
la1pa
l4a3pi
l2ap3l2
lap3o4
la5pre
l4apr4
l2a2p3u
la3q
lar3da
la2r1d
2lar1m
4lar4m.
lar5s4t
la4r2s
l4as3a4
l2a3se4
la2si
las3to
la2s2t
5la2s4t5t2
la3te
la1t
la4t3h4e
la2t5j2
la4t3ro
latr4
4la4ts4
lat3s3l4
2lau
5lauf
lau4s2t
l4aus
l2auw
la3v2
lava3
la4vo
5l2a1w
l4a1z
4laz2i
la4zi2j
2l1b4
lbe4r4t
l3b4e
lber1t5j2
lboo4t4
lb4o
lbo4o2
2l1c
l3ce4l5
l1ce
4ld
l5daa1t5
l1da
lda4a4
l2d3ac
ldak4
l4d3alf
l4da4r
ld3ar1c
l3d3a1ri
ld3a4rt
l2dau
ld3e1co
l1de
lde2ks5
l5dek
l4d3e4z
ldi3a
l1di
l4d5oef
l1do
l3d4o4e
l2d3o2li
l2d3om
l2d3on
ld3oo4g
ldo4o2
l4do4p
ld3o1pi
ld3o2r1d
l2d1o2v2
l3dr4
l5dra1de
l3dra
ld3ram
ld5ra2ng
ld3ra1t
l2d1re
l5d2re4e
ld3ri2j
ld3ro4e
ld3rol
ld3rom
ld3ru2i
ld3s1a2
l2ds
ld3s4l4
ld3s1ma
ld2s1m
ld5sp
ld5s1te
lds2t4
l3du
l2d3u4it
ldu2i
ld3u4u4
l2d1w
l4e2a
le4a1ne
le3an
le3a1t
leba4l
le1b
le3ba
lecht5s2t
le4c2ht
lech4ts
le4e4
le4e1g3
lee1ge4
lee3g5i
4lee2k1h4
lee2k
lee5l
l4eem3
3lee2n
4leep
lee1p3o4
lee3s5e
lee2s3
lee3s5l4
lees5p4o
lee3s4p
2leeu
2le4f1f
lega5s4
l4e1g
le1ga
le4g3e4c
le1ge
le2g3l4
le4go
le5g4o.
le4g5s4
3lei1di
le2i
l4eid
4leier
le4i1e
4leig
lei5tj2
l4e4it2
lei4t5s3
le4ko4
4le2ks
lek5s4tr4
leks4t
5le4ld
le2le
5l2e1li
l3e4lp
le4n3a4d
le1na
le2n3a4k
3le1ne
le4n3e4m
len5kw
le4n1k
le2no
le2n3o2p
len3s1f
le2ns
len3s3m
4l3en5t2h
le2nt
le5o
4le4p.
3le1ra
le4r3a4k
le5re2i
le1re
le4r3e4v2
ler5g4
le3r4o
le4r1on
ler4s2l4
le4rs
ler5sp4o
ler1sp
4l3er4ts
le4rt
le2s
le4sa
le3sc
les5e4t
le3s4e
le3s4h
les3m
le4sp
le3spe
4l3essa
le4s3s4
les3t
les4ta
les5ta4a4
le5s4t4el
les1te
le3s4tr4
le4s3u
le4t4h
le3t3ha
le3t4i
le5tin
le4to4p
le1to
le2t3r4
le3t4re
let4s2t
le4ts
lets5t4e.
let4s1te
le2t3u
leu3k3o
le2uk
leu1m3a
le1um
leu1r4o
leus4
leu5s3te
leu4s2t
5le4u1z
leve2n4s
le1v2
lev4e
lev4ink5j
le3vi
levi4n1k
4lex1c
4l1ex1p
l2fac
l1fa
l3f4a3g
lfa3s
l2fau
lfe4n
l1fe
l4f3e4nd
lf3e1ne
l2fe2z
lf3li
lf2l2
l3f4lo
lf3lu
l4fo
l5fo4e
lf3o4l
l2f1o4p
lf5o2r1d
lf4or
lf5or1g
l5fou
l1fra
lfr4
l3fru
lf4s5e2i
l4f1s
lf3se2
lf4s2l4
lfs3le
lf2s3m
lf4s1o4
l4ft4
lf5ta
lf5t1w
l2f3u4u4
l1fu
2l1g
l5gaar
l1ga
lga4a4
l4gap
lge4n5a
l1ge
l3gla
lgl4
l3g4o4e
l1go
l3g4og
l3g2o4o2
l4g3s4
lg3se5
4l1h
1li
li3a3g2
li1a
li3am
licht5s2t
l4ic
li4c2ht
lich4ts
3lid
5li4d.
5li2d1m
li2d3s4
lie4g3a
l4ie
li4e1g
lie4gr4
lie3ka
li2ek
lie4sp
lie3s4t
lie4to4
li3e2u
3li1è1
3li4ft
l4if
l4ig
li3go
lij3k3a
li2j
li2jk
lij4m3a
4lij2m1v2
5l4ijn
4lijp
3li2j2s
lij1st5a
l4ijs2t
4lijt
4l3ij1z
li5kr4
l4ik
lik5sp
li2ks
li4k1w
li3kwi
li1m4a
li3mi
2li2m1p
lim4p3j
lin4da
li4nd
4l1i2n3f
4l3i4n3h
li5ni
lin4k3a
li4n1k
3lin3n
l3in1na
2li2n3r
2l3i2ns
lin4t3j2
li2nt
l3i2n1v2
4li2n1z
li3o5b
l4i1o
li5om
li5o5s4
li3ot
li2pa
li3pi
li2p3l2
li5s4e4e
l4is
li3se
2liso
l5i4s1w
li1t2h
l4it
lit3r4
lit4sa
li4ts
lit4s2l4
lit4s2t
lit4s5t4e.
lit3s4te
lit5st4en
2lix
4l1j2
l2k3af
l1ka
l4k3a4n1k
lk3ar1m
lk3a4rt4
l3ke
l4k3e2i
l4k3em
lke1n5e
lke2n4s
l4k3ep
l3ki
l5ki2ng4
lk3la4a4
l1k2l4
lk3lag
l5k4las
l4k3le1v2
l5k4lim
lk1li
l3ko
l5ko4e
lk3o2nt
lko4oi5
lko4o4
l4k3o2p1b4
l5kor
l5kou
l5kra
l1kr4
l2kre
lk3rep
lk3res
lk3ri2j
l2k3ro
l2k2s
lk4s2e2
lk4so4
lk3son
lk3s3o4o2
lks5ta4a4
lks4t
lk1sta
lk3s1te
lks5t4el
lk1s5tr4
l4k3u4u4
l1ku
l3kw
lk3wi
l3ky3
2l1l
l5la
lla3d
lla3g4
lla5tr4
lla1t
l4l3eig
lle2i
lle3k
ll4el
lle5o4
lle4r5on
lle3r4o
lle3s4m
lle2s
lle5t4h
llev4ie5
lle1v2
lle3vi
l3l4i
l3lo
llo5f
l5lon
ll3s1h
l4ls
2lm
l3ma4a4
l1ma
lmaa1t5
lm3a4ca
lm3af
lma5ï4
l3mak
lm3ar1c
lm3a4rt
lma3s2
lm3au
l3me
l4me1d
lm3e1di
l4m3ep
lm4e2s
lme5te
l3mi
l3mo
l5mog2
l2m3o1li
lm3or
lmro4z
l2m1r
lm5s2c
l2ms
lm3s1h
lm3su
2l3n
l3n4i4s
l1ni
l4o3a
2l3o4b1j
l2o3b
lo4bo4o2
lob4o
loe4d5a
lo4e
loe1d
loe4d3r4
4lo4e1g
loe4gr4
loen4s4t
loe2ns
loens5t4e.
loen3s1te
4loes
l3oeu
5loe1v2
lo4fa4a4
lo1fa
lo4f5d2
lo4f4s4
log4
log5l4
lo3go
5lo1gr4
lo4g2s3
lo4k3ar
l2ok
lo1ka
lo2k3o2
lo4k3r4
lo2ku4
2lo2l
lo3la
l3o2m3l
lom4p3j
lo2m1p
lom4p3l2
l3o2mt
l3o2m1v2
4l3o2m1z
3lo4n.
4lo4nd
5lo2ng
lon4ga4a4
lon1ga
lon4g3o
lon4g2r4
lon3o
2lo2nt
lon4t3j2
3lo2ok
lo4o2
loo5pi
loo4p1
3loos1h
lo2o4s
loo3t3e
loo4t
l2o3pa
4lo2p1b4
l3o2p3d2
lo1pe
2l3o2p1h4
2l3op3l2
lop4la
2l1o2p1n
lo3p2r4
4l2o4p1t
4l3o2p1v2
4l3o2p1w
2lor
3l4o4r.
lo3re
4l1or1g
lo3ri
l4o1r2o3
3l4o4rs
lo3r1u
lo3spe
l2os
lo2s3t4
los5to
lo4s5tr4
lo5s2u
l4o2ta
lot3a4l
lo4te4t
lo3te
lo2t3h
lo4t3j2
lo4to2f
lo1to
l2ot3r4
l4ou3s
lo3v2
2lov4e
3lo5z
4lp
l1pa
l3pa4a4
l4p3aan
l1p3a4g
lp3a4m
l3par
l3pa4s3
l1pe
lpe2n
l2pex
l3pi
l5p4i2ng
l2p3i2ns
lp3j
l1p2l2
l3p4la
l4plam
l1po
lp3of
l3pom
lp3on
l2p3ope
l3p2os
l3pot
l1pr4
l4p3ram
lp2ra
4l3r
lra2a2ds5
lr2a4a4
lr4aad
lr2u4s5
4ls
l4s1a4a4
ls1a2d
ls3a2g
l1sam
ls3an
l3sa2p
ls3as
l2sa1t
ls4cor
l1sc
l2s1co
l2s4c2u
ls3e1co
l3se
l4s3e2d
l4sef
l5s4en
l4s3e2p
lsge4s2t
l4s5g
ls1ge
ls3ge1s4
l3s2hi
ls1h
l3si
l4s3im
l4sin
ls3in1j4
ls3i4n1k
l2s3i2nt
ls4j2
ls5ja
l3s4kel
l2s1k2
ls1ke
l3s2k4i
l1s2l4
l3sla
l2s4le
ls5le1d
ls5le4e4
l4s5l4e1g
ls5len
l2s3li
ls4lin
l3slo
ls4maak
ls1m
ls1ma
lsma4a4
ls4me1d
ls1me
ls4me4e
l3s3mid
ls1mi
ls3na
ls2n4
l3s1ne
l3sno
ls3nor
l3so1c
ls3of
l3s2ol
l2s3op
ls3o4r
l2s1o2v2
l1sp
l2sp4a
ls3pac
l3s4pan
ls3par
ls4pe
l3spi
ls3p1li4
l2s1p4l2
l3s2po4o2
lsp4o
l4s5poo4t3
l3spor
l2spr4
ls3p2ra
l1s2t
l3sta
l4staf
l4s4tak
ls5ta4k.
l3s1te
l4stek
l4st4e1v2
ls4ti
l3sto
l5str2a4a4
l1s4tr4
ls5trak
l5stra1t
l3stu
l2s5ty1
l2su
l3sur
ls3u4s
l3s4y
4l1t
lt4a4a4
l2t1ac
l4tam
l5ta1me
l5t4an
lt4han
l2t1ha
l4t3hi
l2t3ho
l3thu
lt2o4l
l2t3o1li
l2t3o4v2
l3tr4
ltra3s
l4t3ru2g
lt3s2l4
l4ts
lt3sp
lts5t4e.
lt3s1te
lts2t
l3tu
l2u4b1
lu3b5e
lu3b5l
lu1en
3lu4i.
lu2i
5lui1a
5luid
lui2d4s3
5lui4e.
lu4i3e
2lu4it
lu2k2s
l2uk
luks4t
lu3na
3lu2n2c
2l3u2ni
lu3s4ta
lus2t
lu3ta
lu2t3j2
lut4s2t
lu4ts
lut4s5t4e.
lut3s1te
lu3wi
lve2n5s
l1v2
lv4e
lver1a4
lve2r
l1w
1ly
ly5i
ly3s2t
lys3
4l1z
lzo4oi5
lzo4o2
4m.
1ma
maas3
ma4a4
maat5s2t
maa1t
maa4ts
m3a2c1t
2m3a2d1v2
ma5esto
m4ae
mae2s3
mae4s5t
m3af3l2
ma3f1r4
2m3a4f3s4
4m3a4f1w4
m4ag
m4a3g1l4
ma5go
ma3gr4
ma1ï4
ma5ka2
ma5ke
5ma3k4r4
ma3k1w
ma3l4a
ma5lac
ma4l5e2nt
ma1le
mal5s2t
ma4ls
5m4a4n.
ma2n3ac
ma1na
m3anal
m4an5da
ma4nd
man5do
man2d4s
5m4an3n
ma5no
5m4a2n2s
man4se
mans5e4e
man4so4
mans3p
man4s4t
man1s5ta
man4th
ma2nt
man1t4r4
ma5pa
m4a3pr4
ma3q
m4a5ri
mariet5
mar4ie
5m4ark2
mar3s1h
ma4r2s
mar4s5t
mar5ti
ma4rt
m4a1so
ma3s4p4o
mas1p
5ma4s3s4
ma4s3te
ma2s2t
m4a3s4tr4
ma5ta
ma1t
5mat2er
ma1te
ma2t5j2
ma4tom
ma3tr4
mat4s2t
ma4ts
mat4s5t4e.
mat3s1te
ma3v2
4m1b
m5b4l
mboo4t4j2
mb4o
mbo4o2
mboo4t
mbo5s3t
mb2o4s
m3b4r4
2m1c
2m1d
m5da
mdi3a
m1di
m3d4i4s5
m3do
m2do3p
m3dr4
m3dw
1me
me1c
me5de
me1d
5medi2a
me1di
5med4i3u2
me4e5g
me4e
mee3k4r4
mee2k
mee5las
mee2l
mee1l3a
me4e3lo
mee5re
mee2r
mee5r3i
5mee2s3
mee3s4t5al
mees2t
mee5s4to3v2
mee5s4tr4
m5e4g.
m4e1g
me3g2a
mega5s4
m5e4gd
m5e4g3g4
m5e4gt
me4i
mei2n
mei5tj2
m4e4it2
m2el
me4l4as
me1la
me4l5a4s.
mel5dr4
me4ld
mel4ko
mel4kr4
5m4e1lo
mel3s4m
me4ls
me4mi
3men
m4e4n.
me3na
me2n4a2s2
men3g5ra
men3g4r4
me2ng
me4n5k
me5nor
4m3en1q
men4s5u4u4
men3su
me2ns
men4t3j2
me2nt
men4t3w
me5nu
me3p2j
2m3e2q
me1ra
me4r5aak
me1r2a4a4
me4r3a4k
me4r4am
mer5an3te
mera2nt
me4rap
me3rau
me4ra1v2
mer3e2i
me1re
5merk
mer4k2l4
mer4k2n4
mer4kw
mer5oc
me5ro2ng
mer1on
me3ro4o2
4m3er2os
me3rot
mer4si
me4rs
mer4s2l4
mers5m
mers5ta
mer1s4t
me2ru4
m4es
me3s4h
me4s4l4
mes5li
me5slo
mes3m
me3so
me4sp
mes3p4a
me5spe
me5spot
mesp4o
me5st4el
mes2t
mes1te
mesto4
mes4t5o3v2
me3stu
me5ta5n
me1ta
me3t4h
3me3ti
me5tr4
mets5t4e.
met4s1te
me4ts
mets2t
mev4e4
me1v2
m3e4ven
2mex
3mé
3mè1
3mê1
2m1f
mfa3t
m1fa
mf4l2
mf3li
m2f5l4ie
m5fo
2m5g
mger4
m1ge
2m1h
1mi
3mid
4mi4d.
5mi4d3d4
mie5k2l4
m4ie
mi2ek
mie3s2t
4m3i2js
mi2j
4m3ij1z
mi3k4n4
m4ik
5mi3li
mi3lo
mim4ie4
mi1mi
m3i2m1p
mi5nar
mi1na
2m1i2n3f
5mi2ng
4mi4n3h
2m5i2n3r
2m3i2ns
mi5nu
4m3i2n1w
m4is
mi2s5f
mi2s3i
mi3s4la
mis3l4
mi4s3t
mi5stra
mi3s4tr4
mis5tro
mi3t4a
m4it
mi1tr4
mit4s2t
mi4ts
mit4s5t4e.
mit3s4te
mit5st4en
2m1j
2m3k2
mka4ar4t5j2
m1ka
m5kaa4rt
mka4a4
2m3l
2m1m
2m1n
m5na
1mo
5m4o.
m4o3a
5mo1da
5mo1de
moe2d4s
mo4e
moe1d
2moef
5moe2i
moer1s5t
m4oer
moe4rs
moe2s
moe1s3p
moes4te
moes2t
mog2
5mo1ge
moge2n4s
mo3g1l4
4m2ok
5mo1le
2mo1li
mo4l4ie
mol4m3a
mo2lm
4mo4l1t
3mom
4m3o2m1v2
mon2d3r4
mo4nd
mo5no
5mo2ns
mon4so
mon5ta
mo2nt
3mo4oi
mo4o2
2mop
m2o3pa
m1ope
m4o4p3p
mo4p4s
mo3ra
mo3r4e
mo3ro
mor4sp
mo4rs
mor4s4t
mor4s5t4e.
mor3s1te
5m2os
m4o5s2c
mo4s5l4
mo3s4ta
mo2s3t
mo3t2h
mo4t3j2
mot3ol
mo1to
mot4s2t
mo4ts
mot4s5t4e.
mot3s1te
2m3ou1d
5mouw
mou4wi
mo3v2
m3o4x
2m1p
m2p3ach
m4p3af
m5pan
m4p3ar1m
mp5ar4ts
mpa4rt
m4p3ec
m5pen
m4p3er1v2
m2p3i2ns
m3p2l2
mp3lam
m5pl4an
mp3l4e1g
mp3le2i
mp3le1v2
mp3l4ie
mp1li
m4plu
mp5ol4ie
m1pol
mpo1li
m5pon
mpo2n4g
m2p3ope
mp2r4
mp3rec
mp3re1d
m5pres
m4ps2
mp5sc
m5p4se
mp3s1h
mp5su
2m1r
2ms
m3sam
m4s3a2na
ms3a2p
m1s2c
m2s3co
m2s3c2u
ms2j2
m3s1je
m1s2l4
m2sle
ms3len
m2s3l4ie
ms1li
m3s2m
ms3ma
m1s2n4
m3s3ne4e4
ms1ne
mso4
m3s2ol
ms3or
m3s2p
ms4t
m3sta
m1s1te
m4s5tec
m5st4el
m5st4en
m1s3ti
m1sto
m2s5toc
m4s5ton
m4s4t5s4
m3s4y
2mt
m1ta
mte5re
m1te
mt2er
m3tes4
mte5s3ta
mtes2t
m1th
m1to
m3tr4
m1tu
1mu
mu5da2
mu1d
mue4
5muil3d4e.
mu2i
mui4ld
muil1de
2mu4it
2m2uk
mu4l3p
mu2m3
mu3n4o
mun2t3j2
mu2nt
mu3sa
mus5ta
mus2t
5mut
mu2t3j2
mu4ts2
mut3s5te
mut2s2t
3mu4u4
5m4u1z
2m1v2
mva1ri5
mv4e4
mve4e3
mve1r3e
mve2r
2m1w
1my
my3e
2m1z
mz4e4
mzet5
4n.
1na
3n4a.
3naal
na4a4
5n4aam
4n1aan
2naap
n4a4a4r.
4n3aa2r1d
5naa4r2s
naar4s5tr4
naar1s4t
na4ar5tj2
naa4rt
5n4aa2s2t
5naa1t
n3a4b1d4
na1b
5na3b4e
2nac
na2ca
nacee5t
n4a1ce
nace4e4
n2a3ci
3n2a3co
4n3a2c1t
na5d4a
na1d4e
3na5d4e.
5n4a5den
3na5de2s
3n4a1d4i
4n3a2d1m
na5dra
na1dr4
2n1a2d1v2
5n4ae
n3a3ë
4n1af
na3f4lu
naf3l2
n2a3g4
n2a1h
3nai
3na1ï
n2a1ke
n4a3k4l4
na3kr4
n3a2l1b4
3n4a1le
5nalen
4n3alf
n3a2lm
2na1ly
4nalys3
3nam
4na4m1b
name5s2t
na1me
nam4es
n4a1m4i
n3a2m1p
1n3a2na
n3a4n1k
3na2nt
5nan4t.
5nan3te
n5anten3n
nan5t4e4n
nan4t3j2
2nap
nap3ac
na1pa
3n2a3p4l2
n4a3p4r4
na2p3s
nap5s4t
2n1ar1b
5nares
na1re
2n3ar1g
narie5t
na1ri
nar4ie
2n1ar1m
3na1ro
4na4r2s
nar4s4t
nar4s5t4e.
nar3s1te
nar5st4en
4n1a4rt
nas2
3n4a3sa
na1s4l4
na1s1p
n4a3s3ta
na2s2t
na3s4tu
n4a4t.
na1t
3n4a3ti
na2t5j2
4n3a4t3l
na3to
na4ts4
nat3sp
5nau.
5n4aus
2na3v2
5naven
nav4e
3n4a3vi
3n4a3z4if
na1z
naz2i
na4zi2j
2n1b
nbe5s2t
n3b4e
nbe1s4
nbe5t2
nbot4s5t4e.
nb4o
nbot4s2t
nbo4ts
nbot3s1te
2n1c
n3ce
nces4t
nce2s
n3ch4e
n4c2ht2
nch5t1r4
nch3u
n5co
4nd
n5d4a.
n1da
nd3aan
nda4a4
nd5aas
n4d3ab4o
nda1b
nd3a2c1t
nd5adel
nda1de
n4d3a1dr4
ndags5p
n3dag
nda4gs
n4d3alf
n2d3a2lm
n4d3a2na
n4d1ap
n2dar
nd3a4rt
n4das3
nd3a4s3s4
nda3s2t
n4da1v2
n4d3a4z
n3de
n4d3e1di
nde1d
n4d1e2i
nde5l4a4a4
nde1la
n4d3e2m1m
n5d2e4n.
ndera4
nder5aal
nde1r2a4a4
nder5al
nde4r5an
n4d5e4rec
nde1r2e
nder5i4n.
nde3ri
nder5o2g
nde4t4en
nde3t4
nde3te
ndi3a
n1di
ndie4tj2
ndie2t
nd4ie
n4di2js
ndi2j
nd5ij4s.
n4d3i4n1k
nd4i3o
n3d2ji
n2d1j
n5d4o.
n1do
n5doc
n4d5of
n2d3o2li
nd3o2m1d
n4don
n5d4o1na
4nd5o4nd
n5do2ns
nd3o2nt
nd3oo4g
ndo4o2
nd3ope
n2dop
nd3o4p3p
n2d3o2v2
n4d5rap
n1dr4
n3dra
nd3ra1t
n2d1re
nd4rek
n4dres
nd3rot
nd3ru2g
nd3s4c2u
n2ds
nd1sc
nd4sec
nd3se2
nd5se4t
nd3s4i2
nd3s4jo
nd4s3j2
nd4s1m
nd3sp
nd4sp4o
nd4sp2ra
ndspr4
nds5t4aal
nds2t4
nd1sta
ndsta4a4
nd3su4
n2d3u4it
n1du
ndu2i
n2d3u4r
nd5u1r2e
n4d3u4u4
n2d1w
n3dy
1ne
3n4e.
ne5ac
n4ea
ne3am
nebe4s4
ne1b
ne3b4e
3n4eck
ne2c4l
ne4d4it
ne1d
ne1di
ne3do
n3e1du
ne5d2w
ne4e4
4nee1d
nee5k
nee4l5d
nee2l
ne4e1l3o
3n4eem
4n1ee2n
nee5r3i
nee2r
nee5s4e
nee2s3
nee1t3a
nee2t
nee3t5o
nee3t3r4
nee4t5s
4n1e4f1f
n4e3g2
ne4gel
ne1ge
negen5e2n
1ne3ge3ne
nege4re
4n1e2i
5n4eien
ne4i1e
n5eier
n2eig
5nei4gd
5n4e4i5t2
ne4k3r4
ne2la
4n3e2lem
ne1le
4nelf
3nem
4n3e4m1b
5n4e1me
4n3e4mig
ne1mi
4n3e2m1m
4n3e2m1p
ne2n
3n4e4n.
5ne2n1b
5n4en4d.
ne4nd
nen5d4o
ne4n5e4n5k4
ne1ne
nene2n
ne4ni
ne5nig
ne4n5k4
nen1o4
5ne2n1p
nen5t4a
ne2nt
ne5oc
ne3o
ne5o3k4
ne5om
neo5p
ne5o3s4
ne5ot
ne1p3ag
ne1pa
ne3pe
nep4i3s
ne1pi
ne1ra
ne3ra4d
3n2e5re
n3er3fe
2ner1g
ne4r3id
ne3ri
ne3r2os
ner4s2l4
ne4rs
ner4sp
ner4s4t
ner3s5te
ne1r3u
ne3ry
3nes
ness5a
ne4s3s4
ness5t
ne3sta
nes2t
nes3te
nes4te2i
ne5s4tek
ne4t2er
ne3te
net3on
ne1to
net4si
ne4ts
ne2u
4ne1um
ne3u2ms
neu5s3te
neu4s2t
2nex
3né
2n3f
2ng
ngaa2t5j2
n1ga
ng4aa1t
nga4a4
n2g1a2d
ng3af
ng3a1na
n3gan
n4ga4p
n2gar
nga5s3l4
nga4s
n3ga1v2
nge4ad
n1ge
ng4e3a
n4g3ee2n
n3ge4e4
ngels5t4e.
ngel5s1te
nge4ls
ngel1s2t
n4g3e4m1b
n5gen
nge4ra4p
nge1ra
nge4ras
n4giger
n1gi
ngi3ge
n4gi4gs4
n2g3i2j
n4gi4nd
ng3i4n1k
n4g3i2ns
ng4l4
ng5lad
ng5lam
ng5l4an
ng5le1d
ng5leu
ng2li
ng5lin
ng5lop
n3go4e
n1go
n2g3of
n3go3ï
n2g1on
n2g5oor
ng2o4o2
n2g5op
n4g3o4re
ng3or1g
n3got
n3gr4
ng3rac
n3gra
ng3rad
ng3rai
n4gra4s3
ng5ra4s3s4
n2g4re1d
n4g4ri
ng5r4ie
ng3ri2j
n5gron
ng3ru2i
n4g2s
ng4se4
ngs5lop
ngs3l4
ngs4lo
ngs5lu
ng4s5ne
ngs3n4
ngs5ta4k.
ngst2a
ngs2t
ngs4tak
ngs5ta1ke
ngs5trek
ng1s4tr4
ng5stri
n2g3u4it
n1gu
ngu2i
4n3h
nhek5
nh4e
1ni
n4i2d
nie5kle
n4ie
ni2ek
nie4k2l4
ni3e3ri
nie4s3p
nie4tr4
3nie2u
ni4g3e4e4
ni3ge
ni3g3ra
ni1gr4
nij3f
ni2j
ni2j3k
2n3ij1z
ni5kr4
n4ik
ni2k4s
nik3s3p
3nil
3n4i4m.
5n4i2m1f
n3i2m1p
2n3i4n.
n3i2n1b
2n1i4nd
2n1i2n3f
nin4g3r4
ni2ng
2n3i4n3h
n3in1j4
2ni2n3r
2n1i2ns
2n1i2nt
2n3i2n1v2
n4i3o
ni4o4n.
ni3on
ni4o1ne
ni5or
ni5o5s4
ni1p3l2
3n4is
ni4sau
ni1sa
ni4s2el
ni3se
ni4s3e1v2
ni3s1fe
ni2s1f
ni2s3i
ni4s3l4
ni4s5n4
ni3s4ot
ni5st4el
ni2s3t
nis1te
nis5to
ni3t2h
n4it
ni1tr4
ni4ts4
n1j4
n3je
nje4s4
nje5sp
nje5s2t
nje3t
4n1k
nk3aan
n1ka
nka4a4
nk5aa2r1d
nka4ar4t5j2
n5kaa4rt
n2k3af
n5k4am
n4k3ar1b
nka4r5s
n4k3as1p
nka4s
n3kef
n1ke
n4k3e4f1f
n2k3e2m1p
n3ken
nke1n4e
nker5ku
n2k3i2d
n1ki
nk2j
nk3lad
n1k2l4
n4k3lod
n4k3l4uc
nk3lus
n2k3na
n1k2n4
n3k1ne
n4ko4g
n1ko
nk3o1ge4
nkoo4t5
nko4o4
n3k4ra
n1kr4
n4krim
n2k3rol
nk5s2e2
n2ks
nk5si
nk3s2l4
nk3s4m
nk3s2n4
nk4s5o4
nk1sp
nk1s4t
n4kw
nk3wa4a4
nk3we1z
nk3wi
2n3l
2n3m4
n3n
n5n2e
nnee5t
nne4e4
n1ne3ne
nne2n
nne1po4
nne4p5ol
nne5te
nne1t4j2
n1n4i
nnin4g5r4
nni2ng
n3noo4t5
nno4o2
nno5v2
3n4o.
1noc
1no3d
2noef
no4e
noe2n5s
noes3
noe4t5s
n5of5fi
no4f1f
n3o2ge
n5o1gi
1no1gr4
3no3ï
no3k4l4
n2ok
no3k2w
no2li
1no3lo
1nom
4n3o4m.
n4o1ma
n3o2m3l
n1o2ms
n3o2m1v2
2n3o2m1w
2n3o2m1z
3n2o4n.
3n4o2n1b
3n2o2n1c
4n5o4nd
n4o5ni
4no2nt
3n4oo4d
no4o2
4n5oof
4n1oo4g
nooi5tj2
no4oi
noo4it
3noo4t3
noo4t4j2
3n2o3pa
no4p3a4s3
4n3o2p1b4
no1pe
n1o2p1g
n5op3lei1di
nop3l2
nop3l4eid
nople2i
no4po4o2
no1po
no4por
2no4ps
2n3o2p5z
2no2r1d
no3re
2n1or1g
1nor1m
4no2r5r
3no4rs
3no4r1z
1n2os
no3s3f
no3s4n4
no3sp
1not
3n4o1ta
not5a4p
5no3ti
no4t3j2
n2ot3r4
3n4ou.
no3v2
3n2o3va
no4v4e
3no4x
3no1z
2n1p
nper4s5t4e.
n3pe4rs
nper1s4t
nper3s1te
np4i4s5
npoor4
npo4o2
npoor4t5j2
np4oo4rt
n3ps
2n3r
nraads5l4
nr2a4a4
nr4aad
nra2a2ds
n5re
n5ri
2ns
ns3a4d
n3sag
n1s2al
ns3a4lp
n1sam
ns3an
n3s4a2n1c
n1sa2p
n3s4cal
n1sc
n2s1ca
n5scho
n3s4ch2
n2s4ci
n4s1co
nsee5t
n3se
ns4e4e
n4sef
ns4e4g
ns5e3ge
n2s3e4is
nse2i
ns5e2m1p
ns4em
n3si
ns3i1di
n2sin
n5si2ng4
ns3in1j4
ns3i4n1k
n2s3i2nt
n1sjo
nsj2
n1s2l4
n5s4l4a.
n3s4la4a4
ns5l4aag
n5s4lag
ns5l4a4p.
n3slap
ns5la4p3p
n4sle
n5s4lep
ns4let
n5s4leu
n5sli1b
ns1li
n2s3l4ie
n5s4li4ep
n5sli2m
n5s4lip
ns5lo4t.
n3slot
ns3m
ns5mac
ns1ma
n3s4me
n3s4mi2j
ns1mi
n3smol
ns1mo
n4s1mu
n1s2n4
n2s1na
n5s1ne
n4s3no3d
n4sno4o2
n4s1not
n1so
n2s3o3b
n2sof
n3s2ol
n2son
n2s3o2ng
ns3o2n1z
ns4o4p3p
n2s1op
ns4or
n2s3ou
n2s1o2v2
n4s3pa4a4
nsp4a
n2s3pad
n1spe
n5s4p4e4e4
n5sp4el
n4s3per
n4spet
ns4pi
ns1p4o
n4s3pol
n4spot
n1spr4
ns5q
n4s5s4
ns4t
n1sta
n4st5aa2n5g
nsta4a4
2nst5a4a2ns
ns4t3a4g
n3st2al
n3s1te
n4s5tec
n4st3e2i
n4s5te3ko
ns5te2ks
n5ste4n.
nst4en
n4s5te2nt
n5ste4r.
nst2er
n4s5te4s
ns3th4e
n4sth
n1s3ti
n3stig
n4stij3v2
nsti2j
n1sto
n4st5oef
nst4o4e
n4ston
n3stor
nst5ra1de
n1s4tr4
n4st4rad
n5stre4e
ns5tre4k5k4
n4s5tro4e
ns5trog
n4st5ro2o4s
nstro4o2
n2s5ty1
ns3uil
n1su
n2su2i
n3s4y
2nt
n3ta
n5t4aal
nta4a4
n4t5aa2r1d
nta4ar5tj2
ntaa4rt
n5ta1b
nt3ach
n2tac
nt4a2c1t
n4t1ad
nt3a1ga
n4t3a4rt
n3t4as
n5t4a1t
n3te
n5tec
n4t3e2i
nt4e4lo
nt4el
n5t4em
n5t4e2n
nte5n4ach
nte2n1ac
nte1na
nt4ene5t4en
nte1ne2
ntene4t
n1ten3e3te
nte5rad
nt2er
nte4r3of
n3tè3
n2t3ha
n4tho
n5thol
n5tig
n3ti
n4t3i2n1w
n2t4jo
n1tj2
n3to
nt4og
nt4ol
n4t5o1li
n5ton
nt4o4o2
nt5oo4g
n4top
nt3op3l2
n2t3o2p1m
nt3o4p1t
n1tr4
n2t3rec
nt3re2i
n4t3rel
ntre4s
nt5ri4b1b
ntri1b
n2t5ri2j
n5t4ro2o4s
ntro4o2
n3t4rou
nt3r2u4s
n5try
nts3a
n4ts
nt5s4lu
nt1s2l4
nt1s2n4
nt4s4no
nt1sp
nt4spr4
nts5pre
nt1s2t
nt5s1te
n3tu
n4t3u4it
ntu2i
ntu4n
n5t4wijf
n4t1w
ntw4i2j
n5t4w4is
3nu.
3n4uc
3nue
nu3en
nu3et
4nuf
2nu2i
4n3uil
nu2lo
3num
nu2m3a
5nu2m1m
nu2n
3nu2n2c
n3u1ni
2nu4r
3n4u5ri
nu5ro
1nus
nu4s3o2
nu3tr4
nut4s2t
nu4ts
4nu4u4
5n4uut
nuw5a
nu2w3i
2n1v2
nve5na
nv4e
2n1w
nx3
n3x4e
nxo4
1ny
4n3y1i
4n3y1o
2n1z
nze4t5s
nz4e
3ñ
4o.
4oa
o3a4a4
o2ad
o3af
o1ag
o3ah
o3ai
o1al
oa2m
o1a2n
oa4tie3v2
oa1t
oa3ti
oat4ie
o3au
o3a1v2
o3ax
2o3b
4o4b.
obal4
o3ba
oba4l1t3
3o4b1j
1o4b1li
o3b4l
ob5oor
ob4o
obo4o2
o4b5o4r
4o3br4
4o1ca
ocaa1t5
oca4a4
5o2c4ea
o1ce
o3cha
o1ch4e
o3chi
o3cho
o3c2hr
oc1ke4
4o3co
oc4o3a
oc2o3s4
o2c3t4
od5ac
o1da
o3da3g
ode4m5ar
o1de
od2e1ma
ode4mo
ode5r2e
ode2s4
odi3a
o1di
o5dru
o1dr4
od5sc
o2ds
od5se2i
od3se2
od3s4i2
od2s4l4
ods5lam
od1sla
od5sl4an
od3s1li
od5s4mak
od2s1m
ods1ma
od4s3o4
od3sp4o
od4spr4
ods4t4
od5sta
od4s1te
ods5t4e.
od5stek
od5st4en
o2d3w
o4e
oe5an
o4ea
oe3as
oe2d3a
oe1d
oeda4d
oede4n
oe1de
oe2d3o2
oe4d2r4
oe2d3re
oed3ri
oed3ro
oe2d3u
oe4d3w
oe4e
oe5e2r
oe4f1a
1oe1fe
o4e2fi
oe2f2l2
oef3la
oef5le
oef3lo
oe4f5o4
oe2f3r4
oege3l
o4e1g
oe1ge
oe2g5i2j
oe3g4i
oe2g1l4
oe4gou
oe1go
oei3i4
oe2i
oei3n
oe4i5s4
oei5tj2
o4e4it2
oei3tr4
oe4ka4a4
oe1ka
oek5erk
oe1ke
oe3k2e4t
oe2k3l4
oe4k3op
oe3ko
oe4k3r4
oe2ku4
oek1w
oe4lap
oe1la
oe4lar
oel5dr4
oe4ld
oe4l3e2i
oe1le
o3e3lem
oel5f
o4e1lo4
o4e5lo4e
oelo5p
oel3sp
oe4ls
oe4m3ac
o2e1ma
oe1m3o4
oe2n3al
oe1na
oe5n4e
oen5g4r4
oe2ng
oen3o
oen4s2n4
oe2ns
2oep
oe2p5i4nd
oe1pi
oe4p2l2
oe5plo
oe4p3r4
oe3p4ra
oe4p2s
oe4p3s3e
oe2p3u
4oer
oe1ra
oe4r2a4a4
oer5aal
oe4r3a4l
oe1r4e
oer5e4i.
oere2i
oe4r5e4i1e
oero2
o4e3ro4e
oer3o2g
oer5om
oer4s2l4
oe4rs
oer4sp
oer4sta
oer1s4t
oers5tak
oer4s5t4e.
oer3s1te
4oe4s.
oe3s2fe
oe2s1f
oe3si
oe4s1li
oe3s2l4
oe4s3o4
oes4ta
oes2t
oe4s4th
oe3sto
oe4ta4a4
oe1ta
oe2t3h
oe5t4i
oe2tj2
oe4t3o4
o4e5t4o4e
oe4t3ra
oe1tr4
oet4s3p
oe4ts
oe4t3w
2o3ë
of3ar
o1fa
of3a1t
o4fa3v2
of4d1a4
o4fd
of2d3e2i
of1de
of2d3o
of2d3r4
of4d3w
of3l2
o4f1li
o4flo
4o1fo
of3om
o3fo4o4
o2f3o4p
o3f4or
of3o4x
of1r4
o3f2ra
of5se2
o4f1s
of4s2l4
of5sla
ofs3le
of2s2p
of3spe
of2s3p4l2
of3sp4o
ofs3pr4
of3s4tr4
ofs4t
ofs5tra
4o4ft
of4tu
oft3u4r
oft3u4u4
of3u2i
o1fu
o2g5ac
o1ga
oga4l
o4g3a4l.
og5de
o4gd
og3di
oge4d4
o1ge
oge5la4a4
oge1la
ogel5e2i
oge3l4e
2ogem
o3ger
oge4r2o
oger5on
oge4s3t
o3ge1s4
2o2g5h
1ogig
o1gi
og1l4
og5n4e
o2g1n
o2g3op
o1go
og3s3p
o4gs
og3st2a
ogs2t
og4st5e2i
og1s1te
og3sto
og4ston
og4s4tr4
ogs5tro
og3u2i
o1gu
o3gy
2o1h
3o2h2m
4oi
oi3do
o4i1e
oi3j
o4i5k
o3i2ng
o4i3o4
o4i3s4
oi5sc
ois3p
oi2s3t2
oi4s5t1j2
o3ï
2o1j
2ok
o3k4a.
o1ka
o3ka4a4
o4k3aas
ok3a1b
ok3ag
o3kal
ok3a4n1k
o4k3a4z
o2k3ef
o1ke
o2k4l4
ok5let
o4k1li
ok5lu
o2k3n4
ok3o2l
o1ko
ok3o4p.
ok3o4pe
o3k4o4s5
o2k3ou
o2k3r4
o3k4ra
ok1sa
o2ks
ok3s4l4
ok3s2n4
ok5spri
okspr4
ok1s4t4
oks5t4e.
ok1s1te
ok5st4en
ok4s5tr4
ok5te
o4kt
okte4r4s
okt2er
o1ku4
ok3u2r
ok3u4u4
ok1w
ok2wi
o1la
o3l4a1b
o2l3ac
o3lal
ol3a2p
o2l3ar1m
ola3s4m
4o4ld
ol3d4o
ol3d2w
o1le
o3l4e.
ol4e5g
ol1e2i
o4l3e2ks
ol3e2m1m
o3len
o5ler
oleu2
ole3um
ol3exa
ol2fa
olf3l2
ol3fr4
olf5s2l4
ol4f1s
ol2gl4
o2l1g
ol2g1o
olg5rap
ol1gr4
ol3gra
ol4gre
ol4g3ri
ol2g3u
o3li1a
o1li
o3l4ic
o5lid
o3l4ik
o3lin
o5li2ng
ol3i2nt
o3l4it
ol3k3af
ol1ka
ol5ke
ol2kr4
ol2k4s
ol2k2v2
oll4ie4
o2l1l
ol3l4i
o3lo
o5loc
ol2o3k
ol4om
o4lop
ol3o4p.
ol3o4p3p
olo3s4t4
ol2os
o2lo4v4e
olo3v2
ol4p2ra
o4lp
ol1pr4
4o4ls
ol5se
ol4s5h
ol5si
ol1s4j2
ol3s4l4
ol3s4n4
ol3so
ol3sp
ol5st2er
ol1s2t
ol3s1te
4o1lu
o2l3u4it
olu2i
olu4r
4o1ma
om2a4a4
om1ac
om1af
o3man
4o1me
o4m3ef
om3e1la
om2el
ome2n4s
o3men
omen5st4e.
omens4t
omen3s1te
ome5ren
ome1re
omer5k2l4
o5merk
ome5sp
om4es
ome5t
o1m2i
o4m3i2nt
4o2m1m
4o1mo
omo5l
o5m2o3s
om4p5e2i
o2m1p
5omro
o2m1r
om3s2l4
o2ms
om4st4e.
oms4t
om1s1te
om3u2i
o1mu
3o2m1z
o2n1ac
o1na
on4a3g4
o4n3am
on4an
o2n3ap
ona3th
ona1t
2o2n1c
on4d3ac
o4nd
on1da
on5d4as3
on5der
on3de
ond5e3te
onde3t4
on4d3id
on1di
on4d5i2js
ondi2j
on3d5o4m.
on1do
on2dr4
on2d3re
ond3ro
ond5s3j2
on2ds
ond5slo
onds4l4
on3d4u
on4d3u4r
o5n4e.
o1ne
o3ne1b
o2n1e2c
o4n3e2i
on3erf
on3er1v2
one3s2t
o3nes
4one4t.
on1e3v2
ong5aan
o2ng
on1ga
onga4a4
ong5aap
on4g3a4p
4on5gen
on1ge
ong5le
ong4l4
on3g2r4
on4g2s4
ong5se4
ong3s3p
ong3s2t
on5i2d
o1ni
o5nig
on4k3ap
o4n1k
on1ka
onke5lap
on1ke
onke1la
on3k2i
on4k3lo
on1k2l4
on3k2n4
on5kw
on3nes4
on3n
on5n2e
onne5s2t
o4n3of
ono3l
on1on
o2n1o3v2
on3sc
o2ns
on3s4e
on5se2i
on2s2f
on3s4m
on2s3n4
on2s5op
on1so
on3s4or
on1s2p
on1s4pe
on3s1p4l2
on1s4t
on5st4en
on3s1te
on5s4tr4
4on4t.
o2nt
on4ta4a4
on3ta
3ont1h
on4ti3d4
on3ti
3on4t1s4
ont5sp
3on4t1v2
1on4t3w
o2n1u2i
o2n3u4r
o4o2
4o4o.
oo3c
4oo4d
oo1d1a
oo1d1e4
oo5d4e.
oo1d1o
oo1d1r4
ood3s4l4
oo2ds
ood3sp
4oof
oo3fi
oo4g
oo1g1a
oo1g3e
oo5gi
oo1g1r4
oo4gs4
oog3s1h
oog3s3l4
oo1k3a
o2ok
oo3ke
oo2k5l4
oo2k3s4
ook5s4t4
oo4k5w
oo4l
oo1l5a2
oo1le2
ool3e1d
ool5f
oo2l5g
oo5l4ig
oo1li
ool3i2j
ool3k
oo3l1o4
o4o1l1u
o4o1m5a4
o4o3me
oo1m3i
o4o1m1o4
oom4s5t4e.
oo2ms
ooms4t
oom1s1te
4oon
oo1n5a
oon5d4u
oo4nd
oon3in5
oo1ni
oo4n5k4
oon1o
oon5ta
oo2nt
oo4p1
o2opa2
oop5e4e4
oo1p3o4
oop3r4
oop4sp
oo4ps
oo1r3a
oor4d5a4a4
oo2r1d
oor1da
oor5dop
oor4do
oo1r1e4
oor3g4
oo1r5i
oor5k
oor5m
oor1o
oor3s4m
oo4rs
oor5s1te
oor1s4t
oor5sto
4oo4rt
oor4th
o2o4s
oos3a
oo5se
oos5n4
oo4t
o4o1t1a
oo3t3es
oo3te
oo1t3h
oo1t5o
o2ot3r4
oot4s2l4
oo4ts
o1ö
2opa
o4p3ac
op3ad
o4p3af
o4p3ak
op3a4m
o3pan
op3a4nd
op3a4t.
opa1t
op3a4t3t
3opbre
o2p1b4
op3br4
3op1dr4
o2p3d2
o3p4e.
op4e4e4
op5ee2t
op3e2i
o1p4el
o3pe4n.
3o4pe1ni
o5p4e4r.
o4pe1ra
op3e4te
op3e4v2
4o2p1f
o1pi
o5p4ic
o2p3i2d
opie5t
op4ie
o2p3ij1z
opi2j
op3i4n.
o5pi1na
o5p4is
4op1j
op3l2
op5l2os
1o2p1n
o1po
opo4e3
op1of
o5pog
o5p4oi
o5pol
op3o4nd
o5po1ni
op3o2nt
op3o2r1d
op3o4re
op3o4v2
op1r4
o4p3r4ic
o4pru
o4ps
op5s2c
o4p5se
op5si
3op1s4l4
ops4m
op3s1ma
op3s2n4
op3so
op3sp
op3sta
op1s4t
op3su
2o4p1t
4op4t.
op5tr4
op3u2i
o1p2u
o3p3u2n
o1ra
or3ach
o2rac
or3a2c1t
o4r3a2d1m
or1af
ora4g
o4r3a2l1g
o4r3a1na
o5ra1te
ora1t
or4da4a4
o2r1d
or1da
or4d3as3
or4de2n1v2
or1de
or4do
ord5o4nd
ord3or
or2d3o4v2
or3dr4
or4drad
or3dra
or2d3w
o1re
ore5ad
or4ea
4orec
ore4e4
ore4no
or2gl4
or1g
o1ri
o5ri1a
3ori3ë
o5ri4g.
or4ig
o5rige1re
ori3ge
o4r3i4n1k
o4r3i2ns
or1k2a
or5k4e
or3k2l4
or5k2n4
or3kw
or4m3ac
or1m
or1ma
or4mas
or4m3e4i
or1me
or4n3ac
o2r1n
or3na
or3ni
or1n2o3s4
or3o4e
o3rol
or1on
o4r3o2n2t
or1o4o2
or1o2p
or3or
o3r2os
or5o3v2
4or1p
or4p3ac
or3pa
orp4s5c
or2ps
or3s3a2g
o4rs
or5sc
or5se
or3s1li
or1s2l4
or3s1mi
ors4m
or3so
or4son
or3sp
or5s4p4a
or5s2p4u
or4t3ak
o4rt
or1ta
or4t5ee4n
or1te
orte4e2
or4t5ijl
ort4i2j
or3ti
or3to
or4to2f
or4t3o4o2
or4tre1d
or1tr4
ort5sp
or4ts
ort5s1te
orts2t
or1u
o3ry
orzet5
o4r1z
orz4e
2os
o4s1ac
o5sas
o3sau
4o3s2c
o2s1ca4
o4s3ci
o5s4c4l
o2s3c2u
o5se1d
o3se
os4el
o5ser
o2s3f
os4fe
o4sha
os1h
o3shi
os2ho
o3si
o4sj2
os5je4r.
os1je
o1sjer
o4s1k2
os5ko
os3l4
os5li4
o4s3m
os4n4
os5no
o3s2o
os3p4a
o4s3per
os1pi
os4pir
o4spr4
os4s5m
o4s3s4
o2s3t
os4ta
os5t4aal
osta4a4
os5taar
o2s4t1a3c
os4t3a4g
os5tan
os5tar
o3s3tas
o3sta1t
os5t4e.
os1te
os4t4em
o5ste1ro3ï
ost2er
o4s4th
os4to
os5to5li
os4t2ol
os5tou
os4t3o4v2
o4s5tr4a.
o1s4tr4
os5tr2a4a4
ost3re
ost3ri
o3stro
os5t4r2um
o3stru
o1s1tu
o3s4ty1
o3su
o5s4y
4o1ta
ot3aar
ota4a4
o2t1ac
ot3af
o3tag
ot3a4kt
ot3a4p3p
ot3a4rt
o3tas4
o5ta1t
o3te
ot3e2d
o5te4e.
ote4e2
o5tee2s3
o5t4e1g
ot3e2i
ote4l4an
ot4el
ote1la
o5t4en
o5t2er
oter5sp
ote4rs
ote4s2t
o3tes
ote4t
ot3e3ta
o1th
o2t1ho
o2t3hu
o4tj2
otje5sp
otje4s3
ot1li2
o4t3l
o1to
ot3o4f1f
oto2f
ot3ol1v2
ot2ol
o5tom
ot3o2nt
o2t3o2p1m
oto5po
ot3op1r4
o5t4or
o3t2o3s4
2otr4
o1t4ro
ot3ru
ot5s4i
o4ts
ot2s2l4
ot3sla
ots3li
ot3s1mo
ots1m
ot3s2n4
ot3sp
ot4s3p4a
ot4st4e.
ot3s1te
ots2t
ots5tek
ot5st4en
ot4stu
o1tu
ot3u2i
o3tul
o4t5w
4ou.
ou5a
o4u1c
ou4d1a2
ou1d
ou4de2s
ou1de
ou2do
ou1e
oue2t3
o2u3k4
ou4re4n
ou1r2e
ou5r4e4n.
ou5r4en3n
ou2r3o2
4ous
ou3sa
ou4s5c
ous2t4
ou2ta
out3h
ou2t1j2
ou2t3o
out1r4
out5sp
ou4ts
out5s1te
out2s2t
ouw3a
ouw5do
ou2w1d
ou4w5i2n2s
ou1wi
o2v2
2o3va
o5v4e.
ov4e
2o5ve4e
3o4ver1g
ove2r
over5sp
ove4rs
over5s1te
over1s4t
o5ve2s3
2o3vi
ovi5so
ov4is
4o3vl
4o3vo
4o3v4r2
ovu3
4ow
o1wa
o1we
o5we2n
ow3h
o1wi
o2w2n
o3wo
ow3r
o4x
oys4
ozet5
o1z
oz4e
ö3l
ö1p
öpe1
ö4r
ös4
ös5t
ö5su
4p.
4paan
pa4a4
paar5du
paa2r1d
pa4ar5tj2
paa4rt
5paas
3pa1b
p3a4c1c
2pach
pach4t5s
pa4c2ht
p4a3ci
5p4a1c2u
3p4a4d.
pa4da
4pa2d1v2
p4a3e
4p3a4fd
1pag
pa1g2a
pa4gen
pa1ge
pa3g1h
p4a5gi
3pak
pa2k3a2
4p4a1ke
pa4ki
p4a4k5l4
2p3a2l1b4
3pa1le
pal3f
pa3li
palin4g5s
pali2ng
pal5le4
pa2l1l
pal4m5ac
pa2lm
pal1ma
pal4mo
pa4m
pa3na
pa4n3a4d
5pane4e4
pa1ne
5panel
4pa4n1k
pan5sp
p4a2ns
pan4tr4
pa2nt
1pap
pa4pe4t
pa1pe
5p4a1pi
p2ap3l2
pa3po
p4a3pr4
4p4a4r.
3pa3ra
p3ar1b
pa2r1d4
par3da
3park2
par4ka
par4k5l4
3par3l
4par1m
pa5ro
4pa2r5r
par5ta
pa4rt
3par3ti
p4ar4t3j2
3par4t3n
pa5ru
paru5r
1pa4s3
p4a5sa
pa2s5c
p2a5se
p4a5so
pa4s4th
pa2s2t
pas5to
p4a1s5tr4
pa5te
pa1t
1path
p3a4t3l
3pa3tr4
pat4s5t4e.
pat2s2t
pa4ts
pat3s1te
2pau3t4
5p4a4u1z
pa4vl
pa1v2
5pa1z
2p1b4
2p1c
2p3d2
pe4al
p4ea
4pe3ci
p3e2co
3pectu
p4e2c1t
1pe1d
pe3de
pe3do
p4e4e4
3pe4e.
3pee3ë
pe2e5li
pee2l
4pee2n
5pee2s3
3p4e1g
1p4eil
pe2i
pei4l3a
4pe4is
pek5e4e
pe1ke
pe2k3l4
pe2k3n4
pe2k5s
p4el
pe3l4a4a4
pe1la
pe4l3ak
pel5dr4
pe4ld
pe3le
pe4l3e4e4
pe4l3e4t
p2e3l4i
pe3l4o2r
p4e1lo
pel5si
pe4ls
pel3so
pel3sp
2p3e2m1m
pe3na
pe4n1ak
pe4n1a2p
pe4nau
pe4n3a2z
p3en1cy
pe2n1c
pen5d4r4
pe4nd
pen1ge5
pe2ng
pe4n5k
5pen3n
pen3sa
pe2ns
pen5s2l4
pen3s3m
pen5s2p
pe2nt4
pen5to
2p3e1pi
pe1p3o
pe2p5s
p4e4r.
pe1ra
pera3s4
pe1r4a1t4
3per1c
pe4r5e1g
pe1re
pe5r3e2q
1pe3ri
pe5r4i3s
per1o
pe3r1on
pe5r2os
3pe4rs
per4s4m
per5s3ti
per1s4t
per4s4tr4
p2e4rt
3pes
pe3sa
3pe4t.
pe5ta
5pe5t2er
pe3te
3pe3ti
pe4t3ra
pe1tr4
pet4s5te
pe4ts
pets2t
pe1tu5
3pe2uk
5peut
1pé
3pê1
2p1f
2p1g
p3ge5s4
p1ge
2p1h4
4p3ha
3p4hec
ph4e
p4h4is
4pho
pi3am
pi1a
pi5an
pi4a1t
2pid
piek5la
p4ie
pi2ek
pie4k2l4
5pi4ep
pie4r3o
pie4s3p
pie4tj2
pi2g5a
pi3gl4
3p4i4j.
pi2j
pi2j3k
pij5ke
pij4li
3p4ijn
5pijp
pij4p3a
2pij1z
pi4k3l4
p4ik
pilo5g4
pi1lo
pi5nam
pi1na
2pi4nd
3pin1da
3p4i2ng
5pin4g.
pin4ga4
pin5g4ri
pin4gr4
4p3in1j4
pin1k3r4
pi4n1k
pin2k5s
4pi2n3r
2pi2ns
pin4ta
pi2nt
p4i5o
pi4s5n4
p4is
pis5ta
pi2s3t
pi3th
p4it
pi4t3j2
pit3r4
pit4sp
pi4ts
2p1ja
pje4s5
p3ji
p1jo
2p1k
pka4ar4t5j2
p1ka
p5kaa4rt
pka4a4
p2l2
p3l4a.
plaa2t5j2
pla4a4
plaa1t
2p3lad
pl4a3d4i
4p3la2m1p
4p3la2ng
pl4an
p4la2nt
p3lap
1p4las
3p4la1t
pla4t3r4
5p4lay
p4lec
plee5tj2
ple4e4
plee2t
p3l4eid
ple2i
3p4len
p3lep
pleu5r4o
p4lex
2p3l4ig
p1li
4pli2j
p4lom
p3lo1ne
p5l4oo4d
plo4o2
plooi5tj2
plo4oi
ploo4it
p3l4oon
p3lu4i3e
plu2i
2p1m
pman4s5t
p1ma
p5m4a2n2s
2p1n
p3na
3p4ne1um
p1ne
pne2u
3p4o.
po1da5
3poe2i
po4e
poe2s3
poes5t
poets5t4e.
poet4s1te
poe4ts
poets2t
3poe1z
3p2o3ë
p2o1fa
3po1gi
po5gr4
po2k3i2
p2ok
po4k3o2l
po1ko
1pol
po5l4o
po4lo3p
p4o4l4s
pols5t4e.
pol1s2t
pol3s1te
1pom
2p3o2m3l
3pon2ds
po4nd
pon4s4m
po2ns
pon4s4t
pons5t4e.
pon3s1te
pon5ta
po2nt
5po1ny
p4oo4d
po4o2
poo5d1e4
4poo4g.
poo4g
3poo4l
poo5len
poo1le2
4poo4r.
poor4tj2
p4oo4rt
poo4t3
p2o4p3a
4po2p3d2
2pope
po2p5h4
2p3or1g
2p3ork
po3ro
p4o4rt
5port4ef
por1te
por4to
por4t5ra
por1tr4
po3r1u
1p2os
po1sa
po3s3f
po4ta4a4
p4o1ta
po4t3as4
po5te
pote4s5t
po3tes
po4t1j2
p2ot3r4
3poul
po3v2
4p3p
p5pa
p5pe
pp4e4l3o
pp4el
ppe5ni
pper5s1te
p3pe4rs
pper1s4t
ppi2e5k
pp4ie
p5pij5p
ppi2j
p4ps
pr4
p2ra
3pr4a.
p5r4aad
pr2a4a4
praa2t5j2
praa1t
p5rad
3pra4kt
4pram
p5ra4nd
3pr2a3o
4p3rap
p4ra1t
p4rax
4pree1ku4
pree4k
pre4e
1prem
p3re2m1m
3pre2nt
pren4t5j2
3pres
p3re1so
3pret
pre4t3j2
pre1t3r4
4pr4ic
4p3ri2ek
pr4ie
4priet
prie4t5j2
1pri2j
3pr4ik
3pri2n1c
prin4g5s4
pr4i2ng
5p4ri2ns
3p4r4i1o
3p4r4i3u2
5pri1v2
5p4r2o3b
3p2r4oc
1p2rod
p3roe1d
pro4e
3p4roef
proe2t5j2
3p4roe1v2
5p4rof
5p2rog
1pr2o1j
pro3l3a
3prom
p3r4oo4d
pro4o2
pro4oi5
pr2o5pa
p4ro1q
3pr2os
pr4o5s2c
pro4s5t
pr4o3t4a
3pro1to
3pro5v2
4p3roy
p4ru2t
pru1t3o4
2ps
p3sa1b
ps3a2g
p3sak
ps3ar
ps3a4s3s4
4p3se
ps3erk
p4s3e4t
p3si
p4s3i2d
p4sin
p5s4i4s
p1s2l4
ps3le
ps2me
ps1m
ps5mi
p4s3na
ps2n4
ps3ne2u
ps1ne
p4sof
p3s2ol
ps3o4p1t
p2s1op
pso4r
p1sp
p2s2p4l2
ps3ple
p1s4t
p3sta1t
p3s1te
p4s5te2nt
pst4en
p4s5te4s
p4s5th
ps3tor
ps5tron
p1s4tr4
p3stu
p2s5ty1
3ps4y
5p4sy1c
p3sys5
4p1t
p4t3ad
p4t3a2l1b4
pt2al
p3te
p2t1h
p5ti
p1t3j2
p4t3o4v2
p3tr4
pt3r4ic
1p2u
3p2u2b
pu3ch
p4uc
pu3e
pui1l3o
pu2i
pul4s2t
pu4ls
3pun
4pu4n.
pun2t3j2
pu2nt
3pu4t.
puter5in
pu1te
put2er
pute3ri
pu2t1j2
pu2t3o
put3r4
put4s2t
pu4ts
put4s5t4e.
put3s1te
2p1v2
pva2n4
pva1ri5
2p1w
1py1
2p5z
1q
5qe
qu4
que4s
5quo
4r.
r2a4a4
2raan
4raa4nd
3raar
5ra4a4r.
4r3aa2r1d
5raa4r2s
ra4ar5tj2
raa4rt
2rac
ra4ca
r4a3ce
5r2a1c4l
ra1d4a
3ra2d1b
ra5den
ra1de
r4a3d4i
5radi2a
3rad4i2o
4ra2d1m
4r3a1dr4
3r2a2d3s
4ra2d1v2
2ra4fd
r4a4f1f
raf5o4n4d
r4a1fo
ra3fra
raf1r4
3ra5g4e1z
ra1ge
ra5gi
ra3g2n
ra5go
ra4g4s
3ra4i3s4
rak2e2t3
ra1ke
r4a3k4l4
ra2k5r4
4r3a2la
ra4l3e4e4
ra1le
4r3alf
r3a4lim
ra1li
r3a4l1t
ra4man
ra1ma
r5a2meu
ra1me
ra3m4i
r2a2m1p
4ra1na
ran4dr4
ra4nd
ran4g3o
ra2ng
ran4gr4
r5ang4s4t.
ran4g2s
rangs2t
ra4nim
ra1ni
4ran1j4
ran4k2l4
ra4n1k
ran4k3w
ran4sa
r4a2ns
ran4s4t
ran4t3j2
ra2nt
r3an4t3w
r2a3o
4r4a4p.
ra3po
4r1ap5pa
ra4p3p
rap5ro4e
r4apr4
ra3q
2r3ar1b
r4a5re
4rar4it
ra1ri
2r1ar1m
4r3a2r5r
2r1a4rt
ra5se2i
r2a3se
ra4s3k2
ra4s3l4
r4a1so
ra2s1p
ras3p4o
rast5ri
ra2s2t
r4a1s4tr4
r4a3ti
ra1t
ra2t5j2
ra4tom
ra4tra
ratr4
ra5tri
rat3sp
ra4ts
rat4s2t
rat4s5t4e.
rat3s1te
ra3t4u
2rau
3r4aus
r1au3t4
5ra3v4r2
ra1v2
ra4zi2j
ra1z
raz2i
rbe4ti
r1b
r3b4e
rbet2
r1c
r3ce
rce2s3
r3chi
r3co
2r1d
r4d3a2c1t
r1da
rd3alk
rd4a2m
rd5a1m4a
r3dan
r2d3ar
r2d3e2i
r1de
r4d5e4las
rde1la
rden5dr4
rde4nd
rde5o4
r4derva
rder1v2
rde5s4t
rde2s
rdi3a
r1di
rd4i5o
r4d5l
r3do
r5doc
r4d3ol
rd5ol4ie
r2do2li
rd3o2nt
rd3o2o4s
rdo4o2
rdo3pe
r2dop
r2do3v2
r4d3ras
r1dr4
r3dra
rd3res
r2dre
r4d5ro2o4s
rdro4o2
rd2ru
rd3s1a2
r2ds
rd3s4c
rd3s1o4
rd1sp
rds4t4
rd5sta
rd5s1te
rd3su4
r3du
rd2wi
r2dw
r4d5wo
3r4e.
1re3ac
r4ea
re4a1de
re1ad
4reak
re3a4m1b
4re5a1t
re3co
3re1c4r2
rec5ta
r4e2c1t
3re1da
re1d
3re4d3d4
rede4s3
re1de
4re4di3t2i
re1di
red4it
3re1du
re5d2w
ree4k
re4e
2r1ee2n
ree3n4e2
r5ee4n3h
ree2p
ree2p2s5
ree5r4ad
ree2r
reer1a
4ree4rs
reer5s1te
reer1s4t
r3eer1w
ree4s3
ree5s1h
r4ef
4re4f1b
2re4f1f
3re1f2l2
re3fu
1r4e1g
4re4g.
4re4gd
re3ge5ne
re1ge
re3ge4s4
4re4g3g4
3re3g4i
re3gl4
4re4gt
4re4i1e
re2i
4reil
4rei4nd
rei5tj2
r4e4it2
5r4e4i1z
re4kap
re1ka
5reke1ni
re1ke
re2k3l4
re2k5n4
re4ko
re4k3re
re1kr4
rek3sp
re2ks
re4ku4
re1kw
rel4di
re4ld
rel4d3o
rel4d3r4
re4l3e2i
re1le
rel5k
re4lu4r
r4e1lu
3re4m.
re4mai
r2e1ma
remie5tj2
re1mi
rem4ie
re5mo5v2
re1mo
2re2m1p
3r4e4n.
re2na
re4na4a4
re3n5aar
re5na1d4e
re3nal
re4n3an
ren3a4r
r4e4nd
5rende4e
ren3de
r5ende4rt
re5n4e.
re1ne
re4nel
re5n4e4n.
rene2n
ren5e4n5k4
re2n3e4p
re5ne4r.
ren5erf
re4n5er1v2
5r4e2n3f
2r1e1ni
5r4en1k2l4
re4n1k
r4en3n
re4noc
ren4og
ren4op3l2
re2n1o2p
re3n1o3v2
5r4e2n1p
4r3en1q
ren4s2l4
re2ns
r4en3to
re2nt
r3en4t1w
r5envee2r
re2n1v2
renv4e
renve4e
re4of
re3o
re4op4
re5pa
3repet
re1pe
re4p4ie
re1pi
4re1q
r4e3qu1a
requ4
4r1erf
2r1er1g
re3r2o
re4r4s
2r3e4rt
4r5er1v2
2rer1w
re3sa
re5s4e
re4s2l4
res5le
res3m
re2s1p
res3t
re4t4em
re3te
re3t4h
re3t4i
re4t4ik
re5tin
2re4t3n
re4t3o4g
re1to
re4t3o4o2
rets5t4e.
ret4s1te
re4ts
rets2t
re2u
reu4r5es
reu1r2e
reu4s4t
reu5s3te
3rev4is
re1v2
re3vi
3revo
2r3ex
r4f3a4a4
r1fa
rf3a2c1t
r2f3a4g
rf3al
r3fas
r3fe
r4f3e2ng
r1f2l2
r4f3lag
r4f3le1v2
r2f3li
rf3lus
r4f3o4p
r1fo
r1fr4
r4f3re
r5fr4ea
r4f2s2
rf3s2m
rf3s2p
r4f3u4r
r1fu
r2f3u4u4
r1g
r4g3a1b
r1ga
r4g3a4m1b
r4g3ee2n
r1ge
r3ge4e4
rg3e2i
rg4e4is
rgel5dr4
rge4ld
r5g4e4n.
rge4ra
rge5ra4p
r4g3i2ns
r1gi
r5gla4s
rgl4
r3glo
r4g3lu
r2g4o3v2
r1go
r5gri2j
r1gr4
rg3r4it
r3g4ro
r4g1s4
rg2s1m
rg5s1o4
rg4s5pr4
rgs3p
r3h
ri5abel
ri1a
ri4a1b
ria3b4e
ri4a3g2
ri2ak
ri5an
rias4
ri4a1v2
ri4b4l
ri1b
4ri3ce
r4ic
ri3co
rid1de4
ri4d3d4
ri3di
ri4dol
ri1do
ri4do4o2
rie5d2r4
r4ie
rie1d
rie4k5ap
ri2ek
rie1ka
rie5k2l4
rie3kw
rie4la
riel5a4a4
rie4le2i
rie1l4e
rie4r2o
rie4ta
rie3t3o4
ri1e2u
ri3f3l2
r4if
ri3f3r4
r4ig
ri4g3a4a4
ri1g4a
ri3gl4
5rig1s1te
ri4gs4
rigs2t
r4ijl
ri2j
4r5ij4l.
r5ij4ld
r5ij4l1t
rij5o4
rij3p2l2
rij3pr4
r4ij3sp
ri2js
rij5st2er
r4ijs2t
rij2s3te
rij4s4tr4
4rij3v2
ri4k5l4
r4ik
ri2k5n4
ri3k4o
ri2l5m
ri3ma
rim4p2r4
ri2m1p
4r3i2n1b
4ri4nd
ri5ne
4r5i2n3f
r4i2ng
4r5in3gan
rin2ga4
r5in3ge1ni
rin5gen
rin1ge
ring5l4
4r3i4n3h
ri4n4it
ri1ni
rin4k3l4
ri4n1k
r3in1ko
4rin4kt
r3i2n3l
4r3in1na
rin3n
4r1i2n3r
4ri2ns
r3ins4t
4ri2nt
4r1i2n1v2
ri5on
r4i1o
ri3o5s
ri4sam
r4is
ri1sa
ri4sc
ri3s4ot
ris5to
ri2s3t
ri4t3j2
r4it
ri4t3o4v2
ri1to
rit4s2t
ri4ts
rit4s5t4e.
rit3s4te
rit5st4en
3ri4t3t
r5j4
rjaa4r2s5
r1jaar
rja4a4
r5k4a.
r1ka
rka4ar4t5j2
r5kaa4rt
rka4a4
rk3a1dr4
r2k3af
r2k3ah
r4k3a2ng
r4k3a4rt4
r2k3e2i
r1ke
rke4n
rke2n4s
rker4s2l4
rke4rs
r4k3er1v2
rke4s
rke5stre4e
rkes2t
rke1s4tr4
rke5strer
rk5i4ep
r1ki
rk4ie
r4k3ij3v2
rki2j
r4k3i2n1b
r4k3i4n1k
rkje4s5
rk1j
rk3lag
r1k2l4
r4k3la1t
rk5l4eid
rkle2i
r2klo
rk3lo4o2
rk3lus
r3k2n4
r4k1ne
r2k2o3b
r1ko
r2k3o2lm
r4k3o2m5g
rkoo4t5
rko4o4
r4k3o2p1g
r2k3o2r1d
r4k5o4s.
r3k4o4s3
r4k5o4s3s4
r1k2r4
r5k4ran
r3kra
rk4ri
r5kr4is
r5k4ron
r2k1s
rk3s4f
rk5si
rks4p
rk4t5e4v2
r4kt
rk3te
rkt3h
rk4ti
rk1t3o
rk1t1r4
r2k3u4it
r1ku
rku2i
r1kwa
rk3wa4a4
r4k5wa1t
rk3we4e
r1kwi
r2k3win
r3l
rlaat5s1te
rla4a4
rlaa1t
rlaat2s2t
rlaa4ts
rle4g3r4
rl4e1g
rlin2k4s
r1li
rli4n1k
rlink1s5te
rlink1s4t
rlo4f4s5
r2lu4i5t4
rlu2i
r1m
rma3f4r4
r1ma
r4m3a4rt
r2m3e1b
r1me
r2m5e1g
rme4r3a4
rm4es3
rme4t3j2
rmet5s2t
rme4ts
r4m3i4n3h
r1mi
rm4i2s
r3mo
r5mo4e
r4mop
rm3o2p1m
rmor3s5te
rmor4s4t
rmo4rs
rmo2s5f
r5m2os
rm3s4a
r2ms
rm1s4t
r2m3u4it
r1mu
rmu2i
rmun4
2r1n
r3na
r5n4am
r4n3ap
r4n3a4r2s
rnee5t
r1ne
rne4e4
r4n3e1ne
rne2n
r3nes3
rne5te
rne4t3j2
r2n5i2d
r1ni
r2nin
r2n1on
rn3oor
rno4o2
r5noo4t3
r2n3o4ps
r5not
rn3o4v4e
rno3v2
r2ns4
rn3s3m
rn3sp
rn1s4t
rn3sta
rn3th
r2nt
rn5tj2
rn5to
r3nu
r2nu5r
r4o1a
ro5ac
r4oc
ro1ch
ro3d4o
3ro4e.
ro4e
4roef
4ro4e1g
roe4g3r4
3roem
roe2ns4
roen5s3m
roe4p3l2
r2oep
roe4re2i
r4oer
roe1r4e
roe2t4j2
4roe1v2
3r2o3ë
r5of5fi
ro4f1f
r4o1fi
ro3f3l2
ro3ge1s5
ro1ge
1ro3ï
ro3k4l4
r2ok
3ro2k1m
rok3sp
ro2ks
r4o4l.
ro2l3a
role5s3t
ro1le
role2s
ro2l3g2
2ro1li
rol3o3v2
ro3lo
r4o5ma
r4o3mo
4r3o2m1z
r2o4n.
ron3a4d
ro1na
5r4onal
ron4da
ro4nd
ron4d3o
ron4d3r4
ron4d5u
r2o1ne
r2o1ni
r2o4n1k
ron4ka
r2on3n
r2o1no
r2o2ns
ron4s1te
ron1s4t
rons5t4e.
4ro2n2t
ron1t3j2
ron1t3r4
ro3nu
4ro2n1v2
3r4oof
ro4o2
2roo4g
4r4oon
2r1oor
root5s1te
roo4t
roo4ts
roots2t
r2o3pa
ro4pa4a4
ro4pan
4ro2p1b4
ro1pe
ro5p4e4e4
ro4pin
ro1pi
ro3p4la
rop3l2
4r1o2p1n
r4o1po
rop5rak
rop1r4
rop2ra
rop3s1h
ro4ps
r4op3te
r2o4p1t
ro4p2u
ro2r5d
ro3ro
ro3sa
r2os
ro5se
ro3s3f
ro3s1h
r4o5si
ro3sp
ros4s5t
ro4s3s4
ro5st4el
ro2s3t
ros1te
ros5tra
ro1s4tr4
ro5te
ro3t2h
ro4t3j2
ro5ton
ro1to
r2o3tr4
rot4s1te
ro4ts
rots2t
rot4s5t4e.
r1ou1d
3rou5t4
ro3v2
ro4v4e
ro5ve3ri
rove2r
4roxi
ro4x
3roy
r1p
r3pa
r4p3aan
rpa4a4
r4p3a2d1v2
r4p3a4n1k
r5p4e4e4
r4p3e4is
rpe2i
rp4i3s
r2p3j
rp4lo
rp2l2
rp5lod
rpoor4t5j2
rpo4o2
rp4oo4rt
r4p3o4v2
r4p3rec
rpr4
r4p3r4ic
rp4ro
r3psa
r2ps
rp4si
rp2s2l4
rp3s1li
rp5spe
rp1sp
rp4s5to
rp1s4t
2r5r
rr4e4l3u
rre2n5s4
rre5o
rre2u2
rri5e4r.
rr4ie
rrie4t
rr2o4n5k
rro4t4j2
4rs
rs3a2d
rs3a2g
r3s2al
r4s3a2lm
rs3a4m1b
r3san
r4s3a2na
rs3a2p
rs3ar
rs3as
rs4as5se
rsa4s3s4
r3sa4te
r1sa1t
r5s2chi
r1sc
r3s4ch2
r2s2c4r2
r4s3e4is
r3se
rse2i
rsek5s1te
rsek4s4t
rse2ks
rs4e4t
rsev4e3
rse1v2
r4s3e1z
rs4f4er
r2s1f
rs1fe
rs4hal
rs1h
r3s2hi
r3s4hoc
rs3hot
r4s3i2ni
r1si
r2s3i2nt
r4sj4
r5sjac
r5sj4ou
r5sjt4
r3s4ka1t
r2s1k2
rs1ka
r1s2l4
r4sl4an
r5slec
r5s4lep
r5s4leu
r5sli1b
rs1li
r2s4l4ie
r5sli2ng
rs3l2o4b5
rs5l2oep
r3s4lo4e
r4s3lo4o2
r5sl4u4is
r1s4lu2i
rs4m
r5smaak
rs1ma
rsma4a4
rs5maal
rs5mak
r3s1me
r3s4mi2j
rs1mi
rs5m4is
r5sm4it
rs5mu
r1s2n4
r2s3na
rs3ne2u
rs1ne
r2s3no
r1so
r5s2ol
r2s3o2ng
r2sor
rsorke4s5
r4s5ork
rsor5k4e
r2s1o2v2
r1sp
r3spa4a4
rsp4a
r2s3pad
r4s3par
rs4pa1re
r3spe
r5spec
r5s4p4e4e4
r5s4pek
rs4pe1ne
r4s3pen
r4s3pet
r5sp4it
r5s2po4e
rsp4o
r5spog
r5spon
r5s2po4o2
rs3pot
r5spr2a4a4
rspr4
rsp2ra
r4s2p4u
r5spul
rs3put
r1s4t
r4s5taak
rsta4a4
r4st5aa2n5g
r2s5tas
r5sta1t
r3s1te
r4s3t4e.
r5ste4r.
rst2er
r5sterk
r4s5ter1m
4r5ste4rs
r5s3te4s
rs2te5s2t
r4steva
rst4e1v2
r3s3ti
r4st4it
r3sto
r4s5t4o1ma
r4ston
r4st5o3ra
r3s4tr4
rs5trap
r4st5re1d
r4s5tre2i
r5stren
rs5trog
r4st5ro1z
r3s4ty1
r3su
rs3usa
rsu4s
r3s4y
4rt
r1ta
r5t4a.
r4t3aan
rta4a4
rt5aa4nd
rt5aa2n1v2
r4t1ac
r4t1ad
r2t3a4f.
r4t3a4f1f
rt3am
r5t4a2ns
r2tar
4rt3a4rt
r4tau
r2ta1v2
r4t5c
r5te1co
r1te
r3tec
r4t3eig
rte2i
rt3eil
rte4le2i
rt4el
rte1le
r2t5e4m1b
rt4em
r5te4n.
rt4en
rte5n4ach
rte2n1ac
rte1na
rte3no
rte3ro
rt2er
r3tes4
rte5s3ta
rtes2t
r2t5e2v2
r4t1ha
rt1h4e
r3t2her
rt3hi
r1tho
rt3hol
r2t3hu
rt3hy
rt4i2j
r3ti
rti2j3k
r4t3i1ni
r4t3i4n1k
rt5je1sc
r1tj2
rtje4s3
r3to
rt3o4f1f
rto2f
r5t4o1fo
r5t2ok
r4t3o4m.
r4t3o4nd
r4t3op
r5t4o1ri
r1tr4
r3tra
rt4rap
r4t3ras
r2t3rec
r5tred2e4n.
rtre1d
rtre1de
r3t4rek
r4t3res
rt3ri
r4t3rol
r2t4ru
rt5r2u2k
rt5r2u4s
rt4s5e1co
r4ts
rt3se
rt5s3e2i
rt2s3l4
rt3s3le
rts5li
rt4s4lu
rts5m
rts5no
rts2n4
rt4so4o2
rt1sp
rt4s3pr4
rts5t4en
rt3s1te
rts2t
r1tu
r2t3u4i4t
rtu2i
r4t3w
rt2wi
5ru3br4
r2u2b
rude3r
ru1d
ru1de
ru1e
4ruf
ru2g
ru4g1r4
r5uit5r4
ru2i
ru4it
r2u2k
4ru3ke
ru1k3i
rul3a4a4
ru1la
rul3a4p
ru2li
ru4l3i2j
ru3lin
ru4l5s
r2um
ru2mi
3ru4n.
r2u4nd
rune4t3
ru1ne
4r5u2ni
r1u3ni1v2
ru4r
ru5ra
ru5r4e.
ru1r2e
ru5res
r2u4s
ru3s3e
ru1s5tr4
rus2t
4rut
ru2t3j2
rut4s2t
ru4ts
rut4s5t4e.
rut3s1te
4ru4u4
ru3w1a
rvaa1t5
r1v2
rva4a4
rval4s2t
rva4ls
rvals5t4e.
rval3s1te
rver4s5t4e.
rv4e
rve2r
rve4rs
rver1s4t
rver3s1te
rve2s4
rve3sp
rvloo4t5
r3vl
rvlo4o2
r1w
rwen4s4t
rwe2n
rwe2ns
rwens5t4e.
rwen3s1te
r4w1h
rw2t3j2
r2wt
r3x
r3yu
4r1z
rzet5s2t
rz4e
rze4ts
4s.
5s4a.
s1a4a4
1s4aag
5s2aai
saa4i4s4
3s2aal
3s4aa1t
1sa1b
sa3b4o
2s1ac
sa2ca
3s2a1c4r2
s1a2d1v2
2s1af
3s4a1fe
3s4a1fo
sa3f1r4
s5a4g3g4
s4a3gi
3sa2g1n
sa3go
3s2ah
3sai
3s2a1j
2sak
3s2a2ks
s1a4kt
s2al
5sa4l.
3sa3la
3s4a4ld
5sa4l1h
s3a2l1l
4sa2lm
sal5ma
s3a2l3n
3s4a3lo
3s2a1me
5s4a2m1m
sa2m5p
4sa2na
sa3na1t
s4a2n1c
s2a3ne
s4a2nt
san4t3j2
sa2p
3s4a4p.
sa3pa
2s3a1pe
s4a4pr4
sa5pro
sa3ra
s1ar1b
3sa2r1d
sa2re
s1ar1m
sa1ro4
sar3ol
s4a4r2s
4s1a4rt
sart5se
sar4ts
4sa4s.
3s4asa
sa3sc
3s4a2s2t
1sa1t
3sa3te
5sa3ti
2s3a4t3l
2s1a4t3t
s3au1d
1saur4
3s4aus
s1au3t4
3s4a4u1z
1sax
4s3b
s5ba
s5b4e
s5b4o
1sc
2s1ca
4s1ce
5s2ce3n4a
5scè1
3s4ch2
4s4c4h.
sch4a
5schak
5schap
4s1chau
5sch4e.
sch4e
s5chec
4s1che2f
5schen
4s5che1q
5scher
5sche1v2
5sch4e1w
s2chi
4s5c2hir2
5schol
5scho4o2
5schot
sch5t1a2
s4c2ht
2s3ci
4s1c4l
2s1co
3s4co3la
3s2co4o2
3scope
s2co1p2
5s4co1pi
3s4co5re
3s2cout
2s1c4r2
4s3cr4is
2s1c2u
2s1cy
4s1d
s5de
s4d1h
sdi5a
s1di
s3d4i4s5
s3do
s5dr4
s3dw
3se
5s4e.
s4e2a
se3ak
se3al
se1ar4
se3au
s4e1b
4s3ech
se3c4r2
5s4e2c1t
4s3ec1z
s4e4e
4s5ee1d
5see3i
4s1ee2n
s5ee4n3h
see4t
see5ts
4see5v2
s1e4f1f
se3ge
s4e1g
2s5e2go
se1g2r4
4s3e4i.
se2i
4s3eig
s4ein
5sei4n.
5sei1ne
2se4is
sei2s4t
sei5tj2
s4e4it2
5s4e4i1z
sek4s4t
se2ks
sek3s5t4en
sek1s1te
se1kw
s2el
5s4e4l.
sel3ad
se1la
se4l3a4g
se4lak
se4las
se3le
4s3e4lek
se4l3el
4s3e4lem
4self
se5li2ng
s2e1li
4s3el4it
sel5k
5se2lm
s4e1lo4
5se4lp
5s4e4ls
sel3sp
5se4l1t
s4e2l3u
s4em
se4m3ac
s2e1ma
s5e2m1m
sem3o4o2
se1mo
s4en
5se4n.
se4n3a4g4
se1na
se5nan
se4net
se1ne
5sen3g4r4
se2ng
5se4n3h
se4n5k
se4n3o
4s5en1q
sen5t1w
se2nt
5s4e4r.
se1r4a
se2r5au
5se3r4e
se4re4e
se5ren
s4er1g
5serg2l4
s5er1go
5ser1gr4
se3r4i
se5ri2j
4s3e2r1n
se3ro
se5r3op
se4r2s
ser1s3p
ser3s4t
ser4t5w
se4rt
se3ru
s4es
se5sc
se3s1f
2s5e2s1k2
5se4s3s4
se4t
se5ta
4s3e3te
se5ti
se3tj2
se1t3r4
se5t4ra
set5s2t
se4ts
4s5e1tu
se4t3w
se3um
se4ven
se1v2
sev4e
4s1ex
4se1z
se2z4e
3sé
3sè1
2s1f
4sfe1d
s1fe
s5fe2i
4s1fi
4s5fr4
4s1fu
sfu5m
4s5g
s3gue4
s1gu
s1h
s4h4a.
sha4g
s5ha4l.
3s2ha2m1p
4sh4e
sh4eid4
she4i
shei2d2s5
s5h4ie
5s4hir2
s2h3l
4s2h2m
s3ho4e
s3ho4o2
3s4hop
s2hot
s3ho3te
3s2h4o4w
s5hul
1si
5s4i.
5s4i1a
si5ac
si3am
si5an
5s4ic
si3ci4
si3co
3si4e.
s4ie
3sie3ë2
sie5fr4
sie5k2l4
si2ek
si4ep4
sies4
sie5s2l4
sie3so4
sie3s2t
sie5ta
sie5to4
si5è1
s4i1f4
5s2ig
si5go5
s3ij3v2
si2j
4s1ij1z
5s4i3le
4s5imper
si2m1p
3si3mu
5si1na
s3i2n1b
4s3i2n1c
4s1i4nd
2s1i2n3f
si2ng4
3sin4g.
s3in2ga4
s5in3ge1ni
sin5gen
sin1ge
sin3g4l4
s3in5gr4
s3i4n3h
4si2ni
4s3in1ko
si4n1k
sin5kr4
4s3i2n3m4
s4in3n
4si2n3r
2s1i2ns
2si2nt
4s5i2n1v2
4s3i2n1z
3sir
5si1ro
s3i2r5r
s4i4s
si3s3e4
sis5e4e
1si1s3i
si3s5tr4
si2s3t
3s4it
si5to
si4to5v2
si3tr4
si4t1ru
si5tu
3s4i3u2
3s4i1z
sj2
4s4j.
3s4j4a.
5sja3b
4sj3d
s1je
2s3j4e.
s5je1b
3s2j1e4e
3s2je2i
1sjer
sje4ri
s3je4s3
3s2j4e1w
3s4je1z
4s2j5k4
5sjof
4s3j4on
s2j3s2
sjt4
s5j4u
2s1k2
ska4ar4t5j2
s1ka
s5kaa4rt
ska4a4
s5kad
s4ke1le
s1ke
s5ken
3s2ke2s
s1k4i
3s2k4i.
3s4kie1d
sk4ie
skie3s
3s2ki3ë
ski5sc
sk4is
s2k3j
s3ko
s5kre
s1kr4
sk5r4uim
s3k4ru
skru2i
sk3s1te
s2ks
sks4t
4s1ku
s3k4w
s2l4
3s4l4a.
5s4laan
sla4a4
5slaap
4s5laar
4sla1b
s4lac
4s3lad
3s4lag
5sla4g1m
sla4me
s5lam4p.
sla2m1p
s5lampe
4s5la4nd
sl4an
3sla2ng
3slap
5sla1pe
sl2a3p3l2
4s3las
2s3la1t
3s4la5v2
4s5l2a1w
3s4l4a1z
s3le1d
3s4le4e.
sle4e4
5s4leep
4s5lee2r
s4lee2t
slee5tj2
4s3l4e1g
2s5le2i
s5le2ng
s3le1ni
slen4s4t
sle2ns
slens5t4e.
slen3s1te
3sle2nt
s4lep
4s5ler
s5le2s
sle4t3j2
3s4leu
s5leug
s5leus4
5sleut
2s5le1v2
s3l4i.
s1li
4s3l4ic
4s3lid
2sl4ie
s5lie1d
s3lief
3s4lier
s3l4if
s5l4ig
4s3lijf
sli2j
5s4lijp
4s5li2j2s
s4l4i4k
sli2m
sli1m5a
s5li5ni
4s3lin3n
s4lip
4s3l4it
sl2o4b5
2s3loc
3s4lo4e
3slof
4s3log4
s3lo2l
s3l4oo4d
slo4o2
s5l4oon
s5lo2o4s
5s4loo4t3
s3l2os
3slot
sl2o4t3r4
4s3lou
4s5lo5z
4s5l4uc
1s4lu2i
4s5lu4i.
4s5luid
5slui4s.
sl4u4is
slui4s4t
slui5s1te
5s2lu4it
5slu4i1z
4slun
2s5lus
4s3ly
s1m
4s5maa1t
s1ma
sma4a4
3smad
3sma4k.
3smal
2s5man
s5map
s4ma4rt
4s5ma1t
4s5me1c
s1me
5smeden
sme5de
sme1d
3smee1d
sme4e
5s4mee2t
4s5me4i
4s5m4e1lo
sm2el
4s5men
4s5m4es3
5s4mi4d.
s1mi
s3mid
sm4ie2
smies5
s4mi2j
s5min
5s4m2ok
s1mo
s3mon
5smuilden
s1mu
smu2i
smui4ld
smuil1de
s5mu4i3le
5smui4l1t
s2n4
s5nam
s1na
5s4nap
s4nar
3snau
3s4na3v2
3s4ne1d
s1ne
3sne4e4
snee5t
s5n4e3g2
5s4nel
2s5nes
4s5net
sneus4
sne2u
sneu5s2t
s5ne4u1z
s3n4ie
s1ni
1s4ni2j
s5nim
3s4nip
4s5ni1v2
4s1no3d
3s4no4e
s3nog
2sno4o2
s4no4r.
s3nor1m
sno5v2
3s4nuf
s4nu2i
2s3num
3s4o.
so4b4l
s2o3b
so1c
s3o1ce
3s4o3d
1so4e
2soef
3s2oep
soes3
2s1o4f1f
3s4o4ft
2so2g
3so3ga
s1o1ge
so3g1l4
3so3gy
5s4oi
3so3ï
3s2ok
s2ol
5so4l.
so3la
so3le
so3l4is
so1li
3so5l4o3
solo5v2
5s4o4ls
s2om
3s4o4m.
5s4o2m1m
2s3o2ms
s3o2m1v2
2s3o2m1z
5s4o4n.
3so1na
so5nar
s3o2n1b
2s1o4nd
2so2ng
3son3n
3so3no
s4o2ns
2s1o2n4t3
4s3o2n1v2
s3o2n1w
3so4o2
4s5oo4g
4s3o2ok
4s3oo4r.
s3oo2r1d
4s3oor3l
5s4oo4rt
2s1op
3s4o4p.
4s5ope
so3phi
so2p1h4
s2o5po
so3p1r4
3s4op2ra
sop4re
s2or1b
s3o2r1d
2s1or3g
4s5ork
sor4o
so3r3or
sor4s4t
so4rs
3s2o4rt
s2os4
so3s3f
s4ot
s3ou1d
sou2l
sou3t
2so2v2
s1ov4e
3so5z
4s4p.
sp4a
5spaak
spa4a4
s3paal
5s4paan
5spaa1t
2spad
2s3pak
5s4p4a1ke
s4pan
3span3n
4s5pap
5s4p4a4r.
s4pa1ri
5s4pa2r5r
2s1pa4s5
5spa4t3t
spa1t
s3pau
5s4p4ea
4s3pectu
sp4e2c1t
3s4p4e4e4
spee2t3
4s3pe2i
s4pek
5spe2l1l
sp4el
4s3pen
s5pe4n.
spe4na
s5pep
4sper
s4p4e4r.
s5pe3ri
s4per1m
5s4pe2r5r
4s3pes
s3pe1z
s3pid
1s4p4ie
spie5tj2
4s3p4ijn
spi2j
4s5pijp
s5p4i2ng
5s2p4i5o
s3p4is
spi5s1to
spi2s3t
2s1p4l2
4s5pla
s4plet
s2p1li4
5splin
3spl4it
s3plo
s3plu
sp4o
s2po4e
s3poe2s3
4s3p2o3ë
4spog
4s1pol
2s3pom
s4po4n.
s4pon3n
s2po4o2
s3pop
5s4po1re
s4po1ri
4s3p2os
5spo4ts
4spou
4s3pra4kt
spr4
sp2ra
5spray
s5pre1d
5spre2i
s4prek
4s1prem
4s3pres
5spre2u
5s4priet
spr4ie
4s5pri2j
4s3pr4ik
4s5p4r2o3b
4s3p2r4oc
4s5p2rod
4s5p4rof
4s5p2rog
5s4pron
s4pro4o2
4s3pr2os
4s3ps
4s4p1t
s2p4u
4s3p2u2b
5s4pu2i
4s3pun
s4pur
5spuw
s4q
4s5r
sraads5l4
sr2a4a4
sr4aad
sra2a2ds
sro5v2
4s3s4
ssa1s2
s4s1co
s1sc
s4s5c2u
s5se
s2se4i3s
sse2i
sse3o4
s5si
s5s2l4
s4sp4a
s5spa4a4
s2s5pa4s5
s5su
s5s4y
s2t
4s4t.
5staaf
sta4a4
5staa4n.
4staa2n5g
4st3aa2n1w
sta4ar4t5j2
staa4rt
s4taa1t
staa2t5j2
st3ab4o
sta1b
2s4t1ac
3s4tad
5st2a2d4s3
2staf
5s2ta4f.
st4a4fo
s4tag
s4tak
5sta1ki
4sta4k5k4
st3a4kt
4s3ta3li
st2al
5st4a4m.
5st4a2m1m
3s4ta2m1p
3s4ta4nd
st4a2n4s
s4tap
4s5tapo
s4t3ar1c
4s5ta1ri
2s3tas
stas4ie4
sta1si
5s4tat4i1o
sta1t
sta3ti
4s3tau
s4t3au3t4
s4ta1v2
4s2t1a2vo
4s5tax
4st3a2z
2s4t3b
2s4t5c
2s4t3d2
4s3t4ea
s1te
5steak
4s3tec
s5tech
5s4te1co
3s4te1d
4st3e1du
3s4tee2k
ste4e2
3s4tee4n
4stee4n3h
s5tee2r
stee5t
5st4ein
ste2i
5stekar
ste1ka
5s4te4k5k4
5stel2d1h
st4el
ste4ld
ste4le4e4
ste1le
st5e2lem
3ste2l1l
5ste4m.
st4em
5ste2m1d
5s4te2m1m
4ste1mo
4s3te2nt
st4en
4s5te1nu
ste5ran
st2er
4s3ter1m
ste3r5o2g
st5e4r2os
5sterren
ste2r5r
s5te1ru
4s3te4s
4s4t3ex
s4t3e2z
2s4t3f
4s4t3g2
4sth
s4t1ha
st3he1d
sth4e
st5hee2r
sthe4e
st3hek
s5them
s3t2her
st1hi
s4t1ho
s4t1hu
s4t3hy
2s5ti1a
s3ti
2s5ti1b
4s5ti1c2u
st4ic
s4t3i3d4
5stie1fe
st4ie
s5tie3v2
4s5t4ijd
sti2j
3s4tij1g
5s4tijl
st3i2js
3s4ti4ls
st4il
s4tim
st3i2m1p
sti5ni
4s4t3i2ns
4s5ti2nt
4s5ti1te
st4it
2sti1v2
st3i4vo
4s4t1j2
2s4t3k2
4s4t3l
2s4t3m
2s4t3n
2st2o3b
2s3toc
4stoef
st4o4e
3stoel
5stoe4l.
5stoe5le
4s5toen
4s3t4oer
4s5toe1s4
4s5toe1z
3s4to2f
st3o4ge
5s4t2ok
s4t2ol
s2to5li
4st4o1ma
4st3o2m1z
s4t4o2ng
3s4to4o2
4st3oo4g
stoo4t5j2
stoo4t
s4top
st3o5pe
st5opto
st2o4p1t
4sto3ra
sto4ra1t
4st3o2r1d
st4o5ri
4s5t2os4
s4to3v2
2s4t3p4
1s4tr4
4s3tr4a.
straa2t5j2
str2a4a4
straa1t
4st4rad
3stra4f
5stra4f.
s5trag
4s3t4rai
4s2t3rec
s5tr4ef
4s4t5r4e1g
4s3tre2i
5s4trel
3strep
st3r4if
st5rijp
s2tri2j
s5tr4is
4s3tro4e
s5tr2oep
st4rom
5stro2ok
stro4o2
5stroom
4st4ro2o4s
st5roo4s.
4s5trou
4stro1z
3stru
4s5tru4i.
stru2i
5str4u4ik
4s4t1s4
st3sc
st5se
st3s1f
st3s1k2
st3s2l4
st3so
st5sp
s2t5s2t
2s4t5t2
1stu
4s3t2u2b
4st4uc
5s4tu1d
4s5tuin
stu2i
stui5t4j2
s2tu4it
st5ui4t3k2
5s4t2uk
2s4tun
st3u1ni
stu4n4ie
4s3tu4s3
2s4t1v2
2s4t3w
2s4ty1
1styl
s5ty1p
2s4t1z
1su
5su.
5su1a
5s2u4b1
su3ba4
su3b5e
su5b4l
5s4uc
5su1d
3sug
2su2i
5s4u4ik
4s1u4it
5sui4t.
s5ui4t3l
5suit4s.
sui4ts
5s2uk
3sul
5sum
4s1u2n
5s4up
5s4ur1v2
su4s
su3s3e
suur5
su4u4
4s5v2
svaa1t5
sva4a4
sva1ri5
sve4r
sv4e
sve5ri
4s1w
s5wo
s4y
3sy.
4sy1c
3syn
sy4n3e
1sys5
4s5z
4t.
3taa4k.
ta4a4
t4aal
t5aan1do
taa4nd
t3aa4n5k4
taan4s4t
ta4a2ns
t3aa2n1w
t3aap
taar5sp
taa4r2s
4t3aas
taat4s2t
taa1t
taa4ts
taat3s5ta
3ta3b4e
ta1b
3ta3b4l
2tac
ta2ca
3t4a3ci
4tad
ta4de
t3ader
5ta1do
t3a1dr4
t2a2d4s3
t3adv4e
ta2d1v2
2ta4f.
2t3a4fd
5t4a3fe
4ta4f1f
t3afha
ta4f5h
t4af1r4
ta3fro
4t1a4f3s4
2t3a4f1w4
4ta2f1z
ta4ga4a4
ta1ga
5ta3ge4e4
ta1ge
5t4a5g4l4
ta2g3r4
5ta1ka2
5ta2k3g
5takken
ta4k5k4
tak1ke
t4a3k3l4
5ta2k3n4
5ta2k3p
5ta2k3r4
5t2a2ks
t2al
ta3la4a4
ta1la
ta5la2c1t
ta2lac
4ta2l1b4
5ta5l4e.
ta1le
5tale2nt
ta3li
5tal4ig
t5alli1a
ta2l1l
tal3l4i
tal1m3a
ta2lm
4ta4l1t
ta4mak
ta1ma
4ta4m1b
t3am3ba
5ta3men
ta1me
tamen4t5j2
tame2nt
4ta2m1p
t3am1p2u
5ta4n.
4t3a2na
ta3n2a3g4
ta3na1t
tan4d3r4
ta4nd
tan4k5r4
ta4n1k
t2a3o
t4a1pe
5t4a1pi
t2a3p2l2
5tapo
ta3q
ta3ra
4t3ar1b
5ta1ri
4t1ar1m
ta2ro4
tar5sp
ta4r2s
tar5t4a4a4
ta4rt
tar1ta
t3ar3ti
3t4ar1w
3tas
5t4asa
5ta4sj2
5t4aso
ta3s2p
t4a3s3ta
ta2s2t
t4a3s4tr4
ta3s4y
4tata
ta1t
4tat4i1o
ta3ti
ta2t5j2
4t3a4t3l
3tatr4
3tau
4tau3t4
2t1a2vo
ta1v2
3tax
t3a2z
4t3b
tba2l
t3ba
4t3c
t4ch
t5cha
t5ch4e
t5chi
t5chu
4t3d2
tdor5s4t
t1do
tdo4rs
t2do3v2
1te
3t4ea
te3a4kt
5tea4m
3tec
4t3e4c2ht
4te1co
te4d4it
te1d
te1di
t3e1du
te4e2
teeds5t4e.
tee1d
tee2d1s
teeds2t4
1teed3s1te
te4e4g
4tee2k
tee4k3l4
t4eem1
4tee4n
t5eenh4e
tee4n3h
3tee2r
tee5rin
tee4r3i
tee4t
4t3eeu
t4ef
t5e4f1f
3te1f2l2
3te1h4
4t3eier
te2i
te4i1e
4teig
tei4l3o
t4ein
t5ei4nd
5t4e4it2
tei5tj2
2t3ei5w
5teke1ne
te1ke
5teke2ns
4teker
4te4k5k4
3te3ko
te4k3om
3te2ks
te3kw
te4k3wi
t4el
tel5a2nt
te1la
te3l4an
te4lap
tel5da
te4ld
4telec
te1le
5tele1co
t5el4e2c1t
te3l5ee2n
tele4e4
5telef
5tel4e1g
tel5e4i.
tele2i
tel5e4i1e
tel5e4it2
te5lel
5tele1v2
5te5lex
tel3f
tel5k
te4lo4e
t4e1lo
te4l3o4g4
tel5oo4g
te3lo4o2
te4l3op
te2l3o4r
te4ls4
4tel3se
tel3so
tel5su
te4l3u4u4
t4e1lu
t4em
2te4m1b
4te2m1m
te4mor
te1mo
te4m3o4v2
5temper
te2m1p
5tempo
t4en
ten4ach
te2n1ac
te1na
ten3a4g4
te3n1ak
te5na1re
te4nau
te1ne2
ten3e1d
ten3el
tene4t
3te4n3h
te4n5k4
te5no3re
4t5en1q
ten5s1c4r2
te2ns
ten1sc
ten3s2n4
ten3s2p
ten3su4
tens5u4u4
3te2nt
5ten3ta
5tente4n.
1ten3te
t4en5t4e2n
ten5to
t3en4t1w
5te1nu
t2er
tera2a2ds5
te1r2a4a4
te5r4aad
te4r5aak
ter3a4b
tera5ca
te4r1ac
te4rad
tera4de
te4r5af
ter3ag
te3ral
te4ran
ter3ap
ter3as
5t4erec
te1re
te4re2i
ter5e4ik
te4rel
te4rem
te5r4e4n.
te4r5e4n1k
te4r5e2n1v2
4t4er4f.
4ter4fd
ter3fr4
4t4er4ft
te4r5i4n.
te3ri
3ter5j4
4ter4k.
4ter4kt
ter3k4w
3ter1m
5ter4m.
5ter3m4i
ter5oc
te3rod
te3r3of
te3ro2g
5ter1on
te5r2o2ns
tero5pe
te4r3op
te2r3o4r
te3r2os
5terre2i
te2r5r
5terre2u2
5terror
ter4spr4
te4rs
ter1sp
ter5s3t4e.
ter1s4t
1ter3s1te
ter5ston
ter3sto
3tes
te3s4a2p
tes3m
te3so
tes3ta
tes2t
te5st4el
1tes1te
tes5t4en
tes4t5op
test5ri
te1s4tr4
te1st3u
te3ta
te5tr4
4t3eu1v2
t4e1v2
t5e4va2n
t4eve4r
tev4e
5te3vl
3te3v4r2
2tex
3t4ex.
4t3ex4e
4t1ex1p
1té
tè3
4t3f
4t3g2
tg4aa1t5
t1ga
tga4a4
t5ge
tge3la
tger4
4t4h.
2t1ha
t3ha4a4
t4haan
t4had
t3hak
t5ham
t4h4a2ns
t3har
t3ha3v2
5t2h4ea
th4e
t3he1b
5t4he4e.
the4e
4t3he4i
4t3hel
3t2hen
5t4he5o
1t2her
5the3ra
4t3he1re
3thes
3thet
t4hin
4t2h2m
t1ho4e
t2ho3g2
t3h2ok
t1ho4o2
thoof5di
th4oof
thoo4fd
4t1hou
t3hou1d
5th4ous
4t3ho3v2
3t2hr
2thu
t1hul
4thum
t4hur
3ti
5t4i.
5ti1a
ti5a1b
ti5ae
ti3a1p4
5ti1b
5ti1ca
t4ic
5ti3ce
5ti3ci
5ti1c2u
ti3d4
5ti4e.
t4ie
tie5d4
5tie4f1s
tie3k2n4
ti2ek
tie4kon
tie3ko
ti3e2n1c
tien5s4t
tie2ns
5ti4ep
5ties
tie5s4l4
tie5ta
tie5to4
tie5t1w
ti1e2u
5tieven
tie3v2
tiev4e
ti3fe
t4if
ti3f3r4
ti2g4a
tig5a4a4
4ti4g1m
ti4gu4
tig3ur
5t4ijd
ti2j
t4ije4
tij5ka
ti2jk
tij4k4l4
5t4ijn
tij5p
t3ij4s.
ti2js
t4ij3s2t
tij3t2
tij5tr4
tij5t1w
4t1ij1z
ti3ko
t4ik
ti5kr4
t4il
4ti4ls
5ti2m1m
5ti1mo
tina4d
ti1na
tin3as2
4t3in1c2u
ti2n1c
4t1i4nd
4t1i2n3f
tin4g3i
ti2ng
ting4s1a2
tin4g2s
t3i4n3h
ti4n4it
ti1ni
4t3in1j4
t3in1ko
ti4n1k
4t3i2n3l
t3in1q
4ti2n3r
4t3i2ns
ti3nu
4t3i2n1v2
4ti2n1w
ti5om
t4i1o
ti3o4p5
t4is
ti5sa
ti3s4j2
ti3s3l4
ti3so
ti4son
ti3s4p
ti3s1ta
ti2s3t
5ti1te
t4it
ti3th
ti1t2r4
5ti3vi
ti1v2
ti4vo
1tj2
2t1ja
t5ja4a4
t5j1e4e
t5jek
t3j1en
t5je2t
4t5jeu
2tjo
t1j4ou
2t1j4u
4t3k2
tka4r2s3
t1ka
4t3l
t5l4e.
5tle1b
t5le2s
tli4n
t1li
4t3m
tmen4s4t
t1me
t3men
tme2ns
tmen3s5te
t5m2os5
t1mo
4t3n
tna4m3o
t1na
t3nam
tne4r
t1ne
t3nes4
5t4o.
t4oa2
to3ac
to3ar
to5b4l
t2o3b
3toc
1toch
3tod
to3da
t4o4e
toe5d4
3toe1j2
toe5k
5toe3l4a
toe5le
5toel4ic
to2e1li
toemaa1t5
to2e1ma
toema4a4
5toen
to5en3de
toe4nd
toe5p2l2
t2oep
3t4oer
5toe3ri
5toe2r1n
5toe1s4
toe5s2t
toe3tj2
3toe4ts
5toet4s.
5toet3se
toets5t4e.
toet4s1te
toets2t
3toe1v2
5toe1z
to2f
tof5ar
to1fa
to4f5d
to4f1r4
tof3t3h
t4o4ft
3to2g1n
5to1gr4
3t4oi
to4kan
t2ok
to1ka
to2k3s
t2ol
to3la
5tola4a4
to5le
5tolet
t3olf
2to1li
5to3l4ic
to4l4ie
tol2k5s
5to3lo
tol1p3r4
to4lp
t3o1ly
4to4m.
5tom2a4a4
t4o1ma
tomaa1t5
t3o2m3l
t4o3mo
tom4p3j
to2m1p
4t3o2m5s
5to4n.
4to4nd
3t2o1ne
5tone4e4
5to5ne2n
to5ner
3t4o2ng
5ton4g.
3t4o1ni
5t4on3n
to3no
5to2ns
ton3s1k2
too4m
to4o2
to4o3m3e
5t4oon
t4o4p.
top5a4rt
t2opa
to1p3a4s3
to3pen
to3pet
to3pi
2to2p1m
to4po
to5p2os
t4o4p3p
to4p2u
to5pus
t3opva
to2p1v2
5to4r.
to3ra
to4r3a4g
t3o2r1d
t4o5rec
to1re
5tore2ns
4t1or1g
t5or1ga
t4o1ri
3to5ri1a
t3o4ri3ë
tor3k
tor4m3a
tor1m
toro4
to4r5o1li
to3rol
to3rom
5to2r5r
3to4rs
tor4s5t4e.
tor1s4t
tor3s1te
to3r2u
3t2os4
to3sa
to1s3l4
to1s2p
tos5te
to2s3t
5t4o1ta
t2o3tr4
2t3ou1d
3tour
tou4r3e
to3v2
tove5na
tov4e
to4ve2ns
4t3o4ver1g
tove2r
t4o3w4
4t3p4
tpe4t3
tp4i3s
tr4
3tr4a.
4t3r4aad
tr2a4a4
5tra1cé
t2rac
5tra5f4o.
tr4a1fo
3trag
4t3ra5g4e1z
tra1ge
3t4rai
5tra2in
5tra1ka2
t3ra1ke
3tra4kt
3tr4a2ns
5tran4sa
5t4r4a4p.
5t2rau
4t3ra1z
3t4r4e.
4tr4ea
2trec
5tre4d.
tre1d
4t3re1da
t5rede4s3
tre1de
4t3re1du
3tr4ef
4t5r4e1g
4t3re4is
tre2i
4t5r4e4i1z
4trel
t3re5s4e
t3re3su
tre2t3
t4re2u
t3ri4b.
tri1b
5tri3b4u
5tri3co
tr4ic
trie5ta
tr4ie
tr4ig2
2tri2j
5t4ril
tri5ni
5t4r4i1o4
t3ri1si
tr4is
t3ri4t.
tr4it
5t4ri3t2i
5tro1dy
t3roe1d
tro4e
t3roes
5tro3fy1
3trog
t4ro3ï
5tr2o1j
4tr4o4l.
5tro2l3a
5tro3lo
5tr4o2m1m
5tr2o4n.
5tro1na
t5ro4nd
3tr2o1ne
5tr2on3n
5tr2o1no
5tr2o2ns
tron1t5j2
t4ro2n2t
t3r4oo4d
tro4o2
5t4r4oon
t4ro2o4s
tro5pi
t4r2os
5tro1tu
3trou
4t5rou5t4
tro5v2
5tru4c.
tr4uc
5t4ruf
4tru2g
5tru4i.
tru2i
5tru4i3e
t3r4uim
tru4i5t4
t3r2u2k
t4r2um
4ts
ts3a2d
tsa4g
ts1am
t3sa2p
ts3as
tse4d
t3se
t4s5ee2n
ts4e4e
t4s3e2i
ts5ei4nd
ts4ein
t4s5e1ne
ts4en
t4s3e2ng
t4s4er1g
ts5er1ge
t4s3e2v2
t2si2j
t1si
t4s3i4n1k
t2s3i2nt
ts2j2
ts3ja
t3sj1en
ts1je
3tsji
t1s2l4
ts4la4a4
t3s4lac
t5sl4a4g.
t3s4lag
ts3lam
t2s3le
t5sli1b
ts1li
t5s4lo4e
t3s4lu
ts2me
ts1m
ts4mo4e
ts1mo
ts3ne2u
ts2n4
ts1ne
ts4no
ts5nor
ts5not
ts3nu
ts3o3b
ts2o2l
ts3o1li
ts3om
ts1on
ts4o4p3p
t2s1op
ts1o4r
t2s1o2v2
t2s3pad
tsp4a
t3s4pan
t5spec
t4s3pet
t3spi
t4s3pil
t3s2po4e
tsp4o
t3s2po4o2
t5s4por
ts3pot
t4spro
tspr4
ts4pru
ts5q
t4s5s4
t3sta
ts2t
t4staak
tsta4a4
t4s5ta4n1k
ts5ta2nt
t4star
t4s3tas
t3s1te
t5s4te1d
t5ste4e2
ts5te3ko
t5ste2l1l
tst4el
t5ste4ls4
t5st4em
t5ste4r.
tst2er
t4ste2r5r
t5ste4rs
t5s4te4s.
t4s3te4s
t5steu
t4s3th
t1s4ti
t3sti2j
t5s4tij1g
t5st4il
ts5tin
t4s5t4j2
t1sto
ts5t2oep
tst4o4e
ts5t4o2ng
t4sto1re
t4s5t4rad
t1s4tr4
t4s5tre2i
t3stri
t4s5tro4e
t2s5ty1
t4su4
ts3ur
ts3u4s
ts3u4u4
t1s4y
4t3t
t5t4a
t5te
tte5lo4e
tt4el
tt4e1lo
tte5l4op
tt4e2n
tten4t5j2
t3te2nt
tte5ri
tt2er
t5tlet
t4t3l
tt3oo4g
tto4o2
ttop2
t5t4r4
t5t4um
tt3u4u4
3tu1a
3t2u2b
3tuch
t4uc
3tu3e
5tu1eu
tu3és
3t4uig
tu2i
5tuin
4tuip
2tu4it
tui4t4j2
4t2uk
tu4k3i
tul5pi
tu4lp
t4um
5tu1ne
5tun3n
tu1o
5tur1b
tu3ri
3tu4s3
tu2t3j2
tuurs5la
tu4u4
tuur2s2l4
tuu4rs
tu3w1a
4t1v2
tvaa1t5
tva4a4
t3v4e
4t1w
3t4wijf
tw4i2j
t2win
1ty1
3ty1p
tys4
4t1z
t3za
t3z2i
t5z4w
u1a
u3ac
u3an
ua5ne
ua3p
u5a4r.
ua4r5t
u4a3sa
ua1t4
2u2b
ub3ac
u3ba
ub2e4li
u3b4e
ub5em
u5b2i
u3b4o
ub5or
4uc
u1ch4e
ucht5s2l4
u4c2ht
uch4ts
uc4ki
ucle3
u1c4l
uc4t3a
u2c1t
uc4tin
uc3ti
u1d
u1da2
u5d4a.
ud5am
u2d3e2i
u1de
ud3e4s3s4
ude2s
u4de4z
ud3ez4e
ud4i4o
u1di
udi5olo1ge
udi4ol
udio3lo
udiolog4
udi3om
u3d4o4e2
u1do
ud3o4nd
ud3o4o2
u2d3o2v2
u4d1r4
uds5lo
u2ds
uds4l4
ud2s4m
uds5ma
ud3s1me
ud3s1mi
ud1s2t4
ud4sta
uds5tak
ud4s4ti
u2d1w
u3ec
ue2co
u1e4e4
u3ef
u3e2i
u1el
u4e1ne
u1er
uer3il
ue3ri
ue3s2t
u1eu
u5eul
u3e1z
u3è1
u4f3an
u1fa
u1f2l2
u1f4r4
u4f2s
u5ga
ug4da2
u4gd
ug4der
ug3de
ug2do
ug4dr4
u5g4e4l5o
u1ge
u2g3i2j
u1gi
ug1l4
u2go
ug3or
u2g1r4
ug5s1ce
u4gs
ug2sc
ug4sec
ug3se4
ugs4p
ugs5p4a
ug1s4t
ug4s5tra
ug1s4tr4
u1h
u2i
ui5ac
ui1a
ui2d3a
ui2d1o
ui2d4s1
uid3sp
uid5spre
uidspr4
uid5s4t4e.
uids2t4
uid3s1te
ui1d3u
u4i3e
uie2n4t
ui2fa
u4if
uif3l2
uif5r4
ui2fu
4uig
ui4g5a4a4
ui1g4a
uig1l4
ui2g3o
ui4g3r4
ui4gu
4u4ik
ui2k3a
ui4k3l4
ui2ko
ui2ku
ui2la
uil5a4a4
ui4l3em
u4i3le
ui2l5m
ui4l3og4
ui1lo
ui4lo4o2
uil3o3v2
4uim
ui2m3a
ui3m4ag
ui4n1a
ui2n5g
ui2no
uin5o2g
uin3or
uin4s5lo
uin3s2l4
ui2ns
uin5to
ui2nt
ui2p3l2
ui4p3o4
ui2p3r4
4u4is
ui2s3a
ui4s5c
ui4s3l4
ui5slu
uis5p
ui4s3t
ui4t3a4
u4it
uit5a4a4
uit5al
ui5tar
1ui4t3g2
ui4t1j2
3ui4t3l
ui2t1o
1uit5r4
uit3s2l4
ui4ts
uit3s2n4
uit5sp
uit4s5t4e.
uit3s4te
uits2t
3ui4t3w
3ui4t1z
ui3v2
4u3j
2uk
u2k3al
u1ka
uk3a4s
ukke4r4s5
u4k5k4
uk1ke
u2k3l4
u3k4las
u2k3n4
u2k3o
u3k4oc
uko2p
uk4o3p3l2
u4k3r4
uk3s2m
u2ks
uk3s3p4a
uk3s1p4l2
uk4s3ti
uks4t
uk1w
u1la
u2l3ac
ulam4
ula4p
ul4d3a
u4ld
uld5er3k4
ul1de
ul5do4p
ul1do
ul4d3u
u1le
ule5sp
ule2s
ul3f2l2
ul5fo
ul3fr4
ul3i4n.
u1li
u5li2ng
u3l3in3n
ul3k2a
ul5ke
ul2k3l4
u1lo
ul3o2p
u3l2os
ul2pa
u4lp
ulp3ac
ul4pi
ul2p3l2
ul2po
ul4p3r4
ul3sa
u4ls
ul3so
ul2s3p
uls5t4e.
ul1s2t
ul3s1te
uls5t4el
u3lu
um3af
u1ma
um3ar
3um5da
u2m1d
2u1me
ume4e4
um4es4
ume3s2t
u3m3om
u1mo
u2m3op
um3so4
u2ms
um3s4t
u2m3u2i
u1mu
u2n3ac
u1na
u2n2c
unc2h3r
un4dra
u4nd
un1dr4
un2d4s
und1s5ta
unds2t4
und5s1te
une4t
u1ne
u2n3g
1uni1v2
u1ni
un4k3r4
u4n1k
un4o
uno3g
un5o2p
un1st3a
u2ns
uns4t
un4st4e.
un3s1te
un1st3o
un4s4t5r4
unst5u2i
un1stu
un4tag
u2nt
un3ta
un4t5ee4n
un3te
unte4e2
un2tj2
un4t5o4
unt3s4m
un4ts
un4t3u
u3ol
u3on
u3o4o2
u1or
uo3r1u
u3os
u4o1ta3
4up
u1pa
u1pe
upe3k
upe4r1o
uper5s4t
u3pe4rs
u3p1h4
u3pi
u1p2l2
u4p3le2i
u1po
u3pol
u1p3om
up3op
u1pr4
up4tr4
u4p1t
u1ra
u2r3aan
ur2a4a4
u2r1ac
ur3a1d4a
u4r3a2d1v2
u2r3a4r
uras3
u4r3a2z
ur3d4o
u2r1d
u1r2e
ur3ech
u2r3ee2n
ure4e
uree5s3
ur4e5lu
ure4lu5r
u4rem
ur3e4m1b
ure4n
u3res
ur3e4s3s4
ure3s3t
ur3e1ta
4urf
ur2fa
ur3gi
ur1g
u1ri
uri4gl4
ur4ig
ur3ij1z
uri2j
u4r3i4nd
u4r3i2nt
4urk
urke2n5s
urke4n
ur1ke
ur4k4ie
ur1ki
ur3k4l4
urk4s5t
ur2k1s
u1ro
u4r5o2p1b4
ur3or
ur2o5s
ur5pr4
ur1p
ur4ser1v2
u4rs
ur3se
ur4s3e1v2
ur3s4fe
ur2s1f
ur2s2l4
urs5la4a4
urs5li
ur4s5m
ur2s2n4
ur4sp
urs5p4a
ur5sp4el
ur3spe
ur5spor
ursp4o
urs5ta1ke
ur1s4t
urs4tak
ur4s5th
ur4s3ti
urs5t4ik
ur3ta
u4rt
ur4tro
ur1tr4
ur5tro4e
u3ru
ur3u2i
4ur1v2
u1r4y
4us1a4a4
us3ad
us3a2m
us1a2p
u4sc
u5s2c4r2
use5tj2
u3se
use4t
u5s4ie
u1si
u4sj2
u4s5l4
u4s1m
u2s5n4
uso2
u3s3o3ï
us3os4
u2s3p
us5pi
us5p4u
us4ta
us2t
us5tag
ust3al
u2s3te
us4t3e2i
u4s3ti
u3s4t3o4o2
u4s5tr4a.
u1s4tr4
us5t4r4e.
us5tro
u3s5tru
u1stu4
ust3ur
ust3u4u4
u1ta
ut3aan
uta4a4
utaar5
u2t1ac
ut3af
u3tan
u3ta3s4
u4t5c
u4t3ee2s3
u1te
ute4e2
u4tek
u3t3e2ks
ut4em
u4t5e2m1m
ute4r5an
ut2er
u2t3ex
ut2h
ut3ho
u2tj2
u1to
uto5f
ut3oo4g
uto4o2
uto3pe
utop4l2
uto5po
utop4r4
u3t2o5s4
ut3s1a4a4
u4ts
ut3s2c
ut4s5e2ng
ut3se
uts4en
uts2m
ut1s2n4
ut3sp
ut4sp4a
ut4sp4o
ut2s2t
uts5tak
ut3sta
ut4st4e.
ut3s1te
ut5st4en
ut3s4tr4
ut5su4
u4t3t4
u1tu
u4t5w
u4u4
uu1r3a4
uu1r3e4
uu1r5i
u4ur3k
uu1r1o2
uur5s1te
uu4rs
uur1s4t
uur5s3ti
4uut
uu1t3a
uut3r4
uve4l4s
u1v2
uv4e
u5vel
uve5na
uw1a
u3w4ag
uw4ar
uw5a4rt
u1we
u2w3e2c
u3we5d
uw3ee2n
uwe4e
u2w3e2i
uwe4ne2n
uwe2n
uwe3ne4
uwe2s4
u1wi
u2w4i2j
uw5ij1z
u4wi4nd
u3w4i2ng
u4wi2n2s
uw3i2n1z
uw1o
u3w2o4e
uwo4ge
uw1r
uw3u2
uxa3
u3y1a
4u1z
uze3t4
uz4e
uz4ie2
uz2i
û4t3s4
1ü
ü4b
ü1n
ü3ri
üs3l4
1v2
2v.
vaar4ta
va4a4
vaa4rt
vaar2t5r4
va3de
va3g4
va2ki
v4a4k3l4
va2ko
va2l3a
va2l5m
va3lo
va4lo4e
val5si
va4ls
val4s5p
val4s5tek
val1s2t
val3s1te
va1lu5
va2n
va2n3ac
va1na
va4nd4
van1g3a
va2ng
van4gr4
va3no
va4noc
va1p
va3re
v2a5se
v4a3s4o
v4a1s4t3r4
va2s2t
va3su
va3te
va1t
va2t3h
va2t5j2
va3z
v4b
4v3c
v4e
3v4e.
5ve1b
vee4l
ve4e
vee3l5e
vee3p4
vee2s4
ve3g4h
v4e1g
ve4i3s4
ve2i
vei5tj2
v4e4it2
3vek
5vel
ve4l3a4g
ve1la
vel4d3o
ve4ld
ve3le
vel3k
5vem
v2e1m4a
ve4na
ve5na1re
5ve4nd
ve4n5k
ve2n3o
2ve2n3r
ven4s3e
ve2ns
ven4s2l4
vens5l4an
vens5lo
ven4s2p
vens5taak
vens4t
ven1sta
vensta4a4
vens5ta1ke
vens4tak
vens5tek
ven3s1te
ven4s3u4
ve2r
ver1a
ver5aas
ve1r2a4a4
ve4rad
vera4g
ve4ra4nd
ver5d4o
ve2r1d
v4e3rec
ve1re
ver3e1d
ve3r4e1g
ve3re2i
ver5e4is
ve5r4e4n.
ve5r4e4nd
ver3e4t
ver5ijd
ve3ri
veri2j
ver5ijl
ver5i2js
ve5r4i2ng
ver5k4
ver3o
ve3r3om
1v2er1o5v2
ver5p
ver5spe
ve4rs
ver1sp
ver5sta
ver1s4t
ver5sto
ver5t4w
ve4rt
ve1r1u
ve3ry
ve2s3
ves5ti
ves2t
ve2tj2
ve2to4
vet3og
vet3o4o2
ve3tor
ve2t3r4
ve3t4ro4e
vet5s1te
ve4ts
vets2t
5ve5z
3vi
4v5i4ce1pa
v4ic
vi3ce
vid5s2t4
vi2d2s1
vie4r3a
v4ie
vie4s3
vie2s5n4
vie4tj2
vi3e2u
vijf5
vi2j
vi2k4s
v4ik
vil4t3j2
vi4l1t
vi2ng4
vin4g2s3
v4i3o
vi5om
vi4s3an
v4is
vi1sa
vi1so
vi2s5ot
vis5p
vi4s3t
vi3s5tr4
vi1tr4
v4it
v3j
vje4
vje2t1
3vl
v3lar
vle4i3s4
vle2i
vlie4s5
v1li
vl4ie
vlo4t5s
1v3lo3v2
5v4o.
3vo4e
voe4t3a
voe4t3r4
voet5s3p
voe4ts
3vog
vo1ge4
3v4oi
vo2le
vol4g3a
vo2l1g
vol4gra
vol1gr4
vo2li
vol3i2j
vo4l5p
von4de3t4
vo4nd
von3de
von3d5u
3vo4o2
v4oo5d
voo4i5t
vo4oi
voo2r1n4
voor5na
vo3ra
vor1m3a
vor1m
vor4s5t4e.
vo4rs
vor1s4t
vor3s1te
vor5st4en
v2os3
3vot
vo4t3j2
3vou
v4ous5
3v4r2
vre2i5
vrie4s
vr4ie
vri2j5k4
vri2j
vri2js4
vrij5s3te
vr4ijs2t
v3t
vues4
vu2l
vu4l5p
vuu4r5s
vu4u4
vy3
2w.
w4aad3
wa4a4
w2aar
waa1r5e4
waar5s1te
waa4r2s
waar1s4t
wa4b3
wa3ba
wa5b4l
w2ad
wa3dr4
w4ag
wa2la
wa3l4an
4wam
wan4d5r4
wa4nd
wan4gr4
wa2ng
wang5s3l4
wan4g2s
wa2n1o
w4a2n3s4
3wap
w4ar
w5ar1c
5wa2r1d
war4s4t
wa4r2s
war3s5te
w4ar4t3j2
wa4rt
w4ar4to
wa2si
wa4s5l4
wa4s5p
w4a1s5tr4
wa2s2t
1wa1te
wa1t
wa2t5j2
wa3tr4
3way
2w1b
w1c
2w1d
w4do4o2
w1do
wd3oom
w4e2a
2we2c
3we1d
we1de4
we2d3i
we4d3r4
wee4ki
we4e
wee2k
wee4k3r4
we4e3lo
wee2l
wee3s4t
wee2s3
wee5s1te
3w4e1g
we4g1a
we4ger1v2
we1ge
we2g3l4
we2g3o
we4g5r4
we4i3s
we2i
wei5tj2
w4e4it2
we4k3r4
we4le2
4w3e2lem
w2e3li
w4e2lo
we4l3s
we2m
w2e1m3a
we3me
we2n
we1na4
wen3ad
we3ne4
we4nem
we5n4e4n.
wene2n
wen5e4n5k4
we3ni
wen4k3a
we4n1k
wen3o
wen5to
we2nt
wer2f
4wer1g
wer4ka
wer4k5l4
wer4k2n4
wer4k3o
wer4k3r4
wer3k5ru
wer4k3u4
wer4k3w
wer4p3a
wer1p
wer4p3l2
wer4pr4
we4r4s
wer5s1te
wer1s4t
we2s3
we3sp4o
we1sp
wes4t5o
wes2t
3we4t.
we2th
we2t3j2
wet4s2t
we4ts
we2t3u
2wex
weze2n4s5
w4e3zen
we1z
wez4e
2w1f
w1g
w1h
wie4la
w4ie
wie4t
w4i2j
3w4ijd
wij4ka
wi2jk
wi2j4s
wijs3l4
w4ijs3p
wij1s5ta
w4ijs2t
w4i4k
3wil
win1d3a
wi4nd
win4d3r4
w4i2ng
2wi2n3r
wi2n2s
winst5a4a4
wins4t
win1sta
win1s4t5r4
wi4t3h
w4it
wi4t3j2
wi2t3o4
wit3r4
w1j
2w1k
2w1l
4w1m
2wn
w2n3ac
w1na
w3ne
w3ni
w3no
w3o3b
w2o4e
woes3
woes4t5a
woes2t
wo4l
wo1l3a
wol4f4s5
woon5s2f
wo4o2
w4oon
woo2ns
woor4d5r4
woo2r1d
wor4g3e
wor1g
w1p
wren4s4t
wre2ns
wrens5t4e.
wren3s1te
2ws
ws3a2
w3sc
w1s2l4
w2s3le
w3s2om
w3sp
w2s2p4l2
w4spr4
w5sp2ra
w1s4t
w4sti2j
ws3ti
2wt
w3tes3
w1te
wtje5sp
w1tj2
wtje4s3
w1to
w1tr4
wu2
wva2
w1v2
w1w
xaf4
xa3g
xame2n5t
xa1me
xa3men
xan3
xa2n5t
x1c
x4e
xe4n4d
xe3ro
x1f
x1h
xie4t
x4ie
xi3g
x4i5o
xi3s1ta
x4is
xi2s3t
xi3s1to
xi4t3i
x4it
x3l
x1m
xo3no
x4op
x2o3s4
x1p
xpre2
xpr4
x3pres5
x3r
x3so
x3sp
x1t
x2tak
xt4ie2
x3ti
x3w
xy3
y1a
ya3s4
ya4s5p
y3a1t
yba2l3
y1b
y3ba
ybe4r4t3
y3b4e
y1c
ycho3
y3co
y1d4
ydi3a
y1di
y5dr4
ydro3
y1e
yes3
y3és
y3è1
y1f
y1g
y1gu2
y1h
y1i
y4in
y5is
yks1ge4
y2ks
yk4s5g
y3la
y2l3al
y3le
y4l3et
y3lo
y2lo3l
ym2f5l2
y2m1f
ym5pa
y2m1p
y3na
yn3er
y1ne
y3no
y2n1t
y1o
y3on
y3os
yo3t
y1p
y3p4h4
ypo3
ypot4
y2p3s
yp5si
y1r
y3r4e
y5ri
ys3
y1s4a
y3s4c
y5s4e
yse5t
y3s4f
y3s4h
y1s4i
y3s4o
y3s4p
y2s5p4l2
ys4ta
ys2t
y1s5tr4
y3s4y
y1t
y2t3hu
yto3
y2to2f
ytop4
yu5a
y3u2i
y3u2r
yva1ri5
y1v2
y1w4
1z
4z.
zaa4r5t
za4a4
za3f2
zag1s4t
za4gs
za2k3a2
za2k3r4
za4n2d
z4an1d5a4
zan3di
zan4dr4
zan4g3s
za2ng
za3po
za3s4
4z1b
4zc
4zd
z4e
zee3k
ze4e
zee4l5d
zee2l
zee3r4o
zee2r
zeer1o5v2
zee4r5s
zee3s4
ze5ge
z4e1g
zeg4s3l4
ze4g3s4
zei3s4p
ze2i
ze4is
ze5k
zel5dr4
ze4ld
z3e3lem
ze1le
zel2f1
zel4so
ze4ls
zen4d3a
ze4nd
ze4nin
ze1ni
ze4n5k
zen3o4
zen4og
ze3n1on
ze4r3a
ze3ro
ze4r2s
zer4s5e
ze4s3
ze5s4ch2
ze1sc
ze3s5e
ze3s5l4
ze5s1te
zes2t
ze2t3a
ze2t3h
ze4ti
ze2t3j2
ze2t3r4
zev4e2
ze1v2
zeven3
4zf
4zg
2z3h
z2i
zie4k3l4
z4ie
zi2ek
zie4k3o
ziek3w
zie4l4s
zie5s2l4
3z4if
zi2g5a
zij5k4l4
zi2j
zi2jk
zij3po
zi2j5s4
zik2w
z4ik
zi4n3a4
zin4g2s3
zi2ng
zin4k3l4
zi4n1k
zi2n4s
zins4t
zin1s5ta
zin5s4tr4
z4i3o5
zi1pi3
z4i4t
zi1t3e
zi4t3j2
zi3t3u4
4z3k
4z3l
4zm
zo1di5
zoe2t3j2
zo4e
zoet5s1te
zoe4ts
zoets2t
zo3f2
z4oi4
zo5i1e
zo3la
z4o1me4
zo2na
zon3s2f
zo2ns
zon5ta
zo2nt
zooi5tj2
zo4o2
zo4oi
zoo4it
zo1p
zor4g3a
zor1g
zor4gl4
zor4gr4
zo2t
zo1t3h
z2o3tr4
zo3v2
4z3p
4z3r
2zs
4z5t
zui4d3i
zu2i
zui4dr4
zus3
2z1v2
z4w
zwets5t4e.
zwet4s2t
zwet4s1te
zwe4ts
5zy
2z3z
zz3in
zz2i
zz3or
z4z5w
.a8a8n9d8a8c8h8t8s9t8r8e8k9k8e8r.
.a4an1da
.aa4nd
.aanda4c2ht
.aandachts5tre4k5k4
.aandach4ts
.aandachts2t
.aandacht1s4tr4
.aandachtstrek1ke
.a8a8n9d8a8c8h8t8s9t8r8e8k9k8e8r8s.
.aandachtstrekke4r4s
.a8c8h8t9e8n9d8e8r.
.achten3de
.a8c8h8t9e8n9d8e8r8s.
.achtende4rs
.a8c8h8t9e8n9d8e8r9t8i8g.
.acht5ender5t4i
.achtende4rt
.a8c8h8t9e8n9d8e8r9t8i8g9s8t8e.
.achtenderti4gs4
.ach1tendertig1s1te
.achtendertigs2t
.b8e9h8e8e8r8s9t8a9k8e8n.
.be1h4
.beh4e
.beheers5tak
.behe4e
.behee2r
.behee4rs
.beheer1s4t
.beheersta1ke
.b8e9s8c8h8e8r8m9e8n9g8e8l.
.be1s4
.be1sc
.be3s4ch2
.be5scher
.besch4e
.bescherm5e2ng
.bescher1m
.bescher1me
.bescher3men
.beschermen1ge
.b8e9s8c8h8e8r8m9e8n9g8e9l8e8n.
.beschermenge3l4e
.beschermenge5l4en
.b8e9s8t8u8u8r8s9l8a9g8e8n.
.bes2t
.be1stu
.bestuurs5la
.bestu4u4
.bestuur2s2l4
.bestuu4rs
.bestuur3s4lag
.bestuursla1ge
.b8e9s8t8u8u8r8s9t8a9k8e8n.
.bestuurs5ta1ke
.bes2tuur1s4t
.bestuurs4tak
.b8i8j9s8t8a8n8d8s9t8r8e8k9k8e8r.
.b2i
.bi2j1s2
.bi2j
.b4ijs2t
.bij1sta
.bij3s4ta4nd
.bijstan2ds
.bijs2tands2t4
.bijstands5tre4k5k4
.bijstand1s4tr4
.bijstandstrek1ke
.b8i8j9s8t8a8n8d8s9t8r8e8k9k8e8r8s.
.bijstandstrekke4r4s
.b8u8i9t8e8n9a8n9t8e8n9n8e.
.b4u
.bu2i
.bu4it
.bui3t4en
.bui1te
.buite2nan
.buite1na
.buite3na2nt
.bui1te5nan3te
.buiten5anten3n
.buit4enan5t4e4n
.buitenanten5n2e
.b8u8i9t8e8n9a8n9t8e8n9n8e8s.
.buitenanten3nes
.d8o8n9d8e8r9a8a8l.
.don5der
.do4nd
.don3de
.dondera4
.donder5aal
.donde1r2a4a4
.h8a8n9d8e8l8s9t8a8a8l.
.handel1s2t
.handel3sta
.handelst4aal
.handelsta4a4
.h8e8r9e9n8e8n.
.he1re
.here4ne
.herene2n
.k8e8t9t8i8n8g9s8t8e9k8e8n.
.k2et
.kettin4g5s
.ke4t3t
.ket3ti
.ketting4s2t
.ketti2ng
.ketting1s1te
.kettings3tek
.kettingste1ke
.l8a8n8d8s9t8a8a8l.
.l4an
.la4nd
.lands5t4aal
.lan2ds
.lands2t4
.land1sta
.landsta4a4
.m8e8e8s8t9a8l.
.mee2s3
.mee3s4t5al
.mees2t
.m8i9n8i8s9t8e8r8s9p8o8r9t8e9f8e8u8i8l9l8e.
.mi1ni
.mi3n4is
.mini2s3t
.minis1te
.minist2er
.ministe4rs
.minister1sp
.ministersp4o
.ministersp4o4rt
.ministers5port4ef
.minis1terspor1te
.ministersporte1fe
.ministersportefeu2i
.ministersportefeui2l1l
.m8i9n8i8s9t8e8r8s9p8o8r9t8e9f8e8u8i8l9l8e8s.
.ministersportefeuille2s
.o8n8t9h8o8o8f9d8i8n9g8e8n.
.ont1h
.on4tho
.ont1ho4o2
.onthoof5di
.onth4oof
.onthoo4fd
.onthoof2d3in
.onthoofdi2ng
.onthoofdin5gen
.onthoofdin1ge
.p8a9l8i8n8g9s8t8e9k8e8n.
.pa3li
.palin4g5s
.paling4s2t
.pali2ng
.paling1s1te
.palings3tek
.palingste1ke
.r8e8c8h8t8s9t8a8a8l.
.re4c2ht
.rechts5t4aal
.rech4ts
.recht3sta
.rechts2t
.rechtsta4a4
.s8c8h8i8l9d8e8r8s9t8a9l8e8n8t.
.s4ch2
.s2chi
.schi4ld
.schil1de
.schilde4rs
.schilder1s4t
.schilderst2al
.schilders5tale2nt
.schildersta1le
.s8o8u9v8e9n8i8e8r8t8j8e.
.sou1v2
.souv4e
.souve3n4ie
.souve1ni
.souvenie4rt
.souvenier1tj2
.s8o8u9v8e9n8i8e8r8t8j8e8s.
.souveniertje4s3
.s8p8i8e9g8e8l9e8i.
.s4p4ie
.spie3ge
.spi4e1g
.spiege3l4e
.spiegele2i
.v8e8r9e8n9g8e8l8s9t8e.
.ve1re
.vere2ng
.veren1ge
.verengel5s1te
.verenge4ls
.verengel1s2t
.v8e8r9h8o8l9l8a8n8d8s9t8e.
.ver3h
.verho2l1l
.verhol5la
.verholl4an
.verhol3la4nd
.verhollan2ds
.verhollands2t4
.verholland3s1te
.v8e8r9i8n9l8a8n8d8s9t8e.
.ve3ri
.ver3i2n3l
.verinl4an
.verin3la4nd
.verinlan2ds
.verinlands2t4
.verinland3s1te
.v8e8r9k8i8n8d8s9t8e.
.ver5k4
.ver1ki
.verkind5s1te
.verki4nd
.verkin2ds
.verkinds2t4
.v8e8r9n8e9d8e8r9l8a8n8d8s9t8e.
.ve2r1n
.ver1ne
.verne1d
.verned4er
.verne1de
.verneder3l
.vernederl4an
.verneder3la4nd
.vernederlan2ds
.vernederlands2t4
.vernederland3s1te
.v8e8r9r8e8c8h8t8s9t8e.
.ve2r5r
.verre4c2ht
.verrech4ts
.verrecht3s1te
.verrechts2t
.v8e8r9s8t8e8e8d8s9t8e.
.ve4rs
.ver1s4t
.ver3s1te
.verste4e2
.verstee1d
.verstee2d1s
.vers2teeds2t4
.vers1teed3s1te
.w8a9t8e8r9s8t8a8a8t8s9i8n9g8e9n8i9e8u8r.
.wa1te
.wa1t
.wat2er
.wate4rs
.water1s4t
.wa1ters4taa1t
.watersta4a4
.waterstaat1si4
.waterstaa4ts
.waterstaatsi2ng4
.waterstaats5in3ge1ni
.waterstaatsin5gen
.waterstaatsin1ge
.waterstaatsinge3n4ie
.waterstaatsinge3nie2u
.waterstaatsingeni1eur
.w8a9t8e8r9s8t8a8a8t8s9i8n9g8e9n8i9e8u8r8s.
.waterstaatsingenieu4rs
PK
!<\
,
,hyphenation/hyph_nn.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a4
.aa4
.ab4b4a
.a2b1b
.ab7be
.ab6s5t6
.a6b1s
.ac6tin
.a4c1t
.ac1ti
.ad4e8l4s
.a4del
.a1de
.ad8la.
.a4d1l4
.ad3la
.ad6le.
.ad1le
.ad2r4
.a8d1s2
.a2f7f
.a2f5t4
.af5f6u
.ag6na.
.ag1n
.ag1na
.ag6ne.
.ag1n4e
.a1g4r4
.ai2
.a6k
.ak6ka.
.a2kk
.ak1ka
.ak6ke.
.ak1ke
.ak3kl4
.ak3kr6
.ak6ne.
.a2k1n2
.ak5ne
.ak6r6
.ak8sa.
.ak1sa
.ak4se.
.ak1se
.ak6ta
.a6k1t
.ak4te.
.ak1te
.akte4r
.akte1r5e
.ak5tr4
.akva7
.akv2
.a6l
.albu5e2n
.a2l1b2
.al1b2u
.al1fa3
.a4l1f
.al4ge.
.a2lg
.al1ge
.al5gi
.al6ka.
.a6l1k
.al3ka
.al4ke.
.al1k4e
.al4kom
.al4la.
.a2l1l
.al4le.
.al1le
.al5leg
.al5lo
.al8l5s6
.al5ma.
.a2l1m
.al1ma
.al8me.
.al1me
.al5m6in
.al1mi
.al4na
.a2l1n
.al6s7k
.a8l1s
.als5o
.alt4a
.a2l1t
.al4ta.
.al5tar
.al4te.
.al1te
.alt3o
.al6ve.
.a8lv
.a2m7b
.a6me
.am6ma.
.a2m1m4
.am1ma
.am4me.
.am1me
.a6m4s4
.am6ta
.a2m1t
.a6n
.ana3b
.a1na
.an4da.
.a6nd
.an1da
.an4de.
.an1de
.an5d4ra
.andr4
.an4d5ø
.an3er
.a1ne
.anes5
.an4ga.
.a6ng
.an1ga
.an3g4e
.an4ge.
.an2g6e4s
.an4ja.
.a4n1j
.an4ka.
.a2nk
.an1ka
.an4ke.
.an1ke
.an4la.
.a4n1l
.a4n3n
.an4na.
.an1na
.an7nal
.an1n4e
.an4ne.
.a6n5s8
.an4sa.
.an4se.
.an1se
.a6n1t2
.an4ta.
.an4te.
.an1te
.an5tem
.an5t2e1p
.an4ti.
.an1ti
.an6tin
.an4tis
.an4tiv
.ap8lan
.a1pla
.apl6
.a2p1p6
.a2p8t
.ar1b4i5
.a2r1b8
.ar6de.
.a4rd
.ar1de
.a2re4o7
.a1re
.ar4ge.
.a2r1g
.ar1ge
.ar5g6es
.ari6a
.a1ri
.ar2i8e
.arie5ne
.ari1e4n
.ar5ka
.a2r1k
.ar8ka.
.arlan9
.a2r1l
.ar4me.
.a2r1m
.ar1me
.ar7m2ea2
.ar4na.
.a2r2n
.ar3na
.ar3ne
.ar4ne.
.ar5ne1s2
.ar6ra.
.a2r1r
.ar6r7u
.ar6ta.
.a6r1t
.ar4te.
.ar3te
.ar9ti
.ar6va.
.a2r1v
.arv4a
.ar4ve.
.ar4ve2d1
.ar4ver
.arvi6
.a6s
.a1s8ka
.as4ke.
.a2s1ke
.as5k2e1se
.as2ke1s
.a2s4le.
.as1l4
.as1le
.as6pa.
.asp2
.as1pa
.a4s5s
.ast5ru
.astr4
.a2t4h
.at4ki
.a4t3k2
.at4le.
.a6t3l6
.at1le
.at4na.
.a8t1n
.at3na
.at2o4
.a4t3s
.at1te4
.a6tt
.at1t6r4
.a5ty
.au8de
.a2ud
.a6u6e
.au8ga
.au4ge
.au4ka.
.au1k2a
.au8le.
.au1le
.au4r
.au6sa.
.a4u1sa
.au6se.
.a2u1se
.aus9k
.au4s1p2
.avi2
.a1v7i6d
.av7in1de
.a1vin
.avi6nd
.av4la.
.a6v1l2
.av4le.
.av1le
.a8v7s6
.b6
.ba6by5
.b4a
.b8a6la.
.ba3la
.ba4le.
.b2a1le
.ba4ne.
.ba1ne
.ban4k3l4
.ba2nk
.ba4re.
.ba3re
.ba4ri.
.b2a3ri
.ba8te.
.ba1te
.be4de.
.be1de
.bede4n
.be6d5e1ne
.be4d5et
.be8di.
.be1di
.be5e4d
.be3e2
.be6ke.
.be5ke
.be4la.
.b2e1la
.be9nar
.b2e4na
.be4ne.
.be3ne
.be6ra.
.b4er
.b2e1ra
.be6re.
.be1re
.be4res
.b6er6e6tt
.bere4t
.be8ri.
.be1ri
.be7ska
.b4e1s2
.b6e6sten
.bes1te
.b4e4ta.
.b4et
.be1ta
.be6te.
.b2e3te
.b2e5t4v
.b2i6de.
.b4i
.bi1d
.bi1de
.bi6en
.b2ie
.bi4le.
.bi1le
.bi3let5
.bi6t3r4
.bl4
.b2la4d
.b4le4k3
.b1le
.ble5k6e
.blek4k3f
.bl2e2kk
.blekk3s4t6
.blekk7s
.blekk9s8v4
.bli6ng5
.b1li
.bo8da
.b2o
.b2o1d
.bo8de.
.bo3d4e
.bo8g1s8
.b2og
.bok5
.bo4k4en.
.bo5k6en
.bo1ke
.b4o8la
.b4ol
.b2on2
.bo4na.
.bo1na
.b2o6r6t5
.bor1t6e
.bor8ti8
.bort9r4
.brud8d7s6
.br8
.b4ru
.br2ud
.bru6d1d
.brudds1å7
.bu6da.
.b2u
.bu3da
.b2ud
.bu3d6r4
.bu4et
.bus6sy
.bu4s1s
.by6de.
.by1de
.by8ta.
.by1ta
.bæ2
.b6ø6k5a
.b4ø
.bø8nen
.bø2n
.bø1ne5
.bø8ner
.c4
.ca4en.
.c4a
.c2a4e2
.c2a4r4s5
.ca6se
.c2e6b2
.c2os1
.co4st
.d6
.da4ge.
.da1ge
.da4le.
.d2a1le
.dag1s6e
.da8gs1
.da2m
.da4ne.
.d8a1ne
.d4a4ta5
.dau3s
.deka9r
.d4e1ka
.dek2o7d
.de5k2o
.de4le.
.de1le
.de4l5ei
.d4e8l5s6
.de4mi.
.de1mi
.de3m6o7e
.de1mo
.den5sl4
.d2e6ns
.de4re.
.d2er
.de1re
.de4r5e4t
.de6r3i4
.de4ri.
.de7r4i5v
.de4r5om
.d2ero
.de6s5m
.de1s
.d4e4so
.de4sto
.d4es2t
.de4u
.devi5s
.de1v
.di2a
.di5e4l
.d2ie
.di6er
.di4et.
.di1et
.di9f6t
.d2if
.di4ne.
.d4i1ne
.dings7a2
.din8g2s1
.di6ng
.d6i4sk
.di8s3t
.do4e2n
.d6o1e
.do4er
.do8et
.d2o1me5
.d6o4ra
.do6re.
.d8o1re
.drau4m
.dr4
.ds4
.du4a
.du4en.
.du1e2n
.du4er
.du2ft3
.du4ge.
.du1ge
.du2k3n2
.du4se.
.du1s
.d2u1se
.du4st4
.dy4re.
.dø6la.
.dø1la
.d2ø6r3
.dø7r4a
.dø7r4e
.dø4ve.
.då5ren
.då7ret
.e6
.eb4b4a
.e1b2
.e2b1b
.e1cu6
.ed4da
.e6d1d
.ed4le.
.e4d1l4
.ed1le
.e1fo7
.e1f
.ef4ta
.e8ft
.eft8a5s4
.ef6ter
.ef1te
.eg8de.
.e8g1d
.eg1de
.e1ge2
.ege5l
.eg6ga.
.e4g1g
.eg1ga
.eg4ge.
.eg1ge
.eg4gel
.e6g8la.
.egl2a
.eg4le.
.eg1l6e
.eg6na.
.eg1n
.eg1na
.eg4ne.
.eg1n4e
.eg2o7t8
.e1go
.e8gs4
.eg8se.
.eg1se
.eg5si
.ei2
.e2i1d5a
.e2i5den
.ei1de
.ei4ga.
.ei3ga
.e2ig
.ei7g2er
.ei1g6e
.ei1k5a
.e2ik
.ei4ke.
.ei1ke
.ei3l
.ei3m
.ei8ma.
.e4i1ma
.ei8me.
.ei1me
.ein7as
.e6i1na
.e4i3ni
.ein5og5
.e2i1no
.ein5s6ta
.ei6n1s
.e4in8s7to
.ei9rar
.e2ir
.ei1ra
.ei5res
.ei1re
.ei1r9u
.ei5te
.ei1t9r4
.ekk4o
.e2kk
.ek4le.
.e1kl4
.ek1le
.ek3li
.ek6ne.
.e2k1n2
.ek1ne
.e1k4r6
.ek8sa.
.ek1sa
.ek3se
.ek8se.
.ek4sp2
.ek1s6pi
.eks3t4
.ek5s4ta
.ek8ta
.e6k1t
.ek4te.
.ek1te
.ek7to
.el8da.
.e6ld
.el1da
.el4de.
.el1de
.el4g5r4
.e2lg
.el4i5ne
.e1li
.e6l3k2
.e2l5l
.e8l6s
.els6a
.el6s4k2l4
.els6t
.el6ta.
.e2l1t
.el6te.
.el1te
.el6veg
.e8lv
.e2m3b2
.em1fa9
.e2m1f
.em4ma.
.e2m1m4
.em1ma
.em8me.
.em1me
.em6na.
.e2m1n
.em1na
.e8m5p
.e1n5a
.en4da.
.e6nd
.en1da
.en4de.
.en1de
.end5r4
.en4ga.
.en1g6a
.e6ng
.en6gav
.en3ge
.en4ge.
.en6g5r4
.en8gs6
.en6ka
.e2nk
.en4ke.
.en1ke
.en5og5
.e1no
.en6sa
.e6ns
.en4se.
.en1se
.en5so
.en3sp2
.e6n5t
.en6te.
.en1te
.ent4r4
.en6t5ra
.en4t8re
.e1n3ø4
.e2p6t
.e1p
.e1r8a
.e1r4e
.er4g5r4
.e2r1g
.er4ke
.e2r1k
.er4la.
.e2r1l
.er4le.
.er1le
.er6ma.
.e2r1m
.er1ma
.er4me.
.er3me
.er4mek
.er4na.
.e2rn
.er3na
.er6ta.
.e6r1t
.er4te.
.er1te
.es9ast
.e1sa
.e1sas
.es1k
.e1s3ka
.e2s8ka.
.es4ke.
.e2s1ke
.es5l4
.es8la.
.e1sla
.e2s8le.
.es1le
.e1s3p2
.es6pa.
.es1pa
.e8s9r
.es8sa.
.e4s1s
.es4se.
.es1se
.es4so
.es5ta
.es3te
.e6s6te.
.e4s7tet
.es5ti
.est3r4
.e1s7tu
.et8la.
.e6t3l6
.et8le.
.et1le
.et4na
.e8t1n
.et4ne.
.et1ne
.e4t4s1
.et8sa.
.et5s4e
.et6se.
.et1s4i
.et1te4
.e6tt
.et6ter
.et6ti
.ev8ja
.e1v
.e2vj
.ev4je.
.ev1j2e
.ev4ne.
.e2v1n
.ev1ne
.ex4
.f4
.f2a2e2
.fa8g3s4
.fa4ne.
.fa1ne
.fan3t4o
.fa6n1t
.fe4e2
.fei4l5i
.f4eil
.fe2l
.f2e1la9
.fe2l5l
.fel4ta
.fe2l1t
.fe4ma
.f8e8me
.fem5o6g5
.fe4mo
.fe2m5t
.fer8ro
.f2e2r2r
.fe4r6s
.fe8sl4
.fe8st5
.fes3t6e
.fi4b5
.fi6a
.fi4le.
.fi1le
.fire5o6g5
.f2i2r
.fi1re
.fire3o6
.fi2s6k
.f2jel4
.fj2e
.fla4t5o
.f2l2
.fo4bi.
.f2o3b4i
.fol2
.fo2r
.for3d6ri
.fo4rd
.fordr4
.for7d6ra
.for5en.
.for7e6n
.f8ore
.for6m5s
.fo2r1m
.for7s6o
.f2o4rs
.f2re6e3
.fr2
.fri5e6re
.fri1er
.fr2ie
.fug2
.fu6ge.
.fu1ge
.ful2
.fu8se.
.f2u1se
.fy8s4e
.f2y3s
.fø8rel
.f8ør
.fø1re
.g6
.gaf7
.ga4le.
.g2a1le
.ga8li.
.ga1li
.ga4me
.ga4ne.
.ga1ne
.gan8g5s4
.ga6ng
.gas4
.gas5ta
.g2a2t
.ga4ve.
.g2a1ve
.ga9ve3e2
.g2e2a
.ge1l4e
.ge3ne
.g2en
.ge6ni5
.ge6n5s
.ge4o6
.ge4st
.g6es
.ge5s6tap
.ge9sv2
.gh2a5
.g1h
.gif4t5s
.g2if
.gi8ft
.gi4n
.gi4s
.gis7p2
.g4i8v3a
.gl6
.glo6i
.g1lo9v
.gl2y5s
.glø9se
.g1lø
.gnå7la
.g1n
.g2nå
.g1nål
.go5de1s
.g2o1d
.go1d4e
.g2os7
.gra4v3e6nd
.gr4
.g2rav
.gr2a1ve
.gra1ven
.g2re2i7e
.grun6n5s
.gru4nn
.gru4s5s
.gr4us
.gu4de.
.g2ud
.gu1de
.gu2d5v4
.gu4lat
.gu1la
.gus1
.gu4tu.
.gu1tu
.gø2
.gå1s4e5
.gå2s2
.gå4v
.h4
.hai5s6
.h2a
.hai1
.h4a6ka.
.ha5ka
.ha8ma.
.ha5ma
.hand5s6l4
.h4an8ds
.ha6nd
.handsla9
.ha6v5ak
.ha1v4a
.ha6vi
.he4er.
.he3e2
.he2i7e
.he2i
.he2n
.he1n3i2
.he6r5i6
.h2e6r5o6
.he9r6o1e
.he7r6oi
.he7r6ol
.he9r8os
.he4s4s5
.he4t5
.he4t6s5
.het7s6e
.h2e5t6e
.hi2l4l
.hi4n
.hi6re.
.h2ir
.hi1re
.h2o2d
.h4o
.h6o2e
.ho4re.
.h8or2e1
.ho5ren
.ho7ret
.ho8ta.
.h2ot
.ho1ta
.ho8va.
.ho1v
.hu4di
.h2u4d3
.hus6val
.hus1v2
.hvit3
.hv4
.hvi3t4e
.hy6ra.
.h6y
.hy1ra
.hy4re.
.hø4i5
.hø4re
.hø4va
.hø4ve.
.hø1ve
.høy6s1t5æ
.hø2y1s4
.høys2t
.hå8en.
.h6å
.h4å1e
.håe2n
.hå8er
.hånd5s6l4
.hå6nd
.hån8ds
.i2
.i3a4
.i3bl2
.i1b2
.i4da
.ifø5re
.i3fø
.if8ør
.i6g8la.
.ig1l
.igl2a
.ig4le.
.ig1l6e
.i4her
.i3h
.ik2
.i2k6k
.ik1ke5
.i3k6l4
.i6k5t
.ik8te.
.ik1te
.i5k1v2
.i3la
.i4la.
.i8lan1de
.il6a6nd
.ilbo8da
.i2l1b2
.ilb2o
.ilb2o1d
.il6d3r4
.i6ld
.il6de.
.il1de
.i3leg
.i1le
.i6le1s
.il4ja
.i2lj
.il6je.
.il1j2e
.il6ke.
.il1k4e3
.i6l1k
.il4le.
.i2l1l
.il1le
.il6sk
.i8l1s
.il4te.
.i2l1t
.il1te
.i6me
.imø5te.
.i3mø2
.i7m4øt
.imø1te
.in5ad
.i1na
.in5de
.i6nd
.in8ga.
.i6ng
.in1ga
.in4ge.
.in1ge
.in3gr4
.in6gri
.i2n5k6
.in6ka.
.in1ka
.i4n4n3
.in5n6e
.in4ne.
.in6n7eks
.in2nek
.in6n7e4ts1
.in6n7e6tt
.in4n3i4
.i6n5s
.in7t4es
.i6n1t
.in1te
.io4na
.i2on
.io4nom
.io1no
.i4rer
.i1re
.i2r7k8
.ir8ra.
.i2r1r
.ir6re.
.ir3r6e
.i6sa
.i3sc
.i6se
.is7k6
.isl4a8ga
.is1l4
.is6lam
.i2s4le.
.is1le
.is4let
.is4me.
.is1m
.is1me
.i8s8na.
.is1n
.is1na
.i4s8ne.
.is5ne
.is5pa
.is1p2
.i5s1pe
.i4s3s4
.is6sa.
.is4se.
.is1se
.is5sk
.ist6
.i2s8te.
.i2s1te
.i5s2ted
.i5s2tem
.is7ti
.i6s7tj
.i6s7tr4
.is8ut.
.i2s1u4t1
.i6s5ø
.i5t2i
.i3va
.i4van
.i4var.
.i3ve
.i4vel
.i6ven
.i4v8er.
.i4ve1re
.iv8ra.
.i2v3r8
.iv6re.
.iv1re
.i3ø
.j4
.ja4de.
.ja1de
.j2a6e2
.ja4ne
.ja8se.
.ja7se
.ja4va.
.ja1va
.j2e2a2
.j2e
.je2l
.je2m
.je5r6e
.jer6n5s4
.j2e2rn
.je4ta
.ji4
.jo8en.
.j2o
.j6o1e
.joe2n
.jo4se.
.j2os
.jo3se
.ju6a
.ju6la
.ju2l
.ju1le3
.ju4l4i3
.jø4d2e1p
.j2ø
.jø1de
.k6
.kaf2
.k8a6la.
.ka6li.
.ka1li
.kalve5l8
.ka8lv
.ka5me
.ka3na
.ka4ne.
.ka1ne
.ka4p3r6
.ka4ra.
.ka1ra
.ka5r6a5v
.ka6re.
.ka1re
.ka5rin
.k2a1ri
.ka4te.
.ka1te
.ka5t6h
.kau9k
.ka6va.
.ka1va
.ka4ve.
.k2a1ve
.ke6e2
.kel3
.kier3
.k4ie2
.k6i4na
.ki7ni
.k2i2no3
.k2ir2
.ki4se.
.ki3se
.ki8va.
.k6iv
.k4i1va
.ki6ve.
.ki1ve
.kje3de5
.kj2
.kj2e
.kje4k
.k4je1k7l4
.kj4ø4nn4
.kj2ø
.kle4s
.kl4
.k1le
.kl4i5ne
.k1li
.klo9va
.k1lov
.kly7sa
.kl2ys
.kna7se
.k1n2
.k1na
.kne4p3r6
.k1ne
.k2n2e1p
.ko6da.
.ko5da
.k2o1d
.ko7gr4
.k2og
.k4o4la
.k2o3le
.ko8le.
.k3o6l5j
.ko3pe
.ko6pe.
.kor6s5ed
.k2o4rs
.kor1se
.ko4se.
.k2os
.ko1se
.ko6ta.
.k2ot
.ko1ta
.ko4te.
.ko1te
.ko6ve.
.krin8g5s1
.kr6
.kri6ng
.ks6
.ku5f4l2
.ku4le
.ku8ra.
.ku7ra
.ku4re.
.ku1re
.ku8ta.
.ku1ta
.ku8te.
.ku1te
.kva4r1a
.kv2
.k4v4e9ka
.kve5ke
.ky8la.
.ky1la
.ky4le.
.ky1le
.ky6te.
.ky1te
.kø3s4
.kå6pa
.l6
.la4da.
.l6a1da
.la4de.
.l6a1de
.l4a4ga
.la4ge.
.la1ge
.la5g2er
.la4ma.
.la1ma
.la6ta.
.l4a1ta1
.la4te.
.la1te
.la3tr4
.la4va
.l2a3ve
.la4ve.
.le8ar
.l2ea
.le4da.
.le1da
.le4de.
.le1de
.le4er
.le3e2
.le6e6t
.le2f
.l2ef3l2
.leg4a5ta1
.l2e1ga
.leg2at
.le4ge.
.le1ge
.le4gel
.le8gi.
.le3gi
.lei5er.
.le2i7e
.lei4ve
.le4ke.
.le1k6e
.l2e4k5r6
.le1mu9
.le4ne.
.le1ne
.le6o7
.le7s6a
.le4se.
.l2e1se
.le4sek
.le4se5s2
.le4s5p2
.le2t
.le7ta
.let6tan
.le6tt
.let1ta
.le2u3
.le3va
.le1v
.le4va.
.le4ve.
.le4v4es
.li8a
.l2i4de.
.li1de
.l2i4e4
.li5e1ne
.li1en
.l2i2g
.li1ga3
.l2i2k
.li2k5k6
.li1k3o
.lik3s
.l4i4ma
.l2i2nk6
.li6ra
.l2ir
.li4re.
.li1re
.li4sa
.li4se.
.li1se
.li4ta.
.li1ta
.li4te.
.li1te
.li5t2i
.li4ve.
.li1ve
.li4v5en
.liv8s7u6
.li8v2s3
.liv4s5v2
.l6o4e
.lo6ge.
.l2og
.lo5ge
.lo8gi.
.lo1gi
.lo6g5r4
.lo4i
.lo6na
.l2on
.l2o8o
.lo6ri
.lo8sa.
.lo7sa
.l2os
.lo4se.
.lo1se
.lo6te
.l2ot
.lo4va
.lo3ve
.lo4ve.
.l2u4d
.lu2e
.lu4na
.lu6pa
.lu4pe.
.lu1pe
.lu6ra.
.lu1ra
.lu4re.
.lu1r2e
.lu6se.
.l2u1se
.lu4ta.
.luta3
.lu9ta1s4
.ly4de.
.ly1de
.ly8d3s
.l4y8e
.ly8ge
.ly5s6e
.ly4se.
.l2ys
.ly4sk4
.ly4s5s6
.ly8st3r4
.lys2t
.ly4te.
.ly1te
.ly4ve.
.ly3ve
.ly1v
.lø8de.
.lø1de
.lø6en.
.l4ø1e2
.lø8ne.
.lø1ne
.lø6pa.
.lø1pa
.lø4pe.
.lø1pe
.løv5i
.løy7e6ne
.lø4y1e
.løye4n
.lå8ma
.l6åm
.lå5re
.lå6re.
.lå4te.
.lå1te
.m8
.ma4ge.
.ma1ge
.ma2g9r4
.ma3ka
.ma4ke.
.m2a1ke
.mak6t5at
.ma6k1t
.mak4t5s
.ma4le.
.m2a1le
.ma4li.
.ma1li
.m6a4na.
.ma1na
.ma4ne.
.ma3ne
.ma4ni.
.ma1ni
.ma4ra.
.ma1ra
.ma4re.
.ma1re
.ma4ri.
.m2a1r4i
.ma4sa.
.m4a1sa
.ma4si
.ma6st
.m4a8ta1
.m6at
.ma6t8h
.mat5t8o
.ma6tt
.ma4v
.me4d3
.me6d5ei
.me1de
.me6d4i
.me6d7in
.me6d5r4
.mei5er.
.me2i7e
.mei5et
.me2k5l4
.me6la.
.m2el
.m2e1la
.mel3l6o
.me2l1l
.m2e4l5ø
.me4ne.
.me1ne
.m4e3ri
.me4sk
.me2t6a3
.m4e5ta4l1l
.me3tal
.mes5ti
.me4tri
.m2e1tr4
.mi6kj2
.m2ik
.mi4le.
.mi1le
.mi4me.
.mi1me
.mi4ni.
.mi1ni
.mi4n2ik
.m2i2n4k3
.min5k4e
.mi6s3t4
.m1ne6
.m1n
.mo5d4e
.m2o1d
.mo6er
.m6o1e
.mo4na.
.m2on
.mo1na
.mo8ne.
.mo5ne
.mon4st
.mo6n1s
.mons6t5r4
.mor5d6e
.mo4rd
.mo4re.
.m8o3re
.mor3t6a5
.m2o6r1t
.mo4se.
.m2os
.mo1se
.mo4ta
.m2ot
.mo4tek
.mo1te
.mo4ter
.mo4tr4
.mo5v
.mu8ga
.mu8ge.
.mu1ge
.mu6le.
.mu1le
.mu2l5e6s
.mur7
.mu5r8e
.my8ka
.m6yk
.my4ke.
.my3ke
.mø4re.
.mø1re
.mø5re1s2
.m4ø2t
.må6la.
.m6ål
.må1la
.mål3o
.n8
.nab2o3
.na6ge.
.na3ge
.nak2
.na8ke.
.n2a1ke
.na6n5s
.na4r
.na4sa.
.n4a1sa
.nat2
.na3tr4
.nat6tr4
.na6tt
.ne4de.
.ne1de
.ne4den
.ne4d5i
.ne4d3r4
.ne8d3s4
.ne2i7e
.ne8pa.
.n2e1p
.ne1pa
.ne6pe.
.ne5pe
.ne2s
.ne4t
.ni8ar
.ni6er
.n2ie
.nig4l
.n2ig
.ni4na.
.n6i1na
.ni5o6g5
.n4io
.ni4pa.
.ni1pa
.ni6pe.
.ni1pe
.ni8sa
.ni6se.
.ni1se
.ni1ta9
.n4it
.ni4to
.n4i4va
.no6de.
.n2o1d
.no1d4e
.n6o6e
.no8ka
.no4me.
.n2o1me
.no8mi.
.no1mi
.no4r
.no8se
.n2os
.no8si.
.no1si
.no4va.
.no1v
.nu4e
.ny5a
.ny8sa.
.n2y1s
.ny1sa
.ny6se.
.ny5s4e
.ny4te.
.ny3te
.næ8r9a6st.
.næ1
.næ4re.
.næ1re
.nø6d4d
.nø4re.
.nø1re
.nå4de.
.n6å1d
.nå1de
.nå6le.
.nå1le
.o6
.ob6o5e
.o1b4o
.od4da.
.o1d
.o6d1d
.od1da
.od4de.
.od1de
.od8la.
.o4d1l4
.od3la
.od8le.
.od1le
.off1si6
.o2ff
.of2f1s
.of4te.
.o2ft
.of1te
.of5t2e1b2
.og4
.o2k1k
.ok8ka.
.ok1ka
.ok8ke
.ok4se.
.ok1s4e
.ol8d3s
.o6ld
.ol4ga.
.o2lg
.ol1ga
.o1li5
.ol8la
.o2l1l
.ol8le.
.ol1le
.o2l6m
.om1
.om4ar.
.o1ma
.om6bo.
.o2m1b6
.omb2o
.om4e6n
.o1me
.om4gå.
.o2m5g6
.om1gå
.om4me
.o2m1m4
.o6m5s2
.on4de.
.o6nd
.on1de
.on5de1s
.on8d3s
.on6kl4
.o2n1k
.on6na
.o4nn
.on8ne.
.on1ne
.op5ar
.o1pa
.op4pa
.o2pp
.op4pe
.opp7el
.op4pi
.opp3l6
.oppla8te.
.opp2lat
.oppla1te
.op2p5s6
.o1p7r6
.o2p1t6
.or4da
.o4rd
.or4de.
.or1de
.or4del
.or4dr4
.or8d5s4
.or5g4a
.o2r1g
.or6ka.
.or5ka
.o2r1k
.or4ke.
.or1ke
.or6me.
.o2r1m
.or1me
.or4re.
.o2r1r
.orr6e
.o6r1t6
.or6v4a
.o2r1v
.o1sa5
.os4k
.o4s4s
.o8s4te.
.os1te
.o1t5r4
.ot7t8a
.o6tt
.ot4ta.
.ot4te.
.ot1te
.ot4t4es
.ot3to
.ot4to.
.ove6
.ove2r3
.overe6n6d
.ove1re6
.ove1r3i4
.ove2r5k8
.ov8re
.o2vr8
.o1v5u
.p6
.p8a8la.
.pa1la
.pa4le.
.p2a3le
.pa5n6e
.pa6n7e1u
.pa5n6i
.pa6re.
.pa1re
.pe3do
.pe4ke.
.pe1ke
.pe6k5t
.pel4sj
.p6el
.pe8l1s
.pe4ne.
.pe1ne
.p2e2p
.pep3r6
.pe8sa.
.pe1s
.pe1sa
.pese5t
.p2e3se
.pe4st
.p1h2
.pi6le.
.pi1le
.pi6p
.pi9ra
.p2ir
.pi2s
.ple6n5g6
.pl6
.p1le
.plu4s6s5
.plus7s6e
.plø7se
.p1lø
.po4et
.p6o1e
.p4o3la
.po8la.
.po4le.
.p2o3le
.po4l2og
.p2o1lo
.po2p1
.po2p2e
.po2p3p
.po3p2u
.po8ra
.po6re.
.p8o1r4e
.po4re1s2
.po6st
.p2os
.po8ta.
.po1ta
.p2ot
.po6tek
.po3te
.ps2
.pøn3
.pø6n9s6
.p6å5d6
.r6
.ra6da.
.r6a3d2a
.ra6et
.r2a1e2
.ra4ga.
.r4a1ga
.ra4ge.
.ra1ge
.ra4ja.
.ra1j
.ra4ka
.raks7
.ra2m
.ra4na
.ra4ne.
.r4a1ne
.ran7s6a
.ra6ns
.ra6pa.
.ra1pa
.ra6pe.
.ra1pe
.ra2p4s1
.rap5s4e
.rap5s4o4
.ra4re.
.ra1re
.ra6sa.
.r4a1sa
.r4a7ti
.ra6va.
.ra1va
.ra4ve.
.r2a1ve
.re4al
.r2ea
.re6da.
.re1da
.re4de.
.re1de
.ree6l
.re3e2
.re5ge4l
.re3ge
.re4gi.
.re1gi
.rei2
.rei7de
.rei5er.
.re2i7e
.re4i6n1s
.re6ka.
.r4e1ka
.re4ke.
.re1ke
.re8klar
.re1kl4
.re8k1le
.re4k1li
.r4e6k1n2
.re8le.
.r2e1le
.re4ma.
.re1ma
.re4na.
.r2e3na
.re4ne.
.re1ne
.ren6sk
.re6ns
.re6n4t
.re2p5s
.r2e1p
.re4s9s
.re5s6tan
.rett7s8kri
.re6tt
.ret6ts
.rettskr6
.re4ve.
.re1v
.re4v4e5s
.re2v9n
.ri2d
.r2i8e
.rie5ne
.ri1e4n
.ri6ka.
.r2ik
.ri1k2a
.r4i6ma
.ri4me.
.ri1me
.ri4pa.
.ri1pa
.ri4pe.
.ri1pe
.ri4p5o
.ri4sa.
.ri1sa
.ri4se.
.ri1se
.ris5ko
.ri4s3ø
.ri4ta.
.ri1ta
.ri6te.
.ri3te
.ri6va.
.r4i1va
.ri4ve.
.ri1ve
.ro4a6n
.r2oa
.ro4de.
.r2o1d
.ro1d4e
.ro8di
.r6o4e
.ro8ke.
.ro1ke
.ro4ma.
.r2o1ma
.ro4mi
.r2o6pa
.ro9par
.ro3pe
.ro4pe.
.ro4sa
.r2os
.ro9sar
.ro4se.
.ro1se
.ro4sed
.ro4ta.
.r2ot
.ro1ta
.ro4te.
.ro1te
.ro4tek4
.ro8va
.ro3ve
.ro8ve.
.ru8ga.
.ru1ga
.ru4ge.
.ru1ge
.rug9l6e
.ru4gl
.ru8k2a
.ru4na.
.ru1na
.ru4ne.
.ru1ne
.ru2ne1s6
.ru6n7g
.ru4se.
.r4us
.r2u1se
.ru4sk
.ru8va.
.ru5va
.ru1v
.ru4ve.
.r4y2e
.ry7e7ne
.rye2n
.ry6ke.
.r6yk
.ry2ke
.rød2
.rø8d7s4
.rø8le.
.rø1le
.rø4re.
.rø1re
.rø1v
.rø8va.
.rø1va
.rø4ve.
.rø4y7e
.rå8da.
.r6å1d
.rå1da
.rå4de.
.rå1de
.rå4e2n
.r4å1e
.rå8er.
.rå3er
.r2å5g8
.rå6ka.
.rå1ka
.rå4ke.
.rå1ke
.rå3k4l4
.rå6na.
.rå1na
.rå8sa.
.rå1s2
.rå1sa
.s6
.sa4ge.
.sa1ge
.sag6n
.s8a4la.
.sa1la
.sa4le.
.s2a3le
.sa4me.
.s2am
.sa1me
.sa4mer
.sa6n6d7
.san7d8e
.san7d8i
.san8s7ku
.sa6n1s
.san7s6k
.s4a6u6e
.sch6
.s2e2b2
.se3e2
.se4i
.se4k5l4
.sek4s5o6g5
.sek1so
.se8l4v5
.s2el
.sel4v5i6
.se6na.
.s2e1na
.se4ne.
.s2e1ne
.s2e6n7s
.se4ra.
.s2e3ra
.ser7vel
.se2r1v
.se4te.
.s2e1te
.sha9ke.
.s1h
.sha2k
.sh2a
.sh2a1ke
.si2d
.si6er
.s2ie
.si6ga.
.s2ig
.si1ga
.si4ge.
.si1ge
.sik5k6e
.s2ik
.si2kk
.si8la.
.s2il
.si1la
.si4le.
.si1le
.si4ne.
.s4i1ne
.sin6n6s5
.si4nn
.si4ra.
.s2ir
.si1ra
.si5str4
.s2is
.s4i4va
.si4ve.
.si1ve
.sje4i
.sj2e
.sju5o6g5
.sk4
.ska7ka
.sk2a5ke
.skat4
.sk3ei5d
.s1ke
.s6kei
.ski6n
.sk8in6n1s6
.s2ki4nn
.sko7de.
.sk2o1d
.sko1d4e
.sko4g5u
.s2k2og
.skrit6t9s8
.skr6
.sk2rit
.skri6tt
.skud8d5s6
.s2k2ud
.sku6d1d
.skudds1å7
.sku6m5
.sky6f7la
.s4ky1f
.skyf2l2
.skå5ra
.slim5
.sl4
.s1li
.slot6ts5
.slo6tt4
.sl2ot
.slu9k2a
.slu5ke.
.slu1ke
.slø5se
.s1lø
.sma4s
.s1m
.s1ma
.smas5k
.sm2o9g
.s1mo
.s1må3
.små7k6
.sm6å9l
.små5t4
.s8må6t5t
.s1n4
.s1ne3
.snit6t5s6
.s2n4it
.s1ni
.sni6tt
.snø3k
.s1nø
.s2oa8
.so8d4e
.s2o1d
.som1ma4
.s2o2m5m4
.so1n6a
.s2on
.s4op4pa
.s1o2p
.so2pp
.s8o6r4t5
.so4ta.
.s2ot
.so1ta
.s8p6
.spa5ra
.s1pa
.s4pe6e6
.s1pe
.spi9la
.s3pri5s6
.spr6
.st6
.sta9ka
.s6ta5li
.st2a4t4s1
.ste8d4s
.s1te
.s2ted
.s2t4e4i
.ste6i1n7a8
.ste6i6n7s8
.s4te2m4m4
.s2tem
.stev9na
.ste2v1n
.s2te1v
.sti7me
.s1ti
.sto5ne
.s1t2on
.sto6ra
.strek5s6
.str4
.s4tu8a
.stu9va
.stu1v
.stå2l5l6
.s4un8d5s6
.su6nd
.su4ri
.su5te
.s1u4t1
.sv8
.sva5la
.sva5ra.
.sva1ra
.sva5re
.sv2a4r5s
.sv2e2r8n
.syd5
.sy5d6e
.sy6na.
.s2yn
.sy1na
.sy4ne.
.sy1ne
.sy5ter
.sy2t
.sy1te
.sy3ti
.sy6v5
.sy6v5o6g5
.s4z6
.sæ2
.sær1
.sæ2r3e4g
.s3æ4re
.s6ø8ka
.sø4ke.
.s6ø1ke
.sø8l6v5
.s4øl
.sø1r3a
.sø4y1e5
.søy6e4n
.så5pen
.så2p
.så1p2e
.så3re
.så2r
.t8
.ta8ke.
.t2a1ke
.tak9r6
.tak5s1k
.tak9s8p2
.t8a4la.
.ta4le.
.t2a1le
.ta4le7s6
.tal4li
.ta2l1l
.ta2m5m4
.tan5de
.ta6nd
.ta4p5r6
.ta4ra.
.t6a1ra
.ta4re.
.ta1re
.ta2s
.te4e4
.te6i
.tei9er
.t3e2i7e
.te8ke.
.te1ke
.te4ma.
.te1ma
.te6o6
.te8ne.
.te1ne
.te4se.
.t4es
.t2e1se
.te7si
.te4sta
.te4str4
.t1h2
.ti8a
.tid5r4
.ti8d6s3
.t2i4de.
.ti1de
.t2i4e2
.ti4g3r4
.t2ig
.ti5ki
.t2ik
.t4i4l5
.ti1li6
.ti4med
.ti1me
.ti4na.
.t6i1na
.ti4ne.
.t4i1ne
.ti4p5l6
.ti4s
.to5a2r5m
.t2oa
.to6en.
.t6o1e
.toe2n
.to4er.
.to6et.
.toe4t
.to2kk8
.t6ok
.to6le.
.t2o1le
.to6na.
.t2on
.to5na
.to5o6g5
.t2o3o
.to4ra.
.to1ra
.to4re.
.t8o1re
.to6r2e1b2
.t4o3ro
.tor2s1k6e5
.t2o4rs
.tor4s5v2
.to5r4y1e
.t4ory
.to4str4
.t2o1s
.t2o5t8
.tot8a9la.
.to1ta
.tota4l5a
.to8te.
.to5te
.to6va
.to4ve.
.tr6
.tr8a7c
.t2r2e3b2
.tr2e7p
.tre5o6g5
.tre3o6
.tre6skja
.t2resk
.tre1skj2
.tre5s8ko
.tre3s2p2
.tre5s6t
.ts2
.t1sj2
.tu4en.
.tu1e2n
.tu6na
.tu4ne.
.tu1ne
.tu6ra.
.t2ur
.tu1ra
.tu4re.
.tu1re
.tu8ve.
.tu1v
.t1ve6n
.t1v
.tver6r5a6
.t7ver
.tv2e2r1r
.tv2å7g
.ty6da.
.ty1da
.ty4de.
.ty1de
.ty8e2t
.t4y1e
.ty8re.
.ty4r8s9
.ty5ter
.ty2t
.ty1te
.ty5ti
.tæ4re.
.tæ1re
.tø6v9d
.tø6ve.
.tø9ver
.tø5v4et
.tå5ker
.tå2k
.tå1ke
.tå4le.
.tå1le
.t1å2p9n4
.tå5ren
.t3å7ret
.u5a6
.u1b2
.u6b2e2r1g
.u1be
.ub4er
.u3d2
.u7de
.u3e6
.u4er.
.u1er
.u6e1ra
.u4e1re
.u2f4f4
.uf8sa
.u2f1s
.u4ga
.u5gi
.u6g6la.
.u4gl
.ug1l2a
.ug8n
.u1i
.u1i6m
.u1k6
.u4ka.
.u1k2a
.u6ke
.u7k1n2
.u3le
.u4le.
.u2le8k
.ule6n4d
.u6len1de
.u8le1ne
.u6let
.u4lin
.u3l4i
.ul6ka
.u6l1k
.ul4ke.
.ul1k4e
.ul4la.
.u2l1l
.ul8le.
.ul1le
.ul8ma.
.u2l1m
.ul1ma
.ul4me.
.ul1me
.ul4ne.
.u2l1n
.ul3ne
.u2l5t
.ul6te
.u1lu
.ul4v5i
.u8lv
.u1ly
.u3lå
.u5ma
.u1mu7
.u2n
.unde6ri
.u6nd
.un1de
.und2er
.und5r4
.u1ne8
.u5nek
.u9net
.un4ge.
.u6ng
.un1ge
.u4n2n
.un6n3s
.u3no
.un4se.
.u6ns
.un1se
.u6n4t
.u5nu6
.u1o
.u1p
.u4pi
.up2l6
.u2ra
.u5raf
.ur6an
.u6r4a1ne
.u4r4d4
.ure4n
.u1re
.u4ret.
.u6re4ts1
.u2r2i
.ur4ke
.u2r1k
.ur6na
.u2rn
.ur4ne.
.ur1ne
.u1ro
.u4r1s
.ur8ta
.u6r1t
.ur4te.
.ur1te
.ur2te5m8
.ur6tet
.u5rut
.u3ry
.u5rå
.u1s2
.u4sa.
.u4sas
.u5se
.u3ska
.u5s1n
.u7s1p2
.us3se
.u4s1s
.us6t4
.u7stek
.u2s1te
.u5stel
.usy5r
.u1sy
.u2t
.u2ta4g
.u1t5a1ge
.u3tak
.u4ten.
.ut2en
.u1te
.u4t5e1sk
.u2t4e1s6
.ut6e7sko
.u4ti.
.u3ti
.ut3ka6n6t5
.u4t3k2
.ut4ne.
.u8t1n
.ut1ne
.u5tol
.u2t3o6v
.ut6rer
.ut1r4
.ut3re
.ut6ro1v
.u3t4rø
.ut6se.
.u4t1s4
.ut1se
.ut4si.
.ut1si
.u6t3t4
.u3tu
.v6
.va4da.
.v6a1da
.va4de.
.va1de
.vai4
.va4ke.
.v2a1ke
.va6le1s2
.v2a1le
.val8g5s1
.va2lg
.va4ne.
.v4a1ne
.v4a4n4n5
.van6n5s4
.va6re1ta
.va1re
.var4et
.var2i4e
.v2a1ri
.var5sk
.v2a4rs
.ve8en.
.ve3e2
.vee4n
.ve4ga.
.v2e1ga
.ve6ge.
.ve5ge
.ve4g3i
.ve8g3s4
.ve2i3g4
.v2ei3s
.vei4ta
.ve4l3
.ve5l4ar
.v2e1la
.ve6l5a6r1t
.ve6l5d
.ve4l3e
.ve2l5l
.ve5l4os
.v2e1lo
.ve4l5ov
.v4e4ly
.v2e8læ1
.ve4ne.
.ve1ne
.ve5net
.ve4ra.
.v2e1ra
.ve4ras
.ve6re.
.ve1re
.ver1mo9
.ve2r5m6
.ver1s4t
.ve4rs
.ver5s1te
.ve4sl4
.v4es
.ve4st
.v2e1te5
.v4et
.ve8te.
.ve4ve.
.ve1v
.vi4da.
.v2i1da
.vi5de4
.v2i6de.
.vi4d7å4
.v2i2e
.vi4ka.
.v2ik
.vi1k2a
.vil5l6a5l
.vi2l1l
.vil5l6a5t
.vi6ma.
.v4i1ma
.vi8me.
.vi1me
.vin8g7s1
.v6i6ng
.vi4n5n
.vi4sa.
.vi1sa
.vi4se.
.vi1se
.vi6s2e1ri
.vi4ta.
.vi1ta
.vi4te.
.vi1te
.vi5tr4
.vok6s5
.vok5s6e
.vo6r
.vo6ta.
.v2ot
.vo1ta
.vy4
.vy7e6ne
.v4y1e
.vye2n
.væ4ra
.væ4re.
.væ1re
.v6å4d
.vå4r3
.vå4r2s6
.wa4r
.w2a
.w2i6e
.w4i2
.xe2
.y6
.y1e4
.yn8da.
.y6nd
.yn1da
.yn4de.
.yn1de
.yn6ge.
.y6ng
.yn1ge
.yn8ka.
.y2nk
.yn1ka
.yn6ke.
.yn1ke
.yn6k5v2
.y6ns2
.yp8pa.
.yp7p6a
.y2pp
.yp4pe.
.yp3pe
.yr8ja.
.yr1j
.yr8je.
.yrj2e
.yr8ka
.y2r1k
.yr4ke.
.yr1ke
.yrke4s5
.y6r6t
.ys4
.yster2ie8
.ys2t
.y4s3ter
.ys1te
.yste1ri
.yt9ren
.ytr4
.y6t5t
.yt6te.
.yt1t4e
.y1v6
.z4
.zj8
.æ8
.æ2re4s5a
.æ1re
.ære4st
.æt4te.
.æ6tt
.æt1te
.ø6
.øg8l2a
.øg1
.øg4le.
.øg1l6e
.ø4i5
.ø2i6e
.øko5
.øk6ta
.ø6k1t
.øk4te.
.øk1te
.øl3ed
.ø1le
.ø8l3s6
.ø8l3v6
.øm4me.
.ø2m1m4
.øm1me
.ø4res8t
.ø1re
.øre1s2
.ør4j2e
.ør1j
.ø2r5k
.ør8na.
.ø2rn
.ør3na
.ør4ne.
.ør1n4e
.ør6ski
.ø4rs
.ør9s1m
.ør3s2t
.ør8ta
.ø6r1t
.ørt9an
.ør8te.
.ør1te
.øs2
.øs4t
.ø6v4d
.øve4r5
.øve4r6s
.øv4re.
.ø2v1r8
.øv1re
.øy6de.
.øy1de
.ø4y2e
.øyele8ge.
.øy4e1le
.øye3le1ge
.øy7e6ne
.øye4n
.øy4n
.øy6na
.øy6ra
.øy4re
.øy8rer
.øy4st3r4
.ø2y1s
.øys2t
.å2
.å6e
.å6f
.å6g
.å6k4
.å8l
.å2l6t
.ål8ut.
.ål1u
.å8m
.ån8da.
.å6nd
.ån1da
.ån4de.
.ån1de
.ån8d6s5
.åp6na
.å2p1n4
.års3k
.å4r2s
.å6se
.ås2
.ås3k
.å1s7l4
.ås3m
.å3s1te
.å1st
.ås5v2
.å4t
.åt4te.
.å6t1t
.åt1te
.åtte5o6g5
.åt2te3o6
.å6v
a1ad
4aaf
a3a2ft
aa4g
a1aks
a2ak5v2
aa3la
aa2m
a1a2n
a6an.
aan1s9t6
aa6ns
a7antr4
aa6n1t
a1ap
a2a4ri6
aarie9ne
aari1e4n
aar2ie
a2a2r5n
aa2s
3aa1se
aa5t6h
a1av
a6bab
a1b4a
a6b7av
ab9b8l2
a2b1b
ab4but
ab1b2u
abe4lei
a1be
abe1le
abe2l5t4
abe9na.
ab2e4na
abe1s9ka
ab4e1s2
4abe1v
a5bh
ab2ie6
a1b4i
abi9er
abi9la
a4bi1st
ab1l2
ab9la1ra
ab1lar
a1b4lok
ab6l7u
a5b2o9a
ab2o
abo3b
ab6o3e
a3bo2er
ab4o3kl4
a3bo4rd
5abor1te
ab2o6r1t
ab1r8
ab8re
ab2sl4
a6b1s
abu5e
a1b2u
a4bu1el
a4buf
a6busk
a4bu1te
a4by.
a1by
a4by1b
a4b6yk
aby3r
8ac
a1c4a5
a6ca.
ach2e3a
a2c6k3
a1co
6a1da
a5dal
ad5a6n1t
a4da6r1t
ad9da
a6d1d
a2d2e7b2
a1de
ad2e5i6s
a2dei
a2dek
a4del
a4d5e8lv
a5de6nd
a3de1ne
a2de3o6
a2d2e1p
a5der.
ad2er
ader1le7
ade2r1l
a2de1s
a5de4ser
a2d2e1se
a3desl4
a3d2et.
a3de4ts1
3adfer
a2d1f
ad1fe
1a2d1g4
ad8ge
adi4e1ne
a1di
ad2ie
adi1en
ad1j
1adju
2a4d1l4
1a2d1m
a8d5n
ad2o7a
a1do
a4do2b3
ad7o2pp
a2dop
4ador
a7d6o1ra
ad5raf
adr4
ad7ran
a2dre
a4d7rel
ad5r2e1p
ad3ret
ad3rid
ad1ro
a7drø
ad4s5a2m1t
a8ds
ad1s2am
ad4s1i
adsle6ge.
ads1l4
ads1le
ads3le1ge
ad5s4let
ads5te
adst4
ad5s6tek
ad3str4
ad5un
a1du
1a2d1v
adva5re
2adve
a4dy.
a1dy
a4d5øy
a1dø
2a1e2
4a2ea
a4ed
ae3de
ae1d7r4
ae6k5t
a4el.
a2e5la
a2e3li
a2e1l5o
ae6n5t
a3e1p
aes8ke.
ae2s1ke
aes4t
6a1fa
6afc
a4fe.
a1fe
afei5
af4fan
a2ff
af1fa
af4fei
af1fe
af2fe3s
af7fi.
af1fi
af2fi3d
af5f4u
a1f4i
afia1
afi5a4n
a2fi1b2
a6f5i4nn
afi7re
af2i2r
a4fi1t2i
a5fj2o
a1fj
af9la.
af2l2
af9lar
af3le
af5li
1afri
a4fr2ik
afr2
6a1fo
afo7ra.
afor1a
af8o7r8e
afore5ne
afor7e6n
afra5s
a1fra
af2s1l4
a2f1s
af2t5ei
a2ft
af1te
af4t5e4l
af6t5o2
af4t1s4
af5yr2
a1fy
af8ø4r
a1fø
afø5ri
4a1ga
a2gaa
a5g2a1e2
a4ga4n1f
agan8g7s8
aga6ng
a4ga6r1t
a5g6as.
aga6ve.
ag2a1ve
ag5de
a8g1d
a7g4elen
a1ge
age1le
a2gem
3a4ge6n1t
ag2en
ag2e4r3a
ag2er
a4ge6rek
age1re
age5risk
age4ris
age1ri
a7ge4rs
a5g2eru
ag6e5s2
a4ge1ta
a4gé
ag5gar
a4g1g
ag1ga
ag4gas
ag5ge
ag8g1s2
ag1g7u
ag6gut
a4gi.
a1gi
a2g5id
agi6s
ag1l2a
ag4lem
ag1l6e
ag6le1sa
ag5lå
ag3m6o8e
a2g1m
ag1mo
ag2n5om
ag1n
ag1no
4a1go
ag2o5d8
ag1or
a5g2os
a3g2ot
a4g5ov
ag7ras
a1gr4
ag1re
ag1ri
4a3gru
ag1rå
ag4s3a2m1b
a8gs1
agsa2
ag1s2am3
ags4a6ng
ag3s4ei
ag1se
ag4s2el
ag3sem
ag5s6i6ng
ag2si
ag6sju
ag1sj
ag4ska
ags4kul
ag2sl4
agsmå6la
ags1m
ags1må
agsm6ål
ags3tr4
ag2s3t2v
ag2sy
a6gu.
a1gu
agu3ay
agå8v4a
a1gå
agå1v
agå8ve.
a1h
a5hi
a2h4n5
ah4v4
ai1a4
a2i3e2
ai3er.
ai4is
ai1i
ai5ke.
a2ik
ai1ke
ai5ko
ai9k1v2
a4i5ne
a3i6ng
a1i4nn
a2i4n5o4
ai1ro
a2ir
ai1s4e
ai4s3k
ai2s4k2h
ai8s1m
ais6om
ai4s5s
ai5s4v2
ai5ve
ai5ø
a1j
aja9d
a7j2e
a8je.
ajes7
a4jé
a4ji
a6j1l
6ak.
a1ka
4a1ka.
1a2kad
6akaf
ak3aks
6akan
aka4o5
4akar
a1k6a2r1b8
a4ka4t5r4
ak4au
2a2k1d
2a1ke
a9k2ec
a2ke5h
a3kei5
a6kek
ake1l8e
akel5ei9er
akele2i7e
ak2e5l4i
a4ke4rek
ake1re
a4k2e1rø
ake5s1m
ake1s6p2
a8k2e3te
akhe6n
a4k1h
4a1ki
a6kid
a6k2ik
a6k7i6nd
akis1
2a1kj2
akk6a6nd
a2kk
ak1ka
ak5ke.
ak1ke
ak3ken
akk5er1s2t
akke4rs
ak4kes
ak1ki4
ak5kim
ak4k5is
ak6kj2
ak1ko
akk3ol
ak6k5ri
akkr6
ak1ku
ak4kul
ak4k5v2
2ak1l4
ak3le1v
ak1le
ak6l2ik
ak1li
ak6lus
6a2k1n2
ak5ne
a5k1no
2a1ko
ak5om.
a1kom
a5k6on
a7k2os
ak1o2v
ak4pe6
a6k1p
ak4r4a8sa
akr6
a4k5reg
ak1re1gi4
ak3res
ak3ro.
ak2ro
ak3r6o1e
ak1ru
ak4s5a6nd
ak1sa
ak4sek
ak1se
ak2s2e3l4o
aks2el
ak4s5e8lv
ak4ses
ak7s6id
ak1si
3aksj2e
ak1sj
ak2s1k
ak7sk8u
ak6s1l4
4ak1so
4ak8s9r
aks5ti
ak5stol
aks4tr4
ak6st7ren
4ak1su
ak4tab
a6k1t
ak4tai
ak4tak
akta6le.
ak3t2a1le
akt5a2l1l
ak6tam
ak6ta6ns
ak4tap
ak4tas
ak4tav
ak4teg
ak1te
ak4tek
ak5t8e1me
ak2tem8
4ak2t1h
7akt2ig
ak1ti
ak5tit
aktle6ge.
ak6t3l6
akt1le
akt3le1ge
ak2tr4
ak6t1re
akt3rå
akt5s4la
ak4ts
akts1l4
5aktue
akut2
2akv2
ak3val
ak5øl
a5kå
8a1la.
a2l7adr4
ala4g
al4a9ga
a5la2g1m
alag8ra
ala2g1r4
a5la1h
a1lai
al3a2l1l
a2lal
al3a4me
al3a1na
a3la6nd
a5lan3de.
alan1de
a5l6a3ne
alan5gr4
ala6ng
a4l3a4n1l
a4l3a6n1v
a2la4o
ala5pr6
a5l8ar.
a3la1ra
a5laren
ala1re
al3a2r1k
a7la2r1l
3a4l2a2r1m
a6l7a2r1r
a5l2a4rs
a4l3a6r1t
a7la2r1v
al3a4si
2alat
ala7tr4
4alau
al5auk
a2l1av
ala4va
al4ba.
a2l1b2
al1b4a
alb2o4g
alb2o
albu7er.
al1b2u
albu1er
5album
al3de
a6ld
al7d2er
4al1do
ald3re
aldr4
2a1le
a2le1f
a2leg
a9leg.
alei1e6n
ale2i7e
a9l8e2ik
a5le1k6e
a4leks
a4l2e1li
a2lem
a2l5e4mu
al8en.
a4l3e4n1h2
a4l5en5tr4
ale6n1t
ale4p2os
a2l2e1p
a4le1po
a7le2p1t
a4lered
al8e1re
alere6de.
alere1de
a4l2e5ro
a4l2e1ru
ale1s2
al2e7se
a4lesk
ale6s5kr6
a4lesl4
a6le1su
a4le1ta
a4l2e3te
a2le1u
ale5v
a4leva
a4levi
3alfab
a4l1f
al1fa
2alg.
a2lg
al3g2e1b2
al1ge
al2gu
al4går
al1gå
al3i2l1l
a1li
a2lim
a9l2in.
ali5na.
al6i1na
a4l3i6nd
a4l3i6n1s
a4l3i6n1t
al9ja.
a2lj
al3je.
alj2e
alj5e6n1d
al1jen
al3jer
al1j2o
al4jor
al2j1u
al1j2ø3
al4j5ø6v
al9k2e1ra
a6l1k
al1k4e
alk7s6
all4a4ga
a2l1l
alla6ge.
alla1ge
al4lap
al4l5a6r1t
al5l2e5a
al1le
alle6ge.
al3le1ge
al4lek
al5len.
all5er1s2t
alle4rs
all6e3s4
al4lest
al5let
3alli1a
al1li
al4lid
5all2i5e
all4i9ne
al6li6st.
al4lo3m1
all4sen
al8l1s
all1se
all4s1ti
al6lul
al6løs.
al1lø
al4lø1se
al8løst
al6lå
al6mek
a2l1m
al1me
al4met
4a2l1n
a5lo.
a2l1o4b
a5loi
al1om
a1l2on
al3o1pe
a2lop
a2l7o4rd
alo1ri5
al6o6rit
al3ove
a1lov
alow7
a2l1p2
al3ps
2a8l1s
al7sed
al1se
al9skap
al1ska
al7ska1re
al2sk2ar
al2s1l4
als5lø
al2s1n
al6s1pu
alsp2
als4te
als6ter
6al2su
alt6ak
a2l1t
alta8le.
al3tal
al3t2a1le
al3ted
al1te
al7te1ma
al2tem
5alter3na
alt2e2rn
alte4t
al4t5e1ta
al4t5e3te
al4t3op
a4l5u4k
alul8la
alu2l1l
al5u6nd
alu8re.
alu1r2e
al5va.
a8lv
al1va
alv5aks
alva6k
alvak8se.
alvak1se
al4ve2d1
al5v6er
al8v9e2r8m6
alv4e5s
al9v2es.
alvi8se
al1vi
al1vo
al8v3s2
6a1ly
a2l5y4te
alø5se
a1lø
al3øv
a1lå
al7å6t
a4ma1h
a1ma
ama5is
a2mak
a5m4a1ki
am3aks
a2mal
am5a6ld
a3man
a3mar.
a5ma1ra
a7m6as.
ama1so7
3a4ma1tø
am6at
am4bar
a2m1b
am1b4a
3ambas
am4bat
am5be
a2mei
a1me
am3e2i7e
a3m2eis
a2mek
a4m3eks
amen8de.
ame6nd
amen1de
ame6n4s3
amen6t7a2r1v
ame6n1t
ame4ram
am2e1ra
a4mere6t
ame1re
3a4m4e1ri
4a5merin
a2me5u4
amhu7
a2m1h
2a1mi
am6i7na
am4i7ne
ami2sk3
amis4ku
am4l2e1se
a4m3l
am1le
am6lest
am4mad
a2m1m4
am1ma
6amn.
a2m1n
am4ned
am1ne
a2mo
a3m6o5e
a2m1op
am1or
amo6ve
am4pap
a8m1p
am1pa
am4pa1re
amp5ei
am1pe
am8peria
am3pe1ri
am6per2ie
am2pe1s6
amp7i4nn
am2p3l6
am5p4let
amp1le
am4p4re
ampr6
am4pun
am1pu
am2på
amru4
a2m1r6
am5rå
a6m1s
am7s6k8u
ams4l4
amst6
am4s3tr4
am2s9u4t1
ams2v2
am4s5ve
am2sø
am3ti
a2m1t
am9t2i5da
am3tid
5am2t1m
am7t1v
am6ul
a1mu
am5yr
a1my
a2m5øy
a1mø
6ana.
a1na
anak8te.
a2nak
a8n1a6k1t
anak1te
ana3la
ana4l4f
3a2n6a1ly
8anan
a3n6a1ne
a5na6ng
ana6n1t8
4anar
a6n1a2r1b8
a2n5a6r1t
ana3to
6an7au
anaus7
an9av.
3anb4e1f
a4n1b2
an1be
4and.
a6nd
an4da5m6
an1da
and4a5ta5
an4d6ek
an1de
an5den
ander1le7
and2er
ande2r1l
an9det
6an1do
an4d2os
4an8ds
and4s2el
and1se
a6nd8se6nd
and7slet
ands1l4
ands1le
and5s6tre
andst4
andstr4
a2ned
a1ne
an5e4g1g
a2neg
a4n5e8lv
a2nem
2a3nen
2aner
an2e3ru
a5n2es.
a4nest
a2ne5sv2
an5e6ti1k2a
a4n2e3ti
anet2ik
a5neti5ke
an5e6ti2kk
an5e4tis
a2ne1v
3an5fal
a4n1f
an1fa
anfø5re.
an1fø
anf8ør
anfø1re
an1g4e
a6ng
an4ged4
an4g5e4n1h2
an3g2en
ang5er3me
an3g2er
ange2r1m
an8ges1te
an2g6es
an2ge1st
an4gi.
an1gi
5angiv
an2g5of
an1go
an5g2os
an4g9ra
an1gr4
an4g1re.
an6gres
an4g3ret
an4g3ry
ang4s1m
an8gs1
angs6tro
angstr4
ang5s8t9rå
ang1st5y
ang5sva
angsv2
ang6søy
ang2sø
ang4t5re
an2g1t
angtr4
ang5t6ve
ang2t1v
an4gun
an1gu
an4gå.
an1gå
ania7
a1ni
a6ni1b2
a4ni1sj
4aniv
an4ka1na
a2nk
an1ka
4an1ki
4an5kj2
an4ko2b3
an3ko
an4kop
an4k2os
an2k1r6
ankr6a8na.
ankra1na
ank3re
an4k3u6t
an1ku
8an2kv2
an4kø
an6k5å6
2anla
a4n1l
anla6nd6
anlø9pa
an1lø
an4n5a6ns
a4nn
ann4an
an1na
an6n8e1me
an1ne
an5n4en
an5ner
an5nid
an1ni
a4n4n1i4nn
an4no2m1
an1no
5ann2on
an4no1v
ann4sei
an6ns
ann1se
ann4sid
ann1si
ann6s7kå
ann6sl4
ann3st6
ann5sta
ann4s3u
an4ny
an2nø
a3no.
a1no
a9noa.
an2oa
ano6d2e7b2
an2o1d
ano1d4e
2an2og
a7n4o2r1m
a6n2ot
a2no4v
ano5va
an3o3ve
anri7ke
a2n1r2
anr2ik
ans5a2ft
a6ns
ansa7ka
4ansan
3an1sat
an4sek
an1se
an7s6e6n1t
anseri8e9ne
anser2i7e6
ans2e1ri
anseri1e4n
an4ses
3an3s2ik
an1si
ans5i6n5d
a6ns5i6n1s
an4ski
an3skj2
an2s4k3l4
an2s6kun
an5skø
an6s1lø
ansl4
an4s1n
ans5or
an4s3pi
ansp2
anspor4t7s6
ans1p6o
ansp2o6r1t
an1st6
6anstar
ans4te4
6ans6ti
2an1su
an9s6u6nd
5ans6un1da
1an1sv2
4an2s3ve
6an1sy
an4så
anta8la
a6n1t
an5t6a6nd
an4ta6ns
an3ted
an1te
ant5e1mi
an2tem
5ante4nn
an4tesl4
ant4es
an4ti7k1l4
an1ti
ant2ik
an4tim
an5toi
an4t5o4rd
ant5rab
antr4
an6t7rom
an3t5ryg
an4t5s6
an4tu4l
antus4t
2a1nu
an4us7a
a2n5ut
3an1ven
a6n1v
6anvin
an5vi
6a1ny
a4nya
a2n1æ2
anær8
2a1nø
a2nøk
an7å
a1o
a2o9a
a2og9
a2oi
ao6k6
aon8de.
a2on
ao6nd
aon1de
ao2p
ao4r
a5p2ea
a1pe
a7pé
a1pi
a2pia
a2p1id
a6p2ik
a6pi6n1s
a2p4io
ap2i6r5
api7r6e
api7se
ap1j
a1pla
apl6
a5p6las
ap3li
ap9lo
ap2ly
4apol
a1po
a4p2on
a4p2oo
apo3p
apo5s4ti
apo2st
ap2os
a2p2ot
3ap3par
a2pp
ap1pa
4ap9par.
6appa1re
app7esk
ap1pe
ap2pe1s
ap5p2las
app1l6
ap4p1le
ap5p2li
ap6pri
app1r6
ap3ra
apr6
ap5ren
ap5ret
ap3rin
a2p2s1
apsa4
ap3s2el
ap1se
apse4s
ap8s9l4
a3p1sy
ap4s5ø
8a1pu
a6p5ut
a1py
ap7ø
a3p8ø8l1s
a5på.
a1ra
ara9b4i
a2r5aks
ara6k5t
ar3a2l1t
a4r2a1mi
2aran
a4ranor
ara1no
a4ra6ns
a4ra6n1v
2arar
a4r5a4s1s
ara5te.
ar6a1te
a6r5aug
a4r7au4k
arau9ken
arau1ke
ar7a2v1h
a2rav
1a2r1b8
2ar1b4a
arba8ne.
arba1ne
6arbe1h
ar1be
4arben
6ar3b4et
2ar1b4i
4arbj
6arbl2
2arb2o
arbo8da
arb2o1d
2arbr8
2ar1b2u
2ar1by
2ar5b4ø
4ar1b4å
ar7d2e7b2
a4rd
ar1de
ar6d2e1li
ardfø5re
ar2d1f
ard1fø
ardf8ør
ard3re
ardr4
ard5sta
ar8ds
ardst4
a1re
1a2r2ea
4are1ar
a4r2e1b2
areba4r
are1b4a
a2red
a2re1f
a2rei
are3in
a2rek
a6r5e6k1t
a4r2e9la
a6r7elek
a2r2e1le
a4r6e1li
a6r7e2m1n
a2rem
a5remo.
are1mo
3a2r2e3na
a6r5e6ng
a4re5ni
aren5t4es
are6n1t
aren1te
a2re3o6
a2r2e1p
a6rerel
a1rer
are1re
a6r5er1fa
are2r1f
a6re2r1k
a4r2ero
a6r2e1rø
a5r2es.
are1s8ka
a2resk
ar4et
a7r4eta.
are1ta
a4re1v
ar7e6va
ar3e2v1n
arev6ne.
arev1ne
8a1ré
6a2r1f
ar7g6h
a2r1g
ar5g6i
6ar1gj
arg4l
ar7go
arg5stj
ar8gs1
2a1ri
aria7ne
ari1an
a3ri1b2
ari1b4a9
a2r5idr4
a4rim
a4r3i4nn
a2r3i6n1s
ar4ins9k
a2r3i6n1t
ari3se
ari2s2e4a
a4ri4sto
a2riv
ar4kau
a2r1k
ar3ke
ar4ke5s
ar5kh2a
ar4k1h
3arkit
ar3ki
3ark6iv
ar4k1le
arkl4
ar4k5løf
ark2lø
ar6k2o1d
ark6s1te
ar8ks
ark1st
ark4s1tr4
ark7veg
arkv2
ar4k3øy
arl4a4ga
a2r1l
ar4map
a2r1m
ar1ma
5arm1b4å
ar2m3b
ar4me3di
ar1me
arme7t
ar6m2e1tr4
ar4mi4n1f
ar1mi
armle6ne.
ar4m3l
arm1le
armle1ne
armå6la
arm6ål4
ar1må
2a2r2n
ar4nad
ar3na
ar4nal
arneva7la
ar2ne1v
ar1ne
ar3ni
ar3no
ar3nu4
ar3nå
a1ro.
aro8de.
ar2o1d
aro1d4e
a5rok
a1r2on
ar1op
ar2o6pa
a4r1o4r
a1r2os
ar7ost
a1r2ot
ar3ove
ar6ped
a2r1p
ar1pe
ar9po
arp5ret
arpr6
1arra
a2r1r
ar5re3e2
arr6e
ar7re5sk
arr2i8e7
ar3ri
arrå6da
arr6å1d
2a4rs
arsa6ka
ar1sa
ar3sak
ars5a6n1s
ar3s2el
ar1se
a4r6s5e4rs
ar4s1in
ar1si
ar2s5kam
ar1ska
ar5sk2ar
ars5ke1s
ar2s1ke
ars7kre
ar1skr6
ar4spr6
ar1sp2
ars6t4r4
ar1s2t
ars4vei
ar1sv2
ars1ve
ar1s7æ
arta4la
a6r1t
arta6le.
ar3t2a1le
ar3te
ar7te1le
8ar3te4t2s1
art4ha.
ar4t1h
arth2a
art4has
ar4ti1k2a
ar1ti
art2ik
6ar4tim
4ar8t1n
4arto
ar4top
6art2r4
art9ra
ar6trin
art6s5t4
ar4ts
art1s5ø
6artu
8arty
ar4ty1v
ar1ul
ar1un
ar5u6r
a1r4us
ar5u6t1b2
ar5u8t1n
ar2v4e3s
a2r1v
ar6v2e1te
arv4et
arvi8sa
arvi8se.
arvi1se
a1ry
ar7æ6
arø8ve.
ar1øy
a1rå
arå8de.
ar6å1d
arå1de
a2r7åp
a6r5åt
6as.
4a1sa
asab4
asak4
a4sa5lo
as6an
a4s7aug
as1be2
a8s9b4
a1sc
a2se.
a1se
a2s2e1a4
a2sed
a2seg
a2sek
as4el
ase5le
a2sem
a6senet
as2e1ne
a4se2nk
a7se2ol
a2se3o6
a2se5s
a6se1st
a4s2e1te
a2se3u
as2h
a2s5hu
a4si1b2
a1si
a2sip
a2s2ir
a7sis.
as2is
asis5t
a2siv
as4ja
a1sj
a6sje6tt
asj2e
a2s5jor
asj2o
a2s3k2ar
a1ska
ask6et
a2s1ke
as5k2e1ti
a1sk2i
as5kis
a5skj2
as5ko.
as5k6o3e
a4s5ko1pi
as3kor
as3k2ot
a1skr6
as7k8ra
as3kul
a5s6ku1la
a3s6ku1le
ask9u8t
a2s4kv2
ask5øy
as1l4
a5s4lag
asl4a8ga
as5le1v
as1le
as4lit
as1li
a1so
a4so.
a2s5om
a2s5ov
as4pan
asp2
as1pa
as3pe
as7pis
a1spi
a2s5pl6
as9sa.
a4s1s
as4sab
as4sal
ass5a6ld
ass5a2l1t
as4s2am
as5se.
as1se
as4sed
as4sek
asser2i7e6
ass2e1ri
as4se2r1v
as4ses
as6si4f3r2
as1si
ass2if
as5si6ng
as5s2i3s4
as4s2it
as2sj
as6s1k
assku6le.
as3sku1le
as2s3n
as5so.
ass2o9a
as7sos.
as1s2os
as6s1p2
as2s3t
as4stan
as4st6r4
ass5tru
as2s1v2
as4s2y1s
as1sy
as4søk
as1sø
as6s5å
as1ta
as6tab
a6stan1de
a3s4ta6nd
a4sta3tu
a1stat
a2s5te.
as1te
as3ted
as4teg
as4tek
as4t5e4n1h2
a1stj
a9s2tof
a5st6ok
as2t5ov
ast5ren
astr4
as4tro
as9tua
astu8ve.
astu1v
as2t5ø4v
a1stø
a1su
asu3n
as4u9sa
a1sus
as1va
asv2
a6s4ym
a1sy
asy6n7d
as2yn
a8s7ø4y1e
a1sø
as5å
4at.
4ata1
a5tae1ne
at8a1e2
a1taen
at6af
at4a8ka.
ata1ka
at8a8la.
a4ta6ng
at2a9rar
at6a1ra
a6t7a2r1v
atas4
a5t6as.
atat8
a2tau
at5a2v1h
a2tav
atch5
a2tc
a2t2ea
a1te
a2t2ec
at7e6d1d
a2ted
a2te3e4
a4tei
a2t5ei4d
at3e2ig
ate3in
a4t4e1ka
ate5k8e
ate2k5e7ta
a6t5ek1te
ate6k1t
ate7le
at3e2m1b
a2tem
4aten
a5tene.
ate1ne
a4te1ni
a4t2e1nu
a2te3o6
4ater
a5ter.
a6terat
at2e1ra
a8terek
ate1re
a4t4eril
ate1ri
a2t4es
a4t3e6tt
a2t6e5u
a4té
3atfer
a6t1f
at1fe
at4ha.
a2t1h
ath2a
at8has
4a1ti
atik6ka
at2ik
ati2kk
ati1li5
at4il
ati5n2ea
at4i1ne
ati4r2e1p
a2t2ir
ati1re
ati8sta
a2t3j2o
5atla6n1t
a6t3l6
atl9øy
at1lø
3at2m2os
a2t1m
at1mo
at4nel
a8t1n
at1ne
at2o5a
ato5gr4
at2og
at1oi
a2tom
a6t5op6p1r6
ato2pp
a1tor
a5to3se
at2o1s
a3to1v
a1tra
atr4
a6t5reg
at3ren
at3rer
a4tr2if
at3rin
at5r2ot
a9tru
at5røs
at5røy
2a4ts
at3ser
at1se
at7sj2e
at1sj
at7s6kat
at1ska
at7skj2
ats6kul
at2s3ø
4att.
a6tt
at6tat
at1ta
4at5te.
at1te
at5te6n5s
at3ten
attfø7re
at6t1f
att1fø
attf8ør
at4tid
at1ti
atti4s
att5i3se
at3tit
att1o
att7o6p
at2to6v
at1tr4
at4traf
at4t3re
at6trin
att3s6k
at6ts
att5s8l4
att3sp2
att3s1v2
at6t2y2s
at2t3ø2
a1tu
atu5e4
a2tut
a2t5v
atvi5er.
atv2ie
a3t3w
a1ty
atyr8ke.
aty2r1k
atyr1ke
a5t6y1v
a1tø
atø4r3s
at2ør
atø9se
atøs4
a4t5øy
a3tå.
a7tå1a
a1t4å1e
at5år
at5å4se
atå3s2
at7å2t8
4au.
8a8ua
au9ar.
4au5b
auba6ne.
au1b4a
auba1ne
au3c
au5da.
a2ud
au1da
au9d2et
au1de
au4di.
au1di
4a6ue
au7e2n
au7er
aue5re
au3est
au6e5ta
au1et
au5e6te.
au2e1te
au4gal
au1ga
au4gas
au4gel
au1ge
augele8ge.
auge1le
au1ge3le1ge
5aug2n
3au1gu
au5i
au5k2e1li
auke5l4
au1ke
au5ket
auk5la
aukl4
1a2uk7s6
au5kve
aukv2
au6las
au1la
au4lat
au2l5l
aul8la
au5lu
au9men
a2u1me
a4u6mo
aum5s6k
au6ms1
aum7s4t
a5u6nd
au5r2a1e2
au1ra
aure5s
au1re
au5ret
au5ri
au4r5s6
au1ru
auru4e
a4u1sa
aus9kj2
au6skr6
au1so
au4s5s
au6s8tas
aus8te6tt
au2s1te
au4stet
au2sti6s
aus5ti
aus6t7i3se
aus6t2on8
au5str4
au6s4tri
au1su
au4sun
au2s5ø4
aut6a
au3ta.
au7tar
au5te
1a2uto
au2to5v
au3t4re
aut1r4
a2u4ty
2a4ux1
a1va
a4v5ab
a5v2a1e2
ava7g
6aval
a4v3a8lv
a4v3a6nd
a2v3a6ng
a4v5a4n1l
3a4v4a6n5s
a9var.
av3a6r1t
avar6ta.
4avas
5av1b4i
a2v1b2
1a6v1d
3avdel
av1de
2a1ve
a2ve3d2
a5ve3de.
ave1de
a7v4eil
ave3in
a2vek
a4v2e1la
a4ve1le
a5veleg
ave6ns4
a1ven
a5v8er.
ave7ras
av2e1ra
ave4r5d
a6verei
ave1re
a2v4es
a2ve1v
1av1fa
a2v1f
1a2v1g2
avi4ar
a1via
4a5v2ig
a1v2ik
avi9ke
a1vin
a4v5i6n1t
2a1v2ir
5avi1sa
avi5sa.
3a6vi1se
av5isi6ng
avi1si
avis3t
a5vit
avlu9t
a6v1l2
avlø5se
av1lø
6a2v1n
av7na
av4ne1s
av1ne
a1vo
a4vok
avo3r
avo4v
a2v1r8
av4res
av1re
av5ri
av1sa
a8vs
av5seg
av1se
av1s2i
av3s2k2o7g
av1sk
3avs1ni
av1s2n
av1s2p2
avs2pe9g4
avs2pe
avs4te
av1s2t2
av5su
av1s2v2
1a2v1t
avta9ka
avta1
2a1vu
1avvi
a2v1v
av3ø4l
4a1vå
a6v7å6l
a1w2a
a1wat4
a5we2
awe9ne
a1w4i2
ay2a
ay7a4ne.
ay1an
aya1ne
ay5ar
ay9s8t
a2ys
a5y4t
a5zu
azz3o
a1ø
a7å6
1b4a
ba3a
ba1by5
ba4b2y1s
ba5c2l
b8ac
ba2d
ba4da.
b6a1da
ba7dan
b5a6d1d
ba8de.
ba1de
ba4d2e7b2
ba5d2en.
ba7d2e6ns
ba3di
ba8d1s
4baf
ba2k
b6a5kan7
ba1ka
b4a3kar
bak2a4rs9
ba3ken
b2a1ke
ba4k5e6nd
baken6de.
baken5de
ba3ker
bake3s
ba9ket.
bak9e6tt
b4a3ki
ba8ki.
bak6ke4rs
ba2kk
bak1ke
bak6ko
bak5kr6
4bakr6
bak4re
ba5kri
bak3ro
bak3s1m
bak5sp2
bak5s6ti
bak5s4tr4
ba6k7t6
b2ak1v2
ba3la
bal7ak
ba4le1s2
b2a1le
ba4li.
ba1li
ba2l3j
bal4lag
ba2l1l
bal4lan
bal4led
bal1le
bal4leg
bal4lei
bal4le1v
bal9l2ig
bal1li
bal6lov
bal8l5s6
bal6læ1
ba1lo
ba2l5t
ba1lu
ba1n4a
b6a4na.
b8a5nan
b4a5nar
ban9da
ba6nd
ba4nel
ba1ne
ba4nes
ban6kap
ba2nk
ban1ka
ban4kor
ban3ko
ban2k3u
bantu5
ba6n1t
ba3re
bare6t5t
bar4et
b2a3ri
bari6e7n
bar2ie
bar8k5s
ba2r1k
bar5skr6
b2a4rs
b6ar5t2r4
ba6r1t
ba5ru
ba5sen
ba1se
ba4s2e1ru
ba4set
ba3si
ba2s1k
bas6sak
ba4s1s
bas4san
bas1si4
bas5s2i3s5
bas4so
bas4s3t
bas4s6t6r4
ba2st
ba7s8u
b4a1ta1
2ba1tr4
bat6ti
ba6tt
bau9la
6bav
ba5z
2b1b
b3b4a
b6ba2k
b4b5a2r1b8
b6ba1se
b3be.
b1be
b4b2e1b2
b4be1da
b2b4e1f
b4be3g
b6be1h
b2be6i
bb3e2i7e
bb5e2ig
b2bek
b6b2e2l1o
bbe2l6t3
bbel1te4
b4bem
b9bene.
bbe3ne
b2be3o6
b3b4er
b4b5e2r1f
b4b4e1s6
b7be1ska
b6be1ta
b3b4et
b6beten
bb2e3te
b4b2e1ti
b6b2e1to
b6b2e1tr4
b2be1v
b8b1h
b3b4i
bb2i9e8
b4b5i4nn
b2b1l2
bb7len
bb1le
bb5o1p4
bb2o
bb5rek
bbr8
bbu9ra
b1b2u
bb5ut.
bb5u1te
b2by5
bb4y3e
bb6y1k
b4b4ø
b6b4å
2bc
2b1d
b2dek
b1de
b7den
b4d2e1p
1be
be2au
b2ea
be4bo.
b2e1b2
beb2o
bebo5er.
bebo2er
beb6o1e
bebo9k
be4da.
be1da
be9dar
be3d2er
be1de
bed2i9e8
be1di
be4dre
be1dr4
be2d5red
be8d2s1
bedy9ra
be1dy
be1då3
2be3e2
be5ed
be6e1f
b4e1f
befa5re
b4e1fa
be3far
be3g
b2e1ga9
be4ga.
be4gi.
be1gi
be4g1n
b4e5go
be4g5re.
be1g2r4
be4g5rene.
begre1ne
be4gå.
b4e1gå
be6ha.
be1h
beh2a
beha7g
behe4r4s7
4behu
behå4r
beh6å
be6i
be5ke
be2k3i
bek6kel
b2e2kk
bek1ke
bekke5r
bek4kes
be1k6l4
4beks
be4l3a1b
b2e1la
bel4a9ga
bel5e2i7e
be1le
bel5e2ig
be2l5ein
be4lek
bel6i9na
b2e1li
beli9v
belle5sa
bel3l6e4s
be2l1l
bel1le
bel5let
bel5læ1
b2e2l1o
bel5s4p2
be8l1s
bel7s4t
bel4tag
be2l1t
bel4t4e5s
bel1te
bel4t3ø
bel3u
be8l5v
belæ5re
b2e1læ1
belå7ne
b2e1lå
belå2n
b2e4na
be5nat
be3ne
4bened
be4nest
ben5gu
be6ng
be5ni
ben5s4i
be6ns
ben5skj2
ben5sp2
ben5te
be6n1t
2b2e1p
b4er
4berai
b2e1ra
be7ras
be4r5d
be4r2e1p
be1re
8berest
ber2e5te
bere4t
ber4ga
b2e2r1g
ber4g5en1de
ber1ge
berg2en
berge6nd
ber5g6es
ber1g3j
berg3l
ber1g3o
ber4g3å
be5ri1b2
be1ri
beri5ke
ber2ik
be7ris
ber6kl4
be2r1k
ber5na
b2e2rn
ber5ne
b2e1ro
be4ro.
be3r2o9a
ber3ri6
b2e2r1r
ber5te
be6r1t
ber5ti
b2e1ru
ber4u9sa
be1r4us
berø5v
b2e1rø
b4e1s2
5b2es.
be4se.
b2e1se
be5s4i
4be3s2ik
6be1sj
be2s5ke
be2s6k5n2
be3s1n
bes7ne
4b4e3so
be2so9v
be5s2p2
be4s3s
bes6s7a6
bes6s4el
bes1se
bes7si
bes7te6ns
b6esten
bes1te
be2s6t4e3s4
be5s5t2es.
be6så
3b4et
5bet.
be4tab
be1ta
b2e3te
4betei
be7t4es
beto5ne
b2e1to
be1t2on
be6tra5r4
b2e1tr4
be6t1re.
b8etre
be4t3ri
4b2e1tu
be4ty.
b2e1ty
beva5re
be1v
be6ve.
bevi5se.
bevi1se
be9vo
be2v9r8
6be1å6
beån9
7bé
2b5f
6bg
2bh
bhu1
1b4i
bi5ak
bi6bla
bi1b2
bibl2
bi5ce
bi1d
bi5de3e2
bi1de
bi8dé
bid6r4
bi1du8
bi5el
b2ie
bi6e1le
bi5e2r5v
4bi1fa
b2if
b2i5g
bi3k2a
b2ik
bi2k1k
bik6ki
bi5k4l4
4bi7k1r6
bi6la.
bi1la
bi4las
bi4lau
bi3let
bi1le
bi2l2e1t5r4
bi4lin
bi1li
bi2l3j
bil5la
bi2l1l
bill4a8ga
bil5leg
bil1le
bi2l5m6
bil1o
6bi5m
bi4na6ns
b6i1na
bin2an
bin1go5
bi6ng
bin4gol
b2i7no
4b5i6n1t
b4io7
bi3o8m3s4
4bip
bi4ri5
b2ir
bi7ris.
bir4ken
bi2r1k
bir1ke
bi1ro
bi1s2a
bi3se
b5is1h
bis6hi
bi8s7ke
bi5s4la
bis1l4
8bis1n
bi4s1p2
bi1s4p5i
bis5se
bi4s1s
bi1st
bi5s6ta
bis4t4il
bis1ti
bis5tru
bi2str4
bi3s4v2
bi6ta.
bi1ta
bi4te.
bi1te
bi5t4e1s
bi8ti.
bi1t2i
bi4tre
bi1tr4
bi6tri
bjar3
bj4ek4t5o
bj2e
bje6k1t
bjø6r
bj2ø
2b1k4
b5k1h
bl2
1b2lad
bl6a1d3a
b2la6f3
bl4a8ga
bla2k5r6
bla5me
blan9da
bla6nd
b4la2nk
blan5ke
b4la6n1t
b1lar
b8l2arar
bla1ra
b3lat
bla4u
b4le1f
b1le
ble2i7e5
blei5er
b4lek
b2l4e4k3a
ble5kes
ble1k6e
b2le4mo
b3ler
bl2e7r6a
b6le1sa
bles5s2e9ne
ble4s3s6
bles1se
b4lest
2blet
bli9ke
b1li
bl2ik
bl4i5ma
bl6i5me
bling2s6i
blin8g2s1
bli6ng
bli4s3s
b5lj
blja4
blo6dr4
bl2o1d
1blok
bl2o2m5m4
b6lu
blues3
bl4u9sa5
bly7g6l
blæ5re.
b1læ1
blæ1re
blå5n
b2lå3r
blå7sa
blås2
blå5se.
blå1se
2b1m
6bn
b3ne.
b1ne
b3ner
b7n2es.
b7ni
b2o
boa5s4
b2oa
bobba6ne.
bo2b1b
bob3b4a
bobba1ne
bob5by.
bob2by5
bob9b4y3e
bo2b2l2
bob7la
bo6b1s4
bo3d4e
b2o1d
bo6din
bo1di
bo6d7r4
bo2dø
4boe6f
b6o1e
1boe2n
bo2er
bo4et.
2bo3f6
bo4gel
b2og
bo1ge
bog1n7
bo8g1s1
2b2o1h6
2b4o1j
3bok.
bo2ka
bo6kel
bo1ke
bo4k3et
bok1i
bo2k1k
bo6kop
bo3ko
bo6k5ri
bo1kr6
5bok1s
bok3s2i8da
bok5s4i
boks4p2
bo2ku
bok3ve
bokv2
b4ol
bol5e2i7e
b2o1le
b2o7li
boli7n
bo7lo.
b2o1lo
bo5lo3i
bo4l5o4r
bol5s4p2
bo8l1s
bols8t6
1bo2m1b6
bom4bel
bom1be
b2o2m5m4
b2o6mo
bo6m1s4
bo5nap
b2on
bo1na
bon5ato
4bo4nn
bon5ne
bo6ns4
bo5nus
bo1nu
b2oo6
boom1
bo1p4
bo1ra
bo4ra.
bo6re.
b8ore
bo7r1el
bo3ren
bo3ret
bo4r2e1te
bo6ri.
bo1ri
b4o3ro
bor6t7e1f
b2o6r1t
bor1te
bor6tei
bor4t4es
bor6t7et
bor4ti
bor4t5s6
b4o1s
bose8te.
bo1se
bos2e1te
3bo1ska
bo2s2l4
bos5se
bo4s1s
bo4st1o
1b2ot
bo4ta.
bo1ta
bo4tak
bo4tal
bo6te.
bo1te
2bo7to
4bo1t6r4
4bo2t4v
bou3c
4bo1v
bow4e2
bow1
bo6y
4bp
b7pl6
br8
1b4ra
bra5ka
b2rak5s
bra5se
bred5s1p2
bre8ds
b2re2i7e
br2ei3s
bret7te
bre6tt
b6re1v
brevi9er.
brev2i4e
bre4vin
bri5a
b4r4io
b2ro
bro9ar
br2oa
br2o1s4
3b4ru
bru5ke
b4ruk
br2uk4s3
bru4na
bru7na.
bru5ne1s2
bru1ne
bru5pl6
bru7ren
bru2r
bru1re
br4u5sa
br4us
brus4l4
1bry
bry6n4s1
b4rø
brø5de
br4å1e6
brå9ne
brå5te.
brå1te
6b1s
b1s4e
b5s6e6a5m4
b2s2e1a
b4s5el
b5s6i
bsk4
b4s1li
bsl4
b2s1m
b2s1of
bs2t6
b6s6t7f
b7s6v2
bså5
6b5t
b7t6s
1b2u
bu4ar
4bub
bu3da
b2ud
bud5d6h
bu6d1d
bu3de
4bu3em
bu4en.
bu1e2n
bu9e1ne
bu4er.
bu1er
bue5s
bu1i
bu6is
6bu7ki
buk3l4
bu5la
bu4le.
bu1le
bul3le
bu2l1l
bun7de
bu6nd
b4un8d4s3
bun8ge.
bu6ng
bun1ge
bun4k3r6
b4u2nk
bunnl4a8ga
bu4nn
bun4n1l
bun6n3s
2bu3o
bu1p
bu6ra.
bu1ra
bu4re.
bu1re
4bu3ro
bu3ru
b2u1s6e
bu4se.
bu6s4h5e
bu2s1h
busk7ø
bus6sek
bu4s1s
bus1se
bus4s4el
bus7s6e6n1t
bus6set
busse6te.
buss2e1te
bus6sj
bus4s1n
bus4sp2
bus4s2t
bus2s3v2
bu1st
4bustr4
4bu1sy
bu1ta
bu4tal
bu7tem
bu1te
b5u2t3g2
4b5ut1st4
b1u4t1s4
bu2tu
bu1t1ø
4bu1v
6bu1ø6
2b1v
4b5w
1by
bya2
by5a2l
by9a1re
by7d2e1p
by1de
by4en.
b4y1e
bye2n
by7e6ne
by5e6ns4
by2er
by3e4rs
by4ge.
by1ge
byg3l
3by1i
by1lo
4by1lø
by5n
by1re
by6re.
b2y1s
5bys.
4by1s4e
6by1s6i
6bysp2
bys2t4
by4s1te
4bysv2
by5tar
by1ta
by4te.
by1te
by1tr4
2by1v
bæ5rar
bæ6r4ar.
bæ4re.
bæ1re
bæ5ren
bæ5rer.
bæ1rer
bæ5re1re
bæ5r2es.
bæ5ret.
bæ3ri
bæ4r5i6s
bæ3r1u
1b4ø
b4ø4e2
bø7e6ns
bøf3
bø4ke1s
bø1ke
bø6la.
bø1la
bø6le.
bø1le
bø2n
bø1ne5
bøn6nes
b4ø4nn
bøn1ne
bø1n7o
bø6re.
bø1re
bør4s5k
bø4rs
bør4sp2
bør2s3t
bør2s8ta.
bør2s3v2
bø5ta
b4øt
bø1v
bøy7ar
bø4y1e5
bøy7e6ne
bøye4n
bøy4es2
bøy4e2t
1b4å
bå4de
b6å1d
bå9de1s
b4å6e
bå7e1ne
båe2n
bå4la
bå1re
bå6reg
bå4r2e1p
bå4s3te
bås2
b4å1st
bå6s7ti
bå6t5j
bå6to
båt5r4
1c4a
cab4
c2a4e2
c2a5le
ca5me
ca5mo
ca4pe.
ca1pe
cap1r6
ca6pris
ca3ra
c2a2r5n
ca5ro
car4te.
car3te
ca6r1t
c4a2sa3
ca6set
ca1se
cas2h5
ca5s4t4il
cas1ti
cas5to
ca1t
ca2t1h5
ca4to.
6cb
4cc
c1ci
c2d
c1de6
ceb2o9
c2e1b2
ce3d
ce1i
1cel
ce5le
cel4l6e4s
ce2l1l
cel1le
cel4le1v
3cen.
ce4ned
ce1ne
ce4ne3e2
c4e4nem
ce4ne5s4
ce5n2es.
ce4net
c4e4ne1v
5ce6ns
cen4ti9m
cen3ti
ce6n1t
cen6to
ce5o6
1cer
cerba6ne.
c4e2r1b8
cer1b4a
cerba1ne
ce3re
c2e1ro5
ce6r3t
6c2eru
cest3o
2ch.
ch2a5le
ch2a
3cham4
cha7ne
cha5t
2c2h1b
4che2i
che8l5s2
ch2e7te
chet2
chi4li
4c2h3h
2c2h1m
4c2hn
ch5ne
chom4
ch4o
ch2o5s6
2ch1p
8c6h1s
6c4ht
ch1v4
ci1c
3cid
c2i2e
ci1e8n
ci2e2s5
ci2l5l
c4i3ne
ci5ta
2c6k
c1k5ar.
c1k5a6r1t
ck1en
c1ke
ck3er.
ck4e1re
ck5e4t3
ck5et.
cke8y5
ck1i
c2k1k
ck1o2
ck7r6
ck5s2
cku6
c1kup3
ck9ut
c2l
cla2i4r5
cly4
c1m
cmi1ni4
c1mi
c2oa6
coat5
co6bi1d
c2o3b4i
1c4o4c
2c2o1d
6cof
6c2og
co4la.
c4o1la
co4la1b
co4lak
co4la5r
co2m
c2o5ma
co2m5t2
co6n5os
c2on
co1no
co6n5s
co6n7t
c2o2o
c2o7pa
2cor
co3r6a
c8o1re
co4so
c2os
4cost
co4ve2r
c1pr6
cr8
cras4h
cra2c6k4
cr8ac
c2re3e2
cree4n7
cr2os2
5cru
4c4s
4c1t
c6ta
cty5
1cu
4cu.
cu5la
cu5le
cu5lu
cup1l6
cu6po
c4up1r6
c6u2p7s
cu4på
2cur
cures4
cu1re
cu6t
cy2a
cy6p7
c2ys3
c6z
cæ5
1da
4daa
4dab4er
da1be
8dab2o
d5a6b1s
6d5a4del
da1de
d4a2d1g4
da1dø4
dad2ør5
4dae1f
d2a1e2
2daf
5da2g1b
da4g2e1v
da1ge
4da1gj
dag7l
da6go.
d4a1go
da4g1r4
da6gun
da1gu
4da1gå
4dahe
da1h
5da2hl
da1i
2da1ka
4d2a1ko
4dakr6
dak4se.
dak1se
4d7akt2ig
da6k1t
dak1ti
d2a5kv2
7d6a2l5j
da1la
d8a8la.
da4las
6d4alau
dalbu8er
da2l1b2
dal1b2u
4d5a6ld
da4le1s2
d2a1le
dal6so
d2a8l1s
da1lu
da4ma.
da1ma
da4man
da3mas
da4me.
da1me
da3men
6d5a4m4e1ri
dame3s
dame5t6
da3mo
dam7pe
da8m1p
5dan.
6danal
da1na
d8a1ne
9da1ni
2d1a4n1l
3da4nn
dan5n2e1b2
dan1ne
4dan1no
7da1no
d4ans.
da6ns
dan3sa
dan4s2el
dan1se
dan9s8kan
dan1ska
4d1an1sv2
9dant.
da6n1t
9dan1ti
8d6a1ny
2da3o
2dap
da3pe
d3a2pp
3dar.
5da1ra
2d1a2r1b8
dar8d3s4
da4rd
4d1a2r2ea
da1re
da4res
d5a2r2n
da2ro
dar5os.
da1r2os
d2a4r5s6
dar4ta.
da6r1t
dar6va.
da2r1v
darv4a
dar8ve.
4dasei
da1se
2das2h
6da1si
4das2je1f
da1sj
dasj2e
da4sk
da9sko
6dastr4
6da1su
d4ata5
4da4tal
data6le.
da3t2a1le
datal6i8na
data1li
da4tek
da1te
7d6ato
da3t2o5a
4da5tr4
dat1s6j
d2a4ts
d6a2ud
dau8d7s8
dau5go1
2daut
2dav
d5a2v1b2
d1a6v1l2
d3a8v5s
4da1ø
2d1b2
dba8le.
d1b4a
db2a1le
dba4ne.
dba1ne
db4e8ta.
d1be
d3b4et
dbe1ta
dbe6te.
db2e3te
dbli6ng7
dbl2
db1li
dbo6en.
db2o
d1boe2n
db6o1e
d6by1f
d1by
4d1c
6d1d
d2dad
d1da
ddag4
d4dak
d4d5a2r1m
dd3e2i7e
d1de
d2dei
d2dek
dde4lap
dd2e1la
dd2e4l5o
ddel5s1v2
dd4e8l1s
d4de1mi
dde4r5s
dd2er
d4de1su
dde1s
dd2ie8
d1di
d7dom
d1do
d4d1re
ddr4
dd5run
d2dru
ddsa4
d8ds
dd4sa1la
dd1sal
dd4skap
dd1ska
dds5tab
ddst4
dd3s4te
dds5tr4
d2dy1b
d1dy
d1dø2
dd2ør3
d4d5øy
dd1år
1de
2d2ea
de1a2k3
de2al
d2e7b2
6debar
de1b4a
4debas
3debat
2de1be
4debl2
2deb2o
5debon.
deb2on
2debr8
3debut
de1b2u
2de1by
4de1b4ø
d2e1c
5ded.
2de1da
6d7e6d1d
7de3de.
1de1de
4dedek
4dedel
4de5d6ia
de1di
2de1d4r4
2de1dy
2de3e2
5dee.
de7er
2de1f
d5e2ff
d5e8ft
2deg
6d6e5ge
5de4g1g
de3gl
2de1h
5deha8vs
deh2a
2dei
dei4d
dei4e1ne
de2i7e
dei1e2n
3d8eig.
de2ig
dei8ge.
dei1g6e
5dei3g2en
dei4g5r4
5de2ik
d2e1in
3d2e2ir
de5is.
d2eis
de3ist
7de7it
2de1j
8d4e1ka
2de1ke
2de1ki
2d4e1kj2
5d2e2kk
dek1k3a
dek4kan
dek4kel
dek1ke
de6k6k1v2
de1k2l4
6dekly
5dek1ni
d4e2k1n2
de5k2o
4dek2o1d
4dek6on
4de1k2os
2d2e5k6r6
dek6st
dek3s7ti
deks6tr4
5de6k1t
2de1ku
4de1k2v2
4de1kø
4de1kå
d4el.
6delad
d2e1la
6dela1ge
de6la6ns
4delau
4d4eled
de1le
de4le1f
4delei
del5ei4d
del5e2i7e
4de4lek
de5le1le
de2lel
4d5e2lem
de4lest
6d3e4le1v
4delid
d2e1li
6deli4n1j
de4l5i6n1t
4delis
4deliv
del4lap
de2l1l
del6lei
del1le
del4lek
del6le2r1f
del5ler
del9l2ig
del1li
d4e8l1s
del4s5at
del3se
del3se6s7
del4si
del4s1p6o
delsp2
d4e2l1t
del6tala
del3tal
del3te
del7t2r4
6de3luk
del5ve
de8lv
4d4e1ly
4d2e1læ1
2d2e1lø
delø6pa.
delø1pa
6deløy
2d2e1lå
2de5ma
d3e2m1b
2d8e1me
dem2ie4
de1mi
4demj
dem8na.
de2m1n
dem1na
de4mo.
de1mo
de4m2og
5demok
4demol
demo5no
dem2on
5dem2os
dem5pe
de8m1p
3de2m1r6
5de6ms
4de1mu
2d6e1mø
2de1må
d2en.
4d2e1na
de7n1ak
6d5en6d2en.
de6nd
den1de
de5n2e1b2
de1ne
4dened
6denel
4d4enem
4denet
4d4ene1v
6d5en3gen.
de6ng
den3g2en
den1ge
4de1ni
4d2e1no
de4nom
d2e6ns
den4sin
den3si
den4s3t8
de6n6t5a6n1t
de6n1t
4dent2if
den3ti
4den2tit
dent3o
den4tr4
den4t5s8
den6t5u6
den6tå
4d2e1nu
2d4e7næ1
2de1nø
de2ob
de3o6
de4og1
2de2ol
4deo2pp
deo2p
4de3or
de4ove
4depak
d2e1p
de1pa
4depap
2de1pe
4depi
4dep2l6
6de1pu
d2er
der8am
d2e1ra
de6ra6n1t
de4ra1re
de4ra2r1k
5de7r6ast
der6a5te
de2r3av
de4r5d
4der2ea
de1re
4dered
de4r5e6d1d
de4re1f
4dereg
4der2e1p
6dere1si
der5est
8dere6st.
6deres1te
4d6ere6tt
dere4t
de4r2if
de1ri
d4e4ril
5derin
de4ri1næ1
de2r5k
der5ne
d2e2rn
de5rob
d2ero
4de1r2og
4der2os
de5ro1se
dero8se.
6de7r2ot
der1o4v
der5s6n
de4rs
d6er4sp2
de6r3t
der5un
d2eru
de5rup
6de1r4us
de2r3v
4de1rør
d2e1rø
2d2e1rå
de1s
3d6es.
2de1sa
4desc
2d2e1se
de5seg
des5e4rs
4de1sh4o
des1h
de8s9hop
desi4s5t
de1si
des2is
2de1sj
2desk
9de6sk.
5de2s1ke
4des4le
desl4
2d4e1so
2de1s2p2
7de2s3pl6
6de5s6pr6
d4es2t
5de6st.
5de6s5te.
des1te
des6tem
de5s1ti
4de1stj
4desto
4de3strå
destr4
4de1stu
6de1sty
d8e4s3tå
6de3s2up
de1su
des6v2
2d2e3sy
deså7
d2et.
2de1ta
det4a8ka.
de3tak
deta1ka
d2e3te
4det2ea
6deteg
6de5t2e2k1k
4detel
4deten
4de2t2e1p
det4es7
2d2e1ti
2d2e1tj
4d2e1to
4d2e1t6r4
2d2e1tu
4d2e1ty
4de1tø
6de1tå
2de1u
d1e2ur
5de2us
2de1v
deva8ne.
dev4a1ne
devi9er.
dev2i4e
3dev2ik
d7e2v8n
de5vu
2dey1
4de1ø4
2de1å6
2d1f
dfø6r2arar
d1fø
df8ør
dfø1r6a
dfø5rar
dføra1ra
2d1g4
d4gel
d1ge
d5gi
dgi6n
dgjø6re7n6h2
d1gj
dgj2ø
dgjø1re
d3go
2d1h
dha8v4s
dh2a
dhei6m2s5
dhe2i
d3h4eim
dhu9ga
dhø4r
1di
di6ak
di5a2l3g
dia5li
di1ar
di2a3re
dia1s
dia7sp2
di1as6t
di2a4ts4
dia1t
2di1av
2di1b2
d2id5ri
didr4
di7e2l1l
d2ie
di5en.
di1en
4di5e6nd
di1er
di2e1s4
3di2ff
d2if
dif1fe5
3diful
di1fu
4di3fø
di8g9a6nd
d2ig
di1ga
di4g2at
di3g6e4s5
di1ge
di4g7g
2di1gj
di6g8la.
dig1l
digl2a
dig6le.
dig1l6e
4digren
dig2re
di1gr4
dig5ret
4digru
di2gu
4di3h
4di1i
2di1j
di5k2a
d2ik
di8ka.
di5kem
di1ke
2di1kj2
6di2k1n2
2di1ko
2di1k1r6
dik7v2
2di1li
dil4l6es
di2l1l
dil1le
d4i5ma
6dimed
di1me
6d5i6m2el
4dimes
4dim8et
2di1mo
4d5i8m1p2
4di1må
d6i5na
2d1i6nd
di7nen
d4i1ne
4d5in2g3k2
di6ng
din3gr4
ding8s5en
din8g2s1
ding1se
ding6s5er
din8g9å
di1ni4
di4ni.
4d1i4nn
2d2i1no
2d1i6n1v
4d6i1nø
3di2og
d4io
7diol4
dio3na
di2on
dio4no
di3o1ri
dio5tr4
di2ot
2dip
3di1pl6
4dired
d2ir
di1re
4direg
4d5i4rs
5dis.
di6sc
di6sed
di1se
di1s4i
disi6e5ne
disi1e4n
dis2ie
d6isk
di2s6kam
di1ska
di4skj2
di2s6k7l4
di4sk6o
dis5ko.
dis7k6o3e
dis7ku
di4s5s
dis1t
dis7t2ik
dis1ti
di5s1v2
dis4vi
2di1sy
di4tal
di1ta
di7te
4ditek
dit4tet
di6tt
dit1te
dit4t3r4
di9us.
di1u
di4va.
d4i1va
4di5val
di7van
4di2vek
di1ve
di1v6i
2di1vu
2di1ø4k
di1ø
d1ja
d1je.
dj2e
d1jen
djer5ve
dje2r1v
d7j2es.
d4je9ve
dje1v
d6jingan
dji6ng
djin1ga
d6jingar
d1j2o
dju8la.
dju2l
dju1la
5d8jup
6djupar
dju1pa
d4jø.
dj2ø
2d5k2
dka8ra.
d1ka1ra
dki6
d5kj2
dko2r
dku4le.
dku1le
4d1l4
d3la
dl4a4ga
dlan6d7as
dla6nd
dlan1da
dla6te.
dla1te
d2la4v
d4le1di
d1le
d2le1f
d4l2e1ga
d7legar
d3le3ge
dlei7er.
dle2i7e
d5l4e1ka
d2lel
dlem4st8
dle6ms
d4lenet
dle1ne
d4l2e1ru
dleva8ne.
dle1v
dlev4a1ne
d5li
d3l2i4f
dli5ke
dl2ik
d5lo
dly8se.
dl2ys
dly1s4e
dlø8pa.
d1lø
dlø1pa
d5lå
dlå9re
d2lår
2d1m
dme6la.
d1me
dm2el
d4m2e1la
d3m6o4e
d1mo
dm2o4rs6
dm2o8s
dmø4re.
d1mø
dmø1re
dmå6la.
d1må
dm6ål
dmå1la
8d1n
d1n6a
dno8de.
d1no
dn2o1d
dno1d4e
1do
2do1a4v
d2oa
do2b3
4d2o3b4a
do3b4e
8do9b8lan
do1b2l2
2do3b4å
do4da
d2o1d
do1dø4
dod2ør5
4doe1u
d6o1e
2dof
d3o2ff
d5ofr2
dog6med
d2og
do2g1m
dog1me
dog4me5s
2do1gr4
6do5i
do3ki
4dok4i5ne
dok6kan
do2kk
dok1ka
4d2o2k1n2
d2o5le
4do2led
doli8ne.
d2o1li
dol4i5ne
4doli4n1j
6dolis
4d3o6lj
do2l5l4
4domes
d2o1me
do5mis
do1mi
4do4m3l
d2o4mo
do8m7p2
2d1o2m1r6
dom8sa
do6ms
dom8s5e6
dom6s5i
dom4s3k
doms3l4
domsl4a6ga
dom4so
do4m1u
do4na.
d2on
do1na
do6n5g
4don1ke
do2n1k
6d7on6kl4
don5st
do6ns
do6n5t4
2dop
do4pa.
d2o1pa
do4pe.
do1pe
3do1pi
d6o1ra
do5ran
2d1o4rd
dor4da
d8o1re
dor6ge1st
dor3ge
dorg6e4s
do2r1g
d5or2ie
do1ri
do2r1m8
d4o3ro
5do2r1p
3d4o2r1r
d2o4rs6
dor2s7ke
do1r1u
do7r4y1e
d4ory
5dos.
d2os
do4se.
do1se
do4set
do1sk
6do7s6l4
2do3so
4dosp2
2dost
do3str4
4do1tr4
d2ot
2dov
do5va
d4o5ven
do4ve2r
do2v9n
4do1ø
2d7p8
5dq
dr4
d1ra.
d5raa
d9rad.
d5ra1de
1d6rag
dra5g2en
dra1ge
dra6kes
dr2a1ke
3dra6k1t
1dram
5d6ra6ng
3drap.
5d4ra1pa
3dra1pe
d3ra2pp
d1rar
d6r2arar
dra1ra
d3r1a2r1b8
dra6r4et
dra1re
d5r1a2r1m
d5ra2r1r
d9r4a8sa
9dra6tt
dra2u7ma
2d1re.
5dre1a4s1s
dr2ea
2dred
d5re3de.
dre1de
d5red2er
d2r4e7d6r4
d5re8ds
9dreg.
9drege.
dre3ge
d3re1gj
3d2re2i7e
drei5e2n
drei5er.
drei7e2rn
d5re1j
d6r4e1ka
d7r2e2kk
3dre6k1t
4drel
d7re3l2ig
dr2e1li
4d2rem
d1ren
4d1rer
d3ret.
d3r2e8t1n
d6r2e5t2r4
d3re4ts1
dre6tt4
dret6ts5
5drev4et
dre1v
3dre2v1n
4dria
3d2r2if
2d1r2ig
d2r2ik
d3rik.
dri8ka.
dri1k2a
d3ri1ke
3dri2kk
drik7s6
d9ri6k1t6
5d4ril
2d1rin
dri6ng6
drit7tr4
dri6tt
dri1t5u
dritun8ge.
dri6tun
dritu6ng
dritun1ge
1d2riv
3d4ro4nn
dr2on
dro6pa.
dr2o1pa
dro2p5s
2d1ror
3d4r2os
dro8ta.
dr2ot
dro1ta
2d1rov
dro5va
dro3ve
2dru
3d4ruk
d1rul
d1r4um
d3rup
6d5rut
2dr6yk
dry2kk4
d2r2ys
d4rø6m
drø6re.
d1rør
drø1re
d7r4øt
drø3ve
d1r6å1d
drå4de.
drå1de
3d2råp
8ds
d5sa.
d3s8ab1l2
ds1ad
ds3a6del
dsa1de
d1sag
d5s4a9ga
dsak6se.
dsak1se
d1sal
d7s8a8la.
dsa1la
d4s3a4l1f
d6sa2l1l
d9sa1me
d1s2am
ds1a6n
d1s5a5ne
ds1a2r
d2sas
d4sa6t1f
d1sat
dsbø6n
d8s9b4
ds1b4ø
d5se.
d1se
dse4d
d2s1e1f
d2s1ei
ds7eks
d2s3e1la
ds2el
dsel4s5a
d3se8l1s
dse2m
ds3e1mi
d6s7en9d8a
dse6nd
dsen8de.
dsen1de
d4s3e6ng
d5s2e6ns
d2s3e2p
d7s2er.
d6s1e2r1f
ds5e2r1k
d4s5e4sk
ds5e4st
d2s3e2t6a
dse4te.
ds2e1te
d5s2e1tj
d2s1e1v
d2s1i
d3s2id
d7s2i2da
ds5ide6n1t
d3s2i3den
dsi1de
d4s3idr4
d7s2il
ds5i6nd
d3s2ir
ds3i4s
dsi4ve
d4s3jen
d1sj
dsj2e
d2sj2o
d2s7jor
ds6ju
d4s5kab
d1ska
d4s3kan
d2s3k2ar
d4skat
d1skj2
ds5kjen
dskj2e
d6s5kj4øt
dskj2ø
ds1ko
d5s2k2ot
ds3k2ro
dskr6
d2s1kv2
ds1l4
ds5la.
ds5lan
ds6lem
ds1le
dsl2i6k
ds1li
ds6lo.
d9s6lott.
dslo6tt4
dsl2ot
ds4luk
d1slu
ds3ma
ds1m
d6s1mo
dsmå6la
ds1må
dsm6ål
ds3ne
ds1n
ds5no
ds1o2
d7s4omst
ds1o6ms
d4s5os
d7s2ot
ds1p2
d3s2pek
ds1pe
d3s2p6el
d1s2pi
d9s8py
d4s5s4
dst4
ds3tak
d4s3tal
d3s8tar
d5s4tat
ds6tau
dss8t
d8s9te.
ds1te
ds9te2ik
ds2t4ei
dste6ma.
ds2tem
dste1ma
d6s5te8m1p
d4s5te3o6
d3s4t2ig
ds1ti
d3s4t2ik
d5stil1li
dst4il
dsti2l1l
ds1tj
d3stor
d3stri
dstr4
dstu8na
d5s4tu6nd
d2s3t1v
dst4y8e
d1sty
d4s3ty1v
ds1u2
dsu1re6
d5s6us
d6s7usk
ds1v2
dsva8ne.
dsv4a1ne
d3s4v4et
dsvi6ka.
dsv2ik
dsvi1k2a
d5s6yk
d1sy
d3s2yn
dsy6na
d2s1y2t
d2s1ø
d6sør
ds1å6
4d1t
d3t4a
dt8a6la.
dta4le.
d3t2a1le
dta4s
d3tem
d1te
dte4ma.
dte1ma
d2t4e6ng
dter1le7
dte2r1l
dte4se.
dt4es
dt2e1se
d5t4et
d3ti
dt6j
d7to
d5t4rag
dtr4
d6t1r4oc
d5t4rø
d6t1s2
d3t2v4a
d2t1v
dtø8
d2t6øk
dtø4rs3
dt2ør
dtå4
1du
du2b5b
dub5l2
du1c
du9e1ne
du1e2n
du9e6ns
due5s
4du1fo
3dug
4du1gu
duit6
du6ka.
du1k2a
du2l5l
4d1uly
du6ms2
du4na.
du1na
dun7de
du6nd
4d5u1n6i
d4u6n5s6
dun7sta
7duo
d3u4p5k4
3dur
du6ra.
du1ra
du6re.
du1re
du6rei
dur8ta
du6r1t
du1s
d4u5s4a
du9sem
d2u1se
du5s2i
du2sk
du2s1l4
du2s1p2
du4s1s4
dust4
du2s1v2
2d1ut
du6va.
du1v
du4ve.
2d1v
dv8a6la.
d4va6lel
dv2a1le
dva4ne.
dv4a1ne
dv4e6s
d2ve6va
dve1v
dve8ve.
d7vo
dvo8r
2d1w
1dy
dy9a
2dy1b4a
dy1b
2dy1f
dy5ke.
d6yk
dy2ke
4dykl4
4dykø
2dyl
dy4na.
dy1na
dy4ne.
dy1ne
4dy2nk
dynk6s5
dy3pe1s
dy1p8e
dy2p3r6
dy4ra.
dy1ra
dy5re1b4a
dy2r2e1b2
dyrle8ge.
dy2r1l
dyr1le
dyr3le1ge
dy4r5s4
dy2r5u
dyr5ø
7dys.
d2ys
dy8sa
4dy1s4e
dy6se.
dy4s7s
dyst7r4
dys2t
dy2t
d5z
8dz.
1dæ
dæ4rs4
1dø
4dø.
dø8d2s1
dø3g2r4
døg1
2døk
d5ø2k1n2
dø4ma
dø4pe.
dø1pe
dø4pen
d2ør
dø1r3i
2døs
d6øs.
dø2st3
d4øt3
dø9va
dø3vel
dø1vi
6dø6v1l2
døv8le.
døv1le
2døy.
døya8
døy8g
4d9ø2y1s
1d6å1d
då8d6s5
då8na.
då1na
1dåp
då8ra.
då5ri
d3ås.
dås2
då8sa.
då1sa
då7sem
då1se
d7åt.
2ea
e1a2b
e1ad
ea2d1i
ead5li5
e2a4d1l4
e1af
ea2gu
e1a2k
e4akr6
6ea6k1t
eak6se.
eak1se
e1al.
e5al1le
ea2l1l
eal8le.
ea4lov
e3al1te
ea2l1t
ea2lø
ea4m1
e3a3man
ea1ma
e5a2m1n
e1an
e2an.
ea4ne.
ea1ne
e6a5net
e4a1ni
ea4n7n
ea4nor
ea1no
e6ans.
ea6ns
ean3sl4
e1ap
ea4pe
e1ar
ea2re
ea5rer
ea5r4et
e2a5r4i
ear6ka.
ea2r1k
ear8ma.
ea2r1m
ear1ma
ear4ta.
ea6r1t
ea2s1i
e3asp2
e1a4s1s
e4a7ta1
e5a4tel
ea1te
eate6ren
e4ater
eate1re
ea4t3et
ea2t8h
eat6le
ea6t3l6
e8ato
ea5tri
eatr4
e1a6tt
e1au
eau6ga
eau6ge.
eau1ge
eau8ra.
eau1ra
eau6re
eau5s
ea4u8sa
eau8se.
ea2u1se
e1av
eav8la
ea6v1l2
eav8le.
eav1le
2e1b2
eba3d
e1b4a
eba4ne.
eba1ne
eb3be
e2b1b
eb4be.
ebe4d3e6
e1be
ebei7er.
ebe6i
ebe2i7e
eben6s5k
ebe6ns
ebe4rs6
eb4er
ebe1sku5
eb4e1s2
ebe4t5s1
e3b4et
e2b3h
e5b6lo
ebl2
eb1læ5
ebob3
eb2o
ebo4da
eb2o1d
ebo5e6rs.
ebo2er
eb6o1e
eboe4rs
ebo6kr6
eb4o6la
eb4ol
e6b5s
ebu4e5re
e1b2u
ebu1er
ebu5e2rn
ebu6et
eby4ta
e1by
ebø6n
e1b4ø
2ec
e4c4a
e4c4c
e1ce
e5ch2a
e3co2m
e2dad
e1da
ed5ad.
e5d4ag
eda8g4s5
e2dar
ed3a2r1k
ed3a2r1v
ed7d2e1la
e6d1d
ed1de
ed4dyr
ed1dy
e3de.
e1de
e6d6e6ge
e2deg
ed5eg4n
ed3e2i7e
e2dei
e4deks
ede8le.
ede1le
e4de4n1f
e8d5e6ng
ede6n5t
e6depr6
ed2e1p
ed2e4ra
ed2er
e4d2e1rø
e4desk
ede1s
ede4sl4
ede4s1m
e4des1n
e2de4s5p2
e2de4ta
e6d5e6tt
e8dé
e5d6ia
e1di
edi6a5ne
edi4e1ne
ed2ie
edi1en
e2di6gj
ed2ig
ed4is
edi3s4i
ed6i4s5k
e7div
edle6ge.
e4d1l4
ed3le3ge
ed1le
ed8o2b3
e1do
edok8se.
edok1s4e
e4dol
e2d1op
e2d1ov
e1dr4
ed2ra
ed3reg
e5drev.
edre1v
ed3rom
e6d5ryg
ed7ski
e8ds
ed3skr6
ed3s4la
eds1l4
ed2s1m
ed5t4a
e4d1t
ed3te
edt6r4
edu8a
e1du
ed7va
e2d1v
edvi6s
e3d6yk
e1dy
e4d5y2nk
edyr6ke.
edy2r1k
edyr1ke
e1dæ4
e3d6ø1r
e1dø
e3e2
e2ea7
e2e3b2
e2ed
ee3di
eeg4ga
ee4g1g
eeg6n
e4e5gå
eei4d
ee4i7ni
ee1k1e
eek4te
ee6k1t
e2e3la
e5e4li
eem6na
ee2m1n
ee4n
e5e4n1b2
een3in
ee1ni
ee2n6k
ee6n5t
e3ep2l6
e2e1p
e2e8ra
eer3en
ee1re
ee3ri
e4e2r3l
eer4me.
eer3me
ee2r1m
e6e5s1h
ee1s8ka
ees6ke.
ee2s1ke
e3e6t
eev4ne.
ee1v
ee2v1n
eev1ne
e1f
4e1fa
ef2a8l6s7
e5fal
e4fa1na
e4f3a2nk
e4fa2r1k
e3far
ef2a4r6s5
e2f7e1a4
e1fe
e2f5e1f
ef3fe
e2ff
3effek
ef3fo
4e1fi
e4f3id
e5f6ig
e5f4i2l1m
efi7ren
ef2i2r
efi1re
efisken8
efi2sk
efi8s1ke
4e3fj
2ef2l2
efle6ge.
e2fleg
ef1le
ef3le1ge
e1flå3
e2fn6
efo8bi.
e1fo
ef2o3b4i
efo4no
e3f2on
8efr2
ef2sj
e2f1s
ef2sk
ef2sp2
ef6str4
efs2t
e8ft
ef2ta
efta5r
ef2t5ei
ef1te
ef5ter
eftle8ge.
ef6t5l6
eft1le
eft3le1ge
ef4t1s4
6e1fu
efy4rs5
e1fy
efyr2
e3fæ1
4e1fø
efø5le
eføy9
2e1ga
e3gaf
e6ga1la
e7gam
ega4ve.
eg2a1ve
egde8l
e8g1d
eg1de
eg7de1s
e5ge1do
e1ge
eged4
eg1e2i9e
e2gei
e5ge4let
ege1le
e2gem
e3g8e1me
ege6n5s
eg2en
3e4gensk
e7geom
e2ge3o6
e2g6es2
ege5s2p2
eg9g2en
e4g1g
eg1ge
eg2g3l
eg8g9ut
eg1gu
egi3an
e1gi
eg2i1e2
e4gi1ko
eg2ik
e4gis4p2
e3g6lad
egl2a
e3g4led
eg1l6e
eg7le1sa
e5g4lit
eg1li
eg7ly
e5g6lø
6e2g1m
e9g8nag
eg1n
eg1na
eg4nem
eg1n4e
eg6no
e7g2nå
4e1go
e4go.
7e6goi
e1g2r4
egra6ns5
e7g8rø
eg1s4am3
e8gs1
egsa2
eg1s4ki
egs4ta
e1gø
egøy7e6ne
egøye4n
egø4y1e
4e1gå
egå4v4a
egå1v
egå4ve.
e1h
eh2e2a4
ehe2i9e
ehe2i
eh5er.
e4h5e1re
e4h7e2r5n
ehe4r4s
eh2og5
eh4o
eho1v2
ehy6re.
eh6y
ehø8va
e6hå.
eh6å
2e8ia
e9ia6k1t
ei9a4n
ei7ar.
ei4c
e8id.
ei3d8ar
e2i1da
e3i2d2ea
ei1de
e3ide3o6
ei9d2er
ei5det
5ei2d1f
5ei2d1g4
e6i2do
e1idr4
ei3d4u
e2i7e
ei2e5d
ei8eg
ei1e2n
ei9en.
ei2e1r3a
ei4e6r5t
ei6e1s
ei1f2l2
e2if
8eig.
e2ig
ei3ga
ei1g6e
ei6g2e1v
ei5gi
ei6g2ra
ei1gr4
ei6gu
4ei1i
ei5kaa
e2ik
ei1k2a
ei3ke.
ei1ke
ei6ke3e2
ei6ke5h
ei4kel
ei6ke5ri
ei4ket
ei1k1r6
eik2s3a
eik6se
eik4so
4eil
ei9led
ei1le
eil5e4g1g
e3illu
ei2l1l
ei3lo.
ei9l2oa
ei2l5op
ei5l2os
ei8l5s6
4eim
ei7ma.
e4i1ma
e5i2mag
ei4m5a6l
ei4med
ei1me
ei6mei
ei7men
ei4me5s
ei4m8et
eim9e6tt
ei6mo
8e1i8m1p2
ei4n3al
e6i1na
ei4na6ns
ein2an
ein5a6n1t
ei7na1re
ei6n1d
4e3in1du
ei5ned
e4i1ne
ei7nel
ei4n9f
ein1ga6
ei6ng
ein3gr4
e4i1ni
6ei6n4it
e2i2n3k4
e5inkar
ein1ka
4e1i4nn
ei4nom
e2i1no
ei4no1v
einsi9d
ei6n1s
ein3si
ein4s3l4
eins6o
e4in1spi
e1insp2
e4in7s6to
e6i2n1u4
ei3num
6e1i6n1v
e6i2n3ø
ei5or
e4io
e4ip
ei9pa
ei3pe
ei2p5s
2e2ir
eir9ak
ei1ra
ei7ren
ei1re
ei3ri
ei4r5s
2eis
ei5sar
ei1sa
e4i1se
ei3se.
ei2se5i
ei3sen
eiser2i9e8
eis2e1ri
ei3s4h2a
eis1h
ei4s2il
ei1si
e3i6sk.
e3i8s1ke
ei6s3kj2
eis3ko
ei3s4pe
eis1p2
ei4spi
ei4tek
ei1te
ei4t2e1ra
ei4te3re
ei2to
eit7ta
ei6tt
ei6t7ut
ei1tu
ei3tve
ei2t1v
ei6t7ø
ei4vak
e4i1va
eiva9r
ei9ven
ei1ve
ei3vi
ei8v5s4
e1j
ejo8en.
ej2o
ej6o1e
ejoe2n
eju6la
eju2l
4e1ka
eka1li7
ekal2ie6
eka6m6s5
e1kam
eka6ra.
e1ka1ra
e5ka2v1r8
e2kav
ek3e2i7e
e1ke
e2kei
e4k2ero
e4kes
e2k5e4ta
e6key1
e5k6ho1v
e4k1h
ekh4o
e1ki
e4ki.
4e1kj2
2e2kk
ekke7le
ek1ke
ekk9ist
ek1ki
ek1kj2
ek4kj2ø
ekk3l4
ek5kok
ekko5v
ek6ku
ek4ky6
e1kl4
e8kl6a3ne
e2k6leg
ek1le
e6klen
e2k5let
e3klu
e5k2læ1
ek2lø
4e2k1n2
ek9na.
ek1na
e3k2nek
ek1ne
ekne7s
e3k4ni7p
ek1ni
ek3no
e3k4nok
e3k2nu
e5k2nø
e1k2o
e4ko.
e2k2o7le
ekor8ds8
eko4rd
eko6te.
ek2ot
eko1te
e4kov
2e1kr6
e3k2ra
e3k4red
e7kre1f
e3kren
e4k5ret.
e5k4re4ts1
e5k4re1v
e3k2ri
e3k2ro
ek4ry
e3k4rå
e4k5r6å1d
ek3s4ak
ek1sa
ek4sal
3ek1s2am
ek3s2el
ek1se
3eksem
ek4s2ig
ek1si
eks1k
ek4ska
1eksp2
eks5pe
ek4sta
ek7s6tel
eks1te
ek3s1ti
ek4s4t4il
ekst5o
ek2st3å
ek4sæ
ek6t7a2r1m
e6k1t
ek2t3av
ek3te1f
ek1te
5ek3t2e1p
ek5t4es
ek6test
ek4tid
ek1ti
ekti5m
ek5tiv
4ekto
ekto3ri6a
ekto1ri
ek4t3ra
ektr4
e1ku
eku4le.
eku1le
e1k2v2
ek4val
ek6var.
6ekve
e4k5ve4d2
e4k5v2ik
ek4vin
ek6vis
3e2kviv
6e5kw
e5ky
eky6te.
e2kyt
eky1te
e3kæ
e1kø
e1kå
ekå6pa
2e1la
el4a4ga
e4l3a4g1g
e2l1ak
e5l2a1ke
elak8se.
elak1se
e4l7a6k1t
e2l1al
e8la1me
e4l3a4n1l
e6l5a2r1g
e6l7a2r1r
el7ar1ti
e4la6r1t
e5l6as.
e5la5se
ela4te.
ela1te
el5a1to
el5a6v1l2
e2lav
e6l3a8vs
elbo8da
e2l1b2
elb2o
elb2o1d
el4ch
e2l1c
el5do
e6ld
el4d4rer
eldr4
eld5s1le
el8ds
elds1l4
4eled
e1le
e4le1di
e4le1dr4
e3le3e2
ele8g5d
ele7g2er
e3le1ge
eleg8na
eleg1n
e7l2e2ir
e7l2eis
e5le1k6e
e2lel
e2lem
e5l4em.
e5lem6at
e4le1ma
5elemen
el8e3me
e5lemet
e3le2m1m4
e3le8m1p
4elen
el2e9na
e4l5e4n1h2
ele6ns3
e4lentu
ele6n1t
e5le5pa
e2l2e1p
2e3ler
ele8ra.
el2e1ra
e7lere4t
el8e1re
e6l7e2r1g
ele3sk
e6le1sku
e4le1ta
e4l2e1te
5e8lev.
ele1v
ele4vak
6e5le2v5n
el5fi
e4l1f
el9ga.
e2lg
el1ga
el5ge1le
el1ge
el5gi
el8g3s8
2e1li
e9l2ie
eli5e6rer
elie1re
e4l2if
e4li5g4r4
e3l2ig
e4l3i6nd
elin5es
el4i1ne
e4l1insp2
eli6n1s
eli6o6s
el4io
eli8ta.
eli1ta
eli4tet
eli1te
6e5li6tt
eli6v7en
eli1ve
e2l1j
e6l1k2
el6k5a6l
el3ka
el4ke5s
el1k4e
el4k2e3te
el3k1n2
el5la.
e2l1l
el5l6ar
el4led
el1le
el4leg
ell5e2i7e
el5ler
el3l6e4s
el6l2e1te
elli7ga.
el9l2ig
el1li
elli1ga
el4li1sj
ell7sa
el8l1s
ell5sk4
ell5s4l4
ell5sp2
ell5s1v2
el5l4ur
el4læ1
el5m4o
e2l1m
el5mu
e2l5n
2e1lo
elo6ka.
elo1ka
e2lom
e3l2o2m1m4
el3o2m1n
el3o6ms
e2l1op
e2l1or
e3l2o6r1t
e2l3ost
el2os
el4ot
elo7ve6rs.
e1lov
e6l5ove4rs
elove2r
e4l5o2v1n
el4pet
e2l1p
el1pe
el5p2h
elr2o4s
e2l5r4
el4sei
e8l1s
el1se
els5e2i7e
el3sen
el3se6s3
el3si
el4sj2e
el1sj
el4skal
el1ska
7elska6nd
el4s5kan
els5ka1re
el2sk2ar
el2s1l4
el6s1no
els1n
el4s1pe
elsp2
el4spr6
els4ten
els1te
el4s1ti
el4stj
el2sø
els5øk
el5s6å1p2e
el2s1å
elså2p
elta8le.
e2l1t
el3tal
el3t2a1le
el5te.
el1te
el6teg
el4t2ero
el4t5e6tt
el4t3op
el4tro
elt2r4
elt5rop
el2tu
e3lua
e1lu2e
e4lu4he
elu1h
e3luk
e7l4um
el3u6ng
elun6ge.
elun1ge
e7lup
elu8pe.
elu1pe
e1lur
e7lus
el3u4t5r4
elv3an
e8lv
el1va
el4ve3d1
el4vei
el6ve1re
el2v1r8
5elvs.
el8v1s2
elvæ6re.
el1væ
elvæ1re
4e1ly
ely8et.
el4y5e
elye2t
el5y6nd
2e1læ1
elæ5re3s
elæ1re
el9æ2r3v
e6l5æt
2e1lø
e4l3ø4r
e6l3øy.
e4l5øya
e2l5øyd
e6l3øyn
2e1lå
e2l1år
e5låt
4em.
e2mad
e1ma
em6a1fo5
e2ma1f
ema4ge.
ema1ge
e4ma2gr4
e2ma1h
ema4ke.
em2a1ke
e4m2a1ko
e4m5ak1ti
ema6k1t
e4ma1ku
ema6le.
em2a1le
5e4ma2lj
e4mam
e4ma1na
ema4ni.
ema1ni
e6m2a1nu
e4ma1re
em7a2r8m
e4ma1si
e6masku
e4m4a1ta1
em6at
ema3u4k
e2mau
em2a5ve
e2mav
5emba2l1l
e2m1b
em1b4a
3embed
em1be
emb4e6r5
3em3b4et2
6e2m1d2
8e1me
eme2i9e
em2e4li
em2el
e4me6nd
eme5tri
em2e1tr4
eme6trisk
eme4t3ris
e2m5e4v
e4mi1b2
e1mi
emi5e6r5t
em2ie
e4m2ig
4emin
emi5ni
emini6st
e4minor
em2i1no
e6m2ir
e4mi9sa
e2m7j2e
e5mju
em5le
e4m3l
emle6s
em5me.
e2m1m4
em1me
em5men
em8mi.
em1mi
8em6nd
e2m1n
em4ne.
em1ne
em4ned
em4nem
6em1ni
emo5nol
e1mo
emo2no
em2on
e2m1op
e2m3o4v
em4ped
e8m1p
em1pe
em6pe1ri
em2p3l6
em4p9lane.
em3p4lan
empl6a3ne
em6p2li
em1po3
em4pol
em4p2os
em4se5s
e6ms
em1se
emse8te.
ems2e1te
em4s5ju
em1sj
em4s1l4
em2s2n
em4sor
em1so
em1st8
ems4te
ems3u
emta8la
e2m1t
em3ta
em4t4il
em1ti
e3mug
e1mu
e2mu2k
e5my
em6y4k
6e1mø
emø8r
e5møy
emå4la
e1må
em6ål
emå9lan
emå8l4s
e2m1å8r
2e1na
e2n1ak
en3a6ld
ena5li
en3a2l1l
en5a6nd
e5n6a1ne
e6n7a6ng
e4n3a6n1t
e5n4ar.
en8a1re
e4na2r1r
en3a4s1s
enat6s5p2
en2a4ts
ena2v
en3a1vi
4e4n1b2
enb2e9na
en1be
e6n4d5a6nd
e6nd
en1da
en4d6ek
en1de
en5d4e5l
endelø7se
en2d2e1lø
en5de1mi
en2dem
en3d2er
en4desl4
ende1s
en4d5l4
1endr4
en4d3ro
end3st4
en8ds
en7dø
end2ø4r
endø1r5e
en5e4g1g
e1ne
e2neg
eneg8ga
en3e2i7e
e2nei
en5eks
e2nek
e3ne6k5t
4enem
2e3nen
en5e6ng
3e4n2e2r2g
e4nesk
4ene1v
ene7ven
en1g6a
e6ng
en7gar
en4gem
en1ge
en4ge1ri
en3g2er
eng2e6r5u
en4g1l6e
eng1l
eng4les
en1g4r4
eng3s4e
en8gs1
eng5so
eng5sp2
engs4ti
en4gut
en1gu
en4g5å
4enhj
e4n1h2
e4ni.
e1ni
e4nie2r1k
en2ie
e2n2if
e5n2ir
en4k2e1ra
e2nk
en1ke
en4ke1ri
2en1na
e4nn
en9nal
2en1ne
en4nem
en4ne3s6t
en2ne4s5v2
en1n2i
en1n2o
enn4sj
en6ns
enn5sta
ennst6
en1n7ø
2e1no
e2n3ok
en3og
en3om.
e2n1op
e6n5o4rd
2e6n3p2
en4pås2
en4rem
e2n1r2
en3re
en5sab
e6ns
ens5af
en6s7a6ker
en3s4a3ke
en4sek
en1se
en4s2e1li
ens2el
5en3se2m1b
en2sem
en4ses4
en2se3u
en3si
en3s2i8de.
ensi1de
ensi5e6r5t
ens2ie
en4sim
ensle7g
ensl4
ens1le
ens6le1v
en7s6o2p1t
en2s1o2p
en1st
en6s7tal
ens4ter
ens1te
ens4ti
ens7u6nd
en1su
en2s4ve
ensv2
ens1vi6
en2sø
ens3øk
ens7å
en5ta.
e6n1t
en4tap
en5te.
en1te
en4t2ec
en4teg
en4tel
en3ti
en5t2i1e2
en5t2ig
en5t2ik
en9t4i1ma
en7timen
enti1me
en4to4r
ent5o1ri
ent5rab
entr4
en4tre
ent5ren
en4t5rol
entro1pi5
en4t3rå
en6tu4l
entun6ge.
en2t1u2n
entu6ng
entun1ge
3entus
2e1nu
e2n7ul
e4n5ur
2e6n3v
4en3w
e4ny.
e1ny
e2ny1b
e6nyr
e2n2y1s
e4ny1ta
4e7næ1
enæ8m
enø4k
e1nø
e2n3øv
en5øyd
enøy1
4enå
en1å2s2
en6ås.
e3o6
e2o3a
e6o1b4e
eo2b2l2
e2o5d
eod8de.
eo6d1d
eod1de
e2og1
eo7gr4
e2o1i
e2ok
e3oks
e2ol
e4o7lo
eo6m1s4
eon8de.
e2on
eo6nd
eon1de
eo5ne
e7o2n1k
eon4kl4
e3o6n1t
eo2p
e2o3pa
e2o5pl6
e2o3po3
eop6p5r6
eo2pp
e2o3pr6
eor4da
eo4rd
e8o9re
e2o1ri
eo4rid
eor2i5e6
eo4r2ik
e6o4ris
e4o1ro
e2o1s
eos6l4
e2o1u
2e1p
epa3t
e1pa
e6p2e1no
e1pe
2e2p2e6p
ep2e2r5r
e3pe1s
epe4st
e4pe1ta
e6pe2u
3e2pid
epi6ka.
ep2ik
epi1k2a
epi7k1r6
3e2piso
ep2l6
e8plen
ep1le
ep5le1ne
ep5ler
eple9s6
3eplet
epo6et
e1po
ep6o1e
3e2po1ke
ep2p2s
e2pp
ep6sem
e2ps
ep1se
ep4ses4
ep6s5lu
epsl4
eps1t2
ep2su
e3p2sy
ep6tin
e2p1t
ep5ti
ept6r4
2e1ra
e4r3a2b1b
era8da.
er6a3d2a
e4rael4
er2a1e2
e2raf
e6r3a2ft
e4ra4g
e2r1ak
e3r2a1ke
er2a5k1l4
eral3u
e4ra1ma
er6a6na.
era1na
e5r4an9de.
era6nd
eran1de
e5r4a7ne
er7an1li
e2r5a4n1l
era4no
er5ape.
era1pe
er5aper
e4r5a4pin
era1pi
e4r3a1po
er5ap1pe
era2pp
e5r4ar.
e4r1a2r1g
e5ra1s2a5ne
e4r4a1sa
eras6an
e5rasar
e4rasj2e
era1sj
e4rask
e6ra1so
e4r3a4s1s
er6ast
e4ratek
er6a1te
era7tor
era1to
e4ra1t4r4
er5a6tt
e2rau
e4r5au4k
erau9ken
erau1ke
erau8s
er3av.
e2rav
er3a2v1h
e4r5a6v1l2
e3r6a2v1n
er3a2v6r8
er3a8vs
4e2r1b8
erbi9t2i
er1b4i
erbo8da
erb2o
erb2o1d
erbo8de.
erbo3d4e
er8byl
er1by
er1d2e
e4rd
er4ded
erd2e6n8s3
er4dis
er1di
er1dr4
erd4ra
erd8re
erd4skj2
er8ds
erds3t4
erd4sto
erdsto8ga
erd2s3t2o5g
er3d4v
e1re
e4r3e2ff
ere1f
e4r3e8ft
e4r5e4g2en
ere3ge
e2r3eid
e2r3e2i7e
er4e6ka
ere6ke.
ere1ke
e4r3eks
e6r5ek1te
ere6k1t
e4r3e4le
er3e4lit
er2e1li
e6rel1li
er2e2l1l
e4r3e8l1s
e9r4em.
e2rem
e5re2m1m4
e6r3e2m1n
er5enden
ere6nd
eren1de
e4r3e6ng
e4r5e4n1h2
e3re4o7
er8er.
e1rer
e4r3e2r1f
e5re1ri
e2r3e4s1s
ere4t
e5ret.
e4r3e1ta
er4e2t6h
er2e5to
e7re4ts1
6ere6tt
eret6ts5
e4r3e4va
ere1v
e4r5e4ve
e4r3e2v1n
erev6ne.
erev1ne
e8ré
5erf2a1ri
e2r1f
er1fa
er3far
6er1fi
4erfr2
2e2r1g
erg5e8lv
er1ge
erg2i3f
er1gi
er6g1li
er4go.
er1go
erg5ret
er1gr4
erg5sko
er8gs1
erg5sp2
2e2r1h
erhø6re.
erhø5re
e1ri
e4riad
e4riak
eri2b3b
e2ri1b2
eri3b3l2
e4rice
e2rid
e5rid.
e5ri8d1n
eri4kat
er2ik
eri1k2a
e6ri2k1n2
4eril
e7ri6ma.
e4r4i1ma
e4r3i6nd
e3ri6ng
6eringar
erin1ga
ering6si
erin8g2s1
e4r3i4nn
e2r3i6n1s
e2r3i6n1t
e5rio1ri
e2rior
er4io
e5ris.
e4ri1sa
eri3se
e4riso
e6ris1p2
e5ri6st.
e7rista
e5ri2s1te
e4ri5st4il
eris1ti
e4ri5s6to
e4ri5sø
e4ri3te
e4ri5t6o
e6r7i1vo
er7j2es.
er1j
er2jes
erj2e
4erka
e2r1k
8er1ke
erker2i6e
er3ke5ri
er4ke1sa
er4k2e4se
er4k2e3te
er6k5e6tt
3erk2læ1
erkl4
6er2k1n2
4er5k4o
4erkr6
erlan6d7as
e2r1l
erla6nd
erlan1da
erle6ge.
er1le
er3le1ge
erle7s8t
er3me
e2r1m
ermo8de.
er1mo
er5mo1d4e
erm2o1d
er6m1s
ermå6la.
erm6ål4
er1må
ermå1la
2e2rn
er4nad
er3na
er4ned
er1ne
er5ne1de
er6ne3e2
er4n2e3ro
er4nest
erne1s2
er2no
er3no5b
2ero
e3r2oa
er3o1b2l2
5er2o4b7r8
e1r6o1e
er3o2ff
e1r2og
er5ok7s
e1rol
e2r3o1ly
e1rom
e4r5o2m3k2
er2o5mo
e1r2on
er5o6nd
ero1no5
er1op
e5r2op.
e7r2o6pa
e5r2open
ero1pe
er1o2r
e5r4or.
e3r8o3re
e7ro4sa
er2os
3ero1sj
e4r3os2l4
ero4ta
er2ot
er1ov
2e2r1p
2e2r1r
er3ra
er4r2a1e2
er4rel
err6e
erri1e9n
er3ri
err2ie
6er1sa
e4rs
ersa8ka
er3sak
ers6al
er3s2e1p
er1se
6er1sj
er1sk
er3ska
er2s4ka.
er8sk2aran5
er2sk2ar
ers1ka1ra
er9s6k2arar
er4s5kor
er1sl4
ers4la
ers6led
ers1le
ers4mi
ers1m
er5s1ne
ers1n
6er1sp2
ers5te6n6s
er1s2t
ers1te
ers5ter
er7s2te1v
er1su
4er1sv2
er4sva
ers4ve
er1så
er4s5å2r
2erta
e6r1t
er4ta1re
er4t4e3s4
er1te
er5tia
er1ti
er3ti1b2
ert2i4e2
er5t4il
er4ti4mo7
er4top
ert5rol
ert2r4
4er4ts
ert3sa
ert4y6e
ertå7ren
2eru
e1rua
e5r2ud3
e1rui
e4r1u2k
e3r4um
e4r3u6ng
e4r3u2n6i
e3rup.
er1u8r
eru1re8
e1r4us
er5ut.
e5r4uta
e3ru1t8e
er5u8t1n
er9ut9o
e4r1u4t1s4
er7u6v
erve1le8
e2r1v
6ervi
er8v2s3
ervæ6re.
er1væ
ervæ1re
e1ry4
e2r3y1a4
ery7e7ne
er4y1e
erye2n
e2r1yr
er2ys3
e1ræ
e9r4æ4re
2e1rø
e4r5ø2k1n2
e4r1øko
e2r1ø4l
e4r5øn
e5r6ø4nn
erø8ra
e1rør
e6r5ø4r6s
e4r3øya
e4r5ø4y7e
erøy4n
e4r5øys.
erø2y1s
e6r7øy4s3k
2e1rå
e9rå.
erå4da
er6å1d
erå8de.
erå1de
er4å6e
erå4k
er5å1ke
e4r3å4l
e2r1ån
e2r3å4p
e2r3å4se
erå1s2
erå6t
e6r7å1ta9
e6rå1v
2es.
e1sa
e2s5aa
e2sad
esag6
es4al
e7s8a6la.
esa1la
esa9met
e1s2am
esa1me
e4s3a6n1t
e6s5arab
e1sa1ra
e4s5a2r1v
esb2i9e
e8s9b4
es1b4i
2e1se
es5e2ge
e2s3ei4d
es5eie.
ese2i7e
e2s3e2ig
esei8ge.
esei5g6e
e3sek
e4se1ku
e3se8l5s4
es2el
e3s2en
e5s2er.
e5s2e5r4e
e5s2e1ri
e4s3e2r1k
ese5s2
e3se1st
e4ses1v2
ese5tas
e2se2t6a
ese4te.
es2e1te
e5s2e1tj
e9s4e4ts1
e4se1u
e4sh2a
es1h
es6har
esh9ar.
es4h5er
e6she
e4shi
e1sh4o
e7s8hop
esi6ar
e1si
esi5e6r5t
es2ie
e4s2il
e5si1li
es3i2l1l
e4s3i6n1s
esi6v
esi3st
es2is
e2s3jor
e1sj
esj2o
e5skab
e1ska
es6ked4
e2s1ke
es4ke3e2
es4ker
8eskil
e1ski
e4s1ki6ng
e5sk8in6n1s5
es2ki4nn
e1skj2
e8s9kjer.
eskj2e
es4kjæ
e6skjøn
eskj2ø
e2s5k4n2
e5sko.
e4s7k2oa
e3s2k2ot
e1skr6
e1sku
esku5et
e1s4kue
es3kvi
e2skv2
e5sky
e6s1kyr
e1skø
e7skå
eskå7r
e1sla
esl4
es4lek
es1le
es4let
es7let.
es7le4ts1
es4lit
es1li
e4s5lok
es4løk
es1lø
es4løv
es4m2a1ke
es1m
es1ma
e7s4me6tt
es1me
e8s9na.
es1n
es1na
e4s4ner
es1ne
es2no
es4nu
es2ny
esnæ5re
e2s1næ1
4e1so
e2s1o4b
e2s3o1d
e2s1of
e4s3oks
e2som
e3s2o2m5m4
e4s3o2ri
e1s2p2
espa9ra
es1pa
e4s3p6as
e4s5pe.
es1pe
e4s4ped
es5pe2r1m
e2s3pl6
e4s4p5le
e2s3pol
es1p6o
e3spor
es3pun
es1pu
es4sed
e4s1s
es1se
es4se3e2
es4seg
esse7i
es6sen1di
esse6nd
es4s5e2nk
es4ses
es4s2e1te
ess5e4va
es2se1v
es4s3ja
es1sj
es4sj2e
es4skr6
ess4let
es2sl4
ess1le
es2s3om
es4s2os
es4s1pa
essp2
ess5ti2l3t4
ess2t
ess1ti
esst4il
es4stol
ess5tor
ess9tua
ess5tue
esstu8en.
esstu1e2n
4essu
es2s1v2
es7så
e7s6t2ad
es5t8a1e2
es7ta6k1t
e6s9ta4la
e8s1t8a1ne
e5sta6ns
es5ta6n1t
e2s3tap
e5s4ta6r1t
e3stat
e4s5tato
e4s3tau
e7stav.
es2tav
est5a8vs
e6s5te.
es1te
es4teg
est5e2i7e
es2t4ei
est5e2ig
es4tek
es4tel
e5ste2l1l
es5te8l1s
e4ste1ma
es2tem
e6st8e1me
e4ste1mo
es5te8m1p
6esten
e7s6te6ng
e6s5te2nk
e8s7t8er.
e5s6te2r1k
ester1ne8
est2e2rn
e2st4e3s4
es4test
es4ti5en
es1ti
est2i1e2
e3s2t2if
es4t2ig
e3st2ik
es4ti2kk
5e6s5t4i1ma
e2s4tis
e4stiv
e1stj
es3t6on
e4s3t2og
e3st6ok
e3s4tol
es6to1ne
e6s8t5o4rd
est5o1ri
es5trak
estr4
est5re5ne
est5rer
est9ré
es5t6ri1b2
e5str6yk
e2stry
e3strå
e1stu
e5s4t2ud
es5tus
8e1stå
estå5ren
estå6s2
est7å1se
e1su
e2s5u4t1
esva5re
esv2
e5s4vek
e4s5v2ig
e5s4vi6k1t
esv2ik
e2s3vis
2e3sy
esy4na
es2yn
e4s5y2t
es5øvi
e1sø
e2søv
e8s7ø4y1e
e1ta
4eta.
e4t8a1e2
e2taf
et6a1fo7
e3tak
e6t2a1ko
e3tal
eta3la
et8a4la.
et5a6ld
e3t2a5le
4e5ta2l1l
etal6list
etal1li
etal8l5s6
et2a8l4s7
e2t3a2m1b
e6ta6nd
et2a4nen
e1ta1ne
et4ap
e9ta1pa
e5ta1pe
3etap1pe
eta2pp
e1t8ar.
et8a1re
e4t5a6r1t
5e6tas2je5s4
eta1sj
etasj2e
1e2tat
4e5t4a1ti1
e5tato
e3ta6tt
e4t5aun
e1tau
e8tax
e2t1c
2e1te
e4t3e2i7e
e4t4e1ka
ete4ma.
e2tem
ete1ma
e5teran
et2e1ra
et2e5ru
ete1s5i
et4es
e4tesl4
et8et
e5tet.
e7te4t2s1
4eté
4e2t1h
2e1ti
e3tit
2e1tj
e7tjer
etj2e
e2t3j2ø
6e6t3l6
e2t5m
2e8t1n
et6ne1v
et1ne
etni6ng4
et5ni
etnin8g6s7
2e1to
eto4er
e1t6o1e
e4t5op2p1d4
eto2pp
e4t3o4rd
e4t2ot
2e1tr4
e5t4rad
e7t6rak
8etre
e5t2re3e2
e5tre1f
e4t3ris
e9t8ru.
et5r4um
e4ts1
et9s2ar.
et4sku
etsku8le.
et3sku1le
ets7l4
etsl4a8ga
et2s1næ4
ets1n
et2sø
et4tak
e6tt
et1ta
et4tal
etta6le.
et3t2a1le
ett5a2l1t
et4ta2nk
et8tap
et5te.
et1te
et6tei
et6te2l1t
et4te2nk
et3ten
et4te4nn
ett2e1ra4
etter5at
et5t6e2r1f
5ette2r1k
5ett2e2r5r
et4t5e1sk
ett4es
2et1ti
et6ti5a
ettian8
et4t4il
et2tj
ett2o5a
et4tr4
ett3re
ett4ski
et6ts
ett4s1ti
ett1st4
et3tug
et4t2ur
ettvi5se
et2t1v
et2ty
2e1tu
5e6tui
etu6na
e6t5u1n6i
2e3t2v
e4t3v4a
e5t6va6ng
2e1ty
4e2tz
4e1tæ
etæ3ra
e1tø
e1tå
etå5re
e1u
eu4a
e8uf
eug8l2a
eu4gl
eu4h
eu4ka.
eu1k2a
eu2ke
e4u5kem
eu7kr6
eu2l
eum2
e3u2n
eun4ge.
eu6ng
eun1ge
eu5nu6
e4u1p5a
e6u4p5k4
e2ur
eu6rat
eu1ra
eu1re4
2eu1ri
e3u2rn
eur8na
eur6ne.
eur1ne
euro1
3eu2rop
e3u6r1t
e2us
eu4si
eu1ta
e3u4t1s4
eu6t7t
e4u3z
e1v
evad6r4
ev5ak1ti
e1va6k1t
eva4la
evan6n4s
e1v4a4nn
e4v5anta
eva6n1t
e4v2arar
eva1ra
e4v1a2r1b8
e4v5a2r1k
e6va6t1f
6eve2d1
eve5d6a
eve5del
eve1de
6eveg
e5ve4g1g
ev2ei6s5
6eve4nn
e1ven
eve6n5s
5e4venty
eve6n1t
e4ve1ny
e9v8er.
e5v2es.
ev4es
e5ve1ta
ev4et
e5vev.
eve1v
ev2i4e
evi5e1re
evi5e6r5t
ev2i6n4s
evi4se.
evi1se
evis3t
e2vj
ev2na
e2v1n
ev2n5a4k
ev7nu
ev7o6ms
e2vom
e4v1re
e2vr8
e7v6ri.
ev9rå
e8v1s
evs8v2
evta8la
e2v1t
evta1
ev5t4i4
e2v3un
e1vu
e5vy2r1k
e2vy1
e2vø
ew3a
ey1
eybal4
ey1b
ey1b4a
e1y2d
e6y5k
e1y6ng
e1yr
eyr6ka
ey2r1k
eyr4ke.
eyr1ke
e1y2t
e7y1ti
ey3tr4
e3zi
e1zu
e1æ2
eær3
e1ø4
eør6na.
eø2rn
eør3na
eøy4
eøy9a2n
eøy7e4n
eø4y1e
e1å6
eåk4
eån8da
eå6nd
eå6t8t
é1a
é1b
é1d
éd2ø4r5
é1dø
é5e8
é1f
é1g
é1h
é1i
é1k
é1l
é1m
é4n5e
é5n6et
é5o
é1p
é1r
é2r1j
é1s
é1se2
é1t
é1v
éva8la
évo8re.
év8o1re
é5å
è1r
è2red
è2re1f
è2rek4
è4rener
ère1ne
è2r2e1p
ère3s2
è4rest
è2re1v
è6v4es
ê8lan
ê6ra
ê1re
êr7o
êr9sl4
ê4rs
1fa
4fab2o
f8ac8
fa1ci
fa3de
fa6de.
6f1a2d1m
fa4f
f4a2g1a
fa5g2er
fa1ge
fage4t
fa2g5e1ti
fa2gi
f4a2go
fa2gr4
6f5agro
fag3sk
fa8gs1
fa2g1u
fai5
2fakr6
fakse9t
fak1se
fakta3
fa6k1t
fa1ku
5fal
fa1la
fal6kes
fa6l1k
fal1k4e
fal4k3l4
fal6le4rs
fa2l1l
fal1le
fa2l5m
fa4lo
fal4sk
f2a8l1s
4fa5mo
f6a4na.
fa1na
7fa6n5d
5fa1ne
1fan1fa5
fa4n1f
fan8g4s1
fa6ng
fangs2t7e1v
fangs1t6e
4fa2nk
2f3a4n1l
fa6n5s6
fan7te1sk
fa6n1t
fan1te
fant4es
fan5ti
fan3to
fan4try
fantr4
6f2a1nu
2fap
3far
9f8ar.
fa3ra
fa4ra.
fa4re.
fa1re
fa4res
far4gel
fa2r1g
far1ge
far4g6es
fa4rit
f2a1ri
f2a2r5n
far6skj2
f2a4rs
fa6r1t2
far4vel
fa2r1v
4fa1ry
f4a7sa
fa4s4el
fa1se
fa4s2e1ru
2fas1l4
fast3r4
fa3tal
f4ata1
fa4te.
fa1te
fa6t7e4rs
f4ater
fa1to
fat2r4
fa3t6re
fav5ne
f6a2v1n
fa1vø
2f1b
fba4ne.
f1b4a
fba1ne
2f1d
1fe
3fe.
2f2e1a4
5fea.
fe5a6l
2f2ec
fe4da
3fe1de
fe2dr4
fe3d2ra
fe3d4ri
fe7e1ne
fe3e2
fee4n
2fe1f
2fe3g
2fehj
fe1h
fei5e2n
fe2i7e
fei5er.
feig4de
fe2ig
fei8g3d
fei5g6e
feil5e1s
f4eil
fei1le
fei4li
9fe2in.
7fe4i1ne
fei7ter
fei1te
8fe1j
2f4e1k4a
4fe1kl4
2fe1k2o
4f2e1kr6
fek6t4es
fe6k1t
fek1te
fek2t5e1v
fe5lag
f2e1la
f4e5len
fe1le
f2e5li
fe9l2i7e6
6fel2ik
fel3l6e4s5
fe2l1l
fel1le
fel7læ1
2f2e1lo
fel5ok
fel9ta.
fe2l1t
fel4tek
fel1te
fel4ti
fel4tra
felt2r4
fem5ak
fe1ma
fe2m9b
fem9ne
fe2m1n
fe4mo
fe6m5s
fem4tid
fe2m1t
fem1ti
f6e2m1ø
5fen.
4f2e1na
f1end9r4
fe6nd
4fe1ni
5fe6ns
fentleg5
fe6n1t
fen6t5l6
fent1le
2fe3o6
2f2e3p2
5fer.
fe6ral
f2e1ra
4feram
fe5ras
fer6at
fer4dam
fe4rd
fer1da
fer5d2e
4fer2ea
fe1re
4fereg
fe4rek
fer6en
fe7r8er.
fe1rer
fe3r5e4rs
fer2i6e
fe1ri
feri8e5ne
feri1e4n
7f2e2rn
f2e2r2r
fer2s9k2ar
fer1sk
fer3ska
fe4rs
fer2s7ke
2f2e1ru
3f2es.
2fe1si
2fe1sk
fe2st
fes9t6i
fes8t3r4
fe3s9tum
fe1stu
fe6st9ø
4f2e3sy
f4e6ta.
fe1ta
4fet4ap
fe4te.
f2e1te
4fet2ea
f2e5ti
4fet4il
2f2e1t6r4
fet2t5j
fe6tt
fet6t7s6
4f2e4ty
2fe1u
2fe1v
fe4ven
2fe1ø4
fe6øy.
feøy4
2fe1å6
1fé1r
2ff
f7fa.
f1fa
f4fab
f2f3a2g
f4f3ak
f5fa7l
f6fa4nn
f4f1a2r1b8
f3far
ffa4r6d
ffa7re
f9fas
f6fat
f8f9au
f2f3av
f2f2e1b2
f1fe
f2fe3d
f2fe3e2
f6fe1h
f2fem
f6fer1di
ffe4rd
ffe4rer
ffe1re
f2fes
f4f2e5ti
f4f2e1to
ff5eve
f2fe1v
f2fi1b2
f1fi
f4fice
f2fid
f9fi1en
ff2ie2
f4fi4nn
ff5i6n1t
f2fip
ffi5s4
f2fj
ff3la
ff2l2
ffl4a8ga
f2f9leg
ff1le
ff3li
f2fo
ff4ol
f2f3re
ffr2
ff1ro
ff5sl4
f2f1s
f2f5t4
ffu6r
f1fu
ff5ut
f3fæ1
f4fø
2f3g2
2f1h
1fi
fia7l
fia4n
4fiap
fi4as1
fibi6en
fi1b2
fi1b4i
fib2ie
fi9cen
fi1ci7
fi7di
f2ie2
fi1er
fi7e6re
f2i4f
1fi5fi
fi5fo
fi7f2l2
f6ig
fig4h9t
fi6g1h
fi7g6r4
4fi3h
fi9k2a
f2ik
fika1li7
fikal2ie6
fi7ken
fi1ke
fik6ka
fi2kk
fik6k5n2
fi5ko
fik7sa
4fi1ku
fi4la.
fi1la
fi3li
fil6lel
fi2l1l
fil1le
fil6le1ri
fil4l6es
fil7l2e3ti
fil8m7at
fi2l1m
fil1ma
fi1lo
filt6re
fi2l1t
filt2r4
fil7t3res
fil4tri
4fi1læ1
fi2n5a6r1t
f6i1na
fi6n5d
fi4ne.
f4i1ne
fi5ner
fin2g3r4
fi6ng
6fi4n1j
fin2n5ei
fi4nn
fin1ne
6f3innsa
fin6n1s4
f2i4n3o
fi9nor
2fi6n1s
fin6sle7g
finsl4
fins1le
fin6t4e5s
fi6n1t
fin1te
f6i2nu
7f4io5
f2i2r
fi3ra
fi6ra.
fi9re1ne
fi1re
fi5r2es.
fi3ri
fi5s4a
4fisc
fi6se.
fi1se
fi5se4r6s
fi8si.
fi1si
fi2sk
fi6ska
fis2k5a6d
fis7kal
fisk5a6nd
fi4s6ka6r1t
fi2s3k2ar
fis6k5e6nd
fi8s1ke
fi2s4k3l4
6fi1skol
6fis1ku
fis5ti
6fistu
fiti6me.
fi1t2i
fiti5me
4fi1tr4
fit6t5s
fi6tt
fi7ty
fi4ve.
fi1ve
1fj
f1jeg
fj2e
fjel6le6nd
f2jel
fjel5len
fje2l1l
fjel1le
f5jen
fjer5ne
fj2e2rn
fje4sk
fje2t3
f5ju
fjæ1re5
fjæ4res8
2f5k6
f2l2
f5lag.
f6la4g1g
fl2a7ke
f4las
2fleg
f1le
fle6i
f4lek
f6lel
fle4sl4
f4le6tt
f3le1v
fl4i7ne
f1li
flis7t
fli6t9t8
1flo
flo9ga
f1l2og
f2lok9s4
flo2m3
fl4o6r5o
flo2s6k2l4
fl2os
f6lu
flue3s
flus8
fl5ut
3fly
fl2y3s4
f3løn
f1lø
flø5s
1flå
flå9sa
flås2
flå7se
2f1m
fn6
1fo
2fo.
fo9ar
f2oa
6fo6b1s
2f6o1e
4fof
foi7la
fo8ke.
fo1ke
fo6la.
f4o1la
fol4dr4
f2o6ld
fo4le.
f2o1le
fo5led
fol6k5v2
fo6l1k
fol9ler
fo2l1l
fol1le
fol5l2e1se
fol4l6es
folke5s6
fol1k4e
fol5li
fo4lu
3f2on
fo4na
fo6nd2
fo5ne
fo3n4i2d
fo1ni
fo4nin
fo6n1s2
f1op
for1a
for9dre1v
fo4rd
fordr4
for7dro
for5d6u
fo6re.
f8ore
for9ei
for7e6n
for5e6n1t
fo2r5e4s1s
fore1s2
for4et
for9e8te.
for2e1te
for9e8ten
fo2r9g6
fo2r1h6
fo4r9in
fo1ri
fo2r1k8
for1lo7v
f6o2r1l
forl6o
forlø9pa
for1lø
forlø9se
formo9r
fo2r1m
for1mo
fo2r3n
f4oro6
fo2r5p
for3se
f2o4rs
for3s2i9da
for3si
for3s2i7de.
forsi1de
for3s4m
for1s8t
for9s1te
for1s4v2
forta8le.
f2o6r1t
for3t2a1le
for5t6e
for4t3e2i9e
for7t6i
fort2i5da
for3tid
for3t4v
for3u4l
for1u
fo2r7v6
forva9re
forv4a
for5æ4
f2or5ø4
for9å
fo9se
f2os
fosf8o5re
fo2s1f
1fos5fo
4fo1sj
fo4ta
f2ot
fo6te.
fo1te
fo1to5
fo4t6ok
fo4tom
fo6top
fo4tor
fo4t3s
fots6v2
fo6t3t
4f1ov
3fô
2f3p2
fp6o6e
f1po
fr2
1fra
fra5l
fra4m5e
fr2a6mi
framma8ne.
fra2m1m4
fram1ma
framma3ne
fran3ko5
fra2nk
fran7se
fra6ns
fra7r8
fra1s
fra5se.
fra1se
fra7s2e1p
fras2i8e
fra1si
fra7sk
fras6p2
f2ra7v
fra7v6r8
f2re.
fred8s2el
fre8ds
fred1se
freds5t4
f2re4e3
8freg
f4rek
f2re4m5
fremma8ne.
fre2m1m4
frem1ma
fremma3ne
fre6m1s4
fre7ne
f2re6sk
fre5s7ko.
fres5k6o3e
fre8s9v2
fri5a6re
fri1ar
6f5ri5di
fri4e2r1f
fri1er
fr2ie
f1rin
f4ri1s6ka
fri5s4p2
f4ri5s6t4il
fris1ti
frite8re.
fri3te
frite3re
friti8me.
fri7t2i
friti5me
6f1r4oc
fro7f
fron3t2a9le
fr2on
front6a
fro6n1t
fro8st
fr2os
fr4us1
fruta6le.
fr4uta
fru3tal
fru3t2a1le
f1ryt
f6rø5b
fr1ø7ko
f2r1å9r
fråve4
frå1v
2f1s
fs2h
fsh4o6
f1si2
f5s4ju2k
f1sj
f2sl4
fsle6ge.
fs1le
fs3le1ge
f4s1m
f2s1n
fs7ne8
f2s5ov
fs2t
f2s3tab
f6s5tan
f4s9v2
2ft
f1ta
f3ta.
fta7f
ft1ak
ft8a8la.
f1t3a2lg
f2t1am
f4ta2na
f7ta1ne
f2t3a4n1l
f5t4ar.
ft8as4
f6t1av
f2t2ea
f1te
f3ted
f4te3e4
f2teg
f2tei
f2tek
f6tem
ft2e4na
fte6n3d
f4t3e4n1h2
ften5s6v2
fte6ns
f2te3o6
ft2e4r5a4
f4terin
fte1ri
fte4r5s
f4test
ft4es
f4te1ta
f4t5e6tt
f2t3i6nd
f1ti
f2t5i4s
f6t5l6
f8t7n
f2t1o2
f5t2og
3f1t2on
ft3r4
fts5a2l1t
f4ts
fts3ei
ft1se
fts5e4rs
fts5e2r1v
ft2s1i
fts1k
ft2skaf
ft1ska
ftsl4a6ga
fts1l4
ft5s4lan
ftsle6ge.
fts1le
fts3le1ge
ft3sto
ft1st4
ft6s5top
ft5s6tri
ftstr4
ft5stø
fts1u
ft2s1ø4
f6t5t4
ftta8ka
ft1ta
ft1u
1fu
fu8ga.
fu1ga
fug9le.
fu4gl
fug1l6e
fug6l7eg
fu6le
fu2l1l3
ful9lar
ful1le6
ful9le.
ful4len
fullen8de.
fulle6nd
fullen1de
ful4ler
ful7l6e3s4
fullfø7re
ful4l1f
full1fø
fullf8ør
fu4nn2
fun6ns3
funnsl4a8ga
funnsl4
fu6ra.
fu1ra
fu6re.
fu1re
fu9ret
fu7ro
furu1
fu7s2el
f2u1se
fu6sk
fus6o7
fu4s5t
fu2t4h
fu1tu1
4f1v
fva8la
1fy
fy5la
fyr2
fy8ra.
fy1ra
fy4r5a4b
fy1re
fy4re.
fy2r7k
fyr8ke.
fyr1ke
fy6r5t
f2y3s
fy1sa7
fy8sa.
2fæ1
1fø
fø4da.
fø1da
fø4dek
fø1de
fø5den
fø4de1s
fø5d6es.
fø1f
2føk
fø4le.
fø1le
f8ør
fø1r6a
fø5rar
fø4re.
fø1re
fø4re1s2
fø7re6s7v2
fø8r2e1te
før6et
før6tin
fø6r1t
før1ti
4få
få7ren
få7ret
få5ri
får7u
få7v4a
få1v
1ga
4ga1b4a
ga1b2o
6ga1b4ø
g6a3da
ga4ded
ga1de
4g1a2d1g4
2g1a2d1m
4g5adr4
ga4e5k
g2a1e2
6ga1flo
gaf2l2
g3a2ft
6ga1h
6gak
g1aks
gak8ta.
ga6k1t
gak8te
g2a3kv2
ga1la
g8a4la.
galei5
g2a1le
ga7len
gal4l6e3s4
ga2l1l
gal1le
4gal1li
5ga7lo
ga4ma.
ga1ma
4ga2m1b
ga5mer
ga1me
gamm2e6l5
ga2m1m4
gam1me
6g4and.
ga6nd
6gan5den
gan1de
9ga1ne
gan5g6en
gan1g4e
ga6ng
gan4g5j
4ga2n5k
2g1a4n1l
4ga4nn
4gansa
ga6ns
4g1an1sv2
4g5antre
ga6n1t
gantr4
ga6pa.
ga1pa
ga4pe.
ga1pe
ga4p5l6
ga2p3s2
g8a6p7u
9gar.
ga6raf
ga1ra
ga6r5ak
2g1a2r1b8
5g2arbr8
5g2ar1by
gar4dek
ga4rd
gar1de
7ga1re.
ga1re
4g1a2r2ea
gar5es
gares6ke.
ga2resk
gare2s5ke
g2a1r2i
4gar3ki
ga2r1k
gar3ne
g2a2r2n
gar6ta.
ga6r1t
g5ar3te
gar8te.
g3ar1ti
gart5s6la
gar4ts
garts1l4
gar4un
ga4ryl
ga1ry
ga4sc
ga5s2i
ga2s8ka.
ga1ska
gas8ke.
ga2s1ke
ga6sk2i
4gasp2
gas6s4el
ga4s1s
gas1se
gas5sen
gas7ser
gass5e6tt
gas2s3l4
5gast
gas5te
ga5sto
gas7t6ra
gastr4
gas9tri
g2at
ga4te.
ga1te
ga5te6ns
g4aten
ga2t4e5s
g4a3ti
ga1to
ga3tr4
gat6tap
ga6tt
gat1ta
gau5la.
gau1la
9gav.
2g1a6v1d
6ga4vei
g2a1ve
ga4ve1ri
4g1a2v1g2
g5a6v3k6
2g1a8vs
2g1a2v1t
4ga2v1v
2gaw
4ga1ø
2g1b
gba4ne.
g1b4a
gba1ne
g6b6yk
g1by
2g1c
8g1d
gd5a2l1t
g1da
g6d5au
g2d2e3a
g1de
g2d2e7b2
g3de1b4a
g4de1di
gd2e5lo
g2dem
g4de6nd
g2de5o6
g4d2e1ra
gd2er
g4de5re
g6d2ero
g2de1s
g2det
g3d2et.
gdevi8sa
g2de1v
g2d5op
g1do
gd1or
gdy4d
g1dy
g6d1øy
g1dø
1ge
2g2e1a
geak8ta
ge1a2k
g6ea6k1t
geak6te.
geak1te
gea7ren
ge1ar
gea2re
ge4a2r1k
2g2e1b2
6ge1b4a
4ge5be
3gebri
gebr8
4gebrå
3gebyr
ge1by
ged4
4gedan
ge1da
6gedel
ge1de
ge5d6ia5
ge1di
9ged2om.
ge1do
7ged2o1ma
9ged2o1me
7gedom1me
ged2o2m1m4
6gedo6ms
4ged2os
2ge1dr4
2ge1dy
2ge1dø
2ge3e2
geen8de.
gee4n
gee6nd
geen1de
2ge1f
g5e8ft
3ge3fæ1
2ge1g2
g5e4g1g
g3e2g1n
2ge1h
gehø8ve.
gehø1ve
2gei
g1e2i7e
g4e1in
g2e2i4r3
gei9re
gei8s7p2
g2eis
gei6st5
3geit
gei1t3a
gei2t3o
gei4t3r4
2ge1j
2gek2
ge9kl4
g4e3k1n2
g1eks
6ge6k1t
5gel.
ge4lar
g2e1la
4gelau
ge6le.
ge1le
ge7l2ea
4g4eled
4gelei
gelei5er
gele2i7e
4gelek
4g3e2lem
ge5len.
g4elen
ge5le6ns3
5gelet
gel5e6tt
3g2e1li
4gelid
6geli4g1g
ge3l2ig
4gelit
6geliv
2g2e1lo
ge5lov
7ge8l1s4
gel3se
gel5si
gel6s1k7l4
ge2l5t4
4gelu
ge5lun
gel7ve
ge8lv
4g4e1ly
2g2e1læ1
2g2e1lø
2g2e1lå
5g4em.
2ge1ma
9gema.
2ge1mi
3ge4m3l
4g5e2m1n
gem6na
2ge1mo
9ge2m1r6
3ge6ms
3gem4s5t8
ge3mu
2ge5my
2g6e1mø
2ge1må
g2en
3gen.
g2e2n1a
ge7n6am
gend3s6t4
ge6nd
gen8ds
ge3n2ea
ge1ne
4g4enem
gen5e2r1f
ge5nes
gene4t
ge4n5e3ti
4g4ene1v
gene5ve.
gen8ga.
gen1g6a
ge6ng
gen5g4r4
ge4ni6n
ge1ni
6geniv
ge2n5k
genle6ge.
ge4n1l
gen5le
1gen3le1ge
ge4n3n
gens5l4
ge6ns
gen6sun
gen1su
gen5tr4
ge6n1t
4genum
g2e1nu
4ge1ny
4g4e7næ1
4ge1nø
2ge3o6
5ge2og1
3ge2ol
ge2o1me5
geo5met4
ge5on
6geo2p
ge9o1pe
2g2e1p
g2er
3ger.
ge5ra.
g2e1ra
ge6ral
ge4r5a6n1t
ge9ras
ger5di
ge4rd
4gered
ge1re
4gere1f
4gereg
4gerek
ge6re1ne
4ger2e1p
6ger2e3se
4g6ere6tt
gere4t
ge2r4i5d
ge1ri
4ger2ik
geri8k2a
6geri1ke
ge4rim
ge4ris
ge4rit
ge4riv
gerle9g
ge2r1l
ger1le
g9er8ma.
ge2r1m
ger1ma
g2e2r3n
ger1ne6
4ge3r2oa
g2ero
4ger2om.
ge1rom
4ger2o2m1m4
6ge5r2op.
ger1op
4gero1pe
g2e2r5p
ger4s5af
g6er1sa
ge4rs
5ge5r2ud3
g2eru
ge5rup
ge2r5v
gerø6re.
g2e1rø
ge1rør
gerø1re
ge4r3ø4v
4g2e1rå
g6es
5g2es.
2ge1sa
2g2e1se
5ges2en.
ge3s2en
5ges2e6ns
4ge1si
4ge5sja
ge1sj
4gesj2e
2ge1sk
ge1sl4
ge1slu7
ges4lø
4g4e1so
4ges1pe
ge1s2p2
ge4spr6
ge5spra
2ge1st
3ge6st.
gesta7b6l2
5ge6s5te.
ges1te
ges6t2e2k1k
ges4tek
ges8ti.
ges1ti
ge9s8t5rid
gestr4
gestr2i8de.
gestri1de
6g8e1stå
2ge1su
ges5vik.
gesv2
gesv2ik
2g2e3sy
6ge1sæ
2ge1sø
2ge1så
geså5re
geså2r
5get.
ge5tak
ge1ta
geta6le.
ge3tal
ge3t2a5le
6get4at.
g1e2tat
6geta1te
4g2e3te
5gete.
2g2e1ti
2g2e1tj
2g2e1to
2g2e1tr4
5ge4t5s1
2g2e1tu
2g2e1ty
get4y8e
2ge1tø
2ge1u
g2e1v
2ge7v8a
2geve
4ge1v2ir
gevi6sa
4g5e2v1n
gev6ne.
gev1ne
4ge5vo
5gevå
2gey1
4ge1æ2
2ge1ø4
2ge1å6
1gé
2g1f
gfe2l
g1fe
4g1g
g6g5al
g1ga
gg5ask
g2g1av
g4ge1di
g1ge
gged4
g2g1ei
g4g2e1la
g4ge1le
g2gem
g4g2e1nu
gg2en
g6gerei
gg2er
gge1re
gge4rin
gge1ri
g4g2e1rø
gge8s9b4
gg6es
gge1s5l4
g4ges1m
g6g4e1so
g4ge1s2p2
g6ges6t4io
g2ge1st
gges1ti
g6ge5sv2
g2g2e1v
gg2i1e6
g1gi
g4g2if
g4g5i4m
gg4j2e
g1gj
ggje5s
g2gl
g7glu
g4g1n
g2g1o2
g3go.
g5g2os
gg3rad
g1gr4
ggr6a6d2a
gg5ra5t
gg8re.
gg3red
g4g7rek
gg5s4par
g8gs1
ggsp2
ggs1pa
gg3sto
gg4sy
gg3sø
g7g8ud
g1gu
g4g5u4r
2g1h
ghe8n
ght5e1ne
g4ht
gh1te
gh4to
ghæ8
1gi
3gi.
4giak
gi1ar
4gi1a2r1b8
6gi1av
2gi1b2
gi8c
2gid
gi3de
g2i1e
4giek
3gi1en
gi9e6nd
5gier
4gi1fa
g2if
2gi1fo
4gifr2
4gi1fu
gi6ga.
g2ig
gi1ga
2gi1gr4
2gi3h
2gi1i
5gi1k2a
g2ik
3gi1ke
4gi1ki
3gi2kk
gi5k4r6
4gi1ku
gi5le
4gi5me4s1s
gi1me
gi4m2e1tr4
gim8et
4gi1mi
4gi3mø2
2g1i6nd
6g5inge1ni
gi6ng
gin3g2en
gin1ge
gi4nin
gi1ni
2g1i4nn
gin5n5u4
4g2i1no
4g1i6n1s
4g1i6n1t
2g1i6n1v
4gi5om
g4io
2gi1op
gio4r
2gip
gip5si
gi2ps
6gi1ra
g2ir
gi3re
gi4re.
giro3
gi6rob
5gis.
2gi1sa
gi3se
4gis2el
4gisen
5gisk
6gi1ska
gi6s8lu
gis1l4
gis4lø
4giso
4gis1p2
gi3s4pa
gi5s4pr6
gist2e6ru
gi2s1te
gis5ti
gist5ra
gi2str4
gi5stré
gi6st5rer
4gistu
6gi1sty
gi5ta
4gi5te
gi2t4e4s
gi2t9r4
git5te
gi6tt
2gi1u
g4i7va
4gi5val
4gi1vo
4gi1vu
1gj
2g1jak
g5j2a2r2n
2g1jaz
g2je5f
gj2e
4g3jeg
gje8l1s4
g2jel
gje2n
gjen1op2p3s4
g4j2e1no
gje2n1op
gjen1o2pp
gje6n5s8
6gje6n1t
3gjer
gje7sk
g2je4s3p2
gje8v9ak
g4jev6a
gje1v
2g1job
gj2o
2g1ju
7g6jut
gjø9de
gj2ø
2g3k2
gkly9
gkl4
gl2a
6g1la.
gl6a3de
4glag
gl4a4ga
gla8ge.
gla1ge
g3la6nd
g1lar
g4l5a6r1t
gla2r7v
1g2las
7gla5se
g6la4s1s
6gl6ast
g5la6st.
3glat
g5lau
g1l6e
gle6d2ero
gle5d2er
gle1de
g2le1f
g3le3ge
gleg8ga
gle4g1g
6glei
glei7er.
gle2i7e
g2lek
g7lek.
g5le1k6e
g2lel
3g2le2m1t
4glen
g9len.
g9lene.
gle1ne
g9lenes
g9le6ns
gle9p2l6
g2l2e1p
4gler
gl2e4ra
glere8de.
gl8e1re
glere1de
g9l6es.
gle6se.
gl2e1se
g4le5sk
g6le7s1m
4glet
g2le1v
1g2lid
g1li
gli4del
gli1de
gli7e6n
gl2ie
gli8er
g3l2ig1
3g2lim
gl6i9me
4gl4io
gli6tt4
6g7liv
4g2lj
gl7ja
g2l5l
g4lo.
g2l2oa
5g2lob
5g6l2o1me
1g4lor
glo3ria7
glo1ri
glori6an
glori4e7ne
glor2ie6
glori1e4n
g4l2os
glo5s1te
g2lost
6g1lov
g8l1s4
5glug
g5luk
6glun
gl5u6ng
glun8ge.
glun1ge
gl5ut
g5lyd
3gly1f
gly4se.
gl2ys
gly1s4e
g2løg1
g1lø
glø8pa
2gløs
g6l7øy.
5g4løym
2g1m
g4m2e3te
g1me
g4m2e1tr4
g5mé
gmi1ni6
g1mi
g3m6o4e
g1mo
g3mu
gmu8le.
gmu1le
gmå6la
g1må
gm6ål
g1n
g2n1ak
g1na
gna5lem
gn2a1le
gna4lo
g2nav
g6n1d
gn1dø6
gnd2ør5
g1n4e
g2ne3e2
g4n2e1lo
g6n2ero
g6n2e1rø
gne1s4
gne8se.
g4n2e1se
g4nesk
g4nest
g5net
g6n2e1t4r4
g2ne1v
g4ni1b2
g1ni
g4nid
g6n2ik
gni6ng4
gnin8g6s5
gni4s
g6ni5sk
gni6st
gni3st9r4
g2n5k4
g2nom
g1no
g2n5o6p
gn7o6v
g2n5r2
g6n1s
gn4skr6
gn6s1m
gn4som
gns4pr6
gnsp2
gn4s1ti
gn2s1v2
g6n5t4
gnu5re
g1nu
gnæ6re
g1næ1
g2nå
1go
7go3a6n
g2oa
go1ar
7goar.
2gob
go1b4e5
go4da
g2o1d
go5dal
god5ar
2go6d1d
go4de.
go1d4e
gods9t4
go8ds
2gof
go9ga
g2og
go5ge.
go1ge
go9g8r4
6goi
2gok
gok4se.
gok1s4e
g2o3le
gol6fa
go4l1f
g2o5lo
gol5va
go8lv
gol6var
go4lå
2gom
g7o6ma
6gome4t3ris
g2o1me
go5met
gom2e1tr4
7g2o2m1m4
go4n5a6nd
g2on
go1na
go9ne.
go1ne
go7ni
go5nok
go1no
go6n9s
2g1op
3g6or4a5
go5ra.
go7r2a1e2
go7ras
2go4rd
gor6da
g8o1re
gor2e8a7
2go2r1g
g4o1ri
gor2i5e6
g5o2r1v
gor8v4a
g2o5rø
gos3p2
g2os
4gost
6go1to
g2ot
go1t6r4
4gov
go8ve.
go8vi.
g2o1vi
2g3p6
1gr4
8gr.
4grab
6gr8ac
gra5ce
9gra8d2s3
gra2f5f
g6ra2f5t6
gra4m5
gr4an8d3s4
gra6nd
gran5to
gra6n1t
grant4r4
gra9se.
gra1se
gra9set
gra6sk
gras3t
gra5t
gra8te.
gr6a1te
grati4s
gr4a1ti
gra4u
gra4v3ak
g2rav
gra1va
gr3a7vis
gra1vi
gravta8
g4r1a2v1t
2gr2ea
4g5re8ds
gre4e4n
g2re3e2
2gre1f
g7re1fe
g4r2e1ga
grei1e5n
g2re2i7e
grei6e1ne
6g3r2eis
4grek
2g5rel
g5re6n1t
4g6r5e6pi
gr2e1p
gre2p4s3
g7r2e3se
gres6sak
g2re4s1s
gres7s6e6n1t
gres1se
6g5rest
4gre6tt
4g5ri.
g2ri1b3
4grid
4gr2if
4g1r2ig
gri5s2e1te
gri1se
4grit
gro9ar
gr2oa
g4ro7i
gr2o2m5m4
g4r2on
gro5sk
gr2os
gro3ve
4g5rui
4g3rul
gru6n7g
5g4rup
gr4u7sa
gr4us
grus5t
2g1rut
2gryd
4g5ryg
6g5ry2t1m
grø5de.
grø1de
6g5rør
4g5røv
grø5ve
4grøy
g2r7øy.
6gr6å1d
grå6da
gråk4
grå7n
grå5te.
grå1te
8gs1
gsa2
gs5ake4rs
g3s4a3ke
g7s4al.
g7s2a3le
g5s2alg.
g1sa2lg
g5s4al1ge
g1s2am3
gs3a2m1b
gs7a2m1n
g3s6a2nk
g5s4a4rd
g2sas
g7s6ast
gsbø6n
g8s9b4
gs1b4ø
g1s2ce
g2s5e1f
g1se
g2seg
g2sei
g2sek
g5s2e2k1r6
g7se1k2v2
gse9la.
gs2el
g2s2e1la
gsel4s5a
g3se8l1s
gsel4st
g2sem
gs6en3de.
gse6nd
gsen1de
gsen6ke.
gse2nk
gsen1ke
g5s2e6ns
g7s6e6n1t
g2ser
g5s2er.
g3s2e1ri
gse4st
gse4t
g3s2e1te
g2s3e3ti
g9s4e4ts1
gs4e6tt
g2se2v
gs1fø2
g2s1f
g2si
g5s4id2er
gsi1de
gs5is
gsi2v
gs4jar
g1sj
g3s2je1f
gsj2e
g5sji
g2sj2o
g5s2jå
g2s4ka.
g1ska
g5s2kad
gs4kal
g5ska2l1l
g6s1kam
g4skan
g4ska1pa
g5ska5pe
g4ska1pi
gs7kav
g5s4ki2l1t
g1ski
g7s4kj2
g7s6kjæ
g3s2k2ot
g5skren
gskr6
g7s6kug
g6skv2
gsl4a6ga
gsl4
gsle6ge.
gs1le
gs3le1ge
gs6le1ri
g2sler
g4sluk
g1slu
gs4lun
gsl9ut.
gs4lut
g5s4lyn
gs1ly
gs5med
gs1m
gs1me
g9s6me6r1t
g7s4nel
gs1n
gs1ne
g5s2og
gs9o1pe
g2s1o2p
g5s2pal
gsp2
gs1pa
g3s2p6el
gs1pe
g1s2pi
gs5p2ik
g3spil
gs5pi1le
g6s5p2o4rs
gs1p6o
g3s2pur
gs1pu
g4s3s4
gsse4e2
gs1se
gs5tak
g9s8ta6nd
g5s4ta6ng
g3s8ta1sj
g1stas
g7s4tat
gstatsrå7
gst2a4t2s1
gstat8s9r
gs1t6e
g2s5te.
g5s2te4e4
gs4te2l1l
gs5te1ma
gs2tem
gste6ma.
g7ste2m1t
gs4te1re
g5s7t6e2r1f
g5s4te2r1k
g5s4t2e2rn
g5s4te4rs
g5s4te2r7v
gst7ev3nen
g5ste2v1n
gs2te1v
gstev1ne
g5s4ti.
gs1ti
g3s4t2i1e2
g3s2t2if
g3s4t2ig
g4s5ti1å4
g5s2to.
gs4tol
g5st8ol.
g7st4o1la
g5st2o1le
gs5to2l1l
g4s5trap
gstr4
g5strau
gs4t5r2e3a
g5stre1de
gs4tred
gs4t5rei
g5s4tre1ke
gs6t1ret
gstr2i8de.
g9s8t4rid
gstri1de
g5s4t4rof
g3s2trø
g5s6trå
g5s2tue
g2s4t5ut
gstyr8ka
g1sty
g3s4tyr
gsty2r1k
gs4t2ør
g1stø
g5s4tå
gsu2
g5s2u5g
gs4v4a1ne
gsv2
gs5v2ik
gs7væ
gsy2d
g1sy
g7s4ym
gsy6na
gs2yn
g2sy6t
g2sø
gsø6ki
g7s6øt
gså4
2g1t
g3ta
gt4a8ka.
gta1ka
gta8ke.
gt2a1ke
gt8a6la.
gta4le.
g3t2a1le
g9t6e
gte6ke.
gte1ke
gte6ma.
g2tem
gte1ma
g3ti
gt2i8de.
g3tid
gti1de
g4t5if
gt6re.
gtr4
gtrå8d5s6
gtr6å1d
gt7s1v2
g4ts
g6t5t
gtu8en.
gtu1e2n
gtvek8
g2t1v
gt4y8e
g2t9y2t
1gu
4g5u4b4å
gu2di
g2ud
guds3t4
gu8ds
gu4el
6gug
g5u4g6l
gui4d
guid5ar
gu2i1da
gu4le.
gu1le
4g5u6l1k
gul8ke.
gul1k4e
gul4la
gu2l1l
gu1l5o
gu1l7ø
7gum
gu6n4g
gu2r2g
gu4ri
gur8na
gu2rn
gur6ne.
gur1ne
gu5rua
gu3rue
gu4st
gu1ta
2g1u6t1b2
4g1u2t3d
g5u2te.
gu1te
6g1u2t3g2
g7u6t3l6
4gu8t1n
2g1u4t1s4
gut4tak
gu6tt
gut1ta
gut4t4es
gut1te
4gut1tr4
2g1v
gva4ke.
gv2a1ke
gva8la
gved5li6
gve2d1
gve4d1l4
gvi8ta
6g1w
1gy
g9y8a
gy4da.
gy1da
7gym
gy3ne
gyr6
gy8sa.
g2ys
gy1sa
gy6se.
gy1s4e
gy4te.
gy1te
gy4ve.
gy1v
g5æt
4g5øk
gø4r3s
2g3øv
gøy9a
gøye6r
gø4y1e
gøy5n
1gå
gå6as
gå1a
gå6en.
g4å1e
gåe2n
gå4er.
gå3er
4g5å4k
4g6åm
4g3ån
gån8da
gå6nd
2g1å4p
2g3åre
gå2s2
g4å3st
gå9ven
gå1v
gå4v4et
h2a
ha4a
ha1b4a9
4h2a1e2
ha2el
4ha1f4i
ha2g
h4a3ga
ha4ga.
ha3g2en
ha1ge
hai1
ha7i8s1m
ha5ka
ha4ke.
h2a1ke
ha5ken
ha7k2e1ra
ha4ke5s
ha1la
h8a6la.
hal8d4s7
ha6ld
ha4le.
h2a1le
ha4le5v
hal6lei
ha2l1l
hal1le
hal6lø
hal4s3k
h2a8l1s
hal4so
hal4s3t
ha5lu
ha8l4v5
ha5ma
ha8me.
ha1me
ham6nest
ha2m1n
ham1ne
ha5mo
ham4st6
ha6m1s
ham4s4t7r4
ha5na
han6d5r4
ha6nd
hand7skr6
h4an8ds
han1d5ø
ha4ne.
ha1ne
ha1ni1
hanis4
ha4n2n3
han4ne
han6nel
han5n4en
han5n4o5
han3se
ha6ns
han4sk
ha4pe
ha4re.
ha1re
ha5rei
ha4rel
ha3rem
ha4res
ha2r5k
ha5rov
har7se
h2a4rs
har5tre
h6art2r4
ha6r1t
har4tri
ha4sj
hasj5e
ha2s1l4
ha2s6p7l6
hasp2
has5v2
ha3tar
h4ata1
ha4te.
ha1te
hat6le
ha6t3l6
h4au
hau5ke.
hau1ke
hau6st
hau4t5r4
ha1v4a
ha4va.
ha5van5
ha4ve.
h2a1ve
ha4veg
ha4vei
ha2v4e3s
ha1vi
ha4vo
hav4sl4
ha8vs
h2a6vu6
hav1ø
4h4a1vå
2h1b
hba4ne.
h1b4a
hba1ne
4hc
2he.
h2e2a
he7a6ns
he1an
heat4r4
he6b5n
h2e1b2
he4de.
he1de
hede4r6s5
hed2er
he2d9r4
hef9ta
he1f
he8ft
hef7t4es
hef1te
he3ge
he4ge.
he2g3r4
he2i
h2e8ia3
hei5e2n
he2i7e
3h4eim
he3i6n1s
hei4sk
h2eis
hei4t4s
he2k
hek4sek
hek1se
hek6serin
heks2e1ri
h2e1l1a
he4la.
he4le.
he1le
he4l5ei4
hel6le1su
hel3l6e4s
he2l1l
hel1le
h2e1l3o
he8l2s2
hel3sa
hel4se7
hel3se6s4
hel3sk
hel5sp2
hel5s1te
hel3s1v2
hel4t4es
he2l1t
hel1te
hel9ve
he8lv
he4mak
he1ma
hem5ne
he2m1n
hem3s8k
he6ms
henfø5re
he4n1f
hen1fø
henf8ør
h2e1n5o6
he6n1s2
hen5se
her6a9d2a
h2e1ra
he5re4t
he1re
he4ri.
he1ri
her5j
her3le9ge
he2r1l
her1le
herli9ga
her3l4i
her3l2ig
4h2e2r5n
h2e1ro
he4ro.
he3r2o9a
h2e2r7p
her6rei
h2e2r1r
herr6e
her6re1si
her6ret
her6s4ka.
her1sk
her3ska
he4rs
her8s7ka1re
her2sk2ar
h4er6sv2
her5un4
h2eru
h2e2r3ø
he4se.
h2e1se
he2s2p2
hes7pa
hes5p6el
hes1pe
hes5pen
hes9per
heste5ri
hes1te
he2s4t4e3s4
he6stø
het2
he4te.
h2e1te
h2e5t6i
het4s3a4
he4ts1
het4s5p2
he6t3t
he6va.
he1v
he6v7a2r1m
he4ve.
hev9na.
hev2na
he2v1n
2hf
2hh
h5hu
hi5a1o
hi4ba2k
hi1b2
hi1b4a
hi1e4n
h2ie
hi2et
h2i4f1
hi2ff2
hif9r2
hi6ge.
h2ig
hi1ge
hi1k7e
h2ik
himm2e6l5o
hi2m5m4
him1me
himm2el
hi6n7an
h6i1na
hin4nes
hi4nn
hin1ne
hi6n1s4
hin2s9ke
h4insk
hi4pl6
hi2p3p
hi1ro
h2ir
hi2s1
hi9se.
hi1se
hi3sen
hi5s4i
4hisk
hi1t7o
hi4t5r4
hit5ti
hi6tt
hi8va.
h4i1va
hi4ve.
hi1ve
hi8v1s
hjar4
hj2e4
hj8e1m7e
h2jem
hjor1te5
hj2o
hj2o6r1t
hju6l7
hju7l8e
2hl
h5lan
2h1m
hma8n
h1ma
2hn
h2na
h3ne6n
h1ne
h6n1s
h4o
ho5ar.
h2oa
hob6
ho6da
h2o1d
ho4de.
ho1d4e
ho5den
hod2e3r
ho5der.
ho5dy
ho2f4f3
hof4f3a4
hof4f3e4
hof5f6er
hof4fi
ho8gs6
h2og
hog6s4tr4
hog7s5t6ra
hoi5
h4o2la
ho5lag
ho5lan
ho4le.
h2o1le
ho4lin
h2o1li
ho2l5l
ho4lom
h2o1lo
ho8l1s4
ho4lu
hol7ut
ho4me.
h2o1me
ho4mo.
h2o1mo
ho4m2o1d
ho4m2os
ho5n6o
h2on
h2o4o
ho8pa.
h2o1pa
ho4pe.
ho1pe
ho8pi.
ho1pi
ho5ra
ho6ra.
h8or2e1
h4o1ro
2h2o6r1t
h2o4s
ho1s5a
ho5s2en
ho1se
ho5ser
ho5si5
hou2
ho1v
hove5re6
hove2r
ho4vé
2how1
h1p
2hr
h1ra
h1re
hr4i5ne
hri2s3
6h1s
h5s4e
4ht
h5ter
h1te
hte1re4
h1tr4
h6t5t
h2u4d3
hu4da
hu8d5s6
hudså9re
huds1å6
hudså2r
hu4er
hu6et.
hu1et
hu4ga
hu4ge.
hu1ge
hu8ja.
hu1j
hu6ka.
hu1k2a
hu2k8ra
hukr6
hu4la
hu4le.
hu1le
hu4leg
hu5les
hu4le1v
h2u5ma
hun6de1s
hu6nd
hun1de
hu4n2n3
hu1ru4
h4us1a
hu8sa.
hu5s6a6r
hu6s7a2r1r
husa7r8e
hu4se.
h2u1se
hu4s3ed
hu2s1i
hu2s5j
hu2s1k
hus7m
hu4s5s4
hus1t
hu8str4
hus8t9ran
hu6s4tre
hu6sty
hu6s5u6
hu2sø
hu4va
hu1v
hu4ve.
hu4v4e3s
hv4
hv2a5le
hvas5
hve2r
hv2e1r3a
hvi5l6i
h4v4ir
hvi4ts4
hvo2
hvor5
hvo1r5i6
h6y
hya3
hy2bl2
hy1b
hyd4
h4y2e
hye5ne.
hye2n
hy7e6ne
hye9nes
hyg5gel
hy4g1g
hyg1ge
hy6la.
hy1la
hylde4s7
hy6ld
hyl1de
hy4le.
hy1le
hyr4de1s
hy4rd
hyr1de
h2ys3
hy8sa
hy4se.
hy1s4e
hy2s1j
hys5t
hæ5g6
hær1fø9
h4æ2r1f
hæ4r3s6
h4ø4e2
høf5
hø2g1
høg3ri
hø2g2r4
høg7rø
høk6
hø6le.
hø1le
hø4na
hø4ne.
hø1ne
hø5rar
hø1ra
hø5ren
hø1re
hø5rer
hø4re1s2
hør6sp2
hø4rs
hø2s
høst7a
hø1st5ø4
hø1va
hø1ve
hø1vi
høy5a6
hø6y5k
høy7n
høy7rar
høy1ra
hø2y1s4
høyse6te.
høy1s4e
høys2e1te
h6å
hå7a
hå8le
hå6na.
hå1na
hånd5skr6
hå6nd
hån8ds
hå5nel
hå1ne
hå6pa.
hå1pa
hå4pe.
hå1p2e
hå4p5l6
hå1re
hå1ri
hå4r3s
hår7u
hå8va.
hå1v
håv4a
hå4ve.
ia9al
i1ab1l2
ia1b2o
i2a3de
i1ad1j
ia5d8r4
ia1g2
ia2ge
ia1in
ia1kr6
i1aks
iak8se.
iak1se
ia5ku
i1al.
ial1a
ia2l5ein
i2a1le
iale4t
ia2l5e3ti
ia4l5e6tt
i2a4l1f
ia2l3g
ia4lin
ia1li
i4a6l1k
ia2l3op
ia6lov
i5als.
i2a8l1s
ials4t
i3alt.
ia2l1t
ial1u
ia2lø
i1an.
i2a1na
ia4nal
ian5a6ld
i4a5nar
i7andr4
ia6nd
i5a4ne.
ia1ne
ia8nes
ia7net
i5a2nk
i1a4n3m
ia2no1
i1a6ns
ian3sa
ian3sl4
i1a6n1t
i2a7nø
ia2pa
i3a2pp
i1ar.
iar4do
ia4rd
iar8d5s4
iar4du
i2a1re
i5a2r2ea
ia5r6e8l
i5arn.
i2a2r2n
i9ar6ns
i7ar1ska
i2a4rs
i6a1si
i1as1m
ia4sp2
ia4s3s6
i3as1si
i1ast
i7a6st.
ia1t
ia5te
iat6r4
iaty6ra
ia1ty
i1a6tt
i7auk
i1av
i1b2
iba4ne.
i1b4a
iba1ne
ib5b2o
i2b1b
ib3b2u
i3be1re
i1be
ib4er
ib2e4ro
ibi5er.
i1b4i
ib2ie
ib7lar
ibl2
i2b3le
ib4leg
ib4le5s
i5b2o
ib6o4e
ib2o7n
ib4r8
ib3s2t6
i6b1s
i6b8t
ibya7
i1by
iby9ar.
8ican2arar
i1c4a
ic4anar
ica1na
icana1ra
ice5ne
i1ci
i2c6k1
ick7e3te
ick5e4t3
ic1ke
i1co
i5cy
2i1da
i5dal
i3das
i2d1av
id3del
i6d1d
id1de
iddel5u
id6d2e1p
2ide.
i1de
3ide2al
i2d2ea
i2d2e7b2
i6de1f
i6d6e5ge
i2deg
i7de1ki
i9d4el.
id3e8lv
2iden
i5de6nd
iden5sv2
id2e6ns
5i4dent2if
iden3ti
ide6n1t
5i4den2tit
ideo3v
ide3o6
i6derap
id2er
id2e1ra
i9de5re.
ide1re
i7deren
i9deres
iderl4a8ga
ide2r1l
iderle7g
ider1le
i7d2e2rn
i5de4rs
i6des1m
ide1s
id6gem
i2d1g4
id1ge
id4g6es
idi4en
i1di
id2ie
1i2d4io
i8dj
id7jer
idj2e
id9n6a
i8d1n
1i4dol
i1do
id9ran
idr4
id5reg
4i3dre1v
2idri
i8d2s1
id5s2am
id1se4
id6s2el
id5sim
id2s1i
ids5l4
idsl4a6ga
ids3t4
id4s5tu
i4d2t1
i2d1un
i1du
i2dy
3i2dyl
i3dyr
i3dø
i6d7øy
id7å
2ie
i2e1a2
i2ed
i1e2ff
ie1f
ie4ge
i4e1go4
ie1i
iei6d
2i1e2i7e
i3e2ig
i5e4i1ni
ie1k8l4
i1eks
i2e5l6a
i2e1le
i5e2lem
ie4le1v
i7e6lim
i2e1li
i1e8l1s
i9e2l1t
ielø8pa
i2e1lø
i8e9ma
ie2m7b8
i8e3me
i1en
i2e7na
i2e6nd
ien6d2e1la
ien5d4e5l
ien1de
ie8né
i2e5ni
ie4n7n
i2e5no
i5e6ns
ien4sk
ien4s5v2
ien4t3r4
ie6n1t
i2e5nu
i6e1ny
ie4ran
i2e1ra
i4e4rd
ie6re.
ie1re
ie5reg
ie4r5e6ng
i6ere4t
i2e1ri
i4e4ril
ie4ris
ie4riv
i2e2r1l
i6ero
ie7ro.
ier4ra
i2e2r1r
i6er4sp2
ie4rs
i4er1s3v2
ie6r5t
ie3run
i2eru
ie2r5v
i2e1s
i6es.
ies4c
i2e3se
ie4s5s
ies4ti
i8es6v2
i1et
i2e1ta
i5e4tab1l2
i3e2tat
i2e9te
i4e5té
i8et4re
i2e1tr4
ie1u2
i6e7ve2d1
ie1v
2if
ife4s1
i1fe
if2fa
i2ff
if2i6e2
i1fi
ifj2ø8
i1fj
if3le
if2l2
if4les
ifo2r1m4
i1fo
if1re
ifr2
i8ft
if4tal
if1ta
if4te1re
if1te
ift4e5s
if4t2s
i3fø
2ig
i2g2a1e2
i1ga
i4g3a4nn
i7ga2r1k
ig4a6r1t
iga3ru
iga7te
ig2at
ig3a6tt
i2gav
i8g3d
i6g2e1b2
i1ge
i6ge1di
iged4
i6ged2o2m1m4
ige1do
i6gedo6ms5
i2gem
i3g2en
ig2e6no
i5ge4rs
ig2er
i4g2e1rø
i4ge1s2p2
ig6es
ig3e4ta
ig5e6tt
ig4ged4
i4g1g
ig1ge
igg6es4
ig4gra
ig1gr4
ig8g9s2
i6g1h
i2g1ia
i1gi
i5gi1b2
i4gim
igi2on4
ig4io
ig4je9v6a
i1gj
igj2e
igje1v
ig1l
ig6l6d
igli6se.
ig1li
igli1se
ig5l2oa
ig5ne1u
ig1n
ig1n4e
ig5no
i2gof6
i1go
igo5fr2
i2g1om
igo4no
ig2on
ig2ra
i1gr4
ig2re
i2g1rø
i6g3r6å1d
igrå5t
igs4al
i8gs1
igsa2
ig5s2el
ig1se
ig1s2j
ig5s4ka
ig3s4kr6
igs4mu
igs1m
ig3s4pa
igsp2
igst4
igs4ta
ig4s1t6e
ig5stek
ig7stel
ig5s2tem
igs4tra
igstr4
ig5s4va
igsv2
ig1un
i1gu
ig9ut
i3h
ihen3
iho7le.
ih4o
ih2o1le
i4huk
ihu9la
ihu5le.
ihu1le
i1i
i5in
i7is.
i6i1ta
i1j
2ik
i1k2a
i2kab
i5kabel
ika1be
i2kaf
i2kak
ika6n9d
i2ka1o
i4ka1po
i5kar.
i5ka1ra
ika5re
i4ka2rei
i6k4a1sa
i1kas
i6ka3sp2
i2ke.
i1ke
i2ked4
i2k9ei
i9keleg
ike1le
i5k2e1li
ike5lu
i5ken.
i4k2e1na
i5ke3ne
i9ke6n1s2
i3ker.
i4k2e1ra
i5ke1re.
ike1re
i3ke5ri
ik2e4r5o
i2ke1s2
ike3si
i6kesk
i5k4e1so
ike5su
i5ket.
i5ke4ts2
i1ki
ik4i9ne
i2k5i4nn
iki5st
i1kj2
ik4kaf
i2kk
ik1ka
ik6ka1na
ik4kap
ikk5a2r1v
ik4kas
ik4kat
ik6k5a6tt
ik6k7e6n1t
ik1ke
ik4k2eru
ik8kesk
ik4kest
ik3kj2ø
ikkj2
ik4kjøp
ik4kl4
ikk5la4g
ik2ko
ikk5o4rd
ik4kr6
ikk3re
ikk6s5v2
ikk7s
4ik2ku2
ikk5u6nd
ik1kun
ik2k1v2
ik6ky6
ik1l4
i5k2læ1
i1ko
i2k2oa
i9ko7ar
i2ko2b3
i4k2o1d
iko5d4e
i2ko3f
i4k2og
i4k2o1h6
i2kok
iko5na.
ik6on
iko1na
i2k2o1o
i5k6o4rd
i4k8o1r6e
i2kov
ik1r6
ik4rak
ik5rem
ik5ro1b4e
ik2ro
ikro5b4
ik3r2os
ik2ry
ik2sa
ik4sek
ik1se
iksmå8la
iks1må
iks1m
iksm6ål
ik7s2ot
ik1so
ik5s2p6el
iksp2
iks1pe
iks5ti
iks5to
ik8stu
ik4tav
i6k1t
ik4teg
ik1te
ik2t1r4
iktsl4a8ga
ik4ts
ikts1l4
i1ku
iku6le.
iku1le
i6k7u6t
ik1v2
ik4vin
i1ky
i3kø
i3kå
ikå8pa
i1la
i2l5adr4
i2l7af
i2l3ak
i2l3al
i4la1na
il6a6nd
i4la2r1k
i4l3a6r1t
il5a4s1s
il6as5t6
ila5t
i2l5av
il4dak
i6ld
il1da
il4de5k2o
il1de
il4d2e3te
il7dj
ild3re
ildr4
ildsfa9re
il8ds
ild2s1f
ilds1fa
ilds3far
ild3s4t4
ile1a9r
i1le
i2l2ea
ile8a2re
i3le7e2
i2le1f
ile4ge.
i3le1ge
i4l3eg1n
i2l1ei
i6le2ig
i2lek
i2lel
i4l2e1no
i4l2ero
ile1s
i4l2e1se
i4le5sk
i4lest
ile6t4ri
i2l2e1t2r4
ilet5te
ile6tt
i2l2e4tu
i1lé
ilfø5re
i4l1f
il1fø
ilf8ør
il5ge
i2lg
il1gl
i4lid
i1li
ili5e4rs
il2ie
ili9ga
i3l2ig
i5l2ik
i2l1im
i4l3i6nd
i7l4i1ne
i4li6n1s
i4l3i4r
ili5s6t2ik
ilis5ti
il3ja.
i2lj
il1j2e
il5j2e1se
il4je1s
il1j2o
il1ju
il1k4e3
i6l1k
il5ker
il4kes
il1k4o6s
il5ku
il4la1b
i2l1l
il6lam
illan8da
illa6nd
il6lap
il9lau
il4le3e2
il1le
ille2i9e
il6l7en3d2er
ille6nd
illen1de
il6lesk
ill6es
il4le1v
illi9ga
il9l2ig
il1li
il4lo5m1
il8l3s2
ill5s5kå
illsk4
il1læ4
illæ9re.
illæ1re
il4m5est
i2l1m
il1me
il2m5e4v
il4mi
il6m5s
i1l6o1e
i9l2o1lo9
i4lom
il5o2m1v
i5l2on
il3o2pp
i2lop
i4l1o4r
i5lo3so
il2os
ilo1t3u
il2ot
i4love
i1lov
ilo1w1
ilret4
i2l5r4
il4set8j2e
i8l1s
il1se
ils2e1tj
il3sl4
ilsla7
ils3le7ge
ils1le
ils4mu
il2s1m
ils2p2
ils2t
il5str4
il7su
il3s2v2
ilsva9ra
ilsva5re
ilta9la
i2l1t
il3tal
ilt5re6tt
ilt2r4
ilt1ret
il6t7å
ilu4h
i5luk
il7ul8
il3un
i5lur
i9lus
il1ut
i8l5v6
ilve8d1
ilve4r
i1ly
il1å8
i2lår6
8im.
4i1ma
i2mad
i4maen
im2a1e2
i2mag
i6m5a2kk
i4m2a1ko
im4a2l8n
i2mam
i9man
i2map
i5mar.
ima5s
i4ma1tr4
im6at
i8m1b2
i4me7e2
i1me
i2me1g
i2mek
i6me1lu
im2el
im5e4n1h2
im8et
i9met.
i4me1ta
i4m2e1ti
i6mey1
i8mé
imi9la
i1mi
imi1ni6
i7mj
im4le1v
i4m3l
im1le
im8l9u
i2m5m4
i6m2og
i1mo
imo9l
im5o4rd
imor8da
im7o6v
i8m1p2
5im3pe1ri
im1pe
imp4l6
1im1po
im2p9s
imp1se8
1im1pu
im4re1f
i2m1r6
im4rek
im4res
im9se
i6ms
im2s4k5l4
ims3kr6
im5s4me
im2s1m
im1s4t
im6s2ti
imta8la
i2m1t
im3ta
4i1mu
i3mø2
2in.
6i1na
i4nag
in5a4g1g
i5n6a2kk
i2nak
ina4let
in2a1le
i4na2m
in3a1me
in2an
i9n6a1ne
in5a4nn
i2nap
in5a2pp
i5n4ar.
i5n2a4r5s
i4nask
i4nasp2
i4n5a4s1s
ina4t5ak
in4a1ta1
i6nau
i2n7auk
i4n5a6v1l2
i4n5a2v1r8
ince2
in1c
in7d2er
i6nd
in1de
6ind2ig
in1di
in4d2og
in1do
ind9ra
indr4
in3dru
indr5ø
ind5sk
in8ds
ind3s1p2
ind5s4t4
5industr4
in1du
indu1s
indust4
ind7å
4i1ne
ine8a2re
i2n2ea
ine1ar
i2ned
ine1dy6
in5e4g1g
i2neg
in5eid
i2nei
in3e2i7e
i6ne1le
in2e4li
in7e6ng
i3nen
ine8pa
i2n2e1p
ine8pe.
ine5pe
i7ne1re.
ine1re
i5neren
i9ne5res
i4ne4r2ik
ine1ri
i7n2e2rn
i5ne6r1t
i5n2es.
i4nesk
ine1s8ka
ines8ke.
ine2s1k4e
ines4s2t
ine4s1s
ine5s4ti
i4ne1sø
i2ne1v
8i1né
3infek
i4n1f
in1fe
in4f2os4
in1fo
in4f2ot
in1fu9
4in1fy
in4g5a6ld
i6ng
in1ga
in4gav
in5gebj
in1ge
in2g2e1b2
in6gem
5inge1ni
in3g2en
in5ge1ri
in3g2er
in4g2eru
in3g2e7v
ing5je2n
in1gj
ingj2e
in4g2oa
in1go
in4g2os
in2gr4
ing5r2e1p
ing7ris
in8g2s1
ings5om
ingst8
ings5v2
6in1gu
in2g5ø
i7nia
i1ni
ini9ar.
i2ni1b2
i5n2ie
i4n2if
i2n2ig
i4nil
ini7m
i4n1i4nn
i2nip
i4ni1sa
ini3se
i4ni2ses
5initia
in4it
ini1t2i
i4ni1to
5injek
i4n1j
inj2e
2i2nk
in5kel
in1ke
in4k2ero
in4k3la
inkl4
in4kok
in3ko
i4n5l
inl4a8ga
2inn.
i4nn
in4nal
in1na
in4nem
in1ne
in4n2e1rø
in4ne3si
in4ne3s6t
4innet
in6n7et1te
inne6tt
innfa9s
in4n1f
inn1fa
1in6n3g2
5innhal
in4n1h2
innh2a
3innh4o
2in1ni4
in4ni.
in4n5o2m1
in1no
in5n6ova
inno1v
in6n1s4
6inns.
3innsa
inn5se
inn9sen
inn9s1te
innst6
inn7s3ve
innsv2
1in6n7t4
in4n5u4
4in1næ1
in2nø4
2i1no
in2o5a
i5noar
i4n2o1d
i2nok6
in7oks
i6n1s
4ins.
8insa
in9sa.
in7sal
in5sar
in5se.
in1se
in4sek
in3sen
inseri8e9ne
inser2i7e6
ins2e1ri
inseri1e4n
in3si
ins2is5
6in1sj
in4sja
in3sj2e
4insk
in9s8kas
in1ska
in5skat
inske4t
in2s1ke
in1s4ki
in3skj2
ins5kj4øt
inskj2ø
ins6kor
in2s4k3v2
ins6kø
insle7g
insl4
ins1le
ins8l2e1ga
in3slo
in3s2lø
in5s4ma
ins1m
in3s1o2p
1insp2
in4s1pa
in5s4pl6
ins4p6o
5instal
in8s5te.
ins1te
ins5ten.
i6ns9te6ns
3in3s4ti
in4s4tin
4in3s6to
in5stra
instr4
inst5rel
5in3stru
in6stå
in7sul
in1su
in2sv2
6in3sy
8int.
i6n1t
2in3ta
inta8la
4inte.
in1te
5in7teg
in8t7e1ge
in5t4er
in3t5e4s1s
int4es
in5te4t5
4in5ti
inti5me
int2r4
int6ran4
in5t4rer
in5tres
6i1nu
inu6i
i8n9ul8
in7ut.
i2nut
in9u8te
1i6n1v
6inve1v
iny4i
i1ny
iny4t
6i1nø
in7øk
i7n4øt
in3øv
4io
i2o1a4
i2ob
io4de1re
i2o1d
io1d4e
iod2er
i1o2ff
io5g1n
i2og
io1i
i1oks
iol4
i2o3le
iol7jen
i3o6lj
iolj2e
io3m1u
io4na6ns
i2on
io1na
io4na2r1r
io6n5er1s2t
io3ner
io1ne
ione4rs
io6n3g4
io4nin
io1ni
io4nu
i2o1pl6
i1o2pp
io5ra.
ior8da
io4rd
i8o1re
io4r2ie
io1ri
io4r5in
iorl4a8ga
i6o2r1l
io2s8k3v2
i2os
io1st
iota6le.
i2ot
io1ta
io3t2a1le
io3t4e
io4tra
io1tr4
i5pap
i1pa
ipa4ti.
ipa1t
ip4a1ti
i4pe3e6
i1pe
i4p2e1la9
ip6el
i7p2e6p
i1pi
ip2i1e
ip4i9ne
i4p5in8g2s1
i3pi6ng
i1pl6
i2p1le
ip4p2e1li
i2pp
ip1pe
ipp6el
ippe8l7s6
ipp6lan
ipp1l6
ipplæ8re.
ipplæ5re
ipp1læ1
ip2p1r6
ipp4s3t2
ip2p1s
ippsy8na
ipp1sy
ipps2yn
ipp4s1ø
ip7pun
ip2pu
i1pr6
i6pra
i6p5ru
ipru8te.
ipru5t8e
ips1a
i2ps
ip2s2e4l
ip1se
ips1fø5
ip2s1f
ip2si
ip2s1k
ipsle6ge.
ip2s1le
ipsl4
ips3le1ge
ips1t2
ip6tar
i2p1t
i5py7
i3pø
2ir
i1ra
i9r6a6nd
ir5a2r1k
i9rast
ira6t4r4
ir5de
i4rd
ir8d5s
i1re
i2r3e2i7e
ir6ek
i3re1po
ir2e1p
i3rer
ir2e6r5a
ire7st
i3ret
i8r2e1to
i2r7g
i1ri
iri4a
iri8ka.
ir2ik
iri1k2a
i4r3i6nd
i4r3i4nn
ir5inst
i2ri6n1s
iri9t
ir5ka.
i2r1k
ir4kat
ir5ker.
ir1ke
irke3s6
ir4kest
ir4k1le
irkl4
ir5kv2
irk5ø
ir4mag
i2r1m
ir1ma
ir4mal
ir4mast
ir9mé
i5r2oa
i1r6o1e
i4roi
i1rol
i2r5o2pp
ir2o1s
ir3r6e
i2r1r
ir7sko
i4rs
ir1s2p2
ir3s4t
i6r1t6
ir3ta
irti6g5r4
ir1ti
irt2ig
i1ru
i4r5u4k
ir4u8m
iru7sa.
ir4us
ir4usa
i1rø
i1rå
i4r7å6l
i1sa
i4saks
i4sa4n1b2
i4s3a6ng
is3a4nn
i4s3a6n1s
is3a6n1t
i7sas
is5a2u4d
3is1bry
i8s9b4
isbr8
is6cen
i1sce
isch5
is3co
i5scr8
i2sed
i1se
i4se3e4l
ise3e2
i4se5e4n
ise5e2rn
iseer1
i2s2e3g4
i2sei
is3e2i7e
i2s5e2ig
i3s2e5is
i2sek
i3se6k1t
i4sek1te
i6s7e6l1d
is2el
is4e5li
i2sem
is2e5ne
i4s2e1no
isen3si6
is2e6ns
i5s2er.
i5se4rs
i2ses
is5e4s1s
i4s2e1te
i4set8j2e
is2e1tj
i2se1u
8i1sé
isha7ne
is1h
ish2a
3ishav
is4h5in
ishø9ve
is5hø
isi6e1ne
i1si
isi1e4n
is2ie
i3s5i6ld
is2il
i4s3i6nd
isis5t
is2is
i4s7jun
i1sj
i2s9ka.
i1ska
is3kal
i2s3k2ar
isk9art.
i4s1ka6r1t
isk5ar3te
i8s1ke
is5ke.
is8ke3e2
is4kel
isk5e8l1s4
is2ke5s4
is3kj2e
iskj2
i3skj2o
iskl4a8ga
i2s1k2l4
i6skla
is6k5le
isk3lo
i3s2k2o5g
is7ko1gr4
is3kop
is3k2ot
is1kr6
is6k5rin
is1ku
is4kul
i5sku1la
i2s1kv2
is4kvi
is1l4
i6slu
is4l7ut
is6l9øy.
is1lø
i5smak
is1m
is1ma
is4med
is1me
is3m6o5e
is1mo
i6sm2us
is1mu
is5ne
is1n
is5nu
1isol
6i1s2o6ld
5is2o1mo
i2s5o2m1r6
is2o3pa
i2s1o2p
iso5pe
is2o7pr6
3is2ot
i2s1ov
is1p2
is4pan
is1pa
is5pane.
ispa5ne
is6pis
i1spi
i1s7pru
ispr6
is4ses
i4s1s
is1se
is5si
is5s2is5
is6s7kj2
is4sko
is7skr6
is5sky
is4s4kå
is5sok6
is7sto
iss2t
is6s1ve
issv2
is5svo
is9s4ær
is1sæ
is4søk
is1sø
i5stab
i4s3t2a9le
is5t8a1ne
i2s5tap
i4s5tar
ist5a2v1b2
is2tav
ist7avi
i2s1te
i2s5te.
is4tek
is4t5e6k1t
iste2l5l
iste6ma.
is2tem
iste1ma
ist6en
is5ten.
is5te1ne
is5t8er.
is5t2e2rn
is6t2e1rø
is6té
is4t2ik
is1ti
is5ti2l1b2
ist4il
i2s5tis
is3t6ok
is3tol
i2str4
i8s8t1re.
i4st5ren
i5st2rer
i6st5rer.
i4s2tres
i6st1ret
i2s3try
is3tus
6isu
is5u2f
is5ul
is1un
is1v2
is4vak
is7w
isy8na
i1sy
is2yn
isy5r
i2s5y2t
i2s1øk
i1sø
i4søy
is7åk
is3å2r
i1ta
ita4l3a
ita6l5e4rs
i3t2a1le
ita4lo
it6a4ly
i6ta6ng
i4t1an7s6v2
ita6ns
i1tau4
i4tav
i4t2ec
i1te
i2teg
ite8ke.
ite1ke
i7te5ky
i6t7e2lg
i4te1ni
i2te3o6
ite3re
i4t2ero
ite4r6s5
i4t2e5ru
i2t4e1s
ite6se.
it2e1se
i3te4t6s5
i1té
i1t2i
iti6er.
it2i1e2
i4ti2e1s3
i2t2if
i4ti1kam
it2ik
iti1k2a
i4ti3k1v2
iti5me
i2t4io
i2tip
i2t2i3r
i4ti1sa
i4tisen
iti3se
i4ti1si
i4ti1sj
i4tisko
i4ti3s1p2
i4tist
iti5str4
i2ti1u
itiv4
iti8vs5
it7j2aran
itja1ra
i2t7jer
itj2e
i2t7ji
itma6le.
i2t1m
it1ma
itm2a1le
itne4s4s
i8t1n
it1ne
i1to
it2o9a
i4tom
i4t5o4rd
it2o4s
itostra6
itostr4
i2to8v
i1tr4
i4tra.
i6tr2a1e2
it3rel
it3ren
it5rim
it4r4i8ma
it3rin
i5tris
it7r2o6s
it1sa
i4ts
it4s3ei
it1se
it3ser
it6s4es
it5sku
itsl4a8ga
its1l4
it1s1n
it6s1ti
it1st4
it3sun
it4tag
i6tt
it1ta
it6tak
it4tal
itta9la
it6ta4nn
it6tap
it4t5at
it5ted
it1te
it2t2e4l5o
it4te2nk
it3ten
itt4e5s4
i6tt5e6tt
it4tid
it1ti
it4t4il
it4tj
itt1o
it6tof
it7t2on
it4tr4
itt5skr6
it6ts
itt4sø
it4ty
it7t2y2s
i1tu
itu5e
itær1
i1tæ
i1tø
i5tå9
i1u
iu6a
iu2b
i5u6l
i5um.
ium4f5ar
iu2m1f
ium1fa
i2u2m1i
i4u2m5m4
i4u4mo
i6um1se
iu6ms1
i4um5s4t
i4umsu
iu2n
iu8p
i2ur
i2u5se
4i1va
i4vad
i6va1j
i4v3aks
i5val
iv8a6la.
i4va1na
i4v5a2nk
i4v1a2r1b8
iv2a4r5s6
i4v5a4s1s
iva6t3t
i1ve
i2veg
ive6ge
iv5eg6n
i4vei
iv7e2ig
i2vek
iv2e4ra
ive5ras
ive4r5d
i6vere4t
ive1re
i4vesk
iv4es
i4ves1ti
i6vesv2
i6ve1tø
iv4et
i2ve1v
i1vé
i1vi
iv5i6n2s
iv5is.
iv5i8s9b4
ivle6ge.
i6v1l2
iv9leg
iv1le
iv3le1ge
i2v3n
i1vo
i2v7om
i2v3r8
iv4sal
i8vs
iv2si
iv2sk
iv2sl4
iv2s2n
iv4so
ivs5v2
i2v3un
i1vu
i2v7y1
ivyr8ke.
ivy2r1k
ivyr1ke
i9væ
iv7å2p1n4
i1w2a
iw4i5
i5w2ie6
i5y
iyr8ke.
iy2r1k
iyr1ke
i5za.
i7zas
i4ze
i1zo
i1ø
i1ø4k
i1ør.
iø1r5i
iø4r5s
i4øs
iø3se
i1øy
i1å4
iårs7l4
iå4r2s
iåt4te.
iå6t1t
iåt1te
ja1ak
ja9ar
j6a4da
1j2a1e2
ja7en
5jag.
ja4ga.
j4a1ga
ja7gar
ja4ge.
ja1ge
ja1g6r4
ja4h2a
ja1h
ja4h4v4
ja4j
jak4kel
ja2kk
jak1ke
jak7ke1le
jak4ko
jakk7s4
ja1k8r6
ja8kre
ja6k1t
jak1te
ja7ku
ja3lo
ja1lu
ja2m7b
6jam1b4i
jamhø8ve.
ja2m1h
jamhø1ve
5ja2m1m4
ja6m1s2
jan4gr4
ja6ng
j5a4n1l
ja6ra.
ja1ra
ja6r5ap
ja9rek
ja1re
jar6n3s4
j2a2r2n
jar5ta
ja6r1t
jar5te.
jar3te
jar5tet.
jar7ti
6jarø
ja6rå
ja7se
ja8sk2i
ja5s1ti
j4a5ta1
jau4e5re
j4a6ue
jau7er
ja9vi
1jaz
j1b
2jd
j2e
2j2e1a2
je5a2k
2j2e1b2
je2b9b
je4d2e7b2
je1de
6je4dek
4je1dr4
je2e1p1
je3e2
6jee1v
2je1f
je4f3et
je1fe
j4e3fj
jef5lag
j2ef2l2
je4f3re
j8efr2
jef4sa
je2f1s
jef6s5i2
jef3t4r4
je8ft
je5ge
jeg5ge
je4g1g
6jegl
jeg6les
jeg1l6e
4j4e1go
4je1g2r4
4je1gy
2je1h
je1i
j2e5i8s
4je1j
2j4e1ka
jek6k2eru
j2e2kk
jek1ke
4je1kl4
je4k1li
2je1k2o
jekt3a
je6k1t
jek4tan
jek6t4es
jek1te
jek2t5e1v
jek4t5in
jek1ti
jek4t3r4
jekt1s4t4
jek4ts
4je1kø
2jel
je6le1g2r4
je1le
jelei7er
jele2i7e
je7le1le
je2lel
je4les
jel7ge
je2lg
jel6lag
je2l1l
jell5a6ng
jel5len
jel1le
jel4lo
je2l1t3
jel4t2r4
jel9ut
jel7va.
je8lv
jel1va
2jem
je4mi1a
je1mi
je4mit
je8m1p6
jem5p3l6
jem5se
je6ms
jem4s2ti
jem1st8
2j2e7na
je6n1d
4j1endr4
je3ne.
je1ne
je7ne8s
4jenet
jen5ged4
je6ng
jen1ge
jen8g5s4
2je1ni
je2n3k
4je4n1l
4j2e1no
4j2e1nu
je2n1y4
4j4e7næ1
2je5o6
2j2e1p
je3raf
j2e1ra
je4r5a6n1t
jer4d2e1p
jer1d2e
je4rd
jer8d6s
je3r6e
4jered
6jereg
6jerei
6jere4nn
je5r2ik
je1ri
je6rim
je6ri1næ1
jer4kes
j8er1ke
je2r1k
jer6k5l4
jer4kv2
jer1le7
je2r1l
jer6mu
je2r1m
jer6n5as
j2e2rn
jer3na
jern5sl4
jer6ns
je6r2on
j2ero
j6er4sp2
je4rs
j4er4t5s
je6r1t
4j2e1ru
6j2e1rå
5je2s1f
5jes1h
j8es4kil
je1ski
2je3s2p2
je2s4t2ea
jes1te
je6stim
jes1ti
je3str4
5je2s5u4t1
je1su
je5sve
jesv2
je4t3ag
je1ta
jeta6ka
je3tak
je9t8ar.
je7t6a1ra
je9t8a1re
je4t3ru
j2e1tr4
je4ts2
jet4t5an
je6tt
jet1ta
jet6te2r1m
jet1te
jett3o
jet6t5s6
j2e4t3v
jet4y8e
j2e1ty
2je1u6
4jev6a
je1v
je7v5a2r1k
je6vas
4jeve
jevi4s
jev7na.
jev2na
je2v1n
jev7ne
6je1ø4
j1f
4jg
j1h
4ji.
ji2b5b
ji1b2
j2i7e6
6j2ik
2j3i4n1f
ji7ro
j2ir
ji6sj
4ji1ø
2j1k
j4kap
6j1l
6j1m
6j1n
6jn.
j5ni
j2o
1job
5j4oc
jo4da.
j2o1d
jo1da
jo6dat
jo6dis
jo1di
jo4f2l2
jok9ker
j4ok1ke
jo2kk
jokk9o8
jo2l5t6
j3o2m1r6
j2on2
jo5ne
jo4n7n
jo6ns1
jons5a
jon1se4
jons3p2
jons5t
jon6s1ti
jon4str4
jon1su4
4jop
jo5ra
jor6dek
jo4rd
jor1de
jor6d7e2r3v
jord2er
jor6d7is.
jordi4s
jor1di
jor4d3o
jord3r4
jor8d5s
jords4l4
jor6du
j8o1re
jo4r2ie
jo1ri
jort6a
j2o6r1t
jor5tet
jor1te
jo3r4u
2j2os
jo3se
jo4ses
jo5s2t4ei
jos1te
1jou
jou4r5
j1p
4j1r
2j3s2
j1t
ju8a1re
1jub
ju1b4i3
ju9b2o
ju3de
j2ud
ju2do
ju7d6o1e
jue7ni
ju1e2n
ju4e5re
ju1er
jue9s
jug9l6e
ju4gl
5ju1go1
4ju2k
ju3k2a
ju3ke.
ju1ke
ju3ken
juk9sar
j2uks
juk2sa
ju2l
ju4la.
ju1la
ju5lar
ju5las
ju8l9ei
ju1le
ju4li.
ju3l4i
ju6lid
ju4l2ik
ju2l7i4nn
ju2l1l6
1ju6ng
jun7ge
j6u6n6i
8jup
j6u2p7s
ju6p5å6
ju4ra
ju7ras
3juris
ju1ri
ju9ro
ju3ru
5jury
1jus
ju5so5
ju1ta
j2ut5o
ju6va.
ju1v
ju6vak
ju1å
j5v
jy1
jy8de.
jy1de
jy2p3
jæ5le.
jæ1le
jæ3ra.
jæ9ran
jæ9r2es.
jæ1re
jæ7te.
j4æ1te
j2ø
1jø.
1jøa.
j6ø1a2
jø5b4
1j4ø1e2
2jøe1f
4jøeg
6jøei
2jøek
4jøe6nd
4jøe6ng
4jøe1ta
jø1f
jø3g2r4
jøg1
j4ø9kj2
4jøl
jø4les
jø1le
jøl6ver
jø8lv
jø5me
jøn5ne
j4ø4nn
jø5pa
jør4kel
jø2r1k
jør1ke
jør4sp2
jø4rs
jør2s5v2
jør4tel
jø6r1t
jør1te
jø3ru
jø6r7ut
jø1rø
jø4ses
jø1se
jøst2
jø5ta
j4øt
jø6t7av
jø3te.
jø1te
jøte4t
jø4t5e1ta
jø1tr4
jøt1te4
jø6tt
4jø1v
jø4vel
2jå
jå5ar.
jå1a
jå2ar
jå9ge.
j2åg
jå1ge
jå6la.
jå1la
jå4le.
jå1le
jå7le1s2
jå5let
1ka.
2kaa
5kaa.
5ka4a2n4
k3aa2s
ka3at
6kab4e1s2
ka1be
ka7b2o
4ka1b1r8
4ka1by
k6a3da
6kad2a1le
ka5dal
ka5del
ka1de
ka6d4el.
ka4de1ri
kad2er
4k1a2d1g4
kad2i7e
ka1di
2kadr4
5ka1du
4ka1dy
3kaen
k2a1e2
ka3fe
kaf5fe1re
ka2ff
kaf1fe
2k6a1fo
ka9fr2
2ka2ft
kaf5ta
6ka1fø
2kag
k5a4gi
ka1g2r4
2ka1h
ka5i4s3k
ka5i8s1m
6kaj2o
ka1j
k4a4ka.
ka1ka
ka4ke.
k2a1ke
ka4ked4
ka4kel
ka4kes
ka4ki.
k4a1ki
ka4kis1
6k5ak1sj
4ka6k1t
ka3ku
ka3lam
ka3lan
ka6la6n1t
ka3las
kal8d5s
ka6ld
5kal4d1t
k2a1l2e
3kalen
ka5le5v
5kalis
ka1li
k6a6l1k
5kalky
kal7la
ka2l1l
kal6l2ero
kal1le
kal8l4s
ka1lo
ka2l3p2
kal7s6v2
k2a8l1s
kal4v4e5s
ka8lv
1kam
ka6ma.
ka1ma
ka8me.
ka1me
4k3a4m4e1ri
4kamm2el
ka2m1m4
kam1me
kam4p3i
ka8m1p
kam4pr6
7ka1mu
1ka1na
k6a8na.
4kan1da
ka6nd
kan6da.
5k6an3de.
kan1de
1ka1ne
k2a5ner
ka1n4i
ka5nin
2k1a4n1l
4k1a4n3m
k3an1no
ka4nn
5ka1no
ka4no.
ka8nom
4kansa
ka6ns
6kan1si
3kansl4
1ka6n1t
kan6t7e6nd
kan1te
kan6t9r4
2kap.
5kapad
ka1pa
ka4pak
3kap6as
ka5pe
5kape2l1l
kap6el
4kapen
3ka1pit
ka1pi
1ka2pp
kap3re
kapr6
kap4s3t2
ka2p2s1
1kar.
1ka1ra
4karab
4karam
k2aran5
ka3rav
1k1a2r1b8
4karbe6i
kar1be
4k1a2r2ea
ka1re
ka6rek
ka5rel
1k6a2r1f
5ka2r1g
ka4ri.
k2a1ri
ka6rid
6k3ar3ki
ka2r1k
6k5ar6k1t
1ka2r1m
k2a2r3n
ka3rol
kar3om
kar5pe1s
ka2r1p
kar1pe
kar2p3s
3kar3ri
ka2r1r
7kar1sj
k2a4rs
kar1s4t
1ka6r1t
kar5ti
5karu
4ka1rå
1kas
7k6as.
ka5se4i
ka1se
ka3sek
ka2s5e1v
kas2ju
ka1sj
6ka5s2j2ø
ka4sk
ka1s3ka
kasko5
6kasp2
ka4spi
ka4s1s6
kas6sad
kas4sak
4k3as1si
kas4so
ka4st
8kas5to
kas2t3r4
1k4a7t6a1
ka3ted
ka1te
3kateg
ka3tet
ka1to
ka4t5op
4ka1t4r4
k2a4t7s
kat5ta
ka6tt
kat4tel
kat1te
kat6te2r1m
katt4e5s
kat4t4il
kat1ti
kat4tri
kat1tr4
ka4t5y
k7au4r
2kav
ka7v4es
k2a1ve
k9avis
kav8l9u
ka6v1l2
9kay
6k1b4
2kc
k5ce
kcen4
2k1d
k5d6v
1ke
2k2e1a8
ke9al
2k2e1b2
4kebr8
4kebuk
ke1b2u
4k2ec
ked4
ke5da
4ke5d4ag
4kedal
5ke5dan.
9keda6n1t
ke5de
6ke2dei
4kedem
4kede1s
7ke3d6es.
2ke1di
2ke5dr4
ked8sk
ke8ds
ke1du6
4ke1dy
2ke1dø
2keeg
ke3e2
2keek
2keel
ke4e1p
ke7e2rn
2kee1v
4ke1f
k3e2ff
k5e8ft
2ke1g
k3e4g1g
keg8ga
5kegla2d1h
ke3g6lad
kegl2a
ke5h
3kehet2
4kehj
2kei
k3ei2d
k1e2ig
k4e5il
k4e1im
k6e1in
k2eis2
2ke1j
2kek
ke1k2l4
k4e3k1n2
k1eks
k5e6k4t
k6el.
2k2e1la
ke3l4ak
5ke2l1al
ke6la6ns
ke6lat
kel5eier
ke1le
kele2i7e
kel5e2ig
kel7e2ik
kelei8ke.
kelei1ke
6ke7l2eis
ke2l3e4l
4k5e2lem
4kele6ng
k4elen
4ke5l2e1p
4keles
7k6elet
4kele1v
ke9l2i8e8
k2e1li
keli9en
3ke3l2ig
4ke5lig1n
4keli4n1j
ke4l5i6n1t
4kelis
ke4li1se
5kelit
4keliv
ke2l5l
4kelof
k2e1lo
6kelov.
ke1lov
4kelove
ke8l1s4
kel2s7k2ar
kel1ska
kel5s1l4
kel5sp2
kel5st
kel3s1v2
kelsva9
ke2l2t
kel1t3e
6ke3lua
4ke1lu2e
kel9u1k2a
ke3luk
1kel5u1ke
ke4l3ur
6ke1lu1v
ke8l5v
2k4e1ly
2k2e1læ1
2k2e1lø
kelø8pa.
kelø1pa
6k2e5l4å
kelå7re
ke2l1år
2kem
ke4mit
ke1mi
5kena.
k2e1na
6kenam
ken6ap
ke4nas
5kenat6ts
kena6tt
ken5de
ke6nd
6k5en6den
6k5en4d2er
4k1end5r4
ke3ne
4ke2n2e1b2
4ke2nek
4kenel
4k4enem
6kene6tt
4k4ene1v
3ke4n1f
ke4ni
5k6e4n1h2
4ke5niv
ke2n3k
ke4n7n
kenne4l3
k2en1ne
ke4nom
k2e1no
ke6n1s2
4k5en1se
ken1s6t
ken7te
ke6n1t
ken5t6r4
4k3entu
6ke3ny
6ke1nø
2ke3o6
ke3on
4k2e1p
5kepi.
9kepia
7kep2ie
6kep2l6
kera7l
k2e1ra
4keram
ke4ran
ker5a6n1t
6ke2rau
4kered
ke1re
4kereg
ke2r3ei4d
4ker2e2ir
6ker2eis
4kerek
4ke5rem
kere6n
6kere1ne
ke6r5e6ng
4kere4nn
4ker2e1p
ke4r5e6r1t
ke1rer
ke3res
4ke2r3e4s1s
4kerest
4ke1re1su
4kere4t
5ke5ret.
5ke4r3e1ta
7ke7re4ts1
3ke1ri
ke4r2if
6kerik.
ker2ik
4keri1ke
6keri6k1t6
ke4rim
ke4r4i1ne
6kerin1gi
ke3ri6ng
6kerin1gj
ke4ri1næ1
ke4ris
8ke5ri6st.
8ke7rista
6ke5ri2s1te
3ke2r1m
7ker1op
k2ero
5ker1o2r
4ke3r2os
kero6se.
kero1se
ke3r2ot
k4e6r1t
ker5to
ker3t4v
ker3un
k2eru
4ke1r4us
keru6se.
ker2u1se
kerø6re.
k2e1rø
ke1rør
kerø1re
8ke4r8øya
6ke4r6ø4y7e
2k2e1rå
5k2es.
4ke1s2am
ke1sa
9ke5sam.
7kesd2a1le
ke8s9d6
kes1da
5kese.
k2e1se
k5es2el
ke5s2il
ke1si
4ke3s2it
2ke1sj
k9e2s8ka.
ke1ska
4k5e2s1ke
kes8ke.
4ke1ski
4ke1skj2
6ke7skå
kesle6ge.
kesl4
kes1le
kes3le1ge
ke4s5ped
ke1s2p2
kes1pe
5ke4sp2ot
kes1p6o
5ke4s1pu
5ke6st.
4ke3sta
5ke6s5te.
kes1te
4ke5s2ted
4ke5s2t4ei
kes6tem
4k6e5s4ten
ke3sto
4kestri
kestr4
4ke5stru
4ke1stu
4ke1stø
ke4s5un
ke1su
kes1v2
2k2e3sy
kes1å
2ke1ta
keta6ka
ke3tak
keta6le.
ke3tal
ke3t2a5le
5ket2a8l4s7
k6etar
k2e3te
5kete.
4ketek
7k4e2t1h
2k2e1ti
2k2e1tj
2k2e1to
2k2e1t2r4
ke4ts2
ket6t7e4n1h2
ke6tt
ket3ten
ket1te
ket8t4es
2k2e1tu
2k2e1ty
ket4y6e
4k4e1tæ
ketø2y6s5
ke1tø
ket4øy
2ke1tå
2ke1u2
ke5ur
2ke1v
kev2a
keva8ne.
kev4a1ne
5kevas
3kevel
kevi6sa.
kevi1sa
k1e2vj
kev6ja
kev4je.
kev1j2e
k3e2v1n
kev6ne.
kev1ne
ke3vr8
kevæ8ra
ke1væ
ke2y2t
key1
6ke1æ2
2ke1ø4
2ke1å6
1ké4
ké1t5
2k1f
k5fi
4k5g2
k3ge
k5g1h
k1gu4
4k1h
kh5ak
kh2a
k4har
khe4o6
khju8l8s
khju6l7
k2h6m
ki3a2r
k3ide3o6
ki1de
4k1i4dol
ki1do
ki3dr4
k4ie2
kie5re
ki6el
ki3er.
2k2if
k2i5g4
ki6ka.
k2ik
ki1k2a
kikk8s9
ki2kk
ki1k8l4
ki3k2r6
kik4s
ki8la.
ki1la
3kil1de
ki6ld
ki4le.
ki1le
ki5li
4ki2lj
kil9ja
kil6le5st
ki2l1l
kill6es
kil1le
ki7l2oa
ki4l2og
ki3l2os
ki4lov
2ki2l1t
ki7l1å8
ki8ma.
k4i1ma
ki4me.
ki1me
5ki1mo5
ki4mo9l
2k1i8m1p2
kin9a6nd
k6i1na
kin2an
ki4na2r1k
ki5n6as.
6ki6nd
k4i5ne
ki6ne.
2ki4n1f
1ki6ng
king5a6nd
kin1ga
kin3ge
kin5i6ng
ki1ni
2ki4nn
kinn3s2i8da
kin6n1s4
kinn1si
kinn3s2i8de.
kinnsi1de
kinn4sp2
k2i2no
2ki6n1s
kin3s2i6de.
kin3si
kinsi1de
2ki6n1t
kin9ti8me.
k4in5ti
kinti5me
k6i6nu
2k1i6n1v
k4io4
4kiol4
7ki2os
2kip
ki5pe
ki4p5l6
kip5pe
ki2pp
ki2p2s3
1k2ir
ki5re
ki5ri
4ki4rs
ki3se
ki5sko
ki3s1mu
kis1m
ki1s2p2
ki5s2tav
ki5str4
ki6ten
ki1te
ki7t2i
5ki1to
ki4t2on
4ki1u
k6iv
ki4vek
ki1ve
ki4v5e4n1h2
ki1ven
ki6vi.
ki1vi
ki8v3s
kj2
3k8jan
kjap5pe
kja2pp
6k7j2a2r2n
kj4e7fj
kj2e
k2je1f
kj8e7f6r2
4kje4g1g
3kje6ld
k2jel
kjel7leg
kje2l1l
kjel1le
5kjemas
k2jem
kje1ma
3kje8m1p6
6kje2n3k
kje9n1y4
kje1s
kj2e6t5r4
kje6t5t
6kjor
kj2o
kju5le.
kju2l
kju1le
kju5len
kju9r
3kjæ2r1l
k5jø1di
kj2ø
1kjøp
3kjø1ri
kjø4tr4
kj4øt
2kk
k1ka
k2kab
k2k3a2ft
k2k1ak
k2k3al
k2kam
k4ka4n1b2
kk5a6nd
k8k4and.
k6kan5den
kkan1de
k4k5a6ng
k4ka4nn
k4ka1o
k4k1a2r1b8
k6ka2r1g
k4k7a6r1t
kkar6ta.
k5ka4st
k1kas
k6ka1su
k6ka6tt
k2k1au
k2k1av
k6ke1do
k1ke
kked4
k4ke1du6
k2ke3e2
k2ke5h
k2k1ei
k5k6el.
kke6lei
kke1le
k4k5e4le1v
kke2l3t4
kkelu4
kke4luk
kkel5un
kke6nan
kk2e1na
k5ker.
k4kerei
kke1re
k4kerel
k6kere6n1t
kkere6n
k4ker2ig
k3ke1ri
k4kerit
kke5r6u6nd
kker3un
kk2eru
k4ke1sa
k5kesau
k4k2e1se
k4ke1si
kke3s4l4
k4ke1s2p2
k6ke7s6t2ad
k4ke3sta
k4k2e3te
k5ket8et
k4ke1tø
k4k9g2
k1ki
k6k3i6nd
k2k3i4n1f
k2k9i4nn
k2k3i6n1s
k2k3i6n1t
k2k2ir
k1kja
kkj2
kkjek8
kkj2e
kk5je6n1t
k4k5j2e2rn
kkje7t
kk5ju
2k2k5k
kkla4g
kkl4
kk3lan
kk8lar
kk7lau
kk1lo
kk1ly
kk3læ1
k4k1n2
kk5ne
k3k5ny
kk7s
kk6s5v2
k1k2o1d
k1k6o3e
k4k1of
kko6l2ig
kk2o1li
kk5o4m3l
k1kom
kk7o6ms
k4k9o2m5t2
k6k2o1o
kk1op
k1k2o7s
k4ko9si
k6ko2sk
k4kost
kk3ove
k4kra
kkr6
kk5ram
k4k3rap
kk3r2ea
k4k5rei
kk3ren
kk3res
k4kr2i6k
kk3ro
kkr6o8e
kkr2o4s
kk3ru
kk4r4us
kk7rø
kk7rå
kks6al
kk1sa
kk4sar
kk5sed
kk1se
kk3s4ei
kk4sin
kk1si
kk1sk
kks4kj2
kks4l4
kk3sla
kk7s1le
kk5s1li
kk3s1pe
kksp2
kk1s2t
kk4s5tak
kk2s7t2e1p
kks1te
kks1t4r4
kk8s7va
kk5s4å7
k6k3t2
kk5uk
k4ku1n6i
k1kun
k2k1u6t
kk1v2
k2kvo
k1ky6
kkyl4
k1k5yr
k2k7yt
k2k1ø
k8køl
kkø6r
kkøy6
kk1å2
kl4
6kl.
4k1la.
4k5la2g1t
2klak
kla9ke.
kl2a1ke
kla4m2el
kla1me
3k2las
k7l4a5ti
kla4tr4
kleby7t
k1le
k2l2e1b2
kle1by
3k4le6d1d
k4le4d2e7b2
kle1de
kle4de1s
2kleg
kle2i9e
k7le2ik
k4leiv
k2lem
5k6l4em.
7k4l2e1no
k6ler2ik
kle1ri
kle1s7j
kle5s2no
kles1n
k2le4s2p2
kle5s1ti
kle4s7s8
2klet
8kli1a
k1li
4klie.
kl2ie
4k3l2ig
1k2lim2
3k4li1ni
k5li4n1j
1k2lip
4klit
klo4n7a8l
kl2on
klo1na
1kl2os
2k1lov
k5l6u6k1t
4klun
k4lu6n5t
k5lyd
6k7ly1f
3k4ly6ng
klys2e7te
kl2ys
kly1s4e
k2læ1
klæ5re3s
klæ1re
klø7na
k1lø
k6lør
6kløs
klø9va
klø5ve
6k1lå2n
klå5re
k2lår
2k3m
k5m4a4nn6
k1ma
km6ål2
k1må
2k1n2
3k2nap
k1na
kna5t4re
knatr4
kne4b3l2
k1ne
k2n2e1b2
k4ne1di
5k2ne3e2
kne8se.
k4n2e1se
k4nest
k4n5f
3k4nip
k1ni
knip1pe6
kni2pp
kn4i2t3
3k2niv
kn2o7g
k1no
kn4o6kl4
k2nok
5k2nop
kno9ta
kn2ot
kno5te
3k2nu
4k3num
3k2ny
kn2y4s
k2nø
knø9le
kn3øy1
9koa.
k2oa
4ko1a4k
9koa1ne
ko3a6n
ko7ar
ko2b3
ko3b4o
4k2o3br8
ko5da
k2o1d
4ko6d1d
ko4de.
ko1d4e
3kodek
5koden
2ko1dø
k6o3e
3koe6f
4koek
4koe2l
4ko2es
4koe1v
ko4g5e6ng
k2og
ko1ge
kog2en
kog9g2er
ko4g1g
kog1ge
kog3l
ko4gre
ko1gr4
kogst6
ko8gs1
ko4gu
ko6gå
2koi
koi4e5ne
koi1e4n
ko2ie
ko7i6k
ko1in
2k4o1j
ko8ja.
ko4ka.
ko1ka
ko4kab
ko4ke.
ko1ke
ko6kel
ko1ki
kokk6o
ko2kk
k3okku
4k4ok4l4
4ko3ko
2ko1ku
kol1b4a5
ko2l1b2
2k2o1le
ko4leg
ko4lei
ko6lel
ko4let
ko4le1u
3kol1le
ko2l1l
ko5lo.
k2o1lo
ko2l5p
4ko1ly
2ko1lå
1kom
ko4ma.
k2o1ma
4ko2ma1f
ko4me.
k2o1me
6ko2m5g6
ko4mi.
ko1mi
4komil
4komi8s9b4
ko4mi3se
2k1o2m1r6
4kom1se
ko6ms
kom3sl4
kom5so
kom4st
4ko2m5t2
4ko2m1u
4ko5må
k6on
ko4na.
ko1na
ko6n3d
kon6dr4
kon2e5l6å
ko1ne
4koniv
ko1ni
ko6n3s2
kon6s7v2
k2on6t2on
ko6n1t
ko4nu
1k2o1o
4koom
k2o5pa
ko5pe4rs
ko2per
ko1pe
4k2opet
ko4pi.
ko1pi
ko4pi2p
k2o4p9l6
4k2o3po
kop5per
k4op1pe
ko2pp
2k2o1pr6
ko1ra
ko4ra.
kor6da.
ko4rd
kor1da
kor4d3o
kord5s4ø
kor8ds
kor4du
k8o1r6e
6kore1f
4ko7r1ei9
ko5rem
ko7ret
ko3ri
ko6r2ik
4ko2r1m
ko5rol
k4oro
6kor5pa
ko2r1p
6kor1pe
1k4o2r3r
kor4sal
k2o4rs
kor1sa
kor6s5i4nn
kor3si
kors1in
kor4s5l4
kor6st4r4
kor1s2t
kor4sv2
kor5t4es
k2o6r1t
kor1te
kor6top
kor4t5r4
kor4t5s4
ko5r1u
ko6r2u1me
ko5r4um
1k2os
2ko1sa
ko6sa.
ko4sed
ko1se
ko4sek
5kose1le
kos2el
ko5s2en
ko4ses
ko9si
kosi9d
ko2sk
ko1s2l4
4kos1mu
kos1m
2ko3so
2ko1s2p2
ko6sta
ko1st7as
kos6t5e4rs
kos1te
kost5j
4ko1sø
kot4es7
k2ot
ko1te
ko5t4i
4kot4il
4ko1tr4
ko6tre
ko4t2s
kots5tr4
kot1st4
kot4ti
ko6tt
kot6t4s
kott1s5t4
4ko1ty
2kou4
kout3
kou7t8a
ko1va
kove1r7e6
kove2r
4ko2vr8
1ko8v4s3
4ko1ø
4ko1å
6k1p
kr6
k3rad
1k4raf
5krag
3kr2a2kk
kra7ni6e7n
kra1ni
kran2ie
6kra2nk
3kra6ns
4krap
kra9se
7k8ra4sj
kra4s5s
kra4to
kra4u
1k2rav
4kred.
3kre3di
4k1reg
k1re1gi3
4krei
k2re2i9e
kr2ei5s
6krek
kre4k1li
kre1kl4
2krel
k5r2e9la
4k3re4nn
4kre6n1t
6kre3o6
k5repr6
kr2e1p
5kre2ps
5k2re1sj
4k3re4s1s
kre1ta9
6k7r2e8t1n
k4re4ts1
k3re6tt
2k2ri1b2
4kr2if
7k6r2ig
kri8g6s5
kri9ga
4kr2ik
kri5ke
kri4kj2
krik6ka
kri2kk
1krim
kri4me
3k6ri6n1s
krin6s7t
k2ri2p
1k2ris
7kris2e5ne
kri1se
k2rit
3kri3te
6kriv
k2ro
kro5b4
kro5d4e
kr2o1d
k3r2o1fo
k4rofor
kro2k7k
k6ro1ni
kr2on
k5ro4n1l
kro4nom
kro1no
k4rop
kro2p9n4
k2ro2pp4
k4rost
kr2os
kro9t8e
kr2ot
kroten9
kro3v
kru3i4
kr4um3
kr4u5sa
kr4us
krut1t5i
kru6tt
k6ru3t1r4
2k5ryg
kry7pa
kry5p8e
kry4pi
3kr2ys
krø5ke
6k5rør
krø3v
4kr6å1d
krå8da
kr4å1e6
5kråk
krå5le
k4rål
kr6å9m
krå7ne
k1sa
k3sa.
k2s3ad
ks3a2l1t
ksa5me
k1s2am
k3s2a5ne
k4sa6n1s
k4s3a6n1t
k3s2ar.
k7sa1ra
k6s3a2r1k
k7sas
k1sc
k2sed
k1se
k2se3e2
kse6ge
k2s1ei
k5se6k1t
k5s2e1la
ks2el
k4s3e5le1v
kse1le
ksen5to
k7s6e6n1t
k4s1e2r1f
ks3e2r1k
k5se1rol
k2s2e1ro
k4se1sa
k4ses6k
k4se3s1m
k5s4e1so
kse1s4p2
k5se4s1s
kses6s4el
k1ses1se
kses8ser2ie
ksess2e1ri
kses4sp2
kses4s2t
kse6te.
ks2e1te
ks1fø4
k2s1f
k5sia
k1si
k2s2if
ksi5ko3
k3s2ik
ks3i6nd
k3si6ng
ksi7ni
k2s3i4nn
k4s3i6n1s
k7s4io
ksi3st
ks2is
k4si1t2i
k3s2it
k4s9kab
k1ska
k6s3kal
k6sk4io4
k1ski
ks1kj2
k2s5k2l4
ksko7na
k6s1k6on
ksko5ne
ks1kr6
ksk8u
ks9ku.
ks7ku2a
ks8kut
k2s1k6v2
ks1l4
ksla8ga.
ksl4a1ga
ks5lo
k7s6lu
k8s7lug
k2s5løs
ks1lø
kslø8va
kslø8ve.
kslø5ve
k5s4mak
ks1m
ks1ma
ks4m2el
ks1me
ks1må
k5s4no
ks1n
k7s8nø
k1so
k6s7oa
k6s2o1d
k4s2og
k4so2m1b6
k2s1or
k2s2ot
k2s1ov
k3s2pal
ksp2
ks1pa
ks3pek
ks1pe
ks5p4io
k1spi
ks3p2ir
kspor6t5r4
ks1p6o
ksp2o6r1t
6k6s5p2o4rs
ks1pr6
k8s9r
k3spy
k4s1s6
ks3s1m
k5s6t2ad
k4stak
ks3tal
ks5ta6n1t
ks1v2
k7s8vak
ks6tav
ks4te4da
ks1te
ks2ted
ks4teg
k3s2t4ei
k4stek
ks2t3e1v
kstev6ne.
k5ste2v1n
kstev1ne
ks4t4il
ks1ti
k4stin
ks1tj
ks1tr4
kst6ran
ks4t5rek
k6stren
k6st2rer
ks4tri
ks4tro
k3s9tum
k5stus
k4s3ty1v
k1sty
k2stå
k1su
k6sun
k9s8v2a1ke
ksva8ne.
ksv4a1ne
k6s5veg
k7s6ve6r1t
k5s4vin
ks5w
ksy8na
k1sy
ks2yn
ksy8s7m
k3s2y1s
ks5ær
k1sæ
ksø4ke.
k1sø
k3s6ø1ke
k6s4øl
ks1å
k3så.
k4så2p
kså2r4
ksåt4
6k1t
k5ta.
kta7f6
k4tag
kta6ka
k4t3aks
6k4ta6k1t
kt8a6la.
k4t3a2na
kt6a9na.
k2t3a4n1l
k4t3a4nn
k4ta6n1v
kt3a2pp
k9t4ar.
k4t3a2r1r
k4t3a6r1t
k6ta4s5s4
k5tast
k6t7a6t3l6
kt5a2v1h
k2tav
kt5a6v3k6
k6t3a6v1l2
k3ted
k1te
k4te1da
k6te3e4
k4t4e1fø
k2te1f
kt5e4ge
k2te5i
k6t4e1ka
kte5le
k6t5elsk
kte8l1s
k4t5e4lit
kt2e1li
k2tem8
k6t7e2m1n
k4t3e4n1h2
k9ter.
kteri1e5n
kte1ri
kter2ie
kteri5e7ns
kte4r5s6
k7t2es.
kt4es
k6te1sa
k6t2e1se
kt5e2s1ke
k4te1sk
ktes6ke.
kt5es1ti
k4t5e4ta
k4t2e3te
kt5e2v1n
k2te1v
ktev6ne.
ktev1ne
k5ti1b2
k1ti
k3t4il
k4t3i6n1s
k5t2ir
ktis5t
kt7i6te
k2tit
kti6v7e6nd
kti1ve
kti1ven
k2tja
kt7ju
kt2o9a
kt5o2ff
k2tof
k6togra4m5
kt2og
ktog6ra
kto1gr4
k4t2o1li
k2tom
k2t5o2m1f
k2t1op
k9t4or.
k7to1ra
kt5ord.
k4to4rd
k5t8o1re
ktor2i9e8
kto1ri
kt2o4r7s
k2t1ov
kt3ral
ktr4
k2t1re
kt2ro
kt2r2o5s
k2t1ru
kt4s5a2m1b
k4ts
kt1s2am
kts5ar
kt5sek
kt1se
kt7s2em
kt4s5er
kt6sin
kt1si
kts5k8ra
ktskr6
kt5sku
kt2s4k6v2
ktsle6ge.
kts1l4
kts1le
kts3le1ge
kt6sok
kts1o
kt4s1pa
ktsp2
kt5spre
ktspr6
kt4sta
kt1st4
kts6t2on
kts5top
kt4stå
k6t3t8
ktu9er.
ktu1er
k4tu2k
k4t7u6nd
k2t1ut
kt7y2r1k
ktyr8ke.
ktyr1ke
kt2ør7
ktø1r8e
k6t5øs4
k2t1øv
k2t3år
ku2a
ku9an
ku4be.
ku1be
ku3b1j
2k2ud
ku4dal
ku1da
ku4er.
ku1er
ku2e4r5a
ku4et
6kuf
ku4f3l2
6kug
k5u4gr4
ku7is
kuit6
6kul1di
ku6ld
kuld3r4
ku4led
ku1le
ku4leg
ku6lei
ku4lem
ku5len
ku4let
ku4le1v
ku5l4i
ku6li.
3ku2l1l
1k4u2l1t
ku4man
k2u1ma
kumen6tal
k2u1me
kume6n1t
k2u6mi
ku6m2s1
3ku1mu
1kun
kund5s6l4
k4un8ds
ku6nd
2k1u6ng
kun4ge.
kun1ge
k6u4nn
ku4o
1kup
ku4pe.
ku1pe
kup4p1l6
ku2p1p2
ku7ra
ku4rek
ku1re
ku8r7o
ku7r6op
kur2o8pa
1ku4rs
kur6sk
kur4sp2
kur4s3t
kur2s3v2
ku6r3t
ku5ru6
kuru9ken
ku4ruk6
kuru4ke
kuru7ker
1ku2r1v
kur4v3i
1kus
5kus.
ku7sa.
k4usa
kus5a6k
kus5ar
5k2u1se
ku4s5el
ku4ska
ku7s6pe
kus1p2
kus3t
6k1ut.
ku1ta
4ku5te.
ku1te
k2u5to
ku6t7r4
2k1u4t1s4
1ku1u8
kuøy6
ku1ø6
kv2
kv2a8ke
k4va1li
k6va4rd
kvari6e5n
kv2a1ri
kvar2ie
5k6va6r1t
kva9se
kve3d2
k2ve7e2
k6v4eil
kve4i9se
kv2eis
1k4vel
kv2e5la
4k3ve4rd
5kv2e2rn
kver6n6s8
2kv4es
kves5t
7k6via
4k1vid
3k4v2ie
kvi4e1ne
kvi1en
kvi5er
kvi7l1a
3kvi4nn
kvi5se
7kvist
2kviv
1kvo
k1v2og
6k1vok
k4v2ot
k2vu
4k3væ
k1vå
k7we2
k5wu
ky5a
ky6el
k4y1e
4ky1f
ky4f5le
kyf2l2
2kyg
ky4leg
ky1le
2k3y6ns1
kyn6skjer
kyn2sk
kyn5skj2
kynskj2e
ky2p1
ky4p5r6
1kyr
kyrie5ne
ky1ri
kyri1e4n
kyr2ie
kyr8ke.
ky2r1k
kyr1ke
ky8sa
k2ys
4ky1s4e
ky6se.
kyse8te.
kys2e1te
ky3skr6
kys6sk
ky4s1s
1ky4s2t
kys3t3a
kys4tel
kys1te
2kyt
ky6ta.
ky1ta
ky2t4e3s2
ky1te
k1æt3
1kø.
kø2ar
k6ø1a2
1k4ø1e2
kø4en.
kø2er
6k1øk
kø4le
k1øn
køn6skjer
kø6n2s
køn3skj2
kønskj2e
k3ø2r1r
køs4
kø5se
køy7ar.
k7øyd
køy7e6ne
køye4n
kø4y1e
1k6øyr
4kå1ke
kå3le
kån8da
kå6nd
kå5ne
kå4pe.
kå1p2e
kå7pen
kå9ras
kå1re
kå1ri
kå2t5
k5å6t6t
1la.
2laa
la1b
4la1b4a
la6b1l2
1lab2o
2l3ab2on
4la1by
4lad.
l6a1de
la4dem
4l1a2d1g4
lad2i9e8
la1di
2l1a2d1m
2ladr4
la8d3s4
1l2a1e2
2laei
4lae6n5t
4la3e1p
2laf
la3f2l2
4la4g1g
la1gi4
6lag1n4e
lag1n
l4a2go8
la2g5om
la2g1r4
lag5san
la8gs1
lagsa2
lag4sj
2lah2a
la1h
2la1in
la5ka
la4ke.
l2a1ke
4l2a1kj2
l2a2kk
la2kr6
l7ak1sj
l5ak6s1l4
4la6k1t
lakter2ie6
lak1te
lakte1ri
la5kø
2lal
l1a6l1k
la5mab
la1ma
4la2m1b
la4mes
la1me
la4met
la3mo
l8a8mu
4l3anal
la1na
lan2c
lan6das
la6nd
lan1da
lan6d2e7b2
lan1de
land3r4
l6a3ne
3lane.
4lanet
lan6gel
lan1g4e
la6ng
lan4gem
lan6ge2r1m
lan3g2er
lan4gr4
lan8g3s4
lang4s5e
lan6g5ø6
4la4n1l
4la4nn
l3an1no
la4nor
la1no
6l5an1si
la6ns
lan5ti
la6n1t
4la6n1v
l3an5vi
2la1o
la6pal
la1pa
la7pi
la8pl6
lap5sa4
la2p2s1
lap5sen
lap1se
lap5s4i
1l8ar.
6l7arab
la1ra
2l1a2r1b8
l8a1re.
la1re
4l7a2r2ea
la9red
la5rem
l6aren.
4l3a2r2e3na
lar4e6t
la6r7e1ta
5l6a2r1f
la7ria
l2a1ri
lari1ar
lari6e7n
lar2ie
4l3ar3ki
la2r1k
l2a2r1m
l2a2r3n
6l7a2r1r
4la6r1t
lar6ta.
lar7v4et
la2r1v
la6sc
la5se
las2i9e
la1si
la2sk
7la5skj2
4las1m
la5s1mi
4la4sp2
las6sak
la4s1s
las6s2am
las6sat
las6s4el
las1se
l6ast
4lastr4
las3v2
l4a1ta1
la4t5a6ng
6la2tau
la4teg
la1te
lat6ek
la4t2e1no
l4aten
la2t4e5s4
l4a5ti
la6ti.
4la2t1m
la1to
lat7ra.
la1tra
latr4
lat9ran
lat5ra5r4
lat4t5i4s
la6tt
lat1ti
lat4tra
lat1tr4
latt6u
l8a8u7a
2la2ud
l4a6ue6
4laun
4laur
la2u7se
lau6st
2laut
2lav
l6a4v5al
la1va
la4vel
l2a1ve
l5a2v1h
lav5i4nn
la1vin
6la8vs
7l4a1vå
1law
la6y5
2l1b2
lba3de
l1b4a
lba2d
lba4k
lba5ke.
lb2a1ke
lba4ne.
lba1ne
l5be
lb2e9na
lbe8re.
lb4er
lbe1re
l4b2oa
lb2o
l6bu1h
l1b2u
lb4y4e
l1by
l5b4å
2l1c
l4ce.
6ld
l7da.
l1da
ld5aks
ld5a6k1t
l9da6nd
l7d6a6t5o
lda6t5y
l2d2e7b2
l1de
l5den
l2de5o6
l2d2e1p
l3d2er
l7der.
ld2e1r7a
l4derek
lde1re
l6der2ik
lde1ri
l6d3erk2læ1
lde2r5k
lderkl4
lderle9g
lde2r1l
lder1le
lder5s6te
lde4rs
lder1s2t
lder6s5ti
l4des1h
lde1s
l6destr4
ld4es2t
l9dé
ld6is7k
l1di
ld2o9a
l1do
l8d5oks
l2dol
l4d8o1re
ld5o2v9n
l2dov
l3drak
ldr4
ld3ran
ld5ras
l6d1re.
ld7reg1n
ld5rer.
l4d1rer
l8d1r2es.
ld1ri
l7dry
ld1rø
ld1se8
l8ds
ld4sek
ld4s2el
ld4ses
lds1k
ld4ska
ld1s4kj2
ldsl4a8ga
lds1l4
lds6leg
lds1le
lds4let
ld4s9m
ld5s4om
lds1o2
lds3tr4
ldst4
ld6tus
l4d1t
l1dø2
ldø5d
ldø5l
ld7øy
1le
2l2ea
le7ag
le3a2k
le1al
lea5la
3leas
le5at
2l2e1b2
le4bem
le1be
le6bosta
leb2o
leb4o1s
5lebæ2k
l2ec4
2le1da
5le2dar
led4dø2
le6d1d
4led2e7b2
le1de
le4dem
6led2e1p
le5d2er
le3de1s
2le1do
3le2d1op
5le2d1ov
le4dro
le1dr4
le5dry
le8d1s2
6le1du
4le1dy
2le1dø
4le3då
2le3e2
6leei
6l4e1fa
le1f
lefa6ne.
le5fa1ne
4l1e2ff
2l4e1fi
4l2ef2l2
4lefor
le1fo
4lef2ot
2l8efr2
l1e8ft
6le1fy
4l4e1fø
6legap
l2e1ga
5legas
3le1ge
le4ged4
le9g2en
le4g2e1ra
leg2er
le9geran
le9ge9ras
le4g2ero
le4ge1ta
le4g2e1v
7leg1gi
le4g1g
le3gi
6leg2if
4le1gj
2le1g2l
le4gol
l4e1go
4legre
le1g2r4
le8g3s4
2le1gu
2l4e1gå
2le1h
6lehal
leh2a
6leha2m1r6
6lehan
6leh4au
6leh2a1ve
le6i2do
l6eie.
le2i7e
lei5en1de
lei1e2n
lei2e6nd
lei4e5ne
l6ei8et
lei8g6d
le2ig
lei4g2er
lei1g6e
lei7ger.
lei4get
lei6g4h5
leig6na
leig1n
leig8n4e
2lein
le3i6n1t
lei6r7u
l2e2ir
leis7t
l2eis
l6eit
2le1j
2l4e1ka
le4ka.
6lekan
le1k6e
le3ki
2l4e1kj2
lekk7s
l2e2kk
2le1k2l4
2l4e2k1n2
lek4na
2le1k2o
2l2e1kr6
4l3ek1so
4l1eksp2
lek4t5o4rd
l4ekto
le6k1t
lekt4s5t4
lek4ts
2le1ku
2le1k2v2
4le1kø
4le1kå
2l2e3la
8l9e6ld
2le1le
l3e2lem
4l5e2lg
5le3l2ig
l2e1li
2l2e1lo
3le8l1s
4l3elsk
le1lu
4le3luk
l5e8lv
2l4e1ly
4l2e1læ1
2l2e1lø
lelø6pa
4le1ma
l8e3me
le8me.
4lemet
8leme6trisk
leme5tri
lem2e1tr4
leme4t3ris
8l9e2m1n
lem8na
2le1mo
lem9ped
le8m1p
lem1pe
lem5pe1s2
3le2m1r6
lem4si
le6ms
lem4s5ø
2le2m1t
lem5ti
2le1mu
6le5my
4l6e1mø
2le1må
le4na.
l2e1na
le4nal
4lena2v
len1d8a
le6nd
len9dan
6lenden
len1de
l6endre
l1endr4
4lend1ri
4lened
le1ne
4l4enem
4l4ene1v
l4en8g1d4
le6ng
len4g5r4
len1g9u
le5ni
4leniv
lensa4
le6ns
len4s5ak
4len2sem
len1se
len5ses4
len4sta
len1st
len4tam
le6n1t
len4tr4
len8t9ra
2le1nø
4l4enå
2le3o6
5le2ol
2l2e1p
le5pa
le4p6el
le1pe
le8p9enden
lepen3de
lepe6nd
le2p3j
le8p1la.
lep2l6
1le4p1le
4le1po
4lera1b4a
l2e1ra
4leram
le2r3a2m3b
le4r5d
l8e1re
le9re.
6ler2ea
4lere1f
4lereg
4le9r8ei
4lerek
le5res
4ler4e1so
4le1re1su
4lere4t
4lere1v
9leri.
le1ri
7ler2ie
le6rie1i
le7r6i1na
le4riv
4l5er1næ1
l2e2rn
le5ro.
l2ero
4le1rom
6lero1pe
ler1op
le3r2os
4le3r2ot
7le6rs.
le4rs
l6er1s4p2
ler7te
le6r1t
l2e1ru
4le1r4us
leru8se.
ler2u1se
2le1ry4
5le2r1yr
2l2e1rø
2l2e1rå
5l6es.
le4sab
le1sa
4le1s2am
7lesar
4lesau
2lesc
le5sed
l2e1se
le4seg
4le3sek
le5s2en
le3ser
4leset
4le1sh4o
les1h
le6si.
le1si
4le3s2ig
le7si6ng
le2s5i4nn
4le3s2it
4lesju
le1sj
le7skap
le1ska
le4s3kj2
6le1skol
le6sk9u8t
le1sku
4le1sla
lesl4
2le1s2p2
6les2pal
les1pa
le4s3s6
lesse6ne.
les5s2e1ne
les1se
4lesta
9le2s1ta.
7lestal
le5s2ted
les1te
le5s4teg
le5s4tel
le4stim
les1ti
le3sto
4le1s2trø
lestr4
4le3s4tyr
le1sty
4le1stø
4l8e1stå
4le5s2ug
le1su
les1v2
le5s6vi
4l2e5sy
4le1sø
5lesøy
3let.
le4tab
le1ta
6le3tak
leta8ka
leta8le.
le3tal
1le3t2a5le
l5e4tas
5lete.
l2e1te
6let2ea
4leteg
4letek
le5ten
6lete2nk
6le7t2e1p
le5ter.
4let4es
2l2e3ti
l5e4t2ik
2l2e1tj
2l2e1to
2l2e1t2r4
l8e7tre
le5t4ri
3le4ts1
let6sj
lets8k
let6t2ea
le6tt
let1te
let6tr4
let6t3s4
2l2e1tu
l2e4t2v
2l2e1ty
let4y8e
4le1tø
8le1tå
4leu2l
le1u
2le3u2n
l1e2ur
2leut
le4u1te
2lev.
le1v
le6vad
le6va1lu
l6e4ve2d1
4l6eveg
4levei
4levek
6lev2e2rn
4le4ve1v
le1vi6d
le2v5n
2levo
le2v1r8
4le8v1s
4le1væ
lex1
4le1ø4
2le1å6
5lé5e8
1lé1r
7lét.
lé1t
7lè
5lê
4l1f
l4fa.
l1fa
lf5a6n1t
l5far
l5fe
l2f3f
l5f6ig
l1fi
l5f6in
lf5ja
l1fj
l7fj2e
l6f3nul
lfn6
lf1nu
l4fom
l1fo
l4fut
l1fu
2lg
l6gaf
l1ga
lga8le.
lg2a1le
l4g5a2l1t
l4g3a1na
lg2a5t
lga4ve.
lg2a1ve
l2ged4
l1ge
l4g2e1la
l4ge1le
l2gem
lg2e3n1a
lg2en
lge4r5an
lg2er
lg2e1ra
lge4rap
lge5ri
lger5un
lg2eru
l4ge1ry4
l4ge1sl4
lg6es
l6ge7s2p2
l4g5g
l3gi1e8n
l1gi
lg2i1e
lg1lo
lg5ly
lg5n
l2g3ob
l1go
lg2o2d
l2g1ok
l2g1om
l4g5ov
l2g3re1f
l1gr4
l4gr9øy
lg3s2e
l8gs1
lg2sk
lg5s4kre
lgskr6
lgsle9g
lgsl4
lgs1le
lg5s4tr4
lgu4l
l1gu
lg3un
lg3ur
l1gæ
lg5ø
l6gå.
l1gå
lg5år.
lgå8v4a
lgå1v
lgå8ve.
8l1h
lh4a8ka.
lh2a
lha5ka
lha8v6s5
l5hj
1li
li1a
li4al1a
li2am
li5a6ns
li4as
li1b4a5
li1b2
lib2e5ro
li1be
lib4er
li1b4i5
lib2ie6
lib4y5e
li1by
li4dak
l2i1da
li4ded
li1de
li4do.
li1do
2l1idr4
li4d3t1
li4e1ne
l2ie
li1en
li3er.
li5e6rs.
lie4rs
li2e5s
3l2if
4lifat
li1fa
li2f5f
3l2ig
li4ga.
li1ga
li4g3an
li4gar
lig9a6r1t
li8g6as.
li4g5e4n1h2
li3g2en
li1ge
li4get
5lig1n
li4g3re
li1gr4
lig3se
li8gs1
lig3s4i
lig3sl4
lig3s4p2
lig5s4ti
ligst4
lig5str4
li2gu
4li3h
li5kan
l2ik
li1k2a
li9kar
li7kas
li5ke3e2
li1ke
li3ken
li5kes1å
li2ke1s2
li9ki
lik2k3o
li2kk
li1k2l4
9li2k1n2
liks4t
li5ku
6li1la
8l5i6ld
li3le
lil5le1be
li2l1l
lil1le
lil2l2e1b2
lil5let
li4mar
l4i1ma
li4ma5s
li6ma3te
lim6at
l6i1me
li4me.
4li2m1h
limp3r6
li8m1p2
li2m7r6
l4i1m9u
li4na.
l6i1na
4lina6l
lin6c
linch5
5l4indr4
li6nd
4lin1du
lin8d3s4
l5indu1s
li4ne.
l4i1ne
li3n2e6a
li6nem
2l1i4n1f
lin4g3j
li6ng
ling5l
ling7s2en.
lin8g2s1
ling1se
5lin2g1v
4l5inju
li4n1j
lin5ke1s4
l2i2nk
lin1ke
lin5k1le
linkl4
lin4kv2
2l1i4nn
l4in5net
lin1ne
3l2in1ni4
6lin6n1s4
6l5inntr4
l1in6n7t4
li4nor
l2i1no
l4in1se
li6n1s
5l4insk
4l3inst
4li6n1t
2l1i6n1v
l6i6n5ø6
lio4no
l4io
li2on
lion5sp2
lio6ns
lion5s1v2
li5o6s
2lip
li2pe1s4
li1pe
lip2p4s3
li2pp
li9rar
l2ir
li1ra
li4r6ek
li1re
4lisak
li1sa
li5set
li1se
li2s4k2l4
2liso
4lis1p2
lis6sp2
li4s1s
lis7tan
lis6te2r1k
li2s1te
lis5ti
4lis6t4il
list3o
li6s8tr4
li1s2t7rø
lis4t3u4
li4st5y
listyr8ke.
li3s4tyr
listy2r1k
listyr1ke
li4te3e4
li1te
li4tek
li4ti3a
li1t2i
li4tid
li4t2ig
li4t4il
li4tim
li4ti5st
li4tiv4
lit5j
6li8t1n
li5to
li2t1r4
lit5rer
lit6te1le
li6tt
lit1te
lit6te2r1k
lit6te2r1m
lit6t5s6
li5ty
7li1u
li6va.
l4i1va
li4v3ak
li2v5eg
li1ve
liv2i5e
li1vi
li8v2s3
2lj
l1jan
l1jar
l4j1a2r1b8
lj2a4r5s6
ljas4
l4jed
lj2e
l6je3e2
l2je1i
l2jek
l2je3l
lj9e8lv
l1jen
l3jer.
l4je1s
l5j2es.
ljes4t
l5jet.
l4j2e1te
l5jete.
l6j2e5t6r4
l2je1v
l5jé
l1ji
3l2j2o1d
lj2o
lj6o8e
l4jom
lj5o1ri
3l2j2os
lj5un
l7jur
lj7ut
lj2ø3
l5jøs.
6l1k
l3ka
l5ka6l
lkal2i9e8
lka1li
lk4an
l6k5b4
l1k4e
l3ke.
l2ked4
l5kedal
lke5da
l3ke5de
lkeei4
lke3e2
l6ke5h
l5keleg
lke1le
l3ken
l7ker.
l4k2e1ra
l4ke1ri
l4k5e4rs
l4k2e3ru
l5ke7s6t2ad
l4ke3sta
l5ket.
l5ke4ts2
lk2l4
lk4li
l5k4lu
l6k5nin
l2k1n2
lk1ni
l6ko.
lk2o9ma
l1kom
l2ko9sa
l1k2os
l2k2ot
lkras5
lkr6
l4k5r2i6k
lk4ser
lk1se
l4k3s2h
lk1s4t
lku4le.
lku1le
lku8t
lk9u1te
l6kveg
lkv2
lkå1
2l1l
l5la6a
l2l6a5f
l2lak
l2l1al
l4l2a1mi
l5l6a3ne
l4l3a4n1l
l4l5a1no
lla6ns4
ll5ansk
ll7a1pa
lla2p3s1
ll4as
l4l5a4sp2
l4l5aur
llau6re
ll7a6v1l2
l2lav
ll5a2v1r8
l6l3d2
l2l2e5a
l1le
l5le3a2k
lle8da.
l2le1da
lle4d3r4
l3le3e2
l2le1f
lle5g2e1v
l3le1ge
lle5g2r4
l5leh2a
l2le1h
l5leh4o
l5leh6å
l4l5e2ig
l4le3ki
l6l5ekst
l4l2e1li
l2lem
llen6da.
llen1d8a
lle6nd
l6l5enden
llen1de
l4l1endr4
l6len4d1t
l4le2nk
l5l8e6ns
l4lentu
lle6n1t
l4l2e1nu
l1l5e4p1le
l2l2e1p
llep2l6
l6l5e6r2ik
lle1ri
l4lerob
ll2ero
ll6es
ll4e4so
lles5pr6
l2le1s2p2
l4le5stø
l5let.
l4le1ta
l5le4ts1
l1let6te1le
lle6tt
llet1te
l8l4e1tæ
l2le1u
ll5e1ven
lle1v
l4le7ve1v
l5levå
lle6y1
l2lé.
l2l7g2
ll2i1e
l1li
lli5e4n
l9l2ig
l2lim
l4l3i6nd
l4li6n1s
l4l3i6n1t
l9l8int.
l3lip
l4l2ir
lli6sen
lli1se
l4liv
l2l1j
l6l7k2
l2l5m
llmu7e9ne
ll1mu
llmu2e
llmu1e2n
llmu7e6ns
llmu9e7r
l5lo.
l2lob5
l2l2o1d
l4l3o2ff
llo5id
l2l5ok7s4
llo2m1
llo6m5s6
l2l3op
ll5o2pp
l2l1or
l4lo3so
ll2os
l6lo1te
ll2ot
l2l5p
llra7n
l2l5r4
ll4sak
l8l1s
llsa6me.
ll1s4am
llsa1me
ll4sem
ll1se
lls5e6nd
llsk4
ll2s6k2ar
ll1ska
ll2s5kv2
ll5skå
lls5lag
ll2sl4
ll3s1my
ll2s1m
lls4no
lls1n
lls4te
lls6t2ig
lls1ti
lls7øk
ll1sø
l2l3t4
ll1t6o4e
l8lua
l4lu4e
l4luf
ll7ug
llu4k
l8l7u1k2a
l4l3u1ke
l6l2uks
l5l4um
l4l1un
llun6ge.
llu6ng
llun1ge
llu4pi
l2l1ur
l3lus
l2l1ut
l8l1v4
llva8n
ll1va
lly4se.
ll2ys
lly1s4e
l4løk
l1lø
ll5øks
llø6pa.
llø1pa
l4løve
l6l7øy.
l4l5øya
l4l3ø4y1e
lløy6er
ll5øy6n
l2l1å8r
ll3å2s2
2l1m
l5ma.
l1ma
l4m3a4n3m
l4map
l4mar3ki
lma2r1k
l2m3av
l2m7b
l4m3e8l1s
l1me
lm2el
l4me1lu
lm5e4po
l2m2e1p
l4m5e2r1f
lm4e7ri
lme7s4ti
lm2e5t4r4
l5mil
l1mi
lmi8le.
lmi1le
lm3i6nd
l5mi6ng
lmi1ni6
lmin7ne
lmi4nn
lm3i6n2s
lm5i6n1t
l7mis
l4m5l
l2m5m4
l2m1op
l1mo
l6m1s
lm1s6j
lm5s2p2
l2m3t
lmu4le.
l1mu
lmu1le
l2m3ut
lmyr8ke.
l1my
l4my2r1k
lm5yr1ke
l4mø.
l1mø
l6m5øs
l2m5øy6
lmøya9
lmå6la.
l1må
lm6ål
lmå1la
l2m5å4r
l4m5å1s4
2l1n
l2nab
l1na
l3ne
lni4u
l1ni
l7ny
lo1al
l2oa
7loa1ne
lo3a6n
lo1a4r
5loar.
2lob
lob5by.
lo2b1b
lob2by5
lob9by1an
lobbya2
lob9byar.
lob5b4y3e
lo2b2l2
lo2d3a
l2o1d
lo3d4e
lo4d3ri
lodr4
lod3s4m
lo8ds
lod7s1te
lods8t4
lod7s6v2
lo6d5u
6loe6ng
l6o1e
loe2n
6lo1fj
3l6o2ft
1l2og
lo5ge
lo6g5e8v
lo4g2ir
lo1gi
lo1g2o
lo3g1op
log8res
lo1gr4
lo6g5ro
log5s4a2
lo8gs1
4l2o1h6
lo5id.
lo3i1de
lo1in
3loja
l4o1j
lo1ki
lok4ko
lo2kk
lok6kul
2l4o1k4l4
4lok6on
lo3ko
2lok7s4
lok8se.
lok1s4e
lo1k4v2
lole6ge.
l2o1le
lo3le1ge
2l3o6lj
l2o1lo9
l5omdr4
lo2m1d2
lo4m5in
lo1mi
lom4m2el
l2o2m1m4
lom1me
lomst9r4
lo6ms
lo4nal
l2on
lo1na
6lo6nd
lo5ne
lon4g3r4
lo6ng
lo4n4it
lo1ni
lon7skj2
lons1k4
lo6ns
2lop
l2o3pa
lo4p2ea
lo1pe
l4op5pa
lo2pp
5l4o2p1t
lo1ra
2lo4rd
lor6da.
lor1da
l8o1re
4l1o2r1g
lor4g5l
lor2ie6
lo1ri
l4o1ro
lo7rød
l2orø
lo7sa
l2os
lo6sek
lo1se
lo4ses
lo5se1v
los4k1le
lo2s1k2l4
lo1s2l4
lo7sp2
2lost
lo9t4es
l2ot
lo1te
lo8ti.
lo1ti
lo3to
4lottet
lo6tt
lot1te
lo1un
1lov
lov7a6ld
lo3van
lo9va6nd
lo7v4a1ne
lo3var
lo3ve7d6
l6o1v8er.
love2r
love5re6
l5ov2e2rn
6l5ove4rs
6l5ove6r1t
7l4ovo
lov3sa
lo8vs
2l1p
l7pa
lpa5re
lp6as5
l4pe1do
l1pe
l4pe3e6
l2pei
l2p6el
l5peleg
lpe1le
l3p2e1li
l4pelin
l3pes1m
l2pe1s
l4p2e3ti
l2pe2u
l2p2h
lp2i
lp4i1n3e
lpi5ne.
lp2l6
lpo6et
l1po
lp6o1e
l4put
l1pu
l5q
2l5r4
lr6a8d2a
lradi4u
lra1di
lra6ne.
lr4a1ne
lre4de
lre4i
lr4e8ka
lre8ke.
lre1ke
lre8va
lre1v
l4r4i8ma
lri4ve.
lri1ve
lro8de.
lr2o1d
lro1d4e
lro6pa.
lr2o1pa
lro8sa.
lr2os
lro1sa
lro4se.
lro1se
lro4t5s
lr2ot
lrø6re.
l1rør
lrø1re
lrø5v
lrå8da.
lr6å1d
lrå1da
lrå4de.
lrå1de
8l1s
l2sad
lsag6
ls5a6ld
l1s4am
l7s1a2na
lsan6ke.
lsa2nk
lsan1ke
l4s5a2no
l4s3a6n1s
ls5a2r1k
l4s4at.
l1sat
l4s1cu
l3se.
l1se
l2sed
l2se3e2
l2s3eid
l2s5e2ig
l4s3e1le
ls2el
l4s3e8lv
l2sem
l3s5e2m1b
l4s2e1no
l7s2er.
lse2s
lse1s5ku
lses3l4
lses5pa
lse1s2p2
lse4s5s
lse8s5tr4
lse1st
lse4te.
ls2e1te
l2se1u
l6se1v
l4s1f
l2s1h
l5s2ig
l1si
l5s2ik
l6s5i2l1l
ls2il
l4sim
l5s4i3mu
l4s3i4nn
ls5ja2kk
l1sj
l5s2je1f
lsj2e
ls3je3g
ls3jen
8l6sk.
ls5ka1b4i
l1ska
l4skab
l4s5kan
ls5k2a1ri
l2sk2ar
l4s1k5a6r1t
l6s5ke
l5s2k2if5
l1ski
l2s3ki6ld
ls2ki4nn6
l5sk8in6n1s5
ls1kjed
lskj2
lskj2e
ls3kj2o
l6s1k2l4
lsk3læ1
l4s3k2oa
ls5kor
ls3kov
l4sk8ra
lskr6
l2s8k1s2
l2s1k5un
ls7kva
l2skv2
ls3kvi
l4skå
ls3kåp
lskå6pa
l2sl4
lsl4a4ga
l7slag1n
lsle6ge.
ls1le
ls3le1ge
ls5lo
l5s4luk
l1slu
l6s5løs
ls1lø
l2s1m
l5s2mør
ls1mø
lsmå6la
ls1må
lsm6ål
l2s1nu
ls1n
lsok3
ls5o6nd
l3s2on
lson6de.
lson1de
l2s1or
ls7o6se
l1s2os
l5s2ot
l2s1ov
l2s1pa
lsp2
l9s2peg
ls1pe
l5s2pei
l9s2p6el
l5spi
l4spr6
l5spred
ls3pri
l8s7s
l4s3tak
l4s3tal
l6s1ta2nk
l6s3te.
ls1te
ls4ted
l4steg
l3s2t4e4i
ls4tel
l6s5t2e2r5r
ls6ti.
ls1ti
ls4t2i1e2
ls6t2on
ls5tren
lstr4
l9stri
l2su
l5su2b
l3su2k
ls1un
l2s1v2
ls7v2e1a2
ls5ve3e2
lsve8en.
lsvee4n
l6s5vek
lsve7re
l4sv2ik
ls6vi6nd
l4sør
l1sø
l2s1øy
l2s1å
2l1t
l4ta1a
l4taf
lt3a6k1t
l3tal
lt8a4la.
l4t3a2l1b2
lta6le5v
l3t2a1le
l2t3a4n1l
lta6no
l4t3a6r1t
lta4st
lta8t4es
lta1te
lta4t3o
ltat3r4
lt3a6v3k6
l2tav
l5ta1væ
l6t9b2
l3te1de
l1te
l2ted
l4t4e1ka
lte6ma.
l2tem
lte1ma
l3t4en.
l4t5e4n1h2
lt3epi
l2t2e1p
l4teras
lt2e1ra
l4t5er1s2t
lte4rs
ltesa8me.
lt4es
l2te1sa
lte1s2am
ltesa1me
lti8d6s1
l1ti
l3tid
l4ti3et
lt2i1e2
l2t2if
lt2i6g5
l2t2ik
l4t6i1na
l2t3i6nd
l4t9i4nn
lt3i6n1s
l4ti1vi
l2t1ja
ltle8ge.
l6t3l6
lt1le
lt3le1ge
l8t9n
lto9ar
lt2oa
l3t2og
lt2o4s
lt3ost
lt2r4
l3tra
l6t3reg
l4t3rei
l6tre7k2o
l6t5rel
l6t7rem
l4tre6ns
l2t3res
l5t4rest
l5tre1v
l4t5rit
lt3rol
lt3rom
ltr2o8pa
l5t2r2os
l4t5rød
l5trå
l4t1s2
ltsa8me.
lt1s2am
ltsa1me
lt5s4i
lt7s6t4
lt2s3v2
lts4vi
l6t7t8
l4t3u6nd
ltu4ra
l3t2ur
ltu1r5e6
ltu5r6en
ltu5r6er
ltu4r5s6
ltur5å6
l2tut
l5tv.
l2t1v
ltva8la
ltv4a
l3t8vs
l4t5w
l3ty
l1ty8d
l4t7øl
l2t3øv
lua8r
2lub
lub6ba6nd
lu2b1b
lub3b4a
lu6bri
lu1b4r8
lu5c
lu7e1re
lu1er
1luf
3lu5gar
lu1ga
lu7go1
lui6
luid6er
lui2d
lui1de
1lu1j
4l4uk.
lu4ka.
lu1k2a
4luket
lu1ke
luk6ke1ri
l4u2kk
luk1ke
lu3kr6
5l6u6k1t
4l1uly
l4um
lu6m2el
l2u1me
l4u2mo
lum6sk
lu6ms1
lu4mø
lun5d4r4
lu6nd
lu4ne.
lu1ne
lun4gel
lu6ng
lun1ge
l7u2n6i
lun5ne
lu4nn
3lun1sj
l4u6ns
4lu6n1t
lu6o
l6u2p4s
l6u2p3u
lu1r2e
6lurei
lu5ren
lu5ri
6l5u2rn
lur8na
lur8ne.
lur1ne
lur8ta
lu6r1t
1lus.
l4usa5
lu7sak
lu6s2el
l2u1se
lu6sh7e
lu2s1h
lu2s5k
lus4o5
lus4sid
lu4s1s
lus1si
lus4s3t
lus1t
lus2t3r4
luta3
lu3ta.
lu7tet
lu1te
6l1u6t1f
4l1u2t3g2
lu2t1h
3lu5t6he
lutla9
lu6t3l6
lu2t6m
l2u1to
lu4t5r4
2l1u4t1s4
lut4tal
lu6tt
lut1ta
lut4tap
lut4t5at
6l1u2t1v
1lu1v
lu4va
lu4ve.
lu9ven
lu9ver
8lv
l1va
l4va.
lva6k
l4vak1ti
l1va6k1t
lva6la
l4va4m
l4va1na
lv8a6nd
l7var.
l4v7a1sa
l4v5a4s1s
lve3d4a
lve2d1
lv2e5i6s
l4v2e1la
l4ve1le
l1v4en
lve9ne
l9v8er.
l8ve9rau
lv2e1ra
l4v2e1ru
l4vesk
lv4es
l2ve5sl4
l7v6et.
lv4et
l4ve5str4
l9vé
l1vi
lvi8e1ne
lv2ie
lvi1en
lvi9er
lvi6ka.
lv2ik
lvi1k2a
l4v9im
lvin5g6r4
lv6i6ng
l4v3iro
l1v2ir
lv1j
lvly8se.
l6v1l2
lvl2ys
lvly1s4e
l6v3n
l2v1of
l2v9o6p
lvo8re.
lv8o1re
lv2o4r4s
lv5o4v
l6v5p4
lv7ra8r
l2vr8
lv9ri
l8v1s2
lv5se
lv7s6k
lv1s6l4
lv9ta1
l2v1t
l5v4ø1e2
lv1å
l3v2åg
lvå4p4
lvå6r
l5w
ly1a
2ly1b
ly4d7r4
l4y5e
lyes3
ly4gel
ly1ge
ly5g6l
ly8is
ly1i
2ly1kj2
l6yk
lyk6ke1ri
ly2kk
lyk1ke
ly5ku
ly7kv2
6lykø
ly5l
ly5me
2ly8m3p2
ly2n3a
ly4ne.
ly1ne
ly4n5il
ly1ni
ly6n3s2
2lyo
ly5ok
ly3p8e
ly1r8
ly8ra.
ly1ra
ly6re.
6l7y2r1k
ly4sa.
l2ys
ly1sa
ly4s5a4k
lyse6te.
ly1s4e
lys2e1te
lysk4
lys3kj2
ly2s9k3l4
ly2s1l4
4lysp2
lyst9ra
lys2t
lyst3r4
6ly1sy
ly8ta.
ly1ta
4lytek
ly1te
ly4te1ri
ly2t4e5s2
l5y4tin
ly1ti
ly1tr4
ly3ve
ly1v
l3z
l6z5b4
1læ1
6læd
læ6ra.
læ5rar
læ4r4a2r1m
læ4re.
læ1re
læ8r7e3i
læ3rer
læ4re3s
læ5r2es.
lær6sv2
læ4rs
læ6ta
1lø
lø3de.
lø1de
lø4del
lø5dem
lø4er
l4ø1e2
løk5kj2
l2ø2kk
4l1ø2k1n2
2l1øko
lø4k5r6
l5ø6l
lø9me
lø2na
løns5t
lø6n2s
lø2p6s5
5l4ø4rd
lø2r5k6
lø2r5n
4l5ø2r1r
5løs.
lø2sa
lø5san
lø5ser
lø1se
løs3k
lø4s3s
2l4øt
lø9ta.
lø3te.
lø1te
lø4teg
løva9r
6l7ø6v8d
lø4ve.
lø3ver
lø5v4e1s
6løy.
4løya
2løyd
4lø4y1e
løy8ed
løy5el
2løy1f
løy4g
løy1g5e
4løym
6l5øys.
lø2y1s
løy5ter
løy1te
lå6gal
l2åg
lå1ga
lå6gre
lå1g4r4
lå8gs4
lå4gå
2lå1i
lå1k4
lå6ke.
lå1ke
lå9me
l6åm
lå2m5o
lå2n
6l5å6nd
lå6n2s1
2låp
l1å2p9n4
2lår
lå8ra.
lå9rar
lå5ras
lå3rin
lå1ru
lå6sa.
lås2
lå1sa
lå1sk
lå6sko
lå4s5l4
lå4s1te
l4å1st
lås7ten
lås5ter
lå5su4
lå3te.
lå1te
lå4teg
lå4tek
lå8ti.
lå5ti
4lå6t1t
lå4ve.
lå1v
1ma
2maa
3maa.
ma3ar
2mab
ma5b1r8
ma3che
m8ac
mada5me
m6a1da
ma4del
ma1de
ma3dra
madr4
m6a8d9s
4mae1f
m2a1e2
4maek
7mae1ne
2ma1f
3mafia1
ma1f4i
3mafr2
6mafrå
ma8ga.
m4a1ga
ma4ged4
ma1ge
ma4gel
ma4g6e5s2
3mag1n
ma2gr4
2mahe
ma1h
ma4is
ma4ja
ma1j
2m6ak.
ma5kab
ma1ka
4m1a2kad
ma4kes
m2a1ke
mak7ke
ma2kk
4m2ak1l4
mak6le
ma4k2ot
m2a1ko
mak2r6
ma3kre
mak5r2on
mak2ro
mak4tal
ma6k1t
4m3ak1ti
mak4to
makt1s4t4
mak4ts
ma1ku
2m2a1kv2
ma1la
ma7l2e1b2
m2a1le
ma6le6ng
ma4let
mali9e8n
ma1li
mal2ie
ma2l5l
ma4lov
m2a8l1s4
mal4t5ek
ma2l1t
mal1te
mal3u
ma1l3å
4mamer
ma1me
ma3mo
m4an.
ma3nak
ma1na
m3anal
6manam
4manav
man8ce
man1c
man4dom
m6an1do
ma6nd
man4d2on
ma3ne
m4a4n1f
man5g4a
ma6ng
m6an1g4e
4man1gr4
mania8
ma1ni
2ma4n1l
m3an5le
4ma4n3m
5m4a4nn
man4nem
man1ne
mann6s5l4
man6ns
ma4no
2ma1o
4ma1pa
2mapr6
4m1a2r1b8
ma4r5d6
7ma1re.
ma1re
6mareg
ma3rei
ma7rel
5ma9ren
ma5res
3m2a1r4i
mari8e9ne
mari1e4n
mar2ie
ma3rin
m4arka
ma2r1k
4m3arkit
mar3ki
mar4kv2
ma2r5m
marmo9ra.
mar1mo
mar5mo1ra
m2a2r7n
ma1ro
ma4r1o6p
mar3s4h
m2a4rs
mar7sl4
mar5te
ma6r1t
ma4ry.
ma1ry
ma6rå
4mas4el
ma1se
ma5set
mas2h3
ma4sia
ma1si
ma4s2ik
ma4s2is
6masju
ma1sj
ma1s4k2i
4masko
4masp2
mas4se3e2
ma4s1s
mas1se
mas6set
mas1sø9
7ma6st.
ma5s2tem
mas1te
4masto
4ma5str4
2ma1sy
m6at
m4a1ta1
ma5tad
ma3te
ma4te.
ma6ted
ma4tel
7m4aten
7ma5ter.
m4ater
6mat2e1ra
7matet
6m7at1fe
ma6t1f
4mat4il
m4a1ti
7matil.
ma1to
ma1tr4
4ma1tra
ma4t5ras
ma4t3re
ma6t7rom
m2a4t3s2
mats8l4
mat5ta
ma6tt
m8at7t8r4
2mau
mau4k
mau7l
ma1un
5maur
mau7su
2mav
ma6ve.
m2a1ve
ma5ven
m7a2v1h
m5a6v3k6
ma4ze
ma3zo
2m1b
m4ba1o
m1b4a
mba4r3d
m2b2ea
m1be
m4b2e1b2
mbe9da
m4be1dø
m2b4e1f
m2bek
m4b2e1li
m2bem
m4b2e4na
m4be3o6
m4bereg
mb4er
mbe1re
m4b2e1ro
m3b4et2
mbi6ar
m1b4i
m2bi1b2
m3b4l2
mb6o1e2
mb2o
mbo4e6nd
m1boe2n
mbo5er.
mbo2er
mbo5e4re
mbo5id
mb2o5n
m4bo1p4
mb8o5re
m1b2o9t
mbu7ar.
m1b2u
mbu4ar
mbu4e
mbue7re
mbu1er
mbus5
m1c
m6co
2m1d2
m3de
md7om
m1do
1me
2m2ea2
me5al
mea5m1
2m2e1b2
2m2ec
me2c6k4
4medat
me1da
2me2d1b2
5me3de.
me1de
me4ded
me2d5ei
me7den
me7det
4me2d1f
me3di
4med2ik
4med2ir
2me2d1m
me6dok
me1do
4medom
2me1dr4
me6dret
me8d1s4
4me1du
me6dun
me5d4u1s
me2d5v
7medve
2me1dy
4medå
me7e2
2mee1f
6meek
2meel
4mees
4mee1v
2me1f
m3e2ff
6m2e2ga
me7gal
2me1g2r4
2me1gu
4me1h
me2i7e
mei1e5n
m1e2ig
me3i6ld
m4eil
me3i6n1d
me4i5ni
me7isk
m2eis
4meiso
2me1j
4me1ki
4m4e1kj2
2m2e2kk
me2k1l4
m4e3k1n2
4me1k2o
2m2e5k8r6
4meks
me6k5t
6mek1te
m2el
4m2e1la
me5l4aks
me2l1ak
5mel6a3ne
5melar
me3le
me4le.
4m4eled
4melei
4melek
6m5e6lem
me4l5e6ng
m4elen
4mel2ik
m2e1li
4melis
4melit
4meliv
mel5le
me2l1l
mello6m3
4melok
m2e1lo
4me1lov
mel4si
me8l1s
mel2s3j
melsk4
mel5s4t
mel7t2r4
me2l1t
me1lu
me8l5v
mel1vi6
2m4e1ly
2m2e1læ1
4m2e1lø
4m2e5l4å5
2mem
me6mo.
me1mo
4m5en1g6a
me6ng
me2n5k
menle6ge.
me4n1l
men5le
men3le1ge
m6e4nn
men4ny
me4nom
m2e1no
me4nor
men4si
me6ns
men3s1m
men5s1pl6
mensp2
men5te
me6n1t
men6tek
men4tom
men5tr4
me4nyt
me1ny
2me3o6
me6o1s
2m2e1p
5mer.
me6rab
m2e1ra
4merad
me4ra5l
me4r5a6n1t
mer5di
me4rd
4mered
me1re
4mereg
4merei
4merek
4merel
me6ren1se
mere6ns
me3res
4me2r3e4s1s
mere6t
m4e1ri
4me5ri1b2
meri5ke
mer2ik
5merin
merle7g
me2r1l
mer1le
m2e2r9n
mer5os
m2ero
5me4rs
mer5sk
me3run
m2eru
mer5u6nd
4me1r4us
2me1ry4
m2e2r3ø
2m2e1rå
me4rå4k
5m2es.
2me5sa
4me5s4h
2me1sj
2me1sk
me2s5ke
2me1sl4
mes6le
4m4e3so
2me1s2p2
5me4s1s
7me6st.
4mesta
5me6s5te.
mes1te
mes4ti
6me7sto
mest3r4
me5stro
6me1s2trø
4me3strå
4me1stu
4me1su
2m2e3sy
2me1sø
4metab
me1ta
4me9t4ap
me4tar
m2e3te
4meteg
4metei
4metek
4metel
4me2t2e1p
4met4il
m2e1ti
2m2e1tj
4me1t2on
m2e1to
me4t3ra
m2e1tr4
m8e4tre
met5ren
met7rer
me2t5res
met5r2ik
me4tru
4met6ræ
2m2e1tu
4m2e3t2v
2m2e1ty
2me1tø
4me1tå
2me1u4
7m4e2u3s
2me1v
meva8n
2me1ø4
2me1å6
2mé
2m1f
mfa9ra
m1fa
m3far
mfar8ta
mfa6r1t2
mfav5
m2fek
m1fe
m6f3e4s1s
m4fi.
m1fi
m4fib4r8
mfi1b2
m2fit
m5fr4u5s6
mfr2
m5fun6n8s5
m1fu
mfu4nn2
mfu6se.
mf2u1se
2m5g6
m4ga.
m1ga
m4gi.
m1gi
mgå8v4a
m1gå
mgå1v
mgå8ve.
2m1h
mh2e2a4
m4hu.
1mi
mi1a
5mi6al
mia2n
9mia1ne
7mi1ar.
2mi1av
mi6c
miche6l
8m9idé
2mi1di
mi8d5j
mi8d3s4
mi4d4t1
4mi3e1le
m2ie
mi3er.
mi2e9s8
2m2i5f
5migraf
m2ig
mig2ra
mi1gr4
2mi3h
2mi1i
4mi1j
mi4k6h
m2ik
4mi1kj2
2mi1ko
mik5ro.
mik1r6
mik2ro
mik5r2on
mik5sa
6mi1ku
mi4la.
mi1la
mile6t
mi1le
mi2l7e3ti
mi4le1v
mi7li
4milin
mil6s5v2
mi8l1s
4mi1læ1
2mim
mi8ma.
m4i1ma
5mi1mo
m7i8m1p2
mi4na.
m6i1na
mi5nar
mi4ne.
m4i1ne
mi4n5e4rs
4m3in4n1h2
mi4nn
4m3in6n1s4
6m1in6n7t4
mi6n2s
m1in3sp2
mins4t
m6i3nu
m4i6n1v
mi4n5y
mi7ov
m4io
2mip
4mi1sa
mi4san
mi3se
4mi2sek
4mis2el
9m8i1sé
misha9ge
mis1h
mish2a
misha2g
4mi7si
mi2sk
mis4ko
mi3s4la
mis1l4
mis9le
7mis1m
mis4s5k
mi4s1s
mis4s7p2
mista9k
5mi2s7te
mis4tra
mi2str4
4mistu
2mi1sy
4miså
6mi1ta
mi3te
4mitj
4mi1to
2mi1t6r4
mit6t3s
mi6tt
mi5ur
mi1u
2miv
mi5vå
m6ja.
m6jan
m5jar.
m7j2a2r2n
2mj2e
m7je.
m1ji
m8jingan
mji6ng
mjin1ga
m8jingar
8mj2o
mju7ke
m4ju2k
1mj2ø
mjøs5t2
2m3k2
m4ko.
mk2ro5
mkr6
mku6le.
mku1le
mkå8pa
4m3l
m7la
ml4a6ga
m2le1f
m1le
mlei5er.
mle2i7e
m2lek
m2lel
m2lem
m4l2e1ra
mle6se.
ml2e1se
m4lesk
m6le5s8v2
m4le1ta
mle4ve.
mle1v
m1l6i
ml5ja
m2lj
mly6se.
ml2ys
mly1s4e
mlø6pe.
m1lø
mlø1pe
mløy3
2m1m4
m6mai
m1ma
m4m2a5k1l4
mmal5
mma8le.
mm2a1le
m4mam
mm6an1do5
mma6nd
mman6dol
m5mar
mma3r7in
m3m2a1r4i
mma1r7o
mmatik7ka
mm6at
mm4a1ti
mmat2ik
mmati2kk
m4me1dø
m1me
m2meg
m2mei
m2me3k2
m4me1lu
mm2el
m6me1ni
m4m2e1nu
mme6r5t
mme4run
mm2eru
m2me1s
mmest6
m5met.
m4me1ta
m4m2e1ti
m4me4t3ra
mm2e1tr4
mm8e5t6re
m2me3u4
m3mé
m4mi1a
m1mi
m7mia2n
m2mi1b2
m4mid
mmi1e6n
mm2ie
m2m2ik
mmi5sk
mmi5so
mmi3st
m2m5n
m3mu
m1mø2
mmå8la.
m1må
mm6ål
mmå1la
2m1n
m2ne3e2
m1ne
mn7eid
m2nei
m2ne9l
mn2e4ra
m5n2e1se
m4nesk
m4nesta
mnes9t2i1e2
mnes1ti
m9net.
m4n2e3te
m2ne1v
m6nip
m1ni
mn7sk
m6ns
1mo
3m2oa
mo2ar
4m2o3b4a
5mo1d4e
m2o1d
mo3dem
mo5di
mo6di.
2mo1dy
3m6o1e
mo4en.
moe2n
m1o2ff
mofo6bi.
m2o1fo
mof2o3b4i
mo6gi
m2og
5mogl
mo5go
m5o8gs1
4m2o1h6
2mo1ka
mo8ka.
mo8ke
mo1ki
mo6la.
m4o1la
m2o3le
mo4le.
mo7le6s
4mo2l1t
mo3ly
m5om.
4mo2m1f
2m1o2m1r6
mom4s5ø
mo6ms
mo5ne
m2on
mo4ni1sa
mo1ni
mo2no
mo6n1s
mon4s1te
mo6n5t6
2mop
5mo1ra
mo4ra.
mo4rar
mo7r4ar.
mor5d6e
mo4rd
4mor4d1l4
mor6d5r4
m8o3re
more1s7
m2o1ri
mo4ri.
mo6rid
4m3o4r2ie
mor4kl4
mo2r1k
morl4a8ga
m6o2r1l
mo5rok
m4oro
mo4rom
mor4si
m2o4rs
mor4skj2
mor7sky
mor4sp2
m2o1rø
mo9s2en
m2os
mo1se
mo2s7k
mo3s4o
6mostab
4motap
m2ot
mo1ta
mo4te.
mo1te
mo6te6g6e
mo4t6ei
mo2te7kl4
mo5ter.
mo4t4es
mo5to
4mo1tr4
mot7re
mo4t1s2
6motsa8g1d
motsa2g
motsva5
mots1v2
mo6t7t
mou4r5
3mo3va
mo5w1
8m1p
m4pa1na
m1pa
m4p5anta
mpa6n1t
m4pe3e6
m1pe
m4pel4ot
mp6el
mp2e1lo
m6p2e1na
m6p2e1p
mpera8te.
mpera5t
m4per6a1te
mp2e1ra
mpe5res
mpe1re
m6pe2r3e4s1s
m4p5er1fa
mpe2r1f
mperi6e7n
m3pe1ri
mper2ie
m2pe1s2
mpes6te
mpe4s1ti
m5pe6tt
m2pe5u
m2p3id
m4pinj2e
mpi4n1j
m8p1la.
mpl6
m3p4lan
m2p5le.
mp1le
m6p5lin
mp2li
m6p5n4
m6p5ob
m1po
mp6o1e4
m4poe1ta
m4p2og6
m6pok
m2pop
mp3o2pp
m2p1p8
m4p3rad
mpr6
mp5ret
mp3rop
m1p2ro
mpr2o8pa
m2p1s
mp3sek
mp1se
mps4p2
mp5s6t2
mpun6ge.
m1pu
mpu6ng
mpun1ge
m9pur
mp5ut.
m6p5ys
mpø5
m9på.
m3q
2m1r6
mro8sa.
mr2os
mro1sa
mro6se.
mro1se
mru7te.
mru1t8e
mrø9de
m9r1år
6ms
m5sa.
ms1ak
ms5a6n1t
m1sc
m2se5lu
m1se
ms2el
m9s6ei
m4sem
m4s3e6ng
ms5e4p2l6
m2s2e1p
m4se2r1v
mse5s
m5s6e8t1n
mse6t7ja1re
ms2e1tj
mse8t9jas
ms2i6e
m1si
ms4i6ng
m2s3i4nn
m4s5ja
m1sj
m4s5kab
m1ska
m2ska9k
ms6kin
m1ski
ms1k5i6ng
ms3kor
msk8u
ms3lan
msl4
ms6l2e1ga
ms1le
m8s9lu7a
m1slu
m2s3lu2e
ms4ly
m2s1m
m1s2n
ms9ne
ms5no
m2s3næ1
m1so
6m4s1o6ms
ms3o2ri
m2s1ov
m4s3s2
m4s3tal
m8s7te.
ms1te
m2s7t2ea
ms2ti
ms5ti2l1b2
mst4il
ms3tim
m1sto
m4s5top
m5s6to2pp
m6s8t5o4rd
m8stra6nd
mst4ran
mstr4
m5st5r4a1ne
ms4t5red
ms5tre1f
mst5ren
ms5tr2ik
ms4tru
ms3u4nn
msu9ta
m2s1u4t1
msu7t2en
msu1te
ms1v2
msva9ra
msva5re
m3s4v4et
ms3y6nd
m1sy
ms2yn
msø4ke.
m1sø
m3s6ø1ke
m4s5ør
ms1øy
ms1å
2m1t
m3ta
m6t4b2
mt1be6
m2te3e4
m1te
m2teg
mteks7
m6te7k3v2
m4tel
m6te1stu
mt4es
mti9a
m1ti
m9t2i2da
m3tid
mt2i5e2
m6tien.
mti1en
m6t2if
m2t2ik
m6ti1ni
m4ti1ø8
mtiør6
m5to
mt4r4
mt6ve
m2t1v
mt4vin
mtå5
1mu
mu6a
m1u2b
mu2e
mu3el
mu1e7r
mues1
2mug
mu4g5l
mu4he
mu1h
mu2k
8m9u1k2a
4m1u1ke
mu5la
mu4leg
mu1le
mu2le6s
mu2l1l2
mul8l6s7
mul6tiv
m4u2l1t
mul1ti
4m1uly
7mum
m2u3mi
mu6m2s1
mun2c
mu2ne1s6
mu1ne
4mu6ng
mun6ge.
mun1ge
6m5univ
mu1n6i
m4u2n3k
mun6n5s6
mu4nn
mun4t3r4
mu6n1t
mu6ra.
mu1ra
mu4re.
mu1re
2mu2rn
mu4rs4
6mu6r1t
m2us
mu4se.
m2u1se
mu4ses4
mu4sé
mu2s3k
mus2k2e6l5a
mu2s1ke
must4
mus5tan
2mut
3mu1ta
mu8ta.
mutsa8la
m1u4t1s4
mut7t6r4
mu6tt
2m1v
m1va6k5t
mva6la
mv2a1ri6
mve8g5s4
mv2i7e6
mvi6se.
mvi1se
2mw
1my
my5a
myg4ga
my4g1g
my3ke
m6yk
myk4kes
my2kk
myk1ke
myk3l4
my8kr6
my2ra
my9ran
my9rar
my1re
my4re.
4my2r1k
m5yr1ke
my4r5u
m2ys3
my6sa
my4se.
my1s4e
my4so
my4te.
my1te
myt6t6s5
m4y6tt
5mæ
mæ6la.
mæ1la
1mø
mø2b3l2
mø1b
mø7de1s
mø1de
m4ø6e2
mø9e6ns
møk1k6a
m2ø2kk
m3ø2k1n2
2m1øko
mø6na
mø4ne.
mø1ne
møne9s
mø8nest
6m5ønsk
mø6n2s
mø2o
3mør1ke
mø2r1k
mør4k5r6
mør5s1m
mø4rs
mør3ø
mø5se
7m4øt
mø9ta4s
mø4te1re
mø1te
mø6t9t
2møy
møy9ar
m5øys.
mø2y1s
1må
2må.
må5a
6måe4n1h2
m4å1e
måe2n
m2å7g2
må1k
må4ka.
må1ka
må4ke.
må1ke
må4ke5s
m6ål
må5lar
må1la
må4le.
må1le
må6led
måle3i
målø6pe.
må1lø
målø1pe
må4ne5s6
må1ne
må9n2e1se
må8pa.
må1pa
må4pe.
må1p2e
må7pl6
2mår
måra6r
må1re
må1ro
må1ru
må1s4
må6tak
må1ta
6må6t1f
må5tr4
må3tø
1na
na6a6ns
na1a2n
2nab2o
7na7bort2r4
nab2o6r1t
na6b2ot
4na1by
na4ded
na1de
na8dem
na1di4
n3a2dop
na1do
na8d2s1
4n1a2d1v
8naf
n1a2ff
nafo7r
n6a1fo
na3fr2
na2f7t
na8ga.
n4a1ga
na3ge
4nag2en
na8g3s4
nagså5
na7gø
6na1h
4na2ir
2nak
n6a2kk
nak7ka
nak6ko
5n2ak1l4
na1kr6
n1aks
nak8sa
nak8se.
nak1se
8n1a6k1t
nak6ta.
n2a7kv2
na9la4g
na6la1re
6na7la2r1v
na7leg
n2a1le
na4l3ei
na4lek
na4l5e1po
na2l2e1p
na4le1s2
na4l5e6tt
na4le5v
na2l5g
nal6ge.
nal1ge
na4lil
na1li
nal6lag
na2l1l
nal4løp
nal1lø
na2l3op
n2a8l5s2
na2l3t
na3lur
2n6a1ly
na2l5ø
4na1lå
na3lå5r
5nam.
na7me1re
na1me
na5mes
na5mo9
5na6m1s
2na1mø
9nan.
4na1na
n3anal
n6a1ne
nan1fø8
na4n1f
4n5an1gr4
na6ng
na2n5k
nan6ke.
nan1ke
4n3a4n1l
6n5an5le
nan4ne
na4nn
na4n5o
4nn
nn4an
n4n5a6ns
n1na
nan4sin
na6ns
nan1si
nan4skj2
nan4s5t6
4n6a1ny
na7o
na5pe
na2pe4s
na2p3s4
n4ar.
na4rap
na1ra
2n1a2r1b8
nar5dr4
na4rd
4n1a2r2ea
na1re
na2r7ei
4na5rek
nari4e5n
n2a1ri
nar2ie
7narik4sk
nar2ik
4n3ark6iv
na2r1k
nar3ki
6n5ar2mé
na2r1m
6n5ar1me
nar8ma.
nar1ma
n2a4r5s
nar8s1te
nar1s2t
2na6r1t
nar6ta.
nar5ti
na2r7v
nar5ø
nasa3r
n4a1sa
na4sas
nas7h
8na3s2ik
na1si
na4sk2i
na2s5t4
nas1ta5
8na1su
n4a1ta1
nate8k
na1te
na7tem
4na6t3l6
4na2tom
nator2i5e6
na1tor
nato1ri
na1t8ra
natr4
nat3sp2
n2a4ts
nat6tak
na6tt
nat1ta
nat6t2ea
nat1te
na1ty5
2nauk
naus6p2
na6va.
na1va
4n1a6v1d
6na2v1f
2n1a2v1g2
2n3a2v1h
na1vi
4n5a6v3k6
4na6v1l2
nav4les
nav1le
3n6a2v1n
6na1vo
4na2v1r8
n1a8vs
4n1a2v1t
4n1b2
n5b4a2
nba3d
nba9ser
nba1se
nbe6n4s
n1be
nbo5et
nb2o
nb6o1e
n6buf
n1b2u
n6but1r4
nbyr5
n1by
nbø9le
n1b4ø
nbø6n
n1c
n5c4a
n1cel5
n3che
n4ch3e2i
n6c2ot
n2cy1
6nd
n7daa.
n1da
n4daa
n6da1b4i
n4dad
n2da5f
n7dag4
nda8gs5
n4daks
n3dal
n4da4l1f
n4d5a2l1l
nd2a8l1s3
n4da2r1k
n6d3a6r1t
n8da4sk
n4da5tal
nd4ata5
n6d5d4
n3de.
n1de
n2d2e5a
n2d2e7b2
n2ded
n5d4e1fi
n2de1f
n2d1ei
nd4e1in
nd6ek
n4de1k2l4
n4de5k2o
n5d4e5l
nde4le.
nde1le
n4del2ik
nd2e1li
ndel4sk
nd4e8l1s
ndel4st
n2dem
n5d4em.
nde5mo
n5d2en.
n6dener
nde1ne
n5d2e6ns
n2de5o6
n2d2e1p
n4derab
nd2er
nd2e1ra
n4deras
n4derei
nde1re
n4derim
nde1ri
nd6e2r5k
nderl4a6ga
nde2r1l
n4de1si
nde1s
n4d4es2t
n4de1su
n1dé
ndi4en
n1di
nd2ie
n4d3i6n1t
nd6i6sk
ndito1ri5
ndi1to
nditor2ie6
ndit4t5a
ndi6tt
nd3jer
ndj2e
nd1ju
n2d5k2
nd4lem
n4d1l4
nd1le
nd4l2e1se
nd4lest
nd4le1v
nd2o9a
n1do
n6do2b3
n6d5o2kk
nd4o6m4s3
n2d2oo
n2d1op
n2dor
nd3o2r1g
nd5o2r1m8
n6do1te
nd2ot
nd5r2a1e2
ndr4
n4d3ram
nd3ran
nd6rek
ndr2e4ra
n4d1rer
nd5re3s2en
ndr2e3se
nd5re6tt4
nd1ri
n1dro
nd4sag
n8ds
nds3ak
nd4s5a2m1b
nd1s2am
nds5e8lv
nd1se
nds2el
nds5en1de
ndse6nd
nd4ser
nd4sj2e
nd1sj
nd4skj2
nds7kul
ndsl4a6ga
nds1l4
nds6le6tt
nds1le
nd7s1pu
nds1p2
nd3s4te
ndst4
nds9teg
nds5trek
ndstr4
nd5stry
ndt4a6ka.
n4d1t
nd3t4a
ndta1ka
n6duf
n1du
ndu3is
n4dun
nd3u6ng
ndun6ge.
ndun1ge
n2dup
ndu6s2i
ndu1s
ndu2s7k
n2d7v
n2dyg
n1dy
nd6y1k
n2d2ys
ndy5sp2
ndø5l
n1dø
n2d1øy
1ne
2n2ea
5ne2a1e2
nea4g
ne7a6le
ne5a2l1p2
ne5a8l1s
nea9m1
ne9a8r9an
ne1ar
nea1ra
nea9ren
nea2re
ne1as
6ne7av
2n2e1b2
3ne2b1b
4ne1be
ne4b1le
nebl2
ne2c6k5
n2ec
2ne1da
3nedal
ne6d3d
6n5edd2ik
ned1di
4ned2e7b2
ne1de
4ne2deg
4nedel
5ne2d1g4
ne4di.
ne1di
ned4i4s
2ne1do
ned5o4ve2r7
ne2d1ov
ne1d1r4
ne6dre
5ne8ds
ned3st4
6ne1du
4ne1dø
ne5dår
4neeg
ne3e2
4neei
2neek
5ne4e4r5
2ne1f
n1e2ff
n5e8ft
2neg
5neg4a3ti
n2e1ga
neg2at
ne3gl2a
neg5le1s2p2
neg1l6e
ne1g8r4
6negru
6ne1h
2nei
n2eid
nei8dan
ne2i1da
n5e2ig
n2e1in
n3e4i1ni
5n2e2ir
2ne1j
2nek
ne4ka.
n4e1ka
6ne1k2l4
n4e3k1n2
n2e1k2r6
nek5rin
ne3k2ri
n7ekser
nek1se
ne6k5t
4n2e1la
nel3de
ne6ld
4n4eled
ne1le
6nele4g1g
7neleg1gj
4nelei
nelei5er
nele2i7e
4n3e2lem
6ne5lê
4nelid
n2e1li
4neli1ga
ne3l2ig
4nelis
4nelit
4neliv
nel4lov
ne2l1l
ne8l7s
2ne1lu
4n4e1ly
2n2e1læ1
2n2e1lø
nelø8pa
4nelås2
n2e1lå
2ne1ma
4n3e2m1b
4nem2el
n8e1me
4nemer
6nemes
2ne1mi
nem2ie8
6nemj
ne2m6k2
5ne2m1n
nem5ne.
1nem1ne
nem7ne9l
2ne1mo
2ne1mu
2ne5my
2n6e1mø
2ne1må
3nen
4n2e1na
nen5at.
6n5en6den
ne6nd
nen1de
6n1endr4
1ne1ne4
4nened
4n4enem
ne3nes
4ne2nesl4
4nenet
ne2n5e1ta
4n4ene1v
nen4ga.
nen1g6a
ne6ng
4n3enhe
ne4n1h2
6nenhet2
4ne1ni
nen5se
ne6ns
nen2t5ei
ne6n1t
nen1te
6n3en5tr4
4n2e1nu
4ne1ny
4n4e7næ1
2ne3o6
5neo.
ne5o4r
7ne2o1s
2n2e1p
ne5pe
3n4er.
4nerad
n2e1ra
6nerap
3n4e2r1b8
4ner2ea
ne1re
4nered
nere6de.
nere1de
4nere1f
4nereg
4nerek
4nere6n1t
4ner2e1p
ne5res
4ne2r3e4s1s
4ne1re1su
4nere4t
4nere1v
4ner3far
ne2r1f
ner1fa
4n2e2r2g
ner3ga
ner3ge
4ne3ri1b2
ne1ri
4ner2ik
4nerit
ne4ri9v
5ne2r1l
nerl4a8ga
n3erob
n2ero
6ne1r4oc
ne7rof
ne5rok
4ne1rom
ne5r2ot
ne1r4ov
n6e6rs.
ne4rs
ner7se
ner5s4i
n6er1s4p2
ners8ten
ner1s2t
ners1te
4ne3rul
n2eru
ner5un
2ne1ry4
3ne2r1yr
4ne1rør
n2e1rø
2n2e1rå
2ne1sa
ne4sa.
5nesar
4nesc
4n2e1se
6ne3sek
nes6er
5ne9s4e4ts1
nes5eva
ne2se1v
ne3si
nes5i6nd
4ne3s2it
2ne5sj
ne5s1kam
ne1ska
4ne5sk2ar
ne2s1k4e
4ne1ski
5nes2ki6n1s
4ne1skj2
4ne5sko
4ne1skr6
6ne1sku
2nesl4
ne3s1li
ne7s1lø
nes6mi
nes1m
6nes1n
ne7s1nø
2n4e1so
3nes2ot
2ne1s2p2
nes6s2am
ne4s1s
nes4stu
ness2t
5ne6st.
4ne1stas
nes9t6as.
ne6s9te.
nes1te
4nesto
nes5tor
4ne3str4
4ne1stu
4ne1stø
2ne1su
2ne1sv2
nes8va
6n2e3sy
4ne1sæ
5nesøy
ne1sø
4ne5så
neså5re
neså2r
3net.
2ne1ta
4netaks
ne3tak
neta6le.
ne3tal
ne3t2a5le
n2e3te
5nete.
4n2e3ti
6ne3tid
2n2e1tj
2n2e5to
6netom
2n2e1t4r4
3ne4ts1
net5s4p2
net1t3a4
ne6tt
net6tel
net1te
2n2e1tu
n2e4t2v
4n2e1ty
4ne1tå
netå5ker
netå2k
netå1ke
2ne1u
ne2u1r
ne5us
ne6va.
ne1v
ne4ve.
6ne8ve6n1t
ne1ven
5nev2ik
nevi8sa
3ne2v1n
ne2v1r8
ne4y5t
ney1
2ne1ø4
4ne1å6
1né
4né1b
4né1d
2né1f
6né1h
2né1l
4né1m
6né5o
2né1p
5né1r
4né1v
4né5å
4n1f
nfa2l1l4
n1fa
n5fal
nfal8l1s5
nfa6n5t
n6f2oa
n1fo
n4fob
n4f2o1le
n4fom
n4for1a
nfor9en.
nfor7e6n
nf8ore
nf2os4
nfø5de1s
n1fø
nfø1de
nføy8ed
nfø4y1e
6ng
n8g8ad
n1ga
n4gaf
n6gag
n6g1ak
n6g5and.
nga6nd
n8gan8da
n4g3a2n5k
n4g5a2r1m
n4ga6r1t
ng5art.
n5garta
ngar5u
n2g2at
ng5a1to
nga4ve.
ng2a1ve
ng7avi
n8g1d4
ng4ded
ng1de
ng4del
ng4d2e1p
ng3dr4
n4ge1da
n1ge
nged4
n4g2e1la
n3g2en
n4g2e2n1a
n7ge1ne
6n4g3e6ng
n6g2e1no
n4g2e1nu
n3g2er
n4gere4t
nge1re
nge5run
ng2eru
n2g6es
nge3sl4
n4ge1ta
n2g2e7v
ng1fø4
n2g1f
n4g5g2
n2g5id
n1gi
6ngje6n5g
n1gj
ngje2n
ngj2e
ng1l
n5glem
ng1l6e
ng4lu
ng7n
ng2o4d
n1go
n2g5o6d1d
n7g6o1e
n3g4ok
n8g7o8m
ng9o1me
ng5o6nd
ng2on
ng7o6pe
n2g1op
n2gor
n2g5o4rd
ng3o2r1k
ngos6p2
ng2os
n2g2ot
ng3ra.
n1gr4
n5grad.
ng5ra6nd
n2g7r2ea
ng3reg
ng3ren
n4gre6nd
n4g1rer
n6g5rest
ng3ret
ng3re1v
n4g3rid
ng3rin
ng7ro.
ng9r2oa
ng7r6o1e
n5g6r2os5
n6g3r6å1d
ng8sa.
n8gs1
ngsa2
ngs5e8lv
ng1se
ngs2el
ng4sem
ng4ses
ng2sj
ng2sk
ng7s4kj2
ngs7leg
ngsl4
ngs1le
ng5sløy
ngs1lø
ngsmå6la.
ngs1m
ngs1må
ngsm6ål
ngsmå1la
ngs3ne
ngs1n
ng2s7t2e1p
ngs1t6e
ng9s8t5rid
ngstr4
ngstyr8ke.
ng1sty
ng3s4tyr
ngsty2r1k
ngstyr1ke
ngs9t2ør
ng1stø
ng3u6nd
n1gu
ngu5ru.
ngvi4s
n2g1v
ng5y1e
n1gy
n1gø
n2g1øy
ngå8v4a
n1gå
ngå1v
ngå8ve.
4n1h2
nhat5
nh2a
nhe4t4s1
nhet2
1ni
ni1ak
ni1a2n
nian5d2e1p
nia6nd
nian1de
ni2bl2
ni1b2
4ni1by
4ni1b4å
4nicr8
n8i1da
ni4del
ni1de
ni7de1le
n5idé
ni6do
nid7r4
ni8d2s4
nid5s3t4
4nieg
n2ie
ni1el
7ni3e2n
ni3er.
ni2e5ri
ni7e4rs
ni5e6r5t
4n3i4fr2
n2if
ni5gl2a
n2ig
nig1l
ni9glo
2ni3h
8ni1j
ni3ke
n2ik
2ni1kj2
4ni5ko
2nik1r6
4nik1v2
ni5l4i
5nilu
4nimar
n4i1ma
4nima5s
4ni1mo
2ni8m1p2
ni6n7al
n6i1na
2n1i6nd
4ni4n1f
nin1g3o
ni6ng
nin5gr4
nin8g6s5
ning1se4
nings5t6e
ningst8
2n1i4n1j
n1i4nn
4nin4n1b2
4nin4n1h2
4nin6n1s4
4n1in6n7t4
2n1i6n1s
2n1i6n1t
2n1i6n1v
5nio.
n4io
ni2on2
nio6ns3
4niop
7ni2os
4ni5ov
ni4pet
ni1pe
ni9pet.
ni9pe4t1s6
ni2pl6
nip3li
nip5si
ni2ps
4n2ir
nir7kel
ni2r1k
nir1ke
4ni2sem
ni1se
ni5set
nis5im
ni1si
4ni3skj2
nis4k3o
n1i9sol
ni7s6o5ne
ni3s2on
ni4s1s4
4ni1stas
6ni1stat
nis5t2ik
nis1ti
4nist4il
ni3str4
4ni1sty
4ni1stø
6n6isu
6ni1sy
n4it
ni3ted
ni1te
6nitj
ni4t2og
ni1to
ni4t2o4s
nit4t4r4
ni6tt
nit6t4s3
nitt6sk
4ni1tu
ni3ty
ni2t5z
nitæ1r6e
nitær1
ni1tæ
2niu2b
ni1u
ni4u2m1f
2niut
4n4i1va
2ni1ve
ni4v5ei8
4n5i1vo
ni8v3s2
6ni1å4
4n1j
n6ja1h
njav9
n2jed
nj2e
n5je3de.
nje1de
n4je3e2
n6je1i
n2je1s
n3j2es.
n2jet
n3jet.
nj2e1t6r4
n5je6tt
2nk
n1ka
n2kak
nk3aks
nka2l5l
n2kau
n4ke1do
n1ke
nked4
n4keer
nke3e2
n4ke5h
nk5e2i7e
n2kei
nke8l5s4
nke2l2t3
n5k6e4n1h2
n8k2e1no
n4k5er1fa
nke2r1f
nker4s2t
nke4rs
n4ke1s4
n6ke1tø
n1ki
n4kid
n4k4ie2
nk3ier
n6k5i6nd
n2k7i4n1f
n2k3i4nn
n2k7i6n1t
n5kj2
n2k1k4
n1kla
nkl4
n5k6la6ng
n7k4lis
nk1li
nk3lok
n1klu
nk3ly
n3k2læ1
nk1lå
n3k2nu
n2k1n2
n3k2ny
n3ko
n4kof
nk2o1fi8
nk5oks
n2k2o5le
nkol4la
nko2l1l
n6k2ot
n6kov
n1kr6
n3k2rav
nk1s2
nk4tak
n6k1t
nk6tal
nk4tin
nk1ti
nkt3sk
nk4ts
n1ku
nku4le.
nku1le
nku1ri9
nkur2ie8
n4ku6t
nk9u1te
n1kv2
nk3ve
n7kvit
n6kvo
n9ky
n1kø
n1kå
nkå6pa
4n1l
nlan1d9a
nla6nd
nland6se
nl4an8ds
n5le
nlei7er.
nle2i7e
nle8ma.
n4le1ma
nli4en.
n1li
nl2ie
nli1en
n3lj
nlu4e
nly6de.
nly1de
nly6di
nly4se.
nl2ys
nly1s4e
nlø6pa.
n1lø
nlø1pa
n7lå
4n3m
nma4le.
n1ma
nm2a1le
nm2ik3
n1mi
nmi8l
nmi1ni6
nmo6se.
n1mo
nm2os
nmo1se
nmusi2k7k
n1mu
nm2us
nmu1si
nmu3s2ik
nmå6la.
n1må
nm6ål
nmå1la
n2nad
nn5ad.
nn9a2f7t
n8naf
n4n5a2ir
n6n1ak
n4nala
nn5a2l5g
n4na1me
n4n5a1na
n9n6a1ne
nna8ni
n4n5a2n5k
n5n3a4n1l
n6n7anta
nna6n1t
n9n4ar.
n4n2a1ri
n6na1si
n4nask
n2nat
n2nav
n4n3a6v1l2
nnb6o9e
n4n1b2
nnb2o
nnbu9e
nn1b2u
n6n5d2
nn1dø4
nnd2ør3
n5nea.
n1ne
n2n2ea
n5ne1ap
n5nebar
n2n2e1b2
nne1b4a
nneb4e8r
n4ne1be
n5ne1b2u
n4ne1di
n2ne3e2
n6ne1f
n2n1ei
n3ne1ke
n2nek
n4n2e1lo
n3n4en
nne6nat
n4n2e1na
nn2e4n3o
n9n4er.
n4nerel
nne1re
n4n2ero
n7n2es.
n6ne5sj
n5ne1s4la
n2nesl4
n4ne3st
n5n6et.
n4ne1sø
n4n2e3te
n2ne1v
n2ney1
n6n3g2
nnhø8re.
n4n1h2
nnhø1re
n4nid
n1ni
nn3i1de
nn2i3e
n2nim
n4n7i4n1f
n5nis
nni4sj
nni4s3t6
n2n7k2
nnle6ge.
n4n1l
nn5le
nn3le1ge
nnlø6pe.
nn1lø
nnlø1pe
n4no.
n1no
n2n5of
nno2m1
nn6o6m7s2
nnomsy8na
nnom1sy
nnoms2yn
n2n1op
n2n1o4r
nn7o4rd
n4n3o4ve
nno1v
n2n9r2
nnsa9ka
n6ns
nn4s5a2m1b
nn1s2am
nns5a6nd
nns3ar
nn5seg
nn1se
nn2s9e2i8g
nn4s3em
nn6s5e2nk
nn4s3es
nnse6te.
nn4s2e1te
nn3s2i7da
nn1si
nn4s3in
nn4s5i6s
nn6s5jak
nn1sj
nn4s5kan
nn1ska
nn5s4k1li
nn2s1k2l4
nn2s5o2p
nns3or
nnst6
nn6s5tab
nns5tal
nns5te2l1t
nns4tel
nns1te
nns4ten
nn6s5t2e1p
nns7tin
nns1ti
nns5top
nn6s5tre
nnstr4
nns5tro
nn2s3t1v
nn4s5ul
nn1su
nns3va
nnsv2
nn6s5vo
nnsy8na
nn1sy
nns2yn
nn2sø
nns3øk
nn3så5r
n6n7t4
nnte6se.
nn1te
nnt4es
nnt2e1se
nnto9ga
nnt2og
nnu1i
n1nu
nn6u6ng
n2n1un
n2n1ut
nnvi4s
n6n1v
nn5vi
n2n1yn
n1ny
nny4t
nn7øk
n1nø
nn1øv
nn1å
1no
noa4g
n2oa
no1a4k
5noa1ne
no3a6n
no7ar.
2nob
nobe4l
no1b4e
no4b2l2
nob5le
n2o3b4r8
no5co
n4oc
nodi4e5n
n2o1d
no1di
nod2ie
3n6o1e
4noe6f
4noei
4noek
4noe6n5t
noe2n
2nof
n1o2ff
3n6o3ft
n6og6ra
n2og
no1gr4
no5id
no3in
2nok
no1ki
no1k8r6
nok6se.
nok1s4e
n3ok5s4i
n2o9le
2n3o6lj
no6mid
no1mi
no6m2ik
no4mil
4nom1j
n2o2m9m4
no6m7s2
5n6o1my
no6n1s4
n2on
no6n5t
2n2oo
2nop
n1o2pp
no1r4a
no5ran
no5rar
no5ras
nor4da
no4rd
nor6d5e6nd
nor1de
4n1or8d3n
nor4d5r4
nor8d3s4
nor4dø
no9re3e2
n8ore
no3rek
no3ren
no7ret
no2r5g
3nor3ge
norg6e4s5
n3o4r2ie
no1ri
7n6orit
4no2r1k
nor6kla
norkl4
nor6k1le
n4o2r1m
normlø7se
nor4m3l
norm1lø
n2o4r2s
nor2s6ka.
nor1ska
no3ræ
no4s2el
n2os
no1se
no4ses
nose8te.
nos2e1te
4nosp2
no3stj
nostra4
nostr4
2no1sy
no4ta.
n2ot
no1ta
no4te.
no1te
not6e5i
5notek
no4tel
no4t4es
no4t3s
4no1ty
4nou
no1v
no3ve
4n2o5vi
4n5o2v1n
7nó
6n3p2
n5pe
n7po8ta
n1po
np2ot
n4på1k2
n3q
2n1r2
n9ra
nra8na
n4r4a8sa
n3re
n4r2e1b2
n2re2i7e
n6r4enem
nre1ne
n6re3o6
n6ri.
nro6de.
nr2o1d
nro1d4e
nr2o6t
nry6
nrå8da
nr6å1d
6ns
n1sa.
ns1a4d
n5sag
n5sa2kk
n5sa4k2r6
ns3aks
nsak6se.
nsak1se
6n5ak1ti
ns5akv2
n3sa1la
ns3a6ld
n6s5a2l1p2
n5s2a5ne
n4sa4n1f
n5s6a4nn
n5san1se
6nsa6n1s
ns5an1si
ns5a6n1t
n5saren.
nsa1re
n2s6a2r1m
n6s1a6r1t
n4s4a3ti
n1sat
n1sch
n3sco
nse9a8l
n1se
n2s2e1a
n2sed
ns5e6d5d
ns8e3de
n2se3e2
nse6e3i
nse6er1
n6se1f
n2seg
ns5e2ge
n2sei6d
ns5eie.
nse2i7e
n2s9e2ig
n4seks
n4s3e8lv
ns2el
n2sem
n5s2en.
n5sene.
ns2e1ne
n4sener
n9senes
n4senet
n4se1ni
6n7s2e6ns
n3s2er.
n4s2e3ra
nser2i7e6
ns2e1ri
n4se1sa
n4se1si
n4se3sk
nse3sl4
n4se1s4p2
n4se1st
n6se1su
n4se3s4v2
n4s2e1te
n9s4e4ts1
n2se1u
ns5fr2
n2s1f
n2s1h
ns3h2a
n6s7he
ns5hi
n3s2i5de.
n1si
nsi1de
nsi4e6n1t
nsi1e4n
ns2ie
nsi6er.
nsi6n5d
n3s4i6ng
n2s3i4nn
n4sin1te
n2s1i6n1t
nsi8ra
ns2ir
nsi4s7k
ns2is
ns3jak
n1sj
n4sj3av
n5s2je1f
nsj2e
n6s5jo4rd
n2sjor
nsj2o
n4skan
n1ska
n6s5kauk
ns7ke1le
n2s1ke
ns4k2e3te
n9skim
n1ski
n2s5k2ir
n5skis
n6skja
nskj2
n6skje1le
nskj2e
nsk2jel
ns5kjen
n3skjæ
nsko8g6s1
n3s2k2og
ns5kor
ns3k2ro
nskr6
nsku9et
n1s4kue
n4s6kug
ns5ku5l4i
n5skum
n2s1kv2
n5s6ky.
n1sky
n5s6k4y1e
n6s6ky4s1s
n4sk2ys
n5sla6ng
nsl4
n7sl2arar
nsla1ra
ns5las
nsle6ge.
ns1le
ns3le1ge
n3s4le6k1t
ns6le1ri
n2sler
n2s5lid
ns1li
n5s4lyn
ns1ly
n1slå
ns3mi
ns1m
n4smal
ns1ma
n4s5mo
n4smur
ns1mu
n4sm2us
nsmå6la.
ns1må
nsm6ål
nsmå1la
n5s4nar
ns1n
ns1na
n6s5nes
ns1ne
n1s2o4d
ns1of
n3s2ok
n1sol
n4so6nd
n3s2on
nson6de.
nson1de
n7s6o5ris
nso2ri
n2s1ov
n3so2v1n
nspa9ra
nsp2
ns1pa
ns5pe8l1s
ns2p6el
ns1pe
n5spet
ns1pr6
n6s3pr2os
n8s7p2ro
n7s2pur
ns1pu
n4s7s6
nsse4e2
ns1se
ns6så5
n2s4ta.
n4stak
n4stag
ns5t2a1ke
n3stam
n4s1ta2nk
ns4t5a6r1t
nstar8ta.
ns4tau
n3s6ted
ns1te
n3s4t4ei
ns4tel
nste6ma.
ns2tem
nste1ma
n6s5te2nk
n6s2t2e1p
n5s4t2ik
ns1ti
n4s5ti6l1d4
nst4il
ns7ti4l1f
n6s5ti8l1s
ns4tin
n2s4t3i4s
ns1tj
n5s2to.
n3stru
nstr4
n4s7trøy
n1s2trø
n3s2tue
n6stu2k
n6s5t2ur
n2s4t3ut
n1su
ns1u2k
n4su6ng
ns5va6ng
nsv2
nsva9ra
n2s3ve
n6s9veg
ns5ve4rd
ns5v2e2rn
ns1vi
n5s4vi2l1l
n4svil
ns4vi6nd
n2s9vis
n4s1vu
n3s4væ
nsy4d
n1sy
n2s1y2t
n5søn
n1sø
n4s1øv
n4s5øya
n5så1p2e
nså2p
nså7pen
ns7ås.
nså2s2
6n1t
n5tab
n4ta1gr4
nta4lan
nta2l1l4
nt6a5na.
nta2na
n4t5a6nd
n2t3a4n1l
nt4a6n1v
nt3a2pp
n7ta6r1t
n6t5arvi
nta2r1v
nt6as
nt3a5sia
nta1si
nt4at
nt3avi
n2tav
nt5a2v1r8
nt7a8vs
n2t2ea
n1te
n5teat
n2te3e4
n4te1f
n4te1ge
n4te1gi
n7teg1n
n2tei
nt5e4i1ni
nte5i6n1s
n5t2e5i4s
n4t4e1ka
n7t4e2k1n2
n4t3ek1se
n9teleg
nte1le
n5te3l2ig
nt2e1li
n5te2l1l
n4te8l1s
n9te2l1t
nte4ma.
n2tem
nte1ma
nt3e2m1b
nte4mis
nte1mi
n5ten.
n4t2e7na
n5tene.
nte1ne
n4t3e4n1h2
n5te6ns
n2te3o6
n5te2o1ri
n5te2pp
n2t2e1p
nt4er
n5ter.
n7terek
nte1re
n6terest
nte6risk
nte1ri
nteriø7ra.
nteriø4r3a
nteri1ø
n5te4rs
n7t2es.
nt4es
n4tes2el
nt2e1se
n4te1ta
n4t2e3te
n2t4e3u
nt6e4va
n2te1v
n2t9g2
nti1a
n1ti
n4tia1t
n4tid
n5tiem
nt2i1e2
n4t2ig
n4tikap
nt2ik
nti1k2a
n5ti5ki
n4tik1l4
nti5k1li
n5ti1le
nt4il
nti3lo
n9ti4me.
nti1me
n7times
n4t3i6nd
nti6net
nt4i1ne
n6ti1ni
n2t4io
n2tip
n4ti1sa
n4ti5s1ti
n6t5l6
n8t5n
nt2o3a
n2tob
n2t5o2m1f
nt5o2m1n
n4t3o6m1s
n2t1op
n5t4or.
n5t8o5re
n6to2r1g
n2tou
n2t1ov
nt6ran
ntr4
n4tr2e1p
n5t2rer
n6t5re1si
n2tres
ntres9kja1re
nt2resk
ntre1skj2
n4t3rin
ntrol6li
nt2rol
ntro2l1l
n6trom
n6trul
ntrøy4
nt5skj2
n4ts
nt5s6la
nts1l4
nt1s2t4
n6t3t4
n6t7ub
ntu9e
ntu4l
n2t1u2n
n4t5ur1o
n3t2ur
n2t1ut
ntva8la
n2t1v
ntv4a
nt4y6e
nty4r3s
nt7år.
1nu
4nu.
nu3an
nu4av
nu2ft4
nuf4ts5
2nug
nug6l2a
nu4gl
4nu4h
nui1t8e
nuk5
n4u2l9t8
4nulu
6n1uly
n2u9me
2n1un
nun4ge.
nu6ng
nun1ge
nuo2
6nup
nu3pl6
nu4re
nu5sa.
n4usa
nu5sen
n2u1se
nu7ser
nu2s4k5l4
nus7l4
nu4s1s4
nu6st
nus3ta
2nut
nu7ta
nu3te.
nu1te
n2ute6r
nut5e1ri
n7u6t3l6
nu6u1me
nu1u
6n1v
nva6la
nva6nd5
nver1le9
nve2r1l
nv4es1
n5vi
nvi4et
nv2ie
nvi4ka.
nv2ik
nvi1k2a
nvi5ke
n3vu
nvæ8ra
n1væ
n3w
1ny
ny5ar.
ny9ast
nyas4
2nyd
ny4de.
ny1de
4nye1f
n4y1e
4nyg
ny5ge
5n6yhe
ny1h
ny3ke
n6yk
ny3k4le
nykl4
4ny1ko
ny1lo
2nyn
nyn8da.
ny6nd
nyn1da
nyn4de.
nyn1de
4nyo
4nyp
ny8pa
ny6ra.
ny1ra
ny4re.
ny7re1v
ny4ru
n2y1s
ny5s4e
ny9s6k
nys2t4
nystu4
ny3te
ny3tr4
2ny1v
ny8va.
ny5vak
ny8ve.
4ny5ø
n1z
1næ1
næ8ra.
nær9a6nd
næ8ra6st.
1nø
nø2da
4nø6d3d
nød1de5
nød3sk
nø8ds
nød5sto
nødst4
nø9dun
nø2du
2n4ø1e2
4nøf
nø1f2l2
nø1fr2
nø7g2r4
nøg1
4n1øko
4nøks
nø4le.
nø1le
nø9mo
nø5m1u
nø3p4
nø7ra
nø6red
nø1re
nø1ry
nø3se
nø1sk
nøs4l4
nø7te
n4øt
nø7tr4
n6ø6tt
nøtt6r4
nø6v4d
nø3ver
nøy1
2n1øy.
nøy8a9n
n7øy1h
4nø2y1s
n3øys.
nå6as
nå1a
1n6å1d
nå5ded
nå1de
nå8e2n
n4å1e
nå6et
nå8j
n1å2k4
1nål
nå2la
nå5le1v
nå1le
nå8ma
n6åm
nån6de.
nå6nd
nån1de
n1å4p
nåp8na
nå2p1n4
n1å6r
nå2s2
nå5sa
nå1se4
2oa
o8a5c
o1af
oa4k
oak6ka1na
oa2kk
oak1ka
oak4ku
o1a2l1l
o3a2l1t
o1a2m
o3a6n
o9a6nd
o1a6p
oar6d7e
oa4rd
oar4d5in
oar1di
oa4r5e4g
oa1re
oa4r5e4n1h2
o8a6ré
o2a4r5i
oa2r5m
oa9té
o1au
o1a4v
2o3b4a
obakk8s
oba2k
oba2kk
ob2b4l2
o2b1b
ob4b2o
ob4b5r8
ob4b3u
o1b4e
o4b2ea
ober5e4t
ob4er
obe1re
obe4r4s
obers5ta
ober1s2t
obers5te
o3b4e1s2
2o3b4i
ob2i5e6
1ob1j
o1b2l2
ob5la.
ob3le.
ob1le
ob3len
o2b5li
3ob3l2ig
ob4lo
o1b4o
ob2o9a
2obr8
ob5rar
o1b4ra
ob1re
ob1ri
3obser
o6b1s
ob1s4e
ob5s2t6
2o1b2u
2o1by
obyl5
o5b4ø
o3b4å
4oc
ock5e4rs
o2c6k
oc1ke
o5cy
2o1d
o6dab
o1da
o2da4f
od7a6tt
od2d5ei
o6d1d
od1de
od4del
od4d4es2t
odde1s
od9do
o1d4e
o2d2e7b2
o6d5ei
o4de5k2o
o5de1ku
o2d2e1p
oder1le7
od2er
ode2r1l
o2de1s
o5d2et.
o5de4ts1
odi1e4n
o1di
od2ie
o4d3ig1l
od2ig
o4dj
odko5
o2d5k2
o4d3o4m3l
o1do
o2d3ov
o2d5re
odr4
o4d3rø
od3sk
o8ds
od1s4ka
od3s4p6o
ods1p2
ods8t4
od5s4tol
odu7s
o1du
o4dy.
o1dy
o4dy1b
o2d5øk
o1dø
o5døs
od5øy
o2d1å6
6o1e
oe6f
o4ein
oek6s
oe2l
oe4mu
oe2n
o2e1n5a
o7e1ne
oen8g5d4
oe6ng
oen2g7r4
oen8g3s1
oen1g3u
o5e6ns4
o7ens.
oe6n5t
o3e4re
o6e1rer
oer8ma
oe2r1m
o3er1sk
oe4rs
o2es
oe6sip
oe1si
oe6s2it
oe1s8ka
oes8ke.
oe2s1ke
o4etan
oe1ta
o4e2t2h
o2e5ti
oev6ne.
oe1v
oe2v1n
oev1ne
2o1fa
o2fa.
of4a2g6a
o4fa3ra
o3far
ofa4se
o2fav
o2f2e1b2
o1fe
o4fei
o4f2e1ra
o4fe3st
o1fé
of7f9a6n5d
o2ff
of1fa
of4fek
of1fe
of5fes
of6fia
of1fi
of2f5id
of6fr2
of2fu
of6fy
2o1fi
o4fip
o1f2l2
of5le
2o1fo
o6fra.
ofr2
o1fra
of9ra1s
of1re
6o5fri
of8sa
o2f1s
of8se.
of1se
6o2ft
of4tel
of1te
of4t2s3
2o1fy
2og
o2ga.
o1ga
o4g7a2n5k
o4ga1re
o2g7av
o2ge.
o1ge
o4ged4
o2g1ei
oge7na.
og2en
og2e2n1a
o3get
og4g2e1ra
o4g1g
og1ge
ogg2er
og4g5j
og8g3s4
og6g7u
ogi3a4
o1gi
og2i1e2
o4gie1v
o2g2if
o6g2ig
o4gi1ko
og2ik
o2gil
o2gim
o2gin
o3gi6ng
o2g4i1o
o6gista
o6gi7str4
o2giv
o3g5jer
o1gj
ogj2e
og7l2a
ogly7
og4ned
og1n
og1n4e
5o4g5ni
og6nel
o5g6n2os
og1no
o2go.
o1go
og6ra
o1gr4
og4re.
o4g3reg
o4g3rei
og4rer
o4g7re6tt
o4g3ri
o2g3ryd
o4g5rø
og5rå
ogs4a2
o8gs1
ogs6an
og3s2e
og1sk
og7s4kj2
og5sl4
ogs4le
og1s4p2
ogs5pa
og5s1t6e
og3s1ti
og4s5tj
ogs4to
ogs4tr4
ogs5t6ra
og5stre
og5sy
5og5s6y4v3
ogt6r4
o2g1t
5o4g5åt
o1gå
og1un
o1gu
og5ø
og7å2s2
2o1h6
oh2e5te
ohet2
oh1m9a
o2h1m
o4ho.
oh4o
ohø4
oia4
o1i2d9i
o1idr4
oi4d5t1
oi1e4n
o2ie
oi5er
oi6e1s
o2i5k
oi4la
o1im
o3in.
o1i6ng
o2i2n1o4
o4insp2
oi6n1s
o2i6r
o1is
o2is.
o6i1sa
o2i1se
o4i1si
4o1j
o8je.
oj2e
o4jo.
oj2o
o1ka
o2k7a2ft
ok1ak
oka4n5i6
ok2ar
ok5a2r1k
o3kas
o4k1e1f
o1ke
ok5e8l1s5
ok5e8l5v
o5k6en
o4kesk
o6ke1v
o1kj2
ok4k5a6nd
o2kk
ok1ka
4ok1ke
ok6k2e1ra
ok4ke5s
ok3kj2e
okkj2
ok3ko.
ok5kol
ok4kun
4okl4
o4k8la.
okla5m
ok8le.
ok1le
o1klu
ok7lut
oklå5
2o2k1n2
okn4a8sa
ok1na
o3ko
o8ko.
ok8ol
o4k3o6ms
o1kom
o4k3o2m5t2
oko5pe
ok5o2pp
o4k3o2r1m
o1k2o4s
oko9se
o6kov
o1kr6
o6k5re3o6
o6kret
o4k5ru
oks4al
ok1sa
ok1s4e
ok4sek
oks2e5k1r6
ok4sem
ok7sen
ok6serin
oks2e1ri
ok4ses
oks2e9te
ok5s4i
ok3s2i7da
okst2
oks6ti
ok6s5vi
oks1v2
oks6tr4
ok5ta
o6k1t
3oktan
ok3ti
ok5to
okto4r5i
okt6r4
o1ku
ok5u4k
oku8le.
oku1le
oku6t
ok7u1te
o5kva
okv2
o3kvi
ok5øy
o3kå
8ol.
4o1la
ola6ded
ol6a1de
ol4a8ga
o4l5a4n1l
ol3a6n1t
o9l8ar.
o6lar1be
o2l1a2r1b8
ola5t
olb4er4
o2l1b2
ol5be
olb6o7e
olb2o
olbo7ge.
olb2og
olbo1ge
ol4b4ol
ol1b2u7
2o6ld
ol9dan
ol1da
ol5de.
ol1de
ol4dem
ol4d4es2t
olde1s
ol5det
ol3do
oldo7ve7
ol2dov
ol5dre1v
oldr4
ol3d1ri
old3s4kr6
olds1k
ol8ds
2o1le
o5leaks
o2l2ea
ole3a2k
o6l2e1b2
o2led
o2le1f
ol5e2ig
o2le5in
o2lek
o7le3ki
o2lel
olele6ge.
o2le1le
ole3le1ge
o2lem
o5l4em.
o5le6ms
o7lene.
ole1ne
o9lenes
o4lenet
o4le5ni
o4l2e1no
o6lered
ol8e1re
o4l2e1ru
o2les
o4le5sta
ole5str4
o9let.
o2le1v
o1lé
ol4f5i
o4l1f
ol4fj
ol3g4e
o2lg
ol1g4l
2o1li
o4li1b4a5
oli1b2
oli9e6n
ol2ie
oli7e6r5t
ol4i5ne
3o6lj
olke3s4
o6l1k
ol1k4e
ol4k2e1se
ol4kesk
ol4kest
ol2k3l4
ol9ko
ol6ku
ol5kv2
ol5la.
o2l1l
ol2l3ak
ol4lam
ol7le.
ol1le
ol4led
ol4lel
ol7len
ol4l6es
ol6li4n1j
ol1li
ol4l2og
ol4lo2m1
ol5l2os
ol4lov
oll5ove2r
olls4t
ol8l1s
oll5s1v2
ol2lu
ol4løp
ol1lø
ol6løs
ol6løy
ol2lå
olme5s
o2l1m
ol1me
ol4mest
ol4må
o2l9n
2o1lo
o4lo.
o5lo3a6n
ol2oa
o3l6o1e
o4lof
olo3i
o2l5ok7s4
ol3o6ms
o2l3op
o6l7os.
ol2os
o6l7o6se
o6l5o2s1f
olo5ve
o1lov
ol4ped
o2l1p
ol1pe
ol7so
o8l1s
ols8t
ol5sva
ol2s1v2
ol4s5v2ik
ol3ted
o2l1t
ol1te
ol5t2e1p
ol2t2r4
ol6t7ra
olt5re.
olt5rer
olt5ret
o1lu
olu5l
o4l3u6ng
olun8ge.
olun1ge
o4l5ur
ol5va
o8lv
ol1vo
o1ly
oly7d
ol5ør
o1lø
o1lå
2om.
2o1ma
om3a6ld
o2ma2m
om5a6ng
oma4n2if
oma1ni
o5m4a4nn4
oma6n5t
om3a4rs
omar7ø8
om4as
o2m1av
o2m1b6
om4b4et2
om1be
om4bis
om1b4i
om3b4l2
5omb2o1d
omb2o
1om1b2u
2o1me
o2m1ek
o5m4e1ka
o7menes
ome1ne
o5me4r6s
o6m2e1se
o5met
ome4tak
ome1ta
ome7tar
o2m1e1u4
3omfan
o2m1f
om1fa
3omgre
o2m5g6
om1gr4
8o2m1h
o2mi1a
o1mi
o2mi1b2
om2i3e4
o4miek
o2m2ig
o7mi2kk
om2ik
o7m4i1ne
om7i4nn
om3i6n5s
o4m4io
o2m2ir
o4mi1sj
o4mist
o2mi1u
om1j
2o2m1m4
om6m6at
om1ma
om4med
om1me
om4m2e1tr4
3om1mø2
2o1mo
o4m2o1fo
o2m1op
omo1v
o8m1p2
ompa3t
om1pa
1o2m1r6
2omre
2omro
2omru
6oms.
o6ms
oms3al
om4sek
om1se
3omset
omsk2o9d
omsku9la
omsk8u
om4som
om1so
oms3un
om1s4ø
o2m1t2
3omtal
om3ta
om4t4es
om1te
om3ti
om3t1v
o2m1u
o3m2us
omvæ8re.
o2m1v
om1væ
omvæ1re
6o1my
omyr8ke.
o4my2r1k
om5yr1ke
o2møk
o1mø
omø4r
o2m5øy6
o5må
2on
o2n3ab2o
o1na
o4n8ac
o6nak
o4n3a6ng
o4nap
on3a2pp
o6n1a2r1b8
o4n3a2r1g
o4n3a2r7v
onat5r4
o2n1a4v
o4n5b2
on4dar
o6nd
on1da
onde1r6e
on1de
ond2er
ond3re
ondr4
on4d5ri
ond2s4i
on8ds
o4ne.
o1ne
on5e2i7e
o2nei
o4ne1le
o7ne2l1l
o2nem
o3ner
o4n2eru
one3si6
o4nesk
o4ne3s8t
o4n2e3te
o2ne1v
o4n7f
on7ga.
o6ng
on1ga
on8g3d4
on5ge.
on1ge
on6ged4
on2g6es4
on5gi
on5go.
on1go
on1g2r4
on8gro
on5g9r2os5
ong2s4j
on8gs1
on2gu
on4gy
on4g5ø
o9ni.
o1ni
o2n3i2d
on2i1e
o2nil
on5i6ld
o2nim
oni6mi
o4n3i4n1f
o6n7i4nn
o2n4i1o
o4ni5p
o6niso
o4n3j
o2n1k
on5k6a
3onkel
on1ke
on5k6i
on4k1le
onkl4
on5ku
onle6g
o4n1l
on5le
on5nad
o4nn
on1na
on3ni
on5ny
o4no.
o1no
o5n4or.
o4no4v
on3o3ve
on4s1h
o6ns
on1s1i
ons2i3s
onsi4v
ons1k4
on2s7ke
ons1l4
onsl4a6ga
onstitu2e9ra
ons1ti
ons2tit
onsti1tu
onstitu5e
onstitu1er
onstitue9re
onst5r4um
on3stru
onstr4
ons5u6nd
on1su
ons1v2
on4s3ve
ons1å
ont6a
o6n1t
on3te
on4ted
on5ten
on5ti
on4t6ok
on5tor
ont2r4
on7ul8
o1nu
on5ur
onu4sk
o4n5ø4
o2n6øy.
onøy1
on7å6
2oo
oo8d1s
o2o1d
o1o2ff
oo2k1
oo5k8a
ook5es
oo1ke
o4o1l7a
oo4m5i
o2o6mo
o1o2p
o2o1pa8
oop9an
oo4pe
o2op5en
o2op5et
o6o1pi
o1o4r
oor6da
oo4rd
o2o5s
oo6sp2
o1o4v
2op.
2o1pa
o4pab
o2pak
opa6n9d
o4pa3re
o7paren
o4pa1sj
op6as
o4pau
o3p2ea
o1pe
ope1i
opel6lø
op6el
ope2l1l
2open
o4p2e1na
o9pe6nd
o4pe3net
ope1ne
o7pe6n1t
o2per
6o3per.
3op2e1ra
ope6rar
o7p2ero
o5pe6r1t
2opet
o4pe1ta
4o2p1h
o1pi
o4piek
op2ie
opin8g9s1
o3pi6ng
o4p2ir
2opl6
o1pla
o4p5la6nd
op3li
op9lu
op4na.
o2p1n4
op1na
op4ne
2o3po
2opp.
o2pp
4op1pa
op4p5a6r1t
op4p6as
4op1pe
1op2p1g2
op4pi.
op1pi
op6pia
op4pis
opp3li
opp1l6
3opp1ly
op6p6o1e
op1po
op6p1r6
opp5rop
op1p2ro
opp5u6nd
op2pu
op6p1å2
2o1pr6
op6re
o6p7ru
o4p7rå
op6sa.
o2ps
o8p8si
op2t1r4
o2p1t
o4q
4or.
o6r7a6d1d
o2r3a1dr4
o2rag
or1ak
2oral
or5a6ld
o4r5a2l3g
oral4st
or2a8l1s
or6alt.
ora2l1t
o3r4am
o4ra1na
o3r4a1ne
o4ra6ng
o4ra6ns
or6ap
or3a6tt
o4rau
o4ra3u4k
o2r1a4v
4o2r1b8
orbit5
or1b4i
or7by
4or1c
ord4a9ta5
o4rd
or1da
or3d2ea
or1de
6ordel
or4d7e6p2l6
ord2e1p
or7d2ik
or1di
ordi4s
or4d5i1se
or2d3it
1or8d3n
ordre8gi.
ordr4
ord1re1gi
ordsa6me.
or8ds
ord9sa1me
ord1s2am
ord1s6e
ord3st4
ordy9ra
or1dy
8ore
orea1r8a
or2ea
ore1ar
orea2r8e
o5re1b4ra
o2r2e1b2
orebr8
o8re3di
or1ei
or1el
o4r2e9la
o6re6ld
ore6na.
o2r2e3na
o8re4ned
ore1ne
o6re4net
o2r3e2nk
ore6n3s
ore6o5g6
ore3o6
o6re2r1f
o1rer
o4r2ero
ore1s2
o5re2s3c
ores6te
o4re1ta
or9e8t8n
o6r2e3t2v
o2re1v
2o2r1f
orfa6re.
orfa5re
or1fa
or3far
orf4i7ne
or1fi
orfø9re.
or1fø
orf8ør
orfø1re
1or3g4a
o2r1g
4or5ga.
or3ge
or5g2er
org6e4s
orgi6e5ne
or3gi1e4n
or1gi
org2i1e
org4sk
or8gs1
orha9g
o2r1h
orh2a
orh2a9le
o1ri
o3ria
o2ri5b4
ori4e5ne
ori1e4n
or2ie
5o4rie6n1t
o7ri1et
o2r2ig
ori7k2a
or2ik
o6rim
o4r3i6nd
o2r3i2nk
o4r3i4nn
o2r3i6n1s
oriro8
o4r2i1r
6oris
ori1s4a
6orit
o4ri7t2i
oriti6me.
oriti5me
or5ka
o2r1k
3orkes
or1ke
or5ko
orko6se.
or1k2os
orko1se
or8k7s2
6o2r1l
orla7te
or5le
orm5a6ng
o2r1m
or1ma
or6map
or5m2el
or1me
or4m3un
or1mu
or5mæ9
or4nar
o2rn
or3na
orned5
or1ne
orne6d1r4
or5net
or4nol
or1no
or6n3t
4oro
o3r2oa
o3r6o1e
or3o2ff
o5r2og
o1ro5i
o1r2on
or1op
o4r1or
oror9da
oro4rd
o5ro1sa
or2os
o5r2ot
or3ove
or5o2v1n
or2pe6s
o2r1p
or1pe
4o2r1r
2o4rs
ors5a2l1t
or1sa
or5sen
or1se
or4se2r1k
or2se9t6a
orse7te.
ors2e1te
or3si
or2s5k2ar
or1ska
ors6k5ei4
or2s1ke
or6s8k9l4
or1s4ku
or1s4l4
orsmå8la
orsm6ål4
ors1m
ors1må
or1s1n
or3s2o
orso9na
or3s2o6n5
or7s6o9n8e
or2s1o9v
ors4pa
or1sp2
ors4ten
or1s2t
ors1te
or1su
orsva9re
or1sv2
or3sy
or9sø
2o6r1t
or4t3ak
ort8a8la.
or4t5a6nd
or1ta9pe
or4t5av
orte5i6g
or1te
or4tek
or3tem
or6t5e2r1f
or7t6er
or4t5e2r1m
or5ti1i
or1ti
or7t4il
or5tis
3ort2o1d
or6t6ok
orto9ne
or1t2on
orto4r
ort5o1ri
or4tou
or2t5res
ort2r4
ort5r6å1d
ortå4
or4t3år
or1u
or2u8d
or9u1de
oru4h
o5r4um
oru4t5
or4uta4
or4u6t1f
orva9ra
o2r1v
orv4a
or5veg
or8v3s1
4ory
o7ry1a4
or1yn
o9r4æ4re
2orø
or9ø8k
orø6k8t
o1r5ør
or3ø4v
orø6v8d
or1øy
or5å1s2
2os
o1sa
o4sa5b
o6sad
o2saf
o4s4aku
6o5sau
os1b4i7
o8s9b4
os4e1fi5
o1se
o2se1f
o2seg
o5selei
os2el
ose1le
o2sem
os2en
o6s2e1na
ose5sl4
ose5s1m
oses4sk
ose4s1s
oses6sp2
oses4s2t
o4se1u
osf4a9ta1
o2s1f
os1fa
os2hi
os1h
os7hi2s1
osi6e7ne
o1si
osi1e4n
os2ie
osi5e1re
osi5e6r5t
o2s1i6ng
os6k3ei
o2s1ke
oske2i7e
o4skil
o1ski
osk9lar
o2s1k2l4
o6skla
osk5len
osk1le
o1sko
osko5p
o3skri
oskr6
o2s8k3v2
os2l4
os3le
oslo1
os3l2o1d
o1s5lu
os5lø
os9ma
os1m
os5me
os3mo
o2s1n
o3so
os7ove
o2sov
os6pa.
osp2
os1pa
o4s6pe.
os1pe
os4pil
o1spi
os4por
os1p6o
os4sek
o4s1s
os1se
os3sem
os5s2e6ns
osser2i7e6
oss2e1ri
os2se5v
ossi4s5te
os5s2is4
os1si
os6ski
oss3kj2
os4s5ko
os2s9l4
os6s1pa
ossp2
oss7tro
oss2t
osst6r4
os7sty
os8sv2
ost5adr4
o3s6t2ad
os5tal
o4s3t2a5le
o8s3te.
os1te
os4teg
os4t5e4g1g
os4tek
ost6el
o4s5te3o6
o4sti.
os1ti
o8s3tia
o4st2i1e2
o4stin
o2s5tis
ost1o
os6to6ns
os1t2on
ost5ran
ostr4
ost3re
o3stro
ost5rup
ost7rå
o5stø
os1v2
os5øy
o1sø
os7å2r
2ot
o1ta
o5tad
ota4l5a
ot5a6ld
otal7e5v
o3t2a1le
ota4lov
o9ta1ne
o9t4ar.
ot3a2r1g
o8t9a2r1m
o5tas
o2te7d
o1te
ot6ei
ote5i6n1t
ote1k5i
ote6k7la
o2te1kl4
otekl4a8ga
ote4k1le
ot5e2lem
ote1le
o4te5le1v
otel6lan
ote2l1l
otel6lek
otel1le
otel4li
otel8lø
o4t2e1na
o4t5en1de
ote6nd
oten8de.
o2te3o6
o4teram
ot2e1ra
o4tere4t
ote1re
o6tere1v
o4t2ero
ote4r5s
ot2e5r8u
o4t2e1rå
otes6ter
ot4es
otes1te
ote5sté
ote1t5a
o2t2e1t5o
o3te4t3s6
ot1fø4
o6t1f
oti7e6r5t
o1ti
ot2i1e2
ot2i2k
oti1k2a3
o2t7i6ko
o1tj
o6tja
o2t5jer
otj2e
ot6n2ero
o8t1n
ot1ne
otno7te.
ot1no
otn2ot
otno1te
ot6nå
o1to
o2to.
o4t2o3a
o5toa.
o2tob
oto6en.
o1t6o1e
otoe2n
o6tof
ot3o2ff
o6to1ga
ot2og
o6togra4m5
otog6ra
oto1gr4
o4toi
o2tol
oto5ne
o1t2on
ot2o4ral
oto1ra
o4to4r5d
2o2t2ot
o2tou
o1tr4
ot7red
ot5rer
o4t5re1v
o6t7ri
o6t5rom
o6t5rø
ots5el
o4ts
ot1se
ot6s1h
otshus1væ8
ot2shu
otshus1v2
ot4s3ki
ot2s6o2p
ots1o
otso2pp6
ots5pr6
otsp2
ots5tab
ot1st4
ot7s1te
ots6å
ot6ta2nk
o6tt
ot1ta
ot5tas
ot5teg1n
ot2teg
ot1te
ot6te2nk
ot3ten
ot5t2e1se
ott4es
ot5tin
ot1ti
ot5t2oa
ott4s3k
ot6ts
otts5p6o
ottsp2
otts4ti
ott1st4
ot7tug
ot4typ
o1tu
o6t5ut
o1ty
ot4y8e
o2t1y2t
o1tø
ot7å
oub8
ou2l5l
oun6ge.
ou6ng
oun1ge
ou4r
ou7ri
ou5ro
o4u9sa
out8a
ouve4
ou1v
o5v2a1e2
o1vak
ovan1fø8
ova4n1f
ova1n9o
ov3a6n1v
o7var.
o6v1a2r1b8
ova9re
ov5a6r1t
ov7a2r1v
ove2d3
o1vei
o1vel
ov2e4la
4o1ven
o4ve6nd
o5ven1de
o4ve6nya
ove1ny
ove2r
ov2e1r3a
ove7ra.
ove9ras
o6ver1dr4
ove4rd
ove1re6
ove8r5es
3o6ve2r1f
3ov2e2r1g
o5ver8ks
ove2r1k
5ov2e2r1r
5over3s2ik
ove4rs
over1si
ov6er1s4p2
ove2r9v
o1v4et
6o2v1h
2o1vi
ovi1so3
2ovj
6ov1na
o2v1n
ov4ne1s
ov1ne
2ov1ni
4ovo
o5vo.
o7vom
ov1or
o5v2ot
ov1o2v
ov5sal
o8vs
ov4sek
ov1se
ov4se4n
ov9s8ke
ov1sk
ov4s1le
ov1sl4
ovs1p2
ovs5te
ov1s2t2
ov4s1ti
ov7sun
ovs6y5k
ov1sy
ovta6le.
o2v1t
ovta1
ov3t2a1le
ovve1g7i
o2v1v
ow1
ow2a8
o7was
o1wat2
ow5h
ox3
oy9ar
oys5l4
o2ys
o1y2t
o1za
o3zy
o1ø
o1å
ô6ra
ô2re
ôr5ei
ô1ri
ô4t
ó9sa
ó7t
ó8v
ò9re
1pa
4paa
2p8ac
pa8cen
p5ad.
pa3d2e7b2
pa1de
p4a2d1g4
pa8ds4
2pa1j
6p6ak.
6pa1ka
4p2a1ke
pak4kas
pa2kk
pak1ka
pak4ke4s
pak1ke
pakk7e3s4l4
4pak1ti
pa6k1t
3pa1la
p2a3le
pa7lim
pa1li
pal5in
pa2l1j
pal5lø
pa2l1l
5pa2l1m
4pa1lø
2pam
pa5me
p3anal
pa1na
pa4nap
pan9de
pa6nd
pa5ne
pan8g5s6
pa6ng
pan3ka
pa2nk
9panne.
pa4nn
pan1ne
pan5se
pa6ns
pan5sl4
pant8r4
pa6n1t
4pa6n1v
p6a4ny
2pa1pa
pa8pa.
pa3pe
4pa1po
pap4p1r6
pa2pp
4papr6
pap3ri
pa4ra.
pa1ra
5parad
2p1a2r1b8
4pa2rek
pa1re
4p3a2r2e3na
pa2r5e4s1s
pa2r5g
2p2a1ri
pa4ri.
pa3ris
4par3ki
pa2r1k
par6k7l4
par4kv2
pa2r3m8
pa1ro
4pa2r1r
par8ra.
p1arra
p2a4r9s4
par6tid
pa6r1t
par1ti
par4t2ig
par4tin
par5u
pa2r7v
parvi6
6parø
4pa1rå
p6as
p4a1sa5
pa9se
pase6r5
4pasp2
3pa4s1s
pas5sab
pa4s3t
pas4tar
pas1ta
pas5ti
pas9v2
pa1t
pa3te
pa4tist
p4a1ti
pa6tre
patr4
p8at1ta
pa6tt
pat6tak
2pa1tu
p8a2t6v
pau7k
2pav
pa4ve.
p2a1ve
3pa1vi
2p1b6
pba4ne.
p1b4a
pba1ne
pbo6da
pb2o
pb2o1d
pce6
2p1d4
pde4le.
p1de
pde1le
p1dø2
pdø9d
1pe
2p2ea
pe1a2k3
pe4a3re
pe1ar
4p2e1b2
p2ec3
pe7d6a
5pedas
4pe6d1d
pe3de
ped1fø9
pe2d1f
ped2i9e8
pe1di
pe6d2oa
pe1do
pe6d2on
4pe1dr4
pe4dro
4pe1du
4pe1dy6
2pe1dø
pe7då
pe3e6
pe3er
2pe1f
p5e8ft
2peg
p5e4g1g
peg8ge.
peg1ge
p4e1go4
2pe1h
p4e4il
pei4leg
pei1le
2pe1in
pe6is1m
p2eis
2pe1j
2pek
pe8ka.
p4e1ka
pe1k4l4
pek4t2ro
pe6k1t
pektr4
pekt7r2o5s
8pe1ku
p6el
4p2e1la
pe5l4aks
pe2l1ak
5pe2l1al
pe5lar
pe6l9d
4p4eled
pe1le
pe4le1f
4pelei
pe6l2e1p
4p2e3ler
pe6le1v
6peliv
p2e1li
6pe2l1j
pel4lo
pe2l1l
4pe1lov
p2e1lo
pel5s6e
pe8l1s
pel5s4i
pel7st
4pelu
pe5lun
2p4e1ly
2p2e1lø
6p2e1lå
2pem
p1e2m1b
pe4nan
p2e1na
pe4n3ar
pen3de
pe6nd
6p5en5d2en.
4pener
pe1ne
pe3net
5pe6ng8
pen4gel
pen1ge
pen7g1l
peni4n
pe1ni
4peniv
penly4
pe4n1l
pen7s8a
pe6ns
pensa7k
pen3sa8la
pen5sk
pen4s5l4
pen3s6m
pen5s6o
pen1s4t
pen9sta
pen7s1te
pen7tag
pe6n1t
pen5tr4
6pe1ny
2p4enå
2pe3o6
pe7o6s
2p2e1p
pep5ar.
pe1pa
pe2p7p
pera3a
p2e1ra
4pe4rab
4perad
pe4rai
pe4ral
4perap
pera5t
4per6a1te
4pered
pe1re
4perei
4perek
4pere1s2p2
4perest
4pe1re1su
4pere4t
pe4re1v
3pe1ri
peri5e8ns
peri1e4n
per2ie
4pe5r2ik
peri3s4
4perit
pe2r5k
per6les
pe2r1l
per1le
per5mu
pe2r1m
p2e2r5n
4p2ero
pe3r2os
pero6se.
pero1se
per6re1gj
p2e2r1r
perr6e
per6rei
5perro
pers6m
pe4rs
per4tro
pe6r1t
pert2r4
per4t5rå
perv2i6k
p6ervi
pe2r1v
2pe1ry4
6p2e1rø
4p2e7rå
2pe1s
3p2es.
p2e3se
pe6se.
pe7si
pe2s8ka.
pe1ska
p3e2s1ke
pes4ke.
6pesl4
pes4n
5pe4s1s
3pe6st.
5pe6s5te.
pes1te
pes5t4il
pes1ti
4petab
pe1ta
peta6ka
pe3tak
4pe3tal
4petas
4pe1tau
p2e3te
5pete.
4pe2ted
6petei
6petek
4petel
4pe2tem
p2e3ti
4pe3tid
4pet4il
pe4tim
2p2e1tj
2p2e1to
2p2e1t4r4
pe4t5ru
pe4t1s6
4pe6tt
2p2e1tu
4pe1tø
4pe1tå
pe2u
2pe1v
peva8ne.
pev4a1ne
pe5vi
p5e2v1n
pev6ne.
pev1ne
4pe1ø4
4pe1å6
3pé1r
2p1f
3p6fe4nn
p1fe
p3fo
p5fr2
pfri4
pfø5re
p1fø
pf8ør
2p1g2
p3gjer4
p1gj
pgj2e
2p1h
phav2
ph2a
pha9v4a
pha8vs5
ph2e9te
phet2
phe7va
phe1v
phe5ve
phi5li
7pi3a1ne
7piar
pi4as
7pia1se
4pi1av
4pi1b2
pi6ca.
pi1c4a
2pid
pi8d3s2
pi1e2n
p2ie
6pi2e6nd
pi3er.
pi9e4rs
2p2if
pi9fr2
pig5ge
p2ig
pi4g1g
pig6g9u
p7i6gj
2pi3h
6pi1i
pi4ke5h
p2ik
pi1ke
pi6kel
pik2e5r6o
pi4ké4
8pi2kk
pik3ko
4pik1l4
pi1ku5
pi5la
pi6la.
pi3l2e1p
pi1le
pil4lag
pi2l1l
pill1b4a6
pil2l1b2
pil6led
pil1le
pil4leg
pill2e1ga6
pill2e6ra
pil4l6e5s6
pi1lo
2pim
pi4na.
p6i1na
pi4ne.
p4i1ne
pi9ned
pi4nel
pi2ne4v
3pi6ng
pin6go
pin2g3r4
ping5sk
pin8g2s1
6pin6n1s4
pi4nn
pin4sl4
pi6n1s
p6i2nø
pio6n5an
p4io
pi2on
pio1na
pio6n5s
3pi2p
pi4pi
pip9la
pi1pl6
pi4rar
p2ir
pi1ra
pi4res
pi1re
pi4rut
pi1ru
pis2i9e
pi1si
pi4ski
2piso
pis2s4l4
pi4s1s
pis4sp2
pis4s2t
pis1t
pi2s4t5r4
pis9t8ra.
1pit
pi9ta
pit8a7la.
pita4l3a
pi5té
2pi1t2i
4pitj
4pi1tr4
pi6t5t
2pi1u
2pi5v
4pi1ø
4p5k4
p2ka5v
pka8va
pl6
8pl.
2plad
pla8de.
pl6a1de
p4lak
p4lan.
plan7de
pla6nd
4p3lane.
pl6a3ne
pla6n5g
pla6n5s
plap3
4p1lar
p2las
pl6a4st
p2lat
plat6i1na5
pl4a5ti
2ple.
p1le
2pled
4pl2eis
p2lek
p6l2e5n4u
pl2e5n4a
pl2e6r5u
ple8se.
pl2e1se
4ple1v
p2li
4plit
p3liv
pl4i7va
plo4gj
p1l2og
p4lo8i
p1lok
4p5lov
plu4e
p4luk
plun3
plun6d7ri
plun5d4r4
plu6nd
p1ly
ply5d8
plæ5re
p1læ1
plø6pa
p1lø
p1lå
2p3m
2p1n4
p7ner
p1ne
pne6se.
p4n2e1se
1po
p2o9a
p2o1d8
po6da.
po1da
po4de.
po1d4e
po6em
p6o1e
2pof
po6f7r2
p2og6
4p2o1h6
po5id
2po1ke
po6lan
p4o1la
p2o3le
po4lek
6p3o6lj
pol5li
po2l1l
po6lom
p2o1lo
pol6s4ka
po8l1s
po2lu
2pom
pom6p9u
po8m1p2
po6m5s
4pon.
p2on
po6n5d
pon4g2r4
po6ng
pon8gs6
2po6n1s
pon5sa
pon4s1v2
p2o2p1a
po6pe.
po1pe
p5o2p1n4
po2p1s
2p4or.
po1ra
2p5o4rd
p8o1r4e
po4re1f
po6reg
po8ré
2p1o2r1g
2po1ri
2po2r1k
por6s7v2
p2o4rs
por4to4r
p2o6r1t
por4t5ro
port2r4
por4trå
2po2r1v
po1ræ
p2o1rø
po4se.
p2os
po1se
po4ses
4po1sj
po2st
po4sta
po1s5tas
po5stat
pos3te
4pos4v2
5pot.
p2ot
7po1ta
potak9
po3te
po4te.
po2te1k5l4
po5t6h2a
po2t1h
3po1ti
4po1tr4
4pou
2po4v
pove6
pov2e2r6n
pove2r
pow4
2pp
p2pad
p1pa
p2pak
pp3a6k1t
p2p3a4l
p9pa5ne
pp5an1gr4
ppa6ng
pp5a2nk
p4p3a4n1l
p2pap
p9par.
p4p5a2r1r
p7pa4s3t
pp6as
p4p7a1t
p4pe3e6
p1pe
p2pe5i4
ppe8l5s6
pp6el
p4p2e1na
p8p9en3d2er
ppen3de
ppe6nd
p4p1endr4
ppe9nes
ppe1ne
p2p2e5p8
p4pe1nø
pp2e5ra
p7pe1re.
ppe1re
p4perkl4
ppe2r5k
pp7e6s2en
p2pe1s
pp2e3se
pp9es4n
ppes8ti
p4pe1ta
p4p5e4tas
p4p2e3te
p4p5e6tt
p2pe2u
p1pi
p2p1id
p2p1il
p2p5im
pp3i4n1f
p4p5i4nn
pp7ir
ppir8re.
ppir3r6e
ppi2r1r
p4pis1t
pp1j
p4p5k4
pp1l6
pp5l6a4st
pp2las
p2p3led
pp1le
pp5lei
p4p9le1v
p2p5n4
p2p1of
p1po
p2pol4
pp3o6ld
p2p5om
p2p1op
p2p3o2r1k
p2p1o4v
2p2p5p
pp1r6
pp5rei
pp5rin
p1p4ris
pp7ri4s1s
pp9riv
p8p9ro.
p1p2ro
ppropri6
ppr6o1pr6
pp7r2ot
p2p1s
ppse6te.
pp5s2e1te
pp1se
pp3ska
pp2ska9k
pps2p2
pp9s1pe
pp3s1pl6
pps2t2
pp7sto
p2p7t2
p2pu
pp1u2k
pp5ut
pp3ø4
ppøs8
p6på
pr6
4pr.
4prad
3p2raks
pra7li
2pran
pra6n3s
5pr4at.
5pr6a1te
pra5te.
4pray
5pre1f
prei7er.
p2re2i7e
4pr4ei1i
pr5e2lem
p2r2e1le
1p2rem
pre6n4s
1pres
6p1r2es.
pres6sak
p2re4s1s
p6re1stas
4pre6tt
p3r2if
pri5ke
pr2ik
pri4l3e
4pri6ng
5pr2i1no
3p2r6i6n6s5
3pr6in7s6e
3pr6in7s6i
1pris
pri6s5k
pris3t
2prit
pri9ve
1p2ro
8pro.
6p7r4oc
3prof
4prog.
pr2og
4pro1ge
4progl
4p3roi
p5r2op.
3pr2os
7pro1se
6pru
prun7ge
pru6ng
pr4u5ta
pru5t8e
6p1rør
prør2s5t
prø4rs
prø5s4
5prøv
prø5ve8l1s2
2prøy
4prå
prå8da
pr6å1d
prå1k3i
prå4ko
prå2k5k6
2ps
p1sa.
ps5a6n
p1sc
p3se.
p1se
psei8g6e
p2se2ig
p2sek
p2s2el
p2s5e4ly
p3s2en.
p5s2e6ns
p7s2er.
p5s2e1te
p5s4e4ts1
p2s1h
p6si1b2
p1si
ps5i6n1s
p7s2is
p3s4j2o
p1sj
p4s3kil
p1ski
ps7kjen
pskj2
pskj2e
p2s1ko
p7s6ko.
p3sk2o1d
p5s4k6o3e
p2s1le
psl4
p9s8lo.
pslø8va
ps1lø
pslø6ve.
pslø5ve
p3s4lå
p2s1m
psmå8la.
ps1må
psm6ål
psmå1la
p2s1n
ps4no
ps1o
p3s2o1d
pspi9la
psp2
p1spi
p4s5p1le
p2s1pl6
p3s4pre
pspr6
p8s7p2ro
ps5pu
p4s3s2
pst2
ps7tal
p4s5tem
ps1te
p2s3t1v
p5s6tå
psu4r
p3s4us
ps1v2
p2sva
p2sve
ps4v6i6ng
p4s1væ
ps5w
psy3ke
p1sy
ps6yk
3p2sy1ko
4p3s2y1s
p2s1ø
p3s2øk
psø4ke.
p3s6ø1ke
psøy8
psøy9e6ne
p8sø4y1e
psøye4n
ps1å
2p1t
pt8a8la.
pta4le.
p3t2a1le
pt7a2r1k
p3te
pte6k
pte4ma.
p2tem
pte1ma
pte7re
p5ti
pt2o7g
p4tou
ptus5t
p4tut
1pu
pu4b4r8
5puc
6p3u6dy
p2ud
pu2k
pu7la
pu8le.
pu1le
pu2l1l6
5pum
pu4ma.
p2u1ma
pun6k4t5
p4u2nk
punk5t6e
2pu4nn
2pur
pu4re.
pu1re
pu8r2ea
3pu1ri
3pu2r1k
pur5u
pu2r3v
p4u7sa
pu2s4h
pu4sl7u
pus1l4
pu1ta
pu4ta.
pu5ta1s4
p2u5ter
pu1te
pu5te1v
4p1u2t3g2
2p1u4t1s4
put6tr4
pu6tt
put4tu
2p1u2t1v
6put1ø
2p1v
pver7
pvi4se.
pvi1se
py4dr4
py8o
3py1ra
py1re
py1ro
6p2ys
py6sa.
py1sa
py4se.
py1s4e
pys6t
4p5z
1pæ
pæ4re.
pæ1re
p4ø1kj2
pør4ret
pø2r1r
pørr6e
pø8sa.
pø1sa
pø9ta
p4øt
pø9te
p1ø2v8
5på1b4
på4by.
på1by
på1k2
1pål
på4la.
på1la
på4le.
på1le
p5ån
på3p2e
på1pl6
p1å2p1n4
på1r
1pås2
på7sko
på5s1m
på3t2
6påtå7
1på1v
qa5
qu2
qu9ar.
1que
qu1e7r
4raa2m
4raar
4rabis
ra1b4i
ra1b2o4
4rab1r8
2ra1by
ra3cet
r8ac
ra3ch
5raci
r6a3d2a
4ra2d1f
3rad4io3
ra1di
4rad2ir
4rad1j
2r1a2d1m
2ra1dr4
r3a2dre
ra8d2s3
radvi4
r1a2d1v
ra5e3de
r2a1e2
ra4ed
rael4
4rae2r1k
raf4fer
ra2ff
raf1fe
ra4fi1u
ra1f4i
ra2fj
2r6a1fo
ra5fo.
ra4f2os
2rafr2
ra5fre
6ra2ft
ra4fu
ra6fy
ra7g2e1a
ra1ge
ra5ge3e2
2ragl
2ra1h6
7raid
ra5i6n1t
ra3i4s3k
6r5a2kad
ra1ka
r4a9kar
ra7kel
r2a1ke
ra5k6h2a
ra4k1h
r2a2kk
rak4kel
rak1ke
r6akr6
ra5k2ro
2raks
rak6sa
rak3s4e
rak6se.
rakst6
4rak1ti
ra6k1t
rak2t3r4
r4a5ku
4r2akv2
ral5a6ns
6r5album
ra2l1b2
ral1b2u
5ral8ds
ra6ld
4raled
r2a1le
ra5le3o6
ra2l3g
4r5al1ge
4r5al1go
rali5e6n
ra1li
ral2ie
ra4lin
ra2l1j
ral5le
ra2l1l
ra2l7m
ra5lo6i
ra2l3op
r6a5ly
4ra1lø
4ra5l8å
ra4mag
ra1ma
ra4mas
ra2m3b
6r9am1b4i
rambu9e6ns
rambu4e
ram1b2u
rambu1e2n
6ramed
ra1me
4ra4mer
ram8et.
4ramil
r2a1mi
ra2m1o
ram3pe1ri8
ra8m1p
ram1pe
ram6p3u
ramse8te.
ra6m1s
ram1se
rams2e1te
ramt8a8la.
ra2m1t
ram3ta
ramta6le.
ram3t2a1le
4ra1mu
6ra1my
r4an.
ra4naa
ra1na
6r3anal
r4a5nar
ran9c4s
ran1c
r4an9de.
ra6nd
ran1de
r5an5d4e5l
rand3r4
rand5s6a
r4an8ds
r4a1ne
4ran1fa
ra4n1f
ran5g4e
ra6ng
6ran1gi
rang5st
ran8gs1
rania8
ra1ni
ra6nin
r8an3kv2
ra2nk
2r5a4n1l
r6an1li
2r1a4n3m
r4a4nn
ran6n5e6tt
ran1ne
ran4n5in
ran1ni
ran6n3s4
ran2s7k2ar
ra6ns
ran1ska
4r1an1sv2
ran4t2ik
ra6n1t
ran1ti
r2a5nu
ra2n6ut
4r2a1nø
2ra1o
4ra1pa
ra4p6el
ra1pe
4rapin
ra1pi
ra4p2i6r5
ra4pis
ra6pit
ra1pl6
4ra1po
ra4p2os
4rap1pa
ra2pp
4r5app1l6
3rap1po
2ra1pr6
4ra2p2s1
4r8a1pu
1r4ar.
2r1a2r1b8
7r6arbe1h
rar1be
4r1a2r2ea
ra1re
4rareg
rar5e6l
4ra3r2e1p
rar7e1ta
rar4et
r1a2r1g
6rar1gu
8rarin1na
r2a1ri
ra4r3i4nn
6rarin1ne
rar8ka.
ra2r1k
4r1a2r1m
rar8ma.
rar1ma
6r2a2r2n
4ra2r1r
rar3r6e
r2a4r5s
2r1a6r5t
rar6ta.
ra3rø
4r4a1sa
2ra1sc
ra3s2e1a4
ra1se
4ras4el
ras3h
ras1ka8ra
ra2s3k2ar
ra1ska
ra2s3ke
ra4sk2i
ra7s6ko
ra6s1l4
ras2s4l4
ra4s1s
ras7s3t
4ras1ti
ra5s4t4il
4ra1stj
rast5re
rastr4
6ra1sty
ras7v2
r4a1ta1
ratak9
r6a1te
ra4te.
6ra5teg
8r7a6t2e1li
4rat1fe
ra6t1f
ra1to
ra1t4r4
ra4t5ro
ra4trø
ra5t6røy
rat5tel
ra6tt
rat1te
ratt4e4s
4ratub
ra1tu
ra2t5ut
6ra1ty
7ra2ud
rau8d3s
6raug
rau6ga
rau6ge.
rau1ge
4rau4k
rau4s6s
2rav
6r1a6v1d
ra2v4e5s4
r2a1ve
4r1a2v1g2
ra1vi
r3a4vis
4ra6v1l2
rav8l9u9t
ravlø8pa
rav1lø
ravlø8s
ra2v6r8
4r1a2v1t
ra5vy1
ra3vør
4raw
raz5z6
2ra1ø
raøy4
2r1b8
rba3d
r1b4a
rbe2d
r1be
rbe1de4
rbed5en
rbed5et
rbed9ra
rbe1dr4
rb2ie8
r1b4i
rbi9er
r2b2i5g
r2b2ik
rbi1st6
rbi2s5tr4
rbo8di.
rb2o
rb2o1d
rbo1di
rbo4ni
rb2on
rbo6n7s4
rb8o5re
rbra5s
rbr8
r1b4ra
rbrei6
rbu5e2n
r1b2u
r5b4ø
r1c
4rd
r4dab
r1da
r5dag
rda8g4s5
r5da1h
r4d5ak
rdal4
r4da1la
rda4le.
rd2a1le
rd2a8l1s5
rda4me3s
rda1me
r7da8n9o
r4da6n1t
rd5anta
r4d3a2r1m
r4d3a6r1t
rd5a6t3l6
r4da2t1m
r4d3au
r6d3d2
r6de1di
r1de
rde4e4n
r2de3e2
r3d4e1fi
r2de1f
r6d6e5ge
r2deg
r2d1ei
r9d4e1ka
r4de1k2l4
r4deks
r6d2e1lo
r6de1mi
r7d2e1na
r6d7e6ng
r6d7e4n1h2
rd2e6n4s3
rden1se4
rdenta8le.
rde6n1t
rden3t2a1le
r4de3o6
r6de1po
rd2e1p
r4der2ik
rd2er
rde1ri
r4d5er1s2t
rde4rs
rde6s1m
rde1s
r2d3e4ta
r6d7e6tt
r8dé
rdfes5
r2d1f
rd1fe
rdi3an
r1di
r4di2a1na
r4di1a6ns
r6diau
r4did
rd2i3e2
r2d2if
rdi6gres
rd2ig
rdig2re
rdi1gr4
rdi8g3s4
r4di5k2a
rd2ik
r4dik1l4
r4di1ku
r2dil
r6di1mi
r2d4io
rdi3ov
r4dis1h
r2dit
r2di1u
rd5j2e
r1dju
r2d7m
r8d3n
rd1næ4
r9do2b3
r1do
r4d5o4d
r4dol
rdon8na.
rd2on
rdo4nn
rdon1na
r2d1op
r6dor
r2d3ost
rd2os
r2d1o4v
rdo4ve2r5
r9drad
rdr4
r3drak
rd5ran
rd7ra1ra
rd1rar
rd3ras
r3drei
rd3ret
r5drev.
rdre1v
rd1ri
rd3r2ot
rd3s1ei
r8ds
rd1se
rd8s1ke
rd2s4kv2
rds5tan
rdst4
rd3sto
rd2s3t2o5g
rds7tre
rdstr4
rds7tu
rdsva9r
rds1v2
r4d5t
rdta8ka
rd3t4a
rd5tr4
rd7tø8
rd3u6nd
r1du
rd5ve
r2d1v
rdvi8ka.
rdv2ik
rdvi1k2a
r4dyg
r1dy
rdy5p8e
rdy3re
r6d7y2t
r6d5æ
r7d6ær
r7d6æ5r6e
r2d1øs6
r1dø
rdø4ve.
rd3år
rd7å6s2
1re.
2re1an
r2ea
re7a6r7an
re1ar
rea1ra
rea2r5e
4re1av
2r2e1b2
3redak
re1da
6redam
re3de1f
re1de
re5den
re3de1s
re3di
3red2ig
r1e4d1l4
2re1do
2re1dr4
re4d5ri
red5sku
re8ds
red4s1l4
red3s5la
red7s6led
reds1le
3reduk
re1du
6re1dy
2re1dø
2re3e2
re4el.
re3er
8r4e1fa
re1f
4re2ff
4r4e1fi
2r4e3fj
6ref2os
re1fo
6r8efr2
4re8ft
2r4e1fø
4regar
r2e1ga
reg4a7ta1
reg2at
re3ge
4re2g2e1b2
re5gel
4reg2en
4reg2er
4re4g1g
1re1gi
re4gia
re4gil
reg1l
2re1g2r4
4regub
re1gu
4reg2ud
2r4e1gå
2re1h
2reid
2re2i7e
2r1e2ig
rei8ga.
rei3ga
rei8ge.
rei1g6e
4re2ik
r4ei9l
rei5na.
re6i1na
rei7nas
re5in1de
rei6n1d
rei5ne.
re4i1ne
rei7nes
rein6skj2
rei6n1s
re4insk
re4inva
r6e1i6n1v
rei9ra
r2e2ir
rei5sa
r2eis
rei3si
rei7ska
reis6led
reis1l4
reis1le
re7is1m
re4i7va
rei5ve
2re1j
6rek.
re5ka.
r4e1ka
re5kav
re7ken
re1ke
4r4e1kj2
rekk6an
r2e2kk
rek1ka
rek4k5v2
5reklam
re1kl4
rek4led
rek1le
re5k6l2ir
rek1li
re7k2o
4reko2b3
4re1kom
4rek6on
6re1k2os
4re3k2ra
r2e1kr6
3rekru
r3ek1sa
6r1eksp2
rek4ter
re6k1t
rek1te
4re1ku
r8el.
r2e9la
4relag
9reland6sk.
rel4an8ds
rela6nd
9reland2s1ke
2r2e1le
rele8ge.
re3le1ge
r4e5lei
6relek
r4e7len
7rele1ne
6re2lg
r3elit
r2e1li
4re3l1j
r2e2l1l
rel4lag
rel6la6nd
rel5led
rel1le
rel4le1v
5r4e2l1m
rel5se6s3
re8l1s
rel1se
rel4sk
4re1lu
4re8lv
2r4e5ly
2r2e1læ1
2r2e1lø
4r2e7l6å
re2l1å7r
2rem
re5m4a4nn6
re1ma
r1e2m1b
remi6e1ne
re1mi
rem2ie
remi1en
remi6l
re7mis
rem9ji
remmed5
re2m1m4
rem1me
6re2m1n
rem8na
re8m5p4
re6m1s
rem9t2i9da
re2m1t
rem1ti
rem3tid
4re1mu
8re5my
4re1må
2r2e3na
ren5d4e5l
re6nd
ren1de
4r1endr4
8rened
re1ne
re5neg
re7nei
4re2nek
r3e4nel
4renest
6renet
6ren8g1d4
re6ng
reng5l
4reng7n
reng5st
ren8gs1
re5ni
2re2nk
ren6kl4
r3e4n1l
ren8ne8sl4
r2en1ne
re4nn
ren5ne1s9la
r2e5no
ren5sa
re6ns
r3en4s7s6
6r7en2tit
ren3ti
re6n1t
4r3en5tr4
6rentu
4r2e1nu
5ren1z
6r4e7næ1
4re1nø
re5og1
re3o6
2reo2p
re3o2r
5re2o1u
8repen
r2e1p
re1pe
6r5e6pi
1re1pu
6repus
1rer
6rerad
r2e1ra
6re9ra6ng
4rer6at
re5re.
re1re
4rered
4rere1f
4rereg
4rerei
re4rek
4rere6n1t
4rer2e1p
4rer2e3se
4re1re1su
4rere4t
6rer1fa
re2r1f
4rer2ig
re1ri
4rer2ik
4r3er1næ1
r2e2rn
4re1rol
r2ero
4re1rom
re3r2os
rero6se.
rero1se
re5r2ot
3re4rs
r6er1s4p2
4re3ru1t8e
r2eru
re6r7øy
r2e1rø
2r2e1rå
1r2es.
2re1sa
re2s3c
r2e3se
4res2el
re4sem
4reset
resi7e1re
re1si
res2ie
4resin
2re1sj
2resk
re2s5ke
re6s7kje.
re1skj2
reskj2e
re2s6k2l4
res7k2o1d
re6sky
6re1sl4
re4slu
1res1m
re5s1mo
re3sov
r4e1so
re9s1pe
re1s2p2
4re1spi
4respr6
2re4s1s
res4sal
res4sek
res1se
res4s2it
res1si
res4sj
res6sk
res7s8o6r1t
res4sp2
res4s2t
res4sy
re8s9t8a1ne
5re4s3tau
res6t5e2r7v
res1te
re2s4t4e3s4
res3té
4re3s1ti
res4t4il
re3str4
4restre
7re2s2t1v
4re5sty
4r8e1stå
1re1su
6resu2k
4resun
re3svi
resv2
2r2e3sy
2re1sø
4ret4a1ki
re1ta
re3tak
4re3tal
re4t4ap
4ret2ea
r2e1te
8retek.
6rete1ke
4ret4e2k1n2
6retel
6re5tem
re5ten
4r2e5ti
4r2e1tj
4ret2oa
r2e1to
2r2e5t2r4
ret1s4i
re4ts1
ret4st4
ret7ted
re6tt
ret1te
ret5ter
rett8o
rett6set
ret6ts
rett1se
4r2e5tu
2r2e1ty
2re1tø
5retøya.
ret4øy
retøy5a
7retøye4ts1
retø4y1e
retøye2t
4re7tå
2re1u
re2u6r
4revak
re1v
re5van
reva5re.
reva1re
6r6eveg
4revei
4revel
re6v7e4n1h2
re1ven
re5ver
rev4e5s
r3e4v2ig
re4v5i4nn
re7vom
1re2vy1
re4v5åp
3rew
2re1å6
2r1f
rfa5re
r1fa
r3far
rfat5
rfe8en.
r1fe
rfe3e2
rfee4n
rfe8er
rfe4et.
rfe3e6t
rfei5li
rf4eil
r4f2ik
r1fi
r1flå3
rf2l2
rf6e5m6ø
r2ft2
rf2u8se
r1fu
rfyr4
r1fy
r9fæ1
rfø8r2arar
r1fø
rf8ør
rfø1r6a
rfø5rar
rføra1ra
2r1g
r5ga.
r1ga
rga8le.
rg2a1le
rga8li
rg5a6nd
r7ga4nn
r4g5a6n1v
rg2a3r2i
rg3a6r1t
rga4ve.
rg2a1ve
r6ge1di
r1ge
rged4
r4ge1f
r2gem
rge6n1t4
rg2en
r3ge3o6
r4gerei
rg2er
rge1re
r4gere4t
r4g2e3ru
r4ge1sj
rg6es
r4ge1sl4
r4gesta
r2ge1st
rgi1a
r1gi
r3gi1e4n
rg2i1e
r2g2ik
r2gil
r2gim
r2g4io
r2g2ir
rg3i4ri
rgi7s1l4
r2gi1ø
rg2l6e
rg5le.
rglem5
r4g3len
r4g3ler
r2g1n
r4g2og
r1go
r3gom
r2g2ot
r4g5rab
r1gr4
r2g3r2ea
r2g5rel
rg5re2p4s3
rgr2e1p
rg5rin
r3gru
rg5sc
r8gs1
rgs6kor
rg5s4le
rgsl4
rg1s1n
rg5s6ti2l1l
rgs1ti
rgst4il
rg5sto
rg9stu
rg2sy
rg2u7d
r1gu
rg6ut
rgå9as
r1gå
rgå1a
rgå6v4a
rgå1v
rgå6ve.
2r1h
rhav2
rh2a
rha8vs3
rhju8l8s
rhju6l7
rh2o3d
rh4o
r7hu
rhø5re
rhån8d6s9
rh6å
rhå6nd
rhå9ne
ri1an
4ria4n1f
4ria4n1l
6ri1an1sv2
ri1a6ns
ri1ar
4ri1a2r1b8
4ria2r1r
6riau
2ri1av
ri4a1va
3ri6a1vo
2ri1b2
ri2b3l2
ri8ca.
ri1c4a
ri4co.
ri1co
r2i5da
4rid2a1le
ri5dal
ri5d2er
ri1de
ri5di
2r1idr4
ri4d3t1
4r2i1e2i7e
r2ie
rie1i
4ri3e2ig
4ri1eks
ri1el
6rie6ld
4ri5e2lem
ri2e1le
6ri7e6lim
ri2e1li
riel4la
rie2l1l
ri1e4n
ri3e6nd
4rie4n1h2
4rie6n1t
rien5t4r4
ri1er
ri2e5ra
ri2e5ri
4ri3e2tat
ri1et
ri2e1ta
ri5e1ven
rie1v
2ri1fa
r2if
rifer2i9e8
ri1fe
rife1ri
rif4fi
ri2ff
rifiser1b4a8
ri1fi
rifi1se
rifis4e2r1b8
6ri1fj
1rif2l2
rif5la
2ri1fo
2ri1fu
4ri3fø4
ri4ga.
r2ig
ri1ga
rig4gr4
ri4g1g
ri3gi
4ri1gj
4rig2re
ri1gr4
ri8g2s1
rig6s7t4
2ri3h
2ri1i
2ri1j
5rij.
ri4ka1li
r2ik
ri1k2a
ri5kan
5ri2k1d
ri3ke1s2
ri1ke
ri7ki
rik7ken
ri2kk
rik1ke
rikk5j2
4rik1l4
ri8k9la
2ri7ko
6rik6on
2ri1k2r6
rik4sk
rik4s5u
ri6k1t6
rik4ts3
riku6m
ri1ku
ri3k4v2
4ri3kå
2ri1la
6r5il1de
ri6ld
6riled
ri1le
ri5l1ei
ril6lest
ri2l1l
rill6es
ril1le
2ri5lo
ril4s1n
ri8l1s
2ri1lø
4r4i1ma
ri9mab
ri9mar
6rimes
ri1me
ri5m8et
2ri1mi
ri4mi.
7rimis
ri4m7l
4ri2m5m4
4ri1mo
ri4mor
4r1i8m1p2
4r4i1mu
ri2m9ut
4ri1my
rina5l
r6i1na
ri5n6a2m
4rinas
4r5in1c
4ri6nd
r3in1du
ri4ne3e2
r4i1ne
ri4nes
2r1i4n1f
rin8g7o8m
ri6ng
rin1go
rin2g3r4
ring4sa4
rin8g2s1
rings5ak
ring8s1pa
ringsp2
2r3i4n1j
2r2i2nk
4ri4nn
rin9nes
rin1ne
4rinor
r2i1no
2ri6n1s
r4ins6k
rin6s1m
2ri6n1t
rin4t5j
rin4t2r4
2r1i6n1v
r6i6nø
2ri1of
r4io
6ri3om
2ri1op
2rior
ri2o5s4
rio1t3r4
ri2ot
2rip
4ri1pe
rip2o4s3
ri1po
4r2i1r
4risau
ri1sa
4ri2sed
ri1se
ri2se5i
6ri2sek
4ris2el
ri4s4e5li
4ris1h
5risi1ko3
ri1si
ri3s2ik
2ri1sj
ri6sju
4ri1ska
ri4ski
6ris1ku
4ri1sky
6ri3s6t2ad
4ri5s2ted
ri2s1te
ris5t2ik
ris1ti
4rist4il
ri4sto
ri1s5tof
ri5stun
6ri1stø
ri6stå
4ris1v2
4ri1sy
4ri1sø
ri3te
6ri2te3o6
ri7t2i
rit2i9da
ri3tid
4rit4il
4ritj
ri5t6o
ri5tr4
ri4t1s6
ritt8s7t4
ri6tt
rit6ts
ri6tun
ri1tu
4rity
2riu2n
ri1u
ri2u4r
2riut
4rivar
r4i1va
ri6ve2d1
ri1ve
rive9ge
ri2veg
ri5vei
4rive2r1k
2ri1vi
ri4vi4s
riv5i1se
6ri1vo
4ri1ø4k
ri1ø
riø4r3a
8riøya
ri1øy
4ri1å4
r1j
r6j7am1b4i
rja2m7b
r4je3r6e
rj2e
r2jes
r4j2e1ti
r4j2e1tr4
r4j2e1tu
rju6la
rju2l
2r1k
r4k3a6k1t
r6ka4n1f
r4ka1o
r4kapr6
r6ka1t4r4
r3ke.
r1ke
r2ke5h
rk5e2ik
r2kei
rkei8k2a
r6kek
r4k2e1lo
r4ke3lu
r3ken.
r4kena2v
rk2e1na
r3ke3ne
r4ke4ni
r5ke6n1s2
r3ke5ri
r4ke4ris
r4k2ero
r5ke4rs
r4k2e3ru
r4k2e1rø
rk2e4se
rkeslø7se
rkesl4
rke2sløs
rkes1lø
r9ket.
rk4han
r4k1h
rkh2a
r3ki
rki3d
rk4i3e2
rki4vi
rk6iv
rkjek8
rkj2
rkj2e
r6k5jor
rkj2o
r2k1k2
r5k8led
rkl4
rk1le
rk2li
r4k5lun
rk9lut
rklæ5re
rk2læ1
rk2lø
rklå9ra
rk2lår
r3k2nek
r2k1n2
rk1ne
r5k2n2e1p
rk7nes
r3k1no
r2ko2b3
r4k2o6b5r8
r6k2o1fo
r2k2o1h6
r4k4o1la
r4k2o1li
r4ko1pe
r4ko1ra
r4ko5r1u
r4kos2el
r1k2os
rko1se
r5ko6se1le
r4ko1sj
r6k7ras
rkr6
r4k5rei
r5k6rem
r8ks
rk4sar
rk1sa
r6k6seg
rk1se
rk2s1i
rk4ska
rk1st
rk6s3tal
rk4sten
rks1te
rk4s5ti
rk4s1tj
rk4sto
rk6s5vi
rks1v2
rk5ti
r6k1t
rkti4s
rk5to
rku4le.
rku1le
r6k7u6t
r4k5ve6d2
rkv2
rk9vei
r5k4vel
r4kver
r2k3v4es
rk5v2ik
r4k5øl
r2k3øy
rk9ø2y1s
rkå4k
r4k5å1ke
rkå6pa
rk5å4s2
2r1l
rla4te.
rla1te
r2l2e4a
r1le
r2le1f
r3l2e1p
r4l5e4ri
r6le7sl4
rle4st
r4le1su
r4le1u
r3l4i
rli9ke
rl2ik
rlin8g3s4
rli6ng
rli8ta
rli4te.
rli1te
r3lj
rl6o
r1l2og2
rlo5ve
r1lov
rl4sk
r8l1s
rlu4e
r3ly
rlys7k4
rl2ys
r6l5z
rlø8pa.
r1lø
rlø1pa
r5løy9
2r1m
rma6ge.
r1ma
rma1ge
r4m2a5k4l4
r4m5al1te
rma2l1t
r2m3a4n1l
rm4a6ns
r6ma6n1v
r4ma1re
r4ma2r1r
r2m5av
r2m3b
r2me7g
r1me
r2mek
rme6lap
rm2el
r4m2e1la
rme5ne
r4menet
r6mere1v
rme1re
r4m2e1se
rme9tar
rme1ta
r4mey1
r4m5i1de
r1mi
rmi6e1ne
rm2ie
rmi1en
rmi1ni6
rmin5s4ki
rmi6n2s
rm4insk
r6m5ins4t
r2m5i4v
r2m1j2e
rmlø8pa
r4m3l
rm1lø
r3m6o4e
r1mo
r2mof
r2m1op
rmo7st
rm2os
r8m7p
rm5s6ko
r6ms
rm1sl4
rm1s6n
rm1st
rms5t4il
rms2ti
rm1su
rmta8la
r2m1t
rm3ta
rm3te
rmu7an
r1mu
rmu6a
rmu2e4
rmu7e5ne
rmu1e2n
rmu8la.
rmu5la
rmu6le.
rmu1le
rm5øy.
r1mø
r2møy
r9må.
r1må
rm6ål4
r6m5åp
r9mår
r6m7å1ta
2rn
r3na
r4n1ak
r4n3a6ld
r6n5ap3par
rna2pp
rnap1pa
r4n1a2r1b8
r4n3a6r1t
rnat7r4
r4na2t5v
r6n9a6vis
rna1vi
r4n3a6v1l2
r6n3d
rn1dø4
rnd2ør5
r3ne.
r1ne
r2n2e5a2
r5neb6o1e
r2n2e1b2
rneb2o
r2n2ec
r4ne1f
r2nel
rnele6ge.
rne1le
rne3le1ge
r3ne2l1l
r3ne8l7s
r4n3e6ng
r3nen
r4ne4n1h2
r4nerei
rne1re
r6neris
rne1ri
rn2e3ro
rne1s2
r5n2es.
rne6se.
r4n2e1se
r8ne3si
r4ne3sk
r4nes1m
r2n4e3so
rn5e4tab
r2ne1ta
r4n2e3te
r2ne1v
rne5v1r8
r2né1s
r1né
r6n5g6
r4n1i4nn
r1ni
r2n5k4
r4n3n
r5no.
r1no
r4n2oa
r2no5b
r4n2o1d
r4noi
r6nok
r2nom
rn5o2m1n
rn3o6m7s2
r4n1op
r4n3o2r1k
r2n2os
r4n1o4v
rn7se
r6ns
rn4s3in
rn1si
rn5s1ke
rn3skr6
rn5sla
rnsl4
rns3le7ge
rns1le
rn7s6mi
rns1m
rn6s3o2v1n
rn2s1ov
rn5sp2on
rnsp2
rns1p6o
rn3s4pr6
rn1st
rn4s1ti
rn3te
r6n1t
rn5ti
rn7tr4
rntre4
rn2t4v
r1nu4
r2n5ug
r6n3ut
r7n4øt
r1nø
r4n5øv
rnå8le.
r1nål
rnå1le
rn3å2s2
ro1a4k
r2oa
roa4s
ro5a1si
9roban
r2o3b4a
9robar
1ro1b4e
ro4bed
ro4b4e1f
5roben
ro4b4e1s2
5robøl1gj
ro5b4ø
robø2lg
1r4oc
r3od8ds
r2o1d
ro6d1d
7ro1do
ro5e6nd
r6o1e
roe2n
ro7e6ns4
r2o7fa
ro4fel
ro1fe
ro4fem
roff5ri
rof6fr2
ro2ff
ro7ga.
r2og
ro1ga
rog4a9ta1
rog2at
ro7ge.
ro1ge
rog5ret
ro1gr4
ro6gry
ro8g1s4
ro2gu
1roi
ro4kel
ro1ke
ro5ki
rok6kat
ro2kk
rok1ka
rok6ke1ri
r4ok1ke
rok5kl4
rok4kom
r4o7k6l4
r2o2k5n2
rok7s
rok8se.
rok1s4e
rok5v2
roli7ga
r2o1li
ro3l2ig
4r3o6lj
rol4la1b
ro2l1l
rol4lap
rol4leg
rol1le
rolle8ge.
rol3le1ge
rolle8se.
rol4l6es
roll2e1se
rol4lis
rol1li
rol6ly
ro1lo9v
r2o1lo
2ro1ly
ro6mak
r2o1ma
ro4mal
3ro5m8an
ro4ma3te
rom6at
2ro2m7b6
4r2o3me
rome5d
4ro2m3k2
4r3o2m1n
ro4mor
r2o1mo
2r1o2m1r6
rom5sla
ro6ms
romsl4
r7om1sy
rom1s4ø3
ro2m3t2
ro5ne.
r2on
ro1ne
ro7nim
ro1ni
6ronis1m
6ronista
8roni2s1te
6ronis1ti
4ro4nn
ron4na
ron2o5s
ro1no
ro6n1s4
ronta6le.
ront6a
ro6n1t
ron3t2a1le
ro4pad
r2o1pa
5r2opet
ro1pe
ro4pia
ro1pi
ro8p1la.
r2opl6
ro1pla
2ro2pp
ro9py
ror3a
ror6da.
ro4rd
ror1da
r8o3re
ro1r1u
ro4sat
r2os
ro1sa
ro5s2el
ro1se
ro3s2en
4ros2l4
ros3la
ro4s1m
ro6sov
ro3so
ro1s1p2
ros4s2t
ro4s1s
ros4sy
ro1s7tas
ro3s1ti
ro3str4
ro1s2t7rø
ro1t5ek1te
r2ot
ro1te
rote6k1t
ro5t4es
rote7s6ter
rotes1te
roti7k2a3
rot2i2k
ro1ti
ro4t5o4r5d
ro1to
ro5tu
6ro1ty
roun2
rou6nd3
ro5ut
ro5va.
ro9va9re
ro7vas
ro5vek
r4o7ven
rove5re6
rove2r
rov5s1m
ro8vs
rovve6
ro2v1v
rò6te.
rò1te
2r1p
r5pa
r6p1a2r1b8
r5pe1fo
r1pe
r2pe1f
r4p2e1no
r6pe1nø
r5pesk
r2pe1s
r5pet
rp6j
rp2l6
r2p3lad
rprø5ve
rpr6
r5prøv
r3pu
rpu6n7g
r6p5ut.
r6p5øy
r2på1k2
2r1r
r8raa
r2rag
rra3r
r4raro
r4r3d
rr6e
r4r2e1b2
r7rebart.
rre1b4a
rreba6r1t
r2re1f
rre7i6n1t
r4re1kl4
r5relat
rr2e9la
r4re3o6
r4rep2l6
rr2e1p
r4r2e3ru
r1rer
r2re5sk
r4res1m
r4r4e1so
r4re9s1pe
rre1s2p2
r3re4s1s
rre4st
rres5ta
r4re7s1ti
rre5str4
rre4t6s5
r2re5u
r3ri
rri6ka.
rr2ik
rri1k2a
r6rip
rri5v
r2r3m4
rrmå8la
rrm6ål4
rr1må
rr6o6e
r5rom
rro8sa
rr2os
rro8se.
rro1se
rro8ta
rr2ot
r4r5s2
r2r3un
r2r5v
rrå5de
rr6å1d
4rs
6rs.
r1sa
rs3ab
r2s7ad
r3sak
rsa5ka
r6s1a6k1t
r7s8a6la.
rsa1la
r8s9a6ld
rs3a2l1l
r5s2am
r4s1a2r1r
r4s3a2r1v
r1sc
5s6c2h6l
r8se1di
r1se
rse6g7
r4se1ku
r2s4e1la
rs2el
rs7e6l1d
r4s2e1li
r4s3e8lv
r4s5e4r2ik
rs2e1ri
r3ses
r4se1si
r6se1su
rse4te.
rs2e1te
rs6e6tt
rsett8o
r7sim6
r1si
r2si8m1p7
rs1in
r5s4i6ng
r4si6n1s
rsis5t
rs2is
r7sja
r1sj
r4sj1h
r2sj3or
rsj2o
r3s2kad
r1ska
r6s7kaf
r2s4kam
r3skap
r4s1kar.
r2sk2ar
r4s1kas
r4ski
r5skil
r5sk8in6n1s5
rs2ki4nn
r1skj2
rs5kje6ns
rskj2e
r6s1k2l4
rskla8g
r6skla
rsk5lar
rs4k5le
rs4k3læ1
r5sko.
r5sk6o3e
rsk3op
r4skor
r3s2k2ot
r1skr6
r4s3k8ra
r5s6kriv
r4s3k2ro
r1sku
r5sku.
r5s4kue
rsku7et
rsk5u6nd
r2s1kun
rsk5var
r2skv2
r4s5k2ys
r1sky
rsk5ø
rs4le1f
rsl4
rs1le
r6slei
rs4lek
r8s5les
r5s6lit
rs1li
rs3lok
r4slun
r1slu
rs4m2a1ke
rs1m
rs1ma
rs4mo.
rs1mo
rsm6ål4
rs1må
rs6ne1v
rs1n
rs1ne
r1so
rs2o9a
r4s5o4m3l
r3s2o6n5
rso7n6al
rso1na
r7s6o5n8e
rso5n6i
rso6ns4
r2s1or
r4s5o4rd
r7s6o7ris
rso2ri
r2s1ov
r1sp2
rs4pan
rs1pa
rs6pa1t
r5s2p6el
rs1pe
r4sper
r7s2pe1s
r5spi
rs4por
rs1p6o
r5spred
rspr6
r4spå
r4s3s6
r1s2t
r4s5ta2b1b
rs5ta2nk
r2s3tap
r6s6t7b2
rs4ted
rs1te
rs4tem
rs5te6nd
rste6n6s
rs5t8er.
rstev9na
r5ste2v1n
rs2te1v
r3st4il
rs1ti
r4s5ti4l1f
r4s5ti8l1h
r6s5ti8l1s
r4s5ti8l5v6
r5stis
r4s2tit
r6s4t5k2
rst4r4
rs9t1re.
rs7t2re3e2
r4st3rin
r4s5tro
r5s2trø
r2s4t7ut
rstu9va
rstu1v
rstyg7
r1sty
r3st6ø
r7stå
rs5ukl4
rsu2k
rsu9r
rs4u7sa
r1sus
r4s5u1si
r1sv2
rs8vak
rsva9ra
4rsv2a4r4s5
rs1ve
r3s4vek
rs5vit
rsy4na
r1sy
rs2yn
r2sy3t
r1s4z
rsøks3
r1sø
r8s9ø4y1e
6r1t
r4t5ad
r4t5af
rt4a4ka.
rta1ka
r4ta2na
r2t3a4n1l
rta9pa
r4ta2r1r
6r4t3a6r1t
rt3a8vs
r2tav
r4t2ec
r1te
r4te1da
r2ted
r3te1de
r2te3e4
r4t4e1go
r4te1g2r4
r4t3e2i7e
r4te3in
r4t4e1ka
rte6ke.
rte1ke
r4te1ki
r4te1ku
r4t5e4lit
rt2e1li
rte6ma.
r2tem
rte1ma
r3te8m1p
rten4s5k
rte6ns
rtent1le8
rte6n1t
rten6t5l6
r2te3o6
r7t6er
r5t6e4r5d
r5teres
rte1re
r4teris
rte1ri
r5te2r1k
r4t2e1rå
r4t2e1se
rt4es
r6te1sk
r6tes1ti
r4te1ta
r4t2e3te
r4te1v
r4t1h
rtia6n8d
r1ti
r4ti1a6ns
r4tiar
rti8ar.
rti5en
rt2i1e2
rti6g2ra
rt2ig
rti1gr4
r4ti3k1v2
rt2ik
r4ti1la
rt4il
r4ti1li
r4tilo
r4t6i7na
r2t4io
r2tip
rti7sa
r6ti1s4ka
r4ti1ski
r4t6i9so
r4tis1p2
rti4s3s
r4ti5str4
r3ti1tu
r2tit
r2tiv
rtma6le.
r2t1m
rt1ma
rtm2a1le
r5to.
rt3o2ff
r2tof
r9t2o1fo
r9tok.
rt6ok
r4t3o2m3k2
r4t3o2pp
rt6opp.
r4t3o4rd
r6t7o6s
r2t1o4v
rt2r4
rt8ra
r9t1re.
rt6red
r6t3reg
r4t3rei
r4t5re7k2o
rt5rel
rt5r2e1p
r7t6ri1b2
r4t3ris
r4t5r2os
r2t3rut
r5t6rål
rts3ar
r4ts
rt4seg
rt1se
rts5e6ng
rt2si
rt4s5ja
rt1sj
rt5s1ke
rt3skj2
rt5s4no
rts1n
rt3s4pe
rtsp2
rt4s1ti
rt1st4
rt7s6trek
rtstr4
rt4s5t8øy
rt1stø
rts5un1de
rtsu6nd
r6t3t4
rtu6en.
rtu1e2n
r7tug
r4t3u6nd
r2t1ut
rtu8ve.
rtu1v
rty8da.
r1tyd
rty1da
rty8de.
rty1de
rty4r5s
rty6ra
r2t5y2t
rtæ9ra
r1tæ
r4t5øl
r6t5å3s3
ru3a6nd
6ru1av
r1u6a2v1h
ru8bl2
ru5b2o
ru4di.
r2ud
ru1di
rud4r4
ruds4l4
ru8ds
ru4e1le
ru1el
ru1e4r
rue3s4
ruga8l
ru1ga
rug2a5t
rui3d6
4ruk
ruk4su
r2uks
ruk4t3s
ru6k1t
ru9la
4ru3l4i
6r7u6l1k
rul8ke.
rul1k4e
r2u2l1l
r7u8lv
r4um
rum3al
r2u1ma
rum4p9l6
r2u8m1p
5r2un1de
ru6nd
run5d4e5l
6r3und2er
7r4under.
r5unde4rs
rund3r4
r4un8d3s4
run6ge.
ru6ng
run1ge
4ru2n6i
run5kr6
r4u2nk
r7uly
ru4nøy1
ru3nø
ru2r
ru5ra
ru8ran
ru8rar
ru9rer
ru1re
rur8ta
ru6r1t
r4us
ru2s2h3
6r7u6s2ik
ru1si
ru1s5j
ru4s7lu
rus1l4
ru1s4o
rus5s4el
ru4s1s
rus1se
rus4s2t
ru4s4t3r4
r4uta
ru3tal
rut8a8la.
r7ut6an.
r4ut1be
r1u6t1b2
4r1u2t3d
ru1t8e
ru4te3i
ru4tel
ru9tene.
rut2en
rute1ne
r2u9ter
2r1u2t3g2
r1u4t3k2
r2ut9o
6rut1r4
rut4re
ru6t4rø
rutto5
ru6tt
2r1u2t1v
ru5va
ru1v
ru4ve2d1
ru4veg
ru4vei
ru4vel
ru4ve1re
ru4v4e3s
ru5vi8
ru6v7is
2r1v
rv4a
rva7ka
rva6la
rve3de
rve2d1
rve4den
r4ve5dr4
r4v2e1ga
r4ve1gi
r4ve1g2r4
r4v4eim
rve4i1s7e6
rv2eis
r2ve5kl4
r4v2e1la
rvel9le
rve2l1l
r4v5e6ng
r1ven
r5v8er.
rve5re
r2v4es
r2ve1v
rvi2l9l
rvil4le9d
rvil1le
rv2j
r4v2os
r4vo1v
r3vr8
r8v2s1
r2v5u6ng
r1vu
rvå7r
4r1w
rx1
ry1a4
ry2dr4
ry7fe
ry1f
ry5f2l2
ry5ke.
r6yk
ry2ke
2ry1kl4
ry7le
ryl4l5i8s
ry2l1l
ryl1li
4r5yn4d1l4
ry6nd
ry4ne5s4
ry1ne
ry5n2es.
ry4pa.
ry1pa
2ryr
ry8re
r1y2r1k
ryr4ke.
ryr1ke
ry9ro
ry5rø
rys6sal
r2ys
ry4s1s
ry5ta
ry4tek
ry1te
1ry2t1m
r3ytr4
r4z
ræ7le
8r3æ4re
8r3æ4ren
rær5in
ræ1ri
ræ6r8t
ræ8v
2rø.
rø8ar
r6ø1a2
6rø1b
rø4be.
rø1be
rø4dek
rø1de
rø8d1s
4rø5e1p
r4ø1e2
røf5l2
rø4ke.
rø1ke
rø4k2ero
røk3l4
4rø2k1n2
røk5s4
rø6k7t
røk1v2
2røl
rø6m
rø1m1a
røn5nes
r4ø4nn
røn1ne
rønn5s4a
røn6ns
rønn5sk
røn5sko
rø6n2s
5r6øn2t9g2
rø6n1t1
rø4pe.
rø1pe
røp9l6
1rør
rø4r5d6
r6øren3de.
rø1re
røre6nd
røren1de
rø7r6et
5rø2r1l
rø2r3o
rør4sp2
rø4rs
r8øs.
rø3se
rø5sla
røs1l4
røs5v2
rø8ta
r4øt
røt9a4s
rø1va
rø5ve1de
røve2d1
rø9ve4rs
rø1vi
r4ø5væ
2r1øy.
4røya
røy9ar
røy6ed
rø4y1e
røy7e6ne
røye4n
røy5es2
røy4e2t
5r6ø6yk
3røyr
røy5re
røy8senes
rø2y1s
røy1s4e
røys2e1ne
6røy4s3k
røy4s2t
2rå.
rå7a
4råag
4rå1b4
3rådet
r6å1d
rå1de
rådy9ra
rå1dy
rå9e1ne
r4å1e
råe2n
2rå1f
4r2å5g4
2rå1kj2
råk3re
råkr6
rå2k3u8
råk1v2
4rål
rå2le7s8
rå1le
rå4let
rå5let.
rå5l1u
r5å6nd
rån6da
2råp
2r1år
rårs5k
rå4r2s
rå5ru
rå1s2
4rå8s9b4
2rå1se
rå5si
2rå1sj
r4å1s4t
6rå1ta
råt4a8ka.
råta1ka
rå5tr4
rå5t1u
2saa
5saa.
sa4ba.
sa1b4a
s6a1be
s8ab1l2
sa5bok
sab2o
s3ab2on
sa5by
sa3ce
s8ac
sa4dag
s6a1da
4sadam
sa4de1re
sa1de
sad2er
4s1a2d1m
sa4do
2s1a2d1v
sa4e5d
s2a1e2
3saen
7saer
1sa1fe
5s4a1ga
sa4ga.
sa4gas
sa4g2at
6s3a4ge6n1t
sa1ge
sag2en
6s5a4g1g
6s5a6gi
sag8na
sag1n
sa6go.
s4a1go
sa4g2og
2s1a2gr4
sa8g3s4
sa3ik
sa5ir
sa1is
5s6ak.
sa2ka
3s4a1ka.
4s1a2kad
sa5kai
3sa6k1b4
3s2a2k1d
3s4a3ke
5sa2k1f
1sa4k1h
sakh5e
1s4a1ki
s2a4kj2
sak5kr6
sa2kk
s4ak1ky6
5s2ak1l4
5sa2k3m
4s2a1ko
3sa6k1p
sa1k2r6
5s4aks.
sak4s3i
4s3ak1sj
sak4sp2
5s4ak8s9r
5s4ak1su
2s1a6k1t
sak6ta.
5s4akto
s4aku
4sa7kø
1s4al.
sa1la
4s5a4l2a2r1m
7s8a1la.
5s2a5lat
4sa2l1b2
1s2a3le
sa9let
1sa2lg
s4al1ge
4s5al1go
sal8g6s5
s6a1li
sal4mes
sa2l1m
sal1me
sa5lo
5s2a8l2s3
sals4a
4salter
sa2l1t
sal1te
sa1lu
3sa8lv
sal5ve2d1
2s6a1ly
1s2am
5sam.
sa2ma
sa5ma.
sa3m4an7
sa5mas
sa2m5ei
sa1me
sa4m2el
sa4met
5sa4m3l
sam4le1v
sam1le
5sa2m1m4
sammen5
sam1me
sa4my
4s1a2na
s6a9na.
s4a7nar
sa3nat
san6da.
sa6nd
san1da
san7d8al
5s6an3de.
san1de
sand5r4
sand5s6lo
s4an8ds
sands1l4
sand5st4
san4d5ø
1s2a5ne
4s3a6nek
5sang.
sa6ng
3san1g4e
4s3an1gr4
s7anken
sa2nk
san1ke
2s1a4n1l
s5a4n3m
san5ne
sa4nn
6san1no
sa2no
s5anor
san5os
sa6n1s
5s4ans.
4sansa
5s4ansen
san1se
san7s6k
4s5ans8l4
6s1an1sv2
s8ant.
sa6n1t
san9te
6santr4
4santy
4s1a6n1v
2s1ap
sa2po
1s2ar.
1sa1ra
6sarab
2s1a2r1b8
s4a4rd
9s8a1re.
sa1re
4sareal
s1a2r2ea
4sareg
sa5re1v
3s2a1ri
sa4ri.
sar6ka.
sa2r1k
4s3ar3ki
2s1a2r1m
sar5me
sar8me.
s1a2r1r
2s1a6r1t
sar4ta.
sa4ru
4sa2r1v
s4a1ry
1sas
8sasju
sa1sj
2s1a4sp2
4s1a4s1s
s6ast
4sa1sty
2sa1su
4sa1sy
1sat
s4a1ta1
s4a5ten
sa1te
s4a3ti
2s1a6t3l6
4s3at2m2os
sa2t1m
sat1mo
sa1to
4sa5t6r4
s5atsk
s2a4ts
5s4att.
sa6tt
4s3at1ta
6s5at3ten
sat1te
satt4e4s
5s4au.
sa2u4d
sau5di.
sau1di
5s4a6ue
4saug
sau6ga
s3au1ge
sau6ge.
2s1auk
5saum
3saus
4saut
2s1av
s2a1va
sa4ve.
s2a1ve
5s6a5v6in
s6a2v5n
3s2a1vu
8s9b4
sba4ne.
s1b4a
sba1ne
sb2i6e
s1b4i
sbo4da
sb2o
sb2o1d
sbu6et
s1b2u
s6bug
sbul3
sby8ta
s1by
4s1c4a
1sce
2s1cel
s4ce1ne
s3cer
6s2ch.
8schl.
6s7c2l
4sco.
6s1c4o4c
4sc2os
s4cus
s1cu
8s9d6
sda8g4s5
s1da
sda8m9p
sde6le.
s1de
sde1le
sdu8en.
s1du
sdu1e2n
sdu8er
sdø8v
s1dø
1se
2s2e1a
3sea.
sea4g
se3a6n5d
se1an
se7ansa
sea6ns
sea9re
se1ar
5se4au3sk
se1au
seau5s
2s2e1b2
4s2ec
4se5d4ag
se1da
se6d5d
se3de
5se3de.
5se4d1l4
4se1do
2se1dr4
2se1du
6sedvan5le
sed7va
se2d1v
sedva4n1l
2se1dø
5see.
se3e2
se2e3d
2see1f
2seeg
se6e3i
se3e4l
se5e4n
seer1
5sees
2see1v
2se1f
s1e2ff
4s1e8ft
6s2e1ga
sega6l
se2ge
se6g6es2
se3g2e1v
seg8ga.
se4g1g
seg1ga
9segl.
7se4glet
seg1l6e
3s6e2g1m
4s2e7g8r4
2se1h
2seid
sei8dan
se2i1da
se2i9den
sei1de
sei8e9nes
se2i7e
sei1e2n
seie1ne
s5ei4et
2se2ig
sei6ga.
sei3ga
sei5g6e
se2i5k
3s4e8il
6s4eim
2sein
se3i6n1d
s3e4i1ni
se6i2n3k4
se3i6n1s
se3i6n1t
s2e2i5r
3s2eis
2se1j
5sej.
2s4e5ka
3se1ke
4se1ki
2s4e1kj2
5s2e2kk
sek4kes
sek1ke
2se1k4l4
4s4e3k1n2
2se1k2o
s2e2k1r6
4se3k2ra
4se3k2ri
4se3k2ro
3s4ek1sj
4s1eksp2
sek4st
sek2t3an
se6k1t
sek6te.
sek1te
sekt2e9ra
3s4ek5to
4se5ky
4se1kå
s2el
2s2e1la
se6la.
3se2l1ak
5sel6a3ne
5selar
se4l5a6r1t
s3e4las
se6l5at
se6l1d
se4le.
se1le
4s4e5led
6sel2e1ga
4selei
4s3e2lem
4sele6ng
s4elen
4seles
4s3e4le1v
5s6e2lg
4sel2ik
s2e1li
4selil
4selis
4s3e4lit
sel6løp
se2l1l
sel1lø
2s2e1lo
7selol
se3lom
3se8l1s
sel4sin
sel3si
8s5el6s5ke
sel9s8lag
sel2s1l4
sel4s1p6o
selsp2
se2l5t6
2selu
se6l7u6r
sel4v5a6k
se8lv
sel1va
sel4v3an
selv3e4
sel4ve.
sel4vei4
sel4ver
sel8vin
sel1vi
2s4e1ly
2s2e1læ1
2s2e1lø
selø8pa.
selø1pa
6s2e1lå
2se3ma
3se2m1b
7s6e2m1d2
s8e5me
se4mi.
se1mi
semi5ni6
s4emin
2s1e2m1n
sem4na
9s8em6nd
2se1mo
sem5pe
se8m1p
2se1må
s2en.
6senau
s2e1na
sen9d8a
se6nd
sen6d2e1la
sen5d4e5l
sen1de
6senden
4s1endr4
s2e1ne
4sened
se3neg
4s4enem
8se2ne1sa
6se4n2e1se
sene8se.
s5engas
sen1g6a
se6ng
2s1e4n1h2
se6nin
se1ni
s3en5kj2
se2nk
5se4n3n
s2e6ns
4s5en2sem
1sen1se
sen6s5e6nd
sen4sj
sen3so
7s6e6n1t
sen5t4er
sen1te
8s7en5tr2e1p
sen4tre
sentr4
4senum
s2e1nu
4se1ny
2s4e7næ1
6se1nø
2se3o6
7se2o5d
se3or
2s2e1p
se3pe
seper1so5
sepe4rs
3se2p1t
s2er.
s2e3ra
4seram
5seran
4serap
5seras
6se2rau
se4r5d
s2e5r4e
5se1re.
4ser2ea
4sered
4sere1f
4sereg
4serei
4serek
4serel
4sere4nn
4sere6n1t
4ser2e1p
4ser4e1so
4se2r3e4s1s
4serest
4se1re1su
4sere4t
4sere1v
s1e2r1f
s2e1ri
seri6e5ne
seri1e4n
ser2ie
4ser2ik
4serkj2e
se2r1k
serkj2
5s4erkr6
5s2e2rn
ser7ne1v
ser1ne
2s2e1ro
se4r1op
se4r1o2r
se4r2os
9s6e6r1t
ser4tak
s2erta
ser6tat
ser4t2r4
s2e1ru
4serul
se4r3un
ser4ve2d1
se2r1v
ser4vel
2se1ry4
2s2e5r6ø
2s2e1rå
5s2es.
ses5a6ld
se1sa
ses4al
5se5s2a8l2s3
ses5a2l1t
4sesc
2s2e1se
se4s2e1ne
se3s2en
ses5in
se1si
se3sj
4sesj2e
4ses4ju2k
ses5kal
se1ska
se2s5k2ar
se2s5kv2
ses5lit
sesl4
ses1li
se3s1na
ses1n
5s4e1so
ses3pr6
se1s2p2
ses4s5in
se4s1s
ses1si
se1st
5se6st.
5se6s5te.
ses1te
4sesto
ses5un
se1su
ses1v2
2s2e3sy
4se1sø
ses3å
3s2et.
2se2t6a
3s4eta.
se5t8a1e2
seta8ka
se3tak
se5tar
6set2ea
s2e1te
4seteg
4setei
4setek
se7tel
se4t2e1ra
se5t2ero
4set4es
2s2e3ti
se8ti.
s3e4t2ik
s3e4tis
4setj2e
s2e1tj
5se2t1je.
7s6e6t3l6
5s2e8t1n
2s2e1to
2s2e1t6r4
s4e4ts1
s5ette4rs
se6tt
set1te
2s2e1tu
2s2e3t2v
2s2e1ty
6se1tø
3seum2
se1u
4se3u2n
seure9ne
se2ur
seu1re4
seu2t
2se1v
seva6ne.
sev4a1ne
s8e5var
se6v4d
sevi4sa
s1e2v1n
sev4ne.
sev1ne
se3vr8
3sev2åg
2seyn
sey1
2se1ø4
2se1å6
1sé
2sé1a
6sé1b
4sé5e8
4sé1f
4séj
4sé1k
2sé1l
4sé5o
6sé1p
9sé1r
4sé1s
2sé1v
2s1f
sfa4ne.
s1fa
s5fa1ne
6s1fe
sfe6et.
sfe3e2
sfe3e6t
sfes5
sfisken8
s1fi
sfi2sk
sfi8s1ke
s1flå3
sf2l2
s5fo
sfo8r1a
sfor1lø9
sf6o2r1l
sfra5s
sfr2
s1fra
sfri5e6re
sfri1er
sfr2ie
sfy4rs5
s1fy
sfyr2
3s2fæ1
sfø9ren
s1fø
sf8ør
sfø1re
sfø5rer
sfø5ri
6s9g6
sga4l
s1ga
sga8va
sga4ve.
sg2a1ve
sge6n9s
s1ge
sg2en
s2ge4st
sg6es
sgå4v4a
s1gå
sgå1v
sgå4ve.
s1h
4sh.
sha2k
sh2a
s7hat
s3h4au
6she
sh2e2a4
s5he2i5
7sh6e4r2if
she1ri
s4hi.
s6hip
s4h5isk
shi2s1
4shj
6s7ho2pp
sh4o
3s2h2o6r1t
3s2how1
6s6h1s
2shu
4s5h6y
s5hø
shø8l
shø6va
shø6ve.
shø1ve
s7h6å
1si
sia8l5v6
4sia5m
si7a6ns
4siap
4si1av
si2bl2
si1b2
3s2i2da
3s2i4de.
si1de
3s2i3den
si4de3o6
s4id2er
si5der.
si4d2e3te
2si2do
4s1idr4
sid8ra
4si2dy
4sieg
s2ie
si1el
si1e4n
si5er.
si6eren
sie1re
si2e4s
si3est
6si1fe
s2if
si3f2l2
2si1fo
si4f3r2
2si1fu
4si3fø
3s2ig
si5ge4r4s
si1ge
sig2er
4si1gi
4s3iglo
sig1l
si5gr4
4si3h
3s2ik
si3k2a
si5ke.
si1ke
si4k2h7
sik4ka
si2kk
sikk8artet
sik4k7a6r1t
sikkar3te
sik4k5el
sik1ke
sik4ko
si1ko3
si4kom
si4kop
si4k2os
si4k2ot
sik4t4s3
si6k1t
s2il
5sil.
3si6ld
sil4del
sil1de
sil4d2er
sil4de1s
si2l5j
si6l2k
sil4l6es
si2l1l
sil1le
2si1lø
si6m2el
si1me
4sim8et
2si8m1p2
s4i3mu
4sin1de
si6nd
4s3in1du
si3nek
s4i1ne
2s1i4n1f
sing4s5a4
sin8g2s1
si6ng
7s6in1gu
si4ni
4si4n1j
2si4nn
3s4inn.
7s4in1na
s6inne.
sin1ne
5s4innet
s3in4n1h2
s5in2n7k2
s3in4n1l
4s1in6n7t4
si5nob
s2i1no
sin2s1k5e
si6n1s
s4insk
2s1i6n1t
4s1i6n1v
s6i6nø
4si5ov
s4io
si4pa.
si1pa
si8pe.
si1pe
si6re.
s2ir
si1re
si7ren
si4ri.
si1ri
sir8kl4
si2r1k
s2is
si5s2el
si1se
si4s1e2r1f
si2s5e4v
si6sin
si1si
6sisju
si1sj
si4sk
si8s5ke
si4s1n
si4s5te
si4s1ti
sis3to
4si1sy
3s2it
si5ta
si2t8ji
si6t7ra
si1tr4
si4t5re
si4tri
si4t3s4
sitsva9
sits1v2
sit6te2r1m
si6tt
sit1te
sit4t4e5s4
si4u2m1f
si1u
4siut
5s4i1va
si9van
si6vek
si1ve
si8vi.
si1vi
si9våt
1sj
2sj.
s2ja.
8s5ja9g
4sjam
s4jan
4sj5a4n1l
s7j2a2r2n
2sj3av
6s7jaz
2sj1b
6s2jd
5s4je.
sj2e
sj4e4f3i
s2je1f
sje4fla
sj2ef2l2
sje8f5t
sje3g
sj4ek4t5o
sje6k1t
3s2jel
sje4le1v
sje1le
3s2jen.
5s2je1ne
4s3je6n1t
5s4jer.
s2je5s4
5s4jet.
sj2et4ti
sje6tt
2sj1f
2s4jg
s6jim
2s2j1k
2s6j1l
2s6j1m
2s6j1n
2s1job
sj2o
5sjok
4sjom
9sj2on2
sjo6ns7
2sjor
2s1jou
2sj1p
2s4j1r
2s2j3s2
2s6j1t
1s6j3t6sj
sj4ts
sju1a
6s1jub
6s7jug
sju8la
sju2l
4sjun
4sjur
2s7jus
5s2j2ø
sjø3k6
sjø1p
sjø9rø
sjø1s2
sj4ø3t8
6sk.
1ska
2s1ka.
4skab
ska1be3
s2kad
8sk2a1e2
4s6kag
2skak
5s4kal8a
ska5lar
2s1kam
s4ka4m3l
4s5ka1na
4skan1de
ska6nd
4s1ka1ne
4s5ka1no
6s1ka6n1t
5s6kap.
9s8kapa.
ska1pa
4s3kap6as
5skapen.
ska5pe
s4kapen
6s3ka1pit
ska1pi
4s1ka2pp
2sk2ar
s4k5arab
s1ka1ra
sk7ar1be
s1k1a2r1b8
5ska4rd
4s5ka2r1k
6sk2a4rs
4s1ka6r1t
3s8ka2r1v
2s1kas
4ska1te
8s9kay
4s6k1b4
6s2k1d
2s1ke
s4ke5da
sked4
s6ke1do
s2ke5h
s6kei
skei5er.
ske2i7e
s8k5e4le1v
ske1le
s6kel2ik
sk2e1li
ske2l3t
s4kelu
s3ken.
s4k2e1na
s6kena2v
s4k2e1no
s5ke6n1s2
5ske2p1t
s4k2e1p
s5ker.
s4k2ero
s5ke4rs
s2ke1s
ske3si
skes4m
ske5s1n
s4ket8et
sk2e3te
s3k2e4t3j
s6ke1v
s2key1
2s2k1f
2s4k1h
1ski
5s6ki.
7skia
4skid
5sk4ie2
5s2k2if
5s4k2ik
s6ki2l1l
5s4ki2l1t
ski6net
sk4i5ne
ski4nin
ski1ni
5sk8in6n1s5
s2ki4nn
sk2i2no6
5s4kiol4
sk4io4
5s2kip
2s1k2ir
s4ki5re
6s5ki2r1k
s4k5i4rs
s6kis.
7ski4s1s
4s5kist
5s2k6iv
s6kje.
skj2
skj2e
5skje1ma
sk2jem
6s5kjemas
5s6kje2r4m3
3skjer5m4e
7skje4rs
3s6kjor
skj2o
4s5kjæ2r1l
5skjøn
skj2ø
6s1kjøp
skjø5re1s2
skjø1re
2s2k1k4
2s1k2l4
6skla
s2k5lak
s6k5lan
s5k2las
s2k9leg
sk1le
s6klei
sk3lek
sk5li6ng
sk1li
s4k5lit
s4k5luf
s2k5lyd
2s2k3m
2s2k1n2
s3k2nu
4sk2oa
7s4ko6d1d
sk2o1d
4skof
3s2k2og
2s3koi
1skol
7s4k4o1la
3s2k2o1le
4s3ko2l1l
4s1kom
s6k2o1ma
s5k6o1me
6s1k6on
4s1k2o1o
sk5o4rd
s6korpi
sko2r1p
s7k2o4rs
4sk2o6r1t
2s1k2os
s2k2ot
3sko6tt
sk8ra
skr6
s4k9ra.
5s4kral
s4krat
s2k7re.
4s3kre1f
6s4k1reg
5s8krek
4s3kret
5skre2v1n
skre1v
3s2k2ri1b2
3s4kr2if
4s7k6r2ig
3s4kr2ik
3sk2ri2p
4s3kri3te
sk2rit
3s6kriv
5s4kr2og
sk2ro
s3kr2on
4s3k4rop
sk6r2ud
skr4u3s6
6skry4s1s
s3kr2ys
1s2krå
2sk1s2
2s6k3t4
5s2k2ud
1s4kue
sku4e6nd
sku1e2n
sku4er
skue5re
1s6kuf
5skulan
sku1la
5skular
3sku1le
6s5kulis
sku5l4i
4s3ku2l1l
s4ku2l1p
4s1k4u2l1t
sku6m3s1
2s1kun
s6k5unde2r3v
sku6nd
skun1de
skund2er
4s1kup
8s5ku4rs
8s1ku2r1v
2skv2
sk5va.
3skvad
sk3vas
s6k1ven
sk3ver
sk5vit
3sk2vu
1sky
s6ky5a
s6k4y1e
4s1kyr
sky3re
4sk2ys
6s6ky4s1s
5s6kysk
3s2kyt
6skæ
6s1kø.
6s5k6ø1a2
4s1k4ø1e2
1skå
skå5re
6skå2t5
sl4
sl6a8da
s3l6a1de
sla5ge
sla8ge.
sl2a5ke
s5laks
3s4la6k1t
3s2lal
4s3la6nd
slap5pe
sla2pp
s5l2aran
sla1ra
s5la1re
4s5l6ast
s1lat
sla4te.
sla1te
4s1lau
s5laus
s2la1v
3sl2a1ve
sla4vin
2sle.
s1le
s8le1da
7slega1re
sl2e1ga
5s6legas
s4le3gi
s4leg1n
s6le1g2r4
6slei
slei5er.
sle2i7e
s4leiv
8s5le1k6e
s5l2e2kk
s2lel
s4l8e3me
4sle7ne
s6lener
s6le6n6t3
2sler
8s5les
s6le1sj
s4l4e1so
s6lest
s4le1ta
s5le1v
s4leva
4sley1
s5li1a
s1li
2slid
sli4en.
sl2ie
sli1en
6s3l2ig
sli5ke
sl2ik
s4li2kk
2slin
8s3li4n1j
s4li6n6t5
3s2lip
5s4lit.
3s4li1te
4s5liv
slo2b5b
s2lob
slo6tt4
sl2ot
s6lott.
7s4lottet
slot1te
1slu
8slu7a
2slu2e
slu5es
6s1luf
4slug
sl2uk3s6
sl2u8m4p5
sl4um
slum5p6e
s3lu6nd
s5l4u6ns
s6lup
sl4u7sa5
s4lut
s1ly
sly8et.
sl4y5e
slye2t
1s4lyn
4s5l2ys
sly8t
slæ6r2a1ri
s1læ1
slæ5rar
2sløn
s1lø
8s5løp
slø4pa.
slø1pa
s6lør
2sløs
slø8s5a
3s4lø1si
slø5va
slø5ve
slø5vi
5s2løyd
s4lø4y1e7
9s4lå.
s4l4å1e
s1lå2n
slå5ner
slå1ne
1slåt
s1m
sma6d
s1ma
3s2m6ak.
5s4maken
sm2a1ke
sma9let
sm2a1le
4sman
s4med.
s1me
s4me1de
6s5me3di
s4me2d5k2
smeg5
4smei
sme2k7l4
s4me6k5t
8s5me6ld
sm2el
3s4me2l1l
5s4me2l1t
2s5men
3s4me6r1t
6smes
s6me1si
s6mi1a
s1mi
s4m2ie
smi4e1ne
smi1en
s4m2ig
s6mil.
smi7la
s6mi1le
smi1ni6
s2mit
s3m2o7a
s1mo
smo8de.
s5mo1d4e
sm2o1d
s2mok
6sm2ot
3s2mug
s1mu
6s5mu4g1g
smul2
s6mu5la
s4mu6ld
s6mu1le
5s6mu6r1t
1s2m6y4k
s1my
6smøn
s1mø
1s2mør
smø4r3s
4s7m4øt
5s2må.
s1må
s2må5r
s8må6t1t
s1n
8sna.
s1na
s4nab
7s2nak
4s3nas
6s3nat
4snav
4sne.
s1ne
4s2n2e1b2
4sned
s6ne3e2
s4neg
3s2nei
snei5er
sne2i7e
sne4k2ri
s2nek
sn2e1k2r6
s7ne6k5t
s4nel
2snem
4sner
6snes
s2ne9sa
s2ne1s9v2
4snet
s6n2if
s1ni
sni6g1l
sn2ig
s2n2ik
snik5ko
sni2kk
3s2nil
6s3nin
3s2nip
s4n2ir4
5s2n4it
2s3niv
s8no.
s1no
s6n6o1e
s5no4rd
6s7n2ot
5s6n2ud
s1nu
s3num
s4nur
7s2nut
8sny1h
s1ny
2s1næ1
snæ4re.
snæ1re
5s6nø.
s1nø
snø5d4r4
5s2n4ø1e2
1s2nør
snø1s
snø5vi
so3al
s2oa
so8ar
4so1a4v
2s1ob
so2b2l2
1s2o1d
so4da.
so1da
5s6o1e
s2o1fa1
so4fag
so4fas
6s5o2ff
6s5of1re
sofr2
s6o2ft1
so2ga
s2og
so8gi
so7gl
sog6nem
sog1n
sog1n4e
6s2o1h6
5s4oi4
so5id
5s2o2k1n2
4soks
sok4se.
sok1s4e
7s8ol.
so4la.
s4o1la
so2l5av
1s2o6ld
so4le.
s2o1le
solei5er
sole2i7e
so4le1ne
so4l5f6
1s2o7li
2s3o6lj
s2o2lo
so8lo.
so8l3s2
so2l5t4
so4l4um
so1lu
so4lø
3s2om.
so6me6nd
s2o1me
4s3om1fa
so2m1f
4s1o2m5g6
5s8o2m1h
2s1o2m3k2
4so4m3l
1s2o2m5m4
somma1r5a
som5mar
som1ma
3s6om1me
somme6r5e6
2s1o2m1r6
7somren
s2omre
5som1rer
6s3områ
s1o6ms
som5sl4
s4omst
som5s2ti
4som1sy
1s2o2m1t2
5somt.
6som3ta
3s2on
so4na.
so1na
son7da
so6nd
7s6o1ne
so8n2ea
son8g3s4
so6ng
so4n3o
sons4k4
so6ns
son5st
so2nu
so4ny.
so1ny
2s1o2p
so7pak
s2o1pa
so9par
s2o7pet
so1pe
3s2opp.
so2pp
5s4op1pa
3s4op1pe
sop4pi
8sopp1le
sopp1l6
s2o3pr6
1s4or.
7sora
so3ran
5s4o2r1b8
4s1o4rd
sor4da
1s8o1re
so4rek
7s4or5ga.
s1or3g4a
so2r1g
sor4gl
so2ri
4s3or2ie
7s6oris
so5ri1u
4sor1ke
so2r1k
so2r5n
3s2o4r5s
7s8o6r1t
1s2os
4s5osc
so8se.
so1se
6so2s1f
4so1sj
so4s2l4
so5te
s2ot
so8te.
so4tra
so1tr4
so4ts4
sot5te
so6tt
sot4ti
4sou
2sov
so3va
so4ve.
s4o5ven
sove1r5e6
sove2r
sov4e3s4
5so1v4et
3s2ovj
sp2
4sp.
spa5g6h
s1pa
7s6p6ak.
7s6pa1ka
5s4p2a1ke
8spa2kk
s2pal
s3pa2l1l
5spa2l1t
3s4pa4nn
3s2p2a1ri
4sp6as
5s6pase6r5
spa9se
4spe.
s1pe
s2p2e4a
8spe7d6a
spe9dé
s4pe3e6
1s2pei
s2p6el
spe4leg
spe1le
spe4les
5spelet
3s4pe4nn
s4pe6n1t
s5p2e1p
spe4r5a6nd
sp2e1ra
6s3pe1ri
4spe2r1l
s4p2e2r1r
s3pe4rs
3spe7si
s2pe1s
s4pest
s9pet.
1spi
6s5pi1lo
4s3pi6ng
s2p4io
2s3pi2p
spi7res
sp2ir
spi1re
spi7ri
spi7ro
5s6pi4s1s
2s3piz
2s1pl6
8s9pla6n1t
s4p9lar
4sp1le
s4plin
sp2li
3s4plit
s1p6o
2sp6o1e
spo6et
2spol
5s6p2o3le
6s7p2o1li
s4po1ra
s4p8o1r4e
5s4p2o4rs
spor4t6s5
sp2o6r1t
3s2po2r1v
2s5p2os
4sp2ot
s3po3te
s2po5v
9sprag
spr6
5s2pran
6s3preg
5sprei
s4prek
spre5ke
s3p2rem
5s4pren
6s5pres
5s4pret
s6p2ri6n1t
s5pr4io
8s3pr6in7s6i
s3p2r6i6n6s5
8s3pris
8s7p2ro
6s5prob
s3pr2o1d
4s3prof
5s4prog.
spr2og
5s4pro1ge
5s6pro4s1s
s3pr2os
1s6pru
s4pry
3s2prøy
5s4prå
2s3ps
8spub
s1pu
2spul
3s2pyd
5s2pø
6s3pøl
sp5øy
9spå.
5sp6å1d
8s9r
sr4e8ka
sre8ke.
sre1ke
sr2i8e9
sri8k2a
sr2ik
s4r4i8ma
sri4ve.
sri1ve
s5ro
sr6o4e
sr2o6pa
sro6sa
sr2os
sro6se.
sro1se
sr2o2t
sro9te
sro1t5o
srø1v
srå4da.
sr6å1d
srå1da
sråd2e9r
srå1de
srå6de4rs
srå8ma
sr6åm
4s1s
s7sabel
ss6a1be
s7s8ab1l2
s6s2a1e2
s4sa1j
ssa8ke.
s3s4a3ke
s7s8a6la.
ssa1la
ss5a2l1l
ssa4me.
s1s2am
ssa1me
ss5a8m1p
s5s2a5ne
s7s8ar.
ssari8e9n
s3s2a1ri
ssar2ie
s7s6a2r1m
s4s1a2r1r
ssar8ve.
s4sa2r1v
s1s6as
ssa4u8sa
s3saus
ssau6se.
ssa2u1se
s6se3e6t
s1se
sse3e2
s2seg
sse5ge
ss5e4g1g
ss4el
ss2e5li
s3se8l5s
ss9elv.
sse8lv
ssel5v6å
s2sem
s5s2en.
sse8na.
ss2e1na
s6s5enden
sse6nd
ssen1de
s5s2e1ne
s4se1ni
ssen6ke.
sse2nk
ssen1ke
s4s2e1nu
s6s4enå
s5s2er.
s6serab
ss2e3ra
s6serat
sse7sk
s4se1ski
s4se1st
s7s2et.
s9s4e4ts1
s2se1u
s4sey1
s2s1h
ssi4a
s1si
ssi7e6rer
ss2ie
ssie1re
s4si2ff
ss2if
s6silo
ss2il
s8s9i1me
s6s3i6nd
ss5in4it
ssi4ni
s4s3i4n1j
s2s3i4nn
s4s3i6n1s
s5s2is4
ssis5m
ssi6v7e6nd
ssi1ve
ssi1ven
s5s6ja2r1g
s1sj
ss4ka5pe
s1ska
s2s3k2ar
ss1ka8ra
s4s3kof
s7s2k2og
s4skor
ss6ky.
s1sky
ss6k4y1e
s1s4kå
ss5kål
s7skåp
s2sl4
ss1lo
ss5ly
s2s1m
ss6nar
ss1n
ss1na
ss2no
ss4nø
s4s5nød
ss5nøk
s2sof
s4sok6
ss2o5lo
s2som
ss4o1ma
s7so3ra
s4s1or3g4a
sso2r1g
ss3ove
s2sov
ss6pil
ssp2
s1spi
ss4por
ss1p6o
ss9ri
s8s9r
ss2t
s5s6t2ad
ss4ted
ss1te
ss3tek
sstel6li
sste2l1l
s2s5t2e1p
ss7ti2l1p
ss1ti
sst4il
s7s2tip
sst6r4
ss5tren
ss4trå
ss3tus
s2s3t4v
s2sul
s3sur
ssva7ra
ssv2
ss1ve
ssy6na
s1sy
ss2yn
ssy5r
s8s7ø4y1e
s1sø
s6så
6st.
2s1ta.
sta6b2s3
3s6t2ad
st6a2d3a
sta6d3o
s5t6a1fa
3s2ta5fe
s4ta5f2l2
st3a2ft
s1ta7ge
4s8ta1h
st4a6ka.
sta1ka
5sta2kk
4st2ak1l4
6s1tal.
6sta4la
sta5lak
st5a6ld
4s3t2a1le
sta4le.
5s6ta1li
5sta2l1t
s3t4a1me
3sta2m1m4
st6a5na.
sta2na
3s4ta6nd
6s5t6an3de.
stan1de
s1t8a1ne
s4ta4n1f
s4ta6ng
stan8g5s6
s2t3a4n1l
sta8n9o
s6t5antr4
sta6n1t
st3a6n1v
4s2ta3o4
2stap
s7ta1pa
s4ta2pp
s8tapå
s3t2a1ri
1stas
3s8ta1sj
4s5tast
1stat
4stato
sta5t3op
sta3tu
3st2a1ve
s2tav
2s6t5b2
s2t7c
2s2t3d
2s3te.
s1te
2st2ea
2s4t2e1b2
3sted.
s2ted
s4te1da
5ste4d1t
s2te3e4
2s4te1f
3s6teg.
stega6l
s2t2e1ga
s4te1ge
3s2te2g1h
4s5teg1n
s4te1g2r4
4stegsp2
ste8gs1
5s6teg2s1pl6
ste4her
s4te1h
s2t4ei
stei5er
st3e2i7e
4s3tei2k1n2
ste2ik
ste6i6n6s5
3s4tek.
4s4t4e1ka
s6te1ke
ste4ket
s4te1ki
4s5t4e2k1n2
4s2te1k2o
8vs
v1s2t2
v4s5t4ek8st
vs1te
s4te6k1t
s8te5k3v2
4s2t2e3la
s8te5le.
ste1le
s5teleg
s4te5le1v
s5te3l2ig
st2e1li
5st6e2m1d2
s2tem
3s4te2m1m4
5s6te2m1n
4s4t6e1mø
st2e1n5a
ste4nar
5s4t2e3nen
ste1ne
s4tener
4s5te5nes
3s6te6ng
s4te1ni
6ste2nk
4st2e1no
stens5l4
ste6ns
4ste3o6
s5te2ol
2s2t2e1p
step7per
ste2pp
step1pe
st8er.
8s5t2e1ra
s4teram
s6terest
ste1re
s4tere1v
s7t6e2r1f
s4te2rid
ste1ri
ste7ris
s6te4ri6v
4s3te2r1m
6st2e2r5r
ste5run
st2eru
2st4es
s4t2e1se
ste7s1le
stesl4
4stet
s4te1ta
s2t8e7t6r4
s6te1tø
4steve
s2te1v
4stevi
6s3te6v1l2
5ste2v1n
6s2te1å6
2s6t5f
2s2t3g2
stga6ve.
st1ga
stg2a1ve
stgå8v4a
st1gå
stgå1v
stgå8ve.
2s6t5h
st5he
2s3tia
s1ti
4s2ti1b2
s5ti1be
s9tibl2
3stic
6s5tid
s6tidel
sti1de
sti5en
st2i1e2
s2t2if
3s4ti8ft
s4ti3g2en
st2ig
sti1ge
7s6ti1gi
s4t9i8gj
3s2tig1n
s5ti1j
6s5ti1k2a
st2ik
4s5ti5ke
s4tik1l4
3s6til.
st4il
5s4ti1la
sti4lag
4sti6l1d4
3s2ti1le
sti3le9ge
s6ti6l1k
6sti2l1n
s4tilo
6s5ti8l1s
5s4tilt.
sti2l3t4
6s5tilta
5s4til1te
4sti1me
sti7mer
6sti8mé
3s6t4i1mu
s6t3i6nd
s6ti1ni
4s6t1i4nn
s6t3inst
sti6n1s
s6t1i6n1t
1s2tip
4s3ti2pp
6s3ti2ps
5s2t2ir
2stis
6s7tisk
4s3ti6tt
s2tit
4s2ti1å4
s4tja
5s2t2jel
stj2e
4s3tjen
s6t5je6n1t
3s2tjer
4stju
2s4t3k2
2s6t3l6
2s2t1m
2s8t5n2
s2to.
1s2tof
2s3t2og
4stoks
st6ok
6s5to6k1t
3st2o1li
4s2t3o6lj
s5to2l1l
4stom
s9t8o2m1m4
s4t3o6m1s
6s5to2m1t2
4sto1ni
s1t2on
st3o1pe
st5opp1l6
sto2pp
6s8t5o4rd
sto4re4t
st8o1re
s6to2r1m
st4or3o
4st2o4rs
st5o4s
s1t2ot
sto4t5r4
5s6to1re.
st5ou
5sto1va
s2tov
2s6t3p2
8s8tr.
str4
st3ra.
4s5trad
s8tra2ff
6s3tra1f4i
6strail
st3ral
st4ran
str6a8na.
stra1na
5stra6nd
4st5r4a1ne
6s3t6ra6ns
4s5trap
4stra5r4
st7r6as.
st7ra6st.
5s4traum
s4tr2e3a
s4tred
7s6t4re2ik
s4trei
st5r2eis
7s6t6rek.
s4t5re1kl4
6s3t6re5ni
st5rer.
st2rer
6st3re4rs
6st2r1h
9stria
9s8t4rid
5str2ie
st5ri1ge
str2ig
st3rin
3s4t6rip
5stri3s
6s3tr6o1e
s5t4r2o5g
5s4t4rok
st3rol
6s4t5rom
st3r2on
st7r2op.
st7r2o6pa
s6tro1pe
s6t7rug
9s6t4ruk
st5rum.
str4um
2stry
6s3t2ryg
s7try6k1t
str6yk
4s3t6ræ
1s2trø
6s5t6rø1b
s4t6rø6m
4s2t1rør
4strøs
st5rø3se
s5trøst
4strøy
6str6å1d
2s4t5s6
stsa6me.
st1s2am
stsa1me
stsy8na
st1sy
sts2yn
4s6t7t6
stta6le.
st1ta
st3t2a1le
st3t4r4
s4tua
1s2tub
3s4t2ud
s2tue
s5tu4el
stu4er
stue5re
3s4tum
6s3tu6ng
6s5t4u4n5n
3s8tu6n1t
6s3t2ur
2s4tut
2s2t1v
s3t4ve2d1
1sty
2stya
2s1tyd
3s4t6yk
sty6l
2s5tyn
2s7typ
3s4tyr
sty4rs3
6s3tysk
st2y2s
4sty1v
sty5ve
1stø
2s2t1øk
5s4tøl
6s5t4øm5
5s4tøp
6stø2r1k4
st2ør
5s4t4øt
stø7va.
s2tøv
stø5var
stø3ve
stø5vi
st8øy
1stå
5s4t4å1e
stå5k
4su.
1su4a
su7a8l
1su2b
sub7l2
sub3o
5s2u6b1s
s3u4b4å
1sue
su8er
su2f
5s2ug
su6ga.
su1ga
su4ge.
su1ge
su4g3g
su2h
3sui6
su3is5
sui1t5a
su2k
4s1u1ke
3s4u2k5k
suk3r6
1s2uk5s
su4le.
su1le
3s4u4l1f
su4l2ik
su3l4i
sul4t5r4
s4u2l1t
s1ulu
2s1uly
su1læ5
3s2um
4s3umid
s2u1mi
s4u2mo
su6ms6
5s2und.
su6nd
8s5und2er
sun1de
5s4undet
5s4un1di
sun6d7r4
9s4un8ds
sun4ge.
su6ng
sun1ge
6s7u2n6i
su4o
3s2up
su4pe.
su1pe
su5per3
su3pi9
s4up4r6
su8pre
su4r5d4
su4re.
su1re
4s3u4rei
1s2u2r1f
su4r2ie
su1ri
su4ri1u
surs5k
su4rs
sur4sp2
sur4s1ti
sur1s2t
1sus
su3san
s4usa
su4se.
s2u1se
su6s5es
4su1si
su4s2ik
sus3p2
su4s3s
su2sy
2s1u4t1
sut6ra.
sut1r4
su6t8reg
sut3re
7su1v
8s5u6vi
sv2
6sv.
sva5a
svai5
5sv6ak.
3sv2a1ke
6svaks
sva4la
sva4le1s2
sv2a1le
6svalet
5s6va1li
7s6vam
s4var.
s4var4et
sva1re
s3va2r1m
3s4va6r1t
6s1vas
6s1veg
sve6g7i
s5ve2i7e
3s4v2eis
s5v4e1ka
6s5veks
5s4ve6ns
s1ven
sver8d5s4
sve4rd
sve4re
sve8res
3s4v2e2r1g
5s4ver1j
s5ve2r1k
sver4ki
s4ve2r5m6
6s7ve2r1v
7s4ve1v
s4v2ie
svi6e3l
4sv2ig
svi5ke.
sv2ik
svi1ke
6s7vi4k5g2
4s3viks.
3s4vi6k1t
4s5vik1ti
4svil
s6vin5d4e5l
svi6nd
svin1de
svi4nes
sv4i1ne
svin8g5s4
sv6i6ng
ll6svi6n1t
ll2s1v2
4s1v2ir
2svis
svi4sa.
svi1sa
svi4se
svi5sen
svi5ser
s8viv
svi9ve
4s1vol
5s2vor
sv2o6r7t
s3vr8
3s2vul
s1vu
5s6vu2l1m
s3vy4
svy7e7ne
sv4y1e
svye2n
1s2vø
4svæs
s1væ
2s7vå
1sy
sy4c
sy4de.
sy1de
sy5den
5s4y1e
sy2er
sy8ka
s6yk
sy4ker
sy2ke
sy5ke1re
2sy1ko
sy6k2og
s4ym
sy7me
s2yn
4s5yn4d1l4
sy6nd
sy2nk4
syn6sk
sy6ns1
syn3te
sy6n1t
sy4ra
sy5rar
s6yre
sy4re.
2s1y2r1k
syr4ka
syr4ke.
syr1ke
3s2y1s
sy4s4e
sy2sl4
sy2s3t
sys4t3r4
2sy2t
sy5ten
sy1te
sy5t6h
9s4y6tt
sy2vå
sy1v
s4z
sz3c6z
sz1c
6szt.
s4z1t
1sæ
sæ2l
8s3æ4re
8s3æ4ren
sæ4r1i
sær1le9
sæ2r1l
sæ4r5s8
7s6æ5te4
4sæ6tt
1sø
4sø.
sø2d
s1ø1de
4søf
søg4
4sø1j
9s6øk.
3s6ø1ke
5s4økj2
5s2ø2kk
søk7kj2
3s6øk1na
sø2k1n2
2s1øko
søk4sk
søk6s3e6
5s6øks1m
søk4ta
sø6k1t
3s4øl
sø4la.
sø1la
sø4le.
sø1le
sø5let
3s2øm
sø6må
3s4ø4nn
s1ø6n2s
s4øp
sø4ras
sø1ra
sø4re.
sø1re
sø4r5e6nd
søren6de.
søren1de
sø8r6et
sør9e6tt
7sø2r1l
sø4r3s
sør5ø
3s2øs
4søs.
sø8sa
7s4øt
sø7tast
søta4s
sø7tel
sø1te
sø5t4e1s
2søv
s1øve
3sø2v1n
2s5øy.
4s3øya
6s5øyd
søy8de.
søy1de
8sø4y1e
3s2øyl
6s5øy4n
søy2r
2s7ø2y1s
5s2åg
s3å2ke
5s4ål
så4le.
så1le
6s1ån
sån4da
så6nd
sån6de.
sån1de
så2p
9så1pa
så5pet
så1p2e
6s1å2p1n4
så1p9u
så2r
så6ra
sår9a6st.
så4re.
sår3sk
så4r2s
så1r7ø
så2s2
s7å1sa
s4å8s9b4
s5å1se
sås5k
s4å1s5t
9så1v
såv4a7
ta1a
4tab2o
tab2r8
ta6b2s
ta4bu.
ta1b2u
4ta1b4ø
t8a1c
4tad
t6a2da
ta8d3ei
ta1de
ta6d3e4t
tad5s1pa
ta8ds
tads1p2
tad7s6v2
ta6du
t8a1e2
6taei
4taek
4tael
1taen
4tae6nd
4tae4n1h2
ta6es
2ta5fe
ta2f7f6
5tafis
ta1f4i
4taf2l2
1ta1ge
4ta1gj
8ta1h
ta7is
1t6ak.
t4a4ka.
ta1ka
4tak4au
ta7ken
t2a1ke
ta5ker
tak7kel
ta2kk
tak1ke
tak5k4l4
ta8k9la
t2ak1l4
2takr6
tak4sal
tak1sa
tak6se.
tak1se
tak4si
4t3ak1sj
5takst
tak4tal
ta6k1t
4t7akt2ig
tak1ti
tak4to
t5aktø
ta1ku
t2ak3v2
1tal.
ta9lam
ta3lan
tal5a6ng
ta7las
3t2a1le
ta4lei
tal5e2i7e
ta4lek
ta5ler.
tale7s6
1ta2lg
tal6ge.
tal1ge
7ta2lj
tal4j5e1s
talj2e
tal4led
ta2l1l
tal1le
tal4leg
8t7all2e2r1g
tall7e3s4
tal8lig1n
tal9l2ig
tal1li
tall6s9a
tal8l1s
tal4ly
ta2l3op
6talter
ta2l1t
tal1te
t5alt2e2rn
ta6l9u
ta6lå
2ta2m1b
tamba6ne.
tam1b4a
tamba1ne
t3am1b4i
t4a1me
4tamet
t2a1m4i
2ta3m4o
t6an.
ta2na
4t3anal
t4a3nar
ta5nas
ta5nat
t5andak
ta6nd
tan1da
5t6an3de.
tan1de
t7and4el.
tan5d4e5l
t5ande1le
tand5r4
tan1dø4
tandø1r5e
tand2ør
1ta1ne
ta4nel
ta5nem
4tane6tt
t3an1fa
ta4n1f
tan1fø6
6tan2g1f
ta6ng
6t3an1gr4
1ta2nk
2ta4n1l
t3an5le
2t1a4n3m
3tan4n1l
ta4nn
t5an1no
tan6ns4
tan6sk
ta6ns
tan1s5ka
t5an5sl4
4t5anstr4
tan1st6
4t1an1sv2
8tant4il
ta6n1t
tan1ti
tan4tra
tantr4
6t7antre
t3anve
ta6n1v
2ta3o4
5tap.
ta4pa.
ta1pa
1ta1pe
ta4pe.
3ta1pi
4tapl6
4tap1pa
ta2pp
5tap1pi
1t4ar.
t6a1ra
ta4r3ak
4taram
ta6ra1re
t2arar
2t1a2r1b8
3t2arb2o
5t2ar5b4ø
4t9a2r2e3na
ta1re
6t7ark.
ta2r1k
tar8ka.
4t5ar3ke
4t3ar3ki
4t1arra
ta2r1r
tar9si
t2a4rs
tar7sp2
tar1s6v2
4ta6r1t
tart7est
tar3te
tart4es
t5art2ik
tar1ti
tar7tit
t6ar4t3r4
ta6r5å8k
ta1rå
4t4a1sa
ta3se
6t9a2s1f
t3a4sia
ta1si
tas2i3e
ta4s2if
ta4s2il
ta4s2i5s4
tas4p2
ta4s5s4
6ta7sto
ta7str4
4ta1su
t4a1ta1
ta7t4es
ta1te
tate8se.
tat2e1se
ta2t5e4v
t4a1ti1
tat3op
t2a4t2s1
tat3te
ta6tt
t3at1tr4
1tau
9t4au.
7t4a6ue
4t5aug
2t1a4uk
4taun
tau4ne.
tau1ne
tau4sk
2taut
2tav
6ta1vin
3ta6v1l2
4tav1li
ta9xy2
6t1b2
tba3d
t1b4a
tba2n
t6be.
t1be
tbe6te.
t3b4et
tb2e3te
tb2e6t7r4
tbe6t8ra
tbo6da
tb2o
tb2o1d
tbu8da
t1b2u
tb2ud
tb6y3k
t1by
tb2y4s
tby7te
2tc
t1ce
tcen4
tch5e
t5co
2t3d
tdy5p8e
t1dy
1te
4te1ad
t2ea
4te3ag
2te1a2k
2te9a8l
tea6m1
2te1a2n
2te1ap
2te1ar
te5a2r1b8
tea4s
3teatr4
4te1a6tt
2te1au
2te1av
4t2e1b2
t5e2b1b
tebu8da
te1b2u
teb2ud
4te1cel
t2ec
te1ce
te1co
2ted
6t5ed1di
te6d1d
3te3de.
te1de
4ted2e7b2
te7de1f
4tedek
4tedel
4tedem
te5den
4te1di
5tedil
4te1do
tedo6en.
ted6o1e
tedoe2n
tedo4er
tedo8et.
te4dor
ted4sk
te8ds
ted4s1l4
6te1du
4te1dy
tedy2r9k
4te1dø
te3e4
2te1f
8t4e1fa
4t4e1fi
4t2ef2l2
4te1fo
tefo8r
1t3ef1te
te8ft
3t4e1fø
2t2e1ga
5teg2at
4t3e8g1d
te7g2e1a
te1ge
te9ge3e2
4te4g1g
teg8ga.
teg1ga
2te2g1h
te4gim
te1gi
2te1gj
2tegl
te3gl2a
te9g8li
8tegs.
te8gs1
6teg2s1pl6
tegsp2
6tegsv2
2te1gu
tegvi8
te2g1v
2t4e1gå
5tegå2s2
4te1h
5tei.
2tei4d
t3e2i7e
t3ei3ga
te2ig
tei6ga.
t7ei6ge.
tei1g6e
6tei1gr4
3tei2k1n2
te2ik
tei5le
t4eil
t4e1im
6tei4n9f
te4in1ne
t4e1i4nn
1te5in1te
tei6n1t
t2e5i4s
6teiso
2te1j
te7ken
te1ke
te5ker
4tekil
te1ki
2t4e1kj2
5t2e2k1k
2te1kl4
te5k4la
tek6le3de.
tek1le
tekle1de
tek4li
4te3k2nu
t4e2k1n2
2te1k2o
2t2e1kr6
te7k2ra
te6k5ru
9teks.
4t3ek1sa
t3ek1se
4t1eksp2
t4ek8st
tek8t9r4
te6k1t
tek4t4s
tek6ty
2te1ku
te2k3v2
4te5ky
2te1kø
4te1kå
2t2e3la
te2l6ak
5te4l5ar
3te2lav
8te4le.
te1le
4t4eled
4telei
4telek
4tel8e3me
te2lem
te4le1ne
t4elen
6t2e3ler
te4les
te5le3sk
4te4le1v
te6leva
4telid
t2e1li
4tel2ik
4telil
4telin
te4l2ir
4telis
4telit
4teliv
tel5le1f
te2l1l
tel1le
tel4lei
tel4le1v
tel8lig1n
tel9l2ig
tel1li
tel4lo
2t2e1lo
tel3se
te8l1s
tel3se6s5
t3elsk
tel7s6v2
5teltet
te2l1t
tel1te
2te1lu
7telut
2t4e1ly
6t2e1læ1
2t2e1lø
telø8pa.
telø1pa
2t2e1lå
2tem
3tem2a1e2
te1ma
6teman
te4ma6ns
t5em1b4a
te2m1b
6tem6e4nn
t8e1me
5tem2ik
te1mi
tem9ma
te2m1m4
6te2m1n
tem5ne.
tem1ne
tem5o4rd
te1mo
tem5pe
te8m1p
temp6e8l7
3tem1po3
te6m3s
4t6e1mø
3ten.
te5nab
t2e1na
te4n5al
ten8am
te4nan
te4nat
ten5at.
6t7en1c
6t5en6den
te6nd
ten1de
4t1endr4
4tened
te1ne
te5neg
6t4enem
6t3e4n2e2r2g
5tenes
4tenet
2te6ng
7teng2on
ten1go
t5e4n2ig
te1ni
7ten2ik
5tenis
4teniv
ten9n2o
te4nn
te4nom
t2e1no
te5nor
6ten4o2r1m
4ten2ot
te6no1v
6te7nó
ten3sa
te6ns
ten5sko
ten6slu
tensl4
ten3so
tens5v2
ten4t5in
ten3ti
te6n1t
tentle8ge.
ten6t5l6
tent1le
tent3le1ge
4tenum
t2e1nu
4te1ny
4t4e7næ1
2te1nø
te4n5øks
tenø4k
2teom
te3o6
5te2on
4teo2p
te6ora
4teo4rd
2teov
2t2e1p
te3pa
4tepak
tepa9ra
4tep6as
4te5pe
7tepe3e6
9tepé
tep6pe1re
te2pp
tep1pe
t5e6p1le
tep2l6
4tepr6
4ter8ac
t2e1ra
te7r2a2kk
te2r1ak
6tera2m1m4
te4r7a1pa
4tera2pp
te4ra1re
6te4r4a1sa
4tera1se
7ter6ast
terba6ne.
t4e2r1b8
ter1b4a
terba1ne
t6e4r5d
4tered
te1re
4tere1f
4tereg
4ter2eis
te4rel
8te9r4em.
te2rem
6te5re2m1m4
te4r5e6ng
teren6g6a
6tere4nn
4ter2e1p
te4re2r1k
te1rer
te4r5e3s1ti
4t6ere6tt
tere4t
6terevo
tere1v
3t2e2r5g
3t2e2r1h
te4r2ig
te1ri
4ter2ik
4teri1si
te4riv
ter5j
4terk.
te2r1k
4t8er1ke
4ter4k1h
7t4erkr6
6ter8ks4
4ter6k1t
terl4a6ga
te2r1l
ter3le7ge
ter1le
terle6ve.
terle1v
ter8ma.
te2r1m
ter1ma
ter3no
t2e2rn
ter6n5s
te5ro.
t2ero
te1r6o6e
te3rof
4tero2l1l
te1rol
4te1rom
5te2r1o2m1r6
4te3r2os
4te3r2ot
ter1o6v
t2e2r5r
5te6rs.
te4rs
5tersjø1o
t6er1sj
ter5s2j2ø
ter6s4k2l4
ter1sk
ter7s6ko
ter1s4l4
ters4h
ter3s4m
ter5s6ne
ters1n
t4er5sv2
tersø6ke.
ter1sø
ter3s6ø1ke
3te6r3t
ter9to
ter9ul
t2eru
8te3r4um
te2r7v
tervi6se
t6ervi
2te1ry4
te1r5ør
t2e1rø
4ter4øt
te4r3øv
4ter6å1d
t2e1rå
te4r3å1s2
t4es
5t2es.
2te1sa
5tesar
te7s2en
t2e1se
2te1s1h
4tesid
te1si
4te3s2ik
6te3sin
4te3s2it
4te1sj
6tes2je1f
tesj2e
4te1sk
t9e2s6ka.
te1ska
6te1ski
te5sko
2te1s2m
7tesm4å1e
tes1må
2te1s1n
2t4e3so
6te1s2p2
5te8s9r
3te4s1s
5te6st.
4testaf
te1s5tas
4te3stat
5te6s7te.
1tes1te
4te5s2t4ei
te5s6tiv
tes1ti
5te6s5tid
te3s4t2ik
4te5s4t4il
4te3sto
4te3str4
4te1sty
4te1stø
2te1su
4te1sv2
tes8væ
2t2e3sy
2te3sø
te7så
te8så2r
teså9re
3tet.
teta6ka
te1ta
te3tak
teta6le.
te3tal
te3t2a5le
te4t4ap
1t2e3te
5tete.
tete4e4
4teteg
4tetei
4tetek
4te2tem
2t2e1ti
3tet2ik
3tetis
2t2e1tj
2t2e1to
2t8e1t2r4
3te4t2s1
t2et5ti
te6tt
tet6t3s
2t2e1tu
5tetu.
2t2e3t2v
2t2e1ty
te2t3å
2te1u
teu8k
t1e2ur
3te2us
2te1v
t6eva
te4va1lu
3te6v1l2
tevo6r
te3vr8
2tey6
2te1ø4
2te1å6
4té1a
2té1b
2té1f
2té1h
2té1i
4té1le
té1l
2té1m
2té1se2
té1s
4tést
6t1f
tfe6e2
t1fe
tfe4l
t1flå3
tf2l2
t7fo
2t3g2
t4ga.
t1ga
t2g2e4a
t1ge
t2gei5
t4gi.
t1gi
tgi5r2o1s
tgiro3
tg2ir
tg2re2i9e
t1gr4
t4gå.
t1gå
2t1h
t4hap
th2a
t4h2e2a3
t3h4ei5m
the2i
th2o7li
th4o
th8o3r2e1
3t2hr
thu5le
thu6s
thy5r
th6y
1ti
2tiad
ti3a1g2
2ti1ak
4ti3a2l1l
ti5a8l1s
2tia2m
2ti1ap
ti3a4sp2
4tiau
2ti1av
2ti1b2
2ti2c6k1
3tid
t2i2da
4tidan
ti6d7d
6t3i2de3e2
ti1de
4ti5dem
4t7idé
4tidis
ti1di
4ti1do
4tidr4
2ti1du
tidvi4
ti2d1v
6ti2dy
4ti3dø
t2i1e2
2tie1f
2tie1i
2tiem
4tie4n1h2
ti1en
ti2e5ra
4tie2r1f
4tie2r1k
ti4e6r5t
ti2e1s3
ti3esk
ti3et
4ti2e1ta
4tie6tt
2ti1fa
t2if
tifa8ne.
ti5fa1ne
2ti1fe
4tif2l2
2ti1fo
2ti5f6r2
t8ifrå
4ti8ft
tif5te
2ti1fu
4ti3fø
ti9ge.
t2ig
ti1ge
4ti1gj
2tig1n
3tig5no
ti6g1un
ti1gu
2ti3h
2ti1i
2ti1j
ti3ka.
t2ik
ti1k2a
6tika2r1r
ti5ke
4tiket
4ti5ki
6ti1kj2
tik4kj2
ti2kk
2ti3ko
2ti1k2r6
ti3ku
6tikular
tiku1la
6ti1k4u2l1t
6ti1ky
4ti3kø
t4il
til4a6ga
ti1la
ti6l1d4
3til1de
2ti1le
ti3l1ei
3til5fe
ti4l1f
3ti2lg
ti4l3id
ti1li
4til1k4e3
ti6l1k
3til2k1n2
4tille.
ti2l1l
til1le
4til8l1h
4tillin
til1li
til1lø7
4ti2l1m
7til1næ1
ti2l1n
3tils2t
ti8l1s
ti2l3t4
4til1te
2tilu
2ti1lø
ti7mab
t4i1ma
5tim6at
ti4me.
ti1me
5timed2ie
time3di
ti4m2e1ra
5tim4e1ri
2ti4m3l
4ti1mo7
4tim6o8r
2t1i8m1p2
ti6m7s
2t4i1mu
2ti1my
2ti3mø2
ti6nab
t6i1na
ti2n5a6r1t
4tinaz
2ti6nd
t3in1du
ti4nem
t4i1ne
4t1i4n1f
tin5g6e4s
ti6ng
tin1ge
6ti4n4it
ti1ni
6t5i4n1j
2t2i2nk
7tink1s2
4t1i4nn
6t5in1ne
t2i5no
t4insk5
ti6n1s
4t1insp2
4t3inst
4t1i6n1t
2t1i6n1v
4tinva
4ti1ny
9tiol4
t4io
6ti7om
tio6n9s6
ti2on
4tiop
ti1or
ti1ov
ti3pa
ti6pla
ti1pl6
4ti1pr6
3ti2ps
2t2ir
tira4t
ti1ra
ti4re1f
ti1re
ti1ro
6tisak
ti1sa
4ti1s2am
ti5s6an
ti3se
4ti2s4ei
4ti2sek
4tis2el
4ti2sem
4ti2s2e1p
tise8ra.
tis2e3ra
4ti5se4rs
6ti3s2ig
ti1si
4tis4io
4tisj2e
ti1sj
4tisju
ti1s4ka
4tiskan
4tiskil
ti1ski
tis5kok
6tis1ku
tis5l4
4tis1n
4tiso
6ti5s4prå
tis1p2
tispr6
4ti1stat
tis4ti.
1tis1ti
tis4t4il
ti5stre
ti2str4
4ti1sty
4ti1stø
ti8st8øy
2ti1sy
2tit
ti7ta
ti3te
ti1tj
3ti6t3l6
ti1t4r4
ti4t3s
3ti6tt
4tiu2b
ti1u
2tiut
ti4v5a6nd
t4i1va
tiva9re
ti4v5a6r1t
tiv6is
ti1vi
2ti1vo
tiv5si
ti8vs
tiv5sk
4ti5y
ti5ær.
ti7æ1re
ti9ært.
tiæ6r1t
ti1ø8
2ti1å4
6t3jag
2t1jak
t5j2a2r2n
2t1je.
tj2e
2t1jeg
2t2jel
1tjen
tjen6st
tje6ns
6t2j2e1p
2tjer
t5je1v
2t1ji
5t6jingan
tji6ng
tjin1ga
5t6jingar
2tj2o
t5jo4rd
2t1jub
tju4e
2tjun
6tjur
3t8ju1v
tjæ4res
tjæ1re
2tj2ø
4t2jå
4t3k2
t6kag
tka8ra.
t1ka1ra
tkly9
tkl4
tku6le.
tku1le
tkå8pa
6t3l6
tl4a4ga
tla8te.
tla1te
tle6da.
t1le
t2le1da
t4le1dr4
t6le1f
tlei5er.
tle2i7e
tle8se.
tl2e1se
t2l2e5ti
tli6g1h6
t1li
t3l2ig
tlig4ht5
tli6nes
tl4i1ne
tli4te.
tli1te
tlu4e
t6l7ut
tlø4pa.
t1lø
tlø1pa
t6l7øy.
tlå3ne
tlå2n
t2lå7r
2t1m
tma3d
t1ma
tma8ge.
tma1ge
t6ma1ku
t2mam
t2meg4
t1me
tme3in
t2mek
tmi1ni6
t1mi
tmi6n5s6
t2m2os
t1mo
tmå4la.
t1må
tm6ål
tmå1la
8t1n
t3na
t6ne3e2
t1ne
t4n2e1li
t7ne3l2ig
t4ne4rek
tne1re
tn2e4r5ø
tne8se.
t4n2e1se
t4nesk
t4nest
t5ni
t8no.
t1no
tn5sk
t6ns
tnæ6re
t1næ1
7to3a6n
t2oa
7toar.
toa6t
6to1au
2to1a4v
tob4e6r
to1b4e
t3o2b2l2
6to6b1s
4t4oc
to5da
t2o1d
to9de.
to1d4e
to4d2e1ra
tod2er
3to1do
1t6o1e
2toek
6toe2l
toe4t
2tof
6toff.
to2ff
t3of1re
tofr2
t6o5fri
to2g3at
t2og
to1ga
to7g2en
to1ge
4to4g1g
to6gl
6tog2rav
tog6ra
to1gr4
to4gre
tog3st
to8gs1
to2gu
2t2o1h6
toil3
2to1in
4t4o1j
t6ok
1to3ke
to3ki
2to1kj2
2to2kk
tokk5e6nd
t4ok1ke
t4o3kl4
5tok5s4i
5to6k1t
2to1ku
to4l5a2r1m
t4o1la
5tola5t
to5lel
t2o1le
to2le1le7
5tol2e1ra
toli8ne.
t2o1li
tol4i5ne
2t3o6lj
1to6l3k
7tolki
tol4la1b
to2l1l
tol4lag
tol6leg
tol1le
tol6le6tt
3t2o1lo
tol5t2r4
to2l1t
4to1lå
to9ma.
t2o1ma
2to2m1b6
to5mene.
t2o1me
tome1ne
2to2m1f
4to2m5g6
to3mi
to5mi3se
to7mist
4to2m3k2
tomlø8pa
to4m3l
tom1lø
t8o2m1m4
2t1o2m1r6
4to6m1s
5t6oms.
1t2on
to5na
to4ned
to1ne
to4nel
ton8g5s4
to6ng
to8ni1b2
to1ni
2to1no
ton3sa
to6ns
ton5s4l4
ton5sp2
2to1nu
to4ny.
to1ny
2t2o3o
4t2o1pl6
4top2p1d4
to2pp
6t1op2p1g2
6t5opp1le
topp1l6
4t5opp1læ1
top4po
4top2p7t2
4t2o1pr6
to1ra
to4r1ak
tor5a2l1t
t2oral
to4r5a6ng
to4r5a6n1t
to4r3as
4to4rd
tor8da.
tor1da
t8o1re
6toreg
to4rek
tor7eks
to7rem
6tore6n1t
5to1rer
tore4t
tor5e1te
4t1or3g4a
to2r1g
to2r5i6n1t
to1ri
tor7me
to2r1m
tor6m5s
tor5na
to2rn
tor3s4i
t2o4rs
t2o6r5t8
torvei5
to2r1v
t2o4rø
4torå
t2o1s
4to1sa
4to3se
to2s1h
6to1sj
to2s5ke
to8sku
4tos2p2
2to1sy
4to1ta
t2ot
to5te
to8ti.
to1ti
to5to.
to1to
to4tor
6to1ty
2tov
to1va
to7veg
to5ve4rd
tove2r
tove7re6
t5ove4rs
5tow1
4to1ø
6t3p2
t7pa
tp4i7ne
tpi7pi
t3pi2p
tpo4et
t1po
tp6o1e
t4på1k2
tr4
8tr.
6t9rabat
tra1b4a
3tra1f4i
tr4a8ka.
tra1ka
trak7to
tra6k1t
t5r4a1ne
3t6ra6ns
tran7s1ke
5trap
tra3pe
t4ra5po
tra5r4
5tra1se
tra5se.
tra5se5s
5tra1sé
t2rat
t5ra4te.
tr6a1te
3t2rav
tre4al.
tr2ea
tr2e1c
4t3re8ds
t3r6e1fu
tre1f
6t1reg
t5re1gi
4trei
tre5i6n1t
6t6rek.
4tre1ke
4tre1kl4
4t3rekor
tre7k2o
4tr2e9la
3t6r2e7na
tre5ne
t6re6ng
3t6re5ni
4t3re4nn
tren6sk
tre6ns
t7re1pe
tr2e1p
t5re1pres
trepr6
t2rer
t4r2ero
t4r2e3ru
2tres
6tre1si
tre5sko
t2resk
t6re1s4l4
3tres1n
t5re1su
t1ret
t4re1ta
t6r2e1to
t5ret7ted
tre6tt
tret1te
5t6rettel
4tre1v
tre9va
t6ri1b2
4t1ric
4t4rid
tri7e1i
tr2ie
t5rig.
tr2ig
t4ri2kk
tr2ik
t8r6i1na
t4r4i5ne
4t5ri6ng
3t4ri4nn
7t6r4io
t4rist
tri5t6o
t4ri1u
t2riv
t2ri7vi
5trix
3t4ro.
5tr2oa
tro9ar
3tr6o1e
t4rof
tr2o5g
3t2roi
tro3in
t4rok
t2rol
4t3r2om.
t6ro6nd
tr2on
5t4ro6ng
tro5per
tro1pe
t2r2os
5t6rosk
t4ros3l4
tro1v
t8ru.
t8rua
7trua.
5trued
5tru1et
3trug
tru1i
5tru2k1n2
t4ruk
t3r2u2l1l
2t1run9
t3ru6nd
4tru1pe
tru5sa.
tr4us
tr4usa
7t6rusk
2trut
t5r4uta
t3ru1t8e
t3r6u3ti
tru5v
try3dr4
3t2ryg
tryg5ge
try4g1g
7trykk.
tr6yk
try2kk
5tryk1ke
try7pe.
try1p8e
t6ræ
tr6ø8a2
t5rød.
t5rø4d1t
4trøk
4trø6m
2t1rør
5tr4øt
5t4røya
5trø4y1e5
trøy9e2t
4t3røyr
6trø2y1s
5trøyt
trå8da.
tr6å1d
trå1da
4t3rådet
trå1de
trå4dr4
t6råkl4
t3r4å1s4t
trå1s2
4ts
6ts.
t5sa.
t5sab
tsa2g
tsak6se.
tsak1se
ts1an
ts4a6ng
tsau6r
t1sc
t6sch2a
t6sch5k
t5se.
t1se
tse6d
tse4er1
tse3e2
ts5e2ge
t2sei4d
t2s3e2ig
ts2em
t3s2en.
ts4en1de
tse6nd
t5sen1di
tsen8ka
tse2nk
tsen6ke.
tsen1ke
t5s2e6ns
t4sentu
t7s6e6n1t
t5s2er.
t3s2e1ri
t4s3e2r1k
t2s2e1r3o
t5s6e6rs.
tse4rs
ts4es
t4sesk
ts5e6st
t2s3e2t6a
tse4te.
ts2e1te
t2s1e4v
t2s3f
t3s4fæ1
t7s8hop
ts1h
tsh4o
ts7i2l1l
t1si
ts2il
t2s3i4nn
t4s3i6n1s
t5s2ir
ts5je3g
t1sj
tsj2e
t1sje5t6sj
4tsje4ts2
t2s5jor
tsj2o
5t6sjov.
t7s4ju2k
t4sjur
t5s2kad
t1ska
t4skan
ts6kis
t1ski
t4s5kjed
tskj2
tskj2e
ts6kj2ø
t5skol
t6s3ko2l1l
t7s2k2ot
ts5kren
tskr6
tsk5ru
ts5krø
t2s1k6v2
ts9kvi
ts6ky.
t1sky
t6s7kyn
ts1l4
t7s6lo.
t3s4l2ot
t1s2lu
ts5lø
t5s2mit
ts1m
ts1mi
tsmå6la.
ts1må
tsm6ål
tsmå1la
ts1o
t4s5o4d
t5sok
t5s4omst
ts1o6ms
t5s2pek
tsp2
ts1pe
t3s2p6el
t1s2pi
t2s3pi1ke
tsp2ik
t3s4por
ts1p6o
t4s5s4
t1st4
t2s3ta2b1b
t5s6t2ad
ts3tak
t4s3tal
t5s4ta6nd
t5s4ta6ng
t5star
t5stat
ts6tau
t3s2ted
ts1te
t4steg
t4stek
ts6t2ig
ts1ti
ts4t2ik
t4s6ti6l1k
tst4il
t4stin
t4s2tit
t2s1tj
ts5t2on
t4s5t2o4rs
ts5t2re3e2
tstr4
t4s5tren
t4s5tro
t2s3try
ts6t4rål
t5stu
t2s3t1v
t4su4a
tsu7ge
t5s2ug
tsu4l
ts3u3l4i
ts3u4r
t4s5u1si
t1sus
ts1v2
t3sv2ik
ts1w
t5s6yk
t1sy
t2s5y2t
ts6ø8ka
t1sø
ts5øk1ni
tsø2k1n2
t2s5øv
tsøy8er
t8sø4y1e
ts1å
tsåt8
6tt
t1ta
t3ta.
t2tab
ttaba6ne.
tta1b4a
ttaba1ne
t4t9ab2o
tt5adr4
t4tad
t2taf
tta9fr2
tt3a2ft
tt8a4la.
t6t3a6ld
tta7lel
t3t2a1le
tta2l1l4
t4t3a2na
t5t6a1ne
t4t3a6ng
t4ta6ns
t4ta6n1v
tt5ap1pe
tta2pp
t7t8ar.
tta1re6
t4ta2r1r
t4t5a6r5t
tt6arta
t4ta3se
t4ta1sj
t4tav
tt3avi
tt5a6v3k6
tt5a2v1r8
t3te.
t1te
t2t2ea
t3ted
t4te1da
t5te3de.
tte1de
t8te1dr4
t2te3e4
t4te1f
tt3e2ff
t2teg
t2tei
t2t7ei4d
tte3in
t2tek
tte7k3v2
t4t7e4le1v
tte1le
tteli6te.
t4telit
tt2e1li
tteli1te
t4tem
tte4ma.
tte1ma
t6t5e2m1n
ttem8na
ttem6ne.
ttem1ne
t3ten
t5ten.
t4t2e1na
t5tene.
tte1ne
t4te1ni
tte6n5s
t9tens.
t4t5en1se
t2te3o6
t5ter.
t4terei
tte1re
tter5un
tt2eru
tt2e4r5ø
t4t2e1rå
t4t2e1se
tt4es
t4te1si
ttes6ke.
t4te1sk
tte2s1ke
t4tesl4
t5tet.
t4te1ta
t4t2e3te
t1té
t4t1h
tt5he
tti5a
t1ti
t4tidel
t3tid
tti1de
tt2i3e2
t2t2if
t5t2ig
t4ti4g5j
t2t3i6nd
t5t4i1ne
t7t8i1né
t5ti6ng
t4ti1ni
t4t3i6n1s
t4ti1sj
t6tis1ti
t7ti3te
t2tit
ttle6de.
t6t3l6
tt1le
ttle1de
ttlin5
tt1li
t1to.
t5tofr2
t2tof
tt5om.
t7t1o2m1r6
t2t2on
tton6na
tto4nn
tt3o2pp
t5to1ra
t4t3o4rd
tt8o5re
tt5o4ri
tt3o2r1m
tto5u
ttpar4
t6t3p2
tt7pa
tt6pås2
tt9ra1ka
ttr4
tt5r2a1ke
tt3ram
t5t3rap
tt7rat
t4t3r2e1p
t2t3res
t4t3re1v
tt5rom
t5trop
t5try
t2t1rø
t6ts
tt7saf
tts3ar
tt4s5kan
tt1ska
tts5kj2
ttsl4a6ga
tts1l4
tt5sok
tts1o
tt5sti2l1l
tt1st4
tts1ti
ttst4il
tts5top
tts5tra
ttstr4
tts5t8øy
tt1stø
tt3u4gl
t1tum
tt1un
ttun6ge.
ttu6ng
ttun1ge
tt1ut
tt8u1te
ttva8la
t2t1v
ttv4a
tt5ve
tt6vun
tt1vu
t5tw
t5ty.
tt4y8e
t2t1y6t
t1tæ6
t4t3ø4l
t1t2ør
t2t3øv
tt7øy6r
tt4øy
tt1å4
5tual
4tu4av
tu1b4a3
tu4be.
tu1be
tu6b4å
tu6dem
t2ud
tu1de
5tu1el
tu4er.
tu1er
tug8l2a
tu4gl
tu1in
tu2k
t5u1k2a
t1u1ke
7tu6k1t
3tu1la
1tu1le
tul5l6e3s4
tu2l1l
tul1le
t4u2m5m4
tu6m4s1
3t2un.
4tu6nd
3tu1ne
tun5gesv2
tu6ng
tun2g6es
tun1ge
6t1un8g1d4
tun8g9s1
6t5u1n6i
tu4n4io
5t4u4n5n
9t4u6ns
3t2ur
tur7a6n1t
tu1ra
tu4ras
4tu4rei
tu1re
tu4rek
tu1ri4
tu4rin
tur1o
tu2r3p
tu4r5s4
tu4r3uk6
tu5r4us
tur1ø2
1tus.
t4u1sa
3tu8s9b4
3tu2s1f
tu4sin
tu1si
tu4s1m
tus5o
tus7s6t
tu4s1s
tust6r4
tus5u
2t1u2t3g2
t5ut1j
t5u4t3k2
t1ut1r4
tut6te2nk
tu6tt
tut3ten
tut1te
tut5tor
tut2t5ov
tu8va.
tu1v
2t1v
tv4a
tva8k
tva6ne.
tv4a1ne
3t2va6ng
t4v5a4n1l
8t9var
tve5del
tve2d1
tve1de
3t6veit
4t7ver
tve6r1t5
t2v4et
3t1vet.
7t1ve4ts1
t5v2ik
tvi8ka.
tvi1k2a
t8vi3k2las
tvik1l4
5t4vil.
tvi5l1a
5t4vi1le
t4vi5l4i
t6v6i6ng
tvi4sa.
tvi1sa
tvi4se.
tvi1se
6t7vo
3t2vu6ng
t1vu
6tw2a2
1tyd
tyg9gel
ty4g1g
tyg1ge
tyg5g6es4
6ty1h
tykk7s6
t6yk
ty2kk
ty4med
ty1me
1tyn
ty4ne.
ty1ne
6t5yo
1typ
5t6y1p8e
ty4pe1re
ty4pet
ty4rak
ty1ra
5tyr4a4nn
6tyreg
ty4r2e1p
tyre5st
ty4ri.
ty1ri
tyr2i9e
ty4ri5s
t2y2s
3tysk
tys3t
2ty2t
ty8ta
t3ytel
ty1te
ty5ten
ty4ve.
ty1v
2tz
t5za
5t6zel
t1ze
6t6z5l
1tæ
tæ5l
tær6e1ne
tæ1re
tæ4re4n1h2
tæ1r4er
tær4et
tæ4r5s4
tøf3l2
2t1øk
tø9ket
tø1ke
tøk8ta
tø6k1t
4tøl
t7ø4let
tø1le
5t4øm5
tøn5nes
t4ø4nn
tøn1ne
4tøp
tøp5se
tø2p6s1
t2ør
tø4r3as
tø1ra
tør3in
tø1ri
tø2r1k4
7tørk.
9tørka
5tør1ke
tør5n4e
tø2rn
tør2s7ta.
tør2s2t
tø4rs
tørs7ter
tørs1te
tørs5tin
tørs1ti
tø6r3t
tøs4
tø8sa
4t4øt
tøt5a
tø5ta.
tø3te
tø6tt6
2tøv
tø6v6d
tø7ve1le
t4øy
tøy5a
t9øy2em
tø4y1e
tøy5r
tøy5te
tå4en.
t4å1e
tåe2n
tå2k
tå5ket.
tå1ke
tå5ki
tåk6r6
tå2l3a
tå9la.
tå4leg
tå1le
tå7lel
tå8l5s4
tån8da
tå6nd
tån6de.
tån1de
tå3ne
4tåp
tå6pe.
tå1p2e
t1å2p1n4
tå6re.
tå4r2e1p
t3året
4t5åri
3t8å2rn
2t1å4r2s1
tå3s2
tå2t
t3åtak
tå1ta
tå7ten
tå1te
ua7g
u1ak
uak3s
u1al.
ua7la
ua2l5l
ual3o
ual3u
u3a4ne.
ua1ne
u5a4n1l
u3a6ns
u1ar.
ua4r5d
u4a2r1r
u5a6rs.
u2a4rs
uas2h3
ua3t
u5au8
u1av
1ua2v1h
u4ba.
u1b4a
ub7a2l5t
ub7a6n1t
ub5a2r1k
ub4b2o
u2b1b
ub2br8
ub4b2u
ube8l5s
u1be
u2b5h
ubi6s
u1b4i
ubi1s7t
ub1j
ub7lan3de.
ubl2
ubla6nd
ublan1de
uble3s4
ub1le
u1b4r8
ub5rin
2u6b1s
ubu8e
u1b2u
u4b4å
uch5en
uch5er
u1ci
u2c6k1
u1cu5
2ud
u3da.
u1da
u7d6as.
ud4dag4
u6d1d
ud1da
ud2dr4
udd4s5e
ud8ds
ud2då
u2d2ea9
u1de
ude8a2re
ude1ar
u2d2e7b2
ude2i7e5
u2dei
udei5er
u2dek
ude3lu
u2d2e1p
u6d2ero
ud2er
u2de1s
u3d2et
u6d5e6tt
u5devo
u2de1v
udia2
u1di
udi3an
udi4en
ud2ie
ud4io5
ud7ir
ud7ji
ud5leg
u4d1l4
ud1le
udle6ge.
ud3le3ge
u7do2b3
u1do
u6d6o1e
u2d2ot
udo4ve2r7
u2dov
ud1r4
ud1se4
u8ds
u4d3t
u2d5v
3u4dy
udy9ra
ud5å4s2
u2e5a8
u5ei
u1el
uem8na
ue2m1n
u1e2n
u1end5r4
ue6nd
u7e1ne
uensar4
ue6ns
ue7o6
u1er
u5e6r6ast
u2e1ra
ue4re.
ue1re
ue5ren
ue4rer
ue4res
ue5ri
uer1le7
ue2r1l
u2e2r1m
u2e4se
ue2si
ue4skj2
ues6n
u1et
u2e1t8r4
ueu2l8
ue1u
u4fe.
u1fe
u6f2e1b2
ufe6e2
u2f1f4
uf4f5e2r1m
uf1fe
ufi9l
u1fi
uf4i5ne
u1f2l2
u6f2oa
u1fo
uf2o4r3s
u1fr2
uf1t3a
u2ft
uf4tan
uft5s4la
uf4ts
ufts1l4
uft3sp2
u1fø6
u5gag
u1ga
u5gar
u6g5av
ug1by7
u2g1b
u2gem
u1ge
u5g2en
u3g2er
u4g2e1rø
u2g6e5s4
u9get.
u8ge1ta
u2g2e1v
ug8g3s4
u4g1g
u5gis
u1gi
u4gl
ug1l2a
u5g4lad
ug9l8ar.
ug1lar
ug4le.
ug1l6e
ug9ler.
u4gler
ugle7s
ug5ly
ug7na
ug1n
ug7ne.
ug1n4e
u1go1
u4go.
ug4re
u1gr4
ugrei9er
ug2re2i7e
u4grø
ug5s4i
u8gs1
ugs4k
ug7so
ug3s4pr6
ugsp2
ug5s4va7
ugsv2
uguay7a6ns
u1gu
uguay2a
uguay1an
ug5øy5n
u1h
3uhel
ui8a
u4i6c
ui2d
uid5el
ui1de
uid5en.
u2iden
uid5e6ns
uid5er
uid5in
ui1di
u2i3e6
u4il
ui2l5l
u1im
u4i3ne
u1i6ng
uinns3mi8
uin6n1s4
ui4nn
uinns1m
uinns4m2ig9
u1is1m
uista7
uit3en
ui1te
uit5er
u1i6tt
u1j
u4jas
u8je.
uj2e
u7jå
4uk.
u1k2a
u7kar
uka1re6
u1ka6r5t4
u5kat
u2ke.
u1ke
u4ked4
uke5l4
4u2kem
uke4r5an
uk2e1ra
u4ke4rek
uke1re
u3ke1r5i
ukh4o7
u4k1h
u7ki
u1kj2
4u2kk
uk1k6a
uk7kestal
uk1ke
uk4ke3sta
uk8k9l4
uk4k7n2
ukk7s6
u5k2lem
ukl4
uk1le
uk8lu
ukl7ut
u5k4no
u2k1n2
u1ko
u4k5og
u2kra
ukr6
uk3r4us
2uks
uk2sa
uk4sek
uk1se
uk4s2el
uks2e7te
uk2si
uk2s1k
uk7ski
uk2so
uks5t
uk6s3un
uk1su
uk2sø
uk4t5e4sk
u6k1t
uk1te
ukt4es
ukteslø9se
uktesl4
ukte2sløs
uktes1lø
uk4t3id
uk1ti
uk6tj
uk4t5o1ri
uk2t7r4
ukts6l4
uk4ts
ukt3sp2
ukt9s6v2
ukt4s1ti
ukt1st4
uktu8e9ra
uktu1er
uktue5re
u1ku
uku6e
uku5le
uk5v2ik
ukv2
uk2ys8
uk3ø4
uk5å4
6ul.
u1la
u4la.
ula7d
u6l1af
ul3aks
u4l5a6r1t
ul4det
u6ld
ul1de
u4le1f
u1le
u2lek
u2lel
ulele8ge.
u2le1le
ule3le1ge
u4le5ma
u3len.
u6le5ni
u3le6ns
u5ler.
u5le9re.
ul8e1re
ule3ri
u2l3e2r1l
u4l2ero
u2les
ule3st
u5le6st.
u5le6s5te.
ules1te
u2le1u
u1lé
ul4f5l2
u4l1f
ul4fo
ul4fr2
u3l4i
ul2ie8
uli9en
uli5ke.
ul2ik
uli1ke
3uli4k1h
uli9na.
ul6i1na
ul4i5ne
u7li4n1j
u2l1j
ul5ka
u6l1k
ul4k3v2
ul4lag
u2l1l
ul4lam
ull7a2r1m
ul4leg
ul1le
ull5e4g1g
ulleg8ga
ul6le6ng
ul9l8e1re
ul4l6e3s4
ul8li.
ul1li
ul9l2ig
ullin8g7s1
ulli6ng
ul4l5i4v
ul4l3o6s
ul8l2ot
ul1lo3v
ull5s1le
ul8l1s
ull2sl4
ull3s6m
ulls4t
ull3s1v2
ul2lu4
ul6ly
ul2lø
ul1m4u
u2l1m
u1lo
ulo6i
u2l5op
ulo7va
u1lov
ul4sk
u8l1s
uls4p6o
ulsp2
ul8s1n
ul8s7s8
ul2st
ul8s3t6r4
4u2l1t
ul4teg
ul1te
ul4tek
ul4t4es
ul4t4il
ul1ti
ul4tim
ul4to
ul7tor
ul4t3re
ult2r4
ult3ri
u7lua
u3lue
1uluk
u5lup
u5lus
ul9ut.
u7luta3
u9lu1te
ul7va.
u8lv
ul1va
ul5van
ul4var
ul4ve.
ul4veg
ul4vei
ul4v4es
ul2v3t
ul4v3u
1uly
ulyk5kes
ul6yk
uly2kk
ulyk1ke
ulæ6ra
u1læ1
ulæ5re
ulø3se
u1lø
u4lø4y6e
ul5å6l
2u1ma
uma8ge.
uma1ge
u6ma1re
u2m5au
umau7ken
umau4k
umau1ke
u2m3av
2u2m1b
um5be
4um1c
4u2m1d2
2u1me
u4me6nd
u4m3e4n1h2
umen4t5i
ume6n1t
um2e5ra
2um1fo
u2m1f
2u2m1h
2u1mi
u6mi6d1d
umi1e6n
um2ie
um6i9na
u4mi6n1t
um9ja
2u2m3k2
4u4m3l
4u2m1m4
um3me
um4mil
um1mi
um4mis
u2m3n
um1na8
4u1mo
um2o6g
um5ok
2u8m1p
um7pan
um1pa
um4p1le
umpl6
um4p7ut
um1pu
4u2m1r6
u6ms1
um7se.
um1se
um4sk8u
umsku8le.
um3sku1le
um2s2n
um4sor
um1so
ums3t
6u2m7t
umta8l
um3ta
u2m5ut
u1mu
4umve
u2m1v
umø4r3s
u1mø
u2m7øy
u1må7
2un.
un6a6da
u1na
un5a2l5g
u4n3ap
u9nar
u7nas
u3nat
2und.
u6nd
6un1da
6un3de.
un1de
un4d6ek
un5d4e5l
6unden
5under1sk
und2er
unde4rs
5unde2r3v
4undet
un6d7im
un1di
4un8ds
und5s4i
u2ned
u1ne
u4ne3e2
u9nei.
u2nei
u2nel
unele6ge.
une1le
une3le1ge
u4n5e6ng
u3nen
u4n2e1no
u2ne1s2
u3n2es.
u2ne1v
u3ne2v5n
1un8g1d4
u6ng
ungele8ge.
un1ge
unge1le
un1ge3le1ge
un4gem
un2g1j
un4g5l
un4go
un2g1r4
un9g8ru
ung3s4i
un8gs1
u1n6i
u4ni.
u2nid
1u2n2if
u4nim
u3nin
3uni2on2
un4io
uni4st
4u2nk
un4k5l4
un7ko
un4kr6
un7n1ak
u4nn
un1na
unn5e2r1f
un1ne
un4n5e4rs
un4n5e2r1v
un5nes
un4niv
un1ni
un2no
unn5s1te
unnst6
un6ns
unnta8la
un6n7t4
un4n3y
u4no.
u1no
u2n3os
un2o7t
4u6ns
un6sj5i
un1sj
un3skr6
un5s4p2
un3stek
uns1te
uns4t5e6l
un7s6te2nk
unst3o
un1s2v2
un4tal
u6n1t
un4tam
un4t6as
un3t2r4
un4t5r2e1p
un4tri
untun8ge.
un2t1u2n
untu6ng
untun1ge
u1nu6
u2n5ut
u3ny
u3nø
un5øy6d
unøy1
u7nøyg
u3nå
u2o9a
u1ob
u1om
uo6m7s
u1op
uor8da
uo4rd
u8o1re
u4o5ro
u1ov
u2p3av
u1pa
u3p2ea4
u1pe
u5per
up2e1r3a
upe6ren
upe1re
u4pe1ta
6u2p1f
u3pi
u8pi.
up3i6n1s
up3li
upl6
up6ne1v
u2p1n4
up1ne
upo9pe
u1po
u2p1p2
up4ped
up1pe
up4pla
upp1l6
up6pre
upp1r6
up2p5s
4upr6
u3pra
6u2ps
up6sen
up1se
up2s3k
up2s4ke
ups5pr6
upsp2
6u2p1t
6u1pu
u1på
upåvi6
u1på1v
u1ra
u2rad
u2raf
u6r3a2ft
u2r1ak
ur6a6ly
u4ram
u5r4a1ne
u3ra6ns
uran6t5re
ura6n1t
urantr4
u5r4ar.
u6r7a2r1l
ur3a2r1v
u9r6as.
ur5asp2
ur3a6tt
ur7au
urau9ken
u4rau4k
urau1ke
u2r1av
ur4b2o1d
u2r1b8
urb2o
ur4c
u4rd4
ur3di
ur4d5o
u1re
ure8al
ur2ea
uree1r6e
u2re3e2
ure3er
u4r5e4g1g
u1re1gi6
uregist5
3urein
ure4l
u2r3e1le
u4r2e1li
u7r8e2l1l
u7r8e2l1t
u4r3e8l1s
u4r5e6ng
u7reom
ure3o6
ure7o6s
u4re1su
u4re1ta
u4r2e1te
ur4e5v
2u2r1f
ur4f2e1b2
ur1fe
ur6fed
ur3ge
u2r1g
u1ri
u4ri.
uri6a7ne
uri1an
uri9en.
uri1e4n
ur2ie
uri4e5ne
uri9er.
uri1er
u3r2ik
u3rim
ur4i5ne
u4r3i4nn
u2r3i6n1s
u2r3i6n1t
uri6s1p2
ur4ke5s
u2r1k
ur1ke
ur7k6j2
ur6k7ja
ur8kla
urkl4
ur4k1le
ur6k5n2
ur8k5s
url4a8ga
u2r1l
urle6ge.
ur1le
ur3le1ge
urle8se.
url2e1se
urma7g
u2r1m
ur1ma
ur3n2e5a2
u2rn
ur1ne
ur6n1s2
urnæ6re
ur1næ1
u4ro.
u6r4oc
5ur6o1e
uro6m5s
u2rop
uro7pi
u2r3o2pp
ur1or
ur2o3s4
uro4sta
uro8ta
ur2ot
ur4p6el
u2r1p
ur1pe
ur6p9ut
ur3pu
ur3r6e
u2r1r
ursa8ka
u4rs
ur1sa
ur3sak
ur4sal
ur4s3el
ur1se
ur4s1in
ur1si
ur4skr6
ur4s1pe
ur1sp2
ur5s1te
ur1s2t
ur3s5t4il
urs1ti
ur4stj
ur2sv2
urs3va
ur2s1ø
ur3te3o6
u6r1t
ur1te
ur5ti
ur4to
ur6t5ri
urt2r4
urt1s4t4
ur4ts
urue7re
uru1e4r
u6rug8
ur7u4gl
u4ruk6
uru4ke
uru7k1n2
ur6ul
ur4u1mo7
ur4um
uru6n4g
u7r4u2nk
uru7v
ur4vel
u2r1v
ur8v3s1
u6r2ys
urø2
ur1ød
ur1øy
4usa
u9s2a5ne
us3a6ng
u9s2ar.
u7saren
usa1re
u6s7a2r1k
us7au
us1c
2u1se
u5seal
u2s2e1a
use4al.
u6s7edvan5le
used7va
use2d1v
usedva4n1l
u2s1ei
u2sek
u4se1le
us2el
u2sem
u6s5en1di
use6nd
us7e2r1k
u4s5e2r1m
u6se1si
u4set8j2e
us2e1tj
us3eva
u2se1v
4u6s9g6
u2s1h
u2s3h4a
u2s3h4o
us4id
u1si
us3i6nd
u2s5i4nn
u8s7ja9g
u1sj
u4sji
us5kaf
u1ska
u4s3kan
us1ka8ra
u2sk2ar
us7kat
us5ken
u2s1ke
u6s5kis
u1ski
u2s1k4l4
us6k5le
us5klo
us1ko
u1skr6
us7kru
usk7u6t
u1s4ky
us1l4
usle6ge.
us1le
us3le1ge
u2s5lin
us1li
u4slu
us7mø
us1m
u4s1n
us1ok
uso5l
u1s2o6l5d
us1or
u3so2ri
usove7r6e6
u2sov
usove2r
us1p2
u1s2pin
u1spi
us5sar
u4s1s
us6sat
us4s2jå
us1sj
us4skj2
us4s5kor
us4skur
us2s1l4
us7s1mi
us2s1m
uss7mø
us4s5ti
uss2t
us4st6r4
us2sv2
u6stan1de
u3s4ta6nd
u4stat
us3tav
u2s1te
us3ted
u5s2t4ei
us7ten
us1t9et1te
u4stet
uste6tt
us5ti
u5s6t2ig
us1tj
u1s2t5of
us1t2on8
ust7o4nn
us5tor
us2t5o6v
us8t3ra.
ustr4
us8t4ran
u4s8tra5r4
u6stre
ust5ren
us4tri
us3tro
ust5rød
u1s2trø
u6st5rå
u2s4t7ut
u4stå
us3u6ng
u2sur
us1v2
u5sv2a1le
u7s6va2l1t
usva8n
usvi8ka.
usv2ik
usvi1k2a
6u1sæ
usæ9le
usæ2l
u3s2ø6m
u1sø
us1øy
u1t8a1e2
u2tag
u7ta4g1g
u9tal.
ut3a2l1b2
ut5a6ld
u3ta2l1l
u5tan1de
uta6nd
uta7no
ut6a6n1v
ut3a2r1m
u4t3a6r1t
uta1s4
u5t6as.
u6t5a6sa
u6t5a6se
u2ta1u4
1u6t1b2
ut4ba.
ut1b4a
utbe8d
ut1be
4ut1b4i
ut6by.
ut1by
utch5
u2tc
1u2t3d
4utdat
ut1da
utda7ta.
utd4ata5
u2te.
u1te
u2t2ea
u4te3e4
u4tei
u2tek
ut3eks
u5t4ek8st
ute6ma.
u2tem
ute1ma
u5te2m1m4
u9te2m1t
ut2en
u6t2e1na
u3te2nk
u4t2e1nu
u2te3o6
2uter
u6t2e1rø
u2t4e1s6
u4te1ta
u3te6tt
u2t6e1u
ut6eva9
u2te1v
1u6t1f
utfa7s
ut1fa
utfø5re
ut1fø
utf8ør
1u2t3g2
8utg4å1e
ut1gå
6u3ti
u4t5if
ut4ik2k6u2
ut2ik
uti2kk
uti3ku9
u4ti1li
ut4il
u6ti1ven
uti1ve
ut1j
u5tjen
utj2e
ut6ju
3utlei
u6t3l6
ut1le
utlø5se
ut1lø
utlå7na
utlå2n
utmå5le
u2t1m
ut1må
utm6ål
u8t5ni
u8t1n
1ut1ny
2uto
u4t7o6m1s
ut2o5s
u2to4v
3utpr6
u6t3p2
ut4på
ut1r4
u5t4ra1di
ut3re
u5t6re6ng
u3t2riv
u3t2rol
u3t2r2os
5utru
u5t6rul
ut4rø
ut9rød
1u4t1s4
4u6ts.
4utsei
ut1se
utse8t9ja
uts2e1tj
2ut1sj
ut3sk
5ut1st4
7ut1t6ak.
u6tt
ut1ta
5utt2a1ke
ut4te2r1k
ut1te
utt6eva
ut2te1v
ut4tid
ut1ti
ut4t4il
ut5tor2ea
utt8o5re
ut1tr4
ut4tra
3ut5try
ut6t1s2
u1tu
utu5e
u6t5un
utun8ge.
utu6ng
utun1ge
u2t5ut
1u2t1v
u3t4vil
utvi5se
2u3ty
3u4t6yd
u8ty.
8u2tz
ut1ø
u5t2ør
ut4ø8s4
utøs7t
1u2tø4v
ut5øy5a
ut4øy
ut7å2k
u3tål
ut1ån
ut7år
u1u
u7u1ma
u5u1me
u7ut
u1v
uve5di
uve2d1
uve3in
uve6ris
uve1ri
uv4e3s
u7v2es.
uve1t5ø
uv4et
u6ve1v
u5vi
uv2i6k
uv5ra
u2vr8
uv9sa
u8vs
u5vu
u3vø9re
u1w
4ux1
ux4a4
uy6a
u7yn
u1æ
uær3le9ge
uæ2r1l
uær1le
u1ø6
uøv5
u9åra
u7åre
va3a2m
va8a2n
4vab
6vad1j
vad1r4
vaf3
vaf6r2
va2i5r
vai5s
v4a6ka.
va1ka
v6a7kan
va5ker
v2a1ke
6va4k1h
va8ki.
v4a1ki
4v2a1ko
vak3r6
7v6ak1si
4v3ak1sj
vak5s6t
1va6k1t
vak2t5r4
va1ku
v2ak3v2
va3lan
va4led
v2a1le
va4l9eks
4valel
val4g3r4
va2lg
val2i9e8
va1li
val3la
va2l1l
val6mes
va2l1m
val1me
va6l1o
va1lu
va4lun
4va8lv
va4løy
va1lø
va1l7å
vam8pu
va8m1p
4v3anal
va1na
v4an8d5s
va6nd
v4a1ne
vane5s
va4net
2va6ng
van4gr4
van8g4s7
va8ni.
va1ni
1v4a4nn
van4n4an
van1na
van4niv
van1ni
vann6s1ti
vann3st6
van6ns
va4nom
va1no
v4a6ns
van5sem
van1se
5vansk
van5s6ki
van3s1m
van3ti
va6n1t
van9tr4
v6a4ny
2vap
va5po
va4ra.
va1ra
va6rak
va4ral
va4ram
va4rap
va4re.
va1re
va4reg
va6rem
va4res
var8ka
va2r1k
var5ma
va2r1m
var4me7e2
var1me
var4sa
v2a4rs
var4sk
var4s5ti
var1s2t
var4sv2
varta9la
va6r1t
var6tem
var3te
var4t5i4s
var1ti
4v4a1sa
va4sa.
va9set
va1se
6vasid
va1si
va6s2ie
va4s2if
va4s2ik
va4s2il
va6s2is
va2sk
vass5a
va4s1s
vas4sau
vas4s4el
vas1se
vas6s7ø
4vas1ta
v4at5a1
va3ta.
va6t7e8l
va1te
va2te2r1f
v4ater
vatle6ge.
va6t3l6
vat1le
vat3le1ge
5va8t1n
va1to
va6tr4
va1t7ra
v2a4t1s
4vau
2v1av
va4z
2v1b2
vba4ne.
v1b4a
vba1ne
vb2e2r5g
v1be
vb4er
vbo6da
vb2o
vb2o1d
6v1d
v7de1v
v1de
vdin8g5s4
v1di
vdi6ng
v7do5i
v1do
v7d2on
vd6r4
vdu6en.
v1du
vdu1e2n
vdu6er
v7dø
1ve.
2v2e1a2
ve6ag
ve6ar.
ve1ar
2v2e1b2
2v2ec
ve2d1
ve9d8o2b3
ve1do
vedom5
ve8ds2
ved5s4k
3ve8d5t
ve1dø4
vedå6
2ve3e2
ve4er
2ve1f
ve6g5av
v2e1ga
ve5ge
3ve4g1g
veg8gs4
2ve1gj
4vegl
ve3g1l6e
ve5g6lød
ve5g6lø
veg5n
2v4e1go
6vegre
ve1g2r4
ve4g5re6tt
4vegru
ve4gut
ve1gu
v4e4g5å
2ve1h
1ve2i7e
vei3e2n
4vei2l1l
v4eil
6veit
2ve1j
4v4e1ka
5ve4ka.
ve4ke.
ve1ke
ve4ked4
4veke3e2
2ve1ki
4v4e1kj2
2ve1kl4
vek8la
vek8le
ve4k1li
2ve1k2o
2v2e1kr6
3veks
vek4st
veks4t5r4
1ve6k1t
vek4t5an
v4ek5to
vek4t5r4
2ve1kå
5vel.
ve4l5as
v2e1la
5vel4at.
5vela6t3p2
6veld.
ve6ld
vel4del
vel1de
8vel8ds
4v4eled
ve1le
6velei
7velet
4velis
v2e1li
4velit
6veliv
2v2e1lo
5vel4oc
ve8l1s2
vel3se
vel3se6s5
vel3s1m
vel5sp2
vel5st
4velug
vel5un
ve8l5v
2v4e1ly
2v2e1læ1
2v2e1lø
velø8pa
2vem
ve5ma
1ven
ve8na.
v2e1na
4v4enem
ve1ne
ve5net
ve6n2e3te
4v4ene1v
ve4nin
ve1ni
ven6n5i
ve4nn
venn5le7
ven4n1l
v2e2no
ven6s4ti
ven1st
ve6ns
ven6str4
vent6a
ve6n1t
ven5t6r4
ven4t4s
4venty
2v2e1nu
ve4nus
ve5ny.
ve1ny
4venya
ve4nym
2ve3o6
2v2e1p
1v8er.
ve2r5ak
v2e1ra
ver3al
8ve2rau
ver6de1s
ver1d2e
ve4rd
ver6din
ver1di
ver6d2ir
ver3d7v
ve3re.
ve1re
4vered
4vereg
5ve2r1e2ig
4verek
ve4rel
ve6r7e6ng
4vere4nn
4ver2e1p
ve5rer
ve5r2es.
4v6ere6tt
vere4t
4ve4r2e5tu
verfø5re
ve2r1f
ver1fø
verf8ør
ve9ri.
ve1ri
veri3a
ve4rial
ve7r2ie
4ver2ik
v4er3il
5v6er2k1n2
ve2r1k
ve2r5m6
ver6nal
v2e2rn
ver3na
ver6n6s5
ve9ro.
v2ero
ve3rom
ver8s4ka.
ver1sk
ver3ska
ve4rs
ver5ski
ver7s1p6o
v6er1sp2
vert8a8la.
v2erta
ve6r1t
verta6le.
ver3t2a1le
v2e1ru
5verum.
ve3r4um
6ve1ry4
v2e1rø6
ver3øs4
4v2e1rå
v4es
3v2es.
2ve1s4a
ve5san
4veset
v2e1se
4ves1h
4ve1si
4ve1sj
4ve1ski
2vesl4
ve9s1li
ve1s2m
6v4e3so
4ve1s2p2
ve6s3per
ves1pe
ve5s2ted
ves1te
ves6t5e6nd
v6esten
ves2t5o4v
4vestr4
4ve1stu
4ve1stø
vesva7
vesv2
5vesyn8ds
v2e3sy
ves2yn
vesy6nd
2ve1sø
ve1så
veså5re
veså2r
v4et
1vet.
2ve1ta
8veta1ka
ve3tak
4veteg
v2e1te
4vetek
ve5t4e6s7
6ve2te1v
2v2e7ti
4v2e1tj
2v2e1to
ve4to.
ve1t6o7e
9ve1to3ke
vet6ok
9vetoks.
ve4tor
2v2e1tr4
1ve4ts1
vett5sk
ve6tt
vet6ts
6v2e3t2v
4v2e1ty
vet4y8e
2ve1u
ve8um2
2veva
ve1v
ve6vak
ve5van
ve9var
2vevi
vevi8sa
vev4s1m
ve8v1s
vev4sp2
2ve1ø4
2ve1å6
1vê
2v1f
vfa2l1l4
v1fa
v5fal
vfø7re.
v1fø
vf8ør
vfø1re
2v1g2
v4ga.
v1ga
vga4le.
vg2a1le
vga6li
vga8ve.
vg2a1ve
v9ge
v4gi.
v1gi
vgje4r6s7
v1gj
v3gjer
vgj2e
v1go4
v4gå.
v1gå
2v1h
vhø4re.
vhø1re
1via
vi1an
vi6bl2
vi1b2
vice5s
1vid
vi5de
vide4o7
vi4d5o4
vid3r4
vi8d3s4
vi1el
v2ie
vi5e6nd
vi1en
vi2g4m
v2ig
vi4gu
vi5gø
vi4kat
v2ik
vi1k2a
5vi6k1b4
vi5ked4
vi1ke
7vi4k5g2
vik5ke
vi2kk
vi2k5n2
vi2k3o6
3viks.
vik5sa
vik5s6l4
4vi6k1t
5vik1ti
vi4k5ø4
vi2l1a
vi5la.
vi4le1s4
vi1le
vi4l3in
vi1li
1vi2lj
vil4l5an
vi2l1l
vil4led
vil1le
ville6de.
ville1de
vil4lei
vil2l9e1p
6v5illu
vi2l5m
vil1o
vil4s1ti
vils2t
vi8l1s
vilt7o
vi2l1t
vil6t1ret
vilt2r4
vil4tri
vi2lø
4vim
vi8m7p2
v4i1m7u
4vin4d1l4
vi6nd
5vin1du
vine5st
v4i1ne
v6i6ng
4ving7n
vin5g2om.
vin8g7o8m
vin1go
vin2g3r4
v6in8g9u
vin3na
vi4nn
vin4ned
vin1ne
6vin4n1h2
4v5in4n1l
4v5in6n1s4
4v1in6n7t4
vi6n5o6ve2r
v2i1no
vino1v
vino3ve
v2i6n2s
vin7sja
v6in1sj
vin4t4es
vi6n1t
vin1te
vin5tre
vint2r4
vi8pa.
vi1pa
vi6pe.
vi1pe
vi2p5s
1v2ir
4viro
7vi1ru
vis3ab
vi1sa
vis3ak
vis5a6nd
vi4se3e2
vi1se
vi3sen
vi6se6ng
vi2s1k
visk6re
vis1kr6
vis5m
vi1so
vis3om
vi4s1p2
vis9pa
viss4p2
vi4s1s
vis5ti
vi4tak
vi1ta
vi5t2e1se
vi2t4e1s
vi1te
5vi6t5j
vi4t3o
vi4tr4
vit7ra
vit5re
vit5skr6
vi4ts
vit6t4e6s7
vi6tt
vit1te
vi4t5un
vi1tu
vitun6ge.
vitu6ng
vitun1ge
vi4t5øy4
vi1tø
vi4va.
v4i1va
vi6v4es
vi1ve
vi1vi3
v1ja
v1j2e
vje4t5a
vj2e5t6e
vj2e4t5r4
vje6t3t
vj2e4tu
v1ji
4v5j2o
vju3t
6v3k6
vk4le
vkl4
vk4ler
vku4le.
vku1le
6v1l2
v6la1b
vl4a6ga
v9led
v1le
v9leg
vle2i9e
vl6el
vle4se.
vl2e1se
v5les1n
vle5str4
v6les1v2
vli7v
v1li
v8l1s9
vl4u9sa5
2v1m
vma8le.
v1ma
vm2a1le
vmo8de.
v1mo
v5mo1d4e
vm2o1d
vmo4rd4
vmå8la
v1må
vm6ål
2v1n
v3nad
v1na
vna8ke.
v2nak
vn2a1ke
vn5al
v9nar
vn5dy
v6nd
v2n2e7b2
v1ne
v2ned
vne7de
v4ne1le
v4n2e1li
v4n2e1lo
v2nem
vne5r6e
v4n2ero
vne1s
v4nesta
v6nes1te
v2ne1v
vnor9s8kas
v1no
vn2o4r2s
vnor1ska
vn4s5pa
v6ns
vnsp2
vn1s4t
v6n5t4
vnæ4re.
v1næ1
vnæ1re
vn5ør
v1nø
vo1al
v2oa
vo2ar
6vob
2vof
vof8fan
vo2ff
vof1fa
vo4gu
v2og
voi1
1vok
4vo3ko
vok6s5
voks3k
1vol
v2o1li7
vo4li1tu
vol4t4es
vo2l1t
vol1te
2vom
vo4na
v2on
vo9nal
vo9nar
von5de
vo6nd
vo6ns6
von5s1h
2v1o6p
2vo4rd
vor8da.
vor1da
v8o1re
vo9ren
2v1o2r1g
4v4oro
vors7k
v2o4rs
1v2os
4vo1sj
vos4se
vo4s1s
vo4teg
v2ot
vo1te
vo4tel
6vo1tr4
vo1v
vo4ve.
vo6vi.
v2o1vi
2v1p4
2vr8
3v6rak
v7rar
v1re
v1re1gi3
6vren1ge
vre6ng
4vren1gi
v6ren1gj
v6ren2g1t
vret6t4s
vre6tt
v6ri.
3vrid
7vr2ie
v6rigas
vr2ig
vri1ga
vri6ma.
v4r4i1ma
vri8me.
vri1me
v3ri6ng
v2ri6n5s
vri6ve.
vri1ve
v1ro
v4rob
v4rof
v4rok
v4rop
vro8te.
vr2ot
vro1te
v1ru
v7ry
vrø3ve
v1rå
vrå8da.
vr6å1d
vrå1da
v6sa.
v7s6a1li
v6se.
v1se
v4seg
vs3e4g1g
v3s4el
v4s5e4li
vse4n
v5s2en.
v4s5e6ng
v5s4e6ns
v2ser
v6si.
v1si
vs1in
v1sk
v2s8ke
v6s5kum
v2s1kv2
v1sl4
vs3lan
vs6let
vs1le
v6sl2ik
vs1li
vsmå8la
vs1m
vs1må
vsm6ål
v1s2n
v6s7na
vs3ne
vs3ny
v2s3næ1
v4s3nø
v1s5o1d
vsom5
vs5o6ms4
v7s2on
vs1or
vs2pe
vsp2
vsre6de.
v8s9r
vsre1de
vs7s6t
v4s1s
vs6s5å
vste6ma.
vs2tem
vste1ma
vs3t4il
vs1ti
vs1v2
vsø6ke.
v1sø
v3s6ø1ke
v3s4øl5
v2s5øy
vsøy4er
v8sø4y1e
v8så.
vså7re
vså2r
2v1t
vta1
v4ta.
vt4a8ka.
vta1ka
vta6led
v3t2a1le
vta9len
vta4le5v
vta6s
v5te2pp
v1te
v2t2e1p
v5t4i
vt6i8na
vti8ne.
vt4i1ne
v5t4r4
v3t2v
vt4y8e
1vu
v2u2d1
vu6d5d
vude6n5t
vu1de
vu8ds4
8vué
vu8k
2vul
2vu6ng
vun4ge.
vun1ge
6vu6o
2v1ut
2v1v
vva8la
vve8g9ing.
vve1gi
vvegi6ng
vve6g7in1ga
vven7n6i
v1ven
vve4nn
vvi5ke
vv2ik
vvi6k7l4
vvi8s9an
vvi1sa
vvi4se.
vvi1se
2vy1
vya4
vy7e6ns4
v4y1e
vye2n
1væ
væ1ra
væ9ret
væ1re
væ8ta.
væ5ta
v3ø4d
vø4l
vø7li
vøm4met6
vø2m1m4
vøm1me
3vø1re
vø6re.
v3ør1ke
vø2r1k
vør8na.
vø2rn
vør3na
vø3se.
vø1se
v5øv
4v1øy
vø4y6e
vøy7ene.
vøye4n
vøy7e6ne
vøy9enes
1vå1a
1v4å1e
4våe6nd
våe2n
v4åk
vå8ka.
vå1ka
vå4ke.
vå1ke
vå4ke3ne
vå3ken
vå9kene.
vå4le.
vå1le
1vån
vå5ne
4vå1nu
vå3ren
vå8sa.
vås2
vå1sa
vå5t4es
vå1te
6vå6t1f
våt7å
w2a
wa6l
5wa6ld
wa6l4k
walk5o4
wa2l4l
2wap
war6d7er
wa4rd
war1de
was2h3
1wat
wa3z
w1b
wbo6y5
wb2o
we2
wea1te7
w2ea
w2e7b2
we3g
we5re
wes2
we4s3s
wester6
wes1te
west2e2rn7
wet5
we6tt4
w4i2
wi9ar.
wich3
3wic6z
wi3d
5wi6en
w2ie
wi9er.
w2i5f
wi3ni
wi5ra
w2ir
wi3ren
wi1re
wi5ta
wk3r6
w1l
w1m
w6n5s
wob5
wou6
wout7
ws5
ws6k
wu4rs6
wy2
w1yo
wy1o2r2
wyo2r1k3
w1z
xa6n5t
x7b
x3f
x1ga6
x7h
x1ic
5x6id
x2i5e4
x5k
x1l
x1p6
x3r
x1s2
x7t
x6u
xy2
ya7b
1y8ac
ya5f
ya4h
y1ak
yak6te.
ya6k1t
yak1te
ya2l
y7am
y1an
y6an.
y2a1na
ya4ne.
ya1ne
ya2n7k
yan7sl4
ya6ns
y1a2r1k
y7a2r1r
yas4
ya5si
ya5t
y1av
y1b
yba4ne.
y1b4a
yba1ne
yb4bed
y2b1b
yb1be
yb1de3
y2b1d
yb2o2
ybu6er
y1b2u
ybue7re
ybu7e2rn
ybyg5
y1by
y1c
y8ce.
y8cé
y2co3
y6dab
y1da
y2d5av
4y6d1d
yd6d2e1la
yd1de
yd4d2e1p
y6d7e4n1h2
y1de
y4de3o6
y2d2e1p
y4d2ero
yd2er
y2d1is
y1di
yd6j2e
yd1ji
y1do4
y2d3op
y2d5ov
y1dr4
y4d4r5au
yd1re
y4d5rem
y4dro
y4d4r5ok7s
yd3s1i
y8ds
yds4v2
y2du
yd1un
y1dø4
y4døm
yd2ør5
y2d1øs
4y1e
ye4d8l4
ye6dre
ye1dr4
y2ek
y5eks
y4e4len
ye1le
ye6let
yel4s5j
ye8l1s
yel4sk
yel2s3m
yel6s5t
y2em
ye2n
y7e6ne
y8ener
y2e4n1h2
ye5ni
y2e2nk
y2e4n1l
yenle6ge.
yen5le
yen3le1ge
ye6ns4
y4en1se
y3e6re
ye4rel
y3e2rn
yer8sk
ye4rs
y4er1s2t
yes2
ye5s1m
ye2t
y2e9te
y2e5ty
y2e1v
y1f
yfan9
y1fa
yfje2l1l4
y1fj
yfj2e
yf2jel
y4f1le
yf2l2
yf5le.
y8ga.
y1ga
yga8ve.
yg2a1ve
yg4dal
y8g1d
yg1da
yg4dek
yg1de
yg4del
yg4d2e1p
yg2do
yg2d1r4
yg2d1y
yg2dø
y2ge5i
y1ge
yge6n5s
yg2en
y6gere4t
yg2er
yge1re
y2g6es
y4g2e1v
yg6gam
y4g1g
yg1ga
ygg7a6r1t
ygg6es4
yg1ge
yg5gj
yg2g5l
yg6gr4
yg8g3s2
yg6gu
yg6g5å
y1gi2
y5glo
y2g7m
y1g6r4
y1h
yhes5
y1i
y9in.
yis7t
y1j
6yk
y1ka
ykap3
y6ka1ra
y2ke
y7k6el.
y5k4elen
yke1le
y3k2e1li
y7ke8l1s4
y3ken
y3ker
y4kerel
yke1re
y4k2ero
y5ket.
y9ke4ts2
y1ki
y1kj2
y4kjar
y5kja1re
ykjeva8ne.
ykj2e
yk4jev6a
ykje1v
ykjev4a1ne
yk4ka1na
y2kk
yk1ka
yk5k6e4n1h2
yk1ke
yk5ke1sj
yk4ke6s5ta
yk1kj2
ykk5ni
yk4k1n2
ykk4s5k
ykk7s
yk8k5vi6
ykk1v2
yk4ky6
yk1la
ykl4
yk6lest
yk1le
y1klo
y3k2lub
yk3lus
y5k2læ1
y1ko
y2k2oa
y6k2o1li
y4k2o1mo
y1kom
y5ko1sa
y1k2os
y2k2ot
y5ko5t4i
y1kr6
yk1s
yku6le.
yku1le
yk3var
ykv2
y1la
yl4a8ga
yl4dan
y6ld
yl1da
yl4de2r5k
yl3d2er
yl1de
ylde4s
yld1r4
yld5s6k
yl8ds
yle6ge.
y1le
y3le1ge
y7les
y4le1v
yli6g1h9
y1li
y3l2ig
y6l5k8
yl1k4e2
ylke4s3
ylkesl4a8ga
ylke1sla
ylkesl4
yl4le1v
y2l1l
yl1le
yll5is.
yl1li
y8l3s6
ylst7re
ylstr4
yl5tet
y2l1t
yl1te
y8l2u1se
3yl1v4en
y8lv
y1ly
ylæ7re
y1læ1
y6mei
y1me
y4m2e3te
ym4fe5d6
y2m1f
ym1fe
ym5ja1ra
y4mo.
y1mo
y3m6o5e
y8m3p2
y6m1s
ym8sa.
ym4se
ym8sl4
ym6ta.
y2m1t
ym3ta
ym6te.
ym1te
ymå6la
y1må
ym6ål
y9n8a6nd
y1na
ynde1s8
y6nd
yn1de
yn5det
y2nel
y1ne
yn7e6ld
y4n2e2r3g
yne5s4
yne3si8
y6ne1v
yn5gel
y6ng
yn1ge
yn4gem
yn2gr4
yn4g5ø
yn3ko3
y2nk
yn6k5v2
yn5na
y4nn
yn5ne
yn6n3s4
yn1n4ø
y3no
y4no.
y6ns1
yns6a1re
yn4sem
yn1se
yn9set
yn2si
yn2sk
yns9ket.
yn2s1ke
yn1s7ki
yn5skj2
yn6s7kje1le
ynskj2e
ynsk2jel
yn8s9kjer.
yn4st
yn2su
yn4s5ver
yn2s3ve
ynsv2
yn4tap
y6n1t
yn8teg
yn1te
yn2t5ei
yn4tek
yn6t2e3te
yo4g2at
y2og
yo1ga
yo6gi.
yo1gi
y1om
y2on4
yon5n4en
yo4nn
yon1ne
yo6nu
y1o2p
y1o2r
yor6da
yo4rd
y8o5re
you4
yout5
y1p8e
y2pe.
y4pe3e6
y4p2e1na
y4pe3net
ype1ne
ype4rel
ype1re
y6pe1ta
y1pi
y4pi.
y7pi6ng
yp5i4nn
ypin8na
y1p2l6
y5po
yp7p6a
y2pp
yp3pe
y1pr6
y2p5s8
yp4t6ok
y2p1t
y3pu
y1ra
yra8ka
y4r3a6l
y7r1a2r1b8
y4r5au
y2r5av
yr7da
y4rd
yr5dø
y7real
yr2ea
yre3in
y5r2eis
yrek4
y7r2e2k7k
y3re8l1s
y5re6m1s
y2rem
y1ren
yre9ne
y4re3o6
y6rerel
y1rer
yre1re
y4r2ero
yre5s3c
y5re6st.
yre5s1te
y5re6s5te.
yr6es6ten
y1ret
y4re1ta
y4r2e1te
y4r2e1to
y7re1tø
yr7ga
y2r1g
y1ri
yri6n5g6
yri6ene.
yri1e4n
yr2ie
yrie1ne
y4r2if
yri8ka.
yr2ik
yri1k2a
y4r3i4nn
yris6p2
yr4ke3e2
y2r1k
yr1ke
yrke4s
yr5kj2
y2r3m
y7r6o1e
y3r2o2m1m4
yr1op
y5rosk
yr2os
y2r5r8
yr4san
y4rs
yr1sa
yr7set
yr1se
yr5sk
yr6skj2
yr6skr6
yr2sp2
yrs7tan
yr1s2t
yrs5tar
yrs7te1ne
yrs1te
yrs5te6n6s
yr3s1ti
yr4sv2
yr3te
y6r1t
yr6tek
yr2t3r4
yr3t8ra
yrty8
y1ru
yrul8la.
yr2u2l1l
y6r5ut5r4
y2r3v
y1ry
yr3øk
yrøy4
y1rå1
yr6å1d2
yrå8da
yråd2e7r
yrå1de
yrå6de4rs
y4rå7di
y2r6åm
y6r4å1s4t
yrå1s2
y4råt
y4rå1v
2ys
y1sa
ys6a7ke4rs
y3s4a3ke
y1s4e
yse4b2u
y2s2e1b2
y4sed
y4se3e2
yse6e3i
y2seg
y2sek
ys7ek1te
yse6k1t
y2sem
y4s2e3ra
yse7rid
ys2e1ri
yser2i7e6
y4se3sj
y4sesk
y4se5s6l4
y4se1st
y5ses1te
y6se1su
y9s2et.
y4se1u
y1s6i
ys2i8e
y5s2j1k
y1sj
y7s6kag
y1ska
ys6ka1ra
y2sk2ar
ys7ka1re
ysk9au
y2s2k3l4
ysk5øs4
ys4mi
ys1m
ys4nø
ys1n
y1so
y4s5or
ys2pe
ysp2
ys5pis
y1spi
y5s4p6o
ys6sak
y4s1s
ys6s2am
ys4s3ei
ys1se
ys4s3ek
ys4s5il
ys1si
ys2sj
ys4sku
ys2so
ys2sp2
yss5pe
ys4sta
yss2t
yss5tab
ys4s5ti
ys4su
ys2s5v2
ys2t
ys3ta
y8s1t4ar.
y1s4tat
ys3tel
ys1te
ys3ten
y4s3ter
ysteri1e7n
yste1ri
yster2ie
ys7tesl4
y2st4es
y2s3ti
yst3op
yst3r4
y2s5t6ry
y1s6ty
y1su
ys1ve
ysv2
ys5å2r
y1ta
y4t3a2na
y2te3e4
y1te
y4te1f
y2teg
yt5e4ge
yte3in
y4t4e1ka
y4te1ki
y4t2e1na
y4t2e1no
y2te3o6
y4tere4t
yte1re
yt2e6ro
yte4rs6
yter5s1ke
yter1sk
yt2e5r6ø
y2t4es2
y2tet
y3t2hi
y2t1h
6y5t4il
y1ti
y6tj
yt9ja
y1t5jen
ytj2e
y2t9jer
yt4mei
y2t1m
yt1me
yt4mes
y3to
yt8ra
ytr4
yt4re.
yt5rer
yt1ri
yt4r2ik
y5trå
yt4s3en
y4ts
yt1se
yt3sk
yt5s1v2
4y6tt
yt5t6a
yt1t4e
ytte8r9e6nd
ytte1re
ytt4es6
yt4test
yt8tien.
ytt2i3e2
yt1ti
ytti1en
yttsa6me.
yt6ts
ytt1s2am
yttsa1me
2y3tu
y1ty
yt4y2s
y7tå8
y1u
y6ua
y8ue
yu8g
yu4l
y1v
y4vak
yva8la
y2ve2d1
yve4ri3a
yve1ri
yve4ris
y4v2e1se
yv4es
y6v2e3sy
y6ve1v
yvi8sa
yvi6se.
yvi1se
yv1år
yvå5te
y1w
y5æ
y5ø
y1å2
za5b
3z2a1e2
4zaes
za4g5
za5k6h
za1ni4
zania7
5za5v
z1b4
z1b2u4
z1c
z1d
1ze
z2ea1
ze1b2u9
z2e1b2
z2e1li5
ze6n3s
4z3en1se
4z3e6n1t
z1f
z1g
5zh2a
z3hi
z5hu
5z2ie
1zi1fi
z2if
5zi6ng
z5i6n1t
1zis
6zi1sty
4zi5s4v2
2zi1sy
z1k
4z1l
z1m
5zo.
5z6o1e
zo1f2
zo4no
z2on
z8o5re
6z2os
z1p
z5r
z1s
4z1t
zu3e
z1un
z1v
z1za.
z2z3el
z1ze
æ5by
æ2b4ø
æ5de
æ7di
æ1f
æg6
æ2k
æ3ke
æ5ki
æ8kj2
æk1ja
æk7je.
ækj2e
æk9jer.
æk1ke7
æ2kk
ækkel6
æk6l4
æ1la
æ4le.
æ1le
æ2le3d
æ4l2e1na
æ4le1v
æ2l5j
æls1le9
æ8l1s
æl2sl4
æ4r1ak
æ3r4a1ne
æ4ra6ns
æ4r1a2r1b8
æ6rar1be
ær4ar3te
æ2r1a6r5t
æ4r1at
ærbu5e
æ2r1b8
ær1b2u
æ4r1d4
æ1re
æ2r2ea
æ4red
æ2re1f
æ2reg
æ8r7ei
æ2re2i7e
æ2rek
æ6r7e6ld
ære5ne.
ære1ne
ære7nes
æ2r2e1p
æ5r2e2rn
æ1rer
ære1s2a4m
æ2re1sa
ære6skr6
æ2resk
æ6re4sl4
ære4s2p2
æ4re1ta
æ4retek
ær2e1te
æ9re4ts1
æ2re1v
4æ2r1f
ærg2en5
æ2r1g
ær1ge
ærhø8
æ2r1h
æ1ri
æri6e7ne
æri1e4n
ær2ie
æ4r3il
æ2r3i6n1s
æri6s
ær5is.
4æ2r1k2
ærle6ge.
æ2r1l
ær1le
ær3le1ge
4æ2r1m
ærmå8la
ærm6ål4
ær1må
æ2r3n
ær5ne4
ær4nå
æ2r7o6
ærom5
æro6ms4
æ2r5r
ær6s2el
æ4rs
ær1se
ær4s5il
ær1si
ær1sk
ær7s6no
ærs1n
ær2sp2
æ2r1u
ærut5
æ2r3v
æ2r1ø4
ærø4y7e
ær3å4
æ8se.
æ1se
æ2sj
æ2s1k
æ5ta
4æ1te
æ4te.
æt4r4
æt4ta
æ6tt
æ5va
ævar5
æv4e4s
æ5vi
6ø1a2
øa7re
øau4
ø1b
øbe6le1v
ø1be
øbe1le
øb2e4li
ø2bl2
øb6l9u
øb4r8
ød3ag
ø1da
ø4dak
ø6d3d
ød9de.
ød1de
ø2de.
ø1de
ø4de1de
ø6de1f
ø4d2e1la
ø4dem
ø4de3o6
ø4de1ri
ød2er
ø4d2ero
ø5dj
ød1r4
ø2d4red
ød4rek
ød4r2e1p
ød2sc
ø8ds
ød6s5ek
ød1se
ød2s7ke
øds4ko
ød6sku6
øds4mu
øds1m
ød8t5om
ø4d1t
ød7to
ødt6r4
ø2du
ød3u6nd
ø4d5ur
ø6d5ø
4ø1e2
ø4ed
ø5e1ne
ø5e1p
ø3e6re
ø1fe8
øf3fe
ø2ff
ø1f6j
øf8la.
øf2l2
øf8le.
øf1le
øff2e8l7a
ø5fn6
øft4e5s
ø2ft
øf1te
øg1
ø1g2a
ø6g5ak
øg5al
ø7gar
ø5gas
ø3g2er
ø1ge
øg5gl
ø4g1g
øg8gå
ø7g4j
øglo8ve
ø6g1lov
øgn6s7p2
øg1n
øg6n1s
ø2g2r4
øg5re.
øg5r2es.
ø8gs2
øg4s1t6e
øg4str4
øg3ta5
ø2g1t
4ø1g4u
ø1h
ø4i
ø2i4e
ø5i6ng
ø5isk
ø4it
ø1j
6øk.
6ø1ka
ø5kav
øka8ve.
øk2a1ve
ø6ke5h
ø1ke
ø4k2e1lo
ø6ker6a3d2a
øk2e1ra
ø6kerel
øke1re
ø4k2e5ru
øke1s
ø6k2e1se
ø8kesl4
ø4kest
ø4k2e3te
ø1ki
4økj2
ø5kja1re
ø5kjas
ø1kj2e
2ø2kk
øk5kel
øk1ke
øk5ket
øk4k5l4
økk5r6
øk3lag
økl4
øk3lan
2øk1na
ø2k1n2
1øko
øk5o2pp
ø1kr6
ø8krar
øk8sa.
øk1sa
øk6se.
øk1se
øk4ses
øk4si
øk2so
øks1t6
øks5ti
øku4r
øk5ur.
øk5u1re
øk7ve
økv2
ø7ky
ø1la
ø2la1h
ø6lam
ø6l3d
ø1le
ø4le1di
ø6le1lu
ø4l2ero
ø6le1ta
ø4l2e5te
ø2le1v
øl4g2e1ra
ø2lg
øl1ge
ølg2er
ølge5s6v2
ølg6es
ø2l1j
øl2k4e
ø6l1k
ølke7s
øl4l2e1se
ø2l1l
øll6es
øl1le
øl9l2e3ti
øl6le1v
ø1lo
ø2l7op
ølrå4
ø2l5r4
øl6s2el
ø8l1s
øl1se
øl4s2e5r4e
øl4se3s
øls6t4
øl5s4v2
ø5luk
øl7ut
øl1va6
ø8lv
øl4var
øl4v3ei
øl6vek
øl8vel
ølv7e2r1k
øl4v3in
øl1vi
ølv5o
øl2v7r8
ø1ly
ø4me.
ø1me
ø9met
1øm1fi
ø2m1f
ø5mi
ømi1ni6
øm4med
ø2m1m4
øm1me
øm4m2ero
ømmet6
øm4m2e1tr4
ø2m3op
ø1mo
øm4p6el
ø8m1p
øm1pe
øm7s6p2
ø6ms
øm1st
øm5svi
øms1v2
ø2m1u
ømå8la
ø1må
øm6ål
øn2ad
ø1na
ø6n5al
ø5nas
ø2nem
ø1ne
ø3n2es.
ø9n2e1se
ønhø8re.
ø4n1h2
ønhø1re
4ø4nn
øn4nal
øn1na
øn1ni4
øn4n5it
ønns5al
øn6ns
ønn4s3e
ønn4s5i4d
ønn1si
ønns5kj2e
ønnskj2
ønns5kre
ønnskr6
ønns5ku
ønns3l4
ønns3t6
øn2nø
ø6nom
ø1no
ø6n2s
øn9sa.
øn5sak
øn5s2am
øn5se.
øn1se
øn3ser
øn3skj2
øn8skjer.
ønskj2e
ønst3r4
ønst9ra
ø6n1t1
øn2ta
øn2to
ø1n7u8
ø1o
ø4pe7d6a
ø1pe
ø6pe5i
ø4p2e1nu
ø5p2ero
ø4p2eru
ø2pe1s4
ø4p2e3te
ø4p2e3ti
ø1pi
øp3li
øpl6
ø2p5p
øp6p1l6
ø1pr6
ø2p6s1
øpsa4
øpsl4a8ga
øpsl4
øps8leg
øp2s1le
øp5s1te
øpst2
øp4s5ø
ø1ra
ø2ra.
ø2r1af
ø2r1ak
ø2r1a2m
ø4ra6ns
ø4r3a6n1t
ø4r4a8sa
øra6si
ø4r7au
ør1d4e
ø4rd
ø1re
ø2r2ea
ør7e6d1d
ø6re1du
ø2re1f
ø2reg
ø6rein
ø2rek
øre1k6l4
ør3eks
ør5e6k1t
ørel2e8se
ø2r2e1le
ø9relet
ø4r5e6ng
ø2re5o6
ø2r2e1p
ø6rere4t
ø1rer
øre1re
ø4r2eru
øre1s2
ø5r2es.
ø4r2e3se
ø6re1si
ø7resk2ri1vi
ø2resk
øre1skr6
øre3s6kriv
ø8r4e1so
ø4rest
øre6s7v2
ør6et
ø4re1ta
ø2r1e1u
ø2re1v
ør5fe
ø2r1f
ørg6e5s
ø2r1g
ør1ge
ø1ri
øri8m
ør4jet
ør1j
ørj2e
ør4ke5ri
ø2r1k
ør1ke
ør4kes
ør8k9lag
ørkl4
ør6k5n2
ør4kve
ørkv2
ør4kå
ørl4a8ga
ø2r1l
ør2m9ut
ø2r1m
ør1mu
ør1n4e
ø2rn
ør4ne1re
ør4ne1s4
ør4n3u4
ør4n5ø
ør1o
ø5r6ok
ø1r2os
ø4r3ost
øro4v
ør4rek
ø2r1r
ørr6e
ør4r2e1p
ør3ri4
ør4r5is
ør4råt
ør4sak
ø4rs
ør1sa
ørsa8ka
ørsa6me.
ør5s2am
ørsa1me
ør4sc
ør6ses
ør1se
ør2si
ør5ski
ør4skr6
ørs6le1v
ørsl4
ørs1le
ør4som
ør1so
ør4s1pe
ør1sp2
ør2s2t
ørs9tar
ørs2t4e5i
ørs1te
ørs5te6n6s
ørs5t2ig
ørs1ti
ør3sto
ørs9u
ør2sv2
ørs1å
ør9tar
ø6r1t
ør4tek
ør1te
ør6ti9a
ør1ti
ør4ti1de
ør3tid
ør8tien.
ørti5en
ørt2i1e2
ør6t2if
ør3to
ør4um5
ø6rut
ø2r5v
ør1ø2s
ørø1v
ø2r1åp
ø1sa
øsa6me.
ø1s2am
øsa1me
ø8s2arar
ø1sa1ra
ø1sc
ø2se.
ø1se
ø2sem
øs2e4n5o
øs2e1ri7
øser2ie6
øs5j2o
ø1sj
ø2s7k2ar
ø1ska
øs1l4
øs4lag
ø4slu
øs2me
øs1m
ø1s2p2
ø1s8tas
ø4s5t9ast
øs6teg
øs1te
øs5ter1se
øste4rs
øst9e6tt
ø4stet
østo2
ø1s2t5of
øs2t5ov
øst1r4
øst3re
ø1stø4
ø2s1ø2
4øt
øt3ak
ø9tar
øta4s
ø4t5a1sa
ø2te.
ø1te
ø2t2ea
ø6te1f
ø6te1ge
ø2tei
ø2t5eks
ø2tel
ø5te1la.
ø2t2e3la
ø3ten
ø4te1ni
ø3ter.
øt2e3ru
ø2t4e1s
ø2te7s1n
ø4te1ta
øt7ri
øtr4
øt1sa
ø4ts
øt3s4p2
øt7s6å
øtså9re
øtså2r
øt4t2ero
ø6tt
øt1te
øt6test
øtt4es
øt4t5av
øt1ta
øtt5eks
øt2tek
øt4t5e1ta
øt4t5e3te
øtt5ra
øttr4
øtt7ri
øt6t1s2
øttså7re
øtts1å
øttså2r
øt4t2ur
øt4ty
ø1tu
ø5ty
ø1u2
øug8l2a
øu4gl
øv5aa
øv5a6ns
øve5in
ø4vek
øv2e5no
ø1ven
øveren8
øve1re
øv4e1s
øves4t
øv2e3te
øv4et
ø5v2ik
øv6l7ut
ø6v1l2
øv7ne
ø2v1n
øv6nø
øv9o8
ø2v1r8
øv5r2e1b2
øv1re
ø8v1s
øv9sa
ø5vå
øy3a2l
øy1a2n
7øy6an.
9øy8a6ns
øy4dek
øy1de
øy4de1re
øyd2er
øy1d8i
5øy2e1b2
ø4y1e
øy7e4de
øy2e3e2
øy4e1le
øye4n
øy4e1p
øy4e2r1f
øy1gl
øy3ke.
ø6yk
øy2ke
øyk1s4
øy6k5t
2øyl
øy4led
øy1le
øy4leg
øy5na
øy5ni
øy3o
6øyp
øy3pe.
øy1p8e
øy4p6el
øy4ra.
øy1ra
øy5rer.
øy1rer
øyri6v
øy1ri
øy3rø
ø2y1s
øy5s4ar
øy1sa
øy5s6i
øys2l4
øy4s1m
øy4spi
øysp2
5øysu6nd
øy1su
øy4tei
øy1te
øy4tel
øy4te1re
øy2t4e5s6
øyt2i7da
øy1ti
øy3tid
øy1tr4
øy2t3y6
øy5tø
øy4vei
øy1v
5øyvå
ø1ø2
ø5å6
å1a
åak6ta
åa6k1t
åa4n5
åan8ka.
åa2nk
åan1ka
åan6ke.
åan1ke
å2ar
å5aran
åa1ra
åau4re
å1b4
åba8ne.
å1b4a
åba1ne
åb4er2
å1be
åbo9ta
åb2o
å1b2ot
6å1d
å7dan
å1da
å9dar
å4ded
å1de
å4dek
å4dem
å4de3o6
å4d2e1p
å4de1re
åd2er
å4de1ri
å6d2ero
å2de1s
å7di
åd4ra
ådr4
åd8re
å8ds1
åd2sk
åd3s4la
åds1l4
åd7slo
åd3slå
åd1s2n
åd3s4pe
åds1p2
åd7s1pu
åd5st4
åd6s7te
4å1e
å2e1le
åem8na
åe2m1n
åe2n
å7e6ns
å3er
å3e6re.
åe1re
å1f
å1fe6
åfø5re
å1fø
åf8ør
2åg
å4ga.
å1ga
åg7a6ld
å4ge.
å1ge
å2g7ei
å2g6es
åg1na5
åg1n
åg5n4e
å1g4r4
åg5rin
ågs6k
å8gs1
åg5sl4
åg7sp2
ågs4t
ågu4l
å1gu
åg5ø
å4gå.
å1gå
å1h
å1i
å1j
å1ka
å3kan
å3kar
å4k1a2r1b8
å2k7av
å4ke5h
å1ke
å3ken
å4k2e1na
å5k6e4n1h2
å4ke1re
åke5s
å4kesl4
å5kevi
å2ke1v
å1ki
å6kid
å1kj2
å2k1k
åk5ka
åkk6l4
åk7kr6
åk7lau
åkl4
åklist7
åk1li
åk3læ1
å1ko
åk3o2pp
å1k2o4s
åk6ra
åkr6
åk5røk
åk3rå
åk1s2
åks7l4
å6k3t4
åku8
å5k8ul
åkv8a8la.
åkv2
åkøy8r4ar.
å1k6øyr
åkøy1ra
å2k3å
å1la
å5l6a8m1p
å6l1a2r1b8
å9las
ål5au8
å2l9av
å6l3d
å2le1f
å1le
ål5e4i1ni
å2lein
å2lek
å2lem
å4l5e4n1h2
å4le5ni
åle6ris
åle1ri
å2le1s2
åle9s6u6nd
åle1su
å4l2e1te
å2le1v
ål1gå6
å2lg
å6l4io
å1li
å2l5j
åll4a6ga
å2l1l
ål3or
ål4san
å8l1s
ål4sek
ål1se
ål4ser
ål1s7i
ål2sp2
ål4s1ti
ålsy8na
ål1sy
åls2yn
ål5ti
å2l1t
ål3ti5d
ål1u
ålul8la
ålu2l1l
å1ly
å2l1øy
å1lø
å4lø4y4e
ål5å6k4
ålå8te.
ålå1te
6åm
å6me.
å1me
åm4li.
å4m3l
åm1l6i
åm1om
å1mo
å2m5øy
å1mø
å3nar
å1na
å9nas
ånd3r4
å6nd
ånd4sa
ån8ds
ånd4se
ånd4s1o2
å4ne.
å1ne
åned4
å5ne8ds5
å2ne5i6
å2nel
å4n4e1ly6
å5n2es.
å3net
å4n2e1t4r4
å3ni
å1n5o
å9ny
å3o
å1p2e
åp2e4n3a
åpe4n5i
åpe6n3s
å5per
å6p2ero
å1pi
åp6j
å1pla
åpl6
åp4ne.
å2p1n4
åp1ne
å2p7p
å1pr6
åpra5
å2p2s1
åp6ta
å2p1t
å4på
år3ak
å9ran
å7ra1ra
ård5str4
å4rd
år8ds
årdst4
å2re.
å5rei
å6rel
åre7s
å4rest
år1fi4
å2r1f
å7riv
å2r3k2
år4le.
å2r1l
år1le
2å2rn
år6n5s
år3on
år3op
år1po8
å2r1p
å4r2s
års5af
år1sa
års3el
år1se
år3sem
års3ko
år8sku
årsl4a8ga
årsl4
år3sol
år1so
års3ta
år1s2t
år7s1te
års3ti
år7sto
år5stu
års1u
år1s1v2
år5sy
å4r5u4k
år3un
år5y4
å1rø
å5r8å
ås2
å1sa
ås7au
å4se.
å1se
å4se3e2
å2s5eid
å4sek
å4sem
åser2i7e6
ås2e1ri
åse3s2
å4ses1m
å5s4e1so
å2se5t6a
ås3h
å8si.
å1si
å2s5ka.
å1ska
ås9ke3ne
å2s1ke
å1s8ki
å1skj2
ås7kop
å1skr6
å5sku
å1sl4
ås5ne
ås1n
ås3ni
å5so
å1sp2
å2s7pl6
ås5sa
å4s1s
ås5se
ås3si
åss3k
4å1st
å7s2t4ei
ås1te
åsu4
ås3un
å2s7u4t1
ås7væ
åsv2
ås5øy
å1sø
åså9
å1ta
å4ta.
åtaks5
åta2l
åta9la
å3t2a1le3
åta5le.
åta5len
å4ta6ng
å4ta2r1k
å4t5a2r1m
å2te.
å1te
å2t2ea
å4te3e4
å2t1ei
åte6ke.
åte1ke
å4te3o6
å9ter
å2t4es
5åtfe4rd
å6t1f
åt1fe
å5ti
åt2i7da
å3tid
2å8t1n
å1to
å2t7ov
6å6t3p2
åt1re
åtr4
åtri6pa.
åt2rip
åtri1pa
åtri8pe.
åt4ri1pe
åt1ru
åt5sk
å4ts
å6t1t
åt4ted
åt1te
åt4ti
ått3o
åt4tri
åttr4
åt6ts4
åt6t5æ6
åt1u
åt4un
åtva5ra
å2t1v
åtv4a
å8t9var
åtva7re
åt5ve
å5ty
å2t1øv
å3u
åun8ge.
åu6ng
åun1ge
å1v
åv4a
å2ve7d1
å4veg
åve5l
å4ve1ni
å1ven
å9v8er.
å2ve1v
å8v7s6
å7vy1
åvæ4re.
å1væ
åvæ1re
å1ø8
åøy4
.a8t8t9e8n9d8e.
.at3ten
.atte6nd
.atten1de
.b8e8t9r8e.
.b2e1tr4
.b8etre
PK
!<^8$hyphenation/hyph_pl.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.ć8
.ć3ć8
.ćł8
.ć1ń8
.ć1ś8
.ć1ź8
.ć1ż8
.ć1b8
.ć1c8
.ć1d8
.ć1f8
.ć1g8
.ćh8
.ćj8
.ć1k8
.ćl8
.ć1m8
.ć1n8
.ć1p8
.ćr8
.ć1s8
.ć1t8
.ćv8
.ćw8
.ćwie2r2ć3
.ćwi1
.ćwi2e1
.ćx8
.ć1z8
.ł8
.ł1ć8
.ł3ł8
.ł1ń8
.ł1ś8
.ł1ź8
.ł1ż8
.ł1b8
.ł1c8
.ł1d8
.ł1f8
.ł1g8
.ł1h8
.ł1j8
.ł1k8
.ł1l8
.ł1m8
.ł1n8
.ł1p8
.ł1r8
.ł1s8
.ł1t8
.łv8
.ł1w8
.łx8
.ł1z8
.ń8
.ń1ć8
.ń1ł8
.ń1ń8
.ń1ś8
.ń1ź8
.ń1ż8
.ń1b8
.ń1c8
.ń1d8
.ń1f8
.ń1g8
.ń1h8
.ń1j8
.ń1k8
.ń1l8
.ń1m8
.ń1n8
.ń1p8
.ń1r8
.ń1s8
.ń1t8
.ńv8
.ń1w8
.ńx8
.ń1z8
.ś8
.ść8
.śł8
.śń8
.ś1ś8
.ś1ź8
.ś1ż8
.ś1b8
.ś1c8
.ś1d8
.ś1f8
.ś1g8
.śh8
.śj8
.ś1k8
.śl8
.śm8
.śn8
.ś1p8
.śr8
.śró2d5
.śró1
.śródr2
.ś1s8
.ś1t8
.śv8
.św8
.światło3w2
.świ1
.świ2a1
.światło1
.śx8
.ś1z8
.ź8
.ź1ć8
.źł8
.źń8
.ź1ś8
.ź3ź8
.ź1ż8
.ź1b8
.ź1c8
.ź1d8
.ź2d4ź8
.ź1f8
.ź1g8
.źh8
.źj8
.ź1k8
.ź1l8
.ź1m8
.ź1n8
.ź1p8
.źr8
.ź1s8
.ź1t8
.źv8
.ź1w8
.źx8
.ź1z8
.ż8
.ż1ć8
.ż1ł8
.ż1ń8
.ż1ś8
.ż1ź8
.ż3ż8
.ż1b8
.ż1c8
.ż1d8
.ż1f8
.ż1g8
.żh8
.ż1j8
.ż1k8
.ż1l8
.ż1m8
.ż1n8
.ż1p8
.ż1r8
.ż1s8
.ż1t8
.żv8
.ż1w8
.żx8
.ż1z8
.a2b2s3t
.a1
.a2b1s
.a2d3
.a1d4a1
.ad4e1
.ad4i1
.ad4o1
.ad4u1
.ad4y1
.ad5a2p1t
.ad5i2u1
.ad5op
.ad5or
.ae3ro1
.ae2
.a1eroa2
.ae1roe2
.aeroi2
.aero1o2
.aerou2
.a1ntya2
.a2n1t
.anty1
.antye2
.antyi2
.antyo2
.antyu2
.arcy3ł2
.a2r1c
.arcy1
.arcy3b2
.arcy3b1z2
.arcy3k2
.arcy3m2
.a1rcya2
.arcye2
.arcyi2
.arcyo2
.arcyu2
.au3g2
.a2u1
.au3k2
.au3t2
.auto3c4h2
.auto1
.a1utoa2
.autoe2
.autoi2
.auto1o2
.autotra2n2s3
.autotra1
.au1tou2
.b8
.b1ć8
.bł8
.b1ń8
.b1ś8
.b1ź8
.b1ż8
.b3b8
.b1c8
.b1d8
.be2z3
.be1
.be3z4an
.beza1
.be3z4ec
.beze1
.be3z4ik
.bezi1
.bezc4h2
.be2z1c
.bezm2
.bezo2
.bezo2b1j
.bezw2
.bezzw2
.be4z3z
.b1f8
.b1g8
.bh8
.bj8
.b1k8
.bl8
.b1m8
.b1n8
.b1p8
.br8
.br4z8
.b1s8
.b1t8
.bv8
.bw8
.bx8
.b1z8
.c8
.c1ć8
.cł8
.c1ń8
.c1ś8
.c1ź8
.c1ż8
.cało3ś2
.ca1
.cało1
.cało3k2
.c1b8
.c3c8
.c1d8
.c1f8
.c1g8
.c4h8
.chr4z8
.c2h2r
.cienko3w2
.ci1
.ci2e1
.cie2n1k
.cienko1
.ciepło3kr2
.ciepło1
.cj8
.c1k8
.c1l8
.c1m8
.c1n8
.c1p8
.cr8
.c1s8
.c1t8
.cv8
.cw8
.cx8
.c4z8
.czarno3k2
.cza1
.cza2r1n
.czarno1
.c2z1k8
.cztere2c4h3
.c2z1t
.czte1
.czte1re1
.czterechse2t3
.cztere2c2h1s
.czterechse1
.cztero3ś2
.cztero1
.czwó2r3
.czwó1
.czwó3r4ą1
.czwó3r4ę1
.czwó3r4a1
.czwó3r4e1
.czwó3r4o1
.d8
.d1ć8
.dł8
.długo3tr2
.dłu1
.długo1
.długo3w2
.d1ń8
.d1ś8
.d4ź8
.d4ż8
.daleko3w2
.da1
.dale1
.daleko1
.d1b8
.d1c8
.d3d8
.de2z3
.de1
.deza2
.de3z4a3bil
.dezabi1
.de3z4a3wu1
.de3z4el
.de1ze1
.de3z4er
.de3z4y1
.dezo2
.d1f8
.d1g8
.dh8
.dj8
.d1k8
.dl8
.d1m8
.d1n8
.do3ć2
.do1
.do3ł2
.do3ś2
.do3ź2
.do3ż2
.do3b2
.do3c2
.do3d2
.do3f2
.do3g2
.do3h2
.do3k2
.do3l2
.do3m2
.do3p2
.do3r2
.do3s2
.do3t2
.do3w2
.do3z2
.do4ł3k
.do4k3t
.do4l3n
.do4m3k
.do4r3s
.do4w3c
.do5m4k2n
.dobr2
.dobr4z2
.doc4h2
.doc4z2
.dod4ź2
.dod4ż2
.dod4z2
.dogr4z2
.dopc4h2
.do2p1c
.dopr4z2
.do2r1ż2
.dor4z2
.dosc4h2
.dosm2
.dos4z2
.do2t1k2
.dotr2
.d1p8
.dr8
.drogo3w2
.dro1
.drogo1
.dr4z8
.d1s8
.d1t8
.dv8
.dw8
.dwó2j3
.dwó1
.dwó3j4ą1
.dwó3j4ę1
.dwó3j4a1
.dwó3j4e1
.dwó3j4o1
.dx8
.dy2s3
.dy1
.dy2z3
.dy3s4e1
.dy3s4o1
.dy3s4ta1
.dy3s4y1
.dy3s4z
.dy3z4e1
.dyzu2
.d4z8
.dziesięcio3ś2
.dzi1
.dzi2e1
.dziesi1
.dziesi2ę1
.dziesięci1
.dziesięci2o1
.dziewię2ć3
.dziewięćse2t3
.dzi1ewi1
.dziewi2ę1
.dziewię2ć1s
.dziewięćse1
.dziewięcio3ś2
.dziewięci1
.dziewięci2o1
.e2k2s3
.e1
.e2m3e2s5ze2t
.eme1
.emes4z
.emesze1
.e2s1e2s1ma1
.ese1
.e2s1ha1
.e2s1t
.egoa2
.ego1
.e1goe2
.egoi2
.ego1o2
.egou2
.eks4y1
.elektroa2
.e1le1
.ele2k1t
.elektro1
.e1le1ktroe2
.elektroi2
.elektro1o2
.elektrou2
.f8
.fć8
.fł8
.fń8
.fś8
.fź8
.fż8
.fb8
.f1c8
.fd8
.f3f8
.fg8
.fh8
.fj8
.f1k8
.fl8
.f1m8
.f1n8
.fp8
.fr8
.fs8
.ft8
.fv8
.fw8
.fx8
.fz8
.g8
.g1ć8
.gł8
.g1ń8
.g1ś8
.g1ź8
.g1ż8
.g1b8
.g1c8
.g1d8
.ge2o3
.ge1
.g1f8
.g3g8
.gh8
.gj8
.g1k8
.gl8
.g1m8
.gn8
.go2u3
.go1
.g1p8
.gr8
.grubo3w2
.gru1
.grubo1
.gr4z8
.g1s8
.g1t8
.gv8
.gw8
.gx8
.g1z8
.h8
.h1ć8
.h1ł8
.h1ń8
.h1ś8
.h1ź8
.h1ż8
.h1b8
.h1c8
.h1d8
.h1f8
.h1g8
.h3h8
.hipe2r3
.hi1
.hipe1
.hipe3r4o1
.hipera2
.hipe1re2
.h1j8
.h1k8
.h1l8
.h1m8
.h1n8
.h1p8
.h1r8
.h1s8
.h1t8
.hv8
.h1w8
.hx8
.h1z8
.i2n3
.i1
.i2s3l
.i3n4ic
.ini1
.i3n4o1
.i3n4u1
.i4n5o2k
.in4f3lan
.i2n1f
.infla1
.ino3w2
.izoa2
.izo1
.izoe2
.i1zoi2
.izo1o2
.izou2
.j8
.j1ć8
.j1ł8
.j1ń8
.j1ś8
.j1ź8
.j1ż8
.jadło3w2
.ja1
.jadło1
.j1b8
.j1c8
.j1d8
.j1f8
.j1g8
.j1h8
.j3j8
.j1k8
.j1l8
.j1m8
.j1n8
.j1p8
.j1r8
.j1s8
.j1t8
.jv8
.j1w8
.jx8
.j1z8
.k8
.k1ć8
.kł8
.k1ń8
.k1ś8
.k1ź8
.k1ż8
.k1b8
.k1c8
.k1d8
.k1f8
.k1g8
.kh8
.kilkuse2t3
.ki1
.ki2l1k
.kilku1
.kilkuse1
.kilkuseto2
.kj8
.k3k8
.kl8
.k1m8
.k1n8
.koło3w2
.ko1
.koło1
.kon2t2r3
.ko2n1t
.kon3tr4a1
.kon3tr4e1
.ko1ntro2
.kon3tr4o3l
.kon3tr4o3w
.kon3tr4y1
.kon4tr5a2gi1
.kon4tr5a2se1
.kon4tr5a2sy1
.kon4tr5a2ta1
.kon4tr5a2d1m
.kon4tr5a2k1c
.kon4tr5a2l1t
.kon4tr5a2r1g
.kontru2
.k1p8
.kr8
.krótko3tr2
.kró1
.kró2t1k
.krótko1
.krótko3w2
.kro2ć3
.kro1
.kr4z8
.k1s8
.k1t8
.kv8
.kw8
.kx8
.k1z8
.l8
.l1ć8
.l1ł8
.l1ń8
.l1ś8
.l1ź8
.l1ż8
.l1b8
.l1c8
.l1d8
.l1f8
.l1g8
.l1h8
.l1j8
.l1k8
.l3l8
.l1m8
.l1n8
.l1p8
.l1r8
.l1s8
.l1t8
.ludo3w2
.lu1
.ludo1
.lv8
.l1w8
.lx8
.l1z8
.m8
.m1ć8
.m1ł8
.m1ń8
.m1ś8
.m1ź8
.m1ż8
.m1b8
.m1c8
.m1d8
.m1f8
.m1g8
.m1h8
.mili3a2m1p
.mi1
.mili1
.mili2a1
.m1j8
.m1k8
.m1l8
.m3m8
.m1n8
.możno3w2
.mo1
.mo2ż1n
.możno1
.m1p8
.m1r8
.m1s8
.m1t8
.mv8
.m1w8
.mx8
.m1z8
.n8
.n1ć8
.n1ł8
.n1ń8
.n1ś8
.n1ź8
.n1ż8
.na2d2
.na1
.na2j
.na3ć2
.na3ł2
.na3ś2
.na3ź2
.na3ż2
.na3b2
.na3c2
.na3dą1
.na3dę1
.na3d4ź2
.nad3ł2
.na3d4łub
.nadłu1
.nad3i2
.na3d4ir
.na2d3m2
.na3d4muc4h
.nadmu1
.nad3r2
.na3d4ręc4z
.nadrę1
.na3d4r2w
.na3d4repc4z
.nadre1
.nadre2p1c
.na3d4re2p1t
.na3d4ruk
.nadru1
.na3d4r4z
.nad3w2
.na3d4wo2r1n
.nadwo1
.na3daj
.na1da1
.na3de1
.na3do1
.na3dy1
.nad4z2
.na3dzi1
.na3f2
.na3g2
.na3h2
.na3ją1
.na3ję1
.na3ja2z1d
.na1ja1
.na3je1
.na3k2
.na3l2
.na3m2
.na3p2
.na3r2
.na3s2
.na3t2
.na3u2
.na3w2
.na3z2
.na4d3o2b2ł
.na4d3o2bojc4z
.nado1bo1
.nadobo2j1c
.na4d3o2bowi1
.na4d3o2brot
.nadobro1
.na4d3o2dr4z
.na4d3o2kien
.nadoki1
.nadoki2e1
.na4d3olbr4z
.nado2l1b
.na4d5rzą1
.na4d5rzę1
.na4d5rzec4z
.nadrze1
.na4d5rzy1
.na4d5ziem
.nadzi2e1
.na4f3c
.na4f3t
.na4j3e2f
.na4j3e2g
.na4j3e2k2s
.na4j3e2ko1
.na4j3e2n
.na4j3e2r
.na4j3e2s
.na4j3e2w
.na4j3e2m1f
.na4j3e2u1
.na4r3c
.na4r3d
.na4r3k
.na4r3r
.na4r3t
.nabr4z2
.nac4h2
.nac4z2
.na2d3ś2
.nadśrod5ziem
.nadśro1
.nadśrod4z
.nadśrodzi1
.nadśrodzi2e1
.na2d3ć2
.na2d3b2
.na2d3c2
.na4d3d2
.nade3t2
.nad3e2tat
.nadeta1
.na2d3f2
.na2d3g2
.nad3h2
.nad3j2
.na2d3k2
.nad3l2
.na2d3n2
.na2d3p2
.na2d3s2
.na2d3t2
.nad3u2
.nad5ż2
.nad5zó1
.nad5z2mys
.na2dz1m
.nadzmy1
.nad5zo1
.nad5zwyc4z
.nadzwy1
.nadc4h2
.nadc4z2
.nadd4ź2
.nade3ć2
.nade3ł2
.nade3ś2
.nade3ź2
.nade3ż2
.nade3b2
.nade3c2
.nade3d2
.nade3f2
.nade3g2
.nade3h2
.nade3k2
.nade3l2
.nade3m2
.nade3p2
.nade3r2
.nade3s2
.nade3w2
.nade3z2
.nade4p3c
.nade4p3n
.nade4p3t
.nadec4h2
.nadec4z2
.naded4ź2
.naded4ż2
.naded4z2
.nade2r1ż2
.nader4z2
.nades4z2
.nads4z2
.nadtr2
.nagr4z2
.na2j3ć2
.na2j3ł2
.na2j3ś2
.na2j3ź2
.na2j3ż2
.naj3a2k1t
.naj3a2u1
.na2j3b2
.na2j3c2
.na2j3d2
.na2j3f2
.na2j3g2
.na2j3h2
.naj3i2
.na2j3k2
.na2j3l2
.na2j3m2
.naj3o2
.naj3o2ć2
.naj3o2ł2
.naj3o2ś2
.naj3o2ź2
.naj3o2ż2
.naj3o2b2
.naj3o2c2
.naj3o2d2
.naj3o2f2
.naj3o2g2
.naj3o2h2
.naj3o2k2
.naj3o2l2
.naj3o2m2
.naj3o2p2
.naj3o2r2
.naj3o2s2
.naj3o2t2
.naj3o2w2
.naj3o2z2
.na2j3p2
.na2j3r2
.naj3ro2z3
.najro1
.na2j3s2
.na2j3t2
.naj3u2
.na2j3w2
.na2j3z2
.najbe2z3
.najbe1
.najbezw2
.najc4h2
.najc4z2
.najd4ź2
.najd4ż2
.najdo3ć2
.najdo1
.najdo3ł2
.najdo3ś2
.najdo3ź2
.najdo3ż2
.najdo3b2
.najdo3c2
.najdo3d2
.najdo3f2
.najdo3g2
.najdo3h2
.najdo3k2
.najdo3l2
.najdo3m2
.najdo3p2
.najdo3r2
.najdo3s2
.najdo3t2
.najdo3w2
.najdo3z2
.najdoc4h2
.najdoc4z2
.najdod4ź2
.najdod4ż2
.najdod4z2
.najdor4z2
.najdos4z2
.najdo2t1k2
.najd4z2
.najkr2
.najo2b3ć2
.najob3ł2
.najo2b3ś2
.najo2b3ź2
.najo2b3ż2
.najo2b3c2
.najo2b3d2
.najo2b3f2
.najo2b3g2
.najob3h2
.najob3j2
.najo2b3k2
.najob3l2
.najo2b3m2
.najo2b3n2
.najo2b3p2
.najo2b3s2
.najo2b3t2
.najob3w2
.najobc4h2
.najobc4z2
.najobd4ź2
.najobd4ż2
.najobd4z2
.najobr4z2
.najobs4z2
.najoc4h2
.najoc4z2
.najod4ź2
.najo2d3ć2
.najo2d3ś2
.najo2d3c2
.najo4d3d2
.najo2d3f2
.najo2d3g2
.najod3h2
.najod3j2
.najo2d3k2
.najod3l2
.najo2d3m2
.najo2d3n2
.najo2d3p2
.najo2d3s2
.najo2d3t2
.najod3w2
.najod5ż2
.najodc4h2
.najodc4z2
.najodd4ź2
.najodd4ż2
.najodd4z2
.najods4z2
.najod4z2
.najor4z2
.najos4z2
.najro3z4u1
.najr4z2
.najsm2
.najs4z2
.naj2t1k2
.naj2t1r2
.najuc4z2
.najzw2
.nakr2
.napo2d2
.napo1
.napo3ć2
.napo3ł2
.napo3ś2
.napo3ź2
.napo3ż2
.napo3b2
.napo3c2
.napo3f2
.napo3g2
.napo3h2
.napo3k2
.napo3l2
.napo3m2
.napo3p2
.napo3r2
.napo3s2
.napo3t2
.napo3w2
.napo3z2
.napo4m3p
.napoc4h2
.napoc4z2
.napod4ź2
.napod4ż2
.napo4d3d
.napo2m1k2
.napor4z2
.napos4z2
.napr4z2
.na2r1ż2
.naro2z3
.naro1
.nar4z2
.nasm2
.nas4z2
.natc4h2
.na2t1c
.na2t1k2
.naz3m2
.nazw2
.n1b8
.n1c8
.n1d8
.ne2o3
.ne1
.n1f8
.n1g8
.n1h8
.nie3ć2
.ni1
.ni2e1
.nie3ł2
.nie3ś2
.nie3ź2
.nie3ż2
.nie3b2
.nie3c2
.nie3d2
.nie3f2
.nie3g2
.nie3h2
.nie3k2
.nie3l2
.nie3m2
.nie3p2
.nie3r2
.nie3s2
.nie3t2
.nie3u2
.nie3w2
.nie3z2
.nie4c3c
.nie4c3k
.nie4d4ź3
.nie4m3c
.nie4m3k
.niec4h2
.niec4z2
.nied4ż2
.niedo3ć2
.niedo1
.niedo3ł2
.niedo3ś2
.niedo3ź2
.niedo3ż2
.niedo3b2
.niedo3c2
.niedo3d2
.niedo3f2
.niedo3g2
.niedo3h2
.niedo3k2
.niedo3l2
.niedo3m2
.niedo3p2
.niedo3r2
.niedo3s2
.niedo3t2
.niedo3w2
.niedo3z2
.niedobr4z2
.niedoc4h2
.niedoc4z2
.niedod4ź2
.niedod4ż2
.niedod4z2
.niedokr2
.niedo2m1k2
.niedopc4h2
.niedo2p1c
.niedor4z2
.niedos4z2
.niedo2t1k2
.nied4z2
.nieo2
.nieoć2
.nieoł2
.nieoś2
.nieoź2
.nieoż2
.nieob2
.nieo2b3ć2
.nieo2b3ś2
.nieo2b3ź2
.nieo2b3ż2
.nieo2b3c2
.nieo2b3d2
.nieo2b3f2
.nieo2b3g2
.nieob3h2
.nieob3j2
.nieo2b3k2
.nieo2b3m2
.nieo2b3p2
.nieo2b3s2
.nieob3w2
.nieobc4h2
.nieobc4z2
.nieobd4ź2
.nieobd4ż2
.nieobd4z2
.nieobs4z2
.nieoc2
.nieoc4h2
.nieoc4z2
.nieod2
.nieod4ź2
.nieo2d3ć2
.nieod3ł2
.nieo2d3ś2
.nieo2d3c2
.nieo4d3d2
.nieo2d3f2
.nieo2d3g2
.nieod3h2
.nieod3j2
.nieo2d3k2
.nieod3l2
.nieo2d3n2
.nieo2d3p2
.nieo2d3s2
.nieo2d3t2
.nieodw2
.nieod3w1r
.nieod5ż2
.nieodc4h2
.nieodc4z2
.nieodd4ź2
.nieodd4ż2
.nieodd4z2
.nieods4z2
.nieod4z2
.nieof2
.nieog2
.nieoh2
.nieok2
.nieol2
.nieom2
.nieop2
.nieor2
.nieor4z2
.nieos2
.nieos4z2
.nieot2
.nieow2
.nieoz2
.niepo2d2
.niepo1
.niepo3ć2
.niepo3ł2
.niepo3ś2
.niepo3ź2
.niepo3ż2
.niepo3b2
.niepo3c2
.niepo3d4ź2
.niepod3ł2
.niepo3d4łu1
.niepo2d3m2
.niepo3d4muc4h
.niepodmu1
.niepod3r2
.niepo3d4ręc4z
.niepodrę1
.niepo3d4raż
.niepodra1
.niepo3d4rap
.niepo3d4repc4z
.nie1podre1
.niepodre2p1c
.niepo3d4re2p1t
.niepod3w2
.niepo3d4waj
.niepodwa1
.niepo3d4woj
.niepodwo1
.niepo3do1
.niepo3du1
.niepo3d4z2
.niepo3f2
.niepo3g2
.niepo3h2
.niepo3k2
.niepo3l2
.niepo3m2
.niepo3p2
.niepo3r2
.niepo3s2
.niepo3t2
.niepo3w2
.niepo3z2
.niepo4d3o2choc
.niepodoc4h
.niepodocho1
.niepo4d3o2str4z
.niepoc4h2
.niepoc4z2
.niepo2d3ć2
.niepo2d3ś2
.niepo2d3b2
.niepo2d3c2
.niepo4d3d2
.niepo2d3f2
.niepo2d3g2
.niepod3h2
.niepod3j2
.niepo2d3k2
.niepod3l2
.niepo2d3n2
.niepo2d3p2
.niepo2d3s2
.niepo2d3t2
.niepod5ż
.niepodc4h2
.niepodc4z2
.niepodd4ź2
.niepodd4ż2
.niepodsm2
.niepods4z2
.niepor4z2
.nieposm2
.niepos4z2
.nieprze3ł2
.nieprze2ł1k2
.niepr4z
.nie1prze1
.nieprze2d2
.nieprze3ć2
.nieprze3ś2
.nieprze3ź2
.nieprze3ż2
.nieprze3b2
.niepr4ze3br4z2
.nieprze3c2
.nieprze3d4ź2
.nieprzed3ł2
.nieprze3d4łuż
.nieprzedłu1
.nieprze2d3m2
.nieprze3d4muc4h
.nieprzedmu1
.nieprzed3r2
.nieprze3d4ramat
.nieprzedra1
.nieprzedrama1
.nieprze3d4ruk
.nieprzedru1
.nieprze3d4ryl
.nieprzedry1
.niepr4ze3d4r4z2
.nieprzed3u2
.nieprze3d4um
.nieprze3dy1
.nieprze3d4z2
.nieprze3e2k2s3
.nie1prze1e2
.nieprze3f2
.nieprze3g2
.nieprze3h2
.nieprze3k2
.nieprze3l2
.nieprze3m2
.nieprze3n2
.nieprze3p2
.nieprze3r2
.nieprze3s2
.nieprze3t2
.nieprze3w2
.nieprze3z2
.nieprze4d5łużyc
.nieprzedłuży1
.nieprze4d5ż2
.nieprze4d5z2a1
.nieprze4d5z1g2
.nieprze4d5zim
.nieprzedzi1
.nieprze4d5zj
.nieprze4d5z1l
.nieprze4d5z2w2r
.nieprze4d5zwoj
.nieprzedzwo1
.nieprzec4h2
.nieprzec4z2
.nieprze2d3ć2
.nieprze2d3ś2
.nieprze2d3c2
.nieprze4d3d2
.nieprze2d3f2
.nieprze2d3g2
.nieprzed3h2
.ni1eprzed3i2
.nieprzed3j2
.nieprze2d3k2
.nieprzed3l2
.nieprze2d3n2
.nieprze2d3p2
.nieprze2d3s2
.nieprzed3s4z2
.nieprze2d3t2
.nieprzed3w2
.nieprzedc4h2
.nieprzedc4z2
.nieprzedd4ź2
.nieprzedd4ż2
.nieprzedd4z2
.niepr4zegr4z2
.nieprzekl2
.nieprzekr2
.nieprzepc4h2
.nieprze2p1c
.nieprze2r1ż2
.niepr4zer4z2
.nieprzesc4h2
.nieprzesm2
.nieprzes4z2
.nieprze2t1k2
.nieprzetr2
.niero2z3
.niero1
.nie1ro3z4e1
.niero3z4u1
.niero2z1ś2
.nierozbr4z2
.nieroze3r2
.nierozm2
.nieroztr2
.niero2z1t
.nier4z2
.niesu2b3
.niesu1
.ni2e1su3b4i2e1
.niesubi1
.nies4z2
.nie2t1k2
.nietr2
.nieuc4z2
.nieuw2
.niewy3ć2
.niewy1
.niewy3ł2
.niewy3ś2
.niewy3ź2
.niewy3ż2
.niewy3b2
.niewy3c2
.niewy3d2
.niewy3f2
.niewy3g2
.niewy3h2
.niewy3k2
.niewy3l2
.niewy3m2
.niewy3p2
.niewy3r2
.niewy3s2
.niewy3t2
.niewy3w2
.niewy3z2
.niewybr4z2
.niewyc4h2
.niewyc4z2
.niewyd4ź2
.niewyd4ż2
.niewyd4z2
.niewyr4z2
.niewys4z2
.niewy2t1k2
.niewytr2
.niezw2
.n1j8
.n1k8
.n1l8
.n1m8
.n3n8
.n1p8
.n1r8
.n1s8
.n1t8
.nv8
.n1w8
.nx8
.n1z8
.oć2
.o1
.oś2
.ośmio3ś2
.ośmi1
.o1śmi2o1
.oź2
.oż2
.o2b2
.o2d2
.ot2
.o2t3c2h2ł
.o2t1c
.otc4h
.ob3ł2
.o3b4łą1
.o3b4łę1
.o3b4łoc
.o1bło1
.ob3l2
.o3b4luzg
.oblu1
.ob3r
.o3b4rać
.obra1
.o3b4raso1
.o3b4roń
.o1bro1
.o3b4ron
.o3b4ryź
.obry1
.o3b4ryz
.o3b4r4z2
.o3be1
.o3bi1
.od3i2
.o3d4i2u1
.od3r2
.o3d4ręt
.odrę1
.o3d4rap
.odra1
.o3d4robin
.odro1
.odrobi1
.o3d4rut
.odru1
.o3d4rwi1
.od2r1w
.odr4z2
.o3d4rzeć
.odrze1
.o3d4rz2w
.od5z2
.o3d6zi2a1
.odzi1
.o3d6zi2e1
.o3de1
.o3l2śn
.o2l1ś
.o4b5łoc4z
.o4b5rzą1
.o4b5rzęd
.obrzę1
.o4b5rzez
.obrze1
.o4b5rzuc
.obrzu1
.o4b5rzut
.o4b5rzyn
.obrzy1
.o4d7ziar
.o4d7ziem
.oa3z
.oa2
.o2b3ć2
.o2b3ś2
.o2b3ź2
.o2b3ż2
.o2b3c2
.o2b3d2
.o2b3f2
.o2b3g2
.ob3h2
.ob3j2
.o2b3k2
.o2b3m2
.o2b3n2
.ob3o2str4z
.obo1
.o2b3p2
.o2b3s2
.o2b3t2
.ob3u2m2
.obu1
.ob3w2
.obc4h2
.obc4z2
.obd4ź2
.obd4ż2
.obd4z2
.obe3ć2
.obe3ł2
.obe3ś2
.obe3ź2
.obe3ż2
.obe3b2
.obe3c2
.obe3d2
.obe3f2
.obe3g2
.obe3h2
.obe3k2
.obe3l2
.obe3m2
.obe3p2
.obe3r2
.obe3r3t
.obe3s2
.obe3t2
.obe3w2
.obe3z2
.obe4c3n
.obe4z3w
.obec4h2
.obec4z2
.obed4ź2
.obed4ż2
.obed4z2
.obe2r1ż2
.obe2r3m
.ober4z2
.obesc4h2
.obes4z2
.obe2t1k2
.obi3b2
.obs4z2
.oc2
.oc4h2
.ochr4z2
.oc2h2r
.oc4z2
.od4ź2
.o2d3ć2
.o2d3ś2
.od3a2u1
.oda1
.o2d3b2
.o2d3c2
.o4d3d2
.o2d3f2
.o2d3g2
.od3h2
.o1d3i2zo1
.od3j2
.o2d3k2
.od3l2
.o2d3m2
.o2d3n2
.od3o2s
.odo1
.o2d3p2
.o2d3s2
.o2d3t2
.od3u2c4z
.odu1
.od3u2m2
.od3w2
.od5ż2
.odbe2z3
.odbe1
.odc4h2
.odc4z2
.odd4ź2
.odd4ż2
.odd4z2
.ode3ć2
.ode3ł2
.ode3ś2
.ode3ź2
.ode3ż2
.ode3b2
.ode3c2
.ode3d2
.ode3f2
.ode3g2
.ode3h2
.ode3k2
.ode3l2
.ode3m2
.ode3m1k2
.ode3p2
.ode3r2
.ode3s2
.ode3t2
.ode3w2
.ode3z2
.odec4h2
.odec4z2
.oded4ź2
.oded4ż2
.oded4z2
.odepc4h2
.ode2p1c
.ode2r1ż2
.oder4z2
.odes4z2
.odetc4h2
.ode2t1c
.ode2t1k2
.odkr4z2
.ods4z2
.of2
.og2
.ogólno3k2
.ogó1
.ogó2l1n
.ogólno1
.ognio3tr2
.ogni1
.o1gni2o1
.oh2
.ok2
.oka3m2
.oka1
.okr2
.o1le2o3
.ole1
.om2
.op2
.opc4h2
.o2p1c
.o2r2ż2
.or2tę1
.o2r1t
.or4z2
.os2
.osie2m3
.osi1
.osi2e1
.osiemse2t3
.osie2m1s
.osiemse1
.os4z2
.ow2
.oz2
.p8
.p1ć8
.pł8
.płasko3w2
.pła1
.płasko1
.p1ń8
.pó2ł3
.pó2ł1k2
.pó1
.półkr2
.pó2ł1m2
.póło2
.półob3r
.póło2m2d
.półprzy3m2k
.pó2ł1p
.półpr4z
.półprzy1
.pó3ł4ą1
.pó3ł4ę1
.pó3ł4ec4z
.półe1
.pó3ł4y1
.p1ś8
.p1ź8
.p1ż8
.p1b8
.p1c8
.pc4h8
.p1d8
.pełno3kr2
.pe1
.pe2ł1n
.pełno1
.pe2r3
.pe3c2k
.pe3r4e1
.pe3r4i1
.pe3r4o1
.pe3r4u1
.pe3r4y1
.pe4r5i2n
.pee2se2l
.pe1e2
.peese1
.pepee2r
.pe1pe1
.pe1pe1e2
.pepee2s
.peze2t1pee2r
.peze1
.peze2t1p
.pezetpe1
.pe1ze1tpe1e2
.p1f8
.p1g8
.ph8
.pię2ć3
.pięćse2t3
.pi1
.pi2ę1
.pię2ć1s
.pięćse1
.pięcio3ś2
.pięci1
.pięci2o1
.pierwo3w2
.pi2e1
.pie2r1w
.pierwo1
.piono3w2
.pi2o1
.piono1
.pj8
.p1k8
.pl8
.p1m8
.p1n8
.po3ł2
.po2ł1k2
.po1
.po2d2
.po3ć2
.po3ś2
.po3ź2
.po3ż2
.po3b2
.po3c2
.po3dą1
.po3dę1
.po3d4ź2
.pod3ł2
.po3d4łu1
.po2d3m2
.po3d4muc4h
.podmu1
.po2d3n2
.po3d4naw
.podna1
.pod3r2
.po3d4ręc4z
.podrę1
.po3d4rętw
.po3d4róż
.podró1
.po3d4r2wi1
.pod2r1w
.po3d4raż
.podra1
.po3d4rap
.po3d4repc4z
.podre1
.podre2p1c
.po3d4re2p1t
.po3d4roż
.po1dro1
.po3d4robó1
.po3d4roba1
.po3d4ro1bo1
.po3d4roby1
.po3d4roc4z
.po3d4ruzg
.podru1
.po3d4ryg
.podry1
.po3d4rze1
.podr4z
.pod3w2
.po3d4wó2j1n
.podwó1
.po3d4wór
.po3d4waj
.podwa1
.po3d4woi2
.po1dwo1
.po3d4woj
.po3d4wor4z
.po3da1
.po3de1
.po3dej
.po3di2u1
.podi1
.po3do1
.po3du1
.po3dy1
.po3d4z2
.po3e2k2s3
.poe2
.po3f2
.po3g2
.po3h2
.po3k2
.po3l2
.po3m2
.po3p2
.po3r2
.po3r1ż
.po3s2
.po3t2
.po3w2
.po3z2
.po4ń3c
.poc4z2
.po4c2z3d
.po4c2z3t
.po4d3ów
.podó1
.pode3k2
.po4d3e4k2s3
.po4d3o2bóz
.podobó1
.po4d3o2biad
.podobi1
.podobi2a1
.po4d3o2bojc4z
.podobo1
.podobo2j1c
.po4d3o2braz
.podobra1
.po4d3o2choc
.podoc4h
.podocho1
.po4d3o2d1m
.po4d3o2f
.po4d3o2g
.po4d3o2kien
.podoki1
.podoki2e1
.po4d3o2k1n
.po4d3o2kręg
.podokrę1
.po4d3o2kres
.podokre1
.po4d3o2piec4z
.podopi1
.podopi2e1
.po4d3o2ryw
.podory1
.po4d3o2siniak
.podosi1
.podosini1
.podosini2a1
.po4d3o2str4z
.po4d3obs4z
.podo2b1s
.po4d3o4d3d
.po4d3olbr4z
.podo2l1b
.po4d3u2c4z
.po4d3u2d4z
.po4d3u2pa1
.po4d3u2ral
.podura1
.po4d3u2sta1
.po4d3u2szc4z
.podus4z
.podu2s2z1c
.po4d5rę2cz1n
.po4d5zakr
.podza1
.po4d5zam
.po4d5zast
.po4d5zbi1
.po2dz1b
.po4d5ze1
.po4d5zieleni2ą1
.podzi1
.podzi2e1
.podzie1le1
.podzi1eleni1
.po4d5zielenić
.po4d5zieleni2ę1
.po4d5zielenił
.po4d5zielenic
.po4d5zielenien
.podzie1le1ni2e1
.po4d5zielenil
.po4d5zielenim
.po4d5zieleni2o1
.po4d5zielenis
.po4d5ziem
.po4d5ziom
.po1dzi2o1
.po4d5z2w2r
.po4l3s
.po4m3p
.po4r3c
.po4r3f
.po4r3n
.po4r3t
.po4s2t3d
.po4s2t3f
.po4s2t3g
.po4st3h
.po4st3i2
.po4s2t3k
.po4st3l
.po4s2t3m
.po4s2t3p
.po4st3rom
.postro1
.po4s2t3s
.po5d4uszczyn
.poduszczy1
.po5r4tę1
.pobr2
.pobr4z2
.poc4h2
.pochr4z2
.poc2h2r
.po2d3ć2
.po2d3ś2
.pod3śró2d5
.podśró1
.pod3a2l1p
.po2d3b2
.po2d3c2
.po4d3d2
.po2d3f2
.po2d3g2
.pod3h2
.pod3i2n
.pod3j2
.po2d3k2
.pod3l2
.po2d3p2
.po2d3s2
.po2d3t2
.pod5ż2
.podc4h2
.podc4z2
.podd4ź2
.podd4ż2
.pode3ć2
.pode3ł2
.pode3ś2
.pode3ź2
.pode3ż2
.pode3b2
.pode3c2
.pode3d2
.pode3f2
.pode3g2
.pode3h2
.pode3l2
.pode3m2
.pode3p2
.pode3r2
.pode3s2
.pode3t2
.pode3t1k2
.pode3w2
.pode3z2
.podec4h2
.podec4z2
.poded4ź2
.poded4ż2
.poded4z2
.podepc4h2
.pode2p1c
.pode2r1ż2
.poder4z2
.podesc4h2
.podes4z2
.podro2z3
.podsm2
.pods4z2
.pogr4z2
.pokl2
.pokr2
.pom4p1k
.po2m1k2
.pona2d2
.pona1
.pona3ć2
.pona3ł2
.pona3ś2
.pona3ź2
.pona3ż2
.pona3b2
.pona3c2
.pona3c4z2
.pona3d4ź2
.po1na3do1
.pona3f2
.pona3g2
.pona3h2
.pona3k2
.pona3l2
.pona3m2
.pona3p2
.pona3r2
.pona3s2
.pona3t2
.pona3w2
.pona3z2
.pona4f3t
.ponabr4z2
.ponac4h2
.pona2d3ć2
.pona2d3ś2
.pona2d3c2
.ponad3c4h2
.ponad3c4z2
.ponad3d4ź2
.pona4d3d
.pona2d3f2
.pona2d3g2
.ponad3h2
.ponad3j2
.pona2d3k2
.ponad3l2
.pona2d3p2
.pona2d3s2
.pona2d3t2
.ponad4z2
.ponar4z2
.ponasm2
.ponas4z2
.ponaz3m2
.ponazw2
.ponie3k2
.poni1
.poni2e1
.ponie3w2
.popc4h2
.po2p1c
.popo3w2
.popo1
.popr4z2
.por4t1w
.por4t1f
.por4t1m
.poro2z3
.po1ro1
.poro3z4u1
.por4z2
.posc4h2
.posm2
.pos4z2
.po2t1k2
.potr2
.poz4m2
.poza3u2
.poza1
.pozw2
.p3p8
.pr8
.pra3s2
.pra1
.pra3w2nu1
.pra2w1n
.pra3w2z
.prapra3w2nu1
.prapra1
.prapra2w1n
.predy2s3po1
.pre1
.predy1
.pr4z8
.prze3ł2
.prze2ł1k2
.prze1
.prze2d2
.prze3ć2
.prze3ś2
.prze3ź2
.prze3ż2
.prze3b2
.prze3c2
.prze3dą1
.prze3dę1
.prze3d4ź2
.przed3ł2
.prze3d4łuż
.przedłu1
.prze2d3m2
.prze3d4muc4h
.przedmu1
.przed3o2
.prze3d4o3br
.prze3d4o3st
.prze3d4o3zo1
.przed3r2
.prze3d4ramat
.przedra1
.przedrama1
.prze3d4ruk
.przedru1
.prze3d4ryl
.przedry1
.pr4ze3d4r4z2
.przed3u2
.prze3d4um
.prze3dy1
.prze3d4z2
.prze3e2k2s3
.prze1e2
.prze3f2
.prze3g2
.prze3h2
.prze3k2
.prze3l2
.prze3m2
.prze3n2
.prze3p2
.prze3r2
.prze3s2
.prze3t2
.prze3u2
.prze3w2
.prze3z2
.prze4d5łużyc
.przedłuży1
.prze4d5ż2
.prze4d5o4stat
.przedosta1
.prze4d5za1
.prze4d5z1g2
.prze4d5zim
.przedzi1
.prze4d5zj
.prze4d5z1l
.prze4d5z2w2r
.prze4d5zwoj
.przedzwo1
.przebr2
.pr4zebr4z2
.przec4h2
.pr4zechr4z2
.przec2h2r
.przeci2w3
.przeci1
.prze1ci3w4i2e1
.przeciwi1
.przeciwa2
.przeci4w3w2
.przec4z2
.prze2d3ć2
.prze2d3ś2
.przed3a2gon
.przeda1
.przedago1
.przed3a2k1c
.przed3a2l1p
.prze2d3b2
.prze2d3c2
.prze4d3d2
.przed3e2g1z
.prze1de1
.przed3e2mer
.przedeme1
.prze2d3f2
.prze2d3g2
.przed3h2
.przed3i2
.przed3j2
.prze2d3k2
.przed3l2
.prze2d3n2
.prze2d3p2
.prze2d3s2
.przed3się3w2
.przedsi1
.przedsi2ę1
.przed3s4z2
.prze2d3t2
.przed3w2
.przedc4h2
.przedc4z2
.przedd4ź2
.przedd4ż2
.przedd4z2
.pr4zedgr4z2
.przedy2s3ku1
.pr4zegr4z2
.przekl2
.przekr2
.prze2m1k2
.przepc4h2
.prze2p1c
.prze2r1ż2
.pr4zer4z2
.przesc4h2
.przesm2
.przes4z2
.prze2t1k2
.przetr2
.przetra2n2s3
.przetra1
.przy3ć2
.przy1
.przy3ł2
.przy3ś2
.przy3ź2
.przy3ż2
.przy3b2
.przy3c2
.przy3d2
.przy3f2
.przy3g2
.przy3h2
.przy3k2
.przy3l2
.przy3m2
.przy3p2
.przy3r2
.przy3s2
.przy3t2
.przy3w2
.przy3z2
.przybr2
.przyc4h2
.przyc4z2
.przyd4ź2
.przyd4ż2
.przyd4z2
.pr4zygr4z2
.przy2m1k2
.przyoz2
.przyo2
.przypc4h2
.przy2p1c
.przy2r1ż2
.pr4zyr4z2
.przysc4h2
.przys4z2
.przy2t1k2
.p1s8
.p1t8
.pv8
.pw8
.px8
.p1z8
.r8
.r1ć8
.r1ł8
.r1ń8
.r1ś8
.r1ź8
.r1ż8
.r1b8
.r1c8
.r1d8
.retra2n2s3
.re1
.retra1
.r1f8
.r1g8
.r1h8
.r1j8
.r1k8
.r1l8
.r1m8
.r1n8
.ro2z3
.ro1
.ro3z4a1
.ro3z4e1
.ro3z4e3ć2
.ro3z4e3ł2
.ro3z4e3ś2
.ro3z4e3ź2
.ro3z4e3ż2
.ro3z4e3b2
.ro3z4e3c2
.ro3z4e3d2
.ro3z4e3f2
.ro3z4e3g2
.ro3z4e3h2
.ro3z4e3k2
.ro3z4e3l2
.ro3z4e3m2
.ro3z4e3p2
.ro3z4e3r2
.ro3z4e3s2
.ro3z4e3t2
.ro3z4e3w2
.ro3z4e3z2
.ro3z4ej
.ro3z4u1
.ro4z5a2gi1
.ro4z5a2ni2e1
.rozani1
.ro4z5e2mo1
.ro4z5e4g3z
.ro4z5e4n3t
.ro2z1ś2
.rozbr4z2
.ro2z1d2
.rozec4h2
.rozec4z2
.rozed4ź2
.rozed4ż2
.rozed4z2
.rozepc4h2
.roze2p1c
.roze2r1ż2
.rozer4z2
.rozesc4h2
.rozes4z2
.rozi2
.rozm2
.ro1zo2
.rozpo3w2
.ro2z1p
.rozpo1
.ro2z1t2
.roztr2
.rozw2
.r1p8
.r3r8
.r1s8
.r1t8
.rv8
.r1w8
.rx8
.r4z8
.s8
.sć8
.sł8
.sń8
.sś8
.s1ź8
.s1ż8
.samo3c4h2
.sa1
.samo1
.samo3k2
.samo3p2
.samo3w2
.samoro2z3
.samoro1
.s1b8
.sc8
.sc4h8
.s1d8
.s1f8
.s1g8
.sh8
.siede2m3
.si1
.si2e1
.sie1de1
.siedemse2t3
.siede2m1s
.siedemse1
.siedmio3ś2
.sie2d1m
.siedmi1
.siedmi2o1
.sj8
.sk8
.ską2d5że1
.ską1
.skąd4ż
.skl8
.skr8
.sl8
.sm8
.sn8
.sobo3w2
.so1
.sobo1
.sp8
.spó2ł3
.spó1
.spo2d2
.spo1
.spo3ć2
.spo3ł2
.spo3ś2
.spo3ź2
.spo3ż2
.spo3b2
.spo3c2
.spo3d4z2
.spo3f2
.spo3g2
.spo3h2
.spo3k2
.spo3l2
.spo3m2
.spo3p2
.spo3r2
.spo3s2
.spo3t2
.spo3w2
.spo3z2
.spo4r3n
.spo4r3t
.spoc4h2
.spoc4z2
.spod4ź2
.spod4ż2
.spo4d3d
.spor4z2
.spos4z2
.sr8
.s1s8
.st8
.stere2o3
.ste1
.ste1re1
.stereoa2
.ste1re1oe2
.stereoi2
.stereo1o2
.stereou2
.su2b3
.su1
.su3b4i2e1
.subi1
.su3b4o2t1n
.subo1
.supe2r3
.supe1
.supe3r4at
.supera1
.supe3r4i2o1
.superi1
.supe4r5a2tr
.supe2r5z2b
.super4z
.supe1re2
.supero2d1rzut
.supero1
.superodr4z
.superodrzu1
.sv8
.sw8
.sx8
.s4z8
.sze4ś2ć3
.sześćse2t3
.sze1
.sześ2ć1s
.sześćse1
.sześcio3ś2
.sześ1c
.sze1ś2ci1
.sześci2o1
.sze2s3
.t8
.t1ć8
.tł8
.t1ń8
.t1ś8
.t1ź8
.t1ż8
.ta2o3
.ta1
.ta2r7zan
.tar4z
.tarza1
.t1b8
.t1c8
.tc4h8
.t1d8
.te2o3
.te1
.t1f8
.t1g8
.th8
.tj8
.t1k8
.tl8
.t1m8
.t1n8
.toa3
.to1
.t1p8
.tr8
.tró2j3
.tró1
.tró3j4ą1
.tró3j4ę1
.tró3j4ec4z
.tróje1
.tra2n2s3
.tra1
.tran3s4e1
.tran3s4i2e1
.transi1
.tran3s4y1
.tran3s4z
.tran4s5e2u1
.tra1nsa2
.transo2
.tr4z8
.trze2c4h3
.trze1
.trzechse2t3
.trze2c2h1s
.trzechse1
.t1s8
.t3t8
.tv8
.tw8
.tx8
.tysią2c3
.ty1
.tysi1
.tysi2ą1
.tysią3c4a1
.tysią3c4e1
.tysią3c4z
.tysią4c5zł
.t1z8
.uć2
.u1
.uś2
.u3ł2
.u3ź2
.u3ż2
.u3b2
.u3c2
.u3d2
.u3f2
.u3g2
.u3h2
.u3k2
.u3l2
.u3m2
.u3n2
.u3p2
.u3r2
.u3s2
.u3t2
.u3w2
.u3z2
.u4d3k
.u4f3n
.u4k3lej
.ukle1
.u4l3s
.u4l3t
.u4m3br
.u2m1b
.u4n3c
.u4n3d
.u4p3p2s
.u4p3p
.u4r3s
.u4s2t3n
.u4s2t1c
.u4s2t1k
.u4z3be1
.ube2z3
.ube1
.ubezw2
.ubr2
.uc4h2
.uc4z2
.ud4ź2
.ud4ż2
.ud4z2
.ukr2
.u2m1k2
.upc4h2
.u2p1c
.upo2d2
.upo1
.upo3ć2
.upo3ł2
.upo3ś2
.upo3ź2
.upo3ż2
.upo3b2
.upo3c2
.upo3da1
.upo3f2
.upo3g2
.upo3h2
.upo3k2
.upo3l2
.upo3m2
.upo3p2
.upo3r2
.upo3s2
.upo3t2
.upo3w2
.upo3z2
.upoc4h2
.upoc4z2
.upod4ź2
.upod4ż2
.upo4d3d
.upor4z2
.upos4z2
.u2r1ż2
.uro2z3
.uro1
.ur4z2
.usc4h2
.us4z2
.u2t1k2
.utr2
.uze3w2
.uze1
.v8
.vć8
.vł8
.vń8
.vś8
.vź8
.vż8
.vb8
.vc8
.vd8
.vf8
.vg8
.vh8
.vj8
.vk8
.vl8
.vm8
.vn8
.vp8
.vr8
.vs8
.vt8
.vv8
.vw8
.vx8
.vz8
.w8
.w1ć8
.w1ł8
.w1ń8
.w1ś8
.w1ź8
.w1ż8
.w1b8
.w1c8
.w1d8
.we3ć2
.we1
.we3ł2
.we3ś2
.we3ż2
.we3b2
.we3c2
.we3d2
.we3f2
.we3g2
.we3h2
.we3k2
.we3l2
.we3m2
.we3n2
.we3p2
.we3r2
.we3s2
.we3t2
.we3w2
.we3z2
.we4ł3n
.we4k3t
.we4l3w
.we4n3d
.we4n3t
.we4r3b
.we4r3d
.we4r3n
.we4r3s
.we4r3t
.we4s3pr4z
.we4s3tc4h2
.wes2t1c
.we4z3br
.we4z3gł
.wec4h2
.wec4z2
.wed4ź2
.wed4ż2
.wed4z2
.we2m1k2
.wepc4h2
.we2p1c
.wer4z2
.wes4z2
.we2t1k2
.wewną2tr4z3
.we2w1n
.wewną1
.w1f8
.w1g8
.wh8
.wielo3ś2
.wi1
.wi2e1
.wielo1
.wielo3d2
.wielo3k2
.wieluse2t3
.wielu1
.wieluse1
.wilczo3m2
.wi2l1c
.wilc4z
.wilczo1
.w1j8
.w1k8
.w1l8
.w1m8
.w1n8
.wniebo3w2
.wni1
.wni2e1
.wniebo1
.wodo3w2
.wo1
.wodo1
.w1p8
.w1r8
.w1s8
.wspó2ł3
.współi2
.wspó1
.współo2b3w
.współo1
.współu2
.wspó2ł1w2
.wsze2c4h3
.ws4z
.wsze1
.wszecho2
.wszec2h2w2
.w1t8
.wv8
.w3w8
.wx8
.wy3ć2
.wy1
.wy3ł2
.wy3ś2
.wy3ź2
.wy3ż2
.wy3b2
.wy3c2
.wy3d2
.wy3f2
.wy3g2
.wy3h2
.wy3k2
.wy3l2
.wy3m2
.wy3o2d3r
.wyo2
.wy3p2
.wy3r2
.wy3s2
.wy3t2
.wy3w2
.wy3z2
.wy4ż3s4z
.wy2ż1s
.wyc4z2
.wy4cz3ha1
.wybr2
.wybr4z2
.wyc4h2
.wyd4ź2
.wyd4ż2
.wydr2
.wyd4z2
.wye2k2s3
.wye2
.wygr4z2
.wyi2zo1
.wyi2
.wykl2
.wykr2
.wykr4z2
.wy2m1k2
.wypc4h2
.wy2p1c
.wypr4z2
.wy2r1ż2
.wyr4z2
.wysc4h2
.wysm2
.wys4z2
.wytc4h2
.wy2t1c
.wy2t1k2
.wytr2
.w1z8
.x8
.xć8
.xł8
.xń8
.xś8
.xź8
.xż8
.xb8
.xc8
.xd8
.xf8
.xg8
.xh8
.xj8
.xk8
.xl8
.xm8
.xn8
.xp8
.xr8
.xs8
.xt8
.xv8
.xw8
.xx8
.xz8
.z8
.z1ć8
.zł8
.zło3w2
.zło1
.zń8
.z1ś8
.zź8
.zż8
.za3ć2
.za1
.za3ł2
.za3ś2
.za3ź2
.za3ż2
.za3b2
.za3c2
.za3d2
.za3f2
.za3g2
.za3h2
.za3k2
.za3l2
.za3m2
.za3o2b3r
.zao2
.za3o2b3s
.za3p2
.za3r2
.za3s2
.za3t2
.za3u2
.za3w2
.za3z2
.za4k3t
.za4l3g
.za4l3k
.za4l3t
.za4m3k
.za4r3c4h
.za2r1c
.za4uto1
.za5m4k2n
.zabr2
.zabr4z2
.zac4h2
.zac4z2
.zad4ź2
.zad4ż2
.zado2ść3
.zadośću4
.zado1
.zadr2
.zady2s3po1
.zady1
.zad4z2
.zagr4z2
.zai2n3
.zai2
.zai2zo1
.zain4ic
.zaini1
.zakl2
.zakr2
.zakr4z2
.zanie3d2
.zani1
.zani2e1
.za2r1ż2
.zar4z2
.zasc4h2
.zasm2
.zas4z2
.za2t1k2
.zatr2
.zb8
.z1c8
.z1d8
.zde2z3
.zde1
.zde3z4awu1
.zdeza1
.zde3z4el
.zde1ze1
.zde3z4er
.zde3z4y1
.zdy2s3ko2n1t
.zdy1
.zdysko1
.zdy2s3kred
.zdyskre1
.zdy2s3kwal
.zdyskwa1
.ze3ć2
.ze1
.ze3ł2
.ze3ś2
.ze3ź2
.ze3ż2
.ze3b2
.ze3c2
.ze3d2
.ze3f2
.ze3g2
.ze3h2
.ze3k2
.ze3l2
.ze3m2
.ze3p2
.ze3r2
.ze3s2
.ze3t2
.ze3t1k2
.ze3w2
.ze3z2
.ze4r3k
.ze4t3e2m1e2s
.ze1te1
.zeteme1
.ze4t3e2s1e2l
.zetese1
.ze4t3e2m1p
.ze4t3hap
.zetha1
.zec4h2
.zec4z2
.zed4ź2
.zed4ż2
.zed4z2
.zekl2
.zepc4h2
.ze2p1c
.ze2r1ż2
.zer4z2
.zesc4h2
.zesm4
.zes4z2
.z1f8
.zg8
.zh8
.zimno3kr2
.zi1
.zi2m1n
.zimno1
.zj8
.z1k8
.zl8
.zm8
.zmartwy2c4h3
.zma1
.zma2r1t
.zmartwy1
.zmartwyc2h2w2
.zn8
.znie3ć2
.zni1
.zni2e1
.znie3ł2
.znie3ń2
.znie3ś2
.znie3ź2
.znie3ż2
.znie3b2
.znie3c2
.znie3d2
.znie3f2
.znie3g2
.znie3h2
.znie3k2
.znie3l2
.znie3m2
.znie3n2
.znie3p2
.znie3r2
.znie3s2
.znie3t2
.znie3w2
.znie3z2
.znie4d4ź3
.znie4m3c
.zniec4h2
.zniec4z2
.znied4ż2
.znied4z2
.znier4z2
.znies4z2
.zo2o3
.zo1
.z1p8
.zr8
.zro2z3
.zro1
.zro3z4u1
.z1s8
.z1t8
.zv8
.zw8
.zx8
.z3z8
ą1
ę1
ó1
ó4w3c4z
ó2w1c
ś1c
2ź1d
ź2d4ź
1ś2ci1
2ć1ń
2ć1ś
2ć1ź
2ć1ż
2ć1b
2ć1c
2ć1d
2ć1f
2ć1g
2ć1k
2ć1m
2ć1n
2ć1p
2ć1s
2ć1t
2ć1z
2ł1ć
2ł1ń
2ł1ś
2ł1ź
2ł1ż
2ł1b
2ł1c
2ł1d
2ł1f
2ł1g
2ł1h
2ł1j
2ł1k
2ł1l
2ł1m
2ł1n
2ł1p
2ł1r
2ł1s
2ł1t
2ł1w
2ł1z
2ń1ć
2ń1ł
2ń1ń
2ń1ś
2ń1ź
2ń1ż
2ń1b
2ń1c
2ń1d
2ń1f
2ń1g
2ń1h
2ń1j
2ń1k
2ń1l
2ń1m
2ń1n
2ń1p
2ń1r
2ń1s
2ń1t
2ń1w
2ń1z
2ś2ć1c
2ś1ś
2ś1ź
2ś1ż
2ś1b
2ś1d
2ś1f
2ś1g
2ś1k
2ś1p
2ś1s
2ś1t
2ś1z
2ś2l1m
2ś2l1n
2ź1ć
2ź1ś
2ź1ż
2ź1b
2ź1c
2ź1f
2ź1g
2ź1k
2ź1l
2ź1m
2ź1n
2ź1p
2ź1s
2ź1t
2ź1w
2ź1z
2ż1ć
2ż1ł
2ż1ń
2ż1ś
2ż1ź
2ż1b
2ż1c
2ż1d
2ż1f
2ż1g
2ż1j
2ż1k
2ż1l
2ż1m
2ż1n
2ż1p
2ż1r
2ż1s
2ż1t
2ż1w
2ż1z
2b2ł1k
2b1ć
2b1ń
2b1ś
2b1ź
2b1ż
2b1c
2b1d
2b1f
2b1g
2b1k
2b1m
2b1n
2b1p
2b1s
2b1t
2b1z
2b2r1n
2c1ć
2c1ń
2c1ś
2c1ź
2c1ż
2c1b
2c1d
2c1f
2c1g
2c1k
2c1l
2c1m
2c1n
2c1p
2c1s
2c1t
c4h
2c2h1ć
2c2h1ń
2c2h1ś
2c2h1ź
2c2h1ż
2c2h1b
2c2h1c
2c2h1d
2c2h1f
2c2h1g
2c2h1k
2c2h1m
2c2h1n
2c2h1p
2c2h1s
2c2h1t
2c2h1z
c4z
2c2z1ć
2cz1ń
2c2z1ś
2cz1ź
2cz1ż
2cz1b
2c2z1c
2c2z1d
2c2z1f
2cz1g
2c2z1k
2cz1l
2cz1m
2cz1n
2c2z1p
2c2z1s
2c2z1t
2c4z3z
2d2ł1b
2dłs4z
d2ł1s
d4ź
2d2ź1ć
2dź1ń
2d2ź1ś
2d4ź3ź
2d2ź1ż
2d2ź1b
2d2ź1c
2d2ź1d
2d2ź1f
2d2ź1g
2d2ź1k
2d2ź1m
2d2ź1n
2d2ź1p
2d2ź1s
2d2ź1t
2d2ź1z
d4ż
2d2ż1ć
2d2ż1ń
2d2ż1ś
2d2ż1ź
2d4ż3ż
2d2ż1b
2d2ż1c
2d2ż1d
2d2ż1f
2d2ż1g
2d2ż1k
2d2ż1m
2d2ż1n
2d2ż1p
2d2ż1s
2d2ż1t
2d2ż1z
2d1ć
2d1ń
2d1ś
2d1b
2d1c
2d1f
2d1g
2d1k
2d1m
2d1n
2d1p
2d1s
2d1t
2d2r1n
d4z
2d2z1ć
2dz1ń
2d2z1ś
2dz1ź
2dz1ż
2dz1b
2d2z1c
2d2z1d
2d2z1f
2dz1g
2d2z1k
2dz1l
2dz1m
2dz1n
2d2z1p
2d2z1s
2d2z1t
2d4z3z
2f1c
2f1k
2f1m
2f1n
2g2ł1b
2g1ć
2g1ń
2g1ś
2g1ź
2g1ż
2g1b
2g1c
2g1d
2g1f
2g1k
2g1m
2g1p
2g1s
2g1t
2g1z
2h1ć
2h1ł
2h1ń
2h1ś
2h1ź
2h1ż
2h1b
2h1c
2h1d
2h1f
2h1g
2h1j
2h1k
2h1l
2h1m
2h1n
2h1p
2h1r
2h1s
2h1t
2h1w
2h1z
2j1ć
2j1ł
2j1ń
2j1ś
2j1ź
2j1ż
2j1b
2j1c
2j1d
2j1f
2j1g
2j1h
2j1k
2j1l
2j1m
2j1n
2j1p
2j1r
2j1s
2j1t
2j1w
2j1z
2k2ł1b
2k1ć
2k1ń
2k1ś
2k1ź
2k1ż
2k1b
2k1c
2k1d
2k1f
2k1g
2k1m
2k1n
2k1p
2k1s
2k1s4z
2k1t
2k1z
2l1ć
2l1ł
2l1ń
2l1ś
2l1ź
2l1ż
2l1b
2l1c
2l1d
2l1f
2l1g
2l1h
2l1j
2l1k
2l1m
2l1n
2l1p
2l1r
2l1s
2l1t
2l1w
2l1z
2m1ć
2m1ł
2m1ń
2m1ś
2m1ź
2m1ż
2m1b
2m1c
2m1d
2m1f
2m1g
2m1h
2m1j
2m1k
2m1l
2m1n
2m1p
2m1r
2m1s
2m1t
2m1w
2m1z
2n1ć
2n1ł
2n1ń
2n1ś
2n1ź
2n1ż
2n1b
2n1c
2n1d
2n1f
2n1g
2n1h
2n1j
2n1k
2n1l
2n1m
2n1p
2n1r
2n1s
2n1t
2n1w
2n1z
2n2t1n
2p1ć
2p1ń
2p1ś
2p1ź
2p1ż
2p1b
2p1c
2p1d
2p1f
2p1g
2p1k
2p1m
2p1n
2p1s
2p1s4z
2p1t
2p1z
2p2l1n
2r1ć
2r1ł
2r1ń
2r1ś
2r1ź
2r1ż
2r1b
2r1c
2r1d
2r1f
2r1g
2r1h
2r1j
2r1k
2r1l
2r1m
2r1n
2r1p
2r1s
2r1t
2r1w
r4z
2r2z1ć
2rz1ł
2rz1ń
2r2z1ś
2rz1ź
2rz1ż
2rz1b
2r2z1c
2r2z1d
2r2z1f
2rz1g
2rz1h
2rz1j
2r2z1k
2rz1l
2rz1m
2rz1n
2r2z1p
2rz1r
2r2z1s
2r2z1t
2rz1w
2s2ł1b
2s1ź
2s1ż
2s1b
2s1d
2s1f
2s1g
2s1s
2s2n1k
2s2t1k
2s2t1n
2sts4z
s2t1s
s4z
2s2z1ć
2s2z1ś
2s2z1c
2s2z1f
2s2z1k
2sz1l
2sz1m
2sz1n
2s2z1p
2s2z1s
2s2z1t
2sz1w
2s4z3z
2sz2l1n
2t1ć
2t1ń
2t1ś
2t1ź
2t1ż
2t1b
2t1c
2t1d
2t1f
2t1g
2t1k
2t1m
2t1n
2t1p
2t1s
2t1z
2t2l1n
2t2r1k
2t2rz1n
tr4z
2w1ć
2w1ł
2w1ń
2w1ś
2w1ź
2w1ż
2w1b
2w1c
2w1d
2w1f
2w1g
2w1j
2w1k
2w1l
2w1m
2w1n
2w1p
2w1r
2w1s
2w1t
2w1z
2z1ć
2z1ś
2z1c
2z1d
2z1f
2z1k
2z1p
2z1s
2z1t
2z2d1k
2z2d1n
3d2niow
dni1
dni2o1
3k2s2z2t
3m2k2n
3m2nest
mne1
3m2nezj
3m2s2k2n
3p2ne2u1
pne1
3w2ład
wła1
3w2łos
wło1
3w2czas
wc4z
wcza1
4ć3ć
4ł3ł
4ź3ź
4ż3ż
4b3b
4c3c
4d3d
4f3f
4g3g
4h3h
4j3j
4k3k
4l3l
4m3m
4n3n
4p3p
4r3r
4t3t
4w3w
4z3z
8ć.
8ć8ć.
8ć8ł.
8ć8ń.
8ć8ś.
8ć8ź.
8ć8ż.
8ć8b.
8ć8c.
8ć8d.
8ć8f.
8ć8g.
8ć8h.
8ć8j.
8ć8k.
8ć8l.
8ć8m.
8ć8n.
8ć8p.
8ć8r.
8ć8s.
8ć8t.
8ć8v.
8ć8w.
8ć8x.
8ć8z.
8ł.
8ł8ć.
8ł8ł.
8ł8ń.
8ł8ś.
8ł8ź.
8ł8ż.
8ł8b.
8ł8c.
8ł8d.
8ł8f.
8ł8g.
8ł8h.
8ł8j.
8ł8k.
8ł8l.
8ł8m.
8ł8n.
8ł8p.
8ł8r.
8ł8s.
8ł8t.
8ł8v.
8ł8w.
8ł8x.
8ł8z.
8ń.
8ń8ć.
8ń8ł.
8ń8ń.
8ń8ś.
8ń8ź.
8ń8ż.
8ń8b.
8ń8c.
8ń8d.
8ń8f.
8ń8g.
8ń8h.
8ń8j.
8ń8k.
8ń8l.
8ń8m.
8ń8n.
8ń8p.
8ń8r.
8ń8s.
8ń8t.
8ń8v.
8ń8w.
8ń8x.
8ń8z.
8ś.
8ś8ć.
8ś8ł.
8ś8ń.
8ś8ś.
8ś8ź.
8ś8ż.
8ś8b.
8ś8c.
8ś8d.
8ś8f.
8ś8g.
8ś8h.
8ś8j.
8ś8k.
8ś8l.
8ś8m.
8ś8n.
8ś8p.
8ś8r.
8ś8s.
8ś8t.
8ś8v.
8ś8w.
8ś8x.
8ś8z.
8ź.
8ź8ć.
8ź8ł.
8ź8ń.
8ź8ś.
8ź8ź.
8ź8ż.
8ź8b.
8ź8c.
8ź8d.
8ź8f.
8ź8g.
8ź8h.
8ź8j.
8ź8k.
8ź8l.
8ź8m.
8ź8n.
8ź8p.
8ź8r.
8ź8s.
8ź8t.
8ź8v.
8ź8w.
8ź8x.
8ź8z.
8ż.
8ż8ć.
8ż8ł.
8ż8ń.
8ż8ś.
8ż8ź.
8ż8ż.
8ż8b.
8ż8c.
8ż8d.
8ż8f.
8ż8g.
8ż8h.
8ż8j.
8ż8k.
8ż8l.
8ż8m.
8ż8n.
8ż8p.
8ż8r.
8ż8s.
8ż8t.
8ż8v.
8ż8w.
8ż8x.
8ż8z.
8b.
8b8ć.
8b8ł.
8b8ń.
8b8ś.
8b8ź.
8b8ż.
8b8b.
8b8c.
8b8d.
8b8f.
8b8g.
8b8h.
8b8j.
8b8k.
8b8l.
8b8m.
8b8n.
8b8p.
8b8r.
8b8r8z.
br4z
8b8s.
8b8t.
8b8v.
8b8w.
8b8x.
8b8z.
8c.
8c8ć.
8c8ł.
8c8ń.
8c8ś.
8c8ź.
8c8ż.
8c8b.
8c8c.
8c8d.
8c8f.
8c8g.
8c8h.
c2h2ł
8c8h8ł.
c2h2r
8ch8r8z.
chr4z
c2h2w
8c8h8w.
8c8j.
8c8k.
8c8l.
8c8m.
8c8n.
8c8p.
8c8r.
8c8s.
8c8t.
8c8v.
8c8w.
8c8x.
8c8z.
8c8z8t.
8d.
8d8ć.
8d8ł.
8d8ń.
8d8ś.
8d8ź.
8d8ż.
8d8b.
8d8c.
8d8d.
8d8f.
8d8g.
8d8h.
8d8j.
8d8k.
8d8l.
8d8m.
8d8n.
8d8p.
8d8r.
8d8r8z.
dr4z
8d8s.
8d8t.
8d8v.
8d8w.
8d8x.
8d8z.
8f.
8f8ć.
8f8ł.
8f8ń.
8f8ś.
8f8ź.
8f8ż.
8f8b.
8f8c.
8f8d.
8f8f.
8f8g.
8f8h.
8f8j.
8f8k.
8f8l.
8f8m.
8f8n.
8f8p.
8f8r.
8f8s.
8f8t.
8f8v.
8f8w.
8f8x.
8f8z.
8g.
8g8ć.
8g8ł.
8g8ń.
8g8ś.
8g8ź.
8g8ż.
8g8b.
8g8c.
8g8d.
8g8f.
8g8g.
8g8h.
8g8j.
8g8k.
8g8l.
8g8m.
8g8n.
8g8p.
8g8r.
8g8s.
8g8t.
8g8v.
8g8w.
8g8x.
8g8z.
8h.
8h8ć.
8h8ł.
8h8ń.
8h8ś.
8h8ź.
8h8ż.
8h8b.
8h8c.
8h8d.
8h8f.
8h8g.
8h8h.
8h8j.
8h8k.
8h8l.
8h8m.
8h8n.
8h8p.
8h8r.
8h8s.
8h8t.
8h8v.
8h8w.
8h8x.
8h8z.
8j.
8j8ć.
8j8ł.
8j8ń.
8j8ś.
8j8ź.
8j8ż.
8j8b.
8j8c.
8j8d.
8j8f.
8j8g.
8j8h.
8j8j.
8j8k.
8j8l.
8j8m.
8j8n.
8j8p.
8j8r.
8j8s.
8j8t.
8j8v.
8j8w.
8j8x.
8j8z.
8k.
8k8ć.
8k8ł.
8k8ń.
8k8ś.
8k8ź.
8k8ż.
8k8b.
8k8c.
8k8d.
8k8f.
8k8g.
8k8h.
8k8j.
8k8k.
8k8l.
8k8m.
8k8n.
8k8p.
8k8r.
8k8s.
8k8s8t.
8k8t.
8k8v.
8k8w.
8k8x.
8k8z.
8l.
8l8ć.
8l8ł.
8l8ń.
8l8ś.
8l8ź.
8l8ż.
8l8b.
8l8c.
8l8d.
8l8f.
8l8g.
8l8h.
8l8j.
8l8k.
8l8l.
8l8m.
8l8n.
8l8p.
8l8r.
8l8s.
8l8t.
8l8v.
8l8w.
8l8x.
8l8z.
8m.
8m8ć.
8m8ł.
8m8ń.
8m8ś.
8m8ź.
8m8ż.
8m8b.
8m8c.
8m8d.
8m8f.
8m8g.
8m8h.
8m8j.
8m8k.
8m8l.
8m8m.
8m8n.
8m8p.
8m8r.
8m8s.
8m8s8t.
8m8t.
8m8v.
8m8w.
8m8x.
8m8z.
8n.
8n8ć.
8n8ł.
8n8ń.
8n8ś.
8n8ź.
8n8ż.
8n8b.
8n8c.
8n8d.
8n8f.
8n8g.
8n8h.
8n8j.
8n8k.
8n8l.
8n8m.
8n8n.
8n8p.
8n8r.
8n8s.
8n8t.
8n8v.
8n8w.
8n8x.
8n8z.
8p.
8p8ć.
8p8ł.
8p8ń.
8p8ś.
8p8ź.
8p8ż.
8p8b.
8p8c.
8p8d.
8p8f.
8p8g.
8p8h.
8p8j.
8p8k.
8p8l.
8p8m.
8p8n.
8p8p.
8p8r.
8p8r8z.
pr4z
8p8s.
8p8t.
8p8v.
8p8w.
8p8x.
8p8z.
8r.
8r8ć.
8r8ł.
8r8ń.
8r8ś.
8r8ź.
8r8ż.
8r8b.
8r8c.
8r8d.
8r8f.
8r8g.
8r8h.
8r8j.
8r8k.
8r8l.
8r8m.
8r8n.
8r8p.
8r8r.
8r8s.
8r8s8z.
rs4z
8r8t.
8r8v.
8r8w.
8r8x.
8r8z.
8r8z8ł.
8s.
8s8ć.
8s8ł.
8s8ń.
8s8ś.
8s8ź.
8s8ż.
8s8b.
8s8c.
8s8c8h.
sc4h
8s8d.
8s8f.
8s8g.
8s8h.
8s8j.
8s8k.
8sk8r8z.
skr4z
8s8l.
8s8m.
8s8n.
8s8p.
8s8r.
8s8s.
8s8t.
8s8t8r.
8s8t8r8z.
str4z
8s8t8w.
8s8v.
8s8w.
8s8x.
8s8z.
8sz8c8z.
szc4z
8szc8z8b.
sz2cz1b
8s8z8k.
8s8z8n.
8s8z8t.
8sz8t8r.
8t.
8t8ć.
8t8ł.
8t8ń.
8t8ś.
8t8ź.
8t8ż.
8t8b.
8t8c.
8t8d.
8t8f.
8t8g.
8t8h.
8t8j.
8t8k.
8t8l.
8t8m.
8t8n.
8t8p.
8t8r.
8t8r8z.
8t8s.
8t8t.
8t8v.
8t8w.
8t8x.
8t8z.
8v.
8v8ć.
8v8ł.
8v8ń.
8v8ś.
8v8ź.
8v8ż.
8v8b.
8v8c.
8v8d.
8v8f.
8v8g.
8v8h.
8v8j.
8v8k.
8v8l.
8v8m.
8v8n.
8v8p.
8v8r.
8v8s.
8v8t.
8v8v.
8v8w.
8v8x.
8v8z.
8w.
8w8ć.
8w8ł.
8w8ń.
8w8ś.
8w8ź.
8w8ż.
8w8b.
8w8c.
8w8d.
8w8f.
8w8g.
8w8h.
8w8j.
8w8k.
8w8l.
8w8m.
8w8n.
8w8p.
8w8r.
8w8s.
8w8t.
8w8v.
8w8w.
8w8x.
8w8z.
8x.
8x8ć.
8x8ł.
8x8ń.
8x8ś.
8x8ź.
8x8ż.
8x8b.
8x8c.
8x8d.
8x8f.
8x8g.
8x8h.
8x8j.
8x8k.
8x8l.
8x8m.
8x8n.
8x8p.
8x8r.
8x8s.
8x8t.
8x8v.
8x8w.
8x8x.
8x8z.
8z.
8z8ć.
8z8ł.
8z8ń.
8z8ś.
8z8ź.
8z8ż.
8z8b.
8z8c.
8z8d.
8z8d8r.
8z8d8r8z.
zdr4z
8z8f.
8z8g.
8z8h.
8z8j.
8z8k.
8z8l.
8z8m.
8z8n.
8z8p.
8z8r.
8z8s.
8z8t.
8z8v.
8z8w.
8z8x.
8z8z.
a1
a2u1
a2y1
a1a2
ae2
ai2
ao2
be2eth
be1
be1e2
be2f3s4z2
be2k1he2n1d
bekhe1
bi2n3o2ku1
bi1
bino1
bi2sz3kop
bis4z
bi2s2z1k
biszko1
bi2z3nes
bi2z3ne2s3m
bizne1
birmin2g1ham
bi2r1m
birmi1
birmi2n1g
birmingha1
blo2k1hauz
blo1
blokha1
blokha2u1
bo2s3ma1
bo1
b2r2d
bro2a2d3wa2y1
bro1
broa2
broadwa1
bu2sz3me1
bu1
bus4z
bu2sz1m
buk2sz3pan
bu2k1s
bu2k1s4z
buk2s2z1p
bukszpa1
busine2s2s
busine2ss3m
busi1
busine1
cal2d1we4l3l
ca1
ca2l1d
caldwe1
c2h2j
c2h2l
chus1t
chu1
cu2r7zon
cu1
cur4z
curzo1
d2ż2ł
d2ż2j
d2ż2l
d2ż2r
d2ż2w
dże4z3b
dże1
dże4z3m
deut4sch3la2n1d
de1
de2u1
deu2t1s
deutsc4h
deutsc2h2l
deutschla1
d2rz2w
du2sz3past
du1
dus4z
du2s2z1p
duszpa1
e1
e2r5zac
er4z
erza1
e2u1
e2y1
e3u2s4z
ea2
e1e2
ei2
eo2
fi2s3ha2r1m
fi1
fisha1
fi2sz3bin
fis4z
fiszbi1
fo2k2s3t
fo1
fo2k1s
fo2r5zac
for4z
forza1
fol2k1lor
fo2l1k
folklo1
fos2f1a2zot
fo2s1f
fosfa1
fosfazo1
ga3d2get
ga1
ga2d1g
gadge1
ga1do3p2ta1
gado1
gado2p1t
gol2f3s
go1
go2l1f
golfs4z2
gran2d1ilo1
gra1
gra2n1d
grandi1
gro4t3r
gro1
hi2s2z3p
hi1
his4z
hu2cz1w
hu1
huc4z
hu2x3le2y1
huxle1
i1
i2ą1
i2ę1
i2ó1
i2a1
i2e1
i2i1
i2o1
i2u1
i2y1
in4nsbru2c1k
i4n3n
in2n1s
inn2s1b
innsbru1
in4sbruc
i2n1s
in2s1b
insbru1
j2t1ł
j2t1r
ja4z4z3b
ja1
ja4z3z
ja4z4z3m
karl2s1kron
ka1
ka2r1l
kar2l1s
karlskro1
karl2s1ruhe1
karlsru1
kir2chho4f3f
ki1
ki2r1c
kirc4h
kirc4h3h
kirchho1
kongre2s3m
ko1
ko2n1g
kongre1
led1w
le1
lu2ft3waffe1
lu1
luftwa1
luftwa4f3f
lu2ks1fer
lu2k1s
luk2s1f
luksfe1
ly2o2
ly1
ma2r5z1ł
ma1
mar4z
ma2r5z1l
ma2r5z1n
mi2s4z1mas4z
mi1
mis4z
mi2sz1m
miszma1
mie2r5z1ł
mi2e1
mier4z
mi1e2r5zi1
mon2t3real
mo1
mo2n1t
montre1
montrea2
moza2i3k
moza1
mozai2
mu2r7zasic2h3l
mu1
mur4z
murza1
murzasi1
murzasic4h
na4ł3ko2w1s
na1
na2ł1k
nałko1
na4r3v
o1
o2y1
oa2
och3mistr4z
oc4h
o2c2h1m
ochmi1
oe2
of2f3set
o4f3f
offse1
oi2
o1o2
ou2
pa2n3a2mer
pa1
pana1
paname1
pa2s3cal
pasca1
pa2s3c4h
połu3d2ni1
po1
połu1
połu2d1n
po3d4niepr4z
po2d1n
podni1
podni2e1
po3m2ną1
po2m1n
po3m2nę1
po3m2ni1
po4rt2s3mo2uth
po2r1t
por2t1s
portsmo1
portsmou2
po4rt3la2n1d
portla1
poli3e2t
poli1
poli2e1
poli3u2re1
poli2u1
powsze3d2ni1
po2w1s
pows4z
powsze1
powsze2d1n
pr2chal
p2r1c
prc4h
prcha1
pre2sz3pa1
pre1
pres4z
pre2s2z1p
ro2e3nt2gen
ro1
roe2
roe2n1t
roen2t1g
roentge1
ro2k3roc4z
rokro1
ro2s3to3c2k
rosto1
se2t3le1
se1
sko2r5zoner
sko1
skor4z
skorzo1
skorzone1
s2m2r
sowi3z2
so1
sowi1
sy2n3o2p1t
sy1
syno1
sy2s1tem
syste1
sza2sz1ły1
sza1
szas4z
sze2z1lo2n1g
sze1
szezlo1
sze4ść
szto2k1ho2l1m
szto1
sztokho1
szyn2k1was
szy1
szy2n1k
szynkwa1
to3y2o3t
to1
to2y1
to1yo2
turboo2d3rzut
tu1
tu2r1b
turbo1
turbo1o2
turboodr4z
turboodrzu1
tygo3d2ni1
ty1
tygo1
tygo2d1n
u1
u2y1
ua2
ue2
ui2
uo2
u1u2
vo2l2k2s3
vo1
vo2l1k
we2e2k1e2n1d
we1
we1e2
weeke1
we4s2t3f
we4s2t3m
y1
ya2
ye2
yi2
yo2
yu2
ze4p3p
ze1
.b8e9z8a8c8h.
.bezac4h
.b8e9z8a8m8i.
.bezami1
.b8y9n8a8j9m8n8i8e8j.
.by1
.byna1
.byna2j1m
.bynaj2m1n
.bynajmni1
.bynajmni2e1
.g8d8z8i8e9n8i8e9g8d8z8i8e.
.gd4z
.gdzi1
.gdzi2e1
.gdzieni1
.gdzie1ni2e1
.gdzienie2g1d
.gdzieniegd4z
.gdzieniegdzi1
.gdzie1nie1gdzi2e1
.i8n8a9c8z8e8j.
.ina1
.inac4z
.inacze1
.n8a9d8a8l.
.n8i9g8d8y.
.ni2g1d
.nigdy1
.n8i9g8d8z8i8e.
.nigd4z
.nigdzi1
.nigdzi2e1
.n8i8e8c8h9ż8e.
.nie2c2h1ż
.niechże1
.n8i8e8c8h9b8y.
.nie2c2h1b
.niechby1
.o8w9s8z8e8m.
.o2w1s
.ows4z
.owsze1
.p8ó9ł8a8c8h.
.póła1
.półac4h
.p8ó9ł8a8m8i.
.półami1
.p8ó9ł8e8k.
.p8o8d9ó8w9c8z8a8s.
.podó4w3c4z
.podó2w1c
.podó3w2czas
.podówcza1
.p8r8z8y9n8a8j9m8n8i8e8j.
.przyna1
.przyna2j1m
.przynaj2m1n
.przynajmni1
.przynajmni2e1
.s8k8ą8d9i8n8ą8d.
.skądi1
.skądiną1
.t8r8ó9j8a8c8h.
.trója1
.trójac4h
.t8r8ó9j8a8m8i.
.trójami1
.t8r8ó9j8e8k.
PK
!<_hyphenation/hyph_pt.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
1b2l
1b2r
1ba
1be
1bi
1bo
1bu
1bá
1bâ
1bã
1bé
1bí
1bó
1bú
1bê
1bõ
1c2h
1c2l
1c2r
1ca
1ce
1ci
1co
1cu
1cá
1câ
1cã
1cé
1cí
1có
1cú
1cê
1cõ
1ça
1çe
1çi
1ço
1çu
1çá
1çâ
1çã
1çé
1çí
1çó
1çú
1çê
1çõ
1d2l
1d2r
1da
1de
1di
1do
1du
1dá
1dâ
1dã
1dé
1dí
1dó
1dú
1dê
1dõ
1f2l
1f2r
1fa
1fe
1fi
1fo
1fu
1fá
1fâ
1fã
1fé
1fí
1fó
1fú
1fê
1fõ
1g2l
1g2r
1ga
1ge
1gi
1go
1gu
1gu4a
1gu4e
1gu4i
1gu4o
1gá
1gâ
1gã
1gé
1gí
1gó
1gú
1gê
1gõ
1ja
1je
1ji
1jo
1ju
1já
1jâ
1jã
1jé
1jí
1jó
1jú
1jê
1jõ
1k2l
1k2r
1ka
1ke
1ki
1ko
1ku
1ká
1kâ
1kã
1ké
1kí
1kó
1kú
1kê
1kõ
1l2h
1la
1le
1li
1lo
1lu
1lá
1lâ
1lã
1lé
1lí
1ló
1lú
1lê
1lõ
1ma
1me
1mi
1mo
1mu
1má
1mâ
1mã
1mé
1mí
1mó
1mú
1mê
1mõ
1n2h
1na
1ne
1ni
1no
1nu
1ná
1nâ
1nã
1né
1ní
1nó
1nú
1nê
1nõ
1p2l
1p2r
1pa
1pe
1pi
1po
1pu
1pá
1pâ
1pã
1pé
1pí
1pó
1pú
1pê
1põ
1qu4a
1qu4e
1qu4i
1qu4o
1ra
1re
1ri
1ro
1ru
1rá
1râ
1rã
1ré
1rí
1ró
1rú
1rê
1rõ
1sa
1se
1si
1so
1su
1sá
1sâ
1sã
1sé
1sí
1só
1sú
1sê
1sõ
1t2l
1t2r
1ta
1te
1ti
1to
1tu
1tá
1tâ
1tã
1té
1tí
1tó
1tú
1tê
1tõ
1v2l
1v2r
1va
1ve
1vi
1vo
1vu
1vá
1vâ
1vã
1vé
1ví
1vó
1vú
1vê
1võ
1w2l
1w2r
1xa
1xe
1xi
1xo
1xu
1xá
1xâ
1xã
1xé
1xí
1xó
1xú
1xê
1xõ
1za
1ze
1zi
1zo
1zu
1zá
1zâ
1zã
1zé
1zí
1zó
1zú
1zê
1zõ
a3a
a3e
a3o
c3c
e3a
e3e
e3o
i3a
i3e
i3i
i3o
i3â
i3ê
i3ô
o3a
o3e
o3o
r3r
s3s
u3a
u3e
u3o
u3u
1-
.h8a8r8d9w8a8r8e.
.hardwa1re
.s8o8f8t9w8a8r8e.
.softwa1re
PK
!<Gbbhyphenation/hyph_ru.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.аб1р
.а1б
.аг1ро
.а1г
.а1ди2
.а1д
.а1и2
.ак1р
.а1к
.а3ль3я
.аль1
.ар2т1о2
.а2р1т
.ас1то
.аст1р
.а1у2
.би2о
.б2и
.во2б3л
.во1б2
.во3ж2д
.во1ж
.го2ф
.дек2
.де1кв
.ди2а1к
.ди1а
.ди1о
.до3п2
.до3т2
.епи3
.е1п
.зав2р
.за1
.за3м2н
.за1м
.за3п2
.иг1р
.и1г
.и2з1г2
.из3н
.и1и2
.ик1р
.и1к
.ио2
.ио4на
.и1он
.ис3
.л2е2о
.ле2п3р
.ле1п
.ле2с1к
.ль2
.люст1
.л2ю
.ме2ж1у2
.ме1ж
.ми1о1м
.мо2к1
.му2шт1
.му1ш
.на1в
.на3т
.на3ш2
.не8
.не3в1н
.не1др
.не1з2
.н2е1сл
.не1с2ц
.не3т
.нос1к
.нук1л
.обо3ж2
.о1бо
.ово1
.о1в
.ог3н
.о1г2
.оз4
.ос2ка
.ос2п2
.ос3пи
.от1в2
.о1т
.от1ро
.от1ру
.от1у1ж
.по3в2
.по3ж2
.по1з2н
.прос2
.ра2с3т
.ре2бр
.р2еб
.ре2з3в
.ри2с1к
.ри1с2
.ри2ч
.ро2з3в
.ро2с3л
.ро2х
.се4п1т2
.с2е
.се1п
.ск2
.ст2
.су2ж
.т2е2о3
.т2е
.ти1а3
.ти2г
.тиг1р
.ти2о
.уб2
.уд2
.у1е2
.уз2на
.уз1н
.ук2
.у2м2ч
.у1м
.у1о3
.у1п2
.ур2в
.ус2
.ут2р
.у1ю2
.хо2р3в
.че2с1к
.юс1
4а3а
аа2п
аа2р
аа2ц
а1б
абе3с1т
а1бе
а3бла
аб1л
аб2л2ю
аб1ри
а3б2у
ав1в
а1ве
ав3зо
а1в2з2
а1ви
ави2а
а1во
аво1с
а2во1т
ав1ра
а3в2с2е3
а2в1с2
а2в1т2
а1ву
а2вх
а3в2че
а2в1ч
2аг2а
а1г
ага1с2
а2гд
а2гити
аг2и
аги1т
а2г1ле
аг2ли
а2глос
аг1ло
аг2ло1т
2аго
а3гу
а1д
2адв2
а2д1ве
ад2жи
ад1ж
ади2од
а1ди
а2д1л
а2д1о2б1л
а1до
ад1ро
а2д1ру
аду3ч
а1ду
ад2ц
а2дын
а1ды1
а1е
ае2го
ае1г
ае2ди
ае2л
а2е1п
ае2ре
ае2с
аза4ш3
а1за1
азв2
аз3вез
аз1ве
аз1в1л
а2з1г2
аз1др
аз1об
аз2о1б1р
а2зовь1
а1зо1в
а2золь1
а1зол2
а1зо1ри
аз2о1с2
аз1р
а1и
аи2г1
а2и3гл
а2их
а1к
ак1в
1а2к1к2
ак2л
а2к3ле1м
ако1б2
ак2о
2а3ко2н1с2
ако3т
2акри
а2к1с
а1ла
а3ла1г
а1ле
2алек
а3ли
ало1з
а1ло
а1лу
алу2ш
алуш1т
а1лы1
а2ль1щ
аль1
а1л2ю
2ама
а1м
а4м1б4
2амет
а2ми2н1т
ам2нет
а2м1н
ам1не
2амо
амо1з2
амо3и2
а2мч
ана2д2ц
а1на
ана1д
а2н1а2ме
ана1м
а2на1ф
ан2дра
а2н1д
а2н1о2б2
а1но
ан1о2хр
ан1р
ан2с1п2
а2н1с
ан1с1у
ан2су2р
а2н1уз
а1ну
а1нь1
2а1о
ао2д
ао2к
ао2р
ао2с
ао1ст1
а3пла
а1п
ап2ло1м
ап1ло
2апо
апо4в2с2
апо1в
апо3ч2т
ап2ра
ап1ре1л
а1ра
ара2с1т
ар2бо1к
а2р1б
ар1бо
ар2вал
ар1ва
1аргу
а2р1г
а1ре
аре1дв2
аре1ол
ар2ео
ар2жа
ар1ж
а1ри
а1ро
ар2тор
а2р1т
ар2т1р
а1ру
а2р1х
а1ры1
а1р2ю
а1ря
2ас1к
ас3ми
ас2м
а2с3но
ас1н
1асси1г
а2с1с
а4с5с2и
ас2т1ву
ас3те1м
ас2т2е
ас2тин
а1сти
ас2ти1я
ас1то1о2
ас1тух
а1с3ть2е
ас4ть1
ас2шед
ас1ш2
ас2ше1с2
а1сь2и1
ась1
а1та
1ата1к
а2т3ва
а2т1ви
а2т1ву
2атез
а1т2е
а1ти
а1то
ат1о1бе
а2то2м1н
ато1м
ато2ш
ат1рах
ат1ри
а1ту
а2т2х
а1ты1
а3ть2е
а2ть1
а3ть2ю1
а3ть2я
а1т2ю
а1тя
а1у
а2уб
ау2д
ау3до
а2у1ле
аут1р
ау2х
ау2ч
ау3чь1
а2у1э1
а2ф1л
а1ф
а1х2а
ахми2
ах1м
а2х3с2
а1ч
2а1ча
а2чл
ач1т
а2шл
а1ш
аэ2ли
а1э
а2эр
аю1та
а1ю
а1я
ая2б
ая2в
ая2з
1ба
ба2б1в
ба1б
ба2г1р
ба1г
ба2др
ба1д
ба1з
ба3зу
ба1л2ю1
б2а2о
бас3м
ба1ст
ба1тр
2б1б
б1в
б1вы2
б1г2
2б1д
1бе
3бев
бе2гл
бе1г
бе2гн
бе2д1р
3бе1е
3бе2з
бе1з1а2
без5д4
бе3зи
без3н
без1о2
без1р
бе2с1к
б2ес3п2
бе2с1т
бес3т2е
бе1с3ти
3бе1ц
2бе1щ
2б1ж
б1з2
1б2и
3би1а
б2и2б
2би1ж
3би1к
били3т2
би1л
би1ли
3био
би2об
би2од
би2он
би2ор
би2тв
би1т
би1х
2б3к
б1л
1бла1г
1б2лаз
б3ла1зи
б2лан
1б2ле1е
б3лен
б2ле2с1к
1б2ле1я
б2луд
1б2лу1ж
2блы1
2б2ль1
2б3лю.
бл2ю
б2люд
б2лю1е
б2люл
2б3люсь1
2б1ля
2б3н
1бо
бо1бра
боб1р
бо3в1ш
бо1в
бо2гд
бо1г2
бо1дра
бо1з2
бо1л2ж
бо1льс
боль1
бо3м2л
бо1м
бо2м2ч
бо3м1ш2
бо1ну1
бо1ру
бо2са
бо1ск
бо3ск2о
бо3сти
бо1ст
3бо1т
бо2тв2
бот2р
боя2р
бо1я
2бр.
б3ра1б
б2рав
бра1зо
1б2рал
2б1ра1м
б2ран
1брас
б2ра2ть1
б1рах
1б2ра1ч
2б3ра1я
1б2ред
б1ре2й1
б1рек
б2ре1м
б2рех
б2рид
б2рито
бри1т
б2риты1
1б2ро1ди
б1рол
б1ром.
бро1м
1б2ро1с2и
бро2с1к
2б4р1с
б1ру
3б2ру2кс
2брь1
1б2р2ю
2б3рю.
б1ря
2б1с2
б3ск
бс4л
б1т
1б2у
бу2г1р
бу1г
бук1л
бу1с
2б1ф
2б1х
2б1ц
2б1ч
2б1ш
2б1щ
1бы1
бы2г1
бы2с
быс1к
быст1
1бь1
2бь.
2бьс
2бьт
бэ1р
б1э
3б2ю
бю1та
1бя
1ва
ва2бр
ва1б
3ва1г
ва2д1р
ва1д
вадь2
ва3ж2д
ва1ж
ва1з
ва2н1с2
ва1ст
ва2стр
ва1тр
вах1
3ва1ц
3ва1я
2в1б
в1ви
в1вр
2в1г2
в1д
в2дох
в1до
1вев
3ве1г
вед1р
ве3ду
1ве1е
1вез
3ве3зе
3вез1л
ве1з2у
1вей.
ве2й1
ве2п1
2ве2р1д
1вес
ве2с1к
ве2ст1в
ве1ст
вет3р
1ве1ц
1ве1ю
1ве1я
1в2з2
в2з1г2
взд2
взо1б
взъ2
взъе3д
ви2аз
ви1а
ви2а1к
ви2ар
ви2а1с2
виа1т
ви3а1ф
ви2гв
ви1г
в2и2гл
1виз
1ви2н1т
1ви2н1ч
ви1о
ви1с2ни
вис1н
ви1у3
ви2ф
2в1к
вк2л
3в2кус
вк2у
в1л
в2ла
2в3ла1б
в2лев
в2лек
в2лет
в2леч
2в1ли
в2ли1я
2вл2ю
в2лю1б
2в1ля
2в1м
1вме
2в1н
4в3на
в2нес
в1не
в1но1
в3ну.
в1ну
3в2нук
3в2нуч
в3ны1
во1б2
во2б3ла
воб1л
во1в2
во3в1к
1вод
во1дв2
во1др
во2ер
во1е
во2ж1ж
во1ж
вои2с1
во3и
1во1к
во3м2
во1п2
во1ру
2в2о2р1ц
2ворь1
вос1к
во1с2м
во1с1н
вос3пе1
вос1п2
во2стр
во1ст
вот2р
во1т
1вох
во1хл
во3х2т
1в2о1ю
2в1п2
2вр.
2в1ра.
в2рав
2в1ра1м
в1рас
2в1рах
2вра1ц
2в1ре.
2в1рен
1врид
1в2риз
в1ри1и
в1ри1к
в1ри1л
в1ри1с2
в1ри1т
2в1ро
вро3т2
2в1ры1
1вр2ю
в1ря
2в1с2
3в1с2е3
в3ска1я
4в3ски
4в3ск2у
3в2с1п2
3в2с2ю
в1т2
вто1б2
вто3ш
1ву1а
ву3г
1ву1з
2ву1и
2ву1к
ву3п
ву1с2
ву2х1а
ву2х3в
ву1чл
в1ф2
1в1хо
2в1ц
2в1ч
2в1ш
3в2шив
2в1щ
въ2
1вы1
вы3г2
вы3з1н
вы1п2
вы3т2
вых2
вы3ш2л
вы1ш
2вь.
вь1
1вь2е
1вьин
вь2и1
2вьс
2вьт
1вь2ю
1вь2я
1в2э1
1в2ю
1вя
1г
г2а
га1з
га1ст2
га2у
2г3б
г1ба2
г1ви
2г1г
г3дан
г1да
2г3ди
3ге.
г2е2б1
ге1но1
ге2о2б
г2ео
ге2од
ге1ор
2г3ж
2г1з
г2и
ги2б1л
г2и1б
ги3б1р
ги2гр
ги1г
ги1сл
ги1ст2
2г1к
2г1ла.
г2лав
г1ла2й1
г1лами
гла1м
2глась1
2г1ла1я
г1ле
г2лет
2гли.
г1ли
г2лин
3г2ли1ф
2гло.
г1ло
г3ло2б1л
2гло1в
2гло1г2
2гло1е
2гл2о2й1
2гл2о1ю
2г1лу1ю
2г1лы1
г2ля1ж
г1ля
2гля1к
2г3м
г2нав
г1на
г2нан
г8не.
г1не
г2нев
г3нен
г3не3п2
г3нес
г2ни1р
г1ни
гни2т2р
гни1т
г2но1е
г1но
г2но3и
г2нос
г3ня
го1б2
го2в1л
го1в
го3ж2д
го1ж
го1з
го2з1л
гоз2н
1гои2г2
го3и
3г2о2й1
г2ол
гоми2
го1м
го2с1а
го2сд2
го1ск2л
го1с1н
го1спа
гос1п2
2г1о2т1д
го1т
гоу3т
го1у2
го1ч2л
3г2о1ю
2г1п
2гр.
г1ра1е
г1ра2й1
г1рар
1г1ре1г
г1рек
г1ре1ц
гри4в3н
г1ри1к
г1ри1л
г1р2ин
г1ри1с2
г1ри1ч
г1ро1в
г2роз
г1ро1к
г1рон
г1ро1п
г1ро1т
г1ро1ф
гру2п
г1рыв
гры1
2гр2ю
г1ря1е
г1ря1л
г1рят
2г3с2
г4са
г4сб2
2г3т
гу1в
гу1с
гу2с1к
2г1ф
2г1ч
2г3ш
2г3э
1да
да2б1
да2ген
да1г
да2гр
да1з
д2а2о
даст1р
дат1р
2д1б
дв2
д1ве
1дви
2д1вид
2д1виз
2д1ви2н1т
2д1ви2н1ч
2д1вис
2д1ви1т
д3в1к
д1в1л
2д1вод
д1воз
1д2ворь1
2д1вя
2д1г2
2д1д2
1де
де1б2л
д2еб
де1б2р
3девр
3дез
де2з1а2
де2зи
дез1о2
де2зу
деи2о2
де1и
де1кл
3деме
де1м
де2од
д2ео
део3п2
де3пл
де1п
дерас2
де1ра
де2с3в
де1с2к
де2ср
де1хл
2дж.
д1ж
д2жа1м
д2ж3м
2д2ж1с
2д1з2
1ди
ди2а1д
ди1а
диа2з
ди2а3ли
ди2а1ло
ди2ар
ди2ас
ди2об
дио3де
ди1од
ди2ор
дио1с
ди1о3ти
дио1т
д2и1п2
ди2пи
ди3п1т
ди2с1тр
ди1ст
ди1у3
ди3фр
ди1ф
ди3фто
ди2ф1т
ди1х
2д1к
д1л
д2лев
2д3м2
2д1н
д3на
дне1а2
д1не
3д2не2в1н
4д3но1
дно3д2
днос2
4д3ны1
3д2ня1ш
д1ня
1до
2д1о2бед
до1бе
до2б1л
2д1обла
до1б2ра
доб1р
дов2л
до1в
до3в2м
до1д2
до3д1н
до3ж2д
до1ж
до1з
доз2н
дои2р
до3и
2д1о2к1т
до1к
2доли1м
до1ли
до2м1р
до1м
до1п2
до3пл
2допле
до2пре
до2руб
до1с
д1о2сен
до1с2е
д1о2син
до1с2и
2д1о2с1но
дос1н
дос2п2
2д1о2т1д
до1т
2до2т1л
дот2ри
2д1отря
2до2тъ1
до3ть1
3дохл
до2ш3в
до1ш
до3ш2к2
до2ш1лы1
до2щу
до1щ
2д1п
2др.
д1ра1б
1дра2в1ш
2дразв2
1д2раз1н
д1ра1не
д1рар
д1ра2с3
д1рах
д1ра1ч
д2ра1ю
д1ре
д2р2еб
2д3ре1ж
2дрез
д2рел
д2ре1м
1дрема
1дре2м1л
дре2м3н
1дремы1
2д3рен
дре2ск
д2ре2с1с
д1ри
д2ри2й1
2др2ин
д2р2и1п
д2рих
дро2г3н
дро1г2
д1род
д1ро1е
1д2ро1ж
2д3роз
д1р2о2й1
д1рол
д1рон
д1рос
д1ро1т
д1р2о1ю
д1руб
1дру1г
1дру1ж
д1ру1м
д1ру1ю
д1ры1
2дрыв
1д2ры1г
д1ря
д2ряб
1д2ря1г
д2рях
2д1с2
дск2
д2с3к1н
2д1т
1ду
дуб3р
ду3г
2д1уд
ду2да
ду2о
ду2п1л
ду1п
дус1к
д1ус2л
ду1ст
ду2ста
2дут1р
ду1х
ду2чи
дуэ1т
д2у1э
2д1ф
д1х
2д3це
д1ц
2д3цу
2д3цы1
2д1ч
2д3ш2
2д1щ
2дъ1
дъе2м
1ды1
2ды1г
ды2г1р
2дыд
2дыме
ды1м
2ды2с1
2дыт
2ды1щ
2дь.
дь1
1дь2е
2дьк
2дьт
1дь2ю
1дь2я
дь3яр
1д2ю
1дя
е1а
еа2д
еа1ди3
еа3до
еа2з
еан2д1р
еа2н1д
еат1р
2еб
еба2с
е1ба
е1бра
еб1рен
еб1ри
е1бро
еб1ро1в
еб1ры1
е2б3р2ю
е1ве
2евер
е1ви
е3в2ме
е2в1м
ев2ни1м
е2в1н
ев1ни
ев2нят
ев1ня
е1во
2евол
ев1ра1с
2е1вре
ев1ре1е
ев1ре2й1
ев1ре1я
ев1ри
е2в1т2
е1ву
е1вх
е1в2хо
е1вь1
ега1с2
е1г
ег2а
ег2д
е2глан
е2г1ле
е2г1ли
е2г1ло
ег2на
ег2но
2ег2р
ед1во
едв2
ед2ж
е1дже
е1д2лин
ед1л
ед1ли
едно1у3
е2д1н
е4д3но1
ед1опр
е1до
едо1п2
е2дотв2
едо1т
е2дох
е2д1о1щ
е1дру
е2д1ру.
е2ду2б
е1ду
ед1у1бо
е2дуве
е2ду3г
е2дус
ед1у1ст
2е3ду1ш
е2дын
е1ды1
е1е
е2евид
ее1ви
ее2в1р
ее2г2и
ее1г
ее1с2
ее2ст
еест1р
ее2х
е2ж1г2
е1ж
е4ждев
е2ж1д
еж1де
еж3ди
2еже
е2ж1р
еза2вр
е1за1
еза1у3
е1з2ва
езд1р
е3зе
ез1зу3
ез3з2
е3зи1т
е1зи
ез1об
ез1о2г2
е1зо3м2
ез1о1п
ез1о2р
ез1о1т
ез1о1ш2
ез2ря
ез1у2д
е1зу
е2з1у2к
е2з1у1п
ез1ус
езу2со
езу2сы1
ез1у2х
ез1у1ча
е3зя
е1и
еи2г1
еи2д
еи2м
еи2о
еис1л
еис1тр
еи1ст
е1ка
ека2б
е2к2з
е1ки
2е1к2о
2е1кр
ек2ро
ек1ск
е2кс
ек1с3т2е
е1к2у
е1ла
е1ле
еле3ск
еле1с1ц
е1лу
е1лы1
е1л2ю
е3ля
еми3д2
е1м
еми3к
емо1с
2ему1ж
е2мч
2емы2с
емы1
е3на
ен2д1р
е2н1д
2е1нр
е2н1с2
е2н3ш2
е1н2э
2ео
е1о2б
еоб2ро
еоб1р
е2о3гл
ео1г2
ео2гро
е1од
ео3да
ео2де
ео3де3з
ео2до
е1о2ж
е2о3и
е2о3к2л
ео1к
е1ол.
е1о3ла
ео3ли
е1о2л1к
е1о1лы1
е1оль1
е2о1м
е1он.
е2о3на
е2о1ни
ео3но
е1о2н1с2
ео1п2
е1опе1
ео2пр
ео4пу
е2о3ро
еос2
е1о2сви
ео1с2в
ео1ск
е1ос2м
е1ос1н
еост1р
ео1ст
е2о3с1х2
е1о2т1л
ео1т
еот2ру
е1о2ч
е1о2щ
епат2
е1п
епа1тр
2епе1
епис2к
е2пл
е3пла
еп1ле1ш
е3п2лод
еп1ло
еп1лу
е3плы1
еп1лю1щ
епл2ю
е4п1н
2епо
е4п3с2
е4п1т
е1ра
ер1а2к1т
ера1к
е2рв
ер1ве
е1ре
е3ре.
ере3до
ере1др
ере1к2
ере3м2н
ере1м
ере3п
ере1х4
е1ри
ери1о3з
е1ро
еро2б
ер1об1л
2ерови
еро1в
2е1рокр
еро1к
2ерол
еро3ф2
ер3с2к
е4р1с
е1ру
е2р1у2п
е1ры1
е1р2ю
е1ря
е3с2а
ес2ба
е1сб2
е1с1г2
е1ск
е2с1ка.
ес1кал
е2с1ке
е2с1ко1в
еск2о
е4с1ку.
еск2у
2есл
ес1лас
ес2лин
ес1ли
ес2ло1в
ес1ло
ес2ло1м
е1слу
е1слы1
е1с4м
е3со
2ес1п2
ес2пек
еспе1
е2с3пол
е2спу
е1ст
ес2т2ан
е2с2т1л
е3сту
ес2чет
есч2
ес1че
е1та
ет1ве
е2т1ви
е1тво
2етеч
е1т2е
е1ти
е1то
ето1с
ет1р
ет2ря
е1ту
е1ты1
е3ть2е
е2ть1
е3ть2ю1
е3ть2я
е1т2ю
е1тя
е1у2
2еуб
еуб3р
еуз2
еук2ло
еф2и3б2
е1ф
еф4и
еф2л
еф1ре
еха2т
е1ха
ех1а1то
ех3вал
е1х2в
ех1ва
ех3ло1п
ех1ло
ех1об
е1хо
ех1опо
ехо1п2
ех1ре
ех1ру
ех1у2ч
2ецв
е1ц
е1чл
е2шл
е1ш
е1э2
ею2г
е1ю
е1я
ея2з
1ж
жа2б1л
жа1б
жа2бр
жа1з
жат1в
2ж1б2
2ж1в
ж1г2
2жг2а
ж2г2и
3ж2гл
ж2гу
2ж1д
ж2да1к
ж1да
ж2да1ч
3ж2дел
ж1де
4ж3деме
жде1м
ж2де1п
ж2ди
4ж2д1л
ж1до3
ж1ду1
4ждь1
3ж2дя
3жев
же3д2
же1к2в
же1кл
ж2е1о2
же3п2
же1с2
же3ск
2жжа
1ж1ж
ж2же
2ж3жев
2ж1з2
жи1о
2ж1и2р1р
жи1р
2ж1к
2ж1л
ж2м
ж3ма
2ж3мо
2ж1н
ж1но1
2ж1об
2ж1о2т1
жо1у3
жоу1с
2ж1п2
жпо1
ж2ру
2ж1с
2ж1ф
2ж1ц
2ж1ч
2жъ1
2жь.
жь1
2жьс
2жьт
1за1
з4а3а2
за1б2
за2в1ри
за2вру
з1а1ву
за1г4
з1адр
за1д
зае2д
за1е
зае2х
за3ж2д
за1ж
за3з2
з1а2к1т
за1к
за3м1не
за1м
за2м1н
3з2ан
за3на
за2н1с2
за1п2
зар2в
за3р2д
зар2ж
зас2
заст2
зат2
за3т1к2
за1у2
зах2
зач2т
за1ч
за3ш2
за1я2
з1б2
2з3ва.
з1ва
з2вав
з3валь1
з2ван
2з3ва1я
з1ве
з2вез
з1ви
з3в2к
з1в2ла
зв1л
з1во
2звол
1з2вон
з1вр
1зву
2з1ву1ю
з1вь1
2з1г
з3г2а
з2г1ли
зг2на
з2г1ну
з1д2в2
з2де1ш
з1де
здо1ж3
з1до
1зе
з2е2б1
зе2ев
зе1е
зе2од
з2ео
2з1ж2
з3з2
1зи
3зи.
3зий.
зи2й1
з1и2н1т
зи2оз
зи2о1но
зи1он
зи1о1п
3зис
зи3т2р
зи1т
зиу3м
зи1у2
3зи1ч
2з1к
зк2о1
зко3п2
з1л
з2ла1щ
з2лоб
з1ло
з2ло1п
з2лор
з2лю1щ
зл2ю
2з1м2
з3м1н
з1н
2зна.
з1на
з2нав
з2на1е
з2на2й1
з2на1к
з2нан
з2нат
з2на1ю
2з3на1я
2з1не
2з3ни
2з1но
2з1ну
2з3ны1
з2о1бе
зо2б2и
1зо1в
зо3в2м
зо2гл
зо1г2
зо1др
1зо1е
зо1з2
1зо3и
1зой.
з2о2й1
1зок.
зо1к
з1о2кс
1зол2
зо1л1г
зо1л1ж
зо3м2
1зом.
2зо2м1н
1зон
2зо1нр
1зо1о2
зо2о3п
зо2ос
зо2па
зо1п
з2опл
з2опр
з1о2р1г
1з2о3ре
зос2
з1ос1н
зо1с1п2
зо2тв2
зо1т
з2о1т2е
з1о2т1к2
з2ото
зот2ре
зот2ри
1зох
зо1ш2
зо2ши
1зо1э
1з2о1ю
з1ра
з2ра1к
зра2с
з2ра1ч
з2рен
з1рес
з2ри1ш
з1ро
зро2с3
з1ру
з2р2ю
з1ря
2з1с
2зт
з1ти
1зу
3зу.
2з1у2бе
зу2б3р
зу1в
2зуве
2зу2г
3зу1е
2з1уз3
2зу1к
3зуме
зу1м
з1у2мо
2зу1п
зу2пр
з1у2р1б
з1у2т2е
зу2час
зу1ча
2з1ц
з1ч
2з1ш
зъе2м
зъ1
1зы1
2зы2г1
зы2з
2зыме
зы1м
2зы2мч
2зы2с1
2зы1щ
1зь2е
зь1
1зь2и1
1зь2ю
3зь2я
1з2ю
1зя
и1а
и2а1б
и2ав
иа1г2
и2агр
и2а1де
иа1д
и2а1ди
иа2зо1в
иа2му
иа1м
и3а1на
иа2нал
иа2н1д2
и2а1о2
и2а1п
и2а1с2к
иа1ста
иа1сто
иат1ро
и3а1ту
и2а1ф
и2а1х
иа2це
иа1ц
2и1б
и2б1р
2ива1ж
и1ва
2и1ве
и2в3з2
и1ви
2и1во
и1в2р
и3в2с2
и1ву
и1в2хо
2и1вы1
иг2д
и1г
и3ге
2игл
и2г1ле
и2г1ли
и2гн
иг1ни3
иг1рен
иг1ро
иг1ру
иг1ры1
и2г1ря
и1дв2
и2де2й1
и1де
и1д2ж
иди1о1м
и1ди
иди1о1т
ид1р
и1дь1
и1е
и2е1вод
ие1во
ие2г
ие2д
ие3де
ие2зу
и3е1ни
и2е1о2
иепи1
ие1п
ие2р
и3ж2д
и1ж
из1в2
из2г1не
и2з1г
1из1д
из2нал
из1н
из1на
и1зо
и1зо2о2
из1р
и1и
ийс2
и2й1
и1к
и3к2а
и3к2а1с2
ик2ва
и2к1ви
и2к1ля
и3к2о
ик1ро
ик1ск
и2кс
ик2с1т
и3к2у
и1л
и2л1а2ц
ило1с1к
и1ло
и2л1п2
и2л1у2п
и2ль1
и2льт2
2има
и1м
и2ме1но
и2ме1ну
2имень1
и3ми
имо3и2
им3п2л
и2м1п
и2м1р
и2мч
им2ча
и2н1д2
1ин1ж
ин2о2к3л
и1но
ино1к
ино3п2л
ино1п
ино1с
и2н1с2
1инс1п2
1ин1сти
1ин1су
1и2н1ф2
1инъ1
и1об
ио2бо
ио2вр
ио1в
и2о1г2
и1од
ио2де
и1оз
ио3зо
и1о2кс
ио1к
и1о1ле
и1он
и3оно1в
ио1но
и1о2п1т
ио1п
и1ор
и3о1ра
ио1ру
ио2са
ио3ск2л
ио1с2п2
и1ота
ио1т
ио2т1в2
и1о2т1к2
и1о4т1с2
иоу1г2
ио1у2
ио2хо
и1о1ш
2и1п
ипат2
ипа1тр
ип2ля
и2п3н
ипо3к2
и1р
ира2с1т
и2р1а1у
и2рв
и2р1ж
ири2с1к
ири1с2
и1ри1у3
иро1з2
1и2р1р
иса2н2д1
и1са
и2сб2
и2сд2
ис1к
ис3ка.
и2с3ка1м
и2с3ках
ис3ке
ис3ки
ис3ко1в
иск2о
ис3ку.
иск2у
и2с1ла1м
ис1лы1
ис3ме
ис2м
ис3му
и2с3но
ис1н
исо2ск
и1со
и2с3пр
ис1п2
и4с1с
и1ст
и2ст1в
и2с2т1л
ис1тяз
и1сь2и1
ись1
и1т
ита2в
и2т3ва
и2т1ве
и2т1ви
и2т1ву
и2т1м2
и2т1р
и3т2ре2с
ит3ро1м
и2т1уч
и3ть2ю1
и2ть1
и3ть2я
и1у2
иу3п
иф1л
и1ф
иф2л2ю
и2фр
иха3д
и1ха
и2х1ас
их2ло2
и1х2лор1
и3х2о
и2х1о3к
их1ре
их1ри
и1ху
и1ч
иш2ли
и1ш
и2ш1лы1
и2шт
ию4л
и1ю
ию2н
ию2т
ию3та
и1я
ия2д
2й1
йд2
й2д3в2
й1но1
й2о1с
йо2тр
йо1т
йп2л
й1п
й2сб2
й3ска
йс2ке
йс4мо
йс2м
й2с3му
й2с1н
й2с3ф
й2с1ш2
й2т1м2
й2х1м
й2х2с3
йя1
ка2б1л
ка1б
ка2б1ри
1кав
к2а1д
ка3д1не
ка2д1н
ка2д1р
1ка1е
каз3н
ка1зо
1ка2й1
1кал.
1ка1ло
1ка2л1с2
1ка1м
1кан
ка2п1л
ка1п
ка2пре
кар3т1р
ка2р1т
3к2ас
ка1ст
1кат
ка1т2р
1ках
ка2ш1т
ка1ш
1ка1ю
2к1б
к2ва1к
к1ва
к2вас
к2ва1ш
к1ви
к2воз
к1ву
2к1г
2к1д
к1да2
1ке
2ке1а
ке2гл
ке1г
кед1р
ке2с1к
ке2ст1
2к1з
1кив
ки1о
киос1
ки2пл
к2и1п
ки1с2ни
кис1н
1ки1т
2к1к2
к2к3с
2к3ла.
2к3лась1
2к3ле.
2кле1м
к3лем.
к3лен
к1л2ео
2к3ли.
к1ли
2к3лив
к2ли1к
к2лин
2к3лис
к3ли1я
2к3ло.
к1ло
к2лоз
к3ло1м
2к3лос
кло3т
1клук
к3лы1
2кль1
1кл2ю
2к3лю.
2к3ля.
к1ля
2к3ля1м
2к3лях
2к1м
2к1н
3к2ни1ж
к1ни
к2но1п
к1но
3к2ня1ж
к1ня
к2о
ко1б2ри
коб1р
1ко1в
3ко1ва
1код
ко1др
1коз
1кольс
коль1
2комин
ко1м
3ко2н1с2
коп2р
ко1п
ко2р3в
ко1ру
1кос
ко1ск
кос3м
ко1с1п2
1ко2т1н
ко1т
ко2фр
ко1ф
к2охо2р3
ко1хо
1ко1ш
2к1п
2кр.
к1рел
кр2е1о
кр2е2сл
к1реч
1кр2и1б2
к1рид
к2риз
кри2о3
к2ри1т
к1рих
к1р2о1а2
к1роб
к2ро1е
к1ро1к
к1ро1о2
к1рор
к1рос
к1ро1ф
к1рох
к1ро1э
кру1с
к1ряд
2кс
кса2н1д2
к1са
к2с3в
кс3г2
к2с3д2
к2с2и1б
к1с2и
к1ски
кс1к2л
к1ск2о
кс3м
к3со
к1ста1м
к1ст2ан
кс3т2е
к1сто
кс1тр
к1сту
к3су
2к1т
кта2к
3к2то.
кто1с
кт2р
к2у
ку1ве
3ку1е
1ку2й1
1ку1ля
3ку1м
ку2п1л
ку1п
ку2п1р
1кур
ку3ро
кус1к
ку1ст
1кут
ку3ть1
1ку1че
1куют
ку1ю
3кую1щ
2к1ф
2к1х2
2к1ц
2к1ч
2к1ш
1кь1
к2ю
1ла.
2ла1бе
ла1б
ла2б1л
2л2аго
ла1г
ла2гр
ла2д1а1г
ла1д
ла1да
1ла1е
ла3ж2д
ла1ж
ла1зо
л2а1к
лак2р
1лам.
ла1м
1лами.
лан2д1р
ла2н1д
ла1ста
ласт1в
ла1с2т2е
ла1сто
ла2ст1р
ла1сту
ла1стя
ла1т2р
ла1у1
ла2ус
ла2фр
ла1ф
1ла1х
1ла1я
2лб
л1бр
л1ве
л1ви
л1во
л1ву
1л2гал
л1г
лг2а
л2гл
лго1
2л3д2
1ле.
ле1в1л
лев1ра
ле2г1л
ле1г
ле1д2ж
ле3до
ле1з2о3
ле1зр
лек1л
2ле2м1н
ле1м
1лен
ле1о2н1т
л2ео
ле1о2с2
ле2сб2
ле2ск
ле4ска
л2е1с2л
ле1спе1
л2ес1п2
ле1тв
ле1т2р
1лех
ле1хр
л1зо
1ли
лиа2м
ли1а
3л2и1во
3л2и1вы1
л2иг2л
ли1г
ли2г1ро
лие3р
ли1е
ли2кв
ли1к
2ли2м1п
ли1м
лио1с
ли2пл
л2и1п
лис3м
2л1ис1п2
ли2тв
ли1т
лиу3м
ли1у2
ли2х3в
ли1хл
ли1хр
2л1к
лк2в
л2к1л
2л1л
л2ль1
лл2ю1
2л1м
2л1н
лни2е
л1ни
1ло
ло2б1л
ло1б2р
2лови1я
ло1в
ло2в1л
3ло1вод
ло2г3д
ло1г2
лого1с
ло1др
2лоен
ло1е
ло1зв
ло2к1а2у
ло1к
л2о2к2л
лок3ла
3лопас
ло1п
ло2р2в
2л1о2р1г
ло1ру
лос1к
ло1с2п2
2л1о2т1д
ло1т
лот2р
ло2шл
ло1ш
2л1п
2л1с2
л1с3б2
л1т
1лу.
лу1б1р
лу1в
лу3г
лу1д4р
1лу1е
лу1з1н
лу1кр
1лун
луо2д
лу1о
лу3п2ло
лу2пл
лу1п
лу1с
лу3ть1
1лу1ю
2л3ф2
2л1х2
л2х3в
2л1ц
л1ч
1лы.
лы1
1лые2
1лы1ж
1лы2й1
1лы1м
1лых.
4ль.
ль1
2льд
3ль2е
3ль2и1
2льк
2ль1м
2льн
3ль2о
2льс2к
1ль2сти
1ль2стя
2льт
2ль1ц
2льч
1ль2ща
ль1щ
1ль2ще
1ль2щу
3ль2ю
3ль2я
л2ю
1лю.
1лю1ж
1люсь1
лю1та
1ля
3ля.
ля1ви
3ля1во
3ля1вы1
2ляд
3ля1м
ля1ре
ля1ру
3лях
1м
ма2в2з2
3ма1г
ма2гн
ма2др
ма1д
ма2дь1
ма1зо
ма2к1р
ма1к
2м1а2л1л
ман2д1р
ма2н1д
мас3л
ма1с4т
ма2тоб
ма1то
ма2т1р
ма2у
ма1ф2
3ма1ч
ма2ч1т
4м1б
м3б2и
мб2л
м3б1ля
2м3в2
2м1г2
3м2гл
2м1д
меан2
ме1а
ме2е1г
ме1е
ме2ел
ме2ж1ат
ме1ж
ме1зо
ме2с1к
ме2ст1р
ме1ст
меч1т
2м1ж
2м1з2
ми2гре
ми1г
ми1з1в2
2м1из1д
ми1з1н
ми2кр
ми1к
мик1ри
ми2оз
ми1опи
мио1п
ми2ор
ми1с2л
2м1к2
3м2к1н
2м1л
м2ле1е
м2лел
2м1м
2м1н
4м3на
м3не1д
м1не
3м2не1ш
4мно1е
м1но
м2но1ж
4мн2о2й1
4мно1м
м2нор
4мн2о1ю
м2нут
м1ну
4м3ны1
мо1б2
мо3в1л
мо1в
3мод
мо1др
мо2ж1ж
мо1ж
мо1зв
мо1зр
мои1с1т
мо3и
мо2к3в
мо1к
1мо3м2
3мон
3мо1п
мо1ру
мос1ка
1мо1с2м
мо1с1н
мо1с2п2
3мо3ти
мо1т
мо2т1р
3мо1ф
2м1п
мп2л
м1ра1б
2мри
2м1ро
м1ры1
2м1с
мс2к
мс2н
м2с1ор
м1со
3м2сти
2м1т
му1с2к
му1с4л
му1ст
мут1р
му3ть1
2м1ф
мф4и3
2м1х
2м1ц
м2чав
м1ча
м2чал
м2чи1т
м1чи
м2чи1ш
2м1ш2
2м1щ
3м2ще
1мы1м1
мы1
мы2мр
мы2с
2мь.
мь1
2мьс
мь2ю1
2м1э
мэ1р
м2ю
мя1р
мя1с2т
1на
на3би1о
на1б
на1б2и
наб2р
на1в2р
наг2н
на1г
на3ж1д
на1ж
на1з2
на2и1л
на1и
на2ин
на2и1с2
4н1а2к1к2
на1к
на3м2н
на1м
нап2л
на1п
на1р1ва
на1р2ви
на1с2
на1тв
на1т2р
н1а2фр
на1ф
на1х2
2на1ч
на3ш2л
на1ш
2на1щ
на2э1р
на1э
3на1я
2н1б2
2н1в
2н1г
н2г1в
нги2о
нг2и
нг4л
нго1с
нг2р
2н1д
н2да1к
н1да
н2д1в2
н3де3з
н1де
нде2с
нд2ж
н3д2з2
н2д1л
нд1ра1г
нд1ра1ж
нд2ре
нд2ри3а
нд1ри
н2д1ря
нд2с1п2
н2д1с2
н2д1ц
1не
н2е1б2
не1в2д
2не2в1н
не3в1ня
не1г2
3нед
не1д2л
не1д2о
не2дра
не1дро
не3ду
не3е
нее2д
не3ж2д
не1ж
не1зв
не1з2л
не1з1н
не1зо
не1зр
не1и2
не1к2в
не1кл
не3м2н
не1м
3н2е1о2
не2о3да
не1од
не2ол
не3п2
не1р2ж
не2р1о1т
не1ро
не1с2к
не3с2н
н2е1с2п2
не1ст2
не1с2х2
не1с2ч2
не1т2в
не3т2л
не1т2р
3не1у2
не2фр
не1ф
не1хр
не3ш1к2
не1ш
не1я2
2н1з2
нзо1с2
1ни
н2и3б2
ни2ен
ни1е
3ни2й1
ни2кл
ни1к
нила2
ни1л
ни2л1ал
ни2л1а1м
2н1инс1п2
ни2н1с2
2н1инст
ни1сл
нис3п2
нист2р
ни1ст
ни1у3
ни1х
3ни1ц
3ни1щ
2н1к
нк2в
нк2л
нкоб2
нк2о
нко3п2
н2к1ро
н2к1с
н1л
2н1н
нно3п2
н1но
1но
ноб2
но1б1р
но2в1л
но1в
но1дв2
но1др
но2ер
но1е
но1зв
но2зд
но3з2о
но1зр
но3к1н
но1к
3н2оме
но1м
но2м3ш2
но2р2в
но1ру
но1ск2л
но2с1ли
но1с2л
но1с2п2
но2с2ч2
2н1о2т1д
но1т
но3ф2
но1э2
н3п2
2н1ре
2н1ри
н1ро
2н1с
н2с3в
н2с1г2
нс2ке
н2с1кон
нск2о
н2сл
н3сла
н2с3м
н2с1н
н2с1о1к
н1со
н3с2пе1
нс1п2
нст2р
нсу2р
н1су
н2с3ф
н2съ3
2н1т
н2т1в
нти1о2к
н2т1м2
нт2ра
н2тр1а2г
н2т1р1а1ж
н2трар
нтрас2
нт2ре
н2т1рив
н2тро1к
нт2ру
нт2р1уд
нт2ры1
н2т1ря
1ну
нут1р
ну1х
3ну1ю
2н1ф2
н1х
н1хо1
2н1ц
2н1ч
н2чл
2н1ш
нш2т
2н1щ
1ны1
3ны.
2нь.
нь1
1нь2е
1нь2и1
2ньк
1нь2о
2ньс
2ньт
2ньч
1нь2ю
1нь2я
н2э
1н2ю
2н3ю2р
1ня
ня1ви
2о1а2
о3ав
оа1п1
2о1ба
2о3био
о1б2и
об2лев
об1л
об2ле1м
о1бл2ю
1об1м
обо1л2г
о1бо
обо3м2
обо2с
2о3бо1т
об1р
о2б1ра.
о1б2рав
о1б2ран
1объ1
2о1бь1
о1в
о3в2ла
ов1л
о3в2ло
ов3но1
о2в1н
о3в2ну1ш
ов1ну
о2в1ри
о3в2с2е3
о2в1с2
ов3ск2о
ов2т2
о2вх
о1г2
2о3ге
о2г3ла.
о2г3ли.
ог1ли
о2г3ло.
ог1ло
о3гря
2одан
о1да
од1во1е
одв2
о3де.
о1де
1о2дея1л
оде1я
2оди3а
о1ди
2о3ди1м
од2ли1т
од1л
од1ли
о2д1о2пе1
о1до
одо1п2
одо3пр
о2д1о2пы1
о2до1с2и
одо1с
о2д1о2т1ч
одо1т
о1дра1г
од1ра1ж
од1раз
од1ра1к
о1драл
од3р2еб
од1ре
о1дроб
од1ро1в
о2д1у2ч
о1ду
о2дыма
о1ды1
оды1м
о2дыму
о2дын
о1дь1
о2дьб2
о1е
о2е1б
о2е1в1л
ое2д
о3ежек
о2еже
ое1ж
ое2жи
о2е1о
ое1с2
ое2ст
о2е1то
ое2ц
о3ж2ди
о1ж
о2ж1д
о3ж2ду1
оза2б3в
о1за1
оза1б2
2озав
о1з2ва
оз2вен
оз1ве
оз2ви
о1з2вя
оз2г1ло
о2з1г
оз2дор
оз1до
о1здр
оз2е1о
о1зе
о2з3но
оз1н
о1зо
о2з1об
2о1зон
о2зо1п
озо1ру
о2з1у2г
о1зу
о2зы1м
о1зы1
о3зы2с1
о3и
ои2г1
ои2г2н
ои1е3
ои2з
ои2м
ои3мо
ои2о
2о2й1
ойс2
о1к
2о3кан
ок2в
2ок2л
о3кл2ю
око1б
ок2о
2о3кол
око3п2л
око1п
ок1ск
о2кс
1о2к1т
2окти
2о3ку1м
ок2у
о3ла
ол2ган
ол1г
олг2а
о1ле
1о2ли2м1п
о1ли
оли1м
о3ло
о1лу
олу3д2
о1лы1
о1л2ю
о3ля
о3ма
о1м
ом2б2л
о4м1б
2оме
о3м2не1м
о2м1н
ом1не
о3м2нет
о3м2но1ж
ом1но
о2м1ри
ом2ч
ом2ше
о2м1ш2
о2мь1
о3мь2я
о3на
о2н1д2
оне3ф2
о1не
оно1б2
о1но
о1нр
о2н1с2
он2т2ру
о2н1т
о1о2
о2ол
оо3п1с2
оо1п
оос3м
оост1р
оо1ст
о2о3ти
оо1т
о2о1ф
о3па1к
о1п
о3пар
о2п1ле.
о2п1ле2й1
о2п1ли
оп2ли1т
оп2ло
о2п3лю.
опл2ю
о2п1ля
о3п2ляс
опо4в2с2
опо1в
опо1з2н
опо2ш3л
опо1ш
оп2ри1
о3п2т2е
о2п1т
оп2то
о1ра
ора2с3
ор2б3л
о2р1б
о1р2в
о1ре
2о3ре1г
оре2ск
о1ри
о2р1ис1п2
ори1с2
о1ро
о1ро2с3л
ор2тр
о2р1т
о1ру1е
о1рук
о2р1у2кс
о1рус
2о2р1ц
о1ры1
о1р2ю
о1ря
о3са1д
о1са
оса3ж2
о1с2б2
о2с3ба
о2с1ка.
ос3кар
оск1во
о2с1ке
ос1ки
о2ски.
о2с1ко1в
оск2о
ос1к2о2й1
ос1ко1м
о1с2ко1п
ос1к2о1ю
о2с1ку.
оск2у
ос1ку1ю
о1с2л
ос3ле2й1
ос3ло1г2
ос1ло
ос3лых
ослы1
ос3ми
ос2м
ос3мос
о1с2ни1м
ос1н
ос1ни
ос2ня1л
ос1ня
ос2пас
ос1п2
о1с2пу
ос2пя
ос2с2в
о2с1с
ос2с3м
о1ст
ос2та
о3стра
о2суч
о1су
2ос1х2
ос2цен
ос1ц
о1с2ч2
о1с2шив
ос1ш2
о1т
отв2
о2т3ва
от1ве
о2т1ви
от1в1л
1о2т1г
1о2т1д
2о3тек
о1т2е
о3тер
2о3тех
о3ти
о3ткал
о2т1к2
о2т1м2
от1ра1б
от1ра1д
от1раз
отра2с
от1ре1ж
от1рек
от1реч
от1ре1ш
от1ри
от1род
от1ро1е
от1ро1к
от1рос
от1роч
от1ру1г
от3с3м
о4т1с2
оту2а
от1у2ч
1о2т1х
о3ть2ю1
о2ть1
о3ть2я
о1у2
оу1п2
оус2к
оу3та
оу3то
2о3фа1ш
о1ф
о3фе
2о3фи1т
оф4и
2о3фон
о2фо1ри
2о3фо1т
о2ф1ри
2о1хи
ох1лы1
о2х1ля
ох2ме
ох1м
2охор
о1хо
о1хр
о1ху
о2цо
о1ц
оча1с
о1ча
оч2л
оч2ле
о3ч1ли
о1чт
о2ч1то
ош3ва
о1ш
ош2в
ош2ла
ошпа2к3
ош2п2
ош2т
оэ1ти
о1э
2о1ю
о1я
оя2в
оя2д
оя2з
оя2ри
1п
па1ви3
пав3л
па2вь1
па2др
па1д
па2ен
па1е
па1зо
пас1л
пас1та
па1с2т2е
пас1то
пас1ту
па2с1ты1
па1тро
па2ун
па1у
па3ф
па1ху
па2шт
па1ш
2п1в2
2п1д
пе1
пе2дв2
пе2д1ин
пе1ди
пе2з
пе3за1
пе3зо
пе2к1ла
пе2ль1
пе4пл
пе1п
пери1о
пе1ри
пе2с1к
пе2с1н
пе2ст1р
пе1ст
пе2с1ц
пе2сч2
пе2т1р
пе2шт
пе1ш
пиаст1
пи1а
пи2ж3м
пи1ж
пи2к1р
пи1к
3пи2н1к
3пи1ся
4п3к
2пл.
4п1ла.
пла1с
п1лем.
пле1м
п1ле2м1с
2п1лен
п2ле2н1к
п1л2е2о
пле2с1к
п1ле1ю
2плив
п1ли
3п2ли1к
2пло.
п1ло
2пло1в
2пло1г2
2п1лы2й1
плы1
2п1лы1м
п1лын
п1лых
2п1лю.
пл2ю
п1лют
п2ляс
п1ля
п2ля1ш
2п1н
п3на
п3но1
п3ны1
по1б2
по3в1л
по1в
по3в2с2
под1во
подв2
по2д1о2к
по1до
подо3м2
пое2л
по1е
пое2х
по1з1ве
по1з1до
по1з2л
по1з1н
пои2щ
по3и
3п2о2й1
3по2л1к
по3м1но
по1м
по2м1н
по3м1ну
3по3п2
п1о2р1г
пор2ж
по1ру
по1с4
3по1с2л
по3с1с
пот2в2
по1т
пот2р
по1х2
по2ш1ло
по1ш
по2ш1лы1
по2ш1ля
поэ3м
по1э
2п1п2
ппо1д
2пр.
3прев
пре1з
пре2й2
пре1л
пре1о1г2
пр2ео
3прет
при1
при3в
при1г2
при3д2
при3к
при3л
при2ль2
1пр2и1п2
п2ри1ц
про1б1л
про1д2л
про3ж2
про1з2
п1ро1зо
3про3и
1про3п
профо2
про1ф
2п4р1с
п2ру
2п1с2
3п2сал
п1са
п3син
п1с2и
3п2си1х
п3со
2п1т
п2т3в
3п2тих
п3ту
3пуб
пу2г3н
пу1г
пус1к2у
пу1ст
пу3ть1
2п1ф2
пх2
2п1ц
4п3ч
2п1ш
2п1щ
2пь.
пь1
2пьт
пэ1ра
п1э
п2ю1
1ра.
р4а3а2
ра2б1л
ра1б
1ра1бо
ра2б1р
1рав1ня
ра2в1н
ра2гв
ра1г
ра2гл
рад2ж
ра1д
радо1б2
ра1до
ра2д2ц
ра2жур
ра1ж
ра2зи2й1
ра1зи
ра2зуб
ра1зу
рак2в
ра1к
1ракиз
ра2к3л
1рал1г
1ра2м1к2
ра1м
1ра2м1н
ра2нох
ра1но
ран2с1ц
ра2н1с
ра2п1л
ра1п
р2ас3к2
1расл
рас3п2
рас1т
1раста
рас3т2л
р1а2та1к
ра1та
рат1в
ра1т2р
2ра1хи
1ращи
ра1щ
1ра1ю
1ра1я
2раят
2р1б
рб2ла
рб1л
р2бле
рб2ло
рб2л2ю
рбо3с
р1бо
1р2вав
р1ва
р3ва1к
р3вар
р3ва1та
р3ве1ж
р2в2ео
1рвет
р1ви
р3вин
р2ви1т
р1во
рво1з2д
р1вь1
2р1г
р2гв
р2г1л
р2гн
рг2р
2р1д
рда1с
р1да
р2д1в2
рд2ж
рди2а
р1ди
р2д1л
рдо1с2
р1до
р2д1ц
1ре.
ре1вр
рег2ля
ре1г
рег2н
ре2д1о2п2
ре1до
ре2до1с
рее2в
ре1е
рее2д
рее2л
ре3ж2д
ре1ж
1ре2з1к
ре1з2л
ре1з1на
рез1н
1ре1зо
ре1зр
ре1з2у
1рей1ш
ре2й1
ре1к2л
1ре2к1ш
ре3м1но
ре1м
ре2м1н
3ремо
ремо2г3
1ре2н1к
1рень1
ре1он
р2ео
ре1о1п2
ре1о2р
ре1о2ф
ре1ох
ре1о2ц
1репь1
ре1п
ре3р2
рес1ки
ре1ск
р2е1сл
р2е1с2п2
рес2с3м
ре2с1с
ре3ста
ре1ст
ре3сто
ре1сч2
ре1тв
ре1т2р
реуч3т
ре1у2
ре1чт
ре3шл
ре1ш
р3жа.
р1ж
р3жа1м
р3жан
р3ж2д
2рз
р1з2в
р1зо
ри3а
р2и1б2
ри3б1р
ри3в2н
2риг2и
ри1г
ри2г1ло
р2игл
ри3г2н
2ри1д2ж
ри1д2р
рие2л
ри1е
рие3р
риз2в2
рик2р
ри1к
ри3м2н
ри1м
ри3м2ч
р2ин
1ри2н1с2
ри1о2з
рио2с
ри1о1т
ри3р2
ри1с2
ри3сб2
2рис1п2
ри3ст1в
ри1ст
ри3т2р
ри1т
1ри1у2
ри2ф1л
ри1ф
ри3фр
ри1хл
1ри1ц
1ри1ш
риэти2
ри1э
2р1к
р2кв
р2к1л
р2к1с
2р1л2
р2ль1
рл2ю1
р3ля
2р1м
р2мч
2р1н
рна1с4
р1на
р3н2е3о2
р1не
рне1с2
рно3с2л
р1но
1ро.
ро2бл2ю
роб1л
ро1б2р
ро2в1л
ро1в
1рог2ол
ро1г2
1рогру
ро1дв2
ро3д2з2
ро1д1л
род2ле
ро2д1о1т
ро1до
ро1др
1ро1дь1
рое2л
ро1е
рое2м
рое2х
1розар
ро1за1
ро1з2в
ро1зр
3ро3зы2с1
ро1зы1
рои2с3
ро3и
1рокон
ро1к
рок2о
1рокр
1ролис
ро1ли
1роли1ц
1ромор
ро1м
1рона1ж
ро3на
1рона1п
1ронос
ро1но
рооп1р
ро1о2
роо1п
ро2пл2ю
ро1п
ро3п1с2
2р1о2р1г
ро1р2ж
ро1ру
ро1ск
ро2с1ки
ро2ск2у
1ро1с2л
ро1с2м
ро1с2п2
рос2ф
1рос1ш2
1ро1с2ю
1рот2в2
ро1т
1ро2т1к2
рот2ри
1ро1у2
роу1г2
ро2ф1а1к
ро1ф
ро2фр
ро1хл
рош2л
ро1ш
ро3ш1н
1роя2з
ро1я
2р1п
рп2ло
р2пл2ю
2р1р
4р1с
рс2к
р2с1н
рс2п2
рств2
р3ств1л
2р1т
р2т1а2к1к2
рта1к
р2т1а2к1т
р2т1в
р2т3ва
рт2в1л
р2т1м2
р2т1об
р1т1о2р1г
рт1ра
рт2ран
рт1ре
рт1ри
ртус1
р2т1у2чи
р3ть2ю1
р2ть1
р2т1я2ч
1ру.
1ру1ба
ру2г3н
ру1г
ру2дар
ру1да
1руже2й1
ру1ж
2ру2кс
1рул
рус1к
рус3л
ру1ста
руст1р
ру3ть1
1ру1ха
1ру1хо
1ру2ч1н
2р1ф
рф2л
2рх
р2х2в
р2х1ин
р1хи
рх1л
р1х2ло
р2х1о1п2
р1хо
рх1р
2р1ц
р2цв
2р1ч
р2чл
р2ч1м
2р1ш
р3ш2м
рш2т
2р1щ
2ръ1
1ры.
ры1
1рыб
ры2дв2
2рыз
ры2к2л
1ры1м
ры2с1к
ры2х1
2рь.
рь1
1рь2е
1рь2и1
2рьк
2рьс
2рьт
1рь2ю
1рь2я
рэ1л
р1э
р2ю
1рю.
1рюс
ря1ви
1ря1ю
1са
са2б1л
са1б
са2дь1
са1д
са2к1в
са1к
са2к2л
2с1аль1п
саль1
с1а2п1п2
са1п
2с1а2р1к
2с1а2т1л
са1тр
са2ун
са1у
са2ф1р
са1ф
са1х2
1сб2
2с3бе3з2
с1бе
сбез1о3
сбе3с2
2с3б2у
с2бы1
2с3б2ю
1с2в
2с3вен
с1г2
с2г2и
с2гн
с2го
1сд2
с2да
с2де
с3ди
с2до
1с2е
сег2н
се1г
се1з2
се1кв
сек1л
с2е2к1р
се2кс4
семи1
се1м
сер2е2б
се1ре
се2ск
се2ст
се3ста
се3с2т2е
сест1р
1с2ж
с1з
1с2и
3сиз
си1о1м
си1о1п
си2пл
с2и1п
си1х
4ск.
2с1ка1м
с2ка2н1д
с1кан
1с2ка1ф
2с1ках
ск2ва
с2к1ви
3ски1но
ск2л
с2к1ля
ск3ляв
2с2к1н
с1кон
ск2о
2ско3на
с2ко2п1с2
ско1п
2с1ко1ш
ск2р
с1кра
2с1кр2и1б2
с2к1с
2с3ку1е
ск2у
2с3ла.
1слав
1сла1д
с1ла1м
2с3ла1я
с3лев
с3ле1е
с1ле2й1
сл2ео2
с1лет
с3ле1ю
2с3ли.
с1ли
2сли1ц
2с3ло.
с1ло
с2ло1ж
с3лому
сло1м
2с3лос
2с3лу1ю
2с3лые2
слы1
2с3лы2й1
2с3лы1м
2сль1
с1люс
сл2ю
2с3ля
с2м
1смес
с4ме1я
с3мур
с1н
1с2на1б
с1на
с2на1с2
2с3на1я
1с2не1ж
с1не
2с3ни1к
с1ни
2с1но
сно1з2
2с3ну1ю
с1ну
2с3ны1
1со
со1б2р
с2о1в
сов2р
со1д
со1з2
со1л2г
со3м2
со2ри1е
со1ри
со1ру
со1ск
со1с2п2
со2сь1
сот2р
со1т
со1ч2л
сош2л
со1ш
с1п2
с2пав
с2пе1е
спе1
с2пел
с2пен
с2пех
1с2пе1ц
с2пе1ш
с2пе1ю
с2пи1м
2с3пи1ся
с3п1н
спо1з2
2спол
с2по1с4
2спь1
1ср
2ср.
с2ра1б
сра2с
с1рат
ср2е2б1
сре3до
2с1с
сса2н1д2
с1са
с2сб2
сс3во
с1с2в
4с5с2и
с3с2к
сс2л
с2с1н
с3с2не
с2со1ри
с1со
сс2п2
сст2
сс2ч2
2ст.
1ста.
2с2т1б2
4с2тв.
ст1вер
2ств1л
ст2вол
с2т2вя
с2т2е
1с4те.
1сте2й1
1стел
1стен.
с3тет.
с3т2е1т2е
сте3х
с3те1ш
1сти
с2ти1е
с2ти1и2
2ст1и2м1п
сти1м
2ст1и2н1д2
2с2т1и2н1ф2
2ст1инъ1
с2ти1ч
с2ти2ш1к2
сти1ш
с2ти1ю
2с2т1к2
ст2ла
с2т1л
с3т2ле
2ст1ли
ст2ли1л
ст2ли1т
2ст1ля
2ст1м2
2с2т1н
1сто.
с2то1б
1сто1в
1сто1г2
сто2г3н
1сто1д
1сто1е
3с2то3и
1сто1к
1сто1м
1стон
2с1то2р1г
2с1тор1ж
2с1то4р1с
1стос
1сто1т
с2то1ц
1ст2о1ю
2с2т1п2
2с6тр.
страс2
4ст1ра1я
2ст1ред
ст1ре2й1
2ст1рив
ст1риз
2ст1ри1л
2ст1ри1щ
ст1р2о1а2
с4т1ро1в
ст1род
ст1рох
с1т2руб
ст1ру1ш
2с4т1с2
с1тут
1сту1ю
2с2т1ф
2с4т1ц
1сты1
с2тыв
с4ть1
2с4ть.
2стьс
3сть2ю1
1сть2я
1стя1м
1стях
1су
су2б
су1б1а2
су1б1о
су1в
су3гл
су1г
су2ев
су1е
су2з
су1кр
сума1
су1м
супе2
су1п
сус3л
сус3п2
су1ст
сут1р
су2ф3
су1х
1с2фе
с1ф
с1х2
1с2хе
2сца1
с1ц
с2це3на
2с3ци1
2сцо
сч2
1с1ча
с2час
сче2с1к
с1че
с3чив
с1чи
2с3чи1к
с2чи1т
с1чл
2с3чо
с1ш2
с3ш1н
1съ2
съе3д
съе3л
1сы1
сы2г1
сы2з
сы2п1л
сы1п
сы2с
сыс1ка
2сь.
сь1
1сь2е
2ськ
2сьт
1сь2ю
1сь2я
сэ1р
с1э
с2эс1
1с2ю
сю1с
1ся
2сяз
ся3ть1
та2б1л
та1б
таб2р
та1ври
1та1г
та2гн
та1з2
так3ле
та1к
так2л
т2ан
та2пл
та1п
1тас
та1ст
та1тр
1та1щ
2т1б2
2тв.
2т2ва
т1ве2й1
т1вел
т1вет
2тви
т1во1е
т1во1з
2т1в2о2й1
т1вос
2т1в2о1ю
2т1вр
2тву
2т1вы1
2т1вя
2т1г
2т1д
1т2е
те2гн
те1г
те1д
те1зо
3те1ка
тек1л
3те2к1ш
тел2е1о
те1ле
те4м2б1
те1м
те2о3д
т2ео
те1ох
те4п1л
те1п
те2ра1к
те1ра
тер2е2о
те1ре
3те2рз
те2р3к
3те1ря
те2ска
те1ск
те2с1ки
те2с1к2о
те2ск2у
те1ст2
те2хо
2т1ж
2т1з
тиа2м
ти1а
ти2б1л
т2и1б
ти3д2
ти1з1на
тиз1н
ти1и2
тиис1
ти1к2
тила2м
ти1л
т1и2м1п
ти1м
2т1и2н1в
т1и2н1д2
2т1ин1ж
2т1и2н1ф2
ти1с2л
ти3ст1в
ти1ст
ти3ф2р
ти1ф
ти1хр
2т1к2
3т2кав
3т2кан
3т2кет
т1ке
3т2к1н
2т1л
тло2б
т1ло
т2ль1
т1м2
тми2с
тми1ст1
т3м1щ
2т1н
то2бес
то1бе
то1б2л
2т1объ1
то2в1л
то1в
то1д
то3д2р
то1з2
ток2р
то1к
2т1о2м1м
то1м
2то2м1с
2то2н1г
1то2р1г
1тор1ж
1то4р1с
то1ру
1то2р1ш
то1с2н
то1с2п2
то1с2ц
2т1о2т1д
то1т
то3т1к2
1то1щ
2т1п2
тпа1т
т1р2аг2а
тра1г
2т1ра1ж
2т2р1б
2трв
2т2р1г
2т2р1д
тр1до2
т1ре1а
1тре1бо
тр2еб
1тре1б2у
т1ре1бь1
т1ре1ве
т1ре2в1ш
т1ре1г
т1ред
т1ре1е
т1ре1за1
т1рез1н
тре2п1л
тре1п
3тре2с
тре1с1к
т1ре1ст
т1ре1ту
3т2ре2х
т1ре1ц
т2ре2шь1
тре1ш
т1ре1ю
1тр2и1б2
т1рив
тр2и2г1л
три1г
т1ри1л
т1ри1м
4т1ри2н1с2
тр2ин
три1о
т1ри1т
три3ф
т1ри1щ
2т2р1м
2т2р1н
т1рогл
тро1г2
т1роид
тро3и
2тр2о2й1
тро3пл
тро1п
т1рор
т1ро1со
тро3т
4т3ро1ц
2тр2о1ю
2т2р1п
2т2р1р
1труб
т2руд
2трук
т2ру1м
т2рут
2т2р1ф
2т2р1щ
2т2ръ1
т1ры1
т1ря.
т1ряв
2т1ряд
т1ря1е
т1ря1ж
т1ря2й1
т3ря1к
т1рят
т1ря1щ
т1р2я1я
4т1с2
т2сб2
т2с3д2
тсе1п2
т1с2е
т2с3м
т2с3п2
2т1т
т2т1м2
ту2гр
ту1г
ту2жин
ту1ж
2т1у2пр
ту1п
ту1с2л
ту1ст
ту2ф1л
ту1ф
1туша
ту1ш
1тушо
1ту2шь1
1ту1щ
2т1ф
2т1х
4т1ц
2т1ч
2т1ш2
2т1щ
2тъ1
ты2г1
ты1
ты2с1к
2ть1
4ть.
3ть2е
3ть2и1
ть2м
4тьт
ть2ю1
2т1э
т2ю
тю1т
1тя1г
1тя1ж
1тя1п
2тя2ч
у1а
у2а1ле
у2ас
у3бел
у1бе
убо1д
у1бо
убос2
уб1р
1убра
у1б3р2ю
1у2быт
у1бы1
у1ве.
у1ви
ув2л
у1во
у1ву
у2гв
у1г
у2гл
у2гн
уг2на
уг2не
уг1ре
уг1ря
уда1с
у1да
уд2в2
уд1ра1м
уд1ро
у3ду
у1е
уе2д
уе2л
уе1с
уе2с1к
у2ес2л
уе2х
у2ж1ж
у1ж
у1з2в
у1зо
узо3п
у1и
у1ка
ук1в
у1ки
у1к2о
уко1б
у1к2у1
у1ла
у1ле
у1лу
у1лых
улы1
у1л2ю
у2мч
у1м
у3на
ун2д1р
у2н1д
у1нь1
у1о
уо2б
уо2в
у2о1за1
уо2к
уо2п
уо2с
уо1ст1
уо2т1
уо2ф
у2пл
у1п
уп1л2ю
у3про
у1ра
у1ре
уре2т3р
у1ри
ур1ке3
у2р1к
у1ро
у2род
уро2д1л
урт2р
у2р1т
у3ру
у1ры1
у1р2ю
у1ря
у2са1д
у1са
у1с1г2
ус1ка
ус1ки
уск3л
ус1ко1м
уск2о
у1ск2р
ус1ку.
уск2у
ус2л
усла4ж3
ус3ли
у1с2м
у2с1н
ус2п2
у2с3с
у1с2т2е
у1стя
у1с1ф
2ус1ц
у2сч2
у2сь1
у3сь2я
у1та
у3тер
у1т2е
у1ти
ут2ля
у2т1л
у1то
уто3п2с2
уто1п
ут1ри
у1ту
у1ты1
у3ть2е
у2ть1
у3ть2ю1
1ут2ю
у1тя
у1у
уу1г2
уу2с
у3ф4и
у1ф
уф1л
уф2ля
у2фр
ух1а2д
у1ха
уха2т
у2х2в
у3х4во
ух1л
ух3ля
ух1р
у2ч2еб
у1че
1учр
у1чь1
у3ше
у1ш
у3ши
у2шл
уш1ла
у2ш2п2
2у1э
у1я
уя2з
1ф
фа2б1
фа2гн
фа1г
фа1зо
фа2н2д
фанд1р
фа1тр
фа2х
3фа1ш
фа1э1
2ф1б
2ф1в
2ф1г
2ф1д
фев1р
фед1р
ф2е1о3
фе2с1к
ф4и
фиа2к1
фи1а
ф2и2гл
фи1г
фи2ж
фи2зо
фи2нин
фи1ни
фи1о
3фи1т
2ф1к
ф2ла
ф2ли
ф2ло
2ф1м
2ф1н
2ф1объ1
3фон
фо2р2в
2ф1о2р1г
фор3тр
фо2р1т
фо1ру
фос1к
3фо1т
фото3п
ф1ра1б
фра1з
фра1с
ф1рат
ф2рен
фре2с
ф1ри
ф2ри1ж
ф2риз
ф1ро
ф2рон
ф1ру
2ф3с
2ф1т
ф2т1м2
ф2тор
2ф1у2п
фу3т1л
2фу1ф
2ф1ф
2ф1ч
2ф1ш2
2фь.
фь1
ф2ю1
1ха
ха2б1л
ха1б
ха2д
2х1а1к
ха2н2д
х2а1о3
х1а2р1ш
2х1б
1х2в
2х3ве
2х3ви
х3вы1
2х1г
х3д2
1хе
х2ео3
х1з2
1хи
хиат1
хи1а
хи1е2
2х1и1зы1
хи1с2
х1к2
х1лав
х1лас
х1лат
х1ла1ц
1хл2еб
х2лес
х1лет
х3ло.
х1ло
х2ло1п
1х2лор
х1лу
1х2му
х1м
2х1н
3х2ны1
1хо
2х1о2к
хо1п2
хо2пе1
хо2пор
хо1ру
х1ос2м
2х1ос1н
хо1ф2
хох1л
хо1я2
х1п2
х1раз
1хран
х1ра1с2
х1ре2й1
хри2пл
хр2и1п
х2ри1с2
х1ро1в
1хро1м
хро2м2ч
х1ры1
х1ря
2х1с2
2х1т
1ху.
х1у2г
2ху1е
2ху2й1
1хун
х1у2р
ху3ра
1хус
1ху1ш
2ху1ю
х1х2
2х1ч2
2х1ш
хь2ю1
хь1
1ц
ца1
3ца.
3ца1м
ца2пл
ца1п
3цах
2ц1б
ц2ве
2ц1вы1
2ц1г
2ц1д
це1з
це1к
це1о1т
ц2ео
це2п1л
це1п
ц2ес2л
це1т
2цетат
це1та
2ц1з
ци1
ци2к1
цик3л
ци2ол
ц2и1п2
ци2с1к
ци1у3
ци2ф1р
ци1ф
2ц1к2
2ц1л
2ц1м
2ц1н
ц1о2б
2ц1о2д
2ц1о1т
2ц1п2
2ц1р
2ц1с
2ц1т
3цу
2ц1ц
2ц3ш2
3цы1
цы2п
цып3л
ц1ю1
1ча
ча2др
ча1д
ча2д2ц
ча2е1во
ча1е
ча2е1вы1
ча2ер
част1в
ча1с2т2е
ча1сту
ча1стя
3ча1то
3ча1ты1
2ч1б
ч1в
2ч1д
1че
че1в1л
че2гл
че1г
ч2е1о
че4р2с
черст1
ч2е1сл
ч2ж
чжо2
1чи
3чи1к
3чи1ц
2ч1к
1ч2ла
ч2ле
ч3ле1г
ч3ле1ж
2ч1ли
ч2ли.
1ч2ло
1ч1м
2чма
2чме
ч2мо
2ч1н
3чо
2ч1с
2ч1та
ч2т2е
2чт1м2
1чу
3чук
ч2х
2ч1ч
2чь.
чь1
1чь2е
1чь2и1
2чьс
2чьт
1чь2ю
1чь2я
1ш
ша2б1л
ша1б
ша2гн
ша1г
ша2г1р
ша2др
ша1д
шан2кр
ша2н1к
ша2р3т2
ша1ст
ша1тро
2ш1б
ш2в
ш3вен
ше2гл
ше1г
ше1к
ш2е1о2
ше3пл
ше1п
ше1с2
ши2б1л
ш2и1б
ши2пл
ш2и1п
ши2ф1р
ши1ф
2ш1к2
3ш2кол
шк2о
2ш1ле2й1
2ш1лен
ш2ли.
ш1ли
2шлив
2шли1л
ш2лин
ш2лис
ш2ли1т2е
шли1т
ш2ли1ф
ш2ло.
ш1ло
2шло1в
ш2ло1г2
ш1лы1
ш2л2ю
2шля1е
ш1ля
2шля1к
ш2ля1п
2шлят
2шляч
2шля1ю
2ш1м
3ш2мы1
4ш3мы.
2ш1н
4ш1ни
ш2нур
ш1ну
ш2п2
ш3пр
2ш1р
2ш1с
ш1ти
2ш4т1с2
шу2ев
шу1е
шуст1
2ш1ф
ш1х
2ш1ц
2ш1ч
2шь1
4шь.
3шь2е
3шь2и1
3шь2ю
3шь2я
ш2ю1
1щ
2щ3в2
ще1б2л
щ2еб
ще2гл
ще1г
щед1р
ще1и2
щеис1
ще1с
ще1х
ще1ш2
ще3ш1к2
щи2п1л
щ2и1п
2щ1м
2щ1н
2щ1р
2щь.
щь1
ъ1
ъе2г
ъе2д
ъе3до
ъе2л
ъ2е2р
ъе2с
ъе2хи
ъ1ю2
ъя2
ъя3н
ы1
ы2б1л
ы3г2а
ы1г
ы3г2и
ыг2л
ы2гн
ы2д1л
ыд2ре
ы2д1ро
ы2д1ря
ые2
ы3ж2д
ы1ж
ыз2ва
ыз2д
ы2з1л
ы2з1н
ыз2на
ыи2
ыи1г1
ы2к1в
ык2л
ы2к3ло
ы1ко1з
ык2о
ы2к1с
ы2ль1
ы2мч
ы1м
ыно1с3л
ы1но
ы3по
ы1п
ыра2с3
ыр2в
ыре2х
ы3са
ы3с2е
ыс1ки
ыс1к2у
ы2с1н
ы3со
ыс2п2
ы2с1х2
ыс2ч2
ы2с1ш2
ы2т1ви
ыт2р
ы3ть2ю1
ы2ть1
ы3ть2я
ыу2
ы2ш1л
ы1ш
ы3шь1
ь1
ьб2
ь2вя
ь2д1ц
ь2е
ье1зо
ье1к
ье2с1к
ь2з1н
ь2и1
ь2кл
ьми3д
ь1м
ьми3к
ьмо1
ь3н2е2о2
ь1не
ь2о
ь2п1л
ь1п
ь3п2то
ь2п1т
ьс2к
ь2с1н
ь2сти
ь2стя
ь2т1а2м1п
ьта1м
ьти3м
ь2т1м2
ь2то1т
ь2тра1б
ьт2ре
ьт2ру
ьт2ры1
ь1хо2
ьхоз1
ь2ща
ь1щ
ь2ще
ь2щу
ь2ю
ь2я
ья1в
ь3я2г3с2
ья1г
1э
э1в
эв1р
2э1г
эд1р
эк1л
э2кс1
эк2ст
эл2е1о
э2м
э3ма
э2н
э3нь1
эо2з
э2п
эпи3к
э1ре
э1ри
эри4т2р
эри1т
эро1с2
э1ру
э1ры1
эс1
эск2
эс3м
э2со
эс3т2е
эс2т1р
э2т2е
этил1а
эти1л
эт1ра
э2ф
эх2
э1хо3
э2ц
эя2
1ю
ю1а
ю1б
ю2б1в
ю2б1л
ю2б1ре
ю1в
ю1дь1
ю1е
ю2з2г
юзи2к
ю1зи
ю1зо
ю1и
ю2идал
юи1да
ю1к
ю2к1в
ю1ла
ю1ле
ю2ли
1ю1л2ю
2ю1м
ю2мч
ю2нь1
ю1о1
ю1ра
ю1ре
юре4м
ю1ри
юри2с1к
юри1с2
ю1ро
ю1ру
ю1ры1
ю2с1к
ю1ста
ю1с2т2е
ю1сто
ю1стя
ю1ти
ю1то
ю1ту
ю1ты1
ю1х
юха1с
ю1ха
ю1ч
ю2щь1
ю1щ
ю1я
я2бр
яб1ра
яб3ре
яб1ри
я1б3р2ю
3яви2кс
яви1к
я1во
я1ву
я1в2х
я2г1л
я1г
я2гн
яд1в2
яд1р
я1е
яз2гн
я2з1г
я1зо
я1и
я1к
я2к1в
я2к1л
я2к1с
я1л
я2ль1
ям2б3л
я1м
я4м1б
я2мь1
я3на
я2н1с2
я1ра
я1ри
я1ро
я1рь1
яс1к
яс1л
яс2т
яст3в
я1сто
яст1р
я1та
ят3в
я3ти
яти1з
я1то
я1ту
я1ты1
я3ть2ю1
я2ть1
я3ть2я
я1тя
я1у
ях1л
я1ху
яце1
я1ц
я2шл
я1ш
2яю.
я1ю
2я1я
.бо2дра
.вст2р
.в1с2
.доб2рел
.доб1р
.до1б2ри
.об2л1ю1ю
.о1бл2ю
.об1л
.об2ре1е
.об1р
.об2ре2й1
.об2ре1ю
.об2рив
.об2ри1л
.об2ри1т
.па2н1ис
.па1ни
.по3м2ну
.по1м
.по2м1н
.реа2н
.ре1а
.ро2с3пи
.ро1с2п2
.со2пла
.со1п
а2нь1ш
атро2ск
без1у2с
бе1зу
бино2ск
бино1с
би1но
виз2гн
ви2з1г
выб2ре
гст4р
ди1с2ло1в
дис1ло
дос2ня
дро2ж3ж
2д1руже2й1
е2мьд
емь1
е2о3пла1то
е2о3по1зи
ере3с2со
ере2с1с
4ж3ди1к
4ж3ди1ч
заи2л
за1и
зао2з
з2а1о
2з1а2хав
за1х2а
заю2л
за1ю
з2рят
зу2мь1
6зь.
и2л1а2мин
ила1м
илло3к2
и2л1л
ил1ло
й2кь1
ла2б1р
лу3с4н
ме2ди2н1с2
ме1ди
1ме2д1о2с2м
ме1до
медо1с
1мети2л1а2м
ме1ти
мети1л
мис4с3н
ми4с1с
нар2ват
не2о3ре
ни1с2кол
нис1к
ниск2о
ни4сь.
нись1
но4л1а2мин
но3ла
нола1м
н2тра2с1с
о2д1о2бол
одо1бо
о4ж3дев
ож1де
о1и2с1тр
ои1ст
ойс4ко1в
ойск2о
о2м3че.
ом1че
они3л2а1м
о1ни
онила2
они1л
он2трат
онт2ра
о2плюс
осо4м3н
о1со
осо3м2
оти4д1н
оти3д2
пере1с2н
пе1ре
по2до1де
подо1д2
по2д1у2ро
по1ду
пое2ж
по2стин
по1ст
по1сти
пре3м2но
пре1м
пре2м1н
приче2с1к
при1ч
при1че
пти4д1н
пти3д2
редо4пл
реж4ди
рни3л2а3м
р1ни
рнила2
рни1л
роб2ле1ю
2сбрук1
сб1ру
со2ст1ри1т
со1ст
со3т2кал
со2т1к2
2стче.
с2т1ч
ст1че
2с4тьт
сы2ми1т
сы1м
2сься.
сь1ся
6тр.
тро2етес
тро1е
трое1т2е
6хуя.
ху1я
ы2рь1м
ырь1
ыя2вя
ьбат2
ь1ба
а1вё
а2д1вё
а1ё
аз3вёз
аз1вё
а1лё
2алёк
2амёт
ам2нёт
ам1нё
а1рё
ас3тё1м
ас2т2ё
а3ть2ё
1бё
бё2д1р
б3лён
б2лё2с1к
б2лю1ё
б1рёк
б2рё1м
б2рёх
1ве1ё
3ве3зё
вёд1р
1вёз
2вё2р1д
1вёс
в2лёк
в2лёт
1вмё
в2нёс
в1нё
2в1рён
3в1с2ё3
1вь2ё
г1лё
г2лёт
г2нёв
г1нё
г3нён
г2но1ё
д1вё
1дё
.доб2рёл
2доплё
до2прё
д1рё
д2р2ёб
2д3рё1ж
д2рё1м
1дрёма
1дрёмы1
2д3рён
дъё2м
1дь2ё
еб1рён
е1вё
2евёр
2е1врё
е2г1лё
е1ё
2ежё
е3зё
е1лё
2епё1
ер1вё
е1рё
ерё3до
ерё1к2
ес2чёт
ес1чё
ет1вё
е3ть2ё
2ёб
ё1бра
ёб1ры1
ё1ве
ё1во
2ё1вре
ё1ву
ё1дру
2ё3ду1ш
ё1ду
2ёже
ё1ж
ё3зе
ёз1о2г2
ё1зо3м2
ё1ка
ё1ки
2ё1к2о
2ё1кр
ёк2ро
ё1к2у
ё1ла
ё1ле
ё1лу
ё1лы1
2ёму1ж
ё1м
ё2мч
ё3на
ён2д1р
ё2н1д
ё2н1с2
ёпат2
ё1п
2ёпе1
ё2пл
ё3пла
ёп1лу
ё3плы1
ё4п1н
2ёпо
ё4п1т
ё1ра
ё1ре
ё3ре.
ё1ри
ё1ро
ёр3с2к
ё4р1с
ё1ру
ё1ры1
ё3с2а
ё1ск
ё2с1ка.
ё2с1ке
ё4с1ку.
ёск2у
2ёсл
ё3со
ё1ст
ёс2т2ан
ё3сту
ё1та
2ётеч
ё1т2е
ё1ти
ё1то
ёто1с
ёт1р
ё1ту
ё1ты1
ё1т2ю
ё1тя
ёха2т
ё1ха
ёх1а1то
ёх3вал
ё1х2в
ёх1ва
ёх3ло1п
ёх1ло
ёх1опо
ё1хо
ёхо1п2
ёх1ру
3жёв
жё1с2
ж2жё
за3м1нё
з1вё
з2вёз
1зё
з2на1ё
2з1нё
1з2о3рё
з2о1т2ё
зот2рё
3зу1ё
зъё2м
2зымё
2и1вё
иг1рён
и1ё
их1рё
1ка1ё
1кё
к3лён
к2ро1ё
3ку1ё
ла1с2т2ё
лё3до
лё1з2о3
лёк1л
1лён
лё2ск
лё4ска
1лёх
2лоён
ло1ё
1лу1ё
3ль2ё
1ль2щё
3м2нё1ш
м1нё
3м2щё
нд2рё
не3ё
1нё
н2ё1б2
3н2омё
1нь2ё
од3р2ёб
од1рё
о1ё
оё2жи
оё1ж
о1лё
2омё
о3м2нё1м
ом1нё
о3м2нёт
о2п1лё2й1
о1рё
о2с1кё
от1вё
2о3тёк
о1т2ё
о3тёр
от1рёк
от1рё1ш
о3фё
пё1
пё2ст1р
пё1ст
пё2т1р
2п1лён
п2лё2н1к
плё2с1к
п1лё1ю
поё2ж
по1ё
3прёт
причё2с1к
при1чё
р2блё
1рвёт
.рё2бр
.р2ёб
1рё2з1к
рё1з1на
рёз1н
1рё1зо
рё1з2у
1рё2к1ш
3рёмо
рё1м
1рё2н1к
рё3ста
рё1ст
рё3сто
род2лё
роё2м
ро1ё
1рь2ё
с2дё
се3с2т2ё
1с2ё
сё2кс4
сё2ст
сёст1р
2с3ку1ё
с1лёт
с2т2ё
1стёл
1стён.
с3тёт.
с3тё1т2е
стё3х
с3тё1ш
с3т2лё
счё2с1к
с1чё
1сь2ё
т1вёл
т1во1ё
1т2ё
тё2гн
тё1г
тё1зо
3тё1ка
тёк1л
3тё2к1ш
тё4п1л
тё1п
тё2р3к
тё2ска
тё1ск
тё2с1ки
тё2с1к2о
тё2ск2у
тё2хо
3т2кёт
т1кё
т1ре1вё
3т2рё2х
т2рё2шь1
трё1ш
тро2етёс
трое1т2ё
3ть2ё
уг2нё
уг1рё
.у1ё2
у1ё
у1лё
у1рё
у1с2т2ё
у3тёр
у1т2ё
у3ть2ё
у2ч2ёб
у1чё
у3шё
2х3вё
1хл2ёб
х2лёс
ц2вё
1чё
чё4р2с
чёрст1
.чё2с1к
ч2т2ё
1чь2ё
2ш1лён
3шь2ё
ъ2ё2р
ыд2рё
ырё2х
ы3с2ё
ь2ё
ьё1зо
ь2щё
ю1ё
яб3рё
8не.
8бъ.
бъ1
8въ.
8гъ.
гъ1
8дъ.
8жъ.
8зъ.
8къ.
къ1
8лъ.
лъ1
8мъ.
мъ1
8нъ.
нъ1
8пъ.
пъ1
8ръ.
8съ.
8тъ.
8фъ.
фъ1
8хъ.
хъ1
8цъ.
цъ1
8чъ.
чъ1
8шъ.
шъ1
8щъ.
щъ1
8-7
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
.й1
.к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
.ъ1
.ы8-8
.ы1
.ь8-8
.ь1
.э8-8
.ю8-8
.я8-8
-4а8а8
8а8а8-7
-а8б8
8а8б8-7
-а8в8
8а8в8-7
-а8г8
8а8г8-7
-а8д8
8а8д8-7
-а8е8
8а8е8-7
-а8ё8
8а8ё8-7
-а8ж8
8а8ж8-7
а1ж
-а8з8
8а8з8-7
-а8и8
8а8и8-7
-а8й8
8а8й8-7
а2й1
-а8к8
8а8к8-7
-а8л8
8а8л8-7
-а8м8
8а8м8-7
-а8н8
8а8н8-7
-2а8о8
8а8о8-7
-а8п8
8а8п8-7
-а8р8
8а8р8-7
-а8с8
8а8с8-7
-а8т8
8а8т8-7
-а8у8
8а8у8-7
-а8ф8
8а8ф8-7
-а8х8
8а8х8-7
-а8ц8
8а8ц8-7
а1ц
-а8ч8
8а8ч8-7
-а8ш8
8а8ш8-7
-а8щ8
8а8щ8-7
а1щ
-а8ъ8
8а8ъ8-7
аъ1
-а8ы8
8а8ы8-7
аы1
-а8ь8
8а8ь8-7
аь1
-а8э8
8а8э8-7
-а8ю8
8а8ю8-7
-а8я8
8а8я8-7
-1б8а8
8б8а8-7
-2б8б8
8б8б8-7
-б8в8
8б8в8-7
-б8г8
8б8г8-7
-2б8д8
8б8д8-7
-1б8е8
8б8е8-7
-1б8ё8
8б8ё8-7
-2б8ж8
8б8ж8-7
-б8з8
8б8з8-7
-1б8и8
8б8и8-7
-б8й8
8б8й8-7
б2й1
-2б8к8
8б8к8-7
-б8л8
8б8л8-7
-б8м8
8б8м8-7
б1м
-2б8н8
8б8н8-7
-1б8о8
8б8о8-7
-б8п8
8б8п8-7
б1п
-б8р8
8б8р8-7
-2б8с8
8б8с8-7
-б8т8
8б8т8-7
-1б8у8
8б8у8-7
-2б8ф8
8б8ф8-7
-2б8х8
8б8х8-7
-2б8ц8
8б8ц8-7
-2б8ч8
8б8ч8-7
-2б8ш8
8б8ш8-7
-2б8щ8
8б8щ8-7
-б8ъ8
8б8ъ8-7
-1б8ы8
8б8ы8-7
-1б8ь8
8б8ь8-7
-б8э8
8б8э8-7
-3б8ю8
8б8ю8-7
-1б8я8
8б8я8-7
-1в8а8
8в8а8-7
-2в8б8
8в8б8-7
-в8в8
8в8в8-7
-2в8г8
8в8г8-7
-в8д8
8в8д8-7
-в8е8
8в8е8-7
-в8ё8
8в8ё8-7
-в8ж8
8в8ж8-7
в1ж
-1в8з8
8в8з8-7
-в8и8
8в8и8-7
-в8й8
8в8й8-7
в2й1
-2в8к8
8в8к8-7
-в8л8
8в8л8-7
-2в8м8
8в8м8-7
-2в8н8
8в8н8-7
-в8о8
8в8о8-7
-2в8п8
8в8п8-7
-в8р8
8в8р8-7
-2в8с8
8в8с8-7
-в8т8
8в8т8-7
-в8у8
8в8у8-7
-в8ф8
8в8ф8-7
-в8х8
8в8х8-7
-2в8ц8
8в8ц8-7
-2в8ч8
8в8ч8-7
-2в8ш8
8в8ш8-7
-2в8щ8
8в8щ8-7
-в8ъ8
8в8ъ8-7
-1в8ы8
8в8ы8-7
-в8ь8
8в8ь8-7
-1в8э8
8в8э8-7
-1в8ю8
8в8ю8-7
-1в8я8
8в8я8-7
-г8а8
-1г
8г8а8-7
-2г8б8
8г8б8-7
-г8в8
8г8в8-7
-2г8г8
8г8г8-7
-г8д8
8г8д8-7
-г8е8
8г8е8-7
-г8ё8
8г8ё8-7
-2г8ж8
8г8ж8-7
-2г8з8
8г8з8-7
-г8и8
8г8и8-7
-г8й8
8г8й8-7
г2й1
-2г8к8
8г8к8-7
-г8л8
8г8л8-7
-2г8м8
8г8м8-7
-г8н8
8г8н8-7
-г8о8
8г8о8-7
-2г8п8
8г8п8-7
-г8р8
8г8р8-7
-2г8с8
8г8с8-7
-2г8т8
8г8т8-7
-г8у8
8г8у8-7
-2г8ф8
8г8ф8-7
-г8х8
8г8х8-7
-г8ц8
8г8ц8-7
г1ц
-2г8ч8
8г8ч8-7
-2г8ш8
8г8ш8-7
-г8щ8
8г8щ8-7
г1щ
-г8ъ8
8г8ъ8-7
-г8ы8
8г8ы8-7
гы1
-г8ь8
8г8ь8-7
гь1
-2г8э8
8г8э8-7
-г8ю8
8г8ю8-7
г1ю
-г8я8
8г8я8-7
-1д8а8
8д8а8-7
-2д8б8
8д8б8-7
-д8в8
8д8в8-7
-2д8г8
8д8г8-7
-2д8д8
8д8д8-7
-1д8е8
8д8е8-7
-1д8ё8
8д8ё8-7
-д8ж8
8д8ж8-7
-2д8з8
8д8з8-7
-1д8и8
8д8и8-7
-д8й8
8д8й8-7
д2й1
-2д8к8
8д8к8-7
-д8л8
8д8л8-7
-2д8м8
8д8м8-7
-2д8н8
8д8н8-7
-1д8о8
8д8о8-7
-2д8п8
8д8п8-7
-д8р8
8д8р8-7
-2д8с8
8д8с8-7
-2д8т8
8д8т8-7
-1д8у8
8д8у8-7
-2д8ф8
8д8ф8-7
-д8х8
8д8х8-7
-д8ц8
8д8ц8-7
-2д8ч8
8д8ч8-7
-2д8ш8
8д8ш8-7
-2д8щ8
8д8щ8-7
-2д8ъ8
8д8ъ8-7
-1д8ы8
8д8ы8-7
-д8ь8
8д8ь8-7
-д8э8
8д8э8-7
д1э
-1д8ю8
8д8ю8-7
-1д8я8
8д8я8-7
-е8а8
8е8а8-7
-2е8б8
8е8б8-7
-е8в8
8е8в8-7
-е8г8
8е8г8-7
-е8д8
8е8д8-7
-е8е8
8е8е8-7
-е8ё8
8е8ё8-7
-е8ж8
8е8ж8-7
-е8з8
8е8з8-7
-е8и8
8е8и8-7
-е8й8
8е8й8-7
е2й1
-е8к8
8е8к8-7
-е8л8
8е8л8-7
-е8м8
8е8м8-7
-е8н8
8е8н8-7
-2е8о8
8е8о8-7
-е8п8
8е8п8-7
-е8р8
8е8р8-7
-е8с8
8е8с8-7
-е8т8
8е8т8-7
-е8у8
8е8у8-7
-е8ф8
8е8ф8-7
-е8х8
8е8х8-7
-е8ц8
8е8ц8-7
-е8ч8
8е8ч8-7
-е8ш8
8е8ш8-7
-е8щ8
8е8щ8-7
е1щ
-е8ъ8
8е8ъ8-7
еъ1
-е8ы8
8е8ы8-7
еы1
-е8ь8
8е8ь8-7
еь1
-е8э8
8е8э8-7
-е8ю8
8е8ю8-7
-е8я8
8е8я8-7
-ё8а8
8ё8а8-7
-2ё8б8
8ё8б8-7
-ё8в8
8ё8в8-7
-ё8г8
8ё8г8-7
ё1г
-ё8д8
8ё8д8-7
-ё8е8
8ё8е8-7
-ё8ё8
8ё8ё8-7
-ё8ж8
8ё8ж8-7
-ё8з8
8ё8з8-7
-ё8и8
8ё8и8-7
-ё8й8
8ё8й8-7
ё2й1
-ё8к8
8ё8к8-7
-ё8л8
8ё8л8-7
-ё8м8
8ё8м8-7
-ё8н8
8ё8н8-7
-ё8о8
8ё8о8-7
-ё8п8
8ё8п8-7
-ё8р8
8ё8р8-7
-ё8с8
8ё8с8-7
-ё8т8
8ё8т8-7
-ё8у8
8ё8у8-7
-ё8ф8
8ё8ф8-7
ё1ф
-ё8х8
8ё8х8-7
-ё8ц8
8ё8ц8-7
ё1ц
-ё8ч8
8ё8ч8-7
-ё8ш8
8ё8ш8-7
ё1ш
-ё8щ8
8ё8щ8-7
ё1щ
-ё8ъ8
8ё8ъ8-7
ёъ1
-ё8ы8
8ё8ы8-7
ёы1
-ё8ь8
8ё8ь8-7
ёь1
-ё8э8
8ё8э8-7
ё1э
-ё8ю8
8ё8ю8-7
ё1ю
-ё8я8
8ё8я8-7
-ж8а8
-1ж
8ж8а8-7
-2ж8б8
8ж8б8-7
-2ж8в8
8ж8в8-7
-ж8г8
8ж8г8-7
-2ж8д8
8ж8д8-7
-ж8е8
8ж8е8-7
-ж8ё8
8ж8ё8-7
-1ж8ж8
8ж8ж8-7
-2ж8з8
8ж8з8-7
-ж8и8
8ж8и8-7
-ж8й8
8ж8й8-7
ж2й1
-2ж8к8
8ж8к8-7
-2ж8л8
8ж8л8-7
-ж8м8
8ж8м8-7
-2ж8н8
8ж8н8-7
-ж8о8
8ж8о8-7
-2ж8п8
8ж8п8-7
-ж8р8
8ж8р8-7
-2ж8с8
8ж8с8-7
-ж8т8
8ж8т8-7
-ж8у8
8ж8у8-7
-2ж8ф8
8ж8ф8-7
-ж8х8
8ж8х8-7
-2ж8ц8
8ж8ц8-7
-2ж8ч8
8ж8ч8-7
-ж8ш8
8ж8ш8-7
ж1ш
-ж8щ8
8ж8щ8-7
ж1щ
-2ж8ъ8
8ж8ъ8-7
-ж8ы8
8ж8ы8-7
жы1
-ж8ь8
8ж8ь8-7
-ж8э8
8ж8э8-7
ж1э
-ж8ю8
8ж8ю8-7
ж1ю
-ж8я8
8ж8я8-7
-1з8а8
8з8а8-7
-з8б8
8з8б8-7
-з8в8
8з8в8-7
-2з8г8
8з8г8-7
-з8д8
8з8д8-7
-1з8е8
8з8е8-7
-1з8ё8
8з8ё8-7
-2з8ж8
8з8ж8-7
-з8з8
8з8з8-7
-1з8и8
8з8и8-7
-з8й8
8з8й8-7
з2й1
-2з8к8
8з8к8-7
-з8л8
8з8л8-7
-2з8м8
8з8м8-7
-з8н8
8з8н8-7
-з8о8
8з8о8-7
-з8п8
8з8п8-7
з1п
-з8р8
8з8р8-7
-2з8с8
8з8с8-7
-2з8т8
8з8т8-7
-1з8у8
8з8у8-7
-з8ф8
8з8ф8-7
з1ф
-з8х8
8з8х8-7
-2з8ц8
8з8ц8-7
-з8ч8
8з8ч8-7
-2з8ш8
8з8ш8-7
-з8щ8
8з8щ8-7
з1щ
-з8ъ8
8з8ъ8-7
-1з8ы8
8з8ы8-7
-з8ь8
8з8ь8-7
-з8э8
8з8э8-7
з1э
-1з8ю8
8з8ю8-7
-1з8я8
8з8я8-7
-и8а8
8и8а8-7
-2и8б8
8и8б8-7
-и8в8
8и8в8-7
-и8г8
8и8г8-7
-и8д8
8и8д8-7
-и8е8
8и8е8-7
-и8ё8
8и8ё8-7
-и8ж8
8и8ж8-7
-и8з8
8и8з8-7
-и8и8
8и8и8-7
-и8й8
8и8й8-7
-и8к8
8и8к8-7
-и8л8
8и8л8-7
-и8м8
8и8м8-7
-и8н8
8и8н8-7
-и8о8
8и8о8-7
-2и8п8
8и8п8-7
-и8р8
8и8р8-7
-и8с8
8и8с8-7
-и8т8
8и8т8-7
-и8у8
8и8у8-7
-и8ф8
8и8ф8-7
-и8х8
8и8х8-7
-и8ц8
8и8ц8-7
и1ц
-и8ч8
8и8ч8-7
-и8ш8
8и8ш8-7
-и8щ8
8и8щ8-7
и1щ
-и8ъ8
8и8ъ8-7
иъ1
-и8ы8
8и8ы8-7
иы1
-и8ь8
8и8ь8-7
иь1
-и8э8
8и8э8-7
и1э
-и8ю8
8и8ю8-7
-и8я8
8и8я8-7
-й8а8
-2й1
8й8а8-7
-й8б8
8й8б8-7
-й8в8
8й8в8-7
-й8г8
8й8г8-7
й1г
-й8д8
8й8д8-7
-й8е8
8й8е8-7
-й8ё8
8й8ё8-7
-й8ж8
8й8ж8-7
й1ж
-й8з8
8й8з8-7
-й8и8
8й8и8-7
-2й8й8
8й8й8-7
й2й1
-й8к8
8й8к8-7
-й8л8
8й8л8-7
-й8м8
8й8м8-7
й1м
-й8н8
8й8н8-7
-й8о8
8й8о8-7
-й8п8
8й8п8-7
-й8р8
8й8р8-7
-й8с8
8й8с8-7
-й8т8
8й8т8-7
-й8у8
8й8у8-7
-й8ф8
8й8ф8-7
й1ф
-й8х8
8й8х8-7
-й8ц8
8й8ц8-7
й1ц
-й8ч8
8й8ч8-7
-й8ш8
8й8ш8-7
й1ш
-й8щ8
8й8щ8-7
й1щ
-й8ъ8
8й8ъ8-7
йъ1
-й8ы8
8й8ы8-7
йы1
-й8ь8
8й8ь8-7
йь1
-й8э8
8й8э8-7
й1э
-й8ю8
8й8ю8-7
й1ю
-й8я8
8й8я8-7
-к8а8
8к8а8-7
-2к8б8
8к8б8-7
-к8в8
8к8в8-7
-2к8г8
8к8г8-7
-2к8д8
8к8д8-7
-1к8е8
8к8е8-7
-1к8ё8
8к8ё8-7
-к8ж8
8к8ж8-7
к1ж
-2к8з8
8к8з8-7
-к8и8
8к8и8-7
-к8й8
8к8й8-7
к2й1
-2к8к8
8к8к8-7
-к8л8
8к8л8-7
-2к8м8
8к8м8-7
-2к8н8
8к8н8-7
-к8о8
8к8о8-7
-2к8п8
8к8п8-7
-к8р8
8к8р8-7
-2к8с8
8к8с8-7
-2к8т8
8к8т8-7
-к8у8
8к8у8-7
-2к8ф8
8к8ф8-7
-2к8х8
8к8х8-7
-2к8ц8
8к8ц8-7
-2к8ч8
8к8ч8-7
-2к8ш8
8к8ш8-7
-к8щ8
8к8щ8-7
к1щ
-к8ъ8
8к8ъ8-7
-к8ы8
8к8ы8-7
кы1
-1к8ь8
8к8ь8-7
-к8э8
8к8э8-7
к1э
-к8ю8
8к8ю8-7
-к8я8
8к8я8-7
-л8а8
8л8а8-7
-2л8б8
8л8б8-7
-л8в8
8л8в8-7
-л8г8
8л8г8-7
-2л8д8
8л8д8-7
-л8е8
8л8е8-7
-л8ё8
8л8ё8-7
-л8ж8
8л8ж8-7
л1ж
-л8з8
8л8з8-7
-1л8и8
8л8и8-7
-л8й8
8л8й8-7
л2й1
-2л8к8
8л8к8-7
-2л8л8
8л8л8-7
-2л8м8
8л8м8-7
-2л8н8
8л8н8-7
-1л8о8
8л8о8-7
-2л8п8
8л8п8-7
-л8р8
8л8р8-7
-2л8с8
8л8с8-7
-л8т8
8л8т8-7
-л8у8
8л8у8-7
-2л8ф8
8л8ф8-7
-2л8х8
8л8х8-7
-2л8ц8
8л8ц8-7
-л8ч8
8л8ч8-7
-л8ш8
8л8ш8-7
л1ш
-л8щ8
8л8щ8-7
л1щ
-л8ъ8
8л8ъ8-7
-л8ы8
8л8ы8-7
-л8ь8
8л8ь8-7
-л8э8
8л8э8-7
л1э
-л8ю8
8л8ю8-7
-1л8я8
8л8я8-7
-м8а8
-1м
8м8а8-7
-4м8б8
8м8б8-7
-2м8в8
8м8в8-7
-2м8г8
8м8г8-7
-2м8д8
8м8д8-7
-м8е8
8м8е8-7
-м8ё8
8м8ё8-7
-2м8ж8
8м8ж8-7
-2м8з8
8м8з8-7
-м8и8
8м8и8-7
-м8й8
8м8й8-7
м2й1
-2м8к8
8м8к8-7
-2м8л8
8м8л8-7
-2м8м8
8м8м8-7
-2м8н8
8м8н8-7
-м8о8
8м8о8-7
-2м8п8
8м8п8-7
-м8р8
8м8р8-7
-2м8с8
8м8с8-7
-2м8т8
8м8т8-7
-м8у8
8м8у8-7
-2м8ф8
8м8ф8-7
-2м8х8
8м8х8-7
-2м8ц8
8м8ц8-7
-м8ч8
8м8ч8-7
-2м8ш8
8м8ш8-7
-2м8щ8
8м8щ8-7
-м8ъ8
8м8ъ8-7
-м8ы8
8м8ы8-7
-м8ь8
8м8ь8-7
-2м8э8
8м8э8-7
-м8ю8
8м8ю8-7
-м8я8
8м8я8-7
-1н8а8
8н8а8-7
-2н8б8
8н8б8-7
-2н8в8
8н8в8-7
-2н8г8
8н8г8-7
-2н8д8
8н8д8-7
-1н8е8
8н8е8-7
-1н8ё8
8н8ё8-7
-н8ж8
8н8ж8-7
н1ж
-2н8з8
8н8з8-7
-1н8и8
8н8и8-7
-н8й8
8н8й8-7
н2й1
-2н8к8
8н8к8-7
-н8л8
8н8л8-7
-н8м8
8н8м8-7
н1м
-2н8н8
8н8н8-7
-1н8о8
8н8о8-7
-н8п8
8н8п8-7
-н8р8
8н8р8-7
-2н8с8
8н8с8-7
-2н8т8
8н8т8-7
-1н8у8
8н8у8-7
-2н8ф8
8н8ф8-7
-н8х8
8н8х8-7
-2н8ц8
8н8ц8-7
-2н8ч8
8н8ч8-7
-2н8ш8
8н8ш8-7
-2н8щ8
8н8щ8-7
-н8ъ8
8н8ъ8-7
-1н8ы8
8н8ы8-7
-н8ь8
8н8ь8-7
-н8э8
8н8э8-7
-1н8ю8
8н8ю8-7
-1н8я8
8н8я8-7
-2о8а8
8о8а8-7
-о8б8
8о8б8-7
-о8в8
8о8в8-7
-о8г8
8о8г8-7
-о8д8
8о8д8-7
-о8е8
8о8е8-7
-о8ё8
8о8ё8-7
-о8ж8
8о8ж8-7
-о8з8
8о8з8-7
-о8и8
8о8и8-7
-2о8й8
8о8й8-7
-о8к8
8о8к8-7
-о8л8
8о8л8-7
-о8м8
8о8м8-7
-о8н8
8о8н8-7
-о8о8
8о8о8-7
-о8п8
8о8п8-7
-о8р8
8о8р8-7
-о8с8
8о8с8-7
-о8т8
8о8т8-7
-о8у8
8о8у8-7
-о8ф8
8о8ф8-7
-о8х8
8о8х8-7
-о8ц8
8о8ц8-7
-о8ч8
8о8ч8-7
-о8ш8
8о8ш8-7
-о8щ8
8о8щ8-7
о1щ
-о8ъ8
8о8ъ8-7
оъ1
-о8ы8
8о8ы8-7
оы1
-о8ь8
8о8ь8-7
оь1
-о8э8
8о8э8-7
-2о8ю8
8о8ю8-7
-о8я8
8о8я8-7
-п8а8
-1п
8п8а8-7
-п8б8
8п8б8-7
-2п8в8
8п8в8-7
-п8г8
8п8г8-7
п1г
-2п8д8
8п8д8-7
-п8е8
8п8е8-7
-п8ё8
8п8ё8-7
-п8ж8
8п8ж8-7
п1ж
-п8з8
8п8з8-7
-п8и8
8п8и8-7
-п8й8
8п8й8-7
п2й1
-4п8к8
8п8к8-7
-п8л8
8п8л8-7
-п8м8
8п8м8-7
п1м
-2п8н8
8п8н8-7
-п8о8
8п8о8-7
-2п8п8
8п8п8-7
-п8р8
8п8р8-7
-2п8с8
8п8с8-7
-2п8т8
8п8т8-7
-п8у8
8п8у8-7
-2п8ф8
8п8ф8-7
-п8х8
8п8х8-7
-2п8ц8
8п8ц8-7
-4п8ч8
8п8ч8-7
-2п8ш8
8п8ш8-7
-2п8щ8
8п8щ8-7
-п8ъ8
8п8ъ8-7
-п8ы8
8п8ы8-7
пы1
-п8ь8
8п8ь8-7
-п8э8
8п8э8-7
-п8ю8
8п8ю8-7
-п8я8
8п8я8-7
-р8а8
8р8а8-7
-2р8б8
8р8б8-7
-р8в8
8р8в8-7
-2р8г8
8р8г8-7
-2р8д8
8р8д8-7
-р8е8
8р8е8-7
-р8ё8
8р8ё8-7
-р8ж8
8р8ж8-7
-2р8з8
8р8з8-7
-р8и8
8р8и8-7
-р8й8
8р8й8-7
р2й1
-2р8к8
8р8к8-7
-2р8л8
8р8л8-7
-2р8м8
8р8м8-7
-2р8н8
8р8н8-7
-р8о8
8р8о8-7
-2р8п8
8р8п8-7
-2р8р8
8р8р8-7
-4р8с8
8р8с8-7
-2р8т8
8р8т8-7
-р8у8
8р8у8-7
-2р8ф8
8р8ф8-7
-2р8х8
8р8х8-7
-2р8ц8
8р8ц8-7
-2р8ч8
8р8ч8-7
-2р8ш8
8р8ш8-7
-2р8щ8
8р8щ8-7
-2р8ъ8
8р8ъ8-7
-р8ы8
8р8ы8-7
-р8ь8
8р8ь8-7
-р8э8
8р8э8-7
-р8ю8
8р8ю8-7
-р8я8
8р8я8-7
-1с8а8
8с8а8-7
-1с8б8
8с8б8-7
-1с8в8
8с8в8-7
-с8г8
8с8г8-7
-1с8д8
8с8д8-7
-1с8е8
8с8е8-7
-1с8ё8
8с8ё8-7
-1с8ж8
8с8ж8-7
-с8з8
8с8з8-7
-1с8и8
8с8и8-7
-с8й8
8с8й8-7
с2й1
-с8к8
8с8к8-7
-с8л8
8с8л8-7
-с8м8
8с8м8-7
-с8н8
8с8н8-7
-1с8о8
8с8о8-7
-с8п8
8с8п8-7
-1с8р8
8с8р8-7
-2с8с8
8с8с8-7
-с8т8
8с8т8-7
-1с8у8
8с8у8-7
-с8ф8
8с8ф8-7
-с8х8
8с8х8-7
-с8ц8
8с8ц8-7
-с8ч8
8с8ч8-7
-с8ш8
8с8ш8-7
-с8щ8
8с8щ8-7
с1щ
-1с8ъ8
8с8ъ8-7
-1с8ы8
8с8ы8-7
-с8ь8
8с8ь8-7
-с8э8
8с8э8-7
-1с8ю8
8с8ю8-7
-1с8я8
8с8я8-7
-т8а8
8т8а8-7
-2т8б8
8т8б8-7
-т8в8
8т8в8-7
-2т8г8
8т8г8-7
-2т8д8
8т8д8-7
-1т8е8
8т8е8-7
-1т8ё8
8т8ё8-7
-2т8ж8
8т8ж8-7
-2т8з8
8т8з8-7
-т8и8
8т8и8-7
-т8й8
8т8й8-7
т2й1
-2т8к8
8т8к8-7
-2т8л8
8т8л8-7
-т8м8
8т8м8-7
-2т8н8
8т8н8-7
-т8о8
8т8о8-7
-2т8п8
8т8п8-7
-т8р8
8т8р8-7
-4т8с8
8т8с8-7
-2т8т8
8т8т8-7
-т8у8
8т8у8-7
-2т8ф8
8т8ф8-7
-2т8х8
8т8х8-7
-4т8ц8
8т8ц8-7
-2т8ч8
8т8ч8-7
-2т8ш8
8т8ш8-7
-2т8щ8
8т8щ8-7
-2т8ъ8
8т8ъ8-7
-т8ы8
8т8ы8-7
-2т8ь8
8т8ь8-7
-2т8э8
8т8э8-7
-т8ю8
8т8ю8-7
-т8я8
8т8я8-7
-у8а8
8у8а8-7
-у8б8
8у8б8-7
-у8в8
8у8в8-7
-у8г8
8у8г8-7
-у8д8
8у8д8-7
-у8е8
8у8е8-7
-у8ё8
8у8ё8-7
-у8ж8
8у8ж8-7
-у8з8
8у8з8-7
-у8и8
8у8и8-7
-у8й8
8у8й8-7
у2й1
-у8к8
8у8к8-7
-у8л8
8у8л8-7
-у8м8
8у8м8-7
-у8н8
8у8н8-7
-у8о8
8у8о8-7
-у8п8
8у8п8-7
-у8р8
8у8р8-7
-у8с8
8у8с8-7
-у8т8
8у8т8-7
-у8у8
8у8у8-7
-у8ф8
8у8ф8-7
-у8х8
8у8х8-7
-у8ц8
8у8ц8-7
у1ц
-у8ч8
8у8ч8-7
-у8ш8
8у8ш8-7
-у8щ8
8у8щ8-7
у1щ
-у8ъ8
8у8ъ8-7
уъ1
-у8ы8
8у8ы8-7
уы1
-у8ь8
8у8ь8-7
уь1
-2у8э8
8у8э8-7
-у8ю8
8у8ю8-7
у1ю
-у8я8
8у8я8-7
-ф8а8
-1ф
8ф8а8-7
-2ф8б8
8ф8б8-7
-2ф8в8
8ф8в8-7
-2ф8г8
8ф8г8-7
-2ф8д8
8ф8д8-7
-ф8е8
8ф8е8-7
-ф8ё8
8ф8ё8-7
-ф8ж8
8ф8ж8-7
ф1ж
-ф8з8
8ф8з8-7
-ф8и8
8ф8и8-7
-ф8й8
8ф8й8-7
ф2й1
-2ф8к8
8ф8к8-7
-ф8л8
8ф8л8-7
-2ф8м8
8ф8м8-7
-2ф8н8
8ф8н8-7
-ф8о8
8ф8о8-7
-ф8п8
8ф8п8-7
ф1п
-ф8р8
8ф8р8-7
-2ф8с8
8ф8с8-7
-2ф8т8
8ф8т8-7
-ф8у8
8ф8у8-7
-2ф8ф8
8ф8ф8-7
-ф8х8
8ф8х8-7
-ф8ц8
8ф8ц8-7
ф1ц
-2ф8ч8
8ф8ч8-7
-2ф8ш8
8ф8ш8-7
-ф8щ8
8ф8щ8-7
ф1щ
-ф8ъ8
8ф8ъ8-7
-ф8ы8
8ф8ы8-7
фы1
-ф8ь8
8ф8ь8-7
-ф8э8
8ф8э8-7
ф1э
-ф8ю8
8ф8ю8-7
-ф8я8
8ф8я8-7
-1х8а8
8х8а8-7
-2х8б8
8х8б8-7
-1х8в8
8х8в8-7
-2х8г8
8х8г8-7
-х8д8
8х8д8-7
-1х8е8
8х8е8-7
-х8ё8
8х8ё8-7
-х8ж8
8х8ж8-7
х1ж
-х8з8
8х8з8-7
-1х8и8
8х8и8-7
-х8й8
8х8й8-7
х2й1
-х8к8
8х8к8-7
-х8л8
8х8л8-7
-х8м8
8х8м8-7
-2х8н8
8х8н8-7
-1х8о8
8х8о8-7
-х8п8
8х8п8-7
-х8р8
8х8р8-7
-2х8с8
8х8с8-7
-2х8т8
8х8т8-7
-х8у8
8х8у8-7
-х8ф8
8х8ф8-7
х1ф
-х8х8
8х8х8-7
-х8ц8
8х8ц8-7
х1ц
-2х8ч8
8х8ч8-7
-2х8ш8
8х8ш8-7
-х8щ8
8х8щ8-7
х1щ
-х8ъ8
8х8ъ8-7
-х8ы8
8х8ы8-7
хы1
-х8ь8
8х8ь8-7
-х8э8
8х8э8-7
х1э
-х8ю8
8х8ю8-7
х1ю
-х8я8
8х8я8-7
-ц8а8
-1ц
8ц8а8-7
-2ц8б8
8ц8б8-7
-ц8в8
8ц8в8-7
-2ц8г8
8ц8г8-7
-2ц8д8
8ц8д8-7
-ц8е8
8ц8е8-7
-ц8ё8
8ц8ё8-7
-ц8ж8
8ц8ж8-7
ц1ж
-2ц8з8
8ц8з8-7
-ц8и8
8ц8и8-7
-ц8й8
8ц8й8-7
ц2й1
-2ц8к8
8ц8к8-7
-2ц8л8
8ц8л8-7
-2ц8м8
8ц8м8-7
-2ц8н8
8ц8н8-7
-ц8о8
8ц8о8-7
-2ц8п8
8ц8п8-7
-2ц8р8
8ц8р8-7
-2ц8с8
8ц8с8-7
-2ц8т8
8ц8т8-7
-3ц8у8
8ц8у8-7
-ц8ф8
8ц8ф8-7
ц1ф
-ц8х8
8ц8х8-7
-2ц8ц8
8ц8ц8-7
-ц8ч8
8ц8ч8-7
-2ц8ш8
8ц8ш8-7
-ц8щ8
8ц8щ8-7
ц1щ
-ц8ъ8
8ц8ъ8-7
-3ц8ы8
8ц8ы8-7
-ц8ь8
8ц8ь8-7
ць1
-ц8э8
8ц8э8-7
ц1э
-ц8ю8
8ц8ю8-7
-ц8я8
8ц8я8-7
-1ч8а8
8ч8а8-7
-2ч8б8
8ч8б8-7
-ч8в8
8ч8в8-7
-ч8г8
8ч8г8-7
ч1г
-2ч8д8
8ч8д8-7
-1ч8е8
8ч8е8-7
-1ч8ё8
8ч8ё8-7
-ч8ж8
8ч8ж8-7
-ч8з8
8ч8з8-7
-1ч8и8
8ч8и8-7
-ч8й8
8ч8й8-7
ч2й1
-2ч8к8
8ч8к8-7
-ч8л8
8ч8л8-7
-1ч8м8
8ч8м8-7
-2ч8н8
8ч8н8-7
-3ч8о8
8ч8о8-7
-ч8п8
8ч8п8-7
ч1п
-ч8р8
8ч8р8-7
-2ч8с8
8ч8с8-7
-ч8т8
8ч8т8-7
-1ч8у8
8ч8у8-7
-ч8ф8
8ч8ф8-7
ч1ф
-ч8х8
8ч8х8-7
-ч8ц8
8ч8ц8-7
ч1ц
-2ч8ч8
8ч8ч8-7
-ч8ш8
8ч8ш8-7
ч1ш
-ч8щ8
8ч8щ8-7
ч1щ
-ч8ъ8
8ч8ъ8-7
-ч8ы8
8ч8ы8-7
чы1
-ч8ь8
8ч8ь8-7
-ч8э8
8ч8э8-7
ч1э
-ч8ю8
8ч8ю8-7
ч1ю
-ч8я8
8ч8я8-7
-ш8а8
-1ш
8ш8а8-7
-2ш8б8
8ш8б8-7
-ш8в8
8ш8в8-7
-ш8г8
8ш8г8-7
ш1г
-ш8д8
8ш8д8-7
-ш8е8
8ш8е8-7
-ш8ё8
8ш8ё8-7
-ш8ж8
8ш8ж8-7
ш1ж
-ш8з8
8ш8з8-7
-ш8и8
8ш8и8-7
-ш8й8
8ш8й8-7
ш2й1
-2ш8к8
8ш8к8-7
-ш8л8
8ш8л8-7
-2ш8м8
8ш8м8-7
-2ш8н8
8ш8н8-7
-ш8о8
8ш8о8-7
-ш8п8
8ш8п8-7
-2ш8р8
8ш8р8-7
-2ш8с8
8ш8с8-7
-ш8т8
8ш8т8-7
-ш8у8
8ш8у8-7
-2ш8ф8
8ш8ф8-7
-ш8х8
8ш8х8-7
-2ш8ц8
8ш8ц8-7
-2ш8ч8
8ш8ч8-7
-1ш8ш8
8ш8ш8-7
ш1ш
-ш8щ8
8ш8щ8-7
ш1щ
-ш8ъ8
8ш8ъ8-7
-ш8ы8
8ш8ы8-7
шы1
-2ш8ь8
8ш8ь8-7
-ш8э8
8ш8э8-7
ш1э
-ш8ю8
8ш8ю8-7
-ш8я8
8ш8я8-7
-щ8а8
-1щ
8щ8а8-7
-щ8б8
8щ8б8-7
-2щ8в8
8щ8в8-7
-щ8г8
8щ8г8-7
щ1г
-щ8д8
8щ8д8-7
-щ8е8
8щ8е8-7
-щ8ё8
8щ8ё8-7
-щ8ж8
8щ8ж8-7
щ1ж
-щ8з8
8щ8з8-7
-щ8и8
8щ8и8-7
-щ8й8
8щ8й8-7
щ2й1
-щ8к8
8щ8к8-7
-щ8л8
8щ8л8-7
-2щ8м8
8щ8м8-7
-2щ8н8
8щ8н8-7
-щ8о8
8щ8о8-7
-щ8п8
8щ8п8-7
щ1п
-2щ8р8
8щ8р8-7
-щ8с8
8щ8с8-7
-щ8т8
8щ8т8-7
-щ8у8
8щ8у8-7
-щ8ф8
8щ8ф8-7
щ1ф
-щ8х8
8щ8х8-7
-щ8ц8
8щ8ц8-7
щ1ц
-щ8ч8
8щ8ч8-7
-щ8ш8
8щ8ш8-7
щ1ш
-1щ8щ8
8щ8щ8-7
щ1щ
-щ8ъ8
8щ8ъ8-7
-щ8ы8
8щ8ы8-7
щы1
-щ8ь8
8щ8ь8-7
-щ8э8
8щ8э8-7
щ1э
-щ8ю8
8щ8ю8-7
щ1ю
-щ8я8
8щ8я8-7
-ъ8а8
-ъ1
8ъ8а8-7
-ъ8б8
8ъ8б8-7
-ъ8в8
8ъ8в8-7
-ъ8г8
8ъ8г8-7
ъ1г
-ъ8д8
8ъ8д8-7
-ъ8е8
8ъ8е8-7
-ъ8ё8
8ъ8ё8-7
-ъ8ж8
8ъ8ж8-7
ъ1ж
-ъ8з8
8ъ8з8-7
-ъ8и8
8ъ8и8-7
-ъ8й8
8ъ8й8-7
ъ2й1
-ъ8к8
8ъ8к8-7
-ъ8л8
8ъ8л8-7
-ъ8м8
8ъ8м8-7
ъ1м
-ъ8н8
8ъ8н8-7
-ъ8о8
8ъ8о8-7
-ъ8п8
8ъ8п8-7
ъ1п
-ъ8р8
8ъ8р8-7
-ъ8с8
8ъ8с8-7
-ъ8т8
8ъ8т8-7
-ъ8у8
8ъ8у8-7
-ъ8ф8
8ъ8ф8-7
ъ1ф
-ъ8х8
8ъ8х8-7
-ъ8ц8
8ъ8ц8-7
ъ1ц
-ъ8ч8
8ъ8ч8-7
-ъ8ш8
8ъ8ш8-7
ъ1ш
-ъ8щ8
8ъ8щ8-7
ъ1щ
-ъ8ъ8
8ъ8ъ8-7
ъъ1
-ъ8ы8
8ъ8ы8-7
ъы1
-ъ8ь8
8ъ8ь8-7
ъь1
-ъ8э8
8ъ8э8-7
ъ1э
-ъ8ю8
8ъ8ю8-7
-ъ8я8
8ъ8я8-7
-ы8а8
-ы1
8ы8а8-7
-ы8б8
8ы8б8-7
-ы8в8
8ы8в8-7
-ы8г8
8ы8г8-7
-ы8д8
8ы8д8-7
-ы8е8
8ы8е8-7
-ы8ё8
8ы8ё8-7
-ы8ж8
8ы8ж8-7
-ы8з8
8ы8з8-7
-ы8и8
8ы8и8-7
-ы8й8
8ы8й8-7
ы2й1
-ы8к8
8ы8к8-7
-ы8л8
8ы8л8-7
-ы8м8
8ы8м8-7
-ы8н8
8ы8н8-7
-ы8о8
8ы8о8-7
-ы8п8
8ы8п8-7
-ы8р8
8ы8р8-7
-ы8с8
8ы8с8-7
-ы8т8
8ы8т8-7
-ы8у8
8ы8у8-7
-ы8ф8
8ы8ф8-7
ы1ф
-ы8х8
8ы8х8-7
-ы8ц8
8ы8ц8-7
ы1ц
-ы8ч8
8ы8ч8-7
-ы8ш8
8ы8ш8-7
-ы8щ8
8ы8щ8-7
ы1щ
-ы8ъ8
8ы8ъ8-7
ыъ1
-ы8ы8
8ы8ы8-7
ыы1
-ы8ь8
8ы8ь8-7
ыь1
-ы8э8
8ы8э8-7
ы1э
-ы8ю8
8ы8ю8-7
ы1ю
-ы8я8
8ы8я8-7
-ь8а8
-ь1
8ь8а8-7
-ь8б8
8ь8б8-7
-ь8в8
8ь8в8-7
-ь8г8
8ь8г8-7
ь1г
-ь8д8
8ь8д8-7
-ь8е8
8ь8е8-7
-ь8ё8
8ь8ё8-7
-ь8ж8
8ь8ж8-7
ь1ж
-ь8з8
8ь8з8-7
-ь8и8
8ь8и8-7
-ь8й8
8ь8й8-7
ь2й1
-ь8к8
8ь8к8-7
-ь8л8
8ь8л8-7
-ь8м8
8ь8м8-7
-ь8н8
8ь8н8-7
-ь8о8
8ь8о8-7
-ь8п8
8ь8п8-7
-ь8р8
8ь8р8-7
-ь8с8
8ь8с8-7
-ь8т8
8ь8т8-7
-ь8у8
8ь8у8-7
-ь8ф8
8ь8ф8-7
ь1ф
-ь8х8
8ь8х8-7
-ь8ц8
8ь8ц8-7
ь1ц
-ь8ч8
8ь8ч8-7
-ь8ш8
8ь8ш8-7
ь1ш
-ь8щ8
8ь8щ8-7
-ь8ъ8
8ь8ъ8-7
ьъ1
-ь8ы8
8ь8ы8-7
ьы1
-ь8ь8
8ь8ь8-7
ьь1
-ь8э8
8ь8э8-7
ь1э
-ь8ю8
8ь8ю8-7
-ь8я8
8ь8я8-7
-э8а8
-1э
8э8а8-7
-э8б8
8э8б8-7
-э8в8
8э8в8-7
-2э8г8
8э8г8-7
-э8д8
8э8д8-7
-э8е8
8э8е8-7
-э8ё8
8э8ё8-7
-э8ж8
8э8ж8-7
э1ж
-э8з8
8э8з8-7
-э8и8
8э8и8-7
-э8й8
8э8й8-7
э2й1
-э8к8
8э8к8-7
-э8л8
8э8л8-7
-э8м8
8э8м8-7
-э8н8
8э8н8-7
-э8о8
8э8о8-7
-э8п8
8э8п8-7
-э8р8
8э8р8-7
-э8с8
8э8с8-7
-э8т8
8э8т8-7
-э8у8
8э8у8-7
-э8ф8
8э8ф8-7
-э8х8
8э8х8-7
-э8ц8
8э8ц8-7
-э8ч8
8э8ч8-7
-э8ш8
8э8ш8-7
э1ш
-э8щ8
8э8щ8-7
э1щ
-э8ъ8
8э8ъ8-7
эъ1
-э8ы8
8э8ы8-7
эы1
-э8ь8
8э8ь8-7
эь1
-1э8э8
8э8э8-7
э1э
-э8ю8
8э8ю8-7
э1ю
-э8я8
8э8я8-7
-ю8а8
-1ю
8ю8а8-7
-ю8б8
8ю8б8-7
-ю8в8
8ю8в8-7
-ю8г8
8ю8г8-7
ю1г
-ю8д8
8ю8д8-7
-ю8е8
8ю8е8-7
-ю8ё8
8ю8ё8-7
-ю8ж8
8ю8ж8-7
ю1ж
-ю8з8
8ю8з8-7
-ю8и8
8ю8и8-7
-ю8й8
8ю8й8-7
ю2й1
-ю8к8
8ю8к8-7
-ю8л8
8ю8л8-7
-2ю8м8
8ю8м8-7
-ю8н8
8ю8н8-7
-ю8о8
8ю8о8-7
-ю8п8
8ю8п8-7
ю1п
-ю8р8
8ю8р8-7
-ю8с8
8ю8с8-7
-ю8т8
8ю8т8-7
-ю8у8
8ю8у8-7
-ю8ф8
8ю8ф8-7
ю1ф
-ю8х8
8ю8х8-7
-ю8ц8
8ю8ц8-7
ю1ц
-ю8ч8
8ю8ч8-7
-ю8ш8
8ю8ш8-7
ю1ш
-ю8щ8
8ю8щ8-7
-ю8ъ8
8ю8ъ8-7
юъ1
-ю8ы8
8ю8ы8-7
юы1
-ю8ь8
8ю8ь8-7
юь1
-ю8э8
8ю8э8-7
ю1э
-1ю8ю8
8ю8ю8-7
ю1ю
-ю8я8
8ю8я8-7
-я8а8
8я8а8-7
-я8б8
8я8б8-7
-я8в8
8я8в8-7
-я8г8
8я8г8-7
-я8д8
8я8д8-7
-я8е8
8я8е8-7
-я8ё8
8я8ё8-7
-я8ж8
8я8ж8-7
я1ж
-я8з8
8я8з8-7
-я8и8
8я8и8-7
-я8й8
8я8й8-7
я2й1
-я8к8
8я8к8-7
-я8л8
8я8л8-7
-я8м8
8я8м8-7
-я8н8
8я8н8-7
-я8о8
8я8о8-7
-я8п8
8я8п8-7
я1п
-я8р8
8я8р8-7
-я8с8
8я8с8-7
-я8т8
8я8т8-7
-я8у8
8я8у8-7
-я8ф8
8я8ф8-7
я1ф
-я8х8
8я8х8-7
-я8ц8
8я8ц8-7
-я8ч8
8я8ч8-7
-я8ш8
8я8ш8-7
-я8щ8
8я8щ8-7
я1щ
-я8ъ8
8я8ъ8-7
яъ1
-я8ы8
8я8ы8-7
яы1
-я8ь8
8я8ь8-7
яь1
-я8э8
8я8э8-7
я1э
-я8ю8
8я8ю8-7
-2я8я8
8я8я8-7
.а8с9б8е8с8т.
.а1сб2
.асбе3с2
.ас1бе
.асбе2с1т
.б8е8з8д8н.
.бе2з
.без5д4
.без2д1н
.б8и8з9н8е8с9м8е8н.
.биз1н
.би2з1не
.бизне1с4м
.б8у8й9н8а8к9с8к8е.
.б2у
.бу2й1
.буй1на
.буйна1к
.буйна2к1с
.буйнакс1ке
.в8б8л8и9з8и.
.в1б
.вб1л
.вб1ли
.вбли1зи
.в8з8б8а9л8а9м8у8т8ь9с8я.
.в2з2
.вз1б2
.вз1ба
.взба1ла
.взбала1м
.взбаламу3ть1
.взбаламуть1ся
.в8з8д8р8е8м9н8е8ш8ь.
.взд2
.взд1ре
.взд2ре1м
.вздре2м3н
.вздре3м2не1ш
.вздрем1не
.вздремне2шь1
.в8о9д8о9с8л8и9в8о8м.
.во1до
.водо1с
.водо1с2л
.водос1ли
.водос3л2и1во
.водосливо3м2
.в8о8л8ж9с8к8е.
.вол1ж
.вол2ж1с
.волжс1ке
.в8о8п9л8е8м.
.во1п2
.вопле1м
.в8о8п8л8ь.
.вопль1
.в8о8с8т9р8а.
.во2стр
.во1ст
.во3стра
.в8о9т8к8а8т8ь.
.во1т
.во2т1к2
.вот1кат
.вотка2ть1
.в8о9т8к8е8м.
.вот1ке
.вотке1м
.в8о9т8к8е8ш8ь.
.вотке1ш
.вотке2шь1
.в8о9т8к8у.
.вотк2у
.в8о9т8к8у8т.
.вот1кут
.в8п8о8л9о8б8о9р8о9т8а.
.в1п2
.впо3ло
.вполо1бо
.вполобо1ро
.вполоборо1т
.в8п8о8л9у8х8а.
.впо1лу
.вполу1ха
.в8с8е9в8о9л8о8ж9с8к8е.
.в1с2е3
.все1во
.вс2евол
.всево3ло
.всеволо1ж
.всеволо2ж1с
.всеволожс1ке
.в8ц8с8п8с.
.в1ц
.в2ц1с
.вцс1п2
.вцс2п1с2
.г8а9р8е8м9н8о9г8о.
.г2а
.га1ре
.гаре3м1но
.гаре1м
.гаре2м1н
.гаремно1г2
.г8о9л8о9д8р8а9н8е8ц.
.г2ол
.го3ло
.голо1др
.голод1ра1не
.голодране1ц
.г8р8э8с.
.гр1э
.грэс1
.д8в8у9з8у9б8е8ц.
.дв2
.д1ву1з
.дву1зу
.дву2з1у2бе
.двузу3бе1ц
.д8н8е8п8р.
.д1н
.д1не
.дне3п2
.д8о8б8р8е9е8м.
.добре1е
.добрее1м
.д8о9б8р8е9е8м9с8я.
.добрее2м1с
.добреем1ся
.д8о8б8р8е9е8т.
.д8о8б8р8е9е9т8е.
.добрее1т2е
.д8о9б8р8е9е9т8е8с8ь.
.добреетесь1
.д8о9б8р8е9е8т9с8я.
.добрее4т1с2
.добреет1ся
.д8о8б8р8е9е8ш8ь.
.добрее1ш
.добрее2шь1
.д8о9б8р8е9е8ш8ь9с8я.
.добреешь1ся
.д8о8б8р8е8ю.
.добре1ю
.д8о9б8р8е9ю8с8ь.
.добреюсь1
.д8о8б8р8е9ю8т.
.д8о9б8р8е9ю8т9с8я.
.добрею4т1с2
.добреют1ся
.д8о9б8р8е9с8т8и.
.добре1ст
.добре1сти
.д8о9б8р8о9д8я8т.
.добро1дя
.д8о9б8р8о8с8ь.
.добрось1
.д8о9б8р8о8с8ь9т8е.
.добро2сьт
.добрось1т2е
.д8о9б8р8о9с8я8т.
.добро1ся
.д8о9б8р8о9ш8у.
.добро1ш
.д8о8м8н8у.
.до1м
.до2м1н
.дом1ну
.д8о8п9п8е8л8ь.
.до2п1п2
.доппе1
.доппе2ль1
.д8р8а8х9м8у.
.д1рах
.дра1х2му
.драх1м
.д8р8е8й8ф9л8ю.
.д1ре
.дре2й1
.дрей1ф
.дрейфл2ю
.д8р8е8й8ф8ь9т8е.
.дрейфь1
.дрейфь1т2е
.е8д8и9н8о9ж8д8ы.
.е1ди
.еди1но
.едино1ж
.едино2ж1д
.единож1ды1
.з8а8в9с8е8к9т8о9р8о8м.
.за3в2с2е3
.за2в1с2
.завсе2к1т
.завсекто1ро
.завсекторо1м
.з8а9м8р8у.
.з8а9ч8л8и8с8ь.
.за1ч
.за2чл
.за2ч1ли
.зачлись1
.и8з9д8р8е8в9л8е.
.из1д
.изд1ре
.издрев1л
.и8з8о9т8р8у.
.и1зо
.изо1т
.и8н9к8о8г9н8и9т8о.
.и2н1к
.инк2о
.инко1г2
.инког1ни
.инкогни1т
.и8с8к8р.
.ис1к
.иск2р
.к8а9з8а9ш8е8к.
.каза4ш3
.ка1за1
.казаше1к
.к8а8з8н8ь.
.каз3н
.казнь1
.к8о8л8ь8д9к8р8е9м8о8м.
.к2о
.ко2льд
.коль1
.коль2д1к
.кольдк3ремо
.кольдкре1м
.кольдкре1мо3м2
.к8о8р8н9п8а9п8и8р.
.ко2р1н
.корн3п2
.корнпа1п
.корнпапи1р
.к8с8е8н8д8з.
.к1с2е
.ксе2н1д
.ксен3д2з2
.л8и8к9б8е9з8о8м.
.ли1к
.ли2к1б
.лик1бе
.лик3бе2з
.ликбез1о2
.ликбе1зо3м2
.л8о9ш8а8д8ь9м8и.
.ло1ш
.лоша1д
.лошадь1
.лошадь1м
.л8ю8д8ь9м8и.
.лю1дь1
.людь1м
.л8ю9э9с8о8м.
.лю1э
.люэс1
.люэ2со
.люэсо3м2
.м8а9з8у9т8е.
.ма1зу
.маз1у2т2е
.м8е9т8и9л8а8м.
.мети2л1а2м
.ме1ти
.мети1л
.м8е9т8и9л8а9м8и.
.м8н8о9г8а9ж8д8ы.
.м1н
.м1но
.мно1г2
.мног2а
.многа1ж
.многа2ж1д
.многаж1ды1
.м8о8р8щ8ь9т8е.
.мо2р1щ
.морщь1
.морщь1т2е
.н8а9б8е9к8р8е8н8ь.
.на1б
.на1бе
.наб2е1кр
.набек1рень1
.н8а8в8з9н8и8ч8ь.
.на1в2з2
.навз1н
.нав2з3ни
.навзни1ч
.навзничь1
.н8а9в8с8к8и8д9к8у.
.на2в1с2
.на4в3ски
.навски2д1к
.навскидк2у
.н8а9в8с8т8р8е9ч8у.
.навстре1чу
.н8а8г8л.
.на1г
.н8а9и8з8у8с8т8ь.
.на1и
.наи1зу
.наизус4ть1
.н8а9и8с9к8о9с8о8к.
.на2и1с2
.наис1к
.наиск2о
.наис1кос
.наиско1со
.наискосо1к
.н8а8и9м8е9н8е8е.
.наи1м
.наиме1не
.наимене3е
.н8а9и8с9к8о8с8ь.
.наискось1
.н8а9о8б8о9р8о8т.
.н2а1о
.нао1бо
.наобо1ро
.наоборо1т
.н8а9о8т9р8е8з.
.нао1т
.н8а9с8у8п8ь9с8я.
.на1с2
.на1су
.насу1п
.насупь1
.насупь1ся
.н8а9у8г8а8д.
.на1у
.нау1г
.науг2а
.науга1д
.н8а9у8г8о8л8ь9н8и8к.
.науг2ол
.науго2льн
.науголь1
.науголь1ни
.наугольни1к
.н8е9о8с8т9р8а.
.н2е1о2
.неос2
.неост1р
.нео1ст
.нео3стра
.н8е8с9л8а8с8ь.
.нес1лас
.неслась1
.н8е8с9л8и8с8ь.
.нес1ли
.неслись1
.н8е8т9т8о.
.не2т1т
.н8е9у8д8у.
.не1у2
.неу3ду
.о8б8и8д8ь9с8я.
.о1б2и
.оби1дь1
.обидь1ся
.о8б8о9ш8л8о8с8ь.
.обо1ш
.обош1ло
.обошлось1
.о8б9р8а9с8т8и.
.о1брас
.обрас1т
.обра1сти
.о8д9н8а9ж8д8ы.
.о2д1н
.од3на
.одна3ж1д
.одна1ж
.однаж1ды1
.о8с8л8а8б9л8а.
.о1с2л
.осла2б1л
.осла1б
.осла3бла
.о8т8о9м8с8т8я8т.
.о2то2м1с
.ото1м
.о8т8о9м8щ8у.
.ото2м1щ
.о8т8о9т8р8у.
.ото1т
.о8т8р8у.
.о8т8р8у8с8ь.
.отру2сь1
.п8а8б9л8и9с8и9т8и.
.па1б
.паб1л
.паб1ли
.пабли1с2и
.паблиси1т
.п8а9н8а9м8е.
.па2н1а2ме
.па1на
.пана1м
.п8а9н8а9м8е8ц.
.панаме1ц
.п8а9р8а9т8а8к9с8и8с.
.па1ра
.пар1а2та1к
.пара1та
.парата2к1с
.паратак1с2и
.п8е9р8е9в8р8у.
.пе1
.пе1ре
.пере1вр
.п8е9р8е9м8е9ж8а8т8ь.
.пере1м
.переме2ж1ат
.переме1ж
.перемежа2ть1
.п8е9р8е9м8е9ж8а8т8ь9с8я.
.перемежать1ся
.п8е9р8е9ш8л8а.
.пере3шл
.пере1ш
.п8и8с9ч8а8я.
.писч2
.пи1с1ча
.писча1я
.п8о9в8с8е9д8н8е9в8е8н.
.по3в2с2
.по3в2с2е3
.повсе2д1н
.повсед1не
.повседне1ве
.п8о9г8р8е9м8о8к.
.по1г2
.пог3ремо
.погре1м
.погремо1к
.п8о9д8о9т8р8у.
.по1до
.подо1т
.п8о9и8с9т8и9н8е.
.по3и
.пои1ст
.пои1сти
.поисти1не
.п8о9л8у9т8о9р8а9с8т8а.
.по1лу
.полу1то
.полуто1ра
.полутора2с3
.полуторас1т
.полуто1раста
.п8о9л8у9я8в8ь8ю.
.полу1я
.полуя1вь2ю
.полуявь1
.п8о9м8л8а8д9ш8е.
.по2м1л
.помла1д
.помла2д3ш2
.п8о8м8н8и.
.пом1ни
.п8о9м8н8и8с8ь.
.помнись1
.п8о8м8н8и9т8е.
.помни1т
.помни1т2е
.п8о9м8н8и9т8е8с8ь.
.помнитесь1
.п8о9м8н8о9г8у.
.по3м1но
.помно1г2
.п8о9м8р8у.
.п8о8л9в8т8о9р8о9г8о.
.полв1т2
.полвто1ро
.полвторо1г2
.п8о8л9ш8к8а9ф8а.
.пол1ш
.пол2ш1к2
.полшка1ф
.п8о9н8а9д8о8б9л8ю8с8ь.
.по3на
.пона1д
.пона2д1о2б1л
.пона1до
.понадо1бл2ю
.понадо2б3люсь1
.п8о9т8р8а8ф8ь9т8е.
.пот2р
.по1т
.потра1ф
.потрафь1
.потрафь1т2е
.п8р8е8ж9д8е.
.пре3ж2д
.пре1ж
.преж1де
.п8р8и8д9т8и.
.при1
.при3д2
.при2д1т
.п8р8и9ш8л8а.
.п1ри1ш
.п8р8и9ш8л8о8с8ь.
.приш1ло
.пришлось1
.п8р8о9т8р8у.
.про1т
.п8р8о9х8л8а8д9ц8а.
.про1хл
.прохла1д
.прохлад2ц
.прохладца1
.п8с8к8о9в8а.
.п1с2
.пск2о
.пс1ко1в
.пс3ко1ва
.п8ы8л9ч8е.
.пы1
.пыл1ч
.пыл1че
.р8а8з9о8р8е8м9с8я.
.ра1з2о3ре
.разоре1м
.разоре2м1с
.разорем1ся
.р8а8з9о8р8е9т8е8с8ь.
.разоре1т2е
.разоретесь1
.р8а8з9о8р8е8т9с8я.
.разоре4т1с2
.разорет1ся
.р8а8з9о8р8е8ш8ь9с8я.
.разоре1ш
.разоре2шь1
.разорешь1ся
.р8а8з8о9т8р8у.
.разо1т
.р8а9з8у9м8о8м.
.ра1зу
.раз1у2мо
.разу1м
.разу1мо3м2
.р8е8з8в9л8ю8с8ь.
.резв1л
.рез2вл2ю
.резв1люсь1
.р8с8ф8с8р.
.р1с
.рс1ф
.рс2ф3с
.рсф1ср
.с8а8н9у8з8е8л.
.са2н1уз
.са1ну
.сану1зе
.с8д8р8е8й8ф9л8ю.
.сд2
.сд1ре
.сдре2й1
.сдрей1ф
.сдрейфл2ю
.с8е9г8о9д8н8я.
.се1г
.сего2д1н
.сегод1ня
.с8м8е9ж8а8т.
.с2м
.сме2ж1ат
.сме1ж
.с8о9б8л8ю9с8т8и.
.со1бл2ю
.соб1л
.соблю1сти
.с8о9л8ж8е8ш8ь.
.сол1ж
.солже1ш
.солже2шь1
.с8о8с8т9р8и8м.
.со1ст
.сост1ри1м
.с8о8с8т9р8и8ш8ь.
.сост1ри1ш
.состри2шь1
.с8о8с8т9р8ю.
.состр2ю
.с8о8с8т9р8я8т.
.сост1рят
.с8о9т8к8а8т8ь.
.со1т
.со2т1к2
.сот1кат
.сотка2ть1
.с8о9т8к8е8м.
.сот1ке
.сотке1м
.с8о9т8к8е8ш8ь.
.сотке1ш
.сотке2шь1
.с8о8т8к8у.
.сотк2у
.с8о9т8к8у8т.
.сот1кут
.с8р8о8с9л8а8с8ь.
.с1ро1с2л
.срослась1
.с8р8о8с9л8и8с8ь.
.срос1ли
.срослись1
.с8т8р8е8м9г8л8а8в.
.стре1м
.стре2м1г2
.стре3м2гл
.стремг2лав
.т8а8к9ж8е.
.та1к
.так1ж
.т8в8е9р8е9з8о9г8о.
.тве1ре
.тве1ре1зо
.тверез1о2г2
.т8е9л8е9а8т8е9л8ь8е.
.те1ле
.теле1а
.телеа1т2е
.телеате3ль2е
.телеатель1
.т8е8р9н8о9с8л8и9в8о8м.
.те2р1н
.терно3с2л
.тер1но
.терно2с1ли
.тернос3л2и1во
.терносливо3м2
.т8р8о8п9л8ю.
.тро3пл
.тро2пл2ю
.тро1п
.т8ь8ф8у.
.ть1
.ть1ф
.у8з8у9ф8р8у8к8т.
.у1зу
.узу2фр
.узу1ф
.узуф1ру
.узуфру2к1т
.у8м8н8е8м.
.у2м1н
.ум1не
.умне1м
.у8м8н8е8т.
.у8м8н8е8т8е.
.умне1т2е
.у8м8н8у.
.ум1ну
.у8м8р8у.
.у8с8л8ы8ш8ь9т8е.
.ус2л
.услы1
.услы3шь1
.услы1ш
.услышь1т2е
.у8ш8л8а.
.у2шл
.уш1ла
.у1ш
.ф8о9т8о9п8л8е9н8о8к.
.фо1т
.фото3п
.фото2п1лен
.фотопле1но
.фотоплено1к
.ц8а9р8е9д8в8о9р8е8ц.
.ца1
.ца1ре
.царе1дв2
.царед1во
.царедво1ре
.царедворе1ц
.ч8е9р8е8с9ч8у8р.
.че1ре
.чере1сч2
.черес1чу
.ч8е8р9н8о9с8л8и9в8о8м.
.че2р1н
.черно3с2л
.чер1но
.черно2с1ли
.чернос3л2и1во
.черносливо3м2
.ч8р8е8с8л.
.чр2е1сл
.ч8у8ж9д8о8с8т8ь.
.чу1ж
.чу2ж1д
.чуж1до3
.чуждо1с
.чуждо1ст
.чуждос4ть1
.ш8е8с8т8ь9д8е9с8я8т.
.ше1с2
.ше1ст
.шес4ть1
.шесть1де
.шестьде1ся
.ю8с8о8м.
.ю1со
.юсо3м2
.я8д8о9з8у9б8е.
.я1до
.ядо1з
.ядо1зу
.ядо2з1у2бе
.я8р8е8м9н8о9г8о.
.яре3м1но
.яре1м
.яре2м1н
.яремно1г2
PK
!<(K))hyphenation/hyph_sh.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
t2j
t2l
t2l2j
t2r
t2v
d2j
d2l
d2l2j
d2r
d2v
g2j
g2l
g2l2j
g2r
g2v
h2j
h2l
h2l2j
h2r
h2v
k2j
k2l
k2l2j
k2r
k2v
2d1b
2d1c
2d1d
2d1f
2d1g
2d1h
2d1k
2d1m
2d1n
2d1p
2d1s
2d1t
2d1n2j
2d1d2ž
2d1z
2d1š
2d1đ
2d1ć
2d1č
2g1ž
2g1b
2g1c
2g1d
2g1f
2g1g
2g1h
2g1k
2g1m
2g1n
2g1p
2g1s
2g1t
2g1n2j
2g1d2ž
2g1z
2g1š
2g1đ
2g1ć
2g1č
2h1ž
2h1b
2h1c
2h1d
2h1f
2h1g
2h1h
2h1k
2h1m
2h1n
2h1p
2h1s
2h1t
2h1n2j
2h1d2ž
2h1z
2h1š
2h1đ
2h1ć
2h1č
2k1ž
2k1b
2k1c
2k1d
2k1f
2k1g
2k1h
2k1k
2k1m
2k1n
2k1p
2k1s
2k1t
2k1n2j
2k1d2ž
2k1z
2k1š
2k1đ
2k1ć
2k1č
2t1ž
2t1b
2t1c
2t1d
2t1f
2t1g
2t1h
2t1k
2t1m
2t1n
2t1p
2t1s
2t1t
2t1n2j
2t1d2ž
2t1z
2t1š
2t1đ
2t1ć
2t1č
2dj.
2dl.
2dlj.
2dr.
2dv.
2gj.
2gl.
2glj.
2gr.
2gv.
2hj.
2hl.
2hlj.
2hr.
2hv.
2kj.
2kl.
2klj.
2kr.
2kv.
2tj.
2tl.
2tlj.
2tr.
2tv.
p2j
p2l
p2l2j
p2r
v2j
v2l
v2l2j
v2r
b2j
b2l
b2l2j
b2r
f2j
f2l
f2l2j
f2r
m2j
m2l
m2l2j
m2r
2b1ž
2b1b
2b1c
2b1d
2b1f
2b1g
2b1h
2b1k
2b1m
2b1n
2b1p
2b1s
2b1t
2b1v
2b1n2j
2b1d2ž
2b1z
2b1š
2b1đ
2b1ć
2b1č
2f1ž
2f1b
2f1c
2f1d
2f1f
2f1g
2f1h
2f1k
2f1m
2f1n
2f1p
2f1s
2f1t
2f1v
2f1n2j
2f1d2ž
2f1z
2f1š
2f1đ
2f1ć
2f1č
2m1ž
2m1b
2m1c
2m1d
2m1f
2m1g
2m1h
2m1k
2m1m
2m1n
2m1p
2m1s
2m1t
2m1v
2m1n2j
2m1d2ž
2m1z
2m1š
2m1đ
2m1ć
2m1č
2p1ž
2p1b
2p1c
2p1d
2p1f
2p1g
2p1h
2p1k
2p1m
2p1n
2p1p
2p1s
2p1t
2p1v
2p1n2j
2p1d2ž
2p1z
2p1š
2p1đ
2p1ć
2p1č
2v1ž
2v1b
2v1c
2v1d
2v1f
2v1g
2v1h
2v1k
2v1m
2v1n
2v1p
2v1s
2v1t
2v1v
2v1n2j
2v1d2ž
2v1z
2v1š
2v1đ
2v1ć
2v1č
2bj.
2bl.
2blj.
2br.
2fj.
2fl.
2flj.
2fr.
2mj.
2ml.
2mlj.
2mr.
2pj.
2pl.
2plj.
2pr.
2vj.
2vl.
2vlj.
2vr.
s2c
s2j
s2k
s2l
s2m
s2n
s2p
s2l2j
s2r
s2t
s2v
s2n2j
2s1ž
2s1b
2s1d
2s1f
2s1g
2s1h
2s1s
2s1d2ž
2s1z
2s1š
2s1đ
2s1ć
2s1č
2sj.
2sk.
2sl.
2sm.
2sn.
2sp.
2slj.
2sr.
2st.
2sv.
2snj.
2sc.
z2b
z2d
z2g
z2j
z2l
z2m
z2n
z2l2j
z2r
z2v
z2n2j
2z1ž
2z1c
2z1f
2z1h
2z1k
2z1p
2z1s
2z1t
2z1d2ž
2z1z
2z1š
2z1đ
2z1ć
2z1č
2zj.
2zl.
2zm.
2zn.
2zlj.
2zr.
2zv.
2znj.
2zb.
2zd.
2zg.
š2c
š2k
š2l
š2m
š2n
š2p
š2l2j
š2t
š2v
š2n2j
š2ć
š2č
2š1ž
2š1b
2š1d
2š1f
2š1g
2š1h
2š1s
2š1d2ž
2š1z
2š1š
2š1đ
2š1j
2š1r
2šk.
2šl.
2šm.
2šn.
2šp.
2šlj.
2št.
2šv.
2šnj.
2šć.
2šč.
2šc.
ž2b
ž2d
ž2g
ž2l
ž2m
ž2n
ž2l2j
ž2v
ž2n2j
ž2đ
2ž1ž
2ž1c
2ž1f
2ž1h
2ž1k
2ž1p
2ž1s
2ž1t
2ž1d2ž
2ž1z
2ž1š
2ž1ć
2ž1č
2ž1j
2ž1r
2žl.
2žm.
2žn.
2žlj.
2žv.
2žnj.
2žđ.
2žb.
2žd.
2žg.
c2j
c2r
c2v
2c1ž
2c1b
2c1c
2c1d
2c1f
2c1g
2c1h
2c1k
2c1l
2c1m
2c1n
2c1p
2c1l2j
2c1s
2c1t
2c1n2j
2c1d2ž
2c1z
2c1š
2c1đ
2c1ć
2c1č
2cj.
2cr.
2cv.
č2v
2č1ž
2č1b
2č1c
2č1d
2č1f
2č1g
2č1h
2č1j
2č1k
2č1l
2č1m
2č1n
2č1p
2č1l2j
2č1r
2č1s
2č1t
2č1n2j
2č1d2ž
2č1z
2č1š
2č1đ
2č1ć
2č1č
2čv.
2j1ž
2j1b
2j1c
2j1d
2j1f
2j1g
2j1h
2j1j
2j1k
2j1l
2j1m
2j1n
2j1p
2j1l2j
2j1r
2j1s
2j1t
2j1v
2j1n2j
2j1d2ž
2j1z
2j1š
2j1đ
2j1ć
2j1č
2l1ž
2l1b
2l1c
2l1d
2l1f
2l1g
2l1h
2l1k
2l1l
2l1m
2l1n
2l1p
2l1l2j
2l1r
2l1s
2l1t
2l1v
2l1n2j
2l1d2ž
2l1z
2l1š
2l1đ
2l1ć
2l1č
2n1ž
2n1b
2n1c
2n1d
2n1f
2n1g
2n1h
2n1k
2n1l
2n1m
2n1n
2n1p
2n1l2j
2n1r
2n1s
2n1t
2n1v
2n1n2j
2n1d2ž
2n1z
2n1š
2n1đ
2n1ć
2n1č
l2j
2l2j1ž
2l2j1b
2l2j1c
2l2j1d
2l2j1f
2l2j1g
2l2j1h
2l2j1j
2l2j1k
2l2j1l
2l2j1m
2l2j1n
2l2j1p
2l2j1l2j
2l2j1r
2l2j1s
2l2j1t
2l2j1v
2l2j1n2j
2l2j1d2ž
2l2j1z
2l2j1š
2l2j1đ
2l2j1ć
2l2j1č
2r1ž
2r1b
2r1c
2r1d
2r1f
2r1g
2r1h
2r1j
2r1k
2r1l
2r1m
2r1n
2r1p
2r1l2j
2r1r
2r1s
2r1t
2r1v
2r1n2j
2r1d2ž
2r1z
2r1š
2r1đ
2r1ć
2r1č
n2j
2n2j1ž
2n2j1b
2n2j1c
2n2j1d
2n2j1f
2n2j1g
2n2j1h
2n2j1j
2n2j1k
2n2j1l
2n2j1m
2n2j1n
2n2j1p
2n2j1l2j
2n2j1r
2n2j1s
2n2j1t
2n2j1v
2n2j1n2j
2n2j1d2ž
2n2j1z
2n2j1š
2n2j1đ
2n2j1ć
2n2j1č
d2ž
2d2ž1ž
2dž2b
2d2ž1c
2dž2d
2d2ž1f
2dž2g
2d2ž1h
2d2ž1j
2d2ž1k
2dž2l
2dž2m
2dž2n
2d2ž1p
2dž2l2j
2d2ž1r
2d2ž1s
2d2ž1t
2dž2v
2dž2n2j
2d2ž1d2ž
2d2ž1z
2d2ž1š
2dž2đ
2d2ž1ć
2d2ž1č
2đ1ž
2đ1b
2đ1c
2đ1d
2đ1f
2đ1g
2đ1h
2đ1j
2đ1k
2đ1l
2đ1m
2đ1n
2đ1p
2đ1l2j
2đ1r
2đ1s
2đ1t
2đ1v
2đ1n2j
2đ1d2ž
2đ1z
2đ1š
2đ1đ
2đ1ć
2đ1č
2ć1ž
2ć1b
2ć1c
2ć1d
2ć1f
2ć1g
2ć1h
2ć1j
2ć1k
2ć1l
2ć1m
2ć1n
2ć1p
2ć1l2j
2ć1r
2ć1s
2ć1t
2ć1v
2ć1n2j
2ć1d2ž
2ć1z
2ć1š
2ć1đ
2ć1ć
2ć1č
.h2
.j2
.k2
.l2
.m2
.n2
.p2
.l2j2
.r2
.s2
.t2
.v2
.n2j2
.d2
.d2ž2
.z2
.š2
.đ2
.ć2
.č2
.ž2
.b2
.c2
.f2
.g2
2o1
o3a1
o3e1
o3i1
2o3o1
o3u1
2u1
u3a1
u3e1
u3i1
u3o1
2u3u1
2a1
2a3a1
a3e1
a3i1
a3o1
a3u1
2e1
e3a1
2e3e1
e3i1
e3o1
e3u1
2i1
i3a1
i3e1
2i3i1
i3o1
i3u1
2s2k1b
2s2k1c
2s2k1d
2s2k1f
2s2k1g
2s2k1h
2s2k1k
2s2k1m
2s2k1n
2s2k1p
2s2k1s
2s2k1t
2s2k1n2j
2s2k1d2ž
2s2k1z
2s2k1š
2s2k1đ
2s2k1ć
2s2k1č
2s2k1ž
2s2t1b
2s2t1c
2s2t1d
2s2t1f
2s2t1g
2s2t1h
2s2t1k
2s2t1m
2s2t1n
2s2t1p
2s2t1s
2s2t1t
2s2t1n2j
2s2t1d2ž
2s2t1z
2s2t1š
2s2t1đ
2s2t1ć
2s2t1č
2s2t1ž
2š2k1b
2š2k1c
2š2k1d
2š2k1f
2š2k1g
2š2k1h
2š2k1k
2š2k1m
2š2k1n
2š2k1p
2š2k1s
2š2k1t
2š2k1n2j
2š2k1d2ž
2š2k1z
2š2k1š
2š2k1đ
2š2k1ć
2š2k1č
2š2k1ž
2š2t1b
2š2t1c
2š2t1d
2š2t1f
2š2t1g
2š2t1h
2š2t1k
2š2t1m
2š2t1n
2š2t1p
2š2t1s
2š2t1t
2š2t1n2j
2š2t1d2ž
2š2t1z
2š2t1š
2š2t1đ
2š2t1ć
2š2t1č
2š2t1ž
2s2p1b
2s2p1c
2s2p1d
2s2p1f
2s2p1g
2s2p1h
2s2p1k
2s2p1m
2s2p1n
2s2p1p
2s2p1s
2s2p1t
2s2p1v
2s2p1n2j
2s2p1d2ž
2s2p1z
2s2p1š
2s2p1đ
2s2p1ć
2s2p1č
2s2p1ž
2s2v1b
2s2v1c
2s2v1d
2s2v1f
2s2v1g
2s2v1h
2s2v1k
2s2v1m
2s2v1n
2s2v1p
2s2v1s
2s2v1t
2s2v1v
2s2v1n2j
2s2v1d2ž
2s2v1z
2s2v1š
2s2v1đ
2s2v1ć
2s2v1č
2s2v1ž
2š2p1b
2š2p1c
2š2p1d
2š2p1f
2š2p1g
2š2p1h
2š2p1k
2š2p1m
2š2p1n
2š2p1p
2š2p1s
2š2p1t
2š2p1v
2š2p1n2j
2š2p1d2ž
2š2p1z
2š2p1š
2š2p1đ
2š2p1ć
2š2p1č
2š2p1ž
2š2v1b
2š2v1c
2š2v1d
2š2v1f
2š2v1g
2š2v1h
2š2v1k
2š2v1m
2š2v1n
2š2v1p
2š2v1s
2š2v1t
2š2v1v
2š2v1n2j
2š2v1d2ž
2š2v1z
2š2v1š
2š2v1đ
2š2v1ć
2š2v1č
2š2v1ž
2ž2d1b
2ž2d1c
2ž2d1d
2ž2d1f
2ž2d1g
2ž2d1h
2ž2d1k
2ž2d1m
2ž2d1n
2ž2d1p
2ž2d1s
2ž2d1t
2ž2d1n2j
2ž2d1d2ž
2ž2d1z
2ž2d1š
2ž2d1đ
2ž2d1ć
2ž2d1č
2ž2g1b
2ž2g1c
2ž2g1d
2ž2g1f
2ž2g1g
2ž2g1h
2ž2g1k
2ž2g1m
2ž2g1n
2ž2g1p
2ž2g1s
2ž2g1t
2ž2g1n2j
2ž2g1d2ž
2ž2g1z
2ž2g1š
2ž2g1đ
2ž2g1ć
2ž2g1č
2ž2g1ž
2z2d1b
2z2d1c
2z2d1d
2z2d1f
2z2d1g
2z2d1h
2z2d1k
2z2d1m
2z2d1n
2z2d1p
2z2d1s
2z2d1t
2z2d1n2j
2z2d1d2ž
2z2d1z
2z2d1š
2z2d1đ
2z2d1ć
2z2d1č
2z2g1b
2z2g1c
2z2g1d
2z2g1f
2z2g1g
2z2g1h
2z2g1k
2z2g1m
2z2g1n
2z2g1p
2z2g1s
2z2g1t
2z2g1n2j
2z2g1d2ž
2z2g1z
2z2g1š
2z2g1đ
2z2g1ć
2z2g1č
2z2g1ž
2ž2v1b
2ž2v1c
2ž2v1d
2ž2v1f
2ž2v1g
2ž2v1h
2ž2v1k
2ž2v1m
2ž2v1n
2ž2v1p
2ž2v1s
2ž2v1t
2ž2v1v
2ž2v1n2j
2ž2v1d2ž
2ž2v1z
2ž2v1š
2ž2v1đ
2ž2v1ć
2ž2v1č
2ž2v1ž
2ž2b1b
2ž2b1c
2ž2b1d
2ž2b1f
2ž2b1g
2ž2b1h
2ž2b1k
2ž2b1m
2ž2b1n
2ž2b1p
2ž2b1s
2ž2b1t
2ž2b1v
2ž2b1n2j
2ž2b1d2ž
2ž2b1z
2ž2b1š
2ž2b1đ
2ž2b1ć
2ž2b1č
2ž2b1ž
2z2v1b
2z2v1c
2z2v1d
2z2v1f
2z2v1g
2z2v1h
2z2v1k
2z2v1m
2z2v1n
2z2v1p
2z2v1s
2z2v1t
2z2v1v
2z2v1n2j
2z2v1d2ž
2z2v1z
2z2v1š
2z2v1đ
2z2v1ć
2z2v1č
2z2v1ž
2z2b1b
2z2b1c
2z2b1d
2z2b1f
2z2b1g
2z2b1h
2z2b1k
2z2b1m
2z2b1n
2z2b1p
2z2b1s
2z2b1t
2z2b1v
2z2b1n2j
2z2b1d2ž
2z2b1z
2z2b1š
2z2b1đ
2z2b1ć
2z2b1č
2z2b1ž
2ž2m1b
2ž2m1c
2ž2m1d
2ž2m1f
2ž2m1g
2ž2m1h
2ž2m1k
2ž2m1m
2ž2m1n
2ž2m1p
2ž2m1s
2ž2m1t
2ž2m1v
2ž2m1n2j
2ž2m1d2ž
2ž2m1z
2ž2m1š
2ž2m1đ
2ž2m1ć
2ž2m1č
2ž2m1ž
2s2m1b
2s2m1c
2s2m1d
2s2m1f
2s2m1g
2s2m1h
2s2m1k
2s2m1m
2s2m1n
2s2m1p
2s2m1s
2s2m1t
2s2m1v
2s2m1n2j
2s2m1d2ž
2s2m1z
2s2m1š
2s2m1đ
2s2m1ć
2s2m1č
2s2m1ž
2z2m1b
2z2m1c
2z2m1d
2z2m1f
2z2m1g
2z2m1h
2z2m1k
2z2m1m
2z2m1n
2z2m1p
2z2m1s
2z2m1t
2z2m1v
2z2m1n2j
2z2m1d2ž
2z2m1z
2z2m1š
2z2m1đ
2z2m1ć
2z2m1č
2z2m1ž
2š2m1b
2š2m1c
2š2m1d
2š2m1f
2š2m1g
2š2m1h
2š2m1k
2š2m1m
2š2m1n
2š2m1p
2š2m1s
2š2m1t
2š2m1v
2š2m1n2j
2š2m1d2ž
2š2m1z
2š2m1š
2š2m1đ
2š2m1ć
2š2m1č
2š2m1ž
2s2c1b
2s2c1c
2s2c1d
2s2c1f
2s2c1g
2s2c1h
2s2c1k
2s2c1l
2s2c1m
2s2c1n
2s2c1p
2s2c1l2j
2sc2r
2s2c1s
2s2c1t
2s2c1n2j
2s2c1d2ž
2s2c1z
2s2c1š
2s2c1đ
2s2c1ć
2s2c1č
2s2c1ž
2š2c1b
2š2c1c
2š2c1d
2š2c1f
2š2c1g
2š2c1h
2š2c1k
2š2c1l
2š2c1m
2š2c1n
2š2c1p
2š2c1l2j
2šc2r
2š2c1s
2š2c1t
2š2c1n2j
2š2c1d2ž
2š2c1z
2š2c1š
2š2c1đ
2š2c1ć
2š2c1č
2š2c1ž
2š2č1b
2š2č1c
2š2č1d
2š2č1f
2š2č1g
2š2č1h
2š2č1j
2š2č1k
2š2č1l
2š2č1m
2š2č1n
2š2č1p
2š2č1l2j
2š2č1r
2š2č1s
2š2č1t
2š2č1n2j
2š2č1d2ž
2š2č1z
2š2č1š
2š2č1đ
2š2č1ć
2š2č1č
2š2č1ž
2h2v1b
2h2v1c
2h2v1d
2h2v1f
2h2v1g
2h2v1h
2h2v1k
2h2v1m
2h2v1n
2h2v1p
2h2v1s
2h2v1t
2h2v1n2j
2h2v1d2ž
2h2v1z
2h2v1š
2h2v1đ
2h2v1ć
2h2v1č
2h2v1ž
2ž3v2l
2ž3v2l2j
2c3v2l
2c3v2l2j
2z3v2l
2z3v2l2j
2š3v2l
2š3v2l2j
2č3v2l
2č3v2l2j
2č3v2j
2s3v2l2j
sv2l
2d3v2l
2d3v2l2j
2d3v2r
2k3v2j
2k3v2l
2k3v2l2j
2t3v2j
2t3v2l
2t3v2l2j
2g3v2j
2g3v2l
2g3v2l2j
2g3v2r
2h3v2j
2h3v2l
2h3v2l2j
2h3v2r
2ž3m2j
2ž3m2l
2ž3m2l2j
2ž3m2r
2z3m2l
2z3m2r
2š3m2j
2š3m2l
2š3m2l2j
2š3c2j
2š3c2v
2š3č2v
2š3t2j
2š3t2l
2š3t2l2j
2s3t2l
2s3k2j
2s3k2l2j
sk2l
2š3p2j
2š3p2l
2š3p2l2j
2ž3d2j
2ž3d2l
2ž3d2l2j
2ž3d2v
2ž3g2j
2ž3g2l
2ž3g2l2j
2ž3g2r
2ž3g2v
2z3d2l
2z3d2l2j
2z3d2v
2z3g2j
2z3g2l2j
zg2l
2ž3b2j
2ž3b2l
2ž3b2l2j
2ž3b2r
2z3b2l2j
zb2l
.a4e2r2o1
.a1
.a3e1
.be4o1
.b2e1
.bi4o1
.b2i1
.ge4o1
.g2e1
.za3g2n
.z2a1
.z2a3t2k2a1
.za2t1k
.iza3g2n
.i1
.iz2a1
.iza3t2k
.i2z3g
.i2z3g2n
.i2z3d
.izd2n2o1
.i2z2d1n
.izd2n2u1
.izd2n2a1
.i2z3r
.iz2r2k
.i2s3t
.i2s2t2k
.na2g2n
.n2a1
.na2g2n2j
.na3d2nev
.na2d1n
.nadn2e1
.na3d2nič
.nadn2i1
.na3d2nic
.na3t2kah
.na2t1k
.n2a1tk2a1
.na3t2kam
.na3t2kas2m
.na3t2kas2t
.oda3g2n
.o1
.od2a1
.oda3d2n
.od3g2n
.o2d1g
.od3m2n
.o2d1m
.o3t2kah
.o2t1k
.otk2a1
.o3t2kam
.o3t2kas2m
.o3t2kas2t
.po3g2n
.p2o1
.po3d2nev
.po2d1n
.podn2e1
.po3m2n
.po3m2n2j
.po3r2v
.po3r2đ
.po3t2kah
.po2t1k
.potk2a1
.po3t2kam
.po3t2kat
.po3t2kav
.pred3m2n
.p2r
.pr2e1
.pre2d1m
.pred3m2n2j
.pre3t2kah
.pre2t1k
.pretk2a1
.pre3t2kam
.pre3t2kat
.pro3g2n
.pr2o1
.pro3t2k2i1
.pro2t1k
.pro3t2k2a1
.raza3g2n
.r2a1
.r2a1z2a1
.ra2z3g
.ra2z3g2n
.ra2z3d
.raz3d2n2i1
.ra2z2d1n
.r2a1z2a3t2k2a1
.raza2t1k
.u3g2m2i1
.u1
.u2g1m
.u3g2n
.uz2a3t2k2a1
.uz2a1
.uza2t1k
3h2tet2i1
ht2e1
3h2tjet2i1
ht2j
htj2e1
3h2tel
3h2tev
3h2ten2j
3h2tjel
3h2tjev
3h2tjen2j
3g2degod.
gd2e1
gdeg2o1
3g2djegod.
gd2j
gdj2e1
gdjeg2o1
3g2dekak
gdek2a1
3g2dekad
3g2djekak
gdjek2a1
3g2djekad
ni3g2de.
n2i1
ni2g1d
nigd2e1
ne3g2de.
n2e1
ne2g1d
negd2e1
ni3g2dje.
nigd2j
nigdj2e1
ne3g2dje.
negd2j
negdj2e1
3b2det
bd2e1
3b2den2j
3b2djet
bd2j
bdj2e1
3b2djen2j
3g2mil
gm2i1
3g2mil2j
3g2miz
3g2nos
gn2o1
3g2noz
3g2noj
3g2naj
gn2a1
3g2nez2d
gn2e1
3g2nijez2d
gn2i1
gnij2e1
3g2než2đ
3g2nijež2đ
3g2nev
3g2njev
gnj2e1
3g2njav
gnj2a1
3g2njes
3g2njet
3g2nječ
3g2njil
gnj2i1
3g2nji3o1
3g2njil2j
3g2njit
3g2njur
gnj2u1
3k2nez
kn2e1
3k2než
3k2njiž
knj2i1
3k2njig
3m2nož
mn2o1
3m2nog
3m2naž
mn2a1
3p2sik
ps2i1
3p2sič
3p2sov
ps2o1
3p2suj
ps2u1
3r2đ2a1
3s2fer
sf2e1
3t2mas2t
tm2a1
3t2mul
tm2u1
3t2mu3o1
3t2mul2j
3t2mur
3c2miz
cm2i1
3c2mak
cm2a1
3c2mač
3c2mok
cm2o1
3č2lan
čl2a1
3č2lan2j
3r2j2e1
4r3jem
4r3je.
.be2z3j
.be2z3l
.be2z3m
.be2z3n
.be2z3l2j
.be2z3r
.be2z3v
.be2z3n2j
.be2z3b
.be2z3d
.be2z3g
.be2z3i1
.be2z3o1
.be2z3u1
.be2z3alkohol
.bez2a1
.beza2l1k
.bezalk2o1
.bezalkoh2o1
.be2z3atoms2k
.bezat2o1
.bezato2m1s
.be3z4be2d1n
.b2e1zb2e1
.be3z4bed2a1
.be3z4bje2d1n
.bezb2j
.b2e1zbj2e1
.be3z4bjed2a1
.be3z4bel2i1
.be3z4bol
.bezb2o1
.be3z4vu2č1n
.bezv2u1
.be3z4vuč2a1
.be3z4istan
.bezis2t
.bezist2a1
.be3z4isten
.bezist2e1
.be3z4jak
.bezj2a1
.be3z4jač
.be3z4lo2b1n
.bezl2o1
.be3z4lob2a1
.be3z4načaj
.bezn2a1
.beznač2a1
.be3z4ra2č1n
.bezr2a1
.be3z4r2a1č2a1
.be3z4up
.be3z4ub
.be2s3c
.be2s3k
.be2s3p
.be2s3t
.be3s4krupul
.besk2r
.beskr2u1
.beskrup2u1
.be3s4poko2j1n
.besp2o1
.besp2o1k2o1
.be3s4pokoj2a1
.be3s4po2r1n
.be3s4por2a1
.be3s4tvar
.best2v
.bestv2a1
.be3s4tid
.best2i1
.be3s4tij2a1
.be3s4til2u1
.be3s4til2j
.be3s4tr2a1n2a1
.best2r
.bestr2a1
.be3s4tras
.bes4tseler
.be2s2t1s
.bests2e1
.bestsel2e1
.be2š3ć
.be2š3č
.va2n3ev2r
.v2a1
.van2e1
.va2n3ustav
.van2u1
.vanus2t
.vanust2a1
.i2z3b
.i2z3j
.i2z3l
.i2z3m
.i2z3n
.i2z3l2j
.i2z3v
.i2z3n2j
.i2z3i1
.i2z3o1
.i2z3u1
.i2z3bij2a1
.i1zb2i1
.i2z3biv2a1
.i2z3ved2i1
.izv2e1
.i2z3ve2d1n
.i2z3ve2d1b
.i2z3v2e1d2e1
.i2z3daj
.izd2a1
.i2z3a1b2a1
.i2z3a1k2a1
.i2z3anal
.izan2a1
.i3z4bav
.izb2a1
.i3z4bičk2a1v2a1
.izbi2č1k
.izbičk2a1
.i3z4bleušan
.izb2l
.izbl2e1
.izble3u1
.izbleuš2a1
.i3z4bojak
.izb2o1
.izboj2a1
.i3z4bo2j1k
.i3z4val2i1
.izv2a1
.i3z4val2u1
.i3z4v2a1l2a1
.i3z4val2e1
.i3z4valj2i1
.izval2j
.i3z4viž2d
.i1zv2i1
.i3z4viisk2r
.i1zv2i3i1
.izviis2k
.i3z4vij2a1
.i3z4vijen
.izvij2e1
.i3z4vin
.i3z4vir
.i3z4vin2j
.i3z4vitop
.izvit2o1
.i3z4vjed
.izv2j
.izvj2e1
.i3z4vojac
.izv2o1
.izvoj2a1
.i3z4vo2j1c
.i3z4vor
.i3z4gomet
.izg2o1
.izgom2e1
.i3z4gred
.izg2r
.izgr2e1
.i3z4g2r1n
.i3z4g2r1t
.i3z4drav
.izd2r
.izdr2a1
.i3z4iđ
.i3z4id
.i3z4i1m2i1
.i3z4jež2l2j
.izj2e1
.izjež2l
.i3z4loz
.izl2o1
.i3z4lož
.i3z4log
.i3z4lopać
.izlop2a1
.i3z4nim
.izn2i1
.i3z4noj
.izn2o1
.iz4oanem
.izo3a1
.izoan2e1
.iz4oanom
.izoan2o1
.iz4obat
.izob2a1
.iz4obron
.izob2r
.izobr2o1
.iz4ogam
.izog2a1
.iz4o1ge3o1
.izog2e1
.iz4oglos
.izog2l
.izogl2o1
.iz4ogon
.izog2o1
.iz4ograf
.izog2r
.izogr2a1
.iz4odim
.i1zod2i1
.iz4odin
.iz4odoz
.izod2o1
.iz4oklin
.izok2l
.izokl2i1
.iz4okolon
.izok2o1
.izokol2o1
.i3z4olat
.izol2a1
.i3z4olac
.i3z4olir
.izol2i1
.i3z4olov
.izol2o1
.iz4ole2k1s
.izol2e1
.iz4olu2k1s
.izol2u1
.iz4omer
.izom2e1
.iz4omet2r
.iz4omo2r1f
.izom2o1
.iz4onef
.izon2e1
.iz4onom
.izon2o1
.iz4opat
.izop2a1
.iz4oper
.izop2e1
.iz4opl2e1
.izop2l
.iz4opol
.izop2o1
.iz4opsef
.izo2p1s
.izops2e1
.iz4orah
.izor2a1
.i1z4ose3i1
.izos2e1
.iz4osi2n1t
.i1zos2i1
.iz4osis2t
.iz4oskel
.izos2k
.izosk2e1
.iz4oskop
.izosk2o1
.iz4ostaz
.izos2t
.izost2a1
.iz4ost2e1
.iz4otah
.izot2a1
.iz4otal
.iz4oter
.izot2e1
.iz4oton
.iz2o1t2o1
.iz4otop
.iz4o1tr2o1
.izot2r
.iz4ofon
.iz2o1f2o1
.iz4ofot
.iz4ohal
.izoh2a1
.iz4ohaz
.iz4ohel
.izoh2e1
.iz4ohij
.i1zoh2i1
.iz4ohim
.iz4ohit
.iz4ohi2p1s
.iz4ohor
.izoh2o1
.iz4o1hr2o1
.izoh2r
.i3z4rael
.izr2a1
.izra3e1
.i3z4rail2j
.i1zra3i1
.i3z4rač2i1
.i3z4un
.i3z4u2p1č
.i2s3c
.i2s3k
.i2s3p
.i3s4kak
.isk2a1
.i3s4kat
.i3s4kan2j
.i3s4kariot
.iskar2i1
.iskari3o1
.i3s4kvas
.isk2v
.iskv2a1
.i3s4kv2r1č
.iskv2r
.i3s4kin
.i1sk2i1
.i3s4kit2a1
.i3s4kons2k
.isk2o1
.isko2n1s
.i3s4koč
.i3s4kram
.isk2r
.iskr2a1
.i3s4krit
.i1skr2i1
.i3s4kriš
.i3s4krič
.i3s4kric
.i3s4krat
.i3s4kren
.iskr2e1
.i3s4kren2j
.i3s4kroj
.iskr2o1
.i3s4krs2n
.isk2r1s
.i3s4krs2a1
.i3s4kuplj2a1
.isk2u1
.iskup2l
.iskup2l2j
.i3s4lam
.is2l
.isl2a1
.i3s4lab
.i3s4leđ
.isl2e1
.i3s4led
.i3s4lijeđ
.i1sl2i1
.islij2e1
.i3s4lijed
.i3s4ljeđ
.is2l2j
.islj2e1
.i3s4ljed
.i3s4lik
.i3s4lin
.i3s4lov
.isl2o1
.i3s4luš
.isl2u1
.i3s4luž
.i3s4m2e1
.is2m
.i3s4mij2e1
.ism2i1
.i3s4mj2e1
.ism2j
.i3s4pav
.isp2a1
.i3s4paljiv
.ispal2j
.ispalj2i1
.i3s4pir2a1
.isp2i1
.i3s4plit
.isp2l
.i1spl2i1
.i3s4plić
.i3s4pokoj
.isp2o1
.ispok2o1
.i3s4polin
.ispol2i1
.i3s4pon
.i3s4porav
.ispor2a1
.i3s4prav2i1
.isp2r
.ispr2a1
.i3s4pra2v1k
.i3s4pra2v1n
.i3s4prav2l2j
.isprav2l
.i3s4pr2a1v2a1
.i3s4pu2p1č
.isp2u1
.i3s4pur
.i3s4red
.is2r
.isr2e1
.i3s4r1k
.i3s4tav2i1
.ist2a1
.i3s4tav2l2j
.istav2l
.i3s4ta2k1n
.i3s4tam
.i3s4tar
.i3s4tas
.i3s4tać
.i3s4tin
.i1st2i1
.i3s4tir
.i3s4tic
.i3s4tifan
.istif2a1
.i3s4tok
.ist2o1
.i3s4tor2i1
.i3s4to2č1n
.i3s4to2č1n2j
.i3s4toč2a1
.i3s4trav
.ist2r
.istr2a1
.i3s4trad
.i3s4tran
.i3s4trić
.i1str2i1
.i3s4triž
.i3s4tric
.i3s4trug
.istr2u1
.i3s4tup
.ist2u1
.i3s4uk
.is2u1
.i3s4us
.i3s4ut
.i3s4uš
.i2ž3đ
.i2š3ć
.i2š3č
.iz3be2z3ob2r
.izb2e1
.izbez2o1
.iz3be2z3um
.izbez2u1
.iz3va2n3ev2r
.izvan2e1
.na2d3l
.na2d3l2j
.na2d3v
.na3d4val
.nadv2a1
.na3d4ves2i1
.nadv2e1
.na3d4ves2t
.na3d4vij
.nadv2i1
.na3d4vit
.n2a3d4vl2a1
.na2d3v2l
.na3d4voj2e1
.nadv2o1
.na3d4vor
.na2d3ig2r
.nad2i1
.na2d3i2n1ž
.n2a2d3in2a1
.na2d3is2k
.na2d3jah
.nad2j
.n2a1dj2a1
.na2d3jač
.na2d3jek
.nadj2e1
.na2d3jez
.na2d3ječ
.na2d3jun
.nadj2u1
.na3d4lan
.nadl2a1
.na3d4leš
.nadl2e1
.na3d4lež
.n2a2d3or2a1
.nad2o1
.na2d3o1s2o1
.na2d3os2e1
.na2d3osj2e1
.nados2j
.na2d3of2i1
.na2d3oč
.na2d3ran
.nad2r
.n2a1dr2a1
.na2d3rač
.na2d3ras2t
.na2d3raš2ć
.na2d3real
.nadr2e1
.n2a1dre3a1
.na2d3rep
.na2d3ruk
.nadr2u1
.na2d3ruč
.na2d3rug
.na2d3udar
.nad2u1
.nadud2a1
.na2d3um
.na2d3uč
.n2a2j3a1
.na2j3e1
.na2j3i1
.na2j3o1
.na2j3u1
.na3j4av2i1
.na3j4av2l2j
.najav2l
.n2a3j4a1v2a1
.na3j4av2e1
.na3j4ad2i1
.n2a3j4a1d2a1
.na3j4ad2e1
.na3j4až2i1
.na3j4az2i1
.na3j4ak2o1
.n2a3j4a1k2a1
.na3j4al2o1
.na3j4am2i1
.na3j4am2l
.na3j4a2m1n
.na3j4ar2i1
.na3j4a2r1m
.na3j4a2r1c
.na3j4at2i1
.na3j4auk
.naja3u1
.na3j4ah
.na3j4aš
.na3j4ed2i1
.na3j4e2d1n
.na3j4ed2r
.n2a3j4ed2a1
.na3j4ež2i1
.na3j4ež2u1
.na3j4e1ž2e1
.na3j4ez2n
.na3j4ez2d
.na3j4est2i1
.najes2t
.na3j4e2t1k
.na3j4ec
.na3j4ur2i1
.na3j4uren
.najur2e1
.o2b3j
.o2b3l2j
.ob2l
.o2b3r
.obe2z3b
.ob2e1
.obe2z3d
.obe2z3g
.obe2z3j
.obe2z3l
.obe2z3m
.obe2z3n
.o1be2z3o1
.obe2z3l2j
.obe2z3r
.obe2z3u1
.obe2z3v
.obe3z4vij
.obezv2i1
.obe3z4nan
.obezn2a1
.obe3z4nan2j
.obe3z4nač
.obe3z4ub
.obe2s3c
.obe2s3k
.obe2s3p
.obe2s3t
.obe3s4tan
.obest2a1
.obe3s4tij
.obest2i1
.obe3s4tran
.obest2r
.obestr2a1
.obe2š3ć
.obe2š3č
.o2b3ig2r
.ob2i1
.o2b3istin
.obis2t
.ob2i1st2i1
.o2b3istin2j
.o3b4jek
.obj2e1
.o3b4jer
.o3b4jes2i1
.o3b4jet
.o3b4ješ
.o2b3laj
.obl2a1
.o2b3lam
.o2b3la2k1š
.o2b3las2k
.o2b3lep
.obl2e1
.o2b3let
.o2b3leć
.o2b3lež
.o2b3leg
.o2b3lijep
.obl2i1
.oblij2e1
.o2b3lijet
.o2b3lijež
.o2b3lijeg
.o2b3leden
.obled2e1
.o2b3liv
.o2b3lizat
.obliz2a1
.o2b3lizav
.o2b3l2i1z2i1
.o2b3lis2t
.o2b3lok2a1
.obl2o1
.o2b3luk
.obl2u1
.o2b3luč
.o3b4ljan
.oblj2a1
.o3b4ljut
.oblj2u1
.o3b4ljuz
.o2b3or2u1
.ob2o1
.o3b4raž2e1
.obr2a1
.o3b4raz2i1
.o3b4raz2n
.o3b4raz2o1
.o3b4raz2u1
.o3b4r2a1z2a1
.o3b4raz2d
.o3b4ra2m1b
.o3b4ran
.o3b4ran2j
.o3b4rat
.o3b4rać
.o3b4raš2n
.o3b4raš2č
.o3b4r1v
.o3b4r1đ
.o3b4rem
.obr2e1
.o3b4res
.o3b4ređ
.o3b4reč
.o3b4rež
.o3b4rec
.o3b4red
.o3b4ret2i1
.o3b4re2t1n
.o3b4rij
.obr2i1
.o3b4ris
.o3b4rit
.o3b4riv
.o3b4rič
.o3b4ric
.o3b4r1k
.o3b4r1l
.o3b4r1n
.o3b4r1l2j
.o3b4r1s
.o3b4r1t
.o3b4r1š
.o3b4r1č
.o3b4rok
.o1br2o1
.o3b4ron
.o3b4ron2j
.o3b4roć
.o3b4roč
.o3b4rov2a1
.o3b4ro2v1c
.o3b4ruk
.obr2u1
.o3b4run
.o3b4rus
.o3b4run2j
.o3b4ruš
.o3b4ruč
.o2b3ubož
.ob2u1
.obub2o1
.o2b3uz
.o2b3už
.o2b3ud
.o2b3um2i1
.o2b3um2j
.o2b3um2r
.o2b3um2e1
.o2d3j
.o2d3l
.o2d3l2j
.o2d3r
.o2d3v
.o2d3a2r1g
.o3d4vaj
.odv2a1
.o3d4važ
.o3d4ves2n
.odv2e1
.o3d4ves2t
.o3d4ves2a1
.o3d4vikav
.odv2i1
.odvik2a1
.o3d4vi2k1n
.o3d4vis
.o3d4vić
.o3d4voj
.odv2o1
.o2d3ig2r
.od2i1
.o2d3i2z3v
.o2d3i2z3d
.o2d3is2k
.o2d3i1st2i1
.odis2t
.o3d4jel
.odj2e1
.o3d4jen
.o3d4jev
.o3d4jeć
.o3d4laz
.odl2a1
.o3d4laž
.o3d4lag
.o3d4l2a1k2a1
.o3d4luk
.odl2u1
.o3d4luč
.o2d3oz2d
.o1d2o1
.o2d3oz2g
.o2d3ok
.o2d3o2n1l
.o2d3o1n2o1
.o2d3on2u1
.o2d3o2n1d
.o3d4ran2i1
.odr2a1
.o3d4ran2o1
.o3d4ran2u1
.o3d4r2a1n2a1
.o3d4ran2e1
.o3d4raz
.o3d4rać
.o3d4raž
.o3d4rap2i1
.o3d4rap2l2j
.odrap2l
.o3d4r2a1p2a1
.o3d4rač2i1
.o3d4rven
.od2r1v
.odrv2e1
.o3d4rven2j
.o3d4rveč
.o3d4rem
.odr2e1
.o3d4ren
.o3d4ret
.o3d4ređ
.o3d4red
.o3d4r1l
.o3d4r1n
.o3d4r1p
.o3d4r1l2j
.o3d4r1t
.o3d4r1ž
.o3d4rin
.odr2i1
.o3d4rin2j
.o3d4riš
.o3d4rič
.o3d4rib
.o3d4ric
.o3d4ron
.o1dr2o1
.o3d4ron2j
.o3d4ruž
.odr2u1
.o3d4rug
.o2d3uv2i1
.od2u1
.o2d3uv2e1
.o2d3uz2i1
.o2d3uz2l
.o2d3uz2d
.o2d3uz2e1
.o2d3uk
.o2d3ul
.o2d3um
.o2d3uč
.po2d3a2d1m
.pod2a1
.po2d3varij
.pod2v
.podv2a1
.podvar2i1
.po2d3vez
.podv2e1
.po2d3več
.po2d3vež
.po2d3vik
.podv2i1
.po2d3vil
.po2d3vir
.po2d3vin2j
.po2d3vlas
.po2d3v2l
.podvl2a1
.po2d3vlaš
.po2d3voz
.p2o1dv2o1
.po2d3vođ
.po2d3vož
.po2d3vod
.po2d3vrat
.po2d3v2r
.podvr2a1
.po2d3vrać
.po2d3v2r1ć
.po2d3v2r1ž
.po2d3v2r1g
.po2d3vris
.podvr2i1
.po2d3v2r1s
.po2d3vuć
.podv2u1
.po2d3ig2r
.pod2i1
.po2d3iz2v
.po2d3j
.po3d4jen
.podj2e1
.po3d4ječ
.po2d3lakat
.pod2l
.podl2a1
.podlak2a1
.po2d3la2k1t
.po2d3lep
.podl2e1
.po2d3let
.po2d3leć
.po2d3lež
.po2d3leg
.po2d3liz
.podl2i1
.po2d3lijep
.podlij2e1
.po2d3lijet
.po2d3lijeć
.po2d3lijež
.po2d3lijeg
.po2d3lis2t
.po2d3lok
.p2o1dl2o1
.po2d3lom
.po2d3lup
.podl2u1
.po2d3luč
.po2d3luž
.po2d3ljut
.pod2l2j
.podlj2u1
.po2d3o2k1n
.p2o1d2o1
.po2d3oš
.po2d3oč
.po2d3of
.po2d3ra2v1n
.pod2r
.podr2a1
.po2d3ra2v1n2j
.po2d3rad
.po2d3ra2z3d
.po2d3raz2r
.po2d3raz2u1
.po2d3ram
.po2d3ran
.po2d3ras
.po2d3ran2j
.po2d3rep
.podr2e1
.po2d3res
.po2d3rez
.po2d3rik
.podr2i1
.po2d3rit
.po2d3ron
.p2o1dr2o1
.po2d3rov
.po2d3rož
.po2d3ruk
.podr2u1
.po2d3rub
.po2d3ruč2i1
.po2d3ru2č1n
.po2d3ruč2a1
.po2d3upl2a1
.pod2u1
.podup2l
.po2d3us2m
.po2d3us2n
.pre2d3j
.pre2d3v
.pre3d4vaj
.predv2a1
.pre3d4var
.pre3d4ves2t
.predv2e1
.pre3d4voj2i1
.predv2o1
.pre3d4voj2a1
.pr2e3d4voj2e1
.pre3d4vor
.pre3d4vos
.pre3d4jen
.predj2e1
.pre2d3ig2r
.pred2i1
.pre2d3id
.pre2d3iz2b
.pre2d3i1sp2i1
.predis2p
.pre2d3ist2o1
.predis2t
.pre2d3ist2r
.pre2d3ob2j
.pred2o1
.pr2e2d3odr2e1
.predod2r
.pre2d3okus
.predok2u1
.pre2d3os2v
.pr2e2d3os2e1
.pr2e2d3osj2e1
.predos2j
.pre2d3rat
.pred2r
.predr2a1
.pre2d3rač
.pre2d3rad
.pre2d3ruč
.predr2u1
.pre2d3ubeđ
.pred2u1
.predub2e1
.pre2d3ubijeđ
.predub2i1
.predubij2e1
.pre2d3ubjeđ
.predub2j
.predubj2e1
.pre2d3uver
.preduv2e1
.pre2d3uvjer
.preduv2j
.pr2e1duvj2e1
.pre2d3uvjet
.pre2d3ugov
.predug2o1
.pre2d3udar
.predud2a1
.pre2d3upis
.predup2i1
.pre2d3usl2o1
.predus2l
.proti2v3a2k1c
.prot2i1
.protiv2a1
.proti2v3ot2r
.pr2o1tiv2o1
.proti2v3of
.proti2v3r
.proti2v3us
.protiv2u1
.proti2v3ud
.ra2ž3đ
.ra2z3b
.ra2z3e1
.ra2z3i1
.ra2z3j
.ra2z3l
.ra2z3m
.ra2z3n
.ra2z3l2j
.ra2z3r
.ra2z3v
.ra2z3n2j
.ra2z3anal
.razan2a1
.ra3z4ban
.r2a1zb2a1
.ra3z4bar
.ra3z4ba3u1
.ra3z4bad
.ra3z4bašur
.razbaš2u1
.ra3z4boj
.razb2o1
.ra3z4bor
.ra3z4val
.razv2a1
.ra3z4v2e1d2e1
.razv2e1
.ra3z4ves2t
.ra3z4vig2o1
.razv2i1
.ra3z4vij2u1
.r2a3z4vij2a1
.ra3z4vij2e1
.ra3z4vit
.ra3z4vić
.ra3z4voj
.razv2o1
.ra3z4von
.ra3z4vrat
.razv2r
.r2a1zvr2a1
.ra3z4vrać
.ra3z4v2r1t
.ra3z4v2r1ć
.ra3z4gađ
.razg2a1
.ra3z4g2r1t
.razg2r
.ra3z4ev
.ra3z4ij
.ra3z4il
.ra3z4in
.ra3z4ir
.ra3z4it
.ra3z4iz
.ra3z4iđ
.ra3z4ić
.ra3z4id
.ra3z4laz
.r2a1zl2a1
.ra3z4lag
.ra3z4lik
.razl2i1
.ra3z4lič
.ra3z4loz
.razl2o1
.ra3z4lož
.ra3z4log
.ra3z4met
.razm2e1
.ra3z4meć
.ra3z4mrs2k
.ra2z3m2r
.razm2r1s
.ra3z4nat
.r2a1zn2a1
.ra2z3ob2l
.raz2o1
.ra2z3ob2r
.r2a2z3ob2a1
.ra2z3od
.ra2z3orat
.r2a1zor2a1
.ra2z3orav
.ra2z3o2r1t
.ra2z3or2u1
.ra2z3ot
.ra3z4red
.razr2e1
.ra3z4rok
.razr2o1
.ra3z4roč
.ra2z3uv2e1
.raz2u1
.ra2z3ud2i1
.r2a2z3ud2a1
.ra2z3u2d1b
.ra2z3uz2i1
.ra2z3uz2d
.ra2z3uz2e1
.ra2z3ular
.razul2a1
.ra2z3um2r
.ra2s3c
.ra2s3k
.ra2s3p
.ra2s3t
.ra3s4kak
.r2a1sk2a1
.ra3s4ka2n1d
.ra3s4kin
.rask2i1
.ra3s4klap
.rask2l
.r2a1skl2a1
.ra3s4klan2j
.ra3s4klad
.ra3s4klon
.raskl2o1
.ra3s4klop2i1
.ra3s4klop2l2j
.rasklop2l
.r2a3s4klop2a1
.ra3s4koš
.rask2o1
.ra3s4krop
.rask2r
.raskr2o1
.ra3s4paj
.r2a1sp2a1
.ra3s4pav
.ra3s4pet2i1
.rasp2e1
.ra3s4pet2o1
.r2a3s4pet2a1
.ra3s4p2e1t2e1
.ra3s4pik2u1
.rasp2i1
.ra3s4pin2j
.ra3s4plin
.rasp2l
.raspl2i1
.ra3s4plin2j
.ra3s4p1n
.ra3s4polož
.rasp2o1
.raspol2o1
.ra3s4pon
.ra3s4por
.ra3s4prav
.rasp2r
.raspr2a1
.ra3s4prem
.raspr2e1
.ra3s4r1đ
.ras2r
.ra3s4r1d
.ra3s4r2e1
.ra3s4taj
.r2a1st2a1
.ra3s4tan
.ra3s4tat
.ra3s4tav
.ra3s4ten2j
.rast2e1
.ra3s4til
.rast2i1
.ra3s4tir
.ra3s4tis
.ra3s4tit
.ra3s4tin2j
.ra3s4toj
.rast2o1
.ra3s4trel
.rast2r
.rastr2e1
.ra3s4tret
.ra3s4troj
.rastr2o1
.ra3s4t2r1t
.ra3s4tup
.rast2u1
.ra3s4tur
.ra3s4tuć
.ra4s5tu2r1č
.ra2š3ć
.ra2š3č
.ra3š4ćen2j
.rašć2e1
.ra3š4čić
.rašč2i1
.u2z3b
.u2z3d
.u2z3g
.u2z3i1
.u2z3j
.u2z3l
.u2z3m
.u2z3n
.u2z3l2j
.u2z3r
.u2z3v
.u2z3n2j
.u3z4bor
.uzb2o1
.u3z4van
.uzv2a1
.u3z4vat
.u3z4viž
.uzv2i1
.u3z4vij2o1
.u3z4vij2u1
.u3z4vij2a1
.u3z4vij2e1
.u3z4voj
.uzv2o1
.u3z4dic
.uzd2i1
.u2z3ig2r
.u2z3inat
.uzin2a1
.u2z3isk2r
.uzis2k
.u3z4lan
.uzl2a1
.u3z4lat
.u3z4lim
.uzl2i1
.u3z4lit
.u3z4lić
.u3z4lic
.u3z4lov
.uzl2o1
.u3z4ludob
.uzl2u1
.uzlud2o1
.u3z4nak
.uzn2a1
.u3z4nač
.u3z4n2e1v2e1
.uzn2e1
.u3z4n2e1vj2e1
.uznev2j
.u3z4nič
.uzn2i1
.u3z4nic
.u3z4noj
.uzn2o1
.u2z3obes2t
.uz2o1
.uzob2e1
.u2z3obijes2t
.uzob2i1
.uzobij2e1
.u2z3orat
.uzor2a1
.u2z3orav
.u2z3o1h2o1
.u3z4ret
.uzr2e1
.u3z4rev
.u3z4rijet
.uzr2i1
.uzrij2e1
.u3z4rijev
.u3z4r1n
.u3z4r1n2j
.u3z4r2o1k2o1
.uzr2o1
.u3z4rok2u1
.u3z4rok2a1
.u3z4roč
.u3z4ruj
.uzr2u1
.u2z3ugar
.uz2u1
.uzug2a1
.u2s3c
.u2s3k
.u2s3p
.u3s4kak
.usk2a1
.u3s4klađ
.usk2l
.uskl2a1
.u3s4klad
.u3s4k2o1
.u4s5kom
.u4s5kov
.u4s5koš
.u4s5k2o1k2o1
.u4s5kol2u1
.u4s5kol2e1
.u4s5kop2a1
.u4s5kor2a1
.u4s5kos2i1
.u4s5kot2r
.u3s4kup
.u1sk2u1
.u3s4pav
.usp2a1
.u3s4pal2o1
.u3s4peh
.usp2e1
.u3s4pel
.u3s4pem
.u3s4pet
.u3s4pev
.u3s4peš
.u3s4pjeh
.usp2j
.uspj2e1
.u3s4pjel
.u3s4pjem
.u3s4pjet
.u3s4pjev
.u3s4pješ
.u3s4pe2n1t
.u3s4pij2a1
.usp2i1
.u3s4pij2e1
.u3s4pijuš
.uspij2u1
.u3s4pikuš
.uspik2u1
.u3s4pon
.usp2o1
.u3s4por2i1
.u3s4por2a1
.u3s4poren
.uspor2e1
.u3s4poren2j
.u3s4poreč
.u3s4posob
.uspos2o1
.u3s4prem2i1
.usp2r
.uspr2e1
.u3s4prem2a1
.u3s4r1k
.us2r
.u3s4r1n
.u3s4r1p
.u3s4r1l2j
.us2r1l
.u3s4r1t
.u3s4r1đ
.u3s4r1ž
.u3s4r2a1
.u3s4r1d
.u3s4r2e1
.u3s4rijed
.usr2i1
.usrij2e1
.u2s3talas
.us2t
.ust2a1
.ustal2a1
.u2s3t2a1r2a1
.u2s3tv2r1đ
.ust2v
.ustv2r
.u2s3tv2r1d
.u2s3ter
.ust2e1
.u2s3teć
.u2s3teg
.u2s3tov
.ust2o1
.u2s3traj
.ust2r
.ustr2a1
.u2s3tral
.u2s3t2r1g
.u2s3trep
.ustr2e1
.u2s3tres
.u2s3treb
.u2s3t2r1k
.u2s3t2r1n
.u2s3t2r1p
.u2s3t2r1ć
.u2s3t2r1č
.u2s3tum
.u1st2u1
.u2s3tur
.u2s3tuć
.u2š3ć
.u2š3č
.a2b3alij
.a1b2a1
.abal2i1
.a2b3anac
.aban2a1
.a2b3evak
.ab2e1
.abev2a1
.a2b3erac
.aber2a1
.a2b3erir
.aber2i1
.a2b3irit
.ab2i1
.abir2i1
.a2b3j2u1
.ab2j
.a2b3l2a1
.ab2l
.a2b3leg
.abl2e1
.a2b3lep
.a2b3lok
.abl2o1
.a2b3l2u1
.a2b3orig
.ab2o1
.abor2i1
.a2b3reak
.ab2r
.abr2e1
.a1bre3a1
.a2b3rog
.abr2o1
.a2b3uzus
.ab2u1
.abuz2u1
.a2d3erac
.ad2e1
.ader2a1
.a2d3ve2r1b
.ad2v
.adv2e1
.a2d3j
.a2d3lat
.ad2l
.adl2a1
.a2d3ren
.ad2r
.adr2e1
.a2d3rog
.adr2o1
.a3g2nos
.a2g1n
.agn2o1
.a3g2noz
.a2nabap
.a1n2a1
.a1n2a1b2a1
.a2nabaz
.a2nabat
.a2nabi3o1
.anab2i1
.a2nabol
.anab2o1
.a2nagen
.anag2e1
.a2nagn2o1
.ana2g1n
.a2n3ag2o1
.a2n2a1gr2a1
.anag2r
.a2nadem
.anad2e1
.a2nadip2l
.anad2i1
.a2nadoz
.anad2o1
.a2n3a4e2r2o1
.ana3e1
.a2nakal
.a1n2a1k2a1
.a2nakam
.a2nakat
.a2nakef
.anak2e1
.a2n2a1kl2a1
.anak2l
.a2nakl2i1
.a2nakoj
.anak2o1
.a2n3akuz
.anak2u1
.a2n3a2l1g
.a2n3a2l1d
.a2nalep
.anal2e1
.a2naliz
.anal2i1
.a2nalis
.a2nalit
.a2n3ame2r1t
.anam2e1
.a2namn2e1
.ana2m1n
.a2n3and2r
.ana2n1d
.a2nane3o1
.anan2e1
.a2n3a2n1t
.a2n2a1pl2a1
.anap2l
.a2napl2e1
.a2napn2e1
.ana2p1n
.a2napn2o1
.a2napr2o1
.anap2r
.a2napt2i1
.ana2p1t
.a2n3apt2o1
.a2na2r1t
.a2n3a2r1h
.a2nasar
.anas2a1
.a2nase3i1
.anas2e1
.a2naspaz
.anas2p
.anasp2a1
.a2n2a1st2a1
.anas2t
.a2nastig
.anast2i1
.a2nastom
.anast2o1
.a2natim
.anat2i1
.a2natom
.anat2o1
.a2natoc
.a2natr2e1
.anat2r
.a2natr2i1
.a2natr2o1
.a2nafaz
.anaf2a1
.a2n3afij
.anaf2i1
.a2n2a1fil2a1
.a2nafon
.anaf2o1
.a2n3afrod
.anaf2r
.anafr2o1
.a2nakol
.a2nakron
.anak2r
.anakr2o1
.a2nakr2u1
.a2n3a1lf2a1
.ana2l1f
.a2nafor
.a2nahor
.anah2o1
.a2nahr2o1
.anah2r
.a2n3eger
.an2e1
.aneg2e1
.a2n3ek2l
.a2n3ekum
.anek2u1
.a2n3elek
.anel2e1
.a2n3ener
.anen2e1
.a2n3ep2i1
.a2neor
.ane3o1
.a2n3e2r1g
.a2n3erit
.aner2i1
.a2n3e1st2e1
.anes2t
.a2n3id2r
.an2i1
.a2n3izog
.aniz2o1
.a2n3izom
.a2n3izur
.aniz2u1
.a2n3irid
.anir2i1
.a2n3ovar
.an2o1
.anov2a1
.a2n3o2k1s
.a2n3opis
.anop2i1
.a2n3o2r1h
.a2n3o2f1t
.a2n3o2r1g
.di2s3akor
.d2i1
.dis2a1
.disak2o1
.di2s3ju2n1k
.dis2j
.disj2u1
.di2s3kval
.dis2k
.disk2v
.diskv2a1
.di2s3ko2n1t
.disk2o1
.di2s3ko2r1d
.di2s3kr2e1
.disk2r
.d2i2s3kr2i1
.di2s3kur
.disk2u1
.di2s3l2o1
.dis2l
.di2s3orij
.dis2o1
.disor2i1
.di2s3parit
.dis2p
.disp2a1
.dispar2i1
.di2s3poz
.disp2o1
.di2s3pon
.di2s3prop
.disp2r
.dispr2o1
.di2s3ton
.dis2t
.dist2o1
.di2s3trak
.dist2r
.distr2a1
.i2n3abrup
.in2a1
.inab2r
.inabr2u1
.i2n3adek
.inad2e1
.i2n3akur
.inak2u1
.i2n3akc2e1
.ina2k1c
.i2n3amor
.inam2o1
.i2n3anic
.inan2i1
.i2n3aplik
.inap2l
.inapl2i1
.i2n3aps2t
.ina2p1s
.i2n3a2r1t
.i2n3augur
.ina3u1
.inaug2u1
.i2n3a1ur2a1
.i2n3afek
.inaf2e1
.i2n3evid
.in2e1
.inev2i1
.i2n3eg
.i2n3ed
.i2n3ek2v
.i2n3e2k1s
.i2n3elig
.inel2i1
.i2n3e2p1c
.i2n3efek
.inef2e1
.i2n3ob2l
.in2o1
.i2nogen
.inog2e1
.i2nokor
.inok2o1
.i2n3okup
.inok2u1
.i2n3oper
.inop2e1
.i2n3opor
.inop2o1
.i2n3ops2e1
.ino2p1s
.i2n3ofic
.inof2i1
.i2n3umb2r
.in2u1
.inu2m1b
.i2n3und2a1
.inu2n1d
.i2n3u2n1k
.i2n3util
.inut2i1
.i1nte2r3i1
.i2n1t
.int2e1
.inte2r3o1
.inte2r3u1
.inte2r3a1
.int2e2r3e1
.inte3r4e2g1n
.i1nte3r4es2i1
.inte3r4es2n
.inte3r4es2o1
.inte3r4es2u1
.inte3r4es2a1
.int2e3r4e1s2e1
.inte3r4e2ž1d2ž
.interež2d
.int2e3r4ij2e1
.int2e3r3j2e1
.inte2r1j
.inte3r4ogat
.interog2a1
.juri2s3k
.j2u1
.jur2i1
.juri2s3p
.nu2z3bel
.n2u1
.nuz2b
.nuzb2e1
.nu2z3bil2j
.nuzb2i1
.nu2z3ljub
.nuz2l
.nuz2l2j
.nuzlj2u1
.nu2z3r2e1
.nuz2r
.nu2z3r2j2e1
.nuz2r1j
.nu2z3už
.nuz2u1
.nu2s3pos
.nus2p
.nusp2o1
.nu2s3pr2o1
.nusp2r
.po2st3e2g1z
.pos2t
.post2e1
.po2st3ind2u1
.post2i1
.posti2n1d
.po2st3lim
.po2s3t2l
.postl2i1
.po2st3o2n1k
.p2o1st2o1
.po2st3oper
.postop2e1
.su2b3a1
.s2u1
.su2b3l
.su3b4aš
.su2b3i2n1v
.sub2i1
.su2b3ju2n1k
.sub2j
.subj2u1
.su2b3o2k1s
.sub2o1
.su2b3rep
.sub2r
.subr2e1
.su2b3rog
.subr2o1
.su2b3o2r1d
.supe2r3i1
.sup2e1
.supe2r3o1
.s2u1pe2r3u1
.supe2r3a1
.sup2e2r3e1
.supe3r4ior
.superi3o1
.tr2a1n2s3a1
.t2r
.tr2a1
.tra2n1s
.tran2s3c
.tran2s3e1
.tran2s3k
.tran2s3l
.tran2s3m
.tran2s3n
.tran2s3o1
.tran2s3p
.tran2s3t
.tran2s3u1
.tran2s3v
.tran2s3n2j
.tran3s4ep
.tran3s4kr2i1
.transk2r
.tran3s4um
.tran3s4ud
.a2n3jon
.an2j
.anj2o1
.i2n3jek
.in2j
.inj2e1
.i2n3jur
.inj2u1
.i2n3jus2t
.o2d3žal
.od2ž
.odž2a1
.o2d3žal2j
.o2d3ž2i1
.o2d3ž2v
.o2d3ž2e1
.pre2d3ž2i1
.pred2ž
.pr2e2d3ž2e1
.na2d3žd2r
.nad2ž
.na2dž2d
.na2d3ž2n2j
.na2dž2n
.na2d3ž2e1
.na2d3žan2j
.nadž2a1
.na2d3žir
.nadž2i1
.na2d3živ
.na2d3žup
.nadž2u1
2dž.
.n8a9d8n8o.
.nadn2o1
.n8a9t8k8a.
.n8a9t8k8a9t8i.
.natkat2i1
.n8a9t8k8a9š8e.
.natkaš2e1
.o9d8n8o.
.o2d1n
.odn2o1
.o9t8k8a.
.o9t8k8a9t8i.
.otkat2i1
.o9t8k8a9š8e.
.otkaš2e1
.p8o9d8n8o.
.podn2o1
.p8o9d8n8e.
.p8o9t8k8i.
.potk2i1
.p8o9t8k8u.
.potk2u1
.p8o9t8k8a.
.p8o9t8k8e.
.potk2e1
.u9d8n8o.
.u2d1n
.udn2o1
.i9g8d8e.
.i2g1d
.igd2e1
.i9g8d8j8e.
.igd2j
.igdj2e1
.s8v8u9g8d8e.
.s2v
.sv2u1
.svu2g1d
.svugd2e1
.s8v8e9g8d8e.
.sv2e1
.sve2g1d
.svegd2e1
.s8v8u9g8d8j8e.
.svugd2j
.svugdj2e1
.s8v8e9g8d8j8e.
.svegd2j
.svegdj2e1
.p8o9n8e9g8d8e.
.pon2e1
.pone2g1d
.ponegd2e1
.p8o9n8e9g8d8j8e.
.ponegd2j
.ponegdj2e1
.i9z8b8i.
.i9z8b8a.
.i9z8b8e.
.i9z8b8i9c8i.
.izbic2i1
.i9z8b8i9c8a.
.izbic2a1
.i9z8b8i9c8e.
.izbic2e1
.i9z8v8i8t.
.i9z8i8m.
.i8z8o9b8a9r8i.
.izobar2i1
.i8z8o9b8a9r8u.
.izobar2u1
.i8z8o9b8a9r8a.
.izobar2a1
.i8z8o9b8a9r8e.
.izobar2e1
.i9s8k8o8k.
.i9s8k8o9k8u.
.iskok2u1
.i9s8k8o9k8a.
.iskok2a1
.i9s8k8o8n.
.i9s8k8o9n8i.
.iskon2i1
.i9s8k8o9n8u.
.iskon2u1
.i9s8k8o9n8a.
.iskon2a1
.i9s8k8r8i.
.i9s8k8r8u.
.iskr2u1
.i9s8k8r8a.
.i9s8k8r8e.
.i9s8k8r8a8v.
.i9s8p8o8d.
.i9s8p8o9d8a.
.ispod2a1
.i9s8t8r8i.
.i9s8t8r8o.
.istr2o1
.i9s8t8r8u.
.i9s8t8r8a.
.i9s8t8r8e.
.istr2e1
.n8a9j8i.
.n8a9j8o.
.n8a9j8u.
.n8a9j8a.
.n8a9j8e.
.n8a9j8a8m.
.n8a9j8e8s8t.
.o9b8r8a8z.
.o9b8r8e8t.
.o9d8v8i9k8a.
.o9d8r8a8n.
.o9d8r8a9t8i.
.odrat2i1
.p8r8e9d8v8o8j.
.r8a9z8m8i.
.razm2i1
.r8a9z8m8o.
.razm2o1
.r8a9z8m8u.
.razm2u1
.r8a9z8m8a.
.razm2a1
.r8a9z8m8e.
.r8a9z8n8i.
.razn2i1
.r8a9z8n8o.
.razn2o1
.r8a9z8n8u.
.razn2u1
.r8a9z8n8a.
.r8a9z8n8e.
.razn2e1
.r8a9s8k8l8o8p.
.r8a9s8p8e9l8o.
.raspel2o1
.r8a9s8p8e9l8u.
.raspel2u1
.r8a9s8p8e9l8a.
.raspel2a1
.r8a9s8p8e9ć8u.
.raspeć2u1
.r8a9s8p8e9ć8a.
.raspeć2a1
.r8a9s8p8e9ć8e.
.raspeć2e1
.r8a9s8t8i.
.r8a9s8t8u.
.r8a9s8t8a.
.r8a9s8t8e.
.r8a9s8t8o8m.
.r8a9s8t8e8r.
.r8a9š8ć8i.
.rašć2i1
.r8a9š8ć8o.
.rašć2o1
.r8a9š8ć8u.
.rašć2u1
.r8a9š8ć8a.
.rašć2a1
.r8a9š8ć8e.
.u9z8n8i.
.u9z8n8o.
.u9z8n8a.
.u9z8n8i8k.
.u9z8r8o8k.
.u9s8k8i.
.usk2i1
.u9s8k8o.
.u9s8k8u.
.u9s8k8a.
.u9s8k8e.
.usk2e1
.u8s9k8o8s.
.u9s8p8i8o.
.uspi3o1
.u9s8p8e8o.
.uspe3o1
.u9s8p8o8r.
.u9š8ć8u.
.ušć2u1
.u9š8ć8a.
.ušć2a1
.u9š8ć8e.
.ušć2e1
.i8n9t8e9r8e8s.
.t8r8a8n9s8u.
.t8r8a8n9s8a.
.t8r8a8n9s8o8m.
т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ж
2д1б
2д1ц
2д1д
2д1ф
2д1г
2д1х
2д1к
2д1м
2д1н
2д1п
2д1с
2д1т
2д1њ
2д1џ
2д1з
2д1ш
2д1ђ
2д1ћ
2д1ч
2г1ж
2г1б
2г1ц
2г1д
2г1ф
2г1г
2г1х
2г1к
2г1м
2г1н
2г1п
2г1с
2г1т
2г1њ
2г1џ
2г1з
2г1ш
2г1ђ
2г1ћ
2г1ч
2х1ж
2х1б
2х1ц
2х1д
2х1ф
2х1г
2х1х
2х1к
2х1м
2х1н
2х1п
2х1с
2х1т
2х1њ
2х1џ
2х1з
2х1ш
2х1ђ
2х1ћ
2х1ч
2к1ж
2к1б
2к1ц
2к1д
2к1ф
2к1г
2к1х
2к1к
2к1м
2к1н
2к1п
2к1с
2к1т
2к1њ
2к1џ
2к1з
2к1ш
2к1ђ
2к1ћ
2к1ч
2т1ж
2т1б
2т1ц
2т1д
2т1ф
2т1г
2т1х
2т1к
2т1м
2т1н
2т1п
2т1с
2т1т
2т1њ
2т1џ
2т1з
2т1ш
2т1ђ
2т1ћ
2т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љ
в2р
б2ј
б2л
б2љ
б2р
ф2ј
ф2л
ф2љ
ф2р
м2ј
м2л
м2љ
м2р
2б1ж
2б1б
2б1ц
2б1д
2б1ф
2б1г
2б1х
2б1к
2б1м
2б1н
2б1п
2б1с
2б1т
2б1в
2б1њ
2б1џ
2б1з
2б1ш
2б1ђ
2б1ћ
2б1ч
2ф1ж
2ф1б
2ф1ц
2ф1д
2ф1ф
2ф1г
2ф1х
2ф1к
2ф1м
2ф1н
2ф1п
2ф1с
2ф1т
2ф1в
2ф1њ
2ф1џ
2ф1з
2ф1ш
2ф1ђ
2ф1ћ
2ф1ч
2м1ж
2м1б
2м1ц
2м1д
2м1ф
2м1г
2м1х
2м1к
2м1м
2м1н
2м1п
2м1с
2м1т
2м1в
2м1њ
2м1џ
2м1з
2м1ш
2м1ђ
2м1ћ
2м1ч
2п1ж
2п1б
2п1ц
2п1д
2п1ф
2п1г
2п1х
2п1к
2п1м
2п1н
2п1п
2п1с
2п1т
2п1в
2п1њ
2п1џ
2п1з
2п1ш
2п1ђ
2п1ћ
2п1ч
2в1ж
2в1б
2в1ц
2в1д
2в1ф
2в1г
2в1х
2в1к
2в1м
2в1н
2в1п
2в1с
2в1т
2в1в
2в1њ
2в1џ
2в1з
2в1ш
2в1ђ
2в1ћ
2в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њ
2с1ж
2с1б
2с1д
2с1ф
2с1г
2с1х
2с1с
2с1џ
2с1з
2с1ш
2с1ђ
2с1ћ
2с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з1ж
2з1ц
2з1ф
2з1х
2з1к
2з1п
2з1с
2з1т
2з1џ
2з1з
2з1ш
2з1ђ
2з1ћ
2з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ш1ж
2ш1б
2ш1д
2ш1ф
2ш1г
2ш1х
2ш1с
2ш1џ
2ш1з
2ш1ш
2ш1ђ
2ш1ј
2ш1р
2шк.
2шл.
2шм.
2шн.
2шп.
2шљ.
2шт.
2шв.
2шњ.
2шћ.
2шч.
2шц.
ж2б
ж2д
ж2г
ж2л
ж2м
ж2н
ж2љ
ж2в
ж2њ
ж2ђ
2ж1ж
2ж1ц
2ж1ф
2ж1х
2ж1к
2ж1п
2ж1с
2ж1т
2ж1џ
2ж1з
2ж1ш
2ж1ћ
2ж1ч
2ж1ј
2ж1р
2жл.
2жм.
2жн.
2жљ.
2жв.
2жњ.
2жђ.
2жб.
2жд.
2жг.
ц2ј
ц2р
ц2в
2ц1ж
2ц1б
2ц1ц
2ц1д
2ц1ф
2ц1г
2ц1х
2ц1к
2ц1л
2ц1м
2ц1н
2ц1п
2ц1љ
2ц1с
2ц1т
2ц1њ
2ц1џ
2ц1з
2ц1ш
2ц1ђ
2ц1ћ
2ц1ч
2цј.
2цр.
2цв.
ч2в
2ч1ж
2ч1б
2ч1ц
2ч1д
2ч1ф
2ч1г
2ч1х
2ч1ј
2ч1к
2ч1л
2ч1м
2ч1н
2ч1п
2ч1љ
2ч1р
2ч1с
2ч1т
2ч1њ
2ч1џ
2ч1з
2ч1ш
2ч1ђ
2ч1ћ
2ч1ч
2чв.
2ј1ж
2ј1б
2ј1ц
2ј1д
2ј1ф
2ј1г
2ј1х
2ј1ј
2ј1к
2ј1л
2ј1м
2ј1н
2ј1п
2ј1љ
2ј1р
2ј1с
2ј1т
2ј1в
2ј1њ
2ј1џ
2ј1з
2ј1ш
2ј1ђ
2ј1ћ
2ј1ч
2л1ж
2л1б
2л1ц
2л1д
2л1ф
2л1г
2л1х
2л1ј
2л1к
2л1л
2л1м
2л1н
2л1п
2л1љ
2л1р
2л1с
2л1т
2л1в
2л1њ
2л1џ
2л1з
2л1ш
2л1ђ
2л1ћ
2л1ч
2н1ж
2н1б
2н1ц
2н1д
2н1ф
2н1г
2н1х
2н1ј
2н1к
2н1л
2н1м
2н1н
2н1п
2н1љ
2н1р
2н1с
2н1т
2н1в
2н1њ
2н1џ
2н1з
2н1ш
2н1ђ
2н1ћ
2н1ч
2љ1ж
2љ1б
2љ1ц
2љ1д
2љ1ф
2љ1г
2љ1х
2љ1ј
2љ1к
2љ1л
2љ1м
2љ1н
2љ1п
2љ1љ
2љ1р
2љ1с
2љ1т
2љ1в
2љ1њ
2љ1џ
2љ1з
2љ1ш
2љ1ђ
2љ1ћ
2љ1ч
2р1ж
2р1б
2р1ц
2р1д
2р1ф
2р1г
2р1х
2р1ј
2р1к
2р1л
2р1м
2р1н
2р1п
2р1љ
2р1р
2р1с
2р1т
2р1в
2р1њ
2р1џ
2р1з
2р1ш
2р1ђ
2р1ћ
2р1ч
2њ1ж
2њ1б
2њ1ц
2њ1д
2њ1ф
2њ1г
2њ1х
2њ1ј
2њ1к
2њ1л
2њ1м
2њ1н
2њ1п
2њ1љ
2њ1р
2њ1с
2њ1т
2њ1в
2њ1њ
2њ1џ
2њ1з
2њ1ш
2њ1ђ
2њ1ћ
2њ1ч
2џ1ж
2џ1б
2џ1ц
2џ1д
2џ1ф
2џ1г
2џ1х
2џ1ј
2џ1к
2џ1л
2џ1м
2џ1н
2џ1п
2џ1љ
2џ1р
2џ1с
2џ1т
2џ1в
2џ1њ
2џ1џ
2џ1з
2џ1ш
2џ1ђ
2џ1ћ
2џ1ч
2ђ1ж
2ђ1б
2ђ1ц
2ђ1д
2ђ1ф
2ђ1г
2ђ1х
2ђ1ј
2ђ1к
2ђ1л
2ђ1м
2ђ1н
2ђ1п
2ђ1љ
2ђ1р
2ђ1с
2ђ1т
2ђ1в
2ђ1њ
2ђ1џ
2ђ1з
2ђ1ш
2ђ1ђ
2ђ1ћ
2ђ1ч
2ћ1ж
2ћ1б
2ћ1ц
2ћ1д
2ћ1ф
2ћ1г
2ћ1х
2ћ1ј
2ћ1к
2ћ1л
2ћ1м
2ћ1н
2ћ1п
2ћ1љ
2ћ1р
2ћ1с
2ћ1т
2ћ1в
2ћ1њ
2ћ1џ
2ћ1з
2ћ1ш
2ћ1ђ
2ћ1ћ
2ћ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о1
о3а1
о3е1
о3и1
2о3о1
о3у1
2у1
у3а1
у3е1
у3и1
у3о1
2у3у1
2а1
2а3а1
а3е1
а3и1
а3о1
а3у1
2е1
е3а1
2е3е1
е3и1
е3о1
е3у1
2и1
и3а1
и3е1
2и3и1
и3о1
и3у1
2с2к1б
2с2к1ц
2с2к1д
2с2к1ф
2с2к1г
2с2к1х
2с2к1к
2с2к1м
2с2к1н
2с2к1п
2с2к1с
2с2к1т
2с2к1њ
2с2к1џ
2с2к1з
2с2к1ш
2с2к1ђ
2с2к1ћ
2с2к1ч
2с2к1ж
2с2т1б
2с2т1ц
2с2т1д
2с2т1ф
2с2т1г
2с2т1х
2с2т1к
2с2т1м
2с2т1н
2с2т1п
2с2т1с
2с2т1т
2с2т1њ
2с2т1џ
2с2т1з
2с2т1ш
2с2т1ђ
2с2т1ћ
2с2т1ч
2с2т1ж
2ш2к1б
2ш2к1ц
2ш2к1д
2ш2к1ф
2ш2к1г
2ш2к1х
2ш2к1к
2ш2к1м
2ш2к1н
2ш2к1п
2ш2к1с
2ш2к1т
2ш2к1њ
2ш2к1џ
2ш2к1з
2ш2к1ш
2ш2к1ђ
2ш2к1ћ
2ш2к1ч
2ш2к1ж
2ш2т1б
2ш2т1ц
2ш2т1д
2ш2т1ф
2ш2т1г
2ш2т1х
2ш2т1к
2ш2т1м
2ш2т1н
2ш2т1п
2ш2т1с
2ш2т1т
2ш2т1њ
2ш2т1џ
2ш2т1з
2ш2т1ш
2ш2т1ђ
2ш2т1ћ
2ш2т1ч
2ш2т1ж
2с2п1б
2с2п1ц
2с2п1д
2с2п1ф
2с2п1г
2с2п1х
2с2п1к
2с2п1м
2с2п1н
2с2п1п
2с2п1с
2с2п1т
2с2п1в
2с2п1њ
2с2п1џ
2с2п1з
2с2п1ш
2с2п1ђ
2с2п1ћ
2с2п1ч
2с2п1ж
2с2в1б
2с2в1ц
2с2в1д
2с2в1ф
2с2в1г
2с2в1х
2с2в1к
2с2в1м
2с2в1н
2с2в1п
2с2в1с
2с2в1т
2с2в1в
2с2в1њ
2с2в1џ
2с2в1з
2с2в1ш
2с2в1ђ
2с2в1ћ
2с2в1ч
2с2в1ж
2ш2п1б
2ш2п1ц
2ш2п1д
2ш2п1ф
2ш2п1г
2ш2п1х
2ш2п1к
2ш2п1м
2ш2п1н
2ш2п1п
2ш2п1с
2ш2п1т
2ш2п1в
2ш2п1њ
2ш2п1џ
2ш2п1з
2ш2п1ш
2ш2п1ђ
2ш2п1ћ
2ш2п1ч
2ш2п1ж
2ш2в1б
2ш2в1ц
2ш2в1д
2ш2в1ф
2ш2в1г
2ш2в1х
2ш2в1к
2ш2в1м
2ш2в1н
2ш2в1п
2ш2в1с
2ш2в1т
2ш2в1в
2ш2в1њ
2ш2в1џ
2ш2в1з
2ш2в1ш
2ш2в1ђ
2ш2в1ћ
2ш2в1ч
2ш2в1ж
2ж2д1б
2ж2д1ц
2ж2д1д
2ж2д1ф
2ж2д1г
2ж2д1х
2ж2д1к
2ж2д1м
2ж2д1н
2ж2д1п
2ж2д1с
2ж2д1т
2ж2д1њ
2ж2д1џ
2ж2д1з
2ж2д1ш
2ж2д1ђ
2ж2д1ћ
2ж2д1ч
2ж2д1ж
2ж2г1б
2ж2г1ц
2ж2г1д
2ж2г1ф
2ж2г1г
2ж2г1х
2ж2г1к
2ж2г1м
2ж2г1н
2ж2г1п
2ж2г1с
2ж2г1т
2ж2г1њ
2ж2г1џ
2ж2г1з
2ж2г1ш
2ж2г1ђ
2ж2г1ћ
2ж2г1ч
2ж2г1ж
2з2д1б
2з2д1ц
2з2д1д
2з2д1ф
2з2д1г
2з2д1х
2з2д1к
2з2д1м
2з2д1н
2з2д1п
2з2д1с
2з2д1т
2з2д1њ
2з2д1џ
2з2д1з
2з2д1ш
2з2д1ђ
2з2д1ћ
2з2д1ч
2з2д1ж
2з2г1б
2з2г1ц
2з2г1д
2з2г1ф
2з2г1г
2з2г1х
2з2г1к
2з2г1м
2з2г1н
2з2г1п
2з2г1с
2з2г1т
2з2г1њ
2з2г1џ
2з2г1з
2з2г1ш
2з2г1ђ
2з2г1ћ
2з2г1ч
2з2г1ж
2ж2в1б
2ж2в1ц
2ж2в1д
2ж2в1ф
2ж2в1г
2ж2в1х
2ж2в1к
2ж2в1м
2ж2в1н
2ж2в1п
2ж2в1с
2ж2в1т
2ж2в1в
2ж2в1њ
2ж2в1џ
2ж2в1з
2ж2в1ш
2ж2в1ђ
2ж2в1ћ
2ж2в1ч
2ж2в1ж
2ж2б1б
2ж2б1ц
2ж2б1д
2ж2б1ф
2ж2б1г
2ж2б1х
2ж2б1к
2ж2б1м
2ж2б1н
2ж2б1п
2ж2б1с
2ж2б1т
2ж2б1в
2ж2б1њ
2ж2б1џ
2ж2б1з
2ж2б1ш
2ж2б1ђ
2ж2б1ћ
2ж2б1ч
2ж2б1ж
2з2в1б
2з2в1ц
2з2в1д
2з2в1ф
2з2в1г
2з2в1х
2з2в1к
2з2в1м
2з2в1н
2з2в1п
2з2в1с
2з2в1т
2з2в1в
2з2в1њ
2з2в1џ
2з2в1з
2з2в1ш
2з2в1ђ
2з2в1ћ
2з2в1ч
2з2в1ж
2з2б1б
2з2б1ц
2з2б1д
2з2б1ф
2з2б1г
2з2б1х
2з2б1к
2з2б1м
2з2б1н
2з2б1п
2з2б1с
2з2б1т
2з2б1в
2з2б1њ
2з2б1џ
2з2б1з
2з2б1ш
2з2б1ђ
2з2б1ћ
2з2б1ч
2з2б1ж
2ж2м1б
2ж2м1ц
2ж2м1д
2ж2м1ф
2ж2м1г
2ж2м1х
2ж2м1к
2ж2м1м
2ж2м1н
2ж2м1п
2ж2м1с
2ж2м1т
2ж2м1в
2ж2м1њ
2ж2м1џ
2ж2м1з
2ж2м1ш
2ж2м1ђ
2ж2м1ћ
2ж2м1ч
2ж2м1ж
2с2м1б
2с2м1ц
2с2м1д
2с2м1ф
2с2м1г
2с2м1х
2с2м1к
2с2м1м
2с2м1н
2с2м1п
2с2м1с
2с2м1т
2с2м1в
2с2м1њ
2с2м1џ
2с2м1з
2с2м1ш
2с2м1ђ
2с2м1ћ
2с2м1ч
2с2м1ж
2з2м1б
2з2м1ц
2з2м1д
2з2м1ф
2з2м1г
2з2м1х
2з2м1к
2з2м1м
2з2м1н
2з2м1п
2з2м1с
2з2м1т
2з2м1в
2з2м1њ
2з2м1џ
2з2м1з
2з2м1ш
2з2м1ђ
2з2м1ћ
2з2м1ч
2з2м1ж
2ш2м1б
2ш2м1ц
2ш2м1д
2ш2м1ф
2ш2м1г
2ш2м1х
2ш2м1к
2ш2м1м
2ш2м1н
2ш2м1п
2ш2м1с
2ш2м1т
2ш2м1в
2ш2м1њ
2ш2м1џ
2ш2м1з
2ш2м1ш
2ш2м1ђ
2ш2м1ћ
2ш2м1ч
2ш2м1ж
2с2ц1б
2с2ц1ц
2с2ц1д
2с2ц1ф
2с2ц1г
2с2ц1х
2с2ц1к
2с2ц1л
2с2ц1м
2с2ц1н
2с2ц1п
2с2ц1љ
2сц2р
2с2ц1с
2с2ц1т
2с2ц1њ
2с2ц1џ
2с2ц1з
2с2ц1ш
2с2ц1ђ
2с2ц1ћ
2с2ц1ч
2с2ц1ж
2ш2ц1б
2ш2ц1ц
2ш2ц1д
2ш2ц1ф
2ш2ц1г
2ш2ц1х
2ш2ц1к
2ш2ц1л
2ш2ц1м
2ш2ц1н
2ш2ц1п
2ш2ц1љ
2шц2р
2ш2ц1с
2ш2ц1т
2ш2ц1њ
2ш2ц1џ
2ш2ц1з
2ш2ц1ш
2ш2ц1ђ
2ш2ц1ћ
2ш2ц1ч
2ш2ц1ж
2ш2ч1б
2ш2ч1ц
2ш2ч1д
2ш2ч1ф
2ш2ч1г
2ш2ч1х
2ш2ч1ј
2ш2ч1к
2ш2ч1л
2ш2ч1м
2ш2ч1н
2ш2ч1п
2ш2ч1љ
2ш2ч1р
2ш2ч1с
2ш2ч1т
2ш2ч1њ
2ш2ч1џ
2ш2ч1з
2ш2ч1ш
2ш2ч1ђ
2ш2ч1ћ
2ш2ч1ч
2ш2ч1ж
2х2в1б
2х2в1ц
2х2в1д
2х2в1ф
2х2в1г
2х2в1х
2х2в1к
2х2в1м
2х2в1н
2х2в1п
2х2в1с
2х2в1т
2х2в1њ
2х2в1џ
2х2в1з
2х2в1ш
2х2в1ђ
2х2в1ћ
2х2в1ч
2х2в1ж
2ж3в2л
2ж3в2љ
2ц3в2л
2ц3в2љ
2з3в2л
2з3в2љ
2ш3в2л
2ш3в2љ
2ч3в2л
2ч3в2љ
2ч3в2ј
2с3в2љ
2д3в2л
2д3в2љ
2д3в2р
2к3в2ј
2к3в2л
2к3в2љ
2т3в2ј
2т3в2л
2т3в2љ
2г3в2ј
2г3в2л
2г3в2љ
2г3в2р
2х3в2ј
2х3в2л
2х3в2љ
2х3в2р
2ж3м2ј
2ж3м2л
2ж3м2љ
2ж3м2р
2з3м2л
2з3м2р
2ш3м2ј
2ш3м2л
2ш3м2љ
2ш3ц2ј
2ш3ц2в
2ш3ч2в
2ш3т2ј
2ш3т2л
2ш3т2љ
2с3т2л
2с3к2ј
2с3к2љ
2ш3п2ј
2ш3п2л
2ш3п2љ
2ж3д2ј
2ж3д2л
2ж3д2љ
2ж3д2в
2ж3г2ј
2ж3г2л
2ж3г2љ
2ж3г2р
2ж3г2в
2з3д2л
2з3д2љ
2з3д2в
2з3г2ј
2з3г2љ
2ж3б2ј
2ж3б2л
2ж3б2љ
2ж3б2р
2з3б2љ
.а4е2р2о1
.а1
.а3е1
.бе4о1
.б2е1
.би4о1
.б2и1
.ге4о1
.г2е1
.за3г2н
.з2а1
.з2а3т2к2а1
.за2т1к
.иза3г2н
.и1
.из2а1
.иза3т2к
.и2з3г
.и2з3г2н
.и2з3д
.изд2н2о1
.и2з2д1н
.изд2н2у1
.изд2н2а1
.и2з3р
.из2р2к
.и2с3т
.и2с2т2к
.на2г2н
.н2а1
.на2г2њ
.на3д2нев
.на2д1н
.надн2е1
.на3д2нич
.надн2и1
.на3д2ниц
.на3т2ках
.на2т1к
.н2а1тк2а1
.на3т2кам
.на3т2кас2м
.на3т2кас2т
.ода3г2н
.о1
.од2а1
.ода3д2н
.од3г2н
.о2д1г
.од3м2н
.о2д1м
.о3т2ках
.о2т1к
.отк2а1
.о3т2кам
.о3т2кас2м
.о3т2кас2т
.по3г2н
.п2о1
.по3д2нев
.по2д1н
.подн2е1
.по3м2н
.по3м2њ
.по3р2в
.по3р2ђ
.по3т2ках
.по2т1к
.потк2а1
.по3т2кам
.по3т2кат
.по3т2кав
.пред3м2н
.п2р
.пр2е1
.пре2д1м
.пред3м2њ
.пре3т2ках
.пре2т1к
.претк2а1
.пре3т2кам
.пре3т2кат
.про3г2н
.пр2о1
.про3т2к2и1
.про2т1к
.про3т2к2а1
.раза3г2н
.р2а1
.р2а1з2а1
.ра2з3г
.ра2з3г2н
.ра2з3д
.раз3д2н2и1
.ра2з2д1н
.р2а1з2а3т2к2а1
.раза2т1к
.у3г2м2и1
.у1
.у2г1м
.у3г2н
.уз2а3т2к2а1
.уз2а1
.уза2т1к
3х2тет2и1
хт2е1
3х2тјет2и1
хт2ј
хтј2е1
3х2тел
3х2тев
3х2тењ
3х2тјел
3х2тјев
3х2тјењ
3г2дегод.
гд2е1
гдег2о1
3г2дјегод.
гд2ј
гдј2е1
гдјег2о1
3г2декак
гдек2а1
3г2декад
3г2дјекак
гдјек2а1
3г2дјекад
ни3г2де.
н2и1
ни2г1д
нигд2е1
не3г2де.
н2е1
не2г1д
негд2е1
ни3г2дје.
нигд2ј
нигдј2е1
не3г2дје.
негд2ј
негдј2е1
3б2дет
бд2е1
3б2дењ
3б2дјет
бд2ј
бдј2е1
3б2дјењ
3г2мил
гм2и1
3г2миљ
3г2миз
3г2нос
гн2о1
3г2ноз
3г2ној
3г2нај
гн2а1
3г2нез2д
гн2е1
3г2нијез2д
гн2и1
гниј2е1
3г2неж2ђ
3г2нијеж2ђ
3г2нев
3г2њев
гњ2е1
3г2њав
гњ2а1
3г2њес
3г2њет
3г2њеч
3г2њил
гњ2и1
3г2њи3о1
3г2њиљ
3г2њит
3г2њур
гњ2у1
3к2нез
кн2е1
3к2неж
3к2њиж
књ2и1
3к2њиг
3м2нож
мн2о1
3м2ног
3м2наж
мн2а1
3п2сик
пс2и1
3п2сич
3п2сов
пс2о1
3п2суј
пс2у1
3р2ђ2а1
3с2фер
сф2е1
3т2мас2т
тм2а1
3т2мул
тм2у1
3т2му3о1
3т2муљ
3т2мур
3ц2миз
цм2и1
3ц2мак
цм2а1
3ц2мач
3ц2мок
цм2о1
3ч2лан
чл2а1
3ч2лањ
3р2ј2е1
4р3јем
4р3је.
.бе2з3ј
.бе2з3л
.бе2з3м
.бе2з3н
.бе2з3љ
.бе2з3р
.бе2з3в
.бе2з3њ
.бе2з3б
.бе2з3д
.бе2з3г
.бе2з3и1
.бе2з3о1
.бе2з3у1
.бе2з3алкохол
.без2а1
.беза2л1к
.безалк2о1
.безалкох2о1
.бе2з3атомс2к
.безат2о1
.безато2м1с
.бе3з4бе2д1н
.б2е1зб2е1
.бе3з4бед2а1
.бе3з4бје2д1н
.безб2ј
.б2е1збј2е1
.бе3з4бјед2а1
.бе3з4бел2и1
.бе3з4бол
.безб2о1
.бе3з4ву2ч1н
.безв2у1
.бе3з4вуч2а1
.бе3з4истан
.безис2т
.безист2а1
.бе3з4истен
.безист2е1
.бе3з4јак
.безј2а1
.бе3з4јач
.бе3з4ло2б1н
.безл2о1
.бе3з4лоб2а1
.бе3з4начај
.безн2а1
.безнач2а1
.бе3з4ра2ч1н
.безр2а1
.бе3з4р2а1ч2а1
.бе3з4уп
.бе3з4уб
.бе2с3ц
.бе2с3к
.бе2с3п
.бе2с3т
.бе3с4крупул
.беск2р
.бескр2у1
.бескруп2у1
.бе3с4поко2ј1н
.бесп2о1
.бесп2о1к2о1
.бе3с4покој2а1
.бе3с4по2р1н
.бе3с4пор2а1
.бе3с4твар
.бест2в
.беств2а1
.бе3с4тид
.бест2и1
.бе3с4тиј2а1
.бе3с4тил2у1
.бе3с4тиљ
.бе3с4тр2а1н2а1
.бест2р
.бестр2а1
.бе3с4трас
.бес4тселер
.бе2с2т1с
.бестс2е1
.бестсел2е1
.бе2ш3ћ
.бе2ш3ч
.ва2н3ев2р
.в2а1
.ван2е1
.ва2н3устав
.ван2у1
.ванус2т
.вануст2а1
.и2з3б
.и2з3ј
.и2з3л
.и2з3м
.и2з3н
.и2з3љ
.и2з3в
.и2з3њ
.и2з3и1
.и2з3о1
.и2з3у1
.и2з3биј2а1
.и1зб2и1
.и2з3бив2а1
.и2з3вед2и1
.изв2е1
.и2з3ве2д1н
.и2з3ве2д1б
.и2з3в2е1д2е1
.и2з3дај
.изд2а1
.и2з3а1б2а1
.и2з3а1к2а1
.и2з3анал
.изан2а1
.и3з4бав
.изб2а1
.и3з4бичк2а1в2а1
.изби2ч1к
.избичк2а1
.и3з4блеушан
.изб2л
.избл2е1
.избле3у1
.изблеуш2а1
.и3з4бојак
.изб2о1
.избој2а1
.и3з4бо2ј1к
.и3з4вал2и1
.изв2а1
.и3з4вал2у1
.и3з4в2а1л2а1
.и3з4вал2е1
.и3з4ваљ2и1
.и3з4виж2д
.и1зв2и1
.и3з4вииск2р
.и1зв2и3и1
.извиис2к
.и3з4виј2а1
.и3з4вијен
.извиј2е1
.и3з4вин
.и3з4вир
.и3з4вињ
.и3з4витоп
.извит2о1
.и3з4вјед
.изв2ј
.извј2е1
.и3з4војац
.изв2о1
.извој2а1
.и3з4во2ј1ц
.и3з4вор
.и3з4гомет
.изг2о1
.изгом2е1
.и3з4гред
.изг2р
.изгр2е1
.и3з4г2р1н
.и3з4г2р1т
.и3з4драв
.изд2р
.издр2а1
.и3з4иђ
.и3з4ид
.и3з4и1м2и1
.и3з4јеж2љ
.изј2е1
.и3з4лоз
.изл2о1
.и3з4лож
.и3з4лог
.и3з4лопаћ
.излоп2а1
.и3з4ним
.изн2и1
.и3з4ној
.изн2о1
.из4оанем
.изо3а1
.изоан2е1
.из4оаном
.изоан2о1
.из4обат
.изоб2а1
.из4оброн
.изоб2р
.изобр2о1
.из4огам
.изог2а1
.из4о1ге3о1
.изог2е1
.из4оглос
.изог2л
.изогл2о1
.из4огон
.изог2о1
.из4ограф
.изог2р
.изогр2а1
.из4одим
.и1зод2и1
.из4один
.из4одоз
.изод2о1
.из4оклин
.изок2л
.изокл2и1
.из4околон
.изок2о1
.изокол2о1
.и3з4олат
.изол2а1
.и3з4олац
.и3з4олир
.изол2и1
.и3з4олов
.изол2о1
.из4оле2к1с
.изол2е1
.из4олу2к1с
.изол2у1
.из4омер
.изом2е1
.из4омет2р
.из4омо2р1ф
.изом2о1
.из4онеф
.изон2е1
.из4оном
.изон2о1
.из4опат
.изоп2а1
.из4опер
.изоп2е1
.из4опл2е1
.изоп2л
.из4опол
.изоп2о1
.из4опсеф
.изо2п1с
.изопс2е1
.из4орах
.изор2а1
.и1з4осе3и1
.изос2е1
.из4оси2н1т
.и1зос2и1
.из4осис2т
.из4оскел
.изос2к
.изоск2е1
.из4оскоп
.изоск2о1
.из4остаз
.изос2т
.изост2а1
.из4ост2е1
.из4отах
.изот2а1
.из4отал
.из4отер
.изот2е1
.из4отон
.из2о1т2о1
.из4отоп
.из4о1тр2о1
.изот2р
.из4офон
.из2о1ф2о1
.из4офот
.из4охал
.изох2а1
.из4охаз
.из4охел
.изох2е1
.из4охиј
.и1зох2и1
.из4охим
.из4охит
.из4охи2п1с
.из4охор
.изох2о1
.из4о1хр2о1
.изох2р
.и3з4раел
.изр2а1
.изра3е1
.и3з4раиљ
.и1зра3и1
.и3з4рач2и1
.и3з4ун
.и3з4у2п1ч
.и2с3ц
.и2с3к
.и2с3п
.и3с4как
.иск2а1
.и3с4кат
.и3с4кањ
.и3с4кариот
.искар2и1
.искари3о1
.и3с4квас
.иск2в
.искв2а1
.и3с4кв2р1ч
.искв2р
.и3с4кин
.и1ск2и1
.и3с4кит2а1
.и3с4конс2к
.иск2о1
.иско2н1с
.и3с4коч
.и3с4крам
.иск2р
.искр2а1
.и3с4крит
.и1скр2и1
.и3с4криш
.и3с4крич
.и3с4криц
.и3с4крат
.и3с4крен
.искр2е1
.и3с4крењ
.и3с4крој
.искр2о1
.и3с4крс2н
.иск2р1с
.и3с4крс2а1
.и3с4купљ2а1
.иск2у1
.искуп2љ
.и3с4лам
.ис2л
.исл2а1
.и3с4лаб
.и3с4леђ
.исл2е1
.и3с4лед
.и3с4лијеђ
.и1сл2и1
.ислиј2е1
.и3с4лијед
.и3с4љеђ
.ис2љ
.исљ2е1
.и3с4љед
.и3с4лик
.и3с4лин
.и3с4лов
.исл2о1
.и3с4луш
.исл2у1
.и3с4луж
.и3с4м2е1
.ис2м
.и3с4миј2е1
.исм2и1
.и3с4мј2е1
.исм2ј
.и3с4пав
.исп2а1
.и3с4паљив
.испаљ2и1
.и3с4пир2а1
.исп2и1
.и3с4плит
.исп2л
.и1спл2и1
.и3с4плић
.и3с4покој
.исп2о1
.испок2о1
.и3с4полин
.испол2и1
.и3с4пон
.и3с4порав
.испор2а1
.и3с4прав2и1
.исп2р
.испр2а1
.и3с4пра2в1к
.и3с4пра2в1н
.и3с4прав2љ
.и3с4пр2а1в2а1
.и3с4пу2п1ч
.исп2у1
.и3с4пур
.и3с4ред
.ис2р
.иср2е1
.и3с4р1к
.и3с4тав2и1
.ист2а1
.и3с4тав2љ
.и3с4та2к1н
.и3с4там
.и3с4тар
.и3с4тас
.и3с4таћ
.и3с4тин
.и1ст2и1
.и3с4тир
.и3с4тиц
.и3с4тифан
.истиф2а1
.и3с4ток
.ист2о1
.и3с4тор2и1
.и3с4то2ч1н
.и3с4то2ч1њ
.и3с4точ2а1
.и3с4трав
.ист2р
.истр2а1
.и3с4трад
.и3с4тран
.и3с4трић
.и1стр2и1
.и3с4триж
.и3с4триц
.и3с4труг
.истр2у1
.и3с4туп
.ист2у1
.и3с4ук
.ис2у1
.и3с4ус
.и3с4ут
.и3с4уш
.и2ж3ђ
.и2ш3ћ
.и2ш3ч
.из3бе2з3об2р
.изб2е1
.избез2о1
.из3бе2з3ум
.избез2у1
.из3ва2н3ев2р
.изван2е1
.на2д3л
.на2д3љ
.на2д3в
.на3д4вал
.надв2а1
.на3д4вес2и1
.надв2е1
.на3д4вес2т
.на3д4виј
.надв2и1
.на3д4вит
.н2а3д4вл2а1
.на2д3в2л
.на3д4вој2е1
.надв2о1
.на3д4вор
.на2д3иг2р
.над2и1
.на2д3и2н1ж
.н2а2д3ин2а1
.на2д3ис2к
.на2д3јах
.над2ј
.н2а1дј2а1
.на2д3јач
.на2д3јек
.надј2е1
.на2д3јез
.на2д3јеч
.на2д3јун
.надј2у1
.на3д4лан
.надл2а1
.на3д4леш
.надл2е1
.на3д4леж
.н2а2д3ор2а1
.над2о1
.на2д3о1с2о1
.на2д3ос2е1
.на2д3осј2е1
.надос2ј
.на2д3оф2и1
.на2д3оч
.на2д3ран
.над2р
.н2а1др2а1
.на2д3рач
.на2д3рас2т
.на2д3раш2ћ
.на2д3реал
.надр2е1
.н2а1дре3а1
.на2д3реп
.на2д3рук
.надр2у1
.на2д3руч
.на2д3руг
.на2д3удар
.над2у1
.надуд2а1
.на2д3ум
.на2д3уч
.н2а2ј3а1
.на2ј3е1
.на2ј3и1
.на2ј3о1
.на2ј3у1
.на3ј4ав2и1
.на3ј4ав2љ
.н2а3ј4а1в2а1
.на3ј4ав2е1
.на3ј4ад2и1
.н2а3ј4а1д2а1
.на3ј4ад2е1
.на3ј4аж2и1
.на3ј4аз2и1
.на3ј4ак2о1
.н2а3ј4а1к2а1
.на3ј4ал2о1
.на3ј4ам2и1
.на3ј4ам2л
.на3ј4а2м1н
.на3ј4ар2и1
.на3ј4а2р1м
.на3ј4а2р1ц
.на3ј4ат2и1
.на3ј4аук
.наја3у1
.на3ј4ах
.на3ј4аш
.на3ј4ед2и1
.на3ј4е2д1н
.на3ј4ед2р
.н2а3ј4ед2а1
.на3ј4еж2и1
.на3ј4еж2у1
.на3ј4е1ж2е1
.на3ј4ез2н
.на3ј4ез2д
.на3ј4ест2и1
.најес2т
.на3ј4е2т1к
.на3ј4ец
.на3ј4ур2и1
.на3ј4урен
.најур2е1
.о2б3ј
.о2б3љ
.о2б3р
.обе2з3б
.об2е1
.обе2з3д
.обе2з3г
.обе2з3ј
.обе2з3л
.обе2з3м
.обе2з3н
.о1бе2з3о1
.обе2з3љ
.обе2з3р
.обе2з3у1
.обе2з3в
.обе3з4виј
.обезв2и1
.обе3з4нан
.обезн2а1
.обе3з4нањ
.обе3з4нач
.обе3з4уб
.обе2с3ц
.обе2с3к
.обе2с3п
.обе2с3т
.обе3с4тан
.обест2а1
.обе3с4тиј
.обест2и1
.обе3с4тран
.обест2р
.обестр2а1
.обе2ш3ћ
.обе2ш3ч
.о2б3иг2р
.об2и1
.о2б3истин
.обис2т
.об2и1ст2и1
.о2б3истињ
.о3б4јек
.обј2е1
.о3б4јер
.о3б4јес2и1
.о3б4јет
.о3б4јеш
.о2б3лај
.об2л
.обл2а1
.о2б3лам
.о2б3ла2к1ш
.о2б3лас2к
.о2б3леп
.обл2е1
.о2б3лет
.о2б3лећ
.о2б3леж
.о2б3лег
.о2б3лијеп
.обл2и1
.облиј2е1
.о2б3лијет
.о2б3лијеж
.о2б3лијег
.о2б3леден
.облед2е1
.о2б3лив
.о2б3лизат
.облиз2а1
.о2б3лизав
.о2б3л2и1з2и1
.о2б3лис2т
.о2б3лок2а1
.обл2о1
.о2б3лук
.обл2у1
.о2б3луч
.о3б4љан
.обљ2а1
.о3б4љут
.обљ2у1
.о3б4љуз
.о2б3ор2у1
.об2о1
.о3б4раж2е1
.обр2а1
.о3б4раз2и1
.о3б4раз2н
.о3б4раз2о1
.о3б4раз2у1
.о3б4р2а1з2а1
.о3б4раз2д
.о3б4ра2м1б
.о3б4ран
.о3б4рањ
.о3б4рат
.о3б4раћ
.о3б4раш2н
.о3б4раш2ч
.о3б4р1в
.о3б4р1ђ
.о3б4рем
.обр2е1
.о3б4рес
.о3б4ређ
.о3б4реч
.о3б4реж
.о3б4рец
.о3б4ред
.о3б4рет2и1
.о3б4ре2т1н
.о3б4риј
.обр2и1
.о3б4рис
.о3б4рит
.о3б4рив
.о3б4рич
.о3б4риц
.о3б4р1к
.о3б4р1л
.о3б4р1н
.о3б4р1љ
.о3б4р1с
.о3б4р1т
.о3б4р1ш
.о3б4р1ч
.о3б4рок
.о1бр2о1
.о3б4рон
.о3б4роњ
.о3б4роћ
.о3б4роч
.о3б4ров2а1
.о3б4ро2в1ц
.о3б4рук
.обр2у1
.о3б4рун
.о3б4рус
.о3б4руњ
.о3б4руш
.о3б4руч
.о2б3убож
.об2у1
.обуб2о1
.о2б3уз
.о2б3уж
.о2б3уд
.о2б3ум2и1
.о2б3ум2ј
.о2б3ум2р
.о2б3ум2е1
.о2д3ј
.о2д3л
.о2д3љ
.о2д3р
.о2д3в
.о2д3а2р1г
.о3д4вај
.одв2а1
.о3д4важ
.о3д4вес2н
.одв2е1
.о3д4вес2т
.о3д4вес2а1
.о3д4викав
.одв2и1
.одвик2а1
.о3д4ви2к1н
.о3д4вис
.о3д4вић
.о3д4вој
.одв2о1
.о2д3иг2р
.од2и1
.о2д3и2з3в
.о2д3и2з3д
.о2д3ис2к
.о2д3и1ст2и1
.одис2т
.о3д4јел
.одј2е1
.о3д4јен
.о3д4јев
.о3д4јећ
.о3д4лаз
.одл2а1
.о3д4лаж
.о3д4лаг
.о3д4л2а1к2а1
.о3д4лук
.одл2у1
.о3д4луч
.о2д3оз2д
.о1д2о1
.о2д3оз2г
.о2д3ок
.о2д3о2н1л
.о2д3о1н2о1
.о2д3он2у1
.о2д3о2н1д
.о3д4ран2и1
.одр2а1
.о3д4ран2о1
.о3д4ран2у1
.о3д4р2а1н2а1
.о3д4ран2е1
.о3д4раз
.о3д4раћ
.о3д4раж
.о3д4рап2и1
.о3д4рап2љ
.о3д4р2а1п2а1
.о3д4рач2и1
.о3д4рвен
.од2р1в
.одрв2е1
.о3д4рвењ
.о3д4рвеч
.о3д4рем
.одр2е1
.о3д4рен
.о3д4рет
.о3д4ређ
.о3д4ред
.о3д4р1л
.о3д4р1н
.о3д4р1п
.о3д4р1љ
.о3д4р1т
.о3д4р1ж
.о3д4рин
.одр2и1
.о3д4рињ
.о3д4риш
.о3д4рич
.о3д4риб
.о3д4риц
.о3д4рон
.о1др2о1
.о3д4роњ
.о3д4руж
.одр2у1
.о3д4руг
.о2д3ув2и1
.од2у1
.о2д3ув2е1
.о2д3уз2и1
.о2д3уз2л
.о2д3уз2д
.о2д3уз2е1
.о2д3ук
.о2д3ул
.о2д3ум
.о2д3уч
.по2д3а2д1м
.под2а1
.по2д3вариј
.под2в
.подв2а1
.подвар2и1
.по2д3вез
.подв2е1
.по2д3веч
.по2д3веж
.по2д3вик
.подв2и1
.по2д3вил
.по2д3вир
.по2д3вињ
.по2д3влас
.по2д3в2л
.подвл2а1
.по2д3влаш
.по2д3воз
.п2о1дв2о1
.по2д3вођ
.по2д3вож
.по2д3вод
.по2д3врат
.по2д3в2р
.подвр2а1
.по2д3враћ
.по2д3в2р1ћ
.по2д3в2р1ж
.по2д3в2р1г
.по2д3врис
.подвр2и1
.по2д3в2р1с
.по2д3вућ
.подв2у1
.по2д3иг2р
.под2и1
.по2д3из2в
.по2д3ј
.по3д4јен
.подј2е1
.по3д4јеч
.по2д3лакат
.под2л
.подл2а1
.подлак2а1
.по2д3ла2к1т
.по2д3леп
.подл2е1
.по2д3лет
.по2д3лећ
.по2д3леж
.по2д3лег
.по2д3лиз
.подл2и1
.по2д3лијеп
.подлиј2е1
.по2д3лијет
.по2д3лијећ
.по2д3лијеж
.по2д3лијег
.по2д3лис2т
.по2д3лок
.п2о1дл2о1
.по2д3лом
.по2д3луп
.подл2у1
.по2д3луч
.по2д3луж
.по2д3љут
.под2љ
.подљ2у1
.по2д3о2к1н
.п2о1д2о1
.по2д3ош
.по2д3оч
.по2д3оф
.по2д3ра2в1н
.под2р
.подр2а1
.по2д3ра2в1њ
.по2д3рад
.по2д3ра2з3д
.по2д3раз2р
.по2д3раз2у1
.по2д3рам
.по2д3ран
.по2д3рас
.по2д3рањ
.по2д3реп
.подр2е1
.по2д3рес
.по2д3рез
.по2д3рик
.подр2и1
.по2д3рит
.по2д3рон
.п2о1др2о1
.по2д3ров
.по2д3рож
.по2д3рук
.подр2у1
.по2д3руб
.по2д3руч2и1
.по2д3ру2ч1н
.по2д3руч2а1
.по2д3упл2а1
.под2у1
.подуп2л
.по2д3ус2м
.по2д3ус2н
.пре2д3ј
.пре2д3в
.пре3д4вај
.предв2а1
.пре3д4вар
.пре3д4вес2т
.предв2е1
.пре3д4вој2и1
.предв2о1
.пре3д4вој2а1
.пр2е3д4вој2е1
.пре3д4вор
.пре3д4вос
.пре3д4јен
.предј2е1
.пре2д3иг2р
.пред2и1
.пре2д3ид
.пре2д3из2б
.пре2д3и1сп2и1
.предис2п
.пре2д3ист2о1
.предис2т
.пре2д3ист2р
.пре2д3об2ј
.пред2о1
.пр2е2д3одр2е1
.предод2р
.пре2д3окус
.предок2у1
.пре2д3ос2в
.пр2е2д3ос2е1
.пр2е2д3осј2е1
.предос2ј
.пре2д3рат
.пред2р
.предр2а1
.пре2д3рач
.пре2д3рад
.пре2д3руч
.предр2у1
.пре2д3убеђ
.пред2у1
.предуб2е1
.пре2д3убијеђ
.предуб2и1
.предубиј2е1
.пре2д3убјеђ
.предуб2ј
.предубј2е1
.пре2д3увер
.предув2е1
.пре2д3увјер
.предув2ј
.пр2е1дувј2е1
.пре2д3увјет
.пре2д3угов
.предуг2о1
.пре2д3удар
.предуд2а1
.пре2д3упис
.предуп2и1
.пре2д3усл2о1
.предус2л
.проти2в3а2к1ц
.прот2и1
.против2а1
.проти2в3от2р
.пр2о1тив2о1
.проти2в3оф
.проти2в3р
.проти2в3ус
.против2у1
.проти2в3уд
.ра2ж3ђ
.ра2з3б
.ра2з3е1
.ра2з3и1
.ра2з3ј
.ра2з3л
.ра2з3м
.ра2з3н
.ра2з3љ
.ра2з3р
.ра2з3в
.ра2з3њ
.ра2з3анал
.разан2а1
.ра3з4бан
.р2а1зб2а1
.ра3з4бар
.ра3з4ба3у1
.ра3з4бад
.ра3з4башур
.разбаш2у1
.ра3з4бој
.разб2о1
.ра3з4бор
.ра3з4вал
.разв2а1
.ра3з4в2е1д2е1
.разв2е1
.ра3з4вес2т
.ра3з4виг2о1
.разв2и1
.ра3з4виј2у1
.р2а3з4виј2а1
.ра3з4виј2е1
.ра3з4вит
.ра3з4вић
.ра3з4вој
.разв2о1
.ра3з4вон
.ра3з4врат
.разв2р
.р2а1звр2а1
.ра3з4враћ
.ра3з4в2р1т
.ра3з4в2р1ћ
.ра3з4гађ
.разг2а1
.ра3з4г2р1т
.разг2р
.ра3з4ев
.ра3з4иј
.ра3з4ил
.ра3з4ин
.ра3з4ир
.ра3з4ит
.ра3з4из
.ра3з4иђ
.ра3з4ић
.ра3з4ид
.ра3з4лаз
.р2а1зл2а1
.ра3з4лаг
.ра3з4лик
.разл2и1
.ра3з4лич
.ра3з4лоз
.разл2о1
.ра3з4лож
.ра3з4лог
.ра3з4мет
.разм2е1
.ра3з4мећ
.ра3з4мрс2к
.ра2з3м2р
.разм2р1с
.ра3з4нат
.р2а1зн2а1
.ра2з3об2л
.раз2о1
.ра2з3об2р
.р2а2з3об2а1
.ра2з3од
.ра2з3орат
.р2а1зор2а1
.ра2з3орав
.ра2з3о2р1т
.ра2з3ор2у1
.ра2з3от
.ра3з4ред
.разр2е1
.ра3з4рок
.разр2о1
.ра3з4роч
.ра2з3ув2е1
.раз2у1
.ра2з3уд2и1
.р2а2з3уд2а1
.ра2з3у2д1б
.ра2з3уз2и1
.ра2з3уз2д
.ра2з3уз2е1
.ра2з3улар
.разул2а1
.ра2з3ум2р
.ра2с3ц
.ра2с3к
.ра2с3п
.ра2с3т
.ра3с4как
.р2а1ск2а1
.ра3с4ка2н1д
.ра3с4кин
.раск2и1
.ра3с4клап
.раск2л
.р2а1скл2а1
.ра3с4клањ
.ра3с4клад
.ра3с4клон
.раскл2о1
.ра3с4клоп2и1
.ра3с4клоп2љ
.р2а3с4клоп2а1
.ра3с4кош
.раск2о1
.ра3с4кроп
.раск2р
.раскр2о1
.ра3с4пај
.р2а1сп2а1
.ра3с4пав
.ра3с4пет2и1
.расп2е1
.ра3с4пет2о1
.р2а3с4пет2а1
.ра3с4п2е1т2е1
.ра3с4пик2у1
.расп2и1
.ра3с4пињ
.ра3с4плин
.расп2л
.распл2и1
.ра3с4плињ
.ра3с4п1н
.ра3с4полож
.расп2о1
.распол2о1
.ра3с4пон
.ра3с4пор
.ра3с4прав
.расп2р
.распр2а1
.ра3с4прем
.распр2е1
.ра3с4р1ђ
.рас2р
.ра3с4р1д
.ра3с4р2е1
.ра3с4тај
.р2а1ст2а1
.ра3с4тан
.ра3с4тат
.ра3с4тав
.ра3с4тењ
.раст2е1
.ра3с4тил
.раст2и1
.ра3с4тир
.ра3с4тис
.ра3с4тит
.ра3с4тињ
.ра3с4тој
.раст2о1
.ра3с4трел
.раст2р
.растр2е1
.ра3с4трет
.ра3с4трој
.растр2о1
.ра3с4т2р1т
.ра3с4туп
.раст2у1
.ра3с4тур
.ра3с4тућ
.ра4с5ту2р1ч
.ра2ш3ћ
.ра2ш3ч
.ра3ш4ћењ
.рашћ2е1
.ра3ш4чић
.рашч2и1
.у2з3б
.у2з3д
.у2з3г
.у2з3и1
.у2з3ј
.у2з3л
.у2з3м
.у2з3н
.у2з3љ
.у2з3р
.у2з3в
.у2з3њ
.у3з4бор
.узб2о1
.у3з4ван
.узв2а1
.у3з4ват
.у3з4виж
.узв2и1
.у3з4виј2о1
.у3з4виј2у1
.у3з4виј2а1
.у3з4виј2е1
.у3з4вој
.узв2о1
.у3з4диц
.узд2и1
.у2з3иг2р
.у2з3инат
.узин2а1
.у2з3иск2р
.узис2к
.у3з4лан
.узл2а1
.у3з4лат
.у3з4лим
.узл2и1
.у3з4лит
.у3з4лић
.у3з4лиц
.у3з4лов
.узл2о1
.у3з4лудоб
.узл2у1
.узлуд2о1
.у3з4нак
.узн2а1
.у3з4нач
.у3з4н2е1в2е1
.узн2е1
.у3з4н2е1вј2е1
.узнев2ј
.у3з4нич
.узн2и1
.у3з4ниц
.у3з4ној
.узн2о1
.у2з3обес2т
.уз2о1
.узоб2е1
.у2з3обијес2т
.узоб2и1
.узобиј2е1
.у2з3орат
.узор2а1
.у2з3орав
.у2з3о1х2о1
.у3з4рет
.узр2е1
.у3з4рев
.у3з4ријет
.узр2и1
.узриј2е1
.у3з4ријев
.у3з4р1н
.у3з4р1њ
.у3з4р2о1к2о1
.узр2о1
.у3з4рок2у1
.у3з4рок2а1
.у3з4роч
.у3з4руј
.узр2у1
.у2з3угар
.уз2у1
.узуг2а1
.у2с3ц
.у2с3к
.у2с3п
.у3с4как
.уск2а1
.у3с4клађ
.уск2л
.ускл2а1
.у3с4клад
.у3с4к2о1
.у4с5ком
.у4с5ков
.у4с5кош
.у4с5к2о1к2о1
.у4с5кол2у1
.у4с5кол2е1
.у4с5коп2а1
.у4с5кор2а1
.у4с5кос2и1
.у4с5кот2р
.у3с4куп
.у1ск2у1
.у3с4пав
.усп2а1
.у3с4пал2о1
.у3с4пех
.усп2е1
.у3с4пел
.у3с4пем
.у3с4пет
.у3с4пев
.у3с4пеш
.у3с4пјех
.усп2ј
.успј2е1
.у3с4пјел
.у3с4пјем
.у3с4пјет
.у3с4пјев
.у3с4пјеш
.у3с4пе2н1т
.у3с4пиј2а1
.усп2и1
.у3с4пиј2е1
.у3с4пијуш
.успиј2у1
.у3с4пикуш
.успик2у1
.у3с4пон
.усп2о1
.у3с4пор2и1
.у3с4пор2а1
.у3с4порен
.успор2е1
.у3с4порењ
.у3с4пореч
.у3с4пособ
.успос2о1
.у3с4прем2и1
.усп2р
.успр2е1
.у3с4прем2а1
.у3с4р1к
.ус2р
.у3с4р1н
.у3с4р1п
.у3с4р1љ
.у3с4р1т
.у3с4р1ђ
.у3с4р1ж
.у3с4р2а1
.у3с4р1д
.у3с4р2е1
.у3с4ријед
.уср2и1
.усриј2е1
.у2с3талас
.ус2т
.уст2а1
.устал2а1
.у2с3т2а1р2а1
.у2с3тв2р1ђ
.уст2в
.уств2р
.у2с3тв2р1д
.у2с3тер
.уст2е1
.у2с3тећ
.у2с3тег
.у2с3тов
.уст2о1
.у2с3трај
.уст2р
.устр2а1
.у2с3трал
.у2с3т2р1г
.у2с3треп
.устр2е1
.у2с3трес
.у2с3треб
.у2с3т2р1к
.у2с3т2р1н
.у2с3т2р1п
.у2с3т2р1ћ
.у2с3т2р1ч
.у2с3тум
.у1ст2у1
.у2с3тур
.у2с3тућ
.у2ш3ћ
.у2ш3ч
.а2б3алиј
.а1б2а1
.абал2и1
.а2б3анац
.абан2а1
.а2б3евак
.аб2е1
.абев2а1
.а2б3ерац
.абер2а1
.а2б3ерир
.абер2и1
.а2б3ирит
.аб2и1
.абир2и1
.а2б3ј2у1
.аб2ј
.а2б3л2а1
.аб2л
.а2б3лег
.абл2е1
.а2б3леп
.а2б3лок
.абл2о1
.а2б3л2у1
.а2б3ориг
.аб2о1
.абор2и1
.а2б3реак
.аб2р
.абр2е1
.а1бре3а1
.а2б3рог
.абр2о1
.а2б3узус
.аб2у1
.абуз2у1
.а2д3ерац
.ад2е1
.адер2а1
.а2д3ве2р1б
.ад2в
.адв2е1
.а2д3ј
.а2д3лат
.ад2л
.адл2а1
.а2д3рен
.ад2р
.адр2е1
.а2д3рог
.адр2о1
.а3г2нос
.а2г1н
.агн2о1
.а3г2ноз
.а2набап
.а1н2а1
.а1н2а1б2а1
.а2набаз
.а2набат
.а2наби3о1
.анаб2и1
.а2набол
.анаб2о1
.а2наген
.анаг2е1
.а2нагн2о1
.ана2г1н
.а2н3аг2о1
.а2н2а1гр2а1
.анаг2р
.а2надем
.анад2е1
.а2надип2л
.анад2и1
.а2надоз
.анад2о1
.а2н3а4е2р2о1
.ана3е1
.а2накал
.а1н2а1к2а1
.а2накам
.а2накат
.а2накеф
.анак2е1
.а2н2а1кл2а1
.анак2л
.а2накл2и1
.а2накој
.анак2о1
.а2н3акуз
.анак2у1
.а2н3а2л1г
.а2н3а2л1д
.а2налеп
.анал2е1
.а2нализ
.анал2и1
.а2налис
.а2налит
.а2н3аме2р1т
.анам2е1
.а2намн2е1
.ана2м1н
.а2н3анд2р
.ана2н1д
.а2нане3о1
.анан2е1
.а2н3а2н1т
.а2н2а1пл2а1
.анап2л
.а2напл2е1
.а2напн2е1
.ана2п1н
.а2напн2о1
.а2напр2о1
.анап2р
.а2напт2и1
.ана2п1т
.а2н3апт2о1
.а2на2р1т
.а2н3а2р1х
.а2насар
.анас2а1
.а2насе3и1
.анас2е1
.а2наспаз
.анас2п
.анасп2а1
.а2н2а1ст2а1
.анас2т
.а2настиг
.анаст2и1
.а2настом
.анаст2о1
.а2натим
.анат2и1
.а2натом
.анат2о1
.а2натоц
.а2натр2е1
.анат2р
.а2натр2и1
.а2натр2о1
.а2нафаз
.анаф2а1
.а2н3афиј
.анаф2и1
.а2н2а1фил2а1
.а2нафон
.анаф2о1
.а2н3афрод
.анаф2р
.анафр2о1
.а2накол
.а2накрон
.анак2р
.анакр2о1
.а2накр2у1
.а2н3а1лф2а1
.ана2л1ф
.а2нафор
.а2нахор
.анах2о1
.а2нахр2о1
.анах2р
.а2н3егер
.ан2е1
.анег2е1
.а2н3ек2л
.а2н3екум
.анек2у1
.а2н3елек
.анел2е1
.а2н3енер
.анен2е1
.а2н3еп2и1
.а2неор
.ане3о1
.а2н3е2р1г
.а2н3ерит
.анер2и1
.а2н3е1ст2е1
.анес2т
.а2н3ид2р
.ан2и1
.а2н3изог
.аниз2о1
.а2н3изом
.а2н3изур
.аниз2у1
.а2н3ирид
.анир2и1
.а2н3овар
.ан2о1
.анов2а1
.а2н3о2к1с
.а2н3опис
.аноп2и1
.а2н3о2р1х
.а2н3о2ф1т
.а2н3о2р1г
.ди2с3акор
.д2и1
.дис2а1
.дисак2о1
.ди2с3ју2н1к
.дис2ј
.дисј2у1
.ди2с3квал
.дис2к
.диск2в
.дискв2а1
.ди2с3ко2н1т
.диск2о1
.ди2с3ко2р1д
.ди2с3кр2е1
.диск2р
.д2и2с3кр2и1
.ди2с3кур
.диск2у1
.ди2с3л2о1
.дис2л
.ди2с3ориј
.дис2о1
.дисор2и1
.ди2с3парит
.дис2п
.дисп2а1
.диспар2и1
.ди2с3поз
.дисп2о1
.ди2с3пон
.ди2с3проп
.дисп2р
.диспр2о1
.ди2с3тон
.дис2т
.дист2о1
.ди2с3трак
.дист2р
.дистр2а1
.и2н3абруп
.ин2а1
.инаб2р
.инабр2у1
.и2н3адек
.инад2е1
.и2н3акур
.инак2у1
.и2н3акц2е1
.ина2к1ц
.и2н3амор
.инам2о1
.и2н3аниц
.инан2и1
.и2н3аплик
.инап2л
.инапл2и1
.и2н3апс2т
.ина2п1с
.и2н3а2р1т
.и2н3аугур
.ина3у1
.инауг2у1
.и2н3а1ур2а1
.и2н3афек
.инаф2е1
.и2н3евид
.ин2е1
.инев2и1
.и2н3ег
.и2н3ед
.и2н3ек2в
.и2н3е2к1с
.и2н3елиг
.инел2и1
.и2н3е2п1ц
.и2н3ефек
.инеф2е1
.и2н3об2л
.ин2о1
.и2ноген
.иног2е1
.и2нокор
.инок2о1
.и2н3окуп
.инок2у1
.и2н3опер
.иноп2е1
.и2н3опор
.иноп2о1
.и2н3опс2е1
.ино2п1с
.и2н3офиц
.иноф2и1
.и2н3умб2р
.ин2у1
.ину2м1б
.и2н3унд2а1
.ину2н1д
.и2н3у2н1к
.и2н3утил
.инут2и1
.и1нте2р3и1
.и2н1т
.инт2е1
.инте2р3о1
.инте2р3у1
.инте2р3а1
.инт2е2р3е1
.инте3р4е2г1н
.и1нте3р4ес2и1
.инте3р4ес2н
.инте3р4ес2о1
.инте3р4ес2у1
.инте3р4ес2а1
.инт2е3р4е1с2е1
.инте3р4е2ж1џ
.инт2е3р4иј2е1
.инт2е3р3ј2е1
.инте2р1ј
.инте3р4огат
.интерог2а1
.јури2с3к
.ј2у1
.јур2и1
.јури2с3п
.ну2з3бел
.н2у1
.нуз2б
.нузб2е1
.ну2з3биљ
.нузб2и1
.ну2з3љуб
.нуз2љ
.нузљ2у1
.ну2з3р2е1
.нуз2р
.ну2з3р2ј2е1
.нуз2р1ј
.ну2з3уж
.нуз2у1
.ну2с3пос
.нус2п
.нусп2о1
.ну2с3пр2о1
.нусп2р
.по2ст3е2г1з
.пос2т
.пост2е1
.по2ст3инд2у1
.пост2и1
.пости2н1д
.по2ст3лим
.по2с3т2л
.постл2и1
.по2ст3о2н1к
.п2о1ст2о1
.по2ст3опер
.постоп2е1
.су2б3а1
.с2у1
.су2б3л
.су3б4аш
.су2б3и2н1в
.суб2и1
.су2б3ју2н1к
.суб2ј
.субј2у1
.су2б3о2к1с
.суб2о1
.су2б3реп
.суб2р
.субр2е1
.су2б3рог
.субр2о1
.су2б3о2р1д
.супе2р3и1
.суп2е1
.супе2р3о1
.с2у1пе2р3у1
.супе2р3а1
.суп2е2р3е1
.супе3р4иор
.супери3о1
.тр2а1н2с3а1
.т2р
.тр2а1
.тра2н1с
.тран2с3ц
.тран2с3е1
.тран2с3к
.тран2с3л
.тран2с3м
.тран2с3н
.тран2с3о1
.тран2с3п
.тран2с3т
.тран2с3у1
.тран2с3в
.тран2с3њ
.тран3с4еп
.тран3с4кр2и1
.транск2р
.тран3с4ум
.тран3с4уд
.н8а9д8н8о.
.надн2о1
.н8а9т8к8а.
.н8а9т8к8а9т8и.
.наткат2и1
.н8а9т8к8а9ш8е.
.наткаш2е1
.о9д8н8о.
.о2д1н
.одн2о1
.о9т8к8а.
.о9т8к8а9т8и.
.откат2и1
.о9т8к8а9ш8е.
.откаш2е1
.п8о9д8н8о.
.подн2о1
.п8о9д8н8е.
.п8о9т8к8и.
.потк2и1
.п8о9т8к8у.
.потк2у1
.п8о9т8к8а.
.п8о9т8к8е.
.потк2е1
.у9д8н8о.
.у2д1н
.удн2о1
.и9г8д8е.
.и2г1д
.игд2е1
.и9г8д8ј8е.
.игд2ј
.игдј2е1
.с8в8у9г8д8е.
.с2в
.св2у1
.сву2г1д
.свугд2е1
.с8в8е9г8д8е.
.св2е1
.све2г1д
.свегд2е1
.с8в8у9г8д8ј8е.
.свугд2ј
.свугдј2е1
.с8в8е9г8д8ј8е.
.свегд2ј
.свегдј2е1
.п8о9н8е9г8д8е.
.пон2е1
.поне2г1д
.понегд2е1
.п8о9н8е9г8д8ј8е.
.понегд2ј
.понегдј2е1
.и9з8б8и.
.и9з8б8а.
.и9з8б8е.
.и9з8б8и9ц8и.
.избиц2и1
.и9з8б8и9ц8а.
.избиц2а1
.и9з8б8и9ц8е.
.избиц2е1
.и9з8в8и8т.
.и9з8и8м.
.и8з8о9б8а9р8и.
.изобар2и1
.и8з8о9б8а9р8у.
.изобар2у1
.и8з8о9б8а9р8а.
.изобар2а1
.и8з8о9б8а9р8е.
.изобар2е1
.и9с8к8о8к.
.и9с8к8о9к8у.
.искок2у1
.и9с8к8о9к8а.
.искок2а1
.и9с8к8о8н.
.и9с8к8о9н8и.
.искон2и1
.и9с8к8о9н8у.
.искон2у1
.и9с8к8о9н8а.
.искон2а1
.и9с8к8р8и.
.и9с8к8р8у.
.искр2у1
.и9с8к8р8а.
.и9с8к8р8е.
.и9с8к8р8а8в.
.и9с8п8о8д.
.и9с8п8о9д8а.
.испод2а1
.и9с8т8р8и.
.и9с8т8р8о.
.истр2о1
.и9с8т8р8у.
.и9с8т8р8а.
.и9с8т8р8е.
.истр2е1
.н8а9ј8и.
.н8а9ј8о.
.н8а9ј8у.
.н8а9ј8а.
.н8а9ј8е.
.н8а9ј8а8м.
.н8а9ј8е8с8т.
.о9б8р8а8з.
.о9б8р8е8т.
.о9д8в8и9к8а.
.о9д8р8а8н.
.о9д8р8а9т8и.
.одрат2и1
.п8р8е9д8в8о8ј.
.р8а9з8м8и.
.разм2и1
.р8а9з8м8о.
.разм2о1
.р8а9з8м8у.
.разм2у1
.р8а9з8м8а.
.разм2а1
.р8а9з8м8е.
.р8а9з8н8и.
.разн2и1
.р8а9з8н8о.
.разн2о1
.р8а9з8н8у.
.разн2у1
.р8а9з8н8а.
.р8а9з8н8е.
.разн2е1
.р8а9с8к8л8о8п.
.р8а9с8п8е9л8о.
.распел2о1
.р8а9с8п8е9л8у.
.распел2у1
.р8а9с8п8е9л8а.
.распел2а1
.р8а9с8п8е9ћ8у.
.распећ2у1
.р8а9с8п8е9ћ8а.
.распећ2а1
.р8а9с8п8е9ћ8е.
.распећ2е1
.р8а9с8т8и.
.р8а9с8т8у.
.р8а9с8т8а.
.р8а9с8т8е.
.р8а9с8т8о8м.
.р8а9с8т8е8р.
.р8а9ш8ћ8и.
.рашћ2и1
.р8а9ш8ћ8о.
.рашћ2о1
.р8а9ш8ћ8у.
.рашћ2у1
.р8а9ш8ћ8а.
.рашћ2а1
.р8а9ш8ћ8е.
.у9з8н8и.
.у9з8н8о.
.у9з8н8а.
.у9з8н8и8к.
.у9з8р8о8к.
.у9с8к8и.
.уск2и1
.у9с8к8о.
.у9с8к8у.
.у9с8к8а.
.у9с8к8е.
.уск2е1
.у8с9к8о8с.
.у9с8п8и8о.
.успи3о1
.у9с8п8е8о.
.успе3о1
.у9с8п8о8р.
.у9ш8ћ8у.
.ушћ2у1
.у9ш8ћ8а.
.ушћ2а1
.у9ш8ћ8е.
.ушћ2е1
.и8н9т8е9р8е8с.
.т8р8а8н9с8у.
.т8р8а8н9с8а.
.т8р8а8н9с8о8м.
PK
!<5hyphenation/hyph_sl.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.av5r
.a1v
.di6spo1
.di1s1
.di1sp
.ek3s
.e1k
.ek5v
.i1s1
.i1z1
.obi4d
.o1b
.obi1
.ob5i1t
.o1d1
.po4d5n
.po1
.po1d
.po4v5s
.po1v
.pre6d7n
.pre1
.pre1d
.se4k5s
.se1k
.si4s1
.si1
.st4
.voz5l
.vo1z
.vo1z5n
.z1li1z6
.z1l2i1
a1a
a1b
ab5ba
ab6ro1d
abro1
a1c
ac5ci
a2cc
a1č
a1d
ad2l
a6dobl
ad2o
ado1b
ad6rl.
ad2r1l
ad6rla
ad6ro1b
adro1
ad5ur
a1e1
a1f
af5ga
a2f1t
a1g
a1h
a4hm
ah5mi
ah5mo
a1i
ai2n1
a1j
a4j5e1k
a4jf
aj5fi
aj5fo
aj5ha
a2jh
aj5he
aj5i1m
aj6imo
a2j3o1s
aj6s2t1b
a2j1s
a5ju.
aju1
a2j3u1č
aj3u1g
aj5žn
a1k
ak4s
a4ks1t
a1l
a1m
a4mz
a1n
an6d5ga
a2n3d2
an2d1g
an6d5hi
an2d1h
a4nm
an5mi
an5z4i
a2n1z
a1o
ao2b1
a1p
a4ph
a1ra
ar6d1wa
a2r1d
a1re1
a1ri1
a1ro1
a1ru
ar5xa
ar5xo
ar5xu
a1s
a4sš
as5š2č
a1š
a1t
a4t1f
at4i
a1u1
a4u1f
a2uk
a4u1l
a1v
av5ši
a4vž
av5ža
ay5to
a1ze
az5fo
a4z1i1g
a1zi
az3la
az3le1
a3z4li1l
az1l2i1
a5z4li1t
a5z4li1v
a4z1o1b
a1zo
a4z3o1č
az5o1ra
az5o1ro1
a4zra
a1z1r
az4re1d
azre1
az5v1p
az1v2
a1ž
a4ž5mi
ba6b5ba
ba1b
ba2n3č4
ba1n
ba4u1
2b1c
2b1č
2b1d
be1
be4v
b1h
bi1
b1ja
b4ja.
b5je1l
b3je1m
b5je1t
2b1k
b3le1p
ble1
b5leta
ble1t
b5li1l
bl2i1
b5li1t
b5li1v
b1m
4bmi
2b1n
bo1
bo6chm
bo1c
b5o2r1d
bo5v1p
bo1v
b3ra1b
b5ra1s
b3ra1š
b3re1z
bre1
bre4z2g
bre4zi
bre4z1r
b5reže
bre1ž
b3ro1b
bro1
br6ž5da
br1ž
2b1s
2b1š
2b1t
bu5ki1
bu5ku
bu5kv
bu5ry
2b1v
b1z
b1ž
2cc
2ch.
ch5ma
2ck
c1ka
ck1o2
c5ko.
cko1v3
ck1s
ck5we
2c1n
2c1t
2č1b
2č1g
či1
1čj
2č1k
1čl
4č3le1t
čle1
č5me1s
2č1n
4čo1p
2č1p
2č1s
4ču1p
2d1b
2d1c
2d1č
2d1d
dd6vo1j
d2d1v
d2e
6d5ele1m
de1l
dele1
de4mi1n
de1m
de4mn
de4z3i
de1z
2d1g
2d1h
di5ck
di1c
4d1i2n3d2
di1n
d4i5no
di1s1
di4skr
di6spr
di1sp
2d1j
2d1k
5dle1t
dle1
d2l2i1
d5li1t
d5li1v
d1lo1
2d3m
4d3na1c
dna1
4d5na1č
4d5na1p
4d3nar
4d1na1s
4d5ne1b
dne1
d5ni1v
4d5ni1z
4d5nja1č
4d3no1ž
d2o
4do2b1č
do1b
4d5o2b1d
2d3o2f
do5r1d
do5vč
do1v
do5v4z2
2d1p
d5raz
d3re1p
dre1
dre6pn
d4re1v
2d1s
2d1š
2d1t
dteks6
dte1k
d4ur
du5ro1
du5u1m
2d1v
4d3vi
2d1z2
e1a
e1b
eb4j
eb6li1z
ebl2i1
e1c
e1č
e4čd
eč5d2e
eč5di
eč5d2o
eč3le1
e1čl
e4č5o1p
e4čt
eč5ti
eč5to
eč5tr
e4č5u1p
e2č1v
eč6v2r1s
e1d
e4df
ed5i1g
ed2l
ed5o1b
ed2o
ed6obe1
ed6obr
e4do2b1s
e4d3o1č
ed5vč
e2d1v
ed5z1b
e2d1z2
e1e
e4e1p
e1f
e4ff
ef5fe1
ef5ta
e2ft
e1g
e1h
e1i
ei6p1zi
ei1p
ei2z
eiz5e
e1j
e1k
ek6ma1l
e2k1m
ek6tre1
e2k1t
e3ktr
e1l
e1m
e1n
e1o1
eob4j
eo1b
eob4r
eo4dl
eo1d
eo4z5n
eo1z
e1p
ep5ni1k
e1ra
e3ra6z5l
era5z4r
era5z4v2
e1re1
e4r1f
e1ri1
e1ro1
e4r1r
e1ru
e1s
es5da
e5sta
e5sti.
e5sti1h
e5sti1l
e1š
e4šp
eš5po1
e1t
4eth
e4t1i2n1š
eti1n
e1u1
e1v
eve6t5l
eve1t
ev5ha
ev6pre1
e2v1p
ev6ste
ev5stv
2ew
ew6i2n3d2
ewi1n
ew5le1
e4wt
ew5to
e4y1w
e1z
ez5d1j
e2z1d2
e3z4dr
ez2g
ez5gl
e5zi1j
e1zi
ez6ijo
ez5imn
ezi1m
e5z1i1s
ez6ist
ez5i1z
ez4l
ez6lo1m
ezlo1
ez6ma1n
e2z1m
ez4mo
e4z1o1b
e1zo
e4z5or
ez4re1
e1z1r
e4z1t
e4z5u4m5
e1zu
e4z1ž
e1ž
1fa
fe1
fe6l2j1t
fe1l
ff5ma
fi6zlj
fi1z
fiz1l
2f1n
fo6u1ri1
fo1u
fre4u1
fre1
2f1s
2ft
ft5ve
fu1
2g1d
ge6ige
ge1i
gei1g
ge2l5č4
ge1l
ge6n2j1č
ge1n
gi6t5pr
gi1t
go1
go5v1z2
go1v
2g1t
gu1
ha4u1
2h1č
he4i
2h1k
4hl.
h4lo1
2h1n
h5re1n
hre1
2h1š
2h1t
1hu
hu6ffm
hu1f
i1a
i1b
i1c
i4cs
i1ča
i1če
i1či1
ič5ra
i1ču
ič5vr
i1d
4idor
id2o
i1e1
i1f
i1g
4igh
i1h
i1i
ii2n1
i1j
i1k
i4kč
ik5ča
i1l
il5č4k
i2l1č
4ile1
4ilo1
i1m
i4mh
im5hi
i1n
1i2n3d2
2ine1
3i4n3o1s
1i2n1p
3inse
i2n1s
1i2n1š
4inšk
3intr
i2n1t
i1o1
i1p
i1r
4ire1
i1s
is4a
is6e2r1t
i1si1s4
isi1
i4skv
2i2s1s
i1š
i1t
it5pr
i1u
i1v
i4v5jo
i2v1j
i1x
i1z
iz1l
iz4la
i1z1li4z5
iz1l2i1
iz5me
i2z1m
iz5mo
iz6od2e
i1zo
i2z1o1d
iz5po1
i2z1p
i2z1r
i1z1u
iz6u1re1
i1ž
j5a2k1t
ja1k
2j1b
2j1c
2j1č
2j1d
je4ks4
je1k
2j1g
2jh
j1hi
4jime
ji1m
4j5i2n1t
ji1n
2j1k
2j1l
2j1m
2j1n
4jo1b
2j1o1d
jod4l
2jo1s
4jo1ž
2j1p
2j1r
jra1
jraz4
2j1s
jsis6t
jsi1
jsi1s1
2j1š
2j1t
ju1
2ju1č
ju5d3m
ju1d
2ju1s
ju2ž1
2j1v
2j1z
jz6ve1s
jz1v2
2k1c
2k1d
ke5ti
ke1t
ki1
2k1m
1kn
ko1
ko1k4
ko5k1d
ko6vše
ko1v
koz6lo1
ko1z
1kre1
2ks.
k5sa1t
k1s1c
k1s1p
ks4po1
ks1t
4k2st.
ks6taz4
k3s5te
2k1t
3ktr
4ktra
ku5ro1
k5vi1p
la4i1r
la1i
la6vz.
la1v
lavz2
2l1b
2l1c
2l1č
2l1d
le1
le4e
le6ipz
le1i
lei1p
le5me
le1m
2l1f
2l1g
lg5ča
2l1h
l2i1
li6d8ž.
li1d
1li1z
4l5i2z1d2
2lj.
4l2j1c
2l2j1č
2l2j1k
2l2j1n
2l2j1s
2l2j1š
lju5d6j
lju1
lju1d
2l1k
2l1l
2l1m
2l1n
lo1
1lo1č
2l1p
2l1s
2l1š
2l1t
lu5ki1
lu5ku
2l1v
2l1z
2l1ž
2m1b
2m1c
2m1č
2m1d
me4d5n
me1d
me6do1s
med2o
me4dr
2m1f
4m1i2n3d2
mi1n
4m1i2n1p
4m1i2n1š
mi6th.
mi1t
2m1k
2m1m
m5ni1v
mo6št.
mo1š
mo6v8š.
mo1v
2m1p
2m1s
2m1š
2m1t
m5u2r1n
2m1v
my5hi
2m1ž
na1
5nače1l
na1č
na4d5nj
na1d
nad5r
na6dra
na4dre1
na6d5ur
1na1j
na6ja1k
na4j5e1n
naj3o
na6jo1č
na4j3u1
1na1s
na4v3z2
na1v
navze6
1naz
naz6or
na1zo
2n1b
2n1c
2nč
n1ča
n1če
n1či1
n1ču
2n3d2
nd5ga
n2d1g
nd5hi
n2d1h
n4d3m
ne1
ne3d2
1ne1h
ne3z1m
ne1z
nez4v2
2n1f
2n1g
n4gh
ng5ha
n4gv
ng5vi
2n1h
2nj.
2n2j1c
nje4v5s
nje1v
2n2j1k
2n2j1s
2n2j1š
4n2j1v
2n1k
2n1l
2n1n
no5r1d
n4ost
no1s
2n1p
2n1s
nsi1s4
nsi1
2n1š
2n1t
nteks4
nte1k
n4tg
nt5ga
nt5ge
n4tv
nt5vi
nu1
2n1v
ny5qu2
2n1z
n1z4i
2n1ž
o1a
o4a1s
o1b
ob5gl
ob5id2e
obi1
obi1d
ob5jo
5obla
5obro1
o4b1z
o1c
oc5ke
o2ck
oc5ki1
o4cr
o1č
o1d
od5d1v
o2d1d
od5na1l
odna1
o6d3re1p
odre1
od5z1d2
o2d1z2
o2d1ž
o1e
oele4
oe1l
o1f
o1g
4ogl
o1h
o1i
oi1z2
o1j
o1k
o4kb
ok5ba
ok5be1
o4k1t
o1l
o6l5a2v1t
ola1v
ol6g5ča
o2l1g
o4lr
ol5re1
o1m
o1n
o1o
ood4l
oo1d
o2o1l
o4o1m
o1p
o4pm
op5me
4opy
o1ra
or4de1č
o2r1d
ord2e
o1re1
o1ri1
o1ro1
o1ru
o1s
5ose1b
ose4m5
o1š
o1t
o1u
ou5ki1
ou5ku
o1v
ov5se1m
o4v5šk
o2v1z2
o5v2z2a
ov3z1d2
o1y
o1z
o2z4b
oz2d5j
o2z1d2
oz4g
oz5lo1
o3z6lo1ž
o1z2n
oz5ni1c
oz5ni1š
o1z2o
o1z2r
oz2v2
o1ž
o4ž5mi
2p1c
2p3č2
pč5ka
p2č1k
pe1
1pe1č
pe4k1t
pe1k
pet3l
pe1t
pe4tle1
pe4v5s
pe1v
pe2v5t4
4phs
ph5so
pi5zo
pi1z
2p1k
4plo1z
plo1
po1
po6d1fa
po1d
po4d3l
po4dna1
po4d5o1č
pod2o
po6lo1b
po1l
polo1
po6s2t1d
po1s
pre1z4
pre1
2p1s
2p1š
2p1t
pz6ig.
p1zi
pz1i1g
qu2
3raču
ra1č
2ra1e1
ra6j5žn
ra1j
rav5z2
ra1v
ra6v2z2a
ra4z5i1d
ra1zi
3razl
ra4z5or
ra1zo
2r1b
2r1c
2r1č
2r1d
re1
3rea1l
re1a
re6c2h1t
re1c
re5č1v
re1č
5re2d1č
re1d
re6d5i1g
re6dnju1
re6iba
re1i
rei1b
re5jo
re1j
re5k1m
re1k
re6s5da
re1s
rev6sk
re1v
re6zna1č
re1z
re1zn
rezna1
re6zu1s
re1zu
re6zve
rez1v2
r1f
2r1g
2r1h
ri1
r4i1n
ri5n4o
riz4g
ri1z
riz4l
ri1z4n
2r1j
2r1k
2r1l
2r1m
2r1n
ro1
rob6i1d
ro1b
robi1
3rodi
ro1d
ro5z2o
ro1z
2r1p
r1r
2r1s
2r1š
2r1t
r4th
rt5ha
ru5kl
2r1v
r3v2j
r4v5jo
ry5a1n
2r1z
rz2l
r1ž
rž5da
2s1b
1sc
4sc.
s2ci
se4k5sa
se1k
sek5si1
se5ma
se1m
se5v1p
se1v
2s1f
si1
s4i1d
si6gn.
si1g
si1s1
2s1j
2sk.
s2kn
4s1kre1
s4la1v
s4o1n
soni5
sonič4
1sp
s4plo1d
splo1
spo4d4l
spo1
spo1d
2s1s
2st.
3ste
s4te1n
4st1f
s4tič
5sti1m
s4ti1r
2s2t1k
2s2t1m
1str
s4tra.
su1
su4bo1
su1b
sve5t
š2č
2š8č.
2š2č1k
2š2č1n
še2s
2š1j
ta5wi
taz4
2t1b
2t1c
tch5o
2t1d
tek6s1t
te1k
5tema
te1m
te5xa
t1f
4t1i2n3d2
ti1n
4t3i4n3o1s
4t1i2n1p
4t3inse
ti2n1s
4t3i2n1t
2t1k
6tletno
tle1
tle1t
2t1m
4t1na1j
tna1
to6v8ž.
to1v
trt5u1
t2r1t
tr6tur
2t1s
2t1t
tu1
4tz.
2u1a
u1b
ub4j
u4bp
ub5po1
u1c
u1č
u1d
ud6mi.
u2d3m
u1e
u1f
u1g
u1h
u1i
u1j
u1ka
u1ke
u1ko1
u1l
u1m
u1n
u1p
up6č5ka
u2p3č2
up2č1k
u1ra
u1re1
4u2r1g
u1ri1
u1s
1u1sp
u1š
uše3s
u1t
u4th
uth5o
u1v
ux5e1m
u1z
u1ž
2v1b
2v1c
2vč
v1ča
v1če
v4čer
v1či1
2v1d
ve4čl
ve1č
ve4čm
ve4i
ve4ti1n
ve1t
ve1tle6t
vetle1
v1f
v1g
vi5d1v
vi1d
vid6va
1vi1v
vi6žg.
vi1ž
2v1j
4vjo
2v1k
2v1m
2v1n
vo5r1d
voz5le1
vo1z
2v1p
3v2pa
v4pi1j
v4pi1l
v5s2kn
v5še1k
4všk
2v1t
v2t4k
vz2
v2z2a
3v2z1g
2v3z1k
2v1zo
v3z1p
v2zu
1wa
wo2
x1f
1ye
2y1f
y1j
y1l
y1w
1z2a
z6ane.
za1n
zane1
za5uk
za1u1
za3v1p
za1v
za1z2
za5z1d2
2z1b
3zbi1r
zbi1
z1c
2z1č
2z1d2
zd5ju1
z2d1j
z3d1v
z1g
z4gni
z5go1t
zgo1
2z1h
1zi
z1i1g
2z1i1s
4z5iš2č
zi1š
2z1j
2z1k
z3ku
z5la1s
z1l2i1
3zli1l
5zli1t
5zli1v
z1li1z5
1zlj
3zlo1g
zlo1
z5lo1m
3zlo1ž
z1lu
2z1m
1zn
1zo
z1o1b
2z1o1d
z1o1g
z2o1l
z4o1m
2z1p
1z1r
4z5re2d1č
zre1
zre1d
4zre1š
4zre1z
4zre1ž
4zri1
4zru
2z1s
z1š
z1t
1zu
z4u1j
2z1u1p
2z1u1z
z1v2
z4ve1n
z3v1n
3z4vo1j
z4vo1k
2z1z2
z1ž
2ž1b
2ž1c
2ž1č
2ž1j
2ž1k
4žmi
.č8
.š8
.ž8
8ž.
8š.
8č.
PK
!<G(hyphenation/hyph_sv.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
.a4b2
.a3b5i
.ab5ol
.a3bo
.ab3r
.ac3
.a4d
.a3d2r2
.a4d3s
.a5g4ra
.ag2r4
.a5g1re
.a5kl2
.a5le
.al4p2r2
.a3lu
.am4br
.a2m1b2
.am2p3l
.a2m1p
.a5mu
.and4rar
.a2n1d
.an2d2r2
.and1ra
.a2n5es
.a1ne
.ang4er
.a2ng1
.an5g2o
.a4n5s
.a2p1p
.as2k
.a3sket
.as1k2e
.as4t
.a5sten
.as3t2e
.a3s1ti
.a5ta
.a1t
.a2t3t
.au3s2t
.a4val
.a1va
.a2v3s4
.b4
.ba2k5s
.be4n5s
.bil4d3s
.bi2l2d
.bo2k
.bo2r1t1
.cis4
.c2
.cy5klop
.c2y
.cykl2
.d4
.dä2r3
.ek1v4
.e3l4a
.e2l5in
.e1li
.en5s2t
.e4ns
.e4nä
.e2r3i
.e2s
.e5ska1d
.es2k
.es1k2a
.es3kal
.e1s5kap
.es4t
.e5stra1d
.es4t2r4
.es2t1ra
.e3t2r4
.evan5
.e1v
.ex3
.f4
.fe2b3r
.f4e1b2
.fra2m3
.f1ra
.fre4s5
.f1re
.fåge2l3
.få1ge
.fö2r1a
.fö2r1en
.fö1re
.g2
.gu4lä
.g2u
.gus3
.he2m
.hu5sa
.hu2s
.i1b4
.ik4
.i2m3p
.i2n1
.i4na
.i2n3d
.in4ger
.i2ng1
.i2nk2
.i4n3s2
.i2n3t
.is5k2a
.i2sk
.i3so
.k4
.kans4k
.k2a
.ka4ns
.ko5li
.k2o
.kor2t5s
.ko2r1t
.kri2ng3
.k2r4
.krin2g2s2
.kö2p5s
.l2
.lak5r4
.lek5t2r4
.le4k1t
.lu2s2t
.m2
.mas2k2e
.ma5ske1ri
.me4re
.minis4
.mi3n2i
.mjöl2k5s
.mjö2l1k
.mo4n2s
.må4n3s
.må4s4t
.må1s
.män5sk2o
.mä4ns4
.mäns2k
.mörk5r4
.mö2rk
.n4
.ner1
.no4n
.nö2d5r2
.nö2d
.oc3ku
.oc2
.o4ck
.o4k3t
.o1k
.o3kv4
.o2ma
.o2mo
.o4m3s4
.o3mu
.o2n4k4
.o3o
.or4d3s
.o2r2d
.o5s2c2
.o1s4k
.o3sl2
.o3s2t1ra
.os4t2r4
.os2t
.o3s2v2
.o3t2r4
.o1u
.p4
.pap2p5s
.pa2pp
.pa3s3t2e
.pa2s2t
.pa5s1ti
.pi5s1ti
.pis2t
.pre4s2s
.p2r2
.p1re
.pub3li1k2a
.pu2b2
.pub1li
.r2
.re4g2r4
.re1g
.re2s3t2e
.res2t
.ru4ns4
.ry2m2d
.r2y
.rö1ve5
.rö1v
.s4
.sa2k
.seg3r4
.se2g
.si5o
.sjö1
.s2j
.sk4
.skot2t3s
.sk2o
.sko2tt
.s4lu2t3s
.sl2
.s2lu
.s2t4
.sta2m
.s1ta
.ste4n3s
.s3t2e
.st1ri2ng4
.st2r4
.s2tri
.su2p3p
.t4
.ta3b1l
.ta2b2
.ta4k
.tak5l2
.tes3ta
.t2e
.tes2t
.ti2g3r4
.ti1g
.til4l
.t2il
.ti3o
.top2p5s
.t2op
.to2pp
.tre4d2s
.t2r4
.t1re
.tre2d
.tre3s
.tr4ä5k
.u3k
.ul2t5r4
.u4l1t
.ung2e
.u2ng1
.up2
.u4ra
.u2r3s
.u2t1
.u4ta
.u5t4rer
.ut3r4
.ut1re
.u2t5s
.v2
.va2r4t
.vatte2n3
.va1t
.va2tt
.vat3t2e
.ve4d5s
.ve2d
.vä2g
.vä2g3s
.x2
.y2a
.y4e
.å2ng3
.å2r4s5
.å3s4t
.åter1
.å3t2e2
.ä3ro
.ö3ro
a2b2
ab4bu
a4b2b2
a5be
abe2l4s
abe2s
ab1l
ab3la
ab3o2r1t
a3bo
ab5ric2
ab3ri1o
ab4s2c2
a4b3s
ab4sk
a5bu
ac4ke2s
ac2
a4ck
ac3k2e
ac4kis
ac3ki
ack3s2k4
ac2ks
ack3u4p
a5dag
a1d
a3da
a5de1k4
a3d2e
a5del
ad5ep
a2d3j
ad3op
a3do
a5dran
a2d2r2
ad1ra
a3d1re
1a5dres
ad3ril
ad3ru
a4d2s
a5e2d
af4fo
af2
a2f2f2
3af3fä
1af3ri
af4tor
a2ft
af3to
a1ga
ag2a4ra
a1ge
a2ge.
ag1g2r4
a2gg
a2g1l
a2g5o2r2d
a1g2o
ag3ro
ag2r4
a4gur
a1g2u
a4hj
a1h
ai1b4
a3iv
a1j
a3k2a
a4ka2r1t
a5k2e
a1ki
a2k3n
a1k2o
ak5ram
ak2r4
ak1ra
akri5s
ak3ro3b2
ak4s1ta
a2ks
ak1s4t
1akti1g
a4k1t
ak1ti
ak3tri
ak2t2r4
a1ku
a5kva1ri
akv4
ak3ve
a5kår
ak5åt
4akö
a1la
a2l5a2d1m
ala1d
ali2br
a1li
ali1b2
a2lin
a5lin.
a3li1ne
al3i4ns
ali5stik
alis2t
alis1ti
a4lj
al2k3ak
a2l1k
al1k2a
al2kv4
al4kä
all3s2t
al2l1s
al3lå
alms4k
a4l1m
al4ms
a1lo
al5o2r1t
als5pa
a2l1s
al2s2p2
al3t2r4
a4l1t
al4tu4
al4tä
a1lu
alu5s
al2v3s
a2lv
a1l2y
a4maf2
a1ma
a1m4i
am4p2r2
a2m1p
a4m1s
am3åt
a3mö
ana4bo
a1na
ana2b2
a2n3a2l1f2
a2n3a2rk
a2n3c2
an1ci5
an5dak
a2n1d
an3da
ande2l2s
an5d2e
an4dun
an3du
an4dän
a4ne1f2
a1ne
ang4es
a2ng1
an3g2i
an1g2r4
an4i1u4
a1n2i
ank3r4
a2nk
ano2i
a1no
a4no1k
a4nop
an5s1c4e
a4ns
ans2c2
ansis4t
an1s2i
an4s2j
ans5ku
ans2k
ans3li
an2sl2
ans3par
ans2p2
ans1pa
an1s2t
an4sto
an4s4t2y
1ansvar
an1sva
ans2v2
an4t2j
a2n1t
an4t1re
ant2r4
a1nu
a5n2y
a3nö
a1o
a1pe
a2pe.
ape4n3
a1pi
ap4lan
ap2l
apo3s4t2r4
a1po
a3pos
apos2t
1app2a1ra
a2pp
ap1pa
apps4k
ap2ps
ap3ric2
ap2r2
ap3rif2
a5pris
a2p2s
ap3se
ap2s5l2
aps3p2
apu5s
a1pu
a5p2y
a5pä
2a1ra
a4ra2n1n
a4ra2r1v
a3rar
1a2r1b2
4ar3bi
2ar3bo
4arbr
ar3d2r2
a2r2d
ard5s2t
ar4ds
a4re2n1d
a1re
arg5si
a2r1g
ar2g2s
2a2r1h
a1ri
a4rigen
ari1g
ar3k2a
a2rk
ark3lan
arkl2
ar5k2r4
4a2r1l
4arn.
a2r1n
ar4nal4
ar1na
a1ro
a2ro3b2
4a2r1p
ar2sa
a2rs
ar5s2kal
ars1k2a
ar4sk5l2
ar2s2v2
ar4tro
a2r1t
art2r4
arts5p2
ar2ts
ar4tur
4aru
a4rur
a5rus
ar4väg
a2r1v
a3r2y
a3rä
2a1sa
as3be4
a4s1b2
a1s2c2
a2s2h
asis5t
a1si
as3kis
ask4i
a2sk2o
a4sk2r4
as3ku
a2s5l2
as3pa
as2p2
as3pi1g
as2s2k
a4s1s
as2s5op
as1so
as2s2p2
as2s2t
as2s5up
as1su
as3ta
as2t
a5sta2r2d
as5ter
as3t2e
as5tiker
as1ti
asti1k2e
asti5o
as3to
as4t2r4
ast5rak
as2t1ra
a5stral
ast3rol
as5tör
a1stö
a3su
a4sul
a4su2n1d
a2sun
a2s2ut
as3v2
a1s2y
a2s5å
a2sö
a1t
at2a5ra
a1ta
a5t2e
ati5ö
a1ti
a4t2j
a2t2r4
a3tral
a2t1ra
4a4t3rar
a4t3re
at3ri1a
a2tri
a3tric2
at3r2i1e
a5trik
a3tris
a3t4ro
a4tro.
at4s1k2a
a2ts
ats2k
1at3tac2
a2tt
at1ta
at2tak
at4t2j
at4tos
at1to
at2t3s
a4tu2ng1
2au
au5b2
au2t5a
3autom
au1to
au2t5s
2a1va
a4va2r1t
1a2v1g
2a1v4i
a2v3r
4a3vä
a5å
1b2
3ba
ba4di
ba1d
ba4do
ba4d3s4
bak5l2
ba4k2o
ba4ku
bank5l2
ba2nk
bas4ta
bas2t
ba5stu
4b2b2
b4bak
b3ba
b4ba2tt
bba1t
4b4b2b4
bb3l
bb4ler
bb1le
b4b3r
bb4so
b4b3s
4b3d
3be
be3d4r2
be2d
be5e
be1k
4be2l2d
be5lu
be3l2y
be3lå
be5lö
be2ng4
be3nå
be1rö
be1s
be3sl2
be4s5s
be4s1ta
bes2t
be4s3t2e
be5s2u
be3t2r4
be3tv
be3u
4bex1
2b3f2
2b5h
3bi
bi3d4
4bi2n1v
bis3k2o
bi2sk
bi5skv4
b3je
b3k
b5lar
b5la1t
ble4mo
b1le
ble1m
b5len
5ble1ra
3bles
5bli2d
b1li
3blik2r4
3bli2k3s
4b3m
2b3n
3bo
bo4g2r4
bo1g
bo2kl2
bo1k
bo1mu
5bon
bors5t2e
bo2rs
bors2t
bor4ti
bo2r1t
bor2t3r4
bor2ts2
bort3sl2
bo1s
bo4s2c2
bo2y5
4b3p
2b5raf2
b1ra
4b3rar
2b5ra1ti
bra1t
3brik.
b3ri1k2a
3bri1k2e
3bri2k2s
b5ri3kö
bru4s2t
3br2y
3brö
4b3s
b5s1c4e
bs2c2
bs3c1h
b4slan
b2sl2
b4sof2
b1so
b4s2p2
bs2t4
b4st2j
4b3t
3bu
bun4d4s
bu2n1d
bus2s2t
bu4s1s
b3v
3b2y
by5r
3bå
bå2ng3
bå2t2s
3bä
3bö
bö2r2s
c2
5cap
c3c2
1c4e
cens3t
ce4ns
3cen2t2r4
ce2n1t
ceu4s
4ch.
c1h
3ch2au
3che1f2
5c2hoc2
4c2ht
ch4ä4s3
chör4
ch2ö
1ci
ci4lu
cim2
ci2pp4
4ck
c3k2a
c3k2e
c3ki
c4k5j
ck1l2
ck5lis
ck1li
c2k3n
c3k2o
c4k1or2d1n
c1kor
cko2r2d
c3k3o2r1g
c4ko2r1t
ck3r4
ck4re
ck3sla
c2ks
ck2sl2
c3kus2
ck3va
ckv4
ck3ve
ck3vä
ck5ä
ck3ö
cle2a
c1le
co2a
co4m
4cr
cros2
4cs
1c2y
1d
3da
5da.
4da2d2r2
da1d
da2g2s3
2dak
5da1k2o
da3li
5dam
da3må
4dand.
1da2n1d
4d1ap
4d1a2r1b2
4da2r1t
da4t2r4
da1t
da2t5t
4dax1
2d1b2
4dc2
dcen3
d1c4e
2d1d
2d2d1d4
ddi4s
d3di
d3dj
d4dos
d3do
dd3ra
d2d2r2
dd3re
dd3ri
d3drä
d4d2s
dds3v2
3d2e
de1k4
4de1k2o
4de2l2d
del2sa
de2l1s
dels5ti
del2s2t
de5lut
d4en
denti5ö
de2n1t
den1ti
den2to
de3p2r2
d2ep
5der
de2r1k
de2ro
de5rol
der5s1ti
de2rs
ders2t
de4ru
de2s
d2e3se
de3s2p2
des3ti
des2t
d4et
de3t2r4
4dex1
2d1f2
dfö3ra
d3fö
2d1g
d3gl
2d5h
3di
dias4
di1a
di5el
d2i1e
di2g2r4
di1g
di3k2a
di5ku
4di2n1f4
din3g4o
di2ng1
4di2n1r
4di4ns
2dins2p2
4di2n1t
di1o
1di4o2d
di3s2c2
di4s2j
dis3k2o
di2sk
dis1k2r4
di2s3p2
dis5to
dis2t
dis3t1ra
dist2r4
di4t1re
di1t
dit2r4
2dj
d3jor
dj2o
dju2p5p
d2j2u
3d4jur
2d3k2
4d5l
2d1m
2d1n
3do
d2ol
do5lo
4d1o2m1r
dom2sk
do4ms
5don
do4pak
do1pa
4d5o2r2d
4do1ri
4do2r1t
d5os2t
do3y2
2d1p
2d2r2
1d3ra1d
d1ra
3d4rag
1d3ra2n1d
d5r1a2r1b2
d3rar
d5rasse1ra
dr4as
dra4s1s
dras1se
d5ratu
dra1t
3drej
d1re
d3ren
5dres
d3ret
d4ric2
3drif2
d3ri1g
4d5rik
d3rin
3d4riv
d5roc2
3dro2pp
d3ror
4drot
drot2t2s3
dro2tt
d3rä2k3n
dr4äk
3d4rä4k1t
5drän
d3r2ät
1d5rö2d
4ds
d2s1an
d1sa
d2se
ds5e2n1h
d4s1f2
d2si
d2s3i4n1s
d2s2j
dsk2
d3ske1f2
ds1k2e
ds4ken
d3sk4i
d4s3kl2
d4s5k2n
d2s1l2
ds4lot
ds4mo
d2sm
d4små
ds5nå
d2s2n4
d2so
d4s3p2l
ds2p2
d4s3s4
ds3tal
ds2t
ds1ta
d5sta1t2
ds4t2e
d4ste4a
d5sti1g
ds1ti
ds3tin
ds5tro
dst2r4
d2su
ds2v2
d2sö
2d3t
3du
dub3b4le
du2b2
du4b2b2
dubb3l
4dup
du1s
du2s2c2
du4s3t2e
dus2t
du5sö
4dut
du4vu
d2u1v
2d1v
d3v2r
2d3w
3d2y
dy4kan
dy1k2a
dy4ro
4dz
5dåg
2dås
4dåt
4däg
dä2r
3dö
dö4d2s1
1dö2d
4dög
4döp
d5ös2t
dé4
e1a
e2a5k2e
e4am
4e1b2
e2br
eb3ril
4ec2
e3c1h
echi2f2f5
ech2i
echif2
ecis4
e1ci
e3co
e2d
e4da4ns
e3da
ed2d4r2
e2d1d
ed4i4u
e3di
e2d3j
e5dral
e2d2r2
ed1ra
ed1sk2
e4ds
ed2sk2o
ed3s2l2
ed2so4
e3då
e1e
e2e2d
e4ei
ee2k5
e4en.
e4e1ne
e1f2
e2f4s
3ef3t2e
e2ft
e1g
e3ga
e3ge
ege2l
e2g1l
eg2ler
eg1le
e3gle1ra
e5gle1ri
e4gran
eg2r4
eg1ra
eg5ra1t
eg3rin
e5gru
e2g2s3
e5g2å
ei1g2
ei5g1n
e3ik
e1in
ei5s2h
e1i2sk
e1j2o
e3j2u
e3jä
e5jö
e3k2a
e1ki
e1kl2
e2k3la1t
e2k4le
e2k3n
e1k2o
ekor4d5s
e1kor
eko2r2d
e2k3o4r1r
ek4ret.
ek2r4
ek1re
ek5ro
e1ku
e1k2ve
ekv4
ek5vis
ekv4i
e1k2y
e1kä
e1la
el1a4k1t
e2lak
e2l4a2r1b2
3eld.
e2l2d
ele2b3r
e1le
el4e1b2
elek2t3ri
ele4k1t
elek2t2r4
el4f1ra
e2l1f2
eli5ku
e1li
e2l3k4
el3li
el2l3s
el3lä
e1lo
e4lo2b2
el3p
el2si
e2l1s
el5ug
e5l2u1v
2e1lä
e1m
e5ma1t
e1ma
e5mis2
e1mi
emo4n1s
e1mo
em5o2r1t
emp5le
e2m1p
em2p2l
e2n5a2r1t
e1na
e2nav
en4c4e
e2n1c2
e4ne2d
e1ne
e4nek
ene3rö
2e2n1j
en5klo
e2nk
enkl2
en3k2n
en5k2r4
en5kä
enni2ng5
e2n1n
en1n2i
en3nin
ennin2g2s2
eno2m
e1no
en3s2i
e4ns
ens5k2e
ens2k
en2s2m
en2s2p2
ens4t2e
ens2t
ens4vin
ens3v4i
ens2v2
en4så
ent4ha
e2n1t
en2t1h
en2t2r4
ent4rat.
en2t1ra
entra1t
ent3ra1ti
en2t3ri
ent5ru
e5nus
2en2y
2e1nä
e1o
e2o1g
eo4i
e5or
2ep
e1pe
e1pi
e3pla
ep2l
ep5le
ep2p2s3
e2pp
epp1s5t
e1p2r2
e2p3s
ep4t2r4
e2p1t
epu3b2
e1pu
e3på
e2r1ak
e1ra
4er4as
e2r3d4
er2g4l
e2r1g
er4g2u
er4gås
er1g2å
e1ri
e5ri1b2
e4ri2n1f4
erin2g2s3
eri2ng1
eri5stik
eris2t
eris1ti
erk4lin
e2rk
erkl2
erk1li
erlä4
e2r1l
er5na
e2r1n
e1ro
e3ro3b2
e2rom
e2r1p4
er3ra
e4r1r
er5s2c2
e2rs
ers4ken
ers1k2e
er3sl2
er4s4le
er4s1ta
ers2t
er2s4t2e
er3st4r4
er3s2v2
e1ru
e5rum
e3r2y
e5rå1d
e1rä
e2sal
e1sa
es5all
e2s3a2r1m
e1s2c2
2e1se
es4h2i
e2s1h
es4i4u4
e1si
es2k
e4skan
es1k2a
e4s5kar
e4s3ken
es1k2e
es3ker
es5kul
e2sl2
e5slag
es2mi
e2sm
e1s2p2
e4s3p2l
es2s2k
e4s1s
ess5lä
es2s1l2
es2s2t
e3stal
es2t
es1ta
es5ten.
es3t2e
esti2ge
es1ti
esti1g
es3tin
es5tor.
es4t2r4
e1st5rer
est1re
e3stru
est4rö
e3stå
e1s2u
e1s2y
eta3b2
e1ta
e5ti
eti3ö
e1to
e5tri.
et2r4
e2tri
et3ris
e5trä
e2t2s
ets2a1d
et1sa
ets3k2r4
ets2k
et2s1l2
et2s3m
ets5pa
ets2p2
et4s2v2
ett3r4
e2tt
e1tu
etu4ri
et4va
e2tv
et5vu
e1t2y
2e2tz
e1tä
et2äc4
euk4
e5um.
e5up4
4eur
eu4se.
eu3se
eu5t4ro
eut3r4
e1v
e4va2r1m
e4vj
e2v3r
3exp
ex1
ext4r4
4eä
f2
3fa
fac4
fac5k2e
fa4ck
4fa4ns
4f1a2r1b2
fa3s2h
fa4s2t
fa4tö
fa1t
4fav
4f3b2
f3d
3fe
4f2e1f2
fe2l
fes5ta
fes2t
fe3sto
4fex1
2f2f2
2f2f2f4
f1f3l
ff3n
f3fo
ff3r
f2f3s4
f3fä
ffö5re
f3fö
f3g2
f5h
3fi
fi2br
fi1b2
fib5ri1g
fi3li
fin5s4m
fi4ns
fi3s2kal
fi2sk
fis1k2a
fisk3r4
fi2ti
fi1t
2f3k
1fl
flo4da
flo2d
4f3m
f1ma4
1fo
4f2of2
fol2
fo2l1k1
2f5om
fo2na
for4mo
fo2r1m
fos4t3r4
fos2t
4f3p
fra2m
f1ra
fra2m5p
f4rer
f1re
5fre1ri
fre4s
f4ri.
fri5s3p2
5fri1t
fros5ta
fros2t
fru5st2r4
frus2t
från5
2f3s
fs2k
f4sl2
f4sm
f4s2n4
f4s2p2
f4s2t
f4s2v2
2ft
f3ta
f4taf2
f4tak
f4tap
f4ta2r1m
fte4r
f3t2e
f4tex1
f3ti
f4tin
f3to
f4t3r4
ft2sa
f2ts
ft4set
ft3se
ft2s5i
ft4s2j
ft1s4t
fts5vä
fts2v2
f2t5t
f2t1v
3fu
furs5t2e
fu2rs
furs2t
fu5ru
fu3tu
4fv
5f2y
fy4ma
få3tö
1fä
fäs5ti
f4äs
fäs2t
3fö
fö2ra
fö2ren
fö1re
fö2ri
fö2r3k
för3sm
fö2rs
för3su
fö2r1t4
för1ö
ga5br
ga2b2
3g2ag
4ga4k1t
3g2al
gal2l3s
ga5lä
ga4no
2g1a2r1b2
4ga2r1m
ga2ro
4ga2r1t
ga4s2t
ga4su
5g2a1ti
ga1t
gaus4
g2au
g4av
g5av2s2n4
ga2vs
4gax1
2g1b2
2g1d
g3d4r2
ge2a
g4e5b4
2ge1f2
2ge4j
g2e1li
3gelis
gel5s2t
ge2l1s
gel5y
3g2e1lä
gel5än
g4e1m
ge4nap
ge1na
ge2n5g1
3g2e1n2i
3g2e2n1j
4ge2n1m
geno2m5
ge1no
gen4sa
ge4ns
g4en1se
1g2e2n1t
4ge2n1v
g2e5n2y
3g2e1nä
ge2o
1g2e1ra
4ge4r1a2r1b2
ge3rar
3g2e1ri
ger2s5n4
ge2rs
5g2e1se
ge4to
ge2t5s
5g2e2tt
2g1f2
2gg
g1ga
g4gap
g1ge
2g2g5g
g2g1l
g4gos
g1g2o
ggs4la
g2g2s
gg2s1l2
gg2s4m
gg3s4t
gg3s4v2
g4g2u
2g1h
gh4t2e
g2ht
1g2i
gi1o
gi5s3n4
gi4s3t2e
gis2t
gis4t2r4
gi5stral
gis2t1ra
gi5st4ra1t
3giv
gi2ö
g2j2o
3gjor
g3jä
2g3k2
2gl
g4la4ns
g1lar
g2las
5gla1se
g4las5k
5gla2sö
g4li2d
g1li
4glj
g4lög
5g4löm
2g1m
2g1n
g4nag
g1na
g2no
1g2o
3go.
3gol
go4n3s4
4go2n1t
2go2r2d
4go2r1m
4go2r1t
go3sl2
2g1p
g2r4
3graf2
g1ra
5gral
gra2m5
5gra4ns
4gr4as
5g4r4ec2
g1re
5gre2tt
g3ri1g
4g5rik
5grip
3gris
g5ro1i
gro2v
4grum
grus5t
g4rå
5grå.
grä4n
5grä4ns
2g2s
gs1an
g1sa
g5sa2tt
gsa1t
g3sel
g1se
g4s1f2
gsi4d
g1si
g3s4j2u
g1s2j
g5s2kaf2
gs1k2a
gs4k4i
g4s3k2n
g1s4kot
gsk2o
g3s2k2y
g2s1l2
g2s1m
g4s1me
g2s3n4
gs4n2i
gs4nö
gs1or
g1so
g4s3p2l
gs2p2
gs3po
gs4por
g4s5p1re
gsp2r2
gs3pu
g4s3s
gs3tak
gs2t
gs1ta
gs3tal
g3sta2rk
gs4ten
gs3t2e
g3stif2
gs1ti
gs3till
g2st2il
g2s3t2j
g3stol
gs3t1ra
gst2r4
gst4re
g3s4täm
g1stä
g4sug
g1su
gs2v2
g4s3ve
gs3v4i
gs3vå
gs3yt2
gs2y
g1s1ä
2g1t
g3t2r4
1g2u
4gug
gu2l2d3
gul4da
4gu1lä
gu2ma
4gup
gu5ru
gu2s4k
2gut
g3u2t1b4
2g1v
4gw
3g2y
gytt3j
gy2tt
1g2å
går4d2s5
gå2r2d
2g5å1ri
g4äl
g2är
g4ä4s
1g2ö
4gög
gö5ro
2g5ö2r1t
1h
ha3b1l
ha2b2
ha5ge
ha4li
hal4so
ha2l1s
halv3å
ha2lv
ham4s2t
ha4m1s
hande2l2s3
ha2n1d
han5d2e
hand2s4l2
han4ds
han5g2a
ha2ng1
h2a5ra
ha4s2c2
ha4s2p2
ha4sp5l
has3t
hav2
ha2vs3
h5c2
4h1d
he4a1t
he1a
he4fr
he1f2
h2e4lä
he2t2s1
het1s3t
het4s3v2
h3g
h2i
4hir
his2s2k
hi4s1s
hi4t
hjä2l3s
h1k
2hl
h4le
2hm
4hn
h2na
h2ni1t
h1n2i
ho5nu
hop5plo
hop4p2l
ho2pp
ho2p3s
hos3p2
hos5ti
hos2t
4how
h3p
h5ru
h1s
2ht
hu2s
hust5r4
hus2t
hyg5r4
h2y
hys4t
hys5ta
hy3ster
hys3t2e
hår4d5s4
hå2r2d
häll2
häl2l1s1
häl1so3
hä2l1s
hä4ri
h4ä4s
hä4var
hä1va
h2ö
hö2g
hö5gen
hö1ge
hög5r4
hör4n5s
hö2r1n
hö4s
höst5r4
hös2t
i1a
ia3fr
iaf2
ia3g
ia4lu
ia4sk
ia3t2r4
ia1t
i2b3l
i1b2
i5bril
i3ca
ic2
i4ce.
i1c4e
i5cha
ic1h
ic4ko2r2d
i4ck
ic3k2o
ic1kor
ick3u4
i5co
i2d
ide4n3s
i3d2e
id4en
id4ge
i2d1g
i4dom
i3do
i2d2r2
id3ro
i4d2s
ids3v2
i4dun
i3du
i3då
i4dö
2i1e
ifes4
if2
i3fe
i5fn
i1fr
3ifrån5
i1g
4igan
i2g4e5b4
i2g5e4j
i2g1l
ig3no
i2g1n
i3i
i4ka2r1t
i1k2a
i1ki
i3klo
ikl2
ik5län
i2k3n
i1k2o
ik3re
ik2r4
i5kro3b2
ik5rof2
ik5ros
ik5s2h
i2ks
ik5s1kor
iks2k4
iksk2o
i3kul
i3kum
ik5u4t
ik1v4
i3k2y
i3kå
i3kö
i1la
il4dan
i2l2d
il3da
i2lin
i1li
il1jö
i2l5k
il5lak
il1la
il4lik
il1li
il2l3s2
3il1lu
il5lär
il2min
i4l1m
il1mi
i1lo
il2t2j
i4l1t
i3lu
iluf2ts5
iluf2
ilu2ft
i4lup
i5lä
im2b3r
i2m1b2
im5s2m
i4ms
im4so
i1mu
i5må
i3mä
i5mö
i4n2au
i1na
ind5skä
i2n1d
in4ds
indsk2
ind5s1ti
inds2t
1in3du
in4ga
i2ng1
in4ge.
ing4es.
ing5i4s
in2g2i
in5g2las
in2gl
ings5t2e
in2g2s
ings2t
i3n2i
i4nif2
i2n5j
in5k2ve
i2nk
inkv4
1inne1h
i2n1n
in3ne
5in1re
i2n1r
1inri
3inrä
in4se1m2
i4ns
in1se
in3skrä
ins2k
insk2r4
in3sl2
in2s4m
in3s2n4
1in1spe
ins2p2
5inspel1n
in3s4pel
in5sp2r2
3in3sti2nk
ins2t
ins1ti
3in1stru
inst2r4
in4stå
in5t2e
i2n1t
1int2r4
in4t1ra
in2t3s
i1nu
i4nun
in3ym
in2y
i1nä
i5oc2
i1o1g
i3o1k
io4k2r4
i1ol
io5li
i5om
ion2
i3o1no
io4ns3
i1op
i1or
i1os
i1ot
i1pe
i1pi
i3pos4
i1po
ip5pi
i2pp
i3ra
i4res
i1re
i1ri
irk5l2
i2rk
i1ro
iro3p
i1ru
i5s1c4e
is2c2
isel4
i1se
i2s2h
i2sk
is5k2ep
is1k2e
isk5na
i4s2k2n
is3ko1pa
isk2o
is3ku
is4kun
i1s3k2y
i5slam
i2sl2
is3län
i2s3m
i2s3n4
i2s3p2
is4pri
isp2r2
is3sa
i4s1s
is3se
is2s5n4
is4s3t2r4
iss2t
is1s3tä
i1stal
is2t
is1ta
i1sta4ns
is2t5a2tt
ista1t2
is5ten.
is3t2e
i1ste2n1t
is4tes
is3ti1g
is1ti
is5ti2ng1
is5tor.
is5to1re
ist5ro
ist2r4
i1stå4
is5v2
i3s2y
i4så
i1t
i2t5c2
i4te3i
i3t2e
i4tex1
i4t2j
it5ran
it2r4
i2t1ra
i5trin
i2tri
i3tris
it2t5op
i2tt
it1to
it4t3r4
it4tu
i2t5å
4i1u
i1va
i2vak
i1v4i
i4vin
i2v3r
i2v2s
i1vå
ix2t
ix1
ix5tu
i1ö
1ja
3jakt.
ja4k1t
4j1a2r1b2
jas5p2
2j1b2
2j1d
j2d3r2
jd4st2y
j4ds
jds2t
j4du
1je
je2a
5je1f2
je5s1ta
jes2t
2j1f2
4j3g
4j1h
1ji
4jin
4jk
j4kl2
j3k2o
jk3v4
2j1l
2jm
2j1n
j2o
3jo2b2
jo4k2r4
jo1k
4j1olj
jo5lö
jor4din
jo2r2d
jor3di
jor4d3s4
3jou
4jp
j5p2l
2j3r
2j1s
j5sa
j4sk
js4me
j2sm
js4t2e
js2t
2jt
j2ts4
2j2u
ju4kos4
ju1k2o
ju2k3s
ju2l3k
4jur
jus5k2r4
ju2s1k
ju4s1s4
jus4t
jus5ta
jut4s1ta
ju2ts
jut1s2t
jä5lo
jäl4p5r2
jäl4sa
jä2l1s
järn3s4k
jä2r1n
jär4ns
jä2r5s
jö2r2s
jös4t
5jé
1k2a
3ka.
3kad.
ka1d
3kade.
ka3d2e
ka4d2r2
2kaf2
5ka1fä
ka3i
ka5j2u
ka1j
2kak
k3ak1ti
ka4k1t
4ka2l1f2
4ka2l1g
kal4lo
kal2l3s
3ka2m1p
3ka2m1r
3kan.
4kand.
ka2n1d
5ka1no
2kap
3ka1pi
ka5pla
kap2l
kap4p2r2
ka2pp
kap1s5t
ka2p2s
5kapten
ka2p1t
kap3t2e
3kar.
k2a3ra
4k1a2r1b2
k5arbet
kar3be
ka5ri
4ka2rk
3kar1na
ka2r1n
4k4a2r1p
kar2p5s
4kart.
ka2r1t
4kar3t2e
4ka2r1v
3kas
ka4sk
kas3ti
kas2t
3kat.
ka1t
3kats.
ka2ts
4k2au
2k1b2
4kc2
2k3d4
kdom4
k3do
1k2e
3ke.
2ked.
ke2d
2ke3da
ke3d2r2
ke4d4s
ke4er
ke1e
2ke3fu
ke1f2
4ke2l2d
ke2l1s4
4ken1se
ke4ns
ke5nå
2k2ep
3ke2r1n
ke2s
ke4s3s
4kex1
2k1f2
k3fö2
kfö3ri
2k5g4
2k1h4
ki4d3s
ki2d
4kif2
1ki1g
ki2k4s
ki4l1t4
5ki5må
king3r4
ki2ng1
4kin3ne
ki2n1n
4ki4ns
2ki2n1t
ki4nu
ki4t2r4
ki1t
ki2v3s
4kj
5kjol
kj2o
k3jä
2k3k
kl2
1klag
k2la1ma
kla4m4i
3klang.
kla2ng1
3kla4s1s
2kla1t
5k2lav
2k1le
k2lej
2k3li1g
k1li
k2lim
3k2lip
k2lis
5klist3r4
klis2t
k5lock.
kloc2
klo4ck
5kloc3k2a
3klos
1klu2b2
4kluk
1klä2d
2k3läg
2k1m
2k2n
k4nal
k1na
3k4nap
5k4nip
k1n2i
3k4niv
3k4nu
k4n2y
k5nyk
k2o
4koc2
ko5d2e
ko2d
k5o4d5l
ko2g3n
ko1g
ko4g2r4
ko2g4s3
4ko1la
ko2lin
ko1li
4k1olj
kol5t2r4
ko4l1t
5kolv.
ko2lv
1kom
3ko2m1m2
5ko2m1p
2k3o2m1r
ko4m4s
1kon
3ko2n1f4
3kons2t
ko4ns
3ko2n1t
ko3nu
1kor
3ko2r1g
ko3ri
2ko4r1r
3korres4
kor1re
5korte1ra
kor5t2e
ko2r1t
ko5s4k
ko3sl2
3kos2t
ko4s4t2r4
4k3ou
2k1p
k2r4
3kraf2
k1ra
5kra3ge
4kra2ng1
5kre1ra
k1re
k4reten
kre3t2e
kri4d5s2
kri2d
1kri1g
kri2g2s3
krings2k
kri2ng1
krin2g2s
4kriv
3kro2pp
krop2p2s5
kru5sta1d
krus2t
krus1ta
k3ryg
kr2y
krå2k5s
krå4pa
krå1p
k5rädd.
krä2d
krä2d1d
kräk5l2
kr4äk
4kräl
k3r2ät
2ks
2ksa2ks5
k1sa
k2s5as
ks3c1h
ks2c2
k4ser
k1se
ks2k4
k4s3kl2
ks5k1ra
ksk2r4
k2s5kv4
k3skä
k3skö
k5slag.
k2sl2
ks2li
k5sl2y
k2so
k4s3p2l
ks2p2
k1s4t
ksta2v2s3
ks1ta
ks2tav
k2s5ti2d
ks1ti
k2su
4k1t
k4tex1
k3t2e
kti5ge
k1ti
kti1g
k4ti2n1n
k2ti4ns
k2to2d
k2tom
k2t2r4
kt3re
kt3rin
k2tri
k5tro2d
kt5ro1g
kt3rol
kt5r2ät
kt2s2t
k2ts
k2t5t4
k4tug
k2tut
k4täl
4kug
k5u2g1n4
ku5la
4ku2l2d
3ku1lö
kum5p2l
ku2m1p
kun2g2s5
ku2ng1
5ku2n1n
ku4pen
ku1pe
ku4ro
3ku2rs
3kus
kus1t3a
kus2t
kv4
3kva1li
k5va1re
3kva2r1n
kva2r3s
3kva2r1t
k4vato
kva1t
k2ve
2kven3t2e
kve2n1t
1kvi2n1n
kv4i
5kvi1re
k4vo
k1vå
3kväll
kvä4l
k1vär
kyd4d2s3
k2y
ky2d
ky2d1d
ky4lin
ky3li
3ky2rk
kä4l4m
5kä2m1p
5kä2n1n
3kä4ns
3kä2r1l
4kög
kök1s5t
kö2k2s
5köp.
kö2r4l
kör4sl2
kö2rs
3la.
1lade.
la1d
la3d2e
2la2d1m
4la2d2r2
2laf2
3lagd.
la2g1d
la4gin
la1g2i
5la2g1m
lag3r4
2lak
5la3kan.
la3k2a
5la1ki
3lak3tis
la4k1t
lak1ti
la5lo
3lande.
la2n1d
lan5d2e
lan4di
2l1app2a1ra
la2pp
lap1pa
2l1a2r1b2
1la2r1n
la2r5s
4la2r1t
la2s3h
4lask
la4s2t
5la2s5te.
las3t2e
1lat.
la1t
la5t2r4
lat4tis
la2tt
lat1ti
2l2au
2lav
la5vu
2l1b4
4l1c2
2l2d
lde2r4s
l3d2e
l5der
l3dj
ld3ra
l2d2r2
l5dr2y
ld2s4an
l4ds
ld1sa
1le
3le.
le4ge.
le1g
le3ge
le5i1g2
le2kl2
le4kv4
lem4sö
le1m
le4ms
2l5e2n1l
3ler.
le2r5k
3le2r1n
ler3s4t2e
le2rs
lers2t
le5s2l2
le5tå
le3um
le4vu
le1v
2lex1
2l1f2
2l1g
l2gj
l3g2l
l2g2s4
lg5s2t
2l1h
1li
li5c1h
lic2
3lif2
3li1g
li4g2o
li2g3s
lik2l2
1li5k1li
li2k3s
5limer
li1me
2li2n1d
2linga.
lin4ga
li2ng1
lin2g5o
4ling2r4
lings5t
lin2g2s
2li3n2i
5li2n5j
2li2n1t
li1o
2lip
lis3c2
li4s1ta
lis2t
li3strö
list2r4
li4vo
li2v2s1
l2jak
l1ja
4l1j2o
1l2j2u
l5jå
l1jä
l3jör
2l1k
l3k2e
l5k2j2u
l4kj
l2kl2
l1k5lag
l5klä
l2k2r4
l3k4ra
l4k3t
l1la
l2l2d4
ll3d2r2
ll4e5b2
l1le
l2l3k
ll1l
l1lo
llo2k5v4
llo1k
ll3p
ll4san
l2l1s
ll1sa
ll2se
ll3s1k2a
ll2sk
ll2so
ll4sva
ll2s2v2
ll4ti1g
l4l1t
ll1ti
ll3t2r4
l1lu
l2l5un
llus2t3ra
llus2t
llust2r4
l2l5v
l5l2y
lläg2g2s5
llä2gg
l5lö2d
llör4
ll5ö2r1t
4l1m
l4mol
l1mo
lm3s2t
l4ms
l1n
lo2af2
lo1a
loc4ku
loc2
lo4ck
4lo4d5l
lo2d
lo4do
lod3s2t
lo4ds
lo2ge.
lo1g
2l1olj
2lom
4lo2r2d
2lo2r1g
lo2r4s
lo4vo
l4pak
l1pa
l1pe
l1pi
l5pla
lp2l
lp5lö
lp4s4t
l2ps
4l3r
2l1s
l2s2c2
l4sj2o
l1s2j
l4sjä
l2sk
l4skens2v2
ls1k2e
lske4ns
l3sk4i
l4s2k3n
l5skot
lsk2o
l3skrä
lsk2r4
l3s2k2y
l3skå
lskå4p
l3skä
l3s2lu
l2sl2
l4sm
ls4mo
ls5nyt
l2s2n4
lsn2y
l2s2p2
l3spe
l4s3p2l
ls3pol
ls1po
l4s5s
l2s2t
l3s1ta
l4stak
ls4t2e
ls5ter
l3sto
l3st2y
l4styg
l3stå
l3stä
l5stö
l2su
l5sur
l2s2v2
l4sv4i
ls5vi2d
l4så
4l1t
lta2tu
l1ta
lta1t2
l4te1f2
l3t2e
l4tif2
l1ti
l4ti1h
l4tos
lt5ra1ti
lt2r4
l2t1ra
ltra1t
l4tret
lt1re
l4trö
lt5s2k
l2ts
ltu4
lu5i
luk4to
lu4k1t
4lull.
2lun
lu2ng3
2lu2pp
lu4pu
lus2s5p2
lu4s1s
5lu2st.
lus2t
4l1u2t1b4
4lu2ts
2lv
l1va
l4va2r1m
lve2rs4
l1ve
l1v4i
l4vos4
l1vo
lv3ri
l2v2r
lv3s2p2
l2vs
l1vä
lväv4
lyck1s5t
l2y
lyc2
ly4ck
lyc2ks
ly4ga1t
ly1ga
lyg3r4
ly2g3s2
3lys3t2e
lys2t
5lys2t5n4
ly4st2r4
2lå.
lå2g3s
1lå2ng1
lån2g3s
lå4sk
lås5t2e
lås4t
lå4stå
4l2äc2
läg5r4
1län4ds
lä2n1d
5läng5der
lä2ng1
län2g1d
läng3d2e
lä4san
l4äs
lä1sa
lä4s2p2
lät2t3s
l2ät
lä2tt
4löl
4löm
3lön
3lörer
lö1re
1lös
lö4vä
lö1v
3lé
1ma
ma5fr
maf2
ma2g5n
ma2g5s
ma5j2u
ma1j
mak3r4
ma3li
ma2n1d4
mang2a
ma2ng1
man5g4o
ma5n2i
mani1k
5ma3ri
ma2s2h5
ma2s3k2o
mask3ro
ma4sk2r4
ma5skö
mas3ti
mas2t
mas4v2
2m1b2
mb4sk
m4b3s
2mc2
2m1d
m4da1t
m3da
m4di
m4do
m3d4r2
1me
2me4ds
me2d
me4du
me4kl2
me4k2o
4me2l2d
me1lo5
me5lu
me2n5k
me5nu
m2e5n2y
mer2sk2o
me2rs
me4so
mes4t
me3s1ti
2me1ta
me5trin
met2r4
me2tri
met3ro
meu4
2mex1
2m1f2
m4fes
m3fe
m4fn
2m1g4
2m1h
1mi
mi4d3s
mi2d
mi4lu
2mi2n1d
min2g4o
mi2ng1
4mi2nk
min4k2r4
4mi2n1v
mi3nö
mis2
mi5s1f2
mi4s3p2
miss3t
mi4s1s
mi4te.
mi1t
mi3t2e
mi4t2r4
mit2t3s
mi2tt
2m1k
2m3l
2m1m2
mme5d
m1me
m4m3s4
m4mul
2m1n
m2nam
m1na
mnas3t
m4nav
mn5d2r2
m2n1d
m2n3g4
mn5s2t
m4ns
mn5tu
m2n1t
m2n3å
1mo
m4o2d
mo4i
2m1o2m1r
mo3na
mos3k
mo2ta
m4o4tin
mo1ti
mo4tu
mo2t3v
2m1p
m2pak
m1pa
m4pa2r1t
m2p2l
mp3la1d
m5p4la1ne
mp3la1t
mp3lin
mp1li
m3pos4
m1po
m2p5p4
mps4k
m2ps
mp5s2p2
m4på
2m1r
4ms
m4sal
m1sa
m4s1k2e
m3slag
m2sl2
ms3lä
m2s2m
ms3t2e2
ms2t
m1sto
m2st2r4
mst3rin
ms2tri
m2s5äp
m1sä
2m1t
4mu2d
mul2l1s3
mul2t5r4
mu4l1t
5mum
4mu2n3g4
mun4k2o
mu2nk
3mur
3mu1si
mu3s1ta
mus2t
mut4sl2
mu2ts
2m3v
1myn
m2y
mys4t2e
mys2t
måg4
1mål.
5målet.
må1le
5mån.
4mår
må1s
4mäg
m4äk3
1män
mä4ns4
3mä2rk
1m4äs
mäs5ta
mäs2t
1m2ät
mö4bl
mö1b2
mö4gen.
mö1ge
3möj
mör4k2l2
mö2rk
3mös
4mö1v
1na
3na.
3na1d
na4d2s3
2naf2
na5g2r4
2nak
3na1k2o
3nak2r4
na3kro
n1a4k1t
2na2l1f2
5nal1fl
4na2l1g
na2l3s
na2lu
n5a2m1b2
5na2m1n
4nand.
na2n1d
4na2n1v
na4rap
n2a1ra
2n1a2r1b2
2na2rk
4na2r1m
2na2r1t
nas4t3r4
nas2t
2n1b4
2n1c2
n2c1h
n3cha
n3che
n3ch2i
ncis4
n1ci
ncyk3l2
n1c2y
2n1d
n4dak
n3da
n4dav
n2d3d4
n5d2e
nde3s
n4dil
n3di
nd5rak
n2d2r2
nd1ra
nd5r4as
nd3ra1t
nd3ri
n5dril
n3drop
nd5ros
nd5s2kal
n4ds
ndsk2
nds1k2a
nd3s2n4
nds3or
nd2so
nds5vä
nds2v2
n2d5ås
1ne
3ne.
ne4di
ne2d
5ne4d5l
ne4d3r2
ne4d3s
ne4dö
ne2g2r4
ne1g
ne5gres
neg1re
4nek.
ne5l2y
4ne2n1l
ner5sm
ne2rs
ne4s3s4
ne4s1ta
nes2t
ne5s4ti
ne3t1re
net2r4
ne1ut
2nex1
2n1f4
nfal2l1s5
n3fa
nfis3
n3fi
2ng1
n4gar
n4gen.
n4ge2n1d
n4ge4ns
n4gen1ti
n1g2e2n1t
n4ge2r1m
n4get
n2g2i
ng3i1g
ngi4s
ng4l2y
n2gl
n2g2o
ng5om
ng3or
ng3ra1d
ng2r4
ng1ra
n4grö
ng4ser
n2g2s
ng1se
ngs1k
ngs3pa
ngs2p2
ngs5tim
ngs2t
ngs1ti
ngs3val
ngs2v2
n4gö2d
n1g2ö
2n1h
1n2i
4ni2d
ni5ec2
n2i1e
ni4ki
ni5li
3nin
nin2g2s1
ni2ng1
nings3k
nings5v2
ni1o
4nip
nip4p2r2
ni2pp
ni5ste1ri
nis2t
nis3t2e
nis2t3ra
nist2r4
ni3t4r4
ni1t
niv5s2k
ni2v2s
niv5s2t
2n1j
n4jar
n1ja
n3jun
n2j2u
nju4s
n3jä
2nk
n4ka2r1t
n1k2a
n1ki
n4kis.
n3k4n2y
n2k2n
n1k2o
nkraf2ts5
nk2r4
n3kraf2
nk1ra
nkra2ft
nk3ri
n1kro
nkrus4
nk5sl2
n2ks
nk3s2p2
nk4tin
n4k1t
nk1ti
n1ku
n1kö
2n1l
2n1m
2n1n
n2n3d
n3ne
nnis4
n1n2i
n2n3k
nn3s4t
n4ns
1no
2no4d5l
no2d
no4kl2
no1k
2n1olj
2n1o2m1r
no4m3s4
2no2r2d
2no2r1g
no5sa
no5s2c2
no4tu
2n1p
2n1r
4ns
n1s2i
n4si2n1t
n4sis.
n4si1se
ns2k
ns3kan
ns1k2a
n1sk4i
ns3kor
nsk2o
nsla2g2s5
n2sl2
n4s5las
ns5mi1t
n2sm
ns1mi
n4soc2
n1so
n1spi
ns2p2
n4s3p2l
ns3po
n4s3s4
4n3sta4ns
ns2t
ns1ta
n3stap
ns4tel
ns3t2e
n3stif2
ns1ti
ns3ti1g
ns4t1ra
nst2r4
n2strik
ns2tri
nst5up
nst5vil
n4s2tv
nstv4i
n3s4t2y
n1sva
ns2v2
ns3v4i
ns3vär
2n1t
n4ta2rk
n1ta
n5te2r5s4
n3t2e
n4ti2n1f4
n1ti
n2t5o2m1b2
nt3ra1d
nt2r4
n2t1ra
n3tra1h
n3t2rak
n5t4ra1la
nt3ra1li
n5tram
nt3r2ep
nt1re
n3trer
nt3ri1a
n2tri
nt3rin
nt3ris
n4tropin
ntro1pi
n4tror
n4trö
nts3c2
n2ts
nt4se
nts5kor
nts2k
ntsk2o
nt4st2r4
nt1s2t
n4tut
n3två
n2tv
nuf2ts4
nuf2
nu2ft
4nug
n5u2g1n4
3nui
3num
nu4ms5
2nup
n3u2pp
2n1u2t1b4
2n1v
ny5g2r4
n2y
n5z
4når
4nä.
4n2äc2
3näm
3n2ät
4nög4
3nöj
nö2ja
nö5k2r4
4nöl
nös4
nös5k2e
o1a
o2a2r2d
o2b2
5o4bj
o4b1li
o3b2y4
oc4k5r4
oc2
o4ck
ock3s2k4
oc2ks
oc3ku
o2d
ode4k4
o3d2e
odi4a
o3di
1od1li
o4d5l
o5dral
o2d2r2
od1ra
o3dro
ods4k2
o4ds
od2s2t
ods4ti
od5stu
o3dä
o1e
off4s5t
of2
o2f2f2
of2f3s4
o4fl
o3fr
oförmå4
o3fö
ofö2r1m
o1g
o4g4av
og3g2r4
o2gg
o4gj
o5glo
o2gl
o5gl2y
ognos4
o2g1n
og2no
ogno5s2t
o4gri
og2r4
o4grö
og3se
o2g2s
og4s3t
o4gä
o1i
o4il
o1j
o1k
o4k1li
okl2
o2k3n
ok3sl2
o2ks
ok4su
o2kv4
o1la
o5lak
o2l5au
ol3fö4
o2l1f2
1olj
ol3k2a
o2l1k
ol2k3r4
ol4ku
ol4kä
oll4si
ol2l1s
oll5slä
oll2sl2
ol3lä
ol4m4s
o4l1m
ol4n3s
ol1n
o1lo
olo5kv4
olo1k
ol4sa
o2l1s
ol4tå
o4l1t
o1lu
o4lug
o4lur
o1l2y
ol5år
o1lä
om4brä
o2m1b2
o3men
o1me
o4mo2r2d
o1mo
om5pa
o2m1p
om3p2l
1o2m1r
4om1ra
om1sk
o4ms
om4s3t2e2
oms2t
3om3s2ät
om1sä
om4t2r4
o2m1t
om3tv
o2n3c2
on5g2i
o2ng1
on1g2r4
ong2s4l2
on2g2s
o4ni4ns
o1n2i
o3nin
o2n3j
o2n1k4
ons3c2
o4ns
onsi3s
on1s2i
on2s3m
on5s4tel
ons2t
ons3t2e
ons4ter
on3tr4as
o2n1t
ont2r4
on2t1ra
on4t1re
on2t4s
o1n2y
on5å
o1nä
o3nö
oo4d
oo4m5s
o3or
o1pe
o1pi
o5pli1ne
op2l
op1li
op4p2l
o2pp
opp3le
op4p2r2
op4pu
o3pri
op2r2
op4s4t
o2ps
o3på
o5q
4o1ra
o3rak
ora2n3g4
o2rap
1or2d1n
o2r2d
or4d5ä
o4re1h
o1re
1orga
o2r1g
5orga1n2i
or4g2r4
or4g2å
o1ri
3orie2n1t
or2i1e
4o2rk
or4mö
o2r1m
or4nu
o2r1n
or4nä
o1ro
or4p2l
o2r1p
or5p2r2
or4s1pa
o2rs
or1s2p2
ors5ti1g
ors2t
ors1ti
or5t2e
o2r1t
or2t2r4
ort3re
ort3ro
o1ru
o3r2y
o1rä
o1rö
o3s2fä
o2s1f2
o4sk4l2
o1skop
osk2o
o3som
o1so
os5pi1g
os2p2
os4s2k
o4s1s
os4s4t
os3ti1g
os2t
os1ti
os5tiker
osti1k2e
o5still
o2st2il
os4t2r4
ost5ron
ost5rö
os3tul
ota2lan
o1ta
ota1la
4oti.
o1ti
4ot2i1e
4otin
o1to
o5tro
ot2r4
ot5run
ot3s2v2
o2ts
ot5ti
o2tt
ot4trä
ott2r4
ot2t2s
o1tu
o5tun
otvin4
o2tv
otv4i
o1t2y
o5tå
o3tä
oun4
oup4
4our
ou3rö
ou4s
o3u2t3t
o1va
ova4n
o1v4i
o2v3r
ov4si
o2vs
ov3sl2
ovs4me
ov2sm
o1vä
o3we2
ox5
o2y2
o3å
o3än
o3ö
1pa
4paf2
pag4
pa1ki3
pakis4
pa5la
pa2l1s5
pa5lä
4pand.
pa2n1d
pan4t2r4
pa2n1t
3pap
2p1a2r1b2
4pa2r1m
pa2r3s
2pask
pa5sk4i
pa2s2t
3pa2t2r4
pa1t
p2a3u
2p1b4
2pc2
2p3d4
pek5tri
pe4k1t
pek2t2r4
pekt3ro
4pe2l2d
pel3s4i
pe2l1s
4pe1m
5pe2ng1
3pe2n1n
pen2t5r4
pe2n1t
per4bl
pe2r1b2
3peri1o
pe1ri
3pe2rs
per4sl2
pe5tro
pet2r4
4pex1
2p1f2
4p3g
2p1h
pi4el
p2i1e
1pi1g
pi1o
3pip
pi5so
pi5s1ta
pis2t
pi5sto
p2j
3pj4äs
4p3k2
p2l
p4lac2
5plan.
p4la1ne
p3la2r1n
p3le1v
p1le
3p2lex1
3plic2
p1li
1plik
4pli1t
p3lj
1p2lom
p3lop
2p1m
4p1n
p3n2i
1po
5po1a
2poc2
2pof2
po2i
3poli1t
po1li
4p1olj
po1l2y3
2po2r1g
3pos
pos4ter
pos2t
pos3t2e
4pov
po4vä
2pp
p4pa2r1t
p1pa
p2p5ask
p4pax1
p3pe
p1pi
p4pi4ns
pp3j
pp2l
pp3la
pp3lin
pp1li
pp5lis
pp5lu
pp3l2y
pp3lån
pp3låt
pp3lä
pp3lö
p2p5oc2
p1po
p2p3of2
2p2p3p4
pp2r2
p2p3ra
pp3ri
pp3ru
pp3r2y
pp3rä
pp3t2r4
p2p1t
p2pu
p5p2y
pp3å
p2r2
2p1ra
5prax1
1pres
p1re
pres4t
pre3s1ta
pres5to
p3ri1g
p3rik
5pril
3pri2n1c2
pri2ng3
p5ri1ol
pri1o
3pro
pro3g
p3ror
4prå
3p4r4äs
3prö1v
2ps
p2sal
p1sa
3psa4l1m
p5s2ho
p2s1h
ps4ken
ps1k2e
ps2li
p2sl2
p3s1na
p2s2n4
4p1so
p3so2d
p1s4t
p4stak
ps1ta
p4stäv
p1stä
p2sö
2p1t
p3tri
pt2r4
1pu
4pug
pul2l5ov
pul1lo
pul5t2r4
pu4l1t
5pu2ng1
3pu2nk
pus3t
2p1v
på3d2r2
på1d
3päl
pä5ro
4pör
3pé
qu4
3qu1e
1ra
3ra.
raci4t
rac2
ra1ci
3rade.
ra1d
ra3d2e
4ra2d2r2
ra4du
5ra1e
2r3af3fä
raf2
ra2f2f2
ra3fr
ra5is
2rak
ra2lo
r4an5d2e
ra2n1d
3rande.
4ran4d3r2
ran4d3s
2rans2v2
ra4ns
ra3p2l
3rar
r4ar.
4r1a2r1b2
r4a1re
4ra2r1g
r4a2rk
4ra2r1m
r4a2r1n
r4a2rs
4ra2r1t
r3ar1ta
ra5rö
r4as
ra2s3h
ra2s2t
3ra2s5te.
ras3t2e
3rativ
ra1t
ra1ti
ra3tri
ra2t2r4
2rav
ra5yo
ra2y
2r1b2
2r1c2
2r2d
r4daf2
r3da
rda5g2r4
r3dj
r4dos
r3do
rd3ran
r2d2r2
rd1ra
rd3ra1t
r4dul
r3du
r3då
r3dä
r4dös
r3dö
1re
3re.
4reaus
re1a
re2au
r4e3b2
4r4ec2
5re3co
re3d4r2
re2d
re5du
4re2ft
re1f2
4re2gg
re1g
3regn.
re2g1n
re1k2r4
rek5tri
re4k1t
rek2t2r4
4re2l2d
re3lu
re2m5p
re1m
3re4ms
r4en.
2re1n2i
2re2nk
2re2n1l
re3nö
re3o
3rer.
3re2r1n
3re1so
res2s5k
re4s1s
re1s1ti
res2t
3ret.
4retet
re3t2e
ret3ro
ret2r4
4re1t2y
re5tå
2revi1g
re1v
rev4i
4rex1
2r1f2
rfö3ri
r3fö
2r1g
r2g3g2
rgs5t2op
r2g2s
rgs2t
2r1h
rhan4ds5
rha2n1d
3rial
ri1a
4ri1b2
3ri3fi
rif2
2ri1fr
r3ifrå
3ri3fu
3ri2g1t
ri1g
ri2k2s
3rik2t5n4
ri4k1t
ri4mo
2ri2n1d
rin4d3s
5rin4gen.
ri2ng1
ring3r4
2ri2n1r
2ri4ns
2ri2n1t
ri1o
3ri1ot
ri5p1le
rip2l
ri2stä
ris2t
ri4tut
ri1t
ri4vis
ri1v4i
ri2v3s
4rj
r4jis
r1ji
r3j2o
r5j2u
r5jö
2rk
rk3a4k1t
r1k2a
r2kak
r4kek
r1k2e
rke2s3
r1ki
r3klas
rkl2
r2k2le
r4klö
r2k3n
rk4ne
r1k2o
r4ko2d
rk3t2r4
r4k1t
r1ku
r4kup
r1kä
r5kör
2r1l
r5la3k2a
r2lak
r5lav
r2l2d2
rl4ds3
rl5s2p2
r2l1s
2r1m
r4m1a2r1b2
r1ma
r4mil
r1mi
rm2s5j
r4ms
rm5t2r4
r2m1t
2r1n
rnal4
r1na
r2n3g4
r2n1k
r2nom
r1no
rns4k
r4ns
rns4t
r2n3t
ro3b2
ro4gro
ro1g
rog2r4
ro2k2r4
ro1k
2r1olj
rol4li
ro1m4a
5roman
5ron2au
ro1na
5rond.
ro2n1d
ro2n4v
ro3p2l
rop2p2s
ro2pp
r4o4ra
2ro2r2d
2ro2r1g
2ror2i1e
ro1ri
3ro2r1n
ro4sin
ro1si
ro4s2n4
ros3v2
ro5t2e
2r1p
r4plö
rp2l
r4pö
4r1r
rra4n
r1ra
r2r2d4
rreli1g2i5
r1re
rre1li
rre3li1g
rres4
r5ri1b2
r2r5k4
r4ro3b2
r4rom
r2r1s
rrs2k
r4rur
2rs
r4se2l2d
r1se
r4sex1
r2sin
r1si
r1sk4i
r4ski2d
rsk3na
r4s2k2n
rs5koll
r1skol
rsk2o
rs4kos
rskot2t2s3
r1skot
rsko2tt
r2sku
r3skö
rslags4v2
r2sl2
rsla2g2s
r4s1le
r4slo
r4s5lö
rs4mo
r2sm
rs5na1t
r2s2n4
rs1na
rs5nä
r1s2p2
r4sp2l
r2s1po
r4s3s4
rs5tak
rs2t
rs1ta
rs4t2e
r5stek
rs5te2n1d
r5ste1n2i
rs5till
r2st2il
rs1ti
r1sto
r4ston
rst4r4
r3strö
r3stu
r1s2v2
rs4vag
r2svä
r1s2y
2r1t
r2taf2
r1ta
r2tak1ti
rta4k1t
rt4an
r4ti4ns
r1ti
r4tom
r5tri1t
rt2r4
r2tri
r3trä
r2t3t
r4tut
rubb5l
ru2b2
ru4b2b2
ru3br
ru4dan
ru2d
ru3da
ru2k2s1
ruks3v2
5rulle1ra
rul1le
3rum.
ru2n1n2
run4ns5
4ru2pp
ru2s2h
ru5sha
2rut
5ruti1g
ru1ti
ru2t4ra
rut3r4
ru4v4i
r2u1v
5ru3ö
2r1v
rv4s2j
r2vs
rv2s5kä
rv1s2k
r3w
ryd4d5s
r2y
ry2d
ry2d1d
ry5o
råge5l
rå1ge
4rål
rån2g3s
rå2ng1
rå5ra
rå3s4t
räc2k5s
r2äc2
rä4ck
4rä4k1t
r4äk
4räm
rän2g3s
rä2ng1
rän4s5t
rä4ns
4r4äs
rä4san
rä1sa
rä4s3s
rä5s1ti
räs2t
rä2v5s
röd5el
rö2d
rö3d2e
rö2d5r2
rö4d3s
2rög
r3ö1i
rö2k3s
röns4t
rö4ns
4röp
3rör
rö2r4s
rö4s2t
röst3r4
r1ö2v2r
rö1v
1sa
3sa.
3sad.
sa1d
3sa3d2e
4sa2d3j
2sa3d2r2
sa4d5s
2saf2
sa3i
sak5ri
sak2r4
2s1a4k1t
sa5lo
3s2am
sa2ma
samman3
sa2m1m2
sam1ma
sa2mor
sa1mo
san4d3s
sa2n1d
4sa2ng1
2sa2n1l
s3anlä
san3sla
sa4ns
san2sl2
2sap
3s4ar.
2s1a2r1b2
2sa2r1m
s5arm.
3sa2r1n
2sa2r1t
4sa2r1v
4sa4s1s
5sat.
sa1t
sa4tu
2s2au
s3auk
2s1av
4s1b2
s2c2
2s4ch.
sc1h
1scha
2s3ch2au
4sch1b2
1schen
1scher
1schet
1sch2i
4sch1k
4sc2hm
4sch3p
3sch2y
3sch2ö
sci3p
s1ci
4s3d
1se
se4at.
se1a
sea1t
se2g
2s3e2gg
3se2g1l
seg3ra
seg2r4
se2k5le
se1kl2
sek3r4
sek5t2r4
se4k1t
3sel.
se5l2y
se1m2
3sen.
s5er1sä
se2rs
3set.
2s3exp
sex1
2s1f2
s4fär.
s1fä
s3fö2
4s3g2
2s1h
5s2haw
shi1s
sh2i
s5h2ö
1si
si4d5s
si2d
5s2i1e
si4e1ri
si4es2k
si2e2tt
3s2i1g
3sik
sik2ts3
si4k1t
5sill.
silver3
si2lv
sil1ve
sil2v3r
2s1i2n1d
2s1i2n1f4
sinne2s3
si2n1n
sin3ne
3sin1n2i
4si2n1r
2si4n1s
s1ins2t
5sint.
si2n1t
2s1int2r4
3sio
sis4t
s4i1u4
1s2j
2sjak
s1ja
s3ja4k1t
4s2j1n
4s2jt
s4j2u
5sjuk
4s4jur
sjä2l1s3
3sjö
4sk.
2s3ka.
s1k2a
3s2ka3da
ska1d
s2ka3do
3skaff3n
s2kaf2
ska2f2f2
1ska2ft
s4kag
s2kal
3skal.
1s2kap
5skap.
5skapet
ska1pe
4s3ka1pi
ska2p2s1
4skar
s4k2a3ra
5s4ka2r1v
4s3kas
s2ka1t
s4kav
4s3ke.
s1k2e
3s2ked.
ske2d
s4ke1ne
3ske2pp
s2k2ep
4s2k1h4
sk4i
3s4kif2
5skin
4skis.
5skiv
5skjor
s4kj
skj2o
3sk2j2u
4skl2
sk5lap
s3klas
4s2k2n
3s4ko.
sk2o
1s4ko1g
4skog4s3g2
sko2g4s3
1skol
3s4ko1la
s4ko1lo
s4ko2r1p
s1kor
skor1s2t
sko2rs
1skot
s5kran.
sk2r4
sk1ra
3skra1t
sk4ret
sk1re
3skre1v
1skri
3skrif2
s3kri1g
5skrin
3skrip
s5kris
3s4kriv
s5kron
s4kru
5skru2b2
3skr2u1v
5skr2äc2
s2k3s
2s4k1t
3skulp
s3kup
2skv4
s4k2ve
1s2k2y
s4kyn
2s3ky2rk
1skå
s4kål
5skåp.
skå1p
4skår
5skä2nk
3skä2r1v
2sl2
4s3la.
s5lad.
sla1d
s3la2n1d
3s2la2ng1
s4la2n1t
s3lar.
4slas
s1la1t
s2le1v
s1le
3slev.
s4lic2
s1li
sli4ns3
4slis
s2li1t
s5lor
slot2t2s3
slo2tt
s2lu
s3luc2
s3luf2
4slus
s3lus2t
3slut
slu4to
3s2lå.
5s4lår
s4l4äk
s5läm
s5lä2n1n
3s4läp
4s3lär
s2l2ät
3s2löj
2sm
s2mak
s1ma
3smak.
s3ma4k1t
s2mal
s2met.
s1me
s2mi2d
s1mi
s2mi1t
3smit1ta
smi2tt
s3mj
5smug
5smyg
sm2y
små5g4
små3k
små3s
3smä2d
3smäl
4s1m4äs
3smör
2s2n4
3sna2b2
s1na
3s4nac2
s3nam
s5na1re
s3nas2t
s5ner
s1ne
3sni1b2
s1n2i
3snil
3sni1t
1sni2tt
s3niv
3snut
s4nå
5s4når
5s4n2äc2
s4när
3snö.
s4nö5g4
3snör
snö3s4
1so
3soc2
5so4ck
2so2d
5so1i
2s1olj
so2l3s2
2som
5so2m1m2
3son
son4s2t
so4ns
so5p1ra
sop2r2
so4pu
3sor.
2so2r2d
s5ord.
2so2r1g
3so2r1n
3sot
4so2tt
s2p2
5spann.
s1pa
spa2n1n
s4pa2rk
5spa2r1v
4spas
s3pa4s1s
s3pa5t2r4
spa1t
1spe
4spe2d
3s4pek
3s4pel
4spel2sl2
spe2l1s
2spen
2sper
5spe2t2s
3spill
3spir
4sp2l
s1pla
s3plan
s3pla2ts
spla1t
sp1li4
s4plin
5s4pli1t
s5plä
4sp1re
sp2r2
s3pres
4s3pris
3spri1t
2s3pro
s3pr2y
3s4prå
5sprän
s3ps
1s4på
3spån
3spår
5spän
3spö
4s1r
4s1s
s5sa1d
s1sa
sse4lin
s1se
sse1li
s5sil
s1si
ss2k
s4s5kl2
ss3kun
s2s1l2
ss2lag.
ss2lä
ss2lö
ss3na
s2s2n4
4s4s1s4
ss3u2n1n
s1su
s2sun
s2s2v2
ss3v4i
s2t
2st.
4s3ta.
s1ta
5stac2
3s4ta3di
sta1d
s4taf2
5stalgis
sta2l1g
stal1g2i
3stal1la
2stal1li
5stam.
5sta2m1m2
1sta2n1t
5stark.
sta2rk
5starta1d
s4ta2r1t
star1ta
1sta5t2e
sta1t2
3sta2t3l
1st2au
s2t3c2
2s5te.
s3t2e
4ste1a
5steg.
s4te1g
s4tek.
2ste2k3n
5ste4k1t
s4tell
3stem.
ste1m
3ste1me
5stenar
ste1na
3s4te1ne
3sten1se
ste4ns
5sten2s2m
1ste1ra
1steri2ng1
ste1ri
s4ter4i1u
3ster1ne
ste2r1n
5ste3tis
ste5ti
2sti1a
s1ti
2s5ti1b2
3sti4ck
stic2
2s3ti2d
s4tiken
sti1k2e
2st2il
3stil.
3sti2nk
3s4tis2c2
s3tis
1sti1t
2st2j
s5t2j2u
3s4tjäl
3stjär
2s2t1m
5stoc2
1stol
4sto2l1k
4stom
stori4eu
sto1ri
stor2i1e
5storis
sto2r3s
3stra2f2f2
st2r4
s2t1ra
st4raf2
4st3rativ
stra1t
stra1ti
3st4rato
3st4r4ec2
st1re
3strej
s4t3ren
1strer
2stri1a
s2tri
1stri2d
5stri3d2e
2st5riel
str2i1e
st4rif2
1stri4k1t
st5ri2sk
1stru
3struk
2stru2m1m2
s3tryc2
str2y
5stryk
5s4tråk
3st4rål
3str2äc2
4s3trä2d
3strä2ng1
st4rän
5sträv
3ström
2s2t3s4
s2t3t
4s2tv
s3tvis
stv4i
1st2y
2s3typ
1stå
4s3tåg
5stål
1stä
3stäl
1stö
1su
su4b2
3sug
su3i
3sum
2sun
5sun.
s1un5der
su2n1d
sun5d2e
5su1ne
s5u2ng1
2sup
5su1pa
su2pu
5sus
2s1ut
su4to
su4t3r4
s2v2
5svag.
s3va2g1n
4s3vak
5svam
4s5vap
sva2r2s3
3sva2r1t
4svas
s3va1t
4s5v4ec2
s1ve
3sven
5s4v2ep
4s3ver
s5ves4
4s3vil
sv4i
s4vi1ne
4svis
s5vi2tt
svi1t
s5vå1d
3s4vå1ri
3svä2ng1
5svärm.
svä2r1m
s3v4äs
s3v2ät
4syk
s2y
5syl
3syn
sy2n3k
s3y2rk
3sys
sys4t
sys5ter
sys3t2e
syt2
sy5t1h
1så
5såg
4såk
2sål3d2e
så2l2d
så2ng3
1sä
s4ä2d
2s5ä2gg
s4äl
2säp
5s4äs
3s2ät
4sä1ta
1sö
4sö2d
2sög
s5ö1ga
sö4k2o
4söl
4söp
sö2r2s
2s3ö2r1t
1ta
3ta.
ta1c1h
tac2
3tade.
ta1d
ta3d2e
4ta3di
4ta4d2s5
2ta2f2f2
taf2
3ta1ga
5tak.
ta5k1re
tak2r4
2t1akti1g
ta4k1t
tak1ti
tak4to
4ta2l1f2
5talli1se
tal1li
tal2l5s
4ta2lv
3ta1me
3ta1m4i
3tan.
ta4na2b2
ta1na
3tande.
ta2n1d
tan5d2e
2t3an3fa
ta2n1f4
4ta2n1l
t4ap3l
2tappar
ta2pp
tap1pa
3tar.
4t1a2r1b2
tar4mi
ta2r1m
3ta2r1n
ta2rs4
4ta2r1t
5tarta2v1l
tar1ta
tar2tav
4ta2r1v
4task
3tas2t
ta1s4t2r4
ta1t2
ta4tan
ta1ta
ta2ts3
2ta2tt
2tav
4ta1ve
5tav3la.
ta2v1l
3tavlan
3tavlo
ta2v2s
3tax1
2t1b4
2tc2
t3cha
tc1h
t3che
2t3d4
3t2e
te4as
te1a
t4e3b4
5t4ec2
4te1g
te2g2r4
te3g1re
te3i
te4i2n1t
te1in
4tej
te2j2s
te4kl2
5teknik
te2k3n
tek1n2i
5teknis
4te2l2d
5te5lö
5te1ma
te1m
4te1mo
te4mu
te2n3g4
5ten3s2i
te4ns
ten3t2r4
te2n1t
t2e4nä
te5nör
5ter.
5teri1ö
te1ri
te2r3k4
5te2r1m
5ter5na
te2r1n
5te2rs
te2r3t
te4ru
5tes.
5tes2t
3t2es4t2e
te5stik
tes1ti
te5stu
5tetik
te5ti
te2t2s3
4texa
tex1
2t3exp
2t1f4
2t3g4
2t1h
t4hen
1ti
3tial
ti1a
5ti1b2
5ti1ci
tic2
3ti2d
5ti3d2e
ti4du
4ti4dö
ti4e2d
t2i1e
tif2ts5
tif2
ti2ft
ti2gel
ti1g
3ti2g1h
ti4g2o
ti2g2r4
3ti2g1t
tik3l2
3ti2ks
5ti3kul
t2il
5tilj
3till2s2t
til2l3s2
3til2l5v
3tillä
5ti1me
2ti2n1d
2ti2n1r
2ti2n1t
ti4o2d
3tion2
ti2os
3tis
4tis2c2
5ti2sk
3ti1va
ti4van
5tivi3t2e
ti1v4i
tivi1t
ti2ö
t2j
4t1je
4t3jo2b2
tj2o
2t3jou
4tjäl
4tjäm
3tjän
2t3k2
2t3l
2t1m
2t5n4
tne4r
t1ne
4to4d5l
to2d
3to1k
4tol.
4t1olj
2t1o2m1r
4to4ms
t2op
5to2rap
t4o1ra
t5ord.
to2r2d
5torie2tt
to1ri
tor2i1e
4to2r1m
tor4m3s
3to2r1n
tor1s2t
to2rs
4tort.
to2r1t
tos4k
t5o2st.
tos2t
t4ov
2t1p
t2r4
2t1ra
t4raf2
3tra3fi
3t4ral.
t4ra1la
3t4ra1le
5tra2lo
3tra2l1s
t4ra4l1t
3tra4ns
tran2s5a
4t3rar
t3ras.
tr4as
t3rat.
tra1t
t4rato
4tre1g
t1re
4tren
4t3rer.
4t3re2r1n
t3rets.
tre2t2s
2tri
3tri3bu
t4ri1b2
5tri4ck
tric2
tri4d2s3
tri2d
t5riel
tr2i1e
t1ri2ng1
t3ring.
2troc2
t3ro4ck
t4ro1g
t5ronik
tro1n2i
t3ro1no
4tropi.
tro1pi
5tro4s1s
5tro2t5n4
t4ru2m1p
t4rup
3t4ru2pp
trus5ta
trus2t
1tryc2
tr2y
5tryck.
try4ck
5tryg2g1h
try2gg
4tråk
5trä.
3trä2d
trä4ds4
3träf2
3träg
4tr4äk
t3rä2k3n
t4rän
5trä1n2i
5trö1ja
t4röt
5tré
2ts
t5s4a2n1d
t1sa
t2s5a2r1t
t3s4a1t
t3se
t4se2g
ts4en
t4sex1
ts2k
t5skall
ts2kal
ts1k2a
t3ska2tt
ts2ka1t
t1sk4i
t4s3kl2
2tskot2t2s5
t1skot
tsk2o
tsko2tt
t5slot
t2sl2
ts5l4äk
ts3nä
t2s2n4
t3snö
t2so
t2s3o2r2d
t4s3p2l
ts2p2
t4s1s4
t1s2t
ts4t2e
ts5ter
ts5til2l1f2
t2st2il
ts1ti
t2s3t2j
t3stol
t4ston
t2s2t1ra
tst2r4
t4str2y
t4stur
t5styr
t1st2y
t2su
t3su2d
t5s2y
2tt
t3tac2
t1ta
t4t2au
t4te2d
t3t2e
t4te5g4
t4te1m
tte2n
ttes4
t4tex1
t4ti4ns
t1ti
t4tip
tt3ja
tt2j
t1to
tt3ra1d
tt2r4
t2t1ra
tt3ra2n1d
tt3ra1t
tt3re
t2t3ri
tt4r2y
tt4se
t2ts
tt2si
tt4s1ta
tt1s2t
t3tu
t4tug
t2t1v
tt4vå
t3t2y
t3tä
t3tör
4t5u2g1n4
2tu2n1d
3tunga
tu2ng1
tun2g3s
5tu2n1n
2tu2pp
tu5re
2t1u2t1b4
t3u2t1v
t3utö
tu4vu
t2u1v
5tu3ö
2tv
t1va
4t1ve
t3vi1g
tv4i
3tvi2ng1
t3vi1t
3tviv
t3våg
3tvån
t3vän
tvä2r3s
3tvä2tt
t2v2ät
ty5da
t2y
ty2d
5tyg.
3tyn2g1d
ty2ng1
3typ
ty3pi
5tys
2tz
3tåg
tås4
4tåt
täc4k2o
t2äc2
tä4ck
4t5äg
4täm
4tä2r1m
3tä2v1l
4tö4d
tö5d2e
4tög
4töp
tö4pi
3törer
tö1re
törs3t
tö2rs
tö4vas
tö1v
5té
u1a
u2b2
ub5al
u3ba
ubb4le
u4b2b2
ubb3l
ub3lic2
ub1li
u4bo
u3cha
uc2
uc1h
u5cl
u2d
u4dak
u3da
u5d2e
u2d3r2
ud4ret
ud1re
ud1s4a
u4ds
u4du
u4d2y
u1e
u2es
uf4fä
uf2
u2f2f2
uf4tan
u2ft
uf3ta
uf4to
4u1ga
u1ge
ug2g3s
u2gg
u2g1n4
ug4ns5
u2g3s4
u5i1e
u1in
u3is
u3itet
ui1t
ui3t2e
u3j
u2k4e1b2
u1k2e
u5ki
u4kl2
uk5la
u2k3n
u1k2o
ukos4
u2k2s
uks5k2o
uks2k4
uk3tris
u4k1t
uk2t2r4
uk2tri
uk2t5s
uk4tä
u3ku
uk3v4
u1la
ul4di
u2l2d
uld2s2m
ul4ds
ul4du
ul4dö
ull3s4t2e
ul2l1s
ull2s2t
ull3än
u1lo
uls5ti
u2l1s
ul2s2t
ul2t2r4
u4l1t
u3lu
u1lä
u1lö
um4fä
u2m1f2
um4so
u4ms
ums4t
u1mu
u3mör
5unde2r1l
u2n1d
un5d2e
un5der
1under1sö
unde2rs
1unde2r1v
un4dom
un3do
un2d3r2
un4då
un5g2e1f2
u2ng1
un3gersk
unge2rs
ung5i1t
un2g2i
ung3r4
ungs4p2
un2g2s
3unif2
u1n2i
unk3l2
u2nk
un2k3n
un4k2r4
un1s2k
u4ns
un4t2r4
u2n1t
un5tra1ti
un2t1ra
untra1t
u5nu
u1o
u1pe
u4pe2r1n
u1pi
u2p2l
u3plet
up1le
u1p3lik
up1li
3upp3fa
u2pp
up2p1f2
1up4p3g
up4pin
up1pi
1upp3la
upp2l
5upp3lä
up4p3r2
up2p3s
upp5s2p2
up5ut
u1pu
u2r5ak
u1ra
ur5a2r1v
u3rar
u3re
u1ri
u1ro
u4ro3b2
u4rom
urs5tin
u2rs
urs2t
urs1ti
ur4stä
u5r2y
u2sak
u1sa
u2s5a2n1l
u3scha
us2c2
usc1h
u3se
usen3
u2s1k
us3k2a
us4kla
u4skl2
us4k2r4
u5s2k2y
u1s4kå
us5lä
u2sl2
u2s3n4
u2s2p2
u2s3pen
u1spe
us5ta1t2
us2t
us1ta
us3ti1g
us1ti
u3stik
us5tin
ust5ro
ust2r4
u4stå
u4stä
us3v2
u4så
u4sä
u2sö
u4tak
u1ta
1u2t1b4
u4te1f2
u3t2e
ute3s
utik2
u1ti
u5t2il
uti3ö
ut3j
3u4tjäm
utlan4ds3
u2t3l
utla2n1d
u1to
u3t2op
uto5s
ut3r4
ut4rer
ut1re
ut4ro
ut5rop
1utru
2utsi2d
u2ts
ut1si
ut3sl2
3utslä
2u2tt
utt4j
u2t1v
3ut5v4ec2
u4t1ve
u5t2y
ut3ö1v
u5u
2u1v
u2vak
u4vj
u4vä
u5å
u3ö
va5dro
va1d
va2d2r2
1va2g1n
2v1ak1ti
va4k1t
va2l3k
val4li
val4s2t
va2l1s
5va2lv
5va1ma
4vand.
va2n1d
4va2n1p
van4s2t
va4ns
van5t2r4
va2n1t
5vap
2v1a2r1b2
va4res
va1re
va4ri.
va1ri
4va2rk
va2r2s
vart5r4
va2r1t
v4a1ru
vas5ti
vas2t
5vat2t5n4
va1t
va2tt
4v2au
4vav
5v2a1v4i
2v1b4
2v1c2
2v3d4
1ve
5v4ec2
ve2k
ve3k2e
4ve2l2d
vensk3ä
ve4ns
vens2k
5vente1ra
ve2n1t
ven3t2e
v2e3n2y
ve5nö
4v2ep
ve2r5g
3ve2rk
ves4
ve2s5p2
ve1s2t
3ve1ta
3ve3t2e
vet5sa
ve2t2s
vet2t5s
ve2tt
2v1f2
2v1g
2v1h
v4i
vi4c2
vi4d3s
vi2d
vil4d3s
vi2l2d
vi4l4t
3vind.
vi2n1d
vin2g3s4
vi2ng1
3vinkl2
vi2nk
vi2no
5vin2st.
vi4ns
vins2t
5vins3t2e
vi5n2y
3vis.
vi5sa
vi2s5h
vis5k2o
vi2sk
vi4s2t
vis3ta
vi2t2r4
vi1t
vi4var
vi1va
4vj2o
2v3k2
2v1l
2v1m
vmö2rk4
2v1n4
1vo
4vok.
vo1k
2vom
4vo2r2d
2vo2r1g
vos4
2v1p
2v2r
5v2rak
v1ra
3vre1ra
v1re
v3ru
2vs
v4s2c2
v1s2k
v2skri
vsk2r4
vs4mi
v2sm
v3s1n2i
v2s2n4
v2so
v1s2t
vs4t2e
vs5trå
vst2r4
v5styc2
v1st2y
vs3vå
vs2v2
v2sö
2v1t
vu4d1
v1u2n1d
4v5up
4vut
2v1v
3v2y
5vå2l2d
vån2g2s3
vå2ng1
3vå2r2d
4vå1ri
vå3ru
3väg
väg2g5s
vä2gg
vä4l
väl2l4s3
3vä2n1l
3vär3d2e
vä2r2d
vä4ril
vä1ri
4vä4rj
5vä2rk
3vär2l2d2
vä2r1l
2v2ät
3väx1
4vög
4vöp
3vör
1wa
we2
w2h
wh2i2
w2i2e
w4na
x1
xa2n5d4
xem3pla
xe1m
xe2m1p
xem2p2l
xis4
xk2
x1li4
xs4
x1ti2
x4tå
2y
y1a
y4bris
y1b2
y4b4s
y2d
y4da
y5dan
y4do
y2d3r2
y4ds4
y4du
y4dö
y1e
y1ga
y1ge
ygg3r4
y2gg
yg4g2å
ygs4p2
y2g2s
y1i
y1ki
y5klis2t
ykl2
yk2lis
yk1li
yk5lon
y2k3n
y1k2o
y1la
yl4gj
y2l1g
y3li
y2l5k
yl5lä
y1lo
yl4t2r4
y4l1t
ym2fl
y2m1f2
ym4for
ym1fo
y3må
yng3r4
y2ng1
ynk5l2
y2nk
yn4sa
y4ns
yns4t
y3or
y5ou
y1pe
y5po
yp3ri
yp2r2
yre4s
y1re
y1ri
yr4ku
y2rk
yrk5v4
y1ro
yrs4k
y2rs
yr5s2t
yr5tu
y2r1t
y1rå3
y5scho
ys2c2
ysc1h
ys2s2t
y4s1s
ys3ta
ys2t
ys3ti
ys4tik.
ys2t3ra
yst2r4
y2tak
y1ta
y4te.
y3t2e
y4te1a
y1to
ytt3r4
y2tt
y2t5v
y3va
y3v4i
y3vä
y5w
y5å
1za
1ze
ze4ro
1zi
1zo
zo4nal
zo1na
4zp
z5s
3zu
z4zin
z1zi
å1a
å3dj
å1d
åd2s4l2
å4ds
å1e
å1f2
å1ga
å1ge
åge2l
å2g3l
åg3s4k
å2g2s
åg3s2t
å1g2å4
å3i
å1ki
5å1klag
åkl2
åk4strä
å2ks
åk1s4t
åkst2r4
å1la
1ål5der
å2l2d
ål3d2e
å2lin
å1li
å2l3k
åll4s2p2
ål2l1s
ål2s5e
å2l1s
ål3s2t
å1lä
å1m
åma4n4s
å1ma
ån2d4r2
å2n1d
ån4du
åns4t
å4ns
åns4v2
å3o
å1p
å2p2l
å5pla
å4pö
år4do
å2r2d
ård4ra
år2d2r2
år4d2s
ård4s3t
å4rel
å1re
å1ri
å5ror
5år2s1av
å2rs
år1sa
år5s2li
år2sl2
år2s2v2
år5ö
ås4k2e
å2s3n4
å4s1s4
ås4sk2r4
åss2k
ås4t
å3t2e2
å2t3ri
åt2r4
å3trå
åt2s2j
å2ts
åt2t5s
å2tt
å1v
ä1a
ä2b2
2äc2
äck5v4
ä4ck
ä2d
äd4d3s
ä2d1d
äd4du
äde4s
ä3d2e
ä2d3r2
äd5se
ä4ds
äd3s2t
ä3e
ä1ga
ä1ge
äg4g2o
ä2gg
ä2g1l
äg3r4
äg4re
äg3se
ä2g2s
ä3i
ä5j2o
4äk
ä1ki
ä2k3n
äk3r4
ä1la
äl4pap
äl1pa
äl4se2g
ä2l1s
äl1se
äl1s5ko1g
äl2sk
älsk2o
äl4s2lu
äl2sl2
äl2t3r4
ä4l1t
äl2tu4
äl4vin
ä2lv
äl1v4i
äm2p3l
ä2m1p
4ändli2g1h
ä2n1d
än4d5l
änd1li
änd3li1g
än2d3r2
änd1s2t
än4ds
äng5r4
ä2ng1
änni3s4
ä2n1n
än1n2i
än4n3s
ä4no
än2s1l2
ä4ns
än4s2t
äns5t2e
än4s2v2
än2t3r4
ä2n1t
ä3pe
äpp3l
ä2pp
ä4p2r2
äp4s4t
ä2ps
ä4rap
ä1ra
är2b1re
ä2r1b2
är2g5l
ä2r1g
är4g2r4
ä1ri
ä4ri1b4
är4kä
ä2rk
är4nis
ä2r1n
är1n2i
ärn3s4t
är4ns
är2nå
är4nö
är5o3b2
ä5rol
ä3rop
ä5ror
ä5ros
är2si
ä2rs
är4sk2o
är2so
är4s2p2
är2s2v2
är4ta2n1d
ä2r1t
ärt4an
är1ta
är2t2r4
är2t3s
4äs
äs3pa
äs2p2
äs5pi
äs4s2k
ä4s1s
äs4s2p2
äs3ta
äs2t
äst3r4
ä4stä
ä4så
2ät
ä3to
ä5t1re
ät2r4
ät4s3k
ä2ts
ät5t2e
ä2tt
ät4t2op
ät1to
ätt3r4
ät4tu
ät4t1v
ä1va
ä2vak
ä3v4i
ä5vu
ö1a
ö2d
ö4dak
ö3da
ö4dal
ö4da2r1v
öde4s5
ö3d2e
ö4dis
ö3di
öd3ra
ö2d2r2
ö4d2s
öd3se
ö4du
ö4dö
ö1e
ö1ga
ög5ak
ö5gar
1ö2g1d
ö1ge
ö5ger
ö2gg4
ö2g1l
ö2g2n
ög1n3e
1ö1g2o
ög3si
ö2g2s
ög3sk
ö1i
ö3j2o
öj4s2v2
ö2j1s
ö4ka2r1m
ö1k2a
ö1ki
ö2k3n
ö2k2s
ök3sl2
ö1la
öl4kv4
ö2l1k
öl4kö
öl2p
ö5lä
öman4
ö1ma
öm2kl2
ö2m1k
ö4nal
ö1na
ö2nom
ö1no
öns3k2e
ö4ns
öns2k
ön4so
önst3r4
öns2t
ö3pe
ö4pel
ö3pi
öp5li
öp2l
ö5plo
1öp4p1n
ö2pp
ö4p2r2
ö3r4an5d2e
ö1ra
öra2n1d
ö3r4as
ö4rask
ö2r1b4
ör3d4r2
ö2r2d
ö2r1e1n2i
ö1re
ö3res
ö4res4t2r4
öres2t
ö3ret
ö2r5evi1g
öre1v
örev4i
ö2r3g
ö1ri
ö5ri1g
ö3ri2ng1
ö2r3i2n1t
ör5ir
ör5iv
ör4kal
ö2rk
ör1k2a
ör1k2l2
ör5k1li
ör4nis
ö2r1n
ör1n2i
ör3ol
ör1or
ör2p5la
ö2r1p
örp2l
ör1s2k
ö2rs
ör3sl2
ör4slä
ör5t2e
ö2r1t
ör2t5s
ör1u
ör3v2r
ö2r1v
ör3y
ör1ä
örö4d
ö2sak
ö1sa
ö2s3n4
ös4s2j
ö4s1s
ös2s2k
ös4s2p2
ös3ta
ös2t
ö4s2t3v
ö2tak
ö1ta
öts5k2o
ö2ts
öts2k
öt4s2t
ö1v
ö1ve4
över1
5öve1re
ö2vj
öv3ra
ö2v2r
öv3ri
öv4s2k
ö2vs
é3e
PK
!<PzNhyphenation/hyph_tr.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
2a1
2â1
2e1
2ı1
2i1
2î1
2o1
2ö1
2u1
2ü1
2û1
1b1
1c1
1ç1
1d1
1f1
1g1
1ğ1
1h1
1j1
1k1
1l1
1m1
1n1
1p1
1r1
1s1
1ş1
1t1
1v1
1y1
1z1
2e2cek.
e1c1
ec2e1
ece1k1
2b1b1
2b1c1
2b1ç1
2b1d1
2b1f1
2b1g1
2b1ğ1
2b1h1
2b1j1
2b1k1
2b1l1
2b1m1
2b1n1
2b1p1
2b1r1
2b1s1
2b1ş1
2b1t1
2b1v1
2b1y1
2b1z1
2c1b1
2c1c1
2c1ç1
2c1d1
2c1f1
2c1g1
2c1ğ1
2c1h1
2c1j1
2c1k1
2c1l1
2c1m1
2c1n1
2c1p1
2c1r1
2c1s1
2c1ş1
2c1t1
2c1v1
2c1y1
2c1z1
2ç1b1
2ç1c1
2ç1ç1
2ç1d1
2ç1f1
2ç1g1
2ç1ğ1
2ç1h1
2ç1j1
2ç1k1
2ç1l1
2ç1m1
2ç1n1
2ç1p1
2ç1r1
2ç1s1
2ç1ş1
2ç1t1
2ç1v1
2ç1y1
2ç1z1
2d1b1
2d1c1
2d1ç1
2d1d1
2d1f1
2d1g1
2d1ğ1
2d1h1
2d1j1
2d1k1
2d1l1
2d1m1
2d1n1
2d1p1
2d1r1
2d1s1
2d1ş1
2d1t1
2d1v1
2d1y1
2d1z1
2f1b1
2f1c1
2f1ç1
2f1d1
2f1f1
2f1g1
2f1ğ1
2f1h1
2f1j1
2f1k1
2f1l1
2f1m1
2f1n1
2f1p1
2f1r1
2f1s1
2f1ş1
2f1t1
2f1v1
2f1y1
2f1z1
2g1b1
2g1c1
2g1ç1
2g1d1
2g1f1
2g1g1
2g1ğ1
2g1h1
2g1j1
2g1k1
2g1l1
2g1m1
2g1n1
2g1p1
2g1r1
2g1s1
2g1ş1
2g1t1
2g1v1
2g1y1
2g1z1
2ğ1b1
2ğ1c1
2ğ1ç1
2ğ1d1
2ğ1f1
2ğ1g1
2ğ1ğ1
2ğ1h1
2ğ1j1
2ğ1k1
2ğ1l1
2ğ1m1
2ğ1n1
2ğ1p1
2ğ1r1
2ğ1s1
2ğ1ş1
2ğ1t1
2ğ1v1
2ğ1y1
2ğ1z1
2h1b1
2h1c1
2h1ç1
2h1d1
2h1f1
2h1g1
2h1ğ1
2h1h1
2h1j1
2h1k1
2h1l1
2h1m1
2h1n1
2h1p1
2h1r1
2h1s1
2h1ş1
2h1t1
2h1v1
2h1y1
2h1z1
2j1b1
2j1c1
2j1ç1
2j1d1
2j1f1
2j1g1
2j1ğ1
2j1h1
2j1j1
2j1k1
2j1l1
2j1m1
2j1n1
2j1p1
2j1r1
2j1s1
2j1ş1
2j1t1
2j1v1
2j1y1
2j1z1
2k1b1
2k1c1
2k1ç1
2k1d1
2k1f1
2k1g1
2k1ğ1
2k1h1
2k1j1
2k1k1
2k1l1
2k1m1
2k1n1
2k1p1
2k1r1
2k1s1
2k1ş1
2k1t1
2k1v1
2k1y1
2k1z1
2l1b1
2l1c1
2l1ç1
2l1d1
2l1f1
2l1g1
2l1ğ1
2l1h1
2l1j1
2l1k1
2l1l1
2l1m1
2l1n1
2l1p1
2l1r1
2l1s1
2l1ş1
2l1t1
2l1v1
2l1y1
2l1z1
2m1b1
2m1c1
2m1ç1
2m1d1
2m1f1
2m1g1
2m1ğ1
2m1h1
2m1j1
2m1k1
2m1l1
2m1m1
2m1n1
2m1p1
2m1r1
2m1s1
2m1ş1
2m1t1
2m1v1
2m1y1
2m1z1
2n1b1
2n1c1
2n1ç1
2n1d1
2n1f1
2n1g1
2n1ğ1
2n1h1
2n1j1
2n1k1
2n1l1
2n1m1
2n1n1
2n1p1
2n1r1
2n1s1
2n1ş1
2n1t1
2n1v1
2n1y1
2n1z1
2p1b1
2p1c1
2p1ç1
2p1d1
2p1f1
2p1g1
2p1ğ1
2p1h1
2p1j1
2p1k1
2p1l1
2p1m1
2p1n1
2p1p1
2p1r1
2p1s1
2p1ş1
2p1t1
2p1v1
2p1y1
2p1z1
2r1b1
2r1c1
2r1ç1
2r1d1
2r1f1
2r1g1
2r1ğ1
2r1h1
2r1j1
2r1k1
2r1l1
2r1m1
2r1n1
2r1p1
2r1r1
2r1s1
2r1ş1
2r1t1
2r1v1
2r1y1
2r1z1
2s1b1
2s1c1
2s1ç1
2s1d1
2s1f1
2s1g1
2s1ğ1
2s1h1
2s1j1
2s1k1
2s1l1
2s1m1
2s1n1
2s1p1
2s1r1
2s1s1
2s1ş1
2s1t1
2s1v1
2s1y1
2s1z1
2ş1b1
2ş1c1
2ş1ç1
2ş1d1
2ş1f1
2ş1g1
2ş1ğ1
2ş1h1
2ş1j1
2ş1k1
2ş1l1
2ş1m1
2ş1n1
2ş1p1
2ş1r1
2ş1s1
2ş1ş1
2ş1t1
2ş1v1
2ş1y1
2ş1z1
2t1b1
2t1c1
2t1ç1
2t1d1
2t1f1
2t1g1
2t1ğ1
2t1h1
2t1j1
2t1k1
2t1l1
2t1m1
2t1n1
2t1p1
2t1r1
2t1s1
2t1ş1
2t1t1
2t1v1
2t1y1
2t1z1
2v1b1
2v1c1
2v1ç1
2v1d1
2v1f1
2v1g1
2v1ğ1
2v1h1
2v1j1
2v1k1
2v1l1
2v1m1
2v1n1
2v1p1
2v1r1
2v1s1
2v1ş1
2v1t1
2v1v1
2v1y1
2v1z1
2y1b1
2y1c1
2y1ç1
2y1d1
2y1f1
2y1g1
2y1ğ1
2y1h1
2y1j1
2y1k1
2y1l1
2y1m1
2y1n1
2y1p1
2y1r1
2y1s1
2y1ş1
2y1t1
2y1v1
2y1y1
2y1z1
2z1b1
2z1c1
2z1ç1
2z1d1
2z1f1
2z1g1
2z1ğ1
2z1h1
2z1j1
2z1k1
2z1l1
2z1m1
2z1n1
2z1p1
2z1r1
2z1s1
2z1ş1
2z1t1
2z1v1
2z1y1
2z1z1
2a3a2
a3â2
a3e2
a3ı2
a3i2
a3î2
a3o2
a3ö2
a3u2
a3ü2
a3û2
â3a2
2â3â2
â3e2
â3ı2
â3i2
â3î2
â3o2
â3ö2
â3u2
â3ü2
â3û2
e3a2
e3â2
2e3e2
e3ı2
e3i2
e3î2
e3o2
e3ö2
e3u2
e3ü2
e3û2
ı3a2
ı3â2
ı3e2
2ı3ı2
ı3i2
ı3î2
ı3o2
ı3ö2
ı3u2
ı3ü2
ı3û2
i3a2
i3â2
i3e2
i3ı2
2i3i2
i3î2
i3o2
i3ö2
i3u2
i3ü2
i3û2
î3a2
î3â2
î3e2
î3ı2
î3i2
2î3î2
î3o2
î3ö2
î3u2
î3ü2
î3û2
o3a2
o3â2
o3e2
o3ı2
o3i2
o3î2
2o3o2
o3ö2
o3u2
o3ü2
o3û2
ö3a2
ö3â2
ö3e2
ö3ı2
ö3i2
ö3î2
ö3o2
2ö3ö2
ö3u2
ö3ü2
ö3û2
u3a2
u3â2
u3e2
u3ı2
u3i2
u3î2
u3o2
u3ö2
2u3u2
u3ü2
u3û2
ü3a2
ü3â2
ü3e2
ü3ı2
ü3i2
ü3î2
ü3o2
ü3ö2
ü3u2
2ü3ü2
ü3û2
û3a2
û3â2
û3e2
û3ı2
û3i2
û3î2
û3o2
û3ö2
û3u2
û3ü2
2û3û2
tu4r4k1
t2u1
tu1r1
m1t4ra1k1
m2t1r1
mtr2a1
PK
!<f}kJJhyphenation/hyph_uk.dicUTF-8
LEFTHYPHENMIN 2
RIGHTHYPHENMIN 2
2а1
2а3а1
а3е1
а3і1
а3о1
а3у1
а3ю1
а3я1
а3є1
а3ї1
2е1
е3а1
2е3е1
е3і1
е3о1
е3у1
е3ю1
е3я1
е3є1
е3ї1
2и1
и3а1
и3е1
и3і1
и3о1
и3у1
и3ю1
и3я1
и3є1
и3ї1
2і1
і3а1
і3е1
і3и1
і3о1
і3у1
і3ю1
і3я1
і3є1
і3ї1
2о1
о3а1
о3е1
о3і1
2о3о1
о3у1
о3ю1
о3я1
о3є1
о3ї1
2у1
у3а1
у3е1
у3і1
у3о1
2у3у1
у3ю1
у3я1
у3є1
у3ї1
2ю1
ю3а1
ю3е1
ю3і1
ю3о1
ю3у1
2ю3ю1
ю3я1
ю3є1
ю3ї1
2я1
я3а1
я3е1
я3о1
я3у1
я3ю1
2я3я1
я3є1
я3ї1
2є1
є3у1
є3ю1
2є3є1
є3ї1
2ї1
ї3е1
ї3о1
ї3ю1
2б1к
2б1п
2б1с
2б1т
2б1ф
2б1х
2б1ц
2б1ч
2б1ш
2б1щ
2в1б
2в1г
2в1д
2в1ж
2в1з
2в1к
2в1л
2в1м
2в1н
2в1п
2в1р
2в1с
2в1т
2в1ф
2в1х
2в1ц
2в1ч
2в1ш
2в1щ
2в1й
2в6'3
2в6’3
2г1к
2г1п
2г1с
2г1т
2г1ф
2г1ц
2г1ч
2г1ш
2д1к
2д1п
2д1с
2д1т
2д1ф
2д1х
2д1ц
2д1ч
2д1ш
2д1щ
2ж1к
2ж1п
2ж1с
2ж1т
2ж1ф
2ж1х
2ж1ц
2ж1ч
2ж1ш
2з1к
2з1п
2з1с
2з1т
2з1ф
2з1х
2з1ц
2з1ч
2з1ш
2з1щ
2к1б
2к1г
2к1д
2к1з
2л1б
2л1в
2л1г
2л1ґ
2л1д
2л1ж
2л1з
2л1к
2л1м
2л1н
2л1п
2л1р
2л1с
2л1т
2л1ф
2л1х
2л1ц
2л1ч
2м1б
2м1в
2м1г
2м1д
2м1ж
2м1з
2м1к
2м1л
2м1н
2м1п
2м1р
2м1с
2м1т
2м1ф
2м1х
2м1ц
2м1ч
2м1ш
2м1щ
2м6'3
2м6’3
2н1б
2н1в
2н1г
2н1д
2н1ж
2н1з
2н1к
2н1л
2н1м
2н1п
2н1р
2н1с
2н1т
2н1ф
2н1х
2н1ц
2н1ч
2н1ш
2н1щ
2н6'3
2н6’3
2п1б
2п1д
2п1з
2р1б
2р1в
2р1г
2р1ґ
2р1д
2р1ж
2р1з
2р1к
2р1л
2р1м
2р1н
2р1п
2р1с
2р1т
2р1ф
2р1х
2р1ц
2р1ч
2р1ш
2р1щ
2р1й
2р6'3
2р6’3
2с1б
2с1г
2с1д
2т1б
2т1г
2т1д
2т1ж
2т1з
2ф1б
2ф1г
2ф1з
2х1г
2х1д
2ц1б
2ц1г
2ц1д
2ц1з
2ч1б
2ч1д
2ч1ж
2ш1б
2ш1г
2й1б
2й1в
2й1г
2й1д
2й1ж
2й1з
2й1к
2й1л
2й1м
2й1н
2й1п
2й1р
2й1с
2й1т
2й1ф
2й1х
2й1ц
2й1ч
2й1ш
2й1щ
2дь1к
д6ь
2дь1с
2дь1т
2дь1ц
2зь1к
з6ь
2зь1с
2зь1т
2ль1б
л6ь
2ль1в
2ль1г
2ль1д
2ль1ж
2ль1з
2ль1к
2ль1м
2ль1н
2ль1п
2ль1р
2ль1с
2ль1т
2ль1ф
2ль1х
2ль1ц
2ль1ч
2ль1ш
2ль1щ
2ль1й
2нь1б
н6ь
2нь1г
2нь1з
2нь1к
2нь1л
2нь1м
2нь1с
2нь1т
2нь1х
2нь1ц
2нь1ч
2нь1ш
2нь1й
2рь1к
р6ь
2рь1ц
2сь1б
с6ь
2сь1д
2ть1б
т6ь
2к1сп
2к1ст
2к1ськ
кс6ь
2п1сп
2п1ст
2п1ськ
пс6ь
2с1пк
2с2п1п
2с1пс
2с1пт
2с1пх
2с1пч
2с1с
2с1ськ
сс6ь
2с1тк
2с1тп
2с1тс
2с1тс6ь
2с2т1т
2с1тф
2с1тц
2с1шт
2сь2к1к
2сь1кс
2сь1кт
2т1ск
2т1сп
2т1ст
2т1ськ
тс6ь
2т1шк
2ф1сп
2ф1ст
2ф1ськ
фс6ь
2ф1шт
2х1ст
2х1ськ
хс6ь
2ц1ст
2ц1шк
2ш1тк
2ш1тс6ь
2б1б
2в1в
2г1г
2ґ1ґ
2д1д
2ж1ж
2з1з
2к1к
2л1л
2м1м
2н1н
2п1п
2р1р
2т1т
2ф1ф
2х1х
2ц1ц
2ч1ч
2ш1ш
2щ1щ
2й1й
3нн2я1
3тт2я1
3тт2ю1
3лл2я1
3лл2є1
3лл2ю1
3дд2я1
д4ж
д4з
а2й
е2й
и2й
і2й
о2й
у2й
ю2й
я2й
є2й
ї2й
3й6о1
6ь
ь6о1
6'
6’
.б6'8
.б6’8
.в6'8
.в6’8
.д6'8
.д6’8
.з6'8
.з6’8
.м6'8
.м6’8
.н6'8
.н6’8
.п6'8
.п6’8
.р6'8
.р6’8
.т6'8
.т6’8
.ф6'8
.ф6’8
.ш6'8
.ш6’8
.бд6
.бр6
.в1б6
.вб6'6
.вб6’6
.в2в6'6
.в1в
.в2в6’6
.в1г6
.в1д6
.в1ж6
.в1з6
.вз6д6
.в1к6
.в1л6
.в1м6
.в2м6'6
.в2м6’6
.в1п6
.вп6'6
.вп6’6
.вп6х6
.в1с6
.вс6т6
.в1т6
.вш6к6
.в1ш
.д4ж6
.д4з6
.дл6
.д1с6
.зб6
.з2в6'6
.з2в6’6
.зг6
.зд6
.зд6з6
.зл6
.з2м6'6
.з2м6’6
.з1с6
.зс6к6
.зс6т6
.з1ч6
.з1ш6
.зш6к6
.зґ6
.й1ш6
.кл6
.кп6
.кс6
.кх6
.кш6
.лк6с6
.л1к
.л1с6
.л6ь6
.м1с6
.м1ф6
.н1б6
.пр6
.пс6
.пх6
.р1т6
.ск6
.ск6л6
.сл6
.сп6
.сп6'6
.сп6’6
.сп6л6
.с1п6х6
.ст6
.сх6
.сх6л6
.тк6
.тр6
.тх6
.т6ь6
.фл6
.хл6
.ц6ь6
.чх6
.шк6
.шл6
.шп6
.шт6
6бв.
6бз.
6бй.
6бл.
6б6ль.
бл6ь
6бн.
6бр.
6бс.
6б6ст.
6б6с6тв.
6б6с6тр.
6б6с6ьк.
бс6ь
6б6ць.
бц6ь
6вб.
6вв.
6вд.
6в6др.
6в6дь.
вд6ь
6вж.
6вз.
6в6зь.
вз6ь
6вй.
6вк.
6вл.
6в6ль.
вл6ь
6вм.
6вн.
6вп.
6вр.
6вс.
6в6с6тв.
6в6с6ть.
вст6ь
6в6сь.
вс6ь
6в6с6ьк.
6вт.
6вх.
6в6ць.
вц6ь
6вч.
6вш.
6вщ.
6гв.
6гг.
6гд.
6гл.
6г6ль.
гл6ь
6гм.
6гн.
6гр.
6гс.
6г6с6тв.
6г6сь.
гс6ь
6гт.
6дж.
6дз.
6д6зь.
дз6ь
6дл.
6дм.
6дн.
6др.
6д6с6тв.
6д6с6ьк.
дс6ь
6дт.
6дь.
6д6ь6сь.
дьс6ь
6жб.
6жв.
6ж6дь.
жд6ь
6ж6сь.
жс6ь
6зв.
6зг.
6зд.
6з6дв.
6з6дн.
6з6дь.
зд6ь
6зк.
6зл.
6зм.
6зн.
6з6нь.
зн6ь
6зр.
6з6сь.
зс6ь
6зь.
6з6ьб.
6з6ьк.
6йб.
6йв.
6йг.
6йд.
6йз.
6йк.
6й6кл.
6йл.
6й6ль.
йл6ь
6йм.
6й6мс.
й2м1с
6йн.
6й6нс.
й2н1с
6йп.
6йр.
6йс.
6й6ст.
6й6с6тв.
6й6с6тр.
6й6сь.
йс6ь
6й6с6ьк.
6йт.
6й6тс.
6йф.
6йх.
6йц.
6йч.
6йш.
6кв.
6кк.
6кл.
6к6ль.
кл6ь
6кр.
6кс.
6к6ст.
6к6сь.
6кт.
6к6тр.
6кх.
6кш.
6лб.
6лг.
6лд.
6лк.
6лл.
6л6ль.
лл6ь
6лм.
6л6мс.
л2м1с
6лн.
6лп.
6лс.
6лт.
6л6хв.
6ль.
6л6ьб.
6л6ьв.
6л6ьг.
6л6ьд.
6л6ь6дс.
ль2д1с
6л6ьз.
6л6ьк.
6л6ьм.
6л6ьн.
6л6ьп.
6л6ьс.
6л6ь6с6тв.
6л6ь6сь.
л6ьс6ь
6л6ь6с6ьк.
6л6ьт.
6л6ь6тр.
6л6ьф.
6л6ьх.
6л6ьц.
6л6ьч.
6л6ьш.
6л6ьщ.
6мб.
6м6б6ль.
мбл6ь
6м6бр.
6мг.
6мж.
6мк.
6мл.
6м6ль.
мл6ь
6мм.
6мн.
6мп.
6мр.
6мс.
6м6с6тв.
6м6сь.
мс6ь
6м6с6ьк.
6мт.
6мф.
6мх.
6мш.
6нв.
6нг.
6н6гл.
6н6г6ль.
нгл6ь
6н6гр.
6н6гс.
н2г1с
6нд.
6н6дж.
нд4ж
6н6дз.
нд4з
6н6дп.
н2д1п
6н6др.
6нж.
6нз.
6нк.
6н6кс.
6н6кт.
6нм.
6нн.
6нр.
6нс.
6н6ск.
6н6ст.
6н6с6тв.
6н6с6тр.
6н6с6ьк.
нс6ь
6н6с6ькй.
6нт.
6н6тк.
6н6тр.
6н6т6с6тв.
н2т1ст
6н6ть.
нт6ь
6нф.
6нх.
6нц.
6н6ць.
нц6ь
6нч.
6нш.
6нь.
6н6ьб.
6н6ьг.
6н6ьк.
6н6ь6сь.
ньс6ь
6пд.
6пл.
6пр.
6пс.
6п6с6тв.
6п6сь.
6пт.
6п6тр.
6пф.
6пц.
6рб.
6рв.
6рг.
6рд.
6р6дв.
6р6дж.
рд4ж
6р6дь.
рд6ь
6рж.
6рз.
6р6зн.
6р6зь.
рз6ь
6рк.
6р6кс.
6р6кт.
6рл.
6р6ль.
рл6ь
6р6л6ьз.
р2ль1з
6рм.
6рн.
6р6нс.
р2н1с
6р6н6ст.
6р6нь.
рн6ь
6рп.
6рр.
6рс.
6р6ср.
6р6ст.
6р6с6тв.
6р6с6ть.
рст6ь
6р6сь.
рс6ь
6р6с6ьк.
6рт.
6р6тв.
6р6тр.
6р6ть.
рт6ь
6рф.
6рх.
6рц.
6р6ць.
рц6ь
6рч.
6рш.
6рщ.
6р6щ6сь.
рщс6ь
6рь.
6с6дп.
с2д1п
6с6д6рп.
сд2р1п
6ск.
6сл.
6с6ль.
сл6ь
6см.
6сн.
6сп.
6сс.
6ст.
6с6тв.
6с6тй.
6с6тм.
6с6тр.
6с6т6рь.
стр6ь
6с6ть.
ст6ь
6с6ць.
сц6ь
6сь.
6с6ьб.
6с6ьк.
6с6ьм.
6тв.
6т6вт.
т2в1т
6т6зт.
т2з1т
6тл.
6т6ль.
тл6ь
6тм.
6т6мр.
т2м1р
6тр.
6тс.
6т6с6тв.
6т6с6ьк.
6тт.
6тц.
6тч.
6ть.
6т6ь6сь.
тьс6ь
6фм.
6фр.
6ф6с6тв.
6фт.
6ф6ть.
фт6ь
6фф.
6фь.
ф6ь
6хв.
6хм.
6хн.
6хр.
6хт.
6хш.
6ц6тв.
6ць.
ц6ь
6ц6ьк.
6чб.
6чм.
6чн.
6чт.
6шв.
6ш6ль.
шл6ь
6шм.
6шн.
6ш6нл.
ш2н1л
6ш6сь.
шс6ь
6шт.
6ш6тв.
6щ6сь.
щс6ь
.бе4з3
.бе4з6'3
.б2е1
.бе4з6’3
.без2у4
.бе5з4о3д
.без2о1
.без5о4соб
.безос2о1
.безві4д3
.безв2і1
.без3ро4з3
.безр2о1
ви3ї4
в2и1
.ві4д3
.ві5д4ом
.в2і1
.від2о1
.ві5д4озв
.ві5д4ун
.від2у1
віду4ч
в2і1
від2у1
.ві5д4а1
.ві5д4ер
.від2е1
.в2і5д4і1
.від6'3
.від6’3
.мі4ж3
.м2і1
безві4д3
б2е1
безв2і1
ові4д3
ов2і1
ді4єві4д3
д2і1
ді3є1
дієв2і1
за4вві4д3
з2а1
за2в1в
завв2і1
неві4д3
неві4д6'3
н2е1
нев2і1
неві4д6’3
3п4р2о1
про4ф3ві4д3
профв2і1
спе4ц3ві4д3
сп2е1
спецв2і1
3с4п2і1
співві4д3
спі2в1в
співв2і1
те4х3ві4д3
т2е1
техв2і1
.п2е1р2е3
.пере4д3бач
.п2е1
.передб2а1
.пере4д3виб
.передв2и1
.пере4д3г
.пере4д3д
.пере4д3м
.пере4д3ост
.перед2о1
.пере4д3пл2а1
.пере2д1п
.пере4д3пок
.передп2о1
.пере4д3р
.пере4д3св
.пере2д1с
.пере4д5у4мов
.перед2у1
.передум2о1
.пере4д3ус2і1
.пере4д3фр
.пере2д1ф
.пере4д3ч
.пере4д6'3
.пере4д6’3
.пона4д3
.п2о1
.пон2а1
.пона5д4і1
.пона5д4и1
.пона5д4я1
3п4р2е1
3п4р2и1
при3ї4
3п4р2і1
.пі4д3
.пі5д4о1
.п2і1
.п2і5д4і1
.під6'3
.під6’3
.пі5д4е1
.пі5д4и1
.пі5д4у1
.пі4в3
.спі4в3
.с4п2і1
.напі4в3
.н2а1
.нап2і1
.ро4з3
.ро5з4і1
.р2о1
.ро5з4е1
ро5з4йом
р2о1
р2о1з3й6о1
.ро5з4а1
.ро4з6'3
.ро4з6’3
.чере4з3
.чере4з6'3
.ч2е1
.ч2е1р2е1
.чере4з6’3
оо4б
ооб6'3
ооб6’3
ооб3м
2о2о1б3р2о1
об6'3
об6’3
од6'3
од6’3
на4д6'3
н2а1
на4д6’3
за5о4р
за3о1
до5о4р
д2о1
д2о3о1
по5о4р
п2о1
п2о3о1
пере5о4р
п2е1
п2е1р2е1
пере3о1
пі6д5о4р
п2і1
під2о1
бе4з5і4де2й
без2і1
безід2е1
до3в'4є1
до2в6'3
до3в’4є1
до2в6’3
за3в'4є1
за2в6'3
за3в’4є1
за2в6’3
зі3в'4є1
з2і1
зі2в6'3
зі3в’4є1
зі2в6’3
обі3в'4є1
об2і1
обі2в6'3
обі3в’4є1
обі2в6’3
по3в'4є1
по2в6'3
по3в’4є1
по2в6’3
уі3в'4є1
уі2в6'3
уі3в’4є1
уі2в6’3
з3в'4я1
з2в6'3
з3в’4я1
з2в6’3
по3в'4я1
по3в’4я1
від3в'4я1
від2в6'3
від3в’4я1
від2в6’3
за3в'4я1
за3в’4я1
зі3в'4я1
зі3в’4я1
за3ю4ш
за3ю1
на3в'4я1
на2в6'3
на3в’4я1
на2в6’3
непо3в'4я1
неп2о1
непо2в6'3
непо3в’4я1
непо2в6’3
об3в'4я1
об2в6'3
об3в’4я1
об2в6’3
при3в'4я1
при2в6'3
при3в’4я1
при2в6’3
під3в'4я1
під2в6'3
під3в’4я1
під2в6’3
у3в'4я1
у2в6'3
у3в’4я1
у2в6’3
з3м'4я1
з2м6'3
з3м’4я1
з2м6’3
зі3м'4я1
зі2м6'3
зі3м’4я1
зі2м6’3
у3м'4я1
у2м6'3
у3м’4я1
у2м6’3
в3м'4я1
в2м6'3
в3м’4я1
в2м6’3
но3м'4я1
н2о1
но2м6'3
но3м’4я1
но2м6’3
за3м'4я1
за2м6'3
за3м’4я1
за2м6’3
на3м'4я1
на2м6'3
на3м’4я1
на2м6’3
об3м'4я1
об2м6'3
об3м’4я1
об2м6’3
пере3м'4я1
пере2м6'3
пере3м’4я1
пере2м6’3
по3м'4я1
по2м6'3
по3м’4я1
по2м6’3
при3м'4я1
при2м6'3
при3м’4я1
при2м6’3
піді3м'4я1
п2і1д2і1
піді2м6'3
піді3м’4я1
піді2м6’3
су3м'4я1
с2у1
су2м6'3
су3м’4я1
су2м6’3
до3в'4ю1
до3в’4ю1
за3в'4ю1
за3в’4ю1
зі3в'4ю1
зі3в’4ю1
на3в'4ю1
на3в’4ю1
по3в'4ю1
по3в’4ю1
уі3в'4ю1
уі3в’4ю1
інтер3в'4ю1
і2н1т
інт2е1
інте2р1в
інтер2в6'3
інтер3в’4ю1
інтер2в6’3
за3я4л2о1
за3я1
коу4роч
к2о1
ко3у1
коур2о1
зу4роч
з2у1
зур2о1
наду4роч
над2у1
надур2о1
позау4роч
поз2а1
поза3у1
позаур2о1
поу4роч
по3у1
поур2о1
приу4роч
при3у1
приур2о1
на4й3у4бог
на2й
най2у1
найуб2о1
нао4р
на3о1
прио4р
при3о1
неу4к
не3у1
3в4б4лаг
вбл2а1
3в4к4лад
вкл2а1
3в4п4лив
впл2и1
3в4п4ра2в1н
впр2а1
3в4р4одлив
вр2о1
вродл2и1
3в4т4рут
втр2у1
3в4т4руч
3з4б4ро2й
збр2о1
3з4б4ро3ю1
3з4б4ро3є1
3з4в4'4яз
3з4в4’4яз
3й4ш4л
3м4к4н2е1
3м4к4н2у1
3м4к4н2і1
3п4с4ков
пск2о1
3с4к4лад
скл2а1
3с4к4л2е1
3с4к4лит
скл2и1
3с4к4л2о1
3с4к4рипт
скр2и1
3с4п4лав
спл2а1
3с4п4лат
3с4п4лач
3с4п4рав
спр2а1
3с4п4ритн
с3п4р2и1
3с4п4рият
спри3я1
3с4п4р2о1м2о1
с3п4р2о1
3с4т4вор
ств2о1
3с4т4ражд
стр2а1
3с4т4рах
3с4т4риб
стр2и1
3с4т4риж
3с4т4ро2й
стр2о1
3с4т4рок
3с4т4ром
3с4т4роф
3с4т4роч
3с4т4ро3ю1
3с4т4ро3я1
3с4т4ро3є1
3с4т4ро3ї1
3с4т4рукт
стр2у1
3с4т4рукц
3с4т4рі2й
стр2і1
3с4т4ріл
3с4т4річ
3т4к4нен
ткн2е1
3т4ь4мар
тьм2а1
3т4ь4мян2і1
тьм2я1
3у4п4рав
упр2а1
3блаж
бл2а1
3ближ
бл2и1
3близ
3блиск
3блок
бл2о1
3блоц2і1
3бран
бр2а1
3брат2и1
3брест
бр2е1
3бри2з1к
бр2и1
3британ
брит2а1
3бруд
бр2у1
3в4бив
вб2и1
3в4веден
вв2е1
введ2е1
3в4дал
вд2а1
3в4до2в1з
вд2о1
3в4довол
вдов2о1
3в4живан
вж2и1
вжив2а1
3в4лад
вл2а1
3в4ласн
3в4лашт
3в4лов
вл2о1
3в4пе2в1н
вп2е1
3в4поряд
вп2о1
впор2я1
3в4разлив
вр2а1
вразл2и1
3в4рожа2й
врож2а1
3в4сюд
вс2ю1
3в4тіл
вт2і1
3глад
гл2а1
3глиб
гл2и1
3глин
3глоб
гл2о1
3глуз
гл2у1
3глуш
3гляд
гл2я1
3глян
3гнан
гн2а1
3гнил
гн2и1
3гноз
гн2о1
3гнучк
гн2у1
3грав
гр2а1
3град
3гра2й
3грам
3гран
3грат2и1
3граф
3граш
3гра3ю1
3гра3є1
3грес
гр2е1
3грец6ь
3гроб
гр2о1
3грож
3гроз
3громад
гром2а1
3груван
гр2у1
грув2а1
3гру2н1т
3груп
3грів
гр2і1
3гріт
3гріш
3г4ідр2о1
г2і1
3д4ан
д2а1
3д4бав
дб2а1
3д4бал
3д4бан
3д4бат
3д4ба3є1
3двиг
дв2и1
3дво3ю1
дв2о1
3дво3є1
3дві2й
дв2і1
3двір
3дв2і1ч2і1
3драж
др2а1
3дром
др2о1
3друж
др2у1
3друк
3дряп
др2я1
3дріб
др2і1
3дрім2а1
3жвав
жв2а1
3жміт6ь
жм2і1
3жріт6ь
жр2і1
3з4б2а1г2а1
зб2а1
3з4бала2н1с
збал2а1
3з4був
зб2у1
3з4бут
3зваж
зв2а1
3зван
3звед
зв2е1
3звел
3звест
3звис6ь
зв2и1
3звич
3звищ
3зворуш
зв2о1
звор2у1
3звук
зв2у1
3звуч
3звіт
зв2і1
3з4год
зг2о1
3з4дат
зд2а1
3з4до2в1ж
зд2о1
3з4доров
здор2о1
3з4дійсн
зд2і1
зді2й
зді2й1с
3змін
зм2і1
3зйом
з3й6о1
3зміш
3знав
зн2а1
3зна2й
3знак
3знал
3знан
3знат
3знаход
знах2о1
3знач
3зна3ю1
3зна3є1
3зниж
зн2и1
3знім
зн2і1
3зро5з4ум
зр2о1
зроз2у1
3зрюв
зр2ю1
3зрів
зр2і1
3зріл
3зрін
3з4чеп
зч2е1
3й4м2а1
3й4ме2н1н
йм2е1
3й4мищ
йм2и1
3й4мові2р1н
йм2о1
ймов2і1
3й4му.
йм2у1
3й4мут6ь
3й4міт6ь
йм2і1
3й4шов
йш2о1
3м4нож
мн2о1
3м4ріт6ь
мр2і1
3м4щен
мщ2е1
3п4сов
пс2о1
3п4сон
3п4сув
пс2у1
3р4вав
рв2а1
3р4ват2и1
3р4віт6ь
рв2і1
3с4кид
ск2и1
3с4кок
ск2о1
3с4коп
3с4кор
3с4короч
скор2о1
3с4коч
3с4кіл6ь
ск2і1
3с4кіпл
3с4пад
сп2а1
3с4пект
3с4пе2р1м
3с4пин
сп2и1
3с4пові4д3
сп2о1
спов2і1
3с4пожив
спож2и1
3с4постер
спост2е1
3с4піт6ь
3с4піш
3с4табіл
ст2а1
стаб2і1
3с4тав
3с4тад
3с4таз
3с4та2й1н
ста2й
3с4тал
3с4тан
3с4тар
3с4т2а1р2а1
3с4тат
3с4тач
3с4та3є1
3с4теп
ст2е1
3с4тереж
стер2е1
3с4теріг
стер2і1
3с4тиг
ст2и1
3с4тиж
3с4тисл
3с4тит2у1
3с4то2в1б
ст2о1
3с4то2й
3с4торон
стор2о1
3с4торін
стор2і1
3с4т2о1с2о1
3с4тос2у1
3с4то3ю1
3с4тоян
сто3я1
3с4туп
ст2у1
3с4тяг
ст2я1
3с4тіб
ст2і1
3с4ті2й
3с4тіл6ь
3с4тір
3с4фер
сф2е1
3с4хил
сх2и1
3с4хов
сх2о1
3с4хід
сх2і1
3т4кан
тк2а1
3х4т2о1
3ш4код
шк2о1
3ш4кол
3ш4кідл
шк2і1
3ш4кіл
3ш4кір
3ш4таб
шт2а1
3ш4туч
шт2у1
3ґру2н1т
ґр2у1
3а4вторит
а2в1т
авт2о1
автор2и1
3а4ге2н1т
аг2е1
3а4грес
агр2е1
3а4декват
ад2е1
адекв2а1
3а4дитив
ад2и1
адит2и1
3а4за2р1т
аз2а1
3а4ктив
акт2и1
3а4ктуал
акт2у1
2а1кту3а1
3а4курат
ак2у1
акур2а1
3а4куст
3а4кцепт
акц2е1
3а4кциз
акц2и1
3а4лерг2і1
ал2е1
але2р1г
3а4матор
ам2а1
амат2о1
3а4наліз
2а1н2а1
анал2і1
3а4натом
анат2о1
3а4парат
ап2а1
апар2а1
3а4пеляц
ап2е1
апел2я1
3а4постол
ап2о1
апост2о1
3а4птеч
апт2е1
3а4ргумен
а2р1г
арг2у1
аргум2е1
3а4ромат
ар2о1
аром2а1
3а4соц2і1
ас2о1
3а4с4пект
асп2е1
3а4тлет
атл2е1
3а4хал2і1
ах2а1
3е4колог
ек2о1
екол2о1
3е4коном
екон2о1
3е4лега2н1т
2е1л2е1
елег2а1
3е4лектр
3е4леме2н1т
елем2е1
3е4моц2і1
ем2о1
3е4мігр
ем2і1
3е4не2р1г
2е1н2е1
3е4стакад
ест2а1
естак2а1
3е4стет
ест2е1
3е4тап
ет2а1
3о4б'3є4д3н
об'2є1
3о4б’3є4д3н
об’2є1
3о4б'єкт
3о4б’єкт
3о4береж
об2е1
обер2е1
3о4бир2а1
об2и1
3о4борон
2о1б2о1
обор2о1
3о4бід
3о4біц
3о4дал6ь
од2а1
3о4дяг
од2я1
3о4збр2о1
3о4крем
окр2е1
3о4перат
оп2е1
опер2а1
3о4плат
опл2а1
3о4птим
опт2и1
3о4пуст
оп2у1
3о4пуш2е1
3о4пущ2е1
3о4рдинац
о2р1д
орд2и1
ордин2а1
3о4ре2н1д
ор2е1
3о4соб
ос2о1
3о4сяжн
ос2я1
3о4х2о1л2о1
2о1х2о1
3о4хорон
охор2о1
3о4хоч
3о4чисн
оч2и1
3о4чищ
3у4ваг
ув2а1
3у4важ
3у4гав
уг2а1
3у4з4год
узг2о1
3у4клад
укл2а1
3у4компл
ук2о1
уко2м1п
у4к4р
3у4крупн
укр2у1
3у4люблен
ул2ю1
улюбл2е1
3у4мит
ум2и1
3у4міл
ум2і1
3у4перед
уп2е1
упер2е1
3у4разлив
ур2а1
уразл2и1
3у4рбан
у2р1б
урб2а1
3у4рочист
ур2о1
уроч2и1
3у4ряд
ур2я1
3у4рядов
уряд2о1
3у4с4піш
у3с4п2і1
3у4станов
у3с4тан
уст2а1
устан2о1
3у4стпіш
у2с1тп
устп2і1
3у4суват2и1
ус2у1
усув2а1
3у4твор
утв2о1
3у4тробн
утр2о1
3я4де2р1н
яд2е1
3я4зик
яз2и1
3я4кіс
як2і1
3я4рус
яр2у1
3я4скрав
яскр2а1
3є4д3н
3є4дин
єд2и1
3є4писк
єп2и1
3є4ре2й
єр2е1
3і4зотоп
із2о1
ізот2о1
3і4люстр
іл2ю1
3і4мовір
ім2о1
імов2і1
3і4нте2н1с
3і4нфо2р1м
і2н1ф
інф2о1
3і4ніціат
ін2і1
ініц2і1
ініці3а1
3і4снув
існ2у1
3ї4жд4ж
3ї4зд
3ї4ст
3ї4хав
їх2а1
3ї4хат
.заї4к
.з2а1
.за3ї1
.заї4ц
.заї4ч
.наї4д
.на3ї1
'ї4в
'2ї1
’ї4в
’2ї1
'ї4з
’ї4з
'ї4д
’ї4д
'ї4ж
’ї4ж
'ї4л
’ї4л
'ї4м
’ї4м
'ї4с
’ї4с
'ї4х
’ї4х
2а1ві4а1
ав2і1
авої4д
ав2о1
аво3ї1
ае4тил
ает2и1
альбі5он
а2ль1б
ал6ь
альбі4о3
альб2і1
ахої4д
ах2о1
ахо3ї1
ауді4о1
ауд2і1
ай4с3бе2р1г
а2й1с
ай2с1б
айсб2е1
бактері4о1
б2а1
бакт2е1
бактер2і1
ба4с3енер
бас2е1
басен2е1
б2а4с3а1нтр2а1
бас2а1
баса2н1т
.бе5зе.
.без2е1
бей4сбол
бе2й
бе2й1с
бей2с1б
бейсб2о1
бе5кон
бек2о1
б'4єт6ь
б6'
б'2є1
б’4єт6ь
б6’
б’2є1
бйор4н1с
б3й6о1
бйо2р1н
бі4о3
б2і1
бо4г3д4ан
б2о1
богд2а1
бра2н4д
брі4дж3по2р1т
бр2і1
брід4ж
брід2ж1п
бріджп2о1
без5і4мен
безім2е1
бо4є3гол
бо3є1
б2о1єг2о1
бо4є3гот
бо4є3зап
боєз2а1
бо4є3з4дат
боєзд2а1
бо4є3ко2м1п
боєк2о1
бо4є3пост
боєп2о1
бо4є3прип
боє3п4р2и1
бори4с5п
бор2и1
4в3антрац
в2а1
ва2н1т
вантр2а1
вер4х3н
в2е1
ве2р1х
ви3й4д
ви2й
вина3й4д
вин2а1
вина2й
ви3й4т
вина3й4т
від7зн2а1
від4з
ві5д4ен
від2е1
ві5д4е4о1
ві5д4ом
від2о1
від5о4браж
відобр2а1
від5о4браз
в2о4с5к2о1
в2о1
водо5с4ток
в2о1д2о1
водост2о1
водо5з4бір
водозб2і1
воль4т3метр
во2ль1т
вол6ь
вольтм2е1
воль4т3ампер
вольт2а1
вольта2м1п
вольтамп2е1
3в'4яз
в'2я1
3в’4яз
в’2я1
ге2ть3ман
г2е1
гет6ь
гетьм2а1
ге4о1
го4с4п5роз
г2о1
г2о1с3п4р2о1
гі4д5ро5мет
гі3дром
гідром2е1
4д7зем
дз2е1
дер4ж5а4том
д2е1
де2р1ж
держ2а1
держат2о1
дер4ж5а4дм
дер4ж5бюд4ж
держб2ю1
дер4ж5вид
держв2и1
дер4ж5дум
держд2у1
дер4ж5замов
держз2а1
держзам2о1
дер4ж5ком
дер2ж1к
держк2о1
дер4ж5нафт
держн2а1
дер4ж5реєс
д2е1ржр2е1
держре3є1
дер4ж3без
держб2е1
дер4ж3резе2р1в
держрез2е1
дер4ж5стр
дер2ж1с
дер4ж5служ
держсл2у1
двох4а5том
двох2а1
двохат2о1
джен4тл6ь
дж2е1
дже2н1т
дисбала2н1с
д2и1
ди2с1б
дисб2а1
дисбал2а1
ди4с3га2р1м
ди2с1г
дисг2а1
ди4с3квал
дискв2а1
ди4с3ко2м1ф
диск2о1
ди4с3ко2н1т
ди4с3кред
дискр2е1
ди4с3крет
ди4с3крец
ди4с3крим
дискр2и1
ди4с3кус2і1
диск2у1
ди4с3к2у1т2у1
ди4с3лок
дисл2о1
ди4с3парит
дисп2а1
диспар2и1
ди4с3пе2р1с
дисп2е1
ди4с3петч
ди4с3пл2е1
ди4с3пле2й
д2и4с3поз2и1
дисп2о1
ди4с3проп
дис3п4р2о1
ди4с3пут
дисп2у1
ди4с3тил
дист2и1
ди4с4т4риб
дистр2и1
ди4с4т4роф
дистр2о1
ди4с3функц
дисф2у1
дисфу2н1к
ді3й4т
ді2й
ді3й4д
д4ні3п4р
дн2і1
.дої4в
.д2о1
.до3ї1
.дої4л
.дої5л6ь
дої4д
до3ї1
дої4м
дої4х
дої4ж
до3ї4ст
до3з4вол
дозв2о1
до3з4віл
дозв2і1
дорого5в4каз
дор2о1
дорог2о1
дорого2в1к
дороговк2а1
еу4стр
ео4св2і1
енерго3з4береж
енерг2о1
2е1н2е1ргозб2е1
енергозбер2е1
енерго3з4беріг
енергозбер2і1
ек2с1к
е2к2с1п
е2к2с1т
ек2с1ц
єв4р3атом
є2в1р
євр2а1
єврат2о1
єпі4с5коп
єп2і1
єпіск2о1
єпи4с5коп
єписк2о1
за4п3част
запч2а1
заї4д
за3ї1
заї4ж
заї4з
заї4л
заї4м
заї4х
зе4коном2и1т2и1
з2е1
з3е4коном
зек2о1
зекон2о1
зеконом2и1
3з'4яс2о1
з6'
з'2я1
3з’4яс2о1
з6’
з’2я1
зна3й4д
зна3й4т
зо4к2а1
з2о1
зо4к2е1
зо4к2и1
зо4к2у1
зо4к2і1
игої4д
иг2о1
иго3ї1
2и1й4т2и1
и2й1т
іе4тил
іет2и1
і4л3е4тил
іл2е1
ілет2и1
ій4т2и1
і2й1т
інфор4м3аген
інформ2а1
інформаг2е1
йо4св2і1
каза4х3с4тан
к2а1
каз2а1
каза2х1ст
казахст2а1
квої4д
кв2о1
кво3ї1
корої4д
кор2о1
коро3ї1
квар4т3плат
кв2а1
ква2р1т
квартпл2а1
киї4венер
к2и1
ки3ї1
київ2е1
київен2е1
кон4трр2е1
ко2н1т
конт2р1р
кон4тр3а2р1г
контр2а1
жко4м5а4том
жк2о1
жком2а1
жкомат2о1
кому4н3е4не2р1г
ком2у1
комун2е1
комунен2е1
мі4н5е4к2о1
м2і1
мін2е1
мі4н5е4нер
мінен2е1
мо4к5ри2й
м2о1
мокр2и1
3м'4якш
м'2я1
3м’4якш
м’2я1
3м'4ят
3м’4ят
на3б4лиз
набл2и1
на3в4ряд
на2в1р
навр2я1
н2а3в4ч2а1
на2в1ч
на3з4в
на4д7з4в
над4з
наї4в1с
на3ї1
наї4в1ш
наї4ж
наї4з
наї4л
наї4м
наї4с
наї4х
н2а4й3а1
на4й3е1
на4й7о4бер
на4й3о4береж
на3й6о1
найоб2е1
найобер2е1
на4й7о4гид
найог2и1
на4й7о4гол
найог2о1
на4й7о4гряд
найогр2я1
на4й7о4пук
найоп2у1
на4й7о4ха2й
найох2а1
н2а3й4м2а1
на4й3масл
на2й1м
на4й3с3п4р2и1
на2й1с
на4й3я4кіс
най2я1
найяк2і1
на3в4чен
навч2е1
на3в4чіт6ь
навч2і1
не3в4том
не2в1т
невт2о1
не3д4бан
недб2а1
на3д4бан
надб2а1
не3з4вич
незв2и1
не3з4важ
незв2а1
нео4пал
не3о1
неоп2а1
недо3ї4
нед2о1
не3ї4ст
не3ї1
на5п4лив
напл2и1
ні4т5рат
н2і1
нітр2а1
оної4д
он2о1
оно3ї1
оо4пал
ооп2а1
ео4пал
еоп2а1
обі3д4ран
обідр2а1
обі3й4д
обі2й
обі3й4т
об5у4мов
об2у1
обум2о1
онаї4д
он2а1
она3ї1
оо4св2і1
оо4к
оу4с
оу4стр
оа4том
оат2о1
об4лде2р4ж
об2л1д
облд2е1
об4л3а4дмін
обл2а1
обладм2і1
переї4д
пере3ї1
переї4ж
переї4з
переї4л
переї4с
переї4х
пере5п4лив
перепл2и1
пере3й4д
пере2й
пре4й4с
пре2й
пере3й4т
перег4ні2й
перегн2і1
перед5о4бід
перед2о1
передоб2і1
пере3в4том
пере2в1т
перевт2о1
пере4д5см
пере2д1с
перед5у4мов
перед2у1
передум2о1
під5о4дин
підод2и1
піво4с
пів5о4с4тр
пів2о1
пі5в4ен6ь
пів2е1
по3б4лиз2у1
по3близ
побл2и1
по3в4тор
по2в1т
повт2о1
поч4н2е1
поч4н2и1
поч4н2у1
поя4в
по3я1
по3в4чен
по2в1ч
повч2е1
по3в4чіт6ь
повч2і1
по3д4роб
подр2о1
по3д4раз
подр2а1
п2о3д4в2о1
по5ж4ніт6ь
пожн2і1
по5з4бав
позб2а1
.по3ї4
пої4д
по3ї1
про3ї4
по3ї4зд
по4с4т5радян
постр2а1
пострад2я1
пос4т3ком
по4с4т5ком2у1
по2с1тк
п2о1стк2о1
по4с4т3декр
пос2т1д
постд2е1
по4с4т3контр2а1
постко2н1т
по4с4т3менопауз
постм2е1
постмен2о1
постменоп2а1
постменопа3у1
по4с4тприват
по2с1тп
пост3п4р2и1
постприв2а1
по4с4т3рад2і1
пос4т3соц
по4с4т5соц2і1
по2с1тс
п2о1стс2о1
пос4т3кап
постк2а1
пос4т3нат
постн2а1
пос4т3проц
п2о1ст3п4р2о1
пос4т3фікс
по2с1тф
постф2і1
при3й4т
при2й
про3с4тирад
прост2и1
простир2а1
про4ф3с
полі4т5екон
пол2і1
політ2е1
політек2о1
пор4т3н
по2р1т
пор4т3рет
портр2е1
пор4т3фел
портф2е1
при3й4д
при4нцип
при2н1ц
принц2и1
про4ект3н
про3е1
про3б4лем
пробл2е1
про4м3ма2й
про2м1м
промм2а1
пр4о5плат
пропл2а1
раді4о1
р2а1
рад2і1
рай3в4н2о1
ра2й
ра2й1в
рай2в1н
р2о4з5д4в2о1
ро4з5мінни2й
ро3змін
розм2і1
розмі2н1н
розмінн2и1
роз5у4чен
роз2у1
розуч2е1
роз5і4мен
роз2і1
розім2е1
роз5ва2н1т
ро3зван
розв2а1
роз5вин
розв2и1
роз5вит
ро4з5діл
розд2і1
ро4з5гор
розг2о1
ро4з5вер
розв2е1
ро4з5чеп
ро2з1ч
розч2е1
ро4з'5є4д3н
роз6'
роз'2є1
ро4з’5є4д3н
роз6’
роз’2є1
з'4є4д3н
з'2є1
з’4є4д3н
з’2є1
руко5с4тиск
р2у1
рук2о1
рукост2и1
ро5з4ум
ро4з3гром
розгр2о1
ро4з3лив
розл2и1
рмої4д
рм2о1
рмо3ї1
сан4к4т3
с2а1
са2н1к
сеї4д
с2е1
се3ї1
серцеї4д
се2р1ц
серц2е1
серце3ї1
спе4ц3кур
спецк2у1
спе4ц3мон
спецм2о1
спе4цпр
спе4ц3с
спор4т3вир
спо2р1т
спортв2и1
спор4т3зал
спор2т1з
спортз2а1
спор4т3ком
спортк2о1
спор4т3клуб
спорткл2у1
спор4т3ма2й
спортм2а1
спор4т4с3м2е1
сор4тн
с2о1
со2р1т
3с4промож
сь4квуг
ськв2у1
стат5упр
стат2у1
тор4г3пред
т2о1
то2р1г
тор2г1п
торг3п4р2е1
тра2н4с3
тр2а1
тур4к3мен
т2у1
ту2р1к
туркм2е1
цук3р2о1
ц2у1
цу4к4р
укр3а4в1т
укр2а1
укр3а4гр
укр3е4кс
укр2е1
укр3і4н4ба2н1к
укр2і1
укрі2н1б
укрінб2а1
убої4д
уб2о1
убо3ї1
чорно3б4рив
ч2о1
чо2р1н
чорн2о1
чорнобр2и1
цен4т4р3е4не2р1г
ц2е1
це2н1т
центр2е1
центрен2е1
ясої4д
яс2о1
ясо3ї1
ви3у4ч
ви3у1
за3у4ч
за3у1
на3у4ч
на3у1
недо3у4ч
недо3у1
не3у4ч
під3у4ч
під2у1
пед3у4ч2и1
пед2у1
пере3у4ч
пере3у1
само3у4ч
сам2о1
само3у1
вия4в
ви3я1
з'я4в
з’я4в
зая4в
ная4в
на3я1
уя4в
во4євод
во3є1
воєв2о1
во4єнач
воєн2а1
сво4єчас
св2о1
сво3є1
своєч2а1
сво4єкорис
своєк2о1
своєкор2и1
сво4єрід
своєр2і1
хво4є3г4риз
хв2о1
хво3є1
хвоєгр2и1
гелі4о1
гел2і1
ді4о1
еті4о1
ет2і1
мі4о1
і4он
п2о1лі4о1
с2о1ці4о1
соц2і1
фізі4о1
ф2і1
фіз2і1
хімі4о1
х2і1
хім2і1
г2о1ме4о1
гом2е1
ді4алог
ді3а1
діал2о1
ді4оген
діог2е1
дея4к
де3я1
оо4динок
оод2и1
оодин2о1
ао4пік
аоп2і1
2а1о4х2а1
ео4х2а1
з2о6о1
ка5нал
кан2а1
оі4зол
оіз2о1
міжу4соб
між2у1
міжус2о1
мете4о1
м2е1
мет2е1
абия4к
аб2и1
аби3я1
нія4к
ні3я1
вия4сн
най3я4сн
нея4сн
не3я1
поя4с
поя4сн
проя4сн
про3я1
роз'я4сн
роз'2я1
роз’я4сн
роз’2я1
розо4р2а1
ро5з4ора.
р2о1з2о1
ро5з4о5рам
ро5з4орах
ро5з4ор2и1
р2о5з4о1р2о1
ро5з4ор2у1
ро5з4ор2я1
ро5з4ор2ю1
ро5з4ор2і1
ро6з5о4ри.
розо4р2е1
розо4реш
розо4р1н
напоу4м
нап2о1
напо3у1
неа4б2и1
не3а1
ео4цін
еоц2і1
оо4цін
ооц2і1
доу4к
до3у1
доу4м
ео4бур
еоб2у1
ео4голош
еог2о1
еогол2о1
ео4зор
еоз2о1
бальне4о1
ба2ль1н
бал6ь
бальн2е1
не4оліт
неол2і1
не4ома2ль1т
неом2а1
неомал6ь
не4оклас
неокл2а1
не4окомун
неок2о1
неоком2у1
не4олан2д1ш
неол2а1
неола2н1д
не4олог
неол2о1
не4олібер
неоліб2е1
не4онац
неон2а1
не4офіт
неоф2і1
нею4н
не3ю1
нея4к
нея4рок
неяр2о1
но4к3а4ут
нок2а1
нока3у1
п2і5в4он2і1
пале4о1
п2а1
пал2е1
па4н3о4тец6ь
пан2о1
панот2е1
.пе4ом.
.пе3о1
д3у4сім
д2у1
дус2і1
п4о5б2е1р2е1
поб2е1
ао4хот
аох2о1
2о1е4к2о1
ео4хот
еох2о1
ео4щад
еощ2а1
ао4щад
аощ2а1
о3о4чищ
ооч2и1
роз'я4р
роз’я4р
те4одоліт
те3о1
теод2о1
теодол2і1
те4олог
теол2о1
те4ософ
теос2о1
оо4біг
ооб2і1
оу4сун
оус2у1
оу4ком
оук2о1
пів3о4вал
півов2а1
а3у4дар
ауд2а1
о3у4дар
оуд2а1
з3у4дар
зуд2а1
в3у4дар
в2у1
вуд2а1
контр3у4дар
контр2у1
контруд2а1
о3о4кисл
оок2и1
и3о4кисл
иок2и1
ень7о4кисл
ен6ь
ень6о1
еньок2и1
е3о4кисл
еок2и1
х3о4кисл
х2о1
хок2и1
и3і4стор
иіст2о1
о3і4стор
2о1іст2о1
і3і4стор
2і2і1
ііст2о1
а3і4стор
аіст2о1
я3і4стор
я2і1
яіст2о1
е3і4стор
еіст2о1
наді4стор
над2і1
надіст2о1
най3і4стор
най2і1
найіст2о1
пів3і4стор
пів2і1
півіст2о1
перед3і4стор
перед2і1
передіст2о1
пост3і4стор
пост2і1
постіст2о1
ар4т3афіш
а2р1т
арт2а1
артаф2і1
ар4т3взвод
арт2в1з
артвзв2о1
ар4т3деса2н1т
ар2т1д
артд2е1
артдес2а1
ар4т3каф2е1
артк2а1
ар4т3ма2й1с
артм2а1
артма2й
2а1р4т3меді3а1
артм2е1
артмед2і1
ар4т3ме2й1с
артме2й
ар4т3мін
артм2і1
ар4т3о4бстр
арт2о1
арто2б1с
ар4т3о4дин
артод2и1
ар4т3о4збр
ар4т3під
артп2і1
ар4т3рин
артр2и1
ар4т3у4с4тан
арт2у1
артуст2а1
ар4т3факт
артф2а1
ар4т3хім
артх2і1
ар4т3центр
артц2е1
артце2н1т
наді4стот
найі4стот
еі4стот
оі4стот
ау4т3екол
аут2е1
аутек2о1
оо4чист
з3а4кт
оа4кт
еа4кт
гіпер3а4кт
гіп2е1
гіпер2а1
найа4кт
піва4кт
пів2а1
ао4браз
аобр2а1
ео4браз
еобр2а1
оо4браз
ообр2а1
граф3о4браз
граф2о1
графобр2а1
най3о4браз
найобр2а1
супер3о4браз
суп2е1
супер2о1
суперобр2а1
ар4т3мейст
баге4р3мейст
баг2е1
баге2р1м
багерм2е1
багерме2й
багерме2й1с
бале4т3мейст
бал2е1
балетм2е1
балетме2й
балетме2й1с
бран4д3мейст
брандм2е1
брандме2й
брандме2й1с
ва4ль4д3мейст
ва2ль1д
вал6ь
вальдм2е1
вальдме2й
вальдме2й1с
ве4ль4т3мейст
ве2ль1т
вел6ь
вельтм2е1
вельтме2й
вельтме2й1с
го4ф3мейст
гофм2е1
гофме2й
гофме2й1с
гро4с3мейст
гросм2е1
гросме2й
гросме2й1с
декре4т3мейст
декр2е1
декретм2е1
декретме2й
декретме2й1с
до4к3мейст
докм2е1
докме2й
докме2й1с
капе4ль3мейст
кап2е1
капе2ль1м
капел6ь
капельм2е1
капельме2й
капельме2й1с
кварти4р3мейст
кварт2и1
кварти2р1м
квартирм2е1
квартирме2й
квартирме2й1с
конце4р4т3мейст
ко2н1ц
конц2е1
конце2р1т
концертм2е1
концертме2й
концертме2й1с
кра4н3мейст
кр2а1
кра2н1м
кранм2е1
кранме2й
кранме2й1с
полі4ц3мейст
поліцм2е1
поліцме2й
поліцме2й1с
по4ш4т3мейст
поштм2е1
поштме2й
поштме2й1с
фо4р4с4т3мейст
ф2о1
фо2р1с
форстм2е1
форстме2й
форстме2й1с
хо4р3мейст
хо2р1м
хорм2е1
хорме2й
хорме2й1с
шапі4т3мейст
ш2а1
шап2і1
шапітм2е1
шапітме2й
шапітме2й1с
шта4л3мейст
шта2л1м
шталм2е1
шталме2й
шталме2й1с
єге4р3мейст
єг2е1
єге2р1м
єгерм2е1
єгерме2й
єгерме2й1с
иа4вар2і1
иав2а1
яа4вар2і1
яав2а1
оа4вар2і1
оав2а1
еа4вар2і1
еав2а1
беза4вар2і1
без2а1
безав2а1
м2і1ж3а4вар2і1
між2а1
міжав2а1
над3а4вар2і1
над2а1
надав2а1
пост3а4вар2і1
по3с4тав
пост2а1
постав2а1
нап2і1в3а4вар2і1
нап2і1
напів2а1
напівав2а1
перед3а4вар2і1
перед2а1
передав2а1
супер3а4вар2і1
супер2а1
суперав2а1
аа4дрес
аадр2е1
еа4дрес
еадр2е1
оа4дрес
оадр2е1
іа4дрес
іадр2е1
без3а4дрес
безадр2е1
ае4фект
аеф2е1
ее4фект
ееф2е1
ое4фект
оеф2е1
най3е4фект
найеф2е1
супер3е4фект
супер2е1
супереф2е1
ое4м2і1с2і1
оем2і1
ие4м2і1с2і1
ием2і1
яе4м2і1с2і1
яем2і1
ее4м2і1с2і1
еем2і1
безе4м2і1с2і1
без2е1
безем2і1
г2і1пер3е4м2і1с2і1
гіпер2е1
гіперем2і1
еу4бог
еуб2о1
й3у4бог
й2у1
йуб2о1
ий4н2я1
и2й1н
зай4н2я1
за2й
за2й1н
здій4н2я1
зді2й1н
най4н2я1
на2й1н
обій4н2я1
обі2й1н
перей4н2я1
пере2й1н
підій4н2я1
піді2й
піді2й1н
при2й4м
пі2й4м
пі2й
ді3й4м2а1
ді2й1м
ви2й4м
за2й4м
д4о3й4м
до2й
обо2й4м
обо2й
про2й4м
про2й
обі2й4м
пере2й4м
безу4гл
без2у1
безу4пин
безуп2и1
бло4к3пост
блокп2о1
.блі4ц3а1н2а1
.бл2і1
.бліц2а1
.блі4ц3криг
.бліцкр2и1
.блі4ц3опит
.бліц2о1
.бліцоп2и1
.блі4ц3то2р1г
.бліцт2о1
.блі4ц3тур
.бліцт2у1
.блі4ц3і4спит
.бліц2і1
.бліцісп2и1
о2а4н2а1
о3а4наліз
оанал2і1
бак3а4наліз
бака5нал
бак2а1
бакан2а1
баканал2і1
ц3а4наліз
ц2а1
цан2а1
цанал2і1
ген3а4наліз
ген2а1
генан2а1
генанал2і1
з3а4наліз
зан2а1
занал2і1
м3а4наліз
м2а1
ман2а1
манал2і1
нт3а4наліз
нт2а1
нтан2а1
нтанал2і1
між3а4наліз
міжан2а1
міжанал2і1
полі3а4наліз
полі3а1
поліан2а1
поліанал2і1
ре3а4наліз
р2е1
ре3а1
реан2а1
реанал2і1
оу4год
оуг2о1
ау4год
ауг2о1
еу4год
еуг2о1
пів3у4год
пів2у1
півуг2о1
роз3у4год
розуг2о1
гос4п3у4год
госп2у1
госпуг2о1
ео4пис
еоп2и1
оо4пис
ооп2и1
ао4пис
аоп2и1
бо4р4т3мех
бо2р1т
бортм2е1
бо4р4т3о4пер
борт2о1
бортоп2е1
б2о4р4т3п4р2о1
бо4р4т3рад
бортр2а1
бо4р4т3і4н1ж
борт2і1
оа4кац2і1
оак2а1
оо4де2р1ж
оод2е1
біблі4о1
бібл2і1
.на3в4ч
.ви3в4ч
.в2и1
.до3в4ч
.за3в4ч
.по3в4ч
.при3в4ч
.п4р2и1
ана3в4ч
ена3в4ч
ен2а1
мона3в4ч
мон2а1
жона3в4ч
ж2о1
жон2а1
іона3в4ч
іон2а1
ови3в4ч
ов2и1
еви3в4ч
ев2и1
едо3в4ч
ед2о1
оза3в4ч
оз2а1
по3в4ч2а1
.ом4р2і1
.о1
.о2м1р
е3м4рі2й
е2м1р
емр2і1
.ви3м4р
.віді3м4р
.зав3м4р
.за2в1м
.за3м4р
.зі3м4р
.з2і1
.на3м4р
.пере3м4р
.по3м4р
.при3м4р
.роз3м4р
.ум4р2и1
.у1
.у2м1р
.ум4р2і1
.у1м4р2у1
.ум4р2е1
во4станн2є1
во3с4тан
вост2а1
воста2н1н
най3о4ста2н1н
найо3с4тан
найост2а1
перед3о4ста2н1н
передо3с4тан
передост2а1
и3е4стет
иест2е1
о3е4стет
оест2е1
е3е4стет
еест2е1
й3е4стет
й2е1
йест2е1
пан3е4стет
пан2е1
панест2е1
пар3е4стет
пар2е1
парест2е1
оо4ктан
оокт2а1
іо4ктан
іокт2а1
оо4плачув
оопл2а1
ооплач2у1
ео4плачув
еопл2а1
еоплач2у1
перед3о4пл2а1
в2и1у4д2и1
о3в4каз
о2в1к
овк2а1
е3в4каз
е2в1к
евк2а1
8-7
8-8-8
.а8-8
.а1
.б8-8
.в8-8
.г8-8
.ґ8-8
.д8-8
.е8-8
.е1
.є8-8
.є1
.ж8-8
.з8-8
.и8-8
.и1
.і8-8
.і1
.ї8-8
.ї1
.й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
.ю1
.я8-8
.я1
.'8-8
.’8-8
-2а8а8
-2а1
8а8а8-7
-а8б8
8а8б8-7
-а8в8
8а8в8-7
-а8г8
8а8г8-7
-а8ґ8
8а8ґ8-7
-а8д8
8а8д8-7
-а8е8
8а8е8-7
-а8є8
8а8є8-7
-а8ж8
8а8ж8-7
-а8з8
8а8з8-7
-а8и8
8а8и8-7
а2и1
-а8і8
8а8і8-7
-а8ї8
8а8ї8-7
-а8й8
8а8й8-7
-а8к8
8а8к8-7
-а8л8
8а8л8-7
-а8м8
8а8м8-7
-а8н8
8а8н8-7
-а8о8
8а8о8-7
-а8п8
8а8п8-7
-а8р8
8а8р8-7
-а8с8
8а8с8-7
-а8т8
8а8т8-7
-а8у8
8а8у8-7
-а8ф8
8а8ф8-7
-а8х8
8а8х8-7
-а8ц8
8а8ц8-7
-а8ч8
8а8ч8-7
-а8ш8
8а8ш8-7
-а8щ8
8а8щ8-7
-а8ь8
8а8ь8-7
а6ь
-а8ю8
8а8ю8-7
-а8я8
8а8я8-7
-а8'8
-а8’8
8а8'8-7
а6'
8а8’8-7
а6’
-б8а8
8б8а8-7
-2б8б8
8б8б8-7
-б8в8
8б8в8-7
-б8г8
8б8г8-7
-б8ґ8
8б8ґ8-7
-б8д8
8б8д8-7
-б8е8
8б8е8-7
-б8є8
8б8є8-7
б2є1
-б8ж8
8б8ж8-7
-б8з8
8б8з8-7
-б8и8
8б8и8-7
б2и1
-б8і8
8б8і8-7
-б8ї8
8б8ї8-7
б2ї1
-б8й8
8б8й8-7
-2б8к8
8б8к8-7
-б8л8
8б8л8-7
-б8м8
8б8м8-7
-б8н8
8б8н8-7
-б8о8
8б8о8-7
-2б8п8
8б8п8-7
-б8р8
8б8р8-7
-2б8с8
8б8с8-7
-2б8т8
8б8т8-7
-б8у8
8б8у8-7
б2у1
-2б8ф8
8б8ф8-7
-2б8х8
8б8х8-7
-2б8ц8
8б8ц8-7
-2б8ч8
8б8ч8-7
-2б8ш8
8б8ш8-7
-2б8щ8
8б8щ8-7
-б8ь8
8б8ь8-7
б6ь
-б8ю8
8б8ю8-7
б2ю1
-б8я8
8б8я8-7
б2я1
-б8'8
-б8’8
8б8'8-7
8б8’8-7
-в8а8
8в8а8-7
-2в8б8
8в8б8-7
-2в8в8
8в8в8-7
-2в8г8
8в8г8-7
-в8ґ8
8в8ґ8-7
-2в8д8
8в8д8-7
-в8е8
8в8е8-7
-в8є8
8в8є8-7
в2є1
-2в8ж8
8в8ж8-7
-2в8з8
8в8з8-7
-в8и8
8в8и8-7
-в8і8
8в8і8-7
-в8ї8
8в8ї8-7
в2ї1
-2в8й8
8в8й8-7
-2в8к8
8в8к8-7
-2в8л8
8в8л8-7
-2в8м8
8в8м8-7
-2в8н8
8в8н8-7
-в8о8
8в8о8-7
-2в8п8
8в8п8-7
-2в8р8
8в8р8-7
-2в8с8
8в8с8-7
-2в8т8
8в8т8-7
-в8у8
8в8у8-7
-2в8ф8
8в8ф8-7
-2в8х8
8в8х8-7
-2в8ц8
8в8ц8-7
-2в8ч8
8в8ч8-7
-2в8ш8
8в8ш8-7
-2в8щ8
8в8щ8-7
-в8ь8
8в8ь8-7
в6ь
-в8ю8
8в8ю8-7
в2ю1
-в8я8
8в8я8-7
в2я1
-2в8'8
-2в8’8
8в8'8-7
8в8’8-7
-г8а8
8г8а8-7
г2а1
-г8б8
8г8б8-7
-г8в8
8г8в8-7
-2г8г8
8г8г8-7
-г8ґ8
8г8ґ8-7
-г8д8
8г8д8-7
-г8е8
8г8е8-7
-г8є8
8г8є8-7
г2є1
-г8ж8
8г8ж8-7
-г8з8
8г8з8-7
-г8и8
8г8и8-7
г2и1
-г8і8
8г8і8-7
-г8ї8
8г8ї8-7
г2ї1
-г8й8
8г8й8-7
-2г8к8
8г8к8-7
-г8л8
8г8л8-7
-г8м8
8г8м8-7
-г8н8
8г8н8-7
-г8о8
8г8о8-7
-2г8п8
8г8п8-7
-г8р8
8г8р8-7
-2г8с8
8г8с8-7
-2г8т8
8г8т8-7
-г8у8
8г8у8-7
г2у1
-2г8ф8
8г8ф8-7
-г8х8
8г8х8-7
-2г8ц8
8г8ц8-7
-2г8ч8
8г8ч8-7
-2г8ш8
8г8ш8-7
-г8щ8
8г8щ8-7
-г8ь8
8г8ь8-7
г6ь
-г8ю8
8г8ю8-7
г2ю1
-г8я8
8г8я8-7
г2я1
-г8'8
-г8’8
8г8'8-7
г6'
8г8’8-7
г6’
-ґ8а8
8ґ8а8-7
ґ2а1
-ґ8б8
8ґ8б8-7
-ґ8в8
8ґ8в8-7
-ґ8г8
8ґ8г8-7
-2ґ8ґ8
8ґ8ґ8-7
-ґ8д8
8ґ8д8-7
-ґ8е8
8ґ8е8-7
ґ2е1
-ґ8є8
8ґ8є8-7
ґ2є1
-ґ8ж8
8ґ8ж8-7
-ґ8з8
8ґ8з8-7
-ґ8и8
8ґ8и8-7
ґ2и1
-ґ8і8
8ґ8і8-7
ґ2і1
-ґ8ї8
8ґ8ї8-7
ґ2ї1
-ґ8й8
8ґ8й8-7
-ґ8к8
8ґ8к8-7
-ґ8л8
8ґ8л8-7
-ґ8м8
8ґ8м8-7
-ґ8н8
8ґ8н8-7
-ґ8о8
8ґ8о8-7
ґ2о1
-ґ8п8
8ґ8п8-7
-ґ8р8
8ґ8р8-7
-ґ8с8
8ґ8с8-7
-ґ8т8
8ґ8т8-7
-ґ8у8
8ґ8у8-7
ґ2у1
-ґ8ф8
8ґ8ф8-7
-ґ8х8
8ґ8х8-7
-ґ8ц8
8ґ8ц8-7
-ґ8ч8
8ґ8ч8-7
-ґ8ш8
8ґ8ш8-7
-ґ8щ8
8ґ8щ8-7
-ґ8ь8
8ґ8ь8-7
ґ6ь
-ґ8ю8
8ґ8ю8-7
ґ2ю1
-ґ8я8
8ґ8я8-7
ґ2я1
-ґ8'8
-ґ8’8
8ґ8'8-7
ґ6'
8ґ8’8-7
ґ6’
-д8а8
8д8а8-7
-д8б8
8д8б8-7
-д8в8
8д8в8-7
-д8г8
8д8г8-7
-д8ґ8
8д8ґ8-7
-2д8д8
8д8д8-7
-д8е8
8д8е8-7
-д8є8
8д8є8-7
д2є1
-д8ж8
8д8ж8-7
-д8з8
8д8з8-7
-д8и8
8д8и8-7
-д8і8
8д8і8-7
-д8ї8
8д8ї8-7
д2ї1
-д8й8
8д8й8-7
-2д8к8
8д8к8-7
-д8л8
8д8л8-7
-д8м8
8д8м8-7
-д8н8
8д8н8-7
-д8о8
8д8о8-7
-2д8п8
8д8п8-7
-д8р8
8д8р8-7
-2д8с8
8д8с8-7
-2д8т8
8д8т8-7
-д8у8
8д8у8-7
-2д8ф8
8д8ф8-7
-2д8х8
8д8х8-7
-2д8ц8
8д8ц8-7
-2д8ч8
8д8ч8-7
-2д8ш8
8д8ш8-7
-2д8щ8
8д8щ8-7
-д8ь8
8д8ь8-7
-д8ю8
8д8ю8-7
д2ю1
-д8я8
8д8я8-7
д2я1
-д8'8
-д8’8
8д8'8-7
д6'
8д8’8-7
д6’
-е8а8
-2е1
8е8а8-7
-е8б8
8е8б8-7
-е8в8
8е8в8-7
-е8г8
8е8г8-7
-е8ґ8
8е8ґ8-7
-е8д8
8е8д8-7
-2е8е8
8е8е8-7
-е8є8
8е8є8-7
-е8ж8
8е8ж8-7
-е8з8
8е8з8-7
-е8и8
8е8и8-7
е2и1
-е8і8
8е8і8-7
-е8ї8
8е8ї8-7
-е8й8
8е8й8-7
-е8к8
8е8к8-7
-е8л8
8е8л8-7
-е8м8
8е8м8-7
-е8н8
8е8н8-7
-е8о8
8е8о8-7
-е8п8
8е8п8-7
-е8р8
8е8р8-7
-е8с8
8е8с8-7
-е8т8
8е8т8-7
-е8у8
8е8у8-7
-е8ф8
8е8ф8-7
-е8х8
8е8х8-7
-е8ц8
8е8ц8-7
-е8ч8
8е8ч8-7
-е8ш8
8е8ш8-7
-е8щ8
8е8щ8-7
-е8ь8
8е8ь8-7
е6ь
-е8ю8
8е8ю8-7
-е8я8
8е8я8-7
-е8'8
-е8’8
8е8'8-7
е6'
8е8’8-7
е6’
-є8а8
-2є1
8є8а8-7
є2а1
-є8б8
8є8б8-7
-є8в8
8є8в8-7
-є8г8
8є8г8-7
-є8ґ8
8є8ґ8-7
-є8д8
8є8д8-7
-є8е8
8є8е8-7
є2е1
-2є8є8
8є8є8-7
-є8ж8
8є8ж8-7
-є8з8
8є8з8-7
-є8и8
8є8и8-7
є2и1
-є8і8
8є8і8-7
є2і1
-є8ї8
8є8ї8-7
-є8й8
8є8й8-7
-є8к8
8є8к8-7
-є8л8
8є8л8-7
-є8м8
8є8м8-7
-є8н8
8є8н8-7
-є8о8
8є8о8-7
є2о1
-є8п8
8є8п8-7
-є8р8
8є8р8-7
-є8с8
8є8с8-7
-є8т8
8є8т8-7
-є8у8
8є8у8-7
-є8ф8
8є8ф8-7
-є8х8
8є8х8-7
-є8ц8
8є8ц8-7
-є8ч8
8є8ч8-7
-є8ш8
8є8ш8-7
-є8щ8
8є8щ8-7
-є8ь8
8є8ь8-7
є6ь
-є8ю8
8є8ю8-7
-є8я8
8є8я8-7
є2я1
-є8'8
-є8’8
8є8'8-7
є6'
8є8’8-7
є6’
-ж8а8
8ж8а8-7
ж2а1
-ж8б8
8ж8б8-7
-ж8в8
8ж8в8-7
-ж8г8
8ж8г8-7
-ж8ґ8
8ж8ґ8-7
-ж8д8
8ж8д8-7
-ж8е8
8ж8е8-7
ж2е1
-ж8є8
8ж8є8-7
ж2є1
-2ж8ж8
8ж8ж8-7
-ж8з8
8ж8з8-7
-ж8и8
8ж8и8-7
ж2и1
-ж8і8
8ж8і8-7
ж2і1
-ж8ї8
8ж8ї8-7
ж2ї1
-ж8й8
8ж8й8-7
-2ж8к8
8ж8к8-7
-ж8л8
8ж8л8-7
-ж8м8
8ж8м8-7
-ж8н8
8ж8н8-7
-ж8о8
8ж8о8-7
-2ж8п8
8ж8п8-7
-ж8р8
8ж8р8-7
-2ж8с8
8ж8с8-7
-2ж8т8
8ж8т8-7
-ж8у8
8ж8у8-7
ж2у1
-2ж8ф8
8ж8ф8-7
-2ж8х8
8ж8х8-7
-2ж8ц8
8ж8ц8-7
-2ж8ч8
8ж8ч8-7
-2ж8ш8
8ж8ш8-7
-ж8щ8
8ж8щ8-7
-ж8ь8
8ж8ь8-7
ж6ь
-ж8ю8
8ж8ю8-7
ж2ю1
-ж8я8
8ж8я8-7
ж2я1
-ж8'8
-ж8’8
8ж8'8-7
ж6'
8ж8’8-7
ж6’
-з8а8
8з8а8-7
-з8б8
8з8б8-7
-з8в8
8з8в8-7
-з8г8
8з8г8-7
-з8ґ8
8з8ґ8-7
-з8д8
8з8д8-7
-з8е8
8з8е8-7
-з8є8
8з8є8-7
з2є1
-з8ж8
8з8ж8-7
-2з8з8
8з8з8-7
-з8и8
8з8и8-7
з2и1
-з8і8
8з8і8-7
-з8ї8
8з8ї8-7
з2ї1
-з8й8
8з8й8-7
-2з8к8
8з8к8-7
-з8л8
8з8л8-7
-з8м8
8з8м8-7
-з8н8
8з8н8-7
-з8о8
8з8о8-7
-2з8п8
8з8п8-7
-з8р8
8з8р8-7
-2з8с8
8з8с8-7
-2з8т8
8з8т8-7
-з8у8
8з8у8-7
-2з8ф8
8з8ф8-7
-2з8х8
8з8х8-7
-2з8ц8
8з8ц8-7
-2з8ч8
8з8ч8-7
-2з8ш8
8з8ш8-7
-2з8щ8
8з8щ8-7
-з8ь8
8з8ь8-7
-з8ю8
8з8ю8-7
з2ю1
-з8я8
8з8я8-7
з2я1
-з8'8
-з8’8
8з8'8-7
8з8’8-7
-и8а8
-2и1
8и8а8-7
-и8б8
8и8б8-7
-и8в8
8и8в8-7
-и8г8
8и8г8-7
-и8ґ8
8и8ґ8-7
-и8д8
8и8д8-7
-и8е8
8и8е8-7
-и8є8
8и8є8-7
-и8ж8
8и8ж8-7
-и8з8
8и8з8-7
-2и8и8
8и8и8-7
и2и1
-и8і8
8и8і8-7
-и8ї8
8и8ї8-7
-и8й8
8и8й8-7
-и8к8
8и8к8-7
-и8л8
8и8л8-7
-и8м8
8и8м8-7
-и8н8
8и8н8-7
-и8о8
8и8о8-7
-и8п8
8и8п8-7
-и8р8
8и8р8-7
-и8с8
8и8с8-7
-и8т8
8и8т8-7
-и8у8
8и8у8-7
-и8ф8
8и8ф8-7
-и8х8
8и8х8-7
-и8ц8
8и8ц8-7
-и8ч8
8и8ч8-7
-и8ш8
8и8ш8-7
-и8щ8
8и8щ8-7
-и8ь8
8и8ь8-7
и6ь
-и8ю8
8и8ю8-7
-и8я8
8и8я8-7
-и8'8
-и8’8
8и8'8-7
и6'
8и8’8-7
и6’
-і8а8
-2і1
8і8а8-7
-і8б8
8і8б8-7
-і8в8
8і8в8-7
-і8г8
8і8г8-7
-і8ґ8
8і8ґ8-7
-і8д8
8і8д8-7
-і8е8
8і8е8-7
-і8є8
8і8є8-7
-і8ж8
8і8ж8-7
-і8з8
8і8з8-7
-і8и8
8і8и8-7
-2і8і8
8і8і8-7
-і8ї8
8і8ї8-7
-і8й8
8і8й8-7
-і8к8
8і8к8-7
-і8л8
8і8л8-7
-і8м8
8і8м8-7
-і8н8
8і8н8-7
-і8о8
8і8о8-7
-і8п8
8і8п8-7
-і8р8
8і8р8-7
-і8с8
8і8с8-7
-і8т8
8і8т8-7
-і8у8
8і8у8-7
-і8ф8
8і8ф8-7
-і8х8
8і8х8-7
-і8ц8
8і8ц8-7
-і8ч8
8і8ч8-7
-і8ш8
8і8ш8-7
-і8щ8
8і8щ8-7
-і8ь8
8і8ь8-7
і6ь
-і8ю8
8і8ю8-7
-і8я8
8і8я8-7
-і8'8
-і8’8
8і8'8-7
і6'
8і8’8-7
і6’
-ї8а8
-2ї1
8ї8а8-7
ї2а1
-ї8б8
8ї8б8-7
-ї8в8
8ї8в8-7
-ї8г8
8ї8г8-7
-ї8ґ8
8ї8ґ8-7
-ї8д8
8ї8д8-7
-ї8е8
8ї8е8-7
-ї8є8
8ї8є8-7
ї2є1
-ї8ж8
8ї8ж8-7
-ї8з8
8ї8з8-7
-ї8и8
8ї8и8-7
ї2и1
-ї8і8
8ї8і8-7
ї2і1
-2ї8ї8
8ї8ї8-7
ї2ї1
-ї8й8
8ї8й8-7
-ї8к8
8ї8к8-7
-ї8л8
8ї8л8-7
-ї8м8
8ї8м8-7
-ї8н8
8ї8н8-7
-ї8о8
8ї8о8-7
-ї8п8
8ї8п8-7
-ї8р8
8ї8р8-7
-ї8с8
8ї8с8-7
-ї8т8
8ї8т8-7
-ї8у8
8ї8у8-7
ї2у1
-ї8ф8
8ї8ф8-7
-ї8х8
8ї8х8-7
-ї8ц8
8ї8ц8-7
-ї8ч8
8ї8ч8-7
-ї8ш8
8ї8ш8-7
-ї8щ8
8ї8щ8-7
-ї8ь8
8ї8ь8-7
ї6ь
-ї8ю8
8ї8ю8-7
-ї8я8
8ї8я8-7
ї2я1
-ї8'8
-ї8’8
8ї8'8-7
ї6'
8ї8’8-7
ї6’
-й8а8
8й8а8-7
й2а1
-2й8б8
8й8б8-7
-2й8в8
8й8в8-7
-2й8г8
8й8г8-7
-й8ґ8
8й8ґ8-7
-2й8д8
8й8д8-7
-й8е8
8й8е8-7
-й8є8
8й8є8-7
й2є1
-2й8ж8
8й8ж8-7
-2й8з8
8й8з8-7
-й8и8
8й8и8-7
й2и1
-й8і8
8й8і8-7
й2і1
-й8ї8
8й8ї8-7
й2ї1
-2й8й8
8й8й8-7
-2й8к8
8й8к8-7
-2й8л8
8й8л8-7
-2й8м8
8й8м8-7
-2й8н8
8й8н8-7
-3й8о8
8й8о8-7
-2й8п8
8й8п8-7
-2й8р8
8й8р8-7
-2й8с8
8й8с8-7
-2й8т8
8й8т8-7
-й8у8
8й8у8-7
-2й8ф8
8й8ф8-7
-2й8х8
8й8х8-7
-2й8ц8
8й8ц8-7
-2й8ч8
8й8ч8-7
-2й8ш8
8й8ш8-7
-2й8щ8
8й8щ8-7
-й8ь8
8й8ь8-7
й6ь
-й8ю8
8й8ю8-7
й2ю1
-й8я8
8й8я8-7
й2я1
-й8'8
-й8’8
8й8'8-7
й6'
8й8’8-7
й6’
-к8а8
8к8а8-7
-2к8б8
8к8б8-7
-к8в8
8к8в8-7
-2к8г8
8к8г8-7
-к8ґ8
8к8ґ8-7
-2к8д8
8к8д8-7
-к8е8
8к8е8-7
к2е1
-к8є8
8к8є8-7
к2є1
-к8ж8
8к8ж8-7
-2к8з8
8к8з8-7
-к8и8
8к8и8-7
-к8і8
8к8і8-7
к2і1
-к8ї8
8к8ї8-7
к2ї1
-к8й8
8к8й8-7
-2к8к8
8к8к8-7
-к8л8
8к8л8-7
-к8м8
8к8м8-7
-к8н8
8к8н8-7
-к8о8
8к8о8-7
-к8п8
8к8п8-7
-к8р8
8к8р8-7
-к8с8
8к8с8-7
-к8т8
8к8т8-7
-к8у8
8к8у8-7
к2у1
-к8ф8
8к8ф8-7
-к8х8
8к8х8-7
-к8ц8
8к8ц8-7
-к8ч8
8к8ч8-7
-к8ш8
8к8ш8-7
-к8щ8
8к8щ8-7
-к8ь8
8к8ь8-7
к6ь
-к8ю8
8к8ю8-7
к2ю1
-к8я8
8к8я8-7
к2я1
-к8'8
-к8’8
8к8'8-7
к6'
8к8’8-7
к6’
-л8а8
8л8а8-7
л2а1
-2л8б8
8л8б8-7
-2л8в8
8л8в8-7
-2л8г8
8л8г8-7
-2л8ґ8
8л8ґ8-7
-2л8д8
8л8д8-7
-л8е8
8л8е8-7
л2е1
-л8є8
8л8є8-7
л2є1
-2л8ж8
8л8ж8-7
-2л8з8
8л8з8-7
-л8и8
8л8и8-7
л2и1
-л8і8
8л8і8-7
л2і1
-л8ї8
8л8ї8-7
л2ї1
-л8й8
8л8й8-7
-2л8к8
8л8к8-7
-2л8л8
8л8л8-7
-2л8м8
8л8м8-7
-2л8н8
8л8н8-7
-л8о8
8л8о8-7
л2о1
-2л8п8
8л8п8-7
-2л8р8
8л8р8-7
-2л8с8
8л8с8-7
-2л8т8
8л8т8-7
-л8у8
8л8у8-7
л2у1
-2л8ф8
8л8ф8-7
-2л8х8
8л8х8-7
-2л8ц8
8л8ц8-7
-2л8ч8
8л8ч8-7
-л8ш8
8л8ш8-7
-л8щ8
8л8щ8-7
-л8ь8
8л8ь8-7
-л8ю8
8л8ю8-7
л2ю1
-л8я8
8л8я8-7
л2я1
-л8'8
-л8’8
8л8'8-7
л6'
8л8’8-7
л6’
-м8а8
8м8а8-7
-2м8б8
8м8б8-7
-2м8в8
8м8в8-7
-2м8г8
8м8г8-7
-м8ґ8
8м8ґ8-7
-2м8д8
8м8д8-7
-м8е8
8м8е8-7
-м8є8
8м8є8-7
м2є1
-2м8ж8
8м8ж8-7
-2м8з8
8м8з8-7
-м8и8
8м8и8-7
м2и1
-м8і8
8м8і8-7
-м8ї8
8м8ї8-7
м2ї1
-м8й8
8м8й8-7
-2м8к8
8м8к8-7
-2м8л8
8м8л8-7
-2м8м8
8м8м8-7
-2м8н8
8м8н8-7
-м8о8
8м8о8-7
-2м8п8
8м8п8-7
-2м8р8
8м8р8-7
-2м8с8
8м8с8-7
-2м8т8
8м8т8-7
-м8у8
8м8у8-7
м2у1
-2м8ф8
8м8ф8-7
-2м8х8
8м8х8-7
-2м8ц8
8м8ц8-7
-2м8ч8
8м8ч8-7
-2м8ш8
8м8ш8-7
-2м8щ8
8м8щ8-7
-м8ь8
8м8ь8-7
м6ь
-м8ю8
8м8ю8-7
м2ю1
-м8я8
8м8я8-7
м2я1
-2м8'8
-2м8’8
8м8'8-7
8м8’8-7
-н8а8
8н8а8-7
-2н8б8
8н8б8-7
-2н8в8
8н8в8-7
-2н8г8
8н8г8-7
-н8ґ8
8н8ґ8-7
-2н8д8
8н8д8-7
-н8е8
8н8е8-7
-н8є8
8н8є8-7
н2є1
-2н8ж8
8н8ж8-7
-2н8з8
8н8з8-7
-н8и8
8н8и8-7
н2и1
-н8і8
8н8і8-7
-н8ї8
8н8ї8-7
н2ї1
-н8й8
8н8й8-7
-2н8к8
8н8к8-7
-2н8л8
8н8л8-7
-2н8м8
8н8м8-7
-2н8н8
8н8н8-7
-н8о8
8н8о8-7
-2н8п8
8н8п8-7
-2н8р8
8н8р8-7
-2н8с8
8н8с8-7
-2н8т8
8н8т8-7
-н8у8
8н8у8-7
н2у1
-2н8ф8
8н8ф8-7
-2н8х8
8н8х8-7
-2н8ц8
8н8ц8-7
-2н8ч8
8н8ч8-7
-2н8ш8
8н8ш8-7
-2н8щ8
8н8щ8-7
-н8ь8
8н8ь8-7
-н8ю8
8н8ю8-7
н2ю1
-н8я8
8н8я8-7
н2я1
-2н8'8
-2н8’8
8н8'8-7
8н8’8-7
-о8а8
-2о1
8о8а8-7
-о8б8
8о8б8-7
-о8в8
8о8в8-7
-о8г8
8о8г8-7
-о8ґ8
8о8ґ8-7
-о8д8
8о8д8-7
-о8е8
8о8е8-7
-о8є8
8о8є8-7
-о8ж8
8о8ж8-7
-о8з8
8о8з8-7
-о8и8
8о8и8-7
о2и1
-о8і8
8о8і8-7
-о8ї8
8о8ї8-7
-о8й8
8о8й8-7
-о8к8
8о8к8-7
-о8л8
8о8л8-7
-о8м8
8о8м8-7
-о8н8
8о8н8-7
-2о8о8
8о8о8-7
-о8п8
8о8п8-7
-о8р8
8о8р8-7
-о8с8
8о8с8-7
-о8т8
8о8т8-7
-о8у8
8о8у8-7
-о8ф8
8о8ф8-7
-о8х8
8о8х8-7
-о8ц8
8о8ц8-7
-о8ч8
8о8ч8-7
-о8ш8
8о8ш8-7
-о8щ8
8о8щ8-7
-о8ь8
8о8ь8-7
о6ь
-о8ю8
8о8ю8-7
-о8я8
8о8я8-7
-о8'8
-о8’8
8о8'8-7
о6'
8о8’8-7
о6’
-п8а8
8п8а8-7
-2п8б8
8п8б8-7
-п8в8
8п8в8-7
-п8г8
8п8г8-7
-п8ґ8
8п8ґ8-7
-2п8д8
8п8д8-7
-п8е8
8п8е8-7
-п8є8
8п8є8-7
п2є1
-п8ж8
8п8ж8-7
-2п8з8
8п8з8-7
-п8и8
8п8и8-7
п2и1
-п8і8
8п8і8-7
-п8ї8
8п8ї8-7
п2ї1
-п8й8
8п8й8-7
-п8к8
8п8к8-7
-п8л8
8п8л8-7
-п8м8
8п8м8-7
-п8н8
8п8н8-7
-п8о8
8п8о8-7
-2п8п8
8п8п8-7
-п8р8
8п8р8-7
-п8с8
8п8с8-7
-п8т8
8п8т8-7
-п8у8
8п8у8-7
п2у1
-п8ф8
8п8ф8-7
-п8х8
8п8х8-7
-п8ц8
8п8ц8-7
-п8ч8
8п8ч8-7
-п8ш8
8п8ш8-7
-п8щ8
8п8щ8-7
-п8ь8
8п8ь8-7
п6ь
-п8ю8
8п8ю8-7
п2ю1
-п8я8
8п8я8-7
п2я1
-п8'8
-п8’8
8п8'8-7
п6'
8п8’8-7
п6’
-р8а8
8р8а8-7
-2р8б8
8р8б8-7
-2р8в8
8р8в8-7
-2р8г8
8р8г8-7
-2р8ґ8
8р8ґ8-7
-2р8д8
8р8д8-7
-р8е8
8р8е8-7
-р8є8
8р8є8-7
р2є1
-2р8ж8
8р8ж8-7
-2р8з8
8р8з8-7
-р8и8
8р8и8-7
р2и1
-р8і8
8р8і8-7
р2і1
-р8ї8
8р8ї8-7
р2ї1
-2р8й8
8р8й8-7
-2р8к8
8р8к8-7
-2р8л8
8р8л8-7
-2р8м8
8р8м8-7
-2р8н8
8р8н8-7
-р8о8
8р8о8-7
-2р8п8
8р8п8-7
-2р8р8
8р8р8-7
-2р8с8
8р8с8-7
-2р8т8
8р8т8-7
-р8у8
8р8у8-7
-2р8ф8
8р8ф8-7
-2р8х8
8р8х8-7
-2р8ц8
8р8ц8-7
-2р8ч8
8р8ч8-7
-2р8ш8
8р8ш8-7
-2р8щ8
8р8щ8-7
-р8ь8
8р8ь8-7
-р8ю8
8р8ю8-7
р2ю1
-р8я8
8р8я8-7
р2я1
-2р8'8
-2р8’8
8р8'8-7
8р8’8-7
-с8а8
8с8а8-7
-2с8б8
8с8б8-7
-с8в8
8с8в8-7
-2с8г8
8с8г8-7
-с8ґ8
8с8ґ8-7
-2с8д8
8с8д8-7
-с8е8
8с8е8-7
-с8є8
8с8є8-7
с2є1
-с8ж8
8с8ж8-7
-с8з8
8с8з8-7
-с8и8
8с8и8-7
с2и1
-с8і8
8с8і8-7
с2і1
-с8ї8
8с8ї8-7
с2ї1
-с8й8
8с8й8-7
-с8к8
8с8к8-7
-с8л8
8с8л8-7
-с8м8
8с8м8-7
-с8н8
8с8н8-7
-с8о8
8с8о8-7
-с8п8
8с8п8-7
-с8р8
8с8р8-7
-2с8с8
8с8с8-7
-с8т8
8с8т8-7
-с8у8
8с8у8-7
-с8ф8
8с8ф8-7
-с8х8
8с8х8-7
-с8ц8
8с8ц8-7
-с8ч8
8с8ч8-7
-с8ш8
8с8ш8-7
-с8щ8
8с8щ8-7
-с8ь8
8с8ь8-7
-с8ю8
8с8ю8-7
с2ю1
-с8я8
8с8я8-7
с2я1
-с8'8
-с8’8
8с8'8-7
с6'
8с8’8-7
с6’
-т8а8
8т8а8-7
т2а1
-2т8б8
8т8б8-7
-т8в8
8т8в8-7
-2т8г8
8т8г8-7
-т8ґ8
8т8ґ8-7
-2т8д8
8т8д8-7
-т8е8
8т8е8-7
-т8є8
8т8є8-7
т2є1
-2т8ж8
8т8ж8-7
-2т8з8
8т8з8-7
-т8и8
8т8и8-7
т2и1
-т8і8
8т8і8-7
т2і1
-т8ї8
8т8ї8-7
т2ї1
-т8й8
8т8й8-7
-т8к8
8т8к8-7
-т8л8
8т8л8-7
-т8м8
8т8м8-7
-т8н8
8т8н8-7
-т8о8
8т8о8-7
-т8п8
8т8п8-7
-т8р8
8т8р8-7
-т8с8
8т8с8-7
-2т8т8
8т8т8-7
-т8у8
8т8у8-7
-т8ф8
8т8ф8-7
-т8х8
8т8х8-7
-т8ц8
8т8ц8-7
-т8ч8
8т8ч8-7
-т8ш8
8т8ш8-7
-т8щ8
8т8щ8-7
-т8ь8
8т8ь8-7
-т8ю8
8т8ю8-7
т2ю1
-т8я8
8т8я8-7
т2я1
-т8'8
-т8’8
8т8'8-7
т6'
8т8’8-7
т6’
-у8а8
-2у1
8у8а8-7
-у8б8
8у8б8-7
-у8в8
8у8в8-7
-у8г8
8у8г8-7
-у8ґ8
8у8ґ8-7
-у8д8
8у8д8-7
-у8е8
8у8е8-7
-у8є8
8у8є8-7
-у8ж8
8у8ж8-7
-у8з8
8у8з8-7
-у8и8
8у8и8-7
у2и1
-у8і8
8у8і8-7
-у8ї8
8у8ї8-7
-у8й8
8у8й8-7
-у8к8
8у8к8-7
-у8л8
8у8л8-7
-у8м8
8у8м8-7
-у8н8
8у8н8-7
-у8о8
8у8о8-7
-у8п8
8у8п8-7
-у8р8
8у8р8-7
-у8с8
8у8с8-7
-у8т8
8у8т8-7
-2у8у8
8у8у8-7
-у8ф8
8у8ф8-7
-у8х8
8у8х8-7
-у8ц8
8у8ц8-7
-у8ч8
8у8ч8-7
-у8ш8
8у8ш8-7
-у8щ8
8у8щ8-7
-у8ь8
8у8ь8-7
у6ь
-у8ю8
8у8ю8-7
-у8я8
8у8я8-7
-у8'8
-у8’8
8у8'8-7
у6'
8у8’8-7
у6’
-ф8а8
8ф8а8-7
ф2а1
-2ф8б8
8ф8б8-7
-ф8в8
8ф8в8-7
-2ф8г8
8ф8г8-7
-ф8ґ8
8ф8ґ8-7
-ф8д8
8ф8д8-7
-ф8е8
8ф8е8-7
ф2е1
-ф8є8
8ф8є8-7
ф2є1
-ф8ж8
8ф8ж8-7
-2ф8з8
8ф8з8-7
-ф8и8
8ф8и8-7
ф2и1
-ф8і8
8ф8і8-7
-ф8ї8
8ф8ї8-7
ф2ї1
-ф8й8
8ф8й8-7
-ф8к8
8ф8к8-7
-ф8л8
8ф8л8-7
-ф8м8
8ф8м8-7
-ф8н8
8ф8н8-7
-ф8о8
8ф8о8-7
-ф8п8
8ф8п8-7
-ф8р8
8ф8р8-7
-ф8с8
8ф8с8-7
-ф8т8
8ф8т8-7
-ф8у8
8ф8у8-7
ф2у1
-2ф8ф8
8ф8ф8-7
-ф8х8
8ф8х8-7
-ф8ц8
8ф8ц8-7
-ф8ч8
8ф8ч8-7
-ф8ш8
8ф8ш8-7
-ф8щ8
8ф8щ8-7
-ф8ь8
8ф8ь8-7
-ф8ю8
8ф8ю8-7
ф2ю1
-ф8я8
8ф8я8-7
ф2я1
-ф8'8
-ф8’8
8ф8'8-7
ф6'
8ф8’8-7
ф6’
-х8а8
8х8а8-7
х2а1
-х8б8
8х8б8-7
-х8в8
8х8в8-7
-2х8г8
8х8г8-7
-х8ґ8
8х8ґ8-7
-2х8д8
8х8д8-7
-х8е8
8х8е8-7
х2е1
-х8є8
8х8є8-7
х2є1
-х8ж8
8х8ж8-7
-х8з8
8х8з8-7
-х8и8
8х8и8-7
х2и1
-х8і8
8х8і8-7
-х8ї8
8х8ї8-7
х2ї1
-х8й8
8х8й8-7
-х8к8
8х8к8-7
-х8л8
8х8л8-7
-х8м8
8х8м8-7
-х8н8
8х8н8-7
-х8о8
8х8о8-7
-х8п8
8х8п8-7
-х8р8
8х8р8-7
-х8с8
8х8с8-7
-х8т8
8х8т8-7
-х8у8
8х8у8-7
х2у1
-х8ф8
8х8ф8-7
-2х8х8
8х8х8-7
-х8ц8
8х8ц8-7
-х8ч8
8х8ч8-7
-х8ш8
8х8ш8-7
-х8щ8
8х8щ8-7
-х8ь8
8х8ь8-7
х6ь
-х8ю8
8х8ю8-7
х2ю1
-х8я8
8х8я8-7
х2я1
-х8'8
-х8’8
8х8'8-7
х6'
8х8’8-7
х6’
-ц8а8
8ц8а8-7
-2ц8б8
8ц8б8-7
-ц8в8
8ц8в8-7
-2ц8г8
8ц8г8-7
-ц8ґ8
8ц8ґ8-7
-2ц8д8
8ц8д8-7
-ц8е8
8ц8е8-7
-ц8є8
8ц8є8-7
ц2є1
-ц8ж8
8ц8ж8-7
-2ц8з8
8ц8з8-7
-ц8и8
8ц8и8-7
ц2и1
-ц8і8
8ц8і8-7
ц2і1
-ц8ї8
8ц8ї8-7
ц2ї1
-ц8й8
8ц8й8-7
-ц8к8
8ц8к8-7
-ц8л8
8ц8л8-7
-ц8м8
8ц8м8-7
-ц8н8
8ц8н8-7
-ц8о8
8ц8о8-7
ц2о1
-ц8п8
8ц8п8-7
-ц8р8
8ц8р8-7
-ц8с8
8ц8с8-7
-ц8т8
8ц8т8-7
-ц8у8
8ц8у8-7
-ц8ф8
8ц8ф8-7
-ц8х8
8ц8х8-7
-2ц8ц8
8ц8ц8-7
-ц8ч8
8ц8ч8-7
-ц8ш8
8ц8ш8-7
-ц8щ8
8ц8щ8-7
-ц8ь8
8ц8ь8-7
-ц8ю8
8ц8ю8-7
ц2ю1
-ц8я8
8ц8я8-7
ц2я1
-ц8'8
-ц8’8
8ц8'8-7
ц6'
8ц8’8-7
ц6’
-ч8а8
8ч8а8-7
ч2а1
-2ч8б8
8ч8б8-7
-ч8в8
8ч8в8-7
-ч8г8
8ч8г8-7
-ч8ґ8
8ч8ґ8-7
-2ч8д8
8ч8д8-7
-ч8е8
8ч8е8-7
ч2е1
-ч8є8
8ч8є8-7
ч2є1
-2ч8ж8
8ч8ж8-7
-ч8з8
8ч8з8-7
-ч8и8
8ч8и8-7
ч2и1
-ч8і8
8ч8і8-7
ч2і1
-ч8ї8
8ч8ї8-7
ч2ї1
-ч8й8
8ч8й8-7
-ч8к8
8ч8к8-7
-ч8л8
8ч8л8-7
-ч8м8
8ч8м8-7
-ч8н8
8ч8н8-7
-ч8о8
8ч8о8-7
-ч8п8
8ч8п8-7
-ч8р8
8ч8р8-7
-ч8с8
8ч8с8-7
-ч8т8
8ч8т8-7
-ч8у8
8ч8у8-7
ч2у1
-ч8ф8
8ч8ф8-7
-ч8х8
8ч8х8-7
-ч8ц8
8ч8ц8-7
-2ч8ч8
8ч8ч8-7
-ч8ш8
8ч8ш8-7
-ч8щ8
8ч8щ8-7
-ч8ь8
8ч8ь8-7
ч6ь
-ч8ю8
8ч8ю8-7
ч2ю1
-ч8я8
8ч8я8-7
ч2я1
-ч8'8
-ч8’8
8ч8'8-7
ч6'
8ч8’8-7
ч6’
-ш8а8
8ш8а8-7
-2ш8б8
8ш8б8-7
-ш8в8
8ш8в8-7
-2ш8г8
8ш8г8-7
-ш8ґ8
8ш8ґ8-7
-ш8д8
8ш8д8-7
-ш8е8
8ш8е8-7
ш2е1
-ш8є8
8ш8є8-7
ш2є1
-ш8ж8
8ш8ж8-7
-ш8з8
8ш8з8-7
-ш8и8
8ш8и8-7
ш2и1
-ш8і8
8ш8і8-7
ш2і1
-ш8ї8
8ш8ї8-7
ш2ї1
-ш8й8
8ш8й8-7
-ш8к8
8ш8к8-7
-ш8л8
8ш8л8-7
-ш8м8
8ш8м8-7
-ш8н8
8ш8н8-7
-ш8о8
8ш8о8-7
ш2о1
-ш8п8
8ш8п8-7
-ш8р8
8ш8р8-7
-ш8с8
8ш8с8-7
-ш8т8
8ш8т8-7
-ш8у8
8ш8у8-7
ш2у1
-ш8ф8
8ш8ф8-7
-ш8х8
8ш8х8-7
-ш8ц8
8ш8ц8-7
-ш8ч8
8ш8ч8-7
-2ш8ш8
8ш8ш8-7
-ш8щ8
8ш8щ8-7
-ш8ь8
8ш8ь8-7
ш6ь
-ш8ю8
8ш8ю8-7
ш2ю1
-ш8я8
8ш8я8-7
ш2я1
-ш8'8
-ш8’8
8ш8'8-7
ш6'
8ш8’8-7
ш6’
-щ8а8
8щ8а8-7
щ2а1
-щ8б8
8щ8б8-7
-щ8в8
8щ8в8-7
-щ8г8
8щ8г8-7
-щ8ґ8
8щ8ґ8-7
-щ8д8
8щ8д8-7
-щ8е8
8щ8е8-7
щ2е1
-щ8є8
8щ8є8-7
щ2є1
-щ8ж8
8щ8ж8-7
-щ8з8
8щ8з8-7
-щ8и8
8щ8и8-7
щ2и1
-щ8і8
8щ8і8-7
щ2і1
-щ8ї8
8щ8ї8-7
щ2ї1
-щ8й8
8щ8й8-7
-щ8к8
8щ8к8-7
-щ8л8
8щ8л8-7
-щ8м8
8щ8м8-7
-щ8н8
8щ8н8-7
-щ8о8
8щ8о8-7
щ2о1
-щ8п8
8щ8п8-7
-щ8р8
8щ8р8-7
-щ8с8
8щ8с8-7
-щ8т8
8щ8т8-7
-щ8у8
8щ8у8-7
щ2у1
-щ8ф8
8щ8ф8-7
-щ8х8
8щ8х8-7
-щ8ц8
8щ8ц8-7
-щ8ч8
8щ8ч8-7
-щ8ш8
8щ8ш8-7
-2щ8щ8
8щ8щ8-7
-щ8ь8
8щ8ь8-7
щ6ь
-щ8ю8
8щ8ю8-7
щ2ю1
-щ8я8
8щ8я8-7
щ2я1
-щ8'8
-щ8’8
8щ8'8-7
щ6'
8щ8’8-7
щ6’
-ь8а8
-6ь
8ь8а8-7
ь2а1
-ь8б8
8ь8б8-7
-ь8в8
8ь8в8-7
-ь8г8
8ь8г8-7
-ь8ґ8
8ь8ґ8-7
-ь8д8
8ь8д8-7
-ь8е8
8ь8е8-7
ь2е1
-ь8є8
8ь8є8-7
ь2є1
-ь8ж8
8ь8ж8-7
-ь8з8
8ь8з8-7
-ь8и8
8ь8и8-7
ь2и1
-ь8і8
8ь8і8-7
ь2і1
-ь8ї8
8ь8ї8-7
ь2ї1
-ь8й8
8ь8й8-7
-ь8к8
8ь8к8-7
-ь8л8
8ь8л8-7
-ь8м8
8ь8м8-7
-ь8н8
8ь8н8-7
-ь8о8
8ь8о8-7
-ь8п8
8ь8п8-7
-ь8р8
8ь8р8-7
-ь8с8
8ь8с8-7
-ь8т8
8ь8т8-7
-ь8у8
8ь8у8-7
ь2у1
-ь8ф8
8ь8ф8-7
-ь8х8
8ь8х8-7
-ь8ц8
8ь8ц8-7
-ь8ч8
8ь8ч8-7
-ь8ш8
8ь8ш8-7
-ь8щ8
8ь8щ8-7
-6ь8ь8
8ь8ь8-7
ь6ь
-ь8ю8
8ь8ю8-7
ь2ю1
-ь8я8
8ь8я8-7
ь2я1
-ь8'8
-ь8’8
8ь8'8-7
ь6'
8ь8’8-7
ь6’
-ю8а8
-2ю1
8ю8а8-7
-ю8б8
8ю8б8-7
-ю8в8
8ю8в8-7
-ю8г8
8ю8г8-7
-ю8ґ8
8ю8ґ8-7
-ю8д8
8ю8д8-7
-ю8е8
8ю8е8-7
-ю8є8
8ю8є8-7
-ю8ж8
8ю8ж8-7
-ю8з8
8ю8з8-7
-ю8и8
8ю8и8-7
ю2и1
-ю8і8
8ю8і8-7
-ю8ї8
8ю8ї8-7
-ю8й8
8ю8й8-7
-ю8к8
8ю8к8-7
-ю8л8
8ю8л8-7
-ю8м8
8ю8м8-7
-ю8н8
8ю8н8-7
-ю8о8
8ю8о8-7
-ю8п8
8ю8п8-7
-ю8р8
8ю8р8-7
-ю8с8
8ю8с8-7
-ю8т8
8ю8т8-7
-ю8у8
8ю8у8-7
-ю8ф8
8ю8ф8-7
-ю8х8
8ю8х8-7
-ю8ц8
8ю8ц8-7
-ю8ч8
8ю8ч8-7
-ю8ш8
8ю8ш8-7
-ю8щ8
8ю8щ8-7
-ю8ь8
8ю8ь8-7
ю6ь
-2ю8ю8
8ю8ю8-7
-ю8я8
8ю8я8-7
-ю8'8
-ю8’8
8ю8'8-7
ю6'
8ю8’8-7
ю6’
-я8а8
-2я1
8я8а8-7
-я8б8
8я8б8-7
-я8в8
8я8в8-7
-я8г8
8я8г8-7
-я8ґ8
8я8ґ8-7
-я8д8
8я8д8-7
-я8е8
8я8е8-7
-я8є8
8я8є8-7
-я8ж8
8я8ж8-7
-я8з8
8я8з8-7
-я8и8
8я8и8-7
я2и1
-я8і8
8я8і8-7
-я8ї8
8я8ї8-7
-я8й8
8я8й8-7
-я8к8
8я8к8-7
-я8л8
8я8л8-7
-я8м8
8я8м8-7
-я8н8
8я8н8-7
-я8о8
8я8о8-7
-я8п8
8я8п8-7
-я8р8
8я8р8-7
-я8с8
8я8с8-7
-я8т8
8я8т8-7
-я8у8
8я8у8-7
-я8ф8
8я8ф8-7
-я8х8
8я8х8-7
-я8ц8
8я8ц8-7
-я8ч8
8я8ч8-7
-я8ш8
8я8ш8-7
-я8щ8
8я8щ8-7
-я8ь8
8я8ь8-7
я6ь
-я8ю8
8я8ю8-7
-2я8я8
8я8я8-7
-я8'8
-я8’8
8я8'8-7
я6'
8я8’8-7
я6’
-'8а8
-6'
-’8а8
-6’
8'8а8-7
'2а1
8’8а8-7
’2а1
-'8б8
-’8б8
8'8б8-7
8’8б8-7
-'8в8
-’8в8
8'8в8-7
8’8в8-7
-'8г8
-’8г8
8'8г8-7
8’8г8-7
-'8ґ8
-’8ґ8
8'8ґ8-7
8’8ґ8-7
-'8д8
-’8д8
8'8д8-7
8’8д8-7
-'8е8
-’8е8
8'8е8-7
'2е1
8’8е8-7
’2е1
-'8є8
-’8є8
8'8є8-7
'2є1
8’8є8-7
’2є1
-'8ж8
-’8ж8
8'8ж8-7
8’8ж8-7
-'8з8
-’8з8
8'8з8-7
8’8з8-7
-'8и8
-’8и8
8'8и8-7
'2и1
8’8и8-7
’2и1
-'8і8
-’8і8
8'8і8-7
'2і1
8’8і8-7
’2і1
-'8ї8
-’8ї8
8'8ї8-7
8’8ї8-7
-'8й8
-’8й8
8'8й8-7
8’8й8-7
-'8к8
-’8к8
8'8к8-7
8’8к8-7
-'8л8
-’8л8
8'8л8-7
8’8л8-7
-'8м8
-’8м8
8'8м8-7
8’8м8-7
-'8н8
-’8н8
8'8н8-7
8’8н8-7
-'8о8
-’8о8
8'8о8-7
'2о1
8’8о8-7
’2о1
-'8п8
-’8п8
8'8п8-7
8’8п8-7
-'8р8
-’8р8
8'8р8-7
8’8р8-7
-'8с8
-’8с8
8'8с8-7
8’8с8-7
-'8т8
-’8т8
8'8т8-7
8’8т8-7
-'8у8
-’8у8
8'8у8-7
'2у1
8’8у8-7
’2у1
-'8ф8
-’8ф8
8'8ф8-7
8’8ф8-7
-'8х8
-’8х8
8'8х8-7
8’8х8-7
-'8ц8
-’8ц8
8'8ц8-7
8’8ц8-7
-'8ч8
-’8ч8
8'8ч8-7
8’8ч8-7
-'8ш8
-’8ш8
8'8ш8-7
8’8ш8-7
-'8щ8
-’8щ8
8'8щ8-7
8’8щ8-7
-'8ь8
-’8ь8
8'8ь8-7
'6ь
8’8ь8-7
’6ь
-'8ю8
-’8ю8
8'8ю8-7
'2ю1
8’8ю8-7
’2ю1
-'8я8
-’8я8
8'8я8-7
'2я1
8’8я8-7
’2я1
-6'8'8
-6’8’8
8'8'8-7
'6'
8’8’8-7
’6’
PK
!<W
update.localeen-US
PK
!<M>XGGcomponents/ConsoleAPIStorage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;
var Cc = Components.classes;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// This constant tells how many messages to process in a single timer execution.
const MESSAGES_IN_INTERVAL = 1500

const STORAGE_MAX_EVENTS = 1000;

var _consoleStorage = new Map();

const CONSOLEAPISTORAGE_CID = Components.ID('{96cf7855-dfa9-4c6d-8276-f9705b4890f2}');

/**
 * The ConsoleAPIStorage is meant to cache window.console API calls for later
 * reuse by other components when needed. For example, the Web Console code can
 * display the cached messages when it opens for the active tab.
 *
 * ConsoleAPI messages are stored as they come from the ConsoleAPI code, with
 * all their properties. They are kept around until the inner window object that
 * created the messages is destroyed. Messages are indexed by the inner window
 * ID.
 *
 * Usage:
 *    let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
 *                              .getService(Ci.nsIConsoleAPIStorage);
 *
 *    // Get the cached events array for the window you want (use the inner
 *    // window ID).
 *    let events = ConsoleAPIStorage.getEvents(innerWindowID);
 *    events.forEach(function(event) { ... });
 *
 *    // Clear the events for the given inner window ID.
 *    ConsoleAPIStorage.clearEvents(innerWindowID);
 */
function ConsoleAPIStorageService() {
  this.init();
}

ConsoleAPIStorageService.prototype = {
  classID : CONSOLEAPISTORAGE_CID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleAPIStorage,
                                         Ci.nsIObserver]),
  classInfo: XPCOMUtils.generateCI({
    classID: CONSOLEAPISTORAGE_CID,
    contractID: '@mozilla.org/consoleAPI-storage;1',
    interfaces: [Ci.nsIConsoleAPIStorage, Ci.nsIObserver],
    flags: Ci.nsIClassInfo.SINGLETON
  }),

  observe: function CS_observe(aSubject, aTopic, aData)
  {
    if (aTopic == "xpcom-shutdown") {
      Services.obs.removeObserver(this, "xpcom-shutdown");
      Services.obs.removeObserver(this, "inner-window-destroyed");
      Services.obs.removeObserver(this, "memory-pressure");
    }
    else if (aTopic == "inner-window-destroyed") {
      let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
      this.clearEvents(innerWindowID + "");
    }
    else if (aTopic == "memory-pressure") {
      this.clearEvents();
    }
  },

  /** @private */
  init: function CS_init()
  {
    Services.obs.addObserver(this, "xpcom-shutdown");
    Services.obs.addObserver(this, "inner-window-destroyed");
    Services.obs.addObserver(this, "memory-pressure");
  },

  /**
   * Get the events array by inner window ID or all events from all windows.
   *
   * @param string [aId]
   *        Optional, the inner window ID for which you want to get the array of
   *        cached events.
   * @returns array
   *          The array of cached events for the given window. If no |aId| is
   *          given this function returns all of the cached events, from any
   *          window.
   */
  getEvents: function CS_getEvents(aId)
  {
    if (aId != null) {
      return (_consoleStorage.get(aId) || []).slice(0);
    }

    let result = [];

    for (let [id, events] of _consoleStorage) {
      result.push.apply(result, events);
    }

    return result.sort(function(a, b) {
      return a.timeStamp - b.timeStamp;
    });
  },

  /**
   * Record an event associated with the given window ID.
   *
   * @param string aId
   *        The ID of the inner window for which the event occurred or "jsm" for
   *        messages logged from JavaScript modules..
   * @param string aOuterId
   *        This ID is used as 3rd parameters for the console-api-log-event
   *        notification.
   * @param object aEvent
   *        A JavaScript object you want to store.
   */
  recordEvent: function CS_recordEvent(aId, aOuterId, aEvent)
  {
    if (!_consoleStorage.has(aId)) {
      _consoleStorage.set(aId, []);
    }

    let storage = _consoleStorage.get(aId);

    // Clone originAttributes to prevent "TypeError: can't access dead object"
    // exceptions when cached console messages are retrieved/filtered
    // by the devtools webconsole actor.
    aEvent.originAttributes = Cu.cloneInto(aEvent.originAttributes, {});

    storage.push(aEvent);

    // truncate
    if (storage.length > STORAGE_MAX_EVENTS) {
      storage.shift();
    }

    Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId);
    Services.obs.notifyObservers(aEvent, "console-storage-cache-event", aId);
  },

  /**
   * Clear storage data for the given window.
   *
   * @param string [aId]
   *        Optional, the inner window ID for which you want to clear the
   *        messages. If this is not specified all of the cached messages are
   *        cleared, from all window objects.
   */
  clearEvents: function CS_clearEvents(aId)
  {
    if (aId != null) {
      _consoleStorage.delete(aId);
    }
    else {
      _consoleStorage.clear();
      Services.obs.notifyObservers(null, "console-storage-reset");
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPIStorageService]);
PK
!< ezz"components/BrowserElementParent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;
var Cc = Components.classes;
var Cr = Components.results;

/* BrowserElementParent injects script to listen for certain events in the
 * child.  We then listen to messages from the child script and take
 * appropriate action here in the parent.
 */

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");

function debug(msg) {
  //dump("BrowserElementParent - " + msg + "\n");
}

function getIntPref(prefName, def) {
  try {
    return Services.prefs.getIntPref(prefName);
  }
  catch(err) {
    return def;
  }
}

function handleWindowEvent(e) {
  if (this._browserElementParents) {
    let beps = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(this._browserElementParents);
    beps.forEach(bep => bep._handleOwnerEvent(e));
  }
}

function defineNoReturnMethod(fn) {
  return function method() {
    if (!this._domRequestReady) {
      // Remote browser haven't been created, we just queue the API call.
      let args = Array.slice(arguments);
      args.unshift(this);
      this._pendingAPICalls.push(method.bind.apply(fn, args));
      return;
    }
    if (this._isAlive()) {
      fn.apply(this, arguments);
    }
  };
}

function defineDOMRequestMethod(msgName) {
  return function() {
    return this._sendDOMRequest(msgName);
  };
}

function BrowserElementParent() {
  debug("Creating new BrowserElementParent object");
  this._domRequestCounter = 0;
  this._domRequestReady = false;
  this._pendingAPICalls = [];
  this._pendingDOMRequests = {};
  this._nextPaintListeners = [];
  this._pendingDOMFullscreen = false;

  Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
  Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
  Services.obs.addObserver(this, 'back-docommand', /* ownsWeak = */ true);
}

BrowserElementParent.prototype = {

  classDescription: "BrowserElementAPI implementation",
  classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
  contractID: "@mozilla.org/dom/browser-element-api;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
                                         Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  setFrameLoader: function(frameLoader) {
    debug("Setting frameLoader");
    this._frameLoader = frameLoader;
    this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
    if (!this._frameElement) {
      debug("No frame element?");
      return;
    }
    // Listen to visibilitychange on the iframe's owner window, and forward
    // changes down to the child.  We want to do this while registering as few
    // visibilitychange listeners on _window as possible, because such a listener
    // may live longer than this BrowserElementParent object.
    //
    // To accomplish this, we register just one listener on the window, and have
    // it reference a WeakMap whose keys are all the BrowserElementParent objects
    // on the window.  Then when the listener fires, we iterate over the
    // WeakMap's keys (which we can do, because we're chrome) to notify the
    // BrowserElementParents.
    if (!this._window._browserElementParents) {
      this._window._browserElementParents = new WeakMap();
      let handler = handleWindowEvent.bind(this._window);
      let windowEvents = ['visibilitychange', 'fullscreenchange'];
      let els = Cc["@mozilla.org/eventlistenerservice;1"]
                  .getService(Ci.nsIEventListenerService);
      for (let event of windowEvents) {
        els.addSystemEventListener(this._window, event, handler,
                                   /* useCapture = */ true);
      }
    }

    this._window._browserElementParents.set(this, null);

    // Insert ourself into the prompt service.
    BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
    this._setupMessageListener();
  },

  destroyFrameScripts() {
    debug("Destroying frame scripts");
    this._mm.sendAsyncMessage("browser-element-api:destroy");
  },

  _runPendingAPICall: function() {
    if (!this._pendingAPICalls) {
      return;
    }
    for (let i = 0; i < this._pendingAPICalls.length; i++) {
      try {
        this._pendingAPICalls[i]();
      } catch (e) {
        // throw the expections from pending functions.
        debug('Exception when running pending API call: ' +  e);
      }
    }
    delete this._pendingAPICalls;
  },

  _setupMessageListener: function() {
    this._mm = this._frameLoader.messageManager;
    this._mm.addMessageListener('browser-element-api:call', this);
  },

  receiveMessage: function(aMsg) {
    if (!this._isAlive()) {
      return;
    }

    // Messages we receive are handed to functions which take a (data) argument,
    // where |data| is the message manager's data object.
    // We use a single message and dispatch to various function based
    // on data.msg_name
    let mmCalls = {
      "hello": this._recvHello,
      "loadstart": this._fireProfiledEventFromMsg,
      "loadend": this._fireProfiledEventFromMsg,
      "close": this._fireEventFromMsg,
      "error": this._fireEventFromMsg,
      "firstpaint": this._fireProfiledEventFromMsg,
      "documentfirstpaint": this._fireProfiledEventFromMsg,
      "nextpaint": this._recvNextPaint,
      "got-purge-history": this._gotDOMRequestResult,
      "got-screenshot": this._gotDOMRequestResult,
      "got-contentdimensions": this._gotDOMRequestResult,
      "got-can-go-back": this._gotDOMRequestResult,
      "got-can-go-forward": this._gotDOMRequestResult,
      "requested-dom-fullscreen": this._requestedDOMFullscreen,
      "fullscreen-origin-change": this._fullscreenOriginChange,
      "exit-dom-fullscreen": this._exitDomFullscreen,
      "got-visible": this._gotDOMRequestResult,
      "got-set-input-method-active": this._gotDOMRequestResult,
      "scrollviewchange": this._handleScrollViewChange,
      "caretstatechanged": this._handleCaretStateChanged,
      "findchange": this._handleFindChange,
      "execute-script-done": this._gotDOMRequestResult,
      "got-web-manifest": this._gotDOMRequestResult,
    };

    let mmSecuritySensitiveCalls = {
      "audioplaybackchange": this._fireEventFromMsg,
      "showmodalprompt": this._handleShowModalPrompt,
      "contextmenu": this._fireCtxMenuEvent,
      "securitychange": this._fireEventFromMsg,
      "locationchange": this._fireEventFromMsg,
      "iconchange": this._fireEventFromMsg,
      "scrollareachanged": this._fireEventFromMsg,
      "titlechange": this._fireProfiledEventFromMsg,
      "opensearch": this._fireEventFromMsg,
      "manifestchange": this._fireEventFromMsg,
      "metachange": this._fireEventFromMsg,
      "resize": this._fireEventFromMsg,
      "activitydone": this._fireEventFromMsg,
      "scroll": this._fireEventFromMsg,
      "opentab": this._fireEventFromMsg
    };

    if (aMsg.data.msg_name in mmCalls) {
      return mmCalls[aMsg.data.msg_name].apply(this, arguments);
    } else if (aMsg.data.msg_name in mmSecuritySensitiveCalls) {
      return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(this, arguments);
    }
  },

  _removeMessageListener: function() {
    this._mm.removeMessageListener('browser-element-api:call', this);
  },

  /**
   * You shouldn't touch this._frameElement or this._window if _isAlive is
   * false.  (You'll likely get an exception if you do.)
   */
  _isAlive: function() {
    return !Cu.isDeadWrapper(this._frameElement) &&
           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
           !Cu.isDeadWrapper(this._frameElement.ownerGlobal);
  },

  get _window() {
    return this._frameElement.ownerGlobal;
  },

  get _windowUtils() {
    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
  },

  promptAuth: function(authDetail, callback) {
    let evt;
    let self = this;
    let callbackCalled = false;
    let cancelCallback = function() {
      if (!callbackCalled) {
        callbackCalled = true;
        callback(false, null, null);
      }
    };

    // We don't handle password-only prompts.
    if (authDetail.isOnlyPassword) {
      cancelCallback();
      return;
    }

    /* username and password */
    let detail = {
      host:     authDetail.host,
      path:     authDetail.path,
      realm:    authDetail.realm,
      isProxy:  authDetail.isProxy
    };

    evt = this._createEvent('usernameandpasswordrequired', detail,
                            /* cancelable */ true);
    Cu.exportFunction(function(username, password) {
      if (callbackCalled)
        return;
      callbackCalled = true;
      callback(true, username, password);
    }, evt.detail, { defineAs: 'authenticate' });

    Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });

    this._frameElement.dispatchEvent(evt);

    if (!evt.defaultPrevented) {
      cancelCallback();
    }
  },

  _sendAsyncMsg: function(msg, data) {
    try {
      if (!data) {
        data = { };
      }

      data.msg_name = msg;
      this._mm.sendAsyncMessage('browser-element-api:call', data);
    } catch (e) {
      return false;
    }
    return true;
  },

  _recvHello: function() {
    debug("recvHello");

    // Inform our child if our owner element's document is invisible.  Note
    // that we must do so here, rather than in the BrowserElementParent
    // constructor, because the BrowserElementChild may not be initialized when
    // we run our constructor.
    if (this._window.document.hidden) {
      this._ownerVisibilityChange();
    }

    if (!this._domRequestReady) {
      // At least, one message listener such as for hello is registered.
      // So we can use sendAsyncMessage now.
      this._domRequestReady = true;
      this._runPendingAPICall();
    }
  },

  _fireCtxMenuEvent: function(data) {
    let detail = data.json;
    let evtName = detail.msg_name;

    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
    let evt = this._createEvent(evtName, detail, /* cancellable */ true);

    if (detail.contextmenu) {
      var self = this;
      Cu.exportFunction(function(id) {
        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
      }, evt.detail, { defineAs: 'contextMenuItemSelected' });
    }

    // The embedder may have default actions on context menu events, so
    // we fire a context menu event even if the child didn't define a
    // custom context menu
    return !this._frameElement.dispatchEvent(evt);
  },

  /**
   * add profiler marker for each event fired.
   */
  _fireProfiledEventFromMsg: function(data) {
    if (Services.profiler !== undefined) {
      Services.profiler.AddMarker(data.json.msg_name);
    }
    this._fireEventFromMsg(data);
  },

  /**
   * Fire either a vanilla or a custom event, depending on the contents of
   * |data|.
   */
  _fireEventFromMsg: function(data) {
    let detail = data.json;
    let name = detail.msg_name;

    // For events that send a "_payload_" property, we just want to transmit
    // this in the event.
    if ("_payload_" in detail) {
      detail = detail._payload_;
    }

    debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
    let evt = this._createEvent(name, detail,
                                /* cancelable = */ false);
    this._frameElement.dispatchEvent(evt);
  },

  _handleShowModalPrompt: function(data) {
    // Fire a showmodalprmopt event on the iframe.  When this method is called,
    // the child is spinning in a nested event loop waiting for an
    // unblock-modal-prompt message.
    //
    // If the embedder calls preventDefault() on the showmodalprompt event,
    // we'll block the child until event.detail.unblock() is called.
    //
    // Otherwise, if preventDefault() is not called, we'll send the
    // unblock-modal-prompt message to the child as soon as the event is done
    // dispatching.

    let detail = data.json;
    debug('handleShowPrompt ' + JSON.stringify(detail));

    // Strip off the windowID property from the object we send along in the
    // event.
    let windowID = detail.windowID;
    delete detail.windowID;
    debug("Event will have detail: " + JSON.stringify(detail));
    let evt = this._createEvent('showmodalprompt', detail,
                                /* cancelable = */ true);

    let self = this;
    let unblockMsgSent = false;
    function sendUnblockMsg() {
      if (unblockMsgSent) {
        return;
      }
      unblockMsgSent = true;

      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
      // return value of confirm() to a boolean); Gecko does that for us.

      let data = { windowID: windowID,
                   returnValue: evt.detail.returnValue };
      self._sendAsyncMsg('unblock-modal-prompt', data);
    }

    Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });

    this._frameElement.dispatchEvent(evt);

    if (!evt.defaultPrevented) {
      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
      // evt.detail.unblock().
      sendUnblockMsg();
    }
  },

  // Called when state of accessible caret in child has changed.
  // The fields of data is as following:
  //  - rect: Contains bounding rectangle of selection, Include width, height,
  //          top, bottom, left and right.
  //  - commands: Describe what commands can be executed in child. Include canSelectAll,
  //              canCut, canCopy and canPaste. For example: if we want to check if cut
  //              command is available, using following code, if (data.commands.canCut) {}.
  //  - zoomFactor: Current zoom factor in child frame.
  //  - reason: The reason causes the state changed. Include "visibilitychange",
  //            "updateposition", "longpressonemptycontent", "taponcaret", "presscaret",
  //            "releasecaret".
  //  - collapsed: Indicate current selection is collapsed or not.
  //  - caretVisible: Indicate the caret visiibility.
  //  - selectionVisible: Indicate current selection is visible or not.
  //  - selectionEditable: Indicate current selection is editable or not.
  //  - selectedTextContent: Contains current selected text content, which is
  //                         equivalent to the string returned by Selection.toString().
  _handleCaretStateChanged: function(data) {
    let evt = this._createEvent('caretstatechanged', data.json,
                                /* cancelable = */ false);

    let self = this;
    function sendDoCommandMsg(cmd) {
      let data = { command: cmd };
      self._sendAsyncMsg('copypaste-do-command', data);
    }
    Cu.exportFunction(sendDoCommandMsg, evt.detail, { defineAs: 'sendDoCommandMsg' });

    this._frameElement.dispatchEvent(evt);
  },

  _handleScrollViewChange: function(data) {
    let evt = this._createEvent("scrollviewchange", data.json,
                                /* cancelable = */ false);
    this._frameElement.dispatchEvent(evt);
  },

  _handleFindChange: function(data) {
    let evt = this._createEvent("findchange", data.json,
                                /* cancelable = */ false);
    this._frameElement.dispatchEvent(evt);
  },

  _createEvent: function(evtName, detail, cancelable) {
    // This will have to change if we ever want to send a CustomEvent with null
    // detail.  For now, it's OK.
    if (detail !== undefined && detail !== null) {
      detail = Cu.cloneInto(detail, this._window);
      return new this._window.CustomEvent('mozbrowser' + evtName,
                                          { bubbles: true,
                                            cancelable: cancelable,
                                            detail: detail });
    }

    return new this._window.Event('mozbrowser' + evtName,
                                  { bubbles: true,
                                    cancelable: cancelable });
  },

  /**
   * Kick off a DOMRequest in the child process.
   *
   * We'll fire an event called |msgName| on the child process, passing along
   * an object with two fields:
   *
   *  - id:  the ID of this request.
   *  - arg: arguments to pass to the child along with this request.
   *
   * We expect the child to pass the ID back to us upon completion of the
   * request.  See _gotDOMRequestResult.
   */
  _sendDOMRequest: function(msgName, args) {
    let id = 'req_' + this._domRequestCounter++;
    let req = Services.DOMRequest.createRequest(this._window);
    let self = this;
    let send = function() {
      if (!self._isAlive()) {
        return;
      }
      if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
        self._pendingDOMRequests[id] = req;
      } else {
        Services.DOMRequest.fireErrorAsync(req, "fail");
      }
    };
    if (this._domRequestReady) {
      send();
    } else {
      // Child haven't been loaded.
      this._pendingAPICalls.push(send);
    }
    return req;
  },

  /**
   * Called when the child process finishes handling a DOMRequest.  data.json
   * must have the fields [id, successRv], if the DOMRequest was successful, or
   * [id, errorMsg], if the request was not successful.
   *
   * The fields have the following meanings:
   *
   *  - id:        the ID of the DOM request (see _sendDOMRequest)
   *  - successRv: the request's return value, if the request succeeded
   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
   *               failed.
   *
   */
  _gotDOMRequestResult: function(data) {
    let req = this._pendingDOMRequests[data.json.id];
    delete this._pendingDOMRequests[data.json.id];

    if ('successRv' in data.json) {
      debug("Successful gotDOMRequestResult.");
      let clientObj = Cu.cloneInto(data.json.successRv, this._window);
      Services.DOMRequest.fireSuccess(req, clientObj);
    }
    else {
      debug("Got error in gotDOMRequestResult.");
      Services.DOMRequest.fireErrorAsync(req,
        Cu.cloneInto(data.json.errorMsg, this._window));
    }
  },

  getChildProcessOffset: function() {
    let offset = { x: 0, y: 0 };
    let tabParent = this._frameLoader.tabParent;
    if (tabParent) {
      let offsetX = {};
      let offsetY = {};
      tabParent.getChildProcessOffset(offsetX, offsetY);
      offset.x = offsetX.value;
      offset.y = offsetY.value;
    }
    return offset;
  },

  sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
    let offset = this.getChildProcessOffset();
    x += offset.x;
    y += offset.y;

    this._sendAsyncMsg("send-mouse-event", {
      "type": type,
      "x": x,
      "y": y,
      "button": button,
      "clickCount": clickCount,
      "modifiers": modifiers
    });
  }),

  sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
                                                radiisX, radiisY, rotationAngles, forces,
                                                count, modifiers) {

    let offset = this.getChildProcessOffset();
    for (var i = 0; i < touchesX.length; i++) {
      touchesX[i] += offset.x;
    }
    for (var i = 0; i < touchesY.length; i++) {
      touchesY[i] += offset.y;
    }
    this._sendAsyncMsg("send-touch-event", {
      "type": type,
      "identifiers": identifiers,
      "touchesX": touchesX,
      "touchesY": touchesY,
      "radiisX": radiisX,
      "radiisY": radiisY,
      "rotationAngles": rotationAngles,
      "forces": forces,
      "count": count,
      "modifiers": modifiers
    });
  }),

  getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
  getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
  getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),

  findAll: defineNoReturnMethod(function(searchString, caseSensitivity) {
    return this._sendAsyncMsg('find-all', {
      searchString,
      caseSensitive: caseSensitivity == Ci.nsIBrowserElementAPI.FIND_CASE_SENSITIVE
    });
  }),

  findNext: defineNoReturnMethod(function(direction) {
    return this._sendAsyncMsg('find-next', {
      backward: direction == Ci.nsIBrowserElementAPI.FIND_BACKWARD
    });
  }),

  clearMatch: defineNoReturnMethod(function() {
    return this._sendAsyncMsg('clear-match');
  }),

  goBack: defineNoReturnMethod(function() {
    this._sendAsyncMsg('go-back');
  }),

  goForward: defineNoReturnMethod(function() {
    this._sendAsyncMsg('go-forward');
  }),

  reload: defineNoReturnMethod(function(hardReload) {
    this._sendAsyncMsg('reload', {hardReload: hardReload});
  }),

  stop: defineNoReturnMethod(function() {
    this._sendAsyncMsg('stop');
  }),

  executeScript: function(script, options) {
    if (!this._isAlive()) {
      throw Components.Exception("Dead content process",
                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
    }

    // Enforcing options.url or options.origin
    if (!options.url && !options.origin) {
      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
    }
    return this._sendDOMRequest('execute-script', {script, options});
  },

  /*
   * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
   */
  zoom: defineNoReturnMethod(function(zoom) {
    zoom *= 100;
    zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
    zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
    this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
  }),

  purgeHistory: defineDOMRequestMethod('purge-history'),


  download: function(_url, _options) {
    if (!this._isAlive()) {
      return null;
    }

    let uri = Services.io.newURI(_url);
    let url = uri.QueryInterface(Ci.nsIURL);

    debug('original _options = ' + uneval(_options));

    // Ensure we have _options, we always use it to send the filename.
    _options = _options || {};
    if (!_options.filename) {
      _options.filename = url.fileName;
    }

    debug('final _options = ' + uneval(_options));

    // Ensure we have a filename.
    if (!_options.filename) {
      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
    }

    let interfaceRequestor =
      this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
    let req = Services.DOMRequest.createRequest(this._window);

    function DownloadListener() {
      debug('DownloadListener Constructor');
    }
    DownloadListener.prototype = {
      extListener: null,
      onStartRequest: function(aRequest, aContext) {
        debug('DownloadListener - onStartRequest');
        let extHelperAppSvc =
          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
          getService(Ci.nsIExternalHelperAppService);
        let channel = aRequest.QueryInterface(Ci.nsIChannel);

        // First, we'll ensure the filename doesn't have any leading
        // periods. We have to do it here to avoid ending up with a filename
        // that's only an extension with no extension (e.g. Sending in
        // '.jpeg' without stripping the '.' would result in a filename of
        // 'jpeg' where we want 'jpeg.jpeg'.
        _options.filename = _options.filename.replace(/^\.+/, "");

        let ext = null;
        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
        try {
          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
        } catch (e) { ext = null; }

        // Check if we need to add an extension to the filename.
        if (ext && !_options.filename.endsWith(ext)) {
          _options.filename += ext;
        }
        // Set the filename to use when saving to disk.
        channel.contentDispositionFilename = _options.filename;

        this.extListener =
          extHelperAppSvc.doContent(
              channel.contentType,
              aRequest,
              interfaceRequestor,
              true);
        this.extListener.onStartRequest(aRequest, aContext);
      },
      onStopRequest: function(aRequest, aContext, aStatusCode) {
        debug('DownloadListener - onStopRequest (aStatusCode = ' +
               aStatusCode + ')');
        if (aStatusCode == Cr.NS_OK) {
          // Everything looks great.
          debug('DownloadListener - Download Successful.');
          Services.DOMRequest.fireSuccess(req, aStatusCode);
        }
        else {
          // In case of failure, we'll simply return the failure status code.
          debug('DownloadListener - Download Failed!');
          Services.DOMRequest.fireError(req, aStatusCode);
        }

        if (this.extListener) {
          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
        }
      },
      onDataAvailable: function(aRequest, aContext, aInputStream,
                                aOffset, aCount) {
        this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                         aOffset, aCount);
      },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
                                             Ci.nsIRequestObserver])
    };

    let referrer = Services.io.newURI(_options.referrer);
    let principal =
      Services.scriptSecurityManager.createCodebasePrincipal(
        referrer, this._frameLoader.loadContext.originAttributes);

    let channel = NetUtil.newChannel({
      uri: url,
      loadingPrincipal: principal,
      securityFlags: SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
    });

    // XXX We would set private browsing information prior to calling this.
    channel.notificationCallbacks = interfaceRequestor;

    // Since we're downloading our own local copy we'll want to bypass the
    // cache and local cache if the channel let's us specify this.
    let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
                Ci.nsIChannel.LOAD_BYPASS_CACHE;
    if (channel instanceof Ci.nsICachingChannel) {
      debug('This is a caching channel. Forcing bypass.');
      flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
    }

    channel.loadFlags |= flags;

    if (channel instanceof Ci.nsIHttpChannel) {
      debug('Setting HTTP referrer = ' + (referrer && referrer.spec));
      channel.referrer = referrer;
      if (channel instanceof Ci.nsIHttpChannelInternal) {
        channel.forceAllowThirdPartyCookie = true;
      }
    }

    // Set-up complete, let's get things started.
    channel.asyncOpen2(new DownloadListener());

    return req;
  },

  getScreenshot: function(_width, _height, _mimeType) {
    if (!this._isAlive()) {
      throw Components.Exception("Dead content process",
                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
    }

    let width = parseInt(_width);
    let height = parseInt(_height);
    let mimeType = (typeof _mimeType === 'string') ?
      _mimeType.trim().toLowerCase() : 'image/jpeg';
    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
      throw Components.Exception("Invalid argument",
                                 Cr.NS_ERROR_INVALID_ARG);
    }

    return this._sendDOMRequest('get-screenshot',
                                {width: width, height: height,
                                 mimeType: mimeType});
  },

  _recvNextPaint: function(data) {
    let listeners = this._nextPaintListeners;
    this._nextPaintListeners = [];
    for (let listener of listeners) {
      try {
        listener.recvNextPaint();
      } catch (e) {
        // If a listener throws we'll continue.
      }
    }
  },

  addNextPaintListener: function(listener) {
    if (!this._isAlive()) {
      throw Components.Exception("Dead content process",
                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
    }

    let self = this;
    let run = function() {
      if (self._nextPaintListeners.push(listener) == 1)
        self._sendAsyncMsg('activate-next-paint-listener');
    };
    if (!this._domRequestReady) {
      this._pendingAPICalls.push(run);
    } else {
      run();
    }
  },

  removeNextPaintListener: function(listener) {
    if (!this._isAlive()) {
      throw Components.Exception("Dead content process",
                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
    }

    let self = this;
    let run = function() {
      for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
        if (self._nextPaintListeners[i] == listener) {
          self._nextPaintListeners.splice(i, 1);
          break;
        }
      }

      if (self._nextPaintListeners.length == 0)
        self._sendAsyncMsg('deactivate-next-paint-listener');
    };
    if (!this._domRequestReady) {
      this._pendingAPICalls.push(run);
    } else {
      run();
    }
  },

  getWebManifest: defineDOMRequestMethod('get-web-manifest'),
  /**
   * Called when the visibility of the window which owns this iframe changes.
   */
  _ownerVisibilityChange: function() {
    this._sendAsyncMsg('owner-visibility-change',
                       {visible: !this._window.document.hidden});
  },

  _requestedDOMFullscreen: function() {
    this._pendingDOMFullscreen = true;
    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement);
  },

  _fullscreenOriginChange: function(data) {
    Services.obs.notifyObservers(
      this._frameElement, "fullscreen-origin-change", data.json.originNoSuffix);
  },

  _exitDomFullscreen: function(data) {
    this._windowUtils.remoteFrameFullscreenReverted();
  },

  _handleOwnerEvent: function(evt) {
    switch (evt.type) {
      case 'visibilitychange':
        this._ownerVisibilityChange();
        break;
      case 'fullscreenchange':
        if (!this._window.document.fullscreenElement) {
          this._sendAsyncMsg('exit-fullscreen');
        } else if (this._pendingDOMFullscreen) {
          this._pendingDOMFullscreen = false;
          this._sendAsyncMsg('entered-fullscreen');
        }
        break;
    }
  },

  _fireFatalError: function() {
    let evt = this._createEvent('error', {type: 'fatal'},
                                /* cancelable = */ false);
    this._frameElement.dispatchEvent(evt);
  },

  observe: function(subject, topic, data) {
    switch(topic) {
    case 'oop-frameloader-crashed':
      if (this._isAlive() && subject == this._frameLoader) {
        this._fireFatalError();
      }
      break;
    case 'ask-children-to-execute-copypaste-command':
      if (this._isAlive() && this._frameElement == subject.wrappedJSObject) {
        this._sendAsyncMsg('copypaste-do-command', { command: data });
      }
      break;
    case 'back-docommand':
      if (this._isAlive() && this._frameLoader.visible) {
          this.goBack();
      }
      break;
    default:
      debug('Unknown topic: ' + topic);
      break;
    };
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
PK
!<}components/FeedProcessor.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/. */

function LOG(str) {
  dump("*** " + str + "\n");
}

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
const FP_CLASSID = Components.ID("{26acb1f0-28fc-43bc-867a-a46aabc85dd4}");
const FP_CLASSNAME = "Feed Processor";
const FR_CONTRACTID = "@mozilla.org/feed-result;1";
const FR_CLASSID = Components.ID("{072a5c3d-30c6-4f07-b87f-9f63d51403f2}");
const FR_CLASSNAME = "Feed Result";
const FEED_CONTRACTID = "@mozilla.org/feed;1";
const FEED_CLASSID = Components.ID("{5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}");
const FEED_CLASSNAME = "Feed";
const ENTRY_CONTRACTID = "@mozilla.org/feed-entry;1";
const ENTRY_CLASSID = Components.ID("{8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}");
const ENTRY_CLASSNAME = "Feed Entry";
const TEXTCONSTRUCT_CONTRACTID = "@mozilla.org/feed-textconstruct;1";
const TEXTCONSTRUCT_CLASSID =
  Components.ID("{b992ddcd-3899-4320-9909-924b3e72c922}");
const TEXTCONSTRUCT_CLASSNAME = "Feed Text Construct";
const GENERATOR_CONTRACTID = "@mozilla.org/feed-generator;1";
const GENERATOR_CLASSID =
  Components.ID("{414af362-9ad8-4296-898e-62247f25a20e}");
const GENERATOR_CLASSNAME = "Feed Generator";
const PERSON_CONTRACTID = "@mozilla.org/feed-person;1";
const PERSON_CLASSID = Components.ID("{95c963b7-20b2-11db-92f6-001422106990}");
const PERSON_CLASSNAME = "Feed Person";

const IO_CONTRACTID = "@mozilla.org/network/io-service;1"
const BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1"
const ARRAY_CONTRACTID = "@mozilla.org/array;1";
const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
const PARSERUTILS_CONTRACTID = "@mozilla.org/parserutils;1";

const gMimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
var gIoService = null;

const XMLNS = "http://www.w3.org/XML/1998/namespace";
const RSS090NS = "http://my.netscape.com/rdf/simple/0.9/";

/** *** Some general utils *****/
function strToURI(link, base) {
  base = base || null;
  if (!gIoService)
    gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  try {
    return gIoService.newURI(link, null, base);
  } catch (e) {
    return null;
  }
}

function isArray(a) {
  return isObject(a) && a.constructor == Array;
}

function isObject(a) {
  return (a && typeof a == "object") || isFunction(a);
}

function isFunction(a) {
  return typeof a == "function";
}

function isIID(a, iid) {
  var rv = false;
  try {
    a.QueryInterface(iid);
    rv = true;
  } catch (e) {
  }
  return rv;
}

function isIArray(a) {
  return isIID(a, Ci.nsIArray);
}

function isIFeedContainer(a) {
  return isIID(a, Ci.nsIFeedContainer);
}

function stripTags(someHTML) {
  return someHTML.replace(/<[^>]+>/g, "");
}

/**
 * Searches through an array of links and returns a JS array
 * of matching property bags.
 */
const IANA_URI = "http://www.iana.org/assignments/relation/";
function findAtomLinks(rel, links) {
  var rvLinks = [];
  for (var i = 0; i < links.length; ++i) {
    var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
    // atom:link MUST have @href
    if (bagHasKey(linkElement, "href")) {
      var relAttribute = null;
      if (bagHasKey(linkElement, "rel"))
        relAttribute = linkElement.getPropertyAsAString("rel")
      if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
        rvLinks.push(linkElement);
        continue;
      }
      // catch relations specified by IANA URI
      if (relAttribute == IANA_URI + rel) {
        rvLinks.push(linkElement);
      }
    }
  }
  return rvLinks;
}

function xmlEscape(s) {
  s = s.replace(/&/g, "&amp;");
  s = s.replace(/>/g, "&gt;");
  s = s.replace(/</g, "&lt;");
  s = s.replace(/"/g, "&quot;");
  s = s.replace(/'/g, "&apos;");
  return s;
}

function arrayContains(array, element) {
  for (var i = 0; i < array.length; ++i) {
    if (array[i] == element) {
      return true;
    }
  }
  return false;
}

// XXX add hasKey to nsIPropertyBag
function bagHasKey(bag, key) {
  try {
    bag.getProperty(key);
    return true;
  } catch (e) {
    return false;
  }
}

function makePropGetter(key) {
  return function FeedPropGetter(bag) {
    try {
      return bag.getProperty(key);
    } catch (e) {
    }
    return null;
  }
}

const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
// namespace map
var gNamespaces = {
  "http://webns.net/mvcb/": "admin",
  "http://backend.userland.com/rss": "",
  "http://blogs.law.harvard.edu/tech/rss": "",
  "http://www.w3.org/2005/Atom": "atom",
  "http://purl.org/atom/ns#": "atom03",
  "http://purl.org/rss/1.0/modules/content/": "content",
  "http://purl.org/dc/elements/1.1/": "dc",
  "http://purl.org/dc/terms/": "dcterms",
  "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
  "http://purl.org/rss/1.0/": "rss1",
  "http://my.netscape.com/rdf/simple/0.9/": "rss1",
  "http://wellformedweb.org/CommentAPI/": "wfw",
  "http://purl.org/rss/1.0/modules/wiki/": "wiki",
  "http://www.w3.org/XML/1998/namespace": "xml",
  "http://search.yahoo.com/mrss/": "media",
  "http://search.yahoo.com/mrss": "media"
}

// We allow a very small set of namespaces in XHTML content,
// for attributes only
var gAllowedXHTMLNamespaces = {
  "http://www.w3.org/XML/1998/namespace": "xml",
  // if someone ns qualifies XHTML, we have to prefix it to avoid an
  // attribute collision.
  "http://www.w3.org/1999/xhtml": "xhtml"
}

function FeedResult() {}
FeedResult.prototype = {
  bozo: false,
  doc: null,
  version: null,
  headers: null,
  uri: null,
  stylesheet: null,

  registerExtensionPrefix: function FR_registerExtensionPrefix(ns, prefix) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  // XPCOM stuff
  classID: FR_CLASSID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedResult])
}

function Feed() {
  this.subtitle = null;
  this.title = null;
  this.items = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  this.link = null;
  this.id = null;
  this.generator = null;
  this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  this.baseURI = null;
  this.enclosureCount = 0;
  this.type = Ci.nsIFeed.TYPE_FEED;
}

Feed.prototype = {
  searchLists: {
    title: ["title", "rss1:title", "atom03:title", "atom:title"],
    subtitle: ["description", "dc:description", "rss1:description",
               "atom03:tagline", "atom:subtitle"],
    items: ["items", "atom03_entries", "entries"],
    id: ["atom:id", "rdf:about"],
    generator: ["generator"],
    authors: ["authors"],
    contributors: ["contributors"],
    link:  [["link", strToURI], ["rss1:link", strToURI]],
    categories: ["categories", "dc:subject"],
    rights: ["atom03:rights", "atom:rights"],
    cloud: ["cloud"],
    image: ["image", "rss1:image", "atom:logo"],
    textInput: ["textInput", "rss1:textinput"],
    skipDays: ["skipDays"],
    skipHours: ["skipHours"],
    updated: ["pubDate", "lastBuildDate", "atom03:modified", "dc:date",
              "dcterms:modified", "atom:updated"]
  },

  normalize: function Feed_normalize() {
    fieldsToObj(this, this.searchLists);
    if (this.skipDays)
      this.skipDays = this.skipDays.getProperty("days");
    if (this.skipHours)
      this.skipHours = this.skipHours.getProperty("hours");

    if (this.updated)
      this.updated = dateParse(this.updated);

    // Assign Atom link if needed
    if (bagHasKey(this.fields, "links"))
      this._atomLinksToURI();

    this._calcEnclosureCountAndFeedType();

    // Resolve relative image links
    if (this.image && bagHasKey(this.image, "url"))
      this._resolveImageLink();

    this._resetBagMembersToRawText([this.searchLists.subtitle,
                                    this.searchLists.title]);
  },

  _calcEnclosureCountAndFeedType: function Feed_calcEnclosureCountAndFeedType() {
    var entries_with_enclosures = 0;
    var audio_count = 0;
    var image_count = 0;
    var video_count = 0;
    var other_count = 0;

    for (var i = 0; i < this.items.length; ++i) {
      var entry = this.items.queryElementAt(i, Ci.nsIFeedEntry);
      entry.QueryInterface(Ci.nsIFeedContainer);

      if (entry.enclosures && entry.enclosures.length > 0) {
        ++entries_with_enclosures;

        for (var e = 0; e < entry.enclosures.length; ++e) {
          var enc = entry.enclosures.queryElementAt(e, Ci.nsIWritablePropertyBag2);
          if (enc.hasKey("type")) {
            var enctype = enc.get("type");

            if (/^audio/.test(enctype)) {
              ++audio_count;
            } else if (/^image/.test(enctype)) {
              ++image_count;
            } else if (/^video/.test(enctype)) {
              ++video_count;
            } else {
              ++other_count;
            }
          } else {
            ++other_count;
          }
        }
      }
    }

    var feedtype = Ci.nsIFeed.TYPE_FEED;

    // For a feed to be marked as TYPE_VIDEO, TYPE_AUDIO and TYPE_IMAGE,
    // we enforce two things:
    //
    //    1. all entries must have at least one enclosure
    //    2. all enclosures must be video for TYPE_VIDEO, audio for TYPE_AUDIO or image
    //       for TYPE_IMAGE
    //
    // Otherwise it's a TYPE_FEED.
    if (entries_with_enclosures == this.items.length && other_count == 0) {
      if (audio_count > 0 && !video_count && !image_count) {
        feedtype = Ci.nsIFeed.TYPE_AUDIO;

      } else if (image_count > 0 && !audio_count && !video_count) {
        feedtype = Ci.nsIFeed.TYPE_IMAGE;

      } else if (video_count > 0 && !audio_count && !image_count) {
        feedtype = Ci.nsIFeed.TYPE_VIDEO;
      }
    }

    this.type = feedtype;
    this.enclosureCount = other_count + video_count + audio_count + image_count;
  },

  _atomLinksToURI: function Feed_linkToURI() {
    var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
    var alternates = findAtomLinks("alternate", links);
    if (alternates.length > 0) {
      var href = alternates[0].getPropertyAsAString("href");
      var base;
      if (bagHasKey(alternates[0], "xml:base"))
        base = alternates[0].getPropertyAsAString("xml:base");
      this.link = this._resolveURI(href, base);
    }
  },

  _resolveImageLink: function Feed_resolveImageLink() {
    var base;
    if (bagHasKey(this.image, "xml:base"))
      base = this.image.getPropertyAsAString("xml:base");
    var url = this._resolveURI(this.image.getPropertyAsAString("url"), base);
    if (url)
      this.image.setPropertyAsAString("url", url.spec);
  },

  _resolveURI: function Feed_resolveURI(linkSpec, baseSpec) {
    var uri = null;
    try {
      var base = baseSpec ? strToURI(baseSpec, this.baseURI) : this.baseURI;
      uri = strToURI(linkSpec, base);
    } catch (e) {
      LOG(e);
    }

    return uri;
  },

  // reset the bag to raw contents, not text constructs
  _resetBagMembersToRawText: function Feed_resetBagMembers(fieldLists) {
    for (var i = 0; i < fieldLists.length; i++) {
      for (var j = 0; j < fieldLists[i].length; j++) {
        if (bagHasKey(this.fields, fieldLists[i][j])) {
          var textConstruct = this.fields.getProperty(fieldLists[i][j]);
          this.fields.setPropertyAsAString(fieldLists[i][j],
                                           textConstruct.text);
        }
      }
    }
  },

  // XPCOM stuff
  classID: FEED_CLASSID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeed, Ci.nsIFeedContainer])
}

function Entry() {
  this.summary = null;
  this.content = null;
  this.title = null;
  this.fields = Cc["@mozilla.org/hash-property-bag;1"].
    createInstance(Ci.nsIWritablePropertyBag2);
  this.link = null;
  this.id = null;
  this.baseURI = null;
  this.updated = null;
  this.published = null;
  this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
  this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
}

Entry.prototype = {
  fields: null,
  enclosures: null,
  mediaContent: null,

  searchLists: {
    title: ["title", "rss1:title", "atom03:title", "atom:title"],
    link: [["link", strToURI], ["rss1:link", strToURI]],
    id: [["guid", makePropGetter("guid")], "rdf:about",
         "atom03:id", "atom:id"],
    authors: ["authors"],
    contributors: ["contributors"],
    summary: ["description", "rss1:description", "dc:description",
              "atom03:summary", "atom:summary"],
    content: ["content:encoded", "atom03:content", "atom:content"],
    rights: ["atom03:rights", "atom:rights"],
    published: ["pubDate", "atom03:issued", "dcterms:issued", "atom:published"],
    updated: ["pubDate", "atom03:modified", "dc:date", "dcterms:modified",
              "atom:updated"]
  },

  normalize: function Entry_normalize() {
    fieldsToObj(this, this.searchLists);

    // Assign Atom link if needed
    if (bagHasKey(this.fields, "links"))
      this._atomLinksToURI();

    // Populate enclosures array
    this._populateEnclosures();

    // The link might be a guid w/ permalink=true
    if (!this.link && bagHasKey(this.fields, "guid")) {
      var guid = this.fields.getProperty("guid");
      var isPermaLink = true;

      if (bagHasKey(guid, "isPermaLink"))
        isPermaLink = guid.getProperty("isPermaLink").toLowerCase() != "false";

      if (guid && isPermaLink)
        this.link = strToURI(guid.getProperty("guid"));
    }

    if (this.updated)
      this.updated = dateParse(this.updated);
    if (this.published)
      this.published = dateParse(this.published);

    this._resetBagMembersToRawText([this.searchLists.content,
                                    this.searchLists.summary,
                                    this.searchLists.title]);
  },

  _populateEnclosures: function Entry_populateEnclosures() {
    if (bagHasKey(this.fields, "links"))
      this._atomLinksToEnclosures();

    // Add RSS2 enclosure to enclosures
    if (bagHasKey(this.fields, "enclosure"))
      this._enclosureToEnclosures();

    // Add media:content to enclosures
    if (bagHasKey(this.fields, "mediacontent"))
      this._mediaToEnclosures("mediacontent");

    // Add media:thumbnail to enclosures
    if (bagHasKey(this.fields, "mediathumbnail"))
      this._mediaToEnclosures("mediathumbnail");

    // Add media:content in media:group to enclosures
    if (bagHasKey(this.fields, "mediagroup"))
      this._mediaToEnclosures("mediagroup", "mediacontent");
  },

  __enclosure_map: null,

  _addToEnclosures: function Entry_addToEnclosures(new_enc) {
    // items we add to the enclosures array get displayed in the FeedWriter and
    // they must have non-empty urls.
    if (!bagHasKey(new_enc, "url") || new_enc.getPropertyAsAString("url") == "")
      return;

    if (this.__enclosure_map == null)
      this.__enclosure_map = {};

    var previous_enc = this.__enclosure_map[new_enc.getPropertyAsAString("url")];

    if (previous_enc != undefined) {
      previous_enc.QueryInterface(Ci.nsIWritablePropertyBag2);

      if (!bagHasKey(previous_enc, "type") && bagHasKey(new_enc, "type")) {
        previous_enc.setPropertyAsAString("type", new_enc.getPropertyAsAString("type"));
        try {
          let handlerInfoWrapper = gMimeService.getFromTypeAndExtension(new_enc.getPropertyAsAString("type"), null);
          if (handlerInfoWrapper && handlerInfoWrapper.description) {
            previous_enc.setPropertyAsAString("typeDesc", handlerInfoWrapper.description);
          }
        } catch (ext) {}
      }

      if (!bagHasKey(previous_enc, "length") && bagHasKey(new_enc, "length"))
        previous_enc.setPropertyAsAString("length", new_enc.getPropertyAsAString("length"));

      return;
    }

    if (this.enclosures == null) {
      this.enclosures = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
      this.enclosures.QueryInterface(Ci.nsIMutableArray);
    }

    this.enclosures.appendElement(new_enc);
    this.__enclosure_map[new_enc.getPropertyAsAString("url")] = new_enc;
  },

  _atomLinksToEnclosures: function Entry_linkToEnclosure() {
    var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
    var enc_links = findAtomLinks("enclosure", links);
    if (enc_links.length == 0)
      return;

    for (var i = 0; i < enc_links.length; ++i) {
      var link = enc_links[i];

      // an enclosure must have an href
      if (!(link.getProperty("href")))
        return;

      var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);

      // copy Atom bits over to equivalent enclosure bits
      enc.setPropertyAsAString("url", link.getPropertyAsAString("href"));
      if (bagHasKey(link, "type"))
        enc.setPropertyAsAString("type", link.getPropertyAsAString("type"));
      if (bagHasKey(link, "length"))
        enc.setPropertyAsAString("length", link.getPropertyAsAString("length"));

      this._addToEnclosures(enc);
    }
  },

  _enclosureToEnclosures: function Entry_enclosureToEnclosures() {
    var enc = this.fields.getPropertyAsInterface("enclosure", Ci.nsIPropertyBag2);

    if (!(enc.getProperty("url")))
      return;

    this._addToEnclosures(enc);
  },

  _mediaToEnclosures: function Entry_mediaToEnclosures(mediaType, contentType) {
    var content;

    // If a contentType is specified, the mediaType is a simple propertybag,
    // and the contentType is an array inside it.
    if (contentType) {
      var group = this.fields.getPropertyAsInterface(mediaType, Ci.nsIPropertyBag2);
      content = group.getPropertyAsInterface(contentType, Ci.nsIArray);
    } else {
      content = this.fields.getPropertyAsInterface(mediaType, Ci.nsIArray);
    }

    for (var i = 0; i < content.length; ++i) {
      var contentElement = content.queryElementAt(i, Ci.nsIWritablePropertyBag2);

      // media:content don't require url, but if it's not there, we should
      // skip it.
      if (!bagHasKey(contentElement, "url"))
        continue;

      var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);

      // copy media:content bits over to equivalent enclosure bits
      enc.setPropertyAsAString("url", contentElement.getPropertyAsAString("url"));
      if (bagHasKey(contentElement, "type")) {
        enc.setPropertyAsAString("type", contentElement.getPropertyAsAString("type"));
      } else if (mediaType == "mediathumbnail") {
        // thumbnails won't have a type, but default to image types
        enc.setPropertyAsAString("type", "image/*");
        enc.setPropertyAsBool("thumbnail", true);
      }

      if (bagHasKey(contentElement, "fileSize")) {
        enc.setPropertyAsAString("length", contentElement.getPropertyAsAString("fileSize"));
      }

      this._addToEnclosures(enc);
    }
  },

  // XPCOM stuff
  classID: ENTRY_CLASSID,
  QueryInterface: XPCOMUtils.generateQI(
    [Ci.nsIFeedEntry, Ci.nsIFeedContainer]
  )
}

Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI;
Entry.prototype._resolveURI = Feed.prototype._resolveURI;
Entry.prototype._resetBagMembersToRawText =
   Feed.prototype._resetBagMembersToRawText;

// TextConstruct represents and element that could contain (X)HTML
function TextConstruct() {
  this.lang = null;
  this.base = null;
  this.type = "text";
  this.text = null;
  this.parserUtils = Cc[PARSERUTILS_CONTRACTID].getService(Ci.nsIParserUtils);
}

TextConstruct.prototype = {
  plainText: function TC_plainText() {
    if (this.type != "text") {
      return this.parserUtils.convertToPlainText(stripTags(this.text),
        Ci.nsIDocumentEncoder.OutputSelectionOnly |
        Ci.nsIDocumentEncoder.OutputAbsoluteLinks,
        0);
    }
    return this.text;
  },

  createDocumentFragment: function TC_createDocumentFragment(element) {
    if (this.type == "text") {
      var doc = element.ownerDocument;
      var docFragment = doc.createDocumentFragment();
      var node = doc.createTextNode(this.text);
      docFragment.appendChild(node);
      return docFragment;
    }
    var isXML;
    if (this.type == "xhtml")
      isXML = true
    else if (this.type == "html")
      isXML = false;
    else
      return null;

    let flags = Ci.nsIParserUtils.SanitizerDropForms;
    return this.parserUtils.parseFragment(this.text, flags, isXML,
                                          this.base, element);
  },

  // XPCOM stuff
  classID: TEXTCONSTRUCT_CLASSID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedTextConstruct])
}

// Generator represents the software that produced the feed
function Generator() {
  this.lang = null;
  this.agent = null;
  this.version = null;
  this.uri = null;

  // nsIFeedElementBase
  this._attributes = null;
  this.baseURI = null;
}

Generator.prototype = {

  get attributes() {
    return this._attributes;
  },

  set attributes(value) {
    this._attributes = value;
    this.version = this._attributes.getValueFromName("", "version");
    var uriAttribute = this._attributes.getValueFromName("", "uri") ||
                       this._attributes.getValueFromName("", "url");
    this.uri = strToURI(uriAttribute, this.baseURI);

    // RSS1
    uriAttribute = this._attributes.getValueFromName(RDF_NS, "resource");
    if (uriAttribute) {
      this.agent = uriAttribute;
      this.uri = strToURI(uriAttribute, this.baseURI);
    }
  },

  // XPCOM stuff
  classID: GENERATOR_CLASSID,
  QueryInterface: XPCOMUtils.generateQI(
    [Ci.nsIFeedGenerator, Ci.nsIFeedElementBase]
  )
}

function Person() {
  this.name = null;
  this.uri = null;
  this.email = null;

  // nsIFeedElementBase
  this.attributes = null;
  this.baseURI = null;
}

Person.prototype = {
  // XPCOM stuff
  classID: PERSON_CLASSID,
  QueryInterface: XPCOMUtils.generateQI(
    [Ci.nsIFeedPerson, Ci.nsIFeedElementBase]
  )
}

/**
 * Map a list of fields into properties on a container.
 *
 * @param container An nsIFeedContainer
 * @param fields A list of fields to search for. List members can
 *               be a list, in which case the second member is
 *               transformation function (like parseInt).
 */
function fieldsToObj(container, fields) {
  var props, prop, field, searchList;
  for (var key in fields) {
    searchList = fields[key];
    for (var i = 0; i < searchList.length; ++i) {
      props = searchList[i];
      prop = null;
      field = isArray(props) ? props[0] : props;
      try {
        prop = container.fields.getProperty(field);
      } catch (e) {
      }
      if (prop) {
        prop = isArray(props) ? props[1](prop) : prop;
        container[key] = prop;
      }
    }
  }
}

/**
 * Lower cases an element's localName property
 * @param   element A DOM element.
 *
 * @returns The lower case localName property of the specified element
 */
function LC(element) {
  return element.localName.toLowerCase();
}

// TODO move these post-processor functions
// create a generator element
function atomGenerator(s, generator) {
  generator.QueryInterface(Ci.nsIFeedGenerator);
  generator.agent = s.trim();
  return generator;
}

// post-process atom:logo to create an RSS2-like structure
function atomLogo(s, logo) {
  logo.setPropertyAsAString("url", s.trim());
}

// post-process an RSS category, map it to the Atom fields.
function rssCatTerm(s, cat) {
  // add slash handling?
  cat.setPropertyAsAString("term", s.trim());
  return cat;
}

// post-process a GUID
function rssGuid(s, guid) {
  guid.setPropertyAsAString("guid", s.trim());
  return guid;
}

// post-process an RSS author element
//
// It can contain a field like this:
//
//  <author>lawyer@boyer.net (Lawyer Boyer)</author>
//
// or, delightfully, a field like this:
//
//  <dc:creator>Simon St.Laurent (mailto:simonstl@simonstl.com)</dc:creator>
//
// We want to split this up and assign it to corresponding Atom
// fields.
//
function rssAuthor(s, author) {
  author.QueryInterface(Ci.nsIFeedPerson);
  // check for RSS2 string format
  var chars = s.trim();
  var matches = chars.match(/(.*)\((.*)\)/);
  var emailCheck =
    /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
  if (matches) {
    var match1 = matches[1].trim();
    var match2 = matches[2].trim();
    if (match2.indexOf("mailto:") == 0)
      match2 = match2.substring(7);
    if (emailCheck.test(match1)) {
      author.email = match1;
      author.name = match2;
    } else if (emailCheck.test(match2)) {
      author.email = match2;
      author.name = match1;
    } else {
      // put it back together
      author.name = match1 + " (" + match2 + ")";
    }
  } else {
    author.name = chars;
    if (chars.indexOf("@"))
      author.email = chars;
  }
  return author;
}

//
// skipHours and skipDays map to arrays, so we need to change the
// string to an nsISupports in order to stick it in there.
//
function rssArrayElement(s) {
  var str = Cc["@mozilla.org/supports-string;1"].
              createInstance(Ci.nsISupportsString);
  str.data = s;
  str.QueryInterface(Ci.nsISupportsString);
  return str;
}

/**
 * Tries parsing a string through the JavaScript Date object.
 * @param aDateString
 *        A string that is supposedly an RFC822 or RFC3339 date.
 * @return A Date.toUTCString, or null if the string can't be parsed.
 */
function dateParse(aDateString) {
  let dateString = aDateString.trim();
  // Without bug 682781 fixed, JS won't parse an RFC822 date with a Z for the
  // timezone, so convert to -00:00 which works for any date format.
  dateString = dateString.replace(/z$/i, "-00:00");
  let date = new Date(dateString);
  if (!isNaN(date)) {
    return date.toUTCString();
  }
  return null;
}

const XHTML_NS = "http://www.w3.org/1999/xhtml";

// The XHTMLHandler handles inline XHTML found in things like atom:summary
function XHTMLHandler(processor, isAtom) {
  this._buf = "";
  this._processor = processor;
  this._depth = 0;
  this._isAtom = isAtom;
  // a stack of lists tracking in-scope namespaces
  this._inScopeNS = [];
}

// The fidelity can be improved here, to allow handling of stuff like
// SVG and MathML. XXX
XHTMLHandler.prototype = {

   // look back up at the declared namespaces
   // we always use the same prefixes for our safe stuff
  _isInScope: function XH__isInScope(ns) {
    for (var i in this._inScopeNS) {
      for (var uri in this._inScopeNS[i]) {
        if (this._inScopeNS[i][uri] == ns)
          return true;
      }
    }
    return false;
  },

  startDocument: function XH_startDocument() {
  },
  endDocument: function XH_endDocument() {
  },
  startElement: function XH_startElement(namespace, localName, qName, attributes) {
    ++this._depth;
    this._inScopeNS.push([]);

    // RFC4287 requires XHTML to be wrapped in a div that is *not* part of
    // the content. This prevents people from screwing up namespaces, but
    // we need to skip it here.
    if (this._isAtom && this._depth == 1 && localName == "div")
      return;

    // If it's an XHTML element, record it. Otherwise, it's ignored.
    if (namespace == XHTML_NS) {
      this._buf += "<" + localName;
      var uri;
      for (var i = 0; i < attributes.length; ++i) {
        uri = attributes.getURI(i);
        // XHTML attributes aren't in a namespace
        if (uri == "") {
          this._buf += (" " + attributes.getLocalName(i) + "='" +
                        xmlEscape(attributes.getValue(i)) + "'");
        } else {
          // write a small set of allowed attribute namespaces
          var prefix = gAllowedXHTMLNamespaces[uri];
          if (prefix != null) {
            // The attribute value we'll attempt to write
            var attributeValue = xmlEscape(attributes.getValue(i));

            // it's an allowed attribute NS.
            // write the attribute
            this._buf += (" " + prefix + ":" +
                          attributes.getLocalName(i) +
                          "='" + attributeValue + "'");

            // write an xmlns declaration if necessary
            if (prefix != "xml" && !this._isInScope(uri)) {
              this._inScopeNS[this._inScopeNS.length - 1].push(uri);
              this._buf += " xmlns:" + prefix + "='" + uri + "'";
            }
          }
        }
      }
      this._buf += ">";
    }
  },
  endElement: function XH_endElement(uri, localName, qName) {
    --this._depth;
    this._inScopeNS.pop();

    // We need to skip outer divs in Atom. See comment in startElement.
    if (this._isAtom && this._depth == 0 && localName == "div")
      return;

    // When we peek too far, go back to the main processor
    if (this._depth < 0) {
      this._processor.returnFromXHTMLHandler(this._buf.trim(),
                                             uri, localName, qName);
      return;
    }
    // If it's an XHTML element, record it. Otherwise, it's ignored.
    if (uri == XHTML_NS) {
      this._buf += "</" + localName + ">";
    }
  },
  characters: function XH_characters(data) {
    this._buf += xmlEscape(data);
  },
  startPrefixMapping: function XH_startPrefixMapping(prefix, uri) {
  },
  endPrefixMapping: function FP_endPrefixMapping(prefix) {
  },
  processingInstruction: function XH_processingInstruction() {
  },
}

/**
 * The ExtensionHandler deals with elements we haven't explicitly
 * added to our transition table in the FeedProcessor.
 */
function ExtensionHandler(processor) {
  this._buf = "";
  this._depth = 0;
  this._hasChildElements = false;

  // The FeedProcessor
  this._processor = processor;

  // Fields of the outermost extension element.
  this._localName = null;
  this._uri = null;
  this._qName = null;
  this._attrs = null;
}

ExtensionHandler.prototype = {
  startDocument: function EH_startDocument() {
  },
  endDocument: function EH_endDocument() {
  },
  startElement: function EH_startElement(uri, localName, qName, attrs) {
    ++this._depth;

    if (this._depth == 1) {
      this._uri = uri;
      this._localName = localName;
      this._qName = qName;
      this._attrs = attrs;
    }

    // if we descend into another element, we won't send text
    this._hasChildElements = (this._depth > 1);

  },
  endElement: function EH_endElement(uri, localName, qName) {
    --this._depth;
    if (this._depth == 0) {
      var text = this._hasChildElements ? null : this._buf.trim();
      this._processor.returnFromExtHandler(this._uri, this._localName,
                                           text, this._attrs);
    }
  },
  characters: function EH_characters(data) {
    if (!this._hasChildElements)
      this._buf += data;
  },
  startPrefixMapping: function EH_startPrefixMapping() {
  },
  endPrefixMapping: function EH_endPrefixMapping() {
  },
  processingInstruction: function EH_processingInstruction() {
  },
};


/**
 * ElementInfo is a simple container object that describes
 * some characteristics of a feed element. For example, it
 * says whether an element can be expected to appear more
 * than once inside a given entry or feed.
 */
function ElementInfo(fieldName, containerClass, closeFunc, isArray) {
  this.fieldName = fieldName;
  this.containerClass = containerClass;
  this.closeFunc = closeFunc;
  this.isArray = isArray;
  this.isWrapper = false;
}

/**
 * FeedElementInfo represents a feed element, usually the root.
 */
function FeedElementInfo(fieldName, feedVersion) {
  this.isWrapper = false;
  this.fieldName = fieldName;
  this.feedVersion = feedVersion;
}

/**
 * Some feed formats include vestigial wrapper elements that we don't
 * want to include in our object model, but we do need to keep track
 * of during parsing.
 */
function WrapperElementInfo(fieldName) {
  this.isWrapper = true;
  this.fieldName = fieldName;
}

/** *** The Processor *****/
function FeedProcessor() {
  this._reader = Cc[SAX_CONTRACTID].createInstance(Ci.nsISAXXMLReader);
  this._buf =  "";
  this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
  this._handlerStack = [];
  this._xmlBaseStack = []; // sparse array keyed to nesting depth
  this._depth = 0;
  this._state = "START";
  this._result = null;
  this._extensionHandler = null;
  this._xhtmlHandler = null;
  this._haveSentResult = false;

  // The nsIFeedResultListener waiting for the parse results
  this.listener = null;

  // These elements can contain (X)HTML or plain text.
  // We keep a table here that contains their default treatment
  this._textConstructs = {"atom:title": "text",
                          "atom:summary": "text",
                          "atom:rights": "text",
                          "atom:content": "text",
                          "atom:subtitle": "text",
                          "description": "html",
                          "rss1:description": "html",
                          "dc:description": "html",
                          "content:encoded": "html",
                          "title": "text",
                          "rss1:title": "text",
                          "atom03:title": "text",
                          "atom03:tagline": "text",
                          "atom03:summary": "text",
                          "atom03:content": "text"};
  this._stack = [];

  this._trans = {
    "START": {
      // If we hit a root RSS element, treat as RSS2.
      "rss": new FeedElementInfo("RSS2", "rss2"),

      // If we hit an RDF element, if could be RSS1, but we can't
      // verify that until we hit a rss1:channel element.
      "rdf:RDF": new WrapperElementInfo("RDF"),

      // If we hit a Atom 1.0 element, treat as Atom 1.0.
      "atom:feed": new FeedElementInfo("Atom", "atom"),

      // Treat as Atom 0.3
      "atom03:feed": new FeedElementInfo("Atom03", "atom03"),
    },

    /** ******* RSS2 **********/
    "IN_RSS2": {
      "channel": new WrapperElementInfo("channel")
    },

    "IN_CHANNEL": {
      "item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
      "managingEditor": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                        rssAuthor, true),
      "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                    rssAuthor, true),
      "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                   rssAuthor, true),
      "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
                                         rssAuthor, true),
      "category": new ElementInfo("categories", null, rssCatTerm, true),
      "cloud": new ElementInfo("cloud", null, null, false),
      "image": new ElementInfo("image", null, null, false),
      "textInput": new ElementInfo("textInput", null, null, false),
      "skipDays": new ElementInfo("skipDays", null, null, false),
      "skipHours": new ElementInfo("skipHours", null, null, false),
      "generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
                                   atomGenerator, false),
    },

    "IN_ITEMS": {
      "author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                rssAuthor, true),
      "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                    rssAuthor, true),
      "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                   rssAuthor, true),
      "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
                                         rssAuthor, true),
      "category": new ElementInfo("categories", null, rssCatTerm, true),
      "enclosure": new ElementInfo("enclosure", null, null, false),
      "media:content": new ElementInfo("mediacontent", null, null, true),
      "media:group": new ElementInfo("mediagroup", null, null, false),
      "media:thumbnail": new ElementInfo("mediathumbnail", null, null, true),
      "guid": new ElementInfo("guid", null, rssGuid, false)
    },

    "IN_SKIPDAYS": {
      "day": new ElementInfo("days", null, rssArrayElement, true)
    },

    "IN_SKIPHOURS": {
      "hour": new ElementInfo("hours", null, rssArrayElement, true)
    },

    "IN_MEDIAGROUP": {
      "media:content": new ElementInfo("mediacontent", null, null, true),
      "media:thumbnail": new ElementInfo("mediathumbnail", null, null, true)
    },

    /** ******* RSS1 **********/
    "IN_RDF": {
      // If we hit a rss1:channel, we can verify that we have RSS1
      "rss1:channel": new FeedElementInfo("rdf_channel", "rss1"),
      "rss1:image": new ElementInfo("image", null, null, false),
      "rss1:textinput": new ElementInfo("textInput", null, null, false),
      "rss1:item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
    },

    "IN_RDF_CHANNEL": {
      "admin:generatorAgent": new ElementInfo("generator",
                                              Cc[GENERATOR_CONTRACTID],
                                              null, false),
      "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                    rssAuthor, true),
      "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                   rssAuthor, true),
      "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
                                         rssAuthor, true),
    },

    /** ******* ATOM 1.0 **********/
    "IN_ATOM": {
      "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                     null, true),
      "atom:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
                                        atomGenerator, false),
      "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
                                          null, true),
      "atom:link": new ElementInfo("links", null, null, true),
      "atom:logo": new ElementInfo("atom:logo", null, atomLogo, false),
      "atom:entry": new ElementInfo("entries", Cc[ENTRY_CONTRACTID],
                                    null, true)
    },

    "IN_ENTRIES": {
      "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                     null, true),
      "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
                                          null, true),
      "atom:link": new ElementInfo("links", null, null, true),
    },

    /** ******* ATOM 0.3 **********/
    "IN_ATOM03": {
      "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                       null, true),
      "atom03:contributor": new ElementInfo("contributors",
                                            Cc[PERSON_CONTRACTID],
                                            null, true),
      "atom03:link": new ElementInfo("links", null, null, true),
      "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
                                      null, true),
      "atom03:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
                                          atomGenerator, false),
    },

    "IN_ATOM03_ENTRIES": {
      "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
                                       null, true),
      "atom03:contributor": new ElementInfo("contributors",
                                            Cc[PERSON_CONTRACTID],
                                            null, true),
      "atom03:link": new ElementInfo("links", null, null, true),
      "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
                                      null, true)
    }
  }
}

// See startElement for a long description of how feeds are processed.
FeedProcessor.prototype = {

  // Set ourselves as the SAX handler, and set the base URI
  _init: function FP_init(uri) {
    this._reader.contentHandler = this;
    this._reader.errorHandler = this;
    this._result = Cc[FR_CONTRACTID].createInstance(Ci.nsIFeedResult);
    if (uri) {
      this._result.uri = uri;
      this._reader.baseURI = uri;
      this._xmlBaseStack[0] = uri;
    }
  },

  // This function is called once we figure out what type of feed
  // we're dealing with. Some feed types require digging a bit further
  // than the root.
  _docVerified: function FP_docVerified(version) {
    this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed);
    this._result.doc.baseURI =
      this._xmlBaseStack[this._xmlBaseStack.length - 1];
    this._result.doc.fields = this._feed;
    this._result.version = version;
  },

  // When we're done with the feed, let the listener know what
  // happened.
  _sendResult: function FP_sendResult() {
    this._haveSentResult = true;
    try {
      // Can be null when a non-feed is fed to us
      if (this._result.doc)
        this._result.doc.normalize();
    } catch (e) {
      LOG("FIXME: " + e);
    }

    try {
      if (this.listener != null)
        this.listener.handleResult(this._result);
    } finally {
      this._result = null;
    }
  },

  // Parsing functions
  parseFromStream: function FP_parseFromStream(stream, uri) {
    this._init(uri);
    this._reader.parseFromStream(stream, null, stream.available(),
                                 "application/xml");
    this._reader = null;
  },

  parseFromString: function FP_parseFromString(inputString, uri) {
    this._init(uri);
    this._reader.parseFromString(inputString, "application/xml");
    this._reader = null;
  },

  parseAsync: function FP_parseAsync(requestObserver, uri) {
    this._init(uri);
    this._reader.parseAsync(requestObserver);
  },

  // nsIStreamListener

  // The XMLReader will throw sensible exceptions if these get called
  // out of order.
  onStartRequest: function FP_onStartRequest(request, context) {
    // this will throw if the request is not a channel, but so will nsParser.
    var channel = request.QueryInterface(Ci.nsIChannel);
    channel.contentType = "application/vnd.mozilla.maybe.feed";
    this._reader.onStartRequest(request, context);
  },

  onStopRequest: function FP_onStopRequest(request, context, statusCode) {
    try {
      this._reader.onStopRequest(request, context, statusCode);
    } finally {
      this._reader = null;
    }
  },

  onDataAvailable:
  function FP_onDataAvailable(request, context, inputStream, offset, count) {
    this._reader.onDataAvailable(request, context, inputStream, offset, count);
  },

  // nsISAXErrorHandler

  // We only care about fatal errors. When this happens, we may have
  // parsed through the feed metadata and some number of entries. The
  // listener can still show some of that data if it wants, and we'll
  // set the bozo bit to indicate we were unable to parse all the way
  // through.
  fatalError: function FP_reportError() {
    this._result.bozo = true;
    // XXX need to QI to FeedProgressListener
    if (!this._haveSentResult)
      this._sendResult();
  },

  // nsISAXContentHandler

  startDocument: function FP_startDocument() {
    // LOG("----------");
  },

  endDocument: function FP_endDocument() {
    if (!this._haveSentResult)
      this._sendResult();
  },

  // The transitions defined above identify elements that contain more
  // than just text. For example RSS items contain many fields, and so
  // do Atom authors. The only commonly used elements that contain
  // mixed content are Atom Text Constructs of type="xhtml", which we
  // delegate to another handler for cleaning. That leaves a couple
  // different types of elements to deal with: those that should occur
  // only once, such as title elements, and those that can occur
  // multiple times, such as the RSS category element and the Atom
  // link element. Most of the RSS1/DC elements can occur multiple
  // times in theory, but in practice, the only ones that do have
  // analogues in Atom.
  //
  // Some elements are also groups of attributes or sub-elements,
  // while others are simple text fields. For the most part, we don't
  // have to pay explicit attention to the simple text elements,
  // unless we want to post-process the resulting string to transform
  // it into some richer object like a Date or URI.
  //
  // Elements that have more sophisticated content models still end up
  // being dictionaries, whether they are based on attributes like RSS
  // cloud, sub-elements like Atom author, or even items and
  // entries. These elements are treated as "containers". It's
  // theoretically possible for a container to have an attribute with
  // the same universal name as a sub-element, but none of the feed
  // formats allow this by default, and I don't of any extension that
  // works this way.
  //
  startElement: function FP_startElement(uri, localName, qName, attributes) {
    this._buf = "";
    ++this._depth;
    var elementInfo;

    // LOG("<" + localName + ">");

    // Check for xml:base
    var base = attributes.getValueFromName(XMLNS, "base");
    if (base) {
      this._xmlBaseStack[this._depth] =
        strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]);
    }

    // To identify the element we're dealing with, we look up the
    // namespace URI in our gNamespaces dictionary, which will give us
    // a "canonical" prefix for a namespace URI. For example, this
    // allows Dublin Core "creator" elements to be consistently mapped
    // to "dc:creator", for easy field access by consumer code. This
    // strategy also happens to shorten up our state table.
    var key =  this._prefixForNS(uri) + localName;

    // Check to see if we need to hand this off to our XHTML handler.
    // The elements we're dealing with will look like this:
    //
    // <title type="xhtml">
    //   <div xmlns="http://www.w3.org/1999/xhtml">
    //     A title with <b>bold</b> and <i>italics</i>.
    //   </div>
    // </title>
    //
    // When it returns in returnFromXHTMLHandler, the handler should
    // give us back a string like this:
    //
    //    "A title with <b>bold</b> and <i>italics</i>."
    //
    // The Atom spec explicitly says the div is not part of the content,
    // and explicitly allows whitespace collapsing.
    //
    if ((this._result.version == "atom" || this._result.version == "atom03") &&
        this._textConstructs[key] != null) {
      var type = attributes.getValueFromName("", "type");
      if (type != null && type.indexOf("xhtml") >= 0) {
        this._xhtmlHandler =
          new XHTMLHandler(this, (this._result.version == "atom"));
        this._reader.contentHandler = this._xhtmlHandler;
        return;
      }
    }

    // Check our current state, and see if that state has a defined
    // transition. For example, this._trans["atom:entry"]["atom:author"]
    // will have one, and it tells us to add an item to our authors array.
    if (this._trans[this._state] && this._trans[this._state][key]) {
      elementInfo = this._trans[this._state][key];
    } else {
      // If we don't have a transition, hand off to extension handler
      this._extensionHandler = new ExtensionHandler(this);
      this._reader.contentHandler = this._extensionHandler;
      this._extensionHandler.startElement(uri, localName, qName, attributes);
      return;
    }

    // This distinguishes wrappers like 'channel' from elements
    // we'd actually like to do something with (which will test true).
    this._handlerStack[this._depth] = elementInfo;
    if (elementInfo.isWrapper) {
      this._state = "IN_" + elementInfo.fieldName.toUpperCase();
      this._stack.push([this._feed, this._state]);
    } else if (elementInfo.feedVersion) {
      this._state = "IN_" + elementInfo.fieldName.toUpperCase();

      // Check for the older RSS2 variants
      if (elementInfo.feedVersion == "rss2")
        elementInfo.feedVersion = this._findRSSVersion(attributes);
      else if (uri == RSS090NS)
        elementInfo.feedVersion = "rss090";

      this._docVerified(elementInfo.feedVersion);
      this._stack.push([this._feed, this._state]);
      this._mapAttributes(this._feed, attributes);
    } else {
      this._state = this._processComplexElement(elementInfo, attributes);
    }
  },

  // In the endElement handler, we decrement the stack and look
  // for cleanup/transition functions to execute. The second part
  // of the state transition works as above in startElement, but
  // the state we're looking for is prefixed with an underscore
  // to distinguish endElement events from startElement events.
  endElement:  function FP_endElement(uri, localName, qName) {
    var elementInfo = this._handlerStack[this._depth];
    // LOG("</" + localName + ">");
    if (elementInfo && !elementInfo.isWrapper)
      this._closeComplexElement(elementInfo);

    // cut down xml:base context
    if (this._xmlBaseStack.length == this._depth + 1)
      this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth);

    // our new state is whatever is at the top of the stack now
    if (this._stack.length > 0)
      this._state = this._stack[this._stack.length - 1][1];
    this._handlerStack = this._handlerStack.slice(0, this._depth);
    --this._depth;
  },

  // Buffer up character data. The buffer is cleared with every
  // opening element.
  characters: function FP_characters(data) {
    this._buf += data;
  },
  // TODO: It would be nice to check new prefixes here, and if they
  // don't conflict with the ones we've defined, throw them in a
  // dictionary to check.
  startPrefixMapping: function FP_startPrefixMapping(prefix, uri) {
  },

  endPrefixMapping: function FP_endPrefixMapping(prefix) {
  },

  processingInstruction: function FP_processingInstruction(target, data) {
    if (target == "xml-stylesheet") {
      var hrefAttribute = data.match(/href=[\"\'](.*?)[\"\']/);
      if (hrefAttribute && hrefAttribute.length == 2)
        this._result.stylesheet = strToURI(hrefAttribute[1], this._result.uri);
    }
  },

  // end of nsISAXContentHandler

  // Handle our more complicated elements--those that contain
  // attributes and child elements.
  _processComplexElement:
  function FP__processComplexElement(elementInfo, attributes) {
    var obj;

    // If the container is an entry/item, it'll need to have its
    // more esoteric properties put in the 'fields' property bag.
    if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID]) {
      obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry);
      obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
      this._mapAttributes(obj.fields, attributes);
    } else if (elementInfo.containerClass) {
      obj = elementInfo.containerClass.createInstance(Ci.nsIFeedElementBase);
      obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
      obj.attributes = attributes; // just set the SAX attributes
    } else {
      obj = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
      this._mapAttributes(obj, attributes);
    }

    // We should have a container/propertyBag that's had its
    // attributes processed. Now we need to attach it to its
    // container.
    var newProp;

    // First we'll see what's on top of the stack.
    var container = this._stack[this._stack.length - 1][0];

    // Check to see if it has the property
    var prop;
    try {
      prop = container.getProperty(elementInfo.fieldName);
    } catch (e) {
    }

    if (elementInfo.isArray) {
      if (!prop) {
        container.setPropertyAsInterface(elementInfo.fieldName,
                                         Cc[ARRAY_CONTRACTID].
                                         createInstance(Ci.nsIMutableArray));
      }

      newProp = container.getProperty(elementInfo.fieldName);
      // XXX This QI should not be necessary, but XPConnect seems to fly
      // off the handle in the browser, and loses track of the interface
      // on large files. Bug 335638.
      newProp.QueryInterface(Ci.nsIMutableArray);
      newProp.appendElement(obj);

      // If new object is an nsIFeedContainer, we want to deal with
      // its member nsIPropertyBag instead.
      if (isIFeedContainer(obj))
        newProp = obj.fields;

    } else {
      // If it doesn't, set it.
      if (!prop) {
        container.setPropertyAsInterface(elementInfo.fieldName, obj);
      }
      newProp = container.getProperty(elementInfo.fieldName);
    }

    // make our new state name, and push the property onto the stack
    var newState = "IN_" + elementInfo.fieldName.toUpperCase();
    this._stack.push([newProp, newState, obj]);
    return newState;
  },

  // Sometimes we need reconcile the element content with the object
  // model for a given feed. We use helper functions to do the
  // munging, but we need to identify array types here, so the munging
  // happens only to the last element of an array.
  _closeComplexElement: function FP__closeComplexElement(elementInfo) {
    var stateTuple = this._stack.pop();
    var container = stateTuple[0];
    var containerParent = stateTuple[2];
    var element = null;
    var isArray = isIArray(container);

    // If it's an array and we have to post-process,
    // grab the last element
    if (isArray)
      element = container.queryElementAt(container.length - 1, Ci.nsISupports);
    else
      element = container;

    // Run the post-processing function if there is one.
    if (elementInfo.closeFunc)
      element = elementInfo.closeFunc(this._buf, element);

    // If an nsIFeedContainer was on top of the stack,
    // we need to normalize it
    if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID])
      containerParent.normalize();

    // If it's an array, re-set the last element
    if (isArray)
      container.replaceElementAt(element, container.length - 1, false);
  },

  _prefixForNS: function FP_prefixForNS(uri) {
    if (!uri)
      return "";
    var prefix = gNamespaces[uri];
    if (prefix)
      return prefix + ":";
    if (uri.toLowerCase().indexOf("http://backend.userland.com") == 0)
      return "";
    return null;
  },

  _mapAttributes: function FP__mapAttributes(bag, attributes) {
    // Cycle through the attributes, and set our properties using the
    // prefix:localNames we find in our namespace dictionary.
    for (var i = 0; i < attributes.length; ++i) {
      var key = this._prefixForNS(attributes.getURI(i)) + attributes.getLocalName(i);
      var val = attributes.getValue(i);
      bag.setPropertyAsAString(key, val);
    }
  },

  // Only for RSS2esque formats
  _findRSSVersion: function FP__findRSSVersion(attributes) {
    var versionAttr = attributes.getValueFromName("", "version").trim();
    var versions = { "0.91": "rss091",
                     "0.92": "rss092",
                     "0.93": "rss093",
                     "0.94": "rss094" }
    if (versions[versionAttr])
      return versions[versionAttr];
    if (versionAttr.substr(0, 2) != "2.")
      return "rssUnknown";
    return "rss2";
  },

  // unknown element values are returned here. See startElement above
  // for how this works.
  returnFromExtHandler:
  function FP_returnExt(uri, localName, chars, attributes) {
    --this._depth;

    // take control of the SAX events
    this._reader.contentHandler = this;
    if (localName == null && chars == null)
      return;

    // we don't take random elements inside rdf:RDF
    if (this._state == "IN_RDF")
      return;

    // Grab the top of the stack
    var top = this._stack[this._stack.length - 1];
    if (!top)
      return;

    var container = top[0];
    // Grab the last element if it's an array
    if (isIArray(container)) {
      var contract = this._handlerStack[this._depth].containerClass;
      // check if it's something specific, but not an entry
      if (contract && contract != Cc[ENTRY_CONTRACTID]) {
        var el = container.queryElementAt(container.length - 1,
                                          Ci.nsIFeedElementBase);
        // XXX there must be a way to flatten these interfaces
        if (contract == Cc[PERSON_CONTRACTID])
          el.QueryInterface(Ci.nsIFeedPerson);
        else
          return; // don't know about this interface

        let propName = localName;
        var prefix = gNamespaces[uri];

        // synonyms
        if ((uri == "" ||
             prefix &&
             ((prefix.indexOf("atom") > -1) ||
              (prefix.indexOf("rss") > -1))) &&
            (propName == "url" || propName == "href"))
          propName = "uri";

        try {
          if (el[propName] !== "undefined") {
            var propValue = chars;
            // convert URI-bearing values to an nsIURI
            if (propName == "uri") {
              var base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
              propValue = strToURI(chars, base);
            }
            el[propName] = propValue;
          }
        } catch (e) {
          // ignore XPConnect errors
        }
        // the rest of the function deals with entry- and feed-level stuff
        return;
      }
      container = container.queryElementAt(container.length - 1,
                                           Ci.nsIWritablePropertyBag2);
    }

    // Make the buffer our new property
    var propName = this._prefixForNS(uri) + localName;

    // But, it could be something containing HTML. If so,
    // we need to know about that.
    if (this._textConstructs[propName] != null &&
        this._handlerStack[this._depth].containerClass !== null) {
      var newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
                    createInstance(Ci.nsIFeedTextConstruct);
      newProp.text = chars;
      // Look up the default type in our table
      var type = this._textConstructs[propName];
      var typeAttribute = attributes.getValueFromName("", "type");
      if (this._result.version == "atom" && typeAttribute != null) {
        type = typeAttribute;
      } else if (this._result.version == "atom03" && typeAttribute != null) {
        if (typeAttribute.toLowerCase().indexOf("xhtml") >= 0) {
          type = "xhtml";
        } else if (typeAttribute.toLowerCase().indexOf("html") >= 0) {
          type = "html";
        } else if (typeAttribute.toLowerCase().indexOf("text") >= 0) {
          type = "text";
        }
      }

      // If it's rss feed-level description, it's not supposed to have html
      if (this._result.version.indexOf("rss") >= 0 &&
          this._handlerStack[this._depth].containerClass != ENTRY_CONTRACTID) {
        type = "text";
      }
      newProp.type = type;
      newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
      container.setPropertyAsInterface(propName, newProp);
    } else {
      container.setPropertyAsAString(propName, chars);
    }
  },

  // Sometimes, we'll hand off SAX handling duties to an XHTMLHandler
  // (see above) that will scrape out non-XHTML stuff, normalize
  // namespaces, and remove the wrapper div from Atom 1.0. When the
  // XHTMLHandler is done, it'll callback here.
  returnFromXHTMLHandler:
  function FP_returnFromXHTMLHandler(chars, uri, localName, qName) {
    // retake control of the SAX content events
    this._reader.contentHandler = this;

    // Grab the top of the stack
    var top = this._stack[this._stack.length - 1];
    if (!top)
      return;
    var container = top[0];

    // Assign the property
    var newProp =  newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
                   createInstance(Ci.nsIFeedTextConstruct);
    newProp.text = chars;
    newProp.type = "xhtml";
    newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
    container.setPropertyAsInterface(this._prefixForNS(uri) + localName,
                                     newProp);

    // XHTML will cause us to peek too far. The XHTML handler will
    // send us an end element to call. RFC4287-valid feeds allow a
    // more graceful way to handle this. Unfortunately, we can't count
    // on compliance at this point.
    this.endElement(uri, localName, qName);
  },

  // XPCOM stuff
  classID: FP_CLASSID,
  QueryInterface: XPCOMUtils.generateQI(
    [Ci.nsIFeedProcessor, Ci.nsISAXContentHandler, Ci.nsISAXErrorHandler,
     Ci.nsIStreamListener, Ci.nsIRequestObserver]
  )
}

var components = [FeedProcessor, FeedResult, Feed, Entry,
                  TextConstruct, Generator, Person];

this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<t]%components/UAOverridesBootstrapper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/UserAgentOverrides.jsm");

function UAOverridesBootstrapper() {
  this.init();
}

UAOverridesBootstrapper.prototype = {
  init: function uaob_init() {
    Services.obs.addObserver(this, "profile-change-net-teardown", false);
    UserAgentOverrides.init();
  },

  observe: function uaob_observe(aSubject, aTopic, aData) {
    if (aTopic == "profile-change-net-teardown") {
      Services.obs.removeObserver(this, "profile-change-net-teardown");
      UserAgentOverrides.uninit();
    }
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  classID: Components.ID("{965b0ca8-155b-11e7-93ae-92361f002671}")
};

const components = [UAOverridesBootstrapper];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<m)components/WellKnownOpportunisticUtils.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';

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID = "@mozilla.org/network/well-known-opportunistic-utils;1";
const WELLKNOWNOPPORTUNISTICUTILS_CID = Components.ID("{b4f96c89-5238-450c-8bda-e12c26f1d150}");

function WellKnownOpportunisticUtils() {
  this.valid = false;
  this.mixed = false;
  this.lifetime = 0;
}

WellKnownOpportunisticUtils.prototype = {
  classID: WELLKNOWNOPPORTUNISTICUTILS_CID,
  contractID: WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID,
  classDescription: "Well-Known Opportunistic Utils",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWellKnownOpportunisticUtils]),

    verify: function(aJSON, aOrigin, aAlternatePort) {
	try {
	  let obj = JSON.parse(aJSON.toLowerCase());
	  let ports = obj[aOrigin.toLowerCase()]['tls-ports'];
	  if (ports.indexOf(aAlternatePort) == -1) {
	    throw "invalid port";
	  }
	  this.lifetime = obj[aOrigin.toLowerCase()]['lifetime'];
          this.mixed = obj[aOrigin.toLowerCase()]['mixed-scheme'];
	} catch (e) {
	  return;
	}
      this.valid = true;
    },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WellKnownOpportunisticUtils]);
PK
!<ss#components/nsDNSServiceDiscovery.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/AppConstants.jsm");
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

if (AppConstants.platform == "android" && !Services.prefs.getBoolPref("network.mdns.use_js_fallback")) {
  Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm");
} else {
  Cu.import("resource://gre/modules/MulticastDNS.jsm");
}

const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}");
const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";

function log(aMsg) {
  dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n");
}

function generateUuid() {
  var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].
    getService(Ci.nsIUUIDGenerator);
  return uuidGenerator.generateUUID().toString();
}

// Helper class to transform return objects to correct type.
function ListenerWrapper(aListener, aMdns) {
  this.listener = aListener;
  this.mdns = aMdns;

  this.discoveryStarting = false;
  this.stopDiscovery = false;

  this.registrationStarting = false;
  this.stopRegistration = false;

  this.uuid = generateUuid();
}

ListenerWrapper.prototype = {
  // Helper function for transforming an Object into nsIDNSServiceInfo.
  makeServiceInfo: function (aServiceInfo) {
    let serviceInfo = Cc[DNSSERVICEINFO_CONTRACT_ID].createInstance(Ci.nsIDNSServiceInfo);

    for (let name of ['host', 'address', 'port', 'serviceName', 'serviceType']) {
      try {
        serviceInfo[name] = aServiceInfo[name];
      } catch (e) {
        // ignore exceptions
      }
    }

    let attributes;
    try {
      attributes = _toPropertyBag2(aServiceInfo.attributes);
    } catch (err) {
        // Ignore unset attributes in object.
        log("Caught unset attributes error: " + err + " - " + err.stack);
        attributes = Cc['@mozilla.org/hash-property-bag;1']
                        .createInstance(Ci.nsIWritablePropertyBag2);
    }
    serviceInfo.attributes = attributes;

    return serviceInfo;
  },

  /* transparent types */
  onDiscoveryStarted: function(aServiceType) {
    this.discoveryStarting = false;
    this.listener.onDiscoveryStarted(aServiceType);

    if (this.stopDiscovery) {
      this.mdns.stopDiscovery(aServiceType, this);
    }
  },
  onDiscoveryStopped: function(aServiceType) {
    this.listener.onDiscoveryStopped(aServiceType);
  },
  onStartDiscoveryFailed: function(aServiceType, aErrorCode) {
    log('onStartDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
    this.discoveryStarting = false;
    this.stopDiscovery = true;
    this.listener.onStartDiscoveryFailed(aServiceType, aErrorCode);
  },
  onStopDiscoveryFailed: function(aServiceType, aErrorCode) {
    log('onStopDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
    this.listener.onStopDiscoveryFailed(aServiceType, aErrorCode);
  },

  /* transform types */
  onServiceFound: function(aServiceInfo) {
    this.listener.onServiceFound(this.makeServiceInfo(aServiceInfo));
  },
  onServiceLost: function(aServiceInfo) {
    this.listener.onServiceLost(this.makeServiceInfo(aServiceInfo));
  },
  onServiceRegistered: function(aServiceInfo) {
    this.registrationStarting = false;
    this.listener.onServiceRegistered(this.makeServiceInfo(aServiceInfo));

    if (this.stopRegistration) {
      this.mdns.unregisterService(aServiceInfo, this);
    }
  },
  onServiceUnregistered: function(aServiceInfo) {
    this.listener.onServiceUnregistered(this.makeServiceInfo(aServiceInfo));
  },
  onServiceResolved: function(aServiceInfo) {
    this.listener.onServiceResolved(this.makeServiceInfo(aServiceInfo));
  },

  onRegistrationFailed: function(aServiceInfo, aErrorCode) {
    log('onRegistrationFailed: (' + aErrorCode + ')');
    this.registrationStarting = false;
    this.stopRegistration = true;
    this.listener.onRegistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
  },
  onUnregistrationFailed: function(aServiceInfo, aErrorCode) {
    log('onUnregistrationFailed: (' + aErrorCode + ')');
    this.listener.onUnregistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
  },
  onResolveFailed: function(aServiceInfo, aErrorCode) {
    log('onResolveFailed: (' + aErrorCode + ')');
    this.listener.onResolveFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
  }
};

function nsDNSServiceDiscovery() {
  log("nsDNSServiceDiscovery");
  this.mdns = new MulticastDNS();
}

nsDNSServiceDiscovery.prototype = {
  classID: DNSSERVICEDISCOVERY_CID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIDNSServiceDiscovery]),

  startDiscovery: function(aServiceType, aListener) {
    log("startDiscovery");
    let listener = new ListenerWrapper(aListener, this.mdns);
    listener.discoveryStarting = true;
    this.mdns.startDiscovery(aServiceType, listener);

    return {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
      cancel: (function() {
        if (this.discoveryStarting || this.stopDiscovery) {
          this.stopDiscovery = true;
          return;
        }
        this.mdns.stopDiscovery(aServiceType, listener);
      }).bind(listener)
    };
  },

  registerService: function(aServiceInfo, aListener) {
    log("registerService");
    let listener = new ListenerWrapper(aListener, this.mdns);
    listener.registrationStarting = true;
    this.mdns.registerService(aServiceInfo, listener);

    return {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
      cancel: (function() {
        if (this.registrationStarting || this.stopRegistration) {
          this.stopRegistration = true;
          return;
        }
        this.mdns.unregisterService(aServiceInfo, listener);
      }).bind(listener)
    };
  },

  resolveService: function(aServiceInfo, aListener) {
    log("resolveService");
    this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener));
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDNSServiceDiscovery]);

function _toPropertyBag2(obj)
{
  if (obj.QueryInterface) {
    return obj.QueryInterface(Ci.nsIPropertyBag2);
  }

  let result = Cc['@mozilla.org/hash-property-bag;1']
                  .createInstance(Ci.nsIWritablePropertyBag2);
  for (let name in obj) {
    result.setPropertyAsAString(name, obj[name]);
  }
  return result;
}
PK
!<Ϣ..components/DownloadLegacy.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component implements the XPCOM interfaces required for integration with
 * the legacy download components.
 *
 * New code is expected to use the "Downloads.jsm" module directly, without
 * going through the interfaces implemented in this XPCOM component.  These
 * interfaces are only maintained for backwards compatibility with components
 * that still work synchronously on the main thread.
 */

"use strict";

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, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");

/**
 * nsITransfer implementation that provides a bridge to a Download object.
 *
 * Legacy downloads work differently than the JavaScript implementation.  In the
 * latter, the caller only provides the properties for the Download object and
 * the entire process is handled by the "start" method.  In the legacy
 * implementation, the caller must create a separate object to execute the
 * download, and then make the download visible to the user by hooking it up to
 * an nsITransfer instance.
 *
 * Since nsITransfer instances may be created before the download system is
 * initialized, and initialization as well as other operations are asynchronous,
 * this implementation is able to delay all progress and status notifications it
 * receives until the associated Download object is finally created.
 *
 * Conversely, the DownloadLegacySaver object can also receive execution and
 * cancellation requests asynchronously, before or after it is connected to
 * this nsITransfer instance.  For that reason, those requests are communicated
 * in a potentially deferred way, using promise objects.
 *
 * The component that executes the download implements nsICancelable to receive
 * cancellation requests, but after cancellation it cannot be reused again.
 *
 * Since the components that execute the download may be different and they
 * don't always give consistent results, this bridge takes care of enforcing the
 * expectations, for example by ensuring the target file exists when the
 * download is successful, even if the source has a size of zero bytes.
 */
function DownloadLegacyTransfer() {
  this._deferDownload = PromiseUtils.defer();
}

DownloadLegacyTransfer.prototype = {
  classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),


  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsIWebProgressListener2,
                                         Ci.nsITransfer]),

  // nsIWebProgressListener
  onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags,
                                            aStatus) {
    if (!Components.isSuccessCode(aStatus)) {
      this._componentFailed = true;
    }

    if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) &&
        (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {

      let blockedByParentalControls = false;
      // If it is a failed download, aRequest.responseStatus doesn't exist.
      // (missing file on the server, network failure to download)
      try {
        // If the request's response has been blocked by Windows Parental Controls
        // with an HTTP 450 error code, we must cancel the request synchronously.
        blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel &&
                                      aRequest.responseStatus == 450;
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
          aRequest.cancel(Cr.NS_BINDING_ABORTED);
        }
      }

      if (blockedByParentalControls) {
        aRequest.cancel(Cr.NS_BINDING_ABORTED);
      }

      // The main request has just started.  Wait for the associated Download
      // object to be available before notifying.
      this._deferDownload.promise.then(download => {
        // If the request was blocked, now that we have the download object we
        // should set a flag that can be retrieved later when handling the
        // cancellation so that the proper error can be thrown.
        if (blockedByParentalControls) {
          download._blockedByParentalControls = true;
        }

        download.saver.onTransferStarted(
                         aRequest,
                         this._cancelable instanceof Ci.nsIHelperAppLauncher);

        // To handle asynchronous cancellation properly, we should hook up the
        // handler only after we have been notified that the main request
        // started.  We will wait until the main request stopped before
        // notifying that the download has been canceled.  Since the request has
        // not completed yet, deferCanceled is guaranteed to be set.
        return download.saver.deferCanceled.promise.then(() => {
          // Only cancel if the object executing the download is still running.
          if (this._cancelable && !this._componentFailed) {
            this._cancelable.cancel(Cr.NS_ERROR_ABORT);
            if (this._cancelable instanceof Ci.nsIWebBrowserPersist) {
              // This component will not send the STATE_STOP notification.
              download.saver.onTransferFinished(Cr.NS_ERROR_ABORT);
              this._cancelable = null;
            }
          }
        });
      }).catch(Cu.reportError);
    } else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
        (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
      // The last file has been received, or the download failed.  Wait for the
      // associated Download object to be available before notifying.
      this._deferDownload.promise.then(download => {
        // At this point, the hash has been set and we need to copy it to the
        // DownloadSaver.
        if (Components.isSuccessCode(aStatus)) {
          download.saver.setSha256Hash(this._sha256Hash);
          download.saver.setSignatureInfo(this._signatureInfo);
          download.saver.setRedirects(this._redirects);
        }
        download.saver.onTransferFinished(aStatus);
      }).catch(Cu.reportError);

      // Release the reference to the component executing the download.
      this._cancelable = null;
    }
  },

  // nsIWebProgressListener
  onProgressChange: function DLT_onProgressChange(aWebProgress, aRequest,
                                                  aCurSelfProgress,
                                                  aMaxSelfProgress,
                                                  aCurTotalProgress,
                                                  aMaxTotalProgress) {
    this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
                            aMaxSelfProgress, aCurTotalProgress,
                            aMaxTotalProgress);
  },

  onLocationChange() { },

  // nsIWebProgressListener
  onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus,
                                              aMessage) {
    // The status change may optionally be received in addition to the state
    // change, but if no network request actually started, it is possible that
    // we only receive a status change with an error status code.
    if (!Components.isSuccessCode(aStatus)) {
      this._componentFailed = true;

      // Wait for the associated Download object to be available.
      this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
        aDownload.saver.onTransferFinished(aStatus);
      }).catch(Cu.reportError);
    }
  },

  onSecurityChange() { },

  // nsIWebProgressListener2
  onProgressChange64: function DLT_onProgressChange64(aWebProgress, aRequest,
                                                      aCurSelfProgress,
                                                      aMaxSelfProgress,
                                                      aCurTotalProgress,
                                                      aMaxTotalProgress) {
    // Wait for the associated Download object to be available.
    this._deferDownload.promise.then(function DLT_OPC64_onDownload(aDownload) {
      aDownload.saver.onProgressBytes(aCurTotalProgress, aMaxTotalProgress);
    }).catch(Cu.reportError);
  },

  // nsIWebProgressListener2
  onRefreshAttempted: function DLT_onRefreshAttempted(aWebProgress, aRefreshURI,
                                                      aMillis, aSameURI) {
    // Indicate that refreshes and redirects are allowed by default.  However,
    // note that download components don't usually call this method at all.
    return true;
  },

  // nsITransfer
  init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
                          aTempFile, aCancelable, aIsPrivate) {
    this._cancelable = aCancelable;

    let launchWhenSucceeded = false, contentType = null, launcherPath = null;

    if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
      launchWhenSucceeded =
                aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk;
      contentType = aMIMEInfo.type;

      let appHandler = aMIMEInfo.preferredApplicationHandler;
      if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp &&
          appHandler instanceof Ci.nsILocalHandlerApp) {
        launcherPath = appHandler.executable.path;
      }
    }

    // Create a new Download object associated to a DownloadLegacySaver, and
    // wait for it to be available.  This operation may cause the entire
    // download system to initialize before the object is created.
    Downloads.createDownload({
      source: { url: aSource.spec, isPrivate: aIsPrivate },
      target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path,
                partFilePath: aTempFile && aTempFile.path },
      saver: "legacy",
      launchWhenSucceeded,
      contentType,
      launcherPath
    }).then(aDownload => {
      // Legacy components keep partial data when they use a ".part" file.
      if (aTempFile) {
        aDownload.tryToKeepPartialData = true;
      }

      // Start the download before allowing it to be controlled.  Ignore errors.
      aDownload.start().catch(() => {});

      // Start processing all the other events received through nsITransfer.
      this._deferDownload.resolve(aDownload);

      // Add the download to the list, allowing it to be seen and canceled.
      return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload));
    }).catch(Cu.reportError);
  },

  setSha256Hash(hash) {
    this._sha256Hash = hash;
  },

  setSignatureInfo(signatureInfo) {
    this._signatureInfo = signatureInfo;
  },

  setRedirects(redirects) {
    this._redirects = redirects;
  },

  /**
   * This deferred object contains a promise that is resolved with the Download
   * object associated with this nsITransfer instance, when it is available.
   */
  _deferDownload: null,

  /**
   * Reference to the component that is executing the download.  This component
   * allows cancellation through its nsICancelable interface.
   */
  _cancelable: null,

  /**
   * Indicates that the component that executes the download has notified a
   * failure condition.  In this case, we should never use the component methods
   * that cancel the download.
   */
  _componentFailed: false,

  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file.
   */
  _sha256Hash: null,

  /**
   * Save the signature info in a serialized protobuf of the downloaded file.
   */
  _signatureInfo: null,
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]);
PK
!<;components/nsCrashMonitor.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

var Scope = {}
Components.utils.import("resource://gre/modules/CrashMonitor.jsm", Scope);
var MonitorAPI = Scope.CrashMonitor;

function CrashMonitor() {}

CrashMonitor.prototype = {

  classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"),
  contractID: "@mozilla.org/toolkit/crashmonitor;1",

  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),

  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
    case "profile-after-change":
      MonitorAPI.init();
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashMonitor]);
PK
!<\}}components/nsSearchService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/debug.js");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
  "resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SearchStaticData",
  "resource://gre/modules/SearchStaticData.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Lz4",
  "resource://gre/modules/lz4.js");

XPCOMUtils.defineLazyServiceGetter(this, "gTextToSubURI",
                                   "@mozilla.org/intl/texttosuburi;1",
                                   "nsITextToSubURI");
XPCOMUtils.defineLazyServiceGetter(this, "gEnvironment",
                                   "@mozilla.org/process/environment;1",
                                   "nsIEnvironment");
XPCOMUtils.defineLazyServiceGetter(this, "gChromeReg",
                                   "@mozilla.org/chrome/chrome-registry;1",
                                   "nsIChromeRegistry");

Cu.importGlobalProperties(["XMLHttpRequest"]);

// A text encoder to UTF8, used whenever we commit the cache to disk.
XPCOMUtils.defineLazyGetter(this, "gEncoder",
                            function() {
                              return new TextEncoder();
                            });

const MODE_RDONLY   = 0x01;
const MODE_WRONLY   = 0x02;
const MODE_CREATE   = 0x08;
const MODE_APPEND   = 0x10;
const MODE_TRUNCATE = 0x20;
const PERMS_FILE    = 0o644;

// Directory service keys
const NS_APP_SEARCH_DIR_LIST  = "SrchPluginsDL";
const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
const NS_APP_SEARCH_DIR       = "SrchPlugns";
const NS_APP_USER_PROFILE_50_DIR = "ProfD";

// Loading plugins from NS_APP_SEARCH_DIR is no longer supported.
// Instead, we now load plugins from APP_SEARCH_PREFIX, where a
// list.txt file needs to exist to list available engines.
const APP_SEARCH_PREFIX = "resource://search-plugins/";

// See documentation in nsIBrowserSearchService.idl.
const SEARCH_ENGINE_TOPIC        = "browser-search-engine-modified";
const QUIT_APPLICATION_TOPIC     = "quit-application";

const SEARCH_ENGINE_REMOVED      = "engine-removed";
const SEARCH_ENGINE_ADDED        = "engine-added";
const SEARCH_ENGINE_CHANGED      = "engine-changed";
const SEARCH_ENGINE_LOADED       = "engine-loaded";
const SEARCH_ENGINE_CURRENT      = "engine-current";
const SEARCH_ENGINE_DEFAULT      = "engine-default";

// The following constants are left undocumented in nsIBrowserSearchService.idl
// For the moment, they are meant for testing/debugging purposes only.

/**
 * Topic used for events involving the service itself.
 */
const SEARCH_SERVICE_TOPIC       = "browser-search-service";

/**
 * Sent whenever the cache is fully written to disk.
 */
const SEARCH_SERVICE_CACHE_WRITTEN  = "write-cache-to-disk-complete";

// Delay for lazy serialization (ms)
const LAZY_SERIALIZE_DELAY = 100;

// Delay for batching invalidation of the JSON cache (ms)
const CACHE_INVALIDATION_DELAY = 1000;

// Current cache version. This should be incremented if the format of the cache
// file is modified.
const CACHE_VERSION = 1;

const CACHE_FILENAME = "search.json.mozlz4";

const NEW_LINES = /(\r\n|\r|\n)/;

// Set an arbitrary cap on the maximum icon size. Without this, large icons can
// cause big delays when loading them at startup.
const MAX_ICON_SIZE   = 10000;

// Default charset to use for sending search parameters. ISO-8859-1 is used to
// match previous nsInternetSearchService behavior as a URL parameter. Label
// resolution causes windows-1252 to be actually used.
const DEFAULT_QUERY_CHARSET = "ISO-8859-1";

const SEARCH_BUNDLE = "chrome://global/locale/search/search.properties";
const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";

const OPENSEARCH_NS_10  = "http://a9.com/-/spec/opensearch/1.0/";
const OPENSEARCH_NS_11  = "http://a9.com/-/spec/opensearch/1.1/";

// Although the specification at http://opensearch.a9.com/spec/1.1/description/
// gives the namespace names defined above, many existing OpenSearch engines
// are using the following versions.  We therefore allow either.
const OPENSEARCH_NAMESPACES = [
  OPENSEARCH_NS_11, OPENSEARCH_NS_10,
  "http://a9.com/-/spec/opensearchdescription/1.1/",
  "http://a9.com/-/spec/opensearchdescription/1.0/"
];

const OPENSEARCH_LOCALNAME = "OpenSearchDescription";

const MOZSEARCH_NS_10     = "http://www.mozilla.org/2006/browser/search/";
const MOZSEARCH_LOCALNAME = "SearchPlugin";

const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
const URLTYPE_SEARCH_HTML  = "text/html";
const URLTYPE_OPENSEARCH   = "application/opensearchdescription+xml";

const BROWSER_SEARCH_PREF = "browser.search.";
const LOCALE_PREF = "general.useragent.locale";

const USER_DEFINED = "searchTerms";

// Custom search parameters
const MOZ_PARAM_LOCALE         = "moz:locale";
const MOZ_PARAM_DIST_ID        = "moz:distributionID"
const MOZ_PARAM_OFFICIAL       = "moz:official";

// Supported OpenSearch parameters
// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
const OS_PARAM_USER_DEFINED    = "searchTerms";
const OS_PARAM_INPUT_ENCODING  = "inputEncoding";
const OS_PARAM_LANGUAGE        = "language";
const OS_PARAM_OUTPUT_ENCODING = "outputEncoding";

// Default values
const OS_PARAM_LANGUAGE_DEF         = "*";
const OS_PARAM_OUTPUT_ENCODING_DEF  = "UTF-8";
const OS_PARAM_INPUT_ENCODING_DEF   = "UTF-8";

// "Unsupported" OpenSearch parameters. For example, we don't support
// page-based results, so if the engine requires that we send the "page index"
// parameter, we'll always send "1".
const OS_PARAM_COUNT        = "count";
const OS_PARAM_START_INDEX  = "startIndex";
const OS_PARAM_START_PAGE   = "startPage";

// Default values
const OS_PARAM_COUNT_DEF        = "20"; // 20 results
const OS_PARAM_START_INDEX_DEF  = "1";  // start at 1st result
const OS_PARAM_START_PAGE_DEF   = "1";  // 1st page

// A array of arrays containing parameters that we don't fully support, and
// their default values. We will only send values for these parameters if
// required, since our values are just really arbitrary "guesses" that should
// give us the output we want.
var OS_UNSUPPORTED_PARAMS = [
  [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
  [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
  [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
];

// The default engine update interval, in days. This is only used if an engine
// specifies an updateURL, but not an updateInterval.
const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;

// The default interval before checking again for the name of the
// default engine for the region, in seconds. Only used if the response
// from the server doesn't specify an interval.
const SEARCH_GEO_DEFAULT_UPDATE_INTERVAL = 2592000; // 30 days.

/**
 * Prefixed to all search debug output.
 */
const SEARCH_LOG_PREFIX = "*** Search: ";

/**
 * Outputs aText to the JavaScript console as well as to stdout.
 */
function DO_LOG(aText) {
  dump(SEARCH_LOG_PREFIX + aText + "\n");
  Services.console.logStringMessage(aText);
}

/**
 * In debug builds, use a live, pref-based (browser.search.log) LOG function
 * to allow enabling/disabling without a restart. Otherwise, don't log at all by
 * default. This can be overridden at startup by the pref, see SearchService's
 * _init method.
 */
var LOG = function() {};

if (AppConstants.DEBUG) {
  LOG = function(aText) {
    if (getBoolPref(BROWSER_SEARCH_PREF + "log", false)) {
      DO_LOG(aText);
    }
  };
}

/**
 * Presents an assertion dialog in non-release builds and throws.
 * @param  message
 *         A message to display
 * @param  resultCode
 *         The NS_ERROR_* value to throw.
 * @throws resultCode
 */
function ERROR(message, resultCode) {
  NS_ASSERT(false, SEARCH_LOG_PREFIX + message);
  throw Components.Exception(message, resultCode);
}

/**
 * Logs the failure message (if browser.search.log is enabled) and throws.
 * @param  message
 *         A message to display
 * @param  resultCode
 *         The NS_ERROR_* value to throw.
 * @throws resultCode or NS_ERROR_INVALID_ARG if resultCode isn't specified.
 */
function FAIL(message, resultCode) {
  LOG(message);
  throw Components.Exception(message, resultCode || Cr.NS_ERROR_INVALID_ARG);
}

/**
 * Truncates big blobs of (data-)URIs to console-friendly sizes
 * @param str
 *        String to tone down
 * @param len
 *        Maximum length of the string to return. Defaults to the length of a tweet.
 */
function limitURILength(str, len) {
  len = len || 140;
  if (str.length > len)
    return str.slice(0, len) + "...";
  return str;
}

/**
 * Ensures an assertion is met before continuing. Should be used to indicate
 * fatal errors.
 * @param  assertion
 *         An assertion that must be met
 * @param  message
 *         A message to display if the assertion is not met
 * @param  resultCode
 *         The NS_ERROR_* value to throw if the assertion is not met
 * @throws resultCode
 */
function ENSURE_WARN(assertion, message, resultCode) {
  NS_ASSERT(assertion, SEARCH_LOG_PREFIX + message);
  if (!assertion)
    throw Components.Exception(message, resultCode);
}

function loadListener(aChannel, aEngine, aCallback) {
  this._channel = aChannel;
  this._bytes = [];
  this._engine = aEngine;
  this._callback = aCallback;
}
loadListener.prototype = {
  _callback: null,
  _channel: null,
  _countRead: 0,
  _engine: null,
  _stream: null,

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIRequestObserver,
    Ci.nsIStreamListener,
    Ci.nsIChannelEventSink,
    Ci.nsIInterfaceRequestor,
    // See FIXME comment below.
    Ci.nsIHttpEventSink,
    Ci.nsIProgressEventSink
  ]),

  // nsIRequestObserver
  onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
    LOG("loadListener: Starting request: " + aRequest.name);
    this._stream = Cc["@mozilla.org/binaryinputstream;1"].
                   createInstance(Ci.nsIBinaryInputStream);
  },

  onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
    LOG("loadListener: Stopping request: " + aRequest.name);

    var requestFailed = !Components.isSuccessCode(aStatusCode);
    if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
      requestFailed = !aRequest.requestSucceeded;

    if (requestFailed || this._countRead == 0) {
      LOG("loadListener: request failed!");
      // send null so the callback can deal with the failure
      this._callback(null, this._engine);
    } else
      this._callback(this._bytes, this._engine);
    this._channel = null;
    this._engine  = null;
  },

  // nsIStreamListener
  onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
                                                aInputStream, aOffset,
                                                aCount) {
    this._stream.setInputStream(aInputStream);

    // Get a byte array of the data
    this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
    this._countRead += aCount;
  },

  // nsIChannelEventSink
  asyncOnChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
                                                      aFlags, callback) {
    this._channel = aNewChannel;
    callback.onRedirectVerifyCallback(Components.results.NS_OK);
  },

  // nsIInterfaceRequestor
  getInterface: function SRCH_load_GI(aIID) {
    return this.QueryInterface(aIID);
  },

  // FIXME: bug 253127
  // nsIHttpEventSink
  onRedirect(aChannel, aNewChannel) {},
  // nsIProgressEventSink
  onProgress(aRequest, aContext, aProgress, aProgressMax) {},
  onStatus(aRequest, aContext, aStatus, aStatusArg) {}
}

function isPartnerBuild() {
  try {
    let distroID = Services.prefs.getCharPref("distribution.id");

    // Mozilla-provided builds (i.e. funnelcake) are not partner builds
    if (distroID && !distroID.startsWith("mozilla")) {
      return true;
    }
  } catch (e) {}

  return false;
}

// Method to determine if we should be using geo-specific defaults
function geoSpecificDefaultsEnabled() {
  return Services.prefs.getBoolPref("browser.search.geoSpecificDefaults", false);
}

// Some notes on countryCode and region prefs:
// * A "countryCode" pref is set via a geoip lookup.  It always reflects the
//   result of that geoip request.
// * A "region" pref, once set, is the region actually used for search.  In
//   most cases it will be identical to the countryCode pref.
// * The value of "region" and "countryCode" will only not agree in one edge
//   case - 34/35 users who have previously been configured to use US defaults
//   based purely on a timezone check will have "region" forced to US,
//   regardless of what countryCode geoip returns.
// * We may want to know if we are in the US before we have *either*
//   countryCode or region - in which case we fallback to a timezone check,
//   but we don't persist that value anywhere in the expectation we will
//   eventually get a countryCode/region.

// A method that "migrates" prefs if necessary.
function migrateRegionPrefs() {
  // If we already have a "region" pref there's nothing to do.
  if (Services.prefs.prefHasUserValue("browser.search.region")) {
    return;
  }

  // If we have 'isUS' but no 'countryCode' then we are almost certainly
  // a profile from Fx 34/35 that set 'isUS' based purely on a timezone
  // check. If this said they were US, we force region to be US.
  // (But if isUS was false, we leave region alone - we will do a geoip request
  // and set the region accordingly)
  try {
    if (Services.prefs.getBoolPref("browser.search.isUS") &&
        !Services.prefs.prefHasUserValue("browser.search.countryCode")) {
      Services.prefs.setCharPref("browser.search.region", "US");
    }
  } catch (ex) {
    // no isUS pref, nothing to do.
  }
  // If we have a countryCode pref but no region pref, just force region
  // to be the countryCode.
  try {
    let countryCode = Services.prefs.getCharPref("browser.search.countryCode");
    if (!Services.prefs.prefHasUserValue("browser.search.region")) {
      Services.prefs.setCharPref("browser.search.region", countryCode);
    }
  } catch (ex) {
    // no countryCode pref, nothing to do.
  }
}

// A method to determine if we are in the United States (US) for the search
// service.
// It uses a browser.search.region pref (which typically comes from a geoip
// request) or if that doesn't exist, falls back to a hacky timezone check.
function getIsUS() {
  // Regardless of the region or countryCode, non en-US builds are not
  // considered to be in the US from the POV of the search service.
  if (getLocale() != "en-US") {
    return false;
  }

  // If we've got a region pref, trust it.
  try {
    return Services.prefs.getCharPref("browser.search.region") == "US";
  } catch (e) {}

  // So we are en-US but have no region pref - fallback to hacky timezone check.
  let isNA = isUSTimezone();
  LOG("getIsUS() fell back to a timezone check with the result=" + isNA);
  return isNA;
}

// Helper method to modify preference keys with geo-specific modifiers, if needed.
function getGeoSpecificPrefName(basepref) {
  if (!geoSpecificDefaultsEnabled() || isPartnerBuild())
    return basepref;
  if (getIsUS())
    return basepref + ".US";
  return basepref;
}

// A method that tries to determine if this user is in a US geography.
function isUSTimezone() {
  // Timezone assumptions! We assume that if the system clock's timezone is
  // between Newfoundland and Hawaii, that the user is in North America.

  // This includes all of South America as well, but we have relatively few
  // en-US users there, so that's OK.

  // 150 minutes = 2.5 hours (UTC-2.5), which is
  // Newfoundland Daylight Time (http://www.timeanddate.com/time/zones/ndt)

  // 600 minutes = 10 hours (UTC-10), which is
  // Hawaii-Aleutian Standard Time (http://www.timeanddate.com/time/zones/hast)

  let UTCOffset = (new Date()).getTimezoneOffset();
  return UTCOffset >= 150 && UTCOffset <= 600;
}

// A less hacky method that tries to determine our country-code via an XHR
// geoip lookup.
// If this succeeds and we are using an en-US locale, we set the pref used by
// the hacky method above, so isUS() can avoid the hacky timezone method.
// If it fails we don't touch that pref so isUS() does its normal thing.
var ensureKnownCountryCode = async function(ss) {
  // If we have a country-code already stored in our prefs we trust it.
  let countryCode = Services.prefs.getCharPref("browser.search.countryCode", "");

  if (!countryCode) {
    // We don't have it cached, so fetch it. fetchCountryCode() will call
    // storeCountryCode if it gets a result (even if that happens after the
    // promise resolves) and fetchRegionDefault.
    await fetchCountryCode(ss);
  } else {
    // if nothing to do, return early.
    if (!geoSpecificDefaultsEnabled())
      return;

    let expir = ss.getGlobalAttr("searchDefaultExpir") || 0;
    if (expir > Date.now()) {
      // The territory default we have already fetched hasn't expired yet.
      // If we have a default engine or a list of visible default engines
      // saved, the hashes should be valid, verify them now so that we can
      // refetch if they have been tampered with.
      let defaultEngine = ss.getVerifiedGlobalAttr("searchDefault");
      let visibleDefaultEngines = ss.getVerifiedGlobalAttr("visibleDefaultEngines");
      if ((defaultEngine || defaultEngine === undefined) &&
          (visibleDefaultEngines || visibleDefaultEngines === undefined)) {
        // No geo defaults, or valid hashes; nothing to do.
        return;
      }
    }

    await new Promise(resolve => {
      let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
      let timerId = setTimeout(() => {
        timerId = null;
        resolve();
      }, timeoutMS);

      let callback = () => {
        clearTimeout(timerId);
        resolve();
      };
      fetchRegionDefault(ss).then(callback).catch(err => {
        Components.utils.reportError(err);
        callback();
      });
    });
  }

  // If gInitialized is true then the search service was forced to perform
  // a sync initialization during our XHRs - capture this via telemetry.
  Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
};

// Store the result of the geoip request as well as any other values and
// telemetry which depend on it.
function storeCountryCode(cc) {
  // Set the country-code itself.
  Services.prefs.setCharPref("browser.search.countryCode", cc);
  // And set the region pref if we don't already have a value.
  if (!Services.prefs.prefHasUserValue("browser.search.region")) {
    Services.prefs.setCharPref("browser.search.region", cc);
  }
  // and telemetry...
  let isTimezoneUS = isUSTimezone();
  if (cc == "US" && !isTimezoneUS) {
    Services.telemetry.getHistogramById("SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE").add(1);
  }
  if (cc != "US" && isTimezoneUS) {
    Services.telemetry.getHistogramById("SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY").add(1);
  }
  // telemetry to compare our geoip response with platform-specific country data.
  // On Mac and Windows, we can get a country code via sysinfo
  let platformCC = Services.sysinfo.get("countryCode");
  if (platformCC) {
    let probeUSMismatched, probeNonUSMismatched;
    switch (Services.appinfo.OS) {
      case "Darwin":
        probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX";
        probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX";
        break;
      case "WINNT":
        probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_WIN";
        probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_WIN";
        break;
      default:
        Cu.reportError("Platform " + Services.appinfo.OS + " has system country code but no search service telemetry probes");
        break;
    }
    if (probeUSMismatched && probeNonUSMismatched) {
      if (cc == "US" || platformCC == "US") {
        // one of the 2 said US, so record if they are the same.
        Services.telemetry.getHistogramById(probeUSMismatched).add(cc != platformCC);
      } else {
        // different country - record if they are the same
        Services.telemetry.getHistogramById(probeNonUSMismatched).add(cc != platformCC);
      }
    }
  }
}

// Get the country we are in via a XHR geoip request.
function fetchCountryCode(ss) {
  // values for the SEARCH_SERVICE_COUNTRY_FETCH_RESULT 'enum' telemetry probe.
  const TELEMETRY_RESULT_ENUM = {
    SUCCESS: 0,
    SUCCESS_WITHOUT_DATA: 1,
    XHRTIMEOUT: 2,
    ERROR: 3,
    // Note that we expect to add finer-grained error types here later (eg,
    // dns error, network error, ssl error, etc) with .ERROR remaining as the
    // generic catch-all that doesn't fit into other categories.
  };
  let endpoint = Services.urlFormatter.formatURLPref("browser.search.geoip.url");
  LOG("_fetchCountryCode starting with endpoint " + endpoint);
  // As an escape hatch, no endpoint means no geoip.
  if (!endpoint) {
    return Promise.resolve();
  }
  let startTime = Date.now();
  return new Promise(resolve => {
    // Instead of using a timeout on the xhr object itself, we simulate one
    // using a timer and let the XHR request complete.  This allows us to
    // capture reliable telemetry on what timeout value should actually be
    // used to ensure most users don't see one while not making it so large
    // that many users end up doing a sync init of the search service and thus
    // would see the jank that implies.
    // (Note we do actually use a timeout on the XHR, but that's set to be a
    // large value just incase the request never completes - we don't want the
    // XHR object to live forever)
    let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
    let geoipTimeoutPossible = true;
    let timerId = setTimeout(() => {
      LOG("_fetchCountryCode: timeout fetching country information");
      if (geoipTimeoutPossible)
        Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
      timerId = null;
      resolve();
    }, timeoutMS);

    let resolveAndReportSuccess = (result, reason) => {
      // Even if we timed out, we want to save the country code and everything
      // related so next startup sees the value and doesn't retry this dance.
      if (result) {
        storeCountryCode(result);
      }
      Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT").add(reason);

      // This notification is just for tests...
      Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-complete");

      if (timerId) {
        Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
        geoipTimeoutPossible = false;
      }

      let callback = () => {
        // If we've already timed out then we've already resolved the promise,
        // so there's nothing else to do.
        if (timerId == null) {
          return;
        }
        clearTimeout(timerId);
        resolve();
      };

      if (result && geoSpecificDefaultsEnabled()) {
        fetchRegionDefault(ss).then(callback).catch(err => {
          Components.utils.reportError(err);
          callback();
        });
      } else {
        callback();
      }
    };

    let request = new XMLHttpRequest();
    // This notification is just for tests...
    Services.obs.notifyObservers(request, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-starting");
    request.timeout = 100000; // 100 seconds as the last-chance fallback
    request.onload = function(event) {
      let took = Date.now() - startTime;
      let cc = event.target.response && event.target.response.country_code;
      LOG("_fetchCountryCode got success response in " + took + "ms: " + cc);
      Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS").add(took);
      let reason = cc ? TELEMETRY_RESULT_ENUM.SUCCESS : TELEMETRY_RESULT_ENUM.SUCCESS_WITHOUT_DATA;
      resolveAndReportSuccess(cc, reason);
    };
    request.ontimeout = function(event) {
      LOG("_fetchCountryCode: XHR finally timed-out fetching country information");
      resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.XHRTIMEOUT);
    };
    request.onerror = function(event) {
      LOG("_fetchCountryCode: failed to retrieve country information");
      resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.ERROR);
    };
    request.open("POST", endpoint, true);
    request.setRequestHeader("Content-Type", "application/json");
    request.responseType = "json";
    request.send("{}");
  });
}

// This will make an HTTP request to a Mozilla server that will return
// JSON data telling us what engine should be set as the default for
// the current region, and how soon we should check again.
//
// The optional cohort value returned by the server is to be kept locally
// and sent to the server the next time we ping it. It lets the server
// identify profiles that have been part of a specific experiment.
//
// This promise may take up to 100s to resolve, it's the caller's
// responsibility to ensure with a timer that we are not going to
// block the async init for too long.
var fetchRegionDefault = (ss) => new Promise(resolve => {
  let urlTemplate = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
                            .getCharPref("geoSpecificDefaults.url");
  let endpoint = Services.urlFormatter.formatURL(urlTemplate);

  // As an escape hatch, no endpoint means no region specific defaults.
  if (!endpoint) {
    resolve();
    return;
  }

  // Append the optional cohort value.
  const cohortPref = "browser.search.cohort";
  let cohort = Services.prefs.getCharPref(cohortPref, "");
  if (cohort)
    endpoint += "/" + cohort;

  LOG("fetchRegionDefault starting with endpoint " + endpoint);

  let startTime = Date.now();
  let request = new XMLHttpRequest();
  request.timeout = 100000; // 100 seconds as the last-chance fallback
  request.onload = function(event) {
    let took = Date.now() - startTime;

    let status = event.target.status;
    if (status != 200) {
      LOG("fetchRegionDefault failed with HTTP code " + status);
      let retryAfter = request.getResponseHeader("retry-after");
      if (retryAfter) {
        ss.setGlobalAttr("searchDefaultExpir", Date.now() + retryAfter * 1000);
      }
      resolve();
      return;
    }

    let response = event.target.response || {};
    LOG("received " + response.toSource());

    if (response.cohort) {
      Services.prefs.setCharPref(cohortPref, response.cohort);
    } else {
      Services.prefs.clearUserPref(cohortPref);
    }

    if (response.settings && response.settings.searchDefault) {
      let defaultEngine = response.settings.searchDefault;
      ss.setVerifiedGlobalAttr("searchDefault", defaultEngine);
      LOG("fetchRegionDefault saved searchDefault: " + defaultEngine);
    }

    if (response.settings && response.settings.visibleDefaultEngines) {
      let visibleDefaultEngines = response.settings.visibleDefaultEngines;
      let string = visibleDefaultEngines.join(",");
      ss.setVerifiedGlobalAttr("visibleDefaultEngines", string);
      LOG("fetchRegionDefault saved visibleDefaultEngines: " + string);
    }

    let interval = response.interval || SEARCH_GEO_DEFAULT_UPDATE_INTERVAL;
    let milliseconds = interval * 1000; // |interval| is in seconds.
    ss.setGlobalAttr("searchDefaultExpir", Date.now() + milliseconds);

    LOG("fetchRegionDefault got success response in " + took + "ms");
    resolve();
  };
  request.ontimeout = function(event) {
    LOG("fetchRegionDefault: XHR finally timed-out");
    resolve();
  };
  request.onerror = function(event) {
    LOG("fetchRegionDefault: failed to retrieve territory default information");
    resolve();
  };
  request.open("GET", endpoint, true);
  request.setRequestHeader("Content-Type", "application/json");
  request.responseType = "json";
  request.send();
});

function getVerificationHash(aName) {
  let disclaimer = "By modifying this file, I agree that I am doing so " +
    "only within $appName itself, using official, user-driven search " +
    "engine selection processes, and in a way which does not circumvent " +
    "user consent. I acknowledge that any attempt to change this file " +
    "from outside of $appName is a malicious act, and will be responded " +
    "to accordingly."

  let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
             disclaimer.replace(/\$appName/g, Services.appinfo.name);

  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";

  // Data is an array of bytes.
  let data = converter.convertToByteArray(salt, {});
  let hasher = Cc["@mozilla.org/security/hash;1"]
                 .createInstance(Ci.nsICryptoHash);
  hasher.init(hasher.SHA256);
  hasher.update(data, data.length);

  return hasher.finish(true);
}

/**
 * Safely close a nsISafeOutputStream.
 * @param aFOS
 *        The file output stream to close.
 */
function closeSafeOutputStream(aFOS) {
  if (aFOS instanceof Ci.nsISafeOutputStream) {
    try {
      aFOS.finish();
      return;
    } catch (e) { }
  }
  aFOS.close();
}

/**
 * Wrapper function for nsIIOService::newURI.
 * @param aURLSpec
 *        The URL string from which to create an nsIURI.
 * @returns an nsIURI object, or null if the creation of the URI failed.
 */
function makeURI(aURLSpec, aCharset) {
  try {
    return Services.io.newURI(aURLSpec, aCharset);
  } catch (ex) { }

  return null;
}

/**
 * Wrapper function for nsIIOService::newChannel2.
 * @param url
 *        The URL string from which to create an nsIChannel.
 * @returns an nsIChannel object, or null if the url is invalid.
 */
function makeChannel(url) {
  try {
    let uri = typeof url == "string" ? Services.io.newURI(url) : url;
    return Services.io.newChannelFromURI2(uri,
                                          null, /* loadingNode */
                                          Services.scriptSecurityManager.getSystemPrincipal(),
                                          null, /* triggeringPrincipal */
                                          Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                                          Ci.nsIContentPolicy.TYPE_OTHER);
  } catch (ex) { }

  return null;
}

/**
 * Gets a directory from the directory service.
 * @param aKey
 *        The directory service key indicating the directory to get.
 */
function getDir(aKey, aIFace) {
  if (!aKey)
    FAIL("getDir requires a directory key!");

  return Services.dirsvc.get(aKey, aIFace || Ci.nsIFile);
}

/**
 * Gets the current value of the locale.  It's possible for this preference to
 * be localized, so we have to do a little extra work here.  Similar code
 * exists in nsHttpHandler.cpp when building the UA string.
 */
function getLocale() {
  return Services.locale.getRequestedLocale();
}

/**
 * Wrapper for nsIPrefBranch::getComplexValue.
 * @param aPrefName
 *        The name of the pref to get.
 * @returns aDefault if the requested pref doesn't exist.
 */
function getLocalizedPref(aPrefName, aDefault) {
  const nsIPLS = Ci.nsIPrefLocalizedString;
  try {
    return Services.prefs.getComplexValue(aPrefName, nsIPLS).data;
  } catch (ex) {}

  return aDefault;
}

/**
 * Wrapper for nsIPrefBranch::getBoolPref.
 * @param aPrefName
 *        The name of the pref to get.
 * @returns aDefault if the requested pref doesn't exist.
 */
function getBoolPref(aName, aDefault) {
  if (Services.prefs.getPrefType(aName) != Ci.nsIPrefBranch.PREF_BOOL)
    return aDefault;
  return Services.prefs.getBoolPref(aName);
}

/**
 * @return a sanitized name to be used as a filename, or a random name
 *         if a sanitized name cannot be obtained (if aName contains
 *         no valid characters).
 */
function sanitizeName(aName) {
  const maxLength = 60;
  const minLength = 1;
  var name = aName.toLowerCase();
  name = name.replace(/\s+/g, "-");
  name = name.replace(/[^-a-z0-9]/g, "");

  // Use a random name if our input had no valid characters.
  if (name.length < minLength)
    name = Math.random().toString(36).replace(/^.*\./, "");

  // Force max length.
  return name.substring(0, maxLength);
}

/**
 * Retrieve a pref from the search param branch.
 *
 * @param prefName
 *        The name of the pref.
 **/
function getMozParamPref(prefName) {
  let branch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF + "param.");
  return encodeURIComponent(branch.getCharPref(prefName));
}

/**
 * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
 * the state of the search service.
 *
 * @param aEngine
 *        The nsISearchEngine object to which the change applies.
 * @param aVerb
 *        A verb describing the change.
 *
 * @see nsIBrowserSearchService.idl
 */
var gInitialized = false;
function notifyAction(aEngine, aVerb) {
  if (gInitialized) {
    LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
    Services.obs.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
  }
}

function parseJsonFromStream(aInputStream) {
  const json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
  const data = json.decodeFromStream(aInputStream, aInputStream.available());
  return data;
}

/**
 * Simple object representing a name/value pair.
 */
function QueryParameter(aName, aValue, aPurpose) {
  if (!aName || (aValue == null))
    FAIL("missing name or value for QueryParameter!");

  this.name = aName;
  this.value = aValue;
  this.purpose = aPurpose;
}

/**
 * Perform OpenSearch parameter substitution on aParamValue.
 *
 * @param aParamValue
 *        A string containing OpenSearch search parameters.
 * @param aSearchTerms
 *        The user-provided search terms. This string will inserted into
 *        aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
 *        This value must already be escaped appropriately - it is inserted
 *        as-is.
 * @param aEngine
 *        The engine which owns the string being acted on.
 *
 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
 */
function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
  const PARAM_REGEXP = /\{((?:\w+:)?\w+)(\??)\}/g;
  return aParamValue.replace(PARAM_REGEXP, function(match, name, optional) {
    // {searchTerms} is by far the most common param so handle it first.
    if (name == USER_DEFINED)
      return aSearchTerms;

    // {inputEncoding} is the second most common param.
    if (name == OS_PARAM_INPUT_ENCODING)
      return aEngine.queryCharset;

    // moz: parameters are only available for default search engines.
    if (name.startsWith("moz:") && aEngine._isDefault) {
      // {moz:locale} and {moz:distributionID} are common
      if (name == MOZ_PARAM_LOCALE)
        return getLocale();
      if (name == MOZ_PARAM_DIST_ID) {
        return Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID",
                                          Services.appinfo.distributionID || "");
      }
      // {moz:official} seems to have little use.
      if (name == MOZ_PARAM_OFFICIAL) {
        if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "official",
                                       AppConstants.MOZ_OFFICIAL_BRANDING))
          return "official";
        return "unofficial";
      }
    }

    // Handle the less common OpenSearch parameters we're confident about.
    if (name == OS_PARAM_LANGUAGE)
      return getLocale() || OS_PARAM_LANGUAGE_DEF;
    if (name == OS_PARAM_OUTPUT_ENCODING)
      return OS_PARAM_OUTPUT_ENCODING_DEF;

    // At this point, if a parameter is optional, just omit it.
    if (optional)
      return "";

    // Replace unsupported parameters that only have hardcoded default values.
    for (let param of OS_UNSUPPORTED_PARAMS) {
      if (name == param[0])
        return param[1];
    }

    // Don't replace unknown non-optional parameters.
    return match;
  });
}

/**
 * Creates an engineURL object, which holds the query URL and all parameters.
 *
 * @param aType
 *        A string containing the name of the MIME type of the search results
 *        returned by this URL.
 * @param aMethod
 *        The HTTP request method. Must be a case insensitive value of either
 *        "GET" or "POST".
 * @param aTemplate
 *        The URL to which search queries should be sent. For GET requests,
 *        must contain the string "{searchTerms}", to indicate where the user
 *        entered search terms should be inserted.
 * @param aResultDomain
 *        The root domain for this URL.  Defaults to the template's host.
 *
 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
 *
 * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
 */
function EngineURL(aType, aMethod, aTemplate, aResultDomain) {
  if (!aType || !aMethod || !aTemplate)
    FAIL("missing type, method or template for EngineURL!");

  var method = aMethod.toUpperCase();
  var type   = aType.toLowerCase();

  if (method != "GET" && method != "POST")
    FAIL("method passed to EngineURL must be \"GET\" or \"POST\"");

  this.type     = type;
  this.method   = method;
  this.params   = [];
  this.rels     = [];
  // Don't serialize expanded mozparams
  this.mozparams = {};

  var templateURI = makeURI(aTemplate);
  if (!templateURI)
    FAIL("new EngineURL: template is not a valid URI!", Cr.NS_ERROR_FAILURE);

  switch (templateURI.scheme) {
    case "http":
    case "https":
    // Disable these for now, see bug 295018
    // case "file":
    // case "resource":
      this.template = aTemplate;
      break;
    default:
      FAIL("new EngineURL: template uses invalid scheme!", Cr.NS_ERROR_FAILURE);
  }

  // If no resultDomain was specified in the engine definition file, use the
  // host from the template.
  this.resultDomain = aResultDomain || templateURI.host;
  // We never want to return a "www." prefix, so eventually strip it.
  if (this.resultDomain.startsWith("www.")) {
    this.resultDomain = this.resultDomain.substr(4);
  }
}
EngineURL.prototype = {

  addParam: function SRCH_EURL_addParam(aName, aValue, aPurpose) {
    this.params.push(new QueryParameter(aName, aValue, aPurpose));
  },

  // Note: This method requires that aObj has a unique name or the previous MozParams entry with
  // that name will be overwritten.
  _addMozParam: function SRCH_EURL__addMozParam(aObj) {
    aObj.mozparam = true;
    this.mozparams[aObj.name] = aObj;
  },

  getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine, aPurpose) {
    var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
    // Default to searchbar if the purpose is not provided
    var purpose = aPurpose || "searchbar";

    // If a particular purpose isn't defined in the plugin, fallback to 'searchbar'.
    if (!this.params.some(p => p.purpose !== undefined && p.purpose == purpose))
      purpose = "searchbar";

    // Create an application/x-www-form-urlencoded representation of our params
    // (name=value&name=value&name=value)
    var dataString = "";
    for (var i = 0; i < this.params.length; ++i) {
      var param = this.params[i];

      // If this parameter has a purpose, only add it if the purpose matches
      if (param.purpose !== undefined && param.purpose != purpose)
        continue;

      var value = ParamSubstitution(param.value, aSearchTerms, aEngine);

      dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
    }

    var postData = null;
    if (this.method == "GET") {
      // GET method requests have no post data, and append the encoded
      // query string to the url...
      if (url.indexOf("?") == -1 && dataString)
        url += "?";
      url += dataString;
    } else if (this.method == "POST") {
      // POST method requests must wrap the encoded text in a MIME
      // stream and supply that as POSTDATA.
      var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
                         createInstance(Ci.nsIStringInputStream);
      stringStream.data = dataString;

      postData = Cc["@mozilla.org/network/mime-input-stream;1"].
                 createInstance(Ci.nsIMIMEInputStream);
      postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
      postData.addContentLength = true;
      postData.setData(stringStream);
    }

    return new Submission(Services.io.newURI(url), postData);
  },

  _getTermsParameterName: function SRCH_EURL__getTermsParameterName() {
    let queryParam = this.params.find(p => p.value == "{" + USER_DEFINED + "}");
    return queryParam ? queryParam.name : "";
  },

  _hasRelation: function SRC_EURL__hasRelation(aRel) {
    return this.rels.some(e => e == aRel.toLowerCase());
  },

  _initWithJSON: function SRC_EURL__initWithJSON(aJson, aEngine) {
    if (!aJson.params)
      return;

    this.rels = aJson.rels;

    for (let i = 0; i < aJson.params.length; ++i) {
      let param = aJson.params[i];
      if (param.mozparam) {
        if (param.condition == "pref") {
          let value = getMozParamPref(param.pref);
          this.addParam(param.name, value);
        }
        this._addMozParam(param);
      } else
        this.addParam(param.name, param.value, param.purpose || undefined);
    }
  },

  /**
   * Creates a JavaScript object that represents this URL.
   * @returns An object suitable for serialization as JSON.
   **/
  toJSON: function SRCH_EURL_toJSON() {
    var json = {
      template: this.template,
      rels: this.rels,
      resultDomain: this.resultDomain
    };

    if (this.type != URLTYPE_SEARCH_HTML)
      json.type = this.type;
    if (this.method != "GET")
      json.method = this.method;

    function collapseMozParams(aParam) {
      return this.mozparams[aParam.name] || aParam;
    }
    json.params = this.params.map(collapseMozParams, this);

    return json;
  }
};

/**
 * nsISearchEngine constructor.
 * @param aLocation
 *        A nsILocalFile or nsIURI object representing the location of the
 *        search engine data file.
 * @param aIsReadOnly
 *        Boolean indicating whether the engine should be treated as read-only.
 */
function Engine(aLocation, aIsReadOnly) {
  this._readOnly = aIsReadOnly;
  this._urls = [];
  this._metaData = {};

  let file, uri;
  if (typeof aLocation == "string") {
    this._shortName = aLocation;
  } else if (aLocation instanceof Ci.nsILocalFile) {
    if (!aIsReadOnly) {
      // This is an engine that was installed in NS_APP_USER_SEARCH_DIR by a
      // previous version. We are converting the file to an engine stored only
      // in JSON, but we need to keep the reference to the profile file to
      // remove it if the user ever removes the engine.
      this._filePath = aLocation.persistentDescriptor;
    }
    file = aLocation;
  } else if (aLocation instanceof Ci.nsIURI) {
    switch (aLocation.scheme) {
      case "https":
      case "http":
      case "ftp":
      case "data":
      case "file":
      case "resource":
      case "chrome":
        uri = aLocation;
        break;
      default:
        ERROR("Invalid URI passed to the nsISearchEngine constructor",
              Cr.NS_ERROR_INVALID_ARG);
    }
  } else
    ERROR("Engine location is neither a File nor a URI object",
          Cr.NS_ERROR_INVALID_ARG);

  if (!this._shortName) {
    // If we don't have a shortName at this point, it's the first time we load
    // this engine, so let's generate the shortName, id and loadPath values.
    let shortName;
    if (file) {
      shortName = file.leafName;
    } else if (uri && uri instanceof Ci.nsIURL) {
      if (aIsReadOnly || (gEnvironment.get("XPCSHELL_TEST_PROFILE_DIR") &&
                          uri.scheme == "resource")) {
        shortName = uri.fileName;
      }
    }
    if (shortName && shortName.endsWith(".xml")) {
      this._shortName = shortName.slice(0, -4);
    }
    this._loadPath = this.getAnonymizedLoadPath(file, uri);

    if (!shortName && !aIsReadOnly) {
      // We are in the process of downloading and installing the engine.
      // We'll have the shortName and id once we are done parsing it.
     return;
    }

    // Build the id used for the legacy metadata storage, so that we
    // can do a one-time import of data from old profiles.
    if (this._isDefault ||
        (uri && uri.spec.startsWith(APP_SEARCH_PREFIX))) {
      // The second part of the check is to catch engines from language packs.
      // They aren't default engines (because they aren't app-shipped), but we
      // still need to give their id an [app] prefix for backward compat.
      this._id = "[app]/" + this._shortName + ".xml";
    } else if (!aIsReadOnly) {
      this._id = "[profile]/" + this._shortName + ".xml";
    } else {
      // If the engine is neither a default one, nor a user-installed one,
      // it must be extension-shipped, so use the full path as id.
      LOG("Setting _id to full path for engine from " + this._loadPath);
      this._id = file ? file.path : uri.spec;
    }
  }
}

Engine.prototype = {
  // Data set by the user.
  _metaData: null,
  // The data describing the engine, in the form of an XML document element.
  _data: null,
  // Whether or not the engine is readonly.
  _readOnly: true,
  // Anonymized path of where we initially loaded the engine from.
  // This will stay null for engines installed in the profile before we moved
  // to a JSON storage.
  _loadPath: null,
  // The engine's description
  _description: "",
  // Used to store the engine to replace, if we're an update to an existing
  // engine.
  _engineToUpdate: null,
  // Set to true if the engine has a preferred icon (an icon that should not be
  // overridden by a non-preferred icon).
  _hasPreferredIcon: null,
  // The engine's name.
  _name: null,
  // The name of the charset used to submit the search terms.
  _queryCharset: null,
  // The engine's raw SearchForm value (URL string pointing to a search form).
  __searchForm: null,
  get _searchForm() {
    return this.__searchForm;
  },
  set _searchForm(aValue) {
    if (/^https?:/i.test(aValue))
      this.__searchForm = aValue;
    else
      LOG("_searchForm: Invalid URL dropped for " + this._name ||
          "the current engine");
  },
  // Whether to obtain user confirmation before adding the engine. This is only
  // used when the engine is first added to the list.
  _confirm: false,
  // Whether to set this as the current engine as soon as it is loaded.  This
  // is only used when the engine is first added to the list.
  _useNow: false,
  // A function to be invoked when this engine object's addition completes (or
  // fails). Only used for installation via addEngine.
  _installCallback: null,
  // The number of days between update checks for new versions
  _updateInterval: null,
  // The url to check at for a new update
  _updateURL: null,
  // The url to check for a new icon
  _iconUpdateURL: null,
  /* The extension ID if added by an extension. */
  _extensionID: null,

  /**
   * Retrieves the data from the engine's file.
   * The document element is placed in the engine's data field.
   */
  _initFromFile: function SRCH_ENG_initFromFile(file) {
    if (!file || !file.exists())
      FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);

    var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Ci.nsIFileInputStream);

    fileInStream.init(file, MODE_RDONLY, PERMS_FILE, false);

    var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
                    createInstance(Ci.nsIDOMParser);
    var doc = domParser.parseFromStream(fileInStream, "UTF-8",
                                        file.fileSize,
                                        "text/xml");

    this._data = doc.documentElement;
    fileInStream.close();

    // Now that the data is loaded, initialize the engine object
    this._initFromData();
  },

  /**
   * Retrieves the data from the engine's file asynchronously.
   * The document element is placed in the engine's data field.
   *
   * @param file The file to load the search plugin from.
   *
   * @returns {Promise} A promise, resolved successfully if initializing from
   * data succeeds, rejected if it fails.
   */
  async _asyncInitFromFile(file) {
    if (!file || !(await OS.File.exists(file.path)))
      FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);

    let fileURI = Services.io.newFileURI(file);
    await this._retrieveSearchXMLData(fileURI.spec);

    // Now that the data is loaded, initialize the engine object
    this._initFromData();
  },

  /**
   * Retrieves the engine data from a URI. Initializes the engine, flushes to
   * disk, and notifies the search service once initialization is complete.
   *
   * @param uri The uri to load the search plugin from.
   */
  _initFromURIAndLoad: function SRCH_ENG_initFromURIAndLoad(uri) {
    ENSURE_WARN(uri instanceof Ci.nsIURI,
                "Must have URI when calling _initFromURIAndLoad!",
                Cr.NS_ERROR_UNEXPECTED);

    LOG("_initFromURIAndLoad: Downloading engine from: \"" + uri.spec + "\".");

    var chan = makeChannel(uri);

    if (this._engineToUpdate && (chan instanceof Ci.nsIHttpChannel)) {
      var lastModified = this._engineToUpdate.getAttr("updatelastmodified");
      if (lastModified)
        chan.setRequestHeader("If-Modified-Since", lastModified, false);
    }
    this._uri = uri;
    var listener = new loadListener(chan, this, this._onLoad);
    chan.notificationCallbacks = listener;
    chan.asyncOpen2(listener);
  },

  /**
   * Retrieves the engine data from a URI asynchronously and initializes it.
   *
   * @param uri The uri to load the search plugin from.
   *
   * @returns {Promise} A promise, resolved successfully if retrieveing data
   * succeeds.
   */
  async _asyncInitFromURI(uri) {
    LOG("_asyncInitFromURI: Loading engine from: \"" + uri.spec + "\".");
    await this._retrieveSearchXMLData(uri.spec);
    // Now that the data is loaded, initialize the engine object
    this._initFromData();
  },

  /**
   * Retrieves the engine data for a given URI asynchronously.
   *
   * @returns {Promise} A promise, resolved successfully if retrieveing data
   * succeeds.
   */
  _retrieveSearchXMLData: function SRCH_ENG__retrieveSearchXMLData(aURL) {
    return new Promise(resolve => {
      let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                      createInstance(Ci.nsIXMLHttpRequest);
      request.overrideMimeType("text/xml");
      request.onload = (aEvent) => {
        let responseXML = aEvent.target.responseXML;
        this._data = responseXML.documentElement;
        resolve();
      };
      request.onerror = function(aEvent) {
        resolve();
      };
      request.open("GET", aURL, true);
      request.send();

    });
  },

  _initFromURISync: function SRCH_ENG_initFromURISync(uri) {
    ENSURE_WARN(uri instanceof Ci.nsIURI,
                "Must have URI when calling _initFromURISync!",
                Cr.NS_ERROR_UNEXPECTED);

    ENSURE_WARN(uri.schemeIs("resource"), "_initFromURISync called for non-resource URI",
                Cr.NS_ERROR_FAILURE);

    LOG("_initFromURISync: Loading engine from: \"" + uri.spec + "\".");

    var chan = makeChannel(uri);

    var stream = chan.open2();
    var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                 createInstance(Ci.nsIDOMParser);
    var doc = parser.parseFromStream(stream, "UTF-8", stream.available(), "text/xml");

    this._data = doc.documentElement;

    // Now that the data is loaded, initialize the engine object
    this._initFromData();
  },

  /**
   * Attempts to find an EngineURL object in the set of EngineURLs for
   * this Engine that has the given type string.  (This corresponds to the
   * "type" attribute in the "Url" node in the OpenSearch spec.)
   * This method will return the first matching URL object found, or null
   * if no matching URL is found.
   *
   * @param aType string to match the EngineURL's type attribute
   * @param aRel [optional] only return URLs that with this rel value
   */
  _getURLOfType: function SRCH_ENG__getURLOfType(aType, aRel) {
    for (let url of this._urls) {
      if (url.type == aType && (!aRel || url._hasRelation(aRel)))
        return url;
    }

    return null;
  },

  _confirmAddEngine: function SRCH_SVC_confirmAddEngine() {
    var stringBundle = Services.strings.createBundle(SEARCH_BUNDLE);
    var titleMessage = stringBundle.GetStringFromName("addEngineConfirmTitle");

    // Display only the hostname portion of the URL.
    var dialogMessage =
        stringBundle.formatStringFromName("addEngineConfirmation",
                                          [this._name, this._uri.host], 2);
    var checkboxMessage = null;
    if (!getBoolPref(BROWSER_SEARCH_PREF + "noCurrentEngine", false))
      checkboxMessage = stringBundle.GetStringFromName("addEngineAsCurrentText");

    var addButtonLabel =
        stringBundle.GetStringFromName("addEngineAddButtonLabel");

    var ps = Services.prompt;
    var buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
                      (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1) +
                       ps.BUTTON_POS_0_DEFAULT;

    var checked = {value: false};
    // confirmEx returns the index of the button that was pressed.  Since "Add"
    // is button 0, we want to return the negation of that value.
    var confirm = !ps.confirmEx(null,
                                titleMessage,
                                dialogMessage,
                                buttonFlags,
                                addButtonLabel,
                                null, null, // button 1 & 2 names not used
                                checkboxMessage,
                                checked);

    return {confirmed: confirm, useNow: checked.value};
  },

  /**
   * Handle the successful download of an engine. Initializes the engine and
   * triggers parsing of the data. The engine is then flushed to disk. Notifies
   * the search service once initialization is complete.
   */
  _onLoad: function SRCH_ENG_onLoad(aBytes, aEngine) {
    /**
     * Handle an error during the load of an engine by notifying the engine's
     * error callback, if any.
     */
    function onError(errorCode = Ci.nsISearchInstallCallback.ERROR_UNKNOWN_FAILURE) {
      // Notify the callback of the failure
      if (aEngine._installCallback) {
        aEngine._installCallback(errorCode);
      }
    }

    function promptError(strings = {}, error = undefined) {
      onError(error);

      if (aEngine._engineToUpdate) {
        // We're in an update, so just fail quietly
        LOG("updating " + aEngine._engineToUpdate.name + " failed");
        return;
      }
      var brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
      var brandName = brandBundle.GetStringFromName("brandShortName");

      var searchBundle = Services.strings.createBundle(SEARCH_BUNDLE);
      var msgStringName = strings.error || "error_loading_engine_msg2";
      var titleStringName = strings.title || "error_loading_engine_title";
      var title = searchBundle.GetStringFromName(titleStringName);
      var text = searchBundle.formatStringFromName(msgStringName,
                                                   [brandName, aEngine._location],
                                                   2);

      Services.ww.getNewPrompter(null).alert(title, text);
    }

    if (!aBytes) {
      promptError();
      return;
    }

    var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                 createInstance(Ci.nsIDOMParser);
    var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
    aEngine._data = doc.documentElement;

    try {
      // Initialize the engine from the obtained data
      aEngine._initFromData();
    } catch (ex) {
      LOG("_onLoad: Failed to init engine!\n" + ex);
      // Report an error to the user
      if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
        promptError({ error: "error_invalid_engine_msg2",
                      title: "error_invalid_format_title"
                    });
      } else {
        promptError();
      }
      return;
    }

    if (aEngine._engineToUpdate) {
      let engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;

      // Make this new engine use the old engine's shortName, and preserve
      // metadata.
      aEngine._shortName = engineToUpdate._shortName;
      Object.keys(engineToUpdate._metaData).forEach(key => {
        aEngine.setAttr(key, engineToUpdate.getAttr(key));
      });
      aEngine._loadPath = engineToUpdate._loadPath;

      // Keep track of the last modified date, so that we can make conditional
      // requests for future updates.
      aEngine.setAttr("updatelastmodified", (new Date()).toUTCString());

      // Set the new engine's icon, if it doesn't yet have one.
      if (!aEngine._iconURI && engineToUpdate._iconURI)
        aEngine._iconURI = engineToUpdate._iconURI;
    } else {
      // Check that when adding a new engine (e.g., not updating an
      // existing one), a duplicate engine does not already exist.
      if (Services.search.getEngineByName(aEngine.name)) {
        // If we're confirming the engine load, then display a "this is a
        // duplicate engine" prompt; otherwise, fail silently.
        if (aEngine._confirm) {
          promptError({ error: "error_duplicate_engine_msg",
                        title: "error_invalid_engine_title"
                      }, Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
        } else {
          onError(Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
        }
        LOG("_onLoad: duplicate engine found, bailing");
        return;
      }

      // If requested, confirm the addition now that we have the title.
      // This property is only ever true for engines added via
      // nsIBrowserSearchService::addEngine.
      if (aEngine._confirm) {
        var confirmation = aEngine._confirmAddEngine();
        LOG("_onLoad: confirm is " + confirmation.confirmed +
            "; useNow is " + confirmation.useNow);
        if (!confirmation.confirmed) {
          onError();
          return;
        }
        aEngine._useNow = confirmation.useNow;
      }

      aEngine._shortName = sanitizeName(aEngine.name);
      aEngine._loadPath = aEngine.getAnonymizedLoadPath(null, aEngine._uri);
      aEngine.setAttr("loadPathHash", getVerificationHash(aEngine._loadPath));
    }

    // Notify the search service of the successful load. It will deal with
    // updates by checking aEngine._engineToUpdate.
    notifyAction(aEngine, SEARCH_ENGINE_LOADED);

    // Notify the callback if needed
    if (aEngine._installCallback) {
      aEngine._installCallback();
    }
  },

  /**
   * Creates a key by serializing an object that contains the icon's width
   * and height.
   *
   * @param aWidth
   *        Width of the icon.
   * @param aHeight
   *        Height of the icon.
   * @returns key string
   */
  _getIconKey: function SRCH_ENG_getIconKey(aWidth, aHeight) {
    let keyObj = {
     width: aWidth,
     height: aHeight
    };

    return JSON.stringify(keyObj);
  },

  /**
   * Add an icon to the icon map used by getIconURIBySize() and getIcons().
   *
   * @param aWidth
   *        Width of the icon.
   * @param aHeight
   *        Height of the icon.
   * @param aURISpec
   *        String with the icon's URI.
   */
  _addIconToMap: function SRCH_ENG_addIconToMap(aWidth, aHeight, aURISpec) {
    if (aWidth == 16 && aHeight == 16) {
      // The 16x16 icon is stored in _iconURL, we don't need to store it twice.
      return;
    }

    // Use an object instead of a Map() because it needs to be serializable.
    this._iconMapObj = this._iconMapObj || {};
    let key = this._getIconKey(aWidth, aHeight);
    this._iconMapObj[key] = aURISpec;
  },

  /**
   * Sets the .iconURI property of the engine. If both aWidth and aHeight are
   * provided an entry will be added to _iconMapObj that will enable accessing
   * icon's data through getIcons() and getIconURIBySize() APIs.
   *
   *  @param aIconURL
   *         A URI string pointing to the engine's icon. Must have a http[s],
   *         ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
   *         downloaded and converted to data URIs for storage in the engine
   *         XML files, if the engine is not readonly.
   *  @param aIsPreferred
   *         Whether or not this icon is to be preferred. Preferred icons can
   *         override non-preferred icons.
   *  @param aWidth (optional)
   *         Width of the icon.
   *  @param aHeight (optional)
   *         Height of the icon.
   */
  _setIcon: function SRCH_ENG_setIcon(aIconURL, aIsPreferred, aWidth, aHeight) {
    var uri = makeURI(aIconURL);

    // Ignore bad URIs
    if (!uri)
      return;

    LOG("_setIcon: Setting icon url \"" + limitURILength(uri.spec) + "\" for engine \""
        + this.name + "\".");
    // Only accept remote icons from http[s] or ftp
    switch (uri.scheme) {
      case "resource":
      case "chrome":
        // We only allow chrome and resource icon URLs for built-in search engines
        if (!this._isDefault) {
          return;
        }
        // Fall through to the data case
      case "data":
        if (!this._hasPreferredIcon || aIsPreferred) {
          this._iconURI = uri;
          notifyAction(this, SEARCH_ENGINE_CHANGED);
          this._hasPreferredIcon = aIsPreferred;
        }

        if (aWidth && aHeight) {
          this._addIconToMap(aWidth, aHeight, aIconURL)
        }
        break;
      case "http":
      case "https":
      case "ftp":
        LOG("_setIcon: Downloading icon: \"" + uri.spec +
            "\" for engine: \"" + this.name + "\"");
        var chan = makeChannel(uri);

        let iconLoadCallback = function(aByteArray, aEngine) {
          // This callback may run after we've already set a preferred icon,
          // so check again.
          if (aEngine._hasPreferredIcon && !aIsPreferred)
            return;

          if (!aByteArray || aByteArray.length > MAX_ICON_SIZE) {
            LOG("iconLoadCallback: load failed, or the icon was too large!");
            return;
          }

          let type = chan.contentType;
          if (!type.startsWith("image/"))
            type = "image/x-icon";
          let dataURL = "data:" + type + ";base64," +
            btoa(String.fromCharCode.apply(null, aByteArray));

          aEngine._iconURI = makeURI(dataURL);

          if (aWidth && aHeight) {
            aEngine._addIconToMap(aWidth, aHeight, dataURL)
          }

          notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
          aEngine._hasPreferredIcon = aIsPreferred;
        };

        // If we're currently acting as an "update engine", then the callback
        // should set the icon on the engine we're updating and not us, since
        // |this| might be gone by the time the callback runs.
        var engineToSet = this._engineToUpdate || this;

        var listener = new loadListener(chan, engineToSet, iconLoadCallback);
        chan.notificationCallbacks = listener;
        chan.asyncOpen2(listener);
        break;
    }
  },

  /**
   * Initialize this Engine object from the collected data.
   */
  _initFromData: function SRCH_ENG_initFromData() {
    ENSURE_WARN(this._data, "Can't init an engine with no data!",
                Cr.NS_ERROR_UNEXPECTED);

    // Ensure we have a supported engine type before attempting to parse it.
    let element = this._data;
    if ((element.localName == MOZSEARCH_LOCALNAME &&
         element.namespaceURI == MOZSEARCH_NS_10) ||
        (element.localName == OPENSEARCH_LOCALNAME &&
         OPENSEARCH_NAMESPACES.indexOf(element.namespaceURI) != -1)) {
      LOG("_init: Initing search plugin from " + this._location);

      this._parse();

    } else {
      Cu.reportError("Invalid search plugin due to namespace not matching.");
      FAIL(this._location + " is not a valid search plugin.", Cr.NS_ERROR_FILE_CORRUPTED);
    }
    // No need to keep a ref to our data (which in some cases can be a document
    // element) past this point
    this._data = null;
  },

  /**
   * Initialize this Engine object from a collection of metadata.
   */
  _initFromMetadata: function SRCH_ENG_initMetaData(aName, aIconURL, aAlias,
                                                    aDescription, aMethod,
                                                    aTemplate, aExtensionID) {
    ENSURE_WARN(!this._readOnly,
                "Can't call _initFromMetaData on a readonly engine!",
                Cr.NS_ERROR_FAILURE);

    this._urls.push(new EngineURL(URLTYPE_SEARCH_HTML, aMethod, aTemplate));

    this._name = aName;
    this.alias = aAlias;
    this._description = aDescription;
    this._setIcon(aIconURL, true);
    this._extensionID = aExtensionID;
  },

  /**
   * Extracts data from an OpenSearch URL element and creates an EngineURL
   * object which is then added to the engine's list of URLs.
   *
   * @throws NS_ERROR_FAILURE if a URL object could not be created.
   *
   * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
   * @see EngineURL()
   */
  _parseURL: function SRCH_ENG_parseURL(aElement) {
    var type     = aElement.getAttribute("type");
    // According to the spec, method is optional, defaulting to "GET" if not
    // specified
    var method   = aElement.getAttribute("method") || "GET";
    var template = aElement.getAttribute("template");
    var resultDomain = aElement.getAttribute("resultdomain");

    try {
      var url = new EngineURL(type, method, template, resultDomain);
    } catch (ex) {
      FAIL("_parseURL: failed to add " + template + " as a URL",
           Cr.NS_ERROR_FAILURE);
    }

    if (aElement.hasAttribute("rel"))
      url.rels = aElement.getAttribute("rel").toLowerCase().split(/\s+/);

    for (var i = 0; i < aElement.childNodes.length; ++i) {
      var param = aElement.childNodes[i];
      if (param.localName == "Param") {
        try {
          url.addParam(param.getAttribute("name"), param.getAttribute("value"));
        } catch (ex) {
          // Ignore failure
          LOG("_parseURL: Url element has an invalid param");
        }
      } else if (param.localName == "MozParam" &&
                 // We only support MozParams for default search engines
                 this._isDefault) {
        var value;
        let condition = param.getAttribute("condition");

        // MozParams must have a condition to be valid
        if (!condition) {
          let engineLoc = this._location;
          let paramName = param.getAttribute("name");
          LOG("_parseURL: MozParam (" + paramName + ") without a condition attribute found parsing engine: " + engineLoc);
          continue;
        }

        switch (condition) {
          case "purpose":
            url.addParam(param.getAttribute("name"),
                         param.getAttribute("value"),
                         param.getAttribute("purpose"));
            // _addMozParam is not needed here since it can be serialized fine without. _addMozParam
            // also requires a unique "name" which is not normally the case when @purpose is used.
            break;
          case "pref":
            try {
              value = getMozParamPref(param.getAttribute("pref"), value);
              url.addParam(param.getAttribute("name"), value);
              url._addMozParam({"pref": param.getAttribute("pref"),
                                "name": param.getAttribute("name"),
                                "condition": "pref"});
            } catch (e) { }
            break;
          default:
            let engineLoc = this._location;
            let paramName = param.getAttribute("name");
            LOG("_parseURL: MozParam (" + paramName + ") has an unknown condition: " + condition + ". Found parsing engine: " + engineLoc);
          break;
        }
      }
    }

    this._urls.push(url);
  },

  /**
   * Get the icon from an OpenSearch Image element.
   * @see http://opensearch.a9.com/spec/1.1/description/#image
   */
  _parseImage: function SRCH_ENG_parseImage(aElement) {
    LOG("_parseImage: Image textContent: \"" + limitURILength(aElement.textContent) + "\"");

    let width = parseInt(aElement.getAttribute("width"), 10);
    let height = parseInt(aElement.getAttribute("height"), 10);
    let isPrefered = width == 16 && height == 16;

    if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
      LOG("OpenSearch image element must have positive width and height.");
      return;
    }

    this._setIcon(aElement.textContent, isPrefered, width, height);
  },

  /**
   * Extract search engine information from the collected data to initialize
   * the engine object.
   */
  _parse: function SRCH_ENG_parse() {
    var doc = this._data;

    // The OpenSearch spec sets a default value for the input encoding.
    this._queryCharset = OS_PARAM_INPUT_ENCODING_DEF;

    for (var i = 0; i < doc.childNodes.length; ++i) {
      var child = doc.childNodes[i];
      switch (child.localName) {
        case "ShortName":
          this._name = child.textContent;
          break;
        case "Description":
          this._description = child.textContent;
          break;
        case "Url":
          try {
            this._parseURL(child);
          } catch (ex) {
            // Parsing of the element failed, just skip it.
            LOG("_parse: failed to parse URL child: " + ex);
          }
          break;
        case "Image":
          this._parseImage(child);
          break;
        case "InputEncoding":
          this._queryCharset = child.textContent.toUpperCase();
          break;

        // Non-OpenSearch elements
        case "SearchForm":
          this._searchForm = child.textContent;
          break;
        case "UpdateUrl":
          this._updateURL = child.textContent;
          break;
        case "UpdateInterval":
          this._updateInterval = parseInt(child.textContent);
          break;
        case "IconUpdateUrl":
          this._iconUpdateURL = child.textContent;
          break;
        case "ExtensionID":
          this._extensionID = child.textContent;
          break;
      }
    }
    if (!this.name || (this._urls.length == 0))
      FAIL("_parse: No name, or missing URL!", Cr.NS_ERROR_FAILURE);
    if (!this.supportsResponseType(URLTYPE_SEARCH_HTML))
      FAIL("_parse: No text/html result type!", Cr.NS_ERROR_FAILURE);
  },

  /**
   * Init from a JSON record.
   **/
  _initWithJSON: function SRCH_ENG__initWithJSON(aJson) {
    this._name = aJson._name;
    this._shortName = aJson._shortName;
    this._loadPath = aJson._loadPath;
    this._description = aJson.description;
    this._hasPreferredIcon = aJson._hasPreferredIcon == undefined;
    this._queryCharset = aJson.queryCharset || DEFAULT_QUERY_CHARSET;
    this.__searchForm = aJson.__searchForm;
    this._updateInterval = aJson._updateInterval || null;
    this._updateURL = aJson._updateURL || null;
    this._iconUpdateURL = aJson._iconUpdateURL || null;
    this._readOnly = aJson._readOnly == undefined;
    this._iconURI = makeURI(aJson._iconURL);
    this._iconMapObj = aJson._iconMapObj;
    this._metaData = aJson._metaData || {};
    if (aJson.filePath) {
      this._filePath = aJson.filePath;
    }
    if (aJson.dirPath) {
      this._dirPath = aJson.dirPath;
      this._dirLastModifiedTime = aJson.dirLastModifiedTime;
    }
    if (aJson.extensionID) {
      this._extensionID = aJson.extensionID;
    }
    for (let i = 0; i < aJson._urls.length; ++i) {
      let url = aJson._urls[i];
      let engineURL = new EngineURL(url.type || URLTYPE_SEARCH_HTML,
                                    url.method || "GET", url.template,
                                    url.resultDomain || undefined);
      engineURL._initWithJSON(url, this);
      this._urls.push(engineURL);
    }
  },

  /**
   * Creates a JavaScript object that represents this engine.
   * @returns An object suitable for serialization as JSON.
   **/
  toJSON: function SRCH_ENG_toJSON() {
    var json = {
      _name: this._name,
      _shortName: this._shortName,
      _loadPath: this._loadPath,
      description: this.description,
      __searchForm: this.__searchForm,
      _iconURL: this._iconURL,
      _iconMapObj: this._iconMapObj,
      _metaData: this._metaData,
      _urls: this._urls
    };

    if (this._updateInterval)
      json._updateInterval = this._updateInterval;
    if (this._updateURL)
      json._updateURL = this._updateURL;
    if (this._iconUpdateURL)
      json._iconUpdateURL = this._iconUpdateURL;
    if (!this._hasPreferredIcon)
      json._hasPreferredIcon = this._hasPreferredIcon;
    if (this.queryCharset != DEFAULT_QUERY_CHARSET)
      json.queryCharset = this.queryCharset;
    if (!this._readOnly)
      json._readOnly = this._readOnly;
    if (this._filePath) {
      // File path is stored so that we can remove legacy xml files
      // from the profile if the user removes the engine.
      json.filePath = this._filePath;
    }
    if (this._dirPath) {
      // The directory path is only stored for extension-shipped engines,
      // it's used to invalidate the cache.
      json.dirPath = this._dirPath;
      json.dirLastModifiedTime = this._dirLastModifiedTime;
    }
    if (this._extensionID) {
      json.extensionID = this._extensionID;
    }

    return json;
  },

  setAttr(name, val) {
    this._metaData[name] = val;
  },

  getAttr(name) {
    return this._metaData[name] || undefined;
  },

  // nsISearchEngine
  get alias() {
    return this.getAttr("alias");
  },
  set alias(val) {
    var value = val ? val.trim() : null;
    this.setAttr("alias", value);
    notifyAction(this, SEARCH_ENGINE_CHANGED);
  },

  /**
   * Return the built-in identifier of app-provided engines.
   *
   * Note that this identifier is substantially similar to _id, with the
   * following exceptions:
   *
   * * There is no trailing file extension.
   * * There is no [app] prefix.
   *
   * @return a string identifier, or null.
   */
  get identifier() {
    // No identifier if If the engine isn't app-provided
    return this._isDefault ? this._shortName : null;
  },

  get description() {
    return this._description;
  },

  get hidden() {
    return this.getAttr("hidden") || false;
  },
  set hidden(val) {
    var value = !!val;
    if (value != this.hidden) {
      this.setAttr("hidden", value);
      notifyAction(this, SEARCH_ENGINE_CHANGED);
    }
  },

  get iconURI() {
    if (this._iconURI)
      return this._iconURI;
    return null;
  },

  get _iconURL() {
    if (!this._iconURI)
      return "";
    return this._iconURI.spec;
  },

  // Where the engine is being loaded from: will return the URI's spec if the
  // engine is being downloaded and does not yet have a file. This is only used
  // for logging and error messages.
  get _location() {
    if (this._uri)
      return this._uri.spec;

    return this._loadPath;
  },

  // This indicates where we found the .xml file to load the engine,
  // and attempts to hide user-identifiable data (such as username).
  getAnonymizedLoadPath(file, uri) {
    /* Examples of expected output:
     *   jar:[app]/omni.ja!browser/engine.xml
     *     'browser' here is the name of the chrome package, not a folder.
     *   [profile]/searchplugins/engine.xml
     *   [distribution]/searchplugins/common/engine.xml
     *   [other]/engine.xml
     */

    const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
    const NS_APP_USER_PROFILE_50_DIR = "ProfD";
    const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";

    const knownDirs = {
      app: NS_XPCOM_CURRENT_PROCESS_DIR,
      profile: NS_APP_USER_PROFILE_50_DIR,
      distribution: XRE_APP_DISTRIBUTION_DIR
    };

    let leafName = this._shortName;
    if (!leafName)
      return "null";
    leafName += ".xml";

    let prefix = "", suffix = "";
    if (!file) {
      if (uri.schemeIs("resource")) {
        uri = makeURI(Services.io.getProtocolHandler("resource")
                              .QueryInterface(Ci.nsISubstitutingProtocolHandler)
                              .resolveURI(uri));
      }
      let scheme = uri.scheme;
      let packageName = "";
      if (scheme == "chrome") {
        packageName = uri.hostPort;
        uri = gChromeReg.convertChromeURL(uri);
      }

      if (AppConstants.platform == "android") {
        // On Android the omni.ja file isn't at the same path as the binary
        // used to start the process. We tweak the path here so that the code
        // shared with Desktop will correctly identify files from the omni.ja
        // file as coming from the [app] folder.
        let appPath = Services.io.getProtocolHandler("resource")
                              .QueryInterface(Ci.nsIResProtocolHandler)
                              .getSubstitution("android");
        if (appPath) {
          appPath = appPath.spec;
          let spec = uri.spec;
          if (spec.includes(appPath)) {
            let appURI = Services.io.newFileURI(getDir(knownDirs["app"]));
            uri = Services.io.newURI(spec.replace(appPath, appURI.spec));
          }
        }
      }

      if (uri instanceof Ci.nsINestedURI) {
        prefix = "jar:";
        suffix = "!" + packageName + "/" + leafName;
        uri = uri.innermostURI;
      }
      if (uri instanceof Ci.nsIFileURL) {
        file = uri.file;
      } else {
        let path = "[" + scheme + "]";
        if (/^(?:https?|ftp)$/.test(scheme)) {
          path += uri.host;
        }
        return path + "/" + leafName;
      }
    }

    let id;
    let enginePath = file.path;

    for (let key in knownDirs) {
      let path;
      try {
        path = getDir(knownDirs[key]).path;
      } catch (e) {
        // Getting XRE_APP_DISTRIBUTION_DIR throws during unit tests.
        continue;
      }
      if (enginePath.startsWith(path)) {
        id = "[" + key + "]" + enginePath.slice(path.length).replace(/\\/g, "/");
        break;
      }
    }

    // If the folder doesn't have a known ancestor, don't record its path to
    // avoid leaking user identifiable data.
    if (!id)
      id = "[other]/" + file.leafName;

    return prefix + id + suffix;
  },

  get _isDefault() {
    // If we don't have a shortName, the engine is being parsed from a
    // downloaded file, so this can't be a default engine.
    if (!this._shortName)
      return false;

    // An engine is a default one if we initially loaded it from the application
    // or distribution directory.
    if (/^(?:jar:)?(?:\[app\]|\[distribution\])/.test(this._loadPath))
      return true;

    // If we are using a non-default locale or in the xpcshell test case,
    // we'll accept as a 'default' engine anything that has been registered at
    // resource://search-plugins/ even if the file doesn't come from the
    // application folder.  If not, skip costly additional checks.
    if (!Services.prefs.prefHasUserValue(LOCALE_PREF) &&
        !gEnvironment.get("XPCSHELL_TEST_PROFILE_DIR"))
      return false;

    // Some xpcshell tests use the search service without registering
    // resource://search-plugins/.
    if (!Services.io.getProtocolHandler("resource")
                 .QueryInterface(Ci.nsIResProtocolHandler)
                 .hasSubstitution("search-plugins"))
      return false;

    let uri = makeURI(APP_SEARCH_PREFIX + this._shortName + ".xml");
    if (this.getAnonymizedLoadPath(null, uri) == this._loadPath) {
      // This isn't a real default engine, but it's very close.
      LOG("_isDefault, pretending " + this._loadPath + " is a default engine");
      return true;
    }

    return false;
  },

  get _hasUpdates() {
    // Whether or not the engine has an update URL
    let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH, "self");
    return !!(this._updateURL || this._iconUpdateURL || selfURL);
  },

  get name() {
    return this._name;
  },

  get searchForm() {
    return this._getSearchFormWithPurpose();
  },

  _getSearchFormWithPurpose(aPurpose = "") {
    // First look for a <Url rel="searchform">
    var searchFormURL = this._getURLOfType(URLTYPE_SEARCH_HTML, "searchform");
    if (searchFormURL) {
      let submission = searchFormURL.getSubmission("", this, aPurpose);

      // If the rel=searchform URL is not type="get" (i.e. has postData),
      // ignore it, since we can only return a URL.
      if (!submission.postData)
        return submission.uri.spec;
    }

    if (!this._searchForm) {
      // No SearchForm specified in the engine definition file, use the prePath
      // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
      var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
      ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);
      this._searchForm = makeURI(htmlUrl.template).prePath;
    }

    return ParamSubstitution(this._searchForm, "", this);
  },

  get queryCharset() {
    if (this._queryCharset)
      return this._queryCharset;
    return this._queryCharset = "windows-1252"; // the default
  },

  // from nsISearchEngine
  addParam: function SRCH_ENG_addParam(aName, aValue, aResponseType) {
    if (!aName || (aValue == null))
      FAIL("missing name or value for nsISearchEngine::addParam!");
    ENSURE_WARN(!this._readOnly,
                "called nsISearchEngine::addParam on a read-only engine!",
                Cr.NS_ERROR_FAILURE);
    if (!aResponseType)
      aResponseType = URLTYPE_SEARCH_HTML;

    var url = this._getURLOfType(aResponseType);
    if (!url)
      FAIL("Engine object has no URL for response type " + aResponseType,
           Cr.NS_ERROR_FAILURE);

    url.addParam(aName, aValue);
  },

  get _defaultMobileResponseType() {
    let type = URLTYPE_SEARCH_HTML;

    let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
    let isTablet = sysInfo.get("tablet");
    if (isTablet && this.supportsResponseType("application/x-moz-tabletsearch")) {
      // Check for a tablet-specific search URL override
      type = "application/x-moz-tabletsearch";
    } else if (!isTablet && this.supportsResponseType("application/x-moz-phonesearch")) {
      // Check for a phone-specific search URL override
      type = "application/x-moz-phonesearch";
    }

    delete this._defaultMobileResponseType;
    return this._defaultMobileResponseType = type;
  },

  get _isWhiteListed() {
    let url = this._getURLOfType(URLTYPE_SEARCH_HTML).template;
    let hostname = makeURI(url).host;
    let whitelist = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
                            .getCharPref("reset.whitelist")
                            .split(",");
    if (whitelist.includes(hostname)) {
      LOG("The hostname " + hostname + " is white listed, " +
          "we won't show the search reset prompt");
      return true;
    }

    return false;
  },

  // from nsISearchEngine
  getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType, aPurpose) {
    if (!aResponseType) {
      aResponseType = AppConstants.platform == "android" ? this._defaultMobileResponseType :
                                                           URLTYPE_SEARCH_HTML;
    }

    if (aResponseType == URLTYPE_SEARCH_HTML &&
        Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).getBoolPref("reset.enabled") &&
        this.name == Services.search.currentEngine.name &&
        !this._isDefault &&
        this.name != Services.search.originalDefaultEngine.name &&
        (!this.getAttr("loadPathHash") ||
         this.getAttr("loadPathHash") != getVerificationHash(this._loadPath)) &&
        !this._isWhiteListed) {
      let url = "about:searchreset";
      let data = [];
      if (aData)
        data.push("data=" + encodeURIComponent(aData));
      if (aPurpose)
        data.push("purpose=" + aPurpose);
      if (data.length)
        url += "?" + data.join("&");
      return new Submission(makeURI(url));
    }

    var url = this._getURLOfType(aResponseType);

    if (!url)
      return null;

    if (!aData) {
      // Return a dummy submission object with our searchForm attribute
      return new Submission(makeURI(this._getSearchFormWithPurpose(aPurpose)));
    }

    LOG("getSubmission: In data: \"" + aData + "\"; Purpose: \"" + aPurpose + "\"");
    var data = "";
    try {
      data = gTextToSubURI.ConvertAndEscape(this.queryCharset, aData);
    } catch (ex) {
      LOG("getSubmission: Falling back to default queryCharset!");
      data = gTextToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
    }
    LOG("getSubmission: Out data: \"" + data + "\"");
    return url.getSubmission(data, this, aPurpose);
  },

  // from nsISearchEngine
  supportsResponseType: function SRCH_ENG_supportsResponseType(type) {
    return (this._getURLOfType(type) != null);
  },

  // from nsISearchEngine
  getResultDomain: function SRCH_ENG_getResultDomain(aResponseType) {
    if (!aResponseType) {
      aResponseType = AppConstants.platform == "android" ? this._defaultMobileResponseType :
                                                           URLTYPE_SEARCH_HTML;
    }

    LOG("getResultDomain: responseType: \"" + aResponseType + "\"");

    let url = this._getURLOfType(aResponseType);
    if (url)
      return url.resultDomain;
    return "";
  },

  /**
   * Returns URL parsing properties used by _buildParseSubmissionMap.
   */
  getURLParsingInfo() {
    let responseType = AppConstants.platform == "android" ? this._defaultMobileResponseType :
                                                            URLTYPE_SEARCH_HTML;

    LOG("getURLParsingInfo: responseType: \"" + responseType + "\"");

    let url = this._getURLOfType(responseType);
    if (!url || url.method != "GET") {
      return null;
    }

    let termsParameterName = url._getTermsParameterName();
    if (!termsParameterName) {
      return null;
    }

    let templateUrl = Services.io.newURI(url.template).QueryInterface(Ci.nsIURL);
    return {
      mainDomain: templateUrl.host,
      path: templateUrl.filePath.toLowerCase(),
      termsParameterName,
    };
  },

  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISearchEngine]),

  get wrappedJSObject() {
    return this;
  },

  /**
   * Returns a string with the URL to an engine's icon matching both width and
   * height. Returns null if icon with specified dimensions is not found.
   *
   * @param width
   *        Width of the requested icon.
   * @param height
   *        Height of the requested icon.
   */
  getIconURLBySize: function SRCH_ENG_getIconURLBySize(aWidth, aHeight) {
    if (aWidth == 16 && aHeight == 16)
      return this._iconURL;

    if (!this._iconMapObj)
      return null;

    let key = this._getIconKey(aWidth, aHeight);
    if (key in this._iconMapObj) {
      return this._iconMapObj[key];
    }
    return null;
  },

  /**
   * Gets an array of all available icons. Each entry is an object with
   * width, height and url properties. width and height are numeric and
   * represent the icon's dimensions. url is a string with the URL for
   * the icon.
   */
  getIcons: function SRCH_ENG_getIcons() {
    let result = [];
    if (this._iconURL)
      result.push({width: 16, height: 16, url: this._iconURL});

    if (!this._iconMapObj)
      return result;

    for (let key of Object.keys(this._iconMapObj)) {
      let iconSize = JSON.parse(key);
      result.push({
        width: iconSize.width,
        height: iconSize.height,
        url: this._iconMapObj[key]
      });
    }

    return result;
  },

  /**
   * Opens a speculative connection to the engine's search URI
   * (and suggest URI, if different) to reduce request latency
   *
   * @param  options
   *         An object that must contain the following fields:
   *         {window} the content window for the window performing the search
   *         {originAttributes} the originAttributes for performing the search
   *
   * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required
   *         elemeents
   */
  speculativeConnect: function SRCH_ENG_speculativeConnect(options) {
    if (!options || !options.window) {
      Cu.reportError("invalid options arg passed to nsISearchEngine.speculativeConnect");
      throw Cr.NS_ERROR_INVALID_ARG;
    }
    let connector =
        Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);

    let searchURI = this.getSubmission("dummy").uri;

    let callbacks = options.window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                           .getInterface(Components.interfaces.nsIWebNavigation)
                           .QueryInterface(Components.interfaces.nsILoadContext);

    // Using the codebase principal which is constructed by the search URI
    // and given originAttributes. If originAttributes are not given, we
    // fallback to use the docShell's originAttributes.
    let attrs = options.originAttributes;

    if (!attrs) {
      attrs = options.window.document
                            .docShell
                            .getOriginAttributes();
    }

    let principal = Services.scriptSecurityManager
                            .createCodebasePrincipal(searchURI, attrs);

    connector.speculativeConnect2(searchURI, principal, callbacks);

    if (this.supportsResponseType(URLTYPE_SUGGEST_JSON)) {
      let suggestURI = this.getSubmission("dummy", URLTYPE_SUGGEST_JSON).uri;
      if (suggestURI.prePath != searchURI.prePath)
        connector.speculativeConnect2(suggestURI, principal, callbacks);
    }
  },
};

// nsISearchSubmission
function Submission(aURI, aPostData = null) {
  this._uri = aURI;
  this._postData = aPostData;
}
Submission.prototype = {
  get uri() {
    return this._uri;
  },
  get postData() {
    return this._postData;
  },
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISearchSubmission])
}

// nsISearchParseSubmissionResult
function ParseSubmissionResult(aEngine, aTerms, aTermsOffset, aTermsLength) {
  this._engine = aEngine;
  this._terms = aTerms;
  this._termsOffset = aTermsOffset;
  this._termsLength = aTermsLength;
}
ParseSubmissionResult.prototype = {
  get engine() {
    return this._engine;
  },
  get terms() {
    return this._terms;
  },
  get termsOffset() {
    return this._termsOffset;
  },
  get termsLength() {
    return this._termsLength;
  },
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISearchParseSubmissionResult]),
}

const gEmptyParseSubmissionResult =
      Object.freeze(new ParseSubmissionResult(null, "", -1, 0));

function executeSoon(func) {
  Services.tm.dispatchToMainThread(func);
}

/**
 * Check for sync initialization has completed or not.
 *
 * @param {aPromise} A promise.
 *
 * @returns the value returned by the invoked method.
 * @throws NS_ERROR_ALREADY_INITIALIZED if sync initialization has completed.
 */
function checkForSyncCompletion(aPromise) {
  return aPromise.then(function(aValue) {
    if (gInitialized) {
      throw Components.Exception("Synchronous fallback was called and has " +
                                 "finished so no need to pursue asynchronous " +
                                 "initialization",
                                 Cr.NS_ERROR_ALREADY_INITIALIZED);
    }
    return aValue;
  });
}

// nsIBrowserSearchService
function SearchService() {
  // Replace empty LOG function with the useful one if the log pref is set.
  if (getBoolPref(BROWSER_SEARCH_PREF + "log", false))
    LOG = DO_LOG;

  this._initObservers = PromiseUtils.defer();
}

SearchService.prototype = {
  classID: Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}"),

  // The current status of initialization. Note that it does not determine if
  // initialization is complete, only if an error has been encountered so far.
  _initRV: Cr.NS_OK,

  // The boolean indicates that the initialization has started or not.
  _initStarted: null,

  // Reading the JSON cache file is the first thing done during initialization.
  // During the async init, we save it in a field so that if we have to do a
  // sync init before the async init finishes, we can avoid reading the cache
  // with sync disk I/O and handling lz4 decompression synchronously.
  // This is set back to null as soon as the initialization is finished.
  _cacheFileJSON: null,

  // If initialization has not been completed yet, perform synchronous
  // initialization.
  // Throws in case of initialization error.
  _ensureInitialized: function SRCH_SVC__ensureInitialized() {
    if (gInitialized) {
      if (!Components.isSuccessCode(this._initRV)) {
        LOG("_ensureInitialized: failure");
        throw this._initRV;
      }
      return;
    }

    let warning =
      "Search service falling back to synchronous initialization. " +
      "This is generally the consequence of an add-on using a deprecated " +
      "search service API.";
    Deprecated.warning(warning, "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIBrowserSearchService#async_warning");
    LOG(warning);

    this._syncInit();
    if (!Components.isSuccessCode(this._initRV)) {
      throw this._initRV;
    }
  },

  // Synchronous implementation of the initializer.
  // Used by |_ensureInitialized| as a fallback if initialization is not
  // complete.
  _syncInit: function SRCH_SVC__syncInit() {
    LOG("_syncInit start");
    this._initStarted = true;
    migrateRegionPrefs();

    let cache = this._readCacheFile();
    if (cache.metaData)
      this._metaData = cache.metaData;

    try {
      this._syncLoadEngines(cache);
    } catch (ex) {
      this._initRV = Cr.NS_ERROR_FAILURE;
      LOG("_syncInit: failure loading engines: " + ex);
    }
    this._addObservers();

    gInitialized = true;
    this._cacheFileJSON = null;

    this._initObservers.resolve(this._initRV);

    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
    Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(true);
    this._recordEngineTelemetry();

    LOG("_syncInit end");
  },

  /**
   * Asynchronous implementation of the initializer.
   *
   * @returns {Promise} A promise, resolved successfully if the initialization
   * succeeds.
   */
  async _asyncInit() {
    LOG("_asyncInit start");

    migrateRegionPrefs();

    // See if we have a cache file so we don't have to parse a bunch of XML.
    let cache = {};
    // Not using checkForSyncCompletion here because we want to ensure we
    // fetch the country code and geo specific defaults asynchronously even
    // if a sync init has been forced.
    cache = await this._asyncReadCacheFile();

    if (!gInitialized && cache.metaData)
      this._metaData = cache.metaData;

    try {
      await checkForSyncCompletion(ensureKnownCountryCode(this));
    } catch (ex) {
      if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
        throw ex;
      }
      LOG("_asyncInit: failure determining country code: " + ex);
    }
    try {
      await checkForSyncCompletion(this._asyncLoadEngines(cache));
    } catch (ex) {
      if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
        throw ex;
      }
      this._initRV = Cr.NS_ERROR_FAILURE;
      LOG("_asyncInit: failure loading engines: " + ex);
    }
    this._addObservers();
    gInitialized = true;
    this._cacheFileJSON = null;
    this._initObservers.resolve(this._initRV);
    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
    Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(false);
    this._recordEngineTelemetry();

    LOG("_asyncInit: Completed _asyncInit");
  },

  _metaData: { },
  setGlobalAttr(name, val) {
    this._metaData[name] = val;
    this.batchTask.disarm();
    this.batchTask.arm();
  },
  setVerifiedGlobalAttr(name, val) {
    this.setGlobalAttr(name, val);
    this.setGlobalAttr(name + "Hash", getVerificationHash(val));
  },

  getGlobalAttr(name) {
    return this._metaData[name] || undefined;
  },
  getVerifiedGlobalAttr(name) {
    let val = this.getGlobalAttr(name);
    if (val && this.getGlobalAttr(name + "Hash") != getVerificationHash(val)) {
      LOG("getVerifiedGlobalAttr, invalid hash for " + name);
      return "";
    }
    return val;
  },

  _engines: { },
  __sortedEngines: null,
  _visibleDefaultEngines: [],
  get _sortedEngines() {
    if (!this.__sortedEngines)
      return this._buildSortedEngineList();
    return this.__sortedEngines;
  },

  // Get the original Engine object that is the default for this region,
  // ignoring changes the user may have subsequently made.
  get originalDefaultEngine() {
    let defaultEngine = this.getVerifiedGlobalAttr("searchDefault");
    if (!defaultEngine) {
      let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
      let nsIPLS = Ci.nsIPrefLocalizedString;

      let defPref = getGeoSpecificPrefName("defaultenginename");
      try {
        defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
      } catch (ex) {
        // If the default pref is invalid (e.g. an add-on set it to a bogus value)
        // getEngineByName will just return null, which is the best we can do.
      }
    }

    return this.getEngineByName(defaultEngine);
  },

  resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
    this.currentEngine = this.originalDefaultEngine;
  },

  _buildCache: function SRCH_SVC__buildCache() {
    if (this._batchTask)
      this._batchTask.disarm();

    let cache = {};
    let locale = getLocale();
    let buildID = Services.appinfo.platformBuildID;

    // Allows us to force a cache refresh should the cache format change.
    cache.version = CACHE_VERSION;
    // We don't want to incur the costs of stat()ing each plugin on every
    // startup when the only (supported) time they will change is during
    // app updates (where the buildID is obviously going to change).
    // Extension-shipped plugins are the only exception to this, but their
    // directories are blown away during updates, so we'll detect their changes.
    cache.buildID = buildID;
    cache.locale = locale;

    cache.visibleDefaultEngines = this._visibleDefaultEngines;
    cache.metaData = this._metaData;
    cache.engines = [];

    for (let name in this._engines) {
      cache.engines.push(this._engines[name]);
    }

    try {
      if (!cache.engines.length)
        throw "cannot write without any engine.";

      LOG("_buildCache: Writing to cache file.");
      let path = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
      let data = gEncoder.encode(JSON.stringify(cache));
      let promise = OS.File.writeAtomic(path, data, {compression: "lz4",
                                                     tmpPath: path + ".tmp"});

      promise.then(
        function onSuccess() {
          Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, SEARCH_SERVICE_CACHE_WRITTEN);
        },
        function onError(e) {
          LOG("_buildCache: failure during writeAtomic: " + e);
        }
      );
    } catch (ex) {
      LOG("_buildCache: Could not write to cache file: " + ex);
    }
  },

  _syncLoadEngines: function SRCH_SVC__syncLoadEngines(cache) {
    LOG("_syncLoadEngines: start");
    // See if we have a cache file so we don't have to parse a bunch of XML.
    let chromeURIs = this._findJAREngines();

    let distDirs = [];
    let locations;
    try {
      locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
                         Ci.nsISimpleEnumerator);
    } catch (e) {
      // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
      // so this throws during unit tests (but not xpcshell tests).
      locations = {hasMoreElements: () => false};
    }
    while (locations.hasMoreElements()) {
      let dir = locations.getNext().QueryInterface(Ci.nsIFile);
      if (dir.directoryEntries.hasMoreElements())
        distDirs.push(dir);
    }

    let otherDirs = [];
    let userSearchDir = getDir(NS_APP_USER_SEARCH_DIR);
    locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
    while (locations.hasMoreElements()) {
      let dir = locations.getNext().QueryInterface(Ci.nsIFile);
      if ((!cache.engines || !dir.equals(userSearchDir)) &&
          dir.directoryEntries.hasMoreElements())
        otherDirs.push(dir);
    }

    function modifiedDir(aDir) {
      return cacheOtherPaths.get(aDir.path) != aDir.lastModifiedTime;
    }

    function notInCacheVisibleEngines(aEngineName) {
      return cache.visibleDefaultEngines.indexOf(aEngineName) == -1;
    }

    let buildID = Services.appinfo.platformBuildID;
    let cacheOtherPaths = new Map();
    if (cache.engines) {
      for (let engine of cache.engines) {
        if (engine._dirPath) {
          cacheOtherPaths.set(engine._dirPath, engine._dirLastModifiedTime);
        }
      }
    }

    let rebuildCache = !cache.engines ||
                       cache.version != CACHE_VERSION ||
                       cache.locale != getLocale() ||
                       cache.buildID != buildID ||
                       cacheOtherPaths.size != otherDirs.length ||
                       otherDirs.some(d => !cacheOtherPaths.has(d.path)) ||
                       cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
                       this._visibleDefaultEngines.some(notInCacheVisibleEngines) ||
                       otherDirs.some(modifiedDir);

    if (rebuildCache) {
      LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
      distDirs.forEach(this._loadEnginesFromDir, this);

      this._loadFromChromeURLs(chromeURIs);

      LOG("_loadEngines: load user-installed engines from the obsolete cache");
      this._loadEnginesFromCache(cache, true);

      otherDirs.forEach(this._loadEnginesFromDir, this);

      this._loadEnginesMetadataFromCache(cache);
      this._buildCache();
      return;
    }

    LOG("_loadEngines: loading from cache directories");
    this._loadEnginesFromCache(cache);

    LOG("_loadEngines: done");
  },

  /**
   * Loads engines asynchronously.
   *
   * @returns {Promise} A promise, resolved successfully if loading data
   * succeeds.
   */
  async _asyncLoadEngines(cache) {
    LOG("_asyncLoadEngines: start");
    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
    let chromeURIs =
      await checkForSyncCompletion(this._asyncFindJAREngines());

    // Get the non-empty distribution directories into distDirs...
    let distDirs = [];
    let locations;
    try {
      locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
                         Ci.nsISimpleEnumerator);
    } catch (e) {
      // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
      // so this throws during unit tests (but not xpcshell tests).
      locations = {hasMoreElements: () => false};
    }
    while (locations.hasMoreElements()) {
      let dir = locations.getNext().QueryInterface(Ci.nsIFile);
      let iterator = new OS.File.DirectoryIterator(dir.path,
                                                   { winPattern: "*.xml" });
      try {
        // Add dir to distDirs if it contains any files.
        await checkForSyncCompletion(iterator.next());
        distDirs.push(dir);
      } catch (ex) {
        // Catch for StopIteration exception.
        if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
          throw ex;
        }
      } finally {
        iterator.close();
      }
    }

    // Add the non-empty directories of NS_APP_SEARCH_DIR_LIST to
    // otherDirs...
    let otherDirs = [];
    let userSearchDir = getDir(NS_APP_USER_SEARCH_DIR);
    locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
    while (locations.hasMoreElements()) {
      let dir = locations.getNext().QueryInterface(Ci.nsIFile);
      if (cache.engines && dir.equals(userSearchDir))
        continue;
      let iterator = new OS.File.DirectoryIterator(dir.path,
                                                   { winPattern: "*.xml" });
      try {
        // Add dir to otherDirs if it contains any files.
        await checkForSyncCompletion(iterator.next());
        otherDirs.push(dir);
      } catch (ex) {
        // Catch for StopIteration exception.
        if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
          throw ex;
        }
      } finally {
        iterator.close();
      }
    }

    let hasModifiedDir = async function(aList) {
      let modifiedDir = false;

      for (let dir of aList) {
        let lastModifiedTime = cacheOtherPaths.get(dir.path);
        if (!lastModifiedTime) {
          continue;
        }

        let info = await OS.File.stat(dir.path);
        if (lastModifiedTime != info.lastModificationDate.getTime()) {
          modifiedDir = true;
          break;
        }
      }
      return modifiedDir;
    };

    function notInCacheVisibleEngines(aEngineName) {
      return cache.visibleDefaultEngines.indexOf(aEngineName) == -1;
    }

    let buildID = Services.appinfo.platformBuildID;
    let cacheOtherPaths = new Map();
    if (cache.engines) {
      for (let engine of cache.engines) {
        if (engine._dirPath) {
          cacheOtherPaths.set(engine._dirPath, engine._dirLastModifiedTime);
        }
      }
    }

    let rebuildCache = !cache.engines ||
                       cache.version != CACHE_VERSION ||
                       cache.locale != getLocale() ||
                       cache.buildID != buildID ||
                       cacheOtherPaths.size != otherDirs.length ||
                       otherDirs.some(d => !cacheOtherPaths.has(d.path)) ||
                       cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
                       this._visibleDefaultEngines.some(notInCacheVisibleEngines) ||
                       (await checkForSyncCompletion(hasModifiedDir(otherDirs)));

    if (rebuildCache) {
      LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
      for (let loadDir of distDirs) {
        let enginesFromDir =
          await checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
        enginesFromDir.forEach(this._addEngineToStore, this);
      }
      let enginesFromURLs =
        await checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
      enginesFromURLs.forEach(this._addEngineToStore, this);

      LOG("_asyncLoadEngines: loading user-installed engines from the obsolete cache");
      this._loadEnginesFromCache(cache, true);

      for (let loadDir of otherDirs) {
        let enginesFromDir =
          await checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
        enginesFromDir.forEach(this._addEngineToStore, this);
      }

      this._loadEnginesMetadataFromCache(cache);
      this._buildCache();
      return;
    }

    LOG("_asyncLoadEngines: loading from cache directories");
    this._loadEnginesFromCache(cache);

    LOG("_asyncLoadEngines: done");
  },

  _asyncReInit() {
    LOG("_asyncReInit");
    // Start by clearing the initialized state, so we don't abort early.
    gInitialized = false;

    (async () => {
      try {
        if (this._batchTask) {
          LOG("finalizing batch task");
          let task = this._batchTask;
          this._batchTask = null;
          await task.finalize();
        }

        // Clear the engines, too, so we don't stick with the stale ones.
        this._engines = {};
        this.__sortedEngines = null;
        this._currentEngine = null;
        this._visibleDefaultEngines = [];
        this._metaData = {};
        this._cacheFileJSON = null;

        // Tests that want to force a synchronous re-initialization need to
        // be notified when we are done uninitializing.
        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC,
                                     "uninit-complete");

        let cache = {};
        cache = await this._asyncReadCacheFile();
        if (!gInitialized && cache.metaData)
          this._metaData = cache.metaData;

        await ensureKnownCountryCode(this);
        // Due to the HTTP requests done by ensureKnownCountryCode, it's possible that
        // at this point a synchronous init has been forced by other code.
        if (!gInitialized)
          await this._asyncLoadEngines(cache);

        // Typically we'll re-init as a result of a pref observer,
        // so signal to 'callers' that we're done.
        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
        this._recordEngineTelemetry();
        gInitialized = true;
      } catch (err) {
        LOG("Reinit failed: " + err);
        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-failed");
      } finally {
        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-complete");
      }
    })();
  },

  /**
   * Read the cache file synchronously. This also imports data from the old
   * search-metadata.json file if needed.
   *
   * @returns A JS object containing the cached data.
   */
  _readCacheFile: function SRCH_SVC__readCacheFile() {
    if (this._cacheFileJSON) {
      return this._cacheFileJSON;
    }

    let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
    cacheFile.append(CACHE_FILENAME);

    let stream;
    try {
      stream = Cc["@mozilla.org/network/file-input-stream;1"].
                 createInstance(Ci.nsIFileInputStream);
      stream.init(cacheFile, MODE_RDONLY, PERMS_FILE, 0);

      let bis = Cc["@mozilla.org/binaryinputstream;1"]
                  .createInstance(Ci.nsIBinaryInputStream);
      bis.setInputStream(stream);

      let count = stream.available();
      let array = new Uint8Array(count);
      bis.readArrayBuffer(count, array.buffer);

      let bytes = Lz4.decompressFileContent(array);
      let json = JSON.parse(new TextDecoder().decode(bytes));
      if (!json.engines || !json.engines.length)
        throw "no engine in the file";
      return json;
    } catch (ex) {
      LOG("_readCacheFile: Error reading cache file: " + ex);
    } finally {
      stream.close();
    }

    try {
      cacheFile.leafName = "search-metadata.json";
      stream = Cc["@mozilla.org/network/file-input-stream;1"].
                 createInstance(Ci.nsIFileInputStream);
      stream.init(cacheFile, MODE_RDONLY, PERMS_FILE, 0);
      let metadata = parseJsonFromStream(stream);
      let json = {};
      if ("[global]" in metadata) {
        LOG("_readCacheFile: migrating metadata from search-metadata.json");
        let data = metadata["[global]"];
        json.metaData = {};
        let fields = ["searchDefault", "searchDefaultHash", "searchDefaultExpir",
                      "current", "hash",
                      "visibleDefaultEngines", "visibleDefaultEnginesHash"];
        for (let field of fields) {
          let name = field.toLowerCase();
          if (name in data)
            json.metaData[field] = data[name];
        }
      }
      delete metadata["[global]"];
      json._oldMetadata = metadata;

      return json;
    } catch (ex) {
      LOG("_readCacheFile: failed to read old metadata: " + ex);
      return {};
    } finally {
      stream.close();
    }
  },

  /**
   * Read the cache file asynchronously. This also imports data from the old
   * search-metadata.json file if needed.
   *
   * @returns {Promise} A promise, resolved successfully if retrieveing data
   * succeeds.
   */
  async _asyncReadCacheFile() {
    let json;
    try {
      let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
      let bytes = await OS.File.read(cacheFilePath, {compression: "lz4"});
      json = JSON.parse(new TextDecoder().decode(bytes));
      if (!json.engines || !json.engines.length)
        throw "no engine in the file";
      this._cacheFileJSON = json;
    } catch (ex) {
      LOG("_asyncReadCacheFile: Error reading cache file: " + ex);
      json = {};

      let oldMetadata =
        OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
      try {
        let bytes = await OS.File.read(oldMetadata);
        let metadata = JSON.parse(new TextDecoder().decode(bytes));
        if ("[global]" in metadata) {
          LOG("_asyncReadCacheFile: migrating metadata from search-metadata.json");
          let data = metadata["[global]"];
          json.metaData = {};
          let fields = ["searchDefault", "searchDefaultHash", "searchDefaultExpir",
                        "current", "hash",
                        "visibleDefaultEngines", "visibleDefaultEnginesHash"];
          for (let field of fields) {
            let name = field.toLowerCase();
            if (name in data)
              json.metaData[field] = data[name];
          }
        }
        delete metadata["[global]"];
        json._oldMetadata = metadata;
      } catch (ex) {}
    }
    return json;
  },

  _batchTask: null,
  get batchTask() {
    if (!this._batchTask) {
      let task = () => {
        LOG("batchTask: Invalidating engine cache");
        this._buildCache();
      };
      this._batchTask = new DeferredTask(task, CACHE_INVALIDATION_DELAY);
    }
    return this._batchTask;
  },

  _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
    LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");

    // See if there is an existing engine with the same name. However, if this
    // engine is updating another engine, it's allowed to have the same name.
    var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
                               aEngine.name == aEngine._engineToUpdate.name);
    if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
      LOG("_addEngineToStore: Duplicate engine found, aborting!");
      return;
    }

    if (aEngine._engineToUpdate) {
      // We need to replace engineToUpdate with the engine that just loaded.
      var oldEngine = aEngine._engineToUpdate;

      // Remove the old engine from the hash, since it's keyed by name, and our
      // name might change (the update might have a new name).
      delete this._engines[oldEngine.name];

      // Hack: we want to replace the old engine with the new one, but since
      // people may be holding refs to the nsISearchEngine objects themselves,
      // we'll just copy over all "private" properties (those without a getter
      // or setter) from one object to the other.
      for (var p in aEngine) {
        if (!(aEngine.__lookupGetter__(p) || aEngine.__lookupSetter__(p)))
          oldEngine[p] = aEngine[p];
      }
      aEngine = oldEngine;
      aEngine._engineToUpdate = null;

      // Add the engine back
      this._engines[aEngine.name] = aEngine;
      notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
    } else {
      // Not an update, just add the new engine.
      this._engines[aEngine.name] = aEngine;
      // Only add the engine to the list of sorted engines if the initial list
      // has already been built (i.e. if this.__sortedEngines is non-null). If
      // it hasn't, we're loading engines from disk and the sorted engine list
      // will be built once we need it.
      if (this.__sortedEngines) {
        this.__sortedEngines.push(aEngine);
        this._saveSortedEngineList();
      }
      notifyAction(aEngine, SEARCH_ENGINE_ADDED);
    }

    if (aEngine._hasUpdates) {
      // Schedule the engine's next update, if it isn't already.
      if (!aEngine.getAttr("updateexpir"))
        engineUpdateService.scheduleNextUpdate(aEngine);
    }
  },

  _loadEnginesMetadataFromCache: function SRCH_SVC__loadEnginesMetadataFromCache(cache) {
    if (cache._oldMetadata) {
      // If we have old metadata in the cache, we had no valid cache
      // file and read data from search-metadata.json.
      for (let name in this._engines) {
        let engine = this._engines[name];
        if (engine._id && cache._oldMetadata[engine._id])
          engine._metaData = cache._oldMetadata[engine._id];
      }
      return;
    }

    if (!cache.engines)
      return;

    for (let engine of cache.engines) {
      let name = engine._name;
      if (name in this._engines) {
        LOG("_loadEnginesMetadataFromCache, transfering metadata for " + name);
        this._engines[name]._metaData = engine._metaData;
      }
    }
  },

  _loadEnginesFromCache: function SRCH_SVC__loadEnginesFromCache(cache,
                                                                 skipReadOnly) {
    if (!cache.engines)
      return;

    LOG("_loadEnginesFromCache: Loading " +
        cache.engines.length + " engines from cache");

    let skippedEngines = 0;
    for (let engine of cache.engines) {
      if (skipReadOnly && engine._readOnly == undefined) {
        ++skippedEngines;
        continue;
      }

      this._loadEngineFromCache(engine);
    }

    if (skippedEngines) {
      LOG("_loadEnginesFromCache: skipped " + skippedEngines + " read-only engines.");
    }
  },

  _loadEngineFromCache: function SRCH_SVC__loadEngineFromCache(json) {
    try {
      let engine = new Engine(json._shortName, json._readOnly == undefined);
      engine._initWithJSON(json);
      this._addEngineToStore(engine);
    } catch (ex) {
      LOG("Failed to load " + json._name + " from cache: " + ex);
      LOG("Engine JSON: " + json.toSource());
    }
  },

  _loadEnginesFromDir: function SRCH_SVC__loadEnginesFromDir(aDir) {
    LOG("_loadEnginesFromDir: Searching in " + aDir.path + " for search engines.");

    // Check whether aDir is the user profile dir
    var isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));

    var files = aDir.directoryEntries
                    .QueryInterface(Ci.nsIDirectoryEnumerator);

    while (files.hasMoreElements()) {
      var file = files.nextFile;

      // Ignore hidden and empty files, and directories
      if (!file.isFile() || file.fileSize == 0 || file.isHidden())
        continue;

      var fileURL = Services.io.newFileURI(file).QueryInterface(Ci.nsIURL);
      var fileExtension = fileURL.fileExtension.toLowerCase();

      if (fileExtension != "xml") {
        // Not an engine
        continue;
      }

      var addedEngine = null;
      try {
        addedEngine = new Engine(file, !isInProfile);
        addedEngine._initFromFile(file);
        if (!isInProfile && !addedEngine._isDefault) {
          addedEngine._dirPath = aDir.path;
          addedEngine._dirLastModifiedTime = aDir.lastModifiedTime;
        }
      } catch (ex) {
        LOG("_loadEnginesFromDir: Failed to load " + file.path + "!\n" + ex);
        continue;
      }

      this._addEngineToStore(addedEngine);
    }
  },

  /**
   * Loads engines from a given directory asynchronously.
   *
   * @param aDir the directory.
   *
   * @returns {Promise} A promise, resolved successfully if retrieveing data
   * succeeds.
   */
  async _asyncLoadEnginesFromDir(aDir) {
    LOG("_asyncLoadEnginesFromDir: Searching in " + aDir.path + " for search engines.");

    // Check whether aDir is the user profile dir
    let isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
    let dirPath = aDir.path;
    let iterator = new OS.File.DirectoryIterator(dirPath);

    let osfiles = await iterator.nextBatch();
    iterator.close();

    let engines = [];
    for (let osfile of osfiles) {
      if (osfile.isDir || osfile.isSymLink)
        continue;

      let fileInfo = await OS.File.stat(osfile.path);
      if (fileInfo.size == 0)
        continue;

      let parts = osfile.path.split(".");
      if (parts.length <= 1 || (parts.pop()).toLowerCase() != "xml") {
        // Not an engine
        continue;
      }

      let addedEngine = null;
      try {
        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
        file.initWithPath(osfile.path);
        addedEngine = new Engine(file, !isInProfile);
        await checkForSyncCompletion(addedEngine._asyncInitFromFile(file));
        if (!isInProfile && !addedEngine._isDefault) {
          addedEngine._dirPath = dirPath;
          let info = await OS.File.stat(dirPath);
          addedEngine._dirLastModifiedTime =
            info.lastModificationDate.getTime();
        }
        engines.push(addedEngine);
      } catch (ex) {
        if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
          throw ex;
        }
        LOG("_asyncLoadEnginesFromDir: Failed to load " + osfile.path + "!\n" + ex);
      }
    }
    return engines;
  },

  _loadFromChromeURLs: function SRCH_SVC_loadFromChromeURLs(aURLs) {
    aURLs.forEach(function(url) {
      try {
        LOG("_loadFromChromeURLs: loading engine from chrome url: " + url);

        let uri = makeURI(url);
        let engine = new Engine(uri, true);

        engine._initFromURISync(uri);

        this._addEngineToStore(engine);
      } catch (ex) {
        LOG("_loadFromChromeURLs: failed to load engine: " + ex);
      }
    }, this);
  },

  /**
   * Loads engines from Chrome URLs asynchronously.
   *
   * @param aURLs a list of URLs.
   *
   * @returns {Promise} A promise, resolved successfully if loading data
   * succeeds.
   */
  async _asyncLoadFromChromeURLs(aURLs) {
    let engines = [];
    for (let url of aURLs) {
      try {
        LOG("_asyncLoadFromChromeURLs: loading engine from chrome url: " + url);
        let uri = Services.io.newURI(url);
        let engine = new Engine(uri, true);
        await checkForSyncCompletion(engine._asyncInitFromURI(uri));
        engines.push(engine);
      } catch (ex) {
        if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
          throw ex;
        }
        LOG("_asyncLoadFromChromeURLs: failed to load engine: " + ex);
      }
    }
    return engines;
  },

  _convertChannelToFile(chan) {
    let fileURI = chan.URI;
    while (fileURI instanceof Ci.nsIJARURI)
      fileURI = fileURI.JARFile;
    fileURI.QueryInterface(Ci.nsIFileURL);

    return fileURI.file;
  },

  _findJAREngines: function SRCH_SVC_findJAREngines() {
    LOG("_findJAREngines: looking for engines in JARs")

    let chan = makeChannel(APP_SEARCH_PREFIX + "list.json");
    if (!chan) {
      LOG("_findJAREngines: " + APP_SEARCH_PREFIX + " isn't registered");
      return [];
    }

    let uris = [];

    let sis = Cc["@mozilla.org/scriptableinputstream;1"].
                createInstance(Ci.nsIScriptableInputStream);
    try {
      sis.init(chan.open2());
      this._parseListJSON(sis.read(sis.available()), uris);
      // parseListJSON will catch its own errors, so we
      // should only go into this catch if list.json
      // doesn't exist
    } catch (e) {
      chan = makeChannel(APP_SEARCH_PREFIX + "list.txt");
      sis.init(chan.open2());
      this._parseListTxt(sis.read(sis.available()), uris);
    }
    return uris;
  },

  /**
   * Loads jar engines asynchronously.
   *
   * @returns {Promise} A promise, resolved successfully if finding jar engines
   * succeeds.
   */
  async _asyncFindJAREngines() {
    LOG("_asyncFindJAREngines: looking for engines in JARs")

    let listURL = APP_SEARCH_PREFIX + "list.json";
    let chan = makeChannel(listURL);
    if (!chan) {
      LOG("_asyncFindJAREngines: " + APP_SEARCH_PREFIX + " isn't registered");
      return [];
    }

    let uris = [];

    // Read list.json to find the engines we need to load.
    let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                    createInstance(Ci.nsIXMLHttpRequest);
    request.overrideMimeType("text/plain");
    let list = await new Promise(resolve => {
      request.onload = function(aEvent) {
        resolve(aEvent.target.responseText);
      };
      request.onerror = function(aEvent) {
        LOG("_asyncFindJAREngines: failed to read " + listURL);
        // Couldn't find list.json, try list.txt
        request.onerror = function(aEvent) {
          LOG("_asyncFindJAREngines: failed to read " + APP_SEARCH_PREFIX + "list.txt");
          resolve("");
        }
        request.open("GET", Services.io.newURI(APP_SEARCH_PREFIX + "list.txt").spec, true);
        request.send();
      };
      request.open("GET", Services.io.newURI(listURL).spec, true);
      request.send();
    });

    if (request.responseURL.endsWith(".txt")) {
      this._parseListTxt(list, uris);
    } else {
      this._parseListJSON(list, uris);
    }
    return uris;
  },

  _parseListJSON: function SRCH_SVC_parseListJSON(list, uris) {
    let searchSettings;
    try {
      searchSettings = JSON.parse(list);
    } catch (e) {
      LOG("failing to parse list.json: " + e);
      return;
    }

    // Check if we have a useable country specific list of visible default engines.
    // This will only be set if we got the list from the Mozilla search server;
    // it will not be set for distributions.
    let engineNames;
    let visibleDefaultEngines = this.getVerifiedGlobalAttr("visibleDefaultEngines");
    if (visibleDefaultEngines) {
      let jarNames = new Set();
      for (let region in searchSettings) {
        // Artifact builds use the full list.json which parses
        // slightly differently
        if (!("visibleDefaultEngines" in searchSettings[region])) {
          continue;
        }
        for (let engine of searchSettings[region]["visibleDefaultEngines"]) {
          jarNames.add(engine);
        }
      }

      engineNames = visibleDefaultEngines.split(",");
      for (let engineName of engineNames) {
        // If all engineName values are part of jarNames,
        // then we can use the country specific list, otherwise ignore it.
        // The visibleDefaultEngines string containing the name of an engine we
        // don't ship indicates the server is misconfigured to answer requests
        // from the specific Firefox version we are running, so ignoring the
        // value altogether is safer.
        if (!jarNames.has(engineName)) {
          LOG("_parseListJSON: ignoring visibleDefaultEngines value because " +
              engineName + " is not in the jar engines we have found");
          engineNames = null;
          break;
        }
      }
    }

    // Fallback to building a list based on the regions in the JSON
    if (!engineNames || !engineNames.length) {
      let region;
      if (Services.prefs.prefHasUserValue("browser.search.region")) {
        region = Services.prefs.getCharPref("browser.search.region");
      }
      if (!region || !(region in searchSettings)) {
        region = "default";
      }
      engineNames = searchSettings[region]["visibleDefaultEngines"];
    }

    // Remove any engine names that are supposed to be ignored.
    // This pref is only allows in a partner distribution.
    let branch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
    if (isPartnerBuild() &&
        branch.getPrefType("ignoredJAREngines") == branch.PREF_STRING) {
      let ignoredJAREngines = branch.getCharPref("ignoredJAREngines")
                                    .split(",");
      let filteredEngineNames = engineNames.filter(e => !ignoredJAREngines.includes(e));
      // Don't allow all engines to be hidden
      if (filteredEngineNames.length > 0) {
        engineNames = filteredEngineNames;
      }
    }

    for (let name of engineNames) {
      uris.push(APP_SEARCH_PREFIX + name + ".xml");
    }

    // Store this so that it can be used while writing the cache file.
    this._visibleDefaultEngines = engineNames;
  },

  _parseListTxt: function SRCH_SVC_parseListTxt(list, uris) {
    let names = list.split("\n").filter(n => !!n);
    // This maps the names of our built-in engines to a boolean
    // indicating whether it should be hidden by default.
    let jarNames = new Map();
    for (let name of names) {
      if (name.endsWith(":hidden")) {
        name = name.split(":")[0];
        jarNames.set(name, true);
      } else {
        jarNames.set(name, false);
      }
    }

    // Check if we have a useable country specific list of visible default engines.
    let engineNames;
    let visibleDefaultEngines = this.getVerifiedGlobalAttr("visibleDefaultEngines");
    if (visibleDefaultEngines) {
      engineNames = visibleDefaultEngines.split(",");

      for (let engineName of engineNames) {
        // If all engineName values are part of jarNames,
        // then we can use the country specific list, otherwise ignore it.
        // The visibleDefaultEngines string containing the name of an engine we
        // don't ship indicates the server is misconfigured to answer requests
        // from the specific Firefox version we are running, so ignoring the
        // value altogether is safer.
        if (!jarNames.has(engineName)) {
          LOG("_parseListTxt: ignoring visibleDefaultEngines value because " +
              engineName + " is not in the jar engines we have found");
          engineNames = null;
          break;
        }
      }
    }

    // Fallback to building a list based on the :hidden suffixes found in list.txt.
    if (!engineNames) {
      engineNames = [];
      for (let [name, hidden] of jarNames) {
        if (!hidden)
          engineNames.push(name);
      }
    }

    for (let name of engineNames) {
      uris.push(APP_SEARCH_PREFIX + name + ".xml");
    }

    // Store this so that it can be used while writing the cache file.
    this._visibleDefaultEngines = engineNames;
  },


  _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
    LOG("SRCH_SVC_saveSortedEngineList: starting");

    // Set the useDB pref to indicate that from now on we should use the order
    // information stored in the database.
    Services.prefs.setBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", true);

    var engines = this._getSortedEngines(true);

    for (var i = 0; i < engines.length; ++i) {
      engines[i].setAttr("order", i + 1);
    }

    LOG("SRCH_SVC_saveSortedEngineList: done");
  },

  _buildSortedEngineList: function SRCH_SVC_buildSortedEngineList() {
    LOG("_buildSortedEngineList: building list");
    var addedEngines = { };
    this.__sortedEngines = [];
    var engine;

    // If the user has specified a custom engine order, read the order
    // information from the metadata instead of the default prefs.
    if (getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
      LOG("_buildSortedEngineList: using db for order");

      // Flag to keep track of whether or not we need to call _saveSortedEngineList.
      let needToSaveEngineList = false;

      for (let name in this._engines) {
        let engine = this._engines[name];
        var orderNumber = engine.getAttr("order");

        // Since the DB isn't regularly cleared, and engine files may disappear
        // without us knowing, we may already have an engine in this slot. If
        // that happens, we just skip it - it will be added later on as an
        // unsorted engine.
        if (orderNumber && !this.__sortedEngines[orderNumber - 1]) {
          this.__sortedEngines[orderNumber - 1] = engine;
          addedEngines[engine.name] = engine;
        } else {
          // We need to call _saveSortedEngineList so this gets sorted out.
          needToSaveEngineList = true;
        }
      }

      // Filter out any nulls for engines that may have been removed
      var filteredEngines = this.__sortedEngines.filter(function(a) { return !!a; });
      if (this.__sortedEngines.length != filteredEngines.length)
        needToSaveEngineList = true;
      this.__sortedEngines = filteredEngines;

      if (needToSaveEngineList)
        this._saveSortedEngineList();
    } else {
      // The DB isn't being used, so just read the engine order from the prefs
      var i = 0;
      var engineName;
      var prefName;

      try {
        var extras =
          Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");

        for (prefName of extras) {
          engineName = Services.prefs.getCharPref(prefName);

          engine = this._engines[engineName];
          if (!engine || engine.name in addedEngines)
            continue;

          this.__sortedEngines.push(engine);
          addedEngines[engine.name] = engine;
        }
      } catch (e) { }

      let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
      while (true) {
        prefName = prefNameBase + "." + (++i);
        engineName = getLocalizedPref(prefName);
        if (!engineName)
          break;

        engine = this._engines[engineName];
        if (!engine || engine.name in addedEngines)
          continue;

        this.__sortedEngines.push(engine);
        addedEngines[engine.name] = engine;
      }
    }

    // Array for the remaining engines, alphabetically sorted.
    let alphaEngines = [];

    for (let name in this._engines) {
      let engine = this._engines[name];
      if (!(engine.name in addedEngines))
        alphaEngines.push(this._engines[engine.name]);
    }

    let collation = Cc["@mozilla.org/intl/collation-factory;1"]
                      .createInstance(Ci.nsICollationFactory)
                      .CreateCollation();
    const strength = Ci.nsICollation.kCollationCaseInsensitiveAscii;
    let comparator = (a, b) => collation.compareString(strength, a.name, b.name);
    alphaEngines.sort(comparator);
    return this.__sortedEngines = this.__sortedEngines.concat(alphaEngines);
  },

  /**
   * Get a sorted array of engines.
   * @param aWithHidden
   *        True if hidden plugins should be included in the result.
   */
  _getSortedEngines: function SRCH_SVC_getSorted(aWithHidden) {
    if (aWithHidden)
      return this._sortedEngines;

    return this._sortedEngines.filter(function(engine) {
                                        return !engine.hidden;
                                      });
  },

  // nsIBrowserSearchService
  init: function SRCH_SVC_init(observer) {
    LOG("SearchService.init");
    let self = this;
    if (!this._initStarted) {
      TelemetryStopwatch.start("SEARCH_SERVICE_INIT_MS");
      this._initStarted = true;
      (async function task() {
        try {
          // Complete initialization by calling asynchronous initializer.
          await self._asyncInit();
          TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
        } catch (ex) {
          if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
            // No need to pursue asynchronous because synchronous fallback was
            // called and has finished.
            TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
          } else {
            self._initObservers.reject(ex);
            TelemetryStopwatch.cancel("SEARCH_SERVICE_INIT_MS");
          }
        }
      })();
    }
    if (observer) {
      this._initObservers.promise.then(
        function onSuccess() {
          try {
            observer.onInitComplete(self._initRV);
          } catch (e) {
            Cu.reportError(e);
          }
        },
        function onError(aReason) {
          Cu.reportError("Internal error while initializing SearchService: " + aReason);
          observer.onInitComplete(Components.results.NS_ERROR_UNEXPECTED);
        }
      );
    }
  },

  get isInitialized() {
    return gInitialized;
  },

  getEngines: function SRCH_SVC_getEngines(aCount) {
    this._ensureInitialized();
    LOG("getEngines: getting all engines");
    var engines = this._getSortedEngines(true);
    aCount.value = engines.length;
    return engines;
  },

  getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
    this._ensureInitialized();
    LOG("getVisibleEngines: getting all visible engines");
    var engines = this._getSortedEngines(false);
    aCount.value = engines.length;
    return engines;
  },

  getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
    this._ensureInitialized();
    function isDefault(engine) {
      return engine._isDefault;
    }
    var engines = this._sortedEngines.filter(isDefault);
    var engineOrder = {};
    var engineName;
    var i = 1;

    // Build a list of engines which we have ordering information for.
    // We're rebuilding the list here because _sortedEngines contain the
    // current order, but we want the original order.

    // First, look at the "browser.search.order.extra" branch.
    try {
      var extras = Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");

      for (var prefName of extras) {
        engineName = Services.prefs.getCharPref(prefName);

        if (!(engineName in engineOrder))
          engineOrder[engineName] = i++;
      }
    } catch (e) {
      LOG("Getting extra order prefs failed: " + e);
    }

    // Now look through the "browser.search.order" branch.
    let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
    for (var j = 1; ; j++) {
      let prefName = prefNameBase + "." + j;
      engineName = getLocalizedPref(prefName);
      if (!engineName)
        break;

      if (!(engineName in engineOrder))
        engineOrder[engineName] = i++;
    }

    LOG("getDefaultEngines: engineOrder: " + engineOrder.toSource());

    function compareEngines(a, b) {
      var aIdx = engineOrder[a.name];
      var bIdx = engineOrder[b.name];

      if (aIdx && bIdx)
        return aIdx - bIdx;
      if (aIdx)
        return -1;
      if (bIdx)
        return 1;

      return a.name.localeCompare(b.name);
    }
    engines.sort(compareEngines);

    aCount.value = engines.length;
    return engines;
  },

  getEnginesByExtensionID: function SRCH_SVC_getEngines(aExtensionID, aCount) {
    this._ensureInitialized();
    LOG("getEngines: getting all engines for " + aExtensionID);
    var engines = this._getSortedEngines(true).filter(function(engine) {
      return engine._extensionID == aExtensionID;
    });
    aCount.value = engines.length;
    return engines;
  },


  getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
    this._ensureInitialized();
    return this._engines[aEngineName] || null;
  },

  getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
    this._ensureInitialized();
    for (var engineName in this._engines) {
      var engine = this._engines[engineName];
      if (engine && engine.alias == aAlias)
        return engine;
    }
    return null;
  },

  addEngineWithDetails: function SRCH_SVC_addEWD(aName, aIconURL, aAlias,
                                                 aDescription, aMethod,
                                                 aTemplate, aExtensionID) {
    this._ensureInitialized();
    if (!aName)
      FAIL("Invalid name passed to addEngineWithDetails!");
    if (!aMethod)
      FAIL("Invalid method passed to addEngineWithDetails!");
    if (!aTemplate)
      FAIL("Invalid template passed to addEngineWithDetails!");
    if (this._engines[aName])
      FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);

    var engine = new Engine(sanitizeName(aName), false);
    engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
                             aMethod, aTemplate, aExtensionID);
    engine._loadPath = "[other]addEngineWithDetails";
    this._addEngineToStore(engine);
  },

  addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
                                         aConfirm, aCallback) {
    LOG("addEngine: Adding \"" + aEngineURL + "\".");
    this._ensureInitialized();
    try {
      var uri = makeURI(aEngineURL);
      var engine = new Engine(uri, false);
      if (aCallback) {
        engine._installCallback = function(errorCode) {
          try {
            if (errorCode == null)
              aCallback.onSuccess(engine);
            else
              aCallback.onError(errorCode);
          } catch (ex) {
            Cu.reportError("Error invoking addEngine install callback: " + ex);
          }
          // Clear the reference to the callback now that it's been invoked.
          engine._installCallback = null;
        };
      }
      engine._initFromURIAndLoad(uri);
    } catch (ex) {
      // Drop the reference to the callback, if set
      if (engine)
        engine._installCallback = null;
      FAIL("addEngine: Error adding engine:\n" + ex, Cr.NS_ERROR_FAILURE);
    }
    engine._setIcon(aIconURL, false);
    engine._confirm = aConfirm;
  },

  removeEngine: function SRCH_SVC_removeEngine(aEngine) {
    this._ensureInitialized();
    if (!aEngine)
      FAIL("no engine passed to removeEngine!");

    var engineToRemove = null;
    for (var e in this._engines) {
      if (aEngine.wrappedJSObject == this._engines[e])
        engineToRemove = this._engines[e];
    }

    if (!engineToRemove)
      FAIL("removeEngine: Can't find engine to remove!", Cr.NS_ERROR_FILE_NOT_FOUND);

    if (engineToRemove == this.currentEngine) {
      this._currentEngine = null;
    }

    if (engineToRemove._readOnly) {
      // Just hide it (the "hidden" setter will notify) and remove its alias to
      // avoid future conflicts with other engines.
      engineToRemove.hidden = true;
      engineToRemove.alias = null;
    } else {
      // Remove the engine file from disk if we had a legacy file in the profile.
      if (engineToRemove._filePath) {
        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
        file.persistentDescriptor = engineToRemove._filePath;
        if (file.exists()) {
          file.remove(false);
        }
        engineToRemove._filePath = null;
      }

      // Remove the engine from _sortedEngines
      var index = this._sortedEngines.indexOf(engineToRemove);
      if (index == -1)
        FAIL("Can't find engine to remove in _sortedEngines!", Cr.NS_ERROR_FAILURE);
      this.__sortedEngines.splice(index, 1);

      // Remove the engine from the internal store
      delete this._engines[engineToRemove.name];

      // Since we removed an engine, we need to update the preferences.
      this._saveSortedEngineList();
    }
    notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
  },

  moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
    this._ensureInitialized();
    if ((aNewIndex > this._sortedEngines.length) || (aNewIndex < 0))
      FAIL("SRCH_SVC_moveEngine: Index out of bounds!");
    if (!(aEngine instanceof Ci.nsISearchEngine))
      FAIL("SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
    if (aEngine.hidden)
      FAIL("moveEngine: Can't move a hidden engine!", Cr.NS_ERROR_FAILURE);

    var engine = aEngine.wrappedJSObject;

    var currentIndex = this._sortedEngines.indexOf(engine);
    if (currentIndex == -1)
      FAIL("moveEngine: Can't find engine to move!", Cr.NS_ERROR_UNEXPECTED);

    // Our callers only take into account non-hidden engines when calculating
    // aNewIndex, but we need to move it in the array of all engines, so we
    // need to adjust aNewIndex accordingly. To do this, we count the number
    // of hidden engines in the list before the engine that we're taking the
    // place of. We do this by first finding newIndexEngine (the engine that
    // we were supposed to replace) and then iterating through the complete
    // engine list until we reach it, increasing aNewIndex for each hidden
    // engine we find on our way there.
    //
    // This could be further simplified by having our caller pass in
    // newIndexEngine directly instead of aNewIndex.
    var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
    if (!newIndexEngine)
      FAIL("moveEngine: Can't find engine to replace!", Cr.NS_ERROR_UNEXPECTED);

    for (var i = 0; i < this._sortedEngines.length; ++i) {
      if (newIndexEngine == this._sortedEngines[i])
        break;
      if (this._sortedEngines[i].hidden)
        aNewIndex++;
    }

    if (currentIndex == aNewIndex)
      return; // nothing to do!

    // Move the engine
    var movedEngine = this.__sortedEngines.splice(currentIndex, 1)[0];
    this.__sortedEngines.splice(aNewIndex, 0, movedEngine);

    notifyAction(engine, SEARCH_ENGINE_CHANGED);

    // Since we moved an engine, we need to update the preferences.
    this._saveSortedEngineList();
  },

  restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
    this._ensureInitialized();
    for (let name in this._engines) {
      let e = this._engines[name];
      // Unhide all default engines
      if (e.hidden && e._isDefault)
        e.hidden = false;
    }
  },

  get defaultEngine() { return this.currentEngine; },

  set defaultEngine(val) {
    this.currentEngine = val;
  },

  get currentEngine() {
    this._ensureInitialized();
    if (!this._currentEngine) {
      let name = this.getGlobalAttr("current");
      let engine = this.getEngineByName(name);
      if (engine && (this.getGlobalAttr("hash") == getVerificationHash(name) ||
                     engine._isDefault)) {
        // If the current engine is a default one, we can relax the
        // verification hash check to reduce the annoyance for users who
        // backup/sync their profile in custom ways.
        this._currentEngine = engine;
      }
      if (!name)
        this._currentEngine = this.originalDefaultEngine;
    }

    // If the current engine is not set or hidden, we fallback...
    if (!this._currentEngine || this._currentEngine.hidden) {
      // first to the original default engine
      let originalDefault = this.originalDefaultEngine;
      if (!originalDefault || originalDefault.hidden) {
        // then to the first visible engine
        let firstVisible = this._getSortedEngines(false)[0];
        if (firstVisible && !firstVisible.hidden) {
          this.currentEngine = firstVisible;
          return firstVisible;
        }
        // and finally as a last resort we unhide the original default engine.
        if (originalDefault)
          originalDefault.hidden = false;
      }
      if (!originalDefault)
        return null;

      // If the current engine wasn't set or was hidden, we used a fallback
      // to pick a new current engine. As soon as we return it, this new
      // current engine will become user-visible, so we should persist it.
      // by calling the setter.
      this.currentEngine = originalDefault;
    }

    return this._currentEngine;
  },

  set currentEngine(val) {
    this._ensureInitialized();
    // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers),
    // and sometimes we get raw Engine JS objects (callers in this file), so
    // handle both.
    if (!(val instanceof Ci.nsISearchEngine) && !(val instanceof Engine))
      FAIL("Invalid argument passed to currentEngine setter");

    var newCurrentEngine = this.getEngineByName(val.name);
    if (!newCurrentEngine)
      FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);

    if (!newCurrentEngine._isDefault) {
      // If a non default engine is being set as the current engine, ensure
      // its loadPath has a verification hash.
      if (!newCurrentEngine._loadPath)
        newCurrentEngine._loadPath = "[other]unknown";
      let loadPathHash = getVerificationHash(newCurrentEngine._loadPath);
      let currentHash = newCurrentEngine.getAttr("loadPathHash");
      if (!currentHash || currentHash != loadPathHash) {
        newCurrentEngine.setAttr("loadPathHash", loadPathHash);
        notifyAction(newCurrentEngine, SEARCH_ENGINE_CHANGED);
      }
    }

    if (newCurrentEngine == this._currentEngine)
      return;

    this._currentEngine = newCurrentEngine;

    // If we change the default engine in the future, that change should impact
    // users who have switched away from and then back to the build's "default"
    // engine. So clear the user pref when the currentEngine is set to the
    // build's default engine, so that the currentEngine getter falls back to
    // whatever the default is.
    let newName = this._currentEngine.name;
    if (this._currentEngine == this.originalDefaultEngine) {
      newName = "";
    }

    this.setGlobalAttr("current", newName);
    this.setGlobalAttr("hash", getVerificationHash(newName));

    notifyAction(this._currentEngine, SEARCH_ENGINE_DEFAULT);
    notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
  },

  getDefaultEngineInfo() {
    let result = {};

    let engine;
    try {
      engine = this.defaultEngine;
    } catch (e) {
      // The defaultEngine getter will throw if there's no engine at all,
      // which shouldn't happen unless an add-on or a test deleted all of them.
      // Our preferences UI doesn't let users do that.
      Cu.reportError("getDefaultEngineInfo: No default engine");
    }

    if (!engine) {
      result.name = "NONE";
    } else {
      if (engine.name)
        result.name = engine.name;

      result.loadPath = engine._loadPath;

      let origin;
      if (engine._isDefault)
        origin = "default";
      else {
        let currentHash = engine.getAttr("loadPathHash");
        if (!currentHash)
          origin = "unverified";
        else {
          let loadPathHash = getVerificationHash(engine._loadPath);
          origin = currentHash == loadPathHash ? "verified" : "invalid";
        }
      }
      result.origin = origin;

      // For privacy, we only collect the submission URL for default engines...
      let sendSubmissionURL = engine._isDefault;

      // ... or engines sorted by default near the top of the list.
      if (!sendSubmissionURL) {
        let extras =
          Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");

        for (let prefName of extras) {
          try {
            if (result.name == Services.prefs.getCharPref(prefName)) {
              sendSubmissionURL = true;
              break;
            }
          } catch (e) {}
        }

        let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
        let i = 0;
        while (!sendSubmissionURL) {
          let prefName = prefNameBase + "." + (++i);
          let engineName = getLocalizedPref(prefName);
          if (!engineName)
            break;
          if (result.name == engineName) {
            sendSubmissionURL = true;
            break;
          }
        }
      }

      if (sendSubmissionURL) {
        let uri = engine._getURLOfType("text/html")
                        .getSubmission("", engine, "searchbar").uri;
        uri.userPass = ""; // Avoid reporting a username or password.
        result.submissionURL = uri.spec;
      }
    }

    return result;
  },

  _recordEngineTelemetry() {
    Services.telemetry.getHistogramById("SEARCH_SERVICE_ENGINE_COUNT")
            .add(Object.keys(this._engines).length);
  },

  /**
   * This map is built lazily after the available search engines change.  It
   * allows quick parsing of an URL representing a search submission into the
   * search engine name and original terms.
   *
   * The keys are strings containing the domain name and lowercase path of the
   * engine submission, for example "www.google.com/search".
   *
   * The values are objects with these properties:
   * {
   *   engine: The associated nsISearchEngine.
   *   termsParameterName: Name of the URL parameter containing the search
   *                       terms, for example "q".
   * }
   */
  _parseSubmissionMap: null,

  _buildParseSubmissionMap: function SRCH_SVC__buildParseSubmissionMap() {
    LOG("_buildParseSubmissionMap");
    this._parseSubmissionMap = new Map();

    // Used only while building the map, indicates which entries do not refer to
    // the main domain of the engine but to an alternate domain, for example
    // "www.google.fr" for the "www.google.com" search engine.
    let keysOfAlternates = new Set();

    for (let engine of this._sortedEngines) {
      LOG("Processing engine: " + engine.name);

      if (engine.hidden) {
        LOG("Engine is hidden.");
        continue;
      }

      let urlParsingInfo = engine.getURLParsingInfo();
      if (!urlParsingInfo) {
        LOG("Engine does not support URL parsing.");
        continue;
      }

      // Store the same object on each matching map key, as an optimization.
      let mapValueForEngine = {
        engine,
        termsParameterName: urlParsingInfo.termsParameterName,
      };

      let processDomain = (domain, isAlternate) => {
        let key = domain + urlParsingInfo.path;

        // Apply the logic for which main domains take priority over alternate
        // domains, even if they are found later in the ordered engine list.
        let existingEntry = this._parseSubmissionMap.get(key);
        if (!existingEntry) {
          LOG("Adding new entry: " + key);
          if (isAlternate) {
            keysOfAlternates.add(key);
          }
        } else if (!isAlternate && keysOfAlternates.has(key)) {
          LOG("Overriding alternate entry: " + key +
              " (" + existingEntry.engine.name + ")");
          keysOfAlternates.delete(key);
        } else {
          LOG("Keeping existing entry: " + key +
              " (" + existingEntry.engine.name + ")");
          return;
        }

        this._parseSubmissionMap.set(key, mapValueForEngine);
      };

      processDomain(urlParsingInfo.mainDomain, false);
      SearchStaticData.getAlternateDomains(urlParsingInfo.mainDomain)
                      .forEach(d => processDomain(d, true));
    }
  },

  /**
   * Checks to see if any engine has an EngineURL of type URLTYPE_SEARCH_HTML
   * for this request-method, template URL, and query params.
   */
  hasEngineWithURL(method, template, formData) {
    this._ensureInitialized();

    // Quick helper method to ensure formData filtered/sorted for compares.
    let getSortedFormData = data => {
      return data.filter(a => a.name && a.value).sort((a, b) => {
        if (a.name > b.name) {
          return 1;
        } else if (b.name > a.name) {
          return -1;
        } else if (a.value > b.value) {
          return 1;
        }
        return (b.value > a.value) ? -1 : 0;
      });
    };

    // Sanitize method, ensure formData is pre-sorted.
    let methodUpper = method.toUpperCase();
    let sortedFormData = getSortedFormData(formData);
    let sortedFormLength = sortedFormData.length;

    return this._getSortedEngines(false).some(engine => {
      return engine._urls.some(url => {
        // Not an engineURL match if type, method, url, #params don't match.
        if (url.type != URLTYPE_SEARCH_HTML ||
            url.method != methodUpper ||
            url.template != template ||
            url.params.length != sortedFormLength) {
          return false;
        }

        // Ensure engineURL formData is pre-sorted. Then, we're
        // not an engineURL match if any queryParam doesn't compare.
        let sortedParams = getSortedFormData(url.params);
        for (let i = 0; i < sortedFormLength; i++) {
          let formData = sortedFormData[i];
          let param = sortedParams[i];
          if (param.name != formData.name ||
              param.value != formData.value ||
              param.purpose != formData.purpose) {
            return false;
          }
        }
        // Else we're a match.
        return true;
      });
    });
  },

  parseSubmissionURL: function SRCH_SVC_parseSubmissionURL(aURL) {
    this._ensureInitialized();
    LOG("parseSubmissionURL: Parsing \"" + aURL + "\".");

    if (!this._parseSubmissionMap) {
      this._buildParseSubmissionMap();
    }

    // Extract the elements of the provided URL first.
    let soughtKey, soughtQuery;
    try {
      let soughtUrl = Services.io.newURI(aURL).QueryInterface(Ci.nsIURL);

      // Exclude any URL that is not HTTP or HTTPS from the beginning.
      if (soughtUrl.scheme != "http" && soughtUrl.scheme != "https") {
        LOG("The URL scheme is not HTTP or HTTPS.");
        return gEmptyParseSubmissionResult;
      }

      // Reading these URL properties may fail and raise an exception.
      soughtKey = soughtUrl.host + soughtUrl.filePath.toLowerCase();
      soughtQuery = soughtUrl.query;
    } catch (ex) {
      // Errors while parsing the URL or accessing the properties are not fatal.
      LOG("The value does not look like a structured URL.");
      return gEmptyParseSubmissionResult;
    }

    // Look up the domain and path in the map to identify the search engine.
    let mapEntry = this._parseSubmissionMap.get(soughtKey);
    if (!mapEntry) {
      LOG("No engine associated with domain and path: " + soughtKey);
      return gEmptyParseSubmissionResult;
    }

    // Extract the search terms from the parameter, for example "caff%C3%A8"
    // from the URL "https://www.google.com/search?q=caff%C3%A8&client=firefox".
    let encodedTerms = null;
    for (let param of soughtQuery.split("&")) {
      let equalPos = param.indexOf("=");
      if (equalPos != -1 &&
          param.substr(0, equalPos) == mapEntry.termsParameterName) {
        // This is the parameter we are looking for.
        encodedTerms = param.substr(equalPos + 1);
        break;
      }
    }
    if (encodedTerms === null) {
      LOG("Missing terms parameter: " + mapEntry.termsParameterName);
      return gEmptyParseSubmissionResult;
    }

    let length = 0;
    let offset = aURL.indexOf("?") + 1;
    let query = aURL.slice(offset);
    // Iterate a second time over the original input string to determine the
    // correct search term offset and length in the original encoding.
    for (let param of query.split("&")) {
      let equalPos = param.indexOf("=");
      if (equalPos != -1 &&
          param.substr(0, equalPos) == mapEntry.termsParameterName) {
        // This is the parameter we are looking for.
        offset += equalPos + 1;
        length = param.length - equalPos - 1;
        break;
      }
      offset += param.length + 1;
    }

    // Decode the terms using the charset defined in the search engine.
    let terms;
    try {
      terms = gTextToSubURI.UnEscapeAndConvert(
                                       mapEntry.engine.queryCharset,
                                       encodedTerms.replace(/\+/g, " "));
    } catch (ex) {
      // Decoding errors will cause this match to be ignored.
      LOG("Parameter decoding failed. Charset: " +
          mapEntry.engine.queryCharset);
      return gEmptyParseSubmissionResult;
    }

    LOG("Match found. Terms: " + terms);
    return new ParseSubmissionResult(mapEntry.engine, terms, offset, length);
  },

  // nsIObserver
  observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
    switch (aTopic) {
      case SEARCH_ENGINE_TOPIC:
        switch (aVerb) {
          case SEARCH_ENGINE_LOADED:
            var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
            LOG("nsSearchService::observe: Done installation of " + engine.name
                + ".");
            this._addEngineToStore(engine.wrappedJSObject);
            if (engine.wrappedJSObject._useNow) {
              LOG("nsSearchService::observe: setting current");
              this.currentEngine = aEngine;
            }
            // The addition of the engine to the store always triggers an ADDED
            // or a CHANGED notification, that will trigger the task below.
            break;
          case SEARCH_ENGINE_ADDED:
          case SEARCH_ENGINE_CHANGED:
          case SEARCH_ENGINE_REMOVED:
            this.batchTask.disarm();
            this.batchTask.arm();
            // Invalidate the map used to parse URLs to search engines.
            this._parseSubmissionMap = null;
            break;
        }
        break;

      case QUIT_APPLICATION_TOPIC:
        this._removeObservers();
        break;

      case "nsPref:changed":
        if (aVerb == LOCALE_PREF) {
          // Locale changed. Re-init. We rely on observers, because we can't
          // return this promise to anyone.
          this._asyncReInit();
          break;
        }
    }
  },

  // nsITimerCallback
  notify: function SRCH_SVC_notify(aTimer) {
    LOG("_notify: checking for updates");

    if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true))
      return;

    // Our timer has expired, but unfortunately, we can't get any data from it.
    // Therefore, we need to walk our engine-list, looking for expired engines
    var currentTime = Date.now();
    LOG("currentTime: " + currentTime);
    for (let name in this._engines) {
      let engine = this._engines[name].wrappedJSObject;
      if (!engine._hasUpdates)
        continue;

      LOG("checking " + engine.name);

      var expirTime = engine.getAttr("updateexpir");
      LOG("expirTime: " + expirTime + "\nupdateURL: " + engine._updateURL +
          "\niconUpdateURL: " + engine._iconUpdateURL);

      var engineExpired = expirTime <= currentTime;

      if (!expirTime || !engineExpired) {
        LOG("skipping engine");
        continue;
      }

      LOG(engine.name + " has expired");

      engineUpdateService.update(engine);

      // Schedule the next update
      engineUpdateService.scheduleNextUpdate(engine);

    } // end engine iteration
  },

  _addObservers: function SRCH_SVC_addObservers() {
    if (this._observersAdded) {
      // There might be a race between synchronous and asynchronous
      // initialization for which we try to register the observers twice.
      return;
    }
    this._observersAdded = true;

    Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC);
    Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC);

    if (AppConstants.MOZ_BUILD_APP == "mobile/android") {
      Services.prefs.addObserver(LOCALE_PREF, this);
    }

    // The current stage of shutdown. Used to help analyze crash
    // signatures in case of shutdown timeout.
    let shutdownState = {
      step: "Not started",
      latestError: {
        message: undefined,
        stack: undefined
      }
    };
    OS.File.profileBeforeChange.addBlocker(
      "Search service: shutting down",
      () => (async () => {
        if (this._batchTask) {
          shutdownState.step = "Finalizing batched task";
          try {
            await this._batchTask.finalize();
            shutdownState.step = "Batched task finalized";
          } catch (ex) {
            shutdownState.step = "Batched task failed to finalize";

            shutdownState.latestError.message = "" + ex;
            if (ex && typeof ex == "object") {
              shutdownState.latestError.stack = ex.stack || undefined;
            }

            // Ensure that error is reported and that it causes tests
            // to fail.
            Promise.reject(ex);
          }
        }
      })(),

      () => shutdownState
    );
  },
  _observersAdded: false,

  _removeObservers: function SRCH_SVC_removeObservers() {
    Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
    Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);

    if (AppConstants.MOZ_BUILD_APP == "mobile/android") {
      Services.prefs.removeObserver(LOCALE_PREF, this);
    }
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIBrowserSearchService,
    Ci.nsIObserver,
    Ci.nsITimerCallback
  ])
};


const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";

/**
 * Outputs aText to the JavaScript console as well as to stdout, if the search
 * logging pref (browser.search.update.log) is set to true.
 */
function ULOG(aText) {
  if (getBoolPref(BROWSER_SEARCH_PREF + "update.log", false)) {
    dump(SEARCH_UPDATE_LOG_PREFIX + aText + "\n");
    Services.console.logStringMessage(aText);
  }
}

var engineUpdateService = {
  scheduleNextUpdate: function eus_scheduleNextUpdate(aEngine) {
    var interval = aEngine._updateInterval || SEARCH_DEFAULT_UPDATE_INTERVAL;
    var milliseconds = interval * 86400000; // |interval| is in days
    aEngine.setAttr("updateexpir", Date.now() + milliseconds);
  },

  update: function eus_Update(aEngine) {
    let engine = aEngine.wrappedJSObject;
    ULOG("update called for " + aEngine._name);
    if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true) || !engine._hasUpdates)
      return;

    let testEngine = null;
    let updateURL = engine._getURLOfType(URLTYPE_OPENSEARCH);
    let updateURI = (updateURL && updateURL._hasRelation("self")) ?
                     updateURL.getSubmission("", engine).uri :
                     makeURI(engine._updateURL);
    if (updateURI) {
      if (engine._isDefault && !updateURI.schemeIs("https")) {
        ULOG("Invalid scheme for default engine update");
        return;
      }

      ULOG("updating " + engine.name + " from " + updateURI.spec);
      testEngine = new Engine(updateURI, false);
      testEngine._engineToUpdate = engine;
      testEngine._initFromURIAndLoad(updateURI);
    } else
      ULOG("invalid updateURI");

    if (engine._iconUpdateURL) {
      // If we're updating the engine too, use the new engine object,
      // otherwise use the existing engine object.
      (testEngine || engine)._setIcon(engine._iconUpdateURL, true);
    }
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SearchService]);
PK
!<KGt((!components/nsSearchSuggestions.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
                                  "resource://gre/modules/SearchSuggestionController.jsm");

/**
 * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
 * and can collect results for a given search by using this._suggestionController.
 * We do it this way since the AutoCompleteController in Mozilla requires a
 * unique XPCOM Service for every search provider, even if the logic for two
 * providers is identical.
 * @constructor
 */
function SuggestAutoComplete() {
  this._init();
}
SuggestAutoComplete.prototype = {

  _init() {
    this._suggestionController = new SearchSuggestionController(obj => this.onResultsReturned(obj));
    this._suggestionController.maxLocalResults = this._historyLimit;
  },

  get _suggestionLabel() {
    let bundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
    let label = bundle.GetStringFromName("suggestion_label");
    Object.defineProperty(SuggestAutoComplete.prototype, "_suggestionLabel", {value: label});
    return label;
  },

  /**
   * The object implementing nsIAutoCompleteObserver that we notify when
   * we have found results
   * @private
   */
  _listener: null,

  /**
   * Maximum number of history items displayed. This is capped at 7
   * because the primary consumer (Firefox search bar) displays 10 rows
   * by default, and so we want to leave some space for suggestions
   * to be visible.
   */
  _historyLimit: 7,

  /**
   * Callback for handling results from SearchSuggestionController.jsm
   * @private
   */
  onResultsReturned(results) {
    let finalResults = [];
    let finalComments = [];

    // If form history has results, add them to the list.
    for (let i = 0; i < results.local.length; ++i) {
      finalResults.push(results.local[i]);
      finalComments.push("");
    }

    // If there are remote matches, add them.
    if (results.remote.length) {
      // "comments" column values for suggestions starts as empty strings
      let comments = new Array(results.remote.length).fill("", 1);
      comments[0] = this._suggestionLabel;
      // now put the history results above the suggestions
      finalResults = finalResults.concat(results.remote);
      finalComments = finalComments.concat(comments);
    }

    // Notify the FE of our new results
    this.onResultsReady(results.term, finalResults, finalComments, results.formHistoryResult);
  },

  /**
   * Notifies the front end of new results.
   * @param searchString  the user's query string
   * @param results       an array of results to the search
   * @param comments      an array of metadata corresponding to the results
   * @private
   */
  onResultsReady(searchString, results, comments, formHistoryResult) {
    if (this._listener) {
      // Create a copy of the results array to use as labels, since
      // FormAutoCompleteResult doesn't like being passed the same array
      // for both.
      let labels = results.slice();
      let result = new FormAutoCompleteResult(
          searchString,
          Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
          0,
          "",
          results,
          labels,
          comments,
          formHistoryResult);

      this._listener.onSearchResult(this, result);

      // Null out listener to make sure we don't notify it twice
      this._listener = null;
    }
  },

  /**
   * Initiates the search result gathering process. Part of
   * nsIAutoCompleteSearch implementation.
   *
   * @param searchString    the user's query string
   * @param searchParam     unused, "an extra parameter"; even though
   *                        this parameter and the next are unused, pass
   *                        them through in case the form history
   *                        service wants them
   * @param previousResult  unused, a client-cached store of the previous
   *                        generated resultset for faster searching.
   * @param listener        object implementing nsIAutoCompleteObserver which
   *                        we notify when results are ready.
   */
  startSearch(searchString, searchParam, previousResult, listener) {
    // Don't reuse a previous form history result when it no longer applies.
    if (!previousResult)
      this._formHistoryResult = null;

    var formHistorySearchParam = searchParam.split("|")[0];

    // Receive the information about the privacy mode of the window to which
    // this search box belongs.  The front-end's search.xml bindings passes this
    // information in the searchParam parameter.  The alternative would have
    // been to modify nsIAutoCompleteSearch to add an argument to startSearch
    // and patch all of autocomplete to be aware of this, but the searchParam
    // argument is already an opaque argument, so this solution is hopefully
    // less hackish (although still gross.)
    var privacyMode = (searchParam.split("|")[1] == "private");

    // Start search immediately if possible, otherwise once the search
    // service is initialized
    if (Services.search.isInitialized) {
      this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode);
      return;
    }

    Services.search.init(aResult => {
      if (!Components.isSuccessCode(aResult)) {
        Cu.reportError("Could not initialize search service, bailing out: " + aResult);
        return;
      }
      this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode);
    });
  },

  /**
   * Actual implementation of search.
   */
  _triggerSearch(searchString, searchParam, listener, privacyMode) {
    this._listener = listener;
    this._suggestionController.fetch(searchString,
                                     privacyMode,
                                     Services.search.currentEngine);
  },

  /**
   * Ends the search result gathering process. Part of nsIAutoCompleteSearch
   * implementation.
   */
  stopSearch() {
    this._suggestionController.stop();
  },

  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
                                         Ci.nsIAutoCompleteObserver])
};

/**
 * SearchSuggestAutoComplete is a service implementation that handles suggest
 * results specific to web searches.
 * @constructor
 */
function SearchSuggestAutoComplete() {
  // This calls _init() in the parent class (SuggestAutoComplete) via the
  // prototype, below.
  this._init();
}
SearchSuggestAutoComplete.prototype = {
  classID: Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}"),
  __proto__: SuggestAutoComplete.prototype,
  serviceURL: ""
};

var component = [SearchSuggestAutoComplete];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<components/nsSidebar.js/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

// File extension for Sherlock search plugin description files
const SHERLOCK_FILE_EXT_REGEXP = /\.src$/i;

function nsSidebar() {
}

nsSidebar.prototype = {
  init(window) {
    this.window = window;
    try {
      this.mm = window.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDocShell)
                      .QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIContentFrameMessageManager);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  // Deprecated, only left here to avoid breaking old browser-detection scripts.
  addSearchEngine(engineURL, iconURL, suggestedTitle, suggestedCategory) {
    if (SHERLOCK_FILE_EXT_REGEXP.test(engineURL)) {
      Cu.reportError("Installing Sherlock search plugins is no longer supported.");
      return;
    }

    this.AddSearchProvider(engineURL);
  },

  // This function implements window.external.AddSearchProvider().
  // The capitalization, although nonstandard here, is to match other browsers'
  // APIs and is therefore important.
  AddSearchProvider(engineURL) {
    if (!this.mm) {
      Cu.reportError(`Installing a search provider from this context is not currently supported: ${Error().stack}.`);
      return;
    }

    this.mm.sendAsyncMessage("Search:AddEngine", {
      pageURL: this.window.document.documentURIObject.spec,
      engineURL
    });
  },

  // This function exists to implement window.external.IsSearchProviderInstalled(),
  // for compatibility with other browsers.  The function has been deprecated
  // and so will not be implemented.
  IsSearchProviderInstalled(engineURL) {
    return 0;
  },

  classID: Components.ID("{22117140-9c6e-11d3-aaf1-00805f8a4905}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSidebar]);
PK
!<Ik
k
components/nsLoginInfo.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");


function nsLoginInfo() {}

nsLoginInfo.prototype = {

  classID: Components.ID("{0f2f347c-1e4f-40cc-8efd-792dea70a85e}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo, Ci.nsILoginMetaInfo]),

  //
  // nsILoginInfo interfaces...
  //

  hostname: null,
  formSubmitURL: null,
  httpRealm: null,
  username: null,
  password: null,
  usernameField: null,
  passwordField: null,

  init(aHostname, aFormSubmitURL, aHttpRealm,
                  aUsername, aPassword,
                  aUsernameField, aPasswordField) {
    this.hostname      = aHostname;
    this.formSubmitURL = aFormSubmitURL;
    this.httpRealm     = aHttpRealm;
    this.username      = aUsername;
    this.password      = aPassword;
    this.usernameField = aUsernameField;
    this.passwordField = aPasswordField;
  },

  matches(aLogin, ignorePassword) {
    return LoginHelper.doLoginsMatch(this, aLogin, {
      ignorePassword,
    });
  },

  equals(aLogin) {
    if (this.hostname != aLogin.hostname ||
        this.formSubmitURL != aLogin.formSubmitURL ||
        this.httpRealm != aLogin.httpRealm ||
        this.username != aLogin.username ||
        this.password != aLogin.password ||
        this.usernameField != aLogin.usernameField ||
        this.passwordField != aLogin.passwordField)
      return false;

    return true;
  },

  clone() {
    let clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
                createInstance(Ci.nsILoginInfo);
    clone.init(this.hostname, this.formSubmitURL, this.httpRealm,
               this.username, this.password,
               this.usernameField, this.passwordField);

    // Copy nsILoginMetaInfo props
    clone.QueryInterface(Ci.nsILoginMetaInfo);
    clone.guid = this.guid;
    clone.timeCreated = this.timeCreated;
    clone.timeLastUsed = this.timeLastUsed;
    clone.timePasswordChanged = this.timePasswordChanged;
    clone.timesUsed = this.timesUsed;

    return clone;
  },

  //
  // nsILoginMetaInfo interfaces...
  //

  guid: null,
  timeCreated: null,
  timeLastUsed: null,
  timePasswordChanged: null,
  timesUsed: null

}; // end of nsLoginInfo implementation

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsLoginInfo]);
PK
!<
0DDcomponents/nsLoginManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

const PERMISSION_SAVE_LOGINS = "login-saving";

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/LoginManagerContent.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
                                  "resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
                                  "resource://gre/modules/InsecurePasswordUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "log", () => {
  let logger = LoginHelper.createLogger("nsLoginManager");
  return logger;
});

const MS_PER_DAY = 24 * 60 * 60 * 1000;

function LoginManager() {
  this.init();
}

LoginManager.prototype = {

  classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManager,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsIInterfaceRequestor]),
  getInterface(aIID) {
    if (aIID.equals(Ci.mozIStorageConnection) && this._storage) {
      let ir = this._storage.QueryInterface(Ci.nsIInterfaceRequestor);
      return ir.getInterface(aIID);
    }

    if (aIID.equals(Ci.nsIVariant)) {
      // Allows unwrapping the JavaScript object for regression tests.
      return this;
    }

    throw new Components.Exception("Interface not available", Cr.NS_ERROR_NO_INTERFACE);
  },


  /* ---------- private members ---------- */


  __formFillService: null, // FormFillController, for username autocompleting
  get _formFillService() {
    if (!this.__formFillService) {
      this.__formFillService = Cc["@mozilla.org/satchel/form-fill-controller;1"].
                               getService(Ci.nsIFormFillController);
    }
    return this.__formFillService;
  },


  _storage: null, // Storage component which contains the saved logins
  _prefBranch: null, // Preferences service
  _remember: true,  // mirrors signon.rememberSignons preference


  /**
   * Initialize the Login Manager. Automatically called when service
   * is created.
   *
   * Note: Service created in /browser/base/content/browser.js,
   *       delayedStartup()
   */
  init() {

    // Cache references to current |this| in utility objects
    this._observer._pwmgr            = this;

    // Preferences. Add observer so we get notified of changes.
    this._prefBranch = Services.prefs.getBranch("signon.");
    this._prefBranch.addObserver("rememberSignons", this._observer);

    this._remember = this._prefBranch.getBoolPref("rememberSignons");
    this._autoCompleteLookupPromise = null;

    // Form submit observer checks forms for new logins and pw changes.
    Services.obs.addObserver(this._observer, "xpcom-shutdown");

    if (Services.appinfo.processType ===
        Services.appinfo.PROCESS_TYPE_DEFAULT) {
      Services.obs.addObserver(this._observer, "passwordmgr-storage-replace");

      // Initialize storage so that asynchronous data loading can start.
      this._initStorage();
    }

    Services.obs.addObserver(this._observer, "gather-telemetry");
  },


  _initStorage() {
    let contractID;
    if (AppConstants.platform == "android") {
      contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
    } else {
      contractID = "@mozilla.org/login-manager/storage/json;1";
    }
    try {
      let catMan = Cc["@mozilla.org/categorymanager;1"].
                   getService(Ci.nsICategoryManager);
      contractID = catMan.getCategoryEntry("login-manager-storage",
                                           "nsILoginManagerStorage");
      log.debug("Found alternate nsILoginManagerStorage with contract ID:", contractID);
    } catch (e) {
      log.debug("No alternate nsILoginManagerStorage registered");
    }

    this._storage = Cc[contractID].
                    createInstance(Ci.nsILoginManagerStorage);
    this.initializationPromise = this._storage.initialize();
  },


  /* ---------- Utility objects ---------- */


  /**
   * Internal utility object, implements the nsIObserver interface.
   * Used to receive notification for: form submission, preference changes.
   */
  _observer: {
    _pwmgr: null,

    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsISupportsWeakReference]),

    // nsIObserver
    observe(subject, topic, data) {
      if (topic == "nsPref:changed") {
        var prefName = data;
        log.debug("got change to", prefName, "preference");

        if (prefName == "rememberSignons") {
          this._pwmgr._remember =
              this._pwmgr._prefBranch.getBoolPref("rememberSignons");
        } else {
          log.debug("Oops! Pref not handled, change ignored.");
        }
      } else if (topic == "xpcom-shutdown") {
        delete this._pwmgr.__formFillService;
        delete this._pwmgr._storage;
        delete this._pwmgr._prefBranch;
        this._pwmgr = null;
      } else if (topic == "passwordmgr-storage-replace") {
        (async () => {
          await this._pwmgr._storage.terminate();
          this._pwmgr._initStorage();
          await this._pwmgr.initializationPromise;
          Services.obs.notifyObservers(null,
                       "passwordmgr-storage-replace-complete");
        })();
      } else if (topic == "gather-telemetry") {
        // When testing, the "data" parameter is a string containing the
        // reference time in milliseconds for time-based statistics.
        this._pwmgr._gatherTelemetry(data ? parseInt(data)
                                          : new Date().getTime());
      } else {
        log.debug("Oops! Unexpected notification:", topic);
      }
    }
  },

  /**
   * Collects statistics about the current logins and settings. The telemetry
   * histograms used here are not accumulated, but are reset each time this
   * function is called, since it can be called multiple times in a session.
   *
   * This function might also not be called at all in the current session.
   *
   * @param referenceTimeMs
   *        Current time used to calculate time-based statistics, expressed as
   *        the number of milliseconds since January 1, 1970, 00:00:00 UTC.
   *        This is set to a fake value during unit testing.
   */
  _gatherTelemetry(referenceTimeMs) {
    function clearAndGetHistogram(histogramId) {
      let histogram = Services.telemetry.getHistogramById(histogramId);
      histogram.clear();
      return histogram;
    }

    clearAndGetHistogram("PWMGR_BLOCKLIST_NUM_SITES").add(
      this.getAllDisabledHosts({}).length
    );
    clearAndGetHistogram("PWMGR_NUM_SAVED_PASSWORDS").add(
      this.countLogins("", "", "")
    );
    clearAndGetHistogram("PWMGR_NUM_HTTPAUTH_PASSWORDS").add(
      this.countLogins("", null, "")
    );

    // This is a boolean histogram, and not a flag, because we don't want to
    // record any value if _gatherTelemetry is not called.
    clearAndGetHistogram("PWMGR_SAVING_ENABLED").add(this._remember);

    // Don't try to get logins if MP is enabled, since we don't want to show a MP prompt.
    if (!this.isLoggedIn) {
      return;
    }

    let logins = this.getAllLogins({});

    let usernamePresentHistogram = clearAndGetHistogram("PWMGR_USERNAME_PRESENT");
    let loginLastUsedDaysHistogram = clearAndGetHistogram("PWMGR_LOGIN_LAST_USED_DAYS");

    let hostnameCount = new Map();
    for (let login of logins) {
      usernamePresentHistogram.add(!!login.username);

      let hostname = login.hostname;
      hostnameCount.set(hostname, (hostnameCount.get(hostname) || 0 ) + 1);

      login.QueryInterface(Ci.nsILoginMetaInfo);
      let timeLastUsedAgeMs = referenceTimeMs - login.timeLastUsed;
      if (timeLastUsedAgeMs > 0) {
        loginLastUsedDaysHistogram.add(
          Math.floor(timeLastUsedAgeMs / MS_PER_DAY)
        );
      }
    }

    let passwordsCountHistogram = clearAndGetHistogram("PWMGR_NUM_PASSWORDS_PER_HOSTNAME");
    for (let count of hostnameCount.values()) {
      passwordsCountHistogram.add(count);
    }
  },





  /* ---------- Primary Public interfaces ---------- */




  /**
   * @type Promise
   * This promise is resolved when initialization is complete, and is rejected
   * in case the asynchronous part of initialization failed.
   */
  initializationPromise: null,


  /**
   * Add a new login to login storage.
   */
  addLogin(login) {
    // Sanity check the login
    if (login.hostname == null || login.hostname.length == 0) {
      throw new Error("Can't add a login with a null or empty hostname.");
    }

    // For logins w/o a username, set to "", not null.
    if (login.username == null) {
      throw new Error("Can't add a login with a null username.");
    }

    if (login.password == null || login.password.length == 0) {
      throw new Error("Can't add a login with a null or empty password.");
    }

    if (login.formSubmitURL || login.formSubmitURL == "") {
      // We have a form submit URL. Can't have a HTTP realm.
      if (login.httpRealm != null) {
        throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
      }
    } else if (login.httpRealm) {
      // We have a HTTP realm. Can't have a form submit URL.
      if (login.formSubmitURL != null) {
        throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
      }
    } else {
      // Need one or the other!
      throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
    }


    // Look for an existing entry.
    var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
                                 login.httpRealm);

    if (logins.some(l => login.matches(l, true))) {
      throw new Error("This login already exists.");
    }

    log.debug("Adding login");
    return this._storage.addLogin(login);
  },

  /**
   * Remove the specified login from the stored logins.
   */
  removeLogin(login) {
    log.debug("Removing login");
    return this._storage.removeLogin(login);
  },


  /**
   * Change the specified login to match the new login.
   */
  modifyLogin(oldLogin, newLogin) {
    log.debug("Modifying login");
    return this._storage.modifyLogin(oldLogin, newLogin);
  },


  /**
   * Get a dump of all stored logins. Used by the login manager UI.
   *
   * @param count - only needed for XPCOM.
   * @return {nsILoginInfo[]} - If there are no logins, the array is empty.
   */
  getAllLogins(count) {
    log.debug("Getting a list of all logins");
    return this._storage.getAllLogins(count);
  },


  /**
   * Remove all stored logins.
   */
  removeAllLogins() {
    log.debug("Removing all logins");
    this._storage.removeAllLogins();
  },

  /**
   * Get a list of all origins for which logins are disabled.
   *
   * @param {Number} count - only needed for XPCOM.
   *
   * @return {String[]} of disabled origins. If there are no disabled origins,
   *                    the array is empty.
   */
  getAllDisabledHosts(count) {
    log.debug("Getting a list of all disabled origins");

    let disabledHosts = [];
    let enumerator = Services.perms.enumerator;

    while (enumerator.hasMoreElements()) {
      let perm = enumerator.getNext();
      if (perm.type == PERMISSION_SAVE_LOGINS && perm.capability == Services.perms.DENY_ACTION) {
        disabledHosts.push(perm.principal.URI.prePath);
      }
    }

    if (count)
      count.value = disabledHosts.length; // needed for XPCOM

    log.debug("getAllDisabledHosts: returning", disabledHosts.length, "disabled hosts.");
    return disabledHosts;
  },


  /**
   * Search for the known logins for entries matching the specified criteria.
   */
  findLogins(count, origin, formActionOrigin, httpRealm) {
    log.debug("Searching for logins matching origin:", origin,
              "formActionOrigin:", formActionOrigin, "httpRealm:", httpRealm);

    return this._storage.findLogins(count, origin, formActionOrigin,
                                    httpRealm);
  },


  /**
   * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   * JavaScript object and decrypt the results.
   *
   * @return {nsILoginInfo[]} which are decrypted.
   */
  searchLogins(count, matchData) {
    log.debug("Searching for logins");

    matchData.QueryInterface(Ci.nsIPropertyBag2);
    if (!matchData.hasKey("guid")) {
      if (!matchData.hasKey("hostname")) {
        log.warn("searchLogins: A `hostname` is recommended");
      }

      if (!matchData.hasKey("formSubmitURL") && !matchData.hasKey("httpRealm")) {
        log.warn("searchLogins: `formSubmitURL` or `httpRealm` is recommended");
      }
    }

    return this._storage.searchLogins(count, matchData);
  },


  /**
   * Search for the known logins for entries matching the specified criteria,
   * returns only the count.
   */
  countLogins(origin, formActionOrigin, httpRealm) {
    log.debug("Counting logins matching origin:", origin,
              "formActionOrigin:", formActionOrigin, "httpRealm:", httpRealm);

    return this._storage.countLogins(origin, formActionOrigin, httpRealm);
  },


  get uiBusy() {
    return this._storage.uiBusy;
  },


  get isLoggedIn() {
    return this._storage.isLoggedIn;
  },


  /**
   * Check to see if user has disabled saving logins for the origin.
   */
  getLoginSavingEnabled(origin) {
    log.debug("Checking if logins to", origin, "can be saved.");
    if (!this._remember) {
      return false;
    }

    let uri = Services.io.newURI(origin);
    return Services.perms.testPermission(uri, PERMISSION_SAVE_LOGINS) != Services.perms.DENY_ACTION;
  },


  /**
   * Enable or disable storing logins for the specified origin.
   */
  setLoginSavingEnabled(origin, enabled) {
    // Throws if there are bogus values.
    LoginHelper.checkHostnameValue(origin);

    let uri = Services.io.newURI(origin);
    if (enabled) {
      Services.perms.remove(uri, PERMISSION_SAVE_LOGINS);
    } else {
      Services.perms.add(uri, PERMISSION_SAVE_LOGINS, Services.perms.DENY_ACTION);
    }

    log.debug("Login saving for", origin, "now enabled?", enabled);
    LoginHelper.notifyStorageChanged(enabled ? "hostSavingEnabled" : "hostSavingDisabled", origin);
  },

  /**
   * Yuck. This is called directly by satchel:
   * nsFormFillController::StartSearch()
   * [toolkit/components/satchel/nsFormFillController.cpp]
   *
   * We really ought to have a simple way for code to register an
   * auto-complete provider, and not have satchel calling pwmgr directly.
   */
  autoCompleteSearchAsync(aSearchString, aPreviousResult,
                          aElement, aCallback) {
    // aPreviousResult is an nsIAutoCompleteResult, aElement is
    // nsIDOMHTMLInputElement

    let form = LoginFormFactory.createFromField(aElement);
    let isSecure = InsecurePasswordUtils.isFormSecure(form);
    let isPasswordField = aElement.type == "password";

    let completeSearch = (autoCompleteLookupPromise, { logins, messageManager }) => {
      // If the search was canceled before we got our
      // results, don't bother reporting them.
      if (this._autoCompleteLookupPromise !== autoCompleteLookupPromise) {
        return;
      }

      this._autoCompleteLookupPromise = null;
      let results = new UserAutoCompleteResult(aSearchString, logins, {
        messageManager,
        isSecure,
        isPasswordField,
      });
      aCallback.onSearchCompletion(results);
    };

    if (isPasswordField && aSearchString) {
      // Return empty result on password fields with password already filled.
      let acLookupPromise = this._autoCompleteLookupPromise = Promise.resolve({ logins: [] });
      acLookupPromise.then(completeSearch.bind(this, acLookupPromise));
      return;
    }

    if (!this._remember) {
      let acLookupPromise = this._autoCompleteLookupPromise = Promise.resolve({ logins: [] });
      acLookupPromise.then(completeSearch.bind(this, acLookupPromise));
      return;
    }

    log.debug("AutoCompleteSearch invoked. Search is:", aSearchString);

    let previousResult;
    if (aPreviousResult) {
      previousResult = { searchString: aPreviousResult.searchString,
                         logins: aPreviousResult.wrappedJSObject.logins };
    } else {
      previousResult = null;
    }

    let rect = BrowserUtils.getElementBoundingScreenRect(aElement);
    let acLookupPromise = this._autoCompleteLookupPromise =
      LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult,
                                                   aElement, rect);
    acLookupPromise.then(completeSearch.bind(this, acLookupPromise))
                             .catch(Cu.reportError);
  },

  stopSearch() {
    this._autoCompleteLookupPromise = null;
  },
}; // end of LoginManager implementation

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);
PK
!<e3$components/nsLoginManagerPrompter.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
const { PromptUtils } = Cu.import("resource://gre/modules/SharedPromptUtils.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");

const LoginInfo =
      Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
                             "nsILoginInfo", "init");

const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";

/**
 * Constants for password prompt telemetry.
 * Mirrored in mobile/android/components/LoginManagerPrompter.js */
const PROMPT_DISPLAYED = 0;

const PROMPT_ADD_OR_UPDATE = 1;
const PROMPT_NOTNOW = 2;
const PROMPT_NEVER = 3;

/**
 * Implements nsIPromptFactory
 *
 * Invoked by [toolkit/components/prompts/src/nsPrompter.js]
 */
function LoginManagerPromptFactory() {
  Services.obs.addObserver(this, "quit-application-granted", true);
  Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
  Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true);
}

LoginManagerPromptFactory.prototype = {

  classID: Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]),

  _asyncPrompts: {},
  _asyncPromptInProgress: false,

  observe(subject, topic, data) {
    this.log("Observed: " + topic);
    if (topic == "quit-application-granted") {
      this._cancelPendingPrompts();
    } else if (topic == "passwordmgr-crypto-login") {
      // Start processing the deferred prompters.
      this._doAsyncPrompt();
    } else if (topic == "passwordmgr-crypto-loginCanceled") {
      // User canceled a Master Password prompt, so go ahead and cancel
      // all pending auth prompts to avoid nagging over and over.
      this._cancelPendingPrompts();
    }
  },

  getPrompt(aWindow, aIID) {
    var prompt = new LoginManagerPrompter().QueryInterface(aIID);
    prompt.init(aWindow, this);
    return prompt;
  },

  _doAsyncPrompt() {
    if (this._asyncPromptInProgress) {
      this.log("_doAsyncPrompt bypassed, already in progress");
      return;
    }

    // Find the first prompt key we have in the queue
    var hashKey = null;
    for (hashKey in this._asyncPrompts)
      break;

    if (!hashKey) {
      this.log("_doAsyncPrompt:run bypassed, no prompts in the queue");
      return;
    }

    // If login manger has logins for this host, defer prompting if we're
    // already waiting on a master password entry.
    var prompt = this._asyncPrompts[hashKey];
    var prompter = prompt.prompter;
    var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo);
    var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0);
    if (!hasLogins && LoginHelper.schemeUpgrades && hostname.startsWith("https://")) {
      let httpHostname = hostname.replace(/^https:\/\//, "http://");
      hasLogins = (prompter._pwmgr.countLogins(httpHostname, null, httpRealm) > 0);
    }
    if (hasLogins && prompter._pwmgr.uiBusy) {
      this.log("_doAsyncPrompt:run bypassed, master password UI busy");
      return;
    }

    // Allow only a limited number of authentication dialogs when they are all
    // canceled by the user.
    var cancelationCounter = (prompter._browser && prompter._browser.canceledAuthenticationPromptCounter) || { count: 0, id: 0 };
    if (prompt.channel) {
      var httpChannel = prompt.channel.QueryInterface(Ci.nsIHttpChannel);
      if (httpChannel) {
        var windowId = httpChannel.topLevelContentWindowId;
        if (windowId != cancelationCounter.id) {
          // window has been reloaded or navigated, reset the counter
          cancelationCounter = { count: 0, id: windowId };
        }
      }
    }

    var self = this;

    var runnable = {
      cancel: false,
      run() {
        var ok = false;
        if (!this.cancel) {
          try {
            self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
            ok = prompter.promptAuth(prompt.channel,
                                     prompt.level,
                                     prompt.authInfo);
          } catch (e) {
            if (e instanceof Components.Exception &&
                e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
              self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
            } else {
              Components.utils.reportError("LoginManagerPrompter: " +
                                           "_doAsyncPrompt:run: " + e + "\n");
            }
          }

          delete self._asyncPrompts[hashKey];
          prompt.inProgress = false;
          self._asyncPromptInProgress = false;

          if (ok) {
            cancelationCounter.count = 0;
          } else {
            cancelationCounter.count++;
          }
          if (prompter._browser) {
            prompter._browser.canceledAuthenticationPromptCounter = cancelationCounter;
          }
        }

        for (var consumer of prompt.consumers) {
          if (!consumer.callback)
            // Not having a callback means that consumer didn't provide it
            // or canceled the notification
            continue;

          self.log("Calling back to " + consumer.callback + " ok=" + ok);
          try {
            if (ok) {
              consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
            } else {
              consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
            }
          } catch (e) { /* Throw away exceptions caused by callback */ }
        }
        self._doAsyncPrompt();
      }
    };

    var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");

    this.log("cancelationCounter =", cancelationCounter);
    if (cancelDialogLimit && cancelationCounter.count >= cancelDialogLimit) {
      this.log("Blocking auth dialog, due to exceeding dialog bloat limit");
      delete this._asyncPrompts[hashKey];

      // just make the runnable cancel all consumers
      runnable.cancel = true;
    } else {
      this._asyncPromptInProgress = true;
      prompt.inProgress = true;
    }

    Services.tm.dispatchToMainThread(runnable);
    this.log("_doAsyncPrompt:run dispatched");
  },


  _cancelPendingPrompts() {
    this.log("Canceling all pending prompts...");
    var asyncPrompts = this._asyncPrompts;
    this.__proto__._asyncPrompts = {};

    for (var hashKey in asyncPrompts) {
      let prompt = asyncPrompts[hashKey];
      // Watch out! If this prompt is currently prompting, let it handle
      // notifying the callbacks of success/failure, since it's already
      // asking the user for input. Reusing a callback can be crashy.
      if (prompt.inProgress) {
        this.log("skipping a prompt in progress");
        continue;
      }

      for (var consumer of prompt.consumers) {
        if (!consumer.callback)
          continue;

        this.log("Canceling async auth prompt callback " + consumer.callback);
        try {
          consumer.callback.onAuthCancelled(consumer.context, true);
        } catch (e) { /* Just ignore exceptions from the callback */ }
      }
    }
  },
}; // end of LoginManagerPromptFactory implementation

XPCOMUtils.defineLazyGetter(this.LoginManagerPromptFactory.prototype, "log", () => {
  let logger = LoginHelper.createLogger("Login PromptFactory");
  return logger.log.bind(logger);
});




/* ==================== LoginManagerPrompter ==================== */




/**
 * Implements interfaces for prompting the user to enter/save/change auth info.
 *
 * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
 *
 * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
 * (eg HTTP Authenticate, FTP login).
 *
 * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
 * found in HTML forms.
 */
function LoginManagerPrompter() {}

LoginManagerPrompter.prototype = {

  classID: Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
                                          Ci.nsIAuthPrompt2,
                                          Ci.nsILoginManagerPrompter]),

  _factory: null,
  _chromeWindow: null,
  _browser: null,
  _opener: null,

  __pwmgr: null, // Password Manager service
  get _pwmgr() {
    if (!this.__pwmgr)
      this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
                     getService(Ci.nsILoginManager);
    return this.__pwmgr;
  },

  __promptService: null, // Prompt service for user interaction
  get _promptService() {
    if (!this.__promptService)
      this.__promptService =
          Cc["@mozilla.org/embedcomp/prompt-service;1"].
          getService(Ci.nsIPromptService2);
    return this.__promptService;
  },


  __strBundle: null, // String bundle for L10N
  get _strBundle() {
    if (!this.__strBundle) {
      var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
                       getService(Ci.nsIStringBundleService);
      this.__strBundle = bunService.createBundle(
                  "chrome://passwordmgr/locale/passwordmgr.properties");
      if (!this.__strBundle)
        throw new Error("String bundle for Login Manager not present!");
    }

    return this.__strBundle;
  },


  __ellipsis: null,
  get _ellipsis() {
    if (!this.__ellipsis) {
      this.__ellipsis = "\u2026";
      try {
        this.__ellipsis = Services.prefs.getComplexValue(
                            "intl.ellipsis", Ci.nsIPrefLocalizedString).data;
      } catch (e) { }
    }
    return this.__ellipsis;
  },


  // Whether we are in private browsing mode
  get _inPrivateBrowsing() {
    if (this._chromeWindow) {
      return PrivateBrowsingUtils.isWindowPrivate(this._chromeWindow);
    }
    // If we don't that we're in private browsing mode if the caller did
    // not provide a window.  The callers which really care about this
    // will indeed pass down a window to us, and for those who don't,
    // we can just assume that we don't want to save the entered login
    // information.
    this.log("We have no chromeWindow so assume we're in a private context");
    return true;
  },




  /* ---------- nsIAuthPrompt prompts ---------- */


  /**
   * Wrapper around the prompt service prompt. Saving random fields here
   * doesn't really make sense and therefore isn't implemented.
   */
  prompt(aDialogTitle, aText, aPasswordRealm,
                    aSavePassword, aDefaultText, aResult) {
    if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
      throw new Components.Exception("prompt only supports SAVE_PASSWORD_NEVER",
                                     Cr.NS_ERROR_NOT_IMPLEMENTED);

    this.log("===== prompt() called =====");

    if (aDefaultText) {
      aResult.value = aDefaultText;
    }

    return this._promptService.prompt(this._chromeWindow,
           aDialogTitle, aText, aResult, null, {});
  },


  /**
   * Looks up a username and password in the database. Will prompt the user
   * with a dialog, even if a username and password are found.
   */
  promptUsernameAndPassword(aDialogTitle, aText, aPasswordRealm,
                                       aSavePassword, aUsername, aPassword) {
    this.log("===== promptUsernameAndPassword() called =====");

    if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
      throw new Components.Exception("promptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
                                     Cr.NS_ERROR_NOT_IMPLEMENTED);

    var selectedLogin = null;
    var checkBox = { value: false };
    var checkBoxLabel = null;
    var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);

    // If hostname is null, we can't save this login.
    if (hostname) {
      var canRememberLogin;
      if (this._inPrivateBrowsing)
        canRememberLogin = false;
      else
        canRememberLogin = (aSavePassword ==
                            Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
                           this._pwmgr.getLoginSavingEnabled(hostname);

      // if checkBoxLabel is null, the checkbox won't be shown at all.
      if (canRememberLogin)
        checkBoxLabel = this._getLocalizedString("rememberPassword");

      // Look for existing logins.
      var foundLogins = this._pwmgr.findLogins({}, hostname, null,
                                  realm);

      // XXX Like the original code, we can't deal with multiple
      // account selection. (bug 227632)
      if (foundLogins.length > 0) {
        selectedLogin = foundLogins[0];

        // If the caller provided a username, try to use it. If they
        // provided only a password, this will try to find a password-only
        // login (or return null if none exists).
        if (aUsername.value)
          selectedLogin = this._repickSelectedLogin(foundLogins,
                                                    aUsername.value);

        if (selectedLogin) {
          checkBox.value = true;
          aUsername.value = selectedLogin.username;
          // If the caller provided a password, prefer it.
          if (!aPassword.value)
            aPassword.value = selectedLogin.password;
        }
      }
    }

    var ok = this._promptService.promptUsernameAndPassword(this._chromeWindow,
                aDialogTitle, aText, aUsername, aPassword,
                checkBoxLabel, checkBox);

    if (!ok || !checkBox.value || !hostname)
      return ok;

    if (!aPassword.value) {
      this.log("No password entered, so won't offer to save.");
      return ok;
    }

    // XXX We can't prompt with multiple logins yet (bug 227632), so
    // the entered login might correspond to an existing login
    // other than the one we originally selected.
    selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);

    // If we didn't find an existing login, or if the username
    // changed, save as a new login.
    let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                   createInstance(Ci.nsILoginInfo);
    newLogin.init(hostname, null, realm,
                  aUsername.value, aPassword.value, "", "");
    if (!selectedLogin) {
      // add as new
      this.log("New login seen for " + realm);
      this._pwmgr.addLogin(newLogin);
    } else if (aPassword.value != selectedLogin.password) {
      // update password
      this.log("Updating password for  " + realm);
      this._updateLogin(selectedLogin, newLogin);
    } else {
      this.log("Login unchanged, no further action needed.");
      this._updateLogin(selectedLogin);
    }

    return ok;
  },


  /**
   * If a password is found in the database for the password realm, it is
   * returned straight away without displaying a dialog.
   *
   * If a password is not found in the database, the user will be prompted
   * with a dialog with a text field and ok/cancel buttons. If the user
   * allows it, then the password will be saved in the database.
   */
  promptPassword(aDialogTitle, aText, aPasswordRealm,
                            aSavePassword, aPassword) {
    this.log("===== promptPassword called() =====");

    if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
      throw new Components.Exception("promptPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
                                     Cr.NS_ERROR_NOT_IMPLEMENTED);

    var checkBox = { value: false };
    var checkBoxLabel = null;
    var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);

    username = decodeURIComponent(username);

    // If hostname is null, we can't save this login.
    if (hostname && !this._inPrivateBrowsing) {
      var canRememberLogin = (aSavePassword ==
                              Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
                             this._pwmgr.getLoginSavingEnabled(hostname);

      // if checkBoxLabel is null, the checkbox won't be shown at all.
      if (canRememberLogin)
        checkBoxLabel = this._getLocalizedString("rememberPassword");

      if (!aPassword.value) {
        // Look for existing logins.
        var foundLogins = this._pwmgr.findLogins({}, hostname, null,
                                          realm);

        // XXX Like the original code, we can't deal with multiple
        // account selection (bug 227632). We can deal with finding the
        // account based on the supplied username - but in this case we'll
        // just return the first match.
        for (var i = 0; i < foundLogins.length; ++i) {
          if (foundLogins[i].username == username) {
            aPassword.value = foundLogins[i].password;
            // wallet returned straight away, so this mimics that code
            return true;
          }
        }
      }
    }

    var ok = this._promptService.promptPassword(this._chromeWindow, aDialogTitle,
                                                aText, aPassword,
                                                checkBoxLabel, checkBox);

    if (ok && checkBox.value && hostname && aPassword.value) {
      var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                     createInstance(Ci.nsILoginInfo);
      newLogin.init(hostname, null, realm, username,
                    aPassword.value, "", "");

      this.log("New login seen for " + realm);

      this._pwmgr.addLogin(newLogin);
    }

    return ok;
  },

  /* ---------- nsIAuthPrompt helpers ---------- */


  /**
   * Given aRealmString, such as "http://user@example.com/foo", returns an
   * array of:
   *   - the formatted hostname
   *   - the realm (hostname + path)
   *   - the username, if present
   *
   * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
   * channels, e.g. "example.com:80 (httprealm)", null is returned for all
   * arguments to let callers know the login can't be saved because we don't
   * know whether it's http or https.
   */
  _getRealmInfo(aRealmString) {
    var httpRealm = /^.+ \(.+\)$/;
    if (httpRealm.test(aRealmString))
      return [null, null, null];

    var uri = Services.io.newURI(aRealmString);
    var pathname = "";

    if (uri.path != "/")
      pathname = uri.path;

    var formattedHostname = this._getFormattedHostname(uri);

    return [formattedHostname, formattedHostname + pathname, uri.username];
  },

  /* ---------- nsIAuthPrompt2 prompts ---------- */




  /**
   * Implementation of nsIAuthPrompt2.
   *
   * @param {nsIChannel} aChannel
   * @param {int}        aLevel
   * @param {nsIAuthInformation} aAuthInfo
   */
  promptAuth(aChannel, aLevel, aAuthInfo) {
    var selectedLogin = null;
    var checkbox = { value: false };
    var checkboxLabel = null;
    var epicfail = false;
    var canAutologin = false;
    var notifyObj;
    var foundLogins;

    try {
      this.log("===== promptAuth called =====");

      // If the user submits a login but it fails, we need to remove the
      // notification bar that was displayed. Conveniently, the user will
      // be prompted for authentication again, which brings us here.
      this._removeLoginNotifications();

      var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);

      // Looks for existing logins to prefill the prompt with.
      foundLogins = LoginHelper.searchLoginsWithObject({
        hostname,
        httpRealm,
        schemeUpgrades: LoginHelper.schemeUpgrades,
      });
      this.log("found", foundLogins.length, "matching logins.");
      let resolveBy = [
        "scheme",
        "timePasswordChanged",
      ];
      foundLogins = LoginHelper.dedupeLogins(foundLogins, ["username"], resolveBy, hostname);
      this.log(foundLogins.length, "matching logins remain after deduping");

      // XXX Can't select from multiple accounts yet. (bug 227632)
      if (foundLogins.length > 0) {
        selectedLogin = foundLogins[0];
        this._SetAuthInfo(aAuthInfo, selectedLogin.username,
                                     selectedLogin.password);

        // Allow automatic proxy login
        if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
            !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
            Services.prefs.getBoolPref("signon.autologin.proxy") &&
            !this._inPrivateBrowsing) {

          this.log("Autologin enabled, skipping auth prompt.");
          canAutologin = true;
        }

        checkbox.value = true;
      }

      var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
      if (this._inPrivateBrowsing)
        canRememberLogin = false;

      // if checkboxLabel is null, the checkbox won't be shown at all.
      notifyObj = this._getPopupNote() || this._getNotifyBox();
      if (canRememberLogin && !notifyObj)
        checkboxLabel = this._getLocalizedString("rememberPassword");
    } catch (e) {
      // Ignore any errors and display the prompt anyway.
      epicfail = true;
      Components.utils.reportError("LoginManagerPrompter: " +
          "Epic fail in promptAuth: " + e + "\n");
    }

    var ok = canAutologin;
    if (!ok) {
      if (this._chromeWindow)
        PromptUtils.fireDialogEvent(this._chromeWindow, "DOMWillOpenModalDialog", this._browser);
      ok = this._promptService.promptAuth(this._chromeWindow,
                                          aChannel, aLevel, aAuthInfo,
                                          checkboxLabel, checkbox);
    }

    // If there's a notification box, use it to allow the user to
    // determine if the login should be saved. If there isn't a
    // notification box, only save the login if the user set the
    // checkbox to do so.
    var rememberLogin = notifyObj ? canRememberLogin : checkbox.value;
    if (!ok || !rememberLogin || epicfail)
      return ok;

    try {
      var [username, password] = this._GetAuthInfo(aAuthInfo);

      if (!password) {
        this.log("No password entered, so won't offer to save.");
        return ok;
      }

      // XXX We can't prompt with multiple logins yet (bug 227632), so
      // the entered login might correspond to an existing login
      // other than the one we originally selected.
      selectedLogin = this._repickSelectedLogin(foundLogins, username);

      // If we didn't find an existing login, or if the username
      // changed, save as a new login.
      let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                     createInstance(Ci.nsILoginInfo);
      newLogin.init(hostname, null, httpRealm,
                    username, password, "", "");
      if (!selectedLogin) {
        this.log("New login seen for " + username +
                 " @ " + hostname + " (" + httpRealm + ")");

        if (notifyObj)
          this._showSaveLoginNotification(notifyObj, newLogin);
        else
          this._pwmgr.addLogin(newLogin);
      } else if (password != selectedLogin.password) {
        this.log("Updating password for " + username +
                 " @ " + hostname + " (" + httpRealm + ")");
        if (notifyObj)
          this._showChangeLoginNotification(notifyObj,
                                            selectedLogin, newLogin);
        else
          this._updateLogin(selectedLogin, newLogin);
      } else {
        this.log("Login unchanged, no further action needed.");
        this._updateLogin(selectedLogin);
      }
    } catch (e) {
      Components.utils.reportError("LoginManagerPrompter: " +
          "Fail2 in promptAuth: " + e + "\n");
    }

    return ok;
  },

  asyncPromptAuth(aChannel, aCallback, aContext, aLevel, aAuthInfo) {
    var cancelable = null;

    try {
      this.log("===== asyncPromptAuth called =====");

      // If the user submits a login but it fails, we need to remove the
      // notification bar that was displayed. Conveniently, the user will
      // be prompted for authentication again, which brings us here.
      this._removeLoginNotifications();

      cancelable = this._newAsyncPromptConsumer(aCallback, aContext);

      var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);

      var hashKey = aLevel + "|" + hostname + "|" + httpRealm;
      this.log("Async prompt key = " + hashKey);
      var asyncPrompt = this._factory._asyncPrompts[hashKey];
      if (asyncPrompt) {
        this.log("Prompt bound to an existing one in the queue, callback = " + aCallback);
        asyncPrompt.consumers.push(cancelable);
        return cancelable;
      }

      this.log("Adding new prompt to the queue, callback = " + aCallback);
      asyncPrompt = {
        consumers: [cancelable],
        channel: aChannel,
        authInfo: aAuthInfo,
        level: aLevel,
        inProgress: false,
        prompter: this
      };

      this._factory._asyncPrompts[hashKey] = asyncPrompt;
      this._factory._doAsyncPrompt();
    } catch (e) {
      Components.utils.reportError("LoginManagerPrompter: " +
          "asyncPromptAuth: " + e + "\nFalling back to promptAuth\n");
      // Fail the prompt operation to let the consumer fall back
      // to synchronous promptAuth method
      throw e;
    }

    return cancelable;
  },




  /* ---------- nsILoginManagerPrompter prompts ---------- */


  init(aWindow = null, aFactory = null) {
    if (!aWindow) {
      // There may be no applicable window e.g. in a Sandbox or JSM.
      this._chromeWindow = null;
      this._browser = null;
    } else if (aWindow instanceof Ci.nsIDOMChromeWindow) {
      this._chromeWindow = aWindow;
      // needs to be set explicitly using setBrowser
      this._browser = null;
    } else {
      let {win, browser} = this._getChromeWindow(aWindow);
      this._chromeWindow = win;
      this._browser = browser;
    }
    this._opener = null;
    this._factory = aFactory || null;

    this.log("===== initialized =====");
  },

  set browser(aBrowser) {
    this._browser = aBrowser;
  },

  set opener(aOpener) {
    this._opener = aOpener;
  },

  promptToSavePassword(aLogin) {
    this.log("promptToSavePassword");
    var notifyObj = this._getPopupNote() || this._getNotifyBox();
    if (notifyObj)
      this._showSaveLoginNotification(notifyObj, aLogin);
    else
      this._showSaveLoginDialog(aLogin);
  },

  /**
   * Displays a notification bar.
   */
  _showLoginNotification(aNotifyBox, aName, aText, aButtons) {
    var oldBar = aNotifyBox.getNotificationWithValue(aName);
    const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;

    this.log("Adding new " + aName + " notification bar");
    var newBar = aNotifyBox.appendNotification(
                            aText, aName, "",
                            priority, aButtons);

    // The page we're going to hasn't loaded yet, so we want to persist
    // across the first location change.
    newBar.persistence++;

    // Sites like Gmail perform a funky redirect dance before you end up
    // at the post-authentication page. I don't see a good way to
    // heuristically determine when to ignore such location changes, so
    // we'll try ignoring location changes based on a time interval.
    newBar.timeout = Date.now() + 20000; // 20 seconds

    if (oldBar) {
      this.log("(...and removing old " + aName + " notification bar)");
      aNotifyBox.removeNotification(oldBar);
    }
  },

  /**
   * Displays the PopupNotifications.jsm doorhanger for password save or change.
   *
   * @param {nsILoginInfo} login
   *        Login to save or change. For changes, this login should contain the
   *        new password.
   * @param {string} type
   *        This is "password-save" or "password-change" depending on the
   *        original notification type. This is used for telemetry and tests.
   */
  _showLoginCaptureDoorhanger(login, type) {
    let { browser } = this._getNotifyWindow();

    let saveMsgNames = {
      prompt: login.username === "" ? "saveLoginMsgNoUser"
                                    : "saveLoginMsg",
      buttonLabel: "saveLoginButtonAllow.label",
      buttonAccessKey: "saveLoginButtonAllow.accesskey",
      secondaryButtonLabel: "saveLoginButtonDeny.label",
      secondaryButtonAccessKey: "saveLoginButtonDeny.accesskey",
    };

    let changeMsgNames = {
      prompt: login.username === "" ? "updateLoginMsgNoUser"
                                    : "updateLoginMsg",
      buttonLabel: "updateLoginButtonText",
      buttonAccessKey: "updateLoginButtonAccessKey",
      secondaryButtonLabel: "updateLoginButtonDeny.label",
      secondaryButtonAccessKey: "updateLoginButtonDeny.accesskey",
    };

    let initialMsgNames = type == "password-save" ? saveMsgNames
                                                  : changeMsgNames;

    let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
    let brandShortName = brandBundle.GetStringFromName("brandShortName");
    let host = this._getShortDisplayHost(login.hostname);
    let promptMsg = type == "password-save" ? this._getLocalizedString(saveMsgNames.prompt, [brandShortName, host])
                                            : this._getLocalizedString(changeMsgNames.prompt);

    let histogramName = type == "password-save" ? "PWMGR_PROMPT_REMEMBER_ACTION"
                                                : "PWMGR_PROMPT_UPDATE_ACTION";
    let histogram = Services.telemetry.getHistogramById(histogramName);
    histogram.add(PROMPT_DISPLAYED);

    let chromeDoc = browser.ownerDocument;

    let currentNotification;

    let updateButtonStatus = (element) => {
      let mainActionButton = element.button;
      // Disable the main button inside the menu-button if the password field is empty.
      if (login.password.length == 0) {
        mainActionButton.setAttribute("disabled", true);
        chromeDoc.getElementById("password-notification-password")
                 .classList.add("popup-notification-invalid-input");
      } else {
        mainActionButton.removeAttribute("disabled");
        chromeDoc.getElementById("password-notification-password")
                 .classList.remove("popup-notification-invalid-input");
      }
    };

    let updateButtonLabel = () => {
      let foundLogins = LoginHelper.searchLoginsWithObject({
        formSubmitURL: login.formSubmitURL,
        hostname: login.hostname,
        httpRealm: login.httpRealm,
        schemeUpgrades: LoginHelper.schemeUpgrades,
      });

      let logins = this._filterUpdatableLogins(login, foundLogins);
      let msgNames = (logins.length == 0) ? saveMsgNames : changeMsgNames;

      // Update the label based on whether this will be a new login or not.
      let label = this._getLocalizedString(msgNames.buttonLabel);
      let accessKey = this._getLocalizedString(msgNames.buttonAccessKey);

      // Update the labels for the next time the panel is opened.
      currentNotification.mainAction.label = label;
      currentNotification.mainAction.accessKey = accessKey;

      // Update the labels in real time if the notification is displayed.
      let element = [...currentNotification.owner.panel.childNodes]
                    .find(n => n.notification == currentNotification);
      if (element) {
        element.setAttribute("buttonlabel", label);
        element.setAttribute("buttonaccesskey", accessKey);
        updateButtonStatus(element);
      }
    };

    let writeDataToUI = () => {
      // setAttribute is used since the <textbox> binding may not be attached yet.
      chromeDoc.getElementById("password-notification-username")
               .setAttribute("placeholder", usernamePlaceholder);
      chromeDoc.getElementById("password-notification-username")
               .setAttribute("value", login.username);

      let toggleCheckbox = chromeDoc.getElementById("password-notification-visibilityToggle");
      toggleCheckbox.removeAttribute("checked");
      let passwordField = chromeDoc.getElementById("password-notification-password");
      // Ensure the type is reset so the field is masked.
      passwordField.setAttribute("type", "password");
      passwordField.setAttribute("value", login.password);
      updateButtonLabel();
    };

    let readDataFromUI = () => {
      login.username =
        chromeDoc.getElementById("password-notification-username").value;
      login.password =
        chromeDoc.getElementById("password-notification-password").value;
    };

    let onInput = () => {
      readDataFromUI();
      updateButtonLabel();
    };

    let onVisibilityToggle = (commandEvent) => {
      let passwordField = chromeDoc.getElementById("password-notification-password");
      // Gets the caret position before changing the type of the textbox
      let selectionStart = passwordField.selectionStart;
      let selectionEnd = passwordField.selectionEnd;
      passwordField.setAttribute("type", commandEvent.target.checked ? "" : "password");
      if (!passwordField.hasAttribute("focused")) {
        return;
      }
      passwordField.selectionStart = selectionStart;
      passwordField.selectionEnd = selectionEnd;
    };

    let persistData = () => {
      let foundLogins = LoginHelper.searchLoginsWithObject({
        formSubmitURL: login.formSubmitURL,
        hostname: login.hostname,
        httpRealm: login.httpRealm,
        schemeUpgrades: LoginHelper.schemeUpgrades,
      });

      let logins = this._filterUpdatableLogins(login, foundLogins);

      if (logins.length == 0) {
        // The original login we have been provided with might have its own
        // metadata, but we don't want it propagated to the newly created one.
        Services.logins.addLogin(new LoginInfo(login.hostname,
                                               login.formSubmitURL,
                                               login.httpRealm,
                                               login.username,
                                               login.password,
                                               login.usernameField,
                                               login.passwordField));
      } else if (logins.length == 1) {
        if (logins[0].password == login.password &&
            logins[0].username == login.username) {
          // We only want to touch the login's use count and last used time.
          this._updateLogin(logins[0]);
        } else {
          this._updateLogin(logins[0], login);
        }
      } else {
        Cu.reportError("Unexpected match of multiple logins.");
      }
    };

    // The main action is the "Save" or "Update" button.
    let mainAction = {
      label: this._getLocalizedString(initialMsgNames.buttonLabel),
      accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
      callback: () => {
        histogram.add(PROMPT_ADD_OR_UPDATE);
        if (histogramName == "PWMGR_PROMPT_REMEMBER_ACTION") {
          Services.obs.notifyObservers(null, "LoginStats:NewSavedPassword");
        }
        readDataFromUI();
        persistData();
        browser.focus();
      }
    };

    let secondaryActions = [{
      label: this._getLocalizedString(initialMsgNames.secondaryButtonLabel),
      accessKey: this._getLocalizedString(initialMsgNames.secondaryButtonAccessKey),
      callback: () => {
        histogram.add(PROMPT_NOTNOW);
        browser.focus();
      }
    }];
    // Include a "Never for this site" button when saving a new password.
    if (type == "password-save") {
      secondaryActions.push({
        label: this._getLocalizedString("notifyBarNeverRememberButtonText2"),
        accessKey: this._getLocalizedString("notifyBarNeverRememberButtonAccessKey2"),
        callback: () => {
          histogram.add(PROMPT_NEVER);
          Services.logins.setLoginSavingEnabled(login.hostname, false);
          browser.focus();
        }
      });
    }

    let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
    let togglePasswordLabel = this._getLocalizedString("togglePasswordLabel");
    let togglePasswordAccessKey = this._getLocalizedString("togglePasswordAccessKey2");

    this._getPopupNote().show(
      browser,
      "password",
      promptMsg,
      "password-notification-icon",
      mainAction,
      secondaryActions,
      {
        timeout: Date.now() + 10000,
        persistWhileVisible: true,
        passwordNotificationType: type,
        hideClose: !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton"),
        eventCallback(topic) {
          switch (topic) {
            case "showing":
              currentNotification = this;
              chromeDoc.getElementById("password-notification-password")
                       .removeAttribute("focused");
              chromeDoc.getElementById("password-notification-username")
                       .removeAttribute("focused");
              chromeDoc.getElementById("password-notification-username")
                       .addEventListener("input", onInput);
              chromeDoc.getElementById("password-notification-password")
                       .addEventListener("input", onInput);
              let toggleBtn = chromeDoc.getElementById("password-notification-visibilityToggle");

              if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
                toggleBtn.addEventListener("command", onVisibilityToggle);
                toggleBtn.setAttribute("label", togglePasswordLabel);
                toggleBtn.setAttribute("accesskey", togglePasswordAccessKey);
                toggleBtn.setAttribute("hidden", LoginHelper.isMasterPasswordSet());
              }
              if (this.wasDismissed) {
                chromeDoc.getElementById("password-notification-visibilityToggle")
                         .setAttribute("hidden", true);
              }
              break;
            case "shown":
              writeDataToUI();
              break;
            case "dismissed":
              this.wasDismissed = true;
              readDataFromUI();
              // Fall through.
            case "removed":
              currentNotification = null;
              chromeDoc.getElementById("password-notification-username")
                       .removeEventListener("input", onInput);
              chromeDoc.getElementById("password-notification-password")
                       .removeEventListener("input", onInput);
              chromeDoc.getElementById("password-notification-visibilityToggle")
                       .removeEventListener("command", onVisibilityToggle);
              break;
          }
          return false;
        },
      }
    );
  },

  /**
   * Displays a notification bar or a popup notification, to allow the user
   * to save the specified login. This allows the user to see the results of
   * their login, and only save a login which they know worked.
   *
   * @param aNotifyObj
   *        A notification box or a popup notification.
   * @param aLogin
   *        The login captured from the form.
   */
  _showSaveLoginNotification(aNotifyObj, aLogin) {
    // Ugh. We can't use the strings from the popup window, because they
    // have the access key marked in the string (eg "Mo&zilla"), along
    // with some weird rules for handling access keys that do not occur
    // in the string, for L10N. See commonDialog.js's setLabelForNode().
    var neverButtonText =
          this._getLocalizedString("notifyBarNeverRememberButtonText2");
    var neverButtonAccessKey =
          this._getLocalizedString("notifyBarNeverRememberButtonAccessKey2");
    var rememberButtonText =
          this._getLocalizedString("notifyBarRememberPasswordButtonText");
    var rememberButtonAccessKey =
          this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey");

    var displayHost = this._getShortDisplayHost(aLogin.hostname);
    var notificationText = this._getLocalizedString(
                                  "rememberPasswordMsgNoUsername",
                                  [displayHost]);

    // The callbacks in |buttons| have a closure to access the variables
    // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
    // without a getService() call.
    var pwmgr = this._pwmgr;

    // Notification is a PopupNotification
    if (aNotifyObj == this._getPopupNote()) {
      this._showLoginCaptureDoorhanger(aLogin, "password-save");
    } else {
      var notNowButtonText =
            this._getLocalizedString("notifyBarNotNowButtonText");
      var notNowButtonAccessKey =
            this._getLocalizedString("notifyBarNotNowButtonAccessKey");
      var buttons = [
        // "Remember" button
        {
          label:     rememberButtonText,
          accessKey: rememberButtonAccessKey,
          popup:     null,
          callback(aNotifyObj, aButton) {
            pwmgr.addLogin(aLogin);
          }
        },

        // "Never for this site" button
        {
          label:     neverButtonText,
          accessKey: neverButtonAccessKey,
          popup:     null,
          callback(aNotifyObj, aButton) {
            pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
          }
        },

        // "Not now" button
        {
          label:     notNowButtonText,
          accessKey: notNowButtonAccessKey,
          popup:     null,
          callback() { /* NOP */ }
        }
      ];

      this._showLoginNotification(aNotifyObj, "password-save",
                                  notificationText, buttons);
    }

    Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
  },

  _removeLoginNotifications() {
    var popupNote = this._getPopupNote();
    if (popupNote)
      popupNote = popupNote.getNotification("password");
    if (popupNote)
      popupNote.remove();

    var notifyBox = this._getNotifyBox();
    if (notifyBox) {
      var oldBar = notifyBox.getNotificationWithValue("password-save");
      if (oldBar) {
        this.log("Removing save-password notification bar.");
        notifyBox.removeNotification(oldBar);
      }

      oldBar = notifyBox.getNotificationWithValue("password-change");
      if (oldBar) {
        this.log("Removing change-password notification bar.");
        notifyBox.removeNotification(oldBar);
      }
    }
  },


  /**
   * Called when we detect a new login in a form submission,
   * asks the user what to do.
   */
  _showSaveLoginDialog(aLogin) {
    const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
        (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
        (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
        (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);

    var displayHost = this._getShortDisplayHost(aLogin.hostname);

    var dialogText;
    if (aLogin.username) {
      var displayUser = this._sanitizeUsername(aLogin.username);
      dialogText = this._getLocalizedString(
                           "rememberPasswordMsg",
                           [displayUser, displayHost]);
    } else {
      dialogText = this._getLocalizedString(
                           "rememberPasswordMsgNoUsername",
                           [displayHost]);

    }
    var dialogTitle        = this._getLocalizedString(
                                    "savePasswordTitle");
    var neverButtonText    = this._getLocalizedString(
                                    "neverForSiteButtonText");
    var rememberButtonText = this._getLocalizedString(
                                    "rememberButtonText");
    var notNowButtonText   = this._getLocalizedString(
                                    "notNowButtonText");

    this.log("Prompting user to save/ignore login");
    var userChoice = this._promptService.confirmEx(this._chromeWindow,
                                        dialogTitle, dialogText,
                                        buttonFlags, rememberButtonText,
                                        notNowButtonText, neverButtonText,
                                        null, {});
    //  Returns:
    //   0 - Save the login
    //   1 - Ignore the login this time
    //   2 - Never save logins for this site
    if (userChoice == 2) {
      this.log("Disabling " + aLogin.hostname + " logins by request.");
      this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
    } else if (userChoice == 0) {
      this.log("Saving login for " + aLogin.hostname);
      this._pwmgr.addLogin(aLogin);
    } else {
      // userChoice == 1 --> just ignore the login.
      this.log("Ignoring login.");
    }

    Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
  },


  /**
   * Called when we think we detect a password or username change for
   * an existing login, when the form being submitted contains multiple
   * password fields.
   *
   * @param {nsILoginInfo} aOldLogin
   *                       The old login we may want to update.
   * @param {nsILoginInfo} aNewLogin
   *                       The new login from the page form.
   */
  promptToChangePassword(aOldLogin, aNewLogin) {
    this.log("promptToChangePassword");
    let notifyObj = this._getPopupNote() || this._getNotifyBox();

    if (notifyObj) {
      this._showChangeLoginNotification(notifyObj, aOldLogin,
                                        aNewLogin);
    } else {
      this._showChangeLoginDialog(aOldLogin, aNewLogin);
    }
  },

  /**
   * Shows the Change Password notification bar or popup notification.
   *
   * @param aNotifyObj
   *        A notification box or a popup notification.
   *
   * @param aOldLogin
   *        The stored login we want to update.
   *
   * @param aNewLogin
   *        The login object with the changes we want to make.
   */
  _showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin) {
    var changeButtonText =
          this._getLocalizedString("notifyBarUpdateButtonText");
    var changeButtonAccessKey =
          this._getLocalizedString("notifyBarUpdateButtonAccessKey");

    // We reuse the existing message, even if it expects a username, until we
    // switch to the final terminology in bug 1144856.
    var displayHost = this._getShortDisplayHost(aOldLogin.hostname);
    var notificationText = this._getLocalizedString("updatePasswordMsg",
                                                    [displayHost]);

    // The callbacks in |buttons| have a closure to access the variables
    // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
    // without a getService() call.
    var self = this;

    // Notification is a PopupNotification
    if (aNotifyObj == this._getPopupNote()) {
      aOldLogin.hostname = aNewLogin.hostname;
      aOldLogin.formSubmitURL = aNewLogin.formSubmitURL;
      aOldLogin.password = aNewLogin.password;
      aOldLogin.username = aNewLogin.username;
      this._showLoginCaptureDoorhanger(aOldLogin, "password-change");
    } else {
      var dontChangeButtonText =
            this._getLocalizedString("notifyBarDontChangeButtonText");
      var dontChangeButtonAccessKey =
            this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
      var buttons = [
        // "Yes" button
        {
          label:     changeButtonText,
          accessKey: changeButtonAccessKey,
          popup:     null,
          callback(aNotifyObj, aButton) {
            self._updateLogin(aOldLogin, aNewLogin);
          }
        },

        // "No" button
        {
          label:     dontChangeButtonText,
          accessKey: dontChangeButtonAccessKey,
          popup:     null,
          callback(aNotifyObj, aButton) {
            // do nothing
          }
        }
      ];

      this._showLoginNotification(aNotifyObj, "password-change",
                                  notificationText, buttons);
    }

    let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
    Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
  },


  /**
   * Shows the Change Password dialog.
   */
  _showChangeLoginDialog(aOldLogin, aNewLogin) {
    const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;

    var dialogText;
    if (aOldLogin.username)
      dialogText  = this._getLocalizedString(
                              "updatePasswordMsg",
                              [aOldLogin.username]);
    else
      dialogText  = this._getLocalizedString(
                              "updatePasswordMsgNoUser");

    var dialogTitle = this._getLocalizedString(
                                "passwordChangeTitle");

    // returns 0 for yes, 1 for no.
    var ok = !this._promptService.confirmEx(this._chromeWindow,
                            dialogTitle, dialogText, buttonFlags,
                            null, null, null,
                            null, {});
    if (ok) {
      this.log("Updating password for user " + aOldLogin.username);
      this._updateLogin(aOldLogin, aNewLogin);
    }

    let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
    Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
  },


  /**
   * Called when we detect a password change in a form submission, but we
   * don't know which existing login (username) it's for. Asks the user
   * to select a username and confirm the password change.
   *
   * Note: The caller doesn't know the username for aNewLogin, so this
   *       function fills in .username and .usernameField with the values
   *       from the login selected by the user.
   *
   * Note; XPCOM stupidity: |count| is just |logins.length|.
   */
  promptToChangePasswordWithUsernames(logins, count, aNewLogin) {
    this.log("promptToChangePasswordWithUsernames with count:", count);

    var usernames = logins.map(l => l.username);
    var dialogText  = this._getLocalizedString("userSelectText2");
    var dialogTitle = this._getLocalizedString("passwordChangeTitle");
    var selectedIndex = { value: null };

    // If user selects ok, outparam.value is set to the index
    // of the selected username.
    var ok = this._promptService.select(this._chromeWindow,
                            dialogTitle, dialogText,
                            usernames.length, usernames,
                            selectedIndex);
    if (ok) {
      // Now that we know which login to use, modify its password.
      var selectedLogin = logins[selectedIndex.value];
      this.log("Updating password for user " + selectedLogin.username);
      var newLoginWithUsername = Cc["@mozilla.org/login-manager/loginInfo;1"].
                     createInstance(Ci.nsILoginInfo);
      newLoginWithUsername.init(aNewLogin.hostname,
                                aNewLogin.formSubmitURL, aNewLogin.httpRealm,
                                selectedLogin.username, aNewLogin.password,
                                selectedLogin.userNameField, aNewLogin.passwordField);
      this._updateLogin(selectedLogin, newLoginWithUsername);
    }
  },




  /* ---------- Internal Methods ---------- */




  _updateLogin(login, aNewLogin = null) {
    var now = Date.now();
    var propBag = Cc["@mozilla.org/hash-property-bag;1"].
                  createInstance(Ci.nsIWritablePropertyBag);
    if (aNewLogin) {
      propBag.setProperty("formSubmitURL", aNewLogin.formSubmitURL);
      propBag.setProperty("hostname", aNewLogin.hostname);
      propBag.setProperty("password", aNewLogin.password);
      propBag.setProperty("username", aNewLogin.username);
      // Explicitly set the password change time here (even though it would
      // be changed automatically), to ensure that it's exactly the same
      // value as timeLastUsed.
      propBag.setProperty("timePasswordChanged", now);
    }
    propBag.setProperty("timeLastUsed", now);
    propBag.setProperty("timesUsedIncrement", 1);
    this._pwmgr.modifyLogin(login, propBag);
  },

  /**
   * Given a content DOM window, returns the chrome window and browser it's in.
   */
  _getChromeWindow(aWindow) {
    let windows = Services.wm.getEnumerator(null);
    while (windows.hasMoreElements()) {
      let win = windows.getNext();
      let browser = win.gBrowser.getBrowserForContentWindow(aWindow);
      if (browser) {
        return { win, browser };
      }
    }
    return null;
  },

  _getNotifyWindow() {
    // Some sites pop up a temporary login window, which disappears
    // upon submission of credentials. We want to put the notification
    // bar in the opener window if this seems to be happening.
    if (this._opener) {
      let chromeDoc = this._chromeWindow.document.documentElement;

      // Check to see if the current window was opened with chrome
      // disabled, and if so use the opener window. But if the window
      // has been used to visit other pages (ie, has a history),
      // assume it'll stick around and *don't* use the opener.
      if (chromeDoc.getAttribute("chromehidden") && !this._browser.canGoBack) {
        this.log("Using opener window for notification bar.");
        return this._getChromeWindow(this._opener);
      }
    }

    return { win: this._chromeWindow, browser: this._browser };
  },

  /**
   * Returns the popup notification to this prompter,
   * or null if there isn't one available.
   */
  _getPopupNote() {
    let popupNote = null;

    try {
      let { win: notifyWin } = this._getNotifyWindow();

      // .wrappedJSObject needed here -- see bug 422974 comment 5.
      popupNote = notifyWin.wrappedJSObject.PopupNotifications;
    } catch (e) {
      this.log("Popup notifications not available on window");
    }

    return popupNote;
  },


  /**
   * Returns the notification box to this prompter, or null if there isn't
   * a notification box available.
   */
  _getNotifyBox() {
    let notifyBox = null;

    try {
      let { win: notifyWin } = this._getNotifyWindow();

      // .wrappedJSObject needed here -- see bug 422974 comment 5.
      notifyBox = notifyWin.wrappedJSObject.getNotificationBox(notifyWin);
    } catch (e) {
      this.log("Notification bars not available on window");
    }

    return notifyBox;
  },


  /**
   * The user might enter a login that isn't the one we prefilled, but
   * is the same as some other existing login. So, pick a login with a
   * matching username, or return null.
   */
  _repickSelectedLogin(foundLogins, username) {
    for (var i = 0; i < foundLogins.length; i++)
      if (foundLogins[i].username == username)
        return foundLogins[i];
    return null;
  },


  /**
   * Can be called as:
   *   _getLocalizedString("key1");
   *   _getLocalizedString("key2", ["arg1"]);
   *   _getLocalizedString("key3", ["arg1", "arg2"]);
   *   (etc)
   *
   * Returns the localized string for the specified key,
   * formatted if required.
   *
   */
  _getLocalizedString(key, formatArgs) {
    if (formatArgs)
      return this._strBundle.formatStringFromName(
                                  key, formatArgs, formatArgs.length);
    return this._strBundle.GetStringFromName(key);
  },


  /**
   * Sanitizes the specified username, by stripping quotes and truncating if
   * it's too long. This helps prevent an evil site from messing with the
   * "save password?" prompt too much.
   */
  _sanitizeUsername(username) {
    if (username.length > 30) {
      username = username.substring(0, 30);
      username += this._ellipsis;
    }
    return username.replace(/['"]/g, "");
  },


  /**
   * The aURI parameter may either be a string uri, or an nsIURI instance.
   *
   * Returns the hostname to use in a nsILoginInfo object (for example,
   * "http://example.com").
   */
  _getFormattedHostname(aURI) {
    let uri;
    if (aURI instanceof Ci.nsIURI) {
      uri = aURI;
    } else {
      uri = Services.io.newURI(aURI);
    }

    return uri.scheme + "://" + uri.hostPort;
  },


  /**
   * Converts a login's hostname field (a URL) to a short string for
   * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
   * "ftp://www.site.co.uk" --> "site.co.uk".
   */
  _getShortDisplayHost(aURIString) {
    var displayHost;

    var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
                      getService(Ci.nsIEffectiveTLDService);
    var idnService = Cc["@mozilla.org/network/idn-service;1"].
                     getService(Ci.nsIIDNService);
    try {
      var uri = Services.io.newURI(aURIString);
      var baseDomain = eTLDService.getBaseDomain(uri);
      displayHost = idnService.convertToDisplayIDN(baseDomain, {});
    } catch (e) {
      this.log("_getShortDisplayHost couldn't process " + aURIString);
    }

    if (!displayHost)
      displayHost = aURIString;

    return displayHost;
  },


  /**
   * Returns the hostname and realm for which authentication is being
   * requested, in the format expected to be used with nsILoginInfo.
   */
  _getAuthTarget(aChannel, aAuthInfo) {
    var hostname, realm;

    // If our proxy is demanding authentication, don't use the
    // channel's actual destination.
    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
      this.log("getAuthTarget is for proxy auth");
      if (!(aChannel instanceof Ci.nsIProxiedChannel))
        throw new Error("proxy auth needs nsIProxiedChannel");

      var info = aChannel.proxyInfo;
      if (!info)
        throw new Error("proxy auth needs nsIProxyInfo");

      // Proxies don't have a scheme, but we'll use "moz-proxy://"
      // so that it's more obvious what the login is for.
      var idnService = Cc["@mozilla.org/network/idn-service;1"].
                       getService(Ci.nsIIDNService);
      hostname = "moz-proxy://" +
                  idnService.convertUTF8toACE(info.host) +
                  ":" + info.port;
      realm = aAuthInfo.realm;
      if (!realm)
        realm = hostname;

      return [hostname, realm];
    }

    hostname = this._getFormattedHostname(aChannel.URI);

    // If a HTTP WWW-Authenticate header specified a realm, that value
    // will be available here. If it wasn't set or wasn't HTTP, we'll use
    // the formatted hostname instead.
    realm = aAuthInfo.realm;
    if (!realm)
      realm = hostname;

    return [hostname, realm];
  },


  /**
   * Returns [username, password] as extracted from aAuthInfo (which
   * holds this info after having prompted the user).
   *
   * If the authentication was for a Windows domain, we'll prepend the
   * return username with the domain. (eg, "domain\user")
   */
  _GetAuthInfo(aAuthInfo) {
    var username, password;

    var flags = aAuthInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
      username = aAuthInfo.domain + "\\" + aAuthInfo.username;
    else
      username = aAuthInfo.username;

    password = aAuthInfo.password;

    return [username, password];
  },


  /**
   * Given a username (possibly in DOMAIN\user form) and password, parses the
   * domain out of the username if necessary and sets domain, username and
   * password on the auth information object.
   */
  _SetAuthInfo(aAuthInfo, username, password) {
    var flags = aAuthInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
      // Domain is separated from username by a backslash
      var idx = username.indexOf("\\");
      if (idx == -1) {
        aAuthInfo.username = username;
      } else {
        aAuthInfo.domain   =  username.substring(0, idx);
        aAuthInfo.username =  username.substring(idx + 1);
      }
    } else {
      aAuthInfo.username = username;
    }
    aAuthInfo.password = password;
  },

  _newAsyncPromptConsumer(aCallback, aContext) {
    return {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
      callback: aCallback,
      context: aContext,
      cancel() {
        this.callback.onAuthCancelled(this.context, false);
        this.callback = null;
        this.context = null;
      }
    };
  },

  /**
   * This function looks for existing logins that can be updated
   * to match a submitted login, instead of creating a new one.
   *
   * Given a login and a loginList, it filters the login list
   * to find every login with either the same username as aLogin
   * or with the same password as aLogin and an empty username
   * so the user can add a username.
   *
   * @param {nsILoginInfo} aLogin
   *                       login to use as filter.
   * @param {nsILoginInfo[]} aLoginList
   *                         Array of logins to filter.
   * @returns {nsILoginInfo[]} the filtered array of logins.
   */
  _filterUpdatableLogins(aLogin, aLoginList) {
    return aLoginList.filter(l => l.username == aLogin.username ||
                             (l.password == aLogin.password &&
                              !l.username));
  },

}; // end of LoginManagerPrompter implementation

XPCOMUtils.defineLazyGetter(this.LoginManagerPrompter.prototype, "log", () => {
  let logger = LoginHelper.createLogger("LoginManagerPrompter");
  return logger.log.bind(logger);
});

var component = [LoginManagerPromptFactory, LoginManagerPrompter];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<e%7MCMCcomponents/storage-json.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * nsILoginManagerStorage implementation for the JSON back-end.
 */

"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
                                  "resource://gre/modules/LoginImport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
                                  "resource://gre/modules/LoginStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                   "@mozilla.org/uuid-generator;1",
                                   "nsIUUIDGenerator");

this.LoginManagerStorage_json = function() {};

this.LoginManagerStorage_json.prototype = {
  classID: Components.ID("{c00c432d-a0c9-46d7-bef6-9c45b4d07341}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),

  __crypto: null,  // nsILoginManagerCrypto service
  get _crypto() {
    if (!this.__crypto)
      this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
                      getService(Ci.nsILoginManagerCrypto);
    return this.__crypto;
  },

  initialize() {
    try {
      // Force initialization of the crypto module.
      // See bug 717490 comment 17.
      this._crypto;

      // Set the reference to LoginStore synchronously.
      let jsonPath = OS.Path.join(OS.Constants.Path.profileDir,
                                  "logins.json");
      this._store = new LoginStore(jsonPath);

      return (async () => {
        // Load the data asynchronously.
        this.log("Opening database at", this._store.path);
        await this._store.load();

        // The import from previous versions operates the first time
        // that this built-in storage back-end is used.  This may be
        // later than expected, in case add-ons have registered an
        // alternate storage that disabled the default one.
        try {
          if (Services.prefs.getBoolPref("signon.importedFromSqlite")) {
            return;
          }
        } catch (ex) {
          // If the preference does not exist, we need to import.
        }

        // Import only happens asynchronously.
        let sqlitePath = OS.Path.join(OS.Constants.Path.profileDir,
                                      "signons.sqlite");
        if (await OS.File.exists(sqlitePath)) {
          let loginImport = new LoginImport(this._store, sqlitePath);
          // Failures during import, for example due to a corrupt
          // file or a schema version that is too old, will not
          // prevent us from marking the operation as completed.
          // At the next startup, we will not try the import again.
          await loginImport.import().catch(Cu.reportError);
          this._store.saveSoon();
        }

        // We won't attempt import again on next startup.
        Services.prefs.setBoolPref("signon.importedFromSqlite", true);
      })().catch(Cu.reportError);
    } catch (e) {
      this.log("Initialization failed:", e);
      throw new Error("Initialization failed");
    }
  },

  /**
   * Internal method used by regression tests only.  It is called before
   * replacing this storage module with a new instance.
   */
  terminate() {
    this._store._saver.disarm();
    return this._store._save();
  },

  addLogin(login) {
    this._store.ensureDataReady();

    // Throws if there are bogus values.
    LoginHelper.checkLoginValues(login);

    let [encUsername, encPassword, encType] = this._encryptLogin(login);

    // Clone the login, so we don't modify the caller's object.
    let loginClone = login.clone();

    // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
    loginClone.QueryInterface(Ci.nsILoginMetaInfo);
    if (loginClone.guid) {
      if (!this._isGuidUnique(loginClone.guid))
        throw new Error("specified GUID already exists");
    } else {
      loginClone.guid = gUUIDGenerator.generateUUID().toString();
    }

    // Set timestamps
    let currentTime = Date.now();
    if (!loginClone.timeCreated)
      loginClone.timeCreated = currentTime;
    if (!loginClone.timeLastUsed)
      loginClone.timeLastUsed = currentTime;
    if (!loginClone.timePasswordChanged)
      loginClone.timePasswordChanged = currentTime;
    if (!loginClone.timesUsed)
      loginClone.timesUsed = 1;

    this._store.data.logins.push({
      id:                  this._store.data.nextId++,
      hostname:            loginClone.hostname,
      httpRealm:           loginClone.httpRealm,
      formSubmitURL:       loginClone.formSubmitURL,
      usernameField:       loginClone.usernameField,
      passwordField:       loginClone.passwordField,
      encryptedUsername:   encUsername,
      encryptedPassword:   encPassword,
      guid:                loginClone.guid,
      encType,
      timeCreated:         loginClone.timeCreated,
      timeLastUsed:        loginClone.timeLastUsed,
      timePasswordChanged: loginClone.timePasswordChanged,
      timesUsed:           loginClone.timesUsed
    });
    this._store.saveSoon();

    // Send a notification that a login was added.
    LoginHelper.notifyStorageChanged("addLogin", loginClone);
    return loginClone;
  },

  removeLogin(login) {
    this._store.ensureDataReady();

    let [idToDelete, storedLogin] = this._getIdForLogin(login);
    if (!idToDelete)
      throw new Error("No matching logins");

    let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
    if (foundIndex != -1) {
      this._store.data.logins.splice(foundIndex, 1);
      this._store.saveSoon();
    }

    LoginHelper.notifyStorageChanged("removeLogin", storedLogin);
  },

  modifyLogin(oldLogin, newLoginData) {
    this._store.ensureDataReady();

    let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
    if (!idToModify)
      throw new Error("No matching logins");

    let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);

    // Check if the new GUID is duplicate.
    if (newLogin.guid != oldStoredLogin.guid &&
        !this._isGuidUnique(newLogin.guid)) {
      throw new Error("specified GUID already exists");
    }

    // Look for an existing entry in case key properties changed.
    if (!newLogin.matches(oldLogin, true)) {
      let logins = this.findLogins({}, newLogin.hostname,
                                   newLogin.formSubmitURL,
                                   newLogin.httpRealm);

      if (logins.some(login => newLogin.matches(login, true)))
        throw new Error("This login already exists.");
    }

    // Get the encrypted value of the username and password.
    let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);

    for (let loginItem of this._store.data.logins) {
      if (loginItem.id == idToModify) {
        loginItem.hostname = newLogin.hostname;
        loginItem.httpRealm = newLogin.httpRealm;
        loginItem.formSubmitURL = newLogin.formSubmitURL;
        loginItem.usernameField = newLogin.usernameField;
        loginItem.passwordField = newLogin.passwordField;
        loginItem.encryptedUsername = encUsername;
        loginItem.encryptedPassword = encPassword;
        loginItem.guid = newLogin.guid;
        loginItem.encType = encType;
        loginItem.timeCreated = newLogin.timeCreated;
        loginItem.timeLastUsed = newLogin.timeLastUsed;
        loginItem.timePasswordChanged = newLogin.timePasswordChanged;
        loginItem.timesUsed = newLogin.timesUsed;
        this._store.saveSoon();
        break;
      }
    }

    LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
  },

  /**
   * @return {nsILoginInfo[]}
   */
  getAllLogins(count) {
    let [logins, ids] = this._searchLogins({});

    // decrypt entries for caller.
    logins = this._decryptLogins(logins);

    this.log("_getAllLogins: returning", logins.length, "logins.");
    if (count)
      count.value = logins.length; // needed for XPCOM
    return logins;
  },

  /**
   * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   * JavaScript object and decrypt the results.
   *
   * @return {nsILoginInfo[]} which are decrypted.
   */
  searchLogins(count, matchData) {
    let realMatchData = {};
    let options = {};
    // Convert nsIPropertyBag to normal JS object
    let propEnum = matchData.enumerator;
    while (propEnum.hasMoreElements()) {
      let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
      switch (prop.name) {
        // Some property names aren't field names but are special options to affect the search.
        case "schemeUpgrades": {
          options[prop.name] = prop.value;
          break;
        }
        default: {
          realMatchData[prop.name] = prop.value;
          break;
        }
      }
    }

    let [logins, ids] = this._searchLogins(realMatchData, options);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    count.value = logins.length; // needed for XPCOM
    return logins;
  },

  /**
   * Private method to perform arbitrary searches on any field. Decryption is
   * left to the caller.
   *
   * Returns [logins, ids] for logins that match the arguments, where logins
   * is an array of encrypted nsLoginInfo and ids is an array of associated
   * ids in the database.
   */
  _searchLogins(matchData, aOptions = {
    schemeUpgrades: false,
  }) {
    this._store.ensureDataReady();

    function match(aLogin) {
      for (let field in matchData) {
        let wantedValue = matchData[field];
        switch (field) {
          case "formSubmitURL":
            if (wantedValue != null) {
              // Historical compatibility requires this special case
              if (aLogin.formSubmitURL == "") {
                break;
              }
              if (!LoginHelper.isOriginMatching(aLogin[field], wantedValue, aOptions)) {
                return false;
              }
              break;
            }
            // fall through
          case "hostname":
            if (wantedValue != null) { // needed for formSubmitURL fall through
              if (!LoginHelper.isOriginMatching(aLogin[field], wantedValue, aOptions)) {
                return false;
              }
              break;
            }
            // fall through
          // Normal cases.
          case "httpRealm":
          case "id":
          case "usernameField":
          case "passwordField":
          case "encryptedUsername":
          case "encryptedPassword":
          case "guid":
          case "encType":
          case "timeCreated":
          case "timeLastUsed":
          case "timePasswordChanged":
          case "timesUsed":
            if (wantedValue == null && aLogin[field]) {
              return false;
            } else if (aLogin[field] != wantedValue) {
              return false;
            }
            break;
          // Fail if caller requests an unknown property.
          default:
            throw new Error("Unexpected field: " + field);
        }
      }
      return true;
    }

    let foundLogins = [], foundIds = [];
    for (let loginItem of this._store.data.logins) {
      if (match(loginItem)) {
        // Create the new nsLoginInfo object, push to array
        let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
                    createInstance(Ci.nsILoginInfo);
        login.init(loginItem.hostname, loginItem.formSubmitURL,
                   loginItem.httpRealm, loginItem.encryptedUsername,
                   loginItem.encryptedPassword, loginItem.usernameField,
                   loginItem.passwordField);
        // set nsILoginMetaInfo values
        login.QueryInterface(Ci.nsILoginMetaInfo);
        login.guid = loginItem.guid;
        login.timeCreated = loginItem.timeCreated;
        login.timeLastUsed = loginItem.timeLastUsed;
        login.timePasswordChanged = loginItem.timePasswordChanged;
        login.timesUsed = loginItem.timesUsed;
        foundLogins.push(login);
        foundIds.push(loginItem.id);
      }
    }

    this.log("_searchLogins: returning", foundLogins.length, "logins for", matchData,
             "with options", aOptions);
    return [foundLogins, foundIds];
  },

  /**
   * Removes all logins from storage.
   */
  removeAllLogins() {
    this._store.ensureDataReady();

    this.log("Removing all logins");
    this._store.data.logins = [];
    this._store.saveSoon();

    LoginHelper.notifyStorageChanged("removeAllLogins", null);
  },

  findLogins(count, hostname, formSubmitURL, httpRealm) {
    let loginData = {
      hostname,
      formSubmitURL,
      httpRealm
    };
    let matchData = { };
    for (let field of ["hostname", "formSubmitURL", "httpRealm"])
      if (loginData[field] != "")
        matchData[field] = loginData[field];
    let [logins, ids] = this._searchLogins(matchData);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    this.log("_findLogins: returning", logins.length, "logins");
    count.value = logins.length; // needed for XPCOM
    return logins;
  },

  countLogins(hostname, formSubmitURL, httpRealm) {
    let loginData = {
      hostname,
      formSubmitURL,
      httpRealm
    };
    let matchData = { };
    for (let field of ["hostname", "formSubmitURL", "httpRealm"])
      if (loginData[field] != "")
        matchData[field] = loginData[field];
    let [logins, ids] = this._searchLogins(matchData);

    this.log("_countLogins: counted logins:", logins.length);
    return logins.length;
  },

  get uiBusy() {
    return this._crypto.uiBusy;
  },

  get isLoggedIn() {
    return this._crypto.isLoggedIn;
  },

  /**
   * Returns an array with two items: [id, login]. If the login was not
   * found, both items will be null. The returned login contains the actual
   * stored login (useful for looking at the actual nsILoginMetaInfo values).
   */
  _getIdForLogin(login) {
    let matchData = { };
    for (let field of ["hostname", "formSubmitURL", "httpRealm"])
      if (login[field] != "")
        matchData[field] = login[field];
    let [logins, ids] = this._searchLogins(matchData);

    let id = null;
    let foundLogin = null;

    // The specified login isn't encrypted, so we need to ensure
    // the logins we're comparing with are decrypted. We decrypt one entry
    // at a time, lest _decryptLogins return fewer entries and screw up
    // indices between the two.
    for (let i = 0; i < logins.length; i++) {
      let [decryptedLogin] = this._decryptLogins([logins[i]]);

      if (!decryptedLogin || !decryptedLogin.equals(login))
        continue;

      // We've found a match, set id and break
      foundLogin = decryptedLogin;
      id = ids[i];
      break;
    }

    return [id, foundLogin];
  },

  /**
   * Checks to see if the specified GUID already exists.
   */
  _isGuidUnique(guid) {
    this._store.ensureDataReady();

    return this._store.data.logins.every(l => l.guid != guid);
  },

  /**
   * Returns the encrypted username, password, and encrypton type for the specified
   * login. Can throw if the user cancels a master password entry.
   */
  _encryptLogin(login) {
    let encUsername = this._crypto.encrypt(login.username);
    let encPassword = this._crypto.encrypt(login.password);
    let encType     = this._crypto.defaultEncType;

    return [encUsername, encPassword, encType];
  },

  /**
   * Decrypts username and password fields in the provided array of
   * logins.
   *
   * The entries specified by the array will be decrypted, if possible.
   * An array of successfully decrypted logins will be returned. The return
   * value should be given to external callers (since still-encrypted
   * entries are useless), whereas internal callers generally don't want
   * to lose unencrypted entries (eg, because the user clicked Cancel
   * instead of entering their master password)
   */
  _decryptLogins(logins) {
    let result = [];

    for (let login of logins) {
      try {
        login.username = this._crypto.decrypt(login.username);
        login.password = this._crypto.decrypt(login.password);
      } catch (e) {
        // If decryption failed (corrupt entry?), just skip it.
        // Rethrow other errors (like canceling entry of a master pw)
        if (e.result == Cr.NS_ERROR_FAILURE)
          continue;
        throw e;
      }
      result.push(login);
    }

    return result;
  },
};

XPCOMUtils.defineLazyGetter(this.LoginManagerStorage_json.prototype, "log", () => {
  let logger = LoginHelper.createLogger("Login storage");
  return logger.log.bind(logger);
});

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManagerStorage_json]);
PK
!<Ǜcomponents/crypto-SDR.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");

function LoginManagerCrypto_SDR() {
  this.init();
}

LoginManagerCrypto_SDR.prototype = {

  classID: Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManagerCrypto]),

  __decoderRing: null,  // nsSecretDecoderRing service
  get _decoderRing() {
    if (!this.__decoderRing)
      this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
                           getService(Ci.nsISecretDecoderRing);
    return this.__decoderRing;
  },

  __utfConverter: null, // UCS2 <--> UTF8 string conversion
  get _utfConverter() {
    if (!this.__utfConverter) {
      this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                            createInstance(Ci.nsIScriptableUnicodeConverter);
      this.__utfConverter.charset = "UTF-8";
    }
    return this.__utfConverter;
  },

  _utfConverterReset() {
    this.__utfConverter = null;
  },

  _uiBusy: false,


  init() {
    // Check to see if the internal PKCS#11 token has been initialized.
    // If not, set a blank password.
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
                  getService(Ci.nsIPK11TokenDB);

    let token = tokenDB.getInternalKeyToken();
    if (token.needsUserInit) {
      this.log("Initializing key3.db with default blank password.");
      token.initPassword("");
    }
  },


  /*
   * encrypt
   *
   * Encrypts the specified string, using the SecretDecoderRing.
   *
   * Returns the encrypted string, or throws an exception if there was a
   * problem.
   */
  encrypt(plainText) {
    let cipherText = null;

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = true;
    try {
      let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
      plainOctet += this._utfConverter.Finish();
      cipherText = this._decoderRing.encryptString(plainOctet);
    } catch (e) {
      this.log("Failed to encrypt string. (" + e.name + ")");
      // If the user clicks Cancel, we get NS_ERROR_FAILURE.
      // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
      if (e.result == Cr.NS_ERROR_FAILURE) {
        canceledMP = true;
        throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
      } else {
        throw Components.Exception("Couldn't encrypt string", Cr.NS_ERROR_FAILURE);
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a master password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn)
        this._notifyObservers("passwordmgr-crypto-login");
      else if (canceledMP)
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
    }
    return cipherText;
  },


  /*
   * decrypt
   *
   * Decrypts the specified string, using the SecretDecoderRing.
   *
   * Returns the decrypted string, or throws an exception if there was a
   * problem.
   */
  decrypt(cipherText) {
    let plainText = null;

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = true;
    try {
      let plainOctet;
      plainOctet = this._decoderRing.decryptString(cipherText);
      plainText = this._utfConverter.ConvertToUnicode(plainOctet);
    } catch (e) {
      this.log("Failed to decrypt string: " + cipherText +
          " (" + e.name + ")");

      // In the unlikely event the converter threw, reset it.
      this._utfConverterReset();

      // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
      // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
      // Wrong passwords are handled by the decoderRing reprompting;
      // we get no notification.
      if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
        canceledMP = true;
        throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
      } else {
        throw Components.Exception("Couldn't decrypt string", Cr.NS_ERROR_FAILURE);
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a master password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn)
        this._notifyObservers("passwordmgr-crypto-login");
      else if (canceledMP)
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
    }

    return plainText;
  },


  /*
   * uiBusy
   */
  get uiBusy() {
    return this._uiBusy;
  },


  /*
   * isLoggedIn
   */
  get isLoggedIn() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
                  getService(Ci.nsIPK11TokenDB);
    let token = tokenDB.getInternalKeyToken();
    return !token.hasPassword || token.isLoggedIn();
  },


  /*
   * defaultEncType
   */
  get defaultEncType() {
    return Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
  },


  /*
   * _notifyObservers
   */
  _notifyObservers(topic) {
    this.log("Prompted for a master password, notifying for " + topic);
    Services.obs.notifyObservers(null, topic);
  },
}; // end of nsLoginManagerCrypto_SDR implementation

XPCOMUtils.defineLazyGetter(this.LoginManagerCrypto_SDR.prototype, "log", () => {
  let logger = LoginHelper.createLogger("Login crypto");
  return logger.log.bind(logger);
});

var component = [LoginManagerCrypto_SDR];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<%!components/TooltipTextProvider.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

function TooltipTextProvider() {}

TooltipTextProvider.prototype = {
  getNodeText(tipElement, textOut, directionOut) {
    // Don't show the tooltip if the tooltip node is a document, browser, or disconnected.
    if (!tipElement || !tipElement.ownerDocument ||
        tipElement.localName == "browser" ||
        (tipElement.ownerDocument.compareDocumentPosition(tipElement) &
         tipElement.ownerDocument.DOCUMENT_POSITION_DISCONNECTED)) {
      return false;
    }

    var defView = tipElement.ownerGlobal;
    // XXX Work around bug 350679:
    // "Tooltips can be fired in documents with no view".
    if (!defView)
      return false;

    const XLinkNS = "http://www.w3.org/1999/xlink";
    const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

    var titleText = null;
    var XLinkTitleText = null;
    var SVGTitleText = null;
    var XULtooltiptextText = null;
    var lookingForSVGTitle = true;
    var direction = tipElement.ownerDocument.dir;

    // If the element is invalid per HTML5 Forms specifications and has no title,
    // show the constraint validation error message.
    if ((tipElement instanceof defView.HTMLInputElement ||
         tipElement instanceof defView.HTMLTextAreaElement ||
         tipElement instanceof defView.HTMLSelectElement ||
         tipElement instanceof defView.HTMLButtonElement) &&
        !tipElement.hasAttribute("title") &&
        (!tipElement.form || !tipElement.form.noValidate)) {
      // If the element is barred from constraint validation or valid,
      // the validation message will be the empty string.
      titleText = tipElement.validationMessage || null;
    }

    // If the element is an <input type='file'> without a title, we should show
    // the current file selection.
    if (!titleText &&
        tipElement instanceof defView.HTMLInputElement &&
        tipElement.type == "file" &&
        !tipElement.hasAttribute("title")) {
      let files = tipElement.files;

      try {
        var bundle =
          Services.strings.createBundle("chrome://global/locale/layout/HtmlForm.properties");
        if (files.length == 0) {
          if (tipElement.multiple) {
            titleText = bundle.GetStringFromName("NoFilesSelected");
          } else {
            titleText = bundle.GetStringFromName("NoFileSelected");
          }
        } else {
          titleText = files[0].name;
          // For UX and performance (jank) reasons we cap the number of
          // files that we list in the tooltip to 20 plus a "and xxx more"
          // line, or to 21 if exactly 21 files were picked.
          const TRUNCATED_FILE_COUNT = 20;
          let count = Math.min(files.length, TRUNCATED_FILE_COUNT);
          for (let i = 1; i < count; ++i) {
            titleText += "\n" + files[i].name;
          }
          if (files.length == TRUNCATED_FILE_COUNT + 1) {
            titleText += "\n" + files[TRUNCATED_FILE_COUNT].name;
          } else if (files.length > TRUNCATED_FILE_COUNT + 1) {
            let xmoreStr = bundle.GetStringFromName("AndNMoreFiles");
            let xmoreNum = files.length - TRUNCATED_FILE_COUNT;
            let tmp = {};
            Cu.import("resource://gre/modules/PluralForm.jsm", tmp);
            let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum);
            titleText += "\n" + andXMoreStr;
          }
        }
      } catch (e) {}
    }

    // Check texts against null so that title="" can be used to undefine a
    // title on a child element.
    let usedTipElement = null;
    while (tipElement &&
           (titleText == null) && (XLinkTitleText == null) &&
           (SVGTitleText == null) && (XULtooltiptextText == null)) {

      if (tipElement.nodeType == defView.Node.ELEMENT_NODE) {
        if (tipElement.namespaceURI == XULNS)
          XULtooltiptextText = tipElement.getAttribute("tooltiptext");
        else if (!(tipElement instanceof defView.SVGElement))
          titleText = tipElement.getAttribute("title");

        if ((tipElement instanceof defView.HTMLAnchorElement ||
             tipElement instanceof defView.HTMLAreaElement ||
             tipElement instanceof defView.HTMLLinkElement ||
             tipElement instanceof defView.SVGAElement) && tipElement.href) {
          XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
        }
        if (lookingForSVGTitle &&
            (!(tipElement instanceof defView.SVGElement) ||
             tipElement.parentNode.nodeType == defView.Node.DOCUMENT_NODE)) {
          lookingForSVGTitle = false;
        }
        if (lookingForSVGTitle) {
          for (let childNode of tipElement.childNodes) {
            if (childNode instanceof defView.SVGTitleElement) {
              SVGTitleText = childNode.textContent;
              break;
            }
          }
        }

        usedTipElement = tipElement;
      }

      tipElement = tipElement.parentNode;
    }

    return [titleText, XLinkTitleText, SVGTitleText, XULtooltiptextText].some(function(t) {
      if (t && /\S/.test(t)) {
        // Make CRLF and CR render one line break each.
        textOut.value = t.replace(/\r\n?/g, "\n");

        if (usedTipElement) {
          direction = defView.getComputedStyle(usedTipElement)
                             .getPropertyValue("direction");
        }

        directionOut.value = direction;
        return true;
      }

      return false;
    });
  },

  classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsITooltipTextProvider]),
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TooltipTextProvider]);

PK
!<F++!components/WebVTTParserWrapper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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/vtt.jsm");

var Ci = Components.interfaces;

var WEBVTTPARSERWRAPPER_CID = "{acf6e493-0092-4b26-b172-241e375c57ab}";
var WEBVTTPARSERWRAPPER_CONTRACTID = "@mozilla.org/webvttParserWrapper;1";

function WebVTTParserWrapper()
{
  // Nothing
}

WebVTTParserWrapper.prototype =
{
  loadParser: function(window)
  {
    this.parser = new WebVTT.Parser(window,  new TextDecoder("utf8"));
  },

  parse: function(data)
  {
    // We can safely translate the string data to a Uint8Array as we are
    // guaranteed character codes only from \u0000 => \u00ff
    var buffer = new Uint8Array(data.length);
    for (var i = 0; i < data.length; i++) {
      buffer[i] = data.charCodeAt(i);
    }

    this.parser.parse(buffer);
  },

  flush: function()
  {
    this.parser.flush();
  },

  watch: function(callback)
  {
    this.parser.oncue = callback.onCue;
    this.parser.onregion = callback.onRegion;
    this.parser.onparsingerror = function(e) {
      // Passing the just the error code back is enough for our needs.
      callback.onParsingError(("code" in e) ? e.code : -1);
    };
  },

  convertCueToDOMTree: function(window, cue)
  {
    return WebVTT.convertCueToDOMTree(window, cue.text);
  },

  processCues: function(window, cues, overlay, controls)
  {
    WebVTT.processCues(window, cues, overlay, controls);
  },

  classDescription: "Wrapper for the JS WebVTT implementation (vtt.js)",
  classID: Components.ID(WEBVTTPARSERWRAPPER_CID),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebVTTParserWrapper]),
  classInfo: XPCOMUtils.generateCI({
    classID:    WEBVTTPARSERWRAPPER_CID,
    contractID: WEBVTTPARSERWRAPPER_CONTRACTID,
    interfaces: [Ci.nsIWebVTTParserWrapper]
  })
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebVTTParserWrapper]);
PK
!<?qQccomponents/nsHelperAppDlg.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
                                  "resource://gre/modules/SharedPromptUtils.jsm");

///////////////////////////////////////////////////////////////////////////////
//// Helper Functions

/**
 * Determines if a given directory is able to be used to download to.
 *
 * @param aDirectory
 *        The directory to check.
 * @return true if we can use the directory, false otherwise.
 */
function isUsableDirectory(aDirectory)
{
  return aDirectory.exists() && aDirectory.isDirectory() &&
         aDirectory.isWritable();
}

// Web progress listener so we can detect errors while mLauncher is
// streaming the data to a temporary file.
function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) {
  this.helperAppDlg = aHelperAppDialog;
}

nsUnknownContentTypeDialogProgressListener.prototype = {
  // nsIWebProgressListener methods.
  // Look for error notifications and display alert to user.
  onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
    if ( aStatus != Components.results.NS_OK ) {
      // Display error alert (using text supplied by back-end).
      // FIXME this.dialog is undefined?
      Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
      // Close the dialog.
      this.helperAppDlg.onCancel();
      if ( this.helperAppDlg.mDialog ) {
        this.helperAppDlg.mDialog.close();
      }
    }
  },

  // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
  onProgressChange: function( aWebProgress,
                              aRequest,
                              aCurSelfProgress,
                              aMaxSelfProgress,
                              aCurTotalProgress,
                              aMaxTotalProgress ) {
  },

  onProgressChange64: function( aWebProgress,
                                aRequest,
                                aCurSelfProgress,
                                aMaxSelfProgress,
                                aCurTotalProgress,
                                aMaxTotalProgress ) {
  },



  onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  },

  onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) {
  },

  onSecurityChange: function( aWebProgress, aRequest, state ) {
  },

  onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
    return true;
  }
};

///////////////////////////////////////////////////////////////////////////////
//// nsUnknownContentTypeDialog

/* This file implements the nsIHelperAppLauncherDialog interface.
 *
 * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
 * comprised of:
 *   - a JS constructor function
 *   - a prototype providing all the interface methods and implementation stuff
 *
 * In addition, this file implements an nsIModule object that registers the
 * nsUnknownContentTypeDialog component.
 */

const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
const nsITimer = Components.interfaces.nsITimer;

var downloadModule = {};
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule);
Components.utils.import("resource://gre/modules/DownloadPaths.jsm");
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
Components.utils.import("resource://gre/modules/Downloads.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");

/* ctor
 */
function nsUnknownContentTypeDialog() {
  // Initialize data properties.
  this.mLauncher = null;
  this.mContext  = null;
  this.mReason   = null;
  this.chosenApp = null;
  this.givenDefaultApp = false;
  this.updateSelf = true;
  this.mTitle    = "";
}

nsUnknownContentTypeDialog.prototype = {
  classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),

  nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,

  QueryInterface: function (iid) {
    if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
        !iid.equals(Components.interfaces.nsITimerCallback) &&
        !iid.equals(Components.interfaces.nsISupports)) {
      throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    return this;
  },

  // ---------- nsIHelperAppLauncherDialog methods ----------

  // show: Open XUL dialog using window watcher.  Since the dialog is not
  //       modal, it needs to be a top level window and the way to open
  //       one of those is via that route).
  show: function(aLauncher, aContext, aReason)  {
    this.mLauncher = aLauncher;
    this.mContext  = aContext;
    this.mReason   = aReason;

    // Cache some information in case this context goes away:
    try {
      let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
      this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
    } catch (ex) {
      Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
    }

    const nsITimer = Components.interfaces.nsITimer;
    this._showTimer = Components.classes["@mozilla.org/timer;1"]
                                .createInstance(nsITimer);
    this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
  },

  // When opening from new tab, if tab closes while dialog is opening,
  // (which is a race condition on the XUL file being cached and the timer
  // in nsExternalHelperAppService), the dialog gets a blur and doesn't
  // activate the OK button.  So we wait a bit before doing opening it.
  reallyShow: function() {
    try {
      let ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
      let docShell = ir.getInterface(Components.interfaces.nsIDocShell);
      let rootWin = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
                                 .rootTreeItem
                                 .QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDOMWindow);
      let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                         .getService(Components.interfaces.nsIWindowWatcher);
      this.mDialog = ww.openWindow(rootWin,
                                   "chrome://mozapps/content/downloads/unknownContentType.xul",
                                   null,
                                   "chrome,centerscreen,titlebar,dialog=yes,dependent",
                                   null);
    } catch (ex) {
      // The containing window may have gone away.  Break reference
      // cycles and stop doing the download.
      this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
      return;
    }

    // Hook this object to the dialog.
    this.mDialog.dialog = this;

    // Hook up utility functions.
    this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;

    // Watch for error notifications.
    var progressListener = new nsUnknownContentTypeDialogProgressListener(this);
    this.mLauncher.setWebProgressListener(progressListener);
  },

  //
  // displayBadPermissionAlert()
  //
  // Diplay an alert panel about the bad permission of folder/directory.
  //
  displayBadPermissionAlert: function () {
    let bundle =
      Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");

    Services.prompt.alert(this.dialog,
                   bundle.GetStringFromName("badPermissions.title"),
                   bundle.GetStringFromName("badPermissions"));
  },

  promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
    var result = null;

    this.mLauncher = aLauncher;

    let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
    let bundle =
      Services.strings
              .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");

    let parent;
    let gDownloadLastDir;
    try {
      parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
    } catch (ex) {}

    if (parent) {
      gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
    } else {
      // Use the cached download info, but pick an arbitrary parent window
      // because the original one is definitely gone (and nsIFilePicker doesn't like
      // a null parent):
      gDownloadLastDir = this._mDownloadDir;
      let windowsEnum = Services.wm.getEnumerator("");
      while (windowsEnum.hasMoreElements()) {
        let someWin = windowsEnum.getNext();
        // We need to make sure we don't end up with this dialog, because otherwise
        // that's going to go away when the user clicks "Save", and that breaks the
        // windows file picker that's supposed to show up if we let the user choose
        // where to save files...
        if (someWin != this.mDialog) {
          parent = someWin;
        }
      }
      if (!parent) {
        Cu.reportError("No candidate parent windows were found for the save filepicker." +
                       "This should never happen.");
      }
    }

    (async () => {
      if (!aForcePrompt) {
        // Check to see if the user wishes to auto save to the default download
        // folder without prompting. Note that preference might not be set.
        let autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR, false);

        if (autodownload) {
          // Retrieve the user's default download directory
          let preferredDir = await Downloads.getPreferredDownloadsDirectory();
          let defaultFolder = new FileUtils.File(preferredDir);

          try {
            result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
          }
          catch (ex) {
            // When the default download directory is write-protected,
            // prompt the user for a different target file.
          }

          // Check to make sure we have a valid directory, otherwise, prompt
          if (result) {
            // Notifications for CloudStorage API consumers to show offer
            // prompts while downloading. See Bug 1365129
            Services.obs.notifyObservers(null, "cloudstorage-prompt-notification", result.path);
            // This path is taken when we have a writable default download directory.
            aLauncher.saveDestinationAvailable(result);
            return;
          }
        }
      }

      // Use file picker to show dialog.
      var nsIFilePicker = Components.interfaces.nsIFilePicker;
      var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
      var windowTitle = bundle.GetStringFromName("saveDialogTitle");
      picker.init(parent, windowTitle, nsIFilePicker.modeSave);
      picker.defaultString = aDefaultFile;

      if (aSuggestedFileExtension) {
        // aSuggestedFileExtension includes the period, so strip it
        picker.defaultExtension = aSuggestedFileExtension.substring(1);
      }
      else {
        try {
          picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
        }
        catch (ex) { }
      }

      var wildCardExtension = "*";
      if (aSuggestedFileExtension) {
        wildCardExtension += aSuggestedFileExtension;
        picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
      }

      picker.appendFilters( nsIFilePicker.filterAll );

      // Default to lastDir if it is valid, otherwise use the user's default
      // downloads directory.  getPreferredDownloadsDirectory should always
      // return a valid directory path, so we can safely default to it.
      let preferredDir = await Downloads.getPreferredDownloadsDirectory();
      picker.displayDirectory = new FileUtils.File(preferredDir);

      gDownloadLastDir.getFileAsync(aLauncher.source, lastDir => {
        if (lastDir && isUsableDirectory(lastDir))
          picker.displayDirectory = lastDir;

        picker.open(returnValue => {
          if (returnValue == nsIFilePicker.returnCancel) {
            // null result means user cancelled.
            aLauncher.saveDestinationAvailable(null);
            return;
          }

          // Be sure to save the directory the user chose through the Save As...
          // dialog  as the new browser.download.dir since the old one
          // didn't exist.
          result = picker.file;

          if (result) {
            try {
              // Remove the file so that it's not there when we ensure non-existence later;
              // this is safe because for the file to exist, the user would have had to
              // confirm that he wanted the file overwritten.
              // Only remove file if final name exists
              if (result.exists() && this.getFinalLeafName(result.leafName) == result.leafName)
                result.remove(false);
            }
            catch (ex) {
              // As it turns out, the failure to remove the file, for example due to
              // permission error, will be handled below eventually somehow.
            }

            var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);

            // Do not store the last save directory as a pref inside the private browsing mode
            gDownloadLastDir.setFile(aLauncher.source, newDir);

            try {
              result = this.validateLeafName(newDir, result.leafName, null);
            }
            catch (ex) {
              // When the chosen download directory is write-protected,
              // display an informative error message.
              // In all cases, download will be stopped.

              if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
                this.displayBadPermissionAlert();
                aLauncher.saveDestinationAvailable(null);
                return;
              }

            }
          }
          aLauncher.saveDestinationAvailable(result);
        });
      });
    })().catch(Components.utils.reportError);
  },

  getFinalLeafName: function (aLeafName, aFileExt)
  {
    // Remove any leading periods, since we don't want to save hidden files
    // automatically.
    aLeafName = aLeafName.replace(/^\.+/, "");

    if (aLeafName == "")
      aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");

    return aLeafName;
  },

  /**
   * Ensures that a local folder/file combination does not already exist in
   * the file system (or finds such a combination with a reasonably similar
   * leaf name), creates the corresponding file, and returns it.
   *
   * @param   aLocalFolder
   *          the folder where the file resides
   * @param   aLeafName
   *          the string name of the file (may be empty if no name is known,
   *          in which case a name will be chosen)
   * @param   aFileExt
   *          the extension of the file, if one is known; this will be ignored
   *          if aLeafName is non-empty
   * @return  nsILocalFile
   *          the created file
   * @throw   an error such as permission doesn't allow creation of
   *          file, etc.
   */
  validateLeafName: function (aLocalFolder, aLeafName, aFileExt)
  {
    if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) {
      throw new Components.Exception("Destination directory non-existing or permission error",
                                     Components.results.NS_ERROR_FILE_ACCESS_DENIED);
    }

    aLeafName = this.getFinalLeafName(aLeafName, aFileExt);
    aLocalFolder.append(aLeafName);

    // The following assignment can throw an exception, but
    // is now caught properly in the caller of validateLeafName.
    var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);

    if (AppConstants.platform == "win") {
      let ext;
      try {
        // We can fail here if there's no primary extension set
        ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
      } catch (e) { }

      // Append a file extension if it's an executable that doesn't have one
      // but make sure we actually have an extension to add
      let leaf = createdFile.leafName;
      if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) {
        createdFile.remove(false);
        aLocalFolder.leafName = leaf + ext;
        createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
      }
    }

    return createdFile;
  },

  // ---------- implementation methods ----------

  // initDialog:  Fill various dialog fields with initial content.
  initDialog : function() {
    // Put file name in window title.
    var suggestedFileName = this.mLauncher.suggestedFileName;

    // Some URIs do not implement nsIURL, so we can't just QI.
    var url = this.mLauncher.source;
    if (url instanceof Components.interfaces.nsINestedURI)
      url = url.innermostURI;

    var fname = "";
    var iconPath = "goat";
    this.mSourcePath = url.prePath;
    if (url instanceof Components.interfaces.nsIURL) {
      // A url, use file name from it.
      fname = iconPath = url.fileName;
      this.mSourcePath += url.directory;
    } else {
      // A generic uri, use path.
      fname = url.path;
      this.mSourcePath += url.path;
    }

    if (suggestedFileName)
      fname = iconPath = suggestedFileName;

    var displayName = fname.replace(/ +/g, " ");

    this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
    this.mDialog.document.title = this.mTitle;

    // Put content type, filename and location into intro.
    this.initIntro(url, fname, displayName);

    var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
    this.dialogElement("contentTypeImage").setAttribute("src", iconString);

    // if always-save and is-executable and no-handler
    // then set up simple ui
    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
    var shouldntRememberChoice = (mimeType == "application/octet-stream" ||
                                  mimeType == "application/x-msdownload" ||
                                  this.mLauncher.targetFileIsExecutable);
    if ((shouldntRememberChoice && !this.openWithDefaultOK()) ||
        Services.prefs.getBoolPref("browser.download.forbid_open_with")) {
      // hide featured choice
      this.dialogElement("normalBox").collapsed = true;
      // show basic choice
      this.dialogElement("basicBox").collapsed = false;
      // change button labels and icons; use "save" icon for the accept
      // button since it's the only action possible
      let acceptButton = this.mDialog.document.documentElement
                                              .getButton("accept");
      acceptButton.label = this.dialogElement("strings")
                               .getString("unknownAccept.label");
      acceptButton.setAttribute("icon", "save");
      this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
      // hide other handler
      this.dialogElement("openHandler").collapsed = true;
      // set save as the selected option
      this.dialogElement("mode").selectedItem = this.dialogElement("save");
    }
    else {
      this.initAppAndSaveToDiskValues();

      // Initialize "always ask me" box. This should always be disabled
      // and set to true for the ambiguous type application/octet-stream.
      // We don't also check for application/x-msdownload here since we
      // want users to be able to autodownload .exe files.
      var rememberChoice = this.dialogElement("rememberChoice");

      // Just because we have a content-type of application/octet-stream
      // here doesn't actually mean that the content is of that type. Many
      // servers default to sending text/plain for file types they don't know
      // about. To account for this, the uriloader does some checking to see
      // if a file sent as text/plain contains binary characters, and if so (*)
      // it morphs the content-type into application/octet-stream so that
      // the file can be properly handled. Since this is not generic binary
      // data, rather, a data format that the system probably knows about,
      // we don't want to use the content-type provided by this dialog's
      // opener, as that's the generic application/octet-stream that the
      // uriloader has passed, rather we want to ask the MIME Service.
      // This is so we don't needlessly disable the "autohandle" checkbox.

      // commented out to close the opening brace in the if statement.
      // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
      // var type = mimeService.getTypeFromURI(this.mLauncher.source);
      // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");

      // if (type == "application/octet-stream") {
      if (shouldntRememberChoice) {
        rememberChoice.checked = false;
        rememberChoice.disabled = true;
      }
      else {
        rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling &&
                                 this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally;
      }
      this.toggleRememberChoice(rememberChoice);

      // XXXben - menulist won't init properly, hack.
      var openHandler = this.dialogElement("openHandler");
      openHandler.remove();
      var openHandlerBox = this.dialogElement("openHandlerBox");
      openHandlerBox.appendChild(openHandler);
    }

    this.mDialog.setTimeout("dialog.postShowCallback()", 0);

    this.delayHelper = new EnableDelayHelper({
      disableDialog: () => {
        this.mDialog.document.documentElement.getButton("accept").disabled = true;
      },
      enableDialog: () => {
        this.mDialog.document.documentElement.getButton("accept").disabled = false;
      },
      focusTarget: this.mDialog
    });
  },

  notify: function (aTimer) {
    if (aTimer == this._showTimer) {
      if (!this.mDialog) {
        this.reallyShow();
      }
      // The timer won't release us, so we have to release it.
      this._showTimer = null;
    }
    else if (aTimer == this._saveToDiskTimer) {
      // Since saveToDisk may open a file picker and therefore block this routine,
      // we should only call it once the dialog is closed.
      this.mLauncher.saveToDisk(null, false);
      this._saveToDiskTimer = null;
    }
  },

  postShowCallback: function () {
    this.mDialog.sizeToContent();

    // Set initial focus
    this.dialogElement("mode").focus();
  },

  // initIntro:
  initIntro: function(url, filename, displayname) {
    this.dialogElement( "location" ).value = displayname;
    this.dialogElement( "location" ).setAttribute("realname", filename);
    this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);

    // if mSourcePath is a local file, then let's use the pretty path name
    // instead of an ugly url...
    var pathString;
    if (url instanceof Components.interfaces.nsIFileURL) {
      try {
        // Getting .file might throw, or .parent could be null
        pathString = url.file.parent.path;
      } catch (ex) {}
    }

    if (!pathString) {
      // wasn't a fileURL
      var tmpurl = url.clone(); // don't want to change the real url
      try {
        tmpurl.userPass = "";
      } catch (ex) {}
      pathString = tmpurl.prePath;
    }

    // Set the location text, which is separate from the intro text so it can be cropped
    var location = this.dialogElement( "source" );
    location.value = pathString;
    location.setAttribute("tooltiptext", this.mSourcePath);

    // Show the type of file.
    var type = this.dialogElement("type");
    var mimeInfo = this.mLauncher.MIMEInfo;

    // 1. Try to use the pretty description of the type, if one is available.
    var typeString = mimeInfo.description;

    if (typeString == "") {
      // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
      var primaryExtension = "";
      try {
        primaryExtension = mimeInfo.primaryExtension;
      }
      catch (ex) {
      }
      if (primaryExtension != "")
        typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
      // 3. If we can't even do that, just give up and show the MIME type.
      else
        typeString = mimeInfo.MIMEType;
    }
    // When the length is unknown, contentLength would be -1
    if (this.mLauncher.contentLength >= 0) {
      let [size, unit] = DownloadUtils.
                         convertByteUnits(this.mLauncher.contentLength);
      type.value = this.dialogElement("strings")
                       .getFormattedString("orderedFileSizeWithType", 
                                           [typeString, size, unit]);
    }
    else {
      type.value = typeString;
    }
  },

  // Returns true if opening the default application makes sense.
  openWithDefaultOK: function() {
    // The checking is different on Windows...
    if (AppConstants.platform == "win") {
      // Windows presents some special cases.
      // We need to prevent use of "system default" when the file is
      // executable (so the user doesn't launch nasty programs downloaded
      // from the web), and, enable use of "system default" if it isn't
      // executable (because we will prompt the user for the default app
      // in that case).

      //  Default is Ok if the file isn't executable (and vice-versa).
      return !this.mLauncher.targetFileIsExecutable;
    }
    // On other platforms, default is Ok if there is a default app.
    // Note that nsIMIMEInfo providers need to ensure that this holds true
    // on each platform.
    return this.mLauncher.MIMEInfo.hasDefaultHandler;
  },

  // Set "default" application description field.
  initDefaultApp: function() {
    // Use description, if we can get one.
    var desc = this.mLauncher.MIMEInfo.defaultDescription;
    if (desc) {
      var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
      this.dialogElement("defaultHandler").label = defaultApp;
    }
    else {
      this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
      // Hide the default handler item too, in case the user picks a
      // custom handler at a later date which triggers the menulist to show.
      this.dialogElement("defaultHandler").hidden = true;
    }
  },

  // getPath:
  getPath: function (aFile) {
    if (AppConstants.platform == "macosx") {
      return aFile.leafName || aFile.path;
    }
    return aFile.path;
  },

  // initAppAndSaveToDiskValues:
  initAppAndSaveToDiskValues: function() {
    var modeGroup = this.dialogElement("mode");

    // We don't let users open .exe files or random binary data directly
    // from the browser at the moment because of security concerns.
    var openWithDefaultOK = this.openWithDefaultOK();
    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
    if (this.mLauncher.targetFileIsExecutable || (
      (mimeType == "application/octet-stream" ||
       mimeType == "application/x-msdownload") &&
        !openWithDefaultOK)) {
      this.dialogElement("open").disabled = true;
      var openHandler = this.dialogElement("openHandler");
      openHandler.disabled = true;
      openHandler.selectedItem = null;
      modeGroup.selectedItem = this.dialogElement("save");
      return;
    }

    // Fill in helper app info, if there is any.
    try {
      this.chosenApp =
        this.mLauncher.MIMEInfo.preferredApplicationHandler
                               .QueryInterface(Components.interfaces.nsILocalHandlerApp);
    } catch (e) {
      this.chosenApp = null;
    }
    // Initialize "default application" field.
    this.initDefaultApp();

    var otherHandler = this.dialogElement("otherHandler");

    // Fill application name textbox.
    if (this.chosenApp && this.chosenApp.executable &&
        this.chosenApp.executable.path) {
      otherHandler.setAttribute("path",
                                this.getPath(this.chosenApp.executable));

      otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
      otherHandler.hidden = false;
    }

    var openHandler = this.dialogElement("openHandler");
    openHandler.selectedIndex = 0;
    var defaultOpenHandler = this.dialogElement("defaultHandler");

    if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
      // Open (using system default).
      modeGroup.selectedItem = this.dialogElement("open");
    } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
      // Open with given helper app.
      modeGroup.selectedItem = this.dialogElement("open");
      openHandler.selectedItem = (otherHandler && !otherHandler.hidden) ?
                                 otherHandler : defaultOpenHandler;
    } else {
      // Save to disk.
      modeGroup.selectedItem = this.dialogElement("save");
    }

    // If we don't have a "default app" then disable that choice.
    if (!openWithDefaultOK) {
      var isSelected = defaultOpenHandler.selected;

      // Disable that choice.
      defaultOpenHandler.hidden = true;
      // If that's the default, then switch to "save to disk."
      if (isSelected) {
        openHandler.selectedIndex = 1;
        modeGroup.selectedItem = this.dialogElement("save");
      }
    }

    otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
    this.updateOKButton();
  },

  // Returns the user-selected application
  helperAppChoice: function() {
    return this.chosenApp;
  },

  get saveToDisk() {
    return this.dialogElement("save").selected;
  },

  get useOtherHandler() {
    return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
  },

  get useSystemDefault() {
    return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
  },

  toggleRememberChoice: function (aCheckbox) {
    this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
    this.mDialog.sizeToContent();
  },

  openHandlerCommand: function () {
    var openHandler = this.dialogElement("openHandler");
    if (openHandler.selectedItem.id == "choose")
      this.chooseApp();
    else
      openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
  },

  updateOKButton: function() {
    var ok = false;
    if (this.dialogElement("save").selected) {
      // This is always OK.
      ok = true;
    }
    else if (this.dialogElement("open").selected) {
      switch (this.dialogElement("openHandler").selectedIndex) {
      case 0:
        // No app need be specified in this case.
        ok = true;
        break;
      case 1:
        // only enable the OK button if we have a default app to use or if
        // the user chose an app....
        ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
        break;
      }
    }

    // Enable Ok button if ok to press.
    this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
  },

  // Returns true iff the user-specified helper app has been modified.
  appChanged: function() {
    return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  },

  updateMIMEInfo: function() {
    // Don't update mime type preferences when the preferred action is set to
    // the internal handler -- this dialog is the result of the handler fallback
    // (e.g. Content-Disposition was set as attachment)
    var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally &&
                        !this.dialogElement("rememberChoice").checked;

    var needUpdate = false;
    // If current selection differs from what's in the mime info object,
    // then we need to update.
    if (this.saveToDisk) {
      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
      if (needUpdate)
        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
    }
    else if (this.useSystemDefault) {
      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
      if (needUpdate)
        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
    }
    else {
      // For "open with", we need to check both preferred action and whether the user chose
      // a new app.
      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
      if (needUpdate) {
        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
        // App may have changed - Update application
        var app = this.helperAppChoice();
        this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
      }
    }
    // We will also need to update if the "always ask" flag has changed.
    needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);

    // One last special case: If the input "always ask" flag was false, then we always
    // update.  In that case we are displaying the helper app dialog for the first
    // time for this mime type and we need to store the user's action in the mimeTypes.rdf
    // data source (whether that action has changed or not; if it didn't change, then we need
    // to store the "always ask" flag so the helper app dialog will or won't display
    // next time, per the user's selection).
    needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;

    // Make sure mime info has updated setting for the "always ask" flag.
    this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;

    return needUpdate && !discardUpdate;
  },

  // See if the user changed things, and if so, update the
  // mimeTypes.rdf entry for this mime type.
  updateHelperAppPref: function() {
    var handlerInfo = this.mLauncher.MIMEInfo;
    var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
    hs.store(handlerInfo);
  },

  // onOK:
  onOK: function() {
    // Verify typed app path, if necessary.
    if (this.useOtherHandler) {
      var helperApp = this.helperAppChoice();
      if (!helperApp || !helperApp.executable ||
          !helperApp.executable.exists()) {
        // Show alert and try again.
        var bundle = this.dialogElement("strings");
        var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
        Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg);

        // Disable the OK button.
        this.mDialog.document.documentElement.getButton("accept").disabled = true;
        this.dialogElement("mode").focus();

        // Clear chosen application.
        this.chosenApp = null;

        // Leave dialog up.
        return false;
      }
    }

    // Remove our web progress listener (a progress dialog will be
    // taking over).
    this.mLauncher.setWebProgressListener(null);

    // saveToDisk and launchWithApplication can return errors in
    // certain circumstances (e.g. The user clicks cancel in the
    // "Save to Disk" dialog. In those cases, we don't want to
    // update the helper application preferences in the RDF file.
    try {
      var needUpdate = this.updateMIMEInfo();

      if (this.dialogElement("save").selected) {
        // If we're using a default download location, create a path
        // for the file to be saved to to pass to |saveToDisk| - otherwise
        // we must ask the user to pick a save name.

        /*
        var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
        var targetFile = null;
        try {
          targetFile = prefs.getComplexValue("browser.download.defaultFolder",
                                             Components.interfaces.nsILocalFile);
          var leafName = this.dialogElement("location").getAttribute("realname");
          // Ensure that we don't overwrite any existing files here.
          targetFile = this.validateLeafName(targetFile, leafName, null);
        }
        catch(e) { }

        this.mLauncher.saveToDisk(targetFile, false);
        */

        // see @notify
        // we cannot use opener's setTimeout, see bug 420405
        this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
                                          .createInstance(nsITimer);
        this._saveToDiskTimer.initWithCallback(this, 0,
                                               nsITimer.TYPE_ONE_SHOT);
      }
      else
        this.mLauncher.launchWithApplication(null, false);

      // Update user pref for this mime type (if necessary). We do not
      // store anything in the mime type preferences for the ambiguous
      // type application/octet-stream. We do NOT do this for
      // application/x-msdownload since we want users to be able to
      // autodownload these to disk.
      if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
        this.updateHelperAppPref();
    } catch(e) { }

    // Unhook dialog from this object.
    this.mDialog.dialog = null;

    // Close up dialog by returning true.
    return true;
  },

  // onCancel:
  onCancel: function() {
    // Remove our web progress listener.
    this.mLauncher.setWebProgressListener(null);

    // Cancel app launcher.
    try {
      this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
    } catch(exception) {
    }

    // Unhook dialog from this object.
    this.mDialog.dialog = null;

    // Close up dialog by returning true.
    return true;
  },

  // dialogElement:  Convenience.
  dialogElement: function(id) {
    return this.mDialog.document.getElementById(id);
  },

  // Retrieve the pretty description from the file
  getFileDisplayName: function getFileDisplayName(file)
  {
    if (AppConstants.platform == "win") {
      if (file instanceof Components.interfaces.nsILocalFileWin) {
        try {
          return file.getVersionInfoField("FileDescription");
        } catch (e) {}
      }
    } else if (AppConstants.platform == "macosx") {
      if (file instanceof Components.interfaces.nsILocalFileMac) {
        try {
          return file.bundleDisplayName;
        } catch (e) {}
      }
    }
    return file.leafName;
  },

  finishChooseApp: function() {
    if (this.chosenApp) {
      // Show the "handler" menulist since we have a (user-specified)
      // application now.
      this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");

      // Update dialog.
      var otherHandler = this.dialogElement("otherHandler");
      otherHandler.removeAttribute("hidden");
      otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
      if (AppConstants.platform == "win")
        otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
      else
        otherHandler.label = this.chosenApp.name;
      this.dialogElement("openHandler").selectedIndex = 1;
      this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");

      this.dialogElement("mode").selectedItem = this.dialogElement("open");
    }
    else {
      var openHandler = this.dialogElement("openHandler");
      var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
      if (!lastSelectedID)
        lastSelectedID = "defaultHandler";
      openHandler.selectedItem = this.dialogElement(lastSelectedID);
    }
  },
  // chooseApp:  Open file picker and prompt user for application.
  chooseApp: function() {
    if (AppConstants.platform == "win") {
      // Protect against the lack of an extension
      var fileExtension = "";
      try {
        fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
      } catch(ex) {
      }

      // Try to use the pretty description of the type, if one is available.
      var typeString = this.mLauncher.MIMEInfo.description;

      if (!typeString) {
        // If there is none, use the extension to
        // identify the file, e.g. "ZIP file"
        if (fileExtension) {
          typeString =
            this.dialogElement("strings").
            getFormattedString("fileType", [fileExtension.toUpperCase()]);
        } else {
          // If we can't even do that, just give up and show the MIME type.
          typeString = this.mLauncher.MIMEInfo.MIMEType;
        }
      }

      var params = {};
      params.title =
        this.dialogElement("strings").getString("chooseAppFilePickerTitle");
      params.description = typeString;
      params.filename    = this.mLauncher.suggestedFileName;
      params.mimeInfo    = this.mLauncher.MIMEInfo;
      params.handlerApp  = null;

      this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
                              "chrome,modal,centerscreen,titlebar,dialog=yes",
                              params);

      if (params.handlerApp &&
          params.handlerApp.executable &&
          params.handlerApp.executable.isFile()) {
        // Remember the file they chose to run.
        this.chosenApp = params.handlerApp;
      }
    } else if ("@mozilla.org/applicationchooser;1" in Components.classes) {
      var nsIApplicationChooser = Components.interfaces.nsIApplicationChooser;
      var appChooser = Components.classes["@mozilla.org/applicationchooser;1"]
                                 .createInstance(nsIApplicationChooser);
      appChooser.init(this.mDialog, this.dialogElement("strings").getString("chooseAppFilePickerTitle"));
      var contentTypeDialogObj = this;
      let appChooserCallback = function appChooserCallback_done(aResult) {
        if (aResult) {
           contentTypeDialogObj.chosenApp = aResult.QueryInterface(Components.interfaces.nsILocalHandlerApp);
        }
        contentTypeDialogObj.finishChooseApp();
      };
      appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
      // The finishChooseApp is called from appChooserCallback
      return;
    } else {
      var nsIFilePicker = Components.interfaces.nsIFilePicker;
      var fp = Components.classes["@mozilla.org/filepicker;1"]
                         .createInstance(nsIFilePicker);
      fp.init(this.mDialog,
              this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
              nsIFilePicker.modeOpen);

      fp.appendFilters(nsIFilePicker.filterApps);

      if (fp.show() == nsIFilePicker.returnOK && fp.file) {
        // Remember the file they chose to run.
        var localHandlerApp =
          Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
                     createInstance(Components.interfaces.nsILocalHandlerApp);
        localHandlerApp.executable = fp.file;
        this.chosenApp = localHandlerApp;
      }
    }

    this.finishChooseApp();
  },

  // Turn this on to get debugging messages.
  debug: false,

  // Dump text (if debug is on).
  dump: function( text ) {
    if ( this.debug ) {
      dump( text );
    }
  },

  // dumpObj:
  dumpObj: function( spec ) {
    var val = "<undefined>";
    try {
      val = eval( "this."+spec ).toString();
    } catch( exception ) {
    }
    this.dump( spec + "=" + val + "\n" );
  },

  // dumpObjectProperties
  dumpObjectProperties: function( desc, obj ) {
    for( prop in obj ) {
      this.dump( desc + "." + prop + "=" );
      var val = "<undefined>";
      try {
        val = obj[ prop ];
      } catch ( exception ) {
      }
      this.dump( val + "\n" );
    }
  }
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]);
PK
!<<~V//(components/NetworkGeolocationProvider.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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://gre/modules/Services.jsm");

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

const POSITION_UNAVAILABLE = Ci.nsIDOMGeoPositionError.POSITION_UNAVAILABLE;

var gLoggingEnabled = false;

/*
   The gLocationRequestTimeout controls how long we wait on receiving an update
   from the Wifi subsystem.  If this timer fires, we believe the Wifi scan has
   had a problem and we no longer can use Wifi to position the user this time
   around (we will continue to be hopeful that Wifi will recover).

   This timeout value is also used when Wifi scanning is disabled (see
   gWifiScanningEnabled).  In this case, we use this timer to collect cell/ip
   data and xhr it to the location server.
*/

var gLocationRequestTimeout = 5000;

var gWifiScanningEnabled = true;

function LOG(aMsg) {
  if (gLoggingEnabled) {
    aMsg = "*** WIFI GEO: " + aMsg + "\n";
    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(aMsg);
    dump(aMsg);
  }
}

function CachedRequest(loc, cellInfo, wifiList) {
  this.location = loc;

  let wifis = new Set();
  if (wifiList) {
    for (let i = 0; i < wifiList.length; i++) {
      wifis.add(wifiList[i].macAddress);
    }
  }

  // Use only these values for equality
  // (the JSON will contain additional values in future)
  function makeCellKey(cell) {
    return "" + cell.radio + ":" + cell.mobileCountryCode + ":" +
    cell.mobileNetworkCode + ":" + cell.locationAreaCode + ":" +
    cell.cellId;
  }

  let cells = new Set();
  if (cellInfo) {
    for (let i = 0; i < cellInfo.length; i++) {
      cells.add(makeCellKey(cellInfo[i]));
    }
  }

  this.hasCells = () => cells.size > 0;

  this.hasWifis = () => wifis.size > 0;

  // if fields match
  this.isCellEqual = function(cellInfo) {
    if (!this.hasCells()) {
      return false;
    }

    let len1 = cells.size;
    let len2 = cellInfo.length;

    if (len1 != len2) {
      LOG("cells not equal len");
      return false;
    }

    for (let i = 0; i < len2; i++) {
      if (!cells.has(makeCellKey(cellInfo[i]))) {
        return false;
      }
    }
    return true;
  };

  // if 50% of the SSIDS match
  this.isWifiApproxEqual = function(wifiList) {
    if (!this.hasWifis()) {
      return false;
    }

    // if either list is a 50% subset of the other, they are equal
    let common = 0;
    for (let i = 0; i < wifiList.length; i++) {
      if (wifis.has(wifiList[i].macAddress)) {
        common++;
      }
    }
    let kPercentMatch = 0.5;
    return common >= (Math.max(wifis.size, wifiList.length) * kPercentMatch);
  };

  this.isGeoip = function() {
    return !this.hasCells() && !this.hasWifis();
  };

  this.isCellAndWifi = function() {
    return this.hasCells() && this.hasWifis();
  };

  this.isCellOnly = function() {
    return this.hasCells() && !this.hasWifis();
  };

  this.isWifiOnly = function() {
    return this.hasWifis() && !this.hasCells();
  };
 }

var gCachedRequest = null;
var gDebugCacheReasoning = ""; // for logging the caching logic

// This function serves two purposes:
// 1) do we have a cached request
// 2) is the cached request better than what newCell and newWifiList will obtain
// If the cached request exists, and we know it to have greater accuracy
// by the nature of its origin (wifi/cell/geoip), use its cached location.
//
// If there is more source info than the cached request had, return false
// In other cases, MLS is known to produce better/worse accuracy based on the
// inputs, so base the decision on that.
function isCachedRequestMoreAccurateThanServerRequest(newCell, newWifiList)
{
  gDebugCacheReasoning = "";
  let isNetworkRequestCacheEnabled = true;
  try {
    // Mochitest needs this pref to simulate request failure
    isNetworkRequestCacheEnabled = Services.prefs.getBoolPref("geo.wifi.debug.requestCache.enabled");
    if (!isNetworkRequestCacheEnabled) {
      gCachedRequest = null;
    }
  } catch (e) {}

  if (!gCachedRequest || !isNetworkRequestCacheEnabled) {
    gDebugCacheReasoning = "No cached data";
    return false;
  }

  if (!newCell && !newWifiList) {
    gDebugCacheReasoning = "New req. is GeoIP.";
    return true;
  }

  if (newCell && newWifiList && (gCachedRequest.isCellOnly() || gCachedRequest.isWifiOnly())) {
    gDebugCacheReasoning = "New req. is cell+wifi, cache only cell or wifi.";
    return false;
  }

  if (newCell && gCachedRequest.isWifiOnly()) {
    // In order to know if a cell-only request should trump a wifi-only request
    // need to know if wifi is low accuracy. >5km would be VERY low accuracy,
    // it is worth trying the cell
    var isHighAccuracyWifi = gCachedRequest.location.coords.accuracy < 5000;
    gDebugCacheReasoning = "Req. is cell, cache is wifi, isHigh:" + isHighAccuracyWifi;
    return isHighAccuracyWifi;
  }

  let hasEqualCells = false;
  if (newCell) {
    hasEqualCells = gCachedRequest.isCellEqual(newCell);
  }

  let hasEqualWifis = false;
  if (newWifiList) {
    hasEqualWifis = gCachedRequest.isWifiApproxEqual(newWifiList);
  }

  gDebugCacheReasoning = "EqualCells:" + hasEqualCells + " EqualWifis:" + hasEqualWifis;

  if (gCachedRequest.isCellOnly()) {
    gDebugCacheReasoning += ", Cell only.";
    if (hasEqualCells) {
      return true;
    }
  } else if (gCachedRequest.isWifiOnly() && hasEqualWifis) {
    gDebugCacheReasoning +=", Wifi only."
    return true;
  } else if (gCachedRequest.isCellAndWifi()) {
     gDebugCacheReasoning += ", Cache has Cell+Wifi.";
    if ((hasEqualCells && hasEqualWifis) ||
        (!newWifiList && hasEqualCells) ||
        (!newCell && hasEqualWifis))
    {
     return true;
    }
  }

  return false;
}

function WifiGeoCoordsObject(lat, lon, acc, alt, altacc) {
  this.latitude = lat;
  this.longitude = lon;
  this.accuracy = acc;
  this.altitude = alt;
  this.altitudeAccuracy = altacc;
}

WifiGeoCoordsObject.prototype = {
  QueryInterface:  XPCOMUtils.generateQI([Ci.nsIDOMGeoPositionCoords])
};

function WifiGeoPositionObject(lat, lng, acc) {
  this.coords = new WifiGeoCoordsObject(lat, lng, acc, 0, 0);
  this.address = null;
  this.timestamp = Date.now();
}

WifiGeoPositionObject.prototype = {
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIDOMGeoPosition])
};

function WifiGeoPositionProvider() {
  try {
    gLoggingEnabled = Services.prefs.getBoolPref("geo.wifi.logging.enabled");
  } catch (e) {}

  try {
    gLocationRequestTimeout = Services.prefs.getIntPref("geo.wifi.timeToWaitBeforeSending");
  } catch (e) {}

  try {
    gWifiScanningEnabled = Services.prefs.getBoolPref("geo.wifi.scan");
  } catch (e) {}

  this.wifiService = null;
  this.timer = null;
  this.started = false;
}

WifiGeoPositionProvider.prototype = {
  classID:          Components.ID("{77DA64D3-7458-4920-9491-86CC9914F904}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIGeolocationProvider,
                                           Ci.nsIWifiListener,
                                           Ci.nsITimerCallback,
                                           Ci.nsIObserver]),
  listener: null,

  resetTimer: function() {
    if (this.timer) {
      this.timer.cancel();
      this.timer = null;
    }
    // wifi thread triggers WifiGeoPositionProvider to proceed, with no wifi, do manual timeout
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.initWithCallback(this,
                                gLocationRequestTimeout,
                                this.timer.TYPE_REPEATING_SLACK);
  },

  startup:  function() {
    if (this.started)
      return;

    this.started = true;
    let self = this;

    if (gWifiScanningEnabled && Cc["@mozilla.org/wifi/monitor;1"]) {
      if (this.wifiService) {
        this.wifiService.stopWatching(this);
      }
      this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(Ci.nsIWifiMonitor);
      this.wifiService.startWatching(this);
    }

    this.resetTimer();
    LOG("startup called.");
  },

  watch: function(c) {
    this.listener = c;
  },

  shutdown: function() {
    LOG("shutdown called");
    if (this.started == false) {
      return;
    }

    // Without clearing this, we could end up using the cache almost indefinitely
    // TODO: add logic for cache lifespan, for now just be safe and clear it
    gCachedRequest = null;

    if (this.timer) {
      this.timer.cancel();
      this.timer = null;
    }

    if(this.wifiService) {
      this.wifiService.stopWatching(this);
      this.wifiService = null;
    }

    this.listener = null;
    this.started = false;
  },

  setHighAccuracy: function(enable) {
  },

  onChange: function(accessPoints) {

    // we got some wifi data, rearm the timer.
    this.resetTimer();

    function isPublic(ap) {
      let mask = "_nomap"
      let result = ap.ssid.indexOf(mask, ap.ssid.length - mask.length);
      if (result != -1) {
        LOG("Filtering out " + ap.ssid + " " + result);
        return false;
      }
      return true;
    };

    function sort(a, b) {
      return b.signal - a.signal;
    };

    function encode(ap) {
      return { 'macAddress': ap.mac, 'signalStrength': ap.signal };
    };

    let wifiData = null;
    if (accessPoints) {
      wifiData = accessPoints.filter(isPublic).sort(sort).map(encode);
    }
    this.sendLocationRequest(wifiData);
  },

  onError: function (code) {
    LOG("wifi error: " + code);
    this.sendLocationRequest(null);
  },

  notify: function (timer) {
    this.sendLocationRequest(null);
  },

  sendLocationRequest: function (wifiData) {
    let data = { cellTowers: undefined, wifiAccessPoints: undefined };
    if (wifiData && wifiData.length >= 2) {
      data.wifiAccessPoints = wifiData;
    }

    let useCached = isCachedRequestMoreAccurateThanServerRequest(data.cellTowers,
                                                                 data.wifiAccessPoints);

    LOG("Use request cache:" + useCached + " reason:" + gDebugCacheReasoning);

    if (useCached) {
      gCachedRequest.location.timestamp = Date.now();
      this.listener.update(gCachedRequest.location);
      return;
    }

    // From here on, do a network geolocation request //
    let url = Services.urlFormatter.formatURLPref("geo.wifi.uri");
    LOG("Sending request");

    let xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                        .createInstance(Ci.nsIXMLHttpRequest);

    try {
      xhr.open("POST", url, true);
      xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS;
    } catch (e) {
      this.listener.notifyError(POSITION_UNAVAILABLE);
      return;
    }
    xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    xhr.responseType = "json";
    xhr.mozBackgroundRequest = true;
    xhr.timeout = Services.prefs.getIntPref("geo.wifi.xhr.timeout");
    xhr.ontimeout = () => {
      LOG("Location request XHR timed out.")
      this.listener.notifyError(POSITION_UNAVAILABLE);
    };
    xhr.onerror = () => {
      this.listener.notifyError(POSITION_UNAVAILABLE);
    };
    xhr.onload = () => {
      LOG("server returned status: " + xhr.status + " --> " +  JSON.stringify(xhr.response));
      if ((xhr.channel instanceof Ci.nsIHttpChannel && xhr.status != 200) ||
          !xhr.response || !xhr.response.location) {
        this.listener.notifyError(POSITION_UNAVAILABLE);
        return;
      }

      let newLocation = new WifiGeoPositionObject(xhr.response.location.lat,
                                                  xhr.response.location.lng,
                                                  xhr.response.accuracy);

      this.listener.update(newLocation);
      gCachedRequest = new CachedRequest(newLocation, data.cellTowers, data.wifiAccessPoints);
    };

    var requestData = JSON.stringify(data);
    LOG("sending " + requestData);
    xhr.send(requestData);
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiGeoPositionProvider]);
PK
!<Lxecomponents/EditorUtils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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;

"use strict";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const EDITORUTILS_CID = Components.ID('{12e63991-86ac-4dff-bb1a-703495d67d17}');

function EditorUtils() {
}

EditorUtils.prototype = {
  classID: EDITORUTILS_CID,
  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIEditorUtils ]),

  slurpBlob(aBlob, aScope, aListener) {
    let reader = new aScope.FileReader();
    reader.addEventListener("load", (event) => {
      aListener.onResult(event.target.result);
    });
    reader.addEventListener("error", (event) => {
      aListener.onError(event.target.error.message);
    });

    reader.readAsBinaryString(aBlob);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EditorUtils]);
PK
!<!!components/addonManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component serves as integration between the platform and AddonManager.
 * It is responsible for initializing and shutting down the AddonManager as well
 * as passing new installs from webpages to the AddonManager.
 */

"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

// The old XPInstall error codes
const EXECUTION_ERROR   = -203;
const CANT_READ_ARCHIVE = -207;
const USER_CANCELLED    = -210;
const DOWNLOAD_ERROR    = -228;
const UNSUPPORTED_TYPE  = -244;
const SUCCESS           = 0;

const MSG_INSTALL_ENABLED  = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDON    = "WebInstallerInstallAddonFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";

const MSG_PROMISE_REQUEST  = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT   = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT    = "WebAPIInstallEvent";
const MSG_INSTALL_CLEANUP  = "WebAPICleanup";
const MSG_ADDON_EVENT_REQ  = "WebAPIAddonEventRequest";
const MSG_ADDON_EVENT      = "WebAPIAddonEvent";

const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

var gSingleton = null;

function amManager() {
  Cu.import("resource://gre/modules/AddonManager.jsm");
  /* globals AddonManagerPrivate*/

  Services.mm.loadFrameScript(CHILD_SCRIPT, true);
  Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
  Services.mm.addMessageListener(MSG_INSTALL_ADDON, this);
  Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
  Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
  Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this);

  Services.obs.addObserver(this, "message-manager-close");
  Services.obs.addObserver(this, "message-manager-disconnect");

  AddonManager.webAPI.setEventHandler(this.sendEvent);

  // Needed so receiveMessage can be called directly by JS callers
  this.wrappedJSObject = this;
}

amManager.prototype = {
  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case "addons-startup":
        AddonManagerPrivate.startup();
        break;

      case "message-manager-close":
      case "message-manager-disconnect":
        this.childClosed(aSubject);
        break;
    }
  },

  /**
   * @see amIAddonManager.idl
   */
  mapURIToAddonID(uri, id) {
    id.value = AddonManager.mapURIToAddonID(uri);
    return !!id.value;
  },

  installAddonFromWebpage(aMimetype, aBrowser, aInstallingPrincipal,
                                    aUri, aHash, aName, aIcon, aCallback) {
    let retval = true;
    if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
      aCallback = null;
      retval = false;
    }

    AddonManager.getInstallForURL(aUri, function(aInstall) {
      function callCallback(uri, status) {
        try {
          aCallback.onInstallEnded(uri, status);
        } catch (e) {
          Components.utils.reportError(e);
        }
      }

      if (!aInstall) {
        aCallback.onInstallEnded(aUri, UNSUPPORTED_TYPE);
        return;
      }

      if (aCallback) {
        aInstall.addListener({
          onDownloadCancelled(aInstall) {
            callCallback(aUri, USER_CANCELLED);
          },

          onDownloadFailed(aInstall) {
            if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE)
              callCallback(aUri, CANT_READ_ARCHIVE);
            else
              callCallback(aUri, DOWNLOAD_ERROR);
          },

          onInstallFailed(aInstall) {
            callCallback(aUri, EXECUTION_ERROR);
          },

          onInstallEnded(aInstall, aStatus) {
            callCallback(aUri, SUCCESS);
          }
        });
      }

      AddonManager.installAddonFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, aInstall);
    }, aMimetype, aHash, aName, aIcon, null, aBrowser);

    return retval;
  },

  notify(aTimer) {
    AddonManagerPrivate.backgroundUpdateTimerHandler();
  },

  // Maps message manager instances for content processes to the associated
  // AddonListener instances.
  addonListeners: new Map(),

  _addAddonListener(target) {
    if (!this.addonListeners.has(target)) {
      let handler = (event, id, needsRestart) => {
        target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart});
      };
      let listener = {
        onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart),
        onEnabled: (addon) => handler("onEnabled", addon.id, false),
        onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart),
        onDisabled: (addon) => handler("onDisabled", addon.id, false),
        onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart),
        onInstalled: (addon) => handler("onInstalled", addon.id, false),
        onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart),
        onUninstalled: (addon) => handler("onUninstalled", addon.id, false),
        onOperationCancelled: (addon) => handler("onOperationCancelled", addon.id, false),
      };
      this.addonListeners.set(target, listener);
      AddonManager.addAddonListener(listener);
    }
  },

  _removeAddonListener(target) {
    if (this.addonListeners.has(target)) {
      AddonManager.removeAddonListener(this.addonListeners.get(target));
      this.addonListeners.delete(target);
    }
  },

  /**
   * messageManager callback function.
   *
   * Listens to requests from child processes for InstallTrigger
   * activity, and sends back callbacks.
   */
  receiveMessage(aMessage) {
    let payload = aMessage.data;

    switch (aMessage.name) {
      case MSG_INSTALL_ENABLED:
        return AddonManager.isInstallEnabled(payload.mimetype);

      case MSG_INSTALL_ADDON: {
        let callback = null;
        if (payload.callbackID != -1) {
          let mm = aMessage.target.messageManager;
          callback = {
            onInstallEnded(url, status) {
              mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, {
                callbackID: payload.callbackID,
                url,
                status
              });
            },
          };
        }

        return this.installAddonFromWebpage(payload.mimetype,
          aMessage.target, payload.triggeringPrincipal, payload.uri,
          payload.hash, payload.name, payload.icon, callback);
      }

      case MSG_PROMISE_REQUEST: {
        let mm = aMessage.target.messageManager;
        let resolve = (value) => {
          mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
            callbackID: payload.callbackID,
            resolve: value
          });
        }
        let reject = (value) => {
          mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
            callbackID: payload.callbackID,
            reject: value
          });
        }

        let API = AddonManager.webAPI;
        if (payload.type in API) {
          API[payload.type](aMessage.target, ...payload.args).then(resolve, reject);
        } else {
          reject("Unknown Add-on API request.");
        }
        break;
      }

      case MSG_INSTALL_CLEANUP: {
        AddonManager.webAPI.clearInstalls(payload.ids);
        break;
      }

      case MSG_ADDON_EVENT_REQ: {
        let target = aMessage.target.messageManager;
        if (payload.enabled) {
          this._addAddonListener(target);
        } else {
          this._removeAddonListener(target);
        }
      }
    }
    return undefined;
  },

  childClosed(target) {
    AddonManager.webAPI.clearInstallsFrom(target);
    this._removeAddonListener(target);
  },

  sendEvent(mm, data) {
    mm.sendAsyncMessage(MSG_INSTALL_EVENT, data);
  },

  classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
  _xpcom_factory: {
    createInstance(aOuter, aIid) {
      if (aOuter != null)
        throw Components.Exception("Component does not support aggregation",
                                   Cr.NS_ERROR_NO_AGGREGATION);

      if (!gSingleton)
        gSingleton = new amManager();
      return gSingleton.QueryInterface(aIid);
    }
  },
  QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager,
                                         Ci.nsITimerCallback,
                                         Ci.nsIObserver,
                                         Ci.nsIMessageListener])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]);
PK
!<0
0
components/amContentHandler.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

const XPI_CONTENT_TYPE = "application/x-xpinstall";
const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

function amContentHandler() {
}

amContentHandler.prototype = {
  /**
   * Handles a new request for an application/x-xpinstall file.
   *
   * @param  aMimetype
   *         The mimetype of the file
   * @param  aContext
   *         The context passed to nsIChannel.asyncOpen
   * @param  aRequest
   *         The nsIRequest dealing with the content
   */
  handleContent(aMimetype, aContext, aRequest) {
    if (aMimetype != XPI_CONTENT_TYPE)
      throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;

    if (!(aRequest instanceof Ci.nsIChannel))
      throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;

    let uri = aRequest.URI;

    let window = null;
    let callbacks = aRequest.notificationCallbacks ?
                    aRequest.notificationCallbacks :
                    aRequest.loadGroup.notificationCallbacks;
    if (callbacks)
      window = callbacks.getInterface(Ci.nsIDOMWindow);

    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    let install = {
      uri: uri.spec,
      hash: null,
      name: null,
      icon: null,
      mimetype: XPI_CONTENT_TYPE,
      triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal,
      callbackID: -1
    };

    if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
      // When running in the main process this might be a frame inside an
      // in-content UI page, walk up to find the first frame element in a chrome
      // privileged document
      let element = window.frameElement;
      let ssm = Services.scriptSecurityManager;
      while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
        element = element.ownerGlobal.frameElement;

      if (element) {
        let listener = Cc["@mozilla.org/addons/integration;1"].
                       getService(Ci.nsIMessageListener);
        listener.wrappedJSObject.receiveMessage({
          name: MSG_INSTALL_ADDON,
          target: element,
          data: install
        });
        return;
      }
    }

    // Fall back to sending through the message manager
    let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIDocShell)
                               .QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIContentFrameMessageManager);

    messageManager.sendAsyncMessage(MSG_INSTALL_ADDON, install);
  },

  classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]),

  log(aMsg) {
    let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
      logStringMessage(msg);
    dump(msg + "\n");
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]);
PK
!<"xcomponents/amInstallTrigger.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");

const XPINSTALL_MIMETYPE   = "application/x-xpinstall";

const MSG_INSTALL_ENABLED  = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDON    = "WebInstallerInstallAddonFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";


var log = Log.repository.getLogger("AddonManager.InstallTrigger");
log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];

function CallbackObject(id, callback, mediator) {
  this.id = id;
  this.callback = callback;
  this.callCallback = function(url, status) {
    try {
      this.callback(url, status);
    } catch (e) {
      log.warn("InstallTrigger callback threw an exception: " + e);
    }

    mediator._callbacks.delete(id);
  };
}

function RemoteMediator(window) {
  window.QueryInterface(Ci.nsIInterfaceRequestor);
  let utils = window.getInterface(Ci.nsIDOMWindowUtils);
  this._windowID = utils.currentInnerWindowID;

  this.mm = window
    .getInterface(Ci.nsIDocShell)
    .QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIContentFrameMessageManager);
  this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this);

  this._lastCallbackID = 0;
  this._callbacks = new Map();
}

RemoteMediator.prototype = {
  receiveMessage(message) {
    if (message.name == MSG_INSTALL_CALLBACK) {
      let payload = message.data;
      let callbackHandler = this._callbacks.get(payload.callbackID);
      if (callbackHandler) {
        callbackHandler.callCallback(payload.url, payload.status);
      }
    }
  },

  enabled(url) {
    let params = {
      mimetype: XPINSTALL_MIMETYPE
    };
    return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
  },

  install(install, principal, callback, window) {
    let callbackID = this._addCallback(callback);

    install.mimetype = XPINSTALL_MIMETYPE;
    install.triggeringPrincipal = principal;
    install.callbackID = callbackID;

    if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
      // When running in the main process this might be a frame inside an
      // in-content UI page, walk up to find the first frame element in a chrome
      // privileged document
      let element = window.frameElement;
      let ssm = Services.scriptSecurityManager;
      while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
        element = element.ownerGlobal.frameElement;

      if (element) {
        let listener = Cc["@mozilla.org/addons/integration;1"].
                       getService(Ci.nsIMessageListener);
        return listener.wrappedJSObject.receiveMessage({
          name: MSG_INSTALL_ADDON,
          target: element,
          data: install,
        });
      }
    }

    // Fall back to sending through the message manager
    let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebNavigation)
                               .QueryInterface(Ci.nsIDocShell)
                               .QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIContentFrameMessageManager);

    return messageManager.sendSyncMessage(MSG_INSTALL_ADDON, install)[0];
  },

  _addCallback(callback) {
    if (!callback || typeof callback != "function")
      return -1;

    let callbackID = this._windowID + "-" + ++this._lastCallbackID;
    let callbackObject = new CallbackObject(callbackID, callback, this);
    this._callbacks.set(callbackID, callbackObject);
    return callbackID;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference])
};


function InstallTrigger() {
}

InstallTrigger.prototype = {
  // Here be magic. We've declared ourselves as providing the
  // nsIDOMGlobalPropertyInitializer interface, and are registered in the
  // "JavaScript-global-property" category in the XPCOM category manager. This
  // means that for newly created windows, XPCOM will createinstance this
  // object, and then call init, passing in the window for which we need to
  // provide an instance. We then initialize ourselves and return the webidl
  // version of this object using the webidl-provided _create method, which
  // XPCOM will then duly expose as a property value on the window. All this
  // indirection is necessary because webidl does not (yet) support statics
  // (bug 863952). See bug 926712 for more details about this implementation.
  init(window) {
    this._window = window;
    this._principal = window.document.nodePrincipal;
    this._url = window.document.documentURIObject;

    try {
      this._mediator = new RemoteMediator(window);
    } catch (ex) {
      // If we can't set up IPC (e.g., because this is a top-level window
      // or something), then don't expose InstallTrigger.
      return null;
    }

    return window.InstallTriggerImpl._create(window, this);
  },

  enabled() {
    return this._mediator.enabled(this._url.spec);
  },

  updateEnabled() {
    return this.enabled();
  },

  install(installs, callback) {
    let keys = Object.keys(installs);
    if (keys.length > 1) {
      throw new this._window.Error("Only one XPI may be installed at a time");
    }

    let item = installs[keys[0]];

    if (typeof item === "string") {
      item = { URL: item };
    }
    if (!item.URL) {
      throw new this._window.Error("Missing URL property for '" + name + "'");
    }

    let url = this._resolveURL(item.URL);
    if (!this._checkLoadURIFromScript(url)) {
      throw new this._window.Error("Insufficient permissions to install: " + url.spec);
    }

    let iconUrl = null;
    if (item.IconURL) {
      iconUrl = this._resolveURL(item.IconURL);
      if (!this._checkLoadURIFromScript(iconUrl)) {
        iconUrl = null; // If page can't load the icon, just ignore it
      }
    }

    let installData = {
      uri: url.spec,
      hash: item.Hash || null,
      name: item.name,
      icon: iconUrl ? iconUrl.spec : null,
    };

    return this._mediator.install(installData, this._principal, callback, this._window);
  },

  startSoftwareUpdate(url, flags) {
    let filename = Services.io.newURI(url)
                              .QueryInterface(Ci.nsIURL)
                              .filename;
    let args = {};
    args[filename] = { "URL": url };
    return this.install(args);
  },

  installChrome(type, url, skin) {
    return this.startSoftwareUpdate(url);
  },

  _resolveURL(url) {
    return Services.io.newURI(url, null, this._url);
  },

  _checkLoadURIFromScript(uri) {
    let secman = Services.scriptSecurityManager;
    try {
      secman.checkLoadURIWithPrincipal(this._principal,
                                       uri,
                                       secman.DISALLOW_INHERIT_PRINCIPAL);
      return true;
    } catch (e) {
      return false;
    }
  },

  classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"),
  contractID: "@mozilla.org/addons/installtrigger;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer])
};



this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);
PK
!<2Rcomponents/amWebAPI.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                      "extensions.webextPermissionPrompts", false);

const MSG_PROMISE_REQUEST  = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT   = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT    = "WebAPIInstallEvent";
const MSG_INSTALL_CLEANUP  = "WebAPICleanup";
const MSG_ADDON_EVENT_REQ  = "WebAPIAddonEventRequest";
const MSG_ADDON_EVENT      = "WebAPIAddonEvent";

class APIBroker {
  constructor(mm) {
    this.mm = mm;

    this._promises = new Map();

    // _installMap maps integer ids to DOM AddonInstall instances
    this._installMap = new Map();

    this.mm.addMessageListener(MSG_PROMISE_RESULT, this);
    this.mm.addMessageListener(MSG_INSTALL_EVENT, this);

    this._eventListener = null;
  }

  receiveMessage(message) {
    let payload = message.data;

    switch (message.name) {
      case MSG_PROMISE_RESULT: {
        if (!this._promises.has(payload.callbackID)) {
          return;
        }

        let resolve = this._promises.get(payload.callbackID);
        this._promises.delete(payload.callbackID);
        resolve(payload);
        break;
      }

      case MSG_INSTALL_EVENT: {
        let install = this._installMap.get(payload.id);
        if (!install) {
          let err = new Error(`Got install event for unknown install ${payload.id}`);
          Cu.reportError(err);
          return;
        }
        install._dispatch(payload);
        break;
      }

      case MSG_ADDON_EVENT: {
        if (this._eventListener) {
          this._eventListener(payload);
        }
      }
    }
  }

  sendRequest(type, ...args) {
    return new Promise(resolve => {
      let callbackID = APIBroker._nextID++;

      this._promises.set(callbackID, resolve);
      this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
    });
  }

  setAddonListener(callback) {
    this._eventListener = callback;
    if (callback) {
      this.mm.addMessageListener(MSG_ADDON_EVENT, this);
      this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true});
    } else {
      this.mm.removeMessageListener(MSG_ADDON_EVENT, this);
      this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false});
    }
  }

  sendCleanup(ids) {
    this.setAddonListener(null);
    this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
  }
}

APIBroker._nextID = 0;

// Base class for building classes to back content-exposed interfaces.
class APIObject {
  init(window, broker, properties) {
    this.window = window;
    this.broker = broker;

    // Copy any provided properties onto this object, webidl bindings
    // will only expose to content what should be exposed.
    for (let key of Object.keys(properties)) {
      this[key] = properties[key];
    }
  }

  /**
   * Helper to implement an asychronous method visible to content, where
   * the method is implemented by sending a message to the parent process
   * and then wrapping the returned object or error in an appropriate object.
   * This helper method ensures that:
   *  - Returned Promise objects are from the content window
   *  - Rejected Promises have Error objects from the content window
   *  - Only non-internal errors are exposed to the caller
   *
   * @param {string} apiRequest The command to invoke in the parent process.
   * @param {array<cloneable>} apiArgs The arguments to include with the
   *                                   request to the parent process.
   * @param {function} resultConvert If provided, a function called with the
   *                                 result from the parent process as an
   *                                 argument.  Used to convert the result
   *                                 into something appropriate for content.
   * @returns {Promise<any>} A Promise suitable for passing directly to content.
   */
  _apiTask(apiRequest, apiArgs, resultConverter) {
    let win = this.window;
    let broker = this.broker;
    return new win.Promise((resolve, reject) => {
      (async function() {
        let result = await broker.sendRequest(apiRequest, ...apiArgs);
        if ("reject" in result) {
          let err = new win.Error(result.reject.message);
          // We don't currently put any other properties onto Errors
          // generated by mozAddonManager.  If/when we do, they will
          // need to get copied here.
          reject(err);
          return;
        }

        let obj = result.resolve;
        if (resultConverter) {
          obj = resultConverter(obj);
        }
        resolve(obj);
      })().catch(err => {
        Cu.reportError(err);
        reject(new win.Error("Unexpected internal error"));
      });
    });
  }
}

class Addon extends APIObject {
  constructor(...args) {
    super();
    this.init(...args);
  }

  uninstall() {
    return this._apiTask("addonUninstall", [this.id]);
  }

  setEnabled(value) {
    return this._apiTask("addonSetEnabled", [this.id, value]);
  }
}

class AddonInstall extends APIObject {
  constructor(window, broker, properties) {
    super();
    this.init(window, broker, properties);

    broker._installMap.set(properties.id, this);
  }

  _dispatch(data) {
    // The message for the event includes updated copies of all install
    // properties.  Use the usual "let webidl filter visible properties" trick.
    for (let key of Object.keys(data)) {
      this[key] = data[key];
    }

    let event = new this.window.Event(data.event);
    this.__DOM_IMPL__.dispatchEvent(event);
  }

  install() {
    return this._apiTask("addonInstallDoInstall", [this.id]);
  }

  cancel() {
    return this._apiTask("addonInstallCancel", [this.id]);
  }
}

class WebAPI extends APIObject {
  constructor() {
    super();
    this.allInstalls = [];
    this.listenerCount = 0;
  }

  init(window) {
    let mm = window
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDocShell)
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIContentFrameMessageManager);
    let broker = new APIBroker(mm);

    super.init(window, broker, {});

    window.addEventListener("unload", event => {
      this.broker.sendCleanup(this.allInstalls);
    });
  }

  getAddonByID(id) {
    return this._apiTask("getAddonByID", [id], addonInfo => {
      if (!addonInfo) {
        return null;
      }
      let addon = new Addon(this.window, this.broker, addonInfo);
      return this.window.Addon._create(this.window, addon);
    });
  }

  createInstall(options) {
    return this._apiTask("createInstall", [options], installInfo => {
      if (!installInfo) {
        return null;
      }
      let install = new AddonInstall(this.window, this.broker, installInfo);
      this.allInstalls.push(installInfo.id);
      return this.window.AddonInstall._create(this.window, install);
    });
  }

  get permissionPromptsEnabled() {
    return WEBEXT_PERMISSION_PROMPTS;
  }

  eventListenerWasAdded(type) {
    if (this.listenerCount == 0) {
      this.broker.setAddonListener(data => {
        let event = new this.window.AddonEvent(data.event, data);
        this.__DOM_IMPL__.dispatchEvent(event);
      });
    }
    this.listenerCount++;
  }

  eventListenerWasRemoved(type) {
    this.listenerCount--;
    if (this.listenerCount == 0) {
      this.broker.setAddonListener(null);
    }
  }

  QueryInterface(iid) {
    if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports)
        || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) {
      return this;
    }
    return Cr.NS_ERROR_NO_INTERFACE;
  }
}

WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]);
PK
!<v4TT components/nsBlocklistService.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";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AppConstants.jsm");

try {
  // AddonManager.jsm doesn't allow itself to be imported in the child
  // process. We're used in the child process (for now), so guard against
  // this.
  Components.utils.import("resource://gre/modules/AddonManager.jsm");
  /* globals AddonManagerPrivate*/
} catch (e) {
}

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
                                  "resource://gre/modules/ServiceRequest.jsm");

// The blocklist updater is the new system in charge of fetching remote data
// securely and efficiently. It will replace the current XML-based system.
// See Bug 1257565 and Bug 1252456.
const BlocklistUpdater = {};
XPCOMUtils.defineLazyModuleGetter(BlocklistUpdater, "checkVersions",
                                  "resource://services-common/blocklist-updater.js");

const TOOLKIT_ID                      = "toolkit@mozilla.org";
const KEY_PROFILEDIR                  = "ProfD";
const KEY_APPDIR                      = "XCurProcD";
const FILE_BLOCKLIST                  = "blocklist.xml";
const PREF_BLOCKLIST_LASTUPDATETIME   = "app.update.lastUpdateTime.blocklist-background-update-timer";
const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
const PREF_BLOCKLIST_PINGCOUNTTOTAL   = "extensions.blocklist.pingCountTotal";
const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_BLOCKLIST_SUPPRESSUI       = "extensions.blocklist.suppressUI";
const PREF_ONECRL_VIA_AMO             = "security.onecrl.via.amo";
const PREF_BLOCKLIST_UPDATE_ENABLED   = "services.blocklist.update_enabled";
const PREF_APP_DISTRIBUTION           = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul"
const DEFAULT_SEVERITY                = 3;
const DEFAULT_LEVEL                   = 2;
const MAX_BLOCK_LEVEL                 = 3;
const SEVERITY_OUTDATED               = 0;
const VULNERABILITYSTATUS_NONE             = 0;
const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
const VULNERABILITYSTATUS_NO_UPDATE        = 2;

const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];

var gLoggingEnabled = null;
var gBlocklistEnabled = true;
var gBlocklistLevel = DEFAULT_LEVEL;

XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
                                   "@mozilla.org/consoleservice;1",
                                   "nsIConsoleService");

XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
                                   "@mozilla.org/xpcom/version-comparator;1",
                                   "nsIVersionComparator");

XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService",
                                   "@mozilla.org/security/certblocklist;1",
                                   "nsICertBlocklist");

XPCOMUtils.defineLazyGetter(this, "gPref", function() {
  return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
         QueryInterface(Ci.nsIPrefBranch);
});

// From appinfo in Services.jsm. It is not possible to use the one in
// Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in
// xpcshell tests due to other code calling Services.appinfo before the
// nsIXULAppInfo is created by the tests.
XPCOMUtils.defineLazyGetter(this, "gApp", function() {
  let appinfo = Cc["@mozilla.org/xre/app-info;1"]
                  .getService(Ci.nsIXULRuntime);
  try {
    appinfo.QueryInterface(Ci.nsIXULAppInfo);
  } catch (ex) {
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!(ex instanceof Components.Exception) ||
        ex.result != Cr.NS_NOINTERFACE)
      throw ex;
  }
  return appinfo;
});

XPCOMUtils.defineLazyGetter(this, "gABI", function() {
  let abi = null;
  try {
    abi = gApp.XPCOMABI;
  } catch (e) {
    LOG("BlockList Global gABI: XPCOM ABI unknown.");
  }

  if (AppConstants.platform == "macosx") {
    // Mac universal build should report a different ABI than either macppc
    // or mactel.
    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
                   getService(Ci.nsIMacUtils);

    if (macutils.isUniversalBinary)
      abi += "-u-" + macutils.architecturesInBinary;
  }
  return abi;
});

XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() {
  let osVersion;
  let sysInfo = Cc["@mozilla.org/system-info;1"].
                getService(Ci.nsIPropertyBag2);
  try {
    osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
  } catch (e) {
    LOG("BlockList Global gOSVersion: OS Version unknown.");
  }

  if (osVersion) {
    try {
      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
    } catch (e) {
      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
    }
    osVersion = encodeURIComponent(osVersion);
  }
  return osVersion;
});

// shared code for suppressing bad cert dialogs
XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
  let temp = { };
  Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
  return temp;
});

/**
 * Logs a string to the error console.
 * @param   string
 *          The string to write to the error console..
 */
function LOG(string) {
  if (gLoggingEnabled) {
    dump("*** " + string + "\n");
    gConsole.logStringMessage(string);
  }
}

/**
 * Gets a preference value, handling the case where there is no default.
 * @param   func
 *          The name of the preference function to call, on nsIPrefBranch
 * @param   preference
 *          The name of the preference
 * @param   defaultValue
 *          The default value to return in the event the preference has
 *          no setting
 * @returns The value of the preference, or undefined if there was no
 *          user or default value.
 */
function getPref(func, preference, defaultValue) {
  try {
    return gPref[func](preference);
  } catch (e) {
  }
  return defaultValue;
}

/**
 * Constructs a URI to a spec.
 * @param   spec
 *          The spec to construct a URI to
 * @returns The nsIURI constructed.
 */
function newURI(spec) {
  var ioServ = Cc["@mozilla.org/network/io-service;1"].
               getService(Ci.nsIIOService);
  return ioServ.newURI(spec);
}

// Restarts the application checking in with observers first
function restartApp() {
  // Notify all windows that an application quit has been requested.
  var os = Cc["@mozilla.org/observer-service;1"].
           getService(Ci.nsIObserverService);
  var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                   createInstance(Ci.nsISupportsPRBool);
  os.notifyObservers(cancelQuit, "quit-application-requested");

  // Something aborted the quit process.
  if (cancelQuit.data)
    return;

  var as = Cc["@mozilla.org/toolkit/app-startup;1"].
           getService(Ci.nsIAppStartup);
  as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
}

/**
 * Checks whether this blocklist element is valid for the current OS and ABI.
 * If the element has an "os" attribute then the current OS must appear in
 * its comma separated list for the element to be valid. Similarly for the
 * xpcomabi attribute.
 */
function matchesOSABI(blocklistElement) {
  if (blocklistElement.hasAttribute("os")) {
    var choices = blocklistElement.getAttribute("os").split(",");
    if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
      return false;
  }

  if (blocklistElement.hasAttribute("xpcomabi")) {
    choices = blocklistElement.getAttribute("xpcomabi").split(",");
    if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
      return false;
  }

  return true;
}

/**
 * Gets the current value of the locale.  It's possible for this preference to
 * be localized, so we have to do a little extra work here.  Similar code
 * exists in nsHttpHandler.cpp when building the UA string.
 */
function getLocale() {
  return Services.locale.getRequestedLocales();
}

/* Get the distribution pref values, from defaults only */
function getDistributionPrefValue(aPrefName) {
  return gPref.getDefaultBranch(null).getCharPref(aPrefName, "default");
}

/**
 * Parse a string representation of a regular expression. Needed because we
 * use the /pattern/flags form (because it's detectable), which is only
 * supported as a literal in JS.
 *
 * @param  aStr
 *         String representation of regexp
 * @return RegExp instance
 */
function parseRegExp(aStr) {
  let lastSlash = aStr.lastIndexOf("/");
  let pattern = aStr.slice(1, lastSlash);
  let flags = aStr.slice(lastSlash + 1);
  return new RegExp(pattern, flags);
}

/**
 * Manages the Blocklist. The Blocklist is a representation of the contents of
 * blocklist.xml and allows us to remotely disable / re-enable blocklisted
 * items managed by the Extension Manager with an item's appDisabled property.
 * It also blocklists plugins with data from blocklist.xml.
 */

function Blocklist() {
  Services.obs.addObserver(this, "xpcom-shutdown");
  Services.obs.addObserver(this, "sessionstore-windows-restored");
  gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
  gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                     MAX_BLOCK_LEVEL);
  gPref.addObserver("extensions.blocklist.", this);
  gPref.addObserver(PREF_EM_LOGGING_ENABLED, this);
  this.wrappedJSObject = this;
  // requests from child processes come in here, see receiveMessage.
  Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this);
  Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this);
}

Blocklist.prototype = {
  /**
   * Extension ID -> array of Version Ranges
   * Each value in the version range array is a JS Object that has the
   * following properties:
   *   "minVersion"  The minimum version in a version range (default = 0)
   *   "maxVersion"  The maximum version in a version range (default = *)
   *   "targetApps"  Application ID -> array of Version Ranges
   *                 (default = current application ID)
   *                 Each value in the version range array is a JS Object that
   *                 has the following properties:
   *                   "minVersion"  The minimum version in a version range
   *                                 (default = 0)
   *                   "maxVersion"  The maximum version in a version range
   *                                 (default = *)
   */
  _addonEntries: null,
  _gfxEntries: null,
  _pluginEntries: null,

  shutdown() {
    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this);
    Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this);
    gPref.removeObserver("extensions.blocklist.", this);
    gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  },

  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
    case "xpcom-shutdown":
      this.shutdown();
      break;
    case "nsPref:changed":
      switch (aData) {
        case PREF_EM_LOGGING_ENABLED:
          gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
          break;
        case PREF_BLOCKLIST_ENABLED:
          gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
          this._loadBlocklist();
          this._blocklistUpdated(null, null);
          break;
        case PREF_BLOCKLIST_LEVEL:
          gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                     MAX_BLOCK_LEVEL);
          this._blocklistUpdated(null, null);
          break;
      }
      break;
    case "sessionstore-windows-restored":
      Services.obs.removeObserver(this, "sessionstore-windows-restored");
      this._preloadBlocklist();
      break;
    }
  },

  // Message manager message handlers
  receiveMessage(aMsg) {
    switch (aMsg.name) {
      case "Blocklist:getPluginBlocklistState":
        return this.getPluginBlocklistState(aMsg.data.addonData,
                                            aMsg.data.appVersion,
                                            aMsg.data.toolkitVersion);
      case "Blocklist:content-blocklist-updated":
        Services.obs.notifyObservers(null, "content-blocklist-updated");
        break;
      default:
        throw new Error("Unknown blocklist message received from content: " + aMsg.name);
    }
    return undefined;
  },

  /* See nsIBlocklistService */
  isAddonBlocklisted(addon, appVersion, toolkitVersion) {
    return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) ==
                   Ci.nsIBlocklistService.STATE_BLOCKED;
  },

  /* See nsIBlocklistService */
  getAddonBlocklistState(addon, appVersion, toolkitVersion) {
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();
    return this._getAddonBlocklistState(addon, this._addonEntries,
                                        appVersion, toolkitVersion);
  },

  /**
   * Returns a matching blocklist entry for the given add-on, if one
   * exists.
   *
   * @param   id
   *          The ID of the item to get the blocklist state for.
   * @param   version
   *          The version of the item to get the blocklist state for.
   * @param   addonEntries
   *          The add-on blocklist entries to compare against.
   * @param   appVersion
   *          The application version to compare to, will use the current
   *          version if null.
   * @param   toolkitVersion
   *          The toolkit version to compare to, will use the current version if
   *          null.
   * @returns A blocklist entry for this item, with `state` and `url`
   *          properties indicating the block state and URL, if there is
   *          a matching blocklist entry, or null otherwise.
   */
  _getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion) {
    if (!gBlocklistEnabled)
      return null;

    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!appVersion && !gApp.version)
      return null;

    if (!appVersion)
      appVersion = gApp.version;
    if (!toolkitVersion)
      toolkitVersion = gApp.platformVersion;

    var blItem = this._findMatchingAddonEntry(addonEntries, addon);
    if (!blItem)
      return null;

    for (let currentblItem of blItem.versions) {
      if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) {
        return {
          state: (currentblItem.severity >= gBlocklistLevel ?
                  Ci.nsIBlocklistService.STATE_BLOCKED : Ci.nsIBlocklistService.STATE_SOFTBLOCKED),
          url: blItem.blockID && this._createBlocklistURL(blItem.blockID),
        };
      }
    }
    return null;
  },

  getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();
    return this._getAddonBlocklistEntry(addon, this._addonEntries,
                                        appVersion, toolkitVersion);
  },

  /**
   * Private version of getAddonBlocklistState that allows the caller to pass in
   * the add-on blocklist entries to compare against.
   *
   * @param   id
   *          The ID of the item to get the blocklist state for.
   * @param   version
   *          The version of the item to get the blocklist state for.
   * @param   addonEntries
   *          The add-on blocklist entries to compare against.
   * @param   appVersion
   *          The application version to compare to, will use the current
   *          version if null.
   * @param   toolkitVersion
   *          The toolkit version to compare to, will use the current version if
   *          null.
   * @returns The blocklist state for the item, one of the STATE constants as
   *          defined in nsIBlocklistService.
   */
  _getAddonBlocklistState(addon, addonEntries, appVersion, toolkitVersion) {
    let entry = this._getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion);
    if (entry)
      return entry.state;
    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },

  /**
   * Returns the set of prefs of the add-on stored in the blocklist file
   * (probably to revert them on disabling).
   * @param addon
   *        The add-on whose to-be-reset prefs are to be found.
   */
  _getAddonPrefs(addon) {
    let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
    return entry.prefs.slice(0);
  },

  _findMatchingAddonEntry(aAddonEntries, aAddon) {
    if (!aAddon)
      return null;
    // Returns true if the params object passes the constraints set by entry.
    // (For every non-null property in entry, the same key must exist in
    // params and value must be the same)
    function checkEntry(entry, params) {
      for (let [key, value] of entry) {
        if (value === null || value === undefined)
          continue;
        if (params[key]) {
          if (value instanceof RegExp) {
            if (!value.test(params[key])) {
              return false;
            }
          } else if (value !== params[key]) {
            return false;
          }
        } else {
          return false;
        }
      }
      return true;
    }

    let params = {};
    for (let filter of EXTENSION_BLOCK_FILTERS) {
      params[filter] = aAddon[filter];
    }
    if (params.creator)
      params.creator = params.creator.name;
    for (let entry of aAddonEntries) {
      if (checkEntry(entry.attributes, params)) {
         return entry;
       }
     }
     return null;
  },

  /* See nsIBlocklistService */
  getAddonBlocklistURL(addon, appVersion, toolkitVersion) {
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();

    let entry = this._getAddonBlocklistEntry(addon, this._addonEntries);
    return entry && entry.url;
  },

  _createBlocklistURL(id) {
    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
    url = url.replace(/%blockID%/g, id);

    return url;
  },

  notify(aTimer) {
    if (!gBlocklistEnabled)
      return;

    try {
      var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
    } catch (e) {
      LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
          " is missing!");
      return;
    }

    var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
    var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
    var daysSinceLastPing = 0;
    if (pingCountVersion == 0) {
      daysSinceLastPing = "new";
    } else {
      // Seconds in one day is used because nsIUpdateTimerManager stores the
      // last update time in seconds.
      let secondsInDay = 60 * 60 * 24;
      let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0);
      if (lastUpdateTime == 0) {
        daysSinceLastPing = "invalid";
      } else {
        let now = Math.round(Date.now() / 1000);
        daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
      }

      if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") {
        pingCountVersion = pingCountTotal = "invalid";
      }
    }

    if (pingCountVersion < 1)
      pingCountVersion = 1;
    if (pingCountTotal < 1)
      pingCountTotal = 1;

    dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (gApp.version)
      dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
    dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (gApp.version)
      dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
    dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
    dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
    dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
    dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
    dsURI = dsURI.replace(/%CHANNEL%/g, UpdateUtils.UpdateChannel);
    dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
    dsURI = dsURI.replace(/%DISTRIBUTION%/g,
                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
    dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
    dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion);
    dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal);
    dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
    dsURI = dsURI.replace(/\+/g, "%2B");

    // Under normal operations it will take around 5,883,516 years before the
    // preferences used to store pingCountVersion and pingCountTotal will rollover
    // so this code doesn't bother trying to do the "right thing" here.
    if (pingCountVersion != "invalid") {
      pingCountVersion++;
      if (pingCountVersion > 2147483647) {
        // Rollover to -1 if the value is greater than what is support by an
        // integer preference. The -1 indicates that the counter has been reset.
        pingCountVersion = -1;
      }
      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
    }

    if (pingCountTotal != "invalid") {
      pingCountTotal++;
      if (pingCountTotal > 2147483647) {
        // Rollover to 1 if the value is greater than what is support by an
        // integer preference.
        pingCountTotal = -1;
      }
      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
    }

    // Verify that the URI is valid
    try {
      var uri = newURI(dsURI);
    } catch (e) {
      LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
          "for: " + dsURI + ", error: " + e);
      return;
    }

    LOG("Blocklist::notify: Requesting " + uri.spec);
    let request = new ServiceRequest();
    request.open("GET", uri.spec, true);
    request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
    request.overrideMimeType("text/xml");
    request.setRequestHeader("Cache-Control", "no-cache");
    request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);

    request.addEventListener("error", event => this.onXMLError(event));
    request.addEventListener("load", event => this.onXMLLoad(event));
    request.send(null);

    // When the blocklist loads we need to compare it to the current copy so
    // make sure we have loaded it.
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();

    // If blocklist update via Kinto is enabled, poll for changes and sync.
    // Currently certificates blocklist relies on it by default.
    if (gPref.getBoolPref(PREF_BLOCKLIST_UPDATE_ENABLED)) {
      BlocklistUpdater.checkVersions().catch(() => {
        // Bug 1254099 - Telemetry (success or errors) will be collected during this process.
      });
    }
  },

  async onXMLLoad(aEvent) {
    let request = aEvent.target;
    try {
      gCertUtils.checkCert(request.channel);
    } catch (e) {
      LOG("Blocklist::onXMLLoad: " + e);
      return;
    }
    let responseXML = request.responseXML;
    if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
        (request.status != 200 && request.status != 0)) {
      LOG("Blocklist::onXMLLoad: there was an error during load");
      return;
    }

    var oldAddonEntries = this._addonEntries;
    var oldPluginEntries = this._pluginEntries;
    this._addonEntries = [];
    this._gfxEntries = [];
    this._pluginEntries = [];

    this._loadBlocklistFromString(request.responseText);
    // We don't inform the users when the graphics blocklist changed at runtime.
    // However addons and plugins blocking status is refreshed.
    this._blocklistUpdated(oldAddonEntries, oldPluginEntries);

    try {
      let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
      await OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
    } catch (e) {
      LOG("Blocklist::onXMLLoad: " + e);
    }
  },

  onXMLError(aEvent) {
    try {
      var request = aEvent.target;
      // the following may throw (e.g. a local file or timeout)
      var status = request.status;
    } catch (e) {
      request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
      status = request.status;
    }
    var statusText = "nsIXMLHttpRequest channel unavailable";
    // When status is 0 we don't have a valid channel.
    if (status != 0) {
      try {
        statusText = request.statusText;
      } catch (e) {
      }
    }
    LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
        statusText);
  },

  /**
   * Finds the newest blocklist file from the application and the profile and
   * load it or does nothing if neither exist.
   */
  _loadBlocklist() {
    this._addonEntries = [];
    this._gfxEntries = [];
    this._pluginEntries = [];
    var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
    if (profFile.exists()) {
      this._loadBlocklistFromFile(profFile);
      return;
    }
    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
    if (appFile.exists()) {
      this._loadBlocklistFromFile(appFile);
      return;
    }
    LOG("Blocklist::_loadBlocklist: no XML File found");
  },

  /**
#    The blocklist XML file looks something like this:
#
#    <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
#      <emItems>
#        <emItem id="item_1@domain" blockID="i1">
#          <prefs>
#            <pref>accessibility.accesskeycausesactivation</pref>
#            <pref>accessibility.blockautorefresh</pref>
#          </prefs>
#          <versionRange minVersion="1.0" maxVersion="2.0.*">
#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
#              <versionRange minVersion="1.7" maxVersion="1.7.*"/>
#            </targetApplication>
#            <targetApplication id="toolkit@mozilla.org">
#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
#            </targetApplication>
#          </versionRange>
#          <versionRange minVersion="3.0" maxVersion="3.0.*">
#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
#            </targetApplication>
#            <targetApplication id="toolkit@mozilla.org">
#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
#            </targetApplication>
#          </versionRange>
#        </emItem>
#        <emItem id="item_2@domain" blockID="i2">
#          <versionRange minVersion="3.1" maxVersion="4.*"/>
#        </emItem>
#        <emItem id="item_3@domain">
#          <versionRange>
#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
#            </targetApplication>
#          </versionRange>
#        </emItem>
#        <emItem id="item_4@domain" blockID="i3">
#          <versionRange>
#            <targetApplication>
#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
#            </targetApplication>
#          </versionRange>
#        <emItem id="/@badperson\.com$/"/>
#      </emItems>
#      <pluginItems>
#        <pluginItem blockID="i4">
#          <!-- All match tags must match a plugin to blocklist a plugin -->
#          <match name="name" exp="some plugin"/>
#          <match name="description" exp="1[.]2[.]3"/>
#        </pluginItem>
#      </pluginItems>
#      <certItems>
#        <!-- issuerName is the DER issuer name data base64 encoded... -->
#        <certItem issuerName="MA0xCzAJBgNVBAMMAmNh">
#          <!-- ... as is the serial number DER data -->
#          <serialNumber>AkHVNA==</serialNumber>
#        </certItem>
#        <!-- subject is the DER subject name data base64 encoded... -->
#        <certItem subject="MA0xCzAJBgNVBAMMAmNh" pubKeyHash="/xeHA5s+i9/z9d8qy6JEuE1xGoRYIwgJuTE/lmaGJ7M=">
#        </certItem>
#      </certItems>
#    </blocklist>
   */

  _loadBlocklistFromFile(file) {
    if (!gBlocklistEnabled) {
      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
      return;
    }

    let telemetry = Services.telemetry;

    if (this._isBlocklistPreloaded()) {
      telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
      this._loadBlocklistFromString(this._preloadedBlocklistContent);
      delete this._preloadedBlocklistContent;
      return;
    }

    if (!file.exists()) {
      LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
      return;
    }

    telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);

    let text = "";
    let fstream = null;
    let cstream = null;

    try {
      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                          .createInstance(Components.interfaces.nsIFileInputStream);
      cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
                          .createInstance(Components.interfaces.nsIConverterInputStream);

      fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
      cstream.init(fstream, "UTF-8", 0, 0);

      let str = {};
      let read = 0;

      do {
        read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
        text += str.value;
      } while (read != 0);
    } catch (e) {
      LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e);
    } finally {
      if (cstream)
        cstream.close();
      if (fstream)
        fstream.close();
    }

    if (text)
        this._loadBlocklistFromString(text);
  },

  _isBlocklistLoaded() {
    return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
  },

  _isBlocklistPreloaded() {
    return this._preloadedBlocklistContent != null;
  },

  /* Used for testing */
  _clear() {
    this._addonEntries = null;
    this._gfxEntries = null;
    this._pluginEntries = null;
    this._preloadedBlocklistContent = null;
  },

  async _preloadBlocklist() {
    let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
    try {
      await this._preloadBlocklistFile(profPath);
      return;
    } catch (e) {
      LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
    }

    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
    try {
      await this._preloadBlocklistFile(appFile.path);
      return;
    } catch (e) {
      LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
    }

    LOG("Blocklist::_preloadBlocklist: no XML File found");
  },

  async _preloadBlocklistFile(path) {
    if (this._addonEntries) {
      // The file has been already loaded.
      return;
    }

    if (!gBlocklistEnabled) {
      LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
      return;
    }

    let text = await OS.File.read(path, { encoding: "utf-8" });

    if (!this._addonEntries) {
      // Store the content only if a sync load has not been performed in the meantime.
      this._preloadedBlocklistContent = text;
    }
  },

  _loadBlocklistFromString(text) {
    try {
      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                   createInstance(Ci.nsIDOMParser);
      var doc = parser.parseFromString(text, "text/xml");
      if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
        LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
            "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
            "Received: " + doc.documentElement.namespaceURI);
        return;
      }

      var populateCertBlocklist = getPref("getBoolPref", PREF_ONECRL_VIA_AMO, true);

      var childNodes = doc.documentElement.childNodes;
      for (let element of childNodes) {
        if (!(element instanceof Ci.nsIDOMElement))
          continue;
        switch (element.localName) {
        case "emItems":
          this._addonEntries = this._processItemNodes(element.childNodes, "emItem",
                                                      this._handleEmItemNode);
          break;
        case "pluginItems":
          // We don't support plugins on b2g.
          if (AppConstants.MOZ_B2G) {
            return;
          }
          this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem",
                                                       this._handlePluginItemNode);
          break;
        case "certItems":
          if (populateCertBlocklist) {
            this._processItemNodes(element.childNodes, "certItem",
                                   this._handleCertItemNode.bind(this));
          }
          break;
        case "gfxItems":
          // Parse as simple list of objects.
          this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry",
                                                    this._handleGfxBlacklistNode);
          break;
        default:
          LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName);
        }
      }
      if (populateCertBlocklist) {
        gCertBlocklistService.saveEntries();
      }
      if (this._gfxEntries.length > 0) {
        this._notifyObserversBlocklistGFX();
      }
    } catch (e) {
      LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
    }
  },

  _processItemNodes(itemNodes, itemName, handler) {
    var result = [];
    for (var i = 0; i < itemNodes.length; ++i) {
      var blocklistElement = itemNodes.item(i);
      if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
          blocklistElement.localName != itemName)
        continue;

      handler(blocklistElement, result);
    }
    return result;
  },

  _handleCertItemNode(blocklistElement, result) {
    let issuer = blocklistElement.getAttribute("issuerName");
    if (issuer) {
      for (let snElement of blocklistElement.children) {
        try {
          gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent);
        } catch (e) {
          // we want to keep trying other elements since missing all items
          // is worse than missing one
          LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Issuer and Serial" + e);
        }
      }
      return;
    }

    let pubKeyHash = blocklistElement.getAttribute("pubKeyHash");
    let subject = blocklistElement.getAttribute("subject");

    if (pubKeyHash && subject) {
      try {
        gCertBlocklistService.revokeCertBySubjectAndPubKey(subject, pubKeyHash);
      } catch (e) {
        LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Subject and PubKey" + e);
      }
    }
  },

  _handleEmItemNode(blocklistElement, result) {
    if (!matchesOSABI(blocklistElement))
      return;

    let blockEntry = {
      versions: [],
      prefs: [],
      blockID: null,
      attributes: new Map()
      // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
    };

    // Any filter starting with '/' is interpreted as a regex. So if an attribute
    // starts with a '/' it must be checked via a regex.
    function regExpCheck(attr) {
      return attr.startsWith("/") ? parseRegExp(attr) : attr;
    }

    for (let filter of EXTENSION_BLOCK_FILTERS) {
      let attr = blocklistElement.getAttribute(filter);
      if (attr)
        blockEntry.attributes.set(filter, regExpCheck(attr));
    }

    var childNodes = blocklistElement.childNodes;

    for (let x = 0; x < childNodes.length; x++) {
      var childElement = childNodes.item(x);
      if (!(childElement instanceof Ci.nsIDOMElement))
        continue;
      if (childElement.localName === "prefs") {
        let prefElements = childElement.childNodes;
        for (let i = 0; i < prefElements.length; i++) {
          let prefElement = prefElements.item(i);
          if (!(prefElement instanceof Ci.nsIDOMElement) ||
              prefElement.localName !== "pref")
            continue;
          blockEntry.prefs.push(prefElement.textContent);
        }
      } else if (childElement.localName === "versionRange")
        blockEntry.versions.push(new BlocklistItemData(childElement));
    }
    // if only the extension ID is specified block all versions of the
    // extension for the current application.
    if (blockEntry.versions.length == 0)
      blockEntry.versions.push(new BlocklistItemData(null));

    blockEntry.blockID = blocklistElement.getAttribute("blockID");

    result.push(blockEntry);
  },

  _handlePluginItemNode(blocklistElement, result) {
    if (!matchesOSABI(blocklistElement))
      return;

    var matchNodes = blocklistElement.childNodes;
    var blockEntry = {
      matches: {},
      versions: [],
      blockID: null,
      infoURL: null,
    };
    var hasMatch = false;
    for (var x = 0; x < matchNodes.length; ++x) {
      var matchElement = matchNodes.item(x);
      if (!(matchElement instanceof Ci.nsIDOMElement))
        continue;
      if (matchElement.localName == "match") {
        var name = matchElement.getAttribute("name");
        var exp = matchElement.getAttribute("exp");
        try {
          blockEntry.matches[name] = new RegExp(exp, "m");
          hasMatch = true;
        } catch (e) {
          // Ignore invalid regular expressions
        }
      }
      if (matchElement.localName == "versionRange") {
        blockEntry.versions.push(new BlocklistItemData(matchElement));
      } else if (matchElement.localName == "infoURL") {
        blockEntry.infoURL = matchElement.textContent;
      }
    }
    // Plugin entries require *something* to match to an actual plugin
    if (!hasMatch)
      return;
    // Add a default versionRange if there wasn't one specified
    if (blockEntry.versions.length == 0)
      blockEntry.versions.push(new BlocklistItemData(null));

    blockEntry.blockID = blocklistElement.getAttribute("blockID");

    result.push(blockEntry);
  },

  // <gfxBlacklistEntry blockID="g60">
  //   <os>WINNT 6.0</os>
  //   <osversion>14</osversion> currently only used for Android
  //   <versionRange minVersion="42.0" maxVersion="13.0b2"/>
  //   <vendor>0x8086</vendor>
  //   <devices>
  //     <device>0x2582</device>
  //     <device>0x2782</device>
  //   </devices>
  //   <feature> DIRECT3D_10_LAYERS </feature>
  //   <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
  //   <driverVersion> 8.52.322.2202 </driverVersion>
  //   <driverVersionMax> 8.52.322.2202 </driverVersionMax>
  //   <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
  //   <model>foo</model>
  //   <product>foo</product>
  //   <manufacturer>foo</manufacturer>
  //   <hardware>foo</hardware>
  // </gfxBlacklistEntry>
  _handleGfxBlacklistNode(blocklistElement, result) {
    const blockEntry = {};

    // The blockID attribute is always present in the actual data produced on server
    // (see https://github.com/mozilla/addons-server/blob/2016.05.05/src/olympia/blocklist/templates/blocklist/blocklist.xml#L74)
    // But it is sometimes missing in test fixtures.
    if (blocklistElement.hasAttribute("blockID")) {
      blockEntry.blockID = blocklistElement.getAttribute("blockID");
    }

    // Trim helper (spaces, tabs, no-break spaces..)
    const trim = (s) => (s || "").replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, "");

    for (let i = 0; i < blocklistElement.childNodes.length; ++i) {
      var matchElement = blocklistElement.childNodes.item(i);
      if (!(matchElement instanceof Ci.nsIDOMElement))
        continue;

      let value;
      if (matchElement.localName == "devices") {
        value = [];
        for (let j = 0; j < matchElement.childNodes.length; j++) {
          const childElement = matchElement.childNodes.item(j);
          const childValue = trim(childElement.textContent);
          // Make sure no empty value is added.
          if (childValue) {
            if (/,/.test(childValue)) {
              // Devices can't contain comma.
              // (c.f serialization in _notifyObserversBlocklistGFX)
              const e = new Error(`Unsupported device name ${childValue}`);
              Components.utils.reportError(e);
            } else {
              value.push(childValue);
            }
          }
        }
      } else if (matchElement.localName == "versionRange") {
        value = {minVersion: trim(matchElement.getAttribute("minVersion")) || "0",
                 maxVersion: trim(matchElement.getAttribute("maxVersion")) || "*"};
      } else {
        value = trim(matchElement.textContent);
      }
      if (value) {
        blockEntry[matchElement.localName] = value;
      }
    }
    result.push(blockEntry);
  },

  /* See nsIBlocklistService */
  getPluginBlocklistState(plugin, appVersion, toolkitVersion) {
    if (AppConstants.platform == "android" ||
        AppConstants.MOZ_B2G) {
      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
    }
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();
    return this._getPluginBlocklistState(plugin, this._pluginEntries,
                                         appVersion, toolkitVersion);
  },

  /**
   * Private helper to get the blocklist entry for a plugin given a set of
   * blocklist entries and versions.
   *
   * @param   plugin
   *          The nsIPluginTag to get the blocklist state for.
   * @param   pluginEntries
   *          The plugin blocklist entries to compare against.
   * @param   appVersion
   *          The application version to compare to, will use the current
   *          version if null.
   * @param   toolkitVersion
   *          The toolkit version to compare to, will use the current version if
   *          null.
   * @returns {entry: blocklistEntry, version: blocklistEntryVersion},
   *          or null if there is no matching entry.
   */
  _getPluginBlocklistEntry(plugin, pluginEntries, appVersion, toolkitVersion) {
    if (!gBlocklistEnabled)
      return null;

    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!appVersion && !gApp.version)
      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;

    if (!appVersion)
      appVersion = gApp.version;
    if (!toolkitVersion)
      toolkitVersion = gApp.platformVersion;

    for (var blockEntry of pluginEntries) {
      var matchFailed = false;
      for (var name in blockEntry.matches) {
        if (!(name in plugin) ||
            typeof(plugin[name]) != "string" ||
            !blockEntry.matches[name].test(plugin[name])) {
          matchFailed = true;
          break;
        }
      }

      if (matchFailed)
        continue;

      for (let blockEntryVersion of blockEntry.versions) {
        if (blockEntryVersion.includesItem(plugin.version, appVersion,
                                           toolkitVersion)) {
          return {entry: blockEntry, version: blockEntryVersion};
        }
      }
    }

    return null;
  },

  /**
   * Private version of getPluginBlocklistState that allows the caller to pass in
   * the plugin blocklist entries.
   *
   * @param   plugin
   *          The nsIPluginTag to get the blocklist state for.
   * @param   pluginEntries
   *          The plugin blocklist entries to compare against.
   * @param   appVersion
   *          The application version to compare to, will use the current
   *          version if null.
   * @param   toolkitVersion
   *          The toolkit version to compare to, will use the current version if
   *          null.
   * @returns The blocklist state for the item, one of the STATE constants as
   *          defined in nsIBlocklistService.
   */
  _getPluginBlocklistState(plugin, pluginEntries, appVersion, toolkitVersion) {

    let r = this._getPluginBlocklistEntry(plugin, pluginEntries,
                                          appVersion, toolkitVersion);
    if (!r) {
      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
    }

    let {version: blockEntryVersion} = r;

    if (blockEntryVersion.severity >= gBlocklistLevel)
      return Ci.nsIBlocklistService.STATE_BLOCKED;
    if (blockEntryVersion.severity == SEVERITY_OUTDATED) {
      let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus;
      if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
        return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
      if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
        return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
      return Ci.nsIBlocklistService.STATE_OUTDATED;
    }
    return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
  },

  /* See nsIBlocklistService */
  getPluginBlocklistURL(plugin) {
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();

    let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries);
    if (!r) {
      return null;
    }
    let {entry: blockEntry} = r;
    if (!blockEntry.blockID) {
      return null;
    }

    return this._createBlocklistURL(blockEntry.blockID);
  },

  /* See nsIBlocklistService */
  getPluginInfoURL(plugin) {
    if (!this._isBlocklistLoaded())
      this._loadBlocklist();

    let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries);
    if (!r) {
      return null;
    }
    let {entry: blockEntry} = r;
    if (!blockEntry.blockID) {
      return null;
    }

    return blockEntry.infoURL;
  },

  _notifyObserversBlocklistGFX() {
    // Notify `GfxInfoBase`, by passing a string serialization.
    // This way we avoid spreading XML structure logics there.
    const payload = this._gfxEntries.map((r) => {
      return Object.keys(r).sort().filter((k) => !/id|last_modified/.test(k)).map((key) => {
        let value = r[key];
        if (Array.isArray(value)) {
          value = value.join(",");
        } else if (value.hasOwnProperty("minVersion")) {
          // When XML is parsed, both minVersion and maxVersion are set.
          value = `${value.minVersion},${value.maxVersion}`;
        }
        return `${key}:${value}`;
      }).join("\t");
    }).join("\n");
    Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload);
  },

  _notifyObserversBlocklistUpdated() {
    Services.obs.notifyObservers(this, "blocklist-updated");
    Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {});
  },

  _blocklistUpdated(oldAddonEntries, oldPluginEntries) {
    if (AppConstants.MOZ_B2G) {
      return;
    }

    var addonList = [];

    // A helper function that reverts the prefs passed to default values.
    function resetPrefs(prefs) {
      for (let pref of prefs)
        gPref.clearUserPref(pref);
    }
    const types = ["extension", "theme", "locale", "dictionary", "service"];
    AddonManager.getAddonsByTypes(types, addons => {
      for (let addon of addons) {
        let oldState = addon.blocklistState;
        if (addon.updateBlocklistState) {
          addon.updateBlocklistState(false);
        } else if (oldAddonEntries) {
          oldState = this._getAddonBlocklistState(addon, oldAddonEntries);
        } else {
          oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
        }
        let state = addon.blocklistState;

        LOG("Blocklist state for " + addon.id + " changed from " +
            oldState + " to " + state);

        // We don't want to re-warn about add-ons
        if (state == oldState)
          continue;

        if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
          // It's a hard block. We must reset certain preferences.
          let prefs = this._getAddonPrefs(addon);
          resetPrefs(prefs);
        }

        // Ensure that softDisabled is false if the add-on is not soft blocked
        if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
          addon.softDisabled = false;

        // Don't warn about add-ons becoming unblocked.
        if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
          continue;

        // If an add-on has dropped from hard to soft blocked just mark it as
        // soft disabled and don't warn about it.
        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
            oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
          addon.softDisabled = true;
          continue;
        }

        // If the add-on is already disabled for some reason then don't warn
        // about it
        if (!addon.isActive) {
          // But mark it as softblocked if necessary. Note that we avoid setting
          // softDisabled at the same time as userDisabled to make it clear
          // which was the original cause of the add-on becoming disabled in a
          // way that the user can change.
          if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled)
            addon.softDisabled = true;
          continue;
        }

        addonList.push({
          name: addon.name,
          version: addon.version,
          icon: addon.iconURL,
          disable: false,
          blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
          item: addon,
          url: this.getAddonBlocklistURL(addon),
        });
      }

      AddonManagerPrivate.updateAddonAppDisabledStates();

      var phs = Cc["@mozilla.org/plugin/host;1"].
                getService(Ci.nsIPluginHost);
      var plugins = phs.getPluginTags();

      for (let plugin of plugins) {
        let oldState = -1;
        if (oldPluginEntries)
          oldState = this._getPluginBlocklistState(plugin, oldPluginEntries);
        let state = this.getPluginBlocklistState(plugin);
        LOG("Blocklist state for " + plugin.name + " changed from " +
            oldState + " to " + state);
        // We don't want to re-warn about items
        if (state == oldState)
          continue;

        if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
          if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
            plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
        } else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
          if (state != Ci.nsIBlocklistService.STATE_OUTDATED &&
              state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
              state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
            addonList.push({
              name: plugin.name,
              version: plugin.version,
              icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
              disable: false,
              blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
              item: plugin,
              url: this.getPluginBlocklistURL(plugin),
            });
          }
        }
      }

      if (addonList.length == 0) {
        this._notifyObserversBlocklistUpdated();
        return;
      }

      if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
        try {
          let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
                                 .getService(Ci.nsIBlocklistPrompt);
          blockedPrompter.prompt(addonList);
        } catch (e) {
          LOG(e);
        }
        this._notifyObserversBlocklistUpdated();
        return;
      }

      var args = {
        restart: false,
        list: addonList
      };
      // This lets the dialog get the raw js object
      args.wrappedJSObject = args;

      /*
        Some tests run without UI, so the async code listens to a message
        that can be sent programatically
      */
      let applyBlocklistChanges = () => {
        for (let addon of addonList) {
          if (!addon.disable)
            continue;

          if (addon.item instanceof Ci.nsIPluginTag)
            addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
          else {
            // This add-on is softblocked.
            addon.item.softDisabled = true;
            // We must revert certain prefs.
            let prefs = this._getAddonPrefs(addon.item);
            resetPrefs(prefs);
          }
        }

        if (args.restart)
          restartApp();

        this._notifyObserversBlocklistUpdated();
        Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
      }

      Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");

      if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) {
        applyBlocklistChanges();
        return;
      }

      function blocklistUnloadHandler(event) {
        if (event.target.location == URI_BLOCKLIST_DIALOG) {
          applyBlocklistChanges();
          blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
        }
      }

      let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
                              "chrome,centerscreen,dialog,titlebar", args);
      if (blocklistWindow)
        blocklistWindow.addEventListener("unload", blocklistUnloadHandler);
    });
  },

  classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsIBlocklistService,
                                         Ci.nsITimerCallback]),
};

/**
 * Helper for constructing a blocklist.
 */
function BlocklistItemData(versionRangeElement) {
  var versionRange = this.getBlocklistVersionRange(versionRangeElement);
  this.minVersion = versionRange.minVersion;
  this.maxVersion = versionRange.maxVersion;
  if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
    this.severity = versionRangeElement.getAttribute("severity");
  else
    this.severity = DEFAULT_SEVERITY;
  if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) {
    this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus");
  } else {
    this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
  }
  this.targetApps = { };
  var found = false;

  if (versionRangeElement) {
    for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
      var targetAppElement = versionRangeElement.childNodes.item(i);
      if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
          targetAppElement.localName != "targetApplication")
        continue;
      found = true;
      // default to the current application if id is not provided.
      var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
      this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
    }
  }
  // Default to all versions of the current application when no targetApplication
  // elements were found
  if (!found)
    this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
}

BlocklistItemData.prototype = {
  /**
   * Tests if a version of an item is included in the version range and target
   * application information represented by this BlocklistItemData using the
   * provided application and toolkit versions.
   * @param   version
   *          The version of the item being tested.
   * @param   appVersion
   *          The application version to test with.
   * @param   toolkitVersion
   *          The toolkit version to test with.
   * @returns True if the version range covers the item version and application
   *          or toolkit version.
   */
  includesItem(version, appVersion, toolkitVersion) {
    // Some platforms have no version for plugins, these don't match if there
    // was a min/maxVersion provided
    if (!version && (this.minVersion || this.maxVersion))
      return false;

    // Check if the item version matches
    if (!this.matchesRange(version, this.minVersion, this.maxVersion))
      return false;

    // Check if the application version matches
    if (this.matchesTargetRange(gApp.ID, appVersion))
      return true;

    // Check if the toolkit version matches
    return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
  },

  /**
   * Checks if a version is higher than or equal to the minVersion (if provided)
   * and lower than or equal to the maxVersion (if provided).
   * @param   version
   *          The version to test.
   * @param   minVersion
   *          The minimum version. If null it is assumed that version is always
   *          larger.
   * @param   maxVersion
   *          The maximum version. If null it is assumed that version is always
   *          smaller.
   */
  matchesRange(version, minVersion, maxVersion) {
    if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
      return false;
    if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
      return false;
    return true;
  },

  /**
   * Tests if there is a matching range for the given target application id and
   * version.
   * @param   appID
   *          The application ID to test for, may be for an application or toolkit
   * @param   appVersion
   *          The version of the application to test for.
   * @returns True if this version range covers the application version given.
   */
  matchesTargetRange(appID, appVersion) {
    var blTargetApp = this.targetApps[appID];
    if (!blTargetApp)
      return false;

    for (let app of blTargetApp) {
      if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
        return true;
    }

    return false;
  },

  /**
   * Retrieves a version range (e.g. minVersion and maxVersion) for a
   * blocklist item's targetApplication element.
   * @param   targetAppElement
   *          A targetApplication blocklist element.
   * @returns An array of JS objects with the following properties:
   *          "minVersion"  The minimum version in a version range (default = null).
   *          "maxVersion"  The maximum version in a version range (default = null).
   */
  getBlocklistAppVersions(targetAppElement) {
    var appVersions = [ ];

    if (targetAppElement) {
      for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
        var versionRangeElement = targetAppElement.childNodes.item(i);
        if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
            versionRangeElement.localName != "versionRange")
          continue;
        appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
      }
    }
    // return minVersion = null and maxVersion = null if no specific versionRange
    // elements were found
    if (appVersions.length == 0)
      appVersions.push(this.getBlocklistVersionRange(null));
    return appVersions;
  },

  /**
   * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
   * versionRange element.
   * @param   versionRangeElement
   *          The versionRange blocklist element.
   * @returns A JS object with the following properties:
   *          "minVersion"  The minimum version in a version range (default = null).
   *          "maxVersion"  The maximum version in a version range (default = null).
   */
  getBlocklistVersionRange(versionRangeElement) {
    var minVersion = null;
    var maxVersion = null;
    if (!versionRangeElement)
      return { minVersion, maxVersion };

    if (versionRangeElement.hasAttribute("minVersion"))
      minVersion = versionRangeElement.getAttribute("minVersion");
    if (versionRangeElement.hasAttribute("maxVersion"))
      maxVersion = versionRangeElement.getAttribute("maxVersion");

    return { minVersion, maxVersion };
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);
PK
!<


'components/nsBlocklistServiceContent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

const kMissingAPIMessage = "Unsupported blocklist call in the child process."

/*
 * A lightweight blocklist proxy for the content process that traps plugin
 * related blocklist checks and forwards them to the parent. This interface is
 * primarily designed to insure overlays work.. it does not control plugin
 * or addon loading.
 */

function Blocklist() {
  this.init();
}

Blocklist.prototype = {
  classID: Components.ID("{e0a106ed-6ad4-47a4-b6af-2f1c8aa4712d}"),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsIBlocklistService]),

  init() {
    Services.cpmm.addMessageListener("Blocklist:blocklistInvalidated", this);
    Services.obs.addObserver(this, "xpcom-shutdown");
  },

  uninit() {
    Services.cpmm.removeMessageListener("Blocklist:blocklistInvalidated", this);
    Services.obs.removeObserver(this, "xpcom-shutdown");
  },

  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
    case "xpcom-shutdown":
      this.uninit();
      break;
    }
  },

  // Message manager message handlers
  receiveMessage(aMsg) {
    switch (aMsg.name) {
      case "Blocklist:blocklistInvalidated":
        Services.obs.notifyObservers(null, "blocklist-updated");
        Services.cpmm.sendAsyncMessage("Blocklist:content-blocklist-updated");
        break;
      default:
        throw new Error("Unknown blocklist message received from content: " + aMsg.name);
    }
  },

  /*
   * A helper that queries key data from a plugin or addon object
   * and generates a simple data wrapper suitable for ipc. We hand
   * these directly to the nsBlockListService in the parent which
   * doesn't query for much.. allowing us to get away with this.
   */
  flattenObject(aTag) {
    // Based on debugging the nsBlocklistService, these are the props the
    // parent side will check on our objects.
    let props = ["name", "description", "filename", "version"];
    let dataWrapper = {};
    for (let prop of props) {
      dataWrapper[prop] = aTag[prop];
    }
    return dataWrapper;
  },

  // We support the addon methods here for completeness, but content currently
  // only calls getPluginBlocklistState.

  isAddonBlocklisted(aAddon, aAppVersion, aToolkitVersion) {
    return true;
  },

  getAddonBlocklistState(aAddon, aAppVersion, aToolkitVersion) {
    return Components.interfaces.nsIBlocklistService.STATE_BLOCKED;
  },

  // There are a few callers in layout that rely on this.
  getPluginBlocklistState(aPluginTag, aAppVersion, aToolkitVersion) {
    return Services.cpmm.sendSyncMessage("Blocklist:getPluginBlocklistState", {
      addonData: this.flattenObject(aPluginTag),
      appVersion: aAppVersion,
      toolkitVersion: aToolkitVersion
    })[0];
  },

  getAddonBlocklistURL(aAddon, aAppVersion, aToolkitVersion) {
    throw new Error(kMissingAPIMessage);
  },

  getPluginBlocklistURL(aPluginTag) {
    throw new Error(kMissingAPIMessage);
  },

  getPluginInfoURL(aPluginTag) {
    throw new Error(kMissingAPIMessage);
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);
PK
!<!Qw77components/nsUpdateService.js/* -*- Mode: javascript; 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/. */

"use strict";

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/FileUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/ctypes.jsm", this);
Cu.import("resource://gre/modules/UpdateTelemetry.jsm", this);
Cu.import("resource://gre/modules/AppConstants.jsm", this);
Cu.importGlobalProperties(["XMLHttpRequest"]);

const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";

const PREF_APP_UPDATE_ALTWINDOWTYPE        = "app.update.altwindowtype";
const PREF_APP_UPDATE_AUTO                 = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUNDINTERVAL   = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS     = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS  = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CANCELATIONS         = "app.update.cancelations";
const PREF_APP_UPDATE_CANCELATIONS_OSX     = "app.update.cancelations.osx";
const PREF_APP_UPDATE_CANCELATIONS_OSX_MAX = "app.update.cancelations.osx.max";
const PREF_APP_UPDATE_DOORHANGER           = "app.update.doorhanger";
const PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS    = "app.update.download.attempts";
const PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS = "app.update.download.maxAttempts";
const PREF_APP_UPDATE_ELEVATE_NEVER        = "app.update.elevate.never";
const PREF_APP_UPDATE_ELEVATE_VERSION      = "app.update.elevate.version";
const PREF_APP_UPDATE_ELEVATE_ATTEMPTS     = "app.update.elevate.attempts";
const PREF_APP_UPDATE_ELEVATE_MAXATTEMPTS  = "app.update.elevate.maxAttempts";
const PREF_APP_UPDATE_ENABLED              = "app.update.enabled";
const PREF_APP_UPDATE_IDLETIME             = "app.update.idletime";
const PREF_APP_UPDATE_LOG                  = "app.update.log";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED  = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_POSTUPDATE           = "app.update.postupdate";
const PREF_APP_UPDATE_PROMPTWAITTIME       = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SERVICE_ENABLED      = "app.update.service.enabled";
const PREF_APP_UPDATE_SERVICE_ERRORS       = "app.update.service.errors";
const PREF_APP_UPDATE_SERVICE_MAXERRORS    = "app.update.service.maxErrors";
const PREF_APP_UPDATE_SILENT               = "app.update.silent";
const PREF_APP_UPDATE_SOCKET_MAXERRORS     = "app.update.socket.maxErrors";
const PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT  = "app.update.socket.retryTimeout";
const PREF_APP_UPDATE_STAGING_ENABLED      = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL                  = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS          = "app.update.url.details";

const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
const URI_UPDATE_NS             = "http://www.mozilla.org/2005/app-update";
const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";

const KEY_UPDROOT         = "UpdRootD";
const KEY_EXECUTABLE      = "XREExeF";

const DIR_UPDATES         = "updates";

const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
const FILE_LAST_UPDATE_LOG   = "last-update.log";
const FILE_UPDATES_XML       = "updates.xml";
const FILE_UPDATE_LOG        = "update.log";
const FILE_UPDATE_MAR        = "update.mar";
const FILE_UPDATE_STATUS     = "update.status";
const FILE_UPDATE_TEST       = "update.test";
const FILE_UPDATE_VERSION    = "update.version";

const STATE_NONE            = "null";
const STATE_DOWNLOADING     = "downloading";
const STATE_PENDING         = "pending";
const STATE_PENDING_SERVICE = "pending-service";
const STATE_PENDING_ELEVATE = "pending-elevate";
const STATE_APPLYING        = "applying";
const STATE_APPLIED         = "applied";
const STATE_APPLIED_SERVICE = "applied-service";
const STATE_SUCCEEDED       = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED          = "failed";

// The values below used by this code are from common/errors.h
const WRITE_ERROR                          = 7;
const ELEVATION_CANCELED                   = 9;
const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
const SERVICE_UPDATER_SIGN_ERROR           = 26;
const SERVICE_UPDATER_COMPARE_ERROR        = 27;
const SERVICE_UPDATER_IDENTITY_ERROR       = 28;
const SERVICE_STILL_APPLYING_ON_SUCCESS    = 29;
const SERVICE_STILL_APPLYING_ON_FAILURE    = 30;
const SERVICE_UPDATER_NOT_FIXED_DRIVE      = 31;
const SERVICE_COULD_NOT_LOCK_UPDATER       = 32;
const SERVICE_INSTALLDIR_ERROR             = 33;
const WRITE_ERROR_ACCESS_DENIED            = 35;
const WRITE_ERROR_CALLBACK_APP             = 37;
const SERVICE_COULD_NOT_COPY_UPDATER       = 49;
const SERVICE_STILL_APPLYING_TERMINATED    = 50;
const SERVICE_STILL_APPLYING_NO_EXIT_CODE  = 51;
const WRITE_ERROR_FILE_COPY                = 61;
const WRITE_ERROR_DELETE_FILE              = 62;
const WRITE_ERROR_OPEN_PATCH_FILE          = 63;
const WRITE_ERROR_PATCH_FILE               = 64;
const WRITE_ERROR_APPLY_DIR_PATH           = 65;
const WRITE_ERROR_CALLBACK_PATH            = 66;
const WRITE_ERROR_FILE_ACCESS_DENIED       = 67;
const WRITE_ERROR_DIR_ACCESS_DENIED        = 68;
const WRITE_ERROR_DELETE_BACKUP            = 69;
const WRITE_ERROR_EXTRACT                  = 70;

// Array of write errors to simplify checks for write errors
const WRITE_ERRORS = [WRITE_ERROR,
                      WRITE_ERROR_ACCESS_DENIED,
                      WRITE_ERROR_CALLBACK_APP,
                      WRITE_ERROR_FILE_COPY,
                      WRITE_ERROR_DELETE_FILE,
                      WRITE_ERROR_OPEN_PATCH_FILE,
                      WRITE_ERROR_PATCH_FILE,
                      WRITE_ERROR_APPLY_DIR_PATH,
                      WRITE_ERROR_CALLBACK_PATH,
                      WRITE_ERROR_FILE_ACCESS_DENIED,
                      WRITE_ERROR_DIR_ACCESS_DENIED,
                      WRITE_ERROR_DELETE_BACKUP,
                      WRITE_ERROR_EXTRACT];

// Array of write errors to simplify checks for service errors
const SERVICE_ERRORS = [SERVICE_UPDATER_COULD_NOT_BE_STARTED,
                        SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS,
                        SERVICE_UPDATER_SIGN_ERROR,
                        SERVICE_UPDATER_COMPARE_ERROR,
                        SERVICE_UPDATER_IDENTITY_ERROR,
                        SERVICE_STILL_APPLYING_ON_SUCCESS,
                        SERVICE_STILL_APPLYING_ON_FAILURE,
                        SERVICE_UPDATER_NOT_FIXED_DRIVE,
                        SERVICE_COULD_NOT_LOCK_UPDATER,
                        SERVICE_INSTALLDIR_ERROR,
                        SERVICE_COULD_NOT_COPY_UPDATER,
                        SERVICE_STILL_APPLYING_TERMINATED,
                        SERVICE_STILL_APPLYING_NO_EXIT_CODE];

// Error codes 80 through 99 are reserved for nsUpdateService.js and are not
// defined in common/errors.h
const INVALID_UPDATER_STATE_CODE           = 98;
const INVALID_UPDATER_STATUS_CODE          = 99;

// Custom update error codes
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
const NETWORK_ERROR_OFFLINE             = 111;

// Error codes should be < 1000. Errors above 1000 represent http status codes
const HTTP_ERROR_OFFSET                 = 1000;

const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
const DOWNLOAD_BACKGROUND_INTERVAL  = 600;    // seconds
const DOWNLOAD_FOREGROUND_INTERVAL  = 0;

const UPDATE_WINDOW_NAME      = "Update:Wizard";

// The number of consecutive failures when updating using the service before
// setting the app.update.service.enabled preference to false.
const DEFAULT_SERVICE_MAX_ERRORS = 10;

// The number of consecutive socket errors to allow before falling back to
// downloading a different MAR file or failing if already downloading the full.
const DEFAULT_SOCKET_MAX_ERRORS = 10;

// The number of milliseconds to wait before retrying a connection error.
const DEFAULT_SOCKET_RETRYTIMEOUT = 2000;

// Default maximum number of elevation cancelations per update version before
// giving up.
const DEFAULT_CANCELATIONS_OSX_MAX = 3;

// This maps app IDs to their respective notification topic which signals when
// the application's user interface has been displayed.
const APPID_TO_TOPIC = {
  // Firefox
  "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "sessionstore-windows-restored",
  // SeaMonkey
  "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "sessionstore-windows-restored",
  // Thunderbird
  "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
  // Instantbird
  "{33cb9019-c295-46dd-be21-8c4936574bee}": "xul-window-visible",
};

var gUpdateMutexHandle = null;

XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                  "resource://gre/modules/WindowsRegistry.jsm");

XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
});

XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
  return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
});

// shared code for suppressing bad cert dialogs
XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
  let temp = { };
  Cu.import("resource://gre/modules/CertUtils.jsm", temp);
  return temp;
});

/**
 * Tests to make sure that we can write to a given directory.
 *
 * @param updateTestFile a test file in the directory that needs to be tested.
 * @param createDirectory whether a test directory should be created.
 * @throws if we don't have right access to the directory.
 */
function testWriteAccess(updateTestFile, createDirectory) {
  const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
  const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
  if (updateTestFile.exists())
    updateTestFile.remove(false);
  updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
                        createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
  updateTestFile.remove(false);
}

/**
 * Windows only function that closes a Win32 handle.
 *
 * @param handle The handle to close
 */
function closeHandle(handle) {
  let lib = ctypes.open("kernel32.dll");
  let CloseHandle = lib.declare("CloseHandle",
                                ctypes.winapi_abi,
                                ctypes.int32_t, /* success */
                                ctypes.void_t.ptr); /* handle */
  CloseHandle(handle);
  lib.close();
}

/**
 * Windows only function that creates a mutex.
 *
 * @param  aName
 *         The name for the mutex.
 * @param  aAllowExisting
 *         If false the function will close the handle and return null.
 * @return The Win32 handle to the mutex.
 */
function createMutex(aName, aAllowExisting = true) {
  if (AppConstants.platform != "win") {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  }

  const INITIAL_OWN = 1;
  const ERROR_ALREADY_EXISTS = 0xB7;
  let lib = ctypes.open("kernel32.dll");
  let CreateMutexW = lib.declare("CreateMutexW",
                                 ctypes.winapi_abi,
                                 ctypes.void_t.ptr, /* return handle */
                                 ctypes.void_t.ptr, /* security attributes */
                                 ctypes.int32_t, /* initial owner */
                                 ctypes.char16_t.ptr); /* name */

  let handle = CreateMutexW(null, INITIAL_OWN, aName);
  let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
  if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
    closeHandle(handle);
    handle = null;
  }
  lib.close();

  if (handle && handle.isNull()) {
    handle = null;
  }

  return handle;
}

/**
 * Windows only function that determines a unique mutex name for the
 * installation.
 *
 * @param aGlobal true if the function should return a global mutex. A global
 *                mutex is valid across different sessions
 * @return Global mutex path
 */
function getPerInstallationMutexName(aGlobal = true) {
  if (AppConstants.platform != "win") {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  }

  let hasher = Cc["@mozilla.org/security/hash;1"].
               createInstance(Ci.nsICryptoHash);
  hasher.init(hasher.SHA1);

  let exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);

  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                  createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  var data = converter.convertToByteArray(exeFile.path.toLowerCase());

  hasher.update(data, data.length);
  return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
}

/**
 * Whether or not the current instance has the update mutex. The update mutex
 * gives protection against 2 applications from the same installation updating:
 * 1) Running multiple profiles from the same installation path
 * 2) Two applications running in 2 different user sessions from the same path
 *
 * @return true if this instance holds the update mutex
 */
function hasUpdateMutex() {
  if (AppConstants.platform != "win") {
    return true;
  }
  if (!gUpdateMutexHandle) {
    gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
  }
  return !!gUpdateMutexHandle;
}

/**
 * Determines whether or not all descendants of a directory are writeable.
 * Note: Does not check the root directory itself for writeability.
 *
 * @return true if all descendants are writeable, false otherwise
 */
function areDirectoryEntriesWriteable(aDir) {
  let items = aDir.directoryEntries;
  while (items.hasMoreElements()) {
    let item = items.getNext().QueryInterface(Ci.nsIFile);
    if (!item.isWritable()) {
      LOG("areDirectoryEntriesWriteable - unable to write to " + item.path);
      return false;
    }
    if (item.isDirectory() && !areDirectoryEntriesWriteable(item)) {
      return false;
    }
  }
  return true;
}

/**
 * OSX only function to determine if the user requires elevation to be able to
 * write to the application bundle.
 *
 * @return true if elevation is required, false otherwise
 */
function getElevationRequired() {
  if (AppConstants.platform != "macosx") {
    return false;
  }

  try {
    // Recursively check that the application bundle (and its descendants) can
    // be written to.
    LOG("getElevationRequired - recursively testing write access on " +
        getInstallDirRoot().path);
    if (!getInstallDirRoot().isWritable() ||
        !areDirectoryEntriesWriteable(getInstallDirRoot())) {
      LOG("getElevationRequired - unable to write to application bundle, " +
          "elevation required");
      return true;
    }
  } catch (ex) {
    LOG("getElevationRequired - unable to write to application bundle, " +
        "elevation required. Exception: " + ex);
    return true;
  }
  LOG("getElevationRequired - able to write to application bundle, elevation " +
      "not required");
  return false;
}

/**
 * Determines whether or not an update can be applied. This is always true on
 * Windows when the service is used. Also, this is always true on OSX because we
 * offer users the option to perform an elevated update when necessary.
 *
 * @return true if an update can be applied, false otherwise
 */
function getCanApplyUpdates() {
  if (AppConstants.platform == "macosx") {
    LOG("getCanApplyUpdates - bypass the write since elevation can be used " +
        "on Mac OS X");
    return true;
  }

  if (shouldUseService()) {
    LOG("getCanApplyUpdates - bypass the write checks because the Windows " +
        "Maintenance Service can be used");
    return true;
  }

  try {
    // Test write access to the updates directory. On Linux the updates
    // directory is located in the installation directory so this is the only
    // write access check that is necessary to tell whether the user can apply
    // updates. On Windows the updates directory is in the user's local
    // application data directory so this should always succeed and additional
    // checks are performed below.
    let updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
    LOG("getCanApplyUpdates - testing write access " + updateTestFile.path);
    testWriteAccess(updateTestFile, false);
    if (AppConstants.platform == "win") {
      // On Windows when the maintenance service isn't used updates can still be
      // performed in a location requiring admin privileges by the client
      // accepting a UAC prompt from an elevation request made by the updater.
      // Whether the client can elevate (e.g. has a split token) is determined
      // in nsXULAppInfo::GetUserCanElevate which is located in nsAppRunner.cpp.
      let userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
                           userCanElevate;
      if (!userCanElevate) {
        // if we're unable to create the test file this will throw an exception.
        let appDirTestFile = getAppBaseDir();
        appDirTestFile.append(FILE_UPDATE_TEST);
        LOG("getCanApplyUpdates - testing write access " + appDirTestFile.path);
        if (appDirTestFile.exists()) {
          appDirTestFile.remove(false);
        }
        appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
        appDirTestFile.remove(false);
      }
    }
  } catch (e) {
    LOG("getCanApplyUpdates - unable to apply updates. Exception: " + e);
    // No write access to the installation directory
    return false;
  }

  LOG("getCanApplyUpdates - able to apply updates");
  return true;
}

/**
 * Whether or not the application can stage an update for the current session.
 * These checks are only performed once per session due to using a lazy getter.
 *
 * @return true if updates can be staged for this session.
 */
XPCOMUtils.defineLazyGetter(this, "gCanStageUpdatesSession", function aus_gCSUS() {
  if (getElevationRequired()) {
    LOG("gCanStageUpdatesSession - unable to stage updates because elevation " +
        "is required.");
    return false;
  }

  try {
    let updateTestFile;
    if (AppConstants.platform == "macosx") {
      updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
    } else {
      updateTestFile = getInstallDirRoot();
      updateTestFile.append(FILE_UPDATE_TEST);
    }
    LOG("gCanStageUpdatesSession - testing write access " +
        updateTestFile.path);
    testWriteAccess(updateTestFile, true);
    if (AppConstants.platform != "macosx") {
      // On all platforms except Mac, we need to test the parent directory as
      // well, as we need to be able to move files in that directory during the
      // replacing step.
      updateTestFile = getInstallDirRoot().parent;
      updateTestFile.append(FILE_UPDATE_TEST);
      LOG("gCanStageUpdatesSession - testing write access " +
          updateTestFile.path);
      updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
                                  FileUtils.PERMS_DIRECTORY);
      updateTestFile.remove(false);
    }
  } catch (e) {
    LOG("gCanStageUpdatesSession - unable to stage updates. Exception: " +
        e);
    // No write privileges
    return false;
  }

  LOG("gCanStageUpdatesSession - able to stage updates");
  return true;
});

/**
 * Whether or not the application can stage an update.
 *
 * @return true if updates can be staged.
 */
function getCanStageUpdates() {
  // If staging updates are disabled, then just bail out!
  if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
    LOG("getCanStageUpdates - staging updates is disabled by preference " +
        PREF_APP_UPDATE_STAGING_ENABLED);
    return false;
  }

  if (AppConstants.platform == "win" && shouldUseService()) {
    // No need to perform directory write checks, the maintenance service will
    // be able to write to all directories.
    LOG("getCanStageUpdates - able to stage updates using the service");
    return true;
  }

  if (!hasUpdateMutex()) {
    LOG("getCanStageUpdates - unable to apply updates because another " +
        "instance of the application is already handling updates for this " +
        "installation.");
    return false;
  }

  return gCanStageUpdatesSession;
}

XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
  // If the administrator has disabled app update and locked the preference so
  // users can't check for updates. This preference check is ok in this lazy
  // getter since locked prefs don't change until the application is restarted.
  var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
  if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
    LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
        "the preference is disabled and admistratively locked.");
    return false;
  }

  // If we don't know the binary platform we're updating, we can't update.
  if (!UpdateUtils.ABI) {
    LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
    return false;
  }

  // If we don't know the OS version we're updating, we can't update.
  if (!UpdateUtils.OSVersion) {
    LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
        "version");
    return false;
  }

  LOG("gCanCheckForUpdates - able to check for updates");
  return true;
});

/**
 * Logs a string to the error console.
 * @param   string
 *          The string to write to the error console.
 */
function LOG(string) {
  if (gLogEnabled) {
    dump("*** AUS:SVC " + string + "\n");
    Services.console.logStringMessage("AUS:SVC " + string);
  }
}

/**
 * Gets a preference value, handling the case where there is no default.
 * @param   func
 *          The name of the preference function to call, on nsIPrefBranch
 * @param   preference
 *          The name of the preference
 * @param   defaultValue
 *          The default value to return in the event the preference has
 *          no setting
 * @return  The value of the preference, or undefined if there was no
 *          user or default value.
 */
function getPref(func, preference, defaultValue) {
  try {
    return Services.prefs[func](preference);
  } catch (e) {
  }
  return defaultValue;
}

/**
 * Convert a string containing binary values to hex.
 */
function binaryToHex(input) {
  var result = "";
  for (var i = 0; i < input.length; ++i) {
    var hex = input.charCodeAt(i).toString(16);
    if (hex.length == 1)
      hex = "0" + hex;
    result += hex;
  }
  return result;
}

/**
 * Gets the specified directory at the specified hierarchy under the
 * update root directory and creates it if it doesn't exist.
 * @param   pathArray
 *          An array of path components to locate beneath the directory
 *          specified by |key|
 * @return  nsIFile object for the location specified.
 */
function getUpdateDirCreate(pathArray) {
  return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
}

/**
 * Gets the application base directory.
 *
 * @return  nsIFile object for the application base directory.
 */
function getAppBaseDir() {
  return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
}

/**
 * Gets the root of the installation directory which is the application
 * bundle directory on Mac OS X and the location of the application binary
 * on all other platforms.
 *
 * @return nsIFile object for the directory
 */
function getInstallDirRoot() {
  let dir = getAppBaseDir();
  if (AppConstants.platform == "macosx") {
    // On Mac, we store the Updated.app directory inside the bundle directory.
    dir = dir.parent.parent;
  }
  return dir;
}

/**
 * Gets the file at the specified hierarchy under the update root directory.
 * @param   pathArray
 *          An array of path components to locate beneath the directory
 *          specified by |key|. The last item in this array must be the
 *          leaf name of a file.
 * @return  nsIFile object for the file specified. The file is NOT created
 *          if it does not exist, however all required directories along
 *          the way are.
 */
function getUpdateFile(pathArray) {
  let file = getUpdateDirCreate(pathArray.slice(0, -1));
  file.append(pathArray[pathArray.length - 1]);
  return file;
}

/**
 * Returns human readable status text from the updates.properties bundle
 * based on an error code
 * @param   code
 *          The error code to look up human readable status text for
 * @param   defaultCode
 *          The default code to look up should human readable status text
 *          not exist for |code|
 * @return  A human readable status text string
 */
function getStatusTextFromCode(code, defaultCode) {
  let reason;
  try {
    reason = gUpdateBundle.GetStringFromName("check_error-" + code);
    LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
        code);
  } catch (e) {
    // Use the default reason
    reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
    LOG("getStatusTextFromCode - transfer error: " + reason +
        ", default code: " + defaultCode);
  }
  return reason;
}

/**
 * Get the Active Updates directory
 * @return The active updates directory, as a nsIFile object
 */
function getUpdatesDir() {
  // Right now, we only support downloading one patch at a time, so we always
  // use the same target directory.
  return getUpdateDirCreate([DIR_UPDATES, "0"]);
}

/**
 * Reads the update state from the update.status file in the specified
 * directory.
 * @param   dir
 *          The dir to look for an update.status file in
 * @return  The status value of the update.
 */
function readStatusFile(dir) {
  let statusFile = dir.clone();
  statusFile.append(FILE_UPDATE_STATUS);
  let status = readStringFromFile(statusFile) || STATE_NONE;
  LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
  return status;
}

/**
 * Writes the current update operation/state to a file in the patch
 * directory, indicating to the patching system that operations need
 * to be performed.
 * @param   dir
 *          The patch directory where the update.status file should be
 *          written.
 * @param   state
 *          The state value to write.
 */
function writeStatusFile(dir, state) {
  let statusFile = dir.clone();
  statusFile.append(FILE_UPDATE_STATUS);
  writeStringToFile(statusFile, state);
}

/**
 * Writes the update's application version to a file in the patch directory. If
 * the update doesn't provide application version information via the
 * appVersion attribute the string "null" will be written to the file.
 * This value is compared during startup (in nsUpdateDriver.cpp) to determine if
 * the update should be applied. Note that this won't provide protection from
 * downgrade of the application for the nightly user case where the application
 * version doesn't change.
 * @param   dir
 *          The patch directory where the update.version file should be
 *          written.
 * @param   version
 *          The version value to write. Will be the string "null" when the
 *          update doesn't provide the appVersion attribute in the update xml.
 */
function writeVersionFile(dir, version) {
  let versionFile = dir.clone();
  versionFile.append(FILE_UPDATE_VERSION);
  writeStringToFile(versionFile, version);
}

/**
 * Determines if the service should be used to attempt an update
 * or not.
 *
 * @return  true if the service should be used for updates.
 */
function shouldUseService() {
  // This function will return true if the mantenance service should be used if
  // all of the following conditions are met:
  // 1) This build was done with the maintenance service enabled
  // 2) The maintenance service is installed
  // 3) The pref for using the service is enabled
  if (!AppConstants.MOZ_MAINTENANCE_SERVICE || !isServiceInstalled() ||
      !getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
    LOG("shouldUseService - returning false");
    return false;
  }

  LOG("shouldUseService - returning true");
  return true;
}

/**
 * Determines if the service is is installed.
 *
 * @return  true if the service is installed.
 */
function isServiceInstalled() {
  if (!AppConstants.MOZ_MAINTENANCE_SERVICE || AppConstants.platform != "win") {
    LOG("isServiceInstalled - returning false");
    return false;
  }

  let installed = 0;
  try {
    let wrk = Cc["@mozilla.org/windows-registry-key;1"].
              createInstance(Ci.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) {
  }
  installed = installed == 1;  // convert to bool
  LOG("isServiceInstalled - returning " + installed);
  return installed;
}

/**
 * Removes the contents of the updates patch directory and rotates the update
 * logs when present. If the update.log exists in the patch directory this will
 * move the last-update.log if it exists to backup-update.log in the parent
 * directory of the patch directory and then move the update.log in the patch
 * directory to last-update.log in the parent directory of the patch directory.
 *
 * @param aRemovePatchFiles (optional, defaults to true)
 *        When true the update's patch directory contents are removed.
 */
function cleanUpUpdatesDir(aRemovePatchFiles = true) {
  let updateDir;
  try {
    updateDir = getUpdatesDir();
  } catch (e) {
    LOG("cleanUpUpdatesDir - unable to get the updates patch directory. " +
        "Exception: " + e);
    return;
  }

  // Preserve the last update log file for debugging purposes.
  let updateLogFile = updateDir.clone();
  updateLogFile.append(FILE_UPDATE_LOG);
  if (updateLogFile.exists()) {
    let dir = updateDir.parent;
    let logFile = dir.clone();
    logFile.append(FILE_LAST_UPDATE_LOG);
    if (logFile.exists()) {
      try {
        logFile.moveTo(dir, FILE_BACKUP_UPDATE_LOG);
      } catch (e) {
        LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
            " to " + FILE_BACKUP_UPDATE_LOG);
      }
    }

    try {
      updateLogFile.moveTo(dir, FILE_LAST_UPDATE_LOG);
    } catch (e) {
      LOG("cleanUpUpdatesDir - failed to rename file " + updateLogFile.path +
          " to " + FILE_LAST_UPDATE_LOG);
    }
  }

  if (aRemovePatchFiles) {
    let dirEntries = updateDir.directoryEntries;
    while (dirEntries.hasMoreElements()) {
      let file = dirEntries.getNext().QueryInterface(Ci.nsIFile);
      // Now, recursively remove this file.  The recursive removal is needed for
      // Mac OSX because this directory will contain a copy of updater.app,
      // which is itself a directory and the MozUpdater directory on platforms
      // other than Windows.
      try {
        file.remove(true);
      } catch (e) {
        LOG("cleanUpUpdatesDir - failed to remove file " + file.path);
      }
    }
  }
}

/**
 * Clean up updates list and the updates directory.
 */
function cleanupActiveUpdate() {
  // Move the update from the Active Update list into the Past Updates list.
  var um = Cc["@mozilla.org/updates/update-manager;1"].
           getService(Ci.nsIUpdateManager);
  um.activeUpdate = null;
  um.saveUpdates();

  // Now trash the updates directory, since we're done with it
  cleanUpUpdatesDir();
}

/**
 * An enumeration of items in a JS array.
 * @constructor
 */
function ArrayEnumerator(aItems) {
  this._index = 0;
  if (aItems) {
    for (var i = 0; i < aItems.length; ++i) {
      if (!aItems[i])
        aItems.splice(i, 1);
    }
  }
  this._contents = aItems;
}

ArrayEnumerator.prototype = {
  _index: 0,
  _contents: [],

  hasMoreElements: function ArrayEnumerator_hasMoreElements() {
    return this._index < this._contents.length;
  },

  getNext: function ArrayEnumerator_getNext() {
    return this._contents[this._index++];
  }
};

/**
 * Writes a string of text to a file.  A newline will be appended to the data
 * written to the file.  This function only works with ASCII text.
 */
function writeStringToFile(file, text) {
  let fos = FileUtils.openSafeFileOutputStream(file);
  text += "\n";
  fos.write(text, text.length);
  FileUtils.closeSafeFileOutputStream(fos);
}

function readStringFromInputStream(inputStream) {
  var sis = Cc["@mozilla.org/scriptableinputstream;1"].
            createInstance(Ci.nsIScriptableInputStream);
  sis.init(inputStream);
  var text = sis.read(sis.available());
  sis.close();
  if (text && text[text.length - 1] == "\n") {
    text = text.slice(0, -1);
  }
  return text;
}

/**
 * Reads a string of text from a file.  A trailing newline will be removed
 * before the result is returned.  This function only works with ASCII text.
 */
function readStringFromFile(file) {
  if (!file.exists()) {
    LOG("readStringFromFile - file doesn't exist: " + file.path);
    return null;
  }
  var fis = Cc["@mozilla.org/network/file-input-stream;1"].
            createInstance(Ci.nsIFileInputStream);
  fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
  return readStringFromInputStream(fis);
}

function handleUpdateFailure(update, errorCode) {
  update.errorCode = parseInt(errorCode);
  if (WRITE_ERRORS.includes(update.errorCode)) {
    Cc["@mozilla.org/updates/update-prompt;1"].
      createInstance(Ci.nsIUpdatePrompt).
      showUpdateError(update);
    writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
    return true;
  }

  if (update.errorCode == ELEVATION_CANCELED) {
    if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
      let elevationAttempts = getPref("getIntPref", PREF_APP_UPDATE_ELEVATE_ATTEMPTS, 0);
      elevationAttempts++;
      Services.prefs.setIntPref(PREF_APP_UPDATE_ELEVATE_ATTEMPTS, elevationAttempts);
      let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_ELEVATE_MAXATTEMPTS, 2), 10);

      if (elevationAttempts > maxAttempts) {
        LOG("handleUpdateFailure - notifying observers of error. " +
            "topic: update-error, status: elevation-attempts-exceeded");
        Services.obs.notifyObservers(update, "update-error", "elevation-attempts-exceeded");
      } else {
        LOG("handleUpdateFailure - notifying observers of error. " +
            "topic: update-error, status: elevation-attempt-failed");
        Services.obs.notifyObservers(update, "update-error", "elevation-attempt-failed");
      }
    }

    let cancelations = getPref("getIntPref", PREF_APP_UPDATE_CANCELATIONS, 0);
    cancelations++;
    Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
    if (AppConstants.platform == "macosx") {
      let osxCancelations = getPref("getIntPref",
                                    PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
      osxCancelations++;
      Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
                                osxCancelations);
      let maxCancels = getPref("getIntPref",
                               PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
                               DEFAULT_CANCELATIONS_OSX_MAX);
      // Prevent the preference from setting a value greater than 5.
      maxCancels = Math.min(maxCancels, 5);
      if (osxCancelations >= maxCancels) {
        cleanupActiveUpdate();
      } else {
        writeStatusFile(getUpdatesDir(),
                        update.state = STATE_PENDING_ELEVATE);
      }
      update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
      update.QueryInterface(Ci.nsIWritablePropertyBag);
      update.setProperty("patchingFailed", "elevationFailure");
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateError(update);
    } else {
      writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
    }
    return true;
  }

  if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS)) {
    Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS);
  }
  if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
    Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
  }

  if (SERVICE_ERRORS.includes(update.errorCode)) {
    var failCount = getPref("getIntPref",
                            PREF_APP_UPDATE_SERVICE_ERRORS, 0);
    var maxFail = getPref("getIntPref",
                          PREF_APP_UPDATE_SERVICE_MAXERRORS,
                          DEFAULT_SERVICE_MAX_ERRORS);
    // Prevent the preference from setting a value greater than 10.
    maxFail = Math.min(maxFail, 10);
    // As a safety, when the service reaches maximum failures, it will
    // disable itself and fallback to using the normal update mechanism
    // without the service.
    if (failCount >= maxFail) {
      Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
      Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
    } else {
      failCount++;
      Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
                                failCount);
    }

    writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
    return true;
  }

  if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SERVICE_ERRORS)) {
    Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
  }

  return false;
}

/**
 * Fall back to downloading a complete update in case an update has failed.
 *
 * @param update the update object that has failed to apply.
 * @param postStaging true if we have just attempted to stage an update.
 */
function handleFallbackToCompleteUpdate(update, postStaging) {
  cleanupActiveUpdate();

  update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
  var oldType = update.selectedPatch ? update.selectedPatch.type
                                     : "complete";
  if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
    // Partial patch application failed, try downloading the complete
    // update in the background instead.
    LOG("handleFallbackToCompleteUpdate - install of partial patch " +
        "failed, downloading complete patch");
    var status = Cc["@mozilla.org/updates/update-service;1"].
                 getService(Ci.nsIApplicationUpdateService).
                 downloadUpdate(update, !postStaging);
    if (status == STATE_NONE)
      cleanupActiveUpdate();
  } else {
    LOG("handleFallbackToCompleteUpdate - install of complete or " +
        "only one patch offered failed. Notifying observers. topic: " +
        "update-error, status: unknown, " +
        "update.patchCount: " + update.patchCount + ", " +
        "oldType: " + oldType);
    Services.obs.notifyObservers(update, "update-error", "unknown");
  }
  update.QueryInterface(Ci.nsIWritablePropertyBag);
  update.setProperty("patchingFailed", oldType);
}

function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
  let patchType = AUSTLMY.PATCH_UNKNOWN;
  if (aUpdate && aUpdate.selectedPatch && aUpdate.selectedPatch.type) {
    if (aUpdate.selectedPatch.type == "complete") {
      patchType = AUSTLMY.PATCH_COMPLETE;
    } else if (aUpdate.selectedPatch.type == "partial") {
      patchType = AUSTLMY.PATCH_PARTIAL;
    }
  }

  let suffix = patchType + "_" + (aStartup ? AUSTLMY.STARTUP : AUSTLMY.STAGE);
  let stateCode = 0;
  let parts = aStatus.split(":");
  if (parts.length > 0) {
    switch (parts[0]) {
      case STATE_NONE:
        stateCode = 2;
        break;
      case STATE_DOWNLOADING:
        stateCode = 3;
        break;
      case STATE_PENDING:
        stateCode = 4;
        break;
      case STATE_PENDING_SERVICE:
        stateCode = 5;
        break;
      case STATE_APPLYING:
        stateCode = 6;
        break;
      case STATE_APPLIED:
        stateCode = 7;
        break;
      case STATE_APPLIED_SERVICE:
        stateCode = 9;
        break;
      case STATE_SUCCEEDED:
        stateCode = 10;
        break;
      case STATE_DOWNLOAD_FAILED:
        stateCode = 11;
        break;
      case STATE_FAILED:
        stateCode = 12;
        break;
      case STATE_PENDING_ELEVATE:
        stateCode = 13;
        break;
      default:
        stateCode = 1;
    }

    if (parts.length > 1) {
      let statusErrorCode = INVALID_UPDATER_STATE_CODE;
      if (parts[0] == STATE_FAILED) {
        statusErrorCode = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
      }
      AUSTLMY.pingStatusErrorCode(suffix, statusErrorCode);
    }
  }
  AUSTLMY.pingStateCode(suffix, stateCode);
}

/**
 * Update Patch
 * @param   patch
 *          A <patch> element to initialize this object with
 * @throws if patch has a size of 0
 * @constructor
 */
function UpdatePatch(patch) {
  this._properties = {};
  for (var i = 0; i < patch.attributes.length; ++i) {
    var attr = patch.attributes.item(i);
    attr.QueryInterface(Ci.nsIDOMAttr);
    switch (attr.name) {
      case "selected":
        this.selected = attr.value == "true";
        break;
      case "size":
        if (0 == parseInt(attr.value)) {
          LOG("UpdatePatch:init - 0-sized patch!");
          throw Cr.NS_ERROR_ILLEGAL_VALUE;
        }
        // fall through
      default:
        this[attr.name] = attr.value;
        break;
    }
  }
}
UpdatePatch.prototype = {
  /**
   * See nsIUpdateService.idl
   */
  serialize: function UpdatePatch_serialize(updates) {
    var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
    patch.setAttribute("type", this.type);
    patch.setAttribute("URL", this.URL);
    // finalURL is not available until after the download has started
    if (this.finalURL) {
      patch.setAttribute("finalURL", this.finalURL);
    }
    patch.setAttribute("size", this.size);
    if (this.selected) {
      patch.setAttribute("selected", this.selected);
    }
    patch.setAttribute("state", this.state);

    for (let p in this._properties) {
      if (this._properties[p].present) {
        patch.setAttribute(p, this._properties[p].data);
      }
    }

    return patch;
  },

  /**
   * A hash of custom properties
   */
  _properties: null,

  /**
   * See nsIWritablePropertyBag.idl
   */
  setProperty: function UpdatePatch_setProperty(name, value) {
    this._properties[name] = { data: value, present: true };
  },

  /**
   * See nsIWritablePropertyBag.idl
   */
  deleteProperty: function UpdatePatch_deleteProperty(name) {
    if (name in this._properties)
      this._properties[name].present = false;
    else
      throw Cr.NS_ERROR_FAILURE;
  },

  /**
   * See nsIPropertyBag.idl
   */
  get enumerator() {
    var properties = [];
    for (var p in this._properties)
      properties.push(this._properties[p].data);
    return new ArrayEnumerator(properties);
  },

  /**
   * See nsIPropertyBag.idl
   * Note: returns null instead of throwing when the property doesn't exist to
   *       simplify code and to silence warnings in debug builds.
   */
  getProperty: function UpdatePatch_getProperty(name) {
    if (name in this._properties &&
        this._properties[name].present) {
      return this._properties[name].data;
    }
    return null;
  },

  /**
   * Returns whether or not the update.status file for this patch exists at the
   * appropriate location.
   */
  get statusFileExists() {
    var statusFile = getUpdatesDir();
    statusFile.append(FILE_UPDATE_STATUS);
    return statusFile.exists();
  },

  /**
   * See nsIUpdateService.idl
   */
  get state() {
    if (this._properties.state)
      return this._properties.state;
    return STATE_NONE;
  },
  set state(val) {
    this._properties.state = val;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
                                         Ci.nsIPropertyBag,
                                         Ci.nsIWritablePropertyBag])
};

/**
 * Update
 * Implements nsIUpdate
 * @param   update
 *          An <update> element to initialize this object with
 * @throws if the update contains no patches
 * @constructor
 */
function Update(update) {
  this._properties = {};
  this._patches = [];
  this.isCompleteUpdate = false;
  this.unsupported = false;
  this.channel = "default";
  this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
  this.backgroundInterval = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDINTERVAL,
                                    DOWNLOAD_BACKGROUND_INTERVAL);

  // Null <update>, assume this is a message container and do no
  // further initialization
  if (!update) {
    return;
  }

  const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
  let patch;
  for (let i = 0; i < update.childNodes.length; ++i) {
    let patchElement = update.childNodes.item(i);
    if (patchElement.nodeType != ELEMENT_NODE ||
        patchElement.localName != "patch") {
      continue;
    }

    patchElement.QueryInterface(Ci.nsIDOMElement);
    try {
      patch = new UpdatePatch(patchElement);
    } catch (e) {
      continue;
    }
    this._patches.push(patch);
  }

  if (this._patches.length == 0 && !update.hasAttribute("unsupported")) {
    throw Cr.NS_ERROR_ILLEGAL_VALUE;
  }

  // Set the installDate value with the current time. If the update has an
  // installDate attribute this will be replaced with that value if it doesn't
  // equal 0.
  this.installDate = (new Date()).getTime();

  for (let i = 0; i < update.attributes.length; ++i) {
    var attr = update.attributes.item(i);
    attr.QueryInterface(Ci.nsIDOMAttr);
    if (attr.value == "undefined") {
      continue;
    } else if (attr.name == "detailsURL") {
      this._detailsURL = attr.value;
    } else if (attr.name == "installDate" && attr.value) {
      let val = parseInt(attr.value);
      if (val) {
        this.installDate = val;
      }
    } else if (attr.name == "isCompleteUpdate") {
      this.isCompleteUpdate = attr.value == "true";
    } else if (attr.name == "promptWaitTime") {
      if (!isNaN(attr.value)) {
        this.promptWaitTime = parseInt(attr.value);
      }
    } else if (attr.name == "backgroundInterval") {
      if (!isNaN(attr.value)) {
        this.backgroundInterval = parseInt(attr.value);
      }
    } else if (attr.name == "unsupported") {
      this.unsupported = attr.value == "true";
    } else {
      this[attr.name] = attr.value;

      switch (attr.name) {
        case "appVersion":
        case "buildID":
        case "channel":
        case "displayVersion":
        case "name":
        case "previousAppVersion":
        case "serviceURL":
        case "statusText":
        case "type":
          break;
        default:
          // Save custom attributes when serializing to the local xml file but
          // don't use this method for the expected attributes which are already
          // handled in serialize.
          this.setProperty(attr.name, attr.value);
          break;
      }
    }
  }

  if (!this.displayVersion) {
    this.displayVersion = this.appVersion;
  }

  // Don't allow the background download interval to be greater than 10 minutes.
  this.backgroundInterval = Math.min(this.backgroundInterval, 600);

  // The Update Name is either the string provided by the <update> element, or
  // the string: "<App Name> <Update App Version>"
  var name = "";
  if (update.hasAttribute("name")) {
    name = update.getAttribute("name");
  } else {
    var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
    var appName = brandBundle.GetStringFromName("brandShortName");
    name = gUpdateBundle.formatStringFromName("updateName",
                                              [appName, this.displayVersion], 2);
  }
  this.name = name;
}
Update.prototype = {
  /**
   * See nsIUpdateService.idl
   */
  get patchCount() {
    return this._patches.length;
  },

  /**
   * See nsIUpdateService.idl
   */
  getPatchAt: function Update_getPatchAt(index) {
    return this._patches[index];
  },

  /**
   * See nsIUpdateService.idl
   *
   * We use a copy of the state cached on this object in |_state| only when
   * there is no selected patch, i.e. in the case when we could not load
   * |.activeUpdate| from the update manager for some reason but still have
   * the update.status file to work with.
   */
  _state: "",
  set state(state) {
    if (this.selectedPatch)
      this.selectedPatch.state = state;
    this._state = state;
    return state;
  },
  get state() {
    if (this.selectedPatch)
      return this.selectedPatch.state;
    return this._state;
  },

  /**
   * See nsIUpdateService.idl
   */
  errorCode: 0,

  /**
   * See nsIUpdateService.idl
   */
  get selectedPatch() {
    for (var i = 0; i < this.patchCount; ++i) {
      if (this._patches[i].selected)
        return this._patches[i];
    }
    return null;
  },

  /**
   * See nsIUpdateService.idl
   */
  get detailsURL() {
    if (!this._detailsURL) {
      try {
        // Try using a default details URL supplied by the distribution
        // if the update XML does not supply one.
        return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
      } catch (e) {
      }
    }
    return this._detailsURL || "";
  },

  /**
   * See nsIUpdateService.idl
   */
  serialize: function Update_serialize(updates) {
    // If appVersion isn't defined just return null. This happens when cleaning
    // up invalid updates (e.g. incorrect channel).
    if (!this.appVersion) {
      return null;
    }
    var update = updates.createElementNS(URI_UPDATE_NS, "update");
    update.setAttribute("appVersion", this.appVersion);
    update.setAttribute("buildID", this.buildID);
    update.setAttribute("channel", this.channel);
    update.setAttribute("displayVersion", this.displayVersion);
    update.setAttribute("installDate", this.installDate);
    update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
    update.setAttribute("name", this.name);
    update.setAttribute("serviceURL", this.serviceURL);
    update.setAttribute("promptWaitTime", this.promptWaitTime);
    update.setAttribute("backgroundInterval", this.backgroundInterval);
    update.setAttribute("type", this.type);

    if (this.detailsURL) {
      update.setAttribute("detailsURL", this.detailsURL);
    }
    if (this.previousAppVersion) {
      update.setAttribute("previousAppVersion", this.previousAppVersion);
    }
    if (this.statusText) {
      update.setAttribute("statusText", this.statusText);
    }
    if (this.unsupported) {
      update.setAttribute("unsupported", this.unsupported);
    }
    updates.documentElement.appendChild(update);

    for (let p in this._properties) {
      if (this._properties[p].present) {
        update.setAttribute(p, this._properties[p].data);
      }
    }

    for (let i = 0; i < this.patchCount; ++i) {
      update.appendChild(this.getPatchAt(i).serialize(updates));
    }

    return update;
  },

  /**
   * A hash of custom properties
   */
  _properties: null,

  /**
   * See nsIWritablePropertyBag.idl
   */
  setProperty: function Update_setProperty(name, value) {
    this._properties[name] = { data: value, present: true };
  },

  /**
   * See nsIWritablePropertyBag.idl
   */
  deleteProperty: function Update_deleteProperty(name) {
    if (name in this._properties)
      this._properties[name].present = false;
    else
      throw Cr.NS_ERROR_FAILURE;
  },

  /**
   * See nsIPropertyBag.idl
   */
  get enumerator() {
    var properties = [];
    for (let p in this._properties) {
      properties.push(this._properties[p].data);
    }
    return new ArrayEnumerator(properties);
  },

  /**
   * See nsIPropertyBag.idl
   * Note: returns null instead of throwing when the property doesn't exist to
   *       simplify code and to silence warnings in debug builds.
   */
  getProperty: function Update_getProperty(name) {
    if (name in this._properties && this._properties[name].present) {
      return this._properties[name].data;
    }
    return null;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
                                         Ci.nsIPropertyBag,
                                         Ci.nsIWritablePropertyBag])
};

const UpdateServiceFactory = {
  _instance: null,
  createInstance(outer, iid) {
    if (outer != null)
      throw Cr.NS_ERROR_NO_AGGREGATION;
    return this._instance == null ? this._instance = new UpdateService() :
                                    this._instance;
  }
};

/**
 * UpdateService
 * A Service for managing the discovery and installation of software updates.
 * @constructor
 */
function UpdateService() {
  LOG("Creating UpdateService");
  Services.obs.addObserver(this, "xpcom-shutdown");
  Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this);
}

UpdateService.prototype = {
  /**
   * The downloader we are using to download updates. There is only ever one of
   * these.
   */
  _downloader: null,

  /**
   * Whether or not the service registered the "online" observer.
   */
  _registeredOnlineObserver: false,

  /**
   * The current number of consecutive socket errors
   */
  _consecutiveSocketErrors: 0,

  /**
   * A timer used to retry socket errors
   */
  _retryTimer: null,

  /**
   * Whether or not a background update check was initiated by the
   * application update timer notification.
   */
  _isNotify: true,

  /**
   * Handle Observer Service notifications
   * @param   subject
   *          The subject of the notification
   * @param   topic
   *          The notification name
   * @param   data
   *          Additional data
   */
  observe: function AUS_observe(subject, topic, data) {
    switch (topic) {
      case "post-update-processing":
        if (readStatusFile(getUpdatesDir()) == STATE_SUCCEEDED) {
          // After a successful update the post update preference needs to be
          // set early during startup so applications can perform post update
          // actions when they are defined in the update's metadata.
          Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
        }

        if (Services.appinfo.ID in APPID_TO_TOPIC) {
          // Delay post-update processing to ensure that possible update
          // dialogs are shown in front of the app window, if possible.
          // See bug 311614.
          Services.obs.addObserver(this, APPID_TO_TOPIC[Services.appinfo.ID]);
          break;
        }
        // intentional fallthrough
      case "sessionstore-windows-restored":
      case "mail-startup-done":
      case "xul-window-visible":
        if (Services.appinfo.ID in APPID_TO_TOPIC) {
          Services.obs.removeObserver(this,
                                      APPID_TO_TOPIC[Services.appinfo.ID]);
        }
        // intentional fallthrough
      case "test-post-update-processing":
        // Clean up any extant updates
        this._postUpdateProcessing();
        break;
      case "network:offline-status-changed":
        this._offlineStatusChanged(data);
        break;
      case "nsPref:changed":
        if (data == PREF_APP_UPDATE_LOG) {
          gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
        }
        break;
      case "xpcom-shutdown":
        Services.obs.removeObserver(this, topic);
        Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);

        if (AppConstants.platform == "win" && gUpdateMutexHandle) {
          // If we hold the update mutex, let it go!
          // The OS would clean this up sometime after shutdown,
          // but that would have no guarantee on timing.
          closeHandle(gUpdateMutexHandle);
        }
        if (this._retryTimer) {
          this._retryTimer.cancel();
        }

        this.pauseDownload();
        // Prevent leaking the downloader (bug 454964)
        this._downloader = null;
        break;
    }
  },

  /**
   * The following needs to happen during the post-update-processing
   * notification from nsUpdateServiceStub.js:
   * 1. post update processing
   * 2. resume of a download that was in progress during a previous session
   * 3. start of a complete update download after the failure to apply a partial
   *    update
   */

  /**
   * Perform post-processing on updates lingering in the updates directory
   * from a previous application session - either report install failures (and
   * optionally attempt to fetch a different version if appropriate) or
   * notify the user of install success.
   */
  _postUpdateProcessing: function AUS__postUpdateProcessing() {
    if (!this.canCheckForUpdates) {
      LOG("UpdateService:_postUpdateProcessing - unable to check for " +
          "updates... returning early");
      return;
    }

    if (!this.canApplyUpdates) {
      LOG("UpdateService:_postUpdateProcessing - unable to apply " +
          "updates... returning early");
      // If the update is present in the update directly somehow,
      // it would prevent us from notifying the user of futher updates.
      cleanupActiveUpdate();
      return;
    }

    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    var update = um.activeUpdate;
    var status = readStatusFile(getUpdatesDir());
    pingStateAndStatusCodes(update, true, status);
    // STATE_NONE status typically means that the update.status file is present
    // but a background download error occurred.
    if (status == STATE_NONE) {
      LOG("UpdateService:_postUpdateProcessing - no status, no update");
      cleanupActiveUpdate();
      return;
    }

    // Handle the case when the update is the same or older than the current
    // version and nsUpdateDriver.cpp skipped updating due to the version being
    // older than the current version.
    if (update && update.appVersion &&
        (status == STATE_PENDING || status == STATE_PENDING_SERVICE ||
         status == STATE_APPLIED || status == STATE_APPLIED_SERVICE ||
         status == STATE_PENDING_ELEVATE)) {
      if (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
          Services.vc.compare(update.appVersion, Services.appinfo.version) == 0 &&
          update.buildID == Services.appinfo.appBuildID) {
        LOG("UpdateService:_postUpdateProcessing - removing update for older " +
            "or same application version");
        cleanupActiveUpdate();
        return;
      }
    }

    if (status == STATE_DOWNLOADING) {
      LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
          "state");
      if (update && update.state != STATE_SUCCEEDED) {
        // Resume download
        status = this.downloadUpdate(update, true);
        if (status == STATE_NONE)
          cleanupActiveUpdate();
      }
      return;
    }

    if (status == STATE_APPLYING) {
      // This indicates that the background updater service is in either of the
      // following two states:
      // 1. It is in the process of applying an update in the background, and
      //    we just happen to be racing against that.
      // 2. It has failed to apply an update for some reason, and we hit this
      //    case because the updater process has set the update status to
      //    applying, but has never finished.
      // In order to differentiate between these two states, we look at the
      // state field of the update object.  If it's "pending", then we know
      // that this is the first time we're hitting this case, so we switch
      // that state to "applying" and we just wait and hope for the best.
      // If it's "applying", we know that we've already been here once, so
      // we really want to start from a clean state.
      if (update &&
          (update.state == STATE_PENDING ||
           update.state == STATE_PENDING_SERVICE)) {
        LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
            "state for the first time");
        update.state = STATE_APPLYING;
        um.saveUpdates();
      } else { // We get here even if we don't have an update object
        LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
            "state for the second time");
        cleanupActiveUpdate();
      }
      return;
    }

    if (!update) {
      if (status != STATE_SUCCEEDED) {
        LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
            "and no patch available");
        cleanupActiveUpdate();
        return;
      }
      update = new Update(null);
    }

    let parts = status.split(":");
    update.state = parts[0];
    if (update.state == STATE_FAILED && parts[1]) {
      update.errorCode = parseInt(parts[1]);
    }


    if (status != STATE_SUCCEEDED) {
      // Since the update didn't succeed save a copy of the active update's
      // current state to the updates.xml so it is possible to track failures.
      um.saveUpdates();
      // Rotate the update logs so the update log isn't removed. By passing
      // false the patch directory won't be removed.
      cleanUpUpdatesDir(false);
    }

    if (status == STATE_SUCCEEDED) {
      if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS)) {
        Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS);
      }
      update.statusText = gUpdateBundle.GetStringFromName("installSuccess");

      // Update the patch's metadata.
      um.activeUpdate = update;

      // Done with this update. Clean it up.
      cleanupActiveUpdate();

      Services.prefs.setIntPref(PREF_APP_UPDATE_ELEVATE_ATTEMPTS, 0);
    } else if (status == STATE_PENDING_ELEVATE) {
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateElevationRequired();
    } else {
      // If there was an I/O error it is assumed that the patch is not invalid
      // and it is set to pending so an attempt to apply it again will happen
      // when the application is restarted.
      if (update.state == STATE_FAILED && update.errorCode) {
        if (handleUpdateFailure(update, update.errorCode)) {
          return;
        }
      }

      // Something went wrong with the patch application process.
      handleFallbackToCompleteUpdate(update, false);
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateError(update);
    }
  },

  /**
   * Register an observer when the network comes online, so we can short-circuit
   * the app.update.interval when there isn't connectivity
   */
  _registerOnlineObserver: function AUS__registerOnlineObserver() {
    if (this._registeredOnlineObserver) {
      LOG("UpdateService:_registerOnlineObserver - observer already registered");
      return;
    }

    LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
        "be online, then forcing another check");

    Services.obs.addObserver(this, "network:offline-status-changed");
    this._registeredOnlineObserver = true;
  },

  /**
   * Called from the network:offline-status-changed observer.
   */
  _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
    if (status !== "online") {
      return;
    }

    Services.obs.removeObserver(this, "network:offline-status-changed");
    this._registeredOnlineObserver = false;

    LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
        "another background check");

    // the background checker is contained in notify
    this._attemptResume();
  },

  onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
    this._selectAndInstallUpdate(updates);
  },

  onError: function AUS_onError(request, update) {
    LOG("UpdateService:onError - error during background update. error code: " +
        update.errorCode + ", status text: " + update.statusText);

    if (update.errorCode == NETWORK_ERROR_OFFLINE) {
      // Register an online observer to try again
      this._registerOnlineObserver();
      if (this._pingSuffix) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
      }
      return;
    }

    // Send the error code to telemetry
    AUSTLMY.pingCheckExError(this._pingSuffix, update.errorCode);
    update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
    let errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
    errCount++;
    Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
    // Don't allow the preference to set a value greater than 20 for max errors.
    let maxErrors = Math.min(getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 10), 20);

    if (errCount >= maxErrors) {
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      LOG("UpdateService:onError - notifying observers of error. " +
          "topic: update-error, status: check-attempts-exceeded");
      Services.obs.notifyObservers(update, "update-error", "check-attempts-exceeded");
      prompter.showUpdateError(update);
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_PROMPT);
    } else {
      LOG("UpdateService:onError - notifying observers of error. " +
          "topic: update-error, status: check-attempt-failed");
      Services.obs.notifyObservers(update, "update-error", "check-attempt-failed");
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_SILENT);
    }
  },

  /**
   * Called when a connection should be resumed
   */
  _attemptResume: function AUS_attemptResume() {
    LOG("UpdateService:_attemptResume");
    // If a download is in progress, then resume it.
    if (this._downloader && this._downloader._patch &&
        this._downloader._patch.state == STATE_DOWNLOADING &&
        this._downloader._update) {
      LOG("UpdateService:_attemptResume - _patch.state: " +
          this._downloader._patch.state);
      // Make sure downloading is the state for selectPatch to work correctly
      writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
      var status = this.downloadUpdate(this._downloader._update,
                                       this._downloader.background);
      LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
      if (status == STATE_NONE) {
        cleanupActiveUpdate();
      }
      return;
    }

    this.backgroundChecker.checkForUpdates(this, false);
  },

  /**
   * Notified when a timer fires
   * @param   timer
   *          The timer that fired
   */
  notify: function AUS_notify(timer) {
    this._checkForBackgroundUpdates(true);
  },

  /**
   * See nsIUpdateService.idl
   */
  checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
    this._checkForBackgroundUpdates(false);
  },

  // The suffix used for background update check telemetry histogram ID's.
  get _pingSuffix() {
    return this._isNotify ? AUSTLMY.NOTIFY : AUSTLMY.EXTERNAL;
  },

  /**
   * Checks for updates in the background.
   * @param   isNotify
   *          Whether or not a background update check was initiated by the
   *          application update timer notification.
   */
  _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
    this._isNotify = isNotify;

    // Histogram IDs:
    // UPDATE_PING_COUNT_EXTERNAL
    // UPDATE_PING_COUNT_NOTIFY
    AUSTLMY.pingGeneric("UPDATE_PING_COUNT_" + this._pingSuffix,
                        true, false);

    // Histogram IDs:
    // UPDATE_UNABLE_TO_APPLY_EXTERNAL
    // UPDATE_UNABLE_TO_APPLY_NOTIFY
    AUSTLMY.pingGeneric("UPDATE_UNABLE_TO_APPLY_" + this._pingSuffix,
                        getCanApplyUpdates(), true);
    // Histogram IDs:
    // UPDATE_CANNOT_STAGE_EXTERNAL
    // UPDATE_CANNOT_STAGE_NOTIFY
    AUSTLMY.pingGeneric("UPDATE_CANNOT_STAGE_" + this._pingSuffix,
                        getCanStageUpdates(), true);
    // Histogram IDs:
    // UPDATE_INVALID_LASTUPDATETIME_EXTERNAL
    // UPDATE_INVALID_LASTUPDATETIME_NOTIFY
    // UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL
    // UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY
    AUSTLMY.pingLastUpdateTime(this._pingSuffix);
    // Histogram IDs:
    // UPDATE_NOT_PREF_UPDATE_ENABLED_EXTERNAL
    // UPDATE_NOT_PREF_UPDATE_ENABLED_NOTIFY
    AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_ENABLED_" + this._pingSuffix,
                         PREF_APP_UPDATE_ENABLED, true, true);
    // Histogram IDs:
    // UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL
    // UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY
    AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_AUTO_" + this._pingSuffix,
                         PREF_APP_UPDATE_AUTO, true, true);
    // Histogram IDs:
    // UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL
    // UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY
    AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_" +
                         this._pingSuffix,
                         PREF_APP_UPDATE_STAGING_ENABLED, true, true);
    if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
      // Histogram IDs:
      // UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL
      // UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY
      AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_" + this._pingSuffix,
                          PREF_APP_UPDATE_CANCELATIONS, 0, 0);
    }
    if (AppConstants.platform == "macosx") {
      // Histogram IDs:
      // UPDATE_PREF_UPDATE_CANCELATIONS_OSX_EXTERNAL
      // UPDATE_PREF_UPDATE_CANCELATIONS_OSX_NOTIFY
      AUSTLMY.pingIntPref("UPDATE_PREF_UPDATE_CANCELATIONS_OSX_" +
                          this._pingSuffix,
                          PREF_APP_UPDATE_CANCELATIONS_OSX, 0, 0);
    }
    if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
      // Histogram IDs:
      // UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL
      // UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY
      AUSTLMY.pingBoolPref("UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_" +
                           this._pingSuffix,
                           PREF_APP_UPDATE_SERVICE_ENABLED, true);
      // Histogram IDs:
      // UPDATE_PREF_SERVICE_ERRORS_EXTERNAL
      // UPDATE_PREF_SERVICE_ERRORS_NOTIFY
      AUSTLMY.pingIntPref("UPDATE_PREF_SERVICE_ERRORS_" + this._pingSuffix,
                          PREF_APP_UPDATE_SERVICE_ERRORS, 0, 0);
      if (AppConstants.platform == "win") {
        // Histogram IDs:
        // UPDATE_SERVICE_INSTALLED_EXTERNAL
        // UPDATE_SERVICE_INSTALLED_NOTIFY
        // UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL
        // UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY
        AUSTLMY.pingServiceInstallStatus(this._pingSuffix, isServiceInstalled());
      }
    }

    // If a download is in progress or the patch has been staged do nothing.
    if (this.isDownloading) {
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_DOWNLOADING);
      return;
    }

    if (this._downloader && this._downloader.patchIsStaged) {
      let readState = readStatusFile(getUpdatesDir());
      if (readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
          readState == STATE_PENDING_ELEVATE) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_DOWNLOADED);
      } else {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_IS_STAGED);
      }
      return;
    }

    let validUpdateURL = true;
    this.backgroundChecker.getUpdateURL(false).catch(e => {
      validUpdateURL = false;
    }).then(() => {
      // The following checks are done here so they can be differentiated from
      // foreground checks.
      if (!UpdateUtils.OSVersion) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_VERSION);
      } else if (!UpdateUtils.ABI) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_ABI);
      } else if (!validUpdateURL) {
        AUSTLMY.pingCheckCode(this._pingSuffix,
                              AUSTLMY.CHK_INVALID_DEFAULT_URL);
      } else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
      } else if (!hasUpdateMutex()) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_MUTEX);
      } else if (!gCanCheckForUpdates) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_CHECK);
      } else if (!this.backgroundChecker._enabled) {
        AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DISABLED_FOR_SESSION);
      }

      this.backgroundChecker.checkForUpdates(this, false);
    });
  },

  /**
   * Determine the update from the specified updates that should be offered.
   * If both valid major and minor updates are available the minor update will
   * be offered.
   * @param   updates
   *          An array of available nsIUpdate items
   * @return  The nsIUpdate to offer.
   */
  selectUpdate: function AUS_selectUpdate(updates) {
    if (updates.length == 0) {
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_UPDATE_FOUND);
      return null;
    }

    // The ping for unsupported is sent after the call to showPrompt.
    if (updates.length == 1 && updates[0].unsupported) {
      return updates[0];
    }

    // Choose the newest of the available minor and major updates.
    var majorUpdate = null;
    var minorUpdate = null;
    var vc = Services.vc;
    let lastCheckCode = AUSTLMY.CHK_NO_COMPAT_UPDATE_FOUND;

    updates.forEach(function(aUpdate) {
      // Ignore updates for older versions of the application and updates for
      // the same version of the application with the same build ID.
      if (vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 ||
          vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 &&
          aUpdate.buildID == Services.appinfo.appBuildID) {
        LOG("UpdateService:selectUpdate - skipping update because the " +
            "update's application version is less than the current " +
            "application version");
        lastCheckCode = AUSTLMY.CHK_UPDATE_PREVIOUS_VERSION;
        return;
      }

      switch (aUpdate.type) {
        case "major":
          if (!majorUpdate)
            majorUpdate = aUpdate;
          else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
            majorUpdate = aUpdate;
          break;
        case "minor":
          if (!minorUpdate)
            minorUpdate = aUpdate;
          else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
            minorUpdate = aUpdate;
          break;
        default:
          LOG("UpdateService:selectUpdate - skipping unknown update type: " +
              aUpdate.type);
          lastCheckCode = AUSTLMY.CHK_UPDATE_INVALID_TYPE;
          break;
      }
    });

    let update = minorUpdate || majorUpdate;
    if (AppConstants.platform == "macosx" && update) {
      if (getElevationRequired()) {
        let installAttemptVersion = getPref("getCharPref",
                                            PREF_APP_UPDATE_ELEVATE_VERSION,
                                            null);
        if (vc.compare(installAttemptVersion, update.appVersion) != 0) {
          Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_VERSION,
                                     update.appVersion);
          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);
          }
        } else {
          let numCancels = getPref("getIntPref",
                                   PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
          let rejectedVersion = getPref("getCharPref",
                                        PREF_APP_UPDATE_ELEVATE_NEVER, "");
          let maxCancels = getPref("getIntPref",
                                   PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
                                   DEFAULT_CANCELATIONS_OSX_MAX);
          if (numCancels >= maxCancels) {
            LOG("UpdateService:selectUpdate - the user requires elevation to " +
                "install this update, but the user has exceeded the max " +
                "number of elevation attempts.");
            update.elevationFailure = true;
            AUSTLMY.pingCheckCode(
              this._pingSuffix,
              AUSTLMY.CHK_ELEVATION_DISABLED_FOR_VERSION);
          } else if (vc.compare(rejectedVersion, update.appVersion) == 0) {
            LOG("UpdateService:selectUpdate - the user requires elevation to " +
                "install this update, but elevation is disabled for this " +
                "version.");
            update.elevationFailure = true;
            AUSTLMY.pingCheckCode(this._pingSuffix,
                                  AUSTLMY.CHK_ELEVATION_OPTOUT_FOR_VERSION);
          } else {
            LOG("UpdateService:selectUpdate - the user requires elevation to " +
                "install the update.");
          }
        }
      } else {
        // Clear elevation-related prefs since they no longer apply (the user
        // may have gained write access to the Firefox directory or an update
        // was executed with a different profile).
        if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_VERSION)) {
          Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_VERSION);
        }
        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);
        }
      }
    } else if (!update) {
      AUSTLMY.pingCheckCode(this._pingSuffix, lastCheckCode);
    }

    return update;
  },

  /**
   * Determine which of the specified updates should be installed and begin the
   * download/installation process or notify the user about the update.
   * @param   updates
   *          An array of available updates
   */
  _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
    // Return early if there's an active update. The user is already aware and
    // is downloading or performed some user action to prevent notification.
    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    if (um.activeUpdate) {
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_HAS_ACTIVEUPDATE);
      return;
    }

    var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
    if (!updateEnabled) {
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
      LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
          "update is disabled");
      return;
    }

    var update = this.selectUpdate(updates, updates.length);
    if (!update || update.elevationFailure) {
      return;
    }

    if (update.unsupported) {
      LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
          "this system. Notifying observers. topic: update-available, " +
          "status: unsupported");
      if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
        LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
            "update is not supported for this system");
        this._showPrompt(update);
      }

      Services.obs.notifyObservers(null, "update-available", "unsupported");
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNSUPPORTED);
      return;
    }

    if (!getCanApplyUpdates()) {
      LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
          "apply updates... prompting. Notifying observers. " +
          "topic: update-available, status: cant-apply");

      Services.obs.notifyObservers(null, "update-available", "cant-apply");
      this._showPrompt(update);
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_APPLY);
      return;
    }

    /**
     * From this point on there are two possible outcomes:
     * 1. download and install the update automatically
     * 2. notify the user about the availability of an update
     *
     * Notes:
     * a) if the app.update.auto preference is false then automatic download and
     *    install is disabled and the user will be notified.
     *
     * If the update when it is first read does not have an appVersion attribute
     * the following deprecated behavior will occur:
     * Update Type   Outcome
     * Major         Notify
     * Minor         Auto Install
     */
    if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
      LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
          "install is disabled. Notifying observers. topic: update-available, " +
          "status: show-prompt");
      AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_PREF);

      Services.obs.notifyObservers(update, "update-available", "show-prompt");
      this._showPrompt(update);
      return;
    }

    LOG("UpdateService:_selectAndInstallUpdate - download the update");
    let status = this.downloadUpdate(update, true);
    if (status == STATE_NONE) {
      cleanupActiveUpdate();
    }
    AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DOWNLOAD_UPDATE);
  },

  _showPrompt: function AUS__showPrompt(update) {
    let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                   createInstance(Ci.nsIUpdatePrompt);
    prompter.showUpdateAvailable(update);
  },

  /**
   * The Checker used for background update checks.
   */
  _backgroundChecker: null,

  /**
   * See nsIUpdateService.idl
   */
  get backgroundChecker() {
    if (!this._backgroundChecker)
      this._backgroundChecker = new Checker();
    return this._backgroundChecker;
  },

  /**
   * See nsIUpdateService.idl
   */
  get canCheckForUpdates() {
    return gCanCheckForUpdates && hasUpdateMutex();
  },

  /**
   * See nsIUpdateService.idl
   */
  get elevationRequired() {
    return getElevationRequired();
  },

  /**
   * See nsIUpdateService.idl
   */
  get canApplyUpdates() {
    return getCanApplyUpdates() && hasUpdateMutex();
  },

  /**
   * See nsIUpdateService.idl
   */
  get canStageUpdates() {
    return getCanStageUpdates();
  },

  /**
   * See nsIUpdateService.idl
   */
  get isOtherInstanceHandlingUpdates() {
    return !hasUpdateMutex();
  },


  /**
   * See nsIUpdateService.idl
   */
  addDownloadListener: function AUS_addDownloadListener(listener) {
    if (!this._downloader) {
      LOG("UpdateService:addDownloadListener - no downloader!");
      return;
    }
    this._downloader.addDownloadListener(listener);
  },

  /**
   * See nsIUpdateService.idl
   */
  removeDownloadListener: function AUS_removeDownloadListener(listener) {
    if (!this._downloader) {
      LOG("UpdateService:removeDownloadListener - no downloader!");
      return;
    }
    this._downloader.removeDownloadListener(listener);
  },

  /**
   * See nsIUpdateService.idl
   */
  downloadUpdate: function AUS_downloadUpdate(update, background) {
    if (!update)
      throw Cr.NS_ERROR_NULL_POINTER;

    // Don't download the update if the update's version is less than the
    // current application's version or the update's version is the same as the
    // application's version and the build ID is the same as the application's
    // build ID.
    if (update.appVersion &&
        (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
         update.buildID && update.buildID == Services.appinfo.appBuildID &&
         update.appVersion == Services.appinfo.version)) {
      LOG("UpdateService:downloadUpdate - canceling download of update since " +
          "it is for an earlier or same application version and build ID.\n" +
          "current application version: " + Services.appinfo.version + "\n" +
          "update application version : " + update.appVersion + "\n" +
          "current build ID: " + Services.appinfo.appBuildID + "\n" +
          "update build ID : " + update.buildID);
      cleanupActiveUpdate();
      return STATE_NONE;
    }

    // If a download request is in progress vs. a download ready to resume
    if (this.isDownloading) {
      if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
          background == this._downloader.background) {
        LOG("UpdateService:downloadUpdate - no support for downloading more " +
            "than one update at a time");
        return readStatusFile(getUpdatesDir());
      }
      this._downloader.cancel();
    }
    // Set the previous application version prior to downloading the update.
    update.previousAppVersion = Services.appinfo.version;
    this._downloader = new Downloader(background, this);
    return this._downloader.downloadUpdate(update);
  },

  /**
   * See nsIUpdateService.idl
   */
  pauseDownload: function AUS_pauseDownload() {
    if (this.isDownloading) {
      this._downloader.cancel();
    } else if (this._retryTimer) {
      // Download status is still consider as 'downloading' during retry.
      // We need to cancel both retry and download at this stage.
      this._retryTimer.cancel();
      this._retryTimer = null;
      this._downloader.cancel();
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  getUpdatesDirectory: getUpdatesDir,

  /**
   * See nsIUpdateService.idl
   */
  get isDownloading() {
    return this._downloader && this._downloader.isBusy;
  },

  classID: UPDATESERVICE_CID,
  classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
                                    contractID: UPDATESERVICE_CONTRACTID,
                                    interfaces: [Ci.nsIApplicationUpdateService,
                                                 Ci.nsITimerCallback,
                                                 Ci.nsIObserver],
                                    flags: Ci.nsIClassInfo.SINGLETON}),

  _xpcom_factory: UpdateServiceFactory,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
                                         Ci.nsIUpdateCheckListener,
                                         Ci.nsITimerCallback,
                                         Ci.nsIObserver])
};

/**
 * A service to manage active and past updates.
 * @constructor
 */
function UpdateManager() {
  // Ensure the Active Update file is loaded
  var updates = this._loadXMLFileIntoArray(getUpdateFile(
                  [FILE_ACTIVE_UPDATE_XML]));
  if (updates.length > 0) {
    // Under some edgecases such as Windows system restore the active-update.xml
    // will contain a pending update without the status file which will return
    // STATE_NONE. To recover from this situation clean the updates dir and
    // rewrite the active-update.xml file without the broken update.
    if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
      cleanUpUpdatesDir();
      this._writeUpdatesToXMLFile([], getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
    } else
      this._activeUpdate = updates[0];
  }
}
UpdateManager.prototype = {
  /**
   * All previously downloaded and installed updates, as an array of nsIUpdate
   * objects.
   */
  _updates: null,

  /**
   * The current actively downloading/installing update, as a nsIUpdate object.
   */
  _activeUpdate: null,

  /**
   * Handle Observer Service notifications
   * @param   subject
   *          The subject of the notification
   * @param   topic
   *          The notification name
   * @param   data
   *          Additional data
   */
  observe: function UM_observe(subject, topic, data) {
    // Hack to be able to run and cleanup tests by reloading the update data.
    if (topic == "um-reload-update-data") {
      this._updates = this._loadXMLFileIntoArray(getUpdateFile(
                        [FILE_UPDATES_XML]));
      this._activeUpdate = null;
      var updates = this._loadXMLFileIntoArray(getUpdateFile(
                      [FILE_ACTIVE_UPDATE_XML]));
      if (updates.length > 0)
        this._activeUpdate = updates[0];
    }
  },

  /**
   * Loads an updates.xml formatted file into an array of nsIUpdate items.
   * @param   file
   *          A nsIFile for the updates.xml file
   * @return  The array of nsIUpdate items held in the file.
   */
  _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
    if (!file.exists()) {
      LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
      return [];
    }

    var result = [];
    var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
                     createInstance(Ci.nsIFileInputStream);
    fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
    try {
      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                   createInstance(Ci.nsIDOMParser);
      var doc = parser.parseFromStream(fileStream, "UTF-8",
                                       fileStream.available(), "text/xml");

      const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
      var updateCount = doc.documentElement.childNodes.length;
      for (var i = 0; i < updateCount; ++i) {
        var updateElement = doc.documentElement.childNodes.item(i);
        if (updateElement.nodeType != ELEMENT_NODE ||
            updateElement.localName != "update")
          continue;

        updateElement.QueryInterface(Ci.nsIDOMElement);
        let update;
        try {
          update = new Update(updateElement);
        } catch (e) {
          LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
          continue;
        }
        result.push(update);
      }
    } catch (e) {
      LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
          "list. Exception: " + e);
    }
    fileStream.close();
    return result;
  },

  /**
   * Load the update manager, initializing state from state files.
   */
  _ensureUpdates: function UM__ensureUpdates() {
    if (!this._updates) {
      this._updates = this._loadXMLFileIntoArray(getUpdateFile(
                        [FILE_UPDATES_XML]));
      var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
                            [FILE_ACTIVE_UPDATE_XML]));
      if (activeUpdates.length > 0)
        this._activeUpdate = activeUpdates[0];
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  getUpdateAt: function UM_getUpdateAt(index) {
    this._ensureUpdates();
    return this._updates[index];
  },

  /**
   * See nsIUpdateService.idl
   */
  get updateCount() {
    this._ensureUpdates();
    return this._updates.length;
  },

  /**
   * See nsIUpdateService.idl
   */
  get activeUpdate() {
    if (this._activeUpdate &&
        this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
      LOG("UpdateManager:get activeUpdate - channel has changed, " +
          "reloading default preferences to workaround bug 802022");
      // Workaround to get distribution preferences loaded (Bug 774618). This
      // can be removed after bug 802022 is fixed.
      let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
      prefSvc.observe(null, "reload-default-prefs", null);
      if (this._activeUpdate.channel != UpdateUtils.UpdateChannel) {
        // User switched channels, clear out any old active updates and remove
        // partial downloads
        this._activeUpdate = null;
        this.saveUpdates();

        // Destroy the updates directory, since we're done with it.
        cleanUpUpdatesDir();
      }
    }
    return this._activeUpdate;
  },
  set activeUpdate(activeUpdate) {
    this._addUpdate(activeUpdate);
    this._activeUpdate = activeUpdate;
    if (!activeUpdate) {
      // If |activeUpdate| is null, we have updated both lists - the active list
      // and the history list, so we want to write both files.
      this.saveUpdates();
    } else
      this._writeUpdatesToXMLFile([this._activeUpdate],
                                  getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
    return activeUpdate;
  },

  /**
   * Add an update to the Updates list. If the item already exists in the list,
   * replace the existing value with the new value.
   * @param   update
   *          The nsIUpdate object to add.
   */
  _addUpdate: function UM__addUpdate(update) {
    if (!update)
      return;
    this._ensureUpdates();
    // Only the latest update entry is checked so the the latest successful
    // step for an update is recorded and all failures are kept. This way
    // mutliple attempts to update for the same update are kept in the update
    // history.
    if (this._updates &&
        update.state != STATE_FAILED &&
        this._updates[0] &&
        this._updates[0].state != STATE_FAILED &&
        this._updates[0].appVersion == update.appVersion &&
        this._updates[0].buildID == update.buildID) {
      // Replace the existing entry with the new value, updating
      // all metadata.
      this._updates[0] = update;
      return;
    }
    // Otherwise add it to the front of the list.
    this._updates.unshift(update);
  },

  /**
   * Serializes an array of updates to an XML file
   * @param   updates
   *          An array of nsIUpdate objects
   * @param   file
   *          The nsIFile object to serialize to
   */
  _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
    var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
              createInstance(Ci.nsIFileOutputStream);
    var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                    FileUtils.MODE_TRUNCATE;
    if (!file.exists()) {
      file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
    }
    fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);

    try {
      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                   createInstance(Ci.nsIDOMParser);
      const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
      var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");

      for (var i = 0; i < updates.length; ++i) {
        // If appVersion isn't defined don't add the update. This happens when
        // cleaning up invalid updates (e.g. incorrect channel).
        if (updates[i] && updates[i].appVersion) {
          doc.documentElement.appendChild(updates[i].serialize(doc));
        }
      }

      var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
                       createInstance(Ci.nsIDOMSerializer);
      serializer.serializeToStream(doc.documentElement, fos, null);
    } catch (e) {
    }

    FileUtils.closeSafeFileOutputStream(fos);
  },

  /**
   * See nsIUpdateService.idl
   */
  saveUpdates: function UM_saveUpdates() {
    this._writeUpdatesToXMLFile([this._activeUpdate],
                                getUpdateFile([FILE_ACTIVE_UPDATE_XML]));
    if (this._activeUpdate)
      this._addUpdate(this._activeUpdate);

    this._ensureUpdates();
    // Don't write updates that don't have a state to the updates.xml file.
    if (this._updates) {
      let updates = this._updates.slice();
      for (let i = updates.length - 1; i >= 0; --i) {
        let state = updates[i].state;
        if (state == STATE_NONE) {
          updates.splice(i, 1);
        }
      }

      this._writeUpdatesToXMLFile(updates.slice(0, 20),
                                  getUpdateFile([FILE_UPDATES_XML]));
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  refreshUpdateStatus: function UM_refreshUpdateStatus() {
    var update = this._activeUpdate;
    if (!update) {
      return;
    }
    var status = readStatusFile(getUpdatesDir());
    pingStateAndStatusCodes(update, false, status);
    var parts = status.split(":");
    update.state = parts[0];
    if (update.state == STATE_FAILED && parts[1]) {
      update.errorCode = parseInt(parts[1]);
    }
    let um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    // Save a copy of the active update's current state to the updates.xml so
    // it is possible to track failures.
    um.saveUpdates();

    // Rotate the update logs so the update log isn't removed if a complete
    // update is downloaded. By passing false the patch directory won't be
    // removed.
    cleanUpUpdatesDir(false);

    if (update.state == STATE_FAILED && parts[1]) {
      if (!handleUpdateFailure(update, parts[1])) {
        handleFallbackToCompleteUpdate(update, true);
      }

      update.QueryInterface(Ci.nsIWritablePropertyBag);
      update.setProperty("stagingFailed", "true");
    }
    if (update.state == STATE_APPLIED && shouldUseService()) {
      writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
    }

    // Send an observer notification which the app update doorhanger uses to
    // display a restart notification
    LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
        "the update was staged. topic: update-staged, status: " + update.state);
    Services.obs.notifyObservers(update, "update-staged", update.state);

    // Only prompt when the UI isn't already open.
    let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
    if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
        windowType && Services.wm.getMostRecentWindow(windowType)) {
      return;
    }

    if (update.state == STATE_APPLIED ||
        update.state == STATE_APPLIED_SERVICE ||
        update.state == STATE_PENDING ||
        update.state == STATE_PENDING_SERVICE ||
        update.state == STATE_PENDING_ELEVATE) {
      // Notify the user that an update has been staged and is ready for
      // installation (i.e. that they should restart the application).
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateDownloaded(update, true);
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  elevationOptedIn: function UM_elevationOptedIn() {
    // The user has been been made aware that the update requires elevation.
    let update = this._activeUpdate;
    if (!update) {
      return;
    }
    let status = readStatusFile(getUpdatesDir());
    let parts = status.split(":");
    update.state = parts[0];
    if (update.state == STATE_PENDING_ELEVATE) {
      // Proceed with the pending update.
      // Note: STATE_PENDING_ELEVATE stands for "pending user's approval to
      // proceed with an elevated update". As long as we see this state, we will
      // notify the user of the availability of an update that requires
      // elevation. |elevationOptedIn| (this function) is called when the user
      // gives us approval to proceed, so we want to switch to STATE_PENDING.
      // The updater then detects whether or not elevation is required and
      // displays the elevation prompt if necessary. This last step does not
      // depend on the state in the status file.
      writeStatusFile(getUpdatesDir(), STATE_PENDING);
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  cleanupActiveUpdate: function UM_cleanupActiveUpdate() {
    cleanupActiveUpdate();
  },

  classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
};

/**
 * Checker
 * Checks for new Updates
 * @constructor
 */
function Checker() {
}
Checker.prototype = {
  /**
   * The XMLHttpRequest object that performs the connection.
   */
  _request: null,

  /**
   * The nsIUpdateCheckListener callback
   */
  _callback: null,

  _getCanMigrate: function UC__getCanMigrate() {
    if (AppConstants.platform != "win") {
      return false;
    }

    // The first element of the array is whether the build target is 32 or 64
    // bit and the third element of the array is whether the client's Windows OS
    // system processor is 32 or 64 bit.
    let aryABI = UpdateUtils.ABI.split("-");
    if (aryABI[0] != "x86" || aryABI[2] != "x64") {
      return false;
    }

    let wrk = Cc["@mozilla.org/windows-registry-key;1"].
              createInstance(Ci.nsIWindowsRegKey);

    let regPath = "SOFTWARE\\Mozilla\\" + Services.appinfo.name +
                  "\\32to64DidMigrate";
    let regValHKCU = WindowsRegistry.readRegKey(wrk.ROOT_KEY_CURRENT_USER,
                                                regPath, "Never", wrk.WOW64_32);
    let regValHKLM = WindowsRegistry.readRegKey(wrk.ROOT_KEY_LOCAL_MACHINE,
                                                regPath, "Never", wrk.WOW64_32);
    // The Never registry key value allows configuring a system to never migrate
    // any of the installations.
    if (regValHKCU === 1 || regValHKLM === 1) {
      LOG("Checker:_getCanMigrate - all installations should not be migrated");
      return false;
    }

    let appBaseDirPath = getAppBaseDir().path;
    regValHKCU = WindowsRegistry.readRegKey(wrk.ROOT_KEY_CURRENT_USER, regPath,
                                            appBaseDirPath, wrk.WOW64_32);
    regValHKLM = WindowsRegistry.readRegKey(wrk.ROOT_KEY_LOCAL_MACHINE, regPath,
                                            appBaseDirPath, wrk.WOW64_32);
    // When the registry value is 1 for the installation directory path value
    // name then the installation has already been migrated once or the system
    // was configured to not migrate that installation.
    if (regValHKCU === 1 || regValHKLM === 1) {
      LOG("Checker:_getCanMigrate - this installation should not be migrated");
      return false;
    }

    // When the registry value is 0 for the installation directory path value
    // name then the installation has updated to Firefox 56 and can be migrated.
    if (regValHKCU === 0 || regValHKLM === 0) {
      LOG("Checker:_getCanMigrate - this installation can be migrated");
      return true;
    }

    LOG("Checker:_getCanMigrate - no registry entries for this installation");
    return false;
  },

  /**
   * The URL of the update service XML file to connect to that contains details
   * about available updates.
   */
  getUpdateURL: async function UC_getUpdateURL(force) {
    this._forced = force;

    let url = Services.prefs.getDefaultBranch(null).
              getCharPref(PREF_APP_UPDATE_URL, "");

    if (!url) {
      LOG("Checker:getUpdateURL - update URL not defined");
      return null;
    }

    url = await UpdateUtils.formatUpdateURL(url);

    if (force) {
      url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
    }

    if (this._getCanMigrate()) {
      url += (url.indexOf("?") != -1 ? "&" : "?") + "mig64=1";
    }

    LOG("Checker:getUpdateURL - update URL: " + url);
    return url;
  },

  /**
   * See nsIUpdateService.idl
   */
  checkForUpdates: function UC_checkForUpdates(listener, force) {
    LOG("Checker: checkForUpdates, force: " + force);
    if (!listener) {
      throw Cr.NS_ERROR_NULL_POINTER;
    }

    if (!this.enabled && !force) {
      return;
    }

    this.getUpdateURL(force).then(url => {
      if (!url) {
        return;
      }

      this._request = new XMLHttpRequest();
      this._request.open("GET", url, true);
      this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(false);
      // Prevent the request from reading from the cache.
      this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
      // Prevent the request from writing to the cache.
      this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
      // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
      this._request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;

      this._request.overrideMimeType("text/xml");
      // The Cache-Control header is only interpreted by proxies and the
      // final destination. It does not help if a resource is already
      // cached locally.
      this._request.setRequestHeader("Cache-Control", "no-cache");
      // HTTP/1.0 servers might not implement Cache-Control and
      // might only implement Pragma: no-cache
      this._request.setRequestHeader("Pragma", "no-cache");

      var self = this;
      this._request.addEventListener("error", function(event) { self.onError(event); });
      this._request.addEventListener("load", function(event) { self.onLoad(event); });

      LOG("Checker:checkForUpdates - sending request to: " + url);
      this._request.send(null);

      this._callback = listener;
    });
  },

  /**
   * Returns an array of nsIUpdate objects discovered by the update check.
   * @throws if the XML document element node name is not updates.
   */
  get _updates() {
    var updatesElement = this._request.responseXML.documentElement;
    if (!updatesElement) {
      LOG("Checker:_updates get - empty updates document?!");
      return [];
    }

    if (updatesElement.nodeName != "updates") {
      LOG("Checker:_updates get - unexpected node name!");
      throw new Error("Unexpected node name, expected: updates, got: " +
                      updatesElement.nodeName);
    }

    const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
    var updates = [];
    for (var i = 0; i < updatesElement.childNodes.length; ++i) {
      var updateElement = updatesElement.childNodes.item(i);
      if (updateElement.nodeType != ELEMENT_NODE ||
          updateElement.localName != "update")
        continue;

      updateElement.QueryInterface(Ci.nsIDOMElement);
      let update;
      try {
        update = new Update(updateElement);
      } catch (e) {
        LOG("Checker:_updates get - invalid <update/>, ignoring...");
        continue;
      }
      update.serviceURL = this._request.responseURL;
      update.channel = UpdateUtils.UpdateChannel;
      updates.push(update);
    }

    return updates;
  },

  /**
   * Returns the status code for the XMLHttpRequest
   */
  _getChannelStatus: function UC__getChannelStatus(request) {
    var status = 0;
    try {
      status = request.status;
    } catch (e) {
    }

    if (status == 0)
      status = request.channel.QueryInterface(Ci.nsIRequest).status;
    return status;
  },

  _isHttpStatusCode: function UC__isHttpStatusCode(status) {
    return status >= 100 && status <= 599;
  },

  /**
   * The XMLHttpRequest succeeded and the document was loaded.
   * @param   event
   *          The nsIDOMEvent for the load
   */
  onLoad: function UC_onLoad(event) {
    LOG("Checker:onLoad - request completed downloading document");

    try {
      // Analyze the resulting DOM and determine the set of updates.
      var updates = this._updates;
      LOG("Checker:onLoad - number of updates available: " + updates.length);

      if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
        Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
      }

      // Tell the callback about the updates
      this._callback.onCheckComplete(event.target, updates, updates.length);
    } catch (e) {
      LOG("Checker:onLoad - there was a problem checking for updates. " +
          "Exception: " + e);
      var request = event.target;
      var status = this._getChannelStatus(request);
      LOG("Checker:onLoad - request.status: " + status);
      var update = new Update(null);
      update.errorCode = status;
      update.statusText = getStatusTextFromCode(status, 404);

      if (this._isHttpStatusCode(status)) {
        update.errorCode = HTTP_ERROR_OFFSET + status;
      }

      this._callback.onError(request, update);
    }

    this._callback = null;
    this._request = null;
  },

  /**
   * There was an error of some kind during the XMLHttpRequest
   * @param   event
   *          The nsIDOMEvent for the error
   */
  onError: function UC_onError(event) {
    var request = event.target;
    var status = this._getChannelStatus(request);
    LOG("Checker:onError - request.status: " + status);

    // If we can't find an error string specific to this status code,
    // just use the 200 message from above, which means everything
    // "looks" fine but there was probably an XML error or a bogus file.
    var update = new Update(null);
    update.errorCode = status;
    update.statusText = getStatusTextFromCode(status, 200);

    if (status == Cr.NS_ERROR_OFFLINE) {
      // We use a separate constant here because nsIUpdate.errorCode is signed
      update.errorCode = NETWORK_ERROR_OFFLINE;
    } else if (this._isHttpStatusCode(status)) {
      update.errorCode = HTTP_ERROR_OFFSET + status;
    }

    this._callback.onError(request, update);
    this._request = null;
  },

  /**
   * Whether or not we are allowed to do update checking.
   */
  _enabled: true,
  get enabled() {
    return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
           gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
  },

  /**
   * See nsIUpdateService.idl
   */
  stopChecking: function UC_stopChecking(duration) {
    // Always stop the current check
    if (this._request)
      this._request.abort();

    switch (duration) {
      case Ci.nsIUpdateChecker.CURRENT_SESSION:
        this._enabled = false;
        break;
      case Ci.nsIUpdateChecker.ANY_CHECKS:
        this._enabled = false;
        Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
        break;
    }

    this._callback = null;
  },

  classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
};

/**
 * Manages the download of updates
 * @param   background
 *          Whether or not this downloader is operating in background
 *          update mode.
 * @param   updateService
 *          The update service that created this downloader.
 * @constructor
 */
function Downloader(background, updateService) {
  LOG("Creating Downloader");
  this.background = background;
  this.updateService = updateService;
}
Downloader.prototype = {
  /**
   * The nsIUpdatePatch that we are downloading
   */
  _patch: null,

  /**
   * The nsIUpdate that we are downloading
   */
  _update: null,

  /**
   * The nsIIncrementalDownload object handling the download
   */
  _request: null,

  /**
   * Whether or not the update being downloaded is a complete replacement of
   * the user's existing installation or a patch representing the difference
   * between the new version and the previous version.
   */
  isCompleteUpdate: null,

  /**
   * Cancels the active download.
   */
  cancel: function Downloader_cancel(cancelError) {
    LOG("Downloader: cancel");
    if (cancelError === undefined) {
      cancelError = Cr.NS_BINDING_ABORTED;
    }
    if (this._request && this._request instanceof Ci.nsIRequest) {
      this._request.cancel(cancelError);
    }
  },

  /**
   * Whether or not a patch has been downloaded and staged for installation.
   */
  get patchIsStaged() {
    var readState = readStatusFile(getUpdatesDir());
    // Note that if we decide to download and apply new updates after another
    // update has been successfully applied in the background, we need to stop
    // checking for the APPLIED state here.
    return readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
           readState == STATE_PENDING_ELEVATE ||
           readState == STATE_APPLIED || readState == STATE_APPLIED_SERVICE;
  },

  /**
   * Verify the downloaded file.  We assume that the download is complete at
   * this point.
   */
  _verifyDownload: function Downloader__verifyDownload() {
    LOG("Downloader:_verifyDownload called");
    if (!this._request) {
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                               AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
      return false;
    }

    let destination = this._request.destination;

    // Ensure that the file size matches the expected file size.
    if (destination.fileSize != this._patch.size) {
      LOG("Downloader:_verifyDownload downloaded size != expected size.");
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                               AUSTLMY.DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL);
      return false;
    }

    LOG("Downloader:_verifyDownload downloaded size == expected size.");
    return true;
  },

  /**
   * Select the patch to use given the current state of updateDir and the given
   * set of update patches.
   * @param   update
   *          A nsIUpdate object to select a patch from
   * @param   updateDir
   *          A nsIFile representing the update directory
   * @return  A nsIUpdatePatch object to download
   */
  _selectPatch: function Downloader__selectPatch(update, updateDir) {
    // Given an update to download, we will always try to download the patch
    // for a partial update over the patch for a full update.

    /**
     * Return the first UpdatePatch with the given type.
     * @param   type
     *          The type of the patch ("complete" or "partial")
     * @return  A nsIUpdatePatch object matching the type specified
     */
    function getPatchOfType(type) {
      for (var i = 0; i < update.patchCount; ++i) {
        var patch = update.getPatchAt(i);
        if (patch && patch.type == type)
          return patch;
      }
      return null;
    }

    // Look to see if any of the patches in the Update object has been
    // pre-selected for download, otherwise we must figure out which one
    // to select ourselves.
    var selectedPatch = update.selectedPatch;

    var state = readStatusFile(updateDir);

    // If this is a patch that we know about, then select it.  If it is a patch
    // that we do not know about, then remove it and use our default logic.
    var useComplete = false;
    if (selectedPatch) {
      LOG("Downloader:_selectPatch - found existing patch with state: " +
          state);
      if (state == STATE_DOWNLOADING) {
        LOG("Downloader:_selectPatch - resuming download");
        return selectedPatch;
      }
      if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
          state == STATE_PENDING_ELEVATE || state == STATE_APPLIED ||
          state == STATE_APPLIED_SERVICE) {
        LOG("Downloader:_selectPatch - already downloaded");
        return null;
      }

      if (update && selectedPatch.type == "complete") {
        // This is a pretty fatal error.  Just bail.
        LOG("Downloader:_selectPatch - failed to apply complete patch!");
        writeStatusFile(updateDir, STATE_NONE);
        writeVersionFile(getUpdatesDir(), null);
        return null;
      }

      // Something went wrong when we tried to apply the previous patch.
      // Try the complete patch next time.
      useComplete = true;
      selectedPatch = null;
    }

    // If we were not able to discover an update from a previous download, we
    // select the best patch from the given set.
    var partialPatch = getPatchOfType("partial");
    if (!useComplete)
      selectedPatch = partialPatch;
    if (!selectedPatch) {
      if (partialPatch)
        partialPatch.selected = false;
      selectedPatch = getPatchOfType("complete");
    }

    // Restore the updateDir since we may have deleted it.
    updateDir = getUpdatesDir();

    // if update only contains a partial patch, selectedPatch == null here if
    // the partial patch has been attempted and fails and we're trying to get a
    // complete patch
    if (selectedPatch)
      selectedPatch.selected = true;

    update.isCompleteUpdate = useComplete;

    // Reset the Active Update object on the Update Manager and flush the
    // Active Update DB.
    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    um.activeUpdate = update;

    return selectedPatch;
  },

  /**
   * Whether or not we are currently downloading something.
   */
  get isBusy() {
    return this._request != null;
  },

  /**
   * Download and stage the given update.
   * @param   update
   *          A nsIUpdate object to download a patch for. Cannot be null.
   */
  downloadUpdate: function Downloader_downloadUpdate(update) {
    LOG("UpdateService:_downloadUpdate");
    if (!update) {
      AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
      throw Cr.NS_ERROR_NULL_POINTER;
    }

    var updateDir = getUpdatesDir();

    this._update = update;

    // This function may return null, which indicates that there are no patches
    // to download.
    this._patch = this._selectPatch(update, updateDir);
    if (!this._patch) {
      LOG("Downloader:downloadUpdate - no patch to download");
      AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
      return readStatusFile(updateDir);
    }
    this.isCompleteUpdate = this._patch.type == "complete";

    let patchFile = getUpdatesDir().clone();
    patchFile.append(FILE_UPDATE_MAR);
    update.QueryInterface(Ci.nsIPropertyBag);
    let interval = this.background ? update.getProperty("backgroundInterval")
                                   : DOWNLOAD_FOREGROUND_INTERVAL;

    LOG("Downloader:downloadUpdate - url: " + this._patch.URL + ", path: " +
        patchFile.path + ", interval: " + interval);
    var uri = Services.io.newURI(this._patch.URL);

    this._request = Cc["@mozilla.org/network/incremental-download;1"].
                    createInstance(Ci.nsIIncrementalDownload);
    this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
    this._request.start(this, null);

    writeStatusFile(updateDir, STATE_DOWNLOADING);
    this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
    this._patch.state = STATE_DOWNLOADING;
    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    um.saveUpdates();
    return STATE_DOWNLOADING;
  },

  /**
   * An array of download listeners to notify when we receive
   * nsIRequestObserver or nsIProgressEventSink method calls.
   */
  _listeners: [],

  /**
   * Adds a listener to the download process
   * @param   listener
   *          A download listener, implementing nsIRequestObserver and
   *          nsIProgressEventSink
   */
  addDownloadListener: function Downloader_addDownloadListener(listener) {
    for (var i = 0; i < this._listeners.length; ++i) {
      if (this._listeners[i] == listener)
        return;
    }
    this._listeners.push(listener);
  },

  /**
   * Removes a download listener
   * @param   listener
   *          The listener to remove.
   */
  removeDownloadListener: function Downloader_removeDownloadListener(listener) {
    for (var i = 0; i < this._listeners.length; ++i) {
      if (this._listeners[i] == listener) {
        this._listeners.splice(i, 1);
        return;
      }
    }
  },

  /**
   * When the async request begins
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   */
  onStartRequest: function Downloader_onStartRequest(request, context) {
    if (request instanceof Ci.nsIIncrementalDownload)
      LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
          ", final URI spec: " + request.finalURI.spec);
    // Always set finalURL in onStartRequest since it can change.
    this._patch.finalURL = request.finalURI.spec;
    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    um.saveUpdates();

    var listeners = this._listeners.concat();
    var listenerCount = listeners.length;
    for (var i = 0; i < listenerCount; ++i)
      listeners[i].onStartRequest(request, context);
  },

  /**
   * When new data has been downloaded
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   progress
   *          The current number of bytes transferred
   * @param   maxProgress
   *          The total number of bytes that must be transferred
   */
  onProgress: function Downloader_onProgress(request, context, progress,
                                             maxProgress) {
    LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);

    if (progress > this._patch.size) {
      LOG("Downloader:onProgress - progress: " + progress +
          " is higher than patch size: " + this._patch.size);
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                               AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
      this.cancel(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    if (maxProgress != this._patch.size) {
      LOG("Downloader:onProgress - maxProgress: " + maxProgress +
          " is not equal to expected patch size: " + this._patch.size);
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                               AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL);
      this.cancel(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    var listeners = this._listeners.concat();
    var listenerCount = listeners.length;
    for (var i = 0; i < listenerCount; ++i) {
      var listener = listeners[i];
      if (listener instanceof Ci.nsIProgressEventSink)
        listener.onProgress(request, context, progress, maxProgress);
    }
    this.updateService._consecutiveSocketErrors = 0;
  },

  /**
   * When we have new status text
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   status
   *          A status code
   * @param   statusText
   *          Human readable version of |status|
   */
  onStatus: function Downloader_onStatus(request, context, status, statusText) {
    LOG("Downloader:onStatus - status: " + status + ", statusText: " +
        statusText);

    var listeners = this._listeners.concat();
    var listenerCount = listeners.length;
    for (var i = 0; i < listenerCount; ++i) {
      var listener = listeners[i];
      if (listener instanceof Ci.nsIProgressEventSink)
        listener.onStatus(request, context, status, statusText);
    }
  },

  /**
   * When data transfer ceases
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   status
   *          Status code containing the reason for the cessation.
   */
  onStopRequest: function Downloader_onStopRequest(request, context, status) {
    if (request instanceof Ci.nsIIncrementalDownload)
      LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
          ", final URI spec: " + request.finalURI.spec + ", status: " + status);

    // XXX ehsan shouldShowPrompt should always be false here.
    // But what happens when there is already a UI showing?
    var state = this._patch.state;
    var shouldShowPrompt = false;
    var shouldRegisterOnlineObserver = false;
    var shouldRetrySoon = false;
    var deleteActiveUpdate = false;
    var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
                               DEFAULT_SOCKET_RETRYTIMEOUT);
    // Prevent the preference from setting a value greater than 10000.
    retryTimeout = Math.min(retryTimeout, 10000);
    var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_MAXERRORS,
                          DEFAULT_SOCKET_MAX_ERRORS);
    // Prevent the preference from setting a value greater than 20.
    maxFail = Math.min(maxFail, 20);
    LOG("Downloader:onStopRequest - status: " + status + ", " +
        "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
        "max fail: " + maxFail + ", " +
        "retryTimeout: " + retryTimeout);
    if (Components.isSuccessCode(status)) {
      if (this._verifyDownload()) {
        if (shouldUseService()) {
          state = STATE_PENDING_SERVICE;
        } else if (getElevationRequired()) {
          state = STATE_PENDING_ELEVATE;
        } else {
          state = STATE_PENDING;
        }
        if (this.background) {
          shouldShowPrompt = !getCanStageUpdates();
        }
        AUSTLMY.pingDownloadCode(this.isCompleteUpdate, AUSTLMY.DWNLD_SUCCESS);

        // Tell the updater.exe we're ready to apply.
        writeStatusFile(getUpdatesDir(), state);
        writeVersionFile(getUpdatesDir(), this._update.appVersion);
        this._update.installDate = (new Date()).getTime();
        this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
        Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
      } else {
        LOG("Downloader:onStopRequest - download verification failed");
        state = STATE_DOWNLOAD_FAILED;
        status = Cr.NS_ERROR_CORRUPTED_CONTENT;

        // Yes, this code is a string.
        const vfCode = "verification_failed";
        var message = getStatusTextFromCode(vfCode, vfCode);
        this._update.statusText = message;

        if (this._update.isCompleteUpdate || this._update.patchCount != 2)
          deleteActiveUpdate = true;

        // Destroy the updates directory, since we're done with it.
        cleanUpUpdatesDir();
      }
    } else if (status == Cr.NS_ERROR_OFFLINE) {
      // Register an online observer to try again.
      // The online observer will continue the incremental download by
      // calling downloadUpdate on the active update which continues
      // downloading the file from where it was.
      LOG("Downloader:onStopRequest - offline, register online observer: true");
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                               AUSTLMY.DWNLD_RETRY_OFFLINE);
      shouldRegisterOnlineObserver = true;
      deleteActiveUpdate = false;
    // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
    // NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
    // when disconnecting the internet while a download of a MAR is in
    // progress.  There may be others but I have not encountered them during
    // testing.
    } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
                status == Cr.NS_ERROR_CONNECTION_REFUSED ||
                status == Cr.NS_ERROR_NET_RESET ||
                status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) &&
               this.updateService._consecutiveSocketErrors < maxFail) {
      LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
      let dwnldCode = AUSTLMY.DWNLD_RETRY_CONNECTION_REFUSED;
      if (status == Cr.NS_ERROR_NET_TIMEOUT) {
        dwnldCode = AUSTLMY.DWNLD_RETRY_NET_TIMEOUT;
      } else if (status == Cr.NS_ERROR_NET_RESET) {
        dwnldCode = AUSTLMY.DWNLD_RETRY_NET_RESET;
      } else if (status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
        dwnldCode = AUSTLMY.DWNLD_ERR_DOCUMENT_NOT_CACHED;
      }
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
      shouldRetrySoon = true;
      deleteActiveUpdate = false;
    } else if (status != Cr.NS_BINDING_ABORTED &&
               status != Cr.NS_ERROR_ABORT) {
      LOG("Downloader:onStopRequest - non-verification failure");
      let dwnldCode = AUSTLMY.DWNLD_ERR_BINDING_ABORTED;
      if (status == Cr.NS_ERROR_ABORT) {
        dwnldCode = AUSTLMY.DWNLD_ERR_ABORT;
      }
      AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);

      // Some sort of other failure, log this in the |statusText| property
      state = STATE_DOWNLOAD_FAILED;

      // XXXben - if |request| (The Incremental Download) provided a means
      // for accessing the http channel we could do more here.

      this._update.statusText = getStatusTextFromCode(status,
                                                      Cr.NS_BINDING_FAILED);

      // Destroy the updates directory, since we're done with it.
      cleanUpUpdatesDir();

      deleteActiveUpdate = true;
    }
    LOG("Downloader:onStopRequest - setting state to: " + state);
    this._patch.state = state;
    var um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    if (deleteActiveUpdate) {
      this._update.installDate = (new Date()).getTime();
      um.activeUpdate = null;
    } else if (um.activeUpdate) {
      um.activeUpdate.state = state;
    }
    um.saveUpdates();

    // Only notify listeners about the stopped state if we
    // aren't handling an internal retry.
    if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
      var listeners = this._listeners.concat();
      var listenerCount = listeners.length;
      for (var i = 0; i < listenerCount; ++i) {
        listeners[i].onStopRequest(request, context, status);
      }
    }

    this._request = null;

    if (state == STATE_DOWNLOAD_FAILED) {
      var allFailed = true;
      // Check if there is a complete update patch that can be downloaded.
      if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
        LOG("Downloader:onStopRequest - verification of patch failed, " +
            "downloading complete update patch");
        this._update.isCompleteUpdate = true;
        let updateStatus = this.downloadUpdate(this._update);

        if (updateStatus == STATE_NONE) {
          cleanupActiveUpdate();
        } else {
          allFailed = false;
        }
      }

      if (allFailed) {
        if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
          let downloadAttempts = getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
          downloadAttempts++;
          Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, downloadAttempts);
          let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);

          if (downloadAttempts > maxAttempts) {
            LOG("Downloader:onStopRequest - notifying observers of error. " +
                "topic: update-error, status: download-attempts-exceeded, " +
                "downloadAttempts: " + downloadAttempts + " " +
                "maxAttempts: " + maxAttempts);
            Services.obs.notifyObservers(this._update, "update-error", "download-attempts-exceeded");
          } else {
            this._update.selectedPatch.selected = false;
            LOG("Downloader:onStopRequest - notifying observers of error. " +
                "topic: update-error, status: download-attempt-failed");
            Services.obs.notifyObservers(this._update, "update-error", "download-attempt-failed");
          }
        }

        // If the update UI is not open (e.g. the user closed the window while
        // downloading) and if at any point this was a foreground download
        // notify the user about the error. If the update was a background
        // update there is no notification since the user won't be expecting it.
        if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
          this._update.QueryInterface(Ci.nsIWritablePropertyBag);
          if (this._update.getProperty("foregroundDownload") == "true") {
            let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                           createInstance(Ci.nsIUpdatePrompt);
            prompter.showUpdateError(this._update);
          }
        }

        // Prevent leaking the update object (bug 454964).
        this._update = null;
      }
      // A complete download has been initiated or the failure was handled.
      return;
    }

    if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
        state == STATE_PENDING_ELEVATE) {
      if (getCanStageUpdates()) {
        LOG("Downloader:onStopRequest - attempting to stage update: " +
            this._update.name);

        // Initiate the update in the background
        try {
          Cc["@mozilla.org/updates/update-processor;1"].
            createInstance(Ci.nsIUpdateProcessor).
            processUpdate(this._update);
        } catch (e) {
          // Fail gracefully in case the application does not support the update
          // processor service.
          LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
              e);
          if (this.background) {
            shouldShowPrompt = true;
          }
        }
      }
    }

    // Do this after *everything* else, since it will likely cause the app
    // to shut down.
    if (shouldShowPrompt) {
      // Notify the user that an update has been downloaded and is ready for
      // installation (i.e. that they should restart the application). We do
      // not notify on failed update attempts.
      let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                     createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateDownloaded(this._update, true);
    }

    if (shouldRegisterOnlineObserver) {
      LOG("Downloader:onStopRequest - Registering online observer");
      this.updateService._registerOnlineObserver();
    } else if (shouldRetrySoon) {
      LOG("Downloader:onStopRequest - Retrying soon");
      this.updateService._consecutiveSocketErrors++;
      if (this.updateService._retryTimer) {
        this.updateService._retryTimer.cancel();
      }
      this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      this.updateService._retryTimer.initWithCallback(function() {
        this._attemptResume();
      }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
    } else {
      // Prevent leaking the update object (bug 454964)
      this._update = null;
    }
  },

  /**
   * See nsIInterfaceRequestor.idl
   */
  getInterface: function Downloader_getInterface(iid) {
    // The network request may require proxy authentication, so provide the
    // default nsIAuthPrompt if requested.
    if (iid.equals(Ci.nsIAuthPrompt)) {
      var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
                   createInstance();
      return prompt.QueryInterface(iid);
    }
    throw Cr.NS_NOINTERFACE;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
                                         Ci.nsIProgressEventSink,
                                         Ci.nsIInterfaceRequestor])
};

/**
 * UpdatePrompt
 * An object which can prompt the user with information about updates, request
 * action, etc. Embedding clients can override this component with one that
 * invokes a native front end.
 * @constructor
 */
function UpdatePrompt() {
}
UpdatePrompt.prototype = {
  /**
   * See nsIUpdateService.idl
   */
  checkForUpdates: function UP_checkForUpdates() {
    if (this._getAltUpdateWindow())
      return;

    this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
                 null, null);
  },

  /**
   * See nsIUpdateService.idl
   */
  showUpdateAvailable: function UP_showUpdateAvailable(update) {
    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
        getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false) ||
        this._getUpdateWindow() || this._getAltUpdateWindow()) {
      return;
    }

    this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                           UPDATE_WINDOW_NAME, "updatesavailable", update);
  },

  /**
   * See nsIUpdateService.idl
   */
  showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
    if (background && getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) {
      return;
    }

    // Trigger the display of the hamburger doorhanger.
    LOG("showUpdateDownloaded - Notifying observers that " +
        "an update was downloaded. topic: update-downloaded, status: " + update.state);
    Services.obs.notifyObservers(update, "update-downloaded", update.state);

    if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
      return;
    }

    if (this._getAltUpdateWindow())
      return;

    if (background) {
      this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                              UPDATE_WINDOW_NAME, "finishedBackground", update);
    } else {
      this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                   UPDATE_WINDOW_NAME, "finishedBackground", update);
    }
  },

  /**
   * See nsIUpdateService.idl
   */
  showUpdateError: function UP_showUpdateError(update) {
    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
        getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false) ||
        this._getAltUpdateWindow())
      return;

    // In some cases, we want to just show a simple alert dialog.
    if (update.state == STATE_FAILED &&
        WRITE_ERRORS.includes(update.errorCode)) {
      var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
      var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
                                                    [Services.appinfo.name,
                                                     Services.appinfo.name], 2);
      Services.ww.getNewPrompter(null).alert(title, text);
      return;
    }

    if (update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
      this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
                           UPDATE_WINDOW_NAME, null, update);
      return;
    }

    this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
                 "errors", update);
  },

  /**
   * See nsIUpdateService.idl
   */
  showUpdateHistory: function UP_showUpdateHistory(parent) {
    this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
                 "Update:History", null, null);
  },

  /**
   * See nsIUpdateService.idl
   */
  showUpdateElevationRequired: function UP_showUpdateElevationRequired() {
    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
        this._getAltUpdateWindow()) {
      return;
    }

    let um = Cc["@mozilla.org/updates/update-manager;1"].
             getService(Ci.nsIUpdateManager);
    this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                 UPDATE_WINDOW_NAME, "finishedBackground", um.activeUpdate);
  },

  /**
   * Returns the update window if present.
   */
  _getUpdateWindow: function UP__getUpdateWindow() {
    return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
  },

  /**
   * Returns an alternative update window if present. When a window with this
   * windowtype is open the application update service won't open the normal
   * application update user interface window.
   */
  _getAltUpdateWindow: function UP__getAltUpdateWindow() {
    let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
    if (!windowType)
      return null;
    return Services.wm.getMostRecentWindow(windowType);
  },

  /**
   * Display the update UI after the prompt wait time has elapsed.
   * @param   parent
   *          A parent window, can be null
   * @param   uri
   *          The URI string of the dialog to show
   * @param   name
   *          The Window Name of the dialog to show, in case it is already open
   *          and can merely be focused
   * @param   page
   *          The page of the wizard to be displayed, if one is already open.
   * @param   update
   *          An update to pass to the UI in the window arguments.
   *          Can be null
   */
  _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
                                              update) {
    var observer = {
      updatePrompt: this,
      service: null,
      timer: null,
      notify() {
        // the user hasn't restarted yet => prompt when idle
        this.service.removeObserver(this, "quit-application");
        // If the update window is already open skip showing the UI
        if (this.updatePrompt._getUpdateWindow())
          return;
        this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
      },
      observe(aSubject, aTopic, aData) {
        switch (aTopic) {
          case "quit-application":
            if (this.timer)
              this.timer.cancel();
            this.service.removeObserver(this, "quit-application");
            break;
        }
      }
    };

    // bug 534090 - show the UI for update available notifications when the
    // the system has been idle for at least IDLE_TIME.
    if (page == "updatesavailable") {
      var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                        getService(Ci.nsIIdleService);
      // Don't allow the preference to set a value greater than 600 seconds for the idle time.
      const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
      if (idleService.idleTime / 1000 >= IDLE_TIME) {
        this._showUI(parent, uri, features, name, page, update);
        return;
      }
    }

    observer.service = Services.obs;
    observer.service.addObserver(observer, "quit-application");

    // bug 534090 - show the UI when idle for update available notifications.
    if (page == "updatesavailable") {
      this._showUIWhenIdle(parent, uri, features, name, page, update);
      return;
    }

    // Give the user x seconds to react before prompting as defined by
    // promptWaitTime
    observer.timer = Cc["@mozilla.org/timer;1"].
                     createInstance(Ci.nsITimer);
    observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
                                    observer.timer.TYPE_ONE_SHOT);
  },

  /**
   * Show the UI when the user was idle
   * @param   parent
   *          A parent window, can be null
   * @param   uri
   *          The URI string of the dialog to show
   * @param   name
   *          The Window Name of the dialog to show, in case it is already open
   *          and can merely be focused
   * @param   page
   *          The page of the wizard to be displayed, if one is already open.
   * @param   update
   *          An update to pass to the UI in the window arguments.
   *          Can be null
   */
  _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
                                               page, update) {
    var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                      getService(Ci.nsIIdleService);

    // Don't allow the preference to set a value greater than 600 seconds for the idle time.
    const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
    if (idleService.idleTime / 1000 >= IDLE_TIME) {
      this._showUI(parent, uri, features, name, page, update);
    } else {
      var observer = {
        updatePrompt: this,
        observe(aSubject, aTopic, aData) {
          switch (aTopic) {
            case "idle":
              // If the update window is already open skip showing the UI
              if (!this.updatePrompt._getUpdateWindow())
                this.updatePrompt._showUI(parent, uri, features, name, page, update);
              // fall thru
            case "quit-application":
              idleService.removeIdleObserver(this, IDLE_TIME);
              Services.obs.removeObserver(this, "quit-application");
              break;
          }
        }
      };
      idleService.addIdleObserver(observer, IDLE_TIME);
      Services.obs.addObserver(observer, "quit-application");
    }
  },

  /**
   * Show the Update Checking UI
   * @param   parent
   *          A parent window, can be null
   * @param   uri
   *          The URI string of the dialog to show
   * @param   name
   *          The Window Name of the dialog to show, in case it is already open
   *          and can merely be focused
   * @param   page
   *          The page of the wizard to be displayed, if one is already open.
   * @param   update
   *          An update to pass to the UI in the window arguments.
   *          Can be null
   */
  _showUI: function UP__showUI(parent, uri, features, name, page, update) {
    var ary = null;
    if (update) {
      ary = Cc["@mozilla.org/array;1"].
            createInstance(Ci.nsIMutableArray);
      ary.appendElement(update);
    }

    var win = this._getUpdateWindow();
    if (win) {
      if (page && "setCurrentPage" in win)
        win.setCurrentPage(page);
      win.focus();
    } else {
      var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
      if (features)
        openFeatures += "," + features;
      Services.ww.openWindow(parent, uri, "", openFeatures, ary);
    }
  },

  classDescription: "Update Prompt",
  contractID: "@mozilla.org/updates/update-prompt;1",
  classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
};

var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<4:!components/nsUpdateServiceStub.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/. */

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/FileUtils.jsm", this);

const DIR_UPDATES         = "updates";
const FILE_UPDATE_STATUS  = "update.status";

const KEY_UPDROOT         = "UpdRootD";

/**
 * Gets the specified directory at the specified hierarchy under the update root
 * directory without creating it if it doesn't exist.
 * @param   pathArray
 *          An array of path components to locate beneath the directory
 *          specified by |key|
 * @return  nsIFile object for the location specified.
 */
function getUpdateDirNoCreate(pathArray) {
  return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
}

function UpdateServiceStub() {
  let statusFile = getUpdateDirNoCreate([DIR_UPDATES, "0"]);
  statusFile.append(FILE_UPDATE_STATUS);
  // If the update.status file exists then initiate post update processing.
  if (statusFile.exists()) {
    let aus = Cc["@mozilla.org/updates/update-service;1"].
              getService(Ci.nsIApplicationUpdateService).
              QueryInterface(Ci.nsIObserver);
    aus.observe(null, "post-update-processing", "");
  }
}
UpdateServiceStub.prototype = {
  observe() {},
  classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdateServiceStub]);
PK
!<533"components/nsUpdateTimerManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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", this);
Cu.import("resource://gre/modules/Services.jsm", this);

const PREF_APP_UPDATE_LASTUPDATETIME_FMT  = "app.update.lastUpdateTime.%ID%";
const PREF_APP_UPDATE_TIMERMINIMUMDELAY   = "app.update.timerMinimumDelay";
const PREF_APP_UPDATE_TIMERFIRSTINTERVAL  = "app.update.timerFirstInterval";
const PREF_APP_UPDATE_LOG                 = "app.update.log";

const CATEGORY_UPDATE_TIMER               = "update-timer";

XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
});

/**
 *  Gets a preference value, handling the case where there is no default.
 *  @param   func
 *           The name of the preference function to call, on nsIPrefBranch
 *  @param   preference
 *           The name of the preference
 *  @param   defaultValue
 *           The default value to return in the event the preference has
 *           no setting
 *  @returns The value of the preference, or undefined if there was no
 *           user or default value.
 */
function getPref(func, preference, defaultValue) {
  try {
    return Services.prefs[func](preference);
  } catch (e) {
  }
  return defaultValue;
}

/**
 *  Logs a string to the error console.
 *  @param   string
 *           The string to write to the error console.
 */
function LOG(string) {
  if (gLogEnabled) {
    dump("*** UTM:SVC " + string + "\n");
    Services.console.logStringMessage("UTM:SVC " + string);
  }
}

/**
 *  A manager for timers. Manages timers that fire over long periods of time
 *  (e.g. days, weeks, months).
 *  @constructor
 */
function TimerManager() {
  Services.obs.addObserver(this, "profile-before-change");
}
TimerManager.prototype = {
  /**
   * The Checker Timer
   */
  _timer: null,

  /**
   * The Checker Timer minimum delay interval as specified by the
   * app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
   * pref doesn't exist this will default to 120000.
   */
  _timerMinimumDelay: null,

  /**
   * The set of registered timers.
   */
  _timers: { },

  /**
   * See nsIObserver.idl
   */
  observe: function TM_observe(aSubject, aTopic, aData) {
    // Prevent setting the timer interval to a value of less than 30 seconds.
    var minInterval = 30000;
    // Prevent setting the first timer interval to a value of less than 10
    // seconds.
    var minFirstInterval = 10000;
    switch (aTopic) {
      case "utm-test-init":
        // Enforce a minimum timer interval of 500 ms for tests and fall through
        // to profile-after-change to initialize the timer.
        minInterval = 500;
        minFirstInterval = 500;
      case "profile-after-change":
        this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
                                           minInterval);
        // Prevent the timer delay between notifications to other consumers from
        // being greater than 5 minutes which is 300000 milliseconds.
        this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
        // Prevent the first interval from being less than the value of minFirstInterval
        let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
                                             30000), minFirstInterval);
        // Prevent the first interval from being greater than 2 minutes which is
        // 120000 milliseconds.
        firstInterval = Math.min(firstInterval, 120000);
        // Cancel the timer if it has already been initialized. This is primarily
        // for tests.
        this._canEnsureTimer = true;
        this._ensureTimer(firstInterval);
        break;
      case "profile-before-change":
        Services.obs.removeObserver(this, "profile-before-change");

        // Release everything we hold onto.
        this._cancelTimer();
        for (var timerID in this._timers) {
          delete this._timers[timerID];
        }
        this._timers = null;
        break;
    }
  },

  /**
   * Called when the checking timer fires.
   *
   * We only fire one notification each time, so that the operations are
   * staggered. We don't want too many to happen at once, which could
   * negatively impact responsiveness.
   *
   * @param   timer
   *          The checking timer that fired.
   */
  notify: function TM_notify(timer) {
    var nextDelay = null;
    function updateNextDelay(delay) {
      if (nextDelay === null || delay < nextDelay) {
        nextDelay = delay;
      }
    }

    // Each timer calls tryFire(), which figures out which is the one that
    // wanted to be called earliest. That one will be fired; the others are
    // skipped and will be done later.
    var now = Math.round(Date.now() / 1000);

    var callbackToFire = null;
    var earliestIntendedTime = null;
    var skippedFirings = false;
    var lastUpdateTime = null;
    function tryFire(callback, intendedTime) {
      var selected = false;
      if (intendedTime <= now) {
        if (intendedTime < earliestIntendedTime ||
            earliestIntendedTime === null) {
          callbackToFire = callback;
          earliestIntendedTime = intendedTime;
          selected = true;
        } else if (earliestIntendedTime !== null) {
          skippedFirings = true;
        }
      }
      // We do not need to updateNextDelay for the timer that actually fires;
      // we'll update right after it fires, with the proper intended time.
      // Note that we might select one, then select another later (with an
      // earlier intended time); it is still ok that we did not update for
      // the first one, since if we have skipped firings, the next delay
      // will be the minimum delay anyhow.
      if (!selected) {
        updateNextDelay(intendedTime - now);
      }
    }

    var catMan = Cc["@mozilla.org/categorymanager;1"].
                 getService(Ci.nsICategoryManager);
    var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
    while (entries.hasMoreElements()) {
      let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
      let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
      let [cid, method, timerID, prefInterval, defaultInterval, maxInterval] = value.split(",");

      defaultInterval = parseInt(defaultInterval);
      // cid and method are validated below when calling notify.
      if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
        LOG("TimerManager:notify - update-timer category registered" +
            (cid ? " for " + cid : "") + " without required parameters - " +
             "skipping");
        continue;
      }

      let interval = getPref("getIntPref", prefInterval, defaultInterval);
      // Allow the update-timer category to specify a maximum value to prevent
      // values larger than desired.
      maxInterval = parseInt(maxInterval);
      if (maxInterval && !isNaN(maxInterval)) {
        interval = Math.min(interval, maxInterval);
      }
      let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
                                                                      timerID);
      // Initialize the last update time to 0 when the preference isn't set so
      // the timer will be notified soon after a new profile's first use.
      lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);

      // If the last update time is greater than the current time then reset
      // it to 0 and the timer manager will correct the value when it fires
      // next for this consumer.
      if (lastUpdateTime > now) {
        lastUpdateTime = 0;
      }

      if (lastUpdateTime == 0) {
        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
      }

      tryFire(function() {
        try {
          Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
          LOG("TimerManager:notify - notified " + cid);
        } catch (e) {
          LOG("TimerManager:notify - error notifying component id: " +
              cid + " ,error: " + e);
        }
        lastUpdateTime = now;
        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
        updateNextDelay(lastUpdateTime + interval - now);
      }, lastUpdateTime + interval);
    }

    for (let _timerID in this._timers) {
      let timerID = _timerID; // necessary for the closure to work properly
      let timerData = this._timers[timerID];
      // If the last update time is greater than the current time then reset
      // it to 0 and the timer manager will correct the value when it fires
      // next for this consumer.
      if (timerData.lastUpdateTime > now) {
        let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
        timerData.lastUpdateTime = 0;
        Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
      }
      tryFire(function() {
        if (timerData.callback && timerData.callback.notify) {
          try {
            timerData.callback.notify(timer);
            LOG("TimerManager:notify - notified timerID: " + timerID);
          } catch (e) {
            LOG("TimerManager:notify - error notifying timerID: " + timerID +
                ", error: " + e);
          }
        } else {
          LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
              "implement nsITimerCallback - skipping");
        }
        lastUpdateTime = now;
        timerData.lastUpdateTime = lastUpdateTime;
        let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
        updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
      }, timerData.lastUpdateTime + timerData.interval);
    }

    if (callbackToFire) {
      callbackToFire();
    }

    if (nextDelay !== null) {
      if (skippedFirings) {
        timer.delay = this._timerMinimumDelay;
      } else {
        timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
      }
      this.lastTimerReset = Date.now();
    } else {
      this._cancelTimer();
    }
  },

  /**
   * Starts the timer, if necessary, and ensures that it will fire soon enough
   * to happen after time |interval| (in milliseconds).
   */
  _ensureTimer(interval) {
    if (!this._canEnsureTimer) {
      return;
    }
    if (!this._timer) {
      this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      this._timer.initWithCallback(this, interval,
                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
      this.lastTimerReset = Date.now();
    } else if (Date.now() + interval < this.lastTimerReset + this._timer.delay) {
      this._timer.delay = Math.max(this.lastTimerReset + interval - Date.now(), 0);
    }
  },

  /**
   * Stops the timer, if it is running.
   */
  _cancelTimer() {
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  /**
   * See nsIUpdateTimerManager.idl
   */
  registerTimer: function TM_registerTimer(id, callback, interval) {
    LOG("TimerManager:registerTimer - id: " + id);
    if (this._timers === null) {
      // Use normal logging since reportError is not available while shutting
      // down.
      gLogEnabled = true;
      LOG("TimerManager:registerTimer called after profile-before-change " +
          "notification. Ignoring timer registration for id: " + id);
      return;
    }
    if (id in this._timers && callback != this._timers[id].callback) {
      LOG("TimerManager:registerTimer - Ignoring second registration for " + id);
      return;
    }
    let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
    // Initialize the last update time to 0 when the preference isn't set so
    // the timer will be notified soon after a new profile's first use.
    let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
    let now = Math.round(Date.now() / 1000);
    if (lastUpdateTime > now) {
      lastUpdateTime = 0;
    }
    if (lastUpdateTime == 0) {
      Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
    }
    this._timers[id] = {callback,
                        interval,
                        lastUpdateTime};

    this._ensureTimer(interval * 1000);
  },

  unregisterTimer: function TM_unregisterTimer(id) {
    LOG("TimerManager:unregisterTimer - id: " + id);
    if (id in this._timers) {
      delete this._timers[id];
    } else {
      LOG("TimerManager:unregisterTimer - Ignoring unregistration request for " +
          "unknown id: " + id);
    }
  },

  classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
                                         Ci.nsITimerCallback,
                                         Ci.nsIObserver])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);
PK
!<{R QQcomponents/multiprocessShims.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
                                  "resource://gre/modules/Prefetcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RemoteAddonsParent",
                                  "resource://gre/modules/RemoteAddonsParent.jsm");

/**
 * This service overlays the API that the browser exposes to
 * add-ons. The overlay tries to make a multiprocess browser appear as
 * much as possible like a single process browser. An overlay can
 * replace methods, getters, and setters of arbitrary browser objects.
 *
 * Most of the actual replacement code is implemented in
 * RemoteAddonsParent. The code in this service simply decides how to
 * replace code. For a given type of object (say, an
 * nsIObserverService) the code in RemoteAddonsParent can register a
 * set of replacement methods. This set is called an
 * "interposition". The service keeps track of all the different
 * interpositions. Whenever a method is called on some part of the
 * browser API, this service gets a chance to replace it. To do so, it
 * consults its map based on the type of object. If an interposition
 * is found, the given method is looked up on it and called
 * instead. If no method (or no interposition) is found, then the
 * original target method is called as normal.
 *
 * For each method call, we need to determine the type of the target
 * object.  If the object is an old-style XPConnect wrapped native,
 * then the type is simply the interface that the method was called on
 * (Ci.nsIObserverService, say). For all other objects (WebIDL
 * objects, CPOWs, and normal JS objects), the type is determined by
 * calling getObjectTag.
 *
 * The interpositions defined in RemoteAddonsParent have three
 * properties: methods, getters, and setters. When accessing a
 * property, we first consult methods. If nothing is found, then we
 * consult getters or setters, depending on whether the access is a
 * get or a set.
 *
 * The methods in |methods| are functions that will be called whenever
 * the given method is called on the target object. They are passed
 * the same parameters as the original function except for two
 * additional ones at the beginning: the add-on ID and the original
 * target object that the method was called on. Additionally, the
 * value of |this| is set to the original target object.
 *
 * The values in |getters| and |setters| should also be
 * functions. They are called immediately when the given property is
 * accessed. The functions in |getters| take two parameters: the
 * add-on ID and the original target object. The functions in
 * |setters| take those arguments plus the value that the property is
 * being set to.
 */

function AddonInterpositionService() {
  Prefetcher.init();
  RemoteAddonsParent.init();

  // These maps keep track of the interpositions for all different
  // kinds of objects.
  this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions();
  this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions();

  let wl = [];
  for (let v in this._interfaceInterpositions) {
    let interp = this._interfaceInterpositions[v];
    wl.push(...Object.getOwnPropertyNames(interp.methods));
    wl.push(...Object.getOwnPropertyNames(interp.getters));
    wl.push(...Object.getOwnPropertyNames(interp.setters));
  }

  for (let v in this._taggedInterpositions) {
    let interp = this._taggedInterpositions[v];
    wl.push(...Object.getOwnPropertyNames(interp.methods));
    wl.push(...Object.getOwnPropertyNames(interp.getters));
    wl.push(...Object.getOwnPropertyNames(interp.setters));
  }

  let nameSet = new Set();
  wl = wl.filter(function(item) {
    if (nameSet.has(item))
      return true;

    nameSet.add(item);
    return true;
  });

  this._whitelist = wl;
}

AddonInterpositionService.prototype = {
  classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),

  getWhitelist() {
    return this._whitelist;
  },

  // When the interface is not known for a method call, this code
  // determines the type of the target object.
  getObjectTag(target) {
    if (Cu.isCrossProcessWrapper(target)) {
      return Cu.getCrossProcessWrapperTag(target);
    }

    if (target instanceof Ci.nsIDOMXULElement) {
      if (target.localName == "browser" && target.isRemoteBrowser) {
        return "RemoteBrowserElement";
      }

      if (target.localName == "tabbrowser") {
        return "TabBrowserElement";
      }
    }

    if (target instanceof Ci.nsIDOMChromeWindow && target.gMultiProcessBrowser) {
      return "ChromeWindow";
    }

    if (target instanceof Ci.nsIDOMEventTarget) {
      return "EventTarget";
    }

    return "generic";
  },

  interposeProperty(addon, target, iid, prop) {
    let interp;
    if (iid) {
      interp = this._interfaceInterpositions[iid];
    } else {
      try {
        interp = this._taggedInterpositions[this.getObjectTag(target)];
      } catch (e) {
        Cu.reportError(new Components.Exception("Failed to interpose object", e.result, Components.stack.caller));
      }
    }

    if (!interp) {
      return Prefetcher.lookupInCache(addon, target, prop);
    }

    let desc = { configurable: false, enumerable: true };

    if ("methods" in interp && prop in interp.methods) {
      desc.writable = false;
      desc.value = function(...args) {
        return interp.methods[prop](addon, target, ...args);
      }

      return desc;
    } else if ("getters" in interp && prop in interp.getters) {
      desc.get = function() { return interp.getters[prop](addon, target); };

      if ("setters" in interp && prop in interp.setters) {
        desc.set = function(v) { return interp.setters[prop](addon, target, v); };
      }

      return desc;
    }

    return Prefetcher.lookupInCache(addon, target, prop);
  },

  interposeCall(addonId, originalFunc, originalThis, args) {
    args.splice(0, 0, addonId);
    return originalFunc.apply(originalThis, args);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]);
PK
!<gcomponents/defaultShims.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

/**
 * Using multiprocessShims is optional, and if an add-on is e10s compatible it should not
 * use it. But in some cases we still want to use the interposition service for various
 * features so we have a default shim service.
 */

function DefaultInterpositionService() {
}

DefaultInterpositionService.prototype = {
  classID: Components.ID("{50bc93ce-602a-4bef-bf3a-61fc749c4caf}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),

  getWhitelist() {
    return [];
  },

  interposeProperty(addon, target, iid, prop) {
    return null;
  },

  interposeCall(addonId, originalFunc, originalThis, args) {
    args.splice(0, 0, addonId);
    return originalFunc.apply(originalThis, args);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DefaultInterpositionService]);
PK
!<components/simpleServices.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Dumping ground for simple services for which the isolation of a full global
 * is overkill. Be careful about namespace pollution, and be mindful about
 * importing lots of JSMs in global scope, since this file will almost certainly
 * be loaded from enough callsites that any such imports will always end up getting
 * eagerly loaded at startup.
 */

"use strict";

const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cr = Components.results;

/* globals WebExtensionPolicy */

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

/*
 * This class provides a stream filter for locale messages in CSS files served
 * by the moz-extension: protocol handler.
 *
 * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
 * for usage.
 */
function AddonLocalizationConverter() {
}

AddonLocalizationConverter.prototype = {
  classID: Components.ID("{ded150e3-c92e-4077-a396-0dba9953e39f}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamConverter]),

  FROM_TYPE: "application/vnd.mozilla.webext.unlocalized",
  TO_TYPE: "text/css",

  checkTypes(aFromType, aToType) {
    if (aFromType != this.FROM_TYPE) {
      throw Components.Exception("Invalid aFromType value", Cr.NS_ERROR_INVALID_ARG,
                                 Components.stack.caller.caller);
    }
    if (aToType != this.TO_TYPE) {
      throw Components.Exception("Invalid aToType value", Cr.NS_ERROR_INVALID_ARG,
                                 Components.stack.caller.caller);
    }
  },

  // aContext must be a nsIURI object for a valid moz-extension: URL.
  getAddon(aContext) {
    // In this case, we want the add-on ID even if the URL is web accessible,
    // so check the root rather than the exact path.
    let uri = Services.io.newURI("/", null, aContext);

    let addon = WebExtensionPolicy.getByURI(uri);
    if (!addon) {
      throw new Components.Exception("Invalid context", Cr.NS_ERROR_INVALID_ARG);
    }
    return addon;
  },

  convertToStream(aAddon, aString) {
    let stream = Cc["@mozilla.org/io/string-input-stream;1"]
      .createInstance(Ci.nsIStringInputStream);

    stream.data = aAddon.localize(aString);
    return stream;
  },

  convert(aStream, aFromType, aToType, aContext) {
    this.checkTypes(aFromType, aToType);
    let addon = this.getAddon(aContext);

    let string = (
      aStream.available() ?
      NetUtil.readInputStreamToString(aStream, aStream.available()) : ""
    );
    return this.convertToStream(addon, string);
  },

  asyncConvertData(aFromType, aToType, aListener, aContext) {
    this.checkTypes(aFromType, aToType);
    this.addon = this.getAddon(aContext);
    this.listener = aListener;
  },

  onStartRequest(aRequest, aContext) {
    this.parts = [];
  },

  onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
    this.parts.push(NetUtil.readInputStreamToString(aInputStream, aCount));
  },

  onStopRequest(aRequest, aContext, aStatusCode) {
    try {
      this.listener.onStartRequest(aRequest, null);
      if (Components.isSuccessCode(aStatusCode)) {
        let string = this.parts.join("");
        let stream = this.convertToStream(this.addon, string);

        this.listener.onDataAvailable(aRequest, null, stream, 0, stream.data.length);
      }
    } catch (e) {
      aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
    }
    this.listener.onStopRequest(aRequest, null, aStatusCode);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonLocalizationConverter]);
PK
!<7Xߖ33"components/MainProcessSingleton.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { utils: Cu, interfaces: Ci, classes: Cc, results: Cr } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");

function MainProcessSingleton() {}
MainProcessSingleton.prototype = {
  classID: Components.ID("{0636a680-45cb-11e4-916c-0800200c9a66}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  logConsoleMessage(message) {
    let logMsg = message.data;
    logMsg.wrappedJSObject = logMsg;
    Services.obs.notifyObservers(logMsg, "console-api-log-event");
  },

  // Called when a webpage calls window.external.AddSearchProvider
  addSearchEngine({ target: browser, data: { pageURL, engineURL } }) {
    pageURL = NetUtil.newURI(pageURL);
    engineURL = NetUtil.newURI(engineURL, null, pageURL);

    let iconURL;
    let tabbrowser = browser.getTabBrowser();
    if (browser.mIconURL && (!tabbrowser || tabbrowser.shouldLoadFavIcon(pageURL)))
      iconURL = NetUtil.newURI(browser.mIconURL);

    try {
      // Make sure the URLs are HTTP, HTTPS, or FTP.
      let isWeb = ["https", "http", "ftp"];

      if (isWeb.indexOf(engineURL.scheme) < 0)
        throw "Unsupported search engine URL: " + engineURL;

      if (iconURL && isWeb.indexOf(iconURL.scheme) < 0)
        throw "Unsupported search icon URL: " + iconURL;
    } catch (ex) {
      Cu.reportError("Invalid argument passed to window.external.AddSearchProvider: " + ex);

      var searchBundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
      var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
      var brandName = brandBundle.GetStringFromName("brandShortName");
      var title = searchBundle.GetStringFromName("error_invalid_format_title");
      var msg = searchBundle.formatStringFromName("error_invalid_engine_msg2",
                                                  [brandName, engineURL.spec], 2);
      Services.ww.getNewPrompter(browser.ownerGlobal).alert(title, msg);
      return;
    }

    Services.search.init(function(status) {
      if (status != Cr.NS_OK)
        return;

      Services.search.addEngine(engineURL.spec, null, iconURL ? iconURL.spec : null, true);
    })
  },

  observe(subject, topic, data) {
    switch (topic) {
    case "app-startup": {
      Services.obs.addObserver(this, "xpcom-shutdown");

      // Load this script early so that console.* is initialized
      // before other frame scripts.
      Services.mm.loadFrameScript("chrome://global/content/browser-content.js", true);
      Services.ppmm.loadProcessScript("chrome://global/content/process-content.js", true);
      Services.ppmm.addMessageListener("Console:Log", this.logConsoleMessage);
      Services.mm.addMessageListener("Search:AddEngine", this.addSearchEngine);
      Services.ppmm.loadProcessScript("resource:///modules/ContentObservers.js", true);
      break;
    }

    case "xpcom-shutdown":
      Services.ppmm.removeMessageListener("Console:Log", this.logConsoleMessage);
      Services.mm.removeMessageListener("Search:AddEngine", this.addSearchEngine);
      break;
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MainProcessSingleton]);
PK
!<T#  %components/ContentProcessSingleton.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                   "@mozilla.org/childprocessmessagemanager;1",
                                   "nsIMessageSender");

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                                  "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                  "resource:///modules/E10SUtils.jsm");

/*
 * The message manager has an upper limit on message sizes that it can
 * reliably forward to the parent so we limit the size of console log event
 * messages that we forward here. The web console is local and receives the
 * full console message, but addons subscribed to console event messages
 * in the parent receive the truncated version. Due to fragmentation,
 * messages as small as 1MB have resulted in IPC allocation failures on
 * 32-bit platforms. To limit IPC allocation sizes, console.log messages
 * with arguments with total size > MSG_MGR_CONSOLE_MAX_SIZE (bytes) have
 * their arguments completely truncated. MSG_MGR_CONSOLE_VAR_SIZE is an
 * approximation of how much space (in bytes) a JS non-string variable will
 * require in the manager's implementation. For strings, we use 2 bytes per
 * char. The console message URI and function name are limited to
 * MSG_MGR_CONSOLE_INFO_MAX characters. We don't attempt to calculate
 * the exact amount of space the message manager implementation will require
 * for a given message so this is imperfect.
 */
const MSG_MGR_CONSOLE_MAX_SIZE = 1024 * 1024; // 1MB
const MSG_MGR_CONSOLE_VAR_SIZE = 8;
const MSG_MGR_CONSOLE_INFO_MAX = 1024;

function ContentProcessSingleton() {}
ContentProcessSingleton.prototype = {
  classID: Components.ID("{ca2a8470-45c7-11e4-916c-0800200c9a66}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  observe(subject, topic, data) {
    switch (topic) {
    case "app-startup": {
      Services.obs.addObserver(this, "console-api-log-event");
      Services.obs.addObserver(this, "xpcom-shutdown");
      TelemetryController.observe(null, topic, null);
      break;
    }
    case "console-api-log-event": {
      let consoleMsg = subject.wrappedJSObject;

      let msgData = {
        level: consoleMsg.level,
        filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX),
        lineNumber: consoleMsg.lineNumber,
        functionName: consoleMsg.functionName &&
          consoleMsg.functionName.substring(0, MSG_MGR_CONSOLE_INFO_MAX),
        timeStamp: consoleMsg.timeStamp,
        addonId: consoleMsg.addonId,
        arguments: [],
      };

      // We can't send objects over the message manager, so we sanitize
      // them out, replacing those arguments with "<unavailable>".
      let unavailString = "<unavailable>";
      let unavailStringLength = unavailString.length * 2; // 2-bytes per char

      // When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE,
      // replace all arguments with "<truncated>".
      let totalArgLength = 0;

      // Walk through the arguments, checking the type and size.
      for (let arg of consoleMsg.arguments) {
        if ((typeof arg == "object" || typeof arg == "function") &&
            arg !== null) {
          if (Services.appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE) {
            // For OOP extensions: we want the developer to be able to see the
            // logs in the Browser Console. When the Addon Toolbox will be more
            // prominent we can revisit.
            try {
              // If the argument is clonable, then send it as-is. If
              // cloning fails, fall back to the unavailable string.
              arg = Cu.cloneInto(arg, {});
            } catch (e) {
              arg = unavailString;
            }
          } else {
            arg = unavailString;
          }
          totalArgLength += unavailStringLength;
        } else if (typeof arg == "string") {
          totalArgLength += arg.length * 2; // 2-bytes per char
        } else {
          totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE;
        }

        if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) {
          msgData.arguments.push(arg);
        } else {
          // arguments take up too much space
          msgData.arguments = ["<truncated>"];
          break;
        }
      }

      cpmm.sendAsyncMessage("Console:Log", msgData);
      break;
    }

    case "xpcom-shutdown":
      Services.obs.removeObserver(this, "console-api-log-event");
      Services.obs.removeObserver(this, "xpcom-shutdown");
      break;
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentProcessSingleton]);
PK
!<b%%components/nsURLFormatter.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

 /**
 * @class nsURLFormatterService
 *
 * nsURLFormatterService exposes methods to substitute variables in URL formats.
 *
 * Mozilla Applications linking to Mozilla websites are strongly encouraged to use
 * URLs of the following format:
 *
 *   http[s]://%SERVICE%.mozilla.[com|org]/%LOCALE%/
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

const PREF_APP_DISTRIBUTION           = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";

XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

function nsURLFormatterService() {
  XPCOMUtils.defineLazyGetter(this, "appInfo", function UFS_appInfo() {
    return Cc["@mozilla.org/xre/app-info;1"].
           getService(Ci.nsIXULAppInfo).
           QueryInterface(Ci.nsIXULRuntime);
  });

  XPCOMUtils.defineLazyGetter(this, "ABI", function UFS_ABI() {
    let ABI = "default";
    try {
      ABI = this.appInfo.XPCOMABI;

      if ("@mozilla.org/xpcom/mac-utils;1" in Cc) {
        // Mac universal build should report a different ABI than either macppc
        // or mactel.
        let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]
                         .getService(Ci.nsIMacUtils);
        if (macutils && macutils.isUniversalBinary) {
          ABI = "Universal-gcc3";
        }
      }
    } catch (e) {}

    return ABI;
  });

  XPCOMUtils.defineLazyGetter(this, "OSVersion", function UFS_OSVersion() {
    let OSVersion = "default";
    let sysInfo = Cc["@mozilla.org/system-info;1"].
                  getService(Ci.nsIPropertyBag2);
    try {
      OSVersion = sysInfo.getProperty("name") + " " +
                  sysInfo.getProperty("version");
      OSVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
    } catch (e) {}

    return encodeURIComponent(OSVersion);
  });

  XPCOMUtils.defineLazyGetter(this, "distribution", function UFS_distribution() {
    let distribution = { id: "default", version: "default" };

    let defaults = Services.prefs.getDefaultBranch(null);
    try {
      distribution.id = defaults.getCharPref(PREF_APP_DISTRIBUTION);
    } catch (e) {}
    try {
      distribution.version = defaults.getCharPref(PREF_APP_DISTRIBUTION_VERSION);
    } catch (e) {}

    return distribution;
  });
}

nsURLFormatterService.prototype = {
  classID: Components.ID("{e6156350-2be8-11db-a98b-0800200c9a66}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIURLFormatter]),

  _defaults: {
    LOCALE:           () => Services.locale.getAppLocaleAsLangTag(),
    REGION:           function() {
      try {
        // When the geoip lookup failed to identify the region, we fallback to
        // the 'ZZ' region code to mean 'unknown'.
        return Services.prefs.getCharPref("browser.search.region") || "ZZ";
      } catch(e) {
        return "ZZ";
      }
    },
    VENDOR:           function() { return this.appInfo.vendor; },
    NAME:             function() { return this.appInfo.name; },
    ID:               function() { return this.appInfo.ID; },
    VERSION:          function() { return this.appInfo.version; },
    MAJOR_VERSION:    function() { return this.appInfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); },
    APPBUILDID:       function() { return this.appInfo.appBuildID; },
    PLATFORMVERSION:  function() { return this.appInfo.platformVersion; },
    PLATFORMBUILDID:  function() { return this.appInfo.platformBuildID; },
    APP:              function() { return this.appInfo.name.toLowerCase().replace(/ /, ""); },
    OS:               function() { return this.appInfo.OS; },
    XPCOMABI:         function() { return this.ABI; },
    BUILD_TARGET:     function() { return this.appInfo.OS + "_" + this.ABI; },
    OS_VERSION:       function() { return this.OSVersion; },
    CHANNEL:          () => UpdateUtils.UpdateChannel,
    MOZILLA_API_KEY:  () => AppConstants.MOZ_MOZILLA_API_KEY,
    GOOGLE_API_KEY:   () => AppConstants.MOZ_GOOGLE_API_KEY,
    BING_API_CLIENTID:() => AppConstants.MOZ_BING_API_CLIENTID,
    BING_API_KEY:     () => AppConstants.MOZ_BING_API_KEY,
    DISTRIBUTION:     function() { return this.distribution.id; },
    DISTRIBUTION_VERSION: function() { return this.distribution.version; }
  },

  formatURL: function uf_formatURL(aFormat) {
    var _this = this;
    var replacementCallback = function(aMatch, aKey) {
      if (aKey in _this._defaults) {
        return _this._defaults[aKey].call(_this);
      }
      Cu.reportError("formatURL: Couldn't find value for key: " + aKey);
      return aMatch;
    }
    return aFormat.replace(/%([A-Z_]+)%/g, replacementCallback);
  },

  formatURLPref: function uf_formatURLPref(aPref) {
    var format = null;
    var PS = Cc['@mozilla.org/preferences-service;1'].
             getService(Ci.nsIPrefBranch);

    try {
      format = PS.getStringPref(aPref);
    } catch(ex) {
      Cu.reportError("formatURLPref: Couldn't get pref: " + aPref);
      return "about:blank";
    }

    if (!PS.prefHasUserValue(aPref) &&
        /^(data:text\/plain,.+=.+|chrome:\/\/.+\/locale\/.+\.properties)$/.test(format)) {
      // This looks as if it might be a localised preference
      try {
        format = PS.getComplexValue(aPref, Ci.nsIPrefLocalizedString).data;
      } catch(ex) {}
    }

    return this.formatURL(format);
  },

  trimSensitiveURLs: function uf_trimSensitiveURLs(aMsg) {
    // Only the google API key is sensitive for now.
    return AppConstants.MOZ_GOOGLE_API_KEY ? aMsg.replace(RegExp(AppConstants.MOZ_GOOGLE_API_KEY, 'g'),
                                                 "[trimmed-google-api-key]")
                                  : aMsg;
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsURLFormatterService]);
PK
!<=$	$	#components/txEXSLTRegExFunctions.js/* -*- 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/XPCOMUtils.jsm");

const EXSLT_REGEXP_CID = Components.ID("{18a03189-067b-4978-b4f1-bafe35292ed6}");
const EXSLT_REGEXP_CONTRACTID = "@mozilla.org/exslt/regexp;1";

const NODESET_CONTRACTID = "@mozilla.org/transformiix-nodeset;1";

const Ci = Components.interfaces;

function txEXSLTRegExFunctions()
{
}

var SingletonInstance = null;

txEXSLTRegExFunctions.prototype = {
    classID: EXSLT_REGEXP_CID,

    QueryInterface: XPCOMUtils.generateQI([Ci.txIEXSLTRegExFunctions]),

    classInfo: XPCOMUtils.generateCI({classID: EXSLT_REGEXP_CID,
                                      contractID: EXSLT_REGEXP_CONTRACTID,
                                      interfaces: [Ci.txIEXSLTRegExFunctions]}),

    // txIEXSLTRegExFunctions
    match: function(context, str, regex, flags) {
        var nodeset = Components.classes[NODESET_CONTRACTID]
                                .createInstance(Ci.txINodeSet);

        var re = new RegExp(regex, flags);
        var matches = str.match(re);
        if (matches != null && matches.length > 0) {
            var contextNode = context.contextNode;
            var doc = contextNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ?
                      contextNode : contextNode.ownerDocument;
            var docFrag = doc.createDocumentFragment();

            for (var i = 0; i < matches.length; ++i) {
                var match = matches[i];
                var elem = doc.createElementNS(null, "match");
                var text = doc.createTextNode(match ? match : '');
                elem.appendChild(text);
                docFrag.appendChild(elem);
                nodeset.add(elem);
            }
        }

        return nodeset;
    },

    replace: function(str, regex, flags, replace) {
        var re = new RegExp(regex, flags);

        return str.replace(re, replace);
    },

    test: function(str, regex, flags) {
        var re = new RegExp(regex, flags);

        return re.test(str);
    }
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([txEXSLTRegExFunctions]);
PK
!<>-mmcomponents/nsLivemarkService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

// Modules and services.

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                  "resource://gre/modules/Deprecated.jsm");

XPCOMUtils.defineLazyGetter(this, "asyncHistory", function() {
  // Lazily add an history observer when it's actually needed.
  PlacesUtils.history.addObserver(PlacesUtils.livemarks, true);
  return PlacesUtils.asyncHistory;
});

// Constants

// Delay between reloads of consecute livemarks.
const RELOAD_DELAY_MS = 500;
// Expire livemarks after this time.
const EXPIRE_TIME_MS = 3600000; // 1 hour.
// Expire livemarks after this time on error.
const ONERROR_EXPIRE_TIME_MS = 300000; // 5 minutes.

// Livemarks cache.

XPCOMUtils.defineLazyGetter(this, "CACHE_SQL", () => {
  function getAnnoSQLFragment(aAnnoParam) {
    return `SELECT a.content
            FROM moz_items_annos a
            JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
            WHERE a.item_id = b.id
              AND n.name = ${aAnnoParam}`;
  }

  return `SELECT b.id, b.title, b.parent As parentId, b.position AS 'index',
                 b.guid, b.dateAdded, b.lastModified, p.guid AS parentGuid,
                 ( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI,
                 ( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI
          FROM moz_bookmarks b
          JOIN moz_bookmarks p ON b.parent = p.id
          JOIN moz_items_annos a ON a.item_id = b.id
          JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
          WHERE b.type = :folder_type
            AND n.name = :feedURI_anno`;
});

XPCOMUtils.defineLazyGetter(this, "gLivemarksCachePromised",
  async function() {
    let livemarksMap = new Map();
    let conn = await PlacesUtils.promiseDBConnection();
    let rows = await conn.executeCached(CACHE_SQL,
      { folder_type: Ci.nsINavBookmarksService.TYPE_FOLDER,
        feedURI_anno: PlacesUtils.LMANNO_FEEDURI,
        siteURI_anno: PlacesUtils.LMANNO_SITEURI });
    for (let row of rows) {
      let siteURI = row.getResultByName("siteURI");
      let livemark = new Livemark({
        id: row.getResultByName("id"),
        guid: row.getResultByName("guid"),
        title: row.getResultByName("title"),
        parentId: row.getResultByName("parentId"),
        parentGuid: row.getResultByName("parentGuid"),
        index: row.getResultByName("index"),
        dateAdded: row.getResultByName("dateAdded"),
        lastModified: row.getResultByName("lastModified"),
        feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
        siteURI: siteURI ? NetUtil.newURI(siteURI) : null
      });
      livemarksMap.set(livemark.guid, livemark);
    }
    return livemarksMap;
  }
);

/**
 * Convert a Date object to a PRTime (microseconds).
 *
 * @param date
 *        the Date object to convert.
 * @return microseconds from the epoch.
 */
function toPRTime(date) {
  return date * 1000;
}

/**
 * Convert a PRTime to a Date object.
 *
 * @param time
 *        microseconds from the epoch.
 * @return a Date object or undefined if time was not defined.
 */
function toDate(time) {
  return time ? new Date(parseInt(time / 1000)) : undefined;
}

// LivemarkService

function LivemarkService() {
  // Cleanup on shutdown.
  Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);

  // Observe bookmarks but don't init the service just for that.
  PlacesUtils.bookmarks.addObserver(this, true);
}

LivemarkService.prototype = {
  // This is just an helper for code readability.
  _promiseLivemarksMap: () => gLivemarksCachePromised,

  _reloading: false,
  _startReloadTimer(livemarksMap, forceUpdate, reloaded) {
    if (this._reloadTimer) {
      this._reloadTimer.cancel();
    } else {
      this._reloadTimer = Cc["@mozilla.org/timer;1"]
                            .createInstance(Ci.nsITimer);
    }

    this._reloading = true;
    this._reloadTimer.initWithCallback(() => {
      // Find first livemark to be reloaded.
      for (let [ guid, livemark ] of livemarksMap) {
        if (!reloaded.has(guid)) {
          reloaded.add(guid);
          livemark.reload(forceUpdate);
          this._startReloadTimer(livemarksMap, forceUpdate, reloaded);
          return;
        }
      }
      // All livemarks have been reloaded.
      this._reloading = false;
    }, RELOAD_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  // nsIObserver

  observe(aSubject, aTopic, aData) {
    if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) {
      if (this._reloadTimer) {
        this._reloading = false;
        this._reloadTimer.cancel();
        delete this._reloadTimer;
      }

      // Stop any ongoing network fetch.
      this._promiseLivemarksMap().then(livemarksMap => {
        for (let livemark of livemarksMap.values()) {
          livemark.terminate();
        }
      });
    }
  },

  // mozIAsyncLivemarks

  addLivemark(aLivemarkInfo) {
    if (!aLivemarkInfo) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }
    let hasParentId = "parentId" in aLivemarkInfo;
    let hasParentGuid = "parentGuid" in aLivemarkInfo;
    let hasIndex = "index" in aLivemarkInfo;
    // Must provide at least non-null parent guid/id, index and feedURI.
    if ((!hasParentId && !hasParentGuid) ||
        (hasParentId && aLivemarkInfo.parentId < 1) ||
        (hasParentGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.parentGuid)) ||
        (hasIndex && aLivemarkInfo.index < Ci.nsINavBookmarksService.DEFAULT_INDEX) ||
        !(aLivemarkInfo.feedURI instanceof Ci.nsIURI) ||
        (aLivemarkInfo.siteURI && !(aLivemarkInfo.siteURI instanceof Ci.nsIURI)) ||
        (aLivemarkInfo.guid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid))) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }

    return (async () => {
      if (!aLivemarkInfo.parentGuid)
        aLivemarkInfo.parentGuid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.parentId);

      let livemarksMap = await this._promiseLivemarksMap();

      // Disallow adding a livemark inside another livemark.
      if (livemarksMap.has(aLivemarkInfo.parentGuid)) {
        throw new Components.Exception("Cannot create a livemark inside a livemark", Cr.NS_ERROR_INVALID_ARG);
      }

      // Create a new livemark.
      let folder = await PlacesUtils.bookmarks.insert({
        type: PlacesUtils.bookmarks.TYPE_FOLDER,
        parentGuid: aLivemarkInfo.parentGuid,
        title: aLivemarkInfo.title,
        index: aLivemarkInfo.index,
        guid: aLivemarkInfo.guid,
        dateAdded: toDate(aLivemarkInfo.dateAdded) || toDate(aLivemarkInfo.lastModified),
        source: aLivemarkInfo.source,
      });

      // Set feed and site URI annotations.
      let id = await PlacesUtils.promiseItemId(folder.guid);

      // Create the internal Livemark object.
      let livemark = new Livemark({ id,
                                    title:        folder.title,
                                    parentGuid:   folder.parentGuid,
                                    parentId:     await PlacesUtils.promiseItemId(folder.parentGuid),
                                    index:        folder.index,
                                    feedURI:      aLivemarkInfo.feedURI,
                                    siteURI:      aLivemarkInfo.siteURI,
                                    guid:         folder.guid,
                                    dateAdded:    toPRTime(folder.dateAdded),
                                    lastModified: toPRTime(folder.lastModified)
                                  });

      livemark.writeFeedURI(aLivemarkInfo.feedURI, aLivemarkInfo.source);
      if (aLivemarkInfo.siteURI) {
        livemark.writeSiteURI(aLivemarkInfo.siteURI, aLivemarkInfo.source);
      }

      if (aLivemarkInfo.lastModified) {
        await PlacesUtils.bookmarks.update({ guid: folder.guid,
                                             lastModified: toDate(aLivemarkInfo.lastModified),
                                             source: aLivemarkInfo.source });
        livemark.lastModified = aLivemarkInfo.lastModified;
      }

      livemarksMap.set(folder.guid, livemark);

      return livemark;
    })();
  },

  removeLivemark(aLivemarkInfo) {
    if (!aLivemarkInfo) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }
    // Accept either a guid or an id.
    let hasGuid = "guid" in aLivemarkInfo;
    let hasId = "id" in aLivemarkInfo;
    if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
        (hasId && aLivemarkInfo.id < 1) ||
        (!hasId && !hasGuid)) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }

    return (async () => {
      if (!aLivemarkInfo.guid)
        aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);

      let livemarksMap = await this._promiseLivemarksMap();
      if (!livemarksMap.has(aLivemarkInfo.guid))
        throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);

      await PlacesUtils.bookmarks.remove(aLivemarkInfo.guid,
                                         { source: aLivemarkInfo.source });
    })();
  },

  reloadLivemarks(aForceUpdate) {
    // Check if there's a currently running reload, to save some useless work.
    let notWorthRestarting =
      this._forceUpdate || // We're already forceUpdating.
      !aForceUpdate;       // The caller didn't request a forced update.
    if (this._reloading && notWorthRestarting) {
      // Ignore this call.
      return;
    }

    this._promiseLivemarksMap().then(livemarksMap => {
      this._forceUpdate = !!aForceUpdate;
      // Livemarks reloads happen on a timer for performance reasons.
      this._startReloadTimer(livemarksMap, this._forceUpdate, new Set());
    });
  },

  getLivemark(aLivemarkInfo) {
    if (!aLivemarkInfo) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }
    // Accept either a guid or an id.
    let hasGuid = "guid" in aLivemarkInfo;
    let hasId = "id" in aLivemarkInfo;
    if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
        (hasId && aLivemarkInfo.id < 1) ||
        (!hasId && !hasGuid)) {
      throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
    }

    return (async () => {
      if (!aLivemarkInfo.guid)
        aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);

      let livemarksMap = await this._promiseLivemarksMap();
      if (!livemarksMap.has(aLivemarkInfo.guid))
        throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);

      return livemarksMap.get(aLivemarkInfo.guid);
    })();
  },

  // nsINavBookmarkObserver

  onBeginUpdateBatch() {},
  onEndUpdateBatch() {},
  onItemVisited() {},
  onItemAdded() {},

  onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
                guid, parentGuid) {
    if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
      return;

    this._promiseLivemarksMap().then(livemarksMap => {
      if (livemarksMap.has(guid)) {
        let livemark = livemarksMap.get(guid);
        if (property == "title") {
          livemark.title = value;
        }
        livemark.lastModified = lastModified;
      }
    });
  },

  onItemMoved(id, parentId, oldIndex, newParentId, newIndex, itemType, guid,
              oldParentGuid, newParentGuid) {
    if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
      return;

    this._promiseLivemarksMap().then(livemarksMap => {
      if (livemarksMap.has(guid)) {
        let livemark = livemarksMap.get(guid);
        livemark.parentId = newParentId;
        livemark.parentGuid = newParentGuid;
        livemark.index = newIndex;
      }
    });
  },

  onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
    if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
      return;

    this._promiseLivemarksMap().then(livemarksMap => {
      if (livemarksMap.has(guid)) {
        let livemark = livemarksMap.get(guid);
        livemark.terminate();
        livemarksMap.delete(guid);
      }
    });
  },

  // nsINavHistoryObserver

  onPageChanged() {},
  onTitleChanged() {},
  onDeleteVisits() {},

  onClearHistory() {
    this._promiseLivemarksMap().then(livemarksMap => {
      for (let livemark of livemarksMap.values()) {
        livemark.updateURIVisitedStatus(null, false);
      }
    });
  },

  onDeleteURI(aURI) {
    this._promiseLivemarksMap().then(livemarksMap => {
      for (let livemark of livemarksMap.values()) {
        livemark.updateURIVisitedStatus(aURI, false);
      }
    });
  },

  onVisit(aURI) {
    this._promiseLivemarksMap().then(livemarksMap => {
      for (let livemark of livemarksMap.values()) {
        livemark.updateURIVisitedStatus(aURI, true);
      }
    });
  },

  // nsISupports

  classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.mozIAsyncLivemarks,
    Ci.nsINavBookmarkObserver,
    Ci.nsINavHistoryObserver,
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference
  ])
};

// Livemark

/**
 * Object used internally to represent a livemark.
 *
 * @param aLivemarkInfo
 *        Object containing information on the livemark.  If the livemark is
 *        not included in the object, a new livemark will be created.
 *
 * @note terminate() must be invoked before getting rid of this object.
 */
function Livemark(aLivemarkInfo) {
  this.id = aLivemarkInfo.id;
  this.guid = aLivemarkInfo.guid;
  this.feedURI = aLivemarkInfo.feedURI;
  this.siteURI = aLivemarkInfo.siteURI || null;
  this.title = aLivemarkInfo.title;
  this.parentId = aLivemarkInfo.parentId;
  this.parentGuid = aLivemarkInfo.parentGuid;
  this.index = aLivemarkInfo.index;
  this.dateAdded = aLivemarkInfo.dateAdded;
  this.lastModified = aLivemarkInfo.lastModified;

  this._status = Ci.mozILivemark.STATUS_READY;

  // Hash of resultObservers, hashed by container.
  this._resultObservers = new Map();

  // Sorted array of objects representing livemark children in the form
  // { uri, title, visited }.
  this._children = [];

  // Keeps a separate array of nodes for each requesting container, hashed by
  // the container itself.
  this._nodes = new Map();

  this.loadGroup = null;
  this.expireTime = 0;
}

Livemark.prototype = {
  get status() {
    return this._status;
  },
  set status(val) {
    if (this._status != val) {
      this._status = val;
      this._invalidateRegisteredContainers();
    }
    return this._status;
  },

  writeFeedURI(aFeedURI, aSource) {
    PlacesUtils.annotations
               .setItemAnnotation(this.id, PlacesUtils.LMANNO_FEEDURI,
                                  aFeedURI.spec,
                                  0, PlacesUtils.annotations.EXPIRE_NEVER,
                                  aSource);
    this.feedURI = aFeedURI;
  },

  writeSiteURI(aSiteURI, aSource) {
    if (!aSiteURI) {
      PlacesUtils.annotations.removeItemAnnotation(this.id,
                                                   PlacesUtils.LMANNO_SITEURI,
                                                   aSource)
      this.siteURI = null;
      return;
    }

    // Security check the site URI against the feed URI principal.
    let secMan = Services.scriptSecurityManager;
    let feedPrincipal = secMan.createCodebasePrincipal(this.feedURI, {});
    try {
      secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI,
                                       Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
    } catch (ex) {
      return;
    }

    PlacesUtils.annotations
               .setItemAnnotation(this.id, PlacesUtils.LMANNO_SITEURI,
                                  aSiteURI.spec,
                                  0, PlacesUtils.annotations.EXPIRE_NEVER,
                                  aSource);
    this.siteURI = aSiteURI;
  },

  /**
   * Tries to updates the livemark if needed.
   * The update process is asynchronous.
   *
   * @param [optional] aForceUpdate
   *        If true will try to update the livemark even if its contents have
   *        not yet expired.
   */
  updateChildren(aForceUpdate) {
    // Check if the livemark is already updating.
    if (this.status == Ci.mozILivemark.STATUS_LOADING)
      return;

    // Check the TTL/expiration on this, to check if there is no need to update
    // this livemark.
    if (!aForceUpdate && this.children.length && this.expireTime > Date.now())
      return;

    this.status = Ci.mozILivemark.STATUS_LOADING;

    // Setting the status notifies observers that may remove the livemark.
    if (this._terminated)
      return;

    try {
      // Create a load group for the request.  This will allow us to
      // automatically keep track of redirects, so we can always
      // cancel the channel.
      let loadgroup = Cc["@mozilla.org/network/load-group;1"].
                      createInstance(Ci.nsILoadGroup);
      // Creating a CodeBasePrincipal and using it as the loadingPrincipal
      // is *not* desired and is only tolerated within this file.
      // TODO: Find the right OriginAttributes and pass something other
      // than {} to .createCodeBasePrincipal().
      let channel = NetUtil.newChannel({
        uri: this.feedURI,
        loadingPrincipal: Services.scriptSecurityManager.createCodebasePrincipal(this.feedURI, {}),
        securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
        contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST
      }).QueryInterface(Ci.nsIHttpChannel);
      channel.loadGroup = loadgroup;
      channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
                           Ci.nsIRequest.LOAD_BYPASS_CACHE;
      channel.requestMethod = "GET";
      channel.setRequestHeader("X-Moz", "livebookmarks", false);

      // Stream the result to the feed parser with this listener
      let listener = new LivemarkLoadListener(this);
      channel.notificationCallbacks = listener;
      channel.asyncOpen2(listener);

      this.loadGroup = loadgroup;
    } catch (ex) {
      this.status = Ci.mozILivemark.STATUS_FAILED;
    }
  },

  reload(aForceUpdate) {
    this.updateChildren(aForceUpdate);
  },

  get children() {
    return this._children;
  },
  set children(val) {
    this._children = val;

    // Discard the previous cached nodes, new ones should be generated.
    for (let container of this._resultObservers.keys()) {
      this._nodes.delete(container);
    }

    // Update visited status for each entry.
    for (let child of this._children) {
      asyncHistory.isURIVisited(child.uri, (aURI, aIsVisited) => {
        this.updateURIVisitedStatus(aURI, aIsVisited);
      });
    }

    return this._children;
  },

  _isURIVisited(aURI) {
    return this.children.some(child => child.uri.equals(aURI) && child.visited);
  },

  getNodesForContainer(aContainerNode) {
    if (this._nodes.has(aContainerNode)) {
      return this._nodes.get(aContainerNode);
    }

    let livemark = this;
    let nodes = [];
    let now = Date.now() * 1000;
    for (let child of this.children) {
      let node = {
        // The QueryInterface is needed cause aContainerNode is a jsval.
        // This is required to avoid issues with scriptable wrappers that would
        // not allow the view to correctly set expandos.
        get parent() {
          return aContainerNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
        },
        get parentResult() {
          return this.parent.parentResult;
        },
        get uri() {
          return child.uri.spec;
        },
        get type() {
          return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
        },
        get title() {
          return child.title;
        },
        get accessCount() {
          return Number(livemark._isURIVisited(NetUtil.newURI(this.uri)));
        },
        get time() {
          return 0;
        },
        get icon() {
          return "";
        },
        get indentLevel() {
          return this.parent.indentLevel + 1;
        },
        get bookmarkIndex() {
            return -1;
        },
        get itemId() {
            return -1;
        },
        get dateAdded() {
          return now;
        },
        get lastModified() {
          return now;
        },
        get tags() {
          return PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(this.uri)).join(", ");
        },
        QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryResultNode])
      };
      nodes.push(node);
    }
    this._nodes.set(aContainerNode, nodes);
    return nodes;
  },

  registerForUpdates(aContainerNode, aResultObserver) {
    this._resultObservers.set(aContainerNode, aResultObserver);
  },

  unregisterForUpdates(aContainerNode) {
    this._resultObservers.delete(aContainerNode);
    this._nodes.delete(aContainerNode);
  },

  _invalidateRegisteredContainers() {
    for (let [ container, observer ] of this._resultObservers) {
      observer.invalidateContainer(container);
    }
  },

  /**
   * Updates the visited status of nodes observing this livemark.
   *
   * @param aURI
   *        If provided will update nodes having the given uri,
   *        otherwise any node.
   * @param aVisitedStatus
   *        Whether the nodes should be set as visited.
   */
  updateURIVisitedStatus(aURI, aVisitedStatus) {
    let wasVisited = false;
    for (let child of this.children) {
      if (!aURI || child.uri.equals(aURI)) {
        wasVisited = child.visited;
        child.visited = aVisitedStatus;
      }
    }

    for (let [ container, observer ] of this._resultObservers) {
      if (this._nodes.has(container)) {
        let nodes = this._nodes.get(container);
        for (let node of nodes) {
          if (!aURI || node.uri == aURI.spec) {
            Services.tm.dispatchToMainThread(() => {
              observer.nodeHistoryDetailsChanged(node, node.time, wasVisited);
            });
          }
        }
      }
    }
  },

  /**
   * Terminates the livemark entry, cancelling any ongoing load.
   * Must be invoked before destroying the entry.
   */
  terminate() {
    // Avoid handling any updateChildren request from now on.
    this._terminated = true;
    this.abort();
  },

  /**
   * Aborts the livemark loading if needed.
   */
  abort() {
    this.status = Ci.mozILivemark.STATUS_FAILED;
    if (this.loadGroup) {
      this.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
      this.loadGroup = null;
    }
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.mozILivemark
  ])
}

// LivemarkLoadListener

/**
 * Object used internally to handle loading a livemark's contents.
 *
 * @param aLivemark
 *        The Livemark that is loading.
 */
function LivemarkLoadListener(aLivemark) {
  this._livemark = aLivemark;
  this._processor = null;
  this._isAborted = false;
  this._ttl = EXPIRE_TIME_MS;
}

LivemarkLoadListener.prototype = {
  abort(aException) {
    if (!this._isAborted) {
      this._isAborted = true;
      this._livemark.abort();
      this._setResourceTTL(ONERROR_EXPIRE_TIME_MS);
    }
  },

  // nsIFeedResultListener
  handleResult(aResult) {
    if (this._isAborted) {
      return;
    }

    try {
      // We need this to make sure the item links are safe
      let feedPrincipal =
        Services.scriptSecurityManager
                .createCodebasePrincipal(this._livemark.feedURI, {});

      // Enforce well-formedness because the existing code does
      if (!aResult || !aResult.doc || aResult.bozo) {
        throw new Components.Exception("", Cr.NS_ERROR_FAILURE);
      }

      let feed = aResult.doc.QueryInterface(Ci.nsIFeed);
      let siteURI = this._livemark.siteURI;
      if (feed.link && (!siteURI || !feed.link.equals(siteURI))) {
        siteURI = feed.link;
        this._livemark.writeSiteURI(siteURI);
      }

      // Insert feed items.
      let livemarkChildren = [];
      for (let i = 0; i < feed.items.length; ++i) {
        let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
        let uri = entry.link || siteURI;
        if (!uri) {
          continue;
        }

        try {
          Services.scriptSecurityManager
                  .checkLoadURIWithPrincipal(feedPrincipal, uri,
                                             Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
        } catch (ex) {
          continue;
        }

        let title = entry.title ? entry.title.plainText() : "";
        livemarkChildren.push({ uri, title, visited: false });
      }

      this._livemark.children = livemarkChildren;
    } catch (ex) {
      this.abort(ex);
    } finally {
      this._processor.listener = null;
      this._processor = null;
    }
  },

  onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount) {
    if (this._processor) {
      this._processor.onDataAvailable(aRequest, aContext, aInputStream,
                                      aSourceOffset, aCount);
    }
  },

  onStartRequest(aRequest, aContext) {
    if (this._isAborted) {
      throw new Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
    }

    let channel = aRequest.QueryInterface(Ci.nsIChannel);
    try {
      // Parse feed data as it comes in
      this._processor = Cc["@mozilla.org/feed-processor;1"].
                        createInstance(Ci.nsIFeedProcessor);
      this._processor.listener = this;
      this._processor.parseAsync(null, channel.URI);
      this._processor.onStartRequest(aRequest, aContext);
    } catch (ex) {
      Components.utils.reportError("Livemark Service: feed processor received an invalid channel for " + channel.URI.spec);
      this.abort(ex);
    }
  },

  onStopRequest(aRequest, aContext, aStatus) {
    if (!Components.isSuccessCode(aStatus)) {
      this.abort();
      return;
    }

    // Set an expiration on the livemark, to reloading the data in future.
    try {
      if (this._processor) {
        this._processor.onStopRequest(aRequest, aContext, aStatus);
      }

      // Calculate a new ttl
      let channel = aRequest.QueryInterface(Ci.nsICachingChannel);
      if (channel) {
        let entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntry);
        if (entryInfo) {
          // nsICacheEntry returns value as seconds.
          let expireTime = entryInfo.expirationTime * 1000;
          let nowTime = Date.now();
          // Note, expireTime can be 0, see bug 383538.
          if (expireTime > nowTime) {
            this._setResourceTTL(Math.max((expireTime - nowTime),
                                          EXPIRE_TIME_MS));
            return;
          }
        }
      }
      this._setResourceTTL(EXPIRE_TIME_MS);
    } catch (ex) {
      this.abort(ex);
    } finally {
      if (this._livemark.status == Ci.mozILivemark.STATUS_LOADING) {
        this._livemark.status = Ci.mozILivemark.STATUS_READY;
      }
      this._livemark.locked = false;
      this._livemark.loadGroup = null;
    }
  },

  _setResourceTTL(aMilliseconds) {
    this._livemark.expireTime = Date.now() + aMilliseconds;
  },

  // nsIInterfaceRequestor
  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },

  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIFeedResultListener,
    Ci.nsIStreamListener,
    Ci.nsIRequestObserver,
    Ci.nsIInterfaceRequestor
  ])
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LivemarkService]);
PK
!<F#39Y9Ycomponents/nsTaggingService.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/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                  "resource://gre/modules/Deprecated.jsm");

const TOPIC_SHUTDOWN = "places-shutdown";

/**
 * The Places Tagging Service
 */
function TaggingService() {
  // Observe bookmarks changes.
  PlacesUtils.bookmarks.addObserver(this);

  // Cleanup on shutdown.
  Services.obs.addObserver(this, TOPIC_SHUTDOWN);
}

TaggingService.prototype = {
  /**
   * Creates a tag container under the tags-root with the given name.
   *
   * @param aTagName
   *        the name for the new tag.
   * @param aSource
   *        a change source constant from nsINavBookmarksService::SOURCE_*.
   * @returns the id of the new tag container.
   */
  _createTag: function TS__createTag(aTagName, aSource) {
    var newFolderId = PlacesUtils.bookmarks.createFolder(
      PlacesUtils.tagsFolderId, aTagName, PlacesUtils.bookmarks.DEFAULT_INDEX,
      /* aGuid */ null, aSource
    );
    // Add the folder to our local cache, so we can avoid doing this in the
    // observer that would have to check itemType.
    this._tagFolders[newFolderId] = aTagName;

    return newFolderId;
  },

  /**
   * Checks whether the given uri is tagged with the given tag.
   *
   * @param [in] aURI
   *        url to check for
   * @param [in] aTagName
   *        the tag to check for
   * @returns the item id if the URI is tagged with the given tag, -1
   *          otherwise.
   */
  _getItemIdForTaggedURI: function TS__getItemIdForTaggedURI(aURI, aTagName) {
    var tagId = this._getItemIdForTag(aTagName);
    if (tagId == -1)
      return -1;
    // Using bookmarks service API for this would be a pain.
    // Until tags implementation becomes sane, go the query way.
    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                .DBConnection;
    let stmt = db.createStatement(
      `SELECT id FROM moz_bookmarks
       WHERE parent = :tag_id
       AND fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
    );
    stmt.params.tag_id = tagId;
    stmt.params.page_url = aURI.spec;
    try {
      if (stmt.executeStep()) {
        return stmt.row.id;
      }
    } finally {
      stmt.finalize();
    }
    return -1;
  },

  /**
   * Returns the folder id for a tag, or -1 if not found.
   * @param [in] aTag
   *        string tag to search for
   * @returns integer id for the bookmark folder for the tag
   */
  _getItemIdForTag: function TS_getItemIdForTag(aTagName) {
    for (var i in this._tagFolders) {
      if (aTagName.toLowerCase() == this._tagFolders[i].toLowerCase())
        return parseInt(i);
    }
    return -1;
  },

  /**
   * Makes a proper array of tag objects like  { id: number, name: string }.
   *
   * @param aTags
   *        Array of tags.  Entries can be tag names or concrete item id.
   * @param trim [optional]
   *        Whether to trim passed-in named tags. Defaults to false.
   * @return Array of tag objects like { id: number, name: string }.
   *
   * @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
   *         a valid tag.
   */
  _convertInputMixedTagsArray(aTags, trim = false) {
    // Handle sparse array with a .filter.
    return aTags.filter(tag => tag !== undefined)
                .map(idOrName => {
      let tag = {};
      if (typeof(idOrName) == "number" && this._tagFolders[idOrName]) {
        // This is a tag folder id.
        tag.id = idOrName;
        // We can't know the name at this point, since a previous tag could
        // want to change it.
        tag.__defineGetter__("name", () => this._tagFolders[tag.id]);
      } else if (typeof(idOrName) == "string" && idOrName.length > 0 &&
               idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
        // This is a tag name.
        tag.name = trim ? idOrName.trim() : idOrName;
        // We can't know the id at this point, since a previous tag could
        // have created it.
        tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
      } else {
        throw Components.Exception("Invalid tag value", Cr.NS_ERROR_INVALID_ARG);
      }
      return tag;
    });
  },

  // nsITaggingService
  tagURI: function TS_tagURI(aURI, aTags, aSource) {
    if (!aURI || !aTags || !Array.isArray(aTags) || aTags.length == 0) {
      throw Components.Exception("Invalid value for tags", Cr.NS_ERROR_INVALID_ARG);
    }

    // This also does some input validation.
    let tags = this._convertInputMixedTagsArray(aTags, true);

    let taggingFunction = () => {
      for (let tag of tags) {
        if (tag.id == -1) {
          // Tag does not exist yet, create it.
          this._createTag(tag.name, aSource);
        }

        let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
        if (itemId == -1) {
          // The provided URI is not yet tagged, add a tag for it.
          // Note that bookmarks under tag containers must have null titles.
          PlacesUtils.bookmarks.insertBookmark(
            tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
            /* aTitle */ null, /* aGuid */ null, aSource
          );
        } else {
          // Otherwise, bump the tag's timestamp, so that we can increment the
          // sync change counter for all bookmarks with the URI.
          PlacesUtils.bookmarks.setItemLastModified(itemId,
            PlacesUtils.toPRTime(Date.now()), aSource);
        }

        // Try to preserve user's tag name casing.
        // Rename the tag container so the Places view matches the most-recent
        // user-typed value.
        if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
          // this._tagFolders is updated by the bookmarks observer.
          PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name, aSource);
        }
      }
    };

    // Use a batch only if creating more than 2 tags.
    if (tags.length < 3) {
      taggingFunction();
    } else {
      PlacesUtils.bookmarks.runInBatchMode(taggingFunction, null);
    }
  },

  /**
   * Removes the tag container from the tags root if the given tag is empty.
   *
   * @param aTagId
   *        the itemId of the tag element under the tags root
   * @param aSource
   *        a change source constant from nsINavBookmarksService::SOURCE_*
   */
  _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId, aSource) {
    let count = 0;
    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                .DBConnection;
    let stmt = db.createStatement(
      `SELECT count(*) AS count FROM moz_bookmarks
       WHERE parent = :tag_id`
    );
    stmt.params.tag_id = aTagId;
    try {
      if (stmt.executeStep()) {
        count = stmt.row.count;
      }
    } finally {
      stmt.finalize();
    }

    if (count == 0) {
      PlacesUtils.bookmarks.removeItem(aTagId, aSource);
    }
  },

  // nsITaggingService
  untagURI: function TS_untagURI(aURI, aTags, aSource) {
    if (!aURI || (aTags && (!Array.isArray(aTags) || aTags.length == 0))) {
      throw Components.Exception("Invalid value for tags", Cr.NS_ERROR_INVALID_ARG);
    }

    if (!aTags) {
      // Passing null should clear all tags for aURI, see the IDL.
      // XXXmano: write a perf-sensitive version of this code path...
      aTags = this.getTagsForURI(aURI);
    }

    // This also does some input validation.
    let tags = this._convertInputMixedTagsArray(aTags);

    let isAnyTagNotTrimmed = tags.some(tag => /^\s|\s$/.test(tag.name));
    if (isAnyTagNotTrimmed) {
      Deprecated.warning("At least one tag passed to untagURI was not trimmed",
                         "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
    }

    let untaggingFunction = () => {
      for (let tag of tags) {
        if (tag.id != -1) {
          // A tag could exist.
          let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
          if (itemId != -1) {
            // There is a tagged item.
            PlacesUtils.bookmarks.removeItem(itemId, aSource);
          }
        }
      }
    };

    // Use a batch only if creating more than 2 tags.
    if (tags.length < 3) {
      untaggingFunction();
    } else {
      PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null);
    }
  },

  // nsITaggingService
  getURIsForTag: function TS_getURIsForTag(aTagName) {
    if (!aTagName || aTagName.length == 0) {
      throw Components.Exception("Invalid tag name", Cr.NS_ERROR_INVALID_ARG);
    }

    if (/^\s|\s$/.test(aTagName)) {
      Deprecated.warning("Tag passed to getURIsForTag was not trimmed",
                         "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
    }

    let uris = [];
    let tagId = this._getItemIdForTag(aTagName);
    if (tagId == -1)
      return uris;

    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                .DBConnection;
    let stmt = db.createStatement(
      `SELECT h.url FROM moz_places h
       JOIN moz_bookmarks b ON b.fk = h.id
       WHERE b.parent = :tag_id`
    );
    stmt.params.tag_id = tagId;
    try {
      while (stmt.executeStep()) {
        try {
          uris.push(Services.io.newURI(stmt.row.url));
        } catch (ex) {}
      }
    } finally {
      stmt.finalize();
    }

    return uris;
  },

  // nsITaggingService
  getTagsForURI: function TS_getTagsForURI(aURI, aCount) {
    if (!aURI) {
      throw Components.Exception("Invalid uri", Cr.NS_ERROR_INVALID_ARG);
    }

    let tags = [];
    let db = PlacesUtils.history.DBConnection;
    let stmt = db.createStatement(
      `SELECT t.id AS folderId
       FROM moz_bookmarks b
       JOIN moz_bookmarks t on t.id = b.parent
       WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) AND
       t.parent = :tags_root
       ORDER BY b.lastModified DESC, b.id DESC`
    );
    stmt.params.url = aURI.spec;
    stmt.params.tags_root = PlacesUtils.tagsFolderId;
    try {
      while (stmt.executeStep()) {
        try {
          tags.push(this._tagFolders[stmt.row.folderId]);
        } catch (ex) {}
      }
    } finally {
      stmt.finalize();
    }

    // sort the tag list
    tags.sort(function(a, b) {
        return a.toLowerCase().localeCompare(b.toLowerCase());
      });
    if (aCount) {
      aCount.value = tags.length;
    }
    return tags;
  },

  __tagFolders: null,
  get _tagFolders() {
    if (!this.__tagFolders) {
      this.__tagFolders = [];

      let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                  .DBConnection;
      let stmt = db.createStatement(
        "SELECT id, title FROM moz_bookmarks WHERE parent = :tags_root "
      );
      stmt.params.tags_root = PlacesUtils.tagsFolderId;
      try {
        while (stmt.executeStep()) {
          this.__tagFolders[stmt.row.id] = stmt.row.title;
        }
      } finally {
        stmt.finalize();
      }
    }

    return this.__tagFolders;
  },

  // nsITaggingService
  get allTags() {
    var allTags = [];
    for (var i in this._tagFolders)
      allTags.push(this._tagFolders[i]);
    // sort the tag list
    allTags.sort(function(a, b) {
        return a.toLowerCase().localeCompare(b.toLowerCase());
      });
    return allTags;
  },

  // nsITaggingService
  get hasTags() {
    return this._tagFolders.length > 0;
  },

  // nsIObserver
  observe: function TS_observe(aSubject, aTopic, aData) {
    if (aTopic == TOPIC_SHUTDOWN) {
      PlacesUtils.bookmarks.removeObserver(this);
      Services.obs.removeObserver(this, TOPIC_SHUTDOWN);
    }
  },

  /**
   * If the only bookmark items associated with aURI are contained in tag
   * folders, returns the IDs of those items.  This can be the case if
   * the URI was bookmarked and tagged at some point, but the bookmark was
   * removed, leaving only the bookmark items in tag folders.  If the URI is
   * either properly bookmarked or not tagged just returns and empty array.
   *
   * @param   aURI
   *          A URI (string) that may or may not be bookmarked
   * @returns an array of item ids
   */
  _getTaggedItemIdsIfUnbookmarkedURI:
  function TS__getTaggedItemIdsIfUnbookmarkedURI(aURI) {
    var itemIds = [];
    var isBookmarked = false;

    // Using bookmarks service API for this would be a pain.
    // Until tags implementation becomes sane, go the query way.
    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                .DBConnection;
    let stmt = db.createStatement(
      `SELECT id, parent
       FROM moz_bookmarks
       WHERE fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
    );
    stmt.params.page_url = aURI.spec;
    try {
      while (stmt.executeStep() && !isBookmarked) {
        if (this._tagFolders[stmt.row.parent]) {
          // This is a tag entry.
          itemIds.push(stmt.row.id);
        } else {
          // This is a real bookmark, so the bookmarked URI is not an orphan.
          isBookmarked = true;
        }
      }
    } finally {
      stmt.finalize();
    }

    return isBookmarked ? [] : itemIds;
  },

  // nsINavBookmarkObserver
  onItemAdded: function TS_onItemAdded(aItemId, aFolderId, aIndex, aItemType,
                                       aURI, aTitle) {
    // Nothing to do if this is not a tag.
    if (aFolderId != PlacesUtils.tagsFolderId ||
        aItemType != PlacesUtils.bookmarks.TYPE_FOLDER)
      return;

    this._tagFolders[aItemId] = aTitle;
  },

  onItemRemoved: function TS_onItemRemoved(aItemId, aFolderId, aIndex,
                                           aItemType, aURI, aGuid, aParentGuid,
                                           aSource) {
    // Item is a tag folder.
    if (aFolderId == PlacesUtils.tagsFolderId && this._tagFolders[aItemId]) {
      delete this._tagFolders[aItemId];
    } else if (aURI && !this._tagFolders[aFolderId]) {
      // Item is a bookmark that was removed from a non-tag folder.
      // If the only bookmark items now associated with the bookmark's URI are
      // contained in tag folders, the URI is no longer properly bookmarked, so
      // untag it.
      let itemIds = this._getTaggedItemIdsIfUnbookmarkedURI(aURI);
      for (let i = 0; i < itemIds.length; i++) {
        try {
          PlacesUtils.bookmarks.removeItem(itemIds[i], aSource);
        } catch (ex) {}
      }
    } else if (aURI && this._tagFolders[aFolderId]) {
      // Item is a tag entry.  If this was the last entry for this tag, remove it.
      this._removeTagIfEmpty(aFolderId, aSource);
    }
  },

  onItemChanged: function TS_onItemChanged(aItemId, aProperty,
                                           aIsAnnotationProperty, aNewValue,
                                           aLastModified, aItemType) {
    if (aProperty == "title" && this._tagFolders[aItemId])
      this._tagFolders[aItemId] = aNewValue;
  },

  onItemMoved: function TS_onItemMoved(aItemId, aOldParent, aOldIndex,
                                      aNewParent, aNewIndex, aItemType) {
    if (this._tagFolders[aItemId] && PlacesUtils.tagsFolderId == aOldParent &&
        PlacesUtils.tagsFolderId != aNewParent)
      delete this._tagFolders[aItemId];
  },

  onItemVisited() {},
  onBeginUpdateBatch() {},
  onEndUpdateBatch() {},

  // nsISupports

  classID: Components.ID("{bbc23860-2553-479d-8b78-94d9038334f7}"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(TaggingService),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsITaggingService,
    Ci.nsINavBookmarkObserver,
    Ci.nsIObserver
  ])
};


function TagAutoCompleteResult(searchString, searchResult,
                               defaultIndex, errorDescription,
                               results, comments) {
  this._searchString = searchString;
  this._searchResult = searchResult;
  this._defaultIndex = defaultIndex;
  this._errorDescription = errorDescription;
  this._results = results;
  this._comments = comments;
}

TagAutoCompleteResult.prototype = {

  /**
   * The original search string
   */
  get searchString() {
    return this._searchString;
  },

  /**
   * The result code of this result object, either:
   *         RESULT_IGNORED   (invalid searchString)
   *         RESULT_FAILURE   (failure)
   *         RESULT_NOMATCH   (no matches found)
   *         RESULT_SUCCESS   (matches found)
   */
  get searchResult() {
    return this._searchResult;
  },

  /**
   * Index of the default item that should be entered if none is selected
   */
  get defaultIndex() {
    return this._defaultIndex;
  },

  /**
   * A string describing the cause of a search failure
   */
  get errorDescription() {
    return this._errorDescription;
  },

  /**
   * The number of matches
   */
  get matchCount() {
    return this._results.length;
  },

  /**
   * Get the value of the result at the given index
   */
  getValueAt: function PTACR_getValueAt(index) {
    return this._results[index];
  },

  getLabelAt: function PTACR_getLabelAt(index) {
    return this.getValueAt(index);
  },

  /**
   * Get the comment of the result at the given index
   */
  getCommentAt: function PTACR_getCommentAt(index) {
    return this._comments[index];
  },

  /**
   * Get the style hint for the result at the given index
   */
  getStyleAt: function PTACR_getStyleAt(index) {
    if (!this._comments[index])
      return null;  // not a category label, so no special styling

    if (index == 0)
      return "suggestfirst";  // category label on first line of results

    return "suggesthint";   // category label on any other line of results
  },

  /**
   * Get the image for the result at the given index
   */
  getImageAt: function PTACR_getImageAt(index) {
    return null;
  },

  /**
   * Get the image for the result at the given index
   */
  getFinalCompleteValueAt: function PTACR_getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  },

  /**
   * Remove the value at the given index from the autocomplete results.
   * If removeFromDb is set to true, the value should be removed from
   * persistent storage as well.
   */
  removeValueAt: function PTACR_removeValueAt(index, removeFromDb) {
    this._results.splice(index, 1);
    this._comments.splice(index, 1);
  },

  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIAutoCompleteResult
  ])
};

// Implements nsIAutoCompleteSearch
function TagAutoCompleteSearch() {
  XPCOMUtils.defineLazyServiceGetter(this, "tagging",
                                     "@mozilla.org/browser/tagging-service;1",
                                     "nsITaggingService");
}

TagAutoCompleteSearch.prototype = {
  _stopped: false,

  /*
   * Search for a given string and notify a listener (either synchronously
   * or asynchronously) of the result
   *
   * @param searchString - The string to search for
   * @param searchParam - An extra parameter
   * @param previousResult - A previous result to use for faster searching
   * @param listener - A listener to notify when the search is complete
   */
  startSearch: function PTACS_startSearch(searchString, searchParam, result, listener) {
    var searchResults = this.tagging.allTags;
    var results = [];
    var comments = [];
    this._stopped = false;

    // only search on characters for the last tag
    var index = Math.max(searchString.lastIndexOf(","),
      searchString.lastIndexOf(";"));
    var before = "";
    if (index != -1) {
      before = searchString.slice(0, index + 1);
      searchString = searchString.slice(index + 1);
      // skip past whitespace
      var m = searchString.match(/\s+/);
      if (m) {
         before += m[0];
         searchString = searchString.slice(m[0].length);
      }
    }

    if (!searchString.length) {
      var newResult = new TagAutoCompleteResult(searchString,
        Ci.nsIAutoCompleteResult.RESULT_NOMATCH, 0, "", results, comments);
      listener.onSearchResult(self, newResult);
      return;
    }

    var self = this;
    // generator: if yields true, not done
    function* doSearch() {
      var i = 0;
      while (i < searchResults.length) {
        if (self._stopped)
          yield false;
        // for each match, prepend what the user has typed so far
        if (searchResults[i].toLowerCase()
                            .indexOf(searchString.toLowerCase()) == 0 &&
            !comments.includes(searchResults[i])) {
          results.push(before + searchResults[i]);
          comments.push(searchResults[i]);
        }

        ++i;

        /* TODO: bug 481451
         * For each yield we pass a new result to the autocomplete
         * listener. The listener appends instead of replacing previous results,
         * causing invalid matchCount values.
         *
         * As a workaround, all tags are searched through in a single batch,
         * making this synchronous until the above issue is fixed.
         */

        /*
        // 100 loops per yield
        if ((i % 100) == 0) {
          var newResult = new TagAutoCompleteResult(searchString,
            Ci.nsIAutoCompleteResult.RESULT_SUCCESS_ONGOING, 0, "", results, comments);
          listener.onSearchResult(self, newResult);
          yield true;
        }
        */
      }

      let searchResult = results.length > 0 ?
                           Ci.nsIAutoCompleteResult.RESULT_SUCCESS :
                           Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
      var newResult = new TagAutoCompleteResult(searchString, searchResult, 0,
                                                "", results, comments);
      listener.onSearchResult(self, newResult);
      yield false;
    }

    // chunk the search results via the generator
    var gen = doSearch();
    while (gen.next().value);
  },

  /**
   * Stop an asynchronous search that is in progress
   */
  stopSearch: function PTACS_stopSearch() {
    this._stopped = true;
  },

  // nsISupports

  classID: Components.ID("{1dcc23b0-d4cb-11dc-9ad6-479d56d89593}"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(TagAutoCompleteSearch),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIAutoCompleteSearch
  ])
};

var component = [TaggingService, TagAutoCompleteSearch];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<Z^	W	Wcomponents/UnifiedComplete.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// Constants

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000

// Match type constants.
// These indicate what type of search function we should be using.
const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;

const PREF_BRANCH = "browser.urlbar.";

// Prefs are defined as [pref name, default value].
const PREF_ENABLED =                [ "autocomplete.enabled",   true ];
const PREF_AUTOFILL =               [ "autoFill",               true ];
const PREF_AUTOFILL_TYPED =         [ "autoFill.typed",         true ];
const PREF_AUTOFILL_SEARCHENGINES = [ "autoFill.searchEngines", false ];
const PREF_RESTYLESEARCHES        = [ "restyleSearches",        false ];
const PREF_DELAY =                  [ "delay",                  50 ];
const PREF_BEHAVIOR =               [ "matchBehavior", MATCH_BOUNDARY_ANYWHERE ];
const PREF_FILTER_JS =              [ "filter.javascript",      true ];
const PREF_MAXRESULTS =             [ "maxRichResults",         25 ];

const PREF_SUGGEST_HISTORY =        [ "suggest.history",        true ];
const PREF_SUGGEST_BOOKMARK =       [ "suggest.bookmark",       true ];
const PREF_SUGGEST_OPENPAGE =       [ "suggest.openpage",       true ];
const PREF_SUGGEST_HISTORY_ONLYTYPED = [ "suggest.history.onlyTyped", false ];
const PREF_SUGGEST_SEARCHES =       [ "suggest.searches",       false ];

const PREF_MAX_CHARS_FOR_SUGGEST =  [ "maxCharsForSearchSuggestions", 20];
const PREF_MAX_HISTORICAL_SUGGESTIONS =  [ "maxHistoricalSearchSuggestions", 0];

const PREF_PRELOADED_SITES_ENABLED =  [ "usepreloadedtopurls.enabled",   true ];
const PREF_PRELOADED_SITES_EXPIRE_DAYS = [ "usepreloadedtopurls.expire_days",  14 ];

const PREF_MATCH_BUCKETS = [ "matchBuckets", "general:5,suggestion:Infinity" ];
// Will default to matchBuckets if not defined.
const PREF_MATCH_BUCKETS_SEARCH = [ "matchBucketsSearch", "" ];

// AutoComplete query type constants.
// Describes the various types of queries that we can process rows for.
const QUERYTYPE_FILTERED            = 0;
const QUERYTYPE_AUTOFILL_HOST       = 1;
const QUERYTYPE_AUTOFILL_URL        = 2;

// This separator is used as an RTL-friendly way to split the title and tags.
// It can also be used by an nsIAutoCompleteResult consumer to re-split the
// "comment" back into the title and the tag.
const TITLE_TAGS_SEPARATOR = " \u2013 ";

// Telemetry probes.
const TELEMETRY_1ST_RESULT = "PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS";
const TELEMETRY_6_FIRST_RESULTS = "PLACES_AUTOCOMPLETE_6_FIRST_RESULTS_TIME_MS";
// The default frecency value used when inserting matches with unknown frecency.
const FRECENCY_DEFAULT = 1000;

// Extensions are allowed to add suggestions if they have registered a keyword
// with the omnibox API. This is the maximum number of suggestions an extension
// is allowed to add for a given search string.
// This value includes the heuristic result.
const MAXIMUM_ALLOWED_EXTENSION_MATCHES = 6;

// After this time, we'll give up waiting for the extension to return matches.
const MAXIMUM_ALLOWED_EXTENSION_TIME_MS = 5000;

// A regex that matches "single word" hostnames for whitelisting purposes.
// The hostname will already have been checked for general validity, so we
// don't need to be exhaustive here, so allow dashes anywhere.
const REGEXP_SINGLEWORD_HOST = new RegExp("^[a-z0-9-]+$", "i");

// Regex used to match userContextId.
const REGEXP_USER_CONTEXT_ID = /(?:^| )user-context-id:(\d+)/;

// Regex used to match one or more whitespace.
const REGEXP_SPACES = /\s+/;

// Sqlite result row index constants.
const QUERYINDEX_QUERYTYPE     = 0;
const QUERYINDEX_URL           = 1;
const QUERYINDEX_TITLE         = 2;
const QUERYINDEX_BOOKMARKED    = 3;
const QUERYINDEX_BOOKMARKTITLE = 4;
const QUERYINDEX_TAGS          = 5;
const QUERYINDEX_VISITCOUNT    = 6;
const QUERYINDEX_TYPED         = 7;
const QUERYINDEX_PLACEID       = 8;
const QUERYINDEX_SWITCHTAB     = 9;
const QUERYINDEX_FRECENCY      = 10;

// 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.
const TOKEN_TO_BEHAVIOR_MAP = new Map([
  ["^", "history"],
  ["*", "bookmark"],
  ["+", "tag"],
  ["%", "openpage"],
  ["~", "typed"],
  ["$", "searches"],
  ["#", "title"],
  ["@", "url"],
]);

const MATCHTYPE = {
  HEURISTIC: "heuristic",
  GENERAL: "general",
  SUGGESTION: "suggestion",
  EXTENSION: "extension"
};

// Buckets for match insertion.
// Every time a new match is returned, we go through each bucket in array order,
// and look for the first one having available space for the given match type.
// Each bucket is an array containing the following indices:
//   0: The match type of the acceptable entries.
//   1: available number of slots in this bucket.
// There are different matchBuckets definition for different contexts, currently
// a general one (_matchBuckets) and a search one (_matchBucketsSearch).
//
// First buckets. Anything with an Infinity frecency ends up here.
const DEFAULT_BUCKETS_BEFORE = [
  [MATCHTYPE.HEURISTIC, 1],
  [MATCHTYPE.EXTENSION, MAXIMUM_ALLOWED_EXTENSION_MATCHES - 1],
];
// => USER DEFINED BUCKETS WILL BE INSERTED HERE <=
//
// Catch-all buckets. Anything remaining ends up here.
const DEFAULT_BUCKETS_AFTER = [
  [MATCHTYPE.SUGGESTION, Infinity],
  [MATCHTYPE.GENERAL, Infinity],
];

// If a URL starts with one of these prefixes, then we don't provide search
// suggestions for it.
const DISALLOWED_URLLIKE_PREFIXES = [
  "http", "https", "ftp"
];

// This SQL query fragment provides the following:
//   - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
//   - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE)
//   - the tags associated with a bookmarked entry (QUERYINDEX_TAGS)
const SQL_BOOKMARK_TAGS_FRAGMENT =
  `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
   ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
     ORDER BY lastModified DESC LIMIT 1
   ) AS btitle,
   ( SELECT GROUP_CONCAT(t.title, ', ')
     FROM moz_bookmarks b
     JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
     WHERE b.fk = h.id
   ) AS tags`;

// TODO bug 412736: in case of a frecency tie, we might break it with h.typed
// and h.visit_count.  That is slower though, so not doing it yet...
// NB: as a slight performance optimization, we only evaluate the "btitle"
// and "tags" queries for bookmarked entries.
function defaultQuery(conditions = "") {
  let query =
    `SELECT :query_type, h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT},
            h.visit_count, h.typed, h.id, t.open_count, h.frecency
     FROM moz_places h
     LEFT JOIN moz_openpages_temp t
            ON t.url = h.url
           AND t.userContextId = :userContextId
     WHERE h.frecency <> 0
       AND AUTOCOMPLETE_MATCH(:searchString, h.url,
                              CASE WHEN bookmarked THEN
                                IFNULL(btitle, h.title)
                              ELSE h.title END,
                              CASE WHEN bookmarked THEN
                                tags
                              ELSE '' END,
                              h.visit_count, h.typed,
                              bookmarked, t.open_count,
                              :matchBehavior, :searchBehavior)
       ${conditions}
     ORDER BY h.frecency DESC, h.id DESC
     LIMIT :maxResults`;
  return query;
}

const SQL_SWITCHTAB_QUERY =
  `SELECT :query_type, t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL,
          t.open_count, NULL
   FROM moz_openpages_temp t
   LEFT JOIN moz_places h ON h.url_hash = hash(t.url) AND h.url = t.url
   WHERE h.id IS NULL
     AND t.userContextId = :userContextId
     AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
                            NULL, NULL, NULL, t.open_count,
                            :matchBehavior, :searchBehavior)
   ORDER BY t.ROWID DESC
   LIMIT :maxResults`;

const SQL_ADAPTIVE_QUERY =
  `/* do not warn (bug 487789) */
   SELECT :query_type, h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT},
          h.visit_count, h.typed, h.id, t.open_count, h.frecency
   FROM (
     SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank,
            place_id
     FROM moz_inputhistory
     WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
     GROUP BY place_id
   ) AS i
   JOIN moz_places h ON h.id = i.place_id
   LEFT JOIN moz_openpages_temp t
          ON t.url = h.url
         AND t.userContextId = :userContextId
   WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
                            IFNULL(btitle, h.title), tags,
                            h.visit_count, h.typed, bookmarked,
                            t.open_count,
                            :matchBehavior, :searchBehavior)
   ORDER BY rank DESC, h.frecency DESC`;


function hostQuery(conditions = "") {
  let query =
    `/* do not warn (bug NA): not worth to index on (typed, frecency) */
     SELECT :query_type, host || '/', IFNULL(prefix, 'http://') || host || '/',
            NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency
     FROM moz_hosts
     WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
     AND frecency <> 0
     ${conditions}
     ORDER BY frecency DESC
     LIMIT 1`;
  return query;
}

const SQL_HOST_QUERY = hostQuery();

const SQL_TYPED_HOST_QUERY = hostQuery("AND typed = 1");

function bookmarkedHostQuery(conditions = "") {
  let query =
    `/* do not warn (bug NA): not worth to index on (typed, frecency) */
     SELECT :query_type, host || '/', IFNULL(prefix, 'http://') || host || '/',
            ( SELECT foreign_count > 0 FROM moz_places
              WHERE rev_host = get_unreversed_host(host || '.') || '.'
                 OR rev_host = get_unreversed_host(host || '.') || '.www.'
            ) AS bookmarked, NULL, NULL, NULL, NULL, NULL, NULL, frecency
     FROM moz_hosts
     WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
     AND bookmarked
     AND frecency <> 0
     ${conditions}
     ORDER BY frecency DESC
     LIMIT 1`;
  return query;
}

const SQL_BOOKMARKED_HOST_QUERY = bookmarkedHostQuery();

const SQL_BOOKMARKED_TYPED_HOST_QUERY = bookmarkedHostQuery("AND typed = 1");

function urlQuery(conditions = "") {
  return `/* do not warn (bug no): cannot use an index to sort */
          SELECT :query_type, h.url, NULL,
            foreign_count > 0 AS bookmarked,
            NULL, NULL, NULL, NULL, NULL, NULL, h.frecency
          FROM moz_places h
          WHERE (rev_host = :revHost OR rev_host = :revHost || "www.")
          AND h.frecency <> 0
          AND fixup_url(h.url) BETWEEN :searchString AND :searchString || X'FFFF'
          ${conditions}
          ORDER BY h.frecency DESC, h.id DESC
          LIMIT 1`;
}

const SQL_URL_QUERY = urlQuery();

const SQL_TYPED_URL_QUERY = urlQuery("AND h.typed = 1");

// TODO (bug 1045924): use foreign_count once available.
const SQL_BOOKMARKED_URL_QUERY = urlQuery("AND bookmarked");

const SQL_BOOKMARKED_TYPED_URL_QUERY = urlQuery("AND bookmarked AND h.typed = 1");

// Getters

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

Cu.importGlobalProperties(["fetch"]);

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                  "resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
                                  "resource://gre/modules/ExtensionSearchHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
                                  "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesRemoteTabsAutocompleteProvider",
                                  "resource://gre/modules/PlacesRemoteTabsAutocompleteProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
                                  "resource://gre/modules/ProfileAge.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "textURIService",
                                   "@mozilla.org/intl/texttosuburi;1",
                                   "nsITextToSubURI");

function setTimeout(callback, ms) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(callback, ms, timer.TYPE_ONE_SHOT);
}

function convertBucketsCharPrefToArray(str) {
  return str.toLowerCase()
            .split(",")
            .map(v => {
              let bucket = v.split(":");
              return [ bucket[0].trim(), Number(bucket[1]) ];
            });
}

/**
 * Storage object for switch-to-tab entries.
 * This takes care of caching and registering open pages, that will be reused
 * by switch-to-tab queries.  It has an internal cache, so that the Sqlite
 * store is lazy initialized only on first use.
 * It has a simple API:
 *   initDatabase(conn): initializes the temporary Sqlite entities to store data
 *   add(uri): adds a given nsIURI to the store
 *   delete(uri): removes a given nsIURI from the store
 *   shutdown(): stops storing data to Sqlite
 */
XPCOMUtils.defineLazyGetter(this, "SwitchToTabStorage", () => Object.seal({
  _conn: null,
  // Temporary queue used while the database connection is not available.
  _queue: new Map(),
  async initDatabase(conn) {
    // To reduce IO use an in-memory table for switch-to-tab tracking.
    // Note: this should be kept up-to-date with the definition in
    //       nsPlacesTables.h.
    await conn.execute(
      `CREATE TEMP TABLE moz_openpages_temp (
         url TEXT,
         userContextId INTEGER,
         open_count INTEGER,
         PRIMARY KEY (url, userContextId)
       )`);

    // Note: this should be kept up-to-date with the definition in
    //       nsPlacesTriggers.h.
    await conn.execute(
      `CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger
       AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
       WHEN NEW.open_count = 0
       BEGIN
         DELETE FROM moz_openpages_temp
         WHERE url = NEW.url
           AND userContextId = NEW.userContextId;
       END`);

    this._conn = conn;

    // Populate the table with the current cache contents...
    for (let [userContextId, uris] of this._queue) {
      for (let uri of uris) {
        this.add(uri, userContextId);
      }
    }

    // ...then clear it to avoid double additions.
    this._queue.clear();
  },

  add(uri, userContextId) {
    if (!this._conn) {
      if (!this._queue.has(userContextId)) {
        this._queue.set(userContextId, new Set());
      }
      this._queue.get(userContextId).add(uri);
      return;
    }
    this._conn.executeCached(
      `INSERT OR REPLACE INTO moz_openpages_temp (url, userContextId, open_count)
         VALUES ( :url,
                  :userContextId,
                  IFNULL( ( SELECT open_count + 1
                            FROM moz_openpages_temp
                            WHERE url = :url
                            AND userContextId = :userContextId ),
                          1
                        )
                )`
    , { url: uri.spec, userContextId });
  },

  delete(uri, userContextId) {
    if (!this._conn) {
      // This should not happen.
      if (!this._queue.has(userContextId)) {
        throw new Error("Unknown userContextId!");
      }

      this._queue.get(userContextId).delete(uri);
      if (this._queue.get(userContextId).size == 0) {
        this._queue.delete(userContextId);
      }
      return;
    }
    this._conn.executeCached(
      `UPDATE moz_openpages_temp
       SET open_count = open_count - 1
       WHERE url = :url
       AND userContextId = :userContextId`
    , { url: uri.spec, userContextId });
  },

  shutdown() {
    this._conn = null;
    this._queue.clear();
  }
}));

/**
 * This helper keeps track of preferences and keeps their values up-to-date.
 */
XPCOMUtils.defineLazyGetter(this, "Prefs", () => {
  let prefs = new Preferences(PREF_BRANCH);
  let types = ["History", "Bookmark", "Openpage", "Searches"];

  function syncEnabledPref() {
    loadSyncedPrefs();

    let suggestPrefs = [
      PREF_SUGGEST_HISTORY,
      PREF_SUGGEST_BOOKMARK,
      PREF_SUGGEST_OPENPAGE,
      PREF_SUGGEST_SEARCHES,
    ];

    if (store.enabled) {
      // If the autocomplete preference is active, set to default value all suggest
      // preferences only if all of them are false.
      if (types.every(type => store["suggest" + type] == false)) {
        for (let type of suggestPrefs) {
          prefs.set(...type);
        }
      }
    } else {
      // If the preference was deactivated, deactivate all suggest preferences.
      for (let type of suggestPrefs) {
        prefs.set(type[0], false);
      }
    }
  }

  function loadSyncedPrefs() {
    store.enabled = prefs.get(...PREF_ENABLED);
    store.suggestHistory = prefs.get(...PREF_SUGGEST_HISTORY);
    store.suggestBookmark = prefs.get(...PREF_SUGGEST_BOOKMARK);
    store.suggestOpenpage = prefs.get(...PREF_SUGGEST_OPENPAGE);
    store.suggestTyped = prefs.get(...PREF_SUGGEST_HISTORY_ONLYTYPED);
    store.suggestSearches = prefs.get(...PREF_SUGGEST_SEARCHES);
  }

  function loadPrefs(subject, topic, data) {
    if (data) {
      // Synchronize suggest.* prefs with autocomplete.enabled.
      if (data == PREF_BRANCH + PREF_ENABLED[0]) {
        syncEnabledPref();
      } else if (data.startsWith(PREF_BRANCH + "suggest.")) {
        loadSyncedPrefs();
        prefs.set(PREF_ENABLED[0], types.some(type => store["suggest" + type]));
      }
    }

    store.enabled = prefs.get(...PREF_ENABLED);
    store.autofill = prefs.get(...PREF_AUTOFILL);
    store.autofillTyped = prefs.get(...PREF_AUTOFILL_TYPED);
    store.autofillSearchEngines = prefs.get(...PREF_AUTOFILL_SEARCHENGINES);
    store.restyleSearches = prefs.get(...PREF_RESTYLESEARCHES);
    store.delay = prefs.get(...PREF_DELAY);
    store.matchBehavior = prefs.get(...PREF_BEHAVIOR);
    store.filterJavaScript = prefs.get(...PREF_FILTER_JS);
    store.maxRichResults = prefs.get(...PREF_MAXRESULTS);
    store.suggestHistory = prefs.get(...PREF_SUGGEST_HISTORY);
    store.suggestBookmark = prefs.get(...PREF_SUGGEST_BOOKMARK);
    store.suggestOpenpage = prefs.get(...PREF_SUGGEST_OPENPAGE);
    store.suggestTyped = prefs.get(...PREF_SUGGEST_HISTORY_ONLYTYPED);
    store.suggestSearches = prefs.get(...PREF_SUGGEST_SEARCHES);
    store.maxCharsForSearchSuggestions = prefs.get(...PREF_MAX_CHARS_FOR_SUGGEST);
    store.maxHistoricalSearchSuggestions = prefs.get(...PREF_MAX_HISTORICAL_SUGGESTIONS);
    store.preloadedSitesEnabled = prefs.get(...PREF_PRELOADED_SITES_ENABLED);
    store.preloadedSitesExpireDays = prefs.get(...PREF_PRELOADED_SITES_EXPIRE_DAYS);
    store.matchBuckets = prefs.get(...PREF_MATCH_BUCKETS);
    // Convert from pref char format to an array and add the default buckets.
    try {
      store.matchBuckets = convertBucketsCharPrefToArray(store.matchBuckets);
    } catch (ex) {
      store.matchBuckets = convertBucketsCharPrefToArray(PREF_MATCH_BUCKETS[1]);
    }
    store.matchBuckets = [ ...DEFAULT_BUCKETS_BEFORE,
                           ...store.matchBuckets,
                           ...DEFAULT_BUCKETS_AFTER ];
    store.matchBucketsSearch = prefs.get(...PREF_MATCH_BUCKETS_SEARCH);
    // Default to matchBuckets if not defined.
    if (!store.matchBucketsSearch) {
      store.matchBucketsSearch = store.matchBuckets;
    } else {
      // Convert from pref char format to an array and add the default buckets.
      try {
        store.matchBucketsSearch = convertBucketsCharPrefToArray(store.matchBucketsSearch);
        store.matchBucketsSearch = [ ...DEFAULT_BUCKETS_BEFORE,
                                     ...store.matchBucketsSearch,
                                     ...DEFAULT_BUCKETS_AFTER ];
      } catch (ex) {
        store.matchBucketsSearch = store.matchBuckets;
      }
    }
    store.keywordEnabled = Services.prefs.getBoolPref("keyword.enabled", true);

    // If history is not set, onlyTyped value should be ignored.
    if (!store.suggestHistory) {
      store.suggestTyped = false;
    }
    store.defaultBehavior = [...types, "Typed"].reduce((memo, type) => {
      let prefValue = store["suggest" + type];
      return memo | (prefValue &&
                     Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
    }, 0);

    // Further restrictions to apply for "empty searches" (i.e. searches for "").
    // The empty behavior is typed history, if history is enabled. Otherwise,
    // it is bookmarks, if they are enabled. If both history and bookmarks are disabled,
    // it defaults to open pages.
    store.emptySearchDefaultBehavior = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT;
    if (store.suggestHistory) {
      store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
                                          Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED;
    } else if (store.suggestBookmark) {
      store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
    } else {
      store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
    }

    // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
    if (store.matchBehavior != MATCH_ANYWHERE &&
        store.matchBehavior != MATCH_BOUNDARY &&
        store.matchBehavior != MATCH_BEGINNING) {
      store.matchBehavior = MATCH_BOUNDARY_ANYWHERE;
    }

  }

  let store = {
    _ignoreNotifications: false,
    observe(subject, topic, data) {
      // Avoid re-entrancy when flipping linked preferences.
      if (this._ignoreNotifications)
        return;
      this._ignoreNotifications = true;
      loadPrefs(subject, topic, data);
      this._ignoreNotifications = false;
    },
    QueryInterface: XPCOMUtils.generateQI([
      Ci.nsIObserver,
      Ci.nsISupportsWeakReference ])
  };

  // Synchronize suggest.* prefs with autocomplete.enabled at initialization
  syncEnabledPref();

  loadPrefs();
  Services.prefs.addObserver(PREF_BRANCH, store);
  Services.prefs.addObserver("keyword.enabled", store, true);

  return Object.seal(store);
});

// Preloaded Sites related

function PreloadedSite(url, title) {
  this.uri = Services.io.newURI(url);
  this.title = title;
  this._matchTitle = title.toLowerCase();
  this._hasWWW = this.uri.host.startsWith("www.");
  this._hostWithoutWWW = this._hasWWW ? this.uri.host.slice(4)
                                      : this.uri.host;
}

/**
 * Storage object for Preloaded Sites.
 *   add(url, title): adds a site to storage
 *   populate(sites) : populates the  storage with array of [url,title]
 *   sites[]: resulting array of sites (PreloadedSite objects)
 */
XPCOMUtils.defineLazyGetter(this, "PreloadedSiteStorage", () => Object.seal({
  sites: [],

  add(url, title) {
    let site = new PreloadedSite(url, title);
    this.sites.push(site);
  },

  populate(sites) {
    for (let site of sites) {
      this.add(site[0], site[1]);
    }
  },
}));

XPCOMUtils.defineLazyGetter(this, "ProfileAgeCreatedPromise", () => {
  return (new ProfileAge(null, null)).created;
});

// Helper functions

/**
 * Generates the tokens used in searching from a given string.
 *
 * @param searchString
 *        The string to generate tokens from.
 * @return an array of tokens.
 * @note Calling split on an empty string will return an array containing one
 *       empty string.  We don't want that, as it'll break our logic, so return
 *       an empty array then.
 */
function getUnfilteredSearchTokens(searchString) {
  return searchString.length ? searchString.split(REGEXP_SPACES) : [];
}

/**
 * Strip prefixes from the URI that we don't care about for searching.
 *
 * @param spec
 *        The text to modify.
 * @return the modified spec.
 */
function stripPrefix(spec) {
  ["http://", "https://", "ftp://"].some(scheme => {
    // Strip protocol if not directly followed by a space
    if (spec.startsWith(scheme) && spec[scheme.length] != " ") {
      spec = spec.slice(scheme.length);
      return true;
    }
    return false;
  });

  // Strip www. if not directly followed by a space
  if (spec.startsWith("www.") && spec[4] != " ") {
    spec = spec.slice(4);
  }
  return spec;
}

/**
 * Strip http and trailing separators from a spec.
 *
 * @param spec
 *        The text to modify.
 * @param trimSlash
 *        Whether to trim the trailing slash.
 * @return the modified spec.
 */
function stripHttpAndTrim(spec, trimSlash = true) {
  if (spec.startsWith("http://")) {
    spec = spec.slice(7);
  }
  if (spec.endsWith("?")) {
    spec = spec.slice(0, -1);
  }
  if (trimSlash && spec.endsWith("/")) {
    spec = spec.slice(0, -1);
  }
  return spec;
}

/**
 * Returns the key to be used for a match in a map for the purposes of removing
 * duplicate entries - any 2 URLs that should be considered the same should
 * return the same key. For some moz-action URLs this will unwrap the params
 * and return a key based on the wrapped URL.
 */
function makeKeyForURL(match) {
  let actionUrl = match.value;

  // At this stage we only consider moz-action URLs.
  if (!actionUrl.startsWith("moz-action:")) {
    // For autofill entries, we need to have a key based on the comment rather
    // than the value field, because the latter may have been trimmed.
    if (match.hasOwnProperty("style") && match.style.includes("autofill")) {
      return stripHttpAndTrim(match.comment);
    }
    return stripHttpAndTrim(actionUrl);
  }
  let [, type, params] = actionUrl.match(/^moz-action:([^,]+),(.*)$/);
  try {
    params = JSON.parse(params);
  } catch (ex) {
    // This is unexpected in this context, so just return the input.
    return stripHttpAndTrim(actionUrl);
  }
  // For now we only handle these 2 action types and treat them as the same.
  switch (type) {
    case "remotetab":
    case "switchtab":
      if (params.url) {
        return "moz-action:tab:" + stripHttpAndTrim(params.url);
      }
      break;
      // TODO (bug 1222435) - "switchtab" should be handled as an "autofill"
      // entry.
    default:
      // do nothing.
      // TODO (bug 1222436) - extend this method so it can be used instead of
      // the |placeId| that's also used to remove duplicate entries.
  }
  return stripHttpAndTrim(actionUrl);
}

/**
 * Returns whether the passed in string looks like a url.
 */
function looksLikeUrl(str, ignoreAlphanumericHosts = false) {
  // Single word not including special chars.
  return !REGEXP_SPACES.test(str) &&
         (["/", "@", ":", "["].some(c => str.includes(c)) ||
          (ignoreAlphanumericHosts ? /(.*\..*){3,}/.test(str) : str.includes(".")));
}

/**
 * Manages a single instance of an autocomplete search.
 *
 * The first three parameters all originate from the similarly named parameters
 * of nsIAutoCompleteSearch.startSearch().
 *
 * @param searchString
 *        The search string.
 * @param searchParam
 *        A space-delimited string of search parameters.  The following
 *        parameters are supported:
 *        * enable-actions: Include "actions", such as switch-to-tab and search
 *          engine aliases, in the results.
 *        * disable-private-actions: The search is taking place in a private
 *          window outside of permanent private-browsing mode.  The search
 *          should exclude privacy-sensitive results as appropriate.
 *        * private-window: The search is taking place in a private window,
 *          possibly in permanent private-browsing mode.  The search
 *          should exclude privacy-sensitive results as appropriate.
 *        * user-context-id: The userContextId of the selected tab.
 * @param autocompleteListener
 *        An nsIAutoCompleteObserver.
 * @param resultListener
 *        An nsIAutoCompleteSimpleResultListener.
 * @param autocompleteSearch
 *        An nsIAutoCompleteSearch.
 * @param prohibitSearchSuggestions
 *        Whether search suggestions are allowed for this search.
 */
function Search(searchString, searchParam, autocompleteListener,
                resultListener, autocompleteSearch, prohibitSearchSuggestions) {
  // We want to store the original string for case sensitive searches.
  this._originalSearchString = searchString;
  this._trimmedOriginalSearchString = searchString.trim();
  let strippedOriginalSearchString =
    stripPrefix(this._trimmedOriginalSearchString.toLowerCase());
  this._searchString =
    textURIService.unEscapeURIForUI("UTF-8", strippedOriginalSearchString);

  // The protocol and the host are lowercased by nsIURI, so it's fine to
  // lowercase the typed prefix, to add it back to the results later.
  this._strippedPrefix = this._trimmedOriginalSearchString.slice(
    0, this._trimmedOriginalSearchString.length - strippedOriginalSearchString.length
  ).toLowerCase();

  this._matchBehavior = Prefs.matchBehavior;
  // Set the default behavior for this search.
  this._behavior = this._searchString ? Prefs.defaultBehavior
                                      : Prefs.emptySearchDefaultBehavior;

  let params = new Set(searchParam.split(" "));
  this._enableActions = params.has("enable-actions");
  this._disablePrivateActions = params.has("disable-private-actions");
  this._inPrivateWindow = params.has("private-window");
  this._prohibitAutoFill = params.has("prohibit-autofill");

  let userContextId = searchParam.match(REGEXP_USER_CONTEXT_ID);
  this._userContextId = userContextId ?
                          parseInt(userContextId[1], 10) :
                          Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;

  this._searchTokens =
    this.filterTokens(getUnfilteredSearchTokens(this._searchString));

  this._prohibitSearchSuggestions = prohibitSearchSuggestions;

  this._listener = autocompleteListener;
  this._autocompleteSearch = autocompleteSearch;

  // Create a new result to add eventual matches.  Note we need a result
  // regardless having matches.
  let result = Cc["@mozilla.org/autocomplete/simple-result;1"]
                 .createInstance(Ci.nsIAutoCompleteSimpleResult);
  result.setSearchString(searchString);
  result.setListener(resultListener);
  // Will be set later, if needed.
  result.setDefaultIndex(-1);
  this._result = result;

  // These are used to avoid adding duplicate entries to the results.
  this._usedURLs = new Set();
  this._usedPlaceIds = new Set();

  // Resolved when all the matches providers have been fetched.
  this._allMatchesPromises = [];

  // Counters for the number of matches per MATCHTYPE.
  this._counts = Object.values(MATCHTYPE)
                       .reduce((o, p) => { o[p] = 0; return o; }, {});

  this._searchStringHasWWW = this._strippedPrefix.endsWith("www.");
  this._searchStringWWW = this._searchStringHasWWW ? "www." : "";
  this._searchStringFromWWW = this._searchStringWWW + this._searchString;

  this._searchStringSchemeFound = this._strippedPrefix.match(/^(\w+):/i);
  this._searchStringScheme = this._searchStringSchemeFound ?
                             this._searchStringSchemeFound[1].toLowerCase() : "";
}

Search.prototype = {
  /**
   * Enables the desired AutoComplete behavior.
   *
   * @param type
   *        The behavior type to set.
   */
  setBehavior(type) {
    type = type.toUpperCase();
    this._behavior |=
      Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type];

    // Setting the "typed" behavior should also set the "history" behavior.
    if (type == "TYPED") {
      this.setBehavior("history");
    }
  },

  /**
   * Determines if the specified AutoComplete behavior is set.
   *
   * @param aType
   *        The behavior type to test for.
   * @return true if the behavior is set, false otherwise.
   */
  hasBehavior(type) {
    let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()];

    if (this._disablePrivateActions &&
        behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
      return false;
    }

    return this._behavior & behavior;
  },

  /**
   * Used to delay the most complex queries, to save IO while the user is
   * typing.
   */
  _sleepDeferred: null,
  _sleep(aTimeMs) {
    // Reuse a single instance to try shaving off some usless work before
    // the first query.
    if (!this._sleepTimer)
      this._sleepTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._sleepDeferred = PromiseUtils.defer();
    this._sleepTimer.initWithCallback(() => this._sleepDeferred.resolve(),
                                      aTimeMs, Ci.nsITimer.TYPE_ONE_SHOT);
    return this._sleepDeferred.promise;
  },

  /**
   * Given an array of tokens, this function determines which query should be
   * ran.  It also removes any special search tokens.
   *
   * @param tokens
   *        An array of search tokens.
   * @return the filtered list of tokens to search with.
   */
  filterTokens(tokens) {
    let foundToken = false;
    // Set the proper behavior while filtering tokens.
    for (let i = tokens.length - 1; i >= 0; i--) {
      let behavior = TOKEN_TO_BEHAVIOR_MAP.get(tokens[i]);
      // Don't remove the token if it didn't match, or if it's an action but
      // actions are not enabled.
      if (behavior && (behavior != "openpage" || this._enableActions)) {
        // Don't use the suggest preferences if it is a token search and
        // set the restrict bit to 1 (to intersect the search results).
        if (!foundToken) {
          foundToken = true;
          // Do not take into account previous behavior (e.g.: history, bookmark)
          this._behavior = 0;
          this.setBehavior("restrict");
        }
        this.setBehavior(behavior);
        tokens.splice(i, 1);
      }
    }

    // Set the right JavaScript behavior based on our preference.  Note that the
    // preference is whether or not we should filter JavaScript, and the
    // behavior is if we should search it or not.
    if (!Prefs.filterJavaScript) {
      this.setBehavior("javascript");
    }

    return tokens;
  },

  /**
   * Stop this search.
   * After invoking this method, we won't run any more searches or heuristics,
   * and no new matches may be added to the current result.
   */
  stop() {
    if (this._sleepTimer)
      this._sleepTimer.cancel();
    if (this._sleepDeferred) {
      this._sleepDeferred.resolve();
      this._sleepDeferred = null;
    }
    if (this._searchSuggestionController) {
      this._searchSuggestionController.stop();
      this._searchSuggestionController = null;
    }
    this.pending = false;
  },

  /**
   * Whether this search is active.
   */
  pending: true,

  /**
   * Execute the search and populate results.
   * @param conn
   *        The Sqlite connection.
   */
  async execute(conn) {
    // A search might be canceled before it starts.
    if (!this.pending)
      return;

    TelemetryStopwatch.start(TELEMETRY_1ST_RESULT, this);
    if (this._searchString)
      TelemetryStopwatch.start(TELEMETRY_6_FIRST_RESULTS, this);

    // Since we call the synchronous parseSubmissionURL function later, we must
    // wait for the initialization of PlacesSearchAutocompleteProvider first.
    await PlacesSearchAutocompleteProvider.ensureInitialized();
    if (!this.pending)
      return;

    // For any given search, we run many queries/heuristics:
    // 1) by alias (as defined in SearchService)
    // 2) inline completion from search engine resultDomains
    // 3) inline completion for hosts (this._hostQuery) or urls (this._urlQuery)
    // 4) directly typed in url (ie, can be navigated to as-is)
    // 5) submission for the current search engine
    // 6) Places keywords
    // 7) adaptive learning (this._adaptiveQuery)
    // 8) open pages not supported by history (this._switchToTabQuery)
    // 9) query based on match behavior
    //
    // (6) only gets ran if we get any filtered tokens, since if there are no
    // tokens, there is nothing to match. This is the *first* query we check if
    // we want to run, but it gets queued to be run later.
    //
    // (1), (4), (5) only get run if actions are enabled. When actions are
    // enabled, the first result is always a special result (resulting from one
    // of the queries between (1) and (6) inclusive). As such, the UI is
    // expected to auto-select the first result when actions are enabled. If the
    // first result is an inline completion result, that will also be the
    // default result and therefore be autofilled (this also happens if actions
    // are not enabled).

    // Get the final query, based on the tokens found in the search string.
    let queries = [ this._adaptiveQuery ];

    // "openpage" behavior is supported by the default query.
    // _switchToTabQuery instead returns only pages not supported by history.
    if (this.hasBehavior("openpage")) {
      queries.push(this._switchToTabQuery);
    }
    queries.push(this._searchQuery);

    // Check for Preloaded Sites Expiry before Autofill
    await this._checkPreloadedSitesExpiry();

    // Add the first heuristic result, if any.  Set _addingHeuristicFirstMatch
    // to true so that when the result is added, "heuristic" can be included in
    // its style.
    this._addingHeuristicFirstMatch = true;
    let hasHeuristic = await this._matchFirstHeuristicResult(conn);
    this._addingHeuristicFirstMatch = false;
    if (!this.pending)
      return;

    // We sleep a little between adding the heuristicFirstMatch and matching
    // any other searches so we aren't kicking off potentially expensive
    // searches on every keystroke.
    // Though, if there's no heuristic result, we start searching immediately,
    // since autocomplete may be waiting for us.
    if (hasHeuristic) {
      await this._sleep(Prefs.delay);
      if (!this.pending)
        return;
    }

    // Only add extension suggestions if the first token is a registered keyword
    // and the search string has characters after the first token.
    if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
        this._originalSearchString.length > this._searchTokens[0].length) {
      await this._matchExtensionSuggestions();
      if (!this.pending)
        return;
    } else if (ExtensionSearchHandler.hasActiveInputSession()) {
      ExtensionSearchHandler.handleInputCancelled();
    }

    if (this._enableActions && this._searchTokens.length > 0) {
      await this._matchSearchSuggestions();
      if (!this.pending)
        return;
    }

    for (let [query, params] of queries) {
      await conn.executeCached(query, params, this._onResultRow.bind(this));
      if (!this.pending)
        return;
    }

    if (this._enableActions && this.hasBehavior("openpage")) {
      await this._matchRemoteTabs();
      if (!this.pending)
        return;
    }

    // If we do not have enough results, and our match type is
    // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
    // results.
    if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
        this._counts[MATCHTYPE.GENERAL] < Prefs.maxRichResults) {
      this._matchBehavior = MATCH_ANYWHERE;
      for (let [query, params] of [ this._adaptiveQuery,
                                    this._searchQuery ]) {
        await conn.executeCached(query, params, this._onResultRow.bind(this));
        if (!this.pending)
          return;
      }
    }

    this._matchPreloadedSites();

    // Ensure to fill any remaining space. Suggestions which come from extensions are
    // inserted at the beginning, so any suggestions
    await Promise.all(this._allMatchesPromises);
  },


  async _checkPreloadedSitesExpiry() {
    if (!Prefs.preloadedSitesEnabled)
      return;
    let profileCreationDate = await ProfileAgeCreatedPromise;
    let daysSinceProfileCreation = (Date.now() - profileCreationDate) / MS_PER_DAY;
    if (daysSinceProfileCreation > Prefs.preloadedSitesExpireDays)
      Services.prefs.setBoolPref("browser.urlbar.usepreloadedtopurls.enabled", false);
  },

  _matchPreloadedSites() {
    if (!Prefs.preloadedSitesEnabled)
      return;

    // In case user typed just "https://" or "www." or "https://www."
    // - we do not put out the whole lot of sites
    if (!this._searchString)
      return;

    if (!(this._searchStringScheme === "" ||
          this._searchStringScheme === "https" ||
          this._searchStringScheme === "http"))
      return;

    let strictMatches = [];
    let looseMatches = [];

    for (let site of PreloadedSiteStorage.sites) {
      if (this._searchStringScheme && this._searchStringScheme !== site.uri.scheme)
        continue;
      let match = {
        value: site.uri.spec,
        comment: site.title,
        style: "preloaded-top-site",
        frecency: FRECENCY_DEFAULT - 1,
      };
      if (site.uri.host.includes(this._searchStringFromWWW) ||
          site._matchTitle.includes(this._searchStringFromWWW)) {
        strictMatches.push(match);
      } else if (site.uri.host.includes(this._searchString) ||
                 site._matchTitle.includes(this._searchString)) {
        looseMatches.push(match);
      }
    }
    for (let match of [...strictMatches, ...looseMatches]) {
      this._addMatch(match);
    }
  },

  _matchPreloadedSiteForAutofill() {
    if (!Prefs.preloadedSitesEnabled)
      return false;

    if (!(this._searchStringScheme === "" ||
          this._searchStringScheme === "https" ||
          this._searchStringScheme === "http"))
      return false;

    let searchStringSchemePrefix = this._searchStringScheme
                                   ? (this._searchStringScheme + "://")
                                   : "";

    // If search string has scheme - we'll match it strictly
    function matchScheme(site, search) {
      return !search._searchStringScheme ||
             search._searchStringScheme === site.uri.scheme;
    }

    // First we try to strict-match
    // If search string has "www."- we try to strict-match it along with "www."
    function matchStrict(site) {
      return site.uri.host.startsWith(this._searchStringFromWWW)
             && matchScheme(site, this);
    }
    let site = PreloadedSiteStorage.sites.find(matchStrict, this)
    if (site) {
      let match = {
        // We keep showing prefix that user typed, then what we match on
        value: searchStringSchemePrefix + site.uri.host + "/",
        style: "autofill preloaded-top-site",
        finalCompleteValue: site.uri.spec,
        frecency: Infinity
      };
      this._result.setDefaultIndex(0);
      this._addMatch(match);
      return true;
    }

    // If no strict result found - we try loose match
    // regardless of "www." in Preloaded-sites or search string
    function matchLoose(site) {
      return site._hostWithoutWWW.startsWith(this._searchString)
             && matchScheme(site, this);
    }
    site = PreloadedSiteStorage.sites.find(matchLoose, this);
    if (site) {
      let match = {
        // We keep showing prefix that user typed, then what we match on
        value: searchStringSchemePrefix + this._searchStringWWW +
               site._hostWithoutWWW + "/",
        style: "autofill preloaded-top-site",
        // On loose match, result should always have "www."
        finalCompleteValue: site.uri.scheme + "://www." +
                            site._hostWithoutWWW + "/",
        frecency: Infinity
      };
      this._result.setDefaultIndex(0);
      this._addMatch(match);
      return true;
    }

    return false;
  },

  async _matchFirstHeuristicResult(conn) {
    // We always try to make the first result a special "heuristic" result.  The
    // heuristics below determine what type of result it will be, if any.

    let hasSearchTerms = this._searchTokens.length > 0;

    if (hasSearchTerms) {
      // It may be a keyword registered by an extension.
      let matched = await this._matchExtensionHeuristicResult();
      if (matched) {
        return true;
      }
    }

    if (this._enableActions && hasSearchTerms) {
      // It may be a search engine with an alias - which works like a keyword.
      let matched = await this._matchSearchEngineAlias();
      if (matched) {
        return true;
      }
    }

    if (this.pending && hasSearchTerms) {
      // It may be a Places keyword.
      let matched = await this._matchPlacesKeyword();
      if (matched) {
        return true;
      }
    }

    let shouldAutofill = this._shouldAutofill;
    if (this.pending && shouldAutofill) {
      // It may also look like a URL we know from the database.
      let matched = await this._matchKnownUrl(conn);
      if (matched) {
        return true;
      }
    }

    if (this.pending && shouldAutofill) {
      // Or it may look like a URL we know about from search engines.
      let matched = await this._matchSearchEngineUrl();
      if (matched) {
        return true;
      }
    }

    if (this.pending && shouldAutofill) {
      let matched = this._matchPreloadedSiteForAutofill();
      if (matched) {
        return true;
      }
    }

    if (this.pending && hasSearchTerms && this._enableActions) {
      // If we don't have a result that matches what we know about, then
      // we use a fallback for things we don't know about.

      // We may not have auto-filled, but this may still look like a URL.
      // However, even if the input is a valid URL, we may not want to use
      // it as such. This can happen if the host would require whitelisting,
      // but isn't in the whitelist.
      let matched = await this._matchUnknownUrl();
      if (matched) {
        // Since we can't tell if this is a real URL and
        // whether the user wants to visit or search for it,
        // we always provide an alternative searchengine match.
        try {
          new URL(this._originalSearchString);
        } catch (ex) {
          if (Prefs.keywordEnabled && !looksLikeUrl(this._originalSearchString, true)) {
            this._addingHeuristicFirstMatch = false;
            await this._matchCurrentSearchEngine();
            this._addingHeuristicFirstMatch = true;
          }
        }
        return true;
      }
    }

    if (this.pending && this._enableActions && this._originalSearchString) {
      // When all else fails, and the search string is non-empty, we search
      // using the current search engine.
      let matched = await this._matchCurrentSearchEngine();
      if (matched) {
        return true;
      }
    }

    return false;
  },

  async _matchSearchSuggestions() {
    // Limit the string sent for search suggestions to a maximum length.
    let searchString = this._searchTokens.join(" ")
                           .substr(0, Prefs.maxCharsForSearchSuggestions);
    // Avoid fetching suggestions if they are not required, private browsing
    // mode is enabled, or the search string may expose sensitive information.
    if (!this.hasBehavior("searches") || this._inPrivateWindow ||
        this._prohibitSearchSuggestionsFor(searchString)) {
      return;
    }

    this._searchSuggestionController =
      PlacesSearchAutocompleteProvider.getSuggestionController(
        searchString,
        this._inPrivateWindow,
        Prefs.maxHistoricalSearchSuggestions,
        Prefs.maxRichResults - Prefs.maxHistoricalSearchSuggestions,
        this._userContextId
      );
    let promise = this._searchSuggestionController.fetchCompletePromise
      .then(() => {
        // The search has been canceled already.
        if (!this._searchSuggestionController)
          return;
        if (this._searchSuggestionController.resultsCount >= 0 &&
            this._searchSuggestionController.resultsCount < 2) {
          // The original string is used to properly compare with the next search.
          this._lastLowResultsSearchSuggestion = this._originalSearchString;
        }
        while (this.pending) {
          let result = this._searchSuggestionController.consume();
          if (!result)
            break;
          let { match, suggestion, historical } = result;
          if (!looksLikeUrl(suggestion)) {
            // Don't include the restrict token, if present.
            let searchString = this._searchTokens.join(" ");
            this._addSearchEngineMatch(match, searchString, suggestion, historical);
          }
        }
      });

    if (this.hasBehavior("restrict")) {
      // We're done if we're restricting to search suggestions.
      await promise;
      this.stop();
    } else {
      this._allMatchesPromises.push(promise);
    }
  },

  _prohibitSearchSuggestionsFor(searchString) {
    if (this._prohibitSearchSuggestions)
      return true;

    // Suggestions for a single letter are unlikely to be useful.
    if (searchString.length < 2)
      return true;

    // The first token may be a whitelisted host.
    if (this._searchTokens.length == 1 &&
        REGEXP_SINGLEWORD_HOST.test(this._searchTokens[0]) &&
        Services.uriFixup.isDomainWhitelisted(this._searchTokens[0], -1)) {
      return true;
    }

    // Disallow fetching search suggestions for strings that start off looking
    // like urls.
    if (DISALLOWED_URLLIKE_PREFIXES.some(prefix => this._trimmedOriginalSearchString == prefix) ||
        DISALLOWED_URLLIKE_PREFIXES.some(prefix => this._trimmedOriginalSearchString.startsWith(prefix + ":"))) {
      return true;
    }

    // Disallow fetching search suggestions for strings looking like URLs, to
    // avoid disclosing information about networks or passwords.
    return this._searchTokens.some(looksLikeUrl);
  },

  async _matchKnownUrl(conn) {
    // Hosts have no "/" in them.
    let lastSlashIndex = this._searchString.lastIndexOf("/");
    // Search only URLs if there's a slash in the search string...
    if (lastSlashIndex != -1) {
      // ...but not if it's exactly at the end of the search string.
      if (lastSlashIndex < this._searchString.length - 1) {
        // We don't want to execute this query right away because it needs to
        // search the entire DB without an index, but we need to know if we have
        // a result as it will influence other heuristics. So we guess by
        // assuming that if we get a result from a *host* query and it *looks*
        // like a URL, then we'll probably have a result.
        let gotResult = false;
        let [ query, params ] = this._urlQuery;
        await conn.executeCached(query, params, row => {
          gotResult = true;
          this._onResultRow(row);
        });
        return gotResult;
      }
      return false;
    }

    let gotResult = false;
    let [ query, params ] = this._hostQuery;
    await conn.executeCached(query, params, row => {
      gotResult = true;
      this._onResultRow(row);
    });
    return gotResult;
  },

  _matchExtensionHeuristicResult() {
    if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
        this._originalSearchString.length > this._searchTokens[0].length) {
      let description = ExtensionSearchHandler.getDescription(this._searchTokens[0]);
      this._addExtensionMatch(this._originalSearchString, description);
      return true;
    }
    return false;
  },

  async _matchPlacesKeyword() {
    // The first word could be a keyword, so that's what we'll search.
    let keyword = this._searchTokens[0];
    let entry = await PlacesUtils.keywords.fetch(this._searchTokens[0]);
    if (!entry)
      return false;

    let searchString = this._trimmedOriginalSearchString.substr(keyword.length + 1);

    let url = null, postData = null;
    try {
      [url, postData] =
        await BrowserUtils.parseUrlAndPostData(entry.url.href,
                                               entry.postData,
                                               searchString);
    } catch (ex) {
      // It's not possible to bind a param to this keyword.
      return false;
    }

    let style = "keyword";
    let value = url;
    if (this._enableActions) {
      style = "action " + style;
      value = PlacesUtils.mozActionURI("keyword", {
        url,
        input: this._originalSearchString,
        postData,
      });
    }
    // The title will end up being "host: queryString"
    let comment = entry.url.host;

    this._addMatch({
      value,
      comment,
      // Don't use the url with replaced strings, since the icon doesn't change
      // but the string does, it may cause pointless icon flicker on typing.
      icon: "page-icon:" + entry.url.href,
      style,
      frecency: Infinity
    });
    return true;
  },

  async _matchSearchEngineUrl() {
    if (!Prefs.autofillSearchEngines)
      return false;

    let match = await PlacesSearchAutocompleteProvider.findMatchByToken(
                                                           this._searchString);
    if (!match)
      return false;

    // The match doesn't contain a 'scheme://www.' prefix, but since we have
    // stripped it from the search string, here we could still be matching
    // 'https://www.g' to 'google.com'.
    // There are a couple cases where we don't want to match though:
    //
    //  * If the protocol differs we should not match. For example if the user
    //    searched https we should not return http.
    try {
      let prefixURI = Services.io.newURI(this._strippedPrefix + match.token);
      let finalURI = Services.io.newURI(match.url);
      if (prefixURI.scheme != finalURI.scheme)
        return false;
    } catch (e) {}

    //  * If the user typed "www." but the final url doesn't have it, we
    //    should not match as well, the two urls may point to different pages.
    if (this._strippedPrefix.endsWith("www.") &&
        !stripHttpAndTrim(match.url).startsWith("www."))
      return false;

    let value = this._strippedPrefix + match.token;

    // In any case, we should never arrive here with a value that doesn't
    // match the search string.  If this happens there is some case we
    // are not handling properly yet.
    if (!value.startsWith(this._originalSearchString)) {
      Components.utils.reportError(`Trying to inline complete in-the-middle
                                    ${this._originalSearchString} to ${value}`);
      return false;
    }

    this._result.setDefaultIndex(0);
    this._addMatch({
      value,
      comment: match.engineName,
      icon: match.iconUrl,
      style: "priority-search",
      finalCompleteValue: match.url,
      frecency: Infinity
    });
    return true;
  },

  async _matchSearchEngineAlias() {
    if (this._searchTokens.length < 1)
      return false;

    let alias = this._searchTokens[0];
    let match = await PlacesSearchAutocompleteProvider.findMatchByAlias(alias);
    if (!match)
      return false;

    match.engineAlias = alias;
    let query = this._trimmedOriginalSearchString.substr(alias.length + 1);

    this._addSearchEngineMatch(match, query);
    return true;
  },

  async _matchCurrentSearchEngine() {
    let match = await PlacesSearchAutocompleteProvider.getDefaultMatch();
    if (!match)
      return false;

    let query = this._originalSearchString;
    this._addSearchEngineMatch(match, query);
    return true;
  },

  _addExtensionMatch(content, comment) {
    let count = this._counts[MATCHTYPE.EXTENSION] + this._counts[MATCHTYPE.HEURISTIC];
    if (count >= MAXIMUM_ALLOWED_EXTENSION_MATCHES) {
      return;
    }

    this._addMatch({
      value: PlacesUtils.mozActionURI("extension", {
        content,
        keyword: this._searchTokens[0]
      }),
      comment,
      icon: "chrome://browser/content/extension.svg",
      style: "action extension",
      frecency: Infinity,
      type: MATCHTYPE.EXTENSION
    });
  },

  _addSearchEngineMatch(searchMatch, query, suggestion = "", historical = false) {
    let actionURLParams = {
      engineName: searchMatch.engineName,
      input: suggestion || this._originalSearchString,
      searchQuery: query,
    };
    if (suggestion)
      actionURLParams.searchSuggestion = suggestion;
    if (searchMatch.engineAlias) {
      actionURLParams.alias = searchMatch.engineAlias;
    }
    let value = PlacesUtils.mozActionURI("searchengine", actionURLParams);
    let match = {
      value,
      comment: searchMatch.engineName,
      icon: searchMatch.iconUrl,
      style: "action searchengine",
      frecency: FRECENCY_DEFAULT
    };
    if (suggestion)
      match.type = MATCHTYPE.SUGGESTION;

    this._addMatch(match);
  },

  _matchExtensionSuggestions() {
    let promise = ExtensionSearchHandler.handleSearch(this._searchTokens[0], this._originalSearchString,
      suggestions => {
        for (let suggestion of suggestions) {
          let content = `${this._searchTokens[0]} ${suggestion.content}`;
          this._addExtensionMatch(content, suggestion.description);
        }
      }
    );
    // Since the extension has no way to signale when it's done pushing
    // results, we add a timeout racing with the addition.
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error("timeout waiting for the extension to add its results to the location bar")),
                 MAXIMUM_ALLOWED_EXTENSION_TIME_MS);
    });
    this._allMatchesPromises.push(Promise.race([timeoutPromise, promise]).catch(Cu.reportError));
  },

  async _matchRemoteTabs() {
    let matches = await PlacesRemoteTabsAutocompleteProvider.getMatches(this._originalSearchString);
    for (let {url, title, icon, deviceName} of matches) {
      // It's rare that Sync supplies the icon for the page (but if it does, it
      // is a string URL)
      if (!icon) {
        icon = "page-icon:" + url;
      } else {
        icon = PlacesUtils.favicons
                          .getFaviconLinkForIcon(Services.io.newURI(icon)).spec;
      }

      let match = {
        // We include the deviceName in the action URL so we can render it in
        // the URLBar.
        value: PlacesUtils.mozActionURI("remotetab", { url, deviceName }),
        comment: title || url,
        style: "action remotetab",
        // we want frecency > FRECENCY_DEFAULT so it doesn't get pushed out
        // by "remote" matches.
        frecency: FRECENCY_DEFAULT + 1,
        icon,
      }
      this._addMatch(match);
    }
  },

  // TODO (bug 1054814): Use visited URLs to inform which scheme to use, if the
  // scheme isn't specificed.
  _matchUnknownUrl() {
    let flags = Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
                Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    let fixupInfo = null;
    try {
      fixupInfo = Services.uriFixup.getFixupURIInfo(this._originalSearchString,
                                                    flags);
    } catch (e) {
      if (e.result == Cr.NS_ERROR_MALFORMED_URI && !Prefs.keywordEnabled) {
        let value = PlacesUtils.mozActionURI("visiturl", {
          url: this._originalSearchString,
          input: this._originalSearchString,
        });
        this._addMatch({
          value,
          comment: this._originalSearchString,
          style: "action visiturl",
          frecency: Infinity
        });

        return true;
      }
      return false;
    }

    // If the URI cannot be fixed or the preferred URI would do a keyword search,
    // that basically means this isn't useful to us. Note that
    // fixupInfo.keywordAsSent will never be true if the keyword.enabled pref
    // is false or there are no engines, so in that case we will always return
    // a "visit".
    if (!fixupInfo.fixedURI || fixupInfo.keywordAsSent)
      return false;

    let uri = fixupInfo.fixedURI;
    // Check the host, as "http:///" is a valid nsIURI, but not useful to us.
    // But, some schemes are expected to have no host. So we check just against
    // schemes we know should have a host. This allows new schemes to be
    // implemented without us accidentally blocking access to them.
    let hostExpected = ["http", "https", "ftp", "chrome"].includes(uri.scheme);
    if (hostExpected && !uri.host)
      return false;

    // getFixupURIInfo() escaped the URI, so it may not be pretty.  Embed the
    // escaped URL in the action URI since that URL should be "canonical".  But
    // pass the pretty, unescaped URL as the match comment, since it's likely
    // to be displayed to the user, and in any case the front-end should not
    // rely on it being canonical.
    let escapedURL = uri.spec;
    let displayURL = textURIService.unEscapeURIForUI("UTF-8", uri.spec);

    let value = PlacesUtils.mozActionURI("visiturl", {
      url: escapedURL,
      input: this._originalSearchString,
    });

    let match = {
      value,
      comment: displayURL,
      style: "action visiturl",
      frecency: Infinity
    };

    // We don't know if this url is in Places or not, and checking that would
    // be expensive. Thus we also don't know if we may have an icon.
    // If we'd just try to fetch the icon for the typed string, we'd cause icon
    // flicker, since the url keeps changing while the user types.
    // By default we won't provide an icon, but for the subset of urls with a
    // host we'll check for a typed slash and set favicon for the host part.
    if (hostExpected &&
        (this._trimmedOriginalSearchString.endsWith("/") || uri.path.length > 1)) {
      match.icon = `page-icon:${uri.prePath}/`;
    }

    this._addMatch(match);
    return true;
  },

  _onResultRow(row) {
    if (this._counts[MATCHTYPE.GENERAL] == 0) {
      TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT, this);
    }
    let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
    let match;
    switch (queryType) {
      case QUERYTYPE_AUTOFILL_HOST:
        this._result.setDefaultIndex(0);
        match = this._processHostRow(row);
        break;
      case QUERYTYPE_AUTOFILL_URL:
        this._result.setDefaultIndex(0);
        match = this._processUrlRow(row);
        break;
      case QUERYTYPE_FILTERED:
        match = this._processRow(row);
        break;
    }
    this._addMatch(match);
    // If the search has been canceled by the user or by _addMatch, or we
    // fetched enough results, we can stop the underlying Sqlite query.
    if (!this.pending || this._counts[MATCHTYPE.GENERAL] == Prefs.maxRichResults)
      throw StopIteration;
  },

  _maybeRestyleSearchMatch(match) {
    // Return if the URL does not represent a search result.
    let parseResult =
      PlacesSearchAutocompleteProvider.parseSubmissionURL(match.value);
    if (!parseResult) {
      return;
    }

    // Do not apply the special style if the user is doing a search from the
    // location bar but the entered terms match an irrelevant portion of the
    // URL. For example, "https://www.google.com/search?q=terms&client=firefox"
    // when searching for "Firefox".
    let terms = parseResult.terms.toLowerCase();
    if (this._searchTokens.length > 0 &&
        this._searchTokens.every(token => !terms.includes(token))) {
      return;
    }

    // Turn the match into a searchengine action with a favicon.
    match.value = PlacesUtils.mozActionURI("searchengine", {
      engineName: parseResult.engineName,
      input: parseResult.terms,
      searchQuery: parseResult.terms,
    });
    match.comment = parseResult.engineName;
    match.icon = match.icon || match.iconUrl;
    match.style = "action searchengine favicon";
  },

  _addMatch(match) {
    if (typeof match.frecency != "number")
      throw new Error("Frecency not provided");

    if (this._addingHeuristicFirstMatch)
      match.type = MATCHTYPE.HEURISTIC;
    else if (typeof match.type != "string")
      match.type = MATCHTYPE.GENERAL;

    // A search could be canceled between a query start and its completion,
    // in such a case ensure we won't notify any result for it.
    if (!this.pending)
      return;

    // For autofill entries, the comment field must be a stripped version
    // of the final destination url, so that the user will definitely know
    // where he is going to end up. For example, if the user is visiting a
    // secure page, we'll leave the https on it, to let him know that.
    // This must happen before generating the dedupe key.
    if (match.hasOwnProperty("style") && match.style.includes("autofill")) {
      // We fallback to match.value, as that's what autocomplete does if
      // finalCompleteValue is null.
      // Trim only if the value looks like a domain, we want to retain the
      // trailing slash if we're completing a url to the next slash.
      match.comment = stripHttpAndTrim(match.finalCompleteValue || match.value,
                                       !this._searchString.includes("/"));
    }

    // Must check both id and url, cause keywords dynamically modify the url.
    let urlMapKey = makeKeyForURL(match);
    if ((match.placeId && this._usedPlaceIds.has(match.placeId)) ||
        this._usedURLs.has(urlMapKey)) {
      return;
    }

    // Add this to our internal tracker to ensure duplicates do not end up in
    // the result.
    // Not all entries have a place id, thus we fallback to the url for them.
    // We cannot use only the url since keywords entries are modified to
    // include the search string, and would be returned multiple times.  Ids
    // are faster too.
    if (match.placeId)
      this._usedPlaceIds.add(match.placeId);
    this._usedURLs.add(urlMapKey);

    match.style = match.style || "favicon";

    // Restyle past searches, unless they are bookmarks or special results.
    if (Prefs.restyleSearches && match.style == "favicon") {
      this._maybeRestyleSearchMatch(match);
    }

    if (this._addingHeuristicFirstMatch) {
      match.style += " heuristic";
    }

    match.icon = match.icon || "";
    match.finalCompleteValue = match.finalCompleteValue || "";

    this._result.insertMatchAt(this._getInsertIndexForMatch(match),
                               match.value,
                               match.comment,
                               match.icon,
                               match.style,
                               match.finalCompleteValue);
    this._counts[match.type]++;

    if (this._result.matchCount == 6)
      TelemetryStopwatch.finish(TELEMETRY_6_FIRST_RESULTS, this);

    this.notifyResults(true);
  },

  _getInsertIndexForMatch(match) {
    let index = 0;
    // The buckets change depending on the context, that is currently decided by
    // the first added match (the heuristic one).
    if (!this._buckets) {
      // Convert the buckets to readable objects with a count property.
      let buckets = match.style.includes("searchengine") ? Prefs.matchBucketsSearch
                                                         : Prefs.matchBuckets;
      this._buckets = buckets.map(([type, available]) => ({ type,
                                                            available,
                                                            count: 0,
                                                          }));
    }
    for (let bucket of this._buckets) {
      // Move to the next bucket if the match type is incompatible, or if there
      // is no available space or if the frecency is below the threshold.
      if (match.type != bucket.type || !bucket.available) {
        index += bucket.count;
        continue;
      }

      index += bucket.count;
      bucket.available--;
      bucket.count++;
      break;
    }
    return index;
  },

  _processHostRow(row) {
    let match = {};
    let strippedHost = row.getResultByIndex(QUERYINDEX_URL);
    let url = row.getResultByIndex(QUERYINDEX_TITLE);
    let unstrippedHost = stripHttpAndTrim(url, false);
    let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);

    // If the unfixup value doesn't preserve the user's input just
    // ignore it and complete to the found host.
    if (!unstrippedHost.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
      unstrippedHost = null;
    }

    match.value = this._strippedPrefix + strippedHost;
    match.finalCompleteValue = unstrippedHost;

    match.icon = "page-icon:" + url;

    // Although this has a frecency, this query is executed before any other
    // queries that would result in frecency matches.
    match.frecency = frecency;
    match.style = "autofill";
    return match;
  },

  _processUrlRow(row) {
    let url = row.getResultByIndex(QUERYINDEX_URL);
    let strippedUrl = stripPrefix(url);
    let prefix = url.substr(0, url.length - strippedUrl.length);
    let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);

    // We must complete the URL up to the next separator (which is /, ? or #).
    let searchString = stripPrefix(this._trimmedOriginalSearchString);
    let separatorIndex = strippedUrl.slice(searchString.length)
                                    .search(/[\/\?\#]/);
    if (separatorIndex != -1) {
      separatorIndex += searchString.length;
      if (strippedUrl[separatorIndex] == "/") {
        separatorIndex++; // Include the "/" separator
      }
      strippedUrl = strippedUrl.slice(0, separatorIndex);
    }

    let match = {
      value: this._strippedPrefix + strippedUrl,
      // Although this has a frecency, this query is executed before any other
      // queries that would result in frecency matches.
      frecency,
      style: "autofill"
    };

    // Complete to the found url only if its untrimmed value preserves the
    // user's input.
    if (url.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
      match.finalCompleteValue = prefix + strippedUrl;
    }

    match.icon = "page-icon:" + (match.finalCompleteValue || match.value);

    return match;
  },

  _processRow(row) {
    let match = {};
    match.placeId = row.getResultByIndex(QUERYINDEX_PLACEID);
    let escapedURL = row.getResultByIndex(QUERYINDEX_URL);
    let openPageCount = row.getResultByIndex(QUERYINDEX_SWITCHTAB) || 0;
    let historyTitle = row.getResultByIndex(QUERYINDEX_TITLE) || "";
    let bookmarked = row.getResultByIndex(QUERYINDEX_BOOKMARKED);
    let bookmarkTitle = bookmarked ?
      row.getResultByIndex(QUERYINDEX_BOOKMARKTITLE) : null;
    let tags = row.getResultByIndex(QUERYINDEX_TAGS) || "";
    let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);

    // If actions are enabled and the page is open, add only the switch-to-tab
    // result.  Otherwise, add the normal result.
    let url = escapedURL;
    let action = null;
    if (this._enableActions && openPageCount > 0 && this.hasBehavior("openpage")) {
      url = PlacesUtils.mozActionURI("switchtab", {url: escapedURL});
      action = "switchtab";
      if (frecency == null)
        frecency = FRECENCY_DEFAULT;
    }

    // Always prefer the bookmark title unless it is empty
    let title = bookmarkTitle || historyTitle;

    // We will always prefer to show tags if we have them.
    let showTags = !!tags;

    // However, we'll act as if a page is not bookmarked if the user wants
    // only history and not bookmarks and there are no tags.
    if (this.hasBehavior("history") && !this.hasBehavior("bookmark") &&
        !showTags) {
      showTags = false;
      match.style = "favicon";
    }

    // If we have tags and should show them, we need to add them to the title.
    if (showTags) {
      title += TITLE_TAGS_SEPARATOR + tags;
    }

    // We have to determine the right style to display.  Tags show the tag icon,
    // bookmarks get the bookmark icon, and keywords get the keyword icon.  If
    // the result does not fall into any of those, it just gets the favicon.
    if (!match.style) {
      // It is possible that we already have a style set (from a keyword
      // search or because of the user's preferences), so only set it if we
      // haven't already done so.
      if (showTags) {
        // If we're not suggesting bookmarks, then this shouldn't
        // display as one.
        match.style = this.hasBehavior("bookmark") ? "bookmark-tag" : "tag";
      } else if (bookmarked) {
        match.style = "bookmark";
      }
    }

    if (action)
      match.style = "action " + action;

    match.value = url;
    match.comment = title;
    match.icon = "page-icon:" + escapedURL;
    match.frecency = frecency;

    return match;
  },

  /**
   * @return a string consisting of the search query to be used based on the
   * previously set urlbar suggestion preferences.
   */
  get _suggestionPrefQuery() {
    if (!this.hasBehavior("restrict") && this.hasBehavior("history") &&
        this.hasBehavior("bookmark")) {
      return this.hasBehavior("typed") ? defaultQuery("AND h.typed = 1")
                                       : defaultQuery();
    }
    let conditions = [];
    if (this.hasBehavior("history")) {
      // Enforce ignoring the visit_count index, since the frecency one is much
      // faster in this case.  ANALYZE helps the query planner to figure out the
      // faster path, but it may not have up-to-date information yet.
      conditions.push("+h.visit_count > 0");
    }
    if (this.hasBehavior("typed")) {
      conditions.push("h.typed = 1");
    }
    if (this.hasBehavior("bookmark")) {
      conditions.push("bookmarked");
    }
    if (this.hasBehavior("tag")) {
      conditions.push("tags NOTNULL");
    }

    return conditions.length ? defaultQuery("AND " + conditions.join(" AND "))
                             : defaultQuery();
  },

  /**
   * Obtains the search query to be used based on the previously set search
   * preferences (accessed by this.hasBehavior).
   *
   * @return an array consisting of the correctly optimized query to search the
   *         database with and an object containing the params to bound.
   */
  get _searchQuery() {
    let query = this._suggestionPrefQuery;

    return [
      query,
      {
        parent: PlacesUtils.tagsFolderId,
        query_type: QUERYTYPE_FILTERED,
        matchBehavior: this._matchBehavior,
        searchBehavior: this._behavior,
        // We only want to search the tokens that we are left with - not the
        // original search string.
        searchString: this._searchTokens.join(" "),
        userContextId: this._userContextId,
        // Limit the query to the the maximum number of desired results.
        // This way we can avoid doing more work than needed.
        maxResults: Prefs.maxRichResults
      }
    ];
  },

  /**
   * Obtains the query to search for switch-to-tab entries.
   *
   * @return an array consisting of the correctly optimized query to search the
   *         database with and an object containing the params to bound.
   */
  get _switchToTabQuery() {
    return [
      SQL_SWITCHTAB_QUERY,
      {
        query_type: QUERYTYPE_FILTERED,
        matchBehavior: this._matchBehavior,
        searchBehavior: this._behavior,
        // We only want to search the tokens that we are left with - not the
        // original search string.
        searchString: this._searchTokens.join(" "),
        userContextId: this._userContextId,
        maxResults: Prefs.maxRichResults
      }
    ];
  },

  /**
   * Obtains the query to search for adaptive results.
   *
   * @return an array consisting of the correctly optimized query to search the
   *         database with and an object containing the params to bound.
   */
  get _adaptiveQuery() {
    return [
      SQL_ADAPTIVE_QUERY,
      {
        parent: PlacesUtils.tagsFolderId,
        search_string: this._searchString,
        query_type: QUERYTYPE_FILTERED,
        matchBehavior: this._matchBehavior,
        searchBehavior: this._behavior,
        userContextId: this._userContextId,
      }
    ];
  },

  /**
   * Whether we should try to autoFill.
   */
  get _shouldAutofill() {
    // First of all, check for the autoFill pref.
    if (!Prefs.autofill)
      return false;

    if (this._searchTokens.length != 1)
      return false;

    // autoFill can only cope with history or bookmarks entries.
    if (!this.hasBehavior("history") &&
        !this.hasBehavior("bookmark"))
      return false;

    // autoFill doesn't search titles or tags.
    if (this.hasBehavior("title") || this.hasBehavior("tag"))
      return false;

    // Don't try to autofill if the search term includes any whitespace.
    // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
    // tokenizer ends up trimming the search string and returning a value
    // that doesn't match it, or is even shorter.
    if (REGEXP_SPACES.test(this._originalSearchString))
      return false;

    if (this._searchString.length == 0)
      return false;

    if (this._prohibitAutoFill)
      return false;

    return true;
  },

  /**
   * Obtains the query to search for autoFill host results.
   *
   * @return an array consisting of the correctly optimized query to search the
   *         database with and an object containing the params to bound.
   */
  get _hostQuery() {
    let typed = Prefs.autofillTyped || this.hasBehavior("typed");
    let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");

    let query = [];
    if (bookmarked) {
      query.push(typed ? SQL_BOOKMARKED_TYPED_HOST_QUERY
                       : SQL_BOOKMARKED_HOST_QUERY);
    } else {
      query.push(typed ? SQL_TYPED_HOST_QUERY
                       : SQL_HOST_QUERY);
    }

    query.push({
      query_type: QUERYTYPE_AUTOFILL_HOST,
      searchString: this._searchString.toLowerCase()
    });

    return query;
  },

  /**
   * Obtains the query to search for autoFill url results.
   *
   * @return an array consisting of the correctly optimized query to search the
   *         database with and an object containing the params to bound.
   */
  get _urlQuery() {
    // We expect this to be a full URL, not just a host. We want to extract the
    // host and use that as a guess for whether we'll get a result from a URL
    // query.
    // The URIs in the database are fixed-up, so we can match on a lowercased
    // host, but the path must be matched in a case sensitive way.
    let pathIndex = this._trimmedOriginalSearchString.indexOf("/", this._strippedPrefix.length);
    let revHost = this._trimmedOriginalSearchString
                      .substring(this._strippedPrefix.length, pathIndex)
                      .toLowerCase().split("").reverse().join("") + ".";
    let searchString = stripPrefix(
      this._trimmedOriginalSearchString.slice(0, pathIndex).toLowerCase() +
      this._trimmedOriginalSearchString.slice(pathIndex)
    );

    let typed = Prefs.autofillTyped || this.hasBehavior("typed");
    let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");

    let query = [];
    if (bookmarked) {
      query.push(typed ? SQL_BOOKMARKED_TYPED_URL_QUERY
                       : SQL_BOOKMARKED_URL_QUERY);
    } else {
      query.push(typed ? SQL_TYPED_URL_QUERY
                       : SQL_URL_QUERY);
    }

    query.push({
      query_type: QUERYTYPE_AUTOFILL_URL,
      searchString,
      revHost
    });

    return query;
  },

 /**
   * Notifies the listener about results.
   *
   * @param searchOngoing
   *        Indicates whether the search is ongoing.
   */
  notifyResults(searchOngoing) {
    let result = this._result;
    let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
    if (searchOngoing) {
      resultCode += "_ONGOING";
    }
    result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
    this._listener.onSearchResult(this._autocompleteSearch, result);
  },
}

// UnifiedComplete class
// component @mozilla.org/autocomplete/search;1?name=unifiedcomplete

function UnifiedComplete() {
  // Make sure the preferences are initialized as soon as possible.
  // If the value of browser.urlbar.autocomplete.enabled is set to false,
  // then all the other suggest preferences for history, bookmarks and
  // open pages should be set to false.
  Prefs;

  if (Prefs.preloadedSitesEnabled) {
    // force initializing the profile age check
    // to ensure the off-main-thread-IO happens ASAP
    // and we don't have to wait for it when doing an autocomplete lookup
    ProfileAgeCreatedPromise;

    fetch("chrome://global/content/unifiedcomplete-top-urls.json")
      .then(response => response.json())
      .then(sites => PreloadedSiteStorage.populate(sites))
      .catch(ex => Cu.reportError(ex));
  }
}

UnifiedComplete.prototype = {
  // Database handling

  /**
   * Promise resolved when the database initialization has completed, or null
   * if it has never been requested.
   */
  _promiseDatabase: null,

  /**
   * Gets a Sqlite database handle.
   *
   * @return {Promise}
   * @resolves to the Sqlite database handle (according to Sqlite.jsm).
   * @rejects javascript exception.
   */
  getDatabaseHandle() {
    if (Prefs.enabled && !this._promiseDatabase) {
      this._promiseDatabase = (async function() {
        let conn = await Sqlite.cloneStorageConnection({
          connection: PlacesUtils.history.DBConnection,
          readOnly: true
        });

        try {
           Sqlite.shutdown.addBlocker("Places UnifiedComplete.js clone closing",
                                      async function() {
                                        SwitchToTabStorage.shutdown();
                                        await conn.close();
                                      });
        } catch (ex) {
          // It's too late to block shutdown, just close the connection.
          await conn.close();
          throw ex;
        }

        // Autocomplete often fallbacks to a table scan due to lack of text
        // indices.  A larger cache helps reducing IO and improving performance.
        // The value used here is larger than the default Storage value defined
        // as MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
        await conn.execute("PRAGMA cache_size = -6144"); // 6MiB

        await SwitchToTabStorage.initDatabase(conn);

        return conn;
      })().catch(ex => {
        dump("Couldn't get database handle: " + ex + "\n");
        Cu.reportError(ex);
      });
    }
    return this._promiseDatabase;
  },

  // mozIPlacesAutoComplete

  registerOpenPage(uri, userContextId) {
    SwitchToTabStorage.add(uri, userContextId);
  },

  unregisterOpenPage(uri, userContextId) {
    SwitchToTabStorage.delete(uri, userContextId);
  },

  populatePreloadedSiteStorage(json) {
    PreloadedSiteStorage.populate(json);
  },

  // nsIAutoCompleteSearch

  startSearch(searchString, searchParam, previousResult, listener) {
    // Stop the search in case the controller has not taken care of it.
    if (this._currentSearch) {
      this.stopSearch();
    }

    // Note: We don't use previousResult to make sure ordering of results are
    //       consistent.  See bug 412730 for more details.

    // If the previous search didn't fetch enough search suggestions, it's
    // unlikely a longer text would do.
    let prohibitSearchSuggestions =
      this._lastLowResultsSearchSuggestion &&
      searchString.length > this._lastLowResultsSearchSuggestion.length &&
      searchString.startsWith(this._lastLowResultsSearchSuggestion);

    this._currentSearch = new Search(searchString, searchParam, listener,
                                     this, this, prohibitSearchSuggestions);

    // If we are not enabled, we need to return now.  Notice we need an empty
    // result regardless, so we still create the Search object.
    if (!Prefs.enabled) {
      this.finishSearch(true);
      return;
    }

    let search = this._currentSearch;
    this.getDatabaseHandle().then(conn => search.execute(conn))
                            .catch(ex => {
                              dump(`Query failed: ${ex}\n`);
                              Cu.reportError(ex);
                            })
                            .then(() => {
                              if (search == this._currentSearch) {
                                this.finishSearch(true);
                              }
                            });
  },

  stopSearch() {
    if (this._currentSearch) {
      this._currentSearch.stop();
    }
    // Don't notify since we are canceling this search.  This also means we
    // won't fire onSearchComplete for this search.
    this.finishSearch();
  },

  /**
   * Properly cleans up when searching is completed.
   *
   * @param notify [optional]
   *        Indicates if we should notify the AutoComplete listener about our
   *        results or not.
   */
  finishSearch(notify = false) {
    TelemetryStopwatch.cancel(TELEMETRY_1ST_RESULT, this);
    TelemetryStopwatch.cancel(TELEMETRY_6_FIRST_RESULTS, this);
    // Clear state now to avoid race conditions, see below.
    let search = this._currentSearch;
    if (!search)
      return;
    this._lastLowResultsSearchSuggestion = search._lastLowResultsSearchSuggestion;
    delete this._currentSearch;

    if (!notify)
      return;

    // There is a possible race condition here.
    // When a search completes it calls finishSearch that notifies results
    // here.  When the controller gets the last result it fires
    // onSearchComplete.
    // If onSearchComplete immediately starts a new search it will set a new
    // _currentSearch, and on return the execution will continue here, after
    // notifyResults.
    // Thus, ensure that notifyResults is the last call in this method,
    // otherwise you might be touching the wrong search.
    search.notifyResults(false);
  },

  // nsIAutoCompleteSimpleResultListener

  onValueRemoved(result, spec, removeFromDB) {
    if (removeFromDB) {
      PlacesUtils.history.remove(spec).catch(Cu.reportError);
    }
  },

  // nsIAutoCompleteSearchDescriptor

  get searchType() {
    return Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE;
  },

  get clearingAutoFillSearchesAgain() {
    return true;
  },

  // nsISupports

  classID: Components.ID("f964a319-397a-4d21-8be6-5cdd1ee3e3ae"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(UnifiedComplete),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIAutoCompleteSearch,
    Ci.nsIAutoCompleteSimpleResultListener,
    Ci.nsIAutoCompleteSearchDescriptor,
    Ci.mozIPlacesAutoComplete,
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference
  ])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UnifiedComplete]);
PK
!<%oMM components/nsPlacesExpiration.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component handles history and orphans expiration through asynchronous
 * Storage statements.
 * Expiration runs:
 * - At idle, but just once, we stop any other kind of expiration during idle
 *   to preserve batteries in portable devices.
 * - At shutdown, only if the database is dirty, we should still avoid to
 *   expire too heavily on shutdown.
 * - On a repeating timer we expire in small chunks.
 *
 * Expiration algorithm will adapt itself based on:
 * - Memory size of the device.
 * - Status of the database (clean or dirty).
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");

// Constants

// Last expiration step should run before the final sync.
const TOPIC_PREF_CHANGED = "nsPref:changed";
const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration";
const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
const TOPIC_IDLE_BEGIN = "idle";
const TOPIC_IDLE_END = "active";
const TOPIC_IDLE_DAILY = "idle-daily";
const TOPIC_TESTING_MODE = "testing-mode";
const TOPIC_TEST_INTERVAL_CHANGED = "test-interval-changed";

// Branch for all expiration preferences.
const PREF_BRANCH = "places.history.expiration.";

// Max number of unique URIs to retain in history.
// Notice this is a lazy limit.  This means we will start to expire if we will
// go over it, but we won't ensure that we will stop exactly when we reach it,
// instead we will stop after the next expiration step that will bring us
// below it.
// If this preference does not exist or has a negative value, we will calculate
// a limit based on current hardware.
const PREF_MAX_URIS = "max_pages";
const PREF_MAX_URIS_NOTSET = -1; // Use our internally calculated limit.

// We save the current unique URIs limit to this pref, to make it available to
// other components without having to duplicate the full logic.
const PREF_READONLY_CALCULATED_MAX_URIS = "transient_current_max_pages";

// Seconds between each expiration step.
const PREF_INTERVAL_SECONDS = "interval_seconds";
const PREF_INTERVAL_SECONDS_NOTSET = 3 * 60;

// We calculate an optimal database size, based on hardware specs.
// This percentage of memory size is used to protect against calculating a too
// large database size on systems with small memory.
const DATABASE_TO_MEMORY_PERC = 4;
// This percentage of disk size is used to protect against calculating a too
// large database size on disks with tiny quota or available space.
const DATABASE_TO_DISK_PERC = 2;
// Maximum size of the optimal database.  High-end hardware has plenty of
// memory and disk space, but performances don't grow linearly.
const DATABASE_MAX_SIZE = 62914560; // 60MiB
// If the physical memory size is bogus, fallback to this.
const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
// If the disk available space is bogus, fallback to this.
const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB

// Max number of entries to expire at each expiration step.
// This value is globally used for different kind of data we expire, can be
// tweaked based on data type.  See below in getBoundStatement.
const EXPIRE_LIMIT_PER_STEP = 6;
// When we run a large expiration step, the above limit is multiplied by this.
const EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER = 10;

// When history is clean or dirty enough we will adapt the expiration algorithm
// to be more lazy or more aggressive.
// This is done acting on the interval between expiration steps and the number
// of expirable items.
// 1. Clean history:
//   We expire at (default interval * EXPIRE_AGGRESSIVITY_MULTIPLIER) the
//   default number of entries.
// 2. Dirty history:
//   We expire at the default interval, but a greater number of entries
//   (default number of entries * EXPIRE_AGGRESSIVITY_MULTIPLIER).
const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3;

// This is the average size in bytes of an URI entry in the database.
// Magic numbers are determined through analysis of the distribution of a ratio
// between number of unique URIs and database size among our users.
// Used as a fall back value when it's not possible to calculate the real value.
const URIENTRY_AVG_SIZE = 600;

// Seconds of idle time before starting a larger expiration step.
// Notice during idle we stop the expiration timer since we don't want to hurt
// stand-by or mobile devices batteries.
const IDLE_TIMEOUT_SECONDS = 5 * 60;

// If the number of pages over history limit is greater than this threshold,
// expiration will be more aggressive, to bring back history to a saner size.
const OVERLIMIT_PAGES_THRESHOLD = 1000;

const MSECS_PER_DAY = 86400000;
const ANNOS_EXPIRE_POLICIES = [
  { bind: "expire_days",
    type: Ci.nsIAnnotationService.EXPIRE_DAYS,
    time: 7 * 1000 * MSECS_PER_DAY },
  { bind: "expire_weeks",
    type: Ci.nsIAnnotationService.EXPIRE_WEEKS,
    time: 30 * 1000 * MSECS_PER_DAY },
  { bind: "expire_months",
    type: Ci.nsIAnnotationService.EXPIRE_MONTHS,
    time: 180 * 1000 * MSECS_PER_DAY },
];

// When we expire we can use these limits:
// - SMALL for usual partial expirations, will expire a small chunk.
// - LARGE for idle or shutdown expirations, will expire a large chunk.
// - UNLIMITED will expire all the orphans.
// - DEBUG will use a known limit, passed along with the debug notification.
const LIMIT = {
  SMALL: 0,
  LARGE: 1,
  UNLIMITED: 2,
  DEBUG: 3,
};

// Represents the status of history database.
const STATUS = {
  CLEAN: 0,
  DIRTY: 1,
  UNKNOWN: 2,
};

// Represents actions on which a query will run.
const ACTION = {
  TIMED:           1 << 0, // happens every this._interval
  TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits
  SHUTDOWN_DIRTY:  1 << 2, // happens at shutdown for DIRTY state
  IDLE_DIRTY:      1 << 3, // happens on idle for DIRTY state
  IDLE_DAILY:      1 << 4, // happens once a day on idle
  DEBUG:           1 << 5, // happens on TOPIC_DEBUG_START_EXPIRATION
};

// The queries we use to expire.
const EXPIRATION_QUERIES = {

  // Some visits can be expired more often than others, cause they are less
  // useful to the user and can pollute awesomebar results:
  // 1. urls over 255 chars
  // 2. redirect sources and downloads
  // Note: due to the REPLACE option, this should be executed before
  // QUERY_FIND_VISITS_TO_EXPIRE, that has a more complete result.
  QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify (v_id, url, guid, visit_date, reason)
          SELECT v.id, h.url, h.guid, v.visit_date, "exotic"
          FROM moz_historyvisits v
          JOIN moz_places h ON h.id = v.place_id
          WHERE visit_date < strftime('%s','now','localtime','start of day','-60 days','utc') * 1000000
          AND ( LENGTH(h.url) > 255 OR v.visit_type = 7 )
          ORDER BY v.visit_date ASC
          LIMIT :limit_visits`,
    actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
             ACTION.DEBUG
  },

  // Finds visits to be expired when history is over the unique pages limit,
  // otherwise will return nothing.
  // This explicitly excludes any visits added in the last 7 days, to protect
  // users with thousands of bookmarks from constantly losing history.
  QUERY_FIND_VISITS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify
            (v_id, url, guid, visit_date, expected_results)
          SELECT v.id, h.url, h.guid, v.visit_date, :limit_visits
          FROM moz_historyvisits v
          JOIN moz_places h ON h.id = v.place_id
          WHERE (SELECT COUNT(*) FROM moz_places) > :max_uris
          AND visit_date < strftime('%s','now','localtime','start of day','-7 days','utc') * 1000000
          ORDER BY v.visit_date ASC
          LIMIT :limit_visits`,
    actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
             ACTION.DEBUG
  },

  // Removes the previously found visits.
  QUERY_EXPIRE_VISITS: {
    sql: `DELETE FROM moz_historyvisits WHERE id IN (
            SELECT v_id FROM expiration_notify WHERE v_id NOTNULL
          )`,
    actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
             ACTION.DEBUG
  },

  // Finds orphan URIs in the database.
  // Notice we won't notify single removed URIs on History.clear(), so we don't
  // run this query in such a case, but just delete URIs.
  // This could run in the middle of adding a visit or bookmark to a new page.
  // In such a case since it is async, could end up expiring the orphan page
  // before it actually gets the new visit or bookmark.
  // Thus, since new pages get frecency -1, we filter on that.
  QUERY_FIND_URIS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify (p_id, url, guid, visit_date)
          SELECT h.id, h.url, h.guid, h.last_visit_date
          FROM moz_places h
          LEFT JOIN moz_historyvisits v ON h.id = v.place_id
          WHERE h.last_visit_date IS NULL
            AND h.foreign_count = 0
            AND v.id IS NULL
            AND frecency <> -1
          LIMIT :limit_uris`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire found URIs from the database.
  QUERY_EXPIRE_URIS: {
    sql: `DELETE FROM moz_places WHERE id IN (
            SELECT p_id FROM expiration_notify WHERE p_id NOTNULL
          ) AND foreign_count = 0 AND last_visit_date ISNULL`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Hosts accumulated during the places delete are updated through a trigger
  // (see nsPlacesTriggers.h).
  QUERY_UPDATE_HOSTS: {
    sql: `DELETE FROM moz_updatehosts_temp`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire orphan pages from the icons database.
  QUERY_EXPIRE_FAVICONS_PAGES: {
    sql: `DELETE FROM moz_pages_w_icons
          WHERE page_url_hash NOT IN (
            SELECT url_hash FROM moz_places
          )`,
    actions: ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire orphan icons from the database.
  QUERY_EXPIRE_FAVICONS: {
    sql: `DELETE FROM moz_icons
          WHERE root = 0 AND id NOT IN (
            SELECT icon_id FROM moz_icons_to_pages
          )`,
    actions: ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire orphan page annotations from the database.
  QUERY_EXPIRE_ANNOS: {
    sql: `DELETE FROM moz_annos WHERE id in (
            SELECT a.id FROM moz_annos a
            LEFT JOIN moz_places h ON a.place_id = h.id
            WHERE h.id IS NULL
            LIMIT :limit_annos
          )`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire page annotations based on expiration policy.
  QUERY_EXPIRE_ANNOS_WITH_POLICY: {
    sql: `DELETE FROM moz_annos
          WHERE (expiration = :expire_days
            AND :expire_days_time > MAX(lastModified, dateAdded))
             OR (expiration = :expire_weeks
            AND :expire_weeks_time > MAX(lastModified, dateAdded))
             OR (expiration = :expire_months
            AND :expire_months_time > MAX(lastModified, dateAdded))`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire items annotations based on expiration policy.
  QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY: {
    sql: `DELETE FROM moz_items_annos
          WHERE (expiration = :expire_days
            AND :expire_days_time > MAX(lastModified, dateAdded))
             OR (expiration = :expire_weeks
            AND :expire_weeks_time > MAX(lastModified, dateAdded))
             OR (expiration = :expire_months
            AND :expire_months_time > MAX(lastModified, dateAdded))`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire page annotations based on expiration policy.
  QUERY_EXPIRE_ANNOS_WITH_HISTORY: {
    sql: `DELETE FROM moz_annos
          WHERE expiration = :expire_with_history
            AND NOT EXISTS (SELECT id FROM moz_historyvisits
                            WHERE place_id = moz_annos.place_id LIMIT 1)`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire item annos without a corresponding item id.
  QUERY_EXPIRE_ITEMS_ANNOS: {
    sql: `DELETE FROM moz_items_annos WHERE id IN (
            SELECT a.id FROM moz_items_annos a
            LEFT JOIN moz_bookmarks b ON a.item_id = b.id
            WHERE b.id IS NULL
            LIMIT :limit_annos
          )`,
    actions: ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire all annotation names without a corresponding annotation.
  QUERY_EXPIRE_ANNO_ATTRIBUTES: {
    sql: `DELETE FROM moz_anno_attributes WHERE id IN (
            SELECT n.id FROM moz_anno_attributes n
            LEFT JOIN moz_annos a ON n.id = a.anno_attribute_id
            LEFT JOIN moz_items_annos t ON n.id = t.anno_attribute_id
            WHERE a.anno_attribute_id IS NULL
              AND t.anno_attribute_id IS NULL
            LIMIT :limit_annos
          )`,
    actions: ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY |
             ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Expire orphan inputhistory.
  QUERY_EXPIRE_INPUTHISTORY: {
    sql: `DELETE FROM moz_inputhistory WHERE place_id IN (
            SELECT i.place_id FROM moz_inputhistory i
            LEFT JOIN moz_places h ON h.id = i.place_id
            WHERE h.id IS NULL
            LIMIT :limit_inputhistory
          )`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Select entries for notifications.
  // If p_id is set whole_entry = 1, then we have expired the full page.
  // Either p_id or v_id are always set.
  QUERY_SELECT_NOTIFICATIONS: {
    sql: `SELECT url, guid, MAX(visit_date) AS visit_date,
                 MAX(IFNULL(MIN(p_id, 1), MIN(v_id, 0))) AS whole_entry,
                 MAX(expected_results) AS expected_results,
                 (SELECT MAX(visit_date) FROM expiration_notify
                  WHERE reason = "expired" AND url = n.url AND p_id ISNULL
                 ) AS most_recent_expired_visit
          FROM expiration_notify n
          GROUP BY url`,
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },

  // Empty the notifications table.
  QUERY_DELETE_NOTIFICATIONS: {
    sql: "DELETE FROM expiration_notify",
    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
             ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
  },
};

/**
 * Sends a bookmarks notification through the given observers.
 *
 * @param observers
 *        array of nsINavBookmarkObserver objects.
 * @param notification
 *        the notification name.
 * @param args
 *        array of arguments to pass to the notification.
 */
function notify(observers, notification, args = []) {
  for (let observer of observers) {
    try {
      observer[notification](...args);
    } catch (ex) {}
  }
}

// nsPlacesExpiration definition

function nsPlacesExpiration() {
  // Smart Getters

  XPCOMUtils.defineLazyGetter(this, "_db", function() {
    let db = Cc["@mozilla.org/browser/nav-history-service;1"].
             getService(Ci.nsPIPlacesDatabase).
             DBConnection;

    // Create the temporary notifications table.
    let stmt = db.createAsyncStatement(
      `CREATE TEMP TABLE expiration_notify (
         id INTEGER PRIMARY KEY
       , v_id INTEGER
       , p_id INTEGER
       , url TEXT NOT NULL
       , guid TEXT NOT NULL
       , visit_date INTEGER
       , expected_results INTEGER NOT NULL DEFAULT 0
       , reason TEXT NOT NULL DEFAULT "expired"
       )`);
    stmt.executeAsync();
    stmt.finalize();

    return db;
  });

  XPCOMUtils.defineLazyServiceGetter(this, "_idle",
                                     "@mozilla.org/widget/idleservice;1",
                                     "nsIIdleService");

  this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
                     getService(Ci.nsIPrefService).
                     getBranch(PREF_BRANCH);

  this._loadPrefsPromise = this._loadPrefs().then(() => {
    // Observe our preferences branch for changes.
    this._prefBranch.addObserver("", this, true);

    // Create our expiration timer.
    this._newTimer();
  }, Cu.reportError);

  // Register topic observers.
  Services.obs.addObserver(this, TOPIC_DEBUG_START_EXPIRATION, true);
  Services.obs.addObserver(this, TOPIC_IDLE_DAILY, true);

  // Block shutdown.
  let shutdownClient = PlacesUtils.history.connectionShutdownClient.jsclient;
  shutdownClient.addBlocker("Places Expiration: shutdown", () => {
    if (this._shuttingDown) {
      return;
    }
    this._shuttingDown = true;
    this.expireOnIdle = false;
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
    // If the database is dirty, we want to expire some entries, to speed up
    // the expiration process.
    if (this.status == STATUS.DIRTY) {
      this._expireWithActionAndLimit(ACTION.SHUTDOWN_DIRTY, LIMIT.LARGE);
    }
    this._finalizeInternalStatements();
  });
}

nsPlacesExpiration.prototype = {
  observe: function PEX_observe(aSubject, aTopic, aData) {
    if (this._shuttingDown) {
      return;
    }

    if (aTopic == TOPIC_PREF_CHANGED) {
      this._loadPrefsPromise = this._loadPrefs().then(() => {
        if (aData == PREF_INTERVAL_SECONDS) {
          // Renew the timer with the new interval value.
          this._newTimer();
        }
      }, Cu.reportError);
    } else if (aTopic == TOPIC_DEBUG_START_EXPIRATION) {
      // The passed-in limit is the maximum number of visits to expire when
      // history is over capacity.  Mind to correctly handle the NaN value.
      let limit = parseInt(aData);
      if (limit == -1) {
        // Everything should be expired without any limit.  If history is over
        // capacity then all existing visits will be expired.
        // Should only be used in tests, since may cause dataloss.
        this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.UNLIMITED);
      } else if (limit > 0) {
        // The number of expired visits is limited by this amount.  It may be
        // used for testing purposes, like checking that limited queries work.
        this._debugLimit = limit;
        this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG);
      } else {
        // Any other value is intended as a 0 limit, that means no visits
        // will be expired.  Even if this doesn't touch visits, it will remove
        // any orphan pages, icons, annotations and similar from the database,
        // so it may be used for cleanup purposes.
        this._debugLimit = -1;
        this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG);
      }
    } else if (aTopic == TOPIC_IDLE_BEGIN) {
      // Stop the expiration timer.  We don't want to keep up expiring on idle
      // to preserve batteries on mobile devices and avoid killing stand-by.
      if (this._timer) {
        this._timer.cancel();
        this._timer = null;
      }
      if (this.expireOnIdle)
        this._expireWithActionAndLimit(ACTION.IDLE_DIRTY, LIMIT.LARGE);
    } else if (aTopic == TOPIC_IDLE_END) {
      // Restart the expiration timer.
      if (!this._timer)
        this._newTimer();
    } else if (aTopic == TOPIC_IDLE_DAILY) {
      this._expireWithActionAndLimit(ACTION.IDLE_DAILY, LIMIT.LARGE);
    } else if (aTopic == TOPIC_TESTING_MODE) {
      this._testingMode = true;
    }
  },

  // nsINavHistoryObserver

  _inBatchMode: false,
  onBeginUpdateBatch: function PEX_onBeginUpdateBatch() {
    this._inBatchMode = true;

    // We do not want to expire while we are doing batch work.
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  onEndUpdateBatch: function PEX_onEndUpdateBatch() {
    this._inBatchMode = false;

    // Restore timer.
    if (!this._timer)
      this._newTimer();
  },

  onClearHistory: function PEX_onClearHistory() {
    // History status is clean after a clear history.
    this.status = STATUS.CLEAN;
  },

  onVisit() {},
  onTitleChanged() {},
  onDeleteURI() {},
  onPageChanged() {},
  onDeleteVisits() {},

  // nsITimerCallback

  notify: function PEX_timerCallback() {
    // Check if we are over history capacity, if so visits must be expired.
    this._getPagesStats((aPagesCount) => {
      let overLimitPages = aPagesCount - this._urisLimit;
      this._overLimit = overLimitPages > 0;
      let action = this._overLimit ? ACTION.TIMED_OVERLIMIT : ACTION.TIMED;
      // Adapt expiration aggressivity to the number of pages over the limit.
      let limit = overLimitPages > OVERLIMIT_PAGES_THRESHOLD ? LIMIT.LARGE
                                                             : LIMIT.SMALL;

      this._expireWithActionAndLimit(action, limit);
    });
  },

  // mozIStorageStatementCallback

  handleResult: function PEX_handleResult(aResultSet) {
    // We don't want to notify after shutdown.
    if (this._shuttingDown)
      return;

    let row;
    while ((row = aResultSet.getNextRow())) {
      // expected_results is set to the number of expected visits by
      // QUERY_FIND_VISITS_TO_EXPIRE.  We decrease that counter for each found
      // visit and if it reaches zero we mark the database as dirty, since all
      // the expected visits were expired, so it's likely the next run will
      // find more.
      let expectedResults = row.getResultByName("expected_results");
      if (expectedResults > 0) {
        if (!("_expectedResultsCount" in this)) {
          this._expectedResultsCount = expectedResults;
        }
        if (this._expectedResultsCount > 0) {
          this._expectedResultsCount--;
        }
      }

      let uri = Services.io.newURI(row.getResultByName("url"));
      let guid = row.getResultByName("guid");
      let visitDate = row.getResultByName("visit_date");
      let wholeEntry = row.getResultByName("whole_entry");
      let mostRecentExpiredVisit = row.getResultByName("most_recent_expired_visit");
      let reason = Ci.nsINavHistoryObserver.REASON_EXPIRED;
      let observers = PlacesUtils.history.getObservers();

      if (mostRecentExpiredVisit) {
        let days = parseInt((Date.now() - (mostRecentExpiredVisit / 1000)) / MSECS_PER_DAY);
        if (!this._mostRecentExpiredVisitDays) {
          this._mostRecentExpiredVisitDays = days;
        } else if (days < this._mostRecentExpiredVisitDays) {
          this._mostRecentExpiredVisitDays = days;
        }
      }

      // Dispatch expiration notifications to history.
      if (wholeEntry) {
        notify(observers, "onDeleteURI", [uri, guid, reason]);
      } else {
        notify(observers, "onDeleteVisits", [uri, visitDate, guid, reason, 0]);
      }
    }
  },

  handleError: function PEX_handleError(aError) {
    Cu.reportError("Async statement execution returned with '" +
                   aError.result + "', '" + aError.message + "'");
  },

  // Number of expiration steps needed to reach a CLEAN status.
  _telemetrySteps: 1,
  handleCompletion: function PEX_handleCompletion(aReason) {
    if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
      if (this._mostRecentExpiredVisitDays) {
        try {
          Services.telemetry
                  .getHistogramById("PLACES_MOST_RECENT_EXPIRED_VISIT_DAYS")
                  .add(this._mostRecentExpiredVisitDays);
        } catch (ex) {
          Components.utils.reportError("Unable to report telemetry.");
        } finally {
          delete this._mostRecentExpiredVisitDays;
        }
      }

      if ("_expectedResultsCount" in this) {
        // Adapt the aggressivity of steps based on the status of history.
        // A dirty history will return all the entries we are expecting bringing
        // our countdown to zero, while a clean one will not.
        let oldStatus = this.status;
        this.status = this._expectedResultsCount == 0 ? STATUS.DIRTY
                                                      : STATUS.CLEAN;

        // Collect or send telemetry data.
        if (this.status == STATUS.DIRTY) {
          this._telemetrySteps++;
        } else {
          // Avoid reporting the common cases where the database is clean, or
          // a single step is needed.
          if (oldStatus == STATUS.DIRTY) {
            try {
              Services.telemetry
                      .getHistogramById("PLACES_EXPIRATION_STEPS_TO_CLEAN2")
                      .add(this._telemetrySteps);
            } catch (ex) {
              Components.utils.reportError("Unable to report telemetry.");
            }
          }
          this._telemetrySteps = 1;
        }

        delete this._expectedResultsCount;
      }

      // Dispatch a notification that expiration has finished.
      Services.obs.notifyObservers(null, TOPIC_EXPIRATION_FINISHED);
    }
  },

  // nsPlacesExpiration

  _urisLimit: PREF_MAX_URIS_NOTSET,
  _interval: PREF_INTERVAL_SECONDS_NOTSET,
  _shuttingDown: false,

  _status: STATUS.UNKNOWN,
  set status(aNewStatus) {
    if (aNewStatus != this._status) {
      // If status changes we should restart the timer.
      this._status = aNewStatus;
      this._newTimer();
      // If needed add/remove the cleanup step on idle.  We want to expire on
      // idle only if history is dirty, to preserve mobile devices batteries.
      this.expireOnIdle = aNewStatus == STATUS.DIRTY;
    }
    return aNewStatus;
  },
  get status() {
    return this._status;
  },

  _isIdleObserver: false,
  _expireOnIdle: false,
  set expireOnIdle(aExpireOnIdle) {
    // Observe idle regardless aExpireOnIdle, since we always want to stop
    // timed expiration on idle, to preserve mobile battery life.
    if (!this._isIdleObserver && !this._shuttingDown) {
      this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
      this._isIdleObserver = true;
    } else if (this._isIdleObserver && this._shuttingDown) {
      this._idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
      this._isIdleObserver = false;
    }

    // If running a debug expiration we need full control of what happens
    // but idle cleanup could activate in the middle, since tinderboxes are
    // permanently idle.  That would cause unexpected oranges, so disable it.
    if (this._debugLimit !== undefined)
      this._expireOnIdle = false;
    else
      this._expireOnIdle = aExpireOnIdle;
    return this._expireOnIdle;
  },
  get expireOnIdle() {
    return this._expireOnIdle;
  },

  async _loadPrefs() {
    // Get the user's limit, if it was set.
    this._urisLimit = this._prefBranch.getIntPref(PREF_MAX_URIS,
                                                  PREF_MAX_URIS_NOTSET);
    if (this._urisLimit < 0) {
      // Some testing code expects a pref change to be synchronous, so
      // temporarily set this to a large value, while we asynchronously update
      // to the correct value.
      this._urisLimit = 100000;

      // The user didn't specify a custom limit, so we calculate the number of
      // unique places that may fit an optimal database size on this hardware.
      // Oldest pages over this threshold will be expired.
      let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
      try {
        // Limit the size on systems with small memory.
         memSizeBytes = Services.sysinfo.getProperty("memsize");
      } catch (ex) {}
      if (memSizeBytes <= 0) {
        memSizeBytes = MEMSIZE_FALLBACK_BYTES;
      }

      let diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
      try {
        // Protect against a full disk or tiny quota.
        let dbFile = this._db.databaseFile;
        dbFile.QueryInterface(Ci.nsILocalFile);
        diskAvailableBytes = dbFile.diskSpaceAvailable;
      } catch (ex) {}
      if (diskAvailableBytes <= 0) {
        diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
      }

      let optimalDatabaseSize = Math.min(
        memSizeBytes * DATABASE_TO_MEMORY_PERC / 100,
        diskAvailableBytes * DATABASE_TO_DISK_PERC / 100,
        DATABASE_MAX_SIZE
      );

      // Calculate avg size of a URI in the database.
      let db;
      try {
        db = await PlacesUtils.promiseDBConnection();
        if (db) {
          let row = (await db.execute(`SELECT * FROM pragma_page_size(),
                                                pragma_page_count(),
                                                pragma_freelist_count(),
                                                (SELECT count(*) FROM moz_places)`))[0];
          let pageSize = row.getResultByIndex(0);
          let pageCount = row.getResultByIndex(1);
          let freelistCount = row.getResultByIndex(2);
          let uriCount = row.getResultByIndex(3);
          let dbSize = (pageCount - freelistCount) * pageSize;
          let avgURISize = Math.ceil(dbSize / uriCount);
          // For new profiles this value may be too large, due to the Sqlite header,
          // or Infinity when there are no pages.  Thus we must limit it.
          if (avgURISize > (URIENTRY_AVG_SIZE * 3)) {
            avgURISize = URIENTRY_AVG_SIZE;
          }
          this._urisLimit = Math.ceil(optimalDatabaseSize / avgURISize);
        }
      } catch (ex) {
        // We may have been initialized late in the shutdown process, maybe
        // by a call to clear history on shutdown.
        // If we're unable to get a connection clone, we'll just proceed with
        // the default value, it should not be critical at this point in the
        // application life-cycle.
      }
    }

    // Expose the calculated limit to other components.
    this._prefBranch.setIntPref(PREF_READONLY_CALCULATED_MAX_URIS,
                                this._urisLimit);

    // Get the expiration interval value.
    this._interval = this._prefBranch.getIntPref(PREF_INTERVAL_SECONDS,
                                                 PREF_INTERVAL_SECONDS_NOTSET);
    if (this._interval <= 0) {
      this._interval = PREF_INTERVAL_SECONDS_NOTSET;
    }
  },

  /**
   * Evaluates the real number of pages in the database and the value currently
   * used by the SQLite query planner.
   *
   * @param aCallback
   *        invoked on success, function (aPagesCount).
   */
  _getPagesStats: function PEX__getPagesStats(aCallback) {
    if (!this._cachedStatements["LIMIT_COUNT"]) {
      this._cachedStatements["LIMIT_COUNT"] = this._db.createAsyncStatement(
        `SELECT COUNT(*) FROM moz_places`
      );
    }
    this._cachedStatements["LIMIT_COUNT"].executeAsync({
      _pagesCount: 0,
      handleResult(aResults) {
        let row = aResults.getNextRow();
        this._pagesCount = row.getResultByIndex(0);
      },
      handleCompletion(aReason) {
        if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
          aCallback(this._pagesCount);
        }
      },
      handleError(aError) {
        Cu.reportError("Async statement execution returned with '" +
                       aError.result + "', '" + aError.message + "'");
      }
    });
  },

  /**
   * Execute async statements to expire with the specified queries.
   *
   * @param aAction
   *        The ACTION we are expiring for.  See the ACTION const for values.
   * @param aLimit
   *        Whether to use small, large or no limits when expiring.  See the
   *        LIMIT const for values.
   */
  _expireWithActionAndLimit:
  function PEX__expireWithActionAndLimit(aAction, aLimit) {
    (async () => {
      // Ensure that we'll run statements with the most up-to-date pref values.
      // On shutdown we cannot do this, since we must enqueue the expiration
      // statements synchronously before the connection goes away.
      // TODO (Bug 1275878): handle this properly through a shutdown blocker.
      if (!this._shuttingDown)
        await this._loadPrefsPromise;

      // Skip expiration during batch mode.
      if (this._inBatchMode)
        return;
      // Don't try to further expire after shutdown.
      if (this._shuttingDown && aAction != ACTION.SHUTDOWN_DIRTY) {
        return;
      }

      let boundStatements = [];
      for (let queryType in EXPIRATION_QUERIES) {
        if (EXPIRATION_QUERIES[queryType].actions & aAction)
          boundStatements.push(this._getBoundStatement(queryType, aLimit, aAction));
      }


      // Execute statements asynchronously in a transaction.
      this._db.executeAsync(boundStatements, boundStatements.length, this);
    })().catch(Cu.reportError);
  },

  /**
   * Finalizes all of our mozIStorageStatements so we can properly close the
   * database.
   */
  _finalizeInternalStatements: function PEX__finalizeInternalStatements() {
    for (let queryType in this._cachedStatements) {
      let stmt = this._cachedStatements[queryType];
      stmt.finalize();
    }
  },

  /**
   * Generate the statement used for expiration.
   *
   * @param aQueryType
   *        Type of the query to build statement for.
   * @param aLimit
   *        Whether to use small, large or no limits when expiring.  See the
   *        LIMIT const for values.
   * @param aAction
   *        Current action causing the expiration.  See the ACTION const.
   */
  _cachedStatements: {},
  _getBoundStatement: function PEX__getBoundStatement(aQueryType, aLimit, aAction) {
    // Statements creation can be expensive, so we want to cache them.
    let stmt = this._cachedStatements[aQueryType];
    if (stmt === undefined) {
      stmt = this._cachedStatements[aQueryType] =
        this._db.createAsyncStatement(EXPIRATION_QUERIES[aQueryType].sql);
    }

    let baseLimit;
    switch (aLimit) {
      case LIMIT.UNLIMITED:
        baseLimit = -1;
        break;
      case LIMIT.SMALL:
        baseLimit = EXPIRE_LIMIT_PER_STEP;
        break;
      case LIMIT.LARGE:
        baseLimit = EXPIRE_LIMIT_PER_STEP * EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER;
        break;
      case LIMIT.DEBUG:
        baseLimit = this._debugLimit;
        break;
    }
    if (this.status == STATUS.DIRTY && aAction != ACTION.DEBUG &&
        baseLimit > 0) {
      baseLimit *= EXPIRE_AGGRESSIVITY_MULTIPLIER;
    }

    // Bind the appropriate parameters.
    let params = stmt.params;
    switch (aQueryType) {
      case "QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE":
        // Avoid expiring all visits in case of an unlimited debug expiration,
        // just remove orphans instead.
        params.limit_visits =
          aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit;
        break;
      case "QUERY_FIND_VISITS_TO_EXPIRE":
        params.max_uris = this._urisLimit;
        // Avoid expiring all visits in case of an unlimited debug expiration,
        // just remove orphans instead.
        params.limit_visits =
          aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit;
        break;
      case "QUERY_FIND_URIS_TO_EXPIRE":
        params.limit_uris = baseLimit;
        break;
      case "QUERY_EXPIRE_ANNOS":
        // Each page may have multiple annos.
        params.limit_annos = baseLimit * EXPIRE_AGGRESSIVITY_MULTIPLIER;
        break;
      case "QUERY_EXPIRE_ANNOS_WITH_POLICY":
      case "QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY":
        let microNow = Date.now() * 1000;
        ANNOS_EXPIRE_POLICIES.forEach(function(policy) {
          params[policy.bind] = policy.type;
          params[policy.bind + "_time"] = microNow - policy.time;
        });
        break;
      case "QUERY_EXPIRE_ANNOS_WITH_HISTORY":
        params.expire_with_history = Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY;
        break;
      case "QUERY_EXPIRE_ITEMS_ANNOS":
        params.limit_annos = baseLimit;
        break;
      case "QUERY_EXPIRE_ANNO_ATTRIBUTES":
        params.limit_annos = baseLimit;
        break;
      case "QUERY_EXPIRE_INPUTHISTORY":
        params.limit_inputhistory = baseLimit;
        break;
    }

    return stmt;
  },

  /**
   * Creates a new timer based on this._interval.
   *
   * @return a REPEATING_SLACK nsITimer that runs every this._interval.
   */
  _newTimer: function PEX__newTimer() {
    if (this._timer)
      this._timer.cancel();
    if (this._shuttingDown)
      return undefined;
    let interval = this.status != STATUS.DIRTY ?
      this._interval * EXPIRE_AGGRESSIVITY_MULTIPLIER : this._interval;

    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(this, interval * 1000,
                           Ci.nsITimer.TYPE_REPEATING_SLACK);
    if (this._testingMode) {
      Services.obs.notifyObservers(null, TOPIC_TEST_INTERVAL_CHANGED,
                                   interval);
    }
    return this._timer = timer;
  },

  // nsISupports

  classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesExpiration),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsINavHistoryObserver,
    Ci.nsITimerCallback,
    Ci.mozIStorageStatementCallback,
    Ci.nsISupportsWeakReference
  ])
};

// Module Registration

var components = [nsPlacesExpiration];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<_okk%components/PageIconProtocolHandler.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");

function makeDefaultFaviconChannel(uri, loadInfo) {
  let channel = Services.io.newChannelFromURIWithLoadInfo(
    PlacesUtils.favicons.defaultFavicon, loadInfo);
  channel.originalURI = uri;
  channel.contentType = PlacesUtils.favicons.defaultFaviconMimeType;
  return channel;
}

function streamDefaultFavicon(uri, loadInfo, outputStream, originalChannel) {
  try {
    // Open up a new channel to get that data, and push it to our output stream.
    // Create a listener to hand data to the pipe's output stream.
    let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
                     .createInstance(Ci.nsISimpleStreamListener);
    listener.init(outputStream, {
      onStartRequest(request, context) {},
      onStopRequest(request, context, statusCode) {
        // We must close the outputStream regardless.
        outputStream.close();
      }
    });
    originalChannel.contentType = PlacesUtils.favicons.defaultFaviconMimeType;
    let defaultIconChannel = makeDefaultFaviconChannel(uri, loadInfo);
    defaultIconChannel.asyncOpen2(listener);
  } catch (ex) {
    Cu.reportError(ex);
    outputStream.close();
  }
}

function serveIcon(pipe, data, len) {
  // Pass the icon data to the output stream.
  let stream = Cc["@mozilla.org/binaryoutputstream;1"]
                 .createInstance(Ci.nsIBinaryOutputStream);
  stream.setOutputStream(pipe.outputStream);
  stream.writeByteArray(data, len);
  stream.close();
  pipe.outputStream.close();
}

function PageIconProtocolHandler() {
}

PageIconProtocolHandler.prototype = {
  get scheme() {
    return "page-icon";
  },

  get defaultPort() {
    return -1;
  },

  get protocolFlags() {
    return Ci.nsIProtocolHandler.URI_NORELATIVE |
           Ci.nsIProtocolHandler.URI_NOAUTH |
           Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
           Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE;
  },

  newURI(spec, originCharset, baseURI) {
    let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
    uri.spec = spec;
    return uri;
  },

  newChannel2(uri, loadInfo) {
    try {
      // Create a pipe that will give us an output stream that we can use once
      // we got all the favicon data.
      let pipe = Cc["@mozilla.org/pipe;1"]
                   .createInstance(Ci.nsIPipe);
      pipe.init(true, true, 0, Ci.nsIFaviconService.MAX_FAVICON_BUFFER_SIZE);

      // Create our channel.
      let channel = Cc["@mozilla.org/network/input-stream-channel;1"]
                      .createInstance(Ci.nsIInputStreamChannel);
      channel.QueryInterface(Ci.nsIChannel);
      channel.setURI(uri);
      channel.contentStream = pipe.inputStream;
      channel.loadInfo = loadInfo;

      let pageURI = NetUtil.newURI(uri.path.replace(/[&#]size=[^&]+$/, ""));
      let preferredSize = PlacesUtils.favicons.preferredSizeFromURI(uri);
      PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconURI, len, data, mimeType) => {
        if (len == 0) {
          streamDefaultFavicon(uri, loadInfo, pipe.outputStream, channel);
        } else {
          try {
            channel.contentType = mimeType;
            channel.contentLength = len;
            serveIcon(pipe, data, len);
          } catch (ex) {
            streamDefaultFavicon(uri, loadInfo, pipe.outputStream, channel);
          }
        }
      }, preferredSize);

      return channel;
    } catch (ex) {
      return makeDefaultFaviconChannel(uri, loadInfo);
    }
  },

  newChannel(uri) {
    return this.newChannel2(uri, null);
  },

  allowPort(port, scheme) {
    return false;
  },

  classID: Components.ID("{60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}"),
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIProtocolHandler
  ])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PageIconProtocolHandler]);
PK
!<<û

%components/PlacesCategoriesStarter.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const {utils: Cu, classes: Cc, interfaces: Ci} = Components;

// Fired by TelemetryController when async telemetry data should be collected.
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";

// Seconds between maintenance runs.
const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
                                  "resource://gre/modules/PlacesDBUtils.jsm");

/**
 * This component can be used as a starter for modules that have to run when
 * certain categories are invoked.
 */
function PlacesCategoriesStarter() {
  Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY);
  Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
}

PlacesCategoriesStarter.prototype = {
  observe: function PCS_observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case PlacesUtils.TOPIC_SHUTDOWN:
        Services.obs.removeObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
        Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
        if (Cu.isModuleLoaded("resource://gre/modules/PlacesDBUtils.jsm")) {
          PlacesDBUtils.shutdown();
        }
        break;
      case TOPIC_GATHER_TELEMETRY:
        PlacesDBUtils.telemetry();
        break;
      case PlacesUtils.TOPIC_INIT_COMPLETE:
        // Init keywords so it starts listening to bookmarks notifications.
        PlacesUtils.keywords;
        break;
      case "idle-daily":
        // Once a week run places.sqlite maintenance tasks.
        let lastMaintenance =
          Services.prefs.getIntPref("places.database.lastMaintenance", 0);
        let nowSeconds = parseInt(Date.now() / 1000);
        if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
          PlacesDBUtils.maintenanceOnIdle();
        }
        break;
      default:
        throw new Error("Trying to handle an unknown category.");
    }
  },

  classID: Components.ID("803938d5-e26d-4453-bf46-ad4b26e41114"),
  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PlacesCategoriesStarter),
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
  ])
};

var components = [PlacesCategoriesStarter];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<9Mcomponents/ColorAnalyzer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const XHTML_NS = "http://www.w3.org/1999/xhtml";
const MAXIMUM_PIXELS = Math.pow(144, 2);

function ColorAnalyzer() {
  // a queue of callbacks for each job we give to the worker
  this.callbacks = [];

  this.hiddenWindowDoc = Cc["@mozilla.org/appshell/appShellService;1"].
                         getService(Ci.nsIAppShellService).
                         hiddenDOMWindow.document;

  this.worker = new ChromeWorker("resource://gre/modules/ColorAnalyzer_worker.js");
  this.worker.onmessage = this.onWorkerMessage.bind(this);
  this.worker.onerror = this.onWorkerError.bind(this);
}

ColorAnalyzer.prototype = {
  findRepresentativeColor: function ColorAnalyzer_frc(imageURI, callback) {
    function cleanup() {
      image.removeEventListener("load", loadListener);
      image.removeEventListener("error", errorListener);
    }
    let image = this.hiddenWindowDoc.createElementNS(XHTML_NS, "img");
    let loadListener = this.onImageLoad.bind(this, image, callback, cleanup);
    let errorListener = this.onImageError.bind(this, image, callback, cleanup);
    image.addEventListener("load", loadListener);
    image.addEventListener("error", errorListener);
    image.src = imageURI.spec;
  },

  onImageLoad: function ColorAnalyzer_onImageLoad(image, callback, cleanup) {
    if (image.naturalWidth * image.naturalHeight > MAXIMUM_PIXELS) {
      // this will probably take too long to process - fail
      callback.onComplete(false);
    } else {
      let canvas = this.hiddenWindowDoc.createElementNS(XHTML_NS, "canvas");
      canvas.width = image.naturalWidth;
      canvas.height = image.naturalHeight;
      let ctx = canvas.getContext("2d");
      ctx.drawImage(image, 0, 0);
      this.startJob(ctx.getImageData(0, 0, canvas.width, canvas.height),
                    callback);
    }
    cleanup();
  },

  onImageError: function ColorAnalyzer_onImageError(image, callback, cleanup) {
    Cu.reportError("ColorAnalyzer: image at " + image.src + " didn't load");
    callback.onComplete(false);
    cleanup();
  },

  startJob: function ColorAnalyzer_startJob(imageData, callback) {
    this.callbacks.push(callback);
    this.worker.postMessage({ imageData, maxColors: 1 });
  },

  onWorkerMessage: function ColorAnalyzer_onWorkerMessage(event) {
    // colors can be empty on failure
    if (event.data.colors.length < 1) {
      this.callbacks.shift().onComplete(false);
    } else {
      this.callbacks.shift().onComplete(true, event.data.colors[0]);
    }
  },

  onWorkerError: function ColorAnalyzer_onWorkerError(error) {
    // this shouldn't happen, but just in case
    error.preventDefault();
    Cu.reportError("ColorAnalyzer worker: " + error.message);
    this.callbacks.shift().onComplete(false);
  },

  classID: Components.ID("{d056186c-28a0-494e-aacc-9e433772b143}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.mozIColorAnalyzer])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ColorAnalyzer]);
PK
!<7(`` components/PageThumbsProtocol.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * PageThumbsProtocol.js
 *
 * This file implements the moz-page-thumb:// protocol and the corresponding
 * channel delivering cached thumbnails.
 *
 * URL structure:
 *
 * moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F&revision=XX
 *
 * This URL requests an image for 'http://www.mozilla.org/'.
 * The value of the revision key may change when the stored thumbnail changes.
 */

"use strict";

const Cu = Components.utils;
const Cc = Components.classes;
const Cr = Components.results;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
  "resource://gre/modules/FileUtils.jsm");

const SUBSTITUTING_URL_CID = "{dea9657c-18cf-4984-bde9-ccef5d8ab473}";

/**
 * Implements the thumbnail protocol handler responsible for moz-page-thumb: URLs.
 */
function Protocol() {
}

Protocol.prototype = {
  /**
   * The scheme used by this protocol.
   */
  get scheme() {
    return PageThumbs.scheme;
  },

  /**
   * The default port for this protocol (we don't support ports).
   */
  get defaultPort() {
    return -1;
  },

  /**
   * The flags specific to this protocol implementation.
   */
  get protocolFlags() {
    return Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
           Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
           Ci.nsIProtocolHandler.URI_NORELATIVE |
           Ci.nsIProtocolHandler.URI_NOAUTH;
  },

  /**
   * Creates a new URI object that is suitable for loading by this protocol.
   * @param aSpec The URI string in UTF8 encoding.
   * @param aOriginCharset The charset of the document from which the URI originated.
   * @return The newly created URI.
   */
  newURI: function Proto_newURI(aSpec, aOriginCharset) {
    let uri = Components.classesByID[SUBSTITUTING_URL_CID].createInstance(Ci.nsIURL);
    uri.spec = aSpec;
    return uri;
  },

  /**
   * Constructs a new channel from the given URI for this protocol handler.
   * @param aURI The URI for which to construct a channel.
   * @param aLoadInfo The Loadinfo which to use on the channel.
   * @return The newly created channel.
   */
  newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
    let {file} = aURI.QueryInterface(Ci.nsIFileURL);
    let fileuri = Services.io.newFileURI(file);
    let channel = Services.io.newChannelFromURIWithLoadInfo(fileuri, aLoadInfo);
    channel.originalURI = aURI;
    return channel;
  },

  newChannel: function Proto_newChannel(aURI) {
    return this.newChannel2(aURI, null);
  },

  /**
   * Decides whether to allow a blacklisted port.
   * @return Always false, we'll never allow ports.
   */
  allowPort: () => false,

  // nsISubstitutingProtocolHandler methods

  /*
   * Substituting the scheme and host isn't enough, we also transform the path.
   * So declare no-op implementations for (get|set|has)Substitution methods and
   * do all the work in resolveURI.
   */

  setSubstitution(root, baseURI) {},

  getSubstitution(root) {
    throw Cr.NS_ERROR_NOT_AVAILABLE;
  },

  hasSubstitution(root) {
    return false;
  },

  resolveURI(resURI) {
    let {url} = parseURI(resURI);
    let path = PageThumbsStorage.getFilePathForURL(url);
    return OS.Path.toFileURI(path);
  },

  // xpcom machinery
  classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
                                         Ci.nsISubstitutingProtocolHandler])
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]);

/**
 * Parses a given URI and extracts all parameters relevant to this protocol.
 * @param aURI The URI to parse.
 * @return The parsed parameters.
 */
function parseURI(aURI) {
  if (aURI.host != PageThumbs.staticHost)
    throw Cr.NS_ERROR_NOT_AVAILABLE;

  let {query} = aURI.QueryInterface(Ci.nsIURL);
  let params = {};

  query.split("&").forEach(function(aParam) {
    let [key, value] = aParam.split("=").map(decodeURIComponent);
    params[key.toLowerCase()] = value;
  });

  return params;
}
PK
!<X<x components/mozProtocolHandler.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");

function mozProtocolHandler() {
  XPCOMUtils.defineLazyPreferenceGetter(this, "urlToLoad", "toolkit.mozprotocol.url",
                                        "https://www.mozilla.org/protocol");
}

mozProtocolHandler.prototype = {
  scheme: "moz",
  defaultPort: -1,
  protocolFlags: Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,

  newURI(spec, charset, base) {
    let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
    if (base) {
      uri.spec = base.resolve(spec);
    } else {
      uri.spec = spec;
    }
    return uri;
  },

  newChannel2(uri, loadInfo) {
    let realURL = NetUtil.newURI(this.urlToLoad);
    let channel = Services.io.newChannelFromURIWithLoadInfo(realURL, loadInfo);
    loadInfo.resultPrincipalURI = realURL;
    return channel;
  },

  newChannel(uri) {
    return this.newChannel2(uri, null);
  },

  classID: Components.ID("{47a45e5f-691e-4799-8686-14f8d3fc0f8c}"),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([mozProtocolHandler]);
PK
!<|ãcomponents/nsDefaultCLH.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const nsISupports              = Components.interfaces.nsISupports;

const nsICommandLine           = Components.interfaces.nsICommandLine;
const nsICommandLineHandler    = Components.interfaces.nsICommandLineHandler;
const nsIPrefBranch            = Components.interfaces.nsIPrefBranch;
const nsISupportsString        = Components.interfaces.nsISupportsString;
const nsIWindowWatcher         = Components.interfaces.nsIWindowWatcher;
const nsIProperties            = Components.interfaces.nsIProperties;
const nsIFile                  = Components.interfaces.nsIFile;
const nsISimpleEnumerator      = Components.interfaces.nsISimpleEnumerator;

/**
 * This file provides a generic default command-line handler.
 *
 * It opens the chrome window specified by the pref "toolkit.defaultChromeURI"
 * with the flags specified by the pref "toolkit.defaultChromeFeatures"
 * or "chrome,dialog=no,all" is it is not available.
 * The arguments passed to the window are the nsICommandLine instance.
 *
 * It doesn't do anything if the pref "toolkit.defaultChromeURI" is unset.
 */

function getDirectoryService() {
  return Components.classes["@mozilla.org/file/directory_service;1"]
                   .getService(nsIProperties);
}

function nsDefaultCLH() { }
nsDefaultCLH.prototype = {
  classID: Components.ID("{6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344}"),

  /* nsISupports */

  QueryInterface: XPCOMUtils.generateQI([nsICommandLineHandler]),

  /* nsICommandLineHandler */

  handle: function clh_handle(cmdLine) {
    var printDir;
    while ((printDir = cmdLine.handleFlagWithParam("print-xpcom-dir", false))) {
      var out = "print-xpcom-dir(\"" + printDir + "\"): ";
      try {
        out += getDirectoryService().get(printDir, nsIFile).path;
      } catch (e) {
        out += "<Not Provided>";
      }

      dump(out + "\n");
      Components.utils.reportError(out);
    }

    var printDirList;
    while ((printDirList = cmdLine.handleFlagWithParam("print-xpcom-dirlist",
                                                       false))) {
      out = "print-xpcom-dirlist(\"" + printDirList + "\"): ";
      try {
        var list = getDirectoryService().get(printDirList,
                                             nsISimpleEnumerator);
        while (list.hasMoreElements())
          out += list.getNext().QueryInterface(nsIFile).path + ";";
      } catch (e) {
        out += "<Not Provided>";
      }

      dump(out + "\n");
      Components.utils.reportError(out);
    }

    if (cmdLine.handleFlag("silent", false)) {
      cmdLine.preventDefault = true;
    }

    if (cmdLine.preventDefault)
      return;

    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(nsIPrefBranch);

    try {
      var singletonWindowType =
                              prefs.getCharPref("toolkit.singletonWindowType");
      var windowMediator =
                Components.classes["@mozilla.org/appshell/window-mediator;1"]
                          .getService(Components.interfaces.nsIWindowMediator);

      var win = windowMediator.getMostRecentWindow(singletonWindowType);
      if (win) {
        win.focus();
        cmdLine.preventDefault = true;
        return;
      }
    } catch (e) { }

    // if the pref is missing, ignore the exception
    try {
      var chromeURI = prefs.getCharPref("toolkit.defaultChromeURI");

      var flags = prefs.getCharPref("toolkit.defaultChromeFeatures", "chrome,dialog=no,all");

      var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                            .getService(nsIWindowWatcher);
      wwatch.openWindow(null, chromeURI, "_blank",
                        flags, cmdLine);
    } catch (e) { }
  },

  helpInfo: "",
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDefaultCLH]);
PK
!<-WW"components/nsContentPrefService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;

const CACHE_MAX_GROUP_ENTRIES = 100;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

function ContentPrefService() {
  if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
    return Cu.import("resource://gre/modules/ContentPrefServiceChild.jsm")
             .ContentPrefServiceChild;
  }

  // If this throws an exception, it causes the getService call to fail,
  // but the next time a consumer tries to retrieve the service, we'll try
  // to initialize the database again, which might work if the failure
  // was due to a temporary condition (like being out of disk space).
  this._dbInit();

  this._observerSvc.addObserver(this, "last-pb-context-exited");

  // Observe shutdown so we can shut down the database connection.
  this._observerSvc.addObserver(this, "xpcom-shutdown");
}

Cu.import("resource://gre/modules/ContentPrefStore.jsm");
const cache = new ContentPrefStore();
cache.set = function CPS_cache_set(group, name, val) {
  Object.getPrototypeOf(this).set.apply(this, arguments);
  let groupCount = this._groups.size;
  if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
    // Clean half of the entries
    for (let [group, name, ] of this) {
      this.remove(group, name);
      groupCount--;
      if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
        break;
    }
  }
};

const privModeStorage = new ContentPrefStore();

ContentPrefService.prototype = {
  // XPCOM Plumbing

  classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),

  QueryInterface: function CPS_QueryInterface(iid) {
    let supportedIIDs = [
      Ci.nsIContentPrefService,
      Ci.nsISupports,
    ];
    if (supportedIIDs.some(i => iid.equals(i)))
      return this;
    if (iid.equals(Ci.nsIContentPrefService2)) {
      if (!this._contentPrefService2) {
        let s = {};
        Cu.import("resource://gre/modules/ContentPrefService2.jsm", s);
        this._contentPrefService2 = new s.ContentPrefService2(this);
      }
      return this._contentPrefService2;
    }
    throw Cr.NS_ERROR_NO_INTERFACE;
  },

  // Convenience Getters

  // Observer Service
  __observerSvc: null,
  get _observerSvc() {
    if (!this.__observerSvc)
      this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
                           getService(Ci.nsIObserverService);
    return this.__observerSvc;
  },

  // Console Service
  __consoleSvc: null,
  get _consoleSvc() {
    if (!this.__consoleSvc)
      this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
                          getService(Ci.nsIConsoleService);
    return this.__consoleSvc;
  },

  // Preferences Service
  __prefSvc: null,
  get _prefSvc() {
    if (!this.__prefSvc)
      this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
                       getService(Ci.nsIPrefBranch);
    return this.__prefSvc;
  },


  // Destruction

  _destroy: function ContentPrefService__destroy() {
    this._observerSvc.removeObserver(this, "xpcom-shutdown");
    this._observerSvc.removeObserver(this, "last-pb-context-exited");

    // Finalize statements which may have been used asynchronously.
    // FIXME(696499): put them in an object cache like other components.
    if (this.__stmtSelectPrefID) {
      this.__stmtSelectPrefID.finalize();
      this.__stmtSelectPrefID = null;
    }
    if (this.__stmtSelectGlobalPrefID) {
      this.__stmtSelectGlobalPrefID.finalize();
      this.__stmtSelectGlobalPrefID = null;
    }
    if (this.__stmtInsertPref) {
      this.__stmtInsertPref.finalize();
      this.__stmtInsertPref = null;
    }
    if (this.__stmtInsertGroup) {
      this.__stmtInsertGroup.finalize();
      this.__stmtInsertGroup = null;
    }
    if (this.__stmtInsertSetting) {
      this.__stmtInsertSetting.finalize();
      this.__stmtInsertSetting = null;
    }
    if (this.__stmtSelectGroupID) {
      this.__stmtSelectGroupID.finalize();
      this.__stmtSelectGroupID = null;
    }
    if (this.__stmtSelectSettingID) {
      this.__stmtSelectSettingID.finalize();
      this.__stmtSelectSettingID = null;
    }
    if (this.__stmtSelectPref) {
      this.__stmtSelectPref.finalize();
      this.__stmtSelectPref = null;
    }
    if (this.__stmtSelectGlobalPref) {
      this.__stmtSelectGlobalPref.finalize();
      this.__stmtSelectGlobalPref = null;
    }
    if (this.__stmtSelectPrefsByName) {
      this.__stmtSelectPrefsByName.finalize();
      this.__stmtSelectPrefsByName = null;
    }
    if (this.__stmtDeleteSettingIfUnused) {
      this.__stmtDeleteSettingIfUnused.finalize();
      this.__stmtDeleteSettingIfUnused = null;
    }
    if (this.__stmtSelectPrefs) {
      this.__stmtSelectPrefs.finalize();
      this.__stmtSelectPrefs = null;
    }
    if (this.__stmtDeleteGroupIfUnused) {
      this.__stmtDeleteGroupIfUnused.finalize();
      this.__stmtDeleteGroupIfUnused = null;
    }
    if (this.__stmtDeletePref) {
      this.__stmtDeletePref.finalize();
      this.__stmtDeletePref = null;
    }
    if (this.__stmtUpdatePref) {
      this.__stmtUpdatePref.finalize();
      this.__stmtUpdatePref = null;
    }

    if (this._contentPrefService2)
      this._contentPrefService2.destroy();

    this._dbConnection.asyncClose(() => {
      Services.obs.notifyObservers(null, "content-prefs-db-closed");
    });

    // Delete references to XPCOM components to make sure we don't leak them
    // (although we haven't observed leakage in tests).  Also delete references
    // in _observers and _genericObservers to avoid cycles with those that
    // refer to us and don't remove themselves from those observer pools.
    delete this._observers;
    delete this._genericObservers;
    delete this.__consoleSvc;
    delete this.__grouper;
    delete this.__observerSvc;
    delete this.__prefSvc;
  },


  // nsIObserver

  observe: function ContentPrefService_observe(subject, topic, data) {
    switch (topic) {
      case "xpcom-shutdown":
        this._destroy();
        break;
      case "last-pb-context-exited":
        this._privModeStorage.removeAll();
        break;
    }
  },


  // in-memory cache and private-browsing stores

  _cache: cache,
  _privModeStorage: privModeStorage,

  // nsIContentPrefService

  getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
    warnDeprecated();

    if (!aName)
      throw Components.Exception("aName cannot be null or an empty string",
                                 Cr.NS_ERROR_ILLEGAL_VALUE);

    var group = this._parseGroupParam(aGroup);

    if (aContext && aContext.usePrivateBrowsing) {
      if (this._privModeStorage.has(group, aName)) {
        let value = this._privModeStorage.get(group, aName);
        if (aCallback) {
          this._scheduleCallback(function() { aCallback.onResult(value); });
          return undefined;
        }
        return value;
      }
      // if we don't have a pref specific to this private mode browsing
      // session, to try to get one from normal mode
    }

    if (group == null)
      return this._selectGlobalPref(aName, aCallback);
    return this._selectPref(group, aName, aCallback);
  },

  setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
    warnDeprecated();

    // If the pref is already set to the value, there's nothing more to do.
    var currentValue = this.getPref(aGroup, aName, aContext);
    if (typeof currentValue != "undefined") {
      if (currentValue == aValue)
        return;
    }

    var group = this._parseGroupParam(aGroup);

    if (aContext && aContext.usePrivateBrowsing) {
      this._privModeStorage.setWithCast(group, aName, aValue);
      this._notifyPrefSet(group, aName, aValue, aContext.usePrivateBrowsing);
      return;
    }

    var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
    var groupID, prefID;
    if (group == null) {
      groupID = null;
      prefID = this._selectGlobalPrefID(settingID);
    } else {
      groupID = this._selectGroupID(group) || this._insertGroup(group);
      prefID = this._selectPrefID(groupID, settingID);
    }

    // Update the existing record, if any, or create a new one.
    if (prefID)
      this._updatePref(prefID, aValue);
    else
      this._insertPref(groupID, settingID, aValue);

    this._cache.setWithCast(group, aName, aValue);

    this._notifyPrefSet(group, aName, aValue,
                        aContext ? aContext.usePrivateBrowsing : false);
  },

  hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
    warnDeprecated();

    // XXX If consumers end up calling this method regularly, then we should
    // optimize this to query the database directly.
    return (typeof this.getPref(aGroup, aName, aContext) != "undefined");
  },

  hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
    warnDeprecated();

    if (!aName)
      throw Components.Exception("aName cannot be null or an empty string",
                                 Cr.NS_ERROR_ILLEGAL_VALUE);

    let group = this._parseGroupParam(aGroup);
    let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage : this._cache;
    return storage.has(group, aName);
  },

  removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
    warnDeprecated();

    // If there's no old value, then there's nothing to remove.
    if (!this.hasPref(aGroup, aName, aContext))
      return;

    var group = this._parseGroupParam(aGroup);

    if (aContext && aContext.usePrivateBrowsing) {
      this._privModeStorage.remove(group, aName);
      this._notifyPrefRemoved(group, aName, true);
      return;
    }

    var settingID = this._selectSettingID(aName);
    var groupID, prefID;
    if (group == null) {
      groupID = null;
      prefID = this._selectGlobalPrefID(settingID);
    } else {
      groupID = this._selectGroupID(group);
      prefID = this._selectPrefID(groupID, settingID);
    }

    this._deletePref(prefID);

    // Get rid of extraneous records that are no longer being used.
    this._deleteSettingIfUnused(settingID);
    if (groupID)
      this._deleteGroupIfUnused(groupID);

    this._cache.remove(group, aName);
    this._notifyPrefRemoved(group, aName, false);
  },

  removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
    warnDeprecated();

    // will not delete global preferences
    if (aContext && aContext.usePrivateBrowsing) {
        // keep only global prefs
        this._privModeStorage.removeAllGroups();
    }
    this._cache.removeAllGroups();
    this._dbConnection.beginTransaction();
    try {
      this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
      this._dbConnection.executeSimpleSQL("DELETE FROM groups");
      this._dbConnection.executeSimpleSQL(`
        DELETE FROM settings
        WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
      `);
      this._dbConnection.commitTransaction();
    } catch (ex) {
      this._dbConnection.rollbackTransaction();
      throw ex;
    }
  },

  removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
    warnDeprecated();

    if (!aName)
      throw Components.Exception("aName cannot be null or an empty string",
                                 Cr.NS_ERROR_ILLEGAL_VALUE);

    if (aContext && aContext.usePrivateBrowsing) {
      for (let [group, name, ] of this._privModeStorage) {
        if (name === aName) {
          this._privModeStorage.remove(group, aName);
          this._notifyPrefRemoved(group, aName, true);
        }
      }
    }

    var settingID = this._selectSettingID(aName);
    if (!settingID)
      return;

    var selectGroupsStmt = this._dbCreateStatement(`
      SELECT groups.id AS groupID, groups.name AS groupName
      FROM prefs
      JOIN groups ON prefs.groupID = groups.id
      WHERE prefs.settingID = :setting
    `);

    var groupNames = [];
    var groupIDs = [];
    try {
      selectGroupsStmt.params.setting = settingID;

      while (selectGroupsStmt.executeStep()) {
        groupIDs.push(selectGroupsStmt.row["groupID"]);
        groupNames.push(selectGroupsStmt.row["groupName"]);
      }
    } finally {
      selectGroupsStmt.reset();
    }

    if (this.hasPref(null, aName)) {
      groupNames.push(null);
    }

    this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
    this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);

    for (var i = 0; i < groupNames.length; i++) {
      this._cache.remove(groupNames[i], aName);
      if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
        this._deleteGroupIfUnused(groupIDs[i]);
      if (!aContext || !aContext.usePrivateBrowsing) {
        this._notifyPrefRemoved(groupNames[i], aName, false);
      }
    }
  },

  getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
    warnDeprecated();

    var group = this._parseGroupParam(aGroup);
    if (aContext && aContext.usePrivateBrowsing) {
        let prefs = Cc["@mozilla.org/hash-property-bag;1"].
                    createInstance(Ci.nsIWritablePropertyBag);
        for (let [sgroup, sname, sval] of this._privModeStorage) {
          if (sgroup === group)
            prefs.setProperty(sname, sval);
        }
        return prefs;
    }

    if (group == null)
      return this._selectGlobalPrefs();
    return this._selectPrefs(group);
  },

  getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
    warnDeprecated();

    if (!aName)
      throw Components.Exception("aName cannot be null or an empty string",
                                 Cr.NS_ERROR_ILLEGAL_VALUE);

    if (aContext && aContext.usePrivateBrowsing) {
      let prefs = Cc["@mozilla.org/hash-property-bag;1"].
                  createInstance(Ci.nsIWritablePropertyBag);
      for (let [sgroup, sname, sval] of this._privModeStorage) {
        if (sname === aName)
          prefs.setProperty(sgroup, sval);
      }
      return prefs;
    }

    return this._selectPrefsByName(aName);
  },

  // A hash of arrays of observers, indexed by setting name.
  _observers: {},

  // An array of generic observers, which observe all settings.
  _genericObservers: [],

  addObserver: function ContentPrefService_addObserver(aName, aObserver) {
    warnDeprecated();
    this._addObserver.apply(this, arguments);
  },

  _addObserver: function ContentPrefService__addObserver(aName, aObserver) {
    var observers;
    if (aName) {
      if (!this._observers[aName])
        this._observers[aName] = [];
      observers = this._observers[aName];
    } else
      observers = this._genericObservers;

    if (observers.indexOf(aObserver) == -1)
      observers.push(aObserver);
  },

  removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
    warnDeprecated();
    this._removeObserver.apply(this, arguments);
  },

  _removeObserver: function ContentPrefService__removeObserver(aName, aObserver) {
    var observers;
    if (aName) {
      if (!this._observers[aName])
        return;
      observers = this._observers[aName];
    } else
      observers = this._genericObservers;

    if (observers.indexOf(aObserver) != -1)
      observers.splice(observers.indexOf(aObserver), 1);
  },

  /**
   * Construct a list of observers to notify about a change to some setting,
   * putting setting-specific observers before before generic ones, so observers
   * that initialize individual settings (like the page style controller)
   * execute before observers that display multiple settings and depend on them
   * being initialized first (like the content prefs sidebar).
   */
  _getObservers: function ContentPrefService__getObservers(aName) {
    var observers = [];

    if (aName && this._observers[aName])
      observers = observers.concat(this._observers[aName]);
    observers = observers.concat(this._genericObservers);

    return observers;
  },

  /**
   * Notify all observers about the removal of a preference.
   */
  _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName, aIsPrivate) {
    for (var observer of this._getObservers(aName)) {
      try {
        observer.onContentPrefRemoved(aGroup, aName, aIsPrivate);
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },

  /**
   * Notify all observers about a preference change.
   */
  _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue, aIsPrivate) {
    for (var observer of this._getObservers(aName)) {
      try {
        observer.onContentPrefSet(aGroup, aName, aValue, aIsPrivate);
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },

  get grouper() {
    warnDeprecated();
    return this._grouper;
  },
  __grouper: null,
  get _grouper() {
    if (!this.__grouper)
      this.__grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
                       getService(Ci.nsIContentURIGrouper);
    return this.__grouper;
  },

  get DBConnection() {
    warnDeprecated();
    return this._dbConnection;
  },


  // Data Retrieval & Modification

  __stmtSelectPref: null,
  get _stmtSelectPref() {
    if (!this.__stmtSelectPref)
      this.__stmtSelectPref = this._dbCreateStatement(`
        SELECT prefs.value AS value
        FROM prefs
        JOIN groups ON prefs.groupID = groups.id
        JOIN settings ON prefs.settingID = settings.id
        WHERE groups.name = :group
        AND settings.name = :setting
      `);

    return this.__stmtSelectPref;
  },

  _scheduleCallback(func) {
    let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
    tm.dispatchToMainThread(func);
  },

  _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
    let value = undefined;
    if (this._cache.has(aGroup, aSetting)) {
      value = this._cache.get(aGroup, aSetting);
      if (aCallback) {
        this._scheduleCallback(function() { aCallback.onResult(value); });
        return undefined;
      }
      return value;
    }

    try {
      this._stmtSelectPref.params.group = aGroup;
      this._stmtSelectPref.params.setting = aSetting;

      if (aCallback) {
        let cache = this._cache;
        new AsyncStatement(this._stmtSelectPref).execute({onResult(aResult) {
          cache.set(aGroup, aSetting, aResult);
          aCallback.onResult(aResult);
        }});
      } else {
        if (this._stmtSelectPref.executeStep()) {
          value = this._stmtSelectPref.row["value"];
        }
        this._cache.set(aGroup, aSetting, value);
      }
    } finally {
      this._stmtSelectPref.reset();
    }

    return value;
  },

  __stmtSelectGlobalPref: null,
  get _stmtSelectGlobalPref() {
    if (!this.__stmtSelectGlobalPref)
      this.__stmtSelectGlobalPref = this._dbCreateStatement(`
        SELECT prefs.value AS value
        FROM prefs
        JOIN settings ON prefs.settingID = settings.id
        WHERE prefs.groupID IS NULL
        AND settings.name = :name
      `);

    return this.__stmtSelectGlobalPref;
  },

  _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
    let value = undefined;
    if (this._cache.has(null, aName)) {
      value = this._cache.get(null, aName);
      if (aCallback) {
        this._scheduleCallback(function() { aCallback.onResult(value); });
        return undefined;
      }
      return value;
    }

    try {
      this._stmtSelectGlobalPref.params.name = aName;

      if (aCallback) {
        let cache = this._cache;
        new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult(aResult) {
          cache.set(null, aName, aResult);
          aCallback.onResult(aResult);
        }});
      } else {
        if (this._stmtSelectGlobalPref.executeStep()) {
          value = this._stmtSelectGlobalPref.row["value"];
        }
        this._cache.set(null, aName, value);
      }
    } finally {
      this._stmtSelectGlobalPref.reset();
    }

    return value;
  },

  __stmtSelectGroupID: null,
  get _stmtSelectGroupID() {
    if (!this.__stmtSelectGroupID)
      this.__stmtSelectGroupID = this._dbCreateStatement(`
        SELECT groups.id AS id
        FROM groups
        WHERE groups.name = :name
      `);

    return this.__stmtSelectGroupID;
  },

  _selectGroupID: function ContentPrefService__selectGroupID(aName) {
    var id;

    try {
      this._stmtSelectGroupID.params.name = aName;

      if (this._stmtSelectGroupID.executeStep())
        id = this._stmtSelectGroupID.row["id"];
    } finally {
      this._stmtSelectGroupID.reset();
    }

    return id;
  },

  __stmtInsertGroup: null,
  get _stmtInsertGroup() {
    if (!this.__stmtInsertGroup)
      this.__stmtInsertGroup = this._dbCreateStatement(
        "INSERT INTO groups (name) VALUES (:name)"
      );

    return this.__stmtInsertGroup;
  },

  _insertGroup: function ContentPrefService__insertGroup(aName) {
    this._stmtInsertGroup.params.name = aName;
    this._stmtInsertGroup.execute();
    return this._dbConnection.lastInsertRowID;
  },

  __stmtSelectSettingID: null,
  get _stmtSelectSettingID() {
    if (!this.__stmtSelectSettingID)
      this.__stmtSelectSettingID = this._dbCreateStatement(
        "SELECT id FROM settings WHERE name = :name"
      );

    return this.__stmtSelectSettingID;
  },

  _selectSettingID: function ContentPrefService__selectSettingID(aName) {
    var id;

    try {
      this._stmtSelectSettingID.params.name = aName;

      if (this._stmtSelectSettingID.executeStep())
        id = this._stmtSelectSettingID.row["id"];
    } finally {
      this._stmtSelectSettingID.reset();
    }

    return id;
  },

  __stmtInsertSetting: null,
  get _stmtInsertSetting() {
    if (!this.__stmtInsertSetting)
      this.__stmtInsertSetting = this._dbCreateStatement(
        "INSERT INTO settings (name) VALUES (:name)"
      );

    return this.__stmtInsertSetting;
  },

  _insertSetting: function ContentPrefService__insertSetting(aName) {
    this._stmtInsertSetting.params.name = aName;
    this._stmtInsertSetting.execute();
    return this._dbConnection.lastInsertRowID;
  },

  __stmtSelectPrefID: null,
  get _stmtSelectPrefID() {
    if (!this.__stmtSelectPrefID)
      this.__stmtSelectPrefID = this._dbCreateStatement(
        "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
      );

    return this.__stmtSelectPrefID;
  },

  _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
    var id;

    try {
      this._stmtSelectPrefID.params.groupID = aGroupID;
      this._stmtSelectPrefID.params.settingID = aSettingID;

      if (this._stmtSelectPrefID.executeStep())
        id = this._stmtSelectPrefID.row["id"];
    } finally {
      this._stmtSelectPrefID.reset();
    }

    return id;
  },

  __stmtSelectGlobalPrefID: null,
  get _stmtSelectGlobalPrefID() {
    if (!this.__stmtSelectGlobalPrefID)
      this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
        "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
      );

    return this.__stmtSelectGlobalPrefID;
  },

  _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
    var id;

    try {
      this._stmtSelectGlobalPrefID.params.settingID = aSettingID;

      if (this._stmtSelectGlobalPrefID.executeStep())
        id = this._stmtSelectGlobalPrefID.row["id"];
    } finally {
      this._stmtSelectGlobalPrefID.reset();
    }

    return id;
  },

  __stmtInsertPref: null,
  get _stmtInsertPref() {
    if (!this.__stmtInsertPref)
      this.__stmtInsertPref = this._dbCreateStatement(`
        INSERT INTO prefs (groupID, settingID, value)
        VALUES (:groupID, :settingID, :value)
      `);

    return this.__stmtInsertPref;
  },

  _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
    this._stmtInsertPref.params.groupID = aGroupID;
    this._stmtInsertPref.params.settingID = aSettingID;
    this._stmtInsertPref.params.value = aValue;
    this._stmtInsertPref.execute();
    return this._dbConnection.lastInsertRowID;
  },

  __stmtUpdatePref: null,
  get _stmtUpdatePref() {
    if (!this.__stmtUpdatePref)
      this.__stmtUpdatePref = this._dbCreateStatement(
        "UPDATE prefs SET value = :value WHERE id = :id"
      );

    return this.__stmtUpdatePref;
  },

  _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
    this._stmtUpdatePref.params.id = aPrefID;
    this._stmtUpdatePref.params.value = aValue;
    this._stmtUpdatePref.execute();
  },

  __stmtDeletePref: null,
  get _stmtDeletePref() {
    if (!this.__stmtDeletePref)
      this.__stmtDeletePref = this._dbCreateStatement(
        "DELETE FROM prefs WHERE id = :id"
      );

    return this.__stmtDeletePref;
  },

  _deletePref: function ContentPrefService__deletePref(aPrefID) {
    this._stmtDeletePref.params.id = aPrefID;
    this._stmtDeletePref.execute();
  },

  __stmtDeleteSettingIfUnused: null,
  get _stmtDeleteSettingIfUnused() {
    if (!this.__stmtDeleteSettingIfUnused)
      this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(`
        DELETE FROM settings WHERE id = :id
        AND id NOT IN (SELECT DISTINCT settingID FROM prefs)
      `);

    return this.__stmtDeleteSettingIfUnused;
  },

  _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
    this._stmtDeleteSettingIfUnused.params.id = aSettingID;
    this._stmtDeleteSettingIfUnused.execute();
  },

  __stmtDeleteGroupIfUnused: null,
  get _stmtDeleteGroupIfUnused() {
    if (!this.__stmtDeleteGroupIfUnused)
      this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(`
        DELETE FROM groups WHERE id = :id
        AND id NOT IN (SELECT DISTINCT groupID FROM prefs)
      `);

    return this.__stmtDeleteGroupIfUnused;
  },

  _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
    this._stmtDeleteGroupIfUnused.params.id = aGroupID;
    this._stmtDeleteGroupIfUnused.execute();
  },

  __stmtSelectPrefs: null,
  get _stmtSelectPrefs() {
    if (!this.__stmtSelectPrefs)
      this.__stmtSelectPrefs = this._dbCreateStatement(`
        SELECT settings.name AS name, prefs.value AS value
        FROM prefs
        JOIN groups ON prefs.groupID = groups.id
        JOIN settings ON prefs.settingID = settings.id
        WHERE groups.name = :group
      `);

    return this.__stmtSelectPrefs;
  },

  _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
    var prefs = Cc["@mozilla.org/hash-property-bag;1"].
                createInstance(Ci.nsIWritablePropertyBag);

    try {
      this._stmtSelectPrefs.params.group = aGroup;

      while (this._stmtSelectPrefs.executeStep())
        prefs.setProperty(this._stmtSelectPrefs.row["name"],
                          this._stmtSelectPrefs.row["value"]);
    } finally {
      this._stmtSelectPrefs.reset();
    }

    return prefs;
  },

  __stmtSelectGlobalPrefs: null,
  get _stmtSelectGlobalPrefs() {
    if (!this.__stmtSelectGlobalPrefs)
      this.__stmtSelectGlobalPrefs = this._dbCreateStatement(`
        SELECT settings.name AS name, prefs.value AS value
        FROM prefs
        JOIN settings ON prefs.settingID = settings.id
        WHERE prefs.groupID IS NULL
      `);

    return this.__stmtSelectGlobalPrefs;
  },

  _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
    var prefs = Cc["@mozilla.org/hash-property-bag;1"].
                createInstance(Ci.nsIWritablePropertyBag);

    try {
      while (this._stmtSelectGlobalPrefs.executeStep())
        prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
                          this._stmtSelectGlobalPrefs.row["value"]);
    } finally {
      this._stmtSelectGlobalPrefs.reset();
    }

    return prefs;
  },

  __stmtSelectPrefsByName: null,
  get _stmtSelectPrefsByName() {
    if (!this.__stmtSelectPrefsByName)
      this.__stmtSelectPrefsByName = this._dbCreateStatement(`
        SELECT groups.name AS groupName, prefs.value AS value
        FROM prefs
        JOIN groups ON prefs.groupID = groups.id
        JOIN settings ON prefs.settingID = settings.id
        WHERE settings.name = :setting
      `);

    return this.__stmtSelectPrefsByName;
  },

  _selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
    var prefs = Cc["@mozilla.org/hash-property-bag;1"].
                createInstance(Ci.nsIWritablePropertyBag);

    try {
      this._stmtSelectPrefsByName.params.setting = aName;

      while (this._stmtSelectPrefsByName.executeStep())
        prefs.setProperty(this._stmtSelectPrefsByName.row["groupName"],
                          this._stmtSelectPrefsByName.row["value"]);
    } finally {
      this._stmtSelectPrefsByName.reset();
    }

    var global = this._selectGlobalPref(aName);
    if (typeof global != "undefined") {
      prefs.setProperty(null, global);
    }

    return prefs;
  },


  // Database Creation & Access

  _dbVersion: 4,

  _dbSchema: {
    tables: {
      groups:     "id           INTEGER PRIMARY KEY, \
                   name         TEXT NOT NULL",

      settings:   "id           INTEGER PRIMARY KEY, \
                   name         TEXT NOT NULL",

      prefs:      "id           INTEGER PRIMARY KEY, \
                   groupID      INTEGER REFERENCES groups(id), \
                   settingID    INTEGER NOT NULL REFERENCES settings(id), \
                   value        BLOB, \
                   timestamp    INTEGER NOT NULL DEFAULT 0" // Storage in seconds, API in ms. 0 for migrated values.
    },
    indices: {
      groups_idx: {
        table: "groups",
        columns: ["name"]
      },
      settings_idx: {
        table: "settings",
        columns: ["name"]
      },
      prefs_idx: {
        table: "prefs",
        columns: ["timestamp", "groupID", "settingID"]
      }
    }
  },

  _dbConnection: null,

  _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
    try {
      var statement = this._dbConnection.createStatement(aSQLString);
    } catch (ex) {
      Cu.reportError("error creating statement " + aSQLString + ": " +
                     this._dbConnection.lastError + " - " +
                     this._dbConnection.lastErrorString);
      throw ex;
    }

    return statement;
  },

  // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
  // specific migration methods) must be careful not to call any method
  // of the service that assumes the database connection has already been
  // initialized, since it won't be initialized until at the end of _dbInit.

  _dbInit: function ContentPrefService__dbInit() {
    var dirService = Cc["@mozilla.org/file/directory_service;1"].
                     getService(Ci.nsIProperties);
    var dbFile = dirService.get("ProfD", Ci.nsIFile);
    dbFile.append("content-prefs.sqlite");

    var dbService = Cc["@mozilla.org/storage/service;1"].
                    getService(Ci.mozIStorageService);

    var dbConnection;

    if (!dbFile.exists())
      dbConnection = this._dbCreate(dbService, dbFile);
    else {
      try {
        dbConnection = dbService.openDatabase(dbFile);
      } catch (e) {
        // If the connection isn't ready after we open the database, that means
        // the database has been corrupted, so we back it up and then recreate it.
        if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
          throw e;
        dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
                                                 dbConnection);
      }

      // Get the version of the schema in the file.
      var version = dbConnection.schemaVersion;

      // Try to migrate the schema in the database to the current schema used by
      // the service.  If migration fails, back up the database and recreate it.
      if (version != this._dbVersion) {
        try {
          this._dbMigrate(dbConnection, version, this._dbVersion);
        } catch (ex) {
          Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
          dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
        }
      }
    }

    // Turn off disk synchronization checking to reduce disk churn and speed up
    // operations when prefs are changed rapidly (such as when a user repeatedly
    // changes the value of the browser zoom setting for a site).
    //
    // Note: this could cause database corruption if the OS crashes or machine
    // loses power before the data gets written to disk, but this is considered
    // a reasonable risk for the not-so-critical data stored in this database.
    //
    // If you really don't want to take this risk, however, just set the
    // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
    // (FULL synchronization), in which case mozStorageConnection::Initialize
    // will use that value, and we won't override it here.
    if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
      dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");

    this._dbConnection = dbConnection;
  },

  _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
    var dbConnection = aDBService.openDatabase(aDBFile);

    try {
      this._dbCreateSchema(dbConnection);
      dbConnection.schemaVersion = this._dbVersion;
    } catch (ex) {
      // If we failed to create the database (perhaps because the disk ran out
      // of space), then remove the database file so we don't leave it in some
      // half-created state from which we won't know how to recover.
      dbConnection.close();
      aDBFile.remove(false);
      throw ex;
    }

    return dbConnection;
  },

  _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
    this._dbCreateTables(aDBConnection);
    this._dbCreateIndices(aDBConnection);
  },

  _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
    for (let name in this._dbSchema.tables)
      aDBConnection.createTable(name, this._dbSchema.tables[name]);
  },

  _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
    for (let name in this._dbSchema.indices) {
      let index = this._dbSchema.indices[name];
      let statement = `
        CREATE INDEX IF NOT EXISTS ${name} ON ${index.table}
        (${index.columns.join(", ")})
      `;
      aDBConnection.executeSimpleSQL(statement);
    }
  },

  _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
                                                                         aDBFile,
                                                                         aDBConnection) {
    aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");

    // Close the database, ignoring the "already closed" exception, if any.
    // It'll be open if we're here because of a migration failure but closed
    // if we're here because of database corruption.
    try { aDBConnection.close() } catch (ex) {}

    aDBFile.remove(false);

    let dbConnection = this._dbCreate(aDBService, aDBFile);

    return dbConnection;
  },

  _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
    /**
     * Migrations should follow the template rules in bug 1074817 comment 3 which are:
     * 1. Migration should be incremental and non-breaking.
     * 2. It should be idempotent because one can downgrade an upgrade again.
     * On downgrade:
     * 1. Decrement schema version so that upgrade runs the migrations again.
     */
    aDBConnection.beginTransaction();

    try {
       /**
       * If the schema version is 0, that means it was never set, which means
       * the database was somehow created without the schema being applied, perhaps
       * because the system ran out of disk space (although we check for this
       * in _createDB) or because some other code created the database file without
       * applying the schema.  In any case, recover by simply reapplying the schema.
       */
      if (aOldVersion == 0) {
        this._dbCreateSchema(aDBConnection);
      } else {
        for (let i = aOldVersion; i < aNewVersion; i++) {
          let migrationName = "_dbMigrate" + i + "To" + (i + 1);
          if (typeof this[migrationName] != "function") {
            throw ("no migrator function from version " + aOldVersion + " to version " + aNewVersion);
          }
          this[migrationName](aDBConnection);
        }
      }
      aDBConnection.schemaVersion = aNewVersion;
      aDBConnection.commitTransaction();
    } catch (ex) {
      aDBConnection.rollbackTransaction();
      throw ex;
    }
  },

  _dbMigrate1To2: function ContentPrefService___dbMigrate1To2(aDBConnection) {
    aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
    aDBConnection.createTable("groups", this._dbSchema.tables.groups);
    aDBConnection.executeSimpleSQL(`
      INSERT INTO groups (id, name)
      SELECT id, name FROM groupsOld
    `);

    aDBConnection.executeSimpleSQL("DROP TABLE groupers");
    aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
  },

  _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
    this._dbCreateIndices(aDBConnection);
  },

  _dbMigrate3To4: function ContentPrefService__dbMigrate3To4(aDBConnection) {
    // Add timestamp column if it does not exist yet. This operation is idempotent.
    try {
      let stmt = aDBConnection.createStatement("SELECT timestamp FROM prefs");
      stmt.finalize();
    } catch (e) {
      aDBConnection.executeSimpleSQL("ALTER TABLE prefs ADD COLUMN timestamp INTEGER NOT NULL DEFAULT 0");
    }

    // To modify prefs_idx drop it and create again.
    aDBConnection.executeSimpleSQL("DROP INDEX IF EXISTS prefs_idx");
    this._dbCreateIndices(aDBConnection);
  },

  _parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
    if (aGroup == null)
      return null;
    if (aGroup.constructor.name == "String")
      return aGroup.toString();
    if (aGroup instanceof Ci.nsIURI)
      return this.grouper.group(aGroup);

    throw Components.Exception("aGroup is not a string, nsIURI or null",
                               Cr.NS_ERROR_ILLEGAL_VALUE);
  },
};

function warnDeprecated() {
  let Deprecated = Cu.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
  Deprecated.warning("nsIContentPrefService is deprecated. Please use nsIContentPrefService2 instead.",
                     "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIContentPrefService2",
                     Components.stack.caller);
}


function HostnameGrouper() {}

HostnameGrouper.prototype = {
  // XPCOM Plumbing

  classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),

  // nsIContentURIGrouper

  group: function HostnameGrouper_group(aURI) {
    var group;

    try {
      // Accessing the host property of the URI will throw an exception
      // if the URI is of a type that doesn't have a host property.
      // Otherwise, we manually throw an exception if the host is empty,
      // since the effect is the same (we can't derive a group from it).

      group = aURI.host;
      if (!group)
        throw ("can't derive group from host; no host in URI");
    } catch (ex) {
      // If we don't have a host, then use the entire URI (minus the query,
      // reference, and hash, if possible) as the group.  This means that URIs
      // like about:mozilla and about:blank will be considered separate groups,
      // but at least they'll be grouped somehow.

      // This also means that each individual file: URL will be considered
      // its own group.  This seems suboptimal, but so does treating the entire
      // file: URL space as a single group (especially if folks start setting
      // group-specific capabilities prefs).

      // XXX Is there something better we can do here?

      try {
        var url = aURI.QueryInterface(Ci.nsIURL);
        group = aURI.prePath + url.filePath;
      } catch (ex) {
        group = aURI.spec;
      }
    }

    return group;
  }
};

function AsyncStatement(aStatement) {
  this.stmt = aStatement;
}

AsyncStatement.prototype = {
  execute: function AsyncStmt_execute(aCallback) {
    let stmt = this.stmt;
    stmt.executeAsync({
      _callback: aCallback,
      _hadResult: false,
      handleResult(aResult) {
        this._hadResult = true;
        if (this._callback) {
          let row = aResult.getNextRow();
          this._callback.onResult(row.getResultByName("value"));
        }
      },
      handleCompletion(aReason) {
        if (!this._hadResult && this._callback &&
            aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
          this._callback.onResult(undefined);
      },
      handleError(aError) {}
    });
  }
};

// XPCOM Plumbing

var components = [ContentPrefService, HostnameGrouper];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<RR&components/nsContentDispatchChooser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

// Constants

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

const CONTENT_HANDLING_URL = "chrome://mozapps/content/handling/dialog.xul";
const STRINGBUNDLE_URL = "chrome://mozapps/locale/handling/handling.properties";

// nsContentDispatchChooser class

function nsContentDispatchChooser() {
}

nsContentDispatchChooser.prototype =
{
  classID: Components.ID("e35d5067-95bc-4029-8432-e8f1e431148d"),

  // nsIContentDispatchChooser

  ask: function ask(aHandler, aWindowContext, aURI, aReason) {
    var window = null;
    try {
      if (aWindowContext)
        window = aWindowContext.getInterface(Ci.nsIDOMWindow);
    } catch (e) { /* it's OK to not have a window */ }

    var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
              getService(Ci.nsIStringBundleService);
    var bundle = sbs.createBundle(STRINGBUNDLE_URL);

    var xai = Cc["@mozilla.org/xre/app-info;1"].
              getService(Ci.nsIXULAppInfo);
    // TODO when this is hooked up for content, we will need different strings
    //      for most of these
    var arr = [bundle.GetStringFromName("protocol.title"),
               "",
               bundle.GetStringFromName("protocol.description"),
               bundle.GetStringFromName("protocol.choices.label"),
               bundle.formatStringFromName("protocol.checkbox.label",
                                           [aURI.scheme], 1),
               bundle.GetStringFromName("protocol.checkbox.accesskey"),
               bundle.formatStringFromName("protocol.checkbox.extra",
                                           [xai.name], 1)];

    var params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
    let SupportsString = Components.Constructor(
                           "@mozilla.org/supports-string;1",
                           "nsISupportsString");
    for (let text of arr) {
      let string = new SupportsString;
      string.data = text;
      params.appendElement(string);
    }
    params.appendElement(aHandler);
    params.appendElement(aURI);
    params.appendElement(aWindowContext);

    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
             getService(Ci.nsIWindowWatcher);
    ww.openWindow(window,
                  CONTENT_HANDLING_URL,
                  null,
                  "chrome,dialog=yes,resizable,centerscreen",
                  params);
  },

  // nsISupports

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentDispatchChooser])
};

// Module

var components = [nsContentDispatchChooser];

this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<MGAA#components/nsHandlerService-json.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
                                   "@mozilla.org/uriloader/external-protocol-service;1",
                                   "nsIExternalProtocolService");
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerServiceRDF",
                                   "@mozilla.org/uriloader/handler-service-rdf;1",
                                   "nsIHandlerService");
XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
                                   "@mozilla.org/mime;1",
                                   "nsIMIMEService");

function HandlerService() {
  // Observe handlersvc-json-replace so we can switch to the datasource
  Services.obs.addObserver(this, "handlersvc-json-replace", true);
}

HandlerService.prototype = {

  classID: Components.ID("{220cc253-b60f-41f6-b9cf-fdcb325f970f}"),
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsISupportsWeakReference,
    Ci.nsIHandlerService,
    Ci.nsIObserver
  ]),

  __store: null,
  get _store() {
    if (!this.__store) {
      this.__store = new JSONFile({
        path: OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"),
        dataPostProcessor: this._dataPostProcessor.bind(this),
      });
      this.__store.ensureDataReady();

      // We have to inject new default protocol handlers only if we haven't
      // already done this when migrating data from the RDF back-end.
      let alreadyInjected = this._migrateFromRDFIfNeeded();
      this._injectDefaultProtocolHandlersIfNeeded(alreadyInjected);
    }
    return this.__store;
  },

  _dataPostProcessor(data) {
    return data.defaultHandlersVersion ? data : {
      defaultHandlersVersion: {},
      mimeTypes: {},
      schemes: {},
    };
  },

  /**
   * Migrates data from the RDF back-end, returning true if this happened.
   */
  _migrateFromRDFIfNeeded() {
    try {
      if (Services.prefs.getBoolPref("gecko.handlerService.migrated")) {
        return false;
      }
    } catch (ex) {
      // If the preference does not exist, we need to import.
    }

    try {
      // Don't initialize the RDF back-end if the file does not exist, improving
      // performance on first use for new profiles.
      let rdfFile = FileUtils.getFile("ProfD", ["mimeTypes.rdf"]);
      if (rdfFile.exists()) {
        this._migrateFromRDF();
        return true;
      }
    } catch (ex) {
      Cu.reportError(ex);
    } finally {
      // Don't attempt to import again even if the operation failed.
      Services.prefs.setBoolPref("gecko.handlerService.migrated", true);
    }

    return false;
  },

  _migrateFromRDF() {
    // Initializing the RDF back-end has the side effect of triggering the
    // injection of the default protocol handlers. If the version number is
    // newer and this happens, then the "enumerate" call in the RDF back-end
    // will re-enter the JSON back-end through the MIME service, but this is
    // harmless. The injection will not be repeated in the JSON back-end, so we
    // rely on the new handlers injected by the RDF back-end.
    let handlerInfoEnumerator = gHandlerServiceRDF.enumerate();
    while (handlerInfoEnumerator.hasMoreElements()) {
      let handlerInfo = handlerInfoEnumerator.getNext()
                                             .QueryInterface(Ci.nsIHandlerInfo);
      try {
        // If the import from RDF is repeated by flipping the preference, then
        // handlerInfo might already include some data from the JSON back-end,
        // but any duplication is removed by the "store" method.
        gHandlerServiceRDF.fillHandlerInfo(handlerInfo, "");
        this.store(handlerInfo);
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },

  /**
   * Injects new default protocol handlers if the version in the preferences is
   * newer than the one in the data store. If we just imported data from the RDF
   * back-end, we only need to update the version in the data store.
   */
  _injectDefaultProtocolHandlersIfNeeded(alreadyInjected) {
    let prefsDefaultHandlersVersion;
    try {
      prefsDefaultHandlersVersion = Services.prefs.getComplexValue(
        "gecko.handlerService.defaultHandlersVersion",
        Ci.nsIPrefLocalizedString);
    } catch (ex) {
      if (ex instanceof Components.Exception &&
          ex.result == Cr.NS_ERROR_UNEXPECTED) {
        // This platform does not have any default protocol handlers configured.
        return;
      }
      throw ex;
    }

    try {
      prefsDefaultHandlersVersion = Number(prefsDefaultHandlersVersion.data);
      let locale = Services.locale.getAppLocaleAsLangTag();

      let defaultHandlersVersion =
          this._store.data.defaultHandlersVersion[locale] || 0;
      if (defaultHandlersVersion < prefsDefaultHandlersVersion) {
        if (!alreadyInjected) {
          this._injectDefaultProtocolHandlers();
        }
        this._store.data.defaultHandlersVersion[locale] =
          prefsDefaultHandlersVersion;
      }
    } catch (ex) {
      Cu.reportError(ex);
    }
  },

  _injectDefaultProtocolHandlers() {
    let schemesPrefBranch = Services.prefs.getBranch("gecko.handlerService.schemes.");
    let schemePrefList = schemesPrefBranch.getChildList("");

    let schemes = {};

    // read all the scheme prefs into a hash
    for (let schemePrefName of schemePrefList) {

      let [scheme, handlerNumber, attribute] = schemePrefName.split(".");

      try {
        let attrData =
          schemesPrefBranch.getComplexValue(schemePrefName,
                                            Ci.nsIPrefLocalizedString).data;
        if (!(scheme in schemes)) {
          schemes[scheme] = {};
        }

        if (!(handlerNumber in schemes[scheme])) {
          schemes[scheme][handlerNumber] = {};
        }

        schemes[scheme][handlerNumber][attribute] = attrData;
      } catch (ex) {}
    }

    for (let scheme of Object.keys(schemes)) {

      // This clause is essentially a reimplementation of
      // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
      // Necessary because we want to use this instance of the service,
      // but nsIExternalProtocolHandlerService would call the RDF-based based version
      // until we complete the conversion.
      let osDefaultHandlerFound = {};
      let protoInfo = gExternalProtocolService.getProtocolHandlerInfoFromOS(scheme,
                                                                            osDefaultHandlerFound);

      if (this.exists(protoInfo)) {
        this.fillHandlerInfo(protoInfo, null);
      } else {
        gExternalProtocolService.setProtocolHandlerDefaults(protoInfo,
                                                            osDefaultHandlerFound.value);
      }

      // cache the possible handlers to avoid extra xpconnect traversals.
      let possibleHandlers = protoInfo.possibleApplicationHandlers;

      for (let handlerNumber of Object.keys(schemes[scheme])) {
        let handlerApp = this.handlerAppFromSerializable(schemes[scheme][handlerNumber]);
        // If there is already a handler registered with the same template
        // URL, the newly added one will be ignored when saving.
        possibleHandlers.appendElement(handlerApp, false);
      }

      this.store(protoInfo);
    }
  },

  _onDBChange() {
    return (async () => {
      if (this.__store) {
        await this.__store.finalize();
      }
      this.__store = null;
    })().catch(Cu.reportError);
  },

  // nsIObserver
  observe(subject, topic, data) {
    if (topic != "handlersvc-json-replace") {
      return;
    }
    let promise = this._onDBChange();
    promise.then(() => {
      Services.obs.notifyObservers(null, "handlersvc-json-replace-complete");
    });
  },

  // nsIHandlerService
  enumerate() {
    let handlers = Cc["@mozilla.org/array;1"]
                     .createInstance(Ci.nsIMutableArray);
    for (let type of Object.keys(this._store.data.mimeTypes)) {
      let handler = gMIMEService.getFromTypeAndExtension(type, null);
      handlers.appendElement(handler);
    }
    for (let type of Object.keys(this._store.data.schemes)) {
      let handler = gExternalProtocolService.getProtocolHandlerInfo(type);
      handlers.appendElement(handler);
    }
    return handlers.enumerate();
  },

  // nsIHandlerService
  store(handlerInfo) {
    let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo);

    // Retrieve an existing entry if present, instead of creating a new one, so
    // that we preserve unknown properties for forward compatibility.
    let storedHandlerInfo = handlerList[handlerInfo.type];
    if (!storedHandlerInfo) {
      storedHandlerInfo = {};
      handlerList[handlerInfo.type] = storedHandlerInfo;
    }

    // Only a limited number of preferredAction values is allowed.
    if (handlerInfo.preferredAction == Ci.nsIHandlerInfo.saveToDisk ||
        handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault ||
        handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally) {
      storedHandlerInfo.action = handlerInfo.preferredAction;
    } else {
      storedHandlerInfo.action = Ci.nsIHandlerInfo.useHelperApp;
    }

    if (handlerInfo.alwaysAskBeforeHandling) {
      storedHandlerInfo.ask = true;
    } else {
      delete storedHandlerInfo.ask;
    }

    // Build a list of unique nsIHandlerInfo instances to process later.
    let handlers = [];
    if (handlerInfo.preferredApplicationHandler) {
      handlers.push(handlerInfo.preferredApplicationHandler);
    }
    let enumerator = handlerInfo.possibleApplicationHandlers.enumerate();
    while (enumerator.hasMoreElements()) {
      let handler = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp);
      // If the caller stored duplicate handlers, we save them only once.
      if (!handlers.some(h => h.equals(handler))) {
        handlers.push(handler);
      }
    }

    // If any of the nsIHandlerInfo instances cannot be serialized, it is not
    // included in the final list. The first element is always the preferred
    // handler, or null if there is none.
    let serializableHandlers =
        handlers.map(h => this.handlerAppToSerializable(h)).filter(h => h);
    if (serializableHandlers.length) {
      if (!handlerInfo.preferredApplicationHandler) {
        serializableHandlers.unshift(null);
      }
      storedHandlerInfo.handlers = serializableHandlers;
    } else {
      delete storedHandlerInfo.handlers;
    }

    if (this._isMIMEInfo(handlerInfo)) {
      let extEnumerator = handlerInfo.getFileExtensions();
      let extensions = storedHandlerInfo.extensions || [];
      while (extEnumerator.hasMore()) {
        let extension = extEnumerator.getNext().toLowerCase();
        // If the caller stored duplicate extensions, we save them only once.
        if (!extensions.includes(extension)) {
          extensions.push(extension);
        }
      }
      if (extensions.length) {
        storedHandlerInfo.extensions = extensions;
      } else {
        delete storedHandlerInfo.extensions;
      }
    }

    this._store.saveSoon();
  },

  // nsIHandlerService
  fillHandlerInfo(handlerInfo, overrideType) {
    let type = overrideType || handlerInfo.type;
    let storedHandlerInfo = this._getHandlerListByHandlerInfoType(handlerInfo)[type];
    if (!storedHandlerInfo) {
      throw new Components.Exception("handlerSvc fillHandlerInfo: don't know this type",
                                     Cr.NS_ERROR_NOT_AVAILABLE);
    }

    handlerInfo.preferredAction = storedHandlerInfo.action;
    handlerInfo.alwaysAskBeforeHandling = !!storedHandlerInfo.ask;

    // If the first item is not null, it is also the preferred handler. Since
    // we cannot modify the stored array, use a boolean to keep track of this.
    let isFirstItem = true;
    for (let handler of storedHandlerInfo.handlers || [null]) {
      let handlerApp = this.handlerAppFromSerializable(handler || {});
      if (isFirstItem) {
        isFirstItem = false;
        handlerInfo.preferredApplicationHandler = handlerApp;
      }
      if (handlerApp) {
        handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
      }
    }

    if (this._isMIMEInfo(handlerInfo) && storedHandlerInfo.extensions) {
      for (let extension of storedHandlerInfo.extensions) {
        handlerInfo.appendExtension(extension);
      }
    }
  },

  /**
   * @param handler
   *        A nsIHandlerApp handler app
   * @returns  Serializable representation of a handler app object.
   */
  handlerAppToSerializable(handler) {
    if (handler instanceof Ci.nsILocalHandlerApp) {
      return {
        name: handler.name,
        path: handler.executable.path,
      };
    } else if (handler instanceof Ci.nsIWebHandlerApp) {
      return {
        name: handler.name,
        uriTemplate: handler.uriTemplate,
      };
    } else if (handler instanceof Ci.nsIDBusHandlerApp) {
      return {
        name: handler.name,
        service: handler.service,
        method: handler.method,
        objectPath: handler.objectPath,
        dBusInterface: handler.dBusInterface,
      };
    }
    // If the handler is an unknown handler type, return null.
    // Android default application handler is the case.
    return null;
  },

  /**
   * @param handlerObj
   *        Serializable representation of a handler object.
   * @returns  {nsIHandlerApp}  the handler app, if any; otherwise null
   */
  handlerAppFromSerializable(handlerObj) {
    let handlerApp;
    if ("path" in handlerObj) {
      try {
        let file = new FileUtils.File(handlerObj.path);
        if (!file.exists()) {
          return null;
        }
        handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
                       .createInstance(Ci.nsILocalHandlerApp);
        handlerApp.executable = file;
      } catch (ex) {
        return null;
      }
    } else if ("uriTemplate" in handlerObj) {
      handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]
                     .createInstance(Ci.nsIWebHandlerApp);
      handlerApp.uriTemplate = handlerObj.uriTemplate;
    } else if ("service" in handlerObj) {
      handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]
                     .createInstance(Ci.nsIDBusHandlerApp);
      handlerApp.service = handlerObj.service;
      handlerApp.method = handlerObj.method;
      handlerApp.objectPath = handlerObj.objectPath;
      handlerApp.dBusInterface = handlerObj.dBusInterface;
    } else {
      return null;
    }

    handlerApp.name = handlerObj.name;
    return handlerApp;
  },

  /**
   * The function returns a reference to the "mimeTypes" or "schemes" object
   * based on which type of handlerInfo is provided.
   */
  _getHandlerListByHandlerInfoType(handlerInfo) {
    return this._isMIMEInfo(handlerInfo) ? this._store.data.mimeTypes
                                         : this._store.data.schemes;
  },

  /**
   * Determines whether an nsIHandlerInfo instance represents a MIME type.
   */
  _isMIMEInfo(handlerInfo) {
    // We cannot rely only on the instanceof check because on Android both MIME
    // types and protocols are instances of nsIMIMEInfo. We still do the check
    // so that properties of nsIMIMEInfo become available to the callers.
    return handlerInfo instanceof Ci.nsIMIMEInfo &&
           handlerInfo.type.includes("/");
  },

  // nsIHandlerService
  exists(handlerInfo) {
    return handlerInfo.type in this._getHandlerListByHandlerInfoType(handlerInfo);
  },

  // nsIHandlerService
  remove(handlerInfo) {
    delete this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type];
    this._store.saveSoon();
  },

  // nsIHandlerService
  getTypeFromExtension(fileExtension) {
    let extension = fileExtension.toLowerCase();
    let mimeTypes = this._store.data.mimeTypes;
    for (let type of Object.keys(mimeTypes)) {
      if (mimeTypes[type].extensions &&
          mimeTypes[type].extensions.includes(extension)) {
        return type;
      }
    }
    return "";
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);
PK
!<Flvvcomponents/nsHandlerService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;


const CLASS_MIMEINFO        = "mimetype";
const CLASS_PROTOCOLINFO    = "scheme";


// namespace prefix
const NC_NS                 = "http://home.netscape.com/NC-rdf#";

// the most recent default handlers that have been injected.  Note that
// this is used to construct an RDF resource, which needs to have NC_NS
// prepended, since that hasn't been done yet
const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion";

// type list properties

const NC_MIME_TYPES         = NC_NS + "MIME-types";
const NC_PROTOCOL_SCHEMES   = NC_NS + "Protocol-Schemes";

// content type ("type") properties

// nsIHandlerInfo::type
const NC_VALUE              = NC_NS + "value";

// additional extensions
const NC_FILE_EXTENSIONS    = NC_NS + "fileExtensions";

// references nsIHandlerInfo record
const NC_HANDLER_INFO       = NC_NS + "handlerProp";

// handler info ("info") properties

// nsIHandlerInfo::preferredAction
const NC_SAVE_TO_DISK       = NC_NS + "saveToDisk";
const NC_HANDLE_INTERNALLY  = NC_NS + "handleInternal";
const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault";

// nsIHandlerInfo::alwaysAskBeforeHandling
const NC_ALWAYS_ASK         = NC_NS + "alwaysAsk";

// references nsIHandlerApp records
const NC_PREFERRED_APP      = NC_NS + "externalApplication";
const NC_POSSIBLE_APP       = NC_NS + "possibleApplication";

// handler app ("handler") properties

// nsIHandlerApp::name
const NC_PRETTY_NAME        = NC_NS + "prettyName";

// nsILocalHandlerApp::executable
const NC_PATH               = NC_NS + "path";

// nsIWebHandlerApp::uriTemplate
const NC_URI_TEMPLATE       = NC_NS + "uriTemplate";

// nsIDBusHandlerApp::service
const NC_SERVICE            = NC_NS + "service";

// nsIDBusHandlerApp::method
const NC_METHOD             = NC_NS + "method";

// nsIDBusHandlerApp::objectPath
const NC_OBJPATH            = NC_NS + "objectPath";

// nsIDBusHandlerApp::dbusInterface
const NC_INTERFACE            = NC_NS + "dBusInterface";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");


function HandlerService() {
  this._init();
}

const HandlerServiceFactory = {
  _instance: null,
  createInstance: function (outer, iid) {
    if (this._instance)
      return this._instance;

    let processType = Cc["@mozilla.org/xre/runtime;1"].
      getService(Ci.nsIXULRuntime).processType;
    if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
      return Cr.NS_ERROR_NOT_IMPLEMENTED;

    return (this._instance = new HandlerService());
  }
};

HandlerService.prototype = {
  //**************************************************************************//
  // XPCOM Plumbing

  classID:          Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIHandlerService]),
  _xpcom_factory: HandlerServiceFactory,

  //**************************************************************************//
  // Initialization & Destruction
  
  _init: function HS__init() {
    // Observe profile-before-change so we can switch to the datasource
    // in the new profile when the user changes profiles.
    this._observerSvc.addObserver(this, "profile-before-change");

    // Observe xpcom-shutdown so we can remove these observers
    // when the application shuts down.
    this._observerSvc.addObserver(this, "xpcom-shutdown");

    // Observe profile-do-change so that non-default profiles get upgraded too
    this._observerSvc.addObserver(this, "profile-do-change");

    // Observe handlersvc-rdf-replace so we can switch to the datasource
    this._observerSvc.addObserver(this, "handlersvc-rdf-replace");
  },

  _updateDB: function HS__updateDB() {
    try {
      var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
    } catch(ex) {
      // accessing the datastore failed, we can't update anything
      return;
    }

    try {
      // if we don't have the current version of the default prefs for
      // this locale, inject any new default handers into the datastore
      if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) {

        // set the new version first so that if we recurse we don't
        // call _injectNewDefaults several times
        this._datastoreDefaultHandlersVersion =
          this._prefsDefaultHandlersVersion;
        this._injectNewDefaults();
      } 
    } catch (ex) {
      // if injecting the defaults failed, set the version back to the
      // previous value
      this._datastoreDefaultHandlersVersion = defaultHandlersVersion;
    }
  },

  get _currentLocale() {
    const locSvc = Cc["@mozilla.org/intl/localeservice;1"].
                   getService(Ci.mozILocaleService);
    return locSvc.getAppLocaleAsLangTag();
  }, 

  _destroy: function HS__destroy() {
    this._observerSvc.removeObserver(this, "profile-before-change");
    this._observerSvc.removeObserver(this, "xpcom-shutdown");
    this._observerSvc.removeObserver(this, "profile-do-change");
    this._observerSvc.removeObserver(this, "handlersvc-rdf-replace");

    // XXX Should we also null references to all the services that get stored
    // by our memoizing getters in the Convenience Getters section?
  },

  _onProfileChange: function HS__onProfileChange() {
    // Lose our reference to the datasource so we reacquire it
    // from the new profile the next time we need it.
    this.__ds = null;
  },

  _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) {
    var enumerator = aArray.enumerate();
    while (enumerator.hasMoreElements()) {
      let handler = enumerator.getNext();
      handler.QueryInterface(Ci.nsIHandlerApp);
      if (handler.equals(aHandler))
        return true;
    }
    
    return false;
  },

  // note that this applies to the current locale only 
  get _datastoreDefaultHandlersVersion() {
    var version = this._getValue("urn:root", NC_NS + this._currentLocale +
                                             "_" + DEFAULT_HANDLERS_VERSION);
    
    return version ? version : -1;
  },

  set _datastoreDefaultHandlersVersion(aNewVersion) {
    return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" + 
                            DEFAULT_HANDLERS_VERSION, aNewVersion);
  },

  get _prefsDefaultHandlersVersion() {
    // get handler service pref branch
    var prefSvc = Cc["@mozilla.org/preferences-service;1"].
                  getService(Ci.nsIPrefService);
    var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService.");

    // get the version of the preferences for this locale
    return Number(handlerSvcBranch.
                  getComplexValue("defaultHandlersVersion", 
                                  Ci.nsIPrefLocalizedString).data);
  },
  
  _injectNewDefaults: function HS__injectNewDefaults() {
    // get handler service pref branch
    var prefSvc = Cc["@mozilla.org/preferences-service;1"].
                  getService(Ci.nsIPrefService);

    let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes.");
    let schemePrefList = schemesPrefBranch.getChildList("");

    var schemes = {};

    // read all the scheme prefs into a hash
    for (var schemePrefName of schemePrefList) {

      let [scheme, handlerNumber, attribute] = schemePrefName.split(".");

      try {
        var attrData =
          schemesPrefBranch.getComplexValue(schemePrefName,
                                            Ci.nsIPrefLocalizedString).data;
        if (!(scheme in schemes))
          schemes[scheme] = {};
  
        if (!(handlerNumber in schemes[scheme]))
          schemes[scheme][handlerNumber] = {};
        
        schemes[scheme][handlerNumber][attribute] = attrData;
      } catch (ex) {}
    }

    let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                   getService(Ci.nsIExternalProtocolService);
    for (var scheme in schemes) {

      // This clause is essentially a reimplementation of 
      // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
      // Necessary because calling that from here would make XPConnect barf
      // when getService tried to re-enter the constructor for this
      // service.
      let osDefaultHandlerFound = {};
      let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme, 
                               osDefaultHandlerFound);
      
      if (this.exists(protoInfo))
        this.fillHandlerInfo(protoInfo, null);
      else
        protoSvc.setProtocolHandlerDefaults(protoInfo, 
                                            osDefaultHandlerFound.value);

      // cache the possible handlers to avoid extra xpconnect traversals.      
      let possibleHandlers = protoInfo.possibleApplicationHandlers;

      for (let handlerNumber in schemes[scheme]) {
        let handlerPrefs = schemes[scheme][handlerNumber];
        let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
                         createInstance(Ci.nsIWebHandlerApp);

        handlerApp.uriTemplate = handlerPrefs.uriTemplate;
        handlerApp.name = handlerPrefs.name;                

        if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
          possibleHandlers.appendElement(handlerApp);
        }
      }

      this.store(protoInfo);
    }
  },

  //**************************************************************************//
  // nsIObserver
  
  observe: function HS__observe(subject, topic, data) {
    switch(topic) {
      case "profile-before-change":
        this._onProfileChange();
        break;
      case "xpcom-shutdown":
        this._destroy();
        break;
      case "profile-do-change":
        this._updateDB();
        break;
      case "handlersvc-rdf-replace":
        if (this.__ds) {
          this._rdf.UnregisterDataSource(this.__ds);
          this.__ds = null;
        }
        this._observerSvc.notifyObservers(null, "handlersvc-rdf-replace-complete");
        break;
    }
  },


  //**************************************************************************//
  // nsIHandlerService

  enumerate: function HS_enumerate() {
    var handlers = Cc["@mozilla.org/array;1"].
                   createInstance(Ci.nsIMutableArray);
    this._appendHandlers(handlers, CLASS_MIMEINFO);
    this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
    return handlers.enumerate();
  },

  fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
    var type = aOverrideType || aHandlerInfo.type;
    var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);

    // Determine whether or not information about this handler is available
    // in the datastore by looking for its "value" property, which stores its
    // type and should always be present.
    if (!this._hasValue(typeID, NC_VALUE))
      throw new Components.Exception("handlerSvc fillHandlerInfo: don't know this type",
                                     Cr.NS_ERROR_NOT_AVAILABLE);

    // Note: for historical reasons, we don't actually check that the type
    // record has a "handlerProp" property referencing the info record.  It's
    // unclear whether or not we should start doing this check; perhaps some
    // legacy datasources don't have such references.
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), type);

    aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID);

    var preferredHandlerID =
      this._getPreferredHandlerID(this._getClass(aHandlerInfo), type);

    // Retrieve the preferred handler.
    // Note: for historical reasons, we don't actually check that the info
    // record has an "externalApplication" property referencing the preferred
    // handler record.  It's unclear whether or not we should start doing
    // this check; perhaps some legacy datasources don't have such references.
    aHandlerInfo.preferredApplicationHandler =
      this._retrieveHandlerApp(preferredHandlerID);

    // Fill the array of possible handlers with the ones in the datastore.
    this._fillPossibleHandlers(infoID,
                               aHandlerInfo.possibleApplicationHandlers,
                               aHandlerInfo.preferredApplicationHandler);

    // If we have an "always ask" flag stored in the RDF, always use its
    // value. Otherwise, use the default value stored in the pref service.
    var alwaysAsk;
    if (this._hasValue(infoID, NC_ALWAYS_ASK)) {
      alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false");
    } else {
      var prefSvc = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefService);
      var prefBranch = prefSvc.getBranch("network.protocol-handler.");
      // If neither of the prefs exists, be paranoid and prompt.
      alwaysAsk =
        prefBranch.getBoolPref("warn-external." + type,
                               prefBranch.getBoolPref("warn-external-default",
                                                      true));
    }
    aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk;

    // If the object represents a MIME type handler, then also retrieve
    // any file extensions.
    if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
      for (let fileExtension of this._retrieveFileExtensions(typeID))
        aHandlerInfo.appendExtension(fileExtension);
  },

  store: function HS_store(aHandlerInfo) {
    // FIXME: when we switch from RDF to something with transactions (like
    // SQLite), enclose the following changes in a transaction so they all
    // get rolled back if any of them fail and we don't leave the datastore
    // in an inconsistent state.

    this._ensureRecordsForType(aHandlerInfo);
    this._storePreferredAction(aHandlerInfo);
    this._storePreferredHandler(aHandlerInfo);
    this._storePossibleHandlers(aHandlerInfo);
    this._storeAlwaysAsk(aHandlerInfo);
    this._storeExtensions(aHandlerInfo);

    // Write the changes to the database immediately so we don't lose them
    // if the application crashes.
    if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
      this._ds.Flush();
  },

  exists: function HS_exists(aHandlerInfo) {
    var found;

    try {
      var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
      found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type);
    } catch (e) {
      // If the RDF threw (eg, corrupt file), treat as nonexistent.
      found = false;
    }

    return found;
  },

  remove: function HS_remove(aHandlerInfo) {
    var preferredHandlerID =
      this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    this._removeAssertions(preferredHandlerID);

    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);

    // Get a list of possible handlers.  After we have removed the info record,
    // we'll check if any other info records reference these handlers, and we'll
    // remove the handler records that aren't referenced by other info records.
    var possibleHandlerIDs = [];
    var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
    while (possibleHandlerTargets.hasMoreElements()) {
      let possibleHandlerTarget = possibleHandlerTargets.getNext();
      // Note: possibleHandlerTarget should always be an nsIRDFResource.
      // The conditional is just here in case of a corrupt RDF datasource.
      if (possibleHandlerTarget instanceof Ci.nsIRDFResource)
        possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8);
    }

    // Remove the info record.
    this._removeAssertions(infoID);

    // Now that we've removed the info record, remove any possible handlers
    // that aren't referenced by other info records.
    for (let possibleHandlerID of possibleHandlerIDs)
      if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID))
        this._removeAssertions(possibleHandlerID);

    var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    this._removeAssertions(typeID);

    // Now that there's no longer a handler for this type, remove the type
    // from the list of types for which there are known handlers.
    var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
    var type = this._rdf.GetResource(typeID);
    var typeIndex = typeList.IndexOf(type);
    if (typeIndex != -1)
      typeList.RemoveElementAt(typeIndex, true);

    // Write the changes to the database immediately so we don't lose them
    // if the application crashes.
    // XXX If we're removing a bunch of handlers at once, will flushing
    // after every removal cause a significant performance hit?
    if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
      this._ds.Flush();
  },

  getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) {
    var fileExtension = aFileExtension.toLowerCase();
    var typeID;

    // See bug 1100069 for why we want to fail gracefully and silently here.
    try {
      this._ds;
    } catch (ex) {
      Components.returnCode = Cr.NS_ERROR_NOT_AVAILABLE;
      return;
    }

    if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension))
      typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension);

    if (typeID && this._hasValue(typeID, NC_VALUE)) {
      let type = this._getValue(typeID, NC_VALUE);
      if (type == "")
        throw Cr.NS_ERROR_FAILURE;
      return type;
    }

    return "";
  },


  //**************************************************************************//
  // Retrieval Methods

  /**
   * Retrieve the preferred action for the info record with the given ID.
   *
   * @param aInfoID  {string}  the info record ID
   *
   * @returns  {integer}  the preferred action enumeration value
   */
  _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) {
    if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true")
      return Ci.nsIHandlerInfo.saveToDisk;
    
    if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true")
      return Ci.nsIHandlerInfo.useSystemDefault;
    
    if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true")
      return Ci.nsIHandlerInfo.handleInternally;

    return Ci.nsIHandlerInfo.useHelperApp;
  },

  /**
   * Fill an array of possible handlers with the handlers for the given info ID.
   *
   * @param aInfoID            {string}           the ID of the info record
   * @param aPossibleHandlers  {nsIMutableArray}  the array of possible handlers
   * @param aPreferredHandler  {nsIHandlerApp}    the preferred handler, if any
   */
  _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID,
                                                           aPossibleHandlers,
                                                           aPreferredHandler) {
    // The set of possible handlers should include the preferred handler,
    // but legacy datastores (from before we added possible handlers) won't
    // include the preferred handler, so check if it's included as we build
    // the list of handlers, and, if it's not included, add it to the list.
    if (aPreferredHandler)
      aPossibleHandlers.appendElement(aPreferredHandler);

    var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP);

    while (possibleHandlerTargets.hasMoreElements()) {
      let possibleHandlerTarget = possibleHandlerTargets.getNext();
      if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource))
        continue;

      let possibleHandlerID = possibleHandlerTarget.ValueUTF8;
      let possibleHandler = this._retrieveHandlerApp(possibleHandlerID);
      if (possibleHandler && (!aPreferredHandler ||
                              !possibleHandler.equals(aPreferredHandler)))
        aPossibleHandlers.appendElement(possibleHandler);
    }
  },

  /**
   * Retrieve the handler app object with the given ID.
   *
   * @param aHandlerAppID  {string}  the ID of the handler app to retrieve
   *
   * @returns  {nsIHandlerApp}  the handler app, if any; otherwise null
   */
  _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) {
    var handlerApp;

    // If it has a path, it's a local handler; otherwise, it's a web handler.
    if (this._hasValue(aHandlerAppID, NC_PATH)) {
      let executable =
        this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH));
      if (!executable)
        return null;

      handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
                   createInstance(Ci.nsILocalHandlerApp);
      handlerApp.executable = executable;
    }
    else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) {
      let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE);
      if (!uriTemplate)
        return null;

      handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
                   createInstance(Ci.nsIWebHandlerApp);
      handlerApp.uriTemplate = uriTemplate;
    }
    else if (this._hasValue(aHandlerAppID, NC_SERVICE)) {
      let service = this._getValue(aHandlerAppID, NC_SERVICE);
      if (!service)
        return null;
      
      let method = this._getValue(aHandlerAppID, NC_METHOD);
      if (!method)
        return null;
      
      let objpath = this._getValue(aHandlerAppID, NC_OBJPATH);
      if (!objpath)
        return null;
      
      let iface = this._getValue(aHandlerAppID, NC_INTERFACE);
      if (!iface)
        return null;
      
      handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
                   createInstance(Ci.nsIDBusHandlerApp);
      handlerApp.service   = service;
      handlerApp.method    = method;
      handlerApp.objectPath   = objpath;
      handlerApp.dBusInterface = iface;
      
    }
    else
      return null;

    handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME);

    return handlerApp;
  },

  /*
   * Retrieve file extensions, if any, for the MIME type with the given type ID.
   *
   * @param aTypeID  {string}  the type record ID
   */
  _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) {
    var fileExtensions = [];

    var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS);

    while (fileExtensionTargets.hasMoreElements()) {
      let fileExtensionTarget = fileExtensionTargets.getNext();
      if (fileExtensionTarget instanceof Ci.nsIRDFLiteral &&
          fileExtensionTarget.Value != "")
        fileExtensions.push(fileExtensionTarget.Value);
    }

    return fileExtensions;
  },

  /**
   * Get the file with the given path.  This is not as simple as merely
   * initializing a local file object with the path, because the path might be
   * relative to the current process directory, in which case we have to
   * construct a path starting from that directory.
   *
   * @param aPath  {string}  a path to a file
   *
   * @returns {nsILocalFile} the file, or null if the file does not exist
   */
  _getFileWithPath: function HS__getFileWithPath(aPath) {
    var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);

    try {
      file.initWithPath(aPath);

      if (file.exists())
        return file;
    }
    catch(ex) {
      // Note: for historical reasons, we don't actually check to see
      // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what
      // nsILocalFile::initWithPath throws when a path is relative.

      file = this._dirSvc.get("XCurProcD", Ci.nsIFile);

      try {
        file.append(aPath);
        if (file.exists())
          return file;
      }
      catch(ex) {}
    }

    return null;
  },


  //**************************************************************************//
  // Storage Methods

  _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);

    switch(aHandlerInfo.preferredAction) {
      case Ci.nsIHandlerInfo.saveToDisk:
        this._setLiteral(infoID, NC_SAVE_TO_DISK, "true");
        this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
        this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
        break;

      case Ci.nsIHandlerInfo.handleInternally:
        this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true");
        this._removeTarget(infoID, NC_SAVE_TO_DISK);
        this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
        break;

      case Ci.nsIHandlerInfo.useSystemDefault:
        this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true");
        this._removeTarget(infoID, NC_SAVE_TO_DISK);
        this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
        break;

      // This value is indicated in the datastore either by the absence of
      // the three properties or by setting them all "false".  Of these two
      // options, the former seems preferable, because it reduces the size
      // of the RDF file and thus the amount of stuff we have to parse.
      case Ci.nsIHandlerInfo.useHelperApp:
      default:
        this._removeTarget(infoID, NC_SAVE_TO_DISK);
        this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
        this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
        break;
    }
  },

  _handlerAppIsUnknownType: function HS__handlerAppIsUnknownType(aHandlerApp) {
    if (aHandlerApp instanceof Ci.nsILocalHandlerApp ||
        aHandlerApp instanceof Ci.nsIWebHandlerApp ||
        aHandlerApp instanceof Ci.nsIDBusHandlerApp) {
      return false;
    } else {
      return true;
    }
  },


  _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    var handlerID =
      this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);

    var handler = aHandlerInfo.preferredApplicationHandler;

    if (handler && !this._handlerAppIsUnknownType(handler)) {
      this._storeHandlerApp(handlerID, handler);

      // Make this app be the preferred app for the handler info.
      //
      // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
      // this setting and instead identifies the preferred app as the resource
      // whose URI follows the pattern urn:<class>:externalApplication:<type>.
      // But the old downloadactions.js code used to set this property, so just
      // in case there is still some code somewhere that relies on its presence,
      // we set it here.
      this._setResource(infoID, NC_PREFERRED_APP, handlerID);
    }
    else {
      // There isn't a preferred handler or the handler cannot be serialized,
      // for example the Android default application handler. Remove the
      // existing record for it, if any.
      this._removeTarget(infoID, NC_PREFERRED_APP);
      this._removeAssertions(handlerID);
    }
  },

  /**
   * Store the list of possible handler apps for the content type represented
   * by the given handler info object.
   *
   * @param aHandlerInfo  {nsIHandlerInfo}  the handler info object
   */
  _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) {
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);

    // First, retrieve the set of handler apps currently stored for the type,
    // keeping track of their IDs in a hash that we'll use to determine which
    // ones are no longer valid and should be removed.
    var currentHandlerApps = {};
    var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
    while (currentHandlerTargets.hasMoreElements()) {
      let handlerApp = currentHandlerTargets.getNext();
      if (handlerApp instanceof Ci.nsIRDFResource) {
        let handlerAppID = handlerApp.ValueUTF8;
        currentHandlerApps[handlerAppID] = true;
      }
    }

    // Next, store any new handler apps.
    var newHandlerApps =
      aHandlerInfo.possibleApplicationHandlers.enumerate();
    while (newHandlerApps.hasMoreElements()) {
      let handlerApp =
        newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
      // If the handlerApp is an unknown type, ignore it.
      // Android default application handler is the case.
      if (this._handlerAppIsUnknownType(handlerApp)) {
        continue;
      }
      let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
      if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
        this._storeHandlerApp(handlerAppID, handlerApp);
        this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
      }
      delete currentHandlerApps[handlerAppID];
    }

    // Finally, remove any old handler apps that aren't being used anymore,
    // and if those handler apps aren't being used by any other type either,
    // then completely remove their record from the datastore so we don't
    // leave it clogged up with information about handler apps we don't care
    // about anymore.
    for (let handlerAppID in currentHandlerApps) {
      this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
      if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID))
        this._removeAssertions(handlerAppID);
    }
  },

  /**
   * Store the given handler app.
   *
   * Note: the reason this method takes the ID of the handler app in a param
   * is that the ID is different than it usually is when the handler app
   * in question is a preferred handler app, so this method can't just derive
   * the ID of the handler app by calling _getPossibleHandlerAppID, its callers
   * have to do that for it.
   *
   * @param aHandlerAppID {string}        the ID of the handler app to store
   * @param aHandlerApp   {nsIHandlerApp} the handler app to store
   */
  _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) {
    aHandlerApp.QueryInterface(Ci.nsIHandlerApp);
    this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name);

    // In the case of the preferred handler, the handler ID could have been
    // used to refer to a different kind of handler in the past (i.e. either
    // a local hander or a web handler), so if the new handler is a local
    // handler, then we remove any web handler properties and vice versa.
    // This is unnecessary but harmless for possible handlers.

    if (aHandlerApp instanceof Ci.nsILocalHandlerApp) {
      this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path);
      this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
      this._removeTarget(aHandlerAppID, NC_METHOD);
      this._removeTarget(aHandlerAppID, NC_SERVICE);
      this._removeTarget(aHandlerAppID, NC_OBJPATH);
      this._removeTarget(aHandlerAppID, NC_INTERFACE);
    }
    else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
      aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
      this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate);
      this._removeTarget(aHandlerAppID, NC_PATH);
      this._removeTarget(aHandlerAppID, NC_METHOD);
      this._removeTarget(aHandlerAppID, NC_SERVICE);
      this._removeTarget(aHandlerAppID, NC_OBJPATH);
      this._removeTarget(aHandlerAppID, NC_INTERFACE);
    }
    else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
      aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
      this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service);
      this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method);
      this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath);
      this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface);
      this._removeTarget(aHandlerAppID, NC_PATH);
      this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
    }
    else {
      throw "unknown handler type";
    }
	
  },

  _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) {
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    this._setLiteral(infoID,
                     NC_ALWAYS_ASK,
                     aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
  },

  _storeExtensions: function HS__storeExtensions(aHandlerInfo) {
    if (aHandlerInfo instanceof Ci.nsIMIMEInfo) {
      var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
      let source = this._rdf.GetResource(typeID);
      let property = this._rdf.GetResource(NC_FILE_EXTENSIONS);
      var extEnum = aHandlerInfo.getFileExtensions();
      while (extEnum.hasMore()) {
        let ext = extEnum.getNext().toLowerCase();
        let target = this._rdf.GetLiteral(ext);
        this._ds.Assert(source, property, target, true);
      }
    }
  },


  //**************************************************************************//
  // Convenience Getters

  // Observer Service
  __observerSvc: null,
  get _observerSvc() {
    if (!this.__observerSvc)
      this.__observerSvc =
        Cc["@mozilla.org/observer-service;1"].
        getService(Ci.nsIObserverService);
    return this.__observerSvc;
  },

  // Directory Service
  __dirSvc: null,
  get _dirSvc() {
    if (!this.__dirSvc)
      this.__dirSvc =
        Cc["@mozilla.org/file/directory_service;1"].
        getService(Ci.nsIProperties);
    return this.__dirSvc;
  },

  // MIME Service
  __mimeSvc: null,
  get _mimeSvc() {
    if (!this.__mimeSvc)
      this.__mimeSvc =
        Cc["@mozilla.org/mime;1"].
        getService(Ci.nsIMIMEService);
    return this.__mimeSvc;
  },

  // Protocol Service
  __protocolSvc: null,
  get _protocolSvc() {
    if (!this.__protocolSvc)
      this.__protocolSvc =
        Cc["@mozilla.org/uriloader/external-protocol-service;1"].
        getService(Ci.nsIExternalProtocolService);
    return this.__protocolSvc;
  },

  // RDF Service
  __rdf: null,
  get _rdf() {
    if (!this.__rdf)
      this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
                   getService(Ci.nsIRDFService);
    return this.__rdf;
  },

  // RDF Container Utils
  __containerUtils: null,
  get _containerUtils() {
    if (!this.__containerUtils)
      this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"].
                              getService(Ci.nsIRDFContainerUtils);
    return this.__containerUtils;
  },

  // RDF datasource containing content handling config (i.e. mimeTypes.rdf)
  __ds: null,
  get _ds() {
    if (!this.__ds) {
      var file = this._dirSvc.get("ProfD", Ci.nsIFile);
      file.append("mimeTypes.rdf");
      // FIXME: make this a memoizing getter if we use it anywhere else.
      var ioService = Cc["@mozilla.org/network/io-service;1"].
                      getService(Ci.nsIIOService);
      var fileHandler = ioService.getProtocolHandler("file").
                        QueryInterface(Ci.nsIFileProtocolHandler);
      this.__ds =
        this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
      // do any necessary updating of the datastore
      this._updateDB();
    }

    return this.__ds;
  },


  //**************************************************************************//
  // Datastore Utils

  /**
   * Get the string identifying whether this is a MIME or a protocol handler.
   * This string is used in the URI IDs of various RDF properties.
   * 
   * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
   * 
   * @returns {string} the class
   */
  _getClass: function HS__getClass(aHandlerInfo) {
    if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
      return CLASS_MIMEINFO;
    else
      return CLASS_PROTOCOLINFO;
  },

  /**
   * Return the unique identifier for a content type record, which stores
   * the value field plus a reference to the content type's handler info record.
   *
   * |urn:<class>:<type>|
   *
   * XXX: should this be a property of nsIHandlerInfo?
   *
   * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
   * @param aType  {string} the type (a MIME type or protocol scheme)
   *
   * @returns {string} the ID
   */
  _getTypeID: function HS__getTypeID(aClass, aType) {
    return "urn:" + aClass + ":" + aType;
  },

  /**
   * Return the unique identifier for a handler info record, which stores
   * the preferredAction and alwaysAsk fields plus a reference to the preferred
   * handler app.  Roughly equivalent to the nsIHandlerInfo interface.
   *
   * |urn:<class>:handler:<type>|
   *
   * FIXME: the type info record should be merged into the type record,
   * since there's a one to one relationship between them, and this record
   * merely stores additional attributes of a content type.
   *
   * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
   * @param aType  {string} the type (a MIME type or protocol scheme)
   *
   * @returns {string} the ID
   */
  _getInfoID: function HS__getInfoID(aClass, aType) {
    return "urn:" + aClass + ":handler:" + aType;
  },

  /**
   * Return the unique identifier for a preferred handler record, which stores
   * information about the preferred handler for a given content type, including
   * its human-readable name and the path to its executable (for a local app)
   * or its URI template (for a web app).
   * 
   * |urn:<class>:externalApplication:<type>|
   *
   * XXX: should this be a property of nsIHandlerApp?
   *
   * FIXME: this should be an arbitrary ID, and we should retrieve it from
   * the datastore for a given content type via the NC:ExternalApplication
   * property rather than looking for a specific ID, so a handler doesn't
   * have to change IDs when it goes from being a possible handler to being
   * the preferred one (once we support possible handlers).
   * 
   * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
   * @param aType  {string} the type (a MIME type or protocol scheme)
   * 
   * @returns {string} the ID
   */
  _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) {
    return "urn:" + aClass + ":externalApplication:" + aType;
  },

  /**
   * Return the unique identifier for a handler app record, which stores
   * information about a possible handler for one or more content types,
   * including its human-readable name and the path to its executable (for a
   * local app) or its URI template (for a web app).
   *
   * Note: handler app IDs for preferred handlers are different.  For those,
   * see the _getPreferredHandlerID method.
   *
   * @param aHandlerApp  {nsIHandlerApp}   the handler app object
   */
  _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) {
    var handlerAppID = "urn:handler:";

    if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
      handlerAppID += "local:" + aHandlerApp.executable.path;
    else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
      aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
      handlerAppID += "web:" + aHandlerApp.uriTemplate;
    }
    else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
      aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
      handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate;
    }else{
	throw "unknown handler type";
    }
    
    return handlerAppID;
  },

  /**
   * Get the list of types for the given class, creating the list if it doesn't
   * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
   * (i.e. the result of a call to _getClass).
   * 
   * |urn:<class>s|
   * |urn:<class>s:root|
   * 
   * @param aClass {string} the class for which to retrieve a list of types
   *
   * @returns {nsIRDFContainer} the list of types
   */
  _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
    var source = this._rdf.GetResource("urn:" + aClass + "s");
    var property =
      this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
                                                     : NC_PROTOCOL_SCHEMES);
    var target = this._rdf.GetResource("urn:" + aClass + "s:root");

    // Make sure we have an arc from the source to the target.
    if (!this._ds.HasAssertion(source, property, target, true))
      this._ds.Assert(source, property, target, true);

    // Make sure the target is a container.
    if (!this._containerUtils.IsContainer(this._ds, target))
      this._containerUtils.MakeSeq(this._ds, target);

    // Get the type list as an RDF container.
    var typeList = Cc["@mozilla.org/rdf/container;1"].
                   createInstance(Ci.nsIRDFContainer);
    typeList.Init(this._ds, target);

    return typeList;
  },

  /**
   * Make sure there are records in the datasource for the given content type
   * by creating them if they don't already exist.  We have to do this before
   * storing any specific data, because we can't assume the presence
   * of the records (the nsIHandlerInfo object might have been created
   * from the OS), and the records have to all be there in order for the helper
   * app service to properly construct an nsIHandlerInfo object for the type.
   *
   * Based on old downloadactions.js::_ensureMIMERegistryEntry.
   *
   * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record
   */
  _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) {
    // Get the list of types.
    var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));

    // If there's already a record in the datastore for this type, then we
    // don't need to do anything more.
    var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    var type = this._rdf.GetResource(typeID);
    if (typeList.IndexOf(type) != -1)
      return;

    // Create a basic type record for this type.
    typeList.AppendElement(type);
    this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
    
    // Create a basic info record for this type.
    var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
    this._setResource(typeID, NC_HANDLER_INFO, infoID);
    // XXX Shouldn't we set preferredAction to useSystemDefault?
    // That's what it is if there's no record in the datastore; why should it
    // change to useHelperApp just because we add a record to the datastore?
    
    // Create a basic preferred handler record for this type.
    // XXX Not sure this is necessary, since preferred handlers are optional,
    // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem
    // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
    // used to create it, so we'll do the same.
    var preferredHandlerID =
      this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
    this._setLiteral(preferredHandlerID, NC_PATH, "");
    this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
  },

  /**
   * Append known handlers of the given class to the given array.  The class
   * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
   *
   * @param aHandlers   {array} the array of handlers to append to
   * @param aClass      {string} the class for which to append handlers
   */
  _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
    var typeList = this._ensureAndGetTypeList(aClass);
    var enumerator = typeList.GetElements();

    while (enumerator.hasMoreElements()) {
      var element = enumerator.getNext();
      
      // This should never happen.  If it does, that means our datasource
      // is corrupted with type list entries that point to literal values
      // instead of resources.  If it does happen, let's just do our best
      // to recover by ignoring this entry and moving on to the next one.
      if (!(element instanceof Ci.nsIRDFResource))
        continue;

      // Get the value of the element's NC:value property, which contains
      // the MIME type or scheme for which we're retrieving a handler info.
      var type = this._getValue(element.ValueUTF8, NC_VALUE);
      if (!type)
        continue;

      var handler;
      if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root")
        handler = this._mimeSvc.getFromTypeAndExtension(type, null);
      else
        handler = this._protocolSvc.getProtocolHandlerInfo(type);

      aHandlers.appendElement(handler);
    }
  },

  /**
   * Whether or not a property of an RDF source has a value.
   *
   * @param sourceURI   {string}  the URI of the source
   * @param propertyURI {string}  the URI of the property
   * @returns           {boolean} whether or not the property has a value
   */
  _hasValue: function HS__hasValue(sourceURI, propertyURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    return this._ds.hasArcOut(source, property);
  },

  /**
   * Get the value of a property of an RDF source.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @returns           {string} the value of the property
   */
  _getValue: function HS__getValue(sourceURI, propertyURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);

    var target = this._ds.GetTarget(source, property, true);

    if (!target)
      return null;
    
    if (target instanceof Ci.nsIRDFResource)
      return target.ValueUTF8;

    if (target instanceof Ci.nsIRDFLiteral)
      return target.Value;

    return null;
  },

  /**
   * Get all targets for the property of an RDF source.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * 
   * @returns {nsISimpleEnumerator} an enumerator of targets
   */
  _getTargets: function HS__getTargets(sourceURI, propertyURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);

    return this._ds.GetTargets(source, property, true);
  },

  /**
   * Set a property of an RDF source to a literal value.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param value       {string} the literal value
   */
  _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetLiteral(value);
    
    this._setTarget(source, property, target);
  },

  /**
   * Set a property of an RDF source to a resource target.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param targetURI   {string} the URI of the target
   */
  _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetResource(targetURI);
    
    this._setTarget(source, property, target);
  },

  /**
   * Assert an arc into the RDF datasource if there is no arc with the given
   * source and property; otherwise, if there is already an existing arc,
   * change it to point to the given target. _setLiteral and _setResource
   * call this after converting their string arguments into resources
   * and literals, and most callers should call one of those two methods
   * instead of this one.
   *
   * @param source    {nsIRDFResource}  the source
   * @param property  {nsIRDFResource}  the property
   * @param target    {nsIRDFNode}      the target
   */
  _setTarget: function HS__setTarget(source, property, target) {
    if (this._ds.hasArcOut(source, property)) {
      var oldTarget = this._ds.GetTarget(source, property, true);
      this._ds.Change(source, property, oldTarget, target);
    }
    else
      this._ds.Assert(source, property, target, true);
  },

  /**
   * Assert that a property of an RDF source has a resource target.
   * 
   * The difference between this method and _setResource is that this one adds
   * an assertion even if one already exists, which allows its callers to make
   * sets of assertions (i.e. to set a property to multiple targets).
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param targetURI   {string} the URI of the target
   */
  _addResourceAssertion: function HS__addResourceAssertion(sourceURI,
                                                           propertyURI,
                                                           targetURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetResource(targetURI);
    
    this._ds.Assert(source, property, target, true);
  },

  /**
   * Remove an assertion with a resource target.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param targetURI   {string} the URI of the target
   */
  _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI,
                                                                 propertyURI,
                                                                 targetURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetResource(targetURI);

    this._ds.Unassert(source, property, target);
  },

  /**
   * Whether or not a property of an RDF source has a given resource target.
   * 
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param targetURI   {string} the URI of the target
   *
   * @returns {boolean} whether or not there is such an assertion
   */
  _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI,
                                                           propertyURI,
                                                           targetURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetResource(targetURI);

    return this._ds.HasAssertion(source, property, target, true);
  },

  /**
   * Whether or not a property of an RDF source has a given literal value.
   * 
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   * @param value       {string} the literal value
   *
   * @returns {boolean} whether or not there is such an assertion
   */
  _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI,
                                                         propertyURI,
                                                         value) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetLiteral(value);

    return this._ds.HasAssertion(source, property, target, true);
  },

  /**
   * Whether or not there is an RDF source that has the given property set to
   * the given literal value.
   * 
   * @param propertyURI {string} the URI of the property
   * @param value       {string} the literal value
   *
   * @returns {boolean} whether or not there is a source
   */
  _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) {
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetLiteral(value);

    return this._ds.hasArcIn(target, property);
  },

  /**
   * Get the source for a property set to a given literal value.
   *
   * @param propertyURI {string} the URI of the property
   * @param value       {string} the literal value
   */
  _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) {
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetLiteral(value);

    var source = this._ds.GetSource(property, target, true);
    if (source)
      return source.ValueUTF8;

    return null;
  },

  /**
   * Whether or not there is an RDF source that has the given property set to
   * the given resource target.
   * 
   * @param propertyURI {string} the URI of the property
   * @param targetURI   {string} the URI of the target
   *
   * @returns {boolean} whether or not there is a source
   */
  _existsResourceTarget: function HS__existsResourceTarget(propertyURI,
                                                           targetURI) {
    var property = this._rdf.GetResource(propertyURI);
    var target = this._rdf.GetResource(targetURI);

    return this._ds.hasArcIn(target, property);
  },

  /**
   * Remove a property of an RDF source.
   *
   * @param sourceURI   {string} the URI of the source
   * @param propertyURI {string} the URI of the property
   */
  _removeTarget: function HS__removeTarget(sourceURI, propertyURI) {
    var source = this._rdf.GetResource(sourceURI);
    var property = this._rdf.GetResource(propertyURI);

    if (this._ds.hasArcOut(source, property)) {
      var target = this._ds.GetTarget(source, property, true);
      this._ds.Unassert(source, property, target);
    }
  },

 /**
  * Remove all assertions about a given RDF source.
  *
  * Note: not recursive.  If some assertions point to other resources,
  * and you want to remove assertions about those resources too, you need
  * to do so manually.
  *
  * @param sourceURI {string} the URI of the source
  */
  _removeAssertions: function HS__removeAssertions(sourceURI) {
    var source = this._rdf.GetResource(sourceURI);
    var properties = this._ds.ArcLabelsOut(source);
 
    while (properties.hasMoreElements()) {
      let property = properties.getNext();
      let targets = this._ds.GetTargets(source, property, true);
      while (targets.hasMoreElements()) {
        let target = targets.getNext();
        this._ds.Unassert(source, property, target);
      }
    }
  }

};

//****************************************************************************//
// More XPCOM Plumbing

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);
PK
!<__components/nsWebHandlerApp.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

////////////////////////////////////////////////////////////////////////////////
//// Constants

const Ci = Components.interfaces;
const Cr = Components.results;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import('resource://gre/modules/Services.jsm');

////////////////////////////////////////////////////////////////////////////////
//// nsWebHandler class

function nsWebHandlerApp() {}

nsWebHandlerApp.prototype = {
  //////////////////////////////////////////////////////////////////////////////
  //// nsWebHandler

  classDescription: "A web handler for protocols and content",
  classID: Components.ID("8b1ae382-51a9-4972-b930-56977a57919d"),
  contractID: "@mozilla.org/uriloader/web-handler-app;1",

  _name: null,
  _detailedDescription: null,
  _uriTemplate: null,

  //////////////////////////////////////////////////////////////////////////////
  //// nsIHandlerApp

  get name() {
    return this._name;
  },

  set name(aName) {
    this._name = aName;
  },

  get detailedDescription() {
    return this._detailedDescription;
  },

  set detailedDescription(aDesc) {
    this._detailedDescription = aDesc;
  },

  equals: function(aHandlerApp) {
    if (!aHandlerApp)
      throw Cr.NS_ERROR_NULL_POINTER;

    if (aHandlerApp instanceof Ci.nsIWebHandlerApp &&
        aHandlerApp.uriTemplate &&
        this.uriTemplate &&
        aHandlerApp.uriTemplate == this.uriTemplate)
      return true;

    return false;
  },

  launchWithURI: function nWHA__launchWithURI(aURI, aWindowContext) {

    // XXX need to strip passwd & username from URI to handle, as per the
    // WhatWG HTML5 draft.  nsSimpleURL, which is what we're going to get,
    // can't do this directly.  Ideally, we'd fix nsStandardURL to make it
    // possible to turn off all of its quirks handling, and use that...

    // encode the URI to be handled
    var escapedUriSpecToHandle = encodeURIComponent(aURI.spec);

    // insert the encoded URI and create the object version 
    var uriSpecToSend = this.uriTemplate.replace("%s", escapedUriSpecToHandle);
    var ioService = Cc["@mozilla.org/network/io-service;1"].
                    getService(Ci.nsIIOService);
    var uriToSend = ioService.newURI(uriSpecToSend);
    
    // if we have a window context, use the URI loader to load there
    if (aWindowContext) {
      try {
        // getInterface throws if the object doesn't implement the given
        // interface, so this try/catch statement is more of an if.
        // If aWindowContext refers to a remote docshell, send the load
        // request to the correct process.
        aWindowContext.getInterface(Ci.nsIRemoteWindowContext)
                      .openURI(uriToSend);
        return;
      } catch (e) {
        if (e.result != Cr.NS_NOINTERFACE) {
          throw e;
        }
      }

      // create a channel from this URI
      var channel = NetUtil.newChannel({
        uri: uriToSend,
        loadUsingSystemPrincipal: true
      });
      channel.loadFlags = Ci.nsIChannel.LOAD_DOCUMENT_URI;

      // load the channel
      var uriLoader = Cc["@mozilla.org/uriloader;1"].
                      getService(Ci.nsIURILoader);
      // XXX ideally, whether to pass the IS_CONTENT_PREFERRED flag should be
      // passed in from above.  Practically, the flag is probably a reasonable
      // default since browsers don't care much, and link click is likely to be
      // the more interesting case for non-browser apps.  See 
      // <https://bugzilla.mozilla.org/show_bug.cgi?id=392957#c9> for details.
      uriLoader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED,
                        aWindowContext);
      return;
    } 

    // since we don't have a window context, hand it off to a browser
    var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
      getService(Ci.nsIWindowMediator);

    // get browser dom window
    var browserDOMWin = windowMediator.getMostRecentWindow("navigator:browser")
                        .QueryInterface(Ci.nsIDOMChromeWindow)
                        .browserDOMWindow;

    // if we got an exception, there are several possible reasons why:
    // a) this gecko embedding doesn't provide an nsIBrowserDOMWindow
    //    implementation (i.e. doesn't support browser-style functionality),
    //    so we need to kick the URL out to the OS default browser.  This is
    //    the subject of bug 394479.
    // b) this embedding does provide an nsIBrowserDOMWindow impl, but
    //    there doesn't happen to be a browser window open at the moment; one
    //    should be opened.  It's not clear whether this situation will really
    //    ever occur in real life.  If it does, the only API that I can find
    //    that seems reasonably likely to work for most embedders is the
    //    command line handler.  
    // c) something else went wrong 
    //
    // it's not clear how one would differentiate between the three cases
    // above, so for now we don't catch the exception

    // openURI
    browserDOMWin.openURI(uriToSend,
                          null, // no window.opener
                          Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
                          Ci.nsIBrowserDOMWindow.OPEN_NEW,
                          Services.scriptSecurityManager.getSystemPrincipal());
      
    return;
  },

  //////////////////////////////////////////////////////////////////////////////
  //// nsIWebHandlerApp

  get uriTemplate() {
    return this._uriTemplate;
  },

  set uriTemplate(aURITemplate) {
    this._uriTemplate = aURITemplate;
  },

  //////////////////////////////////////////////////////////////////////////////
  //// nsISupports

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebHandlerApp, Ci.nsIHandlerApp])
};

////////////////////////////////////////////////////////////////////////////////
//// Module

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsWebHandlerApp]);

PK
!<0UU components/nsFormAutoComplete.js/* vim: set ts=4 sts=4 sw=4 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";

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, "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm");

function isAutocompleteDisabled(aField) {
  if (aField.autocomplete !== "") {
    return aField.autocomplete === "off";
  }

  return aField.form && aField.form.autocomplete === "off";
}

/**
 * An abstraction to talk with the FormHistory database over
 * the message layer. FormHistoryClient will take care of
 * figuring out the most appropriate message manager to use,
 * and what things to send.
 *
 * It is assumed that nsFormAutoComplete will only ever use
 * one instance at a time, and will not attempt to perform more
 * than one search request with the same instance at a time.
 * However, nsFormAutoComplete might call remove() any number of
 * times with the same instance of the client.
 *
 * @param {Object} clientInfo
 *        Info required to build the FormHistoryClient
 * @param {Node} clientInfo.formField
 *        A DOM node that we're requesting form history for.
 * @param {string} clientInfo.inputName
 *        The name of the input to do the FormHistory look-up with.
 *        If this is searchbar-history, then formField needs to be null,
 *        otherwise constructing will throw.
 */
function FormHistoryClient({ formField, inputName }) {
  if (formField && inputName != this.SEARCHBAR_ID) {
    let window = formField.ownerGlobal;
    let topDocShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDocShell)
                            .sameTypeRootTreeItem
                            .QueryInterface(Ci.nsIDocShell);
    this.mm = topDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIContentFrameMessageManager);
  } else {
    if (inputName == this.SEARCHBAR_ID && formField) {
      throw new Error("FormHistoryClient constructed with both a " +
                      "formField and an inputName. This is not " +
                      "supported, and only empty results will be " +
                      "returned.");
    }
    this.mm = Services.cpmm;
  }

  this.inputName = inputName;
  this.id = FormHistoryClient.nextRequestID++;
}

FormHistoryClient.prototype = {
  SEARCHBAR_ID: "searchbar-history",

  // It is assumed that nsFormAutoComplete only uses / cares about
  // one FormHistoryClient at a time, and won't attempt to have
  // multiple in-flight searches occurring with the same FormHistoryClient.
  // We use an ID number per instantiated FormHistoryClient to make
  // sure we only respond to messages that were meant for us.
  id: 0,
  callback: null,
  inputName: "",
  mm: null,

  /**
   * Query FormHistory for some results.
   *
   * @param {string} searchString
   *        The string to search FormHistory for. See
   *        FormHistory.getAutoCompleteResults.
   * @param {Object} params
   *        An Object with search properties. See
   *        FormHistory.getAutoCompleteResults.
   * @param {function} callback
   *        A callback function that will take a single
   *        argument (the found entries).
   */
  requestAutoCompleteResults(searchString, params, callback) {
    this.mm.sendAsyncMessage("FormHistory:AutoCompleteSearchAsync", {
      id: this.id,
      searchString,
      params,
    });

    this.mm.addMessageListener("FormHistory:AutoCompleteSearchResults", this);
    this.callback = callback;
  },

  /**
   * Cancel an in-flight results request. This ensures that the
   * callback that requestAutoCompleteResults was passed is never
   * called from this FormHistoryClient.
   */
  cancel() {
    this.clearListeners();
  },

  /**
   * Remove an item from FormHistory.
   *
   * @param {string} value
   *
   *        The value to remove for this particular
   *        field.
   */
  remove(value) {
    this.mm.sendAsyncMessage("FormHistory:RemoveEntry", {
      inputName: this.inputName,
      value,
    });
  },

  // Private methods

  receiveMessage(msg) {
    let { id, results } = msg.data;
    if (id != this.id) {
      return;
    }
    if (!this.callback) {
      Cu.reportError("FormHistoryClient received message with no callback");
      return;
    }
    this.callback(results);
    this.clearListeners();
  },

  clearListeners() {
    this.mm.removeMessageListener("FormHistory:AutoCompleteSearchResults", this);
    this.callback = null;
  },
};

FormHistoryClient.nextRequestID = 1;


function FormAutoComplete() {
  this.init();
}

/**
 * Implements the nsIFormAutoComplete interface in the main process.
 */
FormAutoComplete.prototype = {
  classID: Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormAutoComplete, Ci.nsISupportsWeakReference]),

  _prefBranch: null,
  _debug: true, // mirrors browser.formfill.debug
  _enabled: true, // mirrors browser.formfill.enable preference
  _agedWeight: 2,
  _bucketSize: 1,
  _maxTimeGroupings: 25,
  _timeGroupingSize: 7 * 24 * 60 * 60 * 1000 * 1000,
  _expireDays: null,
  _boundaryWeight: 25,
  _prefixWeight: 5,

  // Only one query via FormHistoryClient is performed at a time, and the
  // most recent FormHistoryClient which will be stored in _pendingClient
  // while the query is being performed. It will be cleared when the query
  // finishes, is cancelled, or an error occurs. If a new query occurs while
  // one is already pending, the existing one is cancelled.
  _pendingClient: null,

  init() {
    // Preferences. Add observer so we get notified of changes.
    this._prefBranch = Services.prefs.getBranch("browser.formfill.");
    this._prefBranch.addObserver("", this.observer, true);
    this.observer._self = this;

    this._debug            = this._prefBranch.getBoolPref("debug");
    this._enabled          = this._prefBranch.getBoolPref("enable");
    this._agedWeight       = this._prefBranch.getIntPref("agedWeight");
    this._bucketSize       = this._prefBranch.getIntPref("bucketSize");
    this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
    this._timeGroupingSize = this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
    this._expireDays       = this._prefBranch.getIntPref("expire_days");
  },

  observer: {
    _self: null,

    QueryInterface: XPCOMUtils.generateQI([
      Ci.nsIObserver,
      Ci.nsISupportsWeakReference,
    ]),

    observe(subject, topic, data) {
      let self = this._self;

      if (topic == "nsPref:changed") {
        let prefName = data;
        self.log("got change to " + prefName + " preference");

        switch (prefName) {
          case "agedWeight":
            self._agedWeight = self._prefBranch.getIntPref(prefName);
            break;
          case "debug":
            self._debug = self._prefBranch.getBoolPref(prefName);
            break;
          case "enable":
            self._enabled = self._prefBranch.getBoolPref(prefName);
            break;
          case "maxTimeGroupings":
            self._maxTimeGroupings = self._prefBranch.getIntPref(prefName);
            break;
          case "timeGroupingSize":
            self._timeGroupingSize = self._prefBranch.getIntPref(prefName) * 1000 * 1000;
            break;
          case "bucketSize":
            self._bucketSize = self._prefBranch.getIntPref(prefName);
            break;
          case "boundaryWeight":
            self._boundaryWeight = self._prefBranch.getIntPref(prefName);
            break;
          case "prefixWeight":
            self._prefixWeight = self._prefBranch.getIntPref(prefName);
            break;
          default:
            self.log("Oops! Pref not handled, change ignored.");
        }
      }
    },
  },

  // AutoCompleteE10S needs to be able to call autoCompleteSearchAsync without
  // going through IDL in order to pass a mock DOM object field.
  get wrappedJSObject() {
    return this;
  },

  /*
   * log
   *
   * Internal function for logging debug messages to the Error Console
   * window
   */
  log(message) {
    if (!this._debug) {
      return;
    }
    dump("FormAutoComplete: " + message + "\n");
    Services.console.logStringMessage("FormAutoComplete: " + message);
  },

  /*
   * autoCompleteSearchAsync
   *
   * aInputName    -- |name| attribute from the form input being autocompleted.
   * aUntrimmedSearchString -- current value of the input
   * aField -- nsIDOMHTMLInputElement being autocompleted (may be null if from chrome)
   * aPreviousResult -- previous search result, if any.
   * aDatalistResult -- results from list=datalist for aField.
   * aListener -- nsIFormAutoCompleteObserver that listens for the nsIAutoCompleteResult
   *              that may be returned asynchronously.
   */
  autoCompleteSearchAsync(aInputName,
                          aUntrimmedSearchString,
                          aField,
                          aPreviousResult,
                          aDatalistResult,
                          aListener) {
    function sortBytotalScore(a, b) {
      return b.totalScore - a.totalScore;
    }

    // Guard against void DOM strings filtering into this code.
    if (typeof aInputName === "object") {
      aInputName = "";
    }
    if (typeof aUntrimmedSearchString === "object") {
      aUntrimmedSearchString = "";
    }

    let client = new FormHistoryClient({ formField: aField, inputName: aInputName });

    // If we have datalist results, they become our "empty" result.
    let emptyResult = aDatalistResult || new FormAutoCompleteResult(client,
                                                                    [],
                                                                    aInputName,
                                                                    aUntrimmedSearchString,
                                                                    null);
    if (!this._enabled) {
      if (aListener) {
        aListener.onSearchCompletion(emptyResult);
      }
      return;
    }

    // don't allow form inputs (aField != null) to get results from search bar history
    if (aInputName == "searchbar-history" && aField) {
      this.log('autoCompleteSearch for input name "' + aInputName + '" is denied');
      if (aListener) {
        aListener.onSearchCompletion(emptyResult);
      }
      return;
    }

    if (aField && isAutocompleteDisabled(aField)) {
      this.log("autoCompleteSearch not allowed due to autcomplete=off");
      if (aListener) {
        aListener.onSearchCompletion(emptyResult);
      }
      return;
    }

    this.log("AutoCompleteSearch invoked. Search is: " + aUntrimmedSearchString);
    let searchString = aUntrimmedSearchString.trim().toLowerCase();

    // reuse previous results if:
    // a) length greater than one character (others searches are special cases) AND
    // b) the the new results will be a subset of the previous results
    if (aPreviousResult && aPreviousResult.searchString.trim().length > 1 &&
      searchString.includes(aPreviousResult.searchString.trim().toLowerCase())) {
      this.log("Using previous autocomplete result");
      let result = aPreviousResult;
      let wrappedResult = result.wrappedJSObject;
      wrappedResult.searchString = aUntrimmedSearchString;

      // Leaky abstraction alert: it would be great to be able to split
      // this code between nsInputListAutoComplete and here but because of
      // the way we abuse the formfill autocomplete API in e10s, we have
      // to deal with the <datalist> results here as well (and down below
      // in mergeResults).
      // If there were datalist results result is a FormAutoCompleteResult
      // as defined in nsFormAutoCompleteResult.jsm with the entire list
      // of results in wrappedResult._values and only the results from
      // form history in wrappedResult.entries.
      // First, grab the entire list of old results.
      let allResults = wrappedResult._labels;
      let datalistResults, datalistLabels;
      if (allResults) {
        // We have datalist results, extract them from the values array.
        // Both allResults and values arrays are in the form of:
        // |--wR.entries--|
        // <history entries><datalist entries>
        let oldLabels = allResults.slice(wrappedResult.entries.length);
        let oldValues = wrappedResult._values.slice(wrappedResult.entries.length);

        datalistLabels = [];
        datalistResults = [];
        for (let i = 0; i < oldLabels.length; ++i) {
          if (oldLabels[i].toLowerCase().includes(searchString)) {
            datalistLabels.push(oldLabels[i]);
            datalistResults.push(oldValues[i]);
          }
        }
      }

      let searchTokens = searchString.split(/\s+/);
      // We have a list of results for a shorter search string, so just
      // filter them further based on the new search string and add to a new array.
      let entries = wrappedResult.entries;
      let filteredEntries = [];
      for (let i = 0; i < entries.length; i++) {
        let entry = entries[i];
        // Remove results that do not contain the token
        // XXX bug 394604 -- .toLowerCase can be wrong for some intl chars
        if (searchTokens.some(tok => !entry.textLowerCase.includes(tok))) {
          continue;
        }
        this._calculateScore(entry, searchString, searchTokens);
        this.log("Reusing autocomplete entry '" + entry.text +
                 "' (" + entry.frecency + " / " + entry.totalScore + ")");
        filteredEntries.push(entry);
      }
      filteredEntries.sort(sortBytotalScore);
      wrappedResult.entries = filteredEntries;

      // If we had datalistResults, re-merge them back into the filtered
      // entries.
      if (datalistResults) {
        filteredEntries = filteredEntries.map(elt => elt.text);

        let comments = new Array(filteredEntries.length + datalistResults.length).fill("");
        comments[filteredEntries.length] = "separator";

        // History entries don't have labels (their labels would be read
        // from their values). Pad out the labels array so the datalist
        // results (which do have separate values and labels) line up.
        datalistLabels = new Array(filteredEntries.length).fill("").concat(datalistLabels);
        wrappedResult._values = filteredEntries.concat(datalistResults);
        wrappedResult._labels = datalistLabels;
        wrappedResult._comments = comments;
      }

      if (aListener) {
        aListener.onSearchCompletion(result);
      }
    } else {
      this.log("Creating new autocomplete search result.");

      // Start with an empty list.
      let result = aDatalistResult ?
        new FormAutoCompleteResult(client, [], aInputName, aUntrimmedSearchString, null) :
        emptyResult;

      let processEntry = (aEntries) => {
        if (aField && aField.maxLength > -1) {
          result.entries =
            aEntries.filter(el => el.text.length <= aField.maxLength);
        } else {
          result.entries = aEntries;
        }

        if (aDatalistResult && aDatalistResult.matchCount > 0) {
          result = this.mergeResults(result, aDatalistResult);
        }

        if (aListener) {
          aListener.onSearchCompletion(result);
        }
      };

      this.getAutoCompleteValues(client, aInputName, searchString, processEntry);
    }
  },

  mergeResults(historyResult, datalistResult) {
    let values = datalistResult.wrappedJSObject._values;
    let labels = datalistResult.wrappedJSObject._labels;
    let comments = new Array(values.length).fill("");

    // historyResult will be null if form autocomplete is disabled. We
    // still want the list values to display.
    let entries = historyResult.wrappedJSObject.entries;
    let historyResults = entries.map(entry => entry.text);
    let historyComments = new Array(entries.length).fill("");

    // now put the history results above the datalist suggestions
    let finalValues = historyResults.concat(values);
    let finalLabels = historyResults.concat(labels);
    let finalComments = historyComments.concat(comments);

    // This is ugly: there are two FormAutoCompleteResult classes in the
    // tree, one in a module and one in this file. Datalist results need to
    // use the one defined in the module but the rest of this file assumes
    // that we use the one defined here. To get around that, we explicitly
    // import the module here, out of the way of the other uses of
    // FormAutoCompleteResult.
    let {FormAutoCompleteResult} = Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm",
                                             {});
    return new FormAutoCompleteResult(datalistResult.searchString,
                                      Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
                                      0,
                                      "",
                                      finalValues,
                                      finalLabels,
                                      finalComments,
                                      historyResult);
  },

  stopAutoCompleteSearch() {
    if (this._pendingClient) {
      this._pendingClient.cancel();
      this._pendingClient = null;
    }
  },

  /*
   * Get the values for an autocomplete list given a search string.
   *
   *  client - a FormHistoryClient instance to perform the search with
   *  fieldName - fieldname field within form history (the form input name)
   *  searchString - string to search for
   *  callback - called when the values are available. Passed an array of objects,
   *             containing properties for each result. The callback is only called
   *             when successful.
   */
  getAutoCompleteValues(client, fieldName, searchString, callback) {
    let params = {
      agedWeight:         this._agedWeight,
      bucketSize:         this._bucketSize,
      expiryDate:         1000 * (Date.now() - this._expireDays * 24 * 60 * 60 * 1000),
      fieldname:          fieldName,
      maxTimeGroupings:   this._maxTimeGroupings,
      timeGroupingSize:   this._timeGroupingSize,
      prefixWeight:       this._prefixWeight,
      boundaryWeight:     this._boundaryWeight,
    };

    this.stopAutoCompleteSearch();
    client.requestAutoCompleteResults(searchString, params, (entries) => {
      this._pendingClient = null;
      callback(entries);
    });
    this._pendingClient = client;
  },

  /*
   * _calculateScore
   *
   * entry    -- an nsIAutoCompleteResult entry
   * aSearchString -- current value of the input (lowercase)
   * searchTokens -- array of tokens of the search string
   *
   * Returns: an int
   */
  _calculateScore(entry, aSearchString, searchTokens) {
    let boundaryCalc = 0;
    // for each word, calculate word boundary weights
    for (let token of searchTokens) {
      if (entry.textLowerCase.startsWith(token)) {
        boundaryCalc++;
      }
      if (entry.textLowerCase.includes(" " + token)) {
        boundaryCalc++;
      }
    }
    boundaryCalc = boundaryCalc * this._boundaryWeight;
    // now add more weight if we have a traditional prefix match and
    // multiply boundary bonuses by boundary weight
    if (entry.textLowerCase.startsWith(aSearchString)) {
      boundaryCalc += this._prefixWeight;
    }
    entry.totalScore = Math.round(entry.frecency * Math.max(1, boundaryCalc));
  },

}; // end of FormAutoComplete implementation

// nsIAutoCompleteResult implementation
function FormAutoCompleteResult(client,
                                entries,
                                fieldName,
                                searchString,
                                messageManager) {
  this.client = client;
  this.entries = entries;
  this.fieldName = fieldName;
  this.searchString = searchString;
  this.messageManager = messageManager;
}

FormAutoCompleteResult.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult, Ci.nsISupportsWeakReference]),

  // private
  client: null,
  entries: null,
  fieldName: null,

  _checkIndexBounds(index) {
    if (index < 0 || index >= this.entries.length) {
      throw Components.Exception("Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  },

  // Allow autoCompleteSearch to get at the JS object so it can
  // modify some readonly properties for internal use.
  get wrappedJSObject() {
    return this;
  },

  // Interfaces from idl...
  searchString: "",
  errorDescription: "",
  get defaultIndex() {
    if (this.entries.length == 0) {
      return -1;
    }
    return 0;
  },
  get searchResult() {
    if (this.entries.length == 0) {
      return Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
    }
    return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  },
  get matchCount() {
    return this.entries.length;
  },

  getValueAt(index) {
    this._checkIndexBounds(index);
    return this.entries[index].text;
  },

  getLabelAt(index) {
    return this.getValueAt(index);
  },

  getCommentAt(index) {
    this._checkIndexBounds(index);
    return "";
  },

  getStyleAt(index) {
    this._checkIndexBounds(index);
    return "";
  },

  getImageAt(index) {
    this._checkIndexBounds(index);
    return "";
  },

  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  },

  removeValueAt(index, removeFromDB) {
    this._checkIndexBounds(index);

    let [removedEntry] = this.entries.splice(index, 1);

    if (removeFromDB) {
      this.client.remove(removedEntry.text);
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutoComplete]);
PK
!<SJJ components/FormHistoryStartup.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                  "resource://gre/modules/FormHistory.jsm");

function FormHistoryStartup() { }

FormHistoryStartup.prototype = {
  classID: Components.ID("{3A0012EB-007F-4BB8-AA81-A07385F77A25}"),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference,
    Ci.nsIFrameMessageListener,
  ]),

  observe(subject, topic, data) {
    switch (topic) {
      case "nsPref:changed":
        FormHistory.updatePrefs();
        break;
      case "idle-daily":
      case "formhistory-expire-now":
        FormHistory.expireOldEntries();
        break;
      case "profile-before-change":
        FormHistory.shutdown();
        break;
      case "profile-after-change":
        this.init();
        break;
    }
  },

  inited: false,
  pendingQuery: null,

  init() {
    if (this.inited) {
      return;
    }
    this.inited = true;

    Services.prefs.addObserver("browser.formfill.", this, true);

    // triggers needed service cleanup and db shutdown
    Services.obs.addObserver(this, "profile-before-change", true);
    Services.obs.addObserver(this, "formhistory-expire-now", true);

    Services.ppmm.loadProcessScript("chrome://satchel/content/formSubmitListener.js", true);
    Services.ppmm.addMessageListener("FormHistory:FormSubmitEntries", this);

    let messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                         .getService(Ci.nsIMessageListenerManager);
    // For each of these messages, we could receive them from content,
    // or we might receive them from the ppmm if the searchbar is
    // having its history queried.
    for (let manager of [messageManager, Services.ppmm]) {
      manager.addMessageListener("FormHistory:AutoCompleteSearchAsync", this);
      manager.addMessageListener("FormHistory:RemoveEntry", this);
    }
  },

  receiveMessage(message) {
    switch (message.name) {
      case "FormHistory:FormSubmitEntries": {
        let entries = message.data;
        let changes = entries.map(entry => ({
          op: "bump",
          fieldname: entry.name,
          value: entry.value,
        }));

        FormHistory.update(changes);
        break;
      }

      case "FormHistory:AutoCompleteSearchAsync": {
        let { id, searchString, params } = message.data;

        if (this.pendingQuery) {
          this.pendingQuery.cancel();
          this.pendingQuery = null;
        }

        let mm;
        let query = null;
        if (message.target instanceof Ci.nsIMessageListenerManager) {
          // The target is the PPMM, meaning that the parent process
          // is requesting FormHistory data on the searchbar.
          mm = message.target;
        } else {
          // Otherwise, the target is a <xul:browser>.
          mm = message.target.messageManager;
        }

        let results = [];
        let processResults = {
          handleResult: aResult => {
            results.push(aResult);
          },
          handleCompletion: aReason => {
            // Check that the current query is still the one we created. Our
            // query might have been canceled shortly before completing, in
            // that case we don't want to call the callback anymore.
            if (query === this.pendingQuery) {
              this.pendingQuery = null;
              if (!aReason) {
                mm.sendAsyncMessage("FormHistory:AutoCompleteSearchResults",
                                    { id, results });
              }
            }
          },
        };

        query = FormHistory.getAutoCompleteResults(searchString, params, processResults);
        this.pendingQuery = query;
        break;
      }

      case "FormHistory:RemoveEntry": {
        let { inputName, value } = message.data;
        FormHistory.update({
          op: "remove",
          fieldname: inputName,
          value,
        });
        break;
      }
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormHistoryStartup]);
PK
!<BtJ%components/nsInputListAutoComplete.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm");

function InputListAutoComplete() {}

InputListAutoComplete.prototype = {
  classID: Components.ID("{bf1e01d0-953e-11df-981c-0800200c9a66}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInputListAutoComplete]),

  autoCompleteSearch(aUntrimmedSearchString, aField) {
    let [values, labels] = this.getListSuggestions(aField);
    let searchResult = values.length > 0 ?
      Ci.nsIAutoCompleteResult.RESULT_SUCCESS :
      Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
    let defaultIndex = values.length > 0 ? 0 : -1;

    return new FormAutoCompleteResult(aUntrimmedSearchString,
                                      searchResult,
                                      defaultIndex,
                                      "",
                                      values,
                                      labels,
                                      [],
                                      null);
  },

  getListSuggestions(aField) {
    let values = [];
    let labels = [];
    if (!aField || !aField.list) {
      return [values, labels];
    }

    let filter = !aField.hasAttribute("mozNoFilter");
    let lowerFieldValue = aField.value.toLowerCase();
    let options = aField.list.options;

    for (let item of options) {
      let label = "";
      if (item.label) {
        label = item.label;
      } else if (item.text) {
        label = item.text;
      } else {
        label = item.value;
      }

      if (filter && !label.toLowerCase().includes(lowerFieldValue)) {
        continue;
      }

      labels.push(label);
      values.push(item.value);
    }

    return [values, labels];
  },
};

var component = [InputListAutoComplete];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<{{%components/contentAreaDropListener.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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/osfile.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

// This component is used for handling dragover and drop of urls.
//
// It checks to see whether a drop of a url is allowed. For instance, a url
// cannot be dropped if it is not a valid uri or the source of the drag cannot
// access the uri. This prevents, for example, a source document from tricking
// the user into dragging a chrome url.

function ContentAreaDropListener() { };

ContentAreaDropListener.prototype =
{
  classID:          Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIDroppedLinkHandler, Ci.nsISupports]),

  _addLink : function(links, url, name, type)
   {
    links.push({ url, name, type });
  },

  _addLinksFromItem: function(links, dt, i)
  {
    let types = dt.mozTypesAt(i);
    let type, data;

    type = "text/uri-list";
    if (types.contains(type)) {
      data = dt.mozGetDataAt(type, i);
      if (data) {
        let urls = data.split("\n");
        for (let url of urls) {
          // lines beginning with # are comments
          if (url.startsWith("#"))
            continue;
          url = url.replace(/^\s+|\s+$/g, "");
          this._addLink(links, url, url, type);
        }
        return;
      }
    }

    type = "text/x-moz-url";
    if (types.contains(type)) {
      data = dt.mozGetDataAt(type, i);
      if (data) {
        let lines = data.split("\n");
        for (let i = 0, length = lines.length; i < length; i += 2) {
          this._addLink(links, lines[i], lines[i + 1], type);
        }
        return;
      }
    }

    for (let type of ["text/plain", "text/x-moz-text-internal"]) {
      if (types.contains(type)) {
        data = dt.mozGetDataAt(type, i);
        if (data) {
          let lines = data.replace(/^\s+|\s+$/mg, "").split("\n");
          for (let line of lines) {
            this._addLink(links, line, line, type);
          }
          return;
        }
      }
    }

    // For shortcuts, we want to check for the file type last, so that the
    // url pointed to in one of the url types is found first before the file
    // type, which points to the actual file.
    let files = dt.files;
    if (files && i < files.length) {
      this._addLink(links, OS.Path.toFileURI(files[i].mozFullPath),
                    files[i].name, "application/x-moz-file");
    }
  },

  _getDropLinks : function (dt)
  {
    let links = [];
    for (let i = 0; i < dt.mozItemCount; i++) {
      this._addLinksFromItem(links, dt, i);
    }
    return links;
  },

  _validateURI: function(dataTransfer, uriString, disallowInherit)
  {
    if (!uriString)
      return "";

    // Strip leading and trailing whitespace, then try to create a
    // URI from the dropped string. If that succeeds, we're
    // dropping a URI and we need to do a security check to make
    // sure the source document can load the dropped URI.
    uriString = uriString.replace(/^\s*|\s*$/g, '');

    let uri;
    let ioService = Cc["@mozilla.org/network/io-service;1"]
                      .getService(Components.interfaces.nsIIOService);
    try {
      // Check that the uri is valid first and return an empty string if not.
      // It may just be plain text and should be ignored here
      uri = ioService.newURI(uriString);
    } catch (ex) { }
    if (!uri)
      return uriString;

    // uriString is a valid URI, so do the security check.
    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
                   getService(Ci.nsIScriptSecurityManager);
    let sourceNode = dataTransfer.mozSourceNode;
    let flags = secMan.STANDARD;
    if (disallowInherit)
      flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;

    let principal;
    if (sourceNode) {
      principal = this._getTriggeringPrincipalFromSourceNode(sourceNode);
    } else {
      // Use file:/// as the default uri so that drops of file URIs are always
      // allowed.
      principal = secMan.createCodebasePrincipal(ioService.newURI("file:///"), {});
    }
    secMan.checkLoadURIStrWithPrincipal(principal, uriString, flags);

    return uriString;
  },

  _getTriggeringPrincipalFromSourceNode: function(aSourceNode)
  {
    if (aSourceNode.localName == "browser" &&
        aSourceNode.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
      return aSourceNode.contentPrincipal;
    }
    return aSourceNode.nodePrincipal;
  },

  getTriggeringPrincipal: function(aEvent)
  {
    let dataTransfer = aEvent.dataTransfer;
    let sourceNode = dataTransfer.mozSourceNode;
    if (sourceNode) {
      return this._getTriggeringPrincipalFromSourceNode(sourceNode, false);
    }
    // Bug 1367038: mozSourceNode is null if the drag event originated
    // in an external application - needs better fallback!
    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
                   getService(Ci.nsIScriptSecurityManager);
    return secMan.getSystemPrincipal();
  },

  canDropLink: function(aEvent, aAllowSameDocument)
  {
    if (this._eventTargetIsDisabled(aEvent))
      return false;

    let dataTransfer = aEvent.dataTransfer;
    let types = dataTransfer.types;
    if (!types.includes("application/x-moz-file") &&
        !types.includes("text/x-moz-url") &&
        !types.includes("text/uri-list") &&
        !types.includes("text/x-moz-text-internal") &&
        !types.includes("text/plain"))
      return false;

    if (aAllowSameDocument)
      return true;

    let sourceNode = dataTransfer.mozSourceNode;
    if (!sourceNode)
      return true;

    // don't allow a drop of a node from the same document onto this one
    let sourceDocument = sourceNode.ownerDocument;
    let eventDocument = aEvent.originalTarget.ownerDocument;
    if (sourceDocument == eventDocument)
      return false;

    // also check for nodes in other child or sibling frames by checking
    // if both have the same top window.
    if (sourceDocument && eventDocument) {
      if (sourceDocument.defaultView == null)
        return true;
      let sourceRoot = sourceDocument.defaultView.top;
      if (sourceRoot && sourceRoot == eventDocument.defaultView.top)
        return false;
    }

    return true;
  },

  dropLink: function(aEvent, aName, aDisallowInherit)
  {
    aName.value = "";
    let links = this.dropLinks(aEvent, aDisallowInherit);
    let url = "";
    if (links.length > 0) {
      url = links[0].url;
      let name = links[0].name;
      if (name)
        aName.value = name;
    }

    return url;
  },

  dropLinks: function(aEvent, aDisallowInherit, aCount)
  {
    if (aEvent && this._eventTargetIsDisabled(aEvent))
      return [];

    let dataTransfer = aEvent.dataTransfer;
    let links = this._getDropLinks(dataTransfer);

    for (let link of links) {
      try {
        link.url = this._validateURI(dataTransfer, link.url, aDisallowInherit);
      } catch (ex) {
        // Prevent the drop entirely if any of the links are invalid even if
        // one of them is valid.
        aEvent.stopPropagation();
        aEvent.preventDefault();
        throw ex;
      }
    }
    if (aCount)
      aCount.value = links.length;

    return links;
  },

  _eventTargetIsDisabled: function(aEvent)
  {
    let ownerDoc = aEvent.originalTarget.ownerDocument;
    if (!ownerDoc || !ownerDoc.defaultView)
      return false;

    return ownerDoc.defaultView
                   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIDOMWindowUtils)
                   .isNodeDisabledForEvents(aEvent.originalTarget);
  }
};

var components = [ContentAreaDropListener];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<_components/nsINIProcessor.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

function INIProcessorFactory() {
}

INIProcessorFactory.prototype = {
    classID: Components.ID("{6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}"),
    QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParserFactory]),

    createINIParser : function (aINIFile) {
        return new INIProcessor(aINIFile);
    }

}; // end of INIProcessorFactory implementation

const MODE_WRONLY = 0x02;
const MODE_CREATE = 0x08;
const MODE_TRUNCATE = 0x20;

// nsIINIParser implementation
function INIProcessor(aFile) {
    this._iniFile = aFile;
    this._iniData = {};
    this._readFile();
}

INIProcessor.prototype = {
    QueryInterface : XPCOMUtils.generateQI([Ci.nsIINIParser, Ci.nsIINIParserWriter]),

    __utf8Converter : null, // UCS2 <--> UTF8 string conversion
    get _utf8Converter() {
        if (!this.__utf8Converter) {
            this.__utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                                  createInstance(Ci.nsIScriptableUnicodeConverter);
            this.__utf8Converter.charset = "UTF-8";
        }
        return this.__utf8Converter;
    },

    __utf16leConverter : null, // UCS2 <--> UTF16LE string conversion
    get _utf16leConverter() {
        if (!this.__utf16leConverter) {
            this.__utf16leConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                                  createInstance(Ci.nsIScriptableUnicodeConverter);
            this.__utf16leConverter.charset = "UTF-16LE";
        }
        return this.__utf16leConverter;
    },

    _utfConverterReset : function() {
        this.__utf8Converter = null;
        this.__utf16leConverter = null;
    },

    _iniFile : null,
    _iniData : null,

    /*
     * Reads the INI file and stores the data internally.
     */
    _readFile : function() {
        // If file doesn't exist, there's nothing to do.
        if (!this._iniFile.exists() || 0 == this._iniFile.fileSize)
            return;

        let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
            .getService(Ci.nsIINIParserFactory).createINIParser(this._iniFile);
        for (let section of XPCOMUtils.IterStringEnumerator(iniParser.getSections())) {
            this._iniData[section] = {};
            for (let key of XPCOMUtils.IterStringEnumerator(iniParser.getKeys(section))) {
                this._iniData[section][key] = iniParser.getString(section, key);
            }
        }
    },

    // nsIINIParser

    getSections : function() {
        let sections = [];
        for (let section in this._iniData)
            sections.push(section);
        return new stringEnumerator(sections);
    },

    getKeys : function(aSection) {
        let keys = [];
        if (aSection in this._iniData)
            for (let key in this._iniData[aSection])
                keys.push(key);
        return new stringEnumerator(keys);
    },

    getString : function(aSection, aKey) {
        if (!(aSection in this._iniData))
            throw Cr.NS_ERROR_FAILURE;
        if (!(aKey in this._iniData[aSection]))
            throw Cr.NS_ERROR_FAILURE;
        return this._iniData[aSection][aKey];
    },


    // nsIINIParserWriter

    setString : function(aSection, aKey, aValue) {
        const isSectionIllegal = /[\0\r\n\[\]]/;
        const isKeyValIllegal  = /[\0\r\n=]/;

        if (isSectionIllegal.test(aSection))
            throw Components.Exception("bad character in section name",
                                       Cr.ERROR_ILLEGAL_VALUE);
        if (isKeyValIllegal.test(aKey) || isKeyValIllegal.test(aValue))
            throw Components.Exception("bad character in key/value",
                                       Cr.ERROR_ILLEGAL_VALUE);

        if (!(aSection in this._iniData))
            this._iniData[aSection] = {};

        this._iniData[aSection][aKey] = aValue;
    },

    writeFile : function(aFile, aFlags) {

        let converter;
        function writeLine(data) {
            data += "\n";
            data = converter.ConvertFromUnicode(data);
            data += converter.Finish();
            outputStream.write(data, data.length);
        }

        if (!aFile)
            aFile = this._iniFile;

        let safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
                         createInstance(Ci.nsIFileOutputStream);
        safeStream.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE,
                        0o600, null);

        var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"].
                           createInstance(Ci.nsIBufferedOutputStream);
        outputStream.init(safeStream, 8192);
        outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish()

        if (Ci.nsIINIParserWriter.WRITE_UTF16 == aFlags
         && 'nsIWindowsRegKey' in Ci) {
            outputStream.write("\xFF\xFE", 2);
            converter = this._utf16leConverter;
        } else {
            converter = this._utf8Converter;
        }

        for (let section in this._iniData) {
            writeLine("[" + section + "]");
            for (let key in this._iniData[section]) {
                writeLine(key + "=" + this._iniData[section][key]);
            }
        }

        outputStream.finish();
    }
};

function stringEnumerator(stringArray) {
    this._strings = stringArray;
}
stringEnumerator.prototype = {
    QueryInterface : XPCOMUtils.generateQI([Ci.nsIUTF8StringEnumerator]),

    _strings : null,
    _enumIndex: 0,

    hasMore : function() {
        return (this._enumIndex < this._strings.length);
    },

    getNext : function() {
        return this._strings[this._enumIndex++];
    }
};

var component = [INIProcessorFactory];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<6components/nsPrompter.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPromptUtils.jsm");

function Prompter() {
    // Note that EmbedPrompter clones this implementation.
}

Prompter.prototype = {
    classID: Components.ID("{1c978d25-b37f-43a8-a2d6-0c7a239ead87}"),
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),


    /* ----------  private members  ---------- */

    pickPrompter(domWin) {
        return new ModalPrompter(domWin);
    },


    /* ----------  nsIPromptFactory  ---------- */


    getPrompt(domWin, iid) {
        // This is still kind of dumb; the C++ code delegated to login manager
        // here, which in turn calls back into us via nsIPromptService2.
        if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
            try {
                let pwmgr = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].
                            getService(Ci.nsIPromptFactory);
                return pwmgr.getPrompt(domWin, iid);
            } catch (e) {
                Cu.reportError("nsPrompter: Delegation to password manager failed: " + e);
            }
        }

        let p = new ModalPrompter(domWin);
        p.QueryInterface(iid);
        return p;
    },


    /* ----------  nsIPromptService  ---------- */


    alert(domWin, title, text) {
        let p = this.pickPrompter(domWin);
        p.alert(title, text);
    },

    alertCheck(domWin, title, text, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        p.alertCheck(title, text, checkLabel, checkValue);
    },

    confirm(domWin, title, text) {
        let p = this.pickPrompter(domWin);
        return p.confirm(title, text);
    },

    confirmCheck(domWin, title, text, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.confirmCheck(title, text, checkLabel, checkValue);
    },

    confirmEx(domWin, title, text, flags, button0, button1, button2, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.confirmEx(title, text, flags, button0, button1, button2, checkLabel, checkValue);
    },

    prompt(domWin, title, text, value, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.nsIPrompt_prompt(title, text, value, checkLabel, checkValue);
    },

    promptUsernameAndPassword(domWin, title, text, user, pass, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, checkLabel, checkValue);
    },

    promptPassword(domWin, title, text, pass, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.nsIPrompt_promptPassword(title, text, pass, checkLabel, checkValue);
    },

    select(domWin, title, text, count, list, selected) {
        let p = this.pickPrompter(domWin);
        return p.select(title, text, count, list, selected);
    },


    /* ----------  nsIPromptService2  ---------- */


    promptAuth(domWin, channel, level, authInfo, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.promptAuth(channel, level, authInfo, checkLabel, checkValue);
    },

    asyncPromptAuth(domWin, channel, callback, context, level, authInfo, checkLabel, checkValue) {
        let p = this.pickPrompter(domWin);
        return p.asyncPromptAuth(channel, callback, context, level, authInfo, checkLabel, checkValue);
    },

};


// Common utils not specific to a particular prompter style.
var PromptUtilsTemp = {
    __proto__: PromptUtils,

    getLocalizedString(key, formatArgs) {
        if (formatArgs)
            return this.strBundle.formatStringFromName(key, formatArgs, formatArgs.length);
        return this.strBundle.GetStringFromName(key);
    },

    confirmExHelper(flags, button0, button1, button2) {
        const BUTTON_DEFAULT_MASK = 0x03000000;
        let defaultButtonNum = (flags & BUTTON_DEFAULT_MASK) >> 24;
        let isDelayEnabled = (flags & Ci.nsIPrompt.BUTTON_DELAY_ENABLE);

        // Flags can be used to select a specific pre-defined button label or
        // a caller-supplied string (button0/button1/button2). If no flags are
        // set for a button, then the button won't be shown.
        let argText = [button0, button1, button2];
        let buttonLabels = [null, null, null];
        for (let i = 0; i < 3; i++) {
            let buttonLabel;
            switch (flags & 0xff) {
              case Ci.nsIPrompt.BUTTON_TITLE_OK:
                buttonLabel = PromptUtils.getLocalizedString("OK");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
                buttonLabel = PromptUtils.getLocalizedString("Cancel");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_YES:
                buttonLabel = PromptUtils.getLocalizedString("Yes");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_NO:
                buttonLabel = PromptUtils.getLocalizedString("No");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
                buttonLabel = PromptUtils.getLocalizedString("Save");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
                buttonLabel = PromptUtils.getLocalizedString("DontSave");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
                buttonLabel = PromptUtils.getLocalizedString("Revert");
                break;
              case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
                buttonLabel = argText[i];
                break;
            }
            if (buttonLabel)
                buttonLabels[i] = buttonLabel;
            flags >>= 8;
        }

        return [buttonLabels[0], buttonLabels[1], buttonLabels[2], defaultButtonNum, isDelayEnabled];
    },

    getAuthInfo(authInfo) {
        let username, password;

        let flags = authInfo.flags;
        if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && authInfo.domain)
            username = authInfo.domain + "\\" + authInfo.username;
        else
            username = authInfo.username;

        password = authInfo.password;

        return [username, password];
    },

    setAuthInfo(authInfo, username, password) {
        let flags = authInfo.flags;
        if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
            // Domain is separated from username by a backslash
            let idx = username.indexOf("\\");
            if (idx == -1) {
                authInfo.username = username;
            } else {
                authInfo.domain   =  username.substring(0, idx);
                authInfo.username =  username.substring(idx + 1);
            }
        } else {
            authInfo.username = username;
        }
        authInfo.password = password;
    },

    /**
     * Strip out things like userPass and path for display.
     */
    getFormattedHostname(uri) {
        return uri.scheme + "://" + uri.hostPort;
    },

    // Copied from login manager
    getAuthTarget(aChannel, aAuthInfo) {
        let hostname, realm;

        // If our proxy is demanding authentication, don't use the
        // channel's actual destination.
        if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
            if (!(aChannel instanceof Ci.nsIProxiedChannel))
                throw "proxy auth needs nsIProxiedChannel";

            let info = aChannel.proxyInfo;
            if (!info)
                throw "proxy auth needs nsIProxyInfo";

            // Proxies don't have a scheme, but we'll use "moz-proxy://"
            // so that it's more obvious what the login is for.
            let idnService = Cc["@mozilla.org/network/idn-service;1"].
                             getService(Ci.nsIIDNService);
            hostname = "moz-proxy://" +
                        idnService.convertUTF8toACE(info.host) +
                        ":" + info.port;
            realm = aAuthInfo.realm;
            if (!realm)
                realm = hostname;

            return [hostname, realm];
        }

        hostname = this.getFormattedHostname(aChannel.URI);

        // If a HTTP WWW-Authenticate header specified a realm, that value
        // will be available here. If it wasn't set or wasn't HTTP, we'll use
        // the formatted hostname instead.
        realm = aAuthInfo.realm;
        if (!realm)
            realm = hostname;

        return [hostname, realm];
    },


    makeAuthMessage(channel, authInfo) {
        let isProxy    = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
        let isPassOnly = (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
        let isCrossOrig = (authInfo.flags &
                           Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE);

        let username = authInfo.username;
        let [displayHost, realm] = this.getAuthTarget(channel, authInfo);

        // Suppress "the site says: $realm" when we synthesized a missing realm.
        if (!authInfo.realm && !isProxy)
            realm = "";

        // Trim obnoxiously long realms.
        if (realm.length > 150) {
            realm = realm.substring(0, 150);
            // Append "..." (or localized equivalent).
            realm += this.ellipsis;
        }

        let text;
        if (isProxy) {
            text = PromptUtils.getLocalizedString("EnterLoginForProxy3", [realm, displayHost]);
        } else if (isPassOnly) {
            text = PromptUtils.getLocalizedString("EnterPasswordFor", [username, displayHost]);
        } else if (isCrossOrig) {
            text = PromptUtils.getLocalizedString("EnterUserPasswordForCrossOrigin2", [displayHost]);
        } else if (!realm) {
            text = PromptUtils.getLocalizedString("EnterUserPasswordFor2", [displayHost]);
        } else {
            text = PromptUtils.getLocalizedString("EnterLoginForRealm3", [realm, displayHost]);
        }

        return text;
    },

    getTabModalPrompt(domWin) {
        var promptBox = null;

        try {
            // Get the topmost window, in case we're in a frame.
            var promptWin = domWin.top;

            // Get the chrome window for the content window we're using.
            // (Unwrap because we need a non-IDL property below.)
            var chromeWin = promptWin.QueryInterface(Ci.nsIInterfaceRequestor)
                                     .getInterface(Ci.nsIWebNavigation)
                                     .QueryInterface(Ci.nsIDocShell)
                                     .chromeEventHandler.ownerGlobal.wrappedJSObject;

            if (chromeWin.getTabModalPromptBox)
                promptBox = chromeWin.getTabModalPromptBox(promptWin);
        } catch (e) {
            // If any errors happen, just assume no tabmodal prompter.
        }

        return promptBox;
    },
};

PromptUtils = PromptUtilsTemp;

XPCOMUtils.defineLazyGetter(PromptUtils, "strBundle", function() {
    let bunService = Cc["@mozilla.org/intl/stringbundle;1"].
                     getService(Ci.nsIStringBundleService);
    let bundle = bunService.createBundle("chrome://global/locale/commonDialogs.properties");
    if (!bundle)
        throw "String bundle for Prompter not present!";
    return bundle;
});

XPCOMUtils.defineLazyGetter(PromptUtils, "ellipsis", function() {
    let ellipsis = "\u2026";
    try {
        ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
    } catch (e) { }
    return ellipsis;
});



function openModalWindow(domWin, uri, args) {
    // There's an implied contract that says modal prompts should still work
    // when no "parent" window is passed for the dialog (eg, the "Master
    // Password" dialog does this).  These prompts must be shown even if there
    // are *no* visible windows at all.
    // There's also a requirement for prompts to be blocked if a window is
    // passed and that window is hidden (eg, auth prompts are supressed if the
    // passed window is the hidden window).
    // See bug 875157 comment 30 for more...
    if (domWin) {
        // a domWin was passed, so we can apply the check for it being hidden.
        let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);

        if (winUtils && !winUtils.isParentWindowMainWidgetVisible) {
            throw Components.Exception("Cannot call openModalWindow on a hidden window",
                                       Cr.NS_ERROR_NOT_AVAILABLE);
        }
    } else {
        // We try and find a window to use as the parent, but don't consider
        // if that is visible before showing the prompt.
        domWin = Services.ww.activeWindow;
        // domWin may still be null here if there are _no_ windows open.
    }
    // Note that we don't need to fire DOMWillOpenModalDialog and
    // DOMModalDialogClosed events here, wwatcher's OpenWindowInternal
    // will do that. Similarly for enterModalState / leaveModalState.

    Services.ww.openWindow(domWin, uri, "_blank", "centerscreen,chrome,modal,titlebar", args);
}

function openTabPrompt(domWin, tabPrompt, args) {
    let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDocShell);
    let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
    let eventDetail = Cu.cloneInto({tabPrompt: true, inPermitUnload}, domWin);
    PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);

    let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindowUtils);
    winUtils.enterModalState();

    let frameMM = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIContentFrameMessageManager);
    frameMM.QueryInterface(Ci.nsIDOMEventTarget);

    // We provide a callback so the prompt can close itself. We don't want to
    // wait for this event loop to return... Otherwise the presence of other
    // prompts on the call stack would in this dialog appearing unresponsive
    // until the other prompts had been closed.
    let callbackInvoked = false;
    let newPrompt;
    function onPromptClose(forceCleanup) {
        if (!newPrompt && !forceCleanup)
            return;
        callbackInvoked = true;
        if (newPrompt)
            tabPrompt.removePrompt(newPrompt);

        frameMM.removeEventListener("pagehide", pagehide, true);

        winUtils.leaveModalState();

        PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
    }

    frameMM.addEventListener("pagehide", pagehide, true);
    function pagehide(e) {
        // Check whether the event relates to our window or its ancestors
        let window = domWin;
        let eventWindow = e.target.defaultView;
        while (window != eventWindow && window.parent != window) {
          window = window.parent;
        }
        if (window != eventWindow) {
          return;
        }
        frameMM.removeEventListener("pagehide", pagehide, true);

        if (newPrompt) {
            newPrompt.abortPrompt();
        }
    }

    try {
        let topPrincipal = domWin.top.document.nodePrincipal;
        let promptPrincipal = domWin.document.nodePrincipal;
        args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
        args.promptActive = true;

        newPrompt = tabPrompt.appendPrompt(args, onPromptClose);

        // TODO since we don't actually open a window, need to check if
        // there's other stuff in nsWindowWatcher::OpenWindowInternal
        // that we might need to do here as well.

        Services.tm.spinEventLoopUntil(() => !args.promptActive);
        delete args.promptActive;

        if (args.promptAborted)
            throw Components.Exception("prompt aborted by user", Cr.NS_ERROR_NOT_AVAILABLE);
    } finally {
        // If the prompt unexpectedly failed to invoke the callback, do so here.
        if (!callbackInvoked)
            onPromptClose(true);
    }
}

function openRemotePrompt(domWin, args, tabPrompt) {
    let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDocShell);
    let messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsITabChild)
                                 .messageManager;

    let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
    let eventDetail = Cu.cloneInto({tabPrompt, inPermitUnload}, domWin);
    PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);

    let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindowUtils);
    winUtils.enterModalState();
    let closed = false;

    let frameMM = docShell.getInterface(Ci.nsIContentFrameMessageManager);
    frameMM.QueryInterface(Ci.nsIDOMEventTarget);

    // It should be hard or impossible to cause a window to create multiple
    // prompts, but just in case, give our prompt an ID.
    let id = "id" + Cc["@mozilla.org/uuid-generator;1"]
                      .getService(Ci.nsIUUIDGenerator).generateUUID().toString();

    messageManager.addMessageListener("Prompt:Close", function listener(message) {
        if (message.data._remoteId !== id) {
            return;
        }

        messageManager.removeMessageListener("Prompt:Close", listener);
        frameMM.removeEventListener("pagehide", pagehide, true);

        winUtils.leaveModalState();
        PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");

        // Copy the response from the closed prompt into our args, it will be
        // read by our caller.
        if (message.data) {
            for (let key in message.data) {
                args[key] = message.data[key];
            }
        }

        // Exit our nested event loop when we unwind.
        closed = true;
    });

    frameMM.addEventListener("pagehide", pagehide, true);
    function pagehide(e) {
        // Check whether the event relates to our window or its ancestors
        let window = domWin;
        let eventWindow = e.target.defaultView;
        while (window != eventWindow && window.parent != window) {
          window = window.parent;
        }
        if (window != eventWindow) {
          return;
        }
        frameMM.removeEventListener("pagehide", pagehide, true);
        messageManager.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
    }

    let topPrincipal = domWin.top.document.nodePrincipal;
    let promptPrincipal = domWin.document.nodePrincipal;
    args.promptPrincipal = promptPrincipal;
    args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
    args.inPermitUnload = inPermitUnload;

    args._remoteId = id;

    messageManager.sendAsyncMessage("Prompt:Open", args, {});

    Services.tm.spinEventLoopUntil(() => closed);
}

function ModalPrompter(domWin) {
    this.domWin = domWin;
}
ModalPrompter.prototype = {
    domWin: null,
    /*
     * Default to not using a tab-modal prompt, unless the caller opts in by
     * QIing to nsIWritablePropertyBag and setting the value of this property
     * to true.
     */
    allowTabModal: false,

    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt,
                                            Ci.nsIAuthPrompt2,
                                            Ci.nsIWritablePropertyBag2]),


    /* ---------- internal methods ---------- */


    openPrompt(args) {
        // Check pref, if false/missing do not ever allow tab-modal prompts.
        const prefName = "prompts.tab_modal.enabled";
        let prefValue = false;
        if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL)
            prefValue = Services.prefs.getBoolPref(prefName);

        let allowTabModal = this.allowTabModal && prefValue;

        if (allowTabModal && this.domWin) {
            if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
                openRemotePrompt(this.domWin, args, true);
                return;
            }

            let tabPrompt = PromptUtils.getTabModalPrompt(this.domWin);
            if (tabPrompt) {
                openTabPrompt(this.domWin, tabPrompt, args);
                return;
            }
        }

        // If we can't do a tab modal prompt, fallback to using a window-modal dialog.
        const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
        const SELECT_DIALOG = "chrome://global/content/selectDialog.xul";

        let uri = (args.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG;

        if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
            args.uri = uri;
            openRemotePrompt(this.domWin, args);
            return;
        }

        let propBag = PromptUtils.objectToPropBag(args);
        openModalWindow(this.domWin, uri, propBag);
        PromptUtils.propBagToObject(propBag, args);
    },



    /*
     * ---------- interface disambiguation ----------
     *
     * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
     * different arguments. All but prompt() have the same number of
     * arguments, so look at the arg types to figure out how we're being
     * called. :-(
     */
    prompt() {
        // also, the nsIPrompt flavor has 5 args instead of 6.
        if (typeof arguments[2] == "object")
            return this.nsIPrompt_prompt.apply(this, arguments);
        return this.nsIAuthPrompt_prompt.apply(this, arguments);
    },

    promptUsernameAndPassword() {
        // Both have 6 args, so use types.
        if (typeof arguments[2] == "object")
            return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
        return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
    },

    promptPassword() {
        // Both have 5 args, so use types.
        if (typeof arguments[2] == "object")
            return this.nsIPrompt_promptPassword.apply(this, arguments);
        return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
    },


    /* ----------  nsIPrompt  ---------- */


    alert(title, text) {
        if (!title)
            title = PromptUtils.getLocalizedString("Alert");

        let args = {
            promptType: "alert",
            title,
            text,
        };

        this.openPrompt(args);
    },

    alertCheck(title, text, checkLabel, checkValue) {
        if (!title)
            title = PromptUtils.getLocalizedString("Alert");

        let args = {
            promptType: "alertCheck",
            title,
            text,
            checkLabel,
            checked:    checkValue.value,
        };

        this.openPrompt(args);

        // Checkbox state always returned, even if cancel clicked.
        checkValue.value = args.checked;
    },

    confirm(title, text) {
        if (!title)
            title = PromptUtils.getLocalizedString("Confirm");

        let args = {
            promptType: "confirm",
            title,
            text,
            ok:         false,
        };

        this.openPrompt(args);

        // Did user click Ok or Cancel?
        return args.ok;
    },

    confirmCheck(title, text, checkLabel, checkValue) {
        if (!title)
            title = PromptUtils.getLocalizedString("ConfirmCheck");

        let args = {
            promptType: "confirmCheck",
            title,
            text,
            checkLabel,
            checked:    checkValue.value,
            ok:         false,
        };

        this.openPrompt(args);

        // Checkbox state always returned, even if cancel clicked.
        checkValue.value = args.checked;

        // Did user click Ok or Cancel?
        return args.ok;
    },

    confirmEx(title, text, flags, button0, button1, button2,
                         checkLabel, checkValue) {

        if (!title)
            title = PromptUtils.getLocalizedString("Confirm");

        let args = {
            promptType:  "confirmEx",
            title,
            text,
            checkLabel,
            checked:     checkValue.value,
            ok:          false,
            buttonNumClicked: 1,
        };

        let [label0, label1, label2, defaultButtonNum, isDelayEnabled] =
            PromptUtils.confirmExHelper(flags, button0, button1, button2);

        args.defaultButtonNum = defaultButtonNum;
        args.enableDelay = isDelayEnabled;

        if (label0) {
            args.button0Label = label0;
            if (label1) {
                args.button1Label = label1;
                if (label2) {
                    args.button2Label = label2;
                }
            }
        }

        this.openPrompt(args);

        // Checkbox state always returned, even if cancel clicked.
        checkValue.value = args.checked;

        // Get the number of the button the user clicked.
        return args.buttonNumClicked;
    },

    nsIPrompt_prompt(title, text, value, checkLabel, checkValue) {
        if (!title)
            title = PromptUtils.getLocalizedString("Prompt");

        let args = {
            promptType: "prompt",
            title,
            text,
            value:      value.value,
            checkLabel,
            checked:    checkValue.value,
            ok:         false,
        };

        this.openPrompt(args);

        // Did user click Ok or Cancel?
        let ok  = args.ok;
        if (ok) {
            checkValue.value = args.checked;
            value.value      = args.value;
        }

        return ok;
    },

    nsIPrompt_promptUsernameAndPassword(title, text, user, pass, checkLabel, checkValue) {
        if (!title)
            title = PromptUtils.getLocalizedString("PromptUsernameAndPassword2");

        let args = {
            promptType: "promptUserAndPass",
            title,
            text,
            user:       user.value,
            pass:       pass.value,
            checkLabel,
            checked:    checkValue.value,
            ok:         false,
        };

        this.openPrompt(args);

        // Did user click Ok or Cancel?
        let ok  = args.ok;
        if (ok) {
            checkValue.value = args.checked;
            user.value       = args.user;
            pass.value       = args.pass;
        }

        return ok;
    },

    nsIPrompt_promptPassword(title, text, pass, checkLabel, checkValue) {
        if (!title)
            title = PromptUtils.getLocalizedString("PromptPassword2");

        let args = {
            promptType: "promptPassword",
            title,
            text,
            pass:       pass.value,
            checkLabel,
            checked:    checkValue.value,
            ok:         false,
        }

        this.openPrompt(args);

        // Did user click Ok or Cancel?
        let ok  = args.ok;
        if (ok) {
            checkValue.value = args.checked;
            pass.value       = args.pass;
        }

        return ok;
    },

    select(title, text, count, list, selected) {
        if (!title)
            title = PromptUtils.getLocalizedString("Select");

        let args = {
            promptType: "select",
            title,
            text,
            list,
            selected:   -1,
            ok:         false,
        };

        this.openPrompt(args);

        // Did user click Ok or Cancel?
        let ok  = args.ok;
        if (ok)
            selected.value = args.selected;

        return ok;
    },


    /* ----------  nsIAuthPrompt  ---------- */


    nsIAuthPrompt_prompt(title, text, passwordRealm, savePassword, defaultText, result) {
        // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
        if (defaultText)
            result.value = defaultText;
        return this.nsIPrompt_prompt(title, text, result, null, {});
    },

    nsIAuthPrompt_promptUsernameAndPassword(title, text, passwordRealm, savePassword, user, pass) {
        // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
        return this.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, null, {});
    },

    nsIAuthPrompt_promptPassword(title, text, passwordRealm, savePassword, pass) {
        // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
        return this.nsIPrompt_promptPassword(title, text, pass, null, {});
    },


    /* ----------  nsIAuthPrompt2  ---------- */


    promptAuth(channel, level, authInfo, checkLabel, checkValue) {
        let message = PromptUtils.makeAuthMessage(channel, authInfo);

        let [username, password] = PromptUtils.getAuthInfo(authInfo);

        let userParam = { value: username };
        let passParam = { value: password };

        let ok;
        if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
            ok = this.nsIPrompt_promptPassword(null, message, passParam, checkLabel, checkValue);
        else
            ok = this.nsIPrompt_promptUsernameAndPassword(null, message, userParam, passParam, checkLabel, checkValue);

        if (ok)
            PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
        return ok;
    },

    asyncPromptAuth(channel, callback, context, level, authInfo, checkLabel, checkValue) {
        // Nothing calls this directly; netwerk ends up going through
        // nsIPromptService::GetPrompt, which delegates to login manager.
        // Login manger handles the async bits itself, and only calls out
        // promptAuth, never asyncPromptAuth.
        //
        // Bug 565582 will change this.
        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    },

    /* ----------  nsIWritablePropertyBag2 ---------- */

    // Only a partial implementation, for one specific use case...

    setPropertyAsBool(name, value) {
        if (name == "allowTabModal")
            this.allowTabModal = value;
        else
            throw Cr.NS_ERROR_ILLEGAL_VALUE;
    },
};


function AuthPromptAdapterFactory() {
}
AuthPromptAdapterFactory.prototype = {
    classID: Components.ID("{6e134924-6c3a-4d86-81ac-69432dd971dc}"),
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPromptAdapterFactory]),

    /* ----------  nsIAuthPromptAdapterFactory ---------- */

    createAdapter(oldPrompter) {
        return new AuthPromptAdapter(oldPrompter);
    }
};


// Takes an nsIAuthPrompt implementation, wraps it with a nsIAuthPrompt2 shell.
function AuthPromptAdapter(oldPrompter) {
    this.oldPrompter = oldPrompter;
}
AuthPromptAdapter.prototype = {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
    oldPrompter: null,

    /* ----------  nsIAuthPrompt2 ---------- */

    promptAuth(channel, level, authInfo, checkLabel, checkValue) {
        let message = PromptUtils.makeAuthMessage(channel, authInfo);

        let [username, password] = PromptUtils.getAuthInfo(authInfo);
        let userParam = { value: username };
        let passParam = { value: password };

        let [host, realm]  = PromptUtils.getAuthTarget(channel, authInfo);
        let authTarget = host + " (" + realm + ")";

        let ok;
        if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
            ok = this.oldPrompter.promptPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, passParam);
        else
            ok = this.oldPrompter.promptUsernameAndPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, userParam, passParam);

        if (ok)
            PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
        return ok;
    },

    asyncPromptAuth(channel, callback, context, level, authInfo, checkLabel, checkValue) {
        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    }
};


// Wrapper using the old embedding contractID, since it's already common in
// the addon ecosystem.
function EmbedPrompter() {
}
EmbedPrompter.prototype = new Prompter();
EmbedPrompter.prototype.classID          = Components.ID("{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}");

var component = [Prompter, EmbedPrompter, AuthPromptAdapterFactory];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
PK
!<`˱components/Weave.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "Utils", () => {
  return Cu.import("resource://services-sync/util.js", {}).Utils;
});

const SYNC_PREFS_BRANCH = "services.sync.";


/**
 * Sync's XPCOM service.
 *
 * It is named "Weave" for historical reasons.
 *
 * It's worth noting how Sync is lazily loaded. We register a timer that
 * loads Sync a few seconds after app startup. This is so Sync does not
 * adversely affect application start time.
 *
 * If Sync is not configured, no extra Sync code is loaded. If an
 * external component (say the UI) needs to interact with Sync, it
 * should use the promise-base function whenLoaded() - something like the
 * following:
 *
 * // 1. Grab a handle to the Sync XPCOM service.
 * let service = Cc["@mozilla.org/weave/service;1"]
 *                 .getService(Components.interfaces.nsISupports)
 *                 .wrappedJSObject;
 *
 * // 2. Use the .then method of the promise.
 * service.whenLoaded().then(() => {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * });
 *
 * And that's it!  However, if you really want to avoid promises and do it
 * old-school, then
 *
 * // 1. Get a reference to the service as done in (1) above.
 *
 * // 2. Check if the service has been initialized.
 * if (service.ready) {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * }
 *
 * // 3. Install "ready" listener.
 * Services.obs.addObserver(function onReady() {
 *   Services.obs.removeObserver(onReady, "weave:service:ready");
 *
 *   // You are free to interact with "Weave." objects.
 * }, "weave:service:ready", false);
 *
 * // 4. Trigger loading of Sync.
 * service.ensureLoaded();
 */
function WeaveService() {
  this.wrappedJSObject = this;
  this.ready = false;
}
WeaveService.prototype = {
  classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  ensureLoaded() {
    Components.utils.import("resource://services-sync/main.js");

    // Side-effect of accessing the service is that it is instantiated.
    Weave.Service;
  },

  whenLoaded() {
    if (this.ready) {
      return Promise.resolve();
    }
    let onReadyPromise = new Promise(resolve => {
      Services.obs.addObserver(function onReady() {
        Services.obs.removeObserver(onReady, "weave:service:ready");
        resolve();
      }, "weave:service:ready");
    });
    this.ensureLoaded();
    return onReadyPromise;
  },

  init() {
    // Force Weave service to load if it hasn't triggered from overlays
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.initWithCallback({
      notify: () => {
        let isConfigured = false;
        // We only load more if it looks like Sync is configured.
        if (this.enabled) {
          // We have an associated FxAccount. So, do a more thorough check.
          // This will import a number of modules and thus increase memory
          // accordingly. We could potentially copy code performed by
          // this check into this file if our above code is yielding too
          // many false positives.
          Components.utils.import("resource://services-sync/main.js");
          isConfigured = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED;
        }
        let getHistogramById = Services.telemetry.getHistogramById;
        getHistogramById("WEAVE_CONFIGURED").add(isConfigured);
        if (isConfigured) {
          getHistogramById("WEAVE_CONFIGURED_MASTER_PASSWORD").add(Utils.mpEnabled());
          this.ensureLoaded();
        }
      }
    }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /**
   * Whether Sync appears to be enabled.
   *
   * This returns true if we have an associated FxA account
   *
   * It does *not* perform a robust check to see if the client is working.
   * For that, you'll want to check Weave.Status.checkSetup().
   */
  get enabled() {
    let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
    return prefs.prefHasUserValue("username");
  }
};

function AboutWeaveLog() {}
AboutWeaveLog.prototype = {
  classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule,
                                         Ci.nsISupportsWeakReference]),

  getURIFlags(aURI) {
    return 0;
  },

  newChannel(aURI, aLoadInfo) {
    let dir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
    let uri = Services.io.newFileURI(dir);
    let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);

    channel.originalURI = aURI;

    // Ensure that the about page has the same privileges as a regular directory
    // view. That way links to files can be opened. make sure we use the correct
    // origin attributes when creating the principal for accessing the
    // about:sync-log data.
    let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                .getService(Ci.nsIScriptSecurityManager);
    let principal = ssm.createCodebasePrincipal(uri, aLoadInfo.originAttributes);

    channel.owner = principal;
    return channel;
  }
};

const components = [WeaveService, AboutWeaveLog];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<ov v components/FxAccountsPush.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");

/**
 * FxAccountsPushService manages Push notifications for Firefox Accounts in the browser
 *
 * @param [options]
 *        Object, custom options that used for testing
 * @constructor
 */
function FxAccountsPushService(options = {}) {
  this.log = log;

  if (options.log) {
    // allow custom log for testing purposes
    this.log = options.log;
  }

  this.log.debug("FxAccountsPush loading service");
  this.wrappedJSObject = this;
  this.initialize(options);
}

FxAccountsPushService.prototype = {
  /**
   * Helps only initialize observers once.
   */
  _initialized: false,
  /**
   * Instance of the nsIPushService or a mocked object.
   */
  pushService: null,
  /**
   * Instance of FxAccounts or a mocked object.
   */
  fxAccounts: null,
  /**
   * Component ID of this service, helps register this component.
   */
  classID: Components.ID("{1b7db999-2ecd-4abf-bb95-a726896798ca}"),
  /**
   * Register used interfaces in this service
   */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  /**
   * Initialize the service and register all the required observers.
   *
   * @param [options]
   */
  initialize(options) {
    if (this._initialized) {
      return false;
    }

    this._initialized = true;

    if (options.pushService) {
      this.pushService = options.pushService;
    } else {
      this.pushService = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
    }

    if (options.fxAccounts) {
      this.fxAccounts = options.fxAccounts;
    } else {
      XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
        "resource://gre/modules/FxAccounts.jsm");
    }

    // listen to new push messages, push changes and logout events
    Services.obs.addObserver(this, this.pushService.pushTopic);
    Services.obs.addObserver(this, this.pushService.subscriptionChangeTopic);
    Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION);

    this.log.debug("FxAccountsPush initialized");
    return true;
  },
  /**
   * Registers a new endpoint with the Push Server
   *
   * @returns {Promise}
   *          Promise always resolves with a subscription or a null if failed to subscribe.
   */
  registerPushEndpoint() {
    this.log.trace("FxAccountsPush registerPushEndpoint");

    return new Promise((resolve) => {
      this.pushService.subscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
        Services.scriptSecurityManager.getSystemPrincipal(),
        (result, subscription) => {
          if (Components.isSuccessCode(result)) {
            this.log.debug("FxAccountsPush got subscription");
            resolve(subscription);
          } else {
            this.log.warn("FxAccountsPush failed to subscribe", result);
            resolve(null);
          }
        });
    });
  },
  /**
   * Standard observer interface to listen to push messages, changes and logout.
   *
   * @param subject
   * @param topic
   * @param data
   * @returns {Promise}
   */
  _observe(subject, topic, data) {
    this.log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
    switch (topic) {
      case this.pushService.pushTopic:
        if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
          let message = subject.QueryInterface(Ci.nsIPushMessage);
          this._onPushMessage(message);
        }
        break;
      case this.pushService.subscriptionChangeTopic:
        if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
          this._onPushSubscriptionChange();
        }
        break;
      case ONLOGOUT_NOTIFICATION:
        // user signed out, we need to stop polling the Push Server
        this.unsubscribe().catch(err => {
          this.log.error("Error during unsubscribe", err);
        });
      default:
        break;
    }
  },
  /**
   * Wrapper around _observe that catches errors
   */
  observe(subject, topic, data) {
    Promise.resolve()
      .then(() => this._observe(subject, topic, data))
      .catch(err => this.log.error(err));
  },
  /**
   * Fired when the Push server sends a notification.
   *
   * @private
   * @returns {Promise}
   */
  _onPushMessage(message) {
    this.log.trace("FxAccountsPushService _onPushMessage");
    if (!message.data) {
      // Use the empty signal to check the verification state of the account right away
      this.log.debug("empty push message - checking account status");
      this.fxAccounts.checkVerificationStatus();
      return;
    }
    let payload = message.data.json();
    this.log.debug(`push command: ${payload.command}`);
    switch (payload.command) {
      case ON_DEVICE_CONNECTED_NOTIFICATION:
        Services.obs.notifyObservers(null, ON_DEVICE_CONNECTED_NOTIFICATION, payload.data.deviceName);
        break;
      case ON_DEVICE_DISCONNECTED_NOTIFICATION:
        this.fxAccounts.handleDeviceDisconnection(payload.data.id);
        return;
      case ON_PROFILE_UPDATED_NOTIFICATION:
        // We already have a "profile updated" notification sent via WebChannel,
        // let's just re-use that.
        Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION);
        return;
      case ON_PASSWORD_CHANGED_NOTIFICATION:
      case ON_PASSWORD_RESET_NOTIFICATION:
        this._onPasswordChanged();
        return;
      case ON_ACCOUNT_DESTROYED_NOTIFICATION:
        this.fxAccounts.handleAccountDestroyed(payload.data.uid);
        return;
      case ON_COLLECTION_CHANGED_NOTIFICATION:
        Services.obs.notifyObservers(null, ON_COLLECTION_CHANGED_NOTIFICATION, payload.data.collections);
        return;
      case ON_VERIFY_LOGIN_NOTIFICATION:
        Services.obs.notifyObservers(null, ON_VERIFY_LOGIN_NOTIFICATION, JSON.stringify(payload.data));
        break;
      default:
        this.log.warn("FxA Push command unrecognized: " + payload.command);
    }
  },
  /**
   * Check the FxA session status after a password change/reset event.
   * If the session is invalid, reset credentials and notify listeners of
   * ON_ACCOUNT_STATE_CHANGE_NOTIFICATION that the account may have changed
   *
   * @returns {Promise}
   * @private
   */
  async _onPasswordChanged() {
    if (!(await this.fxAccounts.sessionStatus())) {
      await this.fxAccounts.resetCredentials();
      Services.obs.notifyObservers(null, ON_ACCOUNT_STATE_CHANGE_NOTIFICATION);
    }
  },
  /**
   * Fired when the Push server drops a subscription, or the subscription identifier changes.
   *
   * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
   *
   * @returns {Promise}
   * @private
   */
  _onPushSubscriptionChange() {
    this.log.trace("FxAccountsPushService _onPushSubscriptionChange");
    return this.fxAccounts.updateDeviceRegistration();
  },
  /**
   * Unsubscribe from the Push server
   *
   * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#unsubscribe()
   *
   * @returns {Promise}
   * @private
   */
  unsubscribe() {
    this.log.trace("FxAccountsPushService unsubscribe");
    return new Promise((resolve) => {
      this.pushService.unsubscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
        Services.scriptSecurityManager.getSystemPrincipal(),
        (result, ok) => {
          if (Components.isSuccessCode(result)) {
            if (ok === true) {
              this.log.debug("FxAccountsPushService unsubscribed");
            } else {
              this.log.debug("FxAccountsPushService had no subscription to unsubscribe");
            }
          } else {
            this.log.warn("FxAccountsPushService failed to unsubscribe", result);
          }
          return resolve(ok);
        });
    });
  },
};

// Service registration below registers with FxAccountsComponents.manifest
const components = [FxAccountsPushService];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<̚#+A+Acomponents/captivedetect.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";

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
                                   "@mozilla.org/system-message-internal;1",
                                   "nsISystemMessagesInternal");

const DEBUG = false; // set to true to show debug messages

const kCAPTIVEPORTALDETECTOR_CONTRACTID = "@mozilla.org/toolkit/captive-detector;1";
const kCAPTIVEPORTALDETECTOR_CID        = Components.ID("{d9cd00ba-aa4d-47b1-8792-b1fe0cd35060}");

const kOpenCaptivePortalLoginEvent = "captive-portal-login";
const kAbortCaptivePortalLoginEvent = "captive-portal-login-abort";
const kCaptivePortalLoginSuccessEvent = "captive-portal-login-success";
const kCaptivePortalCheckComplete = "captive-portal-check-complete";

const kCaptivePortalSystemMessage = "captive-portal";

function URLFetcher(url, timeout) {
  let self = this;
  let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
              .createInstance(Ci.nsIXMLHttpRequest);
  xhr.open("GET", url, true);
  // Prevent the request from reading from the cache.
  xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  // Prevent the request from writing to the cache.
  xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
  // Prevent privacy leaks
  xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
  // The Cache-Control header is only interpreted by proxies and the
  // final destination. It does not help if a resource is already
  // cached locally.
  xhr.setRequestHeader("Cache-Control", "no-cache");
  // HTTP/1.0 servers might not implement Cache-Control and
  // might only implement Pragma: no-cache
  xhr.setRequestHeader("Pragma", "no-cache");

  xhr.timeout = timeout;
  xhr.ontimeout = function() { self.ontimeout(); };
  xhr.onerror = function() { self.onerror(); };
  xhr.onreadystatechange = function(oEvent) {
    if (xhr.readyState === 4) {
      if (self._isAborted) {
        return;
      }
      if (xhr.status === 200) {
        self.onsuccess(xhr.responseText);
      } else if (xhr.status) {
        self.onredirectorerror(xhr.status);
      }
    }
  };
  xhr.send();
  this._xhr = xhr;
}

URLFetcher.prototype = {
  _isAborted: false,
  ontimeout() {},
  onerror() {},
  abort() {
    if (!this._isAborted) {
      this._isAborted = true;
      this._xhr.abort();
    }
  },
}

function LoginObserver(captivePortalDetector) {
  const LOGIN_OBSERVER_STATE_DETACHED = 0; /* Should not monitor network activity since no ongoing login procedure */
  const LOGIN_OBSERVER_STATE_IDLE = 1; /* No network activity currently, waiting for a longer enough idle period */
  const LOGIN_OBSERVER_STATE_BURST = 2; /* Network activity is detected, probably caused by a login procedure */
  const LOGIN_OBSERVER_STATE_VERIFY_NEEDED = 3; /* Verifing network accessiblity is required after a long enough idle */
  const LOGIN_OBSERVER_STATE_VERIFYING = 4; /* LoginObserver is probing if public network is available */

  let state = LOGIN_OBSERVER_STATE_DETACHED;

  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"]
                              .getService(Ci.nsIHttpActivityDistributor);
  let urlFetcher = null;

  let waitForNetworkActivity = Services.appinfo.widgetToolkit == "gonk";

  let pageCheckingDone = function pageCheckingDone() {
    if (state === LOGIN_OBSERVER_STATE_VERIFYING) {
      urlFetcher = null;
      // Finish polling the canonical site, switch back to idle state and
      // waiting for next burst
      state = LOGIN_OBSERVER_STATE_IDLE;
      timer.initWithCallback(observer,
                             captivePortalDetector._pollingTime,
                             timer.TYPE_ONE_SHOT);
    }
  };

  let checkPageContent = function checkPageContent() {
    debug("checking if public network is available after the login procedure");

    urlFetcher = new URLFetcher(captivePortalDetector._canonicalSiteURL,
                                captivePortalDetector._maxWaitingTime);
    urlFetcher.ontimeout = pageCheckingDone;
    urlFetcher.onerror = pageCheckingDone;
    urlFetcher.onsuccess = function(content) {
      if (captivePortalDetector.validateContent(content)) {
        urlFetcher = null;
        captivePortalDetector.executeCallback(true);
      } else {
        pageCheckingDone();
      }
    };
    urlFetcher.onredirectorerror = pageCheckingDone;
  };

  // Public interface of LoginObserver
  let observer = {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIHttpActivityObserver,
                                           Ci.nsITimerCallback]),

    attach: function attach() {
      if (state === LOGIN_OBSERVER_STATE_DETACHED) {
        activityDistributor.addObserver(this);
        state = LOGIN_OBSERVER_STATE_IDLE;
        timer.initWithCallback(this,
                               captivePortalDetector._pollingTime,
                               timer.TYPE_ONE_SHOT);
        debug("attach HttpObserver for login activity");
      }
    },

    detach: function detach() {
      if (state !== LOGIN_OBSERVER_STATE_DETACHED) {
        if (urlFetcher) {
          urlFetcher.abort();
          urlFetcher = null;
        }
        activityDistributor.removeObserver(this);
        timer.cancel();
        state = LOGIN_OBSERVER_STATE_DETACHED;
        debug("detach HttpObserver for login activity");
      }
    },

    /*
     * Treat all HTTP transactions as captive portal login activities.
     */
    observeActivity: function observeActivity(aHttpChannel, aActivityType,
                                              aActivitySubtype, aTimestamp,
                                              aExtraSizeData, aExtraStringData) {
      if (aActivityType === Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION
          && aActivitySubtype === Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE) {
        switch (state) {
          case LOGIN_OBSERVER_STATE_IDLE:
          case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
            state = LOGIN_OBSERVER_STATE_BURST;
            break;
          default:
            break;
        }
      }
    },

    /*
     * Check if login activity is finished according to HTTP burst.
     */
    notify: function notify() {
      switch (state) {
        case LOGIN_OBSERVER_STATE_BURST:
          // Wait while network stays idle for a short period
          state = LOGIN_OBSERVER_STATE_VERIFY_NEEDED;
          // Fall though to start polling timer
        case LOGIN_OBSERVER_STATE_IDLE:
          if (waitForNetworkActivity) {
            timer.initWithCallback(this,
                                   captivePortalDetector._pollingTime,
                                   timer.TYPE_ONE_SHOT);
            break;
          }
          // if we don't need to wait for network activity, just fall through
          // to perform a captive portal check.
        case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
          // Polling the canonical website since network stays idle for a while
          state = LOGIN_OBSERVER_STATE_VERIFYING;
          checkPageContent();
          break;

        default:
          break;
      }
    },
  };

  return observer;
}

function CaptivePortalDetector() {
  // Load preference
  this._canonicalSiteURL = null;
  this._canonicalSiteExpectedContent = null;

  try {
    this._canonicalSiteURL =
      Services.prefs.getCharPref("captivedetect.canonicalURL");
    this._canonicalSiteExpectedContent =
      Services.prefs.getCharPref("captivedetect.canonicalContent");
  } catch (e) {
    debug("canonicalURL or canonicalContent not set.")
  }

  this._maxWaitingTime =
    Services.prefs.getIntPref("captivedetect.maxWaitingTime");
  this._pollingTime =
    Services.prefs.getIntPref("captivedetect.pollingTime");
  this._maxRetryCount =
    Services.prefs.getIntPref("captivedetect.maxRetryCount");
  debug("Load Prefs {site=" + this._canonicalSiteURL + ",content="
        + this._canonicalSiteExpectedContent + ",time=" + this._maxWaitingTime
        + "max-retry=" + this._maxRetryCount + "}");

  // Create HttpObserver for monitoring the login procedure
  this._loginObserver = LoginObserver(this);

  this._nextRequestId = 0;
  this._runningRequest = null;
  this._requestQueue = []; // Maintain a progress table, store callbacks and the ongoing XHR
  this._interfaceNames = {}; // Maintain names of the requested network interfaces

  debug("CaptiveProtalDetector initiated, waiting for network connection established");
}

CaptivePortalDetector.prototype = {
  classID:   kCAPTIVEPORTALDETECTOR_CID,
  classInfo: XPCOMUtils.generateCI({classID: kCAPTIVEPORTALDETECTOR_CID,
                                    contractID: kCAPTIVEPORTALDETECTOR_CONTRACTID,
                                    classDescription: "Captive Portal Detector",
                                    interfaces: [Ci.nsICaptivePortalDetector]}),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsICaptivePortalDetector]),

  // nsICaptivePortalDetector
  checkCaptivePortal: function checkCaptivePortal(aInterfaceName, aCallback) {
    if (!this._canonicalSiteURL) {
      throw Components.Exception("No canonical URL set up.");
    }

    // Prevent multiple requests on a single network interface
    if (this._interfaceNames[aInterfaceName]) {
      throw Components.Exception("Do not allow multiple request on one interface: " + aInterfaceName);
    }

    let request = {interfaceName: aInterfaceName};
    if (aCallback) {
      let callback = aCallback.QueryInterface(Ci.nsICaptivePortalCallback);
      request["callback"] = callback;
      request["retryCount"] = 0;
    }
    this._addRequest(request);
  },

  abort: function abort(aInterfaceName) {
    debug("abort for " + aInterfaceName);
    this._removeRequest(aInterfaceName);
  },

  finishPreparation: function finishPreparation(aInterfaceName) {
    debug('finish preparation phase for interface "' + aInterfaceName + '"');
    if (!this._runningRequest
        || this._runningRequest.interfaceName !== aInterfaceName) {
      debug("invalid finishPreparation for " + aInterfaceName);
      throw Components.Exception("only first request is allowed to invoke |finishPreparation|");
    }

    this._startDetection();
  },

  cancelLogin: function cancelLogin(eventId) {
    debug('login canceled by user for request "' + eventId + '"');
    // Captive portal login procedure is canceled by user
    if (this._runningRequest && this._runningRequest.hasOwnProperty("eventId")) {
      let id = this._runningRequest.eventId;
      if (eventId === id) {
        this.executeCallback(false);
      }
    }
  },

  _applyDetection: function _applyDetection() {
    debug("enter applyDetection(" + this._runningRequest.interfaceName + ")");

    // Execute network interface preparation
    if (this._runningRequest.hasOwnProperty("callback")) {
      this._runningRequest.callback.prepare();
    } else {
      this._startDetection();
    }
  },

  _startDetection: function _startDetection() {
    debug("startDetection {site=" + this._canonicalSiteURL + ",content="
          + this._canonicalSiteExpectedContent + ",time=" + this._maxWaitingTime + "}");
    let self = this;

    let urlFetcher = new URLFetcher(this._canonicalSiteURL, this._maxWaitingTime);

    let mayRetry = this._mayRetry.bind(this);

    urlFetcher.ontimeout = mayRetry;
    urlFetcher.onerror = mayRetry;
    urlFetcher.onsuccess = function(content) {
      if (self.validateContent(content)) {
        self.executeCallback(true);
      } else {
        // Content of the canonical website has been overwrite
        self._startLogin();
      }
    };
    urlFetcher.onredirectorerror = function(status) {
      if (status >= 300 && status <= 399) {
        // The canonical website has been redirected to an unknown location
        self._startLogin();
      } else {
        mayRetry();
      }
    };

    this._runningRequest["urlFetcher"] = urlFetcher;
  },

  _startLogin: function _startLogin() {
    let id = this._allocateRequestId();
    let details = {
      type: kOpenCaptivePortalLoginEvent,
      id,
      url: this._canonicalSiteURL,
    };
    this._loginObserver.attach();
    this._runningRequest["eventId"] = id;
    this._sendEvent(kOpenCaptivePortalLoginEvent, details);
    gSysMsgr.broadcastMessage(kCaptivePortalSystemMessage, {});
  },

  _mayRetry: function _mayRetry() {
    if (this._runningRequest.retryCount++ < this._maxRetryCount) {
      debug("retry-Detection: " + this._runningRequest.retryCount + "/" + this._maxRetryCount);
      this._startDetection();
    } else {
      this.executeCallback(false);
    }
  },

  executeCallback: function executeCallback(success) {
    if (this._runningRequest) {
      debug("callback executed");
      if (this._runningRequest.hasOwnProperty("callback")) {
        this._runningRequest.callback.complete(success);
      }

      // Only when the request has a event id and |success| is true
      // do we need to notify the login-success event.
      if (this._runningRequest.hasOwnProperty("eventId") && success) {
        let details = {
          type: kCaptivePortalLoginSuccessEvent,
          id: this._runningRequest["eventId"],
        };
        this._sendEvent(kCaptivePortalLoginSuccessEvent, details);
      }

      // Continue the following request
      this._runningRequest["complete"] = true;
      this._removeRequest(this._runningRequest.interfaceName);
    }
  },

  _sendEvent: function _sendEvent(topic, details) {
    debug('sendEvent "' + JSON.stringify(details) + '"');
    Services.obs.notifyObservers(this,
                                 topic,
                                 JSON.stringify(details));
  },

  validateContent: function validateContent(content) {
    debug("received content: " + content);
    let valid = content === this._canonicalSiteExpectedContent;
    // We need a way to indicate that a check has been performed, and if we are
    // still in a captive portal.
    this._sendEvent(kCaptivePortalCheckComplete, !valid);
    return valid;
  },

  _allocateRequestId: function _allocateRequestId() {
    let newId = this._nextRequestId++;
    return newId.toString();
  },

  _runNextRequest: function _runNextRequest() {
    let nextRequest = this._requestQueue.shift();
    if (nextRequest) {
      this._runningRequest = nextRequest;
      this._applyDetection();
    }
  },

  _addRequest: function _addRequest(request) {
    this._interfaceNames[request.interfaceName] = true;
    this._requestQueue.push(request);
    if (!this._runningRequest) {
      this._runNextRequest();
    }
  },

  _removeRequest: function _removeRequest(aInterfaceName) {
    if (!this._interfaceNames[aInterfaceName]) {
      return;
    }

    delete this._interfaceNames[aInterfaceName];

    if (this._runningRequest
        && this._runningRequest.interfaceName === aInterfaceName) {
      this._loginObserver.detach();

      if (!this._runningRequest.complete) {
        // Abort the user login procedure
        if (this._runningRequest.hasOwnProperty("eventId")) {
          let details = {
            type: kAbortCaptivePortalLoginEvent,
            id: this._runningRequest.eventId
          };
          this._sendEvent(kAbortCaptivePortalLoginEvent, details);
        }

        // Abort the ongoing HTTP request
        if (this._runningRequest.hasOwnProperty("urlFetcher")) {
          this._runningRequest.urlFetcher.abort();
        }
      }

      debug("remove running request");
      this._runningRequest = null;

      // Continue next pending reqeust if the ongoing one has been aborted
      this._runNextRequest();
      return;
    }

    // Check if a pending request has been aborted
    for (let i = 0; i < this._requestQueue.length; i++) {
      if (this._requestQueue[i].interfaceName == aInterfaceName) {
        this._requestQueue.splice(i, 1);

        debug("remove pending request #" + i + ", remaining " + this._requestQueue.length);
        break;
      }
    }
  },
};

var debug;
if (DEBUG) {
  debug = function(s) {
    dump("-*- CaptivePortalDetector component: " + s + "\n");
  };
} else {
  debug = function(s) {};
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CaptivePortalDetector]);
PK
!<WMضcomponents/TelemetryStartup.js/* -*- 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";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                                  "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                  "resource://gre/modules/TelemetryEnvironment.jsm");

/**
 * TelemetryStartup is needed to forward the "profile-after-change" notification
 * to TelemetryController.jsm.
 */
function TelemetryStartup() {
}

TelemetryStartup.prototype.classID = Components.ID("{117b219f-92fe-4bd2-a21b-95a342a9d474}");
TelemetryStartup.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsIObserver]);
TelemetryStartup.prototype.observe = function(aSubject, aTopic, aData) {
  if (aTopic == "profile-after-change") {
    // In the content process, this is done in ContentProcessSingleton.js.
    TelemetryController.observe(null, aTopic, null);
  }
  if (aTopic == "profile-after-change") {
    annotateEnvironment();
    TelemetryEnvironment.registerChangeListener("CrashAnnotator", annotateEnvironment);
    TelemetryEnvironment.onInitialized().then(() => annotateEnvironment());
  }
}

function annotateEnvironment() {
  try {
    let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"];
    if (cr) {
      let env = JSON.stringify(TelemetryEnvironment.currentEnvironment);
      cr.getService(Ci.nsICrashReporter).annotateCrashReport("TelemetryEnvironment", env);
    }
  } catch (e) {
    // crash reporting not built or disabled? Ignore errors
  }
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelemetryStartup]);
PK
!<=(!!components/XULStore.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// Enables logging and shorter save intervals.
const debugMode = false;

// Delay when a change is made to when the file is saved.
// 30 seconds normally, or 3 seconds for testing
const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000;

const XULSTORE_CONTRACTID = "@mozilla.org/xul/xulstore;1";
const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
const STOREDB_FILENAME = "xulstore.json";

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");

function XULStore() {
  if (!Services.appinfo.inSafeMode)
    this.load();
}

XULStore.prototype = {
  classID: XULSTORE_CID,
  classInfo: XPCOMUtils.generateCI({classID: XULSTORE_CID,
                                    contractID: XULSTORE_CONTRACTID,
                                    classDescription: "XULStore",
                                    interfaces: [Ci.nsIXULStore]}),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore,
                                         Ci.nsISupportsWeakReference]),
  _xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore),

  /* ---------- private members ---------- */

  /*
   * The format of _data is _data[docuri][elementid][attribute]. For example:
   *  {
   *      "chrome://blah/foo.xul" : {
   *                                    "main-window" : { aaa : 1, bbb : "c" },
   *                                    "barColumn"   : { ddd : 9, eee : "f" },
   *                                },
   *
   *      "chrome://foopy/b.xul" :  { ... },
   *      ...
   *  }
   */
  _data: {},
  _storeFile: null,
  _needsSaving: false,
  _saveAllowed: true,
  _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),

  load() {
    Services.obs.addObserver(this, "profile-before-change", true);

    this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
    this._storeFile.append(STOREDB_FILENAME);

    this.readFile();
  },

  observe(subject, topic, data) {
    this.writeFile();
    if (topic == "profile-before-change") {
      this._saveAllowed = false;
    }
  },

  /*
   * Internal function for logging debug messages to the Error Console window
   */
  log(message) {
    if (!debugMode)
      return;
    dump("XULStore: " + message + "\n");
    Services.console.logStringMessage("XULStore: " + message);
  },

  readFile() {
    const MODE_RDONLY = 0x01;
    const FILE_PERMS  = 0o600;

    let stream = Cc["@mozilla.org/network/file-input-stream;1"].
                 createInstance(Ci.nsIFileInputStream);
    let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
    try {
      stream.init(this._storeFile, MODE_RDONLY, FILE_PERMS, 0);
      this._data = json.decodeFromStream(stream, stream.available());
    } catch (e) {
      this.log("Error reading JSON: " + e);
      // This exception could mean that the file didn't exist.
      // We'll just ignore the error and start with a blank slate.
    } finally {
      stream.close();
    }
  },

  async writeFile() {
    if (!this._needsSaving)
      return;

    this._needsSaving = false;

    this.log("Writing to xulstore.json");

    try {
      let data = JSON.stringify(this._data);
      let encoder = new TextEncoder();

      data = encoder.encode(data);
      await OS.File.writeAtomic(this._storeFile.path, data,
                              { tmpPath: this._storeFile.path + ".tmp" });
    } catch (e) {
      this.log("Failed to write xulstore.json: " + e);
      throw e;
    }
  },

  markAsChanged() {
    if (this._needsSaving || !this._storeFile)
      return;

    // Don't write the file more than once every 30 seconds.
    this._needsSaving = true;
    this._writeTimer.init(this, WRITE_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /* ---------- interface implementation ---------- */

  setValue(docURI, id, attr, value) {
    this.log("Saving " + attr + "=" + value + " for id=" + id + ", doc=" + docURI);

    if (!this._saveAllowed) {
      Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
      return;
    }

    // bug 319846 -- don't save really long attributes or values.
    if (id.length > 512 || attr.length > 512) {
      throw Components.Exception("id or attribute name too long", Cr.NS_ERROR_ILLEGAL_VALUE);
    }

    if (value.length > 4096) {
      Services.console.logStringMessage("XULStore: Warning, truncating long attribute value")
      value = value.substr(0, 4096);
    }

    let obj = this._data;
    if (!(docURI in obj)) {
      obj[docURI] = {};
    }
    obj = obj[docURI];
    if (!(id in obj)) {
      obj[id] = {};
    }
    obj = obj[id];

    // Don't set the value if it is already set to avoid saving the file.
    if (attr in obj && obj[attr] == value)
      return;

    obj[attr] = value; // IE, this._data[docURI][id][attr] = value;

    this.markAsChanged();
  },

  hasValue(docURI, id, attr) {
    this.log("has store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs) {
        return attr in attrs;
      }
    }

    return false;
  },

  getValue(docURI, id, attr) {
    this.log("get store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs) {
        return attrs[attr] || "";
      }
    }

    return "";
  },

  removeValue(docURI, id, attr) {
    this.log("remove store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);

    if (!this._saveAllowed) {
      Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
      return;
    }

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs && attr in attrs) {
        delete attrs[attr];

        if (Object.getOwnPropertyNames(attrs).length == 0) {
          delete ids[id];

          if (Object.getOwnPropertyNames(ids).length == 0) {
            delete this._data[docURI];
          }
        }

        this.markAsChanged();
      }
    }
  },

  getIDsEnumerator(docURI) {
    this.log("Getting ID enumerator for doc=" + docURI);

    if (!(docURI in this._data))
      return new nsStringEnumerator([]);

    let result = [];
    let ids = this._data[docURI];
    if (ids) {
      for (let id in this._data[docURI]) {
        result.push(id);
      }
    }

    return new nsStringEnumerator(result);
  },

  getAttributeEnumerator(docURI, id) {
    this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI);

    if (!(docURI in this._data) || !(id in this._data[docURI]))
      return new nsStringEnumerator([]);

    let attrs = [];
    for (let attr in this._data[docURI][id]) {
      attrs.push(attr);
    }

    return new nsStringEnumerator(attrs);
  }
};

function nsStringEnumerator(items) {
  this._items = items;
}

nsStringEnumerator.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIStringEnumerator]),
  _nextIndex: 0,
  hasMore() {
    return this._nextIndex < this._items.length;
  },
  getNext() {
    if (!this.hasMore())
      throw Cr.NS_ERROR_NOT_AVAILABLE;
    return this._items[this._nextIndex++];
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([XULStore]);
PK
!<Ollcomponents/recording-cmdline.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const nsISupports                    = Components.interfaces.nsISupports;
  
const nsICommandLine                 = Components.interfaces.nsICommandLine;
const nsICommandLineHandler          = Components.interfaces.nsICommandLineHandler;
const nsISupportsString              = Components.interfaces.nsISupportsString;
const nsIWindowWatcher               = Components.interfaces.nsIWindowWatcher;

function RecordingCmdLineHandler() {}
RecordingCmdLineHandler.prototype =
{
    classID: Components.ID('{86FB70EC-90FF-45AD-A1C1-F77D3C1184E9}'),

    /* nsISupports */
    QueryInterface: XPCOMUtils.generateQI([nsICommandLineHandler]),

    /* nsICommandLineHandler */
    handle : function handler_handle(cmdLine) {
        var args = { };
        args.wrappedJSObject = args;
        try {
            var uristr = cmdLine.handleFlagWithParam("recording", false);
            if (uristr == null)
                return;
            try {
                args.uri = cmdLine.resolveURI(uristr).spec;
            }
            catch (e) {
                return;
            }
        }
        catch (e) {
            cmdLine.handleFlag("recording", true);
        }

        /**
         * Manipulate preferences by adding to the *default* branch.  Adding
         * to the default branch means the changes we make won't get written
         * back to user preferences.
         *
         * We want to do this here rather than in reftest.js because it's
         * important to set the recording pref before the platform Init gets
         * called.
         */
        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                    getService(Components.interfaces.nsIPrefService);
        var branch = prefs.getDefaultBranch("");

        try {
            var outputstr = cmdLine.handleFlagWithParam("recording-output", false);
            if (outputstr != null) {
                branch.setCharPref("gfx.2d.recordingfile", outputstr);
            }
        } catch (e) { }

        branch.setBoolPref("gfx.2d.recording", true);

        var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                               .getService(nsIWindowWatcher);
        wwatch.openWindow(null, "chrome://recording/content/recording.xul", "_blank",
                          "chrome,dialog=no,all", args);
        cmdLine.preventDefault = true;
    },

    helpInfo : "  --recording <file> Record drawing for a given URL.\n" +
               "  --recording-output <file> Specify destination file for a drawing recording.\n"
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RecordingCmdLineHandler]);
PK
!<q|components/htmlMenuBuilder.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This component is used to build the menus for the HTML contextmenu attribute.

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

// A global value that is used to identify each menu item. It is
// incremented with each one that is found.
var gGeneratedId = 1;

function HTMLMenuBuilder() {
  this.currentNode = null;
  this.root = null;
  this.items = {};
  this.nestedStack = [];
};

// Building is done in two steps:
// The first generates a hierarchical JS object that contains the menu structure.
// This object is returned by toJSONString.
//
// The second step can take this structure and generate a XUL menu hierarchy or
// other UI from this object. The default UI is done in PageMenu.jsm.
//
// When a multi-process browser is used, the first step is performed by the child
// process and the second step is performed by the parent process.

HTMLMenuBuilder.prototype =
{
  classID:        Components.ID("{51c65f5d-0de5-4edc-9058-60e50cef77f8}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMenuBuilder]),

  currentNode: null,
  root: null,
  items: {},
  nestedStack: [],

  toJSONString: function() {
    return JSON.stringify(this.root);
  },

  openContainer: function(aLabel) {
    if (!this.currentNode) {
      this.root = {
        type: "menu",
        children: []
      };
      this.currentNode = this.root;
    }
    else {
      let parent = this.currentNode;
      this.currentNode = {
        type: "menu",
        label: aLabel,
        children: []
      };
      parent.children.push(this.currentNode);
      this.nestedStack.push(parent);
    }
  },

  addItemFor: function(aElement, aCanLoadIcon) {
    if (!("children" in this.currentNode)) {
      return;
    }

    let item = {
      type: "menuitem",
      label: aElement.label
    };

    let elementType = aElement.type;
    if (elementType == "checkbox" || elementType == "radio") {
      item.checkbox = true;

      if (aElement.checked) {
        item.checked = true;
      }
    }

    let icon = aElement.icon;
    if (icon.length > 0 && aCanLoadIcon) {
      item.icon = icon;
    }

    if (aElement.disabled) {
      item.disabled = true;
    }

    item.id = gGeneratedId++;
    this.currentNode.children.push(item);

    this.items[item.id] = aElement;
  },

  addSeparator: function() {
    if (!("children" in this.currentNode)) {
      return;
    }

    this.currentNode.children.push({ type: "separator"});
  },

  undoAddSeparator: function() {
    if (!("children" in this.currentNode)) {
      return;
    }

    let children = this.currentNode.children;
    if (children.length && children[children.length - 1].type == "separator") {
      children.pop();
    }
  },

  closeContainer: function() {
    this.currentNode = this.nestedStack.length ? this.nestedStack.pop() : this.root;
  },

  click: function(id) {
    let item = this.items[id];
    if (item) {
      item.click();
    }
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HTMLMenuBuilder]);
PK
!<Fa@8!!!components/NotificationStorage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 DEBUG = false;
function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); }

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                   "@mozilla.org/childprocessmessagemanager;1",
                                   "nsIMessageSender");

const kMessageNotificationGetAllOk = "Notification:GetAll:Return:OK";
const kMessageNotificationGetAllKo = "Notification:GetAll:Return:KO";
const kMessageNotificationSaveKo   = "Notification:Save:Return:KO";
const kMessageNotificationDeleteKo = "Notification:Delete:Return:KO";

const kMessages = [
  kMessageNotificationGetAllOk,
  kMessageNotificationGetAllKo,
  kMessageNotificationSaveKo,
  kMessageNotificationDeleteKo
];

function NotificationStorage() {
  // cache objects
  this._notifications = {};
  this._byTag = {};
  this._cached = false;

  this._requests = {};
  this._requestCount = 0;

  Services.obs.addObserver(this, "xpcom-shutdown");

  // Register for message listeners.
  this.registerListeners();
}

NotificationStorage.prototype = {

  registerListeners: function() {
    for (let message of kMessages) {
      cpmm.addMessageListener(message, this);
    }
  },

  unregisterListeners: function() {
    for (let message of kMessages) {
      cpmm.removeMessageListener(message, this);
    }
  },

  observe: function(aSubject, aTopic, aData) {
    if (DEBUG) debug("Topic: " + aTopic);
    if (aTopic === "xpcom-shutdown") {
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  },

  put: function(origin, id, title, dir, lang, body, tag, icon, alertName,
                data, behavior, serviceWorkerRegistrationScope) {
    if (DEBUG) { debug("PUT: " + origin + " " + id + ": " + title); }
    var notification = {
      id: id,
      title: title,
      dir: dir,
      lang: lang,
      body: body,
      tag: tag,
      icon: icon,
      alertName: alertName,
      timestamp: new Date().getTime(),
      origin: origin,
      data: data,
      mozbehavior: behavior,
      serviceWorkerRegistrationScope: serviceWorkerRegistrationScope,
    };

    this._notifications[id] = notification;
    if (tag) {
      if (!this._byTag[origin]) {
        this._byTag[origin] = {};
      }

      // We might have existing notification with this tag,
      // if so we need to remove it from our cache.
      if (this._byTag[origin][tag]) {
        var oldNotification = this._byTag[origin][tag];
        delete this._notifications[oldNotification.id];
      }

      this._byTag[origin][tag] = notification;
    };

    if (serviceWorkerRegistrationScope) {
      cpmm.sendAsyncMessage("Notification:Save", {
        origin: origin,
        notification: notification
      });
    }
  },

  get: function(origin, tag, callback) {
    if (DEBUG) { debug("GET: " + origin + " " + tag); }
    if (this._cached) {
      this._fetchFromCache(origin, tag, callback);
    } else {
      this._fetchFromDB(origin, tag, callback);
    }
  },

  getByID: function(origin, id, callback) {
    if (DEBUG) { debug("GETBYID: " + origin + " " + id); }
    var GetByIDProxyCallback = function(id, originalCallback) {
      this.searchID = id;
      this.originalCallback = originalCallback;
      var self = this;
      this.handle = function(id, title, dir, lang, body, tag, icon, data, behavior, serviceWorkerRegistrationScope) {
        if (id == this.searchID) {
          self.originalCallback.handle(id, title, dir, lang, body, tag, icon, data, behavior, serviceWorkerRegistrationScope);
        }
      };
      this.done = function() {
        self.originalCallback.done();
      };
    };

    return this.get(origin, "", new GetByIDProxyCallback(id, callback));
  },

  delete: function(origin, id) {
    if (DEBUG) { debug("DELETE: " + id); }
    var notification = this._notifications[id];
    if (notification) {
      if (notification.tag) {
        delete this._byTag[origin][notification.tag];
      }
      delete this._notifications[id];
    }

    cpmm.sendAsyncMessage("Notification:Delete", {
      origin: origin,
      id: id
    });
  },

  receiveMessage: function(message) {
    var request = this._requests[message.data.requestID];

    switch (message.name) {
      case kMessageNotificationGetAllOk:
        delete this._requests[message.data.requestID];
        this._populateCache(message.data.notifications);
        this._fetchFromCache(request.origin, request.tag, request.callback);
        break;

      case kMessageNotificationGetAllKo:
        delete this._requests[message.data.requestID];
        try {
          request.callback.done();
        } catch (e) {
          debug("Error calling callback done: " + e);
        }
      case kMessageNotificationSaveKo:
      case kMessageNotificationDeleteKo:
        if (DEBUG) {
          debug("Error received when treating: '" + message.name +
                "': " + message.data.errorMsg);
        }
        break;

      default:
        if (DEBUG) debug("Unrecognized message: " + message.name);
        break;
    }
  },

  _fetchFromDB: function(origin, tag, callback) {
    var request = {
      origin: origin,
      tag: tag,
      callback: callback
    };
    var requestID = this._requestCount++;
    this._requests[requestID] = request;
    cpmm.sendAsyncMessage("Notification:GetAll", {
      origin: origin,
      requestID: requestID
    });
  },

  _fetchFromCache: function(origin, tag, callback) {
    var notifications = [];
    // If a tag was specified and we have a notification
    // with this tag, return that. If no tag was specified
    // simple return all stored notifications.
    if (tag && this._byTag[origin] && this._byTag[origin][tag]) {
      notifications.push(this._byTag[origin][tag]);
    } else if (!tag) {
      for (var id in this._notifications) {
        if (this._notifications[id].origin === origin) {
          notifications.push(this._notifications[id]);
        }
      }
    }

    // Pass each notification back separately.
    // The callback is called asynchronously to match the behaviour when
    // fetching from the database.
    notifications.forEach(function(notification) {
      try {
        Services.tm.dispatchToMainThread(
          callback.handle.bind(callback,
                               notification.id,
                               notification.title,
                               notification.dir,
                               notification.lang,
                               notification.body,
                               notification.tag,
                               notification.icon,
                               notification.data,
                               notification.mozbehavior,
                               notification.serviceWorkerRegistrationScope));
      } catch (e) {
        if (DEBUG) { debug("Error calling callback handle: " + e); }
      }
    });
    try {
      Services.tm.dispatchToMainThread(callback.done);
    } catch (e) {
      if (DEBUG) { debug("Error calling callback done: " + e); }
    }
  },

  _populateCache: function(notifications) {
    notifications.forEach(notification => {
      this._notifications[notification.id] = notification;
      if (notification.tag && notification.origin) {
        let tag = notification.tag;
        let origin = notification.origin;
        if (!this._byTag[origin]) {
          this._byTag[origin] = {};
        }
        this._byTag[origin][tag] = notification;
      }
    });
    this._cached = true;
  },

  classID : Components.ID(NOTIFICATIONSTORAGE_CID),
  contractID : NOTIFICATIONSTORAGE_CONTRACTID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsINotificationStorage,
                                         Ci.nsIMessageListener]),
};


this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NotificationStorage]);
PK
!<BY""components/Push.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");

XPCOMUtils.defineLazyGetter(this, "console", () => {
  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "Push",
  });
});

XPCOMUtils.defineLazyServiceGetter(this, "PushService",
  "@mozilla.org/push/Service;1", "nsIPushService");

const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");

/**
 * The Push component runs in the child process and exposes the Push API
 * to the web application. The PushService running in the parent process is the
 * one actually performing all operations.
 */
function Push() {
  console.debug("Push()");
}

Push.prototype = {
  __proto__: DOMRequestIpcHelper.prototype,

  contractID: "@mozilla.org/push/PushManager;1",

  classID : PUSH_CID,

  QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIObserver]),

  init: function(win) {
    console.debug("init()");

    this._window = win;

    this.initDOMRequestHelper(win);

    this._principal = win.document.nodePrincipal;
  },

  __init: function(scope) {
    this._scope = scope;
  },

  askPermission: function () {
    console.debug("askPermission()");

    return this.createPromise((resolve, reject) => {
      let permissionDenied = () => {
        reject(new this._window.DOMException(
          "User denied permission to use the Push API.",
          "NotAllowedError"
        ));
      };

      let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
      try {
        permission = this._testPermission();
      } catch (e) {
        permissionDenied();
        return;
      }

      if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
        resolve();
      } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
        permissionDenied();
      } else {
        this._requestPermission(resolve, permissionDenied);
      }
    });
  },

  subscribe: function(options) {
    console.debug("subscribe()", this._scope);

    return this.askPermission().then(() =>
      this.createPromise((resolve, reject) => {
        let callback = new PushSubscriptionCallback(this, resolve, reject);

        if (!options || options.applicationServerKey === null) {
          PushService.subscribe(this._scope, this._principal, callback);
          return;
        }

        let keyView = this._normalizeAppServerKey(options.applicationServerKey);
        if (keyView.byteLength === 0) {
          callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
          return;
        }
        PushService.subscribeWithKey(this._scope, this._principal,
                                     keyView.byteLength, keyView,
                                     callback);
      })
    );
  },

  _normalizeAppServerKey: function(appServerKey) {
    let key;
    if (typeof appServerKey == "string") {
      try {
        key = Cu.cloneInto(ChromeUtils.base64URLDecode(appServerKey, {
          padding: "reject",
        }), this._window);
      } catch (e) {
        throw new this._window.DOMException(
          "String contains an invalid character",
          "InvalidCharacterError"
        );
      }
    } else if (this._window.ArrayBuffer.isView(appServerKey)) {
      key = appServerKey.buffer;
    } else {
      // `appServerKey` is an array buffer.
      key = appServerKey;
    }
    return new this._window.Uint8Array(key);
  },

  getSubscription: function() {
    console.debug("getSubscription()", this._scope);

    return this.createPromise((resolve, reject) => {
      let callback = new PushSubscriptionCallback(this, resolve, reject);
      PushService.getSubscription(this._scope, this._principal, callback);
    });
  },

  permissionState: function() {
    console.debug("permissionState()", this._scope);

    return this.createPromise((resolve, reject) => {
      let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;

      try {
        permission = this._testPermission();
      } catch(e) {
        reject();
        return;
      }

      let pushPermissionStatus = "prompt";
      if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
        pushPermissionStatus = "granted";
      } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
        pushPermissionStatus = "denied";
      }
      resolve(pushPermissionStatus);
    });
  },

  _testPermission: function() {
    let permission = Services.perms.testExactPermissionFromPrincipal(
      this._principal, "desktop-notification");
    if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
      return permission;
    }
    try {
      if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
        permission = Ci.nsIPermissionManager.ALLOW_ACTION;
      }
    } catch (e) {}
    return permission;
  },

  _requestPermission: function(allowCallback, cancelCallback) {
    // Create an array with a single nsIContentPermissionType element.
    let type = {
      type: "desktop-notification",
      access: null,
      options: [],
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]),
    };
    let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
    typeArray.appendElement(type);

    // create a nsIContentPermissionRequest
    let request = {
      types: typeArray,
      principal: this._principal,
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
      allow: allowCallback,
      cancel: cancelCallback,
      window: this._window,
    };

    // Using askPermission from nsIDOMWindowUtils that takes care of the
    // remoting if needed.
    let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils);
    windowUtils.askPermission(request);
  },
};

function PushSubscriptionCallback(pushManager, resolve, reject) {
  this.pushManager = pushManager;
  this.resolve = resolve;
  this.reject = reject;
}

PushSubscriptionCallback.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscriptionCallback]),

  onPushSubscription: function(ok, subscription) {
    let {pushManager} = this;
    if (!Components.isSuccessCode(ok)) {
      this._rejectWithError(ok);
      return;
    }

    if (!subscription) {
      this.resolve(null);
      return;
    }

    let p256dhKey = this._getKey(subscription, "p256dh");
    let authSecret = this._getKey(subscription, "auth");
    let options = {
      endpoint: subscription.endpoint,
      scope: pushManager._scope,
      p256dhKey: p256dhKey,
      authSecret: authSecret,
    };
    let appServerKey = this._getKey(subscription, "appServer");
    if (appServerKey) {
      // Avoid passing null keys to work around bug 1256449.
      options.appServerKey = appServerKey;
    }
    let sub = new pushManager._window.PushSubscription(options);
    this.resolve(sub);
  },

  _getKey: function(subscription, name) {
    let outKeyLen = {};
    let rawKey = Cu.cloneInto(subscription.getKey(name, outKeyLen),
                              this.pushManager._window);
    if (!outKeyLen.value) {
      return null;
    }

    let key = new this.pushManager._window.ArrayBuffer(outKeyLen.value);
    let keyView = new this.pushManager._window.Uint8Array(key);
    keyView.set(rawKey);
    return key;
  },

  _rejectWithError: function(result) {
    let error;
    switch (result) {
      case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "Invalid raw ECDSA P-256 public key.",
          "InvalidAccessError"
        );
        break;

      case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "A subscription with a different application server key already exists.",
          "InvalidStateError"
        );
        break;

      default:
        error = new this.pushManager._window.DOMException(
          "Error retrieving push subscription.",
          "AbortError"
        );
    }
    this.reject(error);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);
PK
!<@==components/PushComponents.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 exports XPCOM components for C++ and chrome JavaScript callers to
 * interact with the Push service.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

var isParent = Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

// The default Push service implementation.
XPCOMUtils.defineLazyGetter(this, "PushService", function() {
  const {PushService} = Cu.import("resource://gre/modules/PushService.jsm",
                                  {});
  PushService.init();
  return PushService;
});

// Observer notification topics for push messages and subscription status
// changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
// on `nsIPushService` so that JS callers only need to import this service.
const OBSERVER_TOPIC_PUSH = "push-message";
const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";

/**
 * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
 * implement the `nsIPushService` interface. This interface provides calls
 * similar to the Push DOM API, but does not require service workers.
 *
 * Push service methods may be called from the parent or content process. The
 * parent process implementation loads `PushService.jsm` at app startup, and
 * calls its methods directly. The content implementation forwards calls to
 * the parent Push service via IPC.
 *
 * The implementations share a class and contract ID.
 */
function PushServiceBase() {
  this.wrappedJSObject = this;
  this._addListeners();
}

PushServiceBase.prototype = {
  classID: Components.ID("{daaa8d73-677e-4233-8acd-2c404bd01658}"),
  contractID: "@mozilla.org/push/Service;1",
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference,
    Ci.nsIPushService,
    Ci.nsIPushQuotaManager,
    Ci.nsIPushErrorReporter,
  ]),

  pushTopic: OBSERVER_TOPIC_PUSH,
  subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
  subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,

  _handleReady() {},

  _addListeners() {
    for (let message of this._messages) {
      this._mm.addMessageListener(message, this);
    }
  },

  _isValidMessage(message) {
    return this._messages.includes(message.name);
  },

  observe(subject, topic, data) {
    if (topic === "app-startup") {
      Services.obs.addObserver(this, "sessionstore-windows-restored", true);
      return;
    }
    if (topic === "sessionstore-windows-restored") {
      Services.obs.removeObserver(this, "sessionstore-windows-restored");
      this._handleReady();
      return;
    }
    if (topic === "android-push-service") {
      // Load PushService immediately.
      this._handleReady();
      return;
    }
  },

  _deliverSubscription(request, props) {
    if (!props) {
      request.onPushSubscription(Cr.NS_OK, null);
      return;
    }
    request.onPushSubscription(Cr.NS_OK, new PushSubscription(props));
  },

  _deliverSubscriptionError(request, error) {
    let result = typeof error.result == "number" ?
                 error.result : Cr.NS_ERROR_FAILURE;
    request.onPushSubscription(result, null);
  },
};

/**
 * The parent process implementation of `nsIPushService`. This version loads
 * `PushService.jsm` at startup and calls its methods directly. It also
 * receives and responds to requests from the content process.
 */
function PushServiceParent() {
  PushServiceBase.call(this);
}

PushServiceParent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(PushServiceParent.prototype, "_mm",
  "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");

Object.assign(PushServiceParent.prototype, {
  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceParent),

  _messages: [
    "Push:Register",
    "Push:Registration",
    "Push:Unregister",
    "Push:Clear",
    "Push:NotificationForOriginShown",
    "Push:NotificationForOriginClosed",
    "Push:ReportError",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, 0, null, callback);
  },

  subscribeWithKey(scope, principal, keyLen, key, callback) {
    this._handleRequest("Push:Register", principal, {
      scope: scope,
      appServerKey: key,
    }).then(result => {
      this._deliverSubscription(callback, result);
    }, error => {
      this._deliverSubscriptionError(callback, error);
    }).catch(Cu.reportError);
  },

  unsubscribe(scope, principal, callback) {
    this._handleRequest("Push:Unregister", principal, {
      scope: scope,
    }).then(result => {
      callback.onUnsubscribe(Cr.NS_OK, result);
    }, error => {
      callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
    }).catch(Cu.reportError);
  },

  getSubscription(scope, principal, callback) {
    return this._handleRequest("Push:Registration", principal, {
      scope: scope,
    }).then(result => {
      this._deliverSubscription(callback, result);
    }, error => {
      this._deliverSubscriptionError(callback, error);
    }).catch(Cu.reportError);
  },

  clearForDomain(domain, callback) {
    return this._handleRequest("Push:Clear", null, {
      domain: domain,
    }).then(result => {
      callback.onClear(Cr.NS_OK);
    }, error => {
      callback.onClear(Cr.NS_ERROR_FAILURE);
    }).catch(Cu.reportError);
  },

  // nsIPushQuotaManager methods

  notificationForOriginShown(origin) {
    this.service.notificationForOriginShown(origin);
  },

  notificationForOriginClosed(origin) {
    this.service.notificationForOriginClosed(origin);
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this.service.reportDeliveryError(messageId, reason);
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let {name, principal, target, data} = message;
    if (name === "Push:NotificationForOriginShown") {
      this.notificationForOriginShown(data);
      return;
    }
    if (name === "Push:NotificationForOriginClosed") {
      this.notificationForOriginClosed(data);
      return;
    }
    if (name === "Push:ReportError") {
      this.reportDeliveryError(data.messageId, data.reason);
      return;
    }
    let sender = target.QueryInterface(Ci.nsIMessageSender);
    return this._handleRequest(name, principal, data).then(result => {
      sender.sendAsyncMessage(this._getResponseName(name, "OK"), {
        requestID: data.requestID,
        result: result
      });
    }, error => {
      sender.sendAsyncMessage(this._getResponseName(name, "KO"), {
        requestID: data.requestID,
        result: error.result,
      });
    }).catch(Cu.reportError);
  },

  _handleReady() {
    this.service.init();
  },

  _toPageRecord(principal, data) {
    if (!data.scope) {
      throw new Error("Invalid page record: missing scope");
    }
    if (!principal) {
      throw new Error("Invalid page record: missing principal");
    }
    if (principal.isNullPrincipal || principal.isExpandedPrincipal) {
      throw new Error("Invalid page record: unsupported principal");
    }

    // System subscriptions can only be created by chrome callers, and are
    // exempt from the background message quota and permission checks. They
    // also do not fire service worker events.
    data.systemRecord = principal.isSystemPrincipal;

    data.originAttributes =
      ChromeUtils.originAttributesToSuffix(principal.originAttributes);

    return data;
  },

  _handleRequest(name, principal, data) {
    if (name == "Push:Clear") {
      return this.service.clear(data);
    }

    let pageRecord;
    try {
      pageRecord = this._toPageRecord(principal, data);
    } catch (e) {
      return Promise.reject(e);
    }

    if (name === "Push:Register") {
      return this.service.register(pageRecord);
    }
    if (name === "Push:Registration") {
      return this.service.registration(pageRecord);
    }
    if (name === "Push:Unregister") {
      return this.service.unregister(pageRecord);
    }

    return Promise.reject(new Error("Invalid request: unknown name"));
  },

  _getResponseName(requestName, suffix) {
    let name = requestName.slice("Push:".length);
    return "PushService:" + name + ":" + suffix;
  },

  // Methods used for mocking in tests.

  replaceServiceBackend(options) {
    return this.service.changeTestServer(options.serverURI, options);
  },

  restoreServiceBackend() {
    var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL");
    return this.service.changeTestServer(defaultServerURL);
  },
});

// Used to replace the implementation with a mock.
Object.defineProperty(PushServiceParent.prototype, "service", {
  get() {
    return this._service || PushService;
  },
  set(impl) {
    this._service = impl;
  },
});

/**
 * The content process implementation of `nsIPushService`. This version
 * uses the child message manager to forward calls to the parent process.
 * The parent Push service instance handles the request, and responds with a
 * message containing the result.
 */
function PushServiceContent() {
  PushServiceBase.apply(this, arguments);
  this._requests = new Map();
  this._requestId = 0;
}

PushServiceContent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(PushServiceContent.prototype,
  "_mm", "@mozilla.org/childprocessmessagemanager;1",
  "nsISyncMessageSender");

Object.assign(PushServiceContent.prototype, {
  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceContent),

  _messages: [
    "PushService:Register:OK",
    "PushService:Register:KO",
    "PushService:Registration:OK",
    "PushService:Registration:KO",
    "PushService:Unregister:OK",
    "PushService:Unregister:KO",
    "PushService:Clear:OK",
    "PushService:Clear:KO",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, 0, null, callback);
  },

  subscribeWithKey(scope, principal, keyLen, key, callback) {
    let requestId = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Register", {
      scope: scope,
      appServerKey: key,
      requestID: requestId,
    }, null, principal);
  },

  unsubscribe(scope, principal, callback) {
    let requestId = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Unregister", {
      scope: scope,
      requestID: requestId,
    }, null, principal);
  },

  getSubscription(scope, principal, callback) {
    let requestId = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Registration", {
      scope: scope,
      requestID: requestId,
    }, null, principal);
  },

  clearForDomain(domain, callback) {
    let requestId = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Clear", {
      domain: domain,
      requestID: requestId,
    });
  },

  // nsIPushQuotaManager methods

  notificationForOriginShown(origin) {
    this._mm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
  },

  notificationForOriginClosed(origin) {
    this._mm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this._mm.sendAsyncMessage("Push:ReportError", {
      messageId: messageId,
      reason: reason,
    });
  },

  _addRequest(data) {
    let id = ++this._requestId;
    this._requests.set(id, data);
    return id;
  },

  _takeRequest(requestId) {
    let d = this._requests.get(requestId);
    this._requests.delete(requestId);
    return d;
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let {name, data} = message;
    let request = this._takeRequest(data.requestID);

    if (!request) {
      return;
    }

    switch (name) {
      case "PushService:Register:OK":
      case "PushService:Registration:OK":
        this._deliverSubscription(request, data.result);
        break;

      case "PushService:Register:KO":
      case "PushService:Registration:KO":
        this._deliverSubscriptionError(request, data);
        break;

      case "PushService:Unregister:OK":
        if (typeof data.result === "boolean") {
          request.onUnsubscribe(Cr.NS_OK, data.result);
        } else {
          request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        }
        break;

      case "PushService:Unregister:KO":
        request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        break;

      case "PushService:Clear:OK":
        request.onClear(Cr.NS_OK);
        break;

      case "PushService:Clear:KO":
        request.onClear(Cr.NS_ERROR_FAILURE);
        break;

      default:
        break;
    }
  },
});

/** `PushSubscription` instances are passed to all subscription callbacks. */
function PushSubscription(props) {
  this._props = props;
}

PushSubscription.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscription]),

  /** The URL for sending messages to this subscription. */
  get endpoint() {
    return this._props.endpoint;
  },

  /** The last time a message was sent to this subscription. */
  get lastPush() {
    return this._props.lastPush;
  },

  /** The total number of messages sent to this subscription. */
  get pushCount() {
    return this._props.pushCount;
  },

  /** The number of remaining background messages that can be sent to this
   * subscription, or -1 of the subscription is exempt from the quota.
   */
  get quota() {
    return this._props.quota;
  },

  /**
   * Indicates whether this subscription was created with the system principal.
   * System subscriptions are exempt from the background message quota and
   * permission checks.
   */
  get isSystemSubscription() {
    return !!this._props.systemRecord;
  },

  /** The private key used to decrypt incoming push messages, in JWK format */
  get p256dhPrivateKey() {
    return this._props.p256dhPrivateKey;
  },

  /**
   * Indicates whether this subscription is subject to the background message
   * quota.
   */
  quotaApplies() {
    return this.quota >= 0;
  },

  /**
   * Indicates whether this subscription exceeded the background message quota,
   * or the user revoked the notification permission. The caller must request a
   * new subscription to continue receiving push messages.
   */
  isExpired() {
    return this.quota === 0;
  },

  /**
   * Returns a key for encrypting messages sent to this subscription. JS
   * callers receive the key buffer as a return value, while C++ callers
   * receive the key size and buffer as out parameters.
   */
  getKey(name, outKeyLen) {
    switch (name) {
      case "p256dh":
        return this._getRawKey(this._props.p256dhKey, outKeyLen);

      case "auth":
        return this._getRawKey(this._props.authenticationSecret, outKeyLen);

      case "appServer":
        return this._getRawKey(this._props.appServerKey, outKeyLen);
    }
    return null;
  },

  _getRawKey(key, outKeyLen) {
    if (!key) {
      return null;
    }
    let rawKey = new Uint8Array(key);
    if (outKeyLen) {
      outKeyLen.value = rawKey.length;
    }
    return rawKey;
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
  // Export the correct implementation depending on whether we're running in
  // the parent or content process.
  isParent ? PushServiceParent : PushServiceContent,
]);
PK
!<tt!components/RemoteWebNavigation.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/.

const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
  "resource://gre/modules/sessionstore/Utils.jsm");

function makeURI(url) {
  return Services.io.newURI(url);
}

function RemoteWebNavigation() {
  this.wrappedJSObject = this;
}

RemoteWebNavigation.prototype = {
  classDescription: "nsIWebNavigation for remote browsers",
  classID: Components.ID("{4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd}"),
  contractID: "@mozilla.org/remote-web-navigation;1",

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebNavigation, Ci.nsISupports]),

  swapBrowser(aBrowser) {
    this._browser = aBrowser;
  },

  LOAD_FLAGS_MASK: 65535,
  LOAD_FLAGS_NONE: 0,
  LOAD_FLAGS_IS_REFRESH: 16,
  LOAD_FLAGS_IS_LINK: 32,
  LOAD_FLAGS_BYPASS_HISTORY: 64,
  LOAD_FLAGS_REPLACE_HISTORY: 128,
  LOAD_FLAGS_BYPASS_CACHE: 256,
  LOAD_FLAGS_BYPASS_PROXY: 512,
  LOAD_FLAGS_CHARSET_CHANGE: 1024,
  LOAD_FLAGS_STOP_CONTENT: 2048,
  LOAD_FLAGS_FROM_EXTERNAL: 4096,
  LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP: 8192,
  LOAD_FLAGS_FIRST_LOAD: 16384,
  LOAD_FLAGS_ALLOW_POPUPS: 32768,
  LOAD_FLAGS_BYPASS_CLASSIFIER: 65536,
  LOAD_FLAGS_FORCE_ALLOW_COOKIES: 131072,

  STOP_NETWORK: 1,
  STOP_CONTENT: 2,
  STOP_ALL: 3,

  canGoBack: false,
  canGoForward: false,
  goBack() {
    this._sendMessage("WebNavigation:GoBack", {});
  },
  goForward() {
    this._sendMessage("WebNavigation:GoForward", {});
  },
  gotoIndex(aIndex) {
    this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
  },
  loadURI(aURI, aLoadFlags, aReferrer, aPostData, aHeaders) {
    this.loadURIWithOptions(aURI, aLoadFlags, aReferrer,
                            Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
                            aPostData, aHeaders, null);
  },
  loadURIWithOptions(aURI, aLoadFlags, aReferrer, aReferrerPolicy,
                     aPostData, aHeaders, aBaseURI, aTriggeringPrincipal) {
    this._sendMessage("WebNavigation:LoadURI", {
      uri: aURI,
      flags: aLoadFlags,
      referrer: aReferrer ? aReferrer.spec : null,
      referrerPolicy: aReferrerPolicy,
      postData: aPostData ? Utils.serializeInputStream(aPostData) : null,
      headers: aHeaders ? Utils.serializeInputStream(aHeaders) : null,
      baseURI: aBaseURI ? aBaseURI.spec : null,
      triggeringPrincipal: aTriggeringPrincipal
                           ? Utils.serializePrincipal(aTriggeringPrincipal)
                           : null,
      requestTime: Services.telemetry.msSystemNow(),
    });
  },
  setOriginAttributesBeforeLoading(aOriginAttributes) {
    this._sendMessage("WebNavigation:SetOriginAttributes", {
      originAttributes: aOriginAttributes,
    });
  },
  reload(aReloadFlags) {
    this._sendMessage("WebNavigation:Reload", {flags: aReloadFlags});
  },
  stop(aStopFlags) {
    this._sendMessage("WebNavigation:Stop", {flags: aStopFlags});
  },

  get document() {
    return this._browser.contentDocument;
  },

  _currentURI: null,
  get currentURI() {
    if (!this._currentURI) {
      this._currentURI = makeURI("about:blank");
    }

    return this._currentURI;
  },
  set currentURI(aURI) {
    this.loadURI(aURI.spec, null, null, null);
  },

  referringURI: null,

  // Bug 1233803 - accessing the sessionHistory of remote browsers should be
  // done in content scripts.
  get sessionHistory() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },
  set sessionHistory(aValue) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  _sendMessage(aMessage, aData) {
    try {
      this._browser.messageManager.sendAsyncMessage(aMessage, aData);
    } catch (e) {
      Cu.reportError(e);
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RemoteWebNavigation]);
PK
!<3k	k	components/ProcessSelector.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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");
Cu.import('resource://gre/modules/Services.jsm');

// Fills up aProcesses until max and then selects randomly from the available
// ones.
function RandomSelector() {
}

RandomSelector.prototype = {
  classID:          Components.ID("{c616fcfd-9737-41f1-aa74-cee72a38f91b}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentProcessProvider]),

  provideProcess(aType, aOpener, aProcesses, aCount, aMaxCount) {
    if (aCount < aMaxCount) {
      return Ci.nsIContentProcessProvider.NEW_PROCESS;
    }

    let startIdx = Math.floor(Math.random() * aMaxCount);
    let curIdx = startIdx;

    do {
      if (aProcesses[curIdx].opener === aOpener) {
        return curIdx;
      }

      curIdx = (curIdx + 1) % aMaxCount;
    } while (curIdx !== startIdx);

    return Ci.nsIContentProcessProvider.NEW_PROCESS;
  },
};

// Fills up aProcesses until max and then selects one from the available
// ones that host the least number of tabs.
function MinTabSelector() {
}

MinTabSelector.prototype = {
  classID:          Components.ID("{2dc08eaf-6eef-4394-b1df-a3a927c1290b}"),
  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentProcessProvider]),

  provideProcess(aType, aOpener, aProcesses, aCount, aMaxCount) {
    if (aCount < aMaxCount) {
      return Ci.nsIContentProcessProvider.NEW_PROCESS;
    }

    let min = Number.MAX_VALUE;
    let candidate = Ci.nsIContentProcessProvider.NEW_PROCESS;

    // Note, that at this point aMaxCount is in the valid range and
    // the reason for not using aCount here is because if we keep
    // processes alive for testing but want a test to use only single
    // content process we can just keep relying on dom.ipc.processCount = 1
    // this way.
    for (let i = 0; i < aMaxCount; i++) {
      let process = aProcesses[i];
      let tabCount = process.tabCount;
      if (process.opener === aOpener && tabCount < min) {
        min = tabCount;
        candidate = i;
      }
    }

    return candidate;
  },
};

var components = [RandomSelector, MinTabSelector];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<OH<components/SlowScriptDebug.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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");

function SlowScriptDebug() { }

SlowScriptDebug.prototype = {
  classID: Components.ID("{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}"),
  classDescription: "Slow script debug handler",
  contractID: "@mozilla.org/dom/slow-script-debug;1",
  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISlowScriptDebug]),

  get activationHandler()   { return this._activationHandler; },
  set activationHandler(cb) { return this._activationHandler = cb; },

  get remoteActivationHandler()   { return this._remoteActivationHandler; },
  set remoteActivationHandler(cb) { return this._remoteActivationHandler = cb; },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SlowScriptDebug]);
PK
!<Kcomponents/PeerConnection.js/* jshint moz:true, browser:true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
  "resource://gre/modules/media/PeerConnectionIdp.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
  "resource://gre/modules/media/RTCStatsReport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
  "resource://gre/modules/AppConstants.jsm");

const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
const PC_RECEIVER_CONTRACT = "@mozilla.org/dom/rtpreceiver;1";
const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
const PC_DTMF_SENDER_CONTRACT = "@mozilla.org/dom/rtcdtmfsender;1";

const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
const PC_RECEIVER_CID = Components.ID("{d974b814-8fde-411c-8c45-b86791b81030}");
const PC_COREQUEST_CID = Components.ID("{74b2122d-65a8-4824-aa9e-3d664cb75dc2}");
const PC_DTMF_SENDER_CID = Components.ID("{3610C242-654E-11E6-8EC0-6D1BE389A607}");

function logMsg(msg, file, line, flag, winID) {
  let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
  let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
  scriptError.initWithWindowID(msg, file, null, line, 0, flag,
                               "content javascript", winID);
  let console = Cc["@mozilla.org/consoleservice;1"].
  getService(Ci.nsIConsoleService);
  console.logMessage(scriptError);
}

let setupPrototype = (_class, dict) => {
  _class.prototype.classDescription = _class.name;
  Object.assign(_class.prototype, dict);
}

// Global list of PeerConnection objects, so they can be cleaned up when
// a page is torn down. (Maps inner window ID to an array of PC objects).
class GlobalPCList {
  constructor() {
    this._list = {};
    this._networkdown = false; // XXX Need to query current state somehow
    this._lifecycleobservers = {};
    this._nextId = 1;
    Services.obs.addObserver(this, "inner-window-destroyed", true);
    Services.obs.addObserver(this, "profile-change-net-teardown", true);
    Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
    Services.obs.addObserver(this, "network:offline-status-changed", true);
    Services.obs.addObserver(this, "gmp-plugin-crash", true);
    Services.obs.addObserver(this, "PeerConnection:response:allow", true);
    Services.obs.addObserver(this, "PeerConnection:response:deny", true);
    if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
      let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
      mm.addMessageListener("gmp-plugin-crash", this);
    }
  }

  notifyLifecycleObservers(pc, type) {
    for (var key of Object.keys(this._lifecycleobservers)) {
      this._lifecycleobservers[key](pc, pc._winID, type);
    }
  }

  addPC(pc) {
    let winID = pc._winID;
    if (this._list[winID]) {
      this._list[winID].push(Cu.getWeakReference(pc));
    } else {
      this._list[winID] = [Cu.getWeakReference(pc)];
    }
    pc._globalPCListId = this._nextId++;
    this.removeNullRefs(winID);
  }

  findPC(globalPCListId) {
    for (let winId in this._list) {
      if (this._list.hasOwnProperty(winId)) {
        for (let pcref of this._list[winId]) {
          let pc = pcref.get();
          if (pc && pc._globalPCListId == globalPCListId) {
            return pc;
          }
        }
      }
    }
    return null;
  }

  removeNullRefs(winID) {
    if (this._list[winID] === undefined) {
      return;
    }
    this._list[winID] = this._list[winID].filter(
      function(e, i, a) { return e.get() !== null; });

    if (this._list[winID].length === 0) {
      delete this._list[winID];
    }
  }

  hasActivePeerConnection(winID) {
    this.removeNullRefs(winID);
    return !!this._list[winID];
  }

  handleGMPCrash(data) {
    let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
      if (list.hasOwnProperty(winID)) {
        list[winID].forEach(function(pcref) {
          let pc = pcref.get();
          if (pc) {
            pc._pc.pluginCrash(pluginID, pluginName);
          }
        });
      }
    };

    // a plugin crashed; if it's associated with any of our PCs, fire an
    // event to the DOM window
    for (let winId in this._list) {
      broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
    }
  }

  receiveMessage({ name, data }) {
    if (name == "gmp-plugin-crash") {
      this.handleGMPCrash(data);
    }
  }

  observe(subject, topic, data) {
    let cleanupPcRef = function(pcref) {
      let pc = pcref.get();
      if (pc) {
        pc._pc.close();
        delete pc._observer;
        pc._pc = null;
      }
    };

    let cleanupWinId = function(list, winID) {
      if (list.hasOwnProperty(winID)) {
        list[winID].forEach(cleanupPcRef);
        delete list[winID];
      }
    };

    if (topic == "inner-window-destroyed") {
      let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
      cleanupWinId(this._list, winID);

      if (this._lifecycleobservers.hasOwnProperty(winID)) {
        delete this._lifecycleobservers[winID];
      }
    } else if (topic == "profile-change-net-teardown" ||
               topic == "network:offline-about-to-go-offline") {
      // As Necko doesn't prevent us from accessing the network we still need to
      // monitor the network offline/online state here. See bug 1326483
      this._networkdown = true;
    } else if (topic == "network:offline-status-changed") {
      if (data == "offline") {
        this._networkdown = true;
      } else if (data == "online") {
        this._networkdown = false;
      }
    } else if (topic == "gmp-plugin-crash") {
      if (subject instanceof Ci.nsIWritablePropertyBag2) {
        let pluginID = subject.getPropertyAsUint32("pluginID");
        let pluginName = subject.getPropertyAsAString("pluginName");
        let data = { pluginID, pluginName };
        this.handleGMPCrash(data);
      }
    } else if (topic == "PeerConnection:response:allow" ||
               topic == "PeerConnection:response:deny") {
      var pc = this.findPC(data);
      if (pc) {
        if (topic == "PeerConnection:response:allow") {
          pc._settlePermission.allow();
        } else {
          let err = new pc._win.DOMException("The request is not allowed by " +
              "the user agent or the platform in the current context.",
              "NotAllowedError");
          pc._settlePermission.deny(err);
        }
      }
    }
  }

  _registerPeerConnectionLifecycleCallback(winID, cb) {
    this._lifecycleobservers[winID] = cb;
  }
}
setupPrototype(GlobalPCList, {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsIMessageListener,
                                         Ci.nsISupportsWeakReference,
                                         Ci.IPeerConnectionManager]),
  classID: PC_MANAGER_CID,
  _xpcom_factory: {
    createInstance(outer, iid) {
      if (outer) {
        throw Cr.NS_ERROR_NO_AGGREGATION;
      }
      return _globalPCList.QueryInterface(iid);
    }
  }
});

var _globalPCList = new GlobalPCList();

class RTCIceCandidate {
  init(win) {
    this._win = win;
  }

  __init(dict) {
    Object.assign(this, dict);
  }
}
setupPrototype(RTCIceCandidate, {
  classID: PC_ICE_CID,
  contractID: PC_ICE_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
});

class RTCSessionDescription {
  init(win) {
    this._win = win;
    this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  }

  __init({ type, sdp }) {
    Object.assign(this, { _type: type, _sdp: sdp });
  }

  get type() { return this._type; }
  set type(type) {
    this.warn();
    this._type = type;
  }

  get sdp() { return this._sdp; }
  set sdp(sdp) {
    this.warn();
    this._sdp = sdp;
  }

  warn() {
    if (!this._warned) {
      // Warn once per RTCSessionDescription about deprecated writable usage.
      this.logWarning("RTCSessionDescription's members are readonly! " +
                      "Writing to them is deprecated and will break soon!");
      this._warned = true;
    }
  }

  logWarning(msg) {
    let err = this._win.Error();
    logMsg(msg, err.fileName, err.lineNumber, Ci.nsIScriptError.warningFlag,
           this._winID);
  }
}
setupPrototype(RTCSessionDescription, {
  classID: PC_SESSION_CID,
  contractID: PC_SESSION_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
});

class RTCStatsReport {
  constructor(win, dict) {
    this._win = win;
    this._pcid = dict.pcid;
    this._report = convertToRTCStatsReport(dict);
  }

  setInternal(aKey, aObj) {
    return this.__DOM_IMPL__.__set(aKey, aObj);
  }

  // TODO: Remove legacy API eventually
  // see Bug 1328194
  //
  // Since maplike is recent, we still also make the stats available as legacy
  // enumerable read-only properties directly on our content-facing object.
  // Must be called after our webidl sandwich is made.

  makeStatsPublic(warnNullable, isLegacy) {
    let legacyProps = {};
    for (let key in this._report) {
      let internal = Cu.cloneInto(this._report[key], this._win);
      if (isLegacy) {
        internal.type = this._specToLegacyFieldMapping[internal.type] || internal.type;
      }
      this.setInternal(key, internal);
      let value = Cu.cloneInto(this._report[key], this._win);
      value.type = this._specToLegacyFieldMapping[value.type] || value.type;
      legacyProps[key] = {
        enumerable: true, configurable: false,
        get: Cu.exportFunction(function() {
          if (warnNullable.warn) {
            warnNullable.warn();
            warnNullable.warn = null;
          }
          return value;
        }, this.__DOM_IMPL__.wrappedJSObject)
      };
    }
    Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, legacyProps);
  }

  get mozPcid() { return this._pcid; }
}
setupPrototype(RTCStatsReport, {
  classID: PC_STATS_CID,
  contractID: PC_STATS_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  _specToLegacyFieldMapping: {
        "inbound-rtp": "inboundrtp",
        "outbound-rtp": "outboundrtp",
        "candidate-pair": "candidatepair",
        "local-candidate": "localcandidate",
        "remote-candidate": "remotecandidate"
  }
});

class RTCPeerConnection {
  constructor() {
    this._senders = [];
    this._receivers = [];

    this._pc = null;
    this._closed = false;

    this._localType = null;
    this._remoteType = null;
    // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
    // canTrickle == null means unknown; when a remote description is received it
    // is set to true or false based on the presence of the "trickle" ice-option
    this._canTrickle = null;

    // States
    this._iceGatheringState = this._iceConnectionState = "new";

    this._hasStunServer = this._hasTurnServer = false;
    this._iceGatheredRelayCandidates = false;

  // TODO: Remove legacy API eventually
  // see Bug 1328194
  this._onGetStatsIsLegacy = false;
  }

  init(win) {
    this._win = win;
  }

  __init(rtcConfig) {
    this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
    // TODO: Update this code once we support pc.setConfiguration, to track
    // setting from content independently from pref (Bug 1181768).
    if (rtcConfig.iceTransportPolicy == "all" &&
        Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")) {
      rtcConfig.iceTransportPolicy = "relay";
    }
    this._config = Object.assign({}, rtcConfig);

    if (!rtcConfig.iceServers ||
        !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
      try {
         rtcConfig.iceServers =
           JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers") || "[]");
      } catch (e) {
        this.logWarning(
            "Ignoring invalid media.peerconnection.default_iceservers in about:config");
        rtcConfig.iceServers = [];
      }
      try {
        this._mustValidateRTCConfiguration(rtcConfig,
            "Ignoring invalid media.peerconnection.default_iceservers in about:config");
      } catch (e) {
        this.logWarning(e.message);
        rtcConfig.iceServers = [];
      }
    } else {
      // This gets executed in the typical case when iceServers
      // are passed in through the web page.
      this._mustValidateRTCConfiguration(rtcConfig,
        "RTCPeerConnection constructor passed invalid RTCConfiguration");
    }
    var principal = Cu.getWebIDLCallerPrincipal();
    this._isChrome = Services.scriptSecurityManager.isSystemPrincipal(principal);

    if (_globalPCList._networkdown) {
      throw new this._win.DOMException(
          "Can't create RTCPeerConnections when the network is down",
          "InvalidStateError");
    }

    this.makeGetterSetterEH("ontrack");
    this.makeLegacyGetterSetterEH("onaddstream", "Use peerConnection.ontrack instead.");
    this.makeLegacyGetterSetterEH("onaddtrack", "Use peerConnection.ontrack instead.");
    this.makeGetterSetterEH("onicecandidate");
    this.makeGetterSetterEH("onnegotiationneeded");
    this.makeGetterSetterEH("onsignalingstatechange");
    this.makeGetterSetterEH("onremovestream");
    this.makeGetterSetterEH("ondatachannel");
    this.makeGetterSetterEH("oniceconnectionstatechange");
    this.makeGetterSetterEH("onicegatheringstatechange");
    this.makeGetterSetterEH("onidentityresult");
    this.makeGetterSetterEH("onpeeridentity");
    this.makeGetterSetterEH("onidpassertionerror");
    this.makeGetterSetterEH("onidpvalidationerror");

    this._pc = new this._win.PeerConnectionImpl();
    this._operationsChain = this._win.Promise.resolve();

    this.__DOM_IMPL__._innerObject = this;
    this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);

    // Warn just once per PeerConnection about deprecated getStats usage.
    this._warnDeprecatedStatsAccessNullable = { warn: () =>
      this.logWarning("non-maplike pc.getStats access is deprecated, and will be removed in the near future! " +
                      "See http://w3c.github.io/webrtc-pc/#getstats-example for usage.") };

    this._warnDeprecatedStatsCallbacksNullable = { warn: () =>
      this.logWarning("Callback-based pc.getStats is deprecated, and will be removed in the near future! Use promise-version! " +
                      "See http://w3c.github.io/webrtc-pc/#getstats-example for usage.") };

    // Add a reference to the PeerConnection to global list (before init).
    _globalPCList.addPC(this);

    this._impl.initialize(this._observer, this._win, rtcConfig,
                          Services.tm.currentThread);

    this._certificateReady = this._initCertificate(rtcConfig.certificates);
    this._initIdp();
    _globalPCList.notifyLifecycleObservers(this, "initialized");
  }

  get _impl() {
    if (!this._pc) {
      throw new this._win.DOMException(
          "RTCPeerConnection is gone (did you enter Offline mode?)",
          "InvalidStateError");
    }
    return this._pc;
  }

  getConfiguration() {
    return this._config;
  }

  async _initCertificate(certificates = []) {
    let certificate;
    if (certificates.length > 1) {
      throw new this._win.DOMException(
        "RTCPeerConnection does not currently support multiple certificates",
        "NotSupportedError");
    }
    if (certificates.length) {
      certificate = certificates.find(c => c.expires > Date.now());
      if (!certificate) {
        throw new this._win.DOMException(
          "Unable to create RTCPeerConnection with an expired certificate",
          "InvalidParameterError");
      }
    }

    if (!certificate) {
      certificate = await this._win.RTCPeerConnection.generateCertificate({
        name: "ECDSA", namedCurve: "P-256"
      });
    }
    this._impl.certificate = certificate;
  }

  _resetPeerIdentityPromise() {
    this._peerIdentity = new this._win.Promise((resolve, reject) => {
      this._resolvePeerIdentity = resolve;
      this._rejectPeerIdentity = reject;
    });
  }

  _initIdp() {
    this._resetPeerIdentityPromise();
    this._lastIdentityValidation = this._win.Promise.resolve();

    let prefName = "media.peerconnection.identity.timeout";
    let idpTimeout = Services.prefs.getIntPref(prefName);
    this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
    this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
  }

  // Add a function to the internal operations chain.

  async _chain(func) {
    let p = (async () => {
      await this._operationsChain;
      // Don't _checkClosed() inside the chain, because it throws, and spec
      // behavior is to NOT reject outstanding promises on close. This is what
      // happens most of the time anyways, as the c++ code stops calling us once
      // closed, hanging the chain. However, c++ may already have queued tasks
      // on us, so if we're one of those then sit back.
      if (this._closed) {
        return null;
      }
      return func();
    })();
    // don't propagate errors in the operations chain (this is a fork of p).
    this._operationsChain = p.catch(() => {});
    return p;
  }

  // It's basically impossible to use async directly in JSImplemented code,
  // because the implicit promise must be wrapped to the right type for content.
  //
  // The _async wrapper takes care of this. The _legacy wrapper implements
  // legacy callbacks in a manner that produces correct line-numbers in errors,
  // provided that methods validate their inputs before putting themselves on
  // the pc's operations chain.
  //
  // These wrappers also serve as guards against settling promises past close().

  _async(func) {
    return this._win.Promise.resolve(this._closeWrapper(func));
  }

  _legacy(...args) {
    return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
  }

  _auto(onSucc, onErr, func) {
    return (typeof onSucc == "function") ? this._legacy(onSucc, onErr, func)
                                         : this._async(func);
  }

  async _closeWrapper(func) {
    let closed = this._closed;
    try {
      let result = await func();
      if (!closed && this._closed) {
        await new Promise(() => {});
      }
      return result;
    } catch (e) {
      if (!closed && this._closed) {
        await new Promise(() => {});
      }
      throw e;
    }
  }

  async _legacyCloseWrapper(onSucc, onErr, func) {

    let wrapCallback = cb => result => {
      try {
        cb && cb(result);
      } catch (e) {
        this.logErrorAndCallOnError(e);
      }
    };

    try {
      wrapCallback(onSucc)(await func());
    } catch (e) {
      wrapCallback(onErr)(e);
    }
  }

  /**
   * An RTCConfiguration may look like this:
   *
   * { "iceServers": [ { urls: "stun:stun.example.org", },
   *                   { url: "stun:stun.example.org", }, // deprecated version
   *                   { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
   *                     username:"jib", credential:"mypass"} ] }
   *
   * This function normalizes the structure of the input for rtcConfig.iceServers for us,
   * so we test well-formed stun/turn urls before passing along to C++.
   *   msg - Error message to detail which array-entry failed, if any.
   */
  _mustValidateRTCConfiguration({ iceServers }, msg) {

    // Normalize iceServers input
    iceServers.forEach(server => {
      if (typeof server.urls === "string") {
        server.urls = [server.urls];
      } else if (!server.urls && server.url) {
        // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
        server.urls = [server.url];
        this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
      }
    });

    let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);

    let nicerNewURI = uriStr => {
      try {
        return ios.newURI(uriStr);
      } catch (e) {
        if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
          throw new this._win.DOMException(msg + " - malformed URI: " + uriStr,
                                           "SyntaxError");
        }
        throw e;
      }
    };

    var stunServers = 0;

    iceServers.forEach(({ urls, username, credential, credentialType }) => {
      if (!urls) {
        throw new this._win.DOMException(msg + " - missing urls", "InvalidAccessError");
      }
      urls.map(url => nicerNewURI(url)).forEach(({ scheme, spec }) => {
        if (scheme in { turn: 1, turns: 1 }) {
          if (username == undefined) {
            throw new this._win.DOMException(msg + " - missing username: " + spec,
                                             "InvalidAccessError");
          }
          if (credential == undefined) {
            throw new this._win.DOMException(msg + " - missing credential: " + spec,
                                             "InvalidAccessError");
          }
          if (credentialType != "password") {
            this.logWarning("RTCConfiguration TURN credentialType \"" +
                            credentialType +
                            "\" is not yet implemented. Treating as password." +
                            " https://bugzil.la/1247616");
          }
          this._hasTurnServer = true;
          stunServers += 1;
        } else if (scheme in { stun: 1, stuns: 1 }) {
          this._hasStunServer = true;
          stunServers += 1;
        } else {
          throw new this._win.DOMException(msg + " - improper scheme: " + scheme,
                                           "SyntaxError");
        }
        if (scheme in { stuns: 1 }) {
          this.logWarning(scheme.toUpperCase() + " is not yet supported.");
        }
        if (stunServers >= 5) {
          this.logError("Using five or more STUN/TURN servers causes problems");
        } else if (stunServers > 2) {
          this.logWarning("Using more than two STUN/TURN servers slows down discovery");
        }
      });
    });
  }

  // Ideally, this should be of the form _checkState(state),
  // where the state is taken from an enumeration containing
  // the valid peer connection states defined in the WebRTC
  // spec. See Bug 831756.
  _checkClosed() {
    if (this._closed) {
      throw new this._win.DOMException("Peer connection is closed",
                                       "InvalidStateError");
    }
  }

  dispatchEvent(event) {
    // PC can close while events are firing if there is an async dispatch
    // in c++ land. But let through "closed" signaling and ice connection events.
    if (!this._closed || this._inClose) {
      this.__DOM_IMPL__.dispatchEvent(event);
    }
  }

  // Log error message to web console and window.onerror, if present.
  logErrorAndCallOnError(e) {
    this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.exceptionFlag);

    // Safely call onerror directly if present (necessary for testing)
    try {
      if (typeof this._win.onerror === "function") {
        this._win.onerror(e.message, e.fileName, e.lineNumber);
      }
    } catch (e) {
      // If onerror itself throws, service it.
      try {
        this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.errorFlag);
      } catch (e) {}
    }
  }

  logError(msg) {
    this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
  }

  logWarning(msg) {
    this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
  }

  logStackMsg(msg, flag) {
    let err = this._win.Error();
    this.logMsg(msg, err.fileName, err.lineNumber, flag);
  }

  logMsg(msg, file, line, flag) {
    return logMsg(msg, file, line, flag, this._winID);
  }

  getEH(type) {
    return this.__DOM_IMPL__.getEventHandler(type);
  }

  setEH(type, handler) {
    this.__DOM_IMPL__.setEventHandler(type, handler);
  }

  makeGetterSetterEH(name) {
    Object.defineProperty(this, name,
                          {
                            get() { return this.getEH(name); },
                            set(h) { return this.setEH(name, h); }
                          });
  }

  makeLegacyGetterSetterEH(name, msg) {
    Object.defineProperty(this, name,
                          {
                            get() { return this.getEH(name); },
                            set(h) {
                              this.logWarning(name + " is deprecated! " + msg);
                              return this.setEH(name, h);
                            }
                          });
  }

  createOffer(optionsOrOnSucc, onErr, options) {
    // This entry-point handles both new and legacy call sig. Decipher which one
    if (typeof optionsOrOnSucc == "function") {
      return this._legacy(optionsOrOnSucc, onErr, () => this._createOffer(options));
    }
    return this._async(() => this._createOffer(optionsOrOnSucc));
  }

  async _createOffer(options) {
    this._checkClosed();
    let origin = Cu.getWebIDLCallerPrincipal().origin;
    return this._chain(async () => {
      let haveAssertion;
      if (this._localIdp.enabled) {
        haveAssertion = this._getIdentityAssertion(origin);
      }
      await this._getPermission();
      await this._certificateReady;
      let sdp = await new Promise((resolve, reject) => {
        this._onCreateOfferSuccess = resolve;
        this._onCreateOfferFailure = reject;
        this._impl.createOffer(options);
      });
      if (haveAssertion) {
        await haveAssertion;
        sdp = this._localIdp.addIdentityAttribute(sdp);
      }
      return Cu.cloneInto({ type: "offer", sdp }, this._win);
    });
  }

  createAnswer(optionsOrOnSucc, onErr) {
    // This entry-point handles both new and legacy call sig. Decipher which one
    if (typeof optionsOrOnSucc == "function") {
      return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
    }
    return this._async(() => this._createAnswer(optionsOrOnSucc));
  }

  async _createAnswer(options) {
    this._checkClosed();
    let origin = Cu.getWebIDLCallerPrincipal().origin;
    return this._chain(async () => {
      // We give up line-numbers in errors by doing this here, but do all
      // state-checks inside the chain, to support the legacy feature that
      // callers don't have to wait for setRemoteDescription to finish.
      if (!this.remoteDescription) {
        throw new this._win.DOMException("setRemoteDescription not called",
                                         "InvalidStateError");
      }
      if (this.remoteDescription.type != "offer") {
        throw new this._win.DOMException("No outstanding offer",
                                         "InvalidStateError");
      }
      let haveAssertion;
      if (this._localIdp.enabled) {
        haveAssertion = this._getIdentityAssertion(origin);
      }
      await this._getPermission();
      await this._certificateReady;
      let sdp = await new Promise((resolve, reject) => {
        this._onCreateAnswerSuccess = resolve;
        this._onCreateAnswerFailure = reject;
        this._impl.createAnswer();
      });
      if (haveAssertion) {
        await haveAssertion;
        sdp = this._localIdp.addIdentityAttribute(sdp);
      }
      return Cu.cloneInto({ type: "answer", sdp }, this._win);
    });
  }

  async _getPermission() {
    if (!this._havePermission) {
      let privileged = this._isChrome || AppConstants.MOZ_B2G ||
          Services.prefs.getBoolPref("media.navigator.permission.disabled");

      if (privileged) {
        this._havePermission = Promise.resolve();
      } else {
        this._havePermission = new Promise((resolve, reject) => {
          this._settlePermission = { allow: resolve, deny: reject };
          let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
              getInterface(Ci.nsIDOMWindowUtils).outerWindowID;

          let chrome = new CreateOfferRequest(outerId, this._winID,
                                              this._globalPCListId, false);
          let request = this._win.CreateOfferRequest._create(this._win, chrome);
          Services.obs.notifyObservers(request, "PeerConnection:request");
        });
      }
    }
    return this._havePermission;
  }

  setLocalDescription(desc, onSucc, onErr) {
    return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
  }

  async _setLocalDescription({ type, sdp }) {
    this._checkClosed();

    this._localType = type;

    let action = this._actions[type];
    if (action === undefined) {
      throw new this._win.DOMException(
          "Invalid type " + type + " provided to setLocalDescription",
          "InvalidParameterError");
    }
    if (action == Ci.IPeerConnection.kActionPRAnswer) {
      throw new this._win.DOMException("pranswer not yet implemented",
                                       "NotSupportedError");
    }

    if (!sdp && action != Ci.IPeerConnection.kActionRollback) {
      throw new this._win.DOMException(
          "Empty or null SDP provided to setLocalDescription",
          "InvalidParameterError");
    }

    return this._chain(async () => {
      await this._getPermission();
      await new Promise((resolve, reject) => {
        this._onSetLocalDescriptionSuccess = resolve;
        this._onSetLocalDescriptionFailure = reject;
        this._impl.setLocalDescription(action, sdp);
      });
    });
  }

  async _validateIdentity(sdp, origin) {
    let expectedIdentity;

    // Only run a single identity verification at a time.  We have to do this to
    // avoid problems with the fact that identity validation doesn't block the
    // resolution of setRemoteDescription().
    let p = (async () => {
      try {
        await this._lastIdentityValidation;
        let msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
        expectedIdentity = this._impl.peerIdentity;
        // If this pc has an identity already, then the identity in sdp must match
        if (expectedIdentity && (!msg || msg.identity !== expectedIdentity)) {
          this.close();
          throw new this._win.DOMException(
            "Peer Identity mismatch, expected: " + expectedIdentity,
            "IncompatibleSessionDescriptionError");
        }
        if (msg) {
          // Set new identity and generate an event.
          this._impl.peerIdentity = msg.identity;
          this._resolvePeerIdentity(Cu.cloneInto({
            idp: this._remoteIdp.provider,
            name: msg.identity
          }, this._win));
        }
      } catch (e) {
        this._rejectPeerIdentity(e);
        // If we don't expect a specific peer identity, failure to get a valid
        // peer identity is not a terminal state, so replace the promise to
        // allow another attempt.
        if (!this._impl.peerIdentity) {
          this._resetPeerIdentityPromise();
        }
        throw e;
      }
    })();
    this._lastIdentityValidation = p.catch(() => {});

    // Only wait for IdP validation if we need identity matching
    if (expectedIdentity) {
      await p;
    }
  }

  setRemoteDescription(desc, onSucc, onErr) {
    return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
  }

  async _setRemoteDescription({ type, sdp }) {
    this._checkClosed();
    this._remoteType = type;

    let action = this._actions[type];
    if (action === undefined) {
      throw new this._win.DOMException(
          "Invalid type " + type + " provided to setRemoteDescription",
          "InvalidParameterError");
    }
    if (action == Ci.IPeerConnection.kActionPRAnswer) {
      throw new this._win.DOMException("pranswer not yet implemented",
                                       "NotSupportedError");
    }

    if (!sdp && type != "rollback") {
      throw new this._win.DOMException(
          "Empty or null SDP provided to setRemoteDescription",
          "InvalidParameterError");
    }

    // Get caller's origin before hitting the promise chain
    let origin = Cu.getWebIDLCallerPrincipal().origin;

    return this._chain(async () => {
      let haveSetRemote = (async () => {
        await this._getPermission();
        await new Promise((resolve, reject) => {
          this._onSetRemoteDescriptionSuccess = resolve;
          this._onSetRemoteDescriptionFailure = reject;
          this._impl.setRemoteDescription(action, sdp);
        });
        this._updateCanTrickle();
      })();

      if (action != Ci.IPeerConnection.kActionRollback) {
        // Do setRemoteDescription and identity validation in parallel
        await this._validateIdentity(sdp, origin);
      }
      await haveSetRemote;
    });
  }

  setIdentityProvider(provider, protocol, username) {
    this._checkClosed();
    this._localIdp.setIdentityProvider(provider, protocol, username);
  }

  async _getIdentityAssertion(origin) {
    await this._certificateReady;
    return this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin);
  }

  getIdentityAssertion() {
    this._checkClosed();
    let origin = Cu.getWebIDLCallerPrincipal().origin;
    return this._win.Promise.resolve(this._chain(() =>
        this._getIdentityAssertion(origin)));
  }

  get canTrickleIceCandidates() {
    return this._canTrickle;
  }

  _updateCanTrickle() {
    let containsTrickle = section => {
      let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
      return lines.some(line => {
        let prefix = "a=ice-options:";
        if (line.substring(0, prefix.length) !== prefix) {
          return false;
        }
        let tokens = line.substring(prefix.length).split(" ");
        return tokens.some(x => x === "trickle");
      });
    };

    let desc = null;
    try {
      // The getter for remoteDescription can throw if the pc is closed.
      desc = this.remoteDescription;
    } catch (e) {}
    if (!desc) {
      this._canTrickle = null;
      return;
    }

    let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
    let topSection = sections.shift();
    this._canTrickle =
      containsTrickle(topSection) || sections.every(containsTrickle);
  }

  // TODO: Implement processing for end-of-candidates (bug 1318167)
  addIceCandidate(cand, onSucc, onErr) {
    return this._auto(onSucc, onErr, () => cand && this._addIceCandidate(cand));
  }

  async _addIceCandidate({ candidate, sdpMid, sdpMLineIndex }) {
    this._checkClosed();
    if (sdpMid === null && sdpMLineIndex === null) {
      throw new this._win.DOMException(
          "Invalid candidate (both sdpMid and sdpMLineIndex are null).",
          "TypeError");
    }
    return this._chain(() => {
      if (!this.remoteDescription) {
        throw new this._win.DOMException(
            "setRemoteDescription needs to called before addIceCandidate",
            "InvalidStateError");
      }
      return new Promise((resolve, reject) => {
        this._onAddIceCandidateSuccess = resolve;
        this._onAddIceCandidateError = reject;
        this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
      });
    });
  }

  addStream(stream) {
    stream.getTracks().forEach(track => this.addTrack(track, stream));
  }

  addTrack(track, stream) {
    if (stream.currentTime === undefined) {
      throw new this._win.DOMException("invalid stream.", "InvalidParameterError");
    }
    this._checkClosed();
    this._senders.forEach(sender => {
      if (sender.track == track) {
        throw new this._win.DOMException("already added.",
                                         "InvalidParameterError");
      }
    });
    this._impl.addTrack(track, stream);
    let sender = this._win.RTCRtpSender._create(this._win,
                                                new RTCRtpSender(this, track,
                                                                 stream));
    this._senders.push(sender);
    return sender;
  }

  removeTrack(sender) {
    this._checkClosed();
    var i = this._senders.indexOf(sender);
    if (i >= 0) {
      this._senders.splice(i, 1);
      this._impl.removeTrack(sender.track); // fires negotiation needed
    }
  }

  _insertDTMF(sender, tones, duration, interToneGap) {
    return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
  }

  _getDTMFToneBuffer(sender) {
    return this._impl.getDTMFToneBuffer(sender.__DOM_IMPL__);
  }

  async _replaceTrack(sender, withTrack) {
    this._checkClosed();
    return this._chain(() => new Promise((resolve, reject) => {
      this._onReplaceTrackSender = sender;
      this._onReplaceTrackWithTrack = withTrack;
      this._onReplaceTrackSuccess = resolve;
      this._onReplaceTrackFailure = reject;
      this._impl.replaceTrack(sender.track, withTrack);
    }));
  }

  _setParameters({ track }, parameters) {
    if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
      return;
    }
    // validate parameters input
    var encodings = parameters.encodings || [];

    encodings.reduce((uniqueRids, { rid, scaleResolutionDownBy }) => {
      if (scaleResolutionDownBy < 1.0) {
        throw new this._win.RangeError("scaleResolutionDownBy must be >= 1.0");
      }
      if (!rid && encodings.length > 1) {
        throw new this._win.DOMException("Missing rid", "TypeError");
      }
      if (uniqueRids[rid]) {
        throw new this._win.DOMException("Duplicate rid", "TypeError");
      }
      uniqueRids[rid] = true;
      return uniqueRids;
    }, {});

    this._impl.setParameters(track, parameters);
  }

  _getParameters({ track }) {
    if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
      return null;
    }
    return this._impl.getParameters(track);
  }

  close() {
    if (this._closed) {
      return;
    }
    this._closed = true;
    this._inClose = true;
    this.changeIceConnectionState("closed");
    this._localIdp.close();
    this._remoteIdp.close();
    this._impl.close();
    this._inClose = false;
  }

  getLocalStreams() {
    this._checkClosed();
    return this._impl.getLocalStreams();
  }

  getRemoteStreams() {
    this._checkClosed();
    return this._impl.getRemoteStreams();
  }

  getSenders() {
    return this._senders;
  }

  getReceivers() {
    return this._receivers;
  }

  mozAddRIDExtension(receiver, extensionId) {
    this._impl.addRIDExtension(receiver.track, extensionId);
  }

  mozAddRIDFilter(receiver, rid) {
    this._impl.addRIDFilter(receiver.track, rid);
  }

  get localDescription() {
    this._checkClosed();
    let sdp = this._impl.localDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
  }

  get currentLocalDescription() {
    this._checkClosed();
    let sdp = this._impl.currentLocalDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
  }

  get pendingLocalDescription() {
    this._checkClosed();
    let sdp = this._impl.pendingLocalDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
  }

  get remoteDescription() {
    this._checkClosed();
    let sdp = this._impl.remoteDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
  }

  get currentRemoteDescription() {
    this._checkClosed();
    let sdp = this._impl.currentRemoteDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
  }

  get pendingRemoteDescription() {
    this._checkClosed();
    let sdp = this._impl.pendingRemoteDescription;
    if (sdp.length == 0) {
      return null;
    }
    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
  }

  get peerIdentity() { return this._peerIdentity; }
  get idpLoginUrl() { return this._localIdp.idpLoginUrl; }
  get id() { return this._impl.id; }
  set id(s) { this._impl.id = s; }
  get iceGatheringState() { return this._iceGatheringState; }
  get iceConnectionState() { return this._iceConnectionState; }

  get signalingState() {
    // checking for our local pc closed indication
    // before invoking the pc methods.
    if (this._closed) {
      return "closed";
    }
    return {
      "SignalingInvalid":            "",
      "SignalingStable":             "stable",
      "SignalingHaveLocalOffer":     "have-local-offer",
      "SignalingHaveRemoteOffer":    "have-remote-offer",
      "SignalingHaveLocalPranswer":  "have-local-pranswer",
      "SignalingHaveRemotePranswer": "have-remote-pranswer",
      "SignalingClosed":             "closed"
    }[this._impl.signalingState];
  }

  changeIceGatheringState(state) {
    this._iceGatheringState = state;
    _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
    this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
  }

  changeIceConnectionState(state) {
    if (state != this._iceConnectionState) {
      this._iceConnectionState = state;
      _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
      this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
    }
  }

  getStats(selector, onSucc, onErr) {
    let isLegacy = (typeof onSucc) == "function";
    if (isLegacy &&
        this._warnDeprecatedStatsCallbacksNullable.warn) {
      this._warnDeprecatedStatsCallbacksNullable.warn();
      this._warnDeprecatedStatsCallbacksNullable.warn = null;
    }
    return this._auto(onSucc, onErr, () => this._getStats(selector, isLegacy));
  }

  async _getStats(selector, isLegacy) {
    // getStats is allowed even in closed state.
    return this._chain(() => new Promise((resolve, reject) => {
      this._onGetStatsIsLegacy = isLegacy;
      this._onGetStatsSuccess = resolve;
      this._onGetStatsFailure = reject;
      this._impl.getStats(selector);
    }));
  }

  createDataChannel(label, {
                      maxRetransmits, ordered, negotiated, id = 0xFFFF,
                      maxRetransmitTime, maxPacketLifeTime = maxRetransmitTime,
                      protocol
                    } = {}) {
    this._checkClosed();

    if (maxRetransmitTime !== undefined) {
      this.logWarning("Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!");
    }
    if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
      throw new this._win.DOMException(
          "Both maxPacketLifeTime and maxRetransmits cannot be provided",
          "InvalidParameterError");
    }
    // Must determine the type where we still know if entries are undefined.
    let type;
    if (maxPacketLifeTime) {
      type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
    } else if (maxRetransmits) {
      type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
    } else {
      type = Ci.IPeerConnection.kDataChannelReliable;
    }
    // Synchronous since it doesn't block.
    return this._impl.createDataChannel(label, protocol, type, ordered,
                                        maxPacketLifeTime, maxRetransmits,
                                        negotiated, id);
  }
}
setupPrototype(RTCPeerConnection, {
  classID: PC_CID,
  contractID: PC_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer]),
  _actions: {
    offer: Ci.IPeerConnection.kActionOffer,
    answer: Ci.IPeerConnection.kActionAnswer,
    pranswer: Ci.IPeerConnection.kActionPRAnswer,
    rollback: Ci.IPeerConnection.kActionRollback,
  },
});

// This is a separate class because we don't want to expose it to DOM.

class PeerConnectionObserver {
  init(win) {
    this._win = win;
  }

  __init(dompc) {
    this._dompc = dompc._innerObject;
  }

  newError(message, code) {
    // These strings must match those defined in the WebRTC spec.
    const reasonName = [
      "",
      "InternalError",
      "InvalidCandidateError",
      "InvalidParameterError",
      "InvalidStateError",
      "InvalidSessionDescriptionError",
      "IncompatibleSessionDescriptionError",
      "InternalError",
      "IncompatibleMediaStreamTrackError",
      "InternalError"
    ];
    let name = reasonName[Math.min(code, reasonName.length - 1)];
    return new this._dompc._win.DOMException(message, name);
  }

  dispatchEvent(event) {
    this._dompc.dispatchEvent(event);
  }

  onCreateOfferSuccess(sdp) {
    this._dompc._onCreateOfferSuccess(sdp);
  }

  onCreateOfferError(code, message) {
    this._dompc._onCreateOfferFailure(this.newError(message, code));
  }

  onCreateAnswerSuccess(sdp) {
    this._dompc._onCreateAnswerSuccess(sdp);
  }

  onCreateAnswerError(code, message) {
    this._dompc._onCreateAnswerFailure(this.newError(message, code));
  }

  onSetLocalDescriptionSuccess() {
    this._dompc._onSetLocalDescriptionSuccess();
  }

  onSetRemoteDescriptionSuccess() {
    this._dompc._onSetRemoteDescriptionSuccess();
  }

  onSetLocalDescriptionError(code, message) {
    this._localType = null;
    this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
  }

  onSetRemoteDescriptionError(code, message) {
    this._remoteType = null;
    this._dompc._onSetRemoteDescriptionFailure(this.newError(message, code));
  }

  onAddIceCandidateSuccess() {
    this._dompc._onAddIceCandidateSuccess();
  }

  onAddIceCandidateError(code, message) {
    this._dompc._onAddIceCandidateError(this.newError(message, code));
  }

  onIceCandidate(sdpMLineIndex, sdpMid, candidate) {
    let win = this._dompc._win;
    if (candidate) {
      if (candidate.includes(" typ relay ")) {
        this._dompc._iceGatheredRelayCandidates = true;
      }
      candidate = new win.RTCIceCandidate({ candidate, sdpMid, sdpMLineIndex });
    } else {
      candidate = null;

    }
    this.dispatchEvent(new win.RTCPeerConnectionIceEvent("icecandidate",
                                                         { candidate }));
  }

  onNegotiationNeeded() {
    this.dispatchEvent(new this._win.Event("negotiationneeded"));
  }

  // This method is primarily responsible for updating iceConnectionState.
  // This state is defined in the WebRTC specification as follows:
  //
  // iceConnectionState:
  // -------------------
  //   new           Any of the RTCIceTransports are in the new state and none
  //                 of them are in the checking, failed or disconnected state.
  //
  //   checking      Any of the RTCIceTransports are in the checking state and
  //                 none of them are in the failed or disconnected state.
  //
  //   connected     All RTCIceTransports are in the connected, completed or
  //                 closed state and at least one of them is in the connected
  //                 state.
  //
  //   completed     All RTCIceTransports are in the completed or closed state
  //                 and at least one of them is in the completed state.
  //
  //   failed        Any of the RTCIceTransports are in the failed state.
  //
  //   disconnected  Any of the RTCIceTransports are in the disconnected state
  //                 and none of them are in the failed state.
  //
  //   closed        All of the RTCIceTransports are in the closed state.

  handleIceConnectionStateChange(iceConnectionState) {
    let pc = this._dompc;
    if (pc.iceConnectionState === iceConnectionState) {
      return;
    }
    if (pc.iceConnectionState === "new") {
      var checking_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_CHECKING_RATE");
      if (iceConnectionState === "checking") {
        checking_histogram.add(true);
      } else if (iceConnectionState === "failed") {
        checking_histogram.add(false);
      }
    } else if (pc.iceConnectionState === "checking") {
      var success_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
      if (iceConnectionState === "completed" ||
          iceConnectionState === "connected") {
        success_histogram.add(true);
      } else if (iceConnectionState === "failed") {
        success_histogram.add(false);
      }
    }

    if (iceConnectionState === "failed") {
      if (!pc._hasStunServer) {
        pc.logError("ICE failed, add a STUN server and see about:webrtc for more details");
      } else if (!pc._hasTurnServer) {
        pc.logError("ICE failed, add a TURN server and see about:webrtc for more details");
      } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
        pc.logError("ICE failed, your TURN server appears to be broken, see about:webrtc for more details");
      } else {
        pc.logError("ICE failed, see about:webrtc for more details");
      }
    }

    pc.changeIceConnectionState(iceConnectionState);
  }

  // This method is responsible for updating iceGatheringState. This
  // state is defined in the WebRTC specification as follows:
  //
  // iceGatheringState:
  // ------------------
  //   new        The object was just created, and no networking has occurred
  //              yet.
  //
  //   gathering  The ICE agent is in the process of gathering candidates for
  //              this RTCPeerConnection.
  //
  //   complete   The ICE agent has completed gathering. Events such as adding
  //              a new interface or a new TURN server will cause the state to
  //              go back to gathering.
  //
  handleIceGatheringStateChange(gatheringState) {
    let pc = this._dompc;
    if (pc.iceGatheringState === gatheringState) {
      return;
    }
    pc.changeIceGatheringState(gatheringState);
  }

  onStateChange(state) {
    switch (state) {
      case "SignalingState":
        this.dispatchEvent(new this._win.Event("signalingstatechange"));
        break;

      case "IceConnectionState":
        this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
        break;

      case "IceGatheringState":
        this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
        break;

      case "SdpState":
        // No-op
        break;

      case "ReadyState":
        // No-op
        break;

      case "SipccState":
        // No-op
        break;

      default:
        this._dompc.logWarning("Unhandled state type: " + state);
        break;
    }
  }

  onGetStatsSuccess(dict) {
    let pc = this._dompc;
    let chromeobj = new RTCStatsReport(pc._win, dict);
    let webidlobj = pc._win.RTCStatsReport._create(pc._win, chromeobj);
    chromeobj.makeStatsPublic(pc._warnDeprecatedStatsCallbacksNullable &&
                              pc._warnDeprecatedStatsAccessNullable,
                              pc._onGetStatsIsLegacy);
    pc._onGetStatsSuccess(webidlobj);
  }

  onGetStatsError(code, message) {
    this._dompc._onGetStatsFailure(this.newError(message, code));
  }

  onAddStream(stream) {
    let ev = new this._dompc._win.MediaStreamEvent("addstream", { stream });
    this.dispatchEvent(ev);
  }

  onRemoveStream(stream) {
    this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
                                                             { stream }));
  }

  onAddTrack(track, streams) {
    let pc = this._dompc;
    let receiver = pc._win.RTCRtpReceiver._create(pc._win,
                                                  new RTCRtpReceiver(pc,
                                                                     track));
    pc._receivers.push(receiver);
    let ev = new pc._win.RTCTrackEvent("track", { receiver, track, streams });
    this.dispatchEvent(ev);

    // Fire legacy event as well for a little bit.
    ev = new pc._win.MediaStreamTrackEvent("addtrack", { track });
    this.dispatchEvent(ev);
  }

  onRemoveTrack(track) {
    let pc = this._dompc;
    let i = pc._receivers.findIndex(receiver => receiver.track == track);
    if (i >= 0) {
      pc._receivers.splice(i, 1);
    }
  }

  onReplaceTrackSuccess() {
    var pc = this._dompc;
    pc._onReplaceTrackSender.track = pc._onReplaceTrackWithTrack;
    pc._onReplaceTrackWithTrack = null;
    pc._onReplaceTrackSender = null;
    pc._onReplaceTrackSuccess();
  }

  onReplaceTrackError(code, message) {
    var pc = this._dompc;
    pc._onReplaceTrackWithTrack = null;
    pc._onReplaceTrackSender = null;
    pc._onReplaceTrackFailure(this.newError(message, code));
  }

  notifyDataChannel(channel) {
    this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
                                                                { channel }));
  }

  onDTMFToneChange(trackId, tone) {
    var pc = this._dompc;
    var sender = pc._senders.find(({track}) => track.id == trackId);
    sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
                                                                 { tone }));
  }
}
setupPrototype(PeerConnectionObserver, {
  classID: PC_OBS_CID,
  contractID: PC_OBS_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
});

class RTCPeerConnectionStatic {
  init(win) {
    this._winID = win.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  }

  registerPeerConnectionLifecycleCallback(cb) {
    _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
  }
}
setupPrototype(RTCPeerConnectionStatic, {
  classID: PC_STATIC_CID,
  contractID: PC_STATIC_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
});

class RTCDTMFSender {
  constructor(sender) {
    this._sender = sender;
  }

  get toneBuffer() {
    return this._sender._pc._getDTMFToneBuffer(this._sender);
  }

  get ontonechange() {
    return this.__DOM_IMPL__.getEventHandler("ontonechange");
  }

  set ontonechange(handler) {
    this.__DOM_IMPL__.setEventHandler("ontonechange", handler);
  }

  insertDTMF(tones, duration, interToneGap) {
    this._sender._pc._checkClosed();

    if (this._sender._pc._senders.indexOf(this._sender.__DOM_IMPL__) == -1) {
      throw new this._sender._pc._win.DOMException("RTCRtpSender is stopped",
                                                   "InvalidStateError");
    }

    duration = Math.max(40, Math.min(duration, 6000));
    if (interToneGap < 30) interToneGap = 30;

    tones = tones.toUpperCase();

    if (tones.match(/[^0-9A-D#*,]/)) {
      throw new this._sender._pc._win.DOMException("Invalid DTMF characters",
                                                   "InvalidCharacterError");
    }

    this._sender._pc._insertDTMF(this._sender, tones, duration, interToneGap);
  }
}
setupPrototype(RTCDTMFSender, {
  classID: PC_DTMF_SENDER_CID,
  contractID: PC_DTMF_SENDER_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});

class RTCRtpSender {
  constructor(pc, track, stream) {
    let dtmf = pc._win.RTCDTMFSender._create(pc._win, new RTCDTMFSender(this));
    Object.assign(this, { _pc: pc, track, _stream: stream, dtmf });
  }

  replaceTrack(withTrack) {
    return this._pc._async(() => this._pc._replaceTrack(this, withTrack));
  }

  setParameters(parameters) {
    return this._pc._win.Promise.resolve()
      .then(() => this._pc._setParameters(this, parameters));
  }

  getParameters() {
    return this._pc._getParameters(this);
  }

  getStats() {
    return this._pc._async(
      async () => this._pc._getStats(this.track));
  }
}
setupPrototype(RTCRtpSender, {
  classID: PC_SENDER_CID,
  contractID: PC_SENDER_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});

class RTCRtpReceiver {
  constructor(pc, track) {
    Object.assign(this, { _pc: pc, track });
  }

  getStats() {
    return this._pc._async(
      async () => this._pc.getStats(this.track));
  }
}
setupPrototype(RTCRtpReceiver, {
  classID: PC_RECEIVER_CID,
  contractID: PC_RECEIVER_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});

class CreateOfferRequest {
  constructor(windowID, innerWindowID, callID, isSecure) {
    Object.assign(this, { windowID, innerWindowID, callID, isSecure });
  }
}
setupPrototype(CreateOfferRequest, {
  classID: PC_COREQUEST_CID,
  contractID: PC_COREQUEST_CONTRACT,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
});

this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
  [GlobalPCList,
   RTCDTMFSender,
   RTCIceCandidate,
   RTCSessionDescription,
   RTCPeerConnection,
   RTCPeerConnectionStatic,
   RTCRtpReceiver,
   RTCRtpSender,
   RTCStatsReport,
   PeerConnectionObserver,
   CreateOfferRequest]
);
PK
!<]dR2R2*chrome/marionette/content/accessibility.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");

const logger = Log.repository.getLogger("Marionette");

const {ElementNotAccessibleError} =
    Cu.import("chrome://marionette/content/error.js", {});

XPCOMUtils.defineLazyModuleGetter(
    this, "setInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(
    this, "clearInterval", "resource://gre/modules/Timer.jsm");

XPCOMUtils.defineLazyGetter(this, "service", () => {
  try {
    return Cc["@mozilla.org/accessibilityService;1"]
        .getService(Ci.nsIAccessibilityService);
  } catch (e) {
    logger.warn("Accessibility module is not present");
    return undefined;
  }
});

this.EXPORTED_SYMBOLS = ["accessibility"];

/** @namespace */
this.accessibility = {
  get service() {
    return service;
  },
};

/**
 * Accessible states used to check element"s state from the accessiblity API
 * perspective.
 *
 * Note: if gecko is built with --disable-accessibility, the interfaces
 * are not defined. This is why we use getters instead to be able to use
 * these statically.
 */
accessibility.State = {
  get Unavailable() {
    return Ci.nsIAccessibleStates.STATE_UNAVAILABLE;
  },
  get Focusable() {
    return Ci.nsIAccessibleStates.STATE_FOCUSABLE;
  },
  get Selectable() {
    return Ci.nsIAccessibleStates.STATE_SELECTABLE;
  },
  get Selected() {
    return Ci.nsIAccessibleStates.STATE_SELECTED;
  },
};

/**
 * Accessible object roles that support some action.
 */
accessibility.ActionableRoles = new Set([
  "checkbutton",
  "check menu item",
  "check rich option",
  "combobox",
  "combobox option",
  "entry",
  "key",
  "link",
  "listbox option",
  "listbox rich option",
  "menuitem",
  "option",
  "outlineitem",
  "pagetab",
  "pushbutton",
  "radiobutton",
  "radio menu item",
  "rowheader",
  "slider",
  "spinbutton",
  "switch",
]);


/**
 * Factory function that constructs a new {@code accessibility.Checks}
 * object with enforced strictness or not.
 */
accessibility.get = function(strict = false) {
  return new accessibility.Checks(!!strict);
};

/**
 * Component responsible for interacting with platform accessibility
 * API.
 *
 * Its methods serve as wrappers for testing content and chrome
 * accessibility as well as accessibility of user interactions.
 */
accessibility.Checks = class {

  /**
   * @param {boolean} strict
   *     Flag indicating whether the accessibility issue should be logged
   *     or cause an error to be thrown.  Default is to log to stdout.
   */
  constructor(strict) {
    this.strict = strict;
  }

  /**
   * Get an accessible object for an element.
   *
   * @param {DOMElement|XULElement} element
   *     Element to get the accessible object for.
   * @param {boolean=} mustHaveAccessible
   *     Flag indicating that the element must have an accessible object.
   *     Defaults to not require this.
   *
   * @return {Promise.<nsIAccessible>}
   *     Promise with an accessibility object for the given element.
   */
  getAccessible(element, mustHaveAccessible = false) {
    if (!this.strict) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      if (!accessibility.service) {
        reject();
        return;
      }

      // First, check if accessibility is ready.
      let docAcc = accessibility.service
          .getAccessibleFor(element.ownerDocument);
      let state = {};
      docAcc.getState(state, {});
      if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) {
        // Accessibility is ready, resolve immediately.
        let acc = accessibility.service.getAccessibleFor(element);
        if (mustHaveAccessible && !acc) {
          reject();
        } else {
          resolve(acc);
        }
        return;
      }
      // Accessibility for the doc is busy, so wait for the state to change.
      let eventObserver = {
        observe(subject, topic, data) {
          if (topic !== "accessible-event") {
            return;
          }

          // If event type does not match expected type, skip the event.
          let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
          if (event.eventType !== Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
            return;
          }

          // If event's accessible does not match expected accessible,
          // skip the event.
          if (event.accessible !== docAcc) {
            return;
          }

          Services.obs.removeObserver(this, "accessible-event");
          let acc = accessibility.service.getAccessibleFor(element);
          if (mustHaveAccessible && !acc) {
            reject();
          } else {
            resolve(acc);
          }
        },
      };
      Services.obs.addObserver(eventObserver, "accessible-event");
    }).catch(() => this.error(
        "Element does not have an accessible object", element));
  }

  /**
   * Test if the accessible has a role that supports some arbitrary
   * action.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @return {boolean}
   *     True if an actionable role is found on the accessible, false
   *     otherwise.
   */
  isActionableRole(accessible) {
    return accessibility.ActionableRoles.has(
        accessibility.service.getStringRole(accessible.role));
  }

  /**
   * Test if an accessible has at least one action that it supports.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @return {boolean}
   *     True if the accessible has at least one supported action,
   *     false otherwise.
   */
  hasActionCount(accessible) {
    return accessible.actionCount > 0;
  }

  /**
   * Test if an accessible has a valid name.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @return {boolean}
   *     True if the accessible has a non-empty valid name, or false if
   *     this is not the case.
   */
  hasValidName(accessible) {
    return accessible.name && accessible.name.trim();
  }

  /**
   * Test if an accessible has a {@code hidden} attribute.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @return {boolean}
   *     True if the accessible object has a {@code hidden} attribute,
   *     false otherwise.
   */
  hasHiddenAttribute(accessible) {
    let hidden = false;
    try {
      hidden = accessible.attributes.getStringProperty("hidden");
    } catch (e) {}
    // if the property is missing, error will be thrown
    return hidden && hidden === "true";
  }

  /**
   * Verify if an accessible has a given state.
   * Test if an accessible has a given state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object to test.
   * @param {number} stateToMatch
   *     State to match.
   *
   * @return {boolean}
   *     True if |accessible| has |stateToMatch|, false otherwise.
   */
  matchState(accessible, stateToMatch) {
    let state = {};
    accessible.getState(state, {});
    return !!(state.value & stateToMatch);
  }

  /**
   * Test if an accessible is hidden from the user.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @return {boolean}
   *     True if element is hidden from user, false otherwise.
   */
  isHidden(accessible) {
    while (accessible) {
      if (this.hasHiddenAttribute(accessible)) {
        return true;
      }
      accessible = accessible.parent;
    }
    return false;
  }

  /**
   * Test if the element's visible state corresponds to its accessibility
   * API visibility.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   * @param {boolean} visible
   *     Visibility state of |element|.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s visibility state does not correspond to
   *     |accessible|'s.
   */
  assertVisible(accessible, element, visible) {
    if (!accessible) {
      return;
    }

    let hiddenAccessibility = this.isHidden(accessible);

    let message;
    if (visible && hiddenAccessibility) {
      message = "Element is not currently visible via the accessibility API " +
          "and may not be manipulated by it";
    } else if (!visible && !hiddenAccessibility) {
      message = "Element is currently only visible via the accessibility API " +
          "and can be manipulated by it";
    }
    this.error(message, element);
  }

  /**
   * Test if the element's unavailable accessibility state matches the
   * enabled state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   * @param {boolean} enabled
   *     Enabled state of |element|.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s enabled state does not match |accessible|'s.
   */
  assertEnabled(accessible, element, enabled) {
    if (!accessible) {
      return;
    }

    let win = element.ownerGlobal;
    let disabledAccessibility = this.matchState(
        accessible, accessibility.State.Unavailable);
    let explorable = win.getComputedStyle(element)
        .getPropertyValue("pointer-events") !== "none";

    let message;
    if (!explorable && !disabledAccessibility) {
      message = "Element is enabled but is not explorable via the " +
          "accessibility API";
    } else if (enabled && disabledAccessibility) {
      message = "Element is enabled but disabled via the accessibility API";
    } else if (!enabled && !disabledAccessibility) {
      message = "Element is disabled but enabled via the accessibility API";
    }
    this.error(message, element);
  }

  /**
   * Test if it is possible to activate an element with the accessibility
   * API.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   *
   * @throws ElementNotAccessibleError
   *     If it is impossible to activate |element| with |accessible|.
   */
  assertActionable(accessible, element) {
    if (!accessible) {
      return;
    }

    let message;
    if (!this.hasActionCount(accessible)) {
      message = "Element does not support any accessible actions";
    } else if (!this.isActionableRole(accessible)) {
      message = "Element does not have a correct accessibility role " +
          "and may not be manipulated via the accessibility API";
    } else if (!this.hasValidName(accessible)) {
      message = "Element is missing an accessible name";
    } else if (!this.matchState(accessible, accessibility.State.Focusable)) {
      message = "Element is not focusable via the accessibility API";
    }

    this.error(message, element);
  }

  /**
   * Test that an element's selected state corresponds to its
   * accessibility API selected state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement}
   *     Element associated with |accessible|.
   * @param {boolean} selected
   *     The |element|s selected state.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s selected state does not correspond to
   *     |accessible|'s.
   */
  assertSelected(accessible, element, selected) {
    if (!accessible) {
      return;
    }

    // element is not selectable via the accessibility API
    if (!this.matchState(accessible, accessibility.State.Selectable)) {
      return;
    }

    let selectedAccessibility =
        this.matchState(accessible, accessibility.State.Selected);

    let message;
    if (selected && !selectedAccessibility) {
      message = "Element is selected but not selected via the accessibility API";
    } else if (!selected && selectedAccessibility) {
      message = "Element is not selected but selected via the accessibility API";
    }
    this.error(message, element);
  }

  /**
   * Throw an error if strict accessibility checks are enforced and log
   * the error to the log.
   *
   * @param {string} message
   * @param {DOMElement|XULElement} element
   *     Element that caused an error.
   *
   * @throws ElementNotAccessibleError
   *     If |strict| is true.
   */
  error(message, element) {
    if (!message || !this.strict) {
      return;
    }
    if (element) {
      let {id, tagName, className} = element;
      message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
    }

    throw new ElementNotAccessibleError(message);
  }

};
PK
!<iG+#chrome/marionette/content/action.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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-dupe-keys:off */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Task.jsm");

Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/element.js");
const {
  error,
  InvalidArgumentError,
  MoveTargetOutOfBoundsError,
  UnsupportedOperationError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/interaction.js");

this.EXPORTED_SYMBOLS = ["action"];

const {pprint} = error;

// TODO? With ES 2016 and Symbol you can make a safer approximation
// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
/**
 * Implements WebDriver Actions API: a low-level interface for providing
 * virtualised device input to the web browser.
 *
 * @namespace
 */
this.action = {
  Pause: "pause",
  KeyDown: "keyDown",
  KeyUp: "keyUp",
  PointerDown: "pointerDown",
  PointerUp: "pointerUp",
  PointerMove: "pointerMove",
  PointerCancel: "pointerCancel",
};

const ACTIONS = {
  none: new Set([action.Pause]),
  key: new Set([action.Pause, action.KeyDown, action.KeyUp]),
  pointer: new Set([
    action.Pause,
    action.PointerDown,
    action.PointerUp,
    action.PointerMove,
    action.PointerCancel,
  ]),
};

/** Map from normalized key value to UI Events modifier key name */
const MODIFIER_NAME_LOOKUP = {
  "Alt": "alt",
  "Shift": "shift",
  "Control": "ctrl",
  "Meta": "meta",
};

/** Map from raw key (codepoint) to normalized key value */
const NORMALIZED_KEY_LOOKUP = {
  "\uE000": "Unidentified",
  "\uE001": "Cancel",
  "\uE002": "Help",
  "\uE003": "Backspace",
  "\uE004": "Tab",
  "\uE005": "Clear",
  "\uE006": "Enter",
  "\uE007": "Enter",
  "\uE008": "Shift",
  "\uE009": "Control",
  "\uE00A": "Alt",
  "\uE00B": "Pause",
  "\uE00C": "Escape",
  "\uE00D": " ",
  "\uE00E": "PageUp",
  "\uE00F": "PageDown",
  "\uE010": "End",
  "\uE011": "Home",
  "\uE012": "ArrowLeft",
  "\uE013": "ArrowUp",
  "\uE014": "ArrowRight",
  "\uE015": "ArrowDown",
  "\uE016": "Insert",
  "\uE017": "Delete",
  "\uE018": ";",
  "\uE019": "=",
  "\uE01A": "0",
  "\uE01B": "1",
  "\uE01C": "2",
  "\uE01D": "3",
  "\uE01E": "4",
  "\uE01F": "5",
  "\uE020": "6",
  "\uE021": "7",
  "\uE022": "8",
  "\uE023": "9",
  "\uE024": "*",
  "\uE025": "+",
  "\uE026": ",",
  "\uE027": "-",
  "\uE028": ".",
  "\uE029": "/",
  "\uE031": "F1",
  "\uE032": "F2",
  "\uE033": "F3",
  "\uE034": "F4",
  "\uE035": "F5",
  "\uE036": "F6",
  "\uE037": "F7",
  "\uE038": "F8",
  "\uE039": "F9",
  "\uE03A": "F10",
  "\uE03B": "F11",
  "\uE03C": "F12",
  "\uE03D": "Meta",
  "\uE040": "ZenkakuHankaku",
  "\uE050": "Shift",
  "\uE051": "Control",
  "\uE052": "Alt",
  "\uE053": "Meta",
  "\uE054": "PageUp",
  "\uE055": "PageDown",
  "\uE056": "End",
  "\uE057": "Home",
  "\uE058": "ArrowLeft",
  "\uE059": "ArrowUp",
  "\uE05A": "ArrowRight",
  "\uE05B": "ArrowDown",
  "\uE05C": "Insert",
  "\uE05D": "Delete",
};

/** Map from raw key (codepoint) to key location */
const KEY_LOCATION_LOOKUP = {
  "\uE007": 1,
  "\uE008": 1,
  "\uE009": 1,
  "\uE00A": 1,
  "\uE01A": 3,
  "\uE01B": 3,
  "\uE01C": 3,
  "\uE01D": 3,
  "\uE01E": 3,
  "\uE01F": 3,
  "\uE020": 3,
  "\uE021": 3,
  "\uE022": 3,
  "\uE023": 3,
  "\uE024": 3,
  "\uE025": 3,
  "\uE026": 3,
  "\uE027": 3,
  "\uE028": 3,
  "\uE029": 3,
  "\uE03D": 1,
  "\uE050": 2,
  "\uE051": 2,
  "\uE052": 2,
  "\uE053": 2,
  "\uE054": 3,
  "\uE055": 3,
  "\uE056": 3,
  "\uE057": 3,
  "\uE058": 3,
  "\uE059": 3,
  "\uE05A": 3,
  "\uE05B": 3,
  "\uE05C": 3,
  "\uE05D": 3,
};

const KEY_CODE_LOOKUP = {
  "\uE00A": "AltLeft",
  "\uE052": "AltRight",
  "\uE015": "ArrowDown",
  "\uE012": "ArrowLeft",
  "\uE014": "ArrowRight",
  "\uE013": "ArrowUp",
  "`": "Backquote",
  "~": "Backquote",
  "\\": "Backslash",
  "|": "Backslash",
  "\uE003": "Backspace",
  "[": "BracketLeft",
  "{": "BracketLeft",
  "]": "BracketRight",
  "}": "BracketRight",
  ",": "Comma",
  "<": "Comma",
  "\uE009": "ControlLeft",
  "\uE051": "ControlRight",
  "\uE017": "Delete",
  ")": "Digit0",
  "0": "Digit0",
  "!": "Digit1",
  "1": "Digit1",
  "2": "Digit2",
  "@": "Digit2",
  "#": "Digit3",
  "3": "Digit3",
  "$": "Digit4",
  "4": "Digit4",
  "%": "Digit5",
  "5": "Digit5",
  "6": "Digit6",
  "^": "Digit6",
  "&": "Digit7",
  "7": "Digit7",
  "*": "Digit8",
  "8": "Digit8",
  "(": "Digit9",
  "9": "Digit9",
  "\uE010": "End",
  "\uE006": "Enter",
  "+": "Equal",
  "=": "Equal",
  "\uE00C": "Escape",
  "\uE031": "F1",
  "\uE03A": "F10",
  "\uE03B": "F11",
  "\uE03C": "F12",
  "\uE032": "F2",
  "\uE033": "F3",
  "\uE034": "F4",
  "\uE035": "F5",
  "\uE036": "F6",
  "\uE037": "F7",
  "\uE038": "F8",
  "\uE039": "F9",
  "\uE002": "Help",
  "\uE011": "Home",
  "\uE016": "Insert",
  "<": "IntlBackslash",
  ">": "IntlBackslash",
  "A": "KeyA",
  "a": "KeyA",
  "B": "KeyB",
  "b": "KeyB",
  "C": "KeyC",
  "c": "KeyC",
  "D": "KeyD",
  "d": "KeyD",
  "E": "KeyE",
  "e": "KeyE",
  "F": "KeyF",
  "f": "KeyF",
  "G": "KeyG",
  "g": "KeyG",
  "H": "KeyH",
  "h": "KeyH",
  "I": "KeyI",
  "i": "KeyI",
  "J": "KeyJ",
  "j": "KeyJ",
  "K": "KeyK",
  "k": "KeyK",
  "L": "KeyL",
  "l": "KeyL",
  "M": "KeyM",
  "m": "KeyM",
  "N": "KeyN",
  "n": "KeyN",
  "O": "KeyO",
  "o": "KeyO",
  "P": "KeyP",
  "p": "KeyP",
  "Q": "KeyQ",
  "q": "KeyQ",
  "R": "KeyR",
  "r": "KeyR",
  "S": "KeyS",
  "s": "KeyS",
  "T": "KeyT",
  "t": "KeyT",
  "U": "KeyU",
  "u": "KeyU",
  "V": "KeyV",
  "v": "KeyV",
  "W": "KeyW",
  "w": "KeyW",
  "X": "KeyX",
  "x": "KeyX",
  "Y": "KeyY",
  "y": "KeyY",
  "Z": "KeyZ",
  "z": "KeyZ",
  "-": "Minus",
  "_": "Minus",
  "\uE01A": "Numpad0",
  "\uE05C": "Numpad0",
  "\uE01B": "Numpad1",
  "\uE056": "Numpad1",
  "\uE01C": "Numpad2",
  "\uE05B": "Numpad2",
  "\uE01D": "Numpad3",
  "\uE055": "Numpad3",
  "\uE01E": "Numpad4",
  "\uE058": "Numpad4",
  "\uE01F": "Numpad5",
  "\uE020": "Numpad6",
  "\uE05A": "Numpad6",
  "\uE021": "Numpad7",
  "\uE057": "Numpad7",
  "\uE022": "Numpad8",
  "\uE059": "Numpad8",
  "\uE023": "Numpad9",
  "\uE054": "Numpad9",
  "\uE024": "NumpadAdd",
  "\uE026": "NumpadComma",
  "\uE028": "NumpadDecimal",
  "\uE05D": "NumpadDecimal",
  "\uE029": "NumpadDivide",
  "\uE007": "NumpadEnter",
  "\uE024": "NumpadMultiply",
  "\uE026": "NumpadSubtract",
  "\uE03D": "OSLeft",
  "\uE053": "OSRight",
  "\uE01E": "PageDown",
  "\uE01F": "PageUp",
  ".": "Period",
  ">": "Period",
  "\"": "Quote",
  "'": "Quote",
  ":": "Semicolon",
  ";": "Semicolon",
  "\uE008": "ShiftLeft",
  "\uE050": "ShiftRight",
  "/": "Slash",
  "?": "Slash",
  "\uE00D": "Space",
  "  ": "Space",
  "\uE004": "Tab",
};

/** Represents possible values for a pointer-move origin. */
action.PointerOrigin = {
  Viewport: "viewport",
  Pointer: "pointer",
};

/**
 * Look up a PointerOrigin.
 *
 * @param {(undefined|string|WebElement)} obj
 *     Origin for a pointerMove action.
 *
 * @return {action.PointerOrigin}
 *     A pointer origin that is either "viewport" (default), "pointer", or a
 *     web-element reference.
 *
 * @throws {InvalidArgumentError}
 *     If <code>obj</code> is not a valid origin.
 */
action.PointerOrigin.get = function(obj) {
  let origin = obj;
  if (typeof obj == "undefined") {
    origin = this.Viewport;
  } else if (typeof obj == "string") {
    let name = capitalize(obj);
    assert.in(name, this, pprint`Unknown pointer-move origin: ${obj}`);
    origin = this[name];
  } else if (!element.isWebElementReference(obj)) {
    throw new InvalidArgumentError("Expected 'origin' to be a string or a " +
      `web element reference, got: ${obj}`);
  }
  return origin;
};

/** Represents possible subtypes for a pointer input source. */
action.PointerType = {
  Mouse: "mouse",
  // TODO For now, only mouse is supported
  //Pen: "pen",
  //Touch: "touch",
};

/**
 * Look up a PointerType.
 *
 * @param {string} str
 *     Name of pointer type.
 *
 * @return {string}
 *     A pointer type for processing pointer parameters.
 *
 * @throws {InvalidArgumentError}
 *     If <code>str</code> is not a valid pointer type.
 */
action.PointerType.get = function(str) {
  let name = capitalize(str);
  assert.in(name, this, pprint`Unknown pointerType: ${str}`);
  return this[name];
};

/**
 * Input state associated with current session.  This is a map between
 * input ID and the device state for that input source, with one entry
 * for each active input source.
 *
 * Initialized in listener.js.
 */
action.inputStateMap = undefined;

/**
 * List of {@link action.Action} associated with current session.  Used to
 * manage dispatching events when resetting the state of the input sources.
 * Reset operations are assumed to be idempotent.
 *
 * Initialized in listener.js
 */
action.inputsToCancel = undefined;

/**
 * Represents device state for an input source.
 */
class InputState {
  constructor() {
    this.type = this.constructor.name.toLowerCase();
  }

  /**
   * Check equality of this InputState object with another.
   *
   * @param {InputState} other
   *     Object representing an input state.
   *
   * @return {boolean}
   *     True if <code>this</code> has the same <code>type</code>
   *     as <code>other</code>.
   */
  is(other) {
    if (typeof other == "undefined") {
      return false;
    }
    return this.type === other.type;
  }

  toString() {
    return `[object ${this.constructor.name}InputState]`;
  }

  /**
   * @param {Object.<string, ?>} obj
   *     Object with property <code>type</code> and optionally
   *     <code>parameters</code> or <code>pointerType</code>,
   *     representing an action sequence or an action item.
   *
   * @return {action.InputState}
   *     An {@link InputState} object for the type of the
   *     {@link actionSequence}.
   *
   * @throws {InvalidArgumentError}
   *     If {@link actionSequence.type} is not valid.
   */
  static fromJson(obj) {
    let type = obj.type;
    assert.in(type, ACTIONS, pprint`Unknown action type: ${type}`);
    let name = type == "none" ? "Null" : capitalize(type);
    if (name == "Pointer") {
      if (!obj.pointerType &&
          (!obj.parameters || !obj.parameters.pointerType)) {
        throw new InvalidArgumentError(
            pprint`Expected obj to have pointerType, got: ${obj}`);
      }
      let pointerType = obj.pointerType || obj.parameters.pointerType;
      return new action.InputState[name](pointerType);
    }
    return new action.InputState[name]();
  }
}

/** Possible kinds of |InputState| for supported input sources. */
action.InputState = {};

/**
 * Input state associated with a keyboard-type device.
 */
action.InputState.Key = class Key extends InputState {
  constructor() {
    super();
    this.pressed = new Set();
    this.alt = false;
    this.shift = false;
    this.ctrl = false;
    this.meta = false;
  }

  /**
   * Update modifier state according to |key|.
   *
   * @param {string} key
   *     Normalized key value of a modifier key.
   * @param {boolean} value
   *     Value to set the modifier attribute to.
   *
   * @throws {InvalidArgumentError}
   *     If |key| is not a modifier.
   */
  setModState(key, value) {
    if (key in MODIFIER_NAME_LOOKUP) {
      this[MODIFIER_NAME_LOOKUP[key]] = value;
    } else {
      throw new InvalidArgumentError("Expected 'key' to be one of " +
          `${Object.keys(MODIFIER_NAME_LOOKUP)}; got: ${key}`);
    }
  }

  /**
   * Check whether |key| is pressed.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @return {boolean}
   *     True if |key| is in set of pressed keys.
   */
  isPressed(key) {
    return this.pressed.has(key);
  }

  /**
   * Add |key| to the set of pressed keys.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @return {boolean}
   *     True if |key| is in list of pressed keys.
   */
  press(key) {
    return this.pressed.add(key);
  }

  /**
   * Remove |key| from the set of pressed keys.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @return {boolean}
   *     True if |key| was present before removal, false otherwise.
   */
  release(key) {
    return this.pressed.delete(key);
  }
};

/**
 * Input state not associated with a specific physical device.
 */
action.InputState.Null = class Null extends InputState {
  constructor() {
    super();
    this.type = "none";
  }
};

/**
 * Input state associated with a pointer-type input device.
 *
 * @param {string} subtype
 *     Kind of pointing device: mouse, pen, touch.
 *
 * @throws {InvalidArgumentError}
 *     If subtype is undefined or an invalid pointer type.
 */
action.InputState.Pointer = class Pointer extends InputState {
  constructor(subtype) {
    super();
    this.pressed = new Set();
    assert.defined(subtype,
        pprint`Expected subtype to be defined, got: ${subtype}`);
    this.subtype = action.PointerType.get(subtype);
    this.x = 0;
    this.y = 0;
  }

  /**
   * Check whether |button| is pressed.
   *
   * @param {number} button
   *     Positive integer that refers to a mouse button.
   *
   * @return {boolean}
   *     True if |button| is in set of pressed buttons.
   */
  isPressed(button) {
    assert.positiveInteger(button);
    return this.pressed.has(button);
  }

  /**
   * Add |button| to the set of pressed keys.
   *
   * @param {number} button
   *     Positive integer that refers to a mouse button.
   *
   * @return {Set}
   *     Set of pressed buttons.
   */
  press(button) {
    assert.positiveInteger(button);
    return this.pressed.add(button);
  }

   /**
   * Remove |button| from the set of pressed buttons.
   *
   * @param {number} button
   *     A positive integer that refers to a mouse button.
   *
   * @return {boolean}
   *     True if |button| was present before removals, false otherwise.
   */
  release(button) {
    assert.positiveInteger(button);
    return this.pressed.delete(button);
  }
};

/**
 * Repesents an action for dispatch. Used in |action.Chain| and
 * |action.Sequence|.
 *
 * @param {string} id
 *     Input source ID.
 * @param {string} type
 *     Action type: none, key, pointer.
 * @param {string} subtype
 *     Action subtype: {@link action.Pause}, {@link action.KeyUp},
 *     {@link action.KeyDown}, {@link action.PointerUp},
 *     {@link action.PointerDown}, {@link action.PointerMove}, or
 *     {@link action.PointerCancel}.
 *
 * @throws {InvalidArgumentError}
 *      If any parameters are undefined.
 */
action.Action = class {
  constructor(id, type, subtype) {
    if ([id, type, subtype].includes(undefined)) {
      throw new InvalidArgumentError("Missing id, type or subtype");
    }
    for (let attr of [id, type, subtype]) {
      assert.string(attr, pprint`Expected string, got: ${attr}`);
    }
    this.id = id;
    this.type = type;
    this.subtype = subtype;
  }

  toString() {
    return `[action ${this.type}]`;
  }

  /**
   * @param {action.Sequence} actionSequence
   *     Object representing sequence of actions from one input source.
   * @param {action.Action} actionItem
   *     Object representing a single action from |actionSequence|.
   *
   * @return {action.Action}
   *     An action that can be dispatched; corresponds to |actionItem|.
   *
   * @throws {InvalidArgumentError}
   *     If any <code>actionSequence</code> or <code>actionItem</code>
   *     attributes are invalid.
   * @throws {UnsupportedOperationError}
   *     If <code>actionItem.type</code> is {@link action.PointerCancel}.
   */
  static fromJson(actionSequence, actionItem) {
    let type = actionSequence.type;
    let id = actionSequence.id;
    let subtypes = ACTIONS[type];
    if (!subtypes) {
      throw new InvalidArgumentError("Unknown type: " + type);
    }
    let subtype = actionItem.type;
    if (!subtypes.has(subtype)) {
      throw new InvalidArgumentError(
          `Unknown subtype for ${type} action: ${subtype}`);
    }

    let item = new action.Action(id, type, subtype);
    if (type === "pointer") {
      action.processPointerAction(id,
          action.PointerParameters.fromJson(actionSequence.parameters), item);
    }

    switch (item.subtype) {
      case action.KeyUp:
      case action.KeyDown:
        let key = actionItem.value;
        // TODO countGraphemes
        // TODO key.value could be a single code point like "\uE012"
        // (see rawKey) or "grapheme cluster"
        assert.string(key,
            pprint("Expected 'value' to be a string that represents single code point " +
                `or grapheme cluster, got: ${key}`));
        item.value = key;
        break;

      case action.PointerDown:
      case action.PointerUp:
        assert.positiveInteger(actionItem.button,
            pprint`Expected 'button' (${actionItem.button}) to be >= 0`);
        item.button = actionItem.button;
        break;

      case action.PointerMove:
        item.duration = actionItem.duration;
        if (typeof item.duration != "undefined") {
          assert.positiveInteger(item.duration,
              pprint`Expected 'duration' (${item.duration}) to be >= 0`);
        }
        item.origin = action.PointerOrigin.get(actionItem.origin);
        item.x = actionItem.x;
        if (typeof item.x != "undefined") {
          assert.integer(item.x,
              pprint`Expected 'x' (${item.x}) to be an Integer`);
        }
        item.y = actionItem.y;
        if (typeof item.y != "undefined") {
          assert.integer(item.y,
              pprint`Expected 'y' (${item.y}) to be an Integer`);
        }
        break;

      case action.PointerCancel:
        throw new UnsupportedOperationError();

      case action.Pause:
        item.duration = actionItem.duration;
        if (typeof item.duration != "undefined") {
          // eslint-disable-next-line
          assert.positiveInteger(item.duration,
              pprint`Expected 'duration' (${item.duration}) to be >= 0`);
        }
        break;
    }

    return item;
  }
};

/**
 * Represents a series of ticks, specifying which actions to perform at
 * each tick.
 */
action.Chain = class extends Array {
  toString() {
    return `[chain ${super.toString()}]`;
  }

  /**
   * @param {Array.<?>} actions
   *     Array of objects that each represent an action sequence.
   *
   * @return {action.Chain}
   *     Transpose of |actions| such that actions to be performed in a
   *     single tick are grouped together.
   *
   * @throws {InvalidArgumentError}
   *     If |actions| is not an Array.
   */
  static fromJson(actions) {
    assert.array(actions,
        pprint`Expected 'actions' to be an Array, got: ${actions}`);
    let actionsByTick = new action.Chain();
    //  TODO check that each actionSequence in actions refers to a
    // different input ID
    for (let actionSequence of actions) {
      let inputSourceActions = action.Sequence.fromJson(actionSequence);
      for (let i = 0; i < inputSourceActions.length; i++) {
        // new tick
        if (actionsByTick.length < (i + 1)) {
          actionsByTick.push([]);
        }
        actionsByTick[i].push(inputSourceActions[i]);
      }
    }
    return actionsByTick;
  }
};

/**
 * Represents one input source action sequence; this is essentially an
 * |Array.<action.Action>|.
 */
action.Sequence = class extends Array {
  toString() {
    return `[sequence ${super.toString()}]`;
  }

  /**
   * @param {Object.<string, ?>} actionSequence
   *     Object that represents a sequence action items for one input source.
   *
   * @return {action.Sequence}
   *     Sequence of actions that can be dispatched.
   *
   * @throws {InvalidArgumentError}
   *     If |actionSequence.id| is not a string or it's aleady mapped
   *     to an |action.InputState} incompatible with |actionSequence.type|.
   *     If |actionSequence.actions| is not an Array.
   */
  static fromJson(actionSequence) {
    // used here to validate 'type' in addition to InputState type below
    let inputSourceState = InputState.fromJson(actionSequence);
    let id = actionSequence.id;
    assert.defined(id, "Expected 'id' to be defined");
    assert.string(id, pprint`Expected 'id' to be a string, got: ${id}`);
    let actionItems = actionSequence.actions;
    assert.array(actionItems,
        pprint("Expected 'actionSequence.actions' to be an Array, " +
            `got: ${actionSequence.actions}`));
    if (!action.inputStateMap.has(id)) {
      action.inputStateMap.set(id, inputSourceState);
    } else if (!action.inputStateMap.get(id).is(inputSourceState)) {
      throw new InvalidArgumentError(
          `Expected ${id} to be mapped to ${inputSourceState}, ` +
          `got: ${action.inputStateMap.get(id)}`);
    }
    let actions = new action.Sequence();
    for (let actionItem of actionItems) {
      actions.push(action.Action.fromJson(actionSequence, actionItem));
    }
    return actions;
  }
};

/**
 * Represents parameters in an action for a pointer input source.
 *
 * @param {string=} pointerType
 *     Type of pointing device.  If the parameter is undefined, "mouse"
 *     is used.
 */
action.PointerParameters = class {
  constructor(pointerType = "mouse") {
    this.pointerType = action.PointerType.get(pointerType);
  }

  toString() {
    return `[pointerParameters ${this.pointerType}]`;
  }

  /**
   * @param {Object.<string, ?>} parametersData
   *     Object that represents pointer parameters.
   *
   * @return {action.PointerParameters}
   *     Validated pointer paramters.
   */
  static fromJson(parametersData) {
    if (typeof parametersData == "undefined") {
      return new action.PointerParameters();
    }
    return new action.PointerParameters(parametersData.pointerType);
  }
};

/**
 * Adds |pointerType| attribute to Action |act|. Helper function
 * for |action.Action.fromJson|.
 *
 * @param {string} id
 *     Input source ID.
 * @param {action.PointerParams} pointerParams
 *     Input source pointer parameters.
 * @param {action.Action} act
 *     Action to be updated.
 *
 * @throws {InvalidArgumentError}
 *     If |id| is already mapped to an |action.InputState| that is
 *     not compatible with |act.type| or |pointerParams.pointerType|.
 */
action.processPointerAction = function(id, pointerParams, act) {
  if (action.inputStateMap.has(id) &&
      action.inputStateMap.get(id).type !== act.type) {
    throw new InvalidArgumentError(
        `Expected 'id' ${id} to be mapped to InputState whose type is ` +
        `${action.inputStateMap.get(id).type}, got: ${act.type}`);
  }
  let pointerType = pointerParams.pointerType;
  if (action.inputStateMap.has(id) &&
      action.inputStateMap.get(id).subtype !== pointerType) {
    throw new InvalidArgumentError(
        `Expected 'id' ${id} to be mapped to InputState whose subtype is ` +
        `${action.inputStateMap.get(id).subtype}, got: ${pointerType}`);
  }
  act.pointerType = pointerParams.pointerType;
};

/** Collect properties associated with KeyboardEvent */
action.Key = class {
  constructor(rawKey) {
    this.key = NORMALIZED_KEY_LOOKUP[rawKey] || rawKey;
    this.code =  KEY_CODE_LOOKUP[rawKey];
    this.location = KEY_LOCATION_LOOKUP[rawKey] || 0;
    this.altKey = false;
    this.shiftKey = false;
    this.ctrlKey = false;
    this.metaKey = false;
    this.repeat = false;
    this.isComposing = false;
    // keyCode will be computed by event.sendKeyDown
  }

  update(inputState) {
    this.altKey = inputState.alt;
    this.shiftKey = inputState.shift;
    this.ctrlKey = inputState.ctrl;
    this.metaKey = inputState.meta;
  }
};

/** Collect properties associated with MouseEvent */
action.Mouse = class {
  constructor(type, button = 0) {
    this.type = type;
    assert.positiveInteger(button);
    this.button = button;
    this.buttons = 0;
    this.altKey = false;
    this.shiftKey = false;
    this.metaKey = false;
    this.ctrlKey = false;
    // set modifier properties based on whether any corresponding keys are
    // pressed on any key input source
    for (let inputState of action.inputStateMap.values()) {
      if (inputState.type == "key") {
        this.altKey = inputState.alt || this.altKey;
        this.ctrlKey = inputState.ctrl || this.ctrlKey;
        this.metaKey = inputState.meta || this.metaKey;
        this.shiftKey = inputState.shift || this.shiftKey;
      }
    }
  }

  update(inputState) {
    let allButtons = Array.from(inputState.pressed);
    this.buttons = allButtons.reduce((a, i) => a + Math.pow(2, i), 0);
  }
};

/**
 * Dispatch a chain of actions over |chain.length| ticks.
 *
 * This is done by creating a Promise for each tick that resolves once
 * all the Promises for individual tick-actions are resolved.  The next
 * tick's actions are not dispatched until the Promise for the current
 * tick is resolved.
 *
 * @param {action.Chain} chain
 *     Actions grouped by tick; each element in |chain| is a sequence of
 *     actions for one tick.
 * @param {element.Store} seenEls
 *     Element store.
 * @param {Object.<string, nsIDOMWindow>} container
 *     Object with <code>frame</code> property of type
 *     <code>nsIDOMWindow</code>.
 *
 * @return {Promise}
 *     Promise for dispatching all actions in |chain|.
 */
action.dispatch = function(chain, seenEls, container) {
  let chainEvents = Task.spawn(function*() {
    for (let tickActions of chain) {
      yield action.dispatchTickActions(
          tickActions,
          action.computeTickDuration(tickActions),
          seenEls,
          container);
    }
  });
  return chainEvents;
};

/**
 * Dispatch sequence of actions for one tick.
 *
 * This creates a Promise for one tick that resolves once the Promise
 * for each tick-action is resolved, which takes at least |tickDuration|
 * milliseconds.  The resolved set of events for each tick is followed by
 * firing of pending DOM events.
 *
 * Note that the tick-actions are dispatched in order, but they may have
 * different durations and therefore may not end in the same order.
 *
 * @param {Array.<action.Action>} tickActions
 *     List of actions for one tick.
 * @param {number} tickDuration
 *     Duration in milliseconds of this tick.
 * @param {element.Store} seenEls
 *     Element store.
 * @param {Object.<string, nsIDOMWindow>} container
 *     Object with <code>frame</code> property of type
 *     <code>nsIDOMWindow</code>.
 *
 * @return {Promise}
 *     Promise for dispatching all tick-actions and pending DOM events.
 */
action.dispatchTickActions = function(
    tickActions, tickDuration, seenEls, container) {
  let pendingEvents = tickActions.map(
      toEvents(tickDuration, seenEls, container));
  return Promise.all(pendingEvents).then(
      () => interaction.flushEventLoop(container.frame));
};

/**
 * Compute tick duration in milliseconds for a collection of actions.
 *
 * @param {Array.<action.Action>} tickActions
 *     List of actions for one tick.
 *
 * @return {number}
 *     Longest action duration in |tickActions| if any, or 0.
 */
action.computeTickDuration = function(tickActions) {
  let max = 0;
  for (let a of tickActions) {
    let affectsWallClockTime = a.subtype == action.Pause ||
        (a.type == "pointer" && a.subtype == action.PointerMove);
    if (affectsWallClockTime && a.duration) {
      max = Math.max(a.duration, max);
    }
  }
  return max;
};

/**
 * Compute viewport coordinates of pointer target based on given origin.
 *
 * @param {action.Action} a
 *     Action that specifies pointer origin and x and y coordinates of target.
 * @param {action.InputState} inputState
 *     Input state that specifies current x and y coordinates of pointer.
 * @param {Map.<string, number>=} center
 *     Object representing x and y coordinates of an element center-point.
 *     This is only used if |a.origin| is a web element reference.
 *
 * @return {Map.<string, number>}
 *     x and y coordinates of pointer destination.
 */
action.computePointerDestination = function(
    a, inputState, center = undefined) {
  let {x, y} = a;
  switch (a.origin) {
    case action.PointerOrigin.Viewport:
      break;
    case action.PointerOrigin.Pointer:
      x += inputState.x;
      y += inputState.y;
      break;
    default:
      // origin represents web element
      assert.defined(center);
      assert.in("x", center);
      assert.in("y", center);
      x += center.x;
      y += center.y;
  }
  return {"x": x, "y": y};
};

/**
 * Create a closure to use as a map from action definitions to Promise events.
 *
 * @param {number} tickDuration
 *     Duration in milliseconds of this tick.
 * @param {element.Store} seenEls
 *     Element store.
 * @param {Object.<string, nsIDOMWindow>} container
 *     Object with <code>frame</code> property of type
 *     <code>nsIDOMWindow</code>.
 *
 * @return {function(action.Action): Promise}
 *     Function that takes an action and returns a Promise for dispatching
 *     the event that corresponds to that action.
 */
function toEvents(tickDuration, seenEls, container) {
  return a => {
    let inputState = action.inputStateMap.get(a.id);
    switch (a.subtype) {
      case action.KeyUp:
        return dispatchKeyUp(a, inputState, container.frame);

      case action.KeyDown:
        return dispatchKeyDown(a, inputState, container.frame);

      case action.PointerDown:
        return dispatchPointerDown(a, inputState, container.frame);

      case action.PointerUp:
        return dispatchPointerUp(a, inputState, container.frame);

      case action.PointerMove:
        return dispatchPointerMove(
            a, inputState, tickDuration, seenEls, container);

      case action.PointerCancel:
        throw new UnsupportedOperationError();

      case action.Pause:
        return dispatchPause(a, tickDuration);
    }
    return undefined;
  };
}

/**
 * Dispatch a keyDown action equivalent to pressing a key on a keyboard.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {action.InputState} inputState
 *     Input state for this action's input source.
 * @param {nsIDOMWindow} win
 *     Current window.
 *
 * @return {Promise}
 *     Promise to dispatch at least a keydown event, and keypress if
 *     appropriate.
 */
function dispatchKeyDown(a, inputState, win) {
  return new Promise(resolve => {
    let keyEvent = new action.Key(a.value);
    keyEvent.repeat = inputState.isPressed(keyEvent.key);
    inputState.press(keyEvent.key);
    if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
      inputState.setModState(keyEvent.key, true);
    }
    // Append a copy of |a| with keyUp subtype
    action.inputsToCancel.push(Object.assign({}, a, {subtype: action.KeyUp}));
    keyEvent.update(inputState);
    event.sendKeyDown(a.value, keyEvent, win);

    resolve();
  });
}

/**
 * Dispatch a keyUp action equivalent to releasing a key on a keyboard.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {action.InputState} inputState
 *     Input state for this action's input source.
 * @param {nsIDOMWindow} win
 *     Current window.
 *
 * @return {Promise}
 *     Promise to dispatch a keyup event.
 */
function dispatchKeyUp(a, inputState, win) {
  return new Promise(resolve => {
    let keyEvent = new action.Key(a.value);
    if (!inputState.isPressed(keyEvent.key)) {
      resolve();
      return;
    }
    if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
      inputState.setModState(keyEvent.key, false);
    }
    inputState.release(keyEvent.key);
    keyEvent.update(inputState);
    event.sendKeyUp(a.value, keyEvent, win);

    resolve();
  });
}

/**
 * Dispatch a pointerDown action equivalent to pressing a pointer-device
 * button.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {action.InputState} inputState
 *     Input state for this action's input source.
 * @param {nsIDOMWindow} win
 *     Current window.
 *
 * @return {Promise}
 *     Promise to dispatch at least a pointerdown event.
 */
function dispatchPointerDown(a, inputState, win) {
  return new Promise(resolve => {
    if (inputState.isPressed(a.button)) {
      resolve();
      return;
    }

    inputState.press(a.button);
    // Append a copy of |a| with pointerUp subtype
    let copy = Object.assign({}, a, {subtype: action.PointerUp});
    action.inputsToCancel.push(copy);

    switch (inputState.subtype) {
      case action.PointerType.Mouse:
        let mouseEvent = new action.Mouse("mousedown", a.button);
        mouseEvent.update(inputState);
        event.synthesizeMouseAtPoint(
            inputState.x,
            inputState.y,
            mouseEvent,
            win);
        if (event.MouseButton.isSecondary(a.button)) {
          let contextMenuEvent = Object.assign({},
              mouseEvent, {type: "contextmenu"});
          event.synthesizeMouseAtPoint(
              inputState.x,
              inputState.y,
              contextMenuEvent,
              win);
        }
        break;

      case action.PointerType.Pen:
      case action.PointerType.Touch:
        throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");

      default:
        throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
    }

    resolve();
  });
}

/**
 * Dispatch a pointerUp action equivalent to releasing a pointer-device
 * button.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {action.InputState} inputState
 *     Input state for this action's input source.
 * @param {nsIDOMWindow} win
 *     Current window.
 *
 * @return {Promise}
 *     Promise to dispatch at least a pointerup event.
 */
function dispatchPointerUp(a, inputState, win) {
  return new Promise(resolve => {
    if (!inputState.isPressed(a.button)) {
      resolve();
      return;
    }
    inputState.release(a.button);
    switch (inputState.subtype) {
      case action.PointerType.Mouse:
        let mouseEvent = new action.Mouse("mouseup", a.button);
        mouseEvent.update(inputState);
        event.synthesizeMouseAtPoint(inputState.x, inputState.y,
            mouseEvent, win);
        break;
      case action.PointerType.Pen:
      case action.PointerType.Touch:
        throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
      default:
        throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
    }
    resolve();
  });
}

/**
 * Dispatch a pointerMove action equivalent to moving pointer device in
 * a line.
 *
 * If the action duration is 0, the pointer jumps immediately to the
 * target coordinates.  Otherwise, events are synthesized to mimic a
 * pointer travelling in a discontinuous, approximately straight line,
 * with the pointer coordinates being updated around 60 times per second.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {action.InputState} inputState
 *     Input state for this action's input source.
 * @param {element.Store} seenEls
 *     Element store.
 * @param {Object.<string, nsIDOMWindow>} container
 *     Object with <code>frame</code> property of type
 *     <code>nsIDOMWindow</code>.
 *
 * @return {Promise}
 *     Promise to dispatch at least one pointermove event, as well as
 *     mousemove events as appropriate.
 */
function dispatchPointerMove(
    a, inputState, tickDuration, seenEls, container) {
  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  // interval between pointermove increments in ms, based on common vsync
  const fps60 = 17;
  return new Promise(resolve => {
    const start = Date.now();
    const [startX, startY] = [inputState.x, inputState.y];
    let target = action.computePointerDestination(a, inputState,
        getElementCenter(a.origin, seenEls, container));
    const [targetX, targetY] = [target.x, target.y];
    if (!inViewPort(targetX, targetY, container.frame)) {
      throw new MoveTargetOutOfBoundsError(
          `(${targetX}, ${targetY}) is out of bounds of viewport ` +
          `width (${container.frame.innerWidth}) ` +
          `and height (${container.frame.innerHeight})`);
    }

    const duration = typeof a.duration == "undefined" ? tickDuration : a.duration;
    if (duration === 0) {
      // move pointer to destination in one step
      performOnePointerMove(inputState, targetX, targetY, container.frame);
      resolve();
      return;
    }

    const distanceX = targetX - startX;
    const distanceY = targetY - startY;
    const ONE_SHOT = Ci.nsITimer.TYPE_ONE_SHOT;
    let intermediatePointerEvents = Task.spawn(function* () {
      // wait |fps60| ms before performing first incremental pointer move
      yield new Promise(resolveTimer =>
          timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)
      );
      let durationRatio = Math.floor(Date.now() - start) / duration;
      const epsilon = fps60 / duration / 10;
      while ((1 - durationRatio) > epsilon) {
        let x = Math.floor(durationRatio * distanceX + startX);
        let y = Math.floor(durationRatio * distanceY + startY);
        performOnePointerMove(inputState, x, y, container.frame);
        // wait |fps60| ms before performing next pointer move
        yield new Promise(resolveTimer =>
            timer.initWithCallback(resolveTimer, fps60, ONE_SHOT));
        durationRatio = Math.floor(Date.now() - start) / duration;
      }
    });
    // perform last pointer move after all incremental moves are resolved and
    // durationRatio is close enough to 1
    intermediatePointerEvents.then(() => {
      performOnePointerMove(inputState, targetX, targetY, container.frame);
      resolve();
    });

  });
}

function performOnePointerMove(inputState, targetX, targetY, win) {
  if (targetX == inputState.x && targetY == inputState.y) {
    return;
  }

  switch (inputState.subtype) {
    case action.PointerType.Mouse:
      let mouseEvent = new action.Mouse("mousemove");
      mouseEvent.update(inputState);
      // TODO both pointermove (if available) and mousemove
      event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win);
      break;

    case action.PointerType.Pen:
    case action.PointerType.Touch:
      throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");

    default:
      throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
  }

  inputState.x = targetX;
  inputState.y = targetY;
}

/**
 * Dispatch a pause action equivalent waiting for |a.duration|
 * milliseconds, or a default time interval of |tickDuration|.
 *
 * @param {action.Action} a
 *     Action to dispatch.
 * @param {number} tickDuration
 *     Duration in milliseconds of this tick.
 *
 * @return {Promise}
 *     Promise that is resolved after the specified time interval.
 */
function dispatchPause(a, tickDuration) {
  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let duration = typeof a.duration == "undefined" ? tickDuration : a.duration;
  return new Promise(resolve =>
      timer.initWithCallback(resolve, duration, Ci.nsITimer.TYPE_ONE_SHOT)
  );
}

// helpers

function capitalize(str) {
  assert.string(str);
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function inViewPort(x, y, win) {
  assert.number(x, `Expected x to be finite number`);
  assert.number(y, `Expected y to be finite number`);
  // Viewport includes scrollbars if rendered.
  return !(x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight);
}

function getElementCenter(elementReference, seenEls, container) {
  if (element.isWebElementReference(elementReference)) {
    let uuid = elementReference[element.Key] ||
        elementReference[element.LegacyKey];
    let el = seenEls.get(uuid, container);
    return element.coordinates(el);
  }
  return {};
}
PK
!<E"chrome/marionette/content/addon.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/AddonManager.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");

const {UnknownError} = Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["addon"];

/** @namespace */
this.addon = {};

// from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors
addon.Errors = {
  [-1]: "ERROR_NETWORK_FAILURE: A network error occured.",
  [-2]: "ERROR_INCORECT_HASH: The downloaded file did not match the expected hash.",
  [-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.",
  [-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.",
  [-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
};

function lookupError(code) {
  let msg = addon.Errors[code];
  return new UnknownError(msg);
}

/**
 * Install a Firefox addon.
 *
 * If the addon is restartless, it can be used right away.  Otherwise a
 * restart is required.
 *
 * Temporary addons will automatically be uninstalled on shutdown and
 * do not need to be signed, though they must be restartless.
 *
 * @param {string} path
 *     Full path to the extension package archive.
 * @param {boolean=} temporary
 *     True to install the addon temporarily, false (default) otherwise.
 *
 * @return {Promise.<string>}
 *     Addon ID.
 *
 * @throws {UnknownError}
 *     If there is a problem installing the addon.
 */
addon.install = function(path, temporary = false) {
  return new Promise((resolve, reject) => {
    let file = new FileUtils.File(path);

    let listener = {
      onInstallEnded(install, addon) {
        resolve(addon.id);
      },

      onInstallFailed(install) {
        reject(lookupError(install.error));
      },

      onInstalled(addon) {
        AddonManager.removeAddonListener(listener);
        resolve(addon.id);
      },
    };

    if (!temporary) {
      AddonManager.getInstallForFile(file, function(aInstall) {
        if (aInstall.error !== 0) {
          reject(lookupError(aInstall.error));
        }
        aInstall.addListener(listener);
        aInstall.install();
      });
    } else {
      AddonManager.addAddonListener(listener);
      AddonManager.installTemporaryAddon(file);
    }
  });
};

/**
 * Uninstall a Firefox addon.
 *
 * If the addon is restartless it will be uninstalled right away.
 * Otherwise, Firefox must be restarted for the change to take effect.
 *
 * @param {string} id
 *     ID of the addon to uninstall.
 *
 * @return {Promise}
 */
addon.uninstall = function(id) {
  return new Promise(resolve => {
    AddonManager.getAddonByID(id, function(addon) {
      addon.uninstall();
      resolve();
    });
  });
};
PK
!<'%%#chrome/marionette/content/assert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/AppConstants.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const {
  error,
  InvalidArgumentError,
  InvalidSessionIDError,
  NoSuchWindowError,
  UnexpectedAlertOpenError,
  UnsupportedOperationError,
} = Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["assert"];

const isFennec = () => AppConstants.platform == "android";
const isFirefox = () => Services.appinfo.name == "Firefox";

/**
 * Shorthands for common assertions made in Marionette.
 *
 * @namespace
 */
this.assert = {};

/**
 * Asserts that Marionette has a session.
 *
 * @param {GeckoDriver} driver
 *     Marionette driver instance.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {string}
 *     Session ID.
 *
 * @throws {InvalidSessionIDError}
 *     If |driver| does not have a session ID.
 */
assert.session = function(driver, msg = "") {
  assert.that(sessionID => sessionID,
      msg, InvalidSessionIDError)(driver.sessionId);
  return driver.sessionId;
};

/**
 * Asserts that the current browser is Firefox Desktop.
 *
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnsupportedOperationError}
 *     If current browser is not Firefox.
 */
assert.firefox = function(msg = "") {
  msg = msg || "Only supported in Firefox";
  assert.that(isFirefox, msg, UnsupportedOperationError)();
};

/**
 * Asserts that the current browser is Fennec, or Firefox for Android.
 *
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnsupportedOperationError}
 *     If current browser is not Fennec.
 */
assert.fennec = function(msg = "") {
  msg = msg || "Only supported in Fennec";
  assert.that(isFennec, msg, UnsupportedOperationError)();
};

/**
 * Asserts that the current |context| is content.
 *
 * @param {string} context
 *     Context to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {string}
 *     |context| is returned unaltered.
 *
 * @throws {UnsupportedOperationError}
 *     If |context| is not content.
 */
assert.content = function(context, msg = "") {
  msg = msg || "Only supported in content context";
  assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context);
};

/**
 * Asserts that |win| is open.
 *
 * @param {ChromeWindow} win
 *     Chrome window to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {ChromeWindow}
 *     |win| is returned unaltered.
 *
 * @throws {NoSuchWindowError}
 *     If |win| has been closed.
 */
assert.window = function(win, msg = "") {
  msg = msg || "Unable to locate window";
  return assert.that(w => w && !w.closed,
      msg,
      NoSuchWindowError)(win);
};

/**
 * Asserts that |context| is a valid browsing context.
 *
 * @param {browser.Context} context
 *     Browsing context to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {NoSuchWindowError}
 *     If |context| is invalid.
 */
assert.contentBrowser = function(context, msg = "") {
  // TODO: The contentBrowser uses a cached tab, which is only updated when
  // switchToTab is called. Because of that an additional check is needed to
  // make sure that the chrome window has not already been closed.
  assert.window(context && context.window);

  msg = msg || "Current window does not have a content browser";
  assert.that(c => c.contentBrowser,
      msg,
      NoSuchWindowError)(context);
};

/**
 * Asserts that there is no current user prompt.
 *
 * @param {modal.Dialog} dialog
 *     Reference to current dialogue.
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnexpectedAlertOpenError}
 *     If there is a user prompt.
 */
assert.noUserPrompt = function(dialog, msg = "") {
  assert.that(d => d === null || typeof d == "undefined",
      msg,
      UnexpectedAlertOpenError)(dialog);
};

/**
 * Asserts that |obj| is defined.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {?}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not defined.
 */
assert.defined = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be defined`;
  return assert.that(o => typeof o != "undefined", msg)(obj);
};

/**
 * Asserts that |obj| is a finite number.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {number}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not a number.
 */
assert.number = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be finite number`;
  return assert.that(Number.isFinite, msg)(obj);
};

/**
 * Asserts that |obj| is callable.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {Function}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not callable.
 */
assert.callable = function(obj, msg = "") {
  msg = msg || error.pprint`${obj} is not callable`;
  return assert.that(o => typeof o == "function", msg)(obj);
};

/**
 * Asserts that |obj| is an integer.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {number}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not an integer.
 */
assert.integer = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be an integer`;
  return assert.that(Number.isInteger, msg)(obj);
};

/**
 * Asserts that |obj| is a positive integer.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {number}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not a positive integer.
 */
assert.positiveInteger = function(obj, msg = "") {
  assert.integer(obj, msg);
  msg = msg || error.pprint`Expected ${obj} to be >= 0`;
  return assert.that(n => n >= 0, msg)(obj);
};

/**
 * Asserts that |obj| is a boolean.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {boolean}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not a boolean.
 */
assert.boolean = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be boolean`;
  return assert.that(b => typeof b == "boolean", msg)(obj);
};

/**
 * Asserts that |obj| is a string.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {string}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not a string.
 */
assert.string = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be a string`;
  return assert.that(s => typeof s == "string", msg)(obj);
};

/**
 * Asserts that |obj| is an object.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {Object}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not an object.
 */
assert.object = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be an object`;
  return assert.that(o => {
    // unable to use instanceof because LHS and RHS may come from
    // different globals
    let s = Object.prototype.toString.call(o);
    return s == "[object Object]" || s == "[object nsJSIID]";
  }, msg)(obj);
};

/**
 * Asserts that |prop| is in |obj|.
 *
 * @param {?} prop
 *     Own property to test if is in |obj|.
 * @param {?} obj
 *     Object.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {?}
 *     Value of |obj|'s own property |prop|.
 *
 * @throws {InvalidArgumentError}
 *     If |prop| is not in |obj|, or |obj| is not an object.
 */
assert.in = function(prop, obj, msg = "") {
  assert.object(obj, msg);
  msg = msg || error.pprint`Expected ${prop} in ${obj}`;
  assert.that(p => obj.hasOwnProperty(p), msg)(prop);
  return obj[prop];
};

/**
 * Asserts that |obj| is an Array.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @return {Object}
 *     |obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If |obj| is not an Array.
 */
assert.array = function(obj, msg = "") {
  msg = msg || error.pprint`Expected ${obj} to be an Array`;
  return assert.that(Array.isArray, msg)(obj);
};

/**
 * Returns a function that is used to assert the |predicate|.
 *
 * @param {function(?): boolean} predicate
 *     Evaluated on calling the return value of this function.  If its
 *     return value of the inner function is false, |error| is thrown
 *     with |message|.
 * @param {string=} message
 *     Custom error message.
 * @param {Error=} error
 *     Custom error type by its class.
 *
 * @return {function(?): ?}
 *     Function that takes and returns the passed in value unaltered, and
 *     which may throw |error| with |message| if |predicate| evaluates
 *     to false.
 */
assert.that = function(
    predicate, message = "", error = InvalidArgumentError) {
  return obj => {
    if (!predicate(obj)) {
      throw new error(message);
    }
    return obj;
  };
};
PK
!<OXrXr!chrome/marionette/content/atom.js// Copyright 2011-2014 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

this.EXPORTED_SYMBOLS = ["atom"];

/** @namespace */
this.atom = {};

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/action.js#L83
atom.clearElement = function (element, window){return function(){function g(a){throw a;}var h=void 0,i=!0,k=null,l=!1;function n(a){return function(){return this[a]}}function o(a){return function(){return a}}var p,q=this;
function aa(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function r(a){return a!==h}function ba(a){var b=aa(a);return"array"==b||"object"==b&&"number"==typeof a.length}function t(a){return"string"==typeof a}function w(a){return"function"==aa(a)}function ca(a){a=aa(a);return"object"==a||"array"==a||"function"==a}var da="closure_uid_"+Math.floor(2147483648*Math.random()).toString(36),ea=0,fa=Date.now||function(){return+new Date};
function x(a,b){function c(){}c.prototype=b.prototype;a.$=b.prototype;a.prototype=new c};function ga(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function ha(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function ia(a){if(!ja.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(ka,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(la,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(ma,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(na,"&quot;"));return a}var ka=/&/g,la=/</g,ma=/>/g,na=/\"/g,ja=/[&<>\"]/;
function oa(a,b){for(var c=0,d=ha(""+a).split("."),e=ha(""+b).split("."),f=Math.max(d.length,e.length),j=0;0==c&&j<f;j++){var m=d[j]||"",s=e[j]||"",O=RegExp("(\\d*)(\\D*)","g"),E=RegExp("(\\d*)(\\D*)","g");do{var u=O.exec(m)||["","",""],v=E.exec(s)||["","",""];if(0==u[0].length&&0==v[0].length)break;c=((0==u[1].length?0:parseInt(u[1],10))<(0==v[1].length?0:parseInt(v[1],10))?-1:(0==u[1].length?0:parseInt(u[1],10))>(0==v[1].length?0:parseInt(v[1],10))?1:0)||((0==u[2].length)<(0==v[2].length)?-1:(0==
u[2].length)>(0==v[2].length)?1:0)||(u[2]<v[2]?-1:u[2]>v[2]?1:0)}while(0==c)}return c}var pa=2147483648*Math.random()|0,qa={};function ra(a){return qa[a]||(qa[a]=(""+a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()}))};var sa,ta;function ua(){return q.navigator?q.navigator.userAgent:k}var va,wa=q.navigator;va=wa&&wa.platform||"";sa=-1!=va.indexOf("Mac");ta=-1!=va.indexOf("Win");var xa=-1!=va.indexOf("Linux"),ya,za="",Aa=/rv\:([^\);]+)(\)|;)/.exec(ua());ya=za=Aa?Aa[1]:"";var Ba={};var Ca=window;function Da(a,b){for(var c in a)b.call(h,a[c],c,a)}function Ea(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b};function y(a,b){this.code=a;this.message=b||"";this.name=Fa[a]||Fa[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}x(y,Error);
var Fa={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
y.prototype.toString=function(){return"["+this.name+"] "+this.message};function Ga(a){this.stack=Error().stack||"";a&&(this.message=""+a)}x(Ga,Error);Ga.prototype.name="CustomError";function Ha(a,b){b.unshift(a);Ga.call(this,ga.apply(k,b));b.shift()}x(Ha,Ga);Ha.prototype.name="AssertionError";function Ia(a,b,c){if(!a){var d=Array.prototype.slice.call(arguments,2),e="Assertion failed";if(b)var e=e+(": "+b),f=d;g(new Ha(""+e,f||[]))}}function Ja(a,b){g(new Ha("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1)))};function z(a){return a[a.length-1]}var Ka=Array.prototype;function A(a,b){if(t(a))return!t(b)||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1}function La(a,b){for(var c=a.length,d=t(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(h,d[e],e,a)}function Ma(a,b){for(var c=a.length,d=Array(c),e=t(a)?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(h,e[f],f,a));return d}
function Na(a,b,c){for(var d=a.length,e=t(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return i;return l}function Oa(a,b,c){for(var d=a.length,e=t(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&!b.call(c,e[f],f,a))return l;return i}function Pa(a,b){var c;a:{c=a.length;for(var d=t(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(h,d[e],e,a)){c=e;break a}c=-1}return 0>c?k:t(a)?a.charAt(c):a[c]}function Qa(a){return Ka.concat.apply(Ka,arguments)}
function Ra(a){if("array"==aa(a))return Qa(a);for(var b=[],c=0,d=a.length;c<d;c++)b[c]=a[c];return b}function Sa(a,b,c){Ia(a.length!=k);return 2>=arguments.length?Ka.slice.call(a,b):Ka.slice.call(a,b,c)};var Ta;Ba["1.9.1"]||(Ba["1.9.1"]=0<=oa(ya,"1.9.1"));function Ua(a,b){var c;c=(c=a.className)&&"function"==typeof c.split?c.split(/\s+/):[];var d=Sa(arguments,1),e;e=c;for(var f=0,j=0;j<d.length;j++)0<=A(e,d[j])||(e.push(d[j]),f++);e=f==d.length;a.className=c.join(" ");return e};function B(a,b){this.x=r(a)?a:0;this.y=r(b)?b:0}B.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function Va(a,b){this.width=a;this.height=b}Va.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};Va.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Va.prototype.scale=function (a){this.width*=a;this.height*=a;return this};var C=3;function Wa(a){return a?new Xa(D(a)):Ta||(Ta=new Xa)}function Ya(a,b){Da(b,function (b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Za?a.setAttribute(Za[d],b):0==d.lastIndexOf("aria-",0)?a.setAttribute(d,b):a[d]=b})}var Za={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",rowspan:"rowSpan",valign:"vAlign",height:"height",width:"width",usemap:"useMap",frameborder:"frameBorder",maxlength:"maxLength",type:"type"};
function F(a){return a?a.parentWindow||a.defaultView:window}function $a(a,b,c){function d(c){c&&b.appendChild(t(c)?a.createTextNode(c):c)}for(var e=2;e<c.length;e++){var f=c[e];ba(f)&&!(ca(f)&&0<f.nodeType)?La(ab(f)?Ra(f):f,d):d(f)}}function bb(a){return a&&a.parentNode?a.parentNode.removeChild(a):k}
function G(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
function cb(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?db(a,b):!c&&G(e,b)?-1*eb(a,b):!d&&G(f,a)?eb(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=D(a);c=d.createRange();c.selectNode(a);c.collapse(i);d=d.createRange();d.selectNode(b);d.collapse(i);
return c.compareBoundaryPoints(q.Range.START_TO_END,d)}function eb(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return db(d,a)}function db(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1}
function fb(a){var b,c=arguments.length;if(c){if(1==c)return arguments[0]}else return k;var d=[],e=Infinity;for(b=0;b<c;b++){for(var f=[],j=arguments[b];j;)f.unshift(j),j=j.parentNode;d.push(f);e=Math.min(e,f.length)}f=k;for(b=0;b<e;b++){for(var j=d[0][b],m=1;m<c;m++)if(j!=d[m][b])return f;f=j}return f}function D(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function gb(a,b){var c=[];return hb(a,b,c,i)?c[0]:h}
function hb(a,b,c,d){if(a!=k)for(a=a.firstChild;a;){if(b(a)&&(c.push(a),d)||hb(a,b,c,d))return i;a=a.nextSibling}return l}var ib={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},jb={IMG:" ",BR:"\n"};function kb(a,b,c){if(!(a.nodeName in ib))if(a.nodeType==C)c?b.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in jb)b.push(jb[a.nodeName]);else for(a=a.firstChild;a;)kb(a,b,c),a=a.nextSibling}
function ab(a){if(a&&"number"==typeof a.length){if(ca(a))return"function"==typeof a.item||"string"==typeof a.item;if(w(a))return"function"==typeof a.item}return l}function lb(a,b){for(var a=a.parentNode,c=0;a;){if(b(a))return a;a=a.parentNode;c++}return k}function Xa(a){this.v=a||q.document||document}p=Xa.prototype;p.ea=n("v");p.z=function (a){return t(a)?this.v.getElementById(a):a};
p.da=function (a,b,c){var d=this.v,e=arguments,f=e[1],j=d.createElement(e[0]);f&&(t(f)?j.className=f:"array"==aa(f)?Ua.apply(k,[j].concat(f)):Ya(j,f));2<e.length&&$a(d,j,e);return j};p.createElement=function (a){return this.v.createElement(a)};p.createTextNode=function (a){return this.v.createTextNode(a)};p.qa=function(){return this.v.parentWindow||this.v.defaultView};
function mb(a){var b=a.v,a="CSS1Compat"==b.compatMode?b.documentElement:b.body,b=b.parentWindow||b.defaultView;return new B(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}p.appendChild=function (a,b){a.appendChild(b)};p.removeNode=bb;p.contains=G;var H={};H.ya=function(){var a={Oa:"http://www.w3.org/2000/svg"};return function (b){return a[b]||k}}();H.ma=function (a,b,c){var d=D(a);if(!d.implementation.hasFeature("XPath","3.0"))return k;try{var e=d.createNSResolver?d.createNSResolver(d.documentElement):H.ya;return d.evaluate(b,a,e,c,k)}catch(f){"NS_ERROR_ILLEGAL_VALUE"!=f.name&&g(new y(32,"Unable to locate an element with the xpath expression "+b+" because of the following error:\n"+f))}};
H.ka=function (a,b){(!a||1!=a.nodeType)&&g(new y(32,'The result of the xpath expression "'+b+'" is: '+a+". It should be an element."))};H.Ia=function (a,b){var c=function(){var c=H.ma(b,a,9);return c?c.singleNodeValue||k:b.selectSingleNode?(c=D(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):k}();c===k||H.ka(c,a);return c};
H.Na=function (a,b){var c=function(){var c=H.ma(b,a,7);if(c){for(var e=c.snapshotLength,f=[],j=0;j<e;++j)f.push(c.snapshotItem(j));return f}return b.selectNodes?(c=D(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectNodes(a)):[]}();La(c,function (b){H.ka(b,a)});return c};var nb,ob="",pb=/Firefox\/([0-9.]+)/.exec(ua());nb=ob=pb?pb[2]||pb[1]:"";var qb=k,rb=function(){var a=q.Components;if(!a)return l;try{if(!a.classes)return l}catch(b){return l}var c=a.classes,a=a.interfaces,d=c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),e=c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).version;qb=function (a){return 0<=d.Ka(e,""+a)};return i}();var I="StopIteration"in q?q.StopIteration:Error("StopIteration");function J(){}J.prototype.next=function(){g(I)};J.prototype.r=function(){return this};function sb(a){if(a instanceof J)return a;if("function"==typeof a.r)return a.r(l);if(ba(a)){var b=0,c=new J;c.next=function(){for(;;){b>=a.length&&g(I);if(b in a)return a[b++];b++}};return c}g(Error("Not implemented"))};function K(a,b,c,d,e){this.o=!!b;a&&L(this,a,d);this.depth=e!=h?e:this.q||0;this.o&&(this.depth*=-1);this.za=!c}x(K,J);p=K.prototype;p.p=k;p.q=0;p.ha=l;function L(a,b,c,d){if(a.p=b)a.q="number"==typeof c?c:1!=a.p.nodeType?0:a.o?-1:1;"number"==typeof d&&(a.depth=d)}
p.next=function(){var a;if(this.ha){(!this.p||this.za&&0==this.depth)&&g(I);a=this.p;var b=this.o?-1:1;if(this.q==b){var c=this.o?a.lastChild:a.firstChild;c?L(this,c):L(this,a,-1*b)}else(c=this.o?a.previousSibling:a.nextSibling)?L(this,c):L(this,a.parentNode,-1*b);this.depth+=this.q*(this.o?-1:1)}else this.ha=i;(a=this.p)||g(I);return a};
p.splice=function (a){var b=this.p,c=this.o?1:-1;this.q==c&&(this.q=-1*c,this.depth+=this.q*(this.o?-1:1));this.o=!this.o;K.prototype.next.call(this);this.o=!this.o;for(var c=ba(arguments[0])?arguments[0]:arguments,d=c.length-1;0<=d;d--)b.parentNode&&b.parentNode.insertBefore(c[d],b.nextSibling);bb(b)};function tb(a,b,c,d){K.call(this,a,b,c,k,d)}x(tb,K);tb.prototype.next=function(){do tb.$.next.call(this);while(-1==this.q);return this.p};function ub(a,b){var c=D(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,k))?c[b]||c.getPropertyValue(b):""}function vb(a,b){return ub(a,b)||(a.currentStyle?a.currentStyle[b]:k)||a.style&&a.style[b]}
function wb(a){for(var b=D(a),c=vb(a,"position"),d="fixed"==c||"absolute"==c,a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=vb(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return k}
function xb(a){var b=new B;if(1==a.nodeType)if(a.getBoundingClientRect)a=a.getBoundingClientRect(),b.x=a.left,b.y=a.top;else{var c=mb(Wa(a));var d,e=D(a),f=vb(a,"position"),j=e.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(a))&&(0>d.screenX||0>d.screenY),f=new B(0,0),m=(e?9==e.nodeType?e:D(e):document).documentElement;if(a!=m)if(a.getBoundingClientRect)d=a.getBoundingClientRect(),a=mb(Wa(e)),f.x=d.left+a.x,f.y=d.top+a.y;else if(e.getBoxObjectFor&&!j)d=e.getBoxObjectFor(a),
a=e.getBoxObjectFor(m),f.x=d.screenX-a.screenX,f.y=d.screenY-a.screenY;else{d=a;do f.x+=d.offsetLeft,f.y+=d.offsetTop,d!=a&&(f.x+=d.clientLeft||0,f.y+=d.clientTop||0),d=d.offsetParent;while(d&&d!=a);for(d=a;(d=wb(d))&&d!=e.body&&d!=m;)f.x-=d.scrollLeft,f.y-=d.scrollTop}b.x=f.x-c.x;b.y=f.y-c.y}else c=w(a.pa),d=a,a.targetTouches?d=a.targetTouches[0]:c&&a.pa().targetTouches&&(d=a.pa().targetTouches[0]),b.x=d.clientX,b.y=d.clientY;return b}
function yb(a){var b=a.offsetWidth,c=a.offsetHeight;return!r(b)&&a.getBoundingClientRect?(a=a.getBoundingClientRect(),new Va(a.right-a.left,a.bottom-a.top)):new Va(b,c)};function M(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}var zb={"class":"className",readonly:"readOnly"},Ab=["checked","disabled","draggable","hidden"];function Bb(a,b){var c=zb[b]||b,d=a[c];if(!r(d)&&0<=A(Ab,c))return l;if(c="value"==b)if(c=M(a,"OPTION")){var e;c=b.toLowerCase();if(a.hasAttribute)e=a.hasAttribute(c);else try{e=a.attributes[c].specified}catch(f){e=l}c=!e}c&&(d=[],kb(a,d,l),d=d.join(""));return d}
var Cb="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(","),Db="BUTTON,INPUT,OPTGROUP,OPTION,SELECT,TEXTAREA".split(",");
function Eb(a){var b=a.tagName.toUpperCase();return!(0<=A(Db,b))?i:Bb(a,"disabled")?l:a.parentNode&&1==a.parentNode.nodeType&&"OPTGROUP"==b||"OPTION"==b?Eb(a.parentNode):i}var Fb="text,search,tel,url,email,password,number".split(",");function Gb(a){return M(a,"TEXTAREA")?i:M(a,"INPUT")?0<=A(Fb,a.type.toLowerCase()):Hb(a)?i:l}
function Hb(a){function b(a){return"inherit"==a.contentEditable?(a=Ib(a))?b(a):l:"true"==a.contentEditable}return!r(a.contentEditable)?l:r(a.isContentEditable)?a.isContentEditable:b(a)}function Ib(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return M(a)?a:k}function Jb(a,b){b=ra(b);return ub(a,b)||Kb(a,b)}
function Kb(a,b){var c=a.currentStyle||a.style,d=c[b];!r(d)&&w(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?r(d)?d:k:(c=Ib(a))?Kb(c,b):k}function Lb(a){if(w(a.getBBox))try{var b=a.getBBox();if(b)return b}catch(c){}if("none"!=vb(a,"display"))a=yb(a);else{var b=a.style,d=b.display,e=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";a=yb(a);b.display=d;b.position=f;b.visibility=e}return a}
function Mb(a,b){function c(a){if("none"==Jb(a,"display"))return l;a=Ib(a);return!a||c(a)}function d(a){var b=Lb(a);return 0<b.height&&0<b.width?i:Na(a.childNodes,function (a){return a.nodeType==C||M(a)&&d(a)})}function e(a){var b=Ib(a);if(b&&"hidden"==Jb(b,"overflow")){var c=Lb(b),d=xb(b),a=xb(a);return d.x+c.width<a.x||d.y+c.height<a.y?l:e(b)}return i}M(a)||g(Error("Argument to isShown must be of type Element"));if(M(a,"OPTION")||M(a,"OPTGROUP")){var f=lb(a,function (a){return M(a,"SELECT")});return!!f&&
Mb(f,i)}if(M(a,"MAP")){if(!a.name)return l;f=D(a);f=f.evaluate?H.Ia('/descendant::*[@usemap = "#'+a.name+'"]',f):gb(f,function (b){var c;if(c=M(b))8==b.nodeType?b=k:(c="usemap","style"==c?(b=ha(b.style.cssText).toLowerCase(),b=";"==b.charAt(b.length-1)?b:b+";"):(b=b.getAttributeNode(c),b=!b?k:0<=A(Cb,c)?"true":b.specified?b.value:k)),c=b=="#"+a.name;return c});return!!f&&Mb(f,b)}return M(a,"AREA")?(f=lb(a,function (a){return M(a,"MAP")}),!!f&&Mb(f,b)):M(a,"INPUT")&&"hidden"==a.type.toLowerCase()||M(a,
"NOSCRIPT")||"hidden"==Jb(a,"visibility")||!c(a)||!b&&0==Nb(a)||!d(a)||!e(a)?l:i}function Nb(a){var b=1,c=Jb(a,"opacity");c&&(b=Number(c));(a=Ib(a))&&(b*=Nb(a));return b};function N(){this.w=Ca.document.documentElement;this.ua=k;var a=D(this.w).activeElement;a&&Ob(this,a)}N.prototype.z=n("w");function Ob(a,b){a.w=b;a.ua=M(b,"OPTION")?lb(b,function (a){return M(a,"SELECT")}):k}
function Pb(a,b,c,d,e,f){function j(a,c){var d={identifier:a,screenX:c.x,screenY:c.y,clientX:c.x,clientY:c.y,pageX:c.x,pageY:c.y};m.changedTouches.push(d);if(b==Qb||b==Rb)m.touches.push(d),m.targetTouches.push(d)}var m={touches:[],targetTouches:[],changedTouches:[],altKey:l,ctrlKey:l,shiftKey:l,metaKey:l,relatedTarget:k,scale:0,rotation:0};j(c,d);r(e)&&j(e,f);Sb(a.w,b,m)}rb&&rb&&(rb?qb(4):oa(nb,4));rb&&(rb?qb(4):oa(nb,4));function P(a,b,c){this.J=a;this.S=b;this.T=c}P.prototype.create=function (a){a=D(a).createEvent("HTMLEvents");a.initEvent(this.J,this.S,this.T);return a};P.prototype.toString=n("J");function Q(a,b,c){P.call(this,a,b,c)}x(Q,P);
Q.prototype.create=function (a,b){var c=D(a),d=F(c),c=c.createEvent("MouseEvents"),e=1;this==Tb&&(e=b.wheelDelta/-40);this==Ub&&(e=b.wheelDelta);c.initMouseEvent(this.J,this.S,this.T,d,e,0,0,b.clientX,b.clientY,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,b.button,b.relatedTarget);return c};function Vb(a,b,c){P.call(this,a,b,c)}x(Vb,P);
Vb.prototype.create=function (a,b){var c=D(a),d=F(c),e=b.charCode?0:b.keyCode,c=c.createEvent("KeyboardEvent");c.initKeyEvent(this.J,this.S,this.T,d,b.ctrlKey,b.altKey,b.shiftKey,b.metaKey,e,b.charCode);this.J==Wb&&b.preventDefault&&c.preventDefault();return c};function Xb(a,b,c){P.call(this,a,b,c)}x(Xb,P);
Xb.prototype.create=function (a,b){function c(b){var c=Ma(b,function (b){return{identifier:b.identifier,screenX:b.screenX,screenY:b.screenY,clientX:b.clientX,clientY:b.clientY,pageX:b.pageX,pageY:b.pageY,target:a}});c.item=function (a){return c[a]};return c}var d=D(a),e=F(d),f=c(b.changedTouches),j=b.touches==b.changedTouches?f:c(b.touches),m=b.targetTouches==b.changedTouches?f:c(b.targetTouches),d=d.createEvent("MouseEvents");d.initMouseEvent(this.J,this.S,this.T,e,1,0,0,b.clientX,b.clientY,b.ctrlKey,
b.altKey,b.shiftKey,b.metaKey,0,b.relatedTarget);d.touches=j;d.targetTouches=m;d.changedTouches=f;d.scale=b.scale;d.rotation=b.rotation;return d};
var Yb=new P("change",i,l),Zb=new Q("click",i,i),$b=new Q("contextmenu",i,i),ac=new Q("dblclick",i,i),bc=new Q("mousedown",i,i),cc=new Q("mousemove",i,l),dc=new Q("mouseout",i,i),ec=new Q("mouseover",i,i),fc=new Q("mouseup",i,i),Tb=new Q("DOMMouseScroll",i,i),Ub=new Q("MozMousePixelScroll",i,i),Wb=new Vb("keypress",i,i),Rb=new Xb("touchmove",i,i),Qb=new Xb("touchstart",i,i);function Sb(a,b,c){b=b.create(a,c);"isTrusted"in b||(b.La=l);a.dispatchEvent(b)};function gc(a){if("function"==typeof a.L)return a.L();if(t(a))return a.split("");if(ba(a)){for(var b=[],c=a.length,d=0;d<c;d++)b.push(a[d]);return b}return Ea(a)};function hc(a,b){this.n={};this.ta={};var c=arguments.length;if(1<c){c%2&&g(Error("Uneven number of arguments"));for(var d=0;d<c;d+=2)this.set(arguments[d],arguments[d+1])}else a&&this.aa(a)}p=hc.prototype;p.ia=0;p.L=function(){var a=[],b;for(b in this.n)":"==b.charAt(0)&&a.push(this.n[b]);return a};function ic(a){var b=[],c;for(c in a.n)if(":"==c.charAt(0)){var d=c.substring(1);b.push(a.ta[c]?Number(d):d)}return b}
p.set=function (a,b){var c=":"+a;c in this.n||(this.ia++,"number"==typeof a&&(this.ta[c]=i));this.n[c]=b};p.aa=function (a){var b;if(a instanceof hc)b=ic(a),a=a.L();else{b=[];var c=0,d;for(d in a)b[c++]=d;a=Ea(a)}for(c=0;c<b.length;c++)this.set(b[c],a[c])};p.r=function (a){var b=0,c=ic(this),d=this.n,e=this.ia,f=this,j=new J;j.next=function(){for(;;){e!=f.ia&&g(Error("The map has changed since the iterator was created"));b>=c.length&&g(I);var j=c[b++];return a?j:d[":"+j]}};return j};function jc(a){this.n=new hc;a&&this.aa(a)}function kc(a){var b=typeof a;return"object"==b&&a||"function"==b?"o"+(a[da]||(a[da]=++ea)):b.substr(0,1)+a}p=jc.prototype;p.add=function (a){this.n.set(kc(a),a)};p.aa=function (a){for(var a=gc(a),b=a.length,c=0;c<b;c++)this.add(a[c])};p.contains=function (a){return":"+kc(a)in this.n.n};p.L=function(){return this.n.L()};p.r=function(){return this.n.r(l)};function lc(){N.call(this);Gb(this.z())&&Bb(this.z(),"readOnly");this.Ha=new jc}x(lc,N);var mc={};function R(a,b,c){ca(a)&&(a=a.c);a=new nc(a);if(b&&(!(b in mc)||c))mc[b]={key:a,shift:l},c&&(mc[c]={key:a,shift:i})}function nc(a){this.code=a}R(8);R(9);R(13);R(16);R(17);R(18);R(19);R(20);R(27);R(32," ");R(33);R(34);R(35);R(36);R(37);R(38);R(39);R(40);R(44);R(45);R(46);R(48,"0",")");R(49,"1","!");R(50,"2","@");R(51,"3","#");R(52,"4","$");R(53,"5","%");R(54,"6","^");R(55,"7","&");R(56,"8","*");
R(57,"9","(");R(65,"a","A");R(66,"b","B");R(67,"c","C");R(68,"d","D");R(69,"e","E");R(70,"f","F");R(71,"g","G");R(72,"h","H");R(73,"i","I");R(74,"j","J");R(75,"k","K");R(76,"l","L");R(77,"m","M");R(78,"n","N");R(79,"o","O");R(80,"p","P");R(81,"q","Q");R(82,"r","R");R(83,"s","S");R(84,"t","T");R(85,"u","U");R(86,"v","V");R(87,"w","W");R(88,"x","X");R(89,"y","Y");R(90,"z","Z");R(ta?{c:91,e:91,opera:219}:sa?{c:224,e:91,opera:17}:{c:0,e:91,opera:k});
R(ta?{c:92,e:92,opera:220}:sa?{c:224,e:93,opera:17}:{c:0,e:92,opera:k});R(ta?{c:93,e:93,opera:0}:sa?{c:0,e:0,opera:16}:{c:93,e:k,opera:0});R({c:96,e:96,opera:48},"0");R({c:97,e:97,opera:49},"1");R({c:98,e:98,opera:50},"2");R({c:99,e:99,opera:51},"3");R({c:100,e:100,opera:52},"4");R({c:101,e:101,opera:53},"5");R({c:102,e:102,opera:54},"6");R({c:103,e:103,opera:55},"7");R({c:104,e:104,opera:56},"8");R({c:105,e:105,opera:57},"9");R({c:106,e:106,opera:xa?56:42},"*");R({c:107,e:107,opera:xa?61:43},"+");
R({c:109,e:109,opera:xa?109:45},"-");R({c:110,e:110,opera:xa?190:78},".");R({c:111,e:111,opera:xa?191:47},"/");R(144);R(112);R(113);R(114);R(115);R(116);R(117);R(118);R(119);R(120);R(121);R(122);R(123);R({c:107,e:187,opera:61},"=","+");R({c:109,e:189,opera:109},"-","_");R(188,",","<");R(190,".",">");R(191,"/","?");R(192,"`","~");R(219,"[","{");R(220,"\\","|");R(221,"]","}");R({c:59,e:186,opera:59},";",":");R(222,"'",'"');lc.prototype.X=function (a){return this.Ha.contains(a)};function oc(a){return pc(a||arguments.callee.caller,[])}
function pc(a,b){var c=[];if(0<=A(b,a))c.push("[...circular reference...]");else if(a&&50>b.length){c.push(qc(a)+"(");for(var d=a.arguments,e=0;e<d.length;e++){0<e&&c.push(", ");var f;f=d[e];switch(typeof f){case "object":f=f?"object":"null";break;case "string":break;case "number":f=""+f;break;case "boolean":f=f?"true":"false";break;case "function":f=(f=qc(f))?f:"[fn]";break;default:f=typeof f}40<f.length&&(f=f.substr(0,40)+"...");c.push(f)}b.push(a);c.push(")\n");try{c.push(pc(a.caller,b))}catch(j){c.push("[exception trying to get caller]\n")}}else a?
c.push("[...long stack...]"):c.push("[end]");return c.join("")}function qc(a){if(rc[a])return rc[a];a=""+a;if(!rc[a]){var b=/function ([^\(]+)/.exec(a);rc[a]=b?b[1]:"[Anonymous]"}return rc[a]}var rc={};function sc(a,b,c,d,e){this.reset(a,b,c,d,e)}sc.prototype.oa=k;sc.prototype.na=k;var tc=0;sc.prototype.reset=function (a,b,c,d,e){"number"==typeof e||tc++;d||fa();this.N=a;this.Fa=b;delete this.oa;delete this.na};sc.prototype.va=function (a){this.N=a};function S(a){this.Ga=a}S.prototype.Y=k;S.prototype.N=k;S.prototype.ba=k;S.prototype.ra=k;function uc(a,b){this.name=a;this.value=b}uc.prototype.toString=n("name");var vc=new uc("WARNING",900),wc=new uc("CONFIG",700);S.prototype.getParent=n("Y");S.prototype.va=function (a){this.N=a};function xc(a){if(a.N)return a.N;if(a.Y)return xc(a.Y);Ja("Root logger has no level set.");return k}
S.prototype.log=function (a,b,c){if(a.value>=xc(this).value){a=this.Ca(a,b,c);b="log:"+a.Fa;q.console&&q.console.timeStamp&&q.console.timeStamp(b);q.msWriteProfilerMark&&q.msWriteProfilerMark(b);for(b=this;b;){var c=b,d=a;if(c.ra)for(var e=0,f=h;f=c.ra[e];e++)f(d);b=b.getParent()}}};
S.prototype.Ca=function (a,b,c){var d=new sc(a,""+b,this.Ga);if(c){d.oa=c;var e;var f=arguments.callee.caller;try{var j;var m;c:{for(var s=["window","location","href"],O=q,E;E=s.shift();)if(O[E]!=k)O=O[E];else{m=k;break c}m=O}if(t(c))j={message:c,name:"Unknown error",lineNumber:"Not available",fileName:m,stack:"Not available"};else{var u,v,s=l;try{u=c.lineNumber||c.Ma||"Not available"}catch(md){u="Not available",s=i}try{v=c.fileName||c.filename||c.sourceURL||m}catch(nd){v="Not available",s=i}j=s||
!c.lineNumber||!c.fileName||!c.stack?{message:c.message,name:c.name,lineNumber:u,fileName:v,stack:c.stack||"Not available"}:c}e="Message: "+ia(j.message)+'\nUrl: <a href="view-source:'+j.fileName+'" target="_new">'+j.fileName+"</a>\nLine: "+j.lineNumber+"\n\nBrowser stack:\n"+ia(j.stack+"-> ")+"[end]\n\nJS stack traversal:\n"+ia(oc(f)+"-> ")}catch(kd){e="Exception trying to expose exception! You win, we lose. "+kd}d.na=e}return d};var yc={},zc=k;
function Ac(a){zc||(zc=new S(""),yc[""]=zc,zc.va(wc));var b;if(!(b=yc[a])){b=new S(a);var c=a.lastIndexOf("."),d=a.substr(c+1),c=Ac(a.substr(0,c));c.ba||(c.ba={});c.ba[d]=b;b.Y=c;yc[a]=b}return b};function Bc(){}x(Bc,function(){});Ac("goog.dom.SavedRange");x(function (a){this.Ja="goog_"+pa++;this.Aa="goog_"+pa++;this.la=Wa(a.ea());a.R(this.la.da("SPAN",{id:this.Ja}),this.la.da("SPAN",{id:this.Aa}))},Bc);function T(){}function Cc(a){if(a.getSelection)return a.getSelection();var a=a.document,b=a.selection;if(b){try{var c=b.createRange();if(c.parentElement){if(c.parentElement().document!=a)return k}else if(!c.length||c.item(0).document!=a)return k}catch(d){return k}return b}return k}function Dc(a){for(var b=[],c=0,d=a.D();c<d;c++)b.push(a.A(c));return b}T.prototype.F=o(l);T.prototype.ea=function(){return D(this.b())};T.prototype.qa=function(){return F(this.ea())};
T.prototype.containsNode=function (a,b){return this.u(Ec(Fc(a),h),b)};function U(a,b){K.call(this,a,b,i)}x(U,K);function V(){}x(V,T);V.prototype.u=function (a,b){var c=Dc(this),d=Dc(a);return(b?Na:Oa)(d,function (a){return Na(c,function (c){return c.u(a,b)})})};V.prototype.insertNode=function (a,b){if(b){var c=this.b();c.parentNode&&c.parentNode.insertBefore(a,c)}else c=this.g(),c.parentNode&&c.parentNode.insertBefore(a,c.nextSibling);return a};V.prototype.R=function (a,b){this.insertNode(a,i);this.insertNode(b,l)};function Gc(a,b,c,d,e){var f;if(a&&(this.f=a,this.i=b,this.d=c,this.h=d,1==a.nodeType&&"BR"!=a.tagName&&(a=a.childNodes,(b=a[b])?(this.f=b,this.i=0):(a.length&&(this.f=z(a)),f=i)),1==c.nodeType))(this.d=c.childNodes[d])?this.h=0:this.d=c;U.call(this,e?this.d:this.f,e);if(f)try{this.next()}catch(j){j!=I&&g(j)}}x(Gc,U);p=Gc.prototype;p.f=k;p.d=k;p.i=0;p.h=0;p.b=n("f");p.g=n("d");p.M=function(){return this.ha&&this.p==this.d&&(!this.h||1!=this.q)};p.next=function(){this.M()&&g(I);return Gc.$.next.call(this)};"ScriptEngine"in q&&"JScript"==q.ScriptEngine()&&(q.ScriptEngineMajorVersion(),q.ScriptEngineMinorVersion(),q.ScriptEngineBuildVersion());function Hc(){}Hc.prototype.u=function (a,b){var c=b&&!a.isCollapsed(),d=a.a;try{return c?0<=this.l(d,0,1)&&0>=this.l(d,1,0):0<=this.l(d,0,0)&&0>=this.l(d,1,1)}catch(e){g(e)}};Hc.prototype.containsNode=function (a,b){return this.u(Fc(a),b)};Hc.prototype.r=function(){return new Gc(this.b(),this.j(),this.g(),this.k())};function Ic(a){this.a=a}x(Ic,Hc);p=Ic.prototype;p.C=function(){return this.a.commonAncestorContainer};p.b=function(){return this.a.startContainer};p.j=function(){return this.a.startOffset};p.g=function(){return this.a.endContainer};p.k=function(){return this.a.endOffset};p.l=function (a,b,c){return this.a.compareBoundaryPoints(1==c?1==b?q.Range.START_TO_START:q.Range.START_TO_END:1==b?q.Range.END_TO_START:q.Range.END_TO_END,a)};p.isCollapsed=function(){return this.a.collapsed};
p.select=function (a){this.Z(F(D(this.b())).getSelection(),a)};p.Z=function (a){a.removeAllRanges();a.addRange(this.a)};p.insertNode=function (a,b){var c=this.a.cloneRange();c.collapse(b);c.insertNode(a);c.detach();return a};
p.R=function (a,b){var c=F(D(this.b()));if(c=(c=Cc(c||window))&&Jc(c))var d=c.b(),e=c.g(),f=c.j(),j=c.k();var m=this.a.cloneRange(),s=this.a.cloneRange();m.collapse(l);s.collapse(i);m.insertNode(b);s.insertNode(a);m.detach();s.detach();if(c){if(d.nodeType==C)for(;f>d.length;){f-=d.length;do d=d.nextSibling;while(d==a||d==b)}if(e.nodeType==C)for(;j>e.length;){j-=e.length;do e=e.nextSibling;while(e==a||e==b)}c=new Kc;c.G=Lc(d,f,e,j);"BR"==d.tagName&&(m=d.parentNode,f=A(m.childNodes,d),d=m);"BR"==e.tagName&&
(m=e.parentNode,j=A(m.childNodes,e),e=m);c.G?(c.f=e,c.i=j,c.d=d,c.h=f):(c.f=d,c.i=f,c.d=e,c.h=j);c.select()}};p.collapse=function (a){this.a.collapse(a)};function W(a){this.a=a}x(W,Ic);function Fc(a){var b=D(a).createRange();if(a.nodeType==C)b.setStart(a,0),b.setEnd(a,a.length);else if(X(a)){for(var c,d=a;(c=d.firstChild)&&X(c);)d=c;b.setStart(d,0);for(d=a;(c=d.lastChild)&&X(c);)d=c;b.setEnd(d,1==d.nodeType?d.childNodes.length:d.length)}else c=a.parentNode,a=A(c.childNodes,a),b.setStart(c,a),b.setEnd(c,a+1);return new W(b)}
W.prototype.Z=function (a,b){var c=b?this.g():this.b(),d=b?this.k():this.j(),e=b?this.b():this.g(),f=b?this.j():this.k();a.collapse(c,d);(c!=e||d!=f)&&a.extend(e,f)};function Mc(a){this.a=a}x(Mc,Hc);Ac("goog.dom.browserrange.IeRange");function Nc(a){var b=D(a).body.createTextRange();if(1==a.nodeType)b.moveToElementText(a),X(a)&&!a.childNodes.length&&b.collapse(l);else{for(var c=0,d=a;d=d.previousSibling;){var e=d.nodeType;if(e==C)c+=d.length;else if(1==e){b.moveToElementText(d);break}}d||b.moveToElementText(a.parentNode);b.collapse(!d);c&&b.move("character",c);b.moveEnd("character",a.length)}return b}p=Mc.prototype;p.O=k;p.f=k;p.d=k;p.i=-1;p.h=-1;
p.s=function(){this.O=this.f=this.d=k;this.i=this.h=-1};
p.C=function(){if(!this.O){var a=this.a.text,b=this.a.duplicate(),c=a.replace(/ +$/,"");(c=a.length-c.length)&&b.moveEnd("character",-c);c=b.parentElement();b=b.htmlText.replace(/(\r\n|\r|\n)+/g," ").length;if(this.isCollapsed()&&0<b)return this.O=c;for(;b>c.outerHTML.replace(/(\r\n|\r|\n)+/g," ").length;)c=c.parentNode;for(;1==c.childNodes.length&&c.innerText==(c.firstChild.nodeType==C?c.firstChild.nodeValue:c.firstChild.innerText)&&X(c.firstChild);)c=c.firstChild;0==a.length&&(c=Oc(this,c));this.O=
c}return this.O};function Oc(a,b){for(var c=b.childNodes,d=0,e=c.length;d<e;d++){var f=c[d];if(X(f)){var j=Nc(f),m=j.htmlText!=f.outerHTML;if(a.isCollapsed()&&m?0<=a.l(j,1,1)&&0>=a.l(j,1,0):a.a.inRange(j))return Oc(a,f)}}return b}p.b=function(){this.f||(this.f=Pc(this,1),this.isCollapsed()&&(this.d=this.f));return this.f};p.j=function(){0>this.i&&(this.i=Qc(this,1),this.isCollapsed()&&(this.h=this.i));return this.i};
p.g=function(){if(this.isCollapsed())return this.b();this.d||(this.d=Pc(this,0));return this.d};p.k=function(){if(this.isCollapsed())return this.j();0>this.h&&(this.h=Qc(this,0),this.isCollapsed()&&(this.i=this.h));return this.h};p.l=function (a,b,c){return this.a.compareEndPoints((1==b?"Start":"End")+"To"+(1==c?"Start":"End"),a)};
function Pc(a,b,c){c=c||a.C();if(!c||!c.firstChild)return c;for(var d=1==b,e=0,f=c.childNodes.length;e<f;e++){var j=d?e:f-e-1,m=c.childNodes[j],s;try{s=Fc(m)}catch(O){continue}var E=s.a;if(a.isCollapsed())if(X(m)){if(s.u(a))return Pc(a,b,m)}else{if(0==a.l(E,1,1)){a.i=a.h=j;break}}else{if(a.u(s)){if(!X(m)){d?a.i=j:a.h=j+1;break}return Pc(a,b,m)}if(0>a.l(E,1,0)&&0<a.l(E,0,1))return Pc(a,b,m)}}return c}
function Qc(a,b){var c=1==b,d=c?a.b():a.g();if(1==d.nodeType){for(var d=d.childNodes,e=d.length,f=c?1:-1,j=c?0:e-1;0<=j&&j<e;j+=f){var m=d[j];if(!X(m)&&0==a.a.compareEndPoints((1==b?"Start":"End")+"To"+(1==b?"Start":"End"),Fc(m).a))return c?j:j+1}return-1==j?0:j}e=a.a.duplicate();f=Nc(d);e.setEndPoint(c?"EndToEnd":"StartToStart",f);e=e.text.length;return c?d.length-e:e}p.isCollapsed=function(){return 0==this.a.compareEndPoints("StartToEnd",this.a)};p.select=function(){this.a.select()};
function Rc(a,b,c){var d;d=d||Wa(a.parentElement());var e;1!=b.nodeType&&(e=i,b=d.da("DIV",k,b));a.collapse(c);d=d||Wa(a.parentElement());var f=c=b.id;c||(c=b.id="goog_"+pa++);a.pasteHTML(b.outerHTML);(b=d.z(c))&&(f||b.removeAttribute("id"));if(e){a=b.firstChild;e=b;if((d=e.parentNode)&&11!=d.nodeType)if(e.removeNode)e.removeNode(l);else{for(;b=e.firstChild;)d.insertBefore(b,e);bb(e)}b=a}return b}p.insertNode=function (a,b){var c=Rc(this.a.duplicate(),a,b);this.s();return c};
p.R=function (a,b){var c=this.a.duplicate(),d=this.a.duplicate();Rc(c,a,i);Rc(d,b,l);this.s()};p.collapse=function (a){this.a.collapse(a);a?(this.d=this.f,this.h=this.i):(this.f=this.d,this.i=this.h)};function Sc(a){this.a=a}x(Sc,Ic);Sc.prototype.Z=function (a){a.collapse(this.b(),this.j());(this.g()!=this.b()||this.k()!=this.j())&&a.extend(this.g(),this.k());0==a.rangeCount&&a.addRange(this.a)};function Tc(a){this.a=a}x(Tc,Ic);Tc.prototype.l=function (a,b,c){return Ba["528"]||(Ba["528"]=0<=oa(ya,"528"))?Tc.$.l.call(this,a,b,c):this.a.compareBoundaryPoints(1==c?1==b?q.Range.START_TO_START:q.Range.END_TO_START:1==b?q.Range.START_TO_END:q.Range.END_TO_END,a)};Tc.prototype.Z=function (a,b){a.removeAllRanges();b?a.setBaseAndExtent(this.g(),this.k(),this.b(),this.j()):a.setBaseAndExtent(this.b(),this.j(),this.g(),this.k())};function X(a){var b;a:if(1!=a.nodeType)b=l;else{switch(a.tagName){case "APPLET":case "AREA":case "BASE":case "BR":case "COL":case "FRAME":case "HR":case "IMG":case "INPUT":case "IFRAME":case "ISINDEX":case "LINK":case "NOFRAMES":case "NOSCRIPT":case "META":case "OBJECT":case "PARAM":case "SCRIPT":case "STYLE":b=l;break a}b=i}return b||a.nodeType==C};function Kc(){}x(Kc,T);function Ec(a,b){var c=new Kc;c.K=a;c.G=!!b;return c}p=Kc.prototype;p.K=k;p.f=k;p.i=k;p.d=k;p.h=k;p.G=l;p.fa=o("text");p.W=function(){return Y(this).a};p.s=function(){this.f=this.i=this.d=this.h=k};p.D=o(1);p.A=function(){return this};function Y(a){var b;if(!(b=a.K)){b=a.b();var c=a.j(),d=a.g(),e=a.k(),f=D(b).createRange();f.setStart(b,c);f.setEnd(d,e);b=a.K=new W(f)}return b}p.C=function(){return Y(this).C()};p.b=function(){return this.f||(this.f=Y(this).b())};
p.j=function(){return this.i!=k?this.i:this.i=Y(this).j()};p.g=function(){return this.d||(this.d=Y(this).g())};p.k=function(){return this.h!=k?this.h:this.h=Y(this).k()};p.F=n("G");p.u=function (a,b){var c=a.fa();return"text"==c?Y(this).u(Y(a),b):"control"==c?(c=Uc(a),(b?Na:Oa)(c,function (a){return this.containsNode(a,b)},this)):l};p.isCollapsed=function(){return Y(this).isCollapsed()};p.r=function(){return new Gc(this.b(),this.j(),this.g(),this.k())};p.select=function(){Y(this).select(this.G)};
p.insertNode=function (a,b){var c=Y(this).insertNode(a,b);this.s();return c};p.R=function (a,b){Y(this).R(a,b);this.s()};p.ga=function(){return new Vc(this)};p.collapse=function (a){a=this.F()?!a:a;this.K&&this.K.collapse(a);a?(this.d=this.f,this.h=this.i):(this.f=this.d,this.i=this.h);this.G=l};function Vc(a){a.F()?a.g():a.b();a.F()?a.k():a.j();a.F()?a.b():a.g();a.F()?a.j():a.k()}x(Vc,Bc);function Wc(){}x(Wc,V);p=Wc.prototype;p.a=k;p.m=k;p.Q=k;p.s=function(){this.Q=this.m=k};p.fa=o("control");p.W=function(){return this.a||document.body.createControlRange()};p.D=function(){return this.a?this.a.length:0};p.A=function (a){a=this.a.item(a);return Ec(Fc(a),h)};p.C=function(){return fb.apply(k,Uc(this))};p.b=function(){return Xc(this)[0]};p.j=o(0);p.g=function(){var a=Xc(this),b=z(a);return Pa(a,function (a){return G(a,b)})};p.k=function(){return this.g().childNodes.length};
function Uc(a){if(!a.m&&(a.m=[],a.a))for(var b=0;b<a.a.length;b++)a.m.push(a.a.item(b));return a.m}function Xc(a){a.Q||(a.Q=Uc(a).concat(),a.Q.sort(function (a,c){return a.sourceIndex-c.sourceIndex}));return a.Q}p.isCollapsed=function(){return!this.a||!this.a.length};p.r=function(){return new Yc(this)};p.select=function(){this.a&&this.a.select()};p.ga=function(){return new Zc(this)};p.collapse=function(){this.a=k;this.s()};function Zc(a){this.m=Uc(a)}x(Zc,Bc);
function Yc(a){a&&(this.m=Xc(a),this.f=this.m.shift(),this.d=z(this.m)||this.f);U.call(this,this.f,l)}x(Yc,U);p=Yc.prototype;p.f=k;p.d=k;p.m=k;p.b=n("f");p.g=n("d");p.M=function(){return!this.depth&&!this.m.length};p.next=function(){this.M()&&g(I);if(!this.depth){var a=this.m.shift();L(this,a,1,1);return a}return Yc.$.next.call(this)};function $c(){this.t=[];this.P=[];this.U=this.I=k}x($c,V);p=$c.prototype;p.Ea=Ac("goog.dom.MultiRange");p.s=function(){this.P=[];this.U=this.I=k};p.fa=o("mutli");p.W=function(){1<this.t.length&&this.Ea.log(vc,"getBrowserRangeObject called on MultiRange with more than 1 range",h);return this.t[0]};p.D=function(){return this.t.length};p.A=function (a){this.P[a]||(this.P[a]=Ec(new W(this.t[a]),h));return this.P[a]};
p.C=function(){if(!this.U){for(var a=[],b=0,c=this.D();b<c;b++)a.push(this.A(b).C());this.U=fb.apply(k,a)}return this.U};function ad(a){a.I||(a.I=Dc(a),a.I.sort(function (a,c){var d=a.b(),e=a.j(),f=c.b(),j=c.j();return d==f&&e==j?0:Lc(d,e,f,j)?1:-1}));return a.I}p.b=function(){return ad(this)[0].b()};p.j=function(){return ad(this)[0].j()};p.g=function(){return z(ad(this)).g()};p.k=function(){return z(ad(this)).k()};p.isCollapsed=function(){return 0==this.t.length||1==this.t.length&&this.A(0).isCollapsed()};
p.r=function(){return new bd(this)};p.select=function(){var a=Cc(this.qa());a.removeAllRanges();for(var b=0,c=this.D();b<c;b++)a.addRange(this.A(b).W())};p.ga=function(){return new cd(this)};p.collapse=function (a){if(!this.isCollapsed()){var b=a?this.A(0):this.A(this.D()-1);this.s();b.collapse(a);this.P=[b];this.I=[b];this.t=[b.W()]}};function cd(a){Ma(Dc(a),function (a){return a.ga()})}x(cd,Bc);function bd(a){a&&(this.H=Ma(ad(a),function (a){return sb(a)}));U.call(this,a?this.b():k,l)}x(bd,U);p=bd.prototype;
p.H=k;p.V=0;p.b=function(){return this.H[0].b()};p.g=function(){return z(this.H).g()};p.M=function(){return this.H[this.V].M()};p.next=function(){try{var a=this.H[this.V],b=a.next();L(this,a.p,a.q,a.depth);return b}catch(c){return(c!==I||this.H.length-1==this.V)&&g(c),this.V++,this.next()}};function Jc(a){var b,c=l;if(a.createRange)try{b=a.createRange()}catch(d){return k}else if(a.rangeCount){if(1<a.rangeCount){b=new $c;for(var c=0,e=a.rangeCount;c<e;c++)b.t.push(a.getRangeAt(c));return b}b=a.getRangeAt(0);c=Lc(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)}else return k;b&&b.addElement?(a=new Wc,a.a=b):a=Ec(new W(b),c);return a}
function Lc(a,b,c,d){if(a==c)return d<b;var e;if(1==a.nodeType&&b)if(e=a.childNodes[b])a=e,b=0;else if(G(a,c))return i;if(1==c.nodeType&&d)if(e=c.childNodes[d])c=e,d=0;else if(G(c,a))return l;return 0<(cb(a,c)||b-d)};function dd(){N.call(this);this.ja=k;this.B=new B(0,0);this.sa=l}x(dd,N);var Z={};Z[Zb]=[0,1,2,k];Z[$b]=[k,k,2,k];Z[fc]=[0,1,2,k];Z[dc]=[0,0,0,0];Z[cc]=[0,0,0,0];Z[ac]=Z[Zb];Z[bc]=Z[fc];Z[ec]=Z[dc];dd.prototype.move=function (a,b){var c=xb(a);this.B.x=b.x+c.x;this.B.y=b.y+c.y;a!=this.z()&&(c=this.z()===Ca.document.documentElement||this.z()===Ca.document.body,c=!this.sa&&c?k:this.z(),ed(this,dc,a),Ob(this,a),ed(this,ec,c));ed(this,cc)};
function ed(a,b,c){a.sa=i;var d=a.B,e;b in Z?(e=Z[b][a.ja===k?3:a.ja],e===k&&g(new y(13,"Event does not permit the specified mouse button."))):e=0;Mb(a.w,i)&&Eb(a.w)&&(c&&!(ec==b||dc==b)&&g(new y(12,"Event type does not allow related target: "+b)),c={clientX:d.x,clientY:d.y,button:e,altKey:l,ctrlKey:l,shiftKey:l,metaKey:l,wheelDelta:0,relatedTarget:c||k},(a=a.w)&&Sb(a,b,c))};function fd(){N.call(this);this.B=new B(0,0);this.ca=new B(0,0)}x(fd,N);fd.prototype.xa=0;fd.prototype.wa=0;fd.prototype.move=function (a,b,c){this.X()||Ob(this,a);a=xb(a);this.B.x=b.x+a.x;this.B.y=b.y+a.y;r(c)&&(this.ca.x=c.x+a.x,this.ca.y=c.y+a.y);if(this.X()){b=Rb;this.X()||g(new y(13,"Should never fire event when touchscreen is not pressed."));var d,e;this.wa&&(d=this.wa,e=this.ca);Pb(this,b,this.xa,this.B,d,e)}};fd.prototype.X=function(){return!!this.xa};function gd(a,b){this.x=a;this.y=b}x(gd,B);gd.prototype.scale=function (a){this.x*=a;this.y*=a;return this};gd.prototype.add=function (a){this.x+=a.x;this.y+=a.y;return this};function hd(){N.call(this)}x(hd,N);(function (a){a.Ba=function(){return a.Da||(a.Da=new a)}})(hd);function id(a){(!Mb(a,i)||!Eb(a))&&g(new y(12,"Element is not currently interactable and may not be manipulated"));(!Gb(a)||Bb(a,"readOnly"))&&g(new y(12,"Element must be user-editable in order to clear it."));var b=hd.Ba();Ob(b,a);var b=b.ua||b.w,c=D(b).activeElement;if(b!=c){if(c&&w(c.blur))try{c.blur()}catch(d){g(d)}w(b.focus)&&b.focus()}a.value&&(a.value="",Sb(a,Yb));Hb(a)&&(a.innerHTML=" ")}var jd=["_"],$=q;!(jd[0]in $)&&$.execScript&&$.execScript("var "+jd[0]);
for(var ld;jd.length&&(ld=jd.shift());)!jd.length&&r(id)?$[ld]=id:$=$[ld]?$[ld]:$[ld]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/domcore.js#L57
atom.getElementAttribute = function (element, name, window){return function(){var f=null,g=!1,h=this;
function i(a){var c=typeof a;if("object"==c)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return c;var b=Object.prototype.toString.call(a);if("[object Window]"==b)return"object";if("[object Array]"==b||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==b||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==
c&&"undefined"==typeof a.call)return"object";return c}function j(a,c){function b(){}b.prototype=c.prototype;a.f=c.prototype;a.prototype=new b};function k(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function l(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};var m,n="",o=/rv\:([^\);]+)(\)|;)/.exec(h.navigator?h.navigator.userAgent:f);m=n=o?o[1]:"";var p={};function q(a,c){this.code=a;this.message=c||"";this.name=r[a]||r[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}j(q,Error);
var r={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
q.prototype.toString=function(){return"["+this.name+"] "+this.message};function s(a){this.stack=Error().stack||"";a&&(this.message=""+a)}j(s,Error);s.prototype.name="CustomError";function t(a,c){c.unshift(a);s.call(this,k.apply(f,c));c.shift()}j(t,s);t.prototype.name="AssertionError";function u(a,c){if("string"==typeof a)return"string"!=typeof c||1!=c.length?-1:a.indexOf(c,0);for(var b=0;b<a.length;b++)if(b in a&&a[b]===c)return b;return-1};if(!p["1.9.1"]){for(var v=0,w=l(""+m).split("."),x=l("1.9.1").split("."),y=Math.max(w.length,x.length),z=0;0==v&&z<y;z++){var A=w[z]||"",B=x[z]||"",C=RegExp("(\\d*)(\\D*)","g"),D=RegExp("(\\d*)(\\D*)","g");do{var E=C.exec(A)||["","",""],F=D.exec(B)||["","",""];if(0==E[0].length&&0==F[0].length)break;v=((0==E[1].length?0:parseInt(E[1],10))<(0==F[1].length?0:parseInt(F[1],10))?-1:(0==E[1].length?0:parseInt(E[1],10))>(0==F[1].length?0:parseInt(F[1],10))?1:0)||((0==E[2].length)<(0==F[2].length)?-1:(0==
E[2].length)>(0==F[2].length)?1:0)||(E[2]<F[2]?-1:E[2]>F[2]?1:0)}while(0==v)}p["1.9.1"]=0<=v};var G={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},H={IMG:" ",BR:"\n"};function I(a,c,b){if(!(a.nodeName in G))if(3==a.nodeType)b?c.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):c.push(a.nodeValue);else if(a.nodeName in H)c.push(H[a.nodeName]);else for(a=a.firstChild;a;)I(a,c,b),a=a.nextSibling};(function(){var a=h.Components;if(!a)return g;try{if(!a.classes)return g}catch(c){return g}var b=a.classes,a=a.interfaces;b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();var J="StopIteration"in h?h.StopIteration:Error("StopIteration");function K(){}K.prototype.next=function(){throw J;};function L(a,c,b,d,e){this.a=!!c;a&&M(this,a,d);this.depth=void 0!=e?e:this.c||0;this.a&&(this.depth*=-1);this.e=!b}j(L,K);L.prototype.b=f;L.prototype.c=0;L.prototype.d=g;function M(a,c,b){if(a.b=c)a.c="number"==typeof b?b:1!=a.b.nodeType?0:a.a?-1:1}
L.prototype.next=function(){var a;if(this.d){if(!this.b||this.e&&0==this.depth)throw J;a=this.b;var c=this.a?-1:1;if(this.c==c){var b=this.a?a.lastChild:a.firstChild;b?M(this,b):M(this,a,-1*c)}else(b=this.a?a.previousSibling:a.nextSibling)?M(this,b):M(this,a.parentNode,-1*c);this.depth+=this.c*(this.a?-1:1)}else this.d=!0;a=this.b;if(!this.b)throw J;return a};
L.prototype.splice=function (a){var c=this.b,b=this.a?1:-1;this.c==b&&(this.c=-1*b,this.depth+=this.c*(this.a?-1:1));this.a=!this.a;L.prototype.next.call(this);this.a=!this.a;for(var b=arguments[0],d=i(b),b="array"==d||"object"==d&&"number"==typeof b.length?arguments[0]:arguments,d=b.length-1;0<=d;d--)c.parentNode&&c.parentNode.insertBefore(b[d],c.nextSibling);c&&c.parentNode&&c.parentNode.removeChild(c)};function N(a,c,b,d){L.call(this,a,c,b,f,d)}j(N,L);N.prototype.next=function(){do N.f.next.call(this);while(-1==this.c);return this.b};function O(a,c){return!!a&&1==a.nodeType&&(!c||a.tagName.toUpperCase()==c)}function P(a){return O(a,"OPTION")?!0:O(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):g}var Q={"class":"className",readonly:"readOnly"},R=["checked","disabled","draggable","hidden"];
function S(a,c){var b=Q[c]||c,d=a[b];if(void 0===d&&0<=u(R,b))return g;if(b="value"==c)if(b=O(a,"OPTION")){var e;b=c.toLowerCase();if(a.hasAttribute)e=a.hasAttribute(b);else try{e=a.attributes[b].specified}catch(Y){e=g}b=!e}b&&(d=[],I(a,d,g),d=d.join(""));return d}var T="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(",");
function U(a,c){if(8==a.nodeType)return f;c=c.toLowerCase();if("style"==c){var b=l(a.style.cssText).toLowerCase();return b=";"==b.charAt(b.length-1)?b:b+";"}b=a.getAttributeNode(c);return!b?f:0<=u(T,c)?"true":b.specified?b.value:f};function V(a,c){var b=f,d=c.toLowerCase();if("style"==c.toLowerCase()){if((b=a.style)&&"string"!=typeof b)b=b.cssText;return b}if("selected"==d||"checked"==d&&P(a)){if(!P(a))throw new q(15,"Element is not selectable");var e="selected",d=a.type&&a.type.toLowerCase();if("checkbox"==d||"radio"==d)e="checked";return S(a,e)?"true":f}b=O(a,"A");if(O(a,"IMG")&&"src"==d||b&&"href"==d)return(b=U(a,d))&&(b=S(a,d)),b;try{e=S(a,c)}catch(Y){}if(!(d=e==f))d=i(e),d="object"==d||"array"==d||"function"==d;b=d?U(a,
c):e;return b!=f?b.toString():f}var W=["_"],X=h;!(W[0]in X)&&X.execScript&&X.execScript("var "+W[0]);for(var Z;W.length&&(Z=W.shift());)!W.length&&void 0!==V?X[Z]=V:X=X[Z]?X[Z]:X[Z]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/dom.js#L979
atom.getElementText = function (element, window){return function(){var g=void 0,h=!0,i=null,j=!1,k=this;
function l(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function m(a){return"string"==typeof a}function n(a,b){function c(){}c.prototype=b.prototype;a.h=b.prototype;a.prototype=new c};function o(a){var b=a.length-1;return 0<=b&&a.indexOf(" ",b)==b}function aa(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function p(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}var q={};function ba(a){return q[a]||(q[a]=(""+a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()}))};var s,ca="",u=/rv\:([^\);]+)(\)|;)/.exec(k.navigator?k.navigator.userAgent:i);s=ca=u?u[1]:"";var v={};var da=window;function w(a,b){this.code=a;this.message=b||"";this.name=x[a]||x[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}n(w,Error);
var x={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
w.prototype.toString=function(){return"["+this.name+"] "+this.message};function y(a){this.stack=Error().stack||"";a&&(this.message=""+a)}n(y,Error);y.prototype.name="CustomError";function z(a,b){b.unshift(a);y.call(this,aa.apply(i,b));b.shift()}n(z,y);z.prototype.name="AssertionError";function ea(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(g,d[e],e,a)}function fa(a,b){for(var c=a.length,d=m(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(g,d[e],e,a))return h;return j}function A(a,b){var c;a:if(m(a))c=!m(b)||1!=b.length?-1:a.indexOf(b,0);else{for(c=0;c<a.length;c++)if(c in a&&a[c]===b)break a;c=-1}return 0<=c};var B;
if(!v["1.9.1"]){for(var C=0,D=p(""+s).split("."),E=p("1.9.1").split("."),ga=Math.max(D.length,E.length),F=0;0==C&&F<ga;F++){var ha=D[F]||"",ia=E[F]||"",ja=RegExp("(\\d*)(\\D*)","g"),ka=RegExp("(\\d*)(\\D*)","g");do{var G=ja.exec(ha)||["","",""],H=ka.exec(ia)||["","",""];if(0==G[0].length&&0==H[0].length)break;C=((0==G[1].length?0:parseInt(G[1],10))<(0==H[1].length?0:parseInt(H[1],10))?-1:(0==G[1].length?0:parseInt(G[1],10))>(0==H[1].length?0:parseInt(H[1],10))?1:0)||((0==G[2].length)<(0==H[2].length)?-1:
(0==G[2].length)>(0==H[2].length)?1:0)||(G[2]<H[2]?-1:G[2]>H[2]?1:0)}while(0==C)}v["1.9.1"]=0<=C};function I(a,b){this.x=a!==g?a:0;this.y=b!==g?b:0}I.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function J(a,b){this.width=a;this.height=b}J.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};var K=3;function L(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function la(a,b){var c=[];return M(a,b,c,h)?c[0]:g}function M(a,b,c,d){if(a!=i)for(a=a.firstChild;a;){if(b(a)&&(c.push(a),d)||M(a,b,c,d))return h;a=a.nextSibling}return j}function ma(a,b){for(var a=a.parentNode,c=0;a;){if(b(a))return a;a=a.parentNode;c++}return i}function N(a){this.g=a||k.document||document}
function na(a){var b=a.g,a="CSS1Compat"==b.compatMode?b.documentElement:b.body,b=b.parentWindow||b.defaultView;return new I(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)};var oa=function(){var a={i:"http://www.w3.org/2000/svg"};return function (b){return a[b]||i}}();
function pa(a,b){var c=function(){var c;a:{var e=L(b);if(e.implementation.hasFeature("XPath","3.0")){try{var f=e.createNSResolver?e.createNSResolver(e.documentElement):oa;c=e.evaluate(a,b,f,9,i);break a}catch(r){if("NS_ERROR_ILLEGAL_VALUE"!=r.name)throw new w(32,"Unable to locate an element with the xpath expression "+a+" because of the following error:\n"+r);}c=g}else c=i}return c?c.singleNodeValue||i:b.selectSingleNode?(c=L(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):
i}();if(c!==i&&(!c||1!=c.nodeType))throw new w(32,'The result of the xpath expression "'+a+'" is: '+c+". It should be an element.");return c};(function(){var a=k.Components;if(!a)return j;try{if(!a.classes)return j}catch(b){return j}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return h})();var P="StopIteration"in k?k.StopIteration:Error("StopIteration");function qa(){}qa.prototype.next=function(){throw P;};function Q(a,b,c,d,e){this.a=!!b;a&&R(this,a,d);this.depth=e!=g?e:this.c||0;this.a&&(this.depth*=-1);this.f=!c}n(Q,qa);Q.prototype.b=i;Q.prototype.c=0;Q.prototype.e=j;function R(a,b,c){if(a.b=b)a.c="number"==typeof c?c:1!=a.b.nodeType?0:a.a?-1:1}
Q.prototype.next=function(){var a;if(this.e){if(!this.b||this.f&&0==this.depth)throw P;a=this.b;var b=this.a?-1:1;if(this.c==b){var c=this.a?a.lastChild:a.firstChild;c?R(this,c):R(this,a,-1*b)}else(c=this.a?a.previousSibling:a.nextSibling)?R(this,c):R(this,a.parentNode,-1*b);this.depth+=this.c*(this.a?-1:1)}else this.e=h;a=this.b;if(!this.b)throw P;return a};
Q.prototype.splice=function (a){var b=this.b,c=this.a?1:-1;this.c==c&&(this.c=-1*c,this.depth+=this.c*(this.a?-1:1));this.a=!this.a;Q.prototype.next.call(this);this.a=!this.a;for(var c=arguments[0],d=l(c),c="array"==d||"object"==d&&"number"==typeof c.length?arguments[0]:arguments,d=c.length-1;0<=d;d--)b.parentNode&&b.parentNode.insertBefore(c[d],b.nextSibling);b&&b.parentNode&&b.parentNode.removeChild(b)};function S(a,b,c,d){Q.call(this,a,b,c,i,d)}n(S,Q);S.prototype.next=function(){do S.h.next.call(this);while(-1==this.c);return this.b};function ra(a,b){var c=L(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,i))?c[b]||c.getPropertyValue(b):""}function T(a,b){return ra(a,b)||(a.currentStyle?a.currentStyle[b]:i)||a.style&&a.style[b]}
function sa(a){for(var b=L(a),c=T(a,"position"),d="fixed"==c||"absolute"==c,a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=T(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return i}
function ta(a){var b=new I;if(1==a.nodeType)if(a.getBoundingClientRect)a=a.getBoundingClientRect(),b.x=a.left,b.y=a.top;else{var c=na(a?new N(L(a)):B||(B=new N));var d,e=L(a),f=T(a,"position"),r=e.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(a))&&(0>d.screenX||0>d.screenY),f=new I(0,0),t=(e?9==e.nodeType?e:L(e):document).documentElement;if(a!=t)if(a.getBoundingClientRect)d=a.getBoundingClientRect(),a=na(e?new N(L(e)):B||(B=new N)),f.x=d.left+a.x,f.y=d.top+a.y;else if(e.getBoxObjectFor&&
!r)d=e.getBoxObjectFor(a),a=e.getBoxObjectFor(t),f.x=d.screenX-a.screenX,f.y=d.screenY-a.screenY;else{d=a;do f.x+=d.offsetLeft,f.y+=d.offsetTop,d!=a&&(f.x+=d.clientLeft||0,f.y+=d.clientTop||0),d=d.offsetParent;while(d&&d!=a);for(d=a;(d=sa(d))&&d!=e.body&&d!=t;)f.x-=d.scrollLeft,f.y-=d.scrollTop}b.x=f.x-c.x;b.y=f.y-c.y}else c="function"==l(a.d),d=a,a.targetTouches?d=a.targetTouches[0]:c&&a.d().targetTouches&&(d=a.d().targetTouches[0]),b.x=d.clientX,b.y=d.clientY;return b}
function ua(a){var b=a.offsetWidth,c=a.offsetHeight;return b===g&&a.getBoundingClientRect?(a=a.getBoundingClientRect(),new J(a.right-a.left,a.bottom-a.top)):new J(b,c)};function U(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}var va="async,autofocus,autoplay,checked,compact,complete,controls,declare,defaultchecked,defaultselected,defer,disabled,draggable,ended,formnovalidate,hidden,indeterminate,iscontenteditable,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,paused,pubdate,readonly,required,reversed,scoped,seamless,seeking,selected,spellcheck,truespeed,willvalidate".split(",");
function V(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return U(a)?a:i}function W(a,b){b=ba(b);return ra(a,b)||wa(a,b)}function wa(a,b){var c=a.currentStyle||a.style,d=c[b];d===g&&"function"==l(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?d!==g?d:i:(c=V(a))?wa(c,b):i}
function xa(a){if("function"==l(a.getBBox))try{var b=a.getBBox();if(b)return b}catch(c){}if("none"!=T(a,"display"))a=ua(a);else{var b=a.style,d=b.display,e=b.visibility,f=b.position;b.visibility="hidden";b.position="absolute";b.display="inline";a=ua(a);b.display=d;b.position=f;b.visibility=e}return a}
function X(a,b){function c(a){if("none"==W(a,"display"))return j;a=V(a);return!a||c(a)}function d(a){var b=xa(a);return 0<b.height&&0<b.width?h:fa(a.childNodes,function (a){return a.nodeType==K||U(a)&&d(a)})}function e(a){var b=V(a);if(b&&"hidden"==W(b,"overflow")){var c=xa(b),d=ta(b),a=ta(a);return d.x+c.width<a.x||d.y+c.height<a.y?j:e(b)}return h}if(!U(a))throw Error("Argument to isShown must be of type Element");if(U(a,"OPTION")||U(a,"OPTGROUP")){var f=ma(a,function (a){return U(a,"SELECT")});return!!f&&
X(f,h)}if(U(a,"MAP")){if(!a.name)return j;f=L(a);f=f.evaluate?pa('/descendant::*[@usemap = "#'+a.name+'"]',f):la(f,function (b){var c;if(c=U(b))8==b.nodeType?b=i:(c="usemap","style"==c?(b=p(b.style.cssText).toLowerCase(),b=";"==b.charAt(b.length-1)?b:b+";"):(b=b.getAttributeNode(c),b=!b?i:A(va,c)?"true":b.specified?b.value:i)),c=b=="#"+a.name;return c});return!!f&&X(f,b)}return U(a,"AREA")?(f=ma(a,function (a){return U(a,"MAP")}),!!f&&X(f,b)):U(a,"INPUT")&&"hidden"==a.type.toLowerCase()||U(a,"NOSCRIPT")||
"hidden"==W(a,"visibility")||!c(a)||!b&&0==ya(a)||!d(a)||!e(a)?j:h}function za(a){return a.replace(/^[^\S\xa0]+|[^\S\xa0]+$/g,"")}
function Aa(a,b){if(U(a,"BR"))b.push("");else{var c=U(a,"TD"),d=W(a,"display"),e=!c&&!A(Ba,d);e&&!/^[\s\xa0]*$/.test(b[b.length-1]||"")&&b.push("");var f=X(a),r=i,t=i;f&&(r=W(a,"white-space"),t=W(a,"text-transform"));ea(a.childNodes,function (a){a.nodeType==K&&f?Ca(a,b,r,t):U(a)&&Aa(a,b)});var O=b[b.length-1]||"";if((c||"table-cell"==d)&&O&&!o(O))b[b.length-1]+=" ";e&&!/^[\s\xa0]*$/.test(O)&&b.push("")}}var Ba="inline,inline-block,inline-table,none,table-cell,table-column,table-column-group".split(",");
function Ca(a,b,c,d){a=a.nodeValue.replace(/\u200b/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if("normal"==c||"nowrap"==c)a=a.replace(/\n/g," ");a="pre"==c||"pre-wrap"==c?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[\ \f\t\v\u2028\u2029]+/g," ");"capitalize"==d?a=a.replace(/(^|\s)(\S)/g,function (a,b,c){return b+c.toUpperCase()}):"uppercase"==d?a=a.toUpperCase():"lowercase"==d&&(a=a.toLowerCase());c=b.pop()||"";o(c)&&0==a.lastIndexOf(" ",0)&&(a=a.substr(1));b.push(c+a)}
function ya(a){var b=1,c=W(a,"opacity");c&&(b=Number(c));(a=V(a))&&(b*=ya(a));return b};function Da(a){var b;a:{for(b=a;b;){if(b.tagName&&"head"==b.tagName.toLowerCase()){b=h;break a}try{b=b.parentNode}catch(c){break}}b=j}if(b)return b=L(a),"TITLE"==a.tagName.toUpperCase()&&(b?b.parentWindow||b.defaultView:window)==da.top?p(b.title):"";b=[];Aa(a,b);var d=b,a=d.length;b=Array(a);for(var d=m(d)?d.split(""):d,e=0;e<a;e++)e in d&&(b[e]=za.call(g,d[e]));return za(b.join("\n")).replace(/\xa0/g," ")}var Y=["_"],Z=k;!(Y[0]in Z)&&Z.execScript&&Z.execScript("var "+Y[0]);
for(var $;Y.length&&($=Y.shift());)!Y.length&&Da!==g?Z[$]=Da:Z=Z[$]?Z[$]:Z[$]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/dom.js#L189
atom.isElementEnabled = function (element, window){return function(){var e=this;function f(a,c){function b(){}b.prototype=c.prototype;a.d=c.prototype;a.prototype=new b};function g(a,c){for(var b=1;b<arguments.length;b++)var d=(""+arguments[b]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var h,i="",j=/rv\:([^\);]+)(\)|;)/.exec(e.navigator?e.navigator.userAgent:null);h=i=j?j[1]:"";var k={};function l(a,c){this.code=a;this.message=c||"";this.name=m[a]||m[13];var b=Error(this.message);b.name=this.name;this.stack=b.stack||""}f(l,Error);
var m={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
l.prototype.toString=function(){return"["+this.name+"] "+this.message};function n(a){this.stack=Error().stack||"";a&&(this.message=""+a)}f(n,Error);n.prototype.name="CustomError";function o(a,c){c.unshift(a);n.call(this,g.apply(null,c));c.shift()}f(o,n);o.prototype.name="AssertionError";function p(a,c){var b;a:if("string"==typeof a)b="string"!=typeof c||1!=c.length?-1:a.indexOf(c,0);else{for(b=0;b<a.length;b++)if(b in a&&a[b]===c)break a;b=-1}return 0<=b};if(!k["1.9.1"]){for(var q=0,r=(""+h).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),s="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),t=Math.max(r.length,s.length),u=0;0==q&&u<t;u++){var v=r[u]||"",w=s[u]||"",x=RegExp("(\\d*)(\\D*)","g"),z=RegExp("(\\d*)(\\D*)","g");do{var A=x.exec(v)||["","",""],B=z.exec(w)||["","",""];if(0==A[0].length&&0==B[0].length)break;q=((0==A[1].length?0:parseInt(A[1],10))<(0==B[1].length?0:parseInt(B[1],10))?-1:(0==A[1].length?0:parseInt(A[1],10))>(0==B[1].length?
0:parseInt(B[1],10))?1:0)||((0==A[2].length)<(0==B[2].length)?-1:(0==A[2].length)>(0==B[2].length)?1:0)||(A[2]<B[2]?-1:A[2]>B[2]?1:0)}while(0==q)}k["1.9.1"]=0<=q};(function(){var a=e.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(c){return!1}var b=a.classes,a=a.interfaces;b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function C(a,c,b,d,y){this.b=!!c;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=y?y:this.c||0;this.b&&(this.depth*=-1)}f(C,function(){});C.prototype.a=null;C.prototype.c=0;f(function (a,c,b,d){C.call(this,a,c,0,null,d)},C);var D={"class":"className",readonly:"readOnly"},E=["checked","disabled","draggable","hidden"],F="BUTTON,INPUT,OPTGROUP,OPTION,SELECT,TEXTAREA".split(",");function G(a){var c=a.tagName.toUpperCase();if(p(F,c)){var b;b=D.disabled||"disabled";var d=a[b];b=void 0===d&&p(E,b)?!1:d;a=b?!1:a.parentNode&&1==a.parentNode.nodeType&&"OPTGROUP"==c||"OPTION"==c?G(a.parentNode):!0}else a=!0;return a};var H=G,I=["_"],J=e;!(I[0]in J)&&J.execScript&&J.execScript("var "+I[0]);for(var K;I.length&&(K=I.shift());)!I.length&&void 0!==H?J[K]=H:J=J[K]?J[K]:J[K]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/domcore.js#L198
atom.isElementSelected = function (element, window){return function(){var f=!1,g=this;function h(a,b){function c(){}c.prototype=b.prototype;a.d=b.prototype;a.prototype=new c};function i(a,b){for(var c=1;c<arguments.length;c++)var d=(""+arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a};var k,l="",m=/rv\:([^\);]+)(\)|;)/.exec(g.navigator?g.navigator.userAgent:null);k=l=m?m[1]:"";var n={};function o(a,b){this.code=a;this.message=b||"";this.name=p[a]||p[13];var c=Error(this.message);c.name=this.name;this.stack=c.stack||""}h(o,Error);
var p={7:"NoSuchElementError",8:"NoSuchFrameError",9:"UnknownCommandError",10:"StaleElementReferenceError",11:"ElementNotVisibleError",12:"InvalidElementStateError",13:"UnknownError",15:"ElementNotSelectableError",19:"XPathLookupError",23:"NoSuchWindowError",24:"InvalidCookieDomainError",25:"UnableToSetCookieError",26:"ModalDialogOpenedError",27:"NoModalDialogOpenError",28:"ScriptTimeoutError",32:"InvalidSelectorError",33:"SqlDatabaseError",34:"MoveTargetOutOfBoundsError"};
o.prototype.toString=function(){return"["+this.name+"] "+this.message};function q(a){this.stack=Error().stack||"";a&&(this.message=""+a)}h(q,Error);q.prototype.name="CustomError";function r(a,b){b.unshift(a);q.call(this,i.apply(null,b));b.shift()}h(r,q);r.prototype.name="AssertionError";if(!n["1.9.1"]){for(var s=0,t=(""+k).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),u="1.9.1".replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),v=Math.max(t.length,u.length),w=0;0==s&&w<v;w++){var x=t[w]||"",y=u[w]||"",z=RegExp("(\\d*)(\\D*)","g"),A=RegExp("(\\d*)(\\D*)","g");do{var B=z.exec(x)||["","",""],C=A.exec(y)||["","",""];if(0==B[0].length&&0==C[0].length)break;s=((0==B[1].length?0:parseInt(B[1],10))<(0==C[1].length?0:parseInt(C[1],10))?-1:(0==B[1].length?0:parseInt(B[1],10))>(0==C[1].length?
0:parseInt(C[1],10))?1:0)||((0==B[2].length)<(0==C[2].length)?-1:(0==B[2].length)>(0==C[2].length)?1:0)||(B[2]<C[2]?-1:B[2]>C[2]?1:0)}while(0==s)}n["1.9.1"]=0<=s};var D={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},E={IMG:" ",BR:"\n"};function F(a,b,c){if(!(a.nodeName in D))if(3==a.nodeType)c?b.push((""+a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in E)b.push(E[a.nodeName]);else for(a=a.firstChild;a;)F(a,b,c),a=a.nextSibling};(function(){var a=g.Components;if(!a)return f;try{if(!a.classes)return f}catch(b){return f}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function G(a,b,c,d,e){this.b=!!b;if(a&&(this.a=a))this.c="number"==typeof d?d:1!=this.a.nodeType?0:this.b?-1:1;this.depth=void 0!=e?e:this.c||0;this.b&&(this.depth*=-1)}h(G,function(){});G.prototype.a=null;G.prototype.c=0;h(function (a,b,c,d){G.call(this,a,b,0,null,d)},G);function H(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function I(a){return H(a,"OPTION")?!0:H(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):f}var J={"class":"className",readonly:"readOnly"},K=["checked","disabled","draggable","hidden"];function L(a){if(I(a)){if(!I(a))throw new o(15,"Element is not selectable");var b="selected",c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";var c=b,d=J[c]||c,b=a[d],e;if(e=void 0===b){b:if("string"==typeof K)d="string"!=typeof d||1!=d.length?-1:K.indexOf(d,0);else{for(e=0;e<K.length;e++)if(e in K&&K[e]===d){d=e;break b}d=-1}e=0<=d}if(e)a=f;else{if(d="value"==c)if(d=H(a,"OPTION")){var j;c=c.toLowerCase();if(a.hasAttribute)j=a.hasAttribute(c);else try{j=a.attributes[c].specified}catch(P){j=
f}d=!j}d&&(j=[],F(a,j,f),b=j.join(""));a=b}a=!!a}else a=f;return a}var M=["_"],N=g;!(M[0]in N)&&N.execScript&&N.execScript("var "+M[0]);for(var O;M.length&&(O=M.shift());)!M.length&&void 0!==L?N[O]=L:N=N[O]?N[O]:N[O]={};; return this._.apply(null,arguments);}.apply({navigator:typeof window!='undefined'?window.navigator:null}, arguments);}

// https://github.com/SeleniumHQ/selenium/blob/master/javascript/atoms/dom.js#L435
atom.isElementDisplayed = function (element, window){return function(){function h(a){return function(){return a}}var k=this;
function m(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function n(a){return"string"==typeof a};function q(a){var b=0,c=String(r).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");a=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split(".");for(var d=Math.max(c.length,a.length),e=0;0==b&&e<d;e++){var f=c[e]||"",g=a[e]||"",w=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=w.exec(f)||["","",""],v=p.exec(g)||["","",""];if(0==l[0].length&&0==v[0].length)break;b=((0==l[1].length?0:parseInt(l[1],10))<(0==v[1].length?0:parseInt(v[1],10))?-1:(0==l[1].length?0:parseInt(l[1],10))>(0==v[1].length?
0:parseInt(v[1],10))?1:0)||((0==l[2].length)<(0==v[2].length)?-1:(0==l[2].length)>(0==v[2].length)?1:0)||(l[2]<v[2]?-1:l[2]>v[2]?1:0)}while(0==b)}return b}function aa(a){return String(a).replace(/\-([a-z])/g,function (a,c){return c.toUpperCase()})};var s=Array.prototype;function t(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)}function ba(a,b){if(a.reduce)return a.reduce(b,"");var c="";t(a,function (d,e){c=b.call(void 0,c,d,e,a)});return c}function ca(a,b){for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1}
function da(a,b){var c;a:if(n(a))c=n(b)&&1==b.length?a.indexOf(b,0):-1;else{for(c=0;c<a.length;c++)if(c in a&&a[c]===b)break a;c=-1}return 0<=c}function ea(a,b,c){return 2>=arguments.length?s.slice.call(a,b):s.slice.call(a,b,c)};var u={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var fa="background-color border-top-color border-right-color border-bottom-color border-left-color color outline-color".split(" "),ga=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/;function ha(a){if(!x.test(a))throw Error("'"+a+"' is not a valid hex color");4==a.length&&(a=a.replace(ga,"#$1$1$2$2$3$3"));return a.toLowerCase()}var x=/^#(?:[0-9a-f]{3}){1,2}$/i,ia=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i;
function ja(a){var b=a.match(ia);if(b){a=Number(b[1]);var c=Number(b[2]),d=Number(b[3]),b=Number(b[4]);if(0<=a&&255>=a&&0<=c&&255>=c&&0<=d&&255>=d&&0<=b&&1>=b)return[a,c,d,b]}return[]}var ka=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function la(a){var b=a.match(ka);if(b){a=Number(b[1]);var c=Number(b[2]),b=Number(b[3]);if(0<=a&&255>=a&&0<=c&&255>=c&&0<=b&&255>=b)return[a,c,b]}return[]};function y(a,b){this.code=a;this.state=z[a]||ma;this.message=b||"";var c=this.state.replace(/((?:^|\s+)[a-z])/g,function (a){return a.toUpperCase().replace(/^[\s\xa0]+/g,"")}),d=c.length-5;if(0>d||c.indexOf("Error",d)!=d)c+="Error";this.name=c;c=Error(this.message);c.name=this.name;this.stack=c.stack||""}(function(){var a=Error;function b(){}b.prototype=a.prototype;y.I=a.prototype;y.prototype=new b})();
var ma="unknown error",z={15:"element not selectable",11:"element not visible",31:"ime engine activation failed",30:"ime not available",24:"invalid cookie domain",29:"invalid element coordinates",12:"invalid element state",32:"invalid selector",51:"invalid selector",52:"invalid selector",17:"javascript error",405:"unsupported operation",34:"move target out of bounds",27:"no such alert",7:"no such element",8:"no such frame",23:"no such window",28:"script timeout",33:"session not created",10:"stale element reference",
0:"success",21:"timeout",25:"unable to set cookie",26:"unexpected alert open"};z[13]=ma;z[9]="unknown command";y.prototype.toString=function(){return this.name+": "+this.message};var r,na="",oa=/rv\:([^\);]+)(\)|;)/.exec(k.navigator?k.navigator.userAgent:null);r=na=oa?oa[1]:"";var A={};var B;A["1.9.1"]||(A["1.9.1"]=0<=q("1.9.1"));function C(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}C.prototype.toString=function(){return"("+this.x+", "+this.y+")"};C.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};C.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};C.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function D(a,b){this.width=a;this.height=b}D.prototype.toString=function(){return"("+this.width+" x "+this.height+")"};D.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};D.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};D.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var pa=3;function E(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
function qa(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?ra(a,b):!c&&E(e,b)?-1*sa(a,b):!d&&E(f,a)?sa(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=F(a);c=d.createRange();c.selectNode(a);c.collapse(!0);d=d.createRange();d.selectNode(b);d.collapse(!0);
return c.compareBoundaryPoints(k.Range.START_TO_END,d)}function sa(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return ra(d,a)}function ra(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1}function F(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function ta(a,b){a=a.parentNode;for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function G(a){this.p=a||k.document||document}
function ua(a){var b=a.p;a="CSS1Compat"==b.compatMode?b.documentElement:b.body;b=b.parentWindow||b.defaultView;return new C(b.pageXOffset||a.scrollLeft,b.pageYOffset||a.scrollTop)}G.prototype.contains=E;function H(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;for(var c=0,d=[],b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return""+b}
function I(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function J(a,b,c,d,e){return va.call(null,a,b,n(c)?c:null,n(d)?d:null,e||new K)}
function va(a,b,c,d,e){b.getElementsByName&&d&&"name"==c?(b=b.getElementsByName(d),t(b,function (b){a.matches(b)&&e.add(b)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),t(b,function (b){b.className==d&&a.matches(b)&&e.add(b)})):b.getElementsByTagName&&(b=b.getElementsByTagName(a.getName()),t(b,function (a){I(a,c,d)&&e.add(a)}));return e}function wa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)I(b,c,d)&&a.matches(b)&&e.add(b);return e};function K(){this.d=this.c=null;this.g=0}function xa(a){this.m=a;this.next=this.i=null}K.prototype.unshift=function (a){a=new xa(a);a.next=this.c;this.d?this.c.i=a:this.c=this.d=a;this.c=a;this.g++};K.prototype.add=function (a){a=new xa(a);a.i=this.d;this.c?this.d.next=a:this.c=this.d=a;this.d=a;this.g++};function ya(a){return(a=a.c)?a.m:null}function L(a){return new za(a,!1)}function za(a,b){this.F=a;this.j=(this.n=b)?a.d:a.c;this.r=null}
za.prototype.next=function(){var a=this.j;if(null==a)return null;var b=this.r=a;this.j=this.n?a.i:a.next;return b.m};function M(a,b,c,d,e){b=b.evaluate(d);c=c.evaluate(d);var f;if(b instanceof K&&c instanceof K){e=L(b);for(d=e.next();d;d=e.next())for(b=L(c),f=b.next();f;f=b.next())if(a(H(d),H(f)))return!0;return!1}if(b instanceof K||c instanceof K){b instanceof K?e=b:(e=c,c=b);e=L(e);b=typeof c;for(d=e.next();d;d=e.next()){switch(b){case "number":d=+H(d);break;case "boolean":d=!!H(d);break;case "string":d=H(d);break;default:throw Error("Illegal primitive type for comparison.");}if(a(d,c))return!0}return!1}return e?
"boolean"==typeof b||"boolean"==typeof c?a(!!b,!!c):"number"==typeof b||"number"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}function Aa(a,b,c,d){this.s=a;this.H=b;this.o=c;this.q=d}Aa.prototype.toString=function(){return this.s};var Ba={};function N(a,b,c,d){if(a in Ba)throw Error("Binary operator already created: "+a);a=new Aa(a,b,c,d);Ba[a.toString()]=a}N("div",6,1,function (a,b,c){return a.b(c)/b.b(c)});N("mod",6,1,function (a,b,c){return a.b(c)%b.b(c)});N("*",6,1,function (a,b,c){return a.b(c)*b.b(c)});
N("+",5,1,function (a,b,c){return a.b(c)+b.b(c)});N("-",5,1,function (a,b,c){return a.b(c)-b.b(c)});N("<",4,2,function (a,b,c){return M(function (a,b){return a<b},a,b,c)});N(">",4,2,function (a,b,c){return M(function (a,b){return a>b},a,b,c)});N("<=",4,2,function (a,b,c){return M(function (a,b){return a<=b},a,b,c)});N(">=",4,2,function (a,b,c){return M(function (a,b){return a>=b},a,b,c)});N("=",3,2,function (a,b,c){return M(function (a,b){return a==b},a,b,c,!0)});
N("!=",3,2,function (a,b,c){return M(function (a,b){return a!=b},a,b,c,!0)});N("and",2,2,function (a,b,c){return a.f(c)&&b.f(c)});N("or",1,2,function (a,b,c){return a.f(c)||b.f(c)});function Ca(a,b,c,d,e,f,g,w,p){this.h=a;this.o=b;this.D=c;this.C=d;this.B=e;this.q=f;this.A=g;this.w=void 0!==w?w:g;this.G=!!p}Ca.prototype.toString=function(){return this.h};var Da={};function O(a,b,c,d,e,f,g,w){if(a in Da)throw Error("Function already created: "+a+".");Da[a]=new Ca(a,b,c,d,!1,e,f,g,w)}O("boolean",2,!1,!1,function (a,b){return b.f(a)},1);O("ceiling",1,!1,!1,function (a,b){return Math.ceil(b.b(a))},1);
O("concat",3,!1,!1,function (a,b){var c=ea(arguments,1);return ba(c,function (b,c){return b+c.a(a)})},2,null);O("contains",2,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);return-1!=b.indexOf(a)},2);O("count",1,!1,!1,function (a,b){return b.evaluate(a).g},1,1,!0);O("false",2,!1,!1,h(!1),0);O("floor",1,!1,!1,function (a,b){return Math.floor(b.b(a))},1);
O("id",4,!1,!1,function (a,b){var c=a.e(),d=9==c.nodeType?c:c.ownerDocument,c=b.a(a).split(/\s+/),e=[];t(c,function (a){(a=d.getElementById(a))&&!da(e,a)&&e.push(a)});e.sort(qa);var f=new K;t(e,function (a){f.add(a)});return f},1);O("lang",2,!1,!1,h(!1),1);O("last",1,!0,!1,function (a){if(1!=arguments.length)throw Error("Function last expects ()");return a.u()},0);O("local-name",3,!1,!0,function (a,b){var c=b?ya(b.evaluate(a)):a.e();return c?c.nodeName.toLowerCase():""},0,1,!0);
O("name",3,!1,!0,function (a,b){var c=b?ya(b.evaluate(a)):a.e();return c?c.nodeName.toLowerCase():""},0,1,!0);O("namespace-uri",3,!0,!1,h(""),0,1,!0);O("normalize-space",3,!1,!0,function (a,b){return(b?b.a(a):H(a.e())).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);O("not",2,!1,!1,function (a,b){return!b.f(a)},1);O("number",1,!1,!0,function (a,b){return b?b.b(a):+H(a.e())},0,1);O("position",1,!0,!1,function (a){return a.v()},0);O("round",1,!1,!1,function (a,b){return Math.round(b.b(a))},1);
O("starts-with",2,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);return 0==b.lastIndexOf(a,0)},2);O("string",3,!1,!0,function (a,b){return b?b.a(a):H(a.e())},0,1);O("string-length",1,!1,!0,function (a,b){return(b?b.a(a):H(a.e())).length},0,1);
O("substring",3,!1,!1,function (a,b,c,d){c=c.b(a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?d.b(a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=b.a(a);if(Infinity==d)return a.substring(e);b=Math.round(d);return a.substring(e,c+b)},2,3);O("substring-after",3,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2);
O("substring-before",3,!1,!1,function (a,b,c){b=b.a(a);a=c.a(a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);O("sum",1,!1,!1,function (a,b){for(var c=L(b.evaluate(a)),d=0,e=c.next();e;e=c.next())d+=+H(e);return d},1,1,!0);O("translate",3,!1,!1,function (a,b,c,d){b=b.a(a);c=c.a(a);var e=d.a(a);a=[];for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c="";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);O("true",2,!1,!1,h(!0),0);function Ea(a,b,c,d){this.h=a;this.t=b;this.n=c;this.J=d}Ea.prototype.toString=function(){return this.h};var Fa={};function P(a,b,c,d){if(a in Fa)throw Error("Axis already created: "+a);Fa[a]=new Ea(a,b,c,!!d)}P("ancestor",function (a,b){for(var c=new K,d=b;d=d.parentNode;)a.matches(d)&&c.unshift(d);return c},!0);P("ancestor-or-self",function (a,b){var c=new K,d=b;do a.matches(d)&&c.unshift(d);while(d=d.parentNode);return c},!0);
P("attribute",function (a,b){var c=new K,d=a.getName(),e=b.attributes;if(e)if("*"==d)for(var d=0,f;f=e[d];d++)c.add(f);else(f=e.getNamedItem(d))&&c.add(f);return c},!1);P("child",function (a,b,c,d,e){return wa.call(null,a,b,n(c)?c:null,n(d)?d:null,e||new K)},!1,!0);P("descendant",J,!1,!0);P("descendant-or-self",function (a,b,c,d){var e=new K;I(b,c,d)&&a.matches(b)&&e.add(b);return J(a,b,c,d,e)},!1,!0);
P("following",function (a,b,c,d){var e=new K;do for(var f=b;f=f.nextSibling;)I(f,c,d)&&a.matches(f)&&e.add(f),e=J(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);P("following-sibling",function (a,b){for(var c=new K,d=b;d=d.nextSibling;)a.matches(d)&&c.add(d);return c},!1);P("namespace",function(){return new K},!1);P("parent",function (a,b){var c=new K;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;var d=b.parentNode;a.matches(d)&&c.add(d);return c},!1);
P("preceding",function (a,b,c,d){var e=new K,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,w=f.length;g<w;g++){var p=[];for(b=f[g];b=b.previousSibling;)p.unshift(b);for(var l=0,v=p.length;l<v;l++)b=p[l],I(b,c,d)&&a.matches(b)&&e.add(b),e=J(a,b,c,d,e)}return e},!0,!0);P("preceding-sibling",function (a,b){for(var c=new K,d=b;d=d.previousSibling;)a.matches(d)&&c.unshift(d);return c},!0);P("self",function (a,b){var c=new K;a.matches(b)&&c.add(b);return c},!1);var Ga=function(){var a={K:"http://www.w3.org/2000/svg"};return function (b){return a[b]||null}}();
function Ha(a,b){var c=function(){var c;a:{var e=F(b);try{var f=e.createNSResolver?e.createNSResolver(e.documentElement):Ga;c=e.evaluate(a,b,f,9,null);break a}catch(g){if("NS_ERROR_ILLEGAL_VALUE"!=g.name)throw new y(32,"Unable to locate an element with the xpath expression "+a+" because of the following error:\n"+g);}c=void 0}return c?c.singleNodeValue||null:b.selectSingleNode?(c=F(b),c.setProperty&&c.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new y(32,
'The result of the xpath expression "'+a+'" is: '+c+". It should be an element.");return c};(function(){var a=k.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(b){return!1}var c=a.classes,a=a.interfaces;c["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator);c["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo);return!0})();function Q(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}Q.prototype.toString=function(){return"("+this.left+", "+this.top+" - "+this.width+"w x "+this.height+"h)"};Q.prototype.contains=function (a){return a instanceof Q?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height};
Q.prototype.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Q.prototype.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
Q.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ia(a,b){var c=F(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""}function R(a,b){return Ia(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]}function Ja(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}return b}
function Ka(a){var b=F(a),c=R(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=R(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null}
function La(a){if(1==a.nodeType){var b;if(a.getBoundingClientRect)b=Ja(a),b=new C(b.left,b.top);else{b=ua(a?new G(F(a)):B||(B=new G));var c,d=F(a),e=R(a,"position"),f=d.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==e&&(c=d.getBoxObjectFor(a))&&(0>c.screenX||0>c.screenY),e=new C(0,0),g=(d?F(d):document).documentElement;if(a!=g)if(a.getBoundingClientRect)c=Ja(a),d=ua(d?new G(F(d)):B||(B=new G)),e.x=c.left+d.x,e.y=c.top+d.y;else if(d.getBoxObjectFor&&!f)c=d.getBoxObjectFor(a),d=d.getBoxObjectFor(g),
e.x=c.screenX-d.screenX,e.y=c.screenY-d.screenY;else{c=a;do e.x+=c.offsetLeft,e.y+=c.offsetTop,c!=a&&(e.x+=c.clientLeft||0,e.y+=c.clientTop||0),c=c.offsetParent;while(c&&c!=a);for(c=a;(c=Ka(c))&&c!=d.body&&c!=g;)e.x-=c.scrollLeft,e.y-=c.scrollTop}b=new C(e.x-b.x,e.y-b.y)}A[12]||(A[12]=0<=q(12))?a=b:((c=R(a,"-moz-transform"))||(c=R(a,"transform")),a=c?(a=c.match(Ma))?new C(parseFloat(a[1]),parseFloat(a[2])):new C(0,0):new C(0,0),a=new C(b.x+a.x,b.y+a.y));return a}b="function"==m(a.k);c=a;a.targetTouches?
c=a.targetTouches[0]:b&&a.k().targetTouches&&(c=a.k().targetTouches[0]);return new C(c.clientX,c.clientY)}var Ma=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function S(a,b){return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function T(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return S(a)?a:null}
function U(a,b){var c=aa(b);if("float"==c||"cssFloat"==c||"styleFloat"==c)c="cssFloat";c=Ia(a,c)||Na(a,c);if(null===c)c=null;else if(da(fa,b)&&(x.test("#"==c.charAt(0)?c:"#"+c)||la(c).length||u&&u[c.toLowerCase()]||ja(c).length)){var d=ja(c);if(!d.length){a:if(d=la(c),!d.length){d=(d=u[c.toLowerCase()])?d:"#"==c.charAt(0)?c:"#"+c;if(x.test(d)&&(d=ha(d),d=ha(d),d=[parseInt(d.substr(1,2),16),parseInt(d.substr(3,2),16),parseInt(d.substr(5,2),16)],d.length))break a;d=[]}3==d.length&&d.push(1)}c=4!=d.length?
c:"rgba("+d.join(", ")+")"}return c}function Na(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&"function"==m(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?void 0!==d?d:null:(c=T(a))?Na(c,b):null}
function V(a,b){function c(a){if("none"==U(a,"display"))return!1;a=T(a);return!a||c(a)}function d(a){if(a.hasAttribute){if(a.hasAttribute("hidden"))return!1}else return!0;a=T(a);return!a||d(a)}function e(a){var b=W(a);return 0<b.height&&0<b.width?!0:S(a,"PATH")&&(0<b.height||0<b.width)?(a=U(a,"stroke-width"),!!a&&0<parseInt(a,10)):"hidden"!=U(a,"overflow")&&ca(a.childNodes,function (a){return a.nodeType==pa||S(a)&&e(a)})}function f(a){var b=U(a,"-o-transform")||U(a,"-webkit-transform")||U(a,"-ms-transform")||
U(a,"-moz-transform")||U(a,"transform");if(b&&"none"!==b)return b=La(a),a=W(a),0<=b.x+a.width&&0<=b.y+a.height?!0:!1;a=T(a);return!a||f(a)}if(!S(a))throw Error("Argument to isShown must be of type Element");if(S(a,"OPTION")||S(a,"OPTGROUP")){var g=ta(a,function (a){return S(a,"SELECT")});return!!g&&V(g,!0)}return(g=Oa(a))?!!g.l&&0<g.rect.width&&0<g.rect.height&&V(g.l,b):S(a,"INPUT")&&"hidden"==a.type.toLowerCase()||S(a,"NOSCRIPT")||"hidden"==U(a,"visibility")||!c(a)||!b&&0==Pa(a)||!d(a)||!e(a)||Qa(a)==
X?!1:f(a)}var X="hidden";
function Qa(a){function b(a){var b=a;if("visible"==w)if(a==f)b=g;else if(a==g)return{x:"visible",y:"visible"};b={x:U(b,"overflow-x"),y:U(b,"overflow-y")};a==f&&(b.x="hidden"==b.x?"hidden":"auto",b.y="hidden"==b.y?"hidden":"auto");return b}function c(a){var b=U(a,"position");if("fixed"==b)return f;for(a=T(a);a&&a!=f&&(0==U(a,"display").lastIndexOf("inline",0)||"absolute"==b&&"static"==U(a,"position"));)a=T(a);return a}var d=W(a),e=F(a),f=e.documentElement,g=e.body||f,w=U(f,"overflow");for(a=c(a);a;a=
c(a)){var p=W(a),e=b(a),l=d.left>=p.left+p.width,p=d.top>=p.top+p.height;if(l&&"hidden"==e.x||p&&"hidden"==e.y)return X;if(l&&"visible"!=e.x||p&&"visible"!=e.y)return Qa(a)==X?X:"scroll"}return"none"}
function W(a){var b=Oa(a);if(b)return b.rect;if("function"==m(a.getBBox))try{var c=a.getBBox();return new Q(c.x,c.y,c.width,c.height)}catch(d){if("NS_ERROR_FAILURE"===d.name||-1!=d.message.indexOf("Component returned failure code: 0x80004005"))return new Q(0,0,0,0);throw d;}else{if(S(a,"HTML"))return a=((F(a)?F(a).parentWindow||F(a).defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new D(a.clientWidth,a.clientHeight),new Q(0,0,a.width,a.height);b=La(a);
return new Q(b.x,b.y,a.offsetWidth,a.offsetHeight)}}
function Oa(a){var b=S(a,"MAP");if(!b&&!S(a,"AREA"))return null;var c=b?a:S(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;if(c&&c.name&&(d=Ha('/descendant::*[@usemap = "#'+c.name+'"]',F(c)))&&(e=W(d),!b&&"default"!=a.shape.toLowerCase())){var f=Ra(a);a=Math.min(Math.max(f.left,0),e.width);b=Math.min(Math.max(f.top,0),e.height);c=Math.min(f.width,e.width-a);f=Math.min(f.height,e.height-b);e=new Q(a+e.left,b+e.top,c,f)}return{l:d,rect:e||new Q(0,0,0,0)}}
function Ra(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){var b=a[0],c=a[1];return new Q(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new Q(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2<a.length){for(var b=a[0],c=a[1],d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new Q(b,c,d-b,e-c)}return new Q(0,0,0,0)}
function Pa(a){var b=1,c=U(a,"opacity");c&&(b=Number(c));(a=T(a))&&(b*=Pa(a));return b};var Sa=V,Y=["_"],Z=k;Y[0]in Z||!Z.execScript||Z.execScript("var "+Y[0]);for(var $;Y.length&&($=Y.shift());)Y.length||void 0===Sa?Z=Z[$]?Z[$]:Z[$]={}:Z[$]=Sa;; return this._.apply(null,arguments);}.apply({navigator:typeof window!=undefined?window.navigator:null,document:typeof window!=undefined?window.document:null}, arguments);}
PK
!<ISY0Y0$chrome/marionette/content/browser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
/* global frame */

const {utils: Cu} = Components;

Cu.import("chrome://marionette/content/element.js");
const {
  NoSuchWindowError,
  UnsupportedOperationError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/frame.js");

this.EXPORTED_SYMBOLS = ["browser"];

/** @namespace */
this.browser = {};

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

/**
 * Get the <code>&lt;xul:browser&gt;</code> for the specified tab.
 *
 * @param {Tab} tab
 *     The tab whose browser needs to be returned.
 *
 * @return {Browser}
 *     The linked browser for the tab or null if no browser can be found.
 */
browser.getBrowserForTab = function(tab) {
  // Fennec
  if ("browser" in tab) {
    return tab.browser;

  // Firefox
  } else if ("linkedBrowser" in tab) {
    return tab.linkedBrowser;
  }

  return null;
};

/**
 * Return the tab browser for the specified chrome window.
 *
 * @param {nsIDOMWindow} win
 *     The window whose tabbrowser needs to be accessed.
 *
 * @return {Tab}
 *     Tab browser or null if it's not a browser window.
 */
browser.getTabBrowser = function(win) {
  // Fennec
  if ("BrowserApp" in win) {
    return win.BrowserApp;

  // Firefox
  } else if ("gBrowser" in win) {
    return win.gBrowser;
  }

  return null;
};

/**
 * Creates a browsing context wrapper.
 *
 * Browsing contexts handle interactions with the browser, according to
 * the current environment (Firefox, Fennec).
 *
 * @param {nsIDOMWindow} win
 *     The window whose browser needs to be accessed.
 * @param {GeckoDriver} driver
 *     Reference to the driver the browser is attached to.
 */
browser.Context = class {

  /**
   * @param {ChromeWindow} win
   *     ChromeWindow that contains the top-level browsing context.
   * @param {GeckoDriver} driver
   *     Reference to driver instance.
   */
  constructor(win, driver) {
    this.window = win;
    this.driver = driver;

    // In Firefox this is <xul:tabbrowser> (not <xul:browser>!)
    // and BrowserApp in Fennec
    this.tabBrowser = browser.getTabBrowser(win);

    this.knownFrames = [];

    // Used to set curFrameId upon new session
    this.newSession = true;

    this.seenEls = new element.Store();

    // A reference to the tab corresponding to the current window handle,
    // if any.  Specifically, this.tab refers to the last tab that Marionette
    // switched to in this browser window. Note that this may not equal the
    // currently selected tab.  For example, if Marionette switches to tab
    // A, and then clicks on a button that opens a new tab B in the same
    // browser window, this.tab will still point to tab A, despite tab B
    // being the currently selected tab.
    this.tab = null;

    // Commands which trigger a page load can cause the frame script to be
    // reloaded. To not loose the currently active command, or any other
    // already pushed following command, store them as long as they haven't
    // been fully processed. The commands get flushed after a new browser
    // has been registered.
    this.pendingCommands = [];
    this._needsFlushPendingCommands = false;

    // We should have one frame.Manager per browser.Context so that we
    // can handle modals in each <xul:browser>.
    this.frameManager = new frame.Manager(driver);
    this.frameRegsPending = 0;

    // register all message listeners
    this.frameManager.addMessageManagerListeners(driver.mm);
    this.getIdForBrowser = driver.getIdForBrowser.bind(driver);
    this.updateIdForBrowser = driver.updateIdForBrowser.bind(driver);
  }

  /**
   * Returns the content browser for the currently selected tab.
   * If there is no tab selected, null will be returned.
   */
  get contentBrowser() {
    if (this.tab) {
      return browser.getBrowserForTab(this.tab);
    } else if (this.tabBrowser &&
        this.driver.isReftestBrowser(this.tabBrowser)) {
      return this.tabBrowser;
    }

    return null;
  }

  /**
   * The current frame ID is managed per browser element on desktop in
   * case the ID needs to be refreshed. The currently selected window is
   * identified by a tab.
   */
  get curFrameId() {
    let rv = null;
    if (this.tab || this.driver.isReftestBrowser(this.contentBrowser)) {
      rv = this.getIdForBrowser(this.contentBrowser);
    }
    return rv;
  }

  /**
   * Returns the current title of the content browser.
   *
   * @return {string}
   *     Read-only property containing the current title.
   *
   * @throws {NoSuchWindowError}
   *     If the current ChromeWindow does not have a content browser.
   */
  get currentTitle() {
    // Bug 1363368 - contentBrowser could be null until we wait for its
    // initialization been finished
    if (this.contentBrowser) {
      return this.contentBrowser.contentTitle;
    }
    throw new NoSuchWindowError(
        "Current window does not have a content browser");
  }

  /**
   * Returns the current URI of the content browser.
   *
   * @return {nsIURI}
   *     Read-only property containing the currently loaded URL.
   *
   * @throws {NoSuchWindowError}
   *     If the current ChromeWindow does not have a content browser.
   */
  get currentURI() {
    // Bug 1363368 - contentBrowser could be null until we wait for its
    // initialization been finished
    if (this.contentBrowser) {
      return this.contentBrowser.currentURI;
    }
    throw new NoSuchWindowError(
        "Current window does not have a content browser");
  }

  /**
   * Gets the position and dimensions of the top-level browsing context.
   *
   * @return {Map.<string, number>}
   *     Object with |x|, |y|, |width|, and |height| properties.
   */
  get rect() {
    return {
      x: this.window.screenX,
      y: this.window.screenY,
      width: this.window.outerWidth,
      height: this.window.outerHeight,
    };
  }

  /**
   * Retrieves the current tabmodal UI object.  According to the browser
   * associated with the currently selected tab.
   */
  getTabModalUI() {
    let br = this.contentBrowser;
    if (!br.hasAttribute("tabmodalPromptShowing")) {
      return null;
    }

    // The modal is a direct sibling of the browser element.
    // See tabbrowser.xml's getTabModalPromptBox.
    let modals = br.parentNode.getElementsByTagNameNS(
        XUL_NS, "tabmodalprompt");
    return modals[0].ui;
  }

  /**
   * Close the current window.
   *
   * @return {Promise}
   *     A promise which is resolved when the current window has been closed.
   */
  closeWindow() {
    return new Promise(resolve => {
      this.window.addEventListener("unload", ev => {
        resolve();
      }, {once: true});
      this.window.close();
    });
  }

  /**
   * Close the current tab.
   *
   * @return {Promise}
   *     A promise which is resolved when the current tab has been closed.
   *
   * @throws UnsupportedOperationError
   *     If tab handling for the current application isn't supported.
   */
  closeTab() {
    // If the current window is not a browser then close it directly. Do the
    // same if only one remaining tab is open, or no tab selected at all.
    if (!this.tabBrowser ||
        !this.tabBrowser.tabs ||
        this.tabBrowser.tabs.length === 1 ||
        !this.tab) {
      return this.closeWindow();
    }

    return new Promise((resolve, reject) => {
      if (this.tabBrowser.closeTab) {
        // Fennec
        this.tabBrowser.deck.addEventListener("TabClose", ev => {
          resolve();
        }, {once: true});
        this.tabBrowser.closeTab(this.tab);

      } else if (this.tabBrowser.removeTab) {
        // Firefox
        this.tab.addEventListener("TabClose", ev => {
          resolve();
        }, {once: true});
        this.tabBrowser.removeTab(this.tab);

      } else {
        reject(new UnsupportedOperationError(
            `closeTab() not supported in ${this.driver.appName}`));
      }
    });
  }

  /**
   * Opens a tab with given URI.
   *
   * @param {string} uri
   *      URI to open.
   */
  addTab(uri) {
    return this.tabBrowser.addTab(uri, true);
  }

  /**
   * Set the current tab.
   *
   * @param {number=} index
   *     Tab index to switch to. If the parameter is undefined,
   *     the currently selected tab will be used.
   * @param {nsIDOMWindow=} win
   *     Switch to this window before selecting the tab.
   * @param {boolean=} focus
   *      A boolean value which determins whether to focus
   *      the window. Defaults to true.
   *
   * @throws UnsupportedOperationError
   *     If tab handling for the current application isn't supported.
   */
  switchToTab(index, win, focus = true) {
    if (win) {
      this.window = win;
      this.tabBrowser = browser.getTabBrowser(win);
    }

    if (!this.tabBrowser) {
      return;
    }

    if (typeof index == "undefined") {
      this.tab = this.tabBrowser.selectedTab;
    } else {
      this.tab = this.tabBrowser.tabs[index];

      if (focus) {
        if (this.tabBrowser.selectTab) {
          // Fennec
          this.tabBrowser.selectTab(this.tab);

        } else if ("selectedTab" in this.tabBrowser) {
          // Firefox
          this.tabBrowser.selectedTab = this.tab;

        } else {
          throw new UnsupportedOperationError("switchToTab() not supported");
        }
      }
    }
  }

  /**
   * Registers a new frame, and sets its current frame id to this frame
   * if it is not already assigned, and if a) we already have a session
   * or b) we're starting a new session and it is the right start frame.
   *
   * @param {string} uid
   *     Frame uid for use by Marionette.
   * @param {xul:browser} target
   *     The <xul:browser> that was the target of the originating message.
   */
  register(uid, target) {
    if (this.tabBrowser) {
      // If we're setting up a new session on Firefox, we only process the
      // registration for this frame if it belongs to the current tab.
      if (!this.tab) {
        this.switchToTab();
      }

      if (target === this.contentBrowser) {
        this.updateIdForBrowser(this.contentBrowser, uid);
        this._needsFlushPendingCommands = true;
      }
    }

    // used to delete sessions
    this.knownFrames.push(uid);
  }

  /**
   * Flushes any queued pending commands after a reload of the frame script.
   */
  flushPendingCommands() {
    if (!this._needsFlushPendingCommands) {
      return;
    }

    this.pendingCommands.forEach(cb => cb());
    this.pendingCommands = [];
    this._needsFlushPendingCommands = false;
  }

  /**
    * This function intercepts commands interacting with content and queues
    * or executes them as needed.
    *
    * No commands interacting with content are safe to process until
    * the new listener script is loaded and registers itself.
    * This occurs when a command whose effect is asynchronous (such
    * as goBack) results in a reload of the frame script and new commands
    * are subsequently posted to the server.
    */
  executeWhenReady(cb) {
    if (this._needsFlushPendingCommands) {
      this.pendingCommands.push(cb);
    } else {
      cb();
    }
  }

};

/**
 * The window storage is used to save outer window IDs mapped to weak
 * references of Window objects.
 *
 * Usage:
 *
 *     let wins = new browser.Windows();
 *     wins.set(browser.outerWindowID, window);
 *
 *     ...
 *
 *     let win = wins.get(browser.outerWindowID);
 *
 */
browser.Windows = class extends Map {

  /**
   * Save a weak reference to the Window object.
   *
   * @param {string} id
   *     Outer window ID.
   * @param {Window} win
   *     Window object to save.
   *
   * @return {browser.Windows}
   *     Instance of self.
   */
  set(id, win) {
    let wref = Cu.getWeakReference(win);
    super.set(id, wref);
    return this;
  }

  /**
   * Get the window object stored by provided |id|.
   *
   * @param {string} id
   *     Outer window ID.
   *
   * @return {Window}
   *     Saved window object.
   *
   * @throws {RangeError}
   *     If |id| is not in the store.
   */
  get(id) {
    let wref = super.get(id);
    if (!wref) {
      throw new RangeError();
    }
    return wref.get();
  }

};
PK
!<*Ϻ$chrome/marionette/content/capture.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {utils: Cu} = Components;
Cu.importGlobalProperties(["crypto"]);

this.EXPORTED_SYMBOLS = ["capture"];

const CONTEXT_2D = "2d";
const BG_COLOUR = "rgb(255,255,255)";
const PNG_MIME = "image/png";
const XHTML_NS = "http://www.w3.org/1999/xhtml";

/**
 * Provides primitives to capture screenshots.
 *
 * @namespace
 */
this.capture = {};

capture.Format = {
  Base64: 0,
  Hash: 1,
};

/**
 * Take a screenshot of a single element.
 *
 * @param {Node} node
 *     The node to take a screenshot of.
 * @param {Array.<Node>=} highlights
 *     Optional array of nodes, around which a border will be marked to
 *     highlight them in the screenshot.
 *
 * @return {HTMLCanvasElement}
 *     The canvas element where the element has been painted on.
 */
capture.element = function(node, highlights = []) {
  let win = node.ownerGlobal;
  let rect = node.getBoundingClientRect();

  return capture.canvas(
      win,
      rect.left,
      rect.top,
      rect.width,
      rect.height,
      {highlights});
};

/**
 * Take a screenshot of the window's viewport by taking into account
 * the current offsets.
 *
 * @param {DOMWindow} win
 *     The DOM window providing the document element to capture,
 *     and the offsets for the viewport.
 * @param {Array.<Node>=} highlights
 *     Optional array of nodes, around which a border will be marked to
 *     highlight them in the screenshot.
 *
 * @return {HTMLCanvasElement}
 *     The canvas element where the viewport has been painted on.
 */
capture.viewport = function(win, highlights = []) {
  let rootNode = win.document.documentElement;

  return capture.canvas(
      win,
      win.pageXOffset,
      win.pageYOffset,
      rootNode.clientWidth,
      rootNode.clientHeight,
      {highlights});
};

/**
 * Low-level interface to draw a rectangle off the framebuffer.
 *
 * @param {DOMWindow} win
 *     The DOM window used for the framebuffer, and providing the interfaces
 *     for creating an HTMLCanvasElement.
 * @param {number} left
 *     The left, X axis offset of the rectangle.
 * @param {number} top
 *     The top, Y axis offset of the rectangle.
 * @param {number} width
 *     The width dimension of the rectangle to paint.
 * @param {number} height
 *     The height dimension of the rectangle to paint.
 * @param {Array.<Node>=} highlights
 *     Optional array of nodes, around which a border will be marked to
 *     highlight them in the screenshot.
 * @param {HTMLCanvasElement=} canvas
 *     Optional canvas to reuse for the screenshot.
 * @param {number=} flags
 *     Optional integer representing flags to pass to drawWindow; these
 *     are defined on CanvasRenderingContext2D.
 *
 * @return {HTMLCanvasElement}
 *     The canvas on which the selection from the window's framebuffer
 *     has been painted on.
 */
capture.canvas = function(win, left, top, width, height,
    {highlights = [], canvas = null, flags = null} = {}) {
  const scale = win.devicePixelRatio;

  if (canvas === null) {
    canvas = win.document.createElementNS(XHTML_NS, "canvas");
    canvas.width = width * scale;
    canvas.height = height * scale;
  }

  let ctx = canvas.getContext(CONTEXT_2D);
  if (flags === null) {
    flags = ctx.DRAWWINDOW_DRAW_CARET;
    // TODO(ato): https://bugzil.la/1377335
    //
    // Disabled in bug 1243415 for webplatform-test
    // failures due to out of view elements.  Needs
    // https://github.com/w3c/web-platform-tests/issues/4383 fixed.
    /*
    ctx.DRAWWINDOW_DRAW_VIEW;
    */
    // Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
    /*
    ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
    */
  }

  ctx.scale(scale, scale);
  ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
  if (highlights.length) {
    ctx = capture.highlight_(ctx, highlights, top, left);
  }

  return canvas;
};

capture.highlight_ = function(context, highlights, top = 0, left = 0) {
  if (!highlights) {
    throw new TypeError("Missing highlights");
  }

  context.lineWidth = "2";
  context.strokeStyle = "red";
  context.save();

  for (let el of highlights) {
    let rect = el.getBoundingClientRect();
    let oy = -top;
    let ox = -left;

    context.strokeRect(
        rect.left + ox,
        rect.top + oy,
        rect.width,
        rect.height);
  }

  return context;
};

/**
 * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
 *
 * @param {HTMLCanvasElement} canvas
 *     The canvas to encode.
 *
 * @return {string}
 *     A Base64 encoded string.
 */
capture.toBase64 = function(canvas) {
  let u = canvas.toDataURL(PNG_MIME);
  return u.substring(u.indexOf(",") + 1);
};

/**
* Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
*
* @param {HTMLCanvasElement} canvas
*     The canvas to encode.
*
* @return {string}
*     A hex digest of the SHA-256 hash of the base64 encoded string.
*/
capture.toHash = function(canvas) {
  let u = capture.toBase64(canvas);
  let buffer = new TextEncoder("utf-8").encode(u);
  return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
};

/**
* Convert buffer into to hex.
*
* @param {ArrayBuffer} buffer
*     The buffer containing the data to convert to hex.
*
* @return {string}
*     A hex digest of the input buffer.
*/
function hex(buffer) {
  let hexCodes = [];
  let view = new DataView(buffer);
  for (let i = 0; i < view.byteLength; i += 4) {
    let value = view.getUint32(i);
    let stringValue = value.toString(16);
    let padding = "00000000";
    let paddedValue = (padding + stringValue).slice(-padding.length);
    hexCodes.push(paddedValue);
  }
  return hexCodes.join("");
}
PK
!< ^Z!chrome/marionette/content/cert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.EXPORTED_SYMBOLS = ["cert"];

const registrar =
    Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
const sss = Cc["@mozilla.org/ssservice;1"]
    .getService(Ci.nsISiteSecurityService);

const CONTRACT_ID = "@mozilla.org/security/certoverride;1";
const CERT_PINNING_ENFORCEMENT_PREF =
    "security.cert_pinning.enforcement_level";
const HSTS_PRELOAD_LIST_PREF =
    "network.stricttransportsecurity.preloadlist";

/**
 * TLS certificate service override management for Marionette.
 *
 * @namespace
 */
this.cert = {
  Error: {
    Untrusted: 1,
    Mismatch: 2,
    Time: 4,
  },

  currentOverride: null,
};

/**
 * Installs a TLS certificate service override.
 *
 * The provided |service| must implement the |register| and |unregister|
 * functions that causes a new |nsICertOverrideService| interface
 * implementation to be registered with the |nsIComponentRegistrar|.
 *
 * After |service| is registered and made the |cert.currentOverride|,
 * |nsICertOverrideService| is reinitialised to cause all Gecko components
 * to pick up the new service.
 *
 * If an override is already installed, i.e. when |cert.currentOverride|
 * is not null, this functions acts as a NOOP.
 *
 * @param {cert.Override} service
 *     Service generator that registers and unregisters the XPCOM service.
 *
 * @throws {Components.Exception}
 *     If unable to register or initialise |service|.
 */
cert.installOverride = function(service) {
  if (this.currentOverride) {
    return;
  }

  service.register();
  cert.currentOverride = service;
};

/**
 * Uninstall a TLS certificate service override.
 *
 * After the service has been unregistered, |cert.currentOverride|
 * is reset to null.
 *
 * If there no current override installed, i.e. if |cert.currentOverride|
 * is null, this function acts as a NOOP.
 */
cert.uninstallOverride = function() {
  if (!cert.currentOverride) {
    return;
  }
  cert.currentOverride.unregister();
  this.currentOverride = null;
};

/**
 * Certificate override service that acts in an all-inclusive manner
 * on TLS certificates.
 *
 * When an invalid certificate is encountered, it is overriden
 * with the |matching| bit level, which is typically a combination of
 * |cert.Error.Untrusted|, |cert.Error.Mismatch|, and |cert.Error.Time|.
 *
 * @type cert.Override
 *
 * @throws {Components.Exception}
 *     If there are any problems registering the service.
 */
cert.InsecureSweepingOverride = function() {
  const CID = Components.ID("{4b67cce0-a51c-11e6-9598-0800200c9a66}");
  const DESC = "All-encompassing cert service that matches on a bitflag";

  // This needs to be an old-style class with a function constructor
  // and prototype assignment because... XPCOM.  Any attempt at
  // modernisation will be met with cryptic error messages which will
  // make your life miserable.
  let service = function() {};
  service.prototype = {
    hasMatchingOverride(
        aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
      aIsTemporary.value = false;
      aOverrideBits.value =
          cert.Error.Untrusted | cert.Error.Mismatch | cert.Error.Time;

      return true;
    },

    QueryInterface: XPCOMUtils.generateQI([Ci.nsICertOverrideService]),
  };
  let factory = XPCOMUtils.generateSingletonFactory(service);

  return {
    register() {
      // make it possible to register certificate overrides for domains
      // that use HSTS or HPKP
      Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
      Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0);

      registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
    },

    unregister() {
      registrar.unregisterFactory(CID, factory);

      Preferences.reset(HSTS_PRELOAD_LIST_PREF);
      Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF);

      // clear collected HSTS and HPKP state
      // through the site security service
      sss.clearAll();
      sss.clearPreloads();
    },
  };
};
PK
!<)
#chrome/marionette/content/cookie.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Services.jsm");

Cu.import("chrome://marionette/content/assert.js");
const {
  error,
  InvalidCookieDomainError,
} = Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["cookie"];

const IPV4_PORT_EXPR = /:\d+$/;

/** @namespace */
this.cookie = {
  manager: Services.cookies,
};

/**
 * @name Cookie
 *
 * @return {Object.<string, (number|boolean|string)>
 */

/**
 * Unmarshal a JSON Object to a cookie representation.
 *
 * Effectively this will run validation checks on |json|, which will
 * produce the errors expected by WebDriver if the input is not valid.
 *
 * @param {Object.<string, (number|boolean|string)>} json
 *     Cookie to be deserialised.  <var>name</var> and <var>value</var>
 *     are required fields which must be strings.  The <var>path</var>
 *     field is optional, but must be a string if provided.
 *     The <var>secure</var>, <var>httpOnly</var>, and
 *     <var>session</var>fields are similarly optional, but must be
 *     booleans.  Likewise, the <var>expiry</var> field is optional but
 *     must be unsigned integer.
 *
 * @return {Cookie}
 *     Valid cookie object.
 *
 * @throws {InvalidArgumentError}
 *     If any of the properties are invalid.
 */
cookie.fromJSON = function(json) {
  let newCookie = {};

  assert.object(json, error.pprint`Expected cookie object, got ${json}`);

  newCookie.name = assert.string(json.name, "Cookie name must be string");
  newCookie.value = assert.string(json.value, "Cookie value must be string");

  if (typeof json.path != "undefined") {
    newCookie.path = assert.string(json.path, "Cookie path must be string");
  }
  if (typeof json.secure != "undefined") {
    newCookie.secure = assert.boolean(json.secure, "Cookie secure flag must be boolean");
  }
  if (typeof json.httpOnly != "undefined") {
    newCookie.httpOnly = assert.boolean(json.httpOnly, "Cookie httpOnly flag must be boolean");
  }
  if (typeof json.session != "undefined") {
    newCookie.session = assert.boolean(json.session, "Cookie session flag must be boolean");
  }
  if (typeof json.expiry != "undefined") {
    newCookie.expiry = assert.positiveInteger(json.expiry, "Cookie expiry must be a positive integer");
  }

  return newCookie;
};

/**
 * Insert cookie to the cookie store.
 *
 * @param {Cookie} newCookie
 *     Cookie to add.
 * @param {string=} restrictToHost
 *     Perform test that <var>newCookie</var>'s domain matches this.
 *
 * @throws {TypeError}
 *     If <var>name</var>, <var>value</var>, or <var>domain</var> are
 *     not present and of the correct type.
 * @throws {InvalidCookieDomainError}
 *     If <var>restrictToHost</var> is set and <var>newCookie</var>'s
 *     domain does not match.
 */
cookie.add = function(newCookie, {restrictToHost = null} = {}) {
  assert.string(newCookie.name, "Cookie name must be string");
  assert.string(newCookie.value, "Cookie value must be string");
  assert.string(newCookie.domain, "Cookie domain must be string");

  if (typeof newCookie.path == "undefined") {
    newCookie.path = "/";
  }

  if (typeof newCookie.expiry == "undefined") {
    // twenty years into the future
    let date = new Date();
    let now = new Date(Date.now());
    date.setYear(now.getFullYear() + 20);
    newCookie.expiry = date.getTime() / 1000;
  }

  if (restrictToHost) {
    if (newCookie.domain !== restrictToHost) {
      throw new InvalidCookieDomainError(
          `Cookies may only be set ` +
          ` for the current domain (${restrictToHost})`);
    }
  }

  // remove port from domain, if present.
  // unfortunately this catches IPv6 addresses by mistake
  // TODO: Bug 814416
  newCookie.domain = newCookie.domain.replace(IPV4_PORT_EXPR, "");

  cookie.manager.add(
      newCookie.domain,
      newCookie.path,
      newCookie.name,
      newCookie.value,
      newCookie.secure,
      newCookie.httpOnly,
      newCookie.session,
      newCookie.expiry,
      {} /* origin attributes */);
};

/**
 * Remove cookie from the cookie store.
 *
 * @param {Cookie} toDelete
 *     Cookie to remove.
 */
cookie.remove = function(toDelete) {
  cookie.manager.remove(
      toDelete.domain,
      toDelete.name,
      toDelete.path,
      false,
      {} /* originAttributes */);
};

/**
 * Iterates over the cookies for the current <var>host</var>.  You may
 * optionally filter for specific paths on that <var>host</var> by
 * specifying a path in <var>currentPath</var>.
 *
 * @param {string} host
 *     Hostname to retrieve cookies for.
 * @param {string=} [currentPath="/"] currentPath
 *     Optionally filter the cookies for <var>host</var> for the
 *     specific path.  Defaults to "<tt>/</tt>", meaning all cookies
 *     for <var>host</var> are included.
 *
 * @return {Iterable.<Cookie>}
 *     Iterator.
 */
cookie.iter = function*(host, currentPath = "/") {
  assert.string(host, "host must be string");
  assert.string(currentPath, "currentPath must be string");

  const isForCurrentPath = path => currentPath.indexOf(path) != -1;

  let en = cookie.manager.getCookiesFromHost(host, {});
  while (en.hasMoreElements()) {
    let cookie = en.getNext().QueryInterface(Ci.nsICookie2);
    // take the hostname and progressively shorten
    let hostname = host;
    do {
      if ((cookie.host == "." + hostname || cookie.host == hostname) &&
          isForCurrentPath(cookie.path)) {
        yield {
          "name": cookie.name,
          "value": cookie.value,
          "path": cookie.path,
          "domain": cookie.host,
          "secure": cookie.isSecure,
          "httpOnly": cookie.isHttpOnly,
          "expiry": cookie.expiry,
        };
      }
      hostname = hostname.replace(/^.*?\./, "");
    } while (hostname.indexOf(".") != -1);
  }
};
PK
!<}22#chrome/marionette/content/driver.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
/* global XPCNativeWrapper */

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    .getService(Ci.mozIJSSubScriptLoader);

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/XPCOMUtils.jsm");

Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/addon.js");
Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/browser.js");
Cu.import("chrome://marionette/content/capture.js");
Cu.import("chrome://marionette/content/cert.js");
Cu.import("chrome://marionette/content/cookie.js");
Cu.import("chrome://marionette/content/element.js");
const {
  ElementNotInteractableError,
  error,
  InsecureCertificateError,
  InvalidArgumentError,
  InvalidCookieDomainError,
  InvalidSelectorError,
  NoAlertOpenError,
  NoSuchFrameError,
  NoSuchWindowError,
  SessionNotCreatedError,
  UnknownError,
  UnsupportedOperationError,
  WebDriverError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/evaluate.js");
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/interaction.js");
Cu.import("chrome://marionette/content/l10n.js");
Cu.import("chrome://marionette/content/legacyaction.js");
Cu.import("chrome://marionette/content/modal.js");
Cu.import("chrome://marionette/content/proxy.js");
Cu.import("chrome://marionette/content/reftest.js");
Cu.import("chrome://marionette/content/session.js");
Cu.import("chrome://marionette/content/wait.js");

Cu.importGlobalProperties(["URL"]);

this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];

var FRAME_SCRIPT = "chrome://marionette/content/listener.js";

const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
const CONTENT_LISTENER_PREF = "marionette.contentListener";

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

const SUPPORTED_STRATEGIES = new Set([
  element.Strategy.ClassName,
  element.Strategy.Selector,
  element.Strategy.ID,
  element.Strategy.TagName,
  element.Strategy.XPath,
  element.Strategy.Anon,
  element.Strategy.AnonAttribute,
]);

const logger = Log.repository.getLogger("Marionette");
const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
    .getService(Ci.nsIMessageBroadcaster);

/**
 * The Marionette WebDriver services provides a standard conforming
 * implementation of the W3C WebDriver specification.
 *
 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
 * @namespace driver
 */

// This is used to prevent newSession from returning before the telephony
// API's are ready; see bug 792647.  This assumes that marionette-server.js
// will be loaded before the 'system-message-listener-ready' message
// is fired.  If this stops being true, this approach will have to change.
var systemMessageListenerReady = false;
Services.obs.addObserver(function() {
  systemMessageListenerReady = true;
}, "system-message-listener-ready");

/**
 * @enum
 * @memberof driver
 */
this.Context = {
  CHROME: "chrome",
  CONTENT: "content",
};

/** @memberof driver */
this.Context.fromString = function(s) {
  s = s.toUpperCase();
  if (s in this) {
    return this[s];
  }
  return null;
};

/**
 * Helper function for converting a {@link nsISimpleEnumerator} to a
 * JavaScript iterator.
 *
 * @memberof driver
 *
 * @param {nsISimpleEnumerator} enumerator
 *     Enumerator to turn into  iterator.
 *
 * @return {Iterable}
 *     Iterator.
 */
function* enumeratorIterator(enumerator) {
  while (enumerator.hasMoreElements()) {
    yield enumerator.getNext();
  }
}

/**
 * Implements (parts of) the W3C WebDriver protocol.  GeckoDriver lives
 * in chrome space and mediates calls to the message listener of the current
 * browsing context's content frame message listener via ListenerProxy.
 *
 * Throughout this prototype, functions with the argument <var>cmd</var>'s
 * documentation refers to the contents of the <code>cmd.parameter</code>}
 * object.
 *
 * @class GeckoDriver
 *
 * @param {string} appName
 *     Description of the product, for example "Firefox".
 * @param {MarionetteServer} server
 *     The instance of Marionette server.
 */
this.GeckoDriver = function(appName, server) {
  this.appName = appName;
  this._server = server;

  this.sessionId = null;
  this.wins = new browser.Windows();
  this.browsers = {};
  // points to current browser
  this.curBrowser = null;
  // topmost chrome frame
  this.mainFrame = null;
  // chrome iframe that currently has focus
  this.curFrame = null;
  this.mozBrowserClose = null;
  this.currentFrameElement = null;
  // frame ID of the current remote frame, used for mozbrowserclose events
  this.oopFrameId = null;
  this.observing = null;
  this._browserIds = new WeakMap();

  // The curent context decides if commands should affect chrome- or
  // content space.
  this.context = Context.CONTENT;

  this.sandboxes = new Sandboxes(() => this.getCurrentWindow());
  this.legacyactions = new legacyaction.Chain();

  this.timer = null;
  this.inactivityTimer = null;

  this.testName = null;

  this.capabilities = new session.Capabilities();

  this.mm = globalMessageManager;
  this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this),
      () => this.curBrowser);

  // points to an alert instance if a modal dialog is present
  this.dialog = null;
  this.dialogHandler = this.globalModalDialogHandler.bind(this);
};

Object.defineProperty(GeckoDriver.prototype, "a11yChecks", {
  get() {
    return this.capabilities.get("moz:accessibilityChecks");
  },
});

/**
 * Returns the current URL of the ChromeWindow or content browser,
 * depending on context.
 *
 * @return {URL}
 *     Read-only property containing the currently loaded URL.
 */
Object.defineProperty(GeckoDriver.prototype, "currentURL", {
  get() {
    switch (this.context) {
      case Context.CHROME:
        let chromeWin = this.getCurrentWindow();
        return new URL(chromeWin.location.href);

      case Context.CONTENT:
        return new URL(this.curBrowser.currentURI.spec);

      default:
        throw TypeError(`Unknown context: ${this.context}`);
    }
  },
});

Object.defineProperty(GeckoDriver.prototype, "title", {
  get() {
    switch (this.context) {
      case Context.CHROME:
        let chromeWin = this.getCurrentWindow();
        return chromeWin.document.documentElement.getAttribute("title");

      case Context.CONTENT:
        return this.curBrowser.currentTitle;

      default:
        throw TypeError(`Unknown context: ${this.context}`);
    }
  },
});

Object.defineProperty(GeckoDriver.prototype, "proxy", {
  get() {
    return this.capabilities.get("proxy");
  },
});

Object.defineProperty(GeckoDriver.prototype, "secureTLS", {
  get() {
    return !this.capabilities.get("acceptInsecureCerts");
  },
});

Object.defineProperty(GeckoDriver.prototype, "timeouts", {
  get() {
    return this.capabilities.get("timeouts");
  },

  set(newTimeouts) {
    this.capabilities.set("timeouts", newTimeouts);
  },
});

Object.defineProperty(GeckoDriver.prototype, "windows", {
  get() {
    return enumeratorIterator(Services.wm.getEnumerator(null));
  },
});

Object.defineProperty(GeckoDriver.prototype, "windowHandles", {
  get() {
    let hs = [];

    for (let win of this.windows) {
      let tabBrowser = browser.getTabBrowser(win);

      // Only return handles for browser windows
      if (tabBrowser && tabBrowser.tabs) {
        tabBrowser.tabs.forEach(tab => {
          let winId = this.getIdForBrowser(browser.getBrowserForTab(tab));
          if (winId !== null) {
            hs.push(winId);
          }
        });
      }
    }

    return hs;
  },
});

Object.defineProperty(GeckoDriver.prototype, "chromeWindowHandles", {
  get() {
    let hs = [];

    for (let win of this.windows) {
      hs.push(getOuterWindowId(win));
    }

    return hs;
  },
});

GeckoDriver.prototype.QueryInterface = XPCOMUtils.generateQI([
  Ci.nsIMessageListener,
  Ci.nsIObserver,
  Ci.nsISupportsWeakReference,
]);

/**
 * Callback used to observe the creation of new modal or tab modal dialogs
 * during the session's lifetime.
 */
GeckoDriver.prototype.globalModalDialogHandler = function(subject, topic) {
  let winr;
  if (topic === modal.COMMON_DIALOG_LOADED) {
    // Always keep a weak reference to the current dialog
    winr = Cu.getWeakReference(subject);
  }
  this.dialog = new modal.Dialog(() => this.curBrowser, winr);
};

/**
 * Switches to the global ChromeMessageBroadcaster, potentially replacing
 * a frame-specific ChromeMessageSender.  Has no effect if the global
 * ChromeMessageBroadcaster is already in use.  If this replaces a
 * frame-specific ChromeMessageSender, it removes the message listeners
 * from that sender, and then puts the corresponding frame script "to
 * sleep", which removes most of the message listeners from it as well.
 */
GeckoDriver.prototype.switchToGlobalMessageManager = function() {
  if (this.curBrowser &&
      this.curBrowser.frameManager.currentRemoteFrame !== null) {
    this.curBrowser.frameManager.removeMessageManagerListeners(this.mm);
    this.sendAsync("sleepSession");
    this.curBrowser.frameManager.currentRemoteFrame = null;
  }
  this.mm = globalMessageManager;
};

/**
 * Helper method to send async messages to the content listener.
 * Correct usage is to pass in the name of a function in listener.js,
 * a serialisable object, and optionally the current command's ID
 * when not using the modern dispatching technique.
 *
 * @param {string} name
 *     Suffix of the targetted message listener
 *     <tt>Marionette:SUFFIX</tt>.
 * @param {Object=} msg
 *     Optional JSON serialisable object to send to the listener.
 * @param {number=} commandID
 *     Optional command ID to ensure synchronisity.
 */
GeckoDriver.prototype.sendAsync = function(name, data, commandID) {
  name = "Marionette:" + name;
  let payload = copy(data);

  // TODO(ato): When proxy.AsyncMessageChannel
  // is used for all chrome <-> content communication
  // this can be removed.
  if (commandID) {
    payload.command_id = commandID;
  }

  if (!this.curBrowser.frameManager.currentRemoteFrame) {
    this.broadcastDelayedAsyncMessage_(name, payload);
  } else {
    this.sendTargettedAsyncMessage_(name, payload);
  }
};

// eslint-disable-next-line
GeckoDriver.prototype.broadcastDelayedAsyncMessage_ = function(name, payload) {
  this.curBrowser.executeWhenReady(() => {
    if (this.curBrowser.curFrameId) {
      const target = name + this.curBrowser.curFrameId;
      this.mm.broadcastAsyncMessage(target, payload);
    } else {
      throw new NoSuchWindowError(
          "No such content frame; perhaps the listener was not registered?");
    }
  });
};

GeckoDriver.prototype.sendTargettedAsyncMessage_ = function(name, payload) {
  const curRemoteFrame = this.curBrowser.frameManager.currentRemoteFrame;
  const target = name + curRemoteFrame.targetFrameId;

  try {
    this.mm.sendAsyncMessage(target, payload);
  } catch (e) {
    switch (e.result) {
      case Cr.NS_ERROR_FAILURE:
      case Cr.NS_ERROR_NOT_INITIALIZED:
        throw new NoSuchWindowError();

      default:
        throw new WebDriverError(e);
    }
  }
};

/**
 * Get the session's current top-level browsing context.
 *
 * It will return the outer {@link ChromeWindow} previously selected by
 * window handle through {@link #switchToWindow}, or the first window that
 * was registered.
 *
 * @param {Context=} forcedContext
 *     Optional name of the context to use for finding the window.
 *     It will be required if a command always needs a specific context,
 *     whether which context is currently set. Defaults to the current
 *     context.
 *
 * @return {ChromeWindow}
 *     The current top-level browsing context.
 */
GeckoDriver.prototype.getCurrentWindow = function(forcedContext = undefined) {
  let context = typeof forcedContext == "undefined" ? this.context : forcedContext;
  let win = null;

  switch (context) {
    case Context.CHROME:
      if (this.curFrame !== null) {
        win = this.curFrame;
      } else if (this.curBrowser !== null) {
        win = this.curBrowser.window;
      }
      break;

    case Context.CONTENT:
      if (this.curFrame !== null) {
        win = this.curFrame;
      } else if (this.curBrowser !== null && this.curBrowser.contentBrowser) {
        win = this.curBrowser.window;
      }
      break;
  }

  return win;
};

GeckoDriver.prototype.isReftestBrowser = function(element) {
  return this._reftest &&
    element &&
    element.tagName === "xul:browser" &&
    element.parentElement &&
    element.parentElement.id === "reftest";
};

GeckoDriver.prototype.addFrameCloseListener = function(action) {
  let win = this.getCurrentWindow();
  this.mozBrowserClose = e => {
    if (e.target.id == this.oopFrameId) {
      win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
      this.switchToGlobalMessageManager();
      throw new NoSuchWindowError("The window closed during action: " + action);
    }
  };
  win.addEventListener("mozbrowserclose", this.mozBrowserClose, true);
};

/**
 * Create a new browsing context for window and add to known browsers.
 *
 * @param {nsIDOMWindow} win
 *     Window for which we will create a browsing context.
 *
 * @return {string}
 *     Returns the unique server-assigned ID of the window.
 */
GeckoDriver.prototype.addBrowser = function(win) {
  let bc = new browser.Context(win, this);
  let winId = getOuterWindowId(win);

  this.browsers[winId] = bc;
  this.curBrowser = this.browsers[winId];
  if (!this.wins.has(winId)) {
    // add this to seenItems so we can guarantee
    // the user will get winId as this window's id
    this.wins.set(winId, win);
  }
};

/**
 * Registers a new browser, win, with Marionette.
 *
 * If we have not seen the browser content window before, the listener
 * frame script will be loaded into it.  If isNewSession is true, we will
 * switch focus to the start frame when it registers.
 *
 * @param {nsIDOMWindow} win
 *     Window whose browser we need to access.
 * @param {boolean=} [false] isNewSession
 *     True if this is the first time we're talking to this browser.
 */
GeckoDriver.prototype.startBrowser = function(win, isNewSession = false) {
  this.mainFrame = win;
  this.curFrame = null;
  this.addBrowser(win);
  this.curBrowser.isNewSession = isNewSession;
  this.whenBrowserStarted(win, isNewSession);
};

/**
 * Callback invoked after a new session has been started in a browser.
 * Loads the Marionette frame script into the browser if needed.
 *
 * @param {nsIDOMWindow} win
 *     Window whose browser we need to access.
 * @param {boolean} isNewSession
 *     True if this is the first time we're talking to this browser.
 */
GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
  let mm = win.window.messageManager;
  if (mm) {
    if (!isNewSession) {
      // Loading the frame script corresponds to a situation we need to
      // return to the server. If the messageManager is a message broadcaster
      // with no children, we don't have a hope of coming back from this
      // call, so send the ack here. Otherwise, make a note of how many
      // child scripts will be loaded so we known when it's safe to return.
      // Child managers may not have child scripts yet (e.g. socialapi),
      // only count child managers that have children, but only count the top
      // level children as they are the ones that we expect a response from.
      if (mm.childCount !== 0) {
        this.curBrowser.frameRegsPending = 0;
        for (let i = 0; i < mm.childCount; i++) {
          if (mm.getChildAt(i).childCount !== 0) {
            this.curBrowser.frameRegsPending += 1;
          }
        }
      }
    }

    if (!Preferences.get(CONTENT_LISTENER_PREF) || !isNewSession) {
      // load listener into the remote frame
      // and any applicable new frames
      // opened after this call
      mm.loadFrameScript(FRAME_SCRIPT, true);
      Preferences.set(CONTENT_LISTENER_PREF, true);
    }
  } else {
    logger.error(
        `Could not load listener into content for page ${win.location.href}`);
  }
};

/**
 * Recursively get all labeled text.
 *
 * @param {nsIDOMElement} el
 *     The parent element.
 * @param {Array.<string>} lines
 *      Array that holds the text lines.
 */
GeckoDriver.prototype.getVisibleText = function(el, lines) {
  try {
    if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
      if (el.value) {
        lines.push(el.value);
      }
      for (let child in el.childNodes) {
        this.getVisibleText(el.childNodes[child], lines);
      }
    }
  } catch (e) {
    if (el.nodeName == "#text") {
      lines.push(el.textContent);
    }
  }
};

/**
 * Handles registration of new content listener browsers.  Depending on
 * their type they are either accepted or ignored.
 */
GeckoDriver.prototype.registerBrowser = function(id, be) {
  let nullPrevious = this.curBrowser.curFrameId === null;
  let listenerWindow = Services.wm.getOuterWindowWithId(id);

  // go in here if we're already in a remote frame
  if (this.curBrowser.frameManager.currentRemoteFrame !== null &&
      (!listenerWindow || this.mm == this.curBrowser.frameManager
          .currentRemoteFrame.messageManager.get())) {
    // The outerWindowID from an OOP frame will not be meaningful to
    // the parent process here, since each process maintains its own
    // independent window list.  So, it will either be null (!listenerWindow)
    // if we're already in a remote frame, or it will point to some
    // random window, which will hopefully cause an href mismatch.
    // Currently this only happens in B2G for OOP frames registered in
    // Marionette:switchToFrame, so we'll acknowledge the switchToFrame
    // message here.
    //
    // TODO: Should have a better way of determining that this message
    // is from a remote frame.
    this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = id;
  }

  // We want to ignore frames that are XUL browsers that aren't in the "main"
  // tabbrowser, but accept things on Fennec (which doesn't have a
  // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
  // as well as XUL frames. Ideally this should be cleaned up and we should
  // keep track of browsers a different way.
  if (this.appName != "Firefox" || be.namespaceURI != XUL_NS ||
      be.nodeName != "browser" || be.getTabBrowser()) {
    // curBrowser holds all the registered frames in knownFrames
    this.curBrowser.register(id, be);
  }

  this.wins.set(id, listenerWindow);
  if (nullPrevious && (this.curBrowser.curFrameId !== null)) {
    this.sendAsync(
        "newSession",
        this.capabilities.toJSON(),
        this.newSessionCommandId);
    if (this.curBrowser.isNewSession) {
      this.newSessionCommandId = null;
    }
  }

  return [id, this.capabilities.toJSON()];
};

GeckoDriver.prototype.registerPromise = function() {
  const li = "Marionette:register";

  return new Promise(resolve => {
    let cb = msg => {
      let wid = msg.json.value;
      let be = msg.target;
      let rv = this.registerBrowser(wid, be);

      if (this.curBrowser.frameRegsPending > 0) {
        this.curBrowser.frameRegsPending--;
      }

      if (this.curBrowser.frameRegsPending === 0) {
        this.mm.removeMessageListener(li, cb);
        resolve();
      }

      // this is a sync message and listeners expect the ID back
      return rv;
    };
    this.mm.addMessageListener(li, cb);
  });
};

GeckoDriver.prototype.listeningPromise = function() {
  const li = "Marionette:listenersAttached";

  return new Promise(resolve => {
    let cb = msg => {
      if (msg.json.listenerId === this.curBrowser.curFrameId) {
        this.mm.removeMessageListener(li, cb);
        resolve();
      }
    };
    this.mm.addMessageListener(li, cb);
  });
};

/** Create a new session. */
GeckoDriver.prototype.newSession = function* (cmd, resp) {
  if (this.sessionId) {
    throw new SessionNotCreatedError("Maximum number of active sessions");
  }

  this.sessionId = cmd.parameters.sessionId ||
      cmd.parameters.session_id ||
      element.generateUUID();
  this.newSessionCommandId = cmd.id;

  try {
    this.capabilities = session.Capabilities.fromJSON(
        cmd.parameters.capabilities, {merge: true});
    logger.config("Matched capabilities: " +
        JSON.stringify(this.capabilities));
  } catch (e) {
    throw new SessionNotCreatedError(e);
  }

  if (!this.secureTLS) {
    logger.warn("TLS certificate errors will be ignored for this session");
    let acceptAllCerts = new cert.InsecureSweepingOverride();
    cert.installOverride(acceptAllCerts);
  }

  if (this.proxy.init()) {
    logger.info("Proxy settings initialised: " + JSON.stringify(this.proxy));
  }

  // If we are testing accessibility with marionette, start a11y service in
  // chrome first. This will ensure that we do not have any content-only
  // services hanging around.
  if (this.a11yChecks && accessibility.service) {
    logger.info("Preemptively starting accessibility service in Chrome");
  }

  let registerBrowsers = this.registerPromise();
  let browserListening = this.listeningPromise();

  let waitForWindow = function() {
    let win = Services.wm.getMostRecentWindow("navigator:browser");
    if (!win) {
      // if the window isn't even created, just poll wait for it
      let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      checkTimer.initWithCallback(waitForWindow.bind(this), 100,
          Ci.nsITimer.TYPE_ONE_SHOT);
    } else if (win.document.readyState != "complete") {
      // otherwise, wait for it to be fully loaded before proceeding
      let listener = ev => {
        // ensure that we proceed, on the top level document load event
        // (not an iframe one...)
        if (ev.target != win.document) {
          return;
        }
        win.removeEventListener("load", listener);
        waitForWindow.call(this);
      };
      win.addEventListener("load", listener, true);
    } else {
      let clickToStart = Preferences.get(CLICK_TO_START_PREF);
      if (clickToStart) {
        Services.prompt.alert(win, "", "Click to start execution of marionette tests");
      }
      this.startBrowser(win, true);
    }
  };

  if (!Preferences.get(CONTENT_LISTENER_PREF)) {
    waitForWindow.call(this);
  } else if (this.appName != "Firefox" && this.curBrowser === null) {
    // if there is a content listener, then we just wake it up
    let win = this.getCurrentWindow();
    this.addBrowser(win);
    this.whenBrowserStarted(win, false);
    this.mm.broadcastAsyncMessage("Marionette:restart", {});
  } else {
    throw new WebDriverError("Session already running");
  }
  this.switchToGlobalMessageManager();

  yield registerBrowsers;
  yield browserListening;

  if (this.curBrowser.tab) {
    this.curBrowser.contentBrowser.focus();
  }

  // Setup global listener for modal dialogs, and check if there is already
  // one open for the currently selected browser window.
  modal.addHandler(this.dialogHandler);
  this.dialog = modal.findModalDialogs(this.curBrowser);

  return {
    sessionId: this.sessionId,
    capabilities: this.capabilities,
  };
};

/**
 * Send the current session's capabilities to the client.
 *
 * Capabilities informs the client of which WebDriver features are
 * supported by Firefox and Marionette.  They are immutable for the
 * length of the session.
 *
 * The return value is an immutable map of string keys
 * ("capabilities") to values, which may be of types boolean,
 * numerical or string.
 */
GeckoDriver.prototype.getSessionCapabilities = function(cmd, resp) {
  resp.body.capabilities = this.capabilities;
};

/**
 * Sets the context of the subsequent commands to be either "chrome" or
 * "content".
 *
 * @param {string} value
 *     Name of the context to be switched to.  Must be one of "chrome" or
 *     "content".
 */
GeckoDriver.prototype.setContext = function(cmd, resp) {
  let val = cmd.parameters.value;
  let ctx = Context.fromString(val);
  if (ctx === null) {
    throw new WebDriverError(`Invalid context: ${val}`);
  }
  this.context = ctx;
};

/** Gets the context of the server, either "chrome" or "content". */
GeckoDriver.prototype.getContext = function(cmd, resp) {
  resp.body.value = this.context.toString();
};

/**
 * Executes a JavaScript function in the context of the current browsing
 * context, if in content space, or in chrome space otherwise, and returns
 * the return value of the function.
 *
 * It is important to note that if the <var>sandboxName</var> parameter
 * is left undefined, the script will be evaluated in a mutable sandbox,
 * causing any change it makes on the global state of the document to have
 * lasting side-effects.
 *
 * @param {string} script
 *     Script to evaluate as a function body.
 * @param {Array.<(string|boolean|number|object|WebElement)>} args
 *     Arguments exposed to the script in <code>arguments</code>.
 *     The array items must be serialisable to the WebDriver protocol.
 * @param {number} scriptTimeout
 *     Duration in milliseconds of when to interrupt and abort the
 *     script evaluation.
 * @param {string=} sandbox
 *     Name of the sandbox to evaluate the script in.  The sandbox is
 *     cached for later re-use on the same Window object if
 *     <var>newSandbox</var> is false.  If he parameter is undefined,
 *     the script is evaluated in a mutable sandbox.  If the parameter
 *     is "system", it will be evaluted in a sandbox with elevated system
 *     privileges, equivalent to chrome space.
 * @param {boolean=} newSandbox
 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
 *     it is undefined, the script will normally be evaluted in a fresh
 *     sandbox.
 * @param {string=} filename
 *     Filename of the client's program where this script is evaluated.
 * @param {number=} line
 *     Line in the client's program where this script is evaluated.
 * @param {boolean=} debug_script
 *     Attach an <code>onerror</code> event handler on the {@link Window}
 *     object.  It does not differentiate content errors from chrome errors.
 * @param {boolean=} directInject
 *     Evaluate the script without wrapping it in a function.
 *
 * @return {(string|boolean|number|object|WebElement)}
 *     Return value from the script, or null which signifies either the
 *     JavaScript notion of null or undefined.
 *
 * @throws {ScriptTimeoutError}
 *     If the script was interrupted due to reaching the
 *     <var>scriptTimeout</var> or default timeout.
 * @throws {JavaScriptError}
 *     If an {@link Error} was thrown whilst evaluating the script.
 */
GeckoDriver.prototype.executeScript = function*(cmd, resp) {
  assert.window(this.getCurrentWindow());

  let {script, args, scriptTimeout} = cmd.parameters;
  scriptTimeout = scriptTimeout || this.timeouts.script;

  let opts = {
    sandboxName: cmd.parameters.sandbox,
    newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
        cmd.parameters.newSandbox,
    file: cmd.parameters.filename,
    line: cmd.parameters.line,
    debug: cmd.parameters.debug_script,
  };

  resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
};

/**
 * Executes a JavaScript function in the context of the current browsing
 * context, if in content space, or in chrome space otherwise, and returns
 * the object passed to the callback.
 *
 * The callback is always the last argument to the <var>arguments</var>
 * list passed to the function scope of the script.  It can be retrieved
 * as such:
 *
 * <pre><code>
 *     let callback = arguments[arguments.length - 1];
 *     callback("foo");
 *     // "foo" is returned
 * </code></pre>
 *
 * It is important to note that if the <var>sandboxName</var> parameter
 * is left undefined, the script will be evaluated in a mutable sandbox,
 * causing any change it makes on the global state of the document to have
 * lasting side-effects.
 *
 * @param {string} script
 *     Script to evaluate as a function body.
 * @param {Array.<(string|boolean|number|object|WebElement)>} args
 *     Arguments exposed to the script in <code>arguments</code>.
 *     The array items must be serialisable to the WebDriver protocol.
 * @param {number} scriptTimeout
 *     Duration in milliseconds of when to interrupt and abort the
 *     script evaluation.
 * @param {string=} sandbox
 *     Name of the sandbox to evaluate the script in.  The sandbox is
 *     cached for later re-use on the same Window object if
 *     <var>newSandbox</var> is false.  If the parameter is undefined,
 *     the script is evaluated in a mutable sandbox.  If the parameter
 *     is "system", it will be evaluted in a sandbox with elevated system
 *     privileges, equivalent to chrome space.
 * @param {boolean=} newSandbox
 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
 *     it is undefined, the script will normally be evaluted in a fresh
 *     sandbox.
 * @param {string=} filename
 *     Filename of the client's program where this script is evaluated.
 * @param {number=} line
 *     Line in the client's program where this script is evaluated.
 * @param {boolean=} debug_script
 *     Attach an <code>onerror</code> event handler on the {@link Window}
 *     object.  It does not differentiate content errors from chrome errors.
 * @param {boolean=} directInject
 *     Evaluate the script without wrapping it in a function.
 *
 * @return {(string|boolean|number|object|WebElement)}
 *     Return value from the script, or null which signifies either the
 *     JavaScript notion of null or undefined.
 *
 * @throws {ScriptTimeoutError}
 *     If the script was interrupted due to reaching the
 *     <var>scriptTimeout</var> or default timeout.
 * @throws {JavaScriptError}
 *     If an Error was thrown whilst evaluating the script.
 */
GeckoDriver.prototype.executeAsyncScript = function* (cmd, resp) {
  assert.window(this.getCurrentWindow());

  let {script, args, scriptTimeout} = cmd.parameters;
  scriptTimeout = scriptTimeout || this.timeouts.script;

  let opts = {
    sandboxName: cmd.parameters.sandbox,
    newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
        cmd.parameters.newSandbox,
    file: cmd.parameters.filename,
    line: cmd.parameters.line,
    debug: cmd.parameters.debug_script,
    async: true,
  };

  resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
};

GeckoDriver.prototype.execute_ = function(script, args, timeout, opts = {}) {
  switch (this.context) {
    case Context.CONTENT:
      // evaluate in content with lasting side-effects
      if (!opts.sandboxName) {
        return this.listener.execute(script, args, timeout, opts)
            .then(evaluate.toJSON);
      }

      // evaluate in content with sandbox
      return this.listener.executeInSandbox(script, args, timeout, opts)
          .then(evaluate.toJSON);

    case Context.CHROME:
      let sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
      opts.timeout = timeout;
      let wargs = evaluate.fromJSON(args, this.curBrowser.seenEls, sb.window);
      return evaluate.sandbox(sb, script, wargs, opts)
          .then(res => evaluate.toJSON(res, this.curBrowser.seenEls));

    default:
      throw new TypeError(`Unknown context: ${this.context}`);
  }
};

/**
 * Navigate to given URL.
 *
 * Navigates the current browsing context to the given URL and waits for
 * the document to load or the session's page timeout duration to elapse
 * before returning.
 *
 * The command will return with a failure if there is an error loading
 * the document or the URL is blocked.  This can occur if it fails to
 * reach host, the URL is malformed, or if there is a certificate issue
 * to name some examples.
 *
 * The document is considered successfully loaded when the
 * DOMContentLoaded event on the frame element associated with the
 * current window triggers and document.readyState is "complete".
 *
 * In chrome context it will change the current window's location to
 * the supplied URL and wait until document.readyState equals "complete"
 * or the page timeout duration has elapsed.
 *
 * @param {string} url
 *     URL to navigate to.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.get = function* (cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let url = cmd.parameters.url;

  let get = this.listener.get({url, pageTimeout: this.timeouts.pageLoad});

  // If a reload of the frame script interrupts our page load, this will
  // never return. We need to re-issue this request to correctly poll for
  // readyState and send errors.
  this.curBrowser.pendingCommands.push(() => {
    let parameters = {
      // TODO(ato): Bug 1242595
      command_id: this.listener.activeMessageId,
      pageTimeout: this.timeouts.pageLoad,
      startTime: new Date().getTime(),
    };
    this.mm.broadcastAsyncMessage(
        "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId,
        parameters);
  });

  yield get;

  this.curBrowser.contentBrowser.focus();
};

/**
 * Get a string representing the current URL.
 *
 * On Desktop this returns a string representation of the URL of the
 * current top level browsing context.  This is equivalent to
 * document.location.href.
 *
 * When in the context of the chrome, this returns the canonical URL
 * of the current resource.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getCurrentUrl = function(cmd) {
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  return this.currentURL.toString();
};

/**
 * Gets the current title of the window.
 *
 * @return {string}
 *     Document title of the top-level browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getTitle = function* (cmd, resp) {
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  return this.title;
};

/** Gets the current type of the window. */
GeckoDriver.prototype.getWindowType = function(cmd, resp) {
  let win = assert.window(this.getCurrentWindow());

  resp.body.value = win.document.documentElement.getAttribute("windowtype");
};

/**
 * Gets the page source of the content document.
 *
 * @return {string}
 *     String serialisation of the DOM of the current browsing context's
 *     active document.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getPageSource = function* (cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  switch (this.context) {
    case Context.CHROME:
      let s = new win.XMLSerializer();
      resp.body.value = s.serializeToString(win.document);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.getPageSource();
      break;
  }
};

/**
 * Cause the browser to traverse one step backward in the joint history
 * of the current browsing context.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.goBack = function* (cmd, resp) {
  assert.content(this.context);
  assert.contentBrowser(this.curBrowser);
  assert.noUserPrompt(this.dialog);

  // If there is no history, just return
  if (!this.curBrowser.contentBrowser.webNavigation.canGoBack) {
    return;
  }

  let lastURL = this.currentURL;
  let goBack = this.listener.goBack({pageTimeout: this.timeouts.pageLoad});

  // If a reload of the frame script interrupts our page load, this will
  // never return. We need to re-issue this request to correctly poll for
  // readyState and send errors.
  this.curBrowser.pendingCommands.push(() => {
    let parameters = {
      // TODO(ato): Bug 1242595
      command_id: this.listener.activeMessageId,
      lastSeenURL: lastURL.toString(),
      pageTimeout: this.timeouts.pageLoad,
      startTime: new Date().getTime(),
    };
    this.mm.broadcastAsyncMessage(
        "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId,
        parameters);
  });

  yield goBack;
};

/**
 * Cause the browser to traverse one step forward in the joint history
 * of the current browsing context.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.goForward = function* (cmd, resp) {
  assert.content(this.context);
  assert.contentBrowser(this.curBrowser);
  assert.noUserPrompt(this.dialog);

  // If there is no history, just return
  if (!this.curBrowser.contentBrowser.webNavigation.canGoForward) {
    return;
  }

  let lastURL = this.currentURL;
  let goForward = this.listener.goForward(
      {pageTimeout: this.timeouts.pageLoad});

  // If a reload of the frame script interrupts our page load, this will
  // never return. We need to re-issue this request to correctly poll for
  // readyState and send errors.
  this.curBrowser.pendingCommands.push(() => {
    let parameters = {
      // TODO(ato): Bug 1242595
      command_id: this.listener.activeMessageId,
      lastSeenURL: lastURL.toString(),
      pageTimeout: this.timeouts.pageLoad,
      startTime: new Date().getTime(),
    };
    this.mm.broadcastAsyncMessage(
        "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId,
        parameters);
  });

  yield goForward;
};

/**
 * Causes the browser to reload the page in current top-level browsing
 * context.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.refresh = function* (cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let refresh = this.listener.refresh(
      {pageTimeout: this.timeouts.pageLoad});

  // If a reload of the frame script interrupts our page load, this will
  // never return. We need to re-issue this request to correctly poll for
  // readyState and send errors.
  this.curBrowser.pendingCommands.push(() => {
    let parameters = {
      // TODO(ato): Bug 1242595
      command_id: this.listener.activeMessageId,
      pageTimeout: this.timeouts.pageLoad,
      startTime: new Date().getTime(),
    };
    this.mm.broadcastAsyncMessage(
        "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId,
        parameters);
  });

  yield refresh;
};

/**
 * Forces an update for the given browser's id.
 */
GeckoDriver.prototype.updateIdForBrowser = function(browser, newId) {
  this._browserIds.set(browser.permanentKey, newId);
};

/**
 * Retrieves a listener id for the given xul browser element. In case
 * the browser is not known, an attempt is made to retrieve the id from
 * a CPOW, and null is returned if this fails.
 */
GeckoDriver.prototype.getIdForBrowser = function(browser) {
  if (browser === null) {
    return null;
  }
  let permKey = browser.permanentKey;
  if (this._browserIds.has(permKey)) {
    return this._browserIds.get(permKey);
  }

  let winId = browser.outerWindowID;
  if (winId) {
    this._browserIds.set(permKey, winId);
    return winId;
  }
  return null;
},

/**
 * Get the current window's handle. On desktop this typically corresponds
 * to the currently selected tab.
 *
 * Return an opaque server-assigned identifier to this window that
 * uniquely identifies it within this Marionette instance.  This can
 * be used to switch to this window at a later point.
 *
 * @return {string}
 *     Unique window handle.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getWindowHandle = function(cmd, resp) {
  assert.contentBrowser(this.curBrowser);

  return this.curBrowser.curFrameId.toString();
};

/**
 * Get a list of top-level browsing contexts. On desktop this typically
 * corresponds to the set of open tabs for browser windows, or the window
 * itself for non-browser chrome windows.
 *
 * Each window handle is assigned by the server and is guaranteed unique,
 * however the return array does not have a specified ordering.
 *
 * @return {Array.<string>}
 *     Unique window handles.
 */
GeckoDriver.prototype.getWindowHandles = function(cmd, resp) {
  return this.windowHandles.map(String);
};

/**
 * Get the current window's handle.  This corresponds to a window that
 * may itself contain tabs.
 *
 * Return an opaque server-assigned identifier to this window that
 * uniquely identifies it within this Marionette instance.  This can
 * be used to switch to this window at a later point.
 *
 * @return {string}
 *     Unique window handle.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getChromeWindowHandle = function(cmd, resp) {
  assert.window(this.getCurrentWindow(Context.CHROME));

  for (let i in this.browsers) {
    if (this.curBrowser == this.browsers[i]) {
      resp.body.value = i;
      return;
    }
  }
};

/**
 * Returns identifiers for each open chrome window for tests interested in
 * managing a set of chrome windows and tabs separately.
 *
 * @return {Array.<string>}
 *     Unique window handles.
 */
GeckoDriver.prototype.getChromeWindowHandles = function(cmd, resp) {
  return this.chromeWindowHandles.map(String);
};

/**
 * Get the current position and size of the browser window currently in focus.
 *
 * Will return the current browser window size in pixels. Refers to
 * window outerWidth and outerHeight values, which include scroll bars,
 * title bars, etc.
 *
 * @return {Object.<string, number>}
 *     Object with |x| and |y| coordinates, and |width| and |height|
 *     of browser window.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getWindowRect = function(cmd, resp) {
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);
  return this.curBrowser.rect;
};

/**
 * Set the window position and size of the browser on the operating
 * system window manager.
 *
 * The supplied |width| and |height| values refer to the window outerWidth
 * and outerHeight values, which include browser chrome and OS-level
 * window borders.
 *
 * @param {number} x
 *     X coordinate of the top/left of the window that it will be
 *     moved to.
 * @param {number} y
 *     Y coordinate of the top/left of the window that it will be
 *     moved to.
 * @param {number} width
 *     Width to resize the window to.
 * @param {number} height
 *     Height to resize the window to.
 *
 * @return {Object.<string, number>}
 *     Object with |x| and |y| coordinates and |width| and |height|
 *     dimensions.
 *
 * @throws {UnsupportedOperationError}
 *     Not applicable to application.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.setWindowRect = async function(cmd, resp) {
  assert.firefox();
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {x, y, width, height} = cmd.parameters;
  let origRect = this.curBrowser.rect;

  // Throttle resize event by forcing the event queue to flush and delay
  // until the main thread is idle.
  function optimisedResize(resolve) {
    return () => Services.tm.idleDispatchToMainThread(() => {
      win.requestAnimationFrame(resolve);
    });
  }

  // Exit fullscreen and wait for window to resize.
  async function exitFullscreen() {
    return new Promise(resolve => {
      win.addEventListener("sizemodechange", optimisedResize(resolve), {once: true});
      win.fullScreen = false;
    });
  }

  // Synchronous resize to |width| and |height| dimensions.
  async function resizeWindow(width, height) {
    return new Promise(resolve => {
      win.addEventListener("resize", optimisedResize(resolve), {once: true});
      win.resizeTo(width, height);
    });
  }

  // Wait until window size has changed.  We can't wait for the
  // user-requested window size as this may not be achievable on the
  // current system.
  const windowResizeChange = async () => {
    return wait.until((resolve, reject) => {
      let curRect = this.curBrowser.rect;
      if (curRect.width != origRect.width &&
          curRect.height != origRect.height) {
        resolve();
      } else {
        reject();
      }
    });
  };

  // Wait for the window position to change.
  async function windowPosition(x, y) {
    return wait.until((resolve, reject) => {
      if ((x == win.screenX && y == win.screenY) ||
          (win.screenX != origRect.x || win.screenY != origRect.y)) {
        resolve();
      } else {
        reject();
      }
    });
  }

  if (win.windowState == win.STATE_FULLSCREEN) {
    await exitFullscreen();
  }

  if (height != null && width != null) {
    assert.positiveInteger(height);
    assert.positiveInteger(width);

    if (win.outerWidth != width || win.outerHeight != height) {
      await resizeWindow(width, height);
      await windowResizeChange();
    }
  }

  if (x != null && y != null) {
    assert.integer(x);
    assert.integer(y);

    win.moveTo(x, y);
    await windowPosition(x, y);
  }

  return this.curBrowser.rect;
};

/**
 * Switch current top-level browsing context by name or server-assigned
 * ID.  Searches for windows by name, then ID.  Content windows take
 * precedence.
 *
 * @param {string} name
 *     Target name or ID of the window to switch to.
 * @param {boolean=} focus
 *      A boolean value which determines whether to focus
 *      the window. Defaults to true.
 */
GeckoDriver.prototype.switchToWindow = function* (cmd, resp) {
  let focus = true;
  if (typeof cmd.parameters.focus != "undefined") {
    focus = cmd.parameters.focus;
  }

  // Window IDs are internally handled as numbers, but here it could
  // also be the name of the window.
  let switchTo = parseInt(cmd.parameters.name);
  if (isNaN(switchTo)) {
    switchTo = cmd.parameters.name;
  }

  let byNameOrId = function(win, windowId) {
    return switchTo === win.name || switchTo === windowId;
  };

  let found = this.findWindow(this.windows, byNameOrId);

  if (found) {
    yield this.setWindowHandle(found, focus);
  } else {
    throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
  }
};

/**
 * Find a specific window according to some filter function.
 *
 * @param {Iterable.<Window>} winIterable
 *     Iterable that emits Windows.
 * @param {function(Window, number): boolean} filter
 *     A callback function taking two arguments; the window and
 *     the outerId of the window, and returning a boolean indicating
 *     whether the window is the target.
 *
 * @return {Object}
 *     A window handle object containing the window and some
 *     associated metadata.
 */
GeckoDriver.prototype.findWindow = function(winIterable, filter) {
  for (let win of winIterable) {
    let outerId = getOuterWindowId(win);
    let tabBrowser = browser.getTabBrowser(win);

    // In case the wanted window is a chrome window, we are done.
    if (filter(win, outerId)) {
      return {win, outerId, hasTabBrowser: !!tabBrowser};

    // Otherwise check if the chrome window has a tab browser, and that it
    // contains a tab with the wanted window handle.
    } else if (tabBrowser && tabBrowser.tabs) {
      for (let i = 0; i < tabBrowser.tabs.length; ++i) {
        let contentBrowser = browser.getBrowserForTab(tabBrowser.tabs[i]);
        let contentWindowId = this.getIdForBrowser(contentBrowser);

        if (filter(win, contentWindowId)) {
          return {
            win,
            outerId,
            hasTabBrowser: true,
            tabIndex: i,
          };
        }
      }
    }
  }

  return null;
};

/**
 * Switch the marionette window to a given window. If the browser in
 * the window is unregistered, registers that browser and waits for
 * the registration is complete. If |focus| is true then set the focus
 * on the window.
 *
 * @param {Object} winProperties
 *     Object containing window properties such as returned from
 *     GeckoDriver#findWindow
 * @param {boolean=} focus
 *     A boolean value which determines whether to focus the window.
 *     Defaults to true.
 */
GeckoDriver.prototype.setWindowHandle = function* (
    winProperties, focus = true) {
  if (!(winProperties.outerId in this.browsers)) {
    // Initialise Marionette if the current chrome window has not been seen
    // before. Also register the initial tab, if one exists.
    let registerBrowsers, browserListening;

    if (winProperties.hasTabBrowser) {
      registerBrowsers = this.registerPromise();
      browserListening = this.listeningPromise();
    }

    this.startBrowser(winProperties.win, false /* isNewSession */);

    if (registerBrowsers && browserListening) {
      yield registerBrowsers;
      yield browserListening;
    }

  } else {
    // Otherwise switch to the known chrome window, and activate the tab
    // if it's a content browser.
    this.curBrowser = this.browsers[winProperties.outerId];

    if ("tabIndex" in winProperties) {
      this.curBrowser.switchToTab(
          winProperties.tabIndex, winProperties.win, focus);
    }
  }
};

GeckoDriver.prototype.getActiveFrame = function(cmd, resp) {
  assert.window(this.getCurrentWindow());

  switch (this.context) {
    case Context.CHROME:
      // no frame means top-level
      resp.body.value = null;
      if (this.curFrame) {
        let elRef = this.curBrowser.seenEls
            .add(this.curFrame.frameElement);
        let el = element.makeWebElement(elRef);
        resp.body.value = el;
      }
      break;

    case Context.CONTENT:
      resp.body.value = null;
      if (this.currentFrameElement !== null) {
        let el = element.makeWebElement(this.currentFrameElement);
        resp.body.value = el;
      }
      break;
  }
};

/**
 * Set the current browsing context for future commands to the parent
 * of the current browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.switchToParentFrame = function* (cmd, resp) {
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  yield this.listener.switchToParentFrame();
};

/**
 * Switch to a given frame within the current window.
 *
 * @param {Object} element
 *     A web element reference to the element to switch to.
 * @param {(string|number)} id
 *     If element is not defined, then this holds either the id, name,
 *     or index of the frame to switch to.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.switchToFrame = function* (cmd, resp) {
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {id, element, focus} = cmd.parameters;

  const otherErrorsExpr = /about:.+(error)|(blocked)\?/;
  const checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  let curWindow = this.getCurrentWindow();

  let checkLoad = function() {
    let win = this.getCurrentWindow();
    if (win.document.readyState == "complete") {
      return;
    } else if (win.document.readyState == "interactive") {
      let documentURI = win.document.documentURI;
      if (documentURI.startsWith("about:certerror")) {
        throw new InsecureCertificateError();
      } else if (otherErrorsExpr.exec(documentURI)) {
        throw new UnknownError("Reached error page: " + documentURI);
      }
    }

    checkTimer.initWithCallback(
        checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  };

  if (this.context == Context.CHROME) {
    let foundFrame = null;

    // just focus
    if (typeof id == "undefined" && typeof element == "undefined") {
      this.curFrame = null;
      if (focus) {
        this.mainFrame.focus();
      }
      checkTimer.initWithCallback(
          checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
      return;
    }

    // by element
    if (this.curBrowser.seenEls.has(element)) {
      // HTMLIFrameElement
      let wantedFrame = this.curBrowser.seenEls.get(
          element, {frame: curWindow});
      // Deal with an embedded xul:browser case
      if (wantedFrame.tagName == "xul:browser" ||
          wantedFrame.tagName == "browser") {
        curWindow = wantedFrame.contentWindow;
        this.curFrame = curWindow;
        if (focus) {
          this.curFrame.focus();
        }
        checkTimer.initWithCallback(
            checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
        return;
      }

      // Check if the frame is XBL anonymous
      let parent = curWindow.document.getBindingParent(wantedFrame);
      // Shadow nodes also show up in getAnonymousNodes, we should
      // ignore them.
      if (parent &&
          !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
        const doc = curWindow.document;
        let anonNodes = [...doc.getAnonymousNodes(parent) || []];
        if (anonNodes.length > 0) {
          let el = wantedFrame;
          while (el) {
            if (anonNodes.indexOf(el) > -1) {
              curWindow = wantedFrame.contentWindow;
              this.curFrame = curWindow;
              if (focus) {
                this.curFrame.focus();
              }
              checkTimer.initWithCallback(
                  checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
              return;
            }
            el = el.parentNode;
          }
        }
      }

      // else, assume iframe
      let frames = curWindow.document.getElementsByTagName("iframe");
      let numFrames = frames.length;
      for (let i = 0; i < numFrames; i++) {
        let wrappedEl = new XPCNativeWrapper(frames[i]);
        let wrappedWanted = new XPCNativeWrapper(wantedFrame);
        if (wrappedEl == wrappedWanted) {
          curWindow = frames[i].contentWindow;
          this.curFrame = curWindow;
          if (focus) {
            this.curFrame.focus();
          }
          checkTimer.initWithCallback(
              checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
          return;
        }
      }
    }

    switch (typeof id) {
      case "string" :
        let foundById = null;
        let frames = curWindow.document.getElementsByTagName("iframe");
        let numFrames = frames.length;
        for (let i = 0; i < numFrames; i++) {
          // give precedence to name
          let frame = frames[i];
          if (frame.getAttribute("name") == id) {
            foundFrame = i;
            curWindow = frame.contentWindow;
            break;
          } else if (foundById === null && frame.id == id) {
            foundById = i;
          }
        }
        if (foundFrame === null && foundById !== null) {
          foundFrame = foundById;
          curWindow = frames[foundById].contentWindow;
        }
        break;
      case "number":
        if (typeof curWindow.frames[id] != "undefined") {
          foundFrame = id;
          let frameEl = curWindow.frames[foundFrame].frameElement;
          curWindow = frameEl.contentWindow;
        }
        break;
    }

    if (foundFrame !== null) {
      this.curFrame = curWindow;
      if (focus) {
        this.curFrame.focus();
      }
      checkTimer.initWithCallback(
          checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
    } else {
      throw new NoSuchFrameError(`Unable to locate frame: ${id}`);
    }

  } else if (this.context == Context.CONTENT) {
    if (!id && !element &&
        this.curBrowser.frameManager.currentRemoteFrame !== null) {
      // We're currently using a ChromeMessageSender for a remote frame,
      // so this request indicates we need to switch back to the top-level
      // (parent) frame.  We'll first switch to the parent's (global)
      // ChromeMessageBroadcaster, so we send the message to the right
      // listener.
      this.switchToGlobalMessageManager();
    }
    cmd.command_id = cmd.id;

    let res = yield this.listener.switchToFrame(cmd.parameters);
    if (res) {
      let {win: winId, frame: frameId} = res;
      this.mm = this.curBrowser.frameManager.getFrameMM(winId, frameId);

      let registerBrowsers = this.registerPromise();
      let browserListening = this.listeningPromise();

      this.oopFrameId =
          this.curBrowser.frameManager.switchToFrame(winId, frameId);

      yield registerBrowsers;
      yield browserListening;
    }
  }
};

GeckoDriver.prototype.getTimeouts = function(cmd, resp) {
  return this.timeouts;
};

/**
 * Set timeout for page loading, searching, and scripts.
 *
 * @param {Object.<string, number>}
 *     Dictionary of timeout types and their new value, where all timeout
 *     types are optional.
 *
 * @throws {InvalidArgumentError}
 *     If timeout type key is unknown, or the value provided with it is
 *     not an integer.
 */
GeckoDriver.prototype.setTimeouts = function(cmd, resp) {
  // merge with existing timeouts
  let merged = Object.assign(this.timeouts.toJSON(), cmd.parameters);
  this.timeouts = session.Timeouts.fromJSON(merged);
};

/** Single tap. */
GeckoDriver.prototype.singleTap = function*(cmd, resp) {
  assert.window(this.getCurrentWindow());

  let {id, x, y} = cmd.parameters;

  switch (this.context) {
    case Context.CHROME:
      throw new UnsupportedOperationError(
          "Command 'singleTap' is not yet available in chrome context");

    case Context.CONTENT:
      this.addFrameCloseListener("tap");
      yield this.listener.singleTap(id, x, y);
      break;
  }
};

/**
 * Perform a series of grouped actions at the specified points in time.
 *
 * @param {Array.<?>} actions
 *     Array of objects that each represent an action sequence.
 *
 * @throws {UnsupportedOperationError}
 *     Not yet available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.performActions = function* (cmd, resp) {
  assert.content(this.context,
      "Command 'performActions' is not yet available in chrome context");
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let actions = cmd.parameters.actions;
  yield this.listener.performActions({"actions": actions});
};

/**
 * Release all the keys and pointer buttons that are currently depressed.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.releaseActions = function*(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  yield this.listener.releaseActions();
};

/**
 * An action chain.
 *
 * @param {Object} value
 *     A nested array where the inner array represents each event,
 *     and the outer array represents a collection of events.
 *
 * @return {number}
 *     Last touch ID.
 *
 * @throws {UnsupportedOperationError}
 *     Not applicable to application.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.actionChain = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {chain, nextId} = cmd.parameters;

  switch (this.context) {
    case Context.CHROME:
      // be conservative until this has a use case and is established
      // to work as expected in Fennec
      assert.firefox();

      resp.body.value = yield this.legacyactions.dispatchActions(
          chain, nextId, {frame: win}, this.curBrowser.seenEls);
      break;

    case Context.CONTENT:
      this.addFrameCloseListener("action chain");
      resp.body.value = yield this.listener.actionChain(chain, nextId);
      break;
  }
};

/**
 * A multi-action chain.
 *
 * @param {Object} value
 *     A nested array where the inner array represents eache vent,
 *     the middle array represents a collection of events for each
 *     finger, and the outer array represents all fingers.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.multiAction = function* (cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {value, max_length} = cmd.parameters;

  this.addFrameCloseListener("multi action chain");
  yield this.listener.multiAction(value, max_length);
};

/**
 * Find an element using the indicated search strategy.
 *
 * @param {string} using
 *     Indicates which search method to use.
 * @param {string} value
 *     Value the client is looking for.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.findElement = function* (cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let strategy = cmd.parameters.using;
  let expr = cmd.parameters.value;
  let opts = {
    startNode: cmd.parameters.element,
    timeout: this.timeouts.implicit,
    all: false,
  };

  switch (this.context) {
    case Context.CHROME:
      if (!SUPPORTED_STRATEGIES.has(strategy)) {
        throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
      }

      let container = {frame: win};
      if (opts.startNode) {
        opts.startNode = this.curBrowser.seenEls.get(
            opts.startNode, container);
      }
      let el = yield element.find(container, strategy, expr, opts);
      let elRef = this.curBrowser.seenEls.add(el);
      let webEl = element.makeWebElement(elRef);

      resp.body.value = webEl;
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.findElementContent(
          strategy,
          expr,
          opts);
      break;
  }
};

/**
 * Find elements using the indicated search strategy.
 *
 * @param {string} using
 *     Indicates which search method to use.
 * @param {string} value
 *     Value the client is looking for.
 */
GeckoDriver.prototype.findElements = function*(cmd, resp) {
  let win = assert.window(this.getCurrentWindow());

  let strategy = cmd.parameters.using;
  let expr = cmd.parameters.value;
  let opts = {
    startNode: cmd.parameters.element,
    timeout: this.timeouts.implicit,
    all: true,
  };

  switch (this.context) {
    case Context.CHROME:
      if (!SUPPORTED_STRATEGIES.has(strategy)) {
        throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
      }

      let container = {frame: win};
      if (opts.startNode) {
        opts.startNode = this.curBrowser.seenEls.get(
            opts.startNode, container);
      }
      let els = yield element.find(container, strategy, expr, opts);

      let elRefs = this.curBrowser.seenEls.addAll(els);
      let webEls = elRefs.map(element.makeWebElement);
      resp.body = webEls;
      break;

    case Context.CONTENT:
      resp.body = yield this.listener.findElementsContent(
          cmd.parameters.using,
          cmd.parameters.value,
          opts);
      break;
  }
};

/**
 * Return the active element on the page.
 *
 * @return {WebElement}
 *     Active element of the current browsing context's document element.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getActiveElement = function* (cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  resp.body.value = yield this.listener.getActiveElement();
};

/**
 * Send click event to element.
 *
 * @param {string} id
 *     Reference ID to the element that will be clicked.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.clickElement = function* (cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      yield interaction.clickElement(el, this.a11yChecks);
      break;

    case Context.CONTENT:
      // We need to protect against the click causing an OOP frame
      // to close.  This fires the mozbrowserclose event when it closes
      // so we need to listen for it and then just send an error back.
      // The person making the call should be aware something is not right
      // and handle accordingly.
      this.addFrameCloseListener("click");

      let click = this.listener.clickElement(
          {id, pageTimeout: this.timeouts.pageLoad});

      // If a reload of the frame script interrupts our page load, this will
      // never return. We need to re-issue this request to correctly poll for
      // readyState and send errors.
      this.curBrowser.pendingCommands.push(() => {
        let parameters = {
          // TODO(ato): Bug 1242595
          command_id: this.listener.activeMessageId,
          pageTimeout: this.timeouts.pageLoad,
          startTime: new Date().getTime(),
        };
        this.mm.broadcastAsyncMessage(
            "Marionette:waitForPageLoaded" + this.curBrowser.curFrameId,
            parameters);
      });

      yield click;
      break;
  }
};

/**
 * Get a given attribute of an element.
 *
 * @param {string} id
 *     Web element reference ID to the element that will be inspected.
 * @param {string} name
 *     Name of the attribute which value to retrieve.
 *
 * @return {string}
 *     Value of the attribute.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {id, name} = cmd.parameters;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = el.getAttribute(name);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.getElementAttribute(id, name);
      break;
  }
};

/**
 * Returns the value of a property associated with given element.
 *
 * @param {string} id
 *     Web element reference ID to the element that will be inspected.
 * @param {string} name
 *     Name of the property which value to retrieve.
 *
 * @return {string}
 *     Value of the property.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementProperty = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {id, name} = cmd.parameters;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = el[name];
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.getElementProperty(id, name);
      break;
  }
};

/**
 * Get the text of an element, if any.  Includes the text of all child
 * elements.
 *
 * @param {string} id
 *     Reference ID to the element that will be inspected.
 *
 * @return {string}
 *     Element's text "as rendered".
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementText = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      // for chrome, we look at text nodes, and any node with a "label" field
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      let lines = [];
      this.getVisibleText(el, lines);
      resp.body.value = lines.join("\n");
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.getElementText(id);
      break;
  }
};

/**
 * Get the tag name of the element.
 *
 * @param {string} id
 *     Reference ID to the element that will be inspected.
 *
 * @return {string}
 *     Local tag name of element.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementTagName = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = el.tagName.toLowerCase();
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.getElementTagName(id);
      break;
  }
};

/**
 * Check if element is displayed.
 *
 * @param {string} id
 *     Reference ID to the element that will be inspected.
 *
 * @return {boolean}
 *     True if displayed, false otherwise.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = yield interaction.isElementDisplayed(
          el, this.a11yChecks);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.isElementDisplayed(id);
      break;
  }
};

/**
 * Return the property of the computed style of an element.
 *
 * @param {string} id
 *     Reference ID to the element that will be checked.
 * @param {string} propertyName
 *     CSS rule that is being requested.
 *
 * @return {string}
 *     Value of |propertyName|.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementValueOfCssProperty = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {id, propertyName: prop} = cmd.parameters;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      let sty = win.document.defaultView.getComputedStyle(el);
      resp.body.value = sty.getPropertyValue(prop);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener
          .getElementValueOfCssProperty(id, prop);
      break;
  }
};

/**
 * Check if element is enabled.
 *
 * @param {string} id
 *     Reference ID to the element that will be checked.
 *
 * @return {boolean}
 *     True if enabled, false if disabled.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      // Selenium atom doesn't quite work here
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = yield interaction.isElementEnabled(
          el, this.a11yChecks);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.isElementEnabled(id);
      break;
  }
};

/**
 * Check if element is selected.
 *
 * @param {string} id
 *     Reference ID to the element that will be checked.
 *
 * @return {boolean}
 *     True if selected, false if unselected.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      // Selenium atom doesn't quite work here
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      resp.body.value = yield interaction.isElementSelected(
          el, this.a11yChecks);
      break;

    case Context.CONTENT:
      resp.body.value = yield this.listener.isElementSelected(id);
      break;
  }
};

/**
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementRect = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      let rect = el.getBoundingClientRect();
      resp.body = {
        x: rect.x + win.pageXOffset,
        y: rect.y + win.pageYOffset,
        width: rect.width,
        height: rect.height,
      };
      break;

    case Context.CONTENT:
      resp.body = yield this.listener.getElementRect(id);
      break;
  }
};

/**
 * Send key presses to element after focusing on it.
 *
 * @param {string} id
 *     Reference ID to the element that will be checked.
 * @param {string} value
 *     Value to send to the element.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.sendKeysToElement = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {id, text} = cmd.parameters;
  assert.string(text);

  switch (this.context) {
    case Context.CHROME:
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      yield interaction.sendKeysToElement(
          el, text, true, this.a11yChecks);
      break;

    case Context.CONTENT:
      yield this.listener.sendKeysToElement(id, text);
      break;
  }
};

/**
 * Clear the text of an element.
 *
 * @param {string} id
 *     Reference ID to the element that will be cleared.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.clearElement = function*(cmd, resp) {
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let id = cmd.parameters.id;

  switch (this.context) {
    case Context.CHROME:
      // the selenium atom doesn't work here
      let el = this.curBrowser.seenEls.get(id, {frame: win});
      if (el.nodeName == "textbox") {
        el.value = "";
      } else if (el.nodeName == "checkbox") {
        el.checked = false;
      }
      break;

    case Context.CONTENT:
      yield this.listener.clearElement(id);
      break;
  }
};

/**
 * Switch to shadow root of the given host element.
 *
 * @param {string} id element id.
 */
GeckoDriver.prototype.switchToShadowRoot = function*(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());

  let id = cmd.parameters.id;
  yield this.listener.switchToShadowRoot(id);
};

/**
 * Add a single cookie to the cookie store associated with the active
 * document's address.
 *
 * @param {Map.<string, (string|number|boolean)> cookie
 *     Cookie object.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {InvalidCookieDomainError}
 *     If <var>cookie</var> is for a different domain than the active
 *     document's host.
 */
GeckoDriver.prototype.addCookie = function(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {protocol, hostname} = this.currentURL;

  const networkSchemes = ["ftp:", "http:", "https:"];
  if (!networkSchemes.includes(protocol)) {
    throw new InvalidCookieDomainError("Document is cookie-averse");
  }

  let newCookie = cookie.fromJSON(cmd.parameters.cookie);
  if (typeof newCookie.domain == "undefined") {
    newCookie.domain = hostname;
  }

  cookie.add(newCookie, {restrictToHost: hostname});
};

/**
 * Get all the cookies for the current domain.
 *
 * This is the equivalent of calling <code>document.cookie</code> and
 * parsing the result.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getCookies = function(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {hostname, pathname} = this.currentURL;
  resp.body = [...cookie.iter(hostname, pathname)];
};

/**
 * Delete all cookies that are visible to a document.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.deleteAllCookies = function(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {hostname, pathname} = this.currentURL;
  for (let toDelete of cookie.iter(hostname, pathname)) {
    cookie.remove(toDelete);
  }
};

/**
 * Delete a cookie by name.
 *
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.deleteCookie = function(cmd, resp) {
  assert.content(this.context);
  assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let {hostname, pathname} = this.currentURL;
  let candidateName = assert.string(cmd.parameters.name);
  for (let toDelete of cookie.iter(hostname, pathname)) {
    if (toDelete.name === candidateName) {
      return cookie.remove(toDelete);
    }
  }

  throw UnknownError("Unable to find cookie");
};

/**
 * Close the currently selected tab/window.
 *
 * With multiple open tabs present the currently selected tab will
 * be closed.  Otherwise the window itself will be closed. If it is the
 * last window currently open, the window will not be closed to prevent
 * a shutdown of the application. Instead the returned list of window
 * handles is empty.
 *
 * @return {Array.<string>}
 *     Unique window handles of remaining windows.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.close = function(cmd, resp) {
  assert.contentBrowser(this.curBrowser);
  assert.noUserPrompt(this.dialog);

  let nwins = 0;

  for (let win of this.windows) {
    // For browser windows count the tabs. Otherwise take the window itself.
    let tabbrowser = browser.getTabBrowser(win);
    if (tabbrowser && tabbrowser.tabs) {
      nwins += tabbrowser.tabs.length;
    } else {
      nwins += 1;
    }
  }

  // If there is only one window left, do not close it. Instead return
  // a faked empty array of window handles.  This will instruct geckodriver
  // to terminate the application.
  if (nwins === 1) {
    return [];
  }

  if (this.mm != globalMessageManager) {
    this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
  }

  return this.curBrowser.closeTab()
      .then(() => this.windowHandles.map(String));
};

/**
 * Close the currently selected chrome window.
 *
 * If it is the last window currently open, the chrome window will not be
 * closed to prevent a shutdown of the application. Instead the returned
 * list of chrome window handles is empty.
 *
 * @return {Array.<string>}
 *     Unique chrome window handles of remaining chrome windows.
 */
GeckoDriver.prototype.closeChromeWindow = function(cmd, resp) {
  assert.firefox();
  assert.window(this.getCurrentWindow(Context.CHROME));

  let nwins = 0;

  // eslint-disable-next-line
  for (let _ of this.windows) {
    nwins++;
  }

  // If there is only one window left, do not close it.  Instead return
  // a faked empty array of window handles. This will instruct geckodriver
  // to terminate the application.
  if (nwins == 1) {
    return [];
  }

  // reset frame to the top-most frame
  this.curFrame = null;

  if (this.mm != globalMessageManager) {
    this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
  }

  return this.curBrowser.closeWindow()
      .then(() => this.chromeWindowHandles.map(String));
};

/** Delete Marionette session. */
GeckoDriver.prototype.deleteSession = function(cmd, resp) {
  if (this.curBrowser !== null) {
    // frame scripts can be safely reused
    Preferences.set(CONTENT_LISTENER_PREF, false);

    // delete session in each frame in each browser
    for (let win in this.browsers) {
      let browser = this.browsers[win];
      for (let i in browser.knownFrames) {
        globalMessageManager.broadcastAsyncMessage(
            "Marionette:deleteSession" + browser.knownFrames[i], {});
      }
    }

    for (let win of this.windows) {
      if (win.messageManager) {
        win.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
      } else {
        logger.error(
            `Could not remove listener from page ${win.location.href}`);
      }
    }

    this.curBrowser.frameManager.removeMessageManagerListeners(
        globalMessageManager);
  }

  this.switchToGlobalMessageManager();

  // reset frame to the top-most frame
  this.curFrame = null;
  if (this.mainFrame) {
    try {
      this.mainFrame.focus();
    } catch (e) {
      this.mainFrame = null;
    }
  }

  if (this.observing !== null) {
    for (let topic in this.observing) {
      Services.obs.removeObserver(this.observing[topic], topic);
    }
    this.observing = null;
  }

  modal.removeHandler(this.dialogHandler);

  this.sandboxes.clear();
  cert.uninstallOverride();

  this.sessionId = null;
  this.capabilities = new session.Capabilities();
};

/**
 * Takes a screenshot of a web element, current frame, or viewport.
 *
 * The screen capture is returned as a lossless PNG image encoded as
 * a base 64 string.
 *
 * If called in the content context, the |id| argument is not null and
 * refers to a present and visible web element's ID, the capture area will
 * be limited to the bounding box of that element.  Otherwise, the capture
 * area will be the bounding box of the current frame.
 *
 * If called in the chrome context, the screenshot will always represent
 * the entire viewport.
 *
 * @param {string=} id
 *     Optional web element reference to take a screenshot of.
 *     If undefined, a screenshot will be taken of the document element.
 * @param {Array.<string>=} highlights
 *     List of web elements to highlight.
 * @param {boolean} full
 *     True to take a screenshot of the entire document element. Is not
 *     considered if <var>id</var> is not defined. Defaults to true.
 * @param {boolean=} hash
 *     True if the user requests a hash of the image data.
 * @param {boolean=} scroll
 *     Scroll to element if |id| is provided.  If undefined, it will
 *     scroll to the element.
 *
 * @return {string}
 *     If <var>hash</var> is false, PNG image encoded as Base64 encoded
 *     string.  If <var>hash</var> is true, hex digest of the SHA-256
 *     hash of the Base64 encoded string.
 */
GeckoDriver.prototype.takeScreenshot = function(cmd, resp) {
  let win = assert.window(this.getCurrentWindow());

  let {id, highlights, full, hash} = cmd.parameters;
  highlights = highlights || [];
  let format = hash ? capture.Format.Hash : capture.Format.Base64;

  switch (this.context) {
    case Context.CHROME:
      let container = {frame: win.document.defaultView};

      let highlightEls = highlights.map(
          ref => this.curBrowser.seenEls.get(ref, container));

      // viewport
      let canvas;
      if (!id && !full) {
        canvas = capture.viewport(container.frame, highlightEls);

      // element or full document element
      } else {
        let node;
        if (id) {
          node = this.curBrowser.seenEls.get(id, container);
        } else {
          node = container.frame.document.documentElement;
        }

        canvas = capture.element(node, highlightEls);
      }

      switch (format) {
        case capture.Format.Hash:
          return capture.toHash(canvas);

        case capture.Format.Base64:
          return capture.toBase64(canvas);
      }
      break;

    case Context.CONTENT:
      return this.listener.takeScreenshot(format, cmd.parameters);
  }

  throw new TypeError(`Unknown context: ${this.context}`);
};

/**
 * Get the current browser orientation.
 *
 * Will return one of the valid primary orientation values
 * portrait-primary, landscape-primary, portrait-secondary, or
 * landscape-secondary.
 */
GeckoDriver.prototype.getScreenOrientation = function(cmd, resp) {
  assert.fennec();
  let win = assert.window(this.getCurrentWindow());

  resp.body.value = win.screen.mozOrientation;
};

/**
 * Set the current browser orientation.
 *
 * The supplied orientation should be given as one of the valid
 * orientation values.  If the orientation is unknown, an error will
 * be raised.
 *
 * Valid orientations are "portrait" and "landscape", which fall
 * back to "portrait-primary" and "landscape-primary" respectively,
 * and "portrait-secondary" as well as "landscape-secondary".
 */
GeckoDriver.prototype.setScreenOrientation = function(cmd, resp) {
  assert.fennec();
  let win = assert.window(this.getCurrentWindow());

  const ors = [
    "portrait", "landscape",
    "portrait-primary", "landscape-primary",
    "portrait-secondary", "landscape-secondary",
  ];

  let or = String(cmd.parameters.orientation);
  assert.string(or);
  let mozOr = or.toLowerCase();
  if (!ors.includes(mozOr)) {
    throw new InvalidArgumentError(`Unknown screen orientation: ${or}`);
  }

  if (!win.screen.mozLockOrientation(mozOr)) {
    throw new WebDriverError(`Unable to set screen orientation: ${or}`);
  }
};

/**
 * Synchronously minimizes the user agent window as if the user pressed
 * the minimize button, or restores it if it is already minimized.
 *
 * Not supported on Fennec.
 *
 * @return {Object.<string, number>}
 *     Window rect and window state.
 *
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.minimizeWindow = function* (cmd, resp) {
  assert.firefox();
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  let state;
  yield new Promise(resolve => {
    win.addEventListener("sizemodechange", resolve, {once: true});

    if (win.windowState == win.STATE_MINIMIZED) {
      win.restore();
      state = "normal";
    } else {
      win.minimize();
      state = "minimized";
    }
  });

  resp.body = {
    x: win.screenX,
    y: win.screenY,
    width: win.outerWidth,
    height: win.outerHeight,
    state,
  };
};

/**
 * Synchronously maximizes the user agent window as if the user pressed
 * the maximize button, or restores it if it is already maximized.
 *
 * Not supported on Fennec.
 *
 * @return {Map.<string, number>}
 *     Window rect.
 *
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.maximizeWindow = function* (cmd, resp) {
  assert.firefox();
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  yield new Promise(resolve => {
    win.addEventListener("resize", resolve, {once: true});

    if (win.windowState == win.STATE_MAXIMIZED) {
      win.restore();
    } else {
      win.maximize();
    }
  });

  resp.body = {
    x: win.screenX,
    y: win.screenY,
    width: win.outerWidth,
    height: win.outerHeight,
  };
};

/**
 * Synchronously sets the user agent window to full screen as if the user
 * had done "View > Enter Full Screen", or restores it if it is already
 * in full screen.
 *
 * Not supported on Fennec.
 *
 * @return {Map.<string, number>}
 *     Window rect.
 *
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.fullscreen = function* (cmd, resp) {
  assert.firefox();
  const win = assert.window(this.getCurrentWindow());
  assert.noUserPrompt(this.dialog);

  yield new Promise(resolve => {
    win.addEventListener("sizemodechange", resolve, {once: true});

    win.fullScreen = !win.fullScreen;
  });

  resp.body = {
    x: win.screenX,
    y: win.screenY,
    width: win.outerWidth,
    height: win.outerHeight,
  };
};

/**
 * Dismisses a currently displayed tab modal, or returns no such alert if
 * no modal is displayed.
 */
GeckoDriver.prototype.dismissDialog = function(cmd, resp) {
  assert.window(this.getCurrentWindow());
  this._checkIfAlertIsPresent();

  let {button0, button1} = this.dialog.ui;
  (button1 ? button1 : button0).click();
  this.dialog = null;
};

/**
 * Accepts a currently displayed tab modal, or returns no such alert if
 * no modal is displayed.
 */
GeckoDriver.prototype.acceptDialog = function(cmd, resp) {
  assert.window(this.getCurrentWindow());
  this._checkIfAlertIsPresent();

  let {button0} = this.dialog.ui;
  button0.click();
  this.dialog = null;
};

/**
 * Returns the message shown in a currently displayed modal, or returns
 * a no such alert error if no modal is currently displayed.
 */
GeckoDriver.prototype.getTextFromDialog = function(cmd, resp) {
  assert.window(this.getCurrentWindow());
  this._checkIfAlertIsPresent();

  let {infoBody} = this.dialog.ui;
  resp.body.value = infoBody.textContent;
};

/**
 * Set the user prompt's value field.
 *
 * Sends keys to the input field of a currently displayed modal, or
 * returns a no such alert error if no modal is currently displayed. If
 * a tab modal is currently displayed but has no means for text input,
 * an element not visible error is returned.
 *
 * @param {string} text
 *     Input to the user prompt's value field.
 *
 * @throws {ElementNotInteractableError}
 *     If the current user prompt is an alert or confirm.
 * @throws {NoSuchAlertError}
 *     If there is no current user prompt.
 * @throws {UnsupportedOperationError}
 *     If the current user prompt is something other than an alert,
 *     confirm, or a prompt.
 */
GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
  let win = assert.window(this.getCurrentWindow());
  this._checkIfAlertIsPresent();

  // see toolkit/components/prompts/content/commonDialog.js
  let {loginContainer, loginTextbox} = this.dialog.ui;
  if (loginContainer.hidden) {
    throw new ElementNotInteractableError(
        "This prompt does not accept text input");
  }

  event.sendKeysToElement(
      cmd.parameters.text,
      loginTextbox,
      {ignoreVisibility: true},
      this.dialog.window ? this.dialog.window : win);
};

GeckoDriver.prototype._checkIfAlertIsPresent = function() {
  if (!this.dialog || !this.dialog.ui) {
    throw new NoAlertOpenError("No modal dialog is currently open");
  }
};

/**
 * Enables or disables accepting new socket connections.
 *
 * By calling this method with `false` the server will not accept any
 * further connections, but existing connections will not be forcible
 * closed. Use `true` to re-enable accepting connections.
 *
 * Please note that when closing the connection via the client you can
 * end-up in a non-recoverable state if it hasn't been enabled before.
 *
 * This method is used for custom in application shutdowns via
 * marionette.quit() or marionette.restart(), like File -> Quit.
 *
 * @param {boolean} state
 *     True if the server should accept new socket connections.
 */
GeckoDriver.prototype.acceptConnections = function(cmd, resp) {
  assert.boolean(cmd.parameters.value);
  this._server.acceptConnections = cmd.parameters.value;
};

/**
 * Quits the application with the provided flags.
 *
 * Marionette will stop accepting new connections before ending the
 * current session, and finally attempting to quit the application.
 *
 * Optional {@link nsIAppStartup} flags may be provided as
 * an array of masks, and these will be combined by ORing
 * them with a bitmask.  The available masks are defined in
 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
 *
 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
 * flag may be bit-wise combined with one of the *Quit flags to cause
 * the application to restart after it quits.
 *
 * @param {Array.<string>=} flags
 *     Constant name of masks to pass to |Services.startup.quit|.
 *     If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
 *
 * @return {string}
 *     Explaining the reason why the application quit.  This can be
 *     in response to a normal shutdown or restart, yielding "shutdown"
 *     or "restart", respectively.
 *
 * @throws {InvalidArgumentError}
 *     If |flags| contains unknown or incompatible flags, for example
 *     multiple Quit flags.
 */
GeckoDriver.prototype.quit = function* (cmd, resp) {
  const quits = ["eConsiderQuit", "eAttemptQuit", "eForceQuit"];

  let flags = [];
  if (typeof cmd.parameters.flags != "undefined") {
    flags = assert.array(cmd.parameters.flags);
  }

  // bug 1298921
  assert.firefox();

  let quitSeen;
  let mode = 0;
  if (flags.length > 0) {
    for (let k of flags) {
      assert.in(k, Ci.nsIAppStartup);

      if (quits.includes(k)) {
        if (quitSeen) {
          throw new InvalidArgumentError(
              `${k} cannot be combined with ${quitSeen}`);
        }
        quitSeen = k;
      }

      mode |= Ci.nsIAppStartup[k];
    }
  } else {
    mode = Ci.nsIAppStartup.eAttemptQuit;
  }

  this._server.acceptConnections = false;
  this.deleteSession();

  // delay response until the application is about to quit
  let quitApplication = new Promise(resolve => {
    Services.obs.addObserver(
        (subject, topic, data) => resolve(data),
        "quit-application");
  });

  Services.startup.quit(mode);

  yield quitApplication
      .then(cause => resp.body.cause = cause)
      .then(() => resp.send());
};

GeckoDriver.prototype.installAddon = function(cmd, resp) {
  assert.firefox();

  let path = cmd.parameters.path;
  let temp = cmd.parameters.temporary || false;
  if (typeof path == "undefined" || typeof path != "string" ||
      typeof temp != "boolean") {
    throw InvalidArgumentError();
  }

  return addon.install(path, temp);
};

GeckoDriver.prototype.uninstallAddon = function(cmd, resp) {
  assert.firefox();

  let id = cmd.parameters.id;
  if (typeof id == "undefined" || typeof id != "string") {
    throw new InvalidArgumentError();
  }

  return addon.uninstall(id);
};

/** Receives all messages from content messageManager. */
/* eslint-disable consistent-return */
GeckoDriver.prototype.receiveMessage = function(message) {
  switch (message.name) {
    case "Marionette:ok":
    case "Marionette:done":
    case "Marionette:error":
      // check if we need to remove the mozbrowserclose listener
      if (this.mozBrowserClose !== null) {
        let win = this.getCurrentWindow();
        win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
        this.mozBrowserClose = null;
      }
      break;

    case "Marionette:log":
      // log server-side messages
      logger.info(message.json.message);
      break;

    case "Marionette:switchToModalOrigin":
      this.curBrowser.frameManager.switchToModalOrigin(message);
      this.mm = this.curBrowser.frameManager
          .currentRemoteFrame.messageManager.get();
      break;

    case "Marionette:switchedToFrame":
      if (message.json.restorePrevious) {
        this.currentFrameElement = this.previousFrameElement;
      } else {
        // we don't arbitrarily save previousFrameElement, since
        // we allow frame switching after modals appear, which would
        // override this value and we'd lose our reference
        if (message.json.storePrevious) {
          this.previousFrameElement = this.currentFrameElement;
        }
        this.currentFrameElement = message.json.frameValue;
      }
      break;

    case "Marionette:emitTouchEvent":
      globalMessageManager.broadcastAsyncMessage(
          "MarionetteMainListener:emitTouchEvent", message.json);
      break;

    case "Marionette:register":
      let wid = message.json.value;
      let be = message.target;
      let rv = this.registerBrowser(wid, be);
      return rv;

    case "Marionette:listenersAttached":
      if (message.json.listenerId === this.curBrowser.curFrameId) {
        // If the frame script gets reloaded we need to call newSession.
        // In the case of desktop this just sets up a small amount of state
        // that doesn't change over the course of a session.
        this.sendAsync("newSession", this.capabilities.toJSON());
        this.curBrowser.flushPendingCommands();
      }
      break;
  }
};
/* eslint-enable consistent-return */

GeckoDriver.prototype.responseCompleted = function() {
  if (this.curBrowser !== null) {
    this.curBrowser.pendingCommands = [];
  }
};

/**
 * Retrieve the localized string for the specified entity id.
 *
 * Example:
 *     localizeEntity(["chrome://global/locale/about.dtd"], "about.version")
 *
 * @param {Array.<string>} urls
 *     Array of .dtd URLs.
 * @param {string} id
 *     The ID of the entity to retrieve the localized string for.
 *
 * @return {string}
 *     The localized string for the requested entity.
 */
GeckoDriver.prototype.localizeEntity = function(cmd, resp) {
  let {urls, id} = cmd.parameters;

  if (!Array.isArray(urls)) {
    throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
  }
  if (typeof id != "string") {
    throw new InvalidArgumentError("Value of `id` should be of type 'string'");
  }

  resp.body.value = l10n.localizeEntity(urls, id);
};

/**
 * Retrieve the localized string for the specified property id.
 *
 * Example:
 *
 *     localizeProperty(
 *         ["chrome://global/locale/findbar.properties"], "FastFind");
 *
 * @param {Array.<string>} urls
 *     Array of .properties URLs.
 * @param {string} id
 *     The ID of the property to retrieve the localized string for.
 *
 * @return {string}
 *     The localized string for the requested property.
 */
GeckoDriver.prototype.localizeProperty = function(cmd, resp) {
  let {urls, id} = cmd.parameters;

  if (!Array.isArray(urls)) {
    throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
  }
  if (typeof id != "string") {
    throw new InvalidArgumentError("Value of `id` should be of type 'string'");
  }

  resp.body.value = l10n.localizeProperty(urls, id);
};

/**
 * Initialize the reftest mode
 */
GeckoDriver.prototype.setupReftest = function* (cmd, resp) {
  if (this._reftest) {
    throw new UnsupportedOperationError("Called reftest:setup with a reftest session already active");
  }

  if (this.context !== Context.CHROME) {
    throw new UnsupportedOperationError("Must set chrome context before running reftests");
  }

  let {urlCount = {}, screenshot = "unexpected"} = cmd.parameters;
  if (!["always", "fail", "unexpected"].includes(screenshot)) {
    throw new InvalidArgumentError("Value of `screenshot` should be 'always', 'fail' or 'unexpected'");
  }

  this._reftest = new reftest.Runner(this);

  yield this._reftest.setup(urlCount, screenshot);
};


/**
 * Run a reftest
 */
GeckoDriver.prototype.runReftest = function* (cmd, resp) {
  let {test, references, expected, timeout} = cmd.parameters;

  if (!this._reftest) {
    throw new UnsupportedOperationError("Called reftest:run before reftest:start");
  }

  assert.string(test);
  assert.string(expected);
  assert.array(references);

  let result = yield this._reftest.run(test, references, expected, timeout);

  resp.body.value = result;
};

/**
 * End a reftest run
 *
 * Closes the reftest window (without changing the current window handle),
 * and removes cached canvases.
 */
GeckoDriver.prototype.teardownReftest = function* (cmd, resp) {
  if (!this._reftest) {
    throw new UnsupportedOperationError("Called reftest:teardown before reftest:start");
  }

  this._reftest.abort();

  this._reftest = null;
};


GeckoDriver.prototype.commands = {
  // Marionette service
  "Marionette:SetContext": GeckoDriver.prototype.setContext,
  "setContext": GeckoDriver.prototype.setContext,  // deprecated, remove in Firefox 60
  "Marionette:GetContext": GeckoDriver.prototype.getContext,
  "getContext": GeckoDriver.prototype.getContext,
  "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
  "acceptConnections": GeckoDriver.prototype.acceptConnections,  // deprecated, remove in Firefox 60
  "Marionette:Quit": GeckoDriver.prototype.quit,
  "quit": GeckoDriver.prototype.quit,  // deprecated, remove in Firefox 60
  "quitApplication": GeckoDriver.prototype.quit,  // deprecated, remove in Firefox 60

  // Addon service
  "Addon:Install": GeckoDriver.prototype.installAddon,
  "addon:install": GeckoDriver.prototype.installAddon,  // deprecated, remove in Firefox 60
  "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
  "addon:uninstall": GeckoDriver.prototype.uninstallAddon,  // deprecated, remove in Firefox 60

  // L10n service
  "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
  "localization:l10n:localizeEntity": GeckoDriver.prototype.localizeEntity,  // deprecated, remove in Firefox 60
  "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
  "localization:l10n:localizeProperty": GeckoDriver.prototype.localizeProperty,  // deprecated, remove in Firefox 60

  // Reftest service
  "reftest:setup": GeckoDriver.prototype.setupReftest,
  "reftest:run": GeckoDriver.prototype.runReftest,
  "reftest:teardown": GeckoDriver.prototype.teardownReftest,

  // WebDriver service
  "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
  "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
  "WebDriver:Back": GeckoDriver.prototype.goBack,
  "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
  "WebDriver:CloseWindow": GeckoDriver.prototype.close,
  "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
  "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
  "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
  "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
  "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
  "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
  "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
  "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
  "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
  "WebDriver:FindElement": GeckoDriver.prototype.findElement,
  "WebDriver:FindElements": GeckoDriver.prototype.findElements,
  "WebDriver:Forward": GeckoDriver.prototype.goForward,
  "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreen,
  "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
  "WebDriver:GetActiveFrame": GeckoDriver.prototype.getActiveFrame,
  "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
  "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
  "WebDriver:GetChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  "WebDriver:GetChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
  "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
  "WebDriver:GetCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
  "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
  "WebDriver:GetElementCSSValue": GeckoDriver.prototype.getElementValueOfCssProperty,
  "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
  "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
  "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
  "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
  "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
  "WebDriver:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
  "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
  "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
  "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
  "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
  "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
  "WebDriver:GetWindowType": GeckoDriver.prototype.getWindowType,
  "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
  "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
  "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
  "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
  "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
  "WebDriver:Navigate": GeckoDriver.prototype.get,
  "WebDriver:NewSession": GeckoDriver.prototype.newSession,
  "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
  "WebDriver:Refresh":  GeckoDriver.prototype.refresh,
  "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
  "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
  "WebDriver:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
  "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
  "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
  "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
  "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
  "WebDriver:SwitchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
  "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
  "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,

  // deprecated WebDriver commands, remove in Firefox 60
  "acceptDialog": GeckoDriver.prototype.acceptDialog,
  "actionChain": GeckoDriver.prototype.actionChain,
  "addCookie": GeckoDriver.prototype.addCookie,
  "clearElement": GeckoDriver.prototype.clearElement,
  "clickElement": GeckoDriver.prototype.clickElement,
  "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
  "close": GeckoDriver.prototype.close,
  "deleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
  "deleteCookie": GeckoDriver.prototype.deleteCookie,
  "deleteSession": GeckoDriver.prototype.deleteSession,
  "dismissDialog": GeckoDriver.prototype.dismissDialog,
  "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
  "executeScript": GeckoDriver.prototype.executeScript,
  "findElement": GeckoDriver.prototype.findElement,
  "findElements": GeckoDriver.prototype.findElements,
  "fullscreen": GeckoDriver.prototype.fullscreen,
  "getActiveElement": GeckoDriver.prototype.getActiveElement,
  "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
  "getChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
  "getCookies": GeckoDriver.prototype.getCookies,
  "getCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  "getCurrentUrl": GeckoDriver.prototype.getCurrentUrl,
  "getElementAttribute": GeckoDriver.prototype.getElementAttribute,
  "getElementProperty": GeckoDriver.prototype.getElementProperty,
  "getElementRect": GeckoDriver.prototype.getElementRect,
  "getElementTagName": GeckoDriver.prototype.getElementTagName,
  "getElementText": GeckoDriver.prototype.getElementText,
  "getElementValueOfCssProperty": GeckoDriver.prototype.getElementValueOfCssProperty,
  "get": GeckoDriver.prototype.get,
  "getPageSource": GeckoDriver.prototype.getPageSource,
  "getScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
  "getSessionCapabilities": GeckoDriver.prototype.getSessionCapabilities,
  "getTextFromDialog": GeckoDriver.prototype.getTextFromDialog,
  "getTimeouts": GeckoDriver.prototype.getTimeouts,
  "getTitle": GeckoDriver.prototype.getTitle,
  "getWindowHandle": GeckoDriver.prototype.getWindowHandle,
  "getWindowHandles": GeckoDriver.prototype.getWindowHandles,
  "getWindowPosition": GeckoDriver.prototype.getWindowRect, // redirect for compatibility
  "getWindowRect": GeckoDriver.prototype.getWindowRect,
  "getWindowSize": GeckoDriver.prototype.getWindowRect, // redirect for compatibility
  "getWindowType": GeckoDriver.prototype.getWindowType,
  "goBack": GeckoDriver.prototype.goBack,
  "goForward": GeckoDriver.prototype.goForward,
  "isElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
  "isElementEnabled": GeckoDriver.prototype.isElementEnabled,
  "isElementSelected": GeckoDriver.prototype.isElementSelected,
  "maximizeWindow": GeckoDriver.prototype.maximizeWindow,
  "multiAction": GeckoDriver.prototype.multiAction,
  "newSession": GeckoDriver.prototype.newSession,
  "performActions": GeckoDriver.prototype.performActions,
  "refresh":  GeckoDriver.prototype.refresh,
  "releaseActions": GeckoDriver.prototype.releaseActions,
  "sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog,
  "sendKeysToElement": GeckoDriver.prototype.sendKeysToElement,
  "setScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
  "setTimeouts": GeckoDriver.prototype.setTimeouts,
  "setWindowPosition": GeckoDriver.prototype.setWindowRect, // redirect for compatibility
  "setWindowRect": GeckoDriver.prototype.setWindowRect,
  "setWindowSize": GeckoDriver.prototype.setWindowRect, // redirect for compatibility
  "singleTap": GeckoDriver.prototype.singleTap,
  "switchToFrame": GeckoDriver.prototype.switchToFrame,
  "switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
  "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
  "switchToWindow": GeckoDriver.prototype.switchToWindow,
  "takeScreenshot": GeckoDriver.prototype.takeScreenshot,
};

function copy(obj) {
  if (Array.isArray(obj)) {
    return obj.slice();
  } else if (typeof obj == "object") {
    return Object.assign({}, obj);
  }
  return obj;
}

/**
 * Get the outer window ID for the specified window.
 *
 * @param {nsIDOMWindow} win
 *     Window whose browser we need to access.
 *
 * @return {string}
 *     Returns the unique window ID.
 */
function getOuterWindowId(win) {
  return win.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils)
      .outerWindowID;
}
PK
!<Vl~p~p$chrome/marionette/content/element.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
/* global XPCNativeWrapper */

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm");

Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/atom.js");
const {
  error,
  InvalidSelectorError,
  JavaScriptError,
  NoSuchElementError,
  StaleElementReferenceError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/wait.js");

const logger = Log.repository.getLogger("Marionette");

this.EXPORTED_SYMBOLS = ["element"];

const DOCUMENT_POSITION_DISCONNECTED = 1;
const XMLNS = "http://www.w3.org/1999/xhtml";

const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
    .getService(Ci.nsIUUIDGenerator);

/**
 * This module provides shared functionality for dealing with DOM-
 * and web elements in Marionette.
 *
 * A web element is an abstraction used to identify an element when it
 * is transported across the protocol, between remote- and local ends.
 *
 * Each element has an associated web element reference (a UUID) that
 * uniquely identifies the the element across all browsing contexts. The
 * web element reference for every element representing the same element
 * is the same.
 *
 * The {@link element.Store} provides a mapping between web element
 * references and DOM elements for each browsing context.  It also provides
 * functionality for looking up and retrieving elements.
 *
 * @namespace
 */
this.element = {};

element.Key = "element-6066-11e4-a52e-4f735466cecf";
element.LegacyKey = "ELEMENT";

element.Strategy = {
  ClassName: "class name",
  Selector: "css selector",
  ID: "id",
  Name: "name",
  LinkText: "link text",
  PartialLinkText: "partial link text",
  TagName: "tag name",
  XPath: "xpath",
  Anon: "anon",
  AnonAttribute: "anon attribute",
};

/**
 * Stores known/seen elements and their associated web element
 * references.
 *
 * Elements are added by calling |add(el)| or |addAll(elements)|, and
 * may be queried by their web element reference using |get(element)|.
 *
 * @class
 * @memberof element
 */
element.Store = class {
  constructor() {
    this.els = {};
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  }

  clear() {
    this.els = {};
  }

  /**
   * Make a collection of elements seen.
   *
   * The oder of the returned web element references is guaranteed to
   * match that of the collection passed in.
   *
   * @param {NodeList} els
   *     Sequence of elements to add to set of seen elements.
   *
   * @return {Array.<WebElement>}
   *     List of the web element references associated with each element
   *     from |els|.
   */
  addAll(els) {
    let add = this.add.bind(this);
    return [...els].map(add);
  }

  /**
   * Make an element seen.
   *
   * @param {nsIDOMElement} el
   *    Element to add to set of seen elements.
   *
   * @return {string}
   *     Web element reference associated with element.
   */
  add(el) {
    for (let i in this.els) {
      let foundEl;
      try {
        foundEl = this.els[i].get();
      } catch (e) {}

      if (foundEl) {
        if (new XPCNativeWrapper(foundEl) == new XPCNativeWrapper(el)) {
          return i;
        }

      // cleanup reference to gc'd element
      } else {
        delete this.els[i];
      }
    }

    let id = element.generateUUID();
    this.els[id] = Cu.getWeakReference(el);
    return id;
  }

  /**
   * Determine if the provided web element reference has been seen
   * before/is in the element store.
   *
   * @param {string} uuid
   *     Element's associated web element reference.
   *
   * @return {boolean}
   *     True if element is in the store, false otherwise.
   */
  has(uuid) {
    return Object.keys(this.els).includes(uuid);
  }

  /**
   * Retrieve a DOM element by its unique web element reference/UUID.
   *
   * @param {string} uuid
   *     Web element reference, or UUID.
   * @param {(nsIDOMWindow|ShadowRoot)} container
   * Window and an optional shadow root that contains the element.
   *
   * @returns {nsIDOMElement}
   *     Element associated with reference.
   *
   * @throws {JavaScriptError}
   *     If the provided reference is unknown.
   * @throws {StaleElementReferenceError}
   *     If element has gone stale, indicating it is no longer attached to
   *     the DOM provided in the container.
   */
  get(uuid, container) {
    let el = this.els[uuid];
    if (!el) {
      throw new JavaScriptError(`Element reference not seen before: ${uuid}`);
    }

    try {
      el = el.get();
    } catch (e) {
      el = null;
      delete this.els[uuid];
    }

    // use XPCNativeWrapper to compare elements (see bug 834266)
    let wrappedFrame = new XPCNativeWrapper(container.frame);
    let wrappedShadowRoot;
    if (container.shadowRoot) {
      wrappedShadowRoot = new XPCNativeWrapper(container.shadowRoot);
    }
    let wrappedEl = new XPCNativeWrapper(el);
    let wrappedContainer = {
      frame: wrappedFrame,
      shadowRoot: wrappedShadowRoot,
    };
    if (!el ||
        !(wrappedEl.ownerDocument == wrappedFrame.document) ||
        element.isDisconnected(wrappedEl, wrappedContainer)) {
      throw new StaleElementReferenceError(
          error.pprint`The element reference of ${el} stale: ` +
              "either the element is no longer attached to the DOM " +
              "or the page has been refreshed");
    }

    return el;
  }
};

/**
 * Find a single element or a collection of elements starting at the
 * document root or a given node.
 *
 * If |timeout| is above 0, an implicit search technique is used.
 * This will wait for the duration of |timeout| for the element
 * to appear in the DOM.
 *
 * See the |element.Strategy| enum for a full list of supported
 * search strategies that can be passed to |strategy|.
 *
 * Available flags for |opts|:
 *
 *     |all|
 *       If true, a multi-element search selector is used and a sequence
 *       of elements will be returned.  Otherwise a single element.
 *
 *     |timeout|
 *       Duration to wait before timing out the search.  If |all| is
 *       false, a NoSuchElementError is thrown if unable to find
 *       the element within the timeout duration.
 *
 *     |startNode|
 *       Element to use as the root of the search.
 *
 * @param {Object.<string, Window>} container
 *     Window object and an optional shadow root that contains the
 *     root shadow DOM element.
 * @param {string} strategy
 *     Search strategy whereby to locate the element(s).
 * @param {string} selector
 *     Selector search pattern.  The selector must be compatible with
 *     the chosen search |strategy|.
 * @param {Object.<string, ?>} opts
 *     Options.
 *
 * @return {Promise.<(nsIDOMElement|Array.<nsIDOMElement>)>}
 *     Single element or a sequence of elements.
 *
 * @throws InvalidSelectorError
 *     If |strategy| is unknown.
 * @throws InvalidSelectorError
 *     If |selector| is malformed.
 * @throws NoSuchElementError
 *     If a single element is requested, this error will throw if the
 *     element is not found.
 */
element.find = function(container, strategy, selector, opts = {}) {
  opts.all = !!opts.all;
  opts.timeout = opts.timeout || 0;

  let searchFn;
  if (opts.all) {
    searchFn = findElements.bind(this);
  } else {
    searchFn = findElement.bind(this);
  }

  return new Promise((resolve, reject) => {
    let findElements = wait.until((resolve, reject) => {
      let res = find_(container, strategy, selector, searchFn, opts);
      if (res.length > 0) {
        resolve(Array.from(res));
      } else {
        reject([]);
      }
    }, opts.timeout);

    findElements.then(foundEls => {
      // the following code ought to be moved into findElement
      // and findElements when bug 1254486 is addressed
      if (!opts.all && (!foundEls || foundEls.length == 0)) {
        let msg;
        switch (strategy) {
          case element.Strategy.AnonAttribute:
            msg = "Unable to locate anonymous element: " +
                JSON.stringify(selector);
            break;

          default:
            msg = "Unable to locate element: " + selector;
        }

        reject(new NoSuchElementError(msg));
      }

      if (opts.all) {
        resolve(foundEls);
      }
      resolve(foundEls[0]);
    }, reject);
  });
};

function find_(container, strategy, selector, searchFn, opts) {
  let rootNode = container.shadowRoot || container.frame.document;
  let startNode;

  if (opts.startNode) {
    startNode = opts.startNode;
  } else {
    switch (strategy) {
      // For anonymous nodes the start node needs to be of type
      // DOMElement, which will refer to :root in case of a DOMDocument.
      case element.Strategy.Anon:
      case element.Strategy.AnonAttribute:
        if (rootNode instanceof Ci.nsIDOMDocument) {
          startNode = rootNode.documentElement;
        }
        break;

      default:
        startNode = rootNode;
    }
  }

  let res;
  try {
    res = searchFn(strategy, selector, rootNode, startNode);
  } catch (e) {
    throw new InvalidSelectorError(
        `Given ${strategy} expression "${selector}" is invalid: ${e}`);
  }

  if (res) {
    if (opts.all) {
      return res;
    }
    return [res];
  }
  return [];
}

/**
 * Find a single element by XPath expression.
 *
 * @param {DOMElement} root
 *     Document root
 * @param {DOMElement} startNode
 *     Where in the DOM hiearchy to begin searching.
 * @param {string} expr
 *     XPath search expression.
 *
 * @return {DOMElement}
 *     First element matching expression.
 */
element.findByXPath = function(root, startNode, expr) {
  let iter = root.evaluate(expr, startNode, null,
      Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
  return iter.singleNodeValue;
};

/**
 * Find elements by XPath expression.
 *
 * @param {DOMElement} root
 *     Document root.
 * @param {DOMElement} startNode
 *     Where in the DOM hierarchy to begin searching.
 * @param {string} expr
 *     XPath search expression.
 *
 * @return {Array.<DOMElement>}
 *     Sequence of found elements matching expression.
 */
element.findByXPathAll = function(root, startNode, expr) {
  let rv = [];
  let iter = root.evaluate(expr, startNode, null,
      Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
  let el = iter.iterateNext();
  while (el) {
    rv.push(el);
    el = iter.iterateNext();
  }
  return rv;
};

/**
 * Find all hyperlinks dscendant of |node| which link text is |s|.
 *
 * @param {DOMElement} node
 *     Where in the DOM hierarchy to being searching.
 * @param {string} s
 *     Link text to search for.
 *
 * @return {Array.<DOMAnchorElement>}
 *     Sequence of link elements which text is |s|.
 */
element.findByLinkText = function(node, s) {
  return filterLinks(node, link => link.text.trim() === s);
};

/**
 * Find all hyperlinks descendant of |node| which link text contains |s|.
 *
 * @param {DOMElement} node
 *     Where in the DOM hierachy to begin searching.
 * @param {string} s
 *     Link text to search for.
 *
 * @return {Array.<DOMAnchorElement>}
 *     Sequence of link elements which text containins |s|.
 */
element.findByPartialLinkText = function(node, s) {
  return filterLinks(node, link => link.text.indexOf(s) != -1);
};

/**
 * Filters all hyperlinks that are descendant of |node| by |predicate|.
 *
 * @param {DOMElement} node
 *     Where in the DOM hierarchy to begin searching.
 * @param {function(DOMAnchorElement): boolean} predicate
 *     Function that determines if given link should be included in
 *     return value or filtered away.
 *
 * @return {Array.<DOMAnchorElement>}
 *     Sequence of link elements matching |predicate|.
 */
function filterLinks(node, predicate) {
  let rv = [];
  for (let link of node.getElementsByTagName("a")) {
    if (predicate(link)) {
      rv.push(link);
    }
  }
  return rv;
}

/**
 * Finds a single element.
 *
 * @param {element.Strategy} using
 *     Selector strategy to use.
 * @param {string} value
 *     Selector expression.
 * @param {DOMElement} rootNode
 *     Document root.
 * @param {DOMElement=} startNode
 *     Optional node from which to start searching.
 *
 * @return {DOMElement}
 *     Found elements.
 *
 * @throws {InvalidSelectorError}
 *     If strategy |using| is not recognised.
 * @throws {Error}
 *     If selector expression |value| is malformed.
 */
function findElement(using, value, rootNode, startNode) {
  switch (using) {
    case element.Strategy.ID:
      {
        if (startNode.getElementById) {
          return startNode.getElementById(value);
        }
        let expr = `.//*[@id="${value}"]`;
        return element.findByXPath( rootNode, startNode, expr);
      }

    case element.Strategy.Name:
      {
        if (startNode.getElementsByName) {
          return startNode.getElementsByName(value)[0];
        }
        let expr = `.//*[@name="${value}"]`;
        return element.findByXPath(rootNode, startNode, expr);
      }

    case element.Strategy.ClassName:
      // works for >= Firefox 3
      return startNode.getElementsByClassName(value)[0];

    case element.Strategy.TagName:
      // works for all elements
      return startNode.getElementsByTagName(value)[0];

    case element.Strategy.XPath:
      return element.findByXPath(rootNode, startNode, value);

    case element.Strategy.LinkText:
      for (let link of startNode.getElementsByTagName("a")) {
        if (link.text.trim() === value) {
          return link;
        }
      }
      return undefined;

    case element.Strategy.PartialLinkText:
      for (let link of startNode.getElementsByTagName("a")) {
        if (link.text.indexOf(value) != -1) {
          return link;
        }
      }
      return undefined;

    case element.Strategy.Selector:
      try {
        return startNode.querySelector(value);
      } catch (e) {
        throw new InvalidSelectorError(`${e.message}: "${value}"`);
      }

    case element.Strategy.Anon:
      return rootNode.getAnonymousNodes(startNode);

    case element.Strategy.AnonAttribute:
      let attr = Object.keys(value)[0];
      return rootNode.getAnonymousElementByAttribute(
          startNode, attr, value[attr]);
  }

  throw new InvalidSelectorError(`No such strategy: ${using}`);
}

/**
 * Find multiple elements.
 *
 * @param {element.Strategy} using
 *     Selector strategy to use.
 * @param {string} value
 *     Selector expression.
 * @param {DOMElement} rootNode
 *     Document root.
 * @param {DOMElement=} startNode
 *     Optional node from which to start searching.
 *
 * @return {DOMElement}
 *     Found elements.
 *
 * @throws {InvalidSelectorError}
 *     If strategy |using| is not recognised.
 * @throws {Error}
 *     If selector expression |value| is malformed.
 */
function findElements(using, value, rootNode, startNode) {
  switch (using) {
    case element.Strategy.ID:
      value = `.//*[@id="${value}"]`;

    // fall through
    case element.Strategy.XPath:
      return element.findByXPathAll(rootNode, startNode, value);

    case element.Strategy.Name:
      if (startNode.getElementsByName) {
        return startNode.getElementsByName(value);
      }
      return element.findByXPathAll(
          rootNode, startNode, `.//*[@name="${value}"]`);

    case element.Strategy.ClassName:
      return startNode.getElementsByClassName(value);

    case element.Strategy.TagName:
      return startNode.getElementsByTagName(value);

    case element.Strategy.LinkText:
      return element.findByLinkText(startNode, value);

    case element.Strategy.PartialLinkText:
      return element.findByPartialLinkText(startNode, value);

    case element.Strategy.Selector:
      return startNode.querySelectorAll(value);

    case element.Strategy.Anon:
      return rootNode.getAnonymousNodes(startNode);

    case element.Strategy.AnonAttribute:
      let attr = Object.keys(value)[0];
      let el = rootNode.getAnonymousElementByAttribute(
          startNode, attr, value[attr]);
      if (el) {
        return [el];
      }
      return [];

    default:
      throw new InvalidSelectorError(`No such strategy: ${using}`);
  }
}

/** Determines if |obj| is an HTML or JS collection. */
element.isCollection = function(seq) {
  switch (Object.prototype.toString.call(seq)) {
    case "[object Arguments]":
    case "[object Array]":
    case "[object FileList]":
    case "[object HTMLAllCollection]":
    case "[object HTMLCollection]":
    case "[object HTMLFormControlsCollection]":
    case "[object HTMLOptionsCollection]":
    case "[object NodeList]":
      return true;

    default:
      return false;
  }
};

element.makeWebElement = function(uuid) {
  return {
    [element.Key]: uuid,
    [element.LegacyKey]: uuid,
  };
};

/**
 * Checks if |ref| has either |element.Key| or |element.LegacyKey|
 * as properties.
 *
 * @param {Object.<string, string>} ref
 *     Object that represents a web element reference.
 * @return {boolean}
 *     True if |ref| has either expected property.
 */
element.isWebElementReference = function(ref) {
  let properties = Object.getOwnPropertyNames(ref);
  return properties.includes(element.Key) ||
      properties.includes(element.LegacyKey);
};

element.generateUUID = function() {
  let uuid = uuidGen.generateUUID().toString();
  return uuid.substring(1, uuid.length - 1);
};

/**
 * Check if the element is detached from the current frame as well as
 * the optional shadow root (when inside a Shadow DOM context).
 *
 * @param {nsIDOMElement} el
 *     Element to be checked.
 * @param {Container} container
 *     Container with |frame|, which is the window object that contains
 *     the element, and an optional |shadowRoot|.
 *
 * @return {boolean}
 *     Flag indicating that the element is disconnected.
 */
element.isDisconnected = function(el, container = {}) {
  const {frame, shadowRoot} = container;
  assert.defined(frame);

  // shadow DOM
  if (frame.ShadowRoot && shadowRoot) {
    if (el.compareDocumentPosition(shadowRoot) &
        DOCUMENT_POSITION_DISCONNECTED) {
      return true;
    }

    // looking for next possible ShadowRoot ancestor
    let parent = shadowRoot.host;
    while (parent && !(parent instanceof frame.ShadowRoot)) {
      parent = parent.parentNode;
    }
    return element.isDisconnected(
        shadowRoot.host,
        {frame, shadowRoot: parent});
  }

  // outside shadow DOM
  let docEl = frame.document.documentElement;
  return el.compareDocumentPosition(docEl) &
      DOCUMENT_POSITION_DISCONNECTED;
};

/**
 * This function generates a pair of coordinates relative to the viewport
 * given a target element and coordinates relative to that element's
 * top-left corner.
 *
 * @param {Node} node
 *     Target node.
 * @param {number=} xOffset
 *     Horizontal offset relative to target's top-left corner.
 *     Defaults to the centre of the target's bounding box.
 * @param {number=} yOffset
 *     Vertical offset relative to target's top-left corner.  Defaults to
 *     the centre of the target's bounding box.
 *
 * @return {Object.<string, number>}
 *     X- and Y coordinates.
 *
 * @throws TypeError
 *     If |xOffset| or |yOffset| are not numbers.
 */
element.coordinates = function(
    node, xOffset = undefined, yOffset = undefined) {

  let box = node.getBoundingClientRect();

  if (typeof xOffset == "undefined" || xOffset === null) {
    xOffset = box.width / 2.0;
  }
  if (typeof yOffset == "undefined" || yOffset === null) {
    yOffset = box.height / 2.0;
  }

  if (typeof yOffset != "number" || typeof xOffset != "number") {
    throw new TypeError("Offset must be a number");
  }

  return {
    x: box.left + xOffset,
    y: box.top + yOffset,
  };
};

/**
 * This function returns true if the node is in the viewport.
 *
 * @param {Element} el
 *     Target element.
 * @param {number=} x
 *     Horizontal offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 * @param {number=} y
 *     Vertical offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 *
 * @return {boolean}
 *     True if if |el| is in viewport, false otherwise.
 */
element.inViewport = function(el, x = undefined, y = undefined) {
  let win = el.ownerGlobal;
  let c = element.coordinates(el, x, y);
  let vp = {
    top: win.pageYOffset,
    left: win.pageXOffset,
    bottom: (win.pageYOffset + win.innerHeight),
    right: (win.pageXOffset + win.innerWidth),
  };

  return (vp.left <= c.x + win.pageXOffset &&
      c.x + win.pageXOffset <= vp.right &&
      vp.top <= c.y + win.pageYOffset &&
      c.y + win.pageYOffset <= vp.bottom);
};

/**
 * Gets the element's container element.
 *
 * An element container is defined by the WebDriver
 * specification to be an <option> element in a valid element context
 * (https://html.spec.whatwg.org/#concept-element-contexts), meaning
 * that it has an ancestral element that is either <datalist> or <select>.
 *
 * If the element does not have a valid context, its container element
 * is itself.
 *
 * @param {Element} el
 *     Element to get the container of.
 *
 * @return {Element}
 *     Container element of |el|.
 */
element.getContainer = function(el) {
  if (el.localName != "option") {
    return el;
  }

  function validContext(ctx) {
    return ctx.localName == "datalist" || ctx.localName == "select";
  }

  // does <option> have a valid context,
  // meaning is it a child of <datalist> or <select>?
  let parent = el;
  while (parent.parentNode && !validContext(parent)) {
    parent = parent.parentNode;
  }

  if (!validContext(parent)) {
    return el;
  }
  return parent;
};

/**
 * An element is in view if it is a member of its own pointer-interactable
 * paint tree.
 *
 * This means an element is considered to be in view, but not necessarily
 * pointer-interactable, if it is found somewhere in the
 * |elementsFromPoint| list at |el|'s in-view centre coordinates.
 *
 * Before running the check, we change |el|'s pointerEvents style property
 * to "auto", since elements without pointer events enabled do not turn
 * up in the paint tree we get from document.elementsFromPoint.  This is
 * a specialisation that is only relevant when checking if the element is
 * in view.
 *
 * @param {Element} el
 *     Element to check if is in view.
 *
 * @return {boolean}
 *     True if |el| is inside the viewport, or false otherwise.
 */
element.isInView = function(el) {
  let originalPointerEvents = el.style.pointerEvents;
  try {
    el.style.pointerEvents = "auto";
    const tree = element.getPointerInteractablePaintTree(el);
    return tree.includes(el);
  } finally {
    el.style.pointerEvents = originalPointerEvents;
  }
};

/**
 * This function throws the visibility of the element error if the element is
 * not displayed or the given coordinates are not within the viewport.
 *
 * @param {Element} el
 *     Element to check if visible.
 * @param {number=} x
 *     Horizontal offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 * @param {number=} y
 *     Vertical offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 *
 * @return {boolean}
 *     True if visible, false otherwise.
 */
element.isVisible = function(el, x = undefined, y = undefined) {
  let win = el.ownerGlobal;

  // Bug 1094246: webdriver's isShown doesn't work with content xul
  if (!element.isXULElement(el) && !atom.isElementDisplayed(el, win)) {
    return false;
  }

  if (el.tagName.toLowerCase() == "body") {
    return true;
  }

  if (!element.inViewport(el, x, y)) {
    element.scrollIntoView(el);
    if (!element.inViewport(el)) {
      return false;
    }
  }
  return true;
};

/**
 * A pointer-interactable element is defined to be the first
 * non-transparent element, defined by the paint order found at the centre
 * point of its rectangle that is inside the viewport, excluding the size
 * of any rendered scrollbars.
 *
 * An element is obscured if the pointer-interactable paint tree at its
 * centre point is empty, or the first element in this tree is not an
 * inclusive descendant of itself.
 *
 * @param {DOMElement} el
 *     Element determine if is pointer-interactable.
 *
 * @return {boolean}
 *     True if element is obscured, false otherwise.
 */
element.isObscured = function(el) {
  let tree = element.getPointerInteractablePaintTree(el);
  return !el.contains(tree[0]);
};

/**
 * Calculate the in-view centre point of the area of the given DOM client
 * rectangle that is inside the viewport.
 *
 * @param {DOMRect} rect
 *     Element off a DOMRect sequence produced by calling |getClientRects|
 *     on a |DOMElement|.
 * @param {nsIDOMWindow} win
 *     Current browsing context.
 *
 * @return {Map.<string, number>}
 *     X and Y coordinates that denotes the in-view centre point of |rect|.
 */
element.getInViewCentrePoint = function(rect, win) {
  const {max, min} = Math;

  let x = {
    left: max(0, min(rect.x, rect.x + rect.width)),
    right: min(win.innerWidth, max(rect.x, rect.x + rect.width)),
  };
  let y = {
    top: max(0, min(rect.y, rect.y + rect.height)),
    bottom: min(win.innerHeight, max(rect.y, rect.y + rect.height)),
  };

  return {
    x: (x.left + x.right) / 2,
    y: (y.top + y.bottom) / 2,
  };
};

/**
 * Produces a pointer-interactable elements tree from a given element.
 *
 * The tree is defined by the paint order found at the centre point of
 * the element's rectangle that is inside the viewport, excluding the size
 * of any rendered scrollbars.
 *
 * @param {DOMElement} el
 *     Element to determine if is pointer-interactable.
 *
 * @return {Array.<DOMElement>}
 *     Sequence of elements in paint order.
 */
element.getPointerInteractablePaintTree = function(el) {
  const doc = el.ownerDocument;
  const win = doc.defaultView;
  const container = {frame: win};
  const rootNode = el.getRootNode();

  // Include shadow DOM host only if the element's root node is not the
  // owner document.
  if (rootNode !== doc) {
    container.shadowRoot = rootNode;
  }

  // pointer-interactable elements tree, step 1
  if (element.isDisconnected(el, container)) {
    return [];
  }

  // steps 2-3
  let rects = el.getClientRects();
  if (rects.length == 0) {
    return [];
  }

  // step 4
  let centre = element.getInViewCentrePoint(rects[0], win);

  // step 5
  return doc.elementsFromPoint(centre.x, centre.y);
};

// TODO(ato): Not implemented.
// In fact, it's not defined in the spec.
element.isKeyboardInteractable = function(el) {
  return true;
};

/**
 * Attempts to scroll into view |el|.
 *
 * @param {DOMElement} el
 *     Element to scroll into view.
 */
element.scrollIntoView = function(el) {
  if (el.scrollIntoView) {
    el.scrollIntoView({block: "end", inline: "nearest", behavior: "instant"});
  }
};

element.isXULElement = function(el) {
  let ns = atom.getElementAttribute(el, "namespaceURI");
  return ns.indexOf("there.is.only.xul") >= 0;
};

const boolEls = {
  audio: ["autoplay", "controls", "loop", "muted"],
  button: ["autofocus", "disabled", "formnovalidate"],
  details: ["open"],
  dialog: ["open"],
  fieldset: ["disabled"],
  form: ["novalidate"],
  iframe: ["allowfullscreen"],
  img: ["ismap"],
  input: [
    "autofocus",
    "checked",
    "disabled",
    "formnovalidate",
    "multiple",
    "readonly",
    "required",
  ],
  keygen: ["autofocus", "disabled"],
  menuitem: ["checked", "default", "disabled"],
  object: ["typemustmatch"],
  ol: ["reversed"],
  optgroup: ["disabled"],
  option: ["disabled", "selected"],
  script: ["async", "defer"],
  select: ["autofocus", "disabled", "multiple", "required"],
  textarea: ["autofocus", "disabled", "readonly", "required"],
  track: ["default"],
  video: ["autoplay", "controls", "loop", "muted"],
};

/**
 * Tests if the attribute is a boolean attribute on element.
 *
 * @param {DOMElement} el
 *     Element to test if |attr| is a boolean attribute on.
 * @param {string} attr
 *     Attribute to test is a boolean attribute.
 *
 * @return {boolean}
 *     True if the attribute is boolean, false otherwise.
 */
element.isBooleanAttribute = function(el, attr) {
  if (el.namespaceURI !== XMLNS) {
    return false;
  }

  // global boolean attributes that apply to all HTML elements,
  // except for custom elements
  const customElement = !el.localName.includes("-");
  if ((attr == "hidden" || attr == "itemscope") && customElement) {
    return true;
  }

  if (!boolEls.hasOwnProperty(el.localName)) {
    return false;
  }
  return boolEls[el.localName].includes(attr);
};
PK
!<3*{;::"chrome/marionette/content/error.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

const ERRORS = new Set([
  "ElementClickInterceptedError",
  "ElementNotAccessibleError",
  "ElementNotInteractableError",
  "InsecureCertificateError",
  "InvalidArgumentError",
  "InvalidCookieDomainError",
  "InvalidElementStateError",
  "InvalidSelectorError",
  "InvalidSessionIDError",
  "JavaScriptError",
  "MoveTargetOutOfBoundsError",
  "NoAlertOpenError",
  "NoSuchElementError",
  "NoSuchFrameError",
  "NoSuchWindowError",
  "ScriptTimeoutError",
  "SessionNotCreatedError",
  "StaleElementReferenceError",
  "TimeoutError",
  "UnableToSetCookieError",
  "UnexpectedAlertOpenError",
  "UnknownCommandError",
  "UnknownError",
  "UnsupportedOperationError",
  "WebDriverError",
]);

const BUILTIN_ERRORS = new Set([
  "Error",
  "EvalError",
  "InternalError",
  "RangeError",
  "ReferenceError",
  "SyntaxError",
  "TypeError",
  "URIError",
]);

this.EXPORTED_SYMBOLS = ["error", "error.pprint"].concat(Array.from(ERRORS));

/** @namespace */
this.error = {};

/**
 * Check if |val| is an instance of the |Error| prototype.
 *
 * Because error objects may originate from different globals, comparing
 * the prototype of the left hand side with the prototype property from
 * the right hand side, which is what |instanceof| does, will not work.
 * If the LHS and RHS come from different globals, this check will always
 * fail because the two objects will not have the same identity.
 *
 * Therefore it is not safe to use |instanceof| in any multi-global
 * situation, e.g. in content across multiple Window objects or anywhere
 * in chrome scope.
 *
 * This function also contains a special check if |val| is an XPCOM
 * |nsIException| because they are special snowflakes and may indeed
 * cause Firefox to crash if used with |instanceof|.
 *
 * @param {*} val
 *     Any value that should be undergo the test for errorness.
 * @return {boolean}
 *     True if error, false otherwise.
 */
error.isError = function(val) {
  if (val === null || typeof val != "object") {
    return false;
  } else if (val instanceof Ci.nsIException) {
    return true;
  }

  // DOMRectList errors on string comparison
  try {
    let proto = Object.getPrototypeOf(val);
    return BUILTIN_ERRORS.has(proto.toString());
  } catch (e) {
    return false;
  }
};

/**
 * Checks if obj is an object in the WebDriverError prototypal chain.
 */
error.isWebDriverError = function(obj) {
  return error.isError(obj) &&
      ("name" in obj && ERRORS.has(obj.name));
};

/**
 * Ensures error instance is a WebDriverError.
 *
 * If the given error is already in the WebDriverError prototype
 * chain, |err| is returned unmodified.  If it is not, it is wrapped
 * in UnknownError.
 *
 * @param {Error} err
 *     Error to conditionally turn into a WebDriverError.
 *
 * @return {WebDriverError}
 *     If |err| is a WebDriverError, it is returned unmodified.
 *     Otherwise an UnknownError type is returned.
 */
error.wrap = function(err) {
  if (error.isWebDriverError(err)) {
    return err;
  }
  return new UnknownError(err);
};

/**
 * Unhandled error reporter.  Dumps the error and its stacktrace to console,
 * and reports error to the Browser Console.
 */
error.report = function(err) {
  let msg = "Marionette threw an error: " + error.stringify(err);
  dump(msg + "\n");
  if (Cu.reportError) {
    Cu.reportError(msg);
  }
};

/**
 * Prettifies an instance of Error and its stacktrace to a string.
 */
error.stringify = function(err) {
  try {
    let s = err.toString();
    if ("stack" in err) {
      s += "\n" + err.stack;
    }
    return s;
  } catch (e) {
    return "<unprintable error>";
  }
};

/**
 * Pretty-print values passed to template strings.
 *
 * Usage:
 *
 *     const {pprint} = Cu.import("chrome://marionette/content/error.js", {});
 *     let bool = {value: true};
 *     pprint`Expected boolean, got ${bool}`;
 *     => 'Expected boolean, got [object Object] {"value": true}'
 *
 *     let htmlElement = document.querySelector("input#foo");
 *     pprint`Expected element ${htmlElement}`;
 *     => 'Expected element <input id="foo" class="bar baz">'
 */
error.pprint = function(ss, ...values) {
  function prettyObject(obj) {
    let proto = Object.prototype.toString.call(obj);
    let s = "";
    try {
      s = JSON.stringify(obj);
    } catch (e) {
      if (e instanceof TypeError) {
        s = `<${e.message}>`;
      } else {
        throw e;
      }
    }
    return proto + " " + s;
  }

  function prettyElement(el) {
    let ident = [];
    if (el.id) {
      ident.push(`id="${el.id}"`);
    }
    if (el.classList.length > 0) {
      ident.push(`class="${el.className}"`);
    }

    let idents = "";
    if (ident.length > 0) {
      idents = " " + ident.join(" ");
    }

    return `<${el.localName}${idents}>`;
  }

  let res = [];
  for (let i = 0; i < ss.length; i++) {
    res.push(ss[i]);
    if (i < values.length) {
      let val = values[i];
      let s;
      try {
        if (val && val.nodeType === 1) {
          s = prettyElement(val);
        } else {
          s = prettyObject(val);
        }
      } catch (e) {
        s = typeof val;
      }
      res.push(s);
    }
  }
  return res.join("");
};

/**
 * WebDriverError is the prototypal parent of all WebDriver errors.
 * It should not be used directly, as it does not correspond to a real
 * error in the specification.
 */
class WebDriverError extends Error {
  /**
   * @param {(string|Error)=} x
   *     Optional string describing error situation or Error instance
   *     to propagate.
   */
  constructor(x) {
    super(x);
    this.name = this.constructor.name;
    this.status = "webdriver error";

    // Error's ctor does not preserve x' stack
    if (error.isError(x)) {
      this.stack = x.stack;
    }
  }

  /**
   * @return {Object.<string, string>}
   *     JSON serialisation of error prototype.
   */
  toJSON() {
    return {
      error: this.status,
      message: this.message || "",
      stacktrace: this.stack || "",
    };
  }

  /**
   * Unmarshals a JSON error representation to the appropriate Marionette
   * error type.
   *
   * @param {Object.<string, string>} json
   *     Error object.
   *
   * @return {Error}
   *     Error prototype.
   */
  static fromJSON(json) {
    if (typeof json.error == "undefined") {
      let s = JSON.stringify(json);
      throw new TypeError("Undeserialisable error type: " + s);
    }
    if (!STATUSES.has(json.error)) {
      throw new TypeError("Not of WebDriverError descent: " + json.error);
    }

    let cls = STATUSES.get(json.error);
    let err = new cls();
    if ("message" in json) {
      err.message = json.message;
    }
    if ("stacktrace" in json) {
      err.stack = json.stacktrace;
    }
    return err;
  }
}

/** The Gecko a11y API indicates that the element is not accessible. */
class ElementNotAccessibleError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "element not accessible";
  }
}

/**
 * An element click could not be completed because the element receiving
 * the events is obscuring the element that was requested clicked.
 *
 * @param {Element=} obscuredEl
 *     Element obscuring the element receiving the click.  Providing this
 *     is not required, but will produce a nicer error message.
 * @param {Map.<string, number>} coords
 *     Original click location.  Providing this is not required, but
 *     will produce a nicer error message.
 */
class ElementClickInterceptedError extends WebDriverError {
  constructor(obscuredEl = undefined, coords = undefined) {
    let msg = "";
    if (obscuredEl && coords) {
      const doc = obscuredEl.ownerDocument;
      const overlayingEl = doc.elementFromPoint(coords.x, coords.y);

      switch (obscuredEl.style.pointerEvents) {
        case "none":
          msg = error.pprint`Element ${obscuredEl} is not clickable ` +
              `at point (${coords.x},${coords.y}) ` +
              `because it does not have pointer events enabled, ` +
              error.pprint`and element ${overlayingEl} ` +
              `would receive the click instead`;
          break;

        default:
          msg = error.pprint`Element ${obscuredEl} is not clickable ` +
              `at point (${coords.x},${coords.y}) ` +
              error.pprint`because another element ${overlayingEl} ` +
              `obscures it`;
          break;
      }
    }

    super(msg);
    this.status = "element click intercepted";
  }
}

/**
 * A command could not be completed because the element is not pointer-
 * or keyboard interactable.
 */
class ElementNotInteractableError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "element not interactable";
  }
}

/**
 * Navigation caused the user agent to hit a certificate warning, which
 * is usually the result of an expired or invalid TLS certificate.
 */
class InsecureCertificateError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "insecure certificate";
  }
}

/** The arguments passed to a command are either invalid or malformed. */
class InvalidArgumentError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "invalid argument";
  }
}

class InvalidCookieDomainError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "invalid cookie domain";
  }
}

class InvalidElementStateError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "invalid element state";
  }
}

class InvalidSelectorError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "invalid selector";
  }
}

class InvalidSessionIDError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "invalid session id";
  }
}

/**
 * Creates a richly annotated error for an error situation that occurred
 * whilst evaluating injected scripts.
 */
class JavaScriptError extends WebDriverError {
  /**
   * @param {(string|Error)} x
   *     An Error object instance or a string describing the error
   *     situation.
   * @param {string=} fnName
   *     Name of the function to use in the stack trace message.
   * @param {string=} file
   *     Filename of the test file on the client.
   * @param {number=} line
   *     Line number of |file|.
   * @param {string=} script
   *     Script being executed, in text form.
   */
  constructor(x,
      {fnName = null, file = null, line = null, script = null} = {}) {
    let msg = String(x);
    let trace = "";

    if (fnName !== null) {
      trace += fnName;
      if (file !== null) {
        trace += ` @${file}`;
        if (line !== null) {
          trace += `, line ${line}`;
        }
      }
    }

    if (error.isError(x)) {
      let jsStack = x.stack.split("\n");
      let match = jsStack[0].match(/:(\d+):\d+$/);
      let jsLine = match ? parseInt(match[1]) : 0;
      if (script !== null) {
        let src = script.split("\n")[jsLine];
        trace += "\n" +
          `inline javascript, line ${jsLine}\n` +
          `src: "${src}"`;
      }
      trace += "\nStack:\n" + x.stack;
    }

    super(msg);
    this.status = "javascript error";
    this.stack = trace;
  }
}

class MoveTargetOutOfBoundsError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "move target out of bounds";
  }
}

class NoAlertOpenError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "no such alert";
  }
}

class NoSuchElementError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "no such element";
  }
}

class NoSuchFrameError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "no such frame";
  }
}

class NoSuchWindowError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "no such window";
  }
}

class ScriptTimeoutError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "script timeout";
  }
}

class SessionNotCreatedError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "session not created";
  }
}

class StaleElementReferenceError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "stale element reference";
  }
}

class TimeoutError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "timeout";
  }
}

class UnableToSetCookieError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "unable to set cookie";
  }
}

class UnexpectedAlertOpenError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "unexpected alert open";
  }
}

class UnknownCommandError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "unknown command";
  }
}

class UnknownError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "unknown error";
  }
}

class UnsupportedOperationError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "unsupported operation";
  }
}

const STATUSES = new Map([
  ["element click intercepted", ElementClickInterceptedError],
  ["element not accessible", ElementNotAccessibleError],
  ["element not interactable", ElementNotInteractableError],
  ["insecure certificate", InsecureCertificateError],
  ["invalid argument", InvalidArgumentError],
  ["invalid cookie domain", InvalidCookieDomainError],
  ["invalid element state", InvalidElementStateError],
  ["invalid selector", InvalidSelectorError],
  ["invalid session id", InvalidSessionIDError],
  ["javascript error", JavaScriptError],
  ["move target out of bounds", MoveTargetOutOfBoundsError],
  ["no alert open", NoAlertOpenError],
  ["no such element", NoSuchElementError],
  ["no such frame", NoSuchFrameError],
  ["no such window", NoSuchWindowError],
  ["script timeout", ScriptTimeoutError],
  ["session not created", SessionNotCreatedError],
  ["stale element reference", StaleElementReferenceError],
  ["timeout", TimeoutError],
  ["unable to set cookie", UnableToSetCookieError],
  ["unexpected alert open", UnexpectedAlertOpenError],
  ["unknown command", UnknownCommandError],
  ["unknown error", UnknownError],
  ["unsupported operation", UnsupportedOperationError],
  ["webdriver error", WebDriverError],
]);

// Errors must be expored on the local this scope so that the
// EXPORTED_SYMBOLS and the Cu.import("foo", {}) machinery sees them.
// We could assign each error definition directly to |this|, but
// because they are Error prototypes this would mess up their names.
for (let cls of STATUSES.values()) {
  this[cls.name] = cls;
}
PK
!<Ј\7\7%chrome/marionette/content/evaluate.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Log.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.import("chrome://marionette/content/element.js");
const {
  error,
  JavaScriptError,
  ScriptTimeoutError,
  WebDriverError,
} = Cu.import("chrome://marionette/content/error.js", {});

const logger = Log.repository.getLogger("Marionette");

this.EXPORTED_SYMBOLS = ["evaluate", "sandbox", "Sandboxes"];

const ARGUMENTS = "__webDriverArguments";
const CALLBACK = "__webDriverCallback";
const COMPLETE = "__webDriverComplete";
const DEFAULT_TIMEOUT = 10000; // ms
const FINISH = "finish";
const MARIONETTE_SCRIPT_FINISHED = "marionetteScriptFinished";
const ELEMENT_KEY = "element";
const W3C_ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";

/** @namespace */
this.evaluate = {};

/**
 * Evaluate a script in given sandbox.
 *
 * If the option var>directInject</var> is not specified, the script
 * will be executed as a function with the <var>args</var> argument
 * applied.
 *
 * The arguments provided by the <var>args</var> argument are exposed
 * through the <code>arguments</code> object available in the script
 * context, and if the script is executed asynchronously with the
 * <var>async</var> option, an additional last argument that is synonymous
 * to the <code>marionetteScriptFinished</code> global is appended, and
 * can be accessed through <code>arguments[arguments.length - 1]</code>.
 *
 * The <var>timeout</var> option specifies the duration for how long
 * the script should be allowed to run before it is interrupted and aborted.
 * An interrupted script will cause a {@link ScriptTimeoutError} to occur.
 *
 * The <var>async</var> option indicates that the script will
 * not return until the <code>marionetteScriptFinished</code> global
 * callback is invoked, which is analogous to the last argument of the
 * <code>arguments</code> object.
 *
 * The option <var>directInject</var> causes the script to be evaluated
 * without being wrapped in a function and the provided arguments will
 * be disregarded.  This will cause such things as root scope return
 * statements to throw errors because they are not used inside a function.
 *
 * The <var>file</var> option is used in error messages to provide
 * information on the origin script file in the local end.
 *
 * The <var>line</var> option is used in error messages, along with
 * <var>filename</var>, to provide the line number in the origin script
 * file on the local end.
 *
 * @param {nsISandbox} sb
 *     Sandbox the script will be evaluted in.
 * @param {string} script
 *     Script to evaluate.
 * @param {Array.<?>=} args
 *     A sequence of arguments to call the script with.
 * @param {boolean=} [async=false] async
 *     Indicates if the script should return immediately or wait for
 *     the callback to be invoked before returning.
 * @param {boolean=} [debug=false] debug
 *     Attaches an <code>onerror</code> event listener.
 * @param {string=} [file="dummy file"] file
 *     File location of the program in the client.
 * @param {number=} [line=0] line
 *     Line number of th eprogram in the client.
 * @param {string=} sandboxName
 *     Name of the sandbox.  Elevated system privileges, equivalent to
 *     chrome space, will be given if it is <tt>system</tt>.
 * @param {number=} [timeout=DEFAULT_TIMEOUT] timeout
 *     Duration in milliseconds before interrupting the script.
 *
 * @return {Promise}
 *     A promise that when resolved will give you the return value from
 *     the script.  Note that the return value requires serialisation before
 *     it can be sent to the client.
 *
 * @throws {JavaScriptError}
 *   If an {@link Error} was thrown whilst evaluating the script.
 * @throws {ScriptTimeoutError}
 *   If the script was interrupted due to script timeout.
 */
evaluate.sandbox = function(sb, script, args = [],
    {
      async = false,
      debug = false,
      directInject = false,
      file = "dummy file",
      line = 0,
      sandboxName = null,
      timeout = DEFAULT_TIMEOUT,
    } = {}) {
  let scriptTimeoutID, timeoutHandler, unloadHandler;

  let promise = new Promise((resolve, reject) => {
    let src = "";
    sb[COMPLETE] = resolve;
    timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
    unloadHandler = sandbox.cloneInto(
        () => reject(new JavaScriptError("Document was unloaded")),
        sb);

    // wrap in function
    if (!directInject) {
      if (async) {
        sb[CALLBACK] = sb[COMPLETE];
      }
      sb[ARGUMENTS] = sandbox.cloneInto(args, sb);

      // callback function made private
      // so that introspection is possible
      // on the arguments object
      if (async) {
        sb[CALLBACK] = sb[COMPLETE];
        src += `${ARGUMENTS}.push(rv => ${CALLBACK}(rv));`;
      }

      src += `(function() { ${script} }).apply(null, ${ARGUMENTS})`;

      // marionetteScriptFinished is not WebDriver conformant,
      // hence it is only exposed to immutable sandboxes
      if (sandboxName) {
        sb[MARIONETTE_SCRIPT_FINISHED] = sb[CALLBACK];
      }
    }

    // onerror is not hooked on by default because of the inability to
    // differentiate content errors from chrome errors.
    //
    // see bug 1128760 for more details
    if (debug) {
      sb.window.onerror = (msg, url, line) => {
        let err = new JavaScriptError(`${msg} at ${url}:${line}`);
        reject(err);
      };
    }

    // timeout and unload handlers
    scriptTimeoutID = setTimeout(timeoutHandler, timeout);
    sb.window.onunload = unloadHandler;

    let res;
    try {
      res = Cu.evalInSandbox(src, sb, "1.8", file, 0);
    } catch (e) {
      let err = new JavaScriptError(e, {
        fnName: "execute_script",
        file,
        line,
        script,
      });
      reject(err);
    }

    if (!async) {
      resolve(res);
    }
  });

  return promise.then(res => {
    clearTimeout(scriptTimeoutID);
    sb.window.removeEventListener("unload", unloadHandler);
    return res;
  });
};

/**
 * Convert any web elements in arbitrary objects to DOM elements by
 * looking them up in the seen element store.
 *
 * @param {Object} obj
 *     Arbitrary object containing web elements.
 * @param {element.Store} seenEls
 *     Element store to use for lookup of web element references.
 * @param {Window} win
 *     Window.
 * @param {ShadowRoot} shadowRoot
 *     Shadow root.
 *
 * @return {Object}
 *     Same object as provided by <var>obj</var> with the web elements
 *     replaced by DOM elements.
 */
evaluate.fromJSON = function(obj, seenEls, win, shadowRoot = undefined) {
  switch (typeof obj) {
    case "boolean":
    case "number":
    case "string":
    default:
      return obj;

    case "object":
      if (obj === null) {
        return obj;

      // arrays
      } else if (Array.isArray(obj)) {
        return obj.map(e => evaluate.fromJSON(e, seenEls, win, shadowRoot));

      // web elements
      } else if (Object.keys(obj).includes(element.Key) ||
          Object.keys(obj).includes(element.LegacyKey)) {
        /* eslint-disable */
        let uuid = obj[element.Key] || obj[element.LegacyKey];
        let el = seenEls.get(uuid, {frame: win, shadowRoot: shadowRoot});
        /* eslint-enable */
        if (!el) {
          throw new WebDriverError(`Unknown element: ${uuid}`);
        }
        return el;

      }

      // arbitrary objects
      let rv = {};
      for (let prop in obj) {
        rv[prop] = evaluate.fromJSON(obj[prop], seenEls, win, shadowRoot);
      }
      return rv;
  }
};

/**
 * Convert arbitrary objects to JSON-safe primitives that can be
 * transported over the Marionette protocol.
 *
 * Any DOM elements are converted to web elements by looking them up
 * and/or adding them to the element store provided.
 *
 * @param {Object} obj
 *     Object to be marshaled.
 * @param {element.Store} seenEls
 *     Element store to use for lookup of web element references.
 *
 * @return {Object}
 *     Same object as provided by <var>obj</var> with the elements
 *     replaced by web elements.
 */
evaluate.toJSON = function(obj, seenEls) {
  const t = Object.prototype.toString.call(obj);

  // null
  if (t == "[object Undefined]" || t == "[object Null]") {
    return null;

  // literals
  } else if (t == "[object Boolean]" ||
      t == "[object Number]" ||
      t == "[object String]") {
    return obj;

  // Array, NodeList, HTMLCollection, et al.
  } else if (element.isCollection(obj)) {
    return [...obj].map(el => evaluate.toJSON(el, seenEls));

  // HTMLElement
  } else if ("nodeType" in obj && obj.nodeType == obj.ELEMENT_NODE) {
    let uuid = seenEls.add(obj);
    return element.makeWebElement(uuid);

  // custom JSON representation
  } else if (typeof obj["toJSON"] == "function") {
    let unsafeJSON = obj.toJSON();
    return evaluate.toJSON(unsafeJSON, seenEls);
  }

  // arbitrary objects + files
  let rv = {};
  for (let prop in obj) {
    try {
      rv[prop] = evaluate.toJSON(obj[prop], seenEls);
    } catch (e) {
      if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
        logger.debug(`Skipping ${prop}: ${e.message}`);
      } else {
        throw e;
      }
    }
  }
  return rv;
};

/**
 * Cu.isDeadWrapper does not return true for a dead sandbox that was
 * assosciated with and extension popup. This provides a way to still
 * test for a dead object.
 *
 * @param {Object} obj
 *     A potentially dead object.
 * @param {string} prop
 *     Name of a property on the object.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is dead, false otherwise.
 */
evaluate.isDead = function(obj, prop) {
  try {
    obj[prop];
  } catch (e) {
    if (e.message.includes("dead object")) {
      return true;
    }
    throw e;
  }
  return false;
};

this.sandbox = {};

/**
 * Provides a safe way to take an object defined in a privileged scope and
 * create a structured clone of it in a less-privileged scope.  It returns
 * a reference to the clone.
 *
 * Unlike for {@link Components.utils.cloneInto}, <var>obj</var> may
 * contain functions and DOM elemnets.
 */
sandbox.cloneInto = function(obj, sb) {
  return Cu.cloneInto(obj, sb, {cloneFunctions: true, wrapReflectors: true});
};

/**
 * Augment given sandbox by an adapter that has an <code>exports</code>
 * map property, or a normal map, of function names and function
 * references.
 *
 * @param {Sandbox} sb
 *     The sandbox to augment.
 * @param {Object} adapter
 *     Object that holds an <code>exports</code> property, or a map, of
 *     function names and function references.
 *
 * @return {Sandbox}
 *     The augmented sandbox.
 */
sandbox.augment = function(sb, adapter) {
  function* entries(obj) {
    for (let key of Object.keys(obj)) {
      yield [key, obj[key]];
    }
  }

  let funcs = adapter.exports || entries(adapter);
  for (let [name, func] of funcs) {
    sb[name] = func;
  }

  return sb;
};

/**
 * Creates a sandbox.
 *
 * @param {Window} window
 *     The DOM Window object.
 * @param {nsIPrincipal=} principal
 *     An optional, custom principal to prefer over the Window.  Useful if
 *     you need elevated security permissions.
 *
 * @return {Sandbox}
 *     The created sandbox.
 */
sandbox.create = function(window, principal = null, opts = {}) {
  let p = principal || window;
  opts = Object.assign({
    sameZoneAs: window,
    sandboxPrototype: window,
    wantComponents: true,
    wantXrays: true,
  }, opts);
  return new Cu.Sandbox(p, opts);
};

/**
 * Creates a mutable sandbox, where changes to the global scope
 * will have lasting side-effects.
 *
 * @param {Window} window
 *     The DOM Window object.
 *
 * @return {Sandbox}
 *     The created sandbox.
 */
sandbox.createMutable = function(window) {
  let opts = {
    wantComponents: false,
    wantXrays: false,
  };
  return sandbox.create(window, null, opts);
};

sandbox.createSystemPrincipal = function(window) {
  let principal = Cc["@mozilla.org/systemprincipal;1"]
      .createInstance(Ci.nsIPrincipal);
  return sandbox.create(window, principal);
};

sandbox.createSimpleTest = function(window, harness) {
  let sb = sandbox.create(window);
  sb = sandbox.augment(sb, harness);
  sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
  return sb;
};

/**
 * Sandbox storage.  When the user requests a sandbox by a specific name,
 * if one exists in the storage this will be used as long as its window
 * reference is still valid.
 *
 * @memberof evaluate
 */
this.Sandboxes = class {
  /**
   * @param {function(): Window} windowFn
   *     A function that returns the references to the current Window
   *     object.
   */
  constructor(windowFn) {
    this.windowFn_ = windowFn;
    this.boxes_ = new Map();
  }

  get window_() {
    return this.windowFn_();
  }

  /**
   * Factory function for getting a sandbox by name, or failing that,
   * creating a new one.
   *
   * If the sandbox' window does not match the provided window, a new one
   * will be created.
   *
   * @param {string} name
   *     The name of the sandbox to get or create.
   * @param {boolean=} [fresh=false] fresh
   *     Remove old sandbox by name first, if it exists.
   *
   * @return {Sandbox}
   *     A used or fresh sandbox.
   */
  get(name = "default", fresh = false) {
    let sb = this.boxes_.get(name);
    if (sb) {
      if (fresh || evaluate.isDead(sb, "window") || sb.window != this.window_) {
        this.boxes_.delete(name);
        return this.get(name, false);
      }
    } else {
      if (name == "system") {
        sb = sandbox.createSystemPrincipal(this.window_);
      } else {
        sb = sandbox.create(this.window_);
      }
      this.boxes_.set(name, sb);
    }
    return sb;
  }

  /** Clears cache of sandboxes. */
  clear() {
    this.boxes_.clear();
  }
};
PK
!<_DD"chrome/marionette/content/event.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** Provides functionality for creating and sending DOM events. */
this.event = {};

"use strict";
/* global content, is */

const {interfaces: Ci, utils: Cu, classes: Cc} = Components;

Cu.import("resource://gre/modules/Log.jsm");
const logger = Log.repository.getLogger("Marionette");

Cu.import("chrome://marionette/content/element.js");
const {ElementNotInteractableError} =
    Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["event"];

// must be synchronised with nsIDOMWindowUtils
const COMPOSITION_ATTR_RAWINPUT = 0x02;
const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;

// TODO(ato): Document!
let seenEvent = false;

function getDOMWindowUtils(win) {
  if (!win) {
    win = window;
  }

  // this assumes we are operating in chrome space
  return win.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils);
}

/** @namespace */
this.event = {};

event.MouseEvents = {
  click: 0,
  dblclick: 1,
  mousedown: 2,
  mouseup: 3,
  mouseover: 4,
  mouseout: 5,
};

event.Modifiers = {
  shiftKey: 0,
  ctrlKey: 1,
  altKey: 2,
  metaKey: 3,
};

event.MouseButton = {
  isPrimary(button) {
    return button === 0;
  },
  isAuxiliary(button) {
    return button === 1;
  },
  isSecondary(button) {
    return button === 2;
  },
};

/**
 * Sends a mouse event to given target.
 *
 * @param {nsIDOMMouseEvent} mouseEvent
 *     Event to send.
 * @param {(DOMElement|string)} target
 *     Target of event.  Can either be an element or the ID of an element.
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 *
 * @throws {TypeError}
 *     If the event is unsupported.
 */
event.sendMouseEvent = function(mouseEvent, target, window = undefined) {
  if (!event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
    throw new TypeError("Unsupported event type: " + mouseEvent.type);
  }

  if (!target.nodeType && typeof target != "string") {
    throw new TypeError(
        "Target can only be a DOM element or a string: " + target);
  }

  if (!target.nodeType) {
    target = window.document.getElementById(target);
  } else {
    window = window || target.ownerGlobal;
  }

  let ev = window.document.createEvent("MouseEvent");

  let view = window;

  let detail = mouseEvent.detail;
  if (!detail) {
    if (mouseEvent.type in ["click", "mousedown", "mouseup"]) {
      detail = 1;
    } else if (mouseEvent.type == "dblclick") {
      detail = 2;
    } else {
      detail = 0;
    }
  }

  let screenX = mouseEvent.screenX || 0;
  let screenY = mouseEvent.screenY || 0;
  let clientX = mouseEvent.clientX || 0;
  let clientY = mouseEvent.clientY || 0;
  let ctrlKey = mouseEvent.ctrlKey || false;
  let altKey = mouseEvent.altKey || false;
  let shiftKey = mouseEvent.shiftKey || false;
  let metaKey = mouseEvent.metaKey || false;
  let button = mouseEvent.button || 0;
  let relatedTarget = mouseEvent.relatedTarget || null;

  ev.initMouseEvent(
      mouseEvent.type,
      /* canBubble */ true,
      /* cancelable */ true,
      view,
      detail,
      screenX,
      screenY,
      clientX,
      clientY,
      ctrlKey,
      altKey,
      shiftKey,
      metaKey,
      button,
      relatedTarget);
};

/**
 * Send character to the currently focused element.
 *
 * This function handles casing of characters (sends the right charcode,
 * and sends a shift key for uppercase chars).  No other modifiers are
 * handled at this point.
 *
 * For now this method only works for English letters (lower and upper
 * case) and the digits 0-9.
 */
event.sendChar = function(char, window = undefined) {
  // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
  let hasShift = (char == char.toUpperCase());
  event.synthesizeKey(char, {shiftKey: hasShift}, window);
};

/**
 * Send string to the focused element.
 *
 * For now this method only works for English letters (lower and upper
 * case) and the digits 0-9.
 */
event.sendString = function(string, window = undefined) {
  for (let i = 0; i < string.length; ++i) {
    event.sendChar(string.charAt(i), window);
  }
};

/**
 * Send the non-character key to the focused element.
 *
 * The name of the key should be the part that comes after "DOM_VK_"
 * in the nsIDOMKeyEvent constant name for this key.  No modifiers are
 * handled at this point.
 */
event.sendKey = function(key, window = undefined) {
  let keyName = "VK_" + key.toUpperCase();
  event.synthesizeKey(keyName, {shiftKey: false}, window);
};

// TODO(ato): Unexpose this when action.Chain#emitMouseEvent
// no longer emits its own events
event.parseModifiers_ = function(modifiers) {
  let mval = 0;
  if (modifiers.shiftKey) {
    mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
  }
  if (modifiers.ctrlKey) {
    mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
  }
  if (modifiers.altKey) {
    mval |= Ci.nsIDOMNSEvent.ALT_MASK;
  }
  if (modifiers.metaKey) {
    mval |= Ci.nsIDOMNSEvent.META_MASK;
  }
  if (modifiers.accelKey) {
    if (navigator.platform.indexOf("Mac") >= 0) {
      mval |= Ci.nsIDOMNSEvent.META_MASK;
    } else {
      mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
    }
  }
  return mval;
};

/**
 * Synthesise a mouse event on a target.
 *
 * The actual client point is determined by taking the aTarget's client
 * box and offseting it by offsetX and offsetY.  This allows mouse clicks
 * to be simulated by calling this method.
 *
 * If the type is specified, an mouse event of that type is
 * fired. Otherwise, a mousedown followed by a mouse up is performed.
 *
 * @param {Element} element
 *     Element to click.
 * @param {number} offsetX
 *     Horizontal offset to click from the target's bounding box.
 * @param {number} offsetY
 *     Vertical offset to click from the target's bounding box.
 * @param {Object.<string, ?>} opts
 *     Object which may contain the properties "shiftKey", "ctrlKey",
 *     "altKey", "metaKey", "accessKey", "clickCount", "button", and
 *     "type".
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 */
event.synthesizeMouse = function(
    element, offsetX, offsetY, opts, window = undefined) {
  let rect = element.getBoundingClientRect();
  event.synthesizeMouseAtPoint(
      rect.left + offsetX, rect.top + offsetY, opts, window);
};

/*
 * Synthesize a mouse event at a particular point in a window.
 *
 * If the type of the event is specified, a mouse event of that type is
 * fired. Otherwise, a mousedown followed by a mouse up is performed.
 *
 * @param {number} left
 *     CSS pixels from the left document margin.
 * @param {number} top
 *     CSS pixels from the top document margin.
 * @param {Object.<string, ?>} opts
 *     Object which may contain the properties "shiftKey", "ctrlKey",
 *     "altKey", "metaKey", "accessKey", "clickCount", "button", and
 *     "type".
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 */
event.synthesizeMouseAtPoint = function(
    left, top, opts, window = undefined) {

  let domutils = getDOMWindowUtils(window);

  let button = opts.button || 0;
  let clickCount = opts.clickCount || 1;
  let modifiers = event.parseModifiers_(opts);
  let pressure = ("pressure" in opts) ? opts.pressure : 0;
  let inputSource = ("inputSource" in opts) ? opts.inputSource :
      Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
  let isDOMEventSynthesized =
      ("isSynthesized" in opts) ? opts.isSynthesized : true;
  let isWidgetEventSynthesized;
  if ("isWidgetEventSynthesized" in opts) {
    isWidgetEventSynthesized = opts.isWidgetEventSynthesized;
  } else {
    isWidgetEventSynthesized = false;
  }
  let buttons;
  if ("buttons" in opts) {
    buttons = opts.buttons;
  } else {
    buttons = domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
  }

  if (("type" in opts) && opts.type) {
    domutils.sendMouseEvent(
        opts.type,
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        buttons);
  } else {
    domutils.sendMouseEvent(
        "mousedown",
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        buttons);
    domutils.sendMouseEvent(
        "mouseup",
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        buttons);
  }
};

/**
 * Call event.synthesizeMouse with coordinates at the centre of the
 * target.
 */
event.synthesizeMouseAtCenter = function(element, event, window) {
  let rect = element.getBoundingClientRect();
  event.synthesizeMouse(
      element,
      rect.width / 2,
      rect.height / 2,
      event,
      window);
};

/* eslint-disable */
function computeKeyCodeFromChar_(char) {
  if (char.length != 1) {
    return 0;
  }

  if (char in VIRTUAL_KEYCODE_LOOKUP) {
    return Ci.nsIDOMKeyEvent["DOM_" + VIRTUAL_KEYCODE_LOOKUP[char]];
  }

  if (char >= "a" && char <= "z") {
    return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "a".charCodeAt(0);
  }
  if (char >= "A" && char <= "Z") {
    return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "A".charCodeAt(0);
  }
  if (char >= "0" && char <= "9") {
    return Ci.nsIDOMKeyEvent.DOM_VK_0 + char.charCodeAt(0) - "0".charCodeAt(0);
  }

  // returns US keyboard layout's keycode
  switch (char) {
    case "~":
    case "`":
      return Ci.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;

    case "!":
      return Ci.nsIDOMKeyEvent.DOM_VK_1;

    case "@":
      return Ci.nsIDOMKeyEvent.DOM_VK_2;

    case "#":
      return Ci.nsIDOMKeyEvent.DOM_VK_3;

    case "$":
      return Ci.nsIDOMKeyEvent.DOM_VK_4;

    case "%":
      return Ci.nsIDOMKeyEvent.DOM_VK_5;

    case "^":
      return Ci.nsIDOMKeyEvent.DOM_VK_6;

    case "&":
      return Ci.nsIDOMKeyEvent.DOM_VK_7;

    case "*":
      return Ci.nsIDOMKeyEvent.DOM_VK_8;

    case "(":
      return Ci.nsIDOMKeyEvent.DOM_VK_9;

    case ")":
      return Ci.nsIDOMKeyEvent.DOM_VK_0;

    case "-":
    case "_":
      return Ci.nsIDOMKeyEvent.DOM_VK_SUBTRACT;

    case "+":
    case "=":
      return Ci.nsIDOMKeyEvent.DOM_VK_EQUALS;

    case "{":
    case "[":
      return Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;

    case "}":
    case "]":
      return Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;

    case "|":
    case "\\":
      return Ci.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;

    case ":":
    case ";":
      return Ci.nsIDOMKeyEvent.DOM_VK_SEMICOLON;

    case "'":
    case "\"":
      return Ci.nsIDOMKeyEvent.DOM_VK_QUOTE;

    case "<":
    case ",":
      return Ci.nsIDOMKeyEvent.DOM_VK_COMMA;

    case ">":
    case ".":
      return Ci.nsIDOMKeyEvent.DOM_VK_PERIOD;

    case "?":
    case "/":
      return Ci.nsIDOMKeyEvent.DOM_VK_SLASH;

    case "\n":
      return Ci.nsIDOMKeyEvent.DOM_VK_RETURN;

    default:
      return 0;
  }
}
/* eslint-enable */

/**
 * Returns true if the given key should cause keypress event when widget
 * handles the native key event.  Otherwise, false.
 *
 * The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
 * or a key name begins with "VK_", or a character.
 */
event.isKeypressFiredKey = function(key) {
  if (typeof key == "string") {
    if (key.indexOf("VK_") === 0) {
      key = Ci.nsIDOMKeyEvent["DOM_" + key];
      if (!key) {
        throw new TypeError("Unknown key: " + key);
      }

    // if key generates a character, it must cause a keypress event
    } else {
      return true;
    }
  }

  switch (key) {
    case Ci.nsIDOMKeyEvent.DOM_VK_SHIFT:
    case Ci.nsIDOMKeyEvent.DOM_VK_CONTROL:
    case Ci.nsIDOMKeyEvent.DOM_VK_ALT:
    case Ci.nsIDOMKeyEvent.DOM_VK_CAPS_LOCK:
    case Ci.nsIDOMKeyEvent.DOM_VK_NUM_LOCK:
    case Ci.nsIDOMKeyEvent.DOM_VK_SCROLL_LOCK:
    case Ci.nsIDOMKeyEvent.DOM_VK_META:
      return false;

    default:
      return true;
  }
};

/**
 * Synthesise a key event.
 *
 * It is targeted at whatever would be targeted by an actual keypress
 * by the user, typically the focused element.
 *
 * @param {string} key
 *     Key to synthesise.  Should either be a character or a key code
 *     starting with "VK_" such as VK_RETURN, or a normalized key value.
 * @param {Object.<string, ?>} event
 *     Object which may contain the properties shiftKey, ctrlKey, altKey,
 *     metaKey, accessKey, type.  If the type is specified (keydown or keyup),
 *     a key event of that type is fired.  Otherwise, a keydown, a keypress,
 *     and then a keyup event are fired in sequence.
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 *
 * @throws {TypeError}
 *     If unknown key.
 */
event.synthesizeKey = function(key, event, win = undefined) {
  var TIP = getTIP_(win);
  if (!TIP) {
    return;
  }
  var KeyboardEvent = getKeyboardEvent_(win);
  var modifiers = emulateToActivateModifiers_(TIP, event, win);
  var keyEventDict = createKeyboardEventDictionary_(key, event, win);
  var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
  var dispatchKeydown =
    !("type" in event) || event.type === "keydown" || !event.type;
  var dispatchKeyup =
    !("type" in event) || event.type === "keyup" || !event.type;

  try {
    if (dispatchKeydown) {
      TIP.keydown(keyEvent, keyEventDict.flags);
      if ("repeat" in event && event.repeat > 1) {
        keyEventDict.dictionary.repeat = true;
        var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
        for (var i = 1; i < event.repeat; i++) {
          TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
        }
      }
    }
    if (dispatchKeyup) {
      TIP.keyup(keyEvent, keyEventDict.flags);
    }
  } finally {
    emulateToInactivateModifiers_(TIP, modifiers, win);
  }
};

var TIPMap = new WeakMap();

function getTIP_(win, callback) {
  if (!win) {
    win = window;
  }
  var tip;
  if (TIPMap.has(win)) {
    tip = TIPMap.get(win);
  } else {
    tip =
      Cc["@mozilla.org/text-input-processor;1"].
        createInstance(Ci.nsITextInputProcessor);
    TIPMap.set(win, tip);
  }
  if (!tip.beginInputTransactionForTests(win, callback)) {
    tip = null;
    TIPMap.delete(win);
  }
  return tip;
}

function getKeyboardEvent_(win = window) {
  if (typeof KeyboardEvent != "undefined") {
    try {
      // See if the object can be instantiated; sometimes this yields
      // 'TypeError: can't access dead object' or 'KeyboardEvent is not
      // a constructor'.
      new KeyboardEvent("", {});
      return KeyboardEvent;
    } catch (ex) {}
  }
  if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
    return content.KeyboardEvent;
  }
  return win.KeyboardEvent;
}

function createKeyboardEventDictionary_(key, keyEvent, win = window) {
  var result = {dictionary: null, flags: 0};
  var keyCodeIsDefined = "keyCode" in keyEvent &&
      keyEvent.keyCode != undefined;
  var keyCode =
    (keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
      keyEvent.keyCode : 0;
  var keyName = "Unidentified";
  if (key.indexOf("KEY_") == 0) {
    keyName = key.substr("KEY_".length);
    result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
  } else if (key.indexOf("VK_") == 0) {
    keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
    if (!keyCode) {
      throw "Unknown key: " + key;
    }
    keyName = guessKeyNameFromKeyCode_(keyCode, win);
    if (!isPrintable(keyCode, win)) {
      result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
    }
  } else if (key != "") {
    keyName = key;
    if (!keyCodeIsDefined) {
      keyCode = computeKeyCodeFromChar_(key.charAt(0));
    }
    if (!keyCode) {
      result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
    }
    // only force printable if "raw character" and event key match, like "a"
    if (!("key" in keyEvent && key != keyEvent.key)) {
      result.flags |= Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
    }
  }
  var locationIsDefined = "location" in keyEvent;
  if (locationIsDefined && keyEvent.location === 0) {
    result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
  }
  result.dictionary = {
    key: "key" in keyEvent ? keyEvent.key : keyName,
    code: "code" in keyEvent ? keyEvent.code : "",
    location: locationIsDefined ? keyEvent.location : 0,
    repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
    keyCode,
  };
  return result;
}

function emulateToActivateModifiers_(TIP, keyEvent, win = window) {
  if (!keyEvent) {
    return null;
  }
  var KeyboardEvent = getKeyboardEvent_(win);

  var modifiers = {
    normal: [
      {key: "Alt",        attr: "altKey"},
      {key: "AltGraph",   attr: "altGraphKey"},
      {key: "Control",    attr: "ctrlKey"},
      {key: "Fn",         attr: "fnKey"},
      {key: "Meta",       attr: "metaKey"},
      {key: "OS",         attr: "osKey"},
      {key: "Shift",      attr: "shiftKey"},
      {key: "Symbol",     attr: "symbolKey"},
      {key: isMac_(win) ? "Meta" : "Control", attr: "accelKey"},
    ],
    lockable: [
      {key: "CapsLock",   attr: "capsLockKey"},
      {key: "FnLock",     attr: "fnLockKey"},
      {key: "NumLock",    attr: "numLockKey"},
      {key: "ScrollLock", attr: "scrollLockKey"},
      {key: "SymbolLock", attr: "symbolLockKey"},
    ],
  };

  for (let i = 0; i < modifiers.normal.length; i++) {
    if (!keyEvent[modifiers.normal[i].attr]) {
      continue;
    }
    if (TIP.getModifierState(modifiers.normal[i].key)) {
      continue; // already activated.
    }
    let event = new KeyboardEvent("", {key: modifiers.normal[i].key});
    TIP.keydown(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
    modifiers.normal[i].activated = true;
  }

  for (let j = 0; j < modifiers.lockable.length; j++) {
    if (!keyEvent[modifiers.lockable[j].attr]) {
      continue;
    }
    if (TIP.getModifierState(modifiers.lockable[j].key)) {
      continue; // already activated.
    }
    let event = new KeyboardEvent("", {key: modifiers.lockable[j].key});
    TIP.keydown(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
    TIP.keyup(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
    modifiers.lockable[j].activated = true;
  }

  return modifiers;
}

function emulateToInactivateModifiers_(TIP, modifiers, win = window) {
  if (!modifiers) {
    return;
  }
  let KeyboardEvent = getKeyboardEvent_(win);
  for (let i = 0; i < modifiers.normal.length; i++) {
    if (!modifiers.normal[i].activated) {
      continue;
    }
    let event = new KeyboardEvent("", {key: modifiers.normal[i].key});
    TIP.keyup(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  }
  for (let j = 0; j < modifiers.lockable.length; j++) {
    if (!modifiers.lockable[j].activated) {
      continue;
    }
    if (!TIP.getModifierState(modifiers.lockable[j].key)) {
      continue; // who already inactivated this?
    }
    let event = new KeyboardEvent("", {key: modifiers.lockable[j].key});
    TIP.keydown(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
    TIP.keyup(event,
        TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  }
}

function isMac_(win = window) {
  if (win) {
    try {
      return win.navigator.platform.indexOf("Mac") > -1;
    } catch (ex) {}
  }
  return navigator.platform.indexOf("Mac") > -1;
}

/* eslint-disable */
function guessKeyNameFromKeyCode_(aKeyCode, win = window) {
  var KeyboardEvent = getKeyboardEvent_(win);
  switch (aKeyCode) {
    case KeyboardEvent.DOM_VK_CANCEL:
      return "Cancel";
    case KeyboardEvent.DOM_VK_HELP:
      return "Help";
    case KeyboardEvent.DOM_VK_BACK_SPACE:
      return "Backspace";
    case KeyboardEvent.DOM_VK_TAB:
      return "Tab";
    case KeyboardEvent.DOM_VK_CLEAR:
      return "Clear";
    case KeyboardEvent.DOM_VK_RETURN:
      return "Enter";
    case KeyboardEvent.DOM_VK_SHIFT:
      return "Shift";
    case KeyboardEvent.DOM_VK_CONTROL:
      return "Control";
    case KeyboardEvent.DOM_VK_ALT:
      return "Alt";
    case KeyboardEvent.DOM_VK_PAUSE:
      return "Pause";
    case KeyboardEvent.DOM_VK_EISU:
      return "Eisu";
    case KeyboardEvent.DOM_VK_ESCAPE:
      return "Escape";
    case KeyboardEvent.DOM_VK_CONVERT:
      return "Convert";
    case KeyboardEvent.DOM_VK_NONCONVERT:
      return "NonConvert";
    case KeyboardEvent.DOM_VK_ACCEPT:
      return "Accept";
    case KeyboardEvent.DOM_VK_MODECHANGE:
      return "ModeChange";
    case KeyboardEvent.DOM_VK_PAGE_UP:
      return "PageUp";
    case KeyboardEvent.DOM_VK_PAGE_DOWN:
      return "PageDown";
    case KeyboardEvent.DOM_VK_END:
      return "End";
    case KeyboardEvent.DOM_VK_HOME:
      return "Home";
    case KeyboardEvent.DOM_VK_LEFT:
      return "ArrowLeft";
    case KeyboardEvent.DOM_VK_UP:
      return "ArrowUp";
    case KeyboardEvent.DOM_VK_RIGHT:
      return "ArrowRight";
    case KeyboardEvent.DOM_VK_DOWN:
      return "ArrowDown";
    case KeyboardEvent.DOM_VK_SELECT:
      return "Select";
    case KeyboardEvent.DOM_VK_PRINT:
      return "Print";
    case KeyboardEvent.DOM_VK_EXECUTE:
      return "Execute";
    case KeyboardEvent.DOM_VK_PRINTSCREEN:
      return "PrintScreen";
    case KeyboardEvent.DOM_VK_INSERT:
      return "Insert";
    case KeyboardEvent.DOM_VK_DELETE:
      return "Delete";
    case KeyboardEvent.DOM_VK_WIN:
      return "OS";
    case KeyboardEvent.DOM_VK_CONTEXT_MENU:
      return "ContextMenu";
    case KeyboardEvent.DOM_VK_SLEEP:
      return "Standby";
    case KeyboardEvent.DOM_VK_F1:
      return "F1";
    case KeyboardEvent.DOM_VK_F2:
      return "F2";
    case KeyboardEvent.DOM_VK_F3:
      return "F3";
    case KeyboardEvent.DOM_VK_F4:
      return "F4";
    case KeyboardEvent.DOM_VK_F5:
      return "F5";
    case KeyboardEvent.DOM_VK_F6:
      return "F6";
    case KeyboardEvent.DOM_VK_F7:
      return "F7";
    case KeyboardEvent.DOM_VK_F8:
      return "F8";
    case KeyboardEvent.DOM_VK_F9:
      return "F9";
    case KeyboardEvent.DOM_VK_F10:
      return "F10";
    case KeyboardEvent.DOM_VK_F11:
      return "F11";
    case KeyboardEvent.DOM_VK_F12:
      return "F12";
    case KeyboardEvent.DOM_VK_F13:
      return "F13";
    case KeyboardEvent.DOM_VK_F14:
      return "F14";
    case KeyboardEvent.DOM_VK_F15:
      return "F15";
    case KeyboardEvent.DOM_VK_F16:
      return "F16";
    case KeyboardEvent.DOM_VK_F17:
      return "F17";
    case KeyboardEvent.DOM_VK_F18:
      return "F18";
    case KeyboardEvent.DOM_VK_F19:
      return "F19";
    case KeyboardEvent.DOM_VK_F20:
      return "F20";
    case KeyboardEvent.DOM_VK_F21:
      return "F21";
    case KeyboardEvent.DOM_VK_F22:
      return "F22";
    case KeyboardEvent.DOM_VK_F23:
      return "F23";
    case KeyboardEvent.DOM_VK_F24:
      return "F24";
    case KeyboardEvent.DOM_VK_NUM_LOCK:
      return "NumLock";
    case KeyboardEvent.DOM_VK_SCROLL_LOCK:
      return "ScrollLock";
    case KeyboardEvent.DOM_VK_VOLUME_MUTE:
      return "AudioVolumeMute";
    case KeyboardEvent.DOM_VK_VOLUME_DOWN:
      return "AudioVolumeDown";
    case KeyboardEvent.DOM_VK_VOLUME_UP:
      return "AudioVolumeUp";
    case KeyboardEvent.DOM_VK_META:
      return "Meta";
    case KeyboardEvent.DOM_VK_ALTGR:
      return "AltGraph";
    case KeyboardEvent.DOM_VK_ATTN:
      return "Attn";
    case KeyboardEvent.DOM_VK_CRSEL:
      return "CrSel";
    case KeyboardEvent.DOM_VK_EXSEL:
      return "ExSel";
    case KeyboardEvent.DOM_VK_EREOF:
      return "EraseEof";
    case KeyboardEvent.DOM_VK_PLAY:
      return "Play";
    default:
      return "Unidentified";
  }
}
/* eslint-enable */

/**
 * Indicate that an event with an original target and type is expected
 * to be fired, or not expected to be fired.
 */
/* eslint-disable */
function expectEvent_(expectedTarget, expectedEvent, testName) {
  if (!expectedTarget || !expectedEvent) {
    return null;
  }

  seenEvent = false;

  let type;
  if (expectedEvent.charAt(0) == "!") {
    type = expectedEvent.substring(1);
  } else {
    type = expectedEvent;
  }

  let handler = ev => {
    let pass = (!seenEvent && ev.originalTarget == expectedTarget && ev.type == type);
    is(pass, true, `${testName} ${type} event target ${seenEvent ? "twice" : ""}`);
    seenEvent = true;
  };

  expectedTarget.addEventListener(type, handler);
  return handler;
}
/* eslint-enable */

/**
 * Check if the event was fired or not. The provided event handler will
 * be removed.
 */
function checkExpectedEvent_(
    expectedTarget, expectedEvent, eventHandler, testName) {

  if (eventHandler) {
    let expectEvent = (expectedEvent.charAt(0) != "!");
    let type = expectEvent;
    if (!type) {
      type = expectedEvent.substring(1);
    }
    expectedTarget.removeEventListener(type, eventHandler);

    let desc = `${type} event`;
    if (!expectEvent) {
      desc += " not";
    }
    is(seenEvent, expectEvent, `${testName} ${desc} fired`);
  }

  seenEvent = false;
}

/**
 * Similar to event.synthesizeMouse except that a test is performed to
 * see if an event is fired at the right target as a result.
 *
 * To test that an event is not fired, use an expected type preceded by
 * an exclamation mark, such as "!select". This might be used to test that
 * a click on a disabled element doesn't fire certain events for instance.
 *
 * @param {Element} target
 *     Synthesise the mouse event on this target.
 * @param {number} offsetX
 *     Horizontal offset from the target's bounding box.
 * @param {number} offsetY
 *     Vertical offset from the target's bounding box.
 * @param {Object.<string, ?>} ev
 *     Object which may contain the properties shiftKey, ctrlKey, altKey,
 *     metaKey, accessKey, type.
 * @param {Element} expectedTarget
 *     Expected originalTarget of the event.
 * @param {DOMEvent} expectedEvent
 *     Expected type of the event, such as "select".
 * @param {string} testName
 *     Test name when outputing results.
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 */
event.synthesizeMouseExpectEvent = function(
    target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
    testName, window = undefined) {

  let eventHandler = expectEvent_(
      expectedTarget,
      expectedEvent,
      testName);
  event.synthesizeMouse(target, offsetX, offsetY, ev, window);
  checkExpectedEvent_(
      expectedTarget,
      expectedEvent,
      eventHandler,
      testName);
};

/**
 * Similar to synthesizeKey except that a test is performed to see if
 * an event is fired at the right target as a result.
 *
 * @param {string} key
 *     Key to synthesise.
 * @param {Object.<string, ?>} ev
 *     Object which may contain the properties shiftKey, ctrlKey, altKey,
 *     metaKey, accessKey, type.
 * @param {Element} expectedTarget
 *     Expected originalTarget of the event.
 * @param {DOMEvent} expectedEvent
 *     Expected type of the event, such as "select".
 * @param {string} testName
 *     Test name when outputing results
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 *
 * To test that an event is not fired, use an expected type preceded by an
 * exclamation mark, such as "!select".
 *
 * aWindow is optional, and defaults to the current window object.
 */
event.synthesizeKeyExpectEvent = function(
    key, ev, expectedTarget, expectedEvent, testName,
    window = undefined) {

  let eventHandler = expectEvent_(
      expectedTarget,
      expectedEvent,
      testName);
  event.synthesizeKey(key, ev, window);
  checkExpectedEvent_(
      expectedTarget,
      expectedEvent,
      eventHandler,
      testName);
};

/**
 * Synthesize a composition event.
 *
 * @param {DOMEvent} ev
 *     The composition event information.  This must have |type|
 *     member.  The value must be "compositionstart", "compositionend" or
 *     "compositionupdate".  And also this may have |data| and |locale|
 *     which would be used for the value of each property of the
 *     composition event.  Note that the data would be ignored if the
 *     event type were "compositionstart".
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 */
event.synthesizeComposition = function(ev, window = undefined) {
  let domutils = getDOMWindowUtils(window);
  domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
};

/**
 * Synthesize a text event.
 *
 * The text event's information, this has |composition| and |caret|
 * members.  |composition| has |string| and |clauses| members. |clauses|
 * must be array object.  Each object has |length| and |attr|.
 * And |caret| has |start| and |length|.  See the following tree image.
 *
 *     ev
 *      +-- composition
 *      |     +-- string
 *      |     +-- clauses[]
 *      |           +-- length
 *      |           +-- attr
 *      +-- caret
 *            +-- start
 *            +-- length
 *
 * Set the composition string to |composition.string|.  Set its clauses
 * information to the |clauses| array.
 *
 * When it's composing, set the each clauses' length
 * to the |composition.clauses[n].length|.  The sum
 * of the all length values must be same as the length of
 * |composition.string|. Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
 * |composition.clauses[n].attr|.
 *
 * When it's not composing, set 0 to the |composition.clauses[0].length|
 * and |composition.clauses[0].attr|.
 *
 * Set caret position to the |caret.start|. Its offset from the start of
 * the composition string.  Set caret length to |caret.length|.  If it's
 * larger than 0, it should be wide caret.  However, current nsEditor
 * doesn't support wide caret, therefore, you should always set 0 now.
 *
 * @param {Object.<string, ?>} ev
 *     The text event's information,
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 */
event.synthesizeText = function(ev, window = undefined) {
  let domutils = getDOMWindowUtils(window);

  if (!ev.composition ||
      !ev.composition.clauses ||
      !ev.composition.clauses[0]) {
    return;
  }

  let firstClauseLength = ev.composition.clauses[0].length;
  let firstClauseAttr   = ev.composition.clauses[0].attr;
  let secondClauseLength = 0;
  let secondClauseAttr = 0;
  let thirdClauseLength = 0;
  let thirdClauseAttr = 0;
  if (ev.composition.clauses[1]) {
    secondClauseLength = ev.composition.clauses[1].length;
    secondClauseAttr   = ev.composition.clauses[1].attr;
    if (event.composition.clauses[2]) {
      thirdClauseLength = ev.composition.clauses[2].length;
      thirdClauseAttr   = ev.composition.clauses[2].attr;
    }
  }

  let caretStart = -1;
  let caretLength = 0;
  if (event.caret) {
    caretStart = ev.caret.start;
    caretLength = ev.caret.length;
  }

  domutils.sendTextEvent(
      ev.composition.string,
      firstClauseLength,
      firstClauseAttr,
      secondClauseLength,
      secondClauseAttr,
      thirdClauseLength,
      thirdClauseAttr,
      caretStart,
      caretLength);
};

/**
 * Synthesize a query selected text event.
 *
 * @param {Window=}
 *     Window object.  Defaults to the current window.
 *
 * @return {(nsIQueryContentEventResult|null)}
 *     Event's result, or null if it failed.
 */
event.synthesizeQuerySelectedText = function(window = undefined) {
  let domutils = getDOMWindowUtils(window);
  return domutils.sendQueryContentEvent(
      domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
};

/**
 * Synthesize a selection set event.
 *
 * @param {number} offset
 *     Character offset.  0 means the first character in the selection
 *     root.
 * @param {number} length
 *     Length of the text.  If the length is too long, the extra length
 *     is ignored.
 * @param {boolean} reverse
 *     If true, the selection is from |aOffset + aLength| to |aOffset|.
 *     Otherwise, from |aOffset| to |aOffset + aLength|.
 * @param {Window=} window
 *     Window object.  Defaults to the current window.
 *
 * @return         True, if succeeded.  Otherwise false.
 */
event.synthesizeSelectionSet = function(
    offset, length, reverse, window = undefined) {
  let domutils = getDOMWindowUtils(window);
  return domutils.sendSelectionSetEvent(offset, length, reverse);
};

const KEYCODES_LOOKUP = {
  "VK_SHIFT": "shiftKey",
  "VK_CONTROL": "ctrlKey",
  "VK_ALT": "altKey",
  "VK_META": "metaKey",
};

const VIRTUAL_KEYCODE_LOOKUP = {
  "\uE001": "VK_CANCEL",
  "\uE002": "VK_HELP",
  "\uE003": "VK_BACK_SPACE",
  "\uE004": "VK_TAB",
  "\uE005": "VK_CLEAR",
  "\uE006": "VK_RETURN",
  "\uE007": "VK_RETURN",
  "\uE008": "VK_SHIFT",
  "\uE009": "VK_CONTROL",
  "\uE00A": "VK_ALT",
  "\uE03D": "VK_META",
  "\uE00B": "VK_PAUSE",
  "\uE00C": "VK_ESCAPE",
  "\uE00D": "VK_SPACE",  // printable
  "\uE00E": "VK_PAGE_UP",
  "\uE00F": "VK_PAGE_DOWN",
  "\uE010": "VK_END",
  "\uE011": "VK_HOME",
  "\uE012": "VK_LEFT",
  "\uE013": "VK_UP",
  "\uE014": "VK_RIGHT",
  "\uE015": "VK_DOWN",
  "\uE016": "VK_INSERT",
  "\uE017": "VK_DELETE",
  "\uE018": "VK_SEMICOLON",
  "\uE019": "VK_EQUALS",
  "\uE01A": "VK_NUMPAD0",
  "\uE01B": "VK_NUMPAD1",
  "\uE01C": "VK_NUMPAD2",
  "\uE01D": "VK_NUMPAD3",
  "\uE01E": "VK_NUMPAD4",
  "\uE01F": "VK_NUMPAD5",
  "\uE020": "VK_NUMPAD6",
  "\uE021": "VK_NUMPAD7",
  "\uE022": "VK_NUMPAD8",
  "\uE023": "VK_NUMPAD9",
  "\uE024": "VK_MULTIPLY",
  "\uE025": "VK_ADD",
  "\uE026": "VK_SEPARATOR",
  "\uE027": "VK_SUBTRACT",
  "\uE028": "VK_DECIMAL",
  "\uE029": "VK_DIVIDE",
  "\uE031": "VK_F1",
  "\uE032": "VK_F2",
  "\uE033": "VK_F3",
  "\uE034": "VK_F4",
  "\uE035": "VK_F5",
  "\uE036": "VK_F6",
  "\uE037": "VK_F7",
  "\uE038": "VK_F8",
  "\uE039": "VK_F9",
  "\uE03A": "VK_F10",
  "\uE03B": "VK_F11",
  "\uE03C": "VK_F12",
};

function getKeyCode(c) {
  if (c in VIRTUAL_KEYCODE_LOOKUP) {
    return VIRTUAL_KEYCODE_LOOKUP[c];
  }
  return c;
}

function isPrintable(c, win = window) {
  let KeyboardEvent = getKeyboardEvent_(win);
  let NON_PRINT_KEYS = [
    KeyboardEvent.DOM_VK_CANCEL,
    KeyboardEvent.DOM_VK_HELP,
    KeyboardEvent.DOM_VK_BACK_SPACE,
    KeyboardEvent.DOM_VK_TAB,
    KeyboardEvent.DOM_VK_CLEAR,
    KeyboardEvent.DOM_VK_SHIFT,
    KeyboardEvent.DOM_VK_CONTROL,
    KeyboardEvent.DOM_VK_ALT,
    KeyboardEvent.DOM_VK_PAUSE,
    KeyboardEvent.DOM_VK_EISU,
    KeyboardEvent.DOM_VK_ESCAPE,
    KeyboardEvent.DOM_VK_CONVERT,
    KeyboardEvent.DOM_VK_NONCONVERT,
    KeyboardEvent.DOM_VK_ACCEPT,
    KeyboardEvent.DOM_VK_MODECHANGE,
    KeyboardEvent.DOM_VK_PAGE_UP,
    KeyboardEvent.DOM_VK_PAGE_DOWN,
    KeyboardEvent.DOM_VK_END,
    KeyboardEvent.DOM_VK_HOME,
    KeyboardEvent.DOM_VK_LEFT,
    KeyboardEvent.DOM_VK_UP,
    KeyboardEvent.DOM_VK_RIGHT,
    KeyboardEvent.DOM_VK_DOWN,
    KeyboardEvent.DOM_VK_SELECT,
    KeyboardEvent.DOM_VK_PRINT,
    KeyboardEvent.DOM_VK_EXECUTE,
    KeyboardEvent.DOM_VK_PRINTSCREEN,
    KeyboardEvent.DOM_VK_INSERT,
    KeyboardEvent.DOM_VK_DELETE,
    KeyboardEvent.DOM_VK_WIN,
    KeyboardEvent.DOM_VK_CONTEXT_MENU,
    KeyboardEvent.DOM_VK_SLEEP,
    KeyboardEvent.DOM_VK_F1,
    KeyboardEvent.DOM_VK_F2,
    KeyboardEvent.DOM_VK_F3,
    KeyboardEvent.DOM_VK_F4,
    KeyboardEvent.DOM_VK_F5,
    KeyboardEvent.DOM_VK_F6,
    KeyboardEvent.DOM_VK_F7,
    KeyboardEvent.DOM_VK_F8,
    KeyboardEvent.DOM_VK_F9,
    KeyboardEvent.DOM_VK_F10,
    KeyboardEvent.DOM_VK_F11,
    KeyboardEvent.DOM_VK_F12,
    KeyboardEvent.DOM_VK_F13,
    KeyboardEvent.DOM_VK_F14,
    KeyboardEvent.DOM_VK_F15,
    KeyboardEvent.DOM_VK_F16,
    KeyboardEvent.DOM_VK_F17,
    KeyboardEvent.DOM_VK_F18,
    KeyboardEvent.DOM_VK_F19,
    KeyboardEvent.DOM_VK_F20,
    KeyboardEvent.DOM_VK_F21,
    KeyboardEvent.DOM_VK_F22,
    KeyboardEvent.DOM_VK_F23,
    KeyboardEvent.DOM_VK_F24,
    KeyboardEvent.DOM_VK_NUM_LOCK,
    KeyboardEvent.DOM_VK_SCROLL_LOCK,
    KeyboardEvent.DOM_VK_VOLUME_MUTE,
    KeyboardEvent.DOM_VK_VOLUME_DOWN,
    KeyboardEvent.DOM_VK_VOLUME_UP,
    KeyboardEvent.DOM_VK_META,
    KeyboardEvent.DOM_VK_ALTGR,
    KeyboardEvent.DOM_VK_ATTN,
    KeyboardEvent.DOM_VK_CRSEL,
    KeyboardEvent.DOM_VK_EXSEL,
    KeyboardEvent.DOM_VK_EREOF,
    KeyboardEvent.DOM_VK_PLAY,
    KeyboardEvent.DOM_VK_RETURN,
  ];
  return !(NON_PRINT_KEYS.includes(c));
}

event.sendKeyDown = function(keyToSend, modifiers, document) {
  modifiers.type = "keydown";
  event.sendSingleKey(keyToSend, modifiers, document);
  // TODO: This doesn't do anything since |synthesizeKeyEvent| ignores
  // explicit keypress request, and instead figures out itself when to
  // send keypress.
  if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"]
      .indexOf(getKeyCode(keyToSend)) < 0) {
    modifiers.type = "keypress";
    event.sendSingleKey(keyToSend, modifiers, document);
  }
  delete modifiers.type;
};

event.sendKeyUp = function(keyToSend, modifiers, window = undefined) {
  modifiers.type = "keyup";
  event.sendSingleKey(keyToSend, modifiers, window);
  delete modifiers.type;
};

/**
 * Synthesize a key event for a single key.
 *
 * @param {string} keyToSend
 *     Code point or normalized key value
 * @param {Object.<string, boolean>} modifiers
 *     Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
 *     as well as, the event |type| such as keydown. All properties
 *     are optional.
 * @param {Window=} window
 *     Window object.  If |window| is undefined, the event is synthesized
 *     in current window.
 */
event.sendSingleKey = function(keyToSend, modifiers, window = undefined) {
  let keyName = getKeyCode(keyToSend);
  if (keyName in KEYCODES_LOOKUP) {
    // We assume that if |keyToSend| is a raw code point (like "\uE009")
    // then |modifiers| does not already have correct value for corresponding
    // |modName| attribute (like ctrlKey), so that value needs to be flipped.
    let modName = KEYCODES_LOOKUP[keyName];
    modifiers[modName] = !modifiers[modName];
  } else if (modifiers.shiftKey && keyName != "Shift") {
    keyName = keyName.toUpperCase();
  }
  event.synthesizeKey(keyName, modifiers, window);
};

/**
 * Focus element and, if a textual input field and no previous selection
 * state exists, move the caret to the end of the input field.
 *
 * @param {Element} element
 *     Element to focus.
 */
function focusElement(element) {
  let t = element.type;
  if (t && (t == "text" || t == "textarea")) {
    if (element.selectionEnd == 0) {
      let len = element.value.length;
      element.setSelectionRange(len, len);
    }
  }
  element.focus();
}

/**
 * @param {string} keyString
 * @param {Element} element
 * @param {Object.<string, boolean>=} opts
 * @param {Window=} window
 */
event.sendKeysToElement = function(
    keyString, el, opts = {}, window = undefined) {

  if (opts.ignoreVisibility || element.isVisible(el)) {
    focusElement(el);

    // make Object.<modifier, false> map
    let modifiers = Object.create(event.Modifiers);
    for (let modifier in event.Modifiers) {
      modifiers[modifier] = false;
    }

    for (let i = 0; i < keyString.length; i++) {
      let c = keyString.charAt(i);
      event.sendSingleKey(c, modifiers, window);
    }

  } else {
    throw new ElementNotInteractableError("Element is not visible");
  }
};

event.sendEvent = function(eventType, el, modifiers = {}, opts = {}) {
  opts.canBubble = opts.canBubble || true;

  let doc = el.ownerDocument || el.document;
  let ev = doc.createEvent("Event");

  ev.shiftKey = modifiers["shift"];
  ev.metaKey = modifiers["meta"];
  ev.altKey = modifiers["alt"];
  ev.ctrlKey = modifiers["ctrl"];

  ev.initEvent(eventType, opts.canBubble, true);
  el.dispatchEvent(ev);
};

event.focus = function(el, opts = {}) {
  opts.canBubble = opts.canBubble || true;
  let doc = el.ownerDocument || el.document;
  let win = doc.defaultView;

  let ev = new win.FocusEvent(el);
  ev.initEvent("focus", opts.canBubble, true);
  el.dispatchEvent(ev);
};

event.mouseover = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("mouseover", el, modifiers, opts);
};

event.mousemove = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("mousemove", el, modifiers, opts);
};

event.mousedown = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("mousedown", el, modifiers, opts);
};

event.mouseup = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("mouseup", el, modifiers, opts);
};

event.click = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("click", el, modifiers, opts);
};

event.change = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("change", el, modifiers, opts);
};

event.input = function(el, modifiers = {}, opts = {}) {
  return event.sendEvent("input", el, modifiers, opts);
};
PK
!<n''"chrome/marionette/content/frame.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.EXPORTED_SYMBOLS = ["frame"];

/** @namespace */
this.frame = {};

const FRAME_SCRIPT = "chrome://marionette/content/listener.js";

// list of OOP frames that has the frame script loaded
var remoteFrames = [];

/**
 * An object representing a frame that Marionette has loaded a
 * frame script in.
 */
frame.RemoteFrame = function(windowId, frameId) {
  // outerWindowId relative to main process
  this.windowId = windowId;
  // actual frame relative to the windowId's frames list
  this.frameId = frameId;
  // assigned frame ID, used for messaging
  this.targetFrameId = this.frameId;
  // list of OOP frames that has the frame script loaded
  this.remoteFrames = [];
};

/**
 * The FrameManager will maintain the list of Out Of Process (OOP)
 * frames and will handle frame switching between them.
 *
 * It handles explicit frame switching (switchToFrame), and implicit
 * frame switching, which occurs when a modal dialog is triggered in B2G.
 *
 * @param {GeckoDriver} driver
 *     Reference to the driver instance.
 */
frame.Manager = class {
  constructor(driver) {
    // messageManager maintains the messageManager
    // for the current process' chrome frame or the global message manager

    // holds a member of the remoteFrames (for an OOP frame)
    // or null (for the main process)
    this.currentRemoteFrame = null;
    // frame we'll need to restore once interrupt is gone
    this.previousRemoteFrame = null;
    // set to true when we have been interrupted by a modal
    this.handledModal = false;
    this.driver = driver;
  }

  /**
   * Receives all messages from content messageManager.
   */
  /*eslint-disable*/
  receiveMessage(message) {
    switch (message.name) {
      case "MarionetteFrame:getInterruptedState":
        // this will return true if the calling frame was interrupted by a modal dialog
        if (this.previousRemoteFrame) {
          // get the frame window of the interrupted frame
          let interruptedFrame = Services.wm.getOuterWindowWithId(
              this.previousRemoteFrame.windowId);

          if (this.previousRemoteFrame.frameId !== null) {
            // find OOP frame
            let iframes = interruptedFrame.document.getElementsByTagName("iframe");
            interruptedFrame = iframes[this.previousRemoteFrame.frameId];
          }

          // check if the interrupted frame is the same as the calling frame
          if (interruptedFrame.src == message.target.src) {
            return {value: this.handledModal};
          }

        // we get here if previousRemoteFrame and currentRemoteFrame are null,
        // i.e. if we're in a non-OOP process, or we haven't switched into an OOP frame,
        // in which case, handledModal can't be set to true
        } else if (this.currentRemoteFrame === null) {
          return {value: this.handledModal};
        }
        return {value: false};

      // handleModal is called when we need to switch frames to the main
      // process due to a modal dialog interrupt
      case "MarionetteFrame:handleModal":
        // If previousRemoteFrame was set, that means we switched into a
        // remote frame.  If this is the case, then we want to switch back
        // into the system frame.  If it isn't the case, then we're in a
        // non-OOP environment, so we don't need to handle remote frames.
        let isLocal = true;
        if (this.currentRemoteFrame !== null) {
          isLocal = false;
          this.removeMessageManagerListeners(
              this.currentRemoteFrame.messageManager.get());

          // store the previous frame so we can switch back to it when
          // the modal is dismissed
          this.previousRemoteFrame = this.currentRemoteFrame;

          // by setting currentRemoteFrame to null,
          // it signifies we're in the main process
          this.currentRemoteFrame = null;
          this.driver.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
              .getService(Ci.nsIMessageBroadcaster);
        }

        this.handledModal = true;
        this.driver.sendOk(this.driver.command_id);
        return {value: isLocal};

      case "MarionetteFrame:getCurrentFrameId":
        if (this.currentRemoteFrame !== null) {
          return this.currentRemoteFrame.frameId;
        }
    }
  }
  /*eslint-enable*/

  getOopFrame(winId, frameId) {
    // get original frame window
    let outerWin = Services.wm.getOuterWindowWithId(winId);
    // find the OOP frame
    let f = outerWin.document.getElementsByTagName("iframe")[frameId];
    return f;
  }

  getFrameMM(winId, frameId) {
    let oopFrame = this.getOopFrame(winId, frameId);
    let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
        .frameLoader.messageManager;
    return mm;
  }

  /**
   * Switch to OOP frame.  We're handling this here so we can maintain
   * a list of remote frames.
   */
  switchToFrame(winId, frameId) {
    let oopFrame = this.getOopFrame(winId, frameId);
    let mm = this.getFrameMM(winId, frameId);

    // see if this frame already has our frame script loaded in it;
    // if so, just wake it up
    for (let i = 0; i < remoteFrames.length; i++) {
      let f = remoteFrames[i];
      let fmm = f.messageManager.get();
      try {
        fmm.sendAsyncMessage("aliveCheck", {});
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_INITIALIZED) {
          remoteFrames.splice(i--, 1);
          continue;
        }
      }

      if (fmm == mm) {
        this.currentRemoteFrame = f;
        this.addMessageManagerListeners(mm);

        mm.sendAsyncMessage("Marionette:restart");
        return oopFrame.id;
      }
    }

    // if we get here, then we need to load the frame script in this frame,
    // and set the frame's ChromeMessageSender as the active message manager
    // the driver will listen to.
    this.addMessageManagerListeners(mm);
    let f = new frame.RemoteFrame(winId, frameId);
    f.messageManager = Cu.getWeakReference(mm);
    remoteFrames.push(f);
    this.currentRemoteFrame = f;

    mm.loadFrameScript(FRAME_SCRIPT, true, true);

    return oopFrame.id;
  }

  /*
   * This function handles switching back to the frame that was
   * interrupted by the modal dialog.  It gets called by the interrupted
   * frame once the dialog is dismissed and the frame resumes its process.
   */
  switchToModalOrigin() {
    // only handle this if we indeed switched out of the modal's
    // originating frame
    if (this.previousRemoteFrame !== null) {
      this.currentRemoteFrame = this.previousRemoteFrame;
      let mm = this.currentRemoteFrame.messageManager.get();
      this.addMessageManagerListeners(mm);
    }
    this.handledModal = false;
  }

  /**
   * Adds message listeners to the driver,  listening for
   * messages from content frame scripts.  It also adds a
   * MarionetteFrame:getInterruptedState message listener to the
   * FrameManager, so the frame manager's state can be checked by the frame.
   *
   * @param {nsIMessageListenerManager} mm
   *     The message manager object, typically
   *     ChromeMessageBroadcaster or ChromeMessageSender.
   */
  addMessageManagerListeners(mm) {
    mm.addWeakMessageListener("Marionette:ok", this.driver);
    mm.addWeakMessageListener("Marionette:done", this.driver);
    mm.addWeakMessageListener("Marionette:error", this.driver);
    mm.addWeakMessageListener("Marionette:emitTouchEvent", this.driver);
    mm.addWeakMessageListener("Marionette:log", this.driver);
    mm.addWeakMessageListener("Marionette:shareData", this.driver);
    mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.driver);
    mm.addWeakMessageListener("Marionette:switchedToFrame", this.driver);
    mm.addWeakMessageListener("Marionette:getVisibleCookies", this.driver);
    mm.addWeakMessageListener("Marionette:register", this.driver);
    mm.addWeakMessageListener("Marionette:listenersAttached", this.driver);
    mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
    mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
    mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
  }

  /**
   * Removes listeners for messages from content frame scripts.
   * We do not remove the MarionetteFrame:getInterruptedState or
   * the Marionette:switchToModalOrigin message listener, because we
   * want to allow all known frames to contact the frame manager so
   * that it can check if it was interrupted, and if so, it will call
   * switchToModalOrigin when its process gets resumed.
   *
   * @param {nsIMessageListenerManager} mm
   *     The message manager object, typically
   *     ChromeMessageBroadcaster or ChromeMessageSender.
   */
  removeMessageManagerListeners(mm) {
    mm.removeWeakMessageListener("Marionette:ok", this.driver);
    mm.removeWeakMessageListener("Marionette:done", this.driver);
    mm.removeWeakMessageListener("Marionette:error", this.driver);
    mm.removeWeakMessageListener("Marionette:log", this.driver);
    mm.removeWeakMessageListener("Marionette:shareData", this.driver);
    mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
    mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.driver);
    mm.removeWeakMessageListener(
        "Marionette:getImportedScripts", this.driver.importedScripts);
    mm.removeWeakMessageListener("Marionette:listenersAttached", this.driver);
    mm.removeWeakMessageListener("Marionette:register", this.driver);
    mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
    mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
  }
};

frame.Manager.prototype.QueryInterface = XPCOMUtils.generateQI(
    [Ci.nsIMessageListener, Ci.nsISupportsWeakReference]);
PK
!<$55(chrome/marionette/content/interaction.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {utils: Cu} = Components;

Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/atom.js");
const {
  ElementClickInterceptedError,
  ElementNotInteractableError,
  error,
  InvalidArgument,
  InvalidArgumentError,
  InvalidElementStateError,
  pprint,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/event.js");

Cu.importGlobalProperties(["File"]);

this.EXPORTED_SYMBOLS = ["interaction"];

/** XUL elements that support disabled attribute. */
const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
  "ARROWSCROLLBOX",
  "BUTTON",
  "CHECKBOX",
  "COLORPICKER",
  "COMMAND",
  "DATEPICKER",
  "DESCRIPTION",
  "KEY",
  "KEYSET",
  "LABEL",
  "LISTBOX",
  "LISTCELL",
  "LISTHEAD",
  "LISTHEADER",
  "LISTITEM",
  "MENU",
  "MENUITEM",
  "MENULIST",
  "MENUSEPARATOR",
  "PREFERENCE",
  "RADIO",
  "RADIOGROUP",
  "RICHLISTBOX",
  "RICHLISTITEM",
  "SCALE",
  "TAB",
  "TABS",
  "TEXTBOX",
  "TIMEPICKER",
  "TOOLBARBUTTON",
  "TREE",
]);

/** XUL elements that support checked property. */
const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([
  "BUTTON",
  "CHECKBOX",
  "LISTITEM",
  "TOOLBARBUTTON",
]);

/** XUL elements that support selected property. */
const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
  "LISTITEM",
  "MENU",
  "MENUITEM",
  "MENUSEPARATOR",
  "RADIO",
  "RICHLISTITEM",
  "TAB",
]);

/**
 * Common form controls that user can change the value property
 * interactively.
 */
const COMMON_FORM_CONTROLS = new Set([
  "input",
  "textarea",
  "select",
]);

/**
 * Input elements that do not fire <tt>input</tt> and <tt>change</tt>
 * events when value property changes.
 */
const INPUT_TYPES_NO_EVENT = new Set([
  "checkbox",
  "radio",
  "file",
  "hidden",
  "image",
  "reset",
  "button",
  "submit",
]);

/** @namespace */
this.interaction = {};

/**
 * Interact with an element by clicking it.
 *
 * The element is scrolled into view before visibility- or interactability
 * checks are performed.
 *
 * Selenium-style visibility checks will be performed
 * if <var>specCompat</var> is false (default).  Otherwise
 * pointer-interactability checks will be performed.  If either of these
 * fail an {@link ElementNotInteractableError} is thrown.
 *
 * If <var>strict</var> is enabled (defaults to disabled), further
 * accessibility checks will be performed, and these may result in an
 * {@link ElementNotAccessibleError} being returned.
 *
 * When <var>el</var> is not enabled, an {@link InvalidElementStateError}
 * is returned.
 *
 * @param {(DOMElement|XULElement)} el
 *     Element to click.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 * @param {boolean=} [specCompat=false] specCompat
 *     Use WebDriver specification compatible interactability definition.
 *
 * @throws {ElementNotInteractableError}
 *     If either Selenium-style visibility check or
 *     pointer-interactability check fails.
 * @throws {ElementClickInterceptedError}
 *     If <var>el</var> is obscured by another element and a click would
 *     not hit, in <var>specCompat</var> mode.
 * @throws {ElementNotAccessibleError}
 *     If <var>strict</var> is true and element is not accessible.
 * @throws {InvalidElementStateError}
 *     If <var>el</var> is not enabled.
 */
interaction.clickElement = function* (
    el, strict = false, specCompat = false) {
  const a11y = accessibility.get(strict);
  if (element.isXULElement(el)) {
    yield chromeClick(el, a11y);
  } else if (specCompat) {
    yield webdriverClickElement(el, a11y);
  } else {
    yield seleniumClickElement(el, a11y);
  }
};

function* webdriverClickElement(el, a11y) {
  const win = getWindow(el);

  // step 3
  if (el.localName == "input" && el.type == "file") {
    throw new InvalidArgumentError(
        "Cannot click <input type=file> elements");
  }

  let containerEl = element.getContainer(el);

  // step 4
  if (!element.isInView(containerEl)) {
    element.scrollIntoView(containerEl);
  }

  // step 5
  // TODO(ato): wait for containerEl to be in view

  // step 6
  // if we cannot bring the container element into the viewport
  // there is no point in checking if it is pointer-interactable
  if (!element.isInView(containerEl)) {
    throw new ElementNotInteractableError(
        error.pprint`Element ${el} could not be scrolled into view`);
  }

  // step 7
  let rects = containerEl.getClientRects();
  let clickPoint = element.getInViewCentrePoint(rects[0], win);

  if (element.isObscured(containerEl)) {
    throw new ElementClickInterceptedError(containerEl, clickPoint);
  }

  yield a11y.getAccessible(el, true).then(acc => {
    a11y.assertVisible(acc, el, true);
    a11y.assertEnabled(acc, el, true);
    a11y.assertActionable(acc, el);
  });

  // step 8
  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    event.synthesizeMouseAtPoint(clickPoint.x, clickPoint.y, {}, win);
  }

  // step 9
  yield interaction.flushEventLoop(win);

  // step 10
  // if the click causes navigation, the post-navigation checks are
  // handled by the load listener in listener.js
}

function* chromeClick(el, a11y) {
  if (!atom.isElementEnabled(el)) {
    throw new InvalidElementStateError("Element is not enabled");
  }

  yield a11y.getAccessible(el, true).then(acc => {
    a11y.assertVisible(acc, el, true);
    a11y.assertEnabled(acc, el, true);
    a11y.assertActionable(acc, el);
  });

  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    el.click();
  }
}

function* seleniumClickElement(el, a11y) {
  let win = getWindow(el);

  let visibilityCheckEl  = el;
  if (el.localName == "option") {
    visibilityCheckEl = element.getContainer(el);
  }

  if (!element.isVisible(visibilityCheckEl)) {
    throw new ElementNotInteractableError();
  }

  if (!atom.isElementEnabled(el)) {
    throw new InvalidElementStateError("Element is not enabled");
  }

  yield a11y.getAccessible(el, true).then(acc => {
    a11y.assertVisible(acc, el, true);
    a11y.assertEnabled(acc, el, true);
    a11y.assertActionable(acc, el);
  });

  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    let rects = el.getClientRects();
    let centre = element.getInViewCentrePoint(rects[0], win);
    let opts = {};
    event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
  }
}

/**
 * Select <tt>&lt;option&gt;</tt> element in a <tt>&lt;select&gt;</tt>
 * list.
 *
 * Because the dropdown list of select elements are implemented using
 * native widget technology, our trusted synthesised events are not able
 * to reach them.  Dropdowns are instead handled mimicking DOM events,
 * which for obvious reasons is not ideal, but at the current point in
 * time considered to be good enough.
 *
 * @param {HTMLOptionElement} option
 *     Option element to select.
 *
 * @throws {TypeError}
 *     If <var>el</var> is a XUL element or not an <tt>&lt;option&gt;</tt>
 *     element.
 * @throws {Error}
 *     If unable to find <var>el</var>'s parent <tt>&lt;select&gt;</tt>
 *     element.
 */
interaction.selectOption = function(el) {
  if (element.isXULElement(el)) {
    throw new TypeError("XUL dropdowns not supported");
  }
  if (el.localName != "option") {
    throw new TypeError(pprint`Expected <option> element, got ${el}`);
  }

  let containerEl = element.getContainer(el);

  event.mouseover(containerEl);
  event.mousemove(containerEl);
  event.mousedown(containerEl);
  event.focus(containerEl);
  event.input(containerEl);

  // Clicking <option> in <select> should not be deselected if selected.
  // However, clicking one in a <select multiple> should toggle
  // selectedness the way holding down Control works.
  if (containerEl.multiple) {
    el.selected = !el.selected;
  } else if (!el.selected) {
    el.selected = true;
  }

  event.change(containerEl);
  event.mouseup(containerEl);
  event.click(containerEl);
};

/**
 * Flushes the event loop by requesting an animation frame.
 *
 * This will wait for the browser to repaint before returning, typically
 * flushing any queued events.
 *
 * If the document is unloaded during this request, the promise is
 * rejected.
 *
 * @param {Window} win
 *     Associated window.
 *
 * @return {Promise}
 *     Promise is accepted once event queue is flushed, or rejected if
 *     <var>win</var> has closed or been unloaded before the queue can
 *     be flushed.
 */
interaction.flushEventLoop = function* (win) {
  return new Promise(resolve => {
    let handleEvent = event => {
      win.removeEventListener("beforeunload", this);
      resolve();
    };

    if (win.closed) {
      resolve();
      return;
    }

    win.addEventListener("beforeunload", handleEvent);
    win.requestAnimationFrame(handleEvent);
  });
};

/**
 * Appends <var>path</var> to an <tt>&lt;input type=file&gt;</tt>'s
 * file list.
 *
 * @param {HTMLInputElement} el
 *     An <tt>&lt;input type=file&gt;</tt> element.
 * @param {string} path
 *     Full path to file.
 */
interaction.uploadFile = function* (el, path) {
  let file = yield File.createFromFileName(path).then(file => {
    return file;
  }, () => {
    return null;
  });

  if (!file) {
    throw new InvalidArgumentError("File not found: " + path);
  }

  let fs = Array.prototype.slice.call(el.files);
  fs.push(file);

  // <input type=file> opens OS widget dialogue
  // which means the mousedown/focus/mouseup/click events
  // occur before the change event
  event.mouseover(el);
  event.mousemove(el);
  event.mousedown(el);
  event.focus(el);
  event.mouseup(el);
  event.click(el);

  el.mozSetFileArray(fs);

  event.change(el);
};

/**
 * Sets a form element's value.
 *
 * @param {DOMElement} el
 *     An form element, e.g. input, textarea, etc.
 * @param {string} value
 *     The value to be set.
 *
 * @throws {TypeError}
 *     If <var>el</var> is not an supported form element.
 */
interaction.setFormControlValue = function* (el, value) {
  if (!COMMON_FORM_CONTROLS.has(el.localName)) {
    throw new TypeError("This function is for form elements only");
  }

  el.value = value;

  if (INPUT_TYPES_NO_EVENT.has(el.type)) {
    return;
  }

  event.input(el);
  event.change(el);
};

/**
 * Send keys to element.
 *
 * @param {DOMElement|XULElement} el
 *     Element to send key events to.
 * @param {Array.<string>} value
 *     Sequence of keystrokes to send to the element.
 * @param {boolean} ignoreVisibility
 *     Flag to enable or disable element visibility tests.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 */
interaction.sendKeysToElement = function(
    el, value, ignoreVisibility, strict = false) {
  let win = getWindow(el);
  let a11y = accessibility.get(strict);
  return a11y.getAccessible(el, true).then(acc => {
    a11y.assertActionable(acc, el);
    event.sendKeysToElement(value, el, {ignoreVisibility: false}, win);
  });
};

/**
 * Determine the element displayedness of an element.
 *
 * @param {DOMElement|XULElement} el
 *     Element to determine displayedness of.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 *
 * @return {boolean}
 *     True if element is displayed, false otherwise.
 */
interaction.isElementDisplayed = function(el, strict = false) {
  let win = getWindow(el);
  let displayed = atom.isElementDisplayed(el, win);

  let a11y = accessibility.get(strict);
  return a11y.getAccessible(el).then(acc => {
    a11y.assertVisible(acc, el, displayed);
    return displayed;
  });
};

/**
 * Check if element is enabled.
 *
 * @param {DOMElement|XULElement} el
 *     Element to test if is enabled.
 *
 * @return {boolean}
 *     True if enabled, false otherwise.
 */
interaction.isElementEnabled = function(el, strict = false) {
  let enabled = true;
  let win = getWindow(el);

  if (element.isXULElement(el)) {
    // check if XUL element supports disabled attribute
    if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
      let disabled = atom.getElementAttribute(el, "disabled", win);
      if (disabled && disabled === "true") {
        enabled = false;
      }
    }
  } else {
    enabled = atom.isElementEnabled(el, {frame: win});
  }

  let a11y = accessibility.get(strict);
  return a11y.getAccessible(el).then(acc => {
    a11y.assertEnabled(acc, el, enabled);
    return enabled;
  });
};

/**
 * Determines if the referenced element is selected or not.
 *
 * This operation only makes sense on input elements of the Checkbox-
 * and Radio Button states, or option elements.
 *
 * @param {DOMElement|XULElement} el
 *     Element to test if is selected.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 *
 * @return {boolean}
 *     True if element is selected, false otherwise.
 */
interaction.isElementSelected = function(el, strict = false) {
  let selected = true;
  let win = getWindow(el);

  if (element.isXULElement(el)) {
    let tagName = el.tagName.toUpperCase();
    if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
      selected = el.checked;
    }
    if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
      selected = el.selected;
    }
  } else {
    selected = atom.isElementSelected(el, win);
  }

  let a11y = accessibility.get(strict);
  return a11y.getAccessible(el).then(acc => {
    a11y.assertSelected(acc, el, selected);
    return selected;
  });
};

function getWindow(el) {
  return el.ownerGlobal;
}
PK
!<'i>!chrome/marionette/content/l10n.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * An API which allows Marionette to handle localized content.
 *
 * The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko
 * based applications is done via entities and properties. For static
 * values entities are used, which are located in .dtd files. Whereby for
 * dynamically updated content the values come from .property files. Both
 * types of elements can be identifed via a unique id, and the translated
 * content retrieved.
 */

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(
    this, "domParser", "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");

const {NoSuchElementError} =
    Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["l10n"];

/** @namespace */
this.l10n = {};

/**
 * Retrieve the localized string for the specified entity id.
 *
 * Example:
 *     localizeEntity(["chrome://global/locale/about.dtd"], "about.version")
 *
 * @param {Array.<string>} urls
 *     Array of .dtd URLs.
 * @param {string} id
 *     The ID of the entity to retrieve the localized string for.
 *
 * @return {string}
 *     The localized string for the requested entity.
 */
l10n.localizeEntity = function(urls, id) {
  // Build a string which contains all possible entity locations
  let locations = [];
  urls.forEach((url, index) => {
    locations.push(`<!ENTITY % dtd_${index} SYSTEM "${url}">%dtd_${index};`);
  });

  // Use the DOM parser to resolve the entity and extract its real value
  let header = `<?xml version="1.0"?><!DOCTYPE elem [${locations.join("")}]>`;
  let elem = `<elem id="elementID">&${id};</elem>`;
  let doc = domParser.parseFromString(header + elem, "text/xml");
  let element = doc.querySelector("elem[id='elementID']");

  if (element === null) {
    throw new NoSuchElementError(`Entity with id='${id}' hasn't been found`);
  }

  return element.textContent;
};

/**
 * Retrieve the localized string for the specified property id.
 *
 * Example:
 *
 *     localizeProperty(
 *         ["chrome://global/locale/findbar.properties"], "FastFind");
 *
 * @param {Array.<string>} urls
 *     Array of .properties URLs.
 * @param {string} id
 *     The ID of the property to retrieve the localized string for.
 *
 * @return {string}
 *     The localized string for the requested property.
 */
l10n.localizeProperty = function(urls, id) {
  let property = null;

  for (let url of urls) {
    let bundle = Services.strings.createBundle(url);
    try {
      property = bundle.GetStringFromName(id);
      break;
    } catch (e) {}
  }

  if (property === null) {
    throw new NoSuchElementError(
        `Property with ID '${id}' hasn't been found`);
  }

  return property;
};
PK
!<.n66)chrome/marionette/content/legacyaction.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");

Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/evaluate.js");
Cu.import("chrome://marionette/content/event.js");

const CONTEXT_MENU_DELAY_PREF = "ui.click_hold_context_menus.delay";
const DEFAULT_CONTEXT_MENU_DELAY = 750;  // ms

this.EXPORTED_SYMBOLS = ["legacyaction"];

const logger = Log.repository.getLogger("Marionette");

/** @namespace */
this.legacyaction = this.action = {};

/**
 * Functionality for (single finger) action chains.
 */
action.Chain = function (checkForInterrupted) {
  // for assigning unique ids to all touches
  this.nextTouchId = 1000;
  // keep track of active Touches
  this.touchIds = {};
  // last touch for each fingerId
  this.lastCoordinates = null;
  this.isTap = false;
  this.scrolling = false;
  // whether to send mouse event
  this.mouseEventsOnly = false;
  this.checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  if (typeof checkForInterrupted == "function") {
    this.checkForInterrupted = checkForInterrupted;
  } else {
    this.checkForInterrupted = () => {};
  }

  // determines if we create touch events
  this.inputSource = null;
};

action.Chain.prototype.dispatchActions = function (
    args,
    touchId,
    container,
    seenEls,
    touchProvider) {
  // Some touch events code in the listener needs to do ipc, so we can't
  // share this code across chrome/content.
  if (touchProvider) {
    this.touchProvider = touchProvider;
  }

  this.seenEls = seenEls;
  this.container = container;
  let commandArray = evaluate.fromJSON(
      args, seenEls, container.frame, container.shadowRoot);

  if (touchId == null) {
    touchId = this.nextTouchId++;
  }

  if (!container.frame.document.createTouch) {
    this.mouseEventsOnly = true;
  }

  let keyModifiers = {
    shiftKey: false,
    ctrlKey: false,
    altKey: false,
    metaKey: false,
  };

  return new Promise(resolve => {
    this.actions(commandArray, touchId, 0, keyModifiers, resolve);
  }).catch(this.resetValues);
};

/**
 * This function emit mouse event.
 *
 * @param {Document} doc
 *     Current document.
 * @param {string} type
 *     Type of event to dispatch.
 * @param {number} clickCount
 *     Number of clicks, button notes the mouse button.
 * @param {number} elClientX
 *     X coordinate of the mouse relative to the viewport.
 * @param {number} elClientY
 *     Y coordinate of the mouse relative to the viewport.
 * @param {Object} modifiers
 *     An object of modifier keys present.
 */
action.Chain.prototype.emitMouseEvent = function (
    doc,
    type,
    elClientX,
    elClientY,
    button,
    clickCount,
    modifiers) {
  if (!this.checkForInterrupted()) {
    logger.debug(`Emitting ${type} mouse event ` +
        `at coordinates (${elClientX}, ${elClientY}) ` +
        `relative to the viewport, ` +
        `button: ${button}, ` +
        `clickCount: ${clickCount}`);

    let win = doc.defaultView;
    let domUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils);

    let mods;
    if (typeof modifiers != "undefined") {
      mods = event.parseModifiers_(modifiers);
    } else {
      mods = 0;
    }

    domUtils.sendMouseEvent(
        type,
        elClientX,
        elClientY,
        button || 0,
        clickCount || 1,
        mods,
        false,
        0,
        this.inputSource);
  }
};

/**
 * Reset any persisted values after a command completes.
 */
action.Chain.prototype.resetValues = function() {
  this.container = null;
  this.seenEls = null;
  this.touchProvider = null;
  this.mouseEventsOnly = false;
};

/**
 * Emit events for each action in the provided chain.
 *
 * To emit touch events for each finger, one might send a [["press", id],
 * ["wait", 5], ["release"]] chain.
 *
 * @param {Array.<Array<?>>} chain
 *     A multi-dimensional array of actions.
 * @param {Object.<string, number>} touchId
 *     Represents the finger ID.
 * @param {number} i
 *     Keeps track of the current action of the chain.
 * @param {Object.<string, boolean>} keyModifiers
 *     Keeps track of keyDown/keyUp pairs through an action chain.
 * @param {function(?)} cb
 *     Called on success.
 *
 * @return {Object.<string, number>}
 *     Last finger ID, or an empty object.
 */
action.Chain.prototype.actions = function (chain, touchId, i, keyModifiers, cb) {
  if (i == chain.length) {
    cb(touchId || null);
    this.resetValues();
    return;
  }

  let pack = chain[i];
  let command = pack[0];
  let el;
  let c;
  i++;

  if (["press", "wait", "keyDown", "keyUp", "click"].indexOf(command) == -1) {
    // if mouseEventsOnly, then touchIds isn't used
    if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
      this.resetValues();
      throw new WebDriverError("Element has not been pressed");
    }
  }

  switch (command) {
    case "keyDown":
      event.sendKeyDown(pack[1], keyModifiers, this.container.frame);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "keyUp":
      event.sendKeyUp(pack[1], keyModifiers, this.container.frame);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "click":
      el = this.seenEls.get(pack[1], this.container);
      let button = pack[2];
      let clickCount = pack[3];
      c = element.coordinates(el);
      this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers);
      if (button == 2) {
        this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y,
            button, clickCount, keyModifiers);
      }
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "press":
      if (this.lastCoordinates) {
        this.generateEvents(
            "cancel",
            this.lastCoordinates[0],
            this.lastCoordinates[1],
            touchId,
            null,
            keyModifiers);
        this.resetValues();
        throw new WebDriverError(
            "Invalid Command: press cannot follow an active touch event");
      }

      // look ahead to check if we're scrolling,
      // needed for APZ touch dispatching
      if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
        this.scrolling = true;
      }
      el = this.seenEls.get(pack[1], this.container);
      c = element.coordinates(el, pack[2], pack[3]);
      touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "release":
      this.generateEvents(
          "release",
          this.lastCoordinates[0],
          this.lastCoordinates[1],
          touchId,
          null,
          keyModifiers);
      this.actions(chain, null, i, keyModifiers, cb);
      this.scrolling =  false;
      break;

    case "move":
      el = this.seenEls.get(pack[1], this.container);
      c = element.coordinates(el);
      this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "moveByOffset":
      this.generateEvents(
          "move",
          this.lastCoordinates[0] + pack[1],
          this.lastCoordinates[1] + pack[2],
          touchId,
          null,
          keyModifiers);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;

    case "wait":
      if (pack[1] != null) {
        let time = pack[1] * 1000;

        // standard waiting time to fire contextmenu
        let standard = Preferences.get(
            CONTEXT_MENU_DELAY_PREF,
            DEFAULT_CONTEXT_MENU_DELAY);

        if (time >= standard && this.isTap) {
          chain.splice(i, 0, ["longPress"], ["wait", (time - standard) / 1000]);
          time = standard;
        }
        this.checkTimer.initWithCallback(
            () => this.actions(chain, touchId, i, keyModifiers, cb),
            time, Ci.nsITimer.TYPE_ONE_SHOT);
      } else {
        this.actions(chain, touchId, i, keyModifiers, cb);
      }
      break;

    case "cancel":
      this.generateEvents(
          "cancel",
          this.lastCoordinates[0],
          this.lastCoordinates[1],
          touchId,
          null,
          keyModifiers);
      this.actions(chain, touchId, i, keyModifiers, cb);
      this.scrolling = false;
      break;

    case "longPress":
      this.generateEvents(
          "contextmenu",
          this.lastCoordinates[0],
          this.lastCoordinates[1],
          touchId,
          null,
          keyModifiers);
      this.actions(chain, touchId, i, keyModifiers, cb);
      break;
  }
};

/**
 * Given an element and a pair of coordinates, returns an array of the
 * form [clientX, clientY, pageX, pageY, screenX, screenY].
 */
action.Chain.prototype.getCoordinateInfo = function (el, corx, cory) {
  let win = el.ownerGlobal;
  return [
    corx, // clientX
    cory, // clientY
    corx + win.pageXOffset, // pageX
    cory + win.pageYOffset, // pageY
    corx + win.mozInnerScreenX, // screenX
    cory + win.mozInnerScreenY // screenY
  ];
};

/**
 * @param {number} x
 *     X coordinate of the location to generate the event that is relative
 *     to the viewport.
 * @param {number} y
 *     Y coordinate of the location to generate the event that is relative
 *     to the viewport.
 */
action.Chain.prototype.generateEvents = function (
    type, x, y, touchId, target, keyModifiers) {
  this.lastCoordinates = [x, y];
  let doc = this.container.frame.document;

  switch (type) {
    case "tap":
      if (this.mouseEventsOnly) {
        this.mouseTap(
            touch.target.ownerDocument,
            touch.clientX,
            touch.clientY,
            null,
            null,
            keyModifiers);
      } else {
        touchId = this.nextTouchId++;
        let touch = this.touchProvider.createATouch(target, x, y, touchId);
        this.touchProvider.emitTouchEvent("touchstart", touch);
        this.touchProvider.emitTouchEvent("touchend", touch);
        this.mouseTap(
            touch.target.ownerDocument,
            touch.clientX,
            touch.clientY,
            null,
            null,
            keyModifiers);
      }
      this.lastCoordinates = null;
      break;

    case "press":
      this.isTap = true;
      if (this.mouseEventsOnly) {
        this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
        this.emitMouseEvent(doc, "mousedown", x, y, null, null, keyModifiers);
      } else {
        touchId = this.nextTouchId++;
        let touch = this.touchProvider.createATouch(target, x, y, touchId);
        this.touchProvider.emitTouchEvent("touchstart", touch);
        this.touchIds[touchId] = touch;
        return touchId;
      }
      break;

    case "release":
      if (this.mouseEventsOnly) {
        let [x, y] = this.lastCoordinates;
        this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
      } else {
        let touch = this.touchIds[touchId];
        let [x, y] = this.lastCoordinates;

        touch = this.touchProvider.createATouch(touch.target, x, y, touchId);
        this.touchProvider.emitTouchEvent("touchend", touch);

        if (this.isTap) {
          this.mouseTap(
              touch.target.ownerDocument,
              touch.clientX,
              touch.clientY,
              null,
              null,
              keyModifiers);
        }
        delete this.touchIds[touchId];
      }

      this.isTap = false;
      this.lastCoordinates = null;
      break;

    case "cancel":
      this.isTap = false;
      if (this.mouseEventsOnly) {
        let [x, y] = this.lastCoordinates;
        this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
      } else {
        this.touchProvider.emitTouchEvent("touchcancel", this.touchIds[touchId]);
        delete this.touchIds[touchId];
      }
      this.lastCoordinates = null;
      break;

    case "move":
      this.isTap = false;
      if (this.mouseEventsOnly) {
        this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
      } else {
        let touch = this.touchProvider.createATouch(
            this.touchIds[touchId].target, x, y, touchId);
        this.touchIds[touchId] = touch;
        this.touchProvider.emitTouchEvent("touchmove", touch);
      }
      break;

    case "contextmenu":
      this.isTap = false;
      let event = this.container.frame.document.createEvent("MouseEvents");
      if (this.mouseEventsOnly) {
        target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]);
      } else {
        target = this.touchIds[touchId].target;
      }

      let [clientX, clientY, pageX, pageY, screenX, screenY] =
          this.getCoordinateInfo(target, x, y);

      event.initMouseEvent(
          "contextmenu",
          true,
          true,
          target.ownerGlobal,
          1,
          screenX,
          screenY,
          clientX,
          clientY,
          false,
          false,
          false,
          false,
          0,
          null);
      target.dispatchEvent(event);
      break;

    default:
      throw new WebDriverError("Unknown event type: " + type);
  }
  this.checkForInterrupted();
};

action.Chain.prototype.mouseTap = function (doc, x, y, button, count, mod) {
  this.emitMouseEvent(doc, "mousemove", x, y, button, count, mod);
  this.emitMouseEvent(doc, "mousedown", x, y, button, count, mod);
  this.emitMouseEvent(doc, "mouseup", x, y, button, count, mod);
};
PK
!<#AA%chrome/marionette/content/listener.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */
/* global XPCNativeWrapper */

"use strict";

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

var uuidGen = Cc["@mozilla.org/uuid-generator;1"]
    .getService(Ci.nsIUUIDGenerator);

var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    .getService(Ci.mozIJSSubScriptLoader);

Cu.import("resource://gre/modules/FileUtils.jsm");
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/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/action.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/capture.js");
Cu.import("chrome://marionette/content/element.js");
const {
  ElementNotInteractableError,
  error,
  InsecureCertificateError,
  InvalidArgumentError,
  InvalidElementStateError,
  InvalidSelectorError,
  NoSuchElementError,
  NoSuchFrameError,
  TimeoutError,
  UnknownError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/evaluate.js");
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/interaction.js");
Cu.import("chrome://marionette/content/legacyaction.js");
Cu.import("chrome://marionette/content/navigate.js");
Cu.import("chrome://marionette/content/proxy.js");
Cu.import("chrome://marionette/content/session.js");

Cu.importGlobalProperties(["URL"]);

var marionetteTestName;
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIDOMWindowUtils);
var listenerId = null; // unique ID of this listener
var curContainer = {frame: content, shadowRoot: null};
var previousContainer = null;

var seenEls = new element.Store();
var SUPPORTED_STRATEGIES = new Set([
  element.Strategy.ClassName,
  element.Strategy.Selector,
  element.Strategy.ID,
  element.Strategy.Name,
  element.Strategy.LinkText,
  element.Strategy.PartialLinkText,
  element.Strategy.TagName,
  element.Strategy.XPath,
]);

var capabilities;

var legacyactions = new legacyaction.Chain(checkForInterrupted);

// the unload handler
var onunload;

// Flag to indicate whether an async script is currently running or not.
var asyncTestRunning = false;
var asyncTestCommandId;
var asyncTestTimeoutId;

var inactivityTimeoutId = null;

var originalOnError;
// Send move events about this often
var EVENT_INTERVAL = 30; // milliseconds
// last touch for each fingerId
var multiLast = {};
var asyncChrome = proxy.toChromeAsync({
  addMessageListener: addMessageListenerId.bind(this),
  removeMessageListener: removeMessageListenerId.bind(this),
  sendAsyncMessage: sendAsyncMessage.bind(this),
});
var syncChrome = proxy.toChrome(sendSyncMessage.bind(this));

var logger = Log.repository.getLogger("Marionette");
// Append only once to avoid duplicated output after listener.js gets reloaded
if (logger.ownAppenders.length == 0) {
  logger.addAppender(new Log.DumpAppender());
}

var modalHandler = function() {
  // This gets called on the system app only since it receives the
  // mozbrowserprompt event
  sendSyncMessage("Marionette:switchedToFrame",
      {frameValue: null, storePrevious: true});
  let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
  if (isLocal) {
    previousContainer = curContainer;
  }
  curContainer = {frame: content, shadowRoot: null};
};

// sandbox storage and name of the current sandbox
var sandboxes = new Sandboxes(() => curContainer.frame);
var sandboxName = "default";

/**
 * The load listener singleton helps to keep track of active page load
 * activities, and can be used by any command which might cause a navigation
 * to happen. In the specific case of a reload of the frame script it allows
 * to continue observing the current page load.
 */
var loadListener = {
  command_id: null,
  seenBeforeUnload: false,
  seenUnload: false,
  timeout: null,
  timerPageLoad: null,
  timerPageUnload: null,

  /**
   * Start listening for page unload/load events.
   *
   * @param {number} command_id
   *     ID of the currently handled message between the driver and
   *     listener.
   * @param {number} timeout
   *     Timeout in seconds the method has to wait for the page being
   *     finished loading.
   * @param {number} startTime
   *     Unix timestap when the navitation request got triggered.
   * @param {boolean=} waitForUnloaded
   *     If true wait for page unload events, otherwise only for page
   *     load events.
   */
  start(command_id, timeout, startTime, waitForUnloaded = true) {
    this.command_id = command_id;
    this.timeout = timeout;

    this.seenBeforeUnload = false;
    this.seenUnload = false;

    this.timerPageLoad = Cc["@mozilla.org/timer;1"]
        .createInstance(Ci.nsITimer);
    this.timerPageUnload = null;

    // In case the frame script has been reloaded, wait the remaining time
    timeout = startTime + timeout - new Date().getTime();

    if (timeout <= 0) {
      this.notify(this.timerPageLoad);
      return;
    }

    if (waitForUnloaded) {
      addEventListener("hashchange", this, false);
      addEventListener("pagehide", this, false);
      addEventListener("popstate", this, false);

      // The events can only be received when the event listeners are
      // added to the currently selected frame.
      curContainer.frame.addEventListener("beforeunload", this);
      curContainer.frame.addEventListener("unload", this);

      Services.obs.addObserver(this, "outer-window-destroyed");

    } else {
      // The frame script got reloaded due to a new content process.
      // Due to the time it takes to re-register the browser in Marionette,
      // it can happen that page load events are missed before the listeners
      // are getting attached again. By checking the document readyState the
      // command can return immediately if the page load is already done.
      let readyState = content.document.readyState;
      let documentURI = content.document.documentURI;
      logger.debug(`Check readyState "${readyState} for "${documentURI}"`);

      // If the page load has already finished, don't setup listeners and
      // timers but return immediatelly.
      if (this.handleReadyState(readyState, documentURI)) {
        return;
      }

      addEventListener("DOMContentLoaded", loadListener);
      addEventListener("pageshow", loadListener);
    }

    this.timerPageLoad.initWithCallback(
        this, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /**
   * Stop listening for page unload/load events.
   */
  stop() {
    if (this.timerPageLoad) {
      this.timerPageLoad.cancel();
    }

    if (this.timerPageUnload) {
      this.timerPageUnload.cancel();
    }

    removeEventListener("hashchange", this);
    removeEventListener("pagehide", this);
    removeEventListener("popstate", this);
    removeEventListener("DOMContentLoaded", this);
    removeEventListener("pageshow", this);

    // If the original content window, where the navigation was triggered,
    // doesn't exist anymore, exceptions can be silently ignored.
    try {
      curContainer.frame.removeEventListener("beforeunload", this);
      curContainer.frame.removeEventListener("unload", this);
    } catch (e) {
      if (e.name != "TypeError") {
        throw e;
      }
    }

    // In case the observer was added before the frame script has been
    // reloaded, it will no longer be available. Exceptions can be ignored.
    try {
      Services.obs.removeObserver(this, "outer-window-destroyed");
    } catch (e) {}
  },

  /**
   * Callback for registered DOM events.
   */
  handleEvent(event) {
    // Only care about events from the currently selected browsing context,
    // whereby some of those do not bubble up to the window.
    if (event.target != curContainer.frame &&
        event.target != curContainer.frame.document) {
      return;
    }

    let location = event.target.documentURI || event.target.location.href;
    logger.debug(`Received DOM event "${event.type}" for "${location}"`);

    switch (event.type) {
      case "beforeunload":
        this.seenBeforeUnload = true;
        break;

      case "unload":
        this.seenUnload = true;
        break;

      case "pagehide":
        this.seenUnload = true;

        removeEventListener("hashchange", this);
        removeEventListener("pagehide", this);
        removeEventListener("popstate", this);

        // Now wait until the target page has been loaded
        addEventListener("DOMContentLoaded", this, false);
        addEventListener("pageshow", this, false);
        break;

      case "hashchange":
      case "popstate":
        this.stop();
        sendOk(this.command_id);
        break;

      case "DOMContentLoaded":
      case "pageshow":
        this.handleReadyState(event.target.readyState,
            event.target.documentURI);
        break;
    }
  },

  /**
   * Checks the value of readyState for the current page
   * load activity, and resolves the command if the load
   * has been finished. It also takes care of the selected
   * page load strategy.
   *
   * @param {string} readyState
   *     Current ready state of the document.
   * @param {string} documentURI
   *     Current document URI of the document.
   *
   * @return {boolean}
   *     True if the page load has been finished.
   */
  handleReadyState(readyState, documentURI) {
    let finished = false;

    switch (readyState) {
      case "interactive":
        if (documentURI.startsWith("about:certerror")) {
          this.stop();
          sendError(new InsecureCertificateError(), this.command_id);
          finished = true;

        } else if (/about:.*(error)\?/.exec(documentURI)) {
          this.stop();
          sendError(new UnknownError(`Reached error page: ${documentURI}`),
              this.command_id);
          finished = true;

        // Return early with a page load strategy of eager, and also
        // special-case about:blocked pages which should be treated as
        // non-error pages but do not raise a pageshow event. about:blank
        // is also treaded specifically here, because it gets temporary
        // loaded for new content processes, and we only want to rely on
        // complete loads for it.
        } else if ((capabilities.get("pageLoadStrategy") ===
            session.PageLoadStrategy.Eager &&
            documentURI != "about:blank") ||
            /about:blocked\?/.exec(documentURI)) {
          this.stop();
          sendOk(this.command_id);
          finished = true;
        }

        break;

      case "complete":
        this.stop();
        sendOk(this.command_id);
        finished = true;

        break;
    }

    return finished;
  },

  /**
   * Callback for navigation timeout timer.
   */
  notify(timer) {
    switch (timer) {
      case this.timerPageUnload:
        // In the case when a document has a beforeunload handler
        // registered, the currently active command will return immediately
        // due to the modal dialog observer in proxy.js.
        //
        // Otherwise the timeout waiting for the document to start
        // navigating is increased by 5000 ms to ensure a possible load
        // event is not missed. In the common case such an event should
        // occur pretty soon after beforeunload, and we optimise for this.
        if (this.seenBeforeUnload) {
          this.seenBeforeUnload = null;
          this.timerPageUnload.initWithCallback(
              this, 5000, Ci.nsITimer.TYPE_ONE_SHOT);

        // If no page unload has been detected, ensure to properly stop
        // the load listener, and return from the currently active command.
        } else if (!this.seenUnload) {
          logger.debug("Canceled page load listener because no navigation " +
              "has been detected");
          this.stop();
          sendOk(this.command_id);
        }
        break;

      case this.timerPageLoad:
        this.stop();
        sendError(
            new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
            this.command_id);
        break;
    }
  },

  observe(subject, topic, data) {
    const win = curContainer.frame;
    const winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
    const curWinID = win.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;

    logger.debug(`Received observer notification "${topic}" for "${winID}"`);

    switch (topic) {
      // In the case when the currently selected frame is closed,
      // there will be no further load events. Stop listening immediately.
      case "outer-window-destroyed":
        if (curWinID === winID) {
          this.stop();
          sendOk(this.command_id);
        }
        break;
    }
  },

  /**
   * Continue to listen for page load events after the frame script has been
   * reloaded.
   *
   * @param {number} command_id
   *     ID of the currently handled message between the driver and
   *     listener.
   * @param {number} timeout
   *     Timeout in milliseconds the method has to wait for the page
   *     being finished loading.
   * @param {number} startTime
   *     Unix timestap when the navitation request got triggered.
   */
  waitForLoadAfterFramescriptReload(command_id, timeout, startTime) {
    this.start(command_id, timeout, startTime, false);
  },

  /**
   * Use a trigger callback to initiate a page load, and attach listeners if
   * a page load is expected.
   *
   * @param {function} trigger
   *     Callback that triggers the page load.
   * @param {number} command_id
   *     ID of the currently handled message between the driver and listener.
   * @param {number} pageTimeout
   *     Timeout in milliseconds the method has to wait for the page
   *    finished loading.
   * @param {boolean=} loadEventExpected
   *     Optional flag, which indicates that navigate has to wait for the page
   *     finished loading.
   * @param {string=} url
   *     Optional URL, which is used to check if a page load is expected.
   */
  navigate(trigger, command_id, timeout, loadEventExpected = true,
      useUnloadTimer = false) {

    // Only wait if the page load strategy is not `none`
    loadEventExpected = loadEventExpected &&
        (capabilities.get("pageLoadStrategy") !==
        session.PageLoadStrategy.None);

    if (loadEventExpected) {
      let startTime = new Date().getTime();
      this.start(command_id, timeout, startTime, true);
    }

    return Task.spawn(function* () {
      yield trigger();

    }).then(val => {
      if (!loadEventExpected) {
        sendOk(command_id);
        return;
      }

      // If requested setup a timer to detect a possible page load
      if (useUnloadTimer) {
        this.timerPageUnload = Cc["@mozilla.org/timer;1"]
            .createInstance(Ci.nsITimer);
        this.timerPageUnload.initWithCallback(
            this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
      }

    }).catch(err => {
      if (loadEventExpected) {
        this.stop();
      }

      sendError(err, command_id);

    });
  },
};

/**
 * Called when listener is first started up.  The listener sends its
 * unique window ID and its current URI to the actor.  If the actor returns
 * an ID, we start the listeners. Otherwise, nothing happens.
 */
function registerSelf() {
  let msg = {value: winUtil.outerWindowID};
  logger.debug(`Register listener.js for window ${msg.value}`);

  // register will have the ID and a boolean describing if this is the
  // main process or not
  let register = sendSyncMessage("Marionette:register", msg);
  if (register[0]) {
    listenerId = register[0][0];
    capabilities = session.Capabilities.fromJSON(register[0][1]);
    if (typeof listenerId != "undefined") {
      startListeners();
      sendAsyncMessage("Marionette:listenersAttached",
          {"listenerId": listenerId});
    }
  }
}

// Eventually we will not have a closure for every single command, but
// use a generic dispatch for all listener commands.
//
// Perhaps one could even conceive having a separate instance of
// CommandProcessor for the listener, because the code is mostly the same.
function dispatch(fn) {
  if (typeof fn != "function") {
    throw new TypeError("Provided dispatch handler is not a function");
  }

  return function(msg) {
    let id = msg.json.command_id;

    let req = Task.spawn(function* () {
      if (typeof msg.json == "undefined" || msg.json instanceof Array) {
        return yield fn.apply(null, msg.json);
      }
      return yield fn(msg.json);
    });

    let okOrValueResponse = rv => {
      if (typeof rv == "undefined") {
        sendOk(id);
      } else {
        sendResponse(rv, id);
      }
    };

    req.then(okOrValueResponse, err => sendError(err, id))
        .catch(error.report);
  };
}

/**
 * Add a message listener that's tied to our listenerId.
 */
function addMessageListenerId(messageName, handler) {
  addMessageListener(messageName + listenerId, handler);
}

/**
 * Remove a message listener that's tied to our listenerId.
 */
function removeMessageListenerId(messageName, handler) {
  removeMessageListener(messageName + listenerId, handler);
}

var getPageSourceFn = dispatch(getPageSource);
var getActiveElementFn = dispatch(getActiveElement);
var getElementAttributeFn = dispatch(getElementAttribute);
var getElementPropertyFn = dispatch(getElementProperty);
var getElementTextFn = dispatch(getElementText);
var getElementTagNameFn = dispatch(getElementTagName);
var getElementRectFn = dispatch(getElementRect);
var isElementEnabledFn = dispatch(isElementEnabled);
var findElementContentFn = dispatch(findElementContent);
var findElementsContentFn = dispatch(findElementsContent);
var isElementSelectedFn = dispatch(isElementSelected);
var clearElementFn = dispatch(clearElement);
var isElementDisplayedFn = dispatch(isElementDisplayed);
var getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
var switchToShadowRootFn = dispatch(switchToShadowRoot);
var singleTapFn = dispatch(singleTap);
var takeScreenshotFn = dispatch(takeScreenshot);
var performActionsFn = dispatch(performActions);
var releaseActionsFn = dispatch(releaseActions);
var actionChainFn = dispatch(actionChain);
var multiActionFn = dispatch(multiAction);
var executeFn = dispatch(execute);
var executeInSandboxFn = dispatch(executeInSandbox);
var sendKeysToElementFn = dispatch(sendKeysToElement);
var reftestWaitFn = dispatch(reftestWait);

/**
 * Start all message listeners
 */
function startListeners() {
  addMessageListenerId("Marionette:newSession", newSession);
  addMessageListenerId("Marionette:execute", executeFn);
  addMessageListenerId("Marionette:executeInSandbox", executeInSandboxFn);
  addMessageListenerId("Marionette:singleTap", singleTapFn);
  addMessageListenerId("Marionette:performActions", performActionsFn);
  addMessageListenerId("Marionette:releaseActions", releaseActionsFn);
  addMessageListenerId("Marionette:actionChain", actionChainFn);
  addMessageListenerId("Marionette:multiAction", multiActionFn);
  addMessageListenerId("Marionette:get", get);
  addMessageListenerId("Marionette:waitForPageLoaded", waitForPageLoaded);
  addMessageListenerId("Marionette:cancelRequest", cancelRequest);
  addMessageListenerId("Marionette:getPageSource", getPageSourceFn);
  addMessageListenerId("Marionette:goBack", goBack);
  addMessageListenerId("Marionette:goForward", goForward);
  addMessageListenerId("Marionette:refresh", refresh);
  addMessageListenerId("Marionette:findElementContent", findElementContentFn);
  addMessageListenerId(
      "Marionette:findElementsContent", findElementsContentFn);
  addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
  addMessageListenerId("Marionette:clickElement", clickElement);
  addMessageListenerId(
      "Marionette:getElementAttribute", getElementAttributeFn);
  addMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
  addMessageListenerId("Marionette:getElementText", getElementTextFn);
  addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
  addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
  addMessageListenerId(
      "Marionette:getElementValueOfCssProperty",
      getElementValueOfCssPropertyFn);
  addMessageListenerId("Marionette:getElementRect", getElementRectFn);
  addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
  addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
  addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElementFn);
  addMessageListenerId("Marionette:clearElement", clearElementFn);
  addMessageListenerId("Marionette:switchToFrame", switchToFrame);
  addMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
  addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
  addMessageListenerId("Marionette:deleteSession", deleteSession);
  addMessageListenerId("Marionette:sleepSession", sleepSession);
  addMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
  addMessageListenerId("Marionette:reftestWait", reftestWaitFn);
}

/**
 * Called when we start a new session. It registers the
 * current environment, and resets all values
 */
function newSession(msg) {
  capabilities = session.Capabilities.fromJSON(msg.json);
  resetValues();
}

/**
 * Puts the current session to sleep, so all listeners are removed except
 * for the 'restart' listener.
 */
function sleepSession(msg) {
  deleteSession();
  addMessageListener("Marionette:restart", restart);
}

/**
 * Restarts all our listeners after this listener was put to sleep
 */
function restart(msg) {
  removeMessageListener("Marionette:restart", restart);
  registerSelf();
}

/**
 * Removes all listeners
 */
function deleteSession(msg) {
  removeMessageListenerId("Marionette:newSession", newSession);
  removeMessageListenerId("Marionette:execute", executeFn);
  removeMessageListenerId("Marionette:executeInSandbox", executeInSandboxFn);
  removeMessageListenerId("Marionette:singleTap", singleTapFn);
  removeMessageListenerId("Marionette:performActions", performActionsFn);
  removeMessageListenerId("Marionette:releaseActions", releaseActionsFn);
  removeMessageListenerId("Marionette:actionChain", actionChainFn);
  removeMessageListenerId("Marionette:multiAction", multiActionFn);
  removeMessageListenerId("Marionette:get", get);
  removeMessageListenerId("Marionette:waitForPageLoaded", waitForPageLoaded);
  removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
  removeMessageListenerId("Marionette:getPageSource", getPageSourceFn);
  removeMessageListenerId("Marionette:goBack", goBack);
  removeMessageListenerId("Marionette:goForward", goForward);
  removeMessageListenerId("Marionette:refresh", refresh);
  removeMessageListenerId(
      "Marionette:findElementContent", findElementContentFn);
  removeMessageListenerId(
      "Marionette:findElementsContent", findElementsContentFn);
  removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
  removeMessageListenerId("Marionette:clickElement", clickElement);
  removeMessageListenerId(
      "Marionette:getElementAttribute", getElementAttributeFn);
  removeMessageListenerId(
      "Marionette:getElementProperty", getElementPropertyFn);
  removeMessageListenerId(
      "Marionette:getElementText", getElementTextFn);
  removeMessageListenerId(
      "Marionette:getElementTagName", getElementTagNameFn);
  removeMessageListenerId(
      "Marionette:isElementDisplayed", isElementDisplayedFn);
  removeMessageListenerId(
      "Marionette:getElementValueOfCssProperty",
      getElementValueOfCssPropertyFn);
  removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
  removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
  removeMessageListenerId(
      "Marionette:isElementSelected", isElementSelectedFn);
  removeMessageListenerId(
      "Marionette:sendKeysToElement", sendKeysToElementFn);
  removeMessageListenerId("Marionette:clearElement", clearElementFn);
  removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
  removeMessageListenerId(
      "Marionette:switchToParentFrame", switchToParentFrame);
  removeMessageListenerId(
      "Marionette:switchToShadowRoot", switchToShadowRootFn);
  removeMessageListenerId("Marionette:deleteSession", deleteSession);
  removeMessageListenerId("Marionette:sleepSession", sleepSession);
  removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);

  seenEls.clear();
  // reset container frame to the top-most frame
  curContainer = {frame: content, shadowRoot: null};
  curContainer.frame.focus();
  legacyactions.touchIds = {};
  if (action.inputStateMap !== undefined) {
    action.inputStateMap.clear();
  }
  if (action.inputsToCancel !== undefined) {
    action.inputsToCancel.length = 0;
  }
}

/**
 * Send asynchronous reply to chrome.
 *
 * @param {UUID} uuid
 *     Unique identifier of the request.
 * @param {AsyncContentSender.ResponseType} type
 *     Type of response.
 * @param {*} [Object] data
 *     JSON serialisable object to accompany the message.  Defaults to
 *     an empty dictionary.
 */
function sendToServer(uuid, data = undefined) {
  let channel = new proxy.AsyncMessageChannel(
      () => this,
      sendAsyncMessage.bind(this));
  channel.reply(uuid, data);
}

/**
 * Send asynchronous reply with value to chrome.
 *
 * @param {Object} obj
 *     JSON serialisable object of arbitrary type and complexity.
 * @param {UUID} uuid
 *     Unique identifier of the request.
 */
function sendResponse(obj, uuid) {
  sendToServer(uuid, obj);
}

/**
 * Send asynchronous reply to chrome.
 *
 * @param {UUID} uuid
 *     Unique identifier of the request.
 */
function sendOk(uuid) {
  sendToServer(uuid);
}

/**
 * Send asynchronous error reply to chrome.
 *
 * @param {Error} err
 *     Error to notify chrome of.
 * @param {UUID} uuid
 *     Unique identifier of the request.
 */
function sendError(err, uuid) {
  sendToServer(uuid, err);
}

/**
 * Clear test values after completion of test
 */
function resetValues() {
  sandboxes.clear();
  curContainer = {frame: content, shadowRoot: null};
  legacyactions.mouseEventsOnly = false;
  action.inputStateMap = new Map();
  action.inputsToCancel = [];
}

function wasInterrupted() {
  if (previousContainer) {
    let element = content.document.elementFromPoint(
        (content.innerWidth / 2), (content.innerHeight / 2));
    if (element.id.indexOf("modal-dialog") == -1) {
      return true;
    }
    return false;
  }
  return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
}

function checkForInterrupted() {
  if (wasInterrupted()) {
    // if previousContainer is set, then we're in a single process
    // environment
    if (previousContainer) {
      curContainer = legacyactions.container = previousContainer;
      previousContainer = null;
    // else we're in OOP environment, so we'll switch to the original
    // OOP frame
    } else {
      sendSyncMessage("Marionette:switchToModalOrigin");
    }
    sendSyncMessage("Marionette:switchedToFrame", {restorePrevious: true});
  }
}

function* execute(script, args, timeout, opts) {
  opts.timeout = timeout;

  let sb = sandbox.createMutable(curContainer.frame);
  let wargs = evaluate.fromJSON(
      args, seenEls, curContainer.frame, curContainer.shadowRoot);
  let res = yield evaluate.sandbox(sb, script, wargs, opts);

  return evaluate.toJSON(res, seenEls);
}

function* executeInSandbox(script, args, timeout, opts) {
  opts.timeout = timeout;

  let sb = sandboxes.get(opts.sandboxName, opts.newSandbox);
  let wargs = evaluate.fromJSON(
      args, seenEls, curContainer.frame, curContainer.shadowRoot);
  let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);

  let res = yield evaluatePromise;
  return evaluate.toJSON(res, seenEls);
}

function emitTouchEvent(type, touch) {
  if (!wasInterrupted()) {
    logger.info(`Emitting Touch event of type ${type} ` +
        `to element with id: ${touch.target.id} ` +
        `and tag name: ${touch.target.tagName} ` +
        `at coordinates (${touch.clientX}), ` +
        `${touch.clientY}) relative to the viewport`);

    const win = curContainer.frame;
    let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIWebNavigation)
        .QueryInterface(Ci.nsIDocShell);
    if (docShell.asyncPanZoomEnabled && legacyactions.scrolling) {
      // if we're in APZ and we're scrolling, we must use
      // sendNativeTouchPoint to dispatch our touchmove events
      let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");

      // only call emitTouchEventForIFrame if we're inside an iframe.
      if (index != null) {
        let ev = {
          index,
          type,
          id: touch.identifier,
          clientX: touch.clientX,
          clientY: touch.clientY,
          screenX: touch.screenX,
          screenY: touch.screenY,
          radiusX: touch.radiusX,
          radiusY: touch.radiusY,
          rotation: touch.rotationAngle,
          force: touch.force,
        };
        sendSyncMessage("Marionette:emitTouchEvent", ev);
        return;
      }
    }

    // we get here if we're not in asyncPacZoomEnabled land, or if we're
    // the main process
    let domWindowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils);
    domWindowUtils.sendTouchEvent(
        type,
        [touch.identifier],
        [touch.clientX],
        [touch.clientY],
        [touch.radiusX],
        [touch.radiusY],
        [touch.rotationAngle],
        [touch.force],
        1,
        0);
  }
}

/**
 * Function that perform a single tap
 */
function singleTap(id, corx, cory) {
  let el = seenEls.get(id, curContainer);
  // after this block, the element will be scrolled into view
  let visible = element.isVisible(el, corx, cory);
  if (!visible) {
    throw new ElementNotInteractableError(
        "Element is not currently visible and may not be manipulated");
  }

  let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
  return a11y.getAccessible(el, true).then(acc => {
    a11y.assertVisible(acc, el, visible);
    a11y.assertActionable(acc, el);
    if (!curContainer.frame.document.createTouch) {
      legacyactions.mouseEventsOnly = true;
    }
    let c = element.coordinates(el, corx, cory);
    if (!legacyactions.mouseEventsOnly) {
      let touchId = legacyactions.nextTouchId++;
      let touch = createATouch(el, c.x, c.y, touchId);
      emitTouchEvent("touchstart", touch);
      emitTouchEvent("touchend", touch);
    }
    legacyactions.mouseTap(el.ownerDocument, c.x, c.y);
  });
}

/**
 * Function to create a touch based on the element
 * corx and cory are relative to the viewport, id is the touchId
 */
function createATouch(el, corx, cory, touchId) {
  let doc = el.ownerDocument;
  let win = doc.defaultView;
  let [clientX, clientY, pageX, pageY, screenX, screenY] =
      legacyactions.getCoordinateInfo(el, corx, cory);
  let atouch = doc.createTouch(
      win,
      el,
      touchId,
      pageX,
      pageY,
      screenX,
      screenY,
      clientX,
      clientY);
  return atouch;
}

/**
 * Perform a series of grouped actions at the specified points in time.
 *
 * @param {obj} msg
 *      Object with an |actions| attribute that is an Array of objects
 *      each of which represents an action sequence.
 */
function* performActions(msg) {
  let chain = action.Chain.fromJson(msg.actions);
  yield action.dispatch(chain, seenEls, curContainer);
}

/**
 * The Release Actions command is used to release all the keys and pointer
 * buttons that are currently depressed. This causes events to be fired
 * as if the state was released by an explicit series of actions. It also
 * clears all the internal state of the virtual devices.
 */
function* releaseActions() {
  yield action.dispatchTickActions(
      action.inputsToCancel.reverse(), 0, seenEls, curContainer);
  action.inputsToCancel.length = 0;
  action.inputStateMap.clear();
}

/**
 * Start action chain on one finger.
 */
function actionChain(chain, touchId) {
  let touchProvider = {};
  touchProvider.createATouch = createATouch;
  touchProvider.emitTouchEvent = emitTouchEvent;

  return legacyactions.dispatchActions(
      chain,
      touchId,
      curContainer,
      seenEls,
      touchProvider);
}

function emitMultiEvents(type, touch, touches) {
  let target = touch.target;
  let doc = target.ownerDocument;
  let win = doc.defaultView;
  // touches that are in the same document
  let documentTouches = doc.createTouchList(touches.filter(function(t) {
    return ((t.target.ownerDocument === doc) && (type != "touchcancel"));
  }));
  // touches on the same target
  let targetTouches = doc.createTouchList(touches.filter(function(t) {
    return ((t.target === target) &&
        ((type != "touchcancel") || (type != "touchend")));
  }));
  // Create changed touches
  let changedTouches = doc.createTouchList(touch);
  // Create the event object
  let event = doc.createEvent("TouchEvent");
  event.initTouchEvent(type,
      true,
      true,
      win,
      0,
      false, false, false, false,
      documentTouches,
      targetTouches,
      changedTouches);
  target.dispatchEvent(event);
}

function setDispatch(batches, touches, batchIndex = 0) {
  // check if all the sets have been fired
  if (batchIndex >= batches.length) {
    multiLast = {};
    return;
  }

  // a set of actions need to be done
  let batch = batches[batchIndex];
  // each action for some finger
  let pack;
  // the touch id for the finger (pack)
  let touchId;
  // command for the finger
  let command;
  // touch that will be created for the finger
  let el;
  let touch;
  let lastTouch;
  let touchIndex;
  let waitTime = 0;
  let maxTime = 0;
  let c;

  // loop through the batch
  batchIndex++;
  for (let i = 0; i < batch.length; i++) {
    pack = batch[i];
    touchId = pack[0];
    command = pack[1];

    switch (command) {
      case "press":
        el = seenEls.get(pack[2], curContainer);
        c = element.coordinates(el, pack[3], pack[4]);
        touch = createATouch(el, c.x, c.y, touchId);
        multiLast[touchId] = touch;
        touches.push(touch);
        emitMultiEvents("touchstart", touch, touches);
        break;

      case "release":
        touch = multiLast[touchId];
        // the index of the previous touch for the finger may change in
        // the touches array
        touchIndex = touches.indexOf(touch);
        touches.splice(touchIndex, 1);
        emitMultiEvents("touchend", touch, touches);
        break;

      case "move":
        el = seenEls.get(pack[2], curContainer);
        c = element.coordinates(el);
        touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
        touchIndex = touches.indexOf(lastTouch);
        touches[touchIndex] = touch;
        multiLast[touchId] = touch;
        emitMultiEvents("touchmove", touch, touches);
        break;

      case "moveByOffset":
        el = multiLast[touchId].target;
        lastTouch = multiLast[touchId];
        touchIndex = touches.indexOf(lastTouch);
        let doc = el.ownerDocument;
        let win = doc.defaultView;
        // since x and y are relative to the last touch, therefore,
        // it's relative to the position of the last touch
        let clientX = lastTouch.clientX + pack[2];
        let clientY = lastTouch.clientY + pack[3];
        let pageX = clientX + win.pageXOffset;
        let pageY = clientY + win.pageYOffset;
        let screenX = clientX + win.mozInnerScreenX;
        let screenY = clientY + win.mozInnerScreenY;
        touch = doc.createTouch(
            win,
            el,
            touchId,
            pageX,
            pageY,
            screenX,
            screenY,
            clientX,
            clientY);
        touches[touchIndex] = touch;
        multiLast[touchId] = touch;
        emitMultiEvents("touchmove", touch, touches);
        break;

      case "wait":
        if (typeof pack[2] != "undefined") {
          waitTime = pack[2] * 1000;
          if (waitTime > maxTime) {
            maxTime = waitTime;
          }
        }
        break;
    }
  }

  if (maxTime != 0) {
    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(function() {
      setDispatch(batches, touches, batchIndex);
    }, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
  } else {
    setDispatch(batches, touches, batchIndex);
  }
}

/**
 * Start multi-action.
 *
 * @param {Number} maxLen
 *     Longest action chain for one finger.
 */
function multiAction(args, maxLen) {
  // unwrap the original nested array
  let commandArray = evaluate.fromJSON(
      args, seenEls, curContainer.frame, curContainer.shadowRoot);
  let concurrentEvent = [];
  let temp;
  for (let i = 0; i < maxLen; i++) {
    let row = [];
    for (let j = 0; j < commandArray.length; j++) {
      if (typeof commandArray[j][i] != "undefined") {
        // add finger id to the front of each action,
        // i.e. [finger_id, action, element]
        temp = commandArray[j][i];
        temp.unshift(j);
        row.push(temp);
      }
    }
    concurrentEvent.push(row);
  }

  // Now concurrent event is made of sets where each set contain a list
  // of actions that need to be fired.
  //
  // But note that each action belongs to a different finger
  // pendingTouches keeps track of current touches that's on the screen.
  let pendingTouches = [];
  setDispatch(concurrentEvent, pendingTouches);
}

/**
 * Cancel the polling and remove the event listener associated with a
 * current navigation request in case we're interupted by an onbeforeunload
 * handler and navigation doesn't complete.
 */
function cancelRequest() {
  loadListener.stop();
}

/**
 * This implements the latter part of a get request (for the case we need
 * to resume one when the frame script has been reloaded in the middle of a
 * navigate request). This is most of of the work of a navigate request,
 * but doesn't assume DOMContentLoaded is yet to fire.
 *
 * @param {number} command_id
 *     ID of the currently handled message between the driver and
 *     listener.
 * @param {number} pageTimeout
 *     Timeout in seconds the method has to wait for the page being
 *     finished loading.
 * @param {number} startTime
 *     Unix timestap when the navitation request got triggred.
 */
function waitForPageLoaded(msg) {
  let {command_id, pageTimeout, startTime} = msg.json;

  loadListener.waitForLoadAfterFramescriptReload(
      command_id, pageTimeout, startTime);
}

/**
 * Navigate to the given URL.  The operation will be performed on the
 * current browsing context, which means it handles the case where we
 * navigate within an iframe.  All other navigation is handled by the driver
 * (in chrome space).
 */
function get(msg) {
  let {command_id, pageTimeout, url, loadEventExpected = null} = msg.json;

  try {
    if (typeof url == "string") {
      try {
        if (loadEventExpected === null) {
          loadEventExpected = navigate.isLoadEventExpected(
              curContainer.frame.location, url);
        }
      } catch (e) {
        let err = new InvalidArgumentError("Malformed URL: " + e.message);
        sendError(err, command_id);
        return;
      }
    }

    // We need to move to the top frame before navigating
    sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
    curContainer.frame = content;

    loadListener.navigate(() => {
      curContainer.frame.location = url;
    }, command_id, pageTimeout, loadEventExpected);

  } catch (e) {
    sendError(e, command_id);
  }
}

/**
 * Cause the browser to traverse one step backward in the joint history
 * of the current browsing context.
 *
 * @param {number} command_id
 *     ID of the currently handled message between the driver and
 *     listener.
 * @param {number} pageTimeout
 *     Timeout in milliseconds the method has to wait for the page being
 *     finished loading.
 */
function goBack(msg) {
  let {command_id, pageTimeout} = msg.json;

  try {
    loadListener.navigate(() => {
      curContainer.frame.history.back();
    }, command_id, pageTimeout);

  } catch (e) {
    sendError(e, command_id);
  }
}

/**
 * Cause the browser to traverse one step forward in the joint history
 * of the current browsing context.
 *
 * @param {number} command_id
 *     ID of the currently handled message between the driver and
 *     listener.
 * @param {number} pageTimeout
 *     Timeout in milliseconds the method has to wait for the page being
 *     finished loading.
 */
function goForward(msg) {
  let {command_id, pageTimeout} = msg.json;

  try {
    loadListener.navigate(() => {
      curContainer.frame.history.forward();
    }, command_id, pageTimeout);

  } catch (e) {
    sendError(e, command_id);
  }
}

/**
 * Causes the browser to reload the page in in current top-level browsing
 * context.
 *
 * @param {number} command_id
 *     ID of the currently handled message between the driver and
 *     listener.
 * @param {number} pageTimeout
 *     Timeout in milliseconds the method has to wait for the page being
 *     finished loading.
 */
function refresh(msg) {
  let {command_id, pageTimeout} = msg.json;

  try {
    // We need to move to the top frame before navigating
    sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
    curContainer.frame = content;

    loadListener.navigate(() => {
      curContainer.frame.location.reload(true);
    }, command_id, pageTimeout);

  } catch (e) {
    sendError(e, command_id);
  }
}

/**
 * Get source of the current browsing context's DOM.
 */
function getPageSource() {
  return curContainer.frame.document.documentElement.outerHTML;
}

/**
 * Find an element in the current browsing context's document using the
 * given search strategy.
 */
function* findElementContent(strategy, selector, opts = {}) {
  if (!SUPPORTED_STRATEGIES.has(strategy)) {
    throw new InvalidSelectorError("Strategy not supported: " + strategy);
  }

  opts.all = false;
  if (opts.startNode) {
    opts.startNode = seenEls.get(opts.startNode, curContainer);
  }

  let el = yield element.find(curContainer, strategy, selector, opts);
  let elRef = seenEls.add(el);
  let webEl = element.makeWebElement(elRef);
  return webEl;
}

/**
 * Find elements in the current browsing context's document using the
 * given search strategy.
 */
function* findElementsContent(strategy, selector, opts = {}) {
  if (!SUPPORTED_STRATEGIES.has(strategy)) {
    throw new InvalidSelectorError("Strategy not supported: " + strategy);
  }

  opts.all = true;
  if (opts.startNode) {
    opts.startNode = seenEls.get(opts.startNode, curContainer);
  }

  let els = yield element.find(curContainer, strategy, selector, opts);
  let elRefs = seenEls.addAll(els);
  let webEls = elRefs.map(element.makeWebElement);
  return webEls;
}

/** Find and return the active element on the page. */
function getActiveElement() {
  let el = curContainer.frame.document.activeElement;
  return evaluate.toJSON(el, seenEls);
}

/**
 * Send click event to element.
 *
 * @param {number} command_id
 *     ID of the currently handled message between the driver and
 *     listener.
 * @param {WebElement} id
 *     Reference to the web element to click.
 * @param {number} pageTimeout
 *     Timeout in milliseconds the method has to wait for the page being
 *     finished loading.
 */
function clickElement(msg) {
  let {command_id, id, pageTimeout} = msg.json;

  try {
    let loadEventExpected = true;

    let target = getElementAttribute(id, "target");

    if (target === "_blank") {
      loadEventExpected = false;
    }

    loadListener.navigate(() => {
      return interaction.clickElement(
          seenEls.get(id, curContainer),
          capabilities.get("moz:accessibilityChecks"),
          capabilities.get("specificationLevel") >= 1
      );
    }, command_id, pageTimeout, loadEventExpected, true);

  } catch (e) {
    sendError(e, command_id);
  }
}

function getElementAttribute(id, name) {
  let el = seenEls.get(id, curContainer);
  if (element.isBooleanAttribute(el, name)) {
    if (el.hasAttribute(name)) {
      return "true";
    }
    return null;
  }
  return el.getAttribute(name);
}

function getElementProperty(id, name) {
  let el = seenEls.get(id, curContainer);
  return typeof el[name] != "undefined" ? el[name] : null;
}

/**
 * Get the text of this element. This includes text from child elements.
 *
 * @param {WebElement} id
 *     Reference to web element.
 *
 * @return {string}
 *     Text of element.
 */
function getElementText(id) {
  let el = seenEls.get(id, curContainer);
  return atom.getElementText(el, curContainer.frame);
}

/**
 * Get the tag name of an element.
 *
 * @param {WebElement} id
 *     Reference to web element.
 *
 * @return {string}
 *     Tag name of element.
 */
function getElementTagName(id) {
  let el = seenEls.get(id, curContainer);
  return el.tagName.toLowerCase();
}

/**
 * Determine the element displayedness of the given web element.
 *
 * Also performs additional accessibility checks if enabled by session
 * capability.
 */
function isElementDisplayed(id) {
  let el = seenEls.get(id, curContainer);
  return interaction.isElementDisplayed(
      el, capabilities.get("moz:accessibilityChecks"));
}

/**
 * Retrieves the computed value of the given CSS property of the given
 * web element.
 *
 * @param {String} id
 *     Web element reference.
 * @param {String} prop
 *     The CSS property to get.
 *
 * @return {String}
 *     Effective value of the requested CSS property.
 */
function getElementValueOfCssProperty(id, prop) {
  let el = seenEls.get(id, curContainer);
  let st = curContainer.frame.document.defaultView.getComputedStyle(el);
  return st.getPropertyValue(prop);
}

/**
 * Get the position and dimensions of the element.
 *
 * @param {WebElement} id
 *     Reference to web element.
 *
 * @return {Object.<string, number>}
 *     The x, y, width, and height properties of the element.
 */
function getElementRect(id) {
  let el = seenEls.get(id, curContainer);
  let clientRect = el.getBoundingClientRect();
  return {
    x: clientRect.x + curContainer.frame.pageXOffset,
    y: clientRect.y + curContainer.frame.pageYOffset,
    width: clientRect.width,
    height: clientRect.height,
  };
}

/**
 * Check if element is enabled.
 *
 * @param {WebElement} id
 *     Reference to web element.
 *
 * @return {boolean}
 *     True if enabled, false otherwise.
 */
function isElementEnabled(id) {
  let el = seenEls.get(id, curContainer);
  return interaction.isElementEnabled(
      el, capabilities.get("moz:accessibilityChecks"));
}

/**
 * Determines if the referenced element is selected or not.
 *
 * This operation only makes sense on input elements of the Checkbox-
 * and Radio Button states, or option elements.
 */
function isElementSelected(id) {
  let el = seenEls.get(id, curContainer);
  return interaction.isElementSelected(
      el, capabilities.get("moz:accessibilityChecks"));
}

function* sendKeysToElement(id, val) {
  let el = seenEls.get(id, curContainer);
  if (el.type == "file") {
    yield interaction.uploadFile(el, val);
  } else if ((el.type == "date" || el.type == "time") &&
      Preferences.get("dom.forms.datetime")) {
    yield interaction.setFormControlValue(el, val);
  } else {
    yield interaction.sendKeysToElement(
        el, val, false, capabilities.get("moz:accessibilityChecks"));
  }
}

/**
 * Clear the text of an element.
 */
function clearElement(id) {
  try {
    let el = seenEls.get(id, curContainer);
    if (el.type == "file") {
      el.value = null;
    } else {
      atom.clearElement(el, curContainer.frame);
    }
  } catch (e) {
    // Bug 964738: Newer atoms contain status codes which makes wrapping
    // this in an error prototype that has a status property unnecessary
    if (e.name == "InvalidElementStateError") {
      throw new InvalidElementStateError(e.message);
    } else {
      throw e;
    }
  }
}

/**
 * Switch the current context to the specified host's Shadow DOM.
 *
 * @param {WebElement} id
 *     Reference to web element.
 */
function switchToShadowRoot(id) {
  if (!id) {
    // If no host element is passed, attempt to find a parent shadow
    // root or, if none found, unset the current shadow root
    if (curContainer.shadowRoot) {
      let parent;
      try {
        parent = curContainer.shadowRoot.host;
      } catch (e) {
        // There is a chance that host element is dead and we are trying to
        // access a dead object.
        curContainer.shadowRoot = null;
        return;
      }
      while (parent && !(parent instanceof curContainer.frame.ShadowRoot)) {
        parent = parent.parentNode;
      }
      curContainer.shadowRoot = parent;
    }
    return;
  }

  let foundShadowRoot;
  let hostEl = seenEls.get(id, curContainer);
  foundShadowRoot = hostEl.shadowRoot;
  if (!foundShadowRoot) {
    throw new NoSuchElementError("Unable to locate shadow root: " + id);
  }
  curContainer.shadowRoot = foundShadowRoot;
}

/**
 * Switch to the parent frame of the current frame. If the frame is the
 * top most is the current frame then no action will happen.
 */
function switchToParentFrame(msg) {
  curContainer.frame = curContainer.frame.parent;
  let parentElement = seenEls.add(curContainer.frame);

  sendSyncMessage(
      "Marionette:switchedToFrame", {frameValue: parentElement});

  sendOk(msg.json.command_id);
}

/**
 * Switch to frame given either the server-assigned element id,
 * its index in window.frames, or the iframe's name or id.
 */
function switchToFrame(msg) {
  let command_id = msg.json.command_id;
  let foundFrame = null;
  let frames = [];
  let parWindow = null;

  // Check of the curContainer.frame reference is dead
  try {
    frames = curContainer.frame.frames;
    // Until Bug 761935 lands, we won't have multiple nested OOP
    // iframes. We will only have one.  parWindow will refer to the iframe
    // above the nested OOP frame.
    parWindow = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  } catch (e) {
    // We probably have a dead compartment so accessing it is going to
    // make Firefox very upset. Let's now try redirect everything to the
    // top frame even if the user has given us a frame since search doesnt
    // look up.
    msg.json.id = null;
    msg.json.element = null;
  }

  if ((msg.json.id === null || msg.json.id === undefined) &&
      (msg.json.element == null)) {
    // returning to root frame
    sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});

    curContainer.frame = content;
    if (msg.json.focus == true) {
      curContainer.frame.focus();
    }

    sendOk(command_id);
    return;
  }

  let id = msg.json.element;
  if (seenEls.has(id)) {
    let wantedFrame;
    try {
      wantedFrame = seenEls.get(id, curContainer);
    } catch (e) {
      sendError(e, command_id);
    }

    if (frames.length > 0) {
      for (let i = 0; i < frames.length; i++) {
        // use XPCNativeWrapper to compare elements; see bug 834266
        let frameEl = frames[i].frameElement;
        let wrappedItem = new XPCNativeWrapper(frameEl);
        let wrappedWanted = new XPCNativeWrapper(wantedFrame);
        if (wrappedItem == wrappedWanted) {
          curContainer.frame = frameEl;
          foundFrame = i;
        }
      }
    }

    if (foundFrame === null) {
      // Either the frame has been removed or we have a OOP frame
      // so lets just get all the iframes and do a quick loop before
      // throwing in the towel
      const doc = curContainer.frame.document;
      let iframes = doc.getElementsByTagName("iframe");
      for (var i = 0; i < iframes.length; i++) {
        let frameEl = iframes[i];
        let wrappedEl = new XPCNativeWrapper(frameEl);
        let wrappedWanted = new XPCNativeWrapper(wantedFrame);
        if (wrappedEl == wrappedWanted) {
          curContainer.frame = iframes[i];
          foundFrame = i;
        }
      }
    }
  }

  if (foundFrame === null) {
    if (typeof(msg.json.id) === "number") {
      try {
        foundFrame = frames[msg.json.id].frameElement;
        if (foundFrame !== null) {
          curContainer.frame = foundFrame;
          foundFrame = seenEls.add(curContainer.frame);
        } else {
          // If foundFrame is null at this point then we have the top
          // level browsing context so should treat it accordingly.
          sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
          curContainer.frame = content;

          if (msg.json.focus == true) {
            curContainer.frame.focus();
          }

          sendOk(command_id);
          return;
        }
      } catch (e) {
        // Since window.frames does not return OOP frames it will throw
        // and we land up here. Let's not give up and check if there are
        // iframes and switch to the indexed frame there
        let doc = curContainer.frame.document;
        let iframes = doc.getElementsByTagName("iframe");
        if (msg.json.id >= 0 && msg.json.id < iframes.length) {
          curContainer.frame = iframes[msg.json.id];
          foundFrame = msg.json.id;
        }
      }
    }
  }

  if (foundFrame === null) {
    let failedFrame = msg.json.id || msg.json.element;
    let err = new NoSuchFrameError(`Unable to locate frame: ${failedFrame}`);
    sendError(err, command_id);
    return;
  }

  // send a synchronous message to let the server update the currently active
  // frame element (for getActiveFrame)
  let frameValue = evaluate.toJSON(
      curContainer.frame.wrappedJSObject, seenEls)[element.Key];
  sendSyncMessage("Marionette:switchedToFrame", {"frameValue": frameValue});

  if (curContainer.frame.contentWindow === null) {
    // The frame we want to switch to is a remote/OOP frame;
    // notify our parent to handle the switch
    curContainer.frame = content;
    let rv = {win: parWindow, frame: foundFrame};
    sendResponse(rv, command_id);

  } else {
    curContainer.frame = curContainer.frame.contentWindow;

    if (msg.json.focus) {
      curContainer.frame.focus();
    }

    sendOk(command_id);
  }
}

/**
 * Perform a screen capture in content context.
 *
 * Accepted values for |opts|:
 *
 *     @param {UUID=} id
 *         Optional web element reference of an element to take a screenshot
 *         of.
 *     @param {boolean=} full
 *         True to take a screenshot of the entire document element.  Is not
 *         considered if {@code id} is not defined.  Defaults to true.
 *     @param {Array.<UUID>=} highlights
 *         Draw a border around the elements found by their web element
 *         references.
 *     @param {boolean=} scroll
 *         When |id| is given, scroll it into view before taking the
 *         screenshot.  Defaults to true.
 *
 * @param {capture.Format} format
 *     Format to return the screenshot in.
 * @param {Object.<string, ?>} opts
 *     Options.
 *
 * @return {string}
 *     Base64 encoded string or a SHA-256 hash of the screenshot.
 */
function takeScreenshot(format, opts = {}) {
  let id = opts.id;
  let full = !!opts.full;
  let highlights = opts.highlights || [];
  let scroll = !!opts.scroll;

  let highlightEls = highlights.map(ref => seenEls.get(ref, curContainer));

  let canvas;

  // viewport
  if (!id && !full) {
    canvas = capture.viewport(curContainer.frame, highlightEls);

  // element or full document element
  } else {
    let el;
    if (id) {
      el = seenEls.get(id, curContainer);
      if (scroll) {
        element.scrollIntoView(el);
      }
    } else {
      el = curContainer.frame.document.documentElement;
    }

    canvas = capture.element(el, highlightEls);
  }

  switch (format) {
    case capture.Format.Base64:
      return capture.toBase64(canvas);

    case capture.Format.Hash:
      return capture.toHash(canvas);

    default:
      throw new TypeError("Unknown screenshot format: " + format);
  }
}

function flushRendering() {
  let content = curContainer.frame;
  let anyPendingPaintsGeneratedInDescendants = false;

  let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIDOMWindowUtils);

  function flushWindow(win) {
    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils);
    let afterPaintWasPending = utils.isMozAfterPaintPending;

    let root = win.document.documentElement;
    if (root) {
      try {
        // Flush pending restyles and reflows for this window
        root.getBoundingClientRect();
      } catch (e) {
        logger.warning(`flushWindow failed: ${e}`);
      }
    }

    if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
      anyPendingPaintsGeneratedInDescendants = true;
    }

    for (let i = 0; i < win.frames.length; ++i) {
      flushWindow(win.frames[i]);
    }
  }
  flushWindow(content);

  if (anyPendingPaintsGeneratedInDescendants &&
      !windowUtils.isMozAfterPaintPending) {
    logger.error("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!");
  }

  logger.debug(`flushRendering ${windowUtils.isMozAfterPaintPending}`);
  return windowUtils.isMozAfterPaintPending;
}

function* reftestWait(url, remote) {
  let win = curContainer.frame;
  let document = curContainer.frame.document;

  let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils);


  let reftestWait = false;

  if (document.location.href !== url || document.readyState != "complete") {
    logger.debug(`Waiting for page load of ${url}`);
    yield new Promise(resolve => {
      let maybeResolve = (event) => {
        if (event.target === curContainer.frame.document &&
            event.target.location.href === url) {
          win = curContainer.frame;
          document = curContainer.frame.document;
          reftestWait = document.documentElement.classList.contains("reftest-wait");
          removeEventListener("load", maybeResolve, {once: true});
          win.setTimeout(resolve, 0);
        }
      };
      addEventListener("load", maybeResolve, true);
    });
  } else {
    // Ensure that the event loop has spun at least once since load,
    // so that setTimeout(fn, 0) in the load event has run
    reftestWait = document.documentElement.classList.contains("reftest-wait");
    yield new Promise(resolve => win.setTimeout(resolve, 0));
  }

  let root = document.documentElement;
  if (reftestWait) {
    // Check again in case reftest-wait was removed since the load event
    if (root.classList.contains("reftest-wait")) {
      logger.debug("Waiting for reftest-wait removal");
      yield new Promise(resolve => {
        let observer = new win.MutationObserver(() => {
          if (!root.classList.contains("reftest-wait")) {
            observer.disconnect();
            logger.debug("reftest-wait removed");
            win.setTimeout(resolve, 0);
          }
        });
        observer.observe(root, {attributes: true});
      });
    }

    logger.debug("Waiting for rendering");

    yield new Promise(resolve => {
      let maybeResolve = () => {
        if (flushRendering()) {
          win.addEventListener("MozAfterPaint", maybeResolve, {once: true});
        } else {
          win.setTimeout(resolve, 0);
        }
      };
      maybeResolve();
    });
  } else {
    flushRendering();
  }

  if (remote) {
    windowUtils.updateLayerTree();
  }
}

// Call register self when we get loaded
registerSelf();
PK
!<ӠM!M!$chrome/marionette/content/message.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 {utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Task.jsm");

Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/error.js");

this.EXPORTED_SYMBOLS = [
  "Command",
  "Message",
  "MessageOrigin",
  "Response",
];

const logger = Log.repository.getLogger("Marionette");

/**
 * Messages may originate from either the server or the client.
 * Because the remote protocol is full duplex, both endpoints may be the
 * origin of both commands and responses.
 *
 * @enum
 * @see {@link Message}
 */
const MessageOrigin = {
  /** Indicates that the message originates from the client. */
  Client: 0,
  /** Indicates that the message originates from the server. */
  Server: 1,
};

/**
 * Representation of the packets transproted over the wire.
 *
 * @class
 */
this.Message = {};

/**
 * Converts a data packet into a Command or Response type.
 *
 * @param {Array.<number, number, ?, ?>} data
 *     A four element array where the elements, in sequence, signifies
 *     message type, message ID, method name or error, and parameters
 *     or result.
 *
 * @return {Message}
 *     Based on the message type, a {@link Command} or {@link Response}
 *     instance.
 *
 * @throws {TypeError}
 *     If the message type is not recognised.
 */
Message.fromMsg = function(data) {
  switch (data[0]) {
    case Command.TYPE:
      return Command.fromMsg(data);

    case Response.TYPE:
      return Response.fromMsg(data);

    default:
      throw new TypeError(
          "Unrecognised message type in packet: " + JSON.stringify(data));
  }
};

/**
 * A command is a request from the client to run a series of remote end
 * steps and return a fitting response.
 *
 * The command can be synthesised from the message passed over the
 * Marionette socket using the {@code fromMsg} function.  The format of
 * a message is:
 *
 *     [type, id, name, params]
 *
 * where
 *
 *   type (integer)
 *     Must be zero (integer). Zero means that this message is a command.
 *
 *   id (integer)
 *     Integer used as a sequence number.  The server replies with the
 *     same ID for the response.
 *
 *   name (string)
 *     String representing the command name with an associated set of
 *     remote end steps.
 *
 *   params (JSON Object or null)
 *     Object of command function arguments.  The keys of this object
 *     must be strings, but the values can be arbitrary values.
 *
 * A command has an associated message {@code id} that prevents the
 * dispatcher from sending responses in the wrong order.
 *
 * The command may also have optional error- and result handlers that
 * are called when the client returns with a response.  These are
 * {@code function onerror({Object})}, {@code function onresult({Object})},
 * and {@code function onresult({Response})}.
 *
 * @param {number} msgId
 *     Message ID unique identifying this message.
 * @param {string} name
 *     Command name.
 * @param {Object.<string, ?>} params
 *     Command parameters.
 */
class Command {
  constructor(msgID, name, params = {}) {
    this.id = assert.integer(msgID);
    this.name = assert.string(name);
    this.parameters = assert.object(params);

    this.onerror = null;
    this.onresult = null;

    this.origin = MessageOrigin.Client;
    this.sent = false;
  }

  /**
   * Calls the error- or result handler associated with this command.
   * This function can be replaced with a custom response handler.
   *
   * @param {Response} resp
   *     The response to pass on to the result or error to the
   *     {@code onerror} or {@code onresult} handlers to.
   */
  onresponse(resp) {
    if (this.onerror && resp.error) {
      this.onerror(resp.error);
    } else if (this.onresult && resp.body) {
      this.onresult(resp.body);
    }
  }

  toMsg() {
    return [Command.TYPE, this.id, this.name, this.parameters];
  }

  toString() {
    return "Command {id: " + this.id + ", " +
        "name: " + JSON.stringify(this.name) + ", " +
        "parameters: " + JSON.stringify(this.parameters) + "}";
  }

  static fromMsg(msg) {
    let [type, msgID, name, params] = msg;
    assert.that(n => n === Command.TYPE)(type);

    // if parameters are given but null, treat them as undefined
    if (params === null) {
      params = undefined;
    }

    return new Command(msgID, name, params);
  }
}

Command.TYPE = 0;

const validator = {
  exclusionary: {
    "capabilities": ["error", "value"],
    "error": ["value", "sessionId", "capabilities"],
    "sessionId": ["error", "value"],
    "value": ["error", "sessionId", "capabilities"],
  },

  set(obj, prop, val) {
    let tests = this.exclusionary[prop];
    if (tests) {
      for (let t of tests) {
        if (obj.hasOwnProperty(t)) {
          throw new TypeError(`${t} set, cannot set ${prop}`);
        }
      }
    }

    obj[prop] = val;
    return true;
  },
};

/**
 * The response body is exposed as an argument to commands.
 * Commands can set fields on the body through defining properties.
 *
 * Setting properties invokes a validator that performs tests for
 * mutually exclusionary fields on the input against the existing data
 * in the body.
 *
 * For example setting the {@code error} property on the body when
 * {@code value}, {@code sessionId}, or {@code capabilities} have been
 * set previously will cause an error.
 */
const ResponseBody = () => new Proxy({}, validator);

/**
 * @callback ResponseCallback
 *
 * @param {Response} resp
 *     Response to handle.
 */

/**
 * Represents the response returned from the remote end after execution
 * of its corresponding command.
 *
 * The response is a mutable object passed to each command for
 * modification through the available setters.  To send data in a response,
 * you modify the body property on the response.  The body property can
 * also be replaced completely.
 *
 * The response is sent implicitly by CommandProcessor when a command
 * has finished executing, and any modifications made subsequent to that
 * will have no effect.
 *
 * @param {number} msgID
 *     Message ID tied to the corresponding command request this is a
 *     response for.
 * @param {ResponseHandler} respHandler
 *     Function callback called on sending the response.
 */
class Response {
  constructor(msgID, respHandler = () => {}) {
    this.id = assert.integer(msgID);
    this.respHandler_ = assert.callable(respHandler);

    this.error = null;
    this.body = ResponseBody();

    this.origin = MessageOrigin.Server;
    this.sent = false;
  }

  /**
   * Sends response conditionally, given a predicate.
   *
   * @param {function(Response): boolean} predicate
   *     A predicate taking a Response object and returning a boolean.
   */
  sendConditionally(predicate) {
    if (predicate(this)) {
      this.send();
    }
  }

  /**
   * Sends response using the response handler provided on construction.
   *
   * @throws {RangeError}
   *     If the response has already been sent.
   */
  send() {
    if (this.sent) {
      throw new RangeError("Response has already been sent: " + this);
    }
    this.respHandler_(this);
    this.sent = true;
  }

  /**
   * Send given Error to client.
   *
   * Turns the response into an error response, clears any previously
   * set body data, and sends it using the response handler provided
   * on construction.
   *
   * @param {Error} err
   *     The Error instance to send.
   *
   * @throws {Error}
   *     If the {@code error} is not a WebDriverError, the error is
   *     propagated.
   */
  sendError(err) {
    this.error = error.wrap(err).toJSON();
    this.body = null;
    this.send();

    // propagate errors which are implementation problems
    if (!error.isWebDriverError(err)) {
      throw err;
    }
  }

  toMsg() {
    return [Response.TYPE, this.id, this.error, this.body];
  }

  toString() {
    return "Response {id: " + this.id + ", " +
        "error: " + JSON.stringify(this.error) + ", " +
        "body: " + JSON.stringify(this.body) + "}";
  }

  static fromMsg(msg) {
    let [type, msgID, err, body] = msg;
    assert.that(n => n === Response.TYPE)(type);

    let resp = new Response(msgID);
    resp.error = assert.string(err);

    resp.body = body;
    return resp;
  }
}

Response.TYPE = 1;
PK
!<"chrome/marionette/content/modal.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");

this.EXPORTED_SYMBOLS = ["modal"];

const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";

const isFirefox = () => Services.appinfo.name == "Firefox";

/** @namespace */
this.modal = {
  COMMON_DIALOG_LOADED: "common-dialog-loaded",
  TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
  handlers: {
    "common-dialog-loaded": new Set(),
    "tabmodal-dialog-loaded": new Set(),
  },
};

/**
 * Add handler that will be called when a global- or tab modal dialogue
 * appears.
 *
 * This is achieved by installing observers for common-
 * and tab modal loaded events.
 *
 * This function is a no-op if called on any other product than Firefox.
 *
 * @param {function(Object, string)} handler
 *     The handler to be called, which is passed the
 *     subject (e.g. ChromeWindow) and the topic (one of
 *     {@code modal.COMMON_DIALOG_LOADED} or
 *     {@code modal.TABMODAL_DIALOG_LOADED}.
 */
modal.addHandler = function(handler) {
  if (!isFirefox()) {
    return;
  }

  Object.keys(this.handlers).map(topic => {
    this.handlers[topic].add(handler);
    Services.obs.addObserver(handler, topic);
  });
};

/**
 * Check for already existing modal or tab modal dialogs
 *
 * @param {browser.Context} context
 *     Reference to the browser context to check for existent dialogs.
 *
 * @return {modal.Dialog}
 *     Returns instance of the Dialog class, or `null` if no modal dialog
 *     is present.
 */
modal.findModalDialogs = function(context) {
  // First check if there is a modal dialog already present for the
  // current browser window.
  let winEn = Services.wm.getEnumerator(null);
  while (winEn.hasMoreElements()) {
    let win = winEn.getNext();

    // Modal dialogs which do not have an opener set, we cannot detect
    // as long as GetZOrderDOMWindowEnumerator doesn't work on Linux
    // (Bug 156333).
    if (win.document.documentURI === COMMON_DIALOG &&
        win.opener && win.opener === context.window) {
      return new modal.Dialog(() => context, Cu.getWeakReference(win));
    }
  }

  // If no modal dialog has been found, also check if there is an open
  // tab modal dialog present for the current tab.
  // TODO: Find an adequate implementation for Fennec.
  if (context.tab && context.tabBrowser.getTabModalPromptBox) {
    let contentBrowser = context.contentBrowser;
    let promptManager =
        context.tabBrowser.getTabModalPromptBox(contentBrowser);
    let prompts = promptManager.listPrompts();

    if (prompts.length) {
      return new modal.Dialog(() => context, null);
    }
  }

  return null;
};

/**
 * Remove modal dialogue handler by function reference.
 *
 * This function is a no-op if called on any other product than Firefox.
 *
 * @param {function} toRemove
 *     The handler previously passed to modal.addHandler which will now
 *     be removed.
 */
modal.removeHandler = function(toRemove) {
  if (!isFirefox()) {
    return;
  }

  for (let topic of Object.keys(this.handlers)) {
    let handlers = this.handlers[topic];
    for (let handler of handlers) {
      if (handler == toRemove) {
        Services.obs.removeObserver(handler, topic);
        handlers.delete(handler);
      }
    }
  }
};

/**
 * Represents the current modal dialogue.
 *
 * @param {function(): browser.Context} curBrowserFn
 *     Function that returns the current |browser.Context|.
 * @param {nsIWeakReference=} winRef
 *     A weak reference to the current |ChromeWindow|.
 */
modal.Dialog = class {
  constructor(curBrowserFn, winRef = undefined) {
    this.curBrowserFn_ = curBrowserFn;
    this.win_ = winRef;
  }

  get curBrowser_() { return this.curBrowserFn_(); }

  /**
   * Returns the ChromeWindow associated with an open dialog window if
   * it is currently attached to the DOM.
   */
  get window() {
    if (this.win_) {
      let win = this.win_.get();
      if (win && win.parent) {
        return win;
      }
    }
    return null;
  }

  get ui() {
    let win = this.window;
    if (win) {
      return win.Dialog.ui;
    }
    return this.curBrowser_.getTabModalUI();
  }
};
PK
!<W%chrome/marionette/content/navigate.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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.importGlobalProperties(["URL"]);

this.EXPORTED_SYMBOLS = ["navigate"];

/** @namespace */
this.navigate = {};

/**
 * Determines if we expect to get a DOM load event (DOMContentLoaded)
 * on navigating to the <code>future</code> URL.
 *
 * @param {string} current
 *     URL the browser is currently visiting.
 * @param {string=} future
 *     Destination URL, if known.
 *
 * @return {boolean}
 *     Full page load would be expected if future is followed.
 *
 * @throws TypeError
 *     If <code>current</code> is not defined, or any of
 *     <code>current</code> or <code>future</code>  are invalid URLs.
 */
navigate.isLoadEventExpected = function(current, future = undefined) {
  // assume we will go somewhere exciting
  if (typeof current == "undefined") {
    throw TypeError("Expected at least one URL");
  }

  // Assume we will go somewhere exciting
  if (typeof future == "undefined") {
    return true;
  }

  let cur = new URL(current);
  let fut = new URL(future);

  // Assume javascript:<whatever> will modify the current document
  // but this is not an entirely safe assumption to make,
  // considering it could be used to set window.location
  if (fut.protocol == "javascript:") {
    return false;
  }

  // If hashes are present and identical
  if (cur.href.includes("#") && fut.href.includes("#") &&
      cur.hash === fut.hash) {
    return false;
  }

  return true;
};
PK
!<(k+k+$chrome/marionette/content/packets.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Packets contain read / write functionality for the different packet types
 * supported by the debugging protocol, so that a transport can focus on
 * delivery and queue management without worrying too much about the specific
 * packet types.
 *
 * They are intended to be "one use only", so a new packet should be
 * instantiated for each incoming or outgoing packet.
 *
 * A complete Packet type should expose at least the following:
 *   * read(stream, scriptableStream)
 *     Called when the input stream has data to read
 *   * write(stream)
 *     Called when the output stream is ready to write
 *   * get done()
 *     Returns true once the packet is done being read / written
 *   * destroy()
 *     Called to clean up at the end of use
 */

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const {StreamUtils} =
    Cu.import("chrome://marionette/content/stream-utils.js", {});

const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
    .createInstance(Ci.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";

const defer = function() {
  let deferred = {
    promise: new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    }),
  };
  return deferred;
};

this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"];

// The transport's previous check ensured the header length did not
// exceed 20 characters.  Here, we opt for the somewhat smaller, but still
// large limit of 1 TiB.
const PACKET_LENGTH_MAX = Math.pow(2, 40);

/**
 * A generic Packet processing object (extended by two subtypes below).
 *
 * @class
 */
function Packet(transport) {
  this._transport = transport;
  this._length = 0;
}

/**
 * Attempt to initialize a new Packet based on the incoming packet header
 * we've received so far.  We try each of the types in succession, trying
 * JSON packets first since they are much more common.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @return {Packet}
 *     Parsed packet of the matching type, or null if no types matched.
 */
Packet.fromHeader = function(header, transport) {
  return JSONPacket.fromHeader(header, transport) ||
         BulkPacket.fromHeader(header, transport);
};

Packet.prototype = {

  get length() {
    return this._length;
  },

  set length(length) {
    if (length > PACKET_LENGTH_MAX) {
      throw Error("Packet length " + length + " exceeds the max length of " +
                  PACKET_LENGTH_MAX);
    }
    this._length = length;
  },

  destroy() {
    this._transport = null;
  },
};

/**
 * With a JSON packet (the typical packet type sent via the transport),
 * data is transferred as a JSON packet serialized into a string,
 * with the string length prepended to the packet, followed by a colon
 * ([length]:[packet]). The contents of the JSON packet are specified in
 * the Remote Debugging Protocol specification.
 *
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 */
function JSONPacket(transport) {
  Packet.call(this, transport);
  this._data = "";
  this._done = false;
}

/**
 * Attempt to initialize a new JSONPacket based on the incoming packet
 * header we've received so far.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @return {JSONPacket}
 *     Parsed packet, or null if it's not a match.
 */
JSONPacket.fromHeader = function(header, transport) {
  let match = this.HEADER_PATTERN.exec(header);

  if (!match) {
    return null;
  }

  let packet = new JSONPacket(transport);
  packet.length = +match[1];
  return packet;
};

JSONPacket.HEADER_PATTERN = /^(\d+):$/;

JSONPacket.prototype = Object.create(Packet.prototype);

Object.defineProperty(JSONPacket.prototype, "object", {
  /**
   * Gets the object (not the serialized string) being read or written.
   */
  get() {
    return this._object;
  },

  /**
   * Sets the object to be sent when write() is called.
   */
  set(object) {
    this._object = object;
    let data = JSON.stringify(object);
    this._data = unicodeConverter.ConvertFromUnicode(data);
    this.length = this._data.length;
  },
});

JSONPacket.prototype.read = function(stream, scriptableStream) {

  // Read in more packet data.
  this._readData(stream, scriptableStream);

  if (!this.done) {
    // Don't have a complete packet yet.
    return;
  }

  let json = this._data;
  try {
    json = unicodeConverter.ConvertToUnicode(json);
    this._object = JSON.parse(json);
  } catch (e) {
    let msg = "Error parsing incoming packet: " + json + " (" + e +
              " - " + e.stack + ")";
    console.error(msg);
    dump(msg + "\n");
    return;
  }

  this._transport._onJSONObjectReady(this._object);
};

JSONPacket.prototype._readData = function(stream, scriptableStream) {
  let bytesToRead = Math.min(
      this.length - this._data.length,
      stream.available());
  this._data += scriptableStream.readBytes(bytesToRead);
  this._done = this._data.length === this.length;
};

JSONPacket.prototype.write = function(stream) {

  if (this._outgoing === undefined) {
    // Format the serialized packet to a buffer
    this._outgoing = this.length + ":" + this._data;
  }

  let written = stream.write(this._outgoing, this._outgoing.length);
  this._outgoing = this._outgoing.slice(written);
  this._done = !this._outgoing.length;
};

Object.defineProperty(JSONPacket.prototype, "done", {
  get() {
    return this._done;
  },
});

JSONPacket.prototype.toString = function() {
  return JSON.stringify(this._object, null, 2);
};

/**
 * With a bulk packet, data is transferred by temporarily handing over
 * the transport's input or output stream to the application layer for
 * writing data directly.  This can be much faster for large data sets,
 * and avoids various stages of copies and data duplication inherent in
 * the JSON packet type.  The bulk packet looks like:
 *
 *     bulk [actor] [type] [length]:[data]
 *
 * The interpretation of the data portion depends on the kind of actor and
 * the packet's type.  See the Remote Debugging Protocol Stream Transport
 * spec for more details.
 *
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 */
function BulkPacket(transport) {
  Packet.call(this, transport);
  this._done = false;
  this._readyForWriting = defer();
}

/**
 * Attempt to initialize a new BulkPacket based on the incoming packet
 * header we've received so far.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @return {BulkPacket}
 *     Parsed packet, or null if it's not a match.
 */
BulkPacket.fromHeader = function(header, transport) {
  let match = this.HEADER_PATTERN.exec(header);

  if (!match) {
    return null;
  }

  let packet = new BulkPacket(transport);
  packet.header = {
    actor: match[1],
    type: match[2],
    length: +match[3],
  };
  return packet;
};

BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;

BulkPacket.prototype = Object.create(Packet.prototype);

BulkPacket.prototype.read = function(stream) {
  // Temporarily pause monitoring of the input stream
  this._transport.pauseIncoming();

  let deferred = defer();

  this._transport._onBulkReadReady({
    actor: this.actor,
    type: this.type,
    length: this.length,
    copyTo: (output) => {
      let copying = StreamUtils.copyStream(stream, output, this.length);
      deferred.resolve(copying);
      return copying;
    },
    stream,
    done: deferred,
  });

  // Await the result of reading from the stream
  deferred.promise.then(() => {
    this._done = true;
    this._transport.resumeIncoming();
  }, this._transport.close);

  // Ensure this is only done once
  this.read = () => {
    throw new Error("Tried to read() a BulkPacket's stream multiple times.");
  };
};

BulkPacket.prototype.write = function(stream) {
  if (this._outgoingHeader === undefined) {
    // Format the serialized packet header to a buffer
    this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
                           this.length + ":";
  }

  // Write the header, or whatever's left of it to write.
  if (this._outgoingHeader.length) {
    let written = stream.write(this._outgoingHeader,
        this._outgoingHeader.length);
    this._outgoingHeader = this._outgoingHeader.slice(written);
    return;
  }

  // Temporarily pause the monitoring of the output stream
  this._transport.pauseOutgoing();

  let deferred = defer();

  this._readyForWriting.resolve({
    copyFrom: (input) => {
      let copying = StreamUtils.copyStream(input, stream, this.length);
      deferred.resolve(copying);
      return copying;
    },
    stream,
    done: deferred,
  });

  // Await the result of writing to the stream
  deferred.promise.then(() => {
    this._done = true;
    this._transport.resumeOutgoing();
  }, this._transport.close);

  // Ensure this is only done once
  this.write = () => {
    throw new Error("Tried to write() a BulkPacket's stream multiple times.");
  };
};

Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
  get() {
    return this._readyForWriting.promise;
  },
});

Object.defineProperty(BulkPacket.prototype, "header", {
  get() {
    return {
      actor: this.actor,
      type: this.type,
      length: this.length,
    };
  },

  set(header) {
    this.actor = header.actor;
    this.type = header.type;
    this.length = header.length;
  },
});

Object.defineProperty(BulkPacket.prototype, "done", {
  get() {
    return this._done;
  },
});

BulkPacket.prototype.toString = function() {
  return "Bulk: " + JSON.stringify(this.header, null, 2);
};

/**
 * RawPacket is used to test the transport's error handling of malformed
 * packets, by writing data directly onto the stream.
 * @param transport DebuggerTransport
 *        The transport instance that will own the packet.
 * @param data string
 *        The raw string to send out onto the stream.
 */
function RawPacket(transport, data) {
  Packet.call(this, transport);
  this._data = data;
  this.length = data.length;
  this._done = false;
}

RawPacket.prototype = Object.create(Packet.prototype);

RawPacket.prototype.read = function(stream) {
  // This hasn't yet been needed for testing.
  throw Error("Not implmented.");
};

RawPacket.prototype.write = function(stream) {
  let written = stream.write(this._data, this._data.length);
  this._data = this._data.slice(written);
  this._done = !this._data.length;
};

Object.defineProperty(RawPacket.prototype, "done", {
  get() {
    return this._done;
  },
});
PK
!<m3m3"chrome/marionette/content/proxy.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const {
  error,
  WebDriverError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/modal.js");

this.EXPORTED_SYMBOLS = ["proxy"];

const uuidgen = Cc["@mozilla.org/uuid-generator;1"]
    .getService(Ci.nsIUUIDGenerator);

const logger = Log.repository.getLogger("Marionette");

// Proxy handler that traps requests to get a property.  Will prioritise
// properties that exist on the object's own prototype.
var ownPriorityGetterTrap = {
  get: (obj, prop) => {
    if (obj.hasOwnProperty(prop)) {
      return obj[prop];
    }
    return (...args) => obj.send(prop, args);
  },
};

/** @namespace */
this.proxy = {};

/**
 * Creates a transparent interface between the chrome- and content
 * contexts.
 *
 * Calls to this object will be proxied via the message manager to a
 * content frame script, and responses are returend as promises.
 *
 * The argument sequence is serialised and passed as an array, unless it
 * consists of a single object type that isn't null, in which case it's
 * passed literally.  The latter specialisation is temporary to achieve
 * backwards compatibility with listener.js.
 *
 * @param {function(): (nsIMessageSender|nsIMessageBroadcaster)} mmFn
 *     Closure function returning the current message manager.
 * @param {function(string, Object, number)} sendAsyncFn
 *     Callback for sending async messages.
 */
proxy.toListener = function(mmFn, sendAsyncFn, browserFn) {
  let sender = new proxy.AsyncMessageChannel(
      mmFn, sendAsyncFn, browserFn);
  return new Proxy(sender, ownPriorityGetterTrap);
};

/**
 * Provides a transparent interface between chrome- and content space.
 *
 * The AsyncMessageChannel is an abstraction of the message manager
 * IPC architecture allowing calls to be made to any registered message
 * listener in Marionette.  The {@code #send(...)} method returns a promise
 * that gets resolved when the message handler calls {@code .reply(...)}.
 */
proxy.AsyncMessageChannel = class {
  constructor(mmFn, sendAsyncFn, browserFn) {
    this.mmFn_ = mmFn;
    this.sendAsync = sendAsyncFn;
    this.browserFn_ = browserFn;

    // TODO(ato): Bug 1242595
    this.activeMessageId = null;

    this.listeners_ = new Map();
    this.dialogueObserver_ = null;
    this.closeHandler = null;
  }

  get browser() {
    return this.browserFn_();
  }

  get mm() {
    return this.mmFn_();
  }

  /**
   * Send a message across the channel.  The name of the function to
   * call must be registered as a message listener.
   *
   * Usage:
   *
   *     let channel = new AsyncMessageChannel(
   *         messageManager, sendAsyncMessage.bind(this));
   *     let rv = yield channel.send("remoteFunction", ["argument"]);
   *
   * @param {string} name
   *     Function to call in the listener, e.g. for the message listener
   *     "Marionette:foo8", use "foo".
   * @param {Array.<?>=} args
   *     Argument list to pass the function. If args has a single entry
   *     that is an object, we assume it's an old style dispatch, and
   *     the object will passed literally.
   *
   * @return {Promise}
   *     A promise that resolves to the result of the command.
   * @throws {TypeError}
   *     If an unsupported reply type is received.
   * @throws {WebDriverError}
   *     If an error is returned over the channel.
   */
  send(name, args = []) {
    let uuid = uuidgen.generateUUID().toString();
    // TODO(ato): Bug 1242595
    this.activeMessageId = uuid;

    return new Promise((resolve, reject) => {
      let path = proxy.AsyncMessageChannel.makePath(uuid);
      let cb = msg => {
        this.activeMessageId = null;

        switch (msg.json.type) {
          case proxy.AsyncMessageChannel.ReplyType.Ok:
          case proxy.AsyncMessageChannel.ReplyType.Value:
            resolve(msg.json.data);
            break;

          case proxy.AsyncMessageChannel.ReplyType.Error:
            let err = WebDriverError.fromJSON(msg.json.data);
            reject(err);
            break;

          default:
            throw new TypeError(
                `Unknown async response type: ${msg.json.type}`);
        }
      };

      // The currently selected tab or window has been closed. No clean-up
      // is necessary to do because all loaded listeners are gone.
      this.closeHandler = event => {
        logger.debug(`Received DOM event ${event.type} for ${event.target}`);

        switch (event.type) {
          case "TabClose":
          case "unload":
            this.removeHandlers();
            resolve();
            break;
        }
      };

      // A modal or tab modal dialog has been opened. To be able to handle it,
      // the active command has to be aborted. Therefore remove all handlers,
      // and cancel any ongoing requests in the listener.
      this.dialogueObserver_ = (subject, topic) => {
        logger.debug(`Received observer notification "${topic}"`);

        this.removeAllListeners_();
        // TODO(ato): It's not ideal to have listener specific behaviour here:
        this.sendAsync("cancelRequest");

        this.removeHandlers();
        resolve();
      };

      // start content message listener, and install handlers for
      // modal dialogues, and window/tab state changes.
      this.addListener_(path, cb);
      this.addHandlers();

      // sendAsync is GeckoDriver#sendAsync
      this.sendAsync(name, marshal(args), uuid);
    });
  }

  /**
   * Add all necessary handlers for events and observer notifications.
   */
  addHandlers() {
    modal.addHandler(this.dialogueObserver_);

    // Register event handlers in case the command closes the current
    // tab or window, and the promise has to be escaped.
    if (this.browser) {
      this.browser.window.addEventListener("unload", this.closeHandler);

      if (this.browser.tab) {
        let node = this.browser.tab.addEventListener ?
            this.browser.tab : this.browser.contentBrowser;
        node.addEventListener("TabClose", this.closeHandler);
      }
    }
  }

  /**
   * Remove all registered handlers for events and observer notifications.
   */
  removeHandlers() {
    modal.removeHandler(this.dialogueObserver_);

    if (this.browser) {
      this.browser.window.removeEventListener("unload", this.closeHandler);

      if (this.browser.tab) {
        let node = this.browser.tab.addEventListener ?
            this.browser.tab : this.browser.contentBrowser;
        node.removeEventListener("TabClose", this.closeHandler);
      }
    }
  }

  /**
   * Reply to an asynchronous request.
   *
   * Passing an WebDriverError prototype will cause the receiving channel
   * to throw this error.
   *
   * Usage:
   *
   * <pre><code>
   *     let channel = proxy.AsyncMessageChannel(
   *         messageManager, sendAsyncMessage.bind(this));
   *
   *     // throws in requester:
   *     channel.reply(uuid, new WebDriverError());
   *
   *     // returns with value:
   *     channel.reply(uuid, "hello world!");
   *
   *     // returns with undefined:
   *     channel.reply(uuid);
   * </pre></code>
   *
   * @param {UUID} uuid
   *     Unique identifier of the request.
   * @param {*} obj
   *     Message data to reply with.
   */
  reply(uuid, obj = undefined) {
    // TODO(ato): Eventually the uuid will be hidden in the dispatcher
    // in listener, and passing it explicitly to this function will be
    // unnecessary.
    if (typeof obj == "undefined") {
      this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Ok);
    } else if (error.isError(obj)) {
      let err = error.wrap(obj);
      this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Error, err);
    } else {
      this.sendReply_(uuid, proxy.AsyncMessageChannel.ReplyType.Value, obj);
    }
  }

  sendReply_(uuid, type, data = undefined) {
    const path = proxy.AsyncMessageChannel.makePath(uuid);

    let payload;
    if (data && typeof data.toJSON == "function") {
      payload = data.toJSON();
    } else {
      payload = data;
    }

    const msg = {type, data: payload};

    // here sendAsync is actually the content frame's
    // sendAsyncMessage(path, message) global
    this.sendAsync(path, msg);
  }

  /**
   * Produces a path, or a name, for the message listener handler that
   * listens for a reply.
   *
   * @param {UUID} uuid
   *     Unique identifier of the channel request.
   *
   * @return {string}
   *     Path to be used for nsIMessageListener.addMessageListener.
   */
  static makePath(uuid) {
    return "Marionette:asyncReply:" + uuid;
  }

  addListener_(path, callback) {
    let autoRemover = msg => {
      this.removeListener_(path);
      this.removeHandlers();
      callback(msg);
    };

    this.mm.addMessageListener(path, autoRemover);
    this.listeners_.set(path, autoRemover);
  }

  removeListener_(path) {
    if (!this.listeners_.has(path)) {
      return true;
    }

    let l = this.listeners_.get(path);
    this.mm.removeMessageListener(path, l[1]);
    return this.listeners_.delete(path);
  }

  removeAllListeners_() {
    let ok = true;
    for (let [p] of this.listeners_) {
      ok |= this.removeListener_(p);
    }
    return ok;
  }
};
proxy.AsyncMessageChannel.ReplyType = {
  Ok: 0,
  Value: 1,
  Error: 2,
};

/**
 * A transparent content-to-chrome RPC interface where responses are
 * presented as promises.
 *
 * @param {nsIFrameMessageManager} frameMessageManager
 *     The content frame's message manager, which itself is usually an
 *     implementor of.
 */
proxy.toChromeAsync = function(frameMessageManager) {
  let sender = new AsyncChromeSender(frameMessageManager);
  return new Proxy(sender, ownPriorityGetterTrap);
};

/**
 * Sends asynchronous RPC messages to chrome space using a frame's
 * sendAsyncMessage (nsIAsyncMessageSender) function.
 *
 * Example on how to use from a frame content script:
 *
 *     let sender = new AsyncChromeSender(messageManager);
 *     let promise = sender.send("runEmulatorCmd", "my command");
 *     let rv = yield promise;
 */
class AsyncChromeSender {
  constructor(frameMessageManager) {
    this.mm = frameMessageManager;
  }

  /**
   * Call registered function in chrome context.
   *
   * @param {string} name
   *     Function to call in the chrome, e.g. for "Marionette:foo", use
   *     "foo".
   * @param {*} args
   *     Argument list to pass the function.  Must be JSON serialisable.
   *
   * @return {Promise}
   *     A promise that resolves to the value sent back.
   */
  send(name, args) {
    let uuid = uuidgen.generateUUID().toString();

    let proxy = new Promise((resolve, reject) => {
      let responseListener = msg => {
        if (msg.json.id != uuid) {
          return;
        }

        this.mm.removeMessageListener(
            "Marionette:listenerResponse", responseListener);

        if ("value" in msg.json) {
          resolve(msg.json.value);
        } else if ("error" in msg.json) {
          reject(msg.json.error);
        } else {
          throw new TypeError(
              `Unexpected response: ${msg.name} ${JSON.stringify(msg.json)}`);
        }
      };

      let msg = {arguments: marshal(args), id: uuid};
      this.mm.addMessageListener(
          "Marionette:listenerResponse", responseListener);
      this.mm.sendAsyncMessage("Marionette:" + name, msg);
    });

    return proxy;
  }
}

/**
 * Creates a transparent interface from the content- to the chrome context.
 *
 * Calls to this object will be proxied via the frame's sendSyncMessage
 * (nsISyncMessageSender) function.  Since the message is synchronous,
 * the return value is presented as a return value.
 *
 * Example on how to use from a frame content script:
 *
 *     let chrome = proxy.toChrome(sendSyncMessage.bind(this));
 *     let cookie = chrome.getCookie("foo");
 *
 * @param {nsISyncMessageSender} sendSyncMessageFn
 *     The frame message manager's sendSyncMessage function.
 */
proxy.toChrome = function(sendSyncMessageFn) {
  let sender = new proxy.SyncChromeSender(sendSyncMessageFn);
  return new Proxy(sender, ownPriorityGetterTrap);
};

/**
 * The SyncChromeSender sends synchronous RPC messages to the chrome
 * context, using a frame's sendSyncMessage (nsISyncMessageSender)
 * function.
 *
 * Example on how to use from a frame content script:
 *
 *     let sender = new SyncChromeSender(sendSyncMessage.bind(this));
 *     let res = sender.send("addCookie", cookie);
 */
proxy.SyncChromeSender = class {
  constructor(sendSyncMessage) {
    this.sendSyncMessage_ = sendSyncMessage;
  }

  send(func, args) {
    let name = "Marionette:" + func.toString();
    return this.sendSyncMessage_(name, marshal(args));
  }
};

var marshal = function(args) {
  if (args.length == 1 && typeof args[0] == "object") {
    return args[0];
  }
  return args;
};
PK
!<Ou..$chrome/marionette/content/reftest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");

Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/capture.js");
const {InvalidArgumentError} =
    Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["reftest"];

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_E10S = "browser.tabs.remote.autostart";

const logger = Log.repository.getLogger("Marionette");

const SCREENSHOT_MODE = {
  unexpected: 0,
  fail: 1,
  always: 2,
};

const STATUS = {
  PASS: "PASS",
  FAIL: "FAIL",
  ERROR: "ERROR",
  TIMEOUT: "TIMEOUT",
};

/**
 * Implements an fast runner for web-platform-tests format reftests
 * c.f. http://web-platform-tests.org/writing-tests/reftests.html.
 *
 * @namespace
 */
this.reftest = {};

/**
 * @memberof reftest
 * @class Runner
 */
reftest.Runner = class {
  constructor(driver) {
    this.driver = driver;
    this.canvasCache = new Map([[null, []]]);
    this.windowUtils = null;
    this.lastURL = null;
    this.remote = Preferences.get(PREF_E10S);
  }

  /**
   * Setup the required environment for running reftests.
   *
   * This will open a non-browser window in which the tests will
   * be loaded, and set up various caches for the reftest run.
   *
   * @param {Object.<Number>} urlCount
   *     Object holding a map of URL: number of times the URL
   *     will be opened during the reftest run, where that's
   *     greater than 1.
   * @param {string} screenshotMode
   *     String enum representing when screenshots should be taken
   */
  *setup(urlCount, screenshotMode) {
    this.parentWindow =  assert.window(this.driver.getCurrentWindow());

    this.screenshotMode = SCREENSHOT_MODE[screenshotMode] ||
        SCREENSHOT_MODE["unexpected"];

    this.urlCount = Object.keys(urlCount || {})
        .reduce((map, key) => map.set(key, urlCount[key]), new Map());

    yield this.ensureWindow();
  }

  *ensureWindow() {
    if (this.reftestWin && !this.reftestWin.closed) {
      return this.reftestWin;
    }

    let reftestWin = yield this.openWindow();

    let found = this.driver.findWindow([reftestWin], () => true);
    yield this.driver.setWindowHandle(found, true);

    this.windowUtils = reftestWin.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils);
    this.reftestWin = reftestWin;
    return reftestWin;
  }

  *openWindow() {
    let reftestWin;
    yield new Promise(resolve => {
      reftestWin = this.parentWindow.openDialog(
          "chrome://marionette/content/reftest.xul",
          "reftest",
          "chrome,dialog,height=600,width=600,all",
          () => resolve());
    });

    let browser = reftestWin.document.createElementNS(XUL_NS, "xul:browser");
    browser.permanentKey = {};
    browser.setAttribute("id", "browser");
    browser.setAttribute("anonid", "initialBrowser");
    browser.setAttribute("type", "content");
    browser.setAttribute("primary", "true");

    if (this.remote) {
      browser.setAttribute("remote", "true");
      browser.setAttribute("remoteType", "web");
    }
    // Make sure the browser element is exactly 600x600, no matter
    // what size our window is
    const window_style = `padding: 0px; margin: 0px; border:none;
min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
    browser.setAttribute("style", window_style);

    let doc = reftestWin.document.documentElement;
    while (doc.firstChild) {
      doc.firstChild.remove();
    }
    doc.appendChild(browser);
    reftestWin.gBrowser = browser;

    return reftestWin;
  }

  abort() {
    if (this.reftestWin) {
      this.driver.close();
    }
    this.reftestWin = null;
  }

  /**
   * Run a specific reftest.
   *
   * The assumed semantics are those of web-platform-tests where
   * references form a tree and each test must meet all the conditions
   * to reach one leaf node of the tree in order for the overall test
   * to pass.
   *
   * @param {string} testUrl
   *     URL of the test itself.
   * @param {Array.<Array>} references
   *     Array representing a tree of references to try. Each item in
   *     the array represents a single reference node and has the form
   *     [referenceUrl, references, relation], where referenceUrl is a
   *     string to the url, relation is either "==" or "!=" depending on
   *     the type of reftest, and references is another array containing
   *     items of the same form, representing further comparisons treated
   *     as AND with the current item. Sibling entries are treated as OR.
   *     For example with testUrl of T:
   *       references = [[A, [[B, [], ==]], ==]]
   *       Must have T == A AND A == B to pass
   *
   *       references = [[A, [], ==], [B, [], !=]
   *       Must have T == A OR T != B
   *
   *       references = [[A, [[B, [], ==], [C, [], ==]], ==], [D, [], ]]
   *       Must have (T == A AND A == B) OR (T == A AND A == C) OR (T == D)
   * @param {string} expected
   *     Expected test outcome (e.g. PASS, FAIL).
   * @param {number} timeout
   *     Test timeout in ms
   *
   * @return {Object}
   *     Result object with fields status, message and extra.
   */
  *run(testUrl, references, expected, timeout) {

    let timeoutHandle;

    let timeoutPromise = new Promise(resolve => {
      timeoutHandle = this.parentWindow.setTimeout(() => {
        resolve({status: STATUS.TIMEOUT, message: null, extra: {}});
      }, timeout);
    });

    let testRunner = Task.spawn(function*() {
      let result;
      try {
        result = yield this.runTest(testUrl, references, expected, timeout);
      } catch (e) {
        result = {status: STATUS.ERROR, message: e.stack, extra: {}};
      }
      return result;
    }.bind(this));

    let result = yield Promise.race([testRunner, timeoutPromise]);
    this.parentWindow.clearTimeout(timeoutHandle);
    if (result.status === STATUS.TIMEOUT) {
      this.abort();
    }

    return result;
  }

  *runTest(testUrl, references, expected, timeout) {
    let win = yield this.ensureWindow();

    function toBase64(screenshot) {
      let dataURL = screenshot.canvas.toDataURL();
      return dataURL.split(",")[1];
    }

    win.innerWidth = 600;
    win.innerHeight = 600;

    let message = "";

    let screenshotData = [];

    let stack = [];
    for (let i = references.length - 1; i >= 0; i--) {
      let item = references[i];
      stack.push([testUrl, item[0], item[1], item[2]]);
    }

    let status = STATUS.FAIL;

    while (stack.length) {
      let [lhsUrl, rhsUrl, references, relation] = stack.pop();
      message += `Testing ${lhsUrl} ${relation} ${rhsUrl}\n`;

      let comparison = yield this.compareUrls(
          win, lhsUrl, rhsUrl, relation, timeout);

      function recordScreenshot() {
        let encodedLHS = toBase64(comparison.lhs);
        let encodedRHS = toBase64(comparison.rhs);
        screenshotData.push([{url: lhsUrl, screenshot: encodedLHS},
          relation,
          {url: rhsUrl, screenshot: encodedRHS}]);
      }

      if (this.screenshotMode === SCREENSHOT_MODE.always) {
        recordScreenshot();
      }

      if (comparison.passed) {
        if (references.length) {
          for (let i = references.length - 1; i >= 0; i--) {
            let item = references[i];
            stack.push([testUrl, item[0], item[1], item[2]]);
          }
        } else {
          // Reached a leaf node so all of one reference chain passed
          status = STATUS.PASS;
          if (this.screenshotMode <= SCREENSHOT_MODE.fail &&
              expected != status) {
            recordScreenshot();
          }
          break;
        }
      } else if (!stack.length) {
        // If we don't have any alternatives to try then this will be
        // the last iteration, so save the failing screenshots if required.
        let isFail = this.screenshotMode === SCREENSHOT_MODE.fail;
        let isUnexpected = this.screenshotMode === SCREENSHOT_MODE.unexpected;
        if (isFail || (isUnexpected && expected != status)) {
          recordScreenshot();
        }
      }

      // Return any reusable canvases to the pool
      let canvasPool = this.canvasCache.get(null);
      [comparison.lhs, comparison.rhs].map(screenshot => {
        if (screenshot.reuseCanvas) {
          canvasPool.push(screenshot.canvas);
        }
      });
      logger.debug(`Canvas pool is of length ${canvasPool.length}`);
    }

    let result = {status, message, extra: {}};
    if (screenshotData.length) {
      // For now the tbpl formatter only accepts one screenshot, so just
      // return the last one we took.
      let lastScreenshot = screenshotData[screenshotData.length - 1];
      result.extra.reftest_screenshots = lastScreenshot;
    }

    return result;
  }

  *compareUrls(win, lhsUrl, rhsUrl, relation, timeout) {
    logger.info(`Testing ${lhsUrl} ${relation} ${rhsUrl}`);

    // Take the reference screenshot first so that if we pause
    // we see the test rendering
    let rhs = yield this.screenshot(win, rhsUrl, timeout);
    let lhs = yield this.screenshot(win, lhsUrl, timeout);

    let maxDifferences = {};

    let differences = this.windowUtils.compareCanvases(
        lhs.canvas, rhs.canvas, maxDifferences);

    let passed;
    switch (relation) {
      case "==":
        passed = differences === 0;
        if (!passed) {
          logger.info(`Found ${differences} pixels different, ` +
              `maximum difference per channel ${maxDifferences.value}`);
        }
        break;

      case "!=":
        passed = differences !== 0;
        break;

      default:
        throw new InvalidArgumentError("Reftest operator should be '==' or '!='");
    }

    return {lhs, rhs, passed};
  }

  *screenshot(win, url, timeout) {
    let canvas = null;
    let remainingCount = this.urlCount.get(url) || 1;
    let cache = remainingCount > 1;
    logger.debug(`screenshot ${url} remainingCount: ` +
        `${remainingCount} cache: ${cache}`);
    let reuseCanvas = false;
    if (this.canvasCache.has(url)) {
      logger.debug(`screenshot ${url} taken from cache`);
      canvas = this.canvasCache.get(url);
      if (!cache) {
        this.canvasCache.delete(url);
      }
    } else {
      let canvases = this.canvasCache.get(null);
      if (canvases.length) {
        canvas = canvases.pop();
      } else {
        canvas = null;
      }
      reuseCanvas = !cache;

      let ctxInterface = win.CanvasRenderingContext2D;
      let flags = ctxInterface.DRAWWINDOW_DRAW_CARET |
          ctxInterface.DRAWWINDOW_USE_WIDGET_LAYERS |
          ctxInterface.DRAWWINDOW_DRAW_VIEW;

      logger.debug(`Starting load of ${url}`);
      let navigateOpts = {
        commandId: this.driver.listener.activeMessageId,
        pageTimeout: timeout,
      };
      if (this.lastURL === url) {
        logger.debug(`Refreshing page`);
        yield this.driver.listener.refresh(navigateOpts);
      } else {
        navigateOpts.url = url;
        navigateOpts.loadEventExpected = false;
        yield this.driver.listener.get(navigateOpts);
        this.lastURL = url;
      }

      this.driver.curBrowser.contentBrowser.focus();
      yield this.driver.listener.reftestWait(url, this.remote);

      canvas = capture.canvas(
          win,
          0,  // left
          0,  // top
          win.innerWidth,
          win.innerHeight,
          {canvas, flags});
    }
    if (cache) {
      this.canvasCache.set(url, canvas);
    }
    this.urlCount.set(url, remainingCount - 1);
    return {canvas, reuseCanvas};
  }
};
PK
!<
=+%chrome/marionette/content/reftest.xul<window id="reftest" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="window.arguments[0]()"></window>PK
!<LjqMM#chrome/marionette/content/server.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;

const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    .getService(Ci.mozIJSSubScriptLoader);
const ServerSocket = CC(
    "@mozilla.org/network/server-socket;1",
    "nsIServerSocket",
    "initSpecialConnection");

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/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.import("chrome://marionette/content/assert.js");
const {GeckoDriver} = Cu.import("chrome://marionette/content/driver.js", {});
const {
  error,
  UnknownCommandError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/message.js");
const {DebuggerTransport} =
    Cu.import("chrome://marionette/content/transport.js", {});

XPCOMUtils.defineLazyServiceGetter(
    this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");

const logger = Log.repository.getLogger("Marionette");

const {KeepWhenOffline, LoopbackOnly} = Ci.nsIServerSocket;

this.EXPORTED_SYMBOLS = ["server"];

/** @namespace */
this.server = {};

const PROTOCOL_VERSION = 3;

const ENV_ENABLED = "MOZ_MARIONETTE";

const PREF_CONTENT_LISTENER = "marionette.contentListener";
const PREF_PORT = "marionette.port";
const PREF_RECOMMENDED = "marionette.prefs.recommended";

const NOTIFY_RUNNING = "remote-active";

// Marionette sets preferences recommended for automation when it starts,
// unless |marionette.prefs.recommended| has been set to false.
// Where noted, some prefs should also be set in the profile passed to
// Marionette to prevent them from affecting startup, since some of these
// are checked before Marionette initialises.
const RECOMMENDED_PREFS = new Map([

  // Disable automatic downloading of new releases.
  //
  // This should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["app.update.auto", false],

  // Disable automatically upgrading Firefox.
  //
  // This should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["app.update.enabled", false],

  // Increase the APZ content response timeout in tests to 1 minute.
  // This is to accommodate the fact that test environments tends to be
  // slower than production environments (with the b2g emulator being
  // the slowest of them all), resulting in the production timeout value
  // sometimes being exceeded and causing false-positive test failures.
  //
  // (bug 1176798, bug 1177018, bug 1210465)
  ["apz.content_response_timeout", 60000],

  // Indicate that the download panel has been shown once so that
  // whichever download test runs first doesn't show the popup
  // inconsistently.
  ["browser.download.panel.shown", true],

  // Do not show the EULA notification.
  //
  // This should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.EULA.override", true],

  // Turn off about:newtab and make use of about:blank instead for new
  // opened tabs.
  //
  // This should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.newtabpage.enabled", false],

  // Assume the about:newtab page's intro panels have been shown to not
  // depend on which test runs first and happens to open about:newtab
  ["browser.newtabpage.introShown", true],

  // Never start the browser in offline mode
  //
  // This should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.offline", false],

  // Background thumbnails in particular cause grief, and disabling
  // thumbnails in general cannot hurt
  ["browser.pagethumbnails.capturing_disabled", true],

  // Disable safebrowsing components.
  //
  // These should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.safebrowsing.blockedURIs.enabled", false],
  ["browser.safebrowsing.downloads.enabled", false],
  ["browser.safebrowsing.malware.enabled", false],
  ["browser.safebrowsing.phishing.enabled", false],

  // Disable updates to search engines.
  //
  // Should be set in profile.
  ["browser.search.update", false],

  // Do not restore the last open set of tabs if the browser has crashed
  ["browser.sessionstore.resume_from_crash", false],

  // Don't check for the default web browser during startup.
  //
  // These should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.shell.checkDefaultBrowser", false],

  // Start with a blank page (about:blank)
  ["browser.startup.page", 0],

  // Do not redirect user when a milstone upgrade of Firefox is detected
  ["browser.startup.homepage_override.mstone", "ignore"],

  // Disable browser animations
  ["toolkit.cosmeticAnimations.enabled", false],

  // Do not allow background tabs to be zombified, otherwise for tests
  // that open additional tabs, the test harness tab itself might get
  // unloaded
  ["browser.tabs.disableBackgroundZombification", false],

  // Do not warn when closing all other open tabs
  ["browser.tabs.warnOnCloseOtherTabs", false],

  // Do not warn when multiple tabs will be opened
  ["browser.tabs.warnOnOpen", false],

  // Disable first run splash page on Windows 10
  ["browser.usedOnWindows10.introURL", ""],

  // Disable the UI tour.
  //
  // Should be set in profile.
  ["browser.uitour.enabled", false],

  // Do not show datareporting policy notifications which can
  // interfere with tests
  [
    "datareporting.healthreport.about.reportUrl",
    "http://%(server)s/dummy/abouthealthreport/",
  ],
  [
    "datareporting.healthreport.documentServerURI",
    "http://%(server)s/dummy/healthreport/",
  ],
  ["datareporting.healthreport.logging.consoleEnabled", false],
  ["datareporting.healthreport.service.enabled", false],
  ["datareporting.healthreport.service.firstRun", false],
  ["datareporting.healthreport.uploadEnabled", false],
  ["datareporting.policy.dataSubmissionEnabled", false],
  ["datareporting.policy.dataSubmissionPolicyAccepted", false],
  ["datareporting.policy.dataSubmissionPolicyBypassNotification", true],

  // Disable popup-blocker
  ["dom.disable_open_during_load", false],

  // Enabling the support for File object creation in the content process
  ["dom.file.createInChild", true],

  // Disable the ProcessHangMonitor
  ["dom.ipc.reportProcessHangs", false],

  // Disable slow script dialogues
  ["dom.max_chrome_script_run_time", 0],
  ["dom.max_script_run_time", 0],

  // Only load extensions from the application and user profile
  // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
  //
  // Should be set in profile.
  ["extensions.autoDisableScopes", 0],
  ["extensions.enabledScopes", 5],

  // Do not block add-ons for e10s
  ["extensions.e10sBlocksEnabling", false],

  // Disable metadata caching for installed add-ons by default
  ["extensions.getAddons.cache.enabled", false],

  // Disable intalling any distribution extensions or add-ons.
  // Should be set in profile.
  ["extensions.installDistroAddons", false],
  ["extensions.showMismatchUI", false],

  // Turn off extension updates so they do not bother tests
  ["extensions.update.enabled", false],
  ["extensions.update.notifyUser", false],

  // Make sure opening about:addons will not hit the network
  [
    "extensions.webservice.discoverURL",
    "http://%(server)s/dummy/discoveryURL",
  ],

  // Allow the application to have focus even it runs in the background
  ["focusmanager.testmode", true],

  // Disable useragent updates
  ["general.useragent.updates.enabled", false],

  // Always use network provider for geolocation tests so we bypass the
  // macOS dialog raised by the corelocation provider
  ["geo.provider.testing", true],

  // Do not scan Wifi
  ["geo.wifi.scan", false],

  // No hang monitor
  ["hangmonitor.timeout", 0],

  // Show chrome errors and warnings in the error console
  ["javascript.options.showInConsole", true],

  // Do not prompt for temporary redirects
  ["network.http.prompt-temp-redirect", false],

  // Disable speculative connections so they are not reported as leaking
  // when they are hanging around
  ["network.http.speculative-parallel-limit", 0],

  // Do not automatically switch between offline and online
  ["network.manage-offline-status", false],

  // Make sure SNTP requests do not hit the network
  ["network.sntp.pools", "%(server)s"],

  // Local documents have access to all other local documents,
  // including directory listings
  ["security.fileuri.strict_origin_policy", false],

  // Tests do not wait for the notification button security delay
  ["security.notification_enable_delay", 0],

  // Ensure blocklist updates do not hit the network
  ["services.settings.server", "http://%(server)s/dummy/blocklist/"],

  // Do not automatically fill sign-in forms with known usernames and
  // passwords
  ["signon.autofillForms", false],

  // Disable password capture, so that tests that include forms are not
  // influenced by the presence of the persistent doorhanger notification
  ["signon.rememberSignons", false],

  // Disable first-run welcome page
  ["startup.homepage_welcome_url", "about:blank"],
  ["startup.homepage_welcome_url.additional", ""],

  // Prevent starting into safe mode after application crashes
  ["toolkit.startup.max_resumed_crashes", -1],

]);

/**
 * Bootstraps Marionette and handles incoming client connections.
 *
 * Starting the Marionette server will open a TCP socket sporting the
 * debugger transport interface on the provided |port|.  For every new
 * connection, a |server.TCPConnection| is created.
 */
server.TCPListener = class {
  /**
   * @param {number} port
   *     Port for server to listen to.
   */
  constructor(port) {
    this.port = port;
    this.socket = null;
    this.conns = new Set();
    this.nextConnID = 0;
    this.alive = false;
    this._acceptConnections = false;
    this.alteredPrefs = new Set();
  }

  /**
   * Function produces a GeckoDriver.
   *
   * Determines application name to initialise the driver with.
   *
   * @return {GeckoDriver}
   *     A driver instance.
   */
  driverFactory() {
    Preferences.set(PREF_CONTENT_LISTENER, false);
    return new GeckoDriver(Services.appinfo.name, this);
  }

  set acceptConnections(value) {
    if (!value) {
      logger.info("New connections will no longer be accepted");
    } else {
      logger.info("New connections are accepted again");
    }

    this._acceptConnections = value;
  }

  /**
   * Bind this listener to |port| and start accepting incoming socket
   * connections on |onSocketAccepted|.
   *
   * The marionette.port preference will be populated with the value
   * of |this.port|.
   */
  start() {
    if (this.alive) {
      return;
    }

    Services.obs.notifyObservers(this, NOTIFY_RUNNING, true);

    if (Preferences.get(PREF_RECOMMENDED)) {
      // set recommended prefs if they are not already user-defined
      for (let [k, v] of RECOMMENDED_PREFS) {
        if (!Preferences.isSet(k)) {
          logger.debug(`Setting recommended pref ${k} to ${v}`);
          Preferences.set(k, v);
          this.alteredPrefs.add(k);
        }
      }
    }

    const flags = KeepWhenOffline | LoopbackOnly;
    const backlog = 1;
    this.socket = new ServerSocket(this.port, flags, backlog);
    this.socket.asyncListen(this);
    this.port = this.socket.port;
    Preferences.set(PREF_PORT, this.port);

    this.alive = true;
    this._acceptConnections = true;
    env.set(ENV_ENABLED, "1");
  }

  stop() {
    if (!this.alive) {
      return;
    }

    this._acceptConnections = false;

    this.socket.close();
    this.socket = null;

    for (let k of this.alteredPrefs) {
      logger.debug(`Resetting recommended pref ${k}`);
      Preferences.reset(k);
    }
    this.alteredPrefs.clear();

    Services.obs.notifyObservers(this, NOTIFY_RUNNING);

    this.alive = false;
  }

  onSocketAccepted(serverSocket, clientSocket) {
    if (!this._acceptConnections) {
      logger.warn("New connections are currently not accepted");
      return;
    }

    let input = clientSocket.openInputStream(0, 0, 0);
    let output = clientSocket.openOutputStream(0, 0, 0);
    let transport = new DebuggerTransport(input, output);

    let conn = new server.TCPConnection(
        this.nextConnID++, transport, this.driverFactory.bind(this));
    conn.onclose = this.onConnectionClosed.bind(this);
    this.conns.add(conn);

    logger.debug(`Accepted connection ${conn.id} ` +
        `from ${clientSocket.host}:${clientSocket.port}`);
    conn.sayHello();
    transport.ready();
  }

  onConnectionClosed(conn) {
    logger.debug(`Closed connection ${conn.id}`);
    this.conns.delete(conn);
  }
};

/**
 * Marionette client connection.
 *
 * Dispatches packets received to their correct service destinations
 * and sends back the service endpoint's return values.
 *
 * @param {number} connID
 *     Unique identifier of the connection this dispatcher should handle.
 * @param {DebuggerTransport} transport
 *     Debugger transport connection to the client.
 * @param {function(): GeckoDriver} driverFactory
 *     Factory function that produces a |GeckoDriver|.
 */
server.TCPConnection = class {
  constructor(connID, transport, driverFactory) {
    this.id = connID;
    this.conn = transport;

    // transport hooks are TCPConnection#onPacket
    // and TCPConnection#onClosed
    this.conn.hooks = this;

    // callback for when connection is closed
    this.onclose = null;

    // last received/sent message ID
    this.lastID = 0;

    this.driver = driverFactory();

    // lookup of commands sent by server to client by message ID
    this.commands_ = new Map();
  }

  /**
   * Debugger transport callback that cleans up
   * after a connection is closed.
   */
  onClosed(reason) {
    this.driver.deleteSession();
    if (this.onclose) {
      this.onclose(this);
    }
  }

  /**
   * Callback that receives data packets from the client.
   *
   * If the message is a Response, we look up the command previously
   * issued to the client and run its callback, if any.  In case of
   * a Command, the corresponding is executed.
   *
   * @param {Array.<number, number, ?, ?>} data
   *     A four element array where the elements, in sequence, signifies
   *     message type, message ID, method name or error, and parameters
   *     or result.
   */
  onPacket(data) {
    // unable to determine how to respond
    if (!Array.isArray(data)) {
      let e = new TypeError(
          "Unable to unmarshal packet data: " + JSON.stringify(data));
      error.report(e);
      return;
    }

    // return immediately with any error trying to unmarshal message
    let msg;
    try {
      msg = Message.fromMsg(data);
      msg.origin = MessageOrigin.Client;
      this.log_(msg);
    } catch (e) {
      let resp = this.createResponse(data[1]);
      resp.sendError(e);
      return;
    }

    // look up previous command we received a response for
    if (msg instanceof Response) {
      let cmd = this.commands_.get(msg.id);
      this.commands_.delete(msg.id);
      cmd.onresponse(msg);

    // execute new command
    } else if (msg instanceof Command) {
      this.lastID = msg.id;
      this.execute(msg);
    }
  }

  /**
   * Executes a WebDriver command and sends back a response when it has
   * finished executing.
   *
   * Commands implemented in GeckoDriver and registered in its
   * {@code GeckoDriver.commands} attribute.  The return values from
   * commands are expected to be Promises.  If the resolved value of said
   * promise is not an object, the response body will be wrapped in
   * an object under a "value" field.
   *
   * If the command implementation sends the response itself by calling
   * {@code resp.send()}, the response is guaranteed to not be sent twice.
   *
   * Errors thrown in commands are marshaled and sent back, and if they
   * are not WebDriverError instances, they are additionally propagated
   * and reported to {@code Components.utils.reportError}.
   *
   * @param {Command} cmd
   *     The requested command to execute.
   */
  execute(cmd) {
    let resp = this.createResponse(cmd.id);
    let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
    let sendError = resp.sendError.bind(resp);

    let req = Task.spawn(function* () {
      let fn = this.driver.commands[cmd.name];
      if (typeof fn == "undefined") {
        throw new UnknownCommandError(cmd.name);
      }

      if (cmd.name !== "newSession") {
        assert.session(this.driver);
      }

      let rv = yield fn.bind(this.driver)(cmd, resp);

      if (typeof rv != "undefined") {
        if (typeof rv != "object") {
          resp.body = {value: rv};
        } else {
          resp.body = rv;
        }
      }
    }.bind(this));

    req.then(sendResponse, sendError).catch(error.report);
  }

  /**
   * Fail-safe creation of a new instance of |message.Response|.
   *
   * @param {number} msgID
   *     Message ID to respond to.  If it is not a number, -1 is used.
   *
   * @return {message.Response}
   *     Response to the message with |msgID|.
   */
  createResponse(msgID) {
    if (typeof msgID != "number") {
      msgID = -1;
    }
    return new Response(msgID, this.send.bind(this));
  }

  sendError(err, cmdID) {
    let resp = new Response(cmdID, this.send.bind(this));
    resp.sendError(err);
  }

  /**
   * When a client connects we send across a JSON Object defining the
   * protocol level.
   *
   * This is the only message sent by Marionette that does not follow
   * the regular message format.
   */
  sayHello() {
    let whatHo = {
      applicationType: "gecko",
      marionetteProtocol: PROTOCOL_VERSION,
    };
    this.sendRaw(whatHo);
  }

  /**
   * Delegates message to client based on the provided  {@code cmdID}.
   * The message is sent over the debugger transport socket.
   *
   * The command ID is a unique identifier assigned to the client's request
   * that is used to distinguish the asynchronous responses.
   *
   * Whilst responses to commands are synchronous and must be sent in the
   * correct order.
   *
   * @param {Message} msg
   *     The command or response to send.
   */
  send(msg) {
    msg.origin = MessageOrigin.Server;
    if (msg instanceof Command) {
      this.commands_.set(msg.id, msg);
      this.sendToEmulator(msg);
    } else if (msg instanceof Response) {
      this.sendToClient(msg);
    }
  }

  // Low-level methods:

  /**
   * Send given response to the client over the debugger transport socket.
   *
   * @param {Response} resp
   *     The response to send back to the client.
   */
  sendToClient(resp) {
    this.driver.responseCompleted();
    this.sendMessage(resp);
  }

  /**
   * Marshal message to the Marionette message format and send it.
   *
   * @param {Message} msg
   *     The message to send.
   */
  sendMessage(msg) {
    this.log_(msg);
    let payload = msg.toMsg();
    this.sendRaw(payload);
  }

  /**
   * Send the given payload over the debugger transport socket to the
   * connected client.
   *
   * @param {Object.<string, ?>} payload
   *     The payload to ship.
   */
  sendRaw(payload) {
    this.conn.send(payload);
  }

  log_(msg) {
    let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
    let s = JSON.stringify(msg.toMsg());
    logger.trace(this.id + a + s);
  }

  toString() {
    return `[object server.TCPConnection ${this.id}]`;
  }
};
PK
!<`8`8$chrome/marionette/content/session.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");

Cu.import("chrome://marionette/content/assert.js");
const {
  error,
  InvalidArgumentError,
} = Cu.import("chrome://marionette/content/error.js", {});

this.EXPORTED_SYMBOLS = ["session"];

const logger = Log.repository.getLogger("Marionette");
const {pprint} = error;

// Enable testing this module, as Services.appinfo.* is not available
// in xpcshell tests.
const appinfo = {name: "<missing>", version: "<missing>"};
try { appinfo.name = Services.appinfo.name.toLowerCase(); } catch (e) {}
try { appinfo.version = Services.appinfo.version; } catch (e) {}

/**
 * State associated with a WebDriver session.
 *
 * @namespace
 */
this.session = {};

/** Representation of WebDriver session timeouts. */
session.Timeouts = class {
  constructor() {
    // disabled
    this.implicit = 0;
    // five mintues
    this.pageLoad = 300000;
    // 30 seconds
    this.script = 30000;
  }

  toString() { return "[object session.Timeouts]"; }

  /** Marshals timeout durations to a JSON Object. */
  toJSON() {
    return {
      implicit: this.implicit,
      pageLoad: this.pageLoad,
      script: this.script,
    };
  }

  static fromJSON(json) {
    assert.object(json);
    let t = new session.Timeouts();

    for (let [typ, ms] of Object.entries(json)) {
      switch (typ) {
        case "implicit":
          t.implicit = assert.positiveInteger(ms);
          break;

        case "script":
          t.script = assert.positiveInteger(ms);
          break;

        case "pageLoad":
          t.pageLoad = assert.positiveInteger(ms);
          break;

        default:
          throw new InvalidArgumentError("Unrecognised timeout: " + typ);
      }
    }

    return t;
  }
};

/**
 * Enum of page loading strategies.
 *
 * @enum
 */
session.PageLoadStrategy = {
  /** No page load strategy.  Navigation will return immediately. */
  None: "none",
  /**
   * Eager, causing navigation to complete when the document reaches
   * the <code>interactive</code> ready state.
   */
  Eager: "eager",
  /**
   * Normal, causing navigation to return when the document reaches the
   * <code>complete</code> ready state.
   */
  Normal: "normal",
};

/** Proxy configuration object representation. */
session.Proxy = class {
  /** @class */
  constructor() {
    this.proxyType = null;
    this.httpProxy = null;
    this.httpProxyPort = null;
    this.sslProxy = null;
    this.sslProxyPort = null;
    this.ftpProxy = null;
    this.ftpProxyPort = null;
    this.socksProxy = null;
    this.socksProxyPort = null;
    this.socksVersion = null;
    this.proxyAutoconfigUrl = null;
  }

  /**
   * Sets Firefox proxy settings.
   *
   * @return {boolean}
   *     True if proxy settings were updated as a result of calling this
   *     function, or false indicating that this function acted as
   *     a no-op.
   */
  init() {
    switch (this.proxyType) {
      case "manual":
        Preferences.set("network.proxy.type", 1);
        if (this.httpProxy && this.httpProxyPort) {
          Preferences.set("network.proxy.http", this.httpProxy);
          Preferences.set("network.proxy.http_port", this.httpProxyPort);
        }
        if (this.sslProxy && this.sslProxyPort) {
          Preferences.set("network.proxy.ssl", this.sslProxy);
          Preferences.set("network.proxy.ssl_port", this.sslProxyPort);
        }
        if (this.ftpProxy && this.ftpProxyPort) {
          Preferences.set("network.proxy.ftp", this.ftpProxy);
          Preferences.set("network.proxy.ftp_port", this.ftpProxyPort);
        }
        if (this.socksProxy) {
          Preferences.set("network.proxy.socks", this.socksProxy);
          Preferences.set("network.proxy.socks_port", this.socksProxyPort);
          if (this.socksVersion) {
            Preferences.set("network.proxy.socks_version", this.socksVersion);
          }
        }
        return true;

      case "pac":
        Preferences.set("network.proxy.type", 2);
        Preferences.set(
            "network.proxy.autoconfig_url", this.proxyAutoconfigUrl);
        return true;

      case "autodetect":
        Preferences.set("network.proxy.type", 4);
        return true;

      case "system":
        Preferences.set("network.proxy.type", 5);
        return true;

      case "noproxy":
        Preferences.set("network.proxy.type", 0);
        return true;

      default:
        return false;
    }
  }

  toString() { return "[object session.Proxy]"; }

  /**
   * @return {Object.<string, (number|string)>}
   *     JSON serialisation of proxy object.
   */
  toJSON() {
    return marshal({
      proxyType: this.proxyType,
      httpProxy: this.httpProxy,
      httpProxyPort: this.httpProxyPort,
      sslProxy: this.sslProxy,
      sslProxyPort: this.sslProxyPort,
      ftpProxy: this.ftpProxy,
      ftpProxyPort: this.ftpProxyPort,
      socksProxy: this.socksProxy,
      socksProxyPort: this.socksProxyPort,
      socksProxyVersion: this.socksProxyVersion,
      proxyAutoconfigUrl: this.proxyAutoconfigUrl,
    });
  }

  /**
   * @param {Object.<string, ?>} json
   *     JSON Object to unmarshal.
   */
  static fromJSON(json) {
    let p = new session.Proxy();
    if (typeof json == "undefined" || json === null) {
      return p;
    }

    assert.object(json);

    assert.in("proxyType", json);
    p.proxyType = json.proxyType;

    if (json.proxyType == "manual") {
      if (typeof json.httpProxy != "undefined") {
        p.httpProxy = assert.string(json.httpProxy);
        p.httpProxyPort = assert.positiveInteger(json.httpProxyPort);
      }

      if (typeof json.sslProxy != "undefined") {
        p.sslProxy = assert.string(json.sslProxy);
        p.sslProxyPort = assert.positiveInteger(json.sslProxyPort);
      }

      if (typeof json.ftpProxy != "undefined") {
        p.ftpProxy = assert.string(json.ftpProxy);
        p.ftpProxyPort = assert.positiveInteger(json.ftpProxyPort);
      }

      if (typeof json.socksProxy != "undefined") {
        p.socksProxy = assert.string(json.socksProxy);
        p.socksProxyPort = assert.positiveInteger(json.socksProxyPort);
        p.socksProxyVersion = assert.positiveInteger(json.socksProxyVersion);
      }
    }

    if (typeof json.proxyAutoconfigUrl != "undefined") {
      p.proxyAutoconfigUrl = assert.string(json.proxyAutoconfigUrl);
    }

    return p;
  }
};

/** WebDriver session capabilities representation. */
session.Capabilities = class extends Map {
  /** @class */
  constructor() {
    super([
      // webdriver
      ["browserName", appinfo.name],
      ["browserVersion", appinfo.version],
      ["platformName", Services.sysinfo.getProperty("name").toLowerCase()],
      ["platformVersion", Services.sysinfo.getProperty("version")],
      ["pageLoadStrategy", session.PageLoadStrategy.Normal],
      ["acceptInsecureCerts", false],
      ["timeouts", new session.Timeouts()],
      ["proxy", new session.Proxy()],

      // features
      ["rotatable", appinfo.name == "B2G"],

      // proprietary
      ["specificationLevel", 0],
      ["moz:processID", Services.appinfo.processID],
      ["moz:profile", maybeProfile()],
      ["moz:accessibilityChecks", false],
      ["moz:headless", Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless],
    ]);
  }

  /**
   * @param {string} key
   *     Capability name.
   * @param {(string|number|boolean)} value
   *     JSON-safe capability value.
   */
  set(key, value) {
    if (key === "timeouts" && !(value instanceof session.Timeouts)) {
      throw new TypeError();
    } else if (key === "proxy" && !(value instanceof session.Proxy)) {
      throw new TypeError();
    }

    return super.set(key, value);
  }

  /** @return {string} */
  toString() { return "[object session.Capabilities]"; }

  /**
   * JSON serialisation of capabilities object.
   *
   * @return {Object.<string, ?>}
   */
  toJSON() {
    return marshal(this);
  }

  /**
   * Unmarshal a JSON object representation of WebDriver capabilities.
   *
   * @param {Object.<string, ?>=} json
   *     WebDriver capabilities.
   * @param {boolean=} merge
   *     If providing <var>json</var> with <tt>desiredCapabilities</tt> or
   *     <tt>requiredCapabilities</tt> fields, or both, it should be
   *     set to true to merge these before parsing.  This indicates that
   *     the input provided is from a client and not from
   *     {@link session.Capabilities#toJSON}.
   *
   * @return {session.Capabilities}
   *     Internal representation of WebDriver capabilities.
   */
  static fromJSON(json, {merge = false} = {}) {
    if (typeof json == "undefined" || json === null) {
      json = {};
    }
    assert.object(json);

    if (merge) {
      json = session.Capabilities.merge_(json);
    }
    return session.Capabilities.match_(json);
  }

  // Processes capabilities as described by WebDriver.
  static merge_(json) {
    for (let entry of [json.desiredCapabilities, json.requiredCapabilities]) {
      if (typeof entry == "undefined" || entry === null) {
        continue;
      }
      assert.object(entry,
          error.pprint`Expected ${entry} to be a capabilities object`);
    }

    let desired = json.desiredCapabilities || {};
    let required = json.requiredCapabilities || {};

    // One level deep union merge of desired- and required capabilities
    // with preference on required
    return Object.assign({}, desired, required);
  }

  // Matches capabilities as described by WebDriver.
  static match_(caps = {}) {
    let matched = new session.Capabilities();

    const defined = v => typeof v != "undefined" && v !== null;
    const wildcard = v => v === "*";

    // Iff |actual| provides some value, or is a wildcard or an exact
    // match of |expected|.  This means it can be null or undefined,
    // or "*", or "firefox".
    function stringMatch(actual, expected) {
      return !defined(actual) || (wildcard(actual) || actual === expected);
    }

    for (let [k, v] of Object.entries(caps)) {
      switch (k) {
        case "browserName":
          let bname = matched.get("browserName");
          if (!stringMatch(v, bname)) {
            throw new TypeError(
                pprint`Given browserName ${v}, but my name is ${bname}`);
          }
          break;

        // TODO(ato): bug 1326397
        case "browserVersion":
          let bversion = matched.get("browserVersion");
          if (!stringMatch(v, bversion)) {
            throw new TypeError(
                pprint`Given browserVersion ${v}, ` +
                pprint`but current version is ${bversion}`);
          }
          break;

        case "platformName":
          let pname = matched.get("platformName");
          if (!stringMatch(v, pname)) {
            throw new TypeError(
                pprint`Given platformName ${v}, ` +
                pprint`but current platform is ${pname}`);
          }
          break;

        // TODO(ato): bug 1326397
        case "platformVersion":
          let pversion = matched.get("platformVersion");
          if (!stringMatch(v, pversion)) {
            throw new TypeError(
                pprint`Given platformVersion ${v}, ` +
                pprint`but current platform version is ${pversion}`);
          }
          break;

        case "acceptInsecureCerts":
          assert.boolean(v);
          matched.set("acceptInsecureCerts", v);
          break;

        case "pageLoadStrategy":
          if (v === null) {
            matched.set("pageLoadStrategy", session.PageLoadStrategy.Normal);
          } else {
            assert.string(v);

            if (Object.values(session.PageLoadStrategy).includes(v)) {
              matched.set("pageLoadStrategy", v);
            } else {
              throw new InvalidArgumentError(
                  "Unknown page load strategy: " + v);
            }
          }

          break;

        case "proxy":
          let proxy = session.Proxy.fromJSON(v);
          matched.set("proxy", proxy);
          break;

        case "timeouts":
          let timeouts = session.Timeouts.fromJSON(v);
          matched.set("timeouts", timeouts);
          break;

        case "specificationLevel":
          assert.positiveInteger(v);
          matched.set("specificationLevel", v);
          break;

        case "moz:accessibilityChecks":
          assert.boolean(v);
          matched.set("moz:accessibilityChecks", v);
          break;
      }
    }

    return matched;
  }
};

// Specialisation of |JSON.stringify| that produces JSON-safe object
// literals, dropping empty objects and entries which values are undefined
// or null.  Objects are allowed to produce their own JSON representations
// by implementing a |toJSON| function.
function marshal(obj) {
  let rv = Object.create(null);

  function* iter(mapOrObject) {
    if (mapOrObject instanceof Map) {
      for (const [k, v] of mapOrObject) {
        yield [k, v];
      }
    } else {
      for (const k of Object.keys(mapOrObject)) {
        yield [k, mapOrObject[k]];
      }
    }
  }

  for (let [k, v] of iter(obj)) {
    // Skip empty values when serialising to JSON.
    if (typeof v == "undefined" || v === null) {
      continue;
    }

    // Recursively marshal objects that are able to produce their own
    // JSON representation.
    if (typeof v.toJSON == "function") {
      v = marshal(v.toJSON());

    // Or do the same for object literals.
    } else if (isObject(v)) {
      v = marshal(v);
    }

    // And finally drop (possibly marshaled) objects which have no
    // entries.
    if (!isObjectEmpty(v)) {
      rv[k] = v;
    }
  }

  return rv;
}

function isObject(obj) {
  return Object.prototype.toString.call(obj) == "[object Object]";
}

function isObjectEmpty(obj) {
  return isObject(obj) && Object.keys(obj).length === 0;
}

// Services.dirsvc is not accessible from content frame scripts,
// but we should not panic about that.
function maybeProfile() {
  try {
    return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
  } catch (e) {
    return "<protected>";
  }
}
PK
!<e_V)chrome/marionette/content/stream-utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 {
  Constructor: CC,
  classes: Cc,
  interfaces: Ci,
  utils: Cu,
  results: Cr,
} = Components;

Cu.import("resource://gre/modules/EventEmitter.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const IOUtil = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
    "nsIScriptableInputStream", "init");

this.EXPORTED_SYMBOLS = ["StreamUtils"];

const BUFFER_SIZE = 0x8000;

/**
 * This helper function (and its companion object) are used by bulk
 * senders and receivers to read and write data in and out of other streams.
 * Functions that make use of this tool are passed to callers when it is
 * time to read or write bulk data.  It is highly recommended to use these
 * copier functions instead of the stream directly because the copier
 * enforces the agreed upon length. Since bulk mode reuses an existing
 * stream, the sender and receiver must write and read exactly the agreed
 * upon amount of data, or else the entire transport will be left in a
 * invalid state.  Additionally, other methods of stream copying (such as
 * NetUtil.asyncCopy) close the streams involved, which would terminate
 * the debugging transport, and so it is avoided here.
 *
 * Overall, this *works*, but clearly the optimal solution would be
 * able to just use the streams directly.  If it were possible to fully
 * implement nsIInputStream/nsIOutputStream in JS, wrapper streams could
 * be created to enforce the length and avoid closing, and consumers could
 * use familiar stream utilities like NetUtil.asyncCopy.
 *
 * The function takes two async streams and copies a precise number
 * of bytes from one to the other.  Copying begins immediately, but may
 * complete at some future time depending on data size.  Use the returned
 * promise to know when it's complete.
 *
 * @param {nsIAsyncInputStream} input
 *     Stream to copy from.
 * @param {nsIAsyncOutputStream} output
 *        Stream to copy to.
 * @param {number} length
 *        Amount of data that needs to be copied.
 *
 * @return {Promise}
 *     Promise is resolved when copying completes or rejected if any
 *     (unexpected) errors occur.
 */
function copyStream(input, output, length) {
  let copier = new StreamCopier(input, output, length);
  return copier.copy();
}

/** @class */
function StreamCopier(input, output, length) {
  EventEmitter.decorate(this);
  this._id = StreamCopier._nextId++;
  this.input = input;
  // Save off the base output stream, since we know it's async as we've
  // required
  this.baseAsyncOutput = output;
  if (IOUtil.outputStreamIsBuffered(output)) {
    this.output = output;
  } else {
    this.output = Cc["@mozilla.org/network/buffered-output-stream;1"]
                  .createInstance(Ci.nsIBufferedOutputStream);
    this.output.init(output, BUFFER_SIZE);
  }
  this._length = length;
  this._amountLeft = length;
  this._deferred = {
    promise: new Promise((resolve, reject) => {
      this._deferred.resolve = resolve;
      this._deferred.reject = reject;
    }),
  };

  this._copy = this._copy.bind(this);
  this._flush = this._flush.bind(this);
  this._destroy = this._destroy.bind(this);

  // Copy promise's then method up to this object.
  //
  // Allows the copier to offer a promise interface for the simple succeed
  // or fail scenarios, but also emit events (due to the EventEmitter)
  // for other states, like progress.
  this.then = this._deferred.promise.then.bind(this._deferred.promise);
  this.then(this._destroy, this._destroy);

  // Stream ready callback starts as |_copy|, but may switch to |_flush|
  // at end if flushing would block the output stream.
  this._streamReadyCallback = this._copy;
}
StreamCopier._nextId = 0;

StreamCopier.prototype = {

  copy() {
    // Dispatch to the next tick so that it's possible to attach a progress
    // event listener, even for extremely fast copies (like when testing).
    Services.tm.currentThread.dispatch(() => {
      try {
        this._copy();
      } catch (e) {
        this._deferred.reject(e);
      }
    }, 0);
    return this;
  },

  _copy() {
    let bytesAvailable = this.input.available();
    let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
    this._debug("Trying to copy: " + amountToCopy);

    let bytesCopied;
    try {
      bytesCopied = this.output.writeFrom(this.input, amountToCopy);
    } catch (e) {
      if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this._debug("Base stream would block, will retry");
        this._debug("Waiting for output stream");
        this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
        return;
      }
      throw e;
    }

    this._amountLeft -= bytesCopied;
    this._debug("Copied: " + bytesCopied +
                ", Left: " + this._amountLeft);
    this._emitProgress();

    if (this._amountLeft === 0) {
      this._debug("Copy done!");
      this._flush();
      return;
    }

    this._debug("Waiting for input stream");
    this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
  },

  _emitProgress() {
    this.emit("progress", {
      bytesSent: this._length - this._amountLeft,
      totalBytes: this._length,
    });
  },

  _flush() {
    try {
      this.output.flush();
    } catch (e) {
      if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK ||
          e.result == Cr.NS_ERROR_FAILURE) {
        this._debug("Flush would block, will retry");
        this._streamReadyCallback = this._flush;
        this._debug("Waiting for output stream");
        this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
        return;
      }
      throw e;
    }
    this._deferred.resolve();
  },

  _destroy() {
    this._destroy = null;
    this._copy = null;
    this._flush = null;
    this.input = null;
    this.output = null;
  },

  // nsIInputStreamCallback
  onInputStreamReady() {
    this._streamReadyCallback();
  },

  // nsIOutputStreamCallback
  onOutputStreamReady() {
    this._streamReadyCallback();
  },

  _debug(msg) {
  },

};

/**
 * Read from a stream, one byte at a time, up to the next
 * <var>delimiter</var> character, but stopping if we've read |count|
 * without finding it.  Reading also terminates early if there are less
 * than <var>count</var> bytes available on the stream.  In that case,
 * we only read as many bytes as the stream currently has to offer.
 *
 * @param {nsIInputStream} stream
 *     Input stream to read from.
 * @param {string} delimiter
 *     Character we're trying to find.
 * @param {number} count
 *     Max number of characters to read while searching.
 *
 * @return {string}
 *     Collected data.  If the delimiter was found, this string will
 *     end with it.
 */
// TODO: This implementation could be removed if bug 984651 is fixed,
// which provides a native version of the same idea.
function delimitedRead(stream, delimiter, count) {
  let scriptableStream;
  if (stream instanceof Ci.nsIScriptableInputStream) {
    scriptableStream = stream;
  } else {
    scriptableStream = new ScriptableInputStream(stream);
  }

  let data = "";

  // Don't exceed what's available on the stream
  count = Math.min(count, stream.available());

  if (count <= 0) {
    return data;
  }

  let char;
  while (char !== delimiter && count > 0) {
    char = scriptableStream.readBytes(1);
    count--;
    data += char;
  }

  return data;
}

this.StreamUtils = {
  copyStream,
  delimitedRead,
};
PK
!<z?{{"chrome/marionette/content/test.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window [
]>
<window id="winTest" title="Title Test" windowtype="Test Type"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <dialog id="dia"
                xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

      <vbox id="things">
        <checkbox id="testBox"  label="box" />
        <textbox id="textInput"  size="6" value="test" label="input" />
        <textbox id="textInput2"  size="6" value="test" label="input" />
        <textbox id="textInput3" class="asdf" size="6" value="test" label="input" />
      </vbox>

      <iframe id="iframe" name="iframename" src="chrome://marionette/content/test2.xul"/>
      <iframe id="iframe" name="iframename" src="chrome://marionette/content/test_nested_iframe.xul"/>
      <hbox id="testXulBox"/>
      <browser id='aBrowser' src="chrome://marionette/content/test2.xul"/>
    </dialog>
</window>
PK
!<Iյ#chrome/marionette/content/test2.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window [
]>

<dialog id="dia"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="things">
    <checkbox id="testBox"  label="box" />
    <textbox id="textInput"  size="6" value="test" label="input" />
    <textbox id="textInput2"  size="6" value="test" label="input" />
    <textbox id="textInput3" class="asdf" size="6" value="test" label="input" />
  </vbox>
     
</dialog>
PK
!<X4chrome/marionette/content/test_anonymous_content.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE dialog [
]>

<dialog id="testDialogAnonymousNode"
          buttons="accept, cancel"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <bindings id="testBindings" xmlns="http://www.mozilla.org/xbl"
            xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <binding id="framebox">
      <content orient="vertical" mousethrough="never">
        <xul:browser anonid="content" id="browser" flex="1"
                     context="contentAreaContextMenu"
                     src="test.xul"
                     type="content"/>
      </content>
    </binding>

    <binding id="iframebox">
      <content>
        <xul:box>
          <xul:iframe anonid="iframe" src="chrome://marionette/content/test.xul"></xul:iframe>
        </xul:box>
      </content>
    </binding>
  </bindings>

  <hbox id="testAnonymousContentBox"/>
  <hbox id="container" style="-moz-binding: url('#testBindings');"/>
  <hbox id="container2" style="-moz-binding: url('#iframebox');"/>

</dialog>
PK
!<ɤ;##)chrome/marionette/content/test_dialog.dtd<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!ENTITY testDialog.title "Test Dialog">

<!ENTITY settings.label "Settings">
PK
!<y90chrome/marionette/content/test_dialog.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

testDialog.title=Test Dialog

settings.label=Settings
PK
!<)chrome/marionette/content/test_dialog.xul<?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 testdialog [
<!ENTITY % dialogDTD SYSTEM "chrome://marionette/content/test_dialog.dtd" >
%dialogDTD;
]>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>


<dialog id="testDialog"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&testDialog.title;"
        buttons="accept,cancel">

  <vbox flex="1" style="min-width: 300px; min-height: 500px;">
    <label>&settings.label;</label>
    <separator class="thin"/>
    <richlistbox id="test-list" flex="1">
        <richlistitem id="item-choose" orient="horizontal" selected="true">
            <label id="choose-label" value="First Entry" flex="1"/>
            <button id="choose-button" oncommand="" label="Choose..."/>
        </richlistitem>
    </richlistbox>
    <separator class="thin"/>
    <checkbox id="check-box" label="Test Mode 2" />
    <hbox align="center">
      <label id="text-box-label" control="text-box">Name:</label>
      <textbox id="text-box" flex="1" />
    </hbox>
  </vbox>

</dialog>
PK
!<cYI<<0chrome/marionette/content/test_nested_iframe.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window [
]>

  <iframe id="iframe" name="iframename" src="test2.xul"/>
PK
!<Xjj&chrome/marionette/content/transport.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* global Pipe, ScriptableInputStream, uneval */

const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu, results: Cr} =
    Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/EventEmitter.jsm");
const {StreamUtils} =
    Cu.import("chrome://marionette/content/stream-utils.js", {});
const {Packet, JSONPacket, BulkPacket} =
    Cu.import("chrome://marionette/content/packets.js", {});

const defer = function() {
  let deferred = {
    promise: new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    }),
  };
  return deferred;
};

const executeSoon = function(func) {
  Services.tm.dispatchToMainThread(func);
};

const flags = {wantVerbose: false, wantLogging: false};

const dumpv =
  flags.wantVerbose ?
  function(msg) { dump(msg + "\n"); } :
  function() {};

const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");

const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
    "nsIScriptableInputStream", "init");

this.EXPORTED_SYMBOLS = ["DebuggerTransport"];

const PACKET_HEADER_MAX = 200;

/**
 * An adapter that handles data transfers between the debugger client
 * and server. It can work with both nsIPipe and nsIServerSocket
 * transports so long as the properly created input and output streams
 * are specified.  (However, for intra-process connections,
 * LocalDebuggerTransport, below, is more efficient than using an nsIPipe
 * pair with DebuggerTransport.)
 *
 * @param {nsIAsyncInputStream} input
 *     The input stream.
 * @param {nsIAsyncOutputStream} output
 *     The output stream.
 *
 * Given a DebuggerTransport instance dt:
 * 1) Set dt.hooks to a packet handler object (described below).
 * 2) Call dt.ready() to begin watching for input packets.
 * 3) Call dt.send() / dt.startBulkSend() to send packets.
 * 4) Call dt.close() to close the connection, and disengage from
 *    the event loop.
 *
 * A packet handler is an object with the following methods:
 *
 * - onPacket(packet) - called when we have received a complete packet.
 *   |packet| is the parsed form of the packet --- a JavaScript value, not
 *   a JSON-syntax string.
 *
 * - onBulkPacket(packet) - called when we have switched to bulk packet
 *   receiving mode. |packet| is an object containing:
 *   * actor:  Name of actor that will receive the packet
 *   * type:   Name of actor's method that should be called on receipt
 *   * length: Size of the data to be read
 *   * stream: This input stream should only be used directly if you
 *             can ensure that you will read exactly |length| bytes and
 *             will not close the stream when reading is complete
 *   * done:   If you use the stream directly (instead of |copyTo|
 *             below), you must signal completion by resolving/rejecting
 *             this deferred.  If it's rejected, the transport will
 *             be closed.  If an Error is supplied as a rejection value,
 *             it will be logged via |dump|.  If you do use |copyTo|,
 *             resolving is taken care of for you when copying completes.
 *   * copyTo: A helper function for getting your data out of the
 *             stream that meets the stream handling requirements above,
 *             and has the following signature:
 *
 *             @param nsIAsyncOutputStream {output}
 *                 The stream to copy to.
 *
 *             @return {Promise}
 *                 The promise is resolved when copying completes or
 *                 rejected if any (unexpected) errors occur.  This object
 *                 also emits "progress" events for each chunk that is
 *                 copied.  See stream-utils.js.
 *
 * - onClosed(reason) - called when the connection is closed. |reason|
 *   is an optional nsresult or object, typically passed when the
 *   transport is closed due to some error in a underlying stream.
 *
 * See ./packets.js and the Remote Debugging Protocol specification for
 * more details on the format of these packets.
 *
 * @class
 */
function DebuggerTransport(input, output) {
  EventEmitter.decorate(this);

  this._input = input;
  this._scriptableInput = new ScriptableInputStream(input);
  this._output = output;

  // The current incoming (possibly partial) header, which will determine
  // which type of Packet |_incoming| below will become.
  this._incomingHeader = "";
  // The current incoming Packet object
  this._incoming = null;
  // A queue of outgoing Packet objects
  this._outgoing = [];

  this.hooks = null;
  this.active = false;

  this._incomingEnabled = true;
  this._outgoingEnabled = true;

  this.close = this.close.bind(this);
}

DebuggerTransport.prototype = {
  /**
   * Transmit an object as a JSON packet.
   *
   * This method returns immediately, without waiting for the entire
   * packet to be transmitted, registering event handlers as needed to
   * transmit the entire packet. Packets are transmitted in the order they
   * are passed to this method.
   */
  send(object) {
    this.emit("send", object);

    let packet = new JSONPacket(this);
    packet.object = object;
    this._outgoing.push(packet);
    this._flushOutgoing();
  },

  /**
   * Transmit streaming data via a bulk packet.
   *
   * This method initiates the bulk send process by queuing up the header
   * data.  The caller receives eventual access to a stream for writing.
   *
   * N.B.: Do *not* attempt to close the stream handed to you, as it
   * will continue to be used by this transport afterwards.  Most users
   * should instead use the provided |copyFrom| function instead.
   *
   * @param {Object} header
   *     This is modeled after the format of JSON packets above, but does
   *     not actually contain the data, but is instead just a routing
   *     header:
   *
   *       - actor:  Name of actor that will receive the packet
   *       - type:   Name of actor's method that should be called on receipt
   *       - length: Size of the data to be sent
   *
   * @return {Promise}
   *     The promise will be resolved when you are allowed to write to
   *     the stream with an object containing:
   *
   *       - stream:   This output stream should only be used directly
   *                   if you can ensure that you will write exactly
   *                   |length| bytes and will not close the stream when
   *                    writing is complete.
   *       - done:     If you use the stream directly (instead of
   *                   |copyFrom| below), you must signal completion by
   *                   resolving/rejecting this deferred.  If it's
   *                   rejected, the transport will be closed.  If an
   *                   Error is supplied as a rejection value, it will
   *                   be logged via |dump|.  If you do use |copyFrom|,
   *                   resolving is taken care of for you when copying
   *                   completes.
   *       - copyFrom: A helper function for getting your data onto the
   *                   stream that meets the stream handling requirements
   *                   above, and has the following signature:
   *
   *                   @param {nsIAsyncInputStream} input
   *                       The stream to copy from.
   *
   *                   @return {Promise}
   *                       The promise is resolved when copying completes
   *                       or rejected if any (unexpected) errors occur.
   *                       This object also emits "progress" events for
   *                       each chunkthat is copied.  See stream-utils.js.
   */
  startBulkSend(header) {
    this.emit("startbulksend", header);

    let packet = new BulkPacket(this);
    packet.header = header;
    this._outgoing.push(packet);
    this._flushOutgoing();
    return packet.streamReadyForWriting;
  },

  /**
   * Close the transport.
   *
   * @param {(nsresult|object)=} reason
   *     The status code or error message that corresponds to the reason
   *     for closing the transport (likely because a stream closed
   *     or failed).
   */
  close(reason) {
    this.emit("close", reason);

    this.active = false;
    this._input.close();
    this._scriptableInput.close();
    this._output.close();
    this._destroyIncoming();
    this._destroyAllOutgoing();
    if (this.hooks) {
      this.hooks.onClosed(reason);
      this.hooks = null;
    }
    if (reason) {
      dumpv("Transport closed: " + reason);
    } else {
      dumpv("Transport closed.");
    }
  },

  /**
   * The currently outgoing packet (at the top of the queue).
   */
  get _currentOutgoing() {
    return this._outgoing[0];
  },

  /**
   * Flush data to the outgoing stream.  Waits until the output
   * stream notifies us that it is ready to be written to (via
   * onOutputStreamReady).
   */
  _flushOutgoing() {
    if (!this._outgoingEnabled || this._outgoing.length === 0) {
      return;
    }

    // If the top of the packet queue has nothing more to send, remove it.
    if (this._currentOutgoing.done) {
      this._finishCurrentOutgoing();
    }

    if (this._outgoing.length > 0) {
      let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
      this._output.asyncWait(this, 0, 0, threadManager.currentThread);
    }
  },

  /**
   * Pause this transport's attempts to write to the output stream.
   * This is used when we've temporarily handed off our output stream for
   * writing bulk data.
   */
  pauseOutgoing() {
    this._outgoingEnabled = false;
  },

  /**
   * Resume this transport's attempts to write to the output stream.
   */
  resumeOutgoing() {
    this._outgoingEnabled = true;
    this._flushOutgoing();
  },

  // nsIOutputStreamCallback
  /**
   * This is called when the output stream is ready for more data to
   * be written.  The current outgoing packet will attempt to write some
   * amount of data, but may not complete.
   */
  onOutputStreamReady(stream) {
    if (!this._outgoingEnabled || this._outgoing.length === 0) {
      return;
    }

    try {
      this._currentOutgoing.write(stream);
    } catch (e) {
      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this.close(e.result);
        return;
      }
      throw e;
    }

    this._flushOutgoing();
  },

  /**
   * Remove the current outgoing packet from the queue upon completion.
   */
  _finishCurrentOutgoing() {
    if (this._currentOutgoing) {
      this._currentOutgoing.destroy();
      this._outgoing.shift();
    }
  },

  /**
   * Clear the entire outgoing queue.
   */
  _destroyAllOutgoing() {
    for (let packet of this._outgoing) {
      packet.destroy();
    }
    this._outgoing = [];
  },

  /**
   * Initialize the input stream for reading. Once this method has been
   * called, we watch for packets on the input stream, and pass them to
   * the appropriate handlers via this.hooks.
   */
  ready() {
    this.active = true;
    this._waitForIncoming();
  },

  /**
   * Asks the input stream to notify us (via onInputStreamReady) when it is
   * ready for reading.
   */
  _waitForIncoming() {
    if (this._incomingEnabled) {
      let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
      this._input.asyncWait(this, 0, 0, threadManager.currentThread);
    }
  },

  /**
   * Pause this transport's attempts to read from the input stream.
   * This is used when we've temporarily handed off our input stream for
   * reading bulk data.
   */
  pauseIncoming() {
    this._incomingEnabled = false;
  },

  /**
   * Resume this transport's attempts to read from the input stream.
   */
  resumeIncoming() {
    this._incomingEnabled = true;
    this._flushIncoming();
    this._waitForIncoming();
  },

  // nsIInputStreamCallback
  /**
   * Called when the stream is either readable or closed.
   */
  onInputStreamReady(stream) {
    try {
      while (stream.available() && this._incomingEnabled &&
             this._processIncoming(stream, stream.available())) {
         // Loop until there is nothing more to process
      }
      this._waitForIncoming();
    } catch (e) {
      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this.close(e.result);
      } else {
        throw e;
      }
    }
  },

  /**
   * Process the incoming data.  Will create a new currently incoming
   * Packet if needed.  Tells the incoming Packet to read as much data
   * as it can, but reading may not complete.  The Packet signals that
   * its data is ready for delivery by calling one of this transport's
   * _on*Ready methods (see ./packets.js and the _on*Ready methods below).
   *
   * @return {boolean}
   *     Whether incoming stream processing should continue for any
   *     remaining data.
   */
  _processIncoming(stream, count) {
    dumpv("Data available: " + count);

    if (!count) {
      dumpv("Nothing to read, skipping");
      return false;
    }

    try {
      if (!this._incoming) {
        dumpv("Creating a new packet from incoming");

        if (!this._readHeader(stream)) {
          // Not enough data to read packet type
          return false;
        }

        // Attempt to create a new Packet by trying to parse each possible
        // header pattern.
        this._incoming = Packet.fromHeader(this._incomingHeader, this);
        if (!this._incoming) {
          throw new Error("No packet types for header: " +
                        this._incomingHeader);
        }
      }

      if (!this._incoming.done) {
        // We have an incomplete packet, keep reading it.
        dumpv("Existing packet incomplete, keep reading");
        this._incoming.read(stream, this._scriptableInput);
      }
    } catch (e) {
      dump(`Error reading incoming packet: (${e} - ${e.stack})\n`);

      // Now in an invalid state, shut down the transport.
      this.close();
      return false;
    }

    if (!this._incoming.done) {
      // Still not complete, we'll wait for more data.
      dumpv("Packet not done, wait for more");
      return true;
    }

    // Ready for next packet
    this._flushIncoming();
    return true;
  },

  /**
   * Read as far as we can into the incoming data, attempting to build
   * up a complete packet header (which terminates with ":").  We'll only
   * read up to PACKET_HEADER_MAX characters.
   *
   * @return {boolean}
   *     True if we now have a complete header.
   */
  _readHeader() {
    let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
    this._incomingHeader +=
    StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
    if (flags.wantVerbose) {
      dumpv("Header read: " + this._incomingHeader);
    }

    if (this._incomingHeader.endsWith(":")) {
      if (flags.wantVerbose) {
        dumpv("Found packet header successfully: " + this._incomingHeader);
      }
      return true;
    }

    if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
      throw new Error("Failed to parse packet header!");
    }

    // Not enough data yet.
    return false;
  },

  /**
   * If the incoming packet is done, log it as needed and clear the buffer.
   */
  _flushIncoming() {
    if (!this._incoming.done) {
      return;
    }
    if (flags.wantLogging) {
      dumpv("Got: " + this._incoming);
    }
    this._destroyIncoming();
  },

  /**
   * Handler triggered by an incoming JSONPacket completing it's |read|
   * method.  Delivers the packet to this.hooks.onPacket.
   */
  _onJSONObjectReady(object) {
    executeSoon(() => {
    // Ensure the transport is still alive by the time this runs.
      if (this.active) {
        this.emit("packet", object);
        this.hooks.onPacket(object);
      }
    });
  },

  /**
   * Handler triggered by an incoming BulkPacket entering the |read|
   * phase for the stream portion of the packet.  Delivers info about the
   * incoming streaming data to this.hooks.onBulkPacket.  See the main
   * comment on the transport at the top of this file for more details.
   */
  _onBulkReadReady(...args) {
    executeSoon(() => {
    // Ensure the transport is still alive by the time this runs.
      if (this.active) {
        this.emit("bulkpacket", ...args);
        this.hooks.onBulkPacket(...args);
      }
    });
  },

  /**
   * Remove all handlers and references related to the current incoming
   * packet, either because it is now complete or because the transport
   * is closing.
   */
  _destroyIncoming() {
    if (this._incoming) {
      this._incoming.destroy();
    }
    this._incomingHeader = "";
    this._incoming = null;
  },
};

/**
 * An adapter that handles data transfers between the debugger client
 * and server when they both run in the same process. It presents the
 * same API as DebuggerTransport, but instead of transmitting serialized
 * messages across a connection it merely calls the packet dispatcher of
 * the other side.
 *
 * @param {LocalDebuggerTransport} other
 *     The other endpoint for this debugger connection.
 *
 * @see {DebuggerTransport}
 */
function LocalDebuggerTransport(other) {
  EventEmitter.decorate(this);

  this.other = other;
  this.hooks = null;

  // A packet number, shared between this and this.other. This isn't
  // used by the protocol at all, but it makes the packet traces a lot
  // easier to follow.
  this._serial = this.other ? this.other._serial : {count: 0};
  this.close = this.close.bind(this);
}

LocalDebuggerTransport.prototype = {
  /**
   * Transmit a message by directly calling the onPacket handler of the other
   * endpoint.
   */
  send(packet) {
    this.emit("send", packet);

    let serial = this._serial.count++;
    if (flags.wantLogging) {
      // Check 'from' first, as 'echo' packets have both.
      if (packet.from) {
        dumpv("Packet " + serial + " sent from " + uneval(packet.from));
      } else if (packet.to) {
        dumpv("Packet " + serial + " sent to " + uneval(packet.to));
      }
    }
    this._deepFreeze(packet);
    let other = this.other;
    if (other) {
      executeSoon(() => {
        // Avoid the cost of JSON.stringify() when logging is disabled.
        if (flags.wantLogging) {
          dumpv(`Received packet ${serial}: ` +
              JSON.stringify(packet, null, 2));
        }
        if (other.hooks) {
          other.emit("packet", packet);
          other.hooks.onPacket(packet);
        }
      });
    }
  },

  /**
   * Send a streaming bulk packet directly to the onBulkPacket handler
   * of the other endpoint.
   *
   * This case is much simpler than the full DebuggerTransport, since
   * there is no primary stream we have to worry about managing while
   * we hand it off to others temporarily.  Instead, we can just make a
   * single use pipe and be done with it.
   */
  startBulkSend({actor, type, length}) {
    this.emit("startbulksend", {actor, type, length});

    let serial = this._serial.count++;

    dumpv("Sent bulk packet " + serial + " for actor " + actor);
    if (!this.other) {
      let error = new Error("startBulkSend: other side of transport missing");
      return Promise.reject(error);
    }

    let pipe = new Pipe(true, true, 0, 0, null);

    executeSoon(() => {
      dumpv("Received bulk packet " + serial);
      if (!this.other.hooks) {
        return;
      }

      // Receiver
      let deferred = defer();
      let packet = {
        actor,
        type,
        length,
        copyTo: (output) => {
          let copying =
          StreamUtils.copyStream(pipe.inputStream, output, length);
          deferred.resolve(copying);
          return copying;
        },
        stream: pipe.inputStream,
        done: deferred,
      };

      this.other.emit("bulkpacket", packet);
      this.other.hooks.onBulkPacket(packet);

      // Await the result of reading from the stream
      deferred.promise.then(() => pipe.inputStream.close(), this.close);
    });

    // Sender
    let sendDeferred = defer();

    // The remote transport is not capable of resolving immediately here,
    // so we shouldn't be able to either.
    executeSoon(() => {
      let copyDeferred = defer();

      sendDeferred.resolve({
        copyFrom: (input) => {
          let copying =
          StreamUtils.copyStream(input, pipe.outputStream, length);
          copyDeferred.resolve(copying);
          return copying;
        },
        stream: pipe.outputStream,
        done: copyDeferred,
      });

      // Await the result of writing to the stream
      copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
    });

    return sendDeferred.promise;
  },

  /**
   * Close the transport.
   */
  close() {
    this.emit("close");

    if (this.other) {
      // Remove the reference to the other endpoint before calling close(), to
      // avoid infinite recursion.
      let other = this.other;
      this.other = null;
      other.close();
    }
    if (this.hooks) {
      try {
        this.hooks.onClosed();
      } catch (ex) {
        console.error(ex);
      }
      this.hooks = null;
    }
  },

  /**
   * An empty method for emulating the DebuggerTransport API.
   */
  ready() {},

  /**
   * Helper function that makes an object fully immutable.
   */
  _deepFreeze(object) {
    Object.freeze(object);
    for (let prop in object) {
      // Freeze the properties that are objects, not on the prototype,
      // and not already frozen.  Note that this might leave an unfrozen
      // reference somewhere in the object if there is an already frozen
      // object containing an unfrozen object.
      if (object.hasOwnProperty(prop) && typeof object === "object" &&
          !Object.isFrozen(object)) {
        this._deepFreeze(object[prop]);
      }
    }
  },
};

/**
 * A transport for the debugging protocol that uses nsIMessageManagers to
 * exchange packets with servers running in child processes.
 *
 * In the parent process, <var>mm</var> should be the nsIMessageSender
 * for the child process. In a child process, |mm| should be the child
 * process message manager, which sends packets to the parent.
 *
 * <var>prefix</var> is a string included in the message names, to
 * distinguish multiple servers running in the same child process.
 *
 * This transport exchanges messages named <tt>debug:PREFIX:packet</tt>,
 * where <tt>PREFIX</tt> is <var>prefix</var>, whose data is the protocol
 * packet.
 */
function ChildDebuggerTransport(mm, prefix) {
  EventEmitter.decorate(this);

  this._mm = mm;
  this._messageName = "debug:" + prefix + ":packet";
}

/*
 * To avoid confusion, we use 'message' to mean something that
 * nsIMessageSender conveys, and 'packet' to mean a remote debugging
 * protocol packet.
 */
ChildDebuggerTransport.prototype = {
  constructor: ChildDebuggerTransport,

  hooks: null,

  _addListener() {
    this._mm.addMessageListener(this._messageName, this);
  },

  _removeListener() {
    try {
      this._mm.removeMessageListener(this._messageName, this);
    } catch (e) {
      if (e.result != Cr.NS_ERROR_NULL_POINTER) {
        throw e;
      }
      // In some cases, especially when using messageManagers in non-e10s
      // mode, we reach this point with a dead messageManager which only
      // throws errors but does not seem to indicate in any other way that
      // it is dead.
    }
  },

  ready() {
    this._addListener();
  },

  close() {
    this._removeListener();
    this.emit("close");
    this.hooks.onClosed();
  },

  receiveMessage({data}) {
    this.emit("packet", data);
    this.hooks.onPacket(data);
  },

  send(packet) {
    this.emit("send", packet);
    try {
      this._mm.sendAsyncMessage(this._messageName, packet);
    } catch (e) {
      if (e.result != Cr.NS_ERROR_NULL_POINTER) {
        throw e;
      }
      // In some cases, especially when using messageManagers in non-e10s
      // mode, we reach this point with a dead messageManager which only
      // throws errors but does not seem to indicate in any other way that
      // it is dead.
    }
  },

  startBulkSend() {
    throw new Error("Can't send bulk data to child processes.");
  },

  swapBrowser(mm) {
    this._removeListener();
    this._mm = mm;
    this._addListener();
  },
};

// WorkerDebuggerTransport is defined differently depending on whether we are
// on the main thread or a worker thread. In the former case, we are required
// by the devtools loader, and isWorker will be false. Otherwise, we are
// required by the worker loader, and isWorker will be true.
//
// Each worker debugger supports only a single connection to the main thread.
// However, its theoretically possible for multiple servers to connect to the
// same worker. Consequently, each transport has a connection id, to allow
// messages from multiple connections to be multiplexed on a single channel.

if (!this.isWorker) {
  // Main thread
  (function() {
    /**
     * A transport that uses a WorkerDebugger to send packets from the main
     * thread to a worker thread.
     */
    function WorkerDebuggerTransport(dbg, id) {
      this._dbg = dbg;
      this._id = id;
      this.onMessage = this._onMessage.bind(this);
    }

    WorkerDebuggerTransport.prototype = {
      constructor: WorkerDebuggerTransport,

      ready() {
        this._dbg.addListener(this);
      },

      close() {
        this._dbg.removeListener(this);
        if (this.hooks) {
          this.hooks.onClosed();
        }
      },

      send(packet) {
        this._dbg.postMessage(JSON.stringify({
          type: "message",
          id: this._id,
          message: packet,
        }));
      },

      startBulkSend() {
        throw new Error("Can't send bulk data from worker threads!");
      },

      _onMessage(message) {
        let packet = JSON.parse(message);
        if (packet.type !== "message" || packet.id !== this._id) {
          return;
        }

        if (this.hooks) {
          this.hooks.onPacket(packet.message);
        }
      },
    };

  }).call(this);
} else {
  // Worker thread
  (function() {
    /**
     * A transport that uses a WorkerDebuggerGlobalScope to send packets
     * from a worker thread to the main thread.
     */
    function WorkerDebuggerTransport(scope, id) {
      this._scope = scope;
      this._id = id;
      this._onMessage = this._onMessage.bind(this);
    }

    WorkerDebuggerTransport.prototype = {
      constructor: WorkerDebuggerTransport,

      ready() {
        this._scope.addEventListener("message", this._onMessage);
      },

      close() {
        this._scope.removeEventListener("message", this._onMessage);
        if (this.hooks) {
          this.hooks.onClosed();
        }
      },

      send(packet) {
        this._scope.postMessage(JSON.stringify({
          type: "message",
          id: this._id,
          message: packet,
        }));
      },

      startBulkSend() {
        throw new Error("Can't send bulk data from worker threads!");
      },

      _onMessage(event) {
        let packet = JSON.parse(event.data);
        if (packet.type !== "message" || packet.id !== this._id) {
          return;
        }

        if (this.hooks) {
          this.hooks.onPacket(packet.message);
        }
      },
    };

  }).call(this);
}
PK
!<I0x!chrome/marionette/content/wait.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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("chrome://marionette/content/error.js");

this.EXPORTED_SYMBOLS = ["wait"];

/**
 * Poll-waiting utilities.
 *
 * @namespace
 */
this.wait = {};

/**
 * @callback WaitCondition
 *
 * @param {function(*)} resolve
 *     To be called when the condition has been met.  Will return the
 *     resolved value.
 * @param {function} reject
 *     To be called when the condition has not been met.  Will cause
 *     the condition to be revaluated or time out.
 *
 * @return {*}
 *     The value from calling <code>resolve</code>.
 */

/**
 * Runs a promise-like function off the main thread until it is resolved
 * through |resolve| or |rejected| callbacks.  The function is guaranteed
 * to be run at least once, irregardless of the timeout.
 *
 * The |func| is evaluated every |interval| for as long as its runtime
 * duration does not exceed |interval|.  Evaluations occur sequentially,
 * meaning that evaluations of |func| are queued if the runtime evaluation
 * duration of |func| is greater than |interval|.
 *
 * |func| is given two arguments, |resolve| and |reject|, of which one
 * must be called for the evaluation to complete.  Calling |resolve| with
 * an argument indicates that the expected wait condition was met and
 * will return the passed value to the caller.  Conversely, calling
 * |reject| will evaluate |func| again until the |timeout| duration has
 * elapsed or |func| throws.  The passed value to |reject| will also be
 * returned to the caller once the wait has expired.
 *
 * Usage:
 *
 * <pre><code>
 *     let els = wait.until((resolve, reject) => {
 *       let res = document.querySelectorAll("p");
 *       if (res.length > 0) {
 *         resolve(Array.from(res));
 *       } else {
 *         reject([]);
 *       }
 *     });
 * </pre></code>
 *
 * @param {WaitCondition} func
 *     Function to run off the main thread.
 * @param {number=} timeout
 *     Desired timeout.  If 0 or less than the runtime evaluation time
 *     of |func|, |func| is guaranteed to run at least once.  The default
 *     is 2000 milliseconds.
 * @param {number=} interval
 *     Duration between each poll of |func| in milliseconds.  Defaults to
 *     10 milliseconds.
 *
 * @return {Promise.<*>}
 *     Yields the value passed to |func|'s |resolve| or |reject|
 *     callbacks.
 *
 * @throws {*}
 *     If |func| throws, its error is propagated.
 */
wait.until = function(func, timeout = 2000, interval = 10) {
  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  return new Promise((resolve, reject) => {
    const start = new Date().getTime();
    const end = start + timeout;

    let evalFn = () => {
      new Promise(func).then(resolve, rejected => {
        if (error.isError(rejected)) {
          throw rejected;
        }

        // return if timeout is 0, allowing |func| to be evaluated at
        // least once
        if (start == end || new Date().getTime() >= end) {
          resolve(rejected);
        }
      }).catch(reject);
    };

    // the repeating slack timer waits |interval|
    // before invoking |evalFn|
    evalFn();

    timer.init(evalFn, interval, Ci.nsITimer.TYPE_REPEATING_SLACK);

  // cancel timer and propagate result
  }).then(res => {
    timer.cancel();
    return res;
  }, err => {
    timer.cancel();
    throw err;
  });
};
PK
!<Lզ&&components/marionette.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 {Constructor: CC, interfaces: Ci, utils: Cu, classes: Cc} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(
    this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
    "resource://gre/modules/Preferences.jsm");

const MARIONETTE_CONTRACT_ID = "@mozilla.org/remote/marionette;1";
const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");

const PREF_PORT = "marionette.port";
const PREF_PORT_FALLBACK = "marionette.defaultPrefs.port";
const PREF_LOG_LEVEL = "marionette.log.level";
const PREF_LOG_LEVEL_FALLBACK = "marionette.logging";

const DEFAULT_LOG_LEVEL = "info";
const LOG_LEVELS = new class extends Map {
  constructor() {
    super([
      ["fatal", Log.Level.Fatal],
      ["error", Log.Level.Error],
      ["warn", Log.Level.Warn],
      ["info", Log.Level.Info],
      ["config", Log.Level.Config],
      ["debug", Log.Level.Debug],
      ["trace", Log.Level.Trace],
    ]);
  }

  get(level) {
    let s = new String(level).toLowerCase();
    if (!this.has(s)) {
      return DEFAULT_LOG_LEVEL;
    }
    return super.get(s);
  }
};

// Complements -marionette flag for starting the Marionette server.
// We also set this if Marionette is running in order to start the server
// again after a Firefox restart.
const ENV_ENABLED = "MOZ_MARIONETTE";

// Besides starting based on existing prefs in a profile and a command
// line flag, we also support inheriting prefs out of an env var, and to
// start Marionette that way.
//
// This allows marionette prefs to persist when we do a restart into
// a different profile in order to test things like Firefox refresh.
// The environment variable itself, if present, is interpreted as a
// JSON structure, with the keys mapping to preference names in the
// "marionette." branch, and the values to the values of those prefs. So
// something like {"port": 4444} would result in the marionette.port
// pref being set to 4444.
const ENV_PRESERVE_PREFS = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS";

const ServerSocket = CC("@mozilla.org/network/server-socket;1",
    "nsIServerSocket",
    "initSpecialConnection");

const {PREF_STRING, PREF_BOOL, PREF_INT, PREF_INVALID} = Ci.nsIPrefBranch;

function getPrefVal(pref) {
  let prefType = Services.prefs.getPrefType(pref);
  let prefValue;
  switch (prefType) {
    case PREF_STRING:
      prefValue = Services.prefs.getStringPref(pref);
      break;

    case PREF_BOOL:
      prefValue = Services.prefs.getBoolPref(pref);
      break;

    case PREF_INT:
      prefValue = Services.prefs.getIntPref(pref);
      break;

    case PREF_INVALID:
      prefValue = undefined;
      break;

    default:
      throw new TypeError(`Unexpected preference type (${prefType}) for ` +
                          `${pref}`);
  }

  return prefValue;
}

// Get preference value of |preferred|, falling back to |fallback|
// if |preferred| is not user-modified and |fallback| exists.
function getPref(preferred, fallback) {
  if (!Services.prefs.prefHasUserValue(preferred) &&
      Services.prefs.getPrefType(fallback) != Ci.nsIPrefBranch.PREF_INVALID) {
    return getPrefVal(fallback, getPrefVal(preferred));
  }
  return getPrefVal(preferred);
}

// Marionette preferences recently changed names.  This is an abstraction
// that first looks for the new name, but falls back to using the old name
// if the new does not exist.
//
// This shim can be removed when Firefox 55 ships.
const prefs = {
  get port() {
    return getPref(PREF_PORT, PREF_PORT_FALLBACK);
  },

  get logLevel() {
    let s = getPref(PREF_LOG_LEVEL, PREF_LOG_LEVEL_FALLBACK);
    return LOG_LEVELS.get(s);
  },

  readFromEnvironment(key) {
    const env = Cc["@mozilla.org/process/environment;1"]
        .getService(Ci.nsIEnvironment);

    if (env.exists(key)) {
      let prefs;
      try {
        prefs = JSON.parse(env.get(key));
      } catch (e) {
        Cu.reportError(
            "Invalid Marionette preferences in environment; " +
            "preferences will not have been applied");
        Cu.reportError(e);
      }

      if (prefs) {
        for (let prefName of Object.keys(prefs)) {
          Preferences.set(prefName, prefs[prefName]);
        }
      }
    }
  },
};

function MarionetteComponent() {
  this.running = false;
  this.server = null;

  // holds reference to ChromeWindow
  // used to run GFX sanity tests on Windows
  this.gfxWindow = null;

  // indicates that all pending window checks have been completed
  // and that we are ready to start the Marionette server
  this.finalUIStartup = false;

  this.logger = this.setupLogger(prefs.logLevel);

  this.enabled = env.exists(ENV_ENABLED);
  if (this.enabled) {
    this.logger.info(`Enabled via ${ENV_ENABLED}`);
  }
}

MarionetteComponent.prototype = {
  classDescription: "Marionette component",
  classID: MARIONETTE_CID,
  contractID: MARIONETTE_CONTRACT_ID,
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsICommandLineHandler,
    Ci.nsIMarionette,
  ]),
  _xpcom_categories: [
    {category: "command-line-handler", entry: "b-marionette"},
    {category: "profile-after-change", service: true},
  ],
  helpInfo: "  --marionette       Enable remote control server.\n",
};

// Handle -marionette flag
MarionetteComponent.prototype.handle = function(cmdLine) {
  if (!this.enabled && cmdLine.handleFlag("marionette", false)) {
    this.enabled = true;
    this.logger.info("Enabled via --marionette");
  }
};

MarionetteComponent.prototype.observe = function(subject, topic, data) {
  this.logger.debug(`Received observer notification "${topic}"`);

  switch (topic) {
    case "profile-after-change":
      Services.obs.addObserver(this, "command-line-startup");
      Services.obs.addObserver(this, "sessionstore-windows-restored");

      prefs.readFromEnvironment(ENV_PRESERVE_PREFS);
      break;

    // In safe mode the command line handlers are getting parsed after the
    // safe mode dialog has been closed. To allow Marionette to start
    // earlier, use the CLI startup observer notification for
    // special-cased handlers, which gets fired before the dialog appears.
    case "command-line-startup":
      Services.obs.removeObserver(this, topic);
      this.handle(subject);

      // We want to suppress the modal dialog that's shown
      // when starting up in safe-mode to enable testing.
      if (this.enabled && Services.appinfo.inSafeMode) {
        Services.obs.addObserver(this, "domwindowopened");
      }

      break;

    case "domwindowclosed":
      if (this.gfxWindow === null || subject === this.gfxWindow) {
        Services.obs.removeObserver(this, topic);

        Services.obs.addObserver(this, "xpcom-shutdown");
        this.finalUIStartup = true;
        this.init();
      }
      break;

    case "domwindowopened":
      Services.obs.removeObserver(this, topic);
      this.suppressSafeModeDialog(subject);
      break;

    case "sessionstore-windows-restored":
      Services.obs.removeObserver(this, topic);

      // When Firefox starts on Windows, an additional GFX sanity test
      // window may appear off-screen.  Marionette should wait for it
      // to close.
      let winEn = Services.wm.getEnumerator(null);
      while (winEn.hasMoreElements()) {
        let win = winEn.getNext();
        if (win.document.documentURI == "chrome://gfxsanity/content/sanityparent.html") {
          this.gfxWindow = win;
          break;
        }
      }

      if (this.gfxWindow) {
        Services.obs.addObserver(this, "domwindowclosed");
      } else {
        Services.obs.addObserver(this, "xpcom-shutdown");
        this.finalUIStartup = true;
        this.init();
      }

      break;

    case "xpcom-shutdown":
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.uninit();
      break;
  }
};

MarionetteComponent.prototype.setupLogger = function(level) {
  let logger = Log.repository.getLogger("Marionette");
  logger.level = level;
  logger.addAppender(new Log.DumpAppender());
  return logger;
};

MarionetteComponent.prototype.suppressSafeModeDialog = function(win) {
  win.addEventListener("load", () => {
    if (win.document.getElementById("safeModeDialog")) {
      // accept the dialog to start in safe-mode
      this.logger.debug("Safe Mode detected. Going to suspress the dialog now.");
      win.setTimeout(() => {
        win.document.documentElement.getButton("accept").click();
      });
    }
  }, {once: true});
};

MarionetteComponent.prototype.init = function() {
  if (this.running || !this.enabled || !this.finalUIStartup) {
    return;
  }

  // Delay initialization until we are done with delayed startup...
  Services.tm.idleDispatchToMainThread(() => {
    // ... and with startup tests.
    let promise = Promise.resolve();
    if ("@mozilla.org/test/startuprecorder;1" in Cc)
      promise = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject.done;
    promise.then(() => {
      let s;
      try {
        Cu.import("chrome://marionette/content/server.js");
        s = new server.TCPListener(prefs.port);
        s.start();
        this.logger.info(`Listening on port ${s.port}`);
      } finally {
        if (s) {
          this.server = s;
          this.running = true;
        }
      }
    });
  });
};

MarionetteComponent.prototype.uninit = function() {
  if (!this.running) {
    return;
  }
  this.server.stop();
  this.running = false;
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);
PK
!<ecomponents/nsAsyncShutdown.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * An implementation of nsIAsyncShutdown* based on AsyncShutdown.jsm
 */

"use strict";

const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;

var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
  "resource://gre/modules/AsyncShutdown.jsm");


/**
 * Conversion between nsIPropertyBag and JS object
 */
var PropertyBagConverter = {
  // From nsIPropertyBag to JS
  toObject(bag) {
    if (!(bag instanceof Ci.nsIPropertyBag)) {
      throw new TypeError("Not a property bag");
    }
    let result = {};
    let enumerator = bag.enumerator;
    while (enumerator.hasMoreElements()) {
      let {name, value: property} = enumerator.getNext().QueryInterface(Ci.nsIProperty);
      let value = this.toValue(property);
      result[name] = value;
    }
    return result;
  },
  toValue(property) {
    if (typeof property != "object") {
      return property;
    }
    if (Array.isArray(property)) {
      return property.map(this.toValue, this);
    }
    if (property && property instanceof Ci.nsIPropertyBag) {
      return this.toObject(property);
    }
    return property;
  },

  // From JS to nsIPropertyBag
  fromObject(obj) {
    if (obj == null || typeof obj != "object") {
      throw new TypeError("Invalid object: " + obj);
    }
    let bag = Cc["@mozilla.org/hash-property-bag;1"].
      createInstance(Ci.nsIWritablePropertyBag);
    for (let k of Object.keys(obj)) {
      let value = this.fromValue(obj[k]);
      bag.setProperty(k, value);
    }
    return bag;
  },
  fromValue(value) {
    if (typeof value == "function") {
      return null; // Emulating the behavior of JSON.stringify with functions
    }
    if (Array.isArray(value)) {
      return value.map(this.fromValue, this);
    }
    if (value == null || typeof value != "object") {
      // Auto-converted to nsIVariant
      return value;
    }
    return this.fromObject(value);
  },
};



/**
 * Construct an instance of nsIAsyncShutdownClient from a
 * AsyncShutdown.Barrier client.
 *
 * @param {object} moduleClient A client, as returned from the `client`
 * property of an instance of `AsyncShutdown.Barrier`. This client will
 * serve as back-end for methods `addBlocker` and `removeBlocker`.
 * @constructor
 */
function nsAsyncShutdownClient(moduleClient) {
  if (!moduleClient) {
    throw new TypeError("nsAsyncShutdownClient expects one argument");
  }
  this._moduleClient = moduleClient;
  this._byName = new Map();
}
nsAsyncShutdownClient.prototype = {
  _getPromisified(xpcomBlocker) {
    let candidate = this._byName.get(xpcomBlocker.name);
    if (!candidate) {
      return null;
    }
    if (candidate.xpcom === xpcomBlocker) {
      return candidate.jsm;
    }
    return null;
  },
  _setPromisified(xpcomBlocker, moduleBlocker) {
    let candidate = this._byName.get(xpcomBlocker.name);
    if (!candidate) {
      this._byName.set(xpcomBlocker.name, {xpcom: xpcomBlocker,
                                           jsm: moduleBlocker});
      return;
    }
    if (candidate.xpcom === xpcomBlocker) {
      return;
    }
    throw new Error("We have already registered a distinct blocker with the same name: " + xpcomBlocker.name);
  },
  _deletePromisified(xpcomBlocker) {
    let candidate = this._byName.get(xpcomBlocker.name);
    if (!candidate || candidate.xpcom !== xpcomBlocker) {
      return false;
    }
    this._byName.delete(xpcomBlocker.name);
    return true;
  },
  get jsclient() {
    return this._moduleClient;
  },
  get name() {
    return this._moduleClient.name;
  },
  addBlocker(/* nsIAsyncShutdownBlocker*/ xpcomBlocker,
      fileName, lineNumber, stack) {
    // We need a Promise-based function with the same behavior as
    // `xpcomBlocker`. Furthermore, to support `removeBlocker`, we
    // need to ensure that we always get the same Promise-based
    // function if we call several `addBlocker`/`removeBlocker` several
    // times with the same `xpcomBlocker`.
    //
    // Ideally, this should be done with a WeakMap() with xpcomBlocker
    // as a key, but XPConnect NativeWrapped objects cannot serve as
    // WeakMap keys.
    //
    let moduleBlocker = this._getPromisified(xpcomBlocker);
    if (!moduleBlocker) {
      moduleBlocker = () => new Promise(
        // This promise is never resolved. By opposition to AsyncShutdown
        // blockers, `nsIAsyncShutdownBlocker`s are always lifted by calling
        // `removeBlocker`.
        () => xpcomBlocker.blockShutdown(this)
      );

      this._setPromisified(xpcomBlocker, moduleBlocker);
    }

    this._moduleClient.addBlocker(xpcomBlocker.name,
      moduleBlocker,
      {
        fetchState: () => {
          let state = xpcomBlocker.state;
          if (state) {
            return PropertyBagConverter.toValue(state);
          }
          return null;
        },
        filename: fileName,
        lineNumber,
        stack,
      });
  },

  removeBlocker(xpcomBlocker) {
    let moduleBlocker = this._getPromisified(xpcomBlocker);
    if (!moduleBlocker) {
      return false;
    }
    this._deletePromisified(xpcomBlocker);
    return this._moduleClient.removeBlocker(moduleBlocker);
  },

  /* ........ QueryInterface .............. */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
  classID: Components.ID("{314e9e96-cc37-4d5c-843b-54709ce11426}"),
};

/**
 * Construct an instance of nsIAsyncShutdownBarrier from an instance
 * of AsyncShutdown.Barrier.
 *
 * @param {object} moduleBarrier an instance if
 * `AsyncShutdown.Barrier`. This instance will serve as back-end for
 * all methods.
 * @constructor
 */
function nsAsyncShutdownBarrier(moduleBarrier) {
  this._client = new nsAsyncShutdownClient(moduleBarrier.client);
  this._moduleBarrier = moduleBarrier;
}
nsAsyncShutdownBarrier.prototype = {
  get state() {
    return PropertyBagConverter.fromValue(this._moduleBarrier.state);
  },
  get client() {
    return this._client;
  },
  wait(onReady) {
    this._moduleBarrier.wait().then(() => {
      onReady.done();
    });
    // By specification, _moduleBarrier.wait() cannot reject.
  },

  /* ........ QueryInterface .............. */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
  classID: Components.ID("{29a0e8b5-9111-4c09-a0eb-76cd02bf20fa}"),
};

function nsAsyncShutdownService() {
  // Cache for the getters

  for (let _k of
   [// Parent process
    "profileBeforeChange",
    "profileChangeTeardown",
    "quitApplicationGranted",
    "sendTelemetry",

    // Child processes
    "contentChildShutdown",

    // All processes
    "webWorkersShutdown",
    "xpcomWillShutdown",
    ]) {
    let k = _k;
    Object.defineProperty(this, k, {
      configurable: true,
      get() {
        delete this[k];
        let wrapped = AsyncShutdown[k]; // May be undefined, if we're on the wrong process.
        let result = wrapped ? new nsAsyncShutdownClient(wrapped) : undefined;
        Object.defineProperty(this, k, {
          value: result
        });
        return result;
      }
    });
  }

  // Hooks for testing purpose
  this.wrappedJSObject = {
    _propertyBagConverter: PropertyBagConverter
  };
}
nsAsyncShutdownService.prototype = {
  makeBarrier(name) {
    return new nsAsyncShutdownBarrier(new AsyncShutdown.Barrier(name));
  },

  /* ........ QueryInterface .............. */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAsyncShutdownService]),
  classID: Components.ID("{35c496de-a115-475d-93b5-ffa3f3ae6fe3}"),
};


this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
    nsAsyncShutdownService,
    nsAsyncShutdownBarrier,
    nsAsyncShutdownClient,
]);
PK
!<?/9{9{(components/PresentationControlService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
/* globals Components, dump */
"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

/* globals XPCOMUtils */
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/* globals Services */
Cu.import("resource://gre/modules/Services.jsm");
/* globals NetUtil */
Cu.import("resource://gre/modules/NetUtil.jsm");
/* globals setTimeout, clearTimeout */
Cu.import("resource://gre/modules/Timer.jsm");

/* globals ControllerStateMachine */
XPCOMUtils.defineLazyModuleGetter(this, "ControllerStateMachine", // jshint ignore:line
                                  "resource://gre/modules/presentation/ControllerStateMachine.jsm");
/* global ReceiverStateMachine */
XPCOMUtils.defineLazyModuleGetter(this, "ReceiverStateMachine", // jshint ignore:line
                                  "resource://gre/modules/presentation/ReceiverStateMachine.jsm");

const kProtocolVersion = 1; // need to review isCompatibleServer while fiddling the version number.
const kLocalCertName = "presentation";

const DEBUG = Services.prefs.getBoolPref("dom.presentation.tcp_server.debug");
function log(aMsg) {
  dump("-*- PresentationControlService.js: " + aMsg + "\n");
}

function TCPDeviceInfo(aAddress, aPort, aId, aCertFingerprint) {
  this.address = aAddress;
  this.port = aPort;
  this.id = aId;
  this.certFingerprint = aCertFingerprint || "";
}

function PresentationControlService() {
  this._id = null;
  this._port = 0;
  this._serverSocket = null;
}

PresentationControlService.prototype = {
  /**
   * If a user agent connects to this server, we create a control channel but
   * hand it to |TCPDevice.listener| when the initial information exchange
   * finishes. Therefore, we hold the control channels in this period.
   */
  _controlChannels: [],

  startServer: function(aEncrypted, aPort) {
    if (this._isServiceInit()) {
      DEBUG && log("PresentationControlService - server socket has been initialized");  // jshint ignore:line
      throw Cr.NS_ERROR_FAILURE;
    }

    /**
     * 0 or undefined indicates opt-out parameter, and a port will be selected
     * automatically.
     */
    let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1;

    if (aEncrypted) {
      let self = this;
      let localCertService = Cc["@mozilla.org/security/local-cert-service;1"]
                               .getService(Ci.nsILocalCertService);
      localCertService.getOrCreateCert(kLocalCertName, {
        handleCert: function(aCert, aRv) {
          DEBUG && log("PresentationControlService - handleCert");  // jshint ignore:line
          if (aRv) {
            self._notifyServerStopped(aRv);
          } else {
            self._serverSocket = Cc["@mozilla.org/network/tls-server-socket;1"]
                                   .createInstance(Ci.nsITLSServerSocket);

            self._serverSocketInit(serverSocketPort, aCert);
          }
        }
      });
    } else {
      this._serverSocket = Cc["@mozilla.org/network/server-socket;1"]
                             .createInstance(Ci.nsIServerSocket);

      this._serverSocketInit(serverSocketPort, null);
    }
  },

  _serverSocketInit: function(aPort, aCert) {
    if (!this._serverSocket) {
      DEBUG && log("PresentationControlService - create server socket fail."); // jshint ignore:line
      throw Cr.NS_ERROR_FAILURE;
    }

    try {
      this._serverSocket.init(aPort, false, -1);

      if (aCert) {
        this._serverSocket.serverCert = aCert;
        this._serverSocket.setSessionCache(false);
        this._serverSocket.setSessionTickets(false);
        let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
        this._serverSocket.setRequestClientCertificate(requestCert);
      }

      this._serverSocket.asyncListen(this);
    } catch (e) {
      // NS_ERROR_SOCKET_ADDRESS_IN_USE
      DEBUG && log("PresentationControlService - init server socket fail: " + e); // jshint ignore:line
      throw Cr.NS_ERROR_FAILURE;
    }

    this._port = this._serverSocket.port;

    DEBUG && log("PresentationControlService - service start on port: " + this._port); // jshint ignore:line

    // Monitor network interface change to restart server socket.
    Services.obs.addObserver(this, "network:offline-status-changed");

    this._notifyServerReady();
  },

  _notifyServerReady: function() {
    Services.tm.dispatchToMainThread(() => {
      if (this._listener) {
        this._listener.onServerReady(this._port, this.certFingerprint);
      }
    });
  },

  _notifyServerStopped: function(aRv) {
    Services.tm.dispatchToMainThread(() => {
      if (this._listener) {
        this._listener.onServerStopped(aRv);
      }
    });
  },

  isCompatibleServer: function(aVersion) {
    // No compatibility issue for the first version of control protocol
    return this.version === aVersion;
  },

  get id() {
    return this._id;
  },

  set id(aId) {
    this._id = aId;
  },

  get port() {
    return this._port;
  },

  get version() {
    return kProtocolVersion;
  },

  get certFingerprint() {
    if (!this._serverSocket.serverCert) {
      return null;
    }

    return this._serverSocket.serverCert.sha256Fingerprint;
  },

  set listener(aListener) {
    this._listener = aListener;
  },

  get listener() {
    return this._listener;
  },

  _isServiceInit: function() {
    return this._serverSocket !== null;
  },

  connect: function(aDeviceInfo) {
    if (!this.id) {
      DEBUG && log("PresentationControlService - Id has not initialized; connect fails"); // jshint ignore:line
      return null;
    }
    DEBUG && log("PresentationControlService - connect to " + aDeviceInfo.id); // jshint ignore:line

    let socketTransport = this._attemptConnect(aDeviceInfo);
    return new TCPControlChannel(this,
                                 socketTransport,
                                 aDeviceInfo,
                                 "sender");
  },

  _attemptConnect: function(aDeviceInfo) {
    let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
                .getService(Ci.nsISocketTransportService);

    let socketTransport;
    try {
      if (aDeviceInfo.certFingerprint) {
        let overrideService = Cc["@mozilla.org/security/certoverride;1"]
                                .getService(Ci.nsICertOverrideService);
        overrideService.rememberTemporaryValidityOverrideUsingFingerprint(
            aDeviceInfo.address,
            aDeviceInfo.port,
            aDeviceInfo.certFingerprint,
            Ci.nsICertOverrideService.ERROR_UNTRUSTED | Ci.nsICertOverrideService.ERROR_MISMATCH);

        socketTransport = sts.createTransport(["ssl"],
                                              1,
                                              aDeviceInfo.address,
                                              aDeviceInfo.port,
                                              null);
      } else {
        socketTransport = sts.createTransport(null,
                                              0,
                                              aDeviceInfo.address,
                                              aDeviceInfo.port,
                                              null);
      }
      // Shorten the connection failure procedure.
      socketTransport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
    } catch (e) {
      DEBUG && log("PresentationControlService - createTransport throws: " + e);  // jshint ignore:line
      // Pop the exception to |TCPDevice.establishControlChannel|
      throw Cr.NS_ERROR_FAILURE;
    }
    return socketTransport;
  },

  responseSession: function(aDeviceInfo, aSocketTransport) {
    if (!this._isServiceInit()) {
      DEBUG && log("PresentationControlService - should never receive remote " +
                   "session request before server socket initialization"); // jshint ignore:line
      return null;
    }
    DEBUG && log("PresentationControlService - responseSession to " +
                 JSON.stringify(aDeviceInfo)); // jshint ignore:line
    return new TCPControlChannel(this,
                                 aSocketTransport,
                                 aDeviceInfo,
                                 "receiver");
  },

  // Triggered by TCPControlChannel
  onSessionRequest: function(aDeviceInfo, aUrl, aPresentationId, aControlChannel) {
    DEBUG && log("PresentationControlService - onSessionRequest: " +
                 aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
    if (!this.listener) {
      this.releaseControlChannel(aControlChannel);
      return;
    }

    this.listener.onSessionRequest(aDeviceInfo,
                                   aUrl,
                                   aPresentationId,
                                   aControlChannel);
    this.releaseControlChannel(aControlChannel);
  },

  onSessionTerminate: function(aDeviceInfo, aPresentationId, aControlChannel, aIsFromReceiver) {
    DEBUG && log("TCPPresentationServer - onSessionTerminate: " +
                 aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
    if (!this.listener) {
      this.releaseControlChannel(aControlChannel);
      return;
    }

    this.listener.onTerminateRequest(aDeviceInfo,
                                     aPresentationId,
                                     aControlChannel,
                                     aIsFromReceiver);
    this.releaseControlChannel(aControlChannel);
  },

  onSessionReconnect: function(aDeviceInfo, aUrl, aPresentationId, aControlChannel) {
    DEBUG && log("TCPPresentationServer - onSessionReconnect: " +
                 aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
    if (!this.listener) {
      this.releaseControlChannel(aControlChannel);
      return;
    }

    this.listener.onReconnectRequest(aDeviceInfo,
                                     aUrl,
                                     aPresentationId,
                                     aControlChannel);
    this.releaseControlChannel(aControlChannel);
  },

  // nsIServerSocketListener (Triggered by nsIServerSocket.init)
  onSocketAccepted: function(aServerSocket, aClientSocket) {
    DEBUG && log("PresentationControlService - onSocketAccepted: " +
                 aClientSocket.host + ":" + aClientSocket.port); // jshint ignore:line
    let deviceInfo = new TCPDeviceInfo(aClientSocket.host, aClientSocket.port);
    this.holdControlChannel(this.responseSession(deviceInfo, aClientSocket));
  },

  holdControlChannel: function(aControlChannel) {
    this._controlChannels.push(aControlChannel);
  },

  releaseControlChannel: function(aControlChannel) {
    let index = this._controlChannels.indexOf(aControlChannel);
    if (index !== -1) {
      delete this._controlChannels[index];
    }
  },

  // nsIServerSocketListener (Triggered by nsIServerSocket.init)
  onStopListening: function(aServerSocket, aStatus) {
    DEBUG && log("PresentationControlService - onStopListening: " + aStatus); // jshint ignore:line
  },

  close: function() {
    DEBUG && log("PresentationControlService - close"); // jshint ignore:line
    if (this._isServiceInit()) {
      DEBUG && log("PresentationControlService - close server socket"); // jshint ignore:line
      this._serverSocket.close();
      this._serverSocket = null;

      Services.obs.removeObserver(this, "network:offline-status-changed");

      this._notifyServerStopped(Cr.NS_OK);
    }
    this._port = 0;
  },

  // nsIObserver
  observe: function(aSubject, aTopic, aData) {
    DEBUG && log("PresentationControlService - observe: " + aTopic); // jshint ignore:line
    switch (aTopic) {
      case "network:offline-status-changed": {
        if (aData == "offline") {
          DEBUG && log("network offline"); // jshint ignore:line
          return;
        }
        this._restartServer();
        break;
      }
    }
  },

  _restartServer: function() {
    DEBUG && log("PresentationControlService - restart service"); // jshint ignore:line

    // restart server socket
    if (this._isServiceInit()) {
      this.close();

      try {
        this.startServer();
      } catch (e) {
        DEBUG && log("PresentationControlService - restart service fail: " + e); // jshint ignore:line
      }
    }
  },

  classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"),
  QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener,
                                          Ci.nsIPresentationControlService,
                                          Ci.nsIObserver]),
};

function ChannelDescription(aInit) {
  this._type = aInit.type;
  switch (this._type) {
    case Ci.nsIPresentationChannelDescription.TYPE_TCP:
      this._tcpAddresses = Cc["@mozilla.org/array;1"]
                           .createInstance(Ci.nsIMutableArray);
      for (let address of aInit.tcpAddress) {
        let wrapper = Cc["@mozilla.org/supports-cstring;1"]
                      .createInstance(Ci.nsISupportsCString);
        wrapper.data = address;
        this._tcpAddresses.appendElement(wrapper);
      }

      this._tcpPort = aInit.tcpPort;
      break;
    case Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL:
      this._dataChannelSDP = aInit.dataChannelSDP;
      break;
  }
}

ChannelDescription.prototype = {
  _type: 0,
  _tcpAddresses: null,
  _tcpPort: 0,
  _dataChannelSDP: "",

  get type() {
    return this._type;
  },

  get tcpAddress() {
    return this._tcpAddresses;
  },

  get tcpPort() {
    return this._tcpPort;
  },

  get dataChannelSDP() {
    return this._dataChannelSDP;
  },

  classID: Components.ID("{82507aea-78a2-487e-904a-858a6c5bf4e1}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
};

// Helper function: transfer nsIPresentationChannelDescription to json
function discriptionAsJson(aDescription) {
  let json = {};
  json.type = aDescription.type;
  switch(aDescription.type) {
    case Ci.nsIPresentationChannelDescription.TYPE_TCP:
      let addresses = aDescription.tcpAddress.QueryInterface(Ci.nsIArray);
      json.tcpAddress = [];
      for (let idx = 0; idx < addresses.length; idx++) {
        let address = addresses.queryElementAt(idx, Ci.nsISupportsCString);
        json.tcpAddress.push(address.data);
      }
      json.tcpPort = aDescription.tcpPort;
      break;
    case Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL:
      json.dataChannelSDP = aDescription.dataChannelSDP;
      break;
  }
  return json;
}

const kDisconnectTimeout = 5000;
const kTerminateTimeout = 5000;

function TCPControlChannel(presentationService,
                           transport,
                           deviceInfo,
                           direction) {
  DEBUG && log("create TCPControlChannel for : " + direction); // jshint ignore:line
  this._deviceInfo = deviceInfo;
  this._direction = direction;
  this._transport = transport;

  this._presentationService = presentationService;

  if (direction === "receiver") {
    // Need to set security observer before I/O stream operation.
    this._setSecurityObserver(this);
  }

  let currentThread = Services.tm.currentThread;
  transport.setEventSink(this, currentThread);

  this._input = this._transport.openInputStream(0, 0, 0)
                               .QueryInterface(Ci.nsIAsyncInputStream);
  this._input.asyncWait(this.QueryInterface(Ci.nsIStreamListener),
                        Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY,
                        0,
                        currentThread);

  this._output = this._transport
                     .openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0)
                     .QueryInterface(Ci.nsIAsyncOutputStream);

  this._outgoingMsgs = [];


  this._stateMachine =
    (direction === "sender") ? new ControllerStateMachine(this, presentationService.id)
                             : new ReceiverStateMachine(this);

  if (direction === "receiver" && !transport.securityInfo) {
    // Since the transport created by server socket is already CONNECTED_TO.
    this._outgoingEnabled = true;
    this._createInputStreamPump();
  }
}

TCPControlChannel.prototype = {
  _outgoingEnabled: false,
  _incomingEnabled: false,
  _pendingOpen: false,
  _pendingOffer: null,
  _pendingAnswer: null,
  _pendingClose: null,
  _pendingCloseReason: null,
  _pendingReconnect: false,

  sendOffer: function(aOffer) {
    this._stateMachine.sendOffer(discriptionAsJson(aOffer));
  },

  sendAnswer: function(aAnswer) {
    this._stateMachine.sendAnswer(discriptionAsJson(aAnswer));
  },

  sendIceCandidate: function(aCandidate) {
    this._stateMachine.updateIceCandidate(aCandidate);
  },

  launch: function(aPresentationId, aUrl) {
    this._stateMachine.launch(aPresentationId, aUrl);
  },

  terminate: function(aPresentationId) {
    if (!this._terminatingId) {
      this._terminatingId = aPresentationId;
      this._stateMachine.terminate(aPresentationId);

      // Start a guard timer to ensure terminateAck is processed.
      this._terminateTimer = setTimeout(() => {
        DEBUG && log("TCPControlChannel - terminate timeout: " + aPresentationId); // jshint ignore:line
        delete this._terminateTimer;
        if (this._pendingDisconnect) {
          this._pendingDisconnect();
        } else {
          this.disconnect(Cr.NS_OK);
        }
      }, kTerminateTimeout);
    } else {
      this._stateMachine.terminateAck(aPresentationId);
      delete this._terminatingId;
    }
  },

  _flushOutgoing: function() {
    if (!this._outgoingEnabled || this._outgoingMsgs.length === 0) {
      return;
    }

    this._output.asyncWait(this, 0, 0, Services.tm.currentThread);
  },

  // may throw an exception
  _send: function(aMsg) {
    DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); // jshint ignore:line

    /**
     * XXX In TCP streaming, it is possible that more than one message in one
     * TCP packet. We use line delimited JSON to identify where one JSON encoded
     * object ends and the next begins. Therefore, we do not allow newline
     * characters whithin the whole message, and add a newline at the end.
     * Please see the parser code in |onDataAvailable|.
     */
    let message = JSON.stringify(aMsg).replace(["\n"], "") + "\n";
    try {
      this._output.write(message, message.length);
    } catch(e) {
      DEBUG && log("TCPControlChannel - Failed to send message: " + e.name); // jshint ignore:line
      throw e;
    }
  },

  _setSecurityObserver: function(observer) {
    if (this._transport && this._transport.securityInfo) {
      DEBUG && log("TCPControlChannel - setSecurityObserver: " + observer); // jshint ignore:line
      let connectionInfo = this._transport.securityInfo
                               .QueryInterface(Ci.nsITLSServerConnectionInfo);
      connectionInfo.setSecurityObserver(observer);
    }
  },

  // nsITLSServerSecurityObserver
  onHandshakeDone: function(socket, clientStatus) {
    log("TCPControlChannel - onHandshakeDone: TLS version: " + clientStatus.tlsVersionUsed.toString(16));
    this._setSecurityObserver(null);

    // Process input/output after TLS handshake is complete.
    this._outgoingEnabled = true;
    this._createInputStreamPump();
  },

  // nsIAsyncOutputStream
  onOutputStreamReady: function() {
    DEBUG && log("TCPControlChannel - onOutputStreamReady"); // jshint ignore:line
    if (this._outgoingMsgs.length === 0) {
      return;
    }

    try {
      this._send(this._outgoingMsgs[0]);
    } catch (e) {
      if (e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this._output.asyncWait(this, 0, 0, Services.tm.currentThread);
        return;
      }

      this._closeTransport();
      return;
    }
    this._outgoingMsgs.shift();
    this._flushOutgoing();
  },

  // nsIAsyncInputStream (Triggered by nsIInputStream.asyncWait)
  // Only used for detecting connection refused
  onInputStreamReady: function(aStream) {
    DEBUG && log("TCPControlChannel - onInputStreamReady"); // jshint ignore:line
    try {
      aStream.available();
    } catch (e) {
      DEBUG && log("TCPControlChannel - onInputStreamReady error: " + e.name); // jshint ignore:line
      // NS_ERROR_CONNECTION_REFUSED
      this._notifyDisconnected(e.result);
    }
  },

  // nsITransportEventSink (Triggered by nsISocketTransport.setEventSink)
  onTransportStatus: function(aTransport, aStatus) {
    DEBUG && log("TCPControlChannel - onTransportStatus: " + aStatus.toString(16) +
                 " with role: " + this._direction); // jshint ignore:line
    if (aStatus === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
      this._outgoingEnabled = true;
      this._createInputStreamPump();
    }
  },

  // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
  onStartRequest: function() {
    DEBUG && log("TCPControlChannel - onStartRequest with role: " +
                 this._direction); // jshint ignore:line
    this._incomingEnabled = true;
  },

  // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
  onStopRequest: function(aRequest, aContext, aStatus) {
    DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus +
                 " with role: " + this._direction); // jshint ignore:line
    this._stateMachine.onChannelClosed(aStatus, true);
  },

  // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
  onDataAvailable: function(aRequest, aContext, aInputStream) {
    let data = NetUtil.readInputStreamToString(aInputStream,
                                               aInputStream.available());
    DEBUG && log("TCPControlChannel - onDataAvailable: " + data); // jshint ignore:line

    // Parser of line delimited JSON. Please see |_send| for more informaiton.
    let jsonArray = data.split("\n");
    jsonArray.pop();
    for (let json of jsonArray) {
      let msg;
      try {
        msg = JSON.parse(json);
      } catch (e) {
        DEBUG && log("TCPSignalingChannel - error in parsing json: " + e); // jshint ignore:line
      }

      this._handleMessage(msg);
    }
  },

  _createInputStreamPump: function() {
    if (this._pump) {
      return;
    }

    DEBUG && log("TCPControlChannel - create pump with role: " +
                 this._direction); // jshint ignore:line
    this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].
               createInstance(Ci.nsIInputStreamPump);
    this._pump.init(this._input, -1, -1, 0, 0, false);
    this._pump.asyncRead(this, null);
    this._stateMachine.onChannelReady();
  },

  // Handle command from remote side
  _handleMessage: function(aMsg) {
    DEBUG && log("TCPControlChannel - handleMessage from " +
                 JSON.stringify(this._deviceInfo) + ": " + JSON.stringify(aMsg)); // jshint ignore:line
    this._stateMachine.onCommand(aMsg);
  },

  get listener() {
    return this._listener;
  },

  set listener(aListener) {
    DEBUG && log("TCPControlChannel - set listener: " + aListener); // jshint ignore:line
    if (!aListener) {
      this._listener = null;
      return;
    }

    this._listener = aListener;
    if (this._pendingOpen) {
      this._pendingOpen = false;
      DEBUG && log("TCPControlChannel - notify pending opened"); // jshint ignore:line
      this._listener.notifyConnected();
    }

    if (this._pendingOffer) {
      let offer = this._pendingOffer;
      DEBUG && log("TCPControlChannel - notify pending offer: " +
                   JSON.stringify(offer)); // jshint ignore:line
      this._listener.onOffer(new ChannelDescription(offer));
      this._pendingOffer = null;
    }

    if (this._pendingAnswer) {
      let answer = this._pendingAnswer;
      DEBUG && log("TCPControlChannel - notify pending answer: " +
                   JSON.stringify(answer)); // jshint ignore:line
      this._listener.onAnswer(new ChannelDescription(answer));
      this._pendingAnswer = null;
    }

    if (this._pendingClose) {
      DEBUG && log("TCPControlChannel - notify pending closed"); // jshint ignore:line
      this._notifyDisconnected(this._pendingCloseReason);
      this._pendingClose = null;
    }

    if (this._pendingReconnect) {
      DEBUG && log("TCPControlChannel - notify pending reconnected"); // jshint ignore:line
      this._notifyReconnected();
      this._pendingReconnect = false;
    }
  },

  /**
   * These functions are designed to handle the interaction with listener
   * appropriately. |_FUNC| is to handle |this._listener.FUNC|.
   */
  _onOffer: function(aOffer) {
    if (!this._incomingEnabled) {
      return;
    }
    if (!this._listener) {
      this._pendingOffer = aOffer;
      return;
    }
    DEBUG && log("TCPControlChannel - notify offer: " +
                 JSON.stringify(aOffer)); // jshint ignore:line
    this._listener.onOffer(new ChannelDescription(aOffer));
  },

  _onAnswer: function(aAnswer) {
    if (!this._incomingEnabled) {
      return;
    }
    if (!this._listener) {
      this._pendingAnswer = aAnswer;
      return;
    }
    DEBUG && log("TCPControlChannel - notify answer: " +
                 JSON.stringify(aAnswer)); // jshint ignore:line
    this._listener.onAnswer(new ChannelDescription(aAnswer));
  },

  _notifyConnected: function() {
    this._pendingClose = false;
    this._pendingCloseReason = Cr.NS_OK;

    if (!this._listener) {
      this._pendingOpen = true;
      return;
    }

    DEBUG && log("TCPControlChannel - notify opened with role: " +
                 this._direction); // jshint ignore:line
    this._listener.notifyConnected();
  },

  _notifyDisconnected: function(aReason) {
    this._pendingOpen = false;
    this._pendingOffer = null;
    this._pendingAnswer = null;

    // Remote endpoint closes the control channel with abnormal reason.
    if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
      aReason = this._pendingCloseReason;
    }

    if (!this._listener) {
      this._pendingClose = true;
      this._pendingCloseReason = aReason;
      return;
    }

    DEBUG && log("TCPControlChannel - notify closed with role: " +
                 this._direction); // jshint ignore:line
    this._listener.notifyDisconnected(aReason);
  },

  _notifyReconnected: function() {
    if (!this._listener) {
      this._pendingReconnect = true;
      return;
    }

    DEBUG && log("TCPControlChannel - notify reconnected with role: " +
                 this._direction); // jshint ignore:line
    this._listener.notifyReconnected();
  },

  _closeOutgoing: function() {
    if (this._outgoingEnabled) {
      this._output.close();
      this._outgoingEnabled = false;
    }
  },
  _closeIncoming: function() {
    if (this._incomingEnabled) {
      this._pump = null;
      this._input.close();
      this._incomingEnabled = false;
    }
  },
  _closeTransport: function() {
    if (this._disconnectTimer) {
      clearTimeout(this._disconnectTimer);
      delete this._disconnectTimer;
    }

    if (this._terminateTimer) {
      clearTimeout(this._terminateTimer);
      delete this._terminateTimer;
    }

    delete this._pendingDisconnect;

    this._transport.setEventSink(null, null);

    this._closeIncoming();
    this._closeOutgoing();
    this._presentationService.releaseControlChannel(this);
  },

  disconnect: function(aReason) {
    DEBUG && log("TCPControlChannel - disconnect with reason: " + aReason); // jshint ignore:line

    // Pending disconnect during termination procedure.
    if (this._terminateTimer) {
      // Store only the first disconnect action.
      if (!this._pendingDisconnect) {
        this._pendingDisconnect = this.disconnect.bind(this, aReason);
      }
      return;
    }

    if (this._outgoingEnabled && !this._disconnectTimer) {
      // default reason is NS_OK
      aReason = !aReason ? Cr.NS_OK : aReason;

      this._stateMachine.onChannelClosed(aReason, false);

      // Start a guard timer to ensure the transport will be closed.
      this._disconnectTimer = setTimeout(() => {
        DEBUG && log("TCPControlChannel - disconnect timeout"); // jshint ignore:line
        this._closeTransport();
      }, kDisconnectTimeout);
    }
  },

  reconnect: function(aPresentationId, aUrl) {
    DEBUG && log("TCPControlChannel - reconnect with role: " +
                 this._direction); // jshint ignore:line
    if (this._direction != "sender") {
      return Cr.NS_ERROR_FAILURE;
    }

    this._stateMachine.reconnect(aPresentationId, aUrl);
  },

  // callback from state machine
  sendCommand: function(command) {
    this._outgoingMsgs.push(command);
    this._flushOutgoing();
  },

  notifyDeviceConnected: function(deviceId) {
    switch (this._direction) {
      case "receiver":
        this._deviceInfo.id = deviceId;
        break;
    }
    this._notifyConnected();
  },

  notifyDisconnected: function(reason) {
    this._closeTransport();
    this._notifyDisconnected(reason);
  },

  notifyLaunch: function(presentationId, url) {
    switch (this._direction) {
      case "receiver":
        this._presentationService.onSessionRequest(this._deviceInfo,
                                                   url,
                                                   presentationId,
                                                   this);
      break;
    }
  },

  notifyTerminate: function(presentationId) {
    if (!this._terminatingId) {
      this._terminatingId = presentationId;
      this._presentationService.onSessionTerminate(this._deviceInfo,
                                                   presentationId,
                                                   this,
                                                   this._direction === "sender");
      return;
    }

    // Cancel terminate guard timer after receiving terminate-ack.
    if (this._terminateTimer) {
      clearTimeout(this._terminateTimer);
      delete this._terminateTimer;
    }

    if (this._terminatingId !== presentationId) {
      // Requested presentation Id doesn't matched with the one in ACK.
      // Disconnect the control channel with error.
      DEBUG && log("TCPControlChannel - unmatched terminatingId: " + presentationId); // jshint ignore:line
      this.disconnect(Cr.NS_ERROR_FAILURE);
    }

    delete this._terminatingId;
    if (this._pendingDisconnect) {
      this._pendingDisconnect();
    }
  },

  notifyReconnect: function(presentationId, url) {
    switch (this._direction) {
      case "receiver":
        this._presentationService.onSessionReconnect(this._deviceInfo,
                                                     url,
                                                     presentationId,
                                                     this);
        break;
      case "sender":
        this._notifyReconnected();
        break;
    }
  },

  notifyOffer: function(offer) {
    this._onOffer(offer);
  },

  notifyAnswer: function(answer) {
    this._onAnswer(answer);
  },

  notifyIceCandidate: function(candidate) {
    this._listener.onIceCandidate(candidate);
  },

  classID: Components.ID("{fefb8286-0bdc-488b-98bf-0c11b485c955}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel,
                                         Ci.nsIStreamListener]),
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationControlService]); // jshint ignore:line
PK
!<pt--5components/PresentationDataChannelSessionTransport.js/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If 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");

// Bug 1228209 - plan to remove this eventually
function log(aMsg) {
  //dump("-*- PresentationDataChannelSessionTransport.js : " + aMsg + "\n");
}

const PRESENTATIONTRANSPORT_CID = Components.ID("{dd2bbf2f-3399-4389-8f5f-d382afb8b2d6}");
const PRESENTATIONTRANSPORT_CONTRACTID = "mozilla.org/presentation/datachanneltransport;1";

const PRESENTATIONTRANSPORTBUILDER_CID = Components.ID("{215b2f62-46e2-4004-a3d1-6858e56c20f3}");
const PRESENTATIONTRANSPORTBUILDER_CONTRACTID = "mozilla.org/presentation/datachanneltransportbuilder;1";

function PresentationDataChannelDescription(aDataChannelSDP) {
  this._dataChannelSDP = JSON.stringify(aDataChannelSDP);
}

PresentationDataChannelDescription.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
  get type() {
    return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
  },
  get tcpAddress() {
    return null;
  },
  get tcpPort() {
    return null;
  },
  get dataChannelSDP() {
    return this._dataChannelSDP;
  }
};

function PresentationTransportBuilder() {
  log("PresentationTransportBuilder construct");
  this._isControlChannelNeeded = true;
}

PresentationTransportBuilder.prototype = {
  classID: PRESENTATIONTRANSPORTBUILDER_CID,
  contractID: PRESENTATIONTRANSPORTBUILDER_CONTRACTID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilder,
                                         Ci.nsIPresentationDataChannelSessionTransportBuilder,
                                         Ci.nsITimerCallback]),

  buildDataChannelTransport: function(aRole, aWindow, aListener) {
    if (!aRole || !aWindow || !aListener) {
      log("buildDataChannelTransport with illegal parameters");
      throw Cr.NS_ERROR_ILLEGAL_VALUE;
    }

    if (this._window) {
      log("buildDataChannelTransport has started.");
      throw Cr.NS_ERROR_UNEXPECTED;
    }

    log("buildDataChannelTransport with role " + aRole);
    this._role = aRole;
    this._window = aWindow;
    this._listener = aListener.QueryInterface(Ci.nsIPresentationSessionTransportBuilderListener);

    // TODO bug 1227053 set iceServers from |nsIPresentationDevice|
    this._peerConnection = new this._window.RTCPeerConnection();

    // |this._listener == null| will throw since the control channel is
    // abnormally closed.
    this._peerConnection.onicecandidate = aEvent => aEvent.candidate &&
      this._listener.sendIceCandidate(JSON.stringify(aEvent.candidate));

    this._peerConnection.onnegotiationneeded = () => {
      log("onnegotiationneeded with role " + this._role);
      if (!this._peerConnection) {
        log("ignoring negotiationneeded without PeerConnection");
        return;
      }
      this._peerConnection.createOffer()
          .then(aOffer => this._peerConnection.setLocalDescription(aOffer))
          .then(() => this._listener
                          .sendOffer(new PresentationDataChannelDescription(this._peerConnection.localDescription)))
          .catch(e => this._reportError(e));
    }

    switch (this._role) {
      case Ci.nsIPresentationService.ROLE_CONTROLLER:
        this._dataChannel = this._peerConnection.createDataChannel("presentationAPI");
        this._setDataChannel();
        break;

      case Ci.nsIPresentationService.ROLE_RECEIVER:
        this._peerConnection.ondatachannel = aEvent => {
          this._dataChannel = aEvent.channel;
          // Ensure the binaryType of dataChannel is blob.
          this._dataChannel.binaryType = "blob";
          this._setDataChannel();
        }
        break;
      default:
       throw Cr.NS_ERROR_ILLEGAL_VALUE;
    }

    // TODO bug 1228235 we should have a way to let device providers customize
    // the time-out duration.
    let timeout = Services.prefs.getIntPref("presentation.receiver.loading.timeout", 10000);

    // The timer is to check if the negotiation finishes on time.
    this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._timer.initWithCallback(this, timeout, this._timer.TYPE_ONE_SHOT);
  },

  notify: function() {
    if (!this._sessionTransport) {
      this._cleanup(Cr.NS_ERROR_NET_TIMEOUT);
    }
  },

  _reportError: function(aError) {
    log("report Error " + aError.name + ":" + aError.message);
    this._cleanup(Cr.NS_ERROR_FAILURE);
  },

  _setDataChannel: function() {
    this._dataChannel.onopen = () => {
      log("data channel is open, notify the listener, role " + this._role);

      // Handoff the ownership of _peerConnection and _dataChannel to
      // _sessionTransport
      this._sessionTransport = new PresentationTransport();
      this._sessionTransport.init(this._peerConnection, this._dataChannel, this._window);
      this._peerConnection.onicecandidate = null;
      this._peerConnection.onnegotiationneeded = null;
      this._peerConnection = this._dataChannel = null;

      this._listener.onSessionTransport(this._sessionTransport);
      this._sessionTransport.callback.notifyTransportReady();

      this._cleanup(Cr.NS_OK);
    };

    this._dataChannel.onerror = aError => {
      log("data channel onerror " + aError.name + ":" + aError.message);
      this._cleanup(Cr.NS_ERROR_FAILURE);
    }
  },

  _cleanup: function(aReason) {
    if (aReason != Cr.NS_OK) {
      this._listener.onError(aReason);
    }

    if (this._dataChannel) {
      this._dataChannel.close();
      this._dataChannel = null;
    }

    if (this._peerConnection) {
      this._peerConnection.close();
      this._peerConnection = null;
    }

    this._role = null;
    this._window = null;

    this._listener = null;
    this._sessionTransport = null;

    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  // nsIPresentationControlChannelListener
  onOffer: function(aOffer) {
    if (this._role !== Ci.nsIPresentationService.ROLE_RECEIVER ||
          this._sessionTransport) {
      log("onOffer status error");
      this._cleanup(Cr.NS_ERROR_FAILURE);
    }

    log("onOffer: " + aOffer.dataChannelSDP + " with role " + this._role);

    let offer = new this._window
                        .RTCSessionDescription(JSON.parse(aOffer.dataChannelSDP));

    this._peerConnection.setRemoteDescription(offer)
        .then(() => this._peerConnection.signalingState == "stable" ||
                      this._peerConnection.createAnswer())
        .then(aAnswer => this._peerConnection.setLocalDescription(aAnswer))
        .then(() => {
          this._isControlChannelNeeded = false;
          this._listener
              .sendAnswer(new PresentationDataChannelDescription(this._peerConnection.localDescription))
        }).catch(e => this._reportError(e));
  },

  onAnswer: function(aAnswer) {
    if (this._role !== Ci.nsIPresentationService.ROLE_CONTROLLER ||
          this._sessionTransport) {
      log("onAnswer status error");
      this._cleanup(Cr.NS_ERROR_FAILURE);
    }

    log("onAnswer: " + aAnswer.dataChannelSDP + " with role " + this._role);

    let answer = new this._window
                         .RTCSessionDescription(JSON.parse(aAnswer.dataChannelSDP));

    this._peerConnection.setRemoteDescription(answer).catch(e => this._reportError(e));
    this._isControlChannelNeeded = false;
  },

  onIceCandidate: function(aCandidate) {
    log("onIceCandidate: " + aCandidate + " with role " + this._role);
    if (!this._window || !this._peerConnection) {
      log("ignoring ICE candidate after connection");
      return;
    }
    let candidate = new this._window.RTCIceCandidate(JSON.parse(aCandidate));
    this._peerConnection.addIceCandidate(candidate).catch(e => this._reportError(e));
  },

  notifyDisconnected: function(aReason) {
    log("notifyDisconnected reason: " + aReason);

    if (aReason != Cr.NS_OK) {
      this._cleanup(aReason);
    } else if (this._isControlChannelNeeded) {
      this._cleanup(Cr.NS_ERROR_FAILURE);
    }
  },
};

function PresentationTransport() {
  this._messageQueue = [];
  this._closeReason = Cr.NS_OK;
}

PresentationTransport.prototype = {
  classID: PRESENTATIONTRANSPORT_CID,
  contractID: PRESENTATIONTRANSPORT_CONTRACTID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport]),

  init: function(aPeerConnection, aDataChannel, aWindow) {
    log("initWithDataChannel");
    this._enableDataNotification = false;
    this._dataChannel = aDataChannel;
    this._peerConnection = aPeerConnection;
    this._window = aWindow;

    this._dataChannel.onopen = () => {
      log("data channel reopen. Should never touch here");
    };

    this._dataChannel.onclose = () => {
      log("data channel onclose");
      if (this._callback) {
        this._callback.notifyTransportClosed(this._closeReason);
      }
      this._cleanup();
    }

    this._dataChannel.onmessage = aEvent => {
      log("data channel onmessage " + aEvent.data);

      if (!this._enableDataNotification || !this._callback) {
        log("queue message");
        this._messageQueue.push(aEvent.data);
        return;
      }
      this._doNotifyData(aEvent.data);
    };

    this._dataChannel.onerror = aError => {
      log("data channel onerror " + aError.name + ":" + aError.message);
      if (this._callback) {
        this._callback.notifyTransportClosed(Cr.NS_ERROR_FAILURE);
      }
      this._cleanup();
    }
  },

  // nsIPresentationTransport
  get selfAddress() {
    throw NS_ERROR_NOT_AVAILABLE;
  },

  get callback() {
    return this._callback;
  },

  set callback(aCallback) {
    this._callback = aCallback;
  },

  send: function(aData) {
    log("send " + aData);
    this._dataChannel.send(aData);
  },

  sendBinaryMsg: function(aData) {
    log("sendBinaryMsg");

    let array = new Uint8Array(aData.length);
    for (let i = 0; i < aData.length; i++) {
      array[i] = aData.charCodeAt(i);
    }

    this._dataChannel.send(array);
  },

  sendBlob: function(aBlob) {
    log("sendBlob");

    this._dataChannel.send(aBlob);
  },

  enableDataNotification: function() {
    log("enableDataNotification");
    if (this._enableDataNotification) {
      return;
    }

    if (!this._callback) {
      throw NS_ERROR_NOT_AVAILABLE;
    }

    this._enableDataNotification = true;

    this._messageQueue.forEach(aData => this._doNotifyData(aData));
    this._messageQueue = [];
  },

  close: function(aReason) {
    this._closeReason = aReason;

    this._dataChannel.close();
  },

  _cleanup: function() {
    this._dataChannel = null;

    if (this._peerConnection) {
      this._peerConnection.close();
      this._peerConnection = null;
    }
    this._callback = null;
    this._messageQueue = [];
    this._window = null;
  },

  _doNotifyData: function(aData) {
    if (!this._callback) {
      throw NS_ERROR_NOT_AVAILABLE;
    }

    if (aData instanceof this._window.Blob) {
      let reader = new this._window.FileReader();
      reader.addEventListener("load", (aEvent) => {
        this._callback.notifyData(aEvent.target.result, true);
      });
      reader.readAsBinaryString(aData);
    } else {
      this._callback.notifyData(aData, false);
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationTransportBuilder,
                                                     PresentationTransport]);
PK
!<&$

components/mozIntl.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 Cc = Components.classes;
const Ci = Components.interfaces;

const mozIntlHelper =
  Cc["@mozilla.org/mozintlhelper;1"].getService(Ci.mozIMozIntlHelper);
const localeSvc =
  Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
const osPrefs =
  Cc["@mozilla.org/intl/ospreferences;1"].getService(Ci.mozIOSPreferences);

/**
 * This helper function retrives currently used app locales, allowing
 * all mozIntl APIs to use the current regional prefs locales unless
 * called with explicitly listed locales.
 */
function getLocales(locales) {
  if (!locales) {
    return localeSvc.getRegionalPrefsLocales();
  }
  return locales;
}

function getDateTimePatternStyle(option) {
  switch (option) {
    case "full":
      return osPrefs.dateTimeFormatStyleFull;
    case "long":
      return osPrefs.dateTimeFormatStyleLong;
    case "medium":
      return osPrefs.dateTimeFormatStyleMedium;
    case "short":
      return osPrefs.dateTimeFormatStyleShort;
    default:
      return osPrefs.dateTimeFormatStyleNone;
  }
}

class MozIntl {
  constructor() {
    this._cache = {};
  }

  getCalendarInfo(locales, ...args) {
    if (!this._cache.hasOwnProperty("getCalendarInfo")) {
      mozIntlHelper.addGetCalendarInfo(this._cache);
    }

    return this._cache.getCalendarInfo(getLocales(locales), ...args);
  }

  getDisplayNames(locales, ...args) {
    if (!this._cache.hasOwnProperty("getDisplayNames")) {
      mozIntlHelper.addGetDisplayNames(this._cache);
    }

    return this._cache.getDisplayNames(getLocales(locales), ...args);
  }

  getLocaleInfo(locales, ...args) {
    if (!this._cache.hasOwnProperty("getLocaleInfo")) {
      mozIntlHelper.addGetLocaleInfo(this._cache);
    }

    return this._cache.getLocaleInfo(getLocales(locales), ...args);
  }

  createPluralRules(locales, ...args) {
    if (!this._cache.hasOwnProperty("PluralRules")) {
      mozIntlHelper.addPluralRulesConstructor(this._cache);
    }

    return new this._cache.PluralRules(getLocales(locales), ...args);
  }

  createDateTimeFormat(locales, options, ...args) {
    if (!this._cache.hasOwnProperty("DateTimeFormat")) {
      mozIntlHelper.addDateTimeFormatConstructor(this._cache);
    }

    let resolvedLocales =
      this._cache.DateTimeFormat.supportedLocalesOf(getLocales(locales));

    if (options) {
      if (options.dateStyle || options.timeStyle) {
        options.pattern = osPrefs.getDateTimePattern(
          getDateTimePatternStyle(options.dateStyle),
          getDateTimePatternStyle(options.timeStyle),
          resolvedLocales[0]);
      } else {
        // make sure that user doesn't pass a pattern explicitly
        options.pattern = undefined;
      }
    }

    return new this._cache.DateTimeFormat(resolvedLocales, options, ...args);
  }
}

MozIntl.prototype.classID = Components.ID("{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}");
MozIntl.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.mozIMozIntl, Ci.nsISupports]);

var components = [MozIntl];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
PK
!<C(.4.4&components/extension-process-script.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 script contains the minimum, skeleton content process code that we need
 * in order to lazily load other extension modules when they are first
 * necessary. Anything which is not likely to be needed immediately, or shortly
 * after startup, in *every* browser process live outside of this file.
 */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
                                  "resource://gre/modules/ExtensionChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionContent",
                                  "resource://gre/modules/ExtensionContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPageChild",
                                  "resource://gre/modules/ExtensionPageChild.jsm");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());

const {
  DefaultWeakMap,
  getInnerWindowID,
} = ExtensionUtils;

// We need to avoid touching Services.appinfo here in order to prevent
// the wrong version from being cached during xpcshell test startup.
const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT;

function parseScriptOptions(options) {
  return {
    allFrames: options.all_frames,
    matchAboutBlank: options.match_about_blank,
    frameID: options.frame_id,
    runAt: options.run_at,

    matches: new MatchPatternSet(options.matches),
    excludeMatches: new MatchPatternSet(options.exclude_matches || []),
    includeGlobs: options.include_globs && options.include_globs.map(glob => new MatchGlob(glob)),
    excludeGlobs: options.exclude_globs && options.exclude_globs.map(glob => new MatchGlob(glob)),

    jsPaths: options.js || [],
    cssPaths: options.css || [],
  };
}

var extensions = new DefaultWeakMap(policy => {
  let extension = new ExtensionChild.BrowserExtensionContent(policy.initData);
  extension.policy = policy;
  return extension;
});

var contentScripts = new DefaultWeakMap(matcher => {
  return new ExtensionContent.Script(extensions.get(matcher.extension),
                                     matcher);
});

function getMessageManager(window) {
  let docShell = window.document.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
  try {
    return docShell.getInterface(Ci.nsIContentFrameMessageManager);
  } catch (e) {
    // Some windows don't support this interface (hidden window).
    return null;
  }
}

var DocumentManager;
var ExtensionManager;

class ExtensionGlobal {
  constructor(global) {
    this.global = global;
    this.global.addMessageListener("Extension:SetFrameData", this);

    this.frameData = null;

    MessageChannel.addListener(global, "Extension:Capture", this);
    MessageChannel.addListener(global, "Extension:DetectLanguage", this);
    MessageChannel.addListener(global, "Extension:Execute", this);
    MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
    MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
  }

  get messageFilterStrict() {
    return {
      innerWindowID: getInnerWindowID(this.global.content),
    };
  }

  getFrameData(force = false) {
    if (!this.frameData && force) {
      this.frameData = this.global.sendSyncMessage("Extension:GetTabAndWindowId")[0];
    }
    return this.frameData;
  }

  receiveMessage({target, messageName, recipient, data, name}) {
    switch (name) {
      case "Extension:SetFrameData":
        if (this.frameData) {
          Object.assign(this.frameData, data);
        } else {
          this.frameData = data;
        }
        if (data.viewType && WebExtensionPolicy.isExtensionProcess) {
          ExtensionPageChild.expectViewLoad(this.global, data.viewType);
        }
        return;
    }

    switch (messageName) {
      case "Extension:Capture":
        return ExtensionContent.handleExtensionCapture(this.global, data.width, data.height, data.options);
      case "Extension:DetectLanguage":
        return ExtensionContent.handleDetectLanguage(this.global, target);
      case "Extension:Execute":
        let policy = WebExtensionPolicy.getByID(recipient.extensionId);

        let matcher = new WebExtensionContentScript(policy, parseScriptOptions(data.options));

        Object.assign(matcher, {
          wantReturnValue: data.options.wantReturnValue,
          removeCSS: data.options.remove_css,
          cssOrigin: data.options.css_origin,
          cssCode: data.options.cssCode,
          jsCode: data.options.jsCode,
        });

        let script = contentScripts.get(matcher);

        return ExtensionContent.handleExtensionExecute(this.global, target, data.options, script);
      case "WebNavigation:GetFrame":
        return ExtensionContent.handleWebNavigationGetFrame(this.global, data.options);
      case "WebNavigation:GetAllFrames":
        return ExtensionContent.handleWebNavigationGetAllFrames(this.global);
    }
  }
}

// Responsible for creating ExtensionContexts and injecting content
// scripts into them when new documents are created.
DocumentManager = {
  globals: new Map(),

  // Initialize listeners that we need regardless of whether extensions are
  // enabled.
  earlyInit() {
    Services.obs.addObserver(this, "tab-content-frameloader-created"); // eslint-disable-line mozilla/balanced-listeners
  },

  // Initialize a frame script global which extension contexts may be loaded
  // into.
  initGlobal(global) {
    this.globals.set(global, new ExtensionGlobal(global));
    // eslint-disable-next-line mozilla/balanced-listeners
    global.addEventListener("unload", () => {
      this.globals.delete(global);
    });
  },

  initExtension(extension) {
    this.injectExtensionScripts(extension);
  },

  // Listeners

  observe(subject, topic, data) {
    if (topic == "tab-content-frameloader-created") {
      this.initGlobal(subject);
    }
  },

  // Script loading

  injectExtensionScripts(extension) {
    for (let window of this.enumerateWindows()) {
      for (let script of extension.contentScripts) {
        if (script.matchesWindow(window)) {
          contentScripts.get(script).injectInto(window);
        }
      }
    }
  },

  /**
   * Checks that all parent frames for the given withdow either have the
   * same add-on ID, or are special chrome-privileged documents such as
   * about:addons or developer tools panels.
   *
   * @param {Window} window
   *        The window to check.
   * @param {string} addonId
   *        The add-on ID to check.
   * @returns {boolean}
   */
  checkParentFrames(window, addonId) {
    while (window.parent !== window) {
      let {frameElement} = window;
      window = window.parent;

      let principal = window.document.nodePrincipal;

      if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
        // The add-on manager is a special case, since it contains extension
        // options pages in same-type <browser> frames.
        if (window.location.href === "about:addons") {
          return true;
        }

        // NOTE: Special handling for devtools panels using a chrome iframe here
        // for the devtools panel, it is needed because a content iframe breaks
        // switching between docked and undocked mode (see bug 1075490).
        if (frameElement &&
            frameElement.mozMatchesSelector("browser[webextension-view-type='devtools_panel']")) {
          return true;
        }
      }

      if (principal.addonId !== addonId) {
        return false;
      }
    }

    return true;
  },

  loadInto(policy, window) {
    let extension = extensions.get(policy);
    if (WebExtensionPolicy.isExtensionProcess && this.checkParentFrames(window, policy.id)) {
      // We're in a top-level extension frame, or a sub-frame thereof,
      // in the extension process. Inject the full extension page API.
      ExtensionPageChild.initExtensionContext(extension, window);
    } else {
      // We're in a content sub-frame or not in the extension process.
      // Only inject a minimal content script API.
      ExtensionContent.initExtensionContext(extension, window);
    }
  },

  // Helpers

  * enumerateWindows(docShell) {
    if (docShell) {
      let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
                                                 docShell.ENUMERATE_FORWARDS);

      for (let docShell of XPCOMUtils.IterSimpleEnumerator(enum_, Ci.nsIInterfaceRequestor)) {
        yield docShell.getInterface(Ci.nsIDOMWindow);
      }
    } else {
      for (let global of this.globals.keys()) {
        yield* this.enumerateWindows(global.docShell);
      }
    }
  },
};

ExtensionManager = {
  init() {
    MessageChannel.setupMessageManagers([Services.cpmm]);

    Services.cpmm.addMessageListener("Extension:Startup", this);
    Services.cpmm.addMessageListener("Extension:Shutdown", this);
    Services.cpmm.addMessageListener("Extension:FlushJarCache", this);

    let procData = Services.cpmm.initialProcessData || {};

    for (let data of procData["Extension:Extensions"] || []) {
      this.initExtension(data);
    }

    if (isContentProcess) {
      // Make sure we handle new schema data until Schemas.jsm is loaded.
      if (!procData["Extension:Schemas"]) {
        procData["Extension:Schemas"] = new Map();
      }
      this.schemaJSON = procData["Extension:Schemas"];

      Services.cpmm.addMessageListener("Schema:Add", this);
    }
  },

  initExtensionPolicy(data, extension) {
    let policy = WebExtensionPolicy.getByID(data.id);
    if (!policy) {
      let localizeCallback = (
        extension ? extension.localize.bind(extension)
                  : str => extensions.get(policy).localize(str));

      policy = new WebExtensionPolicy({
        id: data.id,
        mozExtensionHostname: data.uuid,
        baseURL: data.resourceURL,

        permissions: Array.from(data.permissions),
        allowedOrigins: new MatchPatternSet(data.whiteListedHosts),
        webAccessibleResources: data.webAccessibleResources.map(host => new MatchGlob(host)),

        contentSecurityPolicy: data.manifest.content_security_policy,

        localizeCallback,

        backgroundScripts: (data.manifest.background &&
                            data.manifest.background.scripts),

        contentScripts: (data.manifest.content_scripts || []).map(parseScriptOptions),
      });

      policy.active = true;
      policy.initData = data;
    }
    return policy;
  },

  initExtension(data) {
    let policy = this.initExtensionPolicy(data);

    DocumentManager.initExtension(policy);
  },

  receiveMessage({name, data}) {
    switch (name) {
      case "Extension:Startup": {
        this.initExtension(data);

        Services.cpmm.sendAsyncMessage("Extension:StartupComplete");
        break;
      }

      case "Extension:Shutdown": {
        let policy = WebExtensionPolicy.getByID(data.id);

        if (policy) {
          if (extensions.has(policy)) {
            extensions.get(policy).shutdown();
          }

          if (isContentProcess) {
            policy.active = false;
          }
        }
        Services.cpmm.sendAsyncMessage("Extension:ShutdownComplete");
        break;
      }

      case "Extension:FlushJarCache": {
        ExtensionUtils.flushJarCache(data.path);
        Services.cpmm.sendAsyncMessage("Extension:FlushJarCacheComplete");
        break;
      }

      case "Schema:Add": {
        this.schemaJSON.set(data.url, data.schema);
        break;
      }
    }
  },
};

function ExtensionProcessScript() {
  if (!ExtensionProcessScript.singleton) {
    ExtensionProcessScript.singleton = this;
  }
  return ExtensionProcessScript.singleton;
}

ExtensionProcessScript.singleton = null;

ExtensionProcessScript.prototype = {
  classID: Components.ID("{21f9819e-4cdf-49f9-85a0-850af91a5058}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.mozIExtensionProcessScript]),

  get wrappedJSObject() { return this; },

  getFrameData(global, force) {
    let extGlobal = DocumentManager.globals.get(global);
    return extGlobal && extGlobal.getFrameData(force);
  },

  initExtension(data, extension) {
    return ExtensionManager.initExtensionPolicy(data, extension);
  },

  initExtensionDocument(policy, doc) {
    if (DocumentManager.globals.has(getMessageManager(doc.defaultView))) {
      DocumentManager.loadInto(policy, doc.defaultView);
    }
  },

  preloadContentScript(contentScript) {
    contentScripts.get(contentScript).preload();
  },

  loadContentScript(contentScript, window) {
    if (DocumentManager.globals.has(getMessageManager(window))) {
      contentScripts.get(contentScript).injectInto(window);
    }
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExtensionProcessScript]);

DocumentManager.earlyInit();
ExtensionManager.init();
PK
!<Ph||modules/AboutReader.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;

this.EXPORTED_SYMBOLS = [ "AboutReader" ];

Cu.import("resource://gre/modules/ReaderMode.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AsyncPrefs", "resource://gre/modules/AsyncPrefs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NarrateControls", "resource://gre/modules/narrate/NarrateControls.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");

var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");

const gIsFirefoxDesktop = Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";

var AboutReader = function(mm, win, articlePromise) {
  let url = this._getOriginalUrl(win);
  if (!(url.startsWith("http://") || url.startsWith("https://"))) {
    let errorMsg = "Only http:// and https:// URLs can be loaded in about:reader.";
    if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
      errorMsg += " Tried to load: " + url + ".";
    Cu.reportError(errorMsg);
    win.location.href = "about:blank";
    return;
  }

  let doc = win.document;

  this._mm = mm;
  this._mm.addMessageListener("Reader:CloseDropdown", this);
  this._mm.addMessageListener("Reader:AddButton", this);
  this._mm.addMessageListener("Reader:RemoveButton", this);
  this._mm.addMessageListener("Reader:GetStoredArticleData", this);

  this._docRef = Cu.getWeakReference(doc);
  this._winRef = Cu.getWeakReference(win);
  this._innerWindowId = win.QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;

  this._article = null;
  this._languagePromise = new Promise(resolve => {
    this._foundLanguage = resolve;
  });

  if (articlePromise) {
    this._articlePromise = articlePromise;
  }

  this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
  this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
  this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
  this._readTimeElementRef = Cu.getWeakReference(doc.getElementById("reader-estimated-time"));
  this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
  this._contentElementRef = Cu.getWeakReference(doc.getElementById("moz-reader-content"));
  this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
  this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));

  this._scrollOffset = win.pageYOffset;

  doc.addEventListener("click", this);

  win.addEventListener("pagehide", this);
  win.addEventListener("scroll", this);
  win.addEventListener("resize", this);

  Services.obs.addObserver(this, "inner-window-destroyed");

  doc.addEventListener("visibilitychange", this);

  this._setupStyleDropdown();
  this._setupButton("close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close");

  if (gIsFirefoxDesktop) {
    // we're ready for any external setup, send a signal for that.
    this._mm.sendAsyncMessage("Reader:OnSetup");
  }

  let colorSchemeValues = JSON.parse(Services.prefs.getCharPref("reader.color_scheme.values"));
  let colorSchemeOptions = colorSchemeValues.map((value) => {
    return {
      name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
      value,
      itemClass: value + "-button"
    };
  });

  let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
  this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
  this._setColorSchemePref(colorScheme);

  let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
  let fontTypeOptions = [
    { name: fontTypeSample,
      description: gStrings.GetStringFromName("aboutReader.fontType.sans-serif"),
      value: "sans-serif",
      itemClass: "sans-serif-button"
    },
    { name: fontTypeSample,
      description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
      value: "serif",
      itemClass: "serif-button" },
  ];

  let fontType = Services.prefs.getCharPref("reader.font_type");
  this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
  this._setFontType(fontType);

  this._setupFontSizeButtons();

  this._setupContentWidthButtons();

  this._setupLineHeightButtons();

  if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
    new NarrateControls(mm, win, this._languagePromise);
  }

  this._loadArticle();
};

AboutReader.prototype = {
  _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
                          ".content p > a:only-child > img:only-child, " +
                          ".content .wp-caption img, " +
                          ".content figure img",

  get _doc() {
    return this._docRef.get();
  },

  get _win() {
    return this._winRef.get();
  },

  get _headerElement() {
    return this._headerElementRef.get();
  },

  get _domainElement() {
    return this._domainElementRef.get();
  },

  get _titleElement() {
    return this._titleElementRef.get();
  },

  get _readTimeElement() {
    return this._readTimeElementRef.get();
  },

  get _creditsElement() {
    return this._creditsElementRef.get();
  },

  get _contentElement() {
    return this._contentElementRef.get();
  },

  get _toolbarElement() {
    return this._toolbarElementRef.get();
  },

  get _messageElement() {
    return this._messageElementRef.get();
  },

  get _isToolbarVertical() {
    if (this._toolbarVertical !== undefined) {
      return this._toolbarVertical;
    }
    return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
  },

  // Provides unique view Id.
  get viewId() {
    let _viewId = Cc["@mozilla.org/uuid-generator;1"].
      getService(Ci.nsIUUIDGenerator).generateUUID().toString();
    Object.defineProperty(this, "viewId", { value: _viewId });

    return _viewId;
  },

  receiveMessage(message) {
    switch (message.name) {
      // Triggered by Android user pressing BACK while the banner font-dropdown is open.
      case "Reader:CloseDropdown": {
        // Just close it.
        this._closeDropdowns();
        break;
      }

      case "Reader:AddButton": {
        if (message.data.id && message.data.image &&
            !this._doc.getElementById(message.data.id)) {
          let btn = this._doc.createElement("button");
          btn.setAttribute("class", "button");
          btn.setAttribute("style", "background-image: url('" + message.data.image + "')");
          btn.setAttribute("id", message.data.id);
          if (message.data.title)
            btn.setAttribute("title", message.data.title);
          if (message.data.text)
            btn.textContent = message.data.text;
          let tb = this._doc.getElementById("reader-toolbar");
          tb.appendChild(btn);
          this._setupButton(message.data.id, button => {
            this._mm.sendAsyncMessage("Reader:Clicked-" + button.getAttribute("id"), { article: this._article });
          });
        }
        break;
      }
      case "Reader:RemoveButton": {
        if (message.data.id) {
          let btn = this._doc.getElementById(message.data.id);
          if (btn)
            btn.remove();
        }
        break;
      }
      case "Reader:GetStoredArticleData": {
        this._mm.sendAsyncMessage("Reader:StoredArticleData", { article: this._article });
      }
    }
  },

  handleEvent(aEvent) {
    if (!aEvent.isTrusted)
      return;

    switch (aEvent.type) {
      case "click":
        let target = aEvent.target;
        if (target.classList.contains("dropdown-toggle")) {
          this._toggleDropdownClicked(aEvent);
        } else if (!target.closest(".dropdown-popup")) {
          this._closeDropdowns();
        }
        break;
      case "scroll":
        this._closeDropdowns(true);
        if (!gIsFirefoxDesktop && this._scrollOffset != aEvent.pageY) {
          let isScrollingUp = this._scrollOffset > aEvent.pageY;
          this._setSystemUIVisibility(isScrollingUp);
          this._setToolbarVisibility(isScrollingUp);
        }
        this._scrollOffset = aEvent.pageY;
        break;
      case "resize":
        this._updateImageMargins();
        if (this._isToolbarVertical) {
          this._win.setTimeout(() => {
            for (let dropdown of this._doc.querySelectorAll(".dropdown.open")) {
              this._updatePopupPosition(dropdown);
            }
          }, 0);
        }
        break;

      case "devicelight":
        this._handleDeviceLight(aEvent.value);
        break;

      case "visibilitychange":
        this._handleVisibilityChange();
        break;

      case "pagehide":
        // Close the Banners Font-dropdown, cleanup Android BackPressListener.
        this._closeDropdowns();

        this._mm.removeMessageListener("Reader:CloseDropdown", this);
        this._mm.removeMessageListener("Reader:AddButton", this);
        this._mm.removeMessageListener("Reader:RemoveButton", this);
        this._mm.removeMessageListener("Reader:GetStoredArticleData", this);
        this._windowUnloaded = true;
        break;
    }
  },

  observe(subject, topic, data) {
    if (subject.QueryInterface(Ci.nsISupportsPRUint64).data != this._innerWindowId) {
      return;
    }

    Services.obs.removeObserver(this, "inner-window-destroyed");

    this._mm.removeMessageListener("Reader:CloseDropdown", this);
    this._mm.removeMessageListener("Reader:AddButton", this);
    this._mm.removeMessageListener("Reader:RemoveButton", this);
    this._windowUnloaded = true;
  },

  _onReaderClose() {
    ReaderMode.leaveReaderMode(this._mm.docShell, this._win);
  },

  _setFontSize(newFontSize) {
    let containerClasses = this._doc.getElementById("container").classList;

    if (this._fontSize > 0)
      containerClasses.remove("font-size" + this._fontSize);

    this._fontSize = newFontSize;
    containerClasses.add("font-size" + this._fontSize);
    return AsyncPrefs.set("reader.font_size", this._fontSize);
  },

  _setupFontSizeButtons() {
    const FONT_SIZE_MIN = 1;
    const FONT_SIZE_MAX = 9;

    // Sample text shown in Android UI.
    let sampleText = this._doc.getElementById("font-size-sample");
    sampleText.textContent = gStrings.GetStringFromName("aboutReader.fontTypeSample");

    let currentSize = Services.prefs.getIntPref("reader.font_size");
    currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize));

    let plusButton = this._doc.getElementById("font-size-plus");
    let minusButton = this._doc.getElementById("font-size-minus");

    function updateControls() {
      if (currentSize === FONT_SIZE_MIN) {
        minusButton.setAttribute("disabled", true);
      } else {
        minusButton.removeAttribute("disabled");
      }
      if (currentSize === FONT_SIZE_MAX) {
        plusButton.setAttribute("disabled", true);
      } else {
        plusButton.removeAttribute("disabled");
      }
    }

    updateControls();
    this._setFontSize(currentSize);

    plusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentSize >= FONT_SIZE_MAX) {
        return;
      }

      currentSize++;
      updateControls();
      this._setFontSize(currentSize);
    }, true);

    minusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentSize <= FONT_SIZE_MIN) {
        return;
      }

      currentSize--;
      updateControls();
      this._setFontSize(currentSize);
    }, true);
  },

  _setContentWidth(newContentWidth) {
    let containerClasses = this._doc.getElementById("container").classList;

    if (this._contentWidth > 0)
      containerClasses.remove("content-width" + this._contentWidth);

    this._contentWidth = newContentWidth;
    containerClasses.add("content-width" + this._contentWidth);
    return AsyncPrefs.set("reader.content_width", this._contentWidth);
  },

  _setupContentWidthButtons() {
    const CONTENT_WIDTH_MIN = 1;
    const CONTENT_WIDTH_MAX = 9;

    let currentContentWidth = Services.prefs.getIntPref("reader.content_width");
    currentContentWidth = Math.max(CONTENT_WIDTH_MIN, Math.min(CONTENT_WIDTH_MAX, currentContentWidth));

    let plusButton = this._doc.getElementById("content-width-plus");
    let minusButton = this._doc.getElementById("content-width-minus");

    function updateControls() {
      if (currentContentWidth === CONTENT_WIDTH_MIN) {
        minusButton.setAttribute("disabled", true);
      } else {
        minusButton.removeAttribute("disabled");
      }
      if (currentContentWidth === CONTENT_WIDTH_MAX) {
        plusButton.setAttribute("disabled", true);
      } else {
        plusButton.removeAttribute("disabled");
      }
    }

    updateControls();
    this._setContentWidth(currentContentWidth);

    plusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentContentWidth >= CONTENT_WIDTH_MAX) {
        return;
      }

      currentContentWidth++;
      updateControls();
      this._setContentWidth(currentContentWidth);
    }, true);

    minusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentContentWidth <= CONTENT_WIDTH_MIN) {
        return;
      }

      currentContentWidth--;
      updateControls();
      this._setContentWidth(currentContentWidth);
    }, true);
  },

  _setLineHeight(newLineHeight) {
    let contentClasses = this._doc.getElementById("moz-reader-content").classList;

    if (this._lineHeight > 0)
      contentClasses.remove("line-height" + this._lineHeight);

    this._lineHeight = newLineHeight;
    contentClasses.add("line-height" + this._lineHeight);
    return AsyncPrefs.set("reader.line_height", this._lineHeight);
  },

  _setupLineHeightButtons() {
    const LINE_HEIGHT_MIN = 1;
    const LINE_HEIGHT_MAX = 9;

    let currentLineHeight = Services.prefs.getIntPref("reader.line_height");
    currentLineHeight = Math.max(LINE_HEIGHT_MIN, Math.min(LINE_HEIGHT_MAX, currentLineHeight));

    let plusButton = this._doc.getElementById("line-height-plus");
    let minusButton = this._doc.getElementById("line-height-minus");

    function updateControls() {
      if (currentLineHeight === LINE_HEIGHT_MIN) {
        minusButton.setAttribute("disabled", true);
      } else {
        minusButton.removeAttribute("disabled");
      }
      if (currentLineHeight === LINE_HEIGHT_MAX) {
        plusButton.setAttribute("disabled", true);
      } else {
        plusButton.removeAttribute("disabled");
      }
    }

    updateControls();
    this._setLineHeight(currentLineHeight);

    plusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentLineHeight >= LINE_HEIGHT_MAX) {
        return;
      }

      currentLineHeight++;
      updateControls();
      this._setLineHeight(currentLineHeight);
    }, true);

    minusButton.addEventListener("click", (event) => {
      if (!event.isTrusted) {
        return;
      }
      event.stopPropagation();

      if (currentLineHeight <= LINE_HEIGHT_MIN) {
        return;
      }

      currentLineHeight--;
      updateControls();
      this._setLineHeight(currentLineHeight);
    }, true);
  },

  _handleDeviceLight(newLux) {
    // Desired size of the this._luxValues array.
    let luxValuesSize = 10;
    // Add new lux value at the front of the array.
    this._luxValues.unshift(newLux);
    // Add new lux value to this._totalLux for averaging later.
    this._totalLux += newLux;

    // Don't update when length of array is less than luxValuesSize except when it is 1.
    if (this._luxValues.length < luxValuesSize) {
      // Use the first lux value to set the color scheme until our array equals luxValuesSize.
      if (this._luxValues.length == 1) {
        this._updateColorScheme(newLux);
      }
      return;
    }
    // Holds the average of the lux values collected in this._luxValues.
    let averageLuxValue = this._totalLux / luxValuesSize;

    this._updateColorScheme(averageLuxValue);
    // Pop the oldest value off the array.
    let oldLux = this._luxValues.pop();
    // Subtract oldLux since it has been discarded from the array.
    this._totalLux -= oldLux;
  },

  _handleVisibilityChange() {
    let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
    if (colorScheme != "auto") {
      return;
    }

    // Turn off the ambient light sensor if the page is hidden
    this._enableAmbientLighting(!this._doc.hidden);
  },

  // Setup or teardown the ambient light tracking system.
  _enableAmbientLighting(enable) {
    if (enable) {
      this._win.addEventListener("devicelight", this);
      this._luxValues = [];
      this._totalLux = 0;
    } else {
      this._win.removeEventListener("devicelight", this);
      delete this._luxValues;
      delete this._totalLux;
    }
  },

  _updateColorScheme(luxValue) {
    // Upper bound value for "dark" color scheme beyond which it changes to "light".
    let upperBoundDark = 50;
    // Lower bound value for "light" color scheme beyond which it changes to "dark".
    let lowerBoundLight = 10;
    // Threshold for color scheme change.
    let colorChangeThreshold = 20;

    // Ignore changes that are within a certain threshold of previous lux values.
    if ((this._colorScheme === "dark" && luxValue < upperBoundDark) ||
        (this._colorScheme === "light" && luxValue > lowerBoundLight))
      return;

    if (luxValue < colorChangeThreshold)
      this._setColorScheme("dark");
    else
      this._setColorScheme("light");
  },

  _setColorScheme(newColorScheme) {
    // "auto" is not a real color scheme
    if (this._colorScheme === newColorScheme || newColorScheme === "auto")
      return;

    let bodyClasses = this._doc.body.classList;

    if (this._colorScheme)
      bodyClasses.remove(this._colorScheme);

    this._colorScheme = newColorScheme;
    bodyClasses.add(this._colorScheme);
  },

  // Pref values include "dark", "light", and "auto", which automatically switches
  // between light and dark color schemes based on the ambient light level.
  _setColorSchemePref(colorSchemePref) {
    this._enableAmbientLighting(colorSchemePref === "auto");
    this._setColorScheme(colorSchemePref);

    AsyncPrefs.set("reader.color_scheme", colorSchemePref);
  },

  _setFontType(newFontType) {
    if (this._fontType === newFontType)
      return;

    let bodyClasses = this._doc.body.classList;

    if (this._fontType)
      bodyClasses.remove(this._fontType);

    this._fontType = newFontType;
    bodyClasses.add(this._fontType);

    AsyncPrefs.set("reader.font_type", this._fontType);
  },

  _setSystemUIVisibility(visible) {
    this._mm.sendAsyncMessage("Reader:SystemUIVisibility", { visible });
  },

  _setToolbarVisibility(visible) {
    let tb = this._toolbarElement;

    if (visible) {
      if (tb.style.opacity != "1") {
        tb.removeAttribute("hidden");
        tb.style.opacity = "1";
      }
    } else if (tb.style.opacity != "0") {
      tb.addEventListener("transitionend", evt => {
        if (tb.style.opacity == "0") {
          tb.setAttribute("hidden", "");
        }
      }, { once: true });
      tb.style.opacity = "0";
    }
  },

  async _loadArticle() {
    let url = this._getOriginalUrl();
    this._showProgressDelayed();

    let article;
    if (this._articlePromise) {
      article = await this._articlePromise;
    } else {
      try {
        article = await this._getArticle(url);
      } catch (e) {
        if (e && e.newURL) {
          let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
          this._win.location.replace(readerURL);
          return;
        }
      }
    }

    if (this._windowUnloaded) {
      return;
    }

    // Replace the loading message with an error message if there's a failure.
    // Users are supposed to navigate away by themselves (because we cannot
    // remove ourselves from session history.)
    if (!article) {
      this._showError();
      return;
    }

    this._showContent(article);
  },

  _getArticle(url) {
    return new Promise((resolve, reject) => {
      let listener = (message) => {
        this._mm.removeMessageListener("Reader:ArticleData", listener);
        if (message.data.newURL) {
          reject({ newURL: message.data.newURL });
          return;
        }
        resolve(message.data.article);
      };
      this._mm.addMessageListener("Reader:ArticleData", listener);
      this._mm.sendAsyncMessage("Reader:ArticleGet", { url });
    });
  },

  _requestFavicon() {
    let handleFaviconReturn = (message) => {
      this._mm.removeMessageListener("Reader:FaviconReturn", handleFaviconReturn);
      this._loadFavicon(message.data.url, message.data.faviconUrl);
    };

    this._mm.addMessageListener("Reader:FaviconReturn", handleFaviconReturn);
    this._mm.sendAsyncMessage("Reader:FaviconRequest", { url: this._article.url });
  },

  _loadFavicon(url, faviconUrl) {
    if (this._article.url !== url)
      return;

    let doc = this._doc;

    let link = doc.createElement("link");
    link.rel = "shortcut icon";
    link.href = faviconUrl;

    doc.getElementsByTagName("head")[0].appendChild(link);
  },

  _updateImageMargins() {
    let windowWidth = this._win.innerWidth;
    let bodyWidth = this._doc.body.clientWidth;

    let setImageMargins = function(img) {
      // If the image is at least as wide as the window, make it fill edge-to-edge on mobile.
      if (img.naturalWidth >= windowWidth) {
        img.setAttribute("moz-reader-full-width", true);
      } else {
        img.removeAttribute("moz-reader-full-width");
      }

      // If the image is at least half as wide as the body, center it on desktop.
      if (img.naturalWidth >= bodyWidth / 2) {
        img.setAttribute("moz-reader-center", true);
      } else {
        img.removeAttribute("moz-reader-center");
      }
    };

    let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
    for (let i = imgs.length; --i >= 0;) {
      let img = imgs[i];

      if (img.naturalWidth > 0) {
        setImageMargins(img);
      } else {
        img.onload = function() {
          setImageMargins(img);
        };
      }
    }
  },

  _maybeSetTextDirection: function Read_maybeSetTextDirection(article) {
    if (article.dir) {
      // Set "dir" attribute on content
      this._contentElement.setAttribute("dir", article.dir);
      this._headerElement.setAttribute("dir", article.dir);

      // The native locale could be set differently than the article's text direction.
      var localeDirection = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
      this._readTimeElement.setAttribute("dir", localeDirection);
      this._readTimeElement.style.textAlign = article.dir == "rtl" ? "right" : "left";
    }
  },

  _formatReadTime(slowEstimate, fastEstimate) {
    let displayStringKey = "aboutReader.estimatedReadTimeRange1";

    // only show one reading estimate when they are the same value
    if (slowEstimate == fastEstimate) {
      displayStringKey = "aboutReader.estimatedReadTimeValue1";
    }

    return PluralForm.get(slowEstimate, gStrings.GetStringFromName(displayStringKey))
      .replace("#1", fastEstimate)
      .replace("#2", slowEstimate);
  },

  _showError() {
    this._headerElement.style.display = "none";
    this._contentElement.style.display = "none";

    let errorMessage = gStrings.GetStringFromName("aboutReader.loadError");
    this._messageElement.textContent = errorMessage;
    this._messageElement.style.display = "block";

    this._doc.title = errorMessage;

    this._doc.documentElement.dataset.isError = true;

    this._error = true;

    this._doc.dispatchEvent(
      new this._win.CustomEvent("AboutReaderContentError", { bubbles: true, cancelable: false }));
  },

  // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
  _stripHost(host) {
    if (!host)
      return host;

    let start = 0;

    if (host.startsWith("www."))
      start = 4;
    else if (host.startsWith("m."))
      start = 2;
    else if (host.startsWith("mobile."))
      start = 7;

    return host.substring(start);
  },

  _showContent(article) {
    this._messageElement.style.display = "none";

    this._article = article;

    this._domainElement.href = article.url;
    let articleUri = Services.io.newURI(article.url);
    this._domainElement.textContent = this._stripHost(articleUri.host);
    this._creditsElement.textContent = article.byline;

    this._titleElement.textContent = article.title;
    this._readTimeElement.textContent = this._formatReadTime(article.readingTimeMinsSlow, article.readingTimeMinsFast);
    this._doc.title = article.title;

    this._headerElement.style.display = "block";

    let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
    let contentFragment = parserUtils.parseFragment(article.content,
      Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
      false, articleUri, this._contentElement);
    this._contentElement.innerHTML = "";
    this._contentElement.appendChild(contentFragment);
    this._maybeSetTextDirection(article);
    this._foundLanguage(article.language);

    this._contentElement.style.display = "block";
    this._updateImageMargins();

    this._requestFavicon();
    this._doc.body.classList.add("loaded");

    this._goToReference(articleUri.ref);

    Services.obs.notifyObservers(this._win, "AboutReader:Ready");

    this._doc.dispatchEvent(
      new this._win.CustomEvent("AboutReaderContentReady", { bubbles: true, cancelable: false }));
  },

  _hideContent() {
    this._headerElement.style.display = "none";
    this._contentElement.style.display = "none";
  },

  _showProgressDelayed() {
    this._win.setTimeout(() => {
      // No need to show progress if the article has been loaded,
      // if the window has been unloaded, or if there was an error
      // trying to load the article.
      if (this._article || this._windowUnloaded || this._error) {
        return;
      }

      this._headerElement.style.display = "none";
      this._contentElement.style.display = "none";

      this._messageElement.textContent = gStrings.GetStringFromName("aboutReader.loading2");
      this._messageElement.style.display = "block";
    }, 300);
  },

  /**
   * Returns the original article URL for this about:reader view.
   */
  _getOriginalUrl(win) {
    let url = win ? win.location.href : this._win.location.href;
    return ReaderMode.getOriginalUrl(url) || url;
  },

  _setupSegmentedButton(id, options, initialValue, callback) {
    let doc = this._doc;
    let segmentedButton = doc.getElementById(id);

    for (let i = 0; i < options.length; i++) {
      let option = options[i];

      let item = doc.createElement("button");

      // Put the name in a div so that Android can hide it.
      let div = doc.createElement("div");
      div.textContent = option.name;
      div.classList.add("name");
      item.appendChild(div);

      if (option.itemClass !== undefined)
        item.classList.add(option.itemClass);

      if (option.description !== undefined) {
        let description = doc.createElement("div");
        description.textContent = option.description;
        description.classList.add("description");
        item.appendChild(description);
      }

      segmentedButton.appendChild(item);

      item.addEventListener("click", function(aEvent) {
        if (!aEvent.isTrusted)
          return;

        aEvent.stopPropagation();

        // Just pass the ID of the button as an extra and hope the ID doesn't change
        // unless the context changes
        UITelemetry.addEvent("action.1", "button", null, id);

        let items = segmentedButton.children;
        for (let j = items.length - 1; j >= 0; j--) {
          items[j].classList.remove("selected");
        }

        item.classList.add("selected");
        callback(option.value);
      }, true);

      if (option.value === initialValue)
        item.classList.add("selected");
    }
  },

  _setupButton(id, callback, titleEntity, textEntity) {
    if (titleEntity) {
      this._setButtonTip(id, titleEntity);
    }

    let button = this._doc.getElementById(id);
    if (textEntity) {
      button.textContent = gStrings.GetStringFromName(textEntity);
    }
    button.removeAttribute("hidden");
    button.addEventListener("click", function(aEvent) {
      if (!aEvent.isTrusted)
        return;

      aEvent.stopPropagation();
      let btn = aEvent.target;
      callback(btn);
    }, true);
  },

  /**
   * Sets a toolTip for a button. Performed at initial button setup
   * and dynamically as button state changes.
   * @param   Localizable string providing UI element usage tip.
   */
  _setButtonTip(id, titleEntity) {
    let button = this._doc.getElementById(id);
    button.setAttribute("title", gStrings.GetStringFromName(titleEntity));
  },

  _setupStyleDropdown() {
    let dropdownToggle = this._doc.querySelector("#style-dropdown .dropdown-toggle");
    dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
  },

  _updatePopupPosition(dropdown) {
    let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
    let dropdownPopup = dropdown.querySelector(".dropdown-popup");

    let toggleHeight = dropdownToggle.offsetHeight;
    let toggleTop = dropdownToggle.offsetTop;
    let popupTop = toggleTop - toggleHeight / 2;

    dropdownPopup.style.top = popupTop + "px";
  },

  _toggleDropdownClicked(event) {
    let dropdown = event.target.closest(".dropdown");

    if (!dropdown)
      return;

    event.stopPropagation();

    if (dropdown.classList.contains("open")) {
      this._closeDropdowns();
    } else {
      this._openDropdown(dropdown);
      if (this._isToolbarVertical) {
        this._updatePopupPosition(dropdown);
      }
    }
  },

  /*
   * If the ReaderView banner font-dropdown is closed, open it.
   */
  _openDropdown(dropdown) {
    if (dropdown.classList.contains("open")) {
      return;
    }

    this._closeDropdowns();

    // Trigger BackPressListener initialization in Android.
    dropdown.classList.add("open");
    this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
  },

  /*
   * If the ReaderView has open dropdowns, close them. If we are closing the
   * dropdowns because the page is scrolling, allow popups to stay open with
   * the keep-open class.
   */
  _closeDropdowns(scrolling) {
    let selector = ".dropdown.open";
    if (scrolling) {
      selector += ":not(.keep-open)";
    }

    let openDropdowns = this._doc.querySelectorAll(selector);
    for (let dropdown of openDropdowns) {
      dropdown.classList.remove("open");
    }

    // Trigger BackPressListener cleanup in Android.
    if (openDropdowns.length) {
      this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
    }
  },

  /*
   * Scroll reader view to a reference
   */
  _goToReference(ref) {
    if (ref) {
      this._win.location.hash = ref;
    }
  }
};
PK
!<>Yemodules/AddonManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
// most tests later register different nsIAppInfo implementations, which
// wouldn't be reflected in Services.appinfo anymore, as the lazy getter
// underlying it would have been initialized if we used it here.
if ("@mozilla.org/xre/app-info;1" in Cc) {
  let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
    // Refuse to run in child processes.
    throw new Error("You cannot use the AddonManager in child processes!");
  }
}

Cu.import("resource://gre/modules/AppConstants.jsm");

const MOZ_COMPATIBILITY_NIGHTLY = !["aurora", "beta", "release", "esr"].includes(AppConstants.MOZ_UPDATE_CHANNEL);

const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_DEFAULT_PROVIDERS_ENABLED  = "extensions.defaultProviders.enabled";
const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
const PREF_EM_LAST_PLATFORM_VERSION   = "extensions.lastPlatformVersion";
const PREF_EM_AUTOUPDATE_DEFAULT      = "extensions.update.autoUpdateDefault";
const PREF_EM_STRICT_COMPATIBILITY    = "extensions.strictCompatibility";
const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
const PREF_APP_UPDATE_AUTO            = "app.update.auto";
const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
const PREF_EM_HOTFIX_LASTVERSION      = "extensions.hotfix.lastVersion";
const PREF_EM_HOTFIX_URL              = "extensions.hotfix.url";
const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";
const UNKNOWN_XPCOM_ABI               = "unknownABI";

const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion";
const PREF_WEBAPI_TESTING             = "extensions.webapi.testing";
const PREF_WEBEXT_PERM_PROMPTS        = "extensions.webextPermissionPrompts";

const UPDATE_REQUEST_VERSION          = 2;
const CATEGORY_UPDATE_PARAMS          = "extension-update-params";

const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";

const KEY_PROFILEDIR                  = "ProfD";
const KEY_APPDIR                      = "XCurProcD";
const FILE_BLOCKLIST                  = "blocklist.xml";

const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ?
                                  PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" :
                                  undefined;

const VALID_TYPES_REGEXP = /^[\w\-]+$/;

const WEBAPI_INSTALL_HOSTS = ["addons.mozilla.org", "testpilot.firefox.com"];
const WEBAPI_TEST_INSTALL_HOSTS = [
  "addons.allizom.org", "addons-dev.allizom.org",
  "testpilot.stage.mozaws.net", "testpilot.dev.mozaws.net",
  "example.com",
];

const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                  "resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils",
                                  "resource://gre/modules/SharedPromptUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
  let certUtils = {};
  Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
  return certUtils;
});

XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                      PREF_WEBEXT_PERM_PROMPTS, false);

// Initialize the WebExtension process script service as early as possible,
// since it needs to be able to track things like new frameLoader globals that
// are created before other framework code has been initialized.
Services.ppmm.loadProcessScript(
  "data:,Components.classes['@mozilla.org/webextensions/extension-process-script;1'].getService()",
  true);

const INTEGER = /^[1-9]\d*$/;

this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];

const CATEGORY_PROVIDER_MODULE = "addon-provider-module";

// A list of providers to load by default
const DEFAULT_PROVIDERS = [
  "resource://gre/modules/addons/XPIProvider.jsm",
  "resource://gre/modules/LightweightThemeManager.jsm"
];

Cu.import("resource://gre/modules/Log.jsm");
// Configure a logger at the parent 'addons' level to format
// messages for all the modules under addons.*
const PARENT_LOGGER_ID = "addons";
var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
var formatter = new Log.BasicFormatter();
// Set parent logger (and its children) to append to
// the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
// Set parent logger (and its children) to
// also append to standard out
parentLogger.addAppender(new Log.DumpAppender(formatter));

// Create a new logger (child of 'addons' logger)
// for use by the Addons Manager
const LOGGER_ID = "addons.manager";
var logger = Log.repository.getLogger(LOGGER_ID);

// Provide the ability to enable/disable logging
// messages at runtime.
// If the "extensions.logging.enabled" preference is
// missing or 'false', messages at the WARNING and higher
// severity should be logged to the JS console and standard error.
// If "extensions.logging.enabled" is set to 'true', messages
// at DEBUG and higher should go to JS console and standard error.
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";

const UNNAMED_PROVIDER = "<unnamed-provider>";
function providerName(aProvider) {
  return aProvider.name || UNNAMED_PROVIDER;
}

/**
 * Preference listener which listens for a change in the
 * "extensions.logging.enabled" preference and changes the logging level of the
 * parent 'addons' level logger accordingly.
 */
var PrefObserver = {
    init() {
      Services.prefs.addObserver(PREF_LOGGING_ENABLED, this);
      Services.obs.addObserver(this, "xpcom-shutdown");
      this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
    },

    observe(aSubject, aTopic, aData) {
      if (aTopic == "xpcom-shutdown") {
        Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
        Services.obs.removeObserver(this, "xpcom-shutdown");
      } else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
        let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
        if (debugLogEnabled) {
          parentLogger.level = Log.Level.Debug;
        } else {
          parentLogger.level = Log.Level.Warn;
        }
      }
    }
};

PrefObserver.init();

/**
 * Calls a callback method consuming any thrown exception. Any parameters after
 * the callback parameter will be passed to the callback.
 *
 * @param  aCallback
 *         The callback method to call
 */
function safeCall(aCallback, ...aArgs) {
  try {
    aCallback.apply(null, aArgs);
  } catch (e) {
    logger.warn("Exception calling callback", e);
  }
}

/**
 * Creates a function that will call the passed callback catching and logging
 * any exceptions.
 *
 * @param  aCallback
 *         The callback method to call
 */
function makeSafe(aCallback) {
  return function(...aArgs) {
    safeCall(aCallback, ...aArgs);
  }
}

/**
 * Given a promise and an optional callback, either:
 *
 * 1) Returns the promise, if no callback was provided, or,
 * 2) Calls the callback with the promise resolution value, and reports
 *    any errors.
 *
 * @param {Promise} promise
 *        The promise to return, or report to the callback function.
 * @param {function | null} callback
 *        The optional callback function to call with the promise
 *        resolution.
 * @returns {Promise?}
 */
function promiseOrCallback(promise, callback) {
  if (!callback)
    return promise;

  if (typeof callback !== "function")
    throw Components.Exception("Callback must be a function",
                               Cr.NS_ERROR_INVALID_ARG);

  promise.then(makeSafe(callback)).catch(error => {
    logger.error(error);
  });

  return undefined;
}

/**
 * Report an exception thrown by a provider API method.
 */
function reportProviderError(aProvider, aMethod, aError) {
  let method = `provider ${providerName(aProvider)}.${aMethod}`;
  AddonManagerPrivate.recordException("AMI", method, aError);
  logger.error("Exception calling " + method, aError);
}

/**
 * Calls a method on a provider if it exists and consumes any thrown exception.
 * Any parameters after the aDefault parameter are passed to the provider's method.
 *
 * @param  aProvider
 *         The provider to call
 * @param  aMethod
 *         The method name to call
 * @param  aDefault
 *         A default return value if the provider does not implement the named
 *         method or throws an error.
 * @return the return value from the provider, or aDefault if the provider does not
 *         implement method or throws an error
 */
function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
  if (!(aMethod in aProvider))
    return aDefault;

  try {
    return aProvider[aMethod].apply(aProvider, aArgs);
  } catch (e) {
    reportProviderError(aProvider, aMethod, e);
    return aDefault;
  }
}

/**
 * Calls a method on a provider if it exists and consumes any thrown exception.
 * Parameters after aMethod are passed to aProvider.aMethod().
 * The last parameter must be a callback function.
 * If the provider does not implement the method, or the method throws, calls
 * the callback with 'undefined'.
 *
 * @param  aProvider
 *         The provider to call
 * @param  aMethod
 *         The method name to call
 */
function callProviderAsync(aProvider, aMethod, ...aArgs) {
  let callback = aArgs[aArgs.length - 1];
  if (!(aMethod in aProvider)) {
    callback(undefined);
    return undefined;
  }
  try {
    return aProvider[aMethod].apply(aProvider, aArgs);
  } catch (e) {
    reportProviderError(aProvider, aMethod, e);
    callback(undefined);
    return undefined;
  }
}

/**
 * Calls a method on a provider if it exists and consumes any thrown exception.
 * Parameters after aMethod are passed to aProvider.aMethod() and an additional
 * callback is added for the provider to return a result to.
 *
 * @param  aProvider
 *         The provider to call
 * @param  aMethod
 *         The method name to call
 * @return {Promise}
 * @resolves The result the provider returns, or |undefined| if the provider
 *           does not implement the method or the method throws.
 * @rejects  Never
 */
function promiseCallProvider(aProvider, aMethod, ...aArgs) {
  return new Promise(resolve => {
    callProviderAsync(aProvider, aMethod, ...aArgs, resolve);
  });
}

/**
 * Gets the currently selected locale for display.
 * @return  the selected locale or "en-US" if none is selected
 */
function getLocale() {
  return Services.locale.getRequestedLocale() || "en-US";
}

function webAPIForAddon(addon) {
  if (!addon) {
    return null;
  }

  let result = {};

  // By default just pass through any plain property, the webidl will
  // control access.  Also filter out private properties, regular Addon
  // objects are okay but MockAddon used in tests has non-serializable
  // private properties.
  for (let prop in addon) {
    if (prop[0] != "_" && typeof(addon[prop]) != "function") {
      result[prop] = addon[prop];
    }
  }

  // A few properties are computed for a nicer API
  result.isEnabled = !addon.userDisabled;
  result.canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);

  return result;
}

/**
 * Listens for a browser changing origin and cancels the installs that were
 * started by it.
 */
function BrowserListener(aBrowser, aInstallingPrincipal, aInstall) {
  this.browser = aBrowser;
  this.principal = aInstallingPrincipal;
  this.install = aInstall;

  aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
  Services.obs.addObserver(this, "message-manager-close", true);

  aInstall.addListener(this);

  this.registered = true;
}

BrowserListener.prototype = {
  browser: null,
  install: null,
  registered: false,

  unregister() {
    if (!this.registered)
      return;
    this.registered = false;

    Services.obs.removeObserver(this, "message-manager-close");
    // The browser may have already been detached
    if (this.browser.removeProgressListener)
      this.browser.removeProgressListener(this);

    this.install.removeListener(this);
    this.install = null;
  },

  cancelInstall() {
    try {
      this.install.cancel();
    } catch (e) {
      // install may have already failed or been cancelled, ignore these
    }
  },

  observe(subject, topic, data) {
    if (subject != this.browser.messageManager)
      return;

    // The browser's message manager has closed and so the browser is
    // going away, cancel the install
    this.cancelInstall();
  },

  onLocationChange(webProgress, request, location) {
    if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal))
      return;

    // The browser has navigated to a new origin so cancel the install
    this.cancelInstall();
  },

  onDownloadCancelled(install) {
    this.unregister();
  },

  onDownloadFailed(install) {
    this.unregister();
  },

  onInstallFailed(install) {
    this.unregister();
  },

  onInstallEnded(install) {
    this.unregister();
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                         Ci.nsIWebProgressListener,
                                         Ci.nsIObserver])
};

/**
 * This represents an author of an add-on (e.g. creator or developer)
 *
 * @param  aName
 *         The name of the author
 * @param  aURL
 *         The URL of the author's profile page
 */
function AddonAuthor(aName, aURL) {
  this.name = aName;
  this.url = aURL;
}

AddonAuthor.prototype = {
  name: null,
  url: null,

  // Returns the author's name, defaulting to the empty string
  toString() {
    return this.name || "";
  }
}

/**
 * This represents an screenshot for an add-on
 *
 * @param  aURL
 *         The URL to the full version of the screenshot
 * @param  aWidth
 *         The width in pixels of the screenshot
 * @param  aHeight
 *         The height in pixels of the screenshot
 * @param  aThumbnailURL
 *         The URL to the thumbnail version of the screenshot
 * @param  aThumbnailWidth
 *         The width in pixels of the thumbnail version of the screenshot
 * @param  aThumbnailHeight
 *         The height in pixels of the thumbnail version of the screenshot
 * @param  aCaption
 *         The caption of the screenshot
 */
function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL,
                         aThumbnailWidth, aThumbnailHeight, aCaption) {
  this.url = aURL;
  if (aWidth) this.width = aWidth;
  if (aHeight) this.height = aHeight;
  if (aThumbnailURL) this.thumbnailURL = aThumbnailURL;
  if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth;
  if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight;
  if (aCaption) this.caption = aCaption;
}

AddonScreenshot.prototype = {
  url: null,
  width: null,
  height: null,
  thumbnailURL: null,
  thumbnailWidth: null,
  thumbnailHeight: null,
  caption: null,

  // Returns the screenshot URL, defaulting to the empty string
  toString() {
    return this.url || "";
  }
}


/**
 * This represents a compatibility override for an addon.
 *
 * @param  aType
 *         Overrride type - "compatible" or "incompatible"
 * @param  aMinVersion
 *         Minimum version of the addon to match
 * @param  aMaxVersion
 *         Maximum version of the addon to match
 * @param  aAppID
 *         Application ID used to match appMinVersion and appMaxVersion
 * @param  aAppMinVersion
 *         Minimum version of the application to match
 * @param  aAppMaxVersion
 *         Maximum version of the application to match
 */
function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID,
                                    aAppMinVersion, aAppMaxVersion) {
  this.type = aType;
  this.minVersion = aMinVersion;
  this.maxVersion = aMaxVersion;
  this.appID = aAppID;
  this.appMinVersion = aAppMinVersion;
  this.appMaxVersion = aAppMaxVersion;
}

AddonCompatibilityOverride.prototype = {
  /**
   * Type of override - "incompatible" or "compatible".
   * Only "incompatible" is supported for now.
   */
  type: null,

  /**
   * Min version of the addon to match.
   */
  minVersion: null,

  /**
   * Max version of the addon to match.
   */
  maxVersion: null,

  /**
   * Application ID to match.
   */
  appID: null,

  /**
   * Min version of the application to match.
   */
  appMinVersion: null,

  /**
   * Max version of the application to match.
   */
  appMaxVersion: null
};


/**
 * A type of add-on, used by the UI to determine how to display different types
 * of add-ons.
 *
 * @param  aID
 *         The add-on type ID
 * @param  aLocaleURI
 *         The URI of a localized properties file to get the displayable name
 *         for the type from
 * @param  aLocaleKey
 *         The key for the string in the properties file or the actual display
 *         name if aLocaleURI is null. Include %ID% to include the type ID in
 *         the key
 * @param  aViewType
 *         The optional type of view to use in the UI
 * @param  aUIPriority
 *         The priority is used by the UI to list the types in order. Lower
 *         values push the type higher in the list.
 * @param  aFlags
 *         An option set of flags that customize the display of the add-on in
 *         the UI.
 */
function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
  if (!aID)
    throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG);

  if (aViewType && aUIPriority === undefined)
    throw Components.Exception("An AddonType with a defined view must have a set UI priority",
                               Cr.NS_ERROR_INVALID_ARG);

  if (!aLocaleKey)
    throw Components.Exception("An AddonType must have a displayable name",
                               Cr.NS_ERROR_INVALID_ARG);

  this.id = aID;
  this.uiPriority = aUIPriority;
  this.viewType = aViewType;
  this.flags = aFlags;

  if (aLocaleURI) {
    XPCOMUtils.defineLazyGetter(this, "name", () => {
      let bundle = Services.strings.createBundle(aLocaleURI);
      return bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID));
    });
  } else {
    this.name = aLocaleKey;
  }
}

var gStarted = false;
var gStartupComplete = false;
var gCheckCompatibility = true;
var gStrictCompatibility = true;
var gCheckUpdateSecurityDefault = true;
var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
var gUpdateEnabled = true;
var gAutoUpdateDefault = true;
var gHotfixID = "";
var gWebExtensionsMinPlatformVersion = "";
var gShutdownBarrier = null;
var gRepoShutdownState = "";
var gShutdownInProgress = false;
var gPluginPageListener = null;
var gBrowserUpdated = null;
var gNonMpcDisabled = false;

/**
 * This is the real manager, kept here rather than in AddonManager to keep its
 * contents hidden from API users.
 */
var AddonManagerInternal = {
  managerListeners: new Set(),
  installListeners: new Set(),
  addonListeners: new Set(),
  typeListeners: new Set(),
  pendingProviders: new Set(),
  providers: new Set(),
  providerShutdowns: new Map(),
  types: {},
  startupChanges: {},
  // Store telemetry details per addon provider
  telemetryDetails: {},
  upgradeListeners: new Map(),

  recordTimestamp(name, value) {
    this.TelemetryTimestamps.add(name, value);
  },

  validateBlocklist() {
    let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);

    // If there is no application shipped blocklist then there is nothing to do
    if (!appBlocklist.exists())
      return;

    let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);

    // If there is no blocklist in the profile then copy the application shipped
    // one there
    if (!profileBlocklist.exists()) {
      try {
        appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
      } catch (e) {
        logger.warn("Failed to copy the application shipped blocklist to the profile", e);
      }
      return;
    }

    let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
                     createInstance(Ci.nsIFileInputStream);
    try {
      let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
                    createInstance(Ci.nsIConverterInputStream);
      fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
      cstream.init(fileStream, "UTF-8", 0, 0);

      let data = "";
      let str = {};
      let read = 0;
      do {
        read = cstream.readString(0xffffffff, str);
        data += str.value;
      } while (read != 0);

      let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                   createInstance(Ci.nsIDOMParser);
      var doc = parser.parseFromString(data, "text/xml");
    } catch (e) {
      logger.warn("Application shipped blocklist could not be loaded", e);
      return;
    } finally {
      try {
        fileStream.close();
      } catch (e) {
        logger.warn("Unable to close blocklist file stream", e);
      }
    }

    // If the namespace is incorrect then ignore the application shipped
    // blocklist
    if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
      logger.warn("Application shipped blocklist has an unexpected namespace (" +
                  doc.documentElement.namespaceURI + ")");
      return;
    }

    // If there is no lastupdate information then ignore the application shipped
    // blocklist
    if (!doc.documentElement.hasAttribute("lastupdate"))
      return;

    // If the application shipped blocklist is older than the profile blocklist
    // then do nothing
    if (doc.documentElement.getAttribute("lastupdate") <=
        profileBlocklist.lastModifiedTime)
      return;

    // Otherwise copy the application shipped blocklist to the profile
    try {
      appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
    } catch (e) {
      logger.warn("Failed to copy the application shipped blocklist to the profile", e);
    }
  },

  /**
   * Start up a provider, and register its shutdown hook if it has one
   */
  _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    logger.debug(`Starting provider: ${providerName(aProvider)}`);
    callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
    if ("shutdown" in aProvider) {
      let name = providerName(aProvider);
      let AMProviderShutdown = () => {
        // If the provider has been unregistered, it will have been removed from
        // this.providers. If it hasn't been unregistered, then this is a normal
        // shutdown - and we move it to this.pendingProviders incase we're
        // running in a test that will start AddonManager again.
        if (this.providers.has(aProvider)) {
          this.providers.delete(aProvider);
          this.pendingProviders.add(aProvider);
        }

        return new Promise((resolve, reject) => {
            logger.debug("Calling shutdown blocker for " + name);
            resolve(aProvider.shutdown());
          })
          .catch(err => {
            logger.warn("Failure during shutdown of " + name, err);
            AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err);
          });
      };
      logger.debug("Registering shutdown blocker for " + name);
      this.providerShutdowns.set(aProvider, AMProviderShutdown);
      AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
    }

    this.pendingProviders.delete(aProvider);
    this.providers.add(aProvider);
    logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
  },

  _getProviderByName(aName) {
    for (let provider of this.providers) {
      if (providerName(provider) == aName)
        return provider;
    }
    return undefined;
  },

  /**
   * Initializes the AddonManager, loading any known providers and initializing
   * them.
   */
  startup() {
    try {
      if (gStarted)
        return;

      this.recordTimestamp("AMI_startup_begin");

      // clear this for xpcshell test restarts
      for (let provider in this.telemetryDetails)
        delete this.telemetryDetails[provider];

      let appChanged = undefined;

      let oldAppVersion = null;
      try {
        oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
        appChanged = Services.appinfo.version != oldAppVersion;
      } catch (e) { }

      gBrowserUpdated = appChanged;

      let oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION, "");

      if (appChanged !== false) {
        logger.debug("Application has been upgraded");
        Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                   Services.appinfo.version);
        Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
                                   Services.appinfo.platformVersion);
        Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
                                  (appChanged === undefined ? 0 : -1));
        this.validateBlocklist();
      }

      if (!MOZ_COMPATIBILITY_NIGHTLY) {
        PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
                                      Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
      }

      gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY,
                                                       gCheckCompatibility);
      Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this);

      gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY,
                                                        gStrictCompatibility);
      Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this);

      let defaultBranch = Services.prefs.getDefaultBranch("");
      gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
                                                              gCheckUpdateSecurityDefault);

      gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
                                                        gCheckUpdateSecurity);
      Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);

      gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, gUpdateEnabled);
      Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this);

      gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT,
                                                      gAutoUpdateDefault);
      Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);

      gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID, gHotfixID);
      Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this);

      gWebExtensionsMinPlatformVersion =
        Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION,
                                   gWebExtensionsMinPlatformVersion);
      Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this);

      let defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED, true);
      AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled);

      // Ensure all default providers have had a chance to register themselves
      if (defaultProvidersEnabled) {
        for (let url of DEFAULT_PROVIDERS) {
          try {
            let scope = {};
            Components.utils.import(url, scope);
            // Sanity check - make sure the provider exports a symbol that
            // has a 'startup' method
            let syms = Object.keys(scope);
            if ((syms.length < 1) ||
                (typeof scope[syms[0]].startup != "function")) {
              logger.warn("Provider " + url + " has no startup()");
              AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()");
            }
            logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource());
          } catch (e) {
            AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
            logger.error("Exception loading default provider \"" + url + "\"", e);
          }
        }
      }

      // Load any providers registered in the category manager
      let catman = Cc["@mozilla.org/categorymanager;1"].
                   getService(Ci.nsICategoryManager);
      let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
      while (entries.hasMoreElements()) {
        let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
        let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);

        try {
          Components.utils.import(url, {});
          logger.debug(`Loaded provider scope for ${url}`);
        } catch (e) {
          AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
          logger.error("Exception loading provider " + entry + " from category \"" +
                url + "\"", e);
        }
      }

      // Register our shutdown handler with the AsyncShutdown manager
      gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
      AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
                                                   this.shutdownManager.bind(this),
                                                   {fetchState: this.shutdownState.bind(this)});

      // Once we start calling providers we must allow all normal methods to work.
      gStarted = true;

      for (let provider of this.pendingProviders) {
        this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
      }

      // If this is a new profile just pretend that there were no changes
      if (appChanged === undefined) {
        for (let type in this.startupChanges)
          delete this.startupChanges[type];
      }

      // Support for remote about:plugins. Note that this module isn't loaded
      // at the top because Services.appinfo is defined late in tests.
      let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {});

      gPluginPageListener = new RemotePages("about:plugins");
      gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins);

      gStartupComplete = true;
      this.recordTimestamp("AMI_startup_end");
    } catch (e) {
      logger.error("startup failed", e);
      AddonManagerPrivate.recordException("AMI", "startup failed", e);
    }

    logger.debug("Completed startup sequence");
    this.callManagerListeners("onStartup");
  },

  /**
   * Registers a new AddonProvider.
   *
   * @param  aProvider
   *         The provider to register
   * @param  aTypes
   *         An optional array of add-on types
   */
  registerProvider(aProvider, aTypes) {
    if (!aProvider || typeof aProvider != "object")
      throw Components.Exception("aProvider must be specified",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aTypes && !Array.isArray(aTypes))
      throw Components.Exception("aTypes must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.pendingProviders.add(aProvider);

    if (aTypes) {
      for (let type of aTypes) {
        if (!(type.id in this.types)) {
          if (!VALID_TYPES_REGEXP.test(type.id)) {
            logger.warn("Ignoring invalid type " + type.id);
            return;
          }

          this.types[type.id] = {
            type,
            providers: [aProvider]
          };

          let typeListeners = new Set(this.typeListeners);
          for (let listener of typeListeners)
            safeCall(() => listener.onTypeAdded(type));
        } else {
          this.types[type.id].providers.push(aProvider);
        }
      }
    }

    // If we're registering after startup call this provider's startup.
    if (gStarted) {
      this._startProvider(aProvider);
    }
  },

  /**
   * Unregisters an AddonProvider.
   *
   * @param  aProvider
   *         The provider to unregister
   * @return Whatever the provider's 'shutdown' method returns (if anything).
   *         For providers that have async shutdown methods returning Promises,
   *         the caller should wait for that Promise to resolve.
   */
  unregisterProvider(aProvider) {
    if (!aProvider || typeof aProvider != "object")
      throw Components.Exception("aProvider must be specified",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.providers.delete(aProvider);
    // The test harness will unregister XPIProvider *after* shutdown, which is
    // after the provider will have been moved from providers to
    // pendingProviders.
    this.pendingProviders.delete(aProvider);

    for (let type in this.types) {
      this.types[type].providers = this.types[type].providers.filter(p => p != aProvider);
      if (this.types[type].providers.length == 0) {
        let oldType = this.types[type].type;
        delete this.types[type];

        let typeListeners = new Set(this.typeListeners);
        for (let listener of typeListeners)
          safeCall(() => listener.onTypeRemoved(oldType));
      }
    }

    // If we're unregistering after startup but before shutting down,
    // remove the blocker for this provider's shutdown and call it.
    // If we're already shutting down, just let gShutdownBarrier call it to avoid races.
    if (gStarted && !gShutdownInProgress) {
      logger.debug("Unregistering shutdown blocker for " + providerName(aProvider));
      let shutter = this.providerShutdowns.get(aProvider);
      if (shutter) {
        this.providerShutdowns.delete(aProvider);
        gShutdownBarrier.client.removeBlocker(shutter);
        return shutter();
      }
    }
    return undefined;
  },

  /**
   * Mark a provider as safe to access via AddonManager APIs, before its
   * startup has completed.
   *
   * Normally a provider isn't marked as safe until after its (synchronous)
   * startup() method has returned. Until a provider has been marked safe,
   * it won't be used by any of the AddonManager APIs. markProviderSafe()
   * allows a provider to mark itself as safe during its startup; this can be
   * useful if the provider wants to perform tasks that block startup, which
   * happen after its required initialization tasks and therefore when the
   * provider is in a safe state.
   *
   * @param aProvider Provider object to mark safe
   */
  markProviderSafe(aProvider) {
    if (!gStarted) {
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);
    }

    if (!aProvider || typeof aProvider != "object") {
      throw Components.Exception("aProvider must be specified",
                                 Cr.NS_ERROR_INVALID_ARG);
    }

    if (!this.pendingProviders.has(aProvider)) {
      return;
    }

    this.pendingProviders.delete(aProvider);
    this.providers.add(aProvider);
  },

  /**
   * Calls a method on all registered providers if it exists and consumes any
   * thrown exception. Return values are ignored. Any parameters after the
   * method parameter are passed to the provider's method.
   * WARNING: Do not use for asynchronous calls; callProviders() does not
   * invoke callbacks if provider methods throw synchronous exceptions.
   *
   * @param  aMethod
   *         The method name to call
   * @see    callProvider
   */
  callProviders(aMethod, ...aArgs) {
    if (!aMethod || typeof aMethod != "string")
      throw Components.Exception("aMethod must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    let providers = [...this.providers];
    for (let provider of providers) {
      try {
        if (aMethod in provider)
          provider[aMethod].apply(provider, aArgs);
      } catch (e) {
        reportProviderError(provider, aMethod, e);
      }
    }
  },

  /**
   * Report the current state of asynchronous shutdown
   */
  shutdownState() {
    let state = [];
    if (gShutdownBarrier) {
      state.push({
        name: gShutdownBarrier.client.name,
        state: gShutdownBarrier.state
      });
    }
    state.push({
      name: "AddonRepository: async shutdown",
      state: gRepoShutdownState
    });
    return state;
  },

  /**
   * Shuts down the addon manager and all registered providers, this must clean
   * up everything in order for automated tests to fake restarts.
   * @return Promise{null} that resolves when all providers and dependent modules
   *                       have finished shutting down
   */
  async shutdownManager() {
    logger.debug("shutdown");
    this.callManagerListeners("onShutdown");

    gRepoShutdownState = "pending";
    gShutdownInProgress = true;
    // Clean up listeners
    Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
    Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
    Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
    Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
    Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
    Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
    gPluginPageListener.destroy();
    gPluginPageListener = null;

    let savedError = null;
    // Only shut down providers if they've been started.
    if (gStarted) {
      try {
        await gShutdownBarrier.wait();
      } catch (err) {
        savedError = err;
        logger.error("Failure during wait for shutdown barrier", err);
        AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err);
      }
    }

    // Shut down AddonRepository after providers (if any).
    try {
      gRepoShutdownState = "in progress";
      await AddonRepository.shutdown();
      gRepoShutdownState = "done";
    } catch (err) {
      savedError = err;
      logger.error("Failure during AddonRepository shutdown", err);
      AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
    }

    logger.debug("Async provider shutdown done");
    this.managerListeners.clear();
    this.installListeners.clear();
    this.addonListeners.clear();
    this.typeListeners.clear();
    this.providerShutdowns.clear();
    for (let type in this.startupChanges)
      delete this.startupChanges[type];
    gStarted = false;
    gStartupComplete = false;
    gShutdownBarrier = null;
    gShutdownInProgress = false;
    if (savedError) {
      throw savedError;
    }
  },

  requestPlugins({ target: port }) {
    // Lists all the properties that plugins.html needs
    const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version",
                          "isActive", "blocklistState", "description",
                          "pluginMimeTypes"];
    function filterProperties(plugin) {
      let filtered = {};
      for (let prop of NEEDED_PROPS) {
        filtered[prop] = plugin[prop];
      }
      return filtered;
    }

    AddonManager.getAddonsByTypes(["plugin"], function(aPlugins) {
      port.sendAsyncMessage("PluginList", aPlugins.map(filterProperties));
    });
  },

  /**
   * Notified when a preference we're interested in has changed.
   *
   * @see nsIObserver
   */
  observe(aSubject, aTopic, aData) {
    switch (aData) {
      case PREF_EM_CHECK_COMPATIBILITY: {
        let oldValue = gCheckCompatibility;
        gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY, true);

        this.callManagerListeners("onCompatibilityModeChanged");

        if (gCheckCompatibility != oldValue)
          this.updateAddonAppDisabledStates();

        break;
      }
      case PREF_EM_STRICT_COMPATIBILITY: {
        let oldValue = gStrictCompatibility;
        gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);

        this.callManagerListeners("onCompatibilityModeChanged");

        if (gStrictCompatibility != oldValue)
          this.updateAddonAppDisabledStates();

        break;
      }
      case PREF_EM_CHECK_UPDATE_SECURITY: {
        let oldValue = gCheckUpdateSecurity;
        gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, true);

        this.callManagerListeners("onCheckUpdateSecurityChanged");

        if (gCheckUpdateSecurity != oldValue)
          this.updateAddonAppDisabledStates();

        break;
      }
      case PREF_EM_UPDATE_ENABLED: {
        gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, true);

        this.callManagerListeners("onUpdateModeChanged");
        break;
      }
      case PREF_EM_AUTOUPDATE_DEFAULT: {
        gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, true);

        this.callManagerListeners("onUpdateModeChanged");
        break;
      }
      case PREF_EM_HOTFIX_ID: {
        gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID, "");
        break;
      }
      case PREF_MIN_WEBEXT_PLATFORM_VERSION: {
        gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION);
        break;
      }
    }
  },

  /**
   * Replaces %...% strings in an addon url (update and updateInfo) with
   * appropriate values.
   *
   * @param  aAddon
   *         The Addon representing the add-on
   * @param  aUri
   *         The string representation of the URI to escape
   * @param  aAppVersion
   *         The optional application version to use for %APP_VERSION%
   * @return The appropriately escaped URI.
   */
  escapeAddonURI(aAddon, aUri, aAppVersion) {
    if (!aAddon || typeof aAddon != "object")
      throw Components.Exception("aAddon must be an Addon object",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aUri || typeof aUri != "string")
      throw Components.Exception("aUri must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aAppVersion && typeof aAppVersion != "string")
      throw Components.Exception("aAppVersion must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled"
                                                                 : "userEnabled";

    if (!aAddon.isCompatible)
      addonStatus += ",incompatible";
    if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
      addonStatus += ",blocklisted";
    if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
      addonStatus += ",softblocked";

    try {
      var xpcomABI = Services.appinfo.XPCOMABI;
    } catch (ex) {
      xpcomABI = UNKNOWN_XPCOM_ABI;
    }

    let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id);
    uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version);
    uri = uri.replace(/%ITEM_STATUS%/g, addonStatus);
    uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID);
    uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion :
                                                      Services.appinfo.version);
    uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION);
    uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS);
    uri = uri.replace(/%APP_ABI%/g, xpcomABI);
    uri = uri.replace(/%APP_LOCALE%/g, getLocale());
    uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version);

    // Replace custom parameters (names of custom parameters must have at
    // least 3 characters to prevent lookups for something like %D0%C8)
    var catMan = null;
    uri = uri.replace(/%(\w{3,})%/g, function(aMatch, aParam) {
      if (!catMan) {
        catMan = Cc["@mozilla.org/categorymanager;1"].
                 getService(Ci.nsICategoryManager);
      }

      try {
        var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam);
        var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2);
        return paramHandler.getPropertyAsAString(aParam);
      } catch (e) {
        return aMatch;
      }
    });

    // escape() does not properly encode + symbols in any embedded FVF strings.
    return uri.replace(/\+/g, "%2B");
  },

  _updatePromptHandler(info) {
    let oldPerms = info.existingAddon.userPermissions;
    if (!oldPerms) {
      // Updating from a legacy add-on, just let it proceed
      return Promise.resolve();
    }

    let newPerms = info.addon.userPermissions;

    let difference = Extension.comparePermissions(oldPerms, newPerms);

    // If there are no new permissions, just go ahead with the update
    if (difference.origins.length == 0 && difference.permissions.length == 0) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      let subject = {wrappedJSObject: {
        addon: info.addon,
        permissions: difference,
        resolve, reject
      }};
      Services.obs.notifyObservers(subject, "webextension-update-permissions");
    });
  },

  /**
   * Performs a background update check by starting an update for all add-ons
   * that can be updated.
   * @return Promise{null} Resolves when the background update check is complete
   *                       (the resulting addon installations may still be in progress).
   */
  backgroundUpdateCheck() {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    let buPromise = (async () => {
      let hotfixID = this.hotfixID;

      let appUpdateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) &&
                             Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO);
      let checkHotfix = hotfixID && appUpdateEnabled;

      logger.debug("Background update check beginning");

      Services.obs.notifyObservers(null, "addons-background-update-start");

      if (this.updateEnabled) {
        let scope = {};
        Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
        scope.LightweightThemeManager.updateCurrentTheme();

        let allAddons = await this.getAllAddons();

        // Repopulate repository cache first, to ensure compatibility overrides
        // are up to date before checking for addon updates.
        await AddonRepository.backgroundUpdateCheck();

        // Keep track of all the async add-on updates happening in parallel
        let updates = [];

        for (let addon of allAddons) {
          if (addon.id == hotfixID) {
            continue;
          }

          // Check all add-ons for updates so that any compatibility updates will
          // be applied
          updates.push(new Promise((resolve, reject) => {
            addon.findUpdates({
              onUpdateAvailable(aAddon, aInstall) {
                // Start installing updates when the add-on can be updated and
                // background updates should be applied.
                logger.debug("Found update for add-on ${id}", aAddon);
                if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
                    AddonManager.shouldAutoUpdate(aAddon)) {
                  // XXX we really should resolve when this install is done,
                  // not when update-available check completes, no?
                  logger.debug(`Starting upgrade install of ${aAddon.id}`);
                  if (WEBEXT_PERMISSION_PROMPTS) {
                    aInstall.promptHandler = (...args) => AddonManagerInternal._updatePromptHandler(...args);
                  }
                  aInstall.install();
                }
              },

              onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
            }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
          }));
        }
        await Promise.all(updates);
      }

      if (checkHotfix) {
        var hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION, "");

        let url = null;
        if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING)
          url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL);
        else
          url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL);

        // Build the URI from a fake add-on data.
        url = AddonManager.escapeAddonURI({
          id: hotfixID,
          version: hotfixVersion,
          userDisabled: false,
          appDisabled: false
        }, url);

        Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");
        let update = null;
        try {
          let foundUpdates = await new Promise((resolve, reject) => {
            AddonUpdateChecker.checkForUpdates(hotfixID, null, url, {
              onUpdateCheckComplete: resolve,
              onUpdateCheckError: reject
            });
          });
          update = AddonUpdateChecker.getNewestCompatibleUpdate(foundUpdates);
        } catch (e) {
          // AUC.checkForUpdates already logged the error
        }

        // Check that we have a hotfix update, and it's newer than the one we already
        // have installed (if any)
        if (update) {
          if (Services.vc.compare(hotfixVersion, update.version) < 0) {
            logger.debug("Downloading hotfix version " + update.version);
            let aInstall = await AddonManagerInternal.getInstallForURL(
                update.updateURL, "application/x-xpinstall", update.updateHash,
                null, null, update.version);

            aInstall.addListener({
              onDownloadEnded(aInstall) {
                if (aInstall.addon.id != hotfixID) {
                  logger.warn("The downloaded hotfix add-on did not have the " +
                              "expected ID and so will not be installed.");
                  aInstall.cancel();
                  return;
                }

                // If XPIProvider has reported the hotfix as properly signed then
                // there is nothing more to do here
                if (aInstall.addon.signedState == AddonManager.SIGNEDSTATE_SIGNED)
                  return;

                try {
                  if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES))
                    return;
                } catch (e) {
                  // By default don't do certificate checks.
                  return;
                }

                try {
                  CertUtils.validateCert(aInstall.certificate,
                                         CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS));
                } catch (e) {
                  logger.warn("The hotfix add-on was not signed by the expected " +
                       "certificate and so will not be installed.", e);
                  aInstall.cancel();
                }
              },

              onInstallEnded(aInstall) {
                // Remember the last successfully installed version.
                Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION,
                                           aInstall.version);
              },

              onInstallCancelled(aInstall) {
                // Revert to the previous version if the installation was
                // cancelled.
                Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION,
                                           hotfixVersion);
              }
            });

            aInstall.install();
          }
        }
      }

      if (appUpdateEnabled) {
        try {
          await AddonManagerInternal._getProviderByName("XPIProvider").updateSystemAddons();
        } catch (e) {
          logger.warn("Failed to update system addons", e);
        }
      }

      logger.debug("Background update check complete");
      Services.obs.notifyObservers(null,
                                   "addons-background-update-complete");
    })();
    // Fork the promise chain so we can log the error and let our caller see it too.
    buPromise.catch(e => logger.warn("Error in background update", e));
    return buPromise;
  },

  /**
   * Adds a add-on to the list of detected changes for this startup. If
   * addStartupChange is called multiple times for the same add-on in the same
   * startup then only the most recent change will be remembered.
   *
   * @param  aType
   *         The type of change as a string. Providers can define their own
   *         types of changes or use the existing defined STARTUP_CHANGE_*
   *         constants
   * @param  aID
   *         The ID of the add-on
   */
  addStartupChange(aType, aID) {
    if (!aType || typeof aType != "string")
      throw Components.Exception("aType must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aID || typeof aID != "string")
      throw Components.Exception("aID must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (gStartupComplete)
      return;
    logger.debug("Registering startup change '" + aType + "' for " + aID);

    // Ensure that an ID is only listed in one type of change
    for (let type in this.startupChanges)
      this.removeStartupChange(type, aID);

    if (!(aType in this.startupChanges))
      this.startupChanges[aType] = [];
    this.startupChanges[aType].push(aID);
  },

  /**
   * Removes a startup change for an add-on.
   *
   * @param  aType
   *         The type of change
   * @param  aID
   *         The ID of the add-on
   */
  removeStartupChange(aType, aID) {
    if (!aType || typeof aType != "string")
      throw Components.Exception("aType must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aID || typeof aID != "string")
      throw Components.Exception("aID must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (gStartupComplete)
      return;

    if (!(aType in this.startupChanges))
      return;

    this.startupChanges[aType] = this.startupChanges[aType].filter(aItem => aItem != aID);
  },

  /**
   * Calls all registered AddonManagerListeners with an event. Any parameters
   * after the method parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   */
  callManagerListeners(aMethod, ...aArgs) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMethod || typeof aMethod != "string")
      throw Components.Exception("aMethod must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    let managerListeners = new Set(this.managerListeners);
    for (let listener of managerListeners) {
      try {
        if (aMethod in listener)
          listener[aMethod].apply(listener, aArgs);
      } catch (e) {
        logger.warn("AddonManagerListener threw exception when calling " + aMethod, e);
      }
    }
  },

  /**
   * Calls all registered InstallListeners with an event. Any parameters after
   * the extraListeners parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   * @param  aExtraListeners
   *         An optional array of extra InstallListeners to also call
   * @return false if any of the listeners returned false, true otherwise
   */
  callInstallListeners(aMethod,
                                 aExtraListeners, ...aArgs) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMethod || typeof aMethod != "string")
      throw Components.Exception("aMethod must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aExtraListeners && !Array.isArray(aExtraListeners))
      throw Components.Exception("aExtraListeners must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    let result = true;
    let listeners;
    if (aExtraListeners)
      listeners = new Set(aExtraListeners.concat(Array.from(this.installListeners)));
    else
      listeners = new Set(this.installListeners);

    for (let listener of listeners) {
      try {
        if (aMethod in listener) {
          if (listener[aMethod].apply(listener, aArgs) === false)
            result = false;
        }
      } catch (e) {
        logger.warn("InstallListener threw exception when calling " + aMethod, e);
      }
    }
    return result;
  },

  /**
   * Calls all registered AddonListeners with an event. Any parameters after
   * the method parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   */
  callAddonListeners(aMethod, ...aArgs) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMethod || typeof aMethod != "string")
      throw Components.Exception("aMethod must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    let addonListeners = new Set(this.addonListeners);
    for (let listener of addonListeners) {
      try {
        if (aMethod in listener)
          listener[aMethod].apply(listener, aArgs);
      } catch (e) {
        logger.warn("AddonListener threw exception when calling " + aMethod, e);
      }
    }
  },

  /**
   * Notifies all providers that an add-on has been enabled when that type of
   * add-on only supports a single add-on being enabled at a time. This allows
   * the providers to disable theirs if necessary.
   *
   * @param  aID
   *         The ID of the enabled add-on
   * @param  aType
   *         The type of the enabled add-on
   * @param  aPendingRestart
   *         A boolean indicating if the change will only take place the next
   *         time the application is restarted
   */
  notifyAddonChanged(aID, aType, aPendingRestart) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (aID && typeof aID != "string")
      throw Components.Exception("aID must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aType || typeof aType != "string")
      throw Components.Exception("aType must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    // Temporary hack until bug 520124 lands.
    // We can get here during synchronous startup, at which point it's
    // considered unsafe (and therefore disallowed by AddonManager.jsm) to
    // access providers that haven't been initialized yet. Since this is when
    // XPIProvider is starting up, XPIProvider can't access itself via APIs
    // going through AddonManager.jsm. Furthermore, LightweightThemeManager may
    // not be initialized until after XPIProvider is, and therefore would also
    // be unaccessible during XPIProvider startup. Thankfully, these are the
    // only two uses of this API, and we know it's safe to use this API with
    // both providers; so we have this hack to allow bypassing the normal
    // safetey guard.
    // The notifyAddonChanged/addonChanged API will be unneeded and therefore
    // removed by bug 520124, so this is a temporary quick'n'dirty hack.
    let providers = [...this.providers, ...this.pendingProviders];
    for (let provider of providers) {
      callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart);
    }
  },

  /**
   * Notifies all providers they need to update the appDisabled property for
   * their add-ons in response to an application change such as a blocklist
   * update.
   */
  updateAddonAppDisabledStates() {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    this.callProviders("updateAddonAppDisabledStates");
  },

  /**
   * Notifies all providers that the repository has updated its data for
   * installed add-ons.
   */
  updateAddonRepositoryData() {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    return (async () => {
      for (let provider of this.providers) {
        await promiseCallProvider(provider, "updateAddonRepositoryData");
      }

      // only tests should care about this
      Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated");
    })();
  },

  /**
   * Asynchronously gets an AddonInstall for a URL.
   *
   * @param  aUrl
   *         The string represenation of the URL the add-on is located at
   * @param  aMimetype
   *         The mimetype of the add-on
   * @param  aHash
   *         An optional hash of the add-on
   * @param  aName
   *         An optional placeholder name while the add-on is being downloaded
   * @param  aIcons
   *         Optional placeholder icons while the add-on is being downloaded
   * @param  aVersion
   *         An optional placeholder version while the add-on is being downloaded
   * @param  aBrowser
   *         An optional <browser> element for download permissions prompts.
   * @throws if the aUrl, aCallback or aMimetype arguments are not specified
   */
  getInstallForURL(aUrl, aMimetype, aHash, aName,
                             aIcons, aVersion, aBrowser) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aUrl || typeof aUrl != "string")
      throw Components.Exception("aURL must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aMimetype || typeof aMimetype != "string")
      throw Components.Exception("aMimetype must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aHash && typeof aHash != "string")
      throw Components.Exception("aHash must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aName && typeof aName != "string")
      throw Components.Exception("aName must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aIcons) {
      if (typeof aIcons == "string")
        aIcons = { "32": aIcons };
      else if (typeof aIcons != "object")
        throw Components.Exception("aIcons must be a string, an object or null",
                                   Cr.NS_ERROR_INVALID_ARG);
    } else {
      aIcons = {};
    }

    if (aVersion && typeof aVersion != "string")
      throw Components.Exception("aVersion must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
      throw Components.Exception("aBrowser must be a nsIDOMElement or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    for (let provider of this.providers) {
      if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
        return promiseCallProvider(
          provider, "getInstallForURL", aUrl, aHash, aName, aIcons,
          aVersion, aBrowser);
      }
    }

    return Promise.resolve(null);
  },

  /**
   * Asynchronously gets an AddonInstall for an nsIFile.
   *
   * @param  aFile
   *         The nsIFile where the add-on is located
   * @param  aMimetype
   *         An optional mimetype hint for the add-on
   * @throws if the aFile or aCallback arguments are not specified
   */
  getInstallForFile(aFile, aMimetype) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!(aFile instanceof Ci.nsIFile))
      throw Components.Exception("aFile must be a nsIFile",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aMimetype && typeof aMimetype != "string")
      throw Components.Exception("aMimetype must be a string or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    return (async () => {
      for (let provider of this.providers) {
        let install = await promiseCallProvider(
          provider, "getInstallForFile", aFile);

        if (install)
          return install;
      }

      return null;
    })();
  },

  /**
   * Asynchronously gets all current AddonInstalls optionally limiting to a list
   * of types.
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   * @throws If the aCallback argument is not specified
   */
  getInstallsByTypes(aTypes) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (aTypes && !Array.isArray(aTypes))
      throw Components.Exception("aTypes must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    return (async () => {
      let installs = [];

      for (let provider of this.providers) {
        let providerInstalls = await promiseCallProvider(
          provider, "getInstallsByTypes", aTypes);

        if (providerInstalls)
          installs.push(...providerInstalls);
      }

      return installs;
    })();
  },

  /**
   * Asynchronously gets all current AddonInstalls.
   */
  getAllInstalls() {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    return this.getInstallsByTypes(null);
  },

  /**
   * Synchronously map a URI to the corresponding Addon ID.
   *
   * Mappable URIs are limited to in-application resources belonging to the
   * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
   * but do not include URIs from meta data, such as the add-on homepage.
   *
   * @param  aURI
   *         nsIURI to map to an addon id
   * @return string containing the Addon ID or null
   * @see    amIAddonManager.mapURIToAddonID
   */
  mapURIToAddonID(aURI) {
    if (!(aURI instanceof Ci.nsIURI)) {
      throw Components.Exception("aURI is not a nsIURI",
                                 Cr.NS_ERROR_INVALID_ARG);
    }

    // Try all providers
    let providers = [...this.providers];
    for (let provider of providers) {
      var id = callProvider(provider, "mapURIToAddonID", null, aURI);
      if (id !== null) {
        return id;
      }
    }

    return null;
  },

  /**
   * Checks whether installation is enabled for a particular mimetype.
   *
   * @param  aMimetype
   *         The mimetype to check
   * @return true if installation is enabled for the mimetype
   */
  isInstallEnabled(aMimetype) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMimetype || typeof aMimetype != "string")
      throw Components.Exception("aMimetype must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    let providers = [...this.providers];
    for (let provider of providers) {
      if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
          callProvider(provider, "isInstallEnabled"))
        return true;
    }
    return false;
  },

  /**
   * Checks whether a particular source is allowed to install add-ons of a
   * given mimetype.
   *
   * @param  aMimetype
   *         The mimetype of the add-on
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @return true if the source is allowed to install this mimetype
   */
  isInstallAllowed(aMimetype, aInstallingPrincipal) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMimetype || typeof aMimetype != "string")
      throw Components.Exception("aMimetype must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
      throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
                                 Cr.NS_ERROR_INVALID_ARG);

    let providers = [...this.providers];
    for (let provider of providers) {
      if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
          callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal))
        return true;
    }
    return false;
  },

  installNotifyObservers(aTopic, aBrowser, aUri, aInstall, aInstallFn) {
    let info = {
      wrappedJSObject: {
        browser: aBrowser,
        originatingURI: aUri,
        installs: [aInstall],
        install: aInstallFn,
      },
    };
    Services.obs.notifyObservers(info, aTopic);
  },

  startInstall(browser, url, install) {
    this.installNotifyObservers("addon-install-started", browser, url, install);

    // Local installs may already be in a failed state in which case
    // we won't get any further events, detect those cases now.
    if (install.state == AddonManager.STATE_DOWNLOADED && install.addon.appDisabled) {
          this.installNotifyObservers("addon-install-failed", browser, url, install);
          return;
    }

    let self = this;
    let listener = {
      onDownloadCancelled() {
        install.removeListener(listener);
      },

      onDownloadFailed() {
        install.removeListener(listener);
        self.installNotifyObservers("addon-install-failed", browser, url, install);
      },

      onDownloadEnded() {
        if (install.addon.appDisabled) {
          // App disabled items are not compatible and so fail to install
          install.removeListener(listener);
          install.cancel();
          self.installNotifyObservers("addon-install-failed", browser, url, install);
        }
      },

      onInstallCancelled() {
        install.removeListener(listener);
      },

      onInstallFailed() {
        install.removeListener(listener);
        self.installNotifyObservers("addon-install-failed", browser, url, install);
      },

      onInstallEnded() {
        install.removeListener(listener);

        // If installing a theme that is disabled and can be enabled
        // then enable it
        if (install.addon.type == "theme" &&
            install.addon.userDisabled == true &&
            install.addon.appDisabled == false) {
              install.addon.userDisabled = false;
        }

        let needsRestart = (install.addon.pendingOperations != AddonManager.PENDING_NONE);

        if (WEBEXT_PERMISSION_PROMPTS && !needsRestart) {
          let subject = {wrappedJSObject: {target: browser, addon: install.addon}};
          Services.obs.notifyObservers(subject, "webextension-install-notify");
        } else {
          self.installNotifyObservers("addon-install-complete", browser, url, install);
        }
      },
    };

    install.addListener(listener);

    // Start downloading if it hasn't already begun
    install.install();
  },

  /**
   * Starts installation of an AddonInstall notifying the registered
   * web install listener of blocked or started installs.
   *
   * @param  aMimetype
   *         The mimetype of add-ons being installed
   * @param  aBrowser
   *         The optional browser element that started the installs
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @param  aInstall
   *         The AddonInstall to be installed
   */
  installAddonFromWebpage(aMimetype, aBrowser,
                                    aInstallingPrincipal, aInstall) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aMimetype || typeof aMimetype != "string")
      throw Components.Exception("aMimetype must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
      throw Components.Exception("aSource must be a nsIDOMElement, or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
      throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
                                 Cr.NS_ERROR_INVALID_ARG);

    // When a chrome in-content UI has loaded a <browser> inside to host a
    // website we want to do our security checks on the inner-browser but
    // notify front-end that install events came from the outer-browser (the
    // main tab's browser). Check this by seeing if the browser we've been
    // passed is in a content type docshell and if so get the outer-browser.
    let topBrowser = aBrowser;
    let docShell = aBrowser.ownerGlobal
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDocShell)
                           .QueryInterface(Ci.nsIDocShellTreeItem);
    if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
      topBrowser = docShell.chromeEventHandler;

    try {
      if (!this.isInstallEnabled(aMimetype)) {
        aInstall.cancel();

        this.installNotifyObservers("addon-install-disabled", topBrowser,
                                    aInstallingPrincipal.URI, aInstall);
        return;
      } else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
        aInstall.cancel();

        this.installNotifyObservers("addon-install-origin-blocked", topBrowser,
                                    aInstallingPrincipal.URI, aInstall);
        return;
      }

      // The install may start now depending on the web install listener,
      // listen for the browser navigating to a new origin and cancel the
      // install in that case.
      new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);

      let startInstall = (source) => {
        AddonManagerInternal.setupPromptHandler(aBrowser, aInstallingPrincipal.URI, aInstall, true, source);

        AddonManagerInternal.startInstall(aBrowser, aInstallingPrincipal.URI, aInstall);
      };
      if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
        this.installNotifyObservers("addon-install-blocked", topBrowser,
                                    aInstallingPrincipal.URI, aInstall,
                                    () => startInstall("other"));
      } else {
        startInstall("AMO");
      }
    } catch (e) {
      // In the event that the weblistener throws during instantiation or when
      // calling onWebInstallBlocked or onWebInstallRequested the
      // install should get cancelled.
      logger.warn("Failure calling web installer", e);
      aInstall.cancel();
    }
  },

  /**
   * Starts installation of an AddonInstall created from add-ons manager
   * front-end code (e.g., drag-and-drop of xpis or "Install Add-on from File"
   *
   * @param  browser
   *         The browser element where the installation was initiated
   * @param  uri
   *         The URI of the page where the installation was initiated
   * @param  install
   *         The AddonInstall to be installed
   */
  installAddonFromAOM(browser, uri, install) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    AddonManagerInternal.setupPromptHandler(browser, uri, install, true, "local");
    AddonManagerInternal.startInstall(browser, uri, install);
  },

  /**
   * Adds a new InstallListener if the listener is not already registered.
   *
   * @param  aListener
   *         The InstallListener to add
   */
  addInstallListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be a InstallListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

      this.installListeners.add(aListener);
  },

  /**
   * Removes an InstallListener if the listener is registered.
   *
   * @param  aListener
   *         The InstallListener to remove
   */
  removeInstallListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be a InstallListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.installListeners.delete(aListener);
  },
  /*
   * Adds new or overrides existing UpgradeListener.
   *
   * @param  aInstanceID
   *         The instance ID of an addon to register a listener for.
   * @param  aCallback
   *         The callback to invoke when updates are available for this addon.
   * @throws if there is no addon matching the instanceID
   */
  addUpgradeListener(aInstanceID, aCallback) {
   if (!aInstanceID || typeof aInstanceID != "symbol")
     throw Components.Exception("aInstanceID must be a symbol",
                                Cr.NS_ERROR_INVALID_ARG);

  if (!aCallback || typeof aCallback != "function")
    throw Components.Exception("aCallback must be a function",
                               Cr.NS_ERROR_INVALID_ARG);

   this.getAddonByInstanceID(aInstanceID).then(wrapper => {
     if (!wrapper) {
       throw Error("No addon matching instanceID:", aInstanceID.toString());
     }
     let addonId = wrapper.id;
     logger.debug(`Registering upgrade listener for ${addonId}`);
     this.upgradeListeners.set(addonId, aCallback);
   });
  },

  /**
   * Removes an UpgradeListener if the listener is registered.
   *
   * @param  aInstanceID
   *         The instance ID of the addon to remove
   */
  removeUpgradeListener(aInstanceID) {
    if (!aInstanceID || typeof aInstanceID != "symbol")
      throw Components.Exception("aInstanceID must be a symbol",
                                 Cr.NS_ERROR_INVALID_ARG);

    return this.getAddonByInstanceID(aInstanceID).then(addon => {
      if (!addon) {
        throw Error("No addon for instanceID:", aInstanceID.toString());
      }
      if (this.upgradeListeners.has(addon.id)) {
        this.upgradeListeners.delete(addon.id);
      } else {
        throw Error("No upgrade listener registered for addon ID:", addon.id);
      }
    });
  },

  /**
   * Installs a temporary add-on from a local file or directory.
   * @param  aFile
   *         An nsIFile for the file or directory of the add-on to be
   *         temporarily installed.
   * @return a Promise that rejects if the add-on is not a valid restartless
   *         add-on or if the same ID is already temporarily installed.
   */
  installTemporaryAddon(aFile) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!(aFile instanceof Ci.nsIFile))
      throw Components.Exception("aFile must be a nsIFile",
                                 Cr.NS_ERROR_INVALID_ARG);

    return AddonManagerInternal._getProviderByName("XPIProvider")
                               .installTemporaryAddon(aFile);
  },

  installAddonFromSources(aFile) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!(aFile instanceof Ci.nsIFile))
      throw Components.Exception("aFile must be a nsIFile",
                                 Cr.NS_ERROR_INVALID_ARG);

    return AddonManagerInternal._getProviderByName("XPIProvider")
                               .installAddonFromSources(aFile);
  },

  /**
   * Returns an Addon corresponding to an instance ID.
   * @param aInstanceID
   *        An Addon Instance ID symbol
   * @return {Promise}
   * @resolves The found Addon or null if no such add-on exists.
   * @rejects  Never
   * @throws if the aInstanceID argument is not specified
   *         or the AddonManager is not initialized
   */
   getAddonByInstanceID(aInstanceID) {
     if (!gStarted)
       throw Components.Exception("AddonManager is not initialized",
                                  Cr.NS_ERROR_NOT_INITIALIZED);

     if (!aInstanceID || typeof aInstanceID != "symbol")
       throw Components.Exception("aInstanceID must be a Symbol()",
                                  Cr.NS_ERROR_INVALID_ARG);

     return AddonManagerInternal._getProviderByName("XPIProvider")
                                .getAddonByInstanceID(aInstanceID);
   },

  /**
   * Gets an icon from the icon set provided by the add-on
   * that is closest to the specified size.
   *
   * The optional window parameter will be used to determine
   * the screen resolution and select a more appropriate icon.
   * Calling this method with 48px on retina screens will try to
   * match an icon of size 96px.
   *
   * @param  aAddon
   *         An addon object, meaning:
   *         An object with either an icons property that is a key-value
   *         list of icon size and icon URL, or an object having an iconURL
   *         and icon64URL property.
   * @param  aSize
   *         Ideal icon size in pixels
   * @param  aWindow
   *         Optional window object for determining the correct scale.
   * @return {String} The absolute URL of the icon or null if the addon doesn't have icons
   */
  getPreferredIconURL(aAddon, aSize, aWindow = undefined) {
    if (aWindow && aWindow.devicePixelRatio) {
      aSize *= aWindow.devicePixelRatio;
    }

    let icons = aAddon.icons;

    // certain addon-types only have iconURLs
    if (!icons) {
      icons = {};
      if (aAddon.iconURL) {
        icons[32] = aAddon.iconURL;
        icons[48] = aAddon.iconURL;
      }
      if (aAddon.icon64URL) {
        icons[64] = aAddon.icon64URL;
      }
    }

    // quick return if the exact size was found
    if (icons[aSize]) {
      return icons[aSize];
    }

    let bestSize = null;

    for (let size of Object.keys(icons)) {
      if (!INTEGER.test(size)) {
        throw Components.Exception("Invalid icon size, must be an integer",
                                   Cr.NS_ERROR_ILLEGAL_VALUE);
      }

      size = parseInt(size, 10);

      if (!bestSize) {
        bestSize = size;
        continue;
      }

      if (size > aSize && bestSize > aSize) {
        // If both best size and current size are larger than the wanted size then choose
        // the one closest to the wanted size
        bestSize = Math.min(bestSize, size);
      } else {
        // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize
        // or above aSize
        bestSize = Math.max(bestSize, size);
      }
    }

    return icons[bestSize] || null;
  },

  /**
   * Asynchronously gets an add-on with a specific ID.
   *
   * @param  aID
   *         The ID of the add-on to retrieve
   * @return {Promise}
   * @resolves The found Addon or null if no such add-on exists.
   * @rejects  Never
   * @throws if the aID argument is not specified
   */
  getAddonByID(aID) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aID || typeof aID != "string")
      throw Components.Exception("aID must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    let promises = Array.from(this.providers,
      p => promiseCallProvider(p, "getAddonByID", aID));
    return Promise.all(promises).then(aAddons => {
      return aAddons.find(a => !!a) || null;
    });
  },

  /**
   * Asynchronously get an add-on with a specific Sync GUID.
   *
   * @param  aGUID
   *         String GUID of add-on to retrieve
   * @throws if the aGUID argument is not specified
   */
  getAddonBySyncGUID(aGUID) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!aGUID || typeof aGUID != "string")
      throw Components.Exception("aGUID must be a non-empty string",
                                 Cr.NS_ERROR_INVALID_ARG);

    return (async () => {
      for (let provider of this.providers) {
        let addon = await promiseCallProvider(
          provider, "getAddonBySyncGUID", aGUID);

        if (addon)
          return addon;
      }

      return null;
    })();
  },

  /**
   * Asynchronously gets an array of add-ons.
   *
   * @param  aIDs
   *         The array of IDs to retrieve
   * @return {Promise}
   * @resolves The array of found add-ons.
   * @rejects  Never
   * @throws if the aIDs argument is not specified
   */
  getAddonsByIDs(aIDs) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (!Array.isArray(aIDs))
      throw Components.Exception("aIDs must be an array",
                                 Cr.NS_ERROR_INVALID_ARG);

    let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a));
    return Promise.all(promises);
  },

  /**
   * Asynchronously gets add-ons of specific types.
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   */
  getAddonsByTypes(aTypes) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (aTypes && !Array.isArray(aTypes))
      throw Components.Exception("aTypes must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    return (async () => {
      let addons = [];

      for (let provider of this.providers) {
        let providerAddons = await promiseCallProvider(
          provider, "getAddonsByTypes", aTypes);

        if (providerAddons)
          addons.push(...providerAddons);
      }

      return addons;
    })();
  },

  /**
   * Gets active add-ons of specific types.
   *
   * This is similar to getAddonsByTypes() but it may return a limited
   * amount of information about only active addons.  Consequently, it
   * can be implemented by providers using only immediately available
   * data as opposed to getAddonsByTypes which may require I/O).
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   */
  async getActiveAddons(aTypes) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (aTypes && !Array.isArray(aTypes))
      throw Components.Exception("aTypes must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    let addons = [];

    for (let provider of this.providers) {
      let providerAddons;
      if ("getActiveAddons" in provider) {
        providerAddons = await callProvider(provider, "getActiveAddons", null, aTypes);
      } else {
        providerAddons = await promiseCallProvider(provider, "getAddonsByTypes", aTypes);
        providerAddons = providerAddons.filter(a => a.isActive);
      }

      if (providerAddons)
        addons.push(...providerAddons);
    }

    return addons;
  },

  /**
   * Asynchronously gets all installed add-ons.
   */
  getAllAddons() {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    return this.getAddonsByTypes(null);
  },

  /**
   * Asynchronously gets add-ons that have operations waiting for an application
   * restart to complete.
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   */
  getAddonsWithOperationsByTypes(aTypes) {
    if (!gStarted)
      throw Components.Exception("AddonManager is not initialized",
                                 Cr.NS_ERROR_NOT_INITIALIZED);

    if (aTypes && !Array.isArray(aTypes))
      throw Components.Exception("aTypes must be an array or null",
                                 Cr.NS_ERROR_INVALID_ARG);

    return (async () => {
      let addons = [];

      for (let provider of this.providers) {
        let providerAddons = await promiseCallProvider(
          provider, "getAddonsWithOperationsByTypes", aTypes);

        if (providerAddons)
          addons.push(...providerAddons);
      }

      return addons;
    })();
  },

  /**
   * Adds a new AddonManagerListener if the listener is not already registered.
   *
   * @param  aListener
   *         The listener to add
   */
  addManagerListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be an AddonManagerListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.managerListeners.add(aListener);
  },

  /**
   * Removes an AddonManagerListener if the listener is registered.
   *
   * @param  aListener
   *         The listener to remove
   */
  removeManagerListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be an AddonManagerListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.managerListeners.delete(aListener);
  },

  /**
   * Adds a new AddonListener if the listener is not already registered.
   *
   * @param  aListener
   *         The AddonListener to add
   */
  addAddonListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be an AddonListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.addonListeners.add(aListener);
  },

  /**
   * Removes an AddonListener if the listener is registered.
   *
   * @param  aListener
   *         The AddonListener to remove
   */
  removeAddonListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be an AddonListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.addonListeners.delete(aListener);
  },

  /**
   * Adds a new TypeListener if the listener is not already registered.
   *
   * @param  aListener
   *         The TypeListener to add
   */
  addTypeListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be a TypeListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.typeListeners.add(aListener);
  },

  /**
   * Removes an TypeListener if the listener is registered.
   *
   * @param  aListener
   *         The TypeListener to remove
   */
  removeTypeListener(aListener) {
    if (!aListener || typeof aListener != "object")
      throw Components.Exception("aListener must be a TypeListener object",
                                 Cr.NS_ERROR_INVALID_ARG);

    this.typeListeners.delete(aListener);
  },

  get addonTypes() {
    // A read-only wrapper around the types dictionary
    return new Proxy(this.types, {
      defineProperty(target, property, descriptor) {
        // Not allowed to define properties
        return false;
      },

      deleteProperty(target, property) {
        // Not allowed to delete properties
        return false;
      },

      get(target, property, receiver) {
        if (!target.hasOwnProperty(property))
          return undefined;

        return target[property].type;
      },

      getOwnPropertyDescriptor(target, property) {
        if (!target.hasOwnProperty(property))
          return undefined;

        return {
          value: target[property].type,
          writable: false,
          // Claim configurability to maintain the proxy invariants.
          configurable: true,
          enumerable: true
        }
      },

      preventExtensions(target) {
        // Not allowed to prevent adding new properties
        return false;
      },

      set(target, property, value, receiver) {
        // Not allowed to set properties
        return false;
      },

      setPrototypeOf(target, prototype) {
        // Not allowed to change prototype
        return false;
      }
    });
  },

  get autoUpdateDefault() {
    return gAutoUpdateDefault;
  },

  set autoUpdateDefault(aValue) {
    aValue = !!aValue;
    if (aValue != gAutoUpdateDefault)
      Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue);
    return aValue;
  },

  get checkCompatibility() {
    return gCheckCompatibility;
  },

  set checkCompatibility(aValue) {
    aValue = !!aValue;
    if (aValue != gCheckCompatibility) {
      if (!aValue)
        Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false);
      else
        Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY);
    }
    return aValue;
  },

  get strictCompatibility() {
    return gStrictCompatibility;
  },

  set strictCompatibility(aValue) {
    aValue = !!aValue;
    if (aValue != gStrictCompatibility)
      Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue);
    return aValue;
  },

  get checkUpdateSecurityDefault() {
    return gCheckUpdateSecurityDefault;
  },

  get checkUpdateSecurity() {
    return gCheckUpdateSecurity;
  },

  set checkUpdateSecurity(aValue) {
    aValue = !!aValue;
    if (aValue != gCheckUpdateSecurity) {
      if (aValue != gCheckUpdateSecurityDefault)
        Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue);
      else
        Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
    }
    return aValue;
  },

  get updateEnabled() {
    return gUpdateEnabled;
  },

  set updateEnabled(aValue) {
    aValue = !!aValue;
    if (aValue != gUpdateEnabled)
      Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue);
    return aValue;
  },

  get hotfixID() {
    return gHotfixID;
  },

  setupPromptHandler(browser, url, install, requireConfirm, source) {
    install.promptHandler = info => new Promise((resolve, _reject) => {
      let reject = () => {
        this.installNotifyObservers("addon-install-cancelled",
                                    browser, url, install);
        _reject();
      };

      // All installs end up in this callback when the add-on is available
      // for installation.  There are numerous different things that can
      // happen from here though.  For webextensions, if the application
      // implements webextension permission prompts, those always take
      // precedence.
      // If this add-on is not a webextension or if the application does not
      // implement permission prompts, no confirmation is displayed for
      // installs created with mozAddonManager (in which case requireConfirm
      // is false).
      // In the remaining cases, a confirmation prompt is displayed but the
      // application may override it either by implementing the
      // "@mozilla.org/addons/web-install-prompt;1" contract or by setting
      // the customConfirmationUI preference and responding to the
      // "addon-install-confirmation" notification.  If the application
      // does not implement its own prompt, use the built-in xul dialog.
      if (info.addon.userPermissions && WEBEXT_PERMISSION_PROMPTS) {
        let subject = {
          wrappedJSObject: {
            target: browser,
            info: Object.assign({resolve, reject, source}, info),
          }
        };
        subject.wrappedJSObject.info.permissions = info.addon.userPermissions;
        Services.obs.notifyObservers(subject, "webextension-permission-prompt");
      } else if (requireConfirm) {
        // The methods below all want to call the install() or cancel()
        // method on the provided AddonInstall object to either accept
        // or reject the confirmation.  Fit that into our promise-based
        // control flow by wrapping the install object.  However,
        // xpInstallConfirm.xul matches the install object it is passed
        // with the argument passed to an InstallListener, so give it
        // access to the underlying object through the .wrapped property.
        let proxy = new Proxy(install, {
          get(target, property) {
            if (property == "install") {
              return resolve;
            } else if (property == "cancel") {
              return reject;
            } else if (property == "wrapped") {
              return target;
            }
            let result = target[property];
            return (typeof result == "function") ? result.bind(target) : result;
          }
        });

        // Check for a custom installation prompt that may be provided by the
        // applicaton
        if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
          try {
            let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"].
                                      getService(Ci.amIWebInstallPrompt);
            prompt.confirm(browser, url, [proxy]);
            return;
          } catch (e) {}
        }

        if (Preferences.get("xpinstall.customConfirmationUI", false)) {
          this.installNotifyObservers("addon-install-confirmation",
                                      browser, url, proxy);
          return;
        }

        let args = {};
        args.url = url;
        args.installs = [proxy];
        args.wrappedJSObject = args;

        try {
          Cc["@mozilla.org/base/telemetry;1"].
                       getService(Ci.nsITelemetry).
                       getHistogramById("SECURITY_UI").
                       add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
          let parentWindow = null;
          if (browser) {
            // Find the outer browser
            let docShell = browser.ownerGlobal
                                  .QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDocShell)
                                  .QueryInterface(Ci.nsIDocShellTreeItem);
            if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
              browser = docShell.chromeEventHandler;

            parentWindow = browser.ownerGlobal;
            PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", browser);
          }

          // Ugh modal dialogs...
          let reallyReject = reject;
          let cancelled = false;
          reject = () => {
            cancelled = true;
          };
          Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG,
                                 null, "chrome,modal,centerscreen", args);
          if (cancelled) {
            reallyReject();
          }
        } catch (e) {
          logger.warn("Exception showing install confirmation dialog", e);
          // Cancel the install, as currently there is no way to make it fail
          // from here.
          reject();
        }
      } else {
        resolve();
      }
    });
  },

  webAPI: {
    // installs maps integer ids to AddonInstall instances.
    installs: new Map(),
    nextInstall: 0,

    sendEvent: null,
    setEventHandler(fn) {
      this.sendEvent = fn;
    },

    getAddonByID(target, id) {
      return new Promise(resolve => {
        AddonManager.getAddonByID(id, (addon) => {
          resolve(webAPIForAddon(addon));
        });
      });
    },

    // helper to copy (and convert) the properties we care about
    copyProps(install, obj) {
      obj.state = AddonManager.stateToString(install.state);
      obj.error = AddonManager.errorToString(install.error);
      obj.progress = install.progress;
      obj.maxProgress = install.maxProgress;
    },

    makeListener(id, mm) {
      const events = [
        "onDownloadStarted",
        "onDownloadProgress",
        "onDownloadEnded",
        "onDownloadCancelled",
        "onDownloadFailed",
        "onInstallStarted",
        "onInstallEnded",
        "onInstallCancelled",
        "onInstallFailed",
      ];

      let listener = {};
      let installPromise = new Promise((resolve, reject) => {
        events.forEach(event => {
          listener[event] = (install, addon) => {
            let data = {event, id};
            AddonManager.webAPI.copyProps(install, data);
            this.sendEvent(mm, data);
            if (event == "onInstallEnded") {
              resolve(addon);
            } else if (event == "onDownloadFailed" || event == "onInstallFailed") {
              reject({message: "install failed"});
            } else if (event == "onDownloadCancelled" || event == "onInstallCancelled") {
              reject({message: "install cancelled"});
            }
          }
        });
      });

      // We create the promise here since this is where we're setting
      // up the InstallListener, but if the install is never started,
      // no handlers will be attached so make sure we terminate errors.
      installPromise.catch(() => {});

      return {listener, installPromise};
    },

    forgetInstall(id) {
      let info = this.installs.get(id);
      if (!info) {
        throw new Error(`forgetInstall cannot find ${id}`);
      }
      info.install.removeListener(info.listener);
      this.installs.delete(id);
    },

    createInstall(target, options) {
      // Throw an appropriate error if the given URL is not valid
      // as an installation source.  Return silently if it is okay.
      function checkInstallUrl(url) {
        let host = Services.io.newURI(options.url).host;
        if (WEBAPI_INSTALL_HOSTS.includes(host)) {
          return;
        }
        if (Services.prefs.getBoolPref(PREF_WEBAPI_TESTING)
            && WEBAPI_TEST_INSTALL_HOSTS.includes(host)) {
          return;
        }

        throw new Error(`Install from ${host} not permitted`);
      }

      try {
        checkInstallUrl(options.url);
      } catch (err) {
        return Promise.reject({message: err.message});
      }

      return AddonManagerInternal.getInstallForURL(options.url, "application/x-xpinstall", options.hash)
                                 .then(install => {
        AddonManagerInternal.setupPromptHandler(target, null, install, false, "AMO");

        let id = this.nextInstall++;
        let {listener, installPromise} = this.makeListener(id, target.messageManager);
        install.addListener(listener);

        this.installs.set(id, {install, target, listener, installPromise});

        let result = {id};
        this.copyProps(install, result);
        return result;
      });
    },

    addonUninstall(target, id) {
      return new Promise(resolve => {
        AddonManager.getAddonByID(id, addon => {
          if (!addon) {
            resolve(false);
          }

          try {
            addon.uninstall();
            resolve(true);
          } catch (err) {
            Cu.reportError(err);
            resolve(false);
          }
        });
      });
    },

    addonSetEnabled(target, id, value) {
      return new Promise((resolve, reject) => {
        AddonManager.getAddonByID(id, addon => {
          if (!addon) {
            reject({message: `No such addon ${id}`});
          }
          addon.userDisabled = !value;
          resolve();
        });
      });
    },

    addonInstallDoInstall(target, id) {
      let state = this.installs.get(id);
      if (!state) {
        return Promise.reject(`invalid id ${id}`);
      }
      let result = state.install.install();

      return state.installPromise.then(addon => new Promise(resolve => {
        let callback = () => resolve(result);
        if (Preferences.get(PREF_WEBEXT_PERM_PROMPTS, false)) {
          let subject = {wrappedJSObject: {target, addon, callback}};
          Services.obs.notifyObservers(subject, "webextension-install-notify")
        } else {
          callback();
        }
      }));
    },

    addonInstallCancel(target, id) {
      let state = this.installs.get(id);
      if (!state) {
        return Promise.reject(`invalid id ${id}`);
      }
      return Promise.resolve(state.install.cancel());
    },

    clearInstalls(ids) {
      for (let id of ids) {
        this.forgetInstall(id);
      }
    },

    clearInstallsFrom(mm) {
      for (let [id, info] of this.installs) {
        if (info.target.messageManager == mm) {
          this.forgetInstall(id);
        }
      }
    },
  },
};

/**
 * Should not be used outside of core Mozilla code. This is a private API for
 * the startup and platform integration code to use. Refer to the methods on
 * AddonManagerInternal for documentation however note that these methods are
 * subject to change at any time.
 */
this.AddonManagerPrivate = {
  startup() {
    AddonManagerInternal.startup();
  },

  addonIsActive(addonId) {
    return AddonManagerInternal._getProviderByName("XPIProvider")
                               .addonIsActive(addonId);
  },

  /**
   * Gets an array of add-ons which were side-loaded prior to the last
   * startup, and are currently disabled.
   *
   * @returns {Promise<Array<Addon>>}
   */
  getNewSideloads() {
    return AddonManagerInternal._getProviderByName("XPIProvider")
                               .getNewSideloads();
  },

  get browserUpdated() {
    return gBrowserUpdated;
  },

  registerProvider(aProvider, aTypes) {
    AddonManagerInternal.registerProvider(aProvider, aTypes);
  },

  unregisterProvider(aProvider) {
    AddonManagerInternal.unregisterProvider(aProvider);
  },

  markProviderSafe(aProvider) {
    AddonManagerInternal.markProviderSafe(aProvider);
  },

  backgroundUpdateCheck() {
    return AddonManagerInternal.backgroundUpdateCheck();
  },

  backgroundUpdateTimerHandler() {
    // Don't call through to the real update check if no checks are enabled.
    let checkHotfix = AddonManagerInternal.hotfixID &&
                      Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) &&
                      Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO);

    if (!AddonManagerInternal.updateEnabled && !checkHotfix) {
      logger.info("Skipping background update check");
      return;
    }
    // Don't return the promise here, since the caller doesn't care.
    AddonManagerInternal.backgroundUpdateCheck();
  },

  addStartupChange(aType, aID) {
    AddonManagerInternal.addStartupChange(aType, aID);
  },

  removeStartupChange(aType, aID) {
    AddonManagerInternal.removeStartupChange(aType, aID);
  },

  notifyAddonChanged(aID, aType, aPendingRestart) {
    AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
  },

  updateAddonAppDisabledStates() {
    AddonManagerInternal.updateAddonAppDisabledStates();
  },

  updateAddonRepositoryData(aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.updateAddonRepositoryData(),
      aCallback);
  },

  callInstallListeners(...aArgs) {
    return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal,
                                                           aArgs);
  },

  callAddonListeners(...aArgs) {
    AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);
  },

  AddonAuthor,

  AddonScreenshot,

  AddonCompatibilityOverride,

  AddonType,

  recordTimestamp(name, value) {
    AddonManagerInternal.recordTimestamp(name, value);
  },

  _simpleMeasures: {},
  recordSimpleMeasure(name, value) {
    this._simpleMeasures[name] = value;
  },

  recordException(aModule, aContext, aException) {
    let report = {
      module: aModule,
      context: aContext
    };

    if (typeof aException == "number") {
      report.message = Components.Exception("", aException).name;
    } else {
      report.message = aException.toString();
      if (aException.fileName) {
        report.file = aException.fileName;
        report.line = aException.lineNumber;
      }
    }

    this._simpleMeasures.exception = report;
  },

  getSimpleMeasures() {
    return this._simpleMeasures;
  },

  getTelemetryDetails() {
    return AddonManagerInternal.telemetryDetails;
  },

  setTelemetryDetails(aProvider, aDetails) {
    AddonManagerInternal.telemetryDetails[aProvider] = aDetails;
  },

  // Start a timer, record a simple measure of the time interval when
  // timer.done() is called
  simpleTimer(aName) {
    let startTime = Cu.now();
    return {
      done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime))
    };
  },

  /**
   * Helper to call update listeners when no update is available.
   *
   * This can be used as an implementation for Addon.findUpdates() when
   * no update mechanism is available.
   */
  callNoUpdateListeners(addon, listener, reason, appVersion, platformVersion) {
    if ("onNoCompatibilityUpdateAvailable" in listener) {
      safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon);
    }
    if ("onNoUpdateAvailable" in listener) {
      safeCall(listener.onNoUpdateAvailable.bind(listener), addon);
    }
    if ("onUpdateFinished" in listener) {
      safeCall(listener.onUpdateFinished.bind(listener), addon);
    }
  },

  get webExtensionsMinPlatformVersion() {
    return gWebExtensionsMinPlatformVersion;
  },

  hasUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.has(aId);
  },

  getUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.get(aId);
  },

  /**
   * Predicate that returns true if we think the given extension ID
   * might have been generated by XPIProvider.
   */
  isTemporaryInstallID(extensionId) {
     if (!gStarted)
       throw Components.Exception("AddonManager is not initialized",
                                  Cr.NS_ERROR_NOT_INITIALIZED);

     if (!extensionId || typeof extensionId != "string")
       throw Components.Exception("extensionId must be a string",
                                  Cr.NS_ERROR_INVALID_ARG);

    return AddonManagerInternal._getProviderByName("XPIProvider")
                               .isTemporaryInstallID(extensionId);
  },

  set nonMpcDisabled(val) {
    gNonMpcDisabled = val;
  },

  isDBLoaded() {
    let provider = AddonManagerInternal._getProviderByName("XPIProvider");
    return provider ? provider.isDBLoaded : false;
  },
};

/**
 * This is the public API that UI and developers should be calling. All methods
 * just forward to AddonManagerInternal.
 */
this.AddonManager = {
  // Constants for the AddonInstall.state property
  // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE)
  _states: new Map([
    // The install is available for download.
    ["STATE_AVAILABLE",  0],
    // The install is being downloaded.
    ["STATE_DOWNLOADING",  1],
    // The install is checking for compatibility information.
    ["STATE_CHECKING", 2],
    // The install is downloaded and ready to install.
    ["STATE_DOWNLOADED", 3],
    // The download failed.
    ["STATE_DOWNLOAD_FAILED", 4],
    // The install may not proceed until the user accepts a prompt
    ["STATE_AWAITING_PROMPT", 5],
    // Any prompts are done
    ["STATE_PROMPTS_DONE", 6],
    // The install has been postponed.
    ["STATE_POSTPONED", 7],
    // The install is ready to be applied.
    ["STATE_READY", 8],
    // The add-on is being installed.
    ["STATE_INSTALLING", 9],
    // The add-on has been installed.
    ["STATE_INSTALLED", 10],
    // The install failed.
    ["STATE_INSTALL_FAILED", 11],
    // The install has been cancelled.
    ["STATE_CANCELLED", 12],
  ]),

  // Constants representing different types of errors while downloading an
  // add-on.
  // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE)
  _errors: new Map([
    // The download failed due to network problems.
    ["ERROR_NETWORK_FAILURE", -1],
    // The downloaded file did not match the provided hash.
    ["ERROR_INCORRECT_HASH", -2],
    // The downloaded file seems to be corrupted in some way.
    ["ERROR_CORRUPT_FILE", -3],
    // An error occured trying to write to the filesystem.
    ["ERROR_FILE_ACCESS", -4],
    // The add-on must be signed and isn't.
    ["ERROR_SIGNEDSTATE_REQUIRED", -5],
    // The downloaded add-on had a different type than expected.
    ["ERROR_UNEXPECTED_ADDON_TYPE", -6],
    // The addon did not have the expected ID
    ["ERROR_INCORRECT_ID", -7],
  ]),

  // These must be kept in sync with AddonUpdateChecker.
  // No error was encountered.
  UPDATE_STATUS_NO_ERROR: 0,
  // The update check timed out
  UPDATE_STATUS_TIMEOUT: -1,
  // There was an error while downloading the update information.
  UPDATE_STATUS_DOWNLOAD_ERROR: -2,
  // The update information was malformed in some way.
  UPDATE_STATUS_PARSE_ERROR: -3,
  // The update information was not in any known format.
  UPDATE_STATUS_UNKNOWN_FORMAT: -4,
  // The update information was not correctly signed or there was an SSL error.
  UPDATE_STATUS_SECURITY_ERROR: -5,
  // The update was cancelled.
  UPDATE_STATUS_CANCELLED: -6,

  // Constants to indicate why an update check is being performed
  // Update check has been requested by the user.
  UPDATE_WHEN_USER_REQUESTED: 1,
  // Update check is necessary to see if the Addon is compatibile with a new
  // version of the application.
  UPDATE_WHEN_NEW_APP_DETECTED: 2,
  // Update check is necessary because a new application has been installed.
  UPDATE_WHEN_NEW_APP_INSTALLED: 3,
  // Update check is a regular background update check.
  UPDATE_WHEN_PERIODIC_UPDATE: 16,
  // Update check is needed to check an Addon that is being installed.
  UPDATE_WHEN_ADDON_INSTALLED: 17,

  // Constants for operations in Addon.pendingOperations
  // Indicates that the Addon has no pending operations.
  PENDING_NONE: 0,
  // Indicates that the Addon will be enabled after the application restarts.
  PENDING_ENABLE: 1,
  // Indicates that the Addon will be disabled after the application restarts.
  PENDING_DISABLE: 2,
  // Indicates that the Addon will be uninstalled after the application restarts.
  PENDING_UNINSTALL: 4,
  // Indicates that the Addon will be installed after the application restarts.
  PENDING_INSTALL: 8,
  PENDING_UPGRADE: 16,

  // Constants for operations in Addon.operationsRequiringRestart
  // Indicates that restart isn't required for any operation.
  OP_NEEDS_RESTART_NONE: 0,
  // Indicates that restart is required for enabling the addon.
  OP_NEEDS_RESTART_ENABLE: 1,
  // Indicates that restart is required for disabling the addon.
  OP_NEEDS_RESTART_DISABLE: 2,
  // Indicates that restart is required for uninstalling the addon.
  OP_NEEDS_RESTART_UNINSTALL: 4,
  // Indicates that restart is required for installing the addon.
  OP_NEEDS_RESTART_INSTALL: 8,

  // Constants for permissions in Addon.permissions.
  // Indicates that the Addon can be uninstalled.
  PERM_CAN_UNINSTALL: 1,
  // Indicates that the Addon can be enabled by the user.
  PERM_CAN_ENABLE: 2,
  // Indicates that the Addon can be disabled by the user.
  PERM_CAN_DISABLE: 4,
  // Indicates that the Addon can be upgraded.
  PERM_CAN_UPGRADE: 8,
  // Indicates that the Addon can be set to be optionally enabled
  // on a case-by-case basis.
  PERM_CAN_ASK_TO_ACTIVATE: 16,

  // General descriptions of where items are installed.
  // Installed in this profile.
  SCOPE_PROFILE: 1,
  // Installed for all of this user's profiles.
  SCOPE_USER: 2,
  // Installed and owned by the application.
  SCOPE_APPLICATION: 4,
  // Installed for all users of the computer.
  SCOPE_SYSTEM: 8,
  // Installed temporarily
  SCOPE_TEMPORARY: 16,
  // The combination of all scopes.
  SCOPE_ALL: 31,

  // Add-on type is expected to be displayed in the UI in a list.
  VIEW_TYPE_LIST: "list",

  // Constants describing how add-on types behave.

  // If no add-ons of a type are installed, then the category for that add-on
  // type should be hidden in the UI.
  TYPE_UI_HIDE_EMPTY: 16,
  // Indicates that this add-on type supports the ask-to-activate state.
  // That is, add-ons of this type can be set to be optionally enabled
  // on a case-by-case basis.
  TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32,
  // The add-on type natively supports undo for restartless uninstalls.
  // If this flag is not specified, the UI is expected to handle this via
  // disabling the add-on, and performing the actual uninstall at a later time.
  TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64,

  // Constants for Addon.applyBackgroundUpdates.
  // Indicates that the Addon should not update automatically.
  AUTOUPDATE_DISABLE: 0,
  // Indicates that the Addon should update automatically only if
  // that's the global default.
  AUTOUPDATE_DEFAULT: 1,
  // Indicates that the Addon should update automatically.
  AUTOUPDATE_ENABLE: 2,

  // Constants for how Addon options should be shown.
  // Options will be opened in a new window
  OPTIONS_TYPE_DIALOG: 1,
  // Options will be displayed within the AM detail view
  OPTIONS_TYPE_INLINE: 2,
  // Options will be displayed in a new tab, if possible
  OPTIONS_TYPE_TAB: 3,
  // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown.
  // Used to indicate that only non-interactive information will be shown.
  OPTIONS_TYPE_INLINE_INFO: 4,
  // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline
  // options from a specially-formatted XUL file, the contents of the
  // file are simply displayed in an inline <browser> element.
  OPTIONS_TYPE_INLINE_BROWSER: 5,

  // Constants for displayed or hidden options notifications
  // Options notification will be displayed
  OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed",
  // Options notification will be hidden
  OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden",

  // Constants for getStartupChanges, addStartupChange and removeStartupChange
  // Add-ons that were detected as installed during startup. Doesn't include
  // add-ons that were pending installation the last time the application ran.
  STARTUP_CHANGE_INSTALLED: "installed",
  // Add-ons that were detected as changed during startup. This includes an
  // add-on moving to a different location, changing version or just having
  // been detected as possibly changed.
  STARTUP_CHANGE_CHANGED: "changed",
  // Add-ons that were detected as uninstalled during startup. Doesn't include
  // add-ons that were pending uninstallation the last time the application ran.
  STARTUP_CHANGE_UNINSTALLED: "uninstalled",
  // Add-ons that were detected as disabled during startup, normally because of
  // an application change making an add-on incompatible. Doesn't include
  // add-ons that were pending being disabled the last time the application ran.
  STARTUP_CHANGE_DISABLED: "disabled",
  // Add-ons that were detected as enabled during startup, normally because of
  // an application change making an add-on compatible. Doesn't include
  // add-ons that were pending being enabled the last time the application ran.
  STARTUP_CHANGE_ENABLED: "enabled",

  // Constants for Addon.signedState. Any states that should cause an add-on
  // to be unusable in builds that require signing should have negative values.
  // Add-on signing is not required, e.g. because the pref is disabled.
  SIGNEDSTATE_NOT_REQUIRED: undefined,
  // Add-on is signed but signature verification has failed.
  SIGNEDSTATE_BROKEN: -2,
  // Add-on may be signed but by an certificate that doesn't chain to our
  // our trusted certificate.
  SIGNEDSTATE_UNKNOWN: -1,
  // Add-on is unsigned.
  SIGNEDSTATE_MISSING: 0,
  // Add-on is preliminarily reviewed.
  SIGNEDSTATE_PRELIMINARY: 1,
  // Add-on is fully reviewed.
  SIGNEDSTATE_SIGNED: 2,
  // Add-on is system add-on.
  SIGNEDSTATE_SYSTEM: 3,
  // Add-on is signed with a "Mozilla Extensions" certificate
  SIGNEDSTATE_PRIVILEGED: 4,

  // Constants for the Addon.userDisabled property
  // Indicates that the userDisabled state of this add-on is currently
  // ask-to-activate. That is, it can be conditionally enabled on a
  // case-by-case basis.
  STATE_ASK_TO_ACTIVATE: "askToActivate",

  get __AddonManagerInternal__() {
    return AppConstants.DEBUG ? AddonManagerInternal : undefined;
  },

  get isReady() {
    return gStartupComplete && !gShutdownInProgress;
  },

  init() {
    this._stateToString = new Map();
    for (let [name, value] of this._states) {
      this[name] = value;
      this._stateToString.set(value, name);
    }
    this._errorToString = new Map();
    for (let [name, value] of this._errors) {
      this[name] = value;
      this._errorToString.set(value, name);
    }
  },

  stateToString(state) {
    return this._stateToString.get(state);
  },

  errorToString(err) {
    return err ? this._errorToString.get(err) : null;
  },

  getInstallForURL(aUrl, aCallback, aMimetype,
                                                 aHash, aName, aIcons,
                                                 aVersion, aBrowser) {
    return promiseOrCallback(
      AddonManagerInternal.getInstallForURL(aUrl, aMimetype, aHash,
                                            aName, aIcons, aVersion, aBrowser),
      aCallback);
  },

  getInstallForFile(aFile, aCallback, aMimetype) {
    return promiseOrCallback(
      AddonManagerInternal.getInstallForFile(aFile, aMimetype),
      aCallback);
  },

  /**
   * Gets an array of add-on IDs that changed during the most recent startup.
   *
   * @param  aType
   *         The type of startup change to get
   * @return An array of add-on IDs
   */
  getStartupChanges(aType) {
    if (!(aType in AddonManagerInternal.startupChanges))
      return [];
    return AddonManagerInternal.startupChanges[aType].slice(0);
  },

  getAddonByID(aID, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAddonByID(aID),
      aCallback);
  },

  getAddonBySyncGUID(aGUID, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAddonBySyncGUID(aGUID),
      aCallback);
  },

  getAddonsByIDs(aIDs, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAddonsByIDs(aIDs),
      aCallback);
  },

  getAddonsWithOperationsByTypes(aTypes, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes),
      aCallback);
  },

  getAddonsByTypes(aTypes, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAddonsByTypes(aTypes),
      aCallback);
  },

  getActiveAddons(aTypes, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getActiveAddons(aTypes),
      aCallback);
  },

  getAllAddons(aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAllAddons(),
      aCallback);
  },

  getInstallsByTypes(aTypes, aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getInstallsByTypes(aTypes),
      aCallback);
  },

  getAllInstalls(aCallback) {
    return promiseOrCallback(
      AddonManagerInternal.getAllInstalls(),
      aCallback);
  },

  mapURIToAddonID(aURI) {
    return AddonManagerInternal.mapURIToAddonID(aURI);
  },

  isInstallEnabled(aType) {
    return AddonManagerInternal.isInstallEnabled(aType);
  },

  isInstallAllowed(aType, aInstallingPrincipal) {
    return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal);
  },

  installAddonFromWebpage(aType, aBrowser, aInstallingPrincipal, aInstall) {
    AddonManagerInternal.installAddonFromWebpage(aType, aBrowser,
                                                 aInstallingPrincipal,
                                                 aInstall);
  },

  installAddonFromAOM(aBrowser, aUri, aInstall) {
    AddonManagerInternal.installAddonFromAOM(aBrowser, aUri, aInstall);
  },

  installTemporaryAddon(aDirectory) {
    return AddonManagerInternal.installTemporaryAddon(aDirectory);
  },

  installAddonFromSources(aDirectory) {
    return AddonManagerInternal.installAddonFromSources(aDirectory);
  },

  getAddonByInstanceID(aInstanceID) {
    return AddonManagerInternal.getAddonByInstanceID(aInstanceID);
  },

  addManagerListener(aListener) {
    AddonManagerInternal.addManagerListener(aListener);
  },

  removeManagerListener(aListener) {
    AddonManagerInternal.removeManagerListener(aListener);
  },

  addInstallListener(aListener) {
    AddonManagerInternal.addInstallListener(aListener);
  },

  removeInstallListener(aListener) {
    AddonManagerInternal.removeInstallListener(aListener);
  },

  getUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.get(aId);
  },

  addUpgradeListener(aInstanceID, aCallback) {
    AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback);
  },

  removeUpgradeListener(aInstanceID) {
    return AddonManagerInternal.removeUpgradeListener(aInstanceID);
  },

  addAddonListener(aListener) {
    AddonManagerInternal.addAddonListener(aListener);
  },

  removeAddonListener(aListener) {
    AddonManagerInternal.removeAddonListener(aListener);
  },

  addTypeListener(aListener) {
    AddonManagerInternal.addTypeListener(aListener);
  },

  removeTypeListener(aListener) {
    AddonManagerInternal.removeTypeListener(aListener);
  },

  get addonTypes() {
    return AddonManagerInternal.addonTypes;
  },

  /**
   * Determines whether an Addon should auto-update or not.
   *
   * @param  aAddon
   *         The Addon representing the add-on
   * @return true if the addon should auto-update, false otherwise.
   */
  shouldAutoUpdate(aAddon) {
    if (!aAddon || typeof aAddon != "object")
      throw Components.Exception("aAddon must be specified",
                                 Cr.NS_ERROR_INVALID_ARG);

    if (!("applyBackgroundUpdates" in aAddon))
      return false;
    if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
      return true;
    if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
      return false;
    return this.autoUpdateDefault;
  },

  get checkCompatibility() {
    return AddonManagerInternal.checkCompatibility;
  },

  set checkCompatibility(aValue) {
    AddonManagerInternal.checkCompatibility = aValue;
  },

  get strictCompatibility() {
    return AddonManagerInternal.strictCompatibility;
  },

  set strictCompatibility(aValue) {
    AddonManagerInternal.strictCompatibility = aValue;
  },

  get checkUpdateSecurityDefault() {
    return AddonManagerInternal.checkUpdateSecurityDefault;
  },

  get checkUpdateSecurity() {
    return AddonManagerInternal.checkUpdateSecurity;
  },

  set checkUpdateSecurity(aValue) {
    AddonManagerInternal.checkUpdateSecurity = aValue;
  },

  get updateEnabled() {
    return AddonManagerInternal.updateEnabled;
  },

  set updateEnabled(aValue) {
    AddonManagerInternal.updateEnabled = aValue;
  },

  get autoUpdateDefault() {
    return AddonManagerInternal.autoUpdateDefault;
  },

  set autoUpdateDefault(aValue) {
    AddonManagerInternal.autoUpdateDefault = aValue;
  },

  get hotfixID() {
    return AddonManagerInternal.hotfixID;
  },

  escapeAddonURI(aAddon, aUri, aAppVersion) {
    return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion);
  },

  getPreferredIconURL(aAddon, aSize, aWindow = undefined) {
    return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow);
  },

  get webAPI() {
    return AddonManagerInternal.webAPI;
  },

  get nonMpcDisabled() {
    return gNonMpcDisabled;
  },

  get shutdown() {
    return gShutdownBarrier.client;
  },
};

this.AddonManager.init();

// load the timestamps module into AddonManagerInternal
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal);
Object.freeze(AddonManagerInternal);
Object.freeze(AddonManagerPrivate);
Object.freeze(AddonManager);
PK
!<%%modules/AppConstants.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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");
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");

this.EXPORTED_SYMBOLS = ["AppConstants"];

// Immutable for export.
this.AppConstants = Object.freeze({
  // See this wiki page for more details about channel specific build
  // defines: https://wiki.mozilla.org/Platform/Channel-specific_build_defines
  NIGHTLY_BUILD:
//@line 22 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 24 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  RELEASE_OR_BETA:
//@line 27 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 31 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  ACCESSIBILITY:
//@line 34 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 38 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  // Official corresponds, roughly, to whether this build is performed
  // on Mozilla's continuous integration infrastructure. You should
  // disable developer-only functionality when this flag is set.
  MOZILLA_OFFICIAL:
//@line 44 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 48 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_OFFICIAL_BRANDING:
//@line 51 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 55 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_DEV_EDITION:
//@line 60 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 62 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_SERVICES_HEALTHREPORT:
//@line 65 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 69 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_DATA_REPORTING:
//@line 72 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 76 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_SANDBOX:
//@line 79 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 83 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_CONTENT_SANDBOX:
//@line 86 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 90 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_TELEMETRY_REPORTING:
//@line 93 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 97 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_TELEMETRY_ON_BY_DEFAULT:
//@line 102 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 104 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_UPDATER:
//@line 107 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 111 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_SWITCHBOARD:
//@line 116 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 118 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_WEBRTC:
//@line 121 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 125 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_WIDGET_GTK:
//@line 130 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 132 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

//@line 134 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  MOZ_B2G:
//@line 138 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 140 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  XP_UNIX:
//@line 145 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 147 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

//@line 150 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  platform:
//@line 154 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  "win",
//@line 164 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  isPlatformAndVersionAtLeast(platform, version) {
    let platformVersion = Services.sysinfo.getProperty("version");
    return platform == this.platform &&
           Services.vc.compare(platformVersion, version) >= 0;
  },

  isPlatformAndVersionAtMost(platform, version) {
    let platformVersion = Services.sysinfo.getProperty("version");
    return platform == this.platform &&
           Services.vc.compare(platformVersion, version) <= 0;
  },

  MOZ_CRASHREPORTER:
//@line 179 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 183 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_MAINTENANCE_SERVICE:
//@line 186 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 190 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  E10S_TESTING_ONLY:
//@line 195 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 197 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  DEBUG:
//@line 202 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 204 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  ASAN:
//@line 209 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 211 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_B2G_RIL:
//@line 216 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 218 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_GRAPHENE:
//@line 223 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 225 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_SYSTEM_NSS:
//@line 230 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 232 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_PLACES:
//@line 235 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 239 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_ADDON_SIGNING:
//@line 242 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 246 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_REQUIRE_SIGNING:
//@line 249 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 253 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_ALLOW_LEGACY_EXTENSIONS:
//@line 256 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 260 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  INSTALL_COMPACT_THEMES:
//@line 263 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 267 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MENUBAR_CAN_AUTOHIDE:
//@line 270 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 274 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  CAN_DRAW_IN_TITLEBAR:
//@line 277 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 281 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_ANDROID_HISTORY:
//@line 286 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 288 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_TOOLKIT_SEARCH:
//@line 291 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 295 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_GECKO_PROFILER:
//@line 298 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  true,
//@line 302 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_ANDROID_ACTIVITY_STREAM:
//@line 307 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 309 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_ANDROID_MOZILLA_ONLINE:
//@line 314 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  false,
//@line 316 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  DLL_PREFIX: "",
  DLL_SUFFIX: ".dll",

  MOZ_APP_NAME: "firefox",
  MOZ_APP_VERSION: "56.0",
  MOZ_APP_VERSION_DISPLAY: "56.0",
  MOZ_BUILD_APP: "browser",
  MOZ_MACBUNDLE_NAME: "Firefox.app",
  MOZ_UPDATE_CHANNEL: "release",
  INSTALL_LOCALE: "en-US",
  MOZ_WIDGET_TOOLKIT: "windows",
  ANDROID_PACKAGE_NAME: "org.mozilla.firefox",
  MOZ_B2G_VERSION: "1.0.0",
  MOZ_B2G_OS_NAME: "",

  DEBUG_JS_MODULES: "",

  MOZ_BING_API_CLIENTID: "no-bing-api-clientid",
  MOZ_BING_API_KEY: "no-bing-api-key",
  MOZ_GOOGLE_API_KEY: "AIzaSyD_Drzahe4dBzGCZ9ArvowCvrPx_yFrlCM",
  MOZ_MOZILLA_API_KEY: "7e40f68c-7938-4c5d-9f95-e61647c213eb",

  // URL to the hg revision this was built from (e.g.
  // "https://hg.mozilla.org/mozilla-central/rev/6256ec9113c1")
  // On unofficial builds, this is an empty string.
//@line 345 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
  SOURCE_REVISION_URL: "https://hg.mozilla.org/releases/mozilla-release/rev/8fbf05f4b92125e081984f5e39b559b83e5cc729",

  HAVE_USR_LIB64_DIR:
//@line 351 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    false,
//@line 353 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  HAVE_SHELL_SERVICE:
//@line 356 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    true,
//@line 360 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_PHOTON_ANIMATIONS:
//@line 365 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    false,
//@line 367 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_PHOTON_PREFERENCES:
//@line 372 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    false,
//@line 374 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_PHOTON_THEME:
//@line 379 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    false,
//@line 381 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

  MOZ_STYLO:
//@line 384 "z:\build\build\src\toolkit\modules\AppConstants.jsm"
    true,
//@line 388 "z:\build\build\src\toolkit\modules\AppConstants.jsm"

});
PK
!<N04 modules/AppMenuNotifications.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["AppMenuNotifications"];

const {utils: Cu, classes: Cc, interfaces: Ci} = Components;

Cu.import("resource://gre/modules/Services.jsm");

function AppMenuNotification(id, mainAction, secondaryAction, options = {}) {
  this.id = id;
  this.mainAction = mainAction;
  this.secondaryAction = secondaryAction;
  this.options = options;
  this.dismissed = this.options.dismissed || false;
}

var AppMenuNotifications = {
  _notifications: [],
  _hasInitialized: false,

  get notifications() {
    return Array.from(this._notifications);
  },

  _lazyInit() {
    if (!this._hasInitialized) {
      Services.obs.addObserver(this, "xpcom-shutdown");
      Services.obs.addObserver(this, "appMenu-notifications-request");
    }
  },

  uninit() {
    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.obs.removeObserver(this, "appMenu-notifications-request");
  },

  observe(subject, topic, status) {
    switch (topic) {
      case "xpcom-shutdown":
        this.uninit();
        break;
      case "appMenu-notifications-request":
        if (this._notifications.length != 0) {
          Services.obs.notifyObservers(null, "appMenu-notifications", "init");
        }
        break;
    }
  },

  get activeNotification() {
    if (this._notifications.length > 0) {
      const doorhanger =
        this._notifications.find(n => !n.dismissed && !n.options.badgeOnly);
      return doorhanger || this._notifications[0];
    }

    return null;
  },

  showNotification(id, mainAction, secondaryAction, options = {}) {
    let notification = new AppMenuNotification(id, mainAction, secondaryAction, options);
    let existingIndex = this._notifications.findIndex(n => n.id == id);
    if (existingIndex != -1) {
      this._notifications.splice(existingIndex, 1);
    }

    // We don't want to clobber doorhanger notifications just to show a badge,
    // so don't dismiss any of them and the badge will show once the doorhanger
    // gets resolved.
    if (!options.badgeOnly && !options.dismissed) {
      this._notifications.forEach(n => { n.dismissed = true; });
    }

    // Since notifications are generally somewhat pressing, the ideal case is that
    // we never have two notifications at once. However, in the event that we do,
    // it's more likely that the older notification has been sitting around for a
    // bit, and so we don't want to hide the new notification behind it. Thus,
    // we want our notifications to behave like a stack instead of a queue.
    this._notifications.unshift(notification);

    this._lazyInit();
    this._updateNotifications();
    return notification;
  },

  showBadgeOnlyNotification(id) {
    return this.showNotification(id, null, null, { badgeOnly: true });
  },

  removeNotification(id) {
    let notifications;
    if (typeof id == "string") {
      notifications = this._notifications.filter(n => n.id == id);
    } else {
      // If it's not a string, assume RegExp
      notifications = this._notifications.filter(n => id.test(n.id));
    }
    // _updateNotifications can be expensive if it forces attachment of XBL
    // bindings that haven't been used yet, so return early if we haven't found
    // any notification to remove, as callers may expect this removeNotification
    // method to be a no-op for non-existent notifications.
    if (!notifications.length) {
      return;
    }

    notifications.forEach(n => {
      this._removeNotification(n);
    });
    this._updateNotifications();
  },

  dismissNotification(id) {
    let notifications;
    if (typeof id == "string") {
      notifications = this._notifications.filter(n => n.id == id);
    } else {
      // If it's not a string, assume RegExp
      notifications = this._notifications.filter(n => id.test(n.id));
    }

    notifications.forEach(n => {
      n.dismissed = true;
    });
    this._updateNotifications();
  },

  callMainAction(win, notification, fromDoorhanger) {
    let action = notification.mainAction;
    this._callAction(win, notification, action, fromDoorhanger);
  },

  callSecondaryAction(win, notification) {
    let action = notification.secondaryAction;
    this._callAction(win, notification, action, true);
  },

  _callAction(win, notification, action, fromDoorhanger) {
    let dismiss = true;
    if (action) {
      try {
        action.callback(win, fromDoorhanger);
      } catch (error) {
        Cu.reportError(error);
      }

      dismiss = action.dismiss;
    }

    if (dismiss) {
      notification.dismissed = true;
    } else {
      this._removeNotification(notification);
    }

    this._updateNotifications();
  },

  _removeNotification(notification) {
    // This notification may already be removed, in which case let's just ignore.
    let notifications = this._notifications;
    if (!notifications)
      return;

    var index = notifications.indexOf(notification);
    if (index == -1)
      return;

    // Remove the notification
    notifications.splice(index, 1);
  },

  _updateNotifications() {
    Services.obs.notifyObservers(null, "appMenu-notifications", "update");
  },
};
PK
!<d;//modules/AsyncPrefs.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["AsyncPrefs"];

const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
Cu.import("resource://gre/modules/Services.jsm");

const kInChildProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

const kAllowedPrefs = new Set([
  // NB: please leave the testing prefs at the top, and sort the rest alphabetically if you add
  // anything.
  "testing.allowed-prefs.some-bool-pref",
  "testing.allowed-prefs.some-char-pref",
  "testing.allowed-prefs.some-int-pref",

  "narrate.rate",
  "narrate.voice",

  "reader.font_size",
  "reader.font_type",
  "reader.color_scheme",
  "reader.content_width",
  "reader.line_height",
]);

const kPrefTypeMap = new Map([
  ["boolean", Services.prefs.PREF_BOOL],
  ["number", Services.prefs.PREF_INT],
  ["string", Services.prefs.PREF_STRING],
]);

function maybeReturnErrorForReset(pref) {
  if (!kAllowedPrefs.has(pref)) {
    return `Resetting pref ${pref} from content is not allowed.`;
  }
  return false;
}

function maybeReturnErrorForSet(pref, value) {
  if (!kAllowedPrefs.has(pref)) {
    return `Setting pref ${pref} from content is not allowed.`;
  }

  let valueType = typeof value;
  if (!kPrefTypeMap.has(valueType)) {
    return `Can't set pref ${pref} to value of type ${valueType}.`;
  }
  let prefType = Services.prefs.getPrefType(pref);
  if (prefType != Services.prefs.PREF_INVALID &&
      prefType != kPrefTypeMap.get(valueType)) {
    return `Can't set pref ${pref} to a value with type ${valueType} that doesn't match the pref's type ${prefType}.`;
  }
  return false;
}

var AsyncPrefs;
if (kInChildProcess) {
  let gUniqueId = 0;
  let gMsgMap = new Map();

  AsyncPrefs = {
    set(pref, value) {
      let error = maybeReturnErrorForSet(pref, value);
      if (error) {
        return Promise.reject(error);
      }

      let msgId = ++gUniqueId;
      return new Promise((resolve, reject) => {
        gMsgMap.set(msgId, {resolve, reject});
        Services.cpmm.sendAsyncMessage("AsyncPrefs:SetPref", {pref, value, msgId});
      });
    },

    reset(pref) {
      let error = maybeReturnErrorForReset(pref);
      if (error) {
        return Promise.reject(error);
      }

      let msgId = ++gUniqueId;
      return new Promise((resolve, reject) => {
        gMsgMap.set(msgId, {resolve, reject});
        Services.cpmm.sendAsyncMessage("AsyncPrefs:ResetPref", {pref, msgId});
      });
    },

    receiveMessage(msg) {
      let promiseRef = gMsgMap.get(msg.data.msgId);
      if (promiseRef) {
        gMsgMap.delete(msg.data.msgId);
        if (msg.data.success) {
          promiseRef.resolve();
        } else {
          promiseRef.reject(msg.data.message);
        }
      }
    },
  };

  Services.cpmm.addMessageListener("AsyncPrefs:PrefSetFinished", AsyncPrefs);
  Services.cpmm.addMessageListener("AsyncPrefs:PrefResetFinished", AsyncPrefs);
} else {
  AsyncPrefs = {
    methodForType: {
      number: "setIntPref",
      boolean: "setBoolPref",
      string: "setCharPref",
    },

    set(pref, value) {
      let error = maybeReturnErrorForSet(pref, value);
      if (error) {
        return Promise.reject(error);
      }
      let methodToUse = this.methodForType[typeof value];
      try {
        Services.prefs[methodToUse](pref, value);
        return Promise.resolve(value);
      } catch (ex) {
        Cu.reportError(ex);
        return Promise.reject(ex.message);
      }
    },

    reset(pref) {
      let error = maybeReturnErrorForReset(pref);
      if (error) {
        return Promise.reject(error);
      }

      try {
        Services.prefs.clearUserPref(pref);
        return Promise.resolve();
      } catch (ex) {
        Cu.reportError(ex);
        return Promise.reject(ex.message);
      }
    },

    receiveMessage(msg) {
      if (msg.name == "AsyncPrefs:SetPref") {
        this.onPrefSet(msg);
      } else {
        this.onPrefReset(msg);
      }
    },

    onPrefReset(msg) {
      let {pref, msgId} = msg.data;
      this.reset(pref).then(function() {
        msg.target.sendAsyncMessage("AsyncPrefs:PrefResetFinished", {msgId, success: true});
      }, function(msg) {
        msg.target.sendAsyncMessage("AsyncPrefs:PrefResetFinished", {msgId, success: false, message: msg});
      });
    },

    onPrefSet(msg) {
      let {pref, value, msgId} = msg.data;
      this.set(pref, value).then(function() {
        msg.target.sendAsyncMessage("AsyncPrefs:PrefSetFinished", {msgId, success: true});
      }, function(msg) {
        msg.target.sendAsyncMessage("AsyncPrefs:PrefSetFinished", {msgId, success: false, message: msg});
      });
    },

    init() {
      // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
      Services.ppmm.addMessageListener("AsyncPrefs:SetPref", this);
      Services.ppmm.addMessageListener("AsyncPrefs:ResetPref", this);
      // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
    }
  };
}
PK
!<l^@@modules/AsyncShutdown.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Managing safe shutdown of asynchronous services.
 *
 * Firefox shutdown is composed of phases that take place
 * sequentially. Typically, each shutdown phase removes some
 * capabilities from the application. For instance, at the end of
 * phase profileBeforeChange, no service is permitted to write to the
 * profile directory (with the exception of Telemetry). Consequently,
 * if any service has requested I/O to the profile directory before or
 * during phase profileBeforeChange, the system must be informed that
 * these requests need to be completed before the end of phase
 * profileBeforeChange. Failing to inform the system of this
 * requirement can (and has been known to) cause data loss.
 *
 * Example: At some point during shutdown, the Add-On Manager needs to
 * ensure that all add-ons have safely written their data to disk,
 * before writing its own data. Since the data is saved to the
 * profile, this must be completed during phase profileBeforeChange.
 *
 * AsyncShutdown.profileBeforeChange.addBlocker(
 *   "Add-on manager: shutting down",
 *   function condition() {
 *     // Do things.
 *     // Perform I/O that must take place during phase profile-before-change
 *     return promise;
 *   }
 * });
 *
 * In this example, function |condition| will be called at some point
 * during phase profileBeforeChange and phase profileBeforeChange
 * itself is guaranteed to not terminate until |promise| is either
 * resolved or rejected.
 */

"use strict";

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
  "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gDebug",
  "@mozilla.org/xpcom/debug;1", "nsIDebug2");
Object.defineProperty(this, "gCrashReporter", {
  get() {
    delete this.gCrashReporter;
    try {
      let reporter = Cc["@mozilla.org/xre/app-info;1"].
            getService(Ci.nsICrashReporter);
      return this.gCrashReporter = reporter;
    } catch (ex) {
      return this.gCrashReporter = null;
    }
  },
  configurable: true
});

// `true` if this is a content process, `false` otherwise.
// It would be nicer to go through `Services.appInfo`, but some tests need to be
// able to replace that field with a custom implementation before it is first
// called.
const isContent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;

// Display timeout warnings after 10 seconds
const DELAY_WARNING_MS = 10 * 1000;


// Crash the process if shutdown is really too long
// (allowing for sleep).
const PREF_DELAY_CRASH_MS = "toolkit.asyncshutdown.crash_timeout";
var DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS,
                                               60 * 1000); // One minute
Services.prefs.addObserver(PREF_DELAY_CRASH_MS, function() {
  DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS);
});

/**
 * A set of Promise that supports waiting.
 *
 * Promise items may be added or removed during the wait. The wait will
 * resolve once all Promise items have been resolved or removed.
 */
function PromiseSet() {
  /**
   * key: the Promise passed pass the client of the `PromiseSet`.
   * value: an indirection on top of `key`, as an object with
   *   the following fields:
   *   - indirection: a Promise resolved if `key` is resolved or
   *     if `resolve` is called
   *   - resolve: a function used to resolve the indirection.
   */
  this._indirections = new Map();
}
PromiseSet.prototype = {
  /**
   * Wait until all Promise have been resolved or removed.
   *
   * Note that calling `wait()` causes Promise to be removed from the
   * Set once they are resolved.
   *
   * @return {Promise} Resolved once all Promise have been resolved or removed,
   * or rejected after at least one Promise has rejected.
   */
  wait() {
    // Pick an arbitrary element in the map, if any exists.
    let entry = this._indirections.entries().next();
    if (entry.done) {
      // No indirections left, we are done.
      return Promise.resolve();
    }

    let [, indirection] = entry.value;
    let promise = indirection.promise;
    promise = promise.then(() =>
      // At this stage, the entry has been cleaned up.
      this.wait()
    );
    return promise;
  },

  /**
   * Add a new Promise to the set.
   *
   * Calls to wait (including ongoing calls) will only return once
   * `key` has either resolved or been removed.
   */
  add(key) {
    this._ensurePromise(key);
    let indirection = PromiseUtils.defer();
    key.then(
      x => {
        // Clean up immediately.
        // This needs to be done before the call to `resolve`, otherwise
        // `wait()` may loop forever.
        this._indirections.delete(key);
        indirection.resolve(x);
      },
      err => {
        this._indirections.delete(key);
        indirection.reject(err);
      });
    this._indirections.set(key, indirection);
  },

  /**
   * Remove a Promise from the set.
   *
   * Calls to wait (including ongoing calls) will ignore this promise,
   * unless it is added again.
   */
  delete(key) {
    this._ensurePromise(key);
    let value = this._indirections.get(key);
    if (!value) {
      return false;
    }
    this._indirections.delete(key);
    value.resolve();
    return true;
  },

  _ensurePromise(key) {
    if (!key || typeof key != "object") {
      throw new Error("Expected an object");
    }
    if ((!("then" in key)) || typeof key.then != "function") {
      throw new Error("Expected a Promise");
    }
  },

};


/**
 * Display a warning.
 *
 * As this code is generally used during shutdown, there are chances
 * that the UX will not be available to display warnings on the
 * console. We therefore use dump() rather than Cu.reportError().
 */
function log(msg, prefix = "", error = null) {
  try {
    dump(prefix + msg + "\n");
    if (error) {
      dump(prefix + error + "\n");
      if (typeof error == "object" && "stack" in error) {
        dump(prefix + error.stack + "\n");
      }
    }
  } catch (ex) {
    dump("INTERNAL ERROR in AsyncShutdown: cannot log message.\n");
  }
}
const PREF_DEBUG_LOG = "toolkit.asyncshutdown.log";
var DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG, false);
Services.prefs.addObserver(PREF_DEBUG_LOG, function() {
  DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG);
});

function debug(msg, error = null) {
  if (DEBUG_LOG) {
    log(msg, "DEBUG: ", error);
  }
}
function warn(msg, error = null) {
  log(msg, "WARNING: ", error);
}
function fatalerr(msg, error = null) {
  log(msg, "FATAL ERROR: ", error);
}

// Utility function designed to get the current state of execution
// of a blocker.
// We are a little paranoid here to ensure that in case of evaluation
// error we do not block the AsyncShutdown.
function safeGetState(fetchState) {
  if (!fetchState) {
    return "(none)";
  }
  let data, string;
  try {
    // Evaluate fetchState(), normalize the result into something that we can
    // safely stringify or upload.
    let state = fetchState();
    if (!state) {
      return "(none)";
    }
    string = JSON.stringify(state);
    data = JSON.parse(string);
    // Simplify the rest of the code by ensuring that we can simply
    // concatenate the result to a message.
    if (data && typeof data == "object") {
      data.toString = function() {
        return string;
      };
    }
    return data;
  } catch (ex) {

    // Make sure that this causes test failures
    Promise.reject(ex);

    if (string) {
      return string;
    }
    try {
      return "Error getting state: " + ex + " at " + ex.stack;
    } catch (ex2) {
      return "Error getting state but could not display error";
    }
  }
}

/**
 * Countdown for a given duration, skipping beats if the computer is too busy,
 * sleeping or otherwise unavailable.
 *
 * @param {number} delay An approximate delay to wait in milliseconds (rounded
 * up to the closest second).
 *
 * @return Deferred
 */
function looseTimer(delay) {
  let DELAY_BEAT = 1000;
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let beats = Math.ceil(delay / DELAY_BEAT);
  let deferred = PromiseUtils.defer();
  timer.initWithCallback(function() {
    if (beats <= 0) {
      deferred.resolve();
    }
    --beats;
  }, DELAY_BEAT, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
  // Ensure that the timer is both canceled once we are done with it
  // and not garbage-collected until then.
  deferred.promise.then(() => timer.cancel(), () => timer.cancel());
  return deferred;
}

/**
 * Given an nsIStackFrame object, find the caller filename, line number,
 * and stack if necessary, and return them as an object.
 *
 * @param {nsIStackFrame} topFrame Top frame of the call stack.
 * @param {string} filename Pre-supplied filename or null if unknown.
 * @param {number} lineNumber Pre-supplied line number or null if unknown.
 * @param {string} stack Pre-supplied stack or null if unknown.
 *
 * @return object
 */
function getOrigin(topFrame, filename = null, lineNumber = null, stack = null) {
  try {
    // Determine the filename and line number of the caller.
    let frame = topFrame;

    for (; frame && frame.filename == topFrame.filename; frame = frame.caller) {
      // Climb up the stack
    }

    if (filename == null) {
      filename = frame ? frame.filename : "?";
    }
    if (lineNumber == null) {
      lineNumber = frame ? frame.lineNumber : 0;
    }
    if (stack == null) {
      // Now build the rest of the stack as a string, using Task.jsm's rewriting
      // to ensure that we do not lose information at each call to `Task.spawn`.
      let frames = [];
      while (frame != null) {
        frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
        frame = frame.caller;
      }
      stack = frames.join("\n");
      // Avoid loading Task.jsm if there's no task on the stack.
      if (stack.includes("/Task.jsm:")) {
        stack = Task.Debugging.generateReadableStack(stack);
      }
      stack = stack.split("\n");
    }

    return {
      filename,
      lineNumber,
      stack,
    };
  } catch (ex) {
    return {
      filename: "<internal error: could not get origin>",
      lineNumber: -1,
      stack: "<internal error: could not get origin>",
    }
  }
}

this.EXPORTED_SYMBOLS = ["AsyncShutdown"];

/**
 * {string} topic -> phase
 */
var gPhases = new Map();

this.AsyncShutdown = {
  /**
   * Access function getPhase. For testing purposes only.
   */
  get _getPhase() {
    let accepted = Services.prefs.getBoolPref("toolkit.asyncshutdown.testing", false);
    if (accepted) {
      return getPhase;
    }
    return undefined;
  }
};

/**
 * Register a new phase.
 *
 * @param {string} topic The notification topic for this Phase.
 * @see {https://developer.mozilla.org/en-US/docs/Observer_Notifications}
 */
function getPhase(topic) {
  let phase = gPhases.get(topic);
  if (phase) {
    return phase;
  }
  let spinner = new Spinner(topic);
  phase = Object.freeze({
    /**
     * Register a blocker for the completion of a phase.
     *
     * @param {string} name The human-readable name of the blocker. Used
     * for debugging/error reporting. Please make sure that the name
     * respects the following model: "Some Service: some action in progress" -
     * for instance "OS.File: flushing all pending I/O";
     * @param {function|promise|*} condition A condition blocking the
     * completion of the phase. Generally, this is a function
     * returning a promise. This function is evaluated during the
     * phase and the phase is guaranteed to not terminate until the
     * resulting promise is either resolved or rejected. If
     * |condition| is not a function but another value |v|, it behaves
     * as if it were a function returning |v|.
     * @param {object*} details Optionally, an object with details
     * that may be useful for error reporting, as a subset of of the following
     * fields:
     * - fetchState (strongly recommended) A function returning
     *    information about the current state of the blocker as an
     *    object. Used for providing more details when logging errors or
     *    crashing.
     * - stack. A string containing stack information. This module can
     *    generally infer stack information if it is not provided.
     * - lineNumber A number containing the line number for the caller.
     *    This module can generally infer this information if it is not
     *    provided.
     * - filename A string containing the filename for the caller. This
     *    module can generally infer  the information if it is not provided.
     *
     * Examples:
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: just a promise",
     *      promise); // profileBeforeChange will not complete until
     *                // promise is resolved or rejected
     *
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: a callback",
     *     function callback() {
     *       // ...
     *       // Execute this code during profileBeforeChange
     *       return promise;
     *       // profileBeforeChange will not complete until promise
     *       // is resolved or rejected
     * });
     *
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: trivial callback",
     *     function callback() {
     *       // ...
     *       // Execute this code during profileBeforeChange
     *       // No specific guarantee about completion of profileBeforeChange
     * });
     */
    addBlocker(name, condition, details = null) {
      spinner.addBlocker(name, condition, details);
    },
    /**
     * Remove the blocker for a condition.
     *
     * If several blockers have been registered for the same
     * condition, remove all these blockers. If no blocker has been
     * registered for this condition, this is a noop.
     *
     * @return {boolean} true if a blocker has been removed, false
     * otherwise. Note that a result of false may mean either that
     * the blocker has never been installed or that the phase has
     * completed and the blocker has already been resolved.
     */
    removeBlocker(condition) {
      return spinner.removeBlocker(condition);
    },

    get name() {
      return spinner.name;
    },

    /**
     * Trigger the phase without having to broadcast a
     * notification. For testing purposes only.
     */
    get _trigger() {
      let accepted = Services.prefs.getBoolPref("toolkit.asyncshutdown.testing", false);
      if (accepted) {
        return () => spinner.observe();
      }
      return undefined;
    }
  });
  gPhases.set(topic, phase);
  return phase;
}

/**
 * Utility class used to spin the event loop until all blockers for a
 * Phase are satisfied.
 *
 * @param {string} topic The xpcom notification for that phase.
 */
function Spinner(topic) {
  this._barrier = new Barrier(topic);
  this._topic = topic;
  Services.obs.addObserver(this, topic);
}

Spinner.prototype = {
  /**
   * Register a new condition for this phase.
   *
   * See the documentation of `addBlocker` in property `client`
   * of instances of `Barrier`.
   */
  addBlocker(name, condition, details) {
    this._barrier.client.addBlocker(name, condition, details);
  },
  /**
   * Remove the blocker for a condition.
   *
   * See the documentation of `removeBlocker` in rpoperty `client`
   * of instances of `Barrier`
   *
   * @return {boolean} true if a blocker has been removed, false
   * otherwise. Note that a result of false may mean either that
   * the blocker has never been installed or that the phase has
   * completed and the blocker has already been resolved.
   */
  removeBlocker(condition) {
    return this._barrier.client.removeBlocker(condition);
  },

  get name() {
    return this._barrier.client.name;
  },

  // nsIObserver.observe
  observe() {
    let topic = this._topic;
    debug(`Starting phase ${ topic }`);
    Services.obs.removeObserver(this, topic);

    let satisfied = false; // |true| once we have satisfied all conditions
    let promise;
    try {
      promise = this._barrier.wait({
        warnAfterMS: DELAY_WARNING_MS,
        crashAfterMS: DELAY_CRASH_MS
      }).catch(
        // Additional precaution to be entirely sure that we cannot reject.
      );
    } catch (ex) {
      debug("Error waiting for notification");
      throw ex;
    }

    // Now, spin the event loop
    debug("Spinning the event loop");
    promise.then(() => satisfied = true); // This promise cannot reject
    let thread = Services.tm.mainThread;
    while (!satisfied) {
      try {
        thread.processNextEvent(true);
      } catch (ex) {
        // An uncaught error should not stop us, but it should still
        // be reported and cause tests to fail.
        Promise.reject(ex);
      }
    }
    debug(`Finished phase ${ topic }`);
  }
};

/**
 * A mechanism used to register blockers that prevent some action from
 * happening.
 *
 * An instance of |Barrier| provides a capability |client| that
 * clients can use to register blockers. The barrier is resolved once
 * all registered blockers have been resolved. The owner of the
 * |Barrier| may wait for the resolution of the barrier and obtain
 * information on which blockers have not been resolved yet.
 *
 * @param {string} name The name of the blocker. Used mainly for error-
 *     reporting.
 */
function Barrier(name) {
  if (!name) {
    throw new TypeError("Instances of Barrier need a (non-empty) name");
  }


  /**
   * The set of all Promise for which we need to wait before the barrier
   * is lifted. Note that this set may be changed while we are waiting.
   *
   * Set to `null` once the wait is complete.
   */
  this._waitForMe = new PromiseSet();

  /**
   * A map from conditions, as passed by users during the call to `addBlocker`,
   * to `promise`, as present in `this._waitForMe`.
   *
   * Used to let users perform cleanup through `removeBlocker`.
   * Set to `null` once the wait is complete.
   *
   * Key: condition (any, as passed by user)
   * Value: promise used as a key in `this._waitForMe`. Note that there is
   * no guarantee that the key is still present in `this._waitForMe`.
   */
  this._conditionToPromise = new Map();

  /**
   * A map from Promise, as present in `this._waitForMe` or
   * `this._conditionToPromise`, to information on blockers.
   *
   * Key: Promise (as present in this._waitForMe or this._conditionToPromise).
   * Value:  {
   *  trigger: function,
   *  promise,
   *  name,
   *  fetchState: function,
   *  stack,
   *  filename,
   *  lineNumber
   * };
   */
  this._promiseToBlocker = new Map();

  /**
   * The name of the barrier.
   */
  if (typeof name != "string") {
    throw new TypeError("The name of the barrier must be a string");
  }
  this._name = name;

  /**
   * A cache for the promise returned by wait().
   */
  this._promise = null;

  /**
   * `true` once we have started waiting.
   */
  this._isStarted = false;

  /**
   * The capability of adding blockers. This object may safely be returned
   * or passed to clients.
   */
  this.client = {
    /**
     * The name of the barrier owning this client.
     */
    get name() {
      return name;
    },

    /**
     * Register a blocker for the completion of this barrier.
     *
     * @param {string} name The human-readable name of the blocker. Used
     * for debugging/error reporting. Please make sure that the name
     * respects the following model: "Some Service: some action in progress" -
     * for instance "OS.File: flushing all pending I/O";
     * @param {function|promise|*} condition A condition blocking the
     * completion of the phase. Generally, this is a function
     * returning a promise. This function is evaluated during the
     * phase and the phase is guaranteed to not terminate until the
     * resulting promise is either resolved or rejected. If
     * |condition| is not a function but another value |v|, it behaves
     * as if it were a function returning |v|.
     * @param {object*} details Optionally, an object with details
     * that may be useful for error reporting, as a subset of of the following
     * fields:
     * - fetchState (strongly recommended) A function returning
     *    information about the current state of the blocker as an
     *    object. Used for providing more details when logging errors or
     *    crashing.
     * - stack. A string containing stack information. This module can
     *    generally infer stack information if it is not provided.
     * - lineNumber A number containing the line number for the caller.
     *    This module can generally infer this information if it is not
     *    provided.
     * - filename A string containing the filename for the caller. This
     *    module can generally infer  the information if it is not provided.
     */
    addBlocker: (name, condition, details) => {
      if (typeof name != "string") {
        throw new TypeError("Expected a human-readable name as first argument");
      }
      if (details && typeof details == "function") {
        details = {
          fetchState: details
        };
      } else if (!details) {
        details = {};
      }
      if (typeof details != "object") {
        throw new TypeError("Expected an object as third argument to `addBlocker`, got " + details);
      }
      if (!this._waitForMe) {
        throw new Error(`Phase "${ this._name }" is finished, it is too late to register completion condition "${ name }"`);
      }
      debug(`Adding blocker ${ name } for phase ${ this._name }`);

      // Normalize the details

      let fetchState = details.fetchState || null;
      if (fetchState != null && typeof fetchState != "function") {
        throw new TypeError("Expected a function for option `fetchState`");
      }
      let filename = details.filename || null;
      let lineNumber = details.lineNumber || null;
      let stack = details.stack || null;

      // Split the condition between a trigger function and a promise.

      // The function to call to notify the blocker that we have started waiting.
      // This function returns a promise resolved/rejected once the
      // condition is complete, and never throws.
      let trigger;

      // A promise resolved once the condition is complete.
      let promise;
      if (typeof condition == "function") {
        promise = new Promise((resolve, reject) => {
          trigger = () => {
            try {
              resolve(condition());
            } catch (ex) {
              reject(ex);
            }
          }
        });
      } else {
        // If `condition` is not a function, `trigger` is not particularly
        // interesting, and `condition` needs to be normalized to a promise.
        trigger = () => {};
        promise = Promise.resolve(condition);
      }

      // Make sure that `promise` never rejects.
      promise = promise.catch(error => {
        let msg = `A blocker encountered an error while we were waiting.
          Blocker:  ${ name }
          Phase: ${ this._name }
          State: ${ safeGetState(fetchState) }`;
        warn(msg, error);

        // The error should remain uncaught, to ensure that it
        // still causes tests to fail.
        Promise.reject(error);
      }).catch(
        // Added as a last line of defense, in case `warn`, `this._name` or
        // `safeGetState` somehow throws an error.
      );

      let topFrame = null;
      if (filename == null || lineNumber == null || stack == null) {
        topFrame = Components.stack;
      }

      let blocker = {
        trigger,
        promise,
        name,
        fetchState,
        getOrigin: () => getOrigin(topFrame, filename, lineNumber, stack),
      };

      this._waitForMe.add(promise);
      this._promiseToBlocker.set(promise, blocker);
      this._conditionToPromise.set(condition, promise);

      // As conditions may hold lots of memory, we attempt to cleanup
      // as soon as we are done (which might be in the next tick, if
      // we have been passed a resolved promise).
      promise = promise.then(() => {
        debug(`Completed blocker ${ name } for phase ${ this._name }`);
        this._removeBlocker(condition);
      });

      if (this._isStarted) {
        // The wait has already started. The blocker should be
        // notified asap. We do it out of band as clients probably
        // expect `addBlocker` to return immediately.
        Promise.resolve().then(trigger);
      }
    },

    /**
     * Remove the blocker for a condition.
     *
     * If several blockers have been registered for the same
     * condition, remove all these blockers. If no blocker has been
     * registered for this condition, this is a noop.
     *
     * @return {boolean} true if at least one blocker has been
     * removed, false otherwise.
     */
    removeBlocker: (condition) => {
      return this._removeBlocker(condition);
    }
  };
}
Barrier.prototype = Object.freeze({
  /**
   * The current state of the barrier, as a JSON-serializable object
   * designed for error-reporting.
   */
  get state() {
    if (!this._isStarted) {
      return "Not started";
    }
    if (!this._waitForMe) {
      return "Complete";
    }
    let frozen = [];
    for (let blocker of this._promiseToBlocker.values()) {
      let {name, fetchState} = blocker;
      let {stack, filename, lineNumber} = blocker.getOrigin();
      frozen.push({
        name,
        state: safeGetState(fetchState),
        filename,
        lineNumber,
        stack
      });
    }
    return frozen;
  },

  /**
   * Wait until all currently registered blockers are complete.
   *
   * Once this method has been called, any attempt to register a new blocker
   * for this barrier will cause an error.
   *
   * Successive calls to this method always return the same value.
   *
   * @param {object=}  options Optionally, an object  that may contain
   * the following fields:
   *  {number} warnAfterMS If provided and > 0, print a warning if the barrier
   *   has not been resolved after the given number of milliseconds.
   *  {number} crashAfterMS If provided and > 0, crash the process if the barrier
   *   has not been resolved after the give number of milliseconds (rounded up
   *   to the next second). To avoid crashing simply because the computer is busy
   *   or going to sleep, we actually wait for ceil(crashAfterMS/1000) successive
   *   periods of at least one second. Upon crashing, if a crash reporter is present,
   *   prepare a crash report with the state of this barrier.
   *
   *
   * @return {Promise} A promise satisfied once all blockers are complete.
   */
  wait(options = {}) {
    // This method only implements caching on top of _wait()
    if (this._promise) {
      return this._promise;
    }
    return this._promise = this._wait(options);
  },
  _wait(options) {

    // Sanity checks
    if (this._isStarted) {
      throw new TypeError("Internal error: already started " + this._name);
    }
    if (!this._waitForMe || !this._conditionToPromise || !this._promiseToBlocker) {
      throw new TypeError("Internal error: already finished " + this._name);
    }

    let topic = this._name;

    // Notify blockers
    for (let blocker of this._promiseToBlocker.values()) {
      blocker.trigger(); // We have guarantees that this method will never throw
    }

    this._isStarted = true;

    // Now, wait
    let promise = this._waitForMe.wait();

    promise = promise.catch(function onError(error) {
      // I don't think that this can happen.
      // However, let's be overcautious with async/shutdown error reporting.
      let msg = "An uncaught error appeared while completing the phase." +
        " Phase: " + topic;
      warn(msg, error);
    });

    promise = promise.then(() => {
      // Cleanup memory
      this._waitForMe = null;
      this._promiseToBlocker = null;
      this._conditionToPromise = null;
    });

    // Now handle warnings and crashes
    let warnAfterMS = DELAY_WARNING_MS;
    if (options && "warnAfterMS" in options) {
      if (typeof options.warnAfterMS == "number"
         || options.warnAfterMS == null) {
        // Change the delay or deactivate warnAfterMS
        warnAfterMS = options.warnAfterMS;
      } else {
        throw new TypeError("Wrong option value for warnAfterMS");
      }
    }

    if (warnAfterMS && warnAfterMS > 0) {
      // If the promise takes too long to be resolved/rejected,
      // we need to notify the user.
      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      timer.initWithCallback(() => {
        let msg = "At least one completion condition is taking too long to complete." +
        " Conditions: " + JSON.stringify(this.state) +
        " Barrier: " + topic;
        warn(msg);
      }, warnAfterMS, Ci.nsITimer.TYPE_ONE_SHOT);

      promise = promise.then(function onSuccess() {
        timer.cancel();
        // As a side-effect, this prevents |timer| from
        // being garbage-collected too early.
      });
    }

    let crashAfterMS = DELAY_CRASH_MS;
    if (options && "crashAfterMS" in options) {
      if (typeof options.crashAfterMS == "number"
         || options.crashAfterMS == null) {
        // Change the delay or deactivate crashAfterMS
        crashAfterMS = options.crashAfterMS;
      } else {
        throw new TypeError("Wrong option value for crashAfterMS");
      }
    }

    if (crashAfterMS > 0) {
      let timeToCrash = null;

      // If after |crashAfterMS| milliseconds (adjusted to take into
      // account sleep and otherwise busy computer) we have not finished
      // this shutdown phase, we assume that the shutdown is somehow
      // frozen, presumably deadlocked. At this stage, the only thing we
      // can do to avoid leaving the user's computer in an unstable (and
      // battery-sucking) situation is report the issue and crash.
      timeToCrash = looseTimer(crashAfterMS);
      timeToCrash.promise.then(
        () => {
          // Report the problem as best as we can, then crash.
          let state = this.state;

          // If you change the following message, please make sure
          // that any information on the topic and state appears
          // within the first 200 characters of the message. This
          // helps automatically sort oranges.
          let msg = "AsyncShutdown timeout in " + topic +
            " Conditions: " + JSON.stringify(state) +
            " At least one completion condition failed to complete" +
            " within a reasonable amount of time. Causing a crash to" +
            " ensure that we do not leave the user with an unresponsive" +
            " process draining resources.";
          fatalerr(msg);
          if (gCrashReporter && gCrashReporter.enabled) {
            let data = {
              phase: topic,
              conditions: state
            };
            gCrashReporter.annotateCrashReport("AsyncShutdownTimeout",
              JSON.stringify(data));
          } else {
            warn("No crash reporter available");
          }

          // To help sorting out bugs, we want to make sure that the
          // call to nsIDebug2.abort points to a guilty client, rather
          // than to AsyncShutdown itself. We pick a client that is
          // still blocking and use its filename/lineNumber,
          // which have been determined during the call to `addBlocker`.
          let filename = "?";
          let lineNumber = -1;
          for (let blocker of this._promiseToBlocker.values()) {
            ({filename, lineNumber} = blocker.getOrigin());
            break;
          }
          gDebug.abort(filename, lineNumber);
        },
        function onSatisfied() {
          // The promise has been rejected, which means that we have satisfied
          // all completion conditions.
        });

      promise = promise.then(function() {
        timeToCrash.reject();
      }/* No error is possible here*/);
    }

    return promise;
  },

  _removeBlocker(condition) {
    if (!this._waitForMe || !this._promiseToBlocker || !this._conditionToPromise) {
      // We have already cleaned up everything.
      return false;
    }

    let promise = this._conditionToPromise.get(condition);
    if (!promise) {
      // The blocker has already been removed
      return false;
    }
    this._conditionToPromise.delete(condition);
    this._promiseToBlocker.delete(promise);
    return this._waitForMe.delete(promise);
  },

});



// List of well-known phases
// Ideally, phases should be registered from the component that decides
// when they start/stop. For compatibility with existing startup/shutdown
// mechanisms, we register a few phases here.

// Parent process
if (!isContent) {
  this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown");
  this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change");
  this.AsyncShutdown.sendTelemetry = getPhase("profile-before-change-telemetry");
}

// Notifications that fire in the parent and content process, but should
// only have phases in the parent process.
if (!isContent) {
  this.AsyncShutdown.quitApplicationGranted = getPhase("quit-application-granted");
}

// Don't add a barrier for content-child-shutdown because this
// makes it easier to cause shutdown hangs.

// All processes
this.AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown");
this.AsyncShutdown.xpcomWillShutdown = getPhase("xpcom-will-shutdown");

this.AsyncShutdown.Barrier = Barrier;

Object.freeze(this.AsyncShutdown);
PK
!<`%%modules/AutoCompletePopup.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["AutoCompletePopup"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// AutoCompleteResultView is an abstraction around a list of results
// we got back up from browser-content.js. It implements enough of
// nsIAutoCompleteController and nsIAutoCompleteInput to make the
// richlistbox popup work.
var AutoCompleteResultView = {
  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIAutoCompleteController,
    Ci.nsIAutoCompleteInput,
  ]),

  // Private variables
  results: [],

  // nsIAutoCompleteController
  get matchCount() {
    return this.results.length;
  },

  getValueAt(index) {
    return this.results[index].value;
  },

  getLabelAt(index) {
    // Unused by richlist autocomplete - see getCommentAt.
    return "";
  },

  getCommentAt(index) {
    // The richlist autocomplete popup uses comment for its main
    // display of an item, which is why we're returning the label
    // here instead.
    return this.results[index].label;
  },

  getStyleAt(index) {
    return this.results[index].style;
  },

  getImageAt(index) {
    return this.results[index].image;
  },

  handleEnter(aIsPopupSelection) {
    AutoCompletePopup.handleEnter(aIsPopupSelection);
  },

  stopSearch() {},

  searchString: "",

  // nsIAutoCompleteInput
  get controller() {
    return this;
  },

  get popup() {
    return null;
  },

  _focus() {
    AutoCompletePopup.requestFocus();
  },

  // Internal JS-only API
  clearResults() {
    this.results = [];
  },

  setResults(results) {
    this.results = results;
  },
};

this.AutoCompletePopup = {
  MESSAGES: [
    "FormAutoComplete:SelectBy",
    "FormAutoComplete:GetSelectedIndex",
    "FormAutoComplete:SetSelectedIndex",
    "FormAutoComplete:MaybeOpenPopup",
    "FormAutoComplete:ClosePopup",
    "FormAutoComplete:Disconnect",
    "FormAutoComplete:RemoveEntry",
    "FormAutoComplete:Invalidate",
  ],

  init() {
    for (let msg of this.MESSAGES) {
      Services.mm.addMessageListener(msg, this);
    }
  },

  uninit() {
    for (let msg of this.MESSAGES) {
      Services.mm.removeMessageListener(msg, this);
    }
  },

  handleEvent(evt) {
    switch (evt.type) {
      case "popupshowing": {
        this.sendMessageToBrowser("FormAutoComplete:PopupOpened");
        break;
      }

      case "popuphidden": {
        AutoCompleteResultView.clearResults();
        this.sendMessageToBrowser("FormAutoComplete:PopupClosed");
        // adjustHeight clears the height from the popup so that
        // we don't have a big shrink effect if we closed with a
        // large list, and then open on a small one.
        this.openedPopup.adjustHeight();
        this.openedPopup = null;
        this.weakBrowser = null;
        evt.target.removeEventListener("popuphidden", this);
        evt.target.removeEventListener("popupshowing", this);
        break;
      }
    }
  },

  // Along with being called internally by the receiveMessage handler,
  // this function is also called directly by the login manager, which
  // uses a single message to fill in the autocomplete results. See
  // "RemoteLogins:autoCompleteLogins".
  showPopupWithResults({ browser, rect, dir, results }) {
    if (!results.length || this.openedPopup) {
      // We shouldn't ever be showing an empty popup, and if we
      // already have a popup open, the old one needs to close before
      // we consider opening a new one.
      return;
    }

    let window = browser.ownerGlobal;
    // Also check window top in case this is a sidebar.
    if (Services.focus.activeWindow !== window.top) {
      // We were sent a message from a window or tab that went into the
      // background, so we'll ignore it for now.
      return;
    }

    let firstResultStyle = results[0].style;
    this.weakBrowser = Cu.getWeakReference(browser);
    this.openedPopup = browser.autoCompletePopup;
    // the layout varies according to different result type
    this.openedPopup.setAttribute("firstresultstyle", firstResultStyle);
    this.openedPopup.hidden = false;
    // don't allow the popup to become overly narrow
    this.openedPopup.setAttribute("width", Math.max(100, rect.width));
    this.openedPopup.style.direction = dir;

    AutoCompleteResultView.setResults(results);
    this.openedPopup.view = AutoCompleteResultView;
    this.openedPopup.selectedIndex = -1;

    if (results.length) {
      // Reset fields that were set from the last time the search popup was open
      this.openedPopup.mInput = AutoCompleteResultView;
      // Temporarily increase the maxRows as we don't want to show
      // the scrollbar in form autofill popup.
      if (firstResultStyle == "autofill-profile") {
        this.openedPopup._normalMaxRows = this.openedPopup.maxRows;
        this.openedPopup.mInput.maxRows = 100;
      }
      this.openedPopup.showCommentColumn = false;
      this.openedPopup.showImageColumn = false;
      this.openedPopup.addEventListener("popuphidden", this);
      this.openedPopup.addEventListener("popupshowing", this);
      this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
                                             rect.width, rect.height, false,
                                             false);
      this.openedPopup.invalidate();
    } else {
      this.closePopup();
    }
  },

  invalidate(results) {
    if (!this.openedPopup) {
      return;
    }

    if (!results.length) {
      this.closePopup();
    } else {
      AutoCompleteResultView.setResults(results);
      this.openedPopup.invalidate();
    }
  },

  closePopup() {
    if (this.openedPopup) {
      // Note that hidePopup() closes the popup immediately,
      // so popuphiding or popuphidden events will be fired
      // and handled during this call.
      this.openedPopup.hidePopup();
    }
  },

  removeLogin(login) {
    Services.logins.removeLogin(login);
  },

  receiveMessage(message) {
    if (!message.target.autoCompletePopup) {
      // Returning false to pacify ESLint, but this return value is
      // ignored by the messaging infrastructure.
      return false;
    }

    switch (message.name) {
      case "FormAutoComplete:SelectBy": {
        if (this.openedPopup) {
          this.openedPopup.selectBy(message.data.reverse, message.data.page);
        }
        break;
      }

      case "FormAutoComplete:GetSelectedIndex": {
        if (this.openedPopup) {
          return this.openedPopup.selectedIndex;
        }
        // If the popup was closed, then the selection
        // has not changed.
        return -1;
      }

      case "FormAutoComplete:SetSelectedIndex": {
        let { index } = message.data;
        if (this.openedPopup) {
          this.openedPopup.selectedIndex = index;
        }
        break;
      }

      case "FormAutoComplete:MaybeOpenPopup": {
        let { results, rect, dir } = message.data;
        this.showPopupWithResults({
          browser: message.target,
          rect,
          dir,
          results,
        });
        break;
      }

      case "FormAutoComplete:Invalidate": {
        let { results } = message.data;
        this.invalidate(results);
        break;
      }

      case "FormAutoComplete:ClosePopup": {
        this.closePopup();
        break;
      }

      case "FormAutoComplete:Disconnect": {
        // The controller stopped controlling the current input, so clear
        // any cached data.  This is necessary cause otherwise we'd clear data
        // only when starting a new search, but the next input could not support
        // autocomplete and it would end up inheriting the existing data.
        AutoCompleteResultView.clearResults();
        break;
      }
    }
    // Returning false to pacify ESLint, but this return value is
    // ignored by the messaging infrastructure.
    return false;
  },

  /**
   * Despite its name, this handleEnter is only called when the user clicks on
   * one of the items in the popup since the popup is rendered in the parent process.
   * The real controller's handleEnter is called directly in the content process
   * for other methods of completing a selection (e.g. using the tab or enter
   * keys) since the field with focus is in that process.
   * @param {boolean} aIsPopupSelection
   */
  handleEnter(aIsPopupSelection) {
    if (this.openedPopup) {
      this.sendMessageToBrowser("FormAutoComplete:HandleEnter", {
        selectedIndex: this.openedPopup.selectedIndex,
        isPopupSelection: aIsPopupSelection,
      });
    }
  },

  /**
   * If a browser exists that AutoCompletePopup knows about,
   * sends it a message. Otherwise, this is a no-op.
   *
   * @param {string} msgName
   *        The name of the message to send.
   * @param {object} data
   *        The optional data to send with the message.
   */
  sendMessageToBrowser(msgName, data) {
    let browser = this.weakBrowser ?
      this.weakBrowser.get() :
      null;
    if (browser) {
      browser.messageManager.sendAsyncMessage(msgName, data);
    }
  },

  stopSearch() {},

  /**
   * Sends a message to the browser requesting that the input
   * that the AutoCompletePopup is open for be focused.
   */
  requestFocus() {
    if (this.openedPopup) {
      this.sendMessageToBrowser("FormAutoComplete:Focus");
    }
  },
};
PK
!<IHIH modules/BackgroundPageThumbs.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = [
  "BackgroundPageThumbs",
];

const DEFAULT_CAPTURE_TIMEOUT = 30000; // ms
const DESTROY_BROWSER_TIMEOUT = 60000; // ms
const FRAME_SCRIPT_URL = "chrome://global/content/backgroundPageThumbsContent.js";

const TELEMETRY_HISTOGRAM_ID_PREFIX = "FX_THUMBNAILS_BG_";

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const HTML_NS = "http://www.w3.org/1999/xhtml";

const ABOUT_NEWTAB_SEGREGATION_PREF = "privacy.usercontext.about_newtab_segregation.enabled";

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// possible FX_THUMBNAILS_BG_CAPTURE_DONE_REASON_2 telemetry values
const TEL_CAPTURE_DONE_OK = 0;
const TEL_CAPTURE_DONE_TIMEOUT = 1;
// 2 and 3 were used when we had special handling for private-browsing.
const TEL_CAPTURE_DONE_CRASHED = 4;
const TEL_CAPTURE_DONE_BAD_URI = 5;

// These are looked up on the global as properties below.
XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_OK", TEL_CAPTURE_DONE_OK);
XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_TIMEOUT", TEL_CAPTURE_DONE_TIMEOUT);
XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_CRASHED", TEL_CAPTURE_DONE_CRASHED);
XPCOMUtils.defineConstant(this, "TEL_CAPTURE_DONE_BAD_URI", TEL_CAPTURE_DONE_BAD_URI);

XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                  "resource://gre/modules/ContextualIdentityService.jsm");
const global = this;

const BackgroundPageThumbs = {

  /**
   * Asynchronously captures a thumbnail of the given URL.
   *
   * The page is loaded anonymously, and plug-ins are disabled.
   *
   * @param url      The URL to capture.
   * @param options  An optional object that configures the capture.  Its
   *                 properties are the following, and all are optional:
   * @opt onDone     A function that will be asynchronously called when the
   *                 capture is complete or times out.  It's called as
   *                   onDone(url),
   *                 where `url` is the captured URL.
   * @opt timeout    The capture will time out after this many milliseconds have
   *                 elapsed after the capture has progressed to the head of
   *                 the queue and started.  Defaults to 30000 (30 seconds).
   */
  capture(url, options = {}) {
    if (!PageThumbs._prefEnabled()) {
      if (options.onDone)
        schedule(() => options.onDone(url));
      return;
    }
    this._captureQueue = this._captureQueue || [];
    this._capturesByURL = this._capturesByURL || new Map();

    tel("QUEUE_SIZE_ON_CAPTURE", this._captureQueue.length);

    // We want to avoid duplicate captures for the same URL.  If there is an
    // existing one, we just add the callback to that one and we are done.
    let existing = this._capturesByURL.get(url);
    if (existing) {
      if (options.onDone)
        existing.doneCallbacks.push(options.onDone);
      // The queue is already being processed, so nothing else to do...
      return;
    }
    let cap = new Capture(url, this._onCaptureOrTimeout.bind(this), options);
    this._captureQueue.push(cap);
    this._capturesByURL.set(url, cap);
    this._processCaptureQueue();
  },

  /**
   * Asynchronously captures a thumbnail of the given URL if one does not
   * already exist.  Otherwise does nothing.
   *
   * @param url      The URL to capture.
   * @param options  An optional object that configures the capture.  See
   *                 capture() for description.
   * @return {Promise} A Promise that resolves when this task completes
   */
  async captureIfMissing(url, options = {}) {
    // The fileExistsForURL call is an optimization, potentially but unlikely
    // incorrect, and no big deal when it is.  After the capture is done, we
    // atomically test whether the file exists before writing it.
    let exists = await PageThumbsStorage.fileExistsForURL(url);
    if (exists) {
      if (options.onDone) {
        options.onDone(url);
      }
      return url;
    }
    let thumbPromise = new Promise((resolve, reject) => {
      let observer = {
        observe(subject, topic, data) { // jshint ignore:line
          if (data === url) {
            switch (topic) {
              case "page-thumbnail:create":
                resolve();
                break;
              case "page-thumbnail:error":
                reject(new Error("page-thumbnail:error"));
                break;
            }
            Services.obs.removeObserver(observer, "page-thumbnail:create");
            Services.obs.removeObserver(observer, "page-thumbnail:error");
          }
        },
        QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                               Ci.nsISupportsWeakReference])
      };
      // Use weak references to avoid leaking in tests where the promise we
      // return is GC'ed before it has resolved.
      Services.obs.addObserver(observer, "page-thumbnail:create", true);
      Services.obs.addObserver(observer, "page-thumbnail:error", true);
    });
    try {
      this.capture(url, options);
      await thumbPromise;
    } catch (err) {
      if (options.onDone) {
        options.onDone(url);
      }
      throw err;
    }
    return url;
  },

  /**
   * Tell the service that the thumbnail browser should be recreated at next
   * call of _ensureBrowser().
   */
  renewThumbnailBrowser() {
    this._renewThumbBrowser = true;
  },

  /**
   * Ensures that initialization of the thumbnail browser's parent window has
   * begun.
   *
   * @return  True if the parent window is completely initialized and can be
   *          used, and false if initialization has started but not completed.
   */
  _ensureParentWindowReady() {
    if (this._parentWin)
      // Already fully initialized.
      return true;
    if (this._startedParentWinInit)
      // Already started initializing.
      return false;

    this._startedParentWinInit = true;

    // Create a windowless browser and load our hosting
    // (privileged) document in it.
    let wlBrowser = Services.appShell.createWindowlessBrowser(true);
    wlBrowser.QueryInterface(Ci.nsIInterfaceRequestor);
    let webProgress = wlBrowser.getInterface(Ci.nsIWebProgress);
    this._listener = {
      QueryInterface: XPCOMUtils.generateQI([
        Ci.nsIWebProgressListener, Ci.nsIWebProgressListener2,
        Ci.nsISupportsWeakReference]),
    };
    this._listener.onStateChange = (wbp, request, stateFlags, status) => {
      if (!request) {
        return;
      }
      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
          stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
        webProgress.removeProgressListener(this._listener);
        delete this._listener;
        // Get the window reference via the document.
        this._parentWin = wlBrowser.document.defaultView;
        this._processCaptureQueue();
      }
    };
    webProgress.addProgressListener(this._listener, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
    wlBrowser.loadURI("chrome://global/content/backgroundPageThumbs.xhtml", 0, null, null, null);
    this._windowlessContainer = wlBrowser;

    return false;
  },

  /**
   * Destroys the service.  Queued and pending captures will never complete, and
   * their consumer callbacks will never be called.
   */
  _destroy() {
    if (this._captureQueue)
      this._captureQueue.forEach(cap => cap.destroy());
    this._destroyBrowser();
    if (this._windowlessContainer)
      this._windowlessContainer.close();
    delete this._captureQueue;
    delete this._windowlessContainer;
    delete this._startedParentWinInit;
    delete this._parentWin;
    delete this._listener;
  },

  /**
   * Creates the thumbnail browser if it doesn't already exist.
   */
  _ensureBrowser() {
    if (this._thumbBrowser && !this._renewThumbBrowser)
      return;

    this._destroyBrowser();
    this._renewThumbBrowser = false;

    let browser = this._parentWin.document.createElementNS(XUL_NS, "browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("remote", "true");
    browser.setAttribute("disableglobalhistory", "true");

    if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
      // Use the private container for thumbnails.
      let privateIdentity =
        ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
      browser.setAttribute("usercontextid", privateIdentity.userContextId);
    }

    // Size the browser.  Make its aspect ratio the same as the canvases' that
    // the thumbnails are drawn into; the canvases' aspect ratio is the same as
    // the screen's, so use that.  Aim for a size in the ballpark of 1024x768.
    let [swidth, sheight] = [{}, {}];
    Cc["@mozilla.org/gfx/screenmanager;1"].
      getService(Ci.nsIScreenManager).
      primaryScreen.
      GetRectDisplayPix({}, {}, swidth, sheight);
    let bwidth = Math.min(1024, swidth.value);
    // Setting the width and height attributes doesn't work -- the resulting
    // thumbnails are blank and transparent -- but setting the style does.
    browser.style.width = bwidth + "px";
    browser.style.height = (bwidth * sheight.value / swidth.value) + "px";

    this._parentWin.document.documentElement.appendChild(browser);

    // an event that is sent if the remote process crashes - no need to remove
    // it as we want it to be there as long as the browser itself lives.
    browser.addEventListener("oop-browser-crashed", () => {
      Cu.reportError("BackgroundThumbnails remote process crashed - recovering");
      this._destroyBrowser();
      let curCapture = this._captureQueue.length ? this._captureQueue[0] : null;
      // we could retry the pending capture, but it's possible the crash
      // was due directly to it, so trying again might just crash again.
      // We could keep a flag to indicate if it previously crashed, but
      // "resetting" the capture requires more work - so for now, we just
      // discard it.
      if (curCapture && curCapture.pending) {
        // Continue queue processing by calling curCapture._done().  Do it after
        // this crashed listener returns, though.  A new browser will be created
        // immediately (on the same stack as the _done call stack) if there are
        // any more queued-up captures, and that seems to mess up the new
        // browser's message manager if it happens on the same stack as the
        // listener.  Trying to send a message to the manager in that case
        // throws NS_ERROR_NOT_INITIALIZED.
        Services.tm.dispatchToMainThread(() => {
          curCapture._done(null, TEL_CAPTURE_DONE_CRASHED);
        });
      }
      // else: we must have been idle and not currently doing a capture (eg,
      // maybe a GC or similar crashed) - so there's no need to attempt a
      // queue restart - the next capture request will set everything up.
    });

    browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
    this._thumbBrowser = browser;
  },

  _destroyBrowser() {
    if (!this._thumbBrowser)
      return;
    this._thumbBrowser.remove();
    delete this._thumbBrowser;
  },

  /**
   * Starts the next capture if the queue is not empty and the service is fully
   * initialized.
   */
  _processCaptureQueue() {
    if (!this._captureQueue.length ||
        this._captureQueue[0].pending ||
        !this._ensureParentWindowReady())
      return;

    // Ready to start the first capture in the queue.
    this._ensureBrowser();
    this._captureQueue[0].start(this._thumbBrowser.messageManager);
    if (this._destroyBrowserTimer) {
      this._destroyBrowserTimer.cancel();
      delete this._destroyBrowserTimer;
    }
  },

  /**
   * Called when the current capture completes or fails (eg, times out, remote
   * process crashes.)
   */
  _onCaptureOrTimeout(capture) {
    // Since timeouts start as an item is being processed, only the first
    // item in the queue can be passed to this method.
    if (capture !== this._captureQueue[0])
      throw new Error("The capture should be at the head of the queue.");
    this._captureQueue.shift();
    this._capturesByURL.delete(capture.url);
    if (capture.doneReason != TEL_CAPTURE_DONE_OK) {
      Services.obs.notifyObservers(null, "page-thumbnail:error", capture.url);
    }

    // Start the destroy-browser timer *before* processing the capture queue.
    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(this._destroyBrowser.bind(this),
                           this._destroyBrowserTimeout,
                           Ci.nsITimer.TYPE_ONE_SHOT);
    this._destroyBrowserTimer = timer;

    this._processCaptureQueue();
  },

  _destroyBrowserTimeout: DESTROY_BROWSER_TIMEOUT,
};

Services.prefs.addObserver(ABOUT_NEWTAB_SEGREGATION_PREF,
  function(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed" && aData == ABOUT_NEWTAB_SEGREGATION_PREF) {
      BackgroundPageThumbs.renewThumbnailBrowser();
    }
  });

Object.defineProperty(this, "BackgroundPageThumbs", {
  value: BackgroundPageThumbs,
  enumerable: true,
  writable: false
});

/**
 * Represents a single capture request in the capture queue.
 *
 * @param url              The URL to capture.
 * @param captureCallback  A function you want called when the capture
 *                         completes.
 * @param options          The capture options.
 */
function Capture(url, captureCallback, options) {
  this.url = url;
  this.captureCallback = captureCallback;
  this.options = options;
  this.id = Capture.nextID++;
  this.creationDate = new Date();
  this.doneCallbacks = [];
  this.doneReason;
  if (options.onDone)
    this.doneCallbacks.push(options.onDone);
}

Capture.prototype = {

  get pending() {
    return !!this._msgMan;
  },

  /**
   * Sends a message to the content script to start the capture.
   *
   * @param messageManager  The nsIMessageSender of the thumbnail browser.
   */
  start(messageManager) {
    this.startDate = new Date();
    tel("CAPTURE_QUEUE_TIME_MS", this.startDate - this.creationDate);

    // timeout timer
    let timeout = typeof(this.options.timeout) == "number" ?
                  this.options.timeout :
                  DEFAULT_CAPTURE_TIMEOUT;
    this._timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._timeoutTimer.initWithCallback(this, timeout,
                                        Ci.nsITimer.TYPE_ONE_SHOT);

    // didCapture registration
    this._msgMan = messageManager;
    this._msgMan.sendAsyncMessage("BackgroundPageThumbs:capture",
                                  { id: this.id, url: this.url });
    this._msgMan.addMessageListener("BackgroundPageThumbs:didCapture", this);
  },

  /**
   * The only intended external use of this method is by the service when it's
   * uninitializing and doing things like destroying the thumbnail browser.  In
   * that case the consumer's completion callback will never be called.
   */
  destroy() {
    // This method may be called for captures that haven't started yet, so
    // guard against not yet having _timeoutTimer, _msgMan etc properties...
    if (this._timeoutTimer) {
      this._timeoutTimer.cancel();
      delete this._timeoutTimer;
    }
    if (this._msgMan) {
      this._msgMan.removeMessageListener("BackgroundPageThumbs:didCapture",
                                         this);
      delete this._msgMan;
    }
    delete this.captureCallback;
    delete this.doneCallbacks;
    delete this.options;
  },

  // Called when the didCapture message is received.
  receiveMessage(msg) {
    if (msg.data.imageData)
      tel("CAPTURE_SERVICE_TIME_MS", new Date() - this.startDate);

    // A different timed-out capture may have finally successfully completed, so
    // discard messages that aren't meant for this capture.
    if (msg.data.id != this.id)
      return;

    if (msg.data.failReason) {
      let reason = global["TEL_CAPTURE_DONE_" + msg.data.failReason];
      this._done(null, reason);
      return;
    }

    this._done(msg.data, TEL_CAPTURE_DONE_OK);
  },

  // Called when the timeout timer fires.
  notify() {
    this._done(null, TEL_CAPTURE_DONE_TIMEOUT);
  },

  _done(data, reason) {
    // Note that _done will be called only once, by either receiveMessage or
    // notify, since it calls destroy here, which cancels the timeout timer and
    // removes the didCapture message listener.
    let { captureCallback, doneCallbacks, options } = this;
    this.destroy();
    this.doneReason = reason;

    if (typeof(reason) != "number") {
      throw new Error("A done reason must be given.");
    }
    tel("CAPTURE_DONE_REASON_2", reason);
    if (data && data.telemetry) {
      // Telemetry is currently disabled in the content process (bug 680508).
      for (let id in data.telemetry) {
        tel(id, data.telemetry[id]);
      }
    }

    let done = () => {
      captureCallback(this);
      for (let callback of doneCallbacks) {
        try {
          callback.call(options, this.url);
        } catch (err) {
          Cu.reportError(err);
        }
      }

      if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
        // Clear the data in the private container for thumbnails.
        let privateIdentity =
          ContextualIdentityService.getPrivateIdentity("userContextIdInternal.thumbnail");
        Services.obs.notifyObservers(null, "clear-origin-attributes-data",
          JSON.stringify({ userContextId: privateIdentity.userContextId }));
      }
    };

    if (!data) {
      done();
      return;
    }

    PageThumbs._store(this.url, data.finalURL, data.imageData, true)
              .then(done, done);
  },
};

Capture.nextID = 0;

/**
 * Adds a value to one of this module's telemetry histograms.
 *
 * @param histogramID  This is prefixed with this module's ID.
 * @param value        The value to add.
 */
function tel(histogramID, value) {
  let id = TELEMETRY_HISTOGRAM_ID_PREFIX + histogramID;
  Services.telemetry.getHistogramById(id).add(value);
}

function schedule(callback) {
  Services.tm.dispatchToMainThread(callback);
}
PK
!<s		modules/Battery.jsm// -*- Mode: javascript; 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/.

"use strict";

/** This module wraps around navigator.getBattery (https://developer.mozilla.org/en-US/docs/Web/API/Navigator.getBattery).
  * and provides a framework for spoofing battery values in test code.
  * To spoof the battery values, set `Debugging.fake = true` after exporting this with a BackstagePass,
  * after which you can spoof a property yb setting the relevant property of the BatteryManager object.
  */
this.EXPORTED_SYMBOLS = ["GetBattery", "Battery"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

// Load Services, for the BatteryManager API
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

// Values for the fake battery. See the documentation of Navigator.battery for the meaning of each field.
var gFakeBattery = {
  charging: false,
  chargingTime: 0,
  dischargingTime: Infinity,
  level: 1,
}

// BackendPass-exported object for toggling spoofing
this.Debugging = {
  /**
   * If `false`, use the DOM Battery implementation.
   * Set it to `true` if you need to fake battery values
   * for testing or debugging purposes.
   */
  fake: false
}

this.GetBattery = function() {
  return new Services.appShell.hiddenDOMWindow.Promise(function(resolve, reject) {
    // Return fake values if spoofing is enabled, otherwise fetch the real values from the BatteryManager API
    if (Debugging.fake) {
      resolve(gFakeBattery);
      return;
    }
    Services.appShell.hiddenDOMWindow.navigator.getBattery().then(resolve, reject);
  });
};

this.Battery = {};

for (let k of ["charging", "chargingTime", "dischargingTime", "level"]) {
  let prop = k;
  Object.defineProperty(this.Battery, prop, {
    get() {
      // Return fake value if spoofing is enabled, otherwise fetch the real value from the BatteryManager API
      if (Debugging.fake) {
        return gFakeBattery[prop];
      }
      return Services.appShell.hiddenDOMWindow.navigator.battery[prop];
    },
    set(fakeSetting) {
      if (!Debugging.fake) {
        throw new Error("Tried to set fake battery value when battery spoofing was disabled");
      }
      gFakeBattery[prop] = fakeSetting;
    }
  })
}
PK
!<h		modules/BinarySearch.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "BinarySearch",
];

this.BinarySearch = Object.freeze({

  /**
   * Returns the index of the given target in the given array or -1 if the
   * target is not found.
   *
   * See search() for a description of this function's parameters.
   *
   * @return The index of `target` in `array` or -1 if `target` is not found.
   */
  indexOf(comparator, array, target) {
    let [found, idx] = this.search(comparator, array, target);
    return found ? idx : -1;
  },

  /**
   * Returns the index within the given array where the given target may be
   * inserted to keep the array ordered.
   *
   * See search() for a description of this function's parameters.
   *
   * @return The index in `array` where `target` may be inserted to keep `array`
   *         ordered.
   */
  insertionIndexOf(comparator, array, target) {
    return this.search(comparator, array, target)[1];
  },

  /**
   * Searches for the given target in the given array.
   *
   * @param  comparator
   *         A function that takes two arguments and compares them, returning a
   *         negative number if the first should be ordered before the second,
   *         zero if the first and second have the same ordering, or a positive
   *         number if the second should be ordered before the first.  The first
   *         argument is always `target`, and the second argument is a value
   *         from the array.
   * @param  array
   *         An array whose elements are ordered by `comparator`.
   * @param  target
   *         The value to search for.
   * @return An array with two elements.  If `target` is found, the first
   *         element is true, and the second element is its index in the array.
   *         If `target` is not found, the first element is false, and the
   *         second element is the index where it may be inserted to keep the
   *         array ordered.
   */
  search(comparator, array, target) {
    let low = 0;
    let high = array.length - 1;
    while (low <= high) {
      // Thanks to http://jsperf.com/code-review-1480 for this tip.
      let mid = (low + high) >> 1;
      let cmp = comparator(target, array[mid]);
      if (cmp == 0)
        return [true, mid];
      if (cmp < 0)
        high = mid - 1;
      else
        low = mid + 1;
    }
    return [false, low];
  },
});
PK
!<L{JJmodules/BookmarkHTMLUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file works on the old-style "bookmarks.html" file.  It includes
 * functions to import and export existing bookmarks to this file format.
 *
 * Format
 * ------
 *
 * Primary heading := h1
 *   Old version used this to set attributes on the bookmarks RDF root, such
 *   as the last modified date. We only use H1 to check for the attribute
 *   PLACES_ROOT, which tells us that this hierarchy root is the places root.
 *   For backwards compatibility, if we don't find this, we assume that the
 *   hierarchy is rooted at the bookmarks menu.
 * Heading := any heading other than h1
 *   Old version used this to set attributes on the current container. We only
 *   care about the content of the heading container, which contains the title
 *   of the bookmark container.
 * Bookmark := a
 *   HREF is the destination of the bookmark
 *   FEEDURL is the URI of the RSS feed if this is a livemark.
 *   LAST_CHARSET is stored as an annotation so that the next time we go to
 *     that page we remember the user's preference.
 *   WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar.
 *   ICON will be stored in the favicon service
 *   ICON_URI is new for places bookmarks.html, it refers to the original
 *     URI of the favicon so we don't have to make up favicon URLs.
 *   Text of the <a> container is the name of the bookmark
 *   Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
 * Bookmark comment := dd
 *   This affects the previosly added bookmark
 * Separator := hr
 *   Insert a separator into the current container
 * The folder hierarchy is defined by <dl>/<ul>/<menu> (the old importing code
 *     handles all these cases, when we write, use <dl>).
 *
 * Overall design
 * --------------
 *
 * We need to emulate a recursive parser. A "Bookmark import frame" is created
 * corresponding to each folder we encounter. These are arranged in a stack,
 * and contain all the state we need to keep track of.
 *
 * A frame is created when we find a heading, which defines a new container.
 * The frame also keeps track of the nesting of <DL>s, (in well-formed
 * bookmarks files, these will have a 1-1 correspondence with frames, but we
 * try to be a little more flexible here). When the nesting count decreases
 * to 0, then we know a frame is complete and to pop back to the previous
 * frame.
 *
 * Note that a lot of things happen when tags are CLOSED because we need to
 * get the text from the content of the tag. For example, link and heading tags
 * both require the content (= title) before actually creating it.
 */

this.EXPORTED_SYMBOLS = [ "BookmarkHTMLUtils" ];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
  "resource://gre/modules/PlacesBackups.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");

const Container_Normal = 0;
const Container_Toolbar = 1;
const Container_Menu = 2;
const Container_Unfiled = 3;
const Container_Places = 4;

const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
const DESCRIPTION_ANNO = "bookmarkProperties/description";

const MICROSEC_PER_SEC = 1000000;

const EXPORT_INDENT = "    "; // four spaces

// Counter used to build fake favicon urls.
var serialNumber = 0;

function base64EncodeString(aString) {
  let stream = Cc["@mozilla.org/io/string-input-stream;1"]
                 .createInstance(Ci.nsIStringInputStream);
  stream.setData(aString, aString.length);
  let encoder = Cc["@mozilla.org/scriptablebase64encoder;1"]
                  .createInstance(Ci.nsIScriptableBase64Encoder);
  return encoder.encodeToString(stream, aString.length);
}

/**
 * Provides HTML escaping for use in HTML attributes and body of the bookmarks
 * file, compatible with the old bookmarks system.
 */
function escapeHtmlEntities(aText) {
  return (aText || "").replace(/&/g, "&amp;")
                      .replace(/</g, "&lt;")
                      .replace(/>/g, "&gt;")
                      .replace(/"/g, "&quot;")
                      .replace(/'/g, "&#39;");
}

/**
 * Provides URL escaping for use in HTML attributes of the bookmarks file,
 * compatible with the old bookmarks system.
 */
function escapeUrl(aText) {
  return (aText || "").replace(/"/g, "%22");
}

function notifyObservers(aTopic, aInitialImport) {
  Services.obs.notifyObservers(null, aTopic, aInitialImport ? "html-initial"
                                                            : "html");
}

this.BookmarkHTMLUtils = Object.freeze({
  /**
   * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
   *
   * @param aSpec
   *        String containing the "file:" URI for the existing "bookmarks.html"
   *        file to be loaded.
   * @param aInitialImport
   *        Whether this is the initial import executed on a new profile.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  importFromURL: function BHU_importFromURL(aSpec, aInitialImport) {
    return (async function() {
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
      try {
        let importer = new BookmarkImporter(aInitialImport);
        await importer.importFromURL(aSpec);

        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
      } catch (ex) {
        Cu.reportError("Failed to import bookmarks from " + aSpec + ": " + ex);
        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
        throw ex;
      }
    })();
  },

  /**
   * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
   *
   * @param aFilePath
   *        OS.File path string of the "bookmarks.html" file to be loaded.
   * @param aInitialImport
   *        Whether this is the initial import executed on a new profile.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   * @deprecated passing an nsIFile is deprecated
   */
  importFromFile: function BHU_importFromFile(aFilePath, aInitialImport) {
    if (aFilePath instanceof Ci.nsIFile) {
      Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.importFromFile " +
                         "is deprecated. Please use an OS.File path string instead.",
                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
      aFilePath = aFilePath.path;
    }

    return (async function() {
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
      try {
        if (!(await OS.File.exists(aFilePath))) {
          throw new Error("Cannot import from nonexisting html file: " + aFilePath);
        }
        let importer = new BookmarkImporter(aInitialImport);
        await importer.importFromURL(OS.Path.toFileURI(aFilePath));

        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
      } catch (ex) {
        Cu.reportError("Failed to import bookmarks from " + aFilePath + ": " + ex);
        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
        throw ex;
      }
    })();
  },

  /**
   * Saves the current bookmarks hierarchy to a "bookmarks.html" file.
   *
   * @param aFilePath
   *        OS.File path string for the "bookmarks.html" file to be created.
   *
   * @return {Promise}
   * @resolves To the exported bookmarks count when the file has been created.
   * @rejects JavaScript exception.
   * @deprecated passing an nsIFile is deprecated
   */
  exportToFile: function BHU_exportToFile(aFilePath) {
    if (aFilePath instanceof Ci.nsIFile) {
      Deprecated.warning("Passing an nsIFile to BookmarksHTMLUtils.exportToFile " +
                         "is deprecated. Please use an OS.File path string instead.",
                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
      aFilePath = aFilePath.path;
    }
    return (async function() {
      let [bookmarks, count] = await PlacesBackups.getBookmarksTree();
      let startTime = Date.now();

      // Report the time taken to convert the tree to HTML.
      let exporter = new BookmarkExporter(bookmarks);
      await exporter.exportToFile(aFilePath);

      try {
        Services.telemetry
                .getHistogramById("PLACES_EXPORT_TOHTML_MS")
                .add(Date.now() - startTime);
      } catch (ex) {
        Components.utils.reportError("Unable to report telemetry.");
      }

      return count;
    })();
  },

  get defaultPath() {
    try {
      return Services.prefs.getCharPref("browser.bookmarks.file");
    } catch (ex) {}
    return OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.html")
  }
});

function Frame(aFrameId) {
  this.containerId = aFrameId;

  /**
   * How many <dl>s have been nested. Each frame/container should start
   * with a heading, and is then followed by a <dl>, <ul>, or <menu>. When
   * that list is complete, then it is the end of this container and we need
   * to pop back up one level for new items. If we never get an open tag for
   * one of these things, we should assume that the container is empty and
   * that things we find should be siblings of it. Normally, these <dl>s won't
   * be nested so this will be 0 or 1.
   */
  this.containerNesting = 0;

  /**
   * when we find a heading tag, it actually affects the title of the NEXT
   * container in the list. This stores that heading tag and whether it was
   * special. 'consumeHeading' resets this._
   */
  this.lastContainerType = Container_Normal;

  /**
   * this contains the text from the last begin tag until now. It is reset
   * at every begin tag. We can check it when we see a </a>, or </h3>
   * to see what the text content of that node should be.
   */
  this.previousText = "";

  /**
   * true when we hit a <dd>, which contains the description for the preceding
   * <a> tag. We can't just check for </dd> like we can for </a> or </h3>
   * because if there is a sub-folder, it is actually a child of the <dd>
   * because the tag is never explicitly closed. If this is true and we see a
   * new open tag, that means to commit the description to the previous
   * bookmark.
   *
   * Additional weirdness happens when the previous <dt> tag contains a <h3>:
   * this means there is a new folder with the given description, and whose
   * children are contained in the following <dl> list.
   *
   * This is handled in openContainer(), which commits previous text if
   * necessary.
   */
  this.inDescription = false;

  /**
   * contains the URL of the previous bookmark created. This is used so that
   * when we encounter a <dd>, we know what bookmark to associate the text with.
   * This is cleared whenever we hit a <h3>, so that we know NOT to save this
   * with a bookmark, but to keep it until
   */
  this.previousLink = null; // nsIURI

  /**
   * contains the URL of the previous livemark, so that when the link ends,
   * and the livemark title is known, we can create it.
   */
  this.previousFeed = null; // nsIURI

  /**
   * Contains the id of an imported, or newly created bookmark.
   */
  this.previousId = 0;

  /**
   * Contains the date-added and last-modified-date of an imported item.
   * Used to override the values set by insertBookmark, createFolder, etc.
   */
  this.previousDateAdded = 0;
  this.previousLastModifiedDate = 0;
}

function BookmarkImporter(aInitialImport) {
  this._isImportDefaults = aInitialImport;
  // The bookmark change source, used to determine the sync status and change
  // counter.
  this._source = aInitialImport ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
                                  PlacesUtils.bookmarks.SOURCE_IMPORT;
  this._frames = [];
  this._frames.push(new Frame(PlacesUtils.bookmarksMenuFolderId));
}

BookmarkImporter.prototype = {

  _safeTrim: function safeTrim(aStr) {
    return aStr ? aStr.trim() : aStr;
  },

  get _curFrame() {
    return this._frames[this._frames.length - 1];
  },

  get _previousFrame() {
    return this._frames[this._frames.length - 2];
  },

  /**
   * This is called when there is a new folder found. The folder takes the
   * name from the previous frame's heading.
   */
  _newFrame: function newFrame() {
    let containerId = -1;
    let frame = this._curFrame;
    let containerTitle = frame.previousText;
    frame.previousText = "";
    let containerType = frame.lastContainerType;

    switch (containerType) {
      case Container_Normal:
        // append a new folder
        containerId =
          PlacesUtils.bookmarks.createFolder(frame.containerId,
                                             containerTitle,
                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
                                             /* aGuid */ null, this._source);
        break;
      case Container_Places:
        containerId = PlacesUtils.placesRootId;
        break;
      case Container_Menu:
        containerId = PlacesUtils.bookmarksMenuFolderId;
        break;
      case Container_Unfiled:
        containerId = PlacesUtils.unfiledBookmarksFolderId;
        break;
      case Container_Toolbar:
        containerId = PlacesUtils.toolbarFolderId;
        break;
      default:
        // NOT REACHED
        throw new Error("Unreached");
    }

    if (frame.previousDateAdded > 0) {
      try {
        PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded, this._source);
      } catch (e) {
      }
      frame.previousDateAdded = 0;
    }
    if (frame.previousLastModifiedDate > 0) {
      try {
        PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate, this._source);
      } catch (e) {
      }
      // don't clear last-modified, in case there's a description
    }

    frame.previousId = containerId;

    this._frames.push(new Frame(containerId));
  },

  /**
   * Handles <hr> as a separator.
   *
   * @note Separators may have a title in old html files, though Places dropped
   *       support for them.
   *       We also don't import ADD_DATE or LAST_MODIFIED for separators because
   *       pre-Places bookmarks did not support them.
   */
  _handleSeparator: function handleSeparator(aElt) {
    let frame = this._curFrame;
    try {
      frame.previousId =
        PlacesUtils.bookmarks.insertSeparator(frame.containerId,
                                              PlacesUtils.bookmarks.DEFAULT_INDEX,
                                              /* aGuid */ null,
                                              this._source);
    } catch (e) {}
  },

  /**
   * Handles <H1>. We check for the attribute PLACES_ROOT and reset the
   * container id if it's found. Otherwise, the default bookmark menu
   * root is assumed and imported things will go into the bookmarks menu.
   */
  _handleHead1Begin: function handleHead1Begin(aElt) {
    if (this._frames.length > 1) {
      return;
    }
    if (aElt.hasAttribute("places_root")) {
      this._curFrame.containerId = PlacesUtils.placesRootId;
    }
  },

  /**
   * Called for h2,h3,h4,h5,h6. This just stores the correct information in
   * the current frame; the actual new frame corresponding to the container
   * associated with the heading will be created when the tag has been closed
   * and we know the title (we don't know to create a new folder or to merge
   * with an existing one until we have the title).
   */
  _handleHeadBegin: function handleHeadBegin(aElt) {
    let frame = this._curFrame;

    // after a heading, a previous bookmark is not applicable (for example, for
    // the descriptions contained in a <dd>). Neither is any previous head type
    frame.previousLink = null;
    frame.lastContainerType = Container_Normal;

    // It is syntactically possible for a heading to appear after another heading
    // but before the <dl> that encloses that folder's contents.  This should not
    // happen in practice, as the file will contain "<dl></dl>" sequence for
    // empty containers.
    //
    // Just to be on the safe side, if we encounter
    //   <h3>FOO</h3>
    //   <h3>BAR</h3>
    //   <dl>...content 1...</dl>
    //   <dl>...content 2...</dl>
    // we'll pop the stack when we find the h3 for BAR, treating that as an
    // implicit ending of the FOO container. The output will be FOO and BAR as
    // siblings. If there's another <dl> following (as in "content 2"), those
    // items will be treated as further siblings of FOO and BAR
    // This special frame popping business, of course, only happens when our
    // frame array has more than one element so we can avoid situations where
    // we don't have a frame to parse into anymore.
    if (frame.containerNesting == 0 && this._frames.length > 1) {
      this._frames.pop();
    }

    // We have to check for some attributes to see if this is a "special"
    // folder, which will have different creation rules when the end tag is
    // processed.
    if (aElt.hasAttribute("personal_toolbar_folder")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Toolbar;
      }
    } else if (aElt.hasAttribute("bookmarks_menu")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Menu;
      }
    } else if (aElt.hasAttribute("unfiled_bookmarks_folder")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Unfiled;
      }
    } else if (aElt.hasAttribute("places_root")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Places;
      }
    } else {
      let addDate = aElt.getAttribute("add_date");
      if (addDate) {
        frame.previousDateAdded =
          this._convertImportedDateToInternalDate(addDate);
      }
      let modDate = aElt.getAttribute("last_modified");
      if (modDate) {
        frame.previousLastModifiedDate =
          this._convertImportedDateToInternalDate(modDate);
      }
    }
    this._curFrame.previousText = "";
  },

  /*
   * Handles "<a" tags by creating a new bookmark. The title of the bookmark
   * will be the text content, which will be stuffed in previousText for us
   * and which will be saved by handleLinkEnd
   */
  _handleLinkBegin: function handleLinkBegin(aElt) {
    let frame = this._curFrame;

    // Make sure that the feed URIs from previous frames are emptied.
    frame.previousFeed = null;
    // Make sure that the bookmark id from previous frames are emptied.
    frame.previousId = 0;
    // mPreviousText will hold link text, clear it.
    frame.previousText = "";

    // Get the attributes we care about.
    let href = this._safeTrim(aElt.getAttribute("href"));
    let feedUrl = this._safeTrim(aElt.getAttribute("feedurl"));
    let icon = this._safeTrim(aElt.getAttribute("icon"));
    let iconUri = this._safeTrim(aElt.getAttribute("icon_uri"));
    let lastCharset = this._safeTrim(aElt.getAttribute("last_charset"));
    let keyword = this._safeTrim(aElt.getAttribute("shortcuturl"));
    let postData = this._safeTrim(aElt.getAttribute("post_data"));
    let webPanel = this._safeTrim(aElt.getAttribute("web_panel"));
    let dateAdded = this._safeTrim(aElt.getAttribute("add_date"));
    let lastModified = this._safeTrim(aElt.getAttribute("last_modified"));
    let tags = this._safeTrim(aElt.getAttribute("tags"));

    // For feeds, get the feed URL.  If it is invalid, mPreviousFeed will be
    // NULL and we'll create it as a normal bookmark.
    if (feedUrl) {
      frame.previousFeed = NetUtil.newURI(feedUrl);
    }

    // Ignore <a> tags that have no href.
    if (href) {
      // Save the address if it's valid.  Note that we ignore errors if this is a
      // feed since href is optional for them.
      try {
        frame.previousLink = NetUtil.newURI(href);
      } catch (e) {
        if (!frame.previousFeed) {
          frame.previousLink = null;
          return;
        }
      }
    } else {
      frame.previousLink = null;
      // The exception is for feeds, where the href is an optional component
      // indicating the source web site.
      if (!frame.previousFeed) {
        return;
      }
    }

    // Save bookmark's last modified date.
    if (lastModified) {
      frame.previousLastModifiedDate =
        this._convertImportedDateToInternalDate(lastModified);
    }

    // If this is a live bookmark, we will handle it in HandleLinkEnd(), so we
    // can skip bookmark creation.
    if (frame.previousFeed) {
      return;
    }

    // Create the bookmark.  The title is unknown for now, we will set it later.
    try {
      frame.previousId =
        PlacesUtils.bookmarks.insertBookmark(frame.containerId,
                                             frame.previousLink,
                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
                                             /* aTitle */ "",
                                             /* aGuid */ null,
                                             this._source);
    } catch (e) {
      return;
    }

    // Set the date added value, if we have it.
    if (dateAdded) {
      try {
        PlacesUtils.bookmarks.setItemDateAdded(frame.previousId,
          this._convertImportedDateToInternalDate(dateAdded), this._source);
      } catch (e) {
      }
    }

    // Adds tags to the URI, if there are any.
    if (tags) {
      try {
        let tagsArray = tags.split(",");
        PlacesUtils.tagging.tagURI(frame.previousLink, tagsArray, this._source);
      } catch (e) {
      }
    }

    // Save the favicon.
    if (icon || iconUri) {
      let iconUriObject;
      try {
        iconUriObject = NetUtil.newURI(iconUri);
      } catch (e) {
      }
      if (icon || iconUriObject) {
        try {
          this._setFaviconForURI(frame.previousLink, iconUriObject, icon);
        } catch (e) {
        }
      }
    }

    // Save the keyword.
    if (keyword) {
      let kwPromise = PlacesUtils.keywords.insert({ keyword,
                                                    url: frame.previousLink.spec,
                                                    postData,
                                                    source: this._source });
      this._importPromises.push(kwPromise);
    }

    // Set load-in-sidebar annotation for the bookmark.
    if (webPanel && webPanel.toLowerCase() == "true") {
      try {
        PlacesUtils.annotations.setItemAnnotation(frame.previousId,
                                                  LOAD_IN_SIDEBAR_ANNO,
                                                  1,
                                                  0,
                                                  PlacesUtils.annotations.EXPIRE_NEVER,
                                                  this._source);
      } catch (e) {
      }
    }

    // Import last charset.
    if (lastCharset) {
      let chPromise = PlacesUtils.setCharsetForURI(frame.previousLink, lastCharset, this._source);
      this._importPromises.push(chPromise);
    }
  },

  _handleContainerBegin: function handleContainerBegin() {
    this._curFrame.containerNesting++;
  },

  /**
   * Our "indent" count has decreased, and when we hit 0 that means that this
   * container is complete and we need to pop back to the outer frame. Never
   * pop the toplevel frame
   */
  _handleContainerEnd: function handleContainerEnd() {
    let frame = this._curFrame;
    if (frame.containerNesting > 0)
      frame.containerNesting --;
    if (this._frames.length > 1 && frame.containerNesting == 0) {
      // we also need to re-set the imported last-modified date here. Otherwise
      // the addition of items will override the imported field.
      let prevFrame = this._previousFrame;
      if (prevFrame.previousLastModifiedDate > 0) {
        PlacesUtils.bookmarks.setItemLastModified(frame.containerId,
                                                  prevFrame.previousLastModifiedDate,
                                                  this._source);
      }
      this._frames.pop();
    }
  },

  /**
   * Creates the new frame for this heading now that we know the name of the
   * container (tokens since the heading open tag will have been placed in
   * previousText).
   */
  _handleHeadEnd: function handleHeadEnd() {
    this._newFrame();
  },

  /**
   * Saves the title for the given bookmark.
   */
  _handleLinkEnd: function handleLinkEnd() {
    let frame = this._curFrame;
    frame.previousText = frame.previousText.trim();

    try {
      if (frame.previousFeed) {
        // The is a live bookmark.  We create it here since in HandleLinkBegin we
        // don't know the title.
        let lmPromise = PlacesUtils.livemarks.addLivemark({
          "title": frame.previousText,
          "parentId": frame.containerId,
          "index": PlacesUtils.bookmarks.DEFAULT_INDEX,
          "feedURI": frame.previousFeed,
          "siteURI": frame.previousLink,
          "source": this._source,
        });
        this._importPromises.push(lmPromise);
      } else if (frame.previousLink) {
        // This is a common bookmark.
        PlacesUtils.bookmarks.setItemTitle(frame.previousId,
                                           frame.previousText,
                                           this._source);
      }
    } catch (e) {
    }


    // Set last modified date as the last change.
    if (frame.previousId > 0 && frame.previousLastModifiedDate > 0) {
      try {
        PlacesUtils.bookmarks.setItemLastModified(frame.previousId,
                                                  frame.previousLastModifiedDate,
                                                  this._source);
      } catch (e) {
      }
      // Note: don't clear previousLastModifiedDate, because if this item has a
      // description, we'll need to set it again.
    }

    frame.previousText = "";

  },

  _openContainer: function openContainer(aElt) {
    if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
      return;
    }
    switch (aElt.localName) {
      case "h1":
        this._handleHead1Begin(aElt);
        break;
      case "h2":
      case "h3":
      case "h4":
      case "h5":
      case "h6":
        this._handleHeadBegin(aElt);
        break;
      case "a":
        this._handleLinkBegin(aElt);
        break;
      case "dl":
      case "ul":
      case "menu":
        this._handleContainerBegin();
        break;
      case "dd":
        this._curFrame.inDescription = true;
        break;
      case "hr":
        this._handleSeparator(aElt);
        break;
    }
  },

  _closeContainer: function closeContainer(aElt) {
    let frame = this._curFrame;

    // see the comment for the definition of inDescription. Basically, we commit
    // any text in previousText to the description of the node/folder if there
    // is any.
    if (frame.inDescription) {
      // NOTE ES5 trim trims more than the previous C++ trim.
      frame.previousText = frame.previousText.trim(); // important
      if (frame.previousText) {

        let itemId = !frame.previousLink ? frame.containerId
                                         : frame.previousId;

        try {
          if (!PlacesUtils.annotations.itemHasAnnotation(itemId, DESCRIPTION_ANNO)) {
            PlacesUtils.annotations.setItemAnnotation(itemId,
                                                      DESCRIPTION_ANNO,
                                                      frame.previousText,
                                                      0,
                                                      PlacesUtils.annotations.EXPIRE_NEVER,
                                                      this._source);
          }
        } catch (e) {
        }
        frame.previousText = "";

        // Set last-modified a 2nd time for all items with descriptions
        // we need to set last-modified as the *last* step in processing
        // any item type in the bookmarks.html file, so that we do
        // not overwrite the imported value. for items without descriptions,
        // setting this value after setting the item title is that
        // last point at which we can save this value before it gets reset.
        // for items with descriptions, it must set after that point.
        // however, at the point at which we set the title, there's no way
        // to determine if there will be a description following,
        // so we need to set the last-modified-date at both places.

        let lastModified;
        if (!frame.previousLink) {
          lastModified = this._previousFrame.previousLastModifiedDate;
        } else {
          lastModified = frame.previousLastModifiedDate;
        }

        if (itemId > 0 && lastModified > 0) {
          PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified,
                                                    this._source);
        }
      }
      frame.inDescription = false;
    }

    if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
      return;
    }
    switch (aElt.localName) {
      case "dl":
      case "ul":
      case "menu":
        this._handleContainerEnd();
        break;
      case "dt":
        break;
      case "h1":
        // ignore
        break;
      case "h2":
      case "h3":
      case "h4":
      case "h5":
      case "h6":
        this._handleHeadEnd();
        break;
      case "a":
        this._handleLinkEnd();
        break;
      default:
        break;
    }
  },

  _appendText: function appendText(str) {
    this._curFrame.previousText += str;
  },

  /**
   * data is a string that is a data URI for the favicon. Our job is to
   * decode it and store it in the favicon service.
   *
   * When aIconURI is non-null, we will use that as the URI of the favicon
   * when storing in the favicon service.
   *
   * When aIconURI is null, we have to make up a URI for this favicon so that
   * it can be stored in the service. The real one will be set the next time
   * the user visits the page. Our made up one should get expired when the
   * page no longer references it.
   */
  _setFaviconForURI: function setFaviconForURI(aPageURI, aIconURI, aData) {
    // if the input favicon URI is a chrome: URI, then we just save it and don't
    // worry about data
    if (aIconURI) {
      if (aIconURI.schemeIs("chrome")) {
        PlacesUtils.favicons.setAndFetchFaviconForPage(aPageURI, aIconURI, false,
                                                       PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
                                                       Services.scriptSecurityManager.getSystemPrincipal());

        return;
      }
    }

    // some bookmarks have placeholder URIs that contain just "data:"
    // ignore these
    if (aData.length <= 5) {
      return;
    }

    let faviconURI;
    if (aIconURI) {
      faviconURI = aIconURI;
    } else {
      // Make up a favicon URI for this page.  Later, we'll make sure that this
      // favicon URI is always associated with local favicon data, so that we
      // don't load this URI from the network.
      let faviconSpec = "http://www.mozilla.org/2005/made-up-favicon/"
                      + serialNumber
                      + "-"
                      + new Date().getTime();
      faviconURI = NetUtil.newURI(faviconSpec);
      serialNumber++;
    }

    // This could fail if the favicon is bigger than defined limit, in such a
    // case neither the favicon URI nor the favicon data will be saved.  If the
    // bookmark is visited again later, the URI and data will be fetched.
    PlacesUtils.favicons.replaceFaviconDataFromDataURL(faviconURI, aData, 0,
                                                       Services.scriptSecurityManager.getSystemPrincipal());
    PlacesUtils.favicons.setAndFetchFaviconForPage(aPageURI, faviconURI, false,
                                                   PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
                                                   Services.scriptSecurityManager.getSystemPrincipal());
  },

  /**
   * Converts a string date in seconds to an int date in microseconds
   */
  _convertImportedDateToInternalDate: function convertImportedDateToInternalDate(aDate) {
    if (aDate && !isNaN(aDate)) {
      return parseInt(aDate) * 1000000; // in bookmarks.html this value is in seconds, not microseconds
    }
    return Date.now();
  },

  runBatched: function runBatched(aDoc) {
    if (!aDoc) {
      return;
    }

    if (this._isImportDefaults) {
      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId, this._source);
      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId, this._source);
      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId, this._source);
    }

    let current = aDoc;
    let next;
    for (;;) {
      switch (current.nodeType) {
        case Ci.nsIDOMNode.ELEMENT_NODE:
          this._openContainer(current);
          break;
        case Ci.nsIDOMNode.TEXT_NODE:
          this._appendText(current.data);
          break;
      }
      if ((next = current.firstChild)) {
        current = next;
        continue;
      }
      for (;;) {
        if (current.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
          this._closeContainer(current);
        }
        if (current == aDoc) {
          return;
        }
        if ((next = current.nextSibling)) {
          current = next;
          break;
        }
        current = current.parentNode;
      }
    }
  },

  _walkTreeForImport: function walkTreeForImport(aDoc) {
    PlacesUtils.bookmarks.runInBatchMode(this, aDoc);
  },

  async importFromURL(href) {
    this._importPromises = [];
    await new Promise((resolve, reject) => {
      let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
                  .createInstance(Ci.nsIXMLHttpRequest);
      xhr.onload = () => {
        try {
          this._walkTreeForImport(xhr.responseXML);
          resolve();
        } catch (e) {
          reject(e);
        }
      };
      xhr.onabort = xhr.onerror = xhr.ontimeout = () => {
        reject(new Error("xmlhttprequest failed"));
      };
      xhr.open("GET", href);
      xhr.responseType = "document";
      xhr.overrideMimeType("text/html");
      xhr.send();
    });
    // TODO (bug 1095427) once converted to the new bookmarks API, methods will
    // yield, so this hack should not be needed anymore.
    try {
      await Promise.all(this._importPromises);
    } finally {
      delete this._importPromises;
    }
  },
};

function BookmarkExporter(aBookmarksTree) {
  // Create a map of the roots.
  let rootsMap = new Map();
  for (let child of aBookmarksTree.children) {
    if (child.root)
      rootsMap.set(child.root, child);
  }

  // For backwards compatibility reasons the bookmarks menu is the root, while
  // the bookmarks toolbar and unfiled bookmarks will be child items.
  this._root = rootsMap.get("bookmarksMenuFolder");

  for (let key of [ "toolbarFolder", "unfiledBookmarksFolder" ]) {
    let root = rootsMap.get(key);
    if (root.children && root.children.length > 0) {
      if (!this._root.children)
        this._root.children = [];
      this._root.children.push(root);
    }
  }
}

BookmarkExporter.prototype = {
  exportToFile: function exportToFile(aFilePath) {
    return (async () => {
      // Create a file that can be accessed by the current user only.
      let out = FileUtils.openAtomicFileOutputStream(new FileUtils.File(aFilePath));
      try {
        // We need a buffered output stream for performance.  See bug 202477.
        let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"]
                          .createInstance(Ci.nsIBufferedOutputStream);
        bufferedOut.init(out, 4096);
        try {
          // Write bookmarks in UTF-8.
          this._converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"]
                               .createInstance(Ci.nsIConverterOutputStream);
          this._converterOut.init(bufferedOut, "utf-8");
          try {
            this._writeHeader();
            await this._writeContainer(this._root);
            // Retain the target file on success only.
            bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
          } finally {
            this._converterOut.close();
            this._converterOut = null;
          }
        } finally {
          bufferedOut.close();
        }
      } finally {
        out.close();
      }
    })();
  },

  _converterOut: null,

  _write(aText) {
    this._converterOut.writeString(aText || "");
  },

  _writeAttribute(aName, aValue) {
    this._write(" " + aName + '="' + aValue + '"');
  },

  _writeLine(aText) {
    this._write(aText + "\n");
  },

  _writeHeader() {
    this._writeLine("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
    this._writeLine("<!-- This is an automatically generated file.");
    this._writeLine("     It will be read and overwritten.");
    this._writeLine("     DO NOT EDIT! -->");
    this._writeLine('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; ' +
                    'charset=UTF-8">');
    this._writeLine("<TITLE>Bookmarks</TITLE>");
  },

  async _writeContainer(aItem, aIndent = "") {
    if (aItem == this._root) {
      this._writeLine("<H1>" + escapeHtmlEntities(this._root.title) + "</H1>");
      this._writeLine("");
    } else {
      this._write(aIndent + "<DT><H3");
      this._writeDateAttributes(aItem);

      if (aItem.root === "toolbarFolder")
        this._writeAttribute("PERSONAL_TOOLBAR_FOLDER", "true");
      else if (aItem.root === "unfiledBookmarksFolder")
        this._writeAttribute("UNFILED_BOOKMARKS_FOLDER", "true");
      this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</H3>");
    }

    this._writeDescription(aItem, aIndent);

    this._writeLine(aIndent + "<DL><p>");
    if (aItem.children)
      await this._writeContainerContents(aItem, aIndent);
    if (aItem == this._root)
      this._writeLine(aIndent + "</DL>");
    else
      this._writeLine(aIndent + "</DL><p>");
  },

  async _writeContainerContents(aItem, aIndent) {
    let localIndent = aIndent + EXPORT_INDENT;

    for (let child of aItem.children) {
      if (child.annos && child.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
        this._writeLivemark(child, localIndent);
      } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
        await this._writeContainer(child, localIndent);
      } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
        this._writeSeparator(child, localIndent);
      } else {
        await this._writeItem(child, localIndent);
      }
    }
  },

  _writeSeparator(aItem, aIndent) {
    this._write(aIndent + "<HR");
    // We keep exporting separator titles, but don't support them anymore.
    if (aItem.title)
      this._writeAttribute("NAME", escapeHtmlEntities(aItem.title));
    this._write(">");
  },

  _writeLivemark(aItem, aIndent) {
    this._write(aIndent + "<DT><A");
    let feedSpec = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_FEEDURI).value;
    this._writeAttribute("FEEDURL", escapeUrl(feedSpec));
    let siteSpecAnno = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_SITEURI);
    if (siteSpecAnno)
      this._writeAttribute("HREF", escapeUrl(siteSpecAnno.value));
    this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
    this._writeDescription(aItem, aIndent);
  },

  async _writeItem(aItem, aIndent) {
    try {
      NetUtil.newURI(aItem.uri);
    } catch (ex) {
      // If the item URI is invalid, skip the item instead of failing later.
      return;
    }

    this._write(aIndent + "<DT><A");
    this._writeAttribute("HREF", escapeUrl(aItem.uri));
    this._writeDateAttributes(aItem);
    await this._writeFaviconAttribute(aItem);

    if (aItem.keyword) {
      this._writeAttribute("SHORTCUTURL", escapeHtmlEntities(aItem.keyword));
      if (aItem.postData)
        this._writeAttribute("POST_DATA", escapeHtmlEntities(aItem.postData));
    }

    if (aItem.annos && aItem.annos.some(anno => anno.name == LOAD_IN_SIDEBAR_ANNO))
      this._writeAttribute("WEB_PANEL", "true");
    if (aItem.charset)
      this._writeAttribute("LAST_CHARSET", escapeHtmlEntities(aItem.charset));
    if (aItem.tags)
      this._writeAttribute("TAGS", aItem.tags);
    this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
    this._writeDescription(aItem, aIndent);
  },

  _writeDateAttributes(aItem) {
    if (aItem.dateAdded)
      this._writeAttribute("ADD_DATE",
                           Math.floor(aItem.dateAdded / MICROSEC_PER_SEC));
    if (aItem.lastModified)
      this._writeAttribute("LAST_MODIFIED",
                           Math.floor(aItem.lastModified / MICROSEC_PER_SEC));
  },

  async _writeFaviconAttribute(aItem) {
    if (!aItem.iconuri)
      return;
    let favicon;
    try {
      favicon  = await PlacesUtils.promiseFaviconData(aItem.uri);
    } catch (ex) {
      Components.utils.reportError("Unexpected Error trying to fetch icon data");
      return;
    }

    this._writeAttribute("ICON_URI", escapeUrl(favicon.uri.spec));

    if (!favicon.uri.schemeIs("chrome") && favicon.dataLen > 0) {
      let faviconContents = "data:image/png;base64," +
        base64EncodeString(String.fromCharCode.apply(String, favicon.data));
      this._writeAttribute("ICON", faviconContents);
    }
  },

  _writeDescription(aItem, aIndent) {
    let descriptionAnno = aItem.annos &&
                          aItem.annos.find(anno => anno.name == DESCRIPTION_ANNO);
    if (descriptionAnno)
      this._writeLine(aIndent + "<DD>" + escapeHtmlEntities(descriptionAnno.value));
  }
};
PK
!<lMLLmodules/BookmarkJSONUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "BookmarkJSONUtils" ];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
  "resource://gre/modules/PlacesBackups.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");

XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", () => new TextEncoder());

/**
 * Generates an hash for the given string.
 *
 * @note The generated hash is returned in base64 form.  Mind the fact base64
 * is case-sensitive if you are going to reuse this code.
 */
function generateHash(aString) {
  let cryptoHash = Cc["@mozilla.org/security/hash;1"]
                     .createInstance(Ci.nsICryptoHash);
  cryptoHash.init(Ci.nsICryptoHash.MD5);
  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
                       .createInstance(Ci.nsIStringInputStream);
  stringStream.data = aString;
  cryptoHash.updateFromStream(stringStream, -1);
  // base64 allows the '/' char, but we can't use it for filenames.
  return cryptoHash.finish(true).replace(/\//g, "-");
}

this.BookmarkJSONUtils = Object.freeze({
  /**
   * Import bookmarks from a url.
   *
   * @param aSpec
   *        url of the bookmark data.
   * @param aReplace
   *        Boolean if true, replace existing bookmarks, else merge.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  importFromURL: function BJU_importFromURL(aSpec, aReplace) {
    return (async function() {
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN);
      try {
        let importer = new BookmarkImporter(aReplace);
        await importer.importFromURL(aSpec);

        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS);
      } catch (ex) {
        Cu.reportError("Failed to restore bookmarks from " + aSpec + ": " + ex);
        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED);
      }
    })();
  },

  /**
   * Restores bookmarks and tags from a JSON file.
   * @note any item annotated with "places/excludeFromBackup" won't be removed
   *       before executing the restore.
   *
   * @param aFilePath
   *        OS.File path string of bookmarks in JSON or JSONlz4 format to be restored.
   * @param aReplace
   *        Boolean if true, replace existing bookmarks, else merge.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   * @deprecated passing an nsIFile is deprecated
   */
  importFromFile: function BJU_importFromFile(aFilePath, aReplace) {
    if (aFilePath instanceof Ci.nsIFile) {
      Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.importFromFile " +
                         "is deprecated. Please use an OS.File path string instead.",
                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
      aFilePath = aFilePath.path;
    }

    return (async function() {
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN);
      try {
        if (!(await OS.File.exists(aFilePath)))
          throw new Error("Cannot restore from nonexisting json file");

        let importer = new BookmarkImporter(aReplace);
        if (aFilePath.endsWith("jsonlz4")) {
          await importer.importFromCompressedFile(aFilePath);
        } else {
          await importer.importFromURL(OS.Path.toFileURI(aFilePath));
        }
        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS);
      } catch (ex) {
        Cu.reportError("Failed to restore bookmarks from " + aFilePath + ": " + ex);
        notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED);
        throw ex;
      }
    })();
  },

  /**
   * Serializes bookmarks using JSON, and writes to the supplied file path.
   *
   * @param aFilePath
   *        OS.File path string for the bookmarks file to be created.
   * @param [optional] aOptions
   *        Object containing options for the export:
   *         - failIfHashIs: if the generated file would have the same hash
   *                         defined here, will reject with ex.becauseSameHash
   *         - compress: if true, writes file using lz4 compression
   * @return {Promise}
   * @resolves once the file has been created, to an object with the
   *           following properties:
   *            - count: number of exported bookmarks
   *            - hash: file hash for contents comparison
   * @rejects JavaScript exception.
   * @deprecated passing an nsIFile is deprecated
   */
  exportToFile: function BJU_exportToFile(aFilePath, aOptions = {}) {
    if (aFilePath instanceof Ci.nsIFile) {
      Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.exportToFile " +
                         "is deprecated. Please use an OS.File path string instead.",
                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
      aFilePath = aFilePath.path;
    }
    return (async function() {
      let [bookmarks, count] = await PlacesBackups.getBookmarksTree();
      let startTime = Date.now();
      let jsonString = JSON.stringify(bookmarks);
      // Report the time taken to convert the tree to JSON.
      try {
        Services.telemetry
                .getHistogramById("PLACES_BACKUPS_TOJSON_MS")
                .add(Date.now() - startTime);
      } catch (ex) {
        Components.utils.reportError("Unable to report telemetry.");
      }

      let hash = generateHash(jsonString);

      if (hash === aOptions.failIfHashIs) {
        let e = new Error("Hash conflict");
        e.becauseSameHash = true;
        throw e;
      }

      // Do not write to the tmp folder, otherwise if it has a different
      // filesystem writeAtomic will fail.  Eventual dangling .tmp files should
      // be cleaned up by the caller.
      let writeOptions = { tmpPath: OS.Path.join(aFilePath + ".tmp") };
      if (aOptions.compress)
        writeOptions.compression = "lz4";

      await OS.File.writeAtomic(aFilePath, jsonString, writeOptions);
      return { count, hash };
    })();
  }
});

function BookmarkImporter(aReplace) {
  this._replace = aReplace;
  // The bookmark change source, used to determine the sync status and change
  // counter.
  this._source = aReplace ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
                            PlacesUtils.bookmarks.SOURCE_IMPORT;
}
BookmarkImporter.prototype = {
  /**
   * Import bookmarks from a url.
   *
   * @param aSpec
   *        url of the bookmark data.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  importFromURL(spec) {
    return new Promise((resolve, reject) => {
      let streamObserver = {
        onStreamComplete: (aLoader, aContext, aStatus, aLength, aResult) => {
          let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                          createInstance(Ci.nsIScriptableUnicodeConverter);
          converter.charset = "UTF-8";
          try {
            let jsonString = converter.convertFromByteArray(aResult,
                                                            aResult.length);
            resolve(this.importFromJSON(jsonString));
          } catch (ex) {
            Cu.reportError("Failed to import from URL: " + ex);
            reject(ex);
          }
        }
      };

      let uri = NetUtil.newURI(spec);
      let channel = NetUtil.newChannel({
        uri,
        loadUsingSystemPrincipal: true
      });
      let streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
                           .createInstance(Ci.nsIStreamLoader);
      streamLoader.init(streamObserver);
      channel.asyncOpen2(streamLoader);
    });
  },

  /**
   * Import bookmarks from a compressed file.
   *
   * @param aFilePath
   *        OS.File path string of the bookmark data.
   *
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  importFromCompressedFile: async function BI_importFromCompressedFile(aFilePath) {
      let aResult = await OS.File.read(aFilePath, { compression: "lz4" });
      let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                        createInstance(Ci.nsIScriptableUnicodeConverter);
      converter.charset = "UTF-8";
      let jsonString = converter.convertFromByteArray(aResult, aResult.length);
      await this.importFromJSON(jsonString);
  },

  /**
   * Import bookmarks from a JSON string.
   *
   * @param {String} aString JSON string of serialized bookmark data.
   * @return {Promise}
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromJSON(aString) {
    let nodes =
      PlacesUtils.unwrapNodes(aString, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);

    if (nodes.length == 0 || !nodes[0].children ||
        nodes[0].children.length == 0) {
      return;
    }

    // Change to nodes[0].children as we don't import the root, and also filter
    // out any obsolete "tagsFolder" sections.
    nodes = nodes[0].children.filter(node => !node.root || node.root != "tagsFolder");

    // If we're replacing, then erase existing bookmarks first.
    if (this._replace) {
      await PlacesBackups.eraseEverythingIncludingUserRoots({ source: this._source });
    }

    let folderIdToGuidMap = {};
    let searchGuids = [];

    // Now do some cleanup on the imported nodes so that the various guids
    // match what we need for insertTree, and we also have mappings of folders
    // so we can repair any searches after inserting the bookmarks (see bug 824502).
    for (let node of nodes) {
      if (!node.children || node.children.length == 0)
        continue;  // Nothing to restore for this root

      // Ensure we set the source correctly.
      node.source = this._source;

      // Translate the node for insertTree.
      let [folders, searches] = translateTreeTypes(node);

      folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
      searchGuids = searchGuids.concat(searches);
    }

    // Now we can add the actual nodes to the database.
    for (let node of nodes) {
      // Drop any nodes without children, we can't insert them.
      if (!node.children || node.children.length == 0) {
        continue;
      }

      // Places is moving away from supporting user-defined folders at the top
      // of the tree, however, until we have a migration strategy we need to
      // ensure any non-built-in folders are created (xref bug 1310299).
      if (!PlacesUtils.bookmarks.userContentRoots.includes(node.guid)) {
        node.parentGuid = PlacesUtils.bookmarks.rootGuid;
        await PlacesUtils.bookmarks.insert(node);
      }

      await PlacesUtils.bookmarks.insertTree(node);

      // Now add any favicons.
      try {
        insertFaviconsForTree(node);
      } catch (ex) {
        Cu.reportError(`Failed to insert favicons: ${ex}`);
      }
    }

    // Now update any bookmarks with a place: search that contain an index to
    // a folder id.
    for (let guid of searchGuids) {
      let searchBookmark = await PlacesUtils.bookmarks.fetch(guid);
      let url = await fixupQuery(searchBookmark.url, folderIdToGuidMap);
      if (url != searchBookmark.url) {
        await PlacesUtils.bookmarks.update({ guid, url, source: this._source });
      }
    }
  },
};

function notifyObservers(topic) {
  Services.obs.notifyObservers(null, topic, "json");
}

/**
 * Replaces imported folder ids with their local counterparts in a place: URI.
 *
 * @param   {nsIURI} aQueryURI
 *          A place: URI with folder ids.
 * @param   {Object} aFolderIdMap
 *          An array mapping of old folder IDs to new folder GUIDs.
 * @return {String} the fixed up URI if all matched. If some matched, it returns
 *         the URI with only the matching folders included. If none matched
 *         it returns the input URI unchanged.
 */
async function fixupQuery(aQueryURI, aFolderIdMap) {
  const reGlobal = /folder=([0-9]+)/g;
  const re = /([0-9]+)/;

  // Unfortunately .replace can't handle async functions. Therefore,
  // we find the folder guids we need to know the ids for first, then
  // do the async request, and finally replace everything in one go.
  let uri = aQueryURI.href;
  let found = uri.match(reGlobal);
  if (!found) {
    return uri;
  }

  let queryFolderGuids = [];
  for (let folderString of found) {
    let existingFolderId = folderString.match(re)[0];
    queryFolderGuids.push(aFolderIdMap[existingFolderId])
  }

  let newFolderIds = await PlacesUtils.promiseManyItemIds(queryFolderGuids);
  let convert = function(str, p1) {
    return "folder=" + newFolderIds.get(aFolderIdMap[p1]);
  }
  return uri.replace(reGlobal, convert);
}

/**
 * A mapping of root folder names to Guids. To help fixupRootFolderGuid.
 */
const rootToFolderGuidMap = {
  "placesRoot": PlacesUtils.bookmarks.rootGuid,
  "bookmarksMenuFolder": PlacesUtils.bookmarks.menuGuid,
  "unfiledBookmarksFolder": PlacesUtils.bookmarks.unfiledGuid,
  "toolbarFolder": PlacesUtils.bookmarks.toolbarGuid,
  "mobileFolder": PlacesUtils.bookmarks.mobileGuid
};

/**
 * Updates a bookmark node from the json version to the places GUID. This
 * will only change GUIDs for the built-in folders. Other folders will remain
 * unchanged.
 *
 * @param {Object} A bookmark node that is updated with the new GUID if necessary.
 */
function fixupRootFolderGuid(node) {
  if (!node.guid && node.root && node.root in rootToFolderGuidMap) {
    node.guid = rootToFolderGuidMap[node.root];
  }
}

/**
 * Translates the JSON types for a node and its children into Places compatible
 * types. Also handles updating of other parameters e.g. dateAdded and lastModified.
 *
 * @param {Object} node A node to be updated. If it contains children, they will
 *                      be updated as well.
 * @return {Array} An array containing two items:
 *       - {Object} A map of current folder ids to GUIDS
 *       - {Array} An array of GUIDs for nodes that contain query URIs
 */
function translateTreeTypes(node) {
  let folderIdToGuidMap = {};
  let searchGuids = [];

  // Do the uri fixup first, so we can be consistent in this function.
  if (node.uri) {
    node.url = node.uri;
    delete node.uri;
  }

  switch (node.type) {
    case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
      node.type = PlacesUtils.bookmarks.TYPE_FOLDER;

      // Older type mobile folders have a random guid with an annotation. We need
      // to make sure those go into the proper mobile folder.
      let isMobileFolder = node.annos &&
                           node.annos.some(anno => anno.name == PlacesUtils.MOBILE_ROOT_ANNO);
      if (isMobileFolder) {
        node.guid = PlacesUtils.bookmarks.mobileGuid;
      } else {
        // In case the Guid is broken, we need to fix it up.
        fixupRootFolderGuid(node);
      }

      // Record the current id and the guid so that we can update any search
      // queries later.
      folderIdToGuidMap[node.id] = node.guid;
      break;
    case PlacesUtils.TYPE_X_MOZ_PLACE:
      node.type = PlacesUtils.bookmarks.TYPE_BOOKMARK;

      if (node.url && node.url.substr(0, 6) == "place:") {
        searchGuids.push(node.guid);
      }

      break;
    case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
      node.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
      if ("title" in node) {
        delete node.title;
      }
      break;
    default:
      // TODO We should handle this in a more robust fashion, see bug 1373610.
      Cu.reportError(`Unexpected bookmark type ${node.type}`);
      break;
  }

  if (node.dateAdded) {
    node.dateAdded = PlacesUtils.toDate(node.dateAdded);
  }

  if (node.lastModified) {
    let lastModified = PlacesUtils.toDate(node.lastModified);
    // Ensure we get a last modified date that's later or equal to the dateAdded
    // so that we don't upset the Bookmarks API.
    if (lastModified >= node.dataAdded) {
      node.lastModified = lastModified;
    } else {
      delete node.lastModified;
    }
  }

  if (node.tags) {
     // Separate any tags into an array, and ignore any that are too long.
    node.tags = node.tags.split(",").filter(aTag =>
      aTag.length > 0 && aTag.length <= Ci.nsITaggingService.MAX_TAG_LENGTH);

    // If we end up with none, then delete the property completely.
    if (!node.tags.length) {
      delete node.tags;
    }
  }

  // Sometimes postData can be null, so delete it to make the validators happy.
  if (node.postData == null) {
    delete node.postData;
  }

  // Now handle any children.
  if (!node.children) {
    return [folderIdToGuidMap, searchGuids];
  }

  // First sort the children by index.
  node.children = node.children.sort((a, b) => {
    return a.index - b.index;
  });

  // Now do any adjustments required for the children.
  for (let child of node.children) {
    let [folders, searches] = translateTreeTypes(child);
    folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
    searchGuids = searchGuids.concat(searches);
  }

  return [folderIdToGuidMap, searchGuids];
}

/**
 * Handles inserting favicons into the database for a bookmark node.
 * It is assumed the node has already been inserted into the bookmarks
 * database.
 *
 * @param {Object} node The bookmark node for icons to be inserted.
 */
function insertFaviconForNode(node) {
  if (node.icon) {
    try {
      // Create a fake faviconURI to use (FIXME: bug 523932)
      let faviconURI = Services.io.newURI("fake-favicon-uri:" + node.url);
      PlacesUtils.favicons.replaceFaviconDataFromDataURL(
        faviconURI, node.icon, 0,
        Services.scriptSecurityManager.getSystemPrincipal());
      PlacesUtils.favicons.setAndFetchFaviconForPage(
        Services.io.newURI(node.url), faviconURI, false,
        PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
        Services.scriptSecurityManager.getSystemPrincipal());
    } catch (ex) {
      Components.utils.reportError("Failed to import favicon data:" + ex);
    }
  }

  if (!node.iconUri) {
    return;
  }

  try {
    PlacesUtils.favicons.setAndFetchFaviconForPage(
      Services.io.newURI(node.url), Services.io.newURI(node.iconUri), false,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
      Services.scriptSecurityManager.getSystemPrincipal());
  } catch (ex) {
    Components.utils.reportError("Failed to import favicon URI:" + ex);
  }
}

/**
 * Handles inserting favicons into the database for a bookmark tree - a node
 * and its children.
 *
 * It is assumed the nodes have already been inserted into the bookmarks
 * database.
 *
 * @param {Object} nodeTree The bookmark node tree for icons to be inserted.
 */
function insertFaviconsForTree(nodeTree) {
  insertFaviconForNode(nodeTree);

  if (nodeTree.children) {
    for (let child of nodeTree.children) {
      insertFaviconsForTree(child);
    }
  }
}
PK
!<3̛vvmodules/Bookmarks.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * This module provides an asynchronous API for managing bookmarks.
 *
 * Bookmarks are organized in a tree structure, and include URLs, folders and
 * separators.  Multiple bookmarks for the same URL are allowed.
 *
 * Note that if you are handling bookmarks operations in the UI, you should
 * not use this API directly, but rather use PlacesTransactions.jsm, so that
 * any operation is undo/redo-able.
 *
 * Each bookmark-item is represented by an object having the following
 * properties:
 *
 *  - guid (string)
 *      The globally unique identifier of the item.
 *  - parentGuid (string)
 *      The globally unique identifier of the folder containing the item.
 *      This will be an empty string for the Places root folder.
 *  - index (number)
 *      The 0-based position of the item in the parent folder.
 *  - dateAdded (Date)
 *      The time at which the item was added.
 *  - lastModified (Date)
 *      The time at which the item was last modified.
 *  - type (number)
 *      The item's type, either TYPE_BOOKMARK, TYPE_FOLDER or TYPE_SEPARATOR.
 *
 *  The following properties are only valid for URLs or folders.
 *
 *  - title (string)
 *      The item's title, if any.  Empty titles and null titles are considered
 *      the same. Titles longer than DB_TITLE_LENGTH_MAX will be truncated.
 *
 *  The following properties are only valid for URLs:
 *
 *  - url (URL, href or nsIURI)
 *      The item's URL.  Note that while input objects can contains either
 *      an URL object, an href string, or an nsIURI, output objects will always
 *      contain an URL object.
 *      An URL cannot be longer than DB_URL_LENGTH_MAX, methods will throw if a
 *      longer value is provided.
 *
 * Each successful operation notifies through the nsINavBookmarksObserver
 * interface.  To listen to such notifications you must register using
 * nsINavBookmarksService addObserver and removeObserver methods.
 * Note that bookmark addition or order changes won't notify onItemMoved for
 * items that have their indexes changed.
 * Similarly, lastModified changes not done explicitly (like changing another
 * property) won't fire an onItemChanged notification for the lastModified
 * property.
 * @see nsINavBookmarkObserver
 */

this.EXPORTED_SYMBOLS = [ "Bookmarks" ];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.importGlobalProperties(["URL"]);

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");

// This is an helper to temporarily cover the need to know the tags folder
// itemId until bug 424160 is fixed.  This exists so that startup paths won't
// pay the price to initialize the bookmarks service just to fetch this value.
// If the method is already initing the bookmarks service for other reasons
// (most of the writing methods will invoke getObservers() already) it can
// directly use the PlacesUtils.tagsFolderId property.
var gTagsFolderId;
async function promiseTagsFolderId() {
  if (gTagsFolderId)
    return gTagsFolderId;
  let db =  await PlacesUtils.promiseDBConnection();
  let rows = await db.execute(
    "SELECT id FROM moz_bookmarks WHERE guid = :guid",
    { guid: Bookmarks.tagsGuid }
  );
  return gTagsFolderId = rows[0].getResultByName("id");
}

const MATCH_ANYWHERE_UNMODIFIED = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED;
const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;

var Bookmarks = Object.freeze({
  /**
   * Item's type constants.
   * These should stay consistent with nsINavBookmarksService.idl
   */
  TYPE_BOOKMARK: 1,
  TYPE_FOLDER: 2,
  TYPE_SEPARATOR: 3,

  /**
   * Sync status constants, stored for each item.
   */
  SYNC_STATUS: {
    UNKNOWN: Ci.nsINavBookmarksService.SYNC_STATUS_UNKNOWN,
    NEW: Ci.nsINavBookmarksService.SYNC_STATUS_NEW,
    NORMAL: Ci.nsINavBookmarksService.SYNC_STATUS_NORMAL,
  },

  /**
   * Default index used to append a bookmark-item at the end of a folder.
   * This should stay consistent with nsINavBookmarksService.idl
   */
  DEFAULT_INDEX: -1,

  /**
   * Bookmark change source constants, passed as optional properties and
   * forwarded to observers. See nsINavBookmarksService.idl for an explanation.
   */
  SOURCES: {
    DEFAULT: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
    SYNC: Ci.nsINavBookmarksService.SOURCE_SYNC,
    IMPORT: Ci.nsINavBookmarksService.SOURCE_IMPORT,
    IMPORT_REPLACE: Ci.nsINavBookmarksService.SOURCE_IMPORT_REPLACE,
    SYNC_REPARENT_REMOVED_FOLDER_CHILDREN: Ci.nsINavBookmarksService.SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
  },

  /**
   * Special GUIDs associated with bookmark roots.
   * It's guaranteed that the roots will always have these guids.
   */

   rootGuid:    "root________",
   menuGuid:    "menu________",
   toolbarGuid: "toolbar_____",
   unfiledGuid: "unfiled_____",
   mobileGuid:  "mobile______",

   // With bug 424160, tags will stop being bookmarks, thus this root will
   // be removed.  Do not rely on this, rather use the tagging service API.
   tagsGuid:    "tags________",

   /**
    * The GUIDs of the user content root folders that we support, for easy access
    * as a set.
    */
   userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],

  /**
   * Inserts a bookmark-item into the bookmarks tree.
   *
   * For creating a bookmark, the following set of properties is required:
   *  - type
   *  - parentGuid
   *  - url, only for bookmarked URLs
   *
   * If an index is not specified, it defaults to appending.
   * It's also possible to pass a non-existent GUID to force creation of an
   * item with the given GUID, but unless you have a very sound reason, such as
   * an undo manager implementation or synchronization, don't do that.
   *
   * Note that any known properties that don't apply to the specific item type
   * cause an exception.
   *
   * @param info
   *        object representing a bookmark-item.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an object representing the created bookmark.
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insert(info) {
    let now = new Date();
    let addedTime = (info && info.dateAdded) || now;
    let modTime = addedTime;
    if (addedTime > now) {
      modTime = now;
    }
    let insertInfo = validateBookmarkObject("Bookmarks.jsm: insert", info,
      { type: { defaultValue: this.TYPE_BOOKMARK },
        index: { defaultValue: this.DEFAULT_INDEX },
        url: { requiredIf: b => b.type == this.TYPE_BOOKMARK,
               validIf: b => b.type == this.TYPE_BOOKMARK },
        parentGuid: { required: true },
        title: { defaultValue: "",
                 validIf: b => b.type == this.TYPE_BOOKMARK ||
                               b.type == this.TYPE_FOLDER ||
                               b.title === "" },
        dateAdded: { defaultValue: addedTime },
        lastModified: { defaultValue: modTime,
                        validIf: b => b.lastModified >= now || (b.dateAdded && b.lastModified >= b.dateAdded) },
        source: { defaultValue: this.SOURCES.DEFAULT }
      });

    return (async () => {
      // Ensure the parent exists.
      let parent = await fetchBookmark({ guid: insertInfo.parentGuid });
      if (!parent)
        throw new Error("parentGuid must be valid");

      // Set index in the appending case.
      if (insertInfo.index == this.DEFAULT_INDEX ||
          insertInfo.index > parent._childCount) {
        insertInfo.index = parent._childCount;
      }

      let item = await insertBookmark(insertInfo, parent);

      // Notify onItemAdded to listeners.
      let observers = PlacesUtils.bookmarks.getObservers();
      // We need the itemId to notify, though once the switch to guids is
      // complete we may stop using it.
      let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
      let itemId = await PlacesUtils.promiseItemId(item.guid);

      // Pass tagging information for the observers to skip over these notifications when needed.
      let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
      let isTagsFolder = parent._id == PlacesUtils.tagsFolderId;
      notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
                                         item.type, uri, item.title,
                                         PlacesUtils.toPRTime(item.dateAdded), item.guid,
                                         item.parentGuid, item.source ],
                                       { isTagging: isTagging || isTagsFolder });

      // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
      if (isTagging) {
        for (let entry of (await fetchBookmarksByURL(item, true))) {
          notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                               PlacesUtils.toPRTime(entry.lastModified),
                                               entry.type, entry._parentId,
                                               entry.guid, entry.parentGuid,
                                               "", item.source ]);
        }
      }

      // Remove non-enumerable properties.
      delete item.source;
      return Object.assign({}, item);
    })();
  },


  /**
   * Inserts a bookmark-tree into the existing bookmarks tree.
   *
   * All the specified folders and bookmarks will be inserted as new, even
   * if duplicates. There's no merge support at this time.
   *
   * The input should be of the form:
   * {
   *   guid: "<some-existing-guid-to-use-as-parent>",
   *   source: "<some valid source>", (optional)
   *   children: [
   *     ... valid bookmark objects.
   *   ]
   * }
   *
   * Children will be appended to any existing children of the parent
   * that is specified. The source specified on the root of the tree
   * will be used for all the items inserted. Any indices or custom parentGuids
   * set on children will be ignored and overwritten.
   *
   * @param {Object} tree
   *        object representing a tree of bookmark items to insert.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an object representing the created bookmark.
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insertTree(tree) {
    if (!tree || typeof tree != "object") {
      throw new Error("Should be provided a valid tree object.");
    }

    if (!Array.isArray(tree.children) || !tree.children.length) {
      throw new Error("Should have a non-zero number of children to insert.");
    }

    if (!PlacesUtils.isValidGuid(tree.guid)) {
      throw new Error(`The parent guid is not valid (${tree.guid} ${tree.title}).`);
    }

    if (tree.guid == this.rootGuid) {
      throw new Error("Can't insert into the root.");
    }

    if (tree.guid == this.tagsGuid) {
      throw new Error("Can't use insertTree to insert tags.");
    }

    if (tree.hasOwnProperty("source") &&
        !Object.values(this.SOURCES).includes(tree.source)) {
      throw new Error("Can't use source value " + tree.source);
    }

    // Serialize the tree into an array of items to insert into the db.
    let insertInfos = [];
    let insertLivemarkInfos = [];
    let urlsThatMightNeedPlaces = [];

    // We want to use the same 'last added' time for all the entries
    // we import (so they won't differ by a few ms based on where
    // they are in the tree, and so we don't needlessly construct
    // multiple dates).
    let fallbackLastAdded = new Date();

    const {TYPE_BOOKMARK, TYPE_FOLDER, SOURCES} = this;

    // Reuse the 'source' property for all the entries.
    let source = tree.source || SOURCES.DEFAULT;

    function appendInsertionInfoForInfoArray(infos, indexToUse, parentGuid) {
      // We want to keep the index of items that will be inserted into the root
      // NULL, and then use a subquery to select the right index, to avoid
      // races where other consumers might add items between when we determine
      // the index and when we insert. However, the validator does not allow
      // NULL values in in the index, so we fake it while validating and then
      // correct later. Keep track of whether we're doing this:
      let shouldUseNullIndices = false;
      if (indexToUse === null) {
        shouldUseNullIndices = true;
        indexToUse = 0;
      }

      // When a folder gets an item added, its last modified date is updated
      // to be equal to the date we added the item (if that date is newer).
      // Because we're inserting a tree, we keep track of this date for the
      // loop, updating it for inserted items as well as from any subfolders
      // we insert.
      let lastAddedForParent = new Date(0);
      for (let info of infos) {
        // Add a guid if none is present.
        if (!info.hasOwnProperty("guid")) {
          info.guid = PlacesUtils.history.makeGuid();
        }
        // Set the correct parent guid.
        info.parentGuid = parentGuid;
        // Ensure to use the same date for dateAdded and lastModified, even if
        // dateAdded may be imposed by the caller.
        let time = (info && info.dateAdded) || fallbackLastAdded;
        let insertInfo = validateBookmarkObject("Bookmarks.jsm: insertTree",
                                                info, {
          type: { defaultValue: TYPE_BOOKMARK },
          url: { requiredIf: b => b.type == TYPE_BOOKMARK,
                 validIf: b => b.type == TYPE_BOOKMARK },
          parentGuid: { required: true },
          title: { defaultValue: "",
                   validIf: b => b.type == TYPE_BOOKMARK ||
                                 b.type == TYPE_FOLDER ||
                                 b.title === "" },
          dateAdded: { defaultValue: time,
                       validIf: b => !b.lastModified ||
                                      b.dateAdded <= b.lastModified },
          lastModified: { defaultValue: time,
                          validIf: b => (!b.dateAdded && b.lastModified >= time) ||
                                        (b.dateAdded && b.lastModified >= b.dateAdded) },
          index: { replaceWith: indexToUse++ },
          source: { replaceWith: source },
          annos: {},
          keyword: { validIf: b => b.type == TYPE_BOOKMARK },
          charset: { validIf: b => b.type == TYPE_BOOKMARK },
          postData: { validIf: b => b.type == TYPE_BOOKMARK },
          tags: { validIf: b => b.type == TYPE_BOOKMARK },
          children: { validIf: b => b.type == TYPE_FOLDER && Array.isArray(b.children) }
        });

        if (shouldUseNullIndices) {
          insertInfo.index = null;
        }
        // Store the URL if this is a bookmark, so we can ensure we create an
        // entry in moz_places for it.
        if (insertInfo.type == Bookmarks.TYPE_BOOKMARK) {
          urlsThatMightNeedPlaces.push(insertInfo.url);
        }

        // As we don't track indexes for children of root folders, and we
        // insert livemarks separately, we create a temporary placeholder in
        // the bookmarks, and later we'll replace it by the real livemark.
        if (isLivemark(insertInfo)) {
          // Make the current insertInfo item a placeholder.
          let livemarkInfo = Object.assign({}, insertInfo);

          // Delete the annotations that make it a livemark.
          delete insertInfo.annos;

          // Now save the livemark info for later.
          insertLivemarkInfos.push(livemarkInfo);
        }

        insertInfos.push(insertInfo);
        // Process any children. We have to use info.children here rather than
        // insertInfo.children because validateBookmarkObject doesn't copy over
        // the children ref, as the default bookmark validators object doesn't
        // know about children.
        if (info.children) {
          // start children of this item off at index 0.
          let childrenLastAdded = appendInsertionInfoForInfoArray(info.children, 0, insertInfo.guid);
          if (childrenLastAdded > insertInfo.lastModified) {
            insertInfo.lastModified = childrenLastAdded;
          }
          if (childrenLastAdded > lastAddedForParent) {
            lastAddedForParent = childrenLastAdded;
          }
        }

        // Ensure we track what time to update the parent to.
        if (insertInfo.dateAdded > lastAddedForParent) {
          lastAddedForParent = insertInfo.dateAdded;
        }
      }
      return lastAddedForParent;
    }

    // We want to validate synchronously, but we can't know the index at which
    // we're inserting into the parent. We just use NULL instead,
    // and the SQL query with which we insert will update it as necessary.
    let lastAddedForParent = appendInsertionInfoForInfoArray(tree.children, null, tree.guid);

    return (async function() {
      let treeParent = await fetchBookmark({ guid: tree.guid });
      if (!treeParent) {
        throw new Error("The parent you specified doesn't exist.");
      }

      if (treeParent._parentId == PlacesUtils.tagsFolderId) {
        throw new Error("Can't use insertTree to insert tags.");
      }

      await insertBookmarkTree(insertInfos, source, treeParent,
                               urlsThatMightNeedPlaces, lastAddedForParent);

      await insertLivemarkData(insertLivemarkInfos);

      // Now update the indices of root items in the objects we return.
      // These may be wrong if someone else modified the table between
      // when we fetched the parent and inserted our items, but the actual
      // inserts will have been correct, and we don't want to query the DB
      // again if we don't have to. bug 1347230 covers improving this.
      let rootIndex = treeParent._childCount;
      for (let insertInfo of insertInfos) {
        if (insertInfo.parentGuid == tree.guid) {
          insertInfo.index += rootIndex++;
        }
      }
      // We need the itemIds to notify, though once the switch to guids is
      // complete we may stop using them.
      let itemIdMap = await PlacesUtils.promiseManyItemIds(insertInfos.map(info => info.guid));
      // Notify onItemAdded to listeners.
      let observers = PlacesUtils.bookmarks.getObservers();
      for (let i = 0; i < insertInfos.length; i++) {
        let item = insertInfos[i];
        let itemId = itemIdMap.get(item.guid);
        let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
        // For sub-folders, we need to make sure their children have the correct parent ids.
        let parentId;
        if (item.parentGuid === treeParent.guid) {
          // This is a direct child of the tree parent, so we can use the
          // existing parent's id.
          parentId = treeParent._id;
        } else {
          // This is a parent folder that's been updated, so we need to
          // use the new item id.
          parentId = itemIdMap.get(item.parentGuid);
        }

        notify(observers, "onItemAdded", [ itemId, parentId, item.index,
                                           item.type, uri, item.title,
                                           PlacesUtils.toPRTime(item.dateAdded), item.guid,
                                           item.parentGuid, item.source ],
                                         { isTagging: false });
        // Remove non-enumerable properties.
        delete item.source;

        // Note, annotations for livemark data are deleted from insertInfo
        // within appendInsertionInfoForInfoArray, so we won't be duplicating
        // the insertions here.
        await handleBookmarkItemSpecialData(itemId, item);

        insertInfos[i] = Object.assign({}, item);
      }
      return insertInfos;
    })();
  },

  /**
   * Updates a bookmark-item.
   *
   * Only set the properties which should be changed (undefined properties
   * won't be taken into account).
   * Moreover, the item's type or dateAdded cannot be changed, since they are
   * immutable after creation.  Trying to change them will reject.
   *
   * Note that any known properties that don't apply to the specific item type
   * cause an exception.
   *
   * @param info
   *        object representing a bookmark-item, as defined above.
   *
   * @return {Promise} resolved when the update is complete.
   * @resolves to an object representing the updated bookmark.
   * @rejects if it's not possible to update the given bookmark.
   * @throws if the arguments are invalid.
   */
  update(info) {
    // The info object is first validated here to ensure it's consistent, then
    // it's compared to the existing item to remove any properties that don't
    // need to be updated.
    let updateInfo = validateBookmarkObject("Bookmarks.jsm: update", info,
      { guid: { required: true },
        index: { requiredIf: b => b.hasOwnProperty("parentGuid"),
                 validIf: b => b.index >= 0 || b.index == this.DEFAULT_INDEX },
        source: { defaultValue: this.SOURCES.DEFAULT }
      });

    // There should be at last one more property in addition to guid and source.
    if (Object.keys(updateInfo).length < 3)
      throw new Error("Not enough properties to update");

    return (async () => {
      // Ensure the item exists.
      let item = await fetchBookmark(updateInfo);
      if (!item)
        throw new Error("No bookmarks found for the provided GUID");
      if (updateInfo.hasOwnProperty("type") && updateInfo.type != item.type)
        throw new Error("The bookmark type cannot be changed");

      // Remove any property that will stay the same.
      removeSameValueProperties(updateInfo, item);
      // Check if anything should still be updated.
      if (Object.keys(updateInfo).length < 3) {
        // Remove non-enumerable properties.
        return Object.assign({}, item);
      }
      const now = new Date();
      let lastModifiedDefault = now;
      // In the case where `dateAdded` is specified, but `lastModified` is not,
      // we only update `lastModified` if it is older than the new `dateAdded`.
      if (!("lastModified" in updateInfo) &&
          "dateAdded" in updateInfo) {
        lastModifiedDefault = new Date(Math.max(item.lastModified, updateInfo.dateAdded));
      }
      updateInfo = validateBookmarkObject("Bookmarks.jsm: update", updateInfo,
        { url: { validIf: () => item.type == this.TYPE_BOOKMARK },
          title: { validIf: () => [ this.TYPE_BOOKMARK,
                                    this.TYPE_FOLDER ].includes(item.type) },
          lastModified: { defaultValue: lastModifiedDefault,
                          validIf: b => b.lastModified >= now ||
                                        b.lastModified >= (b.dateAdded || item.dateAdded) },
          dateAdded: { defaultValue: item.dateAdded }
        });

      return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: update",
        async db => {
        let parent;
        if (updateInfo.hasOwnProperty("parentGuid")) {
          if (item.type == this.TYPE_FOLDER) {
            // Make sure we are not moving a folder into itself or one of its
            // descendants.
            let rows = await db.executeCached(
              `WITH RECURSIVE
               descendants(did) AS (
                 VALUES(:id)
                 UNION ALL
                 SELECT id FROM moz_bookmarks
                 JOIN descendants ON parent = did
                 WHERE type = :type
               )
               SELECT guid FROM moz_bookmarks
               WHERE id IN descendants
              `, { id: item._id, type: this.TYPE_FOLDER });
            if (rows.map(r => r.getResultByName("guid")).includes(updateInfo.parentGuid))
              throw new Error("Cannot insert a folder into itself or one of its descendants");
          }

          parent = await fetchBookmark({ guid: updateInfo.parentGuid });
          if (!parent)
            throw new Error("No bookmarks found for the provided parentGuid");
        }

        if (updateInfo.hasOwnProperty("index")) {
          // If at this point we don't have a parent yet, we are moving into
          // the same container.  Thus we know it exists.
          if (!parent)
            parent = await fetchBookmark({ guid: item.parentGuid });

          if (updateInfo.index >= parent._childCount ||
              updateInfo.index == this.DEFAULT_INDEX) {
             updateInfo.index = parent._childCount;

            // Fix the index when moving within the same container.
            if (parent.guid == item.parentGuid)
               updateInfo.index--;
          }
        }

        let updatedItem = await updateBookmark(updateInfo, item, parent);

        if (item.type == this.TYPE_BOOKMARK &&
            item.url.href != updatedItem.url.href) {
          // ...though we don't wait for the calculation.
          updateFrecency(db, [item.url]).catch(Cu.reportError);
          updateFrecency(db, [updatedItem.url]).catch(Cu.reportError);
        }

        // Notify onItemChanged to listeners.
        let observers = PlacesUtils.bookmarks.getObservers();
        // For lastModified, we only care about the original input, since we
        // should not notify implciit lastModified changes.
        if (info.hasOwnProperty("lastModified") &&
            updateInfo.hasOwnProperty("lastModified") &&
            item.lastModified != updatedItem.lastModified) {
          notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
                                               false,
                                               `${PlacesUtils.toPRTime(updatedItem.lastModified)}`,
                                               PlacesUtils.toPRTime(updatedItem.lastModified),
                                               updatedItem.type,
                                               updatedItem._parentId,
                                               updatedItem.guid,
                                               updatedItem.parentGuid, "",
                                               updatedItem.source ]);
        }
        if (info.hasOwnProperty("dateAdded") &&
            updateInfo.hasOwnProperty("dateAdded") &&
            item.dateAdded != updatedItem.dateAdded) {
          notify(observers, "onItemChanged", [ updatedItem._id, "dateAdded",
                                               false, `${PlacesUtils.toPRTime(updatedItem.dateAdded)}`,
                                               PlacesUtils.toPRTime(updatedItem.lastModified),
                                               updatedItem.type,
                                               updatedItem._parentId,
                                               updatedItem.guid,
                                               updatedItem.parentGuid,
                                               "",
                                               updatedItem.source ]);
        }
        if (updateInfo.hasOwnProperty("title")) {
          let isTagging = updatedItem.parentGuid == Bookmarks.tagsGuid;
          notify(observers, "onItemChanged", [ updatedItem._id, "title",
                                               false, updatedItem.title,
                                               PlacesUtils.toPRTime(updatedItem.lastModified),
                                               updatedItem.type,
                                               updatedItem._parentId,
                                               updatedItem.guid,
                                               updatedItem.parentGuid, "",
                                               updatedItem.source ],
                                               { isTagging });
          // If we're updating a tag, we must notify all the tagged bookmarks
          // about the change.
          if (isTagging) {
            let URIs = PlacesUtils.tagging.getURIsForTag(updatedItem.title);
            for (let uri of URIs) {
              for (let entry of (await fetchBookmarksByURL({ url: new URL(uri.spec) }, true))) {
                notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                     PlacesUtils.toPRTime(entry.lastModified),
                                                     entry.type, entry._parentId,
                                                     entry.guid, entry.parentGuid,
                                                     "", updatedItem.source ]);
              }
            }
          }
        }
        if (updateInfo.hasOwnProperty("url")) {
          notify(observers, "onItemChanged", [ updatedItem._id, "uri",
                                               false, updatedItem.url.href,
                                               PlacesUtils.toPRTime(updatedItem.lastModified),
                                               updatedItem.type,
                                               updatedItem._parentId,
                                               updatedItem.guid,
                                               updatedItem.parentGuid,
                                               item.url.href,
                                               updatedItem.source ]);
        }
        // If the item was moved, notify onItemMoved.
        if (item.parentGuid != updatedItem.parentGuid ||
            item.index != updatedItem.index) {
          notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
                                             item.index, updatedItem._parentId,
                                             updatedItem.index, updatedItem.type,
                                             updatedItem.guid, item.parentGuid,
                                             updatedItem.parentGuid,
                                             updatedItem.source ]);
        }

        // Remove non-enumerable properties.
        delete updatedItem.source;
        return Object.assign({}, updatedItem);
      });
    })();
  },

  /**
   * Removes a bookmark-item.
   *
   * @param guidOrInfo
   *        The globally unique identifier of the item to remove, or an
   *        object representing it, as defined above.
   * @param {Object} [options={}]
   *        Additional options that can be passed to the function.
   *        Currently supports the following properties:
   *         - preventRemovalOfNonEmptyFolders: Causes an exception to be
   *           thrown when attempting to remove a folder that is not empty.
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when the removal is complete.
   * @resolves to an object representing the removed bookmark.
   * @rejects if the provided guid doesn't match any existing bookmark.
   * @throws if the arguments are invalid.
   */
  remove(guidOrInfo, options = {}) {
    let info = guidOrInfo;
    if (!info)
      throw new Error("Input should be a valid object");
    if (typeof(guidOrInfo) != "object")
      info = { guid: guidOrInfo };

    // Disallow removing the root folders.
    if ([this.rootGuid, this.menuGuid, this.toolbarGuid, this.unfiledGuid,
         this.tagsGuid, this.mobileGuid].includes(info.guid)) {
      throw new Error("It's not possible to remove Places root folders.");
    }

    if (!("source" in options)) {
      options.source = Bookmarks.SOURCES.DEFAULT;
    }

    // Even if we ignore any other unneeded property, we still validate any
    // known property to reduce likelihood of hidden bugs.
    let removeInfo = validateBookmarkObject("Bookmarks.jsm: remove", info);

    return (async function() {
      let item = await fetchBookmark(removeInfo);
      if (!item)
        throw new Error("No bookmarks found for the provided GUID.");

      item = await removeBookmark(item, options);

      // Notify onItemRemoved to listeners.
      let observers = PlacesUtils.bookmarks.getObservers();
      let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
      let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
      notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
                                           item.type, uri, item.guid,
                                           item.parentGuid,
                                           options.source ],
                                         { isTagging: isUntagging });

      if (isUntagging) {
        for (let entry of (await fetchBookmarksByURL(item, true))) {
          notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                               PlacesUtils.toPRTime(entry.lastModified),
                                               entry.type, entry._parentId,
                                               entry.guid, entry.parentGuid,
                                               "", options.source ]);
        }
      }

      // Remove non-enumerable properties.
      return Object.assign({}, item);
    })();
  },

  /**
   * Removes ALL bookmarks, resetting the bookmarks storage to an empty tree.
   *
   * Note that roots are preserved, only their children will be removed.
   *
   * @param {Object} [options={}]
   *        Additional options. Currently supports the following properties:
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when the removal is complete.
   * @resolves once the removal is complete.
   */
  eraseEverything(options = {}) {
    if (!options.source) {
      options.source = Bookmarks.SOURCES.DEFAULT;
    }

    return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: eraseEverything",
      async function(db) {
        let urls;

        await db.executeTransaction(async function() {
          urls = await removeFoldersContents(db, Bookmarks.userContentRoots, options);
          const time = PlacesUtils.toPRTime(new Date());
          const syncChangeDelta =
            PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);
          for (let folderGuid of Bookmarks.userContentRoots) {
            await db.executeCached(
              `UPDATE moz_bookmarks SET lastModified = :time,
                                        syncChangeCounter = syncChangeCounter + :syncChangeDelta
               WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
              `, { folderGuid, time, syncChangeDelta });
          }
        });

        // We don't wait for the frecency calculation.
        if (urls && urls.length) {
          updateFrecency(db, urls, true).catch(Cu.reportError);
        }
      }
    );
  },

  /**
   * Returns a list of recently bookmarked items.
   * Only includes actual bookmarks. Excludes folders, separators and queries.
   *
   * @param {integer} numberOfItems
   *        The maximum number of bookmark items to return.
   *
   * @return {Promise} resolved when the listing is complete.
   * @resolves to an array of recent bookmark-items.
   * @rejects if an error happens while querying.
   */
  getRecent(numberOfItems) {
    if (numberOfItems === undefined) {
      throw new Error("numberOfItems argument is required");
    }
    if (!typeof numberOfItems === "number" || (numberOfItems % 1) !== 0) {
      throw new Error("numberOfItems argument must be an integer");
    }
    if (numberOfItems <= 0) {
      throw new Error("numberOfItems argument must be greater than zero");
    }

    return fetchRecentBookmarks(numberOfItems);
  },

  /**
   * Fetches information about a bookmark-item.
   *
   * REMARK: any successful call to this method resolves to a single
   *         bookmark-item (or null), even when multiple bookmarks may exist
   *         (e.g. fetching by url).  If you wish to retrieve all of the
   *         bookmarks for a given match, use the callback instead.
   *
   * Input can be either a guid or an object with one, and only one, of these
   * filtering properties set:
   *  - guid
   *      retrieves the item with the specified guid.
   *  - parentGuid and index
   *      retrieves the item by its position.
   *  - url
   *      retrieves the most recent bookmark having the given URL.
   *      To retrieve ALL of the bookmarks for that URL, you must pass in an
   *      onResult callback, that will be invoked once for each found bookmark.
   *
   * @param guidOrInfo
   *        The globally unique identifier of the item to fetch, or an
   *        object representing it, as defined above.
   * @param onResult [optional]
   *        Callback invoked for each found bookmark.
   * @param options [optional]
   *        an optional object whose properties describe options for the fetch:
   *         - concurrent: fetches concurrently to any writes, returning results
   *                       faster. On the negative side, it may return stale
   *                       information missing the currently ongoing write.
   *
   * @return {Promise} resolved when the fetch is complete.
   * @resolves to an object representing the found item, as described above, or
   *           an array of such objects.  if no item is found, the returned
   *           promise is resolved to null.
   * @rejects if an error happens while fetching.
   * @throws if the arguments are invalid.
   *
   * @note Any unknown property in the info object is ignored.  Known properties
   *       may be overwritten.
   */
  fetch(guidOrInfo, onResult = null, options = {}) {
    if (!("concurrent" in options)) {
      options.concurrent = false;
    }
    if (onResult && typeof onResult != "function")
      throw new Error("onResult callback must be a valid function");
    let info = guidOrInfo;
    if (!info)
      throw new Error("Input should be a valid object");
    if (typeof(info) != "object") {
      info = { guid: guidOrInfo };
    } else if (Object.keys(info).length == 1) {
      // Just a faster code path.
      if (!["url", "guid", "parentGuid", "index"].includes(Object.keys(info)[0]))
        throw new Error(`Unexpected number of conditions provided: 0`);
    } else {
      // Only one condition at a time can be provided.
      let conditionsCount = [
        v => v.hasOwnProperty("guid"),
        v => v.hasOwnProperty("parentGuid") && v.hasOwnProperty("index"),
        v => v.hasOwnProperty("url")
      ].reduce((old, fn) => old + fn(info) | 0, 0);
      if (conditionsCount != 1)
        throw new Error(`Unexpected number of conditions provided: ${conditionsCount}`);
    }

    let behavior = {};
    if (info.hasOwnProperty("parentGuid") || info.hasOwnProperty("index")) {
      behavior = {
        parentGuid: { requiredIf: b => b.hasOwnProperty("index") },
        index: { requiredIf: b => b.hasOwnProperty("parentGuid"),
                 validIf: b => typeof(b.index) == "number" &&
                               b.index >= 0 || b.index == this.DEFAULT_INDEX }
      };
    }

    // Even if we ignore any other unneeded property, we still validate any
    // known property to reduce likelihood of hidden bugs.
    let fetchInfo = validateBookmarkObject("Bookmarks.jsm: fetch", info,
                                           behavior);

    return (async function() {
      let results;
      if (fetchInfo.hasOwnProperty("url"))
        results = await fetchBookmarksByURL(fetchInfo, options && options.concurrent);
      else if (fetchInfo.hasOwnProperty("guid"))
        results = await fetchBookmark(fetchInfo, options && options.concurrent);
      else if (fetchInfo.hasOwnProperty("parentGuid") && fetchInfo.hasOwnProperty("index"))
        results = await fetchBookmarkByPosition(fetchInfo, options && options.concurrent);

      if (!results)
        return null;

      if (!Array.isArray(results))
        results = [results];
      // Remove non-enumerable properties.
      results = results.map(r => Object.assign({}, r));

      // Ideally this should handle an incremental behavior and thus be invoked
      // while we fetch.  Though, the likelihood of 2 or more bookmarks for the
      // same match is very low, so it's not worth the added code complication.
      if (onResult) {
        for (let result of results) {
          try {
            onResult(result);
          } catch (ex) {
            Cu.reportError(ex);
          }
        }
      }

      return results[0];
    })();
  },

  /**
   * Retrieves an object representation of a bookmark-item, along with all of
   * its descendants, if any.
   *
   * Each node in the tree is an object that extends the item representation
   * described above with some additional properties:
   *
   *  - [deprecated] id (number)
   *      the item's id.  Defined only if aOptions.includeItemIds is set.
   *  - annos (array)
   *      the item's annotations.  This is not set if there are no annotations
   *      set for the item.
   *
   * The root object of the tree also has the following properties set:
   *  - itemsCount (number, not enumerable)
   *      the number of items, including the root item itself, which are
   *      represented in the resolved object.
   *
   * Bookmarked URLs may also have the following properties:
   *  - tags (string)
   *      csv string of the bookmark's tags, if any.
   *  - charset (string)
   *      the last known charset of the bookmark, if any.
   *  - iconurl (URL)
   *      the bookmark's favicon URL, if any.
   *
   * Folders may also have the following properties:
   *  - children (array)
   *      the folder's children information, each of them having the same set of
   *      properties as above.
   *
   * @param [optional] guid
   *        the topmost item to be queried.  If it's not passed, the Places
   *        root folder is queried: that is, you get a representation of the
   *        entire bookmarks hierarchy.
   * @param [optional] options
   *        Options for customizing the query behavior, in the form of an
   *        object with any of the following properties:
   *         - excludeItemsCallback: a function for excluding items, along with
   *           their descendants.  Given an item object (that has everything set
   *           apart its potential children data), it should return true if the
   *           item should be excluded.  Once an item is excluded, the function
   *           isn't called for any of its descendants.  This isn't called for
   *           the root item.
   *           WARNING: since the function may be called for each item, using
   *           this option can slow down the process significantly if the
   *           callback does anything that's not relatively trivial.  It is
   *           highly recommended to avoid any synchronous I/O or DB queries.
   *         - includeItemIds: opt-in to include the deprecated id property.
   *           Use it if you must. It'll be removed once the switch to guids is
   *           complete.
   *
   * @return {Promise} resolved when the fetch is complete.
   * @resolves to an object that represents either a single item or a
   *           bookmarks tree.  if guid points to a non-existent item, the
   *           returned promise is resolved to null.
   * @rejects if an error happens while fetching.
   * @throws if the arguments are invalid.
   */
  // TODO must implement these methods yet:
  // PlacesUtils.promiseBookmarksTree()
  fetchTree(guid = "", options = {}) {
    throw new Error("Not yet implemented");
  },

  /**
   * Reorders contents of a folder based on a provided array of GUIDs.
   *
   * @param parentGuid
   *        The globally unique identifier of the folder whose contents should
   *        be reordered.
   * @param orderedChildrenGuids
   *        Ordered array of the children's GUIDs.  If this list contains
   *        non-existing entries they will be ignored.  If the list is
   *        incomplete, and the current child list is already in order with
   *        respect to orderedChildrenGuids, no change is made. Otherwise, the
   *        new items are appended but maintain their current order relative to
   *        eachother.
   * @param {Object} [options={}]
   *        Additional options. Currently supports the following properties:
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when reordering is complete.
   * @rejects if an error happens while reordering.
   * @throws if the arguments are invalid.
   */
  reorder(parentGuid, orderedChildrenGuids, options = {}) {
    let info = { guid: parentGuid };
    info = validateBookmarkObject("Bookmarks.jsm: reorder", info,
                                  { guid: { required: true } });

    if (!Array.isArray(orderedChildrenGuids) || !orderedChildrenGuids.length)
      throw new Error("Must provide a sorted array of children GUIDs.");
    try {
      orderedChildrenGuids.forEach(PlacesUtils.BOOKMARK_VALIDATORS.guid);
    } catch (ex) {
      throw new Error("Invalid GUID found in the sorted children array.");
    }

    if (!("source" in options)) {
      options.source = Bookmarks.SOURCES.DEFAULT;
    }

    return (async () => {
      let parent = await fetchBookmark(info);
      if (!parent || parent.type != this.TYPE_FOLDER)
        throw new Error("No folder found for the provided GUID.");

      let sortedChildren = await reorderChildren(parent, orderedChildrenGuids,
                                                 options);

      let { source = Bookmarks.SOURCES.DEFAULT } = options;
      let observers = PlacesUtils.bookmarks.getObservers();
      // Note that child.index is the old index.
      for (let i = 0; i < sortedChildren.length; ++i) {
        let child = sortedChildren[i];
        notify(observers, "onItemMoved", [ child._id, child._parentId,
                                           child.index, child._parentId,
                                           i, child.type,
                                           child.guid, child.parentGuid,
                                           child.parentGuid,
                                           source ]);
      }
    })();
  },

  /**
   * Searches a list of bookmark-items by a search term, url or title.
   *
   * IMPORTANT:
   * This is intended as an interim API for the web-extensions implementation.
   * It will be removed as soon as we have a new querying API.
   *
   * If you just want to search bookmarks by URL, use .fetch() instead.
   *
   * @param query
   *        Either a string to use as search term, or an object
   *        containing any of these keys: query, title or url with the
   *        corresponding string to match as value.
   *        The url property can be either a string or an nsIURI.
   *
   * @return {Promise} resolved when the search is complete.
   * @resolves to an array of found bookmark-items.
   * @rejects if an error happens while searching.
   * @throws if the arguments are invalid.
   *
   * @note Any unknown property in the query object is ignored.
   *       Known properties may be overwritten.
   */
  search(query) {
    if (!query) {
      throw new Error("Query object is required");
    }
    if (typeof query === "string") {
      query = { query };
    }
    if (typeof query !== "object") {
      throw new Error("Query must be an object or a string");
    }
    if (query.query && typeof query.query !== "string") {
      throw new Error("Query option must be a string");
    }
    if (query.title && typeof query.title !== "string") {
      throw new Error("Title option must be a string");
    }

    if (query.url) {
      if (typeof query.url === "string" || (query.url instanceof URL)) {
        query.url = new URL(query.url).href;
      } else if (query.url instanceof Ci.nsIURI) {
        query.url = query.url.spec;
      } else {
        throw new Error("Url option must be a string or a URL object");
      }
    }

    return queryBookmarks(query);
  },
});

// Globals.

/**
 * Sends a bookmarks notification through the given observers.
 *
 * @param {Array} observers
 *        array of nsINavBookmarkObserver objects.
 * @param {String} notification
 *        the notification name.
 * @param {Array} [args]
 *        array of arguments to pass to the notification.
 * @param {Object} [information]
 *        Information about the notification, so we can filter based
 *        based on the observer's preferences.
 */
function notify(observers, notification, args = [], information = {}) {
  for (let observer of observers) {
    if (information.isTagging && observer.skipTags) {
      continue;
    }

    if (information.isDescendantRemoval && observer.skipDescendantsOnItemRemoval) {
      continue;
    }

    try {
      observer[notification](...args);
    } catch (ex) {}
  }
}

// Update implementation.

function updateBookmark(info, item, newParent) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
    async function(db) {

    let tuples = new Map();
    tuples.set("lastModified", { value: PlacesUtils.toPRTime(info.lastModified) });
    if (info.hasOwnProperty("title"))
      tuples.set("title", { value: info.title,
                            fragment: `title = NULLIF(:title, "")` });
    if (info.hasOwnProperty("dateAdded"))
      tuples.set("dateAdded", { value: PlacesUtils.toPRTime(info.dateAdded) });

    await db.executeTransaction(async function() {
      let isTagging = item._grandParentId == PlacesUtils.tagsFolderId;
      let syncChangeDelta =
        PlacesSyncUtils.bookmarks.determineSyncChangeDelta(info.source);

      if (info.hasOwnProperty("url")) {
        // Ensure a page exists in moz_places for this URL.
        await maybeInsertPlace(db, info.url);
        // Update tuples for the update query.
        tuples.set("url", { value: info.url.href,
                            fragment: "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)" });
      }

      let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
      if (newParent) {
        // For simplicity, update the index regardless.
        tuples.set("position", { value: newIndex });

        if (newParent.guid == item.parentGuid) {
          // Moving inside the original container.
          // When moving "up", add 1 to each index in the interval.
          // Otherwise when moving down, we subtract 1.
          // Only the parent needs a sync change, which is handled in
          // `setAncestorsLastModified`.
          let sign = newIndex < item.index ? +1 : -1;
          await db.executeCached(
            `UPDATE moz_bookmarks SET position = position + :sign
             WHERE parent = :newParentId
               AND position BETWEEN :lowIndex AND :highIndex
            `, { sign, newParentId: newParent._id,
                 lowIndex: Math.min(item.index, newIndex),
                 highIndex: Math.max(item.index, newIndex) });
        } else {
          // Moving across different containers. In this case, both parents and
          // the child need sync changes. `setAncestorsLastModified` handles the
          // parents; the `needsSyncChange` check below handles the child.
          tuples.set("parent", { value: newParent._id} );
          await db.executeCached(
            `UPDATE moz_bookmarks SET position = position + :sign
             WHERE parent = :oldParentId
               AND position >= :oldIndex
            `, { sign: -1, oldParentId: item._parentId, oldIndex: item.index });
          await db.executeCached(
            `UPDATE moz_bookmarks SET position = position + :sign
             WHERE parent = :newParentId
               AND position >= :newIndex
            `, { sign: +1, newParentId: newParent._id, newIndex });

          await setAncestorsLastModified(db, item.parentGuid, info.lastModified,
                                         syncChangeDelta);
        }
        await setAncestorsLastModified(db, newParent.guid, info.lastModified,
                                       syncChangeDelta);
      }

      if (syncChangeDelta) {
        // Sync stores child indices in the parent's record, so we only bump the
        // item's counter if we're updating at least one more property in
        // addition to the index, last modified time, and dateAdded.
        let sizeThreshold = 1;
        if (info.hasOwnProperty("index") && info.index != item.index) {
          ++sizeThreshold;
        }
        if (tuples.has("dateAdded")) {
          ++sizeThreshold;
        }
        let needsSyncChange = tuples.size > sizeThreshold;
        if (needsSyncChange) {
          tuples.set("syncChangeDelta", { value: syncChangeDelta,
                                          fragment: "syncChangeCounter = syncChangeCounter + :syncChangeDelta" });
        }
      }

      if (isTagging) {
        // If we're updating a tag entry, bump the sync change counter for
        // bookmarks with the tagged URL.
        await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
          db, item.url, syncChangeDelta);
        if (info.hasOwnProperty("url")) {
          // Changing the URL of a tag entry is equivalent to untagging the
          // old URL and tagging the new one, so we bump the change counter
          // for the new URL here.
          await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
            db, info.url, syncChangeDelta);
        }
      }

      let isChangingTagFolder = item._parentId == PlacesUtils.tagsFolderId;
      if (isChangingTagFolder) {
        // If we're updating a tag folder (for example, changing a tag's title),
        // bump the change counter for all tagged bookmarks.
        await addSyncChangesForBookmarksInFolder(db, item, syncChangeDelta);
      }

      await db.executeCached(
        `UPDATE moz_bookmarks
         SET ${Array.from(tuples.keys()).map(v => tuples.get(v).fragment || `${v} = :${v}`).join(", ")}
         WHERE guid = :guid
        `, Object.assign({ guid: info.guid },
                         [...tuples.entries()].reduce((p, c) => { p[c[0]] = c[1].value; return p; }, {})));

      if (newParent) {
        if (newParent.guid == item.parentGuid) {
          // Mark all affected separators as changed
          // Also bumps the change counter if the item itself is a separator
          const startIndex = Math.min(newIndex, item.index);
          await adjustSeparatorsSyncCounter(db, newParent._id, startIndex, syncChangeDelta);
        } else {
          // Mark all affected separators as changed
          await adjustSeparatorsSyncCounter(db, item._parentId, item.index, syncChangeDelta);
          await adjustSeparatorsSyncCounter(db, newParent._id, newIndex, syncChangeDelta);
        }
        // Remove the Sync orphan annotation from reparented items. We don't
        // notify annotation observers about this because this is a temporary,
        // internal anno that's only used by Sync.
        await db.executeCached(
          `DELETE FROM moz_items_annos
           WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes
                                      WHERE name = :orphanAnno) AND
                 item_id = :id`,
          { orphanAnno: PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO,
            id: item._id });
      }
    });

    // If the parent changed, update related non-enumerable properties.
    let additionalParentInfo = {};
    if (newParent) {
      Object.defineProperty(additionalParentInfo, "_parentId",
                            { value: newParent._id, enumerable: false });
      Object.defineProperty(additionalParentInfo, "_grandParentId",
                            { value: newParent._parentId, enumerable: false });
    }

    let updatedItem = mergeIntoNewObject(item, info, additionalParentInfo);

    return updatedItem;
  });
}

// Insert implementation.

function insertBookmark(item, parent) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: insertBookmark",
    async function(db) {

    // If a guid was not provided, generate one, so we won't need to fetch the
    // bookmark just after having created it.
    let hasExistingGuid = item.hasOwnProperty("guid");
    if (!hasExistingGuid)
      item.guid = (await db.executeCached("SELECT GENERATE_GUID() AS guid"))[0].getResultByName("guid");

    let isTagging = parent._parentId == PlacesUtils.tagsFolderId;

    await db.executeTransaction(async function transaction() {
      if (item.type == Bookmarks.TYPE_BOOKMARK) {
        // Ensure a page exists in moz_places for this URL.
        // The IGNORE conflict can trigger on `guid`.
        await maybeInsertPlace(db, item.url);
      }

      // Adjust indices.
      await db.executeCached(
        `UPDATE moz_bookmarks SET position = position + 1
         WHERE parent = :parent
         AND position >= :index
        `, { parent: parent._id, index: item.index });

      let syncChangeDelta =
        PlacesSyncUtils.bookmarks.determineSyncChangeDelta(item.source);
      let syncStatus =
        PlacesSyncUtils.bookmarks.determineInitialSyncStatus(item.source);

      // Insert the bookmark into the database.
      await db.executeCached(
        `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
                                    dateAdded, lastModified, guid,
                                    syncChangeCounter, syncStatus)
         VALUES (CASE WHEN :url ISNULL THEN NULL ELSE (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) END,
                 :type, :parent, :index, NULLIF(:title, ""), :date_added,
                 :last_modified, :guid, :syncChangeCounter, :syncStatus)
        `, { url: item.hasOwnProperty("url") ? item.url.href : null,
             type: item.type, parent: parent._id, index: item.index,
             title: item.title, date_added: PlacesUtils.toPRTime(item.dateAdded),
             last_modified: PlacesUtils.toPRTime(item.lastModified), guid: item.guid,
             syncChangeCounter: syncChangeDelta, syncStatus });

      // Mark all affected separators as changed
      await adjustSeparatorsSyncCounter(db, parent._id, item.index + 1, syncChangeDelta);

      if (hasExistingGuid) {
        // Remove stale tombstones if we're reinserting an item.
        await db.executeCached(
          `DELETE FROM moz_bookmarks_deleted WHERE guid = :guid`,
          { guid: item.guid });
      }

      if (isTagging) {
        // New tag entry; bump the change counter for bookmarks with the
        // tagged URL.
        await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
          db, item.url, syncChangeDelta);
      }

      await setAncestorsLastModified(db, item.parentGuid, item.dateAdded,
                                     syncChangeDelta);
    });

    // If not a tag recalculate frecency...
    if (item.type == Bookmarks.TYPE_BOOKMARK && !isTagging) {
      // ...though we don't wait for the calculation.
      updateFrecency(db, [item.url]).catch(Cu.reportError);
    }

    return item;
  });
}

/**
 * Determines if a bookmark is a Livemark depending on how it is annotated.
 *
 * @param {Object} node The bookmark node to check.
 * @returns {Boolean} True if the node is a Livemark, false otherwise.
 */
function isLivemark(node) {
  return node.annos && node.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI);
}

function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: insertBookmarkTree", async function(db) {
    await db.executeTransaction(async function transaction() {
      await maybeInsertManyPlaces(db, urls);

      let syncChangeDelta =
        PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source);
      let syncStatus =
        PlacesSyncUtils.bookmarks.determineInitialSyncStatus(source);

      let rootId = parent._id;

      items = items.map(item => ({
        url: item.url && item.url.href,
        type: item.type, parentGuid: item.parentGuid, index: item.index,
        title: item.title, date_added: PlacesUtils.toPRTime(item.dateAdded),
        last_modified: PlacesUtils.toPRTime(item.lastModified), guid: item.guid,
        syncChangeCounter: syncChangeDelta, syncStatus, rootId
      }));
      await db.executeCached(
        `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
                                    dateAdded, lastModified, guid,
                                    syncChangeCounter, syncStatus)
         VALUES (CASE WHEN :url ISNULL THEN NULL ELSE (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) END, :type,
         (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
         IFNULL(:index, (SELECT count(*) FROM moz_bookmarks WHERE parent = :rootId)),
         NULLIF(:title, ""), :date_added, :last_modified, :guid,
         :syncChangeCounter, :syncStatus)`, items);

      await setAncestorsLastModified(db, parent.guid, lastAddedForParent,
                                     syncChangeDelta);
    });

    // We don't wait for the frecency calculation.
    updateFrecency(db, urls, true).catch(Cu.reportError);

    return items;
  });
}

/**
 * Handles any Livemarks within the passed items.
 *
 * @param {Array} items Livemark items that need to be added.
 */
async function insertLivemarkData(items) {
  for (let item of items) {
    let feedURI = null;
    let siteURI = null;
    item.annos = item.annos.filter(function(aAnno) {
      switch (aAnno.name) {
        case PlacesUtils.LMANNO_FEEDURI:
          feedURI = NetUtil.newURI(aAnno.value);
          return false;
        case PlacesUtils.LMANNO_SITEURI:
          siteURI = NetUtil.newURI(aAnno.value);
          return false;
        default:
          return true;
      }
    });

    let index = null;

    // Delete the placeholder but note the index of it, so that we
    // can insert the livemark item at the right place.
    let placeholder = await Bookmarks.fetch(item.guid);
    index = placeholder.index;

    await Bookmarks.remove(item.guid, {source: item.source});

    if (feedURI) {
      item.feedURI = feedURI;
      item.siteURI = siteURI;
      item.index = index;

      if (item.dateAdded) {
        item.dateAdded = PlacesUtils.toPRTime(item.dateAdded);
      }
      if (item.lastModified) {
        item.lastModified = PlacesUtils.toPRTime(item.lastModified);
      }

      let livemark = await PlacesUtils.livemarks.addLivemark(item);

      let id = livemark.id;
      if (item.annos && item.annos.length) {
        PlacesUtils.setAnnotationsForItem(id, item.annos,
                                          item.source);
      }
    }
  }
}

/**
 * Handles special data on a bookmark, e.g. annotations, keywords, tags, charsets,
 * inserting the data into the appropriate place.
 *
 * @param {Integer} itemId The ID of the item within the bookmarks database.
 * @param {Object} item The bookmark item with possible special data to be inserted.
 */
async function handleBookmarkItemSpecialData(itemId, item) {
  if (item.annos && item.annos.length) {
    PlacesUtils.setAnnotationsForItem(itemId, item.annos, item.source)
  }
  if ("keyword" in item && item.keyword) {
    // POST data could be set in 2 ways:
    // 1. new backups have a postData property
    // 2. old backups have an item annotation
    let postDataAnno = item.annos &&
                       item.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
    let postData = item.postData || (postDataAnno && postDataAnno.value);
    try {
      await PlacesUtils.keywords.insert({
        keyword: item.keyword,
        url: item.url,
        postData,
        source: item.source
      });
    } catch (ex) {
      Cu.reportError(`Failed to insert keywords: ${ex}`);
    }
  }
  if ("tags" in item) {
    try {
      PlacesUtils.tagging.tagURI(NetUtil.newURI(item.url), item.tags, item._source);
    } catch (ex) {
      // Invalid tag child, skip it.
      Cu.reportError(`Unable to set tags "${item.tags.join(", ")}" for ${item.url}: ${ex}`);
    }
  }
  if ("charset" in item && item.charset) {
    await PlacesUtils.setCharsetForURI(NetUtil.newURI(item.url), item.charset);
  }
}

// Query implementation.

async function queryBookmarks(info) {
  let queryParams = {
    tags_folder: await promiseTagsFolderId(),
    type: Bookmarks.TYPE_SEPARATOR,
  };
  // We're searching for bookmarks, so exclude tags and separators.
  let queryString = "WHERE b.type <> :type";
  queryString += " AND b.parent <> :tags_folder";
  queryString += " AND p.parent <> :tags_folder";

  if (info.title) {
    queryString += " AND b.title = :title";
    queryParams.title = info.title;
  }

  if (info.url) {
    queryString += " AND h.url_hash = hash(:url) AND h.url = :url";
    queryParams.url = info.url;
  }

  if (info.query) {
    queryString += " AND AUTOCOMPLETE_MATCH(:query, h.url, b.title, NULL, NULL, 1, 1, NULL, :matchBehavior, :searchBehavior) ";
    queryParams.query = info.query;
    queryParams.matchBehavior = MATCH_ANYWHERE_UNMODIFIED;
    queryParams.searchBehavior = BEHAVIOR_BOOKMARK;
  }

  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: queryBookmarks",
    async function(db) {
      // _id, _childCount, _grandParentId and _parentId fields
      // are required to be in the result by the converting function
      // hence setting them to NULL
      let rows = await db.executeCached(
        `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
                b.dateAdded, b.lastModified, b.type,
                IFNULL(b.title, "") AS title, h.url AS url, b.parent, p.parent,
                NULL AS _id,
                NULL AS _childCount,
                NULL AS _grandParentId,
                NULL AS _parentId,
                NULL AS _syncStatus
         FROM moz_bookmarks b
         LEFT JOIN moz_bookmarks p ON p.id = b.parent
         LEFT JOIN moz_places h ON h.id = b.fk
         ${queryString}
        `, queryParams);

      return rowsToItemsArray(rows);
    }
  );
}


// Fetch implementation.

async function fetchBookmark(info, concurrent) {
  let query = async function(db) {
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, "") AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE b.guid = :guid
      `, { guid: info.guid });

    return rows.length ? rowsToItemsArray(rows)[0] : null;
  };
  if (concurrent) {
    let db = await PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmark",
                                           query);
}

async function fetchBookmarkByPosition(info, concurrent) {
  let query = async function(db) {
    let index = info.index == Bookmarks.DEFAULT_INDEX ? null : info.index;
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, "") AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE p.guid = :parentGuid
       AND b.position = IFNULL(:index, (SELECT count(*) - 1
                                        FROM moz_bookmarks
                                        WHERE parent = p.id))
      `, { parentGuid: info.parentGuid, index });

    return rows.length ? rowsToItemsArray(rows)[0] : null;
  };
  if (concurrent) {
    let db = await PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarkByPosition",
                                           query);
}

async function fetchBookmarksByURL(info, concurrent) {
  let query = async function(db) {
    let tagsFolderId = await promiseTagsFolderId();
    let rows = await db.executeCached(
      `/* do not warn (bug no): not worth to add an index */
      SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, "") AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              NULL AS _childCount, /* Unused for now */
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      JOIN moz_places h ON h.id = b.fk
      WHERE h.url_hash = hash(:url) AND h.url = :url
      AND _grandParentId <> :tagsFolderId
      ORDER BY b.lastModified DESC
      `, { url: info.url.href, tagsFolderId });

    return rows.length ? rowsToItemsArray(rows) : null;
  };

  if (concurrent) {
    let db = await PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByURL",
                                           query);
}

function fetchRecentBookmarks(numberOfItems) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchRecentBookmarks",
    async function(db) {
      let tagsFolderId = await promiseTagsFolderId();
      let rows = await db.executeCached(
        `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
                b.dateAdded, b.lastModified, b.type,
                IFNULL(b.title, "") AS title, h.url AS url, NULL AS _id,
                NULL AS _parentId, NULL AS _childCount, NULL AS _grandParentId,
                NULL AS _syncStatus
        FROM moz_bookmarks b
        JOIN moz_bookmarks p ON p.id = b.parent
        JOIN moz_places h ON h.id = b.fk
        WHERE p.parent <> :tagsFolderId
        AND b.type = :type
        AND url_hash NOT BETWEEN hash("place", "prefix_lo")
                              AND hash("place", "prefix_hi")
        ORDER BY b.dateAdded DESC, b.ROWID DESC
        LIMIT :numberOfItems
        `, {
          tagsFolderId,
          type: Bookmarks.TYPE_BOOKMARK,
          numberOfItems,
        });

      return rows.length ? rowsToItemsArray(rows) : [];
    }
  );
}

function fetchBookmarksByParent(info) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByParent",
    async function(db) {

    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, "") AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE p.guid = :parentGuid
       ORDER BY b.position ASC
      `, { parentGuid: info.parentGuid });

    return rowsToItemsArray(rows);
  });
}

// Remove implementation.

function removeBookmark(item, options) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: removeBookmark",
    async function(db) {
    let urls;
    let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;

    await db.executeTransaction(async function transaction() {
      // If it's a folder, remove its contents first.
      if (item.type == Bookmarks.TYPE_FOLDER) {
        if (options.preventRemovalOfNonEmptyFolders && item._childCount > 0) {
          throw new Error("Cannot remove a non-empty folder.");
        }
        urls = await removeFoldersContents(db, [item.guid], options);
      }

      // Remove annotations first.  If it's a tag, we can avoid paying that cost.
      if (!isUntagging) {
        // We don't go through the annotations service for this cause otherwise
        // we'd get a pointless onItemChanged notification and it would also
        // set lastModified to an unexpected value.
        await removeAnnotationsForItem(db, item._id);
      }

      // Remove the bookmark from the database.
      await db.executeCached(
        `DELETE FROM moz_bookmarks WHERE guid = :guid`, { guid: item.guid });

      // Fix indices in the parent.
      await db.executeCached(
        `UPDATE moz_bookmarks SET position = position - 1 WHERE
         parent = :parentId AND position > :index
        `, { parentId: item._parentId, index: item.index });

      let syncChangeDelta =
        PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);

      // Mark all affected separators as changed
      await adjustSeparatorsSyncCounter(db, item._parentId, item.index, syncChangeDelta);

      if (isUntagging) {
        // If we're removing a tag entry, increment the change counter for all
        // bookmarks with the tagged URL.
        await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
          db, item.url, syncChangeDelta);
      }

      // Write a tombstone for the removed item.
      await insertTombstone(db, item, syncChangeDelta);

      await setAncestorsLastModified(db, item.parentGuid, new Date(),
                                     syncChangeDelta);
    });

    // If not a tag recalculate frecency...
    if (item.type == Bookmarks.TYPE_BOOKMARK && !isUntagging) {
      // ...though we don't wait for the calculation.
      updateFrecency(db, [item.url]).catch(Cu.reportError);
    }

    if (urls && urls.length && item.type == Bookmarks.TYPE_FOLDER) {
      updateFrecency(db, urls, true).catch(Cu.reportError);
    }

    return item;
  });
}

// Reorder implementation.

function reorderChildren(parent, orderedChildrenGuids, options) {
  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
    db => db.executeTransaction(async function() {
      // Select all of the direct children for the given parent.
      let children = await fetchBookmarksByParent({ parentGuid: parent.guid });
      if (!children.length) {
        return [];
      }

      // Maps of GUIDs to indices for fast lookups in the comparator function.
      let guidIndices = new Map();
      let currentIndices = new Map();
      for (let i = 0; i < orderedChildrenGuids.length; ++i) {
        let guid = orderedChildrenGuids[i];
        guidIndices.set(guid, i);
      }

      // If we got an incomplete list but everything we have is in the right
      // order, we do nothing.
      let needReorder = true;
      let requestedChildIndices = [];
      for (let i = 0; i < children.length; ++i) {
        // Take the opportunity to build currentIndices here, since we already
        // are iterating over the children array.
        currentIndices.set(children[i].guid, i);

        if (guidIndices.has(children[i].guid)) {
          let index = guidIndices.get(children[i].guid);
          requestedChildIndices.push(index);
        }
      }

      if (requestedChildIndices.length) {
        needReorder = false;
        for (let i = 1; i < requestedChildIndices.length; ++i) {
          if (requestedChildIndices[i - 1] > requestedChildIndices[i]) {
            needReorder = true;
            break;
          }
        }
      }

      if (needReorder) {


        // Reorder the children array according to the specified order, provided
        // GUIDs come first, others are appended in somehow random order.
        children.sort((a, b) => {
          // This works provided fetchBookmarksByParent returns sorted children.
          if (!guidIndices.has(a.guid) && !guidIndices.has(b.guid)) {
            return currentIndices.get(a.guid) < currentIndices.get(b.guid) ? -1 : 1;
          }
          if (!guidIndices.has(a.guid)) {
            return 1;
          }
          if (!guidIndices.has(b.guid)) {
            return -1;
          }
          return guidIndices.get(a.guid) < guidIndices.get(b.guid) ? -1 : 1;
        });

        // Update the bookmarks position now.  If any unknown guid have been
        // inserted meanwhile, its position will be set to -position, and we'll
        // handle it later.
        // To do the update in a single step, we build a VALUES (guid, position)
        // table.  We then use count() in the sorting table to avoid skipping values
        // when no more existing GUIDs have been provided.
        let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
                                  .join();
        await db.execute(
          `WITH sorting(g, p) AS (
             VALUES ${valuesTable}
           )
           UPDATE moz_bookmarks SET position = (
             SELECT CASE count(*) WHEN 0 THEN -position
                                         ELSE count(*) - 1
                    END
             FROM sorting a
             JOIN sorting b ON b.p <= a.p
             WHERE a.g = guid
           )
           WHERE parent = :parentId
          `, { parentId: parent._id});

        let syncChangeDelta =
          PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);
        if (syncChangeDelta) {
          // Flag the parent as having a change.
          await db.executeCached(`
            UPDATE moz_bookmarks SET
              syncChangeCounter = syncChangeCounter + :syncChangeDelta
            WHERE id = :parentId`,
            { parentId: parent._id, syncChangeDelta });
        }

        // Update position of items that could have been inserted in the meanwhile.
        // Since this can happen rarely and it's only done for schema coherence
        // resonds, we won't notify about these changes.
        await db.executeCached(
          `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
             AFTER UPDATE OF position ON moz_bookmarks
             WHEN NEW.position = -1
           BEGIN
             UPDATE moz_bookmarks
             SET position = (SELECT MAX(position) FROM moz_bookmarks
                             WHERE parent = NEW.parent) +
                            (SELECT count(*) FROM moz_bookmarks
                             WHERE parent = NEW.parent
                               AND position BETWEEN OLD.position AND -1)
             WHERE guid = NEW.guid;
           END
          `);

        await db.executeCached(
          `UPDATE moz_bookmarks SET position = -1 WHERE position < 0`);

        await db.executeCached(`DROP TRIGGER moz_bookmarks_reorder_trigger`);
      }

      // Remove the Sync orphan annotation from the reordered children, so that
      // Sync doesn't try to reparent them once it sees the original parents. We
      // only do this for explicitly ordered children, to avoid removing orphan
      // annos set by Sync.
      let possibleOrphanIds = [];
      for (let child of children) {
        if (guidIndices.has(child.guid)) {
          possibleOrphanIds.push(child._id);
        }
      }
      await db.executeCached(
        `DELETE FROM moz_items_annos
         WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes
                                    WHERE name = :orphanAnno) AND
               item_id IN (${possibleOrphanIds.join(", ")})`,
        { orphanAnno: PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO });

      return children;
    })
  );
}

// Helpers.

/**
 * Merges objects into a new object, included non-enumerable properties.
 *
 * @param sources
 *        source objects to merge.
 * @return a new object including all properties from the source objects.
 */
function mergeIntoNewObject(...sources) {
  let dest = {};
  for (let src of sources) {
    for (let prop of Object.getOwnPropertyNames(src)) {
      Object.defineProperty(dest, prop, Object.getOwnPropertyDescriptor(src, prop));
    }
  }
  return dest;
}

/**
 * Remove properties that have the same value across two bookmark objects.
 *
 * @param dest
 *        destination bookmark object.
 * @param src
 *        source bookmark object.
 * @return a cleaned up bookmark object.
 * @note "guid" is never removed.
 */
function removeSameValueProperties(dest, src) {
  for (let prop in dest) {
    let remove = false;
    switch (prop) {
      case "lastModified":
      case "dateAdded":
        remove = src.hasOwnProperty(prop) && dest[prop].getTime() == src[prop].getTime();
        break;
      case "url":
        remove = src.hasOwnProperty(prop) && dest[prop].href == src[prop].href;
        break;
      default:
        remove = dest[prop] == src[prop];
    }
    if (remove && prop != "guid")
      delete dest[prop];
  }
}

/**
 * Convert an array of mozIStorageRow objects to an array of bookmark objects.
 *
 * @param rows
 *        the array of mozIStorageRow objects.
 * @return an array of bookmark objects.
 */
function rowsToItemsArray(rows) {
  return rows.map(row => {
    let item = {};
    for (let prop of ["guid", "index", "type", "title"]) {
      item[prop] = row.getResultByName(prop);
    }
    for (let prop of ["dateAdded", "lastModified"]) {
      let value = row.getResultByName(prop);
      if (value)
        item[prop] = PlacesUtils.toDate(value);
    }
    let parentGuid = row.getResultByName("parentGuid");
    if (parentGuid) {
      item.parentGuid = parentGuid;
    }
    let url = row.getResultByName("url");
    if (url) {
      item.url = new URL(url);
    }

    for (let prop of ["_id", "_parentId", "_childCount", "_grandParentId",
                      "_syncStatus"]) {
      let val = row.getResultByName(prop);
      if (val !== null) {
        // These properties should not be returned to the API consumer, thus
        // they are non-enumerable and removed through Object.assign just before
        // the object is returned.
        // Configurable is set to support mergeIntoNewObject overwrites.
        Object.defineProperty(item, prop, { value: val, enumerable: false,
                                                        configurable: true });
      }
    }

    return item;
  });
}

function validateBookmarkObject(name, input, behavior) {
  return PlacesUtils.validateItemProperties(name,
    PlacesUtils.BOOKMARK_VALIDATORS, input, behavior);
}

/**
 * Updates frecency for a list of URLs.
 *
 * @param db
 *        the Sqlite.jsm connection handle.
 * @param urls
 *        the array of URLs to update.
 * @param [optional] collapseNotifications
 *        whether we can send just one onManyFrecenciesChanged
 *        notification instead of sending one notification for every URL.
 */
var updateFrecency = async function(db, urls, collapseNotifications = false) {
  let urlQuery = 'hash("' + urls.map(url => url.href).join('"), hash("') + '")';

  let frecencyClause = "CALCULATE_FRECENCY(id)";
  if (!collapseNotifications) {
    frecencyClause = "NOTIFY_FRECENCY(" + frecencyClause +
                     ", url, guid, hidden, last_visit_date)";
  }
  // We just use the hashes, since updating a few additional urls won't hurt.
  await db.execute(
    `UPDATE moz_places
     SET hidden = (url_hash BETWEEN hash("place", "prefix_lo") AND hash("place", "prefix_hi")),
         frecency = ${frecencyClause}
     WHERE url_hash IN ( ${urlQuery} )
    `);
  if (collapseNotifications) {
    let observers = PlacesUtils.history.getObservers();
    notify(observers, "onManyFrecenciesChanged");
  }
};

/**
 * Removes any orphan annotation entries.
 *
 * @param db
 *        the Sqlite.jsm connection handle.
 */
var removeOrphanAnnotations = async function(db) {
  await db.executeCached(
    `DELETE FROM moz_items_annos
     WHERE id IN (SELECT a.id from moz_items_annos a
                  LEFT JOIN moz_bookmarks b ON a.item_id = b.id
                  WHERE b.id ISNULL)
    `);
  await db.executeCached(
    `DELETE FROM moz_anno_attributes
     WHERE id IN (SELECT n.id from moz_anno_attributes n
                  LEFT JOIN moz_annos a1 ON a1.anno_attribute_id = n.id
                  LEFT JOIN moz_items_annos a2 ON a2.anno_attribute_id = n.id
                  WHERE a1.id ISNULL AND a2.id ISNULL)
    `);
};

/**
 * Removes annotations for a given item.
 *
 * @param db
 *        the Sqlite.jsm connection handle.
 * @param itemId
 *        internal id of the item for which to remove annotations.
 */
var removeAnnotationsForItem = async function(db, itemId) {
  await db.executeCached(
    `DELETE FROM moz_items_annos
     WHERE item_id = :id
    `, { id: itemId });
  await db.executeCached(
    `DELETE FROM moz_anno_attributes
     WHERE id IN (SELECT n.id from moz_anno_attributes n
                  LEFT JOIN moz_annos a1 ON a1.anno_attribute_id = n.id
                  LEFT JOIN moz_items_annos a2 ON a2.anno_attribute_id = n.id
                  WHERE a1.id ISNULL AND a2.id ISNULL)
    `);
};

/**
 * Updates lastModified for all the ancestors of a given folder GUID.
 *
 * @param db
 *        the Sqlite.jsm connection handle.
 * @param folderGuid
 *        the GUID of the folder whose ancestors should be updated.
 * @param time
 *        a Date object to use for the update.
 *
 * @note the folder itself is also updated.
 */
var setAncestorsLastModified = async function(db, folderGuid, time, syncChangeDelta) {
  await db.executeCached(
    `WITH RECURSIVE
     ancestors(aid) AS (
       SELECT id FROM moz_bookmarks WHERE guid = :guid
       UNION ALL
       SELECT parent FROM moz_bookmarks
       JOIN ancestors ON id = aid
       WHERE type = :type
     )
     UPDATE moz_bookmarks SET lastModified = :time
     WHERE id IN ancestors
    `, { guid: folderGuid, type: Bookmarks.TYPE_FOLDER,
         time: PlacesUtils.toPRTime(time) });

  if (syncChangeDelta) {
    // Flag the folder as having a change.
    await db.executeCached(`
      UPDATE moz_bookmarks SET
        syncChangeCounter = syncChangeCounter + :syncChangeDelta
      WHERE guid = :guid`,
      { guid: folderGuid, syncChangeDelta });
  }
};

/**
 * Remove all descendants of one or more bookmark folders.
 *
 * @param {Object} db
 *        the Sqlite.jsm connection handle.
 * @param {Array} folderGuids
 *        array of folder guids.
 * @return {Array}
 *         An array of urls that will need to be updated for frecency. These
 *         are returned rather than updated immediately so that the caller
 *         can decide when they need to be updated - they do not need to
 *         stop this function from completing.
 */
var removeFoldersContents =
async function(db, folderGuids, options) {
  let syncChangeDelta =
    PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);

  let itemsRemoved = [];
  for (let folderGuid of folderGuids) {
    let rows = await db.executeCached(
      `WITH RECURSIVE
       descendants(did) AS (
         SELECT b.id FROM moz_bookmarks b
         JOIN moz_bookmarks p ON b.parent = p.id
         WHERE p.guid = :folderGuid
         UNION ALL
         SELECT id FROM moz_bookmarks
         JOIN descendants ON parent = did
       )
       SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
              b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
              b.lastModified, IFNULL(b.title, "") AS title,
              p.parent AS _grandParentId, NULL AS _childCount,
              b.syncStatus AS _syncStatus
       FROM descendants
       /* The usage of CROSS JOIN is not random, it tells the optimizer
          to retain the original rows order, so the hierarchy is respected */
       CROSS JOIN moz_bookmarks b ON did = b.id
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON b.fk = h.id`, { folderGuid });

    itemsRemoved = itemsRemoved.concat(rowsToItemsArray(rows));

    await db.executeCached(
      `WITH RECURSIVE
       descendants(did) AS (
         SELECT b.id FROM moz_bookmarks b
         JOIN moz_bookmarks p ON b.parent = p.id
         WHERE p.guid = :folderGuid
         UNION ALL
         SELECT id FROM moz_bookmarks
         JOIN descendants ON parent = did
       )
       DELETE FROM moz_bookmarks WHERE id IN descendants`, { folderGuid });
  }

  // Write tombstones for removed items.
  await insertTombstones(db, itemsRemoved, syncChangeDelta);

  // Bump the change counter for all tagged bookmarks when removing tag
  // folders.
  await addSyncChangesForRemovedTagFolders(db, itemsRemoved, syncChangeDelta);

  // Cleanup orphans.
  await removeOrphanAnnotations(db);

  // TODO (Bug 1087576): this may leave orphan tags behind.

  // Send onItemRemoved notifications to listeners.
  // TODO (Bug 1087580): for the case of eraseEverything, this should send a
  // single clear bookmarks notification rather than notifying for each
  // bookmark.

  // Notify listeners in reverse order to serve children before parents.
  let { source = Bookmarks.SOURCES.DEFAULT } = options;
  let observers = PlacesUtils.bookmarks.getObservers();
  for (let item of itemsRemoved.reverse()) {
    let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
    notify(observers, "onItemRemoved", [ item._id, item._parentId,
                                         item.index, item.type, uri,
                                         item.guid, item.parentGuid,
                                         source ],
                                       // Notify observers that this item is being
                                       // removed as a descendent.
                                       { isDescendantRemoval: true });

    let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
    if (isUntagging) {
      for (let entry of (await fetchBookmarksByURL(item, true))) {
        notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                             PlacesUtils.toPRTime(entry.lastModified),
                                             entry.type, entry._parentId,
                                             entry.guid, entry.parentGuid,
                                             "", source ]);
      }
    }
  }
  return itemsRemoved.filter(item => "url" in item).map(item => item.url);
};

/**
 * Tries to insert a new place if it doesn't exist yet.
 * @param url
 *        A valid URL object.
 * @return {Promise} resolved when the operation is complete.
 */
function maybeInsertPlace(db, url) {
  // The IGNORE conflict can trigger on `guid`.
  return db.executeCached(
    `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
     VALUES (:url, hash(:url), :rev_host, 0, :frecency,
             IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
                    GENERATE_GUID()))
    `, { url: url.href,
         rev_host: PlacesUtils.getReversedHost(url),
         frecency: url.protocol == "place:" ? 0 : -1 });
}

/**
 * Tries to insert a new place if it doesn't exist yet.
 * @param db
 *        The database to use
 * @param urls
 *        An array with all the url objects to insert.
 * @return {Promise} resolved when the operation is complete.
 */
function maybeInsertManyPlaces(db, urls) {
  return db.executeCached(
    `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid) VALUES
     (:url, hash(:url), :rev_host, 0, :frecency,
     IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :maybeguid))`,
     urls.map(url => ({
       url: url.href,
       rev_host: PlacesUtils.getReversedHost(url),
       frecency: url.protocol == "place:" ? 0 : -1,
       maybeguid: PlacesUtils.history.makeGuid(),
     })));
}

// Indicates whether we should write a tombstone for an item that has been
// uploaded to the server. We ignore "NEW" and "UNKNOWN" items: "NEW" items
// haven't been uploaded yet, and "UNKNOWN" items need a full reconciliation
// with the server.
function needsTombstone(item) {
  return item._syncStatus == Bookmarks.SYNC_STATUS.NORMAL;
}

// Inserts a tombstone for a removed synced item. Tombstones are stored as rows
// in the `moz_bookmarks_deleted` table, and only written for "NORMAL" items.
// After each sync, `PlacesSyncUtils.bookmarks.pushChanges` drops successfully
// uploaded tombstones.
function insertTombstone(db, item, syncChangeDelta) {
  if (!syncChangeDelta || !needsTombstone(item)) {
    return Promise.resolve();
  }
  return db.executeCached(`
    INSERT INTO moz_bookmarks_deleted (guid, dateRemoved)
    VALUES (:guid, :dateRemoved)`,
    { guid: item.guid,
      dateRemoved: PlacesUtils.toPRTime(Date.now()) });
}

// Inserts tombstones for removed synced items.
function insertTombstones(db, itemsRemoved, syncChangeDelta) {
  if (!syncChangeDelta) {
    return Promise.resolve();
  }
  let syncedItems = itemsRemoved.filter(needsTombstone);
  if (!syncedItems.length) {
    return Promise.resolve();
  }
  let dateRemoved = PlacesUtils.toPRTime(Date.now());
  let valuesTable = syncedItems.map(item => `(
    ${JSON.stringify(item.guid)},
    ${dateRemoved}
  )`).join(",");
  return db.execute(`
    INSERT INTO moz_bookmarks_deleted (guid, dateRemoved)
    VALUES ${valuesTable}`
  );
}

// Bumps the change counter for all bookmarks with URLs referenced in removed
// tag folders.
var addSyncChangesForRemovedTagFolders = async function(db, itemsRemoved, syncChangeDelta) {
  if (!syncChangeDelta) {
    return;
  }
  for (let item of itemsRemoved) {
    let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
    if (isUntagging) {
      await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
        db, item.url, syncChangeDelta);
    }
  }
};

// Bumps the change counter for all bookmarked URLs within `folders`.
// This is used to update tagged bookmarks when changing a tag folder.
function addSyncChangesForBookmarksInFolder(db, folder, syncChangeDelta) {
  if (!syncChangeDelta) {
    return Promise.resolve();
  }
  return db.execute(`
    UPDATE moz_bookmarks SET
      syncChangeCounter = syncChangeCounter + :syncChangeDelta
    WHERE type = :type AND
          fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)
    `,
    { syncChangeDelta, type: Bookmarks.TYPE_BOOKMARK, parent: folder._id });
}

function adjustSeparatorsSyncCounter(db, parentId, startIndex, syncChangeDelta) {
  if (!syncChangeDelta) {
    return Promise.resolve();
  }

  return db.executeCached(`
    UPDATE moz_bookmarks
    SET syncChangeCounter = syncChangeCounter + :delta
    WHERE parent = :parent AND position >= :start_index
      AND type = :item_type
    `,
    {
      delta: syncChangeDelta,
      parent: parentId,
      start_index: startIndex,
      item_type: Bookmarks.TYPE_SEPARATOR
    });
}
PK
!<HnWW'modules/BrowserElementPromptService.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* vim: set ft=javascript : */

"use strict";

var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;
var Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);

this.EXPORTED_SYMBOLS = ["BrowserElementPromptService"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";

function debug(msg) {
  //dump("BrowserElementPromptService - " + msg + "\n");
}

function BrowserElementPrompt(win, browserElementChild) {
  this._win = win;
  this._browserElementChild = browserElementChild;
}

BrowserElementPrompt.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),

  alert: function(title, text) {
    this._browserElementChild.showModalPrompt(
      this._win, {promptType: "alert", title: title, message: text, returnValue: undefined});
  },

  alertCheck: function(title, text, checkMsg, checkState) {
    // Treat this like a normal alert() call, ignoring the checkState.  The
    // front-end can do its own suppression of the alert() if it wants.
    this.alert(title, text);
  },

  confirm: function(title, text) {
    return this._browserElementChild.showModalPrompt(
      this._win, {promptType: "confirm", title: title, message: text, returnValue: undefined});
  },

  confirmCheck: function(title, text, checkMsg, checkState) {
    return this.confirm(title, text);
  },

  // Each button is described by an object with the following schema
  // {
  //   string messageType,  // 'builtin' or 'custom'
  //   string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave',
  //                   // 'revert' or a string from caller if messageType was 'custom'.
  // }
  //
  // Expected result from embedder:
  // {
  //   int button, // Index of the button that user pressed.
  //   boolean checked, // True if the check box is checked.
  // }
  confirmEx: function(title, text, buttonFlags, button0Title, button1Title,
                      button2Title, checkMsg, checkState) {
    let buttonProperties = this._buildConfirmExButtonProperties(buttonFlags,
                                                                button0Title,
                                                                button1Title,
                                                                button2Title);
    let defaultReturnValue = { selectedButton: buttonProperties.defaultButton };
    if (checkMsg) {
      defaultReturnValue.checked = checkState.value;
    }
    let ret = this._browserElementChild.showModalPrompt(
      this._win,
      {
        promptType: "custom-prompt",
        title: title,
        message: text,
        defaultButton: buttonProperties.defaultButton,
        buttons: buttonProperties.buttons,
        showCheckbox: !!checkMsg,
        checkboxMessage: checkMsg,
        checkboxCheckedByDefault: !!checkState.value,
        returnValue: defaultReturnValue
      }
    );
    if (checkMsg) {
      checkState.value = ret.checked;
    }
    return buttonProperties.indexToButtonNumberMap[ret.selectedButton];
  },

  prompt: function(title, text, value, checkMsg, checkState) {
    let rv = this._browserElementChild.showModalPrompt(
      this._win,
      { promptType: "prompt",
        title: title,
        message: text,
        initialValue: value.value,
        returnValue: null });

    value.value = rv;

    // nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt,
    // and false if the user pressed "Cancel".
    //
    // BrowserElementChild returns null for "Cancel" and returns the string the
    // user entered otherwise.
    return rv !== null;
  },

  promptUsernameAndPassword: function(title, text, username, password, checkMsg, checkState) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  promptPassword: function(title, text, password, checkMsg, checkState) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  select: function(title, text, aCount, aSelectList, aOutSelection) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  _buildConfirmExButtonProperties: function(buttonFlags, button0Title,
                                            button1Title, button2Title) {
    let r = {
      defaultButton: -1,
      buttons: [],
      // This map is for translating array index to the button number that
      // is recognized by Gecko. This shouldn't be exposed to embedder.
      indexToButtonNumberMap: []
    };

    let defaultButton = 0;  // Default to Button 0.
    if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) {
      defaultButton = 1;
    } else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) {
      defaultButton = 2;
    }

    // Properties of each button.
    let buttonPositions = [
      Ci.nsIPrompt.BUTTON_POS_0,
      Ci.nsIPrompt.BUTTON_POS_1,
      Ci.nsIPrompt.BUTTON_POS_2
    ];

    function buildButton(buttonTitle, buttonNumber) {
      let ret = {};
      let buttonPosition = buttonPositions[buttonNumber];
      let mask = 0xff * buttonPosition;  // 8 bit mask
      let titleType = (buttonFlags & mask) / buttonPosition;

      ret.messageType = 'builtin';
      switch(titleType) {
      case Ci.nsIPrompt.BUTTON_TITLE_OK:
        ret.message = 'ok';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
        ret.message = 'cancel';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_YES:
        ret.message = 'yes';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_NO:
        ret.message = 'no';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
        ret.message = 'save';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
        ret.message = 'dontsave';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
        ret.message = 'revert';
        break;
      case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
        ret.message = buttonTitle;
        ret.messageType = 'custom';
        break;
      default:
        // This button is not shown.
        return;
      }

      // If this is the default button, set r.defaultButton to
      // the index of this button in the array. This value is going to be
      // exposed to the embedder.
      if (defaultButton === buttonNumber) {
        r.defaultButton = r.buttons.length;
      }
      r.buttons.push(ret);
      r.indexToButtonNumberMap.push(buttonNumber);
    }

    buildButton(button0Title, 0);
    buildButton(button1Title, 1);
    buildButton(button2Title, 2);

    // If defaultButton is still -1 here, it means the default button won't
    // be shown.
    if (r.defaultButton === -1) {
      throw new Components.Exception("Default button won't be shown",
                                     Cr.NS_ERROR_FAILURE);
    }

    return r;
  },
};


function BrowserElementAuthPrompt() {
}

BrowserElementAuthPrompt.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),

  promptAuth: function promptAuth(channel, level, authInfo) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  asyncPromptAuth: function asyncPromptAuth(channel, callback, context, level, authInfo) {
    debug("asyncPromptAuth");

    // The cases that we don't support now.
    if ((authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) &&
        (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)) {
      throw Cr.NS_ERROR_FAILURE;
    }

    let frame = this._getFrameFromChannel(channel);
    if (!frame) {
      debug("Cannot get frame, asyncPromptAuth fail");
      throw Cr.NS_ERROR_FAILURE;
    }

    let browserElementParent =
      BrowserElementPromptService.getBrowserElementParentForFrame(frame);

    if (!browserElementParent) {
      debug("Failed to load browser element parent.");
      throw Cr.NS_ERROR_FAILURE;
    }

    let consumer = {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
      callback: callback,
      context: context,
      cancel: function() {
        this.callback.onAuthCancelled(this.context, false);
        this.callback = null;
        this.context = null;
      }
    };

    let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
    let hashKey = level + "|" + hostname + "|" + httpRealm;
    let asyncPrompt = this._asyncPrompts[hashKey];
    if (asyncPrompt) {
      asyncPrompt.consumers.push(consumer);
      return consumer;
    }

    asyncPrompt = {
      consumers: [consumer],
      channel: channel,
      authInfo: authInfo,
      level: level,
      inProgress: false,
      browserElementParent: browserElementParent
    };

    this._asyncPrompts[hashKey] = asyncPrompt;
    this._doAsyncPrompt();
    return consumer;
  },

  // Utilities for nsIAuthPrompt2 ----------------

  _asyncPrompts: {},
  _asyncPromptInProgress: new WeakMap(),
  _doAsyncPrompt: function() {
    // Find the key of a prompt whose browser element parent does not have
    // async prompt in progress.
    let hashKey = null;
    for (let key in this._asyncPrompts) {
      let prompt = this._asyncPrompts[key];
      if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) {
        hashKey = key;
        break;
      }
    }

    // Didn't find an available prompt, so just return.
    if (!hashKey)
      return;

    let prompt = this._asyncPrompts[hashKey];
    let [hostname, httpRealm] = this._getAuthTarget(prompt.channel,
                                                    prompt.authInfo);

    this._asyncPromptInProgress.set(prompt.browserElementParent, true);
    prompt.inProgress = true;

    let self = this;
    let callback = function(ok, username, password) {
      debug("Async auth callback is called, ok = " +
            ok + ", username = " + username);

      // Here we got the username and password provided by embedder, or
      // ok = false if the prompt was cancelled by embedder.
      delete self._asyncPrompts[hashKey];
      prompt.inProgress = false;
      self._asyncPromptInProgress.delete(prompt.browserElementParent);

      // Fill authentication information with username and password provided
      // by user.
      let flags = prompt.authInfo.flags;
      if (username) {
        if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
          // Domain is separated from username by a backslash
          let idx = username.indexOf("\\");
          if (idx == -1) {
            prompt.authInfo.username = username;
          } else {
            prompt.authInfo.domain   = username.substring(0, idx);
            prompt.authInfo.username = username.substring(idx + 1);
          }
        } else {
          prompt.authInfo.username = username;
        }
      }

      if (password) {
        prompt.authInfo.password = password;
      }

      for (let consumer of prompt.consumers) {
        if (!consumer.callback) {
          // Not having a callback means that consumer didn't provide it
          // or canceled the notification.
          continue;
        }

        try {
          if (ok) {
            debug("Ok, calling onAuthAvailable to finish auth");
            consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
          } else {
            debug("Cancelled, calling onAuthCancelled to finish auth.");
            consumer.callback.onAuthCancelled(consumer.context, true);
          }
        } catch (e) { /* Throw away exceptions caused by callback */ }
      }

      // Process the next prompt, if one is pending.
      self._doAsyncPrompt();
    };

    let runnable = {
      run: function() {
        // Call promptAuth of browserElementParent, to show the prompt.
        prompt.browserElementParent.promptAuth(
          self._createAuthDetail(prompt.channel, prompt.authInfo),
          callback);
      }
    }

    Services.tm.dispatchToMainThread(runnable);
  },

  _getFrameFromChannel: function(channel) {
    let loadContext = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
    return loadContext.topFrameElement;
  },

  _createAuthDetail: function(channel, authInfo) {
    let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
    return {
      host:             hostname,
      path:             channel.URI.path,
      realm:            httpRealm,
      username:         authInfo.username,
      isProxy:          !!(authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY),
      isOnlyPassword:   !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
    };
  },

  // The code is taken from nsLoginManagerPrompter.js, with slight
  // modification for parameter name consistency here.
  _getAuthTarget : function (channel, authInfo) {
    let hostname, realm;

    // If our proxy is demanding authentication, don't use the
    // channel's actual destination.
    if (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
      if (!(channel instanceof Ci.nsIProxiedChannel))
        throw new Error("proxy auth needs nsIProxiedChannel");

      let info = channel.proxyInfo;
      if (!info)
        throw new Error("proxy auth needs nsIProxyInfo");

      // Proxies don't have a scheme, but we'll use "moz-proxy://"
      // so that it's more obvious what the login is for.
      var idnService = Cc["@mozilla.org/network/idn-service;1"].
                       getService(Ci.nsIIDNService);
      hostname = "moz-proxy://" +
                  idnService.convertUTF8toACE(info.host) +
                  ":" + info.port;
      realm = authInfo.realm;
      if (!realm)
        realm = hostname;

      return [hostname, realm];
    }

    hostname = this._getFormattedHostname(channel.URI);

    // If a HTTP WWW-Authenticate header specified a realm, that value
    // will be available here. If it wasn't set or wasn't HTTP, we'll use
    // the formatted hostname instead.
    realm = authInfo.realm;
    if (!realm)
      realm = hostname;

    return [hostname, realm];
  },

  /**
   * Strip out things like userPass and path for display.
   */
  _getFormattedHostname : function(uri) {
    return uri.scheme + "://" + uri.hostPort;
  },
};


function AuthPromptWrapper(oldImpl, browserElementImpl) {
  this._oldImpl = oldImpl;
  this._browserElementImpl = browserElementImpl;
}

AuthPromptWrapper.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
  promptAuth: function(channel, level, authInfo) {
    if (this._canGetParentElement(channel)) {
      return this._browserElementImpl.promptAuth(channel, level, authInfo);
    } else {
      return this._oldImpl.promptAuth(channel, level, authInfo);
    }
  },

  asyncPromptAuth: function(channel, callback, context, level, authInfo) {
    if (this._canGetParentElement(channel)) {
      return this._browserElementImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
    } else {
      return this._oldImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
    }
  },

  _canGetParentElement: function(channel) {
    try {
      let context = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
      let frame = context.topFrameElement;
      if (!frame) {
        // This function returns a boolean value
        return !!context.nestedFrameId;
      }

      if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame))
        return false;

      return true;
    } catch (e) {
      return false;
    }
  }
};

function BrowserElementPromptFactory(toWrap) {
  this._wrapped = toWrap;
}

BrowserElementPromptFactory.prototype = {
  classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory]),

  _mayUseNativePrompt: function() {
    try {
      return Services.prefs.getBoolPref("browser.prompt.allowNative");
    } catch (e) {
      // This properity is default to true.
      return true;
    }
  },

  _getNativePromptIfAllowed: function(win, iid, err) {
    if (this._mayUseNativePrompt())
      return this._wrapped.getPrompt(win, iid);
    else {
      // Not allowed, throw an exception.
      throw err;
    }
  },

  getPrompt: function(win, iid) {
    // It is possible for some object to get a prompt without passing
    // valid reference of window, like nsNSSComponent. In such case, we
    // should just fall back to the native prompt service
    if (!win)
      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);

    if (iid.number != Ci.nsIPrompt.number &&
        iid.number != Ci.nsIAuthPrompt2.number) {
      debug("We don't recognize the requested IID (" + iid + ", " +
            "allowed IID: " +
            "nsIPrompt=" + Ci.nsIPrompt + ", " +
            "nsIAuthPrompt2=" + Ci.nsIAuthPrompt2 + ")");
      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
    }

    // Try to find a BrowserElementChild for the window.
    let browserElementChild =
      BrowserElementPromptService.getBrowserElementChildForWindow(win);

    if (iid.number === Ci.nsIAuthPrompt2.number) {
      debug("Caller requests an instance of nsIAuthPrompt2.");

      if (browserElementChild) {
        // If we are able to get a BrowserElementChild, it means that
        // the auth prompt is for a mozbrowser. Therefore we don't need to
        // fall back.
        return new BrowserElementAuthPrompt().QueryInterface(iid);
      }

      // Because nsIAuthPrompt2 is called in parent process. If caller
      // wants nsIAuthPrompt2 and we cannot get BrowserElementchild,
      // it doesn't mean that we should fallback. It is possible that we can
      // get the BrowserElementParent from nsIChannel that passed to
      // functions of nsIAuthPrompt2.
      if (this._mayUseNativePrompt()) {
        return new AuthPromptWrapper(
            this._wrapped.getPrompt(win, iid),
            new BrowserElementAuthPrompt().QueryInterface(iid))
          .QueryInterface(iid);
      } else {
        // Falling back is not allowed, so we don't need wrap the
        // BrowserElementPrompt.
        return new BrowserElementAuthPrompt().QueryInterface(iid);
      }
    }

    if (!browserElementChild) {
      debug("We can't find a browserElementChild for " +
            win + ", " + win.location);
      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE);
    }

    debug("Returning wrapped getPrompt for " + win);
    return new BrowserElementPrompt(win, browserElementChild)
                                   .QueryInterface(iid);
  }
};

this.BrowserElementPromptService = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  _initialized: false,

  _init: function() {
    if (this._initialized) {
      return;
    }

    // If the pref is disabled, do nothing except wait for the pref to change.
    if (!this._browserFramesPrefEnabled()) {
      var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
      prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
      return;
    }

    this._initialized = true;
    this._browserElementParentMap = new WeakMap();

    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
    os.addObserver(this, "outer-window-destroyed", /* ownsWeak = */ true);

    // Wrap the existing @mozilla.org/prompter;1 implementation.
    var contractID = "@mozilla.org/prompter;1";
    var oldCID = Cm.contractIDToCID(contractID);
    var newCID = BrowserElementPromptFactory.prototype.classID;
    var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory);

    if (oldCID == newCID) {
      debug("WARNING: Wrapped prompt factory is already installed!");
      return;
    }

    Cm.unregisterFactory(oldCID, oldFactory);

    var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory);
    var newInstance = new BrowserElementPromptFactory(oldInstance);

    var newFactory = {
      createInstance: function(outer, iid) {
        if (outer != null) {
          throw Cr.NS_ERROR_NO_AGGREGATION;
        }
        return newInstance.QueryInterface(iid);
      }
    };
    Cm.registerFactory(newCID,
                       "BrowserElementPromptService's prompter;1 wrapper",
                       contractID, newFactory);

    debug("Done installing new prompt factory.");
  },

  _getOuterWindowID: function(win) {
    return win.QueryInterface(Ci.nsIInterfaceRequestor)
              .getInterface(Ci.nsIDOMWindowUtils)
              .outerWindowID;
  },

  _browserElementChildMap: {},
  mapWindowToBrowserElementChild: function(win, browserElementChild) {
    this._browserElementChildMap[this._getOuterWindowID(win)] = browserElementChild;
  },
  unmapWindowToBrowserElementChild: function(win) {
    delete this._browserElementChildMap[this._getOuterWindowID(win)];
  },

  getBrowserElementChildForWindow: function(win) {
    // We only have a mapping for <iframe mozbrowser>s, not their inner
    // <iframes>, so we look up win.top below.  window.top (when called from
    // script) respects <iframe mozbrowser> boundaries.
    return this._browserElementChildMap[this._getOuterWindowID(win.top)];
  },

  mapFrameToBrowserElementParent: function(frame, browserElementParent) {
    this._browserElementParentMap.set(frame, browserElementParent);
  },

  getBrowserElementParentForFrame: function(frame) {
    return this._browserElementParentMap.get(frame);
  },

  _observeOuterWindowDestroyed: function(outerWindowID) {
    let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data;
    debug("observeOuterWindowDestroyed " + id);
    delete this._browserElementChildMap[outerWindowID.data];
  },

  _browserFramesPrefEnabled: function() {
    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
    try {
      return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
    }
    catch(e) {
      return false;
    }
  },

  observe: function(subject, topic, data) {
    switch(topic) {
    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
      if (data == BROWSER_FRAMES_ENABLED_PREF) {
        this._init();
      }
      break;
    case "outer-window-destroyed":
      this._observeOuterWindowDestroyed(subject);
      break;
    default:
      debug("Observed unexpected topic " + topic);
    }
  }
};

BrowserElementPromptService._init();
PK
!<.oː__modules/BrowserUtils.jsm/* -*- 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";

this.EXPORTED_SYMBOLS = [ "BrowserUtils" ];

const {interfaces: Ci, utils: Cu, classes: Cc} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");

Cu.importGlobalProperties(["URL"]);

let reflowObservers = new WeakMap();

function ReflowObserver(doc) {
  this._doc = doc;

  doc.docShell.addWeakReflowObserver(this);
  reflowObservers.set(this._doc, this);

  this.callbacks = [];
}

ReflowObserver.prototype = {
  QueryInterface: XPCOMUtils.generateQI(["nsIReflowObserver", "nsISupportsWeakReference"]),

  _onReflow() {
    reflowObservers.delete(this._doc);
    this._doc.docShell.removeWeakReflowObserver(this);

    for (let callback of this.callbacks) {
      try {
        callback();
      } catch (e) {
        Cu.reportError(e);
      }
    }
  },

  reflow() {
    this._onReflow();
  },

  reflowInterruptible() {
    this._onReflow();
  },
};

const FLUSH_TYPES = {
  "style": Ci.nsIDOMWindowUtils.FLUSH_STYLE,
  "layout": Ci.nsIDOMWindowUtils.FLUSH_LAYOUT,
  "display": Ci.nsIDOMWindowUtils.FLUSH_DISPLAY,
};

this.BrowserUtils = {

  /**
   * Prints arguments separated by a space and appends a new line.
   */
  dumpLn(...args) {
    for (let a of args)
      dump(a + " ");
    dump("\n");
  },

  /**
   * restartApplication: Restarts the application, keeping it in
   * safe mode if it is already in safe mode.
   */
  restartApplication() {
    let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
                       .getService(Ci.nsIAppStartup);
    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                       .createInstance(Ci.nsISupportsPRBool);
    Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
    if (cancelQuit.data) { // The quit request has been canceled.
      return false;
    }
    // if already in safe mode restart in safe mode
    if (Services.appinfo.inSafeMode) {
      appStartup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
      return undefined;
    }
    appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
    return undefined;
  },

  /**
   * urlSecurityCheck: JavaScript wrapper for checkLoadURIWithPrincipal
   * and checkLoadURIStrWithPrincipal.
   * If |aPrincipal| is not allowed to link to |aURL|, this function throws with
   * an error message.
   *
   * @param aURL
   *        The URL a page has linked to. This could be passed either as a string
   *        or as a nsIURI object.
   * @param aPrincipal
   *        The principal of the document from which aURL came.
   * @param aFlags
   *        Flags to be passed to checkLoadURIStr. If undefined,
   *        nsIScriptSecurityManager.STANDARD will be passed.
   */
  urlSecurityCheck(aURL, aPrincipal, aFlags) {
    var secMan = Services.scriptSecurityManager;
    if (aFlags === undefined) {
      aFlags = secMan.STANDARD;
    }

    try {
      if (aURL instanceof Ci.nsIURI)
        secMan.checkLoadURIWithPrincipal(aPrincipal, aURL, aFlags);
      else
        secMan.checkLoadURIStrWithPrincipal(aPrincipal, aURL, aFlags);
    } catch (e) {
      let principalStr = "";
      try {
        principalStr = " from " + aPrincipal.URI.spec;
      } catch (e2) { }

      throw "Load of " + aURL + principalStr + " denied.";
    }
  },

  /**
   * Return or create a principal with the codebase of one, and the originAttributes
   * of an existing principal (e.g. on a docshell, where the originAttributes ought
   * not to change, that is, we should keep the userContextId, privateBrowsingId,
   * etc. the same when changing the principal).
   *
   * @param principal
   *        The principal whose codebase/null/system-ness we want.
   * @param existingPrincipal
   *        The principal whose originAttributes we want, usually the current
   *        principal of a docshell.
   * @return an nsIPrincipal that matches the codebase/null/system-ness of the first
   *         param, and the originAttributes of the second.
   */
  principalWithMatchingOA(principal, existingPrincipal) {
    // Don't care about system principals:
    if (principal.isSystemPrincipal) {
      return principal;
    }

    // If the originAttributes already match, just return the principal as-is.
    if (existingPrincipal.originSuffix == principal.originSuffix) {
      return principal;
    }

    let secMan = Services.scriptSecurityManager;
    if (principal.isCodebasePrincipal) {
      return secMan.createCodebasePrincipal(principal.URI, existingPrincipal.originAttributes);
    }

    if (principal.isNullPrincipal) {
      return secMan.createNullPrincipal(existingPrincipal.originAttributes);
    }
    throw new Error("Can't change the originAttributes of an expanded principal!");
  },

  /**
   * Constructs a new URI, using nsIIOService.
   * @param aURL The URI spec.
   * @param aOriginCharset The charset of the URI.
   * @param aBaseURI Base URI to resolve aURL, or null.
   * @return an nsIURI object based on aURL.
   *
   * @deprecated Use Services.io.newURI directly instead.
   */
  makeURI(aURL, aOriginCharset, aBaseURI) {
    return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
  },

  /**
   * @deprecated Use Services.io.newFileURI directly instead.
   */
  makeFileURI(aFile) {
    return Services.io.newFileURI(aFile);
  },

  makeURIFromCPOW(aCPOWURI) {
    return Services.io.newURI(aCPOWURI.spec, aCPOWURI.originCharset);
  },

  /**
   * For a given DOM element, returns its position in "screen"
   * coordinates. In a content process, the coordinates returned will
   * be relative to the left/top of the tab. In the chrome process,
   * the coordinates are relative to the user's screen.
   */
  getElementBoundingScreenRect(aElement) {
    return this.getElementBoundingRect(aElement, true);
  },

  /**
   * For a given DOM element, returns its position as an offset from the topmost
   * window. In a content process, the coordinates returned will be relative to
   * the left/top of the topmost content area. If aInScreenCoords is true,
   * screen coordinates will be returned instead.
   */
  getElementBoundingRect(aElement, aInScreenCoords) {
    let rect = aElement.getBoundingClientRect();
    let win = aElement.ownerGlobal;

    let x = rect.left, y = rect.top;

    // We need to compensate for any iframes that might shift things
    // over. We also need to compensate for zooming.
    let parentFrame = win.frameElement;
    while (parentFrame) {
      win = parentFrame.ownerGlobal;
      let cstyle = win.getComputedStyle(parentFrame);

      let framerect = parentFrame.getBoundingClientRect();
      x += framerect.left + parseFloat(cstyle.borderLeftWidth) + parseFloat(cstyle.paddingLeft);
      y += framerect.top + parseFloat(cstyle.borderTopWidth) + parseFloat(cstyle.paddingTop);

      parentFrame = win.frameElement;
    }

    if (aInScreenCoords) {
      x += win.mozInnerScreenX;
      y += win.mozInnerScreenY;
    }

    let fullZoom = win.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
    rect = {
      left: x * fullZoom,
      top: y * fullZoom,
      width: rect.width * fullZoom,
      height: rect.height * fullZoom
    };

    return rect;
  },

  onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
    // Don't modify non-default targets or targets that aren't in top-level app
    // tab docshells (isAppTab will be false for app tab subframes).
    if (originalTarget != "" || !isAppTab)
      return originalTarget;

    // External links from within app tabs should always open in new tabs
    // instead of replacing the app tab's page (Bug 575561)
    let linkHost;
    let docHost;
    try {
      linkHost = linkURI.host;
      docHost = linkNode.ownerDocument.documentURIObject.host;
    } catch (e) {
      // nsIURI.host can throw for non-nsStandardURL nsIURIs.
      // If we fail to get either host, just return originalTarget.
      return originalTarget;
    }

    if (docHost == linkHost)
      return originalTarget;

    // Special case: ignore "www" prefix if it is part of host string
    let [longHost, shortHost] =
      linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
    if (longHost == "www." + shortHost)
      return originalTarget;

    return "_blank";
  },

  /**
   * Map the plugin's name to a filtered version more suitable for UI.
   *
   * @param aName The full-length name string of the plugin.
   * @return the simplified name string.
   */
  makeNicePluginName(aName) {
    if (aName == "Shockwave Flash")
      return "Adobe Flash";
    // Regex checks if aName begins with "Java" + non-letter char
    if (/^Java\W/.exec(aName))
      return "Java";

    // Clean up the plugin name by stripping off parenthetical clauses,
    // trailing version numbers or "plugin".
    // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
    // Do this by first stripping the numbers, etc. off the end, and then
    // removing "Plugin" (and then trimming to get rid of any whitespace).
    // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
    let newName = aName.replace(/\(.*?\)/g, "").
                        replace(/[\s\d\.\-\_\(\)]+$/, "").
                        replace(/\bplug-?in\b/i, "").trim();
    return newName;
  },

  /**
   * Return true if linkNode has a rel="noreferrer" attribute.
   *
   * @param linkNode The <a> element, or null.
   * @return a boolean indicating if linkNode has a rel="noreferrer" attribute.
   */
  linkHasNoReferrer(linkNode) {
    // A null linkNode typically means that we're checking a link that wasn't
    // provided via an <a> link, like a text-selected URL.  Don't leak
    // referrer information in this case.
    if (!linkNode)
      return true;

    let rel = linkNode.getAttribute("rel");
    if (!rel)
      return false;

    // The HTML spec says that rel should be split on spaces before looking
    // for particular rel values.
    let values = rel.split(/[ \t\r\n\f]/);
    return values.indexOf("noreferrer") != -1;
  },

  /**
   * Returns true if |mimeType| is text-based, or false otherwise.
   *
   * @param mimeType
   *        The MIME type to check.
   */
  mimeTypeIsTextBased(mimeType) {
    return mimeType.startsWith("text/") ||
           mimeType.endsWith("+xml") ||
           mimeType == "application/x-javascript" ||
           mimeType == "application/javascript" ||
           mimeType == "application/json" ||
           mimeType == "application/xml" ||
           mimeType == "mozilla.application/cached-xul";
  },

  /**
   * Return true if we should FAYT for this node + window (could be CPOW):
   *
   * @param elt
   *        The element that is focused
   * @param win
   *        The window that is focused
   *
   */
  shouldFastFind(elt, win) {
    if (elt) {
      if (elt instanceof win.HTMLInputElement && elt.mozIsTextField(false))
        return false;

      if (elt.isContentEditable || win.document.designMode == "on")
        return false;

      if (elt instanceof win.HTMLTextAreaElement ||
          elt instanceof win.HTMLSelectElement ||
          elt instanceof win.HTMLObjectElement ||
          elt instanceof win.HTMLEmbedElement)
        return false;
    }

    return true;
  },

  /**
   * Return true if we can FAYT for this window (could be CPOW):
   *
   * @param win
   *        The top level window that is focused
   *
   */
  canFastFind(win) {
    if (!win)
      return false;

    if (!this.mimeTypeIsTextBased(win.document.contentType))
      return false;

    // disable FAYT in about:blank to prevent FAYT opening unexpectedly.
    let loc = win.location;
    if (loc.href == "about:blank")
      return false;

    // disable FAYT in documents that ask for it to be disabled.
    if ((loc.protocol == "about:" || loc.protocol == "chrome:") &&
        (win.document.documentElement &&
         win.document.documentElement.getAttribute("disablefastfind") == "true"))
      return false;

    return true;
  },

  _visibleToolbarsMap: new WeakMap(),

  /**
   * Return true if any or a specific toolbar that interacts with the content
   * document is visible.
   *
   * @param  {nsIDocShell} docShell The docShell instance that a toolbar should
   *                                be interacting with
   * @param  {String}      which    Identifier of a specific toolbar
   * @return {Boolean}
   */
  isToolbarVisible(docShell, which) {
    let window = this.getRootWindow(docShell);
    if (!this._visibleToolbarsMap.has(window))
      return false;
    let toolbars = this._visibleToolbarsMap.get(window);
    return !!toolbars && toolbars.has(which);
  },

  /**
   * Sets the --toolbarbutton-button-height CSS property on the closest
   * toolbar to the provided element. Useful if you need to vertically
   * center a position:absolute element within a toolbar that uses
   * -moz-pack-align:stretch, and thus a height which is dependant on
   * the font-size.
   *
   * @param element An element within the toolbar whose height is desired.
   * @param options An object with the following properties:
              {
                forceLayoutFlushIfNeeded:
                  Set to true if a sync layout flush is acceptable.
              }
   */
  setToolbarButtonHeightProperty(element, options) {
    let window = element.ownerGlobal;
    let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
    let toolbarItem = element;
    let urlBarContainer = element.closest("#urlbar-container");
    if (urlBarContainer) {
      // The stop-reload-button, which is contained in #urlbar-container,
      // needs to use #urlbar-container to calculate the bounds.
      toolbarItem = urlBarContainer;
    }
    if (!toolbarItem) {
      return;
    }
    let bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
    if (!bounds.height && options.forceLayoutFlushIfNeeded) {
      bounds = toolbarItem.getBoundingClientRect();
    }
    if (bounds.height) {
      toolbarItem.style.setProperty("--toolbarbutton-height", bounds.height + "px");
    }
  },

  /**
   * Track whether a toolbar is visible for a given a docShell.
   *
   * @param  {nsIDocShell} docShell  The docShell instance that a toolbar should
   *                                 be interacting with
   * @param  {String}      which     Identifier of a specific toolbar
   * @param  {Boolean}     [visible] Whether the toolbar is visible. Optional,
   *                                 defaults to `true`.
   */
  trackToolbarVisibility(docShell, which, visible = true) {
    // We have to get the root window object, because XPConnect WrappedNatives
    // can't be used as WeakMap keys.
    let window = this.getRootWindow(docShell);
    let toolbars = this._visibleToolbarsMap.get(window);
    if (!toolbars) {
      toolbars = new Set();
      this._visibleToolbarsMap.set(window, toolbars);
    }
    if (!visible)
      toolbars.delete(which);
    else
      toolbars.add(which);
  },

  /**
   * Retrieve the root window object (i.e. the top-most content global) for a
   * specific docShell object.
   *
   * @param  {nsIDocShell} docShell
   * @return {nsIDOMWindow}
   */
  getRootWindow(docShell) {
    return docShell.QueryInterface(Ci.nsIDocShellTreeItem)
      .sameTypeRootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindow);
  },

  getSelectionDetails(topWindow, aCharLen) {
    // selections of more than 150 characters aren't useful
    const kMaxSelectionLen = 150;
    const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);

    let focusedWindow = {};
    let focusedElement = Services.focus.getFocusedElementForWindow(topWindow, true, focusedWindow);
    focusedWindow = focusedWindow.value;

    let selection = focusedWindow.getSelection();
    let selectionStr = selection.toString();
    let fullText;

    let collapsed = selection.isCollapsed;

    let url;
    let linkText;
    if (selectionStr) {
      // Have some text, let's figure out if it looks like a URL that isn't
      // actually a link.
      linkText = selectionStr.trim();
      if (/^(?:https?|ftp):/i.test(linkText)) {
        try {
          url = this.makeURI(linkText);
        } catch (ex) {}
      } else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
        // Check if this could be a valid url, just missing the protocol.
        // Now let's see if this is an intentional link selection. Our guess is
        // based on whether the selection begins/ends with whitespace or is
        // preceded/followed by a non-word character.

        // selection.toString() trims trailing whitespace, so we look for
        // that explicitly in the first and last ranges.
        let beginRange = selection.getRangeAt(0);
        let delimitedAtStart = /^\s/.test(beginRange);
        if (!delimitedAtStart) {
          let container = beginRange.startContainer;
          let offset = beginRange.startOffset;
          if (container.nodeType == container.TEXT_NODE && offset > 0)
            delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
          else
            delimitedAtStart = true;
        }

        let delimitedAtEnd = false;
        if (delimitedAtStart) {
          let endRange = selection.getRangeAt(selection.rangeCount - 1);
          delimitedAtEnd = /\s$/.test(endRange);
          if (!delimitedAtEnd) {
            let container = endRange.endContainer;
            let offset = endRange.endOffset;
            if (container.nodeType == container.TEXT_NODE &&
                offset < container.textContent.length)
              delimitedAtEnd = /\W/.test(container.textContent[offset]);
            else
              delimitedAtEnd = true;
          }
        }

        if (delimitedAtStart && delimitedAtEnd) {
          let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
                           .getService(Ci.nsIURIFixup);
          try {
            url = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
          } catch (ex) {}
        }
      }
    }

    // try getting a selected text in text input.
    if (!selectionStr && focusedElement instanceof Ci.nsIDOMNSEditableElement) {
      // Don't get the selection for password fields. See bug 565717.
      if (focusedElement instanceof Ci.nsIDOMHTMLTextAreaElement ||
          (focusedElement instanceof Ci.nsIDOMHTMLInputElement &&
           focusedElement.mozIsTextField(true))) {
        selectionStr = focusedElement.editor.selection.toString();
      }
    }

    if (selectionStr) {
      // Pass up to 16K through unmolested.  If an add-on needs more, they will
      // have to use a content script.
      fullText = selectionStr.substr(0, 16384);

      if (selectionStr.length > charLen) {
        // only use the first charLen important chars. see bug 221361
        var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
        pattern.test(selectionStr);
        selectionStr = RegExp.lastMatch;
      }

      selectionStr = selectionStr.trim().replace(/\s+/g, " ");

      if (selectionStr.length > charLen) {
        selectionStr = selectionStr.substr(0, charLen);
      }
    }

    if (url && !url.host) {
      url = null;
    }

    return { text: selectionStr, docSelectionIsCollapsed: collapsed, fullText,
             linkURL: url ? url.spec : null, linkText: url ? linkText : "" };
  },

  // Iterates through every docshell in the window and calls PermitUnload.
  canCloseWindow(window) {
    let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIWebNavigation);
    let node = docShell.QueryInterface(Ci.nsIDocShellTreeItem);
    for (let i = 0; i < node.childCount; ++i) {
      let docShell = node.getChildAt(i).QueryInterface(Ci.nsIDocShell);
      let contentViewer = docShell.contentViewer;
      if (contentViewer && !contentViewer.permitUnload()) {
        return false;
      }
    }

    return true;
  },

  /**
   * Replaces %s or %S in the provided url or postData with the given parameter,
   * acccording to the best charset for the given url.
   *
   * @return [url, postData]
   * @throws if nor url nor postData accept a param, but a param was provided.
   */
  async parseUrlAndPostData(url, postData, param) {
    let hasGETParam = /%s/i.test(url)
    let decodedPostData = postData ? unescape(postData) : "";
    let hasPOSTParam = /%s/i.test(decodedPostData);

    if (!hasGETParam && !hasPOSTParam) {
      if (param) {
        // If nor the url, nor postData contain parameters, but a parameter was
        // provided, return the original input.
        throw new Error("A param was provided but there's nothing to bind it to");
      }
      return [url, postData];
    }

    let charset = "";
    const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
    let matches = url.match(re);
    if (matches) {
      [, url, charset] = matches;
    } else {
      // Try to fetch a charset from History.
      try {
        // Will return an empty string if character-set is not found.
        charset = await PlacesUtils.getCharsetForURI(this.makeURI(url));
      } catch (ex) {
        // makeURI() throws if url is invalid.
        Cu.reportError(ex);
      }
    }

    // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
    // escape() works in those cases, but it doesn't uri-encode +, @, and /.
    // Therefore we need to manually replace these ASCII characters by their
    // encodeURIComponent result, to match the behavior of nsEscape() with
    // url_XPAlphas.
    let encodedParam = "";
    if (charset && charset != "UTF-8") {
      try {
        let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                          .createInstance(Ci.nsIScriptableUnicodeConverter);
        converter.charset = charset;
        encodedParam = converter.ConvertFromUnicode(param) + converter.Finish();
      } catch (ex) {
        encodedParam = param;
      }
      encodedParam = escape(encodedParam).replace(/[+@\/]+/g, encodeURIComponent);
    } else {
      // Default charset is UTF-8
      encodedParam = encodeURIComponent(param);
    }

    url = url.replace(/%s/g, encodedParam).replace(/%S/g, param);
    if (hasPOSTParam) {
      postData = decodedPostData.replace(/%s/g, encodedParam)
                                .replace(/%S/g, param);
    }
    return [url, postData];
  },

  /**
   * Calls the given function when the given document has just reflowed,
   * and returns a promise which resolves to its return value after it
   * has been called.
   *
   * The function *must not trigger any reflows*, or make any changes
   * which would require a layout flush.
   *
   * @param {Document} doc
   * @param {function} callback
   * @returns {Promise}
   */
  promiseReflowed(doc, callback) {
    let observer = reflowObservers.get(doc);
    if (!observer) {
      observer = new ReflowObserver(doc);
      reflowObservers.set(doc, observer);
    }

    return new Promise((resolve, reject) => {
      observer.callbacks.push(() => {
        try {
          resolve(callback());
        } catch (e) {
          reject(e);
        }
      });
    });
  },

  /**
   * Calls the given function as soon as a layout flush of the given
   * type is not necessary, and returns a promise which resolves to the
   * callback's return value after it executes.
   *
   * The function *must not trigger any reflows*, or make any changes
   * which would require a layout flush.
   *
   * @param {Document} doc
   * @param {string} flushType
   *        The flush type required. Must be one of:
   *
   *          - "style"
   *          - "layout"
   *          - "display"
   * @param {function} callback
   * @returns {Promise}
   */
  async promiseLayoutFlushed(doc, flushType, callback) {
    let utils = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);

    if (!utils.needsFlush(FLUSH_TYPES[flushType])) {
      return callback();
    }

    return this.promiseReflowed(doc, callback);
  },
};
PK
!<+?modules/CanonicalJSON.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["CanonicalJSON"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "jsesc",
                                  "resource://gre/modules/third_party/jsesc/jsesc.js");

this.CanonicalJSON = {
  /**
   * Return the canonical JSON form of the passed source, sorting all the object
   * keys recursively. Note that this method will cause an infinite loop if
   * cycles exist in the source (bug 1265357).
   *
   * @param source
   *        The elements to be serialized.
   *
   * The output will have all unicode chars escaped with the unicode codepoint
   * as lowercase hexadecimal.
   *
   * @usage
   *        CanonicalJSON.stringify(listOfRecords);
   **/
  stringify: function stringify(source) {
    if (Array.isArray(source)) {
      const jsonArray = source.map(x => typeof x === "undefined" ? null : x);
      return `[${jsonArray.map(stringify).join(",")}]`;
    }

    if (typeof source === "number") {
      if (source === 0) {
        return (Object.is(source, -0)) ? "-0" : "0";
      }
    }

    // Leverage jsesc library, mainly for unicode escaping.
    const toJSON = (input) => jsesc(input, {lowercaseHex: true, json: true});

    if (typeof source !== "object" || source === null) {
      return toJSON(source);
    }

    // Dealing with objects, ordering keys.
    const sortedKeys = Object.keys(source).sort();
    const lastIndex = sortedKeys.length - 1;
    return sortedKeys.reduce((serial, key, index) => {
      const value = source[key];
      // JSON.stringify drops keys with an undefined value.
      if (typeof value === "undefined") {
        return serial;
      }
      const jsonValue = value && value.toJSON ? value.toJSON() : value;
      const suffix = index !== lastIndex ? "," : "";
      const escapedKey = toJSON(key);
      return serial + `${escapedKey}:${stringify(jsonValue)}${suffix}`;
    }, "{") + "}";
  },
};
PK
!<<杢modules/CertUtils.jsm/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert", "readCertPrefs", "validateCert" ];

const Ce = Components.Exception;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Components.utils.import("resource://gre/modules/Services.jsm");

/**
 * Reads a set of expected certificate attributes from preferences. The returned
 * array can be passed to validateCert or checkCert to validate that a
 * certificate matches the expected attributes. The preferences should look like
 * this:
 *   prefix.1.attribute1
 *   prefix.1.attribute2
 *   prefix.2.attribute1
 *   etc.
 * Each numeric branch contains a set of required attributes for a single
 * certificate. Having multiple numeric branches means that multiple
 * certificates would be accepted by validateCert.
 *
 * @param  aPrefBranch
 *         The prefix for all preferences, should end with a ".".
 * @return An array of JS objects with names / values corresponding to the
 *         expected certificate's attribute names / values.
 */
this.readCertPrefs =
  function readCertPrefs(aPrefBranch) {
  if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0)
    return null;

  let certs = [];
  let counter = 1;
  while (true) {
    let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
    let prefCertAttrs = prefBranchCert.getChildList("");
    if (prefCertAttrs.length == 0)
      break;

    let certAttrs = {};
    for (let prefCertAttr of prefCertAttrs)
      certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);

    certs.push(certAttrs);
    counter++;
  }

  return certs;
}

/**
 * Verifies that an nsIX509Cert matches the expected certificate attribute
 * values.
 *
 * @param  aCertificate
 *         The nsIX509Cert to compare to the expected attributes.
 * @param  aCerts
 *         An array of JS objects with names / values corresponding to the
 *         expected certificate's attribute names / values. If this is null or
 *         an empty array then no checks are performed.
 * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
 *         aCerts param does not exist or the value for a certificate attribute
 *         from the aCerts param is different than the expected value or
 *         aCertificate wasn't specified and aCerts is not null or an empty
 *         array.
 */
this.validateCert =
  function validateCert(aCertificate, aCerts) {
  // If there are no certificate requirements then just exit
  if (!aCerts || aCerts.length == 0)
    return;

  if (!aCertificate) {
    const missingCertErr = "A required certificate was not present.";
    Cu.reportError(missingCertErr);
    throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE);
  }

  var errors = [];
  for (var i = 0; i < aCerts.length; ++i) {
    var error = false;
    var certAttrs = aCerts[i];
    for (var name in certAttrs) {
      if (!(name in aCertificate)) {
        error = true;
        errors.push("Expected attribute '" + name + "' not present in " +
                    "certificate.");
        break;
      }
      if (aCertificate[name] != certAttrs[name]) {
        error = true;
        errors.push("Expected certificate attribute '" + name + "' " +
                    "value incorrect, expected: '" + certAttrs[name] +
                    "', got: '" + aCertificate[name] + "'.");
        break;
      }
    }

    if (!error)
      break;
  }

  if (error) {
    errors.forEach(Cu.reportError.bind(Cu));
    const certCheckErr = "Certificate checks failed. See previous errors " +
                         "for details.";
    Cu.reportError(certCheckErr);
    throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
  }
}

/**
 * Checks if the connection must be HTTPS and if so, only allows built-in
 * certificates and validates application specified certificate attribute
 * values.
 * See bug 340198 and bug 544442.
 *
 * @param  aChannel
 *         The nsIChannel that will have its certificate checked.
 * @param  aAllowNonBuiltInCerts (optional)
 *         When true certificates that aren't builtin are allowed. When false
 *         or not specified the certificate must be a builtin certificate.
 * @param  aCerts (optional)
 *         An array of JS objects with names / values corresponding to the
 *         channel's expected certificate's attribute names / values. If it
 *         isn't null or not specified the the scheme for the channel's
 *         originalURI must be https.
 * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
 *         is not https.
 *         NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
 *         aCerts param does not exist or the value for a certificate attribute
 *         from the aCerts  param is different than the expected value.
 *         NS_ERROR_ABORT if the certificate issuer is not built-in.
 */
this.checkCert =
  function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
  if (!aChannel.originalURI.schemeIs("https")) {
    // Require https if there are certificate values to verify
    if (aCerts) {
      throw new Ce("SSL is required and URI scheme is not https.",
                   Cr.NS_ERROR_UNEXPECTED);
    }
    return;
  }

  var cert =
      aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
      SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;

  validateCert(cert, aCerts);

  if (aAllowNonBuiltInCerts === true)
    return;

  var issuerCert = cert;
  while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert))
    issuerCert = issuerCert.issuer;

  const certNotBuiltInErr = "Certificate issuer is not built-in.";
  if (!issuerCert)
    throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);

  if (!issuerCert.isBuiltInRoot)
    throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
}

/**
 * This class implements nsIBadCertListener.  Its job is to prevent "bad cert"
 * security dialogs from being shown to the user.  It is better to simply fail
 * if the certificate is bad. See bug 304286.
 *
 * @param  aAllowNonBuiltInCerts (optional)
 *         When true certificates that aren't builtin are allowed. When false
 *         or not specified the certificate must be a builtin certificate.
 */
this.BadCertHandler =
  function BadCertHandler(aAllowNonBuiltInCerts) {
  this.allowNonBuiltInCerts = aAllowNonBuiltInCerts;
}
BadCertHandler.prototype = {

  // nsIChannelEventSink
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    if (this.allowNonBuiltInCerts) {
      callback.onRedirectVerifyCallback(Components.results.NS_OK);
      return;
    }

    // make sure the certificate of the old channel checks out before we follow
    // a redirect from it.  See bug 340198.
    // Don't call checkCert for internal redirects. See bug 569648.
    if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL))
      checkCert(oldChannel);

    callback.onRedirectVerifyCallback(Components.results.NS_OK);
  },

  // nsIInterfaceRequestor
  getInterface(iid) {
    return this.QueryInterface(iid);
  },

  // nsISupports
  QueryInterface(iid) {
    if (!iid.equals(Ci.nsIChannelEventSink) &&
        !iid.equals(Ci.nsIInterfaceRequestor) &&
        !iid.equals(Ci.nsISupports))
      throw Cr.NS_ERROR_NO_INTERFACE;
    return this;
  }
};
PK
!<!modules/CharsetMenu.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "CharsetMenu" ];

const { classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
  const kUrl = "chrome://global/locale/charsetMenu.properties";
  return Services.strings.createBundle(kUrl);
});

XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
    "resource://gre/modules/Deprecated.jsm");

const kAutoDetectors = [
  ["off", ""],
  ["ja", "ja_parallel_state_machine"],
  ["ru", "ruprob"],
  ["uk", "ukprob"]
];

/**
 * This set contains encodings that are in the Encoding Standard, except:
 *  - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
 *    too common not to be included).
 *  - x-user-defined, which practically never makes sense as an end-user-chosen
 *    override.
 *  - Encodings that IE11 doesn't have in its correspoding menu.
 */
const kEncodings = new Set([
  // Globally relevant
  "UTF-8",
  "windows-1252",
  // Arabic
  "windows-1256",
  "ISO-8859-6",
  // Baltic
  "windows-1257",
  "ISO-8859-4",
  // "ISO-8859-13", // Hidden since not in menu in IE11
  // Central European
  "windows-1250",
  "ISO-8859-2",
  // Chinese, Simplified
  "GBK",
  // Chinese, Traditional
  "Big5",
  // Cyrillic
  "windows-1251",
  "ISO-8859-5",
  "KOI8-R",
  "KOI8-U",
  "IBM866", // Not in menu in Chromium. Maybe drop this?
  // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
  // Greek
  "windows-1253",
  "ISO-8859-7",
  // Hebrew
  "windows-1255",
  "ISO-8859-8",
  // Japanese
  "Shift_JIS",
  "EUC-JP",
  "ISO-2022-JP",
  // Korean
  "EUC-KR",
  // Thai
  "windows-874",
  // Turkish
  "windows-1254",
  // Vietnamese
  "windows-1258",
  // Hiding rare European encodings that aren't in the menu in IE11 and would
  // make the menu messy by sorting all over the place
  // "ISO-8859-3",
  // "ISO-8859-10",
  // "ISO-8859-14",
  // "ISO-8859-15",
  // "ISO-8859-16",
  // "macintosh"
]);

// Always at the start of the menu, in this order, followed by a separator.
const kPinned = [
  "UTF-8",
  "windows-1252"
];

kPinned.forEach(x => kEncodings.delete(x));

function CharsetComparator(a, b) {
  // Normal sorting sorts the part in parenthesis in an order that
  // happens to make the less frequently-used items first.
  let titleA = a.label.replace(/\(.*/, "") + b.value;
  let titleB = b.label.replace(/\(.*/, "") + a.value;
  // Secondarily reverse sort by encoding name to sort "windows" or
  // "shift_jis" first.
  return titleA.localeCompare(titleB) || b.value.localeCompare(a.value);
}

function SetDetector(event) {
  Services.prefs.setStringPref("intl.charset.detector",
                               event.target.getAttribute("detector"));
}

function UpdateDetectorMenu(event) {
  event.stopPropagation();
  let detector = Services.prefs.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString);
  let menuitem = this.getElementsByAttribute("detector", detector).item(0);
  if (menuitem) {
    menuitem.setAttribute("checked", "true");
  }
}

var gDetectorInfoCache, gCharsetInfoCache, gPinnedInfoCache;

var CharsetMenu = {
  build(parent, deprecatedShowAccessKeys = true, showDetector = true) {
    if (!deprecatedShowAccessKeys) {
      Deprecated.warning("CharsetMenu no longer supports building a menu with no access keys.",
                         "https://bugzilla.mozilla.org/show_bug.cgi?id=1088710");
    }
    function createDOMNode(doc, nodeInfo) {
      let node = doc.createElement("menuitem");
      node.setAttribute("type", "radio");
      node.setAttribute("name", nodeInfo.name + "Group");
      node.setAttribute(nodeInfo.name, nodeInfo.value);
      node.setAttribute("label", nodeInfo.label);
      if (nodeInfo.accesskey) {
        node.setAttribute("accesskey", nodeInfo.accesskey);
      }
      return node;
    }

    if (parent.hasChildNodes()) {
      // Detector menu or charset menu already built
      return;
    }
    this._ensureDataReady();
    let doc = parent.ownerDocument;

    if (showDetector) {
      let menuNode = doc.createElement("menu");
      menuNode.setAttribute("label", gBundle.GetStringFromName("charsetMenuAutodet"));
      menuNode.setAttribute("accesskey", gBundle.GetStringFromName("charsetMenuAutodet.key"));
      parent.appendChild(menuNode);

      let menuPopupNode = doc.createElement("menupopup");
      menuNode.appendChild(menuPopupNode);
      menuPopupNode.addEventListener("command", SetDetector);
      menuPopupNode.addEventListener("popupshown", UpdateDetectorMenu);

      gDetectorInfoCache.forEach(detectorInfo => menuPopupNode.appendChild(createDOMNode(doc, detectorInfo)));
      parent.appendChild(doc.createElement("menuseparator"));
    }

    gPinnedInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
    parent.appendChild(doc.createElement("menuseparator"));
    gCharsetInfoCache.forEach(charsetInfo => parent.appendChild(createDOMNode(doc, charsetInfo)));
  },

  getData() {
    this._ensureDataReady();
    return {
      detectors: gDetectorInfoCache,
      pinnedCharsets: gPinnedInfoCache,
      otherCharsets: gCharsetInfoCache
    };
  },

  _ensureDataReady() {
    if (!gDetectorInfoCache) {
      gDetectorInfoCache = this.getDetectorInfo();
      gPinnedInfoCache = this.getCharsetInfo(kPinned, false);
      gCharsetInfoCache = this.getCharsetInfo(kEncodings);
    }
  },

  getDetectorInfo() {
    return kAutoDetectors.map(([detectorName, nodeId]) => ({
      label: this._getDetectorLabel(detectorName),
      accesskey: this._getDetectorAccesskey(detectorName),
      name: "detector",
      value: nodeId
    }));
  },

  getCharsetInfo(charsets, sort = true) {
    let list = Array.from(charsets, charset => ({
      label: this._getCharsetLabel(charset),
      accesskey: this._getCharsetAccessKey(charset),
      name: "charset",
      value: charset
    }));

    if (sort) {
      list.sort(CharsetComparator);
    }
    return list;
  },

  _getDetectorLabel(detector) {
    try {
      return gBundle.GetStringFromName("charsetMenuAutodet." + detector);
    } catch (ex) {}
    return detector;
  },
  _getDetectorAccesskey(detector) {
    try {
      return gBundle.GetStringFromName("charsetMenuAutodet." + detector + ".key");
    } catch (ex) {}
    return "";
  },

  _getCharsetLabel(charset) {
    if (charset == "GBK") {
      // Localization key has been revised
      charset = "gbk.bis";
    }
    try {
      return gBundle.GetStringFromName(charset);
    } catch (ex) {}
    return charset;
  },
  _getCharsetAccessKey(charset) {
    if (charset == "GBK") {
      // Localization key has been revised
      charset = "gbk.bis";
    }
    try {
      return gBundle.GetStringFromName(charset + ".key");
    } catch (ex) {}
    return "";
  },

  /**
   * For substantially similar encodings, treat two encodings as the same
   * for the purpose of the check mark.
   */
  foldCharset(charset) {
    switch (charset) {
      case "ISO-8859-8-I":
        return "windows-1255";

      case "gb18030":
        return "GBK";

      default:
        return charset;
    }
  },

  update(parent, charset) {
    let menuitem = parent.getElementsByAttribute("charset", this.foldCharset(charset)).item(0);
    if (menuitem) {
      menuitem.setAttribute("checked", "true");
    }
  },
};

Object.freeze(CharsetMenu);

PK
!<DsPP modules/ChromeManifestParser.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["ChromeManifestParser"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");

const MSG_JAR_FLUSH = "AddonJarFlush";


/**
 * Sends local and remote notifications to flush a JAR file cache entry
 *
 * @param aJarFile
 *        The ZIP/XPI/JAR file as a nsIFile
 */
function flushJarCache(aJarFile) {
  Services.obs.notifyObservers(aJarFile, "flush-cache-entry");
  Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster)
    .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}


/**
 * Parses chrome manifest files.
 */
this.ChromeManifestParser = {

  /**
   * Reads and parses a chrome manifest file located at a specified URI, and all
   * secondary manifests it references.
   *
   * @param  aURI
   *         A nsIURI pointing to a chrome manifest.
   *         Typically a file: or jar: URI.
   * @return Array of objects describing each manifest instruction, in the form:
   *         { type: instruction-type, baseURI: string-uri, args: [arguments] }
   **/
  parseSync(aURI) {
    function parseLine(aLine) {
      let line = aLine.trim();
      if (line.length == 0 || line.charAt(0) == "#")
        return;
      let tokens = line.split(/\s+/);
      let type = tokens.shift();
      if (type == "manifest") {
        let uri = NetUtil.newURI(tokens.shift(), null, aURI);
        data = data.concat(this.parseSync(uri));
      } else {
        data.push({type, baseURI, args: tokens});
      }
    }

    let contents = "";
    try {
      if (aURI.scheme == "jar")
        contents = this._readFromJar(aURI);
      else
        contents = this._readFromFile(aURI);
    } catch (e) {
      // Silently fail.
    }

    if (!contents)
      return [];

    let baseURI = NetUtil.newURI(".", null, aURI).spec;

    let data = [];
    let lines = contents.split("\n");
    lines.forEach(parseLine.bind(this));
    return data;
  },

  _readFromJar(aURI) {
    let data = "";
    let entries = [];
    let readers = [];

    try {
      // Deconstrict URI, which can be nested jar: URIs.
      let uri = aURI.clone();
      while (uri instanceof Ci.nsIJARURI) {
        entries.push(uri.JAREntry);
        uri = uri.JARFile;
      }

      // Open the base jar.
      let reader = Cc["@mozilla.org/libjar/zip-reader;1"].
                   createInstance(Ci.nsIZipReader);
      reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
      readers.push(reader);

      // Open the nested jars.
      for (let i = entries.length - 1; i > 0; i--) {
        let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                          createInstance(Ci.nsIZipReader);
        innerReader.openInner(reader, entries[i]);
        readers.push(innerReader);
        reader = innerReader;
      }

      // First entry is the actual file we want to read.
      let zis = reader.getInputStream(entries[0]);
      data = NetUtil.readInputStreamToString(zis, zis.available());
    } finally {
      // Close readers in reverse order.
      for (let i = readers.length - 1; i >= 0; i--) {
        readers[i].close();
        flushJarCache(readers[i].file);
      }
    }

    return data;
  },

  _readFromFile(aURI) {
    let file = aURI.QueryInterface(Ci.nsIFileURL).file;
    if (!file.exists() || !file.isFile())
      return "";

    let data = "";
    let fis = Cc["@mozilla.org/network/file-input-stream;1"].
              createInstance(Ci.nsIFileInputStream);
    try {
      fis.init(file, -1, -1, false);
      data = NetUtil.readInputStreamToString(fis, fis.available());
    } finally {
      fis.close();
    }
    return data;
  },

  /**
  * Detects if there were any instructions of a specified type in a given
  * chrome manifest.
  *
  * @param  aManifest
  *         Manifest data, as returned by ChromeManifestParser.parseSync().
  * @param  aType
  *         Instruction type to filter by.
  * @return True if any matching instructions were found in the manifest.
  */
  hasType(aManifest, aType) {
    return aManifest.some(entry => entry.type == aType);
  }
};
PK
!<wmodules/ClientID.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ClientID"];

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/Log.jsm");

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "ClientID::";

XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                  "resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyGetter(this, "gDatareportingPath", () => {
  return OS.Path.join(OS.Constants.Path.profileDir, "datareporting");
});

XPCOMUtils.defineLazyGetter(this, "gStateFilePath", () => {
  return OS.Path.join(gDatareportingPath, "state.json");
});

const PREF_CACHED_CLIENTID = "toolkit.telemetry.cachedClientID";

/**
 * Checks if client ID has a valid format.
 *
 * @param {String} id A string containing the client ID.
 * @return {Boolean} True when the client ID has valid format, or False
 * otherwise.
 */
function isValidClientID(id) {
  const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return UUID_REGEX.test(id);
}

this.ClientID = Object.freeze({
  /**
   * This returns a promise resolving to the the stable client ID we use for
   * data reporting (FHR & Telemetry). Previously exising FHR client IDs are
   * migrated to this.
   *
   * WARNING: This functionality is duplicated for Android (see GeckoProfile.getClientId
   * for more). There are Java tests (TestGeckoProfile) to ensure the functionality is
   * consistent and Gecko tests to come (bug 1249156). However, THIS IS NOT FOOLPROOF.
   * Be careful when changing this code and, in particular, the underlying file format.
   *
   * @return {Promise<string>} The stable client ID.
   */
  getClientID() {
    return ClientIDImpl.getClientID();
  },

/**
   * Get the client id synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk client id if it was already loaded
   *  - the client id that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedClientID() {
    return ClientIDImpl.getCachedClientID();
  },

  /**
   * Only used for testing. Invalidates the client ID so that it gets read
   * again from file.
   */
  _reset() {
    return ClientIDImpl._reset();
  },
});

var ClientIDImpl = {
  _clientID: null,
  _loadClientIdTask: null,
  _saveClientIdTask: null,
  _logger: null,

  _loadClientID() {
    if (this._loadClientIdTask) {
      return this._loadClientIdTask;
    }

    this._loadClientIdTask = this._doLoadClientID();
    let clear = () => this._loadClientIdTask = null;
    this._loadClientIdTask.then(clear, clear);
    return this._loadClientIdTask;
  },

  async _doLoadClientID() {
    // As we want to correlate FHR and telemetry data (and move towards unifying the two),
    // we first moved the ID management from the FHR implementation to the datareporting
    // service, then to a common shared module.
    // Consequently, we try to import an existing FHR ID, so we can keep using it.

    // Try to load the client id from the DRS state file first.
    try {
      let state = await CommonUtils.readJSON(gStateFilePath);
      if (state && this.updateClientID(state.clientID)) {
        return this._clientID;
      }
    } catch (e) {
      // fall through to next option
    }

    // If we dont have DRS state yet, try to import from the FHR state.
    try {
      let fhrStatePath = OS.Path.join(OS.Constants.Path.profileDir, "healthreport", "state.json");
      let state = await CommonUtils.readJSON(fhrStatePath);
      if (state && this.updateClientID(state.clientID)) {
        this._saveClientID();
        return this._clientID;
      }
    } catch (e) {
      // fall through to next option
    }

    // We dont have an id from FHR yet, generate a new ID.
    this.updateClientID(CommonUtils.generateUUID());
    this._saveClientIdTask = this._saveClientID();

    // Wait on persisting the id. Otherwise failure to save the ID would result in
    // the client creating and subsequently sending multiple IDs to the server.
    // This would appear as multiple clients submitting similar data, which would
    // result in orphaning.
    await this._saveClientIdTask;

    return this._clientID;
  },

  /**
   * Save the client ID to the client ID file.
   *
   * @return {Promise} A promise resolved when the client ID is saved to disk.
   */
  async _saveClientID() {
    let obj = { clientID: this._clientID };
    await OS.File.makeDir(gDatareportingPath);
    await CommonUtils.writeJSON(obj, gStateFilePath);
    this._saveClientIdTask = null;
  },

  /**
   * This returns a promise resolving to the the stable client ID we use for
   * data reporting (FHR & Telemetry). Previously exising FHR client IDs are
   * migrated to this.
   *
   * @return {Promise<string>} The stable client ID.
   */
  getClientID() {
    if (!this._clientID) {
      return this._loadClientID();
    }

    return Promise.resolve(this._clientID);
  },

  /**
   * Get the client id synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk client id if it was already loaded
   *  - the client id that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedClientID() {
    if (this._clientID) {
      // Already loaded the client id from disk.
      return this._clientID;
    }

    // Not yet loaded, return the cached client id if we have one.
    if (Services.prefs.getPrefType(PREF_CACHED_CLIENTID) != Ci.nsIPrefBranch.PREF_STRING) {
      this._log.error("getCachedClientID - invalid client id type in preferences, resetting");
      Services.prefs.clearUserPref(PREF_CACHED_CLIENTID);
    }

    let id = Services.prefs.getStringPref(PREF_CACHED_CLIENTID, null);
    if (id === null) {
      return null;
    }
    if (!isValidClientID(id)) {
      this._log.error("getCachedClientID - invalid client id in preferences, resetting", id);
      Services.prefs.clearUserPref(PREF_CACHED_CLIENTID);
      return null;
    }
    return id;
  },

  /*
   * Resets the provider. This is for testing only.
   */
  async _reset() {
    await this._loadClientIdTask;
    await this._saveClientIdTask;
    this._clientID = null;
  },

  /**
   * Sets the client id to the given value and updates the value cached in
   * preferences only if the given id is a valid.
   *
   * @param {String} id A string containing the client ID.
   * @return {Boolean} True when the client ID has valid format, or False
   * otherwise.
   */
  updateClientID(id) {
    if (!isValidClientID(id)) {
      this._log.error("updateClientID - invalid client ID", id);
      return false;
    }

    this._clientID = id;
    Services.prefs.setStringPref(PREF_CACHED_CLIENTID, this._clientID);
    return true;
  },

  /**
   * A helper for getting access to telemetry logger.
   */
  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },
};
PK
!<f\\modules/CloudStorage.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Java Script module that helps consumers store data directly
 * to cloud storage provider download folders.
 *
 * Takes cloud storage providers metadata as JSON input on Mac, Linux and Windows.
 *
 * Handles scan, prompt response save and exposes preferred storage provider.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["CloudStorage"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.importGlobalProperties(["fetch"]);
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                  "resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

const CLOUD_SERVICES_PREF = "cloud.services.";
const CLOUD_PROVIDERS_URI = "resource://cloudstorage/providers.json";

/**
 * Provider metadata JSON is loaded from resource://cloudstorage/providers.json
 * Sample providers.json format
 *
 * {
 *   "Dropbox": {
 *     "displayName": "Dropbox",
 *     "relativeDownloadPath": ["homeDir", "Dropbox"],
 *     "relativeDiscoveryPath":  {
 *       "linux": ["homeDir", ".dropbox", "info.json"],
 *       "macosx": ["homeDir", ".dropbox", "info.json"],
 *       "win": ["LocalAppData", "Dropbox", "info.json"]
 *     },
 *     "typeSpecificData": {
 *       "default": "Downloads",
 *       "screenshot": "Screenshots"
 *     }
 *  }
 *
 * Providers JSON is flat list of providers metdata with property as key in format @Provider
 *
 * @Provider - Unique cloud provider key, possible values: "Dropbox", "GDrive"
 *
 * @displayName - cloud storage name displayed in the prompt.
 *
 * @relativeDownloadPath - download path on user desktop for a cloud storage provider.
 * By default downloadPath is a concatenation of home dir and name of dropbox folder.
 * Example value: ["homeDir", "Dropbox"]
 *
 * @relativeDiscoveryPath - Lists discoveryPath by platform. Provider is not supported on a platform
 * if its value doesn't exist in relativeDiscoveryPath. relativeDiscoveryPath by platform is stored
 * as an array ofsubdirectories, which when concatenated, forms discovery path.
 * During scan discoveryPath is checked for the existence of cloud storage provider on user desktop.
 *
 * @typeSpecificData - provides folder name for a cloud storage depending
 * on type of data downloaded. Default folder is 'Downloads'. Other options are
 * 'screenshot' depending on provider support.
 */

/**
 *
 * Internal cloud services prefs
 *
 * cloud.services.api.enabled - set to true to initialize and use Cloud Storage module
 *
 * cloud.services.storage.key - set to string with preferred provider key
 *
 * cloud.services.lastPrompt - set to time when last prompt was shown
 *
 * cloud.services.interval.prompt - set to time interval in days after which prompt should be shown
 *
 * cloud.services.rejected.key - set to string with comma separated provider keys rejected
 * by user when prompted to opt-in
 *
 * browser.download.folderList - set to int and indicates the location users wish to save downloaded files to.
 *     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.
 *     3 - The default download location is elsewhere as specified by
 *         cloud storage API getDownloadFolder
 *
 * browser.download.dir - local file handle
 *   A local folder user may have selected for downloaded files to be
 *   saved. This folder is enabled when folderList equals 2.
 */

/**
 * The external API exported by this module.
 */

this.CloudStorage = {
  /**
    * Init method to initialize providers metadata
    */
  async init() {
    let isInitialized = null;
    try {
      // Invoke internal method asynchronously to read and
      // parse providers metadata from JSON
      isInitialized = await CloudStorageInternal.initProviders();
    } catch (err) {
      Cu.reportError(err);
    }
    return isInitialized;
  },

  /**
   * Returns information to allow the consumer to decide whether showing
   * a doorhanger prompt is appropriate. If a preferred provider is set
   * on desktop, user is not prompted again and method returns null.
   *
   * @return {Promise} which resolves to an object with property name
   * as 'key' and 'value'.
   * 'key' property is provider key such as 'Dropbox', 'GDrive'.
   * 'value' property contains metadata for respective provider.
   * Resolves null if it's not appropriate to prompt.
   */
  promisePromptInfo() {
    return CloudStorageInternal.promisePromptInfo();
  },

  /**
   * Save user response from doorhanger prompt.
   * If user confirms and checks 'always remember', update prefs
   * cloud.services.storage.key and browser.download.folderList to pick
   * download location from cloud storage API
   * If user denies, save provider as rejected in cloud.services.rejected.key
   *
   * @param key
   *        cloud storage provider key from provider metadata
   * @param remember
   *        bool value indicating whether user has asked to always remember
   *        the settings
   * @param selected
   *        bool value by default set to false indicating if user has selected
   *        to save downloaded file with cloud provider
   */
  savePromptResponse(key, remember, selected = false) {
    Services.prefs.setIntPref(CLOUD_SERVICES_PREF + "lastprompt",
                              Math.floor(Date.now() / 1000));
    if (remember) {
      if (selected) {
        CloudStorageInternal.setCloudStoragePref(key);
      } else {
        // Store provider as rejected by setting cloud.services.rejected.key
        // and not use in re-prompt
        CloudStorageInternal.handleRejected(key);
      }
    }
  },

  /**
   * Retrieve download folder of an opted-in storage provider
   * by type specific data
   * @param typeSpecificData
   *        type of data downloaded, options are 'default', 'screenshot'
   * @return {Promise} which resolves to full path to provider download folder
   */
  getDownloadFolder(typeSpecificData) {
    return CloudStorageInternal.getDownloadFolder(typeSpecificData);
  },

  /**
   * Get key of provider opted-in by user to store downloaded files
   *
   * @return {String}
   * Storage provider key from provider metadata. Return empty string
   * if user has not selected a preferred provider.
   */
  getPreferredProvider() {
    return CloudStorageInternal.preferredProviderKey;
  },

  /**
   * Get metadata of provider opted-in by user to store downloaded files.
   * Return preferred provider metadata without scanning by doing simple lookup
   * inside storage providers metadata using preferred provider key
   *
   * @return {Object}
   * Object with preferred provider metadata. Return null
   * if user has not selected a preferred provider.
   */
  getPreferredProviderMetaData() {
    return CloudStorageInternal.getPreferredProviderMetaData();
  },

  /**
   * Get display name of a provider actively in use to store downloaded files
   *
   * @return {String}
   * String with provider display name. Returns null if a provider
   * is not in use.
   */
  getProviderIfInUse() {
    return CloudStorageInternal.getProviderIfInUse();
  },

  /**
   * Get providers found on user desktop. Used for unit tests
   *
   * @return {Promise}
   * @resolves
   * Map object with entries key set to storage provider key and values set to
   * storage provider metadata
   */
  getStorageProviders() {
    return CloudStorageInternal.getStorageProviders();
  },
};

/**
 * The internal API for the CloudStorage module.
 */

var CloudStorageInternal = {
  /**
   * promiseInit saves returned init method promise and is
   * used to wait for initialization to complete.
   */
  promiseInit: null,

  /**
   * Internal property having storage providers data
   */
  providersMetaData: null,

  async _downloadJSON(uri) {
    let json = null;
    try {
      let response = await fetch(uri);
      if (response.ok) {
        json = await response.json();
      }
    } catch (e) {
      Cu.reportError("Fetching " + uri + " results in error: " + e);
    }
    return json;
  },

  /**
   * Reset 'browser.download.folderList' cloud storage value '3' back
   * to '2' or '1' depending on custom path or system default Downloads path
   * in pref 'browser.download.dir'.
   */
  async resetFolderListPref() {
    let folderListValue = Services.prefs.getIntPref("browser.download.folderList", 0);
    if (folderListValue !== 3) {
      return;
    }

    let downloadDirPath = null;
    try {
      let file = Services.prefs.getComplexValue("browser.download.dir",
                                                Ci.nsIFile);
      downloadDirPath = file.path;
    } catch (e) {}

    if (!downloadDirPath ||
        (downloadDirPath === await Downloads.getSystemDownloadsDirectory())) {
      // if downloadDirPath is the Downloads folder path or unspecified
      folderListValue = 1;
    } else if (downloadDirPath === Services.dirsvc.get("Desk", Ci.nsIFile).path) {
      // if downloadDirPath is the Desktop path
      folderListValue = 0;
    } else {
      // otherwise
      folderListValue = 2;
    }
    Services.prefs.setIntPref("browser.download.folderList", folderListValue);
  },

  /**
   * Loads storage providers metadata asynchronously from providers.json.
   *
   * @returns {Promise} with resolved boolean value true if providers
   * metadata is successfully initialized
   */
  async initProviders() {
    // Cloud Storage API should continue initialization and load providers metadata
    // only if a consumer add-on using API sets pref 'cloud.services.api.enabled' to true
    // If API is not enabled, check and reset cloud storage value in folderList pref.
    if (!this.isAPIEnabled) {
      this.resetFolderListPref().catch((err) => {
        Cu.reportError("CloudStorage: Failed to reset folderList pref " + err);
      });
      return false;
    }

    let response = await this._downloadJSON(CLOUD_PROVIDERS_URI);
    this.providersMetaData = await this._parseProvidersJSON(response);

    let providersCount = Object.keys(this.providersMetaData).length;
    if (providersCount > 0) {
      // Array of boolean results for each provider handled for custom downloadpath
      let handledProviders = await this.initDownloadPathIfProvidersExist();
      if (handledProviders.length === providersCount) {
        return true;
      }
    }
    return false;
  },

  /**
   * Load parsed metadata inside providers object
   */
  _parseProvidersJSON(providers) {
    if (!providers) {
      return {};
    }

    // Use relativeDiscoveryPath to filter providers object by platform.
    // DownloadPath and discoveryPath are stored as
    // array of subdirectories inside providers.json
    // Update providers object discoveryPath and downloadPath
    // property values by concatenating subdirectories and forming platform
    // specific directory path

    Object.getOwnPropertyNames(providers).forEach(key => {
      if (providers[key].relativeDiscoveryPath.hasOwnProperty(AppConstants.platform)) {
        providers[key].discoveryPath =
          this._concatPath(providers[key].relativeDiscoveryPath[AppConstants.platform]);
        providers[key].downloadPath =
          this._concatPath(providers[key].relativeDownloadPath);
      } else {
        // delete key not supported on AppConstants.platform
        delete providers[key];
      }
    });
    return providers;
  },

  /**
   * Concatenate subdir value inside array to form
   * platform specific directory path
   *
   * @param arrDirs
   *        String Array containing sub directories name
   * @returns Path of type String
   */
  _concatPath(arrDirs) {
    let dirPath = "";
    for (let subDir of arrDirs) {
      switch (subDir) {
        case "homeDir":
          subDir = OS.Constants.Path.homeDir ? OS.Constants.Path.homeDir : "";
          break;
        case "LocalAppData":
          if (OS.Constants.Win) {
            let nsIFileLocal = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
            subDir = nsIFileLocal && nsIFileLocal.path ? nsIFileLocal.path : "";
          } else {
            subDir = "";
          }
          break;
      }
      dirPath = OS.Path.join(dirPath, subDir);
    }
    return dirPath;
  },

  /**
   * Check for custom download paths and override providers metadata
   * downloadPath property
   *
   * For dropbox open config file ~/.dropbox/info.json
   * and override downloadPath with path found
   * See https://www.dropbox.com/en/help/desktop-web/find-folder-paths
   *
   * For all other providers we are using downloadpath from providers.json
   *
   * @returns {Promise} with array boolean values for respective provider. Value is true if a
   * provider exist on user desktop and its downloadPath is updated. Promise returns with
   * resolved array value when all providers in metadata are handled.
   */
  initDownloadPathIfProvidersExist() {
    let providerKeys = Object.keys(this.providersMetaData);
    let promises = providerKeys.map(key => {
      return key === "Dropbox" ?
             this._initDropbox(key) :
             Promise.resolve(false);
    });
    return Promise.all(promises);
  },

  /**
   * Read Dropbox info.json and override providers metadata
   * downloadPath property
   *
   * @return {Promise}
   * @resolves
   * false if dropbox provider is not found. Returns true if dropbox service exist
   * on user desktop and downloadPath in providermetadata is updated with
   * value read from config file info.json
   */
  async _initDropbox(key) {
    // Check if Dropbox provider exist on desktop before continuing
    if (!await this._checkIfAssetExists(this.providersMetaData[key].discoveryPath)) {
      return false;
    }

    // Check in cloud.services.rejected.key if Dropbox is previously rejected before continuing
    let rejectedKeys = this.cloudStorageRejectedKeys.split(",");
    if (rejectedKeys.includes(key)) {
      return false;
    }

    let file = null;
    try {
      file = new FileUtils.File(this.providersMetaData[key].discoveryPath);
    } catch (ex) {
      return false;
    }

    let data = await this._downloadJSON(Services.io.newFileURI(file).spec);

    if (!data) {
      return false;
    }

    let path = data && data.personal && data.personal.path;
    if (!path) {
      return false;
    }
    let isUsable = await this._isUsableDirectory(path);
    if (isUsable) {
      this.providersMetaData.Dropbox.downloadPath = path;
    }
    return isUsable;
  },

  /**
   * Determines if a given directory is valid and can be used to download files
   *
   * @param full absolute path to the directory
   *
   * @return {Promise} which resolves true if we can use the directory, false otherwise.
   */
  async _isUsableDirectory(path) {
    let isUsable = false;
    try {
      let info = await OS.File.stat(path);
      isUsable = info.isDir;
    } catch (e) {
      // Directory doesn't exist, so isUsable will still be false
    }
    return isUsable;
  },

  /**
   * Retrieve download folder of preferred provider by type specific data
   *
   * @param dataType
   *        type of data downloaded, options are 'default', 'screenshot'
   *        default value is 'default'
   * @return {Promise} which resolves to full path to download folder
   * Resolves null if a valid download folder is not found.
   */
  async getDownloadFolder(dataType = "default") {
    // Wait for cloudstorage to initialize if providers metadata is not available
    if (!this.providersMetaData) {
      let isInitialized = await this.promiseInit;
      if (!isInitialized && !this.providersMetaData) {
        Cu.reportError("CloudStorage: Failed to initialize and retrieve download folder ");
        return null;
      }
    }

    let key = this.preferredProviderKey;
    if (!key || !this.providersMetaData.hasOwnProperty(key)) {
      return null;
    }

    let provider = this.providersMetaData[key];
    if (!provider.typeSpecificData[dataType]) {
      return null;
    }

    let downloadDirPath = OS.Path.join(provider.downloadPath,
                                       provider.typeSpecificData[dataType]);
    if (!(await this._isUsableDirectory(downloadDirPath))) {
      return null;
    }
    return downloadDirPath;
  },

  /**
   * Return scanned provider info used by consumer inside doorhanger prompt.
   * @return {Promise}
   * which resolves to an object with property 'key' as found provider and
   * property 'value' as provider metadata.
   * Resolves null if no provider info is returned.
   */
  async promisePromptInfo() {
    // Check if user has not previously opted-in for preferred provider download folder
    // and if time elapsed since last prompt shown has exceeded maximum allowed interval
    // in pref cloud.services.interval.prompt before continuing to scan for providers
    if (!this.preferredProviderKey && this.shouldPrompt()) {
      return this.scan();
    }
    return Promise.resolve(null);
  },

  /**
   * Check if its time to prompt by reading lastprompt service pref.
   * Return true if pref doesn't exist or last prompt time is
   * more than prompt interval
   */
  shouldPrompt() {
    let lastPrompt = this.lastPromptTime;
    let now = Math.floor(Date.now() / 1000);
    let interval = now - lastPrompt;

    // Convert prompt interval to seconds
    let maxAllow = this.promptInterval * 24 * 60 * 60;
    return interval >= maxAllow;
  },

  /**
   * Scans for local storage providers available on user desktop
   *
   * providers list is read in order as specified in providers.json.
   * If a user has multiple cloud storage providers on desktop, return the first
   * provider after filtering the rejected keys
   *
   * @return {Promise}
   * which resolves to an object providerInfo with found provider key and value
   * as provider metadata. Resolves null if no valid provider found
   */
  async scan() {
    let providers = await this.getStorageProviders();
    if (!providers.size) {
      // No storage services installed on user desktop
      return null;
    }

    // Filter the rejected providers in cloud.services.rejected.key
    // from the providers map object
    let rejectedKeys = this.cloudStorageRejectedKeys.split(",");
    for (let rejectedKey of rejectedKeys) {
      providers.delete(rejectedKey);
    }

    // Pick first storage provider from providers
    let provider = providers.entries().next().value;
    if (provider) {
      return {key: provider[0], value: provider[1]};
    }
    return null;
  },

  /**
   * Checks if the asset with input path exist on
   * file system
   * @return {Promise}
   * @resolves
   * boolean value of file existence check
   */
  _checkIfAssetExists(path) {
    return OS.File.exists(path).catch(err => {
      Cu.reportError(`Couldn't check existance of ${path}`, err);
      return false;
    });
  },

  /**
   * get access to all local storage providers available on user desktop
   *
   * @return {Promise}
   * @resolves
   * Map object with entries key set to storage provider key and values set to
   * storage provider metadata
   */
  async getStorageProviders() {
    let providers = Object.entries(this.providersMetaData || {});

    // Array of promises with boolean value exist for respective storage.
    let promises = providers.map(([, provider]) => this._checkIfAssetExists(provider.discoveryPath));
    let results = await Promise.all(promises);

    // Filter providers array to remove provider with discoveryPath asset exist resolved value false
    providers = providers.filter((_, idx) => results[idx]);
    return new Map(providers);
  },

  /**
   * Save the rejected provider in cloud.services.rejected.key. Pref
   * stores rejected keys value as comma separated string.
   *
   * @param key
   *        Provider key to be saved in cloud.services.rejected.key pref
   */
  handleRejected(key) {
    let rejected = this.cloudStorageRejectedKeys;

    if (!rejected) {
      Services.prefs.setCharPref(CLOUD_SERVICES_PREF + "rejected.key", key);
    } else {
      // Pref exists with previous rejected keys, append
      // key at the end and update pref
      let keys = rejected.split(",");
      if (key) {
        keys.push(key);
      }
      Services.prefs.setCharPref(CLOUD_SERVICES_PREF + "rejected.key", keys.join(","));
    }
  },

  /**
   *
   * Sets pref cloud.services.storage.key. It updates download browser.download.folderList
   * value to 3 indicating download location is stored elsewhere, as specified by
   * cloud storage API getDownloadFolder
   *
   * @param key
   *        cloud storage provider key from provider metadata
   */
  setCloudStoragePref(key) {
    Services.prefs.setCharPref(CLOUD_SERVICES_PREF + "storage.key", key);
    Services.prefs.setIntPref("browser.download.folderList", 3);
  },

  /**
   * get access to preferred provider metadata by using preferred provider key
   *
   * @return {Object}
   * Object with preferred provider metadata. Returns null if preferred provider is not set
   */
  getPreferredProviderMetaData() {
    // Use preferred provider key to retrieve metadata from ProvidersMetaData
    return this.providersMetaData.hasOwnProperty(this.preferredProviderKey) ?
      this.providersMetaData[this.preferredProviderKey] : null;
  },

  /**
   * Get provider display name if cloud storage API is used by an add-on
   * and user has set preferred provider and a valid download directory
   * path exists on user desktop.
   *
   * @return {String}
   * String with preferred provider display name. Returns null if provider is not in use.
   */
  async getProviderIfInUse() {
    // Check if consumer add-on is present and user has set preferred provider key
    // and a valid download path exist on user desktop
    if (this.isAPIEnabled && this.preferredProviderKey && await this.getDownloadFolder()) {
      let provider = this.getPreferredProviderMetaData();
      return provider.displayName || null;
    }
    return null;
  },
};

/**
 * Provider key retrieved from service pref cloud.services.storage.key
 */
XPCOMUtils.defineLazyPreferenceGetter(CloudStorageInternal, "preferredProviderKey",
  CLOUD_SERVICES_PREF + "storage.key", "");

/**
 * Provider keys rejected by user for default download
 */
XPCOMUtils.defineLazyPreferenceGetter(CloudStorageInternal, "cloudStorageRejectedKeys",
  CLOUD_SERVICES_PREF + "rejected.key", "");

/**
 * Lastprompt time in seconds, by default set to 0
 */
XPCOMUtils.defineLazyPreferenceGetter(CloudStorageInternal, "lastPromptTime",
  CLOUD_SERVICES_PREF + "lastprompt", 0 /* 0 second */);

/**
 * show prompt interval in days, by default set to 0
 */
XPCOMUtils.defineLazyPreferenceGetter(CloudStorageInternal, "promptInterval",
  CLOUD_SERVICES_PREF + "interval.prompt", 0 /* 0 days */);

/**
 * generic pref that shows if cloud storage API is in use, by default set to false.
 * Re-run CloudStorage init evertytime pref is set.
 */
XPCOMUtils.defineLazyPreferenceGetter(CloudStorageInternal, "isAPIEnabled",
  CLOUD_SERVICES_PREF + "api.enabled", false, () => CloudStorage.init());

CloudStorageInternal.promiseInit = CloudStorage.init();
PK
!<t9Aq!q!modules/ClusterLib.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Class that can run the hierarchical clustering algorithm with the given
 * parameters.
 *
 * @param distance
 *        Function that should return the distance between two items.
 *        Defaults to clusterlib.euclidean_distance.
 * @param merge
 *        Function that should take in two items and return a merged one.
 *        Defaults to clusterlib.average_linkage.
 * @param threshold
 *        The maximum distance between two items for which their clusters
 *        can be merged.
 */
function HierarchicalClustering(distance, merge, threshold) {
  this.distance = distance || clusterlib.euclidean_distance;
  this.merge = merge || clusterlib.average_linkage;
  this.threshold = threshold == undefined ? Infinity : threshold;
}

HierarchicalClustering.prototype = {
  /**
   * Run the hierarchical clustering algorithm on the given items to produce
   * a final set of clusters.  Uses the parameters set in the constructor.
   *
   * @param items
   *        An array of "things" to cluster - this is the domain-specific
   *        collection you're trying to cluster (colors, points, etc.)
   * @param snapshotGap
   *        How many iterations of the clustering algorithm to wait between
   *        calling the snapshotCallback
   * @param snapshotCallback
   *        If provided, will be called as clusters are merged to let you view
   *        the progress of the algorithm.  Passed the current array of
   *        clusters, cached distances, and cached closest clusters.
   *
   * @return An array of merged clusters.  The represented item can be
   *         found in the "item" property of the cluster.
   */
  cluster: function HC_cluster(items, snapshotGap, snapshotCallback) {
    // array of all remaining clusters
    let clusters = [];
    // 2D matrix of distances between each pair of clusters, indexed by key
    let distances = [];
    // closest cluster key for each cluster, indexed by key
    let neighbors = [];
    // an array of all clusters, but indexed by key
    let clustersByKey = [];

    // set up clusters from the initial items array
    for (let index = 0; index < items.length; index++) {
      let cluster = {
        // the item this cluster represents
        item: items[index],
        // a unique key for this cluster, stays constant unless merged itself
        key: index,
        // index of cluster in clusters array, can change during any merge
        index,
        // how many clusters have been merged into this one
        size: 1
      };
      clusters[index] = cluster;
      clustersByKey[index] = cluster;
      distances[index] = [];
      neighbors[index] = 0;
    }

    // initialize distance matrix and cached neighbors
    for (let i = 0; i < clusters.length; i++) {
      for (let j = 0; j <= i; j++) {
        var dist = (i == j) ? Infinity :
          this.distance(clusters[i].item, clusters[j].item);
        distances[i][j] = dist;
        distances[j][i] = dist;

        if (dist < distances[i][neighbors[i]]) {
          neighbors[i] = j;
        }
      }
    }

    // merge the next two closest clusters until none of them are close enough
    for (let next = null, i = 0; (next = this.closestClusters(clusters, distances, neighbors)); i++) {
      if (snapshotCallback && (i % snapshotGap) == 0) {
        snapshotCallback(clusters);
      }
      this.mergeClusters(clusters, distances, neighbors, clustersByKey,
                         clustersByKey[next[0]], clustersByKey[next[1]]);
    }
    return clusters;
  },

  /**
   * Once we decide to merge two clusters in the cluster method, actually
   * merge them.  Alters the given state of the algorithm.
   *
   * @param clusters
   *        The array of all remaining clusters
   * @param distances
   *        Cached distances between pairs of clusters
   * @param neighbors
   *        Cached closest clusters
   * @param clustersByKey
   *        Array of all clusters, indexed by key
   * @param cluster1
   *        First cluster to merge
   * @param cluster2
   *        Second cluster to merge
   */
  mergeClusters: function HC_mergeClus(clusters, distances, neighbors,
                                       clustersByKey, cluster1, cluster2) {
    let merged = { item: this.merge(cluster1.item, cluster2.item),
                   left: cluster1,
                   right: cluster2,
                   key: cluster1.key,
                   size: cluster1.size + cluster2.size };

    clusters[cluster1.index] = merged;
    clusters.splice(cluster2.index, 1);
    clustersByKey[cluster1.key] = merged;

    // update distances with new merged cluster
    for (let i = 0; i < clusters.length; i++) {
      var ci = clusters[i];
      var dist;
      if (cluster1.key == ci.key) {
        dist = Infinity;
      } else if (this.merge == clusterlib.single_linkage) {
        dist = distances[cluster1.key][ci.key];
        if (distances[cluster1.key][ci.key] >
            distances[cluster2.key][ci.key]) {
          dist = distances[cluster2.key][ci.key];
        }
      } else if (this.merge == clusterlib.complete_linkage) {
        dist = distances[cluster1.key][ci.key];
        if (distances[cluster1.key][ci.key] <
            distances[cluster2.key][ci.key]) {
          dist = distances[cluster2.key][ci.key];
        }
      } else if (this.merge == clusterlib.average_linkage) {
        dist = (distances[cluster1.key][ci.key] * cluster1.size
              + distances[cluster2.key][ci.key] * cluster2.size)
              / (cluster1.size + cluster2.size);
      } else {
        dist = this.distance(ci.item, cluster1.item);
      }

      distances[cluster1.key][ci.key] = distances[ci.key][cluster1.key]
                                      = dist;
    }

    // update cached neighbors
    for (let i = 0; i < clusters.length; i++) {
      var key1 = clusters[i].key;
      if (neighbors[key1] == cluster1.key ||
          neighbors[key1] == cluster2.key) {
        let minKey = key1;
        for (let j = 0; j < clusters.length; j++) {
          var key2 = clusters[j].key;
          if (distances[key1][key2] < distances[key1][minKey]) {
            minKey = key2;
          }
        }
        neighbors[key1] = minKey;
      }
      clusters[i].index = i;
    }
  },

  /**
   * Given the current state of the algorithm, return the keys of the two
   * clusters that are closest to each other so we know which ones to merge
   * next.
   *
   * @param clusters
   *        The array of all remaining clusters
   * @param distances
   *        Cached distances between pairs of clusters
   * @param neighbors
   *        Cached closest clusters
   *
   * @return An array of two keys of clusters to merge, or null if there are
   *         no more clusters close enough to merge
   */
  closestClusters: function HC_closestClus(clusters, distances, neighbors) {
    let minKey = 0, minDist = Infinity;
    for (let i = 0; i < clusters.length; i++) {
      var key = clusters[i].key;
      if (distances[key][neighbors[key]] < minDist) {
        minKey = key;
        minDist = distances[key][neighbors[key]];
      }
    }
    if (minDist < this.threshold) {
      return [minKey, neighbors[minKey]];
    }
    return null;
  }
};

var clusterlib = {
  hcluster: function hcluster(items, distance, merge, threshold, snapshotGap,
                              snapshotCallback) {
    return (new HierarchicalClustering(distance, merge, threshold))
           .cluster(items, snapshotGap, snapshotCallback);
  },

  single_linkage: function single_linkage(cluster1, cluster2) {
    return cluster1;
  },

  complete_linkage: function complete_linkage(cluster1, cluster2) {
    return cluster1;
  },

  average_linkage: function average_linkage(cluster1, cluster2) {
    return cluster1;
  },

  euclidean_distance: function euclidean_distance(v1, v2) {
    let total = 0;
    for (let i = 0; i < v1.length; i++) {
      total += Math.pow(v2[i] - v1[i], 2);
    }
    return Math.sqrt(total);
  },

  manhattan_distance: function manhattan_distance(v1, v2) {
    let total = 0;
    for (let i = 0; i < v1.length; i++) {
      total += Math.abs(v2[i] - v1[i]);
    }
    return total;
  },

  max_distance: function max_distance(v1, v2) {
    let max = 0;
    for (let i = 0; i < v1.length; i++) {
      max = Math.max(max, Math.abs(v2[i] - v1[i]));
    }
    return max;
  }
};
PK
!<Whmodules/Color.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["Color"];

/**
 * Color class, which describes a color.
 * In the future, this object may be extended to allow for conversions between
 * different color formats and notations, support transparency.
 *
 * @param {Number} r Red color component
 * @param {Number} g Green color component
 * @param {Number} b Blue color component
 */
function Color(r, g, b) {
  this.r = r;
  this.g = g;
  this.b = b;
}

Color.prototype = {
  /**
   * Formula from W3C's WCAG 2.0 spec's relative luminance, section 1.4.1,
   * http://www.w3.org/TR/WCAG20/.
   *
   * @return {Number} Relative luminance, represented as number between 0 and 1.
   */
  get relativeLuminance() {
    let colorArr = [this.r, this.b, this.g].map(color => {
      color = parseInt(color, 10);
      if (color <= 10)
        return color / 255 / 12.92;
      return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
    });
    return colorArr[0] * 0.2126 +
           colorArr[1] * 0.7152 +
           colorArr[2] * 0.0722;
  },

  /**
   * @return {Boolean} TRUE if the color value can be considered bright.
   */
  get isBright() {
    // Note: this is a high enough value to be considered as 'bright', but was
    //       decided upon empirically.
    return this.relativeLuminance > 0.7;
  },

  /**
   * Get the contrast ratio between the current color and a second other color.
   * A common use case is to express the difference between a foreground and a
   * background color in numbers.
   * Formula from W3C's WCAG 2.0 spec's contrast ratio, section 1.4.1,
   * http://www.w3.org/TR/WCAG20/.
   *
   * @param  {Color}  otherColor Color instance to calculate the contrast with
   * @return {Number} Contrast ratios can range from 1 to 21, commonly written
   *                  as 1:1 to 21:1.
   */
  contrastRatio(otherColor) {
    if (!(otherColor instanceof Color))
      throw new TypeError("The first argument should be an instance of Color");

    let luminance = this.relativeLuminance;
    let otherLuminance = otherColor.relativeLuminance;
    return (Math.max(luminance, otherLuminance) + 0.05) /
      (Math.min(luminance, otherLuminance) + 0.05);
  },

  /**
   * Biased method to check if the contrast ratio between two colors is high
   * enough to be discernable.
   *
   * @param  {Color} otherColor Color instance to calculate the contrast with
   * @return {Boolean}
   */
  isContrastRatioAcceptable(otherColor) {
    // Note: this is a high enough value to be considered as 'high contrast',
    //       but was decided upon empirically.
    return this.contrastRatio(otherColor) > 3;
  }
};
PK
!<ѕW3W3modules/ColorAnalyzer_worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/chrome-worker */

"use strict";

importScripts("ClusterLib.js", "ColorConversion.js");

// Offsets in the ImageData pixel array to reach pixel colors
const PIXEL_RED = 0;
const PIXEL_GREEN = 1;
const PIXEL_BLUE = 2;
const PIXEL_ALPHA = 3;

// Number of components in one ImageData pixel (RGBA)
const NUM_COMPONENTS = 4;

// Shift a color represented as a 24 bit integer by N bits to get a component
const RED_SHIFT = 16;
const GREEN_SHIFT = 8;

// Only run the N most frequent unique colors through the clustering algorithm
// Images with more than this many unique colors will have reduced accuracy.
const MAX_COLORS_TO_MERGE = 500;

// Each cluster of colors has a mean color in the Lab color space.
// If the euclidean distance between the means of two clusters is greater
// than or equal to this threshold, they won't be merged.
const MERGE_THRESHOLD = 12;

// The highest the distance handicap can be for large clusters
const MAX_SIZE_HANDICAP = 5;
// If the handicap is below this number, it is cut off to zero
const SIZE_HANDICAP_CUTOFF = 2;

// If potential background colors deviate from the mean background color by
// this threshold or greater, finding a background color will fail
const BACKGROUND_THRESHOLD = 10;

// Alpha component of colors must be larger than this in order to make it into
// the clustering algorithm or be considered a background color (0 - 255).
const MIN_ALPHA = 25;

// The euclidean distance in the Lab color space under which merged colors
// are weighted lower for being similar to the background color
const BACKGROUND_WEIGHT_THRESHOLD = 15;

// The range in which color chroma differences will affect desirability.
// Colors with chroma outside of the range take on the desirability of
// their nearest extremes. Should be roughly 0 - 150.
const CHROMA_WEIGHT_UPPER = 90;
const CHROMA_WEIGHT_LOWER = 1;
const CHROMA_WEIGHT_MIDDLE = (CHROMA_WEIGHT_UPPER + CHROMA_WEIGHT_LOWER) / 2;

/**
 * When we receive a message from the outside world, find the representative
 * colors of the given image.  The colors will be posted back to the caller
 * through the "colors" property on the event data object as an array of
 * integers.  Colors of lower indices are more representative.
 * This array can be empty if this worker can't find a color.
 *
 * @param event
 *        A MessageEvent whose data should have the following properties:
 *          imageData - A DOM ImageData instance to analyze
 *          maxColors - The maximum number of representative colors to find,
 *                      defaults to 1 if not provided
 */
onmessage = function(event) {
  let imageData = event.data.imageData;
  let pixels = imageData.data;
  let width = imageData.width;
  let height = imageData.height;
  let maxColors = event.data.maxColors;
  if (typeof(maxColors) != "number") {
    maxColors = 1;
  }

  let allColors = getColors(pixels, width, height);

  // Only merge top colors by frequency for speed.
  let mergedColors = mergeColors(allColors.slice(0, MAX_COLORS_TO_MERGE),
                                 width * height, MERGE_THRESHOLD);

  let backgroundColor = getBackgroundColor(pixels, width, height);

  mergedColors = mergedColors.map(function(cluster) {
    // metadata holds a bunch of information about the color represented by
    // this cluster
    let metadata = cluster.item;

    // the basis of color desirability is how much of the image the color is
    // responsible for, but we'll need to weigh this number differently
    // depending on other factors
    metadata.desirability = metadata.ratio;
    let weight = 1;

    // if the color is close to the background color, we don't want it
    if (backgroundColor != null) {
      let backgroundDistance = labEuclidean(metadata.mean, backgroundColor);
      if (backgroundDistance < BACKGROUND_WEIGHT_THRESHOLD) {
        weight = backgroundDistance / BACKGROUND_WEIGHT_THRESHOLD;
      }
    }

    // prefer more interesting colors, but don't knock low chroma colors
    // completely out of the running (lower bound), and we don't really care
    // if a color is slightly more intense than another on the higher end
    let chroma = labChroma(metadata.mean);
    if (chroma < CHROMA_WEIGHT_LOWER) {
      chroma = CHROMA_WEIGHT_LOWER;
    } else if (chroma > CHROMA_WEIGHT_UPPER) {
      chroma = CHROMA_WEIGHT_UPPER;
    }
    weight *= chroma / CHROMA_WEIGHT_MIDDLE;

    metadata.desirability *= weight;
    return metadata;
  });

  // only send back the most desirable colors
  mergedColors.sort(function(a, b) {
    return b.desirability != a.desirability ? b.desirability - a.desirability : b.color - a.color;
  });
  mergedColors = mergedColors.map(function(metadata) {
    return metadata.color;
  }).slice(0, maxColors);
  postMessage({ colors: mergedColors });
};

/**
 * Given the pixel data and dimensions of an image, return an array of objects
 * associating each unique color and its frequency in the image, sorted
 * descending by frequency.  Sufficiently transparent colors are ignored.
 *
 * @param pixels
 *        Pixel data array for the image to get colors from (ImageData.data).
 * @param width
 *        Width of the image, in # of pixels.
 * @param height
 *        Height of the image, in # of pixels.
 *
 * @return An array of objects with color and freq properties, sorted
 *         descending by freq
 */
function getColors(pixels, width, height) {
  let colorFrequency = {};
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      let offset = (x * NUM_COMPONENTS) + (y * NUM_COMPONENTS * width);

      if (pixels[offset + PIXEL_ALPHA] < MIN_ALPHA) {
        continue;
      }

      let color = pixels[offset + PIXEL_RED] << RED_SHIFT
                | pixels[offset + PIXEL_GREEN] << GREEN_SHIFT
                | pixels[offset + PIXEL_BLUE];

      if (color in colorFrequency) {
        colorFrequency[color]++;
      } else {
        colorFrequency[color] = 1;
      }
    }
  }

  let colors = [];
  for (var color in colorFrequency) {
    colors.push({ color: +color, freq: colorFrequency[+color] });
  }
  colors.sort(descendingFreqSort);
  return colors;
}

/**
 * Given an array of objects from getColors, the number of pixels in the
 * image, and a merge threshold, run the clustering algorithm on the colors
 * and return the set of merged clusters.
 *
 * @param colorFrequencies
 *        An array of objects from getColors to cluster
 * @param numPixels
 *        The number of pixels in the image
 * @param threshold
 *        The maximum distance between two clusters for which those clusters
 *        can be merged.
 *
 * @return An array of merged clusters
 *
 * @see clusterlib.hcluster
 * @see getColors
 */
function mergeColors(colorFrequencies, numPixels, threshold) {
  let items = colorFrequencies.map(function(colorFrequency) {
    let color = colorFrequency.color;
    let freq = colorFrequency.freq;
    return {
      mean: rgb2lab(color >> RED_SHIFT, color >> GREEN_SHIFT & 0xff,
                    color & 0xff),
      // the canonical color of the cluster
      // (one w/ highest freq or closest to mean)
      color,
      colors: [color],
      highFreq: freq,
      highRatio: freq / numPixels,
      // the individual color w/ the highest frequency in this cluster
      highColor: color,
      // ratio of image taken up by colors in this cluster
      ratio: freq / numPixels,
      freq,
    };
  });

  let merged = clusterlib.hcluster(items, distance, merge, threshold);
  return merged;
}

function descendingFreqSort(a, b) {
  return b.freq != a.freq ? b.freq - a.freq : b.color - a.color;
}

/**
 * Given two items for a pair of clusters (as created in mergeColors above),
 * determine the distance between them so we know if we should merge or not.
 * Uses the euclidean distance between their mean colors in the lab color
 * space, weighted so larger items are harder to merge.
 *
 * @param item1
 *        The first item to compare
 * @param item2
 *        The second item to compare
 *
 * @return The distance between the two items
 */
function distance(item1, item2) {
  // don't cluster large blocks of color unless they're really similar
  let minRatio = Math.min(item1.ratio, item2.ratio);
  let dist = labEuclidean(item1.mean, item2.mean);
  let handicap = Math.min(MAX_SIZE_HANDICAP, dist * minRatio);
  if (handicap <= SIZE_HANDICAP_CUTOFF) {
    handicap = 0;
  }
  return dist + handicap;
}

/**
 * Find the euclidean distance between two colors in the Lab color space.
 *
 * @param color1
 *        The first color to compare
 * @param color2
 *        The second color to compare
 *
 * @return The euclidean distance between the two colors
 */
function labEuclidean(color1, color2) {
  return Math.sqrt(
      Math.pow(color2.lightness - color1.lightness, 2)
    + Math.pow(color2.a - color1.a, 2)
    + Math.pow(color2.b - color1.b, 2));
}

/**
 * Given items from two clusters we know are appropriate for merging,
 * merge them together into a third item such that its metadata describes both
 * input items.  The "color" property is set to the color in the new item that
 * is closest to its mean color.
 *
 * @param item1
 *        The first item to merge
 * @param item2
 *        The second item to merge
 *
 * @return An item that represents the merging of the given items
 */
function merge(item1, item2) {
  let lab1 = item1.mean;
  let lab2 = item2.mean;

  /* algorithm tweak point - weighting the mean of the cluster */
  let num1 = item1.freq;
  let num2 = item2.freq;

  let total = num1 + num2;

  let mean = {
    lightness: (lab1.lightness * num1 + lab2.lightness * num2) / total,
    a: (lab1.a * num1 + lab2.a * num2) / total,
    b: (lab1.b * num1 + lab2.b * num2) / total
  };

  let colors = item1.colors.concat(item2.colors);

  // get the canonical color of the new cluster
  let color;
  let avgFreq = colors.length / (item1.freq + item2.freq);
  if ((item1.highFreq > item2.highFreq) && (item1.highFreq > avgFreq * 2)) {
    color = item1.highColor;
  } else if (item2.highFreq > avgFreq * 2) {
    color = item2.highColor;
  } else {
    // if there's no stand-out color
    let minDist = Infinity, closest = 0;
    for (let i = 0; i < colors.length; i++) {
      let color = colors[i];
      let lab = rgb2lab(color >> RED_SHIFT, color >> GREEN_SHIFT & 0xff,
                        color & 0xff);
      let dist = labEuclidean(lab, mean);
      if (dist < minDist) {
        minDist = dist;
        closest = i;
      }
    }
    color = colors[closest];
  }

  const higherItem = item1.highFreq > item2.highFreq ? item1 : item2;

  return {
    mean,
    color,
    highFreq: higherItem.highFreq,
    highColor: higherItem.highColor,
    highRatio: higherItem.highRatio,
    ratio: item1.ratio + item2.ratio,
    freq: item1.freq + item2.freq,
    colors,
  };
}

/**
 * Find the background color of the given image.
 *
 * @param pixels
 *        The pixel data for the image (an array of component integers)
 * @param width
 *        The width of the image
 * @param height
 *        The height of the image
 *
 * @return The background color of the image as a Lab object, or null if we
 *         can't determine the background color
 */
function getBackgroundColor(pixels, width, height) {
  // we'll assume that if the four corners are roughly the same color,
  // then that's the background color
  let coordinates = [[0, 0], [width - 1, 0], [width - 1, height - 1],
                     [0, height - 1]];

  // find the corner colors in LAB
  let cornerColors = [];
  for (let i = 0; i < coordinates.length; i++) {
    let offset = (coordinates[i][0] * NUM_COMPONENTS)
               + (coordinates[i][1] * NUM_COMPONENTS * width);
    if (pixels[offset + PIXEL_ALPHA] < MIN_ALPHA) {
      // we can't make very accurate judgements below this opacity
      continue;
    }
    cornerColors.push(rgb2lab(pixels[offset + PIXEL_RED],
                              pixels[offset + PIXEL_GREEN],
                              pixels[offset + PIXEL_BLUE]));
  }

  // we want at least two points at acceptable alpha levels
  if (cornerColors.length <= 1) {
    return null;
  }

  // find the average color among the corners
  let averageColor = { lightness: 0, a: 0, b: 0 };
  cornerColors.forEach(function(color) {
    for (let i in color) {
      averageColor[i] += color[i];
    }
  });
  for (let i in averageColor) {
    averageColor[i] /= cornerColors.length;
  }

  // if we have fewer points due to low alpha, they need to be closer together
  let threshold = BACKGROUND_THRESHOLD
                * (cornerColors.length / coordinates.length);

  // if any of the corner colors deviate enough from the average, they aren't
  // similar enough to be considered the background color
  for (let cornerColor of cornerColors) {
    if (labEuclidean(cornerColor, averageColor) > threshold) {
      return null;
    }
  }
  return averageColor;
}
PK
!<Lmodules/ColorConversion.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Given a color in the Lab space, return its chroma (colorfulness,
 * saturation).
 *
 * @param lab
 *        The lab color to get the chroma from
 *
 * @return A number greater than zero that measures chroma in the image
 */
function labChroma(lab) {
  return Math.sqrt(Math.pow(lab.a, 2) + Math.pow(lab.b, 2));
}

/**
 * Given the RGB components of a color as integers from 0-255, return the
 * color in the XYZ color space.
 *
 * @return An object with x, y, z properties holding those components of the
 *         color in the XYZ color space.
 */
function rgb2xyz(r, g, b) {
  r /= 255;
  g /= 255;
  b /= 255;

  // assume sRGB
  r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
  g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
  b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);

  return {
    x: ((r * 0.4124) + (g * 0.3576) + (b * 0.1805)) * 100,
    y: ((r * 0.2126) + (g * 0.7152) + (b * 0.0722)) * 100,
    z: ((r * 0.0193) + (g * 0.1192) + (b * 0.9505)) * 100
  };
}

/**
 * Given the RGB components of a color as integers from 0-255, return the
 * color in the Lab color space.
 *
 * @return An object with lightness, a, b properties holding those components
 *         of the color in the Lab color space.
 */
function rgb2lab(r, g, b) {
  let xyz = rgb2xyz(r, g, b),
        x = xyz.x / 95.047,
        y = xyz.y / 100,
        z = xyz.z / 108.883;

  x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
  y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
  z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);

  return {
    lightness: (116 * y) - 16,
    a:  500 * (x - y),
    b:  200 * (y - z)
  };
}
PK
!<:b)**modules/CommonDialog.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["CommonDialog"];

const Ci = Components.interfaces;
const Cr = Components.results;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
                                  "resource://gre/modules/SharedPromptUtils.jsm");


this.CommonDialog = function CommonDialog(args, ui) {
    this.args = args;
    this.ui   = ui;
}

CommonDialog.prototype = {
    args: null,
    ui: null,

    hasInputField: true,
    numButtons: undefined,
    iconClass: undefined,
    soundID: undefined,
    focusTimer: null,

    onLoad(xulDialog) {
        switch (this.args.promptType) {
          case "alert":
          case "alertCheck":
            this.hasInputField = false;
            this.numButtons    = 1;
            this.iconClass     = ["alert-icon"];
            this.soundID       = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
            break;
          case "confirmCheck":
          case "confirm":
            this.hasInputField = false;
            this.numButtons    = 2;
            this.iconClass     = ["question-icon"];
            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
            break;
          case "confirmEx":
            var numButtons = 0;
            if (this.args.button0Label)
                numButtons++;
            if (this.args.button1Label)
                numButtons++;
            if (this.args.button2Label)
                numButtons++;
            if (this.args.button3Label)
                numButtons++;
            if (numButtons == 0)
                throw "A dialog with no buttons? Can not haz.";
            this.numButtons    = numButtons;
            this.hasInputField = false;
            this.iconClass     = ["question-icon"];
            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
            break;
          case "prompt":
            this.numButtons = 2;
            this.iconClass  = ["question-icon"];
            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
            this.initTextbox("login", this.args.value);
            // Clear the label, since this isn't really a username prompt.
            this.ui.loginLabel.setAttribute("value", "");
            break;
          case "promptUserAndPass":
            this.numButtons = 2;
            this.iconClass  = ["authentication-icon", "question-icon"];
            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
            this.initTextbox("login", this.args.user);
            this.initTextbox("password1", this.args.pass);
            break;
          case "promptPassword":
            this.numButtons = 2;
            this.iconClass  = ["authentication-icon", "question-icon"];
            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
            this.initTextbox("password1", this.args.pass);
            // Clear the label, since the message presumably indicates its purpose.
            this.ui.password1Label.setAttribute("value", "");
            break;
          default:
            Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
            throw "unknown dialog type";
        }

        if (xulDialog) {
            xulDialog.setAttribute("windowtype", "prompt:" + this.args.promptType);
        }

        // set the document title
        let title = this.args.title;
        // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
        let infoTitle = this.ui.infoTitle;
        infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
        if (xulDialog)
            xulDialog.ownerDocument.title = title;

        // Set button labels and visibility
        //
        // This assumes that button0 defaults to a visible "ok" button, and
        // button1 defaults to a visible "cancel" button. The other 2 buttons
        // have no default labels (and are hidden).
        switch (this.numButtons) {
          case 4:
            this.setLabelForNode(this.ui.button3, this.args.button3Label);
            this.ui.button3.hidden = false;
            // fall through
          case 3:
            this.setLabelForNode(this.ui.button2, this.args.button2Label);
            this.ui.button2.hidden = false;
            // fall through
          case 2:
            // Defaults to a visible "cancel" button
            if (this.args.button1Label)
                this.setLabelForNode(this.ui.button1, this.args.button1Label);
            break;

          case 1:
            this.ui.button1.hidden = true;
            break;
        }
        // Defaults to a visible "ok" button
        if (this.args.button0Label)
            this.setLabelForNode(this.ui.button0, this.args.button0Label);

        // display the main text
        let croppedMessage = "";
        if (this.args.text) {
            // Bug 317334 - crop string length as a workaround.
            croppedMessage = this.args.text.substr(0, 10000);
        }
        let infoBody = this.ui.infoBody;
        infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));

        let label = this.args.checkLabel;
        if (label) {
            // Only show the checkbox if label has a value.
            this.ui.checkboxContainer.hidden = false;
            this.setLabelForNode(this.ui.checkbox, label);
            this.ui.checkbox.checked = this.args.checked;
        }

        // set the icon
        let icon = this.ui.infoIcon;
        if (icon)
            this.iconClass.forEach((el, idx, arr) => icon.classList.add(el));

        // set default result to cancelled
        this.args.ok = false;
        this.args.buttonNumClicked = 1;


        // Set the default button
        let b = (this.args.defaultButtonNum || 0);
        let button = this.ui["button" + b];

        if (xulDialog)
            xulDialog.defaultButton = ["accept", "cancel", "extra1", "extra2"][b];
        else
            button.setAttribute("default", "true");

        // Set default focus / selection.
        this.setDefaultFocus(true);

        if (this.args.enableDelay) {
            this.delayHelper = new EnableDelayHelper({
                disableDialog: () => this.setButtonsEnabledState(false),
                enableDialog: () => this.setButtonsEnabledState(true),
                focusTarget: this.ui.focusTarget
            });
        }

        // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
        try {
            if (xulDialog && this.soundID) {
                Cc["@mozilla.org/sound;1"].
                createInstance(Ci.nsISound).
                playEventSound(this.soundID);
            }
        } catch (e) {
            Cu.reportError("Couldn't play common dialog event sound: " + e);
        }

        let topic = "common-dialog-loaded";
        if (!xulDialog)
            topic = "tabmodal-dialog-loaded";
        Services.obs.notifyObservers(this.ui.prompt, topic);
    },

    setLabelForNode(aNode, aLabel) {
        // This is for labels which may contain embedded access keys.
        // If we end in (&X) where X represents the access key, optionally preceded
        // by spaces and/or followed by the ':' character, store the access key and
        // remove the access key placeholder + leading spaces from the label.
        // Otherwise a character preceded by one but not two &s is the access key.
        // Store it and remove the &.

        // Note that if you change the following code, see the comment of
        // nsTextBoxFrame::UpdateAccessTitle.
        var accessKey = null;
        if (/ *\(\&([^&])\)(:?)$/.test(aLabel)) {
            aLabel = RegExp.leftContext + RegExp.$2;
            accessKey = RegExp.$1;
        } else if (/^([^&]*)\&(([^&]).*$)/.test(aLabel)) {
            aLabel = RegExp.$1 + RegExp.$2;
            accessKey = RegExp.$3;
        }

        // && is the magic sequence to embed an & in your label.
        aLabel = aLabel.replace(/\&\&/g, "&");
        aNode.label = aLabel;

        // XXXjag bug 325251
        // Need to set this after aNode.setAttribute("value", aLabel);
        if (accessKey)
            aNode.accessKey = accessKey;
    },


    initTextbox(aName, aValue) {
        this.ui[aName + "Container"].hidden = false;
        this.ui[aName + "Textbox"].setAttribute("value",
                                                aValue !== null ? aValue : "");
    },

    setButtonsEnabledState(enabled) {
        this.ui.button0.disabled = !enabled;
        // button1 (cancel) remains enabled.
        this.ui.button2.disabled = !enabled;
        this.ui.button3.disabled = !enabled;
    },

    setDefaultFocus(isInitialLoad) {
        let b = (this.args.defaultButtonNum || 0);
        let button = this.ui["button" + b];

        if (!this.hasInputField) {
            let isOSX = ("nsILocalFileMac" in Components.interfaces);
            if (isOSX)
                this.ui.infoBody.focus();
            else
                button.focus();
        } else if (this.args.promptType == "promptPassword") {
            // When the prompt is initialized, focus and select the textbox
            // contents. Afterwards, only focus the textbox.
            if (isInitialLoad)
                this.ui.password1Textbox.select();
            else
                this.ui.password1Textbox.focus();
        } else if (isInitialLoad) {
                this.ui.loginTextbox.select();
        } else {
                this.ui.loginTextbox.focus();
        }
    },

    onCheckbox() {
        this.args.checked = this.ui.checkbox.checked;
    },

    onButton0() {
        this.args.promptActive = false;
        this.args.ok = true;
        this.args.buttonNumClicked = 0;

        let username = this.ui.loginTextbox.value;
        let password = this.ui.password1Textbox.value;

        // Return textfield values
        switch (this.args.promptType) {
          case "prompt":
            this.args.value = username;
            break;
          case "promptUserAndPass":
            this.args.user = username;
            this.args.pass = password;
            break;
          case "promptPassword":
            this.args.pass = password;
            break;
        }
    },

    onButton1() {
        this.args.promptActive = false;
        this.args.buttonNumClicked = 1;
    },

    onButton2() {
        this.args.promptActive = false;
        this.args.buttonNumClicked = 2;
    },

    onButton3() {
        this.args.promptActive = false;
        this.args.buttonNumClicked = 3;
    },

    abortPrompt() {
        this.args.promptActive = false;
        this.args.promptAborted = true;
    },

};
PK
!<	modules/CompatWarning.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["CompatWarning"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "console",
                                  "resource://gre/modules/Console.jsm");

function section(number, url) {
  const baseURL = "https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts";
  return { number, url: baseURL + url };
}

var CompatWarning = {
  // Sometimes we want to generate a warning, but put off issuing it
  // until later. For example, if someone registers a listener, we
  // might only want to warn about it if the listener actually
  // fires. However, we want the warning to show a stack for the
  // registration site.
  delayedWarning(msg, addon, warning) {
    function isShimLayer(filename) {
      return filename.indexOf("CompatWarning.jsm") != -1 ||
        filename.indexOf("RemoteAddonsParent.jsm") != -1 ||
        filename.indexOf("RemoteAddonsChild.jsm") != -1 ||
        filename.indexOf("multiprocessShims.js") != -1;
    }

    let stack = Components.stack;
    while (stack && isShimLayer(stack.filename))
      stack = stack.caller;

    let alreadyWarned = false;

    return function() {
      if (alreadyWarned) {
        return;
      }
      alreadyWarned = true;

      if (addon) {
        let histogram = Services.telemetry.getKeyedHistogramById("ADDON_SHIM_USAGE");
        histogram.add(addon, warning ? warning.number : 0);
      }

      if (!Preferences.get("dom.ipc.shims.enabledWarnings", false))
        return;

      let error = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
      if (!error || !Services.console) {
        // Too late during shutdown to use the nsIConsole
        return;
      }

      let message = `Warning: ${msg}`;
      if (warning)
        message += `\nMore info at: ${warning.url}`;

      error.init(
                 /* message*/ message,
                 /* sourceName*/ stack ? stack.filename : "",
                 /* sourceLine*/ stack ? stack.sourceLine : "",
                 /* lineNumber*/ stack ? stack.lineNumber : 0,
                 /* columnNumber*/ 0,
                 /* flags*/ Ci.nsIScriptError.warningFlag,
                 /* category*/ "chrome javascript");
      Services.console.logMessage(error);

      if (Preferences.get("dom.ipc.shims.dumpWarnings", false)) {
        dump(message + "\n");
        while (stack) {
          dump(stack + "\n");
          stack = stack.caller;
        }
        dump("\n");
      }
    };
  },

  warn(msg, addon, warning) {
    let delayed = this.delayedWarning(msg, addon, warning);
    delayed();
  },

  warnings: {
    content: section(1, "#gBrowser.contentWindow.2C_window.content..."),
    limitations_of_CPOWs: section(2, "#Limitations_of_CPOWs"),
    nsIContentPolicy: section(3, "#nsIContentPolicy"),
    nsIWebProgressListener: section(4, "#nsIWebProgressListener"),
    observers: section(5, "#Observers_in_the_chrome_process"),
    DOM_events: section(6, "#DOM_Events"),
    sandboxes: section(7, "#Sandboxes"),
    JSMs: section(8, "#JavaScript_code_modules_(JSMs)"),
    nsIAboutModule: section(9, "#nsIAboutModule"),
    // If more than 14 values appear here, you need to change the
    // ADDON_SHIM_USAGE histogram definition in Histograms.json.
  },
};
PK
!<QYYmodules/Console.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Define a 'console' API to roughly match the implementation provided by
 * Firebug.
 * This module helps cases where code is shared between the web and Firefox.
 * See also Browser.jsm for an implementation of other web constants to help
 * sharing code between the web and firefox;
 *
 * The API is only be a rough approximation for 3 reasons:
 * - The Firebug console API is implemented in many places with differences in
 *   the implementations, so there isn't a single reference to adhere to
 * - The Firebug console is a rich display compared with dump(), so there will
 *   be many things that we can't replicate
 * - The primary use of this API is debugging and error logging so the perfect
 *   implementation isn't always required (or even well defined)
 */

this.EXPORTED_SYMBOLS = [ "console", "ConsoleAPI" ];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

var gTimerRegistry = new Map();

/**
 * String utility to ensure that strings are a specified length. Strings
 * that are too long are truncated to the max length and the last char is
 * set to "_". Strings that are too short are padded with spaces.
 *
 * @param {string} aStr
 *        The string to format to the correct length
 * @param {number} aMaxLen
 *        The maximum allowed length of the returned string
 * @param {number} aMinLen (optional)
 *        The minimum allowed length of the returned string. If undefined,
 *        then aMaxLen will be used
 * @param {object} aOptions (optional)
 *        An object allowing format customization. Allowed customizations:
 *          'truncate' - can take the value "start" to truncate strings from
 *             the start as opposed to the end or "center" to truncate
 *             strings in the center.
 *          'align' - takes an alignment when padding is needed for MinLen,
 *             either "start" or "end".  Defaults to "start".
 * @return {string}
 *        The original string formatted to fit the specified lengths
 */
function fmt(aStr, aMaxLen, aMinLen, aOptions) {
  if (aMinLen == null) {
    aMinLen = aMaxLen;
  }
  if (aStr == null) {
    aStr = "";
  }
  if (aStr.length > aMaxLen) {
    if (aOptions && aOptions.truncate == "start") {
      return "_" + aStr.substring(aStr.length - aMaxLen + 1);
    } else if (aOptions && aOptions.truncate == "center") {
      let start = aStr.substring(0, (aMaxLen / 2));

      let end = aStr.substring((aStr.length - (aMaxLen / 2)) + 1);
      return start + "_" + end;
    }
    return aStr.substring(0, aMaxLen - 1) + "_";
  }
  if (aStr.length < aMinLen) {
    let padding = Array(aMinLen - aStr.length + 1).join(" ");
    aStr = (aOptions.align === "end") ? padding + aStr : aStr + padding;
  }
  return aStr;
}

/**
 * Utility to extract the constructor name of an object.
 * Object.toString gives: "[object ?????]"; we want the "?????".
 *
 * @param {object} aObj
 *        The object from which to extract the constructor name
 * @return {string}
 *        The constructor name
 */
function getCtorName(aObj) {
  if (aObj === null) {
    return "null";
  }
  if (aObj === undefined) {
    return "undefined";
  }
  if (aObj.constructor && aObj.constructor.name) {
    return aObj.constructor.name;
  }
  // If that fails, use Objects toString which sometimes gives something
  // better than 'Object', and at least defaults to Object if nothing better
  return Object.prototype.toString.call(aObj).slice(8, -1);
}

/**
 * Indicates whether an object is a JS or `Components.Exception` error.
 *
 * @param {object} aThing
          The object to check
 * @return {boolean}
          Is this object an error?
 */
function isError(aThing) {
  return aThing && (
           (typeof aThing.name == "string" &&
            aThing.name.startsWith("NS_ERROR_")) ||
           getCtorName(aThing).endsWith("Error"));
}

/**
 * A single line stringification of an object designed for use by humans
 *
 * @param {any} aThing
 *        The object to be stringified
 * @param {boolean} aAllowNewLines
 * @return {string}
 *        A single line representation of aThing, which will generally be at
 *        most 80 chars long
 */
function stringify(aThing, aAllowNewLines) {
  if (aThing === undefined) {
    return "undefined";
  }

  if (aThing === null) {
    return "null";
  }

  if (isError(aThing)) {
    return "Message: " + aThing;
  }

  if (typeof aThing == "object") {
    let type = getCtorName(aThing);
    if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
      return debugElement(aThing);
    }
    type = (type == "Object" ? "" : type + " ");
    let json;
    try {
      json = JSON.stringify(aThing);
    } catch (ex) {
      // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
      json = "{" + Object.keys(aThing).join(":..,") + ":.., }";
    }
    return type + json;
  }

  if (typeof aThing == "function") {
    return aThing.toString().replace(/\s+/g, " ");
  }

  let str = aThing.toString();
  if (!aAllowNewLines) {
    str = str.replace(/\n/g, "|");
  }
  return str;
}

/**
 * Create a simple debug representation of a given element.
 *
 * @param {nsIDOMElement} aElement
 *        The element to debug
 * @return {string}
 *        A simple single line representation of aElement
 */
function debugElement(aElement) {
  return "<" + aElement.tagName +
      (aElement.id ? "#" + aElement.id : "") +
      (aElement.className && aElement.className.split ?
          "." + aElement.className.split(" ").join(" .") :
          "") +
      ">";
}

/**
 * A multi line stringification of an object, designed for use by humans
 *
 * @param {any} aThing
 *        The object to be stringified
 * @return {string}
 *        A multi line representation of aThing
 */
function log(aThing) {
  if (aThing === null) {
    return "null\n";
  }

  if (aThing === undefined) {
    return "undefined\n";
  }

  if (typeof aThing == "object") {
    let reply = "";
    let type = getCtorName(aThing);
    if (type == "Map") {
      reply += "Map\n";
      for (let [key, value] of aThing) {
        reply += logProperty(key, value);
      }
    } else if (type == "Set") {
      let i = 0;
      reply += "Set\n";
      for (let value of aThing) {
        reply += logProperty("" + i, value);
        i++;
      }
    } else if (isError(aThing)) {
      reply += "  Message: " + aThing + "\n";
      if (aThing.stack) {
        reply += "  Stack:\n";
        var frame = aThing.stack;
        while (frame) {
          reply += "    " + frame + "\n";
          frame = frame.caller;
        }
      }
    } else if (aThing instanceof Components.interfaces.nsIDOMNode && aThing.tagName) {
      reply += "  " + debugElement(aThing) + "\n";
    } else {
      let keys = Object.getOwnPropertyNames(aThing);
      if (keys.length > 0) {
        reply += type + "\n";
        keys.forEach(function(aProp) {
          reply += logProperty(aProp, aThing[aProp]);
        });
      } else {
        reply += type + "\n";
        let root = aThing;
        let logged = [];
        while (root != null) {
          let properties = Object.keys(root);
          properties.sort();
          properties.forEach(function(property) {
            if (!(property in logged)) {
              logged[property] = property;
              reply += logProperty(property, aThing[property]);
            }
          });

          root = Object.getPrototypeOf(root);
          if (root != null) {
            reply += "  - prototype " + getCtorName(root) + "\n";
          }
        }
      }
    }

    return reply;
  }

  return "  " + aThing.toString() + "\n";
}

/**
 * Helper for log() which converts a property/value pair into an output
 * string
 *
 * @param {string} aProp
 *        The name of the property to include in the output string
 * @param {object} aValue
 *        Value assigned to aProp to be converted to a single line string
 * @return {string}
 *        Multi line output string describing the property/value pair
 */
function logProperty(aProp, aValue) {
  let reply = "";
  if (aProp == "stack" && typeof value == "string") {
    let trace = parseStack(aValue);
    reply += formatTrace(trace);
  } else {
    reply += "    - " + aProp + " = " + stringify(aValue) + "\n";
  }
  return reply;
}

const LOG_LEVELS = {
  "all": Number.MIN_VALUE,
  "debug": 2,
  "log": 3,
  "info": 3,
  "clear": 3,
  "trace": 3,
  "timeEnd": 3,
  "time": 3,
  "group": 3,
  "groupEnd": 3,
  "profile": 3,
  "profileEnd": 3,
  "dir": 3,
  "dirxml": 3,
  "warn": 4,
  "error": 5,
  "off": Number.MAX_VALUE,
};

/**
 * Helper to tell if a console message of `aLevel` type
 * should be logged in stdout and sent to consoles given
 * the current maximum log level being defined in `console.maxLogLevel`
 *
 * @param {string} aLevel
 *        Console message log level
 * @param {string} aMaxLevel {string}
 *        String identifier (See LOG_LEVELS for possible
 *        values) that allows to filter which messages
 *        are logged based on their log level
 * @return {boolean}
 *        Should this message be logged or not?
 */
function shouldLog(aLevel, aMaxLevel) {
  return LOG_LEVELS[aMaxLevel] <= LOG_LEVELS[aLevel];
}

/**
 * Parse a stack trace, returning an array of stack frame objects, where
 * each has filename/lineNumber/functionName members
 *
 * @param {string} aStack
 *        The serialized stack trace
 * @return {object[]}
 *        Array of { file: "...", line: NNN, call: "..." } objects
 */
function parseStack(aStack) {
  let trace = [];
  aStack.split("\n").forEach(function(line) {
    if (!line) {
      return;
    }
    let at = line.lastIndexOf("@");
    let posn = line.substring(at + 1);
    trace.push({
      filename: posn.split(":")[0],
      lineNumber: posn.split(":")[1],
      functionName: line.substring(0, at)
    });
  });
  return trace;
}

/**
 * Format a frame coming from Components.stack such that it can be used by the
 * Browser Console, via console-api-log-event notifications.
 *
 * @param {object} aFrame
 *        The stack frame from which to begin the walk.
 * @param {number=0} aMaxDepth
 *        Maximum stack trace depth. Default is 0 - no depth limit.
 * @return {object[]}
 *         An array of {filename, lineNumber, functionName, language} objects.
 *         These objects follow the same format as other console-api-log-event
 *         messages.
 */
function getStack(aFrame, aMaxDepth = 0) {
  if (!aFrame) {
    aFrame = Components.stack.caller;
  }
  let trace = [];
  while (aFrame) {
    trace.push({
      filename: aFrame.filename,
      lineNumber: aFrame.lineNumber,
      functionName: aFrame.name,
      language: aFrame.language,
    });
    if (aMaxDepth == trace.length) {
      break;
    }
    aFrame = aFrame.caller;
  }
  return trace;
}

/**
 * Take the output from parseStack() and convert it to nice readable
 * output
 *
 * @param {object[]} aTrace
 *        Array of trace objects as created by parseStack()
 * @return {string} Multi line report of the stack trace
 */
function formatTrace(aTrace) {
  let reply = "";
  aTrace.forEach(function(frame) {
    reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
             fmt(frame.lineNumber, 5, 5) + " " +
             fmt(frame.functionName, 75, 0, { truncate: "center" }) + "\n";
  });
  return reply;
}

/**
 * Create a new timer by recording the current time under the specified name.
 *
 * @param {string} aName
 *        The name of the timer.
 * @param {number} [aTimestamp=Date.now()]
 *        Optional timestamp that tells when the timer was originally started.
 * @return {object}
 *         The name property holds the timer name and the started property
 *         holds the time the timer was started. In case of error, it returns
 *         an object with the single property "error" that contains the key
 *         for retrieving the localized error message.
 */
function startTimer(aName, aTimestamp) {
  let key = aName.toString();
  if (!gTimerRegistry.has(key)) {
    gTimerRegistry.set(key, aTimestamp || Date.now());
  }
  return { name: aName, started: gTimerRegistry.get(key) };
}

/**
 * Stop the timer with the specified name and retrieve the elapsed time.
 *
 * @param {string} aName
 *        The name of the timer.
 * @param {number} [aTimestamp=Date.now()]
 *        Optional timestamp that tells when the timer was originally stopped.
 * @return {object}
 *         The name property holds the timer name and the duration property
 *         holds the number of milliseconds since the timer was started.
 */
function stopTimer(aName, aTimestamp) {
  let key = aName.toString();
  let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
  gTimerRegistry.delete(key);
  return { name: aName, duration };
}

/**
 * Dump a new message header to stdout by taking care of adding an eventual
 * prefix
 *
 * @param {object} aConsole
 *        ConsoleAPI instance
 * @param {string} aLevel
 *        The string identifier for the message log level
 * @param {string} aMessage
 *        The string message to print to stdout
 */
function dumpMessage(aConsole, aLevel, aMessage) {
  aConsole.dump(
    "console." + aLevel + ": " +
    (aConsole.prefix ? aConsole.prefix + ": " : "") +
    aMessage + "\n"
  );
}

/**
 * Create a function which will output a concise level of output when used
 * as a logging function
 *
 * @param {string} aLevel
 *        A prefix to all output generated from this function detailing the
 *        level at which output occurred
 * @return {function}
 *        A logging function
 * @see createMultiLineDumper()
 */
function createDumper(aLevel) {
  return function() {
    if (!shouldLog(aLevel, this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    sendConsoleAPIMessage(this, aLevel, frame, args);
    let data = args.map(function(arg) {
      return stringify(arg, true);
    });
    dumpMessage(this, aLevel, data.join(" "));
  };
}

/**
 * Create a function which will output more detailed level of output when
 * used as a logging function
 *
 * @param {string} aLevel
 *        A prefix to all output generated from this function detailing the
 *        level at which output occurred
 * @return {function}
 *        A logging function
 * @see createDumper()
 */
function createMultiLineDumper(aLevel) {
  return function() {
    if (!shouldLog(aLevel, this.maxLogLevel)) {
      return;
    }
    dumpMessage(this, aLevel, "");
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    sendConsoleAPIMessage(this, aLevel, frame, args);
    args.forEach(function(arg) {
      this.dump(log(arg));
    }, this);
  };
}

/**
 * Send a Console API message. This function will send a console-api-log-event
 * notification through the nsIObserverService.
 *
 * @param {object} aConsole
 *        The instance of ConsoleAPI performing the logging.
 * @param {string} aLevel
 *        Message severity level. This is usually the name of the console method
 *        that was called.
 * @param {object} aFrame
 *        The youngest stack frame coming from Components.stack, as formatted by
 *        getStack().
 * @param {array} aArgs
 *        The arguments given to the console method.
 * @param {object} aOptions
 *        Object properties depend on the console method that was invoked:
 *        - timer: for time() and timeEnd(). Holds the timer information.
 *        - groupName: for group(), groupCollapsed() and groupEnd().
 *        - stacktrace: for trace(). Holds the array of stack frames as given by
 *        getStack().
 */
function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {}) {
  let consoleEvent = {
    ID: "jsm",
    innerID: aConsole.innerID || aFrame.filename,
    consoleID: aConsole.consoleID,
    level: aLevel,
    filename: aFrame.filename,
    lineNumber: aFrame.lineNumber,
    functionName: aFrame.functionName,
    timeStamp: Date.now(),
    arguments: aArgs,
    prefix: aConsole.prefix,
  };

  consoleEvent.wrappedJSObject = consoleEvent;

  switch (aLevel) {
    case "trace":
      consoleEvent.stacktrace = aOptions.stacktrace;
      break;
    case "time":
    case "timeEnd":
      consoleEvent.timer = aOptions.timer;
      break;
    case "group":
    case "groupCollapsed":
    case "groupEnd":
      try {
        consoleEvent.groupName = Array.prototype.join.call(aArgs, " ");
      } catch (ex) {
        Cu.reportError(ex);
        Cu.reportError(ex.stack);
        return;
      }
      break;
  }

  let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
                            .getService(Ci.nsIConsoleAPIStorage);
  if (ConsoleAPIStorage) {
    ConsoleAPIStorage.recordEvent("jsm", null, consoleEvent);
  }
}

/**
 * This creates a console object that somewhat replicates Firebug's console
 * object
 *
 * @param {object} aConsoleOptions
 *        Optional dictionary with a set of runtime console options:
 *        - prefix {string} : An optional prefix string to be printed before
 *                            the actual logged message
 *        - maxLogLevel {string} : String identifier (See LOG_LEVELS for
 *                            possible values) that allows to filter which
 *                            messages are logged based on their log level.
 *                            If falsy value, all messages will be logged.
 *                            If wrong value that doesn't match any key of
 *                            LOG_LEVELS, no message will be logged
 *        - maxLogLevelPref {string} : String pref name which contains the
 *                            level to use for maxLogLevel. If the pref doesn't
 *                            exist or gets removed, the maxLogLevel will default
 *                            to the value passed to this constructor (or "all"
 *                            if it wasn't specified).
 *        - dump {function} : An optional function to intercept all strings
 *                            written to stdout
 *        - innerID {string}: An ID representing the source of the message.
 *                            Normally the inner ID of a DOM window.
 *        - consoleID {string} : String identified for the console, this will
 *                            be passed through the console notifications
 * @return {object}
 *        A console API instance object
 */
function ConsoleAPI(aConsoleOptions = {}) {
  // Normalize console options to set default values
  // in order to avoid runtime checks on each console method call.
  this.dump = aConsoleOptions.dump || dump;
  this.prefix = aConsoleOptions.prefix || "";
  this.maxLogLevel = aConsoleOptions.maxLogLevel;
  this.innerID = aConsoleOptions.innerID || null;
  this.consoleID = aConsoleOptions.consoleID || "";

  // Setup maxLogLevelPref watching
  let updateMaxLogLevel = () => {
    if (Services.prefs.getPrefType(aConsoleOptions.maxLogLevelPref) == Services.prefs.PREF_STRING) {
      this._maxLogLevel = Services.prefs.getCharPref(aConsoleOptions.maxLogLevelPref).toLowerCase();
    } else {
      this._maxLogLevel = this._maxExplicitLogLevel;
    }
  };

  if (aConsoleOptions.maxLogLevelPref) {
    updateMaxLogLevel();
    Services.prefs.addObserver(aConsoleOptions.maxLogLevelPref, updateMaxLogLevel);
  }

  // Bind all the functions to this object.
  for (let prop in this) {
    if (typeof(this[prop]) === "function") {
      this[prop] = this[prop].bind(this);
    }
  }
}

ConsoleAPI.prototype = {
  /**
   * The last log level that was specified via the constructor or setter. This
   * is used as a fallback if the pref doesn't exist or is removed.
   */
  _maxExplicitLogLevel: null,
  /**
   * The current log level via all methods of setting (pref or via the API).
   */
  _maxLogLevel: null,
  debug: createMultiLineDumper("debug"),
  log: createDumper("log"),
  info: createDumper("info"),
  warn: createDumper("warn"),
  error: createMultiLineDumper("error"),
  exception: createMultiLineDumper("error"),

  trace: function Console_trace() {
    if (!shouldLog("trace", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let trace = getStack(Components.stack.caller);
    sendConsoleAPIMessage(this, "trace", trace[0], args,
                          { stacktrace: trace });
    dumpMessage(this, "trace", "\n" + formatTrace(trace));
  },
  clear: function Console_clear() {},

  dir: createMultiLineDumper("dir"),
  dirxml: createMultiLineDumper("dirxml"),
  group: createDumper("group"),
  groupEnd: createDumper("groupEnd"),

  time: function Console_time() {
    if (!shouldLog("time", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    let timer = startTimer(args[0]);
    sendConsoleAPIMessage(this, "time", frame, args, { timer });
    dumpMessage(this, "time",
                "'" + timer.name + "' @ " + (new Date()));
  },

  timeEnd: function Console_timeEnd() {
    if (!shouldLog("timeEnd", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    let timer = stopTimer(args[0]);
    sendConsoleAPIMessage(this, "timeEnd", frame, args, { timer });
    dumpMessage(this, "timeEnd",
                "'" + timer.name + "' " + timer.duration + "ms");
  },

  profile(profileName) {
    if (!shouldLog("profile", this.maxLogLevel)) {
      return;
    }
    Services.obs.notifyObservers({
      wrappedJSObject: {
        action: "profile",
        arguments: [ profileName ]
      }
    }, "console-api-profiler");
    dumpMessage(this, "profile", `'${profileName}'`);
  },

  profileEnd(profileName) {
    if (!shouldLog("profileEnd", this.maxLogLevel)) {
      return;
    }
    Services.obs.notifyObservers({
      wrappedJSObject: {
        action: "profileEnd",
        arguments: [ profileName ]
      }
    }, "console-api-profiler");
    dumpMessage(this, "profileEnd", `'${profileName}'`);
  },

  get maxLogLevel() {
    return this._maxLogLevel || "all";
  },

  set maxLogLevel(aValue) {
    this._maxLogLevel = this._maxExplicitLogLevel = aValue;
  },
};

this.console = new ConsoleAPI();
this.ConsoleAPI = ConsoleAPI;
PK
!<
j

modules/ContentPrefInstance.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;

this.EXPORTED_SYMBOLS = ["ContentPrefInstance"];

// This is a wrapper for nsIContentPrefService that alleviates the need to pass
// an nsILoadContext argument to every method. Pass the context to the constructor
// instead and continue on your way in blissful ignorance.

this.ContentPrefInstance = function ContentPrefInstance(aContext) {
  this._contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
                           getService(Ci.nsIContentPrefService);
  this._context = aContext;
};

ContentPrefInstance.prototype = {
  getPref: function ContentPrefInstance_init(aName, aGroup, aCallback) {
    return this._contentPrefSvc.getPref(aName, aGroup, this._context, aCallback);
  },

  setPref: function ContentPrefInstance_setPref(aGroup, aName, aValue, aContext) {
    return this._contentPrefSvc.setPref(aGroup, aName, aValue,
                                        aContext ? aContext : this._context);
  },

  hasPref: function ContentPrefInstance_hasPref(aGroup, aName) {
    return this._contentPrefSvc.hasPref(aGroup, aName, this._context);
  },

  hasCachedPref: function ContentPrefInstance_hasCachedPref(aGroup, aName) {
    return this._contentPrefSvc.hasCachedPref(aGroup, aName, this._context);
  },

  removePref: function ContentPrefInstance_removePref(aGroup, aName) {
    return this._contentPrefSvc.removePref(aGroup, aName, this._context);
  },

  removeGroupedPrefs: function ContentPrefInstance_removeGroupedPrefs() {
    return this._contentPrefSvc.removeGroupedPrefs(this._context);
  },

  removePrefsByName: function ContentPrefInstance_removePrefsByName(aName) {
    return this._contentPrefSvc.removePrefsByName(aName, this._context);
  },

  getPrefs: function ContentPrefInstance_getPrefs(aGroup) {
    return this._contentPrefSvc.getPrefs(aGroup, this._context);
  },

  getPrefsByName: function ContentPrefInstance_getPrefsByName(aName) {
    return this._contentPrefSvc.getPrefsByName(aName, this._context);
  },

  addObserver: function ContentPrefInstance_addObserver(aName, aObserver) {
    return this._contentPrefSvc.addObserver(aName, aObserver);
  },

  removeObserver: function ContentPrefInstance_removeObserver(aName, aObserver) {
    return this._contentPrefSvc.removeObserver(aName, aObserver);
  },

  get grouper() {
    return this._contentPrefSvc.grouper;
  },

  get DBConnection() {
    return this._contentPrefSvc.DBConnection;
  },

  set loadContext(aLoadContext) {
    this._context = aLoadContext;
  }
};
PK
!<t"qqmodules/ContentPrefService2.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file is an XPCOM component that implements nsIContentPrefService2.
// Although it's a JSM, it's not intended to be imported by consumers like JSMs
// are usually imported.  It's only a JSM so that nsContentPrefService.js can
// easily use it.  Consumers should access this component with the usual XPCOM
// rigmarole:
//
//   Cc["@mozilla.org/content-pref/service;1"].
//   getService(Ci.nsIContentPrefService2);
//
// That contract ID actually belongs to nsContentPrefService.js, which, when
// QI'ed to nsIContentPrefService2, returns an instance of this component.
//
// The plan is to eventually remove nsIContentPrefService and its
// implementation, nsContentPrefService.js.  At such time this file can stop
// being a JSM, and the "_cps" parts that ContentPrefService2 relies on and
// NSGetFactory and all the other XPCOM initialization goop in
// nsContentPrefService.js can be moved here.
//
// See https://bugzilla.mozilla.org/show_bug.cgi?id=699859

var EXPORTED_SYMBOLS = [
  "ContentPrefService2",
];

const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");

const GROUP_CLAUSE = `
  SELECT id
  FROM groups
  WHERE name = :group OR
        (:includeSubdomains AND name LIKE :pattern ESCAPE '/')
`;

function ContentPrefService2(cps) {
  this._cps = cps;
  this._cache = cps._cache;
  this._pbStore = cps._privModeStorage;
}

ContentPrefService2.prototype = {

  getByName: function CPS2_getByName(name, context, callback) {
    checkNameArg(name);
    checkCallbackArg(callback, true);

    // Some prefs may be in both the database and the private browsing store.
    // Notify the caller of such prefs only once, using the values from private
    // browsing.
    let pbPrefs = new ContentPrefStore();
    if (context && context.usePrivateBrowsing) {
      for (let [sgroup, sname, val] of this._pbStore) {
        if (sname == name) {
          pbPrefs.set(sgroup, sname, val);
        }
      }
    }

    let stmt1 = this._stmt(`
      SELECT groups.name AS grp, prefs.value AS value
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE settings.name = :name
    `);
    stmt1.params.name = name;

    let stmt2 = this._stmt(`
      SELECT NULL AS grp, prefs.value AS value
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      WHERE settings.name = :name AND prefs.groupID ISNULL
    `);
    stmt2.params.name = name;

    this._execStmts([stmt1, stmt2], {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        let val = row.getResultByName("value");
        this._cache.set(grp, name, val);
        if (!pbPrefs.has(grp, name))
          cbHandleResult(callback, new ContentPref(grp, name, val));
      },
      onDone: function onDone(reason, ok, gotRow) {
        if (ok) {
          for (let [pbGroup, pbName, pbVal] of pbPrefs) {
            cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
          }
        }
        cbHandleCompletion(callback, reason);
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  getByDomainAndName: function CPS2_getByDomainAndName(group, name, context,
                                                       callback) {
    checkGroupArg(group);
    this._get(group, name, false, context, callback);
  },

  getBySubdomainAndName: function CPS2_getBySubdomainAndName(group, name,
                                                             context,
                                                             callback) {
    checkGroupArg(group);
    this._get(group, name, true, context, callback);
  },

  getGlobal: function CPS2_getGlobal(name, context, callback) {
    this._get(null, name, false, context, callback);
  },

  _get: function CPS2__get(group, name, includeSubdomains, context, callback) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkCallbackArg(callback, true);

    // Some prefs may be in both the database and the private browsing store.
    // Notify the caller of such prefs only once, using the values from private
    // browsing.
    let pbPrefs = new ContentPrefStore();
    if (context && context.usePrivateBrowsing) {
      for (let [sgroup, val] of
             this._pbStore.match(group, name, includeSubdomains)) {
        pbPrefs.set(sgroup, name, val);
      }
    }

    this._execStmts([this._commonGetStmt(group, name, includeSubdomains)], {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        let val = row.getResultByName("value");
        this._cache.set(grp, name, val);
        if (!pbPrefs.has(group, name))
          cbHandleResult(callback, new ContentPref(grp, name, val));
      },
      onDone: function onDone(reason, ok, gotRow) {
        if (ok) {
          if (!gotRow)
            this._cache.set(group, name, undefined);
          for (let [pbGroup, pbName, pbVal] of pbPrefs) {
            cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
          }
        }
        cbHandleCompletion(callback, reason);
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  _commonGetStmt: function CPS2__commonGetStmt(group, name, includeSubdomains) {
    let stmt = group ?
      this._stmtWithGroupClause(group, includeSubdomains, `
        SELECT groups.name AS grp, prefs.value AS value
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        JOIN groups ON groups.id = prefs.groupID
        WHERE settings.name = :name AND prefs.groupID IN (${GROUP_CLAUSE})
      `) :
      this._stmt(`
        SELECT NULL AS grp, prefs.value AS value
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE settings.name = :name AND prefs.groupID ISNULL
      `);
    stmt.params.name = name;
    return stmt;
  },

  _stmtWithGroupClause: function CPS2__stmtWithGroupClause(group,
                                                           includeSubdomains,
                                                           sql) {
    let stmt = this._stmt(sql);
    stmt.params.group = group;
    stmt.params.includeSubdomains = includeSubdomains || false;
    stmt.params.pattern = "%." + stmt.escapeStringForLIKE(group, "/");
    return stmt;
  },

  getCachedByDomainAndName: function CPS2_getCachedByDomainAndName(group,
                                                                   name,
                                                                   context) {
    checkGroupArg(group);
    let prefs = this._getCached(group, name, false, context);
    return prefs[0] || null;
  },

  getCachedBySubdomainAndName: function CPS2_getCachedBySubdomainAndName(group,
                                                                         name,
                                                                         context,
                                                                         len) {
    checkGroupArg(group);
    let prefs = this._getCached(group, name, true, context);
    if (len)
      len.value = prefs.length;
    return prefs;
  },

  getCachedGlobal: function CPS2_getCachedGlobal(name, context) {
    let prefs = this._getCached(null, name, false, context);
    return prefs[0] || null;
  },

  _getCached: function CPS2__getCached(group, name, includeSubdomains,
                                       context) {
    group = this._parseGroup(group);
    checkNameArg(name);

    let storesToCheck = [this._cache];
    if (context && context.usePrivateBrowsing)
      storesToCheck.push(this._pbStore);

    let outStore = new ContentPrefStore();
    storesToCheck.forEach(function(store) {
      for (let [sgroup, val] of store.match(group, name, includeSubdomains)) {
        outStore.set(sgroup, name, val);
      }
    });

    let prefs = [];
    for (let [sgroup, sname, val] of outStore) {
      prefs.push(new ContentPref(sgroup, sname, val));
    }
    return prefs;
  },

  set: function CPS2_set(group, name, value, context, callback) {
    checkGroupArg(group);
    this._set(group, name, value, context, callback);
  },

  setGlobal: function CPS2_setGlobal(name, value, context, callback) {
    this._set(null, name, value, context, callback);
  },

  _set: function CPS2__set(group, name, value, context, callback) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkValueArg(value);
    checkCallbackArg(callback, false);

    if (context && context.usePrivateBrowsing) {
      this._pbStore.set(group, name, value);
      this._schedule(function() {
        cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK);
        this._cps._notifyPrefSet(group, name, value, context.usePrivateBrowsing);
      });
      return;
    }

    // Invalidate the cached value so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    this._cache.remove(group, name);

    let stmts = [];

    // Create the setting if it doesn't exist.
    let stmt = this._stmt(`
      INSERT OR IGNORE INTO settings (id, name)
      VALUES((SELECT id FROM settings WHERE name = :name), :name)
    `);
    stmt.params.name = name;
    stmts.push(stmt);

    // Create the group if it doesn't exist.
    if (group) {
      stmt = this._stmt(`
        INSERT OR IGNORE INTO groups (id, name)
        VALUES((SELECT id FROM groups WHERE name = :group), :group)
      `);
      stmt.params.group = group;
      stmts.push(stmt);
    }

    // Finally create or update the pref.
    if (group) {
      stmt = this._stmt(`
        INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
        VALUES(
          (SELECT prefs.id
           FROM prefs
           JOIN groups ON groups.id = prefs.groupID
           JOIN settings ON settings.id = prefs.settingID
           WHERE groups.name = :group AND settings.name = :name),
          (SELECT id FROM groups WHERE name = :group),
          (SELECT id FROM settings WHERE name = :name),
          :value,
          :now
        )
      `);
      stmt.params.group = group;
    } else {
      stmt = this._stmt(`
        INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
        VALUES(
          (SELECT prefs.id
           FROM prefs
           JOIN settings ON settings.id = prefs.settingID
           WHERE prefs.groupID IS NULL AND settings.name = :name),
          NULL,
          (SELECT id FROM settings WHERE name = :name),
          :value,
          :now
        )
      `);
    }
    stmt.params.name = name;
    stmt.params.value = value;
    stmt.params.now = Date.now() / 1000;
    stmts.push(stmt);

    this._execStmts(stmts, {
      onDone: function onDone(reason, ok) {
        if (ok)
          this._cache.setWithCast(group, name, value);
        cbHandleCompletion(callback, reason);
        if (ok)
          this._cps._notifyPrefSet(group, name, value, context && context.usePrivateBrowsing);
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  removeByDomainAndName: function CPS2_removeByDomainAndName(group, name,
                                                             context,
                                                             callback) {
    checkGroupArg(group);
    this._remove(group, name, false, context, callback);
  },

  removeBySubdomainAndName: function CPS2_removeBySubdomainAndName(group, name,
                                                                   context,
                                                                   callback) {
    checkGroupArg(group);
    this._remove(group, name, true, context, callback);
  },

  removeGlobal: function CPS2_removeGlobal(name, context, callback) {
    this._remove(null, name, false, context, callback);
  },

  _remove: function CPS2__remove(group, name, includeSubdomains, context,
                                 callback) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
      this._cache.remove(sgroup, name);
    }

    let stmts = [];

    // First get the matching prefs.
    stmts.push(this._commonGetStmt(group, name, includeSubdomains));

    // Delete the matching prefs.
    let stmt = this._stmtWithGroupClause(group, includeSubdomains, `
      DELETE FROM prefs
      WHERE settingID = (SELECT id FROM settings WHERE name = :name) AND
            CASE typeof(:group)
            WHEN 'null' THEN prefs.groupID IS NULL
            ELSE prefs.groupID IN (${GROUP_CLAUSE})
            END
    `);
    stmt.params.name = name;
    stmts.push(stmt);

    stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());

    let prefs = new ContentPrefStore();

    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: function onDone(reason, ok) {
        if (ok) {
          this._cache.set(group, name, undefined);
          if (isPrivate) {
            for (let [sgroup, ] of
                   this._pbStore.match(group, name, includeSubdomains)) {
              prefs.set(sgroup, name, undefined);
              this._pbStore.remove(sgroup, name);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, , ] of prefs) {
            this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
          }
        }
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  // Deletes settings and groups that are no longer used.
  _settingsAndGroupsCleanupStmts() {
    // The NOTNULL term in the subquery of the second statment is needed because of
    // SQLite's weird IN behavior vis-a-vis NULLs.  See http://sqlite.org/lang_expr.html.
    return [
      this._stmt(`
        DELETE FROM settings
        WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
      `),
      this._stmt(`
        DELETE FROM groups WHERE id NOT IN (
          SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
        )
      `)
    ];
  },

  removeByDomain: function CPS2_removeByDomain(group, context, callback) {
    checkGroupArg(group);
    this._removeByDomain(group, false, context, callback);
  },

  removeBySubdomain: function CPS2_removeBySubdomain(group, context, callback) {
    checkGroupArg(group);
    this._removeByDomain(group, true, context, callback);
  },

  removeAllGlobals: function CPS2_removeAllGlobals(context, callback) {
    this._removeByDomain(null, false, context, callback);
  },

  _removeByDomain: function CPS2__removeByDomain(group, includeSubdomains,
                                                 context, callback) {
    group = this._parseGroup(group);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
      this._cache.removeGroup(sgroup);
    }

    let stmts = [];

    // First get the matching prefs, then delete groups and prefs that reference
    // deleted groups.
    if (group) {
      stmts.push(this._stmtWithGroupClause(group, includeSubdomains, `
        SELECT groups.name AS grp, settings.name AS name
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        JOIN groups ON groups.id = prefs.groupID
        WHERE prefs.groupID IN (${GROUP_CLAUSE})
      `));
      stmts.push(this._stmtWithGroupClause(group, includeSubdomains,
        `DELETE FROM groups WHERE id IN (${GROUP_CLAUSE})`
      ));
      stmts.push(this._stmt(`
        DELETE FROM prefs
        WHERE groupID NOTNULL AND groupID NOT IN (SELECT id FROM groups)
      `));
    } else {
      stmts.push(this._stmt(`
        SELECT NULL AS grp, settings.name AS name
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE prefs.groupID IS NULL
      `));
      stmts.push(this._stmt(
        "DELETE FROM prefs WHERE groupID IS NULL"
      ));
    }

    // Finally delete settings that are no longer referenced.
    stmts.push(this._stmt(`
      DELETE FROM settings
      WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
    `));

    let prefs = new ContentPrefStore();

    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        let name = row.getResultByName("name");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: function onDone(reason, ok) {
        if (ok && isPrivate) {
          for (let [sgroup, sname, ] of this._pbStore) {
            if (!group ||
                (!includeSubdomains && group == sgroup) ||
                (includeSubdomains && sgroup && this._pbStore.groupsMatchIncludingSubdomains(group, sgroup))) {
              prefs.set(sgroup, sname, undefined);
              this._pbStore.remove(sgroup, sname);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, sname, ] of prefs) {
            this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
          }
        }
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  _removeAllDomainsSince: function CPS2__removeAllDomainsSince(since, context, callback) {
    checkCallbackArg(callback, false);

    since /= 1000;

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    // Invalidate all the group cache because we don't know which groups will be removed.
    this._cache.removeAllGroups();

    let stmts = [];

    // Get prefs that are about to be removed to notify about their removal.
    let stmt = this._stmt(`
      SELECT groups.name AS grp, settings.name AS name
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE timestamp >= :since
    `);
    stmt.params.since = since;
    stmts.push(stmt);

    // Do the actual remove.
    stmt = this._stmt(`
      DELETE FROM prefs WHERE groupID NOTNULL AND timestamp >= :since
    `);
    stmt.params.since = since;
    stmts.push(stmt);

    // Cleanup no longer used values.
    stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());

    let prefs = new ContentPrefStore();
    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        let name = row.getResultByName("name");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: function onDone(reason, ok) {
        // This nukes all the groups in _pbStore since we don't have their timestamp
        // information.
        if (ok && isPrivate) {
          for (let [sgroup, sname, ] of this._pbStore) {
            if (sgroup) {
              prefs.set(sgroup, sname, undefined);
            }
          }
          this._pbStore.removeAllGroups();
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, sname, ] of prefs) {
            this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
          }
        }
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  removeAllDomainsSince: function CPS2_removeAllDomainsSince(since, context, callback) {
    this._removeAllDomainsSince(since, context, callback);
  },

  removeAllDomains: function CPS2_removeAllDomains(context, callback) {
    this._removeAllDomainsSince(0, context, callback);
  },

  removeByName: function CPS2_removeByName(name, context, callback) {
    checkNameArg(name);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let [group, sname, ] of this._cache) {
      if (sname == name)
        this._cache.remove(group, name);
    }

    let stmts = [];

    // First get the matching prefs.  Include null if any of those prefs are
    // global.
    let stmt = this._stmt(`
      SELECT groups.name AS grp
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE settings.name = :name
      UNION
      SELECT NULL AS grp
      WHERE EXISTS (
        SELECT prefs.id
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE settings.name = :name AND prefs.groupID IS NULL
      )
    `);
    stmt.params.name = name;
    stmts.push(stmt);

    // Delete the target settings.
    stmt = this._stmt(
      "DELETE FROM settings WHERE name = :name"
    );
    stmt.params.name = name;
    stmts.push(stmt);

    // Delete prefs and groups that are no longer used.
    stmts.push(this._stmt(
      "DELETE FROM prefs WHERE settingID NOT IN (SELECT id FROM settings)"
    ));
    stmts.push(this._stmt(`
      DELETE FROM groups WHERE id NOT IN (
        SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
      )
    `));

    let prefs = new ContentPrefStore();
    let isPrivate = context && context.usePrivateBrowsing;

    this._execStmts(stmts, {
      onRow: function onRow(row) {
        let grp = row.getResultByName("grp");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: function onDone(reason, ok) {
        if (ok && isPrivate) {
          for (let [sgroup, sname, ] of this._pbStore) {
            if (sname === name) {
              prefs.set(sgroup, name, undefined);
              this._pbStore.remove(sgroup, name);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, , ] of prefs) {
            this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
          }
        }
      },
      onError: function onError(nsresult) {
        cbHandleError(callback, nsresult);
      }
    });
  },

  destroy: function CPS2_destroy() {
    if (this._statements) {
      for (let sql in this._statements) {
        let stmt = this._statements[sql];
        stmt.finalize();
      }
    }
  },

  /**
   * Returns the cached mozIStorageAsyncStatement for the given SQL.  If no such
   * statement is cached, one is created and cached.
   *
   * @param sql  The SQL query string.
   * @return     The cached, possibly new, statement.
   */
  _stmt: function CPS2__stmt(sql) {
    if (!this._statements)
      this._statements = {};
    if (!this._statements[sql])
      this._statements[sql] = this._cps._dbConnection.createAsyncStatement(sql);
    return this._statements[sql];
  },

  /**
   * Executes some async statements.
   *
   * @param stmts      An array of mozIStorageAsyncStatements.
   * @param callbacks  An object with the following methods:
   *                   onRow(row) (optional)
   *                     Called once for each result row.
   *                     row: A mozIStorageRow.
   *                   onDone(reason, reasonOK, didGetRow) (required)
   *                     Called when done.
   *                     reason: A nsIContentPrefService2.COMPLETE_* value.
   *                     reasonOK: reason == nsIContentPrefService2.COMPLETE_OK.
   *                     didGetRow: True if onRow was ever called.
   *                   onError(nsresult) (optional)
   *                     Called on error.
   *                     nsresult: The error code.
   */
  _execStmts: function CPS2__execStmts(stmts, callbacks) {
    let self = this;
    let gotRow = false;
    this._cps._dbConnection.executeAsync(stmts, stmts.length, {
      handleResult: function handleResult(results) {
        try {
          let row = null;
          while ((row = results.getNextRow())) {
            gotRow = true;
            if (callbacks.onRow)
              callbacks.onRow.call(self, row);
          }
        } catch (err) {
          Cu.reportError(err);
        }
      },
      handleCompletion: function handleCompletion(reason) {
        try {
          let ok = reason == Ci.mozIStorageStatementCallback.REASON_FINISHED;
          callbacks.onDone.call(self,
                                ok ? Ci.nsIContentPrefCallback2.COMPLETE_OK :
                                  Ci.nsIContentPrefCallback2.COMPLETE_ERROR,
                                ok, gotRow);
        } catch (err) {
          Cu.reportError(err);
        }
      },
      handleError: function handleError(error) {
        try {
          if (callbacks.onError)
            callbacks.onError.call(self, Cr.NS_ERROR_FAILURE);
        } catch (err) {
          Cu.reportError(err);
        }
      }
    });
  },

  /**
   * Parses the domain (the "group", to use the database's term) from the given
   * string.
   *
   * @param groupStr  Assumed to be either a string or falsey.
   * @return          If groupStr is a valid URL string, returns the domain of
   *                  that URL.  If groupStr is some other nonempty string,
   *                  returns groupStr itself.  Otherwise returns null.
   */
  _parseGroup: function CPS2__parseGroup(groupStr) {
    if (!groupStr)
      return null;
    try {
      var groupURI = Services.io.newURI(groupStr);
    } catch (err) {
      return groupStr;
    }
    return this._cps._grouper.group(groupURI);
  },

  _schedule: function CPS2__schedule(fn) {
    Services.tm.dispatchToMainThread(fn.bind(this));
  },

  addObserverForName: function CPS2_addObserverForName(name, observer) {
    this._cps._addObserver(name, observer);
  },

  removeObserverForName: function CPS2_removeObserverForName(name, observer) {
    this._cps._removeObserver(name, observer);
  },

  extractDomain: function CPS2_extractDomain(str) {
    return this._parseGroup(str);
  },

  /**
   * Tests use this as a backchannel by calling it directly.
   *
   * @param subj   This value depends on topic.
   * @param topic  The backchannel "method" name.
   * @param data   This value depends on topic.
   */
  observe: function CPS2_observe(subj, topic, data) {
    switch (topic) {
    case "test:reset":
      let fn = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
      this._reset(fn);
      break;
    case "test:db":
      let obj = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
      obj.value = this._cps._dbConnection;
      break;
    }
  },

  /**
   * Removes all state from the service.  Used by tests.
   *
   * @param callback  A function that will be called when done.
   */
  _reset: function CPS2__reset(callback) {
    this._pbStore.removeAll();
    this._cache.removeAll();

    let cps = this._cps;
    cps._observers = {};
    cps._genericObservers = [];

    let tables = ["prefs", "groups", "settings"];
    let stmts = tables.map(t => this._stmt(`DELETE FROM ${t}`));
    this._execStmts(stmts, { onDone: () => callback() });
  },

  QueryInterface: function CPS2_QueryInterface(iid) {
    let supportedIIDs = [
      Ci.nsIContentPrefService2,
      Ci.nsIObserver,
      Ci.nsISupports,
    ];
    if (supportedIIDs.some(i => iid.equals(i)))
      return this;
    if (iid.equals(Ci.nsIContentPrefService))
      return this._cps;
    throw Cr.NS_ERROR_NO_INTERFACE;
  },
};

function checkGroupArg(group) {
  if (!group || typeof(group) != "string")
    throw invalidArg("domain must be nonempty string.");
}

function checkNameArg(name) {
  if (!name || typeof(name) != "string")
    throw invalidArg("name must be nonempty string.");
}

function checkValueArg(value) {
  if (value === undefined)
    throw invalidArg("value must not be undefined.");
}

function checkCallbackArg(callback, required) {
  if (callback && !(callback instanceof Ci.nsIContentPrefCallback2))
    throw invalidArg("callback must be an nsIContentPrefCallback2.");
  if (!callback && required)
    throw invalidArg("callback must be given.");
}

function invalidArg(msg) {
  return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
}
PK
!<#modules/ContentPrefServiceChild.jsm/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "ContentPrefServiceChild" ];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");

// We only need one bit of information out of the context.
function contextArg(context) {
  return (context && context.usePrivateBrowsing) ?
            { usePrivateBrowsing: true } :
            null;
}

function NYI() {
  throw new Error("Do not add any new users of these functions");
}

function CallbackCaller(callback) {
  this._callback = callback;
}

CallbackCaller.prototype = {
  handleResult(contentPref) {
    cbHandleResult(this._callback,
                   new ContentPref(contentPref.domain,
                                   contentPref.name,
                                   contentPref.value));
  },

  handleError(result) {
    cbHandleError(this._callback, result);
  },

  handleCompletion(reason) {
    cbHandleCompletion(this._callback, reason);
  },
};

var ContentPrefServiceChild = {
  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIContentPrefService2 ]),

  // Map from pref name -> set of observers
  _observers: new Map(),

  _mm: Cc["@mozilla.org/childprocessmessagemanager;1"]
         .getService(Ci.nsIMessageSender),

  _getRandomId() {
    return Cc["@mozilla.org/uuid-generator;1"]
             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
  },

  // Map from random ID string -> CallbackCaller, per request
  _requests: new Map(),

  init() {
    this._mm.addMessageListener("ContentPrefs:HandleResult", this);
    this._mm.addMessageListener("ContentPrefs:HandleError", this);
    this._mm.addMessageListener("ContentPrefs:HandleCompletion", this);
  },

  receiveMessage(msg) {
    let data = msg.data;
    let callback;
    switch (msg.name) {
      case "ContentPrefs:HandleResult":
        callback = this._requests.get(data.requestId);
        callback.handleResult(data.contentPref);
        break;

      case "ContentPrefs:HandleError":
        callback = this._requests.get(data.requestId);
        callback.handleError(data.error);
        break;

      case "ContentPrefs:HandleCompletion":
        callback = this._requests.get(data.requestId);
        this._requests.delete(data.requestId);
        callback.handleCompletion(data.reason);
        break;

      case "ContentPrefs:NotifyObservers": {
        let observerList = this._observers.get(data.name);
        if (!observerList)
          break;

        for (let observer of observerList) {
          safeCallback(observer, data.callback, data.args);
        }

        break;
      }
    }
  },

  _callFunction(call, args, callback) {
    let requestId = this._getRandomId();
    let data = { call, args, requestId };

    this._mm.sendAsyncMessage("ContentPrefs:FunctionCall", data);

    this._requests.set(requestId, new CallbackCaller(callback));
  },

  getCachedByDomainAndName: NYI,
  getCachedBySubdomainAndName: NYI,
  getCachedGlobal: NYI,

  addObserverForName(name, observer) {
    let set = this._observers.get(name);
    if (!set) {
      set = new Set();
      if (this._observers.size === 0) {
        // This is the first observer of any kind. Start listening for changes.
        this._mm.addMessageListener("ContentPrefs:NotifyObservers", this);
      }

      // This is the first observer for this name. Start listening for changes
      // to it.
      this._mm.sendAsyncMessage("ContentPrefs:AddObserverForName", { name });
      this._observers.set(name, set);
    }

    set.add(observer);
  },

  removeObserverForName(name, observer) {
    let set = this._observers.get(name);
    if (!set)
      return;

    set.delete(observer);
    if (set.size === 0) {
      // This was the last observer for this name. Stop listening for changes.
      this._mm.sendAsyncMessage("ContentPrefs:RemoveObserverForName", { name });

      this._observers.delete(name);
      if (this._observers.size === 0) {
        // This was the last observer for this process. Stop listing for all
        // changes.
        this._mm.removeMessageListener("ContentPrefs:NotifyObservers", this);
      }
    }
  },

  extractDomain: NYI
};

function forwardMethodToParent(method, signature, ...args) {
  // Ignore superfluous arguments
  args = args.slice(0, signature.length);

  // Process context argument for forwarding
  let contextIndex = signature.indexOf("context");
  if (contextIndex > -1) {
    args[contextIndex] = contextArg(args[contextIndex]);
  }
  // Take out the callback argument, if present.
  let callbackIndex = signature.indexOf("callback");
  let callback = null;
  if (callbackIndex > -1 && args.length > callbackIndex) {
    callback = args.splice(callbackIndex, 1)[0];
  }
  this._callFunction(method, args, callback);
}

for (let [method, signature] of _methodsCallableFromChild) {
  ContentPrefServiceChild[method] = forwardMethodToParent.bind(ContentPrefServiceChild, method, signature);
}

ContentPrefServiceChild.init();
PK
!< $modules/ContentPrefServiceParent.jsm/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "ContentPrefServiceParent" ];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "_methodsCallableFromChild",
                                  "resource://gre/modules/ContentPrefUtils.jsm");

let loadContext = Cc["@mozilla.org/loadcontext;1"].
                    createInstance(Ci.nsILoadContext);
let privateLoadContext = Cc["@mozilla.org/privateloadcontext;1"].
                           createInstance(Ci.nsILoadContext);

function contextArg(context) {
  return (context && context.usePrivateBrowsing) ?
           privateLoadContext :
           loadContext;
}

var ContentPrefServiceParent = {
  // Called on all platforms.
  alwaysInit() {
    let globalMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                     .getService(Ci.nsIMessageListenerManager);

    globalMM.addMessageListener("child-process-shutdown", this);
  },

  // Only called on Android. Listeners are added in nsBrowserGlue.js on other
  // platforms.
  init() {
    let globalMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                     .getService(Ci.nsIMessageListenerManager);

    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
    globalMM.addMessageListener("ContentPrefs:FunctionCall", this);
    globalMM.addMessageListener("ContentPrefs:AddObserverForName", this);
    globalMM.addMessageListener("ContentPrefs:RemoveObserverForName", this);
    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue

    this.alwaysInit();
  },

  // Map from message manager -> content pref observer.
  _observers: new Map(),

  handleObserverChange(msg) {
    let observer = this._observers.get(msg.target);
    if (msg.name === "child-process-shutdown") {
      // If we didn't have any observers for this child process, don't do
      // anything.
      if (!observer)
        return;

      for (let i of observer._names) {
        this._cps2.removeObserverForName(i, observer);
      }

      this._observers.delete(msg.target);
      return;
    }

    let prefName = msg.data.name;
    if (msg.name === "ContentPrefs:AddObserverForName") {
      // The child process is responsible for not adding multiple parent
      // observers for the same name.
      if (!observer) {
        observer = {
          onContentPrefSet(group, name, value, isPrivate) {
            msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
                                        { name, callback: "onContentPrefSet",
                                          args: [ group, name, value, isPrivate ] });
          },

          onContentPrefRemoved(group, name, isPrivate) {
            msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
                                        { name, callback: "onContentPrefRemoved",
                                          args: [ group, name, isPrivate ] });
          },

          // The names we're using this observer object for, used to keep track
          // of the number of names we care about as well as for removing this
          // observer if its associated process goes away.
          _names: new Set()
        };

        this._observers.set(msg.target, observer);
      }

      observer._names.add(prefName);

      this._cps2.addObserverForName(prefName, observer);
    } else {
      // RemoveObserverForName

      // We must have an observer.
      this._cps2.removeObserverForName(prefName, observer);

      observer._names.delete(prefName);
      if (observer._names.size === 0) {
        // This was the last use for this observer.
        this._observers.delete(msg.target);
      }
    }
  },

  // Listeners are added in nsBrowserGlue.js
  receiveMessage(msg) {
    if (msg.name != "ContentPrefs:FunctionCall") {
      this.handleObserverChange(msg);
      return;
    }

    let data = msg.data;
    let signature;

    if (!_methodsCallableFromChild.some(([method, args]) => {
       if (method == data.call) {
         signature = args;
         return true;
       }
       return false;
     })) {
      throw new Error(`Can't call ${data.call} from child!`);
    }

    let args = data.args;
    let requestId = data.requestId;

    let listener = {
      handleResult(pref) {
        msg.target.sendAsyncMessage("ContentPrefs:HandleResult",
                                    { requestId,
                                      contentPref: {
                                        domain: pref.domain,
                                        name: pref.name,
                                        value: pref.value
                                      }
                                    });
      },

      handleError(error) {
        msg.target.sendAsyncMessage("ContentPrefs:HandleError",
                                    { requestId,
                                      error });
      },
      handleCompletion(reason) {
        msg.target.sendAsyncMessage("ContentPrefs:HandleCompletion",
                                    { requestId,
                                      reason });
      }
    };

    // Push our special listener.
    args.push(listener);

    // Process context argument for forwarding
    let contextIndex = signature.indexOf("context");
    if (contextIndex > -1) {
      args[contextIndex] = contextArg(args[contextIndex]);
    }

    // And call the function.
    this._cps2[data.call](...args);
  }
};

XPCOMUtils.defineLazyServiceGetter(ContentPrefServiceParent, "_cps2",
                                   "@mozilla.org/content-pref/service;1",
                                   "nsIContentPrefService2");
PK
!<ByvVmodules/ContentPrefStore.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var EXPORTED_SYMBOLS = [
  "ContentPrefStore",
];

function ContentPrefStore() {
  this._groups = new Map();
  this._globalNames = new Map();
}

ContentPrefStore.prototype = {

  set: function CPS_set(group, name, val) {
    if (group) {
      if (!this._groups.has(group))
        this._groups.set(group, new Map());
      this._groups.get(group).set(name, val);
    } else {
      this._globalNames.set(name, val);
    }
  },

  setWithCast: function CPS_setWithCast(group, name, val) {
    if (typeof(val) == "boolean")
      val = val ? 1 : 0;
    else if (val === undefined)
      val = null;
    this.set(group, name, val);
  },

  has: function CPS_has(group, name) {
    if (group) {
      return this._groups.has(group) &&
             this._groups.get(group).has(name);
    }
    return this._globalNames.has(name);
  },

  get: function CPS_get(group, name) {
    if (group && this._groups.has(group))
      return this._groups.get(group).get(name);
    return this._globalNames.get(name);
  },

  remove: function CPS_remove(group, name) {
    if (group) {
      if (this._groups.has(group)) {
        this._groups.get(group).delete(name);
        if (this._groups.get(group).size == 0)
          this._groups.delete(group);
      }
    } else {
      this._globalNames.delete(name);
    }
  },

  removeGroup: function CPS_removeGroup(group) {
    if (group) {
      this._groups.delete(group);
    } else {
      this._globalNames.clear();
    }
  },

  removeAllGroups: function CPS_removeAllGroups() {
    this._groups.clear();
  },

  removeAll: function CPS_removeAll() {
    this.removeAllGroups();
    this._globalNames.clear();
  },

  groupsMatchIncludingSubdomains: function CPS_groupsMatchIncludingSubdomains(group, group2) {
    let idx = group2.indexOf(group);
    return (idx == group2.length - group.length &&
         (idx == 0 || group2[idx - 1] == "."));
  },

  * [Symbol.iterator]() {
    for (let [group, names] of this._groups) {
      for (let [name, val] of names) {
        yield [group, name, val];
      }
    }
    for (let [name, val] of this._globalNames) {
      yield [null, name, val];
    }
  },

  * match(group, name, includeSubdomains) {
    for (let sgroup of this.matchGroups(group, includeSubdomains)) {
      if (this.has(sgroup, name))
        yield [sgroup, this.get(sgroup, name)];
    }
  },

  * matchGroups(group, includeSubdomains) {
    if (group) {
      if (includeSubdomains) {
        for (let [sgroup, , ] of this) {
          if (sgroup) {
            if (this.groupsMatchIncludingSubdomains(group, sgroup)) {
              yield sgroup;
            }
          }
        }
      } else if (this._groups.has(group)) {
        yield group;
      }
    } else if (this._globalNames.size) {
      yield null;
    }
  },
};
PK
!<Smodules/ContentPrefUtils.jsm/* 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/. */

"use strict";

this.EXPORTED_SYMBOLS = [
  "ContentPref",
  "cbHandleResult",
  "cbHandleError",
  "cbHandleCompletion",
  "safeCallback",
  "_methodsCallableFromChild",
];

const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

function ContentPref(domain, name, value) {
  this.domain = domain;
  this.name = name;
  this.value = value;
}

ContentPref.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
};

function cbHandleResult(callback, pref) {
  safeCallback(callback, "handleResult", [pref]);
}

function cbHandleCompletion(callback, reason) {
  safeCallback(callback, "handleCompletion", [reason]);
}

function cbHandleError(callback, nsresult) {
  safeCallback(callback, "handleError", [nsresult]);
}

function safeCallback(callbackObj, methodName, args) {
  if (!callbackObj || typeof(callbackObj[methodName]) != "function")
    return;
  try {
    callbackObj[methodName].apply(callbackObj, args);
  } catch (err) {
    Cu.reportError(err);
  }
}

const _methodsCallableFromChild = Object.freeze([
  ["getByName", ["name", "context", "callback"]],
  ["getByDomainAndName", ["domain", "name", "context", "callback"]],
  ["getBySubdomainAndName", ["domain", "name", "context", "callback"]],
  ["getGlobal", ["name", "context", "callback"]],
  ["set", ["domain", "name", "value", "context", "callback"]],
  ["setGlobal", ["name", "value", "context", "callback"]],
  ["removeByDomainAndName", ["domain", "name", "context", "callback"]],
  ["removeBySubdomainAndName", ["domain", "name", "context", "callback"]],
  ["removeGlobal", ["name", "context", "callback"]],
  ["removeByDomain", ["domain", "context", "callback"]],
  ["removeBySubdomain", ["domain", "context", "callback"]],
  ["removeByName", ["name", "context", "callback"]],
  ["removeAllDomains", ["context", "callback"]],
  ["removeAllGlobals", ["context", "callback"]],
]);
PK
!<%{C
-
-%modules/ContextualIdentityService.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["ContextualIdentityService"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const DEFAULT_TAB_COLOR = "#909090";
const SAVE_DELAY_MS = 1500;

XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
  return Services.strings.createBundle("chrome://browser/locale/browser.properties");
});

XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
  return new TextDecoder();
});

XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function() {
  return new TextEncoder();
});

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");

function _TabRemovalObserver(resolver, tabParentIds) {
  this._resolver = resolver;
  this._tabParentIds = tabParentIds;
  Services.obs.addObserver(this, "ipc:browser-destroyed");
}

_TabRemovalObserver.prototype = {
  _resolver: null,
  _tabParentIds: null,

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),

  observe(subject, topic, data) {
    let tabParent = subject.QueryInterface(Ci.nsITabParent);
    if (this._tabParentIds.has(tabParent.tabId)) {
      this._tabParentIds.delete(tabParent.tabId);
      if (this._tabParentIds.size == 0) {
        Services.obs.removeObserver(this, "ipc:browser-destroyed");
        this._resolver();
      }
    }
  }
};

function _ContextualIdentityService(path) {
  this.init(path);
}

_ContextualIdentityService.prototype = {
  _defaultIdentities: [
    { userContextId: 1,
      public: true,
      icon: "fingerprint",
      color: "blue",
      l10nID: "userContextPersonal.label",
      accessKey: "userContextPersonal.accesskey",
      telemetryId: 1,
    },
    { userContextId: 2,
      public: true,
      icon: "briefcase",
      color: "orange",
      l10nID: "userContextWork.label",
      accessKey: "userContextWork.accesskey",
      telemetryId: 2,
    },
    { userContextId: 3,
      public: true,
      icon: "dollar",
      color: "green",
      l10nID: "userContextBanking.label",
      accessKey: "userContextBanking.accesskey",
      telemetryId: 3,
    },
    { userContextId: 4,
      public: true,
      icon: "cart",
      color: "pink",
      l10nID: "userContextShopping.label",
      accessKey: "userContextShopping.accesskey",
      telemetryId: 4,
    },
    { userContextId: 5,
      public: false,
      icon: "",
      color: "",
      name: "userContextIdInternal.thumbnail",
      accessKey: "" },
  ],

  _identities: null,
  _openedIdentities: new Set(),
  _lastUserContextId: 0,

  _path: null,
  _dataReady: false,

  _saver: null,

  init(path) {
    this._path = path;
  },

  load() {
    OS.File.read(this._path).then(bytes => {
      // If synchronous loading happened in the meantime, exit now.
      if (this._dataReady) {
        return;
      }

      try {
        let data = JSON.parse(gTextDecoder.decode(bytes));
        if (data.version == 1) {
          this.resetDefault();
        }
        if (data.version != 2) {
          dump("ERROR - ContextualIdentityService - Unknown version found in " + this._path + "\n");
          this.loadError(null);
          return;
        }

        this._identities = data.identities;
        this._lastUserContextId = data.lastUserContextId;

        this._dataReady = true;
      } catch (error) {
        this.loadError(error);
      }
    }, (error) => {
      this.loadError(error);
    });
  },

  resetDefault() {
    this._identities = this._defaultIdentities;
    this._lastUserContextId = this._defaultIdentities.length;

    this._dataReady = true;

    this.saveSoon();
  },

  loadError(error) {
    if (error != null &&
        !(error instanceof OS.File.Error && error.becauseNoSuchFile) &&
        !(error instanceof Components.Exception &&
          error.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
      // Let's report the error.
      Cu.reportError(error);
    }

    // If synchronous loading happened in the meantime, exit now.
    if (this._dataReady) {
      return;
    }

    this.resetDefault();
  },

  saveSoon() {
    if (!this._saver) {
      this._saverCallback = () => this._saver.finalize();

      this._saver = new DeferredTask(() => this.save(), SAVE_DELAY_MS);
      AsyncShutdown.profileBeforeChange.addBlocker(
        "ContextualIdentityService: writing data", this._saverCallback);
    } else {
      this._saver.disarm();
    }

    this._saver.arm();
  },

  save() {
    AsyncShutdown.profileBeforeChange.removeBlocker(this._saverCallback);

    this._saver = null;
    this._saverCallback = null;

    let object = {
      version: 2,
      lastUserContextId: this._lastUserContextId,
      identities: this._identities
    };

    let bytes = gTextEncoder.encode(JSON.stringify(object));
    return OS.File.writeAtomic(this._path, bytes,
                               { tmpPath: this._path + ".tmp" });
  },

  create(name, icon, color) {
    this.ensureDataReady();

    let identity = {
      userContextId: ++this._lastUserContextId,
      public: true,
      icon,
      color,
      name
    };

    this._identities.push(identity);
    this.saveSoon();

    return Cu.cloneInto(identity, {});
  },

  update(userContextId, name, icon, color) {
    this.ensureDataReady();

    let identity = this._identities.find(identity => identity.userContextId == userContextId &&
                                         identity.public);
    if (identity && name) {
      identity.name = name;
      identity.color = color;
      identity.icon = icon;
      delete identity.l10nID;
      delete identity.accessKey;

      Services.obs.notifyObservers(null, "contextual-identity-updated", userContextId);
      this.saveSoon();
    }

    return !!identity;
  },

  remove(userContextId) {
    this.ensureDataReady();

    let index = this._identities.findIndex(i => i.userContextId == userContextId && i.public);
    if (index == -1) {
      return false;
    }

    Services.obs.notifyObservers(null, "clear-origin-attributes-data",
                                 JSON.stringify({ userContextId }));

    this._identities.splice(index, 1);
    this._openedIdentities.delete(userContextId);
    this.saveSoon();

    return true;
  },

  ensureDataReady() {
    if (this._dataReady) {
      return;
    }

    try {
      // This reads the file and automatically detects the UTF-8 encoding.
      let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                          .createInstance(Ci.nsIFileInputStream);
      inputStream.init(new FileUtils.File(this._path),
                       FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
      try {
        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
        let data = json.decodeFromStream(inputStream,
                                         inputStream.available());
        this._identities = data.identities;
        this._lastUserContextId = data.lastUserContextId;

        this._dataReady = true;
      } finally {
        inputStream.close();
      }
    } catch (error) {
      this.loadError(error);
    }
  },

  getPublicIdentities() {
    this.ensureDataReady();
    return Cu.cloneInto(this._identities.filter(info => info.public), {});
  },

  getPrivateIdentity(name) {
    this.ensureDataReady();
    return Cu.cloneInto(this._identities.find(info => !info.public && info.name == name), {});
  },

  getPublicIdentityFromId(userContextId) {
    this.ensureDataReady();
    return Cu.cloneInto(this._identities.find(info => info.userContextId == userContextId &&
                                              info.public), {});
  },

  getUserContextLabel(userContextId) {
    let identity = this.getPublicIdentityFromId(userContextId);
    if (!identity) {
      return "";
    }

    // We cannot localize the user-created identity names.
    if (identity.name) {
      return identity.name;
    }

    return gBrowserBundle.GetStringFromName(identity.l10nID);
  },

  setTabStyle(tab) {
    if (!tab.hasAttribute("usercontextid")) {
      return;
    }

    let userContextId = tab.getAttribute("usercontextid");
    let identity = this.getPublicIdentityFromId(userContextId);
    tab.setAttribute("data-identity-color", identity ? identity.color : "");
  },

  countContainerTabs(userContextId = 0) {
    let count = 0;
    this._forEachContainerTab(function() {
      ++count;
    }, userContextId);
    return count;
  },

  closeContainerTabs(userContextId = 0) {
    return new Promise(resolve => {
      let tabParentIds = new Set();
      this._forEachContainerTab((tab, tabbrowser) => {
        let frameLoader = tab.linkedBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;

        // We don't have tabParent in non-e10s mode.
        if (frameLoader.tabParent) {
          tabParentIds.add(frameLoader.tabParent.tabId);
        }

        tabbrowser.removeTab(tab);
      }, userContextId);

      if (tabParentIds.size == 0) {
        resolve();
        return;
      }

      new _TabRemovalObserver(resolve, tabParentIds);
    });
  },

  notifyAllContainersCleared() {
    for (let identity of this._identities) {
      Services.obs.notifyObservers(null, "clear-origin-attributes-data",
                                   JSON.stringify({ userContextId: identity.userContextId }));
    }
  },

  _forEachContainerTab(callback, userContextId = 0) {
    let windowList = Services.wm.getEnumerator("navigator:browser");
    while (windowList.hasMoreElements()) {
      let win = windowList.getNext();

      if (win.closed || !win.gBrowser) {
        continue;
      }

      let tabbrowser = win.gBrowser;
      for (let i = tabbrowser.tabContainer.childNodes.length - 1; i >= 0; --i) {
        let tab = tabbrowser.tabContainer.childNodes[i];
        if (tab.hasAttribute("usercontextid") &&
                  (!userContextId ||
                   parseInt(tab.getAttribute("usercontextid"), 10) == userContextId)) {
          callback(tab, tabbrowser);
        }
      }
    }
  },

  telemetry(userContextId) {
    let identity = this.getPublicIdentityFromId(userContextId);

    // Let's ignore unknown identities for now.
    if (!identity) {
      return;
    }

    if (!this._openedIdentities.has(userContextId)) {
      this._openedIdentities.add(userContextId);
      Services.telemetry.getHistogramById("UNIQUE_CONTAINERS_OPENED").add(1);
    }

    Services.telemetry.getHistogramById("TOTAL_CONTAINERS_OPENED").add(1);

    if (identity.telemetryId) {
      Services.telemetry.getHistogramById("CONTAINER_USED")
                        .add(identity.telemetryId);
    }
  },

  createNewInstanceForTesting(path) {
    return new _ContextualIdentityService(path);
  },
};

let path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
this.ContextualIdentityService = new _ContextualIdentityService(path);
PK
!<p	~modules/CrashManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const myScope = this;

Cu.import("resource://gre/modules/KeyValueParser.jsm");
Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm");
Cu.import("resource://gre/modules/Timer.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

this.EXPORTED_SYMBOLS = [
  "CrashManager",
];

/**
 * How long to wait after application startup before crash event files are
 * automatically aggregated.
 *
 * We defer aggregation for performance reasons, as we don't want too many
 * services competing for I/O immediately after startup.
 */
const AGGREGATE_STARTUP_DELAY_MS = 57000;

const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;

// Converts Date to days since UNIX epoch.
// This was copied from /services/metrics.storage.jsm. The implementation
// does not account for leap seconds.
function dateToDays(date) {
  return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
}

/**
 * Get a field from the specified object and remove it.
 *
 * @param obj {Object} The object holding the field
 * @param field {String} The name of the field to be parsed and removed
 *
 * @returns {String} the field contents as a string, null if none was found
 */
function getAndRemoveField(obj, field) {
  let value = null;

  if (field in obj) {
    // We split extra files on LF characters but Windows-generated ones might
    // contain trailing CR characters so trim them here.
    value = obj[field].trim();

    delete obj[field];
  }

  return value;
}

/**
 * Parse the string stored in the specified field as JSON and then remove the
 * field from the object.
 *
 * @param obj {Object} The object holding the field
 * @param field {String} The name of the field to be parsed and removed
 *
 * @returns {Object} the parsed object, null if none was found
 */
function parseAndRemoveField(obj, field) {
  let value = null;

  if (field in obj) {
    try {
      value = JSON.parse(obj[field]);
    } catch (e) {
      Cu.reportError(e);
    }

    delete obj[field];
  }

  return value;
}

/**
 * A gateway to crash-related data.
 *
 * This type is generic and can be instantiated any number of times.
 * However, most applications will typically only have one instance
 * instantiated and that instance will point to profile and user appdata
 * directories.
 *
 * Instances are created by passing an object with properties.
 * Recognized properties are:
 *
 *   pendingDumpsDir (string) (required)
 *     Where dump files that haven't been uploaded are located.
 *
 *   submittedDumpsDir (string) (required)
 *     Where records of uploaded dumps are located.
 *
 *   eventsDirs (array)
 *     Directories (defined as strings) where events files are written. This
 *     instance will collects events from files in the directories specified.
 *
 *   storeDir (string)
 *     Directory we will use for our data store. This instance will write
 *     data files into the directory specified.
 *
 *   telemetryStoreSizeKey (string)
 *     Telemetry histogram to report store size under.
 */
this.CrashManager = function(options) {
  for (let k of ["pendingDumpsDir", "submittedDumpsDir", "eventsDirs",
    "storeDir"]) {
    if (!(k in options)) {
      throw new Error("Required key not present in options: " + k);
    }
  }

  this._log = Log.repository.getLogger("Crashes.CrashManager");

  for (let k in options) {
    let v = options[k];

    switch (k) {
      case "pendingDumpsDir":
        this._pendingDumpsDir = v;
        break;

      case "submittedDumpsDir":
        this._submittedDumpsDir = v;
        break;

      case "eventsDirs":
        this._eventsDirs = v;
        break;

      case "storeDir":
        this._storeDir = v;
        break;

      case "telemetryStoreSizeKey":
        this._telemetryStoreSizeKey = v;
        break;

      default:
        throw new Error("Unknown property in options: " + k);
    }
  }

  // Promise for in-progress aggregation operation. We store it on the
  // object so it can be returned for in-progress operations.
  this._aggregatePromise = null;

  // Map of crash ID / promise tuples used to track adding new crashes.
  this._crashPromises = new Map();

  // Promise for the crash ping used only for testing.
  this._pingPromise = null;

  // The CrashStore currently attached to this object.
  this._store = null;

  // A Task to retrieve the store. This is needed to avoid races when
  // _getStore() is called multiple times in a short interval.
  this._getStoreTask = null;

  // The timer controlling the expiration of the CrashStore instance.
  this._storeTimer = null;

  // This is a semaphore that prevents the store from being freed by our
  // timer-based resource freeing mechanism.
  this._storeProtectedCount = 0;
};

this.CrashManager.prototype = Object.freeze({
  // A crash in the main process.
  PROCESS_TYPE_MAIN: "main",

  // A crash in a content process.
  PROCESS_TYPE_CONTENT: "content",

  // A crash in a plugin process.
  PROCESS_TYPE_PLUGIN: "plugin",

  // A crash in a Gecko media plugin process.
  PROCESS_TYPE_GMPLUGIN: "gmplugin",

  // A crash in the GPU process.
  PROCESS_TYPE_GPU: "gpu",

  // A real crash.
  CRASH_TYPE_CRASH: "crash",

  // A hang.
  CRASH_TYPE_HANG: "hang",

  // Submission result values.
  SUBMISSION_RESULT_OK: "ok",
  SUBMISSION_RESULT_FAILED: "failed",

  DUMP_REGEX: /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.dmp$/i,
  SUBMITTED_REGEX: /^bp-(?:hr-)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.txt$/i,
  ALL_REGEX: /^(.*)$/,

  // How long the store object should persist in memory before being
  // automatically garbage collected.
  STORE_EXPIRATION_MS: 60 * 1000,

  // Number of days after which a crash with no activity will get purged.
  PURGE_OLDER_THAN_DAYS: 180,

  // The following are return codes for individual event file processing.
  // File processed OK.
  EVENT_FILE_SUCCESS: "ok",
  // The event appears to be malformed.
  EVENT_FILE_ERROR_MALFORMED: "malformed",
  // The type of event is unknown.
  EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",

  // A whitelist of crash annotations which do not contain sensitive data
  // and are saved in the crash record and sent with Firefox Health Report.
  ANNOTATION_WHITELIST: [
    "AsyncShutdownTimeout",
    "BuildID",
    "ProductID",
    "ProductName",
    "ReleaseChannel",
    "RemoteType",
    "SecondsSinceLastCrash",
    "ShutdownProgress",
    "StartupCrash",
    "TelemetryEnvironment",
    "Version",
    // The following entries are not normal annotations that can be found in
    // the .extra file but are included in the crash record/FHR:
    "AvailablePageFile",
    "AvailablePhysicalMemory",
    "AvailableVirtualMemory",
    "BlockedDllList",
    "BlocklistInitFailed",
    "ContainsMemoryReport",
    "CrashTime",
    "EventLoopNestingLevel",
    "IsGarbageCollecting",
    "MozCrashReason",
    "OOMAllocationSize",
    "SystemMemoryUsePercentage",
    "TextureUsage",
    "TotalPageFile",
    "TotalPhysicalMemory",
    "TotalVirtualMemory",
    "UptimeTS",
    "User32BeforeBlocklist",
  ],

  /**
   * Obtain a list of all dumps pending upload.
   *
   * The returned value is a promise that resolves to an array of objects
   * on success. Each element in the array has the following properties:
   *
   *   id (string)
   *      The ID of the crash (a UUID).
   *
   *   path (string)
   *      The filename of the crash (<UUID.dmp>)
   *
   *   date (Date)
   *      When this dump was created
   *
   * The returned arry is sorted by the modified time of the file backing
   * the entry, oldest to newest.
   *
   * @return Promise<Array>
   */
  pendingDumps() {
    return this._getDirectoryEntries(this._pendingDumpsDir, this.DUMP_REGEX);
  },

  /**
   * Obtain a list of all dump files corresponding to submitted crashes.
   *
   * The returned value is a promise that resolves to an Array of
   * objects. Each object has the following properties:
   *
   *   path (string)
   *     The path of the file this entry comes from.
   *
   *   id (string)
   *     The crash UUID.
   *
   *   date (Date)
   *     The (estimated) date this crash was submitted.
   *
   * The returned array is sorted by the modified time of the file backing
   * the entry, oldest to newest.
   *
   * @return Promise<Array>
   */
  submittedDumps() {
    return this._getDirectoryEntries(this._submittedDumpsDir,
                                     this.SUBMITTED_REGEX);
  },

  /**
   * Aggregates "loose" events files into the unified "database."
   *
   * This function should be called periodically to collect metadata from
   * all events files into the central data store maintained by this manager.
   *
   * Once events have been stored in the backing store the corresponding
   * source files are deleted.
   *
   * Only one aggregation operation is allowed to occur at a time. If this
   * is called when an existing aggregation is in progress, the promise for
   * the original call will be returned.
   *
   * @return promise<int> The number of event files that were examined.
   */
  aggregateEventsFiles() {
    if (this._aggregatePromise) {
      return this._aggregatePromise;
    }

    return this._aggregatePromise = (async () => {
      if (this._aggregatePromise) {
        return this._aggregatePromise;
      }

      try {
        let unprocessedFiles = await this._getUnprocessedEventsFiles();

        let deletePaths = [];
        let needsSave = false;

        this._storeProtectedCount++;
        for (let entry of unprocessedFiles) {
          try {
            let result = await this._processEventFile(entry);

            switch (result) {
              case this.EVENT_FILE_SUCCESS:
                needsSave = true;
                // Fall through.

              case this.EVENT_FILE_ERROR_MALFORMED:
                deletePaths.push(entry.path);
                break;

              case this.EVENT_FILE_ERROR_UNKNOWN_EVENT:
                break;

              default:
                Cu.reportError("Unhandled crash event file return code. Please " +
                               "file a bug: " + result);
            }
          } catch (ex) {
            if (ex instanceof OS.File.Error) {
              this._log.warn("I/O error reading " + entry.path, ex);
            } else {
              // We should never encounter an exception. This likely represents
              // a coding error because all errors should be detected and
              // converted to return codes.
              //
              // If we get here, report the error and delete the source file
              // so we don't see it again.
              Cu.reportError("Exception when processing crash event file: " +
                             Log.exceptionStr(ex));
              deletePaths.push(entry.path);
            }
          }
        }

        if (needsSave) {
          let store = await this._getStore();
          await store.save();
        }

        for (let path of deletePaths) {
          try {
            await OS.File.remove(path);
          } catch (ex) {
            this._log.warn("Error removing event file (" + path + ")", ex);
          }
        }

        return unprocessedFiles.length;

      } finally {
        this._aggregatePromise = false;
        this._storeProtectedCount--;
      }
    })();
  },

  /**
   * Prune old crash data.
   *
   * @param date
   *        (Date) The cutoff point for pruning. Crashes without data newer
   *        than this will be pruned.
   */
  pruneOldCrashes(date) {
    return (async () => {
      let store = await this._getStore();
      store.pruneOldCrashes(date);
      await store.save();
    })();
  },

  /**
   * Run tasks that should be periodically performed.
   */
  runMaintenanceTasks() {
    return (async () => {
      await this.aggregateEventsFiles();

      let offset = this.PURGE_OLDER_THAN_DAYS * MILLISECONDS_IN_DAY;
      await this.pruneOldCrashes(new Date(Date.now() - offset));
    })();
  },

  /**
   * Schedule maintenance tasks for some point in the future.
   *
   * @param delay
   *        (integer) Delay in milliseconds when maintenance should occur.
   */
  scheduleMaintenance(delay) {
    let deferred = PromiseUtils.defer();

    setTimeout(() => {
      this.runMaintenanceTasks().then(deferred.resolve, deferred.reject);
    }, delay);

    return deferred.promise;
  },

  /**
   * Record the occurrence of a crash.
   *
   * This method skips event files altogether and writes directly and
   * immediately to the manager's data store.
   *
   * @param processType (string) One of the PROCESS_TYPE constants.
   * @param crashType (string) One of the CRASH_TYPE constants.
   * @param id (string) Crash ID. Likely a UUID.
   * @param date (Date) When the crash occurred.
   * @param metadata (dictionary) Crash metadata, may be empty.
   *
   * @return promise<null> Resolved when the store has been saved.
   */
  addCrash(processType, crashType, id, date, metadata) {
    let promise = (async () => {
      let store = await this._getStore();
      if (store.addCrash(processType, crashType, id, date, metadata)) {
        await store.save();
      }

      let deferred = this._crashPromises.get(id);

      if (deferred) {
        this._crashPromises.delete(id);
        deferred.resolve();
      }

      // Send a telemetry ping for each non-main process crash
      if (processType === this.PROCESS_TYPE_CONTENT ||
          processType === this.PROCESS_TYPE_GPU) {
        this._sendCrashPing(id, processType, date, metadata);
      }
    })();

    return promise;
  },

  /**
   * Returns a promise that is resolved only the crash with the specified id
   * has been fully recorded.
   *
   * @param id (string) Crash ID. Likely a UUID.
   *
   * @return promise<null> Resolved when the crash is present.
   */
  async ensureCrashIsPresent(id) {
    let store = await this._getStore();
    let crash = store.getCrash(id);

    if (crash) {
      return Promise.resolve();
    }

    let deferred = PromiseUtils.defer();

    this._crashPromises.set(id, deferred);
    return deferred.promise;
  },

  /**
   * Record the remote ID for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param remoteID (Date) Server/Breakpad ID.
   *
   * @return boolean True if the remote ID was recorded.
   */
  async setRemoteCrashID(crashID, remoteID) {
    let store = await this._getStore();
    if (store.setRemoteCrashID(crashID, remoteID)) {
      await store.save();
    }
  },

  /**
   * Generate a submission ID for use with addSubmission{Attempt,Result}.
   */
  generateSubmissionID() {
    return "sub-" + Cc["@mozilla.org/uuid-generator;1"]
                      .getService(Ci.nsIUUIDGenerator)
                      .generateUUID().toString().slice(1, -1);
  },

  /**
   * Record the occurrence of a submission attempt for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param submissionID (string) Submission ID. Likely a UUID.
   * @param date (Date) When the attempt occurred.
   *
   * @return boolean True if the attempt was recorded and false if not.
   */
  async addSubmissionAttempt(crashID, submissionID, date) {
    let store = await this._getStore();
    if (store.addSubmissionAttempt(crashID, submissionID, date)) {
      await store.save();
    }
  },

  /**
   * Record the occurrence of a submission result for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param submissionID (string) Submission ID. Likely a UUID.
   * @param date (Date) When the submission result was obtained.
   * @param result (string) One of the SUBMISSION_RESULT constants.
   *
   * @return boolean True if the result was recorded and false if not.
   */
  async addSubmissionResult(crashID, submissionID, date, result) {
    let store = await this._getStore();
    if (store.addSubmissionResult(crashID, submissionID, date, result)) {
      await store.save();
    }
  },

  /**
   * Set the classification of a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param classifications (array) Crash classifications.
   *
   * @return boolean True if the data was recorded and false if not.
   */
  async setCrashClassifications(crashID, classifications) {
    let store = await this._getStore();
    if (store.setCrashClassifications(crashID, classifications)) {
      await store.save();
    }
  },

  /**
   * Obtain the paths of all unprocessed events files.
   *
   * The promise-resolved array is sorted by file mtime, oldest to newest.
   */
  _getUnprocessedEventsFiles() {
    return (async () => {
      let entries = [];

      for (let dir of this._eventsDirs) {
        for (let e of await this._getDirectoryEntries(dir, this.ALL_REGEX)) {
          entries.push(e);
        }
      }

      entries.sort((a, b) => { return a.date - b.date; });

      return entries;
    })();
  },

  // See docs/crash-events.rst for the file format specification.
  _processEventFile(entry) {
    return (async () => {
      let data = await OS.File.read(entry.path);
      let store = await this._getStore();

      let decoder = new TextDecoder();
      data = decoder.decode(data);

      let type, time;
      let start = 0;
      for (let i = 0; i < 2; i++) {
        let index = data.indexOf("\n", start);
        if (index == -1) {
          return this.EVENT_FILE_ERROR_MALFORMED;
        }

        let sub = data.substring(start, index);
        switch (i) {
          case 0:
            type = sub;
            break;
          case 1:
            time = sub;
            try {
              time = parseInt(time, 10);
            } catch (ex) {
              return this.EVENT_FILE_ERROR_MALFORMED;
            }
        }

        start = index + 1;
      }
      let date = new Date(time * 1000);
      let payload = data.substring(start);

      return this._handleEventFilePayload(store, entry, type, date, payload);
    })();
  },

  _filterAnnotations(annotations) {
    let filteredAnnotations = {};

    for (let line in annotations) {
      if (this.ANNOTATION_WHITELIST.includes(line)) {
        filteredAnnotations[line] = annotations[line];
      }
    }

    return filteredAnnotations;
  },

  _sendCrashPing(crashId, type, date, metadata = {}) {
    // If we have a saved environment, use it. Otherwise report
    // the current environment.
    let reportMeta = Cu.cloneInto(metadata, myScope);
    let crashEnvironment = parseAndRemoveField(reportMeta,
                                               "TelemetryEnvironment");
    let sessionId = getAndRemoveField(reportMeta, "TelemetrySessionId");
    let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
    let minidumpSha256Hash = getAndRemoveField(reportMeta,
                                               "MinidumpSha256Hash");

    // Filter the remaining annotations to remove privacy-sensitive ones
    reportMeta = this._filterAnnotations(reportMeta);

    this._pingPromise = TelemetryController.submitExternalPing("crash",
      {
        version: 1,
        crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
        crashTime: date.toISOString().slice(0, 13) + ":00:00.000Z", // per-hour resolution
        sessionId,
        crashId,
        minidumpSha256Hash,
        processType: type,
        stackTraces,
        metadata: reportMeta,
        hasCrashEnvironment: (crashEnvironment !== null),
      },
      {
        addClientId: true,
        addEnvironment: true,
        overrideEnvironment: crashEnvironment,
      }
    );
  },

  _handleEventFilePayload(store, entry, type, date, payload) {
      // The payload types and formats are documented in docs/crash-events.rst.
      // Do not change the format of an existing type. Instead, invent a new
      // type.
      // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
      let lines = payload.split("\n");

      switch (type) {
        case "crash.main.1":
          if (lines.length > 1) {
            this._log.warn("Multiple lines unexpected in payload for " +
                           entry.path);
            return this.EVENT_FILE_ERROR_MALFORMED;
          }
          // fall-through
        case "crash.main.2":
          let crashID = lines[0];
          let metadata = parseKeyValuePairsFromLines(lines.slice(1));
          store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
                         crashID, date, metadata);

          if (!("CrashPingUUID" in metadata)) {
            // If CrashPingUUID is not present then a ping was not generated
            // by the crashreporter for this crash so we need to send one from
            // here.
            this._sendCrashPing(crashID, this.PROCESS_TYPE_MAIN, date,
                                metadata);
          }

          break;

        case "crash.submission.1":
          if (lines.length == 3) {
            let [crashID, result, remoteID] = lines;
            store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
                           crashID, date);

            let submissionID = this.generateSubmissionID();
            let succeeded = result === "true";
            store.addSubmissionAttempt(crashID, submissionID, date);
            store.addSubmissionResult(crashID, submissionID, date,
                                      succeeded ? this.SUBMISSION_RESULT_OK :
                                                  this.SUBMISSION_RESULT_FAILED);
            if (succeeded) {
              store.setRemoteCrashID(crashID, remoteID);
            }
          } else {
            return this.EVENT_FILE_ERROR_MALFORMED;
          }
          break;

        default:
          return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
      }

      return this.EVENT_FILE_SUCCESS;
  },

  /**
   * The resolved promise is an array of objects with the properties:
   *
   *   path -- String filename
   *   id -- regexp.match()[1] (likely the crash ID)
   *   date -- Date mtime of the file
   */
  _getDirectoryEntries(path, re) {
    return (async function() {
      try {
        await OS.File.stat(path);
      } catch (ex) {
        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
          throw ex;
        }
        return [];
      }

      let it = new OS.File.DirectoryIterator(path);
      let entries = [];

      try {
        await it.forEach((entry, index, it) => {
          if (entry.isDir) {
            return undefined;
          }

          let match = re.exec(entry.name);
          if (!match) {
            return undefined;
          }

          return OS.File.stat(entry.path).then((info) => {
            entries.push({
              path: entry.path,
              id: match[1],
              date: info.lastModificationDate,
            });
          });
        });
      } finally {
        it.close();
      }

      entries.sort((a, b) => { return a.date - b.date; });

      return entries;
    })();
  },

  _getStore() {
    if (this._getStoreTask) {
      return this._getStoreTask;
    }

    return this._getStoreTask = (async () => {
      try {
        if (!this._store) {
          await OS.File.makeDir(this._storeDir, {
            ignoreExisting: true,
            unixMode: OS.Constants.libc.S_IRWXU,
          });

          let store = new CrashStore(this._storeDir,
                                     this._telemetryStoreSizeKey);
          await store.load();

          this._store = store;
          this._storeTimer = Cc["@mozilla.org/timer;1"]
                               .createInstance(Ci.nsITimer);
        }

        // The application can go long periods without interacting with the
        // store. Since the store takes up resources, we automatically "free"
        // the store after inactivity so resources can be returned to the
        // system. We do this via a timer and a mechanism that tracks when the
        // store is being accessed.
        this._storeTimer.cancel();

        // This callback frees resources from the store unless the store
        // is protected from freeing by some other process.
        let timerCB = () => {
          if (this._storeProtectedCount) {
            this._storeTimer.initWithCallback(timerCB, this.STORE_EXPIRATION_MS,
                                              this._storeTimer.TYPE_ONE_SHOT);
            return;
          }

          // We kill the reference that we hold. GC will kill it later. If
          // someone else holds a reference, that will prevent GC until that
          // reference is gone.
          this._store = null;
          this._storeTimer = null;
        };

        this._storeTimer.initWithCallback(timerCB, this.STORE_EXPIRATION_MS,
                                          this._storeTimer.TYPE_ONE_SHOT);

        return this._store;
      } finally {
        this._getStoreTask = null;
      }
    })();
  },

  /**
   * Obtain information about all known crashes.
   *
   * Returns an array of CrashRecord instances. Instances are read-only.
   */
  getCrashes() {
    return (async () => {
      let store = await this._getStore();

      return store.crashes;
    })();
  },

  getCrashCountsByDay() {
    return (async () => {
      let store = await this._getStore();

      return store._countsByDay;
    })();
  },
});

var gCrashManager;

/**
 * Interface to storage of crash data.
 *
 * This type handles storage of crash metadata. It exists as a separate type
 * from the crash manager for performance reasons: since all crash metadata
 * needs to be loaded into memory for access, we wish to easily dispose of all
 * associated memory when this data is no longer needed. Having an isolated
 * object whose references can easily be lost faciliates that simple disposal.
 *
 * When metadata is updated, the caller must explicitly persist the changes
 * to disk. This prevents excessive I/O during updates.
 *
 * The store has a mechanism for ensuring it doesn't grow too large. A ceiling
 * is placed on the number of daily events that can occur for events that can
 * occur with relatively high frequency, notably plugin crashes and hangs
 * (plugins can enter cycles where they repeatedly crash). If we've reached
 * the high water mark and new data arrives, it's silently dropped.
 * However, the count of actual events is always preserved. This allows
 * us to report on the severity of problems beyond the storage threshold.
 *
 * Main process crashes are excluded from limits because they are both
 * important and should be rare.
 *
 * @param storeDir (string)
 *        Directory the store should be located in.
 * @param telemetrySizeKey (string)
 *        The telemetry histogram that should be used to store the size
 *        of the data file.
 */
function CrashStore(storeDir, telemetrySizeKey) {
  this._storeDir = storeDir;
  this._telemetrySizeKey = telemetrySizeKey;

  this._storePath = OS.Path.join(storeDir, "store.json.mozlz4");

  // Holds the read data from disk.
  this._data = null;

  // Maps days since UNIX epoch to a Map of event types to counts.
  // This data structure is populated when the JSON file is loaded
  // and is also updated when new events are added.
  this._countsByDay = new Map();
}

CrashStore.prototype = Object.freeze({
  // Maximum number of events to store per day. This establishes a
  // ceiling on the per-type/per-day records that will be stored.
  HIGH_WATER_DAILY_THRESHOLD: 100,

  /**
   * Reset all data.
   */
  reset() {
    this._data = {
      v: 1,
      crashes: new Map(),
      corruptDate: null,
    };
    this._countsByDay = new Map();
  },

  /**
   * Load data from disk.
   *
   * @return Promise
   */
  load() {
    return (async () => {
      // Loading replaces data.
      this.reset();

      try {
        let decoder = new TextDecoder();
        let data = await OS.File.read(this._storePath, {compression: "lz4"});
        data = JSON.parse(decoder.decode(data));

        if (data.corruptDate) {
          this._data.corruptDate = new Date(data.corruptDate);
        }

        // actualCounts is used to validate that the derived counts by
        // days stored in the payload matches up to actual data.
        let actualCounts = new Map();

        // In the past, submissions were stored as separate crash records
        // with an id of e.g. "someID-submission". If we find IDs ending
        // with "-submission", we will need to convert the data to be stored
        // as actual submissions.
        //
        // The old way of storing submissions was used from FF33 - FF34. We
        // drop this old data on the floor.
        for (let id in data.crashes) {
          if (id.endsWith("-submission")) {
            continue;
          }

          let crash = data.crashes[id];
          let denormalized = this._denormalize(crash);

          denormalized.submissions = new Map();
          if (crash.submissions) {
            for (let submissionID in crash.submissions) {
              let submission = crash.submissions[submissionID];
              denormalized.submissions.set(submissionID,
                                           this._denormalize(submission));
            }
          }

          this._data.crashes.set(id, denormalized);

          let key = dateToDays(denormalized.crashDate) + "-" + denormalized.type;
          actualCounts.set(key, (actualCounts.get(key) || 0) + 1);

          // If we have an OOM size, count the crash as an OOM in addition to
          // being a main process crash.
          if (denormalized.metadata &&
              denormalized.metadata.OOMAllocationSize) {
            let oomKey = key + "-oom";
            actualCounts.set(oomKey, (actualCounts.get(oomKey) || 0) + 1);
          }

        }

        // The validation in this loop is arguably not necessary. We perform
        // it as a defense against unknown bugs.
        for (let dayKey in data.countsByDay) {
          let day = parseInt(dayKey, 10);
          for (let type in data.countsByDay[day]) {
            this._ensureCountsForDay(day);

            let count = data.countsByDay[day][type];
            let key = day + "-" + type;

            // If the payload says we have data for a given day but we
            // don't, the payload is wrong. Ignore it.
            if (!actualCounts.has(key)) {
              continue;
            }

            // If we encountered more data in the payload than what the
            // data structure says, use the proper value.
            count = Math.max(count, actualCounts.get(key));

            this._countsByDay.get(day).set(type, count);
          }
        }
      } catch (ex) {
        // Missing files (first use) are allowed.
        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
          // If we can't load for any reason, mark a corrupt date in the instance
          // and swallow the error.
          //
          // The marking of a corrupted file is intentionally not persisted to
          // disk yet. Instead, we wait until the next save(). This is to give
          // non-permanent failures the opportunity to recover on their own.
          this._data.corruptDate = new Date();
        }
      }
    })();
  },

  /**
   * Save data to disk.
   *
   * @return Promise<null>
   */
  save() {
    return (async () => {
      if (!this._data) {
        return;
      }

      let normalized = {
        // The version should be incremented whenever the format
        // changes.
        v: 1,
        // Maps crash IDs to objects defining the crash.
        crashes: {},
        // Maps days since UNIX epoch to objects mapping event types to
        // counts. This is a mirror of this._countsByDay. e.g.
        // {
        //    15000: {
        //        "main-crash": 2,
        //        "plugin-crash": 1
        //    }
        // }
        countsByDay: {},

        // When the store was last corrupted.
        corruptDate: null,
      };

      if (this._data.corruptDate) {
        normalized.corruptDate = this._data.corruptDate.getTime();
      }

      for (let [id, crash] of this._data.crashes) {
        let c = this._normalize(crash);

        c.submissions = {};
        for (let [submissionID, submission] of crash.submissions) {
          c.submissions[submissionID] = this._normalize(submission);
        }

        normalized.crashes[id] = c;
      }

      for (let [day, m] of this._countsByDay) {
        normalized.countsByDay[day] = {};
        for (let [type, count] of m) {
          normalized.countsByDay[day][type] = count;
        }
      }

      let encoder = new TextEncoder();
      let data = encoder.encode(JSON.stringify(normalized));
      let size = await OS.File.writeAtomic(this._storePath, data, {
                                           tmpPath: this._storePath + ".tmp",
                                           compression: "lz4"});
      if (this._telemetrySizeKey) {
        Services.telemetry.getHistogramById(this._telemetrySizeKey).add(size);
      }
    })();
  },

  /**
   * Normalize an object into one fit for serialization.
   *
   * This function along with _denormalize() serve to hack around the
   * default handling of Date JSON serialization because Date serialization
   * is undefined by JSON.
   *
   * Fields ending with "Date" are assumed to contain Date instances.
   * We convert these to milliseconds since epoch on output and back to
   * Date on input.
   */
  _normalize(o) {
    let normalized = {};

    for (let k in o) {
      let v = o[k];
      if (v && k.endsWith("Date")) {
        normalized[k] = v.getTime();
      } else {
        normalized[k] = v;
      }
    }

    return normalized;
  },

  /**
   * Convert a serialized object back to its native form.
   */
  _denormalize(o) {
    let n = {};

    for (let k in o) {
      let v = o[k];
      if (v && k.endsWith("Date")) {
        n[k] = new Date(parseInt(v, 10));
      } else {
        n[k] = v;
      }
    }

    return n;
  },

  /**
   * Prune old crash data.
   *
   * Crashes without recent activity are pruned from the store so the
   * size of the store is not unbounded. If there is activity on a crash,
   * that activity will keep the crash and all its data around for longer.
   *
   * @param date
   *        (Date) The cutoff at which data will be pruned. If an entry
   *        doesn't have data newer than this, it will be pruned.
   */
  pruneOldCrashes(date) {
    for (let crash of this.crashes) {
      let newest = crash.newestDate;
      if (!newest || newest.getTime() < date.getTime()) {
        this._data.crashes.delete(crash.id);
      }
    }
  },

  /**
   * Date the store was last corrupted and required a reset.
   *
   * May be null (no corruption has ever occurred) or a Date instance.
   */
  get corruptDate() {
    return this._data.corruptDate;
  },

  /**
   * The number of distinct crashes tracked.
   */
  get crashesCount() {
    return this._data.crashes.size;
  },

  /**
   * All crashes tracked.
   *
   * This is an array of CrashRecord.
   */
  get crashes() {
    let crashes = [];
    for (let [, crash] of this._data.crashes) {
      crashes.push(new CrashRecord(crash));
    }

    return crashes;
  },

  /**
   * Obtain a particular crash from its ID.
   *
   * A CrashRecord will be returned if the crash exists. null will be returned
   * if the crash is unknown.
   */
  getCrash(id) {
    for (let crash of this.crashes) {
      if (crash.id == id) {
        return crash;
      }
    }

    return null;
  },

  _ensureCountsForDay(day) {
    if (!this._countsByDay.has(day)) {
      this._countsByDay.set(day, new Map());
    }
  },

  /**
   * Ensure the crash record is present in storage.
   *
   * Returns the crash record if we're allowed to store it or null
   * if we've hit the high water mark.
   *
   * @param processType
   *        (string) One of the PROCESS_TYPE constants.
   * @param crashType
   *        (string) One of the CRASH_TYPE constants.
   * @param id
   *        (string) The crash ID.
   * @param date
   *        (Date) When this crash occurred.
   * @param metadata
   *        (dictionary) Crash metadata, may be empty.
   *
   * @return null | object crash record
   */
  _ensureCrashRecord(processType, crashType, id, date, metadata) {
    if (!id) {
      // Crashes are keyed on ID, so it's not really helpful to store crashes
      // without IDs.
      return null;
    }

    let type = processType + "-" + crashType;

    if (!this._data.crashes.has(id)) {
      let day = dateToDays(date);
      this._ensureCountsForDay(day);

      let count = (this._countsByDay.get(day).get(type) || 0) + 1;
      this._countsByDay.get(day).set(type, count);

      if (count > this.HIGH_WATER_DAILY_THRESHOLD &&
          processType != CrashManager.prototype.PROCESS_TYPE_MAIN) {
        return null;
      }

      // If we have an OOM size, count the crash as an OOM in addition to
      // being a main process crash.
      if (metadata && metadata.OOMAllocationSize) {
        let oomType = type + "-oom";
        let oomCount = (this._countsByDay.get(day).get(oomType) || 0) + 1;
        this._countsByDay.get(day).set(oomType, oomCount);
      }

      this._data.crashes.set(id, {
        id,
        remoteID: null,
        type,
        crashDate: date,
        submissions: new Map(),
        classifications: [],
        metadata,
      });
    }

    let crash = this._data.crashes.get(id);
    crash.type = type;
    crash.crashDate = date;

    return crash;
  },

  /**
   * Record the occurrence of a crash.
   *
   * @param processType (string) One of the PROCESS_TYPE constants.
   * @param crashType (string) One of the CRASH_TYPE constants.
   * @param id (string) Crash ID. Likely a UUID.
   * @param date (Date) When the crash occurred.
   * @param metadata (dictionary) Crash metadata, may be empty.
   *
   * @return boolean True if the crash was recorded and false if not.
   */
  addCrash(processType, crashType, id, date, metadata) {
    return !!this._ensureCrashRecord(processType, crashType, id, date, metadata);
  },

  /**
   * @return boolean True if the remote ID was recorded and false if not.
   */
  setRemoteCrashID(crashID, remoteID) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !remoteID) {
      return false;
    }

    crash.remoteID = remoteID;
    return true;
  },

  getCrashesOfType(processType, crashType) {
    let crashes = [];
    for (let crash of this.crashes) {
      if (crash.isOfType(processType, crashType)) {
        crashes.push(crash);
      }
    }

    return crashes;
  },

  /**
   * Ensure the submission record is present in storage.
   * @returns [submission, crash]
   */
  _ensureSubmissionRecord(crashID, submissionID) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !submissionID) {
      return null;
    }

    if (!crash.submissions.has(submissionID)) {
      crash.submissions.set(submissionID, {
        requestDate: null,
        responseDate: null,
        result: null,
      });
    }

    return [crash.submissions.get(submissionID), crash];
  },

  /**
   * @return boolean True if the attempt was recorded.
   */
  addSubmissionAttempt(crashID, submissionID, date) {
    let [submission, crash] =
      this._ensureSubmissionRecord(crashID, submissionID);
    if (!submission) {
      return false;
    }

    submission.requestDate = date;
    Services.telemetry.getKeyedHistogramById("PROCESS_CRASH_SUBMIT_ATTEMPT")
      .add(crash.type, 1);
    return true;
  },

  /**
   * @return boolean True if the response was recorded.
   */
  addSubmissionResult(crashID, submissionID, date, result) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !submissionID) {
      return false;
    }
    let submission = crash.submissions.get(submissionID);
    if (!submission) {
      return false;
    }

    submission.responseDate = date;
    submission.result = result;
    Services.telemetry.getKeyedHistogramById("PROCESS_CRASH_SUBMIT_SUCCESS")
      .add(crash.type, result == "ok");
    return true;
  },

  /**
   * @return boolean True if the classifications were set.
   */
  setCrashClassifications(crashID, classifications) {
    let crash = this._data.crashes.get(crashID);
    if (!crash) {
      return false;
    }

    crash.classifications = classifications;
    return true;
  },
});

/**
 * Represents an individual crash with metadata.
 *
 * This is a wrapper around the low-level anonymous JS objects that define
 * crashes. It exposes a consistent and helpful API.
 *
 * Instances of this type should only be constructured inside this module,
 * not externally. The constructor is not considered a public API.
 *
 * @param o (object)
 *        The crash's entry from the CrashStore.
 */
function CrashRecord(o) {
  this._o = o;
}

CrashRecord.prototype = Object.freeze({
  get id() {
    return this._o.id;
  },

  get remoteID() {
    return this._o.remoteID;
  },

  get crashDate() {
    return this._o.crashDate;
  },

  /**
   * Obtain the newest date in this record.
   *
   * This is a convenience getter. The returned value is used to determine when
   * to expire a record.
   */
  get newestDate() {
    // We currently only have 1 date, so this is easy.
    return this._o.crashDate;
  },

  get oldestDate() {
    return this._o.crashDate;
  },

  get type() {
    return this._o.type;
  },

  isOfType(processType, crashType) {
    return processType + "-" + crashType == this.type;
  },

  get submissions() {
    return this._o.submissions;
  },

  get classifications() {
    return this._o.classifications;
  },

  get metadata() {
    return this._o.metadata;
  },
});

/**
 * Obtain the global CrashManager instance used by the running application.
 *
 * CrashManager is likely only ever instantiated once per application lifetime.
 * The main reason it's implemented as a reusable type is to facilitate testing.
 */
XPCOMUtils.defineLazyGetter(this.CrashManager, "Singleton", function() {
  if (gCrashManager) {
    return gCrashManager;
  }

  let crPath = OS.Path.join(OS.Constants.Path.userApplicationDataDir,
                            "Crash Reports");
  let storePath = OS.Path.join(OS.Constants.Path.profileDir, "crashes");

  gCrashManager = new CrashManager({
    pendingDumpsDir: OS.Path.join(crPath, "pending"),
    submittedDumpsDir: OS.Path.join(crPath, "submitted"),
    eventsDirs: [OS.Path.join(crPath, "events"), OS.Path.join(storePath, "events")],
    storeDir: storePath,
    telemetryStoreSizeKey: "CRASH_STORE_COMPRESSED_BYTES",
  });

  // Automatically aggregate event files shortly after startup. This
  // ensures it happens with some frequency.
  //
  // There are performance considerations here. While this is doing
  // work and could negatively impact performance, the amount of work
  // is kept small per run by periodically aggregating event files.
  // Furthermore, well-behaving installs should not have much work
  // here to do. If there is a lot of work, that install has bigger
  // issues beyond reduced performance near startup.
  gCrashManager.scheduleMaintenance(AGGREGATE_STARTUP_DELAY_MS);

  return gCrashManager;
});
PK
!<hhmodules/CrashMonitor.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Crash Monitor
 *
 * Monitors execution of a program to detect possible crashes. After
 * program termination, the monitor can be queried during the next run
 * to determine whether the last run exited cleanly or not.
 *
 * The monitoring is done by registering and listening for special
 * notifications, or checkpoints, known to be sent by the monitored
 * program as different stages in the execution are reached. As they
 * are observed, these notifications are written asynchronously to a
 * checkpoint file.
 *
 * During next program startup the crash monitor reads the checkpoint
 * file from the last session. If notifications are missing, a crash
 * has likely happened. By inspecting the notifications present, it is
 * possible to determine what stages were reached in the program
 * before the crash.
 *
 * Note that since the file is written asynchronously it is possible
 * that a received notification is lost if the program crashes right
 * after a checkpoint, but before crash monitor has been able to write
 * it to disk. Thus, while the presence of a notification in the
 * checkpoint file tells us that the corresponding stage was reached
 * during the last run, the absence of a notification after a crash
 * does not necessarily tell us that the checkpoint wasn't reached.
 */

this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];

const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");

const NOTIFICATIONS = [
  "final-ui-startup",
  "sessionstore-windows-restored",
  "quit-application-granted",
  "quit-application",
  "profile-change-net-teardown",
  "profile-change-teardown",
  "profile-before-change",
  "sessionstore-final-state-write-complete"
];

var CrashMonitorInternal = {

  /**
   * Notifications received during the current session.
   *
   * Object where a property with a value of |true| means that the
   * notification of the same name has been received at least once by
   * the CrashMonitor during this session. Notifications that have not
   * yet been received are not present as properties. |NOTIFICATIONS|
   * lists the notifications tracked by the CrashMonitor.
   */
  checkpoints: {},

  /**
   * Notifications received during previous session.
   *
   * Available after |loadPreviousCheckpoints|. Promise which resolves
   * to an object containing a set of properties, where a property
   * with a value of |true| means that the notification with the same
   * name as the property name was received at least once last
   * session.
   */
  previousCheckpoints: null,

  /* Deferred for AsyncShutdown blocker */
  profileBeforeChangeDeferred: PromiseUtils.defer(),

  /**
   * Path to checkpoint file.
   *
   * Each time a new notification is received, this file is written to
   * disc to reflect the information in |checkpoints|.
   */
  path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),

  /**
   * Load checkpoints from previous session asynchronously.
   *
   * @return {Promise} A promise that resolves/rejects once loading is complete
   */
  loadPreviousCheckpoints() {
    this.previousCheckpoints = (async function() {
      let data;
      try {
        data = await OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" });
      } catch (ex) {
        if (!(ex instanceof OS.File.Error)) {
          throw ex;
        }
        if (!ex.becauseNoSuchFile) {
          Cu.reportError("Error while loading crash monitor data: " + ex.toString());
        }

        return null;
      }

      let notifications;
      try {
        notifications = JSON.parse(data);
      } catch (ex) {
        Cu.reportError("Error while parsing crash monitor data: " + ex);
        return null;
      }

      // If `notifications` isn't an object, then the monitor data isn't valid.
      if (Object(notifications) !== notifications) {
        Cu.reportError("Error while parsing crash monitor data: invalid monitor data");
        return null;
      }

      return Object.freeze(notifications);
    })();

    return this.previousCheckpoints;
  }
};

this.CrashMonitor = {

  /**
   * Notifications received during previous session.
   *
   * Return object containing the set of notifications received last
   * session as keys with values set to |true|.
   *
   * @return {Promise} A promise resolving to previous checkpoints
   */
  get previousCheckpoints() {
    if (!CrashMonitorInternal.initialized) {
      throw new Error("CrashMonitor must be initialized before getting previous checkpoints");
    }

    return CrashMonitorInternal.previousCheckpoints
  },

  /**
   * Initialize CrashMonitor.
   *
   * Should only be called from the CrashMonitor XPCOM component.
   *
   * @return {Promise}
   */
  init() {
    if (CrashMonitorInternal.initialized) {
      throw new Error("CrashMonitor.init() must only be called once!");
    }

    let promise = CrashMonitorInternal.loadPreviousCheckpoints();
    // Add "profile-after-change" to checkpoint as this method is
    // called after receiving it
    CrashMonitorInternal.checkpoints["profile-after-change"] = true;

    NOTIFICATIONS.forEach(function(aTopic) {
      Services.obs.addObserver(this, aTopic);
    }, this);

    // Add shutdown blocker for profile-before-change
    OS.File.profileBeforeChange.addBlocker(
      "CrashMonitor: Writing notifications to file after receiving profile-before-change",
      CrashMonitorInternal.profileBeforeChangeDeferred.promise,
      () => this.checkpoints
    );

    CrashMonitorInternal.initialized = true;
    return promise;
  },

  /**
   * Handle registered notifications.
   *
   * Update checkpoint file for every new notification received.
   */
  observe(aSubject, aTopic, aData) {
    if (!(aTopic in CrashMonitorInternal.checkpoints)) {
      // If this is the first time this notification is received,
      // remember it and write it to file
      CrashMonitorInternal.checkpoints[aTopic] = true;
      (async function() {
        try {
          let data = JSON.stringify(CrashMonitorInternal.checkpoints);

          /* Write to the checkpoint file asynchronously, off the main
           * thread, for performance reasons. Note that this means
           * that there's not a 100% guarantee that the file will be
           * written by the time the notification completes. The
           * exception is profile-before-change which has a shutdown
           * blocker. */
          await OS.File.writeAtomic(
            CrashMonitorInternal.path,
            data, {tmpPath: CrashMonitorInternal.path + ".tmp"});

        } finally {
          // Resolve promise for blocker
          if (aTopic == "profile-before-change") {
            CrashMonitorInternal.profileBeforeChangeDeferred.resolve();
          }
        }
      })();
    }

    if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) {
      // All notifications received, unregister observers
      NOTIFICATIONS.forEach(function(aTopic) {
        Services.obs.removeObserver(this, aTopic);
      }, this);
    }
  }
};
Object.freeze(this.CrashMonitor);
PK
!<

modules/CrashReports.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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");

this.EXPORTED_SYMBOLS = [
  "CrashReports"
];

this.CrashReports = {
  pendingDir: null,
  reportsDir: null,
  submittedDir: null,
  getReports: function CrashReports_getReports() {
    let reports = [];

    try {
      // Ignore any non http/https urls
      if (!/^https?:/i.test(Services.prefs.getCharPref("breakpad.reportURL")))
        return reports;
    } catch (e) { }

    if (this.submittedDir.exists() && this.submittedDir.isDirectory()) {
      let entries = this.submittedDir.directoryEntries;
      while (entries.hasMoreElements()) {
        let file = entries.getNext().QueryInterface(Components.interfaces.nsIFile);
        let leaf = file.leafName;
        if (leaf.startsWith("bp-") &&
            leaf.endsWith(".txt")) {
          let entry = {
            id: leaf.slice(0, -4),
            date: file.lastModifiedTime,
            pending: false
          };
          reports.push(entry);
        }
      }
    }

    if (this.pendingDir.exists() && this.pendingDir.isDirectory()) {
      let uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
      let entries = this.pendingDir.directoryEntries;
      while (entries.hasMoreElements()) {
        let file = entries.getNext().QueryInterface(Components.interfaces.nsIFile);
        let leaf = file.leafName;
        let id = leaf.slice(0, -4);
        if (leaf.endsWith(".dmp") && uuidRegex.test(id)) {
          let entry = {
            id,
            date: file.lastModifiedTime,
            pending: true
          };
          reports.push(entry);
        }
      }
    }

    // Sort reports descending by date
    return reports.sort( (a, b) => b.date - a.date);
  }
}

function CrashReports_pendingDir() {
  let pendingDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
  pendingDir.append("Crash Reports");
  pendingDir.append("pending");
  return pendingDir;
}

function CrashReports_reportsDir() {
  let reportsDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
  reportsDir.append("Crash Reports");
  return reportsDir;
}

function CrashReports_submittedDir() {
  let submittedDir = Services.dirsvc.get("UAppData", Components.interfaces.nsIFile);
  submittedDir.append("Crash Reports");
  submittedDir.append("submitted");
  return submittedDir;
}

this.CrashReports.pendingDir = CrashReports_pendingDir();
this.CrashReports.reportsDir = CrashReports_reportsDir();
this.CrashReports.submittedDir = CrashReports_submittedDir();
PK
!<Ɩ
1I1Imodules/CrashSubmit.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/KeyValueParser.jsm");
Cu.importGlobalProperties(["File"]);

XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

this.EXPORTED_SYMBOLS = [
  "CrashSubmit"
];

const STATE_START = Ci.nsIWebProgressListener.STATE_START;
const STATE_STOP = Ci.nsIWebProgressListener.STATE_STOP;

const SUCCESS = "success";
const FAILED  = "failed";
const SUBMITTING = "submitting";

const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

// TODO: this is still synchronous; need an async INI parser to make it async
function parseINIStrings(path) {
  let file = new FileUtils.File(path);
  let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                getService(Ci.nsIINIParserFactory);
  let parser = factory.createINIParser(file);
  let obj = {};
  let en = parser.getKeys("Strings");
  while (en.hasMore()) {
    let key = en.getNext();
    obj[key] = parser.getString("Strings", key);
  }
  return obj;
}

// Since we're basically re-implementing (with async) part of the crashreporter
// client here, we'll just steal the strings we need from crashreporter.ini
async function getL10nStrings() {
  let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
               getService(Ci.nsIProperties);
  let path = OS.Path.join(dirSvc.get("GreD", Ci.nsIFile).path,
                          "crashreporter.ini");
  let pathExists = await OS.File.exists(path);

  if (!pathExists) {
    // we if we're on a mac
    let parentDir = OS.Path.dirname(path);
    path = OS.Path.join(parentDir, "MacOS", "crashreporter.app", "Contents",
                        "Resources", "crashreporter.ini");

    let pathExists = await OS.File.exists(path);

    if (!pathExists) {
      // This happens on Android where everything is in an APK.
      // Android users can't see the contents of the submitted files
      // anyway, so just hardcode some fallback strings.
      return {
        "crashid": "Crash ID: %s",
        "reporturl": "You can view details of this crash at %s"
      };
    }
  }

  let crstrings = parseINIStrings(path);
  let strings = {
    "crashid": crstrings.CrashID,
    "reporturl": crstrings.CrashDetailsURL
  };

  path = OS.Path.join(dirSvc.get("XCurProcD", Ci.nsIFile).path,
                      "crashreporter-override.ini");
  pathExists = await OS.File.exists(path);

  if (pathExists) {
    crstrings = parseINIStrings(path);

    if ("CrashID" in crstrings) {
      strings["crashid"] = crstrings.CrashID;
    }

    if ("CrashDetailsURL" in crstrings) {
      strings["reporturl"] = crstrings.CrashDetailsURL;
    }
  }

  return strings;
}

function getDir(name) {
  let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
               getService(Ci.nsIProperties);
  let uAppDataPath = dirSvc.get("UAppData", Ci.nsIFile).path;
  return OS.Path.join(uAppDataPath, "Crash Reports", name);
}

async function isDirAsync(path) {
  try {
    let dirInfo = await OS.File.stat(path);

    if (!dirInfo.isDir) {
      return false;
    }
  } catch (ex) {
    return false;
  }

  return true;
}

async function writeFileAsync(dirName, fileName, data) {
  let dirPath = getDir(dirName);
  let filePath = OS.Path.join(dirPath, fileName);
  // succeeds even with existing path, permissions 700
  await OS.File.makeDir(dirPath, { unixFlags: OS.Constants.libc.S_IRWXU });
  await OS.File.writeAtomic(filePath, data, { encoding: "utf-8" });
}

function getPendingMinidump(id) {
  let pendingDir = getDir("pending");

  return [".dmp", ".extra", ".memory.json.gz"].map(suffix => {
    return OS.Path.join(pendingDir, `${id}${suffix}`);
  });
}

function addFormEntry(doc, form, name, value) {
  let input = doc.createElement("input");
  input.type = "hidden";
  input.name = name;
  input.value = value;
  form.appendChild(input);
}

async function writeSubmittedReportAsync(crashID, viewURL) {
  let strings = await getL10nStrings();
  let data = strings.crashid.replace("%s", crashID);

  if (viewURL) {
    data += "\n" + strings.reporturl.replace("%s", viewURL);
  }

  await writeFileAsync("submitted", `${crashID}.txt`, data);
}

// the Submitter class represents an individual submission.
function Submitter(id, recordSubmission, noThrottle, extraExtraKeyVals) {
  this.id = id;
  this.recordSubmission = recordSubmission;
  this.noThrottle = noThrottle;
  this.additionalDumps = [];
  this.extraKeyVals = extraExtraKeyVals || {};
  // mimic deferred Promise behavior
  this.submitStatusPromise = new Promise((resolve, reject) => {
    this.resolveSubmitStatusPromise = resolve;
    this.rejectSubmitStatusPromise = reject;
  });
}

Submitter.prototype = {
  submitSuccess: async function Submitter_submitSuccess(ret) {
    // Write out the details file to submitted
    await writeSubmittedReportAsync(ret.CrashID, ret.ViewURL);

    try {
      let toDelete = [this.dump, this.extra];

      if (this.memory) {
        toDelete.push(this.memory);
      }

      for (let dump of this.additionalDumps) {
        toDelete.push(dump);
      }

      await Promise.all(toDelete.map(path => {
        return OS.File.remove(path, { ignoreAbsent: true });
      }));
    } catch (ex) {
      Cu.reportError(ex);
    }

    this.notifyStatus(SUCCESS, ret);
    this.cleanup();
  },

  cleanup: function Submitter_cleanup() {
    // drop some references just to be nice
    this.iframe = null;
    this.dump = null;
    this.extra = null;
    this.memory = null;
    this.additionalDumps = null;
    // remove this object from the list of active submissions
    let idx = CrashSubmit._activeSubmissions.indexOf(this);
    if (idx != -1) {
      CrashSubmit._activeSubmissions.splice(idx, 1);
    }
  },

  submitForm: function Submitter_submitForm() {
    if (!("ServerURL" in this.extraKeyVals)) {
      return false;
    }
    let serverURL = this.extraKeyVals.ServerURL;

    // Override the submission URL from the environment

    let envOverride = Cc["@mozilla.org/process/environment;1"].
      getService(Ci.nsIEnvironment).get("MOZ_CRASHREPORTER_URL");
    if (envOverride != "") {
      serverURL = envOverride;
    }

    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
              .createInstance(Ci.nsIXMLHttpRequest);
    xhr.open("POST", serverURL, true);

    let formData = Cc["@mozilla.org/files/formdata;1"]
                   .createInstance(Ci.nsIDOMFormData);
    // add the data
    for (let [name, value] of Object.entries(this.extraKeyVals)) {
      if (name != "ServerURL") {
        formData.append(name, value);
      }
    }
    if (this.noThrottle) {
      // tell the server not to throttle this, since it was manually submitted
      formData.append("Throttleable", "0");
    }
    // add the minidumps
    let promises = [
      File.createFromFileName(this.dump).then(file => {
        formData.append("upload_file_minidump", file);
      })
    ];

    if (this.memory) {
      promises.push(File.createFromFileName(this.memory).then(file => {
        formData.append("memory_report", file);
      }));
    }

    if (this.additionalDumps.length > 0) {
      let names = [];
      for (let i of this.additionalDumps) {
        names.push(i.name);
        promises.push(File.createFromFileName(i.dump).then(file => {
          formData.append("upload_file_minidump_" + i.name, file);
        }));
      }
    }

    let manager = Services.crashmanager;
    let submissionID = manager.generateSubmissionID();

    xhr.addEventListener("readystatechange", (evt) => {
      if (xhr.readyState == 4) {
        let ret =
          xhr.status === 200 ? parseKeyValuePairs(xhr.responseText) : {};
        let submitted = !!ret.CrashID;
        let p = Promise.resolve();

        if (this.recordSubmission) {
          let result = submitted ? manager.SUBMISSION_RESULT_OK :
                                   manager.SUBMISSION_RESULT_FAILED;
          p = manager.addSubmissionResult(this.id, submissionID, new Date(),
                                          result);
          if (submitted) {
            manager.setRemoteCrashID(this.id, ret.CrashID);
          }
        }

        p.then(() => {
          if (submitted) {
            this.submitSuccess(ret);
          } else {
            this.notifyStatus(FAILED);
            this.cleanup();
          }
        });
      }
    });

    let p = Promise.all(promises);
    let id = this.id;

    if (this.recordSubmission) {
      p = p.then(() => { return manager.ensureCrashIsPresent(id); })
           .then(() => {
             return manager.addSubmissionAttempt(id, submissionID, new Date());
            });
    }
    p.then(() => { xhr.send(formData); });
    return true;
  },

  notifyStatus: function Submitter_notify(status, ret) {
    let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                  createInstance(Ci.nsIWritablePropertyBag2);
    propBag.setPropertyAsAString("minidumpID", this.id);
    if (status == SUCCESS) {
      propBag.setPropertyAsAString("serverCrashID", ret.CrashID);
    }

    let extraKeyValsBag = Cc["@mozilla.org/hash-property-bag;1"].
                          createInstance(Ci.nsIWritablePropertyBag2);
    for (let key in this.extraKeyVals) {
      extraKeyValsBag.setPropertyAsAString(key, this.extraKeyVals[key]);
    }
    propBag.setPropertyAsInterface("extra", extraKeyValsBag);

    Services.obs.notifyObservers(propBag, "crash-report-status", status);

    switch (status) {
      case SUCCESS:
        this.resolveSubmitStatusPromise(ret.CrashID);
        break;
      case FAILED:
        this.rejectSubmitStatusPromise(FAILED);
        break;
      default:
        // no callbacks invoked.
    }
  },

  submit: async function Submitter_submit() {
    let [dump, extra, memory] = getPendingMinidump(this.id);
    let [dumpExists, extraExists, memoryExists] = await Promise.all([
      OS.File.exists(dump),
      OS.File.exists(extra),
      OS.File.exists(memory)
    ]);

    if (!dumpExists || !extraExists) {
      this.notifyStatus(FAILED);
      this.cleanup();
      return this.submitStatusPromise;
    }

    this.dump = dump;
    this.extra = extra;
    this.memory = memoryExists ? memory : null;

    let extraKeyVals = await parseKeyValuePairsFromFileAsync(extra);

    for (let key in extraKeyVals) {
      if (!(key in this.extraKeyVals)) {
        this.extraKeyVals[key] = extraKeyVals[key];
      }
    }

    let additionalDumps = [];

    if ("additional_minidumps" in this.extraKeyVals) {
      let dumpsExistsPromises = [];
      let names = this.extraKeyVals.additional_minidumps.split(",");

      for (let name of names) {
        let [dump /* , extra, memory */] = getPendingMinidump(this.id + "-" + name);

        dumpsExistsPromises.push(OS.File.exists(dump));
        additionalDumps.push({name, dump});
      }

      let dumpsExist = await Promise.all(dumpsExistsPromises);
      let allDumpsExist = dumpsExist.every(exists => exists);

      if (!allDumpsExist) {
        this.notifyStatus(FAILED);
        this.cleanup();
        return this.submitStatusPromise;
      }
    }

    this.notifyStatus(SUBMITTING);
    this.additionalDumps = additionalDumps;

    if (!(await this.submitForm())) {
       this.notifyStatus(FAILED);
       this.cleanup();
    }

    return this.submitStatusPromise;
  }
};

// ===================================
// External API goes here
this.CrashSubmit = {
  /**
   * Submit the crash report named id.dmp from the "pending" directory.
   *
   * @param id
   *        Filename (minus .dmp extension) of the minidump to submit.
   * @param params
   *        An object containing any of the following optional parameters:
   *        - recordSubmission
   *          If true, a submission event is recorded in CrashManager.
   *        - noThrottle
   *          If true, this crash report should be submitted with
   *          an extra parameter of "Throttleable=0" indicating that
   *          it should be processed right away. This should be set
   *          when the report is being submitted and the user expects
   *          to see the results immediately. Defaults to false.
   *        - extraExtraKeyVals
   *          An object whose key-value pairs will be merged with the data from
   *          the ".extra" file submitted with the report.  The properties of
   *          this object will override properties of the same name in the
   *          .extra file.
   *
   *  @return a Promise that is fulfilled with the server crash ID when the
   *          submission succeeds and rejected otherwise.
   */
  submit: function CrashSubmit_submit(id, params) {
    params = params || {};
    let recordSubmission = false;
    let noThrottle = false;
    let extraExtraKeyVals = null;

    if ("recordSubmission" in params) {
      recordSubmission = params.recordSubmission;
    }

    if ("noThrottle" in params) {
      noThrottle = params.noThrottle;
    }

    if ("extraExtraKeyVals" in params) {
      extraExtraKeyVals = params.extraExtraKeyVals;
    }

    let submitter = new Submitter(id, recordSubmission,
                                  noThrottle, extraExtraKeyVals);
    CrashSubmit._activeSubmissions.push(submitter);
    return submitter.submit();
  },

  /**
   * Delete the minidup from the "pending" directory.
   *
   * @param id
   *        Filename (minus .dmp extension) of the minidump to delete.
   *
   * @return a Promise that is fulfilled when the minidump is deleted and
   *         rejected otherwise
   */
  delete: async function CrashSubmit_delete(id) {
    await Promise.all(getPendingMinidump(id).map(path => {
      return OS.File.remove(path, { ignoreAbsent: true });
    }));
  },

  /**
   * Add a .dmg.ignore file along side the .dmp file to indicate that the user
   * shouldn't be prompted to submit this crash report again.
   *
   * @param id
   *        Filename (minus .dmp extension) of the report to ignore
   *
   * @return a Promise that is fulfilled when (if) the .dmg.ignore is created
   *         and rejected otherwise.
   */
  ignore: async function CrashSubmit_ignore(id) {
    let [dump /* , extra, memory */] = getPendingMinidump(id);
    let file = await OS.File.open(`${dump}.ignore`, { create: true },
                                  { unixFlags: OS.Constants.libc.O_CREAT });
    await file.close();
  },

  /**
   * Get the list of pending crash IDs, excluding those marked to be ignored
   * @param minFileDate
   *     A Date object. Any files last modified before that date will be ignored
   *
   * @return a Promise that is fulfilled with an array of string, each
   *         being an ID as expected to be passed to submit() or ignore()
   */
  pendingIDs: async function CrashSubmit_pendingIDs(minFileDate) {
    let ids = [];
    let dirIter = null;
    let pendingDir = getDir("pending");

    try {
      dirIter = new OS.File.DirectoryIterator(pendingDir);
    } catch (ex) {
      if (ex.becauseNoSuchFile) {
        return ids;
      }

      Cu.reportError(ex);
      throw ex;
    }

    try {
      let entries = Object.create(null);
      let ignored = Object.create(null);

      // `await` in order to ensure all callbacks are called
      await dirIter.forEach(entry => {
        if (!entry.isDir /* is file */) {
          let matches = entry.name.match(/(.+)\.dmp$/);

          if (matches) {
            let id = matches[1];

            if (UUID_REGEX.test(id)) {
              entries[id] = OS.File.stat(entry.path);
            }
          } else {
            // maybe it's a .ignore file
            let matchesIgnore = entry.name.match(/(.+)\.dmp.ignore$/);

            if (matchesIgnore) {
              let id = matchesIgnore[1];

              if (UUID_REGEX.test(id)) {
                ignored[id] = true;
              }
            }
          }
        }
      });

      for (let entry in entries) {
        let entryInfo = await entries[entry];

        if (!(entry in ignored) &&
            entryInfo.lastAccessDate > minFileDate) {
          ids.push(entry);
        }
      }
    } catch (ex) {
      Cu.reportError(ex);
      throw ex;
    } finally {
      dirIter.close();
    }

    return ids;
  },

  /**
   * Prune the saved dumps.
   *
   * @return a Promise that is fulfilled when the daved dumps are deleted and
   *         rejected otherwise
   */
  pruneSavedDumps: async function CrashSubmit_pruneSavedDumps() {
    const KEEP = 10;

    let dirIter = null;
    let dirEntries = [];
    let pendingDir = getDir("pending");

    try {
      dirIter = new OS.File.DirectoryIterator(pendingDir);
    } catch (ex) {
      if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
        return [];
      }

      throw ex;
    }

    try {
      await dirIter.forEach(entry => {
        if (!entry.isDir /* is file */) {
          let matches = entry.name.match(/(.+)\.extra$/);

          if (matches) {
            dirEntries.push({
              name: entry.name,
              path: entry.path,
              // dispatch promise instead of blocking iteration on `await`
              infoPromise: OS.File.stat(entry.path)
            });
          }
        }
      });
    } catch (ex) {
      Cu.reportError(ex);
      throw ex;
    } finally {
      dirIter.close();
    }

    dirEntries.sort(async (a, b) => {
      let dateA = (await a.infoPromise).lastModificationDate;
      let dateB = (await b.infoPromise).lastModificationDate;

      if (dateA < dateB) {
        return -1;
      }

      if (dateB < dateA) {
        return 1;
      }

      return 0;
    });

    if (dirEntries.length > KEEP) {
      let toDelete = [];

      for (let i = 0; i < dirEntries.length - KEEP; ++i) {
        let extra = dirEntries[i];
        let matches = extra.leafName.match(/(.+)\.extra$/);

        if (matches) {
          let pathComponents = OS.Path.split(extra.path);
          pathComponents[pathComponents.length - 1] = matches[1];
          let path = OS.Path.join(...pathComponents);

          toDelete.push(extra.path, `${path}.dmp`, `${path}.memory.json.gz`);
        }
      }

      await Promise.all(toDelete.map(path => {
        return OS.File.remove(path, { ignoreAbsent: true });
      }));
    }
  },

  // List of currently active submit objects
  _activeSubmissions: []
};
PK
!<hzJmodules/Credentials.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 client-side key stretching for use in Firefox
 * Accounts account creation and login.
 *
 * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
 */

"use strict";

this.EXPORTED_SYMBOLS = ["Credentials"];

const {utils: Cu, interfaces: Ci} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/utils.js");

const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
const PBKDF2_ROUNDS = 1000;
const STRETCHED_PW_LENGTH_BYTES = 32;
const HKDF_SALT = CommonUtils.hexToBytes("00");
const HKDF_LENGTH = 32;
const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
const HMAC_LENGTH = 32;

// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
// default.
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
try {
  this.LOG_LEVEL =
    Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
    && Services.prefs.getCharPref(PREF_LOG_LEVEL);
} catch (e) {
  this.LOG_LEVEL = Log.Level.Error;
}

var log = Log.repository.getLogger("Identity.FxAccounts");
log.level = LOG_LEVEL;
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));

this.Credentials = Object.freeze({
  /**
   * Make constants accessible to tests
   */
  constants: {
    PROTOCOL_VERSION,
    PBKDF2_ROUNDS,
    STRETCHED_PW_LENGTH_BYTES,
    HKDF_SALT,
    HKDF_LENGTH,
    HMAC_ALGORITHM,
    HMAC_LENGTH,
  },

  /**
   * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord derivation for use as a salt.
   *
   *
   *   @param {String} context  String for use in generating salt
   *
   *   @return {bitArray} the salt
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWord(context) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
  },

  /**
   * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord extended with a name and an email.
   *
   *   @param {String} name The name of the salt
   *   @param {String} email The email of the user.
   *
   *   @return {bitArray} the salt combination with the namespace
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWordExtended(name, email) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ":" + email);
  },

  setup(emailInput, passwordInput, options = {}) {
    return new Promise(resolve => {
      log.debug("setup credentials for " + emailInput);

      let hkdfSalt = options.hkdfSalt || HKDF_SALT;
      let hkdfLength = options.hkdfLength || HKDF_LENGTH;
      let hmacLength = options.hmacLength || HMAC_LENGTH;
      let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
      let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
      let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;

      let result = {};

      let password = CommonUtils.encodeUTF8(passwordInput);
      let salt = this.keyWordExtended("quickStretch", emailInput);

      let runnable = () => {
        let start = Date.now();
        let quickStretchedPW = CryptoUtils.pbkdf2Generate(
            password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);

        result.quickStretchedPW = quickStretchedPW;

        result.authPW =
          CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);

        result.unwrapBKey =
          CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);

        log.debug("Credentials set up after " + (Date.now() - start) + " ms");
        resolve(result);
      }

      Services.tm.dispatchToMainThread(runnable);
      log.debug("Dispatched thread for credentials setup crypto work");
    });
  }
});

PK
!<kdmodules/DNSPacket.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['DNSPacket'];

const { utils: Cu } = Components;

Cu.import('resource://gre/modules/Services.jsm');

Cu.import('resource://gre/modules/DataReader.jsm');
Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSResourceRecord.jsm');

const DEBUG = true;

function debug(msg) {
  Services.console.logStringMessage('DNSPacket: ' + msg);
}

let DNS_PACKET_SECTION_TYPES = [
  'QD', // Question
  'AN', // Answer
  'NS', // Authority
  'AR'  // Additional
];

/**
 * DNS Packet Structure
 * *************************************************
 *
 * Header
 * ======
 *
 * 00                   2-Bytes                   15
 * -------------------------------------------------
 * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
 * -------------------------------------------------
 * |<==================== ID =====================>|
 * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>|
 * |<================== QDCOUNT ==================>|
 * |<================== ANCOUNT ==================>|
 * |<================== NSCOUNT ==================>|
 * |<================== ARCOUNT ==================>|
 * -------------------------------------------------
 *
 * ID:        2-Bytes
 * FLAGS:     2-Bytes
 *  - QR:     1-Bit
 *  - OP:     4-Bits
 *  - AA:     1-Bit
 *  - TC:     1-Bit
 *  - RD:     1-Bit
 *  - RA:     1-Bit
 *  - UN:     1-Bit
 *  - AD:     1-Bit
 *  - CD:     1-Bit
 *  - RC:     4-Bits
 * QDCOUNT:   2-Bytes
 * ANCOUNT:   2-Bytes
 * NSCOUNT:   2-Bytes
 * ARCOUNT:   2-Bytes
 *
 *
 * Data
 * ====
 *
 * 00                   2-Bytes                   15
 * -------------------------------------------------
 * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
 * -------------------------------------------------
 * |<???=============== QD[...] ===============???>|
 * |<???=============== AN[...] ===============???>|
 * |<???=============== NS[...] ===============???>|
 * |<???=============== AR[...] ===============???>|
 * -------------------------------------------------
 *
 * QD:        ??-Bytes
 * AN:        ??-Bytes
 * NS:        ??-Bytes
 * AR:        ??-Bytes
 *
 *
 * Question Record
 * ===============
 *
 * 00                   2-Bytes                   15
 * -------------------------------------------------
 * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
 * -------------------------------------------------
 * |<???================ NAME =================???>|
 * |<=================== TYPE ====================>|
 * |<=================== CLASS ===================>|
 * -------------------------------------------------
 *
 * NAME:      ??-Bytes
 * TYPE:      2-Bytes
 * CLASS:     2-Bytes
 *
 *
 * Resource Record
 * ===============
 *
 * 00                   4-Bytes                   31
 * -------------------------------------------------
 * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30|
 * -------------------------------------------------
 * |<???================ NAME =================???>|
 * |<======= TYPE ========>|<======= CLASS =======>|
 * |<==================== TTL ====================>|
 * |<====== DATALEN ======>|<???==== DATA =====???>|
 * -------------------------------------------------
 *
 * NAME:      ??-Bytes
 * TYPE:      2-Bytes
 * CLASS:     2-Bytes
 * DATALEN:   2-Bytes
 * DATA:      ??-Bytes (Specified By DATALEN)
 */
class DNSPacket {
  constructor() {
    this._flags = _valueToFlags(0x0000);
    this._records = {};

    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      this._records[sectionType] = [];
    });
  }

  static parse(data) {
    let reader = new DataReader(data);
    if (reader.getValue(2) !== 0x0000) {
      throw new Error('Packet must start with 0x0000');
    }

    let packet = new DNSPacket();
    packet._flags = _valueToFlags(reader.getValue(2));

    let recordCounts = {};

    // Parse the record counts.
    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      recordCounts[sectionType] = reader.getValue(2);
    });

    // Parse the actual records.
    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      let recordCount = recordCounts[sectionType];
      for (let i = 0; i < recordCount; i++) {
        if (sectionType === 'QD') {
          packet.addRecord(sectionType,
              DNSRecord.parseFromPacketReader(reader));
        }

        else {
          packet.addRecord(sectionType,
              DNSResourceRecord.parseFromPacketReader(reader));
        }
      }
    });

    if (!reader.eof) {
      DEBUG && debug('Did not complete parsing packet data');
    }

    return packet;
  }

  getFlag(flag) {
    return this._flags[flag];
  }

  setFlag(flag, value) {
    this._flags[flag] = value;
  }

  addRecord(sectionType, record) {
    this._records[sectionType].push(record);
  }

  getRecords(sectionTypes, recordType) {
    let records = [];

    sectionTypes.forEach((sectionType) => {
      records = records.concat(this._records[sectionType]);
    });

    if (!recordType) {
      return records;
    }

    return records.filter(r => r.recordType === recordType);
  }

  serialize() {
    let writer = new DataWriter();

    // Write leading 0x0000 (2 bytes)
    writer.putValue(0x0000, 2);

    // Write `flags` (2 bytes)
    writer.putValue(_flagsToValue(this._flags), 2);

    // Write lengths of record sections (2 bytes each)
    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      writer.putValue(this._records[sectionType].length, 2);
    });

    // Write records
    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      this._records[sectionType].forEach((record) => {
        writer.putBytes(record.serialize());
      });
    });

    return writer.data;
  }

  toJSON() {
    return JSON.stringify(this.toJSONObject());
  }

  toJSONObject() {
    let result = {flags: this._flags};
    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
      result[sectionType] = [];

      let records = this._records[sectionType];
      records.forEach((record) => {
        result[sectionType].push(record.toJSONObject());
      });
    });

    return result;
  }
}

/**
 * @private
 */
function _valueToFlags(value) {
  return {
    QR: (value & 0x8000) >> 15,
    OP: (value & 0x7800) >> 11,
    AA: (value & 0x0400) >> 10,
    TC: (value & 0x0200) >>  9,
    RD: (value & 0x0100) >>  8,
    RA: (value & 0x0080) >>  7,
    UN: (value & 0x0040) >>  6,
    AD: (value & 0x0020) >>  5,
    CD: (value & 0x0010) >>  4,
    RC: (value & 0x000f) >>  0
  };
}

/**
 * @private
 */
function _flagsToValue(flags) {
  let value = 0x0000;

  value += flags.QR & 0x01;

  value <<= 4;
  value += flags.OP & 0x0f;

  value <<= 1;
  value += flags.AA & 0x01;

  value <<= 1;
  value += flags.TC & 0x01;

  value <<= 1;
  value += flags.RD & 0x01;

  value <<= 1;
  value += flags.RA & 0x01;

  value <<= 1;
  value += flags.UN & 0x01;

  value <<= 1;
  value += flags.AD & 0x01;

  value <<= 1;
  value += flags.CD & 0x01;

  value <<= 4;
  value += flags.RC & 0x0f;

  return value;
}
PK
!<a44modules/DNSRecord.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['DNSRecord'];

const { utils: Cu } = Components;

Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');

class DNSRecord {
  constructor(properties = {}) {
    this.name       = properties.name       || '';
    this.recordType = properties.recordType || DNS_RECORD_TYPES.ANY;
    this.classCode  = properties.classCode  || DNS_CLASS_CODES.IN;
    this.cacheFlush = properties.cacheFlush || false;
  }

  static parseFromPacketReader(reader) {
    let name       = reader.getLabel();
    let recordType = reader.getValue(2);
    let classCode  = reader.getValue(2);
    let cacheFlush = (classCode & 0x8000) ? true : false;
    classCode &= 0xff;

    return new this({
      name: name,
      recordType: recordType,
      classCode: classCode,
      cacheFlush: cacheFlush
    });
  }

  serialize() {
    let writer = new DataWriter();

    // Write `name` (ends with trailing 0x00 byte)
    writer.putLabel(this.name);

    // Write `recordType` (2 bytes)
    writer.putValue(this.recordType, 2);

    // Write `classCode` (2 bytes)
    let classCode = this.classCode;
    if (this.cacheFlush) {
      classCode |= 0x8000;
    }
    writer.putValue(classCode, 2);

    return writer.data;
  }

  toJSON() {
    return JSON.stringify(this.toJSONObject());
  }

  toJSONObject() {
    return {
      name: this.name,
      recordType: this.recordType,
      classCode: this.classCode,
      cacheFlush: this.cacheFlush
    };
  }
}
PK
!<gkmodules/DNSResourceRecord.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['DNSResourceRecord'];

const { utils: Cu } = Components;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DataReader.jsm');
Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');

function debug(msg) {
  Services.console.logStringMessage('MulticastDNS: ' + msg);
}

const DNS_RESOURCE_RECORD_DEFAULT_TTL = 120; // 120 seconds

class DNSResourceRecord extends DNSRecord {
  constructor(properties = {}) {
    super(properties);

    this.ttl  = properties.ttl  || DNS_RESOURCE_RECORD_DEFAULT_TTL;
    this.data = properties.data || {};
  }

  static parseFromPacketReader(reader) {
    let record = super.parseFromPacketReader(reader);

    let ttl        = reader.getValue(4);
    let recordData = reader.getBytes(reader.getValue(2));
    let packetData = reader.data;

    let data;

    switch (record.recordType) {
      case DNS_RECORD_TYPES.A:
        data = _parseA(recordData, packetData);
        break;
      case DNS_RECORD_TYPES.PTR:
        data = _parsePTR(recordData, packetData);
        break;
      case DNS_RECORD_TYPES.TXT:
        data = _parseTXT(recordData, packetData);
        break;
      case DNS_RECORD_TYPES.SRV:
        data = _parseSRV(recordData, packetData);
        break;
      default:
        data = null;
        break;
    }

    record.ttl  = ttl;
    record.data = data;

    return record;
  }

  serialize() {
    let writer = new DataWriter(super.serialize());

    // Write `ttl` (4 bytes)
    writer.putValue(this.ttl, 4);

    let data;

    switch (this.recordType) {
      case DNS_RECORD_TYPES.A:
        data = _serializeA(this.data);
        break;
      case DNS_RECORD_TYPES.PTR:
        data = _serializePTR(this.data);
        break;
      case DNS_RECORD_TYPES.TXT:
        data = _serializeTXT(this.data);
        break;
      case DNS_RECORD_TYPES.SRV:
        data = _serializeSRV(this.data);
        break;
      default:
        data = new Uint8Array();
        break;
    }

    // Write `data` length.
    writer.putValue(data.length, 2);

    // Write `data` (ends with trailing 0x00 byte)
    writer.putBytes(data);

    return writer.data;
  }

  toJSON() {
    return JSON.stringify(this.toJSONObject());
  }

  toJSONObject() {
    let result = super.toJSONObject();
    result.ttl = this.ttl;
    result.data = this.data;
    return result;
  }
}

/**
 * @private
 */
function _parseA(recordData, packetData) {
  let reader = new DataReader(recordData);

  let parts = [];
  for (let i = 0; i < 4; i++) {
    parts.push(reader.getValue(1));
  }

  return parts.join('.');
}

/**
 * @private
 */
function _parsePTR(recordData, packetData) {
  let reader = new DataReader(recordData);

  return reader.getLabel(packetData);
}

/**
 * @private
 */
function _parseTXT(recordData, packetData) {
  let reader = new DataReader(recordData);

  let result = {};

  let label = reader.getLabel(packetData);
  if (label.length > 0) {
    let parts = label.split('.');
    parts.forEach((part) => {
      let [name] = part.split('=', 1);
      let value = part.substr(name.length + 1);
      result[name] = value;
    });
  }

  return result;
}

/**
 * @private
 */
function _parseSRV(recordData, packetData) {
  let reader = new DataReader(recordData);

  let priority = reader.getValue(2);
  let weight   = reader.getValue(2);
  let port     = reader.getValue(2);
  let target   = reader.getLabel(packetData);

  return { priority, weight, port, target };
}

/**
 * @private
 */
function _serializeA(data) {
  let writer = new DataWriter();

  let parts = data.split('.');
  for (let i = 0; i < 4; i++) {
    writer.putValue(parseInt(parts[i], 10) || 0);
  }

  return writer.data;
}

/**
 * @private
 */
function _serializePTR(data) {
  let writer = new DataWriter();

  writer.putLabel(data);

  return writer.data;
}

/**
 * @private
 */
function _serializeTXT(data) {
  let writer = new DataWriter();

  for (let name in data) {
    writer.putLengthString(name + '=' + data[name]);
  }

  return writer.data;
}

/**
 * @private
 */
function _serializeSRV(data) {
  let writer = new DataWriter();

  writer.putValue(data.priority || 0, 2);
  writer.putValue(data.weight   || 0, 2);
  writer.putValue(data.port     || 0, 2);
  writer.putLabel(data.target);

  return writer.data;
}
PK
!<=^^modules/DNSTypes.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = [
  'DNS_QUERY_RESPONSE_CODES',
  'DNS_AUTHORITATIVE_ANSWER_CODES',
  'DNS_CLASS_CODES',
  'DNS_RECORD_TYPES'
];

let DNS_QUERY_RESPONSE_CODES = {
  QUERY           : 0,      // RFC 1035 - Query
  RESPONSE        : 1       // RFC 1035 - Reponse
};

let DNS_AUTHORITATIVE_ANSWER_CODES = {
  NO              : 0,      // RFC 1035 - Not Authoritative
  YES             : 1       // RFC 1035 - Is Authoritative
};

let DNS_CLASS_CODES = {
  IN              : 0x01,   // RFC 1035 - Internet
  CS              : 0x02,   // RFC 1035 - CSNET
  CH              : 0x03,   // RFC 1035 - CHAOS
  HS              : 0x04,   // RFC 1035 - Hesiod
  NONE            : 0xfe,   // RFC 2136 - None
  ANY             : 0xff,   // RFC 1035 - Any
};

let DNS_RECORD_TYPES = {
  SIGZERO         : 0,      // RFC 2931
  A               : 1,      // RFC 1035
  NS              : 2,      // RFC 1035
  MD              : 3,      // RFC 1035
  MF              : 4,      // RFC 1035
  CNAME           : 5,      // RFC 1035
  SOA             : 6,      // RFC 1035
  MB              : 7,      // RFC 1035
  MG              : 8,      // RFC 1035
  MR              : 9,      // RFC 1035
  NULL            : 10,     // RFC 1035
  WKS             : 11,     // RFC 1035
  PTR             : 12,     // RFC 1035
  HINFO           : 13,     // RFC 1035
  MINFO           : 14,     // RFC 1035
  MX              : 15,     // RFC 1035
  TXT             : 16,     // RFC 1035
  RP              : 17,     // RFC 1183
  AFSDB           : 18,     // RFC 1183
  X25             : 19,     // RFC 1183
  ISDN            : 20,     // RFC 1183
  RT              : 21,     // RFC 1183
  NSAP            : 22,     // RFC 1706
  NSAP_PTR        : 23,     // RFC 1348
  SIG             : 24,     // RFC 2535
  KEY             : 25,     // RFC 2535
  PX              : 26,     // RFC 2163
  GPOS            : 27,     // RFC 1712
  AAAA            : 28,     // RFC 1886
  LOC             : 29,     // RFC 1876
  NXT             : 30,     // RFC 2535
  EID             : 31,     // RFC ????
  NIMLOC          : 32,     // RFC ????
  SRV             : 33,     // RFC 2052
  ATMA            : 34,     // RFC ????
  NAPTR           : 35,     // RFC 2168
  KX              : 36,     // RFC 2230
  CERT            : 37,     // RFC 2538
  DNAME           : 39,     // RFC 2672
  OPT             : 41,     // RFC 2671
  APL             : 42,     // RFC 3123
  DS              : 43,     // RFC 4034
  SSHFP           : 44,     // RFC 4255
  IPSECKEY        : 45,     // RFC 4025
  RRSIG           : 46,     // RFC 4034
  NSEC            : 47,     // RFC 4034
  DNSKEY          : 48,     // RFC 4034
  DHCID           : 49,     // RFC 4701
  NSEC3           : 50,     // RFC ????
  NSEC3PARAM      : 51,     // RFC ????
  HIP             : 55,     // RFC 5205
  SPF             : 99,     // RFC 4408
  UINFO           : 100,    // RFC ????
  UID             : 101,    // RFC ????
  GID             : 102,    // RFC ????
  UNSPEC          : 103,    // RFC ????
  TKEY            : 249,    // RFC 2930
  TSIG            : 250,    // RFC 2931
  IXFR            : 251,    // RFC 1995
  AXFR            : 252,    // RFC 1035
  MAILB           : 253,    // RFC 1035
  MAILA           : 254,    // RFC 1035
  ANY             : 255,    // RFC 1035
  DLV             : 32769   // RFC 4431
};
PK
!<_B''modules/DOMRequestHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Helper object for APIs that deal with DOMRequests and Promises.
 * It allows objects inheriting from it to create and keep track of DOMRequests
 * and Promises objects in the common scenario where requests are created in
 * the child, handed out to content and delivered to the parent within an async
 * message (containing the identifiers of these requests). The parent may send
 * messages back as answers to different requests and the child will use this
 * helper to get the right request object. This helper also takes care of
 * releasing the requests objects when the window goes out of scope.
 *
 * DOMRequestIPCHelper also deals with message listeners, allowing to add them
 * to the child side of frame and process message manager and removing them
 * when needed.
 */
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                   "@mozilla.org/childprocessmessagemanager;1",
                                   "nsIMessageListenerManager");

this.DOMRequestIpcHelper = function DOMRequestIpcHelper() {
  // _listeners keeps a list of messages for which we added a listener and the
  // kind of listener that we added (strong or weak). It's an object of this
  // form:
  //  {
  //    "message1": true,
  //    "messagen": false
  //  }
  //
  // where each property is the name of the message and its value is a boolean
  // that indicates if the listener is weak or not.
  this._listeners = null;
  this._requests = null;
  this._window = null;
}

DOMRequestIpcHelper.prototype = {
  /**
   * An object which "inherits" from DOMRequestIpcHelper and declares its own
   * queryInterface method MUST implement Ci.nsISupportsWeakReference.
   */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                         Ci.nsIObserver]),

   /**
   *  'aMessages' is expected to be an array of either:
   *  - objects of this form:
   *    {
   *      name: "messageName",
   *      weakRef: false
   *    }
   *    where 'name' is the message identifier and 'weakRef' a boolean
   *    indicating if the listener should be a weak referred one or not.
   *
   *  - or only strings containing the message name, in which case the listener
   *    will be added as a strong reference by default.
   */
  addMessageListeners: function(aMessages) {
    if (!aMessages) {
      return;
    }

    if (!this._listeners) {
      this._listeners = {};
    }

    if (!Array.isArray(aMessages)) {
      aMessages = [aMessages];
    }

    aMessages.forEach((aMsg) => {
      let name = aMsg.name || aMsg;
      // If the listener is already set and it is of the same type we just
      // increase the count and bail out. If it is not of the same type,
      // we throw an exception.
      if (this._listeners[name] != undefined) {
        if (!!aMsg.weakRef == this._listeners[name].weakRef) {
          this._listeners[name].count++;
          return;
        } else {
          throw Cr.NS_ERROR_FAILURE;
        }
      }

      aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
                   : cpmm.addMessageListener(name, this);
      this._listeners[name] = {
        weakRef: !!aMsg.weakRef,
        count: 1
      };
    });
  },

  /**
   * 'aMessages' is expected to be a string or an array of strings containing
   * the message names of the listeners to be removed.
   */
  removeMessageListeners: function(aMessages) {
    if (!this._listeners || !aMessages) {
      return;
    }

    if (!Array.isArray(aMessages)) {
      aMessages = [aMessages];
    }

    aMessages.forEach((aName) => {
      if (this._listeners[aName] == undefined) {
        return;
      }

      // Only remove the listener really when we don't have anybody that could
      // be waiting on a message.
      if (!--this._listeners[aName].count) {
        this._listeners[aName].weakRef ?
            cpmm.removeWeakMessageListener(aName, this)
          : cpmm.removeMessageListener(aName, this);
        delete this._listeners[aName];
      }
    });
  },

  /**
   * Initialize the helper adding the corresponding listeners to the messages
   * provided as the second parameter.
   *
   * 'aMessages' is expected to be an array of either:
   *
   *  - objects of this form:
   *    {
   *      name: 'messageName',
   *      weakRef: false
   *    }
   *    where 'name' is the message identifier and 'weakRef' a boolean
   *    indicating if the listener should be a weak referred one or not.
   *
   *  - or only strings containing the message name, in which case the listener
   *    will be added as a strong referred one by default.
   */
  initDOMRequestHelper: function(aWindow, aMessages) {
    // Query our required interfaces to force a fast fail if they are not
    // provided. These calls will throw if the interface is not available.
    this.QueryInterface(Ci.nsISupportsWeakReference);
    this.QueryInterface(Ci.nsIObserver);

    if (aMessages) {
      this.addMessageListeners(aMessages);
    }

    this._id = this._getRandomId();

    this._window = aWindow;
    if (this._window) {
      // We don't use this.innerWindowID, but other classes rely on it.
      let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
      this.innerWindowID = util.currentInnerWindowID;
    }

    this._destroyed = false;

    Services.obs.addObserver(this, "inner-window-destroyed",
                             /* weak-ref */ true);
  },

  destroyDOMRequestHelper: function() {
    if (this._destroyed) {
      return;
    }

    this._destroyed = true;

    Services.obs.removeObserver(this, "inner-window-destroyed");

    if (this._listeners) {
      Object.keys(this._listeners).forEach((aName) => {
        this._listeners[aName].weakRef ? cpmm.removeWeakMessageListener(aName, this)
                                       : cpmm.removeMessageListener(aName, this);
      });
    }

    this._listeners = null;
    this._requests = null;

    // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
    if (this.uninit) {
      this.uninit();
    }

    this._window = null;
  },

  observe: function(aSubject, aTopic, aData) {
    if (aTopic !== "inner-window-destroyed") {
      return;
    }

    let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
    if (wId != this.innerWindowID) {
      return;
    }

    this.destroyDOMRequestHelper();
  },

  getRequestId: function(aRequest) {
    if (!this._requests) {
      this._requests = {};
    }

    let id = "id" + this._getRandomId();
    this._requests[id] = aRequest;
    return id;
  },

  getPromiseResolverId: function(aPromiseResolver) {
    // Delegates to getRequest() since the lookup table is agnostic about
    // storage.
    return this.getRequestId(aPromiseResolver);
  },

  getRequest: function(aId) {
    if (this._requests && this._requests[aId]) {
      return this._requests[aId];
    }
  },

  getPromiseResolver: function(aId) {
    // Delegates to getRequest() since the lookup table is agnostic about
    // storage.
    return this.getRequest(aId);
  },

  removeRequest: function(aId) {
    if (this._requests && this._requests[aId]) {
      delete this._requests[aId];
    }
  },

  removePromiseResolver: function(aId) {
    // Delegates to getRequest() since the lookup table is agnostic about
    // storage.
    this.removeRequest(aId);
  },

  takeRequest: function(aId) {
    if (!this._requests || !this._requests[aId]) {
      return null;
    }
    let request = this._requests[aId];
    delete this._requests[aId];
    return request;
  },

  takePromiseResolver: function(aId) {
    // Delegates to getRequest() since the lookup table is agnostic about
    // storage.
    return this.takeRequest(aId);
  },

  _getRandomId: function() {
    return Cc["@mozilla.org/uuid-generator;1"]
             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
  },

  createRequest: function() {
    // If we don't have a valid window object, throw.
    if (!this._window) {
      Cu.reportError("DOMRequestHelper trying to create a DOMRequest without a valid window, failing.");
      throw Cr.NS_ERROR_FAILURE;
    }
    return Services.DOMRequest.createRequest(this._window);
  },

  /**
   * createPromise() creates a new Promise, with `aPromiseInit` as the
   * PromiseInit callback. The promise constructor is obtained from the
   * reference to window owned by this DOMRequestIPCHelper.
   */
  createPromise: function(aPromiseInit) {
    // If we don't have a valid window object, throw.
    if (!this._window) {
      Cu.reportError("DOMRequestHelper trying to create a Promise without a valid window, failing.");
      throw Cr.NS_ERROR_FAILURE;
    }
    return new this._window.Promise(aPromiseInit);
  },

  /**
   * createPromiseWithId() creates a new Promise, accepting a callback
   * which is immediately called with the generated resolverId.
   */
  createPromiseWithId: function(aCallback) {
    return this.createPromise((aResolve, aReject) => {
      let resolverId = this.getPromiseResolverId({ resolve: aResolve, reject: aReject });
      aCallback(resolverId);
    });
  },

  forEachRequest: function(aCallback) {
    if (!this._requests) {
      return;
    }

    Object.keys(this._requests).forEach((aKey) => {
      if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
        aCallback(aKey);
      }
    });
  },

  forEachPromiseResolver: function(aCallback) {
    if (!this._requests) {
      return;
    }

    Object.keys(this._requests).forEach((aKey) => {
      if ("resolve" in this.getPromiseResolver(aKey) &&
          "reject" in this.getPromiseResolver(aKey)) {
        aCallback(aKey);
      }
    });
  },
}
PK
!<pUmodules/DataReader.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['DataReader'];

class DataReader {
  // `data` is `Uint8Array`
  constructor(data, startByte = 0) {
    this._data = data;
    this._cursor = startByte;
  }

  get buffer() {
    return this._data.buffer;
  }

  get data() {
    return this._data;
  }

  get eof() {
    return this._cursor >= this._data.length;
  }

  getBytes(length = 1) {
    if (!length) {
      return new Uint8Array();
    }

    let end = this._cursor + length;
    if (end > this._data.length) {
      return new Uint8Array();
    }

    let uint8Array = new Uint8Array(this.buffer.slice(this._cursor, end));
    this._cursor += length;

    return uint8Array;
  }

  getString(length) {
    let uint8Array = this.getBytes(length);
    return _uint8ArrayToString(uint8Array);
  }

  getValue(length) {
    let uint8Array = this.getBytes(length);
    return _uint8ArrayToValue(uint8Array);
  }

  getLabel(decompressData) {
    let parts = [];
    let partLength;

    while ((partLength = this.getValue(1))) {
      // If a length has been specified instead of a pointer,
      // read the string of the specified length.
      if (partLength !== 0xc0) {
        parts.push(this.getString(partLength));
        continue;
      }

      // TODO: Handle case where we have a pointer to the label
      parts.push(String.fromCharCode(0xc0) + this.getString(1));
      break;
    }

    let label = parts.join('.');

    return _decompressLabel(label, decompressData || this._data);
  }
}

/**
 * @private
 */
function _uint8ArrayToValue(uint8Array) {
  let length = uint8Array.length;
  if (length === 0) {
    return null;
  }

  let value = 0;
  for (let i = 0; i < length; i++) {
    value = value << 8;
    value += uint8Array[i];
  }

  return value;
}

/**
 * @private
 */
function _uint8ArrayToString(uint8Array) {
  let length = uint8Array.length;
  if (length === 0) {
    return '';
  }

  let results = [];
  for (let i = 0; i < length; i += 1024) {
    results.push(String.fromCharCode.apply(null, uint8Array.subarray(i, i + 1024)));
  }

  return results.join('');
}

/**
 * @private
 */
function _decompressLabel(label, decompressData) {
  let result = '';

  for (let i = 0, length = label.length; i < length; i++) {
    if (label.charCodeAt(i) !== 0xc0) {
      result += label.charAt(i);
      continue;
    }

    i++;

    let reader = new DataReader(decompressData, label.charCodeAt(i));
    result += _decompressLabel(reader.getLabel(), decompressData);
  }

  return result;
}
PK
!<T		modules/DataWriter.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['DataWriter'];

class DataWriter {
  constructor(data, maxBytes = 512) {
    if (typeof data === 'number') {
      maxBytes = data;
      data = undefined;
    }

    this._buffer = new ArrayBuffer(maxBytes);
    this._data = new Uint8Array(this._buffer);
    this._cursor = 0;

    if (data) {
      this.putBytes(data);
    }
  }

  get buffer() {
    return this._buffer.slice(0, this._cursor);
  }

  get data() {
    return new Uint8Array(this.buffer);
  }

  // `data` is `Uint8Array`
  putBytes(data) {
    if (this._cursor + data.length > this._data.length) {
      throw new Error('DataWriter buffer is exceeded');
    }

    for (let i = 0, length = data.length; i < length; i++) {
      this._data[this._cursor] = data[i];
      this._cursor++;
    }
  }

  putByte(byte) {
    if (this._cursor + 1 > this._data.length) {
      throw new Error('DataWriter buffer is exceeded');
    }

    this._data[this._cursor] = byte
    this._cursor++;
  }

  putValue(value, length) {
    length = length || 1;
    if (length == 1) {
      this.putByte(value);
    } else {
      this.putBytes(_valueToUint8Array(value, length));
    }
  }

  putLabel(label) {
    // Eliminate any trailing '.'s in the label (valid in text representation).
    label = label.replace(/\.$/, '');
    let parts = label.split('.');
    parts.forEach((part) => {
      this.putLengthString(part);
    });
    this.putValue(0);
  }

  putLengthString(string) {
    if (string.length > 0xff) {
        throw new Error("String too long.");
    }
    this.putValue(string.length);
    for (let i = 0; i < string.length; i++) {
      this.putValue(string.charCodeAt(i));
    }
  }
}

/**
 * @private
 */
function _valueToUint8Array(value, length) {
  let arrayBuffer = new ArrayBuffer(length);
  let uint8Array = new Uint8Array(arrayBuffer);
  for (let i = length - 1; i >= 0; i--) {
    uint8Array[i] = value & 0xff;
    value = value >> 8;
  }

  return uint8Array;
}
PK
!<p1 modules/DateTimePickerHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 DEBUG = false;
function debug(aStr) {
  if (DEBUG) {
    dump("-*- DateTimePickerHelper: " + aStr + "\n");
  }
}

this.EXPORTED_SYMBOLS = [
  "DateTimePickerHelper"
];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

/*
 * DateTimePickerHelper receives message from content side (input box) and
 * is reposible for opening, closing and updating the picker. Similary,
 * DateTimePickerHelper listens for picker's events and notifies the content
 * side (input box) about them.
 */
this.DateTimePickerHelper = {
  picker: null,
  weakBrowser: null,

  MESSAGES: [
    "FormDateTime:OpenPicker",
    "FormDateTime:ClosePicker",
    "FormDateTime:UpdatePicker"
  ],

  init() {
    for (let msg of this.MESSAGES) {
      Services.mm.addMessageListener(msg, this);
    }
  },

  uninit() {
    for (let msg of this.MESSAGES) {
      Services.mm.removeMessageListener(msg, this);
    }
  },

  // nsIMessageListener
  receiveMessage(aMessage) {
    debug("receiveMessage: " + aMessage.name);
    switch (aMessage.name) {
      case "FormDateTime:OpenPicker": {
        this.showPicker(aMessage.target, aMessage.data);
        break;
      }
      case "FormDateTime:ClosePicker": {
        if (!this.picker) {
          return;
        }
        this.picker.closePicker();
        break;
      }
      case "FormDateTime:UpdatePicker": {
        this.picker.setPopupValue(aMessage.data);
        break;
      }
      default:
        break;
    }
  },

  // nsIDOMEventListener
  handleEvent(aEvent) {
    debug("handleEvent: " + aEvent.type);
    switch (aEvent.type) {
      case "DateTimePickerValueChanged": {
        this.updateInputBoxValue(aEvent);
        break;
      }
      case "popuphidden": {
        let browser = this.weakBrowser ? this.weakBrowser.get() : null;
        if (browser) {
          browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
        }
        this.close();
        break;
      }
      default:
        break;
    }
  },

  // Called when picker value has changed, notify input box about it.
  updateInputBoxValue(aEvent) {
    let browser = this.weakBrowser ? this.weakBrowser.get() : null;
    if (browser) {
      browser.messageManager.sendAsyncMessage(
        "FormDateTime:PickerValueChanged", aEvent.detail);
    }
  },

  // Get picker from browser and show it anchored to the input box.
  async showPicker(aBrowser, aData) {
    let rect = aData.rect;
    let type = aData.type;
    let detail = aData.detail;

    this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
    this._anchor.left = rect.left;
    this._anchor.top = rect.top;
    this._anchor.width = rect.width;
    this._anchor.height = rect.height;
    this._anchor.hidden = false;

    debug("Opening picker with details: " + JSON.stringify(detail));

    let window = aBrowser.ownerGlobal;
    let tabbrowser = window.gBrowser;
    if (Services.focus.activeWindow != window ||
        tabbrowser.selectedBrowser != aBrowser) {
      // We were sent a message from a window or tab that went into the
      // background, so we'll ignore it for now.
      return;
    }

    this.weakBrowser = Cu.getWeakReference(aBrowser);
    this.picker = aBrowser.dateTimePicker;
    if (!this.picker) {
      debug("aBrowser.dateTimePicker not found, exiting now.");
      return;
    }
    // The datetimepopup binding is only attached when it is needed.
    // Check if openPicker method is present to determine if binding has
    // been attached. If not, attach the binding first before calling it.
    if (!this.picker.openPicker) {
      let bindingPromise = new Promise(resolve => {
        this.picker.addEventListener("DateTimePickerBindingReady",
                                     resolve, {once: true});
      });
      this.picker.setAttribute("active", true);
      await bindingPromise;
    }
    // The arrow panel needs an anchor to work. The popupAnchor (this._anchor)
    // is a transparent div that the arrow can point to.
    this.picker.openPicker(type, this._anchor, detail);

    this.addPickerListeners();
  },

  // Picker is closed, do some cleanup.
  close() {
    this.removePickerListeners();
    this.picker = null;
    this.weakBrowser = null;
    this._anchor.hidden = true;
  },

  // Listen to picker's event.
  addPickerListeners() {
    if (!this.picker) {
      return;
    }
    this.picker.addEventListener("popuphidden", this);
    this.picker.addEventListener("DateTimePickerValueChanged", this);
  },

  // Stop listening to picker's event.
  removePickerListeners() {
    if (!this.picker) {
      return;
    }
    this.picker.removeEventListener("popuphidden", this);
    this.picker.removeEventListener("DateTimePickerValueChanged", this);
  },
};
PK
!<nd\
((modules/DeferredSave.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/osfile.jsm");
/* globals OS*/
Cu.import("resource://gre/modules/PromiseUtils.jsm");

// Make it possible to mock out timers for testing
var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

this.EXPORTED_SYMBOLS = ["DeferredSave"];

// If delay parameter is not provided, default is 50 milliseconds.
const DEFAULT_SAVE_DELAY_MS = 50;

Cu.import("resource://gre/modules/Log.jsm");
// Configure a logger at the parent 'DeferredSave' level to format
// messages for all the modules under DeferredSave.*
const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
var formatter = new Log.BasicFormatter();
// Set parent logger (and its children) to append to
// the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
// Set parent logger (and its children) to
// also append to standard out
parentLogger.addAppender(new Log.DumpAppender(formatter));

// Provide the ability to enable/disable logging
// messages at runtime.
// If the "extensions.logging.enabled" preference is
// missing or 'false', messages at the WARNING and higher
// severity should be logged to the JS console and standard error.
// If "extensions.logging.enabled" is set to 'true', messages
// at DEBUG and higher should go to JS console and standard error.
Cu.import("resource://gre/modules/Services.jsm");

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");

const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";

/**
* Preference listener which listens for a change in the
* "extensions.logging.enabled" preference and changes the logging level of the
* parent 'addons' level logger accordingly.
*/
var PrefObserver = {
 init() {
   Services.prefs.addObserver(PREF_LOGGING_ENABLED, this);
   Services.obs.addObserver(this, "xpcom-shutdown");
   this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
 },

 observe(aSubject, aTopic, aData) {
   if (aTopic == "xpcom-shutdown") {
     Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
     Services.obs.removeObserver(this, "xpcom-shutdown");
   } else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
     let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
     if (debugLogEnabled) {
       parentLogger.level = Log.Level.Debug;
     } else {
       parentLogger.level = Log.Level.Warn;
     }
   }
 }
};

PrefObserver.init();

/**
 * A module to manage deferred, asynchronous writing of data files
 * to disk. Writing is deferred by waiting for a specified delay after
 * a request to save the data, before beginning to write. If more than
 * one save request is received during the delay, all requests are
 * fulfilled by a single write.
 *
 * @constructor
 * @param {string} aPath
 *        String representing the full path of the file where the data
 *        is to be written.
 * @param {function} aDataProvider
 *        Callback function that takes no argument and returns the data to
 *        be written. If aDataProvider returns an ArrayBufferView, the
 *        bytes it contains are written to the file as is.
 *        If aDataProvider returns a String the data are UTF-8 encoded
 *        and then written to the file.
 * @param {object | integer} [aOptions]
 *        The delay in milliseconds between the first saveChanges() call
 *        that marks the data as needing to be saved, and when the DeferredSave
 *        begins writing the data to disk. Default 50 milliseconds.
 *
 *        Or, an options object containing:
 *         - delay: A delay in milliseconds.
 *         - finalizeAt: An AsyncShutdown blocker during which to
 *           finalize any pending writes.
 */
this.DeferredSave = function(aPath, aDataProvider, aOptions = {}) {
  if (typeof aOptions == "number") {
    aOptions = {delay: aOptions};
  }

  // Create a new logger (child of 'DeferredSave' logger)
  // for use by this particular instance of DeferredSave object
  let leafName = OS.Path.basename(aPath);
  let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
  this.logger = Log.repository.getLogger(logger_id);

  // @type {Deferred|null}, null when no data needs to be written
  // @resolves with the result of OS.File.writeAtomic when all writes complete
  // @rejects with the error from OS.File.writeAtomic if the write fails,
  //          or with the error from aDataProvider() if that throws.
  this._pending = null;

  // @type {Promise}, completes when the in-progress write (if any) completes,
  //       kept as a resolved promise at other times to simplify logic.
  //       Because _deferredSave() always uses _writing.then() to execute
  //       its next action, we don't need a special case for whether a write
  //       is in progress - if the previous write is complete (and the _writing
  //       promise is already resolved/rejected), _writing.then() starts
  //       the next action immediately.
  //
  // @resolves with the result of OS.File.writeAtomic
  // @rejects with the error from OS.File.writeAtomic
  this._writing = Promise.resolve(0);

  // Are we currently waiting for a write to complete
  this.writeInProgress = false;

  this._path = aPath;
  this._dataProvider = aDataProvider;

  this._timer = null;

  // Some counters for telemetry
  // The total number of times the file was written
  this.totalSaves = 0;

  // The number of times the data became dirty while
  // another save was in progress
  this.overlappedSaves = 0;

  // Error returned by the most recent write (if any)
  this._lastError = null;

  if (aOptions.delay && (aOptions.delay > 0))
    this._delay = aOptions.delay;
  else
    this._delay = DEFAULT_SAVE_DELAY_MS;

  this._finalizeAt = aOptions.finalizeAt || AsyncShutdown.profileBeforeChange;
  this._finalize = this._finalize.bind(this);
  this._finalizeAt.addBlocker(`DeferredSave: writing data to ${aPath}`,
                              this._finalize);
}

this.DeferredSave.prototype = {
  get dirty() {
    return this._pending || this.writeInProgress;
  },

  get lastError() {
    return this._lastError;
  },

  get path() {
    return this._path;
  },

  // Start the pending timer if data is dirty
  _startTimer() {
    if (!this._pending) {
      return;
    }

      this.logger.debug("Starting timer");
    if (!this._timer)
      this._timer = MakeTimer();
    this._timer.initWithCallback(() => this._deferredSave(),
                                 this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /**
   * Mark the current stored data dirty, and schedule a flush to disk
   * @return A Promise<integer> that will be resolved after the data is written to disk;
   *         the promise is resolved with the number of bytes written.
   */
  saveChanges() {
      this.logger.debug("Save changes");
    if (!this._pending) {
      if (this.writeInProgress) {
          this.logger.debug("Data changed while write in progress");
        this.overlappedSaves++;
      }
      this._pending = PromiseUtils.defer();
      // Wait until the most recent write completes or fails (if it hasn't already)
      // and then restart our timer
      this._writing.then(count => this._startTimer(), error => this._startTimer());
    }
    return this._pending.promise;
  },

  _deferredSave() {
    let pending = this._pending;
    this._pending = null;
    let writing = this._writing;
    this._writing = pending.promise;

    // In either the success or the exception handling case, we don't need to handle
    // the error from _writing here; it's already being handled in another then()
    let toSave = null;
    try {
      toSave = this._dataProvider();
    } catch (e) {
        this.logger.error("Deferred save dataProvider failed", e);
      writing.catch(error => {})
        .then(count => {
          pending.reject(e);
        });
      return;
    }

    writing.catch(error => { return 0; })
    .then(count => {
        this.logger.debug("Starting write");
      this.totalSaves++;
      this.writeInProgress = true;

      OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"})
      .then(
        result => {
          this._lastError = null;
          this.writeInProgress = false;
              this.logger.debug("Write succeeded");
          pending.resolve(result);
        },
        error => {
          this._lastError = error;
          this.writeInProgress = false;
              this.logger.warn("Write failed", error);
          pending.reject(error);
        });
    });
  },

  /**
   * Immediately save the dirty data to disk, skipping
   * the delay of normal operation. Note that the write
   * still happens asynchronously in the worker
   * thread from OS.File.
   *
   * There are four possible situations:
   * 1) Nothing to flush
   * 2) Data is not currently being written, in-memory copy is dirty
   * 3) Data is currently being written, in-memory copy is clean
   * 4) Data is being written and in-memory copy is dirty
   *
   * @return Promise<integer> that will resolve when all in-memory data
   *         has finished being flushed, returning the number of bytes
   *         written. If all in-memory data is clean, completes with the
   *         result of the most recent write.
   */
  flush() {
    // If we have pending changes, cancel our timer and set up the write
    // immediately (_deferredSave queues the write for after the most
    // recent write completes, if it hasn't already)
    if (this._pending) {
        this.logger.debug("Flush called while data is dirty");
      if (this._timer) {
        this._timer.cancel();
        this._timer = null;
      }
      this._deferredSave();
    }

    return this._writing;
  },

  _finalize() {
    return this.flush().catch(Cu.reportError);
  },

};
PK
!<@'kR--modules/DeferredTask.jsm/* -*- 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 = [
  "DeferredTask",
];

/**
 * Sets up a function or an asynchronous task whose execution can be triggered
 * after a defined delay.  Multiple attempts to run the task before the delay
 * has passed are coalesced.  The task cannot be re-entered while running, but
 * can be executed again after a previous run finished.
 *
 * A common use case occurs when a data structure should be saved into a file
 * every time the data changes, using asynchronous calls, and multiple changes
 * to the data may happen within a short time:
 *
 *   let saveDeferredTask = new DeferredTask(function* () {
 *     yield OS.File.writeAtomic(...);
 *     // Any uncaught exception will be reported.
 *   }, 2000);
 *
 *   // The task is ready, but will not be executed until requested.
 *
 * The "arm" method can be used to start the internal timer that will result in
 * the eventual execution of the task.  Multiple attempts to arm the timer don't
 * introduce further delays:
 *
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 2 seconds from now.
 *
 *   yield waitOneSecond();
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 1 second from now.
 *
 * The timer can be disarmed to reset the delay, or just to cancel execution:
 *
 *   saveDeferredTask.disarm();
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 2 seconds from now.
 *
 * When the internal timer fires and the execution of the task starts, the task
 * cannot be canceled anymore.  It is however possible to arm the timer again
 * during the execution of the task, in which case the task will need to finish
 * before the timer is started again, thus guaranteeing a time of inactivity
 * between executions that is at least equal to the provided delay.
 *
 * The "finalize" method can be used to ensure that the task terminates
 * properly.  The promise it returns is resolved only after the last execution
 * of the task is finished.  To guarantee that the task is executed for the
 * last time, the method prevents any attempt to arm the timer again.
 *
 * If the timer is already armed when the "finalize" method is called, then the
 * task is executed immediately.  If the task was already running at this point,
 * then one last execution from start to finish will happen again, immediately
 * after the current execution terminates.  If the timer is not armed, the
 * "finalize" method only ensures that any running task terminates.
 *
 * For example, during shutdown, you may want to ensure that any pending write
 * is processed, using the latest version of the data if the timer is armed:
 *
 *   AsyncShutdown.profileBeforeChange.addBlocker(
 *     "Example service: shutting down",
 *     () => saveDeferredTask.finalize()
 *   );
 *
 * Instead, if you are going to delete the saved data from disk anyways, you
 * might as well prevent any pending write from starting, while still ensuring
 * that any write that is currently in progress terminates, so that the file is
 * not in use anymore:
 *
 *   saveDeferredTask.disarm();
 *   saveDeferredTask.finalize().then(() => OS.File.remove(...))
 *                              .then(null, Components.utils.reportError);
 */

// Globals

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                  "resource://gre/modules/Task.jsm");

const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
                                     "initWithCallback");

// DeferredTask

/**
 * Sets up a task whose execution can be triggered after a delay.
 *
 * @param aTaskFn
 *        Function or generator function to execute.  This argument is passed to
 *        the "Task.spawn" method every time the task should be executed.  This
 *        task is never re-entered while running.
 * @param aDelayMs
 *        Time between executions, in milliseconds.  Multiple attempts to run
 *        the task before the delay has passed are coalesced.  This time of
 *        inactivity is guaranteed to pass between multiple executions of the
 *        task, except on finalization, when the task may restart immediately
 *        after the previous execution finished.
 */
this.DeferredTask = function(aTaskFn, aDelayMs) {
  this._taskFn = aTaskFn;
  this._delayMs = aDelayMs;
}

this.DeferredTask.prototype = {
  /**
   * Function or generator function to execute.
   */
  _taskFn: null,

  /**
   * Time between executions, in milliseconds.
   */
  _delayMs: null,

  /**
   * Indicates whether the task is currently requested to start again later,
   * regardless of whether it is currently running.
   */
  get isArmed() {
    return this._armed;
  },
  _armed: false,

  /**
   * Indicates whether the task is currently running.  This is always true when
   * read from code inside the task function, but can also be true when read
   * from external code, in case the task is an asynchronous generator function.
   */
  get isRunning() {
    return !!this._runningPromise;
  },

  /**
   * Promise resolved when the current execution of the task terminates, or null
   * if the task is not currently running.
   */
  _runningPromise: null,

  /**
   * nsITimer used for triggering the task after a delay, or null in case the
   * task is running or there is no task scheduled for execution.
   */
  _timer: null,

  /**
   * Actually starts the timer with the delay specified on construction.
   */
  _startTimer() {
    this._timer = new Timer(this._timerCallback.bind(this), this._delayMs,
                            Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /**
   * Requests the execution of the task after the delay specified on
   * construction.  Multiple calls don't introduce further delays.  If the task
   * is running, the delay will start when the current execution finishes.
   *
   * The task will always be executed on a different tick of the event loop,
   * even if the delay specified on construction is zero.  Multiple "arm" calls
   * within the same tick of the event loop are guaranteed to result in a single
   * execution of the task.
   *
   * @note By design, this method doesn't provide a way for the caller to detect
   *       when the next execution terminates, or collect a result.  In fact,
   *       doing that would often result in duplicate processing or logging.  If
   *       a special operation or error logging is needed on completion, it can
   *       be better handled from within the task itself, for example using a
   *       try/catch/finally clause in the task.  The "finalize" method can be
   *       used in the common case of waiting for completion on shutdown.
   */
  arm() {
    if (this._finalized) {
      throw new Error("Unable to arm timer, the object has been finalized.");
    }

    this._armed = true;

    // In case the timer callback is running, do not create the timer now,
    // because this will be handled by the timer callback itself.  Also, the
    // timer is not restarted in case it is already running.
    if (!this._runningPromise && !this._timer) {
      this._startTimer();
    }
  },

  /**
   * Cancels any request for a delayed the execution of the task, though the
   * task itself cannot be canceled in case it is already running.
   *
   * This method stops any currently running timer, thus the delay will restart
   * from its original value in case the "arm" method is called again.
   */
  disarm() {
    this._armed = false;
    if (this._timer) {
      // Calling the "cancel" method and discarding the timer reference makes
      // sure that the timer callback will not be called later, even if the
      // timer thread has already posted the timer event on the main thread.
      this._timer.cancel();
      this._timer = null;
    }
  },

  /**
   * Ensures that any pending task is executed from start to finish, while
   * preventing any attempt to arm the timer again.
   *
   * - If the task is running and the timer is armed, then one last execution
   *   from start to finish will happen again, immediately after the current
   *   execution terminates, then the returned promise will be resolved.
   * - If the task is running and the timer is not armed, the returned promise
   *   will be resolved when the current execution terminates.
   * - If the task is not running and the timer is armed, then the task is
   *   started immediately, and the returned promise resolves when the new
   *   execution terminates.
   * - If the task is not running and the timer is not armed, the method returns
   *   a resolved promise.
   *
   * @return {Promise}
   * @resolves After the last execution of the task is finished.
   * @rejects Never.
   */
  finalize() {
    if (this._finalized) {
      throw new Error("The object has been already finalized.");
    }
    this._finalized = true;

    // If the timer is armed, it means that the task is not running but it is
    // scheduled for execution.  Cancel the timer and run the task immediately.
    if (this._timer) {
      this.disarm();
      this._timerCallback();
    }

    // Wait for the operation to be completed, or resolve immediately.
    if (this._runningPromise) {
      return this._runningPromise;
    }
    return Promise.resolve();
  },
  _finalized: false,

  /**
   * Timer callback used to run the delayed task.
   */
  _timerCallback() {
    let runningDeferred = PromiseUtils.defer();

    // All these state changes must occur at the same time directly inside the
    // timer callback, to prevent race conditions and to ensure that all the
    // methods behave consistently even if called from inside the task.  This
    // means that the assignment of "this._runningPromise" must complete before
    // the task gets a chance to start.
    this._timer = null;
    this._armed = false;
    this._runningPromise = runningDeferred.promise;

    runningDeferred.resolve((async () => {
      // Execute the provided function asynchronously.
      await this._runTask();

      // Now that the task has finished, we check the state of the object to
      // determine if we should restart the task again.
      if (this._armed) {
        if (!this._finalized) {
          this._startTimer();
        } else {
          // Execute the task again immediately, for the last time.  The isArmed
          // property should return false while the task is running, and should
          // remain false after the last execution terminates.
          this._armed = false;
          await this._runTask();
        }
      }

      // Indicate that the execution of the task has finished.  This happens
      // synchronously with the previous state changes in the function.
      this._runningPromise = null;
    })().catch(Cu.reportError));
  },

  /**
   * Executes the associated task and catches exceptions.
   */
  async _runTask() {
    try {
      let result = this._taskFn();
      if (Object.prototype.toString.call(result) == "[object Generator]") {
        await Task.spawn(result); // eslint-disable-line mozilla/no-task
      } else {
        await result;
      }
    } catch (ex) {
      Cu.reportError(ex);
    }
  },
};
PK
!<lf	f	modules/Deprecated.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [ "Deprecated" ];

const Cu = Components.utils;
const Ci = Components.interfaces;
const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings";

Cu.import("resource://gre/modules/Services.jsm");

// A flag that indicates whether deprecation warnings should be logged.
var logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);

Services.prefs.addObserver(PREF_DEPRECATION_WARNINGS,
  function(aSubject, aTopic, aData) {
    logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);
  });

/**
 * Build a callstack log message.
 *
 * @param nsIStackFrame aStack
 *        A callstack to be converted into a string log message.
 */
function stringifyCallstack(aStack) {
  // If aStack is invalid, use Components.stack (ignoring the last frame).
  if (!aStack || !(aStack instanceof Ci.nsIStackFrame)) {
    aStack = Components.stack.caller;
  }

  let frame = aStack.caller;
  let msg = "";
  // Get every frame in the callstack.
  while (frame) {
    msg += frame.filename + " " + frame.lineNumber +
      " " + frame.name + "\n";
    frame = frame.caller;
  }
  return msg;
}

this.Deprecated = {
  /**
   * Log a deprecation warning.
   *
   * @param string aText
   *        Deprecation warning text.
   * @param string aUrl
   *        A URL pointing to documentation describing deprecation
   *        and the way to address it.
   * @param nsIStackFrame aStack
   *        An optional callstack. If it is not provided a
   *        snapshot of the current JavaScript callstack will be
   *        logged.
   */
  warning(aText, aUrl, aStack) {
    if (!logWarnings) {
      return;
    }

    // If URL is not provided, report an error.
    if (!aUrl) {
      Cu.reportError("Error in Deprecated.warning: warnings must " +
        "provide a URL documenting this deprecation.");
      return;
    }

    let textMessage = "DEPRECATION WARNING: " + aText +
      "\nYou may find more details about this deprecation at: " +
      aUrl + "\n" +
      // Append a callstack part to the deprecation message.
      stringifyCallstack(aStack);

    // Report deprecation warning.
    Cu.reportError(textMessage);
  }
};
PK
!<Imodules/DownloadCore.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Main implementation of the Downloads API objects. Consumers should get
 * references to these objects through the "Downloads.jsm" module.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "Download",
  "DownloadSource",
  "DownloadTarget",
  "DownloadError",
  "DownloadSaver",
  "DownloadCopySaver",
  "DownloadLegacySaver",
  "DownloadPDFSaver",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm")
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
           "@mozilla.org/browser/download-history;1",
           Ci.nsIDownloadHistory);
XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher",
           "@mozilla.org/uriloader/external-helper-app-service;1",
           Ci.nsPIExternalAppLauncher);
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
           "@mozilla.org/uriloader/external-helper-app-service;1",
           Ci.nsIExternalHelperAppService);
XPCOMUtils.defineLazyServiceGetter(this, "gPrintSettingsService",
           "@mozilla.org/gfx/printsettings-service;1",
           Ci.nsIPrintSettingsService);

/* global DownloadIntegration */
Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
            "resource://gre/modules/DownloadIntegration.jsm");

const BackgroundFileSaverStreamListener = Components.Constructor(
      "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
      "nsIBackgroundFileSaver");

/**
 * Returns true if the given value is a primitive string or a String object.
 */
function isString(aValue) {
  // We cannot use the "instanceof" operator reliably across module boundaries.
  return (typeof aValue == "string") ||
         (typeof aValue == "object" && "charAt" in aValue);
}

/**
 * Serialize the unknown properties of aObject into aSerializable.
 */
function serializeUnknownProperties(aObject, aSerializable) {
  if (aObject._unknownProperties) {
    for (let property in aObject._unknownProperties) {
      aSerializable[property] = aObject._unknownProperties[property];
    }
  }
}

/**
 * Check for any unknown properties in aSerializable and preserve those in the
 * _unknownProperties field of aObject. aFilterFn is called for each property
 * name of aObject and should return true only for unknown properties.
 */
function deserializeUnknownProperties(aObject, aSerializable, aFilterFn) {
  for (let property in aSerializable) {
    if (aFilterFn(property)) {
      if (!aObject._unknownProperties) {
        aObject._unknownProperties = { };
      }

      aObject._unknownProperties[property] = aSerializable[property];
    }
  }
}

/**
 * This determines the minimum time interval between updates to the number of
 * bytes transferred, and is a limiting factor to the sequence of readings used
 * in calculating the speed of the download.
 */
const kProgressUpdateIntervalMs = 400;

/**
 * Represents a single download, with associated state and actions.  This object
 * is transient, though it can be included in a DownloadList so that it can be
 * managed by the user interface and persisted across sessions.
 */
this.Download = function() {
  this._deferSucceeded = PromiseUtils.defer();
}

this.Download.prototype = {
  /**
   * DownloadSource object associated with this download.
   */
  source: null,

  /**
   * DownloadTarget object associated with this download.
   */
  target: null,

  /**
   * DownloadSaver object associated with this download.
   */
  saver: null,

  /**
   * Indicates that the download never started, has been completed successfully,
   * failed, or has been canceled.  This property becomes false when a download
   * is started for the first time, or when a failed or canceled download is
   * restarted.
   */
  stopped: true,

  /**
   * Indicates that the download has been completed successfully.
   */
  succeeded: false,

  /**
   * Indicates that the download has been canceled.  This property can become
   * true, then it can be reset to false when a canceled download is restarted.
   *
   * This property becomes true as soon as the "cancel" method is called, though
   * the "stopped" property might remain false until the cancellation request
   * has been processed.  Temporary files or part files may still exist even if
   * they are expected to be deleted, until the "stopped" property becomes true.
   */
  canceled: false,

  /**
   * When the download fails, this is set to a DownloadError instance indicating
   * the cause of the failure.  If the download has been completed successfully
   * or has been canceled, this property is null.  This property is reset to
   * null when a failed download is restarted.
   */
  error: null,

  /**
   * Indicates the start time of the download.  When the download starts,
   * this property is set to a valid Date object.  The default value is null
   * before the download starts.
   */
  startTime: null,

  /**
   * Indicates whether this download's "progress" property is able to report
   * partial progress while the download proceeds, and whether the value in
   * totalBytes is relevant.  This depends on the saver and the download source.
   */
  hasProgress: false,

  /**
   * Progress percent, from 0 to 100.  Intermediate values are reported only if
   * hasProgress is true.
   *
   * @note You shouldn't rely on this property being equal to 100 to determine
   *       whether the download is completed.  You should use the individual
   *       state properties instead.
   */
  progress: 0,

  /**
   * When hasProgress is true, indicates the total number of bytes to be
   * transferred before the download finishes, that can be zero for empty files.
   *
   * When hasProgress is false, this property is always zero.
   *
   * @note This property may be different than the final file size on disk for
   *       downloads that are encoded during the network transfer.  You can use
   *       the "size" property of the DownloadTarget object to get the actual
   *       size on disk once the download succeeds.
   */
  totalBytes: 0,

  /**
   * Number of bytes currently transferred.  This value starts at zero, and may
   * be updated regardless of the value of hasProgress.
   *
   * @note You shouldn't rely on this property being equal to totalBytes to
   *       determine whether the download is completed.  You should use the
   *       individual state properties instead.  This property may not be
   *       updated during the last part of the download.
   */
  currentBytes: 0,

  /**
   * Fractional number representing the speed of the download, in bytes per
   * second.  This value is zero when the download is stopped, and may be
   * updated regardless of the value of hasProgress.
   */
  speed: 0,

  /**
   * Indicates whether, at this time, there is any partially downloaded data
   * that can be used when restarting a failed or canceled download.
   *
   * Even if the download has partial data on disk, hasPartialData will be false
   * if that data cannot be used to restart the download. In order to determine
   * if a part file is being used which contains partial data the
   * Download.target.partFilePath should be checked.
   *
   * This property is relevant while the download is in progress, and also if it
   * failed or has been canceled.  If the download has been completed
   * successfully, this property is always false.
   *
   * Whether partial data can actually be retained depends on the saver and the
   * download source, and may not be known before the download is started.
   */
  hasPartialData: false,

  /**
   * Indicates whether, at this time, there is any data that has been blocked.
   * Since reputation blocking takes place after the download has fully
   * completed a value of true also indicates 100% of the data is present.
   */
  hasBlockedData: false,

  /**
   * This can be set to a function that is called after other properties change.
   */
  onchange: null,

  /**
   * This tells if the user has chosen to open/run the downloaded file after
   * download has completed.
   */
  launchWhenSucceeded: false,

  /**
   * This represents the MIME type of the download.
   */
  contentType: null,

  /**
   * This indicates the path of the application to be used to launch the file,
   * or null if the file should be launched with the default application.
   */
  launcherPath: null,

  /**
   * Raises the onchange notification.
   */
  _notifyChange: function D_notifyChange() {
    try {
      if (this.onchange) {
        this.onchange();
      }
    } catch (ex) {
      Cu.reportError(ex);
    }
  },

  /**
   * The download may be stopped and restarted multiple times before it
   * completes successfully. This may happen if any of the download attempts is
   * canceled or fails.
   *
   * This property contains a promise that is linked to the current attempt, or
   * null if the download is either stopped or in the process of being canceled.
   * If the download restarts, this property is replaced with a new promise.
   *
   * The promise is resolved if the attempt it represents finishes successfully,
   * and rejected if the attempt fails.
   */
  _currentAttempt: null,

  /**
   * Starts the download for the first time, or restarts a download that failed
   * or has been canceled.
   *
   * Calling this method when the download has been completed successfully has
   * no effect, and the method returns a resolved promise.  If the download is
   * in progress, the method returns the same promise as the previous call.
   *
   * If the "cancel" method was called but the cancellation process has not
   * finished yet, this method waits for the cancellation to finish, then
   * restarts the download immediately.
   *
   * @note If you need to start a new download from the same source, rather than
   *       restarting a failed or canceled one, you should create a separate
   *       Download object with the same source as the current one.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  start: function D_start() {
    // If the download succeeded, it's the final state, we have nothing to do.
    if (this.succeeded) {
      return Promise.resolve();
    }

    // If the download already started and hasn't failed or hasn't been
    // canceled, return the same promise as the previous call, allowing the
    // caller to wait for the current attempt to finish.
    if (this._currentAttempt) {
      return this._currentAttempt;
    }

    // While shutting down or disposing of this object, we prevent the download
    // from returning to be in progress.
    if (this._finalized) {
      return Promise.reject(new DownloadError({
                                message: "Cannot start after finalization."}));
    }

    if (this.error && this.error.becauseBlockedByReputationCheck) {
      return Promise.reject(new DownloadError({
                                message: "Cannot start after being blocked " +
                                         "by a reputation check."}));
    }

    // Initialize all the status properties for a new or restarted download.
    this.stopped = false;
    this.canceled = false;
    this.error = null;
    this.hasProgress = false;
    this.hasBlockedData = false;
    this.progress = 0;
    this.totalBytes = 0;
    this.currentBytes = 0;
    this.startTime = new Date();

    // Create a new deferred object and an associated promise before starting
    // the actual download.  We store it on the download as the current attempt.
    let deferAttempt = PromiseUtils.defer();
    let currentAttempt = deferAttempt.promise;
    this._currentAttempt = currentAttempt;

    // Restart the progress and speed calculations from scratch.
    this._lastProgressTimeMs = 0;

    // This function propagates progress from the DownloadSaver object, unless
    // it comes in late from a download attempt that was replaced by a new one.
    // If the cancellation process for the download has started, then the update
    // is ignored.
    function DS_setProgressBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
      if (this._currentAttempt == currentAttempt) {
        this._setBytes(aCurrentBytes, aTotalBytes, aHasPartialData);
      }
    }

    // This function propagates download properties from the DownloadSaver
    // object, unless it comes in late from a download attempt that was
    // replaced by a new one.  If the cancellation process for the download has
    // started, then the update is ignored.
    function DS_setProperties(aOptions) {
      if (this._currentAttempt != currentAttempt) {
        return;
      }

      let changeMade = false;

      for (let property of ["contentType", "progress", "hasPartialData",
                            "hasBlockedData"]) {
        if (property in aOptions && this[property] != aOptions[property]) {
          this[property] = aOptions[property];
          changeMade = true;
        }
      }

      if (changeMade) {
        this._notifyChange();
      }
    }

    // Now that we stored the promise in the download object, we can start the
    // task that will actually execute the download.
    deferAttempt.resolve((async () => {
      // Wait upon any pending operation before restarting.
      if (this._promiseCanceled) {
        await this._promiseCanceled;
      }
      if (this._promiseRemovePartialData) {
        try {
          await this._promiseRemovePartialData;
        } catch (ex) {
          // Ignore any errors, which are already reported by the original
          // caller of the removePartialData method.
        }
      }

      // In case the download was restarted while cancellation was in progress,
      // but the previous attempt actually succeeded before cancellation could
      // be processed, it is possible that the download has already finished.
      if (this.succeeded) {
        return;
      }

      try {
        // Disallow download if parental controls service restricts it.
        if (await DownloadIntegration.shouldBlockForParentalControls(this)) {
          throw new DownloadError({ becauseBlockedByParentalControls: true });
        }

        // Disallow download if needed runtime permissions have not been granted
        // by user.
        if (await DownloadIntegration.shouldBlockForRuntimePermissions()) {
          throw new DownloadError({ becauseBlockedByRuntimePermissions: true });
        }

        // We should check if we have been canceled in the meantime, after all
        // the previous asynchronous operations have been executed and just
        // before we call the "execute" method of the saver.
        if (this._promiseCanceled) {
          // The exception will become a cancellation in the "catch" block.
          throw undefined;
        }

        // Execute the actual download through the saver object.
        this._saverExecuting = true;
        await this.saver.execute(DS_setProgressBytes.bind(this),
                                 DS_setProperties.bind(this));

        // Now that the actual saving finished, read the actual file size on
        // disk, that may be different from the amount of data transferred.
        await this.target.refresh();

        // Check for the last time if the download has been canceled. This must
        // be done right before setting the "stopped" property of the download,
        // without any asynchronous operations in the middle, so that another
        // cancellation request cannot start in the meantime and stay unhandled.
        if (this._promiseCanceled) {
          try {
            await OS.File.remove(this.target.path);
          } catch (ex) {
            Cu.reportError(ex);
          }

          this.target.exists = false;
          this.target.size = 0;

          // Cancellation exceptions will be changed in the catch block below.
          throw new DownloadError();
        }

        // Update the status properties for a successful download.
        this.progress = 100;
        this.succeeded = true;
        this.hasPartialData = false;
      } catch (originalEx) {
        // We may choose a different exception to propagate in the code below,
        // or wrap the original one. We do this mutation in a different variable
        // because of the "no-ex-assign" ESLint rule.
        let ex = originalEx;

        // Fail with a generic status code on cancellation, so that the caller
        // is forced to actually check the status properties to see if the
        // download was canceled or failed because of other reasons.
        if (this._promiseCanceled) {
          throw new DownloadError({ message: "Download canceled." });
        }

        // An HTTP 450 error code is used by Windows to indicate that a uri is
        // blocked by parental controls. This will prevent the download from
        // occuring, so an error needs to be raised. This is not performed
        // during the parental controls check above as it requires the request
        // to start.
        if (this._blockedByParentalControls) {
          ex = new DownloadError({ becauseBlockedByParentalControls: true });
        }

        // Update the download error, unless a new attempt already started. The
        // change in the status property is notified in the finally block.
        if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
          if (!(ex instanceof DownloadError)) {
            let properties = {innerException: ex};

            if (ex.message) {
              properties.message = ex.message;
            }

            ex = new DownloadError(properties);
          }

          this.error = ex;
        }
        throw ex;
      } finally {
        // Any cancellation request has now been processed.
        this._saverExecuting = false;
        this._promiseCanceled = null;

        // Update the status properties, unless a new attempt already started.
        if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
          this._currentAttempt = null;
          this.stopped = true;
          this.speed = 0;
          this._notifyChange();
          if (this.succeeded) {
            await this._succeed();
          }
        }
      }
    })());

    // Notify the new download state before returning.
    this._notifyChange();
    return currentAttempt;
  },

  /**
   * Perform the actions necessary when a Download succeeds.
   *
   * @return {Promise}
   * @resolves When the steps to take after success have completed.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  async _succeed() {
    await DownloadIntegration.downloadDone(this);

    this._deferSucceeded.resolve();

    if (this.launchWhenSucceeded) {
      this.launch().catch(Cu.reportError);

      // Always schedule files to be deleted at the end of the private browsing
      // mode, regardless of the value of the pref.
      if (this.source.isPrivate) {
        gExternalAppLauncher.deleteTemporaryPrivateFileWhenPossible(
                             new FileUtils.File(this.target.path));
      } else if (Services.prefs.getBoolPref(
                  "browser.helperApps.deleteTempFileOnExit")) {
        gExternalAppLauncher.deleteTemporaryFileOnExit(
                             new FileUtils.File(this.target.path));
      }
    }
  },

  /**
   * When a request to unblock the download is received, contains a promise
   * that will be resolved when the unblock request is completed. This property
   * will then continue to hold the promise indefinitely.
   */
  _promiseUnblock: null,

  /**
   * When a request to confirm the block of the download is received, contains
   * a promise that will be resolved when cleaning up the download has
   * completed. This property will then continue to hold the promise
   * indefinitely.
   */
  _promiseConfirmBlock: null,

  /**
   * Unblocks a download which had been blocked by reputation.
   *
   * The file will be moved out of quarantine and the download will be
   * marked as succeeded.
   *
   * @return {Promise}
   * @resolves When the Download has been unblocked and succeeded.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  unblock() {
    if (this._promiseUnblock) {
      return this._promiseUnblock;
    }

    if (this._promiseConfirmBlock) {
      return Promise.reject(new Error(
        "Download block has been confirmed, cannot unblock."));
    }

    if (!this.hasBlockedData) {
      return Promise.reject(new Error(
        "unblock may only be called on Downloads with blocked data."));
    }

    this._promiseUnblock = (async () => {
      try {
        await OS.File.move(this.target.partFilePath, this.target.path);
        await this.target.refresh();
      } catch (ex) {
        await this.refresh();
        this._promiseUnblock = null;
        throw ex;
      }

      this.succeeded = true;
      this.hasBlockedData = false;
      this._notifyChange();
      await this._succeed();
    })();

    return this._promiseUnblock;
  },

  /**
   * Confirms that a blocked download should be cleaned up.
   *
   * If a download was blocked but retained on disk this method can be used
   * to remove the file.
   *
   * @return {Promise}
   * @resolves When the Download's data has been removed.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  confirmBlock() {
    if (this._promiseConfirmBlock) {
      return this._promiseConfirmBlock;
    }

    if (this._promiseUnblock) {
      return Promise.reject(new Error(
        "Download is being unblocked, cannot confirmBlock."));
    }

    if (!this.hasBlockedData) {
      return Promise.reject(new Error(
        "confirmBlock may only be called on Downloads with blocked data."));
    }

    this._promiseConfirmBlock = (async () => {
      try {
        await OS.File.remove(this.target.partFilePath);
      } catch (ex) {
        await this.refresh();
        this._promiseConfirmBlock = null;
        throw ex;
      }

      this.hasBlockedData = false;
      this._notifyChange();
    })();

    return this._promiseConfirmBlock;
  },

  /*
   * Launches the file after download has completed. This can open
   * the file with the default application for the target MIME type
   * or file extension, or with a custom application if launcherPath
   * is set.
   *
   * @return {Promise}
   * @resolves When the instruction to launch the file has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the file is actually
   *           launched.
   * @rejects  JavaScript exception if there was an error trying to launch
   *           the file.
   */
  launch() {
    if (!this.succeeded) {
      return Promise.reject(
        new Error("launch can only be called if the download succeeded")
      );
    }

    return DownloadIntegration.launchDownload(this);
  },

  /*
   * Shows the folder containing the target file, or where the target file
   * will be saved. This may be called at any time, even if the download
   * failed or is currently in progress.
   *
   * @return {Promise}
   * @resolves When the instruction to open the containing folder has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the folder is actually
   *           opened.
   * @rejects  JavaScript exception if there was an error trying to open
   *           the containing folder.
   */
  showContainingDirectory: function D_showContainingDirectory() {
    return DownloadIntegration.showContainingDirectory(this.target.path);
  },

  /**
   * When a request to cancel the download is received, contains a promise that
   * will be resolved when the cancellation request is processed.  When the
   * request is processed, this property becomes null again.
   */
  _promiseCanceled: null,

  /**
   * True between the call to the "execute" method of the saver and the
   * completion of the current download attempt.
   */
  _saverExecuting: false,

  /**
   * Cancels the download.
   *
   * The cancellation request is asynchronous.  Until the cancellation process
   * finishes, temporary files or part files may still exist even if they are
   * expected to be deleted.
   *
   * In case the download completes successfully before the cancellation request
   * could be processed, this method has no effect, and it returns a resolved
   * promise.  You should check the properties of the download at the time the
   * returned promise is resolved to determine if the download was cancelled.
   *
   * Calling this method when the download has been completed successfully,
   * failed, or has been canceled has no effect, and the method returns a
   * resolved promise.  This behavior is designed for the case where the call
   * to "cancel" happens asynchronously, and is consistent with the case where
   * the cancellation request could not be processed in time.
   *
   * @return {Promise}
   * @resolves When the cancellation process has finished.
   * @rejects Never.
   */
  cancel: function D_cancel() {
    // If the download is currently stopped, we have nothing to do.
    if (this.stopped) {
      return Promise.resolve();
    }

    if (!this._promiseCanceled) {
      // Start a new cancellation request.
      this._promiseCanceled = new Promise(resolve => {
        this._currentAttempt.then(resolve, resolve);
      });

      // The download can already be restarted.
      this._currentAttempt = null;

      // Notify that the cancellation request was received.
      this.canceled = true;
      this._notifyChange();

      // Execute the actual cancellation through the saver object, in case it
      // has already started.  Otherwise, the cancellation will be handled just
      // before the saver is started.
      if (this._saverExecuting) {
        this.saver.cancel();
      }
    }

    return this._promiseCanceled;
  },

  /**
   * Indicates whether any partially downloaded data should be retained, to use
   * when restarting a failed or canceled download.  The default is false.
   *
   * Whether partial data can actually be retained depends on the saver and the
   * download source, and may not be known before the download is started.
   *
   * To have any effect, this property must be set before starting the download.
   * Resetting this property to false after the download has already started
   * will not remove any partial data.
   *
   * If this property is set to true, care should be taken that partial data is
   * removed before the reference to the download is discarded.  This can be
   * done using the removePartialData or the "finalize" methods.
   */
  tryToKeepPartialData: false,

  /**
   * When a request to remove partially downloaded data is received, contains a
   * promise that will be resolved when the removal request is processed.  When
   * the request is processed, this property becomes null again.
   */
  _promiseRemovePartialData: null,

  /**
   * Removes any partial data kept as part of a canceled or failed download.
   *
   * If the download is not canceled or failed, this method has no effect, and
   * it returns a resolved promise.  If the "cancel" method was called but the
   * cancellation process has not finished yet, this method waits for the
   * cancellation to finish, then removes the partial data.
   *
   * After this method has been called, if the tryToKeepPartialData property is
   * still true when the download is restarted, partial data will be retained
   * during the new download attempt.
   *
   * @return {Promise}
   * @resolves When the partial data has been successfully removed.
   * @rejects JavaScript exception if the operation could not be completed.
   */
  removePartialData() {
    if (!this.canceled && !this.error) {
      return Promise.resolve();
    }

    if (!this._promiseRemovePartialData) {
      this._promiseRemovePartialData = (async () => {
        try {
          // Wait upon any pending cancellation request.
          if (this._promiseCanceled) {
            await this._promiseCanceled;
          }
          // Ask the saver object to remove any partial data.
          await this.saver.removePartialData();
          // For completeness, clear the number of bytes transferred.
          if (this.currentBytes != 0 || this.hasPartialData) {
            this.currentBytes = 0;
            this.hasPartialData = false;
            this._notifyChange();
          }
        } finally {
          this._promiseRemovePartialData = null;
        }
      })();
    }

    return this._promiseRemovePartialData;
  },

  /**
   * This deferred object contains a promise that is resolved as soon as this
   * download finishes successfully, and is never rejected.  This property is
   * initialized when the download is created, and never changes.
   */
  _deferSucceeded: null,

  /**
   * Returns a promise that is resolved as soon as this download finishes
   * successfully, even if the download was stopped and restarted meanwhile.
   *
   * You can use this property for scheduling download completion actions in the
   * current session, for downloads that are controlled interactively.  If the
   * download is not controlled interactively, you should use the promise
   * returned by the "start" method instead, to check for success or failure.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects Never.
   */
  whenSucceeded: function D_whenSucceeded() {
    return this._deferSucceeded.promise;
  },

  /**
   * Updates the state of a finished, failed, or canceled download based on the
   * current state in the file system.  If the download is in progress or it has
   * been finalized, this method has no effect, and it returns a resolved
   * promise.
   *
   * This allows the properties of the download to be updated in case the user
   * moved or deleted the target file or its associated ".part" file.
   *
   * @return {Promise}
   * @resolves When the operation has completed.
   * @rejects Never.
   */
  refresh() {
    return (async () => {
      if (!this.stopped || this._finalized) {
        return;
      }

      if (this.succeeded) {
        let oldExists = this.target.exists;
        let oldSize = this.target.size;
        await this.target.refresh();
        if (oldExists != this.target.exists || oldSize != this.target.size) {
          this._notifyChange();
        }
        return;
      }

      // Update the current progress from disk if we retained partial data.
      if ((this.hasPartialData || this.hasBlockedData) &&
          this.target.partFilePath) {

        try {
          let stat = await OS.File.stat(this.target.partFilePath);

          // Ignore the result if the state has changed meanwhile.
          if (!this.stopped || this._finalized) {
            return;
          }

          // Update the bytes transferred and the related progress properties.
          this.currentBytes = stat.size;
          if (this.totalBytes > 0) {
            this.hasProgress = true;
            this.progress = Math.floor(this.currentBytes /
                                           this.totalBytes * 100);
          }
        } catch (ex) {
          if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
            throw ex;
          }
          // Ignore the result if the state has changed meanwhile.
          if (!this.stopped || this._finalized) {
            return;
          }

          this.hasBlockedData = false;
          this.hasPartialData = false;
        }

        this._notifyChange();
      }
    })().catch(Cu.reportError);
  },

  /**
   * True if the "finalize" method has been called.  This prevents the download
   * from starting again after having been stopped.
   */
  _finalized: false,

  /**
   * Ensures that the download is stopped, and optionally removes any partial
   * data kept as part of a canceled or failed download.  After this method has
   * been called, the download cannot be started again.
   *
   * This method should be used in place of "cancel" and removePartialData while
   * shutting down or disposing of the download object, to prevent other callers
   * from interfering with the operation.  This is required because cancellation
   * and other operations are asynchronous.
   *
   * @param aRemovePartialData
   *        Whether any partially downloaded data should be removed after the
   *        download has been stopped.
   *
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects JavaScript exception if an error occurred while removing the
   *          partially downloaded data.
   */
  finalize(aRemovePartialData) {
    // Prevents the download from starting again after having been stopped.
    this._finalized = true;

    if (aRemovePartialData) {
      // Cancel the download, in case it is currently in progress, then remove
      // any partially downloaded data.  The removal operation waits for
      // cancellation to be completed before resolving the promise it returns.
      this.cancel();
      return this.removePartialData();
    }
    // Just cancel the download, in case it is currently in progress.
    return this.cancel();
  },

  /**
   * Indicates the time of the last progress notification, expressed as the
   * number of milliseconds since January 1, 1970, 00:00:00 UTC.  This is zero
   * until some bytes have actually been transferred.
   */
  _lastProgressTimeMs: 0,

  /**
   * Updates progress notifications based on the number of bytes transferred.
   *
   * The number of bytes transferred is not updated unless enough time passed
   * since this function was last called.  This limits the computation load, in
   * particular when the listeners update the user interface in response.
   *
   * @param aCurrentBytes
   *        Number of bytes transferred until now.
   * @param aTotalBytes
   *        Total number of bytes to be transferred, or -1 if unknown.
   * @param aHasPartialData
   *        Indicates whether the partially downloaded data can be used when
   *        restarting the download if it fails or is canceled.
   */
  _setBytes: function D_setBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
    let changeMade = (this.hasPartialData != aHasPartialData);
    this.hasPartialData = aHasPartialData;

    // Unless aTotalBytes is -1, we can report partial download progress.  In
    // this case, notify when the related properties changed since last time.
    if (aTotalBytes != -1 && (!this.hasProgress ||
                              this.totalBytes != aTotalBytes)) {
      this.hasProgress = true;
      this.totalBytes = aTotalBytes;
      changeMade = true;
    }

    // Updating the progress and computing the speed require that enough time
    // passed since the last update, or that we haven't started throttling yet.
    let currentTimeMs = Date.now();
    let intervalMs = currentTimeMs - this._lastProgressTimeMs;
    if (intervalMs >= kProgressUpdateIntervalMs) {
      // Don't compute the speed unless we started throttling notifications.
      if (this._lastProgressTimeMs != 0) {
        // Calculate the speed in bytes per second.
        let rawSpeed = (aCurrentBytes - this.currentBytes) / intervalMs * 1000;
        if (this.speed == 0) {
          // When the previous speed is exactly zero instead of a fractional
          // number, this can be considered the first element of the series.
          this.speed = rawSpeed;
        } else {
          // Apply exponential smoothing, with a smoothing factor of 0.1.
          this.speed = rawSpeed * 0.1 + this.speed * 0.9;
        }
      }

      // Start throttling notifications only when we have actually received some
      // bytes for the first time.  The timing of the first part of the download
      // is not reliable, due to possible latency in the initial notifications.
      // This also allows automated tests to receive and verify the number of
      // bytes initially transferred.
      if (aCurrentBytes > 0) {
        this._lastProgressTimeMs = currentTimeMs;

        // Update the progress now that we don't need its previous value.
        this.currentBytes = aCurrentBytes;
        if (this.totalBytes > 0) {
          this.progress = Math.floor(this.currentBytes / this.totalBytes * 100);
        }
        changeMade = true;
      }
    }

    if (changeMade) {
      this._notifyChange();
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    let serializable = {
      source: this.source.toSerializable(),
      target: this.target.toSerializable(),
    };

    let saver = this.saver.toSerializable();
    if (!serializable.source || !saver) {
      // If we are unable to serialize either the source or the saver,
      // we won't persist the download.
      return null;
    }

    // Simplify the representation for the most common saver type.  If the saver
    // is an object instead of a simple string, we can't simplify it because we
    // need to persist all its properties, not only "type".  This may happen for
    // savers of type "copy" as well as other types.
    if (saver !== "copy") {
      serializable.saver = saver;
    }

    if (this.error) {
      serializable.errorObj = this.error.toSerializable();
    }

    if (this.startTime) {
      serializable.startTime = this.startTime.toJSON();
    }

    // These are serialized unless they are false, null, or empty strings.
    for (let property of kPlainSerializableDownloadProperties) {
      if (this[property]) {
        serializable[property] = this[property];
      }
    }

    serializeUnknownProperties(this, serializable);

    return serializable;
  },

  /**
   * Returns a value that changes only when one of the properties of a Download
   * object that should be saved into a file also change.  This excludes
   * properties whose value doesn't usually change during the download lifetime.
   *
   * This function is used to determine whether the download should be
   * serialized after a property change notification has been received.
   *
   * @return String representing the relevant download state.
   */
  getSerializationHash() {
    // The "succeeded", "canceled", "error", and startTime properties are not
    // taken into account because they all change before the "stopped" property
    // changes, and are not altered in other cases.
    return this.stopped + "," + this.totalBytes + "," + this.hasPartialData +
           "," + this.contentType;
  },
};

/**
 * Defines which properties of the Download object are serializable.
 */
const kPlainSerializableDownloadProperties = [
  "succeeded",
  "canceled",
  "totalBytes",
  "hasPartialData",
  "hasBlockedData",
  "tryToKeepPartialData",
  "launcherPath",
  "launchWhenSucceeded",
  "contentType",
];

/**
 * Creates a new Download object from a serializable representation.  This
 * function is used by the createDownload method of Downloads.jsm when a new
 * Download object is requested, thus some properties may refer to live objects
 * in place of their serializable representations.
 *
 * @param aSerializable
 *        An object with the following fields:
 *        {
 *          source: DownloadSource object, or its serializable representation.
 *                  See DownloadSource.fromSerializable for details.
 *          target: DownloadTarget object, or its serializable representation.
 *                  See DownloadTarget.fromSerializable for details.
 *          saver: Serializable representation of a DownloadSaver object.  See
 *                 DownloadSaver.fromSerializable for details.  If omitted,
 *                 defaults to "copy".
 *        }
 *
 * @return The newly created Download object.
 */
Download.fromSerializable = function(aSerializable) {
  let download = new Download();
  if (aSerializable.source instanceof DownloadSource) {
    download.source = aSerializable.source;
  } else {
    download.source = DownloadSource.fromSerializable(aSerializable.source);
  }
  if (aSerializable.target instanceof DownloadTarget) {
    download.target = aSerializable.target;
  } else {
    download.target = DownloadTarget.fromSerializable(aSerializable.target);
  }
  if ("saver" in aSerializable) {
    download.saver = DownloadSaver.fromSerializable(aSerializable.saver);
  } else {
    download.saver = DownloadSaver.fromSerializable("copy");
  }
  download.saver.download = download;

  if ("startTime" in aSerializable) {
    let time = aSerializable.startTime.getTime
             ? aSerializable.startTime.getTime()
             : aSerializable.startTime;
    download.startTime = new Date(time);
  }

  // If 'errorObj' is present it will take precedence over the 'error' property.
  // 'error' is a legacy property only containing message, which is insufficient
  // to represent all of the error information.
  //
  // Instead of just replacing 'error' we use a new 'errorObj' so that previous
  // versions will keep it as an unknown property.
  if ("errorObj" in aSerializable) {
    download.error = DownloadError.fromSerializable(aSerializable.errorObj);
  } else if ("error" in aSerializable) {
    download.error = aSerializable.error;
  }

  for (let property of kPlainSerializableDownloadProperties) {
    if (property in aSerializable) {
      download[property] = aSerializable[property];
    }
  }

  deserializeUnknownProperties(download, aSerializable, property =>
    kPlainSerializableDownloadProperties.indexOf(property) == -1 &&
    property != "startTime" &&
    property != "source" &&
    property != "target" &&
    property != "error" &&
    property != "saver");

  return download;
};

/**
 * Represents the source of a download, for example a document or an URI.
 */
this.DownloadSource = function() {}

this.DownloadSource.prototype = {
  /**
   * String containing the URI for the download source.
   */
  url: null,

  /**
   * Indicates whether the download originated from a private window.  This
   * determines the context of the network request that is made to retrieve the
   * resource.
   */
  isPrivate: false,

  /**
   * String containing the referrer URI of the download source, or null if no
   * referrer should be sent or the download source is not HTTP.
   */
  referrer: null,

  /**
   * For downloads handled by the (default) DownloadCopySaver, this function
   * can adjust the network channel before it is opened, for example to change
   * the HTTP headers or to upload a stream as POST data.
   *
   * @note If this is defined this object will not be serializable, thus the
   *       Download object will not be persisted across sessions.
   *
   * @param aChannel
   *        The nsIChannel to be adjusted.
   *
   * @return {Promise}
   * @resolves When the channel has been adjusted and can be opened.
   * @rejects JavaScript exception that will cause the download to fail.
   */
   adjustChannel: null,

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    if (this.adjustChannel) {
      // If the callback was used, we can't reproduce this across sessions.
      return null;
    }

    // Simplify the representation if we don't have other details.
    if (!this.isPrivate && !this.referrer && !this._unknownProperties) {
      return this.url;
    }

    let serializable = { url: this.url };
    if (this.isPrivate) {
      serializable.isPrivate = true;
    }
    if (this.referrer) {
      serializable.referrer = this.referrer;
    }

    serializeUnknownProperties(this, serializable);
    return serializable;
  },
};

/**
 * Creates a new DownloadSource object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadSource object.  This may be a
 *        string containing the URI for the download source, an nsIURI, or an
 *        object with the following properties:
 *        {
 *          url: String containing the URI for the download source.
 *          isPrivate: Indicates whether the download originated from a private
 *                     window.  If omitted, the download is public.
 *          referrer: String containing the referrer URI of the download source.
 *                    Can be omitted or null if no referrer should be sent or
 *                    the download source is not HTTP.
 *          adjustChannel: For downloads handled by (default) DownloadCopySaver,
 *                         this function can adjust the network channel before
 *                         it is opened, for example to change the HTTP headers
 *                         or to upload a stream as POST data.  Optional.
 *        }
 *
 * @return The newly created DownloadSource object.
 */
this.DownloadSource.fromSerializable = function(aSerializable) {
  let source = new DownloadSource();
  if (isString(aSerializable)) {
    // Convert String objects to primitive strings at this point.
    source.url = aSerializable.toString();
  } else if (aSerializable instanceof Ci.nsIURI) {
    source.url = aSerializable.spec;
  } else if (aSerializable instanceof Ci.nsIDOMWindow) {
    source.url = aSerializable.location.href;
    source.isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(aSerializable);
    source.windowRef = Cu.getWeakReference(aSerializable);
  } else {
    // Convert String objects to primitive strings at this point.
    source.url = aSerializable.url.toString();
    if ("isPrivate" in aSerializable) {
      source.isPrivate = aSerializable.isPrivate;
    }
    if ("referrer" in aSerializable) {
      source.referrer = aSerializable.referrer;
    }
    if ("adjustChannel" in aSerializable) {
      source.adjustChannel = aSerializable.adjustChannel;
    }

    deserializeUnknownProperties(source, aSerializable, property =>
      property != "url" && property != "isPrivate" && property != "referrer");
  }

  return source;
};

/**
 * Represents the target of a download, for example a file in the global
 * downloads directory, or a file in the system temporary directory.
 */
this.DownloadTarget = function() {}

this.DownloadTarget.prototype = {
  /**
   * String containing the path of the target file.
   */
  path: null,

  /**
   * String containing the path of the ".part" file containing the data
   * downloaded so far, or null to disable the use of a ".part" file to keep
   * partially downloaded data.
   */
  partFilePath: null,

  /**
   * Indicates whether the target file exists.
   *
   * This is a dynamic property updated when the download finishes or when the
   * "refresh" method of the Download object is called. It can be used by the
   * front-end to reduce I/O compared to checking the target file directly.
   */
  exists: false,

  /**
   * Size in bytes of the target file, or zero if the download has not finished.
   *
   * Even if the target file does not exist anymore, this property may still
   * have a value taken from the download metadata. If the metadata has never
   * been available in this session and the size cannot be obtained from the
   * file because it has already been deleted, this property will be zero.
   *
   * For single-file downloads, this property will always match the actual file
   * size on disk, while the totalBytes property of the Download object, when
   * available, may represent the size of the encoded data instead.
   *
   * For downloads involving multiple files, like complete web pages saved to
   * disk, the meaning of this value is undefined. It currently matches the size
   * of the main file only rather than the sum of all the written data.
   *
   * This is a dynamic property updated when the download finishes or when the
   * "refresh" method of the Download object is called. It can be used by the
   * front-end to reduce I/O compared to checking the target file directly.
   */
  size: 0,

  /**
   * Sets the "exists" and "size" properties based on the actual file on disk.
   *
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects JavaScript exception.
   */
  async refresh() {
    try {
      this.size = (await OS.File.stat(this.path)).size;
      this.exists = true;
    } catch (ex) {
      // Report any error not caused by the file not being there. In any case,
      // the size of the download is not updated and the known value is kept.
      if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
        Cu.reportError(ex);
      }
      this.exists = false;
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    // Simplify the representation if we don't have other details.
    if (!this.partFilePath && !this._unknownProperties) {
      return this.path;
    }

    let serializable = { path: this.path,
                         partFilePath: this.partFilePath };
    serializeUnknownProperties(this, serializable);
    return serializable;
  },
};

/**
 * Creates a new DownloadTarget object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadTarget object.  This may be a
 *        string containing the path of the target file, an nsIFile, or an
 *        object with the following properties:
 *        {
 *          path: String containing the path of the target file.
 *          partFilePath: optional string containing the part file path.
 *        }
 *
 * @return The newly created DownloadTarget object.
 */
this.DownloadTarget.fromSerializable = function(aSerializable) {
  let target = new DownloadTarget();
  if (isString(aSerializable)) {
    // Convert String objects to primitive strings at this point.
    target.path = aSerializable.toString();
  } else if (aSerializable instanceof Ci.nsIFile) {
    // Read the "path" property of nsIFile after checking the object type.
    target.path = aSerializable.path;
  } else {
    // Read the "path" property of the serializable DownloadTarget
    // representation, converting String objects to primitive strings.
    target.path = aSerializable.path.toString();
    if ("partFilePath" in aSerializable) {
      target.partFilePath = aSerializable.partFilePath;
    }

    deserializeUnknownProperties(target, aSerializable, property =>
      property != "path" && property != "partFilePath");
  }
  return target;
};

/**
 * Provides detailed information about a download failure.
 *
 * @param aProperties
 *        Object which may contain any of the following properties:
 *          {
 *            result: Result error code, defaulting to Cr.NS_ERROR_FAILURE
 *            message: String error message to be displayed, or null to use the
 *                     message associated with the result code.
 *            inferCause: If true, attempts to determine if the cause of the
 *                        download is a network failure or a local file failure,
 *                        based on a set of known values of the result code.
 *                        This is useful when the error is received by a
 *                        component that handles both aspects of the download.
 *          }
 *        The properties object may also contain any of the DownloadError's
 *        because properties, which will be set accordingly in the error object.
 */
this.DownloadError = function(aProperties) {
  const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
  const NS_ERROR_MODULE_NETWORK = 6;
  const NS_ERROR_MODULE_FILES = 13;

  // Set the error name used by the Error object prototype first.
  this.name = "DownloadError";
  this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
  if (aProperties.message) {
    this.message = aProperties.message;
  } else if (aProperties.becauseBlocked ||
             aProperties.becauseBlockedByParentalControls ||
             aProperties.becauseBlockedByReputationCheck ||
             aProperties.becauseBlockedByRuntimePermissions) {
    this.message = "Download blocked.";
  } else {
    let exception = new Components.Exception("", this.result);
    this.message = exception.toString();
  }
  if (aProperties.inferCause) {
    let module = ((this.result & 0x7FFF0000) >> 16) -
                 NS_ERROR_MODULE_BASE_OFFSET;
    this.becauseSourceFailed = (module == NS_ERROR_MODULE_NETWORK);
    this.becauseTargetFailed = (module == NS_ERROR_MODULE_FILES);
  } else {
    if (aProperties.becauseSourceFailed) {
      this.becauseSourceFailed = true;
    }
    if (aProperties.becauseTargetFailed) {
      this.becauseTargetFailed = true;
    }
  }

  if (aProperties.becauseBlockedByParentalControls) {
    this.becauseBlocked = true;
    this.becauseBlockedByParentalControls = true;
  } else if (aProperties.becauseBlockedByReputationCheck) {
    this.becauseBlocked = true;
    this.becauseBlockedByReputationCheck = true;
    this.reputationCheckVerdict = aProperties.reputationCheckVerdict || "";
  } else if (aProperties.becauseBlockedByRuntimePermissions) {
    this.becauseBlocked = true;
    this.becauseBlockedByRuntimePermissions = true;
  } else if (aProperties.becauseBlocked) {
    this.becauseBlocked = true;
  }

  if (aProperties.innerException) {
    this.innerException = aProperties.innerException;
  }

  this.stack = new Error().stack;
}

/**
 * These constants are used by the reputationCheckVerdict property and indicate
 * the detailed reason why a download is blocked.
 *
 * @note These values should not be changed because they can be serialized.
 */
this.DownloadError.BLOCK_VERDICT_MALWARE = "Malware";
this.DownloadError.BLOCK_VERDICT_POTENTIALLY_UNWANTED = "PotentiallyUnwanted";
this.DownloadError.BLOCK_VERDICT_UNCOMMON = "Uncommon";

this.DownloadError.prototype = {
  __proto__: Error.prototype,

  /**
   * The result code associated with this error.
   */
  result: false,

  /**
   * Indicates an error occurred while reading from the remote location.
   */
  becauseSourceFailed: false,

  /**
   * Indicates an error occurred while writing to the local target.
   */
  becauseTargetFailed: false,

  /**
   * Indicates the download failed because it was blocked.  If the reason for
   * blocking is known, the corresponding property will be also set.
   */
  becauseBlocked: false,

  /**
   * Indicates the download was blocked because downloads are globally
   * disallowed by the Parental Controls or Family Safety features on Windows.
   */
  becauseBlockedByParentalControls: false,

  /**
   * Indicates the download was blocked because it failed the reputation check
   * and may be malware.
   */
  becauseBlockedByReputationCheck: false,

  /**
   * Indicates the download was blocked because a runtime permission required to
   * download files was not granted.
   *
   * This does not apply to all systems. On Android this flag is set to true if
   * a needed runtime permission (storage) has not been granted by the user.
   */
  becauseBlockedByRuntimePermissions: false,

  /**
   * If becauseBlockedByReputationCheck is true, indicates the detailed reason
   * why the download was blocked, according to the "BLOCK_VERDICT_" constants.
   *
   * If the download was not blocked or the reason for the block is unknown,
   * this will be an empty string.
   */
  reputationCheckVerdict: "",

  /**
   * If this DownloadError was caused by an exception this property will
   * contain the original exception. This will not be serialized when saving
   * to the store.
   */
  innerException: null,

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    let serializable = {
      result: this.result,
      message: this.message,
      becauseSourceFailed: this.becauseSourceFailed,
      becauseTargetFailed: this.becauseTargetFailed,
      becauseBlocked: this.becauseBlocked,
      becauseBlockedByParentalControls: this.becauseBlockedByParentalControls,
      becauseBlockedByReputationCheck: this.becauseBlockedByReputationCheck,
      becauseBlockedByRuntimePermissions: this.becauseBlockedByRuntimePermissions,
      reputationCheckVerdict: this.reputationCheckVerdict,
    };

    serializeUnknownProperties(this, serializable);
    return serializable;
  },
};

/**
 * Creates a new DownloadError object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadError object.
 *
 * @return The newly created DownloadError object.
 */
this.DownloadError.fromSerializable = function(aSerializable) {
  let e = new DownloadError(aSerializable);
  deserializeUnknownProperties(e, aSerializable, property =>
    property != "result" &&
    property != "message" &&
    property != "becauseSourceFailed" &&
    property != "becauseTargetFailed" &&
    property != "becauseBlocked" &&
    property != "becauseBlockedByParentalControls" &&
    property != "becauseBlockedByReputationCheck" &&
    property != "becauseBlockedByRuntimePermissions" &&
    property != "reputationCheckVerdict");

  return e;
};

/**
 * Template for an object that actually transfers the data for the download.
 */
this.DownloadSaver = function() {}

this.DownloadSaver.prototype = {
  /**
   * Download object for raising notifications and reading properties.
   *
   * If the tryToKeepPartialData property of the download object is false, the
   * saver should never try to keep partially downloaded data if the download
   * fails.
   */
  download: null,

  /**
   * Executes the download.
   *
   * @param aSetProgressBytesFn
   *        This function may be called by the saver to report progress. It
   *        takes three arguments: the first is the number of bytes transferred
   *        until now, the second is the total number of bytes to be
   *        transferred (or -1 if unknown), the third indicates whether the
   *        partially downloaded data can be used when restarting the download
   *        if it fails or is canceled.
   * @param aSetPropertiesFn
   *        This function may be called by the saver to report information
   *        about new download properties discovered by the saver during the
   *        download process. It takes an object where the keys represents
   *        the names of the properties to set, and the value represents the
   *        value to set.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  execute: function DS_execute(aSetProgressBytesFn, aSetPropertiesFn) {
    throw new Error("Not implemented.");
  },

  /**
   * Cancels the download.
   */
  cancel: function DS_cancel() {
    throw new Error("Not implemented.");
  },

  /**
   * Removes any partial data kept as part of a canceled or failed download.
   *
   * This method is never called until the promise returned by "execute" is
   * either resolved or rejected, and the "execute" method is not called again
   * until the promise returned by this method is resolved or rejected.
   *
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects JavaScript exception.
   */
  removePartialData: function DS_removePartialData() {
    return Promise.resolve();
  },

  /**
   * This can be called by the saver implementation when the download is already
   * started, to add it to the browsing history.  This method has no effect if
   * the download is private.
   */
  addToHistory() {
    if (this.download.source.isPrivate) {
      return;
    }

    let sourceUri = NetUtil.newURI(this.download.source.url);
    let referrer = this.download.source.referrer;
    let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
    let targetUri = NetUtil.newURI(new FileUtils.File(
                                       this.download.target.path));

    // The start time is always available when we reach this point.
    let startPRTime = this.download.startTime.getTime() * 1000;

    try {
      gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
                                   targetUri);
    } catch (ex) {
      if (!(ex instanceof Components.Exception) ||
          ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw ex;
      }
      //
      // Under normal operation the download history service may not
      // be available. We don't want all downloads that are public to fail
      // when this happens so we'll ignore this error and this error only!
      //
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    throw new Error("Not implemented.");
  },

  /**
   * Returns the SHA-256 hash of the downloaded file, if it exists.
   */
  getSha256Hash() {
    throw new Error("Not implemented.");
  },

  getSignatureInfo() {
    throw new Error("Not implemented.");
  },
}; // DownloadSaver

/**
 * Creates a new DownloadSaver object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadSaver object.  If no initial
 *        state information for the saver object is needed, can be a string
 *        representing the class of the download operation, for example "copy".
 *
 * @return The newly created DownloadSaver object.
 */
this.DownloadSaver.fromSerializable = function(aSerializable) {
  let serializable = isString(aSerializable) ? { type: aSerializable }
                                             : aSerializable;
  let saver;
  switch (serializable.type) {
    case "copy":
      saver = DownloadCopySaver.fromSerializable(serializable);
      break;
    case "legacy":
      saver = DownloadLegacySaver.fromSerializable(serializable);
      break;
    case "pdf":
      saver = DownloadPDFSaver.fromSerializable(serializable);
      break;
    default:
      throw new Error("Unrecoginzed download saver type.");
  }
  return saver;
};

/**
 * Saver object that simply copies the entire source file to the target.
 */
this.DownloadCopySaver = function() {}

this.DownloadCopySaver.prototype = {
  __proto__: DownloadSaver.prototype,

  /**
   * BackgroundFileSaver object currently handling the download.
   */
  _backgroundFileSaver: null,

  /**
   * Indicates whether the "cancel" method has been called.  This is used to
   * prevent the request from starting in case the operation is canceled before
   * the BackgroundFileSaver instance has been created.
   */
  _canceled: false,

  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file. This is null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _sha256Hash: null,

  /**
   * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
   * if the file is signed. This is empty if the file is unsigned, and null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _signatureInfo: null,

  /**
   * Save the redirects chain as an nsIArray of nsIPrincipal.
   */
  _redirects: null,

  /**
   * True if the associated download has already been added to browsing history.
   */
  alreadyAddedToHistory: false,

  /**
   * String corresponding to the entityID property of the nsIResumableChannel
   * used to execute the download, or null if the channel was not resumable or
   * the saver was instructed not to keep partially downloaded data.
   */
  entityID: null,

  /**
   * Implements "DownloadSaver.execute".
   */
  execute: function DCS_execute(aSetProgressBytesFn, aSetPropertiesFn) {
    let copySaver = this;

    this._canceled = false;

    let download = this.download;
    let targetPath = download.target.path;
    let partFilePath = download.target.partFilePath;
    let keepPartialData = download.tryToKeepPartialData;

    return (async () => {
      // Add the download to history the first time it is started in this
      // session.  If the download is restarted in a different session, a new
      // history visit will be added.  We do this just to avoid the complexity
      // of serializing this state between sessions, since adding a new visit
      // does not have any noticeable side effect.
      if (!this.alreadyAddedToHistory) {
        this.addToHistory();
        this.alreadyAddedToHistory = true;
      }

      // To reduce the chance that other downloads reuse the same final target
      // file name, we should create a placeholder as soon as possible, before
      // starting the network request.  The placeholder is also required in case
      // we are using a ".part" file instead of the final target while the
      // download is in progress.
      try {
        // If the file already exists, don't delete its contents yet.
        let file = await OS.File.open(targetPath, { write: true });
        await file.close();
      } catch (ex) {
        if (!(ex instanceof OS.File.Error)) {
          throw ex;
        }
        // Throw a DownloadError indicating that the operation failed because of
        // the target file.  We cannot translate this into a specific result
        // code, but we preserve the original message using the toString method.
        let error = new DownloadError({ message: ex.toString() });
        error.becauseTargetFailed = true;
        throw error;
      }

      try {
        let deferSaveComplete = PromiseUtils.defer();

        if (this._canceled) {
          // Don't create the BackgroundFileSaver object if we have been
          // canceled meanwhile.
          throw new DownloadError({ message: "Saver canceled." });
        }

        // Create the object that will save the file in a background thread.
        let backgroundFileSaver = new BackgroundFileSaverStreamListener();
        try {
          // When the operation completes, reflect the status in the promise
          // returned by this download execution function.
          backgroundFileSaver.observer = {
            onTargetChange() { },
            onSaveComplete: (aSaver, aStatus) => {
              // Send notifications now that we can restart if needed.
              if (Components.isSuccessCode(aStatus)) {
                // Save the hash before freeing backgroundFileSaver.
                this._sha256Hash = aSaver.sha256Hash;
                this._signatureInfo = aSaver.signatureInfo;
                this._redirects = aSaver.redirects;
                deferSaveComplete.resolve();
              } else {
                // Infer the origin of the error from the failure code, because
                // BackgroundFileSaver does not provide more specific data.
                let properties = { result: aStatus, inferCause: true };
                deferSaveComplete.reject(new DownloadError(properties));
              }
              // Free the reference cycle, to release resources earlier.
              backgroundFileSaver.observer = null;
              this._backgroundFileSaver = null;
            },
          };

          // Create a channel from the source, and listen to progress
          // notifications.
          let channel = NetUtil.newChannel({
            uri: download.source.url,
            loadUsingSystemPrincipal: true,
          });
          if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
            channel.setPrivate(download.source.isPrivate);
          }
          if (channel instanceof Ci.nsIHttpChannel &&
              download.source.referrer) {
            channel.referrer = NetUtil.newURI(download.source.referrer);
          }

          // This makes the channel be corretly throttled during page loads
          // and also prevents its caching.
          if (channel instanceof Ci.nsIHttpChannelInternal) {
            channel.channelIsForDownload = true;
          }

          // If we have data that we can use to resume the download from where
          // it stopped, try to use it.
          let resumeAttempted = false;
          let resumeFromBytes = 0;
          if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
              partFilePath && keepPartialData) {
            try {
              let stat = await OS.File.stat(partFilePath);
              channel.resumeAt(stat.size, this.entityID);
              resumeAttempted = true;
              resumeFromBytes = stat.size;
            } catch (ex) {
              if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
                throw ex;
              }
            }
          }

          channel.notificationCallbacks = {
            QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor]),
            getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
            onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
                                                 aProgressMax) {
              let currentBytes = resumeFromBytes + aProgress;
              let totalBytes = aProgressMax == -1 ? -1 : (resumeFromBytes +
                                                          aProgressMax);
              aSetProgressBytesFn(currentBytes, totalBytes, aProgress > 0 &&
                                  partFilePath && keepPartialData);
            },
            onStatus() { },
          };

          // If the callback was set, handle it now before opening the channel.
          if (download.source.adjustChannel) {
            await download.source.adjustChannel(channel);
          }

          // Open the channel, directing output to the background file saver.
          backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);
          channel.asyncOpen2({
            onStartRequest: function(aRequest, aContext) {
              backgroundFileSaver.onStartRequest(aRequest, aContext);

              // Check if the request's response has been blocked by Windows
              // Parental Controls with an HTTP 450 error code.
              if (aRequest instanceof Ci.nsIHttpChannel &&
                  aRequest.responseStatus == 450) {
                // Set a flag that can be retrieved later when handling the
                // cancellation so that the proper error can be thrown.
                this.download._blockedByParentalControls = true;
                aRequest.cancel(Cr.NS_BINDING_ABORTED);
                return;
              }

              aSetPropertiesFn({ contentType: channel.contentType });

              // Ensure we report the value of "Content-Length", if available,
              // even if the download doesn't generate any progress events
              // later.
              if (channel.contentLength >= 0) {
                aSetProgressBytesFn(0, channel.contentLength);
              }

              // If the URL we are downloading from includes a file extension
              // that matches the "Content-Encoding" header, for example ".gz"
              // with a "gzip" encoding, we should save the file in its encoded
              // form.  In all other cases, we decode the body while saving.
              if (channel instanceof Ci.nsIEncodedChannel &&
                  channel.contentEncodings) {
                let uri = channel.URI;
                if (uri instanceof Ci.nsIURL && uri.fileExtension) {
                  // Only the first, outermost encoding is considered.
                  let encoding = channel.contentEncodings.getNext();
                  if (encoding) {
                    channel.applyConversion =
                      gExternalHelperAppService.applyDecodingForExtension(
                                                uri.fileExtension, encoding);
                  }
                }
              }

              if (keepPartialData) {
                // If the source is not resumable, don't keep partial data even
                // if we were asked to try and do it.
                if (aRequest instanceof Ci.nsIResumableChannel) {
                  try {
                    // If reading the ID succeeds, the source is resumable.
                    this.entityID = aRequest.entityID;
                  } catch (ex) {
                    if (!(ex instanceof Components.Exception) ||
                        ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
                      throw ex;
                    }
                    keepPartialData = false;
                  }
                } else {
                  keepPartialData = false;
                }
              }

              // Enable hashing and signature verification before setting the
              // target.
              backgroundFileSaver.enableSha256();
              backgroundFileSaver.enableSignatureInfo();
              if (partFilePath) {
                // If we actually resumed a request, append to the partial data.
                if (resumeAttempted) {
                  // TODO: Handle Cr.NS_ERROR_ENTITY_CHANGED
                  backgroundFileSaver.enableAppend();
                }

                // Use a part file, determining if we should keep it on failure.
                backgroundFileSaver.setTarget(new FileUtils.File(partFilePath),
                                              keepPartialData);
              } else {
                // Set the final target file, and delete it on failure.
                backgroundFileSaver.setTarget(new FileUtils.File(targetPath),
                                              false);
              }
            }.bind(copySaver),

            onStopRequest(aRequest, aContext, aStatusCode) {
              try {
                backgroundFileSaver.onStopRequest(aRequest, aContext,
                                                  aStatusCode);
              } finally {
                // If the data transfer completed successfully, indicate to the
                // background file saver that the operation can finish.  If the
                // data transfer failed, the saver has been already stopped.
                if (Components.isSuccessCode(aStatusCode)) {
                  backgroundFileSaver.finish(Cr.NS_OK);
                }
              }
            },

            onDataAvailable(aRequest, aContext, aInputStream,
                                      aOffset, aCount) {
              backgroundFileSaver.onDataAvailable(aRequest, aContext,
                                                  aInputStream, aOffset,
                                                  aCount);
            },
          });

          // We should check if we have been canceled in the meantime, after
          // all the previous asynchronous operations have been executed and
          // just before we set the _backgroundFileSaver property.
          if (this._canceled) {
            throw new DownloadError({ message: "Saver canceled." });
          }

          // If the operation succeeded, store the object to allow cancellation.
          this._backgroundFileSaver = backgroundFileSaver;
        } catch (ex) {
          // In case an error occurs while setting up the chain of objects for
          // the download, ensure that we release the resources of the saver.
          backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
          // Since we're not going to handle deferSaveComplete.promise below,
          // we need to make sure that the rejection is handled.
          deferSaveComplete.promise.catch(() => {});
          throw ex;
        }

        // We will wait on this promise in case no error occurred while setting
        // up the chain of objects for the download.
        await deferSaveComplete.promise;

        await this._checkReputationAndMove(aSetPropertiesFn);
      } catch (ex) {
        // Ensure we always remove the placeholder for the final target file on
        // failure, independently of which code path failed.  In some cases, the
        // background file saver may have already removed the file.
        try {
          await OS.File.remove(targetPath);
        } catch (e2) {
          // If we failed during the operation, we report the error but use the
          // original one as the failure reason of the download.  Note that on
          // Windows we may get an access denied error instead of a no such file
          // error if the file existed before, and was recently deleted.
          if (!(e2 instanceof OS.File.Error &&
                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
            Cu.reportError(e2);
          }
        }
        throw ex;
      }
    })();
  },

  /**
   * Perform the reputation check and cleanup the downloaded data if required.
   * If the download passes the reputation check and is using a part file we
   * will move it to the target path since reputation checking is the final
   * step in the saver.
   *
   * @param aSetPropertiesFn
   *        Function provided to the "execute" method.
   *
   * @return {Promise}
   * @resolves When the reputation check and cleanup is complete.
   * @rejects DownloadError if the download should be blocked.
   */
  async _checkReputationAndMove(aSetPropertiesFn) {
    let download = this.download;
    let targetPath = this.download.target.path;
    let partFilePath = this.download.target.partFilePath;

    let { shouldBlock, verdict } =
        await DownloadIntegration.shouldBlockForReputationCheck(download);
    if (shouldBlock) {
      let newProperties = { progress: 100, hasPartialData: false };

      // We will remove the potentially dangerous file if instructed by
      // DownloadIntegration. We will always remove the file when the
      // download did not use a partial file path, meaning it
      // currently has its final filename.
      if (!DownloadIntegration.shouldKeepBlockedData() || !partFilePath) {
        try {
          await OS.File.remove(partFilePath || targetPath);
        } catch (ex) {
          Cu.reportError(ex);
        }
      } else {
        newProperties.hasBlockedData = true;
      }

      aSetPropertiesFn(newProperties);

      throw new DownloadError({
        becauseBlockedByReputationCheck: true,
        reputationCheckVerdict: verdict,
      });
    }

    if (partFilePath) {
      await OS.File.move(partFilePath, targetPath);
    }
  },

  /**
   * Implements "DownloadSaver.cancel".
   */
  cancel: function DCS_cancel() {
    this._canceled = true;
    if (this._backgroundFileSaver) {
      this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
      this._backgroundFileSaver = null;
    }
  },

  /**
   * Implements "DownloadSaver.removePartialData".
   */
  removePartialData() {
    return (async () => {
      if (this.download.target.partFilePath) {
        try {
          await OS.File.remove(this.download.target.partFilePath);
        } catch (ex) {
          if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
            throw ex;
          }
        }
      }
    })();
  },

  /**
   * Implements "DownloadSaver.toSerializable".
   */
  toSerializable() {
    // Simplify the representation if we don't have other details.
    if (!this.entityID && !this._unknownProperties) {
      return "copy";
    }

    let serializable = { type: "copy",
                         entityID: this.entityID };
    serializeUnknownProperties(this, serializable);
    return serializable;
  },

  /**
   * Implements "DownloadSaver.getSha256Hash"
   */
  getSha256Hash() {
    return this._sha256Hash;
  },

  /*
   * Implements DownloadSaver.getSignatureInfo.
   */
  getSignatureInfo() {
    return this._signatureInfo;
  },

  /*
   * Implements DownloadSaver.getRedirects.
   */
  getRedirects() {
    return this._redirects;
  }
};

/**
 * Creates a new DownloadCopySaver object, with its initial state derived from
 * its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadCopySaver object.
 *
 * @return The newly created DownloadCopySaver object.
 */
this.DownloadCopySaver.fromSerializable = function(aSerializable) {
  let saver = new DownloadCopySaver();
  if ("entityID" in aSerializable) {
    saver.entityID = aSerializable.entityID;
  }

  deserializeUnknownProperties(saver, aSerializable, property =>
    property != "entityID" && property != "type");

  return saver;
};

/**
 * Saver object that integrates with the legacy nsITransfer interface.
 *
 * For more background on the process, see the DownloadLegacyTransfer object.
 */
this.DownloadLegacySaver = function() {
  this.deferExecuted = PromiseUtils.defer();
  this.deferCanceled = PromiseUtils.defer();
}

this.DownloadLegacySaver.prototype = {
  __proto__: DownloadSaver.prototype,

  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
   * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
   * invoked.
   */
  _sha256Hash: null,

  /**
   * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
   * if the file is signed. This is empty if the file is unsigned, and null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _signatureInfo: null,

  /**
   * Save the redirect chain as an nsIArray of nsIPrincipal.
   */
  _redirects: null,

  /**
   * nsIRequest object associated to the status and progress updates we
   * received.  This object is null before we receive the first status and
   * progress update, and is also reset to null when the download is stopped.
   */
  request: null,

  /**
   * This deferred object contains a promise that is resolved as soon as this
   * download finishes successfully, and is rejected in case the download is
   * canceled or receives a failure notification through nsITransfer.
   */
  deferExecuted: null,

  /**
   * This deferred object contains a promise that is resolved if the download
   * receives a cancellation request through the "cancel" method, and is never
   * rejected.  The nsITransfer implementation will register a handler that
   * actually causes the download cancellation.
   */
  deferCanceled: null,

  /**
   * This is populated with the value of the aSetProgressBytesFn argument of the
   * "execute" method, and is null before the method is called.
   */
  setProgressBytesFn: null,

  /**
   * Called by the nsITransfer implementation while the download progresses.
   *
   * @param aCurrentBytes
   *        Number of bytes transferred until now.
   * @param aTotalBytes
   *        Total number of bytes to be transferred, or -1 if unknown.
   */
  onProgressBytes: function DLS_onProgressBytes(aCurrentBytes, aTotalBytes) {
    this.progressWasNotified = true;

    // Ignore progress notifications until we are ready to process them.
    if (!this.setProgressBytesFn) {
      // Keep the data from the last progress notification that was received.
      this.currentBytes = aCurrentBytes;
      this.totalBytes = aTotalBytes;
      return;
    }

    let hasPartFile = !!this.download.target.partFilePath;

    this.setProgressBytesFn(aCurrentBytes, aTotalBytes,
                            aCurrentBytes > 0 && hasPartFile);
  },

  /**
   * Whether the onProgressBytes function has been called at least once.
   */
  progressWasNotified: false,

  /**
   * Called by the nsITransfer implementation when the request has started.
   *
   * @param aRequest
   *        nsIRequest associated to the status update.
   * @param aAlreadyAddedToHistory
   *        Indicates that the nsIExternalHelperAppService component already
   *        added the download to the browsing history, unless it was started
   *        from a private browsing window.  When this parameter is false, the
   *        download is added to the browsing history here.  Private downloads
   *        are never added to history even if this parameter is false.
   */
  onTransferStarted(aRequest, aAlreadyAddedToHistory) {
    // Store a reference to the request, used in some cases when handling
    // completion, and also checked during the download by unit tests.
    this.request = aRequest;

    // Store the entity ID to use for resuming if required.
    if (this.download.tryToKeepPartialData &&
        aRequest instanceof Ci.nsIResumableChannel) {
      try {
        // If reading the ID succeeds, the source is resumable.
        this.entityID = aRequest.entityID;
      } catch (ex) {
        if (!(ex instanceof Components.Exception) ||
            ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
          throw ex;
        }
      }
    }

    // For legacy downloads, we must update the referrer at this time.
    if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
      this.download.source.referrer = aRequest.referrer.spec;
    }

    if (!aAlreadyAddedToHistory) {
      this.addToHistory();
    }
  },

  /**
   * Called by the nsITransfer implementation when the request has finished.
   *
   * @param aStatus
   *        Status code received by the nsITransfer implementation.
   */
  onTransferFinished: function DLS_onTransferFinished(aStatus) {
    if (Components.isSuccessCode(aStatus)) {
      this.deferExecuted.resolve();
    } else {
      // Infer the origin of the error from the failure code, because more
      // specific data is not available through the nsITransfer implementation.
      let properties = { result: aStatus, inferCause: true };
      this.deferExecuted.reject(new DownloadError(properties));
    }
  },

  /**
   * When the first execution of the download finished, it can be restarted by
   * using a DownloadCopySaver object instead of the original legacy component
   * that executed the download.
   */
  firstExecutionFinished: false,

  /**
   * In case the download is restarted after the first execution finished, this
   * property contains a reference to the DownloadCopySaver that is executing
   * the new download attempt.
   */
  copySaver: null,

  /**
   * String corresponding to the entityID property of the nsIResumableChannel
   * used to execute the download, or null if the channel was not resumable or
   * the saver was instructed not to keep partially downloaded data.
   */
  entityID: null,

  /**
   * Implements "DownloadSaver.execute".
   */
  execute: function DLS_execute(aSetProgressBytesFn, aSetPropertiesFn) {
    // Check if this is not the first execution of the download.  The Download
    // object guarantees that this function is not re-entered during execution.
    if (this.firstExecutionFinished) {
      if (!this.copySaver) {
        this.copySaver = new DownloadCopySaver();
        this.copySaver.download = this.download;
        this.copySaver.entityID = this.entityID;
        this.copySaver.alreadyAddedToHistory = true;
      }
      return this.copySaver.execute.apply(this.copySaver, arguments);
    }

    this.setProgressBytesFn = aSetProgressBytesFn;
    if (this.progressWasNotified) {
      this.onProgressBytes(this.currentBytes, this.totalBytes);
    }

    return (async () => {
      try {
        // Wait for the component that executes the download to finish.
        await this.deferExecuted.promise;

        // At this point, the "request" property has been populated.  Ensure we
        // report the value of "Content-Length", if available, even if the
        // download didn't generate any progress events.
        if (!this.progressWasNotified &&
            this.request instanceof Ci.nsIChannel &&
            this.request.contentLength >= 0) {
          aSetProgressBytesFn(0, this.request.contentLength);
        }

        // If the component executing the download provides the path of a
        // ".part" file, it means that it expects the listener to move the file
        // to its final target path when the download succeeds.  In this case,
        // an empty ".part" file is created even if no data was received from
        // the source.
        //
        // When no ".part" file path is provided the download implementation may
        // not have created the target file (if no data was received from the
        // source).  In this case, ensure that an empty file is created as
        // expected.
        if (!this.download.target.partFilePath) {
          try {
            // This atomic operation is more efficient than an existence check.
            let file = await OS.File.open(this.download.target.path,
                                          { create: true });
            await file.close();
          } catch (ex) {
            if (!(ex instanceof OS.File.Error) || !ex.becauseExists) {
              throw ex;
            }
          }
        }

        await this._checkReputationAndMove(aSetPropertiesFn);

      } catch (ex) {
        // Ensure we always remove the final target file on failure,
        // independently of which code path failed.  In some cases, the
        // component executing the download may have already removed the file.
        try {
          await OS.File.remove(this.download.target.path);
        } catch (e2) {
          // If we failed during the operation, we report the error but use the
          // original one as the failure reason of the download.  Note that on
          // Windows we may get an access denied error instead of a no such file
          // error if the file existed before, and was recently deleted.
          if (!(e2 instanceof OS.File.Error &&
                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
            Cu.reportError(e2);
          }
        }
        // In case the operation failed, ensure we stop downloading data.  Since
        // we never re-enter this function, deferCanceled is always available.
        this.deferCanceled.resolve();
        throw ex;
      } finally {
        // We don't need the reference to the request anymore.  We must also set
        // deferCanceled to null in order to free any indirect references it
        // may hold to the request.
        this.request = null;
        this.deferCanceled = null;
        // Allow the download to restart through a DownloadCopySaver.
        this.firstExecutionFinished = true;
      }
    })();
  },

  _checkReputationAndMove() {
    return DownloadCopySaver.prototype._checkReputationAndMove
                                      .apply(this, arguments);
  },

  /**
   * Implements "DownloadSaver.cancel".
   */
  cancel: function DLS_cancel() {
    // We may be using a DownloadCopySaver to handle resuming.
    if (this.copySaver) {
      return this.copySaver.cancel.apply(this.copySaver, arguments);
    }

    // If the download hasn't stopped already, resolve deferCanceled so that the
    // operation is canceled as soon as a cancellation handler is registered.
    // Note that the handler might not have been registered yet.
    if (this.deferCanceled) {
      this.deferCanceled.resolve();
    }
  },

  /**
   * Implements "DownloadSaver.removePartialData".
   */
  removePartialData() {
    // DownloadCopySaver and DownloadLeagcySaver use the same logic for removing
    // partially downloaded data, though this implementation isn't shared by
    // other saver types, thus it isn't found on their shared prototype.
    return DownloadCopySaver.prototype.removePartialData.call(this);
  },

  /**
   * Implements "DownloadSaver.toSerializable".
   */
  toSerializable() {
    // This object depends on legacy components that are created externally,
    // thus it cannot be rebuilt during deserialization.  To support resuming
    // across different browser sessions, this object is transformed into a
    // DownloadCopySaver for the purpose of serialization.
    return DownloadCopySaver.prototype.toSerializable.call(this);
  },

  /**
   * Implements "DownloadSaver.getSha256Hash".
   */
  getSha256Hash() {
    if (this.copySaver) {
      return this.copySaver.getSha256Hash();
    }
    return this._sha256Hash;
  },

  /**
   * Called by the nsITransfer implementation when the hash is available.
   */
  setSha256Hash(hash) {
    this._sha256Hash = hash;
  },

  /**
   * Implements "DownloadSaver.getSignatureInfo".
   */
  getSignatureInfo() {
    if (this.copySaver) {
      return this.copySaver.getSignatureInfo();
    }
    return this._signatureInfo;
  },

  /**
   * Called by the nsITransfer implementation when the hash is available.
   */
  setSignatureInfo(signatureInfo) {
    this._signatureInfo = signatureInfo;
  },

  /**
   * Implements "DownloadSaver.getRedirects".
   */
  getRedirects() {
    if (this.copySaver) {
      return this.copySaver.getRedirects();
    }
    return this._redirects;
  },

  /**
   * Called by the nsITransfer implementation when the redirect chain is
   * available.
   */
  setRedirects(redirects) {
    this._redirects = redirects;
  },
};

/**
 * Returns a new DownloadLegacySaver object.  This saver type has a
 * deserializable form only when creating a new object in memory, because it
 * cannot be serialized to disk.
 */
this.DownloadLegacySaver.fromSerializable = function() {
  return new DownloadLegacySaver();
};

/**
 * This DownloadSaver type creates a PDF file from the current document in a
 * given window, specified using the windowRef property of the DownloadSource
 * object associated with the download.
 *
 * In order to prevent the download from saving a different document than the one
 * originally loaded in the window, any attempt to restart the download will fail.
 *
 * Since this DownloadSaver type requires a live document as a source, it cannot
 * be persisted across sessions, unless the download already succeeded.
 */
this.DownloadPDFSaver = function() {
}

this.DownloadPDFSaver.prototype = {
  __proto__: DownloadSaver.prototype,

  /**
   * An nsIWebBrowserPrint instance for printing this page.
   * This is null when saving has not started or has completed,
   * or while the operation is being canceled.
   */
  _webBrowserPrint: null,

  /**
   * Implements "DownloadSaver.execute".
   */
  execute(aSetProgressBytesFn, aSetPropertiesFn) {
    return (async () => {
      if (!this.download.source.windowRef) {
        throw new DownloadError({
          message: "PDF saver must be passed an open window, and cannot be restarted.",
          becauseSourceFailed: true,
        });
      }

      let win = this.download.source.windowRef.get();

      // Set windowRef to null to avoid re-trying.
      this.download.source.windowRef = null;

      if (!win) {
        throw new DownloadError({
          message: "PDF saver can't save a window that has been closed.",
          becauseSourceFailed: true,
        });
      }

      this.addToHistory();

      let targetPath = this.download.target.path;

      // An empty target file must exist for the PDF printer to work correctly.
      let file = await OS.File.open(targetPath, { truncate: true });
      await file.close();

      let printSettings = gPrintSettingsService.newPrintSettings;

      printSettings.printToFile = true;
      printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
      printSettings.toFileName = targetPath;

      printSettings.printSilent = true;
      printSettings.showPrintProgress = false;

      printSettings.printBGImages = true;
      printSettings.printBGColors = true;
      printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
      printSettings.headerStrCenter = "";
      printSettings.headerStrLeft = "";
      printSettings.headerStrRight = "";
      printSettings.footerStrCenter = "";
      printSettings.footerStrLeft = "";
      printSettings.footerStrRight = "";

      this._webBrowserPrint = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIWebBrowserPrint);

      try {
        await new Promise((resolve, reject) => {
          this._webBrowserPrint.print(printSettings, {
            onStateChange(webProgress, request, stateFlags, status) {
              if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
                if (!Components.isSuccessCode(status)) {
                  reject(new DownloadError({ result: status,
                                             inferCause: true }));
                } else {
                  resolve();
                }
              }
            },
            onProgressChange(webProgress, request, curSelfProgress,
                                       maxSelfProgress, curTotalProgress,
                                       maxTotalProgress) {
              aSetProgressBytesFn(curTotalProgress, maxTotalProgress, false);
            },
            onLocationChange() {},
            onStatusChange() {},
            onSecurityChange() {},
          });
        });
      } finally {
        // Remove the print object to avoid leaks
        this._webBrowserPrint = null;
      }

      let fileInfo = await OS.File.stat(targetPath);
      aSetProgressBytesFn(fileInfo.size, fileInfo.size, false);
    })();
  },

  /**
   * Implements "DownloadSaver.cancel".
   */
  cancel: function DCS_cancel() {
    if (this._webBrowserPrint) {
      this._webBrowserPrint.cancel();
      this._webBrowserPrint = null;
    }
  },

  /**
   * Implements "DownloadSaver.toSerializable".
   */
  toSerializable() {
    if (this.download.succeeded) {
      return DownloadCopySaver.prototype.toSerializable.call(this);
    }

    // This object needs a window to recreate itself. If it didn't succeded
    // it will not be possible to restart. Returning null here will
    // prevent us from serializing it at all.
    return null;
  },
};

/**
 * Creates a new DownloadPDFSaver object, with its initial state derived from
 * its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadPDFSaver object.
 *
 * @return The newly created DownloadPDFSaver object.
 */
this.DownloadPDFSaver.fromSerializable = function(aSerializable) {
  return new DownloadPDFSaver();
};
PK
!<V**modules/DownloadHistory.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides access to downloads from previous sessions on platforms that store
 * them in a different location than session downloads.
 *
 * This module works with objects that are compatible with Download, while using
 * the Places interfaces internally. Some of the Places objects may also be
 * exposed to allow the consumers to integrate with history view commands.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "DownloadHistory",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

const METADATA_ANNO = "downloads/metaData";

const METADATA_STATE_FINISHED = 1;
const METADATA_STATE_FAILED = 2;
const METADATA_STATE_CANCELED = 3;
const METADATA_STATE_BLOCKED_PARENTAL = 6;
const METADATA_STATE_DIRTY = 8;

/**
 * Provides methods to retrieve downloads from previous sessions and store
 * downloads for future sessions.
 */
this.DownloadHistory = {
  /**
   * Stores new detailed metadata for the given download in history. This is
   * normally called after a download finishes, fails, or is canceled.
   *
   * Failed or canceled downloads with partial data are not stored as paused,
   * because the information from the session download is required for resuming.
   *
   * @param download
   *        Download object whose metadata should be updated. If the object
   *        represents a private download, the call has no effect.
   */
  updateMetaData(download) {
    if (download.source.isPrivate || !download.stopped) {
      return;
    }

    let state = METADATA_STATE_CANCELED;
    if (download.succeeded) {
      state = METADATA_STATE_FINISHED;
    } else if (download.error) {
      if (download.error.becauseBlockedByParentalControls) {
        state = METADATA_STATE_BLOCKED_PARENTAL;
      } else if (download.error.becauseBlockedByReputationCheck) {
        state = METADATA_STATE_DIRTY;
      } else {
        state = METADATA_STATE_FAILED;
      }
    }

    let metaData = { state, endTime: download.endTime };
    if (download.succeeded) {
      metaData.fileSize = download.target.size;
    }

    // The verdict may still be present even if the download succeeded.
    if (download.error && download.error.reputationCheckVerdict) {
      metaData.reputationCheckVerdict =
        download.error.reputationCheckVerdict;
    }

    try {
      PlacesUtils.annotations.setPageAnnotation(
                                 Services.io.newURI(download.source.url),
                                 METADATA_ANNO,
                                 JSON.stringify(metaData), 0,
                                 PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
    } catch (ex) {
      Cu.reportError(ex);
    }
  },
};
PK
!<
modules/DownloadIntegration.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides functions to integrate with the host application, handling for
 * example the global prompts on shutdown.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "DownloadIntegration",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                  "resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
                                  "resource://gre/modules/DownloadStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
                                  "resource://gre/modules/DownloadImport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                  "resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CloudStorage",
                                  "resource://gre/modules/CloudStorage.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gDownloadPlatform",
                                   "@mozilla.org/toolkit/download-platform;1",
                                   "mozIDownloadPlatform");
XPCOMUtils.defineLazyServiceGetter(this, "gEnvironment",
                                   "@mozilla.org/process/environment;1",
                                   "nsIEnvironment");
XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
                                   "@mozilla.org/mime;1",
                                   "nsIMIMEService");
XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
                                   "@mozilla.org/uriloader/external-protocol-service;1",
                                   "nsIExternalProtocolService");
XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions",
                                  "resource://gre/modules/RuntimePermissions.jsm");

XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
  if ("@mozilla.org/parental-controls-service;1" in Cc) {
    return Cc["@mozilla.org/parental-controls-service;1"]
      .createInstance(Ci.nsIParentalControlsService);
  }
  return null;
});

XPCOMUtils.defineLazyServiceGetter(this, "gApplicationReputationService",
           "@mozilla.org/downloads/application-reputation-service;1",
           Ci.nsIApplicationReputationService);

XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
                                   "@mozilla.org/telephony/volume-service;1",
                                   "nsIVolumeService");

// We have to use the gCombinedDownloadIntegration identifier because, in this
// module only, the DownloadIntegration identifier refers to the base version.
Integration.downloads.defineModuleGetter(this, "gCombinedDownloadIntegration",
            "resource://gre/modules/DownloadIntegration.jsm",
            "DownloadIntegration");

const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
                                     "initWithCallback");

/**
 * Indicates the delay between a change to the downloads data and the related
 * save operation.
 *
 * For best efficiency, this value should be high enough that the input/output
 * for opening or closing the target file does not overlap with the one for
 * saving the list of downloads.
 */
const kSaveDelayMs = 1500;

/**
 * List of observers to listen against
 */
const kObserverTopics = [
  "quit-application-requested",
  "offline-requested",
  "last-pb-context-exiting",
  "last-pb-context-exited",
  "sleep_notification",
  "suspend_process_notification",
  "wake_notification",
  "resume_process_notification",
  "network:offline-about-to-go-offline",
  "network:offline-status-changed",
  "xpcom-will-shutdown",
];

/**
 * Maps nsIApplicationReputationService verdicts with the DownloadError ones.
 */
const kVerdictMap = {
  [Ci.nsIApplicationReputationService.VERDICT_DANGEROUS]:
                Downloads.Error.BLOCK_VERDICT_MALWARE,
  [Ci.nsIApplicationReputationService.VERDICT_UNCOMMON]:
                Downloads.Error.BLOCK_VERDICT_UNCOMMON,
  [Ci.nsIApplicationReputationService.VERDICT_POTENTIALLY_UNWANTED]:
                Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
  [Ci.nsIApplicationReputationService.VERDICT_DANGEROUS_HOST]:
                Downloads.Error.BLOCK_VERDICT_MALWARE,
};

/**
 * Provides functions to integrate with the host application, handling for
 * example the global prompts on shutdown.
 */
this.DownloadIntegration = {
  /**
   * Main DownloadStore object for loading and saving the list of persistent
   * downloads, or null if the download list was never requested and thus it
   * doesn't need to be persisted.
   */
  _store: null,

  /**
   * Returns whether data for blocked downloads should be kept on disk.
   * Implementations which support unblocking downloads may return true to
   * keep the blocked download on disk until its fate is decided.
   *
   * If a download is blocked and the partial data is kept the Download's
   * 'hasBlockedData' property will be true. In this state Download.unblock()
   * or Download.confirmBlock() may be used to either unblock the download or
   * remove the downloaded data respectively.
   *
   * Even if shouldKeepBlockedData returns true, if the download did not use a
   * partFile the blocked data will be removed - preventing the complete
   * download from existing on disk with its final filename.
   *
   * @return boolean True if data should be kept.
   */
  shouldKeepBlockedData() {
    const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
    return Services.appinfo.ID == FIREFOX_ID;
  },

  /**
   * Performs initialization of the list of persistent downloads, before its
   * first use by the host application.  This function may be called only once
   * during the entire lifetime of the application.
   *
   * @param list
   *        DownloadList object to be initialized.
   *
   * @return {Promise}
   * @resolves When the list has been initialized.
   * @rejects JavaScript exception.
   */
  async initializePublicDownloadList(list) {
    try {
      await this.loadPublicDownloadListFromStore(list);
    } catch (ex) {
      Cu.reportError(ex);
    }

    if (AppConstants.MOZ_PLACES) {
      // After the list of persistent downloads has been loaded, we can add the
      // history observers, even if the load operation failed. This object is kept
      // alive by the history service.
      new DownloadHistoryObserver(list);
    }
  },

  /**
   * Called by initializePublicDownloadList to load the list of persistent
   * downloads, before its first use by the host application.  This function may
   * be called only once during the entire lifetime of the application.
   *
   * @param list
   *        DownloadList object to be populated with the download objects
   *        serialized from the previous session.  This list will be persisted
   *        to disk during the session lifetime.
   *
   * @return {Promise}
   * @resolves When the list has been populated.
   * @rejects JavaScript exception.
   */
  async loadPublicDownloadListFromStore(list) {
    if (this._store) {
      throw new Error("Initialization may be performed only once.");
    }

    this._store = new DownloadStore(list, OS.Path.join(
                                             OS.Constants.Path.profileDir,
                                             "downloads.json"));
    this._store.onsaveitem = this.shouldPersistDownload.bind(this);

    try {
      await this._store.load();
    } catch (ex) {
      Cu.reportError(ex);
    }

    // Add the view used for detecting changes to downloads to be persisted.
    // We must do this after the list of persistent downloads has been loaded,
    // even if the load operation failed. We wait for a complete initialization
    // so other callers cannot modify the list without being detected. The
    // DownloadAutoSaveView is kept alive by the underlying DownloadList.
    await new DownloadAutoSaveView(list, this._store).initialize();
  },

  /**
   * Determines if a Download object from the list of persistent downloads
   * should be saved into a file, so that it can be restored across sessions.
   *
   * This function allows filtering out downloads that the host application is
   * not interested in persisting across sessions, for example downloads that
   * finished successfully.
   *
   * @param aDownload
   *        The Download object to be inspected.  This is originally taken from
   *        the global DownloadList object for downloads that were not started
   *        from a private browsing window.  The item may have been removed
   *        from the list since the save operation started, though in this case
   *        the save operation will be repeated later.
   *
   * @return True to save the download, false otherwise.
   */
  shouldPersistDownload(aDownload) {
    // On all platforms, we save all the downloads currently in progress, as
    // well as stopped downloads for which we retained partially downloaded
    // data or we have blocked data.
    // On Android we store all history; on Desktop, stopped downloads for which
    // we don't need to track the presence of a ".part" file are only retained
    // in the browser history.
    return !aDownload.stopped || aDownload.hasPartialData ||
           aDownload.hasBlockedData || AppConstants.platform == "android";
  },

  /**
   * Returns the system downloads directory asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getSystemDownloadsDirectory() {
    if (this._downloadsDirectory) {
      return this._downloadsDirectory;
    }

    if (AppConstants.platform == "android") {
      // Android doesn't have a $HOME directory, and by default we only have
      // write access to /data/data/org.mozilla.{$APP} and /sdcard
      this._downloadsDirectory = gEnvironment.get("DOWNLOADS_DIRECTORY");
      if (!this._downloadsDirectory) {
        throw new Components.Exception("DOWNLOADS_DIRECTORY is not set.",
                                       Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
      }
    } else {
      try {
        this._downloadsDirectory = this._getDirectory("DfltDwnld");
      } catch(e) {
        this._downloadsDirectory = await this._createDownloadsDirectory("Home");
      }
    }

    return this._downloadsDirectory;
  },
  _downloadsDirectory: null,

  /**
   * Returns the user downloads directory asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getPreferredDownloadsDirectory() {
    let directoryPath = null;
    let prefValue = Services.prefs.getIntPref("browser.download.folderList", 1);

    switch(prefValue) {
      case 0: // Desktop
        directoryPath = this._getDirectory("Desk");
        break;
      case 1: // Downloads
        directoryPath = await this.getSystemDownloadsDirectory();
        break;
      case 2: // Custom
        try {
          let directory = Services.prefs.getComplexValue("browser.download.dir",
                                                         Ci.nsIFile);
          directoryPath = directory.path;
          await OS.File.makeDir(directoryPath, { ignoreExisting: true });
        } catch(ex) {
          // Either the preference isn't set or the directory cannot be created.
          directoryPath = await this.getSystemDownloadsDirectory();
        }
        break;
      case 3: // Cloud Storage
        try {
          directoryPath = await CloudStorage.getDownloadFolder();
        } catch(ex) {}
        if (!directoryPath) {
          directoryPath = await this.getSystemDownloadsDirectory();
        }
        break;
      default:
        directoryPath = await this.getSystemDownloadsDirectory();
    }
    return directoryPath;
  },

  /**
   * Returns the temporary downloads directory asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getTemporaryDownloadsDirectory() {
    let directoryPath = null;
    if (AppConstants.platform == "macosx") {
      directoryPath = await this.getPreferredDownloadsDirectory();
    } else if (AppConstants.platform == "android") {
      directoryPath = await this.getSystemDownloadsDirectory();
    } else {
      directoryPath = this._getDirectory("TmpD");
    }
    return directoryPath;
  },

  /**
   * Checks to determine whether to block downloads for parental controls.
   *
   * aParam aDownload
   *        The download object.
   *
   * @return {Promise}
   * @resolves The boolean indicates to block downloads or not.
   */
  shouldBlockForParentalControls(aDownload) {
    let isEnabled = gParentalControlsService &&
                    gParentalControlsService.parentalControlsEnabled;
    let shouldBlock = isEnabled &&
                      gParentalControlsService.blockFileDownloadsEnabled;

    // Log the event if required by parental controls settings.
    if (isEnabled && gParentalControlsService.loggingEnabled) {
      gParentalControlsService.log(gParentalControlsService.ePCLog_FileDownload,
                                   shouldBlock,
                                   NetUtil.newURI(aDownload.source.url), null);
    }

    return Promise.resolve(shouldBlock);
  },

  /**
   * Checks to determine whether to block downloads for not granted runtime permissions.
   *
   * @return {Promise}
   * @resolves The boolean indicates to block downloads or not.
   */
  async shouldBlockForRuntimePermissions() {
    return AppConstants.platform == "android" &&
           !(await RuntimePermissions.waitForPermissions(
                                      RuntimePermissions.WRITE_EXTERNAL_STORAGE));
  },

  /**
   * Checks to determine whether to block downloads because they might be
   * malware, based on application reputation checks.
   *
   * aParam aDownload
   *        The download object.
   *
   * @return {Promise}
   * @resolves Object with the following properties:
   *           {
   *             shouldBlock: Whether the download should be blocked.
   *             verdict: Detailed reason for the block, according to the
   *                      "Downloads.Error.BLOCK_VERDICT_" constants, or empty
   *                      string if the reason is unknown.
   *           }
   */
  shouldBlockForReputationCheck(aDownload) {
    let hash;
    let sigInfo;
    let channelRedirects;
    try {
      hash = aDownload.saver.getSha256Hash();
      sigInfo = aDownload.saver.getSignatureInfo();
      channelRedirects = aDownload.saver.getRedirects();
    } catch (ex) {
      // Bail if DownloadSaver doesn't have a hash or signature info.
      return Promise.resolve({
        shouldBlock: false,
        verdict: "",
      });
    }
    if (!hash || !sigInfo) {
      return Promise.resolve({
        shouldBlock: false,
        verdict: "",
      });
    }
    return new Promise(resolve => {
      let aReferrer = null;
      if (aDownload.source.referrer) {
        aReferrer = NetUtil.newURI(aDownload.source.referrer);
      }
      gApplicationReputationService.queryReputation({
        sourceURI: NetUtil.newURI(aDownload.source.url),
        referrerURI: aReferrer,
        fileSize: aDownload.currentBytes,
        sha256Hash: hash,
        suggestedFileName: OS.Path.basename(aDownload.target.path),
        signatureInfo: sigInfo,
        redirects: channelRedirects },
        function onComplete(aShouldBlock, aRv, aVerdict) {
          resolve({
            shouldBlock: aShouldBlock,
            verdict: (aShouldBlock && kVerdictMap[aVerdict]) || "",
          });
        });
    });
  },

  /**
   * Checks whether downloaded files should be marked as coming from
   * Internet Zone.
   *
   * @return true if files should be marked
   */
  _shouldSaveZoneInformation() {
    let key = Cc["@mozilla.org/windows-registry-key;1"]
                .createInstance(Ci.nsIWindowsRegKey);
    try {
      key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
               "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
               Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE);
      try {
        return key.readIntValue("SaveZoneInformation") != 1;
      } finally {
        key.close();
      }
    } catch (ex) {
      // If the key is not present, files should be marked by default.
      return true;
    }
  },

  /**
   * Performs platform-specific operations when a download is done.
   *
   * aParam aDownload
   *        The Download object.
   *
   * @return {Promise}
   * @resolves When all the operations completed successfully.
   * @rejects JavaScript exception if any of the operations failed.
   */
  async downloadDone(aDownload) {
    // On Windows, we mark any file saved to the NTFS file system as coming
    // from the Internet security zone unless Group Policy disables the
    // feature.  We do this by writing to the "Zone.Identifier" Alternate
    // Data Stream directly, because the Save method of the
    // IAttachmentExecute interface would trigger operations that may cause
    // the application to hang, or other performance issues.
    // The stream created in this way is forward-compatible with all the
    // current and future versions of Windows.
    if (AppConstants.platform == "win" && this._shouldSaveZoneInformation()) {
      let zone;
      try {
        zone = gDownloadPlatform.mapUrlToZone(aDownload.source.url);
      } catch (e) {
        // Default to Internet Zone if mapUrlToZone failed for
        // whatever reason.
        zone = Ci.mozIDownloadPlatform.ZONE_INTERNET;
      }
      try {
        // Don't write zone IDs for Local, Intranet, or Trusted sites
        // to match Windows behavior.
        if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
          let streamPath = aDownload.target.path + ":Zone.Identifier";
          let stream = await OS.File.open(
            streamPath,
            { create: true },
            { winAllowLengthBeyondMaxPathWithCaveats: true }
          );
          try {
            await stream.write(new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=" + zone + "\r\n"));
          } finally {
            await stream.close();
          }
        }
      } catch (ex) {
        // If writing to the stream fails, we ignore the error and continue.
        // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
        // occur when working on a file system that does not support
        // Alternate Data Streams, like FAT32, thus we don't report this
        // specific error.
        if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
          Cu.reportError(ex);
        }
      }
    }

    // The file with the partially downloaded data has restrictive permissions
    // that don't allow other users on the system to access it.  Now that the
    // download is completed, we need to adjust permissions based on whether
    // this is a permanently downloaded file or a temporary download to be
    // opened read-only with an external application.
    try {
      // The following logic to determine whether this is a temporary download
      // is due to the fact that "deleteTempFileOnExit" is false on Mac, where
      // downloads to be opened with external applications are preserved in
      // the "Downloads" folder like normal downloads.
      let isTemporaryDownload =
        aDownload.launchWhenSucceeded && (aDownload.source.isPrivate ||
        Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
      // Permanently downloaded files are made accessible by other users on
      // this system, while temporary downloads are marked as read-only.
      let options = {};
      if (isTemporaryDownload) {
        options.unixMode = 0o400;
        options.winAttributes = {readOnly: true};
      } else {
        options.unixMode = 0o666;
      }
      // On Unix, the umask of the process is respected.
      await OS.File.setPermissions(aDownload.target.path, options);
    } catch (ex) {
      // We should report errors with making the permissions less restrictive
      // or marking the file as read-only on Unix and Mac, but this should not
      // prevent the download from completing.
      // The setPermissions API error EPERM is expected to occur when working
      // on a file system that does not support file permissions, like FAT32,
      // thus we don't report this error.
      if (!(ex instanceof OS.File.Error) || ex.unixErrno != OS.Constants.libc.EPERM) {
        Cu.reportError(ex);
      }
    }

    let aReferrer = null;
    if (aDownload.source.referrer) {
      aReferrer = NetUtil.newURI(aDownload.source.referrer);
    }

    gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
                                   aReferrer,
                                   new FileUtils.File(aDownload.target.path),
                                   aDownload.contentType,
                                   aDownload.source.isPrivate);
  },

  /**
   * Launches a file represented by the target of a download. This can
   * open the file with the default application for the target MIME type
   * or file extension, or with a custom application if
   * aDownload.launcherPath is set.
   *
   * @param    aDownload
   *           A Download object that contains the necessary information
   *           to launch the file. The relevant properties are: the target
   *           file, the contentType and the custom application chosen
   *           to launch it.
   *
   * @return {Promise}
   * @resolves When the instruction to launch the file has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the file is actually
   *           launched.
   * @rejects  JavaScript exception if there was an error trying to launch
   *           the file.
   */
  async launchDownload(aDownload) {
    let file = new FileUtils.File(aDownload.target.path);

    // In case of a double extension, like ".tar.gz", we only
    // consider the last one, because the MIME service cannot
    // handle multiple extensions.
    let fileExtension = null, mimeInfo = null;
    let match = file.leafName.match(/\.([^.]+)$/);
    if (match) {
      fileExtension = match[1];
    }

    let isWindowsExe = AppConstants.platform == "win" &&
      fileExtension.toLowerCase() == "exe";

    // Ask for confirmation if the file is executable, except for .exe on
    // Windows where the operating system will show the prompt based on the
    // security zone.  We do this here, instead of letting the caller handle
    // the prompt separately in the user interface layer, for two reasons.  The
    // first is because of its security nature, so that add-ons cannot forget
    // to do this check.  The second is that the system-level security prompt
    // would be displayed at launch time in any case.
    if (file.isExecutable() && !isWindowsExe &&
        !(await this.confirmLaunchExecutable(file.path))) {
      return;
    }

    try {
      // The MIME service might throw if contentType == "" and it can't find
      // a MIME type for the given extension, so we'll treat this case as
      // an unknown mimetype.
      mimeInfo = gMIMEService.getFromTypeAndExtension(aDownload.contentType,
                                                      fileExtension);
    } catch (e) { }

    if (aDownload.launcherPath) {
      if (!mimeInfo) {
        // This should not happen on normal circumstances because launcherPath
        // is only set when we had an instance of nsIMIMEInfo to retrieve
        // the custom application chosen by the user.
        throw new Error(
          "Unable to create nsIMIMEInfo to launch a custom application");
      }

      // Custom application chosen
      let localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
                              .createInstance(Ci.nsILocalHandlerApp);
      localHandlerApp.executable = new FileUtils.File(aDownload.launcherPath);

      mimeInfo.preferredApplicationHandler = localHandlerApp;
      mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;

      this.launchFile(file, mimeInfo);
      return;
    }

    // No custom application chosen, let's launch the file with the default
    // handler. First, let's try to launch it through the MIME service.
    if (mimeInfo) {
      mimeInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;

      try {
        this.launchFile(file, mimeInfo);
        return;
      } catch (ex) { }
    }

    // If it didn't work or if there was no MIME info available,
    // let's try to directly launch the file.
    try {
      this.launchFile(file);
      return;
    } catch (ex) { }

    // If our previous attempts failed, try sending it through
    // the system's external "file:" URL handler.
    gExternalProtocolService.loadUrl(NetUtil.newURI(file));
  },

  /**
   * Asks for confirmation for launching the specified executable file. This
   * can be overridden by regression tests to avoid the interactive prompt.
   */
  async confirmLaunchExecutable(path) {
    // We don't anchor the prompt to a specific window intentionally, not
    // only because this is the same behavior as the system-level prompt,
    // but also because the most recently active window is the right choice
    // in basically all cases.
    return await DownloadUIHelper.getPrompter().confirmLaunchExecutable(path);
  },

  /**
   * Launches the specified file, unless overridden by regression tests.
   */
  launchFile(file, mimeInfo) {
    if (mimeInfo) {
      mimeInfo.launchWithFile(file);
    } else {
      file.launch();
    }
  },

  /**
   * Shows the containing folder of a file.
   *
   * @param aFilePath
   *        The path to the file.
   *
   * @return {Promise}
   * @resolves When the instruction to open the containing folder has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the folder is actually
   *           opened.
   * @rejects  JavaScript exception if there was an error trying to open
   *           the containing folder.
   */
  async showContainingDirectory(aFilePath) {
    let file = new FileUtils.File(aFilePath);

    try {
      // Show the directory containing the file and select the file.
      file.reveal();
      return;
    } catch (ex) { }

    // If reveal fails for some reason (e.g., it's not implemented on unix
    // or the file doesn't exist), try using the parent if we have it.
    let parent = file.parent;
    if (!parent) {
      throw new Error(
        "Unexpected reference to a top-level directory instead of a file");
    }

    try {
      // Open the parent directory to show where the file should be.
      parent.launch();
      return;
    } catch (ex) { }

    // If launch also fails (probably because it's not implemented), let
    // the OS handler try to open the parent.
    gExternalProtocolService.loadUrl(NetUtil.newURI(parent));
  },

  /**
   * Calls the directory service, create a downloads directory and returns an
   * nsIFile for the downloads directory.
   *
   * @return {Promise}
   * @resolves The directory string path.
   */
  _createDownloadsDirectory(aName) {
    // We read the name of the directory from the list of translated strings
    // that is kept by the UI helper module, even if this string is not strictly
    // displayed in the user interface.
    let directoryPath = OS.Path.join(this._getDirectory(aName),
                                     DownloadUIHelper.strings.downloadsFolder);

    // Create the Downloads folder and ignore if it already exists.
    return OS.File.makeDir(directoryPath, { ignoreExisting: true })
                  .then(() => directoryPath);
  },

  /**
   * Returns the string path for the given directory service location name. This
   * can be overridden by regression tests to return the path of the system
   * temporary directory in all cases.
   */
  _getDirectory(name) {
    return Services.dirsvc.get(name, Ci.nsIFile).path;
  },

  /**
   * Register the downloads interruption observers.
   *
   * @param aList
   *        The public or private downloads list.
   * @param aIsPrivate
   *        True if the list is private, false otherwise.
   *
   * @return {Promise}
   * @resolves When the views and observers are added.
   */
  addListObservers(aList, aIsPrivate) {
    DownloadObserver.registerView(aList, aIsPrivate);
    if (!DownloadObserver.observersAdded) {
      DownloadObserver.observersAdded = true;
      for (let topic of kObserverTopics) {
        Services.obs.addObserver(DownloadObserver, topic);
      }
    }
    return Promise.resolve();
  },

  /**
   * Force a save on _store if it exists. Used to ensure downloads do not
   * persist after being sanitized on Android.
   *
   * @return {Promise}
   * @resolves When _store.save() completes.
   */
  forceSave() {
    if (this._store) {
      return this._store.save();
    }
    return Promise.resolve();
  },
};

this.DownloadObserver = {
  /**
   * Flag to determine if the observers have been added previously.
   */
  observersAdded: false,

  /**
   * Timer used to delay restarting canceled downloads upon waking and returning
   * online.
   */
  _wakeTimer: null,

  /**
   * Set that contains the in progress publics downloads.
   * It's kept updated when a public download is added, removed or changes its
   * properties.
   */
  _publicInProgressDownloads: new Set(),

  /**
   * Set that contains the in progress private downloads.
   * It's kept updated when a private download is added, removed or changes its
   * properties.
   */
  _privateInProgressDownloads: new Set(),

  /**
   * Set that contains the downloads that have been canceled when going offline
   * or to sleep. These are started again when returning online or waking. This
   * list is not persisted so when exiting and restarting, the downloads will not
   * be started again.
   */
  _canceledOfflineDownloads: new Set(),

  /**
   * Registers a view that updates the corresponding downloads state set, based
   * on the aIsPrivate argument. The set is updated when a download is added,
   * removed or changes its properties.
   *
   * @param aList
   *        The public or private downloads list.
   * @param aIsPrivate
   *        True if the list is private, false otherwise.
   */
  registerView: function DO_registerView(aList, aIsPrivate) {
    let downloadsSet = aIsPrivate ? this._privateInProgressDownloads
                                  : this._publicInProgressDownloads;
    let downloadsView = {
      onDownloadAdded: aDownload => {
        if (!aDownload.stopped) {
          downloadsSet.add(aDownload);
        }
      },
      onDownloadChanged: aDownload => {
        if (aDownload.stopped) {
          downloadsSet.delete(aDownload);
        } else {
          downloadsSet.add(aDownload);
        }
      },
      onDownloadRemoved: aDownload => {
        downloadsSet.delete(aDownload);
        // The download must also be removed from the canceled when offline set.
        this._canceledOfflineDownloads.delete(aDownload);
      }
    };

    // We register the view asynchronously.
    aList.addView(downloadsView).catch(Cu.reportError);
  },

  /**
   * Wrapper that handles the test mode before calling the prompt that display
   * a warning message box that informs that there are active downloads,
   * and asks whether the user wants to cancel them or not.
   *
   * @param aCancel
   *        The observer notification subject.
   * @param aDownloadsCount
   *        The current downloads count.
   * @param aPrompter
   *        The prompter object that shows the confirm dialog.
   * @param aPromptType
   *        The type of prompt notification depending on the observer.
   */
  _confirmCancelDownloads: function DO_confirmCancelDownload(
    aCancel, aDownloadsCount, aPrompter, aPromptType) {
    // If user has already dismissed the request, then do nothing.
    if ((aCancel instanceof Ci.nsISupportsPRBool) && aCancel.data) {
      return;
    }
    // Handle test mode
    if (gCombinedDownloadIntegration._testPromptDownloads) {
      gCombinedDownloadIntegration._testPromptDownloads = aDownloadsCount;
      return;
    }

    aCancel.data = aPrompter.confirmCancelDownloads(aDownloadsCount, aPromptType);
  },

  /**
   * Resume all downloads that were paused when going offline, used when waking
   * from sleep or returning from being offline.
   */
  _resumeOfflineDownloads: function DO_resumeOfflineDownloads() {
    this._wakeTimer = null;

    for (let download of this._canceledOfflineDownloads) {
      download.start().catch(() => {});
    }
  },

  // nsIObserver
  observe: function DO_observe(aSubject, aTopic, aData) {
    let downloadsCount;
    let p = DownloadUIHelper.getPrompter();
    switch (aTopic) {
      case "quit-application-requested":
        downloadsCount = this._publicInProgressDownloads.size +
                         this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(aSubject, downloadsCount, p, p.ON_QUIT);
        break;
      case "offline-requested":
        downloadsCount = this._publicInProgressDownloads.size +
                         this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(aSubject, downloadsCount, p, p.ON_OFFLINE);
        break;
      case "last-pb-context-exiting":
        downloadsCount = this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(aSubject, downloadsCount, p,
                                     p.ON_LEAVE_PRIVATE_BROWSING);
        break;
      case "last-pb-context-exited":
        let promise = (async function() {
          let list = await Downloads.getList(Downloads.PRIVATE);
          let downloads = await list.getAll();

          // We can remove the downloads and finalize them in parallel.
          for (let download of downloads) {
            list.remove(download).catch(Cu.reportError);
            download.finalize(true).catch(Cu.reportError);
          }
        })();
        // Handle test mode
        if (gCombinedDownloadIntegration._testResolveClearPrivateList) {
          gCombinedDownloadIntegration._testResolveClearPrivateList(promise);
        } else {
          promise.catch(ex => Cu.reportError(ex));
        }
        break;
      case "sleep_notification":
      case "suspend_process_notification":
      case "network:offline-about-to-go-offline":
        for (let download of this._publicInProgressDownloads) {
          download.cancel();
          this._canceledOfflineDownloads.add(download);
        }
        for (let download of this._privateInProgressDownloads) {
          download.cancel();
          this._canceledOfflineDownloads.add(download);
        }
        break;
      case "wake_notification":
      case "resume_process_notification":
        let wakeDelay = 10000;
        try {
          wakeDelay = Services.prefs.getIntPref("browser.download.manager.resumeOnWakeDelay");
        } catch(e) {}

        if (wakeDelay >= 0) {
          this._wakeTimer = new Timer(this._resumeOfflineDownloads.bind(this), wakeDelay,
                                      Ci.nsITimer.TYPE_ONE_SHOT);
        }
        break;
      case "network:offline-status-changed":
        if (aData == "online") {
          this._resumeOfflineDownloads();
        }
        break;
      // We need to unregister observers explicitly before we reach the
      // "xpcom-shutdown" phase, otherwise observers may be notified when some
      // required services are not available anymore. We can't unregister
      // observers on "quit-application", because this module is also loaded
      // during "make package" automation, and the quit notification is not sent
      // in that execution environment (bug 973637).
      case "xpcom-will-shutdown":
        for (let topic of kObserverTopics) {
          Services.obs.removeObserver(this, topic);
        }
        break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
};

/**
 * Registers a Places observer so that operations on download history are
 * reflected on the provided list of downloads.
 *
 * You do not need to keep a reference to this object in order to keep it alive,
 * because the history service already keeps a strong reference to it.
 *
 * @param aList
 *        DownloadList object linked to this observer.
 */
this.DownloadHistoryObserver = function (aList)
{
  this._list = aList;
  PlacesUtils.history.addObserver(this);
}

this.DownloadHistoryObserver.prototype = {
  /**
   * DownloadList object linked to this observer.
   */
  _list: null,

  QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]),

  // nsINavHistoryObserver
  onDeleteURI: function DL_onDeleteURI(aURI, aGUID) {
    this._list.removeFinished(download => aURI.equals(NetUtil.newURI(
                                                      download.source.url)));
  },

  // nsINavHistoryObserver
  onClearHistory: function DL_onClearHistory() {
    this._list.removeFinished();
  },

  onTitleChanged: function () {},
  onBeginUpdateBatch: function () {},
  onEndUpdateBatch: function () {},
  onVisit: function () {},
  onPageChanged: function () {},
  onDeleteVisits: function () {},
};

/**
 * This view can be added to a DownloadList object to trigger a save operation
 * in the given DownloadStore object when a relevant change occurs.  You should
 * call the "initialize" method in order to register the view and load the
 * current state from disk.
 *
 * You do not need to keep a reference to this object in order to keep it alive,
 * because the DownloadList object already keeps a strong reference to it.
 *
 * @param aList
 *        The DownloadList object on which the view should be registered.
 * @param aStore
 *        The DownloadStore object used for saving.
 */
this.DownloadAutoSaveView = function (aList, aStore)
{
  this._list = aList;
  this._store = aStore;
  this._downloadsMap = new Map();
  this._writer = new DeferredTask(() => this._store.save(), kSaveDelayMs);
  AsyncShutdown.profileBeforeChange.addBlocker("DownloadAutoSaveView: writing data",
                                               () => this._writer.finalize());
}

this.DownloadAutoSaveView.prototype = {
  /**
   * DownloadList object linked to this view.
   */
  _list: null,

  /**
   * The DownloadStore object used for saving.
   */
  _store: null,

  /**
   * True when the initial state of the downloads has been loaded.
   */
  _initialized: false,

  /**
   * Registers the view and loads the current state from disk.
   *
   * @return {Promise}
   * @resolves When the view has been registered.
   * @rejects JavaScript exception.
   */
  initialize: function ()
  {
    // We set _initialized to true after adding the view, so that
    // onDownloadAdded doesn't cause a save to occur.
    return this._list.addView(this).then(() => this._initialized = true);
  },

  /**
   * This map contains only Download objects that should be saved to disk, and
   * associates them with the result of their getSerializationHash function, for
   * the purpose of detecting changes to the relevant properties.
   */
  _downloadsMap: null,

  /**
   * DeferredTask for the save operation.
   */
  _writer: null,

  /**
   * Called when the list of downloads changed, this triggers the asynchronous
   * serialization of the list of downloads.
   */
  saveSoon: function ()
  {
    this._writer.arm();
  },

  // DownloadList callback
  onDownloadAdded: function (aDownload)
  {
    if (gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
      this._downloadsMap.set(aDownload, aDownload.getSerializationHash());
      if (this._initialized) {
        this.saveSoon();
      }
    }
  },

  // DownloadList callback
  onDownloadChanged: function (aDownload)
  {
    if (!gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
      if (this._downloadsMap.has(aDownload)) {
        this._downloadsMap.delete(aDownload);
        this.saveSoon();
      }
      return;
    }

    let hash = aDownload.getSerializationHash();
    if (this._downloadsMap.get(aDownload) != hash) {
      this._downloadsMap.set(aDownload, hash);
      this.saveSoon();
    }
  },

  // DownloadList callback
  onDownloadRemoved: function (aDownload)
  {
    if (this._downloadsMap.has(aDownload)) {
      this._downloadsMap.delete(aDownload);
      this.saveSoon();
    }
  },
};
PK
!<Thhmodules/DownloadLastDir.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * The behavior implemented by gDownloadLastDir is documented here.
 *
 * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
 * preference to store the last used download directory. The first time the user
 * switches into the private browsing mode, the last download directory is
 * preserved to the pref value, but if the user switches to another directory
 * during the private browsing mode, that directory is not stored in the pref,
 * and will be merely kept in memory.  When leaving the private browsing mode,
 * this in-memory value will be discarded, and the last download directory
 * will be reverted to the pref value.
 *
 * Both the pref and the in-memory value will be cleared when clearing the
 * browsing history.  This effectively changes the last download directory
 * to the default download directory on each platform.
 *
 * If passed a URI, the last used directory is also stored with that URI in the
 * content preferences database. This can be disabled by setting the pref
 * browser.download.lastDir.savePerSite to false.
 */

const LAST_DIR_PREF = "browser.download.lastDir";
const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite";
const nsIFile = Components.interfaces.nsIFile;

this.EXPORTED_SYMBOLS = [ "DownloadLastDir" ];

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");

let nonPrivateLoadContext = Components.classes["@mozilla.org/loadcontext;1"].
                              createInstance(Components.interfaces.nsILoadContext);
let privateLoadContext = Components.classes["@mozilla.org/privateloadcontext;1"].
                              createInstance(Components.interfaces.nsILoadContext);

var observer = {
  QueryInterface(aIID) {
    if (aIID.equals(Components.interfaces.nsIObserver) ||
        aIID.equals(Components.interfaces.nsISupports) ||
        aIID.equals(Components.interfaces.nsISupportsWeakReference))
      return this;
    throw Components.results.NS_NOINTERFACE;
  },
  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case "last-pb-context-exited":
        gDownloadLastDirFile = null;
        break;
      case "browser:purge-session-history":
        gDownloadLastDirFile = null;
        if (Services.prefs.prefHasUserValue(LAST_DIR_PREF))
          Services.prefs.clearUserPref(LAST_DIR_PREF);
        // Ensure that purging session history causes both the session-only PB cache
        // and persistent prefs to be cleared.
        let cps2 = Components.classes["@mozilla.org/content-pref/service;1"].
                     getService(Components.interfaces.nsIContentPrefService2);

        cps2.removeByName(LAST_DIR_PREF, nonPrivateLoadContext);
        cps2.removeByName(LAST_DIR_PREF, privateLoadContext);
        break;
    }
  }
};

var os = Components.classes["@mozilla.org/observer-service;1"]
                   .getService(Components.interfaces.nsIObserverService);
os.addObserver(observer, "last-pb-context-exited", true);
os.addObserver(observer, "browser:purge-session-history", true);

function readLastDirPref() {
  try {
    return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile);
  } catch (e) {
    return null;
  }
}

function isContentPrefEnabled() {
  try {
    return Services.prefs.getBoolPref(SAVE_PER_SITE_PREF);
  } catch (e) {
    return true;
  }
}

var gDownloadLastDirFile = readLastDirPref();

this.DownloadLastDir = function DownloadLastDir(aWindow) {
  let loadContext = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                           .getInterface(Components.interfaces.nsIWebNavigation)
                           .QueryInterface(Components.interfaces.nsILoadContext);
  // Need this in case the real thing has gone away by the time we need it.
  // We only care about the private browsing state. All the rest of the
  // load context isn't of interest to the content pref service.
  this.fakeContext = loadContext.usePrivateBrowsing ?
                       privateLoadContext :
                       nonPrivateLoadContext;
}

DownloadLastDir.prototype = {
  isPrivate: function DownloadLastDir_isPrivate() {
    return this.fakeContext.usePrivateBrowsing;
  },
  // compat shims
  get file() { return this._getLastFile(); },
  set file(val) { this.setFile(null, val); },
  cleanupPrivateFile() {
    gDownloadLastDirFile = null;
  },
  // This function is now deprecated as it uses the sync nsIContentPrefService
  // interface. New consumers should use the getFileAsync function.
  getFile(aURI) {
    let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
    Deprecated.warning("DownloadLastDir.getFile is deprecated. Please use getFileAsync instead.",
                       "https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/DownloadLastDir.jsm",
                       Components.stack.caller);

    if (aURI && isContentPrefEnabled()) {
      let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, this.fakeContext);
      if (lastDir) {
        var lastDirFile = Components.classes["@mozilla.org/file/local;1"]
                                    .createInstance(Components.interfaces.nsIFile);
        lastDirFile.initWithPath(lastDir);
        return lastDirFile;
      }
    }
    return this._getLastFile();
  },

  _getLastFile() {
    if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
      gDownloadLastDirFile = null;

    if (this.isPrivate()) {
      if (!gDownloadLastDirFile)
        gDownloadLastDirFile = readLastDirPref();
      return gDownloadLastDirFile;
    }
    return readLastDirPref();
  },

  getFileAsync(aURI, aCallback) {
    let plainPrefFile = this._getLastFile();
    if (!aURI || !isContentPrefEnabled()) {
      Services.tm.dispatchToMainThread(() => aCallback(plainPrefFile));
      return;
    }

    let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI;
    let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]
                         .getService(Components.interfaces.nsIContentPrefService2);
    let result = null;
    cps2.getByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext, {
      handleResult: aResult => result = aResult,
      handleCompletion(aReason) {
        let file = plainPrefFile;
        if (aReason == Components.interfaces.nsIContentPrefCallback2.COMPLETE_OK &&
           result instanceof Components.interfaces.nsIContentPref) {
          file = Components.classes["@mozilla.org/file/local;1"]
                           .createInstance(Components.interfaces.nsIFile);
          file.initWithPath(result.value);
        }
        aCallback(file);
      }
    });
  },

  setFile(aURI, aFile) {
    if (aURI && isContentPrefEnabled()) {
      let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI;
      let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]
                           .getService(Components.interfaces.nsIContentPrefService2);
      if (aFile instanceof Components.interfaces.nsIFile)
        cps2.set(uri, LAST_DIR_PREF, aFile.path, this.fakeContext);
      else
        cps2.removeByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext);
    }
    if (this.isPrivate()) {
      if (aFile instanceof Components.interfaces.nsIFile)
        gDownloadLastDirFile = aFile.clone();
      else
        gDownloadLastDirFile = null;
    } else if (aFile instanceof Components.interfaces.nsIFile) {
      Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile);
    } else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
      Services.prefs.clearUserPref(LAST_DIR_PREF);
    }
  }
};
PK
!<*>>modules/DownloadList.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides collections of Download objects and aggregate views on them.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "DownloadList",
  "DownloadCombinedList",
  "DownloadSummary",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

/**
 * Represents a collection of Download objects that can be viewed and managed by
 * the user interface, and persisted across sessions.
 */
this.DownloadList = function() {
  this._downloads = [];
  this._views = new Set();
}

this.DownloadList.prototype = {
  /**
   * Array of Download objects currently in the list.
   */
  _downloads: null,

  /**
   * Retrieves a snapshot of the downloads that are currently in the list.  The
   * returned array does not change when downloads are added or removed, though
   * the Download objects it contains are still updated in real time.
   *
   * @return {Promise}
   * @resolves An array of Download objects.
   * @rejects JavaScript exception.
   */
  getAll: function DL_getAll() {
    return Promise.resolve(Array.slice(this._downloads, 0));
  },

  /**
   * Adds a new download to the end of the items list.
   *
   * @note When a download is added to the list, its "onchange" event is
   *       registered by the list, thus it cannot be used to monitor the
   *       download.  To receive change notifications for downloads that are
   *       added to the list, use the addView method to register for
   *       onDownloadChanged notifications.
   *
   * @param aDownload
   *        The Download object to add.
   *
   * @return {Promise}
   * @resolves When the download has been added.
   * @rejects JavaScript exception.
   */
  add: function DL_add(aDownload) {
    this._downloads.push(aDownload);
    aDownload.onchange = this._change.bind(this, aDownload);
    this._notifyAllViews("onDownloadAdded", aDownload);

    return Promise.resolve();
  },

  /**
   * Removes a download from the list.  If the download was already removed,
   * this method has no effect.
   *
   * This method does not change the state of the download, to allow adding it
   * to another list, or control it directly.  If you want to dispose of the
   * download object, you should cancel it afterwards, and remove any partially
   * downloaded data if needed.
   *
   * @param aDownload
   *        The Download object to remove.
   *
   * @return {Promise}
   * @resolves When the download has been removed.
   * @rejects JavaScript exception.
   */
  remove: function DL_remove(aDownload) {
    let index = this._downloads.indexOf(aDownload);
    if (index != -1) {
      this._downloads.splice(index, 1);
      aDownload.onchange = null;
      this._notifyAllViews("onDownloadRemoved", aDownload);
    }

    return Promise.resolve();
  },

  /**
   * This function is called when "onchange" events of downloads occur.
   *
   * @param aDownload
   *        The Download object that changed.
   */
  _change: function DL_change(aDownload) {
    this._notifyAllViews("onDownloadChanged", aDownload);
  },

  /**
   * Set of currently registered views.
   */
  _views: null,

  /**
   * Adds a view that will be notified of changes to downloads.  The newly added
   * view will receive onDownloadAdded notifications for all the downloads that
   * are already in the list.
   *
   * @param aView
   *        The view object to add.  The following methods may be defined:
   *        {
   *          onDownloadAdded: function (aDownload) {
   *            // Called after aDownload is added to the end of the list.
   *          },
   *          onDownloadChanged: function (aDownload) {
   *            // Called after the properties of aDownload change.
   *          },
   *          onDownloadRemoved: function (aDownload) {
   *            // Called after aDownload is removed from the list.
   *          },
   *          onDownloadBatchStarting: function () {
   *            // Called before multiple changes are made at the same time.
   *          },
   *          onDownloadBatchEnded: function () {
   *            // Called after all the changes have been made.
   *          },
   *        }
   *
   * @return {Promise}
   * @resolves When the view has been registered and all the onDownloadAdded
   *           notifications for the existing downloads have been sent.
   * @rejects JavaScript exception.
   */
  addView: function DL_addView(aView) {
    this._views.add(aView);

    if ("onDownloadAdded" in aView) {
      this._notifyAllViews("onDownloadBatchStarting");
      for (let download of this._downloads) {
        try {
          aView.onDownloadAdded(download);
        } catch (ex) {
          Cu.reportError(ex);
        }
      }
      this._notifyAllViews("onDownloadBatchEnded");
    }

    return Promise.resolve();
  },

  /**
   * Removes a view that was previously added using addView.
   *
   * @param aView
   *        The view object to remove.
   *
   * @return {Promise}
   * @resolves When the view has been removed.  At this point, the removed view
   *           will not receive any more notifications.
   * @rejects JavaScript exception.
   */
  removeView: function DL_removeView(aView) {
    this._views.delete(aView);

    return Promise.resolve();
  },

  /**
   * Notifies all the views of a download addition, change, or removal.
   *
   * @param aMethodName
   *        String containing the name of the method to call on the view.
   * @param aDownload
   *        The Download object that changed.
   */
  _notifyAllViews(aMethodName, aDownload) {
    for (let view of this._views) {
      try {
        if (aMethodName in view) {
          view[aMethodName](aDownload);
        }
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },

  /**
   * Removes downloads from the list that have finished, have failed, or have
   * been canceled without keeping partial data.  A filter function may be
   * specified to remove only a subset of those downloads.
   *
   * This method finalizes each removed download, ensuring that any partially
   * downloaded data associated with it is also removed.
   *
   * @param aFilterFn
   *        The filter function is called with each download as its only
   *        argument, and should return true to remove the download and false
   *        to keep it.  This parameter may be null or omitted to have no
   *        additional filter.
   */
  removeFinished: function DL_removeFinished(aFilterFn) {
    (async () => {
      let list = await this.getAll();
      for (let download of list) {
        // Remove downloads that have been canceled, even if the cancellation
        // operation hasn't completed yet so we don't check "stopped" here.
        // Failed downloads with partial data are also removed.
        if (download.stopped && (!download.hasPartialData || download.error) &&
            (!aFilterFn || aFilterFn(download))) {
          // Remove the download first, so that the views don't get the change
          // notifications that may occur during finalization.
          await this.remove(download);
          // Ensure that the download is stopped and no partial data is kept.
          // This works even if the download state has changed meanwhile.  We
          // don't need to wait for the procedure to be complete before
          // processing the other downloads in the list.
          download.finalize(true).catch(Cu.reportError);
        }
      }
    })().catch(Cu.reportError);
  },
};

/**
 * Provides a unified, unordered list combining public and private downloads.
 *
 * Download objects added to this list are also added to one of the two
 * underlying lists, based on their "source.isPrivate" property.  Views on this
 * list will receive notifications for both public and private downloads.
 *
 * @param aPublicList
 *        Underlying DownloadList containing public downloads.
 * @param aPrivateList
 *        Underlying DownloadList containing private downloads.
 */
this.DownloadCombinedList = function(aPublicList, aPrivateList) {
  DownloadList.call(this);
  this._publicList = aPublicList;
  this._privateList = aPrivateList;
  aPublicList.addView(this).catch(Cu.reportError);
  aPrivateList.addView(this).catch(Cu.reportError);
}

this.DownloadCombinedList.prototype = {
  __proto__: DownloadList.prototype,

  /**
   * Underlying DownloadList containing public downloads.
   */
  _publicList: null,

  /**
   * Underlying DownloadList containing private downloads.
   */
  _privateList: null,

  /**
   * Adds a new download to the end of the items list.
   *
   * @note When a download is added to the list, its "onchange" event is
   *       registered by the list, thus it cannot be used to monitor the
   *       download.  To receive change notifications for downloads that are
   *       added to the list, use the addView method to register for
   *       onDownloadChanged notifications.
   *
   * @param aDownload
   *        The Download object to add.
   *
   * @return {Promise}
   * @resolves When the download has been added.
   * @rejects JavaScript exception.
   */
  add(aDownload) {
    if (aDownload.source.isPrivate) {
      return this._privateList.add(aDownload);
    }
    return this._publicList.add(aDownload);
  },

  /**
   * Removes a download from the list.  If the download was already removed,
   * this method has no effect.
   *
   * This method does not change the state of the download, to allow adding it
   * to another list, or control it directly.  If you want to dispose of the
   * download object, you should cancel it afterwards, and remove any partially
   * downloaded data if needed.
   *
   * @param aDownload
   *        The Download object to remove.
   *
   * @return {Promise}
   * @resolves When the download has been removed.
   * @rejects JavaScript exception.
   */
  remove(aDownload) {
    if (aDownload.source.isPrivate) {
      return this._privateList.remove(aDownload);
    }
    return this._publicList.remove(aDownload);
  },

  // DownloadList callback
  onDownloadAdded(aDownload) {
    this._downloads.push(aDownload);
    this._notifyAllViews("onDownloadAdded", aDownload);
  },

  // DownloadList callback
  onDownloadChanged(aDownload) {
    this._notifyAllViews("onDownloadChanged", aDownload);
  },

  // DownloadList callback
  onDownloadRemoved(aDownload) {
    let index = this._downloads.indexOf(aDownload);
    if (index != -1) {
      this._downloads.splice(index, 1);
    }
    this._notifyAllViews("onDownloadRemoved", aDownload);
  },
};

/**
 * Provides an aggregated view on the contents of a DownloadList.
 */
this.DownloadSummary = function() {
  this._downloads = [];
  this._views = new Set();
}

this.DownloadSummary.prototype = {
  /**
   * Array of Download objects that are currently part of the summary.
   */
  _downloads: null,

  /**
   * Underlying DownloadList whose contents should be summarized.
   */
  _list: null,

  /**
   * This method may be called once to bind this object to a DownloadList.
   *
   * Views on the summarized data can be registered before this object is bound
   * to an actual list.  This allows the summary to be used without requiring
   * the initialization of the DownloadList first.
   *
   * @param aList
   *        Underlying DownloadList whose contents should be summarized.
   *
   * @return {Promise}
   * @resolves When the view on the underlying list has been registered.
   * @rejects JavaScript exception.
   */
  bindToList(aList) {
    if (this._list) {
      throw new Error("bindToList may be called only once.");
    }

    return aList.addView(this).then(() => {
      // Set the list reference only after addView has returned, so that we don't
      // send a notification to our views for each download that is added.
      this._list = aList;
      this._onListChanged();
    });
  },

  /**
   * Set of currently registered views.
   */
  _views: null,

  /**
   * Adds a view that will be notified of changes to the summary.  The newly
   * added view will receive an initial onSummaryChanged notification.
   *
   * @param aView
   *        The view object to add.  The following methods may be defined:
   *        {
   *          onSummaryChanged: function () {
   *            // Called after any property of the summary has changed.
   *          },
   *        }
   *
   * @return {Promise}
   * @resolves When the view has been registered and the onSummaryChanged
   *           notification has been sent.
   * @rejects JavaScript exception.
   */
  addView(aView) {
    this._views.add(aView);

    if ("onSummaryChanged" in aView) {
      try {
        aView.onSummaryChanged();
      } catch (ex) {
        Cu.reportError(ex);
      }
    }

    return Promise.resolve();
  },

  /**
   * Removes a view that was previously added using addView.
   *
   * @param aView
   *        The view object to remove.
   *
   * @return {Promise}
   * @resolves When the view has been removed.  At this point, the removed view
   *           will not receive any more notifications.
   * @rejects JavaScript exception.
   */
  removeView(aView) {
    this._views.delete(aView);

    return Promise.resolve();
  },

  /**
   * Indicates whether all the downloads are currently stopped.
   */
  allHaveStopped: true,

  /**
   * Indicates the total number of bytes to be transferred before completing all
   * the downloads that are currently in progress.
   *
   * For downloads that do not have a known final size, the number of bytes
   * currently transferred is reported as part of this property.
   *
   * This is zero if no downloads are currently in progress.
   */
  progressTotalBytes: 0,

  /**
   * Number of bytes currently transferred as part of all the downloads that are
   * currently in progress.
   *
   * This is zero if no downloads are currently in progress.
   */
  progressCurrentBytes: 0,

  /**
   * This function is called when any change in the list of downloads occurs,
   * and will recalculate the summary and notify the views in case the
   * aggregated properties are different.
   */
  _onListChanged() {
    let allHaveStopped = true;
    let progressTotalBytes = 0;
    let progressCurrentBytes = 0;

    // Recalculate the aggregated state.  See the description of the individual
    // properties for an explanation of the summarization logic.
    for (let download of this._downloads) {
      if (!download.stopped) {
        allHaveStopped = false;
        progressTotalBytes += download.hasProgress ? download.totalBytes
                                                   : download.currentBytes;
        progressCurrentBytes += download.currentBytes;
      }
    }

    // Exit now if the properties did not change.
    if (this.allHaveStopped == allHaveStopped &&
        this.progressTotalBytes == progressTotalBytes &&
        this.progressCurrentBytes == progressCurrentBytes) {
      return;
    }

    this.allHaveStopped = allHaveStopped;
    this.progressTotalBytes = progressTotalBytes;
    this.progressCurrentBytes = progressCurrentBytes;

    // Notify all the views that our properties changed.
    for (let view of this._views) {
      try {
        if ("onSummaryChanged" in view) {
          view.onSummaryChanged();
        }
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },

  // DownloadList callback
  onDownloadAdded(aDownload) {
    this._downloads.push(aDownload);
    if (this._list) {
      this._onListChanged();
    }
  },

  // DownloadList callback
  onDownloadChanged(aDownload) {
    this._onListChanged();
  },

  // DownloadList callback
  onDownloadRemoved(aDownload) {
    let index = this._downloads.indexOf(aDownload);
    if (index != -1) {
      this._downloads.splice(index, 1);
    }
    this._onListChanged();
  },
};
PK
!<wtmodules/DownloadPaths.jsm/* -*- 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/. */

this.EXPORTED_SYMBOLS = [
  "DownloadPaths",
];

/**
 * This module provides the DownloadPaths object which contains methods for
 * giving names and paths to files being downloaded.
 *
 * List of methods:
 *
 * nsILocalFile
 * createNiceUniqueFile(nsILocalFile aLocalFile)
 *
 * [string base, string ext]
 * splitBaseNameAndExtension(string aLeafName)
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;

this.DownloadPaths = {
  /**
   * Creates a uniquely-named file starting from the name of the provided file.
   * If a file with the provided name already exists, the function attempts to
   * create nice alternatives, like "base(1).ext" (instead of "base-1.ext").
   *
   * If a unique name cannot be found, the function throws the XPCOM exception
   * NS_ERROR_FILE_TOO_BIG. Other exceptions, like NS_ERROR_FILE_ACCESS_DENIED,
   * can also be expected.
   *
   * @param aTemplateFile
   *        nsILocalFile whose leaf name is going to be used as a template. The
   *        provided object is not modified.
   * @returns A new instance of an nsILocalFile object pointing to the newly
   *          created empty file. On platforms that support permission bits, the
   *          file is created with permissions 644.
   */
  createNiceUniqueFile: function DP_createNiceUniqueFile(aTemplateFile) {
    // Work on a clone of the provided template file object.
    var curFile = aTemplateFile.clone().QueryInterface(Ci.nsILocalFile);
    var [base, ext] = DownloadPaths.splitBaseNameAndExtension(curFile.leafName);
    // Try other file names, for example "base(1).txt" or "base(1).tar.gz",
    // only if the file name initially set already exists.
    for (let i = 1; i < 10000 && curFile.exists(); i++) {
      curFile.leafName = base + "(" + i + ")" + ext;
    }
    // At this point we hand off control to createUnique, which will create the
    // file with the name we chose, if it is valid. If not, createUnique will
    // attempt to modify it again, for example it will shorten very long names
    // that can't be created on some platforms, and for which a normal call to
    // nsIFile.create would result in NS_ERROR_FILE_NOT_FOUND. This can result
    // very rarely in strange names like "base(9999).tar-1.gz" or "ba-1.gz".
    curFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
    return curFile;
  },

  /**
   * Separates the base name from the extension in a file name, recognizing some
   *  double extensions like ".tar.gz".
   *
   * @param aLeafName
   *        The full leaf name to be parsed. Be careful when processing names
   *        containing leading or trailing dots or spaces.
   * @returns [base, ext]
   *          The base name of the file, which can be empty, and its extension,
   *          which always includes the leading dot unless it's an empty string.
   *          Concatenating the two items always results in the original name.
   */
  splitBaseNameAndExtension: function DP_splitBaseNameAndExtension(aLeafName) {
    // The following regular expression is built from these key parts:
    //  .*?                      Matches the base name non-greedily.
    //  \.[A-Z0-9]{1,3}          Up to three letters or numbers preceding a
    //                           double extension.
    //  \.(?:gz|bz2|Z)           The second part of common double extensions.
    //  \.[^.]*                  Matches any extension or a single trailing dot.
    var [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i
                        .exec(aLeafName);
    // Return an empty string instead of undefined if no extension is found.
    return [base, ext || ""];
  }
};
PK
!<=Ymmodules/DownloadStore.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of Download objects and persistence into a file, so
 * that the state of downloads can be restored across sessions.
 *
 * The file is stored in JSON format, without indentation.  With indentation
 * applied, the file would look like this:
 *
 * {
 *   "list": [
 *     {
 *       "source": "http://www.example.com/download.txt",
 *       "target": "/home/user/Downloads/download.txt"
 *     },
 *     {
 *       "source": {
 *         "url": "http://www.example.com/download.txt",
 *         "referrer": "http://www.example.com/referrer.html"
 *       },
 *       "target": "/home/user/Downloads/download-2.txt"
 *     }
 *   ]
 * }
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "DownloadStore",
];

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, "OS",
                                  "resource://gre/modules/osfile.jsm")

XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
  return new TextDecoder();
});

XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function() {
  return new TextEncoder();
});

/**
 * Handles serialization of Download objects and persistence into a file, so
 * that the state of downloads can be restored across sessions.
 *
 * @param aList
 *        DownloadList object to be populated or serialized.
 * @param aPath
 *        String containing the file path where data should be saved.
 */
this.DownloadStore = function(aList, aPath) {
  this.list = aList;
  this.path = aPath;
}

this.DownloadStore.prototype = {
  /**
   * DownloadList object to be populated or serialized.
   */
  list: null,

  /**
   * String containing the file path where data should be saved.
   */
  path: "",

  /**
   * This function is called with a Download object as its first argument, and
   * should return true if the item should be saved.
   */
  onsaveitem: () => true,

  /**
   * Loads persistent downloads from the file to the list.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  load: function DS_load() {
    return (async () => {
      let bytes;
      try {
        bytes = await OS.File.read(this.path);
      } catch (ex) {
        if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
          throw ex;
        }
        // If the file does not exist, there are no downloads to load.
        return;
      }

      let storeData = JSON.parse(gTextDecoder.decode(bytes));

      // Create live downloads based on the static snapshot.
      for (let downloadData of storeData.list) {
        try {
          let download = await Downloads.createDownload(downloadData);
          try {
            if (!download.succeeded && !download.canceled && !download.error) {
              // Try to restart the download if it was in progress during the
              // previous session.  Ignore errors.
              download.start().catch(() => {});
            } else {
              // If the download was not in progress, try to update the current
              // progress from disk.  This is relevant in case we retained
              // partially downloaded data.
              await download.refresh();
            }
          } finally {
            // Add the download to the list if we succeeded in creating it,
            // after we have updated its initial state.
            await this.list.add(download);
          }
        } catch (ex) {
          // If an item is unrecognized, don't prevent others from being loaded.
          Cu.reportError(ex);
        }
      }
    })();
  },

  /**
   * Saves persistent downloads from the list to the file.
   *
   * If an error occurs, the previous file is not deleted.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  save: function DS_save() {
    return (async () => {
      let downloads = await this.list.getAll();

      // Take a static snapshot of the current state of all the downloads.
      let storeData = { list: [] };
      let atLeastOneDownload = false;
      for (let download of downloads) {
        try {
          if (!this.onsaveitem(download)) {
            continue;
          }

          let serializable = download.toSerializable();
          if (!serializable) {
            // This item cannot be persisted across sessions.
            continue;
          }
          storeData.list.push(serializable);
          atLeastOneDownload = true;
        } catch (ex) {
          // If an item cannot be converted to a serializable form, don't
          // prevent others from being saved.
          Cu.reportError(ex);
        }
      }

      if (atLeastOneDownload) {
        // Create or overwrite the file if there are downloads to save.
        let bytes = gTextEncoder.encode(JSON.stringify(storeData));
        await OS.File.writeAtomic(this.path, bytes,
                                  { tmpPath: this.path + ".tmp" });
      } else {
        // Remove the file if there are no downloads to save at all.
        try {
          await OS.File.remove(this.path);
        } catch (ex) {
          if (!(ex instanceof OS.File.Error) ||
              !(ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
            throw ex;
          }
          // On Windows, we may get an access denied error instead of a no such
          // file error if the file existed before, and was recently deleted.
        }
      }
    })();
  },
};
PK
!<1
8mmmodules/DownloadUIHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides functions to handle status and messages in the user interface.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "DownloadUIHelper",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

const kStringBundleUrl =
  "chrome://mozapps/locale/downloads/downloads.properties";

const kStringsRequiringFormatting = {
  fileExecutableSecurityWarning: true,
  cancelDownloadsOKTextMultiple: true,
  quitCancelDownloadsAlertMsgMultiple: true,
  quitCancelDownloadsAlertMsgMacMultiple: true,
  offlineCancelDownloadsAlertMsgMultiple: true,
  leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2: true
};

/**
 * Provides functions to handle status and messages in the user interface.
 */
this.DownloadUIHelper = {
  /**
   * Returns an object that can be used to display prompts related to downloads.
   *
   * The prompts may be either anchored to a specified window, or anchored to
   * the most recently active window, for example if the prompt is displayed in
   * response to global notifications that are not associated with any window.
   *
   * @param aParent
   *        If specified, should reference the nsIDOMWindow to which the prompts
   *        should be attached.  If omitted, the prompts will be attached to the
   *        most recently active window.
   *
   * @return A DownloadPrompter object.
   */
  getPrompter(aParent) {
    return new DownloadPrompter(aParent || null);
  },
};

/**
 * Returns an object whose keys are the string names from the downloads string
 * bundle, and whose values are either the translated strings or functions
 * returning formatted strings.
 */
XPCOMUtils.defineLazyGetter(DownloadUIHelper, "strings", function() {
  let strings = {};
  let sb = Services.strings.createBundle(kStringBundleUrl);
  let enumerator = sb.getSimpleEnumeration();
  while (enumerator.hasMoreElements()) {
    let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
    let stringName = string.key;
    if (stringName in kStringsRequiringFormatting) {
      strings[stringName] = function() {
        // Convert "arguments" to a real array before calling into XPCOM.
        return sb.formatStringFromName(stringName,
                                       Array.slice(arguments, 0),
                                       arguments.length);
      };
    } else {
      strings[stringName] = string.value;
    }
  }
  return strings;
});

/**
 * Allows displaying prompts related to downloads.
 *
 * @param aParent
 *        The nsIDOMWindow to which prompts should be attached, or null to
 *        attach prompts to the most recently active window.
 */
this.DownloadPrompter = function(aParent) {
  if (AppConstants.MOZ_B2G) {
    // On B2G there is no prompter implementation.
    this._prompter = null;
  } else {
    this._prompter = Services.ww.getNewPrompter(aParent);
  }
}

this.DownloadPrompter.prototype = {
  /**
   * Constants with the different type of prompts.
   */
  ON_QUIT: "prompt-on-quit",
  ON_OFFLINE: "prompt-on-offline",
  ON_LEAVE_PRIVATE_BROWSING: "prompt-on-leave-private-browsing",

  /**
   * nsIPrompt instance for displaying messages.
   */
  _prompter: null,

  /**
   * Displays a warning message box that informs that the specified file is
   * executable, and asks whether the user wants to launch it.  The user is
   * given the option of disabling future instances of this warning.
   *
   * @param aPath
   *        String containing the full path to the file to be opened.
   *
   * @return {Promise}
   * @resolves Boolean indicating whether the launch operation can continue.
   * @rejects JavaScript exception.
   */
  confirmLaunchExecutable(aPath) {
    const kPrefAlertOnEXEOpen = "browser.download.manager.alertOnEXEOpen";

    try {
      // Always launch in case we have no prompter implementation.
      if (!this._prompter) {
        return Promise.resolve(true);
      }

      try {
        if (!Services.prefs.getBoolPref(kPrefAlertOnEXEOpen)) {
          return Promise.resolve(true);
        }
      } catch (ex) {
        // If the preference does not exist, continue with the prompt.
      }

      let leafName = OS.Path.basename(aPath);

      let s = DownloadUIHelper.strings;
      let checkState = { value: false };
      let shouldLaunch = this._prompter.confirmCheck(
                           s.fileExecutableSecurityWarningTitle,
                           s.fileExecutableSecurityWarning(leafName, leafName),
                           s.fileExecutableSecurityWarningDontAsk,
                           checkState);

      if (shouldLaunch) {
        Services.prefs.setBoolPref(kPrefAlertOnEXEOpen, !checkState.value);
      }

      return Promise.resolve(shouldLaunch);
    } catch (ex) {
      return Promise.reject(ex);
    }
  },

  /**
   * Displays a warning message box that informs that there are active
   * downloads, and asks whether the user wants to cancel them or not.
   *
   * @param aDownloadsCount
   *        The current downloads count.
   * @param aPromptType
   *        The type of prompt notification depending on the observer.
   *
   * @return False to cancel the downloads and continue, true to abort the
   *         operation.
   */
  confirmCancelDownloads: function DP_confirmCancelDownload(aDownloadsCount,
                                                            aPromptType) {
    // Always continue in case we have no prompter implementation, or if there
    // are no active downloads.
    if (!this._prompter || aDownloadsCount <= 0) {
      return false;
    }

    let s = DownloadUIHelper.strings;
    let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
                      (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
    let okButton = aDownloadsCount > 1 ? s.cancelDownloadsOKTextMultiple(aDownloadsCount)
                                       : s.cancelDownloadsOKText;
    let title, message, cancelButton;

    switch (aPromptType) {
      case this.ON_QUIT:
        title = s.quitCancelDownloadsAlertTitle;
        if (AppConstants.platform != "macosx") {
          message = aDownloadsCount > 1
                    ? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
                    : s.quitCancelDownloadsAlertMsg;
          cancelButton = s.dontQuitButtonWin;
        } else {
          message = aDownloadsCount > 1
                    ? s.quitCancelDownloadsAlertMsgMacMultiple(aDownloadsCount)
                    : s.quitCancelDownloadsAlertMsgMac;
          cancelButton = s.dontQuitButtonMac;
        }
        break;
      case this.ON_OFFLINE:
        title = s.offlineCancelDownloadsAlertTitle;
        message = aDownloadsCount > 1
                  ? s.offlineCancelDownloadsAlertMsgMultiple(aDownloadsCount)
                  : s.offlineCancelDownloadsAlertMsg;
        cancelButton = s.dontGoOfflineButton;
        break;
      case this.ON_LEAVE_PRIVATE_BROWSING:
        title = s.leavePrivateBrowsingCancelDownloadsAlertTitle;
        message = aDownloadsCount > 1
                  ? s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2(aDownloadsCount)
                  : s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2;
        cancelButton = s.dontLeavePrivateBrowsingButton2;
        break;
    }

    let rv = this._prompter.confirmEx(title, message, buttonFlags, okButton,
                                      cancelButton, null, null, {});
    return (rv == 1);
  }
};
PK
!<|//NNmodules/DownloadUtils.jsm/* vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "DownloadUtils" ];

/**
 * This module provides the DownloadUtils object which contains useful methods
 * for downloads such as displaying file sizes, transfer times, and download
 * locations.
 *
 * List of methods:
 *
 * [string status, double newLast]
 * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
 *                   [optional] double aSpeed, [optional] double aLastSec)
 *
 * string progress
 * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
 *
 * [string timeLeft, double newLast]
 * getTimeLeft(double aSeconds, [optional] double aLastSec)
 *
 * [string dateCompact, string dateComplete]
 * getReadableDates(Date aDate, [optional] Date aNow)
 *
 * [string displayHost, string fullHost]
 * getURIHost(string aURIString)
 *
 * [string convertedBytes, string units]
 * convertByteUnits(int aBytes)
 *
 * [int time, string units, int subTime, string subUnits]
 * convertTimeUnits(double aSecs)
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                  "resource://gre/modules/PluralForm.jsm");

const MS_PER_DAY = 24 * 60 * 60 * 1000;

var localeNumberFormatCache = new Map();
function getLocaleNumberFormat(fractionDigits) {
  // Backward compatibility: don't use localized digits
  let locale = Intl.NumberFormat().resolvedOptions().locale +
               "-u-nu-latn";
  let key = locale + "_" + fractionDigits;
  if (!localeNumberFormatCache.has(key)) {
    localeNumberFormatCache.set(key,
      Intl.NumberFormat(locale,
                        { maximumFractionDigits: fractionDigits,
                          minimumFractionDigits: fractionDigits }));
  }
  return localeNumberFormatCache.get(key);
}

const kDownloadProperties =
  "chrome://mozapps/locale/downloads/downloads.properties";

var gStr = {
  statusFormat: "statusFormat3",
  statusFormatInfiniteRate: "statusFormatInfiniteRate",
  statusFormatNoRate: "statusFormatNoRate",
  transferSameUnits: "transferSameUnits2",
  transferDiffUnits: "transferDiffUnits2",
  transferNoTotal: "transferNoTotal2",
  timePair: "timePair3",
  timeLeftSingle: "timeLeftSingle3",
  timeLeftDouble: "timeLeftDouble3",
  timeFewSeconds: "timeFewSeconds2",
  timeUnknown: "timeUnknown2",
  monthDate: "monthDate2",
  yesterday: "yesterday",
  doneScheme: "doneScheme2",
  doneFileScheme: "doneFileScheme",
  units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
  // Update timeSize in convertTimeUnits if changing the length of this array
  timeUnits: ["shortSeconds", "shortMinutes", "shortHours", "shortDays"],
  infiniteRate: "infiniteRate",
};

// This lazily initializes the string bundle upon first use.
Object.defineProperty(this, "gBundle", {
  configurable: true,
  enumerable: true,
  get() {
    delete this.gBundle;
    return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"].
                          getService(Ci.nsIStringBundleService).
                          createBundle(kDownloadProperties);
  },
});

// Keep track of at most this many second/lastSec pairs so that multiple calls
// to getTimeLeft produce the same time left
const kCachedLastMaxSize = 10;
var gCachedLast = [];

this.DownloadUtils = {
  /**
   * Generate a full status string for a download given its current progress,
   * total size, speed, last time remaining
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [download status text, new value of "last seconds"]
   */
  getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes,
                                                   aSpeed, aLastSec) {
    let [transfer, timeLeft, newLast, normalizedSpeed]
      = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);

    let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed);

    let status;
    if (rate === "Infinity") {
      // Infinity download speed doesn't make sense. Show a localized phrase instead.
      let params = [transfer, gBundle.GetStringFromName(gStr.infiniteRate), timeLeft];
      status = gBundle.formatStringFromName(gStr.statusFormatInfiniteRate, params,
                                            params.length);
    } else {
      let params = [transfer, rate, unit, timeLeft];
      status = gBundle.formatStringFromName(gStr.statusFormat, params,
                                            params.length);
    }
    return [status, newLast];
  },

  /**
   * Generate a status string for a download given its current progress,
   * total size, speed, last time remaining. The status string contains the
   * time remaining, as well as the total bytes downloaded. Unlike
   * getDownloadStatus, it does not include the rate of download.
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [download status text, new value of "last seconds"]
   */
  getDownloadStatusNoRate:
  function DU_getDownloadStatusNoRate(aCurrBytes, aMaxBytes, aSpeed,
                                      aLastSec) {
    let [transfer, timeLeft, newLast]
      = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);

    let params = [transfer, timeLeft];
    let status = gBundle.formatStringFromName(gStr.statusFormatNoRate, params,
                                              params.length);
    return [status, newLast];
  },

  /**
   * Helper function that returns a transfer string, a time remaining string,
   * and a new value of "last seconds".
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A triple: [amount transferred string, time remaining string,
   *                    new value of "last seconds"]
   */
  _deriveTransferRate: function DU__deriveTransferRate(aCurrBytes,
                                                       aMaxBytes, aSpeed,
                                                       aLastSec) {
    if (aMaxBytes == null)
      aMaxBytes = -1;
    if (aSpeed == null)
      aSpeed = -1;
    if (aLastSec == null)
      aLastSec = Infinity;

    // Calculate the time remaining if we have valid values
    let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
      (aMaxBytes - aCurrBytes) / aSpeed : -1;

    let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes);
    let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec);
    return [transfer, timeLeft, newLast, aSpeed];
  },

  /**
   * Generate the transfer progress string to show the current and total byte
   * size. Byte units will be as large as possible and the same units for
   * current and max will be suppressed for the former.
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @return The transfer progress text
   */
  getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes) {
    if (aMaxBytes == null)
      aMaxBytes = -1;

    let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
    let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);

    // Figure out which byte progress string to display
    let name, values;
    if (aMaxBytes < 0) {
      name = gStr.transferNoTotal;
      values = [
        progress,
        progressUnits,
      ];
    } else if (progressUnits == totalUnits) {
      name = gStr.transferSameUnits;
      values = [
        progress,
        total,
        totalUnits,
      ];
    } else {
      name = gStr.transferDiffUnits;
      values = [
        progress,
        progressUnits,
        total,
        totalUnits,
      ];
    }

    return gBundle.formatStringFromName(name, values, values.length);
  },

  /**
   * Generate a "time left" string given an estimate on the time left and the
   * last time. The extra time is used to give a better estimate on the time to
   * show. Both the time values are doubles instead of integers to help get
   * sub-second accuracy for current and future estimates.
   *
   * @param aSeconds
   *        Current estimate on number of seconds left for the download
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [time left text, new value of "last seconds"]
   */
  getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec) {
    if (aLastSec == null)
      aLastSec = Infinity;

    if (aSeconds < 0)
      return [gBundle.GetStringFromName(gStr.timeUnknown), aLastSec];

    // Try to find a cached lastSec for the given second
    aLastSec = gCachedLast.reduce((aResult, aItem) =>
      aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec);

    // Add the current second/lastSec pair unless we have too many
    gCachedLast.push([aSeconds, aLastSec]);
    if (gCachedLast.length > kCachedLastMaxSize)
      gCachedLast.shift();

    // Apply smoothing only if the new time isn't a huge change -- e.g., if the
    // new time is more than half the previous time; this is useful for
    // downloads that start/resume slowly
    if (aSeconds > aLastSec / 2) {
      // Apply hysteresis to favor downward over upward swings
      // 30% of down and 10% of up (exponential smoothing)
      let diff = aSeconds - aLastSec;
      aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;

      // If the new time is similar, reuse something close to the last seconds,
      // but subtract a little to provide forward progress
      let diffPct = diff / aLastSec * 100;
      if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
        aSeconds = aLastSec - (diff < 0 ? .4 : .2);
    }

    // Decide what text to show for the time
    let timeLeft;
    if (aSeconds < 4) {
      // Be friendly in the last few seconds
      timeLeft = gBundle.GetStringFromName(gStr.timeFewSeconds);
    } else {
      // Convert the seconds into its two largest units to display
      let [time1, unit1, time2, unit2] =
        DownloadUtils.convertTimeUnits(aSeconds);

      let pair1 =
        gBundle.formatStringFromName(gStr.timePair, [time1, unit1], 2);
      let pair2 =
        gBundle.formatStringFromName(gStr.timePair, [time2, unit2], 2);

      // Only show minutes for under 1 hour unless there's a few minutes left;
      // or the second pair is 0.
      if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
        timeLeft = gBundle.formatStringFromName(gStr.timeLeftSingle,
                                                [pair1], 1);
      } else {
        // We've got 2 pairs of times to display
        timeLeft = gBundle.formatStringFromName(gStr.timeLeftDouble,
                                                [pair1, pair2], 2);
      }
    }

    return [timeLeft, aSeconds];
  },

  /**
   * Converts a Date object to two readable formats, one compact, one complete.
   * The compact format is relative to the current date, and is not an accurate
   * representation. For example, only the time is displayed for today. The
   * complete format always includes both the date and the time, excluding the
   * seconds, and is often shown when hovering the cursor over the compact
   * representation.
   *
   * @param aDate
   *        Date object representing the date and time to format. It is assumed
   *        that this value represents a past date.
   * @param [optional] aNow
   *        Date object representing the current date and time. The real date
   *        and time of invocation is used if this parameter is omitted.
   * @return A pair: [compact text, complete text]
   */
  getReadableDates: function DU_getReadableDates(aDate, aNow) {
    if (!aNow) {
      aNow = new Date();
    }

    // Figure out when today begins
    let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate());

    let dateTimeCompact;
    let dateTimeFull;

    // Figure out if the time is from today, yesterday, this week, etc.
    if (aDate >= today) {
      let dts = Services.intl.createDateTimeFormat(undefined, {
        timeStyle: "short"
      });
      dateTimeCompact = dts.format(aDate);
    } else if (today - aDate < (MS_PER_DAY)) {
      // After yesterday started, show yesterday
      dateTimeCompact = gBundle.GetStringFromName(gStr.yesterday);
    } else if (today - aDate < (6 * MS_PER_DAY)) {
      // After last week started, show day of week
      dateTimeCompact = aDate.toLocaleDateString(undefined, { weekday: "long" });
    } else {
      // Show month/day
      let month = aDate.toLocaleDateString(undefined, { month: "long" });
      let date = aDate.getDate();
      dateTimeCompact = gBundle.formatStringFromName(gStr.monthDate, [month, date], 2);
    }

    const dtOptions = { dateStyle: "long", timeStyle: "short" };
    dateTimeFull =
      Services.intl.createDateTimeFormat(undefined, dtOptions).format(aDate);

    return [dateTimeCompact, dateTimeFull];
  },

  /**
   * Get the appropriate display host string for a URI string depending on if
   * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
   *
   * @param aURIString
   *        The URI string to try getting an eTLD + 1, etc.
   * @return A pair: [display host for the URI string, full host name]
   */
  getURIHost: function DU_getURIHost(aURIString) {
    let ioService = Cc["@mozilla.org/network/io-service;1"].
                    getService(Ci.nsIIOService);
    let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
                      getService(Ci.nsIEffectiveTLDService);
    let idnService = Cc["@mozilla.org/network/idn-service;1"].
                     getService(Ci.nsIIDNService);

    // Get a URI that knows about its components
    let uri;
    try {
      uri = ioService.newURI(aURIString);
    } catch (ex) {
      return ["", ""];
    }

    // Get the inner-most uri for schemes like jar:
    if (uri instanceof Ci.nsINestedURI)
      uri = uri.innermostURI;

    let fullHost;
    try {
      // Get the full host name; some special URIs fail (data: jar:)
      fullHost = uri.host;
    } catch (e) {
      fullHost = "";
    }

    let displayHost;
    try {
      // This might fail if it's an IP address or doesn't have more than 1 part
      let baseDomain = eTLDService.getBaseDomain(uri);

      // Convert base domain for display; ignore the isAscii out param
      displayHost = idnService.convertToDisplayIDN(baseDomain, {});
    } catch (e) {
      // Default to the host name
      displayHost = fullHost;
    }

    // Check if we need to show something else for the host
    if (uri.scheme == "file") {
      // Display special text for file protocol
      displayHost = gBundle.GetStringFromName(gStr.doneFileScheme);
      fullHost = displayHost;
    } else if (displayHost.length == 0) {
      // Got nothing; show the scheme (data: about: moz-icon:)
      displayHost =
        gBundle.formatStringFromName(gStr.doneScheme, [uri.scheme], 1);
      fullHost = displayHost;
    } else if (uri.port != -1) {
      // Tack on the port if it's not the default port
      let port = ":" + uri.port;
      displayHost += port;
      fullHost += port;
    }

    return [displayHost, fullHost];
  },

  /**
   * Converts a number of bytes to the appropriate unit that results in an
   * internationalized number that needs fewer than 4 digits.
   *
   * @param aBytes
   *        Number of bytes to convert
   * @return A pair: [new value with 3 sig. figs., its unit]
   */
  convertByteUnits: function DU_convertByteUnits(aBytes) {
    let unitIndex = 0;

    // Convert to next unit if it needs 4 digits (after rounding), but only if
    // we know the name of the next unit
    while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) {
      aBytes /= 1024;
      unitIndex++;
    }

    // Get rid of insignificant bits by truncating to 1 or 0 decimal points
    // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
    // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100
    let fractionDigits = (aBytes > 0) && (aBytes < 100) && (unitIndex != 0) ? 1 : 0;

    // Don't try to format Infinity values using NumberFormat.
    if (aBytes === Infinity) {
      aBytes = "Infinity";
    } else {
      aBytes = getLocaleNumberFormat(fractionDigits).format(aBytes);
    }

    return [aBytes, gBundle.GetStringFromName(gStr.units[unitIndex])];
  },

  /**
   * Converts a number of seconds to the two largest units. Time values are
   * whole numbers, and units have the correct plural/singular form.
   *
   * @param aSecs
   *        Seconds to convert into the appropriate 2 units
   * @return 4-item array [first value, its unit, second value, its unit]
   */
  convertTimeUnits: function DU_convertTimeUnits(aSecs) {
    // These are the maximum values for seconds, minutes, hours corresponding
    // with gStr.timeUnits without the last item
    let timeSize = [60, 60, 24];

    let time = aSecs;
    let scale = 1;
    let unitIndex = 0;

    // Keep converting to the next unit while we have units left and the
    // current one isn't the largest unit possible
    while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
      time /= timeSize[unitIndex];
      scale *= timeSize[unitIndex];
      unitIndex++;
    }

    let value = convertTimeUnitsValue(time);
    let units = convertTimeUnitsUnits(value, unitIndex);

    let extra = aSecs - value * scale;
    let nextIndex = unitIndex - 1;

    // Convert the extra time to the next largest unit
    for (let index = 0; index < nextIndex; index++)
      extra /= timeSize[index];

    let value2 = convertTimeUnitsValue(extra);
    let units2 = convertTimeUnitsUnits(value2, nextIndex);

    return [value, units, value2, units2];
  },
};

/**
 * Private helper for convertTimeUnits that gets the display value of a time
 *
 * @param aTime
 *        Time value for display
 * @return An integer value for the time rounded down
 */
function convertTimeUnitsValue(aTime) {
  return Math.floor(aTime);
}

/**
 * Private helper for convertTimeUnits that gets the display units of a time
 *
 * @param aTime
 *        Time value for display
 * @param aIndex
 *        Index into gStr.timeUnits for the appropriate unit
 * @return The appropriate plural form of the unit for the time
 */
function convertTimeUnitsUnits(aTime, aIndex) {
  // Negative index would be an invalid unit, so just give empty
  if (aIndex < 0)
    return "";

  return PluralForm.get(aTime, gBundle.GetStringFromName(gStr.timeUnits[aIndex]));
}

/**
 * Private helper function to log errors to the error console and command line
 *
 * @param aMsg
 *        Error message to log or an array of strings to concat
 */
function log(aMsg) {
  let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
    logStringMessage(msg);
  dump(msg + "\n");
}
PK
!<(**modules/Downloads.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Main entry point to get references to all the back-end objects.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "Downloads",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DownloadCore.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "DownloadCombinedList",
                                  "resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadList",
                                  "resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadSummary",
                                  "resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                  "resource://gre/modules/DownloadUIHelper.jsm");

/* global DownloadIntegration */
Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
            "resource://gre/modules/DownloadIntegration.jsm");

/**
 * This object is exposed directly to the consumers of this JavaScript module,
 * and provides the only entry point to get references to back-end objects.
 */
this.Downloads = {
  /**
   * Work on downloads that were not started from a private browsing window.
   */
  get PUBLIC() {
    return "{Downloads.PUBLIC}";
  },
  /**
   * Work on downloads that were started from a private browsing window.
   */
  get PRIVATE() {
    return "{Downloads.PRIVATE}";
  },
  /**
   * Work on both Downloads.PRIVATE and Downloads.PUBLIC downloads.
   */
  get ALL() {
    return "{Downloads.ALL}";
  },

  /**
   * Creates a new Download object.
   *
   * @param aProperties
   *        Provides the initial properties for the newly created download.
   *        This matches the serializable representation of a Download object.
   *        Some of the most common properties in this object include:
   *        {
   *          source: String containing the URI for the download source.
   *                  Alternatively, may be an nsIURI, a DownloadSource object,
   *                  or an object with the following properties:
   *          {
   *            url: String containing the URI for the download source.
   *            isPrivate: Indicates whether the download originated from a
   *                       private window.  If omitted, the download is public.
   *            referrer: String containing the referrer URI of the download
   *                      source.  Can be omitted or null if no referrer should
   *                      be sent or the download source is not HTTP.
   *          },
   *          target: String containing the path of the target file.
   *                  Alternatively, may be an nsIFile, a DownloadTarget object,
   *                  or an object with the following properties:
   *          {
   *            path: String containing the path of the target file.
   *          },
   *          saver: String representing the class of the download operation.
   *                 If omitted, defaults to "copy".  Alternatively, may be the
   *                 serializable representation of a DownloadSaver object.
   *        }
   *
   * @return {Promise}
   * @resolves The newly created Download object.
   * @rejects JavaScript exception.
   */
  createDownload: function D_createDownload(aProperties) {
    try {
      return Promise.resolve(Download.fromSerializable(aProperties));
    } catch (ex) {
      return Promise.reject(ex);
    }
  },

  /**
   * Downloads data from a remote network location to a local file.
   *
   * This download method does not provide user interface, or the ability to
   * cancel or restart the download programmatically.  For that, you should
   * obtain a reference to a Download object using the createDownload function.
   *
   * Since the download cannot be restarted, any partially downloaded data will
   * not be kept in case the download fails.
   *
   * @param aSource
   *        String containing the URI for the download source.  Alternatively,
   *        may be an nsIURI or a DownloadSource object.
   * @param aTarget
   *        String containing the path of the target file.  Alternatively, may
   *        be an nsIFile or a DownloadTarget object.
   * @param aOptions
   *        An optional object used to control the behavior of this function.
   *        You may pass an object with a subset of the following fields:
   *        {
   *          isPrivate: Indicates whether the download originated from a
   *                     private window.
   *        }
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  fetch(aSource, aTarget, aOptions) {
    return this.createDownload({
      source: aSource,
      target: aTarget,
    }).then(function D_SD_onSuccess(aDownload) {
      if (aOptions && ("isPrivate" in aOptions)) {
        aDownload.source.isPrivate = aOptions.isPrivate;
      }
      return aDownload.start();
    });
  },

  /**
   * Retrieves the specified type of DownloadList object.  There is one download
   * list for each type, and this method always retrieves a reference to the
   * same download list when called with the same argument.
   *
   * Calling this function may cause the list of public downloads to be reloaded
   * from the previous session, if it wasn't loaded already.
   *
   * @param aType
   *        This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
   *        Downloads added to the Downloads.PUBLIC and Downloads.PRIVATE lists
   *        are reflected in the Downloads.ALL list, and downloads added to the
   *        Downloads.ALL list are also added to either the Downloads.PUBLIC or
   *        the Downloads.PRIVATE list based on their properties.
   *
   * @return {Promise}
   * @resolves The requested DownloadList or DownloadCombinedList object.
   * @rejects JavaScript exception.
   */
  getList(aType) {
    if (!this._promiseListsInitialized) {
      this._promiseListsInitialized = (async () => {
        let publicList = new DownloadList();
        let privateList = new DownloadList();
        let combinedList = new DownloadCombinedList(publicList, privateList);

        try {
          await DownloadIntegration.addListObservers(publicList, false);
          await DownloadIntegration.addListObservers(privateList, true);
          await DownloadIntegration.initializePublicDownloadList(publicList);
        } catch (ex) {
          Cu.reportError(ex);
        }

        let publicSummary = await this.getSummary(Downloads.PUBLIC);
        let privateSummary = await this.getSummary(Downloads.PRIVATE);
        let combinedSummary = await this.getSummary(Downloads.ALL);

        await publicSummary.bindToList(publicList);
        await privateSummary.bindToList(privateList);
        await combinedSummary.bindToList(combinedList);

        this._lists[Downloads.PUBLIC] = publicList;
        this._lists[Downloads.PRIVATE] = privateList;
        this._lists[Downloads.ALL] = combinedList;
      })();
    }

    return this._promiseListsInitialized.then(() => this._lists[aType]);
  },

  /**
   * Promise resolved when the initialization of the download lists has
   * completed, or null if initialization has never been requested.
   */
  _promiseListsInitialized: null,

  /**
   * After initialization, this object is populated with one key for each type
   * of download list that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
   * or Downloads.ALL).  The values are the DownloadList objects.
   */
  _lists: {},

  /**
   * Retrieves the specified type of DownloadSummary object.  There is one
   * download summary for each type, and this method always retrieves a
   * reference to the same download summary when called with the same argument.
   *
   * Calling this function does not cause the list of public downloads to be
   * reloaded from the previous session.  The summary will behave as if no
   * downloads are present until the getList method is called.
   *
   * @param aType
   *        This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
   *
   * @return {Promise}
   * @resolves The requested DownloadList or DownloadCombinedList object.
   * @rejects JavaScript exception.
   */
  getSummary(aType) {
    if (aType != Downloads.PUBLIC && aType != Downloads.PRIVATE &&
        aType != Downloads.ALL) {
      throw new Error("Invalid aType argument.");
    }

    if (!(aType in this._summaries)) {
      this._summaries[aType] = new DownloadSummary();
    }

    return Promise.resolve(this._summaries[aType]);
  },

  /**
   * This object is populated by the getSummary method with one key for each
   * type of object that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
   * or Downloads.ALL).  The values are the DownloadSummary objects.
   */
  _summaries: {},

  /**
   * Returns the system downloads directory asynchronously.
   *   Mac OSX:
   *     User downloads directory
   *   XP/2K:
   *     My Documents/Downloads
   *   Vista and others:
   *     User downloads directory
   *   Linux:
   *     XDG user dir spec, with a fallback to Home/Downloads
   *   Android:
   *     standard downloads directory i.e. /sdcard
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getSystemDownloadsDirectory: function D_getSystemDownloadsDirectory() {
    return DownloadIntegration.getSystemDownloadsDirectory();
  },

  /**
   * Returns the preferred downloads directory based on the user preferences
   * in the current profile asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getPreferredDownloadsDirectory: function D_getPreferredDownloadsDirectory() {
    return DownloadIntegration.getPreferredDownloadsDirectory();
  },

  /**
   * Returns the temporary directory where downloads are placed before the
   * final location is chosen, or while the document is opened temporarily
   * with an external application. This may or may not be the system temporary
   * directory, based on the platform asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getTemporaryDownloadsDirectory: function D_getTemporaryDownloadsDirectory() {
    return DownloadIntegration.getTemporaryDownloadsDirectory();
  },

  /**
   * Constructor for a DownloadError object.  When you catch an exception during
   * a download, you can use this to verify if "ex instanceof Downloads.Error",
   * before reading the exception properties with the error details.
   */
  Error: DownloadError,
};
PK
!<%modules/EventEmitter.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "console",
                                  "resource://gre/modules/Console.jsm");

this.EXPORTED_SYMBOLS = ["EventEmitter"];

let EventEmitter = this.EventEmitter = function() {};

let loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
Services.prefs.addObserver("toolkit.dump.emit", {
  observe: () => {
    loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
  }
});

/**
 * Decorate an object with event emitter functionality.
 *
 * @param Object objectToDecorate
 *        Bind all public methods of EventEmitter to
 *        the objectToDecorate object.
 */
EventEmitter.decorate = function(objectToDecorate) {
  let emitter = new EventEmitter();
  objectToDecorate.on = emitter.on.bind(emitter);
  objectToDecorate.off = emitter.off.bind(emitter);
  objectToDecorate.once = emitter.once.bind(emitter);
  objectToDecorate.emit = emitter.emit.bind(emitter);
};

function describeNthCaller(n) {
  let caller = Components.stack;
  // Do one extra iteration to skip this function.
  while (n >= 0) {
    --n;
    caller = caller.caller;
  }

  let func = caller.name;
  let file = caller.filename;
  if (file.includes(" -> ")) {
    file = caller.filename.split(/ -> /)[1];
  }
  let path = file + ":" + caller.lineNumber;

  return func + "() -> " + path;
}

EventEmitter.prototype = {
  /**
   * Connect a listener.
   *
   * @param string event
   *        The event name to which we're connecting.
   * @param function listener
   *        Called when the event is fired.
   */
  on(event, listener) {
    if (!this._eventEmitterListeners) {
      this._eventEmitterListeners = new Map();
    }
    if (!this._eventEmitterListeners.has(event)) {
      this._eventEmitterListeners.set(event, []);
    }
    this._eventEmitterListeners.get(event).push(listener);
  },

  /**
   * Listen for the next time an event is fired.
   *
   * @param string event
   *        The event name to which we're connecting.
   * @param function listener
   *        (Optional) Called when the event is fired. Will be called at most
   *        one time.
   * @return promise
   *        A promise which is resolved when the event next happens. The
   *        resolution value of the promise is the first event argument. If
   *        you need access to second or subsequent event arguments (it's rare
   *        that this is needed) then use listener
   */
  once(event, listener) {
    return new Promise(resolve => {
      let handler = (_, first, ...rest) => {
        this.off(event, handler);
        if (listener) {
          listener(event, first, ...rest);
        }
        resolve(first);
      };

      handler._originalListener = listener;
      this.on(event, handler);
    });
  },

  /**
   * Remove a previously-registered event listener.  Works for events
   * registered with either on or once.
   *
   * @param string event
   *        The event name whose listener we're disconnecting.
   * @param function listener
   *        The listener to remove.
   */
  off(event, listener) {
    if (!this._eventEmitterListeners) {
      return;
    }
    let listeners = this._eventEmitterListeners.get(event);
    if (listeners) {
      this._eventEmitterListeners.set(event, listeners.filter(l => {
        return l !== listener && l._originalListener !== listener;
      }));
    }
  },

  /**
   * Emit an event.  All arguments to this method will
   * be sent to listener functions.
   */
  emit(event) {
    this.logEvent(event, arguments);

    if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(event)) {
      return;
    }

    let originalListeners = this._eventEmitterListeners.get(event);
    for (let listener of this._eventEmitterListeners.get(event)) {
      // If the object was destroyed during event emission, stop
      // emitting.
      if (!this._eventEmitterListeners) {
        break;
      }

      // If listeners were removed during emission, make sure the
      // event handler we're going to fire wasn't removed.
      if (originalListeners === this._eventEmitterListeners.get(event) ||
        this._eventEmitterListeners.get(event).some(l => l === listener)) {
        try {
          listener.apply(null, arguments);
        } catch (ex) {
          // Prevent a bad listener from interfering with the others.
          let msg = ex + ": " + ex.stack;
          console.error(msg);
          if (loggingEnabled) {
            dump(msg + "\n");
          }
        }
      }
    }
  },

  logEvent(event, args) {
    if (!loggingEnabled) {
      return;
    }

    let description = describeNthCaller(2);

    let argOut = "(";
    if (args.length === 1) {
      argOut += event;
    }

    let out = "EMITTING: ";

    // We need this try / catch to prevent any dead object errors.
    try {
      for (let i = 1; i < args.length; i++) {
        if (i === 1) {
          argOut = "(" + event + ", ";
        } else {
          argOut += ", ";
        }

        let arg = args[i];
        argOut += arg;

        if (arg && arg.nodeName) {
          argOut += " (" + arg.nodeName;
          if (arg.id) {
            argOut += "#" + arg.id;
          }
          if (arg.className) {
            argOut += "." + arg.className;
          }
          argOut += ")";
        }
      }
    } catch (e) {
      // Object is dead so the toolbox is most likely shutting down,
      // do nothing.
    }

    argOut += ")";
    out += "emit" + argOut + " from " + description + "\n";

    dump(out);
  },
};
PK
!<d^modules/Extension.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["Extension", "ExtensionData"];

/* exported Extension, ExtensionData */
/* globals Extension ExtensionData */

/*
 * This file is the main entry point for extensions. When an extension
 * loads, its bootstrap.js file creates a Extension instance
 * and calls .startup() on it. It calls .shutdown() when the extension
 * unloads. Extension manages any extension-specific state in
 * the chrome process.
 *
 * TODO(rpl): we are current restricting the extensions to a single process
 * (set as the current default value of the "dom.ipc.processCount.extension"
 * preference), if we switch to use more than one extension process, we have to
 * be sure that all the browser's frameLoader are associated to the same process,
 * e.g. by using the `sameProcessAsFrameLoader` property.
 * (http://searchfox.org/mozilla-central/source/dom/interfaces/base/nsIBrowser.idl)
 *
 * At that point we are going to keep track of the existing browsers associated to
 * a webextension to ensure that they are all running in the same process (and we
 * are also going to do the same with the browser element provided to the
 * addon debugging Remote Debugging actor, e.g. because the addon has been
 * reloaded by the user, we have to  ensure that the new extension pages are going
 * to run in the same process of the existing addon debugging browser element).
 */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.importGlobalProperties(["TextEncoder"]);

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

/* globals processCount */

XPCOMUtils.defineLazyPreferenceGetter(this, "processCount", "dom.ipc.processCount.extension");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
                                  "resource://gre/modules/ExtensionAPI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionCommon",
                                  "resource://gre/modules/ExtensionCommon.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
                                  "resource://gre/modules/ExtensionPermissions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                  "resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon",
                                  "resource://testing-common/ExtensionTestCommon.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "require",
                                  "resource://devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                  "resource://gre/modules/TelemetryStopwatch.jsm");

XPCOMUtils.defineLazyGetter(
  this, "processScript",
  () => Cc["@mozilla.org/webextensions/extension-process-script;1"]
          .getService().wrappedJSObject);

Cu.import("resource://gre/modules/ExtensionParent.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
                                   "@mozilla.org/addons/addon-manager-startup;1",
                                   "amIAddonManagerStartup");
XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
                                   "@mozilla.org/uuid-generator;1",
                                   "nsIUUIDGenerator");

var {
  GlobalManager,
  ParentAPIManager,
  StartupCache,
  apiManager: Management,
} = ExtensionParent;

const {
  EventEmitter,
  getUniqueId,
} = ExtensionUtils;

XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);

XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);

// The maximum time to wait for extension shutdown blockers to complete.
const SHUTDOWN_BLOCKER_MAX_MS = 1000;

// The list of properties that themes are allowed to contain.
XPCOMUtils.defineLazyGetter(this, "allowedThemeProperties", () => {
  Cu.import("resource://gre/modules/ExtensionParent.jsm");
  let propertiesInBaseManifest = ExtensionParent.baseManifestProperties;

  // The properties found in the base manifest contain all of the properties that
  // themes are allowed to have. However, the list also contains several properties
  // that aren't allowed, so we need to filter them out first before the list can
  // be used to validate themes.
  return propertiesInBaseManifest.filter(prop => {
    const propertiesToRemove = ["background", "content_scripts", "permissions"];
    return !propertiesToRemove.includes(prop);
  });
});

/**
 * Validates a theme to ensure it only contains static resources.
 *
 * @param {Array<string>} manifestProperties The list of top-level keys found in the
 *    the extension's manifest.
 * @returns {Array<string>} A list of invalid properties or an empty list
 *    if none are found.
 */
function validateThemeManifest(manifestProperties) {
  let invalidProps = [];
  for (let propName of manifestProperties) {
    if (propName != "theme" && !allowedThemeProperties.includes(propName)) {
      invalidProps.push(propName);
    }
  }
  return invalidProps;
}

/**
 * Classify an individual permission from a webextension manifest
 * as a host/origin permission, an api permission, or a regular permission.
 *
 * @param {string} perm  The permission string to classify
 *
 * @returns {object}
 *          An object with exactly one of the following properties:
 *          "origin" to indicate this is a host/origin permission.
 *          "api" to indicate this is an api permission
 *                (as used for webextensions experiments).
 *          "permission" to indicate this is a regular permission.
 */
function classifyPermission(perm) {
  let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm);
  if (!match) {
    return {origin: perm};
  } else if (match[1] == "experiments" && match[2]) {
    return {api: match[2]};
  }
  return {permission: perm};
}

const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";

const COMMENT_REGEXP = new RegExp(String.raw`
    ^
    (
      (?:
        [^"\n] |
        " (?:[^"\\\n] | \\.)* "
      )*?
    )

    //.*
  `.replace(/\s+/g, ""), "gm");

// All moz-extension URIs use a machine-specific UUID rather than the
// extension's own ID in the host component. This makes it more
// difficult for web pages to detect whether a user has a given add-on
// installed (by trying to load a moz-extension URI referring to a
// web_accessible_resource from the extension). UUIDMap.get()
// returns the UUID for a given add-on ID.
var UUIDMap = {
  _read() {
    let pref = Preferences.get(UUID_MAP_PREF, "{}");
    try {
      return JSON.parse(pref);
    } catch (e) {
      Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`);
      return {};
    }
  },

  _write(map) {
    Preferences.set(UUID_MAP_PREF, JSON.stringify(map));
  },

  get(id, create = true) {
    let map = this._read();

    if (id in map) {
      return map[id];
    }

    let uuid = null;
    if (create) {
      uuid = uuidGen.generateUUID().number;
      uuid = uuid.slice(1, -1); // Strip { and } off the UUID.

      map[id] = uuid;
      this._write(map);
    }
    return uuid;
  },

  remove(id) {
    let map = this._read();
    delete map[id];
    this._write(map);
  },
};

// This is the old interface that UUIDMap replaced, to be removed when
// the references listed in bug 1291399 are updated.
/* exported getExtensionUUID */
function getExtensionUUID(id) {
  return UUIDMap.get(id, true);
}

// For extensions that have called setUninstallURL(), send an event
// so the browser can display the URL.
var UninstallObserver = {
  initialized: false,

  init() {
    if (!this.initialized) {
      AddonManager.addAddonListener(this);
      XPCOMUtils.defineLazyPreferenceGetter(this, "leaveStorage", LEAVE_STORAGE_PREF, false);
      XPCOMUtils.defineLazyPreferenceGetter(this, "leaveUuid", LEAVE_UUID_PREF, false);
      this.initialized = true;
    }
  },

  onUninstalling(addon) {
    let extension = GlobalManager.extensionMap.get(addon.id);
    if (extension) {
      // Let any other interested listeners respond
      // (e.g., display the uninstall URL)
      Management.emit("uninstall", extension);
    }
  },

  onUninstalled(addon) {
    let uuid = UUIDMap.get(addon.id, false);
    if (!uuid) {
      return;
    }

    if (!this.leaveStorage) {
      // Clear browser.local.storage
      AsyncShutdown.profileChangeTeardown.addBlocker(
        `Clear Extension Storage ${addon.id}`,
        ExtensionStorage.clear(addon.id));

      // Clear any IndexedDB storage created by the extension
      let baseURI = Services.io.newURI(`moz-extension://${uuid}/`);
      let principal = Services.scriptSecurityManager.createCodebasePrincipal(
        baseURI, {});
      Services.qms.clearStoragesForPrincipal(principal);

      // Clear localStorage created by the extension
      let storage = Services.domStorageManager.getStorage(null, principal);
      if (storage) {
        storage.clear();
      }

      // Remove any permissions related to the unlimitedStorage permission
      // if we are also removing all the data stored by the extension.
      Services.perms.removeFromPrincipal(principal, "WebExtensions-unlimitedStorage");
      Services.perms.removeFromPrincipal(principal, "indexedDB");
      Services.perms.removeFromPrincipal(principal, "persistent-storage");
    }

    if (!this.leaveUuid) {
      // Clear the entry in the UUID map
      UUIDMap.remove(addon.id);
    }
  },
};

UninstallObserver.init();

// Represents the data contained in an extension, contained either
// in a directory or a zip file, which may or may not be installed.
// This class implements the functionality of the Extension class,
// primarily related to manifest parsing and localization, which is
// useful prior to extension installation or initialization.
//
// No functionality of this class is guaranteed to work before
// |loadManifest| has been called, and completed.
this.ExtensionData = class {
  constructor(rootURI) {
    this.rootURI = rootURI;

    this.manifest = null;
    this.id = null;
    this.uuid = null;
    this.localeData = null;
    this._promiseLocales = null;

    this.apiNames = new Set();
    this.dependencies = new Set();
    this.permissions = new Set();

    this.errors = [];
    this.warnings = [];
  }

  get builtinMessages() {
    return null;
  }

  get logger() {
    let id = this.id || "<unknown>";
    return Log.repository.getLogger(LOGGER_ID_BASE + id);
  }

  // Report an error about the extension's manifest file.
  manifestError(message) {
    this.packagingError(`Reading manifest: ${message}`);
  }

  manifestWarning(message) {
    this.packagingWarning(`Reading manifest: ${message}`);
  }

  // Report an error about the extension's general packaging.
  packagingError(message) {
    this.errors.push(message);
    this.logError(message);
  }

  packagingWarning(message) {
    this.warnings.push(message);
    this.logWarning(message);
  }

  logWarning(message) {
    this._logMessage(message, "warn");
  }

  logError(message) {
    this._logMessage(message, "error");
  }

  _logMessage(message, severity) {
    this.logger[severity](`Loading extension '${this.id}': ${message}`);
  }

  /**
   * Returns the moz-extension: URL for the given path within this
   * extension.
   *
   * Must not be called unless either the `id` or `uuid` property has
   * already been set.
   *
   * @param {string} path The path portion of the URL.
   * @returns {string}
   */
  getURL(path = "") {
    if (!(this.id || this.uuid)) {
      throw new Error("getURL may not be called before an `id` or `uuid` has been set");
    }
    if (!this.uuid) {
      this.uuid = UUIDMap.get(this.id);
    }
    return `moz-extension://${this.uuid}/${path}`;
  }

  async readDirectory(path) {
    if (this.rootURI instanceof Ci.nsIFileURL) {
      let uri = Services.io.newURI(this.rootURI.resolve("./" + path));
      let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path;

      let iter = new OS.File.DirectoryIterator(fullPath);
      let results = [];

      try {
        await iter.forEach(entry => {
          results.push(entry);
        });
      } catch (e) {
        // Always return a list, even if the directory does not exist (or is
        // not a directory) for symmetry with the ZipReader behavior.
        if (!e.becauseNoSuchFile) {
          Cu.reportError(e);
        }
      }
      iter.close();

      return results;
    }

    let uri = this.rootURI.QueryInterface(Ci.nsIJARURI);
    let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file;

    // Normalize the directory path.
    path = `${uri.JAREntry}/${path}`;
    path = path.replace(/\/\/+/g, "/").replace(/^\/|\/$/g, "") + "/";

    // Escape pattern metacharacters.
    let pattern = path.replace(/[[\]()?*~|$\\]/g, "\\$&") + "*";

    let results = [];
    for (let name of aomStartup.enumerateZipFile(file, pattern)) {
      if (!name.startsWith(path)) {
        throw new Error("Unexpected ZipReader entry");
      }

      // The enumerator returns the full path of all entries.
      // Trim off the leading path, and filter out entries from
      // subdirectories.
      name = name.slice(path.length);
      if (name && !/\/./.test(name)) {
        results.push({
          name: name.replace("/", ""),
          isDir: name.endsWith("/"),
        });
      }
    }

    return results;
  }

  readJSON(path) {
    return new Promise((resolve, reject) => {
      let uri = this.rootURI.resolve(`./${path}`);

      NetUtil.asyncFetch({uri, loadUsingSystemPrincipal: true}, (inputStream, status) => {
        if (!Components.isSuccessCode(status)) {
          // Convert status code to a string
          let e = Components.Exception("", status);
          reject(new Error(`Error while loading '${uri}' (${e.name})`));
          return;
        }
        try {
          let text = NetUtil.readInputStreamToString(inputStream, inputStream.available(),
                                                     {charset: "utf-8"});

          text = text.replace(COMMENT_REGEXP, "$1");

          resolve(JSON.parse(text));
        } catch (e) {
          reject(e);
        }
      });
    });
  }

  // This method should return a structured representation of any
  // capabilities this extension has access to, as derived from the
  // manifest.  The current implementation just returns the contents
  // of the permissions attribute, if we add things like url_overrides,
  // they should also be added here.
  get userPermissions() {
    let result = {
      origins: this.whiteListedHosts.patterns.map(matcher => matcher.pattern),
      apis: [...this.apiNames],
    };

    if (Array.isArray(this.manifest.content_scripts)) {
      for (let entry of this.manifest.content_scripts) {
        result.origins.push(...entry.matches);
      }
    }
    const EXP_PATTERN = /^experiments\.\w+/;
    result.permissions = [...this.permissions]
      .filter(p => !result.origins.includes(p) && !EXP_PATTERN.test(p));
    return result;
  }

  // Compute the difference between two sets of permissions, suitable
  // for presenting to the user.
  static comparePermissions(oldPermissions, newPermissions) {
    // See bug 1331769: should we do something more complicated to
    // compare host permissions?
    // e.g., if we go from <all_urls> to a specific host or from
    // a *.domain.com to specific-host.domain.com that's actually a
    // drop in permissions but the simple test below will cause a prompt.
    return {
      origins: newPermissions.origins.filter(perm => !oldPermissions.origins.includes(perm)),
      permissions: newPermissions.permissions.filter(perm => !oldPermissions.permissions.includes(perm)),
    };
  }

  parseManifest() {
    return Promise.all([
      this.readJSON("manifest.json"),
      Management.lazyInit(),
    ]).then(([manifest]) => {
      this.manifest = manifest;
      this.rawManifest = manifest;

      if (manifest && manifest.default_locale) {
        return this.initLocale();
      }
    }).then(() => {
      let context = {
        url: this.baseURI && this.baseURI.spec,

        principal: this.principal,

        logError: error => {
          this.manifestWarning(error);
        },

        preprocessors: {},
      };

      if (this.manifest.theme) {
        let invalidProps = validateThemeManifest(Object.getOwnPropertyNames(this.manifest));

        if (invalidProps.length) {
          let message = `Themes defined in the manifest may only contain static resources. ` +
            `If you would like to use additional properties, please use the "theme" permission instead. ` +
            `(the invalid properties found are: ${invalidProps})`;
          this.manifestError(message);
        }
      }

      if (this.localeData) {
        context.preprocessors.localize = (value, context) => this.localize(value);
      }

      let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
      if (normalized.error) {
        this.manifestError(normalized.error);
      } else {
        return normalized.value;
      }
    });
  }

  // Reads the extension's |manifest.json| file, and stores its
  // parsed contents in |this.manifest|.
  async loadManifest() {
    [this.manifest] = await Promise.all([
      this.parseManifest(),
      Management.lazyInit(),
    ]);

    if (!this.manifest) {
      return;
    }

    try {
      // Do not override the add-on id that has been already assigned.
      if (!this.id && this.manifest.applications.gecko.id) {
        this.id = this.manifest.applications.gecko.id;
      }
    } catch (e) {
      // Errors are handled by the type checks above.
    }

    let whitelist = [];
    for (let perm of this.manifest.permissions) {
      if (perm === "geckoProfiler") {
        const acceptedExtensions = Preferences.get("extensions.geckoProfiler.acceptedExtensionIds");
        if (!acceptedExtensions.split(",").includes(this.id)) {
          this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
          continue;
        }
      }

      let type = classifyPermission(perm);
      if (type.origin) {
        let matcher = new MatchPattern(perm, {ignorePath: true});

        whitelist.push(matcher);
        perm = matcher.pattern;
      } else if (type.api) {
        this.apiNames.add(type.api);
      }

      this.permissions.add(perm);
    }

    // An extension always gets permission to its own url.
    if (this.id) {
      let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
      whitelist.push(matcher);
    }

    this.whiteListedHosts = new MatchPatternSet(whitelist);

    for (let api of this.apiNames) {
      this.dependencies.add(`${api}@experiments.addons.mozilla.org`);
    }

    return this.manifest;
  }

  localizeMessage(...args) {
    return this.localeData.localizeMessage(...args);
  }

  localize(...args) {
    return this.localeData.localize(...args);
  }

  // If a "default_locale" is specified in that manifest, returns it
  // as a Gecko-compatible locale string. Otherwise, returns null.
  get defaultLocale() {
    if (this.manifest.default_locale != null) {
      return this.normalizeLocaleCode(this.manifest.default_locale);
    }

    return null;
  }

  // Normalizes a Chrome-compatible locale code to the appropriate
  // Gecko-compatible variant. Currently, this means simply
  // replacing underscores with hyphens.
  normalizeLocaleCode(locale) {
    return locale.replace(/_/g, "-");
  }

  // Reads the locale file for the given Gecko-compatible locale code, and
  // stores its parsed contents in |this.localeMessages.get(locale)|.
  async readLocaleFile(locale) {
    let locales = await this.promiseLocales();
    let dir = locales.get(locale) || locale;
    let file = `_locales/${dir}/messages.json`;

    try {
      let messages = await this.readJSON(file);
      return this.localeData.addLocale(locale, messages, this);
    } catch (e) {
      this.packagingError(`Loading locale file ${file}: ${e}`);
      return new Map();
    }
  }

  async _promiseLocaleMap() {
    let locales = new Map();

    let entries = await this.readDirectory("_locales");
    for (let file of entries) {
      if (file.isDir) {
        let locale = this.normalizeLocaleCode(file.name);
        locales.set(locale, file.name);
      }
    }

    return locales;
  }

  _setupLocaleData(locales) {
    if (this.localeData) {
      return this.localeData.locales;
    }

    this.localeData = new LocaleData({
      defaultLocale: this.defaultLocale,
      locales,
      builtinMessages: this.builtinMessages,
    });

    return locales;
  }

  // Reads the list of locales available in the extension, and returns a
  // Promise which resolves to a Map upon completion.
  // Each map key is a Gecko-compatible locale code, and each value is the
  // "_locales" subdirectory containing that locale:
  //
  // Map(gecko-locale-code -> locale-directory-name)
  promiseLocales() {
    if (!this._promiseLocales) {
      this._promiseLocales = (async () => {
        let locales = this._promiseLocaleMap();
        return this._setupLocaleData(locales);
      })();
    }

    return this._promiseLocales;
  }

  // Reads the locale messages for all locales, and returns a promise which
  // resolves to a Map of locale messages upon completion. Each key in the map
  // is a Gecko-compatible locale code, and each value is a locale data object
  // as returned by |readLocaleFile|.
  async initAllLocales() {
    let locales = await this.promiseLocales();

    await Promise.all(Array.from(locales.keys(),
                                 locale => this.readLocaleFile(locale)));

    let defaultLocale = this.defaultLocale;
    if (defaultLocale) {
      if (!locales.has(defaultLocale)) {
        this.manifestError('Value for "default_locale" property must correspond to ' +
                           'a directory in "_locales/". Not found: ' +
                           JSON.stringify(`_locales/${this.manifest.default_locale}/`));
      }
    } else if (locales.size) {
      this.manifestError('The "default_locale" property is required when a ' +
                         '"_locales/" directory is present.');
    }

    return this.localeData.messages;
  }

  // Reads the locale file for the given Gecko-compatible locale code, or the
  // default locale if no locale code is given, and sets it as the currently
  // selected locale on success.
  //
  // Pre-loads the default locale for fallback message processing, regardless
  // of the locale specified.
  //
  // If no locales are unavailable, resolves to |null|.
  async initLocale(locale = this.defaultLocale) {
    if (locale == null) {
      return null;
    }

    let promises = [this.readLocaleFile(locale)];

    let {defaultLocale} = this;
    if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
      promises.push(this.readLocaleFile(defaultLocale));
    }

    let results = await Promise.all(promises);

    this.localeData.selectedLocale = locale;
    return results[0];
  }
};

const PROXIED_EVENTS = new Set(["test-harness-message", "add-permissions", "remove-permissions"]);

const shutdownPromises = new Map();

// We create one instance of this class per extension. |addonData|
// comes directly from bootstrap.js when initializing.
this.Extension = class extends ExtensionData {
  constructor(addonData, startupReason) {
    super(addonData.resourceURI);

    this.uuid = UUIDMap.get(addonData.id);
    this.instanceId = getUniqueId();

    this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
    Services.ppmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);

    if (addonData.cleanupFile) {
      Services.obs.addObserver(this, "xpcom-shutdown");
      this.cleanupFile = addonData.cleanupFile || null;
      delete addonData.cleanupFile;
    }

    this.addonData = addonData;
    this.startupReason = startupReason;

    if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
      StartupCache.clearAddonData(addonData.id);
    }

    this.remote = !WebExtensionPolicy.isExtensionProcess;

    if (this.remote && processCount !== 1) {
      throw new Error("Out-of-process WebExtensions are not supported with multiple child processes");
    }

    // This is filled in the first time an extension child is created.
    this.parentMessageManager = null;

    this.id = addonData.id;
    this.version = addonData.version;
    this.baseURI = Services.io.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
    this.principal = this.createPrincipal();
    this.views = new Set();
    this._backgroundPageFrameLoader = null;

    this.onStartup = null;

    this.hasShutdown = false;
    this.onShutdown = new Set();

    this.uninstallURL = null;

    this.apis = [];
    this.whiteListedHosts = null;
    this._optionalOrigins = null;
    this.webAccessibleResources = null;

    this.emitter = new EventEmitter();

    /* eslint-disable mozilla/balanced-listeners */
    this.on("add-permissions", (ignoreEvent, permissions) => {
      for (let perm of permissions.permissions) {
        this.permissions.add(perm);
      }

      if (permissions.origins.length > 0) {
        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);

        this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
                                                    {ignorePath: true});
      }

      this.policy.permissions = Array.from(this.permissions);
      this.policy.allowedOrigins = this.whiteListedHosts;
    });

    this.on("remove-permissions", (ignoreEvent, permissions) => {
      for (let perm of permissions.permissions) {
        this.permissions.delete(perm);
      }

      let origins = permissions.origins.map(
        origin => new MatchPattern(origin, {ignorePath: true}).pattern);

      this.whiteListedHosts = new MatchPatternSet(
        this.whiteListedHosts.patterns
            .filter(host => !origins.includes(host.pattern)));

      this.policy.permissions = Array.from(this.permissions);
      this.policy.allowedOrigins = this.whiteListedHosts;
    });
    /* eslint-enable mozilla/balanced-listeners */
  }

  get groupFrameLoader() {
    let frameLoader = this._backgroundPageFrameLoader;
    for (let view of this.views) {
      if (view.viewType === "background" && view.xulBrowser) {
        return view.xulBrowser.frameLoader;
      }
      if (!frameLoader && view.xulBrowser) {
        frameLoader = view.xulBrowser.frameLoader;
      }
    }
    return frameLoader || ExtensionParent.DebugUtils.getFrameLoader(this.id);
  }

  static generateXPI(data) {
    return ExtensionTestCommon.generateXPI(data);
  }

  static generateZipFile(files, baseName = "generated-extension.xpi") {
    return ExtensionTestCommon.generateZipFile(files, baseName);
  }

  static generate(data) {
    return ExtensionTestCommon.generate(data);
  }

  on(hook, f) {
    return this.emitter.on(hook, f);
  }

  off(hook, f) {
    return this.emitter.off(hook, f);
  }

  once(hook, f) {
    return this.emitter.once(hook, f);
  }

  emit(event, ...args) {
    if (PROXIED_EVENTS.has(event)) {
      Services.ppmm.broadcastAsyncMessage(this.MESSAGE_EMIT_EVENT, {event, args});
    }

    return this.emitter.emit(event, ...args);
  }

  receiveMessage({name, data}) {
    if (name === this.MESSAGE_EMIT_EVENT) {
      this.emitter.emit(data.event, ...data.args);
    }
  }

  testMessage(...args) {
    this.emit("test-harness-message", ...args);
  }

  createPrincipal(uri = this.baseURI) {
    return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
  }

  // Checks that the given URL is a child of our baseURI.
  isExtensionURL(url) {
    let uri = Services.io.newURI(url);

    let common = this.baseURI.getCommonBaseSpec(uri);
    return common == this.baseURI.spec;
  }

  async promiseLocales(locale) {
    let locales = await StartupCache.locales
      .get([this.id, "@@all_locales"], () => this._promiseLocaleMap());

    return this._setupLocaleData(locales);
  }

  readLocaleFile(locale) {
    return StartupCache.locales.get([this.id, this.version, locale],
                                    () => super.readLocaleFile(locale))
      .then(result => {
        this.localeData.messages.set(locale, result);
      });
  }

  parseManifest() {
    return StartupCache.manifests.get([this.id, this.version, Services.locale.getAppLocaleAsLangTag()],
                                      () => super.parseManifest());
  }

  loadManifest() {
    return super.loadManifest().then(manifest => {
      if (this.errors.length) {
        return Promise.reject({errors: this.errors});
      }

      // Load Experiments APIs that this extension depends on.
      return Promise.all(
        Array.from(this.apiNames, api => ExtensionAPIs.load(api))
      ).then(apis => {
        for (let API of apis) {
          this.apis.push(new API(this));
        }

        return manifest;
      });
    });
  }

  // Representation of the extension to send to content
  // processes. This should include anything the content process might
  // need.
  serialize() {
    return {
      id: this.id,
      uuid: this.uuid,
      instanceId: this.instanceId,
      manifest: this.manifest,
      resourceURL: this.addonData.resourceURI.spec,
      baseURL: this.baseURI.spec,
      content_scripts: this.manifest.content_scripts || [],  // eslint-disable-line camelcase
      webAccessibleResources: this.webAccessibleResources.map(res => res.glob),
      whiteListedHosts: this.whiteListedHosts.patterns.map(pat => pat.pattern),
      localeData: this.localeData.serialize(),
      permissions: this.permissions,
      principal: this.principal,
      optionalPermissions: this.manifest.optional_permissions,
    };
  }

  broadcast(msg, data) {
    return new Promise(resolve => {
      let {ppmm} = Services;
      let children = new Set();
      for (let i = 0; i < ppmm.childCount; i++) {
        children.add(ppmm.getChildAt(i));
      }

      let maybeResolve;
      function listener(data) {
        children.delete(data.target);
        maybeResolve();
      }
      function observer(subject, topic, data) {
        children.delete(subject);
        maybeResolve();
      }

      maybeResolve = () => {
        if (children.size === 0) {
          ppmm.removeMessageListener(msg + "Complete", listener);
          Services.obs.removeObserver(observer, "message-manager-close");
          Services.obs.removeObserver(observer, "message-manager-disconnect");
          resolve();
        }
      };
      ppmm.addMessageListener(msg + "Complete", listener, true);
      Services.obs.addObserver(observer, "message-manager-close");
      Services.obs.addObserver(observer, "message-manager-disconnect");

      ppmm.broadcastAsyncMessage(msg, data);
    });
  }

  runManifest(manifest) {
    let promises = [];
    for (let directive in manifest) {
      if (manifest[directive] !== null) {
        promises.push(Management.emit(`manifest_${directive}`, directive, this, manifest));

        promises.push(Management.asyncEmitManifestEntry(this, directive));
      }
    }

    let data = Services.ppmm.initialProcessData;
    if (!data["Extension:Extensions"]) {
      data["Extension:Extensions"] = [];
    }
    let serial = this.serialize();
    data["Extension:Extensions"].push(serial);

    return this.broadcast("Extension:Startup", serial).then(() => {
      return Promise.all(promises);
    });
  }

  callOnClose(obj) {
    this.onShutdown.add(obj);
  }

  forgetOnClose(obj) {
    this.onShutdown.delete(obj);
  }

  get builtinMessages() {
    return new Map([
      ["@@extension_id", this.uuid],
    ]);
  }

  // Reads the locale file for the given Gecko-compatible locale code, or if
  // no locale is given, the available locale closest to the UI locale.
  // Sets the currently selected locale on success.
  async initLocale(locale = undefined) {
    if (locale === undefined) {
      let locales = await this.promiseLocales();

      let matches = Services.locale.negotiateLanguages(
        Services.locale.getAppLocalesAsLangTags(),
        Array.from(locales.keys()),
        this.defaultLocale);

      locale = matches[0];
    }

    return super.initLocale(locale);
  }

  initUnlimitedStoragePermission() {
    const principal = this.principal;

    // Check if the site permission has already been set for the extension by the WebExtensions
    // internals (instead of being manually allowed by the user).
    const hasSitePermission = Services.perms.testPermissionFromPrincipal(
      principal, "WebExtensions-unlimitedStorage"
    );

    if (this.hasPermission("unlimitedStorage")) {
      // Set the indexedDB permission and a custom "WebExtensions-unlimitedStorage" to remember
      // that the permission hasn't been selected manually by the user.
      Services.perms.addFromPrincipal(principal, "WebExtensions-unlimitedStorage",
                                      Services.perms.ALLOW_ACTION);
      Services.perms.addFromPrincipal(principal, "indexedDB", Services.perms.ALLOW_ACTION);
      Services.perms.addFromPrincipal(principal, "persistent-storage", Services.perms.ALLOW_ACTION);
    } else if (hasSitePermission) {
      // Remove the indexedDB permission if it has been enabled using the
      // unlimitedStorage WebExtensions permissions.
      Services.perms.removeFromPrincipal(principal, "WebExtensions-unlimitedStorage");
      Services.perms.removeFromPrincipal(principal, "indexedDB");
      Services.perms.removeFromPrincipal(principal, "persistent-storage");
    }
  }

  startup() {
    this.startupPromise = this._startup();

    return this.startupPromise;
  }

  async _startup() {
    if (shutdownPromises.has(this.id)) {
      await shutdownPromises.get(this.id);
    }

    // Create a temporary policy object for the devtools and add-on
    // manager callers that depend on it being available early.
    this.policy = new WebExtensionPolicy({
      id: this.id,
      mozExtensionHostname: this.uuid,
      baseURL: this.baseURI.spec,
      allowedOrigins: new MatchPatternSet([]),
      localizeCallback() {},
    });
    if (!WebExtensionPolicy.getByID(this.id)) {
      // The add-on manager doesn't handle async startup and shutdown,
      // so during upgrades and add-on restarts, startup() gets called
      // before the last shutdown has completed, and this fails when
      // there's another active add-on with the same ID.
      this.policy.active = true;
    }

    TelemetryStopwatch.start("WEBEXT_EXTENSION_STARTUP_MS", this);
    try {
      let [perms] = await Promise.all([
        ExtensionPermissions.get(this),
        this.loadManifest(),
      ]);

      if (!this.hasShutdown) {
        await this.initLocale();
      }

      if (this.errors.length) {
        return Promise.reject({errors: this.errors});
      }

      if (this.hasShutdown) {
        return;
      }

      GlobalManager.init(this);

      // Apply optional permissions
      for (let perm of perms.permissions) {
        this.permissions.add(perm);
      }
      if (perms.origins.length > 0) {
        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);

        this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
                                                    {ignorePath: true});
      }

      // Normalize all patterns to contain a single leading /
      let resources = (this.manifest.web_accessible_resources || [])
          .map(path => path.replace(/^\/*/, "/"));

      this.webAccessibleResources = resources.map(res => new MatchGlob(res));


      this.policy.active = false;
      this.policy = processScript.initExtension(this.serialize(), this);

      this.initUnlimitedStoragePermission();

      // The "startup" Management event sent on the extension instance itself
      // is emitted just before the Management "startup" event,
      // and it is used to run code that needs to be executed before
      // any of the "startup" listeners.
      this.emit("startup", this);
      Management.emit("startup", this);

      await this.runManifest(this.manifest);

      Management.emit("ready", this);
      this.emit("ready");
      TelemetryStopwatch.finish("WEBEXT_EXTENSION_STARTUP_MS", this);
    } catch (e) {
      dump(`Extension error: ${e.message || e} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
      Cu.reportError(e);

      if (this.policy) {
        this.policy.active = false;
      }

      this.cleanupGeneratedFile();

      throw e;
    }

    this.startupPromise = null;
  }

  cleanupGeneratedFile() {
    if (!this.cleanupFile) {
      return;
    }

    let file = this.cleanupFile;
    this.cleanupFile = null;

    Services.obs.removeObserver(this, "xpcom-shutdown");

    return this.broadcast("Extension:FlushJarCache", {path: file.path}).then(() => {
      // We can't delete this file until everyone using it has
      // closed it (because Windows is dumb). So we wait for all the
      // child processes (including the parent) to flush their JAR
      // caches. These caches may keep the file open.
      file.remove(false);
    }).catch(Cu.reportError);
  }

  async shutdown(reason) {
    let promise = this._shutdown(reason);

    let blocker = () => {
      return Promise.race([
        promise,
        new Promise(resolve => setTimeout(resolve, SHUTDOWN_BLOCKER_MAX_MS)),
      ]);
    };

    AsyncShutdown.profileChangeTeardown.addBlocker(
      `Extension Shutdown: ${this.id} (${this.manifest && this.name})`,
      blocker);

    // If we already have a shutdown promise for this extension, wait
    // for it to complete before replacing it with a new one. This can
    // sometimes happen during tests with rapid startup/shutdown cycles
    // of multiple versions.
    if (shutdownPromises.has(this.id)) {
      await shutdownPromises.get(this.id);
    }

    let cleanup = () => {
      shutdownPromises.delete(this.id);
      AsyncShutdown.profileChangeTeardown.removeBlocker(blocker);
    };
    shutdownPromises.set(this.id, promise.then(cleanup, cleanup));

    return Promise.resolve(promise);
  }

  async _shutdown(reason) {
    try {
      if (this.startupPromise) {
        await this.startupPromise;
      }
    } catch (e) {
      Cu.reportError(e);
    }

    this.shutdownReason = reason;
    this.hasShutdown = true;

    if (!this.policy) {
      return;
    }

    if (this.rootURI instanceof Ci.nsIJARURI) {
      let file = this.rootURI.JARFile.QueryInterface(Ci.nsIFileURL).file;
      Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path});
    }

    if (this.cleanupFile ||
        ["ADDON_INSTALL", "ADDON_UNINSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(reason)) {
      StartupCache.clearAddonData(this.id);
    }

    let data = Services.ppmm.initialProcessData;
    data["Extension:Extensions"] = data["Extension:Extensions"].filter(e => e.id !== this.id);

    Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);

    if (!this.manifest) {
      this.policy.active = false;

      return this.cleanupGeneratedFile();
    }

    GlobalManager.uninit(this);

    for (let obj of this.onShutdown) {
      obj.close();
    }

    for (let api of this.apis) {
      api.destroy();
    }

    ParentAPIManager.shutdownExtension(this.id);

    Management.emit("shutdown", this);
    this.emit("shutdown");

    await this.broadcast("Extension:Shutdown", {id: this.id});

    MessageChannel.abortResponses({extensionId: this.id});

    this.policy.active = false;

    return this.cleanupGeneratedFile();
  }

  observe(subject, topic, data) {
    if (topic === "xpcom-shutdown") {
      this.cleanupGeneratedFile();
    }
  }

  hasPermission(perm, includeOptional = false) {
    let manifest_ = "manifest:";
    if (perm.startsWith(manifest_)) {
      return this.manifest[perm.substr(manifest_.length)] != null;
    }

    if (this.permissions.has(perm)) {
      return true;
    }

    if (includeOptional && this.manifest.optional_permissions.includes(perm)) {
      return true;
    }

    return false;
  }

  get name() {
    return this.manifest.name;
  }

  get optionalOrigins() {
    if (this._optionalOrigins == null) {
      let origins = this.manifest.optional_permissions.filter(perm => classifyPermission(perm).origin);
      this._optionalOrigins = new MatchPatternSet(origins, {ignorePath: true});
    }
    return this._optionalOrigins;
  }
};
PK
!<n

modules/ExtensionAPI.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ExtensionAPI", "ExtensionAPIs"];

/* exported ExtensionAPIs */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://gre/modules/EventEmitter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");

const global = this;

class ExtensionAPI {
  constructor(extension) {
    this.extension = extension;

    extension.once("shutdown", () => {
      if (this.onShutdown) {
        this.onShutdown(extension.shutdownReason);
      }
      this.extension = null;
    });
  }

  destroy() {
  }

  onManifestEntry(entry) {
  }

  getAPI(context) {
    throw new Error("Not Implemented");
  }
}

var ExtensionAPIs = {
  apis: new Map(),

  load(apiName) {
    let api = this.apis.get(apiName);

    if (api.loadPromise) {
      return api.loadPromise;
    }

    let {script, schema} = api;

    let addonId = `${apiName}@experiments.addons.mozilla.org`;
    api.sandbox = Cu.Sandbox(global, {
      wantXrays: false,
      sandboxName: script,
      addonId,
      metadata: {addonID: addonId},
    });

    api.sandbox.ExtensionAPI = ExtensionAPI;

    // Create a console getter which lazily provide a ConsoleAPI instance.
    XPCOMUtils.defineLazyGetter(api.sandbox, "console", () => {
      return new ConsoleAPI({prefix: addonId});
    });

    Services.scriptloader.loadSubScript(script, api.sandbox, "UTF-8");

    api.loadPromise = Schemas.load(schema).then(() => {
      let API = Cu.evalInSandbox("API", api.sandbox);
      API.prototype.namespace = apiName;
      return API;
    });

    return api.loadPromise;
  },

  unload(apiName) {
    let api = this.apis.get(apiName);

    let {schema} = api;

    Schemas.unload(schema);
    Cu.nukeSandbox(api.sandbox);

    api.sandbox = null;
    api.loadPromise = null;
  },

  register(namespace, schema, script) {
    if (this.apis.has(namespace)) {
      throw new Error(`API namespace already exists: ${namespace}`);
    }

    this.apis.set(namespace, {schema, script});
  },

  unregister(namespace) {
    if (!this.apis.has(namespace)) {
      throw new Error(`API namespace does not exist: ${namespace}`);
    }

    this.apis.delete(namespace);
  },
};
PK
!<Xttmodules/ExtensionChild.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 ExtensionChild */

this.EXPORTED_SYMBOLS = ["ExtensionChild"];

/*
 * This file handles addon logic that is independent of the chrome process.
 * When addons run out-of-process, this is the main entry point.
 * Its primary function is managing addon globals.
 *
 * Don't put contentscript logic here, use ExtensionContent.jsm instead.
 */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionContent",
                                  "resource://gre/modules/ExtensionContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                  "resource://gre/modules/NativeMessaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");

Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

const {
  DefaultMap,
  EventEmitter,
  LimitedSet,
  defineLazyGetter,
  getMessageManager,
  getUniqueId,
  withHandlingUserInput,
} = ExtensionUtils;

const {
  EventManager,
  LocalAPIImplementation,
  LocaleData,
  NoCloneSpreadArgs,
  SchemaAPIInterface,
} = ExtensionCommon;

const isContentProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

// Copy an API object from |source| into the scope |dest|.
function injectAPI(source, dest) {
  for (let prop in source) {
    // Skip names prefixed with '_'.
    if (prop[0] == "_") {
      continue;
    }

    let desc = Object.getOwnPropertyDescriptor(source, prop);
    if (typeof(desc.value) == "function") {
      Cu.exportFunction(desc.value, dest, {defineAs: prop});
    } else if (typeof(desc.value) == "object") {
      let obj = Cu.createObjectIn(dest, {defineAs: prop});
      injectAPI(desc.value, obj);
    } else {
      Object.defineProperty(dest, prop, desc);
    }
  }
}

/**
 * Abstraction for a Port object in the extension API.
 *
 * @param {BaseContext} context The context that owns this port.
 * @param {nsIMessageSender} senderMM The message manager to send messages to.
 * @param {Array<nsIMessageListenerManager>} receiverMMs Message managers to
 *     listen on.
 * @param {string} name Arbitrary port name as defined by the addon.
 * @param {string} id An ID that uniquely identifies this port's channel.
 * @param {object} sender The `port.sender` property.
 * @param {object} recipient The recipient of messages sent from this port.
 */
class Port {
  constructor(context, senderMM, receiverMMs, name, id, sender, recipient) {
    this.context = context;
    this.senderMM = senderMM;
    this.receiverMMs = receiverMMs;
    this.name = name;
    this.id = id;
    this.sender = sender;
    this.recipient = recipient;
    this.disconnected = false;
    this.disconnectListeners = new Set();
    this.unregisterMessageFuncs = new Set();

    // Common options for onMessage and onDisconnect.
    this.handlerBase = {
      messageFilterStrict: {portId: id},

      filterMessage: (sender, recipient) => {
        return sender.contextId !== this.context.contextId;
      },
    };

    this.disconnectHandler = Object.assign({
      receiveMessage: ({data}) => this.disconnectByOtherEnd(data),
    }, this.handlerBase);

    MessageChannel.addListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler);

    this.context.callOnClose(this);
  }

  api() {
    let portObj = Cu.createObjectIn(this.context.cloneScope);

    let portError = null;
    let publicAPI = {
      name: this.name,

      disconnect: () => {
        this.disconnect();
      },

      postMessage: json => {
        this.postMessage(json);
      },

      onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
        return this.registerOnDisconnect(holder => {
          let error = holder.deserialize(this.context.cloneScope);
          portError = error && this.context.normalizeError(error);
          fire.asyncWithoutClone(portObj);
        });
      }).api(),

      onMessage: new EventManager(this.context, "Port.onMessage", fire => {
        return this.registerOnMessage(holder => {
          let msg = holder.deserialize(this.context.cloneScope);
          fire.asyncWithoutClone(msg, portObj);
        });
      }).api(),

      get error() {
        return portError;
      },
    };

    if (this.sender) {
      publicAPI.sender = this.sender;
    }

    injectAPI(publicAPI, portObj);
    return portObj;
  }

  postMessage(json) {
    if (this.disconnected) {
      throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
    }

    this._sendMessage("Extension:Port:PostMessage", json);
  }

  /**
   * Register a callback that is called when the port is disconnected by the
   * *other* end. The callback is automatically unregistered when the port or
   * context is closed.
   *
   * @param {function} callback Called when the other end disconnects the port.
   *     If the disconnect is caused by an error, the first parameter is an
   *     object with a "message" string property that describes the cause.
   * @returns {function} Function to unregister the listener.
   */
  registerOnDisconnect(callback) {
    let listener = error => {
      if (this.context.active && !this.disconnected) {
        callback(error);
      }
    };
    this.disconnectListeners.add(listener);
    return () => {
      this.disconnectListeners.delete(listener);
    };
  }

  /**
   * Register a callback that is called when a message is received. The callback
   * is automatically unregistered when the port or context is closed.
   *
   * @param {function} callback Called when a message is received.
   * @returns {function} Function to unregister the listener.
   */
  registerOnMessage(callback) {
    let handler = Object.assign({
      receiveMessage: ({data}) => {
        if (this.context.active && !this.disconnected) {
          callback(data);
        }
      },
    }, this.handlerBase);

    let unregister = () => {
      this.unregisterMessageFuncs.delete(unregister);
      MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
    };
    MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
    this.unregisterMessageFuncs.add(unregister);
    return unregister;
  }

  _sendMessage(message, data) {
    let options = {
      recipient: Object.assign({}, this.recipient, {portId: this.id}),
      responseType: MessageChannel.RESPONSE_NONE,
    };

    let holder = new StructuredCloneHolder(data);

    return this.context.sendMessage(this.senderMM, message, holder, options);
  }

  handleDisconnection() {
    MessageChannel.removeListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler);
    for (let unregister of this.unregisterMessageFuncs) {
      unregister();
    }
    this.context.forgetOnClose(this);
    this.disconnected = true;
  }

  /**
   * Disconnect the port from the other end (which may not even exist).
   *
   * @param {Error|{message: string}} [error] The reason for disconnecting,
   *     if it is an abnormal disconnect.
   */
  disconnectByOtherEnd(error = null) {
    if (this.disconnected) {
      return;
    }

    for (let listener of this.disconnectListeners) {
      listener(error);
    }

    this.handleDisconnection();
  }

  /**
   * Disconnect the port from this end.
   *
   * @param {Error|{message: string}} [error] The reason for disconnecting,
   *     if it is an abnormal disconnect.
   */
  disconnect(error = null) {
    if (this.disconnected) {
      // disconnect() may be called without side effects even after the port is
      // closed - https://developer.chrome.com/extensions/runtime#type-Port
      return;
    }
    this.handleDisconnection();
    if (error) {
      error = {message: this.context.normalizeError(error).message};
    }
    this._sendMessage("Extension:Port:Disconnect", error);
  }

  close() {
    this.disconnect();
  }
}

class NativePort extends Port {
  postMessage(data) {
    data = NativeApp.encodeMessage(this.context, data);

    return super.postMessage(data);
  }
}

/**
 * Each extension context gets its own Messenger object. It handles the
 * basics of sendMessage, onMessage, connect and onConnect.
 *
 * @param {BaseContext} context The context to which this Messenger is tied.
 * @param {Array<nsIMessageListenerManager>} messageManagers
 *     The message managers used to receive messages (e.g. onMessage/onConnect
 *     requests).
 * @param {object} sender Describes this sender to the recipient. This object
 *     is extended further by BaseContext's sendMessage method and appears as
 *     the `sender` object to `onConnect` and `onMessage`.
 *     Do not set the `extensionId`, `contextId` or `tab` properties. The former
 *     two are added by BaseContext's sendMessage, while `sender.tab` is set by
 *     the ProxyMessenger in the main process.
 * @param {object} filter A recipient filter to apply to incoming messages from
 *     the broker. Messages are only handled by this Messenger if all key-value
 *     pairs match the `recipient` as specified by the sender of the message.
 *     In other words, this filter defines the required fields of `recipient`.
 * @param {object} [optionalFilter] An additional filter to apply to incoming
 *     messages. Unlike `filter`, the keys from `optionalFilter` are allowed to
 *     be omitted from `recipient`. Only keys that are present in both
 *     `optionalFilter` and `recipient` are applied to filter incoming messages.
 */
class Messenger {
  constructor(context, messageManagers, sender, filter, optionalFilter) {
    this.context = context;
    this.messageManagers = messageManagers;
    this.sender = sender;
    this.filter = filter;
    this.optionalFilter = optionalFilter;
  }

  _sendMessage(messageManager, message, data, recipient) {
    let options = {
      recipient,
      sender: this.sender,
      responseType: MessageChannel.RESPONSE_FIRST,
    };

    return this.context.sendMessage(messageManager, message, data, options);
  }

  sendMessage(messageManager, msg, recipient, responseCallback) {
    let holder = new StructuredCloneHolder(msg);

    let promise = this._sendMessage(messageManager, "Extension:Message", holder, recipient)
      .catch(error => {
        if (error.result == MessageChannel.RESULT_NO_HANDLER) {
          return Promise.reject({message: "Could not establish connection. Receiving end does not exist."});
        } else if (error.result != MessageChannel.RESULT_NO_RESPONSE) {
          return Promise.reject({message: error.message});
        }
      });

    return this.context.wrapPromise(promise, responseCallback);
  }

  sendNativeMessage(messageManager, msg, recipient, responseCallback) {
    msg = NativeApp.encodeMessage(this.context, msg);
    return this.sendMessage(messageManager, msg, recipient, responseCallback);
  }

  _onMessage(name, filter) {
    return new EventManager(this.context, name, fire => {
      let listener = {
        messageFilterPermissive: this.optionalFilter,
        messageFilterStrict: this.filter,

        filterMessage: (sender, recipient) => {
          // Ignore the message if it was sent by this Messenger.
          return (sender.contextId !== this.context.contextId &&
                  filter(sender, recipient));
        },

        receiveMessage: ({target, data: holder, sender, recipient}) => {
          if (!this.context.active) {
            return;
          }

          let sendResponse;
          let response = undefined;
          let promise = new Promise(resolve => {
            sendResponse = value => {
              resolve(value);
              response = promise;
            };
          });

          let message = holder.deserialize(this.context.cloneScope);
          sender = Cu.cloneInto(sender, this.context.cloneScope);
          sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);

          // Note: We intentionally do not use runSafe here so that any
          // errors are propagated to the message sender.
          let result = fire.raw(message, sender, sendResponse);
          if (result instanceof this.context.cloneScope.Promise) {
            return result;
          } else if (result === true) {
            return promise;
          }
          return response;
        },
      };

      MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
      return () => {
        MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
      };
    }).api();
  }

  onMessage(name) {
    return this._onMessage(name, sender => sender.id === this.sender.id);
  }

  onMessageExternal(name) {
    return this._onMessage(name, sender => sender.id !== this.sender.id);
  }

  _connect(messageManager, port, recipient) {
    let msg = {
      name: port.name,
      portId: port.id,
    };

    this._sendMessage(messageManager, "Extension:Connect", msg, recipient).catch(error => {
      if (error.result === MessageChannel.RESULT_NO_HANDLER) {
        error = {message: "Could not establish connection. Receiving end does not exist."};
      } else if (error.result === MessageChannel.RESULT_DISCONNECTED) {
        error = null;
      }
      port.disconnectByOtherEnd(new StructuredCloneHolder(error));
    });

    return port.api();
  }

  connect(messageManager, name, recipient) {
    let portId = getUniqueId();

    let port = new Port(this.context, messageManager, this.messageManagers, name, portId, null, recipient);

    return this._connect(messageManager, port, recipient);
  }

  connectNative(messageManager, name, recipient) {
    let portId = getUniqueId();

    let port = new NativePort(this.context, messageManager, this.messageManagers, name, portId, null, recipient);

    return this._connect(messageManager, port, recipient);
  }

  _onConnect(name, filter) {
    return new EventManager(this.context, name, fire => {
      let listener = {
        messageFilterPermissive: this.optionalFilter,
        messageFilterStrict: this.filter,

        filterMessage: (sender, recipient) => {
          // Ignore the port if it was created by this Messenger.
          return (sender.contextId !== this.context.contextId &&
                  filter(sender, recipient));
        },

        receiveMessage: ({target, data: message, sender}) => {
          let {name, portId} = message;
          let mm = getMessageManager(target);
          let recipient = Object.assign({}, sender);
          if (recipient.tab) {
            recipient.tabId = recipient.tab.id;
            delete recipient.tab;
          }
          let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
          fire.asyncWithoutClone(port.api());
          return true;
        },
      };

      MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
      return () => {
        MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
      };
    }).api();
  }

  onConnect(name) {
    return this._onConnect(name, sender => sender.id === this.sender.id);
  }

  onConnectExternal(name) {
    return this._onConnect(name, sender => sender.id !== this.sender.id);
  }
}

// For test use only.
var ExtensionManager = {
  extensions: new Map(),
};

// Represents a browser extension in the content process.
class BrowserExtensionContent extends EventEmitter {
  constructor(data) {
    super();

    this.data = data;
    this.id = data.id;
    this.uuid = data.uuid;
    this.instanceId = data.instanceId;

    this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
    Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);

    defineLazyGetter(this, "scripts", () => {
      return data.content_scripts.map(scriptData => new ExtensionContent.Script(this, scriptData));
    });

    this.webAccessibleResources = data.webAccessibleResources.map(res => new MatchGlob(res));
    this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts, {ignorePath: true});
    this.permissions = data.permissions;
    this.optionalPermissions = data.optionalPermissions;
    this.principal = data.principal;

    this.localeData = new LocaleData(data.localeData);

    this.manifest = data.manifest;
    this.baseURI = Services.io.newURI(data.baseURL);

    // Only used in addon processes.
    this.views = new Set();

    // Only used for devtools views.
    this.devtoolsViews = new Set();

    /* eslint-disable mozilla/balanced-listeners */
    this.on("add-permissions", (ignoreEvent, permissions) => {
      if (permissions.permissions.length > 0) {
        for (let perm of permissions.permissions) {
          this.permissions.add(perm);
        }
      }

      if (permissions.origins.length > 0) {
        let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);

        this.whiteListedHosts = new MatchPatternSet([...patterns, ...permissions.origins],
                                                    {ignorePath: true});
      }

      if (this.policy) {
        this.policy.permissions = Array.from(this.permissions);
        this.policy.allowedOrigins = this.whiteListedHosts;
      }
    });

    this.on("remove-permissions", (ignoreEvent, permissions) => {
      if (permissions.permissions.length > 0) {
        for (let perm of permissions.permissions) {
          this.permissions.delete(perm);
        }
      }

      if (permissions.origins.length > 0) {
        let origins = permissions.origins.map(
          origin => new MatchPattern(origin, {ignorePath: true}).pattern);

        this.whiteListedHosts = new MatchPatternSet(
          this.whiteListedHosts.patterns
              .filter(host => !origins.includes(host.pattern)));
      }

      if (this.policy) {
        this.policy.permissions = Array.from(this.permissions);
        this.policy.allowedOrigins = this.whiteListedHosts;
      }
    });
    /* eslint-enable mozilla/balanced-listeners */

    ExtensionManager.extensions.set(this.id, this);
  }

  shutdown() {
    ExtensionManager.extensions.delete(this.id);
    ExtensionContent.shutdownExtension(this);
    Services.cpmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
    if (isContentProcess) {
      MessageChannel.abortResponses({extensionId: this.id});
    }
  }

  getContext(window) {
    return ExtensionContent.getContext(this, window);
  }

  emit(event, ...args) {
    Services.cpmm.sendAsyncMessage(this.MESSAGE_EMIT_EVENT, {event, args});

    super.emit(event, ...args);
  }

  receiveMessage({name, data}) {
    if (name === this.MESSAGE_EMIT_EVENT) {
      super.emit(data.event, ...data.args);
    }
  }

  localizeMessage(...args) {
    return this.localeData.localizeMessage(...args);
  }

  localize(...args) {
    return this.localeData.localize(...args);
  }

  hasPermission(perm) {
    let match = /^manifest:(.*)/.exec(perm);
    if (match) {
      return this.manifest[match[1]] != null;
    }
    return this.permissions.has(perm);
  }
}

/**
 * An object that runs an remote implementation of an API.
 */
class ProxyAPIImplementation extends SchemaAPIInterface {
  /**
   * @param {string} namespace The full path to the namespace that contains the
   *     `name` member. This may contain dots, e.g. "storage.local".
   * @param {string} name The name of the method or property.
   * @param {ChildAPIManager} childApiManager The owner of this implementation.
   */
  constructor(namespace, name, childApiManager) {
    super();
    this.path = `${namespace}.${name}`;
    this.childApiManager = childApiManager;
  }

  revoke() {
    let map = this.childApiManager.listeners.get(this.path);
    for (let listener of map.keys()) {
      this.removeListener(listener);
    }

    this.path = null;
    this.childApiManager = null;
  }

  callFunctionNoReturn(args) {
    this.childApiManager.callParentFunctionNoReturn(this.path, args);
  }

  callAsyncFunction(args, callback, requireUserInput) {
    if (requireUserInput) {
      let context = this.childApiManager.context;
      let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils);
      if (!winUtils.isHandlingUserInput) {
        let err = new context.cloneScope.Error(`${this.path} may only be called from a user input handler`);
        return context.wrapPromise(Promise.reject(err), callback);
      }
    }
    return this.childApiManager.callParentAsyncFunction(this.path, args, callback);
  }

  addListener(listener, args) {
    let map = this.childApiManager.listeners.get(this.path);

    if (map.listeners.has(listener)) {
      // TODO: Called with different args?
      return;
    }

    let id = getUniqueId();

    map.ids.set(id, listener);
    map.listeners.set(listener, id);

    this.childApiManager.messageManager.sendAsyncMessage("API:AddListener", {
      childId: this.childApiManager.id,
      listenerId: id,
      path: this.path,
      args,
    });
  }

  removeListener(listener) {
    let map = this.childApiManager.listeners.get(this.path);

    if (!map.listeners.has(listener)) {
      return;
    }

    let id = map.listeners.get(listener);
    map.listeners.delete(listener);
    map.ids.delete(id);
    map.removedIds.add(id);

    this.childApiManager.messageManager.sendAsyncMessage("API:RemoveListener", {
      childId: this.childApiManager.id,
      listenerId: id,
      path: this.path,
    });
  }

  hasListener(listener) {
    let map = this.childApiManager.listeners.get(this.path);
    return map.listeners.has(listener);
  }
}

// We create one instance of this class for every extension context that
// needs to use remote APIs. It uses the message manager to communicate
// with the ParentAPIManager singleton in ExtensionParent.jsm. It
// handles asynchronous function calls as well as event listeners.
class ChildAPIManager {
  constructor(context, messageManager, localAPICan, contextData) {
    this.context = context;
    this.messageManager = messageManager;
    this.url = contextData.url;

    // The root namespace of all locally implemented APIs. If an extension calls
    // an API that does not exist in this object, then the implementation is
    // delegated to the ParentAPIManager.
    this.localApis = localAPICan.root;
    this.apiCan = localAPICan;

    this.id = `${context.extension.id}.${context.contextId}`;

    MessageChannel.addListener(messageManager, "API:RunListener", this);
    messageManager.addMessageListener("API:CallResult", this);

    this.messageFilterStrict = {childId: this.id};

    this.listeners = new DefaultMap(() => ({
      ids: new Map(),
      listeners: new Map(),
      removedIds: new LimitedSet(10),
    }));

    // Map[callId -> Deferred]
    this.callPromises = new Map();

    let params = {
      childId: this.id,
      extensionId: context.extension.id,
      principal: context.principal,
    };
    Object.assign(params, contextData);

    this.messageManager.sendAsyncMessage("API:CreateProxyContext", params);

    this.permissionsChangedCallbacks = new Set();
    this.updatePermissions = null;
    if (this.context.extension.optionalPermissions.length > 0) {
      this.updatePermissions = () => {
        for (let callback of this.permissionsChangedCallbacks) {
          try {
            callback();
          } catch (err) {
            Cu.reportError(err);
          }
        }
      };
      this.context.extension.on("add-permissions", this.updatePermissions);
      this.context.extension.on("remove-permissions", this.updatePermissions);
    }
  }

  receiveMessage({name, messageName, data}) {
    if (data.childId != this.id) {
      return;
    }

    switch (name || messageName) {
      case "API:RunListener":
        let map = this.listeners.get(data.path);
        let listener = map.ids.get(data.listenerId);

        if (listener) {
          let args = data.args.deserialize(this.context.cloneScope);
          let fire = () => this.context.runSafeWithoutClone(listener, ...args);
          return (data.handlingUserInput) ?
                 withHandlingUserInput(this.context.contentWindow, fire) : fire();
        }
        if (!map.removedIds.has(data.listenerId)) {
          Services.console.logStringMessage(
            `Unknown listener at childId=${data.childId} path=${data.path} listenerId=${data.listenerId}\n`);
        }
        break;

      case "API:CallResult":
        let deferred = this.callPromises.get(data.callId);
        if ("error" in data) {
          deferred.reject(data.error);
        } else {
          let result = data.result.deserialize(this.context.cloneScope);

          deferred.resolve(new NoCloneSpreadArgs(result));
        }
        this.callPromises.delete(data.callId);
        break;
    }
  }

  /**
   * Call a function in the parent process and ignores its return value.
   *
   * @param {string} path The full name of the method, e.g. "tabs.create".
   * @param {Array} args The parameters for the function.
   */
  callParentFunctionNoReturn(path, args) {
    this.messageManager.sendAsyncMessage("API:Call", {
      childId: this.id,
      path,
      args,
    });
  }

  /**
   * Calls a function in the parent process and returns its result
   * asynchronously.
   *
   * @param {string} path The full name of the method, e.g. "tabs.create".
   * @param {Array} args The parameters for the function.
   * @param {function(*)} [callback] The callback to be called when the function
   *     completes.
   * @param {object} [options] Extra options.
   * @param {boolean} [options.noClone = false] If true, do not clone
   *     the arguments into an extension sandbox before calling the API
   *     method.
   * @returns {Promise|undefined} Must be void if `callback` is set, and a
   *     promise otherwise. The promise is resolved when the function completes.
   */
  callParentAsyncFunction(path, args, callback, options = {}) {
    let callId = getUniqueId();
    let deferred = PromiseUtils.defer();
    this.callPromises.set(callId, deferred);

    this.messageManager.sendAsyncMessage("API:Call", {
      childId: this.id,
      callId,
      path,
      args,
      noClone: options.noClone || false,
    });

    return this.context.wrapPromise(deferred.promise, callback);
  }

  /**
   * Create a proxy for an event in the parent process. The returned event
   * object shares its internal state with other instances. For instance, if
   * `removeListener` is used on a listener that was added on another object
   * through `addListener`, then the event is unregistered.
   *
   * @param {string} path The full name of the event, e.g. "tabs.onCreated".
   * @returns {object} An object with the addListener, removeListener and
   *   hasListener methods. See SchemaAPIInterface for documentation.
   */
  getParentEvent(path) {
    path = path.split(".");

    let name = path.pop();
    let namespace = path.join(".");

    let impl = new ProxyAPIImplementation(namespace, name, this);
    return {
      addListener: (listener, ...args) => impl.addListener(listener, args),
      removeListener: (listener) => impl.removeListener(listener),
      hasListener: (listener) => impl.hasListener(listener),
    };
  }

  close() {
    this.messageManager.sendAsyncMessage("API:CloseProxyContext", {childId: this.id});
    if (this.updatePermissions) {
      this.context.extension.off("add-permissions", this.updatePermissions);
      this.context.extension.off("remove-permissions", this.updatePermissions);
    }
  }

  get cloneScope() {
    return this.context.cloneScope;
  }

  get principal() {
    return this.context.principal;
  }

  shouldInject(namespace, name, allowedContexts) {
    // Do not generate content script APIs, unless explicitly allowed.
    if (this.context.envType === "content_child" &&
        !allowedContexts.includes("content")) {
      return false;
    }
    if (allowedContexts.includes("addon_parent_only")) {
      return false;
    }

    // Do not generate devtools APIs, unless explicitly allowed.
    if (this.context.envType === "devtools_child" &&
        !allowedContexts.includes("devtools")) {
      return false;
    }

    // Do not generate devtools APIs, unless explicitly allowed.
    if (this.context.envType !== "devtools_child" &&
        allowedContexts.includes("devtools_only")) {
      return false;
    }

    return true;
  }

  getImplementation(namespace, name) {
    this.apiCan.findAPIPath(`${namespace}.${name}`);
    let obj = this.apiCan.findAPIPath(namespace);

    if (obj && name in obj) {
      return new LocalAPIImplementation(obj, name, this.context);
    }

    return this.getFallbackImplementation(namespace, name);
  }

  getFallbackImplementation(namespace, name) {
    // No local API found, defer implementation to the parent.
    return new ProxyAPIImplementation(namespace, name, this);
  }

  hasPermission(permission) {
    return this.context.extension.hasPermission(permission);
  }

  isPermissionRevokable(permission) {
    return this.context.extension.optionalPermissions.includes(permission);
  }

  setPermissionsChangedCallback(callback) {
    this.permissionsChangedCallbacks.add(callback);
  }
}

var ExtensionChild = {
  BrowserExtensionContent,
  ChildAPIManager,
  Messenger,
  Port,
};
PK
!<((t
t
'modules/ExtensionChildDevToolsUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @fileOverview
 * This module contains utilities for interacting with DevTools
 * from the child process.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["ExtensionChildDevToolsUtils"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://gre/modules/EventEmitter.jsm");

// Create a variable to hold the cached ThemeChangeObserver which does not
// get created until a devtools context has been created.
let themeChangeObserver;

/**
 * An observer that watches for changes to the devtools theme and provides
 * that information to the devtools.panels.themeName API property, as well as
 * emits events for the devtools.panels.onThemeChanged event. It also caches
 * the current value of devtools.themeName.
 */
class ThemeChangeObserver extends EventEmitter {
  constructor(themeName, onDestroyed) {
    super();
    this.themeName = themeName;
    this.onDestroyed = onDestroyed;
    this.contexts = new Set();

    Services.cpmm.addMessageListener("Extension:DevToolsThemeChanged", this);
  }

  addContext(context) {
    if (this.contexts.has(context)) {
      throw new Error(
        "addContext on the ThemeChangeObserver was called more than once" +
        " for the context.");
    }

    context.callOnClose({
      close: () => this.onContextClosed(context),
    });

    this.contexts.add(context);
  }

  onContextClosed(context) {
    this.contexts.delete(context);

    if (this.contexts.size === 0) {
      this.destroy();
    }
  }

  onThemeChanged(themeName) {
    // Update the cached themeName and emit an event for the API.
    this.themeName = themeName;
    this.emit("themeChanged", themeName);
  }

  receiveMessage({name, data}) {
    if (name === "Extension:DevToolsThemeChanged") {
      this.onThemeChanged(data.themeName);
    }
  }

  destroy() {
    Services.cpmm.removeMessageListener("Extension:DevToolsThemeChanged", this);
    this.onDestroyed();
    this.onDestroyed = null;
    this.contexts.clear();
    this.contexts = null;
  }
}

this.ExtensionChildDevToolsUtils = {
  /**
   * Creates an cached instance of the ThemeChangeObserver class and
   * initializes it with the current themeName. This cached instance is
   * destroyed when all of the contexts added to it are closed.
   *
   * @param {string} themeName The name of the current devtools theme.
   * @param {DevToolsContextChild} context The newly created devtools page context.
   */
  initThemeChangeObserver(themeName, context) {
    if (!themeChangeObserver) {
      themeChangeObserver = new ThemeChangeObserver(
        themeName,
        function() { themeChangeObserver = null; }
      );
    }
    themeChangeObserver.addContext(context);
  },

  /**
   * Returns the cached instance of ThemeChangeObserver.
   *
   * @returns {ThemeChangeObserver} The cached instance of ThemeChangeObserver.
   */
  getThemeChangeObserver() {
    if (!themeChangeObserver) {
      throw new Error("A ThemeChangeObserver must be created before being retrieved.");
    }
    return themeChangeObserver;
  },
};
PK
!<iNVVmodules/ExtensionCommon.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/**
 * This module contains utilities and base classes for logic which is
 * common between the parent and child process, and in particular
 * between ExtensionParent.jsm and ExtensionChild.jsm.
 */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

/* exported ExtensionCommon */

this.EXPORTED_SYMBOLS = ["ExtensionCommon"];

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                   "@mozilla.org/content/style-sheet-service;1",
                                   "nsIStyleSheetService");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");

var {
  DefaultMap,
  DefaultWeakMap,
  EventEmitter,
  ExtensionError,
  defineLazyGetter,
  getConsole,
  getInnerWindowID,
  getUniqueId,
  runSafeSync,
  runSafeSyncWithoutClone,
  instanceOf,
} = ExtensionUtils;

XPCOMUtils.defineLazyGetter(this, "console", getConsole);

var ExtensionCommon;

/**
 * A sentinel class to indicate that an array of values should be
 * treated as an array when used as a promise resolution value, but as a
 * spread expression (...args) when passed to a callback.
 */
class SpreadArgs extends Array {
  constructor(args) {
    super();
    this.push(...args);
  }
}

/**
 * Like SpreadArgs, but also indicates that the array values already
 * belong to the target compartment, and should not be cloned before
 * being passed.
 *
 * The `unwrappedValues` property contains an Array object which belongs
 * to the target compartment, and contains the same unwrapped values
 * passed the NoCloneSpreadArgs constructor.
 */
class NoCloneSpreadArgs {
  constructor(args) {
    this.unwrappedValues = args;
  }

  [Symbol.iterator]() {
    return this.unwrappedValues[Symbol.iterator]();
  }
}

class BaseContext {
  constructor(envType, extension) {
    this.envType = envType;
    this.onClose = new Set();
    this.checkedLastError = false;
    this._lastError = null;
    this.contextId = getUniqueId();
    this.unloaded = false;
    this.extension = extension;
    this.jsonSandbox = null;
    this.active = true;
    this.incognito = null;
    this.messageManager = null;
    this.docShell = null;
    this.contentWindow = null;
    this.innerWindowID = 0;
  }

  setContentWindow(contentWindow) {
    let {document} = contentWindow;
    let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDocShell);

    this.innerWindowID = getInnerWindowID(contentWindow);
    this.messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIContentFrameMessageManager);

    if (this.incognito == null) {
      this.incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
    }

    MessageChannel.setupMessageManagers([this.messageManager]);

    let onPageShow = event => {
      if (!event || event.target === document) {
        this.docShell = docShell;
        this.contentWindow = contentWindow;
        this.active = true;
      }
    };
    let onPageHide = event => {
      if (!event || event.target === document) {
        // Put this off until the next tick.
        Promise.resolve().then(() => {
          this.docShell = null;
          this.contentWindow = null;
          this.active = false;
        });
      }
    };

    onPageShow();
    contentWindow.addEventListener("pagehide", onPageHide, true);
    contentWindow.addEventListener("pageshow", onPageShow, true);
    this.callOnClose({
      close: () => {
        onPageHide();
        if (this.active) {
          contentWindow.removeEventListener("pagehide", onPageHide, true);
          contentWindow.removeEventListener("pageshow", onPageShow, true);
        }
      },
    });
  }

  get cloneScope() {
    throw new Error("Not implemented");
  }

  get principal() {
    throw new Error("Not implemented");
  }

  runSafe(...args) {
    if (this.unloaded) {
      Cu.reportError("context.runSafe called after context unloaded");
    } else if (!this.active) {
      Cu.reportError("context.runSafe called while context is inactive");
    } else {
      return runSafeSync(this, ...args);
    }
  }

  runSafeWithoutClone(...args) {
    if (this.unloaded) {
      Cu.reportError("context.runSafeWithoutClone called after context unloaded");
    } else if (!this.active) {
      Cu.reportError("context.runSafeWithoutClone called while context is inactive");
    } else {
      return runSafeSyncWithoutClone(...args);
    }
  }

  checkLoadURL(url, options = {}) {
    let ssm = Services.scriptSecurityManager;

    let flags = ssm.STANDARD;
    if (!options.allowScript) {
      flags |= ssm.DISALLOW_SCRIPT;
    }
    if (!options.allowInheritsPrincipal) {
      flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
    }
    if (options.dontReportErrors) {
      flags |= ssm.DONT_REPORT_ERRORS;
    }

    try {
      ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags);
    } catch (e) {
      return false;
    }
    return true;
  }

  /**
   * Safely call JSON.stringify() on an object that comes from an
   * extension.
   *
   * @param {array<any>} args Arguments for JSON.stringify()
   * @returns {string} The stringified representation of obj
   */
  jsonStringify(...args) {
    if (!this.jsonSandbox) {
      this.jsonSandbox = Cu.Sandbox(this.principal, {
        sameZoneAs: this.cloneScope,
        wantXrays: false,
      });
    }

    return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args);
  }

  callOnClose(obj) {
    this.onClose.add(obj);
  }

  forgetOnClose(obj) {
    this.onClose.delete(obj);
  }

  /**
   * A wrapper around MessageChannel.sendMessage which adds the extension ID
   * to the recipient object, and ensures replies are not processed after the
   * context has been unloaded.
   *
   * @param {nsIMessageManager} target
   * @param {string} messageName
   * @param {object} data
   * @param {object} [options]
   * @param {object} [options.sender]
   * @param {object} [options.recipient]
   *
   * @returns {Promise}
   */
  sendMessage(target, messageName, data, options = {}) {
    options.recipient = Object.assign({extensionId: this.extension.id}, options.recipient);
    options.sender = options.sender || {};

    options.sender.extensionId = this.extension.id;
    options.sender.contextId = this.contextId;

    return MessageChannel.sendMessage(target, messageName, data, options);
  }

  get lastError() {
    this.checkedLastError = true;
    return this._lastError;
  }

  set lastError(val) {
    this.checkedLastError = false;
    this._lastError = val;
  }

  /**
   * Normalizes the given error object for use by the target scope. If
   * the target is an error object which belongs to that scope, it is
   * returned as-is. If it is an ordinary object with a `message`
   * property, it is converted into an error belonging to the target
   * scope. If it is an Error object which does *not* belong to the
   * clone scope, it is reported, and converted to an unexpected
   * exception error.
   *
   * @param {Error|object} error
   * @returns {Error}
   */
  normalizeError(error) {
    if (error instanceof this.cloneScope.Error) {
      return error;
    }
    let message, fileName;
    if (instanceOf(error, "Object") || error instanceof ExtensionError ||
        (typeof error == "object" && this.principal.subsumes(Cu.getObjectPrincipal(error)))) {
      message = error.message;
      fileName = error.fileName;
    } else {
      Cu.reportError(error);
    }
    message = message || "An unexpected error occurred";
    return new this.cloneScope.Error(message, fileName);
  }

  /**
   * Sets the value of `.lastError` to `error`, calls the given
   * callback, and reports an error if the value has not been checked
   * when the callback returns.
   *
   * @param {object} error An object with a `message` property. May
   *     optionally be an `Error` object belonging to the target scope.
   * @param {function} callback The callback to call.
   * @returns {*} The return value of callback.
   */
  withLastError(error, callback) {
    this.lastError = this.normalizeError(error);
    try {
      return callback();
    } finally {
      if (!this.checkedLastError) {
        Cu.reportError(`Unchecked lastError value: ${this.lastError}`);
      }
      this.lastError = null;
    }
  }

  /**
   * Wraps the given promise so it can be safely returned to extension
   * code in this context.
   *
   * If `callback` is provided, however, it is used as a completion
   * function for the promise, and no promise is returned. In this case,
   * the callback is called when the promise resolves or rejects. In the
   * latter case, `lastError` is set to the rejection value, and the
   * callback function must check `browser.runtime.lastError` or
   * `extension.runtime.lastError` in order to prevent it being reported
   * to the console.
   *
   * @param {Promise} promise The promise with which to wrap the
   *     callback. May resolve to a `SpreadArgs` instance, in which case
   *     each element will be used as a separate argument.
   *
   *     Unless the promise object belongs to the cloneScope global, its
   *     resolution value is cloned into cloneScope prior to calling the
   *     `callback` function or resolving the wrapped promise.
   *
   * @param {function} [callback] The callback function to wrap
   *
   * @returns {Promise|undefined} If callback is null, a promise object
   *     belonging to the target scope. Otherwise, undefined.
   */
  wrapPromise(promise, callback = null) {
    let runSafe = this.runSafe.bind(this);
    if (promise instanceof this.cloneScope.Promise) {
      runSafe = this.runSafeWithoutClone.bind(this);
    }

    if (callback) {
      promise.then(
        args => {
          if (this.unloaded) {
            dump(`Promise resolved after context unloaded\n`);
          } else if (!this.active) {
            dump(`Promise resolved while context is inactive\n`);
          } else if (args instanceof NoCloneSpreadArgs) {
            this.runSafeWithoutClone(callback, ...args.unwrappedValues);
          } else if (args instanceof SpreadArgs) {
            runSafe(callback, ...args);
          } else {
            runSafe(callback, args);
          }
        },
        error => {
          this.withLastError(error, () => {
            if (this.unloaded) {
              dump(`Promise rejected after context unloaded\n`);
            } else if (!this.active) {
              dump(`Promise rejected while context is inactive\n`);
            } else {
              this.runSafeWithoutClone(callback);
            }
          });
        });
    } else {
      return new this.cloneScope.Promise((resolve, reject) => {
        promise.then(
          value => {
            if (this.unloaded) {
              dump(`Promise resolved after context unloaded\n`);
            } else if (!this.active) {
              dump(`Promise resolved while context is inactive\n`);
            } else if (value instanceof NoCloneSpreadArgs) {
              let values = value.unwrappedValues;
              this.runSafeWithoutClone(resolve, values.length == 1 ? values[0] : values);
            } else if (value instanceof SpreadArgs) {
              runSafe(resolve, value.length == 1 ? value[0] : value);
            } else {
              runSafe(resolve, value);
            }
          },
          value => {
            if (this.unloaded) {
              dump(`Promise rejected after context unloaded: ${value && value.message}\n`);
            } else if (!this.active) {
              dump(`Promise rejected while context is inactive: ${value && value.message}\n`);
            } else {
              this.runSafeWithoutClone(reject, this.normalizeError(value));
            }
          });
      });
    }
  }

  unload() {
    this.unloaded = true;

    MessageChannel.abortResponses({
      extensionId: this.extension.id,
      contextId: this.contextId,
    });

    for (let obj of this.onClose) {
      obj.close();
    }
  }

  /**
   * A simple proxy for unload(), for use with callOnClose().
   */
  close() {
    this.unload();
  }
}

/**
 * An object that runs the implementation of a schema API. Instantiations of
 * this interfaces are used by Schemas.jsm.
 *
 * @interface
 */
class SchemaAPIInterface {
  /**
   * Calls this as a function that returns its return value.
   *
   * @abstract
   * @param {Array} args The parameters for the function.
   * @returns {*} The return value of the invoked function.
   */
  callFunction(args) {
    throw new Error("Not implemented");
  }

  /**
   * Calls this as a function and ignores its return value.
   *
   * @abstract
   * @param {Array} args The parameters for the function.
   */
  callFunctionNoReturn(args) {
    throw new Error("Not implemented");
  }

  /**
   * Calls this as a function that completes asynchronously.
   *
   * @abstract
   * @param {Array} args The parameters for the function.
   * @param {function(*)} [callback] The callback to be called when the function
   *     completes.
   * @param {boolean} [requireUserInput=false] If true, the function should
   *                  fail if the browser is not currently handling user input.
   * @returns {Promise|undefined} Must be void if `callback` is set, and a
   *     promise otherwise. The promise is resolved when the function completes.
   */
  callAsyncFunction(args, callback, requireUserInput = false) {
    throw new Error("Not implemented");
  }

  /**
   * Retrieves the value of this as a property.
   *
   * @abstract
   * @returns {*} The value of the property.
   */
  getProperty() {
    throw new Error("Not implemented");
  }

  /**
   * Assigns the value to this as property.
   *
   * @abstract
   * @param {string} value The new value of the property.
   */
  setProperty(value) {
    throw new Error("Not implemented");
  }

  /**
   * Registers a `listener` to this as an event.
   *
   * @abstract
   * @param {function} listener The callback to be called when the event fires.
   * @param {Array} args Extra parameters for EventManager.addListener.
   * @see EventManager.addListener
   */
  addListener(listener, args) {
    throw new Error("Not implemented");
  }

  /**
   * Checks whether `listener` is listening to this as an event.
   *
   * @abstract
   * @param {function} listener The event listener.
   * @returns {boolean} Whether `listener` is registered with this as an event.
   * @see EventManager.hasListener
   */
  hasListener(listener) {
    throw new Error("Not implemented");
  }

  /**
   * Unregisters `listener` from this as an event.
   *
   * @abstract
   * @param {function} listener The event listener.
   * @see EventManager.removeListener
   */
  removeListener(listener) {
    throw new Error("Not implemented");
  }

  /**
   * Revokes the implementation object, and prevents any further method
   * calls from having external effects.
   *
   * @abstract
   */
  revoke() {
    throw new Error("Not implemented");
  }
}

/**
 * An object that runs a locally implemented API.
 */
class LocalAPIImplementation extends SchemaAPIInterface {
  /**
   * Constructs an implementation of the `name` method or property of `pathObj`.
   *
   * @param {object} pathObj The object containing the member with name `name`.
   * @param {string} name The name of the implemented member.
   * @param {BaseContext} context The context in which the schema is injected.
   */
  constructor(pathObj, name, context) {
    super();
    this.pathObj = pathObj;
    this.name = name;
    this.context = context;
  }

  revoke() {
    if (this.pathObj[this.name][Schemas.REVOKE]) {
      this.pathObj[this.name][Schemas.REVOKE]();
    }

    this.pathObj = null;
    this.name = null;
    this.context = null;
  }

  callFunction(args) {
    return this.pathObj[this.name](...args);
  }

  callFunctionNoReturn(args) {
    this.pathObj[this.name](...args);
  }

  callAsyncFunction(args, callback, requireUserInput) {
    let promise;
    try {
      if (requireUserInput) {
        let winUtils = this.context.contentWindow
                           .getInterface(Ci.nsIDOMWindowUtils);
        if (!winUtils.isHandlingUserInput) {
          throw new ExtensionError(`${this.name} may only be called from a user input handler`);
        }
      }
      promise = this.pathObj[this.name](...args) || Promise.resolve();
    } catch (e) {
      promise = Promise.reject(e);
    }
    return this.context.wrapPromise(promise, callback);
  }

  getProperty() {
    return this.pathObj[this.name];
  }

  setProperty(value) {
    this.pathObj[this.name] = value;
  }

  addListener(listener, args) {
    try {
      this.pathObj[this.name].addListener.call(null, listener, ...args);
    } catch (e) {
      throw this.context.normalizeError(e);
    }
  }

  hasListener(listener) {
    return this.pathObj[this.name].hasListener.call(null, listener);
  }

  removeListener(listener) {
    this.pathObj[this.name].removeListener.call(null, listener);
  }
}

// Recursively copy properties from source to dest.
function deepCopy(dest, source) {
  for (let prop in source) {
    let desc = Object.getOwnPropertyDescriptor(source, prop);
    if (typeof(desc.value) == "object") {
      if (!(prop in dest)) {
        dest[prop] = {};
      }
      deepCopy(dest[prop], source[prop]);
    } else {
      Object.defineProperty(dest, prop, desc);
    }
  }
}

/**
 * Manages loading and accessing a set of APIs for a specific extension
 * context.
 *
 * @param {BaseContext} context
 *        The context to manage APIs for.
 * @param {SchemaAPIManager} apiManager
 *        The API manager holding the APIs to manage.
 * @param {object} root
 *        The root object into which APIs will be injected.
 */
class CanOfAPIs {
  constructor(context, apiManager, root) {
    this.context = context;
    this.scopeName = context.envType;
    this.apiManager = apiManager;
    this.root = root;

    this.apiPaths = new Map();

    this.apis = new Map();
  }

  /**
   * Synchronously loads and initializes an ExtensionAPI instance.
   *
   * @param {string} name
   *        The name of the API to load.
   */
  loadAPI(name) {
    if (this.apis.has(name)) {
      return;
    }

    let {extension} = this.context;

    let api = this.apiManager.getAPI(name, extension, this.scopeName);
    if (!api) {
      return;
    }

    this.apis.set(name, api);

    deepCopy(this.root, api.getAPI(this.context));
  }

  /**
   * Asynchronously loads and initializes an ExtensionAPI instance.
   *
   * @param {string} name
   *        The name of the API to load.
   */
  async asyncLoadAPI(name) {
    if (this.apis.has(name)) {
      return;
    }

    let {extension} = this.context;
    if (!Schemas.checkPermissions(name, extension)) {
      return;
    }

    let api = await this.apiManager.asyncGetAPI(name, extension, this.scopeName);
    // Check again, because async;
    if (this.apis.has(name)) {
      return;
    }

    this.apis.set(name, api);

    deepCopy(this.root, api.getAPI(this.context));
  }

  /**
   * Finds the API at the given path from the root object, and
   * synchronously loads the API that implements it if it has not
   * already been loaded.
   *
   * @param {string} path
   *        The "."-separated path to find.
   * @returns {*}
   */
  findAPIPath(path) {
    if (this.apiPaths.has(path)) {
      return this.apiPaths.get(path);
    }

    let obj = this.root;
    let modules = this.apiManager.modulePaths;

    for (let key of path.split(".")) {
      if (!obj) {
        return;
      }
      modules = modules.get(key);

      for (let name of modules.modules) {
        if (!this.apis.has(name)) {
          this.loadAPI(name);
        }
      }

      obj = obj[key];
    }

    this.apiPaths.set(path, obj);
    return obj;
  }

  /**
   * Finds the API at the given path from the root object, and
   * asynchronously loads the API that implements it if it has not
   * already been loaded.
   *
   * @param {string} path
   *        The "."-separated path to find.
   * @returns {Promise<*>}
   */
  async asyncFindAPIPath(path) {
    if (this.apiPaths.has(path)) {
      return this.apiPaths.get(path);
    }

    let obj = this.root;
    let modules = this.apiManager.modulePaths;

    for (let key of path.split(".")) {
      if (!obj) {
        return;
      }
      modules = modules.get(key);

      for (let name of modules.modules) {
        if (!this.apis.has(name)) {
          await this.asyncLoadAPI(name);
        }
      }

      if (typeof obj[key] === "function") {
        obj = obj[key].bind(obj);
      } else {
        obj = obj[key];
      }
    }

    this.apiPaths.set(path, obj);
    return obj;
  }
}

class DeepMap extends DefaultMap {
  constructor() {
    super(() => new DeepMap());

    this.modules = new Set();
  }

  getPath(path) {
    return path.reduce((map, key) => map.get(key), this);
  }
}

/**
 * @class APIModule
 * @abstract
 *
 * @property {string} url
 *       The URL of the script which contains the module's
 *       implementation. This script must define a global property
 *       matching the modules name, which must be a class constructor
 *       which inherits from {@link ExtensionAPI}.
 *
 * @property {string} schema
 *       The URL of the JSON schema which describes the module's API.
 *
 * @property {Array<string>} scopes
 *       The list of scope names into which the API may be loaded.
 *
 * @property {Array<string>} manifest
 *       The list of top-level manifest properties which will trigger
 *       the module to be loaded, and its `onManifestEntry` method to be
 *       called.
 *
 * @property {Array<string>} events
 *       The list events which will trigger the module to be loaded, and
 *       its appropriate event handler method to be called. Currently
 *       only accepts "startup".
 *
 * @property {Array<Array<string>>} paths
 *       A list of paths from the root API object which, when accessed,
 *       will cause the API module to be instantiated and injected.
 */

/**
 * This object loads the ext-*.js scripts that define the extension API.
 *
 * This class instance is shared with the scripts that it loads, so that the
 * ext-*.js scripts and the instantiator can communicate with each other.
 */
class SchemaAPIManager extends EventEmitter {
  /**
   * @param {string} processType
   *     "main" - The main, one and only chrome browser process.
   *     "addon" - An addon process.
   *     "content" - A content process.
   *     "devtools" - A devtools process.
   *     "proxy" - A proxy script process.
   */
  constructor(processType) {
    super();
    this.processType = processType;
    this.global = this._createExtGlobal();

    this.modules = new Map();
    this.modulePaths = new DeepMap();
    this.manifestKeys = new Map();
    this.eventModules = new DefaultMap(() => new Set());

    this.schemaURLs = new Set();

    this.apis = new DefaultWeakMap(() => new Map());

    this._scriptScopes = [];
  }

  /**
   * Registers a set of ExtensionAPI modules to be lazily loaded and
   * managed by this manager.
   *
   * @param {object} obj
   *        An object containing property for eacy API module to be
   *        registered. Each value should be an object implementing the
   *        APIModule interface.
   */
  registerModules(obj) {
    for (let [name, details] of Object.entries(obj)) {
      details.namespaceName = name;

      if (this.modules.has(name)) {
        throw new Error(`Module '${name}' already registered`);
      }
      this.modules.set(name, details);

      if (details.schema) {
        this.schemaURLs.add(details.schema);
      }

      for (let event of details.events || []) {
        this.eventModules.get(event).add(name);
      }

      for (let key of details.manifest || []) {
        if (this.manifestKeys.has(key)) {
          throw new Error(`Manifest key '${key}' already registered by '${this.manifestKeys.get(key)}'`);
        }

        this.manifestKeys.set(key, name);
      }

      for (let path of details.paths || []) {
        this.modulePaths.getPath(path).modules.add(name);
      }
    }
  }

  /**
   * Emits an `onManifestEntry` event for the top-level manifest entry
   * on all relevant {@link ExtensionAPI} instances for the given
   * extension.
   *
   * The API modules will be synchronously loaded if they have not been
   * loaded already.
   *
   * @param {Extension} extension
   *        The extension for which to emit the events.
   * @param {string} entry
   *        The name of the top-level manifest entry.
   *
   * @returns {*}
   */
  emitManifestEntry(extension, entry) {
    let apiName = this.manifestKeys.get(entry);
    if (apiName) {
      let api = this.getAPI(apiName, extension);
      return api.onManifestEntry(entry);
    }
  }
  /**
   * Emits an `onManifestEntry` event for the top-level manifest entry
   * on all relevant {@link ExtensionAPI} instances for the given
   * extension.
   *
   * The API modules will be asynchronously loaded if they have not been
   * loaded already.
   *
   * @param {Extension} extension
   *        The extension for which to emit the events.
   * @param {string} entry
   *        The name of the top-level manifest entry.
   *
   * @returns {Promise<*>}
   */
  async asyncEmitManifestEntry(extension, entry) {
    let apiName = this.manifestKeys.get(entry);
    if (apiName) {
      let api = await this.asyncGetAPI(apiName, extension);
      return api.onManifestEntry(entry);
    }
  }

  /**
   * Returns the {@link ExtensionAPI} instance for the given API module,
   * for the given extension, in the given scope, synchronously loading
   * and instantiating it if necessary.
   *
   * @param {string} name
   *        The name of the API module to load.
   * @param {Extension} extension
   *        The extension for which to load the API.
   * @param {string} [scope = null]
   *        The scope type for which to retrieve the API, or null if not
   *        being retrieved for a particular scope.
   *
   * @returns {ExtensionAPI?}
   */
  getAPI(name, extension, scope = null) {
    if (!this._checkGetAPI(name, extension, scope)) {
      return;
    }

    let apis = this.apis.get(extension);
    if (apis.has(name)) {
      return apis.get(name);
    }

    let module = this.loadModule(name);

    let api = new module(extension);
    apis.set(name, api);
    return api;
  }
  /**
   * Returns the {@link ExtensionAPI} instance for the given API module,
   * for the given extension, in the given scope, asynchronously loading
   * and instantiating it if necessary.
   *
   * @param {string} name
   *        The name of the API module to load.
   * @param {Extension} extension
   *        The extension for which to load the API.
   * @param {string} [scope = null]
   *        The scope type for which to retrieve the API, or null if not
   *        being retrieved for a particular scope.
   *
   * @returns {Promise<ExtensionAPI>?}
   */
  async asyncGetAPI(name, extension, scope = null) {
    if (!this._checkGetAPI(name, extension, scope)) {
      return;
    }

    let apis = this.apis.get(extension);
    if (apis.has(name)) {
      return apis.get(name);
    }

    let module = await this.asyncLoadModule(name);

    // Check again, because async.
    if (apis.has(name)) {
      return apis.get(name);
    }

    let api = new module(extension);
    apis.set(name, api);
    return api;
  }

  /**
   * Synchronously loads an API module, if not already loaded, and
   * returns its ExtensionAPI constructor.
   *
   * @param {string} name
   *        The name of the module to load.
   *
   * @returns {class}
   */
  loadModule(name) {
    let module = this.modules.get(name);
    if (module.loaded) {
      return this.global[name];
    }

    this._checkLoadModule(module, name);

    Services.scriptloader.loadSubScript(module.url, this.global, "UTF-8");

    module.loaded = true;

    return this.global[name];
  }
  /**
   * aSynchronously loads an API module, if not already loaded, and
   * returns its ExtensionAPI constructor.
   *
   * @param {string} name
   *        The name of the module to load.
   *
   * @returns {Promise<class>}
   */
  asyncLoadModule(name) {
    let module = this.modules.get(name);
    if (module.loaded) {
      return Promise.resolve(this.global[name]);
    }
    if (module.asyncLoaded) {
      return module.asyncLoaded;
    }

    this._checkLoadModule(module, name);

    module.asyncLoaded = ChromeUtils.compileScript(module.url).then(script => {
      script.executeInGlobal(this.global);

      module.loaded = true;

      return this.global[name];
    });

    return module.asyncLoaded;
  }

  /**
   * Checks whether the given API module may be loaded for the given
   * extension, in the given scope.
   *
   * @param {string} name
   *        The name of the API module to check.
   * @param {Extension} extension
   *        The extension for which to check the API.
   * @param {string} [scope = null]
   *        The scope type for which to check the API, or null if not
   *        being checked for a particular scope.
   *
   * @returns {boolean}
   *        Whether the module may be loaded.
   */
  _checkGetAPI(name, extension, scope = null) {
    let module = this.modules.get(name);

    if (!scope) {
      return true;
    }

    if (!module.scopes.includes(scope)) {
      return false;
    }

    if (!Schemas.checkPermissions(module.namespaceName, extension)) {
      return false;
    }

    return true;
  }

  _checkLoadModule(module, name) {
    if (!module) {
      throw new Error(`Module '${name}' does not exist`);
    }
    if (module.asyncLoaded) {
      throw new Error(`Module '${name}' currently being lazily loaded`);
    }
    if (this.global[name]) {
      throw new Error(`Module '${name}' conflicts with existing global property`);
    }
  }


  /**
   * Create a global object that is used as the shared global for all ext-*.js
   * scripts that are loaded via `loadScript`.
   *
   * @returns {object} A sandbox that is used as the global by `loadScript`.
   */
  _createExtGlobal() {
    let global = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
      wantXrays: false,
      sandboxName: `Namespace of ext-*.js scripts for ${this.processType}`,
    });

    Object.assign(global, {global, Cc, Ci, Cu, Cr, XPCOMUtils, ChromeWorker, ExtensionCommon, MatchPattern, MatchPatternSet, StructuredCloneHolder, extensions: this});

    Cu.import("resource://gre/modules/AppConstants.jsm", global);
    Cu.import("resource://gre/modules/ExtensionAPI.jsm", global);

    XPCOMUtils.defineLazyGetter(global, "console", getConsole);

    XPCOMUtils.defineLazyModuleGetter(global, "ExtensionUtils",
                                      "resource://gre/modules/ExtensionUtils.jsm");
    XPCOMUtils.defineLazyModuleGetter(global, "XPCOMUtils",
                                      "resource://gre/modules/XPCOMUtils.jsm");
    XPCOMUtils.defineLazyModuleGetter(global, "require",
                                      "resource://devtools/shared/Loader.jsm");

    return global;
  }

  /**
   * Load an ext-*.js script. The script runs in its own scope, if it wishes to
   * share state with another script it can assign to the `global` variable. If
   * it wishes to communicate with this API manager, use `extensions`.
   *
   * @param {string} scriptUrl The URL of the ext-*.js script.
   */
  loadScript(scriptUrl) {
    // Create the object in the context of the sandbox so that the script runs
    // in the sandbox's context instead of here.
    let scope = Cu.createObjectIn(this.global);

    Services.scriptloader.loadSubScript(scriptUrl, scope, "UTF-8");

    // Save the scope to avoid it being garbage collected.
    this._scriptScopes.push(scope);
  }

  /**
   * Mash together all the APIs from `apis` into `obj`.
   *
   * @param {BaseContext} context The context for which the API bindings are
   *     generated.
   * @param {Array} apis A list of objects, see `registerSchemaAPI`.
   * @param {object} obj The destination of the API.
   */
  static generateAPIs(context, apis, obj) {
    function hasPermission(perm) {
      return context.extension.hasPermission(perm, true);
    }
    for (let api of apis) {
      if (Schemas.checkPermissions(api.namespace, {hasPermission})) {
        api = api.getAPI(context);
        deepCopy(obj, api);
      }
    }
  }
}

function LocaleData(data) {
  this.defaultLocale = data.defaultLocale;
  this.selectedLocale = data.selectedLocale;
  this.locales = data.locales || new Map();
  this.warnedMissingKeys = new Set();

  // Map(locale-name -> Map(message-key -> localized-string))
  //
  // Contains a key for each loaded locale, each of which is a
  // Map of message keys to their localized strings.
  this.messages = data.messages || new Map();

  if (data.builtinMessages) {
    this.messages.set(this.BUILTIN, data.builtinMessages);
  }
}

LocaleData.prototype = {
  // Representation of the object to send to content processes. This
  // should include anything the content process might need.
  serialize() {
    return {
      defaultLocale: this.defaultLocale,
      selectedLocale: this.selectedLocale,
      messages: this.messages,
      locales: this.locales,
    };
  },

  BUILTIN: "@@BUILTIN_MESSAGES",

  has(locale) {
    return this.messages.has(locale);
  },

  // https://developer.chrome.com/extensions/i18n
  localizeMessage(message, substitutions = [], options = {}) {
    let defaultOptions = {
      defaultValue: "",
      cloneScope: null,
    };

    let locales = this.availableLocales;
    if (options.locale) {
      locales = new Set([this.BUILTIN, options.locale, this.defaultLocale]
                        .filter(locale => this.messages.has(locale)));
    }

    options = Object.assign(defaultOptions, options);

    // Message names are case-insensitive, so normalize them to lower-case.
    message = message.toLowerCase();
    for (let locale of locales) {
      let messages = this.messages.get(locale);
      if (messages.has(message)) {
        let str = messages.get(message);

        if (!str.includes("$")) {
          return str;
        }

        if (!Array.isArray(substitutions)) {
          substitutions = [substitutions];
        }

        let replacer = (matched, index, dollarSigns) => {
          if (index) {
            // This is not quite Chrome-compatible. Chrome consumes any number
            // of digits following the $, but only accepts 9 substitutions. We
            // accept any number of substitutions.
            index = parseInt(index, 10) - 1;
            return index in substitutions ? substitutions[index] : "";
          }
          // For any series of contiguous `$`s, the first is dropped, and
          // the rest remain in the output string.
          return dollarSigns;
        };
        return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
      }
    }

    // Check for certain pre-defined messages.
    if (message == "@@ui_locale") {
      return this.uiLocale;
    } else if (message.startsWith("@@bidi_")) {
      let rtl = Services.locale.isAppLocaleRTL;

      if (message == "@@bidi_dir") {
        return rtl ? "rtl" : "ltr";
      } else if (message == "@@bidi_reversed_dir") {
        return rtl ? "ltr" : "rtl";
      } else if (message == "@@bidi_start_edge") {
        return rtl ? "right" : "left";
      } else if (message == "@@bidi_end_edge") {
        return rtl ? "left" : "right";
      }
    }

    if (!this.warnedMissingKeys.has(message)) {
      let error = `Unknown localization message ${message}`;
      if (options.cloneScope) {
        error = new options.cloneScope.Error(error);
      }
      Cu.reportError(error);
      this.warnedMissingKeys.add(message);
    }
    return options.defaultValue;
  },

  // Localize a string, replacing all |__MSG_(.*)__| tokens with the
  // matching string from the current locale, as determined by
  // |this.selectedLocale|.
  //
  // This may not be called before calling either |initLocale| or
  // |initAllLocales|.
  localize(str, locale = this.selectedLocale) {
    if (!str) {
      return str;
    }

    return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
      return this.localizeMessage(message, [], {locale, defaultValue: matched});
    });
  },

  // Validates the contents of a locale JSON file, normalizes the
  // messages into a Map of message key -> localized string pairs.
  addLocale(locale, messages, extension) {
    let result = new Map();

    // Chrome does not document the semantics of its localization
    // system very well. It handles replacements by pre-processing
    // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their
    // replacements. Later, it processes the resulting string for
    // |$[0-9]| replacements.
    //
    // Again, it does not document this, but it accepts any number
    // of sequential |$|s, and replaces them with that number minus
    // 1. It also accepts |$| followed by any number of sequential
    // digits, but refuses to process a localized string which
    // provides more than 9 substitutions.
    if (!instanceOf(messages, "Object")) {
      extension.packagingError(`Invalid locale data for ${locale}`);
      return result;
    }

    for (let key of Object.keys(messages)) {
      let msg = messages[key];

      if (!instanceOf(msg, "Object") || typeof(msg.message) != "string") {
        extension.packagingError(`Invalid locale message data for ${locale}, message ${JSON.stringify(key)}`);
        continue;
      }

      // Substitutions are case-insensitive, so normalize all of their names
      // to lower-case.
      let placeholders = new Map();
      if (instanceOf(msg.placeholders, "Object")) {
        for (let key of Object.keys(msg.placeholders)) {
          placeholders.set(key.toLowerCase(), msg.placeholders[key]);
        }
      }

      let replacer = (match, name) => {
        let replacement = placeholders.get(name.toLowerCase());
        if (instanceOf(replacement, "Object") && "content" in replacement) {
          return replacement.content;
        }
        return "";
      };

      let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer);

      // Message names are also case-insensitive, so normalize them to lower-case.
      result.set(key.toLowerCase(), value);
    }

    this.messages.set(locale, result);
    return result;
  },

  get acceptLanguages() {
    let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString);
    return result.split(/\s*,\s*/g);
  },


  get uiLocale() {
    return Services.locale.getAppLocaleAsBCP47();
  },
};

defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
  return new Set([this.BUILTIN, this.selectedLocale, this.defaultLocale]
                 .filter(locale => this.messages.has(locale)));
});

// This is a generic class for managing event listeners. Example usage:
//
// new EventManager(context, "api.subAPI", fire => {
//   let listener = (...) => {
//     // Fire any listeners registered with addListener.
//     fire.async(arg1, arg2);
//   };
//   // Register the listener.
//   SomehowRegisterListener(listener);
//   return () => {
//     // Return a way to unregister the listener.
//     SomehowUnregisterListener(listener);
//   };
// }).api()
//
// The result is an object with addListener, removeListener, and
// hasListener methods. |context| is an add-on scope (either an
// ExtensionContext in the chrome process or ExtensionContext in a
// content process). |name| is for debugging. |register| is a function
// to register the listener. |register| should return an
// unregister function that will unregister the listener.
function EventManager(context, name, register) {
  this.context = context;
  this.name = name;
  this.register = register;
  this.unregister = new Map();
  this.inputHandling = false;
}

EventManager.prototype = {
  addListener(callback, ...args) {
    if (this.unregister.has(callback)) {
      return;
    }

    let shouldFire = () => {
      if (this.context.unloaded) {
        dump(`${this.name} event fired after context unloaded.\n`);
      } else if (!this.context.active) {
        dump(`${this.name} event fired while context is inactive.\n`);
      } else if (this.unregister.has(callback)) {
        return true;
      }
      return false;
    };

    let fire = {
      sync: (...args) => {
        if (shouldFire()) {
          return this.context.runSafe(callback, ...args);
        }
      },
      async: (...args) => {
        return Promise.resolve().then(() => {
          if (shouldFire()) {
            return this.context.runSafe(callback, ...args);
          }
        });
      },
      raw: (...args) => {
        if (!shouldFire()) {
          throw new Error("Called raw() on unloaded/inactive context");
        }
        return callback(...args);
      },
      asyncWithoutClone: (...args) => {
        return Promise.resolve().then(() => {
          if (shouldFire()) {
            return this.context.runSafeWithoutClone(callback, ...args);
          }
        });
      },
    };


    let unregister = this.register(fire, ...args);
    this.unregister.set(callback, unregister);
    this.context.callOnClose(this);
  },

  removeListener(callback) {
    if (!this.unregister.has(callback)) {
      return;
    }

    let unregister = this.unregister.get(callback);
    this.unregister.delete(callback);
    try {
      unregister();
    } catch (e) {
      Cu.reportError(e);
    }
    if (this.unregister.size == 0) {
      this.context.forgetOnClose(this);
    }
  },

  hasListener(callback) {
    return this.unregister.has(callback);
  },

  revoke() {
    for (let callback of this.unregister.keys()) {
      this.removeListener(callback);
    }
  },

  close() {
    this.revoke();
  },

  api() {
    return {
      addListener: (...args) => this.addListener(...args),
      removeListener: (...args) => this.removeListener(...args),
      hasListener: (...args) => this.hasListener(...args),
      setUserInput: this.inputHandling,
      [Schemas.REVOKE]: () => this.revoke(),
    };
  },
};

// Simple API for event listeners where events never fire.
function ignoreEvent(context, name) {
  return {
    addListener: function(callback) {
      let id = context.extension.id;
      let frame = Components.stack.caller;
      let msg = `In add-on ${id}, attempting to use listener "${name}", which is unimplemented.`;
      let scriptError = Cc["@mozilla.org/scripterror;1"]
        .createInstance(Ci.nsIScriptError);
      scriptError.init(msg, frame.filename, null, frame.lineNumber,
                       frame.columnNumber, Ci.nsIScriptError.warningFlag,
                       "content javascript");
      let consoleService = Cc["@mozilla.org/consoleservice;1"]
        .getService(Ci.nsIConsoleService);
      consoleService.logMessage(scriptError);
    },
    removeListener: function(callback) {},
    hasListener: function(callback) {},
  };
}


const stylesheetMap = new DefaultMap(url => {
  let uri = Services.io.newURI(url);
  return styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET);
});


ExtensionCommon = {
  BaseContext,
  CanOfAPIs,
  EventManager,
  LocalAPIImplementation,
  LocaleData,
  NoCloneSpreadArgs,
  SchemaAPIInterface,
  SchemaAPIManager,
  SpreadArgs,
  ignoreEvent,
  stylesheetMap,
};
PK
!<c[[modules/ExtensionContent.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ExtensionContent"];

/* globals ExtensionContent */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
                                  "resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                  "resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
                                  "resource://gre/modules/WebNavigationFrames.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                   "@mozilla.org/content/style-sheet-service;1",
                                   "nsIStyleSheetService");

// xpcshell doesn't handle idle callbacks well.
XPCOMUtils.defineLazyGetter(this, "idleTimeout",
                            () => Services.appinfo.name === "XPCShell" ? 500 : undefined);

const DocumentEncoder = Components.Constructor(
  "@mozilla.org/layout/documentEncoder;1?type=text/plain",
  "nsIDocumentEncoder", "init");

const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback");

Cu.import("resource://gre/modules/ExtensionChild.jsm");
Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

const {
  DefaultMap,
  DefaultWeakMap,
  defineLazyGetter,
  getInnerWindowID,
  getWinUtils,
  promiseDocumentLoaded,
  promiseDocumentReady,
  runSafeSyncWithoutClone,
} = ExtensionUtils;

const {
  BaseContext,
  CanOfAPIs,
  SchemaAPIManager,
} = ExtensionCommon;

const {
  BrowserExtensionContent,
  ChildAPIManager,
  Messenger,
} = ExtensionChild;

XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);


var DocumentManager;

const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
const CONTENT_SCRIPT_INJECTION_HISTOGRAM = "WEBEXT_CONTENT_SCRIPT_INJECTION_MS";

var apiManager = new class extends SchemaAPIManager {
  constructor() {
    super("content");
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_CONTENT)) {
        this.loadScript(value);
      }
    }
  }
}();

const SCRIPT_EXPIRY_TIMEOUT_MS = 5 * 60 * 1000;
const SCRIPT_CLEAR_TIMEOUT_MS = 5 * 1000;

const CSS_EXPIRY_TIMEOUT_MS = 30 * 60 * 1000;

const scriptCaches = new WeakSet();
const sheetCacheDocuments = new DefaultWeakMap(() => new WeakSet());

class CacheMap extends DefaultMap {
  constructor(timeout, getter) {
    super(getter);

    this.expiryTimeout = timeout;

    scriptCaches.add(this);
  }

  get(url) {
    let promise = super.get(url);

    promise.lastUsed = Date.now();
    if (promise.timer) {
      promise.timer.cancel();
    }
    promise.timer = Timer(this.delete.bind(this, url),
                          this.expiryTimeout,
                          Ci.nsITimer.TYPE_ONE_SHOT);

    return promise;
  }

  delete(url) {
    if (this.has(url)) {
      super.get(url).timer.cancel();
    }

    super.delete(url);
  }

  clear(timeout = SCRIPT_CLEAR_TIMEOUT_MS) {
    let now = Date.now();
    for (let [url, promise] of this.entries()) {
      if (now - promise.lastUsed >= timeout) {
        this.delete(url);
      }
    }
  }
}

class ScriptCache extends CacheMap {
  constructor(options) {
    super(SCRIPT_EXPIRY_TIMEOUT_MS,
          url => ChromeUtils.compileScript(url, options));
  }
}

class CSSCache extends CacheMap {
  constructor(sheetType) {
    super(CSS_EXPIRY_TIMEOUT_MS, url => {
      let uri = Services.io.newURI(url);
      return styleSheetService.preloadSheetAsync(uri, sheetType).then(sheet => {
        return {url, sheet};
      });
    });
  }

  addDocument(url, document) {
    sheetCacheDocuments.get(this.get(url)).add(document);
  }

  deleteDocument(url, document) {
    sheetCacheDocuments.get(this.get(url)).delete(document);
  }

  delete(url) {
    if (this.has(url)) {
      let promise = this.get(url);

      // Never remove a sheet from the cache if it's still being used by a
      // document. Rule processors can be shared between documents with the
      // same preloaded sheet, so we only lose by removing them while they're
      // still in use.
      let docs = ChromeUtils.nondeterministicGetWeakSetKeys(sheetCacheDocuments.get(promise));
      if (docs.length) {
        return;
      }
    }

    super.delete(url);
  }
}

defineLazyGetter(BrowserExtensionContent.prototype, "staticScripts", () => {
  return new ScriptCache({hasReturnValue: false});
});

defineLazyGetter(BrowserExtensionContent.prototype, "dynamicScripts", () => {
  return new ScriptCache({hasReturnValue: true});
});

defineLazyGetter(BrowserExtensionContent.prototype, "userCSS", () => {
  return new CSSCache(Ci.nsIStyleSheetService.USER_SHEET);
});

defineLazyGetter(BrowserExtensionContent.prototype, "authorCSS", () => {
  return new CSSCache(Ci.nsIStyleSheetService.AUTHOR_SHEET);
});

// Represents a content script.
class Script {
  constructor(extension, matcher) {
    this.extension = extension;
    this.matcher = matcher;

    this.runAt = this.matcher.runAt;
    this.js = this.matcher.jsPaths;
    this.css = this.matcher.cssPaths;
    this.removeCSS = this.matcher.removeCSS;
    this.cssOrigin = this.matcher.cssOrigin;

    this.cssCache = extension[this.cssOrigin === "user" ? "userCSS"
                                                        : "authorCSS"];
    this.scriptCache = extension[matcher.wantReturnValue ? "dynamicScripts"
                                                         : "staticScripts"];

    if (matcher.wantReturnValue) {
      this.compileScripts();
      this.loadCSS();
    }

    this.requiresCleanup = !this.removeCss && (this.css.length > 0 || matcher.cssCode);
  }

  compileScripts() {
    return this.js.map(url => this.scriptCache.get(url));
  }

  loadCSS() {
    return this.cssURLs.map(url => this.cssCache.get(url));
  }

  preload() {
    this.loadCSS();
    this.compileScripts();
  }

  cleanup(window) {
    if (!this.removeCss && this.cssURLs.length) {
      let winUtils = getWinUtils(window);

      let type = this.cssOrigin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;
      for (let url of this.cssURLs) {
        this.cssCache.deleteDocument(url, window.document);
        runSafeSyncWithoutClone(winUtils.removeSheetUsingURIString, url, type);
      }

      // Clear any sheets that were kept alive past their timeout as
      // a result of living in this document.
      this.cssCache.clear(CSS_EXPIRY_TIMEOUT_MS);
    }
  }

  matchesWindow(window) {
    return this.matcher.matchesWindow(window);
  }

  async injectInto(window) {
    let context = this.extension.getContext(window);

    if (this.runAt === "document_end") {
      await promiseDocumentReady(window.document);
    } else if (this.runAt === "document_idle") {
      await promiseDocumentLoaded(window.document);
    }

    return this.inject(context);
  }

  /**
   * Tries to inject this script into the given window and sandbox, if
   * there are pending operations for the window's current load state.
   *
   * @param {BaseContext} context
   *        The content script context into which to inject the scripts.
   * @returns {Promise<any>}
   *        Resolves to the last value in the evaluated script, when
   *        execution is complete.
   */
  async inject(context) {
    DocumentManager.lazyInit();
    if (this.requiresCleanup) {
      context.addScript(this);
    }

    let cssPromise;
    if (this.cssURLs.length) {
      let window = context.contentWindow;
      let winUtils = getWinUtils(window);

      let type = this.cssOrigin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;

      if (this.removeCSS) {
        for (let url of this.cssURLs) {
          this.cssCache.deleteDocument(url, window.document);

          runSafeSyncWithoutClone(winUtils.removeSheetUsingURIString, url, type);
        }
      } else {
        cssPromise = Promise.all(this.loadCSS()).then(sheets => {
          let window = context.contentWindow;
          if (!window) {
            return;
          }

          for (let {url, sheet} of sheets) {
            this.cssCache.addDocument(url, window.document);

            runSafeSyncWithoutClone(winUtils.addSheet, sheet, type);
          }
        });
      }
    }

    let scriptsPromise = Promise.all(this.compileScripts());

    // If we're supposed to inject at the start of the document load,
    // and we haven't already missed that point, block further parsing
    // until the scripts have been loaded.
    let {document} = context.contentWindow;
    if (this.runAt === "document_start" && document.readyState !== "complete") {
      document.blockParsing(scriptsPromise);
    }

    let scripts = await scriptsPromise;
    let result;

    if (this.runAt === "document_idle") {
      await new Promise(resolve =>
          context.contentWindow.requestIdleCallback(resolve,
                                                    {timeout: idleTimeout}));
    }

    // The evaluations below may throw, in which case the promise will be
    // automatically rejected.
    TelemetryStopwatch.start(CONTENT_SCRIPT_INJECTION_HISTOGRAM, context);
    try {
      for (let script of scripts) {
        result = script.executeInGlobal(context.cloneScope);
      }

      if (this.matcher.jsCode) {
        result = Cu.evalInSandbox(this.matcher.jsCode, context.cloneScope, "latest");
      }
    } finally {
      TelemetryStopwatch.finish(CONTENT_SCRIPT_INJECTION_HISTOGRAM, context);
    }

    await cssPromise;
    return result;
  }
}

defineLazyGetter(Script.prototype, "cssURLs", function() {
  // We can handle CSS urls (css) and CSS code (cssCode).
  let urls = this.css.slice();

  if (this.matcher.cssCode) {
    urls.push("data:text/css;charset=utf-8," + encodeURIComponent(this.matcher.cssCode));
  }

  return urls;
});

/**
 * An execution context for semi-privileged extension content scripts.
 *
 * This is the child side of the ContentScriptContextParent class
 * defined in ExtensionParent.jsm.
 */
class ContentScriptContextChild extends BaseContext {
  constructor(extension, contentWindow) {
    super("content_child", extension);

    this.setContentWindow(contentWindow);

    let frameId = WebNavigationFrames.getFrameId(contentWindow);
    this.frameId = frameId;

    this.scripts = [];

    let contentPrincipal = contentWindow.document.nodePrincipal;
    let ssm = Services.scriptSecurityManager;

    // Copy origin attributes from the content window origin attributes to
    // preserve the user context id.
    let attrs = contentPrincipal.originAttributes;
    let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, attrs);

    this.isExtensionPage = contentPrincipal.equals(extensionPrincipal);

    let principal;
    if (ssm.isSystemPrincipal(contentPrincipal)) {
      // Make sure we don't hand out the system principal by accident.
      // also make sure that the null principal has the right origin attributes
      principal = ssm.createNullPrincipal(attrs);
    } else if (this.isExtensionPage) {
      principal = contentPrincipal;
    } else {
      principal = [contentPrincipal, extensionPrincipal];
    }

    if (this.isExtensionPage) {
      // This is an iframe with content script API enabled and its principal
      // should be the contentWindow itself. We create a sandbox with the
      // contentWindow as principal and with X-rays disabled because it
      // enables us to create the APIs object in this sandbox object and then
      // copying it into the iframe's window.  See bug 1214658.
      this.sandbox = Cu.Sandbox(contentWindow, {
        sandboxPrototype: contentWindow,
        sameZoneAs: contentWindow,
        wantXrays: false,
        isWebExtensionContentScript: true,
      });
    } else {
      // This metadata is required by the Developer Tools, in order for
      // the content script to be associated with both the extension and
      // the tab holding the content page.
      let metadata = {
        "inner-window-id": this.innerWindowID,
        addonId: extensionPrincipal.addonId,
      };

      this.sandbox = Cu.Sandbox(principal, {
        metadata,
        sandboxPrototype: contentWindow,
        sameZoneAs: contentWindow,
        wantXrays: true,
        isWebExtensionContentScript: true,
        wantExportHelpers: true,
        wantGlobalProperties: ["XMLHttpRequest", "fetch"],
        originAttributes: attrs,
      });

      Cu.evalInSandbox(`
        window.JSON = JSON;
        window.XMLHttpRequest = XMLHttpRequest;
        window.fetch = fetch;
      `, this.sandbox);
    }

    Object.defineProperty(this, "principal", {
      value: Cu.getObjectPrincipal(this.sandbox),
      enumerable: true,
      configurable: true,
    });

    this.url = contentWindow.location.href;

    defineLazyGetter(this, "chromeObj", () => {
      let chromeObj = Cu.createObjectIn(this.sandbox);

      Schemas.inject(chromeObj, this.childManager);
      return chromeObj;
    });

    Schemas.exportLazyGetter(this.sandbox, "browser", () => this.chromeObj);
    Schemas.exportLazyGetter(this.sandbox, "chrome", () => this.chromeObj);
  }

  injectAPI() {
    if (!this.isExtensionPage) {
      throw new Error("Cannot inject extension API into non-extension window");
    }

    // This is an iframe with content script API enabled (bug 1214658)
    Schemas.exportLazyGetter(this.contentWindow,
                             "browser", () => this.chromeObj);
    Schemas.exportLazyGetter(this.contentWindow,
                             "chrome", () => this.chromeObj);
  }

  get cloneScope() {
    return this.sandbox;
  }

  addScript(script) {
    if (script.requiresCleanup) {
      this.scripts.push(script);
    }
  }

  close() {
    super.unload();

    if (this.contentWindow) {
      for (let script of this.scripts) {
        script.cleanup(this.contentWindow);
      }

      // Overwrite the content script APIs with an empty object if the APIs objects are still
      // defined in the content window (bug 1214658).
      if (this.isExtensionPage) {
        Cu.createObjectIn(this.contentWindow, {defineAs: "browser"});
        Cu.createObjectIn(this.contentWindow, {defineAs: "chrome"});
      }
    }
    Cu.nukeSandbox(this.sandbox);
    this.sandbox = null;
  }
}

defineLazyGetter(ContentScriptContextChild.prototype, "messenger", function() {
  // The |sender| parameter is passed directly to the extension.
  let sender = {id: this.extension.id, frameId: this.frameId, url: this.url};
  let filter = {extensionId: this.extension.id};
  let optionalFilter = {frameId: this.frameId};

  return new Messenger(this, [this.messageManager], sender, filter, optionalFilter);
});

defineLazyGetter(ContentScriptContextChild.prototype, "childManager", function() {
  apiManager.lazyInit();

  let localApis = {};
  let can = new CanOfAPIs(this, apiManager, localApis);

  let childManager = new ChildAPIManager(this, this.messageManager, can, {
    envType: "content_parent",
    url: this.url,
  });

  this.callOnClose(childManager);

  return childManager;
});

// Responsible for creating ExtensionContexts and injecting content
// scripts into them when new documents are created.
DocumentManager = {
  // Map[windowId -> Map[ExtensionChild -> ContentScriptContextChild]]
  contexts: new Map(),

  initialized: false,

  lazyInit() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    Services.obs.addObserver(this, "inner-window-destroyed");
    Services.obs.addObserver(this, "memory-pressure");
  },

  uninit() {
    Services.obs.removeObserver(this, "inner-window-destroyed");
    Services.obs.removeObserver(this, "memory-pressure");
  },

  observers: {
    "inner-window-destroyed"(subject, topic, data) {
      let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;

      MessageChannel.abortResponses({innerWindowID: windowId});

      // Close any existent content-script context for the destroyed window.
      if (this.contexts.has(windowId)) {
        let extensions = this.contexts.get(windowId);
        for (let context of extensions.values()) {
          context.close();
        }

        this.contexts.delete(windowId);
      }
    },
    "memory-pressure"(subject, topic, data) {
      let timeout = data === "heap-minimize" ? 0 : undefined;

      for (let cache of ChromeUtils.nondeterministicGetWeakSetKeys(scriptCaches)) {
        cache.clear(timeout);
      }
    },
  },

  observe(subject, topic, data) {
    this.observers[topic].call(this, subject, topic, data);
  },

  shutdownExtension(extension) {
    for (let extensions of this.contexts.values()) {
      let context = extensions.get(extension);
      if (context) {
        context.close();
        extensions.delete(extension);
      }
    }
  },

  getContexts(window) {
    let winId = getInnerWindowID(window);

    let extensions = this.contexts.get(winId);
    if (!extensions) {
      extensions = new Map();
      this.contexts.set(winId, extensions);
    }

    return extensions;
  },

  // For test use only.
  getContext(extensionId, window) {
    for (let [extension, context] of this.getContexts(window)) {
      if (extension.id === extensionId) {
        return context;
      }
    }
  },

  getContentScriptGlobals(window) {
    let extensions = this.contexts.get(getInnerWindowID(window));

    if (extensions) {
      return Array.from(extensions.values(), ctx => ctx.sandbox);
    }

    return [];
  },

  initExtensionContext(extension, window) {
    extension.getContext(window).injectAPI();
  },
};

this.ExtensionContent = {
  BrowserExtensionContent,
  Script,

  shutdownExtension(extension) {
    DocumentManager.shutdownExtension(extension);
  },

  // This helper is exported to be integrated in the devtools RDP actors,
  // that can use it to retrieve the existent WebExtensions ContentScripts
  // of a target window and be able to show the ContentScripts source in the
  // DevTools Debugger panel.
  getContentScriptGlobals(window) {
    return DocumentManager.getContentScriptGlobals(window);
  },

  initExtensionContext(extension, window) {
    DocumentManager.initExtensionContext(extension, window);
  },

  getContext(extension, window) {
    let extensions = DocumentManager.getContexts(window);

    let context = extensions.get(extension);
    if (!context) {
      context = new ContentScriptContextChild(extension, window);
      extensions.set(extension, context);
    }
    return context;
  },

  handleExtensionCapture(global, width, height, options) {
    let win = global.content;

    const XHTML_NS = "http://www.w3.org/1999/xhtml";
    let canvas = win.document.createElementNS(XHTML_NS, "canvas");
    canvas.width = width;
    canvas.height = height;
    canvas.mozOpaque = true;

    let ctx = canvas.getContext("2d");

    // We need to scale the image to the visible size of the browser,
    // in order for the result to appear as the user sees it when
    // settings like full zoom come into play.
    ctx.scale(canvas.width / win.innerWidth, canvas.height / win.innerHeight);

    ctx.drawWindow(win, win.scrollX, win.scrollY, win.innerWidth, win.innerHeight, "#fff");

    return canvas.toDataURL(`image/${options.format}`, options.quality / 100);
  },

  handleDetectLanguage(global, target) {
    let doc = target.content.document;

    return promiseDocumentReady(doc).then(() => {
      let elem = doc.documentElement;

      let language = (elem.getAttribute("xml:lang") || elem.getAttribute("lang") ||
                      doc.contentLanguage || null);

      // We only want the last element of the TLD here.
      // Only country codes have any effect on the results, but other
      // values cause no harm.
      let tld = doc.location.hostname.match(/[a-z]*$/)[0];

      // The CLD2 library used by the language detector is capable of
      // analyzing raw HTML. Unfortunately, that takes much more memory,
      // and since it's hosted by emscripten, and therefore can't shrink
      // its heap after it's grown, it has a performance cost.
      // So we send plain text instead.
      let encoder = new DocumentEncoder(doc, "text/plain", Ci.nsIDocumentEncoder.SkipInvisibleContent);
      let text = encoder.encodeToStringWithMaxLength(60 * 1024);

      let encoding = doc.characterSet;

      return LanguageDetector.detectLanguage({language, tld, text, encoding})
        .then(result => result.language === "un" ? "und" : result.language);
    });
  },

  // Used to executeScript, insertCSS and removeCSS.
  async handleExtensionExecute(global, target, options, script) {
    let executeInWin = (window) => {
      if (script.matchesWindow(window)) {
        return script.injectInto(window);
      }
      return null;
    };

    let promises = Array.from(this.enumerateWindows(global.docShell), executeInWin)
                        .filter(promise => promise);

    if (!promises.length) {
      if (options.frame_id) {
        return Promise.reject({message: `Frame not found, or missing host permission`});
      }

      let frames = options.all_frames ? ", and any iframes" : "";
      return Promise.reject({message: `Missing host permission for the tab${frames}`});
    }
    if (!options.all_frames && promises.length > 1) {
      return Promise.reject({message: `Internal error: Script matched multiple windows`});
    }

    let result = await Promise.all(promises);

    try {
      // Make sure we can structured-clone the result value before
      // we try to send it back over the message manager.
      Cu.cloneInto(result, target);
    } catch (e) {
      const {js} = options;
      const fileName = js.length ? js[js.length - 1] : "<anonymous code>";
      const message = `Script '${fileName}' result is non-structured-clonable data`;
      return Promise.reject({message, fileName});
    }

    return result;
  },

  handleWebNavigationGetFrame(global, {frameId}) {
    return WebNavigationFrames.getFrame(global.docShell, frameId);
  },

  handleWebNavigationGetAllFrames(global) {
    return WebNavigationFrames.getAllFrames(global.docShell);
  },

  // Helpers

  * enumerateWindows(docShell) {
    let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
                                               docShell.ENUMERATE_FORWARDS);

    for (let docShell of XPCOMUtils.IterSimpleEnumerator(enum_, Ci.nsIInterfaceRequestor)) {
      yield docShell.getInterface(Ci.nsIDOMWindow);
    }
  },
};
PK
!<b@.1.1modules/ExtensionPageChild.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 ExtensionPageChild */

this.EXPORTED_SYMBOLS = ["ExtensionPageChild"];

/**
 * This file handles privileged extension page logic that runs in the
 * child process.
 */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChildDevToolsUtils",
                                  "resource://gre/modules/ExtensionChildDevToolsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
                                  "resource://gre/modules/WebNavigationFrames.jsm");

XPCOMUtils.defineLazyGetter(
  this, "processScript",
  () => Cc["@mozilla.org/webextensions/extension-process-script;1"]
          .getService().wrappedJSObject);

const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";

Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionChild.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

const {
  defineLazyGetter,
  getInnerWindowID,
  promiseEvent,
} = ExtensionUtils;

const {
  BaseContext,
  CanOfAPIs,
  SchemaAPIManager,
} = ExtensionCommon;

const {
  ChildAPIManager,
  Messenger,
} = ExtensionChild;

var ExtensionPageChild;

function getFrameData(global) {
  return processScript.getFrameData(global, true);
}

var apiManager = new class extends SchemaAPIManager {
  constructor() {
    super("addon");
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_ADDON)) {
        this.loadScript(value);
      }
    }
  }
}();

var devtoolsAPIManager = new class extends SchemaAPIManager {
  constructor() {
    super("devtools");
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS)) {
        this.loadScript(value);
      }
    }
  }
}();

class ExtensionBaseContextChild extends BaseContext {
  /**
   * This ExtensionBaseContextChild represents an addon execution environment
   * that is running in an addon or devtools child process.
   *
   * @param {BrowserExtensionContent} extension This context's owner.
   * @param {object} params
   * @param {string} params.envType One of "addon_child" or "devtools_child".
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "background", "popup", "tab",
   *   "sidebar", "devtools_page" or "devtools_panel".
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
   */
  constructor(extension, params) {
    if (!params.envType) {
      throw new Error("Missing envType");
    }

    super(params.envType, extension);
    let {viewType = "tab", uri, contentWindow, tabId} = params;
    this.viewType = viewType;
    this.uri = uri || extension.baseURI;

    this.setContentWindow(contentWindow);

    // This is the MessageSender property passed to extension.
    let sender = {id: extension.id};
    if (viewType == "tab") {
      sender.frameId = WebNavigationFrames.getFrameId(contentWindow);
      sender.tabId = tabId;
      Object.defineProperty(this, "tabId",
        {value: tabId, enumerable: true, configurable: true});
    }
    if (uri) {
      sender.url = uri.spec;
    }
    this.sender = sender;

    Schemas.exportLazyGetter(contentWindow, "browser", () => {
      let browserObj = Cu.createObjectIn(contentWindow);
      Schemas.inject(browserObj, this.childManager);
      return browserObj;
    });

    Schemas.exportLazyGetter(contentWindow, "chrome", () => {
      let chromeApiWrapper = Object.create(this.childManager);
      chromeApiWrapper.isChromeCompat = true;

      let chromeObj = Cu.createObjectIn(contentWindow);
      Schemas.inject(chromeObj, chromeApiWrapper);
      return chromeObj;
    });
  }

  get cloneScope() {
    return this.contentWindow;
  }

  get principal() {
    return this.contentWindow.document.nodePrincipal;
  }

  get windowId() {
    if (["tab", "popup", "sidebar"].includes(this.viewType)) {
      let frameData = getFrameData(this.messageManager);
      return frameData ? frameData.windowId : -1;
    }
    return -1;
  }

  get tabId() {
    // Will be overwritten in the constructor if necessary.
    return -1;
  }

  // Called when the extension shuts down.
  shutdown() {
    this.unload();
  }

  // This method is called when an extension page navigates away or
  // its tab is closed.
  unload() {
    // Note that without this guard, we end up running unload code
    // multiple times for tab pages closed by the "page-unload" handlers
    // triggered below.
    if (this.unloaded) {
      return;
    }

    if (this.contentWindow) {
      this.contentWindow.close();
    }

    super.unload();
  }
}

defineLazyGetter(ExtensionBaseContextChild.prototype, "messenger", function() {
  let filter = {extensionId: this.extension.id};
  let optionalFilter = {};
  // Addon-generated messages (not necessarily from the same process as the
  // addon itself) are sent to the main process, which forwards them via the
  // parent process message manager. Specific replies can be sent to the frame
  // message manager.
  return new Messenger(this, [Services.cpmm, this.messageManager], this.sender,
                       filter, optionalFilter);
});

class ExtensionPageContextChild extends ExtensionBaseContextChild {
  /**
   * This ExtensionPageContextChild represents a privileged addon
   * execution environment that has full access to the WebExtensions
   * APIs (provided that the correct permissions have been requested).
   *
   * This is the child side of the ExtensionPageContextParent class
   * defined in ExtensionParent.jsm.
   *
   * @param {BrowserExtensionContent} extension This context's owner.
   * @param {object} params
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "background", "popup", "sidebar" or "tab".
   *     "background", "sidebar" and "tab" are used by `browser.extension.getViews`.
   *     "popup" is only used internally to identify page action and browser
   *     action popups and options_ui pages.
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
   */
  constructor(extension, params) {
    super(extension, Object.assign(params, {envType: "addon_child"}));

    this.extension.views.add(this);
  }

  unload() {
    super.unload();
    this.extension.views.delete(this);
  }
}

defineLazyGetter(ExtensionPageContextChild.prototype, "childManager", function() {
  apiManager.lazyInit();

  let localApis = {};
  let can = new CanOfAPIs(this, apiManager, localApis);

  let childManager = new ChildAPIManager(this, this.messageManager, can, {
    envType: "addon_parent",
    viewType: this.viewType,
    url: this.uri.spec,
    incognito: this.incognito,
  });

  this.callOnClose(childManager);

  if (this.viewType == "background") {
    apiManager.global.initializeBackgroundPage(this.contentWindow);
  }

  return childManager;
});

class DevToolsContextChild extends ExtensionBaseContextChild {
  /**
   * This DevToolsContextChild represents a devtools-related addon execution
   * environment that has access to the devtools API namespace and to the same subset
   * of APIs available in a content script execution environment.
   *
   * @param {BrowserExtensionContent} extension This context's owner.
   * @param {object} params
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "devtools_page" or "devtools_panel".
   * @param {object} [params.devtoolsToolboxInfo] This devtools toolbox's information,
   *   used if viewType is "devtools_page" or "devtools_panel".
   */
  constructor(extension, params) {
    super(extension, Object.assign(params, {envType: "devtools_child"}));

    this.devtoolsToolboxInfo = params.devtoolsToolboxInfo;
    ExtensionChildDevToolsUtils.initThemeChangeObserver(
      params.devtoolsToolboxInfo.themeName, this);

    this.extension.devtoolsViews.add(this);
  }

  unload() {
    super.unload();
    this.extension.devtoolsViews.delete(this);
  }
}

defineLazyGetter(DevToolsContextChild.prototype, "childManager", function() {
  devtoolsAPIManager.lazyInit();

  let localApis = {};
  let can = new CanOfAPIs(this, devtoolsAPIManager, localApis);

  let childManager = new ChildAPIManager(this, this.messageManager, can, {
    envType: "devtools_parent",
    viewType: this.viewType,
    url: this.uri.spec,
    incognito: this.incognito,
  });

  this.callOnClose(childManager);

  return childManager;
});

ExtensionPageChild = {
  // Map<innerWindowId, ExtensionPageContextChild>
  extensionContexts: new Map(),

  initialized: false,

  _init() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
  },

  observe(subject, topic, data) {
    if (topic === "inner-window-destroyed") {
      let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;

      this.destroyExtensionContext(windowId);
    }
  },

  expectViewLoad(global, viewType) {
    if (["popup", "sidebar"].includes(viewType)) {
      global.docShell.isAppTab = true;
    }

    promiseEvent(global, "DOMContentLoaded", true).then(() => {
      let windowId = getInnerWindowID(global.content);
      let context = this.extensionContexts.get(windowId);

      global.sendAsyncMessage("Extension:ExtensionViewLoaded",
                              {childId: context && context.childManager.id});
    });
  },

  /**
   * Create a privileged context at document-element-inserted.
   *
   * @param {BrowserExtensionContent} extension
   *     The extension for which the context should be created.
   * @param {nsIDOMWindow} contentWindow The global of the page.
   */
  initExtensionContext(extension, contentWindow) {
    this._init();

    if (!WebExtensionPolicy.isExtensionProcess) {
      throw new Error("Cannot create an extension page context in current process");
    }

    let windowId = getInnerWindowID(contentWindow);
    let context = this.extensionContexts.get(windowId);
    if (context) {
      if (context.extension !== extension) {
        throw new Error("A different extension context already exists for this frame");
      }
      throw new Error("An extension context was already initialized for this frame");
    }

    let mm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDocShell)
                          .QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIContentFrameMessageManager);

    let {viewType, tabId, devtoolsToolboxInfo} = getFrameData(mm) || {};

    let uri = contentWindow.document.documentURIObject;

    if (devtoolsToolboxInfo) {
      context = new DevToolsContextChild(extension, {
        viewType, contentWindow, uri, tabId, devtoolsToolboxInfo,
      });
    } else {
      context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId});
    }

    this.extensionContexts.set(windowId, context);
  },

  /**
   * Close the ExtensionPageContextChild belonging to the given window, if any.
   *
   * @param {number} windowId The inner window ID of the destroyed context.
   */
  destroyExtensionContext(windowId) {
    let context = this.extensionContexts.get(windowId);
    if (context) {
      context.unload();
      this.extensionContexts.delete(windowId);
    }
  },

  shutdownExtension(extensionId) {
    for (let [windowId, context] of this.extensionContexts) {
      if (context.extension.id == extensionId) {
        context.shutdown();
        this.extensionContexts.delete(windowId);
      }
    }
  },
};
PK
!<VVmodules/ExtensionParent.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/**
 * This module contains code for managing APIs that need to run in the
 * parent process, and handles the parent side of operations that need
 * to be proxied from ExtensionChild.jsm.
 */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

/* exported ExtensionParent */

this.EXPORTED_SYMBOLS = ["ExtensionParent"];

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
                                  "resource://gre/modules/DeferredSave.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                  "resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "IndexedDB",
                                  "resource://gre/modules/IndexedDB.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                  "resource://gre/modules/NativeMessaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gAddonPolicyService",
                                   "@mozilla.org/addons/policy-service;1",
                                   "nsIAddonPolicyService");
XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
                                   "@mozilla.org/addons/addon-manager-startup;1",
                                   "amIAddonManagerStartup");

Cu.import("resource://gre/modules/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

var {
  BaseContext,
  CanOfAPIs,
  SchemaAPIManager,
  SpreadArgs,
} = ExtensionCommon;

var {
  DefaultWeakMap,
  ExtensionError,
  MessageManagerProxy,
  defineLazyGetter,
  promiseDocumentLoaded,
  promiseEvent,
  promiseFileContents,
  promiseObserved,
} = ExtensionUtils;

const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";

let schemaURLs = new Set();

schemaURLs.add("chrome://extensions/content/schemas/experiments.json");

let GlobalManager;
let ParentAPIManager;
let ProxyMessenger;

// This object loads the ext-*.js scripts that define the extension API.
let apiManager = new class extends SchemaAPIManager {
  constructor() {
    super("main");
    this.initialized = null;

    this.on("startup", (event, extension) => { // eslint-disable-line mozilla/balanced-listeners
      let promises = [];
      for (let apiName of this.eventModules.get("startup")) {
        promises.push(this.asyncGetAPI(apiName, extension).then(api => {
          api.onStartup(extension.startupReason);
        }));
      }

      return Promise.all(promises);
    });
  }

  // Loads all the ext-*.js scripts currently registered.
  lazyInit() {
    if (this.initialized) {
      return this.initialized;
    }

    let scripts = [];
    for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS)) {
      scripts.push(value);
    }

    let promise = Promise.all(scripts.map(url => ChromeUtils.compileScript(url))).then(scripts => {
      for (let script of scripts) {
        script.executeInGlobal(this.global);
      }

      // Load order matters here. The base manifest defines types which are
      // extended by other schemas, so needs to be loaded first.
      return Schemas.load(BASE_SCHEMA).then(() => {
        let promises = [];
        for (let [/* name */, url] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCHEMAS)) {
          promises.push(Schemas.load(url));
        }
        for (let url of this.schemaURLs) {
          promises.push(Schemas.load(url));
        }
        for (let url of schemaURLs) {
          promises.push(Schemas.load(url));
        }
        return Promise.all(promises);
      });
    });

    /* eslint-disable mozilla/balanced-listeners */
    Services.mm.addMessageListener("Extension:GetTabAndWindowId", this);
    /* eslint-enable mozilla/balanced-listeners */

    this.initialized = promise;
    return this.initialized;
  }

  receiveMessage({name, target, sync}) {
    if (name === "Extension:GetTabAndWindowId") {
      let result = this.global.tabTracker.getBrowserData(target);

      if (result.tabId) {
        if (sync) {
          return result;
        }
        target.messageManager.sendAsyncMessage("Extension:SetFrameData", result);
      }
    }
  }
}();

// Subscribes to messages related to the extension messaging API and forwards it
// to the relevant message manager. The "sender" field for the `onMessage` and
// `onConnect` events are updated if needed.
ProxyMessenger = {
  _initialized: false,

  init() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    // Listen on the global frame message manager because content scripts send
    // and receive extension messages via their frame.
    // Listen on the parent process message manager because `runtime.connect`
    // and `runtime.sendMessage` requests must be delivered to all frames in an
    // addon process (by the API contract).
    // And legacy addons are not associated with a frame, so that is another
    // reason for having a parent process manager here.
    let messageManagers = [Services.mm, Services.ppmm];

    MessageChannel.addListener(messageManagers, "Extension:Connect", this);
    MessageChannel.addListener(messageManagers, "Extension:Message", this);
    MessageChannel.addListener(messageManagers, "Extension:Port:Disconnect", this);
    MessageChannel.addListener(messageManagers, "Extension:Port:PostMessage", this);
  },

  async receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) {
    if (recipient.toNativeApp) {
      let {childId, toNativeApp} = recipient;
      if (messageName == "Extension:Message") {
        let context = ParentAPIManager.getContextById(childId);
        return new NativeApp(context, toNativeApp).sendMessage(data);
      }
      if (messageName == "Extension:Connect") {
        let context = ParentAPIManager.getContextById(childId);
        NativeApp.onConnectNative(context, target.messageManager, data.portId, sender, toNativeApp);
        return true;
      }
      // "Extension:Port:Disconnect" and "Extension:Port:PostMessage" for
      // native messages are handled by NativeApp.
      return;
    }

    const noHandlerError = {
      result: MessageChannel.RESULT_NO_HANDLER,
      message: "No matching message handler for the given recipient.",
    };

    let extension = GlobalManager.extensionMap.get(sender.extensionId);
    let receiverMM = this.getMessageManagerForRecipient(recipient);
    if (!extension || !receiverMM) {
      return Promise.reject(noHandlerError);
    }

    if ((messageName == "Extension:Message" ||
         messageName == "Extension:Connect") &&
        apiManager.global.tabGetSender) {
      // From ext-tabs.js, undefined on Android.
      apiManager.global.tabGetSender(extension, target, sender);
    }

    let promise1 = MessageChannel.sendMessage(receiverMM, messageName, data, {
      sender,
      recipient,
      responseType,
    });

    if (!(extension.isEmbedded || recipient.toProxyScript) || !extension.remote) {
      return promise1;
    }

    // If we have a proxy script sandbox or a remote, embedded extension, where
    // the legacy side is running in a different process than the WebExtension
    // side. As a result, we need to dispatch the message to both the parent and
    // extension processes, and manually merge the results.
    let promise2 = MessageChannel.sendMessage(Services.ppmm.getChildAt(0), messageName, data, {
      sender,
      recipient,
      responseType,
    });

    let result = undefined;
    let failures = 0;
    let tryPromise = async promise => {
      try {
        let res = await promise;
        if (result === undefined) {
          result = res;
        }
      } catch (e) {
        if (e.result === MessageChannel.RESULT_NO_RESPONSE) {
          // Ignore.
        } else if (e.result === MessageChannel.RESULT_NO_HANDLER) {
          failures++;
        } else {
          throw e;
        }
      }
    };

    await Promise.all([tryPromise(promise1), tryPromise(promise2)]);
    if (failures == 2) {
      return Promise.reject(noHandlerError);
    }
    return result;
  },

  /**
   * @param {object} recipient An object that was passed to
   *     `MessageChannel.sendMessage`.
   * @param {Extension} extension
   * @returns {object|null} The message manager matching the recipient if found.
   */
  getMessageManagerForRecipient(recipient) {
    let {tabId} = recipient;
    // tabs.sendMessage / tabs.connect
    if (tabId) {
      // `tabId` being set implies that the tabs API is supported, so we don't
      // need to check whether `tabTracker` exists.
      let tab = apiManager.global.tabTracker.getTab(tabId, null);
      if (!tab) {
        return null;
      }
      let browser = tab.linkedBrowser || tab.browser;

      // Options panels in the add-on manager currently require
      // special-casing, since their message managers aren't currently
      // connected to the tab's top-level message manager. To deal with
      // this, we find the options <browser> for the tab, and use that
      // directly, insteead.
      if (browser.currentURI.cloneIgnoringRef().spec === "about:addons") {
        let optionsBrowser = browser.contentDocument.querySelector(".inline-options-browser");
        if (optionsBrowser) {
          browser = optionsBrowser;
        }
      }

      return browser.messageManager;
    }

    // runtime.sendMessage / runtime.connect
    let extension = GlobalManager.extensionMap.get(recipient.extensionId);
    if (extension) {
      return extension.parentMessageManager;
    }

    return null;
  },
};

// Responsible for loading extension APIs into the right globals.
GlobalManager = {
  // Map[extension ID -> Extension]. Determines which extension is
  // responsible for content under a particular extension ID.
  extensionMap: new Map(),
  initialized: false,

  init(extension) {
    if (this.extensionMap.size == 0) {
      ProxyMessenger.init();
      apiManager.on("extension-browser-inserted", this._onExtensionBrowser);
      this.initialized = true;
    }

    this.extensionMap.set(extension.id, extension);
  },

  uninit(extension) {
    this.extensionMap.delete(extension.id);

    if (this.extensionMap.size == 0 && this.initialized) {
      apiManager.off("extension-browser-inserted", this._onExtensionBrowser);
      this.initialized = false;
    }
  },

  _onExtensionBrowser(type, browser, additionalData = {}) {
    browser.messageManager.loadFrameScript(`data:,
      Components.utils.import("resource://gre/modules/Services.jsm");

      Services.obs.notifyObservers(this, "tab-content-frameloader-created", "");
    `, false);

    let viewType = browser.getAttribute("webextension-view-type");
    if (viewType) {
      let data = {viewType};

      let {tabTracker} = apiManager.global;
      Object.assign(data, tabTracker.getBrowserData(browser), additionalData);

      browser.messageManager.sendAsyncMessage("Extension:SetFrameData",
                                              data);
    }
  },

  getExtension(extensionId) {
    return this.extensionMap.get(extensionId);
  },

  injectInObject(context, isChromeCompat, dest) {
    SchemaAPIManager.generateAPIs(context, context.extension.apis, dest);
  },
};

/**
 * The proxied parent side of a context in ExtensionChild.jsm, for the
 * parent side of a proxied API.
 */
class ProxyContextParent extends BaseContext {
  constructor(envType, extension, params, xulBrowser, principal) {
    super(envType, extension);

    this.uri = Services.io.newURI(params.url);

    this.incognito = params.incognito;

    this.listenerPromises = new Set();

    // This message manager is used by ParentAPIManager to send messages and to
    // close the ProxyContext if the underlying message manager closes. This
    // message manager object may change when `xulBrowser` swaps docshells, e.g.
    // when a tab is moved to a different window.
    this.messageManagerProxy = new MessageManagerProxy(xulBrowser);

    Object.defineProperty(this, "principal", {
      value: principal, enumerable: true, configurable: true,
    });

    this.listenerProxies = new Map();

    this.pendingEventBrowser = null;

    apiManager.emit("proxy-context-load", this);
  }

  async withPendingBrowser(browser, callable) {
    let savedBrowser = this.pendingEventBrowser;
    this.pendingEventBrowser = browser;
    try {
      let result = await callable();
      return result;
    } finally {
      this.pendingEventBrowser = savedBrowser;
    }
  }

  get cloneScope() {
    return this.sandbox;
  }

  get xulBrowser() {
    return this.messageManagerProxy.eventTarget;
  }

  get parentMessageManager() {
    return this.messageManagerProxy.messageManager;
  }

  shutdown() {
    this.unload();
  }

  unload() {
    if (this.unloaded) {
      return;
    }
    this.messageManagerProxy.dispose();
    super.unload();
    apiManager.emit("proxy-context-unload", this);
  }
}

defineLazyGetter(ProxyContextParent.prototype, "apiCan", function() {
  let obj = {};
  let can = new CanOfAPIs(this, apiManager, obj);
  GlobalManager.injectInObject(this, false, obj);
  return can;
});

defineLazyGetter(ProxyContextParent.prototype, "apiObj", function() {
  return this.apiCan.root;
});

defineLazyGetter(ProxyContextParent.prototype, "sandbox", function() {
  return Cu.Sandbox(this.principal);
});

/**
 * The parent side of proxied API context for extension content script
 * running in ExtensionContent.jsm.
 */
class ContentScriptContextParent extends ProxyContextParent {
}

/**
 * The parent side of proxied API context for extension page, such as a
 * background script, a tab page, or a popup, running in
 * ExtensionChild.jsm.
 */
class ExtensionPageContextParent extends ProxyContextParent {
  constructor(envType, extension, params, xulBrowser) {
    super(envType, extension, params, xulBrowser, extension.principal);

    this.viewType = params.viewType;

    this.extension.views.add(this);

    extension.emit("extension-proxy-context-load", this);
  }

  // The window that contains this context. This may change due to moving tabs.
  get xulWindow() {
    let win = this.xulBrowser.ownerGlobal;
    return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
              .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
              .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
  }

  get currentWindow() {
    if (this.viewType !== "background") {
      return this.xulWindow;
    }
  }

  get windowId() {
    let {currentWindow} = this;
    let {windowTracker} = apiManager.global;

    if (currentWindow && windowTracker) {
      return windowTracker.getId(currentWindow);
    }
  }

  get tabId() {
    let {tabTracker} = apiManager.global;
    let data = tabTracker.getBrowserData(this.xulBrowser);
    if (data.tabId >= 0) {
      return data.tabId;
    }
  }

  onBrowserChange(browser) {
    super.onBrowserChange(browser);
    this.xulBrowser = browser;
  }

  shutdown() {
    apiManager.emit("page-shutdown", this);
    this.extension.views.delete(this);
    super.shutdown();
  }
}

/**
 * The parent side of proxied API context for devtools extension page, such as a
 * devtools pages and panels running in ExtensionChild.jsm.
 */
class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
  set devToolsToolbox(toolbox) {
    if (this._devToolsToolbox) {
      throw new Error("Cannot set the context DevTools toolbox twice");
    }

    this._devToolsToolbox = toolbox;

    return toolbox;
  }

  get devToolsToolbox() {
    return this._devToolsToolbox;
  }

  set devToolsTarget(contextDevToolsTarget) {
    if (this._devToolsTarget) {
      throw new Error("Cannot set the context DevTools target twice");
    }

    this._devToolsTarget = contextDevToolsTarget;

    return contextDevToolsTarget;
  }

  get devToolsTarget() {
    return this._devToolsTarget;
  }

  shutdown() {
    if (this._devToolsTarget) {
      this._devToolsTarget.destroy();
      this._devToolsTarget = null;
    }

    this._devToolsToolbox = null;

    super.shutdown();
  }
}

ParentAPIManager = {
  proxyContexts: new Map(),

  init() {
    Services.obs.addObserver(this, "message-manager-close");

    Services.mm.addMessageListener("API:CreateProxyContext", this);
    Services.mm.addMessageListener("API:CloseProxyContext", this, true);
    Services.mm.addMessageListener("API:Call", this);
    Services.mm.addMessageListener("API:AddListener", this);
    Services.mm.addMessageListener("API:RemoveListener", this);
  },

  observe(subject, topic, data) {
    if (topic === "message-manager-close") {
      let mm = subject;
      for (let [childId, context] of this.proxyContexts) {
        if (context.parentMessageManager === mm) {
          this.closeProxyContext(childId);
        }
      }

      // Reset extension message managers when their child processes shut down.
      for (let extension of GlobalManager.extensionMap.values()) {
        if (extension.parentMessageManager === mm) {
          extension.parentMessageManager = null;
        }
      }
    }
  },

  shutdownExtension(extensionId) {
    for (let [childId, context] of this.proxyContexts) {
      if (context.extension.id == extensionId) {
        context.shutdown();
        this.proxyContexts.delete(childId);
      }
    }
  },

  receiveMessage({name, data, target}) {
    try {
      switch (name) {
        case "API:CreateProxyContext":
          this.createProxyContext(data, target);
          break;

        case "API:CloseProxyContext":
          this.closeProxyContext(data.childId);
          break;

        case "API:Call":
          this.call(data, target);
          break;

        case "API:AddListener":
          this.addListener(data, target);
          break;

        case "API:RemoveListener":
          this.removeListener(data);
          break;
      }
    } catch (e) {
      Cu.reportError(e);
    }
  },

  createProxyContext(data, target) {
    let {envType, extensionId, childId, principal} = data;
    if (this.proxyContexts.has(childId)) {
      throw new Error("A WebExtension context with the given ID already exists!");
    }

    let extension = GlobalManager.getExtension(extensionId);
    if (!extension) {
      throw new Error(`No WebExtension found with ID ${extensionId}`);
    }

    let context;
    if (envType == "addon_parent" || envType == "devtools_parent") {
      let processMessageManager = (target.messageManager.processMessageManager ||
                                   Services.ppmm.getChildAt(0));

      if (!extension.parentMessageManager) {
        let expectedRemoteType = extension.remote ? E10SUtils.EXTENSION_REMOTE_TYPE : null;
        if (target.remoteType === expectedRemoteType) {
          extension.parentMessageManager = processMessageManager;
        }
      }

      if (processMessageManager !== extension.parentMessageManager) {
        throw new Error("Attempt to create privileged extension parent from incorrect child process");
      }

      if (envType == "addon_parent") {
        context = new ExtensionPageContextParent(envType, extension, data, target);
      } else if (envType == "devtools_parent") {
        context = new DevToolsExtensionPageContextParent(envType, extension, data, target);
      }
    } else if (envType == "content_parent") {
      context = new ContentScriptContextParent(envType, extension, data, target, principal);
    } else {
      throw new Error(`Invalid WebExtension context envType: ${envType}`);
    }
    this.proxyContexts.set(childId, context);
  },

  closeProxyContext(childId) {
    let context = this.proxyContexts.get(childId);
    if (context) {
      context.unload();
      this.proxyContexts.delete(childId);
    }
  },

  async call(data, target) {
    let context = this.getContextById(data.childId);
    if (context.parentMessageManager !== target.messageManager) {
      throw new Error("Got message on unexpected message manager");
    }

    let reply = result => {
      if (!context.parentMessageManager) {
        Services.console.logStringMessage("Cannot send function call result: other side closed connection " +
                                          `(call data: ${uneval({path: data.path, args: data.args})})`);
        return;
      }

      context.parentMessageManager.sendAsyncMessage(
        "API:CallResult",
        Object.assign({
          childId: data.childId,
          callId: data.callId,
        }, result));
    };

    try {
      let args = data.noClone ? data.args : Cu.cloneInto(data.args, context.sandbox);
      let pendingBrowser = context.pendingEventBrowser;
      let fun = await context.apiCan.asyncFindAPIPath(data.path);
      let result = context.withPendingBrowser(pendingBrowser,
                                              () => fun(...args));
      if (data.callId) {
        result = result || Promise.resolve();

        result.then(result => {
          result = result instanceof SpreadArgs ? [...result] : [result];

          let holder = new StructuredCloneHolder(result);

          reply({result: holder});
        }, error => {
          error = context.normalizeError(error);
          reply({error: {message: error.message, fileName: error.fileName}});
        });
      }
    } catch (e) {
      if (data.callId) {
        let error = context.normalizeError(e);
        reply({error: {message: error.message}});
      } else {
        Cu.reportError(e);
      }
    }
  },

  async addListener(data, target) {
    let context = this.getContextById(data.childId);
    if (context.parentMessageManager !== target.messageManager) {
      throw new Error("Got message on unexpected message manager");
    }

    let {childId} = data;
    let handlingUserInput = false;

    function listener(...listenerArgs) {
      return context.sendMessage(
        context.parentMessageManager,
        "API:RunListener",
        {
          childId,
          handlingUserInput,
          listenerId: data.listenerId,
          path: data.path,
          args: new StructuredCloneHolder(listenerArgs),
        },
        {
          recipient: {childId},
        });
    }

    context.listenerProxies.set(data.listenerId, listener);

    let args = Cu.cloneInto(data.args, context.sandbox);
    let promise = context.apiCan.asyncFindAPIPath(data.path);

    // Store pending listener additions so we can be sure they're all
    // fully initialize before we consider extension startup complete.
    if (context.viewType === "background" && context.listenerPromises) {
      const {listenerPromises} = context;
      listenerPromises.add(promise);
      let remove = () => { listenerPromises.delete(promise); };
      promise.then(remove, remove);
    }

    let handler = await promise;
    if (handler.setUserInput) {
      handlingUserInput = true;
    }
    handler.addListener(listener, ...args);
  },

  async removeListener(data) {
    let context = this.getContextById(data.childId);
    let listener = context.listenerProxies.get(data.listenerId);

    let handler = await context.apiCan.asyncFindAPIPath(data.path);
    handler.removeListener(listener);
  },

  getContextById(childId) {
    let context = this.proxyContexts.get(childId);
    if (!context) {
      throw new Error("WebExtension context not found!");
    }
    return context;
  },
};

ParentAPIManager.init();

/**
 * This utility class is used to create hidden XUL windows, which are used to
 * contains the extension pages that are not visible (e.g. the background page and
 * the devtools page), and it is also used by the ExtensionDebuggingUtils to
 * contains the browser elements that are used by the addon debugger to be able
 * to connect to the devtools actors running in the same process of the target
 * extension (and be able to stay connected across the addon reloads).
 */
class HiddenXULWindow {
  constructor() {
    this._windowlessBrowser = null;
    this.waitInitialized = this.initWindowlessBrowser();
  }

  shutdown() {
    if (this.unloaded) {
      throw new Error("Unable to shutdown an unloaded HiddenXULWindow instance");
    }

    this.unloaded = true;

    this.chromeShell = null;
    this.waitInitialized = null;

    this._windowlessBrowser.close();
    this._windowlessBrowser = null;
  }

  get chromeDocument() {
    return this._windowlessBrowser.document;
  }

  /**
   * Private helper that create a XULDocument in a windowless browser.
   *
   * @returns {Promise<XULDocument>}
   *          A promise which resolves to the newly created XULDocument.
   */
  async initWindowlessBrowser() {
    if (this.waitInitialized) {
      throw new Error("HiddenXULWindow already initialized");
    }

    // The invisible page is currently wrapped in a XUL window to fix an issue
    // with using the canvas API from a background page (See Bug 1274775).
    let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
    this._windowlessBrowser = windowlessBrowser;

    // The windowless browser is a thin wrapper around a docShell that keeps
    // its related resources alive. It implements nsIWebNavigation and
    // forwards its methods to the underlying docShell, but cannot act as a
    // docShell itself. Calling `getInterface(nsIDocShell)` gives us the
    // underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us
    // access to the webNav methods that are already available on the
    // windowless browser, but contrary to appearances, they are not the same
    // object.
    this.chromeShell = this._windowlessBrowser
                           .QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDocShell)
                           .QueryInterface(Ci.nsIWebNavigation);

    if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
      let attrs = this.chromeShell.getOriginAttributes();
      attrs.privateBrowsingId = 1;
      this.chromeShell.setOriginAttributes(attrs);
    }

    let system = Services.scriptSecurityManager.getSystemPrincipal();
    this.chromeShell.createAboutBlankContentViewer(system);
    this.chromeShell.useGlobalHistory = false;
    this.chromeShell.loadURI("chrome://extensions/content/dummy.xul", 0, null, null, null);

    await promiseObserved("chrome-document-global-created",
                          win => win.document == this.chromeShell.document);
    return promiseDocumentLoaded(windowlessBrowser.document);
  }

  /**
   * Creates the browser XUL element that will contain the WebExtension Page.
   *
   * @param {Object} xulAttributes
   *        An object that contains the xul attributes to set of the newly
   *        created browser XUL element.
   * @param {nsIFrameLoader} [groupFrameLoader]
   *        The frame loader to load this browser into the same process
   *        and tab group as.
   *
   * @returns {Promise<XULElement>}
   *          A Promise which resolves to the newly created browser XUL element.
   */
  async createBrowserElement(xulAttributes, groupFrameLoader = null) {
    if (!xulAttributes || Object.keys(xulAttributes).length === 0) {
      throw new Error("missing mandatory xulAttributes parameter");
    }

    await this.waitInitialized;

    const chromeDoc = this.chromeDocument;

    const browser = chromeDoc.createElement("browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("disableglobalhistory", "true");
    browser.sameProcessAsFrameLoader = groupFrameLoader;

    for (const [name, value] of Object.entries(xulAttributes)) {
      if (value != null) {
        browser.setAttribute(name, value);
      }
    }

    let awaitFrameLoader = Promise.resolve();

    if (browser.getAttribute("remote") === "true") {
      awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
    }

    chromeDoc.documentElement.appendChild(browser);
    await awaitFrameLoader;

    return browser;
  }
}


/**
 * This is a base class used by the ext-backgroundPage and ext-devtools API implementations
 * to inherits the shared boilerplate code needed to create a parent document for the hidden
 * extension pages (e.g. the background page, the devtools page) in the BackgroundPage and
 * DevToolsPage classes.
 *
 * @param {Extension} extension
 *        The Extension which owns the hidden extension page created (used to decide
 *        if the hidden extension page parent doc is going to be a windowlessBrowser or
 *        a visible XUL window).
 * @param {string} viewType
 *        The viewType of the WebExtension page that is going to be loaded
 *        in the created browser element (e.g. "background" or "devtools_page").
 */
class HiddenExtensionPage extends HiddenXULWindow {
  constructor(extension, viewType) {
    if (!extension || !viewType) {
      throw new Error("extension and viewType parameters are mandatory");
    }

    super();
    this.extension = extension;
    this.viewType = viewType;
    this.browser = null;
  }

  /**
   * Destroy the created parent document.
   */
  shutdown() {
    if (this.unloaded) {
      throw new Error("Unable to shutdown an unloaded HiddenExtensionPage instance");
    }

    if (this.browser) {
      this.browser.remove();
      this.browser = null;
    }

    super.shutdown();
  }

  /**
   * Creates the browser XUL element that will contain the WebExtension Page.
   *
   * @returns {Promise<XULElement>}
   *          A Promise which resolves to the newly created browser XUL element.
   */
  async createBrowserElement() {
    if (this.browser) {
      throw new Error("createBrowserElement called twice");
    }

    this.browser = await super.createBrowserElement({
      "webextension-view-type": this.viewType,
      "remote": this.extension.remote ? "true" : null,
      "remoteType": this.extension.remote ?
        E10SUtils.EXTENSION_REMOTE_TYPE : null,
    }, this.extension.groupFrameLoader);

    return this.browser;
  }
}

/**
 * This object provides utility functions needed by the devtools actors to
 * be able to connect and debug an extension (which can run in the main or in
 * a child extension process).
 */
const DebugUtils = {
  // A lazily created hidden XUL window, which contains the browser elements
  // which are used to connect the webextension patent actor to the extension process.
  hiddenXULWindow: null,

  // Map<extensionId, Promise<XULElement>>
  debugBrowserPromises: new Map(),
  // DefaultWeakMap<Promise<browser XULElement>, Set<WebExtensionParentActor>>
  debugActors: new DefaultWeakMap(() => new Set()),

  _extensionUpdatedWatcher: null,
  watchExtensionUpdated() {
    if (!this._extensionUpdatedWatcher) {
      // Watch the updated extension objects.
      this._extensionUpdatedWatcher = async (evt, extension) => {
        const browserPromise = this.debugBrowserPromises.get(extension.id);
        if (browserPromise) {
          const browser = await browserPromise;
          if (browser.isRemoteBrowser !== extension.remote &&
              this.debugBrowserPromises.get(extension.id) === browserPromise) {
            // If the cached browser element is not anymore of the same
            // remote type of the extension, remove it.
            this.debugBrowserPromises.delete(extension.id);
            browser.remove();
          }
        }
      };

      apiManager.on("ready", this._extensionUpdatedWatcher);
    }
  },

  unwatchExtensionUpdated() {
    if (this._extensionUpdatedWatcher) {
      apiManager.off("ready", this._extensionUpdatedWatcher);
      delete this._extensionUpdatedWatcher;
    }
  },

  getExtensionManifestWarnings(id) {
    const addon = GlobalManager.extensionMap.get(id);
    if (addon) {
      return addon.warnings;
    }
    return [];
  },


  /**
   * Retrieve a XUL browser element which has been configured to be able to connect
   * the devtools actor with the process where the extension is running.
   *
   * @param {WebExtensionParentActor} webExtensionParentActor
   *        The devtools actor that is retrieving the browser element.
   *
   * @returns {Promise<XULElement>}
   *          A promise which resolves to the configured browser XUL element.
   */
  async getExtensionProcessBrowser(webExtensionParentActor) {
    const extensionId = webExtensionParentActor.addonId;
    const extension = GlobalManager.getExtension(extensionId);
    if (!extension) {
      throw new Error(`Extension not found: ${extensionId}`);
    }

    const createBrowser = () => {
      if (!this.hiddenXULWindow) {
        this.hiddenXULWindow = new HiddenXULWindow();
        this.watchExtensionUpdated();
      }

      return this.hiddenXULWindow.createBrowserElement({
        "webextension-addon-debug-target": extensionId,
        "remote": extension.remote ? "true" : null,
        "remoteType": extension.remote ?
          E10SUtils.EXTENSION_REMOTE_TYPE : null,
      }, extension.groupFrameLoader);
    };

    let browserPromise = this.debugBrowserPromises.get(extensionId);

    // Create a new promise if there is no cached one in the map.
    if (!browserPromise) {
      browserPromise = createBrowser();
      this.debugBrowserPromises.set(extensionId, browserPromise);
      browserPromise.then(browser => {
        browserPromise.browser = browser;
      });
      browserPromise.catch(e => {
        Cu.reportError(e);
        this.debugBrowserPromises.delete(extensionId);
      });
    }

    this.debugActors.get(browserPromise).add(webExtensionParentActor);

    return browserPromise;
  },

  getFrameLoader(extensionId) {
    let promise = this.debugBrowserPromises.get(extensionId);
    return promise && promise.browser && promise.browser.frameLoader;
  },

  /**
   * Given the devtools actor that has retrieved an addon debug browser element,
   * it destroys the XUL browser element, and it also destroy the hidden XUL window
   * if it is not currently needed.
   *
   * @param {WebExtensionParentActor} webExtensionParentActor
   *        The devtools actor that has retrieved an addon debug browser element.
   */
  async releaseExtensionProcessBrowser(webExtensionParentActor) {
    const extensionId = webExtensionParentActor.addonId;
    const browserPromise = this.debugBrowserPromises.get(extensionId);

    if (browserPromise) {
      const actorsSet = this.debugActors.get(browserPromise);
      actorsSet.delete(webExtensionParentActor);
      if (actorsSet.size === 0) {
        this.debugActors.delete(browserPromise);
        this.debugBrowserPromises.delete(extensionId);
        await browserPromise.then((browser) => browser.remove());
      }
    }

    if (this.debugBrowserPromises.size === 0 && this.hiddenXULWindow) {
      this.hiddenXULWindow.shutdown();
      this.hiddenXULWindow = null;
      this.unwatchExtensionUpdated();
    }
  },
};


function promiseExtensionViewLoaded(browser) {
  return new Promise(resolve => {
    browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad({data}) {
      browser.messageManager.removeMessageListener("Extension:ExtensionViewLoaded", onLoad);
      resolve(data.childId && ParentAPIManager.getContextById(data.childId));
    });
  });
}

/**
 * This helper is used to subscribe a listener (e.g. in the ext-devtools API implementation)
 * to be called for every ExtensionProxyContext created for an extension page given
 * its related extension, viewType and browser element (both the top level context and any context
 * created for the extension urls running into its iframe descendants).
 *
 * @param {object} params.extension
 *        The Extension on which we are going to listen for the newly created ExtensionProxyContext.
 * @param {string} params.viewType
 *        The viewType of the WebExtension page that we are watching (e.g. "background" or
 *        "devtools_page").
 * @param {XULElement} params.browser
 *        The browser element of the WebExtension page that we are watching.
 * @param {function} onExtensionProxyContextLoaded
 *        The callback that is called when a new context has been loaded (as `callback(context)`);
 *
 * @returns {function}
 *          Unsubscribe the listener.
 */
function watchExtensionProxyContextLoad({extension, viewType, browser}, onExtensionProxyContextLoaded) {
  if (typeof onExtensionProxyContextLoaded !== "function") {
    throw new Error("Missing onExtensionProxyContextLoaded handler");
  }

  const listener = (event, context) => {
    if (context.viewType == viewType && context.xulBrowser == browser) {
      onExtensionProxyContextLoaded(context);
    }
  };

  extension.on("extension-proxy-context-load", listener);

  return () => {
    extension.off("extension-proxy-context-load", listener);
  };
}

// Used to cache the list of WebExtensionManifest properties defined in the BASE_SCHEMA.
let gBaseManifestProperties = null;

/**
 * Function to obtain the extension name from a moz-extension URI without exposing GlobalManager.
 *
 * @param {Object} uri The URI for the extension to look up.
 * @returns {string} the name of the extension.
 */
function extensionNameFromURI(uri) {
  let id = null;
  try {
    id = gAddonPolicyService.extensionURIToAddonId(uri);
  } catch (ex) {
    if (ex.name != "NS_ERROR_XPC_BAD_CONVERT_JS") {
      Cu.reportError("Extension cannot be found in AddonPolicyService.");
    }
  }
  return GlobalManager.getExtension(id).name;
}

const INTEGER = /^[1-9]\d*$/;

// Manages icon details for toolbar buttons in the |pageAction| and
// |browserAction| APIs.
let IconDetails = {
  // WeakMap<Extension -> Map<url-string -> object>>
  iconCache: new DefaultWeakMap(() => new Map()),

  // Normalizes the various acceptable input formats into an object
  // with icon size as key and icon URL as value.
  //
  // If a context is specified (function is called from an extension):
  // Throws an error if an invalid icon size was provided or the
  // extension is not allowed to load the specified resources.
  //
  // If no context is specified, instead of throwing an error, this
  // function simply logs a warning message.
  normalize(details, extension, context = null) {
    if (!details.imageData && typeof details.path === "string") {
      let icons = this.iconCache.get(extension);

      let baseURI = context ? context.uri : extension.baseURI;
      let url = baseURI.resolve(details.path);

      let icon = icons.get(url);
      if (!icon) {
        icon = this._normalize(details, extension, context);
        icons.set(url, icon);
      }
      return icon;
    }

    return this._normalize(details, extension, context);
  },

  _normalize(details, extension, context = null) {
    let result = {};

    try {
      let {imageData, path, themeIcons} = details;

      if (imageData) {
        if (typeof imageData == "string") {
          imageData = {"19": imageData};
        }

        for (let size of Object.keys(imageData)) {
          if (!INTEGER.test(size)) {
            throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
          }
          result[size] = imageData[size];
        }
      }

      let baseURI = context ? context.uri : extension.baseURI;

      if (path) {
        if (typeof path != "object") {
          path = {"19": path};
        }

        for (let size of Object.keys(path)) {
          if (!INTEGER.test(size)) {
            throw new ExtensionError(`Invalid icon size ${size}, must be an integer`);
          }

          let url = baseURI.resolve(path[size]);

          // The Chrome documentation specifies these parameters as
          // relative paths. We currently accept absolute URLs as well,
          // which means we need to check that the extension is allowed
          // to load them. This will throw an error if it's not allowed.
          this._checkURL(url, extension.principal);

          result[size] = url;
        }
      }

      if (themeIcons) {
        themeIcons.forEach(({size, light, dark}) => {
          let lightURL = baseURI.resolve(light);
          let darkURL = baseURI.resolve(dark);

          this._checkURL(lightURL, extension.principal);
          this._checkURL(darkURL, extension.principal);

          let defaultURL = result[size];
          result[size] = {
            "default": defaultURL || lightURL, // Fallback to the light url if no default is specified.
            "light": lightURL,
            "dark": darkURL,
          };
        });
      }
    } catch (e) {
      // Function is called from extension code, delegate error.
      if (context) {
        throw e;
      }
      // If there's no context, it's because we're handling this
      // as a manifest directive. Log a warning rather than
      // raising an error.
      extension.manifestError(`Invalid icon data: ${e}`);
    }

    return result;
  },

  // Checks if the extension is allowed to load the given URL with the specified principal.
  // This will throw an error if the URL is not allowed.
  _checkURL(url, principal) {
    try {
      Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
        principal, url,
        Services.scriptSecurityManager.DISALLOW_SCRIPT);
    } catch (e) {
      throw new ExtensionError(`Illegal URL ${url}`);
    }
  },

  // Returns the appropriate icon URL for the given icons object and the
  // screen resolution of the given window.
  getPreferredIcon(icons, extension = null, size = 16) {
    const DEFAULT = "chrome://browser/content/extension.svg";

    let bestSize = null;
    if (icons[size]) {
      bestSize = size;
    } else if (icons[2 * size]) {
      bestSize = 2 * size;
    } else {
      let sizes = Object.keys(icons)
                        .map(key => parseInt(key, 10))
                        .sort((a, b) => a - b);

      bestSize = sizes.find(candidate => candidate > size) || sizes.pop();
    }

    if (bestSize) {
      return {size: bestSize, icon: icons[bestSize]};
    }

    return {size, icon: DEFAULT};
  },

  convertImageURLToDataURL(imageURL, contentWindow, browserWindow, size = 18) {
    return new Promise((resolve, reject) => {
      let image = new contentWindow.Image();
      image.onload = function() {
        let canvas = contentWindow.document.createElement("canvas");
        let ctx = canvas.getContext("2d");
        let dSize = size * browserWindow.devicePixelRatio;

        // Scales the image while maintaing width to height ratio.
        // If the width and height differ, the image is centered using the
        // smaller of the two dimensions.
        let dWidth, dHeight, dx, dy;
        if (this.width > this.height) {
          dWidth = dSize;
          dHeight = image.height * (dSize / image.width);
          dx = 0;
          dy = (dSize - dHeight) / 2;
        } else {
          dWidth = image.width * (dSize / image.height);
          dHeight = dSize;
          dx = (dSize - dWidth) / 2;
          dy = 0;
        }

        canvas.width = dSize;
        canvas.height = dSize;
        ctx.drawImage(this, 0, 0, this.width, this.height, dx, dy, dWidth, dHeight);
        resolve(canvas.toDataURL("image/png"));
      };
      image.onerror = reject;
      image.src = imageURL;
    });
  },

  // 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.
  escapeUrl(url) {
    return url.replace(/[\\\s"]/g, encodeURIComponent);
  },
};

let StartupCache = {
  DB_NAME: "ExtensionStartupCache",

  STORE_NAMES: Object.freeze(["locales", "manifests", "permissions", "schemas"]),

  get file() {
    return FileUtils.getFile("ProfLD", ["startupCache", "webext.sc.lz4"]);
  },

  get saver() {
    if (!this._saver) {
      this._saver = new DeferredSave(this.file.path,
                                     () => this.getBlob(),
                                     {delay: 5000});
    }
    return this._saver;
  },

  async save() {
    return this.saver.saveChanges();
  },

  getBlob() {
    return new Uint8Array(aomStartup.encodeBlob(this._data));
  },

  _data: null,
  async _readData() {
    let result = new Map();
    try {
      let data = await promiseFileContents(this.file);

      result = aomStartup.decodeBlob(data);
    } catch (e) {
      if (!e.becauseNoSuchFile) {
        Cu.reportError(e);
      }
    }

    this._data = result;
    return result;
  },

  get dataPromise() {
    if (!this._dataPromise) {
      this._dataPromise = this._readData();
    }
    return this._dataPromise;
  },

  clearAddonData(id) {
    return Promise.all([
      this.locales.delete(id),
      this.manifests.delete(id),
      this.permissions.delete(id),
    ]).catch(e => {
      // Ignore the error. It happens when we try to flush the add-on
      // data after the AddonManager has flushed the entire startup cache.
    });
  },

  observe(subject, topic, data) {
    if (topic === "startupcache-invalidate") {
      this._data = new Map();
      this._dataPromise = Promise.resolve(this._data);
    }
  },
};

// void StartupCache.dataPromise;

Services.obs.addObserver(StartupCache, "startupcache-invalidate");

class CacheStore {
  constructor(storeName) {
    this.storeName = storeName;
  }

  async getStore(path = null) {
    let data = await StartupCache.dataPromise;

    let store = data.get(this.storeName);
    if (!store) {
      store = new Map();
      data.set(this.storeName, store);
    }

    let key = path;
    if (Array.isArray(path)) {
      for (let elem of path.slice(0, -1)) {
        let next = store.get(elem);
        if (!next) {
          next = new Map();
          store.set(elem, next);
        }
        store = next;
      }
      key = path[path.length - 1];
    }

    return [store, key];
  }

  async get(path, createFunc) {
    let [store, key] = await this.getStore(path);

    let result = store.get(key);

    if (result === undefined) {
      result = await createFunc(path);
      store.set(key, result);
      StartupCache.save();
    }

    return result;
  }

  async getAll() {
    let [store] = await this.getStore();

    return new Map(store);
  }

  async delete(path) {
    let [store, key] = await this.getStore(path);

    store.delete(key);
    StartupCache.save();
  }
}

for (let name of StartupCache.STORE_NAMES) {
  StartupCache[name] = new CacheStore(name);
}

var ExtensionParent = {
  extensionNameFromURI,
  GlobalManager,
  HiddenExtensionPage,
  IconDetails,
  ParentAPIManager,
  StartupCache,
  WebExtensionPolicy,
  apiManager,
  get baseManifestProperties() {
    if (gBaseManifestProperties) {
      return gBaseManifestProperties;
    }

    let types = Schemas.schemaJSON.get(BASE_SCHEMA).deserialize({})[0].types;
    let manifest = types.find(type => type.id === "WebExtensionManifest");
    if (!manifest) {
      throw new Error("Unable to find base manifest properties");
    }

    gBaseManifestProperties = Object.getOwnPropertyNames(manifest.properties);
    return gBaseManifestProperties;
  },
  promiseExtensionViewLoaded,
  watchExtensionProxyContextLoad,
  DebugUtils,
};

XPCOMUtils.defineLazyGetter(ExtensionParent, "PlatformInfo", () => {
  return Object.freeze({
    os: (function() {
      let os = AppConstants.platform;
      if (os == "macosx") {
        os = "mac";
      }
      return os;
    })(),
    arch: (function() {
      let abi = Services.appinfo.XPCOMABI;
      let [arch] = abi.split("-");
      if (arch == "x86") {
        arch = "x86-32";
      } else if (arch == "x86_64") {
        arch = "x86-64";
      }
      return arch;
    })(),
  });
});

/**
 * Retreives the browser_style stylesheets needed for extension popups and sidebars.
 * @returns {Array<string>} an array of stylesheets needed for the current platform.
 */
XPCOMUtils.defineLazyGetter(ExtensionParent, "extensionStylesheets", () => {
  let stylesheets = ["chrome://browser/content/extension.css"];

  if (AppConstants.platform === "macosx") {
    stylesheets.push("chrome://browser/content/extension-mac.css");
  }

  return stylesheets;
});
PK
!<XVoG modules/ExtensionPermissions.jsm"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                  "resource://gre/modules/ExtensionParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                  "resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyGetter(this, "StartupCache", () => ExtensionParent.StartupCache);

this.EXPORTED_SYMBOLS = ["ExtensionPermissions"];

const FILE_NAME = "extension-preferences.json";

let prefs;
let _initPromise;

async function _lazyInit() {
  let file = FileUtils.getFile("ProfD", [FILE_NAME]);

  prefs = new JSONFile({path: file.path});
  prefs.data = {};

  try {
    let blob = await ExtensionUtils.promiseFileContents(file);
    prefs.data = JSON.parse(new TextDecoder().decode(blob));
  } catch (e) {
    if (!e.becauseNoSuchFile) {
      Cu.reportError(e);
    }
  }
}

function lazyInit() {
  if (!_initPromise) {
    _initPromise = _lazyInit();
  }
  return _initPromise;
}

function emptyPermissions() {
  return {permissions: [], origins: []};
}

this.ExtensionPermissions = {
  async _saveSoon(extension) {
    await lazyInit();

    prefs.data[extension.id] = await this._getCached(extension);
    return prefs.saveSoon();
  },

  async _get(extension) {
    await lazyInit();

    let perms = prefs.data[extension.id];
    if (!perms) {
      perms = emptyPermissions();
      prefs.data[extension.id] = perms;
    }

    return perms;
  },

  async _getCached(extension) {
    return StartupCache.permissions.get(extension.id,
                                        () => this._get(extension));
  },

  get(extension) {
    return this._getCached(extension);
  },

  // Add new permissions for the given extension.  `permissions` is
  // in the format that is passed to browser.permissions.request().
  async add(extension, perms) {
    let {permissions, origins} = await this._getCached(extension);

    let added = emptyPermissions();

    for (let perm of perms.permissions) {
      if (!permissions.includes(perm)) {
        added.permissions.push(perm);
        permissions.push(perm);
      }
    }

    for (let origin of perms.origins) {
      origin = new MatchPattern(origin, {ignorePath: true}).pattern;
      if (!origins.includes(origin)) {
        added.origins.push(origin);
        origins.push(origin);
      }
    }

    if (added.permissions.length > 0 || added.origins.length > 0) {
      this._saveSoon(extension);
      extension.emit("add-permissions", added);
    }
  },

  // Revoke permissions from the given extension.  `permissions` is
  // in the format that is passed to browser.permissions.remove().
  async remove(extension, perms) {
    let {permissions, origins} = await this._getCached(extension);

    let removed = emptyPermissions();

    for (let perm of perms.permissions) {
      let i = permissions.indexOf(perm);
      if (i >= 0) {
        removed.permissions.push(perm);
        permissions.splice(i, 1);
      }
    }

    for (let origin of perms.origins) {
      origin = new MatchPattern(origin, {ignorePath: true}).pattern;

      let i = origins.indexOf(origin);
      if (i >= 0) {
        removed.origins.push(origin);
        origins.splice(i, 1);
      }
    }

    if (removed.permissions.length > 0 || removed.origins.length > 0) {
      this._saveSoon(extension);
      extension.emit("remove-permissions", removed);
    }
  },

  async removeAll(extension) {
    let perms = await this._getCached(extension);

    if (perms.permissions.length || perms.origins.length) {
      Object.assign(perms, emptyPermissions());
      prefs.saveSoon();
    }
  },

  // This is meant for tests only
  async _uninit() {
    if (!_initPromise) {
      return;
    }

    await _initPromise;
    await prefs.finalize();
    prefs = null;
    _initPromise = null;
  },
};
PK
!<3Ф**'modules/ExtensionPreferencesManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @fileOverview
 * This module is used for managing preferences from WebExtension APIs.
 * It takes care of the precedence chain and decides whether a preference
 * needs to be updated when a change is requested by an API.
 *
 * It deals with preferences via settings objects, which are objects with
 * the following properties:
 *
 * prefNames:   An array of strings, each of which is a preference on
 *              which the setting depends.
 * setCallback: A function that returns an object containing properties and
 *              values that correspond to the prefs to be set.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["ExtensionPreferencesManager"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
                                  "resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");

XPCOMUtils.defineLazyGetter(this, "defaultPreferences", function() {
  return new Preferences({defaultBranch: true});
});

/* eslint-disable mozilla/balanced-listeners */
Management.on("shutdown", (type, extension) => {
  switch (extension.shutdownReason) {
    case "ADDON_DISABLE":
    case "ADDON_DOWNGRADE":
    case "ADDON_UPGRADE":
      this.ExtensionPreferencesManager.disableAll(extension);
      break;

    case "ADDON_UNINSTALL":
      this.ExtensionPreferencesManager.removeAll(extension);
      break;
  }
});

Management.on("startup", (type, extension) => {
  if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(extension.startupReason)) {
    this.ExtensionPreferencesManager.enableAll(extension);
  }
});
/* eslint-enable mozilla/balanced-listeners */

const STORE_TYPE = "prefs";

// Definitions of settings, each of which correspond to a different API.
let settingsMap = new Map();

/**
 * This function is passed into the ExtensionSettingsStore to determine the
 * initial value of the setting. It reads an array of preference names from
 * the this scope, which gets bound to a settings object.
 *
 * @returns {Object}
 *          An object with one property per preference, which holds the current
 *          value of that preference.
 */
function initialValueCallback() {
  let initialValue = {};
  for (let pref of this.prefNames) {
    initialValue[pref] = Preferences.get(pref);
  }
  return initialValue;
}

/**
 * Loops through a set of prefs, either setting or resetting them.
 *
 * @param {Object} setting
 *        An object that represents a setting, which will have a setCallback
 *        property.
 * @param {Object} item
 *        An object that represents an item handed back from the setting store
 *        from which the new pref values can be calculated.
*/
function setPrefs(setting, item) {
  let prefs = item.initialValue || setting.setCallback(item.value);
  for (let pref in prefs) {
    if (prefs[pref] === undefined) {
      Preferences.reset(pref);
    } else {
      Preferences.set(pref, prefs[pref]);
    }
  }
}

/**
 * Commits a change to a setting and conditionally sets preferences.
 *
 * If the change to the setting causes a different extension to gain
 * control of the pref (or removes all extensions with control over the pref)
 * then the prefs should be updated, otherwise they should not be.
 * In addition, if the current value of any of the prefs does not
 * match what we expect the value to be (which could be the result of a
 * user manually changing the pref value), then we do not change any
 * of the prefs.
 *
 * @param {Extension} extension
 *        The extension for which a setting is being modified.
 * @param {string} name
 *        The name of the setting being processed.
 * @param {string} action
 *        The action that is being performed. Will be one of disable, enable
 *        or removeSetting.

 * @returns {Promise}
 *          Resolves to true if preferences were set as a result and to false
 *          if preferences were not set.
*/
async function processSetting(extension, name, action) {
  await ExtensionSettingsStore.initialize();
  let expectedItem = ExtensionSettingsStore.getSetting(STORE_TYPE, name);
  let item = ExtensionSettingsStore[action](extension, STORE_TYPE, name);
  if (item) {
    let setting = settingsMap.get(name);
    let expectedPrefs = expectedItem.initialValue
      || setting.setCallback(expectedItem.value);
    if (Object.keys(expectedPrefs).some(
        pref => expectedPrefs[pref] && Preferences.get(pref) != expectedPrefs[pref])) {
      return false;
    }
    setPrefs(setting, item);
    return true;
  }
  return false;
}

this.ExtensionPreferencesManager = {
  /**
   * Adds a setting to the settingsMap. This is how an API tells the
   * preferences manager what its setting object is. The preferences
   * manager needs to know this when settings need to be removed
   * automatically.
   *
   * @param {string} name The unique id of the setting.
   * @param {Object} setting
   *        A setting object that should have properties for
   *        prefNames, getCallback and setCallback.
   */
  addSetting(name, setting) {
    settingsMap.set(name, setting);
  },

  /**
   * Gets the default value for a preference.
   *
   * @param {string} prefName The name of the preference.
   *
   * @returns {string|number|boolean} The default value of the preference.
   */
  getDefaultValue(prefName) {
    return defaultPreferences.get(prefName);
  },

  /**
   * Indicates that an extension would like to change the value of a previously
   * defined setting.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being set.
   * @param {string} name
   *        The unique id of the setting.
   * @param {any} value
   *        The value to be stored in the settings store for this
   *        group of preferences.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  async setSetting(extension, name, value) {
    let setting = settingsMap.get(name);
    await ExtensionSettingsStore.initialize();
    let item = await ExtensionSettingsStore.addSetting(
      extension, STORE_TYPE, name, value, initialValueCallback.bind(setting));
    if (item) {
      setPrefs(setting, item);
      return true;
    }
    return false;
  },

  /**
   * Indicates that this extension wants to temporarily cede control over the
   * given setting.
   *
   * @param {Extension} extension
   *        The extension for which a preference setting is being removed.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  disableSetting(extension, name) {
    return processSetting(extension, name, "disable");
  },

  /**
   * Enable a setting that has been disabled.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being enabled.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  enableSetting(extension, name) {
    return processSetting(extension, name, "enable");
  },

  /**
   * Indicates that this extension no longer wants to set the given setting.
   *
   * @param {Extension} extension
   *        The extension for which a preference setting is being removed.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  removeSetting(extension, name) {
    return processSetting(extension, name, "removeSetting");
  },

  /**
   * Disables all previously set settings for an extension. This can be called when
   * an extension is being disabled, for example.
   *
   * @param {Extension} extension
   *        The extension for which all settings are being unset.
   */
  async disableAll(extension) {
    await ExtensionSettingsStore.initialize();
    let settings = ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
    let disablePromises = [];
    for (let name of settings) {
      disablePromises.push(this.disableSetting(extension, name));
    }
    await Promise.all(disablePromises);
  },

  /**
   * Enables all disabled settings for an extension. This can be called when
   * an extension has finsihed updating or is being re-enabled, for example.
   *
   * @param {Extension} extension
   *        The extension for which all settings are being enabled.
   */
  async enableAll(extension) {
    await ExtensionSettingsStore.initialize();
    let settings = ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
    let enablePromises = [];
    for (let name of settings) {
      enablePromises.push(this.enableSetting(extension, name));
    }
    await Promise.all(enablePromises);
  },

  /**
   * Removes all previously set settings for an extension. This can be called when
   * an extension is being uninstalled, for example.
   *
   * @param {Extension} extension
   *        The extension for which all settings are being unset.
   */
  async removeAll(extension) {
    await ExtensionSettingsStore.initialize();
    let settings = ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
    let removePromises = [];
    for (let name of settings) {
      removePromises.push(this.removeSetting(extension, name));
    }
    await Promise.all(removePromises);
  },

  /**
   * Return the levelOfControl for a setting / extension combo.
   * This queries the levelOfControl from the ExtensionSettingsStore and also
   * takes into account whether any of the setting's preferences are locked.
   *
   * @param {Extension} extension
   *        The extension for which levelOfControl is being requested.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to the level of control of the extension over the setting.
   */
  async getLevelOfControl(extension, name) {
    for (let prefName of settingsMap.get(name).prefNames) {
      if (Preferences.locked(prefName)) {
        return "not_controllable";
      }
    }
    await ExtensionSettingsStore.initialize();
    return ExtensionSettingsStore.getLevelOfControl(extension, STORE_TYPE, name);
  },
};
PK
!<6''"modules/ExtensionSearchHandler.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "ExtensionSearchHandler" ];

// Used to keep track of all of the registered keywords, where each keyword is
// mapped to a KeywordInfo instance.
let gKeywordMap = new Map();

// Used to keep track of the active input session.
let gActiveInputSession = null;

// Used to keep track of who has control over the active suggestion callback
// so older callbacks can be ignored. The callback ID should increment whenever
// the input changes or the input session ends.
let gCurrentCallbackID = 0;

// Handles keeping track of information associated to the registered keyword.
class KeywordInfo {
  constructor(extension, description) {
    this._extension = extension;
    this._description = description;
  }

  get description() {
    return this._description;
  }

  set description(desc) {
    this._description = desc;
  }

  get extension() {
    return this._extension;
  }
}

// Responsible for handling communication between the extension and the urlbar.
class InputSession {
  constructor(keyword, extension) {
    this._keyword = keyword;
    this._extension = extension;
    this._suggestionsCallback = null;
    this._searchFinishedCallback = null;
  }

  get keyword() {
    return this._keyword;
  }

  addSuggestions(suggestions) {
    if (this._suggestionsCallback) {
      this._suggestionsCallback(suggestions);
    }
  }

  start(eventName) {
    this._extension.emit(eventName);
  }

  update(eventName, text, suggestionsCallback, searchFinishedCallback) {
    // Check to see if an existing input session needs to be ended first.
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
    }
    this._searchFinishedCallback = searchFinishedCallback;
    this._suggestionsCallback = suggestionsCallback;
    this._extension.emit(eventName, text, ++gCurrentCallbackID);
  }

  cancel(eventName) {
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
      this._searchFinishedCallback = null;
      this._suggestionsCallback = null;
    }
    this._extension.emit(eventName);
  }

  end(eventName, text, disposition) {
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
      this._searchFinishedCallback = null;
      this._suggestionsCallback = null;
    }
    this._extension.emit(eventName, text, disposition);
  }
}

var ExtensionSearchHandler = Object.freeze({
  MSG_INPUT_STARTED: "webext-omnibox-input-started",
  MSG_INPUT_CHANGED: "webext-omnibox-input-changed",
  MSG_INPUT_ENTERED: "webext-omnibox-input-entered",
  MSG_INPUT_CANCELLED: "webext-omnibox-input-cancelled",

  /**
   * Registers a keyword.
   *
   * @param {string} keyword The keyword to register.
   * @param {Extension} extension The extension registering the keyword.
   */
  registerKeyword(keyword, extension) {
    if (gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is already registered: "${keyword}"`);
    }
    gKeywordMap.set(keyword, new KeywordInfo(extension, extension.name));
  },

  /**
   * Unregisters a keyword.
   *
   * @param {string} keyword The keyword to unregister.
   */
  unregisterKeyword(keyword) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    gActiveInputSession = null;
    gKeywordMap.delete(keyword);
  },

  /**
   * Checks if a keyword is registered.
   *
   * @param {string} keyword The word to check.
   * @return {boolean} true if the word is a registered keyword.
   */
  isKeywordRegistered(keyword) {
    return gKeywordMap.has(keyword);
  },

  /**
   * @return {boolean} true if there is an active input session.
   */
  hasActiveInputSession() {
    return gActiveInputSession != null;
  },

  /**
   * @param {string} keyword The keyword to look up.
   * @return {string} the description to use for the heuristic result.
   */
  getDescription(keyword) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    return gKeywordMap.get(keyword).description;
  },

  /**
   * Sets the default suggestion for the registered keyword. The suggestion's
   * description will be used for the comment in the heuristic result.
   *
   * @param {string} keyword The keyword.
   * @param {string} description The description to use for the heuristic result.
   */
  setDefaultSuggestion(keyword, {description}) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    gKeywordMap.get(keyword).description = description;
  },

  /**
   * Adds suggestions for the registered keyword. This function will throw if
   * the keyword provided is not registered or active, or if the callback ID
   * provided is no longer equal to the active callback ID.
   *
   * @param {string} keyword The keyword.
   * @param {integer} id The ID of the suggestion callback.
   * @param {Array<Object>} suggestions An array of suggestions to provide to the urlbar.
   */
  addSuggestions(keyword, id, suggestions) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    if (!gActiveInputSession || gActiveInputSession.keyword != keyword) {
      throw new Error(`The keyword provided is not apart of an active input session: "${keyword}"`);
    }

    if (id != gCurrentCallbackID) {
      throw new Error(`The callback is no longer active for the keyword provided: "${keyword}"`);
    }

    gActiveInputSession.addSuggestions(suggestions);
  },

  /**
   * Called when the input in the urlbar begins with `<keyword><space>`.
   *
   * If the keyword is inactive, MSG_INPUT_STARTED is emitted and the
   * keyword is marked as active. If the keyword is followed by any text,
   * MSG_INPUT_CHANGED is fired with the current callback ID that can be
   * used to provide suggestions to the urlbar while the callback ID is active.
   * The callback is invalidated when either the input changes or the urlbar blurs.
   *
   * @param {string} keyword The keyword to handle.
   * @param {string} text The search text in the urlbar.
   * @param {Function} callback The callback used to provide search suggestions.
   * @return {Promise} promise that resolves when the current search is complete.
   */
  handleSearch(keyword, text, callback) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
      throw new Error("A different input session is already ongoing");
    }

    if (!text || !text.startsWith(`${keyword} `)) {
      throw new Error(`The text provided must start with: "${keyword} "`);
    }

    if (!callback) {
      throw new Error("A callback must be provided");
    }

    // The search text in the urlbar currently starts with <keyword><space>, and
    // we only want the text that follows.
    text = text.substring(keyword.length + 1);

    // We fire MSG_INPUT_STARTED once we have <keyword><space>, and only fire
    // MSG_INPUT_CHANGED when we have text to process. This is different from Chrome's
    // behavior, which always fires MSG_INPUT_STARTED right before MSG_INPUT_CHANGED
    // first fires, but this is a bug in Chrome according to https://crbug.com/258911.
    if (!gActiveInputSession) {
      gActiveInputSession = new InputSession(keyword, gKeywordMap.get(keyword).extension);
      gActiveInputSession.start(this.MSG_INPUT_STARTED);

      // Resolve early if there is no text to process. There can be text to process when
      // the input starts if the user copy/pastes the text into the urlbar.
      if (!text.length) {
        return Promise.resolve();
      }
    }

    return new Promise(resolve => {
      gActiveInputSession.update(this.MSG_INPUT_CHANGED, text, callback, resolve);
    });
  },

  /**
   * Called when the user clicks on a suggestion that was added by
   * an extension. MSG_INPUT_ENTERED is emitted to the extension with
   * the keyword, the current search string, and info about how the
   * the search should be handled. This ends the active input session.
   *
   * @param {string} keyword The keyword associated to the suggestion.
   * @param {string} text The search text in the urlbar.
   * @param {string} where How the page should be opened. Accepted values are:
   *    "current": open the page in the same tab.
   *    "tab": open the page in a new foreground tab.
   *    "tabshifted": open the page in a new background tab.
   */
  handleInputEntered(keyword, text, where) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
      throw new Error("A different input session is already ongoing");
    }

    if (!text || !text.startsWith(`${keyword} `)) {
      throw new Error(`The text provided must start with: "${keyword} "`);
    }

    let dispositionMap = {
      current: "currentTab",
      tab: "newForegroundTab",
      tabshifted: "newBackgroundTab",
    }
    let disposition = dispositionMap[where];

    if (!disposition) {
      throw new Error(`Invalid "where" argument: ${where}`);
    }

    // The search text in the urlbar currently starts with <keyword><space>, and
    // we only want to send the text that follows.
    text = text.substring(keyword.length + 1);

    gActiveInputSession.end(this.MSG_INPUT_ENTERED, text, disposition)
    gActiveInputSession = null;
  },

  /**
   * If the user has ended the keyword input session without accepting the input,
   * MSG_INPUT_CANCELLED is emitted and the input session is ended.
   */
  handleInputCancelled() {
    if (!gActiveInputSession) {
      throw new Error("There is no active input session");
    }
    gActiveInputSession.cancel(this.MSG_INPUT_CANCELLED);
    gActiveInputSession = null;
  }
});
PK
!<1::"modules/ExtensionSettingsStore.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @fileOverview
 * This module is used for storing changes to settings that are
 * requested by extensions, and for finding out what the current value
 * of a setting should be, based on the precedence chain.
 *
 * When multiple extensions request to make a change to a particular
 * setting, the most recently installed extension will be given
 * precedence.
 *
 * This precedence chain of settings is stored in JSON format,
 * without indentation, using UTF-8 encoding.
 * With indentation applied, the file would look like this:
 *
 * {
 *   type: { // The type of settings being stored in this object, i.e., prefs.
 *     key: { // The unique key for the setting.
 *       initialValue, // The initial value of the setting.
 *       precedenceList: [
 *         {
 *           id, // The id of the extension requesting the setting.
 *           installDate, // The install date of the extension, stored as a number.
 *           value, // The value of the setting requested by the extension.
 *           enabled // Whether the setting is currently enabled.
 *         }
 *       ],
 *     },
 *     key: {
 *       // ...
 *     }
 *   }
 * }
 *
 */

"use strict";

this.EXPORTED_SYMBOLS = ["ExtensionSettingsStore"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");

const JSON_FILE_NAME = "extension-settings.json";
const JSON_FILE_VERSION = 2;
const STORE_PATH = OS.Path.join(Services.dirsvc.get("ProfD", Ci.nsIFile).path, JSON_FILE_NAME);

let _initializePromise;
let _store = {};

// Processes the JSON data when read from disk to convert string dates into numbers.
function dataPostProcessor(json) {
  if (json.version !== JSON_FILE_VERSION) {
    for (let storeType in json) {
      for (let setting in json[storeType]) {
        for (let extData of json[storeType][setting].precedenceList) {
          if (typeof extData.installDate != "number") {
            extData.installDate = new Date(extData.installDate).valueOf();
          }
        }
      }
    }
    json.version = JSON_FILE_VERSION;
  }
  return json;
}

// Loads the data from the JSON file into memory.
function initialize() {
  if (!_initializePromise) {
    _store = new JSONFile({
      path: STORE_PATH,
      dataPostProcessor,
    });
    _initializePromise = _store.load();
  }
  return _initializePromise;
}

// Test-only method to force reloading of the JSON file.
async function reloadFile() {
  await _store.finalize();
  _initializePromise = null;
  return initialize();
}

// Checks that the store is ready and that the requested type exists.
function ensureType(type) {
  if (!_store.dataReady) {
    throw new Error(
      "The ExtensionSettingsStore was accessed before the initialize promise resolved.");
  }

  // Ensure a property exists for the given type.
  if (!_store.data[type]) {
    _store.data[type] = {};
  }
}

// Return an object with properties for key and value|initialValue, or null
// if no setting has been stored for that key.
function getTopItem(type, key) {
  ensureType(type);

  let keyInfo = _store.data[type][key];
  if (!keyInfo) {
    return null;
  }

  // Find the highest precedence, enabled setting.
  for (let item of keyInfo.precedenceList) {
    if (item.enabled) {
      return {key, value: item.value};
    }
  }

  // Nothing found in the precedenceList, return the initialValue.
  return {key, initialValue: keyInfo.initialValue};
}

// Comparator used when sorting the precedence list.
function precedenceComparator(a, b) {
  if (a.enabled && !b.enabled) {
    return -1;
  }
  if (b.enabled && !a.enabled) {
    return 1;
  }
  return b.installDate - a.installDate;
}

/**
 * Helper method that alters a setting, either by changing its enabled status
 * or by removing it.
 *
 * @param {Extension} extension
 *        The extension for which a setting is being removed/disabled.
 * @param {string} type
 *        The type of setting to be altered.
 * @param {string} key
 *        A string that uniquely identifies the setting.
 * @param {string} action
 *        The action to perform on the setting.
 *        Will be one of remove|enable|disable.
 *
 * @returns {object | null}
 *          Either an object with properties for key and value, which
 *          corresponds to the current top precedent setting, or null if
 *          the current top precedent setting has not changed.
 */
function alterSetting(extension, type, key, action) {
  let returnItem;
  ensureType(type);

  let keyInfo = _store.data[type][key];
  if (!keyInfo) {
    if (action === "remove") {
      return null;
    }
    throw new Error(
      `Cannot alter the setting for ${type}:${key} as it does not exist.`);
  }

  let id = extension.id;
  let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);

  if (foundIndex === -1) {
    if (action === "remove") {
      return null;
    }
    throw new Error(
      `Cannot alter the setting for ${type}:${key} as it does not exist.`);
  }

  switch (action) {
    case "remove":
      keyInfo.precedenceList.splice(foundIndex, 1);
      break;

    case "enable":
      keyInfo.precedenceList[foundIndex].enabled = true;
      keyInfo.precedenceList.sort(precedenceComparator);
      foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
      break;

    case "disable":
      keyInfo.precedenceList[foundIndex].enabled = false;
      keyInfo.precedenceList.sort(precedenceComparator);
      break;

    default:
      throw new Error(`${action} is not a valid action for alterSetting.`);
  }

  if (foundIndex === 0) {
    returnItem = getTopItem(type, key);
  }

  if (action === "remove" && keyInfo.precedenceList.length === 0) {
    delete _store.data[type][key];
  }

  _store.saveSoon();

  return returnItem;
}

this.ExtensionSettingsStore = {
  /**
   * Loads the JSON file for the SettingsStore into memory.
   * The promise this returns must be resolved before asking the SettingsStore
   * to perform any other operations.
   *
   * @returns {Promise}
   *          A promise that resolves when the Store is ready to be accessed.
   */
  initialize() {
    return initialize();
  },

  /**
   * Adds a setting to the store, possibly returning the current top precedent
   * setting.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being added.
   * @param {string} type
   *        The type of setting to be stored.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   * @param {string} value
   *        The value to be stored in the setting.
   * @param {function} initialValueCallback
   *        An function to be called to determine the initial value for the
   *        setting. This will be passed the value in the callbackArgument
   *        argument.
   * @param {any} callbackArgument
   *        The value to be passed into the initialValueCallback. It defaults to
   *        the value of the key argument.
   *
   * @returns {object | null} Either an object with properties for key and
   *                          value, which corresponds to the item that was
   *                          just added, or null if the item that was just
   *                          added does not need to be set because it is not
   *                          at the top of the precedence list.
   */
  async addSetting(extension, type, key, value, initialValueCallback, callbackArgument = key) {
    if (typeof initialValueCallback != "function") {
      throw new Error("initialValueCallback must be a function.");
    }

    let id = extension.id;
    ensureType(type);

    if (!_store.data[type][key]) {
      // The setting for this key does not exist. Set the initial value.
      let initialValue = await initialValueCallback(callbackArgument);
      _store.data[type][key] = {
        initialValue,
        precedenceList: [],
      };
    }
    let keyInfo = _store.data[type][key];
    // Check for this item in the precedenceList.
    let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
    if (foundIndex === -1) {
      // No item for this extension, so add a new one.
      let addon = await AddonManager.getAddonByID(id);
      keyInfo.precedenceList.push(
        {id, installDate: addon.installDate.valueOf(), value, enabled: true});
    } else {
      // Item already exists or this extension, so update it.
      keyInfo.precedenceList[foundIndex].value = value;
    }

    // Sort the list.
    keyInfo.precedenceList.sort(precedenceComparator);

    _store.saveSoon();

    // Check whether this is currently the top item.
    if (keyInfo.precedenceList[0].id == id) {
      return {key, value};
    }
    return null;
  },

  /**
   * Removes a setting from the store, possibly returning the current top
   * precedent setting.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being removed.
   * @param {string} type
   *        The type of setting to be removed.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value, which
   *          corresponds to the current top precedent setting, or null if
   *          the current top precedent setting has not changed.
   */
  removeSetting(extension, type, key) {
    return alterSetting(extension, type, key, "remove");
  },

  /**
   * Enables a setting in the store, possibly returning the current top
   * precedent setting.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being enabled.
   * @param {string} type
   *        The type of setting to be enabled.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value, which
   *          corresponds to the current top precedent setting, or null if
   *          the current top precedent setting has not changed.
   */
  enable(extension, type, key) {
    return alterSetting(extension, type, key, "enable");
  },

  /**
   * Disables a setting in the store, possibly returning the current top
   * precedent setting.
   *
   * @param {Extension} extension
   *        The extension for which a setting is being disabled.
   * @param {string} type
   *        The type of setting to be disabled.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value, which
   *          corresponds to the current top precedent setting, or null if
   *          the current top precedent setting has not changed.
   */
  disable(extension, type, key) {
    return alterSetting(extension, type, key, "disable");
  },

  /**
   * Retrieves all settings from the store for a given extension.
   *
   * @param {Extension} extension The extension for which a settings are being retrieved.
   * @param {string} type The type of setting to be returned.
   *
   * @returns {array} A list of settings which have been stored for the extension.
   */
  getAllForExtension(extension, type) {
    ensureType(type);

    let keysObj = _store.data[type];
    let items = [];
    for (let key in keysObj) {
      if (keysObj[key].precedenceList.find(item => item.id == extension.id)) {
        items.push(key);
      }
    }
    return items;
  },

  /**
   * Retrieves a setting from the store, returning the current top precedent
   * setting for the key.
   *
   * @param {string} type The type of setting to be returned.
   * @param {string} key A string that uniquely identifies the setting.
   *
   * @returns {object} An object with properties for key and value.
   */
  getSetting(type, key) {
    return getTopItem(type, key);
  },

  /**
   * Returns whether an extension currently has a stored setting for a given
   * key.
   *
   * @param {Extension} extension The extension which is being checked.
   * @param {string} type The type of setting to be checked.
   * @param {string} key A string that uniquely identifies the setting.
   *
   * @returns {boolean} Whether the extension currently has a stored setting.
   */
  hasSetting(extension, type, key) {
    return this.getAllForExtension(extension, type).includes(key);
  },

  /**
   * Return the levelOfControl for a key / extension combo.
   * levelOfControl is required by Google's ChromeSetting prototype which
   * in turn is used by the privacy API among others.
   *
   * It informs a caller of the state of a setting with respect to the current
   * extension, and can be one of the following values:
   *
   * controlled_by_other_extensions: controlled by extensions with higher precedence
   * controllable_by_this_extension: can be controlled by this extension
   * controlled_by_this_extension: controlled by this extension
   *
   * @param {Extension} extension
   *        The extension for which levelOfControl is being requested.
   * @param {string} type
   *        The type of setting to be returned. For example `pref`.
   * @param {string} key
   *        A string that uniquely identifies the setting, for example, a
   *        preference name.
   *
   * @returns {string}
   *          The level of control of the extension over the key.
   */
  async getLevelOfControl(extension, type, key) {
    ensureType(type);

    let keyInfo = _store.data[type][key];
    if (!keyInfo || !keyInfo.precedenceList.length) {
      return "controllable_by_this_extension";
    }

    let id = extension.id;
    let enabledItems = keyInfo.precedenceList.filter(item => item.enabled);
    if (!enabledItems.length) {
      return "controllable_by_this_extension";
    }

    let topItem = enabledItems[0];
    if (topItem.id == id) {
      return "controlled_by_this_extension";
    }

    let addon = await AddonManager.getAddonByID(id);
    return topItem.installDate > addon.installDate.valueOf() ?
      "controlled_by_other_extensions" :
      "controllable_by_this_extension";
  },

  /**
   * Test-only method to force reloading of the JSON file.
   *
   * Note that this method simply clears the local variable that stores the
   * file, so the next time the file is accessed it will be reloaded.
   *
   * @returns {Promise}
   *          A promise that resolves once the settings store has been cleared.
   */
  _reloadFile() {
    return reloadFile();
  },
};
PK
!<-û[((modules/ExtensionStorage.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ExtensionStorage"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                  "resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

const global = this;

function isStructuredCloneHolder(value) {
  return (value && typeof value === "object" &&
          Cu.getClassName(value, true) === "StructuredCloneHolder");
}

class SerializeableMap extends Map {
  toJSON() {
    let result = {};
    for (let [key, value] of this) {
      if (isStructuredCloneHolder(value)) {
        value = value.deserialize(global);
        this.set(key, value);
      }

      result[key] = value;
    }
    return result;
  }

  /**
   * Like toJSON, but attempts to serialize every value separately, and
   * elides any which fail to serialize. Should only be used if initial
   * JSON serialization fails.
   *
   * @returns {object}
   */
  toJSONSafe() {
    let result = {};
    for (let [key, value] of this) {
      try {
        void JSON.serialize(value);

        result[key] = value;
      } catch (e) {
        Cu.reportError(new Error(`Failed to serialize browser.storage key "${key}": ${e}`));
      }
    }
    return result;
  }
}

/**
 * Serializes an arbitrary value into a StructuredCloneHolder, if
 * appropriate. Existing StructuredCloneHolders are returned unchanged.
 * Non-object values are also returned unchanged. Anything else is
 * serialized, and a new StructuredCloneHolder returned.
 *
 * This allows us to avoid a second structured clone operation after
 * sending a storage value across a message manager, before cloning it
 * into an extension scope.
 *
 * @param {StructuredCloneHolder|*} value
 *        A value to serialize.
 * @returns {*}
 */
function serialize(value) {
  if (value && typeof value === "object" && !isStructuredCloneHolder(value)) {
    return new StructuredCloneHolder(value);
  }
  return value;
}

this.ExtensionStorage = {
  // Map<extension-id, Promise<JSONFile>>
  jsonFilePromises: new Map(),

  listeners: new Map(),

  /**
   * Asynchronously reads the storage file for the given extension ID
   * and returns a Promise for its initialized JSONFile object.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file.
   * @returns {Promise<JSONFile>}
   */
  async _readFile(extensionId) {
    OS.File.makeDir(this.getExtensionDir(extensionId), {
      ignoreExisting: true,
      from: OS.Constants.Path.profileDir,
    });

    let jsonFile = new JSONFile({path: this.getStorageFile(extensionId)});
    await jsonFile.load();

    jsonFile.data = new SerializeableMap(Object.entries(jsonFile.data));

    return jsonFile;
  },

  /**
   * Returns a Promise for initialized JSONFile instance for the
   * extension's storage file.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file.
   * @returns {Promise<JSONFile>}
   */
  getFile(extensionId) {
    let promise = this.jsonFilePromises.get(extensionId);
    if (!promise) {
      promise = this._readFile(extensionId);
      this.jsonFilePromises.set(extensionId, promise);
    }
    return promise;
  },

  /**
   * Sanitizes the given value, and returns a JSON-compatible
   * representation of it, based on the privileges of the given global.
   *
   * @param {value} value
   *        The value to sanitize.
   * @param {Context} context
   *        The extension context in which to sanitize the value
   * @returns {value}
   *        The sanitized value.
   */
  sanitize(value, context) {
    let json = context.jsonStringify(value === undefined ? null : value);
    if (json == undefined) {
      throw new ExtensionUtils.ExtensionError("DataCloneError: The object could not be cloned.");
    }
    return JSON.parse(json);
  },


  /**
   * Returns the path to the storage directory within the profile for
   * the given extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a directory path.
   * @returns {string}
   */
  getExtensionDir(extensionId) {
    return OS.Path.join(this.extensionDir, extensionId);
  },

  /**
   * Returns the path to the JSON storage file for the given extension
   * ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file path.
   * @returns {string}
   */
  getStorageFile(extensionId) {
    return OS.Path.join(this.extensionDir, extensionId, "storage.js");
  },

  /**
   * Asynchronously sets the values of the given storage items for the
   * given extension.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to set storage values.
   * @param {object} items
   *        The storage items to set. For each property in the object,
   *        the storage value for that property is set to its value in
   *        said object. Any values which are StructuredCloneHolder
   *        instances are deserialized before being stored.
   * @returns {Promise<void>}
   */
  async set(extensionId, items) {
    let jsonFile = await this.getFile(extensionId);

    let changes = {};
    for (let prop in items) {
      let item = items[prop];
      changes[prop] = {oldValue: serialize(jsonFile.data.get(prop)), newValue: serialize(item)};
      jsonFile.data.set(prop, item);
    }

    this.notifyListeners(extensionId, changes);

    jsonFile.saveSoon();
    return null;
  },

  /**
   * Asynchronously removes the given storage items for the given
   * extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to remove storage values.
   * @param {Array<string>} items
   *        A list of storage items to remove.
   * @returns {Promise<void>}
   */
  async remove(extensionId, items) {
    let jsonFile = await this.getFile(extensionId);

    let changed = false;
    let changes = {};

    for (let prop of [].concat(items)) {
      if (jsonFile.data.has(prop)) {
        changes[prop] = {oldValue: serialize(jsonFile.data.get(prop))};
        jsonFile.data.delete(prop);
        changed = true;
      }
    }

    if (changed) {
      this.notifyListeners(extensionId, changes);
      jsonFile.saveSoon();
    }
    return null;
  },

  /**
   * Asynchronously clears all storage entries for the given extension
   * ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to clear storage.
   * @returns {Promise<void>}
   */
  async clear(extensionId) {
    let jsonFile = await this.getFile(extensionId);

    let changed = false;
    let changes = {};

    for (let [prop, oldValue] of jsonFile.data.entries()) {
      changes[prop] = {oldValue: serialize(oldValue)};
      jsonFile.data.delete(prop);
      changed = true;
    }

    if (changed) {
      this.notifyListeners(extensionId, changes);
      jsonFile.saveSoon();
    }
    return null;
  },

  /**
   * Asynchronously retrieves the values for the given storage items for
   * the given extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to get storage values.
   * @param {Array<string>|object|null} [keys]
   *        The storage items to get. If an array, the value of each key
   *        in the array is returned. If null, the values of all items
   *        are returned. If an object, the value for each key in the
   *        object is returned, or that key's value if the item is not
   *        set.
   * @returns {Promise<object>}
   *        An object which a property for each requested key,
   *        containing that key's storage value. Values are
   *        StructuredCloneHolder objects which can be deserialized to
   *        the original storage value.
   */
  async get(extensionId, keys) {
    let jsonFile = await this.getFile(extensionId);
    let {data} = jsonFile;

    let result = {};
    if (keys === null) {
      Object.assign(result, data.toJSON());
    } else if (typeof(keys) == "object" && !Array.isArray(keys)) {
      for (let prop in keys) {
        if (data.has(prop)) {
          result[prop] = serialize(data.get(prop));
        } else {
          result[prop] = keys[prop];
        }
      }
    } else {
      for (let prop of [].concat(keys)) {
        if (data.has(prop)) {
          result[prop] = serialize(data.get(prop));
        }
      }
    }

    return result;
  },

  addOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId) || new Set();
    listeners.add(listener);
    this.listeners.set(extensionId, listeners);
  },

  removeOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId);
    listeners.delete(listener);
  },

  notifyListeners(extensionId, changes) {
    let listeners = this.listeners.get(extensionId);
    if (listeners) {
      for (let listener of listeners) {
        listener(changes);
      }
    }
  },

  init() {
    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
      return;
    }
    Services.obs.addObserver(this, "extension-invalidate-storage-cache");
    Services.obs.addObserver(this, "xpcom-shutdown");
  },

  observe(subject, topic, data) {
    if (topic == "xpcom-shutdown") {
      Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
      Services.obs.removeObserver(this, "xpcom-shutdown");
    } else if (topic == "extension-invalidate-storage-cache") {
      for (let promise of this.jsonFilePromises.values()) {
        promise.then(jsonFile => { jsonFile.finalize(); });
      }
      this.jsonFilePromises.clear();
    }
  },
};

XPCOMUtils.defineLazyGetter(ExtensionStorage, "extensionDir",
  () => OS.Path.join(OS.Constants.Path.profileDir, "browser-extension-data"));

ExtensionStorage.init();
PK
!<?t modules/ExtensionStorageSync.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// TODO:
// * find out how the Chrome implementation deals with conflicts

"use strict";

/* exported extensionIdToCollectionId */

this.EXPORTED_SYMBOLS = ["ExtensionStorageSync", "extensionStorageSync"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
const global = this;

Cu.importGlobalProperties(["atob", "btoa"]);

Cu.import("resource://gre/modules/AppConstants.jsm");
const KINTO_PROD_SERVER_URL = "https://webextensions.settings.services.mozilla.com/v1";
const KINTO_DEFAULT_SERVER_URL = KINTO_PROD_SERVER_URL;

const STORAGE_SYNC_ENABLED_PREF = "webextensions.storage.sync.enabled";
const STORAGE_SYNC_SERVER_URL_PREF = "webextensions.storage.sync.serverURL";
const STORAGE_SYNC_SCOPE = "sync:addon_storage";
const STORAGE_SYNC_CRYPTO_COLLECTION_NAME = "storage-sync-crypto";
const STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID = "keys";
const STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES = 32;
const FXA_OAUTH_OPTIONS = {
  scope: STORAGE_SYNC_SCOPE,
};
const HISTOGRAM_GET_OPS_SIZE = "STORAGE_SYNC_GET_OPS_SIZE";
const HISTOGRAM_SET_OPS_SIZE = "STORAGE_SYNC_SET_OPS_SIZE";
const HISTOGRAM_REMOVE_OPS = "STORAGE_SYNC_REMOVE_OPS";
const SCALAR_EXTENSIONS_USING = "storage.sync.api.usage.extensions_using";
const SCALAR_ITEMS_STORED = "storage.sync.api.usage.items_stored";
const SCALAR_STORAGE_CONSUMED = "storage.sync.api.usage.storage_consumed";
// Default is 5sec, which seems a bit aggressive on the open internet
const KINTO_REQUEST_TIMEOUT = 30000;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");


XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
                                  "resource://services-sync/keys.js");
XPCOMUtils.defineLazyModuleGetter(this, "CollectionKeyManager",
                                  "resource://services-sync/record.js");
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                  "resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                  "resource://services-crypto/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                  "resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "KintoHttpClient",
                                  "resource://services-common/kinto-http-client.js");
XPCOMUtils.defineLazyModuleGetter(this, "Kinto",
                                  "resource://services-common/kinto-offline-client.js");
XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAdapter",
                                  "resource://services-common/kinto-storage-adapter.js");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Observers",
                                  "resource://services-common/observers.js");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
                                  "resource://services-sync/util.js");
XPCOMUtils.defineLazyPreferenceGetter(this, "prefPermitsStorageSync",
                                      STORAGE_SYNC_ENABLED_PREF, true);
XPCOMUtils.defineLazyPreferenceGetter(this, "prefStorageSyncServerURL",
                                      STORAGE_SYNC_SERVER_URL_PREF,
                                      KINTO_DEFAULT_SERVER_URL);
XPCOMUtils.defineLazyGetter(this, "WeaveCrypto", function() {
  let {WeaveCrypto} = Cu.import("resource://services-crypto/WeaveCrypto.js", {});
  return new WeaveCrypto();
});

const {
  runSafeSyncWithoutClone,
} = ExtensionUtils;

// Map of Extensions to Set<Contexts> to track contexts that are still
// "live" and use storage.sync.
const extensionContexts = new Map();
// Borrow logger from Sync.
const log = Log.repository.getLogger("Sync.Engine.Extension-Storage");

// A global that is fxAccounts, or null if (as on android) fxAccounts
// isn't available.
let _fxaService = null;
if (AppConstants.platform != "android") {
  _fxaService = fxAccounts;
}

class ServerKeyringDeleted extends Error {
  constructor() {
    super("server keyring appears to have disappeared; we were called to decrypt null");
  }
}

/**
 * Check for FXA and throw an exception if we don't have access.
 *
 * @param {Object} fxAccounts  The reference we were hoping to use to
 *     access FxA
 * @param {string} action  The thing we were doing when we decided to
 *     see if we had access to FxA
 */
function throwIfNoFxA(fxAccounts, action) {
  if (!fxAccounts) {
    throw new Error(`${action} is impossible because FXAccounts is not available; are you on Android?`);
  }
}

// Global ExtensionStorageSync instance that extensions and Fx Sync use.
// On Android, because there's no FXAccounts instance, any syncing
// operations will fail.
this.extensionStorageSync = null;

/**
 * Utility function to enforce an order of fields when computing an HMAC.
 *
 * @param {KeyBundle} keyBundle  The key bundle to use to compute the HMAC
 * @param {string}    id         The record ID to use when computing the HMAC
 * @param {string}    IV         The IV to use when computing the HMAC
 * @param {string}    ciphertext The ciphertext over which to compute the HMAC
 * @returns {string} The computed HMAC
 */
function ciphertextHMAC(keyBundle, id, IV, ciphertext) {
  const hasher = keyBundle.sha256HMACHasher;
  return Utils.bytesAsHex(Utils.digestUTF8(id + IV + ciphertext, hasher));
}

/**
 * Get the current user's hashed kB.
 *
 * @param {FXAccounts} fxaService  The service to use to get the
 *     current user.
 * @returns {string} sha256 of the user's kB as a hex string
 */
const getKBHash = async function(fxaService) {
  const signedInUser = await fxaService.getSignedInUser();
  if (!signedInUser) {
    throw new Error("User isn't signed in!");
  }

  if (!signedInUser.kB) {
    throw new Error("User doesn't have kB??");
  }

  let kBbytes = CommonUtils.hexToBytes(signedInUser.kB);
  let hasher = Cc["@mozilla.org/security/hash;1"]
      .createInstance(Ci.nsICryptoHash);
  hasher.init(hasher.SHA256);
  return CommonUtils.bytesAsHex(CryptoUtils.digestBytes(signedInUser.uid + kBbytes, hasher));
};

/**
 * A "remote transformer" that the Kinto library will use to
 * encrypt/decrypt records when syncing.
 *
 * This is an "abstract base class". Subclass this and override
 * getKeys() to use it.
 */
class EncryptionRemoteTransformer {
  async encode(record) {
    const keyBundle = await this.getKeys();
    if (record.ciphertext) {
      throw new Error("Attempt to reencrypt??");
    }
    let id = await this.getEncodedRecordId(record);
    if (!id) {
      throw new Error("Record ID is missing or invalid");
    }

    let IV = WeaveCrypto.generateRandomIV();
    let ciphertext = WeaveCrypto.encrypt(JSON.stringify(record),
                                         keyBundle.encryptionKeyB64, IV);
    let hmac = ciphertextHMAC(keyBundle, id, IV, ciphertext);
    const encryptedResult = {ciphertext, IV, hmac, id};

    // Copy over the _status field, so that we handle concurrency
    // headers (If-Match, If-None-Match) correctly.
    // DON'T copy over "deleted" status, because then we'd leak
    // plaintext deletes.
    encryptedResult._status = record._status == "deleted" ? "updated" : record._status;
    if (record.hasOwnProperty("last_modified")) {
      encryptedResult.last_modified = record.last_modified;
    }

    return encryptedResult;
  }

  async decode(record) {
    if (!record.ciphertext) {
      // This can happen for tombstones if a record is deleted.
      if (record.deleted) {
        return record;
      }
      throw new Error("No ciphertext: nothing to decrypt?");
    }
    const keyBundle = await this.getKeys();
    // Authenticate the encrypted blob with the expected HMAC
    let computedHMAC = ciphertextHMAC(keyBundle, record.id, record.IV, record.ciphertext);

    if (computedHMAC != record.hmac) {
      Utils.throwHMACMismatch(record.hmac, computedHMAC);
    }

    // Handle invalid data here. Elsewhere we assume that cleartext is an object.
    let cleartext = WeaveCrypto.decrypt(record.ciphertext,
                                        keyBundle.encryptionKeyB64, record.IV);
    let jsonResult = JSON.parse(cleartext);
    if (!jsonResult || typeof jsonResult !== "object") {
      throw new Error("Decryption failed: result is <" + jsonResult + ">, not an object.");
    }

    if (record.hasOwnProperty("last_modified")) {
      jsonResult.last_modified = record.last_modified;
    }

    // _status: deleted records were deleted on a client, but
    // uploaded as an encrypted blob so we don't leak deletions.
    // If we get such a record, flag it as deleted.
    if (jsonResult._status == "deleted") {
      jsonResult.deleted = true;
    }

    return jsonResult;
  }

  /**
   * Retrieve keys to use during encryption.
   *
   * Returns a Promise<KeyBundle>.
   */
  getKeys() {
    throw new Error("override getKeys in a subclass");
  }

  /**
   * Compute the record ID to use for the encoded version of the
   * record.
   *
   * The default version just re-uses the record's ID.
   *
   * @param {Object} record The record being encoded.
   * @returns {Promise<string>} The ID to use.
   */
  getEncodedRecordId(record) {
    return Promise.resolve(record.id);
  }
}
global.EncryptionRemoteTransformer = EncryptionRemoteTransformer;

/**
 * An EncryptionRemoteTransformer that provides a keybundle derived
 * from the user's kB, suitable for encrypting a keyring.
 */
class KeyRingEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
  constructor(fxaService) {
    super();
    this._fxaService = fxaService;
  }

  getKeys() {
    throwIfNoFxA(this._fxaService, "encrypting chrome.storage.sync records");
    const self = this;
    return (async function() {
      const user = await self._fxaService.getSignedInUser();
      // FIXME: we should permit this if the user is self-hosting
      // their storage
      if (!user) {
        throw new Error("user isn't signed in to FxA; can't sync");
      }

      if (!user.kB) {
        throw new Error("user doesn't have kB");
      }

      let kB = Utils.hexToBytes(user.kB);

      let keyMaterial = CryptoUtils.hkdf(kB, undefined,
                                       "identity.mozilla.com/picl/v1/chrome.storage.sync", 2 * 32);
      let bundle = new BulkKeyBundle();
      // [encryptionKey, hmacKey]
      bundle.keyPair = [keyMaterial.slice(0, 32), keyMaterial.slice(32, 64)];
      return bundle;
    })();
  }
  // Pass through the kbHash field from the unencrypted record. If
  // encryption fails, we can use this to try to detect whether we are
  // being compromised or if the record here was encoded with a
  // different kB.
  async encode(record) {
    const encoded = await super.encode(record);
    encoded.kbHash = record.kbHash;
    return encoded;
  }

  async decode(record) {
    try {
      return await super.decode(record);
    } catch (e) {
      if (Utils.isHMACMismatch(e)) {
        const currentKBHash = await getKBHash(this._fxaService);
        if (record.kbHash != currentKBHash) {
          // Some other client encoded this with a kB that we don't
          // have access to.
          KeyRingEncryptionRemoteTransformer.throwOutdatedKB(currentKBHash, record.kbHash);
        }
      }
      throw e;
    }
  }

  // Generator and discriminator for KB-is-outdated exceptions.
  static throwOutdatedKB(shouldBe, is) {
    throw new Error(`kB hash on record is outdated: should be ${shouldBe}, is ${is}`);
  }

  static isOutdatedKB(exc) {
    const kbMessage = "kB hash on record is outdated: ";
    return exc && exc.message && exc.message.indexOf &&
      (exc.message.indexOf(kbMessage) == 0);
  }
}
global.KeyRingEncryptionRemoteTransformer = KeyRingEncryptionRemoteTransformer;

/**
 * A Promise that centralizes initialization of ExtensionStorageSync.
 *
 * This centralizes the use of the Sqlite database, to which there is
 * only one connection which is shared by all threads.
 *
 * Fields in the object returned by this Promise:
 *
 * - connection: a Sqlite connection. Meant for internal use only.
 * - kinto: a KintoBase object, suitable for using in Firefox. All
 *   collections in this database will use the same Sqlite connection.
 */
const storageSyncInit = (async function() {
  const path = "storage-sync.sqlite";
  const opts = {path, sharedMemoryCache: false};
  const connection = await Sqlite.openConnection(opts);
  await FirefoxAdapter._init(connection);
  return {
    connection,
    kinto: new Kinto({
      adapter: FirefoxAdapter,
      adapterOptions: {sqliteHandle: connection},
      timeout: KINTO_REQUEST_TIMEOUT,
    }),
  };
})();

AsyncShutdown.profileBeforeChange.addBlocker(
  "ExtensionStorageSync: close Sqlite handle",
  async function() {
    const ret = await storageSyncInit;
    const {connection} = ret;
    await connection.close();
  }
);
// Kinto record IDs have two condtions:
//
// - They must contain only ASCII alphanumerics plus - and _. To fix
// this, we encode all non-letters using _C_, where C is the
// percent-encoded character, so space becomes _20_
// and underscore becomes _5F_.
//
// - They must start with an ASCII letter. To ensure this, we prefix
// all keys with "key-".
function keyToId(key) {
  function escapeChar(match) {
    return "_" + match.codePointAt(0).toString(16).toUpperCase() + "_";
  }
  return "key-" + key.replace(/[^a-zA-Z0-9]/g, escapeChar);
}

// Convert a Kinto ID back into a chrome.storage key.
// Returns null if a key couldn't be parsed.
function idToKey(id) {
  function unescapeNumber(match, group1) {
    return String.fromCodePoint(parseInt(group1, 16));
  }
  // An escaped ID should match this regex.
  // An escaped ID should consist of only letters and numbers, plus
  // code points escaped as _[0-9a-f]+_.
  const ESCAPED_ID_FORMAT = /^(?:[a-zA-Z0-9]|_[0-9A-F]+_)*$/;

  if (!id.startsWith("key-")) {
    return null;
  }
  const unprefixed = id.slice(4);
  // Verify that the ID is the correct format.
  if (!ESCAPED_ID_FORMAT.test(unprefixed)) {
    return null;
  }
  return unprefixed.replace(/_([0-9A-F]+)_/g, unescapeNumber);
}

// An "id schema" used to validate Kinto IDs and generate new ones.
const storageSyncIdSchema = {
  // We should never generate IDs; chrome.storage only acts as a
  // key-value store, so we should always have a key.
  generate() {
    throw new Error("cannot generate IDs");
  },

  // See keyToId and idToKey for more details.
  validate(id) {
    return idToKey(id) !== null;
  },
};

// An "id schema" used for the system collection, which doesn't
// require validation or generation of IDs.
const cryptoCollectionIdSchema = {
  generate() {
    throw new Error("cannot generate IDs for system collection");
  },

  validate(id) {
    return true;
  },
};

/**
 * Wrapper around the crypto collection providing some handy utilities.
 */
class CryptoCollection {
  constructor(fxaService) {
    this._fxaService = fxaService;
  }

  async getCollection() {
    throwIfNoFxA(this._fxaService, "tried to access cryptoCollection");
    const {kinto} = await storageSyncInit;
    return kinto.collection(STORAGE_SYNC_CRYPTO_COLLECTION_NAME, {
      idSchema: cryptoCollectionIdSchema,
      remoteTransformers: [new KeyRingEncryptionRemoteTransformer(this._fxaService)],
    });
  }

  /**
   * Generate a new salt for use in hashing extension and record
   * IDs.
   *
   * @returns {string} A base64-encoded string of the salt
   */
  getNewSalt() {
    return btoa(CryptoUtils.generateRandomBytes(STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES));
  }

  /**
   * Retrieve the keyring record from the crypto collection.
   *
   * You can use this if you want to check metadata on the keyring
   * record rather than use the keyring itself.
   *
   * The keyring record, if present, should have the structure:
   *
   * - kbHash: a hash of the user's kB. When this changes, we will
   *   try to sync the collection.
   * - uuid: a record identifier. This will only change when we wipe
   *   the collection (due to kB getting reset).
   * - keys: a "WBO" form of a CollectionKeyManager.
   * - salts: a normal JS Object with keys being collection IDs and
   *   values being base64-encoded salts to use when hashing IDs
   *   for that collection.
   * @returns {Promise<Object>}
   */
  async getKeyRingRecord() {
    const collection = await this.getCollection();
    const cryptoKeyRecord = await collection.getAny(STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID);

    let data = cryptoKeyRecord.data;
    if (!data) {
      // This is a new keyring. Invent an ID for this record. If this
      // changes, it means a client replaced the keyring, so we need to
      // reupload everything.
      const uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
      const uuid = uuidgen.generateUUID().toString();
      data = {uuid, id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID};
    }
    return data;
  }

  async getSalts() {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    return cryptoKeyRecord && cryptoKeyRecord.salts;
  }

  /**
   * Used for testing with a known salt.
   *
   * @param {string} extensionId  The extension ID for which to set a
   *     salt.
   * @param {string} salt  The salt to use for this extension, as a
   *     base64-encoded salt.
   */
  async _setSalt(extensionId, salt) {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    cryptoKeyRecord.salts = cryptoKeyRecord.salts || {};
    cryptoKeyRecord.salts[extensionId] = salt;
    await this.upsert(cryptoKeyRecord);
  }

  /**
   * Hash an extension ID for a given user so that an attacker can't
   * identify the extensions a user has installed.
   *
   * The extension ID is assumed to be a string (i.e. series of
   * code points), and its UTF8 encoding is prefixed with the salt
   * for that collection and hashed.
   *
   * The returned hash must conform to the syntax for Kinto
   * identifiers, which (as of this writing) must match
   * [a-zA-Z0-9][a-zA-Z0-9_-]*. We thus encode the hash using
   * "base64-url" without padding (so that we don't get any equals
   * signs (=)). For fear that a hash could start with a hyphen
   * (-) or an underscore (_), prefix it with "ext-".
   *
   * @param {string} extensionId The extension ID to obfuscate.
   * @returns {Promise<bytestring>} A collection ID suitable for use to sync to.
   */
  extensionIdToCollectionId(extensionId) {
    return this.hashWithExtensionSalt(CommonUtils.encodeUTF8(extensionId), extensionId)
      .then(hash => `ext-${hash}`);
  }

  /**
   * Hash some value with the salt for the given extension.
   *
   * The value should be a "bytestring", i.e. a string whose
   * "characters" are values, each within [0, 255]. You can produce
   * such a bytestring using e.g. CommonUtils.encodeUTF8.
   *
   * The returned value is a base64url-encoded string of the hash.
   *
   * @param {bytestring} value The value to be hashed.
   * @param {string} extensionId The ID of the extension whose salt
   *                             we should use.
   * @returns {Promise<bytestring>} The hashed value.
   */
  async hashWithExtensionSalt(value, extensionId) {
    const salts = await this.getSalts();
    const saltBase64 = salts && salts[extensionId];
    if (!saltBase64) {
      // This should never happen; salts should be populated before
      // we need them by ensureCanSync.
      throw new Error(`no salt available for ${extensionId}; how did this happen?`);
    }

    const hasher = Cc["@mozilla.org/security/hash;1"]
          .createInstance(Ci.nsICryptoHash);
    hasher.init(hasher.SHA256);

    const salt = atob(saltBase64);
    const message = `${salt}\x00${value}`;
    const hash = CryptoUtils.digestBytes(message, hasher);
    return CommonUtils.encodeBase64URL(hash, false);
  }

  /**
   * Retrieve the actual keyring from the crypto collection.
   *
   * @returns {Promise<CollectionKeyManager>}
   */
  async getKeyRing() {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    const collectionKeys = new CollectionKeyManager();
    if (cryptoKeyRecord.keys) {
      collectionKeys.setContents(cryptoKeyRecord.keys, cryptoKeyRecord.last_modified);
    } else {
      // We never actually use the default key, so it's OK if we
      // generate one multiple times.
      collectionKeys.generateDefaultKey();
    }
    // Pass through uuid field so that we can save it if we need to.
    collectionKeys.uuid = cryptoKeyRecord.uuid;
    return collectionKeys;
  }

  async updateKBHash(kbHash) {
    const coll = await this.getCollection();
    await coll.update({id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID,
                       kbHash: kbHash},
                      {patch: true});
  }

  async upsert(record) {
    const collection = await this.getCollection();
    await collection.upsert(record);
  }

  async sync(extensionStorageSync) {
    const collection = await this.getCollection();
    return extensionStorageSync._syncCollection(collection, {
      strategy: "server_wins",
    });
  }

  /**
   * Reset sync status for ALL collections by directly
   * accessing the FirefoxAdapter.
   */
  async resetSyncStatus() {
    const coll = await this.getCollection();
    await coll.db.resetSyncStatus();
  }

  // Used only for testing.
  async _clear() {
    const collection = await this.getCollection();
    await collection.clear();
  }
}
this.CryptoCollection = CryptoCollection;

/**
 * An EncryptionRemoteTransformer for extension records.
 *
 * It uses the special "keys" record to find a key for a given
 * extension, thus its name
 * CollectionKeyEncryptionRemoteTransformer.
 *
 * Also, during encryption, it will replace the ID of the new record
 * with a hashed ID, using the salt for this collection.
 *
 * @param {string} extensionId The extension ID for which to find a key.
   */
let CollectionKeyEncryptionRemoteTransformer = class extends EncryptionRemoteTransformer {
  constructor(cryptoCollection, extensionId) {
    super();
    this.cryptoCollection = cryptoCollection;
    this.extensionId = extensionId;
  }

  async getKeys() {
    // FIXME: cache the crypto record for the duration of a sync cycle?
    const collectionKeys = await this.cryptoCollection.getKeyRing();
    if (!collectionKeys.hasKeysFor([this.extensionId])) {
      // This should never happen. Keys should be created (and
      // synced) at the beginning of the sync cycle.
      throw new Error(`tried to encrypt records for ${this.extensionId}, but key is not present`);
    }
    return collectionKeys.keyForCollection(this.extensionId);
  }

  getEncodedRecordId(record) {
    // It isn't really clear whether kinto.js record IDs are
    // bytestrings or strings that happen to only contain ASCII
    // characters, so encode them to be sure.
    const id = CommonUtils.encodeUTF8(record.id);
    // Like extensionIdToCollectionId, the rules about Kinto record
    // IDs preclude equals signs or strings starting with a
    // non-alphanumeric, so prefix all IDs with a constant "id-".
    return this.cryptoCollection.hashWithExtensionSalt(id, this.extensionId)
      .then(hash => `id-${hash}`);
  }
};

global.CollectionKeyEncryptionRemoteTransformer = CollectionKeyEncryptionRemoteTransformer;

/**
 * Clean up now that one context is no longer using this extension's collection.
 *
 * @param {Extension} extension
 *                    The extension whose context just ended.
 * @param {Context} context
 *                  The context that just ended.
 */
function cleanUpForContext(extension, context) {
  const contexts = extensionContexts.get(extension);
  if (!contexts) {
    Cu.reportError(new Error(`Internal error: cannot find any contexts for extension ${extension.id}`));
  }
  contexts.delete(context);
  if (contexts.size === 0) {
    // Nobody else is using this collection. Clean up.
    extensionContexts.delete(extension);
  }
}

/**
 * Generate a promise that produces the Collection for an extension.
 *
 * @param {CryptoCollection} cryptoCollection
 * @param {Extension} extension
 *                    The extension whose collection needs to
 *                    be opened.
 * @param {Context} context
 *                  The context for this extension. The Collection
 *                  will shut down automatically when all contexts
 *                  close.
 * @returns {Promise<Collection>}
 */
const openCollection = async function(cryptoCollection, extension, context) {
  let collectionId = extension.id;
  const {kinto} = await storageSyncInit;
  const remoteTransformers = [new CollectionKeyEncryptionRemoteTransformer(cryptoCollection, extension.id)];
  const coll = kinto.collection(collectionId, {
    idSchema: storageSyncIdSchema,
    remoteTransformers,
  });
  return coll;
};

class ExtensionStorageSync {
  /**
   * @param {FXAccounts} fxaService (Optional) If not
   *    present, trying to sync will fail.
   * @param {nsITelemetry} telemetry Telemetry service to use to
   *    report sync usage.
   */
  constructor(fxaService, telemetry) {
    this._fxaService = fxaService;
    this._telemetry = telemetry;
    this.cryptoCollection = new CryptoCollection(fxaService);
    this.listeners = new WeakMap();
  }

  async syncAll() {
    const extensions = extensionContexts.keys();
    const extIds = Array.from(extensions, extension => extension.id);
    log.debug(`Syncing extension settings for ${JSON.stringify(extIds)}`);
    if (extIds.length == 0) {
      // No extensions to sync. Get out.
      return;
    }
    await this.ensureCanSync(extIds);
    await this.checkSyncKeyRing();
    const promises = Array.from(extensionContexts.keys(), extension => {
      return openCollection(this.cryptoCollection, extension).then(coll => {
        return this.sync(extension, coll);
      });
    });
    await Promise.all(promises);

    // This needs access to an adapter, but any adapter will do
    const collection = await this.cryptoCollection.getCollection();
    const storage = await collection.db.calculateStorage();
    this._telemetry.scalarSet(SCALAR_EXTENSIONS_USING, storage.length);
    for (let {collectionName, size, numRecords} of storage) {
      this._telemetry.keyedScalarSet(SCALAR_ITEMS_STORED, collectionName, numRecords);
      this._telemetry.keyedScalarSet(SCALAR_STORAGE_CONSUMED, collectionName, size);
    }
  }

  async sync(extension, collection) {
    throwIfNoFxA(this._fxaService, "syncing chrome.storage.sync");
    const signedInUser = await this._fxaService.getSignedInUser();
    if (!signedInUser) {
      // FIXME: this should support syncing to self-hosted
      log.info("User was not signed into FxA; cannot sync");
      throw new Error("Not signed in to FxA");
    }
    const collectionId = await this.cryptoCollection.extensionIdToCollectionId(extension.id);
    let syncResults;
    try {
      syncResults = await this._syncCollection(collection, {
        strategy: "client_wins",
        collection: collectionId,
      });
    } catch (err) {
      log.warn("Syncing failed", err);
      throw err;
    }

    let changes = {};
    for (const record of syncResults.created) {
      changes[record.key] = {
        newValue: record.data,
      };
    }
    for (const record of syncResults.updated) {
      // N.B. It's safe to just pick old.key because it's not
      // possible to "rename" a record in the storage.sync API.
      const key = record.old.key;
      changes[key] = {
        oldValue: record.old.data,
        newValue: record.new.data,
      };
    }
    for (const record of syncResults.deleted) {
      changes[record.key] = {
        oldValue: record.data,
      };
    }
    for (const resolution of syncResults.resolved) {
      // FIXME: We can't send a "changed" notification because
      // kinto.js only provides the newly-resolved value. But should
      // we even send a notification? We use CLIENT_WINS so nothing
      // has really "changed" on this end. (The change will come on
      // the other end when it pulls down the update, which is handled
      // by the "updated" case above.) If we are going to send a
      // notification, what best values for "old" and "new"?  This
      // might violate client code's assumptions, since from their
      // perspective, we were in state L, but this diff is from R ->
      // L.
      const accepted = resolution.accepted;
      changes[accepted.key] = {
        newValue: accepted.data,
      };
    }
    if (Object.keys(changes).length > 0) {
      this.notifyListeners(extension, changes);
    }
  }

  /**
   * Utility function that handles the common stuff about syncing all
   * Kinto collections (including "meta" collections like the crypto
   * one).
   *
   * @param {Collection} collection
   * @param {Object} options
   *                 Additional options to be passed to sync().
   * @returns {Promise<SyncResultObject>}
   */
  _syncCollection(collection, options) {
    // FIXME: this should support syncing to self-hosted
    return this._requestWithToken(`Syncing ${collection.name}`, function(token) {
      const allOptions = Object.assign({}, {
        remote: prefStorageSyncServerURL,
        headers: {
          Authorization: "Bearer " + token,
        },
      }, options);

      return collection.sync(allOptions);
    });
  }

  // Make a Kinto request with a current FxA token.
  // If the response indicates that the token might have expired,
  // retry the request.
  async _requestWithToken(description, f) {
    throwIfNoFxA(this._fxaService, "making remote requests from chrome.storage.sync");
    const fxaToken = await this._fxaService.getOAuthToken(FXA_OAUTH_OPTIONS);
    try {
      return await f(fxaToken);
    } catch (e) {
      log.error(`${description}: request failed`, e);
      if (e && e.response && e.response.status == 401) {
        // Our token might have expired. Refresh and retry.
        log.info("Token might have expired");
        await this._fxaService.removeCachedOAuthToken({token: fxaToken});
        const newToken = await this._fxaService.getOAuthToken(FXA_OAUTH_OPTIONS);

        // If this fails too, let it go.
        return f(newToken);
      }
      // Otherwise, we don't know how to handle this error, so just reraise.
      throw e;
    }
  }

  /**
   * Helper similar to _syncCollection, but for deleting the user's bucket.
   *
   * @returns {Promise<void>}
   */
  _deleteBucket() {
    log.error("Deleting default bucket and everything in it");
    return this._requestWithToken("Clearing server", function(token) {
      const headers = {Authorization: "Bearer " + token};
      const kintoHttp = new KintoHttpClient(prefStorageSyncServerURL, {
        headers: headers,
        timeout: KINTO_REQUEST_TIMEOUT,
      });
      return kintoHttp.deleteBucket("default");
    });
  }

  async ensureSaltsFor(keysRecord, extIds) {
    const newSalts = Object.assign({}, keysRecord.salts);
    for (let collectionId of extIds) {
      if (newSalts[collectionId]) {
        continue;
      }

      newSalts[collectionId] = this.cryptoCollection.getNewSalt();
    }

    return newSalts;
  }

  /**
   * Check whether the keys record (provided) already has salts for
   * all the extensions given in extIds.
   *
   * @param {Object} keysRecord A previously-retrieved keys record.
   * @param {Array<string>} extIds The IDs of the extensions which
   *                need salts.
   * @returns {boolean}
   */
  hasSaltsFor(keysRecord, extIds) {
    if (!keysRecord.salts) {
      return false;
    }

    for (let collectionId of extIds) {
      if (!keysRecord.salts[collectionId]) {
        return false;
      }
    }

    return true;
  }

  /**
   * Recursive promise that terminates when our local collectionKeys,
   * as well as that on the server, have keys for all the extensions
   * in extIds.
   *
   * @param {Array<string>} extIds
   *                        The IDs of the extensions which need keys.
   * @returns {Promise<CollectionKeyManager>}
   */
  async ensureCanSync(extIds) {
    const keysRecord = await this.cryptoCollection.getKeyRingRecord();
    const collectionKeys = await this.cryptoCollection.getKeyRing();
    if (collectionKeys.hasKeysFor(extIds) && this.hasSaltsFor(keysRecord, extIds)) {
      return collectionKeys;
    }

    log.info(`Need to create keys and/or salts for ${JSON.stringify(extIds)}`);
    const kbHash = await getKBHash(this._fxaService);
    const newKeys = await collectionKeys.ensureKeysFor(extIds);
    const newSalts = await this.ensureSaltsFor(keysRecord, extIds);
    const newRecord = {
      id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID,
      keys: newKeys.asWBO().cleartext,
      salts: newSalts,
      uuid: collectionKeys.uuid,
      // Add a field for the current kB hash.
      kbHash: kbHash,
    };
    await this.cryptoCollection.upsert(newRecord);
    const result = await this._syncKeyRing(newRecord);
    if (result.resolved.length != 0) {
      // We had a conflict which was automatically resolved. We now
      // have a new keyring which might have keys for the
      // collections. Recurse.
      return this.ensureCanSync(extIds);
    }

    // No conflicts. We're good.
    return newKeys;
  }

  /**
   * Update the kB in the crypto record.
   */
  async updateKeyRingKB() {
    throwIfNoFxA(this._fxaService, "use of chrome.storage.sync \"keyring\"");
    const signedInUser = await this._fxaService.getSignedInUser();
    if (!signedInUser) {
      // Although this function is meant to be called on login,
      // it's not unreasonable to check any time, even if we aren't
      // logged in.
      //
      // If we aren't logged in, we don't have any information about
      // the user's kB, so we can't be sure that the user changed
      // their kB, so just return.
      return;
    }

    const thisKBHash = await getKBHash(this._fxaService);
    await this.cryptoCollection.updateKBHash(thisKBHash);
  }

  /**
   * Make sure the keyring is up to date and synced.
   *
   * This is called on syncs to make sure that we don't sync anything
   * to any collection unless the key for that collection is on the
   * server.
   */
  async checkSyncKeyRing() {
    await this.updateKeyRingKB();

    const cryptoKeyRecord = await this.cryptoCollection.getKeyRingRecord();
    if (cryptoKeyRecord && cryptoKeyRecord._status !== "synced") {
      // We haven't successfully synced the keyring since the last
      // change. This could be because kB changed and we touched the
      // keyring, or it could be because we failed to sync after
      // adding a key. Either way, take this opportunity to sync the
      // keyring.
      await this._syncKeyRing(cryptoKeyRecord);
    }
  }

  async _syncKeyRing(cryptoKeyRecord) {
    throwIfNoFxA(this._fxaService, "syncing chrome.storage.sync \"keyring\"");
    try {
      // Try to sync using server_wins.
      //
      // We use server_wins here because whatever is on the server is
      // at least consistent with itself -- the crypto in the keyring
      // matches the crypto on the collection records. This is because
      // we generate and upload keys just before syncing data.
      //
      // It's possible that we can't decode the version on the server.
      // This can happen if a user is locked out of their account, and
      // does a "reset password" to get in on a new device. In this
      // case, we are in a bind -- we can't decrypt the record on the
      // server, so we can't merge keys. If this happens, we try to
      // figure out if we're the one with the correct (new) kB or if
      // we just got locked out because we have the old kB. If we're
      // the one with the correct kB, we wipe the server and reupload
      // everything, including a new keyring.
      //
      // If another device has wiped the server, we need to reupload
      // everything we have on our end too, so we detect this by
      // adding a UUID to the keyring. UUIDs are preserved throughout
      // the lifetime of a keyring, so the only time a keyring UUID
      // changes is when a new keyring is uploaded, which only happens
      // after a server wipe. So when we get a "conflict" (resolved by
      // server_wins), we check whether the server version has a new
      // UUID. If so, reset our sync status, so that we'll reupload
      // everything.
      const result = await this.cryptoCollection.sync(this);
      if (result.resolved.length > 0) {
        // Automatically-resolved conflict. It should
        // be for the keys record.
        const resolutionIds = result.resolved.map(resolution => resolution.id);
        if (resolutionIds > 1) {
          // This should never happen -- there is only ever one record
          // in this collection.
          log.error(`Too many resolutions for sync-storage-crypto collection: ${JSON.stringify(resolutionIds)}`);
        }
        const keyResolution = result.resolved[0];
        if (keyResolution.id != STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID) {
          // This should never happen -- there should only ever be the
          // keyring in this collection.
          log.error(`Strange conflict in sync-storage-crypto collection: ${JSON.stringify(resolutionIds)}`);
        }

        // Due to a bug in the server-side code (see
        // https://github.com/Kinto/kinto/issues/1209), lots of users'
        // keyrings were deleted. We discover this by trying to push a
        // new keyring (because the user aded a new extension), and we
        // get a conflict. We have SERVER_WINS, so the client will
        // accept this deleted keyring and delete it locally. Discover
        // this and undo it.
        if (keyResolution.accepted === null) {
          log.error("Conflict spotted -- the server keyring was deleted");
          await this.cryptoCollection.upsert(keyResolution.rejected);
          // It's possible that the keyring on the server that was
          // deleted had keys for other extensions, which had already
          // encrypted data. For this to happen, another client would
          // have had to upload the keyring and then the delete happened
          // before this client did a sync (and got the new extension
          // and tried to sync the keyring again). Just to be safe,
          // let's signal that something went wrong and we should wipe
          // the bucket.
          throw new ServerKeyringDeleted();
        }

        if (keyResolution.accepted.uuid != cryptoKeyRecord.uuid) {
          log.info(`Detected a new UUID (${keyResolution.accepted.uuid}, was ${cryptoKeyRecord.uuid}). Reseting sync status for everything.`);
          await this.cryptoCollection.resetSyncStatus();

          // Server version is now correct. Return that result.
          return result;
        }
      }
      // No conflicts, or conflict was just someone else adding keys.
      return result;
    } catch (e) {
      if (KeyRingEncryptionRemoteTransformer.isOutdatedKB(e) ||
          e instanceof ServerKeyringDeleted ||
          // This is another way that ServerKeyringDeleted can
          // manifest; see bug 1350088 for more details.
          e.message == "Server has been flushed.") {
        // Check if our token is still valid, or if we got locked out
        // between starting the sync and talking to Kinto.
        const isSessionValid = await this._fxaService.sessionStatus();
        if (isSessionValid) {
          log.error("Couldn't decipher old keyring; deleting the default bucket and resetting sync status");
          await this._deleteBucket();
          await this.cryptoCollection.resetSyncStatus();

          // Reupload our keyring, which is the only new keyring.
          // We don't want client_wins here because another device
          // could have uploaded another keyring in the meantime.
          return this.cryptoCollection.sync(this);
        }
      }
      throw e;
    }
  }

  /**
   * Get the collection for an extension, and register the extension
   * as being "in use".
   *
   * @param {Extension} extension
   *                    The extension for which we are seeking
   *                    a collection.
   * @param {Context} context
   *                  The context of the extension, so that we can
   *                  stop syncing the collection when the extension ends.
   * @returns {Promise<Collection>}
   */
  getCollection(extension, context) {
    if (prefPermitsStorageSync !== true) {
      return Promise.reject({message: `Please set ${STORAGE_SYNC_ENABLED_PREF} to true in about:config`});
    }
    // Register that the extension and context are in use.
    if (!extensionContexts.has(extension)) {
      extensionContexts.set(extension, new Set());
    }
    const contexts = extensionContexts.get(extension);
    if (!contexts.has(context)) {
      // New context. Register it and make sure it cleans itself up
      // when it closes.
      contexts.add(context);
      context.callOnClose({
        close: () => cleanUpForContext(extension, context),
      });
    }

    return openCollection(this.cryptoCollection, extension, context);
  }

  async set(extension, items, context) {
    const coll = await this.getCollection(extension, context);
    const keys = Object.keys(items);
    const ids = keys.map(keyToId);
    const histogramSize = this._telemetry.getKeyedHistogramById(HISTOGRAM_SET_OPS_SIZE);
    const changes = await coll.execute(txn => {
      let changes = {};
      for (let [i, key] of keys.entries()) {
        const id = ids[i];
        let item = items[key];
        histogramSize.add(extension.id, JSON.stringify(item).length);
        let {oldRecord} = txn.upsert({
          id,
          key,
          data: item,
        });
        changes[key] = {
          newValue: item,
        };
        if (oldRecord && oldRecord.data) {
          // Extract the "data" field from the old record, which
          // represents the value part of the key-value store
          changes[key].oldValue = oldRecord.data;
        }
      }
      return changes;
    }, {preloadIds: ids});
    this.notifyListeners(extension, changes);
  }

  async remove(extension, keys, context) {
    const coll = await this.getCollection(extension, context);
    keys = [].concat(keys);
    const ids = keys.map(keyToId);
    let changes = {};
    await coll.execute(txn => {
      for (let [i, key] of keys.entries()) {
        const id = ids[i];
        const res = txn.deleteAny(id);
        if (res.deleted) {
          changes[key] = {
            oldValue: res.data.data,
          };
        }
      }
      return changes;
    }, {preloadIds: ids});
    if (Object.keys(changes).length > 0) {
      this.notifyListeners(extension, changes);
    }
    const histogram = this._telemetry.getKeyedHistogramById(HISTOGRAM_REMOVE_OPS);
    histogram.add(extension.id, keys.length);
  }

  async clear(extension, context) {
    // We can't call Collection#clear here, because that just clears
    // the local database. We have to explicitly delete everything so
    // that the deletions can be synced as well.
    const coll = await this.getCollection(extension, context);
    const res = await coll.list();
    const records = res.data;
    const keys = records.map(record => record.key);
    await this.remove(extension, keys, context);
  }

  async get(extension, spec, context) {
    const coll = await this.getCollection(extension, context);
    const histogramSize = this._telemetry.getKeyedHistogramById(HISTOGRAM_GET_OPS_SIZE);
    let keys, records;
    if (spec === null) {
      records = {};
      const res = await coll.list();
      for (let record of res.data) {
        histogramSize.add(extension.id, JSON.stringify(record.data).length);
        records[record.key] = record.data;
      }
      return records;
    }
    if (typeof spec === "string") {
      keys = [spec];
      records = {};
    } else if (Array.isArray(spec)) {
      keys = spec;
      records = {};
    } else {
      keys = Object.keys(spec);
      records = Cu.cloneInto(spec, global);
    }

    for (let key of keys) {
      const res = await coll.getAny(keyToId(key));
      if (res.data && res.data._status != "deleted") {
        histogramSize.add(extension.id, JSON.stringify(res.data.data).length);
        records[res.data.key] = res.data.data;
      }
    }

    return records;
  }

  addOnChangedListener(extension, listener, context) {
    let listeners = this.listeners.get(extension) || new Set();
    listeners.add(listener);
    this.listeners.set(extension, listeners);

    // Force opening the collection so that we will sync for this extension.
    return this.getCollection(extension, context);
  }

  removeOnChangedListener(extension, listener) {
    let listeners = this.listeners.get(extension);
    listeners.delete(listener);
    if (listeners.size == 0) {
      this.listeners.delete(extension);
    }
  }

  notifyListeners(extension, changes) {
    Observers.notify("ext.storage.sync-changed");
    let listeners = this.listeners.get(extension) || new Set();
    if (listeners) {
      for (let listener of listeners) {
        runSafeSyncWithoutClone(listener, changes);
      }
    }
  }
}
this.ExtensionStorageSync = ExtensionStorageSync;
this.extensionStorageSync = new ExtensionStorageSync(_fxaService, Services.telemetry);
PK
!<gXMmodules/ExtensionTabs.jsm/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

/* exported TabTrackerBase, TabManagerBase, TabBase, WindowTrackerBase, WindowManagerBase, WindowBase */

var EXPORTED_SYMBOLS = ["TabTrackerBase", "TabManagerBase", "TabBase", "WindowTrackerBase", "WindowManagerBase", "WindowBase"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");

const {
  DefaultMap,
  DefaultWeakMap,
  EventEmitter,
  ExtensionError,
  defineLazyGetter,
  getWinUtils,
} = ExtensionUtils;

/**
 * The platform-specific type of native tab objects, which are wrapped by
 * TabBase instances.
 *
 * @typedef {Object|XULElement} NativeTab
 */

/**
 * @typedef {Object} MutedInfo
 * @property {boolean} muted
 *        True if the tab is currently muted, false otherwise.
 * @property {string} [reason]
 *        The reason the tab is muted. Either "user", if the tab was muted by a
 *        user, or "extension", if it was muted by an extension.
 * @property {string} [extensionId]
 *        If the tab was muted by an extension, contains the internal ID of that
 *        extension.
 */

/**
 * A platform-independent base class for extension-specific wrappers around
 * native tab objects.
 *
 * @param {Extension} extension
 *        The extension object for which this wrapper is being created. Used to
 *        determine permissions for access to certain properties and
 *        functionality.
 * @param {NativeTab} nativeTab
 *        The native tab object which is being wrapped. The type of this object
 *        varies by platform.
 * @param {integer} id
 *        The numeric ID of this tab object. This ID should be the same for
 *        every extension, and for the lifetime of the tab.
 */
class TabBase {
  constructor(extension, nativeTab, id) {
    this.extension = extension;
    this.tabManager = extension.tabManager;
    this.id = id;
    this.nativeTab = nativeTab;
    this.activeTabWindowID = null;
  }

  /**
   * Sends a message, via the given context, to the ExtensionContent running in
   * this tab. The tab's current innerWindowID is automatically added to the
   * recipient filter for the message, and is used to ensure that the message is
   * not processed if the content process navigates to a different content page
   * before the message is received.
   *
   * @param {BaseContext} context
   *        The context through which to send the message.
   * @param {string} messageName
   *        The name of the messge to send.
   * @param {object} [data = {}]
   *        Arbitrary, structured-clonable message data to send.
   * @param {object} [options]
   *        An options object, as accepted by BaseContext.sendMessage.
   *
   * @returns {Promise}
   */
  sendMessage(context, messageName, data = {}, options = null) {
    let {browser, innerWindowID} = this;

    options = Object.assign({}, options);
    options.recipient = Object.assign({innerWindowID}, options.recipient);

    return context.sendMessage(browser.messageManager, messageName,
                               data, options);
  }

  /**
   * Capture the visible area of this tab, and return the result as a data: URL.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the capture.
   * @param {Object} [options]
   *        The options with which to perform the capture.
   * @param {string} [options.format = "png"]
   *        The image format in which to encode the captured data. May be one of
   *        "png" or "jpeg".
   * @param {integer} [options.quality = 92]
   *        The quality at which to encode the captured image data, ranging from
   *        0 to 100. Has no effect for the "png" format.
   *
   * @returns {Promise<string>}
   */
  capture(context, options = null) {
    if (!options) {
      options = {};
    }
    if (options.format == null) {
      options.format = "png";
    }
    if (options.quality == null) {
      options.quality = 92;
    }

    let message = {
      options,
      width: this.width,
      height: this.height,
    };

    return this.sendMessage(context, "Extension:Capture", message);
  }

  /**
   * @property {integer | null} innerWindowID
   *        The last known innerWindowID loaded into this tab's docShell. This
   *        property must remain in sync with the last known values of
   *        properties such as `url` and `title`. Any operations on the content
   *        of an out-of-process tab will automatically fail if the
   *        innerWindowID of the tab when the message is received does not match
   *        the value of this property when the message was sent.
   *        @readonly
   */
  get innerWindowID() {
    return this.browser.innerWindowID;
  }

  /**
   * @property {boolean} hasTabPermission
   *        Returns true if the extension has permission to access restricted
   *        properties of this tab, such as `url`, `title`, and `favIconUrl`.
   *        @readonly
   */
  get hasTabPermission() {
    return this.extension.hasPermission("tabs") || this.hasActiveTabPermission;
  }

  /**
   * @property {boolean} hasActiveTabPermission
   *        Returns true if the extension has the "activeTab" permission, and
   *        has been granted access to this tab due to a user executing an
   *        extension action.
   *
   *        If true, the extension may load scripts and CSS into this tab, and
   *        access restricted properties, such as its `url`.
   *        @readonly
   */
  get hasActiveTabPermission() {
    return (this.extension.hasPermission("activeTab") &&
            this.activeTabWindowID != null &&
            this.activeTabWindowID === this.innerWindowID);
  }

  /**
   * @property {boolean} incognito
   *        Returns true if this is a private browsing tab, false otherwise.
   *        @readonly
   */
  get _incognito() {
    return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
  }

  /**
   * @property {string} _url
   *        Returns the current URL of this tab. Does not do any permission
   *        checks.
   *        @readonly
   */
  get _url() {
    return this.browser.currentURI.spec;
  }

  /**
   * @property {string | null} url
   *        Returns the current URL of this tab if the extension has permission
   *        to read it, or null otherwise.
   *        @readonly
   */
  get url() {
    if (this.hasTabPermission) {
      return this._url;
    }
  }

  /**
   * @property {nsIURI | null} uri
   *        Returns the current URI of this tab if the extension has permission
   *        to read it, or null otherwise.
   *        @readonly
   */
  get uri() {
    if (this.hasTabPermission) {
      return this.browser.currentURI;
    }
  }

  /**
   * @property {string} _title
   *        Returns the current title of this tab. Does not do any permission
   *        checks.
   *        @readonly
   */
  get _title() {
    return this.browser.contentTitle || this.nativeTab.label;
  }


  /**
   * @property {nsIURI | null} title
   *        Returns the current title of this tab if the extension has permission
   *        to read it, or null otherwise.
   *        @readonly
   */
  get title() {
    if (this.hasTabPermission) {
      return this._title;
    }
  }

  /**
   * @property {string} _favIconUrl
   *        Returns the current favicon URL of this tab. Does not do any permission
   *        checks.
   *        @readonly
   *        @abstract
   */
  get _favIconUrl() {
    throw new Error("Not implemented");
  }

  /**
   * @property {nsIURI | null} faviconUrl
   *        Returns the current faviron URL of this tab if the extension has permission
   *        to read it, or null otherwise.
   *        @readonly
   */
  get favIconUrl() {
    if (this.hasTabPermission) {
      return this._favIconUrl;
    }
  }

  /**
   * @property {integer} lastAccessed
   *        Returns the last time the tab was accessed as the number of
   *        milliseconds since epoch.
   *        @readonly
   *        @abstract
   */
  get lastAccessed() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} audible
   *        Returns true if the tab is currently playing audio, false otherwise.
   *        @readonly
   *        @abstract
   */
  get audible() {
    throw new Error("Not implemented");
  }

  /**
   * @property {XULElement} browser
   *        Returns the XUL browser for the given tab.
   *        @readonly
   *        @abstract
   */
  get browser() {
    throw new Error("Not implemented");
  }

  /**
   * @property {nsIFrameLoader} browser
   *        Returns the frameloader for the given tab.
   *        @readonly
   */
  get frameLoader() {
    return this.browser.frameLoader;
  }

  /**
   * @property {string} cookieStoreId
   *        Returns the cookie store identifier for the given tab.
   *        @readonly
   *        @abstract
   */
  get cookieStoreId() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the visible area of the tab.
   *        @readonly
   *        @abstract
   */
  get height() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} index
   *        Returns the index of the tab in its window's tab list.
   *        @readonly
   *        @abstract
   */
  get index() {
    throw new Error("Not implemented");
  }

  /**
   * @property {MutedInfo} mutedInfo
   *        Returns information about the tab's current audio muting status.
   *        @readonly
   *        @abstract
   */
  get mutedInfo() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} pinned
   *        Returns true if the tab is pinned, false otherwise.
   *        @readonly
   *        @abstract
   */
  get pinned() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} active
   *        Returns true if the tab is the currently-selected tab, false
   *        otherwise.
   *        @readonly
   *        @abstract
   */
  get active() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} selected
   *        An alias for `active`.
   *        @readonly
   *        @abstract
   */
  get selected() {
    throw new Error("Not implemented");
  }

  /**
   * @property {string} status
   *        Returns the current loading status of the tab. May be either
   *        "loading" or "complete".
   *        @readonly
   *        @abstract
   */
  get status() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the visible area of the tab.
   *        @readonly
   *        @abstract
   */
  get width() {
    throw new Error("Not implemented");
  }

  /**
   * @property {DOMWindow} window
   *        Returns the browser window to which the tab belongs.
   *        @readonly
   *        @abstract
   */
  get window() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} window
   *        Returns the numeric ID of the browser window to which the tab belongs.
   *        @readonly
   *        @abstract
   */
  get windowId() {
    throw new Error("Not implemented");
  }

  /**
   * Returns true if this tab matches the the given query info object. Omitted
   * or null have no effect on the match.
   *
   * @param {object} queryInfo
   *        The query info against which to match.
   * @param {boolean} [queryInfo.active]
   *        Matches against the exact value of the tab's `active` attribute.
   * @param {boolean} [queryInfo.audible]
   *        Matches against the exact value of the tab's `audible` attribute.
   * @param {string} [queryInfo.cookieStoreId]
   *        Matches against the exact value of the tab's `cookieStoreId` attribute.
   * @param {boolean} [queryInfo.highlighted]
   *        Matches against the exact value of the tab's `highlighted` attribute.
   * @param {integer} [queryInfo.index]
   *        Matches against the exact value of the tab's `index` attribute.
   * @param {boolean} [queryInfo.muted]
   *        Matches against the exact value of the tab's `mutedInfo.muted` attribute.
   * @param {boolean} [queryInfo.pinned]
   *        Matches against the exact value of the tab's `pinned` attribute.
   * @param {string} [queryInfo.status]
   *        Matches against the exact value of the tab's `status` attribute.
   * @param {string} [queryInfo.title]
   *        Matches against the exact value of the tab's `title` attribute.
   *
   *        Note: Per specification, this should perform a pattern match, rather
   *        than an exact value match, and will do so in the future.
   * @param {MatchPattern} [queryInfo.url]
   *        Requires the tab's URL to match the given MatchPattern object.
   *
   * @returns {boolean}
   *        True if the tab matches the query.
   */
  matches(queryInfo) {
    const PROPS = ["active", "audible", "cookieStoreId", "highlighted", "index", "pinned", "status", "title"];

    if (PROPS.some(prop => queryInfo[prop] !== null && queryInfo[prop] !== this[prop])) {
      return false;
    }

    if (queryInfo.muted !== null) {
      if (queryInfo.muted !== this.mutedInfo.muted) {
        return false;
      }
    }

    if (queryInfo.url && !queryInfo.url.matches(this.uri)) {
      return false;
    }

    return true;
  }

  /**
   * Converts this tab object to a JSON-compatible object containing the values
   * of its properties which the extension is permitted to access, in the format
   * requried to be returned by WebExtension APIs.
   *
   * @param {Tab} [fallbackTab]
   *        A tab to retrieve geometry data from if the lazy geometry data for
   *        this tab hasn't been initialized yet.
   * @returns {object}
   */
  convert(fallbackTab = null) {
    let result = {
      id: this.id,
      index: this.index,
      windowId: this.windowId,
      highlighted: this.selected,
      active: this.selected,
      pinned: this.pinned,
      status: this.status,
      incognito: this.incognito,
      width: this.width,
      height: this.height,
      lastAccessed: this.lastAccessed,
      audible: this.audible,
      mutedInfo: this.mutedInfo,
    };

    // If the tab has not been fully layed-out yet, fallback to the geometry
    // from a different tab (usually the currently active tab).
    if (fallbackTab && (!result.width || !result.height)) {
      result.width = fallbackTab.width;
      result.height = fallbackTab.height;
    }

    if (this.extension.hasPermission("cookies")) {
      result.cookieStoreId = this.cookieStoreId;
    }

    if (this.hasTabPermission) {
      for (let prop of ["url", "title", "favIconUrl"]) {
        // We use the underscored variants here to avoid the redundant
        // permissions checks imposed on the public properties.
        let val = this[`_${prop}`];
        if (val) {
          result[prop] = val;
        }
      }
    }

    return result;
  }

  /**
   * Inserts a script or stylesheet in the given tab, and returns a promise
   * which resolves when the operation has completed.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the injection.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, where, and
   *        when.
   * @param {string} kind
   *        The kind of data being injected. Either "script" or "css".
   * @param {string} method
   *        The name of the method which was called to trigger the injection.
   *        Used to generate appropriate error messages on failure.
   *
   * @returns {Promise}
   *        Resolves to the result of the execution, once it has completed.
   * @private
   */
  _execute(context, details, kind, method) {
    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 (this.hasActiveTabPermission) {
      // If we have the "activeTab" permission for this tab, ignore
      // the host whitelist.
      options.matches = ["<all_urls>"];
    } else {
      options.matches = this.extension.whiteListedHosts.patterns.map(host => host.pattern);
    }

    if (details.code !== null) {
      options[`${kind}Code`] = details.code;
    }
    if (details.file !== null) {
      let url = context.uri.resolve(details.file);
      if (!this.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";
    }
    if (details.cssOrigin !== null) {
      options.css_origin = details.cssOrigin;
    } else {
      options.css_origin = "author";
    }

    options.wantReturnValue = true;

    return this.sendMessage(context, "Extension:Execute", {options});
  }

  /**
   * Executes a script in the tab's content window, and returns a Promise which
   * resolves to the result of the evaluation, or rejects to the value of any
   * error the injection generates.
   *
   * @param {BaseContext} context
   *        The extension context for which to inject the script.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, where, and
   *        when.
   *
   * @returns {Promise}
   *        Resolves to the result of the evaluation of the given script, once
   *        it has completed, or rejects with any error the evaluation
   *        generates.
   */
  executeScript(context, details) {
    return this._execute(context, details, "js", "executeScript");
  }

  /**
   * Injects CSS into the tab's content window, and returns a Promise which
   * resolves when the injection is complete.
   *
   * @param {BaseContext} context
   *        The extension context for which to inject the script.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, and where.
   *
   * @returns {Promise}
   *        Resolves when the injection has completed.
   */
  insertCSS(context, details) {
    return this._execute(context, details, "css", "insertCSS").then(() => {});
  }


  /**
   * Removes CSS which was previously into the tab's content window via
   * `insertCSS`, and returns a Promise which resolves when the operation is
   * complete.
   *
   * @param {BaseContext} context
   *        The extension context for which to remove the CSS.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to remove, and from where.
   *
   * @returns {Promise}
   *        Resolves when the operation has completed.
   */
  removeCSS(context, details) {
    return this._execute(context, details, "css", "removeCSS").then(() => {});
  }
}

defineLazyGetter(TabBase.prototype, "incognito", function() { return this._incognito; });

// Note: These must match the values in windows.json.
const WINDOW_ID_NONE = -1;
const WINDOW_ID_CURRENT = -2;

/**
 * A platform-independent base class for extension-specific wrappers around
 * native browser windows
 *
 * @param {Extension} extension
 *        The extension object for which this wrapper is being created.
 * @param {DOMWindow} window
 *        The browser DOM window which is being wrapped.
 * @param {integer} id
 *        The numeric ID of this DOM window object. This ID should be the same for
 *        every extension, and for the lifetime of the window.
 */
class WindowBase {
  constructor(extension, window, id) {
    this.extension = extension;
    this.window = window;
    this.id = id;
  }

  /**
   * @property {nsIXULWindow} xulWindow
   *        The nsIXULWindow object for this browser window.
   *        @readonly
   */
  get xulWindow() {
    return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDocShell)
               .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIXULWindow);
  }

  /**
   * Returns true if this window is the current window for the given extension
   * context, false otherwise.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the check.
   *
   * @returns {boolean}
   */
  isCurrentFor(context) {
    if (context && context.currentWindow) {
      return this.window === context.currentWindow;
    }
    return this.isLastFocused;
  }

  /**
   * @property {string} type
   *        The type of the window, as defined by the WebExtension API. May be
   *        either "normal" or "popup".
   *        @readonly
   */
  get type() {
    let {chromeFlags} = this.xulWindow;

    if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
      return "popup";
    }

    return "normal";
  }

  /**
   * Converts this window object to a JSON-compatible object which may be
   * returned to an extension, in the format requried to be returned by
   * WebExtension APIs.
   *
   * @param {object} [getInfo]
   *        An optional object, the properties of which determine what data is
   *        available on the result object.
   * @param {boolean} [getInfo.populate]
   *        Of true, the result object will contain a `tabs` property,
   *        containing an array of converted Tab objects, one for each tab in
   *        the window.
   *
   * @returns {object}
   */
  convert(getInfo) {
    let result = {
      id: this.id,
      focused: this.focused,
      top: this.top,
      left: this.left,
      width: this.width,
      height: this.height,
      incognito: this.incognito,
      type: this.type,
      state: this.state,
      alwaysOnTop: this.alwaysOnTop,
      title: this.title,
    };

    if (getInfo && getInfo.populate) {
      result.tabs = Array.from(this.getTabs(), tab => tab.convert());
    }

    return result;
  }

  /**
   * Returns true if this window matches the the given query info object. Omitted
   * or null have no effect on the match.
   *
   * @param {object} queryInfo
   *        The query info against which to match.
   * @param {boolean} [queryInfo.currentWindow]
   *        Matches against against the return value of `isCurrentFor()` for the
   *        given context.
   * @param {boolean} [queryInfo.lastFocusedWindow]
   *        Matches against the exact value of the window's `isLastFocused` attribute.
   * @param {boolean} [queryInfo.windowId]
   *        Matches against the exact value of the window's ID, taking into
   *        account the special WINDOW_ID_CURRENT value.
   * @param {string} [queryInfo.windowType]
   *        Matches against the exact value of the window's `type` attribute.
   * @param {BaseContext} context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {boolean}
   *        True if the window matches the query.
   */
  matches(queryInfo, context) {
    if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== this.isLastFocused) {
      return false;
    }

    if (queryInfo.windowType !== null && queryInfo.windowType !== this.type) {
      return false;
    }

    if (queryInfo.windowId !== null) {
      if (queryInfo.windowId === WINDOW_ID_CURRENT) {
        if (!this.isCurrentFor(context)) {
          return false;
        }
      } else if (queryInfo.windowId !== this.id) {
        return false;
      }
    }

    if (queryInfo.currentWindow !== null && queryInfo.currentWindow !== this.isCurrentFor(context)) {
      return false;
    }

    return true;
  }

  /**
   * @property {boolean} focused
   *        Returns true if the browser window is currently focused.
   *        @readonly
   *        @abstract
   */
  get focused() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} top
   *        Returns the pixel offset of the top of the window from the top of
   *        the screen.
   *        @readonly
   *        @abstract
   */
  get top() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} left
   *        Returns the pixel offset of the left of the window from the left of
   *        the screen.
   *        @readonly
   *        @abstract
   */
  get left() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} width
   *        Returns the pixel width of the window.
   *        @readonly
   *        @abstract
   */
  get width() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the window.
   *        @readonly
   *        @abstract
   */
  get height() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} incognito
   *        Returns true if this is a private browsing window, false otherwise.
   *        @readonly
   *        @abstract
   */
  get incognito() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} alwaysOnTop
   *        Returns true if this window is constrained to always remain above
   *        other windows.
   *        @readonly
   *        @abstract
   */
  get alwaysOnTop() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} isLastFocused
   *        Returns true if this is the browser window which most recently had
   *        focus.
   *        @readonly
   *        @abstract
   */
  get isLastFocused() {
    throw new Error("Not implemented");
  }

  /**
   * @property {string} state
   *        Returns or sets the current state of this window, as determined by
   *        `getState()`.
   *        @abstract
   */
  get state() {
    throw new Error("Not implemented");
  }

  set state(state) {
    throw new Error("Not implemented");
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns the window state of the given window.
   *
   * @param {DOMWindow} window
   *        The window for which to return a state.
   *
   * @returns {string}
   *        The window's state. One of "normal", "minimized", "maximized",
   *        "fullscreen", or "docked".
   * @static
   * @abstract
   */
  static getState(window) {
    throw new Error("Not implemented");
  }

  /**
   * Returns an iterator of TabBase objects for each tab in this window.
   *
   * @returns {Iterator<TabBase>}
   */
  getTabs() {
    throw new Error("Not implemented");
  }
  /* eslint-enable valid-jsdoc */
}

Object.assign(WindowBase, {WINDOW_ID_NONE, WINDOW_ID_CURRENT});

/**
 * The parameter type of "tab-attached" events, which are emitted when a
 * pre-existing tab is attached to a new window.
 *
 * @typedef {Object} TabAttachedEvent
 * @property {NativeTab} tab
 *        The native tab object in the window to which the tab is being
 *        attached. This may be a different object than was used to represent
 *        the tab in the old window.
 * @property {integer} tabId
 *        The ID of the tab being attached.
 * @property {integer} newWindowId
 *        The ID of the window to which the tab is being attached.
 * @property {integer} newPosition
 *        The position of the tab in the tab list of the new window.
 */

/**
 * The parameter type of "tab-detached" events, which are emitted when a
 * pre-existing tab is detached from a window, in order to be attached to a new
 * window.
 *
 * @typedef {Object} TabDetachedEvent
 * @property {NativeTab} tab
 *        The native tab object in the window from which the tab is being
 *        detached. This may be a different object than will be used to
 *        represent the tab in the new window.
 * @property {NativeTab} adoptedBy
 *        The native tab object in the window to which the tab will be attached,
 *        and is adopting the contents of this tab. This may be a different
 *        object than the tab in the previous window.
 * @property {integer} tabId
 *        The ID of the tab being detached.
 * @property {integer} oldWindowId
 *        The ID of the window from which the tab is being detached.
 * @property {integer} oldPosition
 *        The position of the tab in the tab list of the window from which it is
 *        being detached.
 */

/**
 * The parameter type of "tab-created" events, which are emitted when a
 * new tab is created.
 *
 * @typedef {Object} TabCreatedEvent
 * @property {NativeTab} tab
 *        The native tab object for the tab which is being created.
 */

/**
 * The parameter type of "tab-removed" events, which are emitted when a
 * tab is removed and destroyed.
 *
 * @typedef {Object} TabRemovedEvent
 * @property {NativeTab} tab
 *        The native tab object for the tab which is being removed.
 * @property {integer} tabId
 *        The ID of the tab being removed.
 * @property {integer} windowId
 *        The ID of the window from which the tab is being removed.
 * @property {boolean} isWindowClosing
 *        True if the tab is being removed because the window is closing.
 */

/**
 * An object containg basic, extension-independent information about the window
 * and tab that a XUL <browser> belongs to.
 *
 * @typedef {Object} BrowserData
 * @property {integer} tabId
 *        The numeric ID of the tab that a <browser> belongs to, or -1 if it
 *        does not belong to a tab.
 * @property {integer} windowId
 *        The numeric ID of the browser window that a <browser> belongs to, or -1
 *        if it does not belong to a browser window.
 */

/**
 * A platform-independent base class for the platform-specific TabTracker
 * classes, which track the opening and closing of tabs, and manage the mapping
 * of them between numeric IDs and native tab objects.
 *
 * Instances of this class are EventEmitters which emit the following events,
 * each with an argument of the given type:
 *
 * - "tab-attached" {@link TabAttacheEvent}
 * - "tab-detached" {@link TabDetachedEvent}
 * - "tab-created" {@link TabCreatedEvent}
 * - "tab-removed" {@link TabRemovedEvent}
 */
class TabTrackerBase extends EventEmitter {
  on(...args) {
    if (!this.initialized) {
      this.init();
    }

    return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
  }


  /**
   * Called to initialize the tab tracking listeners the first time that an
   * event listener is added.
   *
   * @protected
   * @abstract
   */
  init() {
    throw new Error("Not implemented");
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns the numeric ID for the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to return an ID.
   *
   * @returns {integer}
   *        The tab's numeric ID.
   * @abstract
   */
  getId(nativeTab) {
    throw new Error("Not implemented");
  }

  /**
   * Returns the native tab with the given numeric ID.
   *
   * @param {integer} tabId
   *        The numeric ID of the tab to return.
   * @param {*} default_
   *        The value to return if no tab exists with the given ID.
   *
   * @returns {NativeTab}
   * @throws {ExtensionError}
   *       If no tab exists with the given ID and a default return value is not
   *       provided.
   * @abstract
   */
  getTab(tabId, default_ = undefined) {
    throw new Error("Not implemented");
  }

  /**
   * Returns basic information about the tab and window that the given browser
   * belongs to.
   *
   * @param {XULElement} browser
   *        The XUL browser element for which to return data.
   *
   * @returns {BrowserData}
   * @abstract
   */
  /* eslint-enable valid-jsdoc */
  getBrowserData(browser) {
    throw new Error("Not implemented");
  }

  /**
   * @property {NativeTab} activeTab
   *        Returns the native tab object for the active tab in the
   *        most-recently focused window, or null if no live tabs currently
   *        exist.
   *        @abstract
   */
  get activeTab() {
    throw new Error("Not implemented");
  }
}

/**
 * A browser progress listener instance which calls a given listener function
 * whenever the status of the given browser changes.
 *
 * @param {function(Object)} listener
 *        A function to be called whenever the status of a tab's top-level
 *        browser. It is passed an object with a `browser` property pointing to
 *        the XUL browser, and a `status` property with a string description of
 *        the browser's status.
 * @private
 */
class StatusListener {
  constructor(listener) {
    this.listener = listener;
  }

  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";
    }

    if (status) {
      this.listener({browser, status});
    }
  }

  onLocationChange(browser, webProgress, request, locationURI, flags) {
    if (webProgress.isTopLevel) {
      let status = webProgress.isLoadingDocument ? "loading" : "complete";
      this.listener({browser, status, url: locationURI.spec});
    }
  }
}

/**
 * A platform-independent base class for the platform-specific WindowTracker
 * classes, which track the opening and closing of windows, and manage the
 * mapping of them between numeric IDs and native tab objects.
 */
class WindowTrackerBase extends EventEmitter {
  constructor() {
    super();

    this._handleWindowOpened = this._handleWindowOpened.bind(this);

    this._openListeners = new Set();
    this._closeListeners = new Set();

    this._listeners = new DefaultMap(() => new Set());

    this._statusListeners = new DefaultWeakMap(listener => {
      return new StatusListener(listener);
    });

    this._windowIds = new DefaultWeakMap(window => {
      window.QueryInterface(Ci.nsIInterfaceRequestor);

      return getWinUtils(window).outerWindowID;
    });
  }

  isBrowserWindow(window) {
    let {documentElement} = window.document;

    return documentElement.getAttribute("windowtype") === "navigator:browser";
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator for all currently active browser windows.
   *
   * @param {boolean} [includeInomplete = false]
   *        If true, include browser windows which are not yet fully loaded.
   *        Otherwise, only include windows which are.
   *
   * @returns {Iterator<DOMWindow>}
   */
  /* eslint-enable valid-jsdoc */
  * 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 = this.isBrowserWindow(window);
      }

      if (ok) {
        yield window;
      }
    }
  }

  /**
   * @property {DOMWindow|null} topWindow
   *        The currently active, or topmost, browser window, or null if no
   *        browser window is currently open.
   *        @readonly
   */
  get topWindow() {
    return Services.wm.getMostRecentWindow("navigator:browser");
  }

  /**
   * Returns the numeric ID for the given browser window.
   *
   * @param {DOMWindow} window
   *        The DOM window for which to return an ID.
   *
   * @returns {integer}
   *        The window's numeric ID.
   */
  getId(window) {
    return this._windowIds.get(window);
  }

  /**
   * Returns the browser window to which the given context belongs, or the top
   * browser window if the context does not belong to a browser window.
   *
   * @param {BaseContext} context
   *        The extension context for which to return the current window.
   *
   * @returns {DOMWindow|null}
   */
  getCurrentWindow(context) {
    return context.currentWindow || this.topWindow;
  }

  /**
   * Returns the browser window with the given ID.
   *
   * @param {integer} id
   *        The ID of the window to return.
   * @param {BaseContext} context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {DOMWindow}
   * @throws {ExtensionError}
   *        If no window exists with the given ID.
   */
  getWindow(id, context) {
    if (id === WINDOW_ID_CURRENT) {
      return this.getCurrentWindow(context);
    }

    for (let window of this.browserWindows(true)) {
      if (this.getId(window) === id) {
        return window;
      }
    }
    throw new ExtensionError(`Invalid window ID: ${id}`);
  }

  /**
   * @property {boolean} _haveListeners
   *        Returns true if any window open or close listeners are currently
   *        registered.
   * @private
   */
  get _haveListeners() {
    return this._openListeners.size > 0 || this._closeListeners.size > 0;
  }

  /**
   * Register the given listener function to be called whenever a new browser
   * window is opened.
   *
   * @param {function(DOMWindow)} listener
   *        The listener function to register.
   */
  addOpenListener(listener) {
    if (!this._haveListeners) {
      Services.ww.registerNotification(this);
    }

    this._openListeners.add(listener);

    for (let window of this.browserWindows(true)) {
      if (window.document.readyState !== "complete") {
        window.addEventListener("load", this);
      }
    }
  }

  /**
   * Unregister a listener function registered in a previous addOpenListener
   * call.
   *
   * @param {function(DOMWindow)} listener
   *        The listener function to unregister.
   */
  removeOpenListener(listener) {
    this._openListeners.delete(listener);

    if (!this._haveListeners) {
      Services.ww.unregisterNotification(this);
    }
  }

  /**
   * Register the given listener function to be called whenever a browser
   * window is closed.
   *
   * @param {function(DOMWindow)} listener
   *        The listener function to register.
   */
  addCloseListener(listener) {
    if (!this._haveListeners) {
      Services.ww.registerNotification(this);
    }

    this._closeListeners.add(listener);
  }

  /**
   * Unregister a listener function registered in a previous addCloseListener
   * call.
   *
   * @param {function(DOMWindow)} listener
   *        The listener function to unregister.
   */
  removeCloseListener(listener) {
    this._closeListeners.delete(listener);

    if (!this._haveListeners) {
      Services.ww.unregisterNotification(this);
    }
  }

  /**
   * Handles load events for recently-opened windows, and adds additional
   * listeners which may only be safely added when the window is fully loaded.
   *
   * @param {Event} event
   *        A DOM event to handle.
   * @private
   */
  handleEvent(event) {
    if (event.type === "load") {
      event.currentTarget.removeEventListener(event.type, this);

      let window = event.target.defaultView;
      if (!this.isBrowserWindow(window)) {
        return;
      }

      for (let listener of this._openListeners) {
        try {
          listener(window);
        } catch (e) {
          Cu.reportError(e);
        }
      }
    }
  }

  /**
   * Observes "domwindowopened" and "domwindowclosed" events, notifies the
   * appropriate listeners, and adds necessary additional listeners to the new
   * windows.
   *
   * @param {DOMWindow} window
   *        A DOM window.
   * @param {string} topic
   *        The topic being observed.
   * @private
   */
  observe(window, topic) {
    if (topic === "domwindowclosed") {
      if (!this.isBrowserWindow(window)) {
        return;
      }

      window.removeEventListener("load", this);
      for (let listener of this._closeListeners) {
        try {
          listener(window);
        } catch (e) {
          Cu.reportError(e);
        }
      }
    } else if (topic === "domwindowopened") {
      window.addEventListener("load", this);
    }
  }

  /**
   * Add an event listener to be called whenever the given DOM event is recieved
   * at the top level of any browser window.
   *
   * @param {string} type
   *        The type of event to listen for. May be any valid DOM event name, or
   *        one of the following special cases:
   *
   *        - "progress": Adds a tab progress listener to every browser window.
   *        - "status": Adds a StatusListener to every tab of every browser
   *           window.
   *        - "domwindowopened": Acts as an alias for addOpenListener.
   *        - "domwindowclosed": Acts as an alias for addCloseListener.
   * @param {function|object} listener
   *        The listener to invoke in response to the given events.
   *
   * @returns {undefined}
   */
  addListener(type, listener) {
    if (type === "domwindowopened") {
      return this.addOpenListener(listener);
    } else if (type === "domwindowclosed") {
      return this.addCloseListener(listener);
    }

    if (this._listeners.size === 0) {
      this.addOpenListener(this._handleWindowOpened);
    }

    if (type === "status") {
      listener = this._statusListeners.get(listener);
      type = "progress";
    }

    this._listeners.get(type).add(listener);

    // Register listener on all existing windows.
    for (let window of this.browserWindows()) {
      this._addWindowListener(window, type, listener);
    }
  }

  /**
   * Removes an event listener previously registered via an addListener call.
   *
   * @param {string} type
   *        The type of event to stop listening for.
   * @param {function|object} listener
   *        The listener to remove.
   *
   * @returns {undefined}
   */
  removeListener(type, listener) {
    if (type === "domwindowopened") {
      return this.removeOpenListener(listener);
    } else if (type === "domwindowclosed") {
      return this.removeCloseListener(listener);
    }

    if (type === "status") {
      listener = this._statusListeners.get(listener);
      type = "progress";
    }

    let listeners = this._listeners.get(type);
    listeners.delete(listener);

    if (listeners.size === 0) {
      this._listeners.delete(type);
      if (this._listeners.size === 0) {
        this.removeOpenListener(this._handleWindowOpened);
      }
    }

    // Unregister listener from all existing windows.
    let useCapture = type === "focus" || type === "blur";
    for (let window of this.browserWindows()) {
      if (type === "progress") {
        this.removeProgressListener(window, listener);
      } else {
        window.removeEventListener(type, listener, useCapture);
      }
    }
  }

  /**
   * Adds a listener for the given event to the given window.
   *
   * @param {DOMWindow} window
   *        The browser window to which to add the listener.
   * @param {string} eventType
   *        The type of DOM event to listen for, or "progress" to add a tab
   *        progress listener.
   * @param {function|object} listener
   *        The listener to add.
   * @private
   */
  _addWindowListener(window, eventType, listener) {
    let useCapture = eventType === "focus" || eventType === "blur";

    if (eventType === "progress") {
      this.addProgressListener(window, listener);
    } else {
      window.addEventListener(eventType, listener, useCapture);
    }
  }

  /**
   * A private method which is called whenever a new browser window is opened,
   * and adds the necessary listeners to it.
   *
   * @param {DOMWindow} window
   *        The window being opened.
   * @private
   */
  _handleWindowOpened(window) {
    for (let [eventType, listeners] of this._listeners) {
      for (let listener of listeners) {
        this._addWindowListener(window, eventType, listener);
      }
    }
  }

  /**
   * Adds a tab progress listener to the given browser window.
   *
   * @param {DOMWindow} window
   *        The browser window to which to add the listener.
   * @param {object} listener
   *        The tab progress listener to add.
   * @abstract
   */
  addProgressListener(window, listener) {
    throw new Error("Not implemented");
  }

  /**
   * Removes a tab progress listener from the given browser window.
   *
   * @param {DOMWindow} window
   *        The browser window from which to remove the listener.
   * @param {object} listener
   *        The tab progress listener to remove.
   * @abstract
   */
  removeProgressListener(window, listener) {
    throw new Error("Not implemented");
  }
}

/**
 * Manages native tabs, their wrappers, and their dynamic permissions for a
 * particular extension.
 *
 * @param {Extension} extension
 *        The extension for which to manage tabs.
 */
class TabManagerBase {
  constructor(extension) {
    this.extension = extension;

    this._tabs = new DefaultWeakMap(tab => this.wrapTab(tab));
  }

  /**
   * If the extension has requested activeTab permission, grant it those
   * permissions for the current inner window in the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to grant permissions.
   */
  addActiveTabPermission(nativeTab) {
    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.
      let tab = this.getWrapper(nativeTab);
      tab.activeTabWindowID = tab.innerWindowID;
    }
  }

  /**
   * Revoke the extension's activeTab permissions for the current inner window
   * of the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to revoke permissions.
   */
  revokeActiveTabPermission(nativeTab) {
    this.getWrapper(nativeTab).activeTabWindowID = null;
  }

  /**
   * Returns true if the extension has requested activeTab permission, and has
   * been granted permissions for the current inner window if this tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to check permissions.
   * @returns {boolean}
   *        True if the extension has activeTab permissions for this tab.
   */
  hasActiveTabPermission(nativeTab) {
    return this.getWrapper(nativeTab).hasActiveTabPermission;
  }

  /**
   * Returns true if the extension has permissions to access restricted
   * properties of the given native tab. In practice, this means that it has
   * either requested the "tabs" permission or has activeTab permissions for the
   * given tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to check permissions.
   * @returns {boolean}
   *        True if the extension has permissions for this tab.
   */
  hasTabPermission(nativeTab) {
    return this.getWrapper(nativeTab).hasTabPermission;
  }

  /**
   * Returns this extension's TabBase wrapper for the given native tab. This
   * method will always return the same wrapper object for any given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The tab for which to return a wrapper.
   *
   * @returns {TabBase}
   *        The wrapper for this tab.
   */
  getWrapper(nativeTab) {
    return this._tabs.get(nativeTab);
  }

  /**
   * Converts the given native tab to a JSON-compatible object, in the format
   * requried to be returned by WebExtension APIs, which may be safely passed to
   * extension code.
   *
   * @param {NativeTab} nativeTab
   *        The native tab to convert.
   * @param {NativeTab} [fallbackTab]
   *        A tab to retrieve geometry data from if the lazy geometry data for
   *        this tab hasn't been initialized yet.
   *
   * @returns {Object}
   */
  convert(nativeTab, fallbackTab = null) {
    return this.getWrapper(nativeTab)
               .convert(fallbackTab && this.getWrapper(fallbackTab));
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator of TabBase objects which match the given query info.
   *
   * @param {Object|null} [queryInfo = null]
   *        An object containing properties on which to filter. May contain any
   *        properties which are recognized by {@link TabBase#matches} or
   *        {@link WindowBase#matches}. Unknown properties will be ignored.
   * @param {BaseContext|null} [context = null]
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {Iterator<TabBase>}
   */
  * query(queryInfo = null, context = null) {
    for (let window of this.extension.windowManager.query(queryInfo, context)) {
      for (let tab of window.getTabs()) {
        if (!queryInfo || tab.matches(queryInfo)) {
          yield tab;
        }
      }
    }
  }

  /**
   * Returns a TabBase wrapper for the tab with the given ID.
   *
   * @param {integer} id
   *        The ID of the tab for which to return a wrapper.
   *
   * @returns {TabBase}
   * @throws {ExtensionError}
   *        If no tab exists with the given ID.
   * @abstract
   */
  get(tabId) {
    throw new Error("Not implemented");
  }

  /**
   * Returns a new TabBase instance wrapping the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to return a wrapper.
   *
   * @returns {TabBase}
   * @protected
   * @abstract
   */
  /* eslint-enable valid-jsdoc */
  wrapTab(nativeTab) {
    throw new Error("Not implemented");
  }
}

/**
 * Manages native browser windows and their wrappers for a particular extension.
 *
 * @param {Extension} extension
 *        The extension for which to manage windows.
 */
class WindowManagerBase {
  constructor(extension) {
    this.extension = extension;

    this._windows = new DefaultWeakMap(window => this.wrapWindow(window));
  }

  /**
   * Converts the given browser window to a JSON-compatible object, in the
   * format requried to be returned by WebExtension APIs, which may be safely
   * passed to extension code.
   *
   * @param {DOMWindow} window
   *        The browser window to convert.
   * @param {*} args
   *        Additional arguments to be passed to {@link WindowBase#convert}.
   *
   * @returns {Object}
   */
  convert(window, ...args) {
    return this.getWrapper(window).convert(...args);
  }

  /**
   * Returns this extension's WindowBase wrapper for the given browser window.
   * This method will always return the same wrapper object for any given
   * browser window.
   *
   * @param {DOMWindow} window
   *        The browser window for which to return a wrapper.
   *
   * @returns {WindowBase}
   *        The wrapper for this tab.
   */
  getWrapper(window) {
    return this._windows.get(window);
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator of WindowBase objects which match the given query info.
   *
   * @param {Object|null} [queryInfo = null]
   *        An object containing properties on which to filter. May contain any
   *        properties which are recognized by {@link WindowBase#matches}.
   *        Unknown properties will be ignored.
   * @param {BaseContext|null} [context = null]
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {Iterator<WindowBase>}
   */
  * query(queryInfo = null, context = null) {
    for (let window of this.getAll()) {
      if (!queryInfo || window.matches(queryInfo, context)) {
        yield window;
      }
    }
  }

  /**
   * Returns a WindowBase wrapper for the browser window with the given ID.
   *
   * @param {integer} id
   *        The ID of the browser window for which to return a wrapper.
   * @param {BaseContext} context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns{WindowBase}
   * @throws {ExtensionError}
   *        If no window exists with the given ID.
   * @abstract
   */
  get(windowId, context) {
    throw new Error("Not implemented");
  }

  /**
   * Returns an iterator of WindowBase wrappers for each currently existing
   * browser window.
   *
   * @returns {Iterator<WindowBase>}
   * @abstract
   */
  getAll() {
    throw new Error("Not implemented");
  }

  /**
   * Returns a new WindowBase instance wrapping the given browser window.
   *
   * @param {DOMWindow} window
   *        The browser window for which to return a wrapper.
   *
   * @returns {WindowBase}
   * @protected
   * @abstract
   */
  wrapWindow(window) {
    throw new Error("Not implemented");
  }
  /* eslint-enable valid-jsdoc */
}
PK
!<M3K3Kmodules/ExtensionUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ExtensionUtils"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                  "resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

function getConsole() {
  return new ConsoleAPI({
    maxLogLevelPref: "extensions.webextensions.log.level",
    prefix: "WebExtensions",
  });
}

XPCOMUtils.defineLazyGetter(this, "console", getConsole);

const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);

let nextId = 0;
XPCOMUtils.defineLazyGetter(this, "uniqueProcessID", () => appinfo.uniqueProcessID);

function getUniqueId() {
  return `${nextId++}-${uniqueProcessID}`;
}

async function promiseFileContents(file) {
  let res = await OS.File.read(file.path);
  return res.buffer;
}


/**
 * An Error subclass for which complete error messages are always passed
 * to extensions, rather than being interpreted as an unknown error.
 */
class ExtensionError extends Error {}

function filterStack(error) {
  return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "<Promise Chain>\n");
}

// Run a function and report exceptions.
function runSafeSyncWithoutClone(f, ...args) {
  try {
    return f(...args);
  } catch (e) {
    dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`);
    Cu.reportError(e);
  }
}

// Run a function and report exceptions.
function runSafeWithoutClone(f, ...args) {
  if (typeof(f) != "function") {
    dump(`Extension error: expected function\n${filterStack(Error())}`);
    return;
  }

  Promise.resolve().then(() => {
    runSafeSyncWithoutClone(f, ...args);
  });
}

// Run a function, cloning arguments into context.cloneScope, and
// report exceptions. |f| is expected to be in context.cloneScope.
function runSafeSync(context, f, ...args) {
  if (context.unloaded) {
    Cu.reportError("runSafeSync called after context unloaded");
    return;
  }

  try {
    args = Cu.cloneInto(args, context.cloneScope);
  } catch (e) {
    Cu.reportError(e);
    dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`);
  }
  return runSafeSyncWithoutClone(f, ...args);
}

// Run a function, cloning arguments into context.cloneScope, and
// report exceptions. |f| is expected to be in context.cloneScope.
function runSafe(context, f, ...args) {
  try {
    args = Cu.cloneInto(args, context.cloneScope);
  } catch (e) {
    Cu.reportError(e);
    dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`);
  }
  if (context.unloaded) {
    dump(`runSafe failure: context is already unloaded ${filterStack(new Error())}\n`);
    return undefined;
  }
  return runSafeWithoutClone(f, ...args);
}

// Return true if the given value is an instance of the given
// native type.
function instanceOf(value, type) {
  return {}.toString.call(value) == `[object ${type}]`;
}

/**
 * Similar to a WeakMap, but creates a new key with the given
 * constructor if one is not present.
 */
class DefaultWeakMap extends WeakMap {
  constructor(defaultConstructor, init) {
    super(init);
    this.defaultConstructor = defaultConstructor;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.defaultConstructor(key));
    }
    return super.get(key);
  }
}

class DefaultMap extends Map {
  constructor(defaultConstructor, init) {
    super(init);
    this.defaultConstructor = defaultConstructor;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.defaultConstructor(key));
    }
    return super.get(key);
  }
}

const _winUtils = new DefaultWeakMap(win => {
  return win.QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIDOMWindowUtils);
});
const getWinUtils = win => _winUtils.get(win);

function getInnerWindowID(window) {
  return getWinUtils(window).currentInnerWindowID;
}

function withHandlingUserInput(window, callable) {
  let handle = getWinUtils(window).setHandlingUserInput(true);
  try {
    return callable();
  } finally {
    handle.destruct();
  }
}

const LISTENERS = Symbol("listeners");
const ONCE_MAP = Symbol("onceMap");

class EventEmitter {
  constructor() {
    this[LISTENERS] = new Map();
    this[ONCE_MAP] = new WeakMap();
  }

  /**
   * Adds the given function as a listener for the given event.
   *
   * The listener function may optionally return a Promise which
   * resolves when it has completed all operations which event
   * dispatchers may need to block on.
   *
   * @param {string} event
   *       The name of the event to listen for.
   * @param {function(string, ...any)} listener
   *        The listener to call when events are emitted.
   */
  on(event, listener) {
    if (!this[LISTENERS].has(event)) {
      this[LISTENERS].set(event, new Set());
    }

    this[LISTENERS].get(event).add(listener);
  }

  /**
   * Removes the given function as a listener for the given event.
   *
   * @param {string} event
   *       The name of the event to stop listening for.
   * @param {function(string, ...any)} listener
   *        The listener function to remove.
   */
  off(event, listener) {
    if (this[LISTENERS].has(event)) {
      let set = this[LISTENERS].get(event);

      set.delete(listener);
      set.delete(this[ONCE_MAP].get(listener));
      if (!set.size) {
        this[LISTENERS].delete(event);
      }
    }
  }

  /**
   * Adds the given function as a listener for the given event once.
   *
   * @param {string} event
   *       The name of the event to listen for.
   * @param {function(string, ...any)} listener
   *        The listener to call when events are emitted.
   */
  once(event, listener) {
    let wrapper = (...args) => {
      this.off(event, wrapper);
      this[ONCE_MAP].delete(listener);

      return listener(...args);
    };
    this[ONCE_MAP].set(listener, wrapper);

    this.on(event, wrapper);
  }


  /**
   * Triggers all listeners for the given event, and returns a promise
   * which resolves when all listeners have been called, and any
   * promises they have returned have likewise resolved.
   *
   * @param {string} event
   *       The name of the event to emit.
   * @param {any} args
   *        Arbitrary arguments to pass to the listener functions, after
   *        the event name.
   * @returns {Promise}
   */
  emit(event, ...args) {
    let listeners = this[LISTENERS].get(event) || new Set();

    let promises = Array.from(listeners, listener => {
      return runSafeSyncWithoutClone(listener, event, ...args);
    });

    return Promise.all(promises);
  }
}

/**
 * A set with a limited number of slots, which flushes older entries as
 * newer ones are added.
 *
 * @param {integer} limit
 *        The maximum size to trim the set to after it grows too large.
 * @param {integer} [slop = limit * .25]
 *        The number of extra entries to allow in the set after it
 *        reaches the size limit, before it is truncated to the limit.
 * @param {iterable} [iterable]
 *        An iterable of initial entries to add to the set.
 */
class LimitedSet extends Set {
  constructor(limit, slop = Math.round(limit * .25), iterable = undefined) {
    super(iterable);
    this.limit = limit;
    this.slop = slop;
  }

  truncate(limit) {
    for (let item of this) {
      // Live set iterators can ge relatively expensive, since they need
      // to be updated after every modification to the set. Since
      // breaking out of the loop early will keep the iterator alive
      // until the next full GC, we're currently better off finishing
      // the entire loop even after we're done truncating.
      if (this.size > limit) {
        this.delete(item);
      }
    }
  }

  add(item) {
    if (!this.has(item) && this.size >= this.limit + this.slop) {
      this.truncate(this.limit - 1);
    }
    super.add(item);
  }
}

/**
 * Returns a Promise which resolves when the given document's DOM has
 * fully loaded.
 *
 * @param {Document} doc The document to await the load of.
 * @returns {Promise<Document>}
 */
function promiseDocumentReady(doc) {
  if (doc.readyState == "interactive" || doc.readyState == "complete") {
    return Promise.resolve(doc);
  }

  return new Promise(resolve => {
    doc.addEventListener("DOMContentLoaded", function onReady(event) {
      if (event.target === event.currentTarget) {
        doc.removeEventListener("DOMContentLoaded", onReady, true);
        resolve(doc);
      }
    }, true);
  });
}

/**
 * Returns a Promise which resolves when the given document is fully
 * loaded.
 *
 * @param {Document} doc The document to await the load of.
 * @returns {Promise<Document>}
 */
function promiseDocumentLoaded(doc) {
  if (doc.readyState == "complete") {
    return Promise.resolve(doc);
  }

  return new Promise(resolve => {
    doc.defaultView.addEventListener("load", function(event) {
      resolve(doc);
    }, {once: true});
  });
}

/**
 * Returns a Promise which resolves when the given event is dispatched to the
 * given element.
 *
 * @param {Element} element
 *        The element on which to listen.
 * @param {string} eventName
 *        The event to listen for.
 * @param {boolean} [useCapture = true]
 *        If true, listen for the even in the capturing rather than
 *        bubbling phase.
 * @param {Event} [test]
 *        An optional test function which, when called with the
 *        observer's subject and data, should return true if this is the
 *        expected event, false otherwise.
 * @returns {Promise<Event>}
 */
function promiseEvent(element, eventName, useCapture = true, test = event => true) {
  return new Promise(resolve => {
    function listener(event) {
      if (test(event)) {
        element.removeEventListener(eventName, listener, useCapture);
        resolve(event);
      }
    }
    element.addEventListener(eventName, listener, useCapture);
  });
}

/**
 * Returns a Promise which resolves the given observer topic has been
 * observed.
 *
 * @param {string} topic
 *        The topic to observe.
 * @param {function(nsISupports, string)} [test]
 *        An optional test function which, when called with the
 *        observer's subject and data, should return true if this is the
 *        expected notification, false otherwise.
 * @returns {Promise<object>}
 */
function promiseObserved(topic, test = () => true) {
  return new Promise(resolve => {
    let observer = (subject, topic, data) => {
      if (test(subject, data)) {
        Services.obs.removeObserver(observer, topic);
        resolve({subject, data});
      }
    };
    Services.obs.addObserver(observer, topic);
  });
}

function getMessageManager(target) {
  if (target instanceof Ci.nsIFrameLoaderOwner) {
    return target.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
  }
  return target.QueryInterface(Ci.nsIMessageSender);
}

function flushJarCache(jarPath) {
  Services.obs.notifyObservers(null, "flush-cache-entry", jarPath);
}

/**
 * Convert any of several different representations of a date/time to a Date object.
 * Accepts several formats:
 * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
 * either a number or a string.
 *
 * @param {Date|string|number} date
 *      The date to convert.
 * @returns {Date}
 *      A Date object
 */
function normalizeTime(date) {
  // Of all the formats we accept the "number of milliseconds since the epoch as a string"
  // is an outlier, everything else can just be passed directly to the Date constructor.
  return new Date((typeof date == "string" && /^\d+$/.test(date))
                        ? parseInt(date, 10) : date);
}

/**
 * Defines a lazy getter for the given property on the given object. The
 * first time the property is accessed, the return value of the getter
 * is defined on the current `this` object with the given property name.
 * Importantly, this means that a lazy getter defined on an object
 * prototype will be invoked separately for each object instance that
 * it's accessed on.
 *
 * @param {object} object
 *        The prototype object on which to define the getter.
 * @param {string|Symbol} prop
 *        The property name for which to define the getter.
 * @param {function} getter
 *        The function to call in order to generate the final property
 *        value.
 */
function defineLazyGetter(object, prop, getter) {
  let redefine = (obj, value) => {
    Object.defineProperty(obj, prop, {
      enumerable: true,
      configurable: true,
      writable: true,
      value,
    });
    return value;
  };

  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,

    get() {
      return redefine(this, getter.call(this));
    },

    set(value) {
      redefine(this, value);
    },
  });
}

/**
 * Acts as a proxy for a message manager or message manager owner, and
 * tracks docShell swaps so that messages are always sent to the same
 * receiver, even if it is moved to a different <browser>.
 *
 * @param {nsIMessageSender|Element} target
 *        The target message manager on which to send messages, or the
 *        <browser> element which owns it.
 */
class MessageManagerProxy {
  constructor(target) {
    this.listeners = new DefaultMap(() => new Map());

    if (target instanceof Ci.nsIMessageSender) {
      Object.defineProperty(this, "messageManager", {
        value: target,
        configurable: true,
        writable: true,
      });
    } else {
      this.addListeners(target);
    }
  }

  /**
   * Disposes of the proxy object, removes event listeners, and drops
   * all references to the underlying message manager.
   *
   * Must be called before the last reference to the proxy is dropped,
   * unless the underlying message manager or <browser> is also being
   * destroyed.
   */
  dispose() {
    if (this.eventTarget) {
      this.removeListeners(this.eventTarget);
      this.eventTarget = null;
    } else {
      this.messageManager = null;
    }
  }

  /**
   * Returns true if the given target is the same as, or owns, the given
   * message manager.
   *
   * @param {nsIMessageSender|MessageManagerProxy|Element} target
   *        The message manager, MessageManagerProxy, or <browser>
   *        element agaisnt which to match.
   * @param {nsIMessageSender} messageManager
   *        The message manager against which to match `target`.
   *
   * @returns {boolean}
   *        True if `messageManager` is the same object as `target`, or
   *        `target` is a MessageManagerProxy or <browser> element that
   *        is tied to it.
   */
  static matches(target, messageManager) {
    return target === messageManager || target.messageManager === messageManager;
  }

  /**
   * @property {nsIMessageSender|null} messageManager
   *        The message manager that is currently being proxied. This
   *        may change during the life of the proxy object, so should
   *        not be stored elsewhere.
   */
  get messageManager() {
    return this.eventTarget && this.eventTarget.messageManager;
  }

  /**
   * Sends a message on the proxied message manager.
   *
   * @param {array} args
   *        Arguments to be passed verbatim to the underlying
   *        sendAsyncMessage method.
   * @returns {undefined}
   */
  sendAsyncMessage(...args) {
    if (this.messageManager) {
      return this.messageManager.sendAsyncMessage(...args);
    }

    Cu.reportError(`Cannot send message: Other side disconnected: ${uneval(args)}`);
  }

  get isDisconnected() {
    return !this.messageManager;
  }

  /**
   * Adds a message listener to the current message manager, and
   * transfers it to the new message manager after a docShell swap.
   *
   * @param {string} message
   *        The name of the message to listen for.
   * @param {nsIMessageListener} listener
   *        The listener to add.
   * @param {boolean} [listenWhenClosed = false]
   *        If true, the listener will receive messages which were sent
   *        after the remote side of the listener began closing.
   */
  addMessageListener(message, listener, listenWhenClosed = false) {
    this.messageManager.addMessageListener(message, listener, listenWhenClosed);
    this.listeners.get(message).set(listener, listenWhenClosed);
  }

  /**
   * Adds a message listener from the current message manager.
   *
   * @param {string} message
   *        The name of the message to stop listening for.
   * @param {nsIMessageListener} listener
   *        The listener to remove.
   */
  removeMessageListener(message, listener) {
    this.messageManager.removeMessageListener(message, listener);

    let listeners = this.listeners.get(message);
    listeners.delete(listener);
    if (!listeners.size) {
      this.listeners.delete(message);
    }
  }

  /**
   * @private
   * Iterates over all of the currently registered message listeners.
   */
  * iterListeners() {
    for (let [message, listeners] of this.listeners) {
      for (let [listener, listenWhenClosed] of listeners) {
        yield {message, listener, listenWhenClosed};
      }
    }
  }

  /**
   * @private
   * Adds docShell swap listeners to the message manager owner.
   *
   * @param {Element} target
   *        The target element.
   */
  addListeners(target) {
    target.addEventListener("SwapDocShells", this);

    for (let {message, listener, listenWhenClosed} of this.iterListeners()) {
      target.addMessageListener(message, listener, listenWhenClosed);
    }

    this.eventTarget = target;
  }

  /**
   * @private
   * Removes docShell swap listeners to the message manager owner.
   *
   * @param {Element} target
   *        The target element.
   */
  removeListeners(target) {
    target.removeEventListener("SwapDocShells", this);

    for (let {message, listener} of this.iterListeners()) {
      target.removeMessageListener(message, listener);
    }
  }

  handleEvent(event) {
    if (event.type == "SwapDocShells") {
      this.removeListeners(this.eventTarget);
      this.addListeners(event.detail);
    }
  }
}

this.ExtensionUtils = {
  defineLazyGetter,
  flushJarCache,
  getConsole,
  getInnerWindowID,
  getMessageManager,
  getUniqueId,
  filterStack,
  getWinUtils,
  instanceOf,
  normalizeTime,
  promiseDocumentLoaded,
  promiseDocumentReady,
  promiseEvent,
  promiseFileContents,
  promiseObserved,
  runSafe,
  runSafeSync,
  runSafeSyncWithoutClone,
  runSafeWithoutClone,
  withHandlingUserInput,
  DefaultMap,
  DefaultWeakMap,
  EventEmitter,
  ExtensionError,
  LimitedSet,
  MessageManagerProxy,
};
PK
!<.eggmodules/FileUtils.jsm/* -*- 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 = [ "FileUtils" ];

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

XPCOMUtils.defineLazyServiceGetter(this, "gDirService",
                                   "@mozilla.org/file/directory_service;1",
                                   "nsIProperties");

this.FileUtils = {
  MODE_RDONLY: 0x01,
  MODE_WRONLY: 0x02,
  MODE_RDWR: 0x04,
  MODE_CREATE: 0x08,
  MODE_APPEND: 0x10,
  MODE_TRUNCATE: 0x20,

  PERMS_FILE: 0o644,
  PERMS_DIRECTORY: 0o755,

  /**
   * Gets a file at the specified hierarchy under a nsIDirectoryService key.
   * @param   key
   *          The Directory Service Key to start from
   * @param   pathArray
   *          An array of path components to locate beneath the directory
   *          specified by |key|. The last item in this array must be the
   *          leaf name of a file.
   * @return  nsIFile object for the file specified. The file is NOT created
   *          if it does not exist, however all required directories along
   *          the way are.
   */
  getFile: function FileUtils_getFile(key, pathArray, followLinks) {
    var file = this.getDir(key, pathArray.slice(0, -1), true, followLinks);
    file.append(pathArray[pathArray.length - 1]);
    return file;
  },

  /**
   * Gets a directory at the specified hierarchy under a nsIDirectoryService
   * key.
   * @param   key
   *          The Directory Service Key to start from
   * @param   pathArray
   *          An array of path components to locate beneath the directory
   *          specified by |key|
   * @param   shouldCreate
   *          true if the directory hierarchy specified in |pathArray|
   *          should be created if it does not exist, false otherwise.
   * @param   followLinks (optional)
   *          true if links should be followed, false otherwise.
   * @return  nsIFile object for the location specified.
   */
  getDir: function FileUtils_getDir(key, pathArray, shouldCreate, followLinks) {
    var dir = gDirService.get(key, Ci.nsIFile);
    for (var i = 0; i < pathArray.length; ++i) {
      dir.append(pathArray[i]);
    }

    if (shouldCreate) {
      try {
        dir.create(Ci.nsIFile.DIRECTORY_TYPE, this.PERMS_DIRECTORY);
      } catch (ex) {
        if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
          throw ex;
        }
        // Ignore the exception due to a directory that already exists.
      }
    }

    if (!followLinks)
      dir.followLinks = false;
    return dir;
  },

  /**
   * Opens a file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   */
  openFileOutputStream: function FileUtils_openFileOutputStream(file, modeFlags) {
    var fos = Cc["@mozilla.org/network/file-output-stream;1"].
              createInstance(Ci.nsIFileOutputStream);
    return this._initFileOutputStream(fos, file, modeFlags);
  },

  /**
   * Opens an atomic file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   *       OpeanAtomicFileOutputStream is generally better than openSafeFileOutputStream
   *       baecause flushing is not needed in most of the issues.
   */
  openAtomicFileOutputStream: function FileUtils_openAtomicFileOutputStream(file, modeFlags) {
    var fos = Cc["@mozilla.org/network/atomic-file-output-stream;1"].
              createInstance(Ci.nsIFileOutputStream);
    return this._initFileOutputStream(fos, file, modeFlags);
  },

  /**
   * Opens a safe file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   */
  openSafeFileOutputStream: function FileUtils_openSafeFileOutputStream(file, modeFlags) {
    var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
              createInstance(Ci.nsIFileOutputStream);
    return this._initFileOutputStream(fos, file, modeFlags);
  },

 _initFileOutputStream: function FileUtils__initFileOutputStream(fos, file, modeFlags) {
    if (modeFlags === undefined)
      modeFlags = this.MODE_WRONLY | this.MODE_CREATE | this.MODE_TRUNCATE;
    fos.init(file, modeFlags, this.PERMS_FILE, fos.DEFER_OPEN);
    return fos;
  },

  /**
   * Closes an atomic file output stream.
   * @param   stream
   *          The stream to close.
   */
  closeAtomicFileOutputStream: function FileUtils_closeAtomicFileOutputStream(stream) {
    if (stream instanceof Ci.nsISafeOutputStream) {
      try {
        stream.finish();
        return;
      } catch (e) {
      }
    }
    stream.close();
  },

  /**
   * Closes a safe file output stream.
   * @param   stream
   *          The stream to close.
   */
  closeSafeFileOutputStream: function FileUtils_closeSafeFileOutputStream(stream) {
    if (stream instanceof Ci.nsISafeOutputStream) {
      try {
        stream.finish();
        return;
      } catch (e) {
      }
    }
    stream.close();
  },

  File: Components.Constructor("@mozilla.org/file/local;1", Ci.nsILocalFile, "initWithPath")
};
PK
!<4\OOmodules/Finder.jsm// vim: set ts=2 sw=2 sts=2 tw=80:
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["Finder", "GetClipboardSearchString"];

const { interfaces: Ci, classes: Cc, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
  "resource://gre/modules/BrowserUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
                                         "@mozilla.org/intl/texttosuburi;1",
                                         "nsITextToSubURI");
XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
                                         "@mozilla.org/widget/clipboard;1",
                                         "nsIClipboard");
XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
                                         "@mozilla.org/widget/clipboardhelper;1",
                                         "nsIClipboardHelper");

const kSelectionMaxLen = 150;
const kMatchesCountLimitPref = "accessibility.typeaheadfind.matchesCountLimit";

function Finder(docShell) {
  this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
  this._fastFind.init(docShell);

  this._currentFoundRange = null;
  this._docShell = docShell;
  this._listeners = [];
  this._previousLink = null;
  this._searchString = null;
  this._highlighter = null;

  docShell.QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIWebProgress)
          .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
  BrowserUtils.getRootWindow(this._docShell).addEventListener("unload",
    this.onLocationChange.bind(this, { isTopLevel: true }));
}

Finder.prototype = {
  get iterator() {
    if (this._iterator)
      return this._iterator;
    this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
    return this._iterator;
  },

  destroy() {
    if (this._iterator)
      this._iterator.reset();
    let window = this._getWindow();
    if (this._highlighter && window) {
      // if we clear all the references before we hide the highlights (in both
      // highlighting modes), we simply can't use them to find the ranges we
      // need to clear from the selection.
      this._highlighter.hide(window);
      this._highlighter.clear(window);
    }
    this.listeners = [];
    this._docShell.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress)
      .removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
    this._listeners = [];
    this._currentFoundRange = this._fastFind = this._docShell = this._previousLink =
      this._highlighter = null;
  },

  addResultListener(aListener) {
    if (this._listeners.indexOf(aListener) === -1)
      this._listeners.push(aListener);
  },

  removeResultListener(aListener) {
    this._listeners = this._listeners.filter(l => l != aListener);
  },

  _notify(options) {
    if (typeof options.storeResult != "boolean")
      options.storeResult = true;

    if (options.storeResult) {
      this._searchString = options.searchString;
      this.clipboardSearchString = options.searchString
    }

    let foundLink = this._fastFind.foundLink;
    let linkURL = null;
    if (foundLink) {
      let docCharset = null;
      let ownerDoc = foundLink.ownerDocument;
      if (ownerDoc)
        docCharset = ownerDoc.characterSet;

      linkURL = TextToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
    }

    options.linkURL = linkURL;
    options.rect = this._getResultRect();
    options.searchString = this._searchString;

    if (!this.iterator.continueRunning({
      caseSensitive: this._fastFind.caseSensitive,
      entireWord: this._fastFind.entireWord,
      linksOnly: options.linksOnly,
      word: options.searchString
    })) {
      this.iterator.stop();
    }

    this.highlighter.update(options);
    this.requestMatchesCount(options.searchString, options.linksOnly);

    this._outlineLink(options.drawOutline);

    for (let l of this._listeners) {
      try {
        l.onFindResult(options);
      } catch (ex) {}
    }
  },

  get searchString() {
    if (!this._searchString && this._fastFind.searchString)
      this._searchString = this._fastFind.searchString;
    return this._searchString;
  },

  get clipboardSearchString() {
    return GetClipboardSearchString(this._getWindow()
                                        .QueryInterface(Ci.nsIInterfaceRequestor)
                                        .getInterface(Ci.nsIWebNavigation)
                                        .QueryInterface(Ci.nsILoadContext));
  },

  set clipboardSearchString(aSearchString) {
    if (!aSearchString || !Clipboard.supportsFindClipboard())
      return;

    ClipboardHelper.copyStringToClipboard(aSearchString,
                                          Ci.nsIClipboard.kFindClipboard);
  },

  set caseSensitive(aSensitive) {
    if (this._fastFind.caseSensitive === aSensitive)
      return;
    this._fastFind.caseSensitive = aSensitive;
    this.iterator.reset();
  },

  set entireWord(aEntireWord) {
    if (this._fastFind.entireWord === aEntireWord)
      return;
    this._fastFind.entireWord = aEntireWord;
    this.iterator.reset();
  },

  get highlighter() {
    if (this._highlighter)
      return this._highlighter;

    const {FinderHighlighter} = Cu.import("resource://gre/modules/FinderHighlighter.jsm", {});
    return this._highlighter = new FinderHighlighter(this);
  },

  get matchesCountLimit() {
    if (typeof this._matchesCountLimit == "number")
      return this._matchesCountLimit;

    this._matchesCountLimit = Services.prefs.getIntPref(kMatchesCountLimitPref) || 0;
    return this._matchesCountLimit;
  },

  _lastFindResult: null,

  /**
   * Used for normal search operations, highlights the first match.
   *
   * @param aSearchString String to search for.
   * @param aLinksOnly Only consider nodes that are links for the search.
   * @param aDrawOutline Puts an outline around matched links.
   */
  fastFind(aSearchString, aLinksOnly, aDrawOutline) {
    this._lastFindResult = this._fastFind.find(aSearchString, aLinksOnly);
    let searchString = this._fastFind.searchString;
    this._notify({
      searchString,
      result: this._lastFindResult,
      findBackwards: false,
      findAgain: false,
      drawOutline: aDrawOutline,
      linksOnly: aLinksOnly
    });
  },

  /**
   * Repeat the previous search. Should only be called after a previous
   * call to Finder.fastFind.
   *
   * @param aFindBackwards Controls the search direction:
   *    true: before current match, false: after current match.
   * @param aLinksOnly Only consider nodes that are links for the search.
   * @param aDrawOutline Puts an outline around matched links.
   */
  findAgain(aFindBackwards, aLinksOnly, aDrawOutline) {
    this._lastFindResult = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
    let searchString = this._fastFind.searchString;
    this._notify({
      searchString,
      result: this._lastFindResult,
      findBackwards: aFindBackwards,
      findAgain: true,
      drawOutline: aDrawOutline,
      linksOnly: aLinksOnly
    });
  },

  /**
   * Forcibly set the search string of the find clipboard to the currently
   * selected text in the window, on supported platforms (i.e. OSX).
   */
  setSearchStringToSelection() {
    let searchString = this.getActiveSelectionText();

    // Empty strings are rather useless to search for.
    if (!searchString.length)
      return null;

    this.clipboardSearchString = searchString;
    return searchString;
  },

  async highlight(aHighlight, aWord, aLinksOnly) {
    await this.highlighter.highlight(aHighlight, aWord, aLinksOnly);
  },

  getInitialSelection() {
    this._getWindow().setTimeout(() => {
      let initialSelection = this.getActiveSelectionText();
      for (let l of this._listeners) {
        try {
          l.onCurrentSelection(initialSelection, true);
        } catch (ex) {}
      }
    }, 0);
  },

  getActiveSelectionText() {
    let focusedWindow = {};
    let focusedElement =
      Services.focus.getFocusedElementForWindow(this._getWindow(), true,
                                                focusedWindow);
    focusedWindow = focusedWindow.value;

    let selText;

    if (focusedElement instanceof Ci.nsIDOMNSEditableElement &&
        focusedElement.editor) {
      // The user may have a selection in an input or textarea.
      selText = focusedElement.editor.selectionController
        .getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
        .toString();
    } else {
      // Look for any selected text on the actual page.
      selText = focusedWindow.getSelection().toString();
    }

    if (!selText)
      return "";

    // Process our text to get rid of unwanted characters.
    selText = selText.trim().replace(/\s+/g, " ");
    let truncLength = kSelectionMaxLen;
    if (selText.length > truncLength) {
      let truncChar = selText.charAt(truncLength).charCodeAt(0);
      if (truncChar >= 0xDC00 && truncChar <= 0xDFFF)
        truncLength++;
      selText = selText.substr(0, truncLength);
    }

    return selText;
  },

  enableSelection() {
    this._fastFind.setSelectionModeAndRepaint(Ci.nsISelectionController.SELECTION_ON);
    this._restoreOriginalOutline();
  },

  removeSelection() {
    this._fastFind.collapseSelection();
    this.enableSelection();
    this.highlighter.clear(this._getWindow());
  },

  focusContent() {
    // Allow Finder listeners to cancel focusing the content.
    for (let l of this._listeners) {
      try {
        if ("shouldFocusContent" in l &&
            !l.shouldFocusContent())
          return;
      } catch (ex) {
        Cu.reportError(ex);
      }
    }

    let fastFind = this._fastFind;
    const fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
    try {
      // Try to find the best possible match that should receive focus and
      // block scrolling on focus since find already scrolls. Further
      // scrolling is due to user action, so don't override this.
      if (fastFind.foundLink) {
        fm.setFocus(fastFind.foundLink, fm.FLAG_NOSCROLL);
      } else if (fastFind.foundEditable) {
        fm.setFocus(fastFind.foundEditable, fm.FLAG_NOSCROLL);
        fastFind.collapseSelection();
      } else {
        this._getWindow().focus()
      }
    } catch (e) {}
  },

  onFindbarClose() {
    this.enableSelection();
    this.highlighter.highlight(false);
    this.iterator.reset();
    BrowserUtils.trackToolbarVisibility(this._docShell, "findbar", false);
  },

  onFindbarOpen() {
    BrowserUtils.trackToolbarVisibility(this._docShell, "findbar", true);
  },

  onModalHighlightChange(useModalHighlight) {
    if (this._highlighter)
      this._highlighter.onModalHighlightChange(useModalHighlight);
  },

  onHighlightAllChange(highlightAll) {
    if (this._highlighter)
      this._highlighter.onHighlightAllChange(highlightAll);
    if (this._iterator)
      this._iterator.reset();
  },

  keyPress(aEvent) {
    let controller = this._getSelectionController(this._getWindow());

    switch (aEvent.keyCode) {
      case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
        if (this._fastFind.foundLink) {
          let view = this._fastFind.foundLink.ownerGlobal;
          this._fastFind.foundLink.dispatchEvent(new view.MouseEvent("click", {
            view,
            cancelable: true,
            bubbles: true,
            ctrlKey: aEvent.ctrlKey,
            altKey: aEvent.altKey,
            shiftKey: aEvent.shiftKey,
            metaKey: aEvent.metaKey
          }));
        }
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_TAB:
        let direction = Services.focus.MOVEFOCUS_FORWARD;
        if (aEvent.shiftKey) {
          direction = Services.focus.MOVEFOCUS_BACKWARD;
        }
        Services.focus.moveFocus(this._getWindow(), null, direction, 0);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
        controller.scrollPage(false);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
        controller.scrollPage(true);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_UP:
        controller.scrollLine(false);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
        controller.scrollLine(true);
        break;
    }
  },

  _notifyMatchesCount(result = this._currentMatchesCountResult) {
    // The `_currentFound` property is only used for internal bookkeeping.
    delete result._currentFound;
    result.limit = this.matchesCountLimit;
    if (result.total == result.limit)
      result.total = -1;

    for (let l of this._listeners) {
      try {
        l.onMatchesCountResult(result);
      } catch (ex) {}
    }

    this._currentMatchesCountResult = null;
  },

  requestMatchesCount(aWord, aLinksOnly) {
    if (this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
        this.searchString == "" || !aWord || !this.matchesCountLimit) {
      this._notifyMatchesCount({
        total: 0,
        current: 0
      });
      return;
    }

    this._currentFoundRange = this._fastFind.getFoundRange();

    let params = {
      caseSensitive: this._fastFind.caseSensitive,
      entireWord: this._fastFind.entireWord,
      linksOnly: aLinksOnly,
      word: aWord
    };
    if (!this.iterator.continueRunning(params))
      this.iterator.stop();

    this.iterator.start(Object.assign(params, {
      finder: this,
      limit: this.matchesCountLimit,
      listener: this,
      useCache: true,
    })).then(() => {
      // Without a valid result, there's nothing to notify about. This happens
      // when the iterator was started before and won the race.
      if (!this._currentMatchesCountResult || !this._currentMatchesCountResult.total)
        return;
      this._notifyMatchesCount();
    });
  },

  // FinderIterator listener implementation

  onIteratorRangeFound(range) {
    let result = this._currentMatchesCountResult;
    if (!result)
      return;

    ++result.total;
    if (!result._currentFound) {
      ++result.current;
      result._currentFound = (this._currentFoundRange &&
        range.startContainer == this._currentFoundRange.startContainer &&
        range.startOffset == this._currentFoundRange.startOffset &&
        range.endContainer == this._currentFoundRange.endContainer &&
        range.endOffset == this._currentFoundRange.endOffset);
    }
  },

  onIteratorReset() {},

  onIteratorRestart({ word, linksOnly }) {
    this.requestMatchesCount(word, linksOnly);
  },

  onIteratorStart() {
    this._currentMatchesCountResult = {
      total: 0,
      current: 0,
      _currentFound: false
    };
  },

  _getWindow() {
    if (!this._docShell)
      return null;
    return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
  },

  /**
   * Get the bounding selection rect in CSS px relative to the origin of the
   * top-level content document.
   */
  _getResultRect() {
    let topWin = this._getWindow();
    let win = this._fastFind.currentWindow;
    if (!win)
      return null;

    let selection = win.getSelection();
    if (!selection.rangeCount || selection.isCollapsed) {
      // The selection can be into an input or a textarea element.
      let nodes = win.document.querySelectorAll("input, textarea");
      for (let node of nodes) {
        if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
          try {
            let sc = node.editor.selectionController;
            selection = sc.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
            if (selection.rangeCount && !selection.isCollapsed) {
              break;
            }
          } catch (e) {
            // If this textarea is hidden, then its selection controller might
            // not be intialized. Ignore the failure.
          }
        }
      }
    }

    if (!selection.rangeCount || selection.isCollapsed) {
      return null;
    }

    let utils = topWin.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDOMWindowUtils);

    let scrollX = {}, scrollY = {};
    utils.getScrollXY(false, scrollX, scrollY);

    for (let frame = win; frame != topWin; frame = frame.parent) {
      let rect = frame.frameElement.getBoundingClientRect();
      let left = frame.getComputedStyle(frame.frameElement).borderLeftWidth;
      let top = frame.getComputedStyle(frame.frameElement).borderTopWidth;
      scrollX.value += rect.left + parseInt(left, 10);
      scrollY.value += rect.top + parseInt(top, 10);
    }
    let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
    return rect.translate(scrollX.value, scrollY.value);
  },

  _outlineLink(aDrawOutline) {
    let foundLink = this._fastFind.foundLink;

    // Optimization: We are drawing outlines and we matched
    // the same link before, so don't duplicate work.
    if (foundLink == this._previousLink && aDrawOutline)
      return;

    this._restoreOriginalOutline();

    if (foundLink && aDrawOutline) {
      // Backup original outline
      this._tmpOutline = foundLink.style.outline;
      this._tmpOutlineOffset = foundLink.style.outlineOffset;

      // Draw pseudo focus rect
      // XXX Should we change the following style for FAYT pseudo focus?
      // XXX Shouldn't we change default design if outline is visible
      //     already?
      // Don't set the outline-color, we should always use initial value.
      foundLink.style.outline = "1px dotted";
      foundLink.style.outlineOffset = "0";

      this._previousLink = foundLink;
    }
  },

  _restoreOriginalOutline() {
    // Removes the outline around the last found link.
    if (this._previousLink) {
      this._previousLink.style.outline = this._tmpOutline;
      this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
      this._previousLink = null;
    }
  },

  _getSelectionController(aWindow) {
    // display: none iframes don't have a selection controller, see bug 493658
    try {
      if (!aWindow.innerWidth || !aWindow.innerHeight)
        return null;
    } catch (e) {
      // If getting innerWidth or innerHeight throws, we can't get a selection
      // controller.
      return null;
    }

    // Yuck. See bug 138068.
    let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIWebNavigation)
                          .QueryInterface(Ci.nsIDocShell);

    let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsISelectionDisplay)
                             .QueryInterface(Ci.nsISelectionController);
    return controller;
  },

  // Start of nsIWebProgressListener implementation.

  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    if (!aWebProgress.isTopLevel)
      return;
    // Ignore events that don't change the document.
    if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
      return;

    // Avoid leaking if we change the page.
    this._lastFindResult = this._previousLink = this._currentFoundRange = null;
    this.highlighter.onLocationChange();
    this.iterator.reset();
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsISupportsWeakReference])
};

function GetClipboardSearchString(aLoadContext) {
  let searchString = "";
  if (!Clipboard.supportsFindClipboard())
    return searchString;

  try {
    let trans = Cc["@mozilla.org/widget/transferable;1"]
                  .createInstance(Ci.nsITransferable);
    trans.init(aLoadContext);
    trans.addDataFlavor("text/unicode");

    Clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);

    let data = {};
    let dataLen = {};
    trans.getTransferData("text/unicode", data, dataLen);
    if (data.value) {
      data = data.value.QueryInterface(Ci.nsISupportsString);
      searchString = data.toString();
    }
  } catch (ex) {}

  return searchString;
}

this.Finder = Finder;
this.GetClipboardSearchString = GetClipboardSearchString;
PK
!<=^SSmodules/FinderHighlighter.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["FinderHighlighter"];

const { interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
  const kDebugPref = "findbar.modalHighlight.debug";
  return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
});

const kContentChangeThresholdPx = 5;
const kBrightTextSampleSize = 5;
// This limit is arbitrary and doesn't scale for low-powered machines or
// high-powered machines. Netbooks will probably need a much lower limit, for
// example. Though getting something out there is better than nothing.
const kPageIsTooBigPx = 500000;
const kModalHighlightRepaintLoFreqMs = 100;
const kModalHighlightRepaintHiFreqMs = 16;
const kHighlightAllPref = "findbar.highlightAll";
const kModalHighlightPref = "findbar.modalHighlight";
const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
  "font-size-adjust", "font-stretch", "font-variant", "font-weight", "line-height",
  "letter-spacing", "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
  let parts = prop.split("-");
  return parts.shift() + parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("");
});
const kRGBRE = /^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*/i;
// This uuid is used to prefix HTML element IDs in order to make them unique and
// hard to clash with IDs content authors come up with.
const kModalIdPrefix = "cedee4d0-74c5-4f2d-ab43-4d37c0f9d463";
const kModalOutlineId = kModalIdPrefix + "-findbar-modalHighlight-outline";
const kOutlineBoxColor = "255,197,53";
const kOutlineBoxBorderSize = 2;
const kOutlineBoxBorderRadius = 3;
const kModalStyles = {
  outlineNode: [
    ["background-color", `rgb(${kOutlineBoxColor})`],
    ["background-clip", "padding-box"],
    ["border", `${kOutlineBoxBorderSize}px solid`],
    ["-moz-border-top-colors", `rgba(${kOutlineBoxColor},.1) rgba(${kOutlineBoxColor},.4) rgba(${kOutlineBoxColor},.7)`],
    ["-moz-border-right-colors", `rgba(${kOutlineBoxColor},.1) rgba(${kOutlineBoxColor},.4) rgba(${kOutlineBoxColor},.7)`],
    ["-moz-border-bottom-colors", `rgba(${kOutlineBoxColor},.1) rgba(${kOutlineBoxColor},.4) rgba(${kOutlineBoxColor},.7)`],
    ["-moz-border-left-colors", `rgba(${kOutlineBoxColor},.1) rgba(${kOutlineBoxColor},.4) rgba(${kOutlineBoxColor},.7)`],
    ["border-radius", `${kOutlineBoxBorderRadius}px`],
    ["box-shadow", `0 ${kOutlineBoxBorderSize}px 0 0 rgba(0,0,0,.1)`],
    ["color", "#000"],
    ["display", "-moz-box"],
    ["margin", `-${kOutlineBoxBorderSize}px 0 0 -${kOutlineBoxBorderSize}px !important`],
    ["overflow", "hidden"],
    ["pointer-events", "none"],
    ["position", "absolute"],
    ["white-space", "nowrap"],
    ["will-change", "transform"],
    ["z-index", 2]
  ],
  outlineNodeDebug: [ ["z-index", 2147483647] ],
  outlineText: [
    ["margin", "0 !important"],
    ["padding", "0 !important"],
    ["vertical-align", "top !important"]
  ],
  maskNode: [
    ["background", "rgba(0,0,0,.25)"],
    ["pointer-events", "none"],
    ["position", "absolute"],
    ["z-index", 1]
  ],
  maskNodeTransition: [
    ["transition", "background .2s ease-in"]
  ],
  maskNodeDebug: [
    ["z-index", 2147483646],
    ["top", 0],
    ["left", 0]
  ],
  maskNodeBrightText: [ ["background", "rgba(255,255,255,.25)"] ]
};
const kModalOutlineAnim = {
  "keyframes": [
    { transform: "scaleX(1) scaleY(1)" },
    { transform: "scaleX(1.5) scaleY(1.5)", offset: .5, easing: "ease-in" },
    { transform: "scaleX(1) scaleY(1)" }
  ],
  duration: 50,
};
const kNSHTML = "http://www.w3.org/1999/xhtml";

function mockAnonymousContentNode(domNode) {
  return {
    setTextContentForElement(id, text) {
      (domNode.querySelector("#" + id) || domNode).textContent = text;
    },
    getAttributeForElement(id, attrName) {
      let node = domNode.querySelector("#" + id) || domNode;
      if (!node.hasAttribute(attrName))
        return undefined;
      return node.getAttribute(attrName);
    },
    setAttributeForElement(id, attrName, attrValue) {
      (domNode.querySelector("#" + id) || domNode).setAttribute(attrName, attrValue);
    },
    removeAttributeForElement(id, attrName) {
      let node = domNode.querySelector("#" + id) || domNode;
      if (!node.hasAttribute(attrName))
        return;
      node.removeAttribute(attrName);
    },
    remove() {
      try {
        domNode.remove();
      } catch (ex) {}
    },
    setAnimationForElement(id, keyframes, duration) {
      return (domNode.querySelector("#" + id) || domNode).animate(keyframes, duration);
    },
    setCutoutRectsForElement(id, rects) {
      // no-op for now.
    }
  };
}

let gWindows = new WeakMap();

/**
 * FinderHighlighter class that is used by Finder.jsm to take care of the
 * 'Highlight All' feature, which can highlight all find occurrences in a page.
 *
 * @param {Finder} finder Finder.jsm instance
 */
function FinderHighlighter(finder) {
  this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
  this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
  this.finder = finder;
}

FinderHighlighter.prototype = {
  get iterator() {
    if (this._iterator)
      return this._iterator;
    this._iterator = Cu.import("resource://gre/modules/FinderIterator.jsm", null).FinderIterator;
    return this._iterator;
  },

  /**
   * Each window is unique, globally, and the relation between an active
   * highlighting session and a window is 1:1.
   * For each window we track a number of properties which _at least_ consist of
   *  - {Boolean} detectedGeometryChange Whether the geometry of the found ranges'
   *                                     rectangles has changed substantially
   *  - {Set}     dynamicRangesSet       Set of ranges that may move around, depending
   *                                     on page layout changes and user input
   *  - {Map}     frames                 Collection of frames that were encountered
   *                                     when inspecting the found ranges
   *  - {Map}     modalHighlightRectsMap Collection of ranges and their corresponding
   *                                     Rects and texts
   *
   * @param  {nsIDOMWindow} window
   * @return {Object}
   */
  getForWindow(window, propName = null) {
    if (!gWindows.has(window)) {
      gWindows.set(window, {
        detectedGeometryChange: false,
        dynamicRangesSet: new Set(),
        frames: new Map(),
        lastWindowDimensions: { width: 0, height: 0 },
        modalHighlightRectsMap: new Map(),
        previousRangeRectsAndTexts: { rectList: [], textList: [] }
      });
    }
    return gWindows.get(window);
  },

  /**
   * Notify all registered listeners that the 'Highlight All' operation finished.
   *
   * @param {Boolean} highlight Whether highlighting was turned on
   */
  notifyFinished(highlight) {
    for (let l of this.finder._listeners) {
      try {
        l.onHighlightFinished(highlight);
      } catch (ex) {}
    }
  },

  /**
   * Toggle highlighting all occurrences of a word in a page. This method will
   * be called recursively for each (i)frame inside a page.
   *
   * @param {Booolean} highlight Whether highlighting should be turned on
   * @param {String}   word      Needle to search for and highlight when found
   * @param {Boolean}  linksOnly Only consider nodes that are links for the search
   * @yield {Promise}  that resolves once the operation has finished
   */
  async highlight(highlight, word, linksOnly) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    let controller = this.finder._getSelectionController(window);
    let doc = window.document;
    this._found = false;

    if (!controller || !doc || !doc.documentElement) {
      // Without the selection controller,
      // we are unable to (un)highlight any matches
      return;
    }

    if (highlight) {
      let params = {
        allowDistance: 1,
        caseSensitive: this.finder._fastFind.caseSensitive,
        entireWord: this.finder._fastFind.entireWord,
        linksOnly, word,
        finder: this.finder,
        listener: this,
        useCache: true,
        window
      };
      if (this.iterator.isAlreadyRunning(params) ||
          (this._modal && this.iterator._areParamsEqual(params, dict.lastIteratorParams))) {
        return;
      }

      if (!this._modal)
        dict.visible = true;
      await this.iterator.start(params);
      if (this._found)
        this.finder._outlineLink(true);
    } else {
      this.hide(window);

      // Removing the highlighting always succeeds, so return true.
      this._found = true;
    }

    this.notifyFinished({ highlight, found: this._found });
  },

  // FinderIterator listener implementation

  onIteratorRangeFound(range) {
    this.highlightRange(range);
    this._found = true;
  },

  onIteratorReset() {},

  onIteratorRestart() {
    this.clear(this.finder._getWindow());
  },

  onIteratorStart(params) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    // Save a clean params set for use later in the `update()` method.
    dict.lastIteratorParams = params;
    if (!this._modal)
      this.hide(window, this.finder._fastFind.getFoundRange());
    this.clear(window);
  },

  /**
   * Add a range to the find selection, i.e. highlight it, and if it's inside an
   * editable node, track it.
   *
   * @param {nsIDOMRange} range Range object to be highlighted
   */
  highlightRange(range) {
    let node = range.startContainer;
    let editableNode = this._getEditableNode(node);
    let window = node.ownerGlobal;
    let controller = this.finder._getSelectionController(window);
    if (editableNode) {
      controller = editableNode.editor.selectionController;
    }

    if (this._modal) {
      this._modalHighlight(range, controller, window);
    } else {
      let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
      findSelection.addRange(range);
      // Check if the range is inside an iframe.
      if (window != window.top) {
        let dict = this.getForWindow(window.top);
        if (!dict.frames.has(window))
          dict.frames.set(window, null);
      }
    }

    if (editableNode) {
      // Highlighting added, so cache this editor, and hook up listeners
      // to ensure we deal properly with edits within the highlighting
      this._addEditorListeners(editableNode.editor);
    }
  },

  /**
   * If modal highlighting is enabled, show the dimmed background that will overlay
   * the page.
   *
   * @param {nsIDOMWindow} window The dimmed background will overlay this window.
   *                              Optional, defaults to the finder window.
   */
  show(window = null) {
    window = (window || this.finder._getWindow()).top;
    let dict = this.getForWindow(window);
    if (!this._modal || dict.visible)
      return;

    dict.visible = true;

    this._maybeCreateModalHighlightNodes(window);
    this._addModalHighlightListeners(window);
  },

  /**
   * Clear all highlighted matches. If modal highlighting is enabled and
   * the outline + dimmed background is currently visible, both will be hidden.
   *
   * @param {nsIDOMWindow} window    The dimmed background will overlay this window.
   *                                 Optional, defaults to the finder window.
   * @param {nsIDOMRange}  skipRange A range that should not be removed from the
   *                                 find selection.
   * @param {nsIDOMEvent}  event     When called from an event handler, this will
   *                                 be the triggering event.
   */
  hide(window, skipRange = null, event = null) {
    try {
      window = window.top;
    } catch (ex) {
      Cu.reportError(ex);
      return;
    }
    let dict = this.getForWindow(window);

    let isBusySelecting = dict.busySelecting;
    dict.busySelecting = false;
    // Do not hide on anything but a left-click.
    if (event && event.type == "click" && (event.button !== 0 || event.altKey ||
        event.ctrlKey || event.metaKey || event.shiftKey || event.relatedTarget ||
        isBusySelecting || (event.target.localName == "a" && event.target.href))) {
      return;
    }

    this._clearSelection(this.finder._getSelectionController(window), skipRange);
    for (let frame of dict.frames.keys())
      this._clearSelection(this.finder._getSelectionController(frame), skipRange);

    // Next, check our editor cache, for editors belonging to this
    // document
    if (this._editors) {
      let doc = window.document;
      for (let x = this._editors.length - 1; x >= 0; --x) {
        if (this._editors[x].document == doc) {
          this._clearSelection(this._editors[x].selectionController, skipRange);
          // We don't need to listen to this editor any more
          this._unhookListenersAtIndex(x);
        }
      }
    }

    if (dict.modalRepaintScheduler) {
      window.clearTimeout(dict.modalRepaintScheduler);
      dict.modalRepaintScheduler = null;
    }
    dict.lastWindowDimensions = { width: 0, height: 0 };

    this._removeRangeOutline(window);
    this._removeHighlightAllMask(window);
    this._removeModalHighlightListeners(window);

    dict.visible = false;
  },

  /**
   * Called by the Finder after a find result comes in; update the position and
   * content of the outline to the newly found occurrence.
   * To make sure that the outline covers the found range completely, all the
   * CSS styles that influence the text are copied and applied to the outline.
   *
   * @param {Object} data Dictionary coming from Finder that contains the
   *                      following properties:
   *   {Number}  result        One of the nsITypeAheadFind.FIND_* constants
   *                           indicating the result of a search operation.
   *   {Boolean} findBackwards If TRUE, the search was performed backwards,
   *                           FALSE if forwards.
   *   {Boolean} findAgain     If TRUE, the search was performed using the same
   *                           search string as before.
   *   {String}  linkURL       If a link was hit, this will contain a URL string.
   *   {Rect}    rect          An object with top, left, width and height
   *                           coordinates of the current selection.
   *   {String}  searchString  The string the search was performed with.
   *   {Boolean} storeResult   Indicator if the search string should be stored
   *                           by the consumer of the Finder.
   */
  update(data) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    let foundRange = this.finder._fastFind.getFoundRange();

    if (data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND || !data.searchString || !foundRange) {
      this.hide(window);
      return;
    }

    if (!this._modal) {
      if (this._highlightAll) {
        dict.previousFoundRange = dict.currentFoundRange;
        dict.currentFoundRange = foundRange;
        let params = this.iterator.params;
        if (dict.visible && this.iterator._areParamsEqual(params, dict.lastIteratorParams))
          return;
        if (!dict.visible && !params)
          params = {word: data.searchString, linksOnly: data.linksOnly};
        if (params)
          this.highlight(true, params.word, params.linksOnly);
      }
      return;
    }

    dict.animateOutline = true;
    // Immediately finish running animations, if any.
    this._finishOutlineAnimations(dict);

    if (foundRange !== dict.currentFoundRange || data.findAgain) {
      dict.previousFoundRange = dict.currentFoundRange;
      dict.currentFoundRange = foundRange;

      if (!dict.visible)
        this.show(window);
      else
        this._maybeCreateModalHighlightNodes(window);
    }

    if (this._highlightAll)
      this.highlight(true, data.searchString, data.linksOnly);
  },

  /**
   * Invalidates the list by clearing the map of highlighted ranges that we
   * keep to build the mask for.
   */
  clear(window = null) {
    if (!window || !window.top)
      return;

    let dict = this.getForWindow(window.top);
    this._finishOutlineAnimations(dict);
    dict.dynamicRangesSet.clear();
    dict.frames.clear();
    dict.modalHighlightRectsMap.clear();
    dict.brightText = null;
  },

  /**
   * When the current page is refreshed or navigated away from, the CanvasFrame
   * contents is not valid anymore, i.e. all anonymous content is destroyed.
   * We need to clear the references we keep, which'll make sure we redraw
   * everything when the user starts to find in page again.
   */
  onLocationChange() {
    let window = this.finder._getWindow();
    if (!window || !window.top)
      return;
    this.hide(window);
    this.clear(window);
    this._removeRangeOutline(window);

    gWindows.delete(window.top);
  },

  /**
   * When `kModalHighlightPref` pref changed during a session, this callback is
   * invoked. When modal highlighting is turned off, we hide the CanvasFrame
   * contents.
   *
   * @param {Boolean} useModalHighlight
   */
  onModalHighlightChange(useModalHighlight) {
    let window = this.finder._getWindow();
    if (window && this._modal && !useModalHighlight) {
      this.hide(window);
      this.clear(window);
    }
    this._modal = useModalHighlight;
  },

  /**
   * When 'Highlight All' is toggled during a session, this callback is invoked
   * and when it's turned off, the found occurrences will be removed from the mask.
   *
   * @param {Boolean} highlightAll
   */
  onHighlightAllChange(highlightAll) {
    this._highlightAll = highlightAll;
    if (!highlightAll) {
      let window = this.finder._getWindow();
      if (!this._modal)
        this.hide(window);
      this.clear(window);
      this._scheduleRepaintOfMask(window);
    }
  },

  /**
   * Utility; removes all ranges from the find selection that belongs to a
   * controller. Optionally skips a specific range.
   *
   * @param  {nsISelectionController} controller
   * @param  {nsIDOMRange}            restoreRange
   */
  _clearSelection(controller, restoreRange = null) {
    if (!controller)
      return;
    let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
    sel.removeAllRanges();
    if (restoreRange) {
      sel = controller.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
      sel.addRange(restoreRange);
      controller.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
      controller.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
    }
  },

  /**
   * Utility; get the nsIDOMWindowUtils for a window.
   *
   * @param  {nsIDOMWindow} window Optional, defaults to the finder window.
   * @return {nsIDOMWindowUtils}
   */
  _getDWU(window = null) {
    return (window || this.finder._getWindow())
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindowUtils);
  },

  /**
   * Utility; returns the bounds of the page relative to the viewport.
   * If the pages is part of a frameset or inside an iframe of any kind, its
   * offset is accounted for.
   * Geometry.jsm takes care of the DOMRect calculations.
   *
   * @param  {nsIDOMWindow} window          Window to read the boundary rect from
   * @param  {Boolean}      [includeScroll] Whether to ignore the scroll offset,
   *                                        which is useful for comparing DOMRects.
   *                                        Optional, defaults to `true`
   * @return {Rect}
   */
  _getRootBounds(window, includeScroll = true) {
    let dwu = this._getDWU(window.top);
    let cssPageRect = Rect.fromRect(dwu.getRootBounds());
    let scrollX = {};
    let scrollY = {};
    if (includeScroll && window == window.top) {
      dwu.getScrollXY(false, scrollX, scrollY);
      cssPageRect.translate(scrollX.value, scrollY.value);
    }

    // If we're in a frame, update the position of the rect (top/ left).
    let currWin = window;
    while (currWin != window.top) {
      // Since the frame is an element inside a parent window, we'd like to
      // learn its position relative to it.
      let el = this._getDWU(currWin).containerElement;
      currWin = currWin.parent;
      dwu = this._getDWU(currWin);
      let parentRect = Rect.fromRect(dwu.getBoundsWithoutFlushing(el));

      if (includeScroll) {
        dwu.getScrollXY(false, scrollX, scrollY);
        parentRect.translate(scrollX.value, scrollY.value);
        // If the current window is an iframe with scrolling="no" and its parent
        // is also an iframe the scroll offsets from the parents' documentElement
        // (inverse scroll position) needs to be subtracted from the parent
        // window rect.
        if (el.getAttribute("scrolling") == "no" && currWin != window.top) {
          let docEl = currWin.document.documentElement;
          parentRect.translate(-docEl.scrollLeft, -docEl.scrollTop);
        }
      }

      cssPageRect.translate(parentRect.left, parentRect.top);
    }

    return cssPageRect;
  },

  /**
   * Utility; fetch the full width and height of the current window, excluding
   * scrollbars.
   *
   * @param  {nsiDOMWindow} window The current finder window.
   * @return {Object} The current full page dimensions with `width` and `height`
   *                  properties
   */
  _getWindowDimensions(window) {
    // First we'll try without flushing layout, because it's way faster.
    let dwu = this._getDWU(window);
    let { width, height } = dwu.getRootBounds();

    if (!width || !height) {
      // We need a flush after all :'(
      width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
      height = window.innerHeight + window.scrollMaxY - window.scrollMinY;

      let scrollbarHeight = {};
      let scrollbarWidth = {};
      dwu.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
      width -= scrollbarWidth.value;
      height -= scrollbarHeight.value;
    }

    return { width, height };
  },

  /**
   * Utility; get all available font styles as applied to the content of a given
   * range. The CSS properties we look for can be found in `kFontPropsCSS`.
   *
   * @param  {nsIDOMRange} range Range to fetch style info from.
   * @return {Object} Dictionary consisting of the styles that were found.
   */
  _getRangeFontStyle(range) {
    let node = range.startContainer;
    while (node.nodeType != 1)
      node = node.parentNode;
    let style = node.ownerGlobal.getComputedStyle(node);
    let props = {};
    for (let prop of kFontPropsCamelCase) {
      if (prop in style && style[prop])
        props[prop] = style[prop];
    }
    return props;
  },

  /**
   * Utility; transform a dictionary object as returned by `_getRangeFontStyle`
   * above into a HTML style attribute value.
   *
   * @param  {Object} fontStyle
   * @return {String}
   */
  _getHTMLFontStyle(fontStyle) {
    let style = [];
    for (let prop of Object.getOwnPropertyNames(fontStyle)) {
      let idx = kFontPropsCamelCase.indexOf(prop);
      if (idx == -1)
        continue;
      style.push(`${kFontPropsCSS[idx]}: ${fontStyle[prop]}`);
    }
    return style.join("; ");
  },

  /**
   * Transform a style definition array as defined in `kModalStyles` into a CSS
   * string that can be used to set the 'style' property of a DOM node.
   *
   * @param  {Array}    stylePairs         Two-dimensional array of style pairs
   * @param  {...Array} [additionalStyles] Optional set of style pairs that will
   *                                       augment or override the styles defined
   *                                       by `stylePairs`
   * @return {String}
   */
  _getStyleString(stylePairs, ...additionalStyles) {
    let baseStyle = new Map(stylePairs);
    for (let additionalStyle of additionalStyles) {
      for (let [prop, value] of additionalStyle)
        baseStyle.set(prop, value);
    }
    return [...baseStyle].map(([cssProp, cssVal]) => `${cssProp}: ${cssVal}`).join("; ");
  },

  /**
   * Checks whether a CSS RGB color value can be classified as being 'bright'.
   *
   * @param  {String} cssColor RGB color value in the default format rgb[a](r,g,b)
   * @return {Boolean}
   */
  _isColorBright(cssColor) {
    cssColor = cssColor.match(kRGBRE);
    if (!cssColor || !cssColor.length)
      return false;
    cssColor.shift();
    return new Color(...cssColor).isBright;
  },

  /**
   * Detects if the overall text color in the page can be described as bright.
   * This is done according to the following algorithm:
   *  1. With the entire set of ranges that we have found thusfar;
   *  2. Get an odd-numbered `sampleSize`, with a maximum of `kBrightTextSampleSize`
   *     ranges,
   *  3. Slice the set of ranges into `sampleSize` number of equal parts,
   *  4. Grab the first range for each slice and inspect the brightness of the
   *     color of its text content.
   *  5. When the majority of ranges are counted as contain bright colored text,
   *     the page is considered to contain bright text overall.
   *
   * @param {Object} dict Dictionary of properties belonging to the
   *                      currently active window. The page text color property
   *                      will be recorded in `dict.brightText` as `true` or `false`.
   */
  _detectBrightText(dict) {
    let sampleSize = Math.min(dict.modalHighlightRectsMap.size, kBrightTextSampleSize);
    let ranges = [...dict.modalHighlightRectsMap.keys()];
    let rangesCount = ranges.length;
    // Make sure the sample size is an odd number.
    if (sampleSize % 2 == 0) {
      // Make the previously or currently found range weigh heavier.
      if (dict.previousFoundRange || dict.currentFoundRange) {
        ranges.push(dict.previousFoundRange || dict.currentFoundRange);
        ++sampleSize;
        ++rangesCount;
      } else {
        --sampleSize;
      }
    }
    let brightCount = 0;
    for (let i = 0; i < sampleSize; ++i) {
      let range = ranges[Math.floor((rangesCount / sampleSize) * i)];
      let fontStyle = this._getRangeFontStyle(range);
      if (this._isColorBright(fontStyle.color))
        ++brightCount;
    }

    dict.brightText = (brightCount >= Math.ceil(sampleSize / 2));
  },

  /**
   * Checks if a range is inside a DOM node that's positioned in a way that it
   * doesn't scroll along when the document is scrolled and/ or zoomed. This
   * is the case for 'fixed' and 'sticky' positioned elements, elements inside
   * (i)frames and elements that have their overflow styles set to 'auto' or
   * 'scroll'.
   *
   * @param  {nsIDOMRange} range Range that be enclosed in a dynamic container
   * @return {Boolean}
   */
  _isInDynamicContainer(range) {
    const kFixed = new Set(["fixed", "sticky", "scroll", "auto"]);
    let node = range.startContainer;
    while (node.nodeType != 1)
      node = node.parentNode;
    let document = node.ownerDocument;
    let window = document.defaultView;
    let dict = this.getForWindow(window.top);

    // Check if we're in a frameset (including iframes).
    if (window != window.top) {
      if (!dict.frames.has(window))
        dict.frames.set(window, null);
      return true;
    }

    do {
      let style = window.getComputedStyle(node);
      if (kFixed.has(style.position) || kFixed.has(style.overflow) ||
          kFixed.has(style.overflowX) || kFixed.has(style.overflowY)) {
        return true;
      }
      node = node.parentNode;
    } while (node && node != document.documentElement)

    return false;
  },

  /**
   * Read and store the rectangles that encompass the entire region of a range
   * for use by the drawing function of the highlighter.
   *
   * @param  {nsIDOMRange} range  Range to fetch the rectangles from
   * @param  {Object}      [dict] Dictionary of properties belonging to
   *                              the currently active window
   * @return {Set}         Set of rects that were found for the range
   */
  _getRangeRectsAndTexts(range, dict = null) {
    let window = range.startContainer.ownerGlobal;
    let bounds;
    // If the window is part of a frameset, try to cache the bounds query.
    if (dict && dict.frames.has(window)) {
      bounds = dict.frames.get(window);
      if (!bounds) {
        bounds = this._getRootBounds(window);
        dict.frames.set(window, bounds);
      }
    } else
      bounds = this._getRootBounds(window);

    let topBounds = this._getRootBounds(window.top, false);
    let rects = [];
    // A range may consist of multiple rectangles, we can also do these kind of
    // precise cut-outs. range.getBoundingClientRect() returns the fully
    // encompassing rectangle, which is too much for our purpose here.
    let {rectList, textList} = range.getClientRectsAndTexts();
    for (let rect of rectList) {
      rect = Rect.fromRect(rect);
      rect.x += bounds.x;
      rect.y += bounds.y;
      // If the rect is not even visible from the top document, we can ignore it.
      if (rect.intersects(topBounds))
        rects.push(rect);
    }
    return {rectList: rects, textList};
  },

  /**
   * Read and store the rectangles that encompass the entire region of a range
   * for use by the drawing function of the highlighter and store them in the
   * cache.
   *
   * @param  {nsIDOMRange} range            Range to fetch the rectangles from
   * @param  {Boolean}     [checkIfDynamic] Whether we should check if the range
   *                                        is dynamic as per the rules in
   *                                        `_isInDynamicContainer()`. Optional,
   *                                        defaults to `true`
   * @param  {Object}      [dict]           Dictionary of properties belonging to
   *                                        the currently active window
   * @return {Set}         Set of rects that were found for the range
   */
  _updateRangeRects(range, checkIfDynamic = true, dict = null) {
    let window = range.startContainer.ownerGlobal;
    let rectsAndTexts = this._getRangeRectsAndTexts(range, dict);

    // Only fetch the rect at this point, if not passed in as argument.
    dict = dict || this.getForWindow(window.top);
    let oldRectsAndTexts = dict.modalHighlightRectsMap.get(range);
    dict.modalHighlightRectsMap.set(range, rectsAndTexts);
    // Check here if we suddenly went down to zero rects from more than zero before,
    // which indicates that we should re-iterate the document.
    if (oldRectsAndTexts && oldRectsAndTexts.rectList.length && !rectsAndTexts.rectList.length)
      dict.detectedGeometryChange = true;
    if (checkIfDynamic && this._isInDynamicContainer(range))
      dict.dynamicRangesSet.add(range);
    return rectsAndTexts;
  },

  /**
   * Re-read the rectangles of the ranges that we keep track of separately,
   * because they're enclosed by a position: fixed container DOM node or (i)frame.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _updateDynamicRangesRects(dict) {
    // Reset the frame bounds cache.
    for (let frame of dict.frames.keys())
      dict.frames.set(frame, null);
    for (let range of dict.dynamicRangesSet)
      this._updateRangeRects(range, false, dict);
  },

  /**
   * Update the content, position and style of the yellow current found range
   * outline that floats atop the mask with the dimmed background.
   * Rebuild it, if necessary, This will deactivate the animation between
   * occurrences.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _updateRangeOutline(dict) {
    let range = dict.currentFoundRange;
    if (!range)
      return;

    let fontStyle = this._getRangeFontStyle(range);
    // Text color in the outline is determined by kModalStyles.
    delete fontStyle.color;

    let rectsAndTexts = this._updateRangeRects(range, true, dict);
    let outlineAnonNode = dict.modalHighlightOutline;
    let rectCount = rectsAndTexts.rectList.length;
    let previousRectCount = dict.previousRangeRectsAndTexts.rectList.length;
    // (re-)Building the outline is conditional and happens when one of the
    // following conditions is met:
    // 1. No outline nodes were built before, or
    // 2. When the amount of rectangles to draw is different from before, or
    // 3. When there's more than one rectangle to draw, because it's impossible
    //    to animate that consistently with AnonymousContent nodes.
    let rebuildOutline = (!outlineAnonNode || rectCount !== previousRectCount ||
      rectCount != 1);
    dict.previousRangeRectsAndTexts = rectsAndTexts;

    let window = range.startContainer.ownerGlobal.top;
    let document = window.document;
    // First see if we need to and can remove the previous outline nodes.
    if (rebuildOutline)
      this._removeRangeOutline(window);

    // Abort when there's no text to highlight OR when it's the exact same range
    // as the previous call and isn't inside a dynamic container.
    if (!rectsAndTexts.textList.length ||
        (!rebuildOutline && dict.previousUpdatedRange == range && !dict.dynamicRangesSet.has(range))) {
      return;
    }

    let outlineBox;
    if (rebuildOutline) {
      // Create the main (yellow) highlight outline box.
      outlineBox = document.createElementNS(kNSHTML, "div");
      outlineBox.setAttribute("id", kModalOutlineId);
    }

    const kModalOutlineTextId = kModalOutlineId + "-text";
    let i = 0;
    for (let rect of rectsAndTexts.rectList) {
      let text = rectsAndTexts.textList[i];

      // Next up is to check of the outline box' borders will not overlap with
      // rects that we drew before or will draw after this one.
      // We're taking the width of the border into account, which is
      // `kOutlineBoxBorderSize` pixels.
      // When left and/ or right sides will overlap with the current, previous
      // or next rect, make sure to make the necessary adjustments to the style.
      // These adjustments will override the styles as defined in `kModalStyles.outlineNode`.
      let intersectingSides = new Set();
      let previous = rectsAndTexts.rectList[i - 1];
      if (previous &&
          rect.left - previous.right <= 2 * kOutlineBoxBorderSize) {
        intersectingSides.add("left");
      }
      let next = rectsAndTexts.rectList[i + 1];
      if (next &&
          next.left - rect.right <= 2 * kOutlineBoxBorderSize) {
        intersectingSides.add("right");
      }
      let borderStyles = [...intersectingSides].map(side => [ "border-" + side, 0 ]);
      if (intersectingSides.size) {
        borderStyles.push([ "margin",  `-${kOutlineBoxBorderSize}px 0 0 ${
          intersectingSides.has("left") ? 0 : -kOutlineBoxBorderSize}px !important`]);
        borderStyles.push([ "border-radius",
          (intersectingSides.has("left") ? 0 : kOutlineBoxBorderRadius) + "px " +
          (intersectingSides.has("right") ? 0 : kOutlineBoxBorderRadius) + "px " +
          (intersectingSides.has("right") ? 0 : kOutlineBoxBorderRadius) + "px " +
          (intersectingSides.has("left") ? 0 : kOutlineBoxBorderRadius) + "px" ]);
      }

      let outlineStyle = this._getStyleString(kModalStyles.outlineNode, [
        ["top", rect.top + "px"],
        ["left", rect.left + "px"],
        ["height", rect.height + "px"],
        ["width", rect.width + "px"]
      ], borderStyles, kDebug ? kModalStyles.outlineNodeDebug : []);
      fontStyle.lineHeight = rect.height + "px";
      let textStyle = this._getStyleString(kModalStyles.outlineText) + "; " +
        this._getHTMLFontStyle(fontStyle);

      if (rebuildOutline) {
        let textBoxParent = outlineBox.appendChild(document.createElementNS(kNSHTML, "div"));
        textBoxParent.setAttribute("id", kModalOutlineId + i);
        textBoxParent.setAttribute("style", outlineStyle);

        let textBox = document.createElementNS(kNSHTML, "span");
        textBox.setAttribute("id", kModalOutlineTextId + i);
        textBox.setAttribute("style", textStyle);
        textBox.textContent = text;
        textBoxParent.appendChild(textBox);
      } else {
        // Set the appropriate properties on the existing nodes, which will also
        // activate the transitions.
        outlineAnonNode.setAttributeForElement(kModalOutlineId + i, "style", outlineStyle);
        outlineAnonNode.setAttributeForElement(kModalOutlineTextId + i, "style", textStyle);
        outlineAnonNode.setTextContentForElement(kModalOutlineTextId + i, text);
      }

      ++i;
    }

    if (rebuildOutline) {
      dict.modalHighlightOutline = kDebug ?
        mockAnonymousContentNode((document.body ||
          document.documentElement).appendChild(outlineBox)) :
        document.insertAnonymousContent(outlineBox);
    }

    if (dict.animateOutline && !this._isPageTooBig(dict)) {
      let animation;
      dict.animations = new Set();
      for (let i = rectsAndTexts.rectList.length - 1; i >= 0; --i) {
        animation = dict.modalHighlightOutline.setAnimationForElement(kModalOutlineId + i,
          Cu.cloneInto(kModalOutlineAnim.keyframes, window), kModalOutlineAnim.duration);
        animation.onfinish = function() { dict.animations.delete(this); };
        dict.animations.add(animation);
      }
    }
    dict.animateOutline = false;

    dict.previousUpdatedRange = range;
  },

  /**
   * Finish any currently playing animations on the found range outline node.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _finishOutlineAnimations(dict) {
    if (!dict.animations)
      return;
    for (let animation of dict.animations)
      animation.finish();
  },

  /**
   * Safely remove the outline AnoymousContent node from the CanvasFrame.
   *
   * @param {nsIDOMWindow} window
   */
  _removeRangeOutline(window) {
    let dict = this.getForWindow(window);
    if (!dict.modalHighlightOutline)
      return;

    if (kDebug) {
      dict.modalHighlightOutline.remove();
    } else {
      try {
        window.document.removeAnonymousContent(dict.modalHighlightOutline);
      } catch (ex) {}
    }

    dict.modalHighlightOutline = null;
  },

  /**
   * Add a range to the list of ranges to highlight on, or cut out of, the dimmed
   * background.
   *
   * @param {nsIDOMRange}  range  Range object that should be inspected
   * @param {nsIDOMWindow} window Window object, whose DOM tree is being traversed
   */
  _modalHighlight(range, controller, window) {
    this._updateRangeRects(range);

    this.show(window);
    // We don't repaint the mask right away, but pass it off to a render loop of
    // sorts.
    this._scheduleRepaintOfMask(window);
  },

  /**
   * Lazily insert the nodes we need as anonymous content into the CanvasFrame
   * of a window.
   *
   * @param {nsIDOMWindow} window Window to draw in.
   */
  _maybeCreateModalHighlightNodes(window) {
    window = window.top;
    let dict = this.getForWindow(window);
    if (dict.modalHighlightOutline) {
      if (!dict.modalHighlightAllMask) {
        // Make sure to at least show the dimmed background.
        this._repaintHighlightAllMask(window, false);
        this._scheduleRepaintOfMask(window);
      } else {
        this._scheduleRepaintOfMask(window, { scrollOnly: true });
      }
      return;
    }

    let document = window.document;
    // A hidden document doesn't accept insertAnonymousContent calls yet.
    if (document.hidden) {
      let onVisibilityChange = () => {
        document.removeEventListener("visibilitychange", onVisibilityChange);
        this._maybeCreateModalHighlightNodes(window);
      };
      document.addEventListener("visibilitychange", onVisibilityChange);
      return;
    }

    // Make sure to at least show the dimmed background.
    this._repaintHighlightAllMask(window, false);
  },

  /**
   * Build and draw the mask that takes care of the dimmed background that
   * overlays the current page and the mask that cuts out all the rectangles of
   * the ranges that were found.
   *
   * @param {nsIDOMWindow} window Window to draw in.
   * @param {Boolean} [paintContent]
   */
  _repaintHighlightAllMask(window, paintContent = true) {
    window = window.top;
    let dict = this.getForWindow(window);

    const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
    if (!dict.modalHighlightAllMask) {
      let document = window.document;
      let maskNode = document.createElementNS(kNSHTML, "div");
      maskNode.setAttribute("id", kMaskId);
      dict.modalHighlightAllMask = kDebug ?
        mockAnonymousContentNode((document.body || document.documentElement).appendChild(maskNode)) :
        document.insertAnonymousContent(maskNode);
    }

    // Make sure the dimmed mask node takes the full width and height that's available.
    let {width, height} = dict.lastWindowDimensions = this._getWindowDimensions(window);
    if (typeof dict.brightText != "boolean" || dict.updateAllRanges)
      this._detectBrightText(dict);
    let maskStyle = this._getStyleString(kModalStyles.maskNode,
      [ ["width", width + "px"], ["height", height + "px"] ],
      dict.brightText ? kModalStyles.maskNodeBrightText : [],
      paintContent ? kModalStyles.maskNodeTransition : [],
      kDebug ? kModalStyles.maskNodeDebug : []);
    dict.modalHighlightAllMask.setAttributeForElement(kMaskId, "style", maskStyle);

    this._updateRangeOutline(dict);

    let allRects = [];
    if (paintContent || dict.modalHighlightAllMask) {
      // No need to update dynamic ranges separately when we already about to
      // update all of them anyway.
      if (!dict.updateAllRanges)
        this._updateDynamicRangesRects(dict);

      let DOMRect = window.DOMRect;
      for (let [range, rectsAndTexts] of dict.modalHighlightRectsMap) {
        if (dict.updateAllRanges)
          rectsAndTexts = this._updateRangeRects(range);

        // If a geometry change was detected, we bail out right away here, because
        // the current set of ranges has been invalidated.
        if (dict.detectedGeometryChange)
          return;

        for (let rect of rectsAndTexts.rectList)
          allRects.push(new DOMRect(rect.x, rect.y, rect.width, rect.height));
      }
      dict.updateAllRanges = false;
    }

    dict.modalHighlightAllMask.setCutoutRectsForElement(kMaskId, allRects);
  },

  /**
   * Safely remove the mask AnoymousContent node from the CanvasFrame.
   *
   * @param {nsIDOMWindow} window
   */
  _removeHighlightAllMask(window) {
    window = window.top;
    let dict = this.getForWindow(window);
    if (!dict.modalHighlightAllMask)
      return;

    // If the current window isn't the one the content was inserted into, this
    // will fail, but that's fine.
    if (kDebug) {
      dict.modalHighlightAllMask.remove();
    } else {
      try {
        window.document.removeAnonymousContent(dict.modalHighlightAllMask);
      } catch (ex) {}
    }
    dict.modalHighlightAllMask = null;
  },

  /**
   * Check if the width or height of the current document is too big to handle
   * for certain operations. This allows us to degrade gracefully when we expect
   * the performance to be negatively impacted due to drawing-intensive operations.
   *
   * @param  {Object} dict Dictionary of properties belonging to the currently
   *                       active window
   * @return {Boolean}
   */
  _isPageTooBig(dict) {
    let {height, width} = dict.lastWindowDimensions;
    return height >= kPageIsTooBigPx || width >= kPageIsTooBigPx;
  },

  /**
   * Doing a full repaint each time a range is delivered by the highlight iterator
   * is way too costly, thus we pipe the frequency down to every
   * `kModalHighlightRepaintLoFreqMs` milliseconds. If there are dynamic ranges
   * found (see `_isInDynamicContainer()` for the definition), the frequency
   * will be upscaled to `kModalHighlightRepaintHiFreqMs`.
   *
   * @param {nsIDOMWindow} window
   * @param {Object}       options Dictionary of painter hints that contains the
   *                               following properties:
   *   {Boolean} contentChanged  Whether the documents' content changed in the
   *                             meantime. This happens when the DOM is updated
   *                             whilst the page is loaded.
   *   {Boolean} scrollOnly      TRUE when the page has scrolled in the meantime,
   *                             which means that the dynamically positioned
   *                             elements need to be repainted.
   *   {Boolean} updateAllRanges Whether to recalculate the rects of all ranges
   *                             that were found up until now.
   */
  _scheduleRepaintOfMask(window, { contentChanged, scrollOnly, updateAllRanges } =
                                 { contentChanged: false, scrollOnly: false, updateAllRanges: false }) {
    if (!this._modal)
      return;

    window = window.top;
    let dict = this.getForWindow(window);
    let hasDynamicRanges = !!dict.dynamicRangesSet.size;
    let pageIsTooBig = this._isPageTooBig(dict);
    let repaintDynamicRanges = ((scrollOnly || contentChanged) && hasDynamicRanges
      && !pageIsTooBig);

    // When we request to repaint unconditionally, we mean to call
    // `_repaintHighlightAllMask()` right after the timeout.
    if (!dict.unconditionalRepaintRequested)
      dict.unconditionalRepaintRequested = !contentChanged || repaintDynamicRanges;
    // Some events, like a resize, call for recalculation of all the rects of all ranges.
    if (!dict.updateAllRanges)
      dict.updateAllRanges = updateAllRanges;

    if (dict.modalRepaintScheduler)
      return;

    dict.modalRepaintScheduler = window.setTimeout(() => {
      dict.modalRepaintScheduler = null;

      let pageContentChanged = dict.detectedGeometryChange;
      if (!pageContentChanged && !pageIsTooBig) {
        let { width: previousWidth, height: previousHeight } = dict.lastWindowDimensions;
        let { width, height } = dict.lastWindowDimensions = this._getWindowDimensions(window);
        pageContentChanged = dict.detectedGeometryChange ||
                             (Math.abs(previousWidth - width) > kContentChangeThresholdPx ||
                              Math.abs(previousHeight - height) > kContentChangeThresholdPx);
      }
      dict.detectedGeometryChange = false;
      // When the page has changed significantly enough in size, we'll restart
      // the iterator with the same parameters as before to find us new ranges.
      if (pageContentChanged && !pageIsTooBig)
        this.iterator.restart(this.finder);

      if (dict.unconditionalRepaintRequested ||
          (dict.modalHighlightRectsMap.size && pageContentChanged)) {
        dict.unconditionalRepaintRequested = false;
        this._repaintHighlightAllMask(window);
      }
    }, hasDynamicRanges ? kModalHighlightRepaintHiFreqMs : kModalHighlightRepaintLoFreqMs);
  },

  /**
   * Add event listeners to the content which will cause the modal highlight
   * AnonymousContent to be re-painted or hidden.
   *
   * @param {nsIDOMWindow} window
   */
  _addModalHighlightListeners(window) {
    window = window.top;
    let dict = this.getForWindow(window);
    if (dict.highlightListeners)
      return;

    window = window.top;
    dict.highlightListeners = [
      this._scheduleRepaintOfMask.bind(this, window, { contentChanged: true }),
      this._scheduleRepaintOfMask.bind(this, window, { updateAllRanges: true }),
      this._scheduleRepaintOfMask.bind(this, window, { scrollOnly: true }),
      this.hide.bind(this, window, null),
      () => dict.busySelecting = true
    ];
    let target = this.iterator._getDocShell(window).chromeEventHandler;
    target.addEventListener("MozAfterPaint", dict.highlightListeners[0]);
    target.addEventListener("resize", dict.highlightListeners[1]);
    target.addEventListener("scroll", dict.highlightListeners[2], { capture: true, passive: true });
    target.addEventListener("click", dict.highlightListeners[3]);
    target.addEventListener("selectstart", dict.highlightListeners[4]);
  },

  /**
   * Remove event listeners from content.
   *
   * @param {nsIDOMWindow} window
   */
  _removeModalHighlightListeners(window) {
    window = window.top;
    let dict = this.getForWindow(window);
    if (!dict.highlightListeners)
      return;

    let target = this.iterator._getDocShell(window).chromeEventHandler;
    target.removeEventListener("MozAfterPaint", dict.highlightListeners[0]);
    target.removeEventListener("resize", dict.highlightListeners[1]);
    target.removeEventListener("scroll", dict.highlightListeners[2], { capture: true, passive: true });
    target.removeEventListener("click", dict.highlightListeners[3]);
    target.removeEventListener("selectstart", dict.highlightListeners[4]);

    dict.highlightListeners = null;
  },

  /**
   * For a given node returns its editable parent or null if there is none.
   * It's enough to check if node is a text node and its parent's parent is
   * instance of nsIDOMNSEditableElement.
   *
   * @param node the node we want to check
   * @returns the first node in the parent chain that is editable,
   *          null if there is no such node
   */
  _getEditableNode(node) {
    if (node.nodeType === node.TEXT_NODE && node.parentNode && node.parentNode.parentNode &&
        node.parentNode.parentNode instanceof Ci.nsIDOMNSEditableElement) {
      return node.parentNode.parentNode;
    }
    return null;
  },

  /**
   * Add ourselves as an nsIEditActionListener and nsIDocumentStateListener for
   * a given editor
   *
   * @param editor the editor we'd like to listen to
   */
  _addEditorListeners(editor) {
    if (!this._editors) {
      this._editors = [];
      this._stateListeners = [];
    }

    let existingIndex = this._editors.indexOf(editor);
    if (existingIndex == -1) {
      let x = this._editors.length;
      this._editors[x] = editor;
      this._stateListeners[x] = this._createStateListener();
      this._editors[x].addEditActionListener(this);
      this._editors[x].addDocumentStateListener(this._stateListeners[x]);
    }
  },

  /**
   * Helper method to unhook listeners, remove cached editors
   * and keep the relevant arrays in sync
   *
   * @param idx the index into the array of editors/state listeners
   *        we wish to remove
   */
  _unhookListenersAtIndex(idx) {
    this._editors[idx].removeEditActionListener(this);
    this._editors[idx]
        .removeDocumentStateListener(this._stateListeners[idx]);
    this._editors.splice(idx, 1);
    this._stateListeners.splice(idx, 1);
    if (!this._editors.length) {
      delete this._editors;
      delete this._stateListeners;
    }
  },

  /**
   * Remove ourselves as an nsIEditActionListener and
   * nsIDocumentStateListener from a given cached editor
   *
   * @param editor the editor we no longer wish to listen to
   */
  _removeEditorListeners(editor) {
    // editor is an editor that we listen to, so therefore must be
    // cached. Find the index of this editor
    let idx = this._editors.indexOf(editor);
    if (idx == -1) {
      return;
    }
    // Now unhook ourselves, and remove our cached copy
    this._unhookListenersAtIndex(idx);
  },

  /*
   * nsIEditActionListener logic follows
   *
   * We implement this interface to allow us to catch the case where
   * the findbar found a match in a HTML <input> or <textarea>. If the
   * user adjusts the text in some way, it will no longer match, so we
   * want to remove the highlight, rather than have it expand/contract
   * when letters are added or removed.
   */

  /**
   * Helper method used to check whether a selection intersects with
   * some highlighting
   *
   * @param selectionRange the range from the selection to check
   * @param findRange the highlighted range to check against
   * @returns true if they intersect, false otherwise
   */
  _checkOverlap(selectionRange, findRange) {
    if (!selectionRange || !findRange)
      return false;
    // The ranges overlap if one of the following is true:
    // 1) At least one of the endpoints of the deleted selection
    //    is in the find selection
    // 2) At least one of the endpoints of the find selection
    //    is in the deleted selection
    if (findRange.isPointInRange(selectionRange.startContainer,
                                 selectionRange.startOffset))
      return true;
    if (findRange.isPointInRange(selectionRange.endContainer,
                                 selectionRange.endOffset))
      return true;
    if (selectionRange.isPointInRange(findRange.startContainer,
                                      findRange.startOffset))
      return true;
    if (selectionRange.isPointInRange(findRange.endContainer,
                                      findRange.endOffset))
      return true;

    return false;
  },

  /**
   * Helper method to determine if an edit occurred within a highlight
   *
   * @param selection the selection we wish to check
   * @param node the node we want to check is contained in selection
   * @param offset the offset into node that we want to check
   * @returns the range containing (node, offset) or null if no ranges
   *          in the selection contain it
   */
  _findRange(selection, node, offset) {
    let rangeCount = selection.rangeCount;
    let rangeidx = 0;
    let foundContainingRange = false;
    let range = null;

    // Check to see if this node is inside one of the selection's ranges
    while (!foundContainingRange && rangeidx < rangeCount) {
      range = selection.getRangeAt(rangeidx);
      if (range.isPointInRange(node, offset)) {
        foundContainingRange = true;
        break;
      }
      rangeidx++;
    }

    if (foundContainingRange) {
      return range;
    }

    return null;
  },

  // Start of nsIEditActionListener implementations

  WillDeleteText(textNode, offset, length) {
    let editor = this._getEditableNode(textNode).editor;
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
    let range = this._findRange(fSelection, textNode, offset);

    if (range) {
      // Don't remove the highlighting if the deleted text is at the
      // end of the range
      if (textNode != range.endContainer ||
          offset != range.endOffset) {
        // Text within the highlight is being removed - the text can
        // no longer be a match, so remove the highlighting
        fSelection.removeRange(range);
        if (fSelection.rangeCount == 0) {
          this._removeEditorListeners(editor);
        }
      }
    }
  },

  DidInsertText(textNode, offset, aString) {
    let editor = this._getEditableNode(textNode).editor;
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
    let range = this._findRange(fSelection, textNode, offset);

    if (range) {
      // If the text was inserted before the highlight
      // adjust the highlight's bounds accordingly
      if (textNode == range.startContainer &&
          offset == range.startOffset) {
        range.setStart(range.startContainer,
                       range.startOffset + aString.length);
      } else if (textNode != range.endContainer ||
                 offset != range.endOffset) {
        // The edit occurred within the highlight - any addition of text
        // will result in the text no longer being a match,
        // so remove the highlighting
        fSelection.removeRange(range);
        if (fSelection.rangeCount == 0) {
          this._removeEditorListeners(editor);
        }
      }
    }
  },

  WillDeleteSelection(selection) {
    let editor = this._getEditableNode(selection.getRangeAt(0)
                                                 .startContainer).editor;
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);

    let shouldDelete = {};
    let numberOfDeletedSelections = 0;
    let numberOfMatches = fSelection.rangeCount;

    // We need to test if any ranges in the deleted selection (selection)
    // are in any of the ranges of the find selection
    // Usually both selections will only contain one range, however
    // either may contain more than one.

    for (let fIndex = 0; fIndex < numberOfMatches; fIndex++) {
      shouldDelete[fIndex] = false;
      let fRange = fSelection.getRangeAt(fIndex);

      for (let index = 0; index < selection.rangeCount; index++) {
        if (shouldDelete[fIndex]) {
          continue;
        }

        let selRange = selection.getRangeAt(index);
        let doesOverlap = this._checkOverlap(selRange, fRange);
        if (doesOverlap) {
          shouldDelete[fIndex] = true;
          numberOfDeletedSelections++;
        }
      }
    }

    // OK, so now we know what matches (if any) are in the selection
    // that is being deleted. Time to remove them.
    if (!numberOfDeletedSelections) {
      return;
    }

    for (let i = numberOfMatches - 1; i >= 0; i--) {
      if (shouldDelete[i])
        fSelection.removeRange(fSelection.getRangeAt(i));
    }

    // Remove listeners if no more highlights left
    if (!fSelection.rangeCount) {
      this._removeEditorListeners(editor);
    }
  },

  /*
   * nsIDocumentStateListener logic follows
   *
   * When attaching nsIEditActionListeners, there are no guarantees
   * as to whether the findbar or the documents in the browser will get
   * destructed first. This leads to the potential to either leak, or to
   * hold on to a reference an editable element's editor for too long,
   * preventing it from being destructed.
   *
   * However, when an editor's owning node is being destroyed, the editor
   * sends out a DocumentWillBeDestroyed notification. We can use this to
   * clean up our references to the object, to allow it to be destroyed in a
   * timely fashion.
   */

  /**
   * Unhook ourselves when one of our state listeners has been called.
   * This can happen in 4 cases:
   *  1) The document the editor belongs to is navigated away from, and
   *     the document is not being cached
   *
   *  2) The document the editor belongs to is expired from the cache
   *
   *  3) The tab containing the owning document is closed
   *
   *  4) The <input> or <textarea> that owns the editor is explicitly
   *     removed from the DOM
   *
   * @param the listener that was invoked
   */
  _onEditorDestruction(aListener) {
    // First find the index of the editor the given listener listens to.
    // The listeners and editors arrays must always be in sync.
    // The listener will be in our array of cached listeners, as this
    // method could not have been called otherwise.
    let idx = 0;
    while (this._stateListeners[idx] != aListener) {
      idx++;
    }

    // Unhook both listeners
    this._unhookListenersAtIndex(idx);
  },

  /**
   * Creates a unique document state listener for an editor.
   *
   * It is not possible to simply have the findbar implement the
   * listener interface itself, as it wouldn't have sufficient information
   * to work out which editor was being destroyed. Therefore, we create new
   * listeners on the fly, and cache them in sync with the editors they
   * listen to.
   */
  _createStateListener() {
    return {
      findbar: this,

      QueryInterface(iid) {
        if (iid.equals(Ci.nsIDocumentStateListener) ||
            iid.equals(Ci.nsISupports))
          return this;

        throw Components.results.NS_ERROR_NO_INTERFACE;
      },

      NotifyDocumentWillBeDestroyed() {
        this.findbar._onEditorDestruction(this);
      },

      // Unimplemented
      notifyDocumentCreated() {},
      notifyDocumentStateChanged(aDirty) {}
    };
  }
};
PK
!<{z``modules/FinderIterator.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["FinderIterator"];

const { interfaces: Ci, classes: Cc, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NLP", "resource://gre/modules/NLP.jsm");

const kDebug = false;
const kIterationSizeMax = 100;
const kTimeoutPref = "findbar.iteratorTimeout";

/**
 * FinderIterator singleton. See the documentation for the `start()` method to
 * learn more.
 */
this.FinderIterator = {
  _currentParams: null,
  _listeners: new Map(),
  _catchingUp: new Set(),
  _previousParams: null,
  _previousRanges: [],
  _spawnId: 0,
  _timeout: Services.prefs.getIntPref(kTimeoutPref),
  _timer: null,
  ranges: [],
  running: false,

  // Expose `kIterationSizeMax` to the outside world for unit tests to use.
  get kIterationSizeMax() { return kIterationSizeMax },

  get params() {
    if (!this._currentParams && !this._previousParams)
      return null;
    return Object.assign({}, this._currentParams || this._previousParams);
  },

  /**
   * Start iterating the active Finder docShell, using the options below. When
   * it already started at the request of another consumer, we first yield the
   * results we already collected before continuing onward to yield fresh results.
   * We make sure to pause every `kIterationSizeMax` iterations to make sure we
   * don't block the host process too long. In the case of a break like this, we
   * yield `undefined`, instead of a range.
   * Upon re-entrance after a break, we check if `stop()` was called during the
   * break and if so, we stop iterating.
   * Results are also passed to the `listener.onIteratorRangeFound` callback
   * method, along with a flag that specifies if the result comes from the cache
   * or is fresh. The callback also adheres to the `limit` flag.
   * The returned promise is resolved when 1) the limit is reached, 2) when all
   * the ranges have been found or 3) when `stop()` is called whilst iterating.
   *
   * @param {Number}  [options.allowDistance] Allowed edit distance between the
   *                                          current word and `options.word`
   *                                          when the iterator is already running
   * @param {Boolean} options.caseSensitive   Whether to search in case sensitive
   *                                          mode
   * @param {Boolean} options.entireWord      Whether to search in entire-word mode
   * @param {Finder}  options.finder          Currently active Finder instance
   * @param {Number}  [options.limit]         Limit the amount of results to be
   *                                          passed back. Optional, defaults to no
   *                                          limit.
   * @param {Boolean} [options.linksOnly]     Only yield ranges that are inside a
   *                                          hyperlink (used by QuickFind).
   *                                          Optional, defaults to `false`.
   * @param {Object}  options.listener        Listener object that implements the
   *                                          following callback functions:
   *                                           - onIteratorRangeFound({nsIDOMRange} range);
   *                                           - onIteratorReset();
   *                                           - onIteratorRestart({Object} iterParams);
   *                                           - onIteratorStart({Object} iterParams);
   * @param {Boolean} [options.useCache]        Whether to allow results already
   *                                            present in the cache or demand fresh.
   *                                            Optional, defaults to `false`.
   * @param {String}  options.word              Word to search for
   * @return {Promise}
   */
  start({ allowDistance, caseSensitive, entireWord, finder, limit, linksOnly, listener, useCache, word }) {
    // Take care of default values for non-required options.
    if (typeof allowDistance != "number")
      allowDistance = 0;
    if (typeof limit != "number")
      limit = -1;
    if (typeof linksOnly != "boolean")
      linksOnly = false;
    if (typeof useCache != "boolean")
      useCache = false;

    // Validate the options.
    if (typeof caseSensitive != "boolean")
      throw new Error("Missing required option 'caseSensitive'");
    if (typeof entireWord != "boolean")
      throw new Error("Missing required option 'entireWord'");
    if (!finder)
      throw new Error("Missing required option 'finder'");
    if (!word)
      throw new Error("Missing required option 'word'");
    if (typeof listener != "object" || !listener.onIteratorRangeFound)
      throw new TypeError("Missing valid, required option 'listener'");

    // If the listener was added before, make sure the promise is resolved before
    // we replace it with another.
    if (this._listeners.has(listener)) {
      let { onEnd } = this._listeners.get(listener);
      if (onEnd)
        onEnd();
    }

    let window = finder._getWindow();
    let resolver;
    let promise = new Promise(resolve => resolver = resolve);
    let iterParams = { caseSensitive, entireWord, linksOnly, useCache, window, word };

    this._listeners.set(listener, { limit, onEnd: resolver });

    // If we're not running anymore and we're requesting the previous result, use it.
    if (!this.running && this._previousResultAvailable(iterParams)) {
      this._yieldPreviousResult(listener, window);
      return promise;
    }

    if (this.running) {
      // Double-check if we're not running the iterator with a different set of
      // parameters, otherwise report an error with the most common reason.
      if (!this._areParamsEqual(this._currentParams, iterParams, allowDistance)) {
        if (kDebug) {
          Cu.reportError(`We're currently iterating over '${this._currentParams.word}', not '${word}'\n` +
            new Error().stack);
        }
        this._listeners.delete(listener);
        resolver();
        return promise;
      }

      // if we're still running, yield the set we have built up this far.
      this._yieldIntermediateResult(listener, window);

      return promise;
    }

    // Start!
    this.running = true;
    this._currentParams = iterParams;
    this._findAllRanges(finder, ++this._spawnId);

    return promise;
  },

  /**
   * Stop the currently running iterator as soon as possible and optionally cache
   * the result for later.
   *
   * @param {Boolean} [cachePrevious] Whether to save the result for later.
   *                                  Optional.
   */
  stop(cachePrevious = false) {
    if (!this.running)
      return;

    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
    }
    if (this._runningFindResolver) {
      this._runningFindResolver();
      this._runningFindResolver = null;
    }

    if (cachePrevious) {
      this._previousRanges = [].concat(this.ranges);
      this._previousParams = Object.assign({}, this._currentParams);
    } else {
      this._previousRanges = [];
      this._previousParams = null;
    }

    this._catchingUp.clear();
    this._currentParams = null;
    this.ranges = [];
    this.running = false;

    for (let [, { onEnd }] of this._listeners)
      onEnd();
  },

  /**
   * Stops the iteration that currently running, if it is, and start a new one
   * with the exact same params as before.
   *
   * @param {Finder} finder Currently active Finder instance
   */
  restart(finder) {
    // Capture current iterator params before we stop the show.
    let iterParams = this.params;
    if (!iterParams)
      return;
    this.stop();

    // Restart manually.
    this.running = true;
    this._currentParams = iterParams;

    this._findAllRanges(finder, ++this._spawnId);
    this._notifyListeners("restart", iterParams);
  },

  /**
   * Reset the internal state of the iterator. Typically this would be called
   * when the docShell is not active anymore, which makes the current and cached
   * previous result invalid.
   * If the iterator is running, it will be stopped as soon as possible.
   */
  reset() {
    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
    }
    if (this._runningFindResolver) {
      this._runningFindResolver();
      this._runningFindResolver = null;
    }

    this._catchingUp.clear();
    this._currentParams = this._previousParams = null;
    this._previousRanges = [];
    this.ranges = [];
    this.running = false;

    this._notifyListeners("reset");
    for (let [, { onEnd }] of this._listeners)
      onEnd();
    this._listeners.clear();
  },

  /**
   * Check if the currently running iterator parameters are the same as the ones
   * passed through the arguments. When `true`, we can keep it running as-is and
   * the consumer should stop the iterator when `false`.
   *
   * @param {Boolean}  options.caseSensitive Whether to search in case sensitive
   *                                         mode
   * @param {Boolean}  options.entireWord    Whether to search in entire-word mode
   * @param  {Boolean} options.linksOnly     Whether to search for the word to be
   *                                         present in links only
   * @param  {String}  options.word          The word being searched for
   * @return {Boolean}
   */
  continueRunning({ caseSensitive, entireWord, linksOnly, word }) {
    return (this.running &&
      this._currentParams.caseSensitive === caseSensitive &&
      this._currentParams.entireWord === entireWord &&
      this._currentParams.linksOnly === linksOnly &&
      this._currentParams.word == word);
  },

  /**
   * The default mode of operation of the iterator is to not accept duplicate
   * listeners, resolve the promise of the older listeners and replace it with
   * the new listener.
   * Consumers may opt-out of this behavior by using this check and not call
   * start().
   *
   * @param  {Object} paramSet Property bag with the same signature as you would
   *                           pass into `start()`
   * @return {Boolean}
   */
  isAlreadyRunning(paramSet) {
    return (this.running &&
      this._areParamsEqual(this._currentParams, paramSet) &&
      this._listeners.has(paramSet.listener));
  },

  /**
   * Safely notify all registered listeners that an event has occurred.
   *
   * @param {String}   callback    Name of the callback to invoke
   * @param {mixed}    [params]    Optional argument that will be passed to the
   *                               callback
   * @param {Iterable} [listeners] Set of listeners to notify. Optional, defaults
   *                               to `this._listeners.keys()`.
   */
  _notifyListeners(callback, params, listeners = this._listeners.keys()) {
    callback = "onIterator" + callback.charAt(0).toUpperCase() + callback.substr(1);
    for (let listener of listeners) {
      try {
        listener[callback](params);
      } catch (ex) {
        Cu.reportError("FinderIterator Error: " + ex);
      }
    }
  },

  /**
   * Internal; check if an iteration request is available in the previous result
   * that we cached.
   *
   * @param  {Boolean} options.caseSensitive Whether to search in case sensitive
   *                                         mode
   * @param  {Boolean} options.entireWord    Whether to search in entire-word mode
   * @param  {Boolean} options.linksOnly     Whether to search for the word to be
   *                                         present in links only
   * @param  {Boolean} options.useCache      Whether the consumer wants to use the
   *                                         cached previous result at all
   * @param  {String}  options.word          The word being searched for
   * @return {Boolean}
   */
  _previousResultAvailable({ caseSensitive, entireWord, linksOnly, useCache, word }) {
    return !!(useCache &&
      this._areParamsEqual(this._previousParams, { caseSensitive, entireWord, linksOnly, word }) &&
      this._previousRanges.length);
  },

  /**
   * Internal; compare if two sets of iterator parameters are equivalent.
   *
   * @param  {Object} paramSet1       First set of params (left hand side)
   * @param  {Object} paramSet2       Second set of params (right hand side)
   * @param  {Number} [allowDistance] Allowed edit distance between the two words.
   *                                  Optional, defaults to '0', which means 'no
   *                                  distance'.
   * @return {Boolean}
   */
  _areParamsEqual(paramSet1, paramSet2, allowDistance = 0) {
    return (!!paramSet1 && !!paramSet2 &&
      paramSet1.caseSensitive === paramSet2.caseSensitive &&
      paramSet1.entireWord === paramSet2.entireWord &&
      paramSet1.linksOnly === paramSet2.linksOnly &&
      paramSet1.window === paramSet2.window &&
      NLP.levenshtein(paramSet1.word, paramSet2.word) <= allowDistance);
  },

  /**
   * Internal; iterate over a predefined set of ranges that have been collected
   * before.
   * Also here, we make sure to pause every `kIterationSizeMax` iterations to
   * make sure we don't block the host process too long. In the case of a break
   * like this, we yield `undefined`, instead of a range.
   *
   * @param {Object}       listener    Listener object
   * @param {Array}        rangeSource Set of ranges to iterate over
   * @param {nsIDOMWindow} window      The window object is only really used
   *                                   for access to `setTimeout`
   * @param {Boolean}      [withPause] Whether to pause after each `kIterationSizeMax`
   *                                   number of ranges yielded. Optional, defaults
   *                                   to `true`.
   * @yield {nsIDOMRange}
   */
  async _yieldResult(listener, rangeSource, window, withPause = true) {
    // We keep track of the number of iterations to allow a short pause between
    // every `kIterationSizeMax` number of iterations.
    let iterCount = 0;
    let { limit, onEnd } = this._listeners.get(listener);
    let ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
    for (let range of ranges) {
      try {
        range.startContainer;
      } catch (ex) {
        // Don't yield dead objects, so use the escape hatch.
        if (ex.message.includes("dead object"))
          return;
      }

      // Pass a flag that is `true` when we're returning the result from a
      // cached previous iteration.
      listener.onIteratorRangeFound(range, !this.running);
      await range;

      if (withPause && ++iterCount >= kIterationSizeMax) {
        iterCount = 0;
        // Make sure to save the current limit for later.
        this._listeners.set(listener, { limit, onEnd });
        // Sleep for the rest of this cycle.
        await new Promise(resolve => window.setTimeout(resolve, 0));
        // After a sleep, the set of ranges may have updated.
        ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
      }

      if (limit !== -1 && --limit === 0) {
        // We've reached our limit; no need to do more work.
        this._listeners.delete(listener);
        onEnd();
        return;
      }
    }

    // Save the updated limit globally.
    this._listeners.set(listener, { limit, onEnd });
  },

  /**
   * Internal; iterate over the set of previously found ranges. Meanwhile it'll
   * mark the listener as 'catching up', meaning it will not receive fresh
   * results from a running iterator.
   *
   * @param {Object}       listener Listener object
   * @param {nsIDOMWindow} window   The window object is only really used
   *                                for access to `setTimeout`
   * @yield {nsIDOMRange}
   */
  async _yieldPreviousResult(listener, window) {
    this._notifyListeners("start", this.params, [listener]);
    this._catchingUp.add(listener);
    await this._yieldResult(listener, this._previousRanges, window);
    this._catchingUp.delete(listener);
    let { onEnd } = this._listeners.get(listener);
    if (onEnd)
      onEnd();
  },

  /**
   * Internal; iterate over the set of already found ranges. Meanwhile it'll
   * mark the listener as 'catching up', meaning it will not receive fresh
   * results from the running iterator.
   *
   * @param {Object}       listener Listener object
   * @param {nsIDOMWindow} window   The window object is only really used
   *                                for access to `setTimeout`
   * @yield {nsIDOMRange}
   */
  async _yieldIntermediateResult(listener, window) {
    this._notifyListeners("start", this.params, [listener]);
    this._catchingUp.add(listener);
    await this._yieldResult(listener, this.ranges, window, false);
    this._catchingUp.delete(listener);
  },

  /**
   * Internal; see the documentation of the start() method above.
   *
   * @param {Finder}       finder  Currently active Finder instance
   * @param {Number}       spawnId Since `stop()` is synchronous and this method
   *                               is not, this identifier is used to learn if
   *                               it's supposed to still continue after a pause.
   * @yield {nsIDOMRange}
   */
  async _findAllRanges(finder, spawnId) {
    if (this._timeout) {
      if (this._timer)
        clearTimeout(this._timer);
      if (this._runningFindResolver)
        this._runningFindResolver();

      let timeout = this._timeout;
      let searchTerm = this._currentParams.word;
      // Wait a little longer when the first or second character is typed into
      // the findbar.
      if (searchTerm.length == 1)
        timeout *= 4;
      else if (searchTerm.length == 2)
        timeout *= 2;
      await new Promise(resolve => {
        this._runningFindResolver = resolve;
        this._timer = setTimeout(resolve, timeout);
      });
      this._timer = this._runningFindResolver = null;
      // During the timeout, we could have gotten the signal to stop iterating.
      // Make sure we do here.
      if (!this.running || spawnId !== this._spawnId)
        return;
    }

    this._notifyListeners("start", this.params);

    let { linksOnly, window } = this._currentParams;
    // First we collect all frames we need to search through, whilst making sure
    // that the parent window gets dibs.
    let frames = [window].concat(this._collectFrames(window, finder));
    let iterCount = 0;
    for (let frame of frames) {
      for (let range of this._iterateDocument(this._currentParams, frame)) {
        // Between iterations, for example after a sleep of one cycle, we could
        // have gotten the signal to stop iterating. Make sure we do here.
        if (!this.running || spawnId !== this._spawnId)
          return;

        // Deal with links-only mode here.
        if (linksOnly && !this._rangeStartsInLink(range))
          continue;

        this.ranges.push(range);

        // Call each listener with the range we just found.
        for (let [listener, { limit, onEnd }] of this._listeners) {
          if (this._catchingUp.has(listener))
            continue;

          listener.onIteratorRangeFound(range);

          if (limit !== -1 && --limit === 0) {
            // We've reached our limit; no need to do more work for this listener.
            this._listeners.delete(listener);
            onEnd();
            continue;
          }

          // Save the updated limit globally.
          this._listeners.set(listener, { limit, onEnd });
        }

        await range;

        if (++iterCount >= kIterationSizeMax) {
          iterCount = 0;
          // Sleep for the rest of this cycle.
          await new Promise(resolve => window.setTimeout(resolve, 0));
        }
      }
    }

    // When the iterating has finished, make sure we reset and save the state
    // properly.
    this.stop(true);
  },

  /**
   * Internal; basic wrapper around nsIFind that provides a generator yielding
   * a range each time an occurence of `word` string is found.
   *
   * @param {Boolean}      options.caseSensitive Whether to search in case
   *                                             sensitive mode
   * @param {Boolean}      options.entireWord    Whether to search in entire-word
   *                                             mode
   * @param {String}       options.word          The word to search for
   * @param {nsIDOMWindow} window                The window to search in
   * @yield {nsIDOMRange}
   */
  *_iterateDocument({ caseSensitive, entireWord, word }, window) {
    let doc = window.document;
    let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
               doc.body : doc.documentElement;

    if (!body)
      return;

    let searchRange = doc.createRange();
    searchRange.selectNodeContents(body);

    let startPt = searchRange.cloneRange();
    startPt.collapse(true);

    let endPt = searchRange.cloneRange();
    endPt.collapse(false);

    let retRange = null;

    let nsIFind = Cc["@mozilla.org/embedcomp/rangefind;1"]
                    .createInstance()
                    .QueryInterface(Ci.nsIFind);
    nsIFind.caseSensitive = caseSensitive;
    nsIFind.entireWord = entireWord;

    while ((retRange = nsIFind.Find(word, searchRange, startPt, endPt))) {
      yield retRange;
      startPt = retRange.cloneRange();
      startPt.collapse(false);
    }
  },

  /**
   * Internal; helper method for the iterator that recursively collects all
   * visible (i)frames inside a window.
   *
   * @param  {nsIDOMWindow} window The window to extract the (i)frames from
   * @param  {Finder}       finder The Finder instance
   * @return {Array}        Stack of frames to iterate over
   */
  _collectFrames(window, finder) {
    let frames = [];
    if (!("frames" in window) || !window.frames.length)
      return frames;

    // Casting `window.frames` to an Iterator doesn't work, so we're stuck with
    // a plain, old for-loop.
    for (let i = 0, l = window.frames.length; i < l; ++i) {
      let frame = window.frames[i];
      // Don't count matches in hidden frames.
      let frameEl = frame && frame.frameElement;
      if (!frameEl)
        continue;
      // Construct a range around the frame element to check its visiblity.
      let range = window.document.createRange();
      range.setStart(frameEl, 0);
      range.setEnd(frameEl, 0);
      if (!finder._fastFind.isRangeVisible(range, this._getDocShell(range), true))
        continue;
      // All conditions pass, so push the current frame and its children on the
      // stack.
      frames.push(frame, ...this._collectFrames(frame, finder));
    }

    return frames;
  },

  /**
   * Internal; helper method to extract the docShell reference from a Window or
   * Range object.
   *
   * @param  {nsIDOMRange} windowOrRange Window object to query. May also be a
   *                                     Range, from which the owner window will
   *                                     be queried.
   * @return {nsIDocShell}
   */
  _getDocShell(windowOrRange) {
    let window = windowOrRange;
    // Ranges may also be passed in, so fetch its window.
    if (windowOrRange instanceof Ci.nsIDOMRange)
      window = windowOrRange.startContainer.ownerGlobal;
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .QueryInterface(Ci.nsIDocShell);
  },

  /**
   * Internal; determines whether a range is inside a link.
   *
   * @param  {nsIDOMRange} range the range to check
   * @return {Boolean}     True if the range starts in a link
   */
  _rangeStartsInLink(range) {
    let isInsideLink = false;
    let node = range.startContainer;

    if (node.nodeType == node.ELEMENT_NODE) {
      if (node.hasChildNodes) {
        let childNode = node.item(range.startOffset);
        if (childNode)
          node = childNode;
      }
    }

    const XLink_NS = "http://www.w3.org/1999/xlink";
    const HTMLAnchorElement = (node.ownerDocument || node).defaultView.HTMLAnchorElement;
    do {
      if (node instanceof HTMLAnchorElement) {
        isInsideLink = node.hasAttribute("href");
        break;
      } else if (typeof node.hasAttributeNS == "function" &&
                 node.hasAttributeNS(XLink_NS, "href")) {
        isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
        break;
      }

      node = node.parentNode;
    } while (node);

    return isInsideLink;
  }
};
PK
!<QW%%modules/ForgetAboutSite.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                  "resource://gre/modules/Downloads.jsm");

this.EXPORTED_SYMBOLS = ["ForgetAboutSite"];

/**
 * Returns true if the string passed in is part of the root domain of the
 * current string.  For example, if this is "www.mozilla.org", and we pass in
 * "mozilla.org", this will return true.  It would return false the other way
 * around.
 */
function hasRootDomain(str, aDomain) {
  let index = str.indexOf(aDomain);
  // If aDomain is not found, we know we do not have it as a root domain.
  if (index == -1)
    return false;

  // If the strings are the same, we obviously have a match.
  if (str == aDomain)
    return true;

  // Otherwise, we have aDomain as our root domain iff the index of aDomain is
  // aDomain.length subtracted from our length and (since we do not have an
  // exact match) the character before the index is a dot or slash.
  let prevChar = str[index - 1];
  return (index == (str.length - aDomain.length)) &&
         (prevChar == "." || prevChar == "/");
}

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.ForgetAboutSite = {
  async removeDataFromDomain(aDomain) {
    PlacesUtils.history.removePagesFromHost(aDomain, true);

    let promises = [];
    // Cache
    promises.push((async function() {
      let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
               getService(Ci.nsICacheStorageService);
      // NOTE: there is no way to clear just that domain, so we clear out
      //       everything)
      cs.clear();
    })().catch(ex => {
      throw new Error("Exception thrown while clearing the cache: " + ex);
    }));

    // Image Cache
    promises.push((async function() {
      let imageCache = Cc["@mozilla.org/image/tools;1"].
                       getService(Ci.imgITools).getImgCacheForDocument(null);
      imageCache.clearCache(false); // true=chrome, false=content
    })().catch(ex => {
      throw new Error("Exception thrown while clearing the image cache: " + ex);
    }));

    // Cookies
    // Need to maximize the number of cookies cleaned here
    promises.push((async function() {
      let cm = Cc["@mozilla.org/cookiemanager;1"].
               getService(Ci.nsICookieManager2);
      let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
      while (enumerator.hasMoreElements()) {
        let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
        cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
      }
    })().catch(ex => {
      throw new Error("Exception thrown while clearning cookies: " + ex);
    }));

    // EME
    promises.push((async function() {
      let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
                getService(Ci.mozIGeckoMediaPluginChromeService);
      mps.forgetThisSite(aDomain, JSON.stringify({}));
    })().catch(ex => {
      throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
    }));


    // Plugin data
    const phInterface = Ci.nsIPluginHost;
    const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
    let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
    let tags = ph.getPluginTags();
    for (let i = 0; i < tags.length; i++) {
      promises.push(new Promise(resolve => {
        try {
          ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, resolve);
        } catch (e) {
          // Ignore errors from the plugin, but resolve the promise
          // We cannot check if something is a bailout or an error
          resolve();
        }
      }));
    }

    // Downloads
    promises.push((async function() {
      let list = await Downloads.getList(Downloads.ALL);
      list.removeFinished(download => hasRootDomain(
        NetUtil.newURI(download.source.url).host, aDomain));
    })().catch(ex => {
      throw new Error("Exception in clearing Downloads: " + ex);
    }));

    // Passwords
    promises.push((async function() {
      let lm = Cc["@mozilla.org/login-manager;1"].
               getService(Ci.nsILoginManager);
      // Clear all passwords for domain
      let logins = lm.getAllLogins();
      for (let i = 0; i < logins.length; i++)
        if (hasRootDomain(logins[i].hostname, aDomain))
          lm.removeLogin(logins[i]);
    })().catch(ex => {
      // XXXehsan: is there a better way to do this rather than this
      // hacky comparison?
      if (ex.message.indexOf("User canceled Master Password entry") == -1) {
        throw new Error("Exception occured in clearing passwords :" + ex);
      }
    }));

    // Permissions
    let pm = Cc["@mozilla.org/permissionmanager;1"].
             getService(Ci.nsIPermissionManager);
    // Enumerate all of the permissions, and if one matches, remove it
    let enumerator = pm.enumerator;
    while (enumerator.hasMoreElements()) {
      let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
      promises.push(new Promise((resolve, reject) => {
        try {
          if (hasRootDomain(perm.principal.URI.host, aDomain)) {
            pm.removePermission(perm);
          }
        } catch (ex) {
          // Ignore entry
        } finally {
          resolve();
        }
      }));
    }

    // Offline Storages
    promises.push((async function() {
      let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
                getService(Ci.nsIQuotaManagerService);
      // delete data from both HTTP and HTTPS sites
      let httpURI = NetUtil.newURI("http://" + aDomain);
      let httpsURI = NetUtil.newURI("https://" + aDomain);
      // Following code section has been reverted to the state before Bug 1238183,
      // but added a new argument to clearStoragesForPrincipal() for indicating
      // clear all storages under a given origin.
      let httpPrincipal = Services.scriptSecurityManager
                                   .createCodebasePrincipal(httpURI, {});
      let httpsPrincipal = Services.scriptSecurityManager
                                   .createCodebasePrincipal(httpsURI, {});
      qms.clearStoragesForPrincipal(httpPrincipal, null, true);
      qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
    })().catch(ex => {
      throw new Error("Exception occured while clearing offline storages: " + ex);
    }));

    // Content Preferences
    promises.push((async function() {
      let cps2 = Cc["@mozilla.org/content-pref/service;1"].
                 getService(Ci.nsIContentPrefService2);
      cps2.removeBySubdomain(aDomain, null, {
        handleCompletion: (reason) => {
          // Notify other consumers, including extensions
          Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
          if (reason === cps2.COMPLETE_ERROR) {
            throw new Error("Exception occured while clearing content preferences");
          }
        },
        handleError() {}
      });
    })());

    // Predictive network data - like cache, no way to clear this per
    // domain, so just trash it all
    promises.push((async function() {
      let np = Cc["@mozilla.org/network/predictor;1"].
               getService(Ci.nsINetworkPredictor);
      np.reset();
    })().catch(ex => {
      throw new Error("Exception occured while clearing predictive network data: " + ex);
    }));

    // Push notifications.
    promises.push((async function() {
      var push = Cc["@mozilla.org/push/Service;1"].
                 getService(Ci.nsIPushService);
      push.clearForDomain(aDomain, status => {
        if (!Components.isSuccessCode(status)) {
          throw new Error("Exception occured while clearing push notifications: " + status);
        }
      });
    })());

    // HSTS and HPKP
    promises.push((async function() {
      let sss = Cc["@mozilla.org/ssservice;1"].
                getService(Ci.nsISiteSecurityService);
      for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
                        Ci.nsISiteSecurityService.HEADER_HPKP]) {
        // Also remove HSTS/HPKP information for subdomains by enumerating the
        // information in the site security service.
        let enumerator = sss.enumerate(type);
        while (enumerator.hasMoreElements()) {
          let entry = enumerator.getNext();
          let hostname = entry.QueryInterface(Ci.nsISiteSecurityState).hostname;
          // If the hostname is aDomain's subdomain, we remove its state.
          if (hostname == aDomain || hostname.endsWith("." + aDomain)) {
            // This uri is used as a key to remove the state.
            let uri = NetUtil.newURI("https://" + hostname);
            sss.removeState(type, uri, 0, entry.originAttributes);
          }
        }
      }
    })().catch(ex => {
      throw new Error("Exception thrown while clearing HSTS/HPKP: " + ex);
    }));

    let ErrorCount = 0;
    for (let promise of promises) {
      try {
        await promise;
      } catch (ex) {
        Cu.reportError(ex);
        ErrorCount++;
      }
    }
    if (ErrorCount !== 0)
      throw new Error(`There were a total of ${ErrorCount} errors during removal`);
  }
}
PK
!<"XTo8o8modules/FormData.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["FormData"];

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/**
 * Returns whether the given URL very likely has input
 * fields that contain serialized session store data.
 */
function isRestorationPage(url) {
  return url == "about:sessionrestore" || url == "about:welcomeback";
}

/**
 * Returns whether the given form |data| object contains nested restoration
 * data for a page like about:sessionrestore or about:welcomeback.
 */
function hasRestorationData(data) {
  if (isRestorationPage(data.url) && data.id) {
    return typeof(data.id.sessionData) == "object";
  }

  return false;
}

/**
 * Returns the given document's current URI and strips
 * off the URI's anchor part, if any.
 */
function getDocumentURI(doc) {
  return doc.documentURI.replace(/#.*$/, "");
}

/**
 * Returns whether the given value is a valid credit card number based on
 * the Luhn algorithm. See https://en.wikipedia.org/wiki/Luhn_algorithm.
 */
function isValidCCNumber(value) {
  // Remove dashes and whitespace.
  let ccNumber = value.replace(/[-\s]+/g, "");

  // Check for non-alphanumeric characters.
  if (/[^0-9]/.test(ccNumber)) {
    return false;
  }

  // Check for invalid length.
  let length = ccNumber.length;
  if (length != 9 && length != 15 && length != 16) {
    return false;
  }

  let total = 0;
  for (let i = 0; i < length; i++) {
    let currentChar = ccNumber.charAt(length - i - 1);
    let currentDigit = parseInt(currentChar, 10);

    if (i % 2) {
      // Double every other value.
      total += currentDigit * 2;
      // If the doubled value has two digits, add the digits together.
      if (currentDigit > 4) {
        total -= 9;
      }
    } else {
      total += currentDigit;
    }
  }
  return total % 10 == 0;
}

// For a comprehensive list of all available <INPUT> types see
// https://dxr.mozilla.org/mozilla-central/search?q=kInputTypeTable&redirect=false
const IGNORE_ATTRIBUTES = [
  ["type", new Set(["password", "hidden", "button", "image", "submit", "reset"])],
  ["autocomplete", new Set(["off"])]
];
function shouldIgnoreNode(node) {
  for (let i = 0; i < IGNORE_ATTRIBUTES.length; ++i) {
    let [attrName, attrValues] = IGNORE_ATTRIBUTES[i];
    if (node.hasAttribute(attrName) && attrValues.has(node.getAttribute(attrName).toLowerCase())) {
      return true;
    }
  }
  return false;
}

/**
 * The public API exported by this module that allows to collect
 * and restore form data for a document and its subframes.
 */
this.FormData = Object.freeze({
  collect(frame) {
    return FormDataInternal.collect(frame);
  },

  restoreTree(root, data) {
    FormDataInternal.restoreTree(root, data);
  }
});

/**
 * This module's internal API.
 */
var FormDataInternal = {
  namespaceURIs: {
    "xhtml": "http://www.w3.org/1999/xhtml",
    "xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  },

  /**
   * Resolves an XPath query generated by node.generateXPath.
   */
  resolve(aDocument, aQuery) {
    let xptype = Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
    return aDocument.evaluate(aQuery, aDocument, this.resolveNS.bind(this), xptype, null).singleNodeValue;
  },

  /**
   * Namespace resolver for the above XPath resolver.
   */
  resolveNS(aPrefix) {
    return this.namespaceURIs[aPrefix] || null;
  },

  /**
   * @returns an XPath query to all savable form field nodes
   */
  get restorableFormNodesXPath() {
    let formNodesXPath = "//textarea|//xhtml:textarea|" +
      "//select|//xhtml:select|" +
      "//input|//xhtml:input" +
      // Special case for about:config's search field.
      "|/xul:window[@id='config']//xul:textbox[@id='textbox']";

    delete this.restorableFormNodesXPath;
    return (this.restorableFormNodesXPath = formNodesXPath);
  },

  /**
   * Collect form data for a given |frame| *not* including any subframes.
   *
   * The returned object may have an "id", "xpath", or "innerHTML" key or a
   * combination of those three. Form data stored under "id" is for input
   * fields with id attributes. Data stored under "xpath" is used for input
   * fields that don't have a unique id and need to be queried using XPath.
   * The "innerHTML" key is used for editable documents (designMode=on).
   *
   * Example:
   *   {
   *     id: {input1: "value1", input3: "value3"},
   *     xpath: {
   *       "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value2",
   *       "/xhtml:html/xhtml:body/xhtml:input[@name='input4']" : "value4"
   *     }
   *   }
   *
   * @param  doc
   *         DOMDocument instance to obtain form data for.
   * @return object
   *         Form data encoded in an object.
   */
  collect({document: doc}) {
    let formNodes = doc.evaluate(
      this.restorableFormNodesXPath,
      doc,
      this.resolveNS.bind(this),
      Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
    );

    let node;
    let ret = {};

    // Limit the number of XPath expressions for performance reasons. See
    // bug 477564.
    const MAX_TRAVERSED_XPATHS = 100;
    let generatedCount = 0;

    while ((node = formNodes.iterateNext())) {
      if (shouldIgnoreNode(node)) {
        continue;
      }
      let hasDefaultValue = true;
      let value;

      // Only generate a limited number of XPath expressions for perf reasons
      // (cf. bug 477564)
      if (!node.id && generatedCount > MAX_TRAVERSED_XPATHS) {
        continue;
      }

      // We do not want to collect credit card numbers.
      if (node instanceof Ci.nsIDOMHTMLInputElement &&
          isValidCCNumber(node.value)) {
        continue;
      }

      if (node instanceof Ci.nsIDOMHTMLInputElement ||
          node instanceof Ci.nsIDOMHTMLTextAreaElement ||
          node instanceof Ci.nsIDOMXULTextBoxElement) {
        switch (node.type) {
          case "checkbox":
          case "radio":
            value = node.checked;
            hasDefaultValue = value == node.defaultChecked;
            break;
          case "file":
            value = { type: "file", fileList: node.mozGetFileNameArray() };
            hasDefaultValue = !value.fileList.length;
            break;
          default: // text, textarea
            value = node.value;
            hasDefaultValue = value == node.defaultValue;
            break;
        }
      } else if (!node.multiple) {
        // <select>s without the multiple attribute are hard to determine the
        // default value, so assume we don't have the default.
        hasDefaultValue = false;
        value = { selectedIndex: node.selectedIndex, value: node.value };
      } else {
        // <select>s with the multiple attribute are easier to determine the
        // default value since each <option> has a defaultSelected property
        let options = Array.map(node.options, opt => {
          hasDefaultValue = hasDefaultValue && (opt.selected == opt.defaultSelected);
          return opt.selected ? opt.value : -1;
        });
        value = options.filter(ix => ix > -1);
      }

      // In order to reduce XPath generation (which is slow), we only save data
      // for form fields that have been changed. (cf. bug 537289)
      if (hasDefaultValue) {
        continue;
      }

      if (node.id) {
        ret.id = ret.id || {};
        ret.id[node.id] = value;
      } else {
        generatedCount++;
        ret.xpath = ret.xpath || {};
        ret.xpath[node.generateXPath()] = value;
      }
    }

    // designMode is undefined e.g. for XUL documents (as about:config)
    if ((doc.designMode || "") == "on" && doc.body) {
      // eslint-disable-next-line no-unsanitized/property
      ret.innerHTML = doc.body.innerHTML;
    }

    // Return |null| if no form data has been found.
    if (Object.keys(ret).length === 0) {
      return null;
    }

    // Store the frame's current URL with its form data so that we can compare
    // it when restoring data to not inject form data into the wrong document.
    ret.url = getDocumentURI(doc);

    // We want to avoid saving data for about:sessionrestore as a string.
    // Since it's stored in the form as stringified JSON, stringifying further
    // causes an explosion of escape characters. cf. bug 467409
    if (isRestorationPage(ret.url)) {
      ret.id.sessionData = JSON.parse(ret.id.sessionData);
    }

    return ret;
  },

  /**
   * Restores form |data| for the given frame. The data is expected to be in
   * the same format that FormData.collect() returns.
   *
   * @param frame (DOMWindow)
   *        The frame to restore form data to.
   * @param data (object)
   *        An object holding form data.
   */
  restore({document: doc}, data) {
    // Don't restore any data for the given frame if the URL
    // stored in the form data doesn't match its current URL.
    if (!data.url || data.url != getDocumentURI(doc)) {
      return;
    }

    // For about:{sessionrestore,welcomeback} we saved the field as JSON to
    // avoid nested instances causing humongous sessionstore.js files.
    // cf. bug 467409
    if (hasRestorationData(data)) {
      data.id.sessionData = JSON.stringify(data.id.sessionData);
    }

    if ("id" in data) {
      let retrieveNode = id => doc.getElementById(id);
      this.restoreManyInputValues(data.id, retrieveNode);
    }

    if ("xpath" in data) {
      let retrieveNode = xpath => this.resolve(doc, xpath);
      this.restoreManyInputValues(data.xpath, retrieveNode);
    }

    if ("innerHTML" in data) {
      if (doc.body && doc.designMode == "on") {
      // eslint-disable-next-line no-unsanitized/property
        doc.body.innerHTML = data.innerHTML;
        this.fireInputEvent(doc.body);
      }
    }
  },

  /**
   * Iterates the given form data, retrieving nodes for all the keys and
   * restores their appropriate values.
   *
   * @param data (object)
   *        A subset of the form data as collected by FormData.collect(). This
   *        is either data stored under "id" or under "xpath".
   * @param retrieve (function)
   *        The function used to retrieve the input field belonging to a key
   *        in the given |data| object.
   */
  restoreManyInputValues(data, retrieve) {
    for (let key of Object.keys(data)) {
      let input = retrieve(key);
      if (input) {
        this.restoreSingleInputValue(input, data[key]);
      }
    }
  },

  /**
   * Restores a given form value to a given DOMNode and takes care of firing
   * the appropriate DOM event should the input's value change.
   *
   * @param  aNode
   *         DOMNode to set form value on.
   * @param  aValue
   *         Value to set form element to.
   */
  restoreSingleInputValue(aNode, aValue) {
    let fireEvent = false;

    if (typeof aValue == "string" && aNode.type != "file") {
      // Don't dispatch an input event if there is no change.
      if (aNode.value == aValue) {
        return;
      }

      aNode.value = aValue;
      fireEvent = true;
    } else if (typeof aValue == "boolean") {
      // Don't dispatch a change event for no change.
      if (aNode.checked == aValue) {
        return;
      }

      aNode.checked = aValue;
      fireEvent = true;
    } else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
      // Don't dispatch a change event for no change
      if (aNode.options[aNode.selectedIndex].value == aValue.value) {
        return;
      }

      // find first option with matching aValue if possible
      for (let i = 0; i < aNode.options.length; i++) {
        if (aNode.options[i].value == aValue.value) {
          aNode.selectedIndex = i;
          fireEvent = true;
          break;
        }
      }
    } else if (aValue && aValue.fileList && aValue.type == "file" &&
      aNode.type == "file") {
      try {
        // FIXME (bug 1122855): This won't work in content processes.
        aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
      } catch (e) {
        Cu.reportError("mozSetFileNameArray: " + e);
      }
      fireEvent = true;
    } else if (Array.isArray(aValue) && aNode.options) {
      Array.forEach(aNode.options, function(opt, index) {
        // don't worry about malformed options with same values
        opt.selected = aValue.indexOf(opt.value) > -1;

        // Only fire the event here if this wasn't selected by default
        if (!opt.defaultSelected) {
          fireEvent = true;
        }
      });
    }

    // Fire events for this node if applicable
    if (fireEvent) {
      this.fireInputEvent(aNode);
    }
  },

  /**
   * Dispatches an event of type "input" to the given |node|.
   *
   * @param node (DOMNode)
   */
  fireInputEvent(node) {
    let doc = node.ownerDocument;
    let event = doc.createEvent("UIEvents");
    event.initUIEvent("input", true, true, doc.defaultView, 0);
    node.dispatchEvent(event);
  },

  /**
   * Restores form data for the current frame hierarchy starting at |root|
   * using the given form |data|.
   *
   * If the given |root| frame's hierarchy doesn't match that of the given
   * |data| object we will silently discard data for unreachable frames. For
   * security reasons we will never restore form data to the wrong frames as
   * we bail out silently if the stored URL doesn't match the frame's current
   * URL.
   *
   * @param root (DOMWindow)
   * @param data (object)
   *        {
   *          formdata: {id: {input1: "value1"}},
   *          children: [
   *            {formdata: {id: {input2: "value2"}}},
   *            null,
   *            {formdata: {xpath: { ... }}, children: [ ... ]}
   *          ]
   *        }
   */
  restoreTree(root, data) {
    // Don't restore any data for the root frame and its subframes if there
    // is a URL stored in the form data and it doesn't match its current URL.
    if (data.url && data.url != getDocumentURI(root.document)) {
      return;
    }

    if (data.url) {
      this.restore(root, data);
    }

    if (!data.hasOwnProperty("children")) {
      return;
    }

    let frames = root.frames;
    for (let index of Object.keys(data.children)) {
      if (index < frames.length) {
        this.restoreTree(frames[index], data.children[index]);
      }
    }
  }
};
PK
!<8t((modules/FormHistory.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * FormHistory
 *
 * Used to store values that have been entered into forms which may later
 * be used to automatically fill in the values when the form is visited again.
 *
 * search(terms, queryData, callback)
 *   Look up values that have been previously stored.
 *     terms - array of terms to return data for
 *     queryData - object that contains the query terms
 *       The query object contains properties for each search criteria to match, where the value
 *       of the property specifies the value that term must have. For example,
 *       { term1: value1, term2: value2 }
 *     callback - callback that is called when results are available or an error occurs.
 *       The callback is passed a result array containing each found entry. Each element in
 *       the array is an object containing a property for each search term specified by 'terms'.
 * count(queryData, callback)
 *   Find the number of stored entries that match the given criteria.
 *     queryData - array of objects that indicate the query. See the search method for details.
 *     callback - callback that is called when results are available or an error occurs.
 *       The callback is passed the number of found entries.
 * update(changes, callback)
 *    Write data to form history storage.
 *      changes - an array of changes to be made. If only one change is to be made, it
 *                may be passed as an object rather than a one-element array.
 *        Each change object is of the form:
 *          { op: operation, term1: value1, term2: value2, ... }
 *        Valid operations are:
 *          add - add a new entry
 *          update - update an existing entry
 *          remove - remove an entry
 *          bump - update the last accessed time on an entry
 *        The terms specified allow matching of one or more specific entries. If no terms
 *        are specified then all entries are matched. This means that { op: "remove" } is
 *        used to remove all entries and clear the form history.
 *      callback - callback that is called when results have been stored.
 * getAutoCompeteResults(searchString, params, callback)
 *   Retrieve an array of form history values suitable for display in an autocomplete list.
 *   Returns an mozIStoragePendingStatement that can be used to cancel the operation if
 *   needed.
 *     searchString - the string to search for, typically the entered value of a textbox
 *     params - zero or more filter arguments:
 *       fieldname - form field name
 *       agedWeight
 *       bucketSize
 *       expiryDate
 *       maxTimeGroundings
 *       timeGroupingSize
 *       prefixWeight
 *       boundaryWeight
 *     callback - callback that is called with the array of results. Each result in the array
 *                is an object with four arguments:
 *                  text, textLowerCase, frecency, totalScore
 * schemaVersion
 *   This property holds the version of the database schema
 *
 * Terms:
 *  guid - entry identifier. For 'add', a guid will be generated.
 *  fieldname - form field name
 *  value - form value
 *  timesUsed - the number of times the entry has been accessed
 *  firstUsed - the time the the entry was first created
 *  lastUsed - the time the entry was last accessed
 *  firstUsedStart - search for entries created after or at this time
 *  firstUsedEnd - search for entries created before or at this time
 *  lastUsedStart - search for entries last accessed after or at this time
 *  lastUsedEnd - search for entries last accessed before or at this time
 *  newGuid - a special case valid only for 'update' and allows the guid for
 *            an existing record to be updated. The 'guid' term is the only
 *            other term which can be used (ie, you can not also specify a
 *            fieldname, value etc) and indicates the guid of the existing
 *            record that should be updated.
 *
 * In all of the above methods, the callback argument should be an object with
 * handleResult(result), handleFailure(error) and handleCompletion(reason) functions.
 * For search and getAutoCompeteResults, result is an object containing the desired
 * properties. For count, result is the integer count. For, update, handleResult is
 * not called. For handleCompletion, reason is either 0 if successful or 1 if
 * an error occurred.
 */

this.EXPORTED_SYMBOLS = ["FormHistory"];

const { 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/AppConstants.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "uuidService",
                                   "@mozilla.org/uuid-generator;1",
                                   "nsIUUIDGenerator");

const DB_SCHEMA_VERSION = 4;
const DAY_IN_MS  = 86400000; // 1 day in milliseconds
const MAX_SEARCH_TOKENS = 10;
const NOOP = function noop() {};

var supportsDeletedTable = AppConstants.platform == "android";

var Prefs = {
  initialized: false,

  get debug() { this.ensureInitialized(); return this._debug; },
  get enabled() { this.ensureInitialized(); return this._enabled; },
  get expireDays() { this.ensureInitialized(); return this._expireDays; },

  ensureInitialized() {
    if (this.initialized) {
      return;
    }

    this.initialized = true;

    this._debug = Services.prefs.getBoolPref("browser.formfill.debug");
    this._enabled = Services.prefs.getBoolPref("browser.formfill.enable");
    this._expireDays = Services.prefs.getIntPref("browser.formfill.expire_days");
  },
};

function log(aMessage) {
  if (Prefs.debug) {
    Services.console.logStringMessage("FormHistory: " + aMessage);
  }
}

function sendNotification(aType, aData) {
  if (typeof aData == "string") {
    let strWrapper = Cc["@mozilla.org/supports-string;1"]
                     .createInstance(Ci.nsISupportsString);
    strWrapper.data = aData;
    aData = strWrapper;
  } else if (typeof aData == "number") {
    let intWrapper = Cc["@mozilla.org/supports-PRInt64;1"]
                     .createInstance(Ci.nsISupportsPRInt64);
    intWrapper.data = aData;
    aData = intWrapper;
  } else if (aData) {
    throw Components.Exception("Invalid type " + (typeof aType) + " passed to sendNotification",
                               Cr.NS_ERROR_ILLEGAL_VALUE);
  }

  Services.obs.notifyObservers(aData, "satchel-storage-changed", aType);
}

/**
 * Current database schema
 */

const dbSchema = {
  tables: {
    moz_formhistory: {
      "id": "INTEGER PRIMARY KEY",
      "fieldname": "TEXT NOT NULL",
      "value": "TEXT NOT NULL",
      "timesUsed": "INTEGER",
      "firstUsed": "INTEGER",
      "lastUsed": "INTEGER",
      "guid": "TEXT",
    },
    moz_deleted_formhistory: {
      "id": "INTEGER PRIMARY KEY",
      "timeDeleted": "INTEGER",
      "guid": "TEXT",
    },
  },
  indices: {
    moz_formhistory_index: {
      table: "moz_formhistory",
      columns: ["fieldname"],
    },
    moz_formhistory_lastused_index: {
      table: "moz_formhistory",
      columns: ["lastUsed"],
    },
    moz_formhistory_guid_index: {
      table: "moz_formhistory",
      columns: ["guid"],
    },
  },
};

/**
 * Validating and processing API querying data
 */

const validFields = [
  "fieldname",
  "value",
  "timesUsed",
  "firstUsed",
  "lastUsed",
  "guid",
];

const searchFilters = [
  "firstUsedStart",
  "firstUsedEnd",
  "lastUsedStart",
  "lastUsedEnd",
];

function validateOpData(aData, aDataType) {
  let thisValidFields = validFields;
  // A special case to update the GUID - in this case there can be a 'newGuid'
  // field and of the normally valid fields, only 'guid' is accepted.
  if (aDataType == "Update" && "newGuid" in aData) {
    thisValidFields = ["guid", "newGuid"];
  }
  for (let field in aData) {
    if (field != "op" && !thisValidFields.includes(field)) {
      throw Components.Exception(
        aDataType + " query contains an unrecognized field: " + field,
        Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  }
  return aData;
}

function validateSearchData(aData, aDataType) {
  for (let field in aData) {
    if (field != "op" && !validFields.includes(field) && !searchFilters.includes(field)) {
      throw Components.Exception(
        aDataType + " query contains an unrecognized field: " + field,
        Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  }
}

function makeQueryPredicates(aQueryData, delimiter = " AND ") {
  return Object.keys(aQueryData).map(field => {
    switch (field) {
      case "firstUsedStart": {
        return "firstUsed >= :" + field;
      }
      case "firstUsedEnd": {
        return "firstUsed <= :" + field;
      }
      case "lastUsedStart": {
        return "lastUsed >= :" + field;
      }
      case "lastUsedEnd": {
        return "lastUsed <= :" + field;
      }
    }

    return field + " = :" + field;
  }).join(delimiter);
}

/**
 * Storage statement creation and parameter binding
 */

function makeCountStatement(aSearchData) {
  let query = "SELECT COUNT(*) AS numEntries FROM moz_formhistory";
  let queryTerms = makeQueryPredicates(aSearchData);
  if (queryTerms) {
    query += " WHERE " + queryTerms;
  }
  return dbCreateAsyncStatement(query, aSearchData);
}

function makeSearchStatement(aSearchData, aSelectTerms) {
  let query = "SELECT " + aSelectTerms.join(", ") + " FROM moz_formhistory";
  let queryTerms = makeQueryPredicates(aSearchData);
  if (queryTerms) {
    query += " WHERE " + queryTerms;
  }

  return dbCreateAsyncStatement(query, aSearchData);
}

function makeAddStatement(aNewData, aNow, aBindingArrays) {
  let query = "INSERT INTO moz_formhistory " +
              "(fieldname, value, timesUsed, firstUsed, lastUsed, guid) " +
              "VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)";

  aNewData.timesUsed = aNewData.timesUsed || 1;
  aNewData.firstUsed = aNewData.firstUsed || aNow;
  aNewData.lastUsed = aNewData.lastUsed || aNow;
  return dbCreateAsyncStatement(query, aNewData, aBindingArrays);
}

function makeBumpStatement(aGuid, aNow, aBindingArrays) {
  let query = "UPDATE moz_formhistory " +
              "SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE guid = :guid";
  let queryParams = {
    lastUsed: aNow,
    guid: aGuid,
  };

  return dbCreateAsyncStatement(query, queryParams, aBindingArrays);
}

function makeRemoveStatement(aSearchData, aBindingArrays) {
  let query = "DELETE FROM moz_formhistory";
  let queryTerms = makeQueryPredicates(aSearchData);

  if (queryTerms) {
    log("removeEntries");
    query += " WHERE " + queryTerms;
  } else {
    log("removeAllEntries");
    // Not specifying any fields means we should remove all entries. We
    // won't need to modify the query in this case.
  }

  return dbCreateAsyncStatement(query, aSearchData, aBindingArrays);
}

function makeUpdateStatement(aGuid, aNewData, aBindingArrays) {
  let query = "UPDATE moz_formhistory SET ";
  let queryTerms = makeQueryPredicates(aNewData, ", ");

  if (!queryTerms) {
    throw Components.Exception("Update query must define fields to modify.",
                               Cr.NS_ERROR_ILLEGAL_VALUE);
  }

  query += queryTerms + " WHERE guid = :existing_guid";
  aNewData.existing_guid = aGuid;

  return dbCreateAsyncStatement(query, aNewData, aBindingArrays);
}

function makeMoveToDeletedStatement(aGuid, aNow, aData, aBindingArrays) {
  if (supportsDeletedTable) {
    let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted)";
    let queryTerms = makeQueryPredicates(aData);

    if (aGuid) {
      query += " VALUES (:guid, :timeDeleted)";
    } else {
      // TODO: Add these items to the deleted items table once we've sorted
      //       out the issues from bug 756701
      if (!queryTerms) {
        return undefined;
      }

      query += " SELECT guid, :timeDeleted FROM moz_formhistory WHERE " + queryTerms;
    }

    aData.timeDeleted = aNow;

    return dbCreateAsyncStatement(query, aData, aBindingArrays);
  }

  return null;
}

function generateGUID() {
  // string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
  let uuid = uuidService.generateUUID().toString();
  let raw = ""; // A string with the low bytes set to random values
  let bytes = 0;
  for (let i = 1; bytes < 12; i += 2) {
    // Skip dashes
    if (uuid[i] == "-") {
      i++;
    }
    let hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
    raw += String.fromCharCode(hexVal);
    bytes++;
  }
  return btoa(raw);
}

/**
 * Database creation and access
 */

var _dbConnection = null;
XPCOMUtils.defineLazyGetter(this, "dbConnection", function() {
  let dbFile;

  try {
    dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
    dbFile.append("formhistory.sqlite");
    log("Opening database at " + dbFile.path);

    _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
    dbInit();
  } catch (e) {
    if (e.result != Cr.NS_ERROR_FILE_CORRUPTED) {
      throw e;
    }
    dbCleanup(dbFile);
    _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
    dbInit();
  }

  return _dbConnection;
});


var dbStmts = new Map();

/*
 * dbCreateAsyncStatement
 *
 * Creates a statement, wraps it, and then does parameter replacement
 */
function dbCreateAsyncStatement(aQuery, aParams, aBindingArrays) {
  if (!aQuery) {
    return null;
  }

  let stmt = dbStmts.get(aQuery);
  if (!stmt) {
    log("Creating new statement for query: " + aQuery);
    stmt = dbConnection.createAsyncStatement(aQuery);
    dbStmts.set(aQuery, stmt);
  }

  if (aBindingArrays) {
    let bindingArray = aBindingArrays.get(stmt);
    if (!bindingArray) {
      // first time using a particular statement in update
      bindingArray = stmt.newBindingParamsArray();
      aBindingArrays.set(stmt, bindingArray);
    }

    if (aParams) {
      let bindingParams = bindingArray.newBindingParams();
      for (let field in aParams) {
        bindingParams.bindByName(field, aParams[field]);
      }
      bindingArray.addParams(bindingParams);
    }
  } else if (aParams) {
    for (let field in aParams) {
      stmt.params[field] = aParams[field];
    }
  }

  return stmt;
}

/**
 * Attempts to initialize the database. This creates the file if it doesn't
 * exist, performs any migrations, etc.
 */
function dbInit() {
  log("Initializing Database");

  if (!_dbConnection.tableExists("moz_formhistory")) {
    dbCreate();
    return;
  }

  // When FormHistory is released, we will no longer support the various schema versions prior to
  // this release that nsIFormHistory2 once did.
  let version = _dbConnection.schemaVersion;
  if (version < 3) {
    throw Components.Exception("DB version is unsupported.",
                               Cr.NS_ERROR_FILE_CORRUPTED);
  } else if (version != DB_SCHEMA_VERSION) {
    dbMigrate(version);
  }
}

var Migrators = {
  /*
   * Updates the DB schema to v3 (bug 506402).
   * Adds deleted form history table.
   */
  dbMigrateToVersion4() {
    if (!_dbConnection.tableExists("moz_deleted_formhistory")) {
      let table = dbSchema.tables.moz_deleted_formhistory;
      let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
      _dbConnection.createTable("moz_deleted_formhistory", tSQL);
    }
  },
};

function dbCreate() {
  log("Creating DB -- tables");
  for (let name in dbSchema.tables) {
    let table = dbSchema.tables[name];
    let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
    log("Creating table " + name + " with " + tSQL);
    _dbConnection.createTable(name, tSQL);
  }

  log("Creating DB -- indices");
  for (let name in dbSchema.indices) {
    let index = dbSchema.indices[name];
    let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
                    "(" + index.columns.join(", ") + ")";
    _dbConnection.executeSimpleSQL(statement);
  }

  _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
}

function dbMigrate(oldVersion) {
  log("Attempting to migrate from version " + oldVersion);

  if (oldVersion > DB_SCHEMA_VERSION) {
    log("Downgrading to version " + DB_SCHEMA_VERSION);
    // User's DB is newer. Sanity check that our expected columns are
    // present, and if so mark the lower version and merrily continue
    // on. If the columns are borked, something is wrong so blow away
    // the DB and start from scratch. [Future incompatible upgrades
    // should switch to a different table or file.]

    if (!dbAreExpectedColumnsPresent()) {
      throw Components.Exception("DB is missing expected columns",
                                 Cr.NS_ERROR_FILE_CORRUPTED);
    }

    // Change the stored version to the current version. If the user
    // runs the newer code again, it will see the lower version number
    // and re-upgrade (to fixup any entries the old code added).
    _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
    return;
  }

  // Note that migration is currently performed synchronously.
  _dbConnection.beginTransaction();

  try {
    for (let v = oldVersion + 1; v <= DB_SCHEMA_VERSION; v++) {
      this.log("Upgrading to version " + v + "...");
      Migrators["dbMigrateToVersion" + v]();
    }
  } catch (e) {
    this.log("Migration failed: " + e);
    this.dbConnection.rollbackTransaction();
    throw e;
  }

  _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
  _dbConnection.commitTransaction();

  log("DB migration completed.");
}

/**
 * Sanity check to ensure that the columns this version of the code expects
 * are present in the DB we're using.
 * @returns {boolean} whether expected columns are present
 */
function dbAreExpectedColumnsPresent() {
  for (let name in dbSchema.tables) {
    let table = dbSchema.tables[name];
    let query = "SELECT " +
                Object.keys(table).join(", ") +
                " FROM " + name;
    try {
      let stmt = _dbConnection.createStatement(query);
      // (no need to execute statement, if it compiled we're good)
      stmt.finalize();
    } catch (e) {
      return false;
    }
  }

  log("verified that expected columns are present in DB.");
  return true;
}

/**
 * Called when database creation fails. Finalizes database statements,
 * closes the database connection, deletes the database file.
 * @param {Object} dbFile database file to close
 */
function dbCleanup(dbFile) {
  log("Cleaning up DB file - close & remove & backup");

  // Create backup file
  let backupFile = dbFile.leafName + ".corrupt";
  Services.storage.backupDatabaseFile(dbFile, backupFile);

  dbClose(false);
  dbFile.remove(false);
}

function dbClose(aShutdown) {
  log("dbClose(" + aShutdown + ")");

  if (aShutdown) {
    sendNotification("formhistory-shutdown", null);
  }

  // Connection may never have been created if say open failed but we still
  // end up calling dbClose as part of the rest of dbCleanup.
  if (!_dbConnection) {
    return;
  }

  log("dbClose finalize statements");
  for (let stmt of dbStmts.values()) {
    stmt.finalize();
  }

  dbStmts = new Map();

  let closed = false;
  _dbConnection.asyncClose(() => closed = true);

  if (!aShutdown) {
    Services.tm.spinEventLoopUntil(() => closed);
  }
}

/**
 * Constructs and executes database statements from a pre-processed list of
 * inputted changes.
 *
 * @param {Array.<Object>} aChanges changes to form history
 * @param {Object} aCallbacks
 */
function updateFormHistoryWrite(aChanges, aCallbacks) {
  log("updateFormHistoryWrite  " + aChanges.length);

  // pass 'now' down so that every entry in the batch has the same timestamp
  let now = Date.now() * 1000;

  // for each change, we either create and append a new storage statement to
  // stmts or bind a new set of parameters to an existing storage statement.
  // stmts and bindingArrays are updated when makeXXXStatement eventually
  // calls dbCreateAsyncStatement.
  let stmts = [];
  let notifications = [];
  let bindingArrays = new Map();

  for (let change of aChanges) {
    let operation = change.op;
    delete change.op;
    let stmt;
    switch (operation) {
      case "remove":
        log("Remove from form history  " + change);
        let delStmt = makeMoveToDeletedStatement(change.guid, now, change, bindingArrays);
        if (delStmt && !stmts.includes(delStmt)) {
          stmts.push(delStmt);
        }
        if ("timeDeleted" in change) {
          delete change.timeDeleted;
        }
        stmt = makeRemoveStatement(change, bindingArrays);
        notifications.push(["formhistory-remove", change.guid]);
        break;
      case "update":
        log("Update form history " + change);
        let guid = change.guid;
        delete change.guid;
        // a special case for updating the GUID - the new value can be
        // specified in newGuid.
        if (change.newGuid) {
          change.guid = change.newGuid;
          delete change.newGuid;
        }
        stmt = makeUpdateStatement(guid, change, bindingArrays);
        notifications.push(["formhistory-update", guid]);
        break;
      case "bump":
        log("Bump form history " + change);
        if (change.guid) {
          stmt = makeBumpStatement(change.guid, now, bindingArrays);
          notifications.push(["formhistory-update", change.guid]);
        } else {
          change.guid = generateGUID();
          stmt = makeAddStatement(change, now, bindingArrays);
          notifications.push(["formhistory-add", change.guid]);
        }
        break;
      case "add":
        log("Add to form history " + change);
        change.guid = generateGUID();
        stmt = makeAddStatement(change, now, bindingArrays);
        notifications.push(["formhistory-add", change.guid]);
        break;
      default:
        // We should've already guaranteed that change.op is one of the above
        throw Components.Exception("Invalid operation " + operation,
                                   Cr.NS_ERROR_ILLEGAL_VALUE);
    }

    // As identical statements are reused, only add statements if they aren't already present.
    if (stmt && !stmts.includes(stmt)) {
      stmts.push(stmt);
    }
  }

  for (let stmt of stmts) {
    stmt.bindParameters(bindingArrays.get(stmt));
  }

  let handlers = {
    handleCompletion(aReason) {
      if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
        for (let [notification, param] of notifications) {
          // We're either sending a GUID or nothing at all.
          sendNotification(notification, param);
        }
      }

      if (aCallbacks && aCallbacks.handleCompletion) {
        aCallbacks.handleCompletion(
          aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ?
            0 :
            1
        );
      }
    },
    handleError(aError) {
      if (aCallbacks && aCallbacks.handleError) {
        aCallbacks.handleError(aError);
      }
    },
    handleResult: NOOP,
  };

  dbConnection.executeAsync(stmts, stmts.length, handlers);
}

/**
 * Functions that expire entries in form history and shrinks database
 * afterwards as necessary initiated by expireOldEntries.
 */

/**
 * Removes entries from database.
 *
 * @param {number} aExpireTime expiration timestamp
 * @param {number} aBeginningCount numer of entries at first
 */
function expireOldEntriesDeletion(aExpireTime, aBeginningCount) {
  log("expireOldEntriesDeletion(" + aExpireTime + "," + aBeginningCount + ")");

  FormHistory.update([{
    op: "remove",
    lastUsedEnd: aExpireTime,
  }], {
    handleCompletion() {
      expireOldEntriesVacuum(aExpireTime, aBeginningCount);
    },
    handleError(aError) {
      log("expireOldEntriesDeletionFailure");
    },
  });
}

/**
 * Counts number of entries removed and shrinks database as necessary.
 *
 * @param {number} aExpireTime expiration timestamp
 * @param {number} aBeginningCount number of entries at first
 */
function expireOldEntriesVacuum(aExpireTime, aBeginningCount) {
  FormHistory.count({}, {
    handleResult(aEndingCount) {
      if (aBeginningCount - aEndingCount > 500) {
        log("expireOldEntriesVacuum");

        let stmt = dbCreateAsyncStatement("VACUUM");
        stmt.executeAsync({
          handleResult: NOOP,
          handleError(aError) {
            log("expireVacuumError");
          },
          handleCompletion: NOOP,
        });
      }

      sendNotification("formhistory-expireoldentries", aExpireTime);
    },
    handleError(aError) {
      log("expireEndCountFailure");
    },
  });
}

this.FormHistory = {
  get enabled() {
    return Prefs.enabled;
  },

  search(aSelectTerms, aSearchData, aCallbacks) {
    // if no terms selected, select everything
    if (!aSelectTerms) {
      aSelectTerms = validFields;
    }
    validateSearchData(aSearchData, "Search");

    let stmt = makeSearchStatement(aSearchData, aSelectTerms);

    let handlers = {
      handleResult(aResultSet) {
        for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
          let result = {};
          for (let field of aSelectTerms) {
            result[field] = row.getResultByName(field);
          }

          if (aCallbacks && aCallbacks.handleResult) {
            aCallbacks.handleResult(result);
          }
        }
      },

      handleError(aError) {
        if (aCallbacks && aCallbacks.handleError) {
          aCallbacks.handleError(aError);
        }
      },

      handleCompletion(aReason) {
        if (aCallbacks && aCallbacks.handleCompletion) {
          aCallbacks.handleCompletion(
            aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ?
              0 :
              1
          );
        }
      },
    };

    stmt.executeAsync(handlers);
  },

  count(aSearchData, aCallbacks) {
    validateSearchData(aSearchData, "Count");
    let stmt = makeCountStatement(aSearchData);
    let handlers = {
      handleResult(aResultSet) {
        let row = aResultSet.getNextRow();
        let count = row.getResultByName("numEntries");
        if (aCallbacks && aCallbacks.handleResult) {
          aCallbacks.handleResult(count);
        }
      },

      handleError(aError) {
        if (aCallbacks && aCallbacks.handleError) {
          aCallbacks.handleError(aError);
        }
      },

      handleCompletion(aReason) {
        if (aCallbacks && aCallbacks.handleCompletion) {
          aCallbacks.handleCompletion(
            aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ?
              0 :
              1
          );
        }
      },
    };

    stmt.executeAsync(handlers);
  },

  update(aChanges, aCallbacks) {
    // Used to keep track of how many searches have been started. When that number
    // are finished, updateFormHistoryWrite can be called.
    let numSearches = 0;
    let completedSearches = 0;
    let searchFailed = false;

    function validIdentifier(change) {
      // The identifier is only valid if one of either the guid
      // or the (fieldname/value) are set (so an X-OR)
      return Boolean(change.guid) != Boolean(change.fieldname && change.value);
    }

    if (!("length" in aChanges)) {
      aChanges = [aChanges];
    }

    let isRemoveOperation = aChanges.every(change => change && change.op && change.op == "remove");
    if (!Prefs.enabled && !isRemoveOperation) {
      if (aCallbacks && aCallbacks.handleError) {
        aCallbacks.handleError({
          message: "Form history is disabled, only remove operations are allowed",
          result: Ci.mozIStorageError.MISUSE,
        });
      }
      if (aCallbacks && aCallbacks.handleCompletion) {
        aCallbacks.handleCompletion(1);
      }
      return;
    }

    for (let change of aChanges) {
      switch (change.op) {
        case "remove":
          validateSearchData(change, "Remove");
          continue;
        case "update":
          if (validIdentifier(change)) {
            validateOpData(change, "Update");
            if (change.guid) {
              continue;
            }
          } else {
            throw Components.Exception(
              "update op='update' does not correctly reference a entry.",
              Cr.NS_ERROR_ILLEGAL_VALUE);
          }
          break;
        case "bump":
          if (validIdentifier(change)) {
            validateOpData(change, "Bump");
            if (change.guid) {
              continue;
            }
          } else {
            throw Components.Exception(
              "update op='bump' does not correctly reference a entry.",
              Cr.NS_ERROR_ILLEGAL_VALUE);
          }
          break;
        case "add":
          if (change.guid) {
            throw Components.Exception(
              "op='add' cannot contain field 'guid'. Either use op='update' " +
                "explicitly or make 'guid' undefined.",
              Cr.NS_ERROR_ILLEGAL_VALUE);
          } else if (change.fieldname && change.value) {
            validateOpData(change, "Add");
          }
          break;
        default:
          throw Components.Exception(
            "update does not recognize op='" + change.op + "'",
            Cr.NS_ERROR_ILLEGAL_VALUE);
      }

      numSearches++;
      let changeToUpdate = change;
      FormHistory.search(
        ["guid"],
        {
          fieldname: change.fieldname,
          value: change.value,
        }, {
          foundResult: false,
          handleResult(aResult) {
            if (this.foundResult) {
              log("Database contains multiple entries with the same fieldname/value pair.");
              if (aCallbacks && aCallbacks.handleError) {
                aCallbacks.handleError({
                  message:
                    "Database contains multiple entries with the same fieldname/value pair.",
                  result: 19, // Constraint violation
                });
              }

              searchFailed = true;
              return;
            }

            this.foundResult = true;
            changeToUpdate.guid = aResult.guid;
          },

          handleError(aError) {
            if (aCallbacks && aCallbacks.handleError) {
              aCallbacks.handleError(aError);
            }
          },

          handleCompletion(aReason) {
            completedSearches++;
            if (completedSearches == numSearches) {
              if (!aReason && !searchFailed) {
                updateFormHistoryWrite(aChanges, aCallbacks);
              } else if (aCallbacks && aCallbacks.handleCompletion) {
                aCallbacks.handleCompletion(1);
              }
            }
          },
        });
    }

    if (numSearches == 0) {
      // We don't have to wait for any statements to return.
      updateFormHistoryWrite(aChanges, aCallbacks);
    }
  },

  getAutoCompleteResults(searchString, params, aCallbacks) {
    // only do substring matching when the search string contains more than one character
    let searchTokens;
    let where = "";
    let boundaryCalc = "";
    if (searchString.length > 1) {
      searchTokens = searchString.split(/\s+/);

      // build up the word boundary and prefix match bonus calculation
      boundaryCalc = "MAX(1, :prefixWeight * (value LIKE :valuePrefix ESCAPE '/') + (";
      // for each word, calculate word boundary weights for the SELECT clause and
      // add word to the WHERE clause of the query
      let tokenCalc = [];
      let searchTokenCount = Math.min(searchTokens.length, MAX_SEARCH_TOKENS);
      for (let i = 0; i < searchTokenCount; i++) {
        tokenCalc.push("(value LIKE :tokenBegin" + i + " ESCAPE '/') + " +
                            "(value LIKE :tokenBoundary" + i + " ESCAPE '/')");
        where += "AND (value LIKE :tokenContains" + i + " ESCAPE '/') ";
      }
      // add more weight if we have a traditional prefix match and
      // multiply boundary bonuses by boundary weight
      boundaryCalc += tokenCalc.join(" + ") + ") * :boundaryWeight)";
    } else if (searchString.length == 1) {
      where = "AND (value LIKE :valuePrefix ESCAPE '/') ";
      boundaryCalc = "1";
      delete params.prefixWeight;
      delete params.boundaryWeight;
    } else {
      where = "";
      boundaryCalc = "1";
      delete params.prefixWeight;
      delete params.boundaryWeight;
    }

    params.now = Date.now() * 1000; // convert from ms to microseconds

    /* Three factors in the frecency calculation for an entry (in order of use in calculation):
     * 1) average number of times used - items used more are ranked higher
     * 2) how recently it was last used - items used recently are ranked higher
     * 3) additional weight for aged entries surviving expiry - these entries are relevant
     *    since they have been used multiple times over a large time span so rank them higher
     * The score is then divided by the bucket size and we round the result so that entries
     * with a very similar frecency are bucketed together with an alphabetical sort. This is
     * to reduce the amount of moving around by entries while typing.
     */

    let query = "/* do not warn (bug 496471): can't use an index */ " +
                "SELECT value, " +
                "ROUND( " +
                    "timesUsed / MAX(1.0, (lastUsed - firstUsed) / :timeGroupingSize) * " +
                    "MAX(1.0, :maxTimeGroupings - (:now - lastUsed) / :timeGroupingSize) * " +
                    "MAX(1.0, :agedWeight * (firstUsed < :expiryDate)) / " +
                    ":bucketSize " +
                ", 3) AS frecency, " +
                boundaryCalc + " AS boundaryBonuses " +
                "FROM moz_formhistory " +
                "WHERE fieldname=:fieldname " + where +
                "ORDER BY ROUND(frecency * boundaryBonuses) DESC, UPPER(value) ASC";

    let stmt = dbCreateAsyncStatement(query, params);

    // Chicken and egg problem: Need the statement to escape the params we
    // pass to the function that gives us the statement. So, fix it up now.
    if (searchString.length >= 1) {
      stmt.params.valuePrefix = stmt.escapeStringForLIKE(searchString, "/") + "%";
    }
    if (searchString.length > 1) {
      let searchTokenCount = Math.min(searchTokens.length, MAX_SEARCH_TOKENS);
      for (let i = 0; i < searchTokenCount; i++) {
        let escapedToken = stmt.escapeStringForLIKE(searchTokens[i], "/");
        stmt.params["tokenBegin" + i] = escapedToken + "%";
        stmt.params["tokenBoundary" + i] =  "% " + escapedToken + "%";
        stmt.params["tokenContains" + i] = "%" + escapedToken + "%";
      }
    } else {
      // no additional params need to be substituted into the query when the
      // length is zero or one
    }

    let pending = stmt.executeAsync({
      handleResult(aResultSet) {
        for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
          let value = row.getResultByName("value");
          let frecency = row.getResultByName("frecency");
          let entry = {
            text:          value,
            textLowerCase: value.toLowerCase(),
            frecency,
            totalScore:    Math.round(frecency * row.getResultByName("boundaryBonuses")),
          };
          if (aCallbacks && aCallbacks.handleResult) {
            aCallbacks.handleResult(entry);
          }
        }
      },

      handleError(aError) {
        if (aCallbacks && aCallbacks.handleError) {
          aCallbacks.handleError(aError);
        }
      },

      handleCompletion(aReason) {
        if (aCallbacks && aCallbacks.handleCompletion) {
          aCallbacks.handleCompletion(
            aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ?
              0 :
              1
          );
        }
      },
    });
    return pending;
  },

  get schemaVersion() {
    return dbConnection.schemaVersion;
  },

  // This is used only so that the test can verify deleted table support.
  get _supportsDeletedTable() {
    return supportsDeletedTable;
  },
  set _supportsDeletedTable(val) {
    supportsDeletedTable = val;
  },

  // The remaining methods are called by FormHistoryStartup.js
  updatePrefs() {
    Prefs.initialized = false;
  },

  expireOldEntries() {
    log("expireOldEntries");

    // Determine how many days of history we're supposed to keep.
    // Calculate expireTime in microseconds
    let expireTime = (Date.now() - Prefs.expireDays * DAY_IN_MS) * 1000;

    sendNotification("formhistory-beforeexpireoldentries", expireTime);

    FormHistory.count({}, {
      handleResult(aBeginningCount) {
        expireOldEntriesDeletion(expireTime, aBeginningCount);
      },
      handleError(aError) {
        log("expireStartCountFailure");
      },
    });
  },

  shutdown() { dbClose(true); },
};

// Prevent add-ons from redefining this API
Object.freeze(FormHistory);
PK
!<B!!modules/FormLikeFactory.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["FormLikeFactory"];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

/**
 * A factory to generate FormLike objects that represent a set of related fields
 * which aren't necessarily marked up with a <form> element. FormLike's emulate
 * the properties of an HTMLFormElement which are relevant to form tasks.
 */
let FormLikeFactory = {
  _propsFromForm: [
    "action",
    "autocomplete",
    "ownerDocument",
  ],

  /**
   * Create a FormLike object from a <form>.
   *
   * @param {HTMLFormElement} aForm
   * @return {FormLike}
   * @throws Error if aForm isn't an HTMLFormElement
   */
  createFromForm(aForm) {
    if (!(aForm instanceof Ci.nsIDOMHTMLFormElement)) {
      throw new Error("createFromForm: aForm must be a nsIDOMHTMLFormElement");
    }

    let formLike = {
      elements: [...aForm.elements],
      rootElement: aForm,
    };

    for (let prop of this._propsFromForm) {
      formLike[prop] = aForm[prop];
    }

    this._addToJSONProperty(formLike);

    return formLike;
  },

  /**
   * Create a FormLike object from an <input>/<select> in a document.
   *
   * If the field is in a <form>, construct the FormLike from the form.
   * Otherwise, create a FormLike with a rootElement (wrapper) according to
   * heuristics. Currently all <input>/<select> not in a <form> are one FormLike
   * but this shouldn't be relied upon as the heuristics may change to detect
   * multiple "forms" (e.g. registration and login) on one page with a <form>.
   *
   * Note that two FormLikes created from the same field won't return the same FormLike object.
   * Use the `rootElement` property on the FormLike as a key instead.
   *
   * @param {HTMLInputElement|HTMLSelectElement} aField - an <input> or <select> field in a document
   * @return {FormLike}
   * @throws Error if aField isn't a password or username field in a document
   */
  createFromField(aField) {
    if ((!(aField instanceof Ci.nsIDOMHTMLInputElement) && !(aField instanceof Ci.nsIDOMHTMLSelectElement)) ||
        !aField.ownerDocument) {
      throw new Error("createFromField requires a field in a document");
    }

    let rootElement = this.findRootForField(aField);
    if (rootElement instanceof Ci.nsIDOMHTMLFormElement) {
      return this.createFromForm(rootElement);
    }

    let doc = aField.ownerDocument;
    let elements = [];
    for (let el of rootElement.querySelectorAll("input, select")) {
      // Exclude elements inside the rootElement that are already in a <form> as
      // they will be handled by their own FormLike.
      if (!el.form) {
        elements.push(el);
      }
    }
    let formLike = {
      action: doc.baseURI,
      autocomplete: "on",
      elements,
      ownerDocument: doc,
      rootElement,
    };

    this._addToJSONProperty(formLike);
    return formLike;
  },

  /**
   * Determine the Element that encapsulates the related fields. For example, if
   * a page contains a login form and a checkout form which are "submitted"
   * separately, and the username field is passed in, ideally this would return
   * an ancestor Element of the username and password fields which doesn't
   * include any of the checkout fields.
   *
   * @param {HTMLInputElement|HTMLSelectElement} aField - a field in a document
   * @return {HTMLElement} - the root element surrounding related fields
   */
  findRootForField(aField) {
    if (aField.form) {
      return aField.form;
    }

    return aField.ownerDocument.documentElement;
  },

  /**
   * Add a `toJSON` property to a FormLike so logging which ends up going
   * through dump doesn't include usless garbage from DOM objects.
   */
  _addToJSONProperty(aFormLike) {
    function prettyElementOutput(aElement) {
      let idText = aElement.id ? "#" + aElement.id : "";
      let classText = "";
      for (let className of aElement.classList) {
        classText += "." + className;
      }
      return `<${aElement.nodeName + idText + classText}>`;
    }

    Object.defineProperty(aFormLike, "toJSON", {
      value: () => {
        let cleansed = {};
        for (let key of Object.keys(aFormLike)) {
          let value = aFormLike[key];
          let cleansedValue = value;

          switch (key) {
            case "elements": {
              cleansedValue = [];
              for (let element of value) {
                cleansedValue.push(prettyElementOutput(element));
              }
              break;
            }

            case "ownerDocument": {
              cleansedValue = {
                location: {
                  href: value.location.href,
                },
              };
              break;
            }

            case "rootElement": {
              cleansedValue = prettyElementOutput(value);
              break;
            }
          }

          cleansed[key] = cleansedValue;
        }
        return cleansed;
      }
    });
  },
};
PK
!<\modules/FxAccounts.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["fxAccounts", "FxAccounts"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.importGlobalProperties(["URL"]);

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");

XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
  "resource://gre/modules/FxAccountsClient.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsConfig",
  "resource://gre/modules/FxAccountsConfig.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
  "resource://services-crypto/jwcrypto.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthGrantClient",
  "resource://gre/modules/FxAccountsOAuthGrantClient.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfile",
  "resource://gre/modules/FxAccountsProfile.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Utils",
  "resource://services-sync/util.js");

// All properties exposed by the public FxAccounts API.
var publicProperties = [
  "accountStatus",
  "checkVerificationStatus",
  "getAccountsClient",
  "getAssertion",
  "getDeviceId",
  "getDeviceList",
  "getKeys",
  "getOAuthToken",
  "getProfileCache",
  "getSignedInUser",
  "getSignedInUserProfile",
  "handleDeviceDisconnection",
  "handleAccountDestroyed",
  "invalidateCertificate",
  "loadAndPoll",
  "localtimeOffsetMsec",
  "notifyDevices",
  "now",
  "promiseAccountsChangeProfileURI",
  "promiseAccountsForceSigninURI",
  "promiseAccountsManageURI",
  "promiseAccountsManageDevicesURI",
  "promiseAccountsSignUpURI",
  "promiseAccountsSignInURI",
  "removeCachedOAuthToken",
  "requiresHttps",
  "resendVerificationEmail",
  "resetCredentials",
  "sessionStatus",
  "setProfileCache",
  "setSignedInUser",
  "signOut",
  "updateDeviceRegistration",
  "deleteDeviceRegistration",
  "updateUserAccountData",
  "whenVerified",
];

// An AccountState object holds all state related to one specific account.
// Only one AccountState is ever "current" in the FxAccountsInternal object -
// whenever a user logs out or logs in, the current AccountState is discarded,
// making it impossible for the wrong state or state data to be accidentally
// used.
// In addition, it has some promise-related helpers to ensure that if an
// attempt is made to resolve a promise on a "stale" state (eg, if an
// operation starts, but a different user logs in before the operation
// completes), the promise will be rejected.
// It is intended to be used thusly:
// somePromiseBasedFunction: function() {
//   let currentState = this.currentAccountState;
//   return someOtherPromiseFunction().then(
//     data => currentState.resolve(data)
//   );
// }
// If the state has changed between the function being called and the promise
// being resolved, the .resolve() call will actually be rejected.
var AccountState = this.AccountState = function(storageManager) {
  this.storageManager = storageManager;
  this.promiseInitialized = this.storageManager.getAccountData().then(data => {
    this.oauthTokens = data && data.oauthTokens ? data.oauthTokens : {};
  }).catch(err => {
    log.error("Failed to initialize the storage manager", err);
    // Things are going to fall apart, but not much we can do about it here.
  });
};

AccountState.prototype = {
  oauthTokens: null,
  whenVerifiedDeferred: null,
  whenKeysReadyDeferred: null,

  // If the storage manager has been nuked then we are no longer current.
  get isCurrent() {
    return this.storageManager != null;
  },

  abort() {
    if (this.whenVerifiedDeferred) {
      this.whenVerifiedDeferred.reject(
        new Error("Verification aborted; Another user signing in"));
      this.whenVerifiedDeferred = null;
    }

    if (this.whenKeysReadyDeferred) {
      this.whenKeysReadyDeferred.reject(
        new Error("Verification aborted; Another user signing in"));
      this.whenKeysReadyDeferred = null;
    }

    this.cert = null;
    this.keyPair = null;
    this.oauthTokens = null;
    // Avoid finalizing the storageManager multiple times (ie, .signOut()
    // followed by .abort())
    if (!this.storageManager) {
      return Promise.resolve();
    }
    let storageManager = this.storageManager;
    this.storageManager = null;
    return storageManager.finalize();
  },

  // Clobber all cached data and write that empty data to storage.
  signOut() {
    this.cert = null;
    this.keyPair = null;
    this.oauthTokens = null;
    let storageManager = this.storageManager;
    this.storageManager = null;
    return storageManager.deleteAccountData().then(() => {
      return storageManager.finalize();
    });
  },

  // Get user account data. Optionally specify explicit field names to fetch
  // (and note that if you require an in-memory field you *must* specify the
  // field name(s).)
  getUserAccountData(fieldNames = null) {
    if (!this.isCurrent) {
      return Promise.reject(new Error("Another user has signed in"));
    }
    return this.storageManager.getAccountData(fieldNames).then(result => {
      return this.resolve(result);
    });
  },

  updateUserAccountData(updatedFields) {
    if (!this.isCurrent) {
      return Promise.reject(new Error("Another user has signed in"));
    }
    return this.storageManager.updateAccountData(updatedFields);
  },

  resolve(result) {
    if (!this.isCurrent) {
      log.info("An accountState promise was resolved, but was actually rejected" +
               " due to a different user being signed in. Originally resolved" +
               " with", result);
      return Promise.reject(new Error("A different user signed in"));
    }
    return Promise.resolve(result);
  },

  reject(error) {
    // It could be argued that we should just let it reject with the original
    // error - but this runs the risk of the error being (eg) a 401, which
    // might cause the consumer to attempt some remediation and cause other
    // problems.
    if (!this.isCurrent) {
      log.info("An accountState promise was rejected, but we are ignoring that" +
               "reason and rejecting it due to a different user being signed in." +
               "Originally rejected with", error);
      return Promise.reject(new Error("A different user signed in"));
    }
    return Promise.reject(error);
  },

  // Abstractions for storage of cached tokens - these are all sync, and don't
  // handle revocation etc - it's just storage (and the storage itself is async,
  // but we don't return the storage promises, so it *looks* sync)
  // These functions are sync simply so we can handle "token races" - when there
  // are multiple in-flight requests for the same scope, we can detect this
  // and revoke the redundant token.

  // A preamble for the cache helpers...
  _cachePreamble() {
    if (!this.isCurrent) {
      throw new Error("Another user has signed in");
    }
  },

  // Set a cached token. |tokenData| must have a 'token' element, but may also
  // have additional fields (eg, it probably specifies the server to revoke
  // from). The 'get' functions below return the entire |tokenData| value.
  setCachedToken(scopeArray, tokenData) {
    this._cachePreamble();
    if (!tokenData.token) {
      throw new Error("No token");
    }
    let key = getScopeKey(scopeArray);
    this.oauthTokens[key] = tokenData;
    // And a background save...
    this._persistCachedTokens();
  },

  // Return data for a cached token or null (or throws on bad state etc)
  getCachedToken(scopeArray) {
    this._cachePreamble();
    let key = getScopeKey(scopeArray);
    let result = this.oauthTokens[key];
    if (result) {
      // later we might want to check an expiry date - but we currently
      // have no such concept, so just return it.
      log.trace("getCachedToken returning cached token");
      return result;
    }
    return null;
  },

  // Remove a cached token from the cache.  Does *not* revoke it from anywhere.
  // Returns the entire token entry if found, null otherwise.
  removeCachedToken(token) {
    this._cachePreamble();
    let data = this.oauthTokens;
    for (let [key, tokenValue] of Object.entries(data)) {
      if (tokenValue.token == token) {
        delete data[key];
        // And a background save...
        this._persistCachedTokens();
        return tokenValue;
      }
    }
    return null;
  },

  // A hook-point for tests.  Returns a promise that's ignored in most cases
  // (notable exceptions are tests and when we explicitly are saving the entire
  // set of user data.)
  _persistCachedTokens() {
    this._cachePreamble();
    return this.updateUserAccountData({ oauthTokens: this.oauthTokens }).catch(err => {
      log.error("Failed to update cached tokens", err);
    });
  },
}

/* Given an array of scopes, make a string key by normalizing. */
function getScopeKey(scopeArray) {
  let normalizedScopes = scopeArray.map(item => item.toLowerCase());
  return normalizedScopes.sort().join("|");
}

/**
 * Copies properties from a given object to another object.
 *
 * @param from (object)
 *        The object we read property descriptors from.
 * @param to (object)
 *        The object that we set property descriptors on.
 * @param options (object) (optional)
 *        {keys: [...]}
 *          Lets the caller pass the names of all properties they want to be
 *          copied. Will copy all properties of the given source object by
 *          default.
 *        {bind: object}
 *          Lets the caller specify the object that will be used to .bind()
 *          all function properties we find to. Will bind to the given target
 *          object by default.
 */
function copyObjectProperties(from, to, opts = {}) {
  let keys = (opts && opts.keys) || Object.keys(from);
  let thisArg = (opts && opts.bind) || to;

  for (let prop of keys) {
    let desc = Object.getOwnPropertyDescriptor(from, prop);

    if (typeof(desc.value) == "function") {
      desc.value = desc.value.bind(thisArg);
    }

    if (desc.get) {
      desc.get = desc.get.bind(thisArg);
    }

    if (desc.set) {
      desc.set = desc.set.bind(thisArg);
    }

    Object.defineProperty(to, prop, desc);
  }
}

function urlsafeBase64Encode(key) {
  return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
}

/**
 * The public API's constructor.
 */
this.FxAccounts = function(mockInternal) {
  let internal = new FxAccountsInternal();
  let external = {};

  // Copy all public properties to the 'external' object.
  let prototype = FxAccountsInternal.prototype;
  let options = {keys: publicProperties, bind: internal};
  copyObjectProperties(prototype, external, options);

  // Copy all of the mock's properties to the internal object.
  if (mockInternal && !mockInternal.onlySetInternal) {
    copyObjectProperties(mockInternal, internal);
  }

  if (mockInternal) {
    // Exposes the internal object for testing only.
    external.internal = internal;
  }

  if (!internal.fxaPushService) {
    // internal.fxaPushService option is used in testing.
    // Otherwise we load the service lazily.
    XPCOMUtils.defineLazyGetter(internal, "fxaPushService", function() {
      return Components.classes["@mozilla.org/fxaccounts/push;1"]
        .getService(Components.interfaces.nsISupports)
        .wrappedJSObject;
    });
  }

  // wait until after the mocks are setup before initializing.
  internal.initialize();

  return Object.freeze(external);
}

/**
 * The internal API's constructor.
 */
function FxAccountsInternal() {
  // Make a local copy of this constant so we can mock it in testing
  this.POLL_SESSION = POLL_SESSION;

  // All significant initialization should be done in the initialize() method
  // below as it helps with testing.
}

/**
 * The internal API's prototype.
 */
FxAccountsInternal.prototype = {
  // The timeout (in ms) we use to poll for a verified mail for the first
  // VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD minutes if the user has
  // logged-in in this session.
  VERIFICATION_POLL_TIMEOUT_INITIAL: 60000, // 1 minute.
  // All the other cases (> 5 min, on restart etc).
  VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 5 * 60000, // 5 minutes.
  // After X minutes, the polling will slow down to _SUBSEQUENT if we have
  // logged-in in this session.
  VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD: 5,
  // The current version of the device registration, we use this to re-register
  // devices after we update what we send on device registration.
  DEVICE_REGISTRATION_VERSION: 2,

  _fxAccountsClient: null,

  // All significant initialization should be done in this initialize() method,
  // as it's called after this object has been mocked for tests.
  initialize() {
    this.currentTimer = null;
    this.currentAccountState = this.newAccountState();
  },

  get fxAccountsClient() {
    if (!this._fxAccountsClient) {
      this._fxAccountsClient = new FxAccountsClient();
    }
    return this._fxAccountsClient;
  },

  // The profile object used to fetch the actual user profile.
  _profile: null,
  get profile() {
    if (!this._profile) {
      let profileServerUrl = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.profile.uri");
      this._profile = new FxAccountsProfile({
        fxa: this,
        profileServerUrl,
      });
    }
    return this._profile;
  },

  // A hook-point for tests who may want a mocked AccountState or mocked storage.
  newAccountState(credentials) {
    let storage = new FxAccountsStorageManager();
    storage.initialize(credentials);
    return new AccountState(storage);
  },

  /**
   * Send a message to a set of devices in the same account
   *
   * @return Promise
   */
  notifyDevices(deviceIds, excludedIds, payload, TTL) {
    if (typeof deviceIds == "string") {
      deviceIds = [deviceIds];
    }
    return this.currentAccountState.getUserAccountData()
      .then(data => {
        if (!data) {
          throw this._error(ERROR_NO_ACCOUNT);
        }
        if (!data.sessionToken) {
          throw this._error(ERROR_AUTH_ERROR,
            "notifyDevices called without a session token");
        }
        return this.fxAccountsClient.notifyDevices(data.sessionToken, deviceIds,
          excludedIds, payload, TTL);
    });
  },

  /**
   * Return the current time in milliseconds as an integer.  Allows tests to
   * manipulate the date to simulate certificate expiration.
   */
  now() {
    return this.fxAccountsClient.now();
  },

  getAccountsClient() {
    return this.fxAccountsClient;
  },

  /**
   * Return clock offset in milliseconds, as reported by the fxAccountsClient.
   * This can be overridden for testing.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this.fxAccountsClient.localtimeOffsetMsec;
  },

  /**
   * Ask the server whether the user's email has been verified
   */
  checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) {
    if (!sessionToken) {
      return Promise.reject(new Error(
        "checkEmailStatus called without a session token"));
    }
    return this.fxAccountsClient.recoveryEmailStatus(sessionToken,
      options).catch(error => this._handleTokenError(error));
  },

  /**
   * Once the user's email is verified, we can request the keys
   */
  fetchKeys: function fetchKeys(keyFetchToken) {
    log.debug("fetchKeys: " + !!keyFetchToken);
    if (logPII) {
      log.debug("fetchKeys - the token is " + keyFetchToken);
    }
    return this.fxAccountsClient.accountKeys(keyFetchToken);
  },

  // set() makes sure that polling is happening, if necessary.
  // get() does not wait for verification, and returns an object even if
  // unverified. The caller of get() must check .verified .
  // The "fxaccounts:onverified" event will fire only when the verified
  // state goes from false to true, so callers must register their observer
  // and then call get(). In particular, it will not fire when the account
  // was found to be verified in a previous boot: if our stored state says
  // the account is verified, the event will never fire. So callers must do:
  //   register notification observer (go)
  //   userdata = get()
  //   if (userdata.verified()) {go()}

  /**
   * Get the user currently signed in to Firefox Accounts.
   *
   * @return Promise
   *        The promise resolves to the credentials object of the signed-in user:
   *        {
   *          email: The user's email address
   *          uid: The user's unique id
   *          sessionToken: Session for the FxA server
   *          kA: An encryption key from the FxA server
   *          kB: An encryption key derived from the user's FxA password
   *          verified: email verification status
   *          authAt: The time (seconds since epoch) that this record was
   *                  authenticated
   *        }
   *        or null if no user is signed in.
   */
  getSignedInUser: function getSignedInUser() {
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData().then(data => {
      if (!data) {
        return null;
      }
      if (!this.isUserEmailVerified(data)) {
        // If the email is not verified, start polling for verification,
        // but return null right away.  We don't want to return a promise
        // that might not be fulfilled for a long time.
        this.startVerifiedCheck(data);
      }
      return data;
    }).then(result => currentState.resolve(result));
  },

  /**
   * Set the current user signed in to Firefox Accounts.
   *
   * @param credentials
   *        The credentials object obtained by logging in or creating
   *        an account on the FxA server:
   *        {
   *          authAt: The time (seconds since epoch) that this record was
   *                  authenticated
   *          email: The users email address
   *          keyFetchToken: a keyFetchToken which has not yet been used
   *          sessionToken: Session for the FxA server
   *          uid: The user's unique id
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified: true/false
   *        }
   * @return Promise
   *         The promise resolves to null when the data is saved
   *         successfully and is rejected on error.
   */
  setSignedInUser: function setSignedInUser(credentials) {
    log.debug("setSignedInUser - aborting any existing flows");
    return this.getSignedInUser().then(signedInUser => {
      if (signedInUser) {
        return this.deleteDeviceRegistration(signedInUser.sessionToken, signedInUser.deviceId);
      }
      return null;
    }).then(() =>
      this.abortExistingFlow()
    ).then(() => {
      let currentAccountState = this.currentAccountState = this.newAccountState(
        Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object.
      );
      // This promise waits for storage, but not for verification.
      // We're telling the caller that this is durable now (although is that
      // really something we should commit to? Why not let the write happen in
      // the background? Already does for updateAccountData ;)
      return currentAccountState.promiseInitialized.then(() => {
        // Starting point for polling if new user
        if (!this.isUserEmailVerified(credentials)) {
          this.startVerifiedCheck(credentials);
        }

        return this.updateDeviceRegistration();
      }).then(() => {
        Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
        this.notifyObservers(ONLOGIN_NOTIFICATION);
      }).then(() => {
        return currentAccountState.resolve();
      });
    });
  },

  /**
   * Update account data for the currently signed in user.
   *
   * @param credentials
   *        The credentials object containing the fields to be updated.
   *        This object must contain |email| and |uid| fields and they must
   *        match the currently signed in user.
   */
  updateUserAccountData(credentials) {
    log.debug("updateUserAccountData called with fields", Object.keys(credentials));
    if (logPII) {
      log.debug("updateUserAccountData called with data", credentials);
    }
    let currentAccountState = this.currentAccountState;
    return currentAccountState.promiseInitialized.then(() => {
      return currentAccountState.getUserAccountData(["email", "uid"]);
    }).then(existing => {
      if (existing.email != credentials.email || existing.uid != credentials.uid) {
        throw new Error("The specified credentials aren't for the current user");
      }
      // We need to nuke email and uid as storage will complain if we try and
      // update them (even when the value is the same)
      credentials = Cu.cloneInto(credentials, {}); // clone it first
      delete credentials.email;
      delete credentials.uid;
      return currentAccountState.updateUserAccountData(credentials);
    });
  },

  /**
   * returns a promise that fires with the assertion.  If there is no verified
   * signed-in user, fires with null.
   */
  getAssertion: function getAssertion(audience) {
    return this._getAssertion(audience);
  },

  // getAssertion() is "public" so screws with our mock story. This
  // implementation method *can* be (and is) mocked by tests.
  _getAssertion: function _getAssertion(audience) {
    log.debug("enter getAssertion()");
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData().then(data => {
      if (!data) {
        // No signed-in user
        return null;
      }
      if (!this.isUserEmailVerified(data)) {
        // Signed-in user has not verified email
        return null;
      }
      if (!data.sessionToken) {
        // can't get a signed certificate without a session token. This
        // can happen if we request an assertion after clearing an invalid
        // session token from storage.
        throw this._error(ERROR_AUTH_ERROR, "getAssertion called without a session token");
      }
      return this.getKeypairAndCertificate(currentState).then(
        ({keyPair, certificate}) => {
          return this.getAssertionFromCert(data, keyPair, certificate, audience);
        }
      );
    }).catch(err =>
      this._handleTokenError(err)
    ).then(result => currentState.resolve(result));
  },

  /**
   * Invalidate the FxA certificate, so that it will be refreshed from the server
   * the next time it is needed.
   */
  invalidateCertificate() {
    return this.currentAccountState.updateUserAccountData({ cert: null });
  },

  getDeviceId() {
    return this.currentAccountState.getUserAccountData()
      .then(data => {
        if (data) {
          if (!data.deviceId || !data.deviceRegistrationVersion ||
              data.deviceRegistrationVersion < this.DEVICE_REGISTRATION_VERSION) {
            // There is no device id or the device registration is outdated.
            // Either way, we should register the device with FxA
            // before returning the id to the caller.
            return this._registerOrUpdateDevice(data);
          }

          // Return the device id that we already registered with the server.
          return data.deviceId;
        }

        // Without a signed-in user, there can be no device id.
        return null;
      });
  },

  async getDeviceList() {
    let accountData = await this._getVerifiedAccountOrReject();
    return this.fxAccountsClient.getDeviceList(accountData.sessionToken);
  },

  /**
   * Resend the verification email fot the currently signed-in user.
   *
   */
  resendVerificationEmail: function resendVerificationEmail() {
    let currentState = this.currentAccountState;
    return this.getSignedInUser().then(data => {
      // If the caller is asking for verification to be re-sent, and there is
      // no signed-in user to begin with, this is probably best regarded as an
      // error.
      if (data) {
        if (!data.sessionToken) {
          return Promise.reject(new Error(
            "resendVerificationEmail called without a session token"));
        }
        this.startPollEmailStatus(currentState, data.sessionToken, "start");
        return this.fxAccountsClient.resendVerificationEmail(
          data.sessionToken).catch(err => this._handleTokenError(err));
      }
      throw new Error("Cannot resend verification email; no signed-in user");
    });
  },

  /*
   * Reset state such that any previous flow is canceled.
   */
  abortExistingFlow: function abortExistingFlow() {
    if (this.currentTimer) {
      log.debug("Polling aborted; Another user signing in");
      clearTimeout(this.currentTimer);
      this.currentTimer = 0;
    }
    if (this._profile) {
      this._profile.tearDown();
      this._profile = null;
    }
    // We "abort" the accountState and assume our caller is about to throw it
    // away and replace it with a new one.
    return this.currentAccountState.abort();
  },

  accountStatus: function accountStatus() {
    return this.currentAccountState.getUserAccountData().then(data => {
      if (!data) {
        return false;
      }
      return this.fxAccountsClient.accountStatus(data.uid);
    });
  },

  checkVerificationStatus() {
    log.trace("checkVerificationStatus");
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData().then(data => {
      if (!data) {
        log.trace("checkVerificationStatus - no user data");
        return null;
      }

      // Always check the verification status, even if the local state indicates
      // we're already verified. If the user changed their password, the check
      // will fail, and we'll enter the reauth state.
      log.trace("checkVerificationStatus - forcing verification status check");
      return this.startPollEmailStatus(currentState, data.sessionToken, "push");
    });
  },

  _destroyOAuthToken(tokenData) {
    let client = new FxAccountsOAuthGrantClient({
      serverURL: tokenData.server,
      client_id: FX_OAUTH_CLIENT_ID
    });
    return client.destroyToken(tokenData.token)
  },

  _destroyAllOAuthTokens(tokenInfos) {
    // let's just destroy them all in parallel...
    let promises = [];
    for (let tokenInfo of Object.values(tokenInfos || {})) {
      promises.push(this._destroyOAuthToken(tokenInfo));
    }
    return Promise.all(promises);
  },

  signOut: function signOut(localOnly) {
    let currentState = this.currentAccountState;
    let sessionToken;
    let tokensToRevoke;
    let deviceId;
    return currentState.getUserAccountData().then(data => {
      // Save the session token, tokens to revoke and the
      // device id for use in the call to signOut below.
      if (data) {
        sessionToken = data.sessionToken;
        tokensToRevoke = data.oauthTokens;
        deviceId = data.deviceId;
      }
      return this._signOutLocal();
    }).then(() => {
      // FxAccountsManager calls here, then does its own call
      // to FxAccountsClient.signOut().
      if (!localOnly) {
        // Wrap this in a promise so *any* errors in signOut won't
        // block the local sign out. This is *not* returned.
        Promise.resolve().then(() => {
          // This can happen in the background and shouldn't block
          // the user from signing out. The server must tolerate
          // clients just disappearing, so this call should be best effort.
          if (sessionToken) {
            return this._signOutServer(sessionToken, deviceId);
          }
          log.warn("Missing session token; skipping remote sign out");
          return null;
        }).catch(err => {
          log.error("Error during remote sign out of Firefox Accounts", err);
        }).then(() => {
          return this._destroyAllOAuthTokens(tokensToRevoke);
        }).catch(err => {
          log.error("Error during destruction of oauth tokens during signout", err);
        }).then(() => {
          FxAccountsConfig.resetConfigURLs();
          // just for testing - notifications are cheap when no observers.
          this.notifyObservers("testhelper-fxa-signout-complete");
        })
      } else {
        // We want to do this either way -- but if we're signing out remotely we
        // need to wait until we destroy the oauth tokens if we want that to succeed.
        FxAccountsConfig.resetConfigURLs();
      }
    }).then(() => {
      this.notifyObservers(ONLOGOUT_NOTIFICATION);
    });
  },

  /**
   * This function should be called in conjunction with a server-side
   * signOut via FxAccountsClient.
   */
  _signOutLocal: function signOutLocal() {
    let currentAccountState = this.currentAccountState;
    return currentAccountState.signOut().then(() => {
      // this "aborts" this.currentAccountState but doesn't make a new one.
      return this.abortExistingFlow();
    }).then(() => {
      this.currentAccountState = this.newAccountState();
      return this.currentAccountState.promiseInitialized;
    });
  },

  _signOutServer(sessionToken, deviceId) {
    // For now we assume the service being logged out from is Sync, so
    // we must tell the server to either destroy the device or sign out
    // (if no device exists). We might need to revisit this when this
    // FxA code is used in a context that isn't Sync.

    const options = { service: "sync" };

    if (deviceId) {
      log.debug("destroying device, session and unsubscribing from FxA push");
      return this.deleteDeviceRegistration(sessionToken, deviceId);
    }

    log.debug("destroying session");
    return this.fxAccountsClient.signOut(sessionToken, options);
  },

  /**
   * Check the status of the current session using cached credentials.
   *
   * @return Promise
   *        Resolves with a boolean indicating if the session is still valid
   */
  sessionStatus() {
    return this.getSignedInUser().then(data => {
      if (!data.sessionToken) {
        return Promise.reject(new Error(
          "sessionStatus called without a session token"));
      }
      return this.fxAccountsClient.sessionStatus(data.sessionToken);
    });
  },

  /**
   * Fetch encryption keys for the signed-in-user from the FxA API server.
   *
   * Not for user consumption.  Exists to cause the keys to be fetch.
   *
   * Returns user data so that it can be chained with other methods.
   *
   * @return Promise
   *        The promise resolves to the credentials object of the signed-in user:
   *        {
   *          email: The user's email address
   *          uid: The user's unique id
   *          sessionToken: Session for the FxA server
   *          kA: An encryption key from the FxA server
   *          kB: An encryption key derived from the user's FxA password
   *          verified: email verification status
   *        }
   *        or null if no user is signed in
   */
  getKeys() {
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData().then((userData) => {
      if (!userData) {
        throw new Error("Can't get keys; User is not signed in");
      }
      if (userData.kA && userData.kB) {
        return userData;
      }
      if (!currentState.whenKeysReadyDeferred) {
        currentState.whenKeysReadyDeferred = PromiseUtils.defer();
        if (userData.keyFetchToken) {
          this.fetchAndUnwrapKeys(userData.keyFetchToken).then(
            (dataWithKeys) => {
              if (!dataWithKeys.kA || !dataWithKeys.kB) {
                currentState.whenKeysReadyDeferred.reject(
                  new Error("user data missing kA or kB")
                );
                return;
              }
              currentState.whenKeysReadyDeferred.resolve(dataWithKeys);
            },
            (err) => {
              currentState.whenKeysReadyDeferred.reject(err);
            }
          );
        } else {
          currentState.whenKeysReadyDeferred.reject("No keyFetchToken");
        }
      }
      return currentState.whenKeysReadyDeferred.promise;
    }).catch(err =>
      this._handleTokenError(err)
    ).then(result => currentState.resolve(result));
   },

  async fetchAndUnwrapKeys(keyFetchToken) {
    if (logPII) {
      log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
    }
    let currentState = this.currentAccountState;
    // Sign out if we don't have a key fetch token.
    if (!keyFetchToken) {
      log.warn("improper fetchAndUnwrapKeys() call: token missing");
      await this.signOut();
      return currentState.resolve(null);
    }

    let {kA, wrapKB} = await this.fetchKeys(keyFetchToken);

    let data = await currentState.getUserAccountData();

    // Sanity check that the user hasn't changed out from under us
    if (data.keyFetchToken !== keyFetchToken) {
      throw new Error("Signed in user changed while fetching keys!");
    }

    // Next statements must be synchronous until we setUserAccountData
    // so that we don't risk getting into a weird state.
    let kB_hex = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
                                 wrapKB);

    if (logPII) {
      log.debug("kB_hex: " + kB_hex);
    }
    let updateData = {
      kA: CommonUtils.bytesAsHex(kA),
      kB: CommonUtils.bytesAsHex(kB_hex),
      keyFetchToken: null, // null values cause the item to be removed.
      unwrapBKey: null,
    }

    log.debug("Keys Obtained: kA=" + !!updateData.kA + ", kB=" + !!updateData.kB);
    if (logPII) {
      log.debug("Keys Obtained: kA=" + updateData.kA + ", kB=" + updateData.kB);
    }

    await currentState.updateUserAccountData(updateData);
    // We are now ready for business. This should only be invoked once
    // per setSignedInUser(), regardless of whether we've rebooted since
    // setSignedInUser() was called.
    this.notifyObservers(ONVERIFIED_NOTIFICATION);
    data = await currentState.getUserAccountData();
    return currentState.resolve(data);
  },

  async getAssertionFromCert(data, keyPair, cert, audience) {
    log.debug("getAssertionFromCert");
    let options = {
      duration: ASSERTION_LIFETIME,
      localtimeOffsetMsec: this.localtimeOffsetMsec,
      now: this.now()
    };
    let currentState = this.currentAccountState;
    // "audience" should look like "http://123done.org".
    // The generated assertion will expire in two minutes.
    let assertion = await new Promise((resolve, reject) => {
      jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
        if (err) {
          log.error("getAssertionFromCert: " + err);
          reject(err);
        } else {
          log.debug("getAssertionFromCert returning signed: " + !!signed);
          if (logPII) {
            log.debug("getAssertionFromCert returning signed: " + signed);
          }
          resolve(signed);
        }
      });
    });
    return currentState.resolve(assertion);
  },

  getCertificateSigned(sessionToken, serializedPublicKey, lifetime) {
    log.debug("getCertificateSigned: " + !!sessionToken + " " + !!serializedPublicKey);
    if (logPII) {
      log.debug("getCertificateSigned: " + sessionToken + " " + serializedPublicKey);
    }
    return this.fxAccountsClient.signCertificate(
      sessionToken,
      JSON.parse(serializedPublicKey),
      lifetime
    );
  },

  /**
   * returns a promise that fires with {keyPair, certificate}.
   */
  async getKeypairAndCertificate(currentState) {
    // If the debugging pref to ignore cached authentication credentials is set for Sync,
    // then don't use any cached key pair/certificate, i.e., generate a new
    // one and get it signed.
    // The purpose of this pref is to expedite any auth errors as the result of a
    // expired or revoked FxA session token, e.g., from resetting or changing the FxA
    // password.
    let ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials", false);
    let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
    let accountData = await currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);

    let keyPairValid = !ignoreCachedAuthCredentials &&
                       accountData.keyPair &&
                       (accountData.keyPair.validUntil > mustBeValidUntil);
    let certValid = !ignoreCachedAuthCredentials &&
                    accountData.cert &&
                    (accountData.cert.validUntil > mustBeValidUntil);
    // TODO: get the lifetime from the cert's .exp field
    if (keyPairValid && certValid) {
      log.debug("getKeypairAndCertificate: already have keyPair and certificate");
      return {
        keyPair: accountData.keyPair.rawKeyPair,
        certificate: accountData.cert.rawCert
      }
    }
    // We are definately going to generate a new cert, either because it has
    // already expired, or the keyPair has - and a new keyPair means we must
    // generate a new cert.

    // A keyPair has a longer lifetime than a cert, so it's possible we will
    // have a valid keypair but an expired cert, which means we can skip
    // keypair generation.
    // Either way, the cert will require hitting the network, so bail now if
    // we know that's going to fail.
    if (Services.io.offline) {
      throw new Error(ERROR_OFFLINE);
    }

    let keyPair;
    if (keyPairValid) {
      keyPair = accountData.keyPair;
    } else {
      let keyWillBeValidUntil = this.now() + KEY_LIFETIME;
      keyPair = await new Promise((resolve, reject) => {
        jwcrypto.generateKeyPair("DS160", (err, kp) => {
          if (err) {
            reject(err);
            return;
          }
          log.debug("got keyPair");
          resolve({
            rawKeyPair: kp,
            validUntil: keyWillBeValidUntil,
          });
        });
      });
    }

    // and generate the cert.
    let certWillBeValidUntil = this.now() + CERT_LIFETIME;
    let certificate = await this.getCertificateSigned(accountData.sessionToken,
                                                      keyPair.rawKeyPair.serializedPublicKey,
                                                      CERT_LIFETIME);
    log.debug("getCertificate got a new one: " + !!certificate);
    if (certificate) {
      // Cache both keypair and cert.
      let toUpdate = {
        keyPair,
        cert: {
          rawCert: certificate,
          validUntil: certWillBeValidUntil,
        },
      };
      await currentState.updateUserAccountData(toUpdate);
    }
    return {
      keyPair: keyPair.rawKeyPair,
      certificate,
    }
  },

  getUserAccountData() {
    return this.currentAccountState.getUserAccountData();
  },

  isUserEmailVerified: function isUserEmailVerified(data) {
    return !!(data && data.verified);
  },

  /**
   * Setup for and if necessary do email verification polling.
   */
  loadAndPoll() {
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData()
      .then(data => {
        if (data) {
          Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
          if (!this.isUserEmailVerified(data)) {
            this.startPollEmailStatus(currentState, data.sessionToken, "browser-startup");
          }
        }
        return data;
      });
  },

  startVerifiedCheck(data) {
    log.debug("startVerifiedCheck", data && data.verified);
    if (logPII) {
      log.debug("startVerifiedCheck with user data", data);
    }

    // Get us to the verified state, then get the keys. This returns a promise
    // that will fire when we are completely ready.
    //
    // Login is truly complete once keys have been fetched, so once getKeys()
    // obtains and stores kA and kB, it will fire the onverified observer
    // notification.

    // The callers of startVerifiedCheck never consume a returned promise (ie,
    // this is simply kicking off a background fetch) so we must add a rejection
    // handler to avoid runtime warnings about the rejection not being handled.
    this.whenVerified(data).then(
      () => this.getKeys(),
      err => log.info("startVerifiedCheck promise was rejected: " + err)
    );
  },

  whenVerified(data) {
    let currentState = this.currentAccountState;
    if (data.verified) {
      log.debug("already verified");
      return currentState.resolve(data);
    }
    if (!currentState.whenVerifiedDeferred) {
      log.debug("whenVerified promise starts polling for verified email");
      this.startPollEmailStatus(currentState, data.sessionToken, "start");
    }
    return currentState.whenVerifiedDeferred.promise.then(
      result => currentState.resolve(result)
    );
  },

  notifyObservers(topic, data) {
    log.debug("Notifying observers of " + topic);
    Services.obs.notifyObservers(null, topic, data);
  },

  startPollEmailStatus(currentState, sessionToken, why) {
    log.debug("entering startPollEmailStatus: " + why);
    // If we were already polling, stop and start again.  This could happen
    // if the user requested the verification email to be resent while we
    // were already polling for receipt of an earlier email.
    if (this.currentTimer) {
      log.debug("startPollEmailStatus starting while existing timer is running");
      clearTimeout(this.currentTimer);
      this.currentTimer = null;
    }

    this.pollStartDate = Date.now();
    if (!currentState.whenVerifiedDeferred) {
      currentState.whenVerifiedDeferred = PromiseUtils.defer();
      // This deferred might not end up with any handlers (eg, if sync
      // is yet to start up.)  This might cause "A promise chain failed to
      // handle a rejection" messages, so add an error handler directly
      // on the promise to log the error.
      currentState.whenVerifiedDeferred.promise.catch(err => {
        log.info("the wait for user verification was stopped: " + err);
      });
    }
    return this.pollEmailStatus(currentState, sessionToken, why);
  },

  // We return a promise for testing only. Other callers can ignore this,
  // since verification polling continues in the background.
  async pollEmailStatus(currentState, sessionToken, why) {
    log.debug("entering pollEmailStatus: " + why);
    let nextPollMs;
    try {
      const response = await this.checkEmailStatus(sessionToken, { reason: why });
      log.debug("checkEmailStatus -> " + JSON.stringify(response));
      if (response && response.verified) {
        await this.onPollEmailSuccess(currentState, why);
        return;
      }
    } catch (error) {
      if (error && error.code && error.code == 401) {
        let error = new Error("Verification status check failed");
        this._rejectWhenVerified(currentState, error);
        return;
      }
      if (error && error.retryAfter) {
        // If the server told us to back off, back off the requested amount.
        nextPollMs = (error.retryAfter + 3) * 1000;
      }
    }
    if (why == "push") {
      return;
    }
    let pollDuration = Date.now() - this.pollStartDate;
    // Polling session expired.
    if (pollDuration >= this.POLL_SESSION) {
      if (currentState.whenVerifiedDeferred) {
        let error = new Error("User email verification timed out.");
        this._rejectWhenVerified(currentState, error);
      }
      log.debug("polling session exceeded, giving up");
      return;
    }
    // Poll email status again after a short delay.
    if (nextPollMs === undefined) {
      let currentMinute = Math.ceil(pollDuration / 60000);
      nextPollMs = (why == "start" && currentMinute < this.VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD) ?
                   this.VERIFICATION_POLL_TIMEOUT_INITIAL :
                   this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
    }
    this._scheduleNextPollEmailStatus(currentState, sessionToken, nextPollMs, why);
  },

  // Easy-to-mock testable method
  _scheduleNextPollEmailStatus(currentState, sessionToken, nextPollMs, why) {
    log.debug("polling with timeout = " + nextPollMs);
    this.currentTimer = setTimeout(() => {
      this.pollEmailStatus(currentState, sessionToken, why);
    }, nextPollMs);
  },

  async onPollEmailSuccess(currentState, why) {
    try {
      await currentState.updateUserAccountData({ verified: true })
      const accountData = await currentState.getUserAccountData();
      // Now that the user is verified, we can proceed to fetch keys
      if (currentState.whenVerifiedDeferred) {
        currentState.whenVerifiedDeferred.resolve(accountData);
        delete currentState.whenVerifiedDeferred;
      }
      // Tell FxAccountsManager to clear its cache
      this.notifyObservers(ON_FXA_UPDATE_NOTIFICATION, ONVERIFIED_NOTIFICATION);
      // Record how we determined the account was verified
      Services.telemetry.scalarSet("services.sync.fxa_verification_method",
                                   why == "push" ? "push" : "poll");
    } catch (e) {
      log.error(e);
    }
  },

  _rejectWhenVerified(currentState, error) {
    currentState.whenVerifiedDeferred.reject(error);
    delete currentState.whenVerifiedDeferred;
  },

  requiresHttps() {
    let allowHttp = Services.prefs.getBoolPref("identity.fxaccounts.allowHttp", false);
    return allowHttp !== true;
  },

  promiseAccountsSignUpURI() {
    return FxAccountsConfig.promiseAccountsSignUpURI();
  },

  promiseAccountsSignInURI() {
    return FxAccountsConfig.promiseAccountsSignInURI();
  },

  /**
   * Pull an URL defined in the user preferences, add the current UID and email
   * to the query string, add entrypoint and extra params to the query string if
   * requested.
   * @param {string} prefName The preference name from where to pull the URL to format.
   * @param {string} [entrypoint] "entrypoint" searchParam value.
   * @param {Object.<string, string>} [extraParams] Additionnal searchParam key and values.
   * @returns {Promise.<string>} A promise that resolves to the formatted URL
   */
  async _formatPrefURL(prefName, entrypoint, extraParams) {
    let url = new URL(Services.urlFormatter.formatURLPref(prefName));
    if (this.requiresHttps() && url.protocol != "https:") {
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    let accountData = await this.getSignedInUser();
    if (!accountData) {
      return Promise.resolve(null);
    }
    url.searchParams.append("uid", accountData.uid);
    url.searchParams.append("email", accountData.email);
    if (entrypoint) {
      url.searchParams.append("entrypoint", entrypoint);
    }
    if (extraParams) {
      for (let [k, v] of Object.entries(extraParams)) {
        url.searchParams.append(k, v);
      }
    }
    return this.currentAccountState.resolve(url.href);
  },

  // Returns a promise that resolves with the URL to use to force a re-signin
  // of the current account.
  async promiseAccountsForceSigninURI() {
    await FxAccountsConfig.ensureConfigured();
    return this._formatPrefURL("identity.fxaccounts.remote.force_auth.uri");
  },

  // Returns a promise that resolves with the URL to use to change
  // the current account's profile image.
  // if settingToEdit is set, the profile page should hightlight that setting
  // for the user to edit.
  async promiseAccountsChangeProfileURI(entrypoint, settingToEdit = null) {
    let extraParams;
    if (settingToEdit) {
      extraParams = { setting: settingToEdit };
    }
    return this._formatPrefURL("identity.fxaccounts.settings.uri", entrypoint, extraParams);
  },

  // Returns a promise that resolves with the URL to use to manage the current
  // user's FxA acct.
  async promiseAccountsManageURI(entrypoint) {
    return this._formatPrefURL("identity.fxaccounts.settings.uri", entrypoint);
  },

  // Returns a promise that resolves with the URL to use to manage the devices in
  // the current user's FxA acct.
  async promiseAccountsManageDevicesURI(entrypoint) {
    return this._formatPrefURL("identity.fxaccounts.settings.devices.uri", entrypoint);
  },

  /**
   * Get an OAuth token for the user
   *
   * @param options
   *        {
   *          scope: (string/array) the oauth scope(s) being requested. As a
   *                 convenience, you may pass a string if only one scope is
   *                 required, or an array of strings if multiple are needed.
   *        }
   *
   * @return Promise.<string | Error>
   *        The promise resolves the oauth token as a string or rejects with
   *        an error object ({error: ERROR, details: {}}) of the following:
   *          INVALID_PARAMETER
   *          NO_ACCOUNT
   *          UNVERIFIED_ACCOUNT
   *          NETWORK_ERROR
   *          AUTH_ERROR
   *          UNKNOWN_ERROR
   */
  async getOAuthToken(options = {}) {
    log.debug("getOAuthToken enter");
    let scope = options.scope;
    if (typeof scope === "string") {
      scope = [scope];
    }

    if (!scope || !scope.length) {
      throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'scope' option");
    }

    await this._getVerifiedAccountOrReject();

    // Early exit for a cached token.
    let currentState = this.currentAccountState;
    let cached = currentState.getCachedToken(scope);
    if (cached) {
      log.debug("getOAuthToken returning a cached token");
      return cached.token;
    }

    // We are going to hit the server - this is the string we pass to it.
    let scopeString = scope.join(" ");
    let client = options.client;

    if (!client) {
      try {
        let defaultURL = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.oauth.uri");
        client = new FxAccountsOAuthGrantClient({
          serverURL: defaultURL,
          client_id: FX_OAUTH_CLIENT_ID
        });
      } catch (e) {
        throw this._error(ERROR_INVALID_PARAMETER, e);
      }
    }
    let oAuthURL = client.serverURL.href;

    try {
      log.debug("getOAuthToken fetching new token from", oAuthURL);
      let assertion = await this.getAssertion(oAuthURL);
      let result = await client.getTokenFromAssertion(assertion, scopeString);
      let token = result.access_token;
      // If we got one, cache it.
      if (token) {
        let entry = {token, server: oAuthURL};
        // But before we do, check the cache again - if we find one now, it
        // means someone else concurrently requested the same scope and beat
        // us to the cache write. To be nice to the server, we revoke the one
        // we just got and return the newly cached value.
        let cached = currentState.getCachedToken(scope);
        if (cached) {
          log.debug("Detected a race for this token - revoking the new one.");
          this._destroyOAuthToken(entry);
          return cached.token;
        }
        currentState.setCachedToken(scope, entry);
      }
      return token;
    } catch (err) {
      throw this._errorToErrorClass(err);
    }
  },

  /**
   * Remove an OAuth token from the token cache.  Callers should call this
   * after they determine a token is invalid, so a new token will be fetched
   * on the next call to getOAuthToken().
   *
   * @param options
   *        {
   *          token: (string) A previously fetched token.
   *        }
   * @return Promise.<undefined> This function will always resolve, even if
   *         an unknown token is passed.
   */
  async removeCachedOAuthToken(options) {
    if (!options.token || typeof options.token !== "string") {
      throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'token' option");
    }
    let currentState = this.currentAccountState;
    let existing = currentState.removeCachedToken(options.token);
    if (existing) {
      // background destroy.
      this._destroyOAuthToken(existing).catch(err => {
        log.warn("FxA failed to revoke a cached token", err);
      });
    }
  },

  async _getVerifiedAccountOrReject() {
    let data = await this.currentAccountState.getUserAccountData();
    if (!data) {
      // No signed-in user
      throw this._error(ERROR_NO_ACCOUNT);
    }
    if (!this.isUserEmailVerified(data)) {
      // Signed-in user has not verified email
      throw this._error(ERROR_UNVERIFIED_ACCOUNT);
    }
    return data;
  },

  /*
   * Coerce an error into one of the general error cases:
   *          NETWORK_ERROR
   *          AUTH_ERROR
   *          UNKNOWN_ERROR
   *
   * These errors will pass through:
   *          INVALID_PARAMETER
   *          NO_ACCOUNT
   *          UNVERIFIED_ACCOUNT
   */
  _errorToErrorClass(aError) {
    if (aError.errno) {
      let error = SERVER_ERRNO_TO_ERROR[aError.errno];
      return this._error(ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN, aError);
    } else if (aError.message &&
        (aError.message === "INVALID_PARAMETER" ||
        aError.message === "NO_ACCOUNT" ||
        aError.message === "UNVERIFIED_ACCOUNT" ||
        aError.message === "AUTH_ERROR")) {
      return aError;
    }
    return this._error(ERROR_UNKNOWN, aError);
  },

  _error(aError, aDetails) {
    log.error("FxA rejecting with error ${aError}, details: ${aDetails}", {aError, aDetails});
    let reason = new Error(aError);
    if (aDetails) {
      reason.details = aDetails;
    }
    return reason;
  },

  /**
   * Get the user's account and profile data
   *
   * @param options
   *        {
   *          contentUrl: (string) Used by the FxAccountsWebChannel.
   *            Defaults to pref identity.fxaccounts.settings.uri
   *          profileServerUrl: (string) Used by the FxAccountsWebChannel.
   *            Defaults to pref identity.fxaccounts.remote.profile.uri
   *        }
   *
   * @return Promise.<object | Error>
   *        The promise resolves to an accountData object with extra profile
   *        information such as profileImageUrl, or rejects with
   *        an error object ({error: ERROR, details: {}}) of the following:
   *          INVALID_PARAMETER
   *          NO_ACCOUNT
   *          UNVERIFIED_ACCOUNT
   *          NETWORK_ERROR
   *          AUTH_ERROR
   *          UNKNOWN_ERROR
   */
  getSignedInUserProfile() {
    let currentState = this.currentAccountState;
    return this.profile.getProfile().then(
      profileData => {
        let profile = Cu.cloneInto(profileData, {});
        return currentState.resolve(profile);
      },
      error => {
        log.error("Could not retrieve profile data", error);
        return currentState.reject(error);
      }
    ).catch(err => Promise.reject(this._errorToErrorClass(err)));
  },

  // Attempt to update the auth server with whatever device details are stored
  // in the account data. Returns a promise that always resolves, never rejects.
  // If the promise resolves to a value, that value is the device id.
  updateDeviceRegistration() {
    return this.getSignedInUser().then(signedInUser => {
      if (signedInUser) {
        return this._registerOrUpdateDevice(signedInUser);
      }
      return null;
    }).catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
  },

  // Delete the Push Subscription and the device registration on the auth server.
  // Returns a promise that always resolves, never rejects.
  async deleteDeviceRegistration(sessionToken, deviceId) {
    try {
      // Allow tests to skip device registration because it makes remote requests to the auth server.
      if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
        return Promise.resolve();
      }
    } catch (ignore) {}

    try {
      await this.fxaPushService.unsubscribe();
      if (sessionToken && deviceId) {
        await this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId);
      }
      this.currentAccountState.updateUserAccountData({
        deviceId: null,
        deviceRegistrationVersion: null
      });
    } catch (err) {
      log.error("Could not delete the device registration", err);
    }
    return Promise.resolve();
  },

  async handleDeviceDisconnection(deviceId) {
    const accountData = await this.currentAccountState.getUserAccountData();
    const localDeviceId = accountData ? accountData.deviceId : null;
    const isLocalDevice = (deviceId == localDeviceId);
    if (isLocalDevice) {
      this.signOut(true);
    }
    const data = JSON.stringify({ isLocalDevice });
    Services.obs.notifyObservers(null, ON_DEVICE_DISCONNECTED_NOTIFICATION, data);
    return null;
  },

  async handleAccountDestroyed(uid) {
    const accountData = await this.currentAccountState.getUserAccountData();
    const localUid = accountData ? accountData.uid : null;
    if (!localUid) {
      log.info(`Account destroyed push notification received, but we're already logged-out`);
      return null;
    }
    if (uid == localUid) {
      const data = JSON.stringify({ isLocalDevice: true });
      Services.obs.notifyObservers(null, ON_DEVICE_DISCONNECTED_NOTIFICATION, data);
      this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, data);
      return this.signOut(true);
    }
    log.info(
      `The destroyed account uid doesn't match with the local uid. ` +
      `Local: ${localUid}, account uid destroyed: ${uid}`);
    return null;
  },

  /**
   * Delete all the cached persisted credentials we store for FxA.
   *
   * @return Promise resolves when the user data has been persisted
  */
  resetCredentials() {
    // Delete all fields except those required for the user to
    // reauthenticate.
    let updateData = {};
    let clearField = field => {
      if (!FXA_PWDMGR_REAUTH_WHITELIST.has(field)) {
        updateData[field] = null;
      }
    }
    FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField);
    FXA_PWDMGR_SECURE_FIELDS.forEach(clearField);
    FXA_PWDMGR_MEMORY_FIELDS.forEach(clearField);

    let currentState = this.currentAccountState;
    return currentState.updateUserAccountData(updateData);
  },

  getProfileCache() {
    return this.currentAccountState.getUserAccountData(["profileCache"])
      .then(data => data ? data.profileCache : null);
  },

  setProfileCache(profileCache) {
    return this.currentAccountState.updateUserAccountData({
      profileCache
    });
  },

  // If you change what we send to the FxA servers during device registration,
  // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
  // devices to re-register when Firefox updates
  _registerOrUpdateDevice(signedInUser) {
    try {
      // Allow tests to skip device registration because:
      //   1. It makes remote requests to the auth server.
      //   2. _getDeviceName does not work from xpcshell.
      //   3. The B2G tests fail when attempting to import services-sync/util.js.
      if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
        return Promise.resolve();
      }
    } catch (ignore) {}

    if (!signedInUser.sessionToken) {
      return Promise.reject(new Error(
        "_registerOrUpdateDevice called without a session token"));
    }

    return this.fxaPushService.registerPushEndpoint().then(subscription => {
      const deviceName = this._getDeviceName();
      let deviceOptions = {};

      // if we were able to obtain a subscription
      if (subscription && subscription.endpoint) {
        deviceOptions.pushCallback = subscription.endpoint;
        let publicKey = subscription.getKey("p256dh");
        let authKey = subscription.getKey("auth");
        if (publicKey && authKey) {
          deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
          deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
        }
      }

      if (signedInUser.deviceId) {
        log.debug("updating existing device details");
        return this.fxAccountsClient.updateDevice(
          signedInUser.sessionToken, signedInUser.deviceId, deviceName, deviceOptions);
      }

      log.debug("registering new device details");
      return this.fxAccountsClient.registerDevice(
        signedInUser.sessionToken, deviceName, this._getDeviceType(), deviceOptions);
    }).then(device =>
      this.currentAccountState.updateUserAccountData({
        deviceId: device.id,
        deviceRegistrationVersion: this.DEVICE_REGISTRATION_VERSION
      }).then(() => device.id)
    ).catch(error => this._handleDeviceError(error, signedInUser.sessionToken));
  },

  _getDeviceName() {
    return Utils.getDeviceName();
  },

  _getDeviceType() {
    return Utils.getDeviceType();
  },

  _handleDeviceError(error, sessionToken) {
    return Promise.resolve().then(() => {
      if (error.code === 400) {
        if (error.errno === ERRNO_UNKNOWN_DEVICE) {
          return this._recoverFromUnknownDevice();
        }

        if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) {
          return this._recoverFromDeviceSessionConflict(error, sessionToken);
        }
      }

      // `_handleTokenError` re-throws the error.
      return this._handleTokenError(error);
    }).catch(error =>
      this._logErrorAndResetDeviceRegistrationVersion(error)
    ).catch(() => {});
  },

  _recoverFromUnknownDevice() {
    // FxA did not recognise the device id. Handle it by clearing the device
    // id on the account data. At next sync or next sign-in, registration is
    // retried and should succeed.
    log.warn("unknown device id, clearing the local device data");
    return this.currentAccountState.updateUserAccountData({ deviceId: null })
      .catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
  },

  _recoverFromDeviceSessionConflict(error, sessionToken) {
    // FxA has already associated this session with a different device id.
    // Perhaps we were beaten in a race to register. Handle the conflict:
    //   1. Fetch the list of devices for the current user from FxA.
    //   2. Look for ourselves in the list.
    //   3. If we find a match, set the correct device id and device registration
    //      version on the account data and return the correct device id. At next
    //      sync or next sign-in, registration is retried and should succeed.
    //   4. If we don't find a match, log the original error.
    log.warn("device session conflict, attempting to ascertain the correct device id");
    return this.fxAccountsClient.getDeviceList(sessionToken)
      .then(devices => {
        const matchingDevices = devices.filter(device => device.isCurrentDevice);
        const length = matchingDevices.length;
        if (length === 1) {
          const deviceId = matchingDevices[0].id;
          return this.currentAccountState.updateUserAccountData({
            deviceId,
            deviceRegistrationVersion: null
          }).then(() => deviceId);
        }
        if (length > 1) {
          log.error("insane server state, " + length + " devices for this session");
        }
        return this._logErrorAndResetDeviceRegistrationVersion(error);
      }).catch(secondError => {
        log.error("failed to recover from device-session conflict", secondError);
        this._logErrorAndResetDeviceRegistrationVersion(error)
      });
  },

  _logErrorAndResetDeviceRegistrationVersion(error) {
    // Device registration should never cause other operations to fail.
    // If we've reached this point, just log the error and reset the device
    // registration version on the account data. At next sync or next sign-in,
    // registration will be retried.
    log.error("device registration failed", error);
    return this.currentAccountState.updateUserAccountData({
      deviceRegistrationVersion: null
    }).catch(secondError => {
      log.error(
        "failed to reset the device registration version, device registration won't be retried",
        secondError);
    }).then(() => {});
  },

  _handleTokenError(err) {
    if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) {
      throw err;
    }
    log.warn("recovering from invalid token error", err);
    return this.accountStatus().then(exists => {
      if (!exists) {
        // Delete all local account data. Since the account no longer
        // exists, we can skip the remote calls.
        log.info("token invalidated because the account no longer exists");
        return this.signOut(true);
      }
      log.info("clearing credentials to handle invalid token error");
      return this.resetCredentials();
    }).then(() => Promise.reject(err));
  },
};


// A getter for the instance to export
XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
  let a = new FxAccounts();

  // XXX Bug 947061 - We need a strategy for resuming email verification after
  // browser restart
  a.loadAndPoll();

  return a;
});
PK
!<$LPPmodules/FxAccountsClient.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["FxAccountsClient"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/hawkclient.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Credentials.jsm");

const HOST_PREF = "identity.fxaccounts.auth.uri";

const SIGNIN = "/account/login";
const SIGNUP = "/account/create";

this.FxAccountsClient = function(host = Services.prefs.getCharPref(HOST_PREF)) {
  this.host = host;

  // The FxA auth server expects requests to certain endpoints to be authorized
  // using Hawk.
  this.hawk = new HawkClient(host);
  this.hawk.observerPrefix = "FxA:hawk";

  // Manage server backoff state. C.f.
  // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol
  this.backoffError = null;
};

this.FxAccountsClient.prototype = {

  /**
   * Return client clock offset, in milliseconds, as determined by hawk client.
   * Provided because callers should not have to know about hawk
   * implementation.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this.hawk.localtimeOffsetMsec;
  },

  /*
   * Return current time in milliseconds
   *
   * Not used by this module, but made available to the FxAccounts.jsm
   * that uses this client.
   */
  now() {
    return this.hawk.now();
  },

  /**
   * Common code from signIn and signUp.
   *
   * @param path
   *        Request URL path. Can be /account/create or /account/login
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @param [retryOK=true]
   *        If capitalization of the email is wrong and retryOK is set to true,
   *        we will retry with the suggested capitalization from the server
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          authAt: authentication time for the session (seconds since epoch)
   *          email: the primary email for this account
   *          keyFetchToken: a key fetch token (hex)
   *          sessionToken: a session token (hex)
   *          uid: the user's unique ID (hex)
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified (optional): flag indicating verification status of the
   *                               email
   *        }
   */
  _createSession(path, email, password, getKeys = false,
                 retryOK = true) {
    return Credentials.setup(email, password).then((creds) => {
      let data = {
        authPW: CommonUtils.bytesAsHex(creds.authPW),
        email,
      };
      let keys = getKeys ? "?keys=true" : "";

      return this._request(path + keys, "POST", null, data).then(
        // Include the canonical capitalization of the email in the response so
        // the caller can set its signed-in user state accordingly.
        result => {
          result.email = data.email;
          result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey);

          return result;
        },
        error => {
          log.debug("Session creation failed", error);
          // If the user entered an email with different capitalization from
          // what's stored in the database (e.g., Greta.Garbo@gmail.COM as
          // opposed to greta.garbo@gmail.com), the server will respond with a
          // errno 120 (code 400) and the expected capitalization of the email.
          // We retry with this email exactly once.  If successful, we use the
          // server's version of the email as the signed-in-user's email. This
          // is necessary because the email also serves as salt; so we must be
          // in agreement with the server on capitalization.
          //
          // API reference:
          // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
          if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) {
            if (!error.email) {
              log.error("Server returned errno 120 but did not provide email");
              throw error;
            }
            return this._createSession(path, error.email, password, getKeys,
                                       false);
          }
          throw error;
        }
      );
    });
  },

  /**
   * Create a new Firefox Account and authenticate
   *
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          uid: the user's unique ID (hex)
   *          sessionToken: a session token (hex)
   *          keyFetchToken: a key fetch token (hex),
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *        }
   */
  signUp(email, password, getKeys = false) {
    return this._createSession(SIGNUP, email, password, getKeys,
                               false /* no retry */);
  },

  /**
   * Authenticate and create a new session with the Firefox Account API server
   *
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          authAt: authentication time for the session (seconds since epoch)
   *          email: the primary email for this account
   *          keyFetchToken: a key fetch token (hex)
   *          sessionToken: a session token (hex)
   *          uid: the user's unique ID (hex)
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified: flag indicating verification status of the email
   *        }
   */
  signIn: function signIn(email, password, getKeys = false) {
    return this._createSession(SIGNIN, email, password, getKeys,
                               true /* retry */);
  },

  /**
   * Check the status of a session given a session token
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @return Promise
   *        Resolves with a boolean indicating if the session is still valid
   */
  sessionStatus(sessionTokenHex) {
    return this._request("/session/status", "GET",
      deriveHawkCredentials(sessionTokenHex, "sessionToken")).then(
        () => Promise.resolve(true),
        error => {
          if (isInvalidTokenError(error)) {
            return Promise.resolve(false);
          }
          throw error;
        }
      );
  },

  /**
   * Destroy the current session with the Firefox Account API server
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @return Promise
   */
  signOut(sessionTokenHex, options = {}) {
    let path = "/session/destroy";
    if (options.service) {
      path += "?service=" + encodeURIComponent(options.service);
    }
    return this._request(path, "POST",
      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
  },

  /**
   * Check the verification status of the user's FxA email address
   *
   * @param sessionTokenHex
   *        The current session token encoded in hex
   * @return Promise
   */
  recoveryEmailStatus(sessionTokenHex, options = {}) {
    let path = "/recovery_email/status";
    if (options.reason) {
      path += "?reason=" + encodeURIComponent(options.reason);
    }

    return this._request(path, "GET",
      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
  },

  /**
   * Resend the verification email for the user
   *
   * @param sessionTokenHex
   *        The current token encoded in hex
   * @return Promise
   */
  resendVerificationEmail(sessionTokenHex) {
    return this._request("/recovery_email/resend_code", "POST",
      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
  },

  /**
   * Retrieve encryption keys
   *
   * @param keyFetchTokenHex
   *        A one-time use key fetch token encoded in hex
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          kA: an encryption key for recevorable data (bytes)
   *          wrapKB: an encryption key that requires knowledge of the
   *                  user's password (bytes)
   *        }
   */
  accountKeys(keyFetchTokenHex) {
    let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
    let keyRequestKey = creds.extra.slice(0, 32);
    let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
                                     Credentials.keyWord("account/keys"), 3 * 32);
    let respHMACKey = morecreds.slice(0, 32);
    let respXORKey = morecreds.slice(32, 96);

    return this._request("/account/keys", "GET", creds).then(resp => {
      if (!resp.bundle) {
        throw new Error("failed to retrieve keys");
      }

      let bundle = CommonUtils.hexToBytes(resp.bundle);
      let mac = bundle.slice(-32);

      let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
        CryptoUtils.makeHMACKey(respHMACKey));

      let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
      if (mac !== bundleMAC) {
        throw new Error("error unbundling encryption keys");
      }

      let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));

      return {
        kA: keyAWrapB.slice(0, 32),
        wrapKB: keyAWrapB.slice(32)
      };
    });
  },

  /**
   * Sends a public key to the FxA API server and returns a signed certificate
   *
   * @param sessionTokenHex
   *        The current session token encoded in hex
   * @param serializedPublicKey
   *        A public key (usually generated by jwcrypto)
   * @param lifetime
   *        The lifetime of the certificate
   * @return Promise
   *        Returns a promise that resolves to the signed certificate.
   *        The certificate can be used to generate a Persona assertion.
   * @throws a new Error
   *         wrapping any of these HTTP code/errno pairs:
   *           https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
   */
  signCertificate(sessionTokenHex, serializedPublicKey, lifetime) {
    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");

    let body = { publicKey: serializedPublicKey,
                 duration: lifetime };
    return Promise.resolve()
      .then(_ => this._request("/certificate/sign", "POST", creds, body))
      .then(resp => resp.cert,
            err => {
              log.error("HAWK.signCertificate error: " + JSON.stringify(err));
              throw err;
            });
  },

  /**
   * Determine if an account exists
   *
   * @param email
   *        The email address to check
   * @return Promise
   *        The promise resolves to true if the account exists, or false
   *        if it doesn't. The promise is rejected on other errors.
   */
  accountExists(email) {
    return this.signIn(email, "").then(
      (cantHappen) => {
        throw new Error("How did I sign in with an empty password?");
      },
      (expectedError) => {
        switch (expectedError.errno) {
          case ERRNO_ACCOUNT_DOES_NOT_EXIST:
            return false;
          case ERRNO_INCORRECT_PASSWORD:
            return true;
          default:
            // not so expected, any more ...
            throw expectedError;
        }
      }
    );
  },

  /**
   * Given the uid of an existing account (not an arbitrary email), ask
   * the server if it still exists via /account/status.
   *
   * Used for differentiating between password change and account deletion.
   */
  accountStatus(uid) {
    return this._request("/account/status?uid=" + uid, "GET").then(
      (result) => {
        return result.exists;
      },
      (error) => {
        log.error("accountStatus failed with: " + error);
        return Promise.reject(error);
      }
    );
  },

  /**
   * Register a new device
   *
   * @method registerDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  name
   *         Device name
   * @param  type
   *         Device type (mobile|desktop)
   * @param  [options]
   *         Extra device options
   * @param  [options.pushCallback]
   *         `pushCallback` push endpoint callback
   * @param  [options.pushPublicKey]
   *         `pushPublicKey` push public key (URLSafe Base64 string)
   * @param  [options.pushAuthKey]
   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
   * @return Promise
   *         Resolves to an object:
   *         {
   *           id: Device identifier
   *           createdAt: Creation time (milliseconds since epoch)
   *           name: Name of device
   *           type: Type of device (mobile|desktop)
   *         }
   */
  registerDevice(sessionTokenHex, name, type, options = {}) {
    let path = "/account/device";

    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
    let body = { name, type };

    if (options.pushCallback) {
      body.pushCallback = options.pushCallback;
    }
    if (options.pushPublicKey && options.pushAuthKey) {
      body.pushPublicKey = options.pushPublicKey;
      body.pushAuthKey = options.pushAuthKey;
    }

    return this._request(path, "POST", creds, body);
  },

  /**
   * Sends a message to other devices. Must conform with the push payload schema:
   * https://github.com/mozilla/fxa-auth-server/blob/master/docs/pushpayloads.schema.json
   *
   * @method notifyDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  deviceIds
   *         Devices to send the message to. If null, will be sent to all devices.
   * @param  excludedIds
   *         Devices to exclude when sending to all devices (deviceIds must be null).
   * @param  payload
   *         Data to send with the message
   * @return Promise
   *         Resolves to an empty object:
   *         {}
   */
  notifyDevices(sessionTokenHex, deviceIds, excludedIds, payload, TTL = 0) {
    if (deviceIds && excludedIds) {
      throw new Error("You cannot specify excluded devices if deviceIds is set.")
    }
    const body = {
      to: deviceIds || "all",
      payload,
      TTL
    };
    if (excludedIds) {
      body.excluded = excludedIds;
    }
    return this._request("/account/devices/notify", "POST",
      deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
  },

  /**
   * Update the session or name for an existing device
   *
   * @method updateDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  id
   *         Device identifier
   * @param  name
   *         Device name
   * @param  [options]
   *         Extra device options
   * @param  [options.pushCallback]
   *         `pushCallback` push endpoint callback
   * @param  [options.pushPublicKey]
   *         `pushPublicKey` push public key (URLSafe Base64 string)
   * @param  [options.pushAuthKey]
   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
   * @return Promise
   *         Resolves to an object:
   *         {
   *           id: Device identifier
   *           name: Device name
   *         }
   */
  updateDevice(sessionTokenHex, id, name, options = {}) {
    let path = "/account/device";

    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
    let body = { id, name };
    if (options.pushCallback) {
      body.pushCallback = options.pushCallback;
    }
    if (options.pushPublicKey && options.pushAuthKey) {
      body.pushPublicKey = options.pushPublicKey;
      body.pushAuthKey = options.pushAuthKey;
    }

    return this._request(path, "POST", creds, body);
  },

  /**
   * Delete a device and its associated session token, signing the user
   * out of the server.
   *
   * @method signOutAndDestroyDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  id
   *         Device identifier
   * @param  [options]
   *         Options object
   * @param  [options.service]
   *         `service` query parameter
   * @return Promise
   *         Resolves to an empty object:
   *         {}
   */
  signOutAndDestroyDevice(sessionTokenHex, id, options = {}) {
    let path = "/account/device/destroy";

    if (options.service) {
      path += "?service=" + encodeURIComponent(options.service);
    }

    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
    let body = { id };

    return this._request(path, "POST", creds, body);
  },

  /**
   * Get a list of currently registered devices
   *
   * @method getDeviceList
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @return Promise
   *         Resolves to an array of objects:
   *         [
   *           {
   *             id: Device id
   *             isCurrentDevice: Boolean indicating whether the item
   *                              represents the current device
   *             name: Device name
   *             type: Device type (mobile|desktop)
   *           },
   *           ...
   *         ]
   */
  getDeviceList(sessionTokenHex) {
    let path = "/account/devices";
    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");

    return this._request(path, "GET", creds, {});
  },

  _clearBackoff() {
      this.backoffError = null;
  },

  /**
   * A general method for sending raw API calls to the FxA auth server.
   * All request bodies and responses are JSON.
   *
   * @param path
   *        API endpoint path
   * @param method
   *        The HTTP request method
   * @param credentials
   *        Hawk credentials
   * @param jsonPayload
   *        A JSON payload
   * @return Promise
   *        Returns a promise that resolves to the JSON response of the API call,
   *        or is rejected with an error. Error responses have the following properties:
   *        {
   *          "code": 400, // matches the HTTP status code
   *          "errno": 107, // stable application-level error number
   *          "error": "Bad Request", // string description of the error type
   *          "message": "the value of salt is not allowed to be undefined",
   *          "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
   *        }
   */
  async _request(path, method, credentials, jsonPayload) {
    // We were asked to back off.
    if (this.backoffError) {
      log.debug("Received new request during backoff, re-rejecting.");
      throw this.backoffError;
    }
    let response;
    try {
      response = await this.hawk.request(path, method, credentials, jsonPayload);
    } catch (error) {
      log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
      if (error.retryAfter) {
        log.debug("Received backoff response; caching error as flag.");
        this.backoffError = error;
        // Schedule clearing of cached-error-as-flag.
        CommonUtils.namedTimer(
          this._clearBackoff,
          error.retryAfter * 1000,
          this,
          "fxaBackoffTimer"
         );
      }
      throw error;
    }
    try {
      return JSON.parse(response.body);
    } catch (error) {
      log.error("json parse error on response: " + response.body);
      // eslint-disable-next-line no-throw-literal
      throw {error};
    }
  },
};

function isInvalidTokenError(error) {
  if (error.code != 401) {
    return false;
  }
  switch (error.errno) {
    case ERRNO_INVALID_AUTH_TOKEN:
    case ERRNO_INVALID_AUTH_TIMESTAMP:
    case ERRNO_INVALID_AUTH_NONCE:
      return true;
  }
  return false;
}
PK
!<D\\modules/FxAccountsCommon.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 { interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");

// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Debug" will be used by
// default.  Note "Debug" is usually appropriate so that when this log is
// included in the Sync file logs we get verbose output.
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
// The level of messages that will be dumped to the console.  If not specified,
// "Error" will be used.
const PREF_LOG_LEVEL_DUMP = "identity.fxaccounts.log.appender.dump";

// A pref that can be set so "sensitive" information (eg, personally
// identifiable info, credentials, etc) will be logged.
const PREF_LOG_SENSITIVE_DETAILS = "identity.fxaccounts.log.sensitive";

var exports = Object.create(null);

XPCOMUtils.defineLazyGetter(exports, "log", function() {
  let log = Log.repository.getLogger("FirefoxAccounts");
  // We set the log level to debug, but the default dump appender is set to
  // the level reflected in the pref.  Other code that consumes FxA may then
  // choose to add another appender at a different level.
  log.level = Log.Level.Debug;
  let appender = new Log.DumpAppender();
  appender.level = Log.Level.Error;

  log.addAppender(appender);
  try {
    // The log itself.
    let level =
      Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
      && Services.prefs.getCharPref(PREF_LOG_LEVEL);
    log.level = Log.Level[level] || Log.Level.Debug;

    // The appender.
    level =
      Services.prefs.getPrefType(PREF_LOG_LEVEL_DUMP) == Ci.nsIPrefBranch.PREF_STRING
      && Services.prefs.getCharPref(PREF_LOG_LEVEL_DUMP);
    appender.level = Log.Level[level] || Log.Level.Error;
  } catch (e) {
    log.error(e);
  }

  return log;
});

// A boolean to indicate if personally identifiable information (or anything
// else sensitive, such as credentials) should be logged.
XPCOMUtils.defineLazyGetter(exports, "logPII", function() {
  try {
    return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
  } catch (_) {
    return false;
  }
});

exports.FXACCOUNTS_PERMISSION = "firefox-accounts";

exports.DATA_FORMAT_VERSION = 1;
exports.DEFAULT_STORAGE_FILENAME = "signedInUser.json";

// Token life times.
// Having this parameter be short has limited security value and can cause
// spurious authentication values if the client's clock is skewed and
// we fail to adjust. See Bug 983256.
exports.ASSERTION_LIFETIME = 1000 * 3600 * 24 * 365 * 25; // 25 years
// This is a time period we want to guarantee that the assertion will be
// valid after we generate it (e.g., the signed cert won't expire in this
// period).
exports.ASSERTION_USE_PERIOD = 1000 * 60 * 5; // 5 minutes
exports.CERT_LIFETIME      = 1000 * 3600 * 6;  // 6 hours
exports.KEY_LIFETIME       = 1000 * 3600 * 12; // 12 hours

// After we start polling for account verification, we stop polling when this
// many milliseconds have elapsed.
exports.POLL_SESSION       = 1000 * 60 * 20;   // 20 minutes

// Observer notifications.
exports.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
exports.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
// Internal to services/fxaccounts only
exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
exports.ON_DEVICE_CONNECTED_NOTIFICATION = "fxaccounts:device_connected";
exports.ON_DEVICE_DISCONNECTED_NOTIFICATION = "fxaccounts:device_disconnected";
exports.ON_PROFILE_UPDATED_NOTIFICATION = "fxaccounts:profile_updated"; // Push
exports.ON_PASSWORD_CHANGED_NOTIFICATION = "fxaccounts:password_changed";
exports.ON_PASSWORD_RESET_NOTIFICATION = "fxaccounts:password_reset";
exports.ON_ACCOUNT_DESTROYED_NOTIFICATION = "fxaccounts:account_destroyed";
exports.ON_COLLECTION_CHANGED_NOTIFICATION = "sync:collection_changed";
exports.ON_VERIFY_LOGIN_NOTIFICATION = "fxaccounts:verify_login";

exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";

exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChannel
exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";

// UI Requests.
exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";

// The OAuth client ID for Firefox Desktop
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";

// Firefox Accounts WebChannel ID
exports.WEBCHANNEL_ID = "account_updates";

// Server errno.
// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
exports.ERRNO_ACCOUNT_ALREADY_EXISTS         = 101;
exports.ERRNO_ACCOUNT_DOES_NOT_EXIST         = 102;
exports.ERRNO_INCORRECT_PASSWORD             = 103;
exports.ERRNO_UNVERIFIED_ACCOUNT             = 104;
exports.ERRNO_INVALID_VERIFICATION_CODE      = 105;
exports.ERRNO_NOT_VALID_JSON_BODY            = 106;
exports.ERRNO_INVALID_BODY_PARAMETERS        = 107;
exports.ERRNO_MISSING_BODY_PARAMETERS        = 108;
exports.ERRNO_INVALID_REQUEST_SIGNATURE      = 109;
exports.ERRNO_INVALID_AUTH_TOKEN             = 110;
exports.ERRNO_INVALID_AUTH_TIMESTAMP         = 111;
exports.ERRNO_MISSING_CONTENT_LENGTH         = 112;
exports.ERRNO_REQUEST_BODY_TOO_LARGE         = 113;
exports.ERRNO_TOO_MANY_CLIENT_REQUESTS       = 114;
exports.ERRNO_INVALID_AUTH_NONCE             = 115;
exports.ERRNO_ENDPOINT_NO_LONGER_SUPPORTED   = 116;
exports.ERRNO_INCORRECT_LOGIN_METHOD         = 117;
exports.ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD = 118;
exports.ERRNO_INCORRECT_API_VERSION          = 119;
exports.ERRNO_INCORRECT_EMAIL_CASE           = 120;
exports.ERRNO_ACCOUNT_LOCKED                 = 121;
exports.ERRNO_ACCOUNT_UNLOCKED               = 122;
exports.ERRNO_UNKNOWN_DEVICE                 = 123;
exports.ERRNO_DEVICE_SESSION_CONFLICT        = 124;
exports.ERRNO_SERVICE_TEMP_UNAVAILABLE       = 201;
exports.ERRNO_PARSE                          = 997;
exports.ERRNO_NETWORK                        = 998;
exports.ERRNO_UNKNOWN_ERROR                  = 999;

// Offset oauth server errnos so they don't conflict with auth server errnos
exports.OAUTH_SERVER_ERRNO_OFFSET = 1000;

// OAuth Server errno.
exports.ERRNO_UNKNOWN_CLIENT_ID              = 101 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INCORRECT_CLIENT_SECRET        = 102 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INCORRECT_REDIRECT_URI         = 103 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INVALID_FXA_ASSERTION          = 104 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_UNKNOWN_CODE                   = 105 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INCORRECT_CODE                 = 106 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_EXPIRED_CODE                   = 107 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_OAUTH_INVALID_TOKEN            = 108 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INVALID_REQUEST_PARAM          = 109 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INVALID_RESPONSE_TYPE          = 110 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_UNAUTHORIZED                   = 111 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_FORBIDDEN                      = 112 + exports.OAUTH_SERVER_ERRNO_OFFSET;
exports.ERRNO_INVALID_CONTENT_TYPE           = 113 + exports.OAUTH_SERVER_ERRNO_OFFSET;

// Errors.
exports.ERROR_ACCOUNT_ALREADY_EXISTS         = "ACCOUNT_ALREADY_EXISTS";
exports.ERROR_ACCOUNT_DOES_NOT_EXIST         = "ACCOUNT_DOES_NOT_EXIST ";
exports.ERROR_ACCOUNT_LOCKED                 = "ACCOUNT_LOCKED";
exports.ERROR_ACCOUNT_UNLOCKED               = "ACCOUNT_UNLOCKED";
exports.ERROR_ALREADY_SIGNED_IN_USER         = "ALREADY_SIGNED_IN_USER";
exports.ERROR_DEVICE_SESSION_CONFLICT        = "DEVICE_SESSION_CONFLICT";
exports.ERROR_ENDPOINT_NO_LONGER_SUPPORTED   = "ENDPOINT_NO_LONGER_SUPPORTED";
exports.ERROR_INCORRECT_API_VERSION          = "INCORRECT_API_VERSION";
exports.ERROR_INCORRECT_EMAIL_CASE           = "INCORRECT_EMAIL_CASE";
exports.ERROR_INCORRECT_KEY_RETRIEVAL_METHOD = "INCORRECT_KEY_RETRIEVAL_METHOD";
exports.ERROR_INCORRECT_LOGIN_METHOD         = "INCORRECT_LOGIN_METHOD";
exports.ERROR_INVALID_EMAIL                  = "INVALID_EMAIL";
exports.ERROR_INVALID_AUDIENCE               = "INVALID_AUDIENCE";
exports.ERROR_INVALID_AUTH_TOKEN             = "INVALID_AUTH_TOKEN";
exports.ERROR_INVALID_AUTH_TIMESTAMP         = "INVALID_AUTH_TIMESTAMP";
exports.ERROR_INVALID_AUTH_NONCE             = "INVALID_AUTH_NONCE";
exports.ERROR_INVALID_BODY_PARAMETERS        = "INVALID_BODY_PARAMETERS";
exports.ERROR_INVALID_PASSWORD               = "INVALID_PASSWORD";
exports.ERROR_INVALID_VERIFICATION_CODE      = "INVALID_VERIFICATION_CODE";
exports.ERROR_INVALID_REFRESH_AUTH_VALUE     = "INVALID_REFRESH_AUTH_VALUE";
exports.ERROR_INVALID_REQUEST_SIGNATURE      = "INVALID_REQUEST_SIGNATURE";
exports.ERROR_INTERNAL_INVALID_USER          = "INTERNAL_ERROR_INVALID_USER";
exports.ERROR_MISSING_BODY_PARAMETERS        = "MISSING_BODY_PARAMETERS";
exports.ERROR_MISSING_CONTENT_LENGTH         = "MISSING_CONTENT_LENGTH";
exports.ERROR_NO_TOKEN_SESSION               = "NO_TOKEN_SESSION";
exports.ERROR_NO_SILENT_REFRESH_AUTH         = "NO_SILENT_REFRESH_AUTH";
exports.ERROR_NOT_VALID_JSON_BODY            = "NOT_VALID_JSON_BODY";
exports.ERROR_OFFLINE                        = "OFFLINE";
exports.ERROR_PERMISSION_DENIED              = "PERMISSION_DENIED";
exports.ERROR_REQUEST_BODY_TOO_LARGE         = "REQUEST_BODY_TOO_LARGE";
exports.ERROR_SERVER_ERROR                   = "SERVER_ERROR";
exports.ERROR_SYNC_DISABLED                  = "SYNC_DISABLED";
exports.ERROR_TOO_MANY_CLIENT_REQUESTS       = "TOO_MANY_CLIENT_REQUESTS";
exports.ERROR_SERVICE_TEMP_UNAVAILABLE       = "SERVICE_TEMPORARY_UNAVAILABLE";
exports.ERROR_UI_ERROR                       = "UI_ERROR";
exports.ERROR_UI_REQUEST                     = "UI_REQUEST";
exports.ERROR_PARSE                          = "PARSE_ERROR";
exports.ERROR_NETWORK                        = "NETWORK_ERROR";
exports.ERROR_UNKNOWN                        = "UNKNOWN_ERROR";
exports.ERROR_UNKNOWN_DEVICE                 = "UNKNOWN_DEVICE";
exports.ERROR_UNVERIFIED_ACCOUNT             = "UNVERIFIED_ACCOUNT";

// OAuth errors.
exports.ERROR_UNKNOWN_CLIENT_ID              = "UNKNOWN_CLIENT_ID";
exports.ERROR_INCORRECT_CLIENT_SECRET        = "INCORRECT_CLIENT_SECRET";
exports.ERROR_INCORRECT_REDIRECT_URI         = "INCORRECT_REDIRECT_URI";
exports.ERROR_INVALID_FXA_ASSERTION          = "INVALID_FXA_ASSERTION";
exports.ERROR_UNKNOWN_CODE                   = "UNKNOWN_CODE";
exports.ERROR_INCORRECT_CODE                 = "INCORRECT_CODE";
exports.ERROR_EXPIRED_CODE                   = "EXPIRED_CODE";
exports.ERROR_OAUTH_INVALID_TOKEN            = "OAUTH_INVALID_TOKEN";
exports.ERROR_INVALID_REQUEST_PARAM          = "INVALID_REQUEST_PARAM";
exports.ERROR_INVALID_RESPONSE_TYPE          = "INVALID_RESPONSE_TYPE";
exports.ERROR_UNAUTHORIZED                   = "UNAUTHORIZED";
exports.ERROR_FORBIDDEN                      = "FORBIDDEN";
exports.ERROR_INVALID_CONTENT_TYPE           = "INVALID_CONTENT_TYPE";

// Additional generic error classes for external consumers
exports.ERROR_NO_ACCOUNT                     = "NO_ACCOUNT";
exports.ERROR_AUTH_ERROR                     = "AUTH_ERROR";
exports.ERROR_INVALID_PARAMETER              = "INVALID_PARAMETER";

// Status code errors
exports.ERROR_CODE_METHOD_NOT_ALLOWED        = 405;
exports.ERROR_MSG_METHOD_NOT_ALLOWED         = "METHOD_NOT_ALLOWED";

// FxAccounts has the ability to "split" the credentials between a plain-text
// JSON file in the profile dir and in the login manager.
// In order to prevent new fields accidentally ending up in the "wrong" place,
// all fields stored are listed here.

// The fields we save in the plaintext JSON.
// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
exports.FXA_PWDMGR_PLAINTEXT_FIELDS = new Set(
  ["email", "verified", "authAt", "sessionToken", "uid", "oauthTokens", "profile",
  "deviceId", "deviceRegistrationVersion", "profileCache"]);

// Fields we store in secure storage if it exists.
exports.FXA_PWDMGR_SECURE_FIELDS = new Set(
  ["kA", "kB", "keyFetchToken", "unwrapBKey", "assertion"]);

// Fields we keep in memory and don't persist anywhere.
exports.FXA_PWDMGR_MEMORY_FIELDS = new Set(
  ["cert", "keyPair"]);

// A whitelist of fields that remain in storage when the user needs to
// reauthenticate. All other fields will be removed.
exports.FXA_PWDMGR_REAUTH_WHITELIST = new Set(
  ["email", "uid", "profile", "deviceId", "deviceRegistrationVersion", "verified"]);

// The pseudo-host we use in the login manager
exports.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
// The realm we use in the login manager.
exports.FXA_PWDMGR_REALM = "Firefox Accounts credentials";

// Error matching.
exports.SERVER_ERRNO_TO_ERROR = {};

// Error mapping
exports.ERROR_TO_GENERAL_ERROR_CLASS = {};

for (let id in exports) {
  this[id] = exports[id];
}

// Allow this file to be imported via Components.utils.import().
this.EXPORTED_SYMBOLS = Object.keys(exports);

// Set these up now that everything has been loaded into |this|.
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_ACCOUNT_ALREADY_EXISTS]         = exports.ERROR_ACCOUNT_ALREADY_EXISTS;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_ACCOUNT_DOES_NOT_EXIST]         = exports.ERROR_ACCOUNT_DOES_NOT_EXIST;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_PASSWORD]             = exports.ERROR_INVALID_PASSWORD;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNVERIFIED_ACCOUNT]             = exports.ERROR_UNVERIFIED_ACCOUNT;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_VERIFICATION_CODE]      = exports.ERROR_INVALID_VERIFICATION_CODE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_NOT_VALID_JSON_BODY]            = exports.ERROR_NOT_VALID_JSON_BODY;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_BODY_PARAMETERS]        = exports.ERROR_INVALID_BODY_PARAMETERS;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_MISSING_BODY_PARAMETERS]        = exports.ERROR_MISSING_BODY_PARAMETERS;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_REQUEST_SIGNATURE]      = exports.ERROR_INVALID_REQUEST_SIGNATURE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_AUTH_TOKEN]             = exports.ERROR_INVALID_AUTH_TOKEN;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_AUTH_TIMESTAMP]         = exports.ERROR_INVALID_AUTH_TIMESTAMP;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_MISSING_CONTENT_LENGTH]         = exports.ERROR_MISSING_CONTENT_LENGTH;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_REQUEST_BODY_TOO_LARGE]         = exports.ERROR_REQUEST_BODY_TOO_LARGE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_TOO_MANY_CLIENT_REQUESTS]       = exports.ERROR_TOO_MANY_CLIENT_REQUESTS;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_AUTH_NONCE]             = exports.ERROR_INVALID_AUTH_NONCE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_ENDPOINT_NO_LONGER_SUPPORTED]   = exports.ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_LOGIN_METHOD]         = exports.ERROR_INCORRECT_LOGIN_METHOD;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = exports.ERROR_INCORRECT_KEY_RETRIEVAL_METHOD;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_API_VERSION]          = exports.ERROR_INCORRECT_API_VERSION;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_EMAIL_CASE]           = exports.ERROR_INCORRECT_EMAIL_CASE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_ACCOUNT_LOCKED]                 = exports.ERROR_ACCOUNT_LOCKED;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_ACCOUNT_UNLOCKED]               = exports.ERROR_ACCOUNT_UNLOCKED;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNKNOWN_DEVICE]                 = exports.ERROR_UNKNOWN_DEVICE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_DEVICE_SESSION_CONFLICT]        = exports.ERROR_DEVICE_SESSION_CONFLICT;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_SERVICE_TEMP_UNAVAILABLE]       = exports.ERROR_SERVICE_TEMP_UNAVAILABLE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNKNOWN_ERROR]                  = exports.ERROR_UNKNOWN;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_NETWORK]                        = exports.ERROR_NETWORK;

// oauth
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNKNOWN_CLIENT_ID]              = exports.ERROR_UNKNOWN_CLIENT_ID;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_CLIENT_SECRET]        = exports.ERROR_INCORRECT_CLIENT_SECRET;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_REDIRECT_URI]         = exports.ERROR_INCORRECT_REDIRECT_URI;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_FXA_ASSERTION]          = exports.ERROR_INVALID_FXA_ASSERTION;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNKNOWN_CODE]                   = exports.ERROR_UNKNOWN_CODE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INCORRECT_CODE]                 = exports.ERROR_INCORRECT_CODE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_EXPIRED_CODE]                   = exports.ERROR_EXPIRED_CODE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_OAUTH_INVALID_TOKEN]            = exports.ERROR_OAUTH_INVALID_TOKEN;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_REQUEST_PARAM]          = exports.ERROR_INVALID_REQUEST_PARAM;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_RESPONSE_TYPE]          = exports.ERROR_INVALID_RESPONSE_TYPE;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_UNAUTHORIZED]                   = exports.ERROR_UNAUTHORIZED;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_FORBIDDEN]                      = exports.ERROR_FORBIDDEN;
exports.SERVER_ERRNO_TO_ERROR[exports.ERRNO_INVALID_CONTENT_TYPE]           = exports.ERROR_INVALID_CONTENT_TYPE;


// Map internal errors to more generic error classes for consumers
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ACCOUNT_ALREADY_EXISTS]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ACCOUNT_DOES_NOT_EXIST]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ACCOUNT_LOCKED]                 = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ACCOUNT_UNLOCKED]               = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ALREADY_SIGNED_IN_USER]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_DEVICE_SESSION_CONFLICT]        = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_ENDPOINT_NO_LONGER_SUPPORTED]   = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_API_VERSION]          = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_EMAIL_CASE]           = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_KEY_RETRIEVAL_METHOD] = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_LOGIN_METHOD]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_EMAIL]                  = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_AUDIENCE]               = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_AUTH_TOKEN]             = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_AUTH_TIMESTAMP]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_AUTH_NONCE]             = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_BODY_PARAMETERS]        = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_PASSWORD]               = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_VERIFICATION_CODE]      = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_REFRESH_AUTH_VALUE]     = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_REQUEST_SIGNATURE]      = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INTERNAL_INVALID_USER]          = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_MISSING_BODY_PARAMETERS]        = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_MISSING_CONTENT_LENGTH]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_NO_TOKEN_SESSION]               = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_NO_SILENT_REFRESH_AUTH]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_NOT_VALID_JSON_BODY]            = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_PERMISSION_DENIED]              = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_REQUEST_BODY_TOO_LARGE]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UNKNOWN_DEVICE]                 = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UNVERIFIED_ACCOUNT]             = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UI_ERROR]                       = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UI_REQUEST]                     = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_OFFLINE]                        = exports.ERROR_NETWORK;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_SERVER_ERROR]                   = exports.ERROR_NETWORK;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_TOO_MANY_CLIENT_REQUESTS]       = exports.ERROR_NETWORK;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_SERVICE_TEMP_UNAVAILABLE]       = exports.ERROR_NETWORK;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_PARSE]                          = exports.ERROR_NETWORK;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_NETWORK]                        = exports.ERROR_NETWORK;

// oauth
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_CLIENT_SECRET]        = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_REDIRECT_URI]         = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_FXA_ASSERTION]          = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UNKNOWN_CODE]                   = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INCORRECT_CODE]                 = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_EXPIRED_CODE]                   = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_OAUTH_INVALID_TOKEN]            = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_REQUEST_PARAM]          = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_RESPONSE_TYPE]          = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_UNAUTHORIZED]                   = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_FORBIDDEN]                      = exports.ERROR_AUTH_ERROR;
exports.ERROR_TO_GENERAL_ERROR_CLASS[exports.ERROR_INVALID_CONTENT_TYPE]           = exports.ERROR_AUTH_ERROR;
PK
!<7oomodules/FxAccountsConfig.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["FxAccountsConfig"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://services-common/rest.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
                                  "resource://gre/modules/FxAccountsWebChannel.jsm");

const CONFIG_PREFS = [
  "identity.fxaccounts.auth.uri",
  "identity.fxaccounts.remote.oauth.uri",
  "identity.fxaccounts.remote.profile.uri",
  "identity.sync.tokenserver.uri",
  "identity.fxaccounts.remote.webchannel.uri",
  "identity.fxaccounts.settings.uri",
  "identity.fxaccounts.settings.devices.uri",
  "identity.fxaccounts.remote.signup.uri",
  "identity.fxaccounts.remote.signin.uri",
  "identity.fxaccounts.remote.force_auth.uri",
];

this.FxAccountsConfig = {

  // Returns a promise that resolves with the URI of the remote UI flows.
  async promiseAccountsSignUpURI() {
    await this.ensureConfigured();
    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    return url;
  },

  // Returns a promise that resolves with the URI of the remote UI flows.
  async promiseAccountsSignInURI() {
    await this.ensureConfigured();
    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    return url;
  },

  resetConfigURLs() {
    let autoconfigURL = this.getAutoConfigURL();
    if (!autoconfigURL) {
      return;
    }
    // They have the autoconfig uri pref set, so we clear all the prefs that we
    // will have initialized, which will leave them pointing at production.
    for (let pref of CONFIG_PREFS) {
      Services.prefs.clearUserPref(pref);
    }
    // Reset the webchannel.
    EnsureFxAccountsWebChannel();
    if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
      return;
    }
    let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
    if (whitelistValue.startsWith(autoconfigURL + " ")) {
      whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
      // Check and see if the value will be the default, and just clear the pref if it would
      // to avoid it showing up as changed in about:config.
      let defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist", "");

      if (defaultWhitelist === whitelistValue) {
        Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
      } else {
        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
      }
    }
  },

  getAutoConfigURL() {
    let pref = Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri", "");
    if (!pref) {
      // no pref / empty pref means we don't bother here.
      return "";
    }
    let rootURL = Services.urlFormatter.formatURL(pref);
    if (rootURL.endsWith("/")) {
      rootURL.slice(0, -1);
    }
    return rootURL;
  },

  async ensureConfigured() {
    let isSignedIn = !!(await fxAccounts.getSignedInUser());
    if (!isSignedIn) {
      await this.fetchConfigURLs();
    }
  },

  // Read expected client configuration from the fxa auth server
  // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
  // and replace all the relevant our prefs with the information found there.
  // This is only done before sign-in and sign-up, and even then only if the
  // `identity.fxaccounts.autoconfig.uri` preference is set.
  async fetchConfigURLs() {
    let rootURL = this.getAutoConfigURL();
    if (!rootURL) {
      return;
    }
    let configURL = rootURL + "/.well-known/fxa-client-configuration";
    let jsonStr = await new Promise((resolve, reject) => {
      let request = new RESTRequest(configURL);
      request.setHeader("Accept", "application/json");
      request.get(error => {
        if (error) {
          log.error(`Failed to get configuration object from "${configURL}"`, error);
          reject(error);
          return;
        }
        if (!request.response.success) {
          log.error(`Received HTTP response code ${request.response.status} from configuration object request`);
          if (request.response && request.response.body) {
            log.debug("Got error response", request.response.body);
          }
          reject(request.response.status);
          return;
        }
        resolve(request.response.body);
      });
    });

    log.debug("Got successful configuration response", jsonStr);
    try {
      // Update the prefs directly specified by the config.
      let config = JSON.parse(jsonStr)
      let authServerBase = config.auth_server_base_url;
      if (!authServerBase.endsWith("/v1")) {
        authServerBase += "/v1";
      }
      Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
      Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
      Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
      Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
      // Update the prefs that are based off of the autoconfig url

      let contextParam = encodeURIComponent(
        Services.prefs.getCharPref("identity.fxaccounts.contextParam"));

      Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
      Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.settings.devices.uri", rootURL + "/settings/clients?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
      Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);

      let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
      if (!whitelistValue.includes(rootURL)) {
        whitelistValue = `${rootURL} ${whitelistValue}`;
        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
      }
      // Ensure the webchannel is pointed at the correct uri
      EnsureFxAccountsWebChannel();
    } catch (e) {
      log.error("Failed to initialize configuration preferences from autoconfig object", e);
      throw e;
    }
  },

};
PK
!<mۘ77&modules/FxAccountsOAuthGrantClient.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Firefox Accounts OAuth Grant Client allows clients to obtain
 * an OAuth token from a BrowserID assertion. Only certain client
 * IDs support this privilage.
 */

this.EXPORTED_SYMBOLS = ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-common/rest.js");

Cu.importGlobalProperties(["URL"]);

const AUTH_ENDPOINT = "/authorization";
const DESTROY_ENDPOINT = "/destroy";

/**
 * Create a new FxAccountsOAuthClient for browser some service.
 *
 * @param {Object} options Options
 *   @param {Object} options.parameters
 *     @param {String} options.parameters.client_id
 *     OAuth id returned from client registration
 *     @param {String} options.parameters.serverURL
 *     The FxA OAuth server URL
 *   @param [authorizationEndpoint] {String}
 *   Optional authorization endpoint for the OAuth server
 * @constructor
 */
this.FxAccountsOAuthGrantClient = function(options) {

  this._validateOptions(options);
  this.parameters = options;

  try {
    this.serverURL = new URL(this.parameters.serverURL);
  } catch (e) {
    throw new Error("Invalid 'serverURL'");
  }

  log.debug("FxAccountsOAuthGrantClient Initialized");
};

this.FxAccountsOAuthGrantClient.prototype = {

  /**
   * Retrieves an OAuth access token for the signed in user
   *
   * @param {Object} assertion BrowserID assertion
   * @param {String} scope OAuth scope
   * @return Promise
   *        Resolves: {Object} Object with access_token property
   */
  getTokenFromAssertion(assertion, scope) {
    if (!assertion) {
      throw new Error("Missing 'assertion' parameter");
    }
    if (!scope) {
      throw new Error("Missing 'scope' parameter");
    }
    let params = {
      scope,
      client_id: this.parameters.client_id,
      assertion,
      response_type: "token"
    };

    return this._createRequest(AUTH_ENDPOINT, "POST", params);
  },

  /**
   * Destroys a previously fetched OAuth access token.
   *
   * @param {String} token The previously fetched token
   * @return Promise
   *        Resolves: {Object} with the server response, which is typically
   *        ignored.
   */
  destroyToken(token) {
    if (!token) {
      throw new Error("Missing 'token' parameter");
    }
    let params = {
      token,
    };

    return this._createRequest(DESTROY_ENDPOINT, "POST", params);
  },

  /**
   * Validates the required FxA OAuth parameters
   *
   * @param options {Object}
   *        OAuth client options
   * @private
   */
  _validateOptions(options) {
    if (!options) {
      throw new Error("Missing configuration options");
    }

    ["serverURL", "client_id"].forEach(option => {
      if (!options[option]) {
        throw new Error("Missing '" + option + "' parameter");
      }
    });
  },

  /**
   * Interface for making remote requests.
   */
  _Request: RESTRequest,

  /**
   * Remote request helper
   *
   * @param {String} path
   *        Profile server path, i.e "/profile".
   * @param {String} [method]
   *        Type of request, i.e "GET".
   * @return Promise
   *         Resolves: {Object} Successful response from the Profile server.
   *         Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
   * @private
   */
  _createRequest(path, method = "POST", params) {
    return new Promise((resolve, reject) => {
      let profileDataUrl = this.serverURL + path;
      let request = new this._Request(profileDataUrl);
      method = method.toUpperCase();

      request.setHeader("Accept", "application/json");
      request.setHeader("Content-Type", "application/json");

      request.onComplete = function(error) {
        if (error) {
          reject(new FxAccountsOAuthGrantClientError({
            error: ERROR_NETWORK,
            errno: ERRNO_NETWORK,
            message: error.toString(),
          }));
          return;
        }

        let body = null;
        try {
          body = JSON.parse(request.response.body);
        } catch (e) {
          reject(new FxAccountsOAuthGrantClientError({
            error: ERROR_PARSE,
            errno: ERRNO_PARSE,
            code: request.response.status,
            message: request.response.body,
          }));
          return;
        }

        // "response.success" means status code is 200
        if (request.response.success) {
          resolve(body);
          return;
        }

        if (typeof body.errno === "number") {
          // Offset oauth server errnos to avoid conflict with other FxA server errnos
          body.errno += OAUTH_SERVER_ERRNO_OFFSET;
        } else if (body.errno) {
          body.errno = ERRNO_UNKNOWN_ERROR;
        }
        reject(new FxAccountsOAuthGrantClientError(body));
      };

      if (method === "POST") {
        request.post(params);
      } else {
        // method not supported
        reject(new FxAccountsOAuthGrantClientError({
          error: ERROR_NETWORK,
          errno: ERRNO_NETWORK,
          code: ERROR_CODE_METHOD_NOT_ALLOWED,
          message: ERROR_MSG_METHOD_NOT_ALLOWED,
        }));
      }
    });
  },

};

/**
 * Normalized profile client errors
 * @param {Object} [details]
 *        Error details object
 *   @param {number} [details.code]
 *          Error code
 *   @param {number} [details.errno]
 *          Error number
 *   @param {String} [details.error]
 *          Error description
 *   @param {String|null} [details.message]
 *          Error message
 * @constructor
 */
this.FxAccountsOAuthGrantClientError = function(details) {
  details = details || {};

  this.name = "FxAccountsOAuthGrantClientError";
  this.code = details.code || null;
  this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
  this.error = details.error || ERROR_UNKNOWN;
  this.message = details.message || null;
};

/**
 * Returns error object properties
 *
 * @returns {{name: *, code: *, errno: *, error: *, message: *}}
 * @private
 */
FxAccountsOAuthGrantClientError.prototype._toStringFields = function() {
  return {
    name: this.name,
    code: this.code,
    errno: this.errno,
    error: this.error,
    message: this.message,
  };
};

/**
 * String representation of a oauth grant client error
 *
 * @returns {String}
 */
FxAccountsOAuthGrantClientError.prototype.toString = function() {
  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
};
PK
!<X}uumodules/FxAccountsProfile.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Firefox Accounts Profile helper.
 *
 * This class abstracts interaction with the profile server for an account.
 * It will handle things like fetching profile data, listening for updates to
 * the user's profile in open browser tabs, and cacheing/invalidating profile data.
 */

this.EXPORTED_SYMBOLS = ["FxAccountsProfile"];

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/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccounts.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileClient",
  "resource://gre/modules/FxAccountsProfileClient.jsm");

this.FxAccountsProfile = function(options = {}) {
  this._currentFetchPromise = null;
  this._cachedAt = 0; // when we saved the cached version.
  this._isNotifying = false; // are we sending a notification?
  this.fxa = options.fxa || fxAccounts;
  this.client = options.profileClient || new FxAccountsProfileClient({
    fxa: this.fxa,
    serverURL: options.profileServerUrl,
  });

  // An observer to invalidate our _cachedAt optimization. We use a weak-ref
  // just incase this.tearDown isn't called in some cases.
  Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
  // for testing
  if (options.channel) {
    this.channel = options.channel;
  }
}

this.FxAccountsProfile.prototype = {
  // If we get subsequent requests for a profile within this period, don't bother
  // making another request to determine if it is fresh or not.
  PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes

  observe(subject, topic, data) {
    // If we get a profile change notification from our webchannel it means
    // the user has just changed their profile via the web, so we want to
    // ignore our "freshness threshold"
    if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
      log.debug("FxAccountsProfile observed profile change");
      this._cachedAt = 0;
    }
  },

  tearDown() {
    this.fxa = null;
    this.client = null;
    Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
  },

  _notifyProfileChange(uid) {
    this._isNotifying = true;
    Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
    this._isNotifying = false;
  },

  // Cache fetched data and send out a notification so that UI can update.
  _cacheProfile(response) {
    let profileCache = {
      profile: response.body,
      etag: response.etag
    };

    return this.fxa.setProfileCache(profileCache)
      .then(() => {
        return this.fxa.getSignedInUser();
      })
      .then(userData => {
        log.debug("notifying profile changed for user ${uid}", userData);
        this._notifyProfileChange(userData.uid);
        return response.body;
      });
  },

  _fetchAndCacheProfileInternal() {
    let onFinally = () => {
      this._cachedAt = Date.now();
      this._currentFetchPromise = null;
    }
    return this.fxa.getProfileCache()
      .then(profileCache => {
        const etag = profileCache ? profileCache.etag : null;
        return this.client.fetchProfile(etag);
      })
      .then(response => {
        // response may be null if the profile was not modified (same ETag).
        return response ? this._cacheProfile(response) : null;
      })
      .then(body => { // finally block
        onFinally();
        // body may be null if the profile was not modified
        return body;
      }, err => {
        onFinally();
        throw err;
      });
  },

  _fetchAndCacheProfile() {
    if (!this._currentFetchPromise) {
      this._currentFetchPromise = this._fetchAndCacheProfileInternal();
    }
    return this._currentFetchPromise;
  },

  // Returns cached data right away if available, then fetches the latest profile
  // data in the background. After data is fetched a notification will be sent
  // out if the profile has changed.
  getProfile() {
    return this.fxa.getProfileCache()
      .then(profileCache => {
        if (profileCache) {
          if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
            // Note that _fetchAndCacheProfile isn't returned, so continues
            // in the background.
            this._fetchAndCacheProfile().catch(err => {
              log.error("Background refresh of profile failed", err);
            });
          } else {
            log.trace("not checking freshness of profile as it remains recent");
          }
          return profileCache.profile;
        }
        return this._fetchAndCacheProfile();
      });
  },

  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsIObserver,
      Ci.nsISupportsWeakReference,
  ]),
};
PK
!<41[  #modules/FxAccountsProfileClient.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 client to fetch profile information for a Firefox Account.
 */
 "use strict;"

this.EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientError"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://services-common/rest.js");

Cu.importGlobalProperties(["URL"]);

/**
 * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information.
 *
 * @param {Object} options Options
 *   @param {String} options.serverURL
 *   The URL of the profile server to query.
 *   Example: https://profile.accounts.firefox.com/v1
 *   @param {String} options.token
 *   The bearer token to access the profile server
 * @constructor
 */
this.FxAccountsProfileClient = function(options) {
  if (!options || !options.serverURL) {
    throw new Error("Missing 'serverURL' configuration option");
  }

  this.fxa = options.fxa || fxAccounts;
  // This is a work-around for loop that manages its own oauth tokens.
  // * If |token| is in options we use it and don't attempt any token refresh
  //  on 401. This is for loop.
  // * If |token| doesn't exist we will fetch our own token. This is for the
  //   normal FxAccounts methods for obtaining the profile.
  // We should nuke all |this.token| support once loop moves closer to FxAccounts.
  this.token = options.token;

  try {
    this.serverURL = new URL(options.serverURL);
  } catch (e) {
    throw new Error("Invalid 'serverURL'");
  }
  this.oauthOptions = {
    scope: "profile",
  };
  log.debug("FxAccountsProfileClient: Initialized");
};

this.FxAccountsProfileClient.prototype = {
  /**
   * {nsIURI}
   * The server to fetch profile information from.
   */
  serverURL: null,

  /**
   * Interface for making remote requests.
   */
  _Request: RESTRequest,

  /**
   * Remote request helper which abstracts authentication away.
   *
   * @param {String} path
   *        Profile server path, i.e "/profile".
   * @param {String} [method]
   *        Type of request, i.e "GET".
   * @param {String} [etag]
   *        Optional ETag used for caching purposes.
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server.
   *         Rejects: {FxAccountsProfileClientError} Profile client error.
   * @private
   */
  async _createRequest(path, method = "GET", etag = null) {
    let token = this.token;
    if (!token) {
      // tokens are cached, so getting them each request is cheap.
      token = await this.fxa.getOAuthToken(this.oauthOptions);
    }
    try {
      return (await this._rawRequest(path, method, token, etag));
    } catch (ex) {
      if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
        throw ex;
      }
      // If this object was instantiated with a token then we don't refresh it.
      if (this.token) {
        throw ex;
      }
      // it's an auth error - assume our token expired and retry.
      log.info("Fetching the profile returned a 401 - revoking our token and retrying");
      await this.fxa.removeCachedOAuthToken({token});
      token = await this.fxa.getOAuthToken(this.oauthOptions);
      // and try with the new token - if that also fails then we fail after
      // revoking the token.
      try {
        return (await this._rawRequest(path, method, token, etag));
      } catch (ex) {
        if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
          throw ex;
        }
        log.info("Retry fetching the profile still returned a 401 - revoking our token and failing");
        await this.fxa.removeCachedOAuthToken({token});
        throw ex;
      }
    }
  },

  /**
   * Remote "raw" request helper - doesn't handle auth errors and tokens.
   *
   * @param {String} path
   *        Profile server path, i.e "/profile".
   * @param {String} method
   *        Type of request, i.e "GET".
   * @param {String} token
   * @param {String} etag
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server
                        or null if 304 is hit (same ETag).
   *         Rejects: {FxAccountsProfileClientError} Profile client error.
   * @private
   */
  _rawRequest(path, method, token, etag) {
    return new Promise((resolve, reject) => {
      let profileDataUrl = this.serverURL + path;
      let request = new this._Request(profileDataUrl);
      method = method.toUpperCase();

      request.setHeader("Authorization", "Bearer " + token);
      request.setHeader("Accept", "application/json");
      if (etag) {
        request.setHeader("If-None-Match", etag);
      }

      request.onComplete = function(error) {
        if (error) {
          reject(new FxAccountsProfileClientError({
            error: ERROR_NETWORK,
            errno: ERRNO_NETWORK,
            message: error.toString(),
          }));
          return;
        }

        let body = null;
        try {
          if (request.response.status == 304) {
            resolve(null);
            return;
          }
          body = JSON.parse(request.response.body);
        } catch (e) {
          reject(new FxAccountsProfileClientError({
            error: ERROR_PARSE,
            errno: ERRNO_PARSE,
            code: request.response.status,
            message: request.response.body,
          }));
          return;
        }

        // "response.success" means status code is 200
        if (request.response.success) {
          resolve({
            body,
            etag: request.response.headers["etag"]
          });
          return;
        }
        reject(new FxAccountsProfileClientError({
          error: body.error || ERROR_UNKNOWN,
          errno: body.errno || ERRNO_UNKNOWN_ERROR,
          code: request.response.status,
          message: body.message || body,
        }));
      };

      if (method === "GET") {
        request.get();
      } else {
        // method not supported
        reject(new FxAccountsProfileClientError({
          error: ERROR_NETWORK,
          errno: ERRNO_NETWORK,
          code: ERROR_CODE_METHOD_NOT_ALLOWED,
          message: ERROR_MSG_METHOD_NOT_ALLOWED,
        }));
      }
    });
  },

  /**
   * Retrieve user's profile from the server
   *
   * @param {String} [etag]
   *        Optional ETag used for caching purposes. (may generate a 304 exception)
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the '/profile' endpoint.
   *         Rejects: {FxAccountsProfileClientError} profile client error.
   */
  fetchProfile(etag) {
    log.debug("FxAccountsProfileClient: Requested profile");
    return this._createRequest("/profile", "GET", etag);
  }
};

/**
 * Normalized profile client errors
 * @param {Object} [details]
 *        Error details object
 *   @param {number} [details.code]
 *          Error code
 *   @param {number} [details.errno]
 *          Error number
 *   @param {String} [details.error]
 *          Error description
 *   @param {String|null} [details.message]
 *          Error message
 * @constructor
 */
this.FxAccountsProfileClientError = function(details) {
  details = details || {};

  this.name = "FxAccountsProfileClientError";
  this.code = details.code || null;
  this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
  this.error = details.error || ERROR_UNKNOWN;
  this.message = details.message || null;
};

/**
 * Returns error object properties
 *
 * @returns {{name: *, code: *, errno: *, error: *, message: *}}
 * @private
 */
FxAccountsProfileClientError.prototype._toStringFields = function() {
  return {
    name: this.name,
    code: this.code,
    errno: this.errno,
    error: this.error,
    message: this.message,
  };
};

/**
 * String representation of a profile client error
 *
 * @returns {String}
 */
FxAccountsProfileClientError.prototype.toString = function() {
  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
};
PK
!<[!]!]modules/FxAccountsStorage.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "FxAccountsStorageManagerCanStoreField",
  "FxAccountsStorageManager",
];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://services-common/utils.js");

// A helper function so code can check what fields are able to be stored by
// the storage manager without having a reference to a manager instance.
function FxAccountsStorageManagerCanStoreField(fieldName) {
  return FXA_PWDMGR_MEMORY_FIELDS.has(fieldName) ||
         FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName) ||
         FXA_PWDMGR_SECURE_FIELDS.has(fieldName);
}

// The storage manager object.
this.FxAccountsStorageManager = function(options = {}) {
  this.options = {
    filename: options.filename || DEFAULT_STORAGE_FILENAME,
    baseDir: options.baseDir || OS.Constants.Path.profileDir,
  }
  this.plainStorage = new JSONStorage(this.options);
  // On b2g we have no loginManager for secure storage, and tests may want
  // to pretend secure storage isn't available.
  let useSecure = "useSecure" in options ? options.useSecure : haveLoginManager;
  if (useSecure) {
    this.secureStorage = new LoginManagerStorage();
  } else {
    this.secureStorage = null;
  }
  this._clearCachedData();
  // See .initialize() below - this protects against it not being called.
  this._promiseInitialized = Promise.reject("initialize not called");
  // A promise to avoid storage races - see _queueStorageOperation
  this._promiseStorageComplete = Promise.resolve();
}

this.FxAccountsStorageManager.prototype = {
  _initialized: false,
  _needToReadSecure: true,

  // An initialization routine that *looks* synchronous to the callers, but
  // is actually async as everything else waits for it to complete.
  initialize(accountData) {
    if (this._initialized) {
      throw new Error("already initialized");
    }
    this._initialized = true;
    // If we just throw away our pre-rejected promise it is reported as an
    // unhandled exception when it is GCd - so add an empty .catch handler here
    // to prevent this.
    this._promiseInitialized.catch(() => {});
    this._promiseInitialized = this._initialize(accountData);
  },

  async _initialize(accountData) {
    log.trace("initializing new storage manager");
    try {
      if (accountData) {
        // If accountData is passed we don't need to read any storage.
        this._needToReadSecure = false;
        // split it into the 2 parts, write it and we are done.
        for (let [name, val] of Object.entries(accountData)) {
          if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
            this.cachedPlain[name] = val;
          } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
            this.cachedSecure[name] = val;
          } else {
            // Hopefully it's an "in memory" field. If it's not we log a warning
            // but still treat it as such (so it will still be available in this
            // session but isn't persisted anywhere.)
            if (!FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
              log.warn("Unknown FxA field name in user data, treating as in-memory", name);
            }
            this.cachedMemory[name] = val;
          }
        }
        // write it out and we are done.
        await this._write();
        return;
      }
      // So we were initialized without account data - that means we need to
      // read the state from storage. We try and read plain storage first and
      // only attempt to read secure storage if the plain storage had a user.
      this._needToReadSecure = await this._readPlainStorage();
      if (this._needToReadSecure && this.secureStorage) {
        await this._doReadAndUpdateSecure();
      }
    } finally {
      log.trace("initializing of new storage manager done");
    }
  },

  finalize() {
    // We can't throw this instance away while it is still writing or we may
    // end up racing with the newly created one.
    log.trace("StorageManager finalizing");
    return this._promiseInitialized.then(() => {
      return this._promiseStorageComplete;
    }).then(() => {
      this._promiseStorageComplete = null;
      this._promiseInitialized = null;
      this._clearCachedData();
      log.trace("StorageManager finalized");
    })
  },

  // We want to make sure we don't end up doing multiple storage requests
  // concurrently - which has a small window for reads if the master-password
  // is locked at initialization time and becomes unlocked later, and always
  // has an opportunity for updates.
  // We also want to make sure we finished writing when finalizing, so we
  // can't accidentally end up with the previous user's write finishing after
  // a signOut attempts to clear it.
  // So all such operations "queue" themselves via this.
  _queueStorageOperation(func) {
    // |result| is the promise we return - it has no .catch handler, so callers
    // of the storage operation still see failure as a normal rejection.
    let result = this._promiseStorageComplete.then(func);
    // But the promise we assign to _promiseStorageComplete *does* have a catch
    // handler so that rejections in one storage operation does not prevent
    // future operations from starting (ie, _promiseStorageComplete must never
    // be in a rejected state)
    this._promiseStorageComplete = result.catch(err => {
      log.error("${func} failed: ${err}", {func, err});
    });
    return result;
  },

  // Get the account data by combining the plain and secure storage.
  // If fieldNames is specified, it may be a string or an array of strings,
  // and only those fields are returned. If not specified the entire account
  // data is returned except for "in memory" fields. Note that not specifying
  // field names will soon be deprecated/removed - we want all callers to
  // specify the fields they care about.
  async getAccountData(fieldNames = null) {
    await this._promiseInitialized;
    // We know we are initialized - this means our .cachedPlain is accurate
    // and doesn't need to be read (it was read if necessary by initialize).
    // So if there's no uid, there's no user signed in.
    if (!("uid" in this.cachedPlain)) {
      return null;
    }
    let result = {};
    if (fieldNames === null) {
      // The "old" deprecated way of fetching a logged in user.
      for (let [name, value] of Object.entries(this.cachedPlain)) {
        result[name] = value;
      }
      // But the secure data may not have been read, so try that now.
      await this._maybeReadAndUpdateSecure();
      // .cachedSecure now has as much as it possibly can (which is possibly
      // nothing if (a) secure storage remains locked and (b) we've never updated
      // a field to be stored in secure storage.)
      for (let [name, value] of Object.entries(this.cachedSecure)) {
        result[name] = value;
      }
      // Note we don't return cachedMemory fields here - they must be explicitly
      // requested.
      return result;
    }
    // The new explicit way of getting attributes.
    if (!Array.isArray(fieldNames)) {
      fieldNames = [fieldNames];
    }
    let checkedSecure = false;
    for (let fieldName of fieldNames) {
      if (FXA_PWDMGR_MEMORY_FIELDS.has(fieldName)) {
        if (this.cachedMemory[fieldName] !== undefined) {
          result[fieldName] = this.cachedMemory[fieldName];
        }
      } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName)) {
        if (this.cachedPlain[fieldName] !== undefined) {
          result[fieldName] = this.cachedPlain[fieldName];
        }
      } else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) {
        // We may not have read secure storage yet.
        if (!checkedSecure) {
          await this._maybeReadAndUpdateSecure();
          checkedSecure = true;
        }
        if (this.cachedSecure[fieldName] !== undefined) {
          result[fieldName] = this.cachedSecure[fieldName];
        }
      } else {
        throw new Error("unexpected field '" + name + "'");
      }
    }
    return result;
  },

  // Update just the specified fields. This DOES NOT allow you to change to
  // a different user, nor to set the user as signed-out.
  async updateAccountData(newFields) {
    await this._promiseInitialized;
    if (!("uid" in this.cachedPlain)) {
      // If this storage instance shows no logged in user, then you can't
      // update fields.
      throw new Error("No user is logged in");
    }
    if (!newFields || "uid" in newFields || "email" in newFields) {
      // Once we support
      // user changing email address this may need to change, but it's not
      // clear how we would be told of such a change anyway...
      throw new Error("Can't change uid or email address");
    }
    log.debug("_updateAccountData with items", Object.keys(newFields));
    // work out what bucket.
    for (let [name, value] of Object.entries(newFields)) {
      if (FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
        if (value == null) {
          delete this.cachedMemory[name];
        } else {
          this.cachedMemory[name] = value;
        }
      } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
        if (value == null) {
          delete this.cachedPlain[name];
        } else {
          this.cachedPlain[name] = value;
        }
      } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
        // don't do the "delete on null" thing here - we need to keep it until
        // we have managed to read so we can nuke it on write.
        this.cachedSecure[name] = value;
      } else {
        // Throwing seems reasonable here as some client code has explicitly
        // specified the field name, so it's either confused or needs to update
        // how this field is to be treated.
        throw new Error("unexpected field '" + name + "'");
      }
    }
    // If we haven't yet read the secure data, do so now, else we may write
    // out partial data.
    await this._maybeReadAndUpdateSecure();
    // Now save it - but don't wait on the _write promise - it's queued up as
    // a storage operation, so .finalize() will wait for completion, but no need
    // for us to.
    this._write();
  },

  _clearCachedData() {
    this.cachedMemory = {};
    this.cachedPlain = {};
    // If we don't have secure storage available we have cachedPlain and
    // cachedSecure be the same object.
    this.cachedSecure = this.secureStorage == null ? this.cachedPlain : {};
  },

  /* Reads the plain storage and caches the read values in this.cachedPlain.
     Only ever called once and unlike the "secure" storage, is expected to never
     fail (ie, plain storage is considered always available, whereas secure
     storage may be unavailable if it is locked).

     Returns a promise that resolves with true if valid account data was found,
     false otherwise.

     Note: _readPlainStorage is only called during initialize, so isn't
     protected via _queueStorageOperation() nor _promiseInitialized.
  */
  async _readPlainStorage() {
    let got;
    try {
      got = await this.plainStorage.get();
    } catch (err) {
      // File hasn't been created yet.  That will be done
      // when write is called.
      if (!(err instanceof OS.File.Error) || !err.becauseNoSuchFile) {
        log.error("Failed to read plain storage", err);
      }
      // either way, we return null.
      got = null;
    }
    if (!got || !got.accountData || !got.accountData.uid ||
        got.version != DATA_FORMAT_VERSION) {
      return false;
    }
    // We need to update our .cachedPlain, but can't just assign to it as
    // it may need to be the exact same object as .cachedSecure
    // As a sanity check, .cachedPlain must be empty (as we are called by init)
    // XXX - this would be a good use-case for a RuntimeAssert or similar, as
    // being added in bug 1080457.
    if (Object.keys(this.cachedPlain).length != 0) {
      throw new Error("should be impossible to have cached data already.")
    }
    for (let [name, value] of Object.entries(got.accountData)) {
      this.cachedPlain[name] = value;
    }
    return true;
  },

  /* If we haven't managed to read the secure storage, try now, so
     we can merge our cached data with the data that's already been set.
  */
  _maybeReadAndUpdateSecure() {
    if (this.secureStorage == null || !this._needToReadSecure) {
      return null;
    }
    return this._queueStorageOperation(() => {
      if (this._needToReadSecure) { // we might have read it by now!
        return this._doReadAndUpdateSecure();
      }
      return null;
    });
  },

  /* Unconditionally read the secure storage and merge our cached data (ie, data
     which has already been set while the secure storage was locked) with
     the read data
  */
  async _doReadAndUpdateSecure() {
    let { uid, email } = this.cachedPlain;
    try {
      log.debug("reading secure storage with existing", Object.keys(this.cachedSecure));
      // If we already have anything in .cachedSecure it means something has
      // updated cachedSecure before we've read it. That means that after we do
      // manage to read we must write back the merged data.
      let needWrite = Object.keys(this.cachedSecure).length != 0;
      let readSecure = await this.secureStorage.get(uid, email);
      // and update our cached data with it - anything already in .cachedSecure
      // wins (including the fact it may be null or undefined, the latter
      // which means it will be removed from storage.
      if (readSecure && readSecure.version != DATA_FORMAT_VERSION) {
        log.warn("got secure data but the data format version doesn't match");
        readSecure = null;
      }
      if (readSecure && readSecure.accountData) {
        log.debug("secure read fetched items", Object.keys(readSecure.accountData));
        for (let [name, value] of Object.entries(readSecure.accountData)) {
          if (!(name in this.cachedSecure)) {
            this.cachedSecure[name] = value;
          }
        }
        if (needWrite) {
          log.debug("successfully read secure data; writing updated data back")
          await this._doWriteSecure();
        }
      }
      this._needToReadSecure = false;
    } catch (ex) {
      if (ex instanceof this.secureStorage.STORAGE_LOCKED) {
        log.debug("setAccountData: secure storage is locked trying to read");
      } else {
        log.error("failed to read secure storage", ex);
        throw ex;
      }
    }
  },

  _write() {
    // We don't want multiple writes happening concurrently, and we also need to
    // know when an "old" storage manager is done (this.finalize() waits for this)
    return this._queueStorageOperation(() => this.__write());
  },

  async __write() {
    // Write everything back - later we could track what's actually dirty,
    // but for now we write it all.
    log.debug("writing plain storage", Object.keys(this.cachedPlain));
    let toWritePlain = {
      version: DATA_FORMAT_VERSION,
      accountData: this.cachedPlain,
    }
    await this.plainStorage.set(toWritePlain);

    // If we have no secure storage manager we are done.
    if (this.secureStorage == null) {
      return;
    }
    // and only attempt to write to secure storage if we've managed to read it,
    // otherwise we might clobber data that's already there.
    if (!this._needToReadSecure) {
      await this._doWriteSecure();
    }
  },

  /* Do the actual write of secure data. Caller is expected to check if we actually
     need to write and to ensure we are in a queued storage operation.
  */
  async _doWriteSecure() {
    // We need to remove null items here.
    for (let [name, value] of Object.entries(this.cachedSecure)) {
      if (value == null) {
        delete this.cachedSecure[name];
      }
    }
    log.debug("writing secure storage", Object.keys(this.cachedSecure));
    let toWriteSecure = {
      version: DATA_FORMAT_VERSION,
      accountData: this.cachedSecure,
    }
    try {
      await this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
    } catch (ex) {
      if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) {
        throw ex;
      }
      // This shouldn't be possible as once it is unlocked it can't be
      // re-locked, and we can only be here if we've previously managed to
      // read.
      log.error("setAccountData: secure storage is locked trying to write");
    }
  },

  // Delete the data for an account - ie, called on "sign out".
  deleteAccountData() {
    return this._queueStorageOperation(() => this._deleteAccountData());
  },

  async _deleteAccountData() {
    log.debug("removing account data");
    await this._promiseInitialized;
    await this.plainStorage.set(null);
    if (this.secureStorage) {
      await this.secureStorage.set(null);
    }
    this._clearCachedData();
    log.debug("account data reset");
  },
}

/**
 * JSONStorage constructor that creates instances that may set/get
 * to a specified file, in a directory that will be created if it
 * doesn't exist.
 *
 * @param options {
 *                  filename: of the file to write to
 *                  baseDir: directory where the file resides
 *                }
 * @return instance
 */
function JSONStorage(options) {
  this.baseDir = options.baseDir;
  this.path = OS.Path.join(options.baseDir, options.filename);
}

JSONStorage.prototype = {
  set(contents) {
    log.trace("starting write of json user data", contents ? Object.keys(contents.accountData) : "null");
    let start = Date.now();
    return OS.File.makeDir(this.baseDir, {ignoreExisting: true})
      .then(CommonUtils.writeJSON.bind(null, contents, this.path))
      .then(result => {
        log.trace("finished write of json user data - took", Date.now() - start);
        return result;
      });
  },

  get() {
    log.trace("starting fetch of json user data");
    let start = Date.now();
    return CommonUtils.readJSON(this.path).then(result => {
      log.trace("finished fetch of json user data - took", Date.now() - start);
      return result;
    });
  },
};

function StorageLockedError() {
}
/**
 * LoginManagerStorage constructor that creates instances that set/get
 * data stored securely in the nsILoginManager.
 *
 * @return instance
 */

function LoginManagerStorage() {
}

LoginManagerStorage.prototype = {
  STORAGE_LOCKED: StorageLockedError,
  // The fields in the credentials JSON object that are stored in plain-text
  // in the profile directory.  All other fields are stored in the login manager,
  // and thus are only available when the master-password is unlocked.

  // a hook point for testing.
  get _isLoggedIn() {
    return Services.logins.isLoggedIn;
  },

  // Clear any data from the login manager.  Returns true if the login manager
  // was unlocked (even if no existing logins existed) or false if it was
  // locked (meaning we don't even know if it existed or not.)
  async _clearLoginMgrData() {
    try { // Services.logins might be third-party and broken...
      await Services.logins.initializationPromise;
      if (!this._isLoggedIn) {
        return false;
      }
      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
      for (let login of logins) {
        Services.logins.removeLogin(login);
      }
      return true;
    } catch (ex) {
      log.error("Failed to clear login data: ${}", ex);
      return false;
    }
  },

  async set(uid, contents) {
    if (!contents) {
      // Nuke it from the login manager.
      let cleared = await this._clearLoginMgrData();
      if (!cleared) {
        // just log a message - we verify that the uid matches when
        // we reload it, so having a stale entry doesn't really hurt.
        log.info("not removing credentials from login manager - not logged in");
      }
      log.trace("storage set finished clearing account data");
      return;
    }

    // We are saving actual data.
    log.trace("starting write of user data to the login manager");
    try { // Services.logins might be third-party and broken...
      // and the stuff into the login manager.
      await Services.logins.initializationPromise;
      // If MP is locked we silently fail - the user may need to re-auth
      // next startup.
      if (!this._isLoggedIn) {
        log.info("not saving credentials to login manager - not logged in");
        throw new this.STORAGE_LOCKED();
      }
      // write the data to the login manager.
      let loginInfo = new Components.Constructor(
         "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
      let login = new loginInfo(FXA_PWDMGR_HOST,
                                null, // aFormSubmitURL,
                                FXA_PWDMGR_REALM, // aHttpRealm,
                                uid, // aUsername
                                JSON.stringify(contents), // aPassword
                                "", // aUsernameField
                                "");// aPasswordField

      let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
                                                      FXA_PWDMGR_REALM);
      if (existingLogins.length) {
        Services.logins.modifyLogin(existingLogins[0], login);
      } else {
        Services.logins.addLogin(login);
      }
      log.trace("finished write of user data to the login manager");
    } catch (ex) {
      if (ex instanceof this.STORAGE_LOCKED) {
        throw ex;
      }
      // just log and consume the error here - it may be a 3rd party login
      // manager replacement that's simply broken.
      log.error("Failed to save data to the login manager", ex);
    }
  },

  async get(uid, email) {
    log.trace("starting fetch of user data from the login manager");

    try { // Services.logins might be third-party and broken...
      // read the data from the login manager and merge it for return.
      await Services.logins.initializationPromise;

      if (!this._isLoggedIn) {
        log.info("returning partial account data as the login manager is locked.");
        throw new this.STORAGE_LOCKED();
      }

      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
      if (logins.length == 0) {
        // This could happen if the MP was locked when we wrote the data.
        log.info("Can't find any credentials in the login manager");
        return null;
      }
      let login = logins[0];
      // Support either the uid or the email as the username - as of bug 1183951
      // we store the uid, but we support having either for b/w compat.
      if (login.username == uid || login.username == email) {
        return JSON.parse(login.password);
      }
      log.info("username in the login manager doesn't match - ignoring it");
      await this._clearLoginMgrData();
    } catch (ex) {
      if (ex instanceof this.STORAGE_LOCKED) {
        throw ex;
      }
      // just log and consume the error here - it may be a 3rd party login
      // manager replacement that's simply broken.
      log.error("Failed to get data from the login manager", ex);
    }
    return null;
  },
}

// A global variable to indicate if the login manager is available - it doesn't
// exist on b2g. Defined here as the use of preprocessor directives skews line
// numbers in the runtime, meaning stack-traces etc end up off by a few lines.
// Doing it at the end of the file makes that less of a pita.
var haveLoginManager = !AppConstants.MOZ_B2G;
PK
!<f:KK modules/FxAccountsWebChannel.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Firefox Accounts Web Channel.
 *
 * Uses the WebChannel component to receive messages
 * about account state changes.
 */

this.EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
                                  "resource://gre/modules/WebChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsStorageManagerCanStoreField",
                                  "resource://gre/modules/FxAccountsStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
                                  "resource://services-sync/main.js");

const COMMAND_PROFILE_CHANGE       = "profile:change";
const COMMAND_CAN_LINK_ACCOUNT     = "fxaccounts:can_link_account";
const COMMAND_LOGIN                = "fxaccounts:login";
const COMMAND_LOGOUT               = "fxaccounts:logout";
const COMMAND_DELETE               = "fxaccounts:delete";
const COMMAND_SYNC_PREFERENCES     = "fxaccounts:sync_preferences";
const COMMAND_CHANGE_PASSWORD      = "fxaccounts:change_password";
const COMMAND_FXA_STATUS           = "fxaccounts:fxa_status";

const PREF_LAST_FXA_USER           = "identity.fxaccounts.lastSignedInUserHash";

// These engines were added years after Sync had been introduced, they need
// special handling since they are system add-ons and are un-available on
// older versions of Firefox.
const EXTRA_ENGINES = ["addresses", "creditcards"];

/**
 * A helper function that extracts the message and stack from an error object.
 * Returns a `{ message, stack }` tuple. `stack` will be null if the error
 * doesn't have a stack trace.
 */
function getErrorDetails(error) {
  let details = { message: String(error), stack: null };

  // Adapted from Console.jsm.
  if (error.stack) {
    let frames = [];
    for (let frame = error.stack; frame; frame = frame.caller) {
      frames.push(String(frame).padStart(4));
    }
    details.stack = frames.join("\n");
  }

  return details;
}

/**
 * Create a new FxAccountsWebChannel to listen for account updates
 *
 * @param {Object} options Options
 *   @param {Object} options
 *     @param {String} options.content_uri
 *     The FxA Content server uri
 *     @param {String} options.channel_id
 *     The ID of the WebChannel
 *     @param {String} options.helpers
 *     Helpers functions. Should only be passed in for testing.
 * @constructor
 */
this.FxAccountsWebChannel = function(options) {
  if (!options) {
    throw new Error("Missing configuration options");
  }
  if (!options["content_uri"]) {
    throw new Error("Missing 'content_uri' option");
  }
  this._contentUri = options.content_uri;

  if (!options["channel_id"]) {
    throw new Error("Missing 'channel_id' option");
  }
  this._webChannelId = options.channel_id;

  // options.helpers is only specified by tests.
  XPCOMUtils.defineLazyGetter(this, "_helpers", () => {
    return options.helpers || new FxAccountsWebChannelHelpers(options);
  });

  this._setupChannel();
};

this.FxAccountsWebChannel.prototype = {
  /**
   * WebChannel that is used to communicate with content page
   */
  _channel: null,

  /**
   * Helpers interface that does the heavy lifting.
   */
  _helpers: null,

  /**
   * WebChannel ID.
   */
  _webChannelId: null,
  /**
   * WebChannel origin, used to validate origin of messages
   */
  _webChannelOrigin: null,

  /**
   * Release all resources that are in use.
   */
  tearDown() {
    this._channel.stopListening();
    this._channel = null;
    this._channelCallback = null;
  },

  /**
   * Configures and registers a new WebChannel
   *
   * @private
   */
  _setupChannel() {
    // if this.contentUri is present but not a valid URI, then this will throw an error.
    try {
      this._webChannelOrigin = Services.io.newURI(this._contentUri);
      this._registerChannel();
    } catch (e) {
      log.error(e);
      throw e;
    }
  },

  _receiveMessage(message, sendingContext) {
    let command = message.command;
    let data = message.data;

    switch (command) {
      case COMMAND_PROFILE_CHANGE:
        Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
        break;
      case COMMAND_LOGIN:
        this._helpers.login(data).catch(error =>
          this._sendError(error, message, sendingContext));
        break;
      case COMMAND_LOGOUT:
      case COMMAND_DELETE:
        this._helpers.logout(data.uid).catch(error =>
          this._sendError(error, message, sendingContext));
        break;
      case COMMAND_CAN_LINK_ACCOUNT:
        let canLinkAccount = this._helpers.shouldAllowRelink(data.email);

        let response = {
          command,
          messageId: message.messageId,
          data: { ok: canLinkAccount }
        };

        log.debug("FxAccountsWebChannel response", response);
        this._channel.send(response, sendingContext);
        break;
      case COMMAND_SYNC_PREFERENCES:
        this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
        break;
      case COMMAND_CHANGE_PASSWORD:
        this._helpers.changePassword(data).catch(error =>
          this._sendError(error, message, sendingContext));
        break;
      case COMMAND_FXA_STATUS:
        log.debug("fxa_status received");

        const service = data && data.service;
        this._helpers.getFxaStatus(service, sendingContext)
          .then(fxaStatus => {
            let response = {
              command,
              messageId: message.messageId,
              data: fxaStatus
            };
            this._channel.send(response, sendingContext);
          }).catch(error =>
            this._sendError(error, message, sendingContext)
          );
        break;
      default:
        log.warn("Unrecognized FxAccountsWebChannel command", command);
        break;
    }
  },

  _sendError(error, incomingMessage, sendingContext) {
    log.error("Failed to handle FxAccountsWebChannel message", error);
    this._channel.send({
      command: incomingMessage.command,
      messageId: incomingMessage.messageId,
      data: {
        error: getErrorDetails(error),
      },
    }, sendingContext);
  },

  /**
   * Create a new channel with the WebChannelBroker, setup a callback listener
   * @private
   */
  _registerChannel() {
    /**
     * Processes messages that are called back from the FxAccountsChannel
     *
     * @param webChannelId {String}
     *        Command webChannelId
     * @param message {Object}
     *        Command message
     * @param sendingContext {Object}
     *        Message sending context.
     *        @param sendingContext.browser {browser}
     *               The <browser> object that captured the
     *               WebChannelMessageToChrome.
     *        @param sendingContext.eventTarget {EventTarget}
     *               The <EventTarget> where the message was sent.
     *        @param sendingContext.principal {Principal}
     *               The <Principal> of the EventTarget where the message was sent.
     * @private
     *
     */
    let listener = (webChannelId, message, sendingContext) => {
      if (message) {
        log.debug("FxAccountsWebChannel message received", message.command);
        if (logPII) {
          log.debug("FxAccountsWebChannel message details", message);
        }
        try {
          this._receiveMessage(message, sendingContext);
        } catch (error) {
          this._sendError(error, message, sendingContext);
        }
      }
    };

    this._channelCallback = listener;
    this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
    this._channel.listen(listener);
    log.debug("FxAccountsWebChannel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
  }
};

this.FxAccountsWebChannelHelpers = function(options) {
  options = options || {};

  this._fxAccounts = options.fxAccounts || fxAccounts;
  this._privateBrowsingUtils = options.privateBrowsingUtils || PrivateBrowsingUtils;
};

this.FxAccountsWebChannelHelpers.prototype = {
  // 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...
  shouldAllowRelink(acctName) {
    return !this._needRelinkWarning(acctName) ||
            this._promptForRelink(acctName);
  },

  /**
   * stores sync login info it in the fxaccounts service
   *
   * @param accountData the user's account data and credentials
   */
  login(accountData) {

    // We don't act on customizeSync anymore, it used to open a dialog inside
    // the browser to selecte the engines to sync but we do it on the web now.
    delete accountData.customizeSync;

    if (accountData.offeredSyncEngines) {
      EXTRA_ENGINES.forEach(engine => {
        if (accountData.offeredSyncEngines.includes(engine) &&
            !accountData.declinedSyncEngines.includes(engine)) {
          // These extra engines are disabled by default.
          Services.prefs.setBoolPref(`services.sync.engine.${engine}`, true);
        }
      });
      delete accountData.offeredSyncEngines;
    }

    if (accountData.declinedSyncEngines) {
      let declinedSyncEngines = accountData.declinedSyncEngines;
      log.debug("Received declined engines", declinedSyncEngines);
      Weave.Service.engineManager.setDeclined(declinedSyncEngines);
      declinedSyncEngines.forEach(engine => {
        Services.prefs.setBoolPref("services.sync.engine." + engine, false);
      });
      delete accountData.declinedSyncEngines;
    }

    // the user has already been shown the "can link account"
    // screen. No need to keep this data around.
    delete accountData.verifiedCanLinkAccount;

    // Remember who it was so we can log out next time.
    this.setPreviousAccountNameHashPref(accountData.email);

    // 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;
    return xps.whenLoaded().then(() => {
      return this._fxAccounts.setSignedInUser(accountData);
    });
  },

  /**
   * logout the fxaccounts service
   *
   * @param the uid of the account which have been logged out
   */
  logout(uid) {
    return fxAccounts.getSignedInUser().then(userData => {
      if (userData && userData.uid === uid) {
        // true argument is `localOnly`, because server-side stuff
        // has already been taken care of by the content server
        return fxAccounts.signOut(true);
      }
      return null;
    });
  },

  /**
   * Check if `sendingContext` is in private browsing mode.
   */
  isPrivateBrowsingMode(sendingContext) {
    if (!sendingContext) {
      log.error("Unable to check for private browsing mode, assuming true");
      return true;
    }

    const isPrivateBrowsing = this._privateBrowsingUtils.isBrowserPrivate(sendingContext.browser);
    log.debug("is private browsing", isPrivateBrowsing);
    return isPrivateBrowsing;
  },

  /**
   * Check whether sending fxa_status data should be allowed.
   */
  shouldAllowFxaStatus(service, sendingContext) {
    // Return user data for any service in non-PB mode. In PB mode,
    // only return user data if service==="sync".
    //
    // This behaviour allows users to click the "Manage Account"
    // link from about:preferences#sync while in PB mode and things
    // "just work". While in non-PB mode, users can sign into
    // Pocket w/o entering their password a 2nd time, while in PB
    // mode they *will* have to enter their email/password again.
    //
    // The difference in behaviour is to try to match user
    // expectations as to what is and what isn't part of the browser.
    // Sync is viewed as an integral part of the browser, interacting
    // with FxA as part of a Sync flow should work all the time. If
    // Sync is broken in PB mode, users will think Firefox is broken.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1323853
    log.debug("service", service);
    return !this.isPrivateBrowsingMode(sendingContext) || service === "sync";
  },

  /**
   * Get fxa_status information. Resolves to { signedInUser: <user_data> }.
   * If returning status information is not allowed or no user is signed into
   * Sync, `user_data` will be null.
   */
  async getFxaStatus(service, sendingContext) {
    let signedInUser = null;

    if (this.shouldAllowFxaStatus(service, sendingContext)) {
      const userData = await this._fxAccounts.getSignedInUser();
      if (userData) {
        signedInUser = {
          email: userData.email,
          sessionToken: userData.sessionToken,
          uid: userData.uid,
          verified: userData.verified
        };
      }
    }

    return {
      signedInUser,
      capabilities: {
        engines: this._getAvailableExtraEngines()
      }
    };
  },

  _getAvailableExtraEngines() {
    return EXTRA_ENGINES.filter(engineName => {
      try {
        return Services.prefs.getBoolPref(`services.sync.engine.${engineName}.available`);
      } catch (e) {
        return false;
      }
    });
  },

  changePassword(credentials) {
    // If |credentials| has fields that aren't handled by accounts storage,
    // updateUserAccountData will throw - mainly to prevent errors in code
    // that hard-codes field names.
    // However, in this case the field names aren't really in our control.
    // We *could* still insist the server know what fields names are valid,
    // but that makes life difficult for the server when Firefox adds new
    // features (ie, new fields) - forcing the server to track a map of
    // versions to supported field names doesn't buy us much.
    // So we just remove field names we know aren't handled.
    let newCredentials = {
      deviceId: null
    };
    for (let name of Object.keys(credentials)) {
      if (name == "email" || name == "uid" || FxAccountsStorageManagerCanStoreField(name)) {
        newCredentials[name] = credentials[name];
      } else {
        log.info("changePassword ignoring unsupported field", name);
      }
    }
    return this._fxAccounts.updateUserAccountData(newCredentials)
      .then(() => this._fxAccounts.updateDeviceRegistration());
  },

  /**
   * Get the hash of account name of the previously signed in account
   */
  getPreviousAccountNameHashPref() {
    try {
      return Services.prefs.getStringPref(PREF_LAST_FXA_USER);
    } catch (_) {
      return "";
    }
  },

  /**
   * Given an account name, set the hash of the previously signed in account
   *
   * @param acctName the account name of the user's account.
   */
  setPreviousAccountNameHashPref(acctName) {
    Services.prefs.setStringPref(PREF_LAST_FXA_USER, this.sha256(acctName));
  },

  /**
   * Given a string, returns the SHA265 hash in base64
   */
  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);
  },

  /**
   * Open Sync Preferences in the current tab of the browser
   *
   * @param {Object} browser the browser in which to open preferences
   * @param {String} [entryPoint] entryPoint to use for logging
   */
  openSyncPreferences(browser, entryPoint) {
    let uri = "about:preferences";
    if (entryPoint) {
      uri += "?entrypoint=" + encodeURIComponent(entryPoint);
    }
    uri += "#sync";

    browser.loadURI(uri);
  },

  /**
   * If a user signs in using a different account, the data from the
   * previous account and the new account will be merged. Ask the user
   * if they want to continue.
   *
   * @private
   */
  _needRelinkWarning(acctName) {
    let prevAcctHash = this.getPreviousAccountNameHashPref();
    return prevAcctHash && prevAcctHash != this.sha256(acctName);
  },

  /**
   * Show the user a warning dialog that the data from the previous account
   * and the new account will be merged.
   *
   * @private
   */
  _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;

    // If running in context of the browser chrome, window does not exist.
    var targetWindow = typeof window === "undefined" ? null : window;
    let pressed = Services.prompt.confirmEx(targetWindow, title, body, buttonFlags,
                                       continueLabel, null, null, null,
                                       {});
    return pressed === 0; // 0 is the "continue" button
  }
};

var singleton;
// The entry-point for this module, which ensures only one of our channels is
// ever created - we require this because the WebChannel is global in scope
// (eg, it uses the observer service to tell interested parties of interesting
// things) and allowing multiple channels would cause such notifications to be
// sent multiple times.
this.EnsureFxAccountsWebChannel = function() {
  let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
  if (singleton && singleton._contentUri !== contentUri) {
    singleton.tearDown();
    singleton = null;
  }
  if (!singleton) {
    try {
      if (contentUri) {
        // The FxAccountsWebChannel listens for events and updates
        // the state machine accordingly.
        singleton = new this.FxAccountsWebChannel({
          content_uri: contentUri,
          channel_id: WEBCHANNEL_ID,
        });
      } else {
        log.warn("FxA WebChannel functionaly is disabled due to no URI pref.");
      }
    } catch (ex) {
      log.error("Failed to create FxA WebChannel", ex);
    }
  }
}
PK
!<yߞccmodules/GCTelemetry.jsm/* -*- 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 module records detailed timing information about selected
 * GCs. The data is sent back in the telemetry session ping. To avoid
 * bloating the ping, only a few GCs are included. There are two
 * selection strategies. We always save the five GCs with the worst
 * max_pause time. Additionally, five collections are selected at
 * random. If a GC runs for C milliseconds and the total time for all
 * GCs since the session began is T milliseconds, then the GC has a
 * 5*C/T probablility of being selected (the factor of 5 is because we
 * save 5 of them).
 *
 * GCs from both the main process and all content processes are
 * recorded. The data is cleared for each new subsession.
 */

const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm", this);

this.EXPORTED_SYMBOLS = ["GCTelemetry"];

// Names of processes where we record GCs.
const PROCESS_NAMES = ["main", "content"];

// Should be the time we started up in milliseconds since the epoch.
const BASE_TIME = Date.now() - Services.telemetry.msSinceProcessStart();

// Records selected GCs. There is one instance per process type.
class GCData {
  constructor(kind) {
    let numRandom = {main: 0, content: 2};
    let numWorst = {main: 2, content: 2};

    this.totalGCTime = 0;
    this.randomlySelected = Array(numRandom[kind]).fill(null);
    this.worst = Array(numWorst[kind]).fill(null);
  }

  // Turn absolute timestamps (in microseconds since the epoch) into
  // milliseconds since startup.
  rebaseTimes(data) {
    function fixup(t) {
      return t / 1000.0 - BASE_TIME;
    }

    data.timestamp = fixup(data.timestamp);

    for (let i = 0; i < data.slices.length; i++) {
      let slice = data.slices[i];
      slice.start_timestamp = fixup(slice.start_timestamp);
      slice.end_timestamp = fixup(slice.end_timestamp);
    }
  }

  // Records a GC (represented by |data|) in the randomlySelected or
  // worst batches depending on the criteria above.
  record(data) {
    this.rebaseTimes(data);

    let time = data.total_time;
    this.totalGCTime += time;

    // Probability that we will replace any one of our
    // current randomlySelected GCs with |data|.
    let prob = time / this.totalGCTime;

    // Note that we may replace multiple GCs in
    // randomlySelected. It's easier to reason about the
    // probabilities this way, and it's unlikely to have any effect in
    // practice.
    for (let i = 0; i < this.randomlySelected.length; i++) {
      let r = Math.random();
      if (r <= prob) {
        this.randomlySelected[i] = data;
      }
    }

    // Save the 5 worst GCs based on max_pause. A GC may appear in
    // both worst and randomlySelected.
    for (let i = 0; i < this.worst.length; i++) {
      if (!this.worst[i]) {
        this.worst[i] = data;
        break;
      }

      if (this.worst[i].max_pause < data.max_pause) {
        this.worst.splice(i, 0, data);
        this.worst.length--;
        break;
      }
    }
  }

  entries() {
    return {
      random: this.randomlySelected.filter(e => e !== null),
      worst: this.worst.filter(e => e !== null),
    };
  }
}

// If you adjust any of the constants here (slice limit, number of keys, etc.)
// make sure to update the JSON schema at:
// https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/telemetry/main.schema.json
// You should also adjust browser_TelemetryGC.js.
const MAX_GC_KEYS = 25;
const MAX_SLICES = 4;
const MAX_SLICE_KEYS = 15;
const MAX_PHASES = 65;

function limitProperties(obj, count) {
  // If there are too many properties, just delete them all. We don't
  // expect this ever to happen.
  if (Object.keys(obj).length > count) {
    for (let key of Object.keys(obj)) {
      delete obj[key];
    }
  }
}

function limitSize(data) {
  // Store the number of slices so we know if we lost any at the end.
  data.num_slices = data.slices.length;

  data.slices.sort((a, b) => b.pause - a.pause);

  if (data.slices.length > MAX_SLICES) {
    // Make sure we always keep the first slice since it has the
    // reason the GC was started.
    let firstSliceIndex = data.slices.findIndex(s => s.slice == 0);
    if (firstSliceIndex >= MAX_SLICES) {
      data.slices[MAX_SLICES - 1] = data.slices[firstSliceIndex];
    }

    data.slices.length = MAX_SLICES;
  }

  data.slices.sort((a, b) => a.slice - b.slice);

  limitProperties(data, MAX_GC_KEYS);

  for (let slice of data.slices) {
    limitProperties(slice, MAX_SLICE_KEYS);
    limitProperties(slice.times, MAX_PHASES);
  }

  limitProperties(data.totals, MAX_PHASES);
}

let processData = new Map();
for (let name of PROCESS_NAMES) {
  processData.set(name, new GCData(name));
}

var GCTelemetry = {
  initialized: false,

  init() {
    if (this.initialized) {
      return false;
    }

    this.initialized = true;
    Services.obs.addObserver(this, "garbage-collection-statistics");

    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
      Services.ppmm.addMessageListener("Telemetry:GCStatistics", this);
    }

    return true;
  },

  shutdown() {
    if (!this.initialized) {
      return;
    }

    Services.obs.removeObserver(this, "garbage-collection-statistics");

    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
      Services.ppmm.removeMessageListener("Telemetry:GCStatistics", this);
    }
    this.initialized = false;
  },

  observe(subject, topic, arg) {
    let data = JSON.parse(arg);

    limitSize(data);

    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
      processData.get("main").record(data);
    } else {
      Services.cpmm.sendAsyncMessage("Telemetry:GCStatistics", data);
    }
  },

  receiveMessage(msg) {
    processData.get("content").record(msg.data);
  },

  entries(kind, clear) {
    let result = processData.get(kind).entries();
    if (clear) {
      processData.set(kind, new GCData(kind));
    }
    return result;
  },
};
PK
!<{q	q	modules/GMPExtractorWorker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/chrome-worker */

"use strict";

importScripts("resource://gre/modules/osfile.jsm");

const FILE_ENTRY = "201: ";

onmessage = async function(msg) {
  try {
    let extractedPaths = [];
    let jarPath = "jar:file://" + msg.data.zipPath + "!/";
    let jarResponse = await fetch(jarPath);
    let dirListing = await jarResponse.text();
    let lines = dirListing.split("\n");
    let reader = new FileReader();
    for (let line of lines) {
      if (!line.startsWith(FILE_ENTRY)) {
        // Not a file entry, skip.
        continue;
      }
      let lineSplits = line.split(" ");
      let fileName = lineSplits[1];
      // We don't need these types of files.
      if (fileName == "verified_contents.json" ||
          fileName == "icon-128x128.png") {
        continue;
      }
      let filePath = jarPath + fileName;
      let filePathResponse = await fetch(filePath);
      let fileContents = await filePathResponse.blob();
      let fileData = await new Promise(resolve => {
        reader.onloadend = function() { resolve(reader.result) };
        reader.readAsArrayBuffer(fileContents);
      });
      let profileDirPath = OS.Constants.Path.profileDir;
      let installToDirPath = OS.Path.join(profileDirPath,
                                          msg.data.relativeInstallPath);
      await OS.File.makeDir(installToDirPath, {ignoreExisting: true,
                                               unixMode: 0o755,
                                               from: profileDirPath});
      // Do not extract into directories. Extract all files to the same
      // directory.
      let destPath = OS.Path.join(installToDirPath, fileName);
      await OS.File.writeAtomic(destPath, new Uint8Array(fileData),
                                {tmpPath: destPath + ".tmp"});
      // Ensure files are writable and executable. Otherwise, we may be
      // unable to execute or uninstall them.
      await OS.File.setPermissions(destPath, {unixMode: 0o700});
      extractedPaths.push(destPath);
    }
    postMessage({
      "result": "success",
      extractedPaths
    });
  } catch (e) {
    postMessage({
      "result": "fail",
      "exception": e.message
    });
  }
}
PK
!<TWt=t=modules/GMPInstallManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
  Components;
// 1 day default
const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;

var GMPInstallFailureReason = {
  GMP_INVALID: 1,
  GMP_HIDDEN: 2,
  GMP_DISABLED: 3,
  GMP_UPDATE_DISABLED: 4,
};

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/GMPUtils.jsm");
Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm");

this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
                         "GMPAddon"];

// Shared code for suppressing bad cert dialogs
XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
  let temp = { };
  Cu.import("resource://gre/modules/CertUtils.jsm", temp);
  return temp;
});

XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

function getScopedLogger(prefix) {
  // `PARENT_LOGGER_ID.` being passed here effectively links this logger
  // to the parentLogger.
  return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
}

/**
 * Provides an easy API for downloading and installing GMP Addons
 */
function GMPInstallManager() {
}
/**
 * Temp file name used for downloading
 */
GMPInstallManager.prototype = {
  /**
   * Obtains a URL with replacement of vars
   */
  async _getURL() {
    let log = getScopedLogger("GMPInstallManager._getURL");
    // Use the override URL if it is specified.  The override URL is just like
    // the normal URL but it does not check the cert.
    let url = GMPPrefs.getString(GMPPrefs.KEY_URL_OVERRIDE, "");
    if (url) {
      log.info("Using override url: " + url);
    } else {
      url = GMPPrefs.getString(GMPPrefs.KEY_URL);
      log.info("Using url: " + url);
    }

    url = await UpdateUtils.formatUpdateURL(url);

    log.info("Using url (with replacement): " + url);
    return url;
  },
  /**
   * Performs an addon check.
   * @return a promise which will be resolved or rejected.
   *         The promise is resolved with an object with properties:
   *           gmpAddons: array of GMPAddons
   *           usedFallback: whether the data was collected from online or
   *                         from fallback data within the build
   *         The promise is rejected with an object with properties:
   *           target: The XHR request object
   *           status: The HTTP status code
   *           type: Sometimes specifies type of rejection
   */
  async checkForAddons() {
    let log = getScopedLogger("GMPInstallManager.checkForAddons");
    if (this._deferred) {
        log.error("checkForAddons already called");
        return Promise.reject({type: "alreadycalled"});
    }
    this._deferred = PromiseUtils.defer();

    let allowNonBuiltIn = true;
    let certs = null;
    if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE)) {
      allowNonBuiltIn = !GMPPrefs.getString(GMPPrefs.KEY_CERT_REQUIREBUILTIN, true);
      if (GMPPrefs.getBool(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
        certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
      }
    }

    let url = await this._getURL();

    let addonPromise = ProductAddonChecker
        .getProductAddonList(url, allowNonBuiltIn, certs);

    addonPromise.then(res => {
      if (!res || !res.gmpAddons) {
        this._deferred.resolve({gmpAddons: []});
      } else {
        res.gmpAddons = res.gmpAddons.map(a => new GMPAddon(a));
        this._deferred.resolve(res);
      }
      delete this._deferred;
    }, (ex) => {
      this._deferred.reject(ex);
      delete this._deferred;
    });
    return this._deferred.promise;
  },
  /**
   * Installs the specified addon and calls a callback when done.
   * @param gmpAddon The GMPAddon object to install
   * @return a promise which will be resolved or rejected
   *         The promise will resolve with an array of paths that were extracted
   *         The promise will reject with an error object:
   *           target: The XHR request object
   *           status: The HTTP status code
   *           type: A string to represent the type of error
   *                 downloaderr, verifyerr or previouserrorencountered
   */
  installAddon(gmpAddon) {
    if (this._deferred) {
        let log = getScopedLogger("GMPInstallManager.installAddon");
        log.error("previous error encountered");
        return Promise.reject({type: "previouserrorencountered"});
    }
    this.gmpDownloader = new GMPDownloader(gmpAddon);
    return this.gmpDownloader.start();
  },
  _getTimeSinceLastCheck() {
    let now = Math.round(Date.now() / 1000);
    // Default to 0 here because `now - 0` will be returned later if that case
    // is hit. We want a large value so a check will occur.
    let lastCheck = GMPPrefs.getInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
    // Handle clock jumps, return now since we want it to represent
    // a lot of time has passed since the last check.
    if (now < lastCheck) {
      return now;
    }
    return now - lastCheck;
  },
  get _isEMEEnabled() {
    return GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true);
  },
  _isAddonEnabled(aAddon) {
    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon);
  },
  _isAddonUpdateEnabled(aAddon) {
    return this._isAddonEnabled(aAddon) &&
           GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon);
  },
  _updateLastCheck() {
    let now = Math.round(Date.now() / 1000);
    GMPPrefs.setInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, now);
  },
  _versionchangeOccurred() {
    let savedBuildID = GMPPrefs.getString(GMPPrefs.KEY_BUILDID, "");
    let buildID = Services.appinfo.platformBuildID || "";
    if (savedBuildID == buildID) {
      return false;
    }
    GMPPrefs.setString(GMPPrefs.KEY_BUILDID, buildID);
    return true;
  },
  /**
   * Wrapper for checkForAddons and installAddon.
   * Will only install if not already installed and will log the results.
   * This will only install/update the OpenH264 and EME plugins
   * @return a promise which will be resolved if all addons could be installed
   *         successfully, rejected otherwise.
   */
  async simpleCheckAndInstall() {
    let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall");

    if (this._versionchangeOccurred()) {
      log.info("A version change occurred. Ignoring " +
               "media.gmp-manager.lastCheck to check immediately for " +
               "new or updated GMPs.");
    } else {
      let secondsBetweenChecks =
        GMPPrefs.getInt(GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS,
                        DEFAULT_SECONDS_BETWEEN_CHECKS)
      let secondsSinceLast = this._getTimeSinceLastCheck();
      log.info("Last check was: " + secondsSinceLast +
               " seconds ago, minimum seconds: " + secondsBetweenChecks);
      if (secondsBetweenChecks > secondsSinceLast) {
        log.info("Will not check for updates.");
        return {status: "too-frequent-no-check"};
      }
    }

    try {
      let {usedFallback, gmpAddons} = await this.checkForAddons();
      this._updateLastCheck();
      log.info("Found " + gmpAddons.length + " addons advertised.");
      let addonsToInstall = gmpAddons.filter(function(gmpAddon) {
        log.info("Found addon: " + gmpAddon.toString());

        if (!gmpAddon.isValid) {
          log.info("Addon |" + gmpAddon.id + "| is invalid.");
          return false;
        }

        if (GMPUtils.isPluginHidden(gmpAddon)) {
          log.info("Addon |" + gmpAddon.id + "| has been hidden.");
          return false;
        }

        if (gmpAddon.isInstalled) {
          log.info("Addon |" + gmpAddon.id + "| already installed.");
          return false;
        }

        // Do not install from fallback if already installed as it
        // may be a downgrade
        if (usedFallback && gmpAddon.isUpdate) {
         log.info("Addon |" + gmpAddon.id + "| not installing updates based " +
                  "on fallback.");
         return false;
        }

        let addonUpdateEnabled = false;
        if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) {
          if (!this._isAddonEnabled(gmpAddon.id)) {
            log.info("GMP |" + gmpAddon.id + "| has been disabled; skipping check.");
          } else if (!this._isAddonUpdateEnabled(gmpAddon.id)) {
            log.info("Auto-update is off for " + gmpAddon.id +
                     ", skipping check.");
          } else {
            addonUpdateEnabled = true;
          }
        } else {
          // Currently, we only support installs of OpenH264 and EME plugins.
          log.info("Auto-update is off for unknown plugin '" + gmpAddon.id +
                   "', skipping check.");
        }

        return addonUpdateEnabled;
      }, this);

      if (!addonsToInstall.length) {
        log.info("No new addons to install, returning");
        return {status: "nothing-new-to-install"};
      }

      let installResults = [];
      let failureEncountered = false;
      for (let addon of addonsToInstall) {
        try {
          await this.installAddon(addon);
          installResults.push({
            id:     addon.id,
            result: "succeeded",
          });
        } catch (e) {
          failureEncountered = true;
          installResults.push({
            id:     addon.id,
            result: "failed",
          });
        }
      }
      if (failureEncountered) {
        throw {status:  "failed",
               results: installResults};
      }
      return {status:  "succeeded",
              results: installResults};
    } catch (e) {
      log.error("Could not check for addons", e);
      throw e;
    }
  },

  /**
   * Makes sure everything is cleaned up
   */
  uninit() {
    let log = getScopedLogger("GMPInstallManager.uninit");
    if (this._request) {
      log.info("Aborting request");
      this._request.abort();
    }
    if (this._deferred) {
        log.info("Rejecting deferred");
        this._deferred.reject({type: "uninitialized"});
    }
    log.info("Done cleanup");
  },

  /**
   * If set to true, specifies to leave the temporary downloaded zip file.
   * This is useful for tests.
   */
  overrideLeaveDownloadedZip: false,
};

/**
 * Used to construct a single GMP addon
 * GMPAddon objects are returns from GMPInstallManager.checkForAddons
 * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
 *
 * @param addon The ProductAddonChecker `addon` object
 */
function GMPAddon(addon) {
  let log = getScopedLogger("GMPAddon.constructor");
  for (let name of Object.keys(addon)) {
    this[name] = addon[name];
  }
  log.info("Created new addon: " + this.toString());
}

GMPAddon.prototype = {
  /**
   * Returns a string representation of the addon
   */
  toString() {
    return this.id + " (" +
           "isValid: " + this.isValid +
           ", isInstalled: " + this.isInstalled +
           ", hashFunction: " + this.hashFunction +
           ", hashValue: " + this.hashValue +
           (this.size !== undefined ? ", size: " + this.size : "" ) +
           ")";
  },
  /**
   * If all the fields aren't specified don't consider this addon valid
   * @return true if the addon is parsed and valid
   */
  get isValid() {
    return this.id && this.URL && this.version &&
      this.hashFunction && !!this.hashValue;
  },
  get isInstalled() {
    return this.version &&
      GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) === this.version;
  },
  get isEME() {
    return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0;
  },
  /**
   * @return true if the addon has been previously installed and this is
   * a new version, if this is a fresh install return false
   */
  get isUpdate() {
    return this.version &&
      GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id);
  },
};
/**
 * Constructs a GMPExtractor object which is used to extract a GMP zip
 * into the specified location. (Which typically leties per platform)
 * @param zipPath The path on disk of the zip file to extract
 */
function GMPExtractor(zipPath, relativeInstallPath) {
    this.zipPath = zipPath;
    this.relativeInstallPath = relativeInstallPath;
}
GMPExtractor.prototype = {
  /**
   * Installs the this.zipPath contents into the directory used to store GMP
   * addons for the current platform.
   *
   * @return a promise which will be resolved or rejected
   *         See GMPInstallManager.installAddon for resolve/rejected info
   */
  install() {
    this._deferred = PromiseUtils.defer();
    let deferredPromise = this._deferred;
    let {zipPath, relativeInstallPath} = this;
    let worker =
      new ChromeWorker("resource://gre/modules/GMPExtractorWorker.js");
    worker.onmessage = function(msg) {
      let log = getScopedLogger("GMPExtractor");
      worker.terminate();
      if (msg.data.result != "success") {
        log.error("Failed to extract zip file: " + zipPath);
        return deferredPromise.reject({
          target: this,
          status: msg.data.exception,
          type: "exception"
        });
      }
      log.info("Successfully extracted zip file: " + zipPath);
      return deferredPromise.resolve(msg.data.extractedPaths);
    }
    worker.postMessage({zipPath, relativeInstallPath});
    return this._deferred.promise;
  }
};


/**
 * Constructs an object which downloads and initiates an install of
 * the specified GMPAddon object.
 * @param gmpAddon The addon to install.
 */
function GMPDownloader(gmpAddon) {
  this._gmpAddon = gmpAddon;
}

GMPDownloader.prototype = {
  /**
   * Starts the download process for an addon.
   * @return a promise which will be resolved or rejected
   *         See GMPInstallManager.installAddon for resolve/rejected info
   */
  start() {
    let log = getScopedLogger("GMPDownloader");
    let gmpAddon = this._gmpAddon;

    if (!gmpAddon.isValid) {
      log.info("gmpAddon is not valid, will not continue");
      return Promise.reject({
        target: this,
        status,
        type: "downloaderr"
      });
    }

    return ProductAddonChecker.downloadAddon(gmpAddon).then((zipPath) => {
      let relativePath = OS.Path.join(gmpAddon.id,
                                      gmpAddon.version);
      log.info("install to directory path: " + relativePath);
      let gmpInstaller = new GMPExtractor(zipPath, relativePath);
      let installPromise = gmpInstaller.install();
      return installPromise.then(extractedPaths => {
        // Success, set the prefs
        let now = Math.round(Date.now() / 1000);
        GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
        // Remember our ABI, so that if the profile is migrated to another
        // platform or from 32 -> 64 bit, we notice and don't try to load the
        // unexecutable plugin library.
        GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI, gmpAddon.id);
        // Setting the version pref signals installation completion to consumers,
        // if you need to set other prefs etc. do it before this.
        GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version,
                           gmpAddon.id);
        return extractedPaths;
      });
    });
  },
};
PK
!<+V5 5 modules/GMPUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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, manager: Cm} =
  Components;

this.EXPORTED_SYMBOLS = [ "GMP_PLUGIN_IDS",
                          "GMPPrefs",
                          "GMPUtils",
                          "OPEN_H264_ID",
                          "WIDEVINE_ID" ];

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

// GMP IDs
const OPEN_H264_ID  = "gmp-gmpopenh264";
const WIDEVINE_ID   = "gmp-widevinecdm";
const GMP_PLUGIN_IDS = [ OPEN_H264_ID, WIDEVINE_ID ];

var GMPPluginUnsupportedReason = {
  NOT_WINDOWS: 1,
  WINDOWS_VERSION: 2,
};

var GMPPluginHiddenReason = {
  UNSUPPORTED: 1,
  EME_DISABLED: 2,
};

this.GMPUtils = {
  /**
   * Checks whether or not a given plugin is hidden. Hidden plugins are neither
   * downloaded nor displayed in the addons manager.
   * @param   aPlugin
   *          The plugin to check.
   */
  isPluginHidden(aPlugin) {
    if (this._is32bitModeMacOS()) {
      // GMPs are hidden on MacOS when running in 32 bit mode.
      // See bug 1291537.
      return true;
    }
    if (!aPlugin.isEME) {
      return false;
    }

    if (!this._isPluginSupported(aPlugin) ||
        !this._isPluginVisible(aPlugin)) {
      return true;
    }

    if (!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
      return true;
    }

    return false;
  },

  /**
   * Checks whether or not a given plugin is supported by the current OS.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginSupported(aPlugin) {
    if (this._isPluginForceSupported(aPlugin)) {
      return true;
    }
    if (aPlugin.id == WIDEVINE_ID) {
      // The Widevine plugin is available for Windows versions Vista and later,
      // Mac OSX, and Linux.
      return AppConstants.isPlatformAndVersionAtLeast("win", "6") ||
             AppConstants.platform == "macosx" ||
             AppConstants.platform == "linux";
    }

    return true;
  },

  _is32bitModeMacOS() {
    if (AppConstants.platform != "macosx") {
      return false;
    }
    return Services.appinfo.XPCOMABI.split("-")[0] == "x86";
  },

  /**
   * Checks whether or not a given plugin is visible in the addons manager
   * UI and the "enable DRM" notification box. This can be used to test
   * plugins that aren't yet turned on in the mozconfig.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginVisible(aPlugin) {
    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VISIBLE, false, aPlugin.id);
  },

  /**
   * Checks whether or not a given plugin is forced-supported. This is used
   * in automated tests to override the checks that prevent GMPs running on an
   * unsupported platform.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginForceSupported(aPlugin) {
    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED, false, aPlugin.id);
  },
};

/**
 * Manages preferences for GMP addons
 */
this.GMPPrefs = {
  KEY_EME_ENABLED:              "media.eme.enabled",
  KEY_PLUGIN_ENABLED:           "media.{0}.enabled",
  KEY_PLUGIN_LAST_UPDATE:       "media.{0}.lastUpdate",
  KEY_PLUGIN_VERSION:           "media.{0}.version",
  KEY_PLUGIN_AUTOUPDATE:        "media.{0}.autoupdate",
  KEY_PLUGIN_VISIBLE:           "media.{0}.visible",
  KEY_PLUGIN_ABI:               "media.{0}.abi",
  KEY_PLUGIN_FORCE_SUPPORTED:   "media.{0}.forceSupported",
  KEY_URL:                      "media.gmp-manager.url",
  KEY_URL_OVERRIDE:             "media.gmp-manager.url.override",
  KEY_CERT_CHECKATTRS:          "media.gmp-manager.cert.checkAttributes",
  KEY_CERT_REQUIREBUILTIN:      "media.gmp-manager.cert.requireBuiltIn",
  KEY_UPDATE_LAST_CHECK:        "media.gmp-manager.lastCheck",
  KEY_SECONDS_BETWEEN_CHECKS:   "media.gmp-manager.secondsBetweenChecks",
  KEY_UPDATE_ENABLED:           "media.gmp-manager.updateEnabled",
  KEY_APP_DISTRIBUTION:         "distribution.id",
  KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
  KEY_BUILDID:                  "media.gmp-manager.buildID",
  KEY_CERTS_BRANCH:             "media.gmp-manager.certs.",
  KEY_PROVIDER_ENABLED:         "media.gmp-provider.enabled",
  KEY_LOG_BASE:                 "media.gmp.log.",
  KEY_LOGGING_LEVEL:            "media.gmp.log.level",
  KEY_LOGGING_DUMP:             "media.gmp.log.dump",

  /**
   * Obtains the specified string preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getString(aKey, aDefaultValue, aPlugin) {
    if (aKey === this.KEY_APP_DISTRIBUTION ||
        aKey === this.KEY_APP_DISTRIBUTION_VERSION) {
      return Services.prefs.getDefaultBranch(null).getCharPref(aKey, "default");
    }
    return Services.prefs.getStringPref(this.getPrefKey(aKey, aPlugin), aDefaultValue);
  },

  /**
   * Obtains the specified int preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getInt(aKey, aDefaultValue, aPlugin) {
    return Services.prefs.getIntPref(this.getPrefKey(aKey, aPlugin), aDefaultValue);
  },

  /**
   * Obtains the specified bool preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getBool(aKey, aDefaultValue, aPlugin) {
    return Services.prefs.getBoolPref(this.getPrefKey(aKey, aPlugin), aDefaultValue);
  },

  /**
   * Sets the specified string preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setString(aKey, aVal, aPlugin) {
    Services.prefs.setStringPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Sets the specified bool preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setBool(aKey, aVal, aPlugin) {
    Services.prefs.setBoolPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Sets the specified int preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setInt(aKey, aVal, aPlugin) {
    Services.prefs.setIntPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Checks whether or not the specified preference is set in relation to the
   * specified plugin.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   * @return true if the preference is set, false otherwise.
   */
  isSet(aKey, aPlugin) {
    return Services.prefs.prefHasUserValue(this.getPrefKey(aKey, aPlugin));
  },

  /**
   * Resets the specified preference in relation to the specified plugin to its
   * default.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   */
  reset(aKey, aPlugin) {
    Services.prefs.clearUserPref(this.getPrefKey(aKey, aPlugin));
  },

  /**
   * Scopes the specified preference key to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   * @return A preference key scoped to the specified plugin.
   */
  getPrefKey(aKey, aPlugin) {
    return aKey.replace("{0}", aPlugin || "");
  },
};
PK
!<!!modules/Geometry.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Point", "Rect"];

/**
 * Simple Point class.
 *
 * Any method that takes an x and y may also take a point.
 */
this.Point = function Point(x, y) {
  this.set(x, y);
}

Point.prototype = {
  clone: function clone() {
    return new Point(this.x, this.y);
  },

  set: function set(x, y) {
    this.x = x;
    this.y = y;
    return this;
  },

  equals: function equals(x, y) {
    return this.x == x && this.y == y;
  },

  toString: function toString() {
    return "(" + this.x + "," + this.y + ")";
  },

  map: function map(f) {
    this.x = f.call(this, this.x);
    this.y = f.call(this, this.y);
    return this;
  },

  add: function add(x, y) {
    this.x += x;
    this.y += y;
    return this;
  },

  subtract: function subtract(x, y) {
    this.x -= x;
    this.y -= y;
    return this;
  },

  scale: function scale(s) {
    this.x *= s;
    this.y *= s;
    return this;
  },

  isZero() {
    return this.x == 0 && this.y == 0;
  }
};

(function() {
  function takePointOrArgs(f) {
    return function(arg1, arg2) {
      if (arg2 === undefined)
        return f.call(this, arg1.x, arg1.y);
      return f.call(this, arg1, arg2);
    };
  }

  for (let f of ["add", "subtract", "equals", "set"])
    Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
})();


/**
 * Rect is a simple data structure for representation of a rectangle supporting
 * many basic geometric operations.
 *
 * NOTE: Since its operations are closed, rectangles may be empty and will report
 * non-positive widths and heights in that case.
 */

this.Rect = function Rect(x, y, w, h) {
  this.left = x;
  this.top = y;
  this.right = x + w;
  this.bottom = y + h;
};

Rect.fromRect = function fromRect(r) {
  return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
};

Rect.prototype = {
  get x() { return this.left; },
  get y() { return this.top; },
  get width() { return this.right - this.left; },
  get height() { return this.bottom - this.top; },
  set x(v) {
    let diff = this.left - v;
    this.left = v;
    this.right -= diff;
  },
  set y(v) {
    let diff = this.top - v;
    this.top = v;
    this.bottom -= diff;
  },
  set width(v) { this.right = this.left + v; },
  set height(v) { this.bottom = this.top + v; },

  isEmpty: function isEmpty() {
    return this.left >= this.right || this.top >= this.bottom;
  },

  setRect(x, y, w, h) {
    this.left = x;
    this.top = y;
    this.right = x + w;
    this.bottom = y + h;

    return this;
  },

  setBounds(l, t, r, b) {
    this.top = t;
    this.left = l;
    this.bottom = b;
    this.right = r;

    return this;
  },

  equals: function equals(other) {
    return other != null &&
            (this.isEmpty() && other.isEmpty() ||
            this.top == other.top &&
            this.left == other.left &&
            this.bottom == other.bottom &&
            this.right == other.right);
  },

  clone: function clone() {
    return new Rect(this.left, this.top, this.right - this.left, this.bottom - this.top);
  },

  center: function center() {
    if (this.isEmpty())
      throw "Empty rectangles do not have centers";
    return new Point(this.left + (this.right - this.left) / 2,
                          this.top + (this.bottom - this.top) / 2);
  },

  copyFrom(other) {
    this.top = other.top;
    this.left = other.left;
    this.bottom = other.bottom;
    this.right = other.right;

    return this;
  },

  translate(x, y) {
    this.left += x;
    this.right += x;
    this.top += y;
    this.bottom += y;

    return this;
  },

  toString() {
    return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]";
  },

  /** return a new rect that is the union of that one and this one */
  union(other) {
    return this.clone().expandToContain(other);
  },

  contains(other) {
    if (other.isEmpty()) return true;
    if (this.isEmpty()) return false;

    return (other.left >= this.left &&
            other.right <= this.right &&
            other.top >= this.top &&
            other.bottom <= this.bottom);
  },

  intersect(other) {
    return this.clone().restrictTo(other);
  },

  intersects(other) {
    if (this.isEmpty() || other.isEmpty())
      return false;

    let x1 = Math.max(this.left, other.left);
    let x2 = Math.min(this.right, other.right);
    let y1 = Math.max(this.top, other.top);
    let y2 = Math.min(this.bottom, other.bottom);
    return x1 < x2 && y1 < y2;
  },

  /** Restrict area of this rectangle to the intersection of both rectangles. */
  restrictTo: function restrictTo(other) {
    if (this.isEmpty() || other.isEmpty())
      return this.setRect(0, 0, 0, 0);

    let x1 = Math.max(this.left, other.left);
    let x2 = Math.min(this.right, other.right);
    let y1 = Math.max(this.top, other.top);
    let y2 = Math.min(this.bottom, other.bottom);
    // If width or height is 0, the intersection was empty.
    return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
  },

  /** Expand this rectangle to the union of both rectangles. */
  expandToContain: function expandToContain(other) {
    if (this.isEmpty()) return this.copyFrom(other);
    if (other.isEmpty()) return this;

    let l = Math.min(this.left, other.left);
    let r = Math.max(this.right, other.right);
    let t = Math.min(this.top, other.top);
    let b = Math.max(this.bottom, other.bottom);
    return this.setRect(l, t, r - l, b - t);
  },

  /**
   * Expands to the smallest rectangle that contains original rectangle and is bounded
   * by lines with integer coefficients.
   */
  expandToIntegers: function round() {
    this.left = Math.floor(this.left);
    this.top = Math.floor(this.top);
    this.right = Math.ceil(this.right);
    this.bottom = Math.ceil(this.bottom);
    return this;
  },

  scale: function scale(xscl, yscl) {
    this.left *= xscl;
    this.right *= xscl;
    this.top *= yscl;
    this.bottom *= yscl;
    return this;
  },

  map: function map(f) {
    this.left = f.call(this, this.left);
    this.top = f.call(this, this.top);
    this.right = f.call(this, this.right);
    this.bottom = f.call(this, this.bottom);
    return this;
  },

  /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
  translateInside: function translateInside(other) {
    let offsetX = 0;
    if (this.left <= other.left)
      offsetX = other.left - this.left;
    else if (this.right > other.right)
      offsetX = other.right - this.right;

    let offsetY = 0;
    if (this.top <= other.top)
      offsetY = other.top - this.top;
    else if (this.bottom > other.bottom)
      offsetY = other.bottom - this.bottom;

    return this.translate(offsetX, offsetY);
  },

  /** Subtract other area from this. Returns array of rects whose union is this-other. */
  subtract: function subtract(other) {
    let r = new Rect(0, 0, 0, 0);
    let result = [];
    other = other.intersect(this);
    if (other.isEmpty())
      return [this.clone()];

    // left strip
    r.setBounds(this.left, this.top, other.left, this.bottom);
    if (!r.isEmpty())
      result.push(r.clone());
    // inside strip
    r.setBounds(other.left, this.top, other.right, other.top);
    if (!r.isEmpty())
      result.push(r.clone());
    r.setBounds(other.left, other.bottom, other.right, this.bottom);
    if (!r.isEmpty())
      result.push(r.clone());
    // right strip
    r.setBounds(other.right, this.top, this.right, this.bottom);
    if (!r.isEmpty())
      result.push(r.clone());

    return result;
  },

  /**
   * Blends two rectangles together.
   * @param rect Rectangle to blend this one with
   * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
   * @return New blended rectangle.
   */
  blend: function blend(rect, scalar) {
    return new Rect(
      this.left + (rect.left - this.left ) * scalar,
      this.top + (rect.top - this.top ) * scalar,
      this.width + (rect.width - this.width ) * scalar,
      this.height + (rect.height - this.height) * scalar);
  },

  /**
   * Grows or shrinks the rectangle while keeping the center point.
   * Accepts single multipler, or separate for both axes.
   */
  inflate: function inflate(xscl, yscl) {
    let xAdj = (this.width * xscl - this.width) / 2;
    let s = (arguments.length > 1) ? yscl : xscl;
    let yAdj = (this.height * s - this.height) / 2;
    this.left -= xAdj;
    this.right += xAdj;
    this.top -= yAdj;
    this.bottom += yAdj;
    return this;
  }
};
PK
!<4

modules/HiddenFrame.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/XPCOMUtils.jsm");

const XUL_PAGE = "chrome://global/content/win.xul";

const gAllHiddenFrames = new WeakSet();

let cleanupRegistered = false;
function ensureCleanupRegistered() {
  if (!cleanupRegistered) {
    cleanupRegistered = true;
    Services.obs.addObserver(function() {
      for (let hiddenFrame of ChromeUtils.nondeterministicGetWeakSetKeys(gAllHiddenFrames)) {
        hiddenFrame.destroy();
      }
    }, "xpcom-shutdown");
  }
}


/**
 * An hidden frame object. It takes care of creating a windowless browser and
 * passing the window containing a blank XUL <window> back.
 */
function HiddenFrame() {
}

HiddenFrame.prototype = {
  _frame: null,
  _browser: null,
  _listener: null,
  _webProgress: null,
  _deferred: null,

  /**
   * 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() {
    if (!this._deferred) {
      this._deferred = PromiseUtils.defer();
      this._create();
    }

    return this._deferred.promise;
  },

  /**
   * Fetch a sync ref to the window inside the frame (needed for the add-on SDK).
   */
  getWindow() {
    this.get();
    this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
    return this._browser.getInterface(Ci.nsIDOMWindow);
  },


  destroy() {
    if (this._browser) {
      if (this._listener) {
        this._webProgress.removeProgressListener(this._listener);
        this._listener = null;
        this._webProgress = null;
      }
      this._frame = null;
      this._deferred = null;

      gAllHiddenFrames.delete(this);
      this._browser.close();
      this._browser = null;
    }
  },

  _create() {
    ensureCleanupRegistered();
    this._browser = Services.appShell.createWindowlessBrowser(true);
    this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
    gAllHiddenFrames.add(this);
    this._webProgress = this._browser.getInterface(Ci.nsIWebProgress);
    this._listener = {
      QueryInterface: XPCOMUtils.generateQI([
        Ci.nsIWebProgressListener, Ci.nsIWebProgressListener2,
        Ci.nsISupportsWeakReference]),
    };
    this._listener.onStateChange = (wbp, request, stateFlags, status) => {
      if (!request) {
        return;
      }
      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        this._webProgress.removeProgressListener(this._listener);
        this._listener = null;
        this._webProgress = null;
        // Get the window reference via the document.
        this._frame = this._browser.document.ownerGlobal;
        this._deferred.resolve(this._frame);
      }
    };
    this._webProgress.addProgressListener(this._listener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
    let docShell = this._browser.getInterface(Ci.nsIDocShell);
    docShell.createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());
    docShell.useGlobalHistory = false;
    this._browser.loadURI(XUL_PAGE, 0, null, null, null);
  }
};
PK
!<Նmodules/History.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Asynchronous API for managing history.
 *
 *
 * The API makes use of `PageInfo` and `VisitInfo` objects, defined as follows.
 *
 * A `PageInfo` object is any object that contains A SUBSET of the
 * following properties:
 * - guid: (string)
 *     The globally unique id of the page.
 * - url: (URL)
 *     or (nsIURI)
 *     or (string)
 *     The full URI of the page. Note that `PageInfo` values passed as
 *     argument may hold `nsIURI` or `string` values for property `url`,
 *     but `PageInfo` objects returned by this module always hold `URL`
 *     values.
 * - title: (string)
 *     The title associated with the page, if any.
 * - description: (string)
 *     The description of the page, if any.
 * - previewImageURL: (URL)
 *     or (nsIURI)
 *     or (string)
 *     The preview image URL of the page, if any.
 * - frecency: (number)
 *     The frecency of the page, if any.
 *     See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Frecency_algorithm
 *     Note that this property may not be used to change the actualy frecency
 *     score of a page, only to retrieve it. In other words, any `frecency` field
 *     passed as argument to a function of this API will be ignored.
 *  - visits: (Array<VisitInfo>)
 *     All the visits for this page, if any.
 *
 * See the documentation of individual methods to find out which properties
 * are required for `PageInfo` arguments or returned for `PageInfo` results.
 *
 * A `VisitInfo` object is any object that contains A SUBSET of the following
 * properties:
 * - date: (Date)
 *     The time the visit occurred.
 * - transition: (number)
 *     How the user reached the page. See constants `TRANSITIONS.*`
 *     for the possible transition types.
 * - referrer: (URL)
 *          or (nsIURI)
 *          or (string)
 *     The referring URI of this visit. Note that `VisitInfo` passed
 *     as argument may hold `nsIURI` or `string` values for property `referrer`,
 *     but `VisitInfo` objects returned by this module always hold `URL`
 *     values.
 * See the documentation of individual methods to find out which properties
 * are required for `VisitInfo` arguments or returned for `VisitInfo` results.
 *
 *
 *
 * Each successful operation notifies through the nsINavHistoryObserver
 * interface. To listen to such notifications you must register using
 * nsINavHistoryService `addObserver` and `removeObserver` methods.
 * @see nsINavHistoryObserver
 */

this.EXPORTED_SYMBOLS = [ "History" ];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
Cu.importGlobalProperties(["URL"]);

/**
 * Whenever we update or remove numerous pages, it is preferable
 * to yield time to the main thread every so often to avoid janking.
 * These constants determine the maximal number of notifications we
 * may emit before we yield.
 */
const NOTIFICATION_CHUNK_SIZE = 300;
const ONRESULT_CHUNK_SIZE = 300;

// This constant determines the maximum number of remove pages before we cycle.
const REMOVE_PAGES_CHUNKLEN = 300;

/**
 * Sends a bookmarks notification through the given observers.
 *
 * @param observers
 *        array of nsINavBookmarkObserver objects.
 * @param notification
 *        the notification name.
 * @param args
 *        array of arguments to pass to the notification.
 */
function notify(observers, notification, args = []) {
  for (let observer of observers) {
    try {
      observer[notification](...args);
    } catch (ex) {}
  }
}

this.History = Object.freeze({
  /**
   * Fetch the available information for one page.
   *
   * @param guidOrURI: (string) or (URL, nsIURI or href)
   *      Either the full URI of the page or the GUID of the page.
   * @param [optional] options (object)
   *      An optional object whose properties describe options:
   *        - `includeVisits` (boolean) set this to true if `visits` in the
   *           PageInfo needs to contain VisitInfo in a reverse chronological order.
   *           By default, `visits` is undefined inside the returned `PageInfo`.
   *        - `includeMeta` (boolean) set this to true to fetch page meta fields,
   *           i.e. `description` and `preview_image_url`.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (PageInfo | null) If the page could be found, the information
   *      on that page.
   * @note the VisitInfo objects returned while fetching visits do not
   *       contain the property `referrer`.
   *       TODO: Add `referrer` to VisitInfo. See Bug #1365913.
   * @note the visits returned will not contain `TRANSITION_EMBED` visits.
   *
   * @throws (Error)
   *      If `guidOrURI` does not have the expected type or if it is a string
   *      that may be parsed neither as a valid URL nor as a valid GUID.
   */
  fetch(guidOrURI, options = {}) {
    // First, normalize to guid or string, and throw if not possible
    guidOrURI = PlacesUtils.normalizeToURLOrGUID(guidOrURI);

    // See if options exists and make sense
    if (!options || typeof options !== "object") {
      throw new TypeError("options should be an object and not null");
    }

    let hasIncludeVisits = "includeVisits" in options;
    if (hasIncludeVisits && typeof options.includeVisits !== "boolean") {
      throw new TypeError("includeVisits should be a boolean if exists");
    }

    let hasIncludeMeta = "includeMeta" in options;
    if (hasIncludeMeta && typeof options.includeMeta !== "boolean") {
      throw new TypeError("includeMeta should be a boolean if exists");
    }

    return PlacesUtils.promiseDBConnection()
                      .then(db => fetch(db, guidOrURI, options));
  },

  /**
   * Adds a number of visits for a single page.
   *
   * Any change may be observed through nsINavHistoryObserver
   *
   * @param pageInfo: (PageInfo)
   *      Information on a page. This `PageInfo` MUST contain
   *        - a property `url`, as specified by the definition of `PageInfo`.
   *        - a property `visits`, as specified by the definition of
   *          `PageInfo`, which MUST contain at least one visit.
   *      If a property `title` is provided, the title of the page
   *      is updated.
   *      If the `date` of a visit is not provided, it defaults
   *      to now.
   *      If the `transition` of a visit is not provided, it defaults to
   *      TRANSITION_LINK.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (PageInfo)
   *      A PageInfo object populated with data after the insert is complete.
   * @rejects (Error)
   *      Rejects if the insert was unsuccessful.
   *
   * @throws (Error)
   *      If the `url` specified was for a protocol that should not be
   *      stored (@see nsNavHistory::CanAddURI).
   * @throws (Error)
   *      If `pageInfo` has an unexpected type.
   * @throws (Error)
   *      If `pageInfo` does not have a `url`.
   * @throws (Error)
   *      If `pageInfo` does not have a `visits` property or if the
   *      value of `visits` is ill-typed or is an empty array.
   * @throws (Error)
   *      If an element of `visits` has an invalid `date`.
   * @throws (Error)
   *      If an element of `visits` has an invalid `transition`.
   */
  insert(pageInfo) {
    let info = PlacesUtils.validatePageInfo(pageInfo);

    return PlacesUtils.withConnectionWrapper("History.jsm: insert",
      db => insert(db, info));
  },

  /**
   * Adds a number of visits for a number of pages.
   *
   * Any change may be observed through nsINavHistoryObserver
   *
   * @param pageInfos: (Array<PageInfo>)
   *      Information on a page. This `PageInfo` MUST contain
   *        - a property `url`, as specified by the definition of `PageInfo`.
   *        - a property `visits`, as specified by the definition of
   *          `PageInfo`, which MUST contain at least one visit.
   *      If a property `title` is provided, the title of the page
   *      is updated.
   *      If the `date` of a visit is not provided, it defaults
   *      to now.
   *      If the `transition` of a visit is not provided, it defaults to
   *      TRANSITION_LINK.
   * @param onResult: (function(PageInfo))
   *      A callback invoked for each page inserted.
   * @param onError: (function(PageInfo))
   *      A callback invoked for each page which generated an error
   *      when an insert was attempted.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (null)
   * @rejects (Error)
   *      Rejects if all of the inserts were unsuccessful.
   *
   * @throws (Error)
   *      If the `url` specified was for a protocol that should not be
   *      stored (@see nsNavHistory::CanAddURI).
   * @throws (Error)
   *      If `pageInfos` has an unexpected type.
   * @throws (Error)
   *      If a `pageInfo` does not have a `url`.
   * @throws (Error)
   *      If a `PageInfo` does not have a `visits` property or if the
   *      value of `visits` is ill-typed or is an empty array.
   * @throws (Error)
   *      If an element of `visits` has an invalid `date`.
   * @throws (Error)
   *      If an element of `visits` has an invalid `transition`.
   */
  insertMany(pageInfos, onResult, onError) {
    let infos = [];

    if (!Array.isArray(pageInfos)) {
      throw new TypeError("pageInfos must be an array");
    }
    if (!pageInfos.length) {
      throw new TypeError("pageInfos may not be an empty array");
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError(`onResult: ${onResult} is not a valid function`);
    }
    if (onError && typeof onError != "function") {
      throw new TypeError(`onError: ${onError} is not a valid function`);
    }

    for (let pageInfo of pageInfos) {
      let info = PlacesUtils.validatePageInfo(pageInfo);
      infos.push(info);
    }

    return PlacesUtils.withConnectionWrapper("History.jsm: insertMany",
      db => insertMany(db, infos, onResult, onError));
  },

  /**
   * Remove pages from the database.
   *
   * Any change may be observed through nsINavHistoryObserver
   *
   *
   * @param page: (URL or nsIURI)
   *      The full URI of the page.
   *             or (string)
   *      Either the full URI of the page or the GUID of the page.
   *             or (Array<URL|nsIURI|string>)
   *      An array of the above, to batch requests.
   * @param onResult: (function(PageInfo))
   *      A callback invoked for each page found.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if at least one page was removed, `false` otherwise.
   * @throws (TypeError)
   *       If `pages` has an unexpected type or if a string provided
   *       is neither a valid GUID nor a valid URI or if `pages`
   *       is an empty array.
   */
  remove(pages, onResult = null) {
    // Normalize and type-check arguments
    if (Array.isArray(pages)) {
      if (pages.length == 0) {
        throw new TypeError("Expected at least one page");
      }
    } else {
      pages = [pages];
    }

    let guids = [];
    let urls = [];
    for (let page of pages) {
      // Normalize to URL or GUID, or throw if `page` cannot
      // be normalized.
      let normalized = PlacesUtils.normalizeToURLOrGUID(page);
      if (typeof normalized === "string") {
        guids.push(normalized);
      } else {
        urls.push(normalized.href);
      }
    }

    // At this stage, we know that either `guids` is not-empty
    // or `urls` is not-empty.

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return (async function() {
      let removedPages = false;
      let count = 0;
      while (guids.length || urls.length) {
        if (count && count % 2 == 0) {
          // Every few cycles, yield time back to the main
          // thread to avoid jank.
          await Promise.resolve();
        }
        count++;
        let guidsSlice = guids.splice(0, REMOVE_PAGES_CHUNKLEN);
        let urlsSlice = [];
        if (guidsSlice.length < REMOVE_PAGES_CHUNKLEN) {
          urlsSlice = urls.splice(0, REMOVE_PAGES_CHUNKLEN - guidsSlice.length);
        }

        let pages = {guids: guidsSlice, urls: urlsSlice};

        let result =
          await PlacesUtils.withConnectionWrapper("History.jsm: remove",
                                                  db => remove(db, pages, onResult));

        removedPages = removedPages || result;
      }
      return removedPages;
    })();
  },

  /**
   * Remove visits matching specific characteristics.
   *
   * Any change may be observed through nsINavHistoryObserver.
   *
   * @param filter: (object)
   *      The `object` may contain some of the following
   *      properties:
   *          - beginDate: (Date) Remove visits that have
   *                been added since this date (inclusive).
   *          - endDate: (Date) Remove visits that have
   *                been added before this date (inclusive).
   *          - limit: (Number) Limit the number of visits
   *                we remove to this number
   *          - url: (URL) Only remove visits to this URL
   *      If both `beginDate` and `endDate` are specified,
   *      visits between `beginDate` (inclusive) and `end`
   *      (inclusive) are removed.
   *
   * @param onResult: (function(VisitInfo), [optional])
   *     A callback invoked for each visit found and removed.
   *     Note that the referrer property of `VisitInfo`
   *     is NOT populated.
   *
   * @return (Promise)
   * @resolve (bool)
   *      `true` if at least one visit was removed, `false`
   *      otherwise.
   * @throws (TypeError)
   *      If `filter` does not have the expected type, in
   *      particular if the `object` is empty.
   */
  removeVisitsByFilter(filter, onResult = null) {
    if (!filter || typeof filter != "object") {
      throw new TypeError("Expected a filter");
    }

    let hasBeginDate = "beginDate" in filter;
    let hasEndDate = "endDate" in filter;
    let hasURL = "url" in filter;
    let hasLimit = "limit" in filter;
    if (hasBeginDate) {
      this.ensureDate(filter.beginDate);
    }
    if (hasEndDate) {
      this.ensureDate(filter.endDate);
    }
    if (hasBeginDate && hasEndDate && filter.beginDate > filter.endDate) {
      throw new TypeError("`beginDate` should be at least as old as `endDate`");
    }
    if (!hasBeginDate && !hasEndDate && !hasURL && !hasLimit) {
      throw new TypeError("Expected a non-empty filter");
    }

    if (hasURL && !(filter.url instanceof URL) && typeof filter.url != "string" &&
        !(filter.url instanceof Ci.nsIURI)) {
      throw new TypeError("Expected a valid URL for `url`");
    }

    if (hasLimit &&
        (typeof filter.limit != "number" ||
         filter.limit <= 0 ||
         !Number.isInteger(filter.limit))) {
      throw new TypeError("Expected a non-zero positive integer as a limit");
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return PlacesUtils.withConnectionWrapper("History.jsm: removeVisitsByFilter",
      db => removeVisitsByFilter(db, filter, onResult)
    );
  },

  /**
   * Remove pages from the database based on a filter.
   *
   * Any change may be observed through nsINavHistoryObserver
   *
   *
   * @param filter: An object containing a non empty subset of the following
   * properties:
   * - host: (string)
   *     Hostname with subhost wildcard (at most one *), or empty for local files.
   *     The * can be used only if it is the first character in the url, and not the host.
   *     For example, *.mozilla.org is allowed, *.org, www.*.org or * is not allowed.
   * - beginDate: (Date)
   *     The first time the page was visited (inclusive)
   * - endDate: (Date)
   *     The last time the page was visited (inclusive)
   * @param [optional] onResult: (function(PageInfo))
   *      A callback invoked for each page found.
   *
   * @note This removes pages with at least one visit inside the timeframe.
   *       Any visits outside the timeframe will also be removed with the page.
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if at least one page was removed, `false` otherwise.
   * @throws (TypeError)
   *       if `filter` does not have the expected type, in particular
   *       if the `object` is empty, or its components do not satisfy the
   *       criteria given above
   */
  removeByFilter(filter, onResult) {
    if (!filter || typeof filter !== "object") {
      throw new TypeError("Expected a filter object");
    }

    let hasHost = "host" in filter;
    if (hasHost) {
      if (typeof filter.host !== "string") {
        throw new TypeError("`host` should be a string");
      }
      filter.host = filter.host.toLowerCase();
    }

    let hasBeginDate = "beginDate" in filter;
    if (hasBeginDate) {
      this.ensureDate(filter.beginDate);
    }

    let hasEndDate = "endDate" in filter;
    if (hasEndDate) {
      this.ensureDate(filter.endDate);
    }

    if (hasBeginDate && hasEndDate && filter.beginDate > filter.endDate) {
      throw new TypeError("`beginDate` should be at least as old as `endDate`");
    }

    if (!hasBeginDate && !hasEndDate && !hasHost) {
      throw new TypeError("Expected a non-empty filter");
    }

    // Host should follow one of these formats
    // The first one matches `localhost` or any other custom set in hostsfile
    // The second one matches *.mozilla.org or mozilla.com etc
    // The third one is for local files
    if (hasHost &&
        !((/^[a-z0-9-]+$/).test(filter.host)) &&
        !((/^(\*\.)?([a-z0-9-]+)(\.[a-z0-9-]+)+$/).test(filter.host)) &&
        (filter.host !== "")) {
      throw new TypeError("Expected well formed hostname string for `host` with atmost 1 wildcard.");
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return PlacesUtils.withConnectionWrapper(
      "History.jsm: removeByFilter",
      db => removeByFilter(db, filter, onResult)
    );
  },

  /**
   * Determine if a page has been visited.
   *
   * @param guidOrURI: (string) or (URL, nsIURI or href)
   *      Either the full URI of the page or the GUID of the page.
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if the page has been visited, `false` otherwise.
   * @throws (Error)
   *      If `guidOrURI` has an unexpected type or if a string provided
   *      is neither not a valid GUID nor a valid URI.
   */
  hasVisits(guidOrURI) {
    // Quick fallback to the cpp version.
    if (guidOrURI instanceof Ci.nsIURI) {
      return new Promise(resolve => {
        PlacesUtils.asyncHistory.isURIVisited(guidOrURI, (aURI, aIsVisited) => {
          resolve(aIsVisited);
        });
      });
    }

    guidOrURI = PlacesUtils.normalizeToURLOrGUID(guidOrURI);
    let isGuid = typeof guidOrURI == "string";
    let sqlFragment = isGuid ? "guid = :val"
                             : "url_hash = hash(:val) AND url = :val "

    return PlacesUtils.promiseDBConnection().then(async db => {
      let rows = await db.executeCached(`SELECT 1 FROM moz_places
                                         WHERE ${sqlFragment}
                                         AND last_visit_date NOTNULL`,
                                        { val: isGuid ? guidOrURI : guidOrURI.href });
      return !!rows.length;
    });
  },

  /**
   * Clear all history.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   */
  clear() {
    return PlacesUtils.withConnectionWrapper("History.jsm: clear",
      clear
    );
  },

  /**
   * Is a value a valid transition type?
   *
   * @param transitionType: (String)
   * @return (Boolean)
   */
  isValidTransition(transitionType) {
    return Object.values(History.TRANSITIONS).includes(transitionType);
  },

  /**
   * Throw if an object is not a Date object.
   */
  ensureDate(arg) {
    if (!arg || typeof arg != "object" || arg.constructor.name != "Date") {
      throw new TypeError("Expected a Date, got " + arg);
    }
  },

   /**
   * Update information for a page.
   *
   * Currently, it supports updating the description and the preview image URL
   * for a page, any other fields will be ignored.
   *
   * Note that this function will ignore the update if the target page has not
   * yet been stored in the database. `History.fetch` could be used to check
   * whether the page and its meta information exist or not. Beware that
   * fetch&update might fail as they are not executed in a single transaction.
   *
   * @param pageInfo: (PageInfo)
   *      pageInfo must contain a URL of the target page. It will be ignored
   *      if a valid page `guid` is also provided.
   *
   *      If a property `description` is provided, the description of the
   *      page is updated. Note that:
   *      1). An empty string or null `description` will clear the existing
   *          value in the database.
   *      2). Descriptions longer than DB_DESCRIPTION_LENGTH_MAX will be
   *          truncated.
   *
   *      If a property `previewImageURL` is provided, the preview image
   *      URL of the page is updated. Note that:
   *      1). A null `previewImageURL` will clear the existing value in the
   *          database.
   *      2). It throws if its length is greater than DB_URL_LENGTH_MAX
   *          defined in PlacesUtils.jsm.
   *
   * @return (Promise)
   *      A promise resolved once the update is complete.
   * @rejects (Error)
   *      Rejects if the update was unsuccessful.
   *
   * @throws (Error)
   *      If `pageInfo` has an unexpected type.
   * @throws (Error)
   *      If `pageInfo` has an invalid `url` or an invalid `guid`.
   * @throws (Error)
   *      If `pageInfo` has neither `description` nor `previewImageURL`.
   * @throws (Error)
   *      If the length of `pageInfo.previewImageURL` is greater than
   *      DB_URL_LENGTH_MAX defined in PlacesUtils.jsm.
   */
  update(pageInfo) {
    let info = PlacesUtils.validatePageInfo(pageInfo, false);

    if (info.description === undefined && info.previewImageURL === undefined) {
      throw new TypeError("pageInfo object must at least have either a description or a previewImageURL property");
    }

    return PlacesUtils.withConnectionWrapper("History.jsm: update", db => update(db, info));
  },


  /**
   * Possible values for the `transition` property of `VisitInfo`
   * objects.
   */

  TRANSITIONS: {
    /**
     * The user followed a link and got a new toplevel window.
     */
    LINK: Ci.nsINavHistoryService.TRANSITION_LINK,

    /**
     * The user typed the page's URL in the URL bar or selected it from
     * URL bar autocomplete results, clicked on it from a history query
     * (from the History sidebar, History menu, or history query in the
     * personal toolbar or Places organizer.
     */
    TYPED: Ci.nsINavHistoryService.TRANSITION_TYPED,

    /**
     * The user followed a bookmark to get to the page.
     */
    BOOKMARK: Ci.nsINavHistoryService.TRANSITION_BOOKMARK,

    /**
     * Some inner content is loaded. This is true of all images on a
     * page, and the contents of the iframe. It is also true of any
     * content in a frame if the user did not explicitly follow a link
     * to get there.
     */
    EMBED: Ci.nsINavHistoryService.TRANSITION_EMBED,

    /**
     * Set when the transition was a permanent redirect.
     */
    REDIRECT_PERMANENT: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,

    /**
     * Set when the transition was a temporary redirect.
     */
    REDIRECT_TEMPORARY: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,

    /**
     * Set when the transition is a download.
     */
    DOWNLOAD: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,

    /**
     * The user followed a link and got a visit in a frame.
     */
    FRAMED_LINK: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK,

    /**
     * The user reloaded a page.
     */
    RELOAD: Ci.nsINavHistoryService.TRANSITION_RELOAD,
  },
});

/**
 * Convert a PageInfo object into the format expected by updatePlaces.
 *
 * Note: this assumes that the PageInfo object has already been validated
 * via PlacesUtils.validatePageInfo.
 *
 * @param pageInfo: (PageInfo)
 * @return (info)
 */
function convertForUpdatePlaces(pageInfo) {
  let info = {
    uri: PlacesUtils.toURI(pageInfo.url),
    title: pageInfo.title,
    visits: [],
  };

  for (let inVisit of pageInfo.visits) {
    let visit = {
      visitDate: PlacesUtils.toPRTime(inVisit.date),
      transitionType: inVisit.transition,
      referrerURI: (inVisit.referrer) ? PlacesUtils.toURI(inVisit.referrer) : undefined,
    };
    info.visits.push(visit);
  }
  return info;
}

/**
 * Convert a list of strings or numbers to its SQL
 * representation as a string.
 */
function sqlList(list) {
  return list.map(JSON.stringify).join();
}

/**
 * Invalidate and recompute the frecency of a list of pages,
 * informing frecency observers.
 *
 * @param db: (Sqlite connection)
 * @param idList: (Array)
 *      The `moz_places` identifiers for the places to invalidate.
 * @return (Promise)
 */
var invalidateFrecencies = async function(db, idList) {
  if (idList.length == 0) {
    return;
  }
  let ids = sqlList(idList);
  await db.execute(
    `UPDATE moz_places
     SET frecency = NOTIFY_FRECENCY(
       CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date
     ) WHERE id in (${ ids })`
  );
  await db.execute(
    `UPDATE moz_places
     SET hidden = 0
     WHERE id in (${ ids })
     AND frecency <> 0`
  );
};

// Inner implementation of History.clear().
var clear = async function(db) {
  await db.executeTransaction(async function() {
    // Remove all non-bookmarked places entries first, this will speed up the
    // triggers work.
    await db.execute(`DELETE FROM moz_places WHERE foreign_count = 0`);
    await db.execute(`DELETE FROM moz_updatehosts_temp`);

    // Expire orphan icons.
    await db.executeCached(`DELETE FROM moz_pages_w_icons
                            WHERE page_url_hash NOT IN (SELECT url_hash FROM moz_places)`);
    await db.executeCached(`DELETE FROM moz_icons
                            WHERE root = 0 AND id NOT IN (SELECT icon_id FROM moz_icons_to_pages)`);

    // Expire annotations.
    await db.execute(`DELETE FROM moz_items_annos WHERE expiration = :expire_session`,
                     { expire_session: Ci.nsIAnnotationService.EXPIRE_SESSION });
    await db.execute(`DELETE FROM moz_annos WHERE id in (
                        SELECT a.id FROM moz_annos a
                        LEFT JOIN moz_places h ON a.place_id = h.id
                        WHERE h.id IS NULL
                           OR expiration = :expire_session
                           OR (expiration = :expire_with_history
                               AND h.last_visit_date ISNULL)
                      )`, { expire_session: Ci.nsIAnnotationService.EXPIRE_SESSION,
                            expire_with_history: Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY });

    // Expire inputhistory.
    await db.execute(`DELETE FROM moz_inputhistory WHERE place_id IN (
                        SELECT i.place_id FROM moz_inputhistory i
                        LEFT JOIN moz_places h ON h.id = i.place_id
                        WHERE h.id IS NULL)`);

    // Remove all history.
    await db.execute("DELETE FROM moz_historyvisits");

    // Invalidate frecencies for the remaining places.
    await db.execute(`UPDATE moz_places SET frecency =
                        (CASE
                          WHEN url_hash BETWEEN hash("place", "prefix_lo") AND
                                                hash("place", "prefix_hi")
                          THEN 0
                          ELSE -1
                          END)
                        WHERE frecency > 0`);
  });

  // Clear the registered embed visits.
  PlacesUtils.history.clearEmbedVisits();

  let observers = PlacesUtils.history.getObservers();
  notify(observers, "onClearHistory");
  // Notify frecency change observers.
  notify(observers, "onManyFrecenciesChanged");
};

/**
 * Clean up pages whose history has been modified, by either
 * removing them entirely (if they are marked for removal,
 * typically because all visits have been removed and there
 * are no more foreign keys such as bookmarks) or updating
 * their frecency (otherwise).
 *
 * @param db: (Sqlite connection)
 *      The database.
 * @param pages: (Array of objects)
 *      Pages that have been touched and that need cleaning up.
 *      Each object should have the following properties:
 *          - id: (number) The `moz_places` identifier for the place.
 *          - hasVisits: (boolean) If `true`, there remains at least one
 *              visit to this page, so the page should be kept and its
 *              frecency updated.
 *          - hasForeign: (boolean) If `true`, the page has at least
 *              one foreign reference (i.e. a bookmark), so the page should
 *              be kept and its frecency updated.
 * @return (Promise)
 */
var cleanupPages = async function(db, pages) {
  await invalidateFrecencies(db, pages.filter(p => p.hasForeign || p.hasVisits).map(p => p.id));

  let pageIdsToRemove = pages.filter(p => !p.hasForeign && !p.hasVisits).map(p => p.id);
  if (pageIdsToRemove.length > 0) {
    let idsList = sqlList(pageIdsToRemove);
    // Note, we are already in a transaction, since callers create it.
    // Check relations regardless, to avoid creating orphans in case of
    // async race conditions.
    await db.execute(`DELETE FROM moz_places WHERE id IN ( ${ idsList } )
                      AND foreign_count = 0 AND last_visit_date ISNULL`);
    // Hosts accumulated during the places delete are updated through a trigger
    // (see nsPlacesTriggers.h).
    await db.executeCached(`DELETE FROM moz_updatehosts_temp`);

    // Expire orphans.
    await db.executeCached(`DELETE FROM moz_pages_w_icons
                            WHERE page_url_hash NOT IN (SELECT url_hash FROM moz_places)`);
    await db.executeCached(`DELETE FROM moz_icons
                            WHERE root = 0 AND id NOT IN (SELECT icon_id FROM moz_icons_to_pages)`);
    await db.execute(`DELETE FROM moz_annos
                      WHERE place_id IN ( ${ idsList } )`);
    await db.execute(`DELETE FROM moz_inputhistory
                      WHERE place_id IN ( ${ idsList } )`);
  }
};

/**
 * Notify observers that pages have been removed/updated.
 *
 * @param db: (Sqlite connection)
 *      The database.
 * @param pages: (Array of objects)
 *      Pages that have been touched and that need cleaning up.
 *      Each object should have the following properties:
 *          - id: (number) The `moz_places` identifier for the place.
 *          - hasVisits: (boolean) If `true`, there remains at least one
 *              visit to this page, so the page should be kept and its
 *              frecency updated.
 *          - hasForeign: (boolean) If `true`, the page has at least
 *              one foreign reference (i.e. a bookmark), so the page should
 *              be kept and its frecency updated.
 * @return (Promise)
 */
var notifyCleanup = async function(db, pages) {
  let notifiedCount = 0;
  let observers = PlacesUtils.history.getObservers();

  let reason = Ci.nsINavHistoryObserver.REASON_DELETED;

  for (let page of pages) {
    let uri = NetUtil.newURI(page.url.href);
    let guid = page.guid;
    if (page.hasVisits) {
      // For the moment, we do not have the necessary observer API
      // to notify when we remove a subset of visits, see bug 937560.
      continue;
    }
    if (page.hasForeign) {
      // We have removed all visits, but the page is still alive, e.g.
      // because of a bookmark.
      notify(observers, "onDeleteVisits",
        [uri, /* last visit*/0, guid, reason, -1]);
    } else {
      // The page has been entirely removed.
      notify(observers, "onDeleteURI",
        [uri, guid, reason]);
    }
    if (++notifiedCount % NOTIFICATION_CHUNK_SIZE == 0) {
      // Every few notifications, yield time back to the main
      // thread to avoid jank.
      await Promise.resolve();
    }
  }
};

/**
 * Notify an `onResult` callback of a set of operations
 * that just took place.
 *
 * @param data: (Array)
 *      The data to send to the callback.
 * @param onResult: (function [optional])
 *      If provided, call `onResult` with `data[0]`, `data[1]`, etc.
 *      Otherwise, do nothing.
 */
var notifyOnResult = async function(data, onResult) {
  if (!onResult) {
    return;
  }
  let notifiedCount = 0;
  for (let info of data) {
    try {
      onResult(info);
    } catch (ex) {
      // Errors should be reported but should not stop the operation.
      Promise.reject(ex);
    }
    if (++notifiedCount % ONRESULT_CHUNK_SIZE == 0) {
      // Every few notifications, yield time back to the main
      // thread to avoid jank.
      await Promise.resolve();
    }
  }
};

// Inner implementation of History.fetch.
var fetch = async function(db, guidOrURL, options) {
  let whereClauseFragment = "";
  let params = {};
  if (guidOrURL instanceof URL) {
    whereClauseFragment = "WHERE h.url_hash = hash(:url) AND h.url = :url";
    params.url = guidOrURL.href;
  } else {
    whereClauseFragment = "WHERE h.guid = :guid";
    params.guid = guidOrURL;
  }

  let visitSelectionFragment = "";
  let joinFragment = "";
  let visitOrderFragment = ""
  if (options.includeVisits) {
    visitSelectionFragment = ", v.visit_date, v.visit_type";
    joinFragment = "JOIN moz_historyvisits v ON h.id = v.place_id";
    visitOrderFragment = "ORDER BY v.visit_date DESC";
  }

  let pageMetaSelectionFragment = "";
  if (options.includeMeta) {
    pageMetaSelectionFragment = ", description, preview_image_url";
  }

  let query = `SELECT h.id, guid, url, title, frecency
               ${pageMetaSelectionFragment} ${visitSelectionFragment}
               FROM moz_places h ${joinFragment}
               ${whereClauseFragment}
               ${visitOrderFragment}`;
  let pageInfo = null;
  await db.executeCached(
    query,
    params,
    row => {
      if (pageInfo === null) {
        // This means we're on the first row, so we need to get all the page info.
        pageInfo = {
          guid: row.getResultByName("guid"),
          url: new URL(row.getResultByName("url")),
          frecency: row.getResultByName("frecency"),
          title: row.getResultByName("title") || ""
        };
      }
      if (options.includeMeta) {
        pageInfo.description = row.getResultByName("description") || ""
        let previewImageURL = row.getResultByName("preview_image_url");
        pageInfo.previewImageURL = previewImageURL ? new URL(previewImageURL) : null;
      }
      if (options.includeVisits) {
        // On every row (not just the first), we need to collect visit data.
        if (!("visits" in pageInfo)) {
          pageInfo.visits = [];
        }
        let date = PlacesUtils.toDate(row.getResultByName("visit_date"));
        let transition = row.getResultByName("visit_type");

        // TODO: Bug #1365913 add referrer URL to the `VisitInfo` data as well.
        pageInfo.visits.push({ date, transition });
      }
    });
  return pageInfo;
};

// Inner implementation of History.removeVisitsByFilter.
var removeVisitsByFilter = async function(db, filter, onResult = null) {
  // 1. Determine visits that took place during the interval.  Note
  // that the database uses microseconds, while JS uses milliseconds,
  // so we need to *1000 one way and /1000 the other way.
  let conditions = [];
  let args = {};
  if ("beginDate" in filter) {
    conditions.push("v.visit_date >= :begin * 1000");
    args.begin = Number(filter.beginDate);
  }
  if ("endDate" in filter) {
    conditions.push("v.visit_date <= :end * 1000");
    args.end = Number(filter.endDate);
  }
  if ("limit" in filter) {
    args.limit = Number(filter.limit);
  }

  let optionalJoin = "";
  if ("url" in filter) {
    let url = filter.url;
    if (url instanceof Ci.nsIURI) {
      url = filter.url.spec;
    } else {
      url = new URL(url).href;
    }
    optionalJoin = `JOIN moz_places h ON h.id = v.place_id`;
    conditions.push("h.url_hash = hash(:url)", "h.url = :url");
    args.url = url;
  }


  let visitsToRemove = [];
  let pagesToInspect = new Set();
  let onResultData = onResult ? [] : null;

  await db.executeCached(
     `SELECT v.id, place_id, visit_date / 1000 AS date, visit_type FROM moz_historyvisits v
             ${optionalJoin}
             WHERE ${ conditions.join(" AND ") }${ args.limit ? " LIMIT :limit" : "" }`,
     args,
     row => {
       let id = row.getResultByName("id");
       let place_id = row.getResultByName("place_id");
       visitsToRemove.push(id);
       pagesToInspect.add(place_id);

       if (onResult) {
         onResultData.push({
           date: new Date(row.getResultByName("date")),
           transition: row.getResultByName("visit_type")
         });
       }
     }
  );

  try {
    if (visitsToRemove.length == 0) {
      // Nothing to do
      return false;
    }

    let pages = [];
    await db.executeTransaction(async function() {
      // 2. Remove all offending visits.
      await db.execute(`DELETE FROM moz_historyvisits
                        WHERE id IN (${ sqlList(visitsToRemove) } )`);

      // 3. Find out which pages have been orphaned
      await db.execute(
        `SELECT id, url, guid,
          (foreign_count != 0) AS has_foreign,
          (last_visit_date NOTNULL) as has_visits
         FROM moz_places
         WHERE id IN (${ sqlList([...pagesToInspect]) })`,
         null,
         row => {
           let page = {
             id:  row.getResultByName("id"),
             guid: row.getResultByName("guid"),
             hasForeign: row.getResultByName("has_foreign"),
             hasVisits: row.getResultByName("has_visits"),
             url: new URL(row.getResultByName("url")),
           };
           pages.push(page);
         });

      // 4. Clean up and notify
      await cleanupPages(db, pages);
    });

    notifyCleanup(db, pages);
    notifyOnResult(onResultData, onResult); // don't wait
  } finally {
    // Ensure we cleanup embed visits, even if we bailed out early.
    PlacesUtils.history.clearEmbedVisits();
  }

  return visitsToRemove.length != 0;
};

// Inner implementation of History.removeByFilter
var removeByFilter = async function(db, filter, onResult = null) {
  // 1. Create fragment for date filtration
  let dateFilterSQLFragment = "";
  let conditions = [];
  let params = {};
  if ("beginDate" in filter) {
    conditions.push("v.visit_date >= :begin");
    params.begin = PlacesUtils.toPRTime(filter.beginDate);
  }
  if ("endDate" in filter) {
    conditions.push("v.visit_date <= :end");
    params.end = PlacesUtils.toPRTime(filter.endDate);
  }

  if (conditions.length !== 0) {
    dateFilterSQLFragment =
      `EXISTS
         (SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id AND
         ${ conditions.join(" AND ") }
         LIMIT 1)`;
  }

  // 2. Create fragment for host and subhost filtering
  let hostFilterSQLFragment = "";
  if (filter.host || filter.host === "") {
    // There are four cases that we need to consider,
    // mozilla.org, *.mozilla.org, localhost, and local files

    if (filter.host.indexOf("*") === 0) {
      // Case 1: subhost wildcard is specified (*.mozilla.org)
      let revHost = filter.host.slice(2).split("").reverse().join("");
      hostFilterSQLFragment =
        `h.rev_host between :revHostStart and :revHostEnd`;
      params.revHostStart = revHost + ".";
      params.revHostEnd = revHost + "/";
    } else {
      // This covers the rest (mozilla.org, localhost and local files)
      let revHost = filter.host.split("").reverse().join("") + ".";
      hostFilterSQLFragment =
        `h.rev_host = :hostName`;
      params.hostName = revHost;
    }
  }

  // 3. Find out what needs to be removed
  let fragmentArray = [hostFilterSQLFragment, dateFilterSQLFragment];
  let query =
      `SELECT h.id, url, rev_host, guid, title, frecency, foreign_count
       FROM moz_places h WHERE
       (${ fragmentArray.filter(f => f !== "").join(") AND (") })`;
  let onResultData = onResult ? [] : null;
  let pages = [];
  let hasPagesToRemove = false;

  await db.executeCached(
    query,
    params,
    row => {
      let hasForeign = row.getResultByName("foreign_count") != 0;
      if (!hasForeign) {
        hasPagesToRemove = true;
      }
      let id = row.getResultByName("id");
      let guid = row.getResultByName("guid");
      let url = row.getResultByName("url");
      let page = {
        id,
        guid,
        hasForeign,
        hasVisits: false,
        url: new URL(url)
      };
      pages.push(page);
      if (onResult) {
        onResultData.push({
          guid,
          title: row.getResultByName("title"),
          frecency: row.getResultByName("frecency"),
          url: new URL(url)
        });
      }
    });

  if (pages.length === 0) {
    // Nothing to do
    return false;
  }

  try {
    await db.executeTransaction(async function() {
      // 4. Actually remove visits
      await db.execute(`DELETE FROM moz_historyvisits
                        WHERE place_id IN(${ sqlList(pages.map(p => p.id)) })`);
      // 5. Clean up and notify
      await cleanupPages(db, pages);
    });

    notifyCleanup(db, pages);
    notifyOnResult(onResultData, onResult);
  } finally {
    PlacesUtils.history.clearEmbedVisits();
  }

  return hasPagesToRemove;
};

// Inner implementation of History.remove.
var remove = async function(db, {guids, urls}, onResult = null) {
  // 1. Find out what needs to be removed
  let query =
    `SELECT id, url, guid, foreign_count, title, frecency
     FROM moz_places
     WHERE guid IN (${ sqlList(guids) })
        OR (url_hash IN (${ urls.map(u => "hash(" + JSON.stringify(u) + ")").join(",") })
            AND url IN (${ sqlList(urls) }))
    `;

  let onResultData = onResult ? [] : null;
  let pages = [];
  let hasPagesToRemove = false;
  await db.execute(query, null, function(row) {
    let hasForeign = row.getResultByName("foreign_count") != 0;
    if (!hasForeign) {
      hasPagesToRemove = true;
    }
    let id = row.getResultByName("id");
    let guid = row.getResultByName("guid");
    let url = row.getResultByName("url");
    let page = {
      id,
      guid,
      hasForeign,
      hasVisits: false,
      url: new URL(url),
    };
    pages.push(page);
    if (onResult) {
      onResultData.push({
        guid,
        title: row.getResultByName("title"),
        frecency: row.getResultByName("frecency"),
        url: new URL(url)
      });
    }
  });

  try {
    if (pages.length == 0) {
      // Nothing to do
      return false;
    }

    await db.executeTransaction(async function() {
      // 2. Remove all visits to these pages.
      await db.execute(`DELETE FROM moz_historyvisits
                        WHERE place_id IN (${ sqlList(pages.map(p => p.id)) })
                       `);

      // 3. Clean up and notify
      await cleanupPages(db, pages);
    });

    notifyCleanup(db, pages);
    notifyOnResult(onResultData, onResult); // don't wait
  } finally {
    // Ensure we cleanup embed visits, even if we bailed out early.
    PlacesUtils.history.clearEmbedVisits();
  }

  return hasPagesToRemove;
};

/**
 * Merges an updateInfo object, as returned by asyncHistory.updatePlaces
 * into a PageInfo object as defined in this file.
 *
 * @param updateInfo: (Object)
 *      An object that represents a page that is generated by
 *      asyncHistory.updatePlaces.
 * @param pageInfo: (PageInfo)
 *      An PageInfo object into which to merge the data from updateInfo.
 *      Defaults to an empty object so that this method can be used
 *      to simply convert an updateInfo object into a PageInfo object.
 *
 * @return (PageInfo)
 *      A PageInfo object populated with data from updateInfo.
 */
function mergeUpdateInfoIntoPageInfo(updateInfo, pageInfo = {}) {
  pageInfo.guid = updateInfo.guid;
  if (!pageInfo.url) {
    pageInfo.url = new URL(updateInfo.uri.spec);
    pageInfo.title = updateInfo.title;
    pageInfo.visits = updateInfo.visits.map(visit => {
      return {
        date: PlacesUtils.toDate(visit.visitDate),
        transition: visit.transitionType,
        referrer: (visit.referrerURI) ? new URL(visit.referrerURI.spec) : null
      }
    });
  }
  return pageInfo;
}

// Inner implementation of History.insert.
var insert = function(db, pageInfo) {
  let info = convertForUpdatePlaces(pageInfo);

  return new Promise((resolve, reject) => {
    PlacesUtils.asyncHistory.updatePlaces(info, {
      handleError: error => {
        reject(error);
      },
      handleResult: result => {
        pageInfo = mergeUpdateInfoIntoPageInfo(result, pageInfo);
      },
      handleCompletion: () => {
        resolve(pageInfo);
      }
    });
  });
};

// Inner implementation of History.insertMany.
var insertMany = function(db, pageInfos, onResult, onError) {
  let infos = [];
  let onResultData = [];
  let onErrorData = [];

  for (let pageInfo of pageInfos) {
    let info = convertForUpdatePlaces(pageInfo);
    infos.push(info);
  }

  return new Promise((resolve, reject) => {
    PlacesUtils.asyncHistory.updatePlaces(infos, {
      handleError: (resultCode, result) => {
        let pageInfo = mergeUpdateInfoIntoPageInfo(result);
        onErrorData.push(pageInfo);
      },
      handleResult: result => {
        let pageInfo = mergeUpdateInfoIntoPageInfo(result);
        onResultData.push(pageInfo);
      },
      ignoreErrors: !onError,
      ignoreResults: !onResult,
      handleCompletion: (updatedCount) => {
        notifyOnResult(onResultData, onResult);
        notifyOnResult(onErrorData, onError);
        if (updatedCount > 0) {
          resolve();
        } else {
          reject({message: "No items were added to history."})
        }
      }
    }, true);
  });
};

// Inner implementation of History.update.
var update = async function(db, pageInfo) {
  let updateFragments = [];
  let whereClauseFragment = "";
  let info = {};

  // Prefer GUID over url if it's present
  if (typeof pageInfo.guid === "string") {
    whereClauseFragment = "WHERE guid = :guid";
    info.guid = pageInfo.guid;
  } else {
    whereClauseFragment = "WHERE url_hash = hash(:url) AND url = :url";
    info.url = pageInfo.url.href;
  }

  if (pageInfo.description || pageInfo.description === null) {
    updateFragments.push("description = :description");
    info.description = pageInfo.description;
  }
  if (pageInfo.previewImageURL || pageInfo.previewImageURL === null) {
    updateFragments.push("preview_image_url = :previewImageURL");
    info.previewImageURL = pageInfo.previewImageURL ? pageInfo.previewImageURL.href : null;
  }
  let query = `UPDATE moz_places
               SET ${updateFragments.join(", ")}
               ${whereClauseFragment}`;
  await db.execute(query, info);
}
PK
!<Zmodules/Http.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const EXPORTED_SYMBOLS = ["httpRequest", "percentEncode"];

const {classes: Cc, interfaces: Ci} = Components;

// Strictly follow RFC 3986 when encoding URI components.
// Accepts a unescaped string and returns the URI encoded string for use in
// an HTTP request.
function percentEncode(aString) {
  return encodeURIComponent(aString).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
}

/*
 * aOptions can have a variety of fields:
 *  headers, an array of headers
 *  postData, this can be:
 *    a string: send it as is
 *    an array of parameters: encode as form values
 *    null/undefined: no POST data.
 *  method, GET, POST or PUT (this is set automatically if postData exists).
 *  onLoad, a function handle to call when the load is complete, it takes two
 *          parameters: the responseText and the XHR object.
 *  onError, a function handle to call when an error occcurs, it takes three
 *          parameters: the error, the responseText and the XHR object.
 *  logger, an object that implements the debug and log methods (e.g. log.jsm).
 *
 * Headers or post data are given as an array of arrays, for each each inner
 * array the first value is the key and the second is the value, e.g.
 *  [["key1", "value1"], ["key2", "value2"]].
 */
function httpRequest(aUrl, aOptions) {
  let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
              .createInstance(Ci.nsIXMLHttpRequest);
  xhr.mozBackgroundRequest = true; // no error dialogs
  xhr.open(aOptions.method || (aOptions.postData ? "POST" : "GET"), aUrl);
  xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS | // don't send cookies
                          Ci.nsIChannel.LOAD_BYPASS_CACHE |
                          Ci.nsIChannel.INHIBIT_CACHING;
  xhr.onerror = function(aProgressEvent) {
    if (aOptions.onError) {
      // adapted from toolkit/mozapps/extensions/nsBlocklistService.js
      let request = aProgressEvent.target;
      let status;
      try {
        // may throw (local file or timeout)
        status = request.status;
      } catch (e) {
        request = request.channel.QueryInterface(Ci.nsIRequest);
        status = request.status;
      }
      // When status is 0 we don't have a valid channel.
      let statusText = status ? request.statusText : "offline";
      aOptions.onError(statusText, null, this);
    }
  };
  xhr.onload = function(aRequest) {
    try {
      let target = aRequest.target;
      if (aOptions.logger)
        aOptions.logger.debug("Received response: " + target.responseText);
      if (target.status < 200 || target.status >= 300) {
        let errorText = target.responseText;
        if (!errorText || /<(ht|\?x)ml\b/i.test(errorText))
          errorText = target.statusText;
        throw target.status + " - " + errorText;
      }
      if (aOptions.onLoad)
        aOptions.onLoad(target.responseText, this);
    } catch (e) {
      if (aOptions.onError)
        aOptions.onError(e, aRequest.target.responseText, this);
    }
  };

  if (aOptions.headers) {
    aOptions.headers.forEach(function(header) {
      xhr.setRequestHeader(header[0], header[1]);
    });
  }

  // Handle adding postData as defined above.
  let POSTData = aOptions.postData || null;
  if (POSTData && Array.isArray(POSTData)) {
    xhr.setRequestHeader("Content-Type",
                         "application/x-www-form-urlencoded; charset=utf-8");
    POSTData = POSTData.map(p => p[0] + "=" + percentEncode(p[1]))
                       .join("&");
  }

  if (aOptions.logger) {
    aOptions.logger.log("sending request to " + aUrl + " (POSTData = " +
                        POSTData + ")");
  }
  xhr.send(POSTData);
  return xhr;
}
PK
!<_modules/ISO8601DateUtils.jsm/* -*- 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 HOURS_TO_MINUTES = 60;
const MINUTES_TO_SECONDS = 60;
const SECONDS_TO_MILLISECONDS = 1000;
const MINUTES_TO_MILLISECONDS = MINUTES_TO_SECONDS * SECONDS_TO_MILLISECONDS;
const HOURS_TO_MILLISECONDS = HOURS_TO_MINUTES * MINUTES_TO_MILLISECONDS;

this.EXPORTED_SYMBOLS = ["ISO8601DateUtils"];

debug("*** loading ISO8601DateUtils\n");

this.ISO8601DateUtils = {

  /**
  * XXX Thunderbird's W3C-DTF function
  *
  * Converts a W3C-DTF (subset of ISO 8601) date string to a Javascript
  * date object. W3C-DTF is described in this note:
  * http://www.w3.org/TR/NOTE-datetime IETF is obtained via the Date
  * object's toUTCString() method.  The object's toString() method is
  * insufficient because it spells out timezones on Win32
  * (f.e. "Pacific Standard Time" instead of "PST"), which Mail doesn't
  * grok.  For info, see
  * http://lxr.mozilla.org/mozilla/source/js/src/jsdate.c#1526.
  */
  parse: function ISO8601_parse(aDateString) {
    var dateString = aDateString;
    if (!dateString.match('-')) {
      // Workaround for server sending
      // dates such as: 20030530T11:18:50-08:00
      // instead of: 2003-05-30T11:18:50-08:00
      var year = dateString.slice(0, 4);
      var month = dateString.slice(4, 6);
      var rest = dateString.slice(6, dateString.length);
      dateString = year + "-" + month + "-" + rest;
    }

    var parts = dateString.match(/(\d{4})(-(\d{2,3}))?(-(\d{2}))?(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|([+-])(\d{2}):(\d{2}))?)?/);

    // Here's an example of a W3C-DTF date string and what .match returns for it.
    //
    // date: 2003-05-30T11:18:50.345-08:00
    // date.match returns array values:
    //
    //   0: 2003-05-30T11:18:50-08:00,
    //   1: 2003,
    //   2: -05,
    //   3: 05,
    //   4: -30,
    //   5: 30,
    //   6: T11:18:50-08:00,
    //   7: 11,
    //   8: 18,
    //   9: :50,
    //   10: 50,
    //   11: .345,
    //   12: 345,
    //   13: -08:00,
    //   14: -,
    //   15: 08,
    //   16: 00

    // Create a Date object from the date parts.  Note that the Date
    // object apparently can't deal with empty string parameters in lieu
    // of numbers, so optional values (like hours, minutes, seconds, and
    // milliseconds) must be forced to be numbers.
    var date = new Date(parts[1], parts[3] - 1, parts[5], parts[7] || 0,
      parts[8] || 0, parts[10] || 0, parts[12] || 0);

    // We now have a value that the Date object thinks is in the local
    // timezone but which actually represents the date/time in the
    // remote timezone (f.e. the value was "10:00 EST", and we have
    // converted it to "10:00 PST" instead of "07:00 PST").  We need to
    // correct that.  To do so, we're going to add the offset between
    // the remote timezone and UTC (to convert the value to UTC), then
    // add the offset between UTC and the local timezone //(to convert
    // the value to the local timezone).

    // Ironically, W3C-DTF gives us the offset between UTC and the
    // remote timezone rather than the other way around, while the
    // getTimezoneOffset() method of a Date object gives us the offset
    // between the local timezone and UTC rather than the other way
    // around.  Both of these are the additive inverse (i.e. -x for x)
    // of what we want, so we have to invert them to use them by
    // multipying by -1 (f.e. if "the offset between UTC and the remote
    // timezone" is -5 hours, then "the offset between the remote
    // timezone and UTC" is -5*-1 = 5 hours).

    // Note that if the timezone portion of the date/time string is
    // absent (which violates W3C-DTF, although ISO 8601 allows it), we
    // assume the value to be in UTC.

    // The offset between the remote timezone and UTC in milliseconds.
    var remoteToUTCOffset = 0;
    if (parts[13] && parts[13] != "Z") {
      var direction = (parts[14] == "+" ? 1 : -1);
      if (parts[15])
        remoteToUTCOffset += direction * parts[15] * HOURS_TO_MILLISECONDS;
      if (parts[16])
        remoteToUTCOffset += direction * parts[16] * MINUTES_TO_MILLISECONDS;
    }
    remoteToUTCOffset = remoteToUTCOffset * -1; // invert it

    // The offset between UTC and the local timezone in milliseconds.
    var UTCToLocalOffset = date.getTimezoneOffset() * MINUTES_TO_MILLISECONDS;
    UTCToLocalOffset = UTCToLocalOffset * -1; // invert it
    date.setTime(date.getTime() + remoteToUTCOffset + UTCToLocalOffset);

    return date;
  },

  create: function ISO8601_create(aDate) {
    function zeropad (s, l) {
      s = s.toString(); // force it to a string
      while (s.length < l) {
        s = '0' + s;
      }
      return s;
    }

    var myDate;
    // if d is a number, turn it into a date
    if (typeof aDate == 'number') {
      myDate = new Date()
      myDate.setTime(aDate);
    } else {
      myDate = aDate;
    }

    // YYYY-MM-DDThh:mm:ssZ
    var result = zeropad(myDate.getUTCFullYear (), 4) +
                 zeropad(myDate.getUTCMonth () + 1, 2) +
                 zeropad(myDate.getUTCDate (), 2) + 'T' +
                 zeropad(myDate.getUTCHours (), 2) + ':' +
                 zeropad(myDate.getUTCMinutes (), 2) + ':' +
                 zeropad(myDate.getUTCSeconds (), 2) + 'Z';

    return result;
  }
}
PK
!<p modules/ImageObjectProcessor.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
 * ImageObjectProcessor
 * Implementation of Image Object processing algorithms from:
 * http://www.w3.org/TR/appmanifest/#image-object-and-its-members
 *
 * This is intended to be used in conjunction with ManifestProcessor.jsm
 *
 * Creates an object to process Image Objects as defined by the
 * W3C specification. This is used to process things like the
 * icon member and the splash_screen member.
 *
 * Usage:
 *
 *   .process(aManifest, aBaseURL, aMemberName);
 *
 */
/*exported EXPORTED_SYMBOLS*/
/*globals Components */
'use strict';
const {
  utils: Cu,
  interfaces: Ci,
  classes: Cc
} = Components;

Cu.importGlobalProperties(['URL']);
const netutil = Cc['@mozilla.org/network/util;1']
  .getService(Ci.nsINetUtil);

function ImageObjectProcessor(aConsole, aExtractor) {
  this.console = aConsole;
  this.extractor = aExtractor;
}

// Static getters
Object.defineProperties(ImageObjectProcessor, {
  'decimals': {
    get: function() {
      return /^\d+$/;
    }
  },
  'anyRegEx': {
    get: function() {
      return new RegExp('any', 'i');
    }
  }
});

ImageObjectProcessor.prototype.process = function(
  aManifest, aBaseURL, aMemberName
) {
  const spec = {
    objectName: 'manifest',
    object: aManifest,
    property: aMemberName,
    expectedType: 'array',
    trim: false
  };
  const extractor = this.extractor;
  const images = [];
  const value = extractor.extractValue(spec);
  if (Array.isArray(value)) {
    // Filter out images whose "src" is not useful.
    value.filter(item => !!processSrcMember(item, aBaseURL))
      .map(toImageObject)
      .forEach(image => images.push(image));
  }
  return images;

  function toImageObject(aImageSpec) {
    return {
      'src': processSrcMember(aImageSpec, aBaseURL),
      'type': processTypeMember(aImageSpec),
      'sizes': processSizesMember(aImageSpec),
    };
  }

  function processTypeMember(aImage) {
    const charset = {};
    const hadCharset = {};
    const spec = {
      objectName: 'image',
      object: aImage,
      property: 'type',
      expectedType: 'string',
      trim: true
    };
    let value = extractor.extractValue(spec);
    if (value) {
      value = netutil.parseRequestContentType(value, charset, hadCharset);
    }
    return value || undefined;
  }

  function processSrcMember(aImage, aBaseURL) {
    const spec = {
      objectName: 'image',
      object: aImage,
      property: 'src',
      expectedType: 'string',
      trim: false
    };
    const value = extractor.extractValue(spec);
    let url;
    if (value && value.length) {
      try {
        url = new URL(value, aBaseURL).href;
      } catch (e) {}
    }
    return url;
  }

  function processSizesMember(aImage) {
    const sizes = new Set();
    const spec = {
      objectName: 'image',
      object: aImage,
      property: 'sizes',
      expectedType: 'string',
      trim: true
    };
    const value = extractor.extractValue(spec);
    if (value) {
      // Split on whitespace and filter out invalid values.
      value.split(/\s+/)
        .filter(isValidSizeValue)
        .reduce((collector, size) => collector.add(size), sizes);
    }
    return (sizes.size) ? Array.from(sizes).join(" ") : undefined;
    // Implementation of HTML's link@size attribute checker.
    function isValidSizeValue(aSize) {
      const size = aSize.toLowerCase();
      if (ImageObjectProcessor.anyRegEx.test(aSize)) {
        return true;
      }
      if (!size.includes('x') || size.indexOf('x') !== size.lastIndexOf('x')) {
        return false;
      }
      // Split left of x for width, after x for height.
      const widthAndHeight = size.split('x');
      const w = widthAndHeight.shift();
      const h = widthAndHeight.join('x');
      const validStarts = !w.startsWith('0') && !h.startsWith('0');
      const validDecimals = ImageObjectProcessor.decimals.test(w + h);
      return (validStarts && validDecimals);
    }
  }
};
this.ImageObjectProcessor = ImageObjectProcessor; // jshint ignore:line
this.EXPORTED_SYMBOLS = ['ImageObjectProcessor']; // jshint ignore:line
PK
!<''modules/IndexedDB.jsm/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/**
 * @file
 *
 * This module provides Promise-based wrappers around ordinarily
 * IDBRequest-based IndexedDB methods and classes.
 */

/* exported IndexedDB */
var EXPORTED_SYMBOLS = ["IndexedDB"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.importGlobalProperties(["indexedDB"]);

/**
 * Wraps the given request object, and returns a Promise which resolves when
 * the requests succeeds or rejects when it fails.
 *
 * @param {IDBRequest} request
 *        An IndexedDB request object to wrap.
 * @returns {Promise}
 */
function wrapRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => {
      resolve(request.result);
    };
    request.onerror = () => {
      reject(request.error);
    };
  });
}

/**
 * Forwards a set of getter properties from a wrapper class to the wrapped
 * object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the getters.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the getters.
 * @param {Array<string>} props
 *        A list of property names to forward.
 */
function forwardGetters(cls, target, props) {
  for (let prop of props) {
    Object.defineProperty(cls.prototype, prop, {
      get() {
        return this[target][prop];
      },
    });
  }
}

/**
 * Forwards a set of getter and setter properties from a wrapper class to the
 * wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the properties.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the properties.
 * @param {Array<string>} props
 *        A list of property names to forward.
 */
function forwardProps(cls, target, props) {
  for (let prop of props) {
    Object.defineProperty(cls.prototype, prop, {
      get() {
        return this[target][prop];
      },
      set(value) {
        this[target][prop] = value;
      },
    });
  }
}

/**
 * Wraps a set of IDBRequest-based methods via {@link wrapRequest} and
 * forwards them to the equivalent methods on the wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the methods.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the methods.
 * @param {Array<string>} methods
 *        A list of method names to forward.
 */
function wrapMethods(cls, target, methods) {
  for (let method of methods) {
    cls.prototype[method] = function(...args) {
      return wrapRequest(this[target][method](...args));
    };
  }
}

/**
 * Forwards a set of methods from a wrapper class to the wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the getters.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the methods.
 * @param {Array<string>} methods
 *        A list of method names to forward.
 */
function forwardMethods(cls, target, methods) {
  for (let method of methods) {
    cls.prototype[method] = function(...args) {
      return this[target][method](...args);
    };
  }
}

class Cursor {
  constructor(cursor, source) {
    this.cursor = cursor;
    this.source = source;
  }
}

forwardGetters(Cursor, "cursor",
               ["direction", "key", "primaryKey"]);

wrapMethods(Cursor, "cursor", ["delete", "update"]);

forwardMethods(Cursor, "cursor",
               ["advance", "continue", "continuePrimaryKey"]);

class CursorWithValue extends Cursor {}

forwardGetters(CursorWithValue, "cursor", ["value"]);

class Cursed {
  constructor(cursed) {
    this.cursed = cursed;
  }

  openCursor(...args) {
    return wrapRequest(this.cursed.openCursor(...args)).then(cursor => {
      return new CursorWithValue(cursor, this);
    });
  }

  openKeyCursor(...args) {
    return wrapRequest(this.cursed.openKeyCursor(...args)).then(cursor => {
      return new Cursor(cursor, this);
    });
  }
}

wrapMethods(Cursed, "cursed",
            ["count", "get", "getAll", "getAllKeys", "getKey"]);

class Index extends Cursed {
  constructor(index, objectStore) {
    super(index);

    this.objectStore = objectStore;
    this.index = index;
  }
}

forwardGetters(Index, "index",
               ["isAutoLocale", "keyPath", "locale", "multiEntry", "name", "unique"]);

class ObjectStore extends Cursed {
  constructor(store) {
    super(store);

    this.store = store;
  }

  createIndex(...args) {
    return new Index(this.store.createIndex(...args),
                     this);
  }

  index(...args) {
    return new Index(this.store.index(...args),
                     this);
  }
}

wrapMethods(ObjectStore, "store",
            ["add", "clear", "delete", "put"]);

forwardMethods(ObjectStore, "store", ["deleteIndex"]);

class Transaction {
  constructor(transaction) {
    this.transaction = transaction;

    this._completionPromise = new Promise((resolve, reject) => {
      transaction.oncomplete = resolve;
      transaction.onerror = () => {
        reject(transaction.error);
      };
    });
  }

  objectStore(name) {
    return new ObjectStore(this.transaction.objectStore(name));
  }

  /**
   * Returns a Promise which resolves when the transaction completes, or
   * rejects when a transaction error occurs.
   *
   * @returns {Promise}
   */
  promiseComplete() {
    return this._completionPromise;
  }
}

forwardGetters(Transaction, "transaction",
               ["db", "mode", "error", "objectStoreNames"]);

forwardMethods(Transaction, "transaction", ["abort"]);

class IndexedDB {
  /**
   * Opens the database with the given name, and returns a Promise which
   * resolves to an IndexedDB instance when the operation completes.
   *
   * @param {string} dbName
   *        The name of the database to open.
   * @param {object} options
   *        The options with which to open the database.
   * @param {integer} options.version
   *        The schema version with which the database needs to be opened. If
   *        the database does not exist, or its current schema version does
   *        not match, the `onupgradeneeded` function will be called.
   * @param {string} [options.storage]
   *        The storage type of the database. If present, may be one of
   *        "temporary" or "persistent".
   * @param {function} [onupgradeneeded]
   *        A function which will be called with an IndexedDB object as its
   *        first parameter when the database needs to be created, or its
   *        schema needs to be upgraded. If this function is not provided, the
   *        {@link #onupgradeneeded} method will be called instead.
   *
   * @returns {Promise<IndexedDB>}
   */
  static open(dbName, options, onupgradeneeded = null) {
    let request = indexedDB.open(dbName, options);

    request.onupgradeneeded = event => {
      let db = new this(request.result);
      if (onupgradeneeded) {
        onupgradeneeded(db, event);
      } else {
        db.onupgradeneeded(event);
      }
    };

    return wrapRequest(request).then(db => new IndexedDB(db));
  }

  constructor(db) {
    this.db = db;
  }

  onupgradeneeded() {}

  /**
   * Opens a transaction for the given object stores.
   *
   * @param {Array<string>} storeNames
   *        The names of the object stores for which to open a transaction.
   * @param {string} [mode = "readonly"]
   *        The mode in which to open the transaction.
   * @param {function} [callback]
   *        An optional callback function. If provided, the function will be
   *        called with the Transaction, and a Promise will be returned, which
   *        will resolve to the callback's return value when the transaction
   *        completes.
   * @returns {Transaction|Promise}
   */
  transaction(storeNames, mode, callback = null) {
    let transaction = new Transaction(this.db.transaction(storeNames, mode));

    if (callback) {
      let result = new Promise(resolve => {
        resolve(callback(transaction));
      });
      return transaction.promiseComplete().then(() => result);
    }

    return transaction;
  }

  /**
   * Opens a transaction for a single object store, and returns that object
   * store.
   *
   * @param {string} storeName
   *        The name of the object store to open.
   * @param {string} [mode = "readonly"]
   *        The mode in which to open the transaction.
   * @param {function} [callback]
   *        An optional callback function. If provided, the function will be
   *        called with the ObjectStore, and a Promise will be returned, which
   *        will resolve to the callback's return value when the transaction
   *        completes.
   * @returns {ObjectStore|Promise}
   */
  objectStore(storeName, mode, callback = null) {
    let transaction = this.transaction([storeName], mode);
    let objectStore = transaction.objectStore(storeName);

    if (callback) {
      let result = new Promise(resolve => {
        resolve(callback(objectStore));
      });
      return transaction.promiseComplete().then(() => result);
    }

    return objectStore;
  }

  createObjectStore(...args) {
    return new ObjectStore(this.db.createObjectStore(...args));
  }
}

for (let method of ["cmp", "deleteDatabase"]) {
  IndexedDB[method] = function(...args) {
    return indexedDB[method](...args);
  };
}

forwardMethods(IndexedDB, "db",
               ["addEventListener", "close", "deleteObjectStore", "hasEventListener", "removeEventListener"]);

forwardGetters(IndexedDB, "db",
               ["name", "objectStoreNames", "version"]);

forwardProps(IndexedDB, "db",
             ["onabort", "onclose", "onerror", "onversionchange"]);
PK
!<V?modules/IndexedDBHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 DEBUG = 0;
var debug;
if (DEBUG) {
  debug = function (s) { dump("-*- IndexedDBHelper: " + s + "\n"); }
} else {
  debug = function (s) {}
}

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

this.EXPORTED_SYMBOLS = ["IndexedDBHelper"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["indexedDB"]);

XPCOMUtils.defineLazyModuleGetter(this, 'Services',
  'resource://gre/modules/Services.jsm');

function getErrorName(err) {
  return err && err.name || "UnknownError";
}

this.IndexedDBHelper = function IndexedDBHelper() {
}

IndexedDBHelper.prototype = {
  // Close the database
  close: function close() {
    if (this._db) {
      this._db.close();
      this._db = null;
    }
  },

  /**
   * Open a new database.
   * User has to provide upgradeSchema.
   *
   * @param successCb
   *        Success callback to call once database is open.
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  open: function open(aCallback) {
    if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) {
      this._waitForOpenCallbacks.add(aCallback);
      if (this._waitForOpenCallbacks.size !== 1) {
        return;
      }
    }

    let self = this;
    let invokeCallbacks = err => {
      for (let callback of self._waitForOpenCallbacks) {
        callback(err);
      }
      self._waitForOpenCallbacks.clear();
    };

    if (DEBUG) debug("Try to open database:" + self.dbName + " " + self.dbVersion);
    let req;
    try {
      req = indexedDB.open(this.dbName, this.dbVersion);
    } catch (e) {
      if (DEBUG) debug("Error opening database: " + self.dbName);
      Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e)));
      return;
    }
    req.onsuccess = function (event) {
      if (DEBUG) debug("Opened database:" + self.dbName + " " + self.dbVersion);
      self._db = event.target.result;
      self._db.onversionchange = function(event) {
        if (DEBUG) debug("WARNING: DB modified from a different window.");
      }
      invokeCallbacks();
    };

    req.onupgradeneeded = function (aEvent) {
      if (DEBUG) {
        debug("Database needs upgrade:" + self.dbName + aEvent.oldVersion + aEvent.newVersion);
        debug("Correct new database version:" + (aEvent.newVersion == this.dbVersion));
      }

      let _db = aEvent.target.result;
      self.upgradeSchema(req.transaction, _db, aEvent.oldVersion, aEvent.newVersion);
    };
    req.onerror = function (aEvent) {
      if (DEBUG) debug("Failed to open database: " + self.dbName);
      invokeCallbacks(getErrorName(aEvent.target.error));
    };
    req.onblocked = function (aEvent) {
      if (DEBUG) debug("Opening database request is blocked.");
    };
  },

  /**
   * Use the cached DB or open a new one.
   *
   * @param successCb
   *        Success callback to call.
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  ensureDB: function ensureDB(aSuccessCb, aFailureCb) {
    if (this._db) {
      if (DEBUG) debug("ensureDB: already have a database, returning early.");
      if (aSuccessCb) {
        Services.tm.dispatchToMainThread(aSuccessCb);
      }
      return;
    }
    this.open(aError => {
      if (aError) {
        aFailureCb && aFailureCb(aError);
      } else {
        aSuccessCb && aSuccessCb();
      }
    });
  },

  /**
   * Start a new transaction.
   *
   * @param txn_type
   *        Type of transaction (e.g. "readwrite")
   * @param store_name
   *        The object store you want to be passed to the callback
   * @param callback
   *        Function to call when the transaction is available. It will
   *        be invoked with the transaction and the `store' object store.
   * @param successCb
   *        Success callback to call on a successful transaction commit.
   *        The result is stored in txn.result.
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  newTxn: function newTxn(txn_type, store_name, callback, successCb, failureCb) {
    this.ensureDB(() => {
      if (DEBUG) debug("Starting new transaction" + txn_type);
      let txn;
      try {
        txn = this._db.transaction(Array.isArray(store_name) ? store_name : this.dbStoreNames, txn_type);
      } catch (e) {
        if (DEBUG) debug("Error starting transaction: " + this.dbName);
        failureCb(getErrorName(e));
        return;
      }
      if (DEBUG) debug("Retrieving object store: " + this.dbName);
      let stores;
      if (Array.isArray(store_name)) {
        stores = [];
        for (let i = 0; i < store_name.length; ++i) {
          stores.push(txn.objectStore(store_name[i]));
        }
      } else {
        stores = txn.objectStore(store_name);
      }

      txn.oncomplete = function (event) {
        if (DEBUG) debug("Transaction complete. Returning to callback.");
        if (successCb) {
          successCb(txn.result);
        }
      };

      txn.onabort = function (event) {
        if (DEBUG) debug("Caught error on transaction");
        /*
         * event.target.error may be null
         * if txn was aborted by calling txn.abort()
         */
        if (failureCb) {
          failureCb(getErrorName(event.target.error));
        }
      };
      callback(txn, stores);
    }, failureCb);
  },

  /**
   * Initialize the DB. Does not call open.
   *
   * @param aDBName
   *        DB name for the open call.
   * @param aDBVersion
   *        Current DB version. User has to implement upgradeSchema.
   * @param aDBStoreName
   *        ObjectStore that is used.
   */
  initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) {
    this.dbName = aDBName;
    this.dbVersion = aDBVersion;
    this.dbStoreNames = aDBStoreNames;
    // Cache the database.
    this._db = null;
    this._waitForOpenCallbacks = new Set();
  }
}
PK
!<015L5Lmodules/InlineSpellChecker.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = [ "InlineSpellChecker",
                          "SpellCheckHelper" ];
var gLanguageBundle;
var gRegionBundle;
const MAX_UNDO_STACK_DEPTH = 1;

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.InlineSpellChecker = function InlineSpellChecker(aEditor) {
  this.init(aEditor);
  this.mAddedWordStack = []; // We init this here to preserve it between init/uninit calls
}

InlineSpellChecker.prototype = {
  // Call this function to initialize for a given editor
  init(aEditor) {
    this.uninit();
    this.mEditor = aEditor;
    try {
      this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
      // note: this might have been NULL if there is no chance we can spellcheck
    } catch (e) {
      this.mInlineSpellChecker = null;
    }
  },

  initFromRemote(aSpellInfo) {
    if (this.mRemote)
      throw new Error("Unexpected state");
    this.uninit();

    if (!aSpellInfo)
      return;
    this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo);
    this.mOverMisspelling = aSpellInfo.overMisspelling;
    this.mMisspelling = aSpellInfo.misspelling;
  },

  // call this to clear state
  uninit() {
    if (this.mRemote) {
      this.mRemote.uninit();
      this.mRemote = null;
    }

    this.mEditor = null;
    this.mInlineSpellChecker = null;
    this.mOverMisspelling = false;
    this.mMisspelling = "";
    this.mMenu = null;
    this.mSpellSuggestions = [];
    this.mSuggestionItems = [];
    this.mDictionaryMenu = null;
    this.mDictionaryNames = [];
    this.mDictionaryItems = [];
    this.mWordNode = null;
  },

  // for each UI event, you must call this function, it will compute the
  // word the cursor is over
  initFromEvent(rangeParent, rangeOffset) {
    this.mOverMisspelling = false;

    if (!rangeParent || !this.mInlineSpellChecker)
      return;

    var selcon = this.mEditor.selectionController;
    var spellsel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
    if (spellsel.rangeCount == 0)
      return; // easy case - no misspellings

    var range = this.mInlineSpellChecker.getMisspelledWord(rangeParent,
                                                          rangeOffset);
    if (!range)
      return; // not over a misspelled word

    this.mMisspelling = range.toString();
    this.mOverMisspelling = true;
    this.mWordNode = rangeParent;
    this.mWordOffset = rangeOffset;
  },

  // returns false if there should be no spellchecking UI enabled at all, true
  // means that you can at least give the user the ability to turn it on.
  get canSpellCheck() {
    // inline spell checker objects will be created only if there are actual
    // dictionaries available
    if (this.mRemote)
      return this.mRemote.canSpellCheck;
    return this.mInlineSpellChecker != null;
  },

  get initialSpellCheckPending() {
    if (this.mRemote) {
      return this.mRemote.spellCheckPending;
    }
    return !!(this.mInlineSpellChecker &&
              !this.mInlineSpellChecker.spellChecker &&
              this.mInlineSpellChecker.spellCheckPending);
  },

  // Whether spellchecking is enabled in the current box
  get enabled() {
    if (this.mRemote)
      return this.mRemote.enableRealTimeSpell;
    return (this.mInlineSpellChecker &&
            this.mInlineSpellChecker.enableRealTimeSpell);
  },
  set enabled(isEnabled) {
    if (this.mRemote)
      this.mRemote.setSpellcheckUserOverride(isEnabled);
    else if (this.mInlineSpellChecker)
      this.mEditor.setSpellcheckUserOverride(isEnabled);
  },

  // returns true if the given event is over a misspelled word
  get overMisspelling() {
    return this.mOverMisspelling;
  },

  // this prepends up to "maxNumber" suggestions at the given menu position
  // for the word under the cursor. Returns the number of suggestions inserted.
  addSuggestionsToMenu(menu, insertBefore, maxNumber) {
    if (!this.mRemote && (!this.mInlineSpellChecker || !this.mOverMisspelling))
      return 0; // nothing to do

    var spellchecker = this.mRemote || this.mInlineSpellChecker.spellChecker;
    try {
      if (!this.mRemote && !spellchecker.CheckCurrentWord(this.mMisspelling))
        return 0;  // word seems not misspelled after all (?)
    } catch (e) {
        return 0;
    }

    this.mMenu = menu;
    this.mSpellSuggestions = [];
    this.mSuggestionItems = [];
    for (var i = 0; i < maxNumber; i++) {
      var suggestion = spellchecker.GetSuggestedWord();
      if (!suggestion.length)
        break;
      this.mSpellSuggestions.push(suggestion);

      var item = menu.ownerDocument.createElement("menuitem");
      this.mSuggestionItems.push(item);
      item.setAttribute("label", suggestion);
      item.setAttribute("value", suggestion);
      // this function thing is necessary to generate a callback with the
      // correct binding of "val" (the index in this loop).
      var callback = function(me, val) { return function(evt) { me.replaceMisspelling(val); } };
      item.addEventListener("command", callback(this, i), true);
      item.setAttribute("class", "spell-suggestion");
      menu.insertBefore(item, insertBefore);
    }
    return this.mSpellSuggestions.length;
  },

  // undoes the work of addSuggestionsToMenu for the same menu
  // (call from popup hiding)
  clearSuggestionsFromMenu() {
    for (var i = 0; i < this.mSuggestionItems.length; i++) {
      this.mMenu.removeChild(this.mSuggestionItems[i]);
    }
    this.mSuggestionItems = [];
  },

  sortDictionaryList(list) {
    var sortedList = [];
    for (var i = 0; i < list.length; i++) {
      sortedList.push({"id": list[i],
                       "label": this.getDictionaryDisplayName(list[i])});
    }
    sortedList.sort(function(a, b) {
      if (a.label < b.label)
        return -1;
      if (a.label > b.label)
        return 1;
      return 0;
    });

    return sortedList;
  },

  // returns the number of dictionary languages. If insertBefore is NULL, this
  // does an append to the given menu
  addDictionaryListToMenu(menu, insertBefore) {
    this.mDictionaryMenu = menu;
    this.mDictionaryNames = [];
    this.mDictionaryItems = [];

    if (!this.enabled)
      return 0;

    var list;
    var curlang = "";
    if (this.mRemote) {
      list = this.mRemote.dictionaryList;
      curlang = this.mRemote.currentDictionary;
    } else if (this.mInlineSpellChecker) {
      var spellchecker = this.mInlineSpellChecker.spellChecker;
      var o1 = {}, o2 = {};
      spellchecker.GetDictionaryList(o1, o2);
      list = o1.value;
      try {
        curlang = spellchecker.GetCurrentDictionary();
      } catch (e) {}
    }

    var sortedList = this.sortDictionaryList(list);

    for (var i = 0; i < sortedList.length; i++) {
      this.mDictionaryNames.push(sortedList[i].id);
      var item = menu.ownerDocument.createElement("menuitem");
      item.setAttribute("id", "spell-check-dictionary-" + sortedList[i].id);
      item.setAttribute("label", sortedList[i].label);
      item.setAttribute("type", "radio");
      this.mDictionaryItems.push(item);
      if (curlang == sortedList[i].id) {
        item.setAttribute("checked", "true");
      } else {
        var callback = function(me, val, dictName) {
          return function(evt) {
            me.selectDictionary(val);
            // Notify change of dictionary, especially for Thunderbird,
            // which is otherwise not notified any more.
            var view = menu.ownerGlobal;
            var spellcheckChangeEvent = new view.CustomEvent(
                  "spellcheck-changed", {detail: { dictionary: dictName}});
            menu.ownerDocument.dispatchEvent(spellcheckChangeEvent);
          }
        };
        item.addEventListener("command", callback(this, i, sortedList[i].id), true);
      }
      if (insertBefore)
        menu.insertBefore(item, insertBefore);
      else
        menu.appendChild(item);
    }
    return list.length;
  },

  // Formats a valid BCP 47 language tag based on available localized names.
  getDictionaryDisplayName(dictionaryName) {
    try {
      // Get the display name for this dictionary.
      let languageTagMatch = /^([a-z]{2,3}|[a-z]{4}|[a-z]{5,8})(?:[-_]([a-z]{4}))?(?:[-_]([A-Z]{2}|[0-9]{3}))?((?:[-_](?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*)(?:[-_][a-wy-z0-9](?:[-_][a-z0-9]{2,8})+)*(?:[-_]x(?:[-_][a-z0-9]{1,8})+)?$/i;
      var [/* languageTag */, languageSubtag, scriptSubtag, regionSubtag, variantSubtags] = dictionaryName.match(languageTagMatch);
    } catch (e) {
      // If we weren't given a valid language tag, just use the raw dictionary name.
      return dictionaryName;
    }

    if (!gLanguageBundle) {
      // Create the bundles for language and region names.
      var bundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                    .getService(Components.interfaces.nsIStringBundleService);
      gLanguageBundle = bundleService.createBundle(
          "chrome://global/locale/languageNames.properties");
      gRegionBundle = bundleService.createBundle(
          "chrome://global/locale/regionNames.properties");
    }

    var displayName = "";

    // Language subtag will normally be 2 or 3 letters, but could be up to 8.
    try {
      displayName += gLanguageBundle.GetStringFromName(languageSubtag.toLowerCase());
    } catch (e) {
      displayName += languageSubtag.toLowerCase(); // Fall back to raw language subtag.
    }

    // Region subtag will be 2 letters or 3 digits.
    if (regionSubtag) {
      displayName += " (";

      try {
        displayName += gRegionBundle.GetStringFromName(regionSubtag.toLowerCase());
      } catch (e) {
        displayName += regionSubtag.toUpperCase(); // Fall back to raw region subtag.
      }

      displayName += ")";
    }

    // Script subtag will be 4 letters.
    if (scriptSubtag) {
      displayName += " / ";

      // XXX: See bug 666662 and bug 666731 for full implementation.
      displayName += scriptSubtag; // Fall back to raw script subtag.
    }

    // Each variant subtag will be 4 to 8 chars.
    if (variantSubtags)
      // XXX: See bug 666662 and bug 666731 for full implementation.
      displayName += " (" + variantSubtags.substr(1).split(/[-_]/).join(" / ") + ")"; // Collapse multiple variants.

    return displayName;
  },

  // undoes the work of addDictionaryListToMenu for the menu
  // (call on popup hiding)
  clearDictionaryListFromMenu() {
    for (var i = 0; i < this.mDictionaryItems.length; i++) {
      this.mDictionaryMenu.removeChild(this.mDictionaryItems[i]);
    }
    this.mDictionaryItems = [];
  },

  // callback for selecting a dictionary
  selectDictionary(index) {
    if (this.mRemote) {
      this.mRemote.selectDictionary(index);
      return;
    }
    if (!this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
      return;
    var spellchecker = this.mInlineSpellChecker.spellChecker;
    spellchecker.SetCurrentDictionary(this.mDictionaryNames[index]);
    this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
  },

  // callback for selecting a suggested replacement
  replaceMisspelling(index) {
    if (this.mRemote) {
      this.mRemote.replaceMisspelling(index);
      return;
    }
    if (!this.mInlineSpellChecker || !this.mOverMisspelling)
      return;
    if (index < 0 || index >= this.mSpellSuggestions.length)
      return;
    this.mInlineSpellChecker.replaceWord(this.mWordNode, this.mWordOffset,
                                         this.mSpellSuggestions[index]);
  },

  // callback for enabling or disabling spellchecking
  toggleEnabled() {
    if (this.mRemote)
      this.mRemote.toggleEnabled();
    else
      this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
  },

  // callback for adding the current misspelling to the user-defined dictionary
  addToDictionary() {
    // Prevent the undo stack from growing over the max depth
    if (this.mAddedWordStack.length == MAX_UNDO_STACK_DEPTH)
      this.mAddedWordStack.shift();

    this.mAddedWordStack.push(this.mMisspelling);
    if (this.mRemote)
      this.mRemote.addToDictionary();
    else {
      this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
    }
  },
  // callback for removing the last added word to the dictionary LIFO fashion
  undoAddToDictionary() {
    if (this.mAddedWordStack.length > 0) {
      var word = this.mAddedWordStack.pop();
      if (this.mRemote)
        this.mRemote.undoAddToDictionary(word);
      else
        this.mInlineSpellChecker.removeWordFromDictionary(word);
    }
  },
  canUndo() {
    // Return true if we have words on the stack
    return (this.mAddedWordStack.length > 0);
  },
  ignoreWord() {
    if (this.mRemote)
      this.mRemote.ignoreWord();
    else
      this.mInlineSpellChecker.ignoreWord(this.mMisspelling);
  }
};

var SpellCheckHelper = {
  // Set when over a non-read-only <textarea> or editable <input>.
  EDITABLE: 0x1,

  // Set when over an <input> element of any type.
  INPUT: 0x2,

  // Set when over any <textarea>.
  TEXTAREA: 0x4,

  // Set when over any text-entry <input>.
  TEXTINPUT: 0x8,

  // Set when over an <input> that can be used as a keyword field.
  KEYWORD: 0x10,

  // Set when over an element that otherwise would not be considered
  // "editable" but is because content editable is enabled for the document.
  CONTENTEDITABLE: 0x20,

  // Set when over an <input type="number"> or other non-text field.
  NUMERIC: 0x40,

  // Set when over an <input type="password"> field.
  PASSWORD: 0x80,

  isTargetAKeywordField(aNode, window) {
    if (!(aNode instanceof window.HTMLInputElement))
      return false;

    var form = aNode.form;
    if (!form || aNode.type == "password")
      return false;

    var method = form.method.toUpperCase();

    // These are the following types of forms we can create keywords for:
    //
    // method   encoding type       can create keyword
    // GET      *                                 YES
    //          *                                 YES
    // POST                                       YES
    // POST     application/x-www-form-urlencoded YES
    // POST     text/plain                        NO (a little tricky to do)
    // POST     multipart/form-data               NO
    // POST     everything else                   YES
    return (method == "GET" || method == "") ||
           (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
  },

  // Returns the computed style attribute for the given element.
  getComputedStyle(aElem, aProp) {
    return aElem.ownerGlobal
                .getComputedStyle(aElem).getPropertyValue(aProp);
  },

  isEditable(element, window) {
    var flags = 0;
    if (element instanceof window.HTMLInputElement) {
      flags |= this.INPUT;

      if (element.mozIsTextField(false) || element.type == "number") {
        flags |= this.TEXTINPUT;

        if (element.type == "number") {
          flags |= this.NUMERIC;
        }

        // Allow spellchecking UI on all text and search inputs.
        if (!element.readOnly &&
            (element.type == "text" || element.type == "search")) {
          flags |= this.EDITABLE;
        }
        if (this.isTargetAKeywordField(element, window))
          flags |= this.KEYWORD;
        if (element.type == "password") {
          flags |= this.PASSWORD;
        }
      }
    } else if (element instanceof window.HTMLTextAreaElement) {
      flags |= this.TEXTINPUT | this.TEXTAREA;
      if (!element.readOnly) {
        flags |= this.EDITABLE;
      }
    }

    if (!(flags & this.EDITABLE)) {
      var win = element.ownerGlobal;
      if (win) {
        var isEditable = false;
        try {
          var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIWebNavigation)
                                  .QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIEditingSession);
          if (editingSession.windowIsEditable(win) &&
              this.getComputedStyle(element, "-moz-user-modify") == "read-write") {
            isEditable = true;
          }
        } catch (ex) {
          // If someone built with composer disabled, we can't get an editing session.
        }

        if (isEditable)
          flags |= this.CONTENTEDITABLE;
      }
    }

    return flags;
  },
};

function RemoteSpellChecker(aSpellInfo) {
  this._spellInfo = aSpellInfo;
  this._suggestionGenerator = null;
}

RemoteSpellChecker.prototype = {
  get canSpellCheck() { return this._spellInfo.canSpellCheck; },
  get spellCheckPending() { return this._spellInfo.initialSpellCheckPending; },
  get overMisspelling() { return this._spellInfo.overMisspelling; },
  get enableRealTimeSpell() { return this._spellInfo.enableRealTimeSpell; },

  GetSuggestedWord() {
    if (!this._suggestionGenerator) {
      this._suggestionGenerator = (function*(spellInfo) {
        for (let i of spellInfo.spellSuggestions)
          yield i;
      })(this._spellInfo);
    }

    let next = this._suggestionGenerator.next();
    if (next.done) {
      this._suggestionGenerator = null;
      return "";
    }
    return next.value;
  },

  get currentDictionary() { return this._spellInfo.currentDictionary },
  get dictionaryList() { return this._spellInfo.dictionaryList.slice(); },

  selectDictionary(index) {
    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:selectDictionary",
                                            { index });
  },

  replaceMisspelling(index) {
    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
                                            { index });
  },

  toggleEnabled() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {}); },
  addToDictionary() {
    // This is really ugly. There is an nsISpellChecker somewhere in the
    // parent that corresponds to our current element's spell checker in the
    // child, but it's hard to access it. However, we know that
    // addToDictionary adds the word to the singleton personal dictionary, so
    // we just do that here.
    // NB: We also rely on the fact that we only ever pass an empty string in
    // as the "lang".

    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                       .getService(Ci.mozIPersonalDictionary);
    dictionary.addWord(this._spellInfo.misspelling, "");

    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
  },
  undoAddToDictionary(word) {
    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                       .getService(Ci.mozIPersonalDictionary);
    dictionary.removeWord(word, "");

    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
  },
  ignoreWord() {
    let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                       .getService(Ci.mozIPersonalDictionary);
    dictionary.ignoreWord(this._spellInfo.misspelling);

    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
  },
  uninit() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:uninit", {}); }
};
PK
!<=		%modules/InlineSpellCheckerContent.jsm/* vim: set ts=2 sw=2 sts=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/. */

"use strict";

var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

var { InlineSpellChecker, SpellCheckHelper } =
  Cu.import("resource://gre/modules/InlineSpellChecker.jsm", {});

this.EXPORTED_SYMBOLS = [ "InlineSpellCheckerContent" ]

var InlineSpellCheckerContent = {
  _spellChecker: null,
  _manager: null,

  initContextMenu(event, editFlags, messageManager) {
    this._manager = messageManager;

    let spellChecker;
    if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
      // Get the editor off the window.
      let win = event.target.ownerGlobal;
      let editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIEditingSession);
      spellChecker = this._spellChecker =
        new InlineSpellChecker(editingSession.getEditorForWindow(win));
    } else {
      // Use the element's editor.
      spellChecker = this._spellChecker =
        new InlineSpellChecker(event.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
    }

    this._spellChecker.initFromEvent(event.rangeParent, event.rangeOffset)

    this._addMessageListeners();

    if (!spellChecker.canSpellCheck) {
      return { canSpellCheck: false,
               initialSpellCheckPending: true,
               enableRealTimeSpell: false };
    }

    if (!spellChecker.mInlineSpellChecker.enableRealTimeSpell) {
      return { canSpellCheck: true,
               initialSpellCheckPending: spellChecker.initialSpellCheckPending,
               enableRealTimeSpell: false };
    }

    let dictionaryList = {};
    let realSpellChecker = spellChecker.mInlineSpellChecker.spellChecker;
    realSpellChecker.GetDictionaryList(dictionaryList, {});

    // The original list we get is in random order. We need our list to be
    // sorted by display names.
    dictionaryList = spellChecker.sortDictionaryList(dictionaryList.value).map((obj) => {
      return obj.id;
    });
    spellChecker.mDictionaryNames = dictionaryList;

    return { canSpellCheck: spellChecker.canSpellCheck,
             initialSpellCheckPending: spellChecker.initialSpellCheckPending,
             enableRealTimeSpell: spellChecker.enabled,
             overMisspelling: spellChecker.overMisspelling,
             misspelling: spellChecker.mMisspelling,
             spellSuggestions: this._generateSpellSuggestions(),
             currentDictionary: spellChecker.mInlineSpellChecker.spellChecker.GetCurrentDictionary(),
             dictionaryList };
  },

  uninitContextMenu() {
    for (let i of this._messages)
      this._manager.removeMessageListener(i, this);

    this._manager = null;
    this._spellChecker = null;
  },

  _generateSpellSuggestions() {
    let spellChecker = this._spellChecker.mInlineSpellChecker.spellChecker;
    try {
      spellChecker.CheckCurrentWord(this._spellChecker.mMisspelling);
    } catch (e) {
      return [];
    }

    let suggestions = new Array(5);
    for (let i = 0; i < 5; ++i) {
      suggestions[i] = spellChecker.GetSuggestedWord();
      if (suggestions[i].length === 0) {
        suggestions.length = i;
        break;
      }
    }

    this._spellChecker.mSpellSuggestions = suggestions;
    return suggestions;
  },

  _messages: [
      "InlineSpellChecker:selectDictionary",
      "InlineSpellChecker:replaceMisspelling",
      "InlineSpellChecker:toggleEnabled",

      "InlineSpellChecker:recheck",

      "InlineSpellChecker:uninit"
    ],

  _addMessageListeners() {
    for (let i of this._messages)
      this._manager.addMessageListener(i, this);
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "InlineSpellChecker:selectDictionary":
        this._spellChecker.selectDictionary(msg.data.index);
        break;

      case "InlineSpellChecker:replaceMisspelling":
        this._spellChecker.replaceMisspelling(msg.data.index);
        break;

      case "InlineSpellChecker:toggleEnabled":
        this._spellChecker.toggleEnabled();
        break;

      case "InlineSpellChecker:recheck":
        this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
        break;

      case "InlineSpellChecker:uninit":
        this.uninitContextMenu();
        break;
    }
  }
};
PK
!<<ɰ!modules/InsecurePasswordUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ownerGlobal doesn't exist in content privileged windows. */
/* eslint-disable mozilla/use-ownerGlobal */

this.EXPORTED_SYMBOLS = [ "InsecurePasswordUtils" ];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const STRINGS_URI = "chrome://global/locale/security/security.properties";

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                   "@mozilla.org/contentsecuritymanager;1",
                                   "nsIContentSecurityManager");
XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
                                   "@mozilla.org/scriptsecuritymanager;1",
                                   "nsIScriptSecurityManager");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");

XPCOMUtils.defineLazyGetter(this, "log", () => {
  return LoginHelper.createLogger("InsecurePasswordUtils");
});

/*
 * A module that provides utility functions for form security.
 *
 * Note:
 *  This module uses isSecureContextIfOpenerIgnored instead of isSecureContext.
 *
 *  We don't want to expose JavaScript APIs in a non-Secure Context even if
 *  the context is only insecure because the windows has an insecure opener.
 *  Doing so prevents sites from implementing postMessage workarounds to enable
 *  an insecure opener to gain access to Secure Context-only APIs. However,
 *  in the case of form fields such as password fields we don't need to worry
 *  about whether the opener is secure or not. In fact to flag a password
 *  field as insecure in such circumstances would unnecessarily confuse our
 *  users.
 */
this.InsecurePasswordUtils = {
  _formRootsWarned: new WeakMap(),

  /**
   * Gets the ID of the inner window of this DOM window.
   *
   * @param nsIDOMWindow window
   * @return integer
   *         Inner ID for the given window.
   */
  _getInnerWindowId(window) {
      return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  },

  _sendWebConsoleMessage(messageTag, domDoc) {
    let windowId = this._getInnerWindowId(domDoc.defaultView);
    let category = "Insecure Password Field";
    // All web console messages are warnings for now.
    let flag = Ci.nsIScriptError.warningFlag;
    let bundle = Services.strings.createBundle(STRINGS_URI);
    let message = bundle.GetStringFromName(messageTag);
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
    consoleMsg.initWithWindowID(message, domDoc.location.href, 0, 0, 0, flag, category, windowId);

    Services.console.logMessage(consoleMsg);
  },

  /**
   * Gets the security state of the passed form.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   *
   * @returns {Object} An object with the following boolean values:
   *  isFormSubmitHTTP: if the submit action is an http:// URL
   *  isFormSubmitSecure: if the submit action URL is secure,
   *    either because it is HTTPS or because its origin is considered trustworthy
   */
  _checkFormSecurity(aForm) {
    let isFormSubmitHTTP = false, isFormSubmitSecure = false;
    if (aForm.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
      let uri = Services.io.newURI(aForm.rootElement.action || aForm.rootElement.baseURI);
      let principal = gScriptSecurityManager.getCodebasePrincipal(uri);

      if (uri.schemeIs("http")) {
        isFormSubmitHTTP = true;
        if (gContentSecurityManager.isOriginPotentiallyTrustworthy(principal)) {
          isFormSubmitSecure = true;
        }
      } else {
        isFormSubmitSecure = true;
      }
    }

    return { isFormSubmitHTTP, isFormSubmitSecure };
  },

  _isPrincipalForLocalIPAddress(aPrincipal) {
    try {
      let uri = aPrincipal.URI;
      if (Services.io.hostnameIsLocalIPAddress(uri)) {
        log.debug("hasInsecureLoginForms: detected local IP address:", uri);
        return true;
      }
    } catch (e) {
      log.debug("hasInsecureLoginForms: unable to check for local IP address:", e);
    }
    return false;
  },

  /**
   * Checks if there are insecure password fields present on the form's document
   * i.e. passwords inside forms with http action, inside iframes with http src,
   * or on insecure web pages.
   *
   * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
   * @return {boolean} whether the form is secure
   */
  isFormSecure(aForm) {
    // Ignores window.opener, see top level documentation.
    let isSafePage = aForm.ownerDocument.defaultView.isSecureContextIfOpenerIgnored;

    // Ignore insecure documents with URLs that are local IP addresses.
    // This is done because the vast majority of routers and other devices
    // on the network do not use HTTPS, making this warning show up almost
    // constantly on local connections, which annoys users and hurts our cause.
    if (!isSafePage && this._ignoreLocalIPAddress) {
      let isLocalIP = this._isPrincipalForLocalIPAddress(aForm.rootElement.nodePrincipal);
      let topWindow = aForm.ownerDocument.defaultView.top;
      let topIsLocalIP = this._isPrincipalForLocalIPAddress(topWindow.document.nodePrincipal);

      // Only consider the page safe if the top window has a local IP address
      // and, if this is an iframe, the iframe also has a local IP address.
      if (isLocalIP && topIsLocalIP) {
        isSafePage = true;
      }
    }

    let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);

    return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
  },

  /**
   * Report insecure password fields in a form to the web console to warn developers.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   */
  reportInsecurePasswords(aForm) {
    if (this._formRootsWarned.has(aForm.rootElement) ||
        this._formRootsWarned.get(aForm.rootElement)) {
      return;
    }

    let domDoc = aForm.ownerDocument;
    // Ignores window.opener, see top level documentation.
    let isSafePage = domDoc.defaultView.isSecureContextIfOpenerIgnored;

    let { isFormSubmitHTTP, isFormSubmitSecure } = this._checkFormSecurity(aForm);

    if (!isSafePage) {
      if (domDoc.defaultView == domDoc.defaultView.parent) {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
      } else {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
      }
      this._formRootsWarned.set(aForm.rootElement, true);
    } else if (isFormSubmitHTTP && !isFormSubmitSecure) {
      this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
      this._formRootsWarned.set(aForm.rootElement, true);
    }

    // The safety of a password field determined by the form action and the page protocol
    let passwordSafety;
    if (isSafePage) {
      if (isFormSubmitSecure) {
        passwordSafety = 0;
      } else if (isFormSubmitHTTP) {
        passwordSafety = 1;
      } else {
        passwordSafety = 2;
      }
    } else if (isFormSubmitSecure) {
      passwordSafety = 3;
    } else if (isFormSubmitHTTP) {
      passwordSafety = 4;
    } else {
      passwordSafety = 5;
    }

    Services.telemetry.getHistogramById("PWMGR_LOGIN_PAGE_SAFETY").add(passwordSafety);
  },
};

XPCOMUtils.defineLazyPreferenceGetter(this.InsecurePasswordUtils, "_ignoreLocalIPAddress",
                                      "security.insecure_field_warning.ignore_local_ip_address", true);
PK
!<mi))modules/Integration.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 low-overhead integration between components of the application.
 * This may have different uses depending on the component, including:
 *
 * - Providing product-specific implementations registered at startup.
 * - Using alternative implementations during unit tests.
 * - Allowing add-ons to change specific behaviors.
 *
 * Components may define one or more integration points, each defined by a
 * root integration object whose properties and methods are the public interface
 * and default implementation of the integration point. For example:
 *
 *   const DownloadIntegration = {
 *     getTemporaryDirectory() {
 *       return "/tmp/";
 *     },
 *
 *     getTemporaryFile(name) {
 *       return this.getTemporaryDirectory() + name;
 *     },
 *   };
 *
 * Other parts of the application may register overrides for some or all of the
 * defined properties and methods. The component defining the integration point
 * does not have to be loaded at this stage, because the name of the integration
 * point is the only information required. For example, if the integration point
 * is called "downloads":
 *
 *   Integration.downloads.register(base => ({
 *     getTemporaryDirectory() {
 *       return base.getTemporaryDirectory.call(this) + "subdir/";
 *     },
 *   }));
 *
 * When the component defining the integration point needs to call a method on
 * the integration object, instead of using it directly the component would use
 * the "getCombined" method to retrieve an object that includes all overrides.
 * For example:
 *
 *   let combined = Integration.downloads.getCombined(DownloadIntegration);
 *   Assert.is(combined.getTemporaryFile("file"), "/tmp/subdir/file");
 *
 * Overrides can be registered at startup or at any later time, so each call to
 * "getCombined" may return a different object. The simplest way to create a
 * reference to the combined object that stays updated to the latest version is
 * to define the root object in a JSM and use the "defineModuleGetter" method.
 *
 * *** Registration ***
 *
 * Since the interface is not declared formally, the registrations can happen
 * at startup without loading the component, so they do not affect performance.
 *
 * Hovever, this module does not provide a startup registry, this means that the
 * code that registers and implements the override must be loaded at startup.
 *
 * If performance for the override code is a concern, you can take advantage of
 * the fact that the function used to create the override is called lazily, and
 * include only a stub loader for the final code in an existing startup module.
 *
 * The registration of overrides should be repeated for each process where the
 * relevant integration methods will be called.
 *
 * *** Accessing base methods and properties ***
 *
 * Overrides are included in the prototype chain of the combined object in the
 * same order they were registered, where the first is closest to the root.
 *
 * When defining overrides, you do not need to set the "__proto__" property of
 * the objects you create, because their properties and methods are moved to a
 * new object with the correct prototype. If you do, however, you can call base
 * properties and methods using the "super" keyword. For example:
 *
 *   Integration.downloads.register(base => ({
 *     __proto__: base,
 *     getTemporaryDirectory() {
 *       return super.getTemporaryDirectory() + "subdir/";
 *     },
 *   }));
 *
 * *** State handling ***
 *
 * Storing state directly on the combined integration object using the "this"
 * reference is not recommended. When a new integration is registered, own
 * properties stored on the old combined object are copied to the new combined
 * object using a shallow copy, but the "this" reference for new invocations
 * of the methods will be different.
 *
 * If the root object defines a property that always points to the same object,
 * for example a "state" property, you can safely use it across registrations.
 *
 * Integration overrides provided by restartless add-ons should not use the
 * "this" reference to store state, to avoid conflicts with other add-ons.
 *
 * *** Interaction with XPCOM ***
 *
 * Providing the combined object as an argument to any XPCOM method will
 * generate a console error message, and will throw an exception where possible.
 * For example, you cannot register observers directly on the combined object.
 * This helps preventing mistakes due to the fact that the combined object
 * reference changes when new integration overrides are registered.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "Integration",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

/**
 * Maps integration point names to IntegrationPoint objects.
 */
const gIntegrationPoints = new Map();

/**
 * This Proxy object creates IntegrationPoint objects using their name as key.
 * The objects will be the same for the duration of the process. For example:
 *
 *   Integration.downloads.register(...);
 *   Integration["addon-provided-integration"].register(...);
 */
this.Integration = new Proxy({}, {
  get(target, name) {
    let integrationPoint = gIntegrationPoints.get(name);
    if (!integrationPoint) {
      integrationPoint = new IntegrationPoint();
      gIntegrationPoints.set(name, integrationPoint);
    }
    return integrationPoint;
  },
});

/**
 * Individual integration point for which overrides can be registered.
 */
this.IntegrationPoint = function() {
  this._overrideFns = new Set();
  this._combined = {
    QueryInterface() {
      let ex = new Components.Exception(
                   "Integration objects should not be used with XPCOM because" +
                   " they change when new overrides are registered.",
                   Cr.NS_ERROR_NO_INTERFACE);
      Cu.reportError(ex);
      throw ex;
    },
  };
}

this.IntegrationPoint.prototype = {
  /**
   * Ordered set of registered functions defining integration overrides.
   */
  _overrideFns: null,

  /**
   * Combined integration object. When this reference changes, properties
   * defined directly on this object are copied to the new object.
   *
   * Initially, the only property of this object is a "QueryInterface" method
   * that throws an exception, to prevent misuse as a permanent XPCOM listener.
   */
  _combined: null,

  /**
   * Indicates whether the integration object is current based on the list of
   * registered integration overrides.
   */
  _combinedIsCurrent: false,

  /**
   * Registers new overrides for the integration methods. For example:
   *
   *   Integration.nameOfIntegrationPoint.register(base => ({
   *     asyncMethod: Task.async(function* () {
   *       return yield base.asyncMethod.apply(this, arguments);
   *     }),
   *   }));
   *
   * @param overrideFn
   *        Function returning an object defining the methods that should be
   *        overridden. Its only parameter is an object that contains the base
   *        implementation of all the available methods.
   *
   * @note The override function is called every time the list of registered
   *       override functions changes. Thus, it should not have any side
   *       effects or do any other initialization.
   */
  register(overrideFn) {
    this._overrideFns.add(overrideFn);
    this._combinedIsCurrent = false;
  },

  /**
   * Removes a previously registered integration override.
   *
   * Overrides don't usually need to be unregistered, unless they are added by a
   * restartless add-on, in which case they should be unregistered when the
   * add-on is disabled or uninstalled.
   *
   * @param overrideFn
   *        This must be the same function object passed to "register".
   */
  unregister(overrideFn) {
    this._overrideFns.delete(overrideFn);
    this._combinedIsCurrent = false;
  },

  /**
   * Retrieves the dynamically generated object implementing the integration
   * methods. Platform-specific code and add-ons can override methods of this
   * object using the "register" method.
   */
  getCombined(root) {
    if (this._combinedIsCurrent) {
      return this._combined;
    }

    // In addition to enumerating all the registered integration overrides in
    // order, we want to keep any state that was previously stored in the
    // combined object using the "this" reference in integration methods.
    let overrideFnArray = [...this._overrideFns, () => this._combined];

    let combined = root;
    for (let overrideFn of overrideFnArray) {
      try {
        // Obtain a new set of methods from the next override function in the
        // list, specifying the current combined object as the base argument.
        let override = overrideFn(combined);

        // Retrieve a list of property descriptors from the returned object, and
        // use them to build a new combined object whose prototype points to the
        // previous combined object.
        let descriptors = {};
        for (let name of Object.getOwnPropertyNames(override)) {
          descriptors[name] = Object.getOwnPropertyDescriptor(override, name);
        }
        combined = Object.create(combined, descriptors);
      } catch (ex) {
        // Any error will result in the current override being skipped.
        Cu.reportError(ex);
      }
    }

    this._combinedIsCurrent = true;
    return this._combined = combined;
  },

  /**
   * Defines a getter to retrieve the dynamically generated object implementing
   * the integration methods, loading the root implementation lazily from the
   * specified JSM module. For example:
   *
   *   Integration.test.defineModuleGetter(this, "TestIntegration",
   *                    "resource://testing-common/TestIntegration.jsm");
   *
   * @param targetObject
   *        The object on which the lazy getter will be defined.
   * @param name
   *        The name of the getter to define.
   * @param moduleUrl
   *        The URL used to obtain the module.
   * @param symbol [optional]
   *        The name of the symbol exported by the module. This can be omitted
   *        if the name of the exported symbol is equal to the getter name.
   */
  defineModuleGetter(targetObject, name, moduleUrl, symbol) {
    let moduleHolder = {};
    XPCOMUtils.defineLazyModuleGetter(moduleHolder, name, moduleUrl, symbol);
    Object.defineProperty(targetObject, name, {
      get: () => this.getCombined(moduleHolder[name]),
      configurable: true,
      enumerable: true,
    });
  },
};
PK
!<Rʖg//modules/JSONFile.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of the data and persistence into a file.
 *
 * This modules handles the raw data stored in JavaScript serializable objects,
 * and contains no special validation or query logic, that is handled entirely
 * by "storage.js" instead.
 *
 * The data can be manipulated only after it has been loaded from disk.  The
 * load process can happen asynchronously, through the "load" method, or
 * synchronously, through "ensureDataReady".  After any modification, the
 * "saveSoon" method must be called to flush the data to disk asynchronously.
 *
 * The raw data should be manipulated synchronously, without waiting for the
 * event loop or for promise resolution, so that the saved file is always
 * consistent.  This synchronous approach also simplifies the query and update
 * logic.  For example, it is possible to find an object and modify it
 * immediately without caring whether other code modifies it in the meantime.
 *
 * An asynchronous shutdown observer makes sure that data is always saved before
 * the browser is closed. The data cannot be modified during shutdown.
 *
 * The file is stored in JSON format, without indentation, using UTF-8 encoding.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "JSONFile",
];

// 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, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
  return new TextDecoder();
});

XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function() {
  return new TextEncoder();
});

const FileInputStream =
      Components.Constructor("@mozilla.org/network/file-input-stream;1",
                             "nsIFileInputStream", "init");

/**
 * Delay between a change to the data and the related save operation.
 */
const kSaveDelayMs = 1500;

// JSONFile

/**
 * Handles serialization of the data and persistence into a file.
 *
 * @param config An object containing following members:
 *        - path: String containing the file path where data should be saved.
 *        - dataPostProcessor: Function triggered when data is just loaded. The
 *                             data object will be passed as the first argument
 *                             and should be returned no matter it's modified or
 *                             not. Its failure leads to the failure of load()
 *                             and ensureDataReady().
 *        - saveDelayMs: Number indicating the delay (in milliseconds) between a
 *                       change to the data and the related save operation. The
 *                       default value will be applied if omitted.
 *        - beforeSave: Promise-returning function triggered just before the
 *                      data is written to disk. This can be used to create any
 *                      intermediate directories before saving. The file will
 *                      not be saved if the promise rejects or the function
 *                      throws an exception.
 *        - finalizeAt: An `AsyncShutdown` phase or barrier client that should
 *                      automatically finalize the file when triggered. Defaults
 *                      to `profileBeforeChange`; exposed as an option for
 *                      testing.
 *        - compression: A compression algorithm to use when reading and
 *                       writing the data.
 */
function JSONFile(config) {
  this.path = config.path;

  if (typeof config.dataPostProcessor === "function") {
    this._dataPostProcessor = config.dataPostProcessor;
  }
  if (typeof config.beforeSave === "function") {
    this._beforeSave = config.beforeSave;
  }

  if (config.saveDelayMs === undefined) {
    config.saveDelayMs = kSaveDelayMs;
  }
  this._saver = new DeferredTask(() => this._save(), config.saveDelayMs);

  this._options = {};
  if (config.compression) {
    this._options.compression = config.compression;
  }

  this._finalizeAt = config.finalizeAt || AsyncShutdown.profileBeforeChange;
  this._finalizeInternalBound = this._finalizeInternal.bind(this);
  this._finalizeAt.addBlocker("JSON store: writing data",
                              this._finalizeInternalBound);
}

JSONFile.prototype = {
  /**
   * String containing the file path where data should be saved.
   */
  path: "",

  /**
   * True when data has been loaded.
   */
  dataReady: false,

  /**
   * DeferredTask that handles the save operation.
   */
  _saver: null,

  /**
   * Internal data object.
   */
  _data: null,

  /**
   * Internal fields used during finalization.
   */
  _finalizeAt: null,
  _finalizePromise: null,
  _finalizeInternalBound: null,

  /**
   * Serializable object containing the data. This is populated directly with
   * the data loaded from the file, and is saved without modifications.
   *
   * The raw data should be manipulated synchronously, without waiting for the
   * event loop or for promise resolution, so that the saved file is always
   * consistent.
   */
  get data() {
    if (!this.dataReady) {
      throw new Error("Data is not ready.");
    }
    return this._data;
  },

  /**
   * Sets the loaded data to a new object. This will overwrite any persisted
   * data on the next save.
   */
  set data(data) {
    this._data = data;
    this.dataReady = true;
  },

  /**
   * Loads persistent data from the file to memory.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception when dataPostProcessor fails. It never fails
   *          if there is no dataPostProcessor.
   */
  async load() {
    let data = {};

    try {
      let bytes = await OS.File.read(this.path, this._options);

      // If synchronous loading happened in the meantime, exit now.
      if (this.dataReady) {
        return;
      }

      data = JSON.parse(gTextDecoder.decode(bytes));
    } catch (ex) {
      // If an exception occurred because the file did not exist, we should
      // just start with new data.  Other errors may indicate that the file is
      // corrupt, thus we move it to a backup location before allowing it to
      // be overwritten by an empty file.
      if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
        Cu.reportError(ex);

        // Move the original file to a backup location, ignoring errors.
        try {
          let openInfo = await OS.File.openUnique(this.path + ".corrupt",
                                                  { humanReadable: true });
          await openInfo.file.close();
          await OS.File.move(this.path, openInfo.path);
        } catch (e2) {
          Cu.reportError(e2);
        }
      }

      // In some rare cases it's possible for data to have been added to
      // our database between the call to OS.File.read and when we've been
      // notified that there was a problem with it. In that case, leave the
      // synchronously-added data alone.
      if (this.dataReady) {
        return;
      }
    }

    this._processLoadedData(data);
  },

  /**
   * Loads persistent data from the file to memory, synchronously. An exception
   * can be thrown only if dataPostProcessor exists and fails.
   */
  ensureDataReady() {
    if (this.dataReady) {
      return;
    }

    let data = {};

    try {
      // This reads the file and automatically detects the UTF-8 encoding.
      let inputStream = new FileInputStream(new FileUtils.File(this.path),
                                            FileUtils.MODE_RDONLY,
                                            FileUtils.PERMS_FILE, 0);
      try {
        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
        data = json.decodeFromStream(inputStream, inputStream.available());
      } finally {
        inputStream.close();
      }
    } catch (ex) {
      // If an exception occurred because the file did not exist, we should just
      // start with new data.  Other errors may indicate that the file is
      // corrupt, thus we move it to a backup location before allowing it to be
      // overwritten by an empty file.
      if (!(ex instanceof Components.Exception &&
            ex.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
        Cu.reportError(ex);
        // Move the original file to a backup location, ignoring errors.
        try {
          let originalFile = new FileUtils.File(this.path);
          let backupFile = originalFile.clone();
          backupFile.leafName += ".corrupt";
          backupFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE,
                                  FileUtils.PERMS_FILE);
          backupFile.remove(false);
          originalFile.moveTo(backupFile.parent, backupFile.leafName);
        } catch (e2) {
          Cu.reportError(e2);
        }
      }
    }

    this._processLoadedData(data);
  },

  /**
   * Called when the data changed, this triggers asynchronous serialization.
   */
  saveSoon() {
    return this._saver.arm();
  },

  /**
   * Saves persistent data from memory to the file.
   *
   * If an error occurs, the previous file is not deleted.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  async _save() {
    let json;
    try {
      json = JSON.stringify(this._data);
    } catch (e) {
      // If serialization fails, try fallback safe JSON converter.
      if (typeof this._data.toJSONSafe == "function") {
        json = JSON.stringify(this._data.toJSONSafe());
      } else {
        throw e;
      }
    }

    // Create or overwrite the file.
    let bytes = gTextEncoder.encode(json);
    if (this._beforeSave) {
      await Promise.resolve(this._beforeSave());
    }
    await OS.File.writeAtomic(this.path, bytes,
                              Object.assign(
                                { tmpPath: this.path + ".tmp" },
                                this._options));
  },

  /**
   * Synchronously work on the data just loaded into memory.
   */
  _processLoadedData(data) {
    if (this._finalizePromise) {
      // It's possible for `load` to race with `finalize`. In that case, don't
      // process or set the loaded data.
      return;
    }
    this.data = this._dataPostProcessor ? this._dataPostProcessor(data) : data;
  },

  /**
   * Finishes persisting data to disk and resets all state for this file.
   *
   * @return {Promise}
   * @resolves When the object is finalized.
   */
  _finalizeInternal() {
    if (this._finalizePromise) {
      // Finalization already in progress; return the pending promise. This is
      // possible if `finalize` is called concurrently with shutdown.
      return this._finalizePromise;
    }
    this._finalizePromise = (async () => {
      await this._saver.finalize();
      this._data = null;
      this.dataReady = false;
    })();
    return this._finalizePromise;
  },

  /**
   * Ensures that all data is persisted to disk, and prevents future calls to
   * `saveSoon`. This is called automatically on shutdown, but can also be
   * called explicitly when the file is no longer needed.
   */
  async finalize() {
    if (this._finalizePromise) {
      throw new Error(`The file ${this.path} has already been finalized`);
    }
    // Wait for finalization before removing the shutdown blocker.
    await this._finalizeInternal();
    this._finalizeAt.removeBlocker(this._finalizeInternalBound);
  },
};
PK
!<I^modules/KeyValueParser.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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");

XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

this.EXPORTED_SYMBOLS = [
  "parseKeyValuePairsFromLines",
  "parseKeyValuePairs",
  "parseKeyValuePairsFromFile",
  "parseKeyValuePairsFromFileAsync"
];

const Cc = Components.classes;
const Ci = Components.interfaces;

this.parseKeyValuePairsFromLines = function(lines) {
  let data = {};
  for (let line of lines) {
    if (line == "")
      continue;

    // can't just .split() because the value might contain = characters
    let eq = line.indexOf("=");
    if (eq != -1) {
      let [key, value] = [line.substring(0, eq),
                          line.substring(eq + 1)];
      if (key && value)
        data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
    }
  }
  return data;
}

this.parseKeyValuePairs = function parseKeyValuePairs(text) {
  let lines = text.split("\n");
  return parseKeyValuePairsFromLines(lines);
};

// some test setup still uses this sync version
this.parseKeyValuePairsFromFile = function parseKeyValuePairsFromFile(file) {
  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
                createInstance(Ci.nsIFileInputStream);
  fstream.init(file, -1, 0, 0);
  let is = Cc["@mozilla.org/intl/converter-input-stream;1"].
           createInstance(Ci.nsIConverterInputStream);
  is.init(fstream, "UTF-8", 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  let str = {};
  let contents = "";
  while (is.readString(4096, str) != 0) {
    contents += str.value;
  }
  is.close();
  fstream.close();
  return parseKeyValuePairs(contents);
};

this.parseKeyValuePairsFromFileAsync = async function parseKeyValuePairsFromFileAsync(file) {
  let contents = await OS.File.read(file, { encoding: "utf-8" });
  return parseKeyValuePairs(contents);
};
PK
!<|""!modules/LegacyExtensionsUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["LegacyExtensionsUtils"];

/* exported LegacyExtensionsUtils, LegacyExtensionContext */

/**
 * This file exports helpers for Legacy Extensions that want to embed a webextensions
 * and exchange messages with the embedded WebExtension.
 */

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                  "resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
                                  "resource://gre/modules/ExtensionChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

Cu.import("resource://gre/modules/ExtensionCommon.jsm");

var {
  BaseContext,
} = ExtensionCommon;

/**
 * Instances created from this class provide to a legacy extension
 * a simple API to exchange messages with a webextension.
 */
var LegacyExtensionContext = class extends BaseContext {
  /**
   * Create a new LegacyExtensionContext given a target Extension instance.
   *
   * @param {Extension} targetExtension
   *   The webextension instance associated with this context. This will be the
   *   instance of the newly created embedded webextension when this class is
   *   used through the EmbeddedWebExtensionsUtils.
   */
  constructor(targetExtension) {
    super("legacy_extension", targetExtension);

    // Legacy Extensions (xul overlays, bootstrap restartless and Addon SDK)
    // runs with a systemPrincipal.
    let addonPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
    Object.defineProperty(
      this, "principal",
      {value: addonPrincipal, enumerable: true, configurable: true}
    );

    let cloneScope = Cu.Sandbox(this.principal, {});
    Cu.setSandboxMetadata(cloneScope, {addonId: targetExtension.id});
    Object.defineProperty(
      this, "cloneScope",
      {value: cloneScope, enumerable: true, configurable: true, writable: true}
    );

    let sender = {id: targetExtension.id};
    let filter = {extensionId: targetExtension.id};
    // Legacy addons live in the main process. Messages from other addons are
    // Messages from WebExtensions are sent to the main process and forwarded via
    // the parent process manager to the legacy extension.
    this.messenger = new ExtensionChild.Messenger(this, [Services.cpmm], sender, filter);

    this.api = {
      browser: {
        runtime: {
          onConnect: this.messenger.onConnect("runtime.onConnect"),
          onMessage: this.messenger.onMessage("runtime.onMessage"),
        },
      },
    };
  }

  /**
   * This method is called when the extension shuts down or is unloaded,
   * and it nukes the cloneScope sandbox, if any.
   */
  unload() {
    if (this.unloaded) {
      throw new Error("Error trying to unload LegacyExtensionContext twice.");
    }
    super.unload();
    Cu.nukeSandbox(this.cloneScope);
    this.cloneScope = null;
  }
};

var EmbeddedExtensionManager;

/**
 * Instances of this class are used internally by the exported EmbeddedWebExtensionsUtils
 * to manage the embedded webextension instance and the related LegacyExtensionContext
 * instance used to exchange messages with it.
 */
class EmbeddedExtension {
  /**
   * Create a new EmbeddedExtension given the add-on id and the base resource URI of the
   * container add-on (the webextension resources will be loaded from the "webextension/"
   * subdir of the base resource URI for the legacy extension add-on).
   *
   * @param {Object} containerAddonParams
   *   An object with the following properties:
   * @param {string} containerAddonParams.id
   *   The Add-on id of the Legacy Extension which will contain the embedded webextension.
   * @param {string} containerAddonParams.version
   *   The add-on version.
   * @param {nsIURI} containerAddonParams.resourceURI
   *   The nsIURI of the Legacy Extension container add-on.
   */
  constructor({id, resourceURI, version}) {
    this.addonId = id;
    this.resourceURI = resourceURI;
    this.version = version;

    // Setup status flag.
    this.started = false;
  }

  /**
   * Start the embedded webextension.
   *
   * @param {number} reason
   *   The add-on startup bootstrap reason received from the XPIProvider.
   *
   * @returns {Promise<LegacyContextAPI>} A promise which resolve to the API exposed to the
   *   legacy context.
   */
  startup(reason) {
    if (this.started) {
      return Promise.reject(new Error("This embedded extension has already been started"));
    }

    // Setup the startup promise.
    this.startupPromise = new Promise((resolve, reject) => {
      let embeddedExtensionURI = Services.io.newURI("webextension/", null, this.resourceURI);

      // This is the instance of the WebExtension embedded in the hybrid add-on.
      this.extension = new Extension({
        id: this.addonId,
        resourceURI: embeddedExtensionURI,
        version: this.version,
      });

      this.extension.isEmbedded = true;

      // This callback is register to the "startup" event, emitted by the Extension instance
      // after the extension manifest.json has been loaded without any errors, but before
      // starting any of the defined contexts (which give the legacy part a chance to subscribe
      // runtime.onMessage/onConnect listener before the background page has been loaded).
      const onBeforeStarted = () => {
        this.extension.off("startup", onBeforeStarted);

        // Resolve the startup promise and reset the startupError.
        this.started = true;
        this.startupPromise = null;

        // Create the legacy extension context, the legacy container addon
        // needs to use it before the embedded webextension startup,
        // because it is supposed to be used during the legacy container startup
        // to subscribe its message listeners (which are supposed to be able to
        // receive any message that the embedded part can try to send to it
        // during its startup).
        this.context = new LegacyExtensionContext(this.extension);

        // Destroy the LegacyExtensionContext cloneScope when
        // the embedded webextensions is unloaded.
        this.extension.callOnClose({
          close: () => {
            this.context.unload();
          },
        });

        // resolve startupPromise to execute any pending shutdown that has been
        // chained to it.
        resolve(this.context.api);
      };

      this.extension.on("startup", onBeforeStarted);

      // Run ambedded extension startup and catch any error during embedded extension
      // startup.
      this.extension.startup(reason).catch((err) => {
        this.started = false;
        this.startupPromise = null;
        this.extension.off("startup", onBeforeStarted);

        reject(err);
      });
    });

    return this.startupPromise;
  }

  /**
   * Shuts down the embedded webextension.
   *
   * @param {number} reason
   *   The add-on shutdown bootstrap reason received from the XPIProvider.
   *
   * @returns {Promise<void>} a promise that is resolved when the shutdown has been done
   */
  async shutdown(reason) {
    EmbeddedExtensionManager.untrackEmbeddedExtension(this);

    if (this.extension && !this.extension.hasShutdown) {
      let {extension} = this;
      this.extension = null;

      await extension.shutdown(reason);
    }
    return undefined;
  }
}

// Keep track on the created EmbeddedExtension instances and destroy
// them when their container addon is going to be disabled or uninstalled.
EmbeddedExtensionManager = {
  // Map of the existent EmbeddedExtensions instances by addon id.
  embeddedExtensionsByAddonId: new Map(),

  untrackEmbeddedExtension(embeddedExtensionInstance) {
    // Remove this instance from the tracked embedded extensions
    let id = embeddedExtensionInstance.addonId;
    if (this.embeddedExtensionsByAddonId.get(id) == embeddedExtensionInstance) {
      this.embeddedExtensionsByAddonId.delete(id);
    }
  },

  getEmbeddedExtensionFor({id, resourceURI, version}) {
    let embeddedExtension = this.embeddedExtensionsByAddonId.get(id);

    if (!embeddedExtension) {
      embeddedExtension = new EmbeddedExtension({id, resourceURI, version});
      // Keep track of the embedded extension instance.
      this.embeddedExtensionsByAddonId.set(id, embeddedExtension);
    }

    return embeddedExtension;
  },
};

this.LegacyExtensionsUtils = {
  getEmbeddedExtensionFor: (addon) => {
    return EmbeddedExtensionManager.getEmbeddedExtensionFor(addon);
  },
};
PK
!<F$modules/LightweightThemeConsumer.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["LightweightThemeConsumer"];

const {utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
  "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");

this.LightweightThemeConsumer =
 function LightweightThemeConsumer(aDocument) {
  this._doc = aDocument;
  this._win = aDocument.defaultView;

  let screen = this._win.screen;
  this._lastScreenWidth = screen.width;
  this._lastScreenHeight = screen.height;

  Services.obs.addObserver(this, "lightweight-theme-styling-update");

  var temp = {};
  Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
  this._update(temp.LightweightThemeManager.currentThemeForDisplay);
  this._win.addEventListener("resize", this);
}

LightweightThemeConsumer.prototype = {
  _lastData: null,
  _lastScreenWidth: null,
  _lastScreenHeight: null,
  // Whether the active lightweight theme should be shown on the window.
  _enabled: true,
  // Whether a lightweight theme is enabled.
  _active: false,

  enable() {
    this._enabled = true;
    this._update(this._lastData);
  },

  disable() {
    // Dance to keep the data, but reset the applied styles:
    let lastData = this._lastData
    this._update(null);
    this._enabled = false;
    this._lastData = lastData;
  },

  getData() {
    return this._enabled ? Cu.cloneInto(this._lastData, this._win) : null;
  },

  observe(aSubject, aTopic, aData) {
    if (aTopic != "lightweight-theme-styling-update")
      return;

    this._update(JSON.parse(aData));
  },

  handleEvent(aEvent) {
    let {width, height} = this._win.screen;

    if (this._lastScreenWidth != width || this._lastScreenHeight != height) {
      this._lastScreenWidth = width;
      this._lastScreenHeight = height;
      if (!this._active)
        return;
      this._update(this._lastData);
      Services.obs.notifyObservers(this._win, "lightweight-theme-optimized",
                                   JSON.stringify(this._lastData));
    }
  },

  destroy() {
    Services.obs.removeObserver(this, "lightweight-theme-styling-update");

    this._win.removeEventListener("resize", this);

    this._win = this._doc = null;
  },

  _update(aData) {
    if (!aData) {
      aData = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" };
      this._lastData = aData;
    } else {
      this._lastData = aData;
      aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen);
    }
    if (!this._enabled)
      return;

    let root = this._doc.documentElement;
    let active = !!aData.headerURL;
    let stateChanging = (active != this._active);

    // We need to clear these either way: either because the theme is being removed,
    // or because we are applying a new theme and the data might be bogus CSS,
    // so if we don't reset first, it'll keep the old value.
    root.style.removeProperty("--lwt-text-color");
    root.style.removeProperty("--lwt-accent-color");
    let textcolor = aData.textcolor || "black";
    _setProperty(root, active, "--lwt-text-color", textcolor);
    _setProperty(root, active, "--lwt-accent-color", aData.accentcolor || "white");
    if (active) {
      let dummy = this._doc.createElement("dummy");
      dummy.style.color = textcolor;
      let [r, g, b] = _parseRGB(this._doc.defaultView.getComputedStyle(dummy).color);
      let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
      root.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright");
      root.setAttribute("lwtheme", "true");
    } else {
      root.removeAttribute("lwthemetextcolor");
      root.removeAttribute("lwtheme");
    }

    this._active = active;

    if (aData.icons) {
      let activeIcons = active ? Object.keys(aData.icons).join(" ") : "";
      root.setAttribute("lwthemeicons", activeIcons);
      for (let [name, value] of Object.entries(aData.icons)) {
        _setImage(root, active, name, value);
      }
    } else {
      root.removeAttribute("lwthemeicons");
    }

    _setImage(root, active, "--lwt-header-image", aData.headerURL);
    _setImage(root, active, "--lwt-footer-image", aData.footerURL);
    _setImage(root, active, "--lwt-additional-images", aData.additionalBackgrounds);
    _setProperty(root, active, "--lwt-background-alignment", aData.backgroundsAlignment);
    _setProperty(root, active, "--lwt-background-tiling", aData.backgroundsTiling);

    if (active && aData.footerURL)
      root.setAttribute("lwthemefooter", "true");
    else
      root.removeAttribute("lwthemefooter");

    // On OS X, we extend the lightweight theme into the titlebar, which means setting
    // the chromemargin attribute. Some XUL applications already draw in the titlebar,
    // so we need to save the chromemargin value before we overwrite it with the value
    // that lets us draw in the titlebar. We stash this value on the root attribute so
    // that XUL applications have the ability to invalidate the saved value.
    if (AppConstants.platform == "macosx" && stateChanging) {
      if (!root.hasAttribute("chromemargin-nonlwtheme")) {
        root.setAttribute("chromemargin-nonlwtheme", root.getAttribute("chromemargin"));
      }

      if (active) {
        root.setAttribute("chromemargin", "0,-1,-1,-1");
      } else {
        let defaultChromemargin = root.getAttribute("chromemargin-nonlwtheme");
        if (defaultChromemargin) {
          root.setAttribute("chromemargin", defaultChromemargin);
        } else {
          root.removeAttribute("chromemargin");
        }
      }
    }
    Services.obs.notifyObservers(this._win, "lightweight-theme-window-updated",
                                 JSON.stringify(aData));
  }
}

function _setImage(aRoot, aActive, aVariableName, aURLs) {
  if (aURLs && !Array.isArray(aURLs)) {
    aURLs = [aURLs];
  }
  _setProperty(aRoot, aActive, aVariableName, aURLs && aURLs.map(v => `url("${v.replace(/"/g, '\\"')}")`).join(","));
}

function _setProperty(root, active, variableName, value) {
  if (active && value) {
    root.style.setProperty(variableName, value);
  } else {
    root.style.removeProperty(variableName);
  }
}

function _parseRGB(aColorString) {
  var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
  rgb.shift();
  return rgb.map(x => parseInt(x));
}
PK
!<JR\d\d#modules/LightweightThemeManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["LightweightThemeManager"];

const Cc = Components.classes;
const Ci = Components.interfaces;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Components.utils.import("resource://gre/modules/Services.jsm");

const ID_SUFFIX              = "@personas.mozilla.org";
const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_SKIN_TO_SELECT             = "extensions.lastSelectedSkin";
const ADDON_TYPE             = "theme";
const ADDON_TYPE_WEBEXT      = "webextension-theme";

const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";

const STRING_TYPE_NAME       = "type.%ID%.name";

const DEFAULT_MAX_USED_THEMES_COUNT = 30;

const MAX_PREVIEW_SECONDS = 30;

const MANDATORY = ["id", "name", "headerURL"];
const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
                  "previewURL", "author", "description", "homepageURL",
                  "updateURL", "version"];

const PERSIST_ENABLED = true;
const PERSIST_BYPASS_CACHE = false;
const PERSIST_FILES = {
  headerURL: "lightweighttheme-header",
  footerURL: "lightweighttheme-footer"
};

XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
  "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
  "resource://gre/modules/ServiceRequest.jsm");


XPCOMUtils.defineLazyGetter(this, "_prefs", () => {
  return Services.prefs.getBranch("lightweightThemes.");
});

Object.defineProperty(this, "_maxUsedThemes", {
  get() {
    delete this._maxUsedThemes;
    this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes", DEFAULT_MAX_USED_THEMES_COUNT);
    return this._maxUsedThemes;
  },

  set(val) {
    delete this._maxUsedThemes;
    return this._maxUsedThemes = val;
  },
  configurable: true,
});

// Holds the ID of the theme being enabled or disabled while sending out the
// events so cached AddonWrapper instances can return correct values for
// permissions and pendingOperations
var _themeIDBeingEnabled = null;
var _themeIDBeingDisabled = null;

// Holds optional fallback theme data that will be returned when no data for an
// active theme can be found. This the case for WebExtension Themes, for example.
var _fallbackThemeData = null;

// Convert from the old storage format (in which the order of usedThemes
// was combined with isThemeSelected to determine which theme was selected)
// to the new one (where a selectedThemeID determines which theme is selected).
(function() {
  let wasThemeSelected = _prefs.getBoolPref("isThemeSelected", false);

  if (wasThemeSelected) {
    _prefs.clearUserPref("isThemeSelected");
    let themes = [];
    try {
      themes = JSON.parse(_prefs.getStringPref("usedThemes"));
    } catch (e) { }

    if (Array.isArray(themes) && themes[0]) {
      _prefs.setCharPref("selectedThemeID", themes[0].id);
    }
  }
})();

this.LightweightThemeManager = {
  get name() {
    return "LightweightThemeManager";
  },

  set fallbackThemeData(data) {
    if (data && Object.getOwnPropertyNames(data).length) {
      _fallbackThemeData = Object.assign({}, data);
      if (PERSIST_ENABLED) {
        LightweightThemeImageOptimizer.purge();
        _persistImages(_fallbackThemeData, () => {});
      }
    } else {
      _fallbackThemeData = null;
    }
    return _fallbackThemeData;
  },

  // Themes that can be added for an application.  They can't be removed, and
  // will always show up at the top of the list.
  _builtInThemes: new Map(),

  get usedThemes() {
    let themes = [];
    try {
      themes = JSON.parse(_prefs.getStringPref("usedThemes"));
    } catch (e) { }

    themes.push(...this._builtInThemes.values());
    return themes;
  },

  get currentTheme() {
    let selectedThemeID = _prefs.getCharPref("selectedThemeID", "");

    let data = null;
    if (selectedThemeID) {
      data = this.getUsedTheme(selectedThemeID);
    }
    return data;
  },

  get currentThemeForDisplay() {
    var data = this.currentTheme;
    if (!data && _fallbackThemeData)
      data = _fallbackThemeData;

    if (data && PERSIST_ENABLED) {
      for (let key in PERSIST_FILES) {
        try {
          if (data[key] && _prefs.getBoolPref("persisted." + key))
            data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
                        + "?" + data.id + ";" + _version(data);
        } catch (e) {}
      }
    }

    return data;
  },

  set currentTheme(aData) {
    return _setCurrentTheme(aData, false);
  },

  setLocalTheme(aData) {
    _setCurrentTheme(aData, true);
  },

  getUsedTheme(aId) {
    var usedThemes = this.usedThemes;
    for (let usedTheme of usedThemes) {
      if (usedTheme.id == aId)
        return usedTheme;
    }
    return null;
  },

  forgetUsedTheme(aId) {
    let theme = this.getUsedTheme(aId);
    if (!theme || LightweightThemeManager._builtInThemes.has(theme.id))
      return;

    let wrapper = new AddonWrapper(theme);
    AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);

    var currentTheme = this.currentTheme;
    if (currentTheme && currentTheme.id == aId) {
      this.themeChanged(null);
      AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
    }

    _updateUsedThemes(_usedThemesExceptId(aId));
    AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
  },

  addBuiltInTheme(theme) {
    if (!theme || !theme.id || this.usedThemes.some(t => t.id == theme.id)) {
      throw new Error("Trying to add invalid builtIn theme");
    }

    this._builtInThemes.set(theme.id, theme);

    if (_prefs.getCharPref("selectedThemeID") == theme.id) {
      this.currentTheme = theme;
    }
  },

  forgetBuiltInTheme(id) {
    if (!this._builtInThemes.has(id)) {
      let currentTheme = this.currentTheme;
      if (currentTheme && currentTheme.id == id) {
        this.currentTheme = null;
      }
    }
    return this._builtInThemes.delete(id);
  },

  clearBuiltInThemes() {
    for (let id of this._builtInThemes.keys()) {
      this.forgetBuiltInTheme(id);
    }
  },

  previewTheme(aData) {
    let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
    cancel.data = false;
    Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
                                 JSON.stringify(aData));
    if (cancel.data)
      return;

    if (_previewTimer)
      _previewTimer.cancel();
    else
      _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    _previewTimer.initWithCallback(_previewTimerCallback,
                                   MAX_PREVIEW_SECONDS * 1000,
                                   _previewTimer.TYPE_ONE_SHOT);

    _notifyWindows(aData);
  },

  resetPreview() {
    if (_previewTimer) {
      _previewTimer.cancel();
      _previewTimer = null;
      _notifyWindows(this.currentThemeForDisplay);
    }
  },

  parseTheme(aString, aBaseURI) {
    try {
      return _sanitizeTheme(JSON.parse(aString), aBaseURI, false);
    } catch (e) {
      return null;
    }
  },

  updateCurrentTheme() {
    try {
      if (!_prefs.getBoolPref("update.enabled"))
        return;
    } catch (e) {
      return;
    }

    var theme = this.currentTheme;
    if (!theme || !theme.updateURL)
      return;

    var req = new ServiceRequest();

    req.mozBackgroundRequest = true;
    req.overrideMimeType("text/plain");
    req.open("GET", theme.updateURL, true);
    // Prevent the request from reading from the cache.
    req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    // Prevent the request from writing to the cache.
    req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;

    req.addEventListener("load", () => {
      if (req.status != 200)
        return;

      let newData = this.parseTheme(req.responseText, theme.updateURL);
      if (!newData ||
          newData.id != theme.id ||
          _version(newData) == _version(theme))
        return;

      var currentTheme = this.currentTheme;
      if (currentTheme && currentTheme.id == theme.id)
        this.currentTheme = newData;
    });

    req.send(null);
  },

  /**
   * Switches to a new lightweight theme.
   *
   * @param  aData
   *         The lightweight theme to switch to
   */
  themeChanged(aData) {
    if (_previewTimer) {
      _previewTimer.cancel();
      _previewTimer = null;
    }

    if (aData) {
      let usedThemes = _usedThemesExceptId(aData.id);
      usedThemes.unshift(aData);
      _updateUsedThemes(usedThemes);
      if (PERSIST_ENABLED) {
        LightweightThemeImageOptimizer.purge();
        _persistImages(aData, () => {
          _notifyWindows(this.currentThemeForDisplay);
        });
      }
    }

    if (aData)
      _prefs.setCharPref("selectedThemeID", aData.id);
    else
      _prefs.setCharPref("selectedThemeID", "");

    _notifyWindows(aData);
    Services.obs.notifyObservers(null, "lightweight-theme-changed");
  },

  /**
   * Starts the Addons provider and enables the new lightweight theme if
   * necessary.
   */
  startup() {
    if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
      let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
      if (id)
        this.themeChanged(this.getUsedTheme(id));
      else
        this.themeChanged(null);
      Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
    }

    _prefs.addObserver("", _prefObserver);
  },

  /**
   * Shuts down the provider.
   */
  shutdown() {
    _prefs.removeObserver("", _prefObserver);
  },

  /**
   * Called when a new add-on has been enabled when only one add-on of that type
   * can be enabled.
   *
   * @param  aId
   *         The ID of the newly enabled add-on
   * @param  aType
   *         The type of the newly enabled add-on
   * @param  aPendingRestart
   *         true if the newly enabled add-on will only become enabled after a
   *         restart
   */
  addonChanged(aId, aType, aPendingRestart) {
    if (aType != ADDON_TYPE && aType != ADDON_TYPE_WEBEXT)
      return;

    let id = _getInternalID(aId);
    let current = this.currentTheme;

    try {
      let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
      if (id == next && aPendingRestart)
        return;

      Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
      if (next) {
        AddonManagerPrivate.callAddonListeners("onOperationCancelled",
                                               new AddonWrapper(this.getUsedTheme(next)));
      } else if (id == current.id) {
        AddonManagerPrivate.callAddonListeners("onOperationCancelled",
                                               new AddonWrapper(current));
        return;
      }
    } catch (e) {
    }

    if (current) {
      if (current.id == id)
        return;
      _themeIDBeingDisabled = current.id;
      let wrapper = new AddonWrapper(current);
      if (aPendingRestart) {
        Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, "");
        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true);
      } else {
        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
        this.themeChanged(null);
        AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
      }
      _themeIDBeingDisabled = null;
    }

    if (id) {
      let theme = this.getUsedTheme(id);
      // WebExtension themes have an ID, but no LWT wrapper, so bail out here.
      if (!theme)
        return;
      _themeIDBeingEnabled = id;
      let wrapper = new AddonWrapper(theme);
      if (aPendingRestart) {
        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true);
        Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id);

        // Flush the preferences to disk so they survive any crash
        Services.prefs.savePrefFile(null);
      } else {
        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
        this.themeChanged(theme);
        AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
      }
      _themeIDBeingEnabled = null;
    }
  },

  /**
   * Called to get an Addon with a particular ID.
   *
   * @param  aId
   *         The ID of the add-on to retrieve
   * @param  aCallback
   *         A callback to pass the Addon to
   */
  getAddonByID(aId, aCallback) {
    let id = _getInternalID(aId);
    if (!id) {
      aCallback(null);
      return;
     }

    let theme = this.getUsedTheme(id);
    if (!theme) {
      aCallback(null);
      return;
    }

    aCallback(new AddonWrapper(theme));
  },

  /**
   * Called to get Addons of a particular type.
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types.
   * @param  aCallback
   *         A callback to pass an array of Addons to
   */
  getAddonsByTypes(aTypes, aCallback) {
    if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) {
      aCallback([]);
      return;
    }

    aCallback(this.usedThemes.map(a => new AddonWrapper(a)));
  },
};

const wrapperMap = new WeakMap();
let themeFor = wrapper => wrapperMap.get(wrapper);

/**
 * The AddonWrapper wraps lightweight theme to provide the data visible to
 * consumers of the AddonManager API.
 */
function AddonWrapper(aTheme) {
  wrapperMap.set(this, aTheme);
}

AddonWrapper.prototype = {
  get id() {
    return themeFor(this).id + ID_SUFFIX;
  },

  get type() {
    return ADDON_TYPE;
  },

  get isActive() {
    let current = LightweightThemeManager.currentTheme;
    if (current)
      return themeFor(this).id == current.id;
    return false;
  },

  get name() {
    return themeFor(this).name;
  },

  get version() {
    let theme = themeFor(this);
    return "version" in theme ? theme.version : "";
  },

  get creator() {
    let theme = themeFor(this);
    return "author" in theme ? new AddonManagerPrivate.AddonAuthor(theme.author) : null;
  },

  get screenshots() {
    let url = themeFor(this).previewURL;
    return [new AddonManagerPrivate.AddonScreenshot(url)];
  },

  get pendingOperations() {
    let pending = AddonManager.PENDING_NONE;
    if (this.isActive == this.userDisabled)
      pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE;
    return pending;
  },

  get operationsRequiringRestart() {
    // If a non-default theme is in use then a restart will be required to
    // enable lightweight themes unless dynamic theme switching is enabled
    if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
      return AddonManager.OP_NEEDS_RESTART_ENABLE;
    return AddonManager.OP_NEEDS_RESTART_NONE;
  },

  get size() {
    // The size changes depending on whether the theme is in use or not, this is
    // probably not worth exposing.
    return null;
  },

  get permissions() {
    let permissions = 0;

    // Do not allow uninstall of builtIn themes.
    if (!LightweightThemeManager._builtInThemes.has(themeFor(this).id))
      permissions = AddonManager.PERM_CAN_UNINSTALL;
    if (this.userDisabled)
      permissions |= AddonManager.PERM_CAN_ENABLE;
    else
      permissions |= AddonManager.PERM_CAN_DISABLE;
    return permissions;
  },

  get userDisabled() {
    let id = themeFor(this).id;
    if (_themeIDBeingEnabled == id)
      return false;
    if (_themeIDBeingDisabled == id)
      return true;

    try {
      let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
      return id != toSelect;
    } catch (e) {
      let current = LightweightThemeManager.currentTheme;
      return !current || current.id != id;
    }
  },

  set userDisabled(val) {
    if (val == this.userDisabled)
      return val;

    if (val)
      LightweightThemeManager.currentTheme = null;
    else
      LightweightThemeManager.currentTheme = themeFor(this);

    return val;
  },

  // Lightweight themes are never disabled by the application
  get appDisabled() {
    return false;
  },

  // Lightweight themes are always compatible
  get isCompatible() {
    return true;
  },

  get isPlatformCompatible() {
    return true;
  },

  get scope() {
    return AddonManager.SCOPE_PROFILE;
  },

  get foreignInstall() {
    return false;
  },

  uninstall() {
    LightweightThemeManager.forgetUsedTheme(themeFor(this).id);
  },

  cancelUninstall() {
    throw new Error("Theme is not marked to be uninstalled");
  },

  findUpdates(listener, reason, appVersion, platformVersion) {
    AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion);
  },

  // Lightweight themes are always compatible
  isCompatibleWith(appVersion, platformVersion) {
    return true;
  },

  // Lightweight themes are always securely updated
  get providesUpdatesSecurely() {
    return true;
  },

  // Lightweight themes are never blocklisted
  get blocklistState() {
    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  }
};

["description", "homepageURL", "iconURL"].forEach(function(prop) {
  Object.defineProperty(AddonWrapper.prototype, prop, {
    get() {
      let theme = themeFor(this);
      return prop in theme ? theme[prop] : null;
    },
    enumarable: true,
  });
});

["installDate", "updateDate"].forEach(function(prop) {
  Object.defineProperty(AddonWrapper.prototype, prop, {
    get() {
      let theme = themeFor(this);
      return prop in theme ? new Date(theme[prop]) : null;
    },
    enumarable: true,
  });
});

/**
 * Converts the ID used by the public AddonManager API to an lightweight theme
 * ID.
 *
 * @param   id
 *          The ID to be converted
 *
 * @return  the lightweight theme ID or null if the ID was not for a lightweight
 *          theme.
 */
function _getInternalID(id) {
  if (!id)
    return null;
  let len = id.length - ID_SUFFIX.length;
  if (len > 0 && id.substring(len) == ID_SUFFIX)
    return id.substring(0, len);
  return null;
}

function _setCurrentTheme(aData, aLocal) {
  aData = _sanitizeTheme(aData, null, aLocal);

  let needsRestart = (ADDON_TYPE == "theme") &&
                     Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN);

  let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
  cancel.data = false;
  Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested",
                               JSON.stringify(aData));

  let notify = true;

  if (aData) {
    let theme = LightweightThemeManager.getUsedTheme(aData.id);
    let isInstall = !theme || theme.version != aData.version;
    if (isInstall) {
      aData.updateDate = Date.now();
      if (theme && "installDate" in theme)
        aData.installDate = theme.installDate;
      else
        aData.installDate = aData.updateDate;

      var oldWrapper = theme ? new AddonWrapper(theme) : null;
      var wrapper = new AddonWrapper(aData);
      AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
                                               wrapper, oldWrapper, false);
      AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
    }

    let current = LightweightThemeManager.currentTheme;
    let usedThemes = _usedThemesExceptId(aData.id);
    if (current && current.id != aData.id) {
      usedThemes.splice(1, 0, aData);
    } else {
      if (current && current.id == aData.id && !needsRestart &&
          !Services.prefs.prefHasUserValue(PREF_SKIN_TO_SELECT)) {
        notify = false;
      }
      usedThemes.unshift(aData);
    }
    _updateUsedThemes(usedThemes);

    if (isInstall)
      AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
  }

  if (cancel.data)
    return null;

  if (notify) {
    AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null,
                                           ADDON_TYPE, needsRestart);
  }

  return LightweightThemeManager.currentTheme;
}

function _sanitizeTheme(aData, aBaseURI, aLocal) {
  if (!aData || typeof aData != "object")
    return null;

  var resourceProtocols = ["http", "https", "resource"];
  if (aLocal)
    resourceProtocols.push("file");
  var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):");

  function sanitizeProperty(prop) {
    if (!(prop in aData))
      return null;
    if (typeof aData[prop] != "string")
      return null;
    let val = aData[prop].trim();
    if (!val)
      return null;

    if (!/URL$/.test(prop))
      return val;

    try {
      val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec;
      if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val))
        return val;
      return null;
    } catch (e) {
      return null;
    }
  }

  let result = {};
  for (let mandatoryProperty of MANDATORY) {
    let val = sanitizeProperty(mandatoryProperty);
    if (!val)
      throw Components.results.NS_ERROR_INVALID_ARG;
    result[mandatoryProperty] = val;
  }

  for (let optionalProperty of OPTIONAL) {
    let val = sanitizeProperty(optionalProperty);
    if (!val)
      continue;
    result[optionalProperty] = val;
  }

  return result;
}

function _usedThemesExceptId(aId) {
  return LightweightThemeManager.usedThemes.filter(function(t) {
      return "id" in t && t.id != aId;
    });
}

function _version(aThemeData) {
  return aThemeData.version || "";
}

function _makeURI(aURL, aBaseURI) {
  return Services.io.newURI(aURL, null, aBaseURI);
}

function _updateUsedThemes(aList) {
  // Remove app-specific themes before saving them to the usedThemes pref.
  aList = aList.filter(theme => !LightweightThemeManager._builtInThemes.has(theme.id));

  // Send uninstall events for all themes that need to be removed.
  while (aList.length > _maxUsedThemes) {
    let wrapper = new AddonWrapper(aList[aList.length - 1]);
    AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
    aList.pop();
    AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
  }

  _prefs.setStringPref("usedThemes", JSON.stringify(aList));

  Services.obs.notifyObservers(null, "lightweight-theme-list-changed");
}

function _notifyWindows(aThemeData) {
  Services.obs.notifyObservers(null, "lightweight-theme-styling-update",
                               JSON.stringify(aThemeData));
}

var _previewTimer;
var _previewTimerCallback = {
  notify() {
    LightweightThemeManager.resetPreview();
  }
};

/**
 * Called when any of the lightweightThemes preferences are changed.
 */
function _prefObserver(aSubject, aTopic, aData) {
  switch (aData) {
    case "maxUsedThemes":
      _maxUsedThemes = _prefs.getIntPref(aData, DEFAULT_MAX_USED_THEMES_COUNT);

      // Update the theme list to remove any themes over the number we keep
      _updateUsedThemes(LightweightThemeManager.usedThemes);
      break;
  }
}

function _persistImages(aData, aCallback) {
  function onSuccess(key) {
    return function() {
      let current = LightweightThemeManager.currentTheme;
      if (current && current.id == aData.id) {
        _prefs.setBoolPref("persisted." + key, true);
      }
      if (--numFilesToPersist == 0 && aCallback) {
        aCallback();
      }
    };
  }

  let numFilesToPersist = 0;
  for (let key in PERSIST_FILES) {
    _prefs.setBoolPref("persisted." + key, false);
    if (aData[key]) {
      numFilesToPersist++;
      _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
    }
  }
}

function _getLocalImageURI(localFileName) {
  var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
  localFile.append(localFileName);
  return Services.io.newFileURI(localFile);
}

function _persistImage(sourceURL, localFileName, successCallback) {
  if (/^(file|resource):/.test(sourceURL))
    return;

  var targetURI = _getLocalImageURI(localFileName);
  var sourceURI = _makeURI(sourceURL);

  var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
                  .createInstance(Ci.nsIWebBrowserPersist);

  persist.persistFlags =
    Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
    Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
    (PERSIST_BYPASS_CACHE ?
       Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
       Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);

  persist.progressListener = new _persistProgressListener(successCallback);

  persist.saveURI(sourceURI, null,
                  null, Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
                  null, null, targetURI, null);
}

function _persistProgressListener(successCallback) {
  this.onLocationChange = function() {};
  this.onProgressChange = function() {};
  this.onStatusChange   = function() {};
  this.onSecurityChange = function() {};
  this.onStateChange    = function(aWebProgress, aRequest, aStateFlags, aStatus) {
    if (aRequest &&
        aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
        aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
      try {
        if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
          // success
          successCallback();
        }
      } catch (e) { }
      // failure
    }
  };
}

AddonManagerPrivate.registerProvider(LightweightThemeManager, [
  new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 5000)
]);
PK
!<;Ѱmodules/LoadContextInfo.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 jsm is here only for compatibility.  Extension developers may use it
 * to build nsILoadContextInfo to pass down to HTTP cache APIs.  Originally
 * it was possible to implement nsILoadContextInfo in JS.  But now it turned
 * out to be a built-in class only, so we need a component (service) as
 * a factory to build nsILoadContextInfo in a JS code.
 */

this.EXPORTED_SYMBOLS = ["LoadContextInfo"];
this.LoadContextInfo = Components.classes["@mozilla.org/load-context-info-factory;1"]
                                 .getService(Components.interfaces.nsILoadContextInfoFactory);
PK
!<!

modules/Locale.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Locale"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");

this.Locale = {
  /**
   * Gets the currently selected locale for display.
   * @return  the selected locale or "en-US" if none is selected
   */
  getLocale() {
    return Services.locale.getRequestedLocale() || "en-US";
  },

  /**
   * Selects the closest matching locale from a list of locales.
   *
   * @param  aLocales
   *         An array of locales
   * @return the best match for the currently selected locale
   */
  findClosestLocale(aLocales) {
    let appLocale = this.getLocale();

    // Holds the best matching localized resource
    var bestmatch = null;
    // The number of locale parts it matched with
    var bestmatchcount = 0;
    // The number of locale parts in the match
    var bestpartcount = 0;

    var matchLocales = [appLocale.toLowerCase()];
    /* If the current locale is English then it will find a match if there is
       a valid match for en-US so no point searching that locale too. */
    if (matchLocales[0].substring(0, 3) != "en-")
      matchLocales.push("en-us");

    for (let locale of matchLocales) {
      var lparts = locale.split("-");
      for (let localized of aLocales) {
        for (let found of localized.locales) {
          found = found.toLowerCase();
          // Exact match is returned immediately
          if (locale == found)
            return localized;

          var fparts = found.split("-");
          /* If we have found a possible match and this one isn't any longer
             then we dont need to check further. */
          if (bestmatch && fparts.length < bestmatchcount)
            continue;

          // Count the number of parts that match
          var maxmatchcount = Math.min(fparts.length, lparts.length);
          var matchcount = 0;
          while (matchcount < maxmatchcount &&
                 fparts[matchcount] == lparts[matchcount])
            matchcount++;

          /* If we matched more than the last best match or matched the same and
             this locale is less specific than the last best match. */
          if (matchcount > bestmatchcount ||
              (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
            bestmatch = localized;
            bestmatchcount = matchcount;
            bestpartcount = fparts.length;
          }
        }
      }
      // If we found a valid match for this locale return it
      if (bestmatch)
        return bestmatch;
    }
    return null;
  },
};
PK
!<ZO;d;dmodules/Log.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["Log"];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

const ONE_BYTE = 1;
const ONE_KILOBYTE = 1024 * ONE_BYTE;
const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;

const STREAM_SEGMENT_SIZE = 4096;
const PR_UINT32_MAX = 0xffffffff;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                  "resource://gre/modules/Task.jsm");
const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);


/*
 * Dump a message everywhere we can if we have a failure.
 */
function dumpError(text) {
  dump(text + "\n");
  Cu.reportError(text);
}

this.Log = {
  Level: {
    Fatal:  70,
    Error:  60,
    Warn:   50,
    Info:   40,
    Config: 30,
    Debug:  20,
    Trace:  10,
    All:    -1, // We don't want All to be falsy.
    Desc: {
      70: "FATAL",
      60: "ERROR",
      50: "WARN",
      40: "INFO",
      30: "CONFIG",
      20: "DEBUG",
      10: "TRACE",
      "-1":  "ALL",
    },
    Numbers: {
      "FATAL": 70,
      "ERROR": 60,
      "WARN": 50,
      "INFO": 40,
      "CONFIG": 30,
      "DEBUG": 20,
      "TRACE": 10,
      "ALL": -1,
    }
  },

  get repository() {
    delete Log.repository;
    Log.repository = new LoggerRepository();
    return Log.repository;
  },
  set repository(value) {
    delete Log.repository;
    Log.repository = value;
  },

  LogMessage,
  Logger,
  LoggerRepository,

  Formatter,
  BasicFormatter,
  MessageOnlyFormatter,
  StructuredFormatter,

  Appender,
  DumpAppender,
  ConsoleAppender,
  StorageStreamAppender,

  FileAppender,
  BoundedFileAppender,

  ParameterFormatter,
  // Logging helper:
  // let logger = Log.repository.getLogger("foo");
  // logger.info(Log.enumerateInterfaces(someObject).join(","));
  enumerateInterfaces: function Log_enumerateInterfaces(aObject) {
    let interfaces = [];

    for (let i in Ci) {
      try {
        aObject.QueryInterface(Ci[i]);
        interfaces.push(i);
      } catch (ex) {}
    }

    return interfaces;
  },

  // Logging helper:
  // let logger = Log.repository.getLogger("foo");
  // logger.info(Log.enumerateProperties(someObject).join(","));
  enumerateProperties(aObject, aExcludeComplexTypes) {
    let properties = [];

    for (let p in aObject) {
      try {
        if (aExcludeComplexTypes &&
            (typeof(aObject[p]) == "object" || typeof(aObject[p]) == "function"))
          continue;
        properties.push(p + " = " + aObject[p]);
      } catch (ex) {
        properties.push(p + " = " + ex);
      }
    }

    return properties;
  },

  _formatError: function _formatError(e) {
    let result = e.toString();
    if (e.fileName) {
      result +=  " (" + e.fileName;
      if (e.lineNumber) {
        result += ":" + e.lineNumber;
      }
      if (e.columnNumber) {
        result += ":" + e.columnNumber;
      }
      result += ")";
    }
    return result + " " + Log.stackTrace(e);
  },

  // This is for back compatibility with services/common/utils.js; we duplicate
  // some of the logic in ParameterFormatter
  exceptionStr: function exceptionStr(e) {
    if (!e) {
      return "" + e;
    }
    if (e instanceof Ci.nsIException) {
      return e.toString() + " " + Log.stackTrace(e);
    } else if (isError(e)) {
      return Log._formatError(e);
    }
    // else
    let message = e.message ? e.message : e;
    return message + " " + Log.stackTrace(e);
  },

  stackTrace: function stackTrace(e) {
    // Wrapped nsIException
    if (e.location) {
      let frame = e.location;
      let output = [];
      while (frame) {
        // Works on frames or exceptions, munges file:// URIs to shorten the paths
        // FIXME: filename munging is sort of hackish, might be confusing if
        // there are multiple extensions with similar filenames
        let str = "<file:unknown>";

        let file = frame.filename || frame.fileName;
        if (file) {
          str = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
        }

        if (frame.lineNumber) {
          str += ":" + frame.lineNumber;
        }

        if (frame.name) {
          str = frame.name + "()@" + str;
        }

        if (str) {
          output.push(str);
        }
        frame = frame.caller;
      }
      return "Stack trace: " + output.join(" < ");
    }
    // Standard JS exception
    if (e.stack) {
      let stack = e.stack;
      // Avoid loading Task.jsm if there's no task on the stack.
      if (stack.includes("/Task.jsm:"))
        stack = Task.Debugging.generateReadableStack(stack);
      return "JS Stack trace: " + stack.trim()
        .replace(/\n/g, " < ").replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
    }

    return "No traceback available";
  }
};

/*
 * LogMessage
 * Encapsulates a single log event's data
 */
function LogMessage(loggerName, level, message, params) {
  this.loggerName = loggerName;
  this.level = level;
  /*
   * Special case to handle "log./level/(object)", for example logging a caught exception
   * without providing text or params like: catch(e) { logger.warn(e) }
   * Treating this as an empty text with the object in the 'params' field causes the
   * object to be formatted properly by BasicFormatter.
   */
  if (!params && message && (typeof(message) == "object") &&
      (typeof(message.valueOf()) != "string")) {
    this.message = null;
    this.params = message;
  } else {
    // If the message text is empty, or a string, or a String object, normal handling
    this.message = message;
    this.params = params;
  }

  // The _structured field will correspond to whether this message is to
  // be interpreted as a structured message.
  this._structured = this.params && this.params.action;
  this.time = Date.now();
}
LogMessage.prototype = {
  get levelDesc() {
    if (this.level in Log.Level.Desc)
      return Log.Level.Desc[this.level];
    return "UNKNOWN";
  },

  toString: function LogMsg_toString() {
    let msg = "LogMessage [" + this.time + " " + this.level + " " +
      this.message;
    if (this.params) {
      msg += " " + JSON.stringify(this.params);
    }
    return msg + "]"
  }
};

/*
 * Logger
 * Hierarchical version.  Logs to all appenders, assigned or inherited
 */

function Logger(name, repository) {
  if (!repository)
    repository = Log.repository;
  this._name = name;
  this.children = [];
  this.ownAppenders = [];
  this.appenders = [];
  this._repository = repository;
}
Logger.prototype = {
  get name() {
    return this._name;
  },

  _level: null,
  get level() {
    if (this._level != null)
      return this._level;
    if (this.parent)
      return this.parent.level;
    dumpError("Log warning: root logger configuration error: no level defined");
    return Log.Level.All;
  },
  set level(level) {
    this._level = level;
  },

  _parent: null,
  get parent() {
    return this._parent;
  },
  set parent(parent) {
    if (this._parent == parent) {
      return;
    }
    // Remove ourselves from parent's children
    if (this._parent) {
      let index = this._parent.children.indexOf(this);
      if (index != -1) {
        this._parent.children.splice(index, 1);
      }
    }
    this._parent = parent;
    parent.children.push(this);
    this.updateAppenders();
  },

  updateAppenders: function updateAppenders() {
    if (this._parent) {
      let notOwnAppenders = this._parent.appenders.filter(function(appender) {
        return this.ownAppenders.indexOf(appender) == -1;
      }, this);
      this.appenders = notOwnAppenders.concat(this.ownAppenders);
    } else {
      this.appenders = this.ownAppenders.slice();
    }

    // Update children's appenders.
    for (let i = 0; i < this.children.length; i++) {
      this.children[i].updateAppenders();
    }
  },

  addAppender: function Logger_addAppender(appender) {
    if (this.ownAppenders.indexOf(appender) != -1) {
      return;
    }
    this.ownAppenders.push(appender);
    this.updateAppenders();
  },

  removeAppender: function Logger_removeAppender(appender) {
    let index = this.ownAppenders.indexOf(appender);
    if (index == -1) {
      return;
    }
    this.ownAppenders.splice(index, 1);
    this.updateAppenders();
  },

  /**
   * Logs a structured message object.
   *
   * @param action
   *        (string) A message action, one of a set of actions known to the
   *          log consumer.
   * @param params
   *        (object) Parameters to be included in the message.
   *          If _level is included as a key and the corresponding value
   *          is a number or known level name, the message will be logged
   *          at the indicated level. If _message is included as a key, the
   *          value is used as the descriptive text for the message.
   */
  logStructured(action, params) {
    if (!action) {
      throw "An action is required when logging a structured message.";
    }
    if (!params) {
      this.log(this.level, undefined, {"action": action});
      return;
    }
    if (typeof(params) != "object") {
      throw "The params argument is required to be an object.";
    }

    let level = params._level;
    if (level) {
      let ulevel = level.toUpperCase();
      if (ulevel in Log.Level.Numbers) {
        level = Log.Level.Numbers[ulevel];
      }
    } else {
      level = this.level;
    }

    params.action = action;
    this.log(level, params._message, params);
  },

  log(level, string, params) {
    if (this.level > level)
      return;

    // Hold off on creating the message object until we actually have
    // an appender that's responsible.
    let message;
    let appenders = this.appenders;
    for (let appender of appenders) {
      if (appender.level > level) {
        continue;
      }
      if (!message) {
        message = new LogMessage(this._name, level, string, params);
      }
      appender.append(message);
    }
  },

  fatal(string, params) {
    this.log(Log.Level.Fatal, string, params);
  },
  error(string, params) {
    this.log(Log.Level.Error, string, params);
  },
  warn(string, params) {
    this.log(Log.Level.Warn, string, params);
  },
  info(string, params) {
    this.log(Log.Level.Info, string, params);
  },
  config(string, params) {
    this.log(Log.Level.Config, string, params);
  },
  debug(string, params) {
    this.log(Log.Level.Debug, string, params);
  },
  trace(string, params) {
    this.log(Log.Level.Trace, string, params);
  }
};

/*
 * LoggerRepository
 * Implements a hierarchy of Loggers
 */

function LoggerRepository() {}
LoggerRepository.prototype = {
  _loggers: {},

  _rootLogger: null,
  get rootLogger() {
    if (!this._rootLogger) {
      this._rootLogger = new Logger("root", this);
      this._rootLogger.level = Log.Level.All;
    }
    return this._rootLogger;
  },
  set rootLogger(logger) {
    throw "Cannot change the root logger";
  },

  _updateParents: function LogRep__updateParents(name) {
    let pieces = name.split(".");
    let cur, parent;

    // find the closest parent
    // don't test for the logger name itself, as there's a chance it's already
    // there in this._loggers
    for (let i = 0; i < pieces.length - 1; i++) {
      if (cur)
        cur += "." + pieces[i];
      else
        cur = pieces[i];
      if (cur in this._loggers)
        parent = cur;
    }

    // if we didn't assign a parent above, there is no parent
    if (!parent)
      this._loggers[name].parent = this.rootLogger;
    else
      this._loggers[name].parent = this._loggers[parent];

    // trigger updates for any possible descendants of this logger
    for (let logger in this._loggers) {
      if (logger != name && logger.indexOf(name) == 0)
        this._updateParents(logger);
    }
  },

  /**
   * Obtain a named Logger.
   *
   * The returned Logger instance for a particular name is shared among
   * all callers. In other words, if two consumers call getLogger("foo"),
   * they will both have a reference to the same object.
   *
   * @return Logger
   */
  getLogger(name) {
    if (name in this._loggers)
      return this._loggers[name];
    this._loggers[name] = new Logger(name, this);
    this._updateParents(name);
    return this._loggers[name];
  },

  /**
   * Obtain a Logger that logs all string messages with a prefix.
   *
   * A common pattern is to have separate Logger instances for each instance
   * of an object. But, you still want to distinguish between each instance.
   * Since Log.repository.getLogger() returns shared Logger objects,
   * monkeypatching one Logger modifies them all.
   *
   * This function returns a new object with a prototype chain that chains
   * up to the original Logger instance. The new prototype has log functions
   * that prefix content to each message.
   *
   * @param name
   *        (string) The Logger to retrieve.
   * @param prefix
   *        (string) The string to prefix each logged message with.
   */
  getLoggerWithMessagePrefix(name, prefix) {
    let log = this.getLogger(name);

    let proxy = Object.create(log);
    proxy.log = (level, string, params) => log.log(level, prefix + string, params);
    return proxy;
  },
};

/*
 * Formatters
 * These massage a LogMessage into whatever output is desired.
 * BasicFormatter and StructuredFormatter are implemented here.
 */

// Abstract formatter
function Formatter() {}
Formatter.prototype = {
  format: function Formatter_format(message) {}
};

// Basic formatter that doesn't do anything fancy.
function BasicFormatter(dateFormat) {
  if (dateFormat) {
    this.dateFormat = dateFormat;
  }
  this.parameterFormatter = new ParameterFormatter();
}
BasicFormatter.prototype = {
  __proto__: Formatter.prototype,

  /**
   * Format the text of a message with optional parameters.
   * If the text contains ${identifier}, replace that with
   * the value of params[identifier]; if ${}, replace that with
   * the entire params object. If no params have been substituted
   * into the text, format the entire object and append that
   * to the message.
   */
  formatText(message) {
    let params = message.params;
    if (typeof(params) == "undefined") {
      return message.message || "";
    }
    // Defensive handling of non-object params
    // We could add a special case for NSRESULT values here...
    let pIsObject = (typeof(params) == "object" || typeof(params) == "function");

    // if we have params, try and find substitutions.
    if (this.parameterFormatter) {
      // have we successfully substituted any parameters into the message?
      // in the log message
      let subDone = false;
      let regex = /\$\{(\S*)\}/g;
      let textParts = [];
      if (message.message) {
        textParts.push(message.message.replace(regex, (_, sub) => {
          // ${foo} means use the params['foo']
          if (sub) {
            if (pIsObject && sub in message.params) {
              subDone = true;
              return this.parameterFormatter.format(message.params[sub]);
            }
            return "${" + sub + "}";
          }
          // ${} means use the entire params object.
          subDone = true;
          return this.parameterFormatter.format(message.params);
        }));
      }
      if (!subDone) {
        // There were no substitutions in the text, so format the entire params object
        let rest = this.parameterFormatter.format(message.params);
        if (rest !== null && rest != "{}") {
          textParts.push(rest);
        }
      }
      return textParts.join(": ");
    }
    return undefined;
  },

  format: function BF_format(message) {
    return message.time + "\t" +
      message.loggerName + "\t" +
      message.levelDesc + "\t" +
      this.formatText(message);
  }
};

/**
 * A formatter that only formats the string message component.
 */
function MessageOnlyFormatter() {
}
MessageOnlyFormatter.prototype = Object.freeze({
  __proto__: Formatter.prototype,

  format(message) {
    return message.message;
  },
});

// Structured formatter that outputs JSON based on message data.
// This formatter will format unstructured messages by supplying
// default values.
function StructuredFormatter() { }
StructuredFormatter.prototype = {
  __proto__: Formatter.prototype,

  format(logMessage) {
    let output = {
      _time: logMessage.time,
      _namespace: logMessage.loggerName,
      _level: logMessage.levelDesc
    };

    for (let key in logMessage.params) {
      output[key] = logMessage.params[key];
    }

    if (!output.action) {
      output.action = "UNKNOWN";
    }

    if (!output._message && logMessage.message) {
      output._message = logMessage.message;
    }

    return JSON.stringify(output);
  }
}

/**
 * Test an object to see if it is a Mozilla JS Error.
 */
function isError(aObj) {
  return (aObj && typeof(aObj) == "object" && "name" in aObj && "message" in aObj &&
          "fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
}

/*
 * Parameter Formatters
 * These massage an object used as a parameter for a LogMessage into
 * a string representation of the object.
 */

function ParameterFormatter() {
  this._name = "ParameterFormatter"
}
ParameterFormatter.prototype = {
  format(ob) {
    try {
      if (ob === undefined) {
        return "undefined";
      }
      if (ob === null) {
        return "null";
      }
      // Pass through primitive types and objects that unbox to primitive types.
      if ((typeof(ob) != "object" || typeof(ob.valueOf()) != "object") &&
          typeof(ob) != "function") {
        return ob;
      }
      if (ob instanceof Ci.nsIException) {
        return ob.toString() + " " + Log.stackTrace(ob);
      } else if (isError(ob)) {
        return Log._formatError(ob);
      }
      // Just JSONify it. Filter out our internal fields and those the caller has
      // already handled.
      return JSON.stringify(ob, (key, val) => {
        if (INTERNAL_FIELDS.has(key)) {
          return undefined;
        }
        return val;
      });
    } catch (e) {
      dumpError("Exception trying to format object for log message: " + Log.exceptionStr(e));
    }
    // Fancy formatting failed. Just toSource() it - but even this may fail!
    try {
      return ob.toSource();
    } catch (_) { }
    try {
      return "" + ob;
    } catch (_) {
      return "[object]"
    }
  }
}

/*
 * Appenders
 * These can be attached to Loggers to log to different places
 * Simply subclass and override doAppend to implement a new one
 */

function Appender(formatter) {
  this._name = "Appender";
  this._formatter = formatter ? formatter : new BasicFormatter();
}
Appender.prototype = {
  level: Log.Level.All,

  append: function App_append(message) {
    if (message) {
      this.doAppend(this._formatter.format(message));
    }
  },
  toString: function App_toString() {
    return this._name + " [level=" + this.level +
      ", formatter=" + this._formatter + "]";
  },
  doAppend: function App_doAppend(formatted) {}
};

/*
 * DumpAppender
 * Logs to standard out
 */

function DumpAppender(formatter) {
  Appender.call(this, formatter);
  this._name = "DumpAppender";
}
DumpAppender.prototype = {
  __proto__: Appender.prototype,

  doAppend: function DApp_doAppend(formatted) {
    dump(formatted + "\n");
  }
};

/*
 * ConsoleAppender
 * Logs to the javascript console
 */

function ConsoleAppender(formatter) {
  Appender.call(this, formatter);
  this._name = "ConsoleAppender";
}
ConsoleAppender.prototype = {
  __proto__: Appender.prototype,

  // XXX this should be replaced with calls to the Browser Console
  append: function App_append(message) {
    if (message) {
      let m = this._formatter.format(message);
      if (message.level > Log.Level.Warn) {
        Cu.reportError(m);
        return;
      }
      this.doAppend(m);
    }
  },

  doAppend: function CApp_doAppend(formatted) {
    Cc["@mozilla.org/consoleservice;1"].
      getService(Ci.nsIConsoleService).logStringMessage(formatted);
  }
};

/**
 * Append to an nsIStorageStream
 *
 * This writes logging output to an in-memory stream which can later be read
 * back as an nsIInputStream. It can be used to avoid expensive I/O operations
 * during logging. Instead, one can periodically consume the input stream and
 * e.g. write it to disk asynchronously.
 */
function StorageStreamAppender(formatter) {
  Appender.call(this, formatter);
  this._name = "StorageStreamAppender";
}

StorageStreamAppender.prototype = {
  __proto__: Appender.prototype,

  _converterStream: null, // holds the nsIConverterOutputStream
  _outputStream: null,    // holds the underlying nsIOutputStream

  _ss: null,

  get outputStream() {
    if (!this._outputStream) {
      // First create a raw stream. We can bail out early if that fails.
      this._outputStream = this.newOutputStream();
      if (!this._outputStream) {
        return null;
      }

      // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
      // the instance if we already have one.
      if (!this._converterStream) {
        this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
                                  .createInstance(Ci.nsIConverterOutputStream);
      }
      this._converterStream.init(this._outputStream, "UTF-8");
    }
    return this._converterStream;
  },

  newOutputStream: function newOutputStream() {
    let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
                          .createInstance(Ci.nsIStorageStream);
    ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
    return ss.getOutputStream(0);
  },

  getInputStream: function getInputStream() {
    if (!this._ss) {
      return null;
    }
    return this._ss.newInputStream(0);
  },

  reset: function reset() {
    if (!this._outputStream) {
      return;
    }
    this.outputStream.close();
    this._outputStream = null;
    this._ss = null;
  },

  doAppend(formatted) {
    if (!formatted) {
      return;
    }
    try {
      this.outputStream.writeString(formatted + "\n");
    } catch (ex) {
      if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
        // The underlying output stream is closed, so let's open a new one
        // and try again.
        this._outputStream = null;
      } try {
          this.outputStream.writeString(formatted + "\n");
      } catch (ex) {
        // Ah well, we tried, but something seems to be hosed permanently.
      }
    }
  }
};

/**
 * File appender
 *
 * Writes output to file using OS.File.
 */
function FileAppender(path, formatter) {
  Appender.call(this, formatter);
  this._name = "FileAppender";
  this._encoder = new TextEncoder();
  this._path = path;
  this._file = null;
  this._fileReadyPromise = null;

  // This is a promise exposed for testing/debugging the logger itself.
  this._lastWritePromise = null;
}

FileAppender.prototype = {
  __proto__: Appender.prototype,

  _openFile() {
    return (async () => {
      try {
        this._file = await OS.File.open(this._path,
                                        {truncate: true});
      } catch (err) {
        if (err instanceof OS.File.Error) {
          this._file = null;
        } else {
          throw err;
        }
      }
    })();
  },

  _getFile() {
    if (!this._fileReadyPromise) {
      this._fileReadyPromise = this._openFile();
    }

    return this._fileReadyPromise;
  },

  doAppend(formatted) {
    let array = this._encoder.encode(formatted + "\n");
    if (this._file) {
      this._lastWritePromise = this._file.write(array);
    } else {
      this._lastWritePromise = this._getFile().then(_ => {
        this._fileReadyPromise = null;
        if (this._file) {
          return this._file.write(array);
        }
        return undefined;
      });
    }
  },

  reset() {
    let fileClosePromise = this._file.close();
    return fileClosePromise.then(_ => {
      this._file = null;
      return OS.File.remove(this._path);
    });
  }
};

/**
 * Bounded File appender
 *
 * Writes output to file using OS.File. After the total message size
 * (as defined by formatted.length) exceeds maxSize, existing messages
 * will be discarded, and subsequent writes will be appended to a new log file.
 */
function BoundedFileAppender(path, formatter, maxSize = 2 * ONE_MEGABYTE) {
  FileAppender.call(this, path, formatter);
  this._name = "BoundedFileAppender";
  this._size = 0;
  this._maxSize = maxSize;
  this._closeFilePromise = null;
}

BoundedFileAppender.prototype = {
  __proto__: FileAppender.prototype,

  doAppend(formatted) {
    if (!this._removeFilePromise) {
      if (this._size < this._maxSize) {
        this._size += formatted.length;
        return FileAppender.prototype.doAppend.call(this, formatted);
      }
      this._removeFilePromise = this.reset();
    }
    this._removeFilePromise.then(_ => {
      this._removeFilePromise = null;
      this.doAppend(formatted);
    });
    return undefined;
  },

  reset() {
    let fileClosePromise;
    if (this._fileReadyPromise) {
      // An attempt to open the file may still be in progress.
      fileClosePromise = this._fileReadyPromise.then(_ => {
        return this._file.close();
      });
    } else {
      fileClosePromise = this._file.close();
    }

    return fileClosePromise.then(_ => {
      this._size = 0;
      this._file = null;
      return OS.File.remove(this._path);
    });
  }
};
PK
!<P	h	hmodules/LoginHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Contains functions shared by different Login Manager components.
 *
 * This JavaScript module exists in order to share code between the different
 * XPCOM components that constitute the Login Manager, including implementations
 * of nsILoginManager and nsILoginManagerStorage.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "LoginHelper",
];

// Globals

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

// LoginHelper

/**
 * Contains functions shared by different Login Manager components.
 */
this.LoginHelper = {
  /**
   * Warning: these only update if a logger was created.
   */
  debug: Services.prefs.getBoolPref("signon.debug"),
  formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
  schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
  insecureAutofill: Services.prefs.getBoolPref("signon.autofillForms.http"),
  showInsecureFieldWarning: Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled"),

  createLogger(aLogPrefix) {
    let getMaxLogLevel = () => {
      return this.debug ? "debug" : "warn";
    };

    // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
    let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
    let consoleOptions = {
      maxLogLevel: getMaxLogLevel(),
      prefix: aLogPrefix,
    };
    let logger = new ConsoleAPI(consoleOptions);

    // Watch for pref changes and update this.debug and the maxLogLevel for created loggers
    Services.prefs.addObserver("signon.", () => {
      this.debug = Services.prefs.getBoolPref("signon.debug");
      this.formlessCaptureEnabled = Services.prefs.getBoolPref("signon.formlessCapture.enabled");
      this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
      this.insecureAutofill = Services.prefs.getBoolPref("signon.autofillForms.http");
      logger.maxLogLevel = getMaxLogLevel();
    });

    Services.prefs.addObserver("security.insecure_field_warning.", () => {
      this.showInsecureFieldWarning = Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled");
    });

    return logger;
  },

  /**
   * Due to the way the signons2.txt file is formatted, we need to make
   * sure certain field values or characters do not cause the file to
   * be parsed incorrectly.  Reject hostnames that we can't store correctly.
   *
   * @throws String with English message in case validation failed.
   */
  checkHostnameValue(aHostname) {
    // Nulls are invalid, as they don't round-trip well.  Newlines are also
    // invalid for any field stored as plaintext, and a hostname made of a
    // single dot cannot be stored in the legacy format.
    if (aHostname == "." ||
        aHostname.indexOf("\r") != -1 ||
        aHostname.indexOf("\n") != -1 ||
        aHostname.indexOf("\0") != -1) {
      throw new Error("Invalid hostname");
    }
  },

  /**
   * Due to the way the signons2.txt file is formatted, we need to make
   * sure certain field values or characters do not cause the file to
   * be parsed incorrectly.  Reject logins that we can't store correctly.
   *
   * @throws String with English message in case validation failed.
   */
  checkLoginValues(aLogin) {
    function badCharacterPresent(l, c) {
      return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
              (l.httpRealm && l.httpRealm.indexOf(c) != -1) ||
                                  l.hostname.indexOf(c) != -1 ||
                                  l.usernameField.indexOf(c) != -1 ||
                                  l.passwordField.indexOf(c) != -1);
    }

    // Nulls are invalid, as they don't round-trip well.
    // Mostly not a formatting problem, although ".\0" can be quirky.
    if (badCharacterPresent(aLogin, "\0")) {
      throw new Error("login values can't contain nulls");
    }

    // In theory these nulls should just be rolled up into the encrypted
    // values, but nsISecretDecoderRing doesn't use nsStrings, so the
    // nulls cause truncation. Check for them here just to avoid
    // unexpected round-trip surprises.
    if (aLogin.username.indexOf("\0") != -1 ||
        aLogin.password.indexOf("\0") != -1) {
      throw new Error("login values can't contain nulls");
    }

    // Newlines are invalid for any field stored as plaintext.
    if (badCharacterPresent(aLogin, "\r") ||
        badCharacterPresent(aLogin, "\n")) {
      throw new Error("login values can't contain newlines");
    }

    // A line with just a "." can have special meaning.
    if (aLogin.usernameField == "." ||
        aLogin.formSubmitURL == ".") {
      throw new Error("login values can't be periods");
    }

    // A hostname with "\ \(" won't roundtrip.
    // eg host="foo (", realm="bar" --> "foo ( (bar)"
    // vs host="foo", realm=" (bar" --> "foo ( (bar)"
    if (aLogin.hostname.indexOf(" (") != -1) {
      throw new Error("bad parens in hostname");
    }
  },

  /**
   * Returns a new XPCOM property bag with the provided properties.
   *
   * @param {Object} aProperties
   *        Each property of this object is copied to the property bag.  This
   *        parameter can be omitted to return an empty property bag.
   *
   * @return A new property bag, that is an instance of nsIWritablePropertyBag,
   *         nsIWritablePropertyBag2, nsIPropertyBag, and nsIPropertyBag2.
   */
  newPropertyBag(aProperties) {
    let propertyBag = Cc["@mozilla.org/hash-property-bag;1"]
                      .createInstance(Ci.nsIWritablePropertyBag);
    if (aProperties) {
      for (let [name, value] of Object.entries(aProperties)) {
        propertyBag.setProperty(name, value);
      }
    }
    return propertyBag.QueryInterface(Ci.nsIPropertyBag)
                      .QueryInterface(Ci.nsIPropertyBag2)
                      .QueryInterface(Ci.nsIWritablePropertyBag2);
  },

  /**
   * Helper to avoid the `count` argument and property bags when calling
   * Services.logins.searchLogins from JS.
   *
   * @param {Object} aSearchOptions - A regular JS object to copy to a property bag before searching
   * @return {nsILoginInfo[]} - The result of calling searchLogins.
   */
  searchLoginsWithObject(aSearchOptions) {
    return Services.logins.searchLogins({}, this.newPropertyBag(aSearchOptions));
  },

  /**
   * @param {String} aLoginOrigin - An origin value from a stored login's
   *                                hostname or formSubmitURL properties.
   * @param {String} aSearchOrigin - The origin that was are looking to match
   *                                 with aLoginOrigin. This would normally come
   *                                 from a form or page that we are considering.
   * @param {nsILoginFindOptions} aOptions - Options to affect whether the origin
   *                                         from the login (aLoginOrigin) is a
   *                                         match for the origin we're looking
   *                                         for (aSearchOrigin).
   */
  isOriginMatching(aLoginOrigin, aSearchOrigin, aOptions = {
    schemeUpgrades: false,
  }) {
    if (aLoginOrigin == aSearchOrigin) {
      return true;
    }

    if (!aOptions) {
      return false;
    }

    if (aOptions.schemeUpgrades) {
      try {
        let loginURI = Services.io.newURI(aLoginOrigin);
        let searchURI = Services.io.newURI(aSearchOrigin);
        if (loginURI.scheme == "http" && searchURI.scheme == "https" &&
            loginURI.hostPort == searchURI.hostPort) {
          return true;
        }
      } catch (ex) {
        // newURI will throw for some values e.g. chrome://FirefoxAccounts
        return false;
      }
    }

    return false;
  },

  doLoginsMatch(aLogin1, aLogin2, {
    ignorePassword = false,
    ignoreSchemes = false,
  }) {
    if (aLogin1.httpRealm != aLogin2.httpRealm ||
        aLogin1.username != aLogin2.username)
      return false;

    if (!ignorePassword && aLogin1.password != aLogin2.password)
      return false;

    if (ignoreSchemes) {
      let hostname1URI = Services.io.newURI(aLogin1.hostname);
      let hostname2URI = Services.io.newURI(aLogin2.hostname);
      if (hostname1URI.hostPort != hostname2URI.hostPort)
        return false;

      if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
          Services.io.newURI(aLogin1.formSubmitURL).hostPort !=
          Services.io.newURI(aLogin2.formSubmitURL).hostPort)
        return false;
    } else {
      if (aLogin1.hostname != aLogin2.hostname)
        return false;

      // If either formSubmitURL is blank (but not null), then match.
      if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
          aLogin1.formSubmitURL != aLogin2.formSubmitURL)
        return false;
    }

    // The .usernameField and .passwordField values are ignored.

    return true;
  },

  /**
   * Creates a new login object that results by modifying the given object with
   * the provided data.
   *
   * @param aOldStoredLogin
   *        Existing nsILoginInfo object to modify.
   * @param aNewLoginData
   *        The new login values, either as nsILoginInfo or nsIProperyBag.
   *
   * @return The newly created nsILoginInfo object.
   *
   * @throws String with English message in case validation failed.
   */
  buildModifiedLogin(aOldStoredLogin, aNewLoginData) {
    function bagHasProperty(aPropName) {
      try {
        aNewLoginData.getProperty(aPropName);
        return true;
      } catch (ex) { }
      return false;
    }

    aOldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);

    let newLogin;
    if (aNewLoginData instanceof Ci.nsILoginInfo) {
      // Clone the existing login to get its nsILoginMetaInfo, then init it
      // with the replacement nsILoginInfo data from the new login.
      newLogin = aOldStoredLogin.clone();
      newLogin.init(aNewLoginData.hostname,
                    aNewLoginData.formSubmitURL, aNewLoginData.httpRealm,
                    aNewLoginData.username, aNewLoginData.password,
                    aNewLoginData.usernameField, aNewLoginData.passwordField);
      newLogin.QueryInterface(Ci.nsILoginMetaInfo);

      // Automatically update metainfo when password is changed.
      if (newLogin.password != aOldStoredLogin.password) {
        newLogin.timePasswordChanged = Date.now();
      }
    } else if (aNewLoginData instanceof Ci.nsIPropertyBag) {
      // Clone the existing login, along with all its properties.
      newLogin = aOldStoredLogin.clone();
      newLogin.QueryInterface(Ci.nsILoginMetaInfo);

      // Automatically update metainfo when password is changed.
      // (Done before the main property updates, lest the caller be
      // explicitly updating both .password and .timePasswordChanged)
      if (bagHasProperty("password")) {
        let newPassword = aNewLoginData.getProperty("password");
        if (newPassword != aOldStoredLogin.password) {
          newLogin.timePasswordChanged = Date.now();
        }
      }

      let propEnum = aNewLoginData.enumerator;
      while (propEnum.hasMoreElements()) {
        let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
        switch (prop.name) {
          // nsILoginInfo
          case "hostname":
          case "httpRealm":
          case "formSubmitURL":
          case "username":
          case "password":
          case "usernameField":
          case "passwordField":
          // nsILoginMetaInfo
          case "guid":
          case "timeCreated":
          case "timeLastUsed":
          case "timePasswordChanged":
          case "timesUsed":
            newLogin[prop.name] = prop.value;
            break;

          // Fake property, allows easy incrementing.
          case "timesUsedIncrement":
            newLogin.timesUsed += prop.value;
            break;

          // Fail if caller requests setting an unknown property.
          default:
            throw new Error("Unexpected propertybag item: " + prop.name);
        }
      }
    } else {
      throw new Error("newLoginData needs an expected interface!");
    }

    // Sanity check the login
    if (newLogin.hostname == null || newLogin.hostname.length == 0) {
      throw new Error("Can't add a login with a null or empty hostname.");
    }

    // For logins w/o a username, set to "", not null.
    if (newLogin.username == null) {
      throw new Error("Can't add a login with a null username.");
    }

    if (newLogin.password == null || newLogin.password.length == 0) {
      throw new Error("Can't add a login with a null or empty password.");
    }

    if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
      // We have a form submit URL. Can't have a HTTP realm.
      if (newLogin.httpRealm != null) {
        throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
      }
    } else if (newLogin.httpRealm) {
      // We have a HTTP realm. Can't have a form submit URL.
      if (newLogin.formSubmitURL != null) {
        throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
      }
    } else {
      // Need one or the other!
      throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
    }

    // Throws if there are bogus values.
    this.checkLoginValues(newLogin);

    return newLogin;
  },

  /**
   * Removes duplicates from a list of logins while preserving the sort order.
   *
   * @param {nsILoginInfo[]} logins
   *        A list of logins we want to deduplicate.
   * @param {string[]} [uniqueKeys = ["username", "password"]]
   *        A list of login attributes to use as unique keys for the deduplication.
   * @param {string[]} [resolveBy = ["timeLastUsed"]]
   *        Ordered array of keyword strings used to decide which of the
   *        duplicates should be used. "scheme" would prefer the login that has
   *        a scheme matching `preferredOrigin`'s if there are two logins with
   *        the same `uniqueKeys`. The default preference to distinguish two
   *        logins is `timeLastUsed`. If there is no preference between two
   *        logins, the first one found wins.
   * @param {string} [preferredOrigin = undefined]
   *        String representing the origin to use for preferring one login over
   *        another when they are dupes. This is used with "scheme" for
   *        `resolveBy` so the scheme from this origin will be preferred.
   *
   * @returns {nsILoginInfo[]} list of unique logins.
   */
  dedupeLogins(logins, uniqueKeys = ["username", "password"],
               resolveBy = ["timeLastUsed"],
               preferredOrigin = undefined) {
    const KEY_DELIMITER = ":";

    if (!preferredOrigin && resolveBy.includes("scheme")) {
      throw new Error("dedupeLogins: `preferredOrigin` is required in order to " +
                      "prefer schemes which match it.");
    }

    let preferredOriginScheme;
    if (preferredOrigin) {
      try {
        preferredOriginScheme = Services.io.newURI(preferredOrigin).scheme;
      } catch (ex) {
        // Handle strings that aren't valid URIs e.g. chrome://FirefoxAccounts
      }
    }

    if (!preferredOriginScheme && resolveBy.includes("scheme")) {
      log.warn("dedupeLogins: Deduping with a scheme preference but couldn't " +
               "get the preferred origin scheme.");
    }

    // We use a Map to easily lookup logins by their unique keys.
    let loginsByKeys = new Map();

    // Generate a unique key string from a login.
    function getKey(login, uniqueKeys) {
      return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
    }

    /**
     * @return {bool} whether `login` is preferred over its duplicate (considering `uniqueKeys`)
     *                `existingLogin`.
     *
     * `resolveBy` is a sorted array so we can return true the first time `login` is preferred
     * over the existingLogin.
     */
    function isLoginPreferred(existingLogin, login) {
      if (!resolveBy || resolveBy.length == 0) {
        // If there is no preference, prefer the existing login.
        return false;
      }

      for (let preference of resolveBy) {
        switch (preference) {
          case "scheme": {
            if (!preferredOriginScheme) {
              break;
            }

            try {
              // Only `hostname` is currently considered
              let existingLoginURI = Services.io.newURI(existingLogin.hostname);
              let loginURI = Services.io.newURI(login.hostname);
              // If the schemes of the two logins are the same or neither match the
              // preferredOriginScheme then we have no preference and look at the next resolveBy.
              if (loginURI.scheme == existingLoginURI.scheme ||
                  (loginURI.scheme != preferredOriginScheme &&
                   existingLoginURI.scheme != preferredOriginScheme)) {
                break;
              }

              return loginURI.scheme == preferredOriginScheme;
            } catch (ex) {
              // Some URLs aren't valid nsIURI (e.g. chrome://FirefoxAccounts)
              log.debug("dedupeLogins/shouldReplaceExisting: Error comparing schemes:",
                        existingLogin.hostname, login.hostname,
                        "preferredOrigin:", preferredOrigin, ex);
            }
            break;
          }
          case "timeLastUsed":
          case "timePasswordChanged": {
            // If we find a more recent login for the same key, replace the existing one.
            let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo)[preference];
            let storedLoginDate = existingLogin.QueryInterface(Ci.nsILoginMetaInfo)[preference];
            if (loginDate == storedLoginDate) {
              break;
            }

            return loginDate > storedLoginDate;
          }
          default: {
            throw new Error("dedupeLogins: Invalid resolveBy preference: " + preference);
          }
        }
      }

      return false;
    }

    for (let login of logins) {
      let key = getKey(login, uniqueKeys);

      if (loginsByKeys.has(key)) {
        if (!isLoginPreferred(loginsByKeys.get(key), login)) {
          // If there is no preference for the new login, use the existing one.
          continue;
        }
      }
      loginsByKeys.set(key, login);
    }

    // Return the map values in the form of an array.
    return [...loginsByKeys.values()];
  },

  /**
   * Open the password manager window.
   *
   * @param {Window} window
   *                 the window from where we want to open the dialog
   *
   * @param {string} [filterString=""]
   *                 the filterString parameter to pass to the login manager dialog
   */
  openPasswordManager(window, filterString = "") {
    let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
    if (win) {
      win.setFilter(filterString);
      win.focus();
    } else {
      window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
                        "Toolkit:PasswordManager", "",
                        {filterString});
    }
  },

  /**
   * Checks if a field type is username compatible.
   *
   * @param {Element} element
   *                  the field we want to check.
   *
   * @returns {Boolean} true if the field type is one
   *                    of the username types.
   */
  isUsernameFieldType(element) {
    if (!(element instanceof Ci.nsIDOMHTMLInputElement))
      return false;

    let fieldType = (element.hasAttribute("type") ?
                     element.getAttribute("type").toLowerCase() :
                     element.type);
    if (fieldType == "text" ||
        fieldType == "email" ||
        fieldType == "url" ||
        fieldType == "tel" ||
        fieldType == "number") {
      return true;
    }
    return false;
  },

  /**
   * Add the login to the password manager if a similar one doesn't already exist. Merge it
   * otherwise with the similar existing ones.
   * @param {Object} loginData - the data about the login that needs to be added.
   * @returns {nsILoginInfo} the newly added login, or null if no login was added.
   *                          Note that we will also return null if an existing login
   *                          was modified.
   */
  maybeImportLogin(loginData) {
    // create a new login
    let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
    login.init(loginData.hostname,
               loginData.formSubmitURL || (typeof(loginData.httpRealm) == "string" ? null : ""),
               typeof(loginData.httpRealm) == "string" ? loginData.httpRealm : null,
               loginData.username,
               loginData.password,
               loginData.usernameElement || "",
               loginData.passwordElement || "");

    login.QueryInterface(Ci.nsILoginMetaInfo);
    login.timeCreated = loginData.timeCreated;
    login.timeLastUsed = loginData.timeLastUsed || loginData.timeCreated;
    login.timePasswordChanged = loginData.timePasswordChanged || loginData.timeCreated;
    login.timesUsed = loginData.timesUsed || 1;
    // While here we're passing formSubmitURL and httpRealm, they could be empty/null and get
    // ignored in that case, leading to multiple logins for the same username.
    let existingLogins = Services.logins.findLogins({}, login.hostname,
                                                    login.formSubmitURL,
                                                    login.httpRealm);
    // Check for an existing login that matches *including* the password.
    // If such a login exists, we do not need to add a new login.
    if (existingLogins.some(l => login.matches(l, false /* ignorePassword */))) {
      return null;
    }
    // Now check for a login with the same username, where it may be that we have an
    // updated password.
    let foundMatchingLogin = false;
    for (let existingLogin of existingLogins) {
      if (login.username == existingLogin.username) {
        foundMatchingLogin = true;
        existingLogin.QueryInterface(Ci.nsILoginMetaInfo);
        if (login.password != existingLogin.password &
           login.timePasswordChanged > existingLogin.timePasswordChanged) {
          // if a login with the same username and different password already exists and it's older
          // than the current one, update its password and timestamp.
          let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                        createInstance(Ci.nsIWritablePropertyBag);
          propBag.setProperty("password", login.password);
          propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
          Services.logins.modifyLogin(existingLogin, propBag);
        }
      }
    }
    // if the new login is an update or is older than an exiting login, don't add it.
    if (foundMatchingLogin) {
      return null;
    }
    return Services.logins.addLogin(login);
  },

  /**
   * Convert an array of nsILoginInfo to vanilla JS objects suitable for
   * sending over IPC.
   *
   * NB: All members of nsILoginInfo and nsILoginMetaInfo are strings.
   */
  loginsToVanillaObjects(logins) {
    return logins.map(this.loginToVanillaObject);
  },

  /**
   * Same as above, but for a single login.
   */
  loginToVanillaObject(login) {
    let obj = {};
    for (let i in login.QueryInterface(Ci.nsILoginMetaInfo)) {
      if (typeof login[i] !== "function") {
        obj[i] = login[i];
      }
    }

    return obj;
  },

  /**
   * Convert an object received from IPC into an nsILoginInfo (with guid).
   */
  vanillaObjectToLogin(login) {
    let formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                    createInstance(Ci.nsILoginInfo);
    formLogin.init(login.hostname, login.formSubmitURL,
                   login.httpRealm, login.username,
                   login.password, login.usernameField,
                   login.passwordField);

    formLogin.QueryInterface(Ci.nsILoginMetaInfo);
    for (let prop of ["guid", "timeCreated", "timeLastUsed", "timePasswordChanged", "timesUsed"]) {
      formLogin[prop] = login[prop];
    }
    return formLogin;
  },

  /**
   * As above, but for an array of objects.
   */
  vanillaObjectsToLogins(logins) {
    return logins.map(this.vanillaObjectToLogin);
  },

  removeLegacySignonFiles() {
    const {Constants, Path, File} = Cu.import("resource://gre/modules/osfile.jsm").OS;

    const profileDir = Constants.Path.profileDir;
    const defaultSignonFilePrefs = new Map([
      ["signon.SignonFileName", "signons.txt"],
      ["signon.SignonFileName2", "signons2.txt"],
      ["signon.SignonFileName3", "signons3.txt"]
    ]);
    const toDeletes = new Set();

    for (let [pref, val] of defaultSignonFilePrefs.entries()) {
      toDeletes.add(Path.join(profileDir, val));

      try {
        let signonFile = Services.prefs.getCharPref(pref);

        toDeletes.add(Path.join(profileDir, signonFile));
        Services.prefs.clearUserPref(pref);
      } catch (e) {}
    }

    for (let file of toDeletes) {
      File.remove(file);
    }
  },

  /**
   * Returns true if the user has a master password set and false otherwise.
   */
  isMasterPasswordSet() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                    .getService(Ci.nsIPK11TokenDB);
    let token = tokenDB.getInternalKeyToken();
    return token.hasPassword;
  },

  /**
   * Send a notification when stored data is changed.
   */
  notifyStorageChanged(changeType, data) {
    let dataObject = data;
    // Can't pass a raw JS string or array though notifyObservers(). :-(
    if (Array.isArray(data)) {
      dataObject = Cc["@mozilla.org/array;1"].
                   createInstance(Ci.nsIMutableArray);
      for (let i = 0; i < data.length; i++) {
        dataObject.appendElement(data[i]);
      }
    } else if (typeof(data) == "string") {
      dataObject = Cc["@mozilla.org/supports-string;1"].
                   createInstance(Ci.nsISupportsString);
      dataObject.data = data;
    }
    Services.obs.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
  }
};

XPCOMUtils.defineLazyGetter(this, "log", () => {
  let logger = LoginHelper.createLogger("LoginHelper");
  return logger;
});
PK
!<2gCmodules/LoginImport.jsm/* -*- 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/. */

/**
 * Provides an object that has a method to import login-related data from the
 * previous SQLite storage format.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "LoginImport",
];

// Globals

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");

// LoginImport

/**
 * Provides an object that has a method to import login-related data from the
 * previous SQLite storage format.
 *
 * @param aStore
 *        LoginStore object where imported data will be added.
 * @param aPath
 *        String containing the file path of the SQLite login database.
 */
this.LoginImport = function(aStore, aPath) {
  this.store = aStore;
  this.path = aPath;
};

this.LoginImport.prototype = {
  /**
   * LoginStore object where imported data will be added.
   */
  store: null,

  /**
   * String containing the file path of the SQLite login database.
   */
  path: null,

  /**
   * Imports login-related data from the previous SQLite storage format.
   */
  async import() {
    // We currently migrate data directly from the database to the JSON store at
    // first run, then we set a preference to prevent repeating the import.
    // Thus, merging with existing data is not a use case we support.  This
    // restriction might be removed to support re-importing passwords set by an
    // old version by flipping the import preference and restarting.
    if (this.store.data.logins.length > 0 ||
        this.store.data.disabledHosts.length > 0) {
      throw new Error("Unable to import saved passwords because some data " +
                      "has already been imported or saved.");
    }

    // When a timestamp is not specified, we will use the same reference time.
    let referenceTimeMs = Date.now();

    let connection = await Sqlite.openConnection({ path: this.path });
    try {
      let schemaVersion = await connection.getSchemaVersion();

      // We support importing database schema versions from 3 onwards.
      // Version 3 was implemented in bug 316084 (Firefox 3.6, March 2009).
      // Version 4 was implemented in bug 465636 (Firefox 4, March 2010).
      // Version 5 was implemented in bug 718817 (Firefox 13, February 2012).
      if (schemaVersion < 3) {
        throw new Error("Unable to import saved passwords because " +
                        "the existing profile is too old.");
      }

      let rows = await connection.execute("SELECT * FROM moz_logins");
      for (let row of rows) {
        try {
          let hostname = row.getResultByName("hostname");
          let httpRealm = row.getResultByName("httpRealm");
          let formSubmitURL = row.getResultByName("formSubmitURL");
          let usernameField = row.getResultByName("usernameField");
          let passwordField = row.getResultByName("passwordField");
          let encryptedUsername = row.getResultByName("encryptedUsername");
          let encryptedPassword = row.getResultByName("encryptedPassword");

          // The "guid" field was introduced in schema version 2, and the
          // "enctype" field was introduced in schema version 3.  We don't
          // support upgrading from older versions of the database.
          let guid = row.getResultByName("guid");
          let encType = row.getResultByName("encType");

          // The time and count fields were introduced in schema version 4.
          let timeCreated = null;
          let timeLastUsed = null;
          let timePasswordChanged = null;
          let timesUsed = null;
          try {
            timeCreated = row.getResultByName("timeCreated");
            timeLastUsed = row.getResultByName("timeLastUsed");
            timePasswordChanged = row.getResultByName("timePasswordChanged");
            timesUsed = row.getResultByName("timesUsed");
          } catch (ex) { }

          // These columns may be null either because they were not present in
          // the database or because the record was created on a new schema
          // version by an old application version.
          if (!timeCreated) {
            timeCreated = referenceTimeMs;
          }
          if (!timeLastUsed) {
            timeLastUsed = referenceTimeMs;
          }
          if (!timePasswordChanged) {
            timePasswordChanged = referenceTimeMs;
          }
          if (!timesUsed) {
            timesUsed = 1;
          }

          this.store.data.logins.push({
            id: this.store.data.nextId++,
            hostname,
            httpRealm,
            formSubmitURL,
            usernameField,
            passwordField,
            encryptedUsername,
            encryptedPassword,
            guid,
            encType,
            timeCreated,
            timeLastUsed,
            timePasswordChanged,
            timesUsed,
          });
        } catch (ex) {
          Cu.reportError("Error importing login: " + ex);
        }
      }

      rows = await connection.execute("SELECT * FROM moz_disabledHosts");
      for (let row of rows) {
        try {
          let hostname = row.getResultByName("hostname");

          this.store.data.disabledHosts.push(hostname);
        } catch (ex) {
          Cu.reportError("Error importing disabled host: " + ex);
        }
      }
    } finally {
      await connection.close();
    }
  },
};
PK
!<modules/LoginManagerContent.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
                          "LoginFormFactory",
                          "UserAutoCompleteResult" ];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                  "resource://gre/modules/FormLikeFactory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
                                  "resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
                                  "resource://gre/modules/InsecurePasswordUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
                                   "@mozilla.org/network/util;1",
                                   "nsINetUtil");

XPCOMUtils.defineLazyGetter(this, "log", () => {
  let logger = LoginHelper.createLogger("LoginManagerContent");
  return logger.log.bind(logger);
});

// These mirror signon.* prefs.
var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
var gLastRightClickTimeStamp = Number.NEGATIVE_INFINITY;

var observer = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIFormSubmitObserver,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsIDOMEventListener,
                                          Ci.nsISupportsWeakReference]),

  // nsIFormSubmitObserver
  notify(formElement, aWindow, actionURI) {
    log("observer notified for form submission.");

    // We're invoked before the content's |onsubmit| handlers, so we
    // can grab form data before it might be modified (see bug 257781).

    try {
      let formLike = LoginFormFactory.createFromForm(formElement);
      LoginManagerContent._onFormSubmit(formLike);
    } catch (e) {
      log("Caught error in onFormSubmit(", e.lineNumber, "):", e.message);
      Cu.reportError(e);
    }

    return true; // Always return true, or form submit will be canceled.
  },

  onPrefChange() {
    gEnabled = Services.prefs.getBoolPref("signon.rememberSignons");
    gAutofillForms = Services.prefs.getBoolPref("signon.autofillForms");
    gStoreWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
  },

  // nsIWebProgressListener
  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    // Only handle pushState/replaceState here.
    if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
        !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
      return;
    }

    log("onLocationChange handled:", aLocation.spec, aWebProgress.DOMWindow.document);

    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
  },

  onStateChange(aWebProgress, aRequest, aState, aStatus) {
    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
      return;
    }

    // We only care about when a page triggered a load, not the user. For example:
    // clicking refresh/back/forward, typing a URL and hitting enter, and loading a bookmark aren't
    // likely to be when a user wants to save a login.
    let channel = aRequest.QueryInterface(Ci.nsIChannel);
    let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
    if (triggeringPrincipal.isNullPrincipal ||
        triggeringPrincipal.equals(Services.scriptSecurityManager.getSystemPrincipal())) {
      return;
    }

    // Don't handle history navigation, reload, or pushState not triggered via chrome UI.
    // e.g. history.go(-1), location.reload(), history.replaceState()
    if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
      log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
      return;
    }

    log("onStateChange handled:", channel);
    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
  },

  handleEvent(aEvent) {
    if (!aEvent.isTrusted) {
      return;
    }

    if (!gEnabled) {
      return;
    }

    switch (aEvent.type) {
      // Only used for username fields.
      case "focus": {
        LoginManagerContent._onUsernameFocus(aEvent);
        break;
      }

      case "mousedown": {
        if (aEvent.button == 2) {
          // Date.now() is used instead of event.timeStamp since
          // dom.event.highrestimestamp.enabled isn't true on all channels yet.
          gLastRightClickTimeStamp = Date.now();
        }

        break;
      }

      default: {
        throw new Error("Unexpected event");
      }
    }
  },
};

Services.obs.addObserver(observer, "earlyformsubmit");
var prefBranch = Services.prefs.getBranch("signon.");
prefBranch.addObserver("", observer.onPrefChange);

observer.onPrefChange(); // read initial values


function messageManagerFromWindow(win) {
  return win.QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIWebNavigation)
            .QueryInterface(Ci.nsIDocShell)
            .QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIContentFrameMessageManager);
}

// This object maps to the "child" process (even in the single-process case).
var LoginManagerContent = {

  __formFillService: null, // FormFillController, for username autocompleting
  get _formFillService() {
    if (!this.__formFillService)
      this.__formFillService =
                      Cc["@mozilla.org/satchel/form-fill-controller;1"].
                      getService(Ci.nsIFormFillController);
    return this.__formFillService;
  },

  _getRandomId() {
    return Cc["@mozilla.org/uuid-generator;1"]
             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
  },

  _messages: [ "RemoteLogins:loginsFound",
               "RemoteLogins:loginsAutoCompleted" ],

  /**
   * WeakMap of the root element of a FormLike to the FormLike representing its fields.
   *
   * This is used to be able to lookup an existing FormLike for a given root element since multiple
   * calls to LoginFormFactory won't give the exact same object. When batching fills we don't always
   * want to use the most recent list of elements for a FormLike since we may end up doing multiple
   * fills for the same set of elements when a field gets added between arming and running the
   * DeferredTask.
   *
   * @type {WeakMap}
   */
  _formLikeByRootElement: new WeakMap(),

  /**
   * WeakMap of the root element of a WeakMap to the DeferredTask to fill its fields.
   *
   * This is used to be able to throttle fills for a FormLike since onDOMInputPasswordAdded gets
   * dispatched for each password field added to a document but we only want to fill once per
   * FormLike when multiple fields are added at once.
   *
   * @type {WeakMap}
   */
  _deferredPasswordAddedTasksByRootElement: new WeakMap(),

  // Map from form login requests to information about that request.
  _requests: new Map(),

  // Number of outstanding requests to each manager.
  _managers: new Map(),

  _takeRequest(msg) {
    let data = msg.data;
    let request = this._requests.get(data.requestId);

    this._requests.delete(data.requestId);

    let count = this._managers.get(msg.target);
    if (--count === 0) {
      this._managers.delete(msg.target);

      for (let message of this._messages)
        msg.target.removeMessageListener(message, this);
    } else {
      this._managers.set(msg.target, count);
    }

    return request;
  },

  _sendRequest(messageManager, requestData,
                         name, messageData) {
    let count;
    if (!(count = this._managers.get(messageManager))) {
      this._managers.set(messageManager, 1);

      for (let message of this._messages)
        messageManager.addMessageListener(message, this);
    } else {
      this._managers.set(messageManager, ++count);
    }

    let requestId = this._getRandomId();
    messageData.requestId = requestId;

    messageManager.sendAsyncMessage(name, messageData);

    let deferred = PromiseUtils.defer();
    requestData.promise = deferred;
    this._requests.set(requestId, requestData);
    return deferred.promise;
  },

  receiveMessage(msg, window) {
    if (msg.name == "RemoteLogins:fillForm") {
      this.fillForm({
        topDocument: window.document,
        loginFormOrigin: msg.data.loginFormOrigin,
        loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
        recipes: msg.data.recipes,
        inputElement: msg.objects.inputElement,
      });
      return;
    }

    let request = this._takeRequest(msg);
    switch (msg.name) {
      case "RemoteLogins:loginsFound": {
        let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
        request.promise.resolve({
          form: request.form,
          loginsFound,
          recipes: msg.data.recipes,
        });
        break;
      }

      case "RemoteLogins:loginsAutoCompleted": {
        let loginsFound =
          LoginHelper.vanillaObjectsToLogins(msg.data.logins);
        let messageManager = msg.target;
        request.promise.resolve({ logins: loginsFound, messageManager });
        break;
      }
    }
  },

  /**
   * Get relevant logins and recipes from the parent
   *
   * @param {HTMLFormElement} form - form to get login data for
   * @param {Object} options
   * @param {boolean} options.showMasterPassword - whether to show a master password prompt
   */
  _getLoginDataFromParent(form, options) {
    let doc = form.ownerDocument;
    let win = doc.defaultView;

    let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
    if (!formOrigin) {
      return Promise.reject("_getLoginDataFromParent: A form origin is required");
    }
    let actionOrigin = LoginUtils._getActionOrigin(form);

    let messageManager = messageManagerFromWindow(win);

    // XXX Weak??
    let requestData = { form };
    let messageData = { formOrigin,
                        actionOrigin,
                        options };

    return this._sendRequest(messageManager, requestData,
                             "RemoteLogins:findLogins",
                             messageData);
  },

  _autoCompleteSearchAsync(aSearchString, aPreviousResult,
                                     aElement, aRect) {
    let doc = aElement.ownerDocument;
    let form = LoginFormFactory.createFromField(aElement);
    let win = doc.defaultView;

    let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
    let actionOrigin = LoginUtils._getActionOrigin(form);

    let messageManager = messageManagerFromWindow(win);

    let previousResult = aPreviousResult ?
                           { searchString: aPreviousResult.searchString,
                             logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
                           null;

    let requestData = {};
    let messageData = { formOrigin,
                        actionOrigin,
                        searchString: aSearchString,
                        previousResult,
                        rect: aRect,
                        isSecure: InsecurePasswordUtils.isFormSecure(form),
                        isPasswordField: aElement.type == "password",
                      };

    return this._sendRequest(messageManager, requestData,
                             "RemoteLogins:autoCompleteLogins",
                             messageData);
  },

  setupProgressListener(window) {
    if (!LoginHelper.formlessCaptureEnabled) {
      return;
    }

    try {
      let webProgress = window.QueryInterface(Ci.nsIInterfaceRequestor).
                        getInterface(Ci.nsIWebNavigation).
                        QueryInterface(Ci.nsIDocShell).
                        QueryInterface(Ci.nsIInterfaceRequestor).
                        getInterface(Ci.nsIWebProgress);
      webProgress.addProgressListener(observer,
                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
                                      Ci.nsIWebProgress.NOTIFY_LOCATION);
    } catch (ex) {
      // Ignore NS_ERROR_FAILURE if the progress listener was already added
    }
  },

  onDOMFormHasPassword(event, window) {
    if (!event.isTrusted) {
      return;
    }

    let form = event.target;
    let formLike = LoginFormFactory.createFromForm(form);
    log("onDOMFormHasPassword:", form, formLike);
    this._fetchLoginsFromParentAndFillForm(formLike, window);
  },

  onDOMInputPasswordAdded(event, window) {
    if (!event.isTrusted) {
      return;
    }

    let pwField = event.target;
    if (pwField.form) {
      // Fill is handled by onDOMFormHasPassword which is already throttled.
      return;
    }

    // Only setup the listener for formless inputs.
    // Capture within a <form> but without a submit event is bug 1287202.
    this.setupProgressListener(window);

    let formLike = LoginFormFactory.createFromField(pwField);
    log("onDOMInputPasswordAdded:", pwField, formLike);

    let deferredTask = this._deferredPasswordAddedTasksByRootElement.get(formLike.rootElement);
    if (!deferredTask) {
      log("Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon");
      this._formLikeByRootElement.set(formLike.rootElement, formLike);

      deferredTask = new DeferredTask(() => {
        // Get the updated formLike instead of the one at the time of creating the DeferredTask via
        // a closure since it could be stale since FormLike.elements isn't live.
        let formLike2 = this._formLikeByRootElement.get(formLike.rootElement);
        log("Running deferred processing of onDOMInputPasswordAdded", formLike2);
        this._deferredPasswordAddedTasksByRootElement.delete(formLike2.rootElement);
        this._fetchLoginsFromParentAndFillForm(formLike2, window);
      }, PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS);

      this._deferredPasswordAddedTasksByRootElement.set(formLike.rootElement, deferredTask);
    }

    if (deferredTask.isArmed) {
      log("DeferredTask is already armed so just updating the FormLike");
      // We update the FormLike so it (most important .elements) is fresh when the task eventually
      // runs since changes to the elements could affect our field heuristics.
      this._formLikeByRootElement.set(formLike.rootElement, formLike);
    } else if (window.document.readyState == "complete") {
      log("Arming the DeferredTask we just created since document.readyState == 'complete'");
      deferredTask.arm();
    } else {
      window.addEventListener("DOMContentLoaded", function() {
        log("Arming the onDOMInputPasswordAdded DeferredTask due to DOMContentLoaded");
        deferredTask.arm();
      }, {once: true});
    }
  },

  /**
   * Fetch logins from the parent for a given form and then attempt to fill it.
   *
   * @param {FormLike} form to fetch the logins for then try autofill.
   * @param {Window} window
   */
  _fetchLoginsFromParentAndFillForm(form, window) {
    this._detectInsecureFormLikes(window);

    let messageManager = messageManagerFromWindow(window);
    messageManager.sendAsyncMessage("LoginStats:LoginEncountered");

    if (!gEnabled) {
      return;
    }

    this._getLoginDataFromParent(form, { showMasterPassword: true })
        .then(this.loginsFound.bind(this))
        .catch(Cu.reportError);
  },

  onPageShow(event, window) {
    this._detectInsecureFormLikes(window);
  },

  /**
   * Maps all DOM content documents in this content process, including those in
   * frames, to the current state used by the Login Manager.
   */
  loginFormStateByDocument: new WeakMap(),

  /**
   * Retrieves a reference to the state object associated with the given
   * document. This is initialized to an object with default values.
   */
  stateForDocument(document) {
    let loginFormState = this.loginFormStateByDocument.get(document);
    if (!loginFormState) {
      loginFormState = {
        /**
         * Keeps track of filled fields and values.
         */
        fillsByRootElement: new WeakMap(),
        loginFormRootElements: new Set(),
      };
      this.loginFormStateByDocument.set(document, loginFormState);
    }
    return loginFormState;
  },

  /**
   * Compute whether there is an insecure login form on any frame of the current page, and
   * notify the parent process. This is used to control whether insecure password UI appears.
   */
  _detectInsecureFormLikes(topWindow) {
    log("_detectInsecureFormLikes", topWindow.location.href);

    // Returns true if this window or any subframes have insecure login forms.
    let hasInsecureLoginForms = (thisWindow) => {
      let doc = thisWindow.document;
      let hasLoginForm = this.stateForDocument(doc).loginFormRootElements.size > 0;
      // Ignores window.opener, because it's not relevant for indicating
      // form security. See InsecurePasswordUtils docs for details.
      return (hasLoginForm && !thisWindow.isSecureContextIfOpenerIgnored) ||
             Array.some(thisWindow.frames,
                        frame => hasInsecureLoginForms(frame));
    };

    let messageManager = messageManagerFromWindow(topWindow);
    messageManager.sendAsyncMessage("RemoteLogins:insecureLoginFormPresent", {
      hasInsecureLoginForms: hasInsecureLoginForms(topWindow),
    });
  },

  /**
   * Perform a password fill upon user request coming from the parent process.
   * The fill will be in the form previously identified during page navigation.
   *
   * @param An object with the following properties:
   *        {
   *          topDocument:
   *            DOM document currently associated to the the top-level window
   *            for which the fill is requested. This may be different from the
   *            document that originally caused the login UI to be displayed.
   *          loginFormOrigin:
   *            String with the origin for which the login UI was displayed.
   *            This must match the origin of the form used for the fill.
   *          loginsFound:
   *            Array containing the login to fill. While other messages may
   *            have more logins, for this use case this is expected to have
   *            exactly one element. The origin of the login may be different
   *            from the origin of the form used for the fill.
   *          recipes:
   *            Fill recipes transmitted together with the original message.
   *          inputElement:
   *            Username or password input element from the form we want to fill.
   *        }
   */
  fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
    if (!inputElement) {
      log("fillForm: No input element specified");
      return;
    }
    if (LoginUtils._getPasswordOrigin(topDocument.documentURI) != loginFormOrigin) {
      if (!inputElement ||
          LoginUtils._getPasswordOrigin(inputElement.ownerDocument.documentURI) != loginFormOrigin) {
        log("fillForm: The requested origin doesn't match the one form the",
            "document. This may mean we navigated to a document from a different",
            "site before we had a chance to indicate this change in the user",
            "interface.");
        return;
      }
    }

    let clobberUsername = true;
    let form = LoginFormFactory.createFromField(inputElement);
    if (inputElement.type == "password") {
      clobberUsername = false;
    }

    this._fillForm(form, loginsFound, recipes, {
      inputElement,
      autofillForm: true,
      clobberUsername,
      clobberPassword: true,
      userTriggered: true,
    });
  },

  loginsFound({ form, loginsFound, recipes }) {
    let doc = form.ownerDocument;
    let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);

    this._fillForm(form, loginsFound, recipes, {autofillForm});
  },

  /**
   * Focus event handler for username fields to decide whether to show autocomplete.
   * @param {FocusEvent} event
   */
  _onUsernameFocus(event) {
    let focusedField = event.target;
    if (!focusedField.mozIsTextField(true) || focusedField.readOnly) {
      return;
    }

    if (this._isLoginAlreadyFilled(focusedField)) {
      log("_onUsernameFocus: Already filled");
      return;
    }

    /*
     * A `mousedown` event is fired before the `focus` event if the user right clicks into an
     * unfocused field. In that case we don't want to show both autocomplete and a context menu
     * overlapping so we check against the timestamp that was set by the `mousedown` event if the
     * button code indicated a right click.
     * We use a timestamp instead of a bool to avoid complexity when dealing with multiple input
     * forms and the fact that a mousedown into an already focused field does not trigger another focus.
     * Date.now() is used instead of event.timeStamp since dom.event.highrestimestamp.enabled isn't
     * true on all channels yet.
     */
    let timeDiff = Date.now() - gLastRightClickTimeStamp;
    if (timeDiff < AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS) {
      log("Not opening autocomplete after focus since a context menu was opened within",
          timeDiff, "ms");
      return;
    }

    log("maybeOpenAutocompleteAfterFocus: Opening the autocomplete popup");
    this._formFillService.showPopup();
  },

  /**
   * Listens for DOMAutoComplete and blur events on an input field.
   */
  onUsernameInput(event) {
    if (!event.isTrusted)
      return;

    if (!gEnabled)
      return;

    var acInputField = event.target;

    // This is probably a bit over-conservatative.
    if (!(acInputField.ownerDocument instanceof Ci.nsIDOMHTMLDocument))
      return;

    if (!LoginHelper.isUsernameFieldType(acInputField))
      return;

    var acForm = LoginFormFactory.createFromField(acInputField);
    if (!acForm)
      return;

    // If the username is blank, bail out now -- we don't want
    // fillForm() to try filling in a login without a username
    // to filter on (bug 471906).
    if (!acInputField.value)
      return;

    log("onUsernameInput from", event.type);

    let doc = acForm.ownerDocument;
    let messageManager = messageManagerFromWindow(doc.defaultView);
    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
      formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
    })[0];

    // Make sure the username field fillForm will use is the
    // same field as the autocomplete was activated on.
    var [usernameField, passwordField, ignored] =
        this._getFormFields(acForm, false, recipes);
    if (usernameField == acInputField && passwordField) {
      this._getLoginDataFromParent(acForm, { showMasterPassword: false })
          .then(({ form, loginsFound, recipes }) => {
            this._fillForm(form, loginsFound, recipes, {
              autofillForm: true,
              clobberPassword: true,
              userTriggered: true,
            });
          })
          .catch(Cu.reportError);
    } else {
      // Ignore the event, it's for some input we don't care about.
    }
  },

  /**
   * @param {FormLike} form - the FormLike to look for password fields in.
   * @param {Object} options
   * @param {bool} [options.skipEmptyFields=false] - Whether to ignore password fields with no value.
   *                                                 Used at capture time since saving empty values isn't
   *                                                 useful.
   * @param {Object} [options.fieldOverrideRecipe=null] - A relevant field override recipe to use.
   * @return {Array|null} Array of password field elements for the specified form.
   *                      If no pw fields are found, or if more than 3 are found, then null
   *                      is returned.
   */
  _getPasswordFields(form, {
    fieldOverrideRecipe = null,
    skipEmptyFields = false,
  } = {}) {
    // Locate the password fields in the form.
    let pwFields = [];
    for (let i = 0; i < form.elements.length; i++) {
      let element = form.elements[i];
      if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
          element.type != "password") {
        continue;
      }

      // Exclude ones matching a `notPasswordSelector`, if specified.
      if (fieldOverrideRecipe && fieldOverrideRecipe.notPasswordSelector &&
          element.matches(fieldOverrideRecipe.notPasswordSelector)) {
        log("skipping password field (id/name is", element.id, " / ",
            element.name + ") due to recipe:", fieldOverrideRecipe);
        continue;
      }

      if (skipEmptyFields && !element.value.trim()) {
        continue;
      }

      pwFields[pwFields.length] = {
                                    index: i,
                                    element
                                  };
    }

    // If too few or too many fields, bail out.
    if (pwFields.length == 0) {
      log("(form ignored -- no password fields.)");
      return null;
    } else if (pwFields.length > 3) {
      log("(form ignored -- too many password fields. [ got ", pwFields.length, "])");
      return null;
    }

    return pwFields;
  },

  /**
   * Returns the username and password fields found in the form.
   * Can handle complex forms by trying to figure out what the
   * relevant fields are.
   *
   * @param {FormLike} form
   * @param {bool} isSubmission
   * @param {Set} recipes
   * @return {Array} [usernameField, newPasswordField, oldPasswordField]
   *
   * usernameField may be null.
   * newPasswordField will always be non-null.
   * oldPasswordField may be null. If null, newPasswordField is just
   * "theLoginField". If not null, the form is apparently a
   * change-password field, with oldPasswordField containing the password
   * that is being changed.
   *
   * Note that even though we can create a FormLike from a text field,
   * this method will only return a non-null usernameField if the
   * FormLike has a password field.
   */
  _getFormFields(form, isSubmission, recipes) {
    var usernameField = null;
    var pwFields = null;
    var fieldOverrideRecipe = LoginRecipesContent.getFieldOverrides(recipes, form);
    if (fieldOverrideRecipe) {
      var pwOverrideField = LoginRecipesContent.queryLoginField(
        form,
        fieldOverrideRecipe.passwordSelector
      );
      if (pwOverrideField) {
        // The field from the password override may be in a different FormLike.
        let formLike = LoginFormFactory.createFromField(pwOverrideField);
        pwFields = [{
          index: [...formLike.elements].indexOf(pwOverrideField),
          element: pwOverrideField,
        }];
      }

      var usernameOverrideField = LoginRecipesContent.queryLoginField(
        form,
        fieldOverrideRecipe.usernameSelector
      );
      if (usernameOverrideField) {
        usernameField = usernameOverrideField;
      }
    }

    if (!pwFields) {
      // Locate the password field(s) in the form. Up to 3 supported.
      // If there's no password field, there's nothing for us to do.
      pwFields = this._getPasswordFields(form, {
        fieldOverrideRecipe,
        skipEmptyFields: isSubmission,
      });
    }

    if (!pwFields) {
      return [null, null, null];
    }

    if (!usernameField) {
      // Locate the username field in the form by searching backwards
      // from the first password field, assume the first text field is the
      // username. We might not find a username field if the user is
      // already logged in to the site.
      for (var i = pwFields[0].index - 1; i >= 0; i--) {
        var element = form.elements[i];
        if (!LoginHelper.isUsernameFieldType(element)) {
          continue;
        }

        if (fieldOverrideRecipe && fieldOverrideRecipe.notUsernameSelector &&
            element.matches(fieldOverrideRecipe.notUsernameSelector)) {
          continue;
        }

        usernameField = element;
        break;
      }
    }

    if (!usernameField)
      log("(form -- no username field found)");
    else
      log("Username field ", usernameField, "has name/value:",
          usernameField.name, "/", usernameField.value);

    // If we're not submitting a form (it's a page load), there are no
    // password field values for us to use for identifying fields. So,
    // just assume the first password field is the one to be filled in.
    if (!isSubmission || pwFields.length == 1) {
      var passwordField = pwFields[0].element;
      log("Password field", passwordField, "has name: ", passwordField.name);
      return [usernameField, passwordField, null];
    }


    // Try to figure out WTF is in the form based on the password values.
    var oldPasswordField, newPasswordField;
    var pw1 = pwFields[0].element.value;
    var pw2 = pwFields[1].element.value;
    var pw3 = (pwFields[2] ? pwFields[2].element.value : null);

    if (pwFields.length == 3) {
      // Look for two identical passwords, that's the new password

      if (pw1 == pw2 && pw2 == pw3) {
        // All 3 passwords the same? Weird! Treat as if 1 pw field.
        newPasswordField = pwFields[0].element;
        oldPasswordField = null;
      } else if (pw1 == pw2) {
        newPasswordField = pwFields[0].element;
        oldPasswordField = pwFields[2].element;
      } else if (pw2 == pw3) {
        oldPasswordField = pwFields[0].element;
        newPasswordField = pwFields[2].element;
      } else if (pw1 == pw3) {
        // A bit odd, but could make sense with the right page layout.
        newPasswordField = pwFields[0].element;
        oldPasswordField = pwFields[1].element;
      } else {
        // We can't tell which of the 3 passwords should be saved.
        log("(form ignored -- all 3 pw fields differ)");
        return [null, null, null];
      }
    } else if (pw1 == pw2) {
      // pwFields.length == 2
      // Treat as if 1 pw field
      newPasswordField = pwFields[0].element;
      oldPasswordField = null;
    } else {
      // Just assume that the 2nd password is the new password
      oldPasswordField = pwFields[0].element;
      newPasswordField = pwFields[1].element;
    }

    log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
    if (oldPasswordField) {
      log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
    } else {
      log("Password field (old):", oldPasswordField);
    }
    return [usernameField, newPasswordField, oldPasswordField];
  },


  /**
   * @return true if the page requests autocomplete be disabled for the
   *              specified element.
   */
  _isAutocompleteDisabled(element) {
    return element && element.autocomplete == "off";
  },

  /**
   * Trigger capture on any relevant FormLikes due to a navigation alone (not
   * necessarily due to an actual form submission). This method is used to
   * capture logins for cases where form submit events are not used.
   *
   * To avoid multiple notifications for the same FormLike, this currently
   * avoids capturing when dealing with a real <form> which are ideally already
   * using a submit event.
   *
   * @param {Document} document being navigated
   */
  _onNavigation(aDocument) {
    let state = this.stateForDocument(aDocument);
    let loginFormRootElements = state.loginFormRootElements;
    log("_onNavigation: state:", state, "loginFormRootElements size:", loginFormRootElements.size,
        "document:", aDocument);

    for (let formRoot of state.loginFormRootElements) {
      if (formRoot instanceof Ci.nsIDOMHTMLFormElement) {
        // For now only perform capture upon navigation for FormLike's without
        // a <form> to avoid capture from both an earlyformsubmit and
        // navigation for the same "form".
        log("Ignoring navigation for the form root to avoid multiple prompts " +
            "since it was for a real <form>");
        continue;
      }
      let formLike = this._formLikeByRootElement.get(formRoot);
      this._onFormSubmit(formLike);
    }
  },

  /**
   * Called by our observer when notified of a form submission.
   * [Note that this happens before any DOM onsubmit handlers are invoked.]
   * Looks for a password change in the submitted form, so we can update
   * our stored password.
   *
   * @param {FormLike} form
   */
  _onFormSubmit(form) {
    log("_onFormSubmit", form);
    var doc = form.ownerDocument;
    var win = doc.defaultView;

    if (PrivateBrowsingUtils.isContentWindowPrivate(win)) {
      // We won't do anything in private browsing mode anyway,
      // so there's no need to perform further checks.
      log("(form submission ignored in private browsing mode)");
      return;
    }

    // If password saving is disabled (globally or for host), bail out now.
    if (!gEnabled)
      return;

    var hostname = LoginUtils._getPasswordOrigin(doc.documentURI);
    if (!hostname) {
      log("(form submission ignored -- invalid hostname)");
      return;
    }

    let formSubmitURL = LoginUtils._getActionOrigin(form);
    let messageManager = messageManagerFromWindow(win);

    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
      formOrigin: hostname,
    })[0];

    // Get the appropriate fields from the form.
    var [usernameField, newPasswordField, oldPasswordField] =
          this._getFormFields(form, true, recipes);

    // Need at least 1 valid password field to do anything.
    if (newPasswordField == null)
      return;

    // Check for autocomplete=off attribute. We don't use it to prevent
    // autofilling (for existing logins), but won't save logins when it's
    // present and the storeWhenAutocompleteOff pref is false.
    // XXX spin out a bug that we don't update timeLastUsed in this case?
    if ((this._isAutocompleteDisabled(form) ||
         this._isAutocompleteDisabled(usernameField) ||
         this._isAutocompleteDisabled(newPasswordField) ||
         this._isAutocompleteDisabled(oldPasswordField)) &&
        !gStoreWhenAutocompleteOff) {
      log("(form submission ignored -- autocomplete=off found)");
      return;
    }

    // Don't try to send DOM nodes over IPC.
    let mockUsername = usernameField ?
                         { name: usernameField.name,
                           value: usernameField.value } :
                         null;
    let mockPassword = { name: newPasswordField.name,
                         value: newPasswordField.value };
    let mockOldPassword = oldPasswordField ?
                            { name: oldPasswordField.name,
                              value: oldPasswordField.value } :
                            null;

    // Make sure to pass the opener's top in case it was in a frame.
    let openerTopWindow = win.opener ? win.opener.top : null;

    messageManager.sendAsyncMessage("RemoteLogins:onFormSubmit",
                                    { hostname,
                                      formSubmitURL,
                                      usernameField: mockUsername,
                                      newPasswordField: mockPassword,
                                      oldPasswordField: mockOldPassword },
                                    { openerTopWindow });
  },

  /**
   * Attempt to find the username and password fields in a form, and fill them
   * in using the provided logins and recipes.
   *
   * @param {LoginForm} form
   * @param {nsILoginInfo[]} foundLogins an array of nsILoginInfo that could be
            used for the form
   * @param {Set} recipes a set of recipes that could be used to affect how the
            form is filled
   * @param {Object} [options = {}] a list of options for this method
   * @param {HTMLInputElement} [options.inputElement = null] an optional target
   *        input element we want to fill
   * @param {bool} [options.autofillForm = false] denotes if we should fill the
   *        form in automatically
   * @param {bool} [options.clobberUsername = false] controls if an existing
   *        username can be overwritten. If this is false and an inputElement
   *        of type password is also passed, the username field will be ignored.
   *        If this is false and no inputElement is passed, if the username
   *        field value is not found in foundLogins, it will not fill the
   *        password.
   * @param {bool} [options.clobberPassword = false] controls if an existing
   *        password value can be overwritten
   * @param {bool} [options.userTriggered = false] an indication of whether
   *        this filling was triggered by the user
   */
  _fillForm(form, foundLogins, recipes, {
    inputElement = null,
    autofillForm = false,
    clobberUsername = false,
    clobberPassword = false,
    userTriggered = false,
  } = {}) {
    if (form instanceof Ci.nsIDOMHTMLFormElement) {
      throw new Error("_fillForm should only be called with FormLike objects");
    }

    log("_fillForm", form.elements);
    let ignoreAutocomplete = true;
    // Will be set to one of AUTOFILL_RESULT in the `try` block.
    let autofillResult = -1;
    const AUTOFILL_RESULT = {
      FILLED: 0,
      NO_PASSWORD_FIELD: 1,
      PASSWORD_DISABLED_READONLY: 2,
      NO_LOGINS_FIT: 3,
      NO_SAVED_LOGINS: 4,
      EXISTING_PASSWORD: 5,
      EXISTING_USERNAME: 6,
      MULTIPLE_LOGINS: 7,
      NO_AUTOFILL_FORMS: 8,
      AUTOCOMPLETE_OFF: 9,
      INSECURE: 10,
    };

    try {
      // Nothing to do if we have no matching logins available,
      // and there isn't a need to show the insecure form warning.
      if (foundLogins.length == 0 &&
          (InsecurePasswordUtils.isFormSecure(form) ||
          !LoginHelper.showInsecureFieldWarning)) {
        // We don't log() here since this is a very common case.
        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
        return;
      }

      // Heuristically determine what the user/pass fields are
      // We do this before checking to see if logins are stored,
      // so that the user isn't prompted for a master password
      // without need.
      var [usernameField, passwordField, ignored] =
            this._getFormFields(form, false, recipes);

      // If we have a password inputElement parameter and it's not
      // the same as the one heuristically found, use the parameter
      // one instead.
      if (inputElement) {
        if (inputElement.type == "password") {
          passwordField = inputElement;
          if (!clobberUsername) {
            usernameField = null;
          }
        } else if (LoginHelper.isUsernameFieldType(inputElement)) {
          usernameField = inputElement;
        } else {
          throw new Error("Unexpected input element type.");
        }
      }

      // Need a valid password field to do anything.
      if (passwordField == null) {
        log("not filling form, no password field found");
        autofillResult = AUTOFILL_RESULT.NO_PASSWORD_FIELD;
        return;
      }

      // If the password field is disabled or read-only, there's nothing to do.
      if (passwordField.disabled || passwordField.readOnly) {
        log("not filling form, password field disabled or read-only");
        autofillResult = AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY;
        return;
      }

      // Attach autocomplete stuff to the username field, if we have
      // one. This is normally used to select from multiple accounts,
      // but even with one account we should refill if the user edits.
      // We would also need this attached to show the insecure login
      // warning, regardless of saved login.
      if (usernameField) {
        this._formFillService.markAsLoginManagerField(usernameField);
      }

      // Nothing to do if we have no matching logins available.
      // Only insecure pages reach this block and logs the same
      // telemetry flag.
      if (foundLogins.length == 0) {
        // We don't log() here since this is a very common case.
        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
        return;
      }

      // Prevent autofilling insecure forms.
      if (!userTriggered && !LoginHelper.insecureAutofill &&
          !InsecurePasswordUtils.isFormSecure(form)) {
        log("not filling form since it's insecure");
        autofillResult = AUTOFILL_RESULT.INSECURE;
        return;
      }

      var isAutocompleteOff = false;
      if (this._isAutocompleteDisabled(form) ||
          this._isAutocompleteDisabled(usernameField) ||
          this._isAutocompleteDisabled(passwordField)) {
        isAutocompleteOff = true;
      }

      // Discard logins which have username/password values that don't
      // fit into the fields (as specified by the maxlength attribute).
      // The user couldn't enter these values anyway, and it helps
      // with sites that have an extra PIN to be entered (bug 391514)
      var maxUsernameLen = Number.MAX_VALUE;
      var maxPasswordLen = Number.MAX_VALUE;

      // If attribute wasn't set, default is -1.
      if (usernameField && usernameField.maxLength >= 0)
        maxUsernameLen = usernameField.maxLength;
      if (passwordField.maxLength >= 0)
        maxPasswordLen = passwordField.maxLength;

      var logins = foundLogins.filter(function(l) {
        var fit = (l.username.length <= maxUsernameLen &&
                   l.password.length <= maxPasswordLen);
        if (!fit)
          log("Ignored", l.username, "login: won't fit");

        return fit;
      }, this);

      if (logins.length == 0) {
        log("form not filled, none of the logins fit in the field");
        autofillResult = AUTOFILL_RESULT.NO_LOGINS_FIT;
        return;
      }

      // Don't clobber an existing password.
      if (passwordField.value && !clobberPassword) {
        log("form not filled, the password field was already filled");
        autofillResult = AUTOFILL_RESULT.EXISTING_PASSWORD;
        return;
      }

      // Select a login to use for filling in the form.
      var selectedLogin;
      if (!clobberUsername && usernameField && (usernameField.value ||
                                                usernameField.disabled ||
                                                usernameField.readOnly)) {
        // If username was specified in the field, it's disabled or it's readOnly, only fill in the
        // password if we find a matching login.
        var username = usernameField.value.toLowerCase();

        let matchingLogins = logins.filter(l =>
                                           l.username.toLowerCase() == username);
        if (matchingLogins.length == 0) {
          log("Password not filled. None of the stored logins match the username already present.");
          autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
          return;
        }

        // If there are multiple, and one matches case, use it
        for (let l of matchingLogins) {
          if (l.username == usernameField.value) {
            selectedLogin = l;
          }
        }
        // Otherwise just use the first
        if (!selectedLogin) {
          selectedLogin = matchingLogins[0];
        }
      } else if (logins.length == 1) {
        selectedLogin = logins[0];
      } else {
        // We have multiple logins. Handle a special case here, for sites
        // which have a normal user+pass login *and* a password-only login
        // (eg, a PIN). Prefer the login that matches the type of the form
        // (user+pass or pass-only) when there's exactly one that matches.
        let matchingLogins;
        if (usernameField)
          matchingLogins = logins.filter(l => l.username);
        else
          matchingLogins = logins.filter(l => !l.username);

        if (matchingLogins.length != 1) {
          log("Multiple logins for form, so not filling any.");
          autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
          return;
        }

        selectedLogin = matchingLogins[0];
      }

      // We will always have a selectedLogin at this point.

      if (!autofillForm) {
        log("autofillForms=false but form can be filled");
        autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
        return;
      }

      if (isAutocompleteOff && !ignoreAutocomplete) {
        log("Not filling the login because we're respecting autocomplete=off");
        autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
        return;
      }

      // Fill the form

      if (usernameField) {
      // Don't modify the username field if it's disabled or readOnly so we preserve its case.
        let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;

        let userNameDiffers = selectedLogin.username != usernameField.value;
        // Don't replace the username if it differs only in case, and the user triggered
        // this autocomplete. We assume that if it was user-triggered the entered text
        // is desired.
        let userEnteredDifferentCase = userTriggered && userNameDiffers &&
               usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();

        if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
          usernameField.setUserInput(selectedLogin.username);
        }
      }

      let doc = form.ownerDocument;
      if (passwordField.value != selectedLogin.password) {
        passwordField.setUserInput(selectedLogin.password);
        let autoFilledLogin = {
          guid: selectedLogin.QueryInterface(Ci.nsILoginMetaInfo).guid,
          username: selectedLogin.username,
          usernameField: usernameField ? Cu.getWeakReference(usernameField) : null,
          password: selectedLogin.password,
          passwordField: Cu.getWeakReference(passwordField),
        };
        log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement);
        this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin);
      }

      log("_fillForm succeeded");
      autofillResult = AUTOFILL_RESULT.FILLED;

      let win = doc.defaultView;
      let messageManager = messageManagerFromWindow(win);
      messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
    } finally {
      if (autofillResult == -1) {
        // eslint-disable-next-line no-unsafe-finally
        throw new Error("_fillForm: autofillResult must be specified");
      }

      if (!userTriggered) {
        // Ignore fills as a result of user action for this probe.
        Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT").add(autofillResult);

        if (usernameField) {
          let focusedElement = this._formFillService.focusedInput;
          if (usernameField == focusedElement &&
              autofillResult !== AUTOFILL_RESULT.FILLED) {
            log("_fillForm: Opening username autocomplete popup since the form wasn't autofilled");
            this._formFillService.showPopup();
          }
        }
      }

      if (usernameField) {
        log("_fillForm: Attaching event listeners to usernameField");
        usernameField.addEventListener("focus", observer);
        usernameField.addEventListener("mousedown", observer);
      }

      Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form");
    }
  },

  /**
   * Given a field, determine whether that field was last filled as a username
   * field AND whether the username is still filled in with the username AND
   * whether the associated password field has the matching password.
   *
   * @note This could possibly be unified with getFieldContext but they have
   * slightly different use cases. getFieldContext looks up recipes whereas this
   * method doesn't need to since it's only returning a boolean based upon the
   * recipes used for the last fill (in _fillForm).
   *
   * @param {HTMLInputElement} aUsernameField element contained in a FormLike
   *                                          cached in _formLikeByRootElement.
   * @returns {Boolean} whether the username and password fields still have the
   *                    last-filled values, if previously filled.
   */
  _isLoginAlreadyFilled(aUsernameField) {
    let formLikeRoot = FormLikeFactory.findRootForField(aUsernameField);
    // Look for the existing FormLike.
    let existingFormLike = this._formLikeByRootElement.get(formLikeRoot);
    if (!existingFormLike) {
      throw new Error("_isLoginAlreadyFilled called with a username field with " +
                      "no rootElement FormLike");
    }

    log("_isLoginAlreadyFilled: existingFormLike", existingFormLike);
    let filledLogin = this.stateForDocument(aUsernameField.ownerDocument).fillsByRootElement.get(formLikeRoot);
    if (!filledLogin) {
      return false;
    }

    // Unpack the weak references.
    let autoFilledUsernameField = filledLogin.usernameField ? filledLogin.usernameField.get() : null;
    let autoFilledPasswordField = filledLogin.passwordField.get();

    // Check username and password values match what was filled.
    if (!autoFilledUsernameField ||
        autoFilledUsernameField != aUsernameField ||
        autoFilledUsernameField.value != filledLogin.username ||
        !autoFilledPasswordField ||
        autoFilledPasswordField.value != filledLogin.password) {
      return false;
    }

    return true;
  },

  /**
   * Verify if a field is a valid login form field and
   * returns some information about it's FormLike.
   *
   * @param {Element} aField
   *                  A form field we want to verify.
   *
   * @returns {Object} an object with information about the
   *                   FormLike username and password field
   *                   or null if the passed field is invalid.
   */
  getFieldContext(aField) {
    // If the element is not a proper form field, return null.
    if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
        (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
        !aField.ownerDocument) {
      return null;
    }
    let form = LoginFormFactory.createFromField(aField);

    let doc = aField.ownerDocument;
    let messageManager = messageManagerFromWindow(doc.defaultView);
    let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
      formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
    })[0];

    let [usernameField, newPasswordField] =
          this._getFormFields(form, false, recipes);

    // If we are not verifying a password field, we want
    // to use aField as the username field.
    if (aField.type != "password") {
      usernameField = aField;
    }

    return {
      usernameField: {
        found: !!usernameField,
        disabled: usernameField && (usernameField.disabled || usernameField.readOnly),
      },
      passwordField: {
        found: !!newPasswordField,
        disabled: newPasswordField && (newPasswordField.disabled || newPasswordField.readOnly),
      },
    };
  },
};

var LoginUtils = {
  /**
   * Get the parts of the URL we want for identification.
   * Strip out things like the userPass portion
   */
  _getPasswordOrigin(uriString, allowJS) {
    var realm = "";
    try {
      var uri = Services.io.newURI(uriString);

      if (allowJS && uri.scheme == "javascript")
        return "javascript:";

      // Build this manually instead of using prePath to avoid including the userPass portion.
      realm = uri.scheme + "://" + uri.hostPort;
    } catch (e) {
      // bug 159484 - disallow url types that don't support a hostPort.
      // (although we handle "javascript:..." as a special case above.)
      log("Couldn't parse origin for", uriString, e);
      realm = null;
    }

    return realm;
  },

  _getActionOrigin(form) {
    var uriString = form.action;

    // A blank or missing action submits to where it came from.
    if (uriString == "")
      uriString = form.baseURI; // ala bug 297761

    return this._getPasswordOrigin(uriString, true);
  },
};

// nsIAutoCompleteResult implementation
function UserAutoCompleteResult(aSearchString, matchingLogins, {isSecure, messageManager, isPasswordField}) {
  function loginSort(a, b) {
    var userA = a.username.toLowerCase();
    var userB = b.username.toLowerCase();

    if (userA < userB)
      return -1;

    if (userA > userB)
      return 1;

    return 0;
  }

  function findDuplicates(loginList) {
    let seen = new Set();
    let duplicates = new Set();
    for (let login of loginList) {
      if (seen.has(login.username)) {
        duplicates.add(login.username);
      }
      seen.add(login.username);
    }
    return duplicates;
  }

  this._showInsecureFieldWarning = (!isSecure && LoginHelper.showInsecureFieldWarning) ? 1 : 0;
  this.searchString = aSearchString;
  this.logins = matchingLogins.sort(loginSort);
  this.matchCount = matchingLogins.length + this._showInsecureFieldWarning;
  this._messageManager = messageManager;
  this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
  this._dateAndTimeFormatter = Services.intl.createDateTimeFormat(undefined, { dateStyle: "medium" });

  this._isPasswordField = isPasswordField;

  this._duplicateUsernames = findDuplicates(matchingLogins);

  if (this.matchCount > 0) {
    this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
    this.defaultIndex = 0;
  }
}

UserAutoCompleteResult.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
                                          Ci.nsISupportsWeakReference]),

  // private
  logins: null,

  // Allow autoCompleteSearch to get at the JS object so it can
  // modify some readonly properties for internal use.
  get wrappedJSObject() {
    return this;
  },

  // Interfaces from idl...
  searchString: null,
  searchResult: Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
  defaultIndex: -1,
  errorDescription: "",
  matchCount: 0,

  getValueAt(index) {
    if (index < 0 || index >= this.matchCount) {
      throw new Error("Index out of range.");
    }

    if (this._showInsecureFieldWarning && index === 0) {
      return "";
    }

    let selectedLogin = this.logins[index - this._showInsecureFieldWarning];

    return this._isPasswordField ? selectedLogin.password : selectedLogin.username;
  },

  getLabelAt(index) {
    if (index < 0 || index >= this.matchCount) {
      throw new Error("Index out of range.");
    }

    let getLocalizedString = (key, formatArgs = null) => {
      if (formatArgs) {
        return this._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
      }
      return this._stringBundle.GetStringFromName(key);
    };

    if (this._showInsecureFieldWarning && index === 0) {
      let learnMoreString = getLocalizedString("insecureFieldWarningLearnMore");
      return getLocalizedString("insecureFieldWarningDescription2", [learnMoreString]);
    }

    let login = this.logins[index - this._showInsecureFieldWarning];
    let username = login.username;
    // If login is empty or duplicated we want to append a modification date to it.
    if (!username || this._duplicateUsernames.has(username)) {
      if (!username) {
        username = getLocalizedString("noUsername");
      }
      let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
      let time = this._dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
      username = getLocalizedString("loginHostAge", [username, time]);
    }

    return username;
  },

  getCommentAt(index) {
    return "";
  },

  getStyleAt(index) {
    if (index == 0 && this._showInsecureFieldWarning) {
      return "insecureWarning";
    }

    return "login";
  },

  getImageAt(index) {
    return "";
  },

  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  },

  removeValueAt(index, removeFromDB) {
    if (index < 0 || index >= this.matchCount) {
      throw new Error("Index out of range.");
    }

    if (this._showInsecureFieldWarning && index === 0) {
      // Ignore the warning message item.
      return;
    }
    if (this._showInsecureFieldWarning) {
      index--;
    }

    var [removedLogin] = this.logins.splice(index, 1);

    this.matchCount--;
    if (this.defaultIndex > this.logins.length)
      this.defaultIndex--;

    if (removeFromDB) {
      if (this._messageManager) {
        let vanilla = LoginHelper.loginToVanillaObject(removedLogin);
        this._messageManager.sendAsyncMessage("RemoteLogins:removeLogin",
                                              { login: vanilla });
      } else {
        Services.logins.removeLogin(removedLogin);
      }
    }
  }
};

/**
 * A factory to generate FormLike objects that represent a set of login fields
 * which aren't necessarily marked up with a <form> element.
 */
var LoginFormFactory = {
  /**
   * Create a LoginForm object from a <form>.
   *
   * @param {HTMLFormElement} aForm
   * @return {LoginForm}
   * @throws Error if aForm isn't an HTMLFormElement
   */
  createFromForm(aForm) {
    let formLike = FormLikeFactory.createFromForm(aForm);
    formLike.action = LoginUtils._getActionOrigin(aForm);

    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
    state.loginFormRootElements.add(formLike.rootElement);
    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);

    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
    return formLike;
  },

  /**
   * Create a LoginForm object from a password or username field.
   *
   * If the field is in a <form>, construct the LoginForm from the form.
   * Otherwise, create a LoginForm with a rootElement (wrapper) according to
   * heuristics. Currently all <input> not in a <form> are one LoginForm but this
   * shouldn't be relied upon as the heuristics may change to detect multiple
   * "forms" (e.g. registration and login) on one page with a <form>.
   *
   * Note that two LoginForms created from the same field won't return the same LoginForm object.
   * Use the `rootElement` property on the LoginForm as a key instead.
   *
   * @param {HTMLInputElement} aField - a password or username field in a document
   * @return {LoginForm}
   * @throws Error if aField isn't a password or username field in a document
   */
  createFromField(aField) {
    if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
        (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
        !aField.ownerDocument) {
      throw new Error("createFromField requires a password or username field in a document");
    }

    if (aField.form) {
      return this.createFromForm(aField.form);
    }

    let formLike = FormLikeFactory.createFromField(aField);
    formLike.action = LoginUtils._getPasswordOrigin(aField.ownerDocument.baseURI);
    log("Created non-form FormLike for rootElement:", aField.ownerDocument.documentElement);

    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
    state.loginFormRootElements.add(formLike.rootElement);
    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);


    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);

    return formLike;
  },
};
PK
!<It'#modules/LoginManagerContextMenu.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["LoginManagerContextMenu"];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                  "resource://gre/modules/LoginManagerParent.jsm");

/*
 * Password manager object for the browser contextual menu.
 */
var LoginManagerContextMenu = {
  /**
   * Look for login items and add them to the contextual menu.
   *
   * @param {HTMLInputElement} inputElement
   *        The target input element of the context menu click.
   * @param {xul:browser} browser
   *        The browser for the document the context menu was open on.
   * @param {nsIURI} documentURI
   *        The URI of the document that the context menu was activated from.
   *        This isn't the same as the browser's top-level document URI
   *        when subframes are involved.
   * @returns {DocumentFragment} a document fragment with all the login items.
   */
  addLoginsToMenu(inputElement, browser, documentURI) {
    let foundLogins = this._findLogins(documentURI);

    if (!foundLogins.length) {
      return null;
    }

    let fragment = browser.ownerDocument.createDocumentFragment();
    let duplicateUsernames = this._findDuplicates(foundLogins);
    for (let login of foundLogins) {
        let item = fragment.ownerDocument.createElement("menuitem");

        let username = login.username;
        // If login is empty or duplicated we want to append a modification date to it.
        if (!username || duplicateUsernames.has(username)) {
          if (!username) {
            username = this._getLocalizedString("noUsername");
          }
          let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
          let time = this.dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
          username = this._getLocalizedString("loginHostAge", [username, time]);
        }
        item.setAttribute("label", username);
        item.setAttribute("class", "context-login-item");

        // login is bound so we can keep the reference to each object.
        item.addEventListener("command", function(login, event) {
          this._fillTargetField(login, inputElement, browser, documentURI);
        }.bind(this, login));

        fragment.appendChild(item);
    }

    return fragment;
  },

  /**
   * Undoes the work of addLoginsToMenu for the same menu.
   *
   * @param {Document}
   *        The context menu owner document.
   */
  clearLoginsFromMenu(document) {
    let loginItems = document.getElementsByClassName("context-login-item");
    while (loginItems.item(0)) {
      loginItems.item(0).remove();
    }
  },

  /**
   * Find logins for the current URI.
   *
   * @param {nsIURI} documentURI
   *        URI object with the hostname of the logins we want to find.
   *        This isn't the same as the browser's top-level document URI
   *        when subframes are involved.
   *
   * @returns {nsILoginInfo[]} a login list
   */
  _findLogins(documentURI) {
    let searchParams = {
      hostname: documentURI.prePath,
      schemeUpgrades: LoginHelper.schemeUpgrades,
    };
    let logins = LoginHelper.searchLoginsWithObject(searchParams);
    let resolveBy = [
      "scheme",
      "timePasswordChanged",
    ];
    logins = LoginHelper.dedupeLogins(logins, ["username", "password"], resolveBy, documentURI.prePath);

    // Sort logins in alphabetical order and by date.
    logins.sort((loginA, loginB) => {
      // Sort alphabetically
      let result = loginA.username.localeCompare(loginB.username);
      if (result) {
        // Forces empty logins to be at the end
        if (!loginA.username) {
          return 1;
        }
        if (!loginB.username) {
          return -1;
        }
        return result;
      }

      // Same username logins are sorted by last change date
      let metaA = loginA.QueryInterface(Ci.nsILoginMetaInfo);
      let metaB = loginB.QueryInterface(Ci.nsILoginMetaInfo);
      return metaB.timePasswordChanged - metaA.timePasswordChanged;
    });

    return logins;
  },

  /**
   * Find duplicate usernames in a login list.
   *
   * @param {nsILoginInfo[]} loginList
   *        A list of logins we want to look for duplicate usernames.
   *
   * @returns {Set} a set with the duplicate usernames.
   */
  _findDuplicates(loginList) {
    let seen = new Set();
    let duplicates = new Set();
    for (let login of loginList) {
      if (seen.has(login.username)) {
        duplicates.add(login.username);
      }
      seen.add(login.username);
    }
    return duplicates;
  },

  /**
   * @param {nsILoginInfo} login
   *        The login we want to fill the form with.
   * @param {Element} inputElement
   *        The target input element we want to fill.
   * @param {xul:browser} browser
   *        The target tab browser.
   * @param {nsIURI} documentURI
   *        URI of the document owning the form we want to fill.
   *        This isn't the same as the browser's top-level
   *        document URI when subframes are involved.
   */
  _fillTargetField(login, inputElement, browser, documentURI) {
    LoginManagerParent.fillForm({
      browser,
      loginFormOrigin: documentURI.prePath,
      login,
      inputElement,
    }).catch(Cu.reportError);
  },

  /**
   * @param {string} key
   *        The localized string key
   * @param {string[]} formatArgs
   *        An array of formatting argument string
   *
   * @returns {string} the localized string for the specified key,
   *          formatted with arguments if required.
   */
  _getLocalizedString(key, formatArgs) {
    if (formatArgs) {
      return this._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
    }
    return this._stringBundle.GetStringFromName(key);
  },
};

XPCOMUtils.defineLazyGetter(LoginManagerContextMenu, "_stringBundle", function() {
  return Services.strings.
         createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
});

XPCOMUtils.defineLazyGetter(LoginManagerContextMenu, "dateAndTimeFormatter", function() {
  return Services.intl.createDateTimeFormat(undefined, {
    dateStyle: "medium"
  });
});
PK
!<njIjImodules/LoginManagerParent.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AutoCompletePopup",
                                  "resource://gre/modules/AutoCompletePopup.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");

XPCOMUtils.defineLazyGetter(this, "log", () => {
  let logger = LoginHelper.createLogger("LoginManagerParent");
  return logger.log.bind(logger);
});

this.EXPORTED_SYMBOLS = [ "LoginManagerParent" ];

var LoginManagerParent = {
  /**
   * Reference to the default LoginRecipesParent (instead of the initialization promise) for
   * synchronous access. This is a temporary hack and new consumers should yield on
   * recipeParentPromise instead.
   *
   * @type LoginRecipesParent
   * @deprecated
   */
  _recipeManager: null,

  // Tracks the last time the user cancelled the master password prompt,
  // to avoid spamming master password prompts on autocomplete searches.
  _lastMPLoginCancelled: Math.NEGATIVE_INFINITY,

  _searchAndDedupeLogins(formOrigin, actionOrigin) {
    let logins;
    try {
      logins = LoginHelper.searchLoginsWithObject({
        hostname: formOrigin,
        formSubmitURL: actionOrigin,
        schemeUpgrades: LoginHelper.schemeUpgrades,
      });
    } catch (e) {
      // Record the last time the user cancelled the MP prompt
      // to avoid spamming them with MP prompts for autocomplete.
      if (e.result == Cr.NS_ERROR_ABORT) {
        log("User cancelled master password prompt.");
        this._lastMPLoginCancelled = Date.now();
        return [];
      }
      throw e;
    }

    // Dedupe so the length checks below still make sense with scheme upgrades.
    let resolveBy = [
      "scheme",
      "timePasswordChanged",
    ];
    return LoginHelper.dedupeLogins(logins, ["username"], resolveBy, formOrigin);
  },

  // This should only be called on Android. Listeners are added in
  // nsBrowserGlue.js on desktop. Please make sure that the list of
  // listeners added here stays in sync with the listeners added in
  // nsBrowserGlue when you change either.
  init() {
    let mm = Cc["@mozilla.org/globalmessagemanager;1"]
               .getService(Ci.nsIMessageListenerManager);
    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
    mm.addMessageListener("RemoteLogins:findLogins", this);
    mm.addMessageListener("RemoteLogins:findRecipes", this);
    mm.addMessageListener("RemoteLogins:onFormSubmit", this);
    mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
    mm.addMessageListener("RemoteLogins:removeLogin", this);
    mm.addMessageListener("RemoteLogins:insecureLoginFormPresent", this);
    // PLEASE KEEP THIS LIST IN SYNC WITH THE LISTENERS ADDED IN nsBrowserGlue
  },

  // Listeners are added in nsBrowserGlue.js
  receiveMessage(msg) {
    let data = msg.data;
    switch (msg.name) {
      case "RemoteLogins:findLogins": {
        // TODO Verify msg.target's principals against the formOrigin?
        this.sendLoginDataToChild(data.options.showMasterPassword,
                                  data.formOrigin,
                                  data.actionOrigin,
                                  data.requestId,
                                  msg.target.messageManager);
        break;
      }

      case "RemoteLogins:findRecipes": {
        let formHost = (new URL(data.formOrigin)).host;
        return this._recipeManager.getRecipesForHost(formHost);
      }

      case "RemoteLogins:onFormSubmit": {
        // TODO Verify msg.target's principals against the formOrigin?
        this.onFormSubmit(data.hostname,
                          data.formSubmitURL,
                          data.usernameField,
                          data.newPasswordField,
                          data.oldPasswordField,
                          msg.objects.openerTopWindow,
                          msg.target);
        break;
      }

      case "RemoteLogins:insecureLoginFormPresent": {
        this.setHasInsecureLoginForms(msg.target, data.hasInsecureLoginForms);
        break;
      }

      case "RemoteLogins:autoCompleteLogins": {
        this.doAutocompleteSearch(data, msg.target);
        break;
      }

      case "RemoteLogins:removeLogin": {
        let login = LoginHelper.vanillaObjectToLogin(data.login);
        AutoCompletePopup.removeLogin(login);
        break;
      }
    }

    return undefined;
  },

  /**
   * Trigger a login form fill and send relevant data (e.g. logins and recipes)
   * to the child process (LoginManagerContent).
   */
  async fillForm({ browser, loginFormOrigin, login, inputElement }) {
    let recipes = [];
    if (loginFormOrigin) {
      let formHost;
      try {
        formHost = (new URL(loginFormOrigin)).host;
        let recipeManager = await this.recipeParentPromise;
        recipes = recipeManager.getRecipesForHost(formHost);
      } catch (ex) {
        // Some schemes e.g. chrome aren't supported by URL
      }
    }

    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    let jsLogins = [LoginHelper.loginToVanillaObject(login)];

    let objects = inputElement ? {inputElement} : null;
    browser.messageManager.sendAsyncMessage("RemoteLogins:fillForm", {
      loginFormOrigin,
      logins: jsLogins,
      recipes,
    }, objects);
  },

  /**
   * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
   */
  async sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
                                             requestId, target) {
    let recipes = [];
    if (formOrigin) {
      let formHost;
      try {
        formHost = (new URL(formOrigin)).host;
        let recipeManager = await this.recipeParentPromise;
        recipes = recipeManager.getRecipesForHost(formHost);
      } catch (ex) {
        // Some schemes e.g. chrome aren't supported by URL
      }
    }

    if (!showMasterPassword && !Services.logins.isLoggedIn) {
      try {
        target.sendAsyncMessage("RemoteLogins:loginsFound", {
          requestId,
          logins: [],
          recipes,
        });
      } catch (e) {
        log("error sending message to target", e);
      }
      return;
    }

    // If we're currently displaying a master password prompt, defer
    // processing this form until the user handles the prompt.
    if (Services.logins.uiBusy) {
      log("deferring sendLoginDataToChild for", formOrigin);
      let self = this;
      let observer = {
        QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                               Ci.nsISupportsWeakReference]),

        observe(subject, topic, data) {
          log("Got deferred sendLoginDataToChild notification:", topic);
          // Only run observer once.
          Services.obs.removeObserver(this, "passwordmgr-crypto-login");
          Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
          if (topic == "passwordmgr-crypto-loginCanceled") {
            target.sendAsyncMessage("RemoteLogins:loginsFound", {
              requestId,
              logins: [],
              recipes,
            });
            return;
          }

          self.sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
                                    requestId, target);
        },
      };

      // Possible leak: it's possible that neither of these notifications
      // will fire, and if that happens, we'll leak the observer (and
      // never return). We should guarantee that at least one of these
      // will fire.
      // See bug XXX.
      Services.obs.addObserver(observer, "passwordmgr-crypto-login");
      Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
      return;
    }

    let logins = this._searchAndDedupeLogins(formOrigin, actionOrigin);

    log("sendLoginDataToChild:", logins.length, "deduped logins");
    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    var jsLogins = LoginHelper.loginsToVanillaObjects(logins);
    target.sendAsyncMessage("RemoteLogins:loginsFound", {
      requestId,
      logins: jsLogins,
      recipes,
    });
  },

  doAutocompleteSearch({ formOrigin, actionOrigin,
                         searchString, previousResult,
                         rect, requestId, isSecure, isPasswordField,
                       }, target) {
    // Note: previousResult is a regular object, not an
    // nsIAutoCompleteResult.

    // Cancel if we unsuccessfully prompted for the master password too recently.
    if (!Services.logins.isLoggedIn) {
      let timeDiff = Date.now() - this._lastMPLoginCancelled;
      if (timeDiff < this._repromptTimeout) {
        log("Not searching logins for autocomplete since the master password " +
            `prompt was last cancelled ${Math.round(timeDiff / 1000)} seconds ago.`);
        // Send an empty array to make LoginManagerContent clear the
        // outstanding request it has temporarily saved.
        target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
          requestId,
          logins: [],
        });
        return;
      }
    }

    let searchStringLower = searchString.toLowerCase();
    let logins;
    if (previousResult &&
        searchStringLower.startsWith(previousResult.searchString.toLowerCase())) {
      log("Using previous autocomplete result");

      // We have a list of results for a shorter search string, so just
      // filter them further based on the new search string.
      logins = LoginHelper.vanillaObjectsToLogins(previousResult.logins);
    } else {
      log("Creating new autocomplete search result.");

      logins = this._searchAndDedupeLogins(formOrigin, actionOrigin);
    }

    let matchingLogins = logins.filter(function(fullMatch) {
      let match = fullMatch.username;

      // Remove results that are too short, or have different prefix.
      // Also don't offer empty usernames as possible results except
      // for password field.
      if (isPasswordField) {
        return true;
      }
      return match && match.toLowerCase().startsWith(searchStringLower);
    });

    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
    target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
      requestId,
      logins: jsLogins,
    });
  },

  onFormSubmit(hostname, formSubmitURL,
                         usernameField, newPasswordField,
                         oldPasswordField, openerTopWindow,
                         target) {
    function getPrompter() {
      var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                        createInstance(Ci.nsILoginManagerPrompter);
      prompterSvc.init(target.ownerGlobal);
      prompterSvc.browser = target;
      prompterSvc.opener = openerTopWindow;
      return prompterSvc;
    }

    function recordLoginUse(login) {
      // Update the lastUsed timestamp and increment the use count.
      let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                    createInstance(Ci.nsIWritablePropertyBag);
      propBag.setProperty("timeLastUsed", Date.now());
      propBag.setProperty("timesUsedIncrement", 1);
      Services.logins.modifyLogin(login, propBag);
    }

    if (!Services.logins.getLoginSavingEnabled(hostname)) {
      log("(form submission ignored -- saving is disabled for:", hostname, ")");
      return;
    }

    var formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
                    createInstance(Ci.nsILoginInfo);
    formLogin.init(hostname, formSubmitURL, null,
                   (usernameField ? usernameField.value : ""),
                   newPasswordField.value,
                   (usernameField ? usernameField.name : ""),
                   newPasswordField.name);

    // Below here we have one login per hostPort + action + username with the
    // matching scheme being preferred.
    let logins = this._searchAndDedupeLogins(hostname, formSubmitURL);

    // If we didn't find a username field, but seem to be changing a
    // password, allow the user to select from a list of applicable
    // logins to update the password for.
    if (!usernameField && oldPasswordField && logins.length > 0) {
      var prompter = getPrompter();

      if (logins.length == 1) {
        var oldLogin = logins[0];

        if (oldLogin.password == formLogin.password) {
          recordLoginUse(oldLogin);
          log("(Not prompting to save/change since we have no username and the " +
              "only saved password matches the new password)");
          return;
        }

        formLogin.username      = oldLogin.username;
        formLogin.usernameField = oldLogin.usernameField;

        prompter.promptToChangePassword(oldLogin, formLogin);
      } else {
        // Note: It's possible that that we already have the correct u+p saved
        // but since we don't have the username, we don't know if the user is
        // changing a second account to the new password so we ask anyways.

        prompter.promptToChangePasswordWithUsernames(
                            logins, logins.length, formLogin);
      }

      return;
    }


    var existingLogin = null;
    // Look for an existing login that matches the form login.
    for (let login of logins) {
      let same;

      // If one login has a username but the other doesn't, ignore
      // the username when comparing and only match if they have the
      // same password. Otherwise, compare the logins and match even
      // if the passwords differ.
      if (!login.username && formLogin.username) {
        var restoreMe = formLogin.username;
        formLogin.username = "";
        same = LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: false,
          ignoreSchemes: LoginHelper.schemeUpgrades,
        });
        formLogin.username = restoreMe;
      } else if (!formLogin.username && login.username) {
        formLogin.username = login.username;
        same = LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: false,
          ignoreSchemes: LoginHelper.schemeUpgrades,
        });
        formLogin.username = ""; // we know it's always blank.
      } else {
        same = LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: true,
          ignoreSchemes: LoginHelper.schemeUpgrades,
        });
      }

      if (same) {
        existingLogin = login;
        break;
      }
    }

    if (existingLogin) {
      log("Found an existing login matching this form submission");

      // Change password if needed.
      if (existingLogin.password != formLogin.password) {
        log("...passwords differ, prompting to change.");
        prompter = getPrompter();
        prompter.promptToChangePassword(existingLogin, formLogin);
      } else if (!existingLogin.username && formLogin.username) {
        log("...empty username update, prompting to change.");
        prompter = getPrompter();
        prompter.promptToChangePassword(existingLogin, formLogin);
      } else {
        recordLoginUse(existingLogin);
      }

      return;
    }


    // Prompt user to save login (via dialog or notification bar)
    prompter = getPrompter();
    prompter.promptToSavePassword(formLogin);
  },

  /**
   * Maps all the <browser> elements for tabs in the parent process to the
   * current state used to display tab-specific UI.
   *
   * This mapping is not updated in case a web page is moved to a different
   * chrome window by the swapDocShells method. In this case, it is possible
   * that a UI update just requested for the login fill doorhanger and then
   * delayed by a few hundred milliseconds will be lost. Later requests would
   * use the new browser reference instead.
   *
   * Given that the case above is rare, and it would not cause any origin
   * mismatch at the time of filling because the origin is checked later in the
   * content process, this case is left unhandled.
   */
  loginFormStateByBrowser: new WeakMap(),

  /**
   * Retrieves a reference to the state object associated with the given
   * browser. This is initialized to an empty object.
   */
  stateForBrowser(browser) {
    let loginFormState = this.loginFormStateByBrowser.get(browser);
    if (!loginFormState) {
      loginFormState = {};
      this.loginFormStateByBrowser.set(browser, loginFormState);
    }
    return loginFormState;
  },

  /**
   * Returns true if the page currently loaded in the given browser element has
   * insecure login forms. This state may be updated asynchronously, in which
   * case a custom event named InsecureLoginFormsStateChange will be dispatched
   * on the browser element.
   */
  hasInsecureLoginForms(browser) {
    return !!this.stateForBrowser(browser).hasInsecureLoginForms;
  },

  /**
   * Called to indicate whether an insecure password field is present so
   * insecure password UI can know when to show.
   */
  setHasInsecureLoginForms(browser, hasInsecureLoginForms) {
    let state = this.stateForBrowser(browser);

    // Update the data to use to the latest known values. Since messages are
    // processed in order, this will always be the latest version to use.
    state.hasInsecureLoginForms = hasInsecureLoginForms;

    // Report the insecure login form state immediately.
    browser.dispatchEvent(new browser.ownerGlobal
                                 .CustomEvent("InsecureLoginFormsStateChange"));
  },
};

XPCOMUtils.defineLazyGetter(LoginManagerParent, "recipeParentPromise", function() {
  const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
  this._recipeManager = new LoginRecipesParent({
    defaults: Services.prefs.getStringPref("signon.recipes.path"),
  });
  return this._recipeManager.initializationPromise;
});

XPCOMUtils.defineLazyPreferenceGetter(LoginManagerParent, "_repromptTimeout",
  "signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
PK
!<!!modules/LoginRecipes.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["LoginRecipesContent", "LoginRecipesParent"];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const REQUIRED_KEYS = ["hosts"];
const OPTIONAL_KEYS = [
  "description",
  "notPasswordSelector",
  "notUsernameSelector",
  "passwordSelector",
  "pathRegex",
  "usernameSelector",
];
const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);

Cu.importGlobalProperties(["URL"]);

Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                  "resource://gre/modules/LoginHelper.jsm");

XPCOMUtils.defineLazyGetter(this, "log", () => LoginHelper.createLogger("LoginRecipes"));

/**
 * Create an instance of the object to manage recipes in the parent process.
 * Consumers should wait until {@link initializationPromise} resolves before
 * calling methods on the object.
 *
 * @constructor
 * @param {String} [aOptions.defaults=null] the URI to load the recipes from.
 *                                          If it's null, nothing is loaded.
 *
*/
function LoginRecipesParent(aOptions = { defaults: null }) {
  if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
    throw new Error("LoginRecipesParent should only be used from the main process");
  }
  this._defaults = aOptions.defaults;
  this.reset();
}

LoginRecipesParent.prototype = {
  /**
   * Promise resolved with an instance of itself when the module is ready.
   *
   * @type {Promise}
   */
  initializationPromise: null,

  /**
   * @type {bool} Whether default recipes were loaded at construction time.
   */
  _defaults: null,

  /**
   * @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes.
   *             e.g. "example.com:8080" => Set({...})
   */
  _recipesByHost: null,

  /**
   * @param {Object} aRecipes an object containing recipes to load for use. The object
   *                          should be compatible with JSON (e.g. no RegExp).
   * @return {Promise} resolving when the recipes are loaded
   */
  load(aRecipes) {
    let recipeErrors = 0;
    for (let rawRecipe of aRecipes.siteRecipes) {
      try {
        rawRecipe.pathRegex = rawRecipe.pathRegex ? new RegExp(rawRecipe.pathRegex) : undefined;
        this.add(rawRecipe);
      } catch (ex) {
        recipeErrors++;
        log.error("Error loading recipe", rawRecipe, ex);
      }
    }

    if (recipeErrors) {
      return Promise.reject(`There were ${recipeErrors} recipe error(s)`);
    }

    return Promise.resolve();
  },

  /**
   * Reset the set of recipes to the ones from the time of construction.
   */
  reset() {
    log.debug("Resetting recipes with defaults:", this._defaults);
    this._recipesByHost = new Map();

    if (this._defaults) {
      let channel = NetUtil.newChannel({uri: NetUtil.newURI(this._defaults, "UTF-8"),
                                        loadUsingSystemPrincipal: true});
      channel.contentType = "application/json";

      try {
        this.initializationPromise = new Promise(function(resolve) {
          NetUtil.asyncFetch(channel, function(stream, result) {
            if (!Components.isSuccessCode(result)) {
              throw new Error("Error fetching recipe file:" + result);
            }
            let count = stream.available();
            let data = NetUtil.readInputStreamToString(stream, count, { charset: "UTF-8" });
            resolve(JSON.parse(data));
          });
        }).then(recipes => {
          return this.load(recipes);
        }).then(resolve => {
          return this;
        });
      } catch (e) {
        throw new Error("Error reading recipe file:" + e);
      }
    } else {
      this.initializationPromise = Promise.resolve(this);
    }
  },

  /**
   * Validate the recipe is sane and then add it to the set of recipes.
   *
   * @param {Object} recipe
   */
  add(recipe) {
    log.debug("Adding recipe:", recipe);
    let recipeKeys = Object.keys(recipe);
    let unknownKeys = recipeKeys.filter(key => SUPPORTED_KEYS.indexOf(key) == -1);
    if (unknownKeys.length > 0) {
      throw new Error("The following recipe keys aren't supported: " + unknownKeys.join(", "));
    }

    let missingRequiredKeys = REQUIRED_KEYS.filter(key => recipeKeys.indexOf(key) == -1);
    if (missingRequiredKeys.length > 0) {
      throw new Error("The following required recipe keys are missing: " + missingRequiredKeys.join(", "));
    }

    if (!Array.isArray(recipe.hosts)) {
      throw new Error("'hosts' must be a array");
    }

    if (!recipe.hosts.length) {
      throw new Error("'hosts' must be a non-empty array");
    }

    if (recipe.pathRegex && recipe.pathRegex.constructor.name != "RegExp") {
      throw new Error("'pathRegex' must be a regular expression");
    }

    const OPTIONAL_STRING_PROPS = ["description", "passwordSelector", "usernameSelector"];
    for (let prop of OPTIONAL_STRING_PROPS) {
      if (recipe[prop] && typeof(recipe[prop]) != "string") {
        throw new Error(`'${prop}' must be a string`);
      }
    }

    // Add the recipe to the map for each host
    for (let host of recipe.hosts) {
      if (!this._recipesByHost.has(host)) {
        this._recipesByHost.set(host, new Set());
      }
      this._recipesByHost.get(host).add(recipe);
    }
  },

  /**
   * Currently only exact host matches are returned but this will eventually handle parent domains.
   *
   * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
   * @return {Set} of recipes that apply to the host ordered by host priority
   */
  getRecipesForHost(aHost) {
    let hostRecipes = this._recipesByHost.get(aHost);
    if (!hostRecipes) {
      return new Set();
    }

    return hostRecipes;
  },
};


var LoginRecipesContent = {
  /**
   * @param {Set} aRecipes - Possible recipes that could apply to the form
   * @param {FormLike} aForm - We use a form instead of just a URL so we can later apply
   * tests to the page contents.
   * @return {Set} a subset of recipes that apply to the form with the order preserved
   */
  _filterRecipesForForm(aRecipes, aForm) {
    let formDocURL = aForm.ownerDocument.location;
    let hostRecipes = aRecipes;
    let recipes = new Set();
    log.debug("_filterRecipesForForm", aRecipes);
    if (!hostRecipes) {
      return recipes;
    }

    for (let hostRecipe of hostRecipes) {
      if (hostRecipe.pathRegex && !hostRecipe.pathRegex.test(formDocURL.pathname)) {
        continue;
      }
      recipes.add(hostRecipe);
    }

    return recipes;
  },

  /**
   * Given a set of recipes that apply to the host, choose the one most applicable for
   * overriding login fields in the form.
   *
   * @param {Set} aRecipes The set of recipes to consider for the form
   * @param {FormLike} aForm The form where login fields exist.
   * @return {Object} The recipe that is most applicable for the form.
   */
  getFieldOverrides(aRecipes, aForm) {
    let recipes = this._filterRecipesForForm(aRecipes, aForm);
    log.debug("getFieldOverrides: filtered recipes:", recipes);
    if (!recipes.size) {
      return null;
    }

    let chosenRecipe = null;
    // Find the first (most-specific recipe that involves field overrides).
    for (let recipe of recipes) {
      if (!recipe.usernameSelector && !recipe.passwordSelector &&
          !recipe.notUsernameSelector && !recipe.notPasswordSelector) {
        continue;
      }

      chosenRecipe = recipe;
      break;
    }

    return chosenRecipe;
  },

  /**
   * @param {HTMLElement} aParent the element to query for the selector from.
   * @param {CSSSelector} aSelector the CSS selector to query for the login field.
   * @return {HTMLElement|null}
   */
  queryLoginField(aParent, aSelector) {
    if (!aSelector) {
      return null;
    }
    let field = aParent.ownerDocument.querySelector(aSelector);
    if (!field) {
      log.debug("Login field selector wasn't matched:", aSelector);
      return null;
    }
    // ownerGlobal doesn't exist in content privileged windows.
    // eslint-disable-next-line mozilla/use-ownerGlobal
    if (!(field instanceof aParent.ownerDocument.defaultView.HTMLInputElement)) {
      log.warn("Login field isn't an <input> so ignoring it:", aSelector);
      return null;
    }
    return field;
  },
};
PK
!<<]BBmodules/LoginStore.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of the data and persistence into a file.
 *
 * The file is stored in JSON format, without indentation, using UTF-8 encoding.
 * With indentation applied, the file would look like this:
 *
 * {
 *   "logins": [
 *     {
 *       "id": 2,
 *       "hostname": "http://www.example.com",
 *       "httpRealm": null,
 *       "formSubmitURL": "http://www.example.com/submit-url",
 *       "usernameField": "username_field",
 *       "passwordField": "password_field",
 *       "encryptedUsername": "...",
 *       "encryptedPassword": "...",
 *       "guid": "...",
 *       "encType": 1,
 *       "timeCreated": 1262304000000,
 *       "timeLastUsed": 1262304000000,
 *       "timePasswordChanged": 1262476800000,
 *       "timesUsed": 1
 *     },
 *     {
 *       "id": 4,
 *       (...)
 *     }
 *   ],
 *   "disabledHosts": [
 *     "http://www.example.org",
 *     "http://www.example.net"
 *   ],
 *   "nextId": 10,
 *   "version": 1
 * }
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "LoginStore",
];

// Globals

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");

/**
 * Current data version assigned by the code that last touched the data.
 *
 * This number should be updated only when it is important to understand whether
 * an old version of the code has touched the data, for example to execute an
 * update logic.  In most cases, this number should not be changed, in
 * particular when no special one-time update logic is needed.
 *
 * For example, this number should NOT be changed when a new optional field is
 * added to a login entry.
 */
const kDataVersion = 2;

// The permission type we store in the permission manager.
const PERMISSION_SAVE_LOGINS = "login-saving";

// LoginStore

/**
 * Inherits from JSONFile and handles serialization of login-related data and
 * persistence into a file.
 *
 * @param aPath
 *        String containing the file path where data should be saved.
 */
function LoginStore(aPath) {
  JSONFile.call(this, {
    path: aPath,
    dataPostProcessor: this._dataPostProcessor.bind(this)
  });
}

LoginStore.prototype = Object.create(JSONFile.prototype);
LoginStore.prototype.constructor = LoginStore;

/**
 * Synchronously work on the data just loaded into memory.
 */
LoginStore.prototype._dataPostProcessor = function(data) {
  if (data.nextId === undefined) {
    data.nextId = 1;
  }

  // Create any arrays that are not present in the saved file.
  if (!data.logins) {
    data.logins = [];
  }

  // Stub needed for login imports before data has been migrated.
  if (!data.disabledHosts) {
    data.disabledHosts = [];
  }

  if (data.version === 1) {
    this._migrateDisabledHosts(data);
  }

  // Indicate that the current version of the code has touched the file.
  data.version = kDataVersion;

  return data;
};

/**
 * Migrates disabled hosts to the permission manager.
 */
LoginStore.prototype._migrateDisabledHosts = function(data) {
  for (let host of data.disabledHosts) {
    try {
      let uri = Services.io.newURI(host);
      Services.perms.add(uri, PERMISSION_SAVE_LOGINS, Services.perms.DENY_ACTION);
    } catch (e) {
      Cu.reportError(e);
    }
  }

  delete data.disabledHosts;
};
PK
!<܈modules/Manifest.jsm/*
 * Manifest.jsm is the top level api for managing installed web applications
 * https://www.w3.org/TR/appmanifest/
 *
 * It is used to trigger the installation of a web application via .install()
 * and to access the manifest data (including icons).
 *
 * TODO:
 *  - Trigger appropriate app installed events
 */

"use strict";

const Ci = Components.interfaces;
const Cu = Components.utils;
const Cc = Components.classes;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const { ManifestObtainer } =
  Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
const { ManifestIcons } =
  Cu.import("resource://gre/modules/ManifestIcons.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");

/**
 * Generates an hash for the given string.
 *
 * @note The generated hash is returned in base64 form.  Mind the fact base64
 * is case-sensitive if you are going to reuse this code.
 */
function generateHash(aString) {
  const cryptoHash = Cc["@mozilla.org/security/hash;1"]
    .createInstance(Ci.nsICryptoHash);
  cryptoHash.init(Ci.nsICryptoHash.MD5);
  const stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
    .createInstance(Ci.nsIStringInputStream);
  stringStream.data = aString;
  cryptoHash.updateFromStream(stringStream, -1);
  // base64 allows the '/' char, but we can't use it for filenames.
  return cryptoHash.finish(true).replace(/\//g, "-");
}

/**
 * Trims the query paramters from a url
 */
function stripQuery(url) {
  return url.split("?")[0];
}

// Folder in which we store the manifest files
const MANIFESTS_DIR = OS.Path.join(OS.Constants.Path.profileDir, "manifests");

// We maintain a list of scopes for installed webmanifests so we can determine
// whether a given url is within the scope of a previously installed manifest
const MANIFESTS_FILE = "manifest-scopes.json";

/**
 * Manifest object
 */

class Manifest {

  constructor(browser, manifestUrl) {
    this._manifestUrl = manifestUrl;
    // The key for this is the manifests URL that is required to be unique.
    // However arbitrary urls are not safe file paths so lets hash it.
    const fileName = generateHash(manifestUrl) + ".json";
    this._path = OS.Path.join(MANIFESTS_DIR, fileName);
    this._browser = browser;
  }

  async initialise() {
    this._store = new JSONFile({path: this._path});
    await this._store.load();
  }

  async install() {
    const manifestData = await ManifestObtainer.browserObtainManifest(this._browser);
    this._store.data = {
      installed: true,
      manifest: manifestData
    };
    Manifests.manifestInstalled(this);
    this._store.saveSoon();
  }

  async icon(expectedSize) {
    if ('cached_icon' in this._store.data) {
      return this._store.data.cached_icon;
    }
    const icon = await ManifestIcons
      .browserFetchIcon(this._browser, this._store.data.manifest, expectedSize);
    // Cache the icon so future requests do not go over the network
    this._store.data.cached_icon = icon;
    this._store.saveSoon();
    return icon;
  }

  get scope() {
    const scope = this._store.data.manifest.scope ||
      this._store.data.manifest.start_url;
    return stripQuery(scope);
  }

  get name() {
    return this._store.data.manifest.short_name ||
      this._store.data.manifest.short_url;
  }

  get url() {
    return this._manifestUrl;
  }

  get installed() {
    return this._store.data && this._store.data.installed || false;
  }

  get start_url() {
    return this._store.data.manifest.start_url;
  }

  get path() {
    return this._path;
  }
}

/*
 * Manifests maintains the list of installed manifests
 */
var Manifests = {

  async initialise () {

    if (this.started) {
      return this.started;
    }

    this.started = (async () => {

      // Make sure the manifests have the folder needed to save into
      await OS.File.makeDir(MANIFESTS_DIR, {ignoreExisting: true});

      // Ensure any existing scope data we have about manifests is loaded
      this._path = OS.Path.join(OS.Constants.Path.profileDir, MANIFESTS_FILE);
      this._store = new JSONFile({path: this._path});
      await this._store.load();

      // If we dont have any existing data, initialise empty
      if (!this._store.data.hasOwnProperty("scopes")) {
        this._store.data.scopes = new Map();
      }

      // Cache the Manifest objects creates as they are references to files
      // and we do not want multiple file handles
      this.manifestObjs = {};

    })();

    return this.started;
  },

  // When a manifest is installed, we save its scope so we can determine if
  // fiture visits fall within this manifests scope
  manifestInstalled(manifest) {
    this._store.data.scopes[manifest.scope] = manifest.url;
    this._store.saveSoon();
  },

  // Given a url, find if it is within an installed manifests scope and if so
  // return that manifests url
  findManifestUrl(url) {
    for (let scope in this._store.data.scopes) {
      if (url.startsWith(scope)) {
        return this._store.data.scopes[scope];
      }
    }
    return null;
  },

  // Get the manifest given a url, or if not look for a manifest that is
  // tied to the current page
  async getManifest(browser, manifestUrl) {

    // Ensure we have all started up
    await this.initialise();

    // If the client does not already know its manifestUrl, we take the
    // url of the client and see if it matches the scope of any installed
    // manifests
    if (!manifestUrl) {
      const url = stripQuery(browser.currentURI.spec);
      manifestUrl = this.findManifestUrl(url);
    }

    // No matches so no manifest
    if (manifestUrl === null) {
      return null;
    }

    // If we have already created this manifest return cached
    if (manifestUrl in this.manifestObjs) {
      return this.manifestObjs[manifestUrl];
    }

    // Otherwise create a new manifest object
    this.manifestObjs[manifestUrl] = new Manifest(browser, manifestUrl);
    await this.manifestObjs[manifestUrl].initialise();
    return this.manifestObjs[manifestUrl];
  }

};

this.EXPORTED_SYMBOLS = ["Manifests"]; // jshint ignore:line
PK
!<:\modules/ManifestFinder.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/* globals Components, Task, PromiseMessage */
"use strict";
const {
  utils: Cu
} = Components;
Cu.import("resource://gre/modules/PromiseMessage.jsm");

this.ManifestFinder = {// jshint ignore:line
  /**
  * Check from content process if DOM Window has a conforming
  * manifest link relationship.
  * @param aContent DOM Window to check.
  * @return {Promise<Boolean>}
  */
  contentHasManifestLink(aContent) {
    if (!aContent || isXULBrowser(aContent)) {
      throw new TypeError("Invalid input.");
    }
    return checkForManifest(aContent);
  },

  /**
  * Check from a XUL browser (parent process) if it's content document has a
  * manifest link relationship.
  * @param aBrowser The XUL browser to check.
  * @return {Promise}
  */
  async browserHasManifestLink(aBrowser) {
      if (!isXULBrowser(aBrowser)) {
        throw new TypeError("Invalid input.");
      }
      const msgKey = "DOM:WebManifest:hasManifestLink";
      const mm = aBrowser.messageManager;
      const reply = await PromiseMessage.send(mm, msgKey);
      return reply.data.result;
    }
};

function isXULBrowser(aBrowser) {
  if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
    return false;
  }
  const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  return (aBrowser.namespaceURI === XUL && aBrowser.localName === "browser");
}

function checkForManifest(aWindow) {
  // Only top-level browsing contexts are valid.
  if (!aWindow || aWindow.top !== aWindow) {
    return false;
  }
  const elem = aWindow.document.querySelector("link[rel~='manifest']");
  // Only if we have an element and a non-empty href attribute.
  if (!elem || !elem.getAttribute("href")) {
    return false;
  }
  return true;
}

this.EXPORTED_SYMBOLS = [// jshint ignore:line
  "ManifestFinder"
];
PK
!<u?

modules/ManifestIcons.jsm"use strict";

const {
  utils: Cu,
  classes: Cc,
  interfaces: Ci
} = Components;

Cu.import("resource://gre/modules/PromiseMessage.jsm");

this.ManifestIcons = {

  async browserFetchIcon(aBrowser, manifest, iconSize) {
    const msgKey = "DOM:WebManifest:fetchIcon";
    const mm = aBrowser.messageManager;
    const {data: {success, result}} =
      await PromiseMessage.send(mm, msgKey, {manifest, iconSize});
    if (!success) {
      throw result;
    }
    return result;
  },

  async contentFetchIcon(aWindow, manifest, iconSize) {
    return await getIcon(aWindow, toIconArray(manifest.icons), iconSize);
  }
};

function parseIconSize(size) {
  if (size === "any" || size === "") {
    // We want icons without size specified to sorted
    // as the largest available icons
    return Number.MAX_SAFE_INTEGER;
  }
  // 100x100 will parse as 100
  return parseInt(size, 10);
}

// Create an array of icons sorted by their size
function toIconArray(icons) {
  const iconBySize = [];
  icons.forEach(icon => {
    const sizes = ("sizes" in icon) ? icon.sizes : "";
    sizes.split(" ").forEach(size => {
      iconBySize.push({src: icon.src, size: parseIconSize(size)});
    });
  });
  return iconBySize.sort((a, b) => a.size - b.size);
}

async function getIcon(aWindow, icons, expectedSize) {
  if (!icons.length) {
    throw new Error("Could not find valid icon");
  }
  // We start trying the smallest icon that is larger than the requested
  // size and go up to the largest icon if they fail, if all those fail
  // go back down to the smallest
  let index = icons.findIndex(icon => icon.size >= expectedSize);
  if (index === -1) {
    index = icons.length - 1;
  }

  return fetchIcon(aWindow, icons[index].src).catch(err => {
    // Remove all icons with the failed source, the same source
    // may have been used for multiple sizes
    icons = icons.filter(x => x.src !== icons[index].src);
    return getIcon(aWindow, icons, expectedSize);
  });
}

async function fetchIcon(aWindow, src) {
  const iconURL = new aWindow.URL(src, aWindow.location);
  const request = new aWindow.Request(iconURL, {mode: "cors"});
  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
  return aWindow.fetch(request)
    .then(response => response.blob())
    .then(blob => new Promise((resolve, reject) => {
      var reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    }));
}

this.EXPORTED_SYMBOLS = ["ManifestIcons"]; // jshint ignore:line
PK
!<tmodules/ManifestObtainer.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 /*
 * ManifestObtainer is an implementation of:
 * http://w3c.github.io/manifest/#obtaining
 *
 * Exposes 2 public method:
 *
 *  .contentObtainManifest(aContent) - used in content process
 *  .browserObtainManifest(aBrowser) - used in browser/parent process
 *
 * both return a promise. If successful, you get back a manifest object.
 *
 * Import it with URL:
 *   'chrome://global/content/manifestMessages.js'
 *
 * e10s IPC message from this components are handled by:
 *   dom/ipc/manifestMessages.js
 *
 * Which is injected into every browser instance via browser.js.
 *
 * exported ManifestObtainer
 */
/*globals Components, Task, PromiseMessage, XPCOMUtils, ManifestProcessor, BrowserUtils*/
"use strict";
const {
  utils: Cu,
  classes: Cc,
  interfaces: Ci
} = Components;
Cu.import("resource://gre/modules/PromiseMessage.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ManifestProcessor.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",  // jshint ignore:line
  "resource://gre/modules/BrowserUtils.jsm");

this.ManifestObtainer = { // jshint ignore:line
  /**
  * Public interface for obtaining a web manifest from a XUL browser, to use
  * on the parent process.
  * @param  {XULBrowser} The browser to check for the manifest.
  * @return {Promise<Object>} The processed manifest.
  */
  async browserObtainManifest(aBrowser) {
    const msgKey = "DOM:ManifestObtainer:Obtain";
    if (!isXULBrowser(aBrowser)) {
      throw new TypeError("Invalid input. Expected XUL browser.");
    }
    const mm = aBrowser.messageManager;
    const {data: {success, result}} = await PromiseMessage.send(mm, msgKey);
    if (!success) {
      const error = toError(result);
      throw error;
    }
    return result;
  },
  /**
   * Public interface for obtaining a web manifest from a XUL browser.
   * @param  {Window} The content Window from which to extract the manifest.
   * @return {Promise<Object>} The processed manifest.
   */
  async contentObtainManifest(aContent) {
    if (!aContent || isXULBrowser(aContent)) {
      throw new TypeError("Invalid input. Expected a DOM Window.");
    }
    let manifest;
    try {
      manifest = await fetchManifest(aContent);
    } catch (err) {
      throw err;
    }
    return manifest;
  }
};

function toError(aErrorClone) {
  let error;
  switch (aErrorClone.name) {
  case "TypeError":
    error = new TypeError();
    break;
  default:
    error = new Error();
  }
  Object.getOwnPropertyNames(aErrorClone)
    .forEach(name => error[name] = aErrorClone[name]);
  return error;
}

function isXULBrowser(aBrowser) {
  if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
    return false;
  }
  const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  return (aBrowser.namespaceURI === XUL && aBrowser.localName === "browser");
}

/**
 * Asynchronously processes the result of response after having fetched
 * a manifest.
 * @param {Response} aResp Response from fetch().
 * @param {Window} aContentWindow The content window.
 * @return {Promise<Object>} The processed manifest.
 */
const processResponse = async function(aResp, aContentWindow) {
  const badStatus = aResp.status < 200 || aResp.status >= 300;
  if (aResp.type === "error" || badStatus) {
    const msg =
      `Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
    throw new Error(msg);
  }
  const text = await aResp.text();
  const args = {
    jsonText: text,
    manifestURL: aResp.url,
    docURL: aContentWindow.location.href
  };
  const manifest = ManifestProcessor.process(args);
  return manifest;
};

/**
 * Asynchronously fetches a web manifest.
 * @param {Window} a The content Window from where to extract the manifest.
 * @return {Promise<Object>}
 */
const fetchManifest = async function(aWindow) {
  if (!aWindow || aWindow.top !== aWindow) {
    let msg = "Window must be a top-level browsing context.";
    throw new Error(msg);
  }
  const elem = aWindow.document.querySelector("link[rel~='manifest']");
  if (!elem || !elem.getAttribute("href")) {
    let msg = `No manifest to fetch at ${aWindow.location}`;
    throw new Error(msg);
  }
  // Throws on malformed URLs
  const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
  const reqInit = {
    mode: "cors"
  };
  if (elem.crossOrigin === "use-credentials") {
    reqInit.credentials = "include";
  }
  const request = new aWindow.Request(manifestURL, reqInit);
  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
  let response;
  try {
    response = await aWindow.fetch(request);
  } catch (err) {
    throw err;
  }
  const manifest = await processResponse(response, aWindow);
  return manifest;
};

this.EXPORTED_SYMBOLS = ["ManifestObtainer"]; // jshint ignore:line
PK
!<
""modules/ManifestProcessor.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * ManifestProcessor
 * Implementation of processing algorithms from:
 * http://www.w3.org/2008/webapps/manifest/
 *
 * Creates manifest processor that lets you process a JSON file
 * or individual parts of a manifest object. A manifest is just a
 * standard JS object that has been cleaned up.
 *
 *   .process({jsonText,manifestURL,docURL});
 *
 * Depends on ImageObjectProcessor to process things like
 * icons and splash_screens.
 *
 * TODO: The constructor should accept the UA's supported orientations.
 * TODO: The constructor should accept the UA's supported display modes.
 * TODO: hook up developer tools to console. (1086997).
 */
/*globals Components, ValueExtractor, ImageObjectProcessor, ConsoleAPI*/
'use strict';
const {
  utils: Cu
} = Components;
Cu.importGlobalProperties(['URL']);
const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui',
  'browser'
]);
const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait',
  'portrait-primary', 'portrait-secondary', 'landscape-primary',
  'landscape-secondary'
]);
const textDirections = new Set(['ltr', 'rtl', 'auto']);

Cu.import('resource://gre/modules/Console.jsm');
Cu.import("resource://gre/modules/Services.jsm");
// ValueExtractor is used by the various processors to get values
// from the manifest and to report errors.
Cu.import('resource://gre/modules/ValueExtractor.jsm');
// ImageObjectProcessor is used to process things like icons and images
Cu.import('resource://gre/modules/ImageObjectProcessor.jsm');

this.ManifestProcessor = { // jshint ignore:line
  get defaultDisplayMode() {
    return 'browser';
  },
  get displayModes() {
    return displayModes;
  },
  get orientationTypes() {
    return orientationTypes;
  },
  get textDirections() {
    return textDirections;
  },
  // process() method processes JSON text into a clean manifest
  // that conforms with the W3C specification. Takes an object
  // expecting the following dictionary items:
  //  * jsonText: the JSON string to be processed.
  //  * manifestURL: the URL of the manifest, to resolve URLs.
  //  * docURL: the URL of the owner doc, for security checks
  process({
    jsonText,
    manifestURL: aManifestURL,
    docURL: aDocURL
  }) {
    const domBundle = Services.strings.createBundle("chrome://global/locale/dom/dom.properties");

    const console = new ConsoleAPI({
      prefix: 'Web Manifest'
    });
    const manifestURL = new URL(aManifestURL);
    const docURL = new URL(aDocURL);
    let rawManifest = {};
    try {
      rawManifest = JSON.parse(jsonText);
    } catch (e) {}
    if (typeof rawManifest !== 'object' || rawManifest === null) {
      console.warn(domBundle.GetStringFromName('ManifestShouldBeObject'));
      rawManifest = {};
    }
    const extractor = new ValueExtractor(console, domBundle);
    const imgObjProcessor = new ImageObjectProcessor(console, extractor);
    const processedManifest = {
      'dir': processDirMember.call(this),
      'lang': processLangMember(),
      'start_url': processStartURLMember(),
      'display': processDisplayMember.call(this),
      'orientation': processOrientationMember.call(this),
      'name': processNameMember(),
      'icons': imgObjProcessor.process(
        rawManifest, manifestURL, 'icons'
      ),
      'short_name': processShortNameMember(),
      'theme_color': processThemeColorMember(),
      'background_color': processBackgroundColorMember(),
    };
    processedManifest.scope = processScopeMember();
    return processedManifest;

    function processDirMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'dir',
        expectedType: 'string',
        trim: true,
      };
      const value = extractor.extractValue(spec);
      if (this.textDirections.has(value)) {
        return value;
      }
      return 'auto';
    }

    function processNameMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'name',
        expectedType: 'string',
        trim: true
      };
      return extractor.extractValue(spec);
    }

    function processShortNameMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'short_name',
        expectedType: 'string',
        trim: true
      };
      return extractor.extractValue(spec);
    }

    function processOrientationMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'orientation',
        expectedType: 'string',
        trim: true
      };
      const value = extractor.extractValue(spec);
      if (value && typeof value === "string" && this.orientationTypes.has(value.toLowerCase())) {
        return value.toLowerCase();
      }
      return undefined;
    }

    function processDisplayMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'display',
        expectedType: 'string',
        trim: true
      };
      const value = extractor.extractValue(spec);
      if (value && typeof value === "string" && displayModes.has(value.toLowerCase())) {
        return value.toLowerCase();
      }
      return this.defaultDisplayMode;
    }

    function processScopeMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'scope',
        expectedType: 'string',
        trim: false
      };
      let scopeURL;
      const startURL = new URL(processedManifest.start_url);
      const value = extractor.extractValue(spec);
      if (value === undefined || value === '') {
        return undefined;
      }
      try {
        scopeURL = new URL(value, manifestURL);
      } catch (e) {
        console.warn(domBundle.GetStringFromName('ManifestScopeURLInvalid'));
        return undefined;
      }
      if (scopeURL.origin !== docURL.origin) {
        console.warn(domBundle.GetStringFromName('ManifestScopeNotSameOrigin'));
        return undefined;
      }
      // If start URL is not within scope of scope URL:
      let isSameOrigin = startURL && startURL.origin !== scopeURL.origin;
      if (isSameOrigin || !startURL.pathname.startsWith(scopeURL.pathname)) {
        console.warn(domBundle.GetStringFromName('ManifestStartURLOutsideScope'));
        return undefined;
      }
      return scopeURL.href;
    }

    function processStartURLMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'start_url',
        expectedType: 'string',
        trim: false
      };
      let result = new URL(docURL).href;
      const value = extractor.extractValue(spec);
      if (value === undefined || value === '') {
        return result;
      }
      let potentialResult;
      try {
        potentialResult = new URL(value, manifestURL);
      } catch (e) {
        console.warn(domBundle.GetStringFromName('ManifestStartURLInvalid'))
        return result;
      }
      if (potentialResult.origin !== docURL.origin) {
        console.warn(domBundle.GetStringFromName('ManifestStartURLShouldBeSameOrigin'));
      } else {
        result = potentialResult.href;
      }
      return result;
    }

    function processThemeColorMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'theme_color',
        expectedType: 'string',
        trim: true
      };
      return extractor.extractColorValue(spec);
    }

    function processBackgroundColorMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'background_color',
        expectedType: 'string',
        trim: true
      };
      return extractor.extractColorValue(spec);
    }

    function processLangMember() {
      const spec = {
        objectName: 'manifest',
        object: rawManifest,
        property: 'lang',
        expectedType: 'string', trim: true
      };
      let tag = extractor.extractValue(spec);
      // TODO: Check if tag is structurally valid.
      //       Cannot do this because we don't support Intl API on Android.
      //       https://bugzilla.mozilla.org/show_bug.cgi?id=864843
      //       https://github.com/tc39/ecma402/issues/5
      // TODO: perform canonicalization on the tag.
      //       Can't do this today because there is no direct means to
      //       access canonicalization algorithms through Intl API.
      //       https://github.com/tc39/ecma402/issues/5
      return tag;
    }
  }
};
this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line
PK
!<z411modules/MatchPattern.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

this.EXPORTED_SYMBOLS = ["MatchPattern", "MatchGlobs", "MatchURLFilters"];

/* globals MatchPattern, MatchGlobs */

const PERMITTED_SCHEMES = ["http", "https", "ws", "wss", "file", "ftp", "data"];
const PERMITTED_SCHEMES_REGEXP = [...PERMITTED_SCHEMES, "moz-extension"].join("|");

// The basic RE for matching patterns
const PATTERN_REGEXP = new RegExp(`^(${PERMITTED_SCHEMES_REGEXP}|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$`);

// The schemes/protocols implied by a pattern that starts with *://
const WILDCARD_SCHEMES = ["http", "https"];

// This function converts a glob pattern (containing * and possibly ?
// as wildcards) to a regular expression.
function globToRegexp(pat, allowQuestion) {
  // Escape everything except ? and *.
  pat = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");

  if (allowQuestion) {
    pat = pat.replace(/\?/g, ".");
  } else {
    pat = pat.replace(/\?/g, "\\?");
  }
  pat = pat.replace(/\*/g, ".*");
  return new RegExp("^" + pat + "$");
}

// These patterns follow the syntax in
// https://developer.chrome.com/extensions/match_patterns
function SingleMatchPattern(pat) {
  this.pat = pat;
  if (pat == "<all_urls>") {
    this.schemes = PERMITTED_SCHEMES;
    this.hostMatch = () => true;
    this.pathMatch = () => true;
  } else if (!pat) {
    this.schemes = [];
  } else {
    let match = PATTERN_REGEXP.exec(pat);
    if (!match) {
      Cu.reportError(`Invalid match pattern: '${pat}'`);
      this.schemes = [];
      return;
    }

    if (match[1] == "*") {
      this.schemes = WILDCARD_SCHEMES;
    } else {
      this.schemes = [match[1]];
    }

    // We allow the host to be empty for file URLs.
    if (match[2] == "" && this.schemes[0] != "file") {
      Cu.reportError(`Invalid match pattern: '${pat}'`);
      this.schemes = [];
      return;
    }

    // We disallow the host to be * for moz-extension URLs.
    if (match[2] == "*" && this.schemes[0] == "moz-extension") {
      Cu.reportError(`Invalid match pattern: '${pat}'`);
      this.schemes = [];
      return;
    }

    this.host = match[2];
    this.hostMatch = this.getHostMatcher(match[2]);

    let pathMatch = globToRegexp(match[3], false);
    this.pathMatch = pathMatch.test.bind(pathMatch);
  }
}

SingleMatchPattern.prototype = {
  getHostMatcher(host) {
    // This code ignores the port, as Chrome does.
    if (host == "*") {
      return () => true;
    }
    if (host.startsWith("*.")) {
      let suffix = host.substr(2);
      let dotSuffix = "." + suffix;

      return ({host}) => host === suffix || host.endsWith(dotSuffix);
    }
    return uri => uri.host === host;
  },

  matches(uri, ignorePath = false) {
    return (
      this.schemes.includes(uri.scheme) &&
      this.hostMatch(uri) &&
      (ignorePath || (
        this.pathMatch(uri.cloneIgnoringRef().path)
      ))
    );
  },

  // Tests if this can possibly overlap with the |other| SingleMatchPattern.
  overlapsIgnoringPath(other) {
    return this.schemes.some(scheme => other.schemes.includes(scheme)) &&
           (this.hostMatch(other) || other.hostMatch(this));
  },

  get pattern() { return this.pat; },
};

this.MatchPattern = function(pat) {
  this.pat = pat;
  if (!pat) {
    this.matchers = [];
  } else if (pat instanceof String || typeof(pat) == "string") {
    this.matchers = [new SingleMatchPattern(pat)];
  } else {
    this.matchers = pat.map(p => new SingleMatchPattern(p));
  }

  XPCOMUtils.defineLazyGetter(this, "explicitMatchers", () => {
    return this.matchers.filter(matcher => matcher.pat != "<all_urls>" &&
                                           matcher.host &&
                                           !matcher.host.startsWith("*"));
  });
};

MatchPattern.prototype = {
  // |uri| should be an nsIURI.
  matches(uri) {
    return this.matchers.some(matcher => matcher.matches(uri));
  },

  get patterns() { return this.matchers; },

  matchesIgnoringPath(uri, explicit = false) {
    if (explicit) {
      return this.explicitMatchers.some(matcher => matcher.matches(uri, true));
    }
    return this.matchers.some(matcher => matcher.matches(uri, true));
  },

  // Checks that this match pattern grants access to read the given
  // cookie. |cookie| should be an |nsICookie2| instance.
  matchesCookie(cookie) {
    // First check for simple matches.
    let secureURI = NetUtil.newURI(`https://${cookie.rawHost}/`);
    if (this.matchesIgnoringPath(secureURI)) {
      return true;
    }

    let plainURI = NetUtil.newURI(`http://${cookie.rawHost}/`);
    if (!cookie.isSecure && this.matchesIgnoringPath(plainURI)) {
      return true;
    }

    if (!cookie.isDomain) {
      return false;
    }

    // Things get tricker for domain cookies. The extension needs to be able
    // to read any cookies that could be read any host it has permissions
    // for. This means that our normal host matching checks won't work,
    // since the pattern "*://*.foo.example.com/" doesn't match ".example.com",
    // but it does match "bar.foo.example.com", which can read cookies
    // with the domain ".example.com".
    //
    // So, instead, we need to manually check our filters, and accept any
    // with hosts that end with our cookie's host.

    let {host, isSecure} = cookie;

    for (let matcher of this.matchers) {
      let schemes = matcher.schemes;
      if (schemes.includes("https") || (!isSecure && schemes.includes("http"))) {
        if (matcher.host.endsWith(host)) {
          return true;
        }
      }
    }

    return false;
  },

  // Checks if every part of this filter overlaps with
  // some of the |hosts| or |optional| permissions MatchPatterns.
  overlapsPermissions(hosts, optional) {
    const perms = hosts.matchers.concat(optional.matchers);
    return this.matchers.length &&
           this.matchers.every(m => perms.some(p => p.overlapsIgnoringPath(m)));
  },

  // Test if this MatchPattern subsumes the given pattern (i.e., whether
  // this pattern matches everything the given pattern does).
  // Note, this method considers only to protocols and hosts/domains,
  // paths are ignored.
  subsumes(pattern) {
    let match = PATTERN_REGEXP.exec(pattern);
    if (!match) {
      throw new Error("Invalid match pattern");
    }

    if (match[1] == "*") {
      return WILDCARD_SCHEMES.every(scheme => this.matchesIgnoringPath({scheme, host: match[2]}));
    }

    return this.matchesIgnoringPath({scheme: match[1], host: match[2]});
  },

  serialize() {
    return this.pat;
  },

  removeOne(pattern) {
    if (!Array.isArray(this.pat)) {
      return;
    }

    let index = this.pat.indexOf(pattern);
    if (index >= 0) {
      if (this.matchers[index].pat != pattern) {
        throw new Error("pat/matcher mismatch in removeOne()");
      }
      this.pat.splice(index, 1);
      this.matchers.splice(index, 1);
    }
  },
};

// Globs can match everything. Be careful, this DOES NOT filter by allowed schemes!
this.MatchGlobs = function(globs) {
  this.original = globs;
  if (globs) {
    this.regexps = Array.from(globs, (glob) => globToRegexp(glob, true));
  } else {
    this.regexps = [];
  }
};

MatchGlobs.prototype = {
  matches(str) {
    return this.regexps.some(regexp => regexp.test(str));
  },
  serialize() {
    return this.original;
  },
};

// Match WebNavigation URL Filters.
this.MatchURLFilters = function(filters) {
  if (!Array.isArray(filters)) {
    throw new TypeError("filters should be an array");
  }

  if (filters.length == 0) {
    throw new Error("filters array should not be empty");
  }

  this.filters = filters;
};

MatchURLFilters.prototype = {
  matches(url) {
    let uri = NetUtil.newURI(url);
    // Set uriURL to an empty object (needed because some schemes, e.g. about doesn't support nsIURL).
    let uriURL = {};
    if (uri instanceof Ci.nsIURL) {
      uriURL = uri;
    }

    // Set host to a empty string by default (needed so that schemes without an host,
    // e.g. about, can pass an empty string for host based event filtering as expected).
    let host = "";
    try {
      host = uri.host;
    } catch (e) {
      // 'uri.host' throws an exception with some uri schemes (e.g. about).
    }

    let port;
    try {
      port = uri.port;
    } catch (e) {
      // 'uri.port' throws an exception with some uri schemes (e.g. about),
      // in which case it will be |undefined|.
    }

    let data = {
      // NOTE: This properties are named after the name of their related
      // filters (e.g. `pathContains/pathEquals/...` will be tested against the
      // `data.path` property, and the same is done for the `host`, `query` and `url`
      // components as well).
      path: uriURL.filePath,
      query: uriURL.query,
      host,
      port,
      url,
    };

    // If any of the filters matches, matches returns true.
    return this.filters.some(filter => this.matchURLFilter({filter, data, uri, uriURL}));
  },

  matchURLFilter({filter, data, uri, uriURL}) {
    // Test for scheme based filtering.
    if (filter.schemes) {
      // Return false if none of the schemes matches.
      if (!filter.schemes.some((scheme) => uri.schemeIs(scheme))) {
        return false;
      }
    }

    // Test for exact port matching or included in a range of ports.
    if (filter.ports) {
      let port = data.port;
      if (port === -1) {
        // NOTE: currently defaultPort for "resource" and "chrome" schemes defaults to -1,
        // for "about", "data" and "javascript" schemes defaults to undefined.
        if (["resource", "chrome"].includes(uri.scheme)) {
          port = undefined;
        } else {
          port = Services.io.getProtocolHandler(uri.scheme).defaultPort;
        }
      }

      // Return false if none of the ports (or port ranges) is verified
      return filter.ports.some((filterPort) => {
        if (Array.isArray(filterPort)) {
          let [lower, upper] = filterPort;
          return port >= lower && port <= upper;
        }

        return port === filterPort;
      });
    }

    // Filters on host, url, path, query:
    // hostContains, hostEquals, hostSuffix, hostPrefix,
    // urlContains, urlEquals, ...
    for (let urlComponent of ["host", "path", "query", "url"]) {
      if (!this.testMatchOnURLComponent({urlComponent, data, filter})) {
        return false;
      }
    }

    // urlMatches is a regular expression string and it is tested for matches
    // on the "url without the ref".
    if (filter.urlMatches) {
      let urlWithoutRef = uri.specIgnoringRef;
      if (!urlWithoutRef.match(filter.urlMatches)) {
        return false;
      }
    }

    // originAndPathMatches is a regular expression string and it is tested for matches
    // on the "url without the query and the ref".
    if (filter.originAndPathMatches) {
      let urlWithoutQueryAndRef = uri.resolve(uriURL.filePath);
      // The above 'uri.resolve(...)' will be null for some URI schemes
      // (e.g. about).
      // TODO: handle schemes which will not be able to resolve the filePath
      // (e.g. for "about:blank", 'urlWithoutQueryAndRef' should be "about:blank" instead
      // of null)
      if (!urlWithoutQueryAndRef ||
          !urlWithoutQueryAndRef.match(filter.originAndPathMatches)) {
        return false;
      }
    }

    return true;
  },

  testMatchOnURLComponent({urlComponent: key, data, filter}) {
    // Test for equals.
    // NOTE: an empty string should not be considered a filter to skip.
    if (filter[`${key}Equals`] != null) {
      if (data[key] !== filter[`${key}Equals`]) {
        return false;
      }
    }

    // Test for contains.
    if (filter[`${key}Contains`]) {
      let value = (key == "host" ? "." : "") + data[key];
      if (!data[key] || !value.includes(filter[`${key}Contains`])) {
        return false;
      }
    }

    // Test for prefix.
    if (filter[`${key}Prefix`]) {
      if (!data[key] || !data[key].startsWith(filter[`${key}Prefix`])) {
        return false;
      }
    }

    // Test for suffix.
    if (filter[`${key}Suffix`]) {
      if (!data[key] || !data[key].endsWith(filter[`${key}Suffix`])) {
        return false;
      }
    }

    return true;
  },

  serialize() {
    return this.filters;
  },
};
PK
!<\O
O
modules/Memory.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Memory"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
// How long we should wait for the Promise to resolve.
const TIMEOUT_INTERVAL = 2000;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");

this.Memory = {
  /**
   * This function returns a Promise that resolves with an Object that
   * describes basic memory usage for each content process and the parent
   * process.
   * @returns Promise
   * @resolves JS Object
   * An Object in the following format:
   * {
   *   "parent": {
   *     uss: <int>,
   *     rss: <int>,
   *   },
   *   <pid>: {
   *     uss: <int>,
   *     rss: <int>,
   *   },
   *   ...
   * }
   */
  summary() {
    if (!this._pendingPromise) {
      this._pendingPromise = new Promise((resolve) => {
        this._pendingResolve = resolve;
        this._summaries = {};
        Services.ppmm.broadcastAsyncMessage("Memory:GetSummary");
        Services.ppmm.addMessageListener("Memory:Summary", this);
        this._pendingTimeout = setTimeout(() => { this.finish(); }, TIMEOUT_INTERVAL);
      });
    }
    return this._pendingPromise;
  },

  receiveMessage(msg) {
    if (msg.name != "Memory:Summary" || !this._pendingResolve) {
      return;
    }
    this._summaries[msg.data.pid] = msg.data.summary;
    // Now we check if we are done for all content processes.
    // Services.ppmm.childCount is a count of how many processes currently
    // exist that might respond to messages sent through the ppmm, including
    // the parent process. So we subtract the parent process with the "- 1",
    // and that’s how many content processes we’re waiting for.
    if (Object.keys(this._summaries).length >= Services.ppmm.childCount - 1) {
      this.finish();
    }
  },

  finish() {
    // Code to gather the USS and RSS values for the parent process. This
    // functions the same way as in process-content.js.
    let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                   .getService(Ci.nsIMemoryReporterManager);
    let rss = memMgr.resident;
    let uss = memMgr.residentUnique;
    this._summaries["Parent"] = { uss, rss };
    this._pendingResolve(this._summaries);
    this._pendingResolve = null;
    this._summaries = null;
    this._pendingPromise = null;
    clearTimeout(this._pendingTimeout);
    Services.ppmm.removeMessageListener("Memory:Summary", this);
  }
};
PK
!<!>nnmodules/MessageChannel.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * This module provides wrappers around standard message managers to
 * simplify bidirectional communication. It currently allows a caller to
 * send a message to a single listener, and receive a reply. If there
 * are no matching listeners, or the message manager disconnects before
 * a reply is received, the caller is returned an error.
 *
 * The listener end may specify filters for the messages it wishes to
 * receive, and the sender end likewise may specify recipient tags to
 * match the filters.
 *
 * The message handler on the listener side may return its response
 * value directly, or may return a promise, the resolution or rejection
 * of which will be returned instead. The sender end likewise receives a
 * promise which resolves or rejects to the listener's response.
 *
 *
 * A basic setup works something like this:
 *
 * A content script adds a message listener to its global
 * nsIContentFrameMessageManager, with an appropriate set of filters:
 *
 *  {
 *    init(messageManager, window, extensionID) {
 *      this.window = window;
 *
 *      MessageChannel.addListener(
 *        messageManager, "ContentScript:TouchContent",
 *        this);
 *
 *      this.messageFilterStrict = {
 *        innerWindowID: getInnerWindowID(window),
 *        extensionID: extensionID,
 *      };
 *
 *      this.messageFilterPermissive = {
 *        outerWindowID: getOuterWindowID(window),
 *      };
 *    },
 *
 *    receiveMessage({ target, messageName, sender, recipient, data }) {
 *      if (messageName == "ContentScript:TouchContent") {
 *        return new Promise(resolve => {
 *          this.touchWindow(data.touchWith, result => {
 *            resolve({ touchResult: result });
 *          });
 *        });
 *      }
 *    },
 *  };
 *
 * A script in the parent process sends a message to the content process
 * via a tab message manager, including recipient tags to match its
 * filter, and an optional sender tag to identify itself:
 *
 *  let data = { touchWith: "pencil" };
 *  let sender = { extensionID, contextID };
 *  let recipient = { innerWindowID: tab.linkedBrowser.innerWindowID, extensionID };
 *
 *  MessageChannel.sendMessage(
 *    tab.linkedBrowser.messageManager, "ContentScript:TouchContent",
 *    data, {recipient, sender}
 *  ).then(result => {
 *    alert(result.touchResult);
 *  });
 *
 * Since the lifetimes of message senders and receivers may not always
 * match, either side of the message channel may cancel pending
 * responses which match its sender or recipient tags.
 *
 * For the above client, this might be done from an
 * inner-window-destroyed observer, when its target scope is destroyed:
 *
 *  observe(subject, topic, data) {
 *    if (topic == "inner-window-destroyed") {
 *      let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
 *
 *      MessageChannel.abortResponses({ innerWindowID });
 *    }
 *  },
 *
 * From the parent, it may be done when its context is being destroyed:
 *
 *  onDestroy() {
 *    MessageChannel.abortResponses({
 *      extensionID: this.extensionID,
 *      contextID: this.contextID,
 *    });
 *  },
 *
 */

this.EXPORTED_SYMBOLS = ["MessageChannel"];

/* globals MessageChannel */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                  "resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "MessageManagerProxy",
                            () => ExtensionUtils.MessageManagerProxy);

/**
 * Handles the mapping and dispatching of messages to their registered
 * handlers. There is one broker per message manager and class of
 * messages. Each class of messages is mapped to one native message
 * name, e.g., "MessageChannel:Message", and is dispatched to handlers
 * based on an internal message name, e.g., "Extension:ExecuteScript".
 */
class FilteringMessageManager {
  /**
   * @param {string} messageName
   *     The name of the native message this broker listens for.
   * @param {function} callback
   *     A function which is called for each message after it has been
   *     mapped to its handler. The function receives two arguments:
   *
   *       result:
   *         An object containing either a `handler` or an `error` property.
   *         If no error occurs, `handler` will be a matching handler that
   *         was registered by `addHandler`. Otherwise, the `error` property
   *         will contain an object describing the error.
   *
   *        data:
   *          An object describing the message, as defined in
   *          `MessageChannel.addListener`.
   * @param {nsIMessageListenerManager} messageManager
   */
  constructor(messageName, callback, messageManager) {
    this.messageName = messageName;
    this.callback = callback;
    this.messageManager = messageManager;

    this.messageManager.addMessageListener(this.messageName, this, true);

    this.handlers = new Map();
  }

  /**
   * Receives a message from our message manager, maps it to a handler, and
   * passes the result to our message callback.
   */
  receiveMessage({data, target}) {
    let handlers = Array.from(this.getHandlers(data.messageName, data.sender || null, data.recipient));

    data.target = target;
    this.callback(handlers, data);
  }

  /**
   * Iterates over all handlers for the given message name. If `recipient`
   * is provided, only iterates over handlers whose filters match it.
   *
   * @param {string|number} messageName
   *     The message for which to return handlers.
   * @param {object} sender
   *     The sender data on which to filter handlers.
   * @param {object} recipient
   *     The recipient data on which to filter handlers.
   */
  * getHandlers(messageName, sender, recipient) {
    let handlers = this.handlers.get(messageName) || new Set();
    for (let handler of handlers) {
      if (MessageChannel.matchesFilter(handler.messageFilterStrict || {}, recipient) &&
          MessageChannel.matchesFilter(handler.messageFilterPermissive || {}, recipient, false) &&
          (!handler.filterMessage || handler.filterMessage(sender, recipient))) {
        yield handler;
      }
    }
  }

  /**
   * Registers a handler for the given message.
   *
   * @param {string} messageName
   *     The internal message name for which to register the handler.
   * @param {object} handler
   *     An opaque handler object. The object may have a
   *     `messageFilterStrict` and/or a `messageFilterPermissive`
   *     property and/or a `filterMessage` method on which to filter messages.
   *
   *     Final dispatching is handled by the message callback passed to
   *     the constructor.
   */
  addHandler(messageName, handler) {
    if (!this.handlers.has(messageName)) {
      this.handlers.set(messageName, new Set());
    }

    this.handlers.get(messageName).add(handler);
  }

  /**
   * Unregisters a handler for the given message.
   *
   * @param {string} messageName
   *     The internal message name for which to unregister the handler.
   * @param {object} handler
   *     The handler object to unregister.
   */
  removeHandler(messageName, handler) {
    if (this.handlers.has(messageName)) {
      this.handlers.get(messageName).delete(handler);
    }
  }
}

/**
 * Manages mappings of message managers to their corresponding message
 * brokers. Brokers are lazily created for each message manager the
 * first time they are accessed. In the case of content frame message
 * managers, they are also automatically destroyed when the frame
 * unload event fires.
 */
class FilteringMessageManagerMap extends Map {
  // Unfortunately, we can't use a WeakMap for this, because message
  // managers do not support preserved wrappers.

  /**
   * @param {string} messageName
   *     The native message name passed to `FilteringMessageManager` constructors.
   * @param {function} callback
   *     The message callback function passed to
   *     `FilteringMessageManager` constructors.
   */
  constructor(messageName, callback) {
    super();

    this.messageName = messageName;
    this.callback = callback;
  }

  /**
   * Returns, and possibly creates, a message broker for the given
   * message manager.
   *
   * @param {nsIMessageListenerManager} target
   *     The message manager for which to return a broker.
   *
   * @returns {FilteringMessageManager}
   */
  get(target) {
    if (this.has(target)) {
      return super.get(target);
    }

    let broker = new FilteringMessageManager(this.messageName, this.callback, target);
    this.set(target, broker);

    if (target instanceof Ci.nsIDOMEventTarget) {
      let onUnload = event => {
        target.removeEventListener("unload", onUnload);
        this.delete(target);
      };
      target.addEventListener("unload", onUnload);
    }

    return broker;
  }
}

const MESSAGE_MESSAGE = "MessageChannel:Message";
const MESSAGE_RESPONSE = "MessageChannel:Response";

this.MessageChannel = {
  init() {
    Services.obs.addObserver(this, "message-manager-close");
    Services.obs.addObserver(this, "message-manager-disconnect");

    this.messageManagers = new FilteringMessageManagerMap(
      MESSAGE_MESSAGE, this._handleMessage.bind(this));

    this.responseManagers = new FilteringMessageManagerMap(
      MESSAGE_RESPONSE, this._handleResponse.bind(this));

    /**
     * Contains a list of pending responses, either waiting to be
     * received or waiting to be sent. @see _addPendingResponse
     */
    this.pendingResponses = new Set();

    /**
     * Contains the message name of a limited number of aborted response
     * handlers, the responses for which will be ignored.
     */
    this.abortedResponses = new ExtensionUtils.LimitedSet(30);
  },

  RESULT_SUCCESS: 0,
  RESULT_DISCONNECTED: 1,
  RESULT_NO_HANDLER: 2,
  RESULT_MULTIPLE_HANDLERS: 3,
  RESULT_ERROR: 4,
  RESULT_NO_RESPONSE: 5,

  REASON_DISCONNECTED: {
    result: 1, // this.RESULT_DISCONNECTED
    message: "Message manager disconnected",
  },

  /**
   * Specifies that only a single listener matching the specified
   * recipient tag may be listening for the given message, at the other
   * end of the target message manager.
   *
   * If no matching listeners exist, a RESULT_NO_HANDLER error will be
   * returned. If multiple matching listeners exist, a
   * RESULT_MULTIPLE_HANDLERS error will be returned.
   */
  RESPONSE_SINGLE: 0,

  /**
   * If multiple message managers matching the specified recipient tag
   * are listening for a message, all listeners are notified, but only
   * the first response or error is returned.
   *
   * Only handlers which return a value other than `undefined` are
   * considered to have responded. Returning a Promise which evaluates
   * to `undefined` is interpreted as an explicit response.
   *
   * If no matching listeners exist, a RESULT_NO_HANDLER error will be
   * returned. If no listeners return a response, a RESULT_NO_RESPONSE
   * error will be returned.
   */
  RESPONSE_FIRST: 1,

  /**
   * If multiple message managers matching the specified recipient tag
   * are listening for a message, all listeners are notified, and all
   * responses are returned as an array, once all listeners have
   * replied.
   */
  RESPONSE_ALL: 2,

  /**
   * Fire-and-forget: The sender of this message does not expect a reply.
   */
  RESPONSE_NONE: 3,

  /**
   * Initializes message handlers for the given message managers if needed.
   *
   * @param {Array<nsIMessageListenerManager>} messageManagers
   */
  setupMessageManagers(messageManagers) {
    for (let mm of messageManagers) {
      // This call initializes a FilteringMessageManager for |mm| if needed.
      // The FilteringMessageManager must be created to make sure that senders
      // of messages that expect a reply, such as MessageChannel:Message, do
      // actually receive a default reply even if there are no explicit message
      // handlers.
      this.messageManagers.get(mm);
    }
  },

  /**
   * Returns true if the properties of the `data` object match those in
   * the `filter` object. Matching is done on a strict equality basis,
   * and the behavior varies depending on the value of the `strict`
   * parameter.
   *
   * @param {object} filter
   *    The filter object to match against.
   * @param {object} data
   *    The data object being matched.
   * @param {boolean} [strict=true]
   *    If true, all properties in the `filter` object have a
   *    corresponding property in `data` with the same value. If
   *    false, properties present in both objects must have the same
   *    value.
   * @returns {boolean} True if the objects match.
   */
  matchesFilter(filter, data, strict = true) {
    if (strict) {
      return Object.keys(filter).every(key => {
        return key in data && data[key] === filter[key];
      });
    }
    return Object.keys(filter).every(key => {
      return !(key in data) || data[key] === filter[key];
    });
  },

  /**
   * Adds a message listener to the given message manager.
   *
   * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
   *    The message managers on which to listen.
   * @param {string|number} messageName
   *    The name of the message to listen for.
   * @param {MessageReceiver} handler
   *    The handler to dispatch to. Must be an object with the following
   *    properties:
   *
   *      receiveMessage:
   *        A method which is called for each message received by the
   *        listener. The method takes one argument, an object, with the
   *        following properties:
   *
   *          messageName:
   *            The internal message name, as passed to `sendMessage`.
   *
   *          target:
   *            The message manager which received this message.
   *
   *          channelId:
   *            The internal ID of the transaction, used to map responses to
   *            the original sender.
   *
   *          sender:
   *            An object describing the sender, as passed to `sendMessage`.
   *
   *          recipient:
   *            An object describing the recipient, as passed to
   *            `sendMessage`.
   *
   *          data:
   *            The contents of the message, as passed to `sendMessage`.
   *
   *        The method may return any structured-clone-compatible
   *        object, which will be returned as a response to the message
   *        sender. It may also instead return a `Promise`, the
   *        resolution or rejection value of which will likewise be
   *        returned to the message sender.
   *
   *      messageFilterStrict:
   *        An object containing arbitrary properties on which to filter
   *        received messages. Messages will only be dispatched to this
   *        object if the `recipient` object passed to `sendMessage`
   *        matches this filter, as determined by `matchesFilter` with
   *        `strict=true`.
   *
   *      messageFilterPermissive:
   *        An object containing arbitrary properties on which to filter
   *        received messages. Messages will only be dispatched to this
   *        object if the `recipient` object passed to `sendMessage`
   *        matches this filter, as determined by `matchesFilter` with
   *        `strict=false`.
   *
   *      filterMessage:
   *        An optional function that prevents the handler from handling a
   *        message by returning `false`. See `getHandlers` for the parameters.
   */
  addListener(targets, messageName, handler) {
    if (!Array.isArray(targets)) {
      targets = [targets];
    }
    for (let target of targets) {
      this.messageManagers.get(target).addHandler(messageName, handler);
    }
  },

  /**
   * Removes a message listener from the given message manager.
   *
   * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
   *    The message managers on which to stop listening.
   * @param {string|number} messageName
   *    The name of the message to stop listening for.
   * @param {MessageReceiver} handler
   *    The handler to stop dispatching to.
   */
  removeListener(targets, messageName, handler) {
    if (!Array.isArray(targets)) {
      targets = [targets];
    }
    for (let target of targets) {
      if (this.messageManagers.has(target)) {
        this.messageManagers.get(target).removeHandler(messageName, handler);
      }
    }
  },

  /**
   * Sends a message via the given message manager. Returns a promise which
   * resolves or rejects with the return value of the message receiver.
   *
   * The promise also rejects if there is no matching listener, or the other
   * side of the message manager disconnects before the response is received.
   *
   * @param {nsIMessageSender} target
   *    The message manager on which to send the message.
   * @param {string} messageName
   *    The name of the message to send, as passed to `addListener`.
   * @param {object} data
   *    A structured-clone-compatible object to send to the message
   *    recipient.
   * @param {object} [options]
   *    An object containing any of the following properties:
   * @param {object} [options.recipient]
   *    A structured-clone-compatible object to identify the message
   *    recipient. The object must match the `messageFilterStrict` and
   *    `messageFilterPermissive` filters defined by recipients in order
   *    for the message to be received.
   * @param {object} [options.sender]
   *    A structured-clone-compatible object to identify the message
   *    sender. This object may also be used to avoid delivering the
   *    message to the sender, and as a filter to prematurely
   *    abort responses when the sender is being destroyed.
   *    @see `abortResponses`.
   * @param {integer} [options.responseType=RESPONSE_SINGLE]
   *    Specifies the type of response expected. See the `RESPONSE_*`
   *    contents for details.
   * @returns {Promise}
   */
  sendMessage(target, messageName, data, options = {}) {
    let sender = options.sender || {};
    let recipient = options.recipient || {};
    let responseType = options.responseType || this.RESPONSE_SINGLE;

    let channelId = ExtensionUtils.getUniqueId();
    let message = {messageName, channelId, sender, recipient, data, responseType};

    if (responseType == this.RESPONSE_NONE) {
      try {
        target.sendAsyncMessage(MESSAGE_MESSAGE, message);
      } catch (e) {
        // Caller is not expecting a reply, so dump the error to the console.
        Cu.reportError(e);
        return Promise.reject(e);
      }
      return Promise.resolve();  // Not expecting any reply.
    }

    let deferred = PromiseUtils.defer();
    deferred.sender = recipient;
    deferred.messageManager = target;
    deferred.channelId = channelId;

    this._addPendingResponse(deferred);

    // The channel ID is used as the message name when routing responses.
    // Add a message listener to the response broker, and remove it once
    // we've gotten (or canceled) a response.
    let broker = this.responseManagers.get(target);
    broker.addHandler(channelId, deferred);

    let cleanup = () => {
      broker.removeHandler(channelId, deferred);
    };
    deferred.promise.then(cleanup, cleanup);

    try {
      target.sendAsyncMessage(MESSAGE_MESSAGE, message);
    } catch (e) {
      deferred.reject(e);
    }
    return deferred.promise;
  },

  _callHandlers(handlers, data) {
    let responseType = data.responseType;

    // At least one handler is required for all response types but
    // RESPONSE_ALL.
    if (handlers.length == 0 && responseType != this.RESPONSE_ALL) {
      return Promise.reject({result: MessageChannel.RESULT_NO_HANDLER,
                             message: "No matching message handler"});
    }

    if (responseType == this.RESPONSE_SINGLE) {
      if (handlers.length > 1) {
        return Promise.reject({result: MessageChannel.RESULT_MULTIPLE_HANDLERS,
                               message: `Multiple matching handlers for ${data.messageName}`});
      }

      // Note: We use `new Promise` rather than `Promise.resolve` here
      // so that errors from the handler are trapped and converted into
      // rejected promises.
      return new Promise(resolve => {
        resolve(handlers[0].receiveMessage(data));
      });
    }

    let responses = handlers.map(handler => {
      try {
        return handler.receiveMessage(data);
      } catch (e) {
        return Promise.reject(e);
      }
    });
    responses = responses.filter(response => response !== undefined);

    switch (responseType) {
      case this.RESPONSE_FIRST:
        if (responses.length == 0) {
          return Promise.reject({result: MessageChannel.RESULT_NO_RESPONSE,
                                 message: "No handler returned a response"});
        }

        return Promise.race(responses);

      case this.RESPONSE_ALL:
        return Promise.all(responses);
    }
    return Promise.reject({message: "Invalid response type"});
  },

  /**
   * Handles dispatching message callbacks from the message brokers to their
   * appropriate `MessageReceivers`, and routing the responses back to the
   * original senders.
   *
   * Each handler object is a `MessageReceiver` object as passed to
   * `addListener`.
   *
   * @param {Array<MessageHandler>} handlers
   * @param {object} data
   * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
   */
  _handleMessage(handlers, data) {
    if (data.responseType == this.RESPONSE_NONE) {
      handlers.forEach(handler => {
        // The sender expects no reply, so dump any errors to the console.
        new Promise(resolve => {
          resolve(handler.receiveMessage(data));
        }).catch(e => {
          Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e);
        });
      });
      // Note: Unhandled messages are silently dropped.
      return;
    }

    let target = new MessageManagerProxy(data.target);

    let deferred = {
      sender: data.sender,
      messageManager: target,
      channelId: data.channelId,
    };
    deferred.promise = new Promise((resolve, reject) => {
      deferred.reject = reject;

      this._callHandlers(handlers, data).then(resolve, reject);
    }).then(
      value => {
        let response = {
          result: this.RESULT_SUCCESS,
          messageName: data.channelId,
          recipient: {},
          value,
        };

        target.sendAsyncMessage(MESSAGE_RESPONSE, response);
      },
      error => {
        if (target.isDisconnected) {
          // Target is disconnected. We can't send an error response, so
          // don't even try.
          if (error.result !== this.RESULT_DISCONNECTED &&
              error.result !== this.RESULT_NO_RESPONSE) {
            Cu.reportError(Cu.getClassName(error, false) === "Object" ? error.message : error);
          }
          return;
        }

        let response = {
          result: this.RESULT_ERROR,
          messageName: data.channelId,
          recipient: {},
          error: {},
        };

        if (error && typeof(error) == "object") {
          if (error.result) {
            response.result = error.result;
          }
          // Error objects are not structured-clonable, so just copy
          // over the important properties.
          for (let key of ["fileName", "filename", "lineNumber",
                           "columnNumber", "message", "stack", "result"]) {
            if (key in error) {
              response.error[key] = error[key];
            }
          }
        }

        target.sendAsyncMessage(MESSAGE_RESPONSE, response);
      }).catch(e => {
        Cu.reportError(e);
      }).then(() => {
        target.dispose();
      });

    this._addPendingResponse(deferred);
  },

  /**
   * Handles message callbacks from the response brokers.
   *
   * Each handler object is a deferred object created by `sendMessage`, and
   * should be resolved or rejected based on the contents of the response.
   *
   * @param {Array<MessageHandler>} handlers
   * @param {object} data
   * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
   */
  _handleResponse(handlers, data) {
    // If we have an error at this point, we have handler to report it to,
    // so just log it.
    if (handlers.length == 0) {
      if (this.abortedResponses.has(data.messageName)) {
        this.abortedResponses.delete(data.messageName);
        Services.console.logStringMessage(`Ignoring response to aborted listener for ${data.messageName}`);
      } else {
        Cu.reportError(`No matching message response handler for ${data.messageName}`);
      }
    } else if (handlers.length > 1) {
      Cu.reportError(`Multiple matching response handlers for ${data.messageName}`);
    } else if (data.result === this.RESULT_SUCCESS) {
      handlers[0].resolve(data.value);
    } else {
      handlers[0].reject(data.error);
    }
  },

  /**
   * Adds a pending response to the the `pendingResponses` list.
   *
   * The response object must be a deferred promise with the following
   * properties:
   *
   *  promise:
   *    The promise object which resolves or rejects when the response
   *    is no longer pending.
   *
   *  reject:
   *    A function which, when called, causes the `promise` object to be
   *    rejected.
   *
   *  sender:
   *    A sender object, as passed to `sendMessage.
   *
   *  messageManager:
   *    The message manager the response will be sent or received on.
   *
   * When the promise resolves or rejects, it will be removed from the
   * list.
   *
   * These values are used to clear pending responses when execution
   * contexts are destroyed.
   *
   * @param {Deferred} deferred
   */
  _addPendingResponse(deferred) {
    let cleanup = () => {
      this.pendingResponses.delete(deferred);
    };
    this.pendingResponses.add(deferred);
    deferred.promise.then(cleanup, cleanup);
  },

  /**
   * Aborts any pending message responses to senders matching the given
   * filter.
   *
   * @param {object} sender
   *    The object on which to filter senders, as determined by
   *    `matchesFilter`.
   * @param {object} [reason]
   *    An optional object describing the reason the response was aborted.
   *    Will be passed to the promise rejection handler of all aborted
   *    responses.
   */
  abortResponses(sender, reason = this.REASON_DISCONNECTED) {
    for (let response of this.pendingResponses) {
      if (this.matchesFilter(sender, response.sender)) {
        this.pendingResponses.delete(response);
        this.abortedResponses.add(response.channelId);
        response.reject(reason);
      }
    }
  },

  /**
   * Aborts any pending message responses to the broker for the given
   * message manager.
   *
   * @param {nsIMessageListenerManager} target
   *    The message manager for which to abort brokers.
   * @param {object} reason
   *    An object describing the reason the responses were aborted.
   *    Will be passed to the promise rejection handler of all aborted
   *    responses.
   */
  abortMessageManager(target, reason) {
    for (let response of this.pendingResponses) {
      if (MessageManagerProxy.matches(response.messageManager, target)) {
        this.abortedResponses.add(response.channelId);
        response.reject(reason);
      }
    }
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "message-manager-close":
      case "message-manager-disconnect":
        try {
          if (this.responseManagers.has(subject)) {
            this.abortMessageManager(subject, this.REASON_DISCONNECTED);
          }
        } finally {
          this.responseManagers.delete(subject);
          this.messageManagers.delete(subject);
        }
        break;
    }
  },
};

MessageChannel.init();
PK
!<:Wafafmodules/MulticastDNS.jsm/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */

'use strict';

this.EXPORTED_SYMBOLS = ['MulticastDNS'];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');

Cu.import('resource://gre/modules/DNSPacket.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');

const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';

let observerService     = Cc["@mozilla.org/observer-service;1"]
                            .getService(Components.interfaces.nsIObserverService);
let networkInfoService  = Cc['@mozilla.org/network-info-service;1']
                            .createInstance(Ci.nsINetworkInfoService);

const DEBUG = true;

const MDNS_MULTICAST_GROUP = '224.0.0.251';
const MDNS_PORT            = 5353;
const DEFAULT_TTL          = 120;

function debug(msg) {
  dump('MulticastDNS: ' + msg + '\n');
}

function ServiceKey(svc) {
  return "" + svc.serviceType.length + "/" + svc.serviceType + "|" +
              svc.serviceName.length + "/" + svc.serviceName + "|" +
              svc.port;
}

function TryGet(obj, name) {
  try {
    return obj[name];
  } catch (err) {
    return undefined;
  }
}

function IsIpv4Address(addr) {
  let parts = addr.split('.');
  if (parts.length != 4) {
    return false;
  }
  for (let part of parts) {
    let partInt = Number.parseInt(part, 10);
    if (partInt.toString() != part) {
      return false;
    }
    if (partInt < 0 || partInt >= 256) {
      return false;
    }
  }
  return true;
}

class PublishedService {
  constructor(attrs) {
    this.serviceType = attrs.serviceType.replace(/\.$/, '');
    this.serviceName = attrs.serviceName;
    this.domainName = TryGet(attrs, 'domainName') || "local";
    this.address = TryGet(attrs, 'address') || "0.0.0.0";
    this.port = attrs.port;
    this.serviceAttrs = _propertyBagToObject(TryGet(attrs, 'attributes') || {});
    this.host = TryGet(attrs, 'host');
    this.key = this.generateKey();
    this.lastAdvertised = undefined;
    this.advertiseTimer = undefined;
  }

  equals(svc) {
    return (this.port == svc.port) &&
           (this.serviceName == svc.serviceName) &&
           (this.serviceType == svc.serviceType);
  }

  generateKey() {
    return ServiceKey(this);
  }

  ptrMatch(name) {
    return name == (this.serviceType + "." + this.domainName);
  }

  clearAdvertiseTimer() {
    if (!this.advertiseTimer) {
      return;
    }
    clearTimeout(this.advertiseTimer);
    this.advertiseTimer = undefined;
  }
}

class MulticastDNS {
  constructor() {
    this._listeners       = new Map();
    this._sockets         = new Map();
    this._services        = new Map();
    this._discovered      = new Map();
    this._querySocket     = undefined;
    this._broadcastReceiverSocket = undefined;
    this._broadcastTimer  = undefined;

    this._networkLinkObserver = {
      observe: (subject, topic, data) => {
        DEBUG && debug(NS_NETWORK_LINK_TOPIC + '(' + data + '); Clearing list of previously discovered services');
        this._discovered.clear();
      }
    };
  }

  _attachNetworkLinkObserver() {
    if (this._networkLinkObserverTimeout) {
      clearTimeout(this._networkLinkObserverTimeout);
    }

    if (!this._isNetworkLinkObserverAttached) {
      DEBUG && debug('Attaching observer ' + NS_NETWORK_LINK_TOPIC);
      observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC);
      this._isNetworkLinkObserverAttached = true;
    }
  }

  _detachNetworkLinkObserver() {
    if (this._isNetworkLinkObserverAttached) {
      if (this._networkLinkObserverTimeout) {
        clearTimeout(this._networkLinkObserverTimeout);
      }

      this._networkLinkObserverTimeout = setTimeout(() => {
        DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC);
        observerService.removeObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC);
        this._isNetworkLinkObserverAttached = false;
        this._networkLinkObserverTimeout = null;
      }, 5000);
    }
  }

  startDiscovery(aServiceType, aListener) {
    DEBUG && debug('startDiscovery("' + aServiceType + '")');
    let { serviceType } = _parseServiceDomainName(aServiceType);

    this._attachNetworkLinkObserver();
    this._addServiceListener(serviceType, aListener);

    try {
      this._query(serviceType + '.local');
      aListener.onDiscoveryStarted(serviceType);
    } catch (e) {
      DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e);
      this._removeServiceListener(serviceType, aListener);
      aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
    }
  }

  stopDiscovery(aServiceType, aListener) {
    DEBUG && debug('stopDiscovery("' + aServiceType + '")');
    let { serviceType } = _parseServiceDomainName(aServiceType);

    this._detachNetworkLinkObserver();
    this._removeServiceListener(serviceType, aListener);

    aListener.onDiscoveryStopped(serviceType);

    this._checkCloseSockets();
  }

  resolveService(aServiceInfo, aListener) {
    DEBUG && debug('resolveService(): ' + aServiceInfo.serviceName);

    // Address info is already resolved during discovery
    setTimeout(() => aListener.onServiceResolved(aServiceInfo));
  }

  registerService(aServiceInfo, aListener) {
    DEBUG && debug('registerService(): ' + aServiceInfo.serviceName);

    // Initialize the broadcast receiver socket in case it
    // hasn't already been started so we can listen for
    // multicast queries/announcements on all interfaces.
    this._getBroadcastReceiverSocket();

    for (let name of ['port', 'serviceName', 'serviceType']) {
      if (!TryGet(aServiceInfo, name)) {
        aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE);
        throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"');
      }
    }

    let publishedService;
    try {
      publishedService = new PublishedService(aServiceInfo);
    } catch (e) {
      DEBUG && debug("Error constructing PublishedService: " + e + " - " + e.stack);
      setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
      return;
    }

    // Ensure such a service does not already exist.
    if (this._services.get(publishedService.key)) {
      setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
      return;
    }

    // Make sure that the service addr is '0.0.0.0', or there is at least one
    // socket open on the address the service is open on.
    this._getSockets().then((sockets) => {
      if (publishedService.address != '0.0.0.0' && !sockets.get(publishedService.address)) {
        setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
        return;
      }

      this._services.set(publishedService.key, publishedService);

      // Service registered.. call onServiceRegistered on next tick.
      setTimeout(() => aListener.onServiceRegistered(aServiceInfo));

      // Set a timeout to start advertising the service too.
      publishedService.advertiseTimer = setTimeout(() => {
        this._advertiseService(publishedService.key, /* firstAdv = */ true);
      });
    });
  }

  unregisterService(aServiceInfo, aListener) {
    DEBUG && debug('unregisterService(): ' + aServiceInfo.serviceName);

    let serviceKey;
    try {
      serviceKey = ServiceKey(aServiceInfo);
    } catch (e) {
      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
      return;
    }

    let publishedService = this._services.get(serviceKey);
    if (!publishedService) {
      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
      return;
    }

    // Clear any advertise timeout for this published service.
    publishedService.clearAdvertiseTimer();

    // Delete the service from the service map.
    if (!this._services.delete(serviceKey)) {
      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
      return;
    }

    // Check the broadcast timer again to rejig when it should run next.
    this._checkStartBroadcastTimer();

    // Check to see if sockets should be closed, and if so close them.
    this._checkCloseSockets();

    aListener.onServiceUnregistered(aServiceInfo);
  }

  _respondToQuery(serviceKey, message) {
    let address = message.fromAddr.address;
    let port = message.fromAddr.port;
    DEBUG && debug('_respondToQuery(): key=' + serviceKey + ', fromAddr='
                        + address + ":" + port);

    let publishedService = this._services.get(serviceKey);
    if (!publishedService) {
      debug("_respondToQuery Could not find service (key=" + serviceKey + ")");
      return;
    }

    DEBUG && debug('_respondToQuery(): key=' + serviceKey + ': SENDING RESPONSE');
    this._advertiseServiceHelper(publishedService, {address,port});
  }

  _advertiseService(serviceKey, firstAdv) {
    DEBUG && debug('_advertiseService(): key=' + serviceKey);
    let publishedService = this._services.get(serviceKey);
    if (!publishedService) {
      debug("_advertiseService Could not find service to advertise (key=" + serviceKey + ")");
      return;
    }

    publishedService.advertiseTimer = undefined;

    this._advertiseServiceHelper(publishedService, null).then(() => {
      // If first advertisement, re-advertise in 1 second.
      // Otherwise, set the lastAdvertised time.
      if (firstAdv) {
        publishedService.advertiseTimer = setTimeout(() => {
          this._advertiseService(serviceKey)
        }, 1000);
      } else {
        publishedService.lastAdvertised = Date.now();
        this._checkStartBroadcastTimer();
      }
    });
  }

  _advertiseServiceHelper(svc, target) {
    if (!target) {
      target = {address:MDNS_MULTICAST_GROUP, port:MDNS_PORT};
    }

    return this._getSockets().then((sockets) => {
      sockets.forEach((socket, address) => {
        if (svc.address == "0.0.0.0" || address == svc.address)
        {
          let packet = this._makeServicePacket(svc, [address]);
          let data = packet.serialize();
          try {
            socket.send(target.address, target.port, data, data.length);
          } catch (err) {
            DEBUG && debug("Failed to send packet to "
                            + target.address + ":" + target.port);
          }
        }
      });
    });
  }

  _cancelBroadcastTimer() {
    if (!this._broadcastTimer) {
      return;
    }
    clearTimeout(this._broadcastTimer);
    this._broadcastTimer = undefined;
  }

  _checkStartBroadcastTimer() {
    DEBUG && debug("_checkStartBroadcastTimer()");
    // Cancel any existing broadcasting timer.
    this._cancelBroadcastTimer();

    let now = Date.now();

    // Go through services and find services to broadcast.
    let bcastServices = [];
    let nextBcastWait = undefined;
    for (let [serviceKey, publishedService] of this._services) {
      // if lastAdvertised is undefined, service hasn't finished it's initial
      // two broadcasts.
      if (publishedService.lastAdvertised === undefined) {
        continue;
      }

      // Otherwise, check lastAdvertised against now.
      let msSinceAdv = now - publishedService.lastAdvertised;

      // If msSinceAdv is more than 90% of the way to the TTL, advertise now.
      if (msSinceAdv > (DEFAULT_TTL * 1000 * 0.9)) {
        bcastServices.push(publishedService);
        continue;
      }

      // Otherwise, calculate the next time to advertise for this service.
      // We set that at 95% of the time to the TTL expiry.
      let nextAdvWait = (DEFAULT_TTL * 1000 * 0.95) - msSinceAdv;
      if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) {
        nextBcastWait = nextAdvWait;
      }
    }

    // Schedule an immediate advertisement of all services to be advertised now.
    for (let svc of bcastServices) {
        svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key));
    }

    // Schedule next broadcast check for the next bcast time.
    if (nextBcastWait !== undefined) {
      DEBUG && debug("_checkStartBroadcastTimer(): Scheduling next check in " + nextBcastWait + "ms");
      this._broadcastTimer = setTimeout(() => this._checkStartBroadcastTimer(), nextBcastWait);
    }
  }

  _query(name) {
    DEBUG && debug('query("' + name + '")');
    let packet = new DNSPacket();
    packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.QUERY);

    // PTR Record
    packet.addRecord('QD', new DNSRecord({
      name: name,
      recordType: DNS_RECORD_TYPES.PTR,
      classCode: DNS_CLASS_CODES.IN,
      cacheFlush: true
    }));

    let data = packet.serialize();

    // Initialize the broadcast receiver socket in case it
    // hasn't already been started so we can listen for
    // multicast queries/announcements on all interfaces.
    this._getBroadcastReceiverSocket();

    this._getQuerySocket().then((querySocket) => {
      DEBUG && debug('sending query on query socket ("' + name + '")');
      querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
    });

    // Automatically announce previously-discovered
    // services that match and haven't expired yet.
    setTimeout(() => {
      DEBUG && debug('announcing previously discovered services ("' + name + '")');
      let { serviceType } = _parseServiceDomainName(name);

      this._clearExpiredDiscoveries();
      this._discovered.forEach((discovery, key) => {
        let serviceInfo = discovery.serviceInfo;
        if (serviceInfo.serviceType !== serviceType) {
          return;
        }

        let listeners = this._listeners.get(serviceInfo.serviceType) || [];
        listeners.forEach((listener) => {
          listener.onServiceFound(serviceInfo);
        });
      });
    });
  }

  _clearExpiredDiscoveries() {
    this._discovered.forEach((discovery, key) => {
      if (discovery.expireTime < Date.now()) {
        this._discovered.delete(key);
        return;
      }
    });
  }

  _handleQueryPacket(packet, message) {
    packet.getRecords(['QD']).forEach((record) => {
      // Don't respond if the query's class code is not IN or ANY.
      if (record.classCode !== DNS_CLASS_CODES.IN &&
          record.classCode !== DNS_CLASS_CODES.ANY) {
        return;
      }

      // Don't respond if the query's record type is not PTR or ANY.
      if (record.recordType !== DNS_RECORD_TYPES.PTR &&
          record.recordType !== DNS_RECORD_TYPES.ANY) {
        return;
      }

      for (let [serviceKey, publishedService] of this._services) {
        DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
        if (publishedService.ptrMatch(record.name)) {
          this._respondToQuery(serviceKey, message);
        }
      }
    });
  }

  _makeServicePacket(service, addresses) {
    let packet = new DNSPacket();
    packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE);
    packet.setFlag('AA', DNS_AUTHORITATIVE_ANSWER_CODES.YES);

    let host = service.host || _hostname;

    // e.g.: foo-bar-service._http._tcp.local
    let serviceDomainName = service.serviceName + '.' + service.serviceType + '.local';

    // PTR Record
    packet.addRecord('AN', new DNSResourceRecord({
      name: service.serviceType + '.local', // e.g.: _http._tcp.local
      recordType: DNS_RECORD_TYPES.PTR,
      data: serviceDomainName
    }));

    // SRV Record
    packet.addRecord('AR', new DNSResourceRecord({
      name: serviceDomainName,
      recordType: DNS_RECORD_TYPES.SRV,
      classCode: DNS_CLASS_CODES.IN,
      cacheFlush: true,
      data: {
        priority: 0,
        weight: 0,
        port: service.port,
        target: host // e.g.: My-Android-Phone.local
      }
    }));

    // A Records
    for (let address of addresses) {
        packet.addRecord('AR', new DNSResourceRecord({
          name: host,
          recordType: DNS_RECORD_TYPES.A,
          data: address
        }));
    }

    // TXT Record
    packet.addRecord('AR', new DNSResourceRecord({
      name: serviceDomainName,
      recordType: DNS_RECORD_TYPES.TXT,
      classCode: DNS_CLASS_CODES.IN,
      cacheFlush: true,
      data: service.serviceAttrs || {}
    }));

    return packet;
  }

  _handleResponsePacket(packet, message) {
    let services = {};
    let hosts = {};

    let srvRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.SRV);
    let txtRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.TXT);
    let ptrRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.PTR);
    let aRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.A);

    srvRecords.forEach((record) => {
      let data = record.data || {};

      services[record.name] = {
        host: data.target,
        port: data.port,
        ttl: record.ttl
      };
    });

    txtRecords.forEach((record) => {
      if (!services[record.name]) {
        return;
      }

      services[record.name].attributes = record.data;
    });

    aRecords.forEach((record) => {
      if (IsIpv4Address(record.data)) {
        hosts[record.name] = record.data;
      }
    });

    ptrRecords.forEach((record) => {
      let name = record.data;
      if (!services[name]) {
        return;
      }

      let {host, port} = services[name];
      if (!host || !port) {
        return;
      }

      let { serviceName, serviceType, domainName } = _parseServiceDomainName(name);
      if (!serviceName || !serviceType || !domainName) {
        return;
      }

      let address = hosts[host];
      if (!address) {
        return;
      }

      let ttl = services[name].ttl || 0;
      let serviceInfo = {
        serviceName: serviceName,
        serviceType: serviceType,
        host: host,
        address: address,
        port: port,
        domainName: domainName,
        attributes: services[name].attributes || {}
      };

      this._onServiceFound(serviceInfo, ttl);
    });
  }

  _onServiceFound(serviceInfo, ttl = 0) {
    let expireTime = Date.now() + (ttl * 1000);
    let key = serviceInfo.serviceName + '.' +
              serviceInfo.serviceType + '.' +
              serviceInfo.domainName + ' @' +
              serviceInfo.address + ':' +
              serviceInfo.port;

    // If this service was already discovered, just update
    // its expiration time and don't re-emit it.
    if (this._discovered.has(key)) {
      this._discovered.get(key).expireTime = expireTime;
      return;
    }

    this._discovered.set(key, {
      serviceInfo: serviceInfo,
      expireTime: expireTime
    });

    let listeners = this._listeners.get(serviceInfo.serviceType) || [];
    listeners.forEach((listener) => {
      listener.onServiceFound(serviceInfo);
    });

    DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName);
  }

  /**
   * Gets a non-exclusive socket on 0.0.0.0:{random} to send
   * multicast queries on all interfaces. This socket does
   * not need to join a multicast group since it is still
   * able to *send* multicast queries, but it does not need
   * to *listen* for multicast queries/announcements since
   * the `_broadcastReceiverSocket` is already handling them.
   */
  _getQuerySocket() {
    return new Promise((resolve, reject) => {
      if (!this._querySocket) {
        this._querySocket = _openSocket('0.0.0.0', 0, {
          onPacketReceived: this._onPacketReceived.bind(this),
          onStopListening: this._onStopListening.bind(this)
        });
      }
      resolve(this._querySocket);
    });
  }

  /**
   * Gets a non-exclusive socket on 0.0.0.0:5353 to listen
   * for multicast queries/announcements on all interfaces.
   * Since this socket needs to listen for multicast queries
   * and announcements, this socket joins the multicast
   * group on *all* interfaces (0.0.0.0).
   */
  _getBroadcastReceiverSocket() {
    return new Promise((resolve, reject) => {
      if (!this._broadcastReceiverSocket) {
        this._broadcastReceiverSocket = _openSocket('0.0.0.0', MDNS_PORT, {
          onPacketReceived: this._onPacketReceived.bind(this),
          onStopListening: this._onStopListening.bind(this)
        }, /* multicastInterface = */ '0.0.0.0');
      }
      resolve(this._broadcastReceiverSocket);
    });
  }

  /**
   * Gets a non-exclusive socket for each interface on
   * {iface-ip}:5353 for sending query responses as
   * well as for listening for unicast queries. These
   * sockets do not need to join a multicast group
   * since they are still able to *send* multicast
   * query responses, but they do not need to *listen*
   * for multicast queries since the `_querySocket` is
   * already handling them.
   */
  _getSockets() {
    return new Promise((resolve) => {
      if (this._sockets.size > 0) {
        resolve(this._sockets);
        return;
      }

      Promise.all([getAddresses(), getHostname()]).then(() => {
        _addresses.forEach((address) => {
          let socket = _openSocket(address, MDNS_PORT, null);
          this._sockets.set(address, socket);
        });

        resolve(this._sockets);
      });
    });
  }

  _checkCloseSockets() {
    // Nothing to do if no sockets to close.
    if (this._sockets.size == 0)
      return;

    // Don't close sockets if discovery listeners are still present.
    if (this._listeners.size > 0)
      return;

    // Don't close sockets if advertised services are present.
    // Since we need to listen for service queries and respond to them.
    if (this._services.size > 0)
      return;

    this._closeSockets();
  }

  _closeSockets() {
    this._sockets.forEach(socket => socket.close());
    this._sockets.clear();
  }

  _onPacketReceived(socket, message) {
    let packet = DNSPacket.parse(message.rawData);

    switch (packet.getFlag('QR')) {
      case DNS_QUERY_RESPONSE_CODES.QUERY:
        this._handleQueryPacket(packet, message);
        break;
      case DNS_QUERY_RESPONSE_CODES.RESPONSE:
        this._handleResponsePacket(packet, message);
        break;
      default:
        break;
    }
  }

  _onStopListening(socket, status) {
    DEBUG && debug('_onStopListening() ' + status);
  }

  _addServiceListener(serviceType, listener) {
    let listeners = this._listeners.get(serviceType);
    if (!listeners) {
      listeners = [];
      this._listeners.set(serviceType, listeners);
    }

    if (!listeners.find(l => l === listener)) {
      listeners.push(listener);
    }
  }

  _removeServiceListener(serviceType, listener) {
    let listeners = this._listeners.get(serviceType);
    if (!listeners) {
      return;
    }

    let index = listeners.findIndex(l => l === listener);
    if (index >= 0) {
      listeners.splice(index, 1);
    }

    if (listeners.length === 0) {
      this._listeners.delete(serviceType);
    }
  }
}

let _addresses;

/**
 * @private
 */
function getAddresses() {
  return new Promise((resolve, reject) => {
    if (_addresses) {
      resolve(_addresses);
      return;
    }

    networkInfoService.listNetworkAddresses({
      onListedNetworkAddresses(aAddressArray) {
        _addresses = aAddressArray.filter((address) => {
          return address.indexOf('%p2p') === -1 &&  // No WiFi Direct interfaces
                 address.indexOf(':')    === -1 &&  // XXX: No IPv6 for now
                 address != "127.0.0.1"             // No ipv4 loopback addresses.
        });

        DEBUG && debug('getAddresses(): ' + _addresses);
        resolve(_addresses);
      },

      onListNetworkAddressesFailed() {
        DEBUG && debug('getAddresses() FAILED!');
        resolve([]);
      }
    });
  });
}

let _hostname;

/**
 * @private
 */
function getHostname() {
  return new Promise((resolve) => {
    if (_hostname) {
      resolve(_hostname);
      return;
    }

    networkInfoService.getHostname({
      onGotHostname(aHostname) {
        _hostname = aHostname.replace(/\s/g, '-') + '.local';

        DEBUG && debug('getHostname(): ' + _hostname);
        resolve(_hostname);
      },

      onGetHostnameFailed() {
        DEBUG && debug('getHostname() FAILED');
        resolve('localhost');
      }
    });
  });
}

/**
 * Parse fully qualified domain name to service name, instance name,
 * and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
 *
 * Example: 'foo-bar-service._http._tcp.local' -> {
 *   serviceName: 'foo-bar-service',
 *   serviceType: '_http._tcp',
 *   domainName: 'local'
 * }
 *
 * @private
 */
function _parseServiceDomainName(serviceDomainName) {
  let parts = serviceDomainName.split('.');
  let index = Math.max(parts.lastIndexOf('_tcp'), parts.lastIndexOf('_udp'));

  return {
    serviceName: parts.splice(0, index - 1).join('.'),
    serviceType: parts.splice(0, 2).join('.'),
    domainName: parts.join('.')
  };
}

/**
 * @private
 */
function _propertyBagToObject(propBag) {
  let result = {};
  if (propBag.QueryInterface) {
    propBag.QueryInterface(Ci.nsIPropertyBag2);
    let propEnum = propBag.enumerator;
    while (propEnum.hasMoreElements()) {
      let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
      result[prop.name] = prop.value.toString();
    }
  } else {
    for (let name in propBag) {
      result[name] = propBag[name].toString();
    }
  }
  return result;
}

/**
 * @private
 */
function _openSocket(addr, port, handler, multicastInterface) {
  let socket = Cc['@mozilla.org/network/udp-socket;1'].createInstance(Ci.nsIUDPSocket);
  socket.init2(addr, port, Services.scriptSecurityManager.getSystemPrincipal(), true);

  if (handler) {
    socket.asyncListen({
      onPacketReceived: handler.onPacketReceived,
      onStopListening: handler.onStopListening
    });
  }

  if (multicastInterface) {
    socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface);
  }

  return socket;
}
PK
!<	modules/NLP.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["NLP"];

/**
 * NLP, which stands for Natural Language Processing, is a module that provides
 * an entry point to various methods to interface with human language.
 *
 * At least, that's the goal. Eventually. Right now, the find toolbar only really
 * needs the Levenshtein distance algorithm.
 */
this.NLP = {
  /**
   * Calculate the Levenshtein distance between two words.
   * The implementation of this method was heavily inspired by
   * http://locutus.io/php/strings/levenshtein/index.html
   * License: MIT.
   *
   * @param  {String} word1   Word to compare against
   * @param  {String} word2   Word that may be different
   * @param  {Number} costIns The cost to insert a character
   * @param  {Number} costRep The cost to replace a character
   * @param  {Number} costDel The cost to delete a character
   * @return {Number}
   */
  levenshtein(word1 = "", word2 = "", costIns = 1, costRep = 1, costDel = 1) {
    if (word1 === word2)
      return 0;

    let l1 = word1.length;
    let l2 = word2.length;
    if (!l1)
      return l2 * costIns;
    if (!l2)
      return l1 * costDel;

    let p1 = new Array(l2 + 1)
    let p2 = new Array(l2 + 1)

    let i1, i2, c0, c1, c2, tmp;

    for (i2 = 0; i2 <= l2; i2++)
      p1[i2] = i2 * costIns;

    for (i1 = 0; i1 < l1; i1++) {
      p2[0] = p1[0] + costDel;

      for (i2 = 0; i2 < l2; i2++) {
        c0 = p1[i2] + ((word1[i1] === word2[i2]) ? 0 : costRep);
        c1 = p1[i2 + 1] + costDel;

        if (c1 < c0)
          c0 = c1;

        c2 = p2[i2] + costIns;

        if (c2 < c0)
          c0 = c2;

        p2[i2 + 1] = c0;
      }

      tmp = p1;
      p1 = p2;
      p2 = tmp;
    }

    c0 = p1[l2];

    return c0;
  }
};
PK
!<F==modules/NativeMessaging.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["HostManifestManager", "NativeApp"];
/* globals NativeApp */

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
                                  "resource://gre/modules/ExtensionChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Subprocess",
                                  "resource://gre/modules/Subprocess.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
                                  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                  "resource://gre/modules/WindowsRegistry.jsm");

const HOST_MANIFEST_SCHEMA = "chrome://extensions/content/schemas/native_host_manifest.json";
const VALID_APPLICATION = /^\w+(\.\w+)*$/;

// For a graceful shutdown (i.e., when the extension is unloaded or when it
// explicitly calls disconnect() on a native port), how long we give the native
// application to exit before we start trying to kill it.  (in milliseconds)
const GRACEFUL_SHUTDOWN_TIME = 3000;

// Hard limits on maximum message size that can be read/written
// These are defined in the native messaging documentation, note that
// the write limit is imposed by the "wire protocol" in which message
// boundaries are defined by preceding each message with its length as
// 4-byte unsigned integer so this is the largest value that can be
// represented.  Good luck generating a serialized message that large,
// the practical write limit is likely to be dictated by available memory.
const MAX_READ = 1024 * 1024;
const MAX_WRITE = 0xffffffff;

// Preferences that can lower the message size limits above,
// used for testing the limits.
const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes";
const PREF_MAX_WRITE = "webextensions.native-messaging.max-output-message-bytes";

const REGPATH = "Software\\Mozilla\\NativeMessagingHosts";

const global = this;

this.HostManifestManager = {
  _initializePromise: null,
  _lookup: null,

  init() {
    if (!this._initializePromise) {
      let platform = AppConstants.platform;
      if (platform == "win") {
        this._lookup = this._winLookup;
      } else if (platform == "macosx" || platform == "linux") {
        let dirs = [
          Services.dirsvc.get("XREUserNativeMessaging", Ci.nsIFile).path,
          Services.dirsvc.get("XRESysNativeMessaging", Ci.nsIFile).path,
        ];
        this._lookup = (application, context) => this._tryPaths(application, dirs, context);
      } else {
        throw new Error(`Native messaging is not supported on ${AppConstants.platform}`);
      }
      this._initializePromise = Schemas.load(HOST_MANIFEST_SCHEMA);
    }
    return this._initializePromise;
  },

  _winLookup(application, context) {
    const REGISTRY = Ci.nsIWindowsRegKey;
    let regPath = `${REGPATH}\\${application}`;
    let path = WindowsRegistry.readRegKey(REGISTRY.ROOT_KEY_CURRENT_USER,
                                          regPath, "", REGISTRY.WOW64_64);
    if (!path) {
      path = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                        regPath, "", REGISTRY.WOW64_64);
    }
    if (!path) {
      return null;
    }
    return this._tryPath(path, application, context)
      .then(manifest => manifest ? {path, manifest} : null);
  },

  _tryPath(path, application, context) {
    return Promise.resolve()
      .then(() => OS.File.read(path, {encoding: "utf-8"}))
      .then(data => {
        let manifest;
        try {
          manifest = JSON.parse(data);
        } catch (ex) {
          let msg = `Error parsing native host manifest ${path}: ${ex.message}`;
          Cu.reportError(msg);
          return null;
        }

        let normalized = Schemas.normalize(manifest, "manifest.NativeHostManifest", context);
        if (normalized.error) {
          Cu.reportError(normalized.error);
          return null;
        }
        manifest = normalized.value;
        if (manifest.name != application) {
          let msg = `Native host manifest ${path} has name property ${manifest.name} (expected ${application})`;
          Cu.reportError(msg);
          return null;
        }
        return normalized.value;
      }).catch(ex => {
        if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
          return null;
        }
        throw ex;
      });
  },

  async _tryPaths(application, dirs, context) {
    for (let dir of dirs) {
      let path = OS.Path.join(dir, `${application}.json`);
      let manifest = await this._tryPath(path, application, context);
      if (manifest) {
        return {path, manifest};
      }
    }
    return null;
  },

  /**
   * Search for a valid native host manifest for the given application name.
   * The directories searched and rules for manifest validation are all
   * detailed in the native messaging documentation.
   *
   * @param {string} application The name of the applciation to search for.
   * @param {object} context A context object as expected by Schemas.normalize.
   * @returns {object} The contents of the validated manifest, or null if
   *                   no valid manifest can be found for this application.
   */
  lookupApplication(application, context) {
    if (!VALID_APPLICATION.test(application)) {
      throw new Error(`Invalid application "${application}"`);
    }
    return this.init().then(() => this._lookup(application, context));
  },
};

this.NativeApp = class extends EventEmitter {
  /**
   * @param {BaseContext} context The context that initiated the native app.
   * @param {string} application The identifier of the native app.
   */
  constructor(context, application) {
    super();

    this.context = context;
    this.name = application;

    // We want a close() notification when the window is destroyed.
    this.context.callOnClose(this);

    this.proc = null;
    this.readPromise = null;
    this.sendQueue = [];
    this.writePromise = null;
    this.sentDisconnect = false;

    this.startupPromise = HostManifestManager.lookupApplication(application, context)
      .then(hostInfo => {
        // Put the two errors together to not leak information about whether a native
        // application is installed to addons that do not have the right permission.
        if (!hostInfo || !hostInfo.manifest.allowed_extensions.includes(context.extension.id)) {
          throw new context.cloneScope.Error(`This extension does not have permission to use native application ${application} (or the application is not installed)`);
        }

        let command = hostInfo.manifest.path;
        if (AppConstants.platform == "win") {
          // OS.Path.join() ignores anything before the last absolute path
          // it sees, so if command is already absolute, it remains unchanged
          // here.  If it is relative, we get the proper absolute path here.
          command = OS.Path.join(OS.Path.dirname(hostInfo.path), command);
        }

        let subprocessOpts = {
          command: command,
          arguments: [hostInfo.path, context.extension.id],
          workdir: OS.Path.dirname(command),
          stderr: "pipe",
        };
        return Subprocess.call(subprocessOpts);
      }).then(proc => {
        this.startupPromise = null;
        this.proc = proc;
        this._startRead();
        this._startWrite();
        this._startStderrRead();
      }).catch(err => {
        this.startupPromise = null;
        Cu.reportError(err instanceof Error ? err : err.message);
        this._cleanup(err);
      });
  }

  /**
   * Open a connection to a native messaging host.
   *
   * @param {BaseContext} context The context associated with the port.
   * @param {nsIMessageSender} messageManager The message manager used to send
   *     and receive messages from the port's creator.
   * @param {string} portId A unique internal ID that identifies the port.
   * @param {object} sender The object describing the creator of the connection
   *     request.
   * @param {string} application The name of the native messaging host.
   */
  static onConnectNative(context, messageManager, portId, sender, application) {
    let app = new NativeApp(context, application);
    let port = new ExtensionChild.Port(context, messageManager, [Services.mm], "", portId, sender, sender);
    app.once("disconnect", (what, err) => port.disconnect(err));

    /* eslint-disable mozilla/balanced-listeners */
    app.on("message", (what, msg) => port.postMessage(msg));
    /* eslint-enable mozilla/balanced-listeners */

    port.registerOnMessage(holder => app.send(holder));
    port.registerOnDisconnect(msg => app.close());
  }

  /**
   * @param {BaseContext} context The scope from where `message` originates.
   * @param {*} message A message from the extension, meant for a native app.
   * @returns {ArrayBuffer} An ArrayBuffer that can be sent to the native app.
   */
  static encodeMessage(context, message) {
    message = context.jsonStringify(message);
    let buffer = new TextEncoder().encode(message).buffer;
    if (buffer.byteLength > NativeApp.maxWrite) {
      throw new context.cloneScope.Error("Write too big");
    }
    return buffer;
  }

  // A port is definitely "alive" if this.proc is non-null.  But we have
  // to provide a live port object immediately when connecting so we also
  // need to consider a port alive if proc is null but the startupPromise
  // is still pending.
  get _isDisconnected() {
    return (!this.proc && !this.startupPromise);
  }

  _startRead() {
    if (this.readPromise) {
      throw new Error("Entered _startRead() while readPromise is non-null");
    }
    this.readPromise = this.proc.stdout.readUint32()
      .then(len => {
        if (len > NativeApp.maxRead) {
          throw new this.context.cloneScope.Error(`Native application tried to send a message of ${len} bytes, which exceeds the limit of ${NativeApp.maxRead} bytes.`);
        }
        return this.proc.stdout.readJSON(len);
      }).then(msg => {
        this.emit("message", msg);
        this.readPromise = null;
        this._startRead();
      }).catch(err => {
        if (err.errorCode != Subprocess.ERROR_END_OF_FILE) {
          Cu.reportError(err instanceof Error ? err : err.message);
        }
        this._cleanup(err);
      });
  }

  _startWrite() {
    if (this.sendQueue.length == 0) {
      return;
    }

    if (this.writePromise) {
      throw new Error("Entered _startWrite() while writePromise is non-null");
    }

    let buffer = this.sendQueue.shift();
    let uintArray = Uint32Array.of(buffer.byteLength);

    this.writePromise = Promise.all([
      this.proc.stdin.write(uintArray.buffer),
      this.proc.stdin.write(buffer),
    ]).then(() => {
      this.writePromise = null;
      this._startWrite();
    }).catch(err => {
      Cu.reportError(err.message);
      this._cleanup(err);
    });
  }

  _startStderrRead() {
    let proc = this.proc;
    let app = this.name;
    (async function() {
      let partial = "";
      while (true) {
        let data = await proc.stderr.readString();
        if (data.length == 0) {
          // We have hit EOF, just stop reading
          if (partial) {
            Services.console.logStringMessage(`stderr output from native app ${app}: ${partial}`);
          }
          break;
        }

        let lines = data.split(/\r?\n/);
        lines[0] = partial + lines[0];
        partial = lines.pop();

        for (let line of lines) {
          Services.console.logStringMessage(`stderr output from native app ${app}: ${line}`);
        }
      }
    })();
  }

  send(holder) {
    if (this._isDisconnected) {
      throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
    }
    let msg = holder.deserialize(global);
    if (Cu.getClassName(msg, true) != "ArrayBuffer") {
      // This error cannot be triggered by extensions; it indicates an error in
      // our implementation.
      throw new Error("The message to the native messaging host is not an ArrayBuffer");
    }

    let buffer = msg;

    if (buffer.byteLength > NativeApp.maxWrite) {
      throw new this.context.cloneScope.Error("Write too big");
    }

    this.sendQueue.push(buffer);
    if (!this.startupPromise && !this.writePromise) {
      this._startWrite();
    }
  }

  // Shut down the native application and also signal to the extension
  // that the connect has been disconnected.
  _cleanup(err) {
    this.context.forgetOnClose(this);

    let doCleanup = () => {
      // Set a timer to kill the process gracefully after one timeout
      // interval and kill it forcefully after two intervals.
      let timer = setTimeout(() => {
        this.proc.kill(GRACEFUL_SHUTDOWN_TIME);
      }, GRACEFUL_SHUTDOWN_TIME);

      let promise = Promise.all([
        this.proc.stdin.close()
          .catch(err => {
            if (err.errorCode != Subprocess.ERROR_END_OF_FILE) {
              throw err;
            }
          }),
        this.proc.wait(),
      ]).then(() => {
        this.proc = null;
        clearTimeout(timer);
      });

      AsyncShutdown.profileBeforeChange.addBlocker(
        `Native Messaging: Wait for application ${this.name} to exit`,
        promise);

      promise.then(() => {
        AsyncShutdown.profileBeforeChange.removeBlocker(promise);
      });

      return promise;
    };

    if (this.proc) {
      doCleanup();
    } else if (this.startupPromise) {
      this.startupPromise.then(doCleanup);
    }

    if (!this.sentDisconnect) {
      this.sentDisconnect = true;
      if (err && err.errorCode == Subprocess.ERROR_END_OF_FILE) {
        err = null;
      }
      this.emit("disconnect", err);
    }
  }

  // Called from Context when the extension is shut down.
  close() {
    this._cleanup();
  }

  sendMessage(holder) {
    let responsePromise = new Promise((resolve, reject) => {
      this.once("message", (what, msg) => { resolve(msg); });
      this.once("disconnect", (what, err) => { reject(err); });
    });

    let result = this.startupPromise.then(() => {
      this.send(holder);
      return responsePromise;
    });

    result.then(() => {
      this._cleanup();
    }, () => {
      // Prevent the response promise from being reported as an
      // unchecked rejection if the startup promise fails.
      responsePromise.catch(() => {});

      this._cleanup();
    });

    return result;
  }
};

XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxRead", PREF_MAX_READ, MAX_READ);
XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxWrite", PREF_MAX_WRITE, MAX_WRITE);
PK
!<VMMmodules/NetUtil.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
 * vim: sw=4 ts=4 sts=4 et filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "NetUtil",
];

/**
 * Necko utilities
 */

////////////////////////////////////////////////////////////////////////////////
//// Constants

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;

const PR_UINT32_MAX = 0xffffffff;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

const BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
                                                 "nsIBinaryInputStream", "setInputStream");

////////////////////////////////////////////////////////////////////////////////
//// NetUtil Object

this.NetUtil = {
    /**
     * Function to perform simple async copying from aSource (an input stream)
     * to aSink (an output stream).  The copy will happen on some background
     * thread.  Both streams will be closed when the copy completes.
     *
     * @param aSource
     *        The input stream to read from
     * @param aSink
     *        The output stream to write to
     * @param aCallback [optional]
     *        A function that will be called at copy completion with a single
     *        argument: the nsresult status code for the copy operation.
     *
     * @return An nsIRequest representing the copy operation (for example, this
     *         can be used to cancel the copying).  The consumer can ignore the
     *         return value if desired.
     */
    asyncCopy: function NetUtil_asyncCopy(aSource, aSink,
                                          aCallback = null)
    {
        if (!aSource || !aSink) {
            let exception = new Components.Exception(
                "Must have a source and a sink",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        // make a stream copier
        var copier = Cc["@mozilla.org/network/async-stream-copier;1"].
            createInstance(Ci.nsIAsyncStreamCopier2);
        copier.init(aSource, aSink,
                    null /* Default event target */,
                    0 /* Default length */,
                    true, true /* Auto-close */);

        var observer;
        if (aCallback) {
            observer = {
                onStartRequest: function(aRequest, aContext) {},
                onStopRequest: function(aRequest, aContext, aStatusCode) {
                    aCallback(aStatusCode);
                }
            }
        } else {
            observer = null;
        }

        // start the copying
        copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
        return copier;
    },

    /**
     * Asynchronously opens a source and fetches the response.  While the fetch
     * is asynchronous, I/O may happen on the main thread.  When reading from
     * a local file, prefer using "OS.File" methods instead.
     *
     * @param aSource
     *        This argument can be one of the following:
     *         - An options object that will be passed to NetUtil.newChannel.
     *         - An existing nsIChannel.
     *         - An existing nsIInputStream.
     *        Using an nsIURI, nsIFile, or string spec directly is deprecated.
     * @param aCallback
     *        The callback function that will be notified upon completion.  It
     *        will get these arguments:
     *        1) An nsIInputStream containing the data from aSource, if any.
     *        2) The status code from opening the source.
     *        3) Reference to the nsIRequest.
     */
    asyncFetch: function NetUtil_asyncFetch(aSource, aCallback)
    {
        if (!aSource || !aCallback) {
            let exception = new Components.Exception(
                "Must have a source and a callback",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        // Create a pipe that will create our output stream that we can use once
        // we have gotten all the data.
        let pipe = Cc["@mozilla.org/pipe;1"].
                   createInstance(Ci.nsIPipe);
        pipe.init(true, true, 0, PR_UINT32_MAX, null);

        // Create a listener that will give data to the pipe's output stream.
        let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
                       createInstance(Ci.nsISimpleStreamListener);
        listener.init(pipe.outputStream, {
            onStartRequest: function(aRequest, aContext) {},
            onStopRequest: function(aRequest, aContext, aStatusCode) {
                pipe.outputStream.close();
                aCallback(pipe.inputStream, aStatusCode, aRequest);
            }
        });

        // Input streams are handled slightly differently from everything else.
        if (aSource instanceof Ci.nsIInputStream) {
            let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
                       createInstance(Ci.nsIInputStreamPump);
            pump.init(aSource, -1, -1, 0, 0, true);
            pump.asyncRead(listener, null);
            return;
        }

        let channel = aSource;
        if (!(channel instanceof Ci.nsIChannel)) {
            channel = this.newChannel(aSource);
        }

        try {
            // Open the channel using asyncOpen2() if the loadinfo contains one
            // of the security mode flags, otherwise fall back to use asyncOpen().
            if (channel.loadInfo &&
                channel.loadInfo.securityMode != 0) {
                channel.asyncOpen2(listener);
            }
            else {
                // Log deprecation warning to console to make sure all channels
                // are created providing the correct security flags in the loadinfo.
                // See nsILoadInfo for all available security flags and also the API
                // of NetUtil.newChannel() for details above.
                Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " +
                    "one of the security flags set in the loadinfo (see nsILoadInfo). " +
                    "Please create channel using NetUtil.newChannel()");
                channel.asyncOpen(listener, null);
            }
        }
        catch (e) {
            let exception = new Components.Exception(
                "Failed to open input source '" + channel.originalURI.spec + "'",
                e.result,
                Components.stack.caller,
                aSource,
                e
            );
            throw exception;
        }
    },

    /**
     * Constructs a new URI for the given spec, character set, and base URI, or
     * an nsIFile.
     *
     * @param aTarget
     *        The string spec for the desired URI or an nsIFile.
     * @param aOriginCharset [optional]
     *        The character set for the URI.  Only used if aTarget is not an
     *        nsIFile.
     * @param aBaseURI [optional]
     *        The base URI for the spec.  Only used if aTarget is not an
     *        nsIFile.
     *
     * @return an nsIURI object.
     */
    newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI)
    {
        if (!aTarget) {
            let exception = new Components.Exception(
                "Must have a non-null string spec or nsIFile object",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        if (aTarget instanceof Ci.nsIFile) {
            return this.ioService.newFileURI(aTarget);
        }

        return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI);
    },

    /**
     * Constructs a new channel for the given source.
     *
     * Keep in mind that URIs coming from a webpage should *never* use the
     * systemPrincipal as the loadingPrincipal.
     *
     * @param aWhatToLoad
     *        This argument used to be a string spec for the desired URI, an
     *        nsIURI, or an nsIFile.  Now it should be an options object with
     *        the following properties:
     *        {
     *          uri:
     *            The full URI spec string, nsIURI or nsIFile to create the
     *            channel for.
     *            Note that this cannot be an nsIFile if you have to specify a
     *            non-default charset or base URI.  Call NetUtil.newURI first if
     *            you need to construct an URI using those options.
     *          loadingNode:
     *          loadingPrincipal:
     *          triggeringPrincipal:
     *          securityFlags:
     *          contentPolicyType:
     *            These will be used as values for the nsILoadInfo object on the
     *            created channel. For details, see nsILoadInfo in nsILoadInfo.idl
     *          loadUsingSystemPrincipal:
     *            Set this to true to use the system principal as
     *            loadingPrincipal.  This must be omitted if loadingPrincipal or
     *            loadingNode are present.
     *            This should be used with care as it skips security checks.
     *        }
     * @param aOriginCharset [deprecated]
     *        The character set for the URI.  Only used if aWhatToLoad is a
     *        string, which is a deprecated API.  Must be undefined otherwise.
     *        Use NetUtil.newURI if you need to use this option.
     * @param aBaseURI [deprecated]
     *        The base URI for the spec.  Only used if aWhatToLoad is a string,
     *        which is a deprecated API.  Must be undefined otherwise.  Use
     *        NetUtil.newURI if you need to use this option.
     * @return an nsIChannel object.
     */
    newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI)
    {
        // Check for the deprecated API first.
        if (typeof aWhatToLoad == "string" ||
            (aWhatToLoad instanceof Ci.nsIFile) ||
            (aWhatToLoad instanceof Ci.nsIURI)) {

            let uri = (aWhatToLoad instanceof Ci.nsIURI)
                      ? aWhatToLoad
                      : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);

            // log deprecation warning for developers.
            Services.console.logStringMessage(
              "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'");

            // Provide default loadinfo arguments and call the new API.
            let systemPrincipal =
              Services.scriptSecurityManager.getSystemPrincipal();

            return this.ioService.newChannelFromURI2(
                     uri,
                     null, // loadingNode
                     systemPrincipal, // loadingPrincipal
                     null, // triggeringPrincipal
                     Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                     Ci.nsIContentPolicy.TYPE_OTHER);
        }

        // We are using the updated API, that requires only the options object.
        if (typeof aWhatToLoad != "object" ||
             aOriginCharset !== undefined ||
             aBaseURI !== undefined) {
            throw new Components.Exception(
                "newChannel requires a single object argument",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
        }

        let { uri,
              loadingNode,
              loadingPrincipal,
              loadUsingSystemPrincipal,
              triggeringPrincipal,
              securityFlags,
              contentPolicyType } = aWhatToLoad;

        if (!uri) {
            throw new Components.Exception(
                "newChannel requires the 'uri' property on the options object.",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
        }

        if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
            uri = this.newURI(uri);
        }

        if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
            throw new Components.Exception(
                "newChannel requires at least one of the 'loadingNode'," +
                " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
                " properties on the options object.",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
        }

        if (loadUsingSystemPrincipal === true) {
            if (loadingNode || loadingPrincipal) {
                throw new Components.Exception(
                    "newChannel does not accept 'loadUsingSystemPrincipal'" +
                    " if the 'loadingNode' or 'loadingPrincipal' properties" +
                    " are present on the options object.",
                    Cr.NS_ERROR_INVALID_ARG,
                    Components.stack.caller
                );
            }
            loadingPrincipal = Services.scriptSecurityManager
                                       .getSystemPrincipal();
        } else if (loadUsingSystemPrincipal !== undefined) {
            throw new Components.Exception(
                "newChannel requires the 'loadUsingSystemPrincipal'" +
                " property on the options object to be 'true' or 'undefined'.",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
        }

        if (securityFlags === undefined) {
            securityFlags = loadUsingSystemPrincipal
                            ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
                            : Ci.nsILoadInfo.SEC_NORMAL;
        }

        if (contentPolicyType === undefined) {
            if (!loadUsingSystemPrincipal) {
                throw new Components.Exception(
                    "newChannel requires the 'contentPolicyType' property on" +
                    " the options object unless loading from system principal.",
                    Cr.NS_ERROR_INVALID_ARG,
                    Components.stack.caller
                );
            }
            contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
        }

        return this.ioService.newChannelFromURI2(uri,
                                                 loadingNode || null,
                                                 loadingPrincipal || null,
                                                 triggeringPrincipal || null,
                                                 securityFlags,
                                                 contentPolicyType);
    },

    /**
     * Reads aCount bytes from aInputStream into a string.
     *
     * @param aInputStream
     *        The input stream to read from.
     * @param aCount
     *        The number of bytes to read from the stream.
     * @param aOptions [optional]
     *        charset
     *          The character encoding of stream data.
     *        replacement
     *          The character to replace unknown byte sequences.
     *          If unset, it causes an exceptions to be thrown.
     *
     * @return the bytes from the input stream in string form.
     *
     * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
     * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
     *         block the calling thread (non-blocking mode only).
     * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
     *         aCount amount of data.
     * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
     */
    readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream,
                                                                      aCount,
                                                                      aOptions)
    {
        if (!(aInputStream instanceof Ci.nsIInputStream)) {
            let exception = new Components.Exception(
                "First argument should be an nsIInputStream",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        if (!aCount) {
            let exception = new Components.Exception(
                "Non-zero amount of bytes must be specified",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        if (aOptions && "charset" in aOptions) {
          let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].
                    createInstance(Ci.nsIConverterInputStream);
          try {
            // When replacement is set, the character that is unknown sequence
            // replaces with aOptions.replacement character.
            if (!("replacement" in aOptions)) {
              // aOptions.replacement isn't set.
              // If input stream has unknown sequences for aOptions.charset,
              // throw NS_ERROR_ILLEGAL_INPUT.
              aOptions.replacement = 0;
            }

            cis.init(aInputStream, aOptions.charset, aCount,
                     aOptions.replacement);
            let str = {};
            cis.readString(-1, str);
            cis.close();
            return str.value;
          }
          catch (e) {
            // Adjust the stack so it throws at the caller's location.
            throw new Components.Exception(e.message, e.result,
                                           Components.stack.caller, e.data);
          }
        }

        let sis = Cc["@mozilla.org/scriptableinputstream;1"].
                  createInstance(Ci.nsIScriptableInputStream);
        sis.init(aInputStream);
        try {
            return sis.readBytes(aCount);
        }
        catch (e) {
            // Adjust the stack so it throws at the caller's location.
            throw new Components.Exception(e.message, e.result,
                                           Components.stack.caller, e.data);
        }
    },

    /**
     * Reads aCount bytes from aInputStream into a string.
     *
     * @param {nsIInputStream} aInputStream
     *        The input stream to read from.
     * @param {integer} [aCount = aInputStream.available()]
     *        The number of bytes to read from the stream.
     *
     * @return the bytes from the input stream in string form.
     *
     * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
     * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
     *         block the calling thread (non-blocking mode only).
     * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
     *         aCount amount of data.
     */
    readInputStream(aInputStream, aCount)
    {
        if (!(aInputStream instanceof Ci.nsIInputStream)) {
            let exception = new Components.Exception(
                "First argument should be an nsIInputStream",
                Cr.NS_ERROR_INVALID_ARG,
                Components.stack.caller
            );
            throw exception;
        }

        if (!aCount) {
            aCount = aInputStream.available();
        }

        let stream = new BinaryInputStream(aInputStream);
        let result = new ArrayBuffer(aCount);
        stream.readArrayBuffer(result.byteLength, result);
        return result;
    },

    /**
     * Returns a reference to nsIIOService.
     *
     * @return a reference to nsIIOService.
     */
    get ioService()
    {
        delete this.ioService;
        return this.ioService = Cc["@mozilla.org/network/io-service;1"].
                                getService(Ci.nsIIOService);
    },
};
PK
!<m9((modules/NewTabUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["NewTabUtils"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["btoa"]);

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
  "resource://gre/modules/PageThumbs.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
  "resource://gre/modules/BinarySearch.jsm");

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;
});

// Boolean preferences that control newtab content
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";

// The preference that tells the number of rows of the newtab grid.
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";

// The preference that tells the number of columns of the newtab grid.
const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";

// The maximum number of results PlacesProvider retrieves from history.
const HISTORY_RESULTS_LIMIT = 100;

// The maximum number of links Links.getLinks will return.
const LINKS_GET_LINKS_LIMIT = 100;

// The gather telemetry topic.
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";

// The number of top sites to display on Activity Stream page
const TOP_SITES_LENGTH = 6;

// Use double the number to allow for immediate display when blocking sites
const TOP_SITES_LIMIT = TOP_SITES_LENGTH * 2;

/**
 * Calculate the MD5 hash for a string.
 * @param aValue
 *        The string to convert.
 * @return The base64 representation of the MD5 hash.
 */
function toHash(aValue) {
  let value = gUnicodeConverter.convertToByteArray(aValue);
  gCryptoHash.init(gCryptoHash.MD5);
  gCryptoHash.update(value, value.length);
  return gCryptoHash.finish(true);
}

/**
 * Singleton that provides storage functionality.
 */
XPCOMUtils.defineLazyGetter(this, "Storage", function() {
  return new LinksStorage();
});

function LinksStorage() {
  // Handle migration of data across versions.
  try {
    if (this._storedVersion < this._version) {
      // This is either an upgrade, or version information is missing.
      if (this._storedVersion < 1) {
        // Version 1 moved data from DOM Storage to prefs.  Since migrating from
        // version 0 is no more supported, we just reportError a dataloss later.
        throw new Error("Unsupported newTab storage version");
      }
      // Add further migration steps here.
    } else {
      // This is a downgrade.  Since we cannot predict future, upgrades should
      // be backwards compatible.  We will set the version to the old value
      // regardless, so, on next upgrade, the migration steps will run again.
      // For this reason, they should also be able to run multiple times, even
      // on top of an already up-to-date storage.
    }
  } catch (ex) {
    // Something went wrong in the update process, we can't recover from here,
    // so just clear the storage and start from scratch (dataloss!).
    Components.utils.reportError(
      "Unable to migrate the newTab storage to the current version. " +
      "Restarting from scratch.\n" + ex);
    this.clear();
  }

  // Set the version to the current one.
  this._storedVersion = this._version;
}

LinksStorage.prototype = {
  get _version() {
    return 1;
  },

  get _prefs() {
    return Object.freeze({
      pinnedLinks: "browser.newtabpage.pinned",
      blockedLinks: "browser.newtabpage.blocked",
    });
  },

  get _storedVersion() {
    if (this.__storedVersion === undefined) {
      // When the pref is not set, the storage version is unknown, so either:
      // - it's a new profile
      // - it's a profile where versioning information got lost
      // In this case we still run through all of the valid migrations,
      // starting from 1, as if it was a downgrade.  As previously stated the
      // migrations should already support running on an updated store.
      this.__storedVersion =
        Services.prefs.getIntPref("browser.newtabpage.storageVersion", 1);
    }
    return this.__storedVersion;
  },
  set _storedVersion(aValue) {
    Services.prefs.setIntPref("browser.newtabpage.storageVersion", aValue);
    this.__storedVersion = aValue;
    return aValue;
  },

  /**
   * Gets the value for a given key from the storage.
   * @param aKey The storage key (a string).
   * @param aDefault A default value if the key doesn't exist.
   * @return The value for the given key.
   */
  get: function Storage_get(aKey, aDefault) {
    let value;
    try {
      let prefValue = Services.prefs.getStringPref(this._prefs[aKey]);
      value = JSON.parse(prefValue);
    } catch (e) {}
    return value || aDefault;
  },

  /**
   * Sets the storage value for a given key.
   * @param aKey The storage key (a string).
   * @param aValue The value to set.
   */
  set: function Storage_set(aKey, aValue) {
    // Page titles may contain unicode, thus use complex values.
    Services.prefs.setStringPref(this._prefs[aKey], JSON.stringify(aValue));
  },

  /**
   * Removes the storage value for a given key.
   * @param aKey The storage key (a string).
   */
  remove: function Storage_remove(aKey) {
    Services.prefs.clearUserPref(this._prefs[aKey]);
  },

  /**
   * Clears the storage and removes all values.
   */
  clear: function Storage_clear() {
    for (let key in this._prefs) {
      this.remove(key);
    }
  }
};


/**
 * Singleton that serves as a registry for all open 'New Tab Page's.
 */
var AllPages = {
  /**
   * The array containing all active pages.
   */
  _pages: [],

  /**
   * Cached value that tells whether the New Tab Page feature is enabled.
   */
  _enabled: null,

  /**
   * Cached value that tells whether the New Tab Page feature is enhanced.
   */
  _enhanced: null,

  /**
   * Adds a page to the internal list of pages.
   * @param aPage The page to register.
   */
  register: function AllPages_register(aPage) {
    this._pages.push(aPage);
    this._addObserver();
  },

  /**
   * Removes a page from the internal list of pages.
   * @param aPage The page to unregister.
   */
  unregister: function AllPages_unregister(aPage) {
    let index = this._pages.indexOf(aPage);
    if (index > -1)
      this._pages.splice(index, 1);
  },

  /**
   * Returns whether the 'New Tab Page' is enabled.
   */
  get enabled() {
    if (this._enabled === null)
      this._enabled = Services.prefs.getBoolPref(PREF_NEWTAB_ENABLED);

    return this._enabled;
  },

  /**
   * Enables or disables the 'New Tab Page' feature.
   */
  set enabled(aEnabled) {
    if (this.enabled != aEnabled)
      Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled);
  },

  /**
   * Returns whether the history tiles are enhanced.
   */
  get enhanced() {
    if (this._enhanced === null)
      this._enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);

    return this._enhanced;
  },

  /**
   * Enables or disables the enhancement of history tiles feature.
   */
  set enhanced(aEnhanced) {
    if (this.enhanced != aEnhanced)
      Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, !!aEnhanced);
  },

  /**
   * Returns the number of registered New Tab Pages (i.e. the number of open
   * about:newtab instances).
   */
  get length() {
    return this._pages.length;
  },

  /**
   * Updates all currently active pages but the given one.
   * @param aExceptPage The page to exclude from updating.
   * @param aReason The reason for updating all pages.
   */
  update(aExceptPage, aReason = "") {
    for (let page of this._pages.slice()) {
      if (aExceptPage != page) {
        page.update(aReason);
      }
    }
  },

  /**
   * Implements the nsIObserver interface to get notified when the preference
   * value changes or when a new copy of a page thumbnail is available.
   */
  observe: function AllPages_observe(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed") {
      // Clear the cached value.
      switch (aData) {
        case PREF_NEWTAB_ENABLED:
          this._enabled = null;
          break;
        case PREF_NEWTAB_ENHANCED:
          this._enhanced = null;
          break;
      }
    }
    // and all notifications get forwarded to each page.
    this._pages.forEach(function(aPage) {
      aPage.observe(aSubject, aTopic, aData);
    }, this);
  },

  /**
   * Adds a preference and new thumbnail observer and turns itself into a
   * no-op after the first invokation.
   */
  _addObserver: function AllPages_addObserver() {
    Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
    Services.prefs.addObserver(PREF_NEWTAB_ENHANCED, this, true);
    Services.obs.addObserver(this, "page-thumbnail:create", true);
    this._addObserver = function() {};
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference])
};

/**
 * Singleton that keeps Grid preferences
 */
var GridPrefs = {
  /**
   * Cached value that tells the number of rows of newtab grid.
   */
  _gridRows: null,
  get gridRows() {
    if (!this._gridRows) {
      this._gridRows = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_ROWS));
    }

    return this._gridRows;
  },

  /**
   * Cached value that tells the number of columns of newtab grid.
   */
  _gridColumns: null,
  get gridColumns() {
    if (!this._gridColumns) {
      this._gridColumns = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_COLUMNS));
    }

    return this._gridColumns;
  },


  /**
   * Initializes object. Adds a preference observer
   */
  init: function GridPrefs_init() {
    Services.prefs.addObserver(PREF_NEWTAB_ROWS, this);
    Services.prefs.addObserver(PREF_NEWTAB_COLUMNS, this);
  },

 /**
  * Uninitializes object. Removes the preference observers
  */
  uninit: function GridPrefs_uninit() {
    Services.prefs.removeObserver(PREF_NEWTAB_ROWS, this);
    Services.prefs.removeObserver(PREF_NEWTAB_COLUMNS, this);
  },

  /**
   * Implements the nsIObserver interface to get notified when the preference
   * value changes.
   */
  observe: function GridPrefs_observe(aSubject, aTopic, aData) {
    if (aData == PREF_NEWTAB_ROWS) {
      this._gridRows = null;
    } else {
      this._gridColumns = null;
    }

    AllPages.update();
  }
};

GridPrefs.init();

/**
 * Singleton that keeps track of all pinned links and their positions in the
 * grid.
 */
var PinnedLinks = {
  /**
   * The cached list of pinned links.
   */
  _links: null,

  /**
   * The array of pinned links.
   */
  get links() {
    if (!this._links)
      this._links = Storage.get("pinnedLinks", []);

    return this._links;
  },

  /**
   * Pins a link at the given position.
   * @param aLink The link to pin.
   * @param aIndex The grid index to pin the cell at.
   * @return true if link changes, false otherwise
   */
  pin: function PinnedLinks_pin(aLink, aIndex) {
    // Clear the link's old position, if any.
    this.unpin(aLink);

    // change pinned link into a history link
    let changed = this._makeHistoryLink(aLink);
    this.links[aIndex] = aLink;
    this.save();
    return changed;
  },

  /**
   * Unpins a given link.
   * @param aLink The link to unpin.
   */
  unpin: function PinnedLinks_unpin(aLink) {
    let index = this._indexOfLink(aLink);
    if (index == -1)
      return;
    let links = this.links;
    links[index] = null;
    // trim trailing nulls
    let i = links.length - 1;
    while (i >= 0 && links[i] == null)
      i--;
    links.splice(i + 1);
    this.save();
  },

  /**
   * Saves the current list of pinned links.
   */
  save: function PinnedLinks_save() {
    Storage.set("pinnedLinks", this.links);
  },

  /**
   * Checks whether a given link is pinned.
   * @params aLink The link to check.
   * @return whether The link is pinned.
   */
  isPinned: function PinnedLinks_isPinned(aLink) {
    return this._indexOfLink(aLink) != -1;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function PinnedLinks_resetCache() {
    this._links = null;
  },

  /**
   * Finds the index of a given link in the list of pinned links.
   * @param aLink The link to find an index for.
   * @return The link's index.
   */
  _indexOfLink: function PinnedLinks_indexOfLink(aLink) {
    for (let i = 0; i < this.links.length; i++) {
      let link = this.links[i];
      if (link && link.url == aLink.url)
        return i;
    }

    // The given link is unpinned.
    return -1;
  },

  /**
   * Transforms link into a "history" link
   * @param aLink The link to change
   * @return true if link changes, false otherwise
   */
  _makeHistoryLink: function PinnedLinks_makeHistoryLink(aLink) {
    if (!aLink.type || aLink.type == "history") {
      return false;
    }
    aLink.type = "history";
    return true;
  },

  /**
   * Replaces existing link with another link.
   * @param aUrl The url of existing link
   * @param aLink The replacement link
   */
  replace: function PinnedLinks_replace(aUrl, aLink) {
    let index = this._indexOfLink({url: aUrl});
    if (index == -1) {
      return;
    }
    this.links[index] = aLink;
    this.save();
  },

};

/**
 * Singleton that keeps track of all blocked links in the grid.
 */
var BlockedLinks = {
  /**
   * A list of objects that are observing blocked link changes.
   */
  _observers: [],

  /**
   * The cached list of blocked links.
   */
  _links: null,

  /**
   * Registers an object that will be notified when the blocked links change.
   */
  addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  /**
   * Remove the observers.
   */
  removeObservers() {
    this._observers = [];
  },

  /**
   * The list of blocked links.
   */
  get links() {
    if (!this._links)
      this._links = Storage.get("blockedLinks", {});

    return this._links;
  },

  /**
   * Blocks a given link. Adjusts siteMap accordingly, and notifies listeners.
   * @param aLink The link to block.
   */
  block: function BlockedLinks_block(aLink) {
    this._callObservers("onLinkBlocked", aLink);
    this.links[toHash(aLink.url)] = 1;
    this.save();

    // Make sure we unpin blocked links.
    PinnedLinks.unpin(aLink);
  },

  /**
   * Unblocks a given link. Adjusts siteMap accordingly, and notifies listeners.
   * @param aLink The link to unblock.
   */
  unblock: function BlockedLinks_unblock(aLink) {
    if (this.isBlocked(aLink)) {
      delete this.links[toHash(aLink.url)];
      this.save();
      this._callObservers("onLinkUnblocked", aLink);
    }
  },

  /**
   * Saves the current list of blocked links.
   */
  save: function BlockedLinks_save() {
    Storage.set("blockedLinks", this.links);
  },

  /**
   * Returns whether a given link is blocked.
   * @param aLink The link to check.
   */
  isBlocked: function BlockedLinks_isBlocked(aLink) {
    return (toHash(aLink.url) in this.links);
  },

  /**
   * Checks whether the list of blocked links is empty.
   * @return Whether the list is empty.
   */
  isEmpty: function BlockedLinks_isEmpty() {
    return Object.keys(this.links).length == 0;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function BlockedLinks_resetCache() {
    this._links = null;
  },

  _callObservers(methodName, ...args) {
    for (let obs of this._observers) {
      if (typeof(obs[methodName]) == "function") {
        try {
          obs[methodName](...args);
        } catch (err) {
          Cu.reportError(err);
        }
      }
    }
  }
};

/**
 * Singleton that serves as the default link provider for the grid. It queries
 * the history to retrieve the most frequently visited sites.
 */
var PlacesProvider = {
  /**
   * A count of how many batch updates are under way (batches may be nested, so
   * we keep a counter instead of a simple bool).
   **/
  _batchProcessingDepth: 0,

  /**
   * A flag that tracks whether onFrecencyChanged was notified while a batch
   * operation was in progress, to tell us whether to take special action after
   * the batch operation completes.
   **/
  _batchCalledFrecencyChanged: false,

  /**
   * Set this to change the maximum number of links the provider will provide.
   */
  maxNumLinks: HISTORY_RESULTS_LIMIT,

  /**
   * Must be called before the provider is used.
   */
  init: function PlacesProvider_init() {
    PlacesUtils.history.addObserver(this, true);
  },

  /**
   * Gets the current set of links delivered by this provider.
   * @param aCallback The function that the array of links is passed to.
   */
  getLinks: function PlacesProvider_getLinks(aCallback) {
    let options = PlacesUtils.history.getNewQueryOptions();
    options.maxResults = this.maxNumLinks;

    // Sort by frecency, descending.
    options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING

    let links = [];

    let callback = {
      handleResult(aResultSet) {
        let row;

        while ((row = aResultSet.getNextRow())) {
          let url = row.getResultByIndex(1);
          if (LinkChecker.checkLoadURI(url)) {
            let title = row.getResultByIndex(2);
            let frecency = row.getResultByIndex(12);
            let lastVisitDate = row.getResultByIndex(5);
            links.push({
              url,
              title,
              frecency,
              lastVisitDate,
              type: "history",
            });
          }
        }
      },

      handleError(aError) {
        // Should we somehow handle this error?
        aCallback([]);
      },

      handleCompletion(aReason) {
        // The Places query breaks ties in frecency by place ID descending, but
        // that's different from how Links.compareLinks breaks ties, because
        // compareLinks doesn't have access to place IDs.  It's very important
        // that the initial list of links is sorted in the same order imposed by
        // compareLinks, because Links uses compareLinks to perform binary
        // searches on the list.  So, ensure the list is so ordered.
        let i = 1;
        let outOfOrder = [];
        while (i < links.length) {
          if (Links.compareLinks(links[i - 1], links[i]) > 0)
            outOfOrder.push(links.splice(i, 1)[0]);
          else
            i++;
        }
        for (let link of outOfOrder) {
          i = BinarySearch.insertionIndexOf(Links.compareLinks, links, link);
          links.splice(i, 0, link);
        }

        aCallback(links);
      }
    };

    // Execute the query.
    let query = PlacesUtils.history.getNewQuery();
    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase);
    db.asyncExecuteLegacyQueries([query], 1, options, callback);
  },

  /**
   * Registers an object that will be notified when the provider's links change.
   * @param aObserver An object with the following optional properties:
   *        * onLinkChanged: A function that's called when a single link
   *          changes.  It's passed the provider and the link object.  Only the
   *          link's `url` property is guaranteed to be present.  If its `title`
   *          property is present, then its title has changed, and the
   *          property's value is the new title.  If any sort properties are
   *          present, then its position within the provider's list of links may
   *          have changed, and the properties' values are the new sort-related
   *          values.  Note that this link may not necessarily have been present
   *          in the lists returned from any previous calls to getLinks.
   *        * onManyLinksChanged: A function that's called when many links
   *          change at once.  It's passed the provider.  You should call
   *          getLinks to get the provider's new list of links.
   */
  addObserver: function PlacesProvider_addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  _observers: [],

  /**
   * Called by the history service.
   */
  onBeginUpdateBatch() {
    this._batchProcessingDepth += 1;
  },

  onEndUpdateBatch() {
    this._batchProcessingDepth -= 1;
    if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
      this.onManyFrecenciesChanged();
      this._batchCalledFrecencyChanged = false;
    }
  },

  onVisit(aURI, aVisitId, aTime, aSessionId, aReferrerVisitId, aTransitionType,
          aGuid, aHidden, aVisitCount, aTyped, aLastKnownTitle) {
    // For new visits, if we're not batch processing, notify for a title // update
    if (!this._batchProcessingDepth && aVisitCount == 1 && aLastKnownTitle) {
      this.onTitleChanged(aURI, aLastKnownTitle, aGuid);
    }
  },

  onDeleteURI: function PlacesProvider_onDeleteURI(aURI, aGUID, aReason) {
    // let observers remove sensetive data associated with deleted visit
    this._callObservers("onDeleteURI", {
      url: aURI.spec,
    });
  },

  onClearHistory() {
    this._callObservers("onClearHistory")
  },

  /**
   * Called by the history service.
   */
  onFrecencyChanged: function PlacesProvider_onFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, aLastVisitDate) {
    // If something is doing a batch update of history entries we don't want
    // to do lots of work for each record. So we just track the fact we need
    // to call onManyFrecenciesChanged() once the batch is complete.
    if (this._batchProcessingDepth > 0) {
      this._batchCalledFrecencyChanged = true;
      return;
    }
    // The implementation of the query in getLinks excludes hidden and
    // unvisited pages, so it's important to exclude them here, too.
    if (!aHidden && aLastVisitDate) {
      this._callObservers("onLinkChanged", {
        url: aURI.spec,
        frecency: aNewFrecency,
        lastVisitDate: aLastVisitDate,
        type: "history",
      });
    }
  },

  /**
   * Called by the history service.
   */
  onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
    this._callObservers("onManyLinksChanged");
  },

  /**
   * Called by the history service.
   */
  onTitleChanged: function PlacesProvider_onTitleChanged(aURI, aNewTitle, aGUID) {
    this._callObservers("onLinkChanged", {
      url: aURI.spec,
      title: aNewTitle
    });
  },

  _callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
    for (let obs of this._observers) {
      if (obs[aMethodName]) {
        try {
          obs[aMethodName](this, aArg);
        } catch (err) {
          Cu.reportError(err);
        }
      }
    }
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
                                         Ci.nsISupportsWeakReference]),
};

/**
 * Queries history to retrieve the most frecent sites. Emits events when the
 * history changes.
 */
var ActivityStreamProvider = {

  /**
   * Process links after getting them from the database.
   *
   * @param {Array} aLinks
   *          an array containing link objects
   *
   * @returns {Array} an array of checked links with favicons and eTLDs added
   */
  _processLinks(aLinks) {
    let links_ = aLinks.filter(link => LinkChecker.checkLoadURI(link.url));
    links_ = this._faviconBytesToDataURI(links_);
    return this._addETLD(links_);
  },

  /**
   * From an Array of links, if favicons are present, convert to data URIs
   *
   * @param {Array} aLinks
   *          an array containing objects with favicon data and mimeTypes
   *
   * @returns {Array} an array of links with favicons as data uri
   */
  _faviconBytesToDataURI(aLinks) {
    return aLinks.map(link => {
      if (link.favicon) {
        let encodedData = btoa(String.fromCharCode.apply(null, link.favicon));
        link.favicon = `data:${link.mimeType};base64,${encodedData}`;
      }
      delete link.mimeType;
      return link;
    });
  },

  /**
   * Computes favicon data for each url in a set of links
   *
   * @param {Array} links
   *          an array containing objects without favicon data or mimeTypes yet
   *
   * @returns {Promise} Returns a promise with the array of links with favicon data,
   *                    mimeType, and byte array length
   */
  async _addFavicons(aLinks) {
    if (aLinks.length) {
      // Each link in the array needs a favicon for it's page - so we fire off
      // a promise for each link to compute the favicon data and attach it back
      // to the original link object. We must wait until all favicons for
      // the array of links are computed before returning
      await Promise.all(aLinks.map(link => new Promise(resolve => {
        return PlacesUtils.favicons.getFaviconDataForPage(
            Services.io.newURI(link.url),
            (iconuri, len, data, mime) => {
              // Due to the asynchronous behaviour of inserting a favicon into
              // moz_favicons, the data may not be available to us just yet,
              // since we listen on a history entry being inserted. As a result,
              // we don't want to throw if the icon uri is not here yet, we
              // just want to resolve on an empty favicon. Activity Stream
              // knows how to handle null favicons
              if (!iconuri) {
                link.favicon = null;
                link.mimeType = null;
              } else {
                link.favicon = data;
                link.mimeType = mime;
                link.faviconLength = len;
              }
              return resolve(link);
            });
        }).catch(() => {
          // If something goes wrong - that's ok - just return a null favicon
          // without rejecting the entire Promise.all
          link.favicon = null;
          link.mimeType = null;
          return link;
        })
      ));
    }
    return aLinks;
  },

  /**
   * Add the eTLD to each link in the array of links.
   *
   * @param {Array} aLinks
   *          an array containing objects with urls
   *
   * @returns {Array} an array of links with eTLDs added
   */
  _addETLD(aLinks) {
    return aLinks.map(link => {
      try {
        link.eTLD = Services.eTLD.getPublicSuffix(Services.io.newURI(link.url));
      } catch (e) {
        link.eTLD = "";
      }
      return link;
    });
  },

  /*
   * Gets the top frecent sites for Activity Stream.
   *
   * @param {Object} aOptions
   *          options.ignoreBlocked: Do not filter out blocked links .
   *
   * @returns {Promise} Returns a promise with the array of links as payload.
   */
  async getTopFrecentSites(aOptions = {}) {
    let {ignoreBlocked} = aOptions;

    // GROUP first by rev_host to get the most-frecent page of an exact host
    // then GROUP by rev_nowww to dedupe between top two pages of nowww host.
    // Note that unlike mysql, sqlite picks the last raw from groupby bucket.
    // Which is why subselect orders frecency and last_visit_date backwards.
    // In general the groupby behavior in the absence of aggregates is not
    // defined in SQL, hence we are relying on sqlite implementation that may
    // change in the future.

    const limit = Object.keys(BlockedLinks.links).length + TOP_SITES_LIMIT;
    let sqlQuery = `/* do not warn (bug N/A): do not need index */
                    SELECT url, title, SUM(frecency) frecency, guid, bookmarkGuid,
                     last_visit_date / 1000 as lastVisitDate, "history" as type
                    FROM (SELECT * FROM (
                      SELECT
                        rev_host,
                        fixup_url(get_unreversed_host(rev_host)) AS rev_nowww,
                        moz_places.url,
                        moz_places.title,
                        frecency,
                        last_visit_date,
                        moz_places.guid AS guid,
                        moz_bookmarks.guid AS bookmarkGuid
                      FROM moz_places
                      LEFT JOIN moz_bookmarks
                      on moz_places.id = moz_bookmarks.fk
                      WHERE hidden = 0 AND last_visit_date NOTNULL
                      AND (SUBSTR(moz_places.url, 1, 6) == "https:" OR SUBSTR(moz_places.url, 1, 5) == "http:")
                      ORDER BY frecency, last_visit_date, moz_places.url DESC
                    ) GROUP BY rev_host)
                    GROUP BY rev_nowww
                    ORDER BY frecency DESC, lastVisitDate DESC, url
                    LIMIT ${limit}`;

    let links = await this.executePlacesQuery(sqlQuery, {
      columns: [
        "bookmarkGuid",
        "frecency",
        "guid",
        "lastVisitDate",
        "title",
        "type",
        "url"
      ]
    });

    if (!ignoreBlocked) {
      links = links.filter(link => !BlockedLinks.isBlocked(link));
    }
    links = links.slice(0, TOP_SITES_LIMIT);
    links = await this._addFavicons(links);
    return this._processLinks(links);
  },

  /**
   * Gets a specific bookmark given an id
   *
   * @param {String} aGuid
   *          A bookmark guid to use as a refrence to fetch the bookmark
   */
  async getBookmark(aGuid) {
    let bookmark = await PlacesUtils.bookmarks.fetch(aGuid);
    if (!bookmark) {
      return null;
    }
    let result = {};
    result.bookmarkGuid = bookmark.guid;
    result.bookmarkTitle = bookmark.title;
    result.lastModified = bookmark.lastModified.getTime();
    result.url = bookmark.url.href;
    return result;
  },

  /**
   * Gets History size
   *
   * @returns {Promise} Returns a promise with the count of moz_places records
   */
  async getHistorySize() {
    let sqlQuery = `SELECT count(*) FROM moz_places
                    WHERE hidden = 0 AND last_visit_date NOT NULL`;

    let result = await this.executePlacesQuery(sqlQuery);
    return result;
  },

  /**
   * Gets Bookmarks count
   *
   * @returns {Promise} Returns a promise with the count of bookmarks
   */
  async getBookmarksSize() {
    let sqlQuery = `SELECT count(*) FROM moz_bookmarks WHERE type = :type`;

    let result = await this.executePlacesQuery(sqlQuery, {params: {type: PlacesUtils.bookmarks.TYPE_BOOKMARK}});
    return result;
  },

  /**
   * Executes arbitrary query against places database
   *
   * @param {String} aQuery
   *        SQL query to execute
   * @param {Object} [optional] aOptions
   *          aOptions.columns - an array of column names. if supplied the return
   *          items will consists of objects keyed on column names. Otherwise
   *          array of raw values is returned in the select order
   *          aOptions.param - an object of SQL binding parameters
   *
   * @returns {Promise} Returns a promise with the array of retrieved items
   */
  async executePlacesQuery(aQuery, aOptions = {}) {
    let {columns, params} = aOptions;
    let items = [];
    let queryError = null;
    let conn = await PlacesUtils.promiseDBConnection();
    await conn.executeCached(aQuery, params, aRow => {
      try {
        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;
  }
};

/**
 * A set of actions which influence what sites shown on the Activity Stream page
 */
var ActivityStreamLinks = {
  /**
   * Block a url
   *
   * @param {Object} aLink
   *          The link which contains a URL to add to the block list
   */
  blockURL(aLink) {
    BlockedLinks.block(aLink);
  },

  onLinkBlocked(aLink) {
    Services.obs.notifyObservers(null, "newtab-linkBlocked", aLink.url);
  },

  /**
   * Adds a bookmark
   *
   * @param {String} aUrl
   *          The url to bookmark
   *
   * @returns {Promise} Returns a promise set to an object representing the bookmark
   */
  addBookmark(aUrl) {
    return PlacesUtils.bookmarks.insert({
      url: aUrl,
      parentGuid: PlacesUtils.bookmarks.unfiledGuid
    });
  },

  /**
   * Removes a bookmark
   *
   * @param {String} aBookmarkGuid
   *          The bookmark guid associated with the bookmark to remove
   *
   * @returns {Promise} Returns a promise set to an object representing the
   *            removed bookmark
   */
  deleteBookmark(aBookmarkGuid) {
    return PlacesUtils.bookmarks.remove(aBookmarkGuid);
  },

  /**
   * Removes a history link
   *
   * @param {String} aUrl
   *           The url to be removed from history
   *
   * @returns {Promise} Returns a promise set to true if link was removed
   */
  deleteHistoryEntry(aUrl) {
    return PlacesUtils.history.remove(aUrl);
  },

  /**
   * Get the top sites to show on Activity Stream
   *
   * @return {Promise} Returns a promise with the array of links as the payload
   */
  async getTopSites(aOptions = {}) {
    return ActivityStreamProvider.getTopFrecentSites(aOptions);
  }
};

/**
 * Singleton that provides access to all links contained in the grid (including
 * the ones that don't fit on the grid). A link is a plain object that looks
 * like this:
 *
 * {
 *   url: "http://www.mozilla.org/",
 *   title: "Mozilla",
 *   frecency: 1337,
 *   lastVisitDate: 1394678824766431,
 * }
 */
var Links = {
  /**
   * The maximum number of links returned by getLinks.
   */
  maxNumLinks: LINKS_GET_LINKS_LIMIT,

  /**
   * A mapping from each provider to an object { sortedLinks, siteMap, linkMap }.
   * sortedLinks is the cached, sorted array of links for the provider.
   * siteMap is a mapping from base domains to URL count associated with the domain.
   *         The count does not include blocked URLs. siteMap is used to look up a
   *         user's top sites that can be targeted with a suggested tile.
   * linkMap is a Map from link URLs to link objects.
   */
  _providers: new Map(),

  /**
   * The properties of link objects used to sort them.
   */
  _sortProperties: [
    "frecency",
    "lastVisitDate",
    "url",
  ],

  /**
   * List of callbacks waiting for the cache to be populated.
   */
  _populateCallbacks: [],

  /**
   * A list of objects that are observing links updates.
   */
  _observers: [],

  /**
   * Registers an object that will be notified when links updates.
   */
  addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  /**
   * Adds a link provider.
   * @param aProvider The link provider.
   */
  addProvider: function Links_addProvider(aProvider) {
    this._providers.set(aProvider, null);
    aProvider.addObserver(this);
  },

  /**
   * Removes a link provider.
   * @param aProvider The link provider.
   */
  removeProvider: function Links_removeProvider(aProvider) {
    if (!this._providers.delete(aProvider))
      throw new Error("Unknown provider");
  },

  /**
   * Populates the cache with fresh links from the providers.
   * @param aCallback The callback to call when finished (optional).
   * @param aForce When true, populates the cache even when it's already filled.
   */
  populateCache: function Links_populateCache(aCallback, aForce) {
    let callbacks = this._populateCallbacks;

    // Enqueue the current callback.
    callbacks.push(aCallback);

    // There was a callback waiting already, thus the cache has not yet been
    // populated.
    if (callbacks.length > 1)
      return;

    function executeCallbacks() {
      while (callbacks.length) {
        let callback = callbacks.shift();
        if (callback) {
          try {
            callback();
          } catch (e) {
            // We want to proceed even if a callback fails.
          }
        }
      }
    }

    let numProvidersRemaining = this._providers.size;
    for (let [provider /* , links */] of this._providers) {
      this._populateProviderCache(provider, () => {
        if (--numProvidersRemaining == 0)
          executeCallbacks();
      }, aForce);
    }

    this._addObserver();
  },

  /**
   * Gets the current set of links contained in the grid.
   * @return The links in the grid.
   */
  getLinks: function Links_getLinks() {
    let pinnedLinks = Array.slice(PinnedLinks.links);
    let links = this._getMergedProviderLinks();

    let sites = new Set();
    for (let link of pinnedLinks) {
      if (link)
        sites.add(NewTabUtils.extractSite(link.url));
    }

    // Filter blocked and pinned links and duplicate base domains.
    links = links.filter(function(link) {
      let site = NewTabUtils.extractSite(link.url);
      if (site == null || sites.has(site))
        return false;
      sites.add(site);

      return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
    });

    // Try to fill the gaps between pinned links.
    for (let i = 0; i < pinnedLinks.length && links.length; i++)
      if (!pinnedLinks[i])
        pinnedLinks[i] = links.shift();

    // Append the remaining links if any.
    if (links.length)
      pinnedLinks = pinnedLinks.concat(links);

    for (let link of pinnedLinks) {
      if (link) {
        link.baseDomain = NewTabUtils.extractSite(link.url);
      }
    }
    return pinnedLinks;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function Links_resetCache() {
    for (let provider of this._providers.keys()) {
      this._providers.set(provider, null);
    }
  },

  /**
   * Compares two links.
   * @param aLink1 The first link.
   * @param aLink2 The second link.
   * @return A negative number if aLink1 is ordered before aLink2, zero if
   *         aLink1 and aLink2 have the same ordering, or a positive number if
   *         aLink1 is ordered after aLink2.
   *
   * @note compareLinks's this object is bound to Links below.
   */
  compareLinks: function Links_compareLinks(aLink1, aLink2) {
    for (let prop of this._sortProperties) {
      if (!(prop in aLink1) || !(prop in aLink2))
        throw new Error("Comparable link missing required property: " + prop);
    }
    return aLink2.frecency - aLink1.frecency ||
           aLink2.lastVisitDate - aLink1.lastVisitDate ||
           aLink1.url.localeCompare(aLink2.url);
  },

  _incrementSiteMap(map, link) {
    if (NewTabUtils.blockedLinks.isBlocked(link)) {
      // Don't count blocked URLs.
      return;
    }
    let site = NewTabUtils.extractSite(link.url);
    map.set(site, (map.get(site) || 0) + 1);
  },

  _decrementSiteMap(map, link) {
    if (NewTabUtils.blockedLinks.isBlocked(link)) {
      // Blocked URLs are not included in map.
      return;
    }
    let site = NewTabUtils.extractSite(link.url);
    let previousURLCount = map.get(site);
    if (previousURLCount === 1) {
      map.delete(site);
    } else {
      map.set(site, previousURLCount - 1);
    }
  },

  /**
    * Update the siteMap cache based on the link given and whether we need
    * to increment or decrement it. We do this by iterating over all stored providers
    * to find which provider this link already exists in. For providers that
    * have this link, we will adjust siteMap for them accordingly.
    *
    * @param aLink The link that will affect siteMap
    * @param increment A boolean for whether to increment or decrement siteMap
    */
  _adjustSiteMapAndNotify(aLink, increment = true) {
    for (let [/* provider */, cache] of this._providers) {
      // We only update siteMap if aLink is already stored in linkMap.
      if (cache.linkMap.get(aLink.url)) {
        if (increment) {
          this._incrementSiteMap(cache.siteMap, aLink);
          continue;
        }
        this._decrementSiteMap(cache.siteMap, aLink);
      }
    }
    this._callObservers("onLinkChanged", aLink);
  },

  onLinkBlocked(aLink) {
    this._adjustSiteMapAndNotify(aLink, false);
  },

  onLinkUnblocked(aLink) {
    this._adjustSiteMapAndNotify(aLink);
  },

  populateProviderCache(provider, callback) {
    if (!this._providers.has(provider)) {
      throw new Error("Can only populate provider cache for existing provider.");
    }

    return this._populateProviderCache(provider, callback, false);
  },

  /**
   * Calls getLinks on the given provider and populates our cache for it.
   * @param aProvider The provider whose cache will be populated.
   * @param aCallback The callback to call when finished.
   * @param aForce When true, populates the provider's cache even when it's
   *               already filled.
   */
  _populateProviderCache(aProvider, aCallback, aForce) {
    let cache = this._providers.get(aProvider);
    let createCache = !cache;
    if (createCache) {
      cache = {
        // Start with a resolved promise.
        populatePromise: new Promise(resolve => resolve()),
      };
      this._providers.set(aProvider, cache);
    }
    // Chain the populatePromise so that calls are effectively queued.
    cache.populatePromise = cache.populatePromise.then(() => {
      return new Promise(resolve => {
        if (!createCache && !aForce) {
          aCallback();
          resolve();
          return;
        }
        aProvider.getLinks(links => {
          // Filter out null and undefined links so we don't have to deal with
          // them in getLinks when merging links from providers.
          links = links.filter((link) => !!link);
          cache.sortedLinks = links;
          cache.siteMap = links.reduce((map, link) => {
            this._incrementSiteMap(map, link);
            return map;
          }, new Map());
          cache.linkMap = links.reduce((map, link) => {
            map.set(link.url, link);
            return map;
          }, new Map());
          aCallback();
          resolve();
        });
      });
    });
  },

  /**
   * Merges the cached lists of links from all providers whose lists are cached.
   * @return The merged list.
   */
  _getMergedProviderLinks: function Links__getMergedProviderLinks() {
    // Build a list containing a copy of each provider's sortedLinks list.
    let linkLists = [];
    for (let provider of this._providers.keys()) {
      if (!AllPages.enhanced && provider != PlacesProvider) {
        // Only show history tiles if we're not in 'enhanced' mode.
        continue;
      }
      let links = this._providers.get(provider);
      if (links && links.sortedLinks) {
        linkLists.push(links.sortedLinks.slice());
      }
    }

    return this.mergeLinkLists(linkLists);
  },

  mergeLinkLists: function Links_mergeLinkLists(linkLists) {
    if (linkLists.length == 1) {
      return linkLists[0];
    }

    function getNextLink() {
      let minLinks = null;
      for (let links of linkLists) {
        if (links.length &&
            (!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0))
          minLinks = links;
      }
      return minLinks ? minLinks.shift() : null;
    }

    let finalLinks = [];
    for (let nextLink = getNextLink();
         nextLink && finalLinks.length < this.maxNumLinks;
         nextLink = getNextLink()) {
      finalLinks.push(nextLink);
    }

    return finalLinks;
  },

  /**
   * Called by a provider to notify us when a single link changes.
   * @param aProvider The provider whose link changed.
   * @param aLink The link that changed.  If the link is new, it must have all
   *              of the _sortProperties.  Otherwise, it may have as few or as
   *              many as is convenient.
   * @param aIndex The current index of the changed link in the sortedLinks
                   cache in _providers. Defaults to -1 if the provider doesn't know the index
   * @param aDeleted Boolean indicating if the provider has deleted the link.
   */
  onLinkChanged: function Links_onLinkChanged(aProvider, aLink, aIndex = -1, aDeleted = false) {
    if (!("url" in aLink))
      throw new Error("Changed links must have a url property");

    let links = this._providers.get(aProvider);
    if (!links)
      // This is not an error, it just means that between the time the provider
      // was added and the future time we call getLinks on it, it notified us of
      // a change.
      return;

    let { sortedLinks, siteMap, linkMap } = links;
    let existingLink = linkMap.get(aLink.url);
    let insertionLink = null;
    let updatePages = false;

    if (existingLink) {
      // Update our copy's position in O(lg n) by first removing it from its
      // list.  It's important to do this before modifying its properties.
      if (this._sortProperties.some(prop => prop in aLink)) {
        let idx = aIndex;
        if (idx < 0) {
          idx = this._indexOf(sortedLinks, existingLink);
        } else if (this.compareLinks(aLink, sortedLinks[idx]) != 0) {
          throw new Error("aLink should be the same as sortedLinks[idx]");
        }

        if (idx < 0) {
          throw new Error("Link should be in _sortedLinks if in _linkMap");
        }
        sortedLinks.splice(idx, 1);

        if (aDeleted) {
          updatePages = true;
          linkMap.delete(existingLink.url);
          this._decrementSiteMap(siteMap, existingLink);
        } else {
          // Update our copy's properties.
          Object.assign(existingLink, aLink);

          // Finally, reinsert our copy below.
          insertionLink = existingLink;
        }
      }
      // Update our copy's title in O(1).
      if ("title" in aLink && aLink.title != existingLink.title) {
        existingLink.title = aLink.title;
        updatePages = true;
      }
    } else if (this._sortProperties.every(prop => prop in aLink)) {
      // Before doing the O(lg n) insertion below, do an O(1) check for the
      // common case where the new link is too low-ranked to be in the list.
      if (sortedLinks.length && sortedLinks.length == aProvider.maxNumLinks) {
        let lastLink = sortedLinks[sortedLinks.length - 1];
        if (this.compareLinks(lastLink, aLink) < 0) {
          return;
        }
      }
      // Copy the link object so that changes later made to it by the caller
      // don't affect our copy.
      insertionLink = {};
      for (let prop in aLink) {
        insertionLink[prop] = aLink[prop];
      }
      linkMap.set(aLink.url, insertionLink);
      this._incrementSiteMap(siteMap, aLink);
    }

    if (insertionLink) {
      let idx = this._insertionIndexOf(sortedLinks, insertionLink);
      sortedLinks.splice(idx, 0, insertionLink);
      if (sortedLinks.length > aProvider.maxNumLinks) {
        let lastLink = sortedLinks.pop();
        linkMap.delete(lastLink.url);
        this._decrementSiteMap(siteMap, lastLink);
      }
      updatePages = true;
    }

    if (updatePages) {
      AllPages.update(null, "links-changed");
    }
  },

  /**
   * Called by a provider to notify us when many links change.
   */
  onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
    this._populateProviderCache(aProvider, () => {
      AllPages.update(null, "links-changed");
    }, true);
  },

  _indexOf: function Links__indexOf(aArray, aLink) {
    return this._binsearch(aArray, aLink, "indexOf");
  },

  _insertionIndexOf: function Links__insertionIndexOf(aArray, aLink) {
    return this._binsearch(aArray, aLink, "insertionIndexOf");
  },

  _binsearch: function Links__binsearch(aArray, aLink, aMethod) {
    return BinarySearch[aMethod](this.compareLinks, aArray, aLink);
  },

  /**
   * Implements the nsIObserver interface to get notified about browser history
   * sanitization.
   */
  observe: function Links_observe(aSubject, aTopic, aData) {
    // Make sure to update open about:newtab instances. If there are no opened
    // pages we can just wait for the next new tab to populate the cache again.
    if (AllPages.length && AllPages.enabled)
      this.populateCache(function() { AllPages.update() }, true);
    else
      this.resetCache();
  },

  _callObservers(methodName, ...args) {
    for (let obs of this._observers) {
      if (typeof(obs[methodName]) == "function") {
        try {
          obs[methodName](this, ...args);
        } catch (err) {
          Cu.reportError(err);
        }
      }
    }
  },

  /**
   * Adds a sanitization observer and turns itself into a no-op after the first
   * invokation.
   */
  _addObserver: function Links_addObserver() {
    Services.obs.addObserver(this, "browser:purge-session-history", true);
    this._addObserver = function() {};
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference])
};

Links.compareLinks = Links.compareLinks.bind(Links);

/**
 * Singleton used to collect telemetry data.
 *
 */
var Telemetry = {
  /**
   * Initializes object.
   */
  init: function Telemetry_init() {
    Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY);
  },

  uninit: function Telemetry_uninit() {
    Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
  },

  /**
   * Collects data.
   */
  _collect: function Telemetry_collect() {
    let probes = [
      { histogram: "NEWTAB_PAGE_ENABLED",
        value: AllPages.enabled },
      { histogram: "NEWTAB_PAGE_ENHANCED",
        value: AllPages.enhanced },
      { histogram: "NEWTAB_PAGE_PINNED_SITES_COUNT",
        value: PinnedLinks.links.length },
      { histogram: "NEWTAB_PAGE_BLOCKED_SITES_COUNT",
        value: Object.keys(BlockedLinks.links).length }
    ];

    probes.forEach(function Telemetry_collect_forEach(aProbe) {
      Services.telemetry.getHistogramById(aProbe.histogram)
        .add(aProbe.value);
    });
  },

  /**
   * Listens for gather telemetry topic.
   */
  observe: function Telemetry_observe(aSubject, aTopic, aData) {
    this._collect();
  }
};

/**
 * Singleton that checks if a given link should be displayed on about:newtab
 * or if we should rather not do it for security reasons. URIs that inherit
 * their caller's principal will be filtered.
 */
var LinkChecker = {
  _cache: {},

  get flags() {
    return Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL |
           Ci.nsIScriptSecurityManager.DONT_REPORT_ERRORS;
  },

  checkLoadURI: function LinkChecker_checkLoadURI(aURI) {
    if (!(aURI in this._cache))
      this._cache[aURI] = this._doCheckLoadURI(aURI);

    return this._cache[aURI];
  },

  _doCheckLoadURI: function Links_doCheckLoadURI(aURI) {
    try {
      // about:newtab is currently privileged. In any case, it should be
      // possible for tiles to point to pretty much everything - but not
      // to stuff that inherits the system principal, so we check:
      let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
      Services.scriptSecurityManager.
        checkLoadURIStrWithPrincipal(systemPrincipal, aURI, this.flags);
      return true;
    } catch (e) {
      // We got a weird URI or one that would inherit the caller's principal.
      return false;
    }
  }
};

var ExpirationFilter = {
  init: function ExpirationFilter_init() {
    PageThumbs.addExpirationFilter(this);
  },

  filterForThumbnailExpiration:
  function ExpirationFilter_filterForThumbnailExpiration(aCallback) {
    if (!AllPages.enabled) {
      aCallback([]);
      return;
    }

    Links.populateCache(function() {
      let urls = [];

      // Add all URLs to the list that we want to keep thumbnails for.
      for (let link of Links.getLinks().slice(0, 25)) {
        if (link && link.url)
          urls.push(link.url);
      }

      aCallback(urls);
    });
  }
};

/**
 * Singleton that provides the public API of this JSM.
 */
this.NewTabUtils = {
  _initialized: false,

  /**
   * Extract a "site" from a url in a way that multiple urls of a "site" returns
   * the same "site."
   * @param aUrl Url spec string
   * @return The "site" string or null
   */
  extractSite: function Links_extractSite(url) {
    let host;
    try {
      // Note that nsIURI.asciiHost throws NS_ERROR_FAILURE for some types of
      // URIs, including jar and moz-icon URIs.
      host = Services.io.newURI(url).asciiHost;
    } catch (ex) {
      return null;
    }

    // Strip off common subdomains of the same site (e.g., www, load balancer)
    return host.replace(/^(m|mobile|www\d*)\./, "");
  },

  init: function NewTabUtils_init() {
    if (this.initWithoutProviders()) {
      PlacesProvider.init();
      Links.addProvider(PlacesProvider);
      BlockedLinks.addObserver(Links);
      BlockedLinks.addObserver(ActivityStreamLinks);
    }
  },

  initWithoutProviders: function NewTabUtils_initWithoutProviders() {
    if (!this._initialized) {
      this._initialized = true;
      ExpirationFilter.init();
      Telemetry.init();
      return true;
    }
    return false;
  },

  uninit: function NewTabUtils_uninit() {
    if (this.initialized) {
      Telemetry.uninit();
      GridPrefs.uninit();
      BlockedLinks.removeObservers();
    }
  },

  getProviderLinks(aProvider) {
    let cache = Links._providers.get(aProvider);
    if (cache && cache.sortedLinks) {
      return cache.sortedLinks;
    }
    return [];
  },

  isTopSiteGivenProvider(aSite, aProvider) {
    let cache = Links._providers.get(aProvider);
    if (cache && cache.siteMap) {
      return cache.siteMap.has(aSite);
    }
    return false;
  },

  isTopPlacesSite(aSite) {
    return this.isTopSiteGivenProvider(aSite, PlacesProvider);
  },

  /**
   * Restores all sites that have been removed from the grid.
   */
  restore: function NewTabUtils_restore() {
    Storage.clear();
    Links.resetCache();
    PinnedLinks.resetCache();
    BlockedLinks.resetCache();

    Links.populateCache(function() {
      AllPages.update();
    }, true);
  },

  /**
   * Undoes all sites that have been removed from the grid and keep the pinned
   * tabs.
   * @param aCallback the callback method.
   */
  undoAll: function NewTabUtils_undoAll(aCallback) {
    Storage.remove("blockedLinks");
    Links.resetCache();
    BlockedLinks.resetCache();
    Links.populateCache(aCallback, true);
  },

  links: Links,
  allPages: AllPages,
  linkChecker: LinkChecker,
  pinnedLinks: PinnedLinks,
  blockedLinks: BlockedLinks,
  gridPrefs: GridPrefs,
  placesProvider: PlacesProvider,
  activityStreamLinks: ActivityStreamLinks,
  activityStreamProvider: ActivityStreamProvider
};
PK
!<95b~((modules/NotificationDB.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [];

const DEBUG = false;
function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                   "@mozilla.org/parentprocessmessagemanager;1",
                                   "nsIMessageListenerManager");

XPCOMUtils.defineLazyServiceGetter(this, "notificationStorage",
                                   "@mozilla.org/notificationStorage;1",
                                   "nsINotificationStorage");

const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
const NOTIFICATION_STORE_PATH =
        OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");

const kMessages = [
  "Notification:Save",
  "Notification:Delete",
  "Notification:GetAll"
];

var NotificationDB = {

  // Ensure we won't call init() while xpcom-shutdown is performed
  _shutdownInProgress: false,

  init: function() {
    if (this._shutdownInProgress) {
      return;
    }

    this.notifications = {};
    this.byTag = {};
    this.loaded = false;

    this.tasks = []; // read/write operation queue
    this.runningTask = null;

    Services.obs.addObserver(this, "xpcom-shutdown");
    this.registerListeners();
  },

  registerListeners: function() {
    for (let message of kMessages) {
      ppmm.addMessageListener(message, this);
    }
  },

  unregisterListeners: function() {
    for (let message of kMessages) {
      ppmm.removeMessageListener(message, this);
    }
  },

  observe: function(aSubject, aTopic, aData) {
    if (DEBUG) debug("Topic: " + aTopic);
    if (aTopic == "xpcom-shutdown") {
      this._shutdownInProgress = true;
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  },

  filterNonAppNotifications: function(notifications) {
    for (let origin in notifications) {
      let persistentNotificationCount = 0;
      for (let id in notifications[origin]) {
        if (notifications[origin][id].serviceWorkerRegistrationScope) {
          persistentNotificationCount++;
        } else {
          delete notifications[origin][id];
        }
      }
      if (persistentNotificationCount == 0) {
        if (DEBUG) debug("Origin " + origin + " is not linked to an app manifest, deleting.");
        delete notifications[origin];
      }
    }

    return notifications;
  },

  // Attempt to read notification file, if it's not there we will create it.
  load: function() {
    var promise = OS.File.read(NOTIFICATION_STORE_PATH, { encoding: "utf-8"});
    return promise.then(
      data => {
        if (data.length > 0) {
          // Preprocessing phase intends to cleanly separate any migration-related
          // tasks.
          this.notifications = this.filterNonAppNotifications(JSON.parse(data));
        }

        // populate the list of notifications by tag
        if (this.notifications) {
          for (var origin in this.notifications) {
            this.byTag[origin] = {};
            for (var id in this.notifications[origin]) {
              var curNotification = this.notifications[origin][id];
              if (curNotification.tag) {
                this.byTag[origin][curNotification.tag] = curNotification;
              }
            }
          }
        }

        this.loaded = true;
      },

      // If read failed, we assume we have no notifications to load.
      reason => {
        this.loaded = true;
        return this.createStore();
      }
    );
  },

  // Creates the notification directory.
  createStore: function() {
    var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
      ignoreExisting: true
    });
    return promise.then(
      this.createFile.bind(this)
    );
  },

  // Creates the notification file once the directory is created.
  createFile: function() {
    return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, "");
  },

  // Save current notifications to the file.
  save: function() {
    var data = JSON.stringify(this.notifications);
    return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data, { encoding: "utf-8"});
  },

  // Helper function: promise will be resolved once file exists and/or is loaded.
  ensureLoaded: function() {
    if (!this.loaded) {
      return this.load();
    } else {
      return Promise.resolve();
    }
  },

  receiveMessage: function(message) {
    if (DEBUG) { debug("Received message:" + message.name); }

    // sendAsyncMessage can fail if the child process exits during a
    // notification storage operation, so always wrap it in a try/catch.
    function returnMessage(name, data) {
      try {
        message.target.sendAsyncMessage(name, data);
      } catch (e) {
        if (DEBUG) { debug("Return message failed, " + name); }
      }
    }

    switch (message.name) {
      case "Notification:GetAll":
        this.queueTask("getall", message.data).then(function(notifications) {
          returnMessage("Notification:GetAll:Return:OK", {
            requestID: message.data.requestID,
            origin: message.data.origin,
            notifications: notifications
          });
        }).catch(function(error) {
          returnMessage("Notification:GetAll:Return:KO", {
            requestID: message.data.requestID,
            origin: message.data.origin,
            errorMsg: error
          });
        });
        break;

      case "Notification:Save":
        this.queueTask("save", message.data).then(function() {
          returnMessage("Notification:Save:Return:OK", {
            requestID: message.data.requestID
          });
        }).catch(function(error) {
          returnMessage("Notification:Save:Return:KO", {
            requestID: message.data.requestID,
            errorMsg: error
          });
        });
        break;

      case "Notification:Delete":
        this.queueTask("delete", message.data).then(function() {
          returnMessage("Notification:Delete:Return:OK", {
            requestID: message.data.requestID
          });
        }).catch(function(error) {
          returnMessage("Notification:Delete:Return:KO", {
            requestID: message.data.requestID,
            errorMsg: error
          });
        });
        break;

      default:
        if (DEBUG) { debug("Invalid message name" + message.name); }
    }
  },

  // We need to make sure any read/write operations are atomic,
  // so use a queue to run each operation sequentially.
  queueTask: function(operation, data) {
    if (DEBUG) { debug("Queueing task: " + operation); }

    var defer = {};

    this.tasks.push({
      operation: operation,
      data: data,
      defer: defer
    });

    var promise = new Promise(function(resolve, reject) {
      defer.resolve = resolve;
      defer.reject = reject;
    });

    // Only run immediately if we aren't currently running another task.
    if (!this.runningTask) {
      if (DEBUG) { debug("Task queue was not running, starting now..."); }
      this.runNextTask();
    }

    return promise;
  },

  runNextTask: function() {
    if (this.tasks.length === 0) {
      if (DEBUG) { debug("No more tasks to run, queue depleted"); }
      this.runningTask = null;
      return;
    }
    this.runningTask = this.tasks.shift();

    // Always make sure we are loaded before performing any read/write tasks.
    this.ensureLoaded()
    .then(() => {
      var task = this.runningTask;

      switch (task.operation) {
        case "getall":
          return this.taskGetAll(task.data);
          break;

        case "save":
          return this.taskSave(task.data);
          break;

        case "delete":
          return this.taskDelete(task.data);
          break;
      }

    })
    .then(payload => {
      if (DEBUG) {
        debug("Finishing task: " + this.runningTask.operation);
      }
      this.runningTask.defer.resolve(payload);
    })
    .catch(err => {
      if (DEBUG) {
        debug("Error while running " + this.runningTask.operation + ": " + err);
      }
      this.runningTask.defer.reject(new String(err));
    })
    .then(() => {
      this.runNextTask();
    });
  },

  taskGetAll: function(data) {
    if (DEBUG) { debug("Task, getting all"); }
    var origin = data.origin;
    var notifications = [];
    // Grab only the notifications for specified origin.
    if (this.notifications[origin]) {
      for (var i in this.notifications[origin]) {
        notifications.push(this.notifications[origin][i]);
      }
    }
    return Promise.resolve(notifications);
  },

  taskSave: function(data) {
    if (DEBUG) { debug("Task, saving"); }
    var origin = data.origin;
    var notification = data.notification;
    if (!this.notifications[origin]) {
      this.notifications[origin] = {};
      this.byTag[origin] = {};
    }

    // We might have existing notification with this tag,
    // if so we need to remove it before saving the new one.
    if (notification.tag) {
      var oldNotification = this.byTag[origin][notification.tag];
      if (oldNotification) {
        delete this.notifications[origin][oldNotification.id];
      }
      this.byTag[origin][notification.tag] = notification;
    }

    this.notifications[origin][notification.id] = notification;
    return this.save();
  },

  taskDelete: function(data) {
    if (DEBUG) { debug("Task, deleting"); }
    var origin = data.origin;
    var id = data.id;
    if (!this.notifications[origin]) {
      if (DEBUG) { debug("No notifications found for origin: " + origin); }
      return Promise.resolve();
    }

    // Make sure we can find the notification to delete.
    var oldNotification = this.notifications[origin][id];
    if (!oldNotification) {
      if (DEBUG) { debug("No notification found with id: " + id); }
      return Promise.resolve();
    }

    if (oldNotification.tag) {
      delete this.byTag[origin][oldNotification.tag];
    }
    delete this.notifications[origin][id];
    return this.save();
  }
};

NotificationDB.init();
PK
!<L>modules/OSCrypto.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Common front for various implementations of OSCrypto
 */

"use strict";

Components.utils.import("resource://gre/modules/AppConstants.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

this.EXPORTED_SYMBOLS = ["OSCrypto"];

var OSCrypto = {};

if (AppConstants.platform == "win") {
  Services.scriptloader.loadSubScript("resource://gre/modules/OSCrypto_win.js", this);
} else {
  throw new Error("OSCrypto.jsm isn't supported on this platform");
}
PK
!<pDFi%i%modules/OSCrypto_win.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ctypes", "resource://gre/modules/ctypes.jsm");

const FLAGS_NOT_SET = 0;

const wintypes = {
  BOOL: ctypes.bool,
  BYTE: ctypes.uint8_t,
  DWORD: ctypes.uint32_t,
  PBYTE: ctypes.unsigned_char.ptr,
  PCHAR: ctypes.char.ptr,
  PDWORD: ctypes.uint32_t.ptr,
  PVOID: ctypes.voidptr_t,
  WORD: ctypes.uint16_t,
};

function OSCrypto() {
  this._structs = {};
  this._functions = new Map();
  this._libs = new Map();
  this._structs.DATA_BLOB = new ctypes.StructType("DATA_BLOB",
                                                  [
                                                    {cbData: wintypes.DWORD},
                                                    {pbData: wintypes.PVOID}
                                                  ]);

  try {

    this._libs.set("crypt32", ctypes.open("Crypt32"));
    this._libs.set("kernel32", ctypes.open("Kernel32"));

    this._functions.set("CryptProtectData",
                        this._libs.get("crypt32").declare("CryptProtectData",
                                                          ctypes.winapi_abi,
                                                          wintypes.DWORD,
                                                          this._structs.DATA_BLOB.ptr,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.DWORD,
                                                          this._structs.DATA_BLOB.ptr));
    this._functions.set("CryptUnprotectData",
                        this._libs.get("crypt32").declare("CryptUnprotectData",
                                                          ctypes.winapi_abi,
                                                          wintypes.DWORD,
                                                          this._structs.DATA_BLOB.ptr,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.PVOID,
                                                          wintypes.DWORD,
                                                          this._structs.DATA_BLOB.ptr));
    this._functions.set("LocalFree",
                        this._libs.get("kernel32").declare("LocalFree",
                                                           ctypes.winapi_abi,
                                                           wintypes.DWORD,
                                                           wintypes.PVOID));
  } catch (ex) {
    Cu.reportError(ex);
    this.finalize();
    throw ex;
  }
}
OSCrypto.prototype = {
  /**
   * Convert an array containing only two bytes unsigned numbers to a string.
   * @param {number[]} arr - the array that needs to be converted.
   * @returns {string} the string representation of the array.
   */
  arrayToString(arr) {
    let str = "";
    for (let i = 0; i < arr.length; i++) {
      str += String.fromCharCode(arr[i]);
    }
    return str;
  },

  /**
   * Convert a string to an array.
   * @param {string} str - the string that needs to be converted.
   * @returns {number[]} the array representation of the string.
   */
  stringToArray(str) {
    let arr = [];
    for (let i = 0; i < str.length; i++) {
      arr.push(str.charCodeAt(i));
    }
    return arr;
  },

  /**
   * Calculate the hash value used by IE as the name of the registry value where login details are
   * stored.
   * @param {string} data - the string value that needs to be hashed.
   * @returns {string} the hash value of the string.
   */
  getIELoginHash(data) {
    // return the two-digit hexadecimal code for a byte
    function toHexString(charCode) {
      return ("00" + charCode.toString(16)).slice(-2);
    }

    // the data needs to be encoded in null terminated UTF-16
    data += "\0";

    // dataArray is an array of bytes
    let dataArray = new Array(data.length * 2);
    for (let i = 0; i < data.length; i++) {
      let c = data.charCodeAt(i);
      let lo = c & 0xFF;
      let hi = (c & 0xFF00) >> 8;
      dataArray[i * 2] = lo;
      dataArray[i * 2 + 1] = hi;
    }

    // calculation of SHA1 hash value
    let cryptoHash = Cc["@mozilla.org/security/hash;1"].
                     createInstance(Ci.nsICryptoHash);
    cryptoHash.init(cryptoHash.SHA1);
    cryptoHash.update(dataArray, dataArray.length);
    let hash = cryptoHash.finish(false);

    let tail = 0; // variable to calculate value for the last 2 bytes
    // convert to a character string in hexadecimal notation
    for (let c of hash) {
      tail += c.charCodeAt(0);
    }
    hash += String.fromCharCode(tail % 256);

    // convert the binary hash data to a hex string.
    let hashStr = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
    return hashStr.toUpperCase();
  },

  /**
   * Decrypt a string using the windows CryptUnprotectData API.
   * @param {string} data - the encrypted string that needs to be decrypted.
   * @param {?string} entropy - the entropy value of the decryption (could be null). Its value must
   * be the same as the one used when the data was encrypted.
   * @returns {string} the decryption of the string.
   */
  decryptData(data, entropy = null) {
    let array = this.stringToArray(data);
    let decryptedData = "";
    let encryptedData = wintypes.BYTE.array(array.length)(array);
    let inData = new this._structs.DATA_BLOB(encryptedData.length, encryptedData);
    let outData = new this._structs.DATA_BLOB();
    let entropyParam;
    if (entropy) {
      let entropyArray = this.stringToArray(entropy);
      entropyArray.push(0);
      let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
      let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
                                                        entropyData);
      entropyParam = optionalEntropy.address();
    } else {
      entropyParam = null;
    }

    let status = this._functions.get("CryptUnprotectData")(inData.address(), null,
                                     entropyParam,
                                     null, null, FLAGS_NOT_SET,
                                     outData.address());
    if (status === 0) {
      throw new Error("decryptData failed: " + status);
    }

    // convert byte array to JS string.
    let len = outData.cbData;
    let decrypted = ctypes.cast(outData.pbData,
                                wintypes.BYTE.array(len).ptr).contents;
    for (let i = 0; i < decrypted.length; i++) {
      decryptedData += String.fromCharCode(decrypted[i]);
    }

    this._functions.get("LocalFree")(outData.pbData);
    return decryptedData;
 },

  /**
   * Encrypt a string using the windows CryptProtectData API.
   * @param {string} data - the string that is going to be encrypted.
   * @param {?string} entropy - the entropy value of the encryption (could be null). Its value must
   * be the same as the one that is going to be used for the decryption.
   * @returns {string} the encrypted string.
   */
  encryptData(data, entropy = null) {
    let encryptedData = "";
    let decryptedData = wintypes.BYTE.array(data.length)(this.stringToArray(data));

    let inData = new this._structs.DATA_BLOB(data.length, decryptedData);
    let outData = new this._structs.DATA_BLOB();
    let entropyParam;
    if (!entropy) {
      entropyParam = null;
    } else {
      let entropyArray = this.stringToArray(entropy);
      entropyArray.push(0);
      let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
      let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
                                                        entropyData);
      entropyParam = optionalEntropy.address();
    }

    let status = this._functions.get("CryptProtectData")(inData.address(), null,
                                                         entropyParam,
                                                         null, null, FLAGS_NOT_SET,
                                                         outData.address());
    if (status === 0) {
      throw new Error("encryptData failed: " + status);
    }

    // convert byte array to JS string.
    let len = outData.cbData;
    let encrypted = ctypes.cast(outData.pbData,
                                wintypes.BYTE.array(len).ptr).contents;
    encryptedData = this.arrayToString(encrypted);
    this._functions.get("LocalFree")(outData.pbData);
    return encryptedData;
  },

  /**
   * Must be invoked once after last use of any of the provided helpers.
   */
  finalize() {
    this._structs = {};
    this._functions.clear();
    for (let lib of this._libs.values()) {
      try {
        lib.close();
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
    this._libs.clear();
  },
};
PK
!<8?Ȓmodules/ObjectUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Portions of this file are originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

"use strict";

this.EXPORTED_SYMBOLS = [
  "ObjectUtils"
];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

// Used only to cause test failures.

var pSlice = Array.prototype.slice;

this.ObjectUtils = {
  /**
   * This tests objects & values for deep equality.
   *
   * We check using the most exact approximation of equality between two objects
   * to keep the chance of false positives to a minimum.
   * `JSON.stringify` is not designed to be used for this purpose; objects may
   * have ambiguous `toJSON()` implementations that would influence the test.
   *
   * @param a (mixed) Object or value to be compared.
   * @param b (mixed) Object or value to be compared.
   * @return Boolean Whether the objects are deep equal.
   */
  deepEqual(a, b) {
    return _deepEqual(a, b);
  },

  /**
   * A thin wrapper on an object, designed to prevent client code from
   * accessing non-existent properties because of typos.
   *
   * // Without `strict`
   * let foo = { myProperty: 1 };
   * foo.MyProperty; // undefined
   *
   * // With `strict`
   * let strictFoo = ObjectUtils.strict(foo);
   * strictFoo.myProperty; // 1
   * strictFoo.MyProperty; // TypeError: No such property "MyProperty"
   *
   * Note that `strict` has no effect in non-DEBUG mode.
   */
  strict(obj) {
    return _strict(obj);
  }
};

// ... Start of previously MIT-licensed code.
// This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

function _deepEqual(a, b) {
  // The numbering below refers to sections in the CommonJS spec.

  // 7.1 All identical values are equivalent, as determined by ===.
  if (a === b) {
    return true;
  // 7.2 If the b value is a Date object, the a value is
  // equivalent if it is also a Date object that refers to the same time.
  }
  let aIsDate = instanceOf(a, "Date");
  let bIsDate = instanceOf(b, "Date");
  if (aIsDate || bIsDate) {
    if (!aIsDate || !bIsDate) {
      return false;
    }
    if (isNaN(a.getTime()) && isNaN(b.getTime()))
      return true;
    return a.getTime() === b.getTime();
  // 7.3 If the b value is a RegExp object, the a value is
  // equivalent if it is also a RegExp object with the same source and
  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
  }
  let aIsRegExp = instanceOf(a, "RegExp");
  let bIsRegExp = instanceOf(b, "RegExp");
  if (aIsRegExp || bIsRegExp) {
    return aIsRegExp && bIsRegExp &&
           a.source === b.source &&
           a.global === b.global &&
           a.multiline === b.multiline &&
           a.lastIndex === b.lastIndex &&
           a.ignoreCase === b.ignoreCase;
  // 7.4 Other pairs that do not both pass typeof value == "object",
  // equivalence is determined by ==.
  }
  if (typeof a != "object" || typeof b != "object") {
    return a == b;
  }
  // 7.5 For all other Object pairs, including Array objects, equivalence is
  // determined by having the same number of owned properties (as verified
  // with Object.prototype.hasOwnProperty.call), the same set of keys
  // (although not necessarily the same order), equivalent values for every
  // corresponding key, and an identical 'prototype' property. Note: this
  // accounts for both named and indexed properties on Arrays.
  return objEquiv(a, b);
}

function instanceOf(object, type) {
  return Object.prototype.toString.call(object) == "[object " + type + "]";
}

function isUndefinedOrNull(value) {
  return value === null || value === undefined;
}

function isArguments(object) {
  return instanceOf(object, "Arguments");
}

function objEquiv(a, b) {
  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
    return false;
  }
  // An identical 'prototype' property.
  if ((a.prototype || undefined) != (b.prototype || undefined)) {
    return false;
  }
  // Object.keys may be broken through screwy arguments passing. Converting to
  // an array solves the problem.
  if (isArguments(a)) {
    if (!isArguments(b)) {
      return false;
    }
    a = pSlice.call(a);
    b = pSlice.call(b);
    return _deepEqual(a, b);
  }
  let ka, kb;
  try {
    ka = Object.keys(a);
    kb = Object.keys(b);
  } catch (e) {
    // Happens when one is a string literal and the other isn't
    return false;
  }
  // Having the same number of owned properties (keys incorporates
  // hasOwnProperty)
  if (ka.length != kb.length)
    return false;
  // The same set of keys (although not necessarily the same order),
  ka.sort();
  kb.sort();
  // Equivalent values for every corresponding key, and possibly expensive deep
  // test
  for (let key of ka) {
    if (!_deepEqual(a[key], b[key])) {
      return false;
    }
  }
  return true;
}

// ... End of previously MIT-licensed code.

function _strict(obj) {
  if (typeof obj != "object") {
    throw new TypeError("Expected an object");
  }

  return new Proxy(obj, {
    get(target, name) {
      if (name in obj) {
        return obj[name];
      }

      let error = new TypeError(`No such property: "${name}"`);
      Promise.reject(error); // Cause an xpcshell/mochitest failure.
      throw error;
    }
  });
}
PK
!<<8%8%modules/PageMenu.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["PageMenuParent", "PageMenuChild"];

var {interfaces: Ci} = Components;

this.PageMenu = function PageMenu() {
}

PageMenu.prototype = {
  PAGEMENU_ATTR: "pagemenu",
  GENERATEDITEMID_ATTR: "generateditemid",

  _popup: null,

  // Only one of builder or browser will end up getting set.
  _builder: null,
  _browser: null,

  // Given a target node, get the context menu for it or its ancestor.
  getContextMenu(aTarget) {
    let target = aTarget;
    while (target) {
      let contextMenu = target.contextMenu;
      if (contextMenu) {
        return contextMenu;
      }
      target = target.parentNode;
    }

    return null;
  },

  // Given a target node, generate a JSON object for any context menu
  // associated with it, or null if there is no context menu.
  maybeBuild(aTarget) {
    let pageMenu = this.getContextMenu(aTarget);
    if (!pageMenu) {
      return null;
    }

    pageMenu.sendShowEvent();
    // the show event is not cancelable, so no need to check a result here

    this._builder = pageMenu.createBuilder();
    if (!this._builder) {
      return null;
    }

    pageMenu.build(this._builder);

    // This serializes then parses again, however this could be avoided in
    // the single-process case with further improvement.
    let menuString = this._builder.toJSONString();
    if (!menuString) {
      return null;
    }

    return JSON.parse(menuString);
  },

  // Given a JSON menu object and popup, add the context menu to the popup.
  buildAndAttachMenuWithObject(aMenu, aBrowser, aPopup) {
    if (!aMenu) {
      return false;
    }

    let insertionPoint = this.getInsertionPoint(aPopup);
    if (!insertionPoint) {
      return false;
    }

    let fragment = aPopup.ownerDocument.createDocumentFragment();
    this.buildXULMenu(aMenu, fragment);

    let pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
    if (pos == "start") {
      insertionPoint.insertBefore(fragment,
                                  insertionPoint.firstChild);
    } else if (pos.startsWith("#")) {
      insertionPoint.insertBefore(fragment, insertionPoint.querySelector(pos));
    } else {
      insertionPoint.appendChild(fragment);
    }

    this._browser = aBrowser;
    this._popup = aPopup;

    this._popup.addEventListener("command", this);
    this._popup.addEventListener("popuphidden", this);

    return true;
  },

  // Construct the XUL menu structure for a given JSON object.
  buildXULMenu(aNode, aElementForAppending) {
    let document = aElementForAppending.ownerDocument;

    let children = aNode.children;
    for (let child of children) {
      let menuitem;
      switch (child.type) {
        case "menuitem":
          if (!child.id) {
            continue; // Ignore children without ids
          }

          menuitem = document.createElement("menuitem");
          if (child.checkbox) {
            menuitem.setAttribute("type", "checkbox");
            if (child.checked) {
              menuitem.setAttribute("checked", "true");
            }
          }

          if (child.label) {
            menuitem.setAttribute("label", child.label);
          }
          if (child.icon) {
            menuitem.setAttribute("image", child.icon);
            menuitem.className = "menuitem-iconic";
          }
          if (child.disabled) {
            menuitem.setAttribute("disabled", true);
          }

          break;

        case "separator":
          menuitem = document.createElement("menuseparator");
          break;

        case "menu":
          menuitem = document.createElement("menu");
          if (child.label) {
            menuitem.setAttribute("label", child.label);
          }

          let menupopup = document.createElement("menupopup");
          menuitem.appendChild(menupopup);

          this.buildXULMenu(child, menupopup);
          break;
      }

      menuitem.setAttribute(this.GENERATEDITEMID_ATTR, child.id ? child.id : 0);
      aElementForAppending.appendChild(menuitem);
    }
  },

  // Called when the generated menuitem is executed.
  handleEvent(event) {
    let type = event.type;
    let target = event.target;
    if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
      // If a builder is assigned, call click on it directly. Otherwise, this is
      // likely a menu with data from another process, so send a message to the
      // browser to execute the menuitem.
      if (this._builder) {
        this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
      } else if (this._browser) {
        let win = target.ownerGlobal;
        let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
        this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand", {
          generatedItemId: target.getAttribute(this.GENERATEDITEMID_ATTR),
          handlingUserInput: windowUtils.isHandlingUserInput
        });
      }
    } else if (type == "popuphidden" && this._popup == target) {
      this.removeGeneratedContent(this._popup);

      this._popup.removeEventListener("popuphidden", this);
      this._popup.removeEventListener("command", this);

      this._popup = null;
      this._builder = null;
      this._browser = null;
    }
  },

  // Get the first child of the given element with the given tag name.
  getImmediateChild(element, tag) {
    let child = element.firstChild;
    while (child) {
      if (child.localName == tag) {
        return child;
      }
      child = child.nextSibling;
    }
    return null;
  },

  // Return the location where the generated items should be inserted into the
  // given popup. They should be inserted as the next sibling of the returned
  // element.
  getInsertionPoint(aPopup) {
    if (aPopup.hasAttribute(this.PAGEMENU_ATTR))
      return aPopup;

    let element = aPopup.firstChild;
    while (element) {
      if (element.localName == "menu") {
        let popup = this.getImmediateChild(element, "menupopup");
        if (popup) {
          let result = this.getInsertionPoint(popup);
          if (result) {
            return result;
          }
        }
      }
      element = element.nextSibling;
    }

    return null;
  },

  // Remove the generated content from the given popup.
  removeGeneratedContent(aPopup) {
    let ungenerated = [];
    ungenerated.push(aPopup);

    let count;
    while (0 != (count = ungenerated.length)) {
      let last = count - 1;
      let element = ungenerated[last];
      ungenerated.splice(last, 1);

      let i = element.childNodes.length;
      while (i-- > 0) {
        let child = element.childNodes[i];
        if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
          ungenerated.push(child);
          continue;
        }
        element.removeChild(child);
      }
    }
  }
}

// This object is expected to be used from a parent process.
this.PageMenuParent = function PageMenuParent() {
}

PageMenuParent.prototype = {
  __proto__: PageMenu.prototype,

  /*
   * Given a target node and popup, add the context menu to the popup. This is
   * intended to be called when a single process is used. This is equivalent to
   * calling PageMenuChild.build and PageMenuParent.addToPopup in sequence.
   *
   * Returns true if custom menu items were present.
   */
  buildAndAddToPopup(aTarget, aPopup) {
    let menuObject = this.maybeBuild(aTarget);
    if (!menuObject) {
      return false;
    }

    return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
  },

  /*
   * Given a JSON menu object and popup, add the context menu to the popup. This
   * is intended to be called when the child page is in a different process.
   * aBrowser should be the browser containing the page the context menu is
   * displayed for, which may be null.
   *
   * Returns true if custom menu items were present.
   */
  addToPopup(aMenu, aBrowser, aPopup) {
    return this.buildAndAttachMenuWithObject(aMenu, aBrowser, aPopup);
  }
}

// This object is expected to be used from a child process.
this.PageMenuChild = function PageMenuChild() {
}

PageMenuChild.prototype = {
  __proto__: PageMenu.prototype,

  /*
   * Given a target node, return a JSON object for the custom menu commands. The
   * object will consist of a hierarchical structure of menus, menuitems or
   * separators. Supported properties of each are:
   *   Menu: children, label, type="menu"
   *   Menuitems: checkbox, checked, disabled, icon, label, type="menuitem"
   *   Separators: type="separator"
   *
   * In addition, the id of each item will be used to identify the item
   * when it is executed. The type will either be 'menu', 'menuitem' or
   * 'separator'. The toplevel node will be a menu with a children property. The
   * children property of a menu is an array of zero or more other items.
   *
   * If there is no menu associated with aTarget, null will be returned.
   */
  build(aTarget) {
    return this.maybeBuild(aTarget);
  },

  /*
   * Given the id of a menu, execute the command associated with that menu. It
   * is assumed that only one command will be executed so the builder is
   * cleared afterwards.
   */
  executeMenu(aId) {
    if (this._builder) {
      this._builder.click(aId);
      this._builder = null;
    }
  }
}
PK
!<t'z##modules/PageMetadata.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["PageMetadata"];

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/microformat-shiv.js");

XPCOMUtils.defineLazyServiceGetter(this, "UnescapeService",
                                   "@mozilla.org/feed-unescapehtml;1",
                                   "nsIScriptableUnescapeHTML");


/**
 * Maximum number of images to discover in the document, when no preview images
 * are explicitly specified by the metadata.
 * @type {Number}
 */
const DISCOVER_IMAGES_MAX  = 5;


/**
 * Extract metadata and microformats from a HTML document.
 * @type {Object}
 */
this.PageMetadata = {
  /**
   * Get all metadata from an HTML document. This includes:
   * - URL
   * - title
   * - Metadata specified in <meta> tags, including OpenGraph data
   * - Links specified in <link> tags (short, canonical, preview images, alternative)
   * - Content that can be found in the page content that we consider useful metadata
   * - Microformats
   *
   * @param {Document} document - Document to extract data from.
   * @param {Element} [target] - Optional element to restrict microformats lookup to.
   * @returns {Object} Object containing the various metadata, normalized to
   *                   merge some common alternative names for metadata.
   */
  getData(document, target = null) {
    let result = {
      url: this._validateURL(document, document.documentURI),
      title: document.title,
      previews: [],
    };

    // if pushState was used to change the url, most likely all meta data is
    // invalid. This is the case with several major sites that rely on
    // pushState. In that case, we'll only return uri and title. If document is
    // via XHR or something, there is no view or history.
    if (document.defaultView) {
      let docshell = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                                         .getInterface(Ci.nsIWebNavigation)
                                         .QueryInterface(Ci.nsIDocShell);
      let shentry = {};
      if (docshell.getCurrentSHEntry(shentry) &&
          shentry.value && shentry.value.URIWasModified) {
        return result;
      }
    }

    this._getMetaData(document, result);
    this._getLinkData(document, result);
    this._getPageData(document, result);
    result.microformats = this.getMicroformats(document, target);

    return result;
  },

  getMicroformats(document, target = null) {
    if (target) {
      return Microformats.getParent(target, {node: document});
    }
    return Microformats.get({node: document});
  },

  /**
   * Get metadata as defined in <meta> tags.
   * This adds properties to an existing result object.
   *
   * @param {Document} document - Document to extract data from.
   * @param {Object}  result - Existing result object to add properties to.
   */
  _getMetaData(document, result) {
    // Query for standardized meta data.
    let elements = document.querySelectorAll("head > meta[property], head > meta[name]");
    if (elements.length < 1) {
      return;
    }

    for (let element of elements) {
      let value = element.getAttribute("content")
      if (!value) {
        continue;
      }
      value = UnescapeService.unescape(value.trim());

      let key = element.getAttribute("property") || element.getAttribute("name");
      if (!key) {
        continue;
      }

      // There are a wide array of possible meta tags, expressing articles,
      // products, etc. so all meta tags are passed through but we touch up the
      // most common attributes.
      result[key] = value;

      switch (key) {
        case "title":
        case "og:title": {
          // Only set the title if one hasn't already been obtained (e.g. from the
          // document title element).
          if (!result.title) {
            result.title = value;
          }
          break;
        }

        case "description":
        case "og:description": {
          result.description = value;
          break;
        }

        case "og:site_name": {
          result.siteName = value;
          break;
        }

        case "medium":
        case "og:type": {
          result.medium = value;
          break;
        }

        case "og:video": {
          let url = this._validateURL(document, value);
          if (url) {
            result.source = url;
          }
          break;
        }

        case "og:url": {
          let url = this._validateURL(document, value);
          if (url) {
            result.url = url;
          }
          break;
        }

        case "og:image": {
          let url = this._validateURL(document, value);
          if (url) {
            result.previews.push(url);
          }
          break;
        }
      }
    }
  },

  /**
   * Get metadata as defined in <link> tags.
   * This adds properties to an existing result object.
   *
   * @param {Document} document - Document to extract data from.
   * @param {Object}  result - Existing result object to add properties to.
   */
  _getLinkData(document, result) {
    let elements = document.querySelectorAll("head > link[rel], head > link[id]");

    for (let element of elements) {
      let url = element.getAttribute("href");
      if (!url) {
        continue;
      }
      url = this._validateURL(document, UnescapeService.unescape(url.trim()));

      let key = element.getAttribute("rel") || element.getAttribute("id");
      if (!key) {
        continue;
      }

      switch (key) {
        case "shorturl":
        case "shortlink": {
          result.shortUrl = url;
          break;
        }

        case "canonicalurl":
        case "canonical": {
          result.url = url;
          break;
        }

        case "image_src": {
          result.previews.push(url);
          break;
        }

        case "alternate": {
          // Expressly for oembed support but we're liberal here and will let
          // other alternate links through. oembed defines an href, supplied by
          // the site, where you can fetch additional meta data about a page.
          // We'll let the client fetch the oembed data themselves, but they
          // need the data from this link.
          if (!result.alternate) {
            result.alternate = [];
          }

          result.alternate.push({
            type: element.getAttribute("type"),
            href: element.getAttribute("href"),
            title: element.getAttribute("title")
          });
        }
      }
    }
  },

  /**
   * Scrape thought the page content for additional content that may be used to
   * suppliment explicitly defined metadata. This includes:
   * - First few images, when no preview image metadata is explicitly defined.
   *
   * This adds properties to an existing result object.
   *
   * @param {Document} document - Document to extract data from.
   * @param {Object}  result - Existing result object to add properties to.
   */
  _getPageData(document, result) {
    if (result.previews.length < 1) {
      result.previews = this._getImageUrls(document);
    }
  },

  /**
   * Find the first few images in a document, for use as preview images.
   * Will return upto DISCOVER_IMAGES_MAX number of images.
   *
   * @note This is not very clever. It does not (yet) check if any of the
   *       images may be appropriate as a preview image.
   *
   * @param {Document} document - Document to extract data from.
   * @return {[string]} Array of URLs.
   */
  _getImageUrls(document) {
    let result = [];
    let elements = document.querySelectorAll("img");

    for (let element of elements) {
      let src = element.getAttribute("src");
      if (src) {
        result.push(this._validateURL(document, UnescapeService.unescape(src)));

        // We don't want a billion images.
        // TODO: Move this magic number to a const.
        if (result.length > DISCOVER_IMAGES_MAX) {
          break;
        }
      }
    }

    return result;
  },

  /**
   * Validate a URL. This involves resolving the URL if it's relative to the
   * document location, ensuring it's using an expected scheme, and stripping
   * the userPass portion of the URL.
   *
   * @param {Document} document - Document to use as the root location for a relative URL.
   * @param {string} url - URL to validate.
   * @return {string} Result URL.
   */
  _validateURL(document, url) {
    let docURI = Services.io.newURI(document.documentURI);
    let uri = Services.io.newURI(docURI.resolve(url));

    if (["http", "https"].indexOf(uri.scheme) < 0) {
      return null;
    }

    uri.userPass = "";

    return uri.spec;
  },
};
PK
!<BpI11modules/PageThumbUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Common thumbnailing routines used by various consumers, including
 * PageThumbs and backgroundPageThumbsContent.
 */

this.EXPORTED_SYMBOLS = ["PageThumbUtils"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
  "resource://gre/modules/BrowserUtils.jsm");

this.PageThumbUtils = {
  // The default background color for page thumbnails.
  THUMBNAIL_BG_COLOR: "#fff",
  // The namespace for thumbnail canvas elements.
  HTML_NAMESPACE: "http://www.w3.org/1999/xhtml",

  /**
   * Creates a new canvas element in the context of aWindow, or if aWindow
   * is undefined, in the context of hiddenDOMWindow.
   *
   * @param aWindow (optional) The document of this window will be used to
   *  create the canvas.  If not given, the hidden window will be used.
   * @param aWidth (optional) width of the canvas to create
   * @param aHeight (optional) height of the canvas to create
   * @return The newly created canvas.
   */
  createCanvas(aWindow, aWidth = 0, aHeight = 0) {
    let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
    let canvas = doc.createElementNS(this.HTML_NAMESPACE, "canvas");
    canvas.mozOpaque = true;
    canvas.imageSmoothingEnabled = true;
    let [thumbnailWidth, thumbnailHeight] = this.getThumbnailSize(aWindow);
    canvas.width = aWidth ? aWidth : thumbnailWidth;
    canvas.height = aHeight ? aHeight : thumbnailHeight;
    return canvas;
  },

  /**
   * Calculates a preferred initial thumbnail size based based on newtab.css
   * sizes or a preference for other applications. The sizes should be the same
   * as set for the tile sizes in newtab.
   *
   * @param aWindow (optional) aWindow that is used to calculate the scaling size.
   * @return The calculated thumbnail size or a default if unable to calculate.
   */
  getThumbnailSize(aWindow = null) {
    if (!this._thumbnailWidth || !this._thumbnailHeight) {
      let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
                            .getService(Ci.nsIScreenManager);
      let left = {}, top = {}, screenWidth = {}, screenHeight = {};
      screenManager.primaryScreen.GetRectDisplayPix(left, top, screenWidth, screenHeight);

      /**
       * The primary monitor default scale might be different than
       * what is reported by the window on mixed-DPI systems.
       * To get the best image quality, query both and take the highest one.
       */
      let primaryScale = screenManager.primaryScreen.defaultCSSScaleFactor;
      let windowScale = aWindow ? aWindow.devicePixelRatio : primaryScale;
      let scale = Math.max(primaryScale, windowScale);

      /** *
       * THESE VALUES ARE DEFINED IN newtab.css and hard coded.
       * If you change these values from the prefs,
       * ALSO CHANGE THEM IN newtab.css
       */
      let prefWidth = Services.prefs.getIntPref("toolkit.pageThumbs.minWidth");
      let prefHeight = Services.prefs.getIntPref("toolkit.pageThumbs.minHeight");
      let divisor = Services.prefs.getIntPref("toolkit.pageThumbs.screenSizeDivisor");

      prefWidth *= scale;
      prefHeight *= scale;

      this._thumbnailWidth = Math.max(Math.round(screenWidth.value / divisor), prefWidth);
      this._thumbnailHeight = Math.max(Math.round(screenHeight.value / divisor), prefHeight);
    }

    return [this._thumbnailWidth, this._thumbnailHeight];
  },

  /** *
   * Given a browser window, return the size of the content
   * minus the scroll bars.
   */
  getContentSize(aWindow) {
    let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
    // aWindow may be a cpow, add exposed props security values.
    let sbWidth = {}, sbHeight = {};

    try {
      utils.getScrollbarSize(false, sbWidth, sbHeight);
    } catch (e) {
      // This might fail if the window does not have a presShell.
      Cu.reportError("Unable to get scrollbar size in determineCropSize.");
      sbWidth.value = sbHeight.value = 0;
    }

    // Even in RTL mode, scrollbars are always on the right.
    // So there's no need to determine a left offset.
    let width = aWindow.innerWidth - sbWidth.value;
    let height = aWindow.innerHeight - sbHeight.value;

    return [width, height];
  },

  /** *
   * Given a browser window, this creates a snapshot of the content
   * and returns a canvas with the resulting snapshot of the content
   * at the thumbnail size. It has to do this through a two step process:
   *
   * 1) Render the content at the window size to a canvas that is 2x the thumbnail size
   * 2) Downscale the canvas from (1) down to the thumbnail size
   *
   * This is because the thumbnail size is too small to render at directly,
   * causing pages to believe the browser is a small resolution. Also,
   * at that resolution, graphical artifacts / text become very jagged.
   * It's actually better to the eye to have small blurry text than sharp
   * jagged pixels to represent text.
   *
   * @params aWindow - the window to create a snapshot of.
   * @params aDestCanvas destination canvas to draw the final
   *   snapshot to. Can be null.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   * @return Canvas with a scaled thumbnail of the window.
   */
  createSnapshotThumbnail(aWindow, aDestCanvas, aArgs) {
    if (Cu.isCrossProcessWrapper(aWindow)) {
      throw new Error("Do not pass cpows here.");
    }
    let fullScale = aArgs ? aArgs.fullScale : false;
    let [contentWidth, contentHeight] = this.getContentSize(aWindow);
    let [thumbnailWidth, thumbnailHeight] = aDestCanvas ?
                                            [aDestCanvas.width, aDestCanvas.height] :
                                            this.getThumbnailSize(aWindow);

    // If the caller wants a fullscale image, set the desired thumbnail dims
    // to the dims of content and (if provided) size the incoming canvas to
    // support our results.
    if (fullScale) {
      thumbnailWidth = contentWidth;
      thumbnailHeight = contentHeight;
      if (aDestCanvas) {
        aDestCanvas.width = contentWidth;
        aDestCanvas.height = contentHeight;
      }
    }

    let intermediateWidth = thumbnailWidth * 2;
    let intermediateHeight = thumbnailHeight * 2;
    let skipDownscale = false;

    // If the intermediate thumbnail is larger than content dims (hiDPI
    // devices can experience this) or a full preview is requested render
    // at the final thumbnail size.
    if ((intermediateWidth >= contentWidth ||
         intermediateHeight >= contentHeight) || fullScale) {
      intermediateWidth = thumbnailWidth;
      intermediateHeight = thumbnailHeight;
      skipDownscale = true;
    }

    // Create an intermediate surface
    let snapshotCanvas = this.createCanvas(aWindow, intermediateWidth,
                                           intermediateHeight);

    // Step 1: capture the image at the intermediate dims. For thumbnails
    // this is twice the thumbnail size, for fullScale images this is at
    // content dims.
    // Also by default, canvas does not draw the scrollbars, so no need to
    // remove the scrollbar sizes.
    let scale = Math.min(Math.max(intermediateWidth / contentWidth,
                                  intermediateHeight / contentHeight), 1);

    let snapshotCtx = snapshotCanvas.getContext("2d");
    snapshotCtx.save();
    snapshotCtx.scale(scale, scale);
    snapshotCtx.drawWindow(aWindow, 0, 0, contentWidth, contentHeight,
                           PageThumbUtils.THUMBNAIL_BG_COLOR,
                           snapshotCtx.DRAWWINDOW_DO_NOT_FLUSH);
    snapshotCtx.restore();

    // Part 2: Downscale from our intermediate dims to the final thumbnail
    // dims and copy the result to aDestCanvas. If the caller didn't
    // provide a target canvas, create a new canvas and return it.
    let finalCanvas = aDestCanvas ||
      this.createCanvas(aWindow, thumbnailWidth, thumbnailHeight);

    let finalCtx = finalCanvas.getContext("2d");
    finalCtx.save();
    if (!skipDownscale) {
      finalCtx.scale(0.5, 0.5);
    }
    finalCtx.drawImage(snapshotCanvas, 0, 0);
    finalCtx.restore();

    return finalCanvas;
  },

  /**
   * Determine a good thumbnail crop size and scale for a given content
   * window.
   *
   * @param aWindow The content window.
   * @param aCanvas The target canvas.
   * @return An array containing width, height and scale.
   */
  determineCropSize(aWindow, aCanvas) {
    if (Cu.isCrossProcessWrapper(aWindow)) {
      throw new Error("Do not pass cpows here.");
    }
    let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
    // aWindow may be a cpow, add exposed props security values.
    let sbWidth = {}, sbHeight = {};

    try {
      utils.getScrollbarSize(false, sbWidth, sbHeight);
    } catch (e) {
      // This might fail if the window does not have a presShell.
      Cu.reportError("Unable to get scrollbar size in determineCropSize.");
      sbWidth.value = sbHeight.value = 0;
    }

    // Even in RTL mode, scrollbars are always on the right.
    // So there's no need to determine a left offset.
    let width = aWindow.innerWidth - sbWidth.value;
    let height = aWindow.innerHeight - sbHeight.value;

    let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
    let scale = Math.min(Math.max(thumbnailWidth / width, thumbnailHeight / height), 1);
    let scaledWidth = width * scale;
    let scaledHeight = height * scale;

    if (scaledHeight > thumbnailHeight)
      height -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);

    if (scaledWidth > thumbnailWidth)
      width -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);

    return [width, height, scale];
  },

  shouldStoreContentThumbnail(aDocument, aDocShell) {
    if (BrowserUtils.isToolbarVisible(aDocShell, "findbar")) {
      return false;
    }

    // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
    //       that currently regresses Talos SVG tests.
    if (aDocument instanceof Ci.nsIDOMXMLDocument) {
      return false;
    }

    let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);

    // Don't take screenshots of about: pages.
    if (webNav.currentURI.schemeIs("about")) {
      return false;
    }

    // There's no point in taking screenshot of loading pages.
    if (aDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
      return false;
    }

    let channel = aDocShell.currentDocumentChannel;

    // No valid document channel. We shouldn't take a screenshot.
    if (!channel) {
      return false;
    }

    // Don't take screenshots of internally redirecting about: pages.
    // This includes error pages.
    let uri = channel.originalURI;
    if (uri.schemeIs("about")) {
      return false;
    }

    let httpChannel;
    try {
      httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (e) { /* Not an HTTP channel. */ }

    if (httpChannel) {
      // Continue only if we have a 2xx status code.
      try {
        if (Math.floor(httpChannel.responseStatus / 100) != 2) {
        return false;
        }
      } catch (e) {
        // Can't get response information from the httpChannel
        // because mResponseHead is not available.
        return false;
      }

      // Cache-Control: no-store.
      if (httpChannel.isNoStoreResponse()) {
        return false;
      }

      // Don't capture HTTPS pages unless the user explicitly enabled it.
      if (uri.schemeIs("https") &&
          !Services.prefs.getBoolPref("browser.cache.disk_cache_ssl")) {
        return false;
      }
    } // httpChannel
    return true;
  },

  /**
   * Given a channel, returns true if it should be considered an "error
   * response", false otherwise.
   */
  isChannelErrorResponse(channel) {
    // No valid document channel sounds like an error to me!
    if (!channel)
      return true;
    if (!(channel instanceof Ci.nsIHttpChannel))
      // it might be FTP etc, so assume it's ok.
      return false;
    try {
      return !channel.requestSucceeded;
    } catch (_) {
      // not being able to determine success is surely failure!
      return true;
    }
  },
};
PK
!<o(wwmodules/PageThumbs.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage"];

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
const LATEST_STORAGE_VERSION = 3;

const EXPIRATION_MIN_CHUNK_SIZE = 50;
const EXPIRATION_INTERVAL_SECS = 3600;

var gRemoteThumbId = 0;

// If a request for a thumbnail comes in and we find one that is "stale"
// (or don't find one at all) we automatically queue a request to generate a
// new one.
const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.

/**
 * Name of the directory in the profile that contains the thumbnails.
 */
const THUMBNAIL_DIRECTORY = "thumbnails";

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);

Cu.importGlobalProperties(["FileReader"]);

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
  "resource://gre/modules/NetUtil.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
  "resource://gre/modules/FileUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  "resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gUpdateTimerManager",
  "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");

XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function() {
  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
});

XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function() {
  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "utf8";
  return converter;
});

XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
  "resource://gre/modules/PageThumbUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
  "resource://gre/modules/PrivateBrowsingUtils.jsm");

/**
 * Utilities for dealing with promises and Task.jsm
 */
const TaskUtils = {
  /**
   * Read the bytes from a blob, asynchronously.
   *
   * @return {Promise}
   * @resolve {ArrayBuffer} In case of success, the bytes contained in the blob.
   * @reject {DOMError} In case of error, the underlying DOMError.
   */
  readBlob: function readBlob(blob) {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onloadend = function onloadend() {
        if (reader.readyState != FileReader.DONE) {
          reject(reader.error);
        } else {
          resolve(reader.result);
        }
      };
      reader.readAsArrayBuffer(blob);
    });
  }
};




/**
 * Singleton providing functionality for capturing web page thumbnails and for
 * accessing them if already cached.
 */
this.PageThumbs = {
  _initialized: false,

  /**
   * The calculated width and height of the thumbnails.
   */
  _thumbnailWidth: 0,
  _thumbnailHeight: 0,

  /**
   * The scheme to use for thumbnail urls.
   */
  get scheme() {
    return "moz-page-thumb";
  },

  /**
   * The static host to use for thumbnail urls.
   */
  get staticHost() {
    return "thumbnail";
  },

  /**
   * The thumbnails' image type.
   */
  get contentType() {
    return "image/png";
  },

  init: function PageThumbs_init() {
    if (!this._initialized) {
      this._initialized = true;
      PlacesUtils.history.addObserver(PageThumbsHistoryObserver, true);

      // Migrate the underlying storage, if needed.
      PageThumbsStorageMigrator.migrate();
      PageThumbsExpiration.init();
    }
  },

  uninit: function PageThumbs_uninit() {
    if (this._initialized) {
      this._initialized = false;
    }
  },

  /**
   * Gets the thumbnail image's url for a given web page's url.
   * @param aUrl The web page's url that is depicted in the thumbnail.
   * @return The thumbnail image's url.
   */
  getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
    return this.scheme + "://" + this.staticHost +
           "/?url=" + encodeURIComponent(aUrl) +
           "&revision=" + PageThumbsStorage.getRevision(aUrl);
  },

   /**
    * Gets the path of the thumbnail file for a given web page's
    * url. This file may or may not exist depending on whether the
    * thumbnail has been captured or not.
    *
    * @param aUrl The web page's url.
    * @return The path of the thumbnail file.
    */
   getThumbnailPath: function PageThumbs_getThumbnailPath(aUrl) {
     return PageThumbsStorage.getFilePathForURL(aUrl);
   },

  /**
   * Asynchronously returns a thumbnail as a blob for the given
   * window.
   *
   * @param aBrowser The <browser> to capture a thumbnail from.
   * @return {Promise}
   * @resolve {Blob} The thumbnail, as a Blob.
   */
  captureToBlob: function PageThumbs_captureToBlob(aBrowser) {
    if (!this._prefEnabled()) {
      return null;
    }

    return new Promise(resolve => {

      let canvas = this.createCanvas(aBrowser.contentWindow);
      this.captureToCanvas(aBrowser, canvas, () => {
        canvas.toBlob(blob => {
          resolve(blob, this.contentType);
        });
      });

    });
  },

  /**
   * Captures a thumbnail from a given window and draws it to the given canvas.
   * Note, when dealing with remote content, this api draws into the passed
   * canvas asynchronously. Pass aCallback to receive an async callback after
   * canvas painting has completed.
   * @param aBrowser The browser to capture a thumbnail from.
   * @param aCanvas The canvas to draw to. The thumbnail will be scaled to match
   *   the dimensions of this canvas. If callers pass a 0x0 canvas, the canvas
   *   will be resized to default thumbnail dimensions just prior to painting.
   * @param aCallback (optional) A callback invoked once the thumbnail has been
   *   rendered to aCanvas.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   */
  captureToCanvas(aBrowser, aCanvas, aCallback, aArgs) {
    let telemetryCaptureTime = new Date();
    let args = {
      fullScale: aArgs ? aArgs.fullScale : false
    };
    this._captureToCanvas(aBrowser, aCanvas, args, (aCanvas) => {
      Services.telemetry
              .getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
              .add(new Date() - telemetryCaptureTime);
      if (aCallback) {
        aCallback(aCanvas);
      }
    });
  },

  /**
   * Asynchronously check the state of aBrowser to see if it passes a set of
   * predefined security checks. Consumers should refrain from storing
   * thumbnails if these checks fail. Note the final result of this call is
   * transitory as it is based on current navigation state and the type of
   * content being displayed.
   *
   * @param aBrowser The target browser
   * @param aCallback(aResult) A callback invoked once security checks have
   *   completed. aResult is a boolean indicating the combined result of the
   *   security checks performed.
   */
  shouldStoreThumbnail(aBrowser, aCallback) {
    // Don't capture in private browsing mode.
    if (PrivateBrowsingUtils.isBrowserPrivate(aBrowser)) {
      aCallback(false);
      return;
    }
    if (aBrowser.isRemoteBrowser) {
      let mm = aBrowser.messageManager;
      let resultFunc = function(aMsg) {
        mm.removeMessageListener("Browser:Thumbnail:CheckState:Response", resultFunc);
        aCallback(aMsg.data.result);
      }
      mm.addMessageListener("Browser:Thumbnail:CheckState:Response", resultFunc);
      try {
        mm.sendAsyncMessage("Browser:Thumbnail:CheckState");
      } catch (ex) {
        Cu.reportError(ex);
        // If the message manager is not able send our message, taking a content
        // screenshot is also not going to work: return false.
        resultFunc({ data: { result: false } });
      }
    } else {
      aCallback(PageThumbUtils.shouldStoreContentThumbnail(aBrowser.contentDocument,
                                                           aBrowser.docShell));
    }
  },

  // The background thumbnail service captures to canvas but doesn't want to
  // participate in this service's telemetry, which is why this method exists.
  _captureToCanvas(aBrowser, aCanvas, aArgs, aCallback) {
    if (aBrowser.isRemoteBrowser) {
      (async () => {
        let data =
          await this._captureRemoteThumbnail(aBrowser, aCanvas.width,
                                             aCanvas.height, aArgs);
        let canvas = data.thumbnail;
        let ctx = canvas.getContext("2d");
        let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        aCanvas.width = canvas.width;
        aCanvas.height = canvas.height;
        aCanvas.getContext("2d").putImageData(imgData, 0, 0);
        if (aCallback) {
          aCallback(aCanvas);
        }
      })();
      return;
    }
    // The content is a local page, grab a thumbnail sync.
    PageThumbUtils.createSnapshotThumbnail(aBrowser.contentWindow,
                                           aCanvas,
                                           aArgs);

    if (aCallback) {
      aCallback(aCanvas);
    }
  },

  /**
   * Asynchrnously render an appropriately scaled thumbnail to canvas.
   *
   * @param aBrowser The browser to capture a thumbnail from.
   * @param aWidth The desired canvas width.
   * @param aHeight The desired canvas height.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   * @return a promise
   */
  _captureRemoteThumbnail(aBrowser, aWidth, aHeight, aArgs) {
    return new Promise(resolve => {

      // The index we send with the request so we can identify the
      // correct response.
      let index = gRemoteThumbId++;

      // Thumbnail request response handler
      let mm = aBrowser.messageManager;

      // Browser:Thumbnail:Response handler
      let thumbFunc = function(aMsg) {
        // Ignore events unrelated to our request
        if (aMsg.data.id != index) {
          return;
        }

        mm.removeMessageListener("Browser:Thumbnail:Response", thumbFunc);
        let imageBlob = aMsg.data.thumbnail;
        let doc = aBrowser.parentElement.ownerDocument;
        let reader = new FileReader();
        reader.addEventListener("loadend", function() {
          let image = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "img");
          image.onload = function() {
            let thumbnail = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "canvas");
            thumbnail.width = image.naturalWidth;
            thumbnail.height = image.naturalHeight;
            let ctx = thumbnail.getContext("2d");
            ctx.drawImage(image, 0, 0);
            resolve({
              thumbnail
            });
          }
          image.src = reader.result;
        });
        // xxx wish there was a way to skip this encoding step
        reader.readAsDataURL(imageBlob);
      }

      // Send a thumbnail request
      mm.addMessageListener("Browser:Thumbnail:Response", thumbFunc);
      mm.sendAsyncMessage("Browser:Thumbnail:Request", {
        canvasWidth: aWidth,
        canvasHeight: aHeight,
        background: PageThumbUtils.THUMBNAIL_BG_COLOR,
        id: index,
        additionalArgs: aArgs
      });

    });
  },

  /**
   * Captures a thumbnail for the given browser and stores it to the cache.
   * @param aBrowser The browser to capture a thumbnail for.
   * @param aCallback The function to be called when finished (optional).
   */
  captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
    if (!this._prefEnabled()) {
      return;
    }

    let url = aBrowser.currentURI.spec;
    let originalURL;
    let channelError = false;

    (async () => {
      if (!aBrowser.isRemoteBrowser) {
        let channel = aBrowser.docShell.currentDocumentChannel;
        originalURL = channel.originalURI.spec;
        // see if this was an error response.
        channelError = PageThumbUtils.isChannelErrorResponse(channel);
      } else {
        let resp = await new Promise(resolve => {
          let mm = aBrowser.messageManager;
          let respName = "Browser:Thumbnail:GetOriginalURL:Response";
          mm.addMessageListener(respName, function onResp(msg) {
            mm.removeMessageListener(respName, onResp);
            resolve(msg.data);
          });
          mm.sendAsyncMessage("Browser:Thumbnail:GetOriginalURL");
        });
        originalURL = resp.originalURL || url;
        channelError = resp.channelError;
      }

      let isSuccess = true;
      try {
        let blob = await this.captureToBlob(aBrowser);
        let buffer = await TaskUtils.readBlob(blob);
        await this._store(originalURL, url, buffer, channelError);
      } catch (ex) {
        Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
        isSuccess = false;
      }
      if (aCallback) {
        aCallback(isSuccess);
      }
    })();
  },

  /**
   * Checks if an existing thumbnail for the specified URL is either missing
   * or stale, and if so, captures and stores it.  Once the thumbnail is stored,
   * an observer service notification will be sent, so consumers should observe
   * such notifications if they want to be notified of an updated thumbnail.
   *
   * @param aBrowser The content window of this browser will be captured.
   * @param aCallback The function to be called when finished (optional).
   */
  captureAndStoreIfStale: function PageThumbs_captureAndStoreIfStale(aBrowser, aCallback) {
    let url = aBrowser.currentURI.spec;
    PageThumbsStorage.isFileRecentForURL(url).then(recent => {
      if (!recent &&
          // Careful, the call to PageThumbsStorage is async, so the browser may
          // have navigated away from the URL or even closed.
          aBrowser.currentURI &&
          aBrowser.currentURI.spec == url) {
        this.captureAndStore(aBrowser, aCallback);
      } else if (aCallback) {
        aCallback(true);
      }
    }, err => {
      if (aCallback)
        aCallback(false);
    });
  },

  /**
   * Stores data to disk for the given URLs.
   *
   * NB: The background thumbnail service calls this, too.
   *
   * @param aOriginalURL The URL with which the capture was initiated.
   * @param aFinalURL The URL to which aOriginalURL ultimately resolved.
   * @param aData An ArrayBuffer containing the image data.
   * @param aNoOverwrite If true and files for the URLs already exist, the files
   *                     will not be overwritten.
   * @return {Promise}
   */
  _store: function PageThumbs__store(aOriginalURL, aFinalURL, aData, aNoOverwrite) {
    return (async function() {
      let telemetryStoreTime = new Date();
      await PageThumbsStorage.writeData(aFinalURL, aData, aNoOverwrite);
      Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
        .add(new Date() - telemetryStoreTime);

      Services.obs.notifyObservers(null, "page-thumbnail:create", aFinalURL);
      // We've been redirected. Create a copy of the current thumbnail for
      // the redirect source. We need to do this because:
      //
      // 1) Users can drag any kind of links onto the newtab page. If those
      //    links redirect to a different URL then we want to be able to
      //    provide thumbnails for both of them.
      //
      // 2) The newtab page should actually display redirect targets, only.
      //    Because of bug 559175 this information can get lost when using
      //    Sync and therefore also redirect sources appear on the newtab
      //    page. We also want thumbnails for those.
      if (aFinalURL != aOriginalURL) {
        await PageThumbsStorage.copy(aFinalURL, aOriginalURL, aNoOverwrite);
        Services.obs.notifyObservers(null, "page-thumbnail:create", aOriginalURL);
      }
    })();
  },

  /**
   * Register an expiration filter.
   *
   * When thumbnails are going to expire, each registered filter is asked for a
   * list of thumbnails to keep.
   *
   * The filter (if it is a callable) or its filterForThumbnailExpiration method
   * (if the filter is an object) is called with a single argument.  The
   * argument is a callback function.  The filter must call the callback
   * function and pass it an array of zero or more URLs.  (It may do so
   * asynchronously.)  Thumbnails for those URLs will be except from expiration.
   *
   * @param aFilter callable, or object with filterForThumbnailExpiration method
   */
  addExpirationFilter: function PageThumbs_addExpirationFilter(aFilter) {
    PageThumbsExpiration.addFilter(aFilter);
  },

  /**
   * Unregister an expiration filter.
   * @param aFilter A filter that was previously passed to addExpirationFilter.
   */
  removeExpirationFilter: function PageThumbs_removeExpirationFilter(aFilter) {
    PageThumbsExpiration.removeFilter(aFilter);
  },

  /**
   * Creates a new hidden canvas element.
   * @param aWindow The document of this window will be used to create the
   *                canvas.  If not given, the hidden window will be used.
   * @return The newly created canvas.
   */
  createCanvas: function PageThumbs_createCanvas(aWindow) {
    return PageThumbUtils.createCanvas(aWindow);
  },

  _prefEnabled: function PageThumbs_prefEnabled() {
    try {
      return !Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
    } catch (e) {
      return true;
    }
  },
};

this.PageThumbsStorage = {
  // The path for the storage
  _path: null,
  get path() {
    if (!this._path) {
      this._path = OS.Path.join(OS.Constants.Path.localProfileDir, THUMBNAIL_DIRECTORY);
    }
    return this._path;
  },

  ensurePath: function Storage_ensurePath() {
    // Create the directory (ignore any error if the directory
    // already exists). As all writes are done from the PageThumbsWorker
    // thread, which serializes its operations, this ensures that
    // future operations can proceed without having to check whether
    // the directory exists.
    return PageThumbsWorker.post("makeDir",
      [this.path, {ignoreExisting: true}]).catch(function onError(aReason) {
          Components.utils.reportError("Could not create thumbnails directory" + aReason);
        });
  },

  getLeafNameForURL: function Storage_getLeafNameForURL(aURL) {
    if (typeof aURL != "string") {
      throw new TypeError("Expecting a string");
    }
    let hash = this._calculateMD5Hash(aURL);
    return hash + ".png";
  },

  getFilePathForURL: function Storage_getFilePathForURL(aURL) {
    return OS.Path.join(this.path, this.getLeafNameForURL(aURL));
  },

  _revisionTable: {},

  // Generate an arbitrary revision tag, i.e. one that can't be used to
  // infer URL frecency.
  _updateRevision(aURL) {
    // Initialize with a random value and increment on each update. Wrap around
    // modulo _revisionRange, so that even small values carry no meaning.
    let rev = this._revisionTable[aURL];
    if (rev == null)
      rev = Math.floor(Math.random() * this._revisionRange);
    this._revisionTable[aURL] = (rev + 1) % this._revisionRange;
  },

  // If two thumbnails with the same URL and revision are in cache at the
  // same time, the image loader may pick the stale thumbnail in some cases.
  // Therefore _revisionRange must be large enough to prevent this, e.g.
  // in the pathological case image.cache.size (5MB by default) could fill
  // with (abnormally small) 10KB thumbnail images if the browser session
  // runs long enough (though this is unlikely as thumbnails are usually
  // only updated every MAX_THUMBNAIL_AGE_SECS).
  _revisionRange: 8192,

  /**
  * Return a revision tag for the thumbnail stored for a given URL.
  *
  * @param aURL The URL spec string
  * @return A revision tag for the corresponding thumbnail. Returns a changed
  * value whenever the stored thumbnail changes.
  */
  getRevision(aURL) {
    let rev = this._revisionTable[aURL];
    if (rev == null) {
      this._updateRevision(aURL);
      rev = this._revisionTable[aURL];
    }
    return rev;
  },

  /**
   * Write the contents of a thumbnail, off the main thread.
   *
   * @param {string} aURL The url for which to store a thumbnail.
   * @param {ArrayBuffer} aData The data to store in the thumbnail, as
   * an ArrayBuffer. This array buffer will be detached and cannot be
   * reused after the copy.
   * @param {boolean} aNoOverwrite If true and the thumbnail's file already
   * exists, the file will not be overwritten.
   *
   * @return {Promise}
   */
  writeData: function Storage_writeData(aURL, aData, aNoOverwrite) {
    let path = this.getFilePathForURL(aURL);
    this.ensurePath();
    aData = new Uint8Array(aData);
    let msg = [
      path,
      aData,
      {
        tmpPath: path + ".tmp",
        bytes: aData.byteLength,
        noOverwrite: aNoOverwrite,
        flush: false /* thumbnails do not require the level of guarantee provided by flush*/
      }];
    return PageThumbsWorker.post("writeAtomic", msg,
      msg /* we don't want that message garbage-collected,
           as OS.Shared.Type.void_t.in_ptr.toMsg uses C-level
           memory tricks to enforce zero-copy*/).
      then(() => this._updateRevision(aURL), this._eatNoOverwriteError(aNoOverwrite));
  },

  /**
   * Copy a thumbnail, off the main thread.
   *
   * @param {string} aSourceURL The url of the thumbnail to copy.
   * @param {string} aTargetURL The url of the target thumbnail.
   * @param {boolean} aNoOverwrite If true and the target file already exists,
   * the file will not be overwritten.
   *
   * @return {Promise}
   */
  copy: function Storage_copy(aSourceURL, aTargetURL, aNoOverwrite) {
    this.ensurePath();
    let sourceFile = this.getFilePathForURL(aSourceURL);
    let targetFile = this.getFilePathForURL(aTargetURL);
    let options = { noOverwrite: aNoOverwrite };
    return PageThumbsWorker.post("copy", [sourceFile, targetFile, options]).
      then(() => this._updateRevision(aTargetURL), this._eatNoOverwriteError(aNoOverwrite));
  },

  /**
   * Remove a single thumbnail, off the main thread.
   *
   * @return {Promise}
   */
  remove: function Storage_remove(aURL) {
    return PageThumbsWorker.post("remove", [this.getFilePathForURL(aURL)]);
  },

  /**
   * Remove all thumbnails, off the main thread.
   *
   * @return {Promise}
   */
  wipe: async function Storage_wipe() {
    //
    // This operation may be launched during shutdown, so we need to
    // take a few precautions to ensure that:
    //
    // 1. it is not interrupted by shutdown, in which case we
    //    could be leaving privacy-sensitive files on disk;
    // 2. it is not launched too late during shutdown, in which
    //    case this could cause shutdown freezes (see bug 1005487,
    //    which will eventually be fixed by bug 965309)
    //

    let blocker = () => promise;

    // The following operation will rise an error if we have already
    // reached profileBeforeChange, in which case it is too late
    // to clear the thumbnail wipe.
    AsyncShutdown.profileBeforeChange.addBlocker(
      "PageThumbs: removing all thumbnails",
      blocker);

    // Start the work only now that `profileBeforeChange` has had
    // a chance to throw an error.

    let promise = PageThumbsWorker.post("wipe", [this.path]);
    try {
      await promise;
    } finally {
       // Generally, we will be done much before profileBeforeChange,
       // so let's not hoard blockers.
       if ("removeBlocker" in AsyncShutdown.profileBeforeChange) {
         // `removeBlocker` was added with bug 985655. In the interest
         // of backporting, let's degrade gracefully if `removeBlocker`
         // doesn't exist.
         AsyncShutdown.profileBeforeChange.removeBlocker(blocker);
       }
    }
  },

  fileExistsForURL: function Storage_fileExistsForURL(aURL) {
    return PageThumbsWorker.post("exists", [this.getFilePathForURL(aURL)]);
  },

  isFileRecentForURL: function Storage_isFileRecentForURL(aURL) {
    return PageThumbsWorker.post("isFileRecent",
                                 [this.getFilePathForURL(aURL),
                                  MAX_THUMBNAIL_AGE_SECS]);
  },

  _calculateMD5Hash: function Storage_calculateMD5Hash(aValue) {
    let hash = gCryptoHash;
    let value = gUnicodeConverter.convertToByteArray(aValue);

    hash.init(hash.MD5);
    hash.update(value, value.length);
    return this._convertToHexString(hash.finish(false));
  },

  _convertToHexString: function Storage_convertToHexString(aData) {
    let hex = "";
    for (let i = 0; i < aData.length; i++)
      hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
    return hex;
  },

  /**
   * For functions that take a noOverwrite option, OS.File throws an error if
   * the target file exists and noOverwrite is true.  We don't consider that an
   * error, and we don't want such errors propagated.
   *
   * @param {aNoOverwrite} The noOverwrite option used in the OS.File operation.
   *
   * @return {function} A function that should be passed as the second argument
   * to then() (the `onError` argument).
   */
  _eatNoOverwriteError: function Storage__eatNoOverwriteError(aNoOverwrite) {
    return function onError(err) {
      if (!aNoOverwrite ||
          !(err instanceof OS.File.Error) ||
          !err.becauseExists) {
        throw err;
      }
    };
  },

  // Deprecated, please do not use
  getFileForURL: function Storage_getFileForURL_DEPRECATED(aURL) {
    Deprecated.warning("PageThumbs.getFileForURL is deprecated. Please use PageThumbs.getFilePathForURL and OS.File",
                       "https://developer.mozilla.org/docs/JavaScript_OS.File");
    // Note: Once this method has been removed, we can get rid of the dependency towards FileUtils
    return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
  }
};

var PageThumbsStorageMigrator = {
  get currentVersion() {
    try {
      return Services.prefs.getIntPref(PREF_STORAGE_VERSION);
    } catch (e) {
      // The pref doesn't exist, yet. Return version 0.
      return 0;
    }
  },

  set currentVersion(aVersion) {
    Services.prefs.setIntPref(PREF_STORAGE_VERSION, aVersion);
  },

  migrate: function Migrator_migrate() {
    let version = this.currentVersion;

    // Storage version 1 never made it to beta.
    // At the time of writing only Windows had (ProfD != ProfLD) and we
    // needed to move thumbnails from the roaming profile to the locale
    // one so that they're not needlessly included in backups and/or
    // written via SMB.

    // Storage version 2 also never made it to beta.
    // The thumbnail folder structure has been changed and old thumbnails
    // were not migrated. Instead, we just renamed the current folder to
    // "<name>-old" and will remove it later.

    if (version < 3) {
      this.migrateToVersion3();
    }

    this.currentVersion = LATEST_STORAGE_VERSION;
  },

  /**
   * Bug 239254 added support for having the disk cache and thumbnail
   * directories on a local path (i.e. ~/.cache/) under Linux. We'll first
   * try to move the old thumbnails to their new location. If that's not
   * possible (because ProfD might be on a different file system than
   * ProfLD) we'll just discard them.
   *
   * @param {string*} local The path to the local profile directory.
   * Used for testing. Default argument is good for all non-testing uses.
   * @param {string*} roaming The path to the roaming profile directory.
   * Used for testing. Default argument is good for all non-testing uses.
   */
  migrateToVersion3: function Migrator_migrateToVersion3(
    local = OS.Constants.Path.localProfileDir,
    roaming = OS.Constants.Path.profileDir) {
    PageThumbsWorker.post(
      "moveOrDeleteAllThumbnails",
      [OS.Path.join(roaming, THUMBNAIL_DIRECTORY),
       OS.Path.join(local, THUMBNAIL_DIRECTORY)]
    );
  }
};

var PageThumbsExpiration = {
  _filters: [],

  init: function Expiration_init() {
    gUpdateTimerManager.registerTimer("browser-cleanup-thumbnails", this,
                                      EXPIRATION_INTERVAL_SECS);
  },

  addFilter: function Expiration_addFilter(aFilter) {
    this._filters.push(aFilter);
  },

  removeFilter: function Expiration_removeFilter(aFilter) {
    let index = this._filters.indexOf(aFilter);
    if (index > -1)
      this._filters.splice(index, 1);
  },

  notify: function Expiration_notify(aTimer) {
    let urls = [];
    let filtersToWaitFor = this._filters.length;

    let expire = () => {
      this.expireThumbnails(urls);
    };

    // No registered filters.
    if (!filtersToWaitFor) {
      expire();
      return;
    }

    function filterCallback(aURLs) {
      urls = urls.concat(aURLs);
      if (--filtersToWaitFor == 0)
        expire();
    }

    for (let filter of this._filters) {
      if (typeof filter == "function")
        filter(filterCallback)
      else
        filter.filterForThumbnailExpiration(filterCallback);
    }
  },

  expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
    let keep = aURLsToKeep.map(url => PageThumbsStorage.getLeafNameForURL(url));
    let msg = [
      PageThumbsStorage.path,
      keep,
      EXPIRATION_MIN_CHUNK_SIZE
    ];

    return PageThumbsWorker.post(
      "expireFilesInDirectory",
      msg
    );
  }
};

/**
 * Interface to a dedicated thread handling I/O
 */
var PageThumbsWorker = new BasePromiseWorker("resource://gre/modules/PageThumbsWorker.js");
// As the PageThumbsWorker performs I/O, we can receive instances of
// OS.File.Error, so we need to install a decoder.
PageThumbsWorker.ExceptionHandlers["OS.File.Error"] = OS.File.Error.fromMsg;

var PageThumbsHistoryObserver = {
  onDeleteURI(aURI, aGUID) {
    PageThumbsStorage.remove(aURI.spec);
  },

  onClearHistory() {
    PageThumbsStorage.wipe();
  },

  onTitleChanged() {},
  onBeginUpdateBatch() {},
  onEndUpdateBatch() {},
  onVisit() {},
  onPageChanged() {},
  onDeleteVisits() {},

  QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
                                         Ci.nsISupportsWeakReference])
};
PK
!<̹modules/PageThumbsWorker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/chrome-worker */

/**
 * A worker dedicated for the I/O component of PageThumbs storage.
 *
 * Do not rely on the API of this worker. In a future version, it might be
 * fully replaced by a OS.File global I/O worker.
 */

"use strict";

importScripts("resource://gre/modules/osfile.jsm");

var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");

var File = OS.File;
var Type = OS.Shared.Type;

var worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function(method, args = []) {
  return Agent[method](...args);
};
worker.postMessage = function(message, ...transfers) {
  self.postMessage(message, ...transfers);
};
worker.close = function() {
  self.close();
};

self.addEventListener("message", msg => worker.handleMessage(msg));


var Agent = {
  // Checks if the specified file exists and has an age less than as
  // specifed (in seconds).
  isFileRecent: function Agent_isFileRecent(path, maxAge) {
    try {
      let stat = OS.File.stat(path);
      let maxDate = new Date();
      maxDate.setSeconds(maxDate.getSeconds() - maxAge);
      return stat.lastModificationDate > maxDate;
    } catch (ex) {
      if (!(ex instanceof OS.File.Error)) {
        throw ex;
      }
      // file doesn't exist (or can't be stat'd) - must be stale.
      return false;
    }
  },

  remove: function Agent_removeFile(path) {
    try {
      OS.File.remove(path);
      return true;
    } catch (e) {
      return false;
    }
  },

  expireFilesInDirectory:
  function Agent_expireFilesInDirectory(path, filesToKeep, minChunkSize) {
    let entries = this.getFileEntriesInDirectory(path, filesToKeep);
    let limit = Math.max(minChunkSize, Math.round(entries.length / 2));

    for (let entry of entries) {
      this.remove(entry.path);

      // Check if we reached the limit of files to remove.
      if (--limit <= 0) {
        break;
      }
    }

    return true;
  },

  getFileEntriesInDirectory:
  function Agent_getFileEntriesInDirectory(path, skipFiles) {
    let iter = new OS.File.DirectoryIterator(path);
    try {
      if (!iter.exists()) {
        return [];
      }

      let skip = new Set(skipFiles);

      let entries = [];
      for (let entry in iter) {
        if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name)) {
          entries.push(entry);
        }
      }
      return entries;
    } finally {
      iter.close();
    }
  },

  moveOrDeleteAllThumbnails:
  function Agent_moveOrDeleteAllThumbnails(pathFrom, pathTo) {
    OS.File.makeDir(pathTo, {ignoreExisting: true});
    if (pathFrom == pathTo) {
      return true;
    }
    let iter = new OS.File.DirectoryIterator(pathFrom);
    if (iter.exists()) {
      for (let entry in iter) {
        if (entry.isDir || entry.isSymLink) {
          continue;
        }


        let from = OS.Path.join(pathFrom, entry.name);
        let to = OS.Path.join(pathTo, entry.name);

        try {
          OS.File.move(from, to, {noOverwrite: true, noCopy: true});
        } catch (e) {
          OS.File.remove(from);
        }
      }
    }
    iter.close();

    try {
      OS.File.removeEmptyDir(pathFrom);
    } catch (e) {
      // This could fail if there's something in
      // the folder we're not permitted to remove.
    }

    return true;
  },

  writeAtomic: function Agent_writeAtomic(path, buffer, options) {
    return File.writeAtomic(path,
      buffer,
      options);
  },

  makeDir: function Agent_makeDir(path, options) {
    return File.makeDir(path, options);
  },

  copy: function Agent_copy(source, dest, options) {
    return File.copy(source, dest, options);
  },

  wipe: function Agent_wipe(path) {
    let iterator = new File.DirectoryIterator(path);
    try {
      for (let entry in iterator) {
        try {
          File.remove(entry.path);
        } catch (ex) {
          // If a file cannot be removed, we should still continue.
          // This can happen at least for any of the following reasons:
          // - access denied;
          // - file has been removed recently during a previous wipe
          //  and the file system has not flushed that yet (yes, this
          //  can happen under Windows);
          // - file has been removed by the user or another process.
        }
      }
    } finally {
      iterator.close();
    }
  },

  exists: function Agent_exists(path) {
    return File.exists(path);
  },
};
PK
!<<modules/PerfMeasurement.jsm/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "PerfMeasurement" ];

/*
 * This is the js module for jsperf. Import it like so:
 *   Components.utils.import("resource://gre/modules/PerfMeasurement.jsm");
 *
 * This will create a 'PerfMeasurement' class.  Instances of this class can
 * be used to benchmark browser operations.
 *
 * For documentation on the API, see js/src/perf/jsperf.h.
 *
 */

Components.classes["@mozilla.org/jsperf;1"].createInstance()();
PK
!<JGG#modules/PerformanceStats-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A proxy implementing communication between the PerformanceStats.jsm modules
 * of the parent and children processes.
 *
 * This script is loaded in all processes but is essentially a NOOP in the
 * parent process.
 */

"use strict";

var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
  "resource://gre/modules/PerformanceStats.jsm");

/**
 * A global performance monitor used by this process.
 *
 * For the sake of simplicity, rather than attempting to map each PerformanceMonitor
 * of the parent to a PerformanceMonitor in each child process, we maintain a single
 * PerformanceMonitor in each child process. Probes activation/deactivation for this
 * monitor is controlled by the activation/deactivation of probes in the parent.
 *
 * In the parent, this is always an empty monitor.
 */
var gMonitor = PerformanceStats.getMonitor([]);

/**
 * `true` if this is a content process, `false` otherwise.
 */
var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

/**
 * Handle message `performance-stats-service-acquire`: ensure that the global
 * monitor has a given probe. This message must be sent by the parent process
 * whenever a probe is activated application-wide.
 *
 * Note that we may miss acquire messages if they are sent before this process is
 * launched. For this reason, `performance-stats-service-collect` automatically
 * re-acquires probes if it realizes that they are missing.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
  if (!isContent) {
    return;
  }
  let name = msg.data.payload;
  ensureAcquired(name);
});

/**
 * Handle message `performance-stats-service-release`: release a given probe
 * from the global monitor. This message must be sent by the parent process
 * whenever a probe is deactivated application-wide.
 *
 * Note that we may miss release messages if they are sent before this process is
 * launched. This is ok, as probes are inactive by default: if we miss the release
 * message, we have already missed the acquire message, and the effect of both
 * messages together is to reset to the default state.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
  if (!isContent) {
    return;
  }

  // Keep only the probes that do not appear in the payload
  let probes = gMonitor.probeNames
    .filter(x => msg.data.payload.indexOf(x) == -1);
  gMonitor = PerformanceStats.getMonitor(probes);
});

/**
 * Ensure that this process has all the probes it needs.
 *
 * @param {Array<string>} probeNames The name of all probes needed by the
 * process.
 */
function ensureAcquired(probeNames) {
  let alreadyAcquired = gMonitor.probeNames;

  // Algorithm is O(n^2) because we expect that n ≤ 3.
  let shouldAcquire = [];
  for (let probeName of probeNames) {
    if (alreadyAcquired.indexOf(probeName) == -1) {
      shouldAcquire.push(probeName)
    }
  }

  if (shouldAcquire.length == 0) {
    return;
  }
  gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
}

/**
 * Handle message `performance-stats-service-collected`: collect the data
 * obtained by the monitor. This message must be sent by the parent process
 * whenever we grab a performance snapshot of the application.
 *
 * This operation provides `null` on the parent process.
 *
 * @param {{data: {payload: Array<string>}}} msg The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-collect", async function(msg) {
  let {id, payload: {probeNames}} = msg.data;
  if (!isContent) {
    // This message was sent by the parent process to itself.
    // As per protocol, respond `null`.
    Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
      id,
      data: null
    });
    return;
  }

  // We may have missed acquire messages if the process was loaded too late.
  // Catch up now.
  ensureAcquired(probeNames);

  // Collect and return data.
  let data = await gMonitor.promiseSnapshot({probeNames});
  Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
    id,
    data
  });
});
PK
!<G'>o>omodules/PerformanceStats.jsm// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["PerformanceStats"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

/**
 * API for querying and examining performance data.
 *
 * This API exposes data from several probes implemented by the JavaScript VM.
 * See `PerformanceStats.getMonitor()` for information on how to monitor data
 * from one or more probes and `PerformanceData` for the information obtained
 * from the probes.
 *
 * Data is collected by "Performance Group". Typically, a Performance Group
 * is a frame, or the internals of the application.
 *
 * Generally, if you have the choice between PerformanceStats and PerformanceWatcher,
 * you should favor PerformanceWatcher.
 */

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/ObjectUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
  "resource://gre/modules/Timer.jsm");

// The nsIPerformanceStatsService provides lower-level
// access to SpiderMonkey and the probes.
XPCOMUtils.defineLazyServiceGetter(this, "performanceStatsService",
  "@mozilla.org/toolkit/performance-stats-service;1",
  Ci.nsIPerformanceStatsService);

// The finalizer lets us automatically release (and when possible deactivate)
// probes when a monitor is garbage-collected.
XPCOMUtils.defineLazyServiceGetter(this, "finalizer",
  "@mozilla.org/toolkit/finalizationwitness;1",
  Ci.nsIFinalizationWitnessService
);

// The topic used to notify that a PerformanceMonitor has been garbage-collected
// and that we can release/close the probes it holds.
const FINALIZATION_TOPIC = "performancemonitor-finalize";

const PROPERTIES_META_IMMUTABLE = ["isSystem", "isChildProcess", "groupId", "processId"];
const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title", "name"];

// How long we wait for children processes to respond.
const MAX_WAIT_FOR_CHILD_PROCESS_MS = 5000;

var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
/**
 * Access to a low-level performance probe.
 *
 * Each probe is dedicated to some form of performance monitoring.
 * As each probe may have a performance impact, a probe is activated
 * only when a client has requested a PerformanceMonitor for this probe,
 * and deactivated once all clients are disposed of.
 */
function Probe(name, impl) {
  this._name = name;
  this._counter = 0;
  this._impl = impl;
}
Probe.prototype = {
  /**
   * Acquire the probe on behalf of a client.
   *
   * If the probe was inactive, activate it. Note that activating a probe
   * can incur a memory or performance cost.
   */
  acquire() {
    if (this._counter == 0) {
      this._impl.isActive = true;
      Process.broadcast("acquire", [this._name]);
    }
    this._counter++;
  },

  /**
   * Release the probe on behalf of a client.
   *
   * If this was the last client for this probe, deactivate it.
   */
  release() {
    this._counter--;
    if (this._counter == 0) {
      try {
        this._impl.isActive = false;
      } catch (ex) {
        if (ex && typeof ex == "object" && ex.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
          // The service has already been shutdown. Ignore further shutdown requests.
          return;
        }
        throw ex;
      }
      Process.broadcast("release", [this._name]);
    }
  },

  /**
   * Obtain data from this probe, once it is available.
   *
   * @param {nsIPerformanceStats} xpcom A xpcom object obtained from
   * SpiderMonkey. Only the fields updated by the low-level probe
   * are in a specified state.
   * @return {object} An object containing the data extracted from this
   * probe. Actual format depends on the probe.
   */
  extract(xpcom) {
    if (!this._impl.isActive) {
      throw new Error(`Probe is inactive: ${this._name}`);
    }
    return this._impl.extract(xpcom);
  },

  /**
   * @param {object} a An object returned by `this.extract()`.
   * @param {object} b An object returned by `this.extract()`.
   *
   * @return {true} If `a` and `b` hold identical values.
   */
  isEqual(a, b) {
    if (a == null && b == null) {
      return true;
    }
    if (a != null && b != null) {
      return this._impl.isEqual(a, b);
    }
    return false;
  },

  /**
   * @param {object} a An object returned by `this.extract()`. May
   * NOT be `null`.
   * @param {object} b An object returned by `this.extract()`. May
   * be `null`.
   *
   * @return {object} An object representing `a - b`. If `b` is
   * `null`, this is `a`.
   */
  subtract(a, b) {
    if (a == null) {
      throw new TypeError();
    }
    if (b == null) {
      return a;
    }
    return this._impl.subtract(a, b);
  },

  importChildCompartments(parent, children) {
    if (!Array.isArray(children)) {
      throw new TypeError();
    }
    if (!parent || !(parent instanceof PerformanceDataLeaf)) {
      throw new TypeError();
    }
    return this._impl.importChildCompartments(parent, children);
  },

  /**
   * The name of the probe.
   */
  get name() {
    return this._name;
  },

  compose(stats) {
    if (!Array.isArray(stats)) {
      throw new TypeError();
    }
    return this._impl.compose(stats);
  }
};

// Utility function. Return the position of the last non-0 item in an
// array, or -1 if there isn't any such item.
function lastNonZero(array) {
  for (let i = array.length - 1; i >= 0; --i) {
    if (array[i] != 0) {
      return i;
    }
  }
  return -1;
}

/**
 * The actual Probes implemented by SpiderMonkey.
 */
var Probes = {
  /**
   * A probe measuring jank.
   *
   * Data provided by this probe uses the following format:
   *
   * @field {number} totalCPUTime The total amount of time spent using the
   * CPU for this performance group, in µs.
   * @field {number} totalSystemTime The total amount of time spent in the
   * kernel for this performance group, in µs.
   * @field {Array<number>} durations An array containing at each position `i`
   * the number of times execution of this component has lasted at least `2^i`
   * milliseconds.
   * @field {number} longestDuration The index of the highest non-0 value in
   * `durations`.
   */
  jank: new Probe("jank", {
    set isActive(x) {
      performanceStatsService.isMonitoringJank = x;
    },
    get isActive() {
      return performanceStatsService.isMonitoringJank;
    },
    extract(xpcom) {
      let durations = xpcom.getDurations();
      return {
        totalUserTime: xpcom.totalUserTime,
        totalSystemTime: xpcom.totalSystemTime,
        totalCPUTime: xpcom.totalUserTime + xpcom.totalSystemTime,
        durations,
        longestDuration: lastNonZero(durations)
      }
    },
    isEqual(a, b) {
      // invariant: `a` and `b` are both non-null
      if (a.totalUserTime != b.totalUserTime) {
        return false;
      }
      if (a.totalSystemTime != b.totalSystemTime) {
        return false;
      }
      for (let i = 0; i < a.durations.length; ++i) {
        if (a.durations[i] != b.durations[i]) {
          return false;
        }
      }
      return true;
    },
    subtract(a, b) {
      // invariant: `a` and `b` are both non-null
      let result = {
        totalUserTime: a.totalUserTime - b.totalUserTime,
        totalSystemTime: a.totalSystemTime - b.totalSystemTime,
        totalCPUTime: a.totalCPUTime - b.totalCPUTime,
        durations: [],
        longestDuration: -1,
      };
      for (let i = 0; i < a.durations.length; ++i) {
        result.durations[i] = a.durations[i] - b.durations[i];
      }
      result.longestDuration = lastNonZero(result.durations);
      return result;
    },
    importChildCompartments() { /* nothing to do */ },
    compose(stats) {
      let result = {
        totalUserTime: 0,
        totalSystemTime: 0,
        totalCPUTime: 0,
        durations: [],
        longestDuration: -1
      };
      for (let stat of stats) {
        result.totalUserTime += stat.totalUserTime;
        result.totalSystemTime += stat.totalSystemTime;
        result.totalCPUTime += stat.totalCPUTime;
        for (let i = 0; i < stat.durations.length; ++i) {
          result.durations[i] += stat.durations[i];
        }
        result.longestDuration = Math.max(result.longestDuration, stat.longestDuration);
      }
      return result;
    }
  }),

  /**
   * A probe measuring CPOW activity.
   *
   * Data provided by this probe uses the following format:
   *
   * @field {number} totalCPOWTime The amount of wallclock time
   * spent executing blocking cross-process calls, in µs.
   */
  cpow: new Probe("cpow", {
    set isActive(x) {
      performanceStatsService.isMonitoringCPOW = x;
    },
    get isActive() {
      return performanceStatsService.isMonitoringCPOW;
    },
    extract(xpcom) {
      return {
        totalCPOWTime: xpcom.totalCPOWTime
      };
    },
    isEqual(a, b) {
      return a.totalCPOWTime == b.totalCPOWTime;
    },
    subtract(a, b) {
      return {
        totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime
      };
    },
    importChildCompartments() { /* nothing to do */ },
    compose(stats) {
      let totalCPOWTime = 0;
      for (let stat of stats) {
        totalCPOWTime += stat.totalCPOWTime;
      }
      return { totalCPOWTime };
    },
  }),

  /**
   * A probe measuring activations, i.e. the number
   * of times code execution has entered a given
   * PerformanceGroup.
   *
   * Note that this probe is always active.
   *
   * Data provided by this probe uses the following format:
   * @type {number} ticks The number of times execution has entered
   * this performance group.
   */
  ticks: new Probe("ticks", {
    set isActive(x) { /* this probe cannot be deactivated */ },
    get isActive() { return true; },
    extract(xpcom) {
      return {
        ticks: xpcom.ticks
      };
    },
    isEqual(a, b) {
      return a.ticks == b.ticks;
    },
    subtract(a, b) {
      return {
        ticks: a.ticks - b.ticks
      };
    },
    importChildCompartments() { /* nothing to do */ },
    compose(stats) {
      let ticks = 0;
      for (let stat of stats) {
        ticks += stat.ticks;
      }
      return { ticks };
    },
  }),

  compartments: new Probe("compartments", {
    set isActive(x) {
      performanceStatsService.isMonitoringPerCompartment = x;
    },
    get isActive() {
      return performanceStatsService.isMonitoringPerCompartment;
    },
    extract(xpcom) {
      return null;
    },
    isEqual(a, b) {
      return true;
    },
    subtract(a, b) {
      return true;
    },
    importChildCompartments(parent, children) {
      parent.children = children;
    },
    compose(stats) {
      return null;
    },
  }),
};

/**
 * A monitor for a set of probes.
 *
 * Keeping probes active when they are unused is often a bad
 * idea for performance reasons. Upon destruction, or whenever
 * a client calls `dispose`, this monitor releases the probes,
 * which may let the system deactivate them.
 */
function PerformanceMonitor(probes) {
  this._probes = probes;

  // Activate low-level features as needed
  for (let probe of probes) {
    probe.acquire();
  }

  // A finalization witness. At some point after the garbage-collection of
  // `this` object, a notification of `FINALIZATION_TOPIC` will be triggered
  // with `id` as message.
  this._id = PerformanceMonitor.makeId();
  this._finalizer = finalizer.make(FINALIZATION_TOPIC, this._id)
  PerformanceMonitor._monitors.set(this._id, probes);
}
PerformanceMonitor.prototype = {
  /**
   * The names of probes activated in this monitor.
   */
  get probeNames() {
    return this._probes.map(probe => probe.name);
  },

  /**
   * Return asynchronously a snapshot with the data
   * for each probe monitored by this PerformanceMonitor.
   *
   * All numeric values are non-negative and can only increase. Depending on
   * the probe and the underlying operating system, probes may not be available
   * immediately and may miss some activity.
   *
   * Clients should NOT expect that the first call to `promiseSnapshot()`
   * will return a `Snapshot` in which all values are 0. For most uses,
   * the appropriate scenario is to perform a first call to `promiseSnapshot()`
   * to obtain a baseline, and then watch evolution of the values by calling
   * `promiseSnapshot()` and `subtract()`.
   *
   * On the other hand, numeric values are also monotonic across several instances
   * of a PerformanceMonitor with the same probes.
   *  let a = PerformanceStats.getMonitor(someProbes);
   *  let snapshot1 = yield a.promiseSnapshot();
   *
   *  // ...
   *  let b = PerformanceStats.getMonitor(someProbes); // Same list of probes
   *  let snapshot2 = yield b.promiseSnapshot();
   *
   *  // all values of `snapshot2` are greater or equal to values of `snapshot1`.
   *
   * @param {object} options If provided, an object that may contain the following
   *   fields:
   *   {Array<string>} probeNames The subset of probes to use for this snapshot.
   *      These probes must be a subset of the probes active in the monitor.
   *
   * @return {Promise}
   * @resolve {Snapshot}
   */
  _checkBeforeSnapshot(options) {
    if (!this._finalizer) {
      throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore");
    }
    let probes;
    if (options && options.probeNames || undefined) {
      if (!Array.isArray(options.probeNames)) {
        throw new TypeError();
      }
      // Make sure that we only request probes that we have
      for (let probeName of options.probeNames) {
        let probe = this._probes.find(probe => probe.name == probeName);
        if (!probe) {
          throw new TypeError(`I need probe ${probeName} but I only have ${this.probeNames}`);
        }
        if (!probes) {
          probes = [];
        }
        probes.push(probe);
      }
    } else {
      probes = this._probes;
    }
    return probes;
  },
  promiseContentSnapshot(options = null) {
    this._checkBeforeSnapshot(options);
    return (new ProcessSnapshot(performanceStatsService.getSnapshot()));
  },
  promiseSnapshot(options = null) {
    let probes = this._checkBeforeSnapshot(options);
    return (async function() {
      let childProcesses = await Process.broadcastAndCollect("collect", {probeNames: probes.map(p => p.name)});
      let xpcom = performanceStatsService.getSnapshot();
      return new ApplicationSnapshot({
        xpcom,
        childProcesses,
        probes,
        date: Cu.now()
      });
    })();
  },

  /**
   * Release the probes used by this monitor.
   *
   * Releasing probes as soon as they are unused is a good idea, as some probes
   * cost CPU and/or memory.
   */
  dispose() {
    if (!this._finalizer) {
      return;
    }
    this._finalizer.forget();
    PerformanceMonitor.dispose(this._id);

    // As a safeguard against double-release, reset everything to `null`
    this._probes = null;
    this._id = null;
    this._finalizer = null;
  }
};
/**
 * @type {Map<string, Array<string>>} A map from id (as produced by `makeId`)
 * to list of probes. Used to deallocate a list of probes during finalization.
 */
PerformanceMonitor._monitors = new Map();

/**
 * Create a `PerformanceMonitor` for a list of probes, register it for
 * finalization.
 */
PerformanceMonitor.make = function(probeNames) {
  // Sanity checks
  if (!Array.isArray(probeNames)) {
    throw new TypeError("Expected an array, got " + probes);
  }
  let probes = [];
  for (let probeName of probeNames) {
    if (!(probeName in Probes)) {
      throw new TypeError("Probe not implemented: " + probeName);
    }
    probes.push(Probes[probeName]);
  }

  return (new PerformanceMonitor(probes));
};

/**
 * Implementation of `dispose`.
 *
 * The actual implementation of `dispose` is as a method of `PerformanceMonitor`,
 * rather than `PerformanceMonitor.prototype`, to avoid needing a strong reference
 * to instances of `PerformanceMonitor`, which would defeat the purpose of
 * finalization.
 */
PerformanceMonitor.dispose = function(id) {
  let probes = PerformanceMonitor._monitors.get(id);
  if (!probes) {
    throw new TypeError("`dispose()` has already been called on this monitor");
  }

  PerformanceMonitor._monitors.delete(id);
  for (let probe of probes) {
    probe.release();
  }
}

// Generate a unique id for each PerformanceMonitor. Used during
// finalization.
PerformanceMonitor._counter = 0;
PerformanceMonitor.makeId = function() {
  return "PerformanceMonitor-" + (this._counter++);
}

// Once a `PerformanceMonitor` has been garbage-collected,
// release the probes unless `dispose()` has already been called.
Services.obs.addObserver(function(subject, topic, value) {
  PerformanceMonitor.dispose(value);
}, FINALIZATION_TOPIC);

// Public API
this.PerformanceStats = {
  /**
   * Create a monitor for observing a set of performance probes.
   */
  getMonitor(probes) {
    return PerformanceMonitor.make(probes);
  }
};


/**
 * Information on a single performance group.
 *
 * This offers the following fields:
 *
 * @field {string} name The name of the performance group:
 * - for the process itself, "<process>";
 * - for platform code, "<platform>";
 * - for a webpage, the url of the page.
 *
 * @field {string|null} title The title of the webpage to which this code
 *  belongs. Note that this is the title of the entire webpage (i.e. the tab),
 *  even if the code is executed in an iframe. Also note that this title may
 *  change over time.
 *
 * @field {number} windowId The outer window ID of the top-level nsIDOMWindow
 *  to which this code belongs. May be 0 if the code doesn't belong to any
 *  nsIDOMWindow.
 *
 * @field {boolean} isSystem `true` if the component is a system component (i.e.
 *  an add-on or platform-code), `false` otherwise (i.e. a webpage).
 *
 * @field {object|undefined} activations See the documentation of probe "ticks".
 *   `undefined` if this probe is not active.
 *
 * @field {object|undefined} jank See the documentation of probe "jank".
 *   `undefined` if this probe is not active.
 *
 * @field {object|undefined} cpow See the documentation of probe "cpow".
 *   `undefined` if this probe is not active.
 */
function PerformanceDataLeaf({xpcom, json, probes}) {
  if (xpcom && json) {
    throw new TypeError("Cannot import both xpcom and json data");
  }
  let source = xpcom || json;
  for (let k of PROPERTIES_META) {
    this[k] = source[k];
  }
  if (xpcom) {
    for (let probe of probes) {
      this[probe.name] = probe.extract(xpcom);
    }
    this.isChildProcess = false;
  } else {
    for (let probe of probes) {
      this[probe.name] = json[probe.name];
    }
    this.isChildProcess = true;
  }
  this.owner = null;
}
PerformanceDataLeaf.prototype = {
  /**
   * Compare two instances of `PerformanceData`
   *
   * @return `true` if `this` and `to` have equal values in all fields.
   */
  equals(to) {
    if (!(to instanceof PerformanceDataLeaf)) {
      throw new TypeError();
    }
    for (let probeName of Object.keys(Probes)) {
      let probe = Probes[probeName];
      if (!probe.isEqual(this[probeName], to[probeName])) {
        return false;
      }
    }
    return true;
  },

  /**
   * Compute the delta between two instances of `PerformanceData`.
   *
   * @param {PerformanceData|null} to. If `null`, assumed an instance of
   * `PerformanceData` in which all numeric values are 0.
   *
   * @return {PerformanceDiff} The performance usage between `to` and `this`.
   */
  subtract(to = null) {
    return (new PerformanceDiffLeaf(this, to));
  }
};

function PerformanceData(timestamp) {
  this._parent = null;
  this._content = new Map();
  this._all = [];
  this._timestamp = timestamp;
}
PerformanceData.prototype = {
  addChild(stat) {
    if (!(stat instanceof PerformanceDataLeaf)) {
      throw new TypeError(); // FIXME
    }
    if (!stat.isChildProcess) {
      throw new TypeError(); // FIXME
    }
    this._content.set(stat.groupId, stat);
    this._all.push(stat);
    stat.owner = this;
  },
  setParent(stat) {
    if (!(stat instanceof PerformanceDataLeaf)) {
      throw new TypeError(); // FIXME
    }
    if (stat.isChildProcess) {
      throw new TypeError(); // FIXME
    }
    this._parent = stat;
    this._all.push(stat);
    stat.owner = this;
  },
  equals(to) {
    if (this._parent && !to._parent) {
      return false;
    }
    if (!this._parent && to._parent) {
      return false;
    }
    if (this._content.size != to._content.size) {
      return false;
    }
    if (this._parent && !this._parent.equals(to._parent)) {
      return false;
    }
    for (let [k, v] of this._content) {
      let v2 = to._content.get(k);
      if (!v2) {
        return false;
      }
      if (!v.equals(v2)) {
        return false;
      }
    }
    return true;
  },
  subtract(to = null) {
    return (new PerformanceDiff(this, to));
  },
  get title() {
    return this._all[0].title;
  }
};

function PerformanceDiff(current, old = null) {
  this.title = current.title;
  this.windowId = current.windowId;
  this.deltaT = old ? current._timestamp - old._timestamp : Infinity;
  this._all = [];

  // Handle the parent, if any.
  if (current._parent) {
    this._parent = old ? current._parent.subtract(old._parent) : current._parent;
    this._all.push(this._parent);
    this._parent.owner = this;
  } else {
    this._parent = null;
  }

  // Handle the children, if any.
  this._content = new Map();
  for (let [k, stat] of current._content) {
    let diff = stat.subtract(old ? old._content.get(k) : null);
    this._content.set(k, diff);
    this._all.push(diff);
    diff.owner = this;
  }

  // Now consolidate data
  for (let k of Object.keys(Probes)) {
    if (!(k in this._all[0])) {
      // The stats don't contain data from this probe.
      continue;
    }
    let data = this._all.map(item => item[k]);
    let probe = Probes[k];
    this[k] = probe.compose(data);
  }
}
PerformanceDiff.prototype = {
  toString() {
    return `[PerformanceDiff] ${this.key}`;
  },
  get windowIds() {
    return this._all.map(item => item.windowId).filter(x => !!x);
  },
  get groupIds() {
    return this._all.map(item => item.groupId);
  },
  get key() {
    if (this._parent) {
      return this._parent.windowId;
    }
    return this._all[0].groupId;
  },
  get names() {
    return this._all.map(item => item.name);
  },
  get processes() {
    return this._all.map(item => ({ isChildProcess: item.isChildProcess, processId: item.processId}));
  }
};

/**
 * The delta between two instances of `PerformanceDataLeaf`.
 *
 * Used to monitor resource usage between two timestamps.
 */
function PerformanceDiffLeaf(current, old = null) {
  for (let k of PROPERTIES_META) {
    this[k] = current[k];
  }

  for (let probeName of Object.keys(Probes)) {
    let other = null;
    if (old && probeName in old) {
      other = old[probeName];
    }

    if (probeName in current) {
      this[probeName] = Probes[probeName].subtract(current[probeName], other);
    }
  }
}

/**
 * A snapshot of a single process.
 */
function ProcessSnapshot({xpcom, probes}) {
  this.componentsData = [];

  let subgroups = new Map();
  let enumeration = xpcom.getComponentsData().enumerate();
  while (enumeration.hasMoreElements()) {
    let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
    let stat = (new PerformanceDataLeaf({xpcom, probes}));

    if (!xpcom.parentId) {
      this.componentsData.push(stat);
    } else {
      let siblings = subgroups.get(xpcom.parentId);
      if (!siblings) {
        subgroups.set(xpcom.parentId, (siblings = []));
      }
      siblings.push(stat);
    }
  }

  for (let group of this.componentsData) {
    for (let probe of probes) {
      probe.importChildCompartments(group, subgroups.get(group.groupId) || []);
    }
  }

  this.processData = (new PerformanceDataLeaf({xpcom: xpcom.getProcessData(), probes}));
}

/**
 * A snapshot of the performance usage of the application.
 *
 * @param {nsIPerformanceSnapshot} xpcom The data acquired from this process.
 * @param {Array<Object>} childProcesses The data acquired from children processes.
 * @param {Array<Probe>} probes The active probes.
 */
function ApplicationSnapshot({xpcom, childProcesses, probes, date}) {
  ProcessSnapshot.call(this, {xpcom, probes});

  this.webpages = new Map();
  this.date = date;

  // Child processes
  for (let {componentsData} of (childProcesses || [])) {
    // We are only interested in `componentsData` for the time being.
    for (let json of componentsData) {
      let leaf = (new PerformanceDataLeaf({json, probes}));
      this.componentsData.push(leaf);
    }
  }

  for (let leaf of this.componentsData) {
    let key, map;
    if (leaf.windowId) {
      key = leaf.windowId;
      map = this.webpages;
    } else {
      continue;
    }

    let combined = map.get(key);
    if (!combined) {
      combined = new PerformanceData(date);
      map.set(key, combined);
    }
    if (leaf.isChildProcess) {
      combined.addChild(leaf);
    } else {
      combined.setParent(leaf);
    }
  }
}

/**
 * Communication with other processes
 */
var Process = {
  // a counter used to match responses to requests
  _idcounter: 0,
  _loader: null,
  /**
   * If we are in a child process, return `null`.
   * Otherwise, return the global parent process message manager
   * and load the script to connect to children processes.
   */
  get loader() {
    if (isContent) {
      return null;
    }
    if (this._loader) {
      return this._loader;
    }
    Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceStats-content.js",
      true/* including future processes*/);
    return this._loader = Services.ppmm;
  },

  /**
   * Broadcast a message to all children processes.
   *
   * NOOP if we are in a child process.
   */
  broadcast(topic, payload) {
    if (!this.loader) {
      return;
    }
    this.loader.broadcastAsyncMessage("performance-stats-service-" + topic, {payload});
  },

  /**
   * Brodcast a message to all children processes and wait for answer.
   *
   * NOOP if we are in a child process, or if we have no children processes,
   * in which case we return `undefined`.
   *
   * @return {undefined} If we have no children processes, in particular
   * if we are in a child process.
   * @return {Promise<Array<Object>>} If we have children processes, an
   * array of objects with a structure similar to PerformanceData. Note
   * that the array may be empty if no child process responded.
   */
  async broadcastAndCollect(topic, payload) {
    if (!this.loader || this.loader.childCount == 1) {
      return undefined;
    }
    const TOPIC = "performance-stats-service-" + topic;
    let id = this._idcounter++;

    // The number of responses we are expecting. Note that we may
    // not receive all responses if a process is too long to respond.
    let expecting = this.loader.childCount;

    // The responses we have collected, in arbitrary order.
    let collected = [];
    let deferred = PromiseUtils.defer();

    let observer = function({data, target}) {
      if (data.id != id) {
        // Collision between two collections,
        // ignore the other one.
        return;
      }
      if (data.data) {
        collected.push(data.data)
      }
      if (--expecting > 0) {
        // We are still waiting for at least one response.
        return;
      }
      deferred.resolve();
    };
    this.loader.addMessageListener(TOPIC, observer);
    this.loader.broadcastAsyncMessage(
      TOPIC,
      {id, payload}
    );

    // Processes can die/freeze/be busy loading a page..., so don't expect
    // that they will always respond.
    let timeout = setTimeout(() => {
      if (expecting == 0) {
        return;
      }
      deferred.resolve();
    }, MAX_WAIT_FOR_CHILD_PROCESS_MS);

    deferred.promise.then(() => {
      clearTimeout(timeout);
    });

    await deferred.promise;
    this.loader.removeMessageListener(TOPIC, observer);

    return collected;
  }
};
PK
!<tX*%modules/PerformanceWatcher-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * An API for being informed of slow tabs (content process scripts).
 */

const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

/**
 * `true` if this is a content process, `false` otherwise.
 */
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

if (isContent) {

const { PerformanceWatcher } = Cu.import("resource://gre/modules/PerformanceWatcher.jsm", {});

let toMsg = function(alerts) {
  let result = [];
  for (let {source, details} of alerts) {
    // Convert xpcom values to serializable data.
    let serializableSource = {};
    for (let k of ["groupId", "name", "windowId", "isSystem", "processId", "isContentProcess"]) {
      serializableSource[k] = source[k];
    }

    let serializableDetails = {};
    for (let k of ["reason", "highestJank", "highestCPOW"]) {
      serializableDetails[k] = details[k];
    }
    result.push({source: serializableSource, details: serializableDetails});
  }
  return result;
}

PerformanceWatcher.addPerformanceListener({windowId: 0}, alerts => {
  Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
    {windows: toMsg(alerts)}
  );
});

}
PK
!<S**modules/PerformanceWatcher.jsm// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * An API for being informed of slow tabs.
 *
 * Generally, this API is both more CPU-efficient and more battery-efficient
 * than PerformanceStats. As PerformanceStats, this API does not provide any
 * information during the startup or shutdown of Firefox.
 *
 * = Example =
 *
 * Example use: reporting whenever any webpage slows down Firefox.
 * let listener = function(alerts) {
 *   // This listener is triggered whenever any window causes Firefox to miss
 *   // frames. FieldArgument `source` contains information about the source of the
 *   // slowdown (including the process in which it happens), while `details`
 *   // contains performance statistics.
 *   for (let {source, details} of alerts) {
 *     console.log(`Oops, window ${source.windowId} seems to be slowing down Firefox.`, details);
 * };
 * // Special windowId 0 lets us to listen to all webpages.
 * PerformanceWatcher.addPerformanceListener({windowId: 0}, listener);
 *
 *
 * = How this works =
 *
 * This high-level API is based on the lower-level nsIPerformanceStatsService.
 * At the end of each event (including micro-tasks), the nsIPerformanceStatsService
 * updates its internal performance statistics and determines whether any
 * window in the current process has exceeded the jank threshold.
 *
 * The PerformanceWatcher maintains low-level performance observers in each
 * process and forwards alerts to the main process. Internal observers collate
 * low-level main process alerts and children process alerts and notify clients
 * of this API.
 */

this.EXPORTED_SYMBOLS = ["PerformanceWatcher"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

let { PerformanceStats, performanceStatsService } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

// `true` if the code is executed in content, `false` otherwise
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

if (!isContent) {
  // Initialize communication with children.
  //
  // To keep the protocol simple, the children inform the parent whenever a slow
  // tab is detected. We do not attempt to implement thresholds.
  Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceWatcher-content.js",
    true/* including future processes*/);

  Services.ppmm.addMessageListener("performancewatcher-propagate-notifications",
    (...args) => ChildManager.notifyObservers(...args)
  );
}

// Configure the performance stats service to inform us in case of jank.
performanceStatsService.jankAlertThreshold = 64000 /* us */;


/**
 * Handle communications with child processes. Handle listening to
 * a single window id (including the special window id 0, which is
 * notified for all windows).
 *
 * Acquire through `ChildManager.getWindow`.
 */
function ChildManager(map, key) {
  this.key = key;
  this._map = map;
  this._listeners = new Set();
}
ChildManager.prototype = {
  /**
   * Add a listener, which will be notified whenever a child process
   * reports a slow performance alert for this window.
   */
  addListener(listener) {
    this._listeners.add(listener);
  },
  /**
   * Remove a listener.
   */
  removeListener(listener) {
    let deleted = this._listeners.delete(listener);
    if (!deleted) {
      throw new Error("Unknown listener");
    }
  },

  listeners() {
    return this._listeners.values();
  }
};

/**
 * Dispatch child alerts to observers.
 *
 * Triggered by messages from content processes.
 */
ChildManager.notifyObservers = function({data: {windows}}) {
  if (windows && windows.length > 0) {
    // Dispatch the entire list to universal listeners
    this._notify(ChildManager.getWindow(0).listeners(), windows);

    // Dispatch individual alerts to individual listeners
    for (let {source, details} of windows) {
      this._notify(ChildManager.getWindow(source.windowId).listeners(), source, details);
    }
  }
};

ChildManager._notify = function(targets, ...args) {
  for (let target of targets) {
    target(...args);
  }
};

ChildManager.getWindow = function(key) {
  return this._get(this._windows, key);
};
ChildManager._windows = new Map();

ChildManager._get = function(map, key) {
  let result = map.get(key);
  if (!result) {
    result = new ChildManager(map, key);
    map.set(key, result);
  }
  return result;
};
let gListeners = new WeakMap();

/**
 * An object in charge of managing all the observables for a single
 * target (window/all windows).
 *
 * In a content process, a target is represented by a single observable.
 * The situation is more sophisticated in a parent process, as a target
 * has both an in-process observable and several observables across children
 * processes.
 *
 * This class abstracts away the difference to simplify the work of
 * (un)registering observers for targets.
 *
 * @param {object} target The target being observed, as an object
 * with one of the following fields:
 *   - {xul:tab} tab A single tab. It must already be initialized.
 *   - {number} windowId Either 0 for the universal window observer
 *     or the outer window id of the window.
 */
function Observable(target) {
  // A mapping from `listener` (function) to `Observer`.
  this._observers = new Map();
  if ("tab" in target || "windowId" in target) {
    let windowId;
    if ("tab" in target) {
      windowId = target.tab.linkedBrowser.outerWindowID;
      // By convention, outerWindowID may not be 0.
    } else if ("windowId" in target) {
      windowId = target.windowId;
    }
    if (windowId == undefined || windowId == null) {
      throw new TypeError(`No outerWindowID. Perhaps the target is a tab that is not initialized yet.`);
    }
    this._key = `tab-windowId: ${windowId}`;
    this._process = performanceStatsService.getObservableWindow(windowId);
    this._children = isContent ? null : ChildManager.getWindow(windowId);
    this._isBuffered = windowId == 0;
  } else {
    throw new TypeError("Unexpected target");
  }
}
Observable.prototype = {
  addJankObserver(listener) {
    if (this._observers.has(listener)) {
      throw new TypeError(`Listener already registered for target ${this._key}`);
    }
    if (this._children) {
      this._children.addListener(listener);
    }
    let observer = this._isBuffered ? new BufferedObserver(listener)
      : new Observer(listener);
    // Store the observer to be able to call `this._process.removeJankObserver`.
    this._observers.set(listener, observer);

    this._process.addJankObserver(observer);
  },
  removeJankObserver(listener) {
    let observer = this._observers.get(listener);
    if (!observer) {
      throw new TypeError(`No listener for target ${this._key}`);
    }
    this._observers.delete(listener);

    if (this._children) {
      this._children.removeListener(listener);
    }

    this._process.removeJankObserver(observer);
    observer.dispose();
  },
};

/**
 * Get a cached observable for a given target.
 */
Observable.get = function(target) {
  let key;
  if ("tab" in target) {
    // We do not want to use a tab as a key, as this would prevent it from
    // being garbage-collected.
    key = target.tab.linkedBrowser.outerWindowID;
  } else if ("windowId" in target) {
    key = target.windowId;
  }
  if (key == null) {
    throw new TypeError(`Could not extract a key from ${JSON.stringify(target)}. Could the target be an unitialized tab?`);
  }
  let observable = this._cache.get(key);
  if (!observable) {
    observable = new Observable(target);
    this._cache.set(key, observable);
  }
  return observable;
};
Observable._cache = new Map();

/**
 * Wrap a listener callback as an unbuffered nsIPerformanceObserver.
 *
 * Each observation is propagated immediately to the listener.
 */
function Observer(listener) {
  // Make sure that monitoring stays alive (in all processes) at least as
  // long as the observer.
  this._monitor = PerformanceStats.getMonitor(["jank", "cpow"]);
  this._listener = listener;
}
Observer.prototype = {
  observe(...args) {
    this._listener(...args);
  },
  dispose() {
    this._monitor.dispose();
    this.observe = function poison() {
      throw new Error("Internal error: I should have stopped receiving notifications");
    }
  },
};

/**
 * Wrap a listener callback as an buffered nsIPerformanceObserver.
 *
 * Observations are buffered and dispatch in the next tick to the listener.
 */
function BufferedObserver(listener) {
  Observer.call(this, listener);
  this._buffer = [];
  this._isDispatching = false;
  this._pending = null;
}
BufferedObserver.prototype = Object.create(Observer.prototype);
BufferedObserver.prototype.observe = function(source, details) {
  this._buffer.push({source, details});
  if (!this._isDispatching) {
    this._isDispatching = true;
    Services.tm.dispatchToMainThread(() => {
      // Grab buffer, in case something in the listener could modify it.
      let buffer = this._buffer;
      this._buffer = [];

      // As of this point, any further observations need to use the new buffer
      // and a new dispatcher.
      this._isDispatching = false;

      this._listener(buffer);
    });
  }
};

this.PerformanceWatcher = {
  /**
   * Add a listener informed whenever we receive a slow performance alert
   * in the application.
   *
   * @param {object} target An object with one of the following fields:
   *  - {number} windowId Either 0 to observe all windows or an outer window ID
   *      to observe a single tab.
   *  - {xul:browser} tab To observe a single tab.
   * @param {function} listener A function that will be triggered whenever
   *    the target causes a slow performance notification. The notification may
   *    have originated in any process of the application.
   *
   *    If the listener listens to a single webpage, it is triggered with
   *    the following arguments:
   *       source: {groupId, name, windowId, isSystem, processId}
   *         Information on the source of the notification.
   *       details: {reason, highestJank, highestCPOW} Information on the
   *         notification.
   *
   *    If the listener listens to all webpages, it is triggered with
   *    an array of {source, details}, as described above.
   */
  addPerformanceListener(target, listener) {
    if (typeof listener != "function") {
      throw new TypeError();
    }
    let observable = Observable.get(target);
    observable.addJankObserver(listener);
  },
  removePerformanceListener(target, listener) {
    if (typeof listener != "function") {
      throw new TypeError();
    }
    let observable = Observable.get(target);
    observable.removeJankObserver(listener);
  },
};
PK
!<S

modules/PermissionsUtils.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.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 = ["PermissionsUtils"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/BrowserUtils.jsm")


var gImportedPrefBranches = new Set();

function importPrefBranch(aPrefBranch, aPermission, aAction) {
  let list = Services.prefs.getChildList(aPrefBranch, {});

  for (let pref of list) {
    let origins = Services.prefs.getCharPref(pref, "");

    if (!origins)
      continue;

    origins = origins.split(",");

    for (let origin of origins) {
      let principals = [];
      try {
        principals = [ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin) ];
      } catch (e) {
        // This preference used to contain a list of hosts. For back-compat
        // reasons, we convert these hosts into http:// and https:// permissions
        // on default ports.
        try {
          let httpURI = Services.io.newURI("http://" + origin);
          let httpsURI = Services.io.newURI("https://" + origin);

          principals = [
            Services.scriptSecurityManager.createCodebasePrincipal(httpURI, {}),
            Services.scriptSecurityManager.createCodebasePrincipal(httpsURI, {})
          ];
        } catch (e2) {}
      }

      for (let principal of principals) {
        try {
          Services.perms.addFromPrincipal(principal, aPermission, aAction);
        } catch (e) {}
      }
    }

    Services.prefs.setCharPref(pref, "");
  }
}


this.PermissionsUtils = {
  /**
   * Import permissions from perferences to the Permissions Manager. After being
   * imported, all processed permissions will be set to an empty string.
   * Perferences are only processed once during the application's
   * lifetime - it's safe to call this multiple times without worrying about
   * doing unnecessary work, as the preferences branch will only be processed
   * the first time.
   *
   * @param aPrefBranch  Preferences branch to import from. The preferences
   *                     under this branch can specify whitelist (ALLOW_ACTION)
   *                     or blacklist (DENY_ACTION) additions using perference
   *                     names of the form:
   *                     * <BRANCH>.whitelist.add.<ID>
   *                     * <BRANCH>.blacklist.add.<ID>
   *                     Where <ID> can be any valid preference name.
   *                     The value is expected to be a comma separated list of
   *                     host named. eg:
   *                     * something.example.com
   *                     * foo.exmaple.com,bar.example.com
   *
   * @param aPermission Permission name to be passsed to the Permissions
   *                    Manager.
   */
  importFromPrefs(aPrefBranch, aPermission) {
    if (!aPrefBranch.endsWith("."))
      aPrefBranch += ".";

    // Ensure we only import this pref branch once.
    if (gImportedPrefBranches.has(aPrefBranch))
     return;

    importPrefBranch(aPrefBranch + "whitelist.add", aPermission,
                     Services.perms.ALLOW_ACTION);
    importPrefBranch(aPrefBranch + "blacklist.add", aPermission,
                     Services.perms.DENY_ACTION);

    gImportedPrefBranches.add(aPrefBranch);
  }
};
PK
!<WUWUmodules/PlacesBackups.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["PlacesBackups"];

const Ci = Components.interfaces;
const Cu = Components.utils;
const Cc = Components.classes;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
  "resource://gre/modules/BookmarkJSONUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyGetter(this, "localFileCtor",
  () => Components.Constructor("@mozilla.org/file/local;1",
                               "nsILocalFile", "initWithPath"));

XPCOMUtils.defineLazyGetter(this, "filenamesRegex",
  () => /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=+-]{24})){0,1}\.(json(lz4)?)$/i
);

/**
 * Appends meta-data information to a given filename.
 */
function appendMetaDataToFilename(aFilename, aMetaData) {
  let matches = aFilename.match(filenamesRegex);
  return "bookmarks-" + matches[1] +
                  "_" + aMetaData.count +
                  "_" + aMetaData.hash +
                  "." + matches[4];
}

/**
 * Gets the hash from a backup filename.
 *
 * @return the extracted hash or null.
 */
function getHashFromFilename(aFilename) {
  let matches = aFilename.match(filenamesRegex);
  if (matches && matches[3])
    return matches[3];
  return null;
}

/**
 * Given two filenames, checks if they contain the same date.
 */
function isFilenameWithSameDate(aSourceName, aTargetName) {
  let sourceMatches = aSourceName.match(filenamesRegex);
  let targetMatches = aTargetName.match(filenamesRegex);

  return sourceMatches && targetMatches &&
         sourceMatches[1] == targetMatches[1];
}

/**
 * Given a filename, searches for another backup with the same date.
 *
 * @return OS.File path string or null.
 */
function getBackupFileForSameDate(aFilename) {
  return (async function() {
    let backupFiles = await PlacesBackups.getBackupFiles();
    for (let backupFile of backupFiles) {
      if (isFilenameWithSameDate(OS.Path.basename(backupFile), aFilename))
        return backupFile;
    }
    return null;
  })();
}

/**
 * Returns the top-level bookmark folders ids and guids.
 *
 * @return {Promise} Resolve with an array of objects containing id and guid
 *                   when the query is complete.
 */
async function getTopLevelFolderIds() {
  let db =  await PlacesUtils.promiseDBConnection();
  let rows = await db.execute(
    "SELECT id, guid FROM moz_bookmarks WHERE parent = :parentId",
    { parentId: PlacesUtils.placesRootId }
  );

  let guids = [];
  for (let row of rows) {
    guids.push({
      id: row.getResultByName("id"),
      guid: row.getResultByName("guid")
    });
  }
  return guids;
}


this.PlacesBackups = {
  /**
   * Matches the backup filename:
   *  0: file name
   *  1: date in form Y-m-d
   *  2: bookmarks count
   *  3: contents hash
   *  4: file extension
   */
  get filenamesRegex() {
    return filenamesRegex;
  },

  get folder() {
    Deprecated.warning(
      "PlacesBackups.folder is deprecated and will be removed in a future version",
      "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
    return this._folder;
  },

  /**
   * This exists just to avoid spamming deprecate warnings from internal calls
   * needed to support deprecated methods themselves.
   */
  get _folder() {
    let bookmarksBackupDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
    bookmarksBackupDir.append(this.profileRelativeFolderPath);
    if (!bookmarksBackupDir.exists()) {
      bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
      if (!bookmarksBackupDir.exists())
        throw ("Unable to create bookmarks backup folder");
    }
    delete this._folder;
    return this._folder = bookmarksBackupDir;
  },

  /**
   * Gets backup folder asynchronously.
   * @return {Promise}
   * @resolve the folder (the folder string path).
   */
  getBackupFolder: function PB_getBackupFolder() {
    return (async () => {
      if (this._backupFolder) {
        return this._backupFolder;
      }
      let profileDir = OS.Constants.Path.profileDir;
      let backupsDirPath = OS.Path.join(profileDir, this.profileRelativeFolderPath);
      await OS.File.makeDir(backupsDirPath, { ignoreExisting: true });
      return this._backupFolder = backupsDirPath;
    })();
  },

  get profileRelativeFolderPath() {
    return "bookmarkbackups";
  },

  /**
   * Cache current backups in a sorted (by date DESC) array.
   */
  get entries() {
    Deprecated.warning(
      "PlacesBackups.entries is deprecated and will be removed in a future version",
      "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
    return this._entries;
  },

  /**
   * This exists just to avoid spamming deprecate warnings from internal calls
   * needed to support deprecated methods themselves.
   */
  get _entries() {
    delete this._entries;
    this._entries = [];
    let files = this._folder.directoryEntries;
    while (files.hasMoreElements()) {
      let entry = files.getNext().QueryInterface(Ci.nsIFile);
      // A valid backup is any file that matches either the localized or
      // not-localized filename (bug 445704).
      if (!entry.isHidden() && filenamesRegex.test(entry.leafName)) {
        // Remove bogus backups in future dates.
        if (this.getDateForFile(entry) > new Date()) {
          entry.remove(false);
          continue;
        }
        this._entries.push(entry);
      }
    }
    this._entries.sort((a, b) => {
      let aDate = this.getDateForFile(a);
      let bDate = this.getDateForFile(b);
      return bDate - aDate;
    });
    return this._entries;
  },

  /**
   * Cache current backups in a sorted (by date DESC) array.
   * @return {Promise}
   * @resolve a sorted array of string paths.
   */
  getBackupFiles: function PB_getBackupFiles() {
    return (async () => {
      if (this._backupFiles)
        return this._backupFiles;

      this._backupFiles = [];

      let backupFolderPath = await this.getBackupFolder();
      let iterator = new OS.File.DirectoryIterator(backupFolderPath);
      await iterator.forEach(aEntry => {
        // Since this is a lazy getter and OS.File I/O is serialized, we can
        // safely remove .tmp files without risking to remove ongoing backups.
        if (aEntry.name.endsWith(".tmp")) {
          OS.File.remove(aEntry.path);
          return undefined;
        }

        if (filenamesRegex.test(aEntry.name)) {
          // Remove bogus backups in future dates.
          let filePath = aEntry.path;
          if (this.getDateForFile(filePath) > new Date()) {
            return OS.File.remove(filePath);
          }
          this._backupFiles.push(filePath);
        }

        return undefined;
      });
      iterator.close();

      this._backupFiles.sort((a, b) => {
        let aDate = this.getDateForFile(a);
        let bDate = this.getDateForFile(b);
        return bDate - aDate;
      });

      return this._backupFiles;
    })();
  },

  /**
   * Generates a ISO date string (YYYY-MM-DD) from a Date object.
   *
   * @param dateObj
   *        The date object to parse.
   * @return an ISO date string.
   */
   toISODateString: function toISODateString(dateObj) {
    if (!dateObj || dateObj.constructor.name != "Date" || !dateObj.getTime())
      throw new Error("invalid date object");
    let padDate = val => ("0" + val).substr(-2, 2);
    return [
      dateObj.getFullYear(),
      padDate(dateObj.getMonth() + 1),
      padDate(dateObj.getDate())
    ].join("-");
   },

  /**
   * Creates a filename for bookmarks backup files.
   *
   * @param [optional] aDateObj
   *                   Date object used to build the filename.
   *                   Will use current date if empty.
   * @param [optional] bool - aCompress
   *                   Determines if file extension is json or jsonlz4
                       Default is json
   * @return A bookmarks backup filename.
   */
  getFilenameForDate: function PB_getFilenameForDate(aDateObj, aCompress) {
    let dateObj = aDateObj || new Date();
    // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
    // and makes the alphabetical order of multiple backup files more useful.
      return "bookmarks-" + PlacesBackups.toISODateString(dateObj) + ".json" +
                            (aCompress ? "lz4" : "");
  },

  /**
   * Creates a Date object from a backup file.  The date is the backup
   * creation date.
   *
   * @param aBackupFile
   *        nsIFile or string path of the backup.
   * @return A Date object for the backup's creation time.
   */
  getDateForFile: function PB_getDateForFile(aBackupFile) {
    let filename = (aBackupFile instanceof Ci.nsIFile) ? aBackupFile.leafName
                                                       : OS.Path.basename(aBackupFile);
    let matches = filename.match(filenamesRegex);
    if (!matches)
      throw ("Invalid backup file name: " + filename);
    return new Date(matches[1].replace(/-/g, "/"));
  },

  /**
   * Get the most recent backup file.
   *
   * @returns nsIFile backup file
   */
  getMostRecent: function PB_getMostRecent() {
    Deprecated.warning(
      "PlacesBackups.getMostRecent is deprecated and will be removed in a future version",
      "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");

    for (let i = 0; i < this._entries.length; i++) {
      let rx = /\.json(lz4)?$/;
      if (this._entries[i].leafName.match(rx))
        return this._entries[i];
    }
    return null;
  },

   /**
    * Get the most recent backup file.
    *
    * @return {Promise}
    * @result the path to the file.
    */
   getMostRecentBackup: function PB_getMostRecentBackup() {
     return (async () => {
       let entries = await this.getBackupFiles();
       for (let entry of entries) {
         let rx = /\.json(lz4)?$/;
         if (OS.Path.basename(entry).match(rx)) {
           return entry;
         }
       }
       return null;
    })();
  },

  /**
   * Serializes bookmarks using JSON, and writes to the supplied file.
   * Note: any item that should not be backed up must be annotated with
   *       "places/excludeFromBackup".
   *
   * @param aFilePath
   *        OS.File path for the "bookmarks.json" file to be created.
   * @return {Promise}
   * @resolves the number of serialized uri nodes.
   * @deprecated passing an nsIFile is deprecated
   */
  saveBookmarksToJSONFile: function PB_saveBookmarksToJSONFile(aFilePath) {
    if (aFilePath instanceof Ci.nsIFile) {
      Deprecated.warning("Passing an nsIFile to PlacesBackups.saveBookmarksToJSONFile " +
                         "is deprecated. Please use an OS.File path instead.",
                         "https://developer.mozilla.org/docs/JavaScript_OS.File");
      aFilePath = aFilePath.path;
    }
    return (async () => {
      let { count: nodeCount, hash: hash } =
        await BookmarkJSONUtils.exportToFile(aFilePath);

      let backupFolderPath = await this.getBackupFolder();
      if (OS.Path.dirname(aFilePath) == backupFolderPath) {
        // We are creating a backup in the default backups folder,
        // so just update the internal cache.
        this._entries.unshift(new localFileCtor(aFilePath));
        if (!this._backupFiles) {
          await this.getBackupFiles();
        }
        this._backupFiles.unshift(aFilePath);
      } else {
        // If we are saving to a folder different than our backups folder, then
        // we also want to create a new compressed version in it.
        // This way we ensure the latest valid backup is the same saved by the
        // user.  See bug 424389.
        let mostRecentBackupFile = await this.getMostRecentBackup();
        if (!mostRecentBackupFile ||
            hash != getHashFromFilename(OS.Path.basename(mostRecentBackupFile))) {
          let name = this.getFilenameForDate(undefined, true);
          let newFilename = appendMetaDataToFilename(name,
                                                     { count: nodeCount,
                                                       hash });
          let newFilePath = OS.Path.join(backupFolderPath, newFilename);
          let backupFile = await getBackupFileForSameDate(name);
          if (backupFile) {
            // There is already a backup for today, replace it.
            await OS.File.remove(backupFile, { ignoreAbsent: true });
            if (!this._backupFiles)
              await this.getBackupFiles();
            else
              this._backupFiles.shift();
            this._backupFiles.unshift(newFilePath);
          } else {
            // There is no backup for today, add the new one.
            this._entries.unshift(new localFileCtor(newFilePath));
            if (!this._backupFiles)
              await this.getBackupFiles();
            this._backupFiles.unshift(newFilePath);
          }
          let jsonString = await OS.File.read(aFilePath);
          await OS.File.writeAtomic(newFilePath, jsonString, { compression: "lz4" });
        }
      }

      return nodeCount;
    })();
  },

  /**
   * Creates a dated backup in <profile>/bookmarkbackups.
   * Stores the bookmarks using a lz4 compressed JSON file.
   * Note: any item that should not be backed up must be annotated with
   *       "places/excludeFromBackup".
   *
   * @param [optional] int aMaxBackups
   *                       The maximum number of backups to keep.  If set to 0
   *                       all existing backups are removed and aForceBackup is
   *                       ignored, so a new one won't be created.
   * @param [optional] bool aForceBackup
   *                        Forces creating a backup even if one was already
   *                        created that day (overwrites).
   * @return {Promise}
   */
  create: function PB_create(aMaxBackups, aForceBackup) {
    let limitBackups = async () => {
      let backupFiles = await this.getBackupFiles();
      if (typeof aMaxBackups == "number" && aMaxBackups > -1 &&
          backupFiles.length >= aMaxBackups) {
        let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
        while (numberOfBackupsToDelete--) {
          this._entries.pop();
          let oldestBackup = this._backupFiles.pop();
          await OS.File.remove(oldestBackup);
        }
      }
    };

    return (async () => {
      if (aMaxBackups === 0) {
        // Backups are disabled, delete any existing one and bail out.
        await limitBackups(0);
        return;
      }

      // Ensure to initialize _backupFiles
      if (!this._backupFiles)
        await this.getBackupFiles();
      let newBackupFilename = this.getFilenameForDate(undefined, true);
      // If we already have a backup for today we should do nothing, unless we
      // were required to enforce a new backup.
      let backupFile = await getBackupFileForSameDate(newBackupFilename);
      if (backupFile && !aForceBackup)
        return;

      if (backupFile) {
        // In case there is a backup for today we should recreate it.
        this._backupFiles.shift();
        this._entries.shift();
        await OS.File.remove(backupFile, { ignoreAbsent: true });
      }

      // Now check the hash of the most recent backup, and try to create a new
      // backup, if that fails due to hash conflict, just rename the old backup.
      let mostRecentBackupFile = await this.getMostRecentBackup();
      let mostRecentHash = mostRecentBackupFile &&
                           getHashFromFilename(OS.Path.basename(mostRecentBackupFile));

      // Save bookmarks to a backup file.
      let backupFolder = await this.getBackupFolder();
      let newBackupFile = OS.Path.join(backupFolder, newBackupFilename);
      let newFilenameWithMetaData;
      try {
        let { count: nodeCount, hash: hash } =
          await BookmarkJSONUtils.exportToFile(newBackupFile,
                                               { compress: true,
                                                 failIfHashIs: mostRecentHash });
        newFilenameWithMetaData = appendMetaDataToFilename(newBackupFilename,
                                                           { count: nodeCount,
                                                             hash });
      } catch (ex) {
        if (!ex.becauseSameHash) {
          throw ex;
        }
        // The last backup already contained up-to-date information, just
        // rename it as if it was today's backup.
        this._backupFiles.shift();
        this._entries.shift();
        newBackupFile = mostRecentBackupFile;
        // Ensure we retain the proper extension when renaming
        // the most recent backup file.
        if (/\.json$/.test(OS.Path.basename(mostRecentBackupFile)))
          newBackupFilename = this.getFilenameForDate();
        newFilenameWithMetaData = appendMetaDataToFilename(
          newBackupFilename,
          { count: this.getBookmarkCountForFile(mostRecentBackupFile),
            hash: mostRecentHash });
      }

      // Append metadata to the backup filename.
      let newBackupFileWithMetadata = OS.Path.join(backupFolder, newFilenameWithMetaData);
      await OS.File.move(newBackupFile, newBackupFileWithMetadata);
      this._entries.unshift(new localFileCtor(newBackupFileWithMetadata));
      this._backupFiles.unshift(newBackupFileWithMetadata);

      // Limit the number of backups.
      await limitBackups(aMaxBackups);
    })();
  },

  /**
   * Gets the bookmark count for backup file.
   *
   * @param aFilePath
   *        File path The backup file.
   *
   * @return the bookmark count or null.
   */
  getBookmarkCountForFile: function PB_getBookmarkCountForFile(aFilePath) {
    let count = null;
    let filename = OS.Path.basename(aFilePath);
    let matches = filename.match(filenamesRegex);
    if (matches && matches[2])
      count = matches[2];
    return count;
  },

  /**
   * Gets a bookmarks tree representation usable to create backups in different
   * file formats.  The root or the tree is PlacesUtils.placesRootId.
   * Items annotated with PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO and all of their
   * descendants are excluded.
   *
   * @return an object representing a tree with the places root as its root.
   *         Each bookmark is represented by an object having these properties:
   *         * id: the item id (make this not enumerable after bug 824502)
   *         * title: the title
   *         * guid: unique id
   *         * parent: item id of the parent folder, not enumerable
   *         * index: the position in the parent
   *         * dateAdded: microseconds from the epoch
   *         * lastModified: microseconds from the epoch
   *         * type: type of the originating node as defined in PlacesUtils
   *         The following properties exist only for a subset of bookmarks:
   *         * annos: array of annotations
   *         * uri: url
   *         * iconuri: favicon's url
   *         * keyword: associated keyword
   *         * charset: last known charset
   *         * tags: csv string of tags
   *         * root: string describing whether this represents a root
   *         * children: array of child items in a folder
   */
  async getBookmarksTree() {
    let startTime = Date.now();
    let root = await PlacesUtils.promiseBookmarksTree(PlacesUtils.bookmarks.rootGuid, {
      excludeItemsCallback: aItem => {
        return aItem.annos &&
          aItem.annos.find(a => a.name == PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
      },
      includeItemIds: true
    });

    try {
      Services.telemetry
              .getHistogramById("PLACES_BACKUPS_BOOKMARKSTREE_MS")
              .add(Date.now() - startTime);
    } catch (ex) {
      Components.utils.reportError("Unable to report telemetry.");
    }
    return [root, root.itemsCount];
  },

  /**
   * Wrapper for PlacesUtils.bookmarks.eraseEverything that removes non-default
   * roots.
   *
   * Note that default roots are preserved, only their children will be removed.
   *
   * TODO Ideally we wouldn't need to worry about non-default roots. However,
   * until bug 1310299 is fixed, we still need to manage them.
   *
   * @param {Object} [options={}]
   *        Additional options. Currently supports the following properties:
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when the removal is complete.
   * @resolves once the removal is complete.
   */
  async eraseEverythingIncludingUserRoots(options = {}) {
    if (!options.source) {
      options.source = PlacesUtils.bookmarks.SOURCES.DEFAULT;
    }

    let excludeItems =
      PlacesUtils.annotations.getItemsWithAnnotation(PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);

    let rootFolderChildren = await getTopLevelFolderIds();

    // We only need to do top-level roots here.
    for (let child of rootFolderChildren) {
      if (!PlacesUtils.bookmarks.userContentRoots.includes(child.guid) &&
          child.guid != PlacesUtils.bookmarks.tagsGuid &&
          !excludeItems.includes(child.id)) {
       await PlacesUtils.bookmarks.remove(child.guid, {source: options.source});
      }
    }

    return PlacesUtils.bookmarks.eraseEverything(options);
  },
}
PK
!<x``modules/PlacesDBUtils.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

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");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

this.EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];

// Constants

const BYTES_PER_MEBIBYTE = 1048576;

this.PlacesDBUtils = {

  /**
   * Converts the `Map` returned by `runTasks` to an array of messages (legacy).
   * @param taskStatusMap
   *        The Map[String -> Object] returned by `runTasks`.
   * @return an array of log messages.
   */
  getLegacyLog(taskStatusMap) {
    let logs = [];
    for (let [key, value] of taskStatusMap) {
      logs.push(`> Task: ${key}`);
      let prefix = value.succeeded ? "+ " : "- ";
      logs = logs.concat(value.logs.map(m => `${prefix}${m}`));
    }
    return logs;
  },

  _isShuttingDown: false,
  shutdown() {
    PlacesDBUtils._isShuttingDown = true;
  },

  _clearTaskQueue: false,
  clearPendingTasks() {
    PlacesDBUtils._clearTaskQueue = true;
  },

  /**
   * Executes integrity check and common maintenance tasks.
   *
   * @return a Map[taskName(String) -> Object]. The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task.
   */
  async maintenanceOnIdle() {
    let tasks = [
      this.checkIntegrity,
      this.checkCoherence,
      this._refreshUI
    ];
    let telemetryStartTime = Date.now();
    let taskStatusMap = await PlacesDBUtils.runTasks(tasks);

    Services.prefs.setIntPref("places.database.lastMaintenance",
                               parseInt(Date.now() / 1000));
    Services.telemetry.getHistogramById("PLACES_IDLE_MAINTENANCE_TIME_MS")
                      .add(Date.now() - telemetryStartTime);
    return taskStatusMap;
  },

  /**
   * Executes integrity check, common and advanced maintenance tasks (like
   * expiration and vacuum).  Will also collect statistics on the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise}
   *        A promise that resolves with a Map[taskName(String) -> Object].
   *        The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task.
   */
  async checkAndFixDatabase() { // eslint-disable-line require-await
    let tasks = [
      this.checkIntegrity,
      this.checkCoherence,
      this.expire,
      this.vacuum,
      this.stats,
      this._refreshUI,
    ];
    return PlacesDBUtils.runTasks(tasks);
  },

  /**
   * Forces a full refresh of Places views.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @returns {Array} An empty array.
   */
  async _refreshUI() { // eslint-disable-line require-await
    // Send batch update notifications to update the UI.
    let observers = PlacesUtils.history.getObservers();
    for (let observer of observers) {
      observer.onBeginUpdateBatch();
      observer.onEndUpdateBatch();
    }
    return [];
  },

  /**
   * Tries to execute a REINDEX on the database.
   *
   * @return {Promise} resolved when reindex is complete.
   * @resolves to an array of logs for this task.
   * @rejects if we're unable to reindex the database.
   */
  async reindex() {
    try {
      let logs = [];
      await PlacesUtils.withConnectionWrapper(
        "PlacesDBUtils: Reindex the database",
        async (db) => {
          let query = "REINDEX";
          await db.execute(query);
          logs.push("The database has been re indexed");
        });
      return logs;
    } catch (ex) {
      throw new Error("Unable to reindex the database.");
    }
  },

  /**
   * Checks integrity but does not try to fix the database through a reindex.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise} resolves if database is sane.
   * @resolves to an array of logs for this task.
   * @rejects if we're unable to fix corruption or unable to check status.
   */
  async _checkIntegritySkipReindex() { // eslint-disable-line require-await
    return this.checkIntegrity(true);
  },

  /**
   * Checks integrity and tries to fix the database through a reindex.
   *
   * @param [optional] skipReindex
   *        Whether to try to reindex database or not.
   *
   * @return {Promise} resolves if database is sane or is made sane.
   * @resolves to an array of logs for this task.
   * @rejects if we're unable to fix corruption or unable to check status.
   */
  async checkIntegrity(skipReindex) {
    let logs = [];

    try {
      // Run a integrity check, but stop at the first error.
      await PlacesUtils.withConnectionWrapper("PlacesDBUtils: check the integrity", async (db) => {
        let row;
        await db.execute(
          "PRAGMA integrity_check",
          null,
          r => {
            row = r;
            throw StopIteration;
          });
        if (row.getResultByIndex(0) === "ok") {
          logs.push("The database is sane");
        } else {
          // We stopped due to an integrity corruption, try to fix if possible.
          logs.push("The database is corrupt");
          if (skipReindex) {
            Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
            PlacesDBUtils.clearPendingTasks();
            throw new Error("Unable to fix corruption, database will be replaced on next startup");
          } else {
            // Try to reindex, this often fixes simple indices corruption.
            let reindexLogs = await PlacesDBUtils.reindex();
            let checkLogs = await PlacesDBUtils._checkIntegritySkipReindex();
            logs = logs.concat(reindexLogs).concat(checkLogs);
          }
        }
      });
    } catch (ex) {
      if (ex.message.indexOf("Unable to fix corruption") !== 0) {
        // There was some other error, so throw.
        PlacesDBUtils.clearPendingTasks();
        throw new Error("Unable to check database integrity");
      }
    }
    return logs;
  },

  /**
   * Checks data coherence and tries to fix most common errors.
   *
   * @return {Promise} resolves when coherence is checked.
   * @resolves to an array of logs for this task.
   * @rejects if database is not coherent.
   */
  async checkCoherence() {
    let logs = [];

    let stmts = await PlacesDBUtils._getBoundCoherenceStatements();
    let coherenceCheck = true;
    await PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: coherence check:",
      db => db.executeTransaction(async () => {
        for (let {query, params} of stmts) {
          params = params ? params : null;
          try {
            await db.execute(query, params);
          } catch (ex) {
            Cu.reportError(ex);
            coherenceCheck = false;
          }
        }
      })
    );

    if (coherenceCheck) {
      logs.push("The database is coherent");
    } else {
      PlacesDBUtils.clearPendingTasks();
      throw new Error("Unable to check database coherence");
    }
    return logs;
  },

  async _getBoundCoherenceStatements() {
    let cleanupStatements = [];

    // MOZ_ANNO_ATTRIBUTES
    // A.1 remove obsolete annotations from moz_annos.
    // The 'weave0' idiom exploits character ordering (0 follows /) to
    // efficiently select all annos with a 'weave/' prefix.
    let deleteObsoleteAnnos = {
      query:
      `DELETE FROM moz_annos
       WHERE type = 4
          OR anno_attribute_id IN (
         SELECT id FROM moz_anno_attributes
         WHERE name = 'downloads/destinationFileName' OR
               name BETWEEN 'weave/' AND 'weave0'
       )`
    };
    cleanupStatements.push(deleteObsoleteAnnos);

    // A.2 remove obsolete annotations from moz_items_annos.
    let deleteObsoleteItemsAnnos = {
      query:
      `DELETE FROM moz_items_annos
       WHERE type = 4
          OR anno_attribute_id IN (
         SELECT id FROM moz_anno_attributes
         WHERE name = 'sync/children'
            OR name = 'placesInternal/GUID'
            OR name BETWEEN 'weave/' AND 'weave0'
       )`
    };
    cleanupStatements.push(deleteObsoleteItemsAnnos);

    // A.3 remove unused attributes.
    let deleteUnusedAnnoAttributes = {
      query:
      `DELETE FROM moz_anno_attributes WHERE id IN (
         SELECT id FROM moz_anno_attributes n
         WHERE NOT EXISTS
             (SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1)
           AND NOT EXISTS
             (SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteUnusedAnnoAttributes);

    // MOZ_ANNOS
    // B.1 remove annos with an invalid attribute
    let deleteInvalidAttributeAnnos = {
      query:
      `DELETE FROM moz_annos WHERE id IN (
         SELECT id FROM moz_annos a
         WHERE NOT EXISTS
           (SELECT id FROM moz_anno_attributes
             WHERE id = a.anno_attribute_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteInvalidAttributeAnnos);

    // B.2 remove orphan annos
    let deleteOrphanAnnos = {
      query:
      `DELETE FROM moz_annos WHERE id IN (
         SELECT id FROM moz_annos a
         WHERE NOT EXISTS
           (SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteOrphanAnnos);

    // Bookmarks roots
    // C.1 fix missing Places root
    //     Bug 477739 shows a case where the root could be wrongly removed
    //     due to an endianness issue.  We try to fix broken roots here.
    let selectPlacesRoot = {
      query: "SELECT id FROM moz_bookmarks WHERE id = :places_root",
      params: {}
    };
    selectPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
    let db = await PlacesUtils.promiseDBConnection();
    let rows = await db.execute(selectPlacesRoot.query, selectPlacesRoot.params);
    if (rows.length === 0) {
      // We are missing the root, try to recreate it.
      let createPlacesRoot = {
        query:
        `INSERT INTO moz_bookmarks (id, type, fk, parent, position, title, guid)
           VALUES (:places_root, 2, NULL, 0, 0, :title, :guid)`,
        params: {}
      };
      createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
      createPlacesRoot.params["title"] = "";
      createPlacesRoot.params["guid"] = PlacesUtils.bookmarks.rootGuid;
      cleanupStatements.push(createPlacesRoot);

      // Now ensure that other roots are children of Places root.
      let fixPlacesRootChildren = {
        query:
        `UPDATE moz_bookmarks SET parent = :places_root WHERE guid IN
             ( :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid )`,
        params: {}
      };
      fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId;
      fixPlacesRootChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
      fixPlacesRootChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
      fixPlacesRootChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
      fixPlacesRootChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
      cleanupStatements.push(fixPlacesRootChildren);
    }
    // C.2 fix roots titles
    //     some alpha version has wrong roots title, and this also fixes them if
    //     locale has changed.
    let updateRootTitleSql = `UPDATE moz_bookmarks SET title = :title
                              WHERE id = :root_id AND title <> :title`;
    // root
    let fixPlacesRootTitle = {
      query: updateRootTitleSql,
      params: {}
    };
    fixPlacesRootTitle.params["root_id"] = PlacesUtils.placesRootId;
    fixPlacesRootTitle.params["title"] = "";
    cleanupStatements.push(fixPlacesRootTitle);
    // bookmarks menu
    let fixBookmarksMenuTitle = {
      query: updateRootTitleSql,
      params: {}
    };
    fixBookmarksMenuTitle.params["root_id"] = PlacesUtils.bookmarksMenuFolderId;
    fixBookmarksMenuTitle.params["title"] =
      PlacesUtils.getString("BookmarksMenuFolderTitle");
    cleanupStatements.push(fixBookmarksMenuTitle);
    // bookmarks toolbar
    let fixBookmarksToolbarTitle = {
      query: updateRootTitleSql,
      params: {}
    };
    fixBookmarksToolbarTitle.params["root_id"] = PlacesUtils.toolbarFolderId;
    fixBookmarksToolbarTitle.params["title"] =
      PlacesUtils.getString("BookmarksToolbarFolderTitle");
    cleanupStatements.push(fixBookmarksToolbarTitle);
    // unsorted bookmarks
    let fixUnsortedBookmarksTitle = {
      query: updateRootTitleSql,
      params: {}
    };
    fixUnsortedBookmarksTitle.params["root_id"] = PlacesUtils.unfiledBookmarksFolderId;
    fixUnsortedBookmarksTitle.params["title"] =
      PlacesUtils.getString("OtherBookmarksFolderTitle");
    cleanupStatements.push(fixUnsortedBookmarksTitle);
    // tags
    let fixTagsRootTitle = {
      query: updateRootTitleSql,
      params: {}
    };
    fixTagsRootTitle.params["root_id"] = PlacesUtils.tagsFolderId;
    fixTagsRootTitle.params["title"] =
      PlacesUtils.getString("TagsFolderTitle");
    cleanupStatements.push(fixTagsRootTitle);

    // MOZ_BOOKMARKS
    // D.1 remove items without a valid place
    // if fk IS NULL we fix them in D.7
    let deleteNoPlaceItems = {
      query:
      `DELETE FROM moz_bookmarks WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT b.id FROM moz_bookmarks b
         WHERE fk NOT NULL AND b.type = :bookmark_type
           AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1)
       )`,
      params: {}
    };
    deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
    deleteNoPlaceItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    deleteNoPlaceItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    deleteNoPlaceItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    deleteNoPlaceItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    deleteNoPlaceItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(deleteNoPlaceItems);

    // D.2 remove items that are not uri bookmarks from tag containers
    let deleteBogusTagChildren = {
      query:
      `DELETE FROM moz_bookmarks WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT b.id FROM moz_bookmarks b
         WHERE b.parent IN
           (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
           AND b.type <> :bookmark_type
       )`,
      params: {}
    };
    deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId;
    deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
    deleteBogusTagChildren.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    deleteBogusTagChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    deleteBogusTagChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    deleteBogusTagChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    deleteBogusTagChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(deleteBogusTagChildren);

    // D.3 remove empty tags
    let deleteEmptyTags = {
      query:
      `DELETE FROM moz_bookmarks WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT b.id FROM moz_bookmarks b
         WHERE b.id IN
           (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
           AND NOT EXISTS
             (SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1)
       )`,
      params: {}
    };
    deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
    deleteEmptyTags.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    deleteEmptyTags.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    deleteEmptyTags.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    deleteEmptyTags.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    deleteEmptyTags.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(deleteEmptyTags);

    // D.4 move orphan items to unsorted folder
    let fixOrphanItems = {
      query:
      `UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT b.id FROM moz_bookmarks b
         WHERE NOT EXISTS
           (SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1)
       )`,
      params: {}
    };
    fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
    fixOrphanItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    fixOrphanItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    fixOrphanItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    fixOrphanItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    fixOrphanItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(fixOrphanItems);

    // D.6 fix wrong item types
    //     Folders and separators should not have an fk.
    //     If they have a valid fk convert them to bookmarks. Later in D.9 we
    //     will move eventual children to unsorted bookmarks.
    let fixBookmarksAsFolders = {
      query:
      `UPDATE moz_bookmarks SET type = :bookmark_type WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT id FROM moz_bookmarks b
         WHERE type IN (:folder_type, :separator_type)
           AND fk NOTNULL
       )`,
      params: {}
    };
    fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
    fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
    fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
    fixBookmarksAsFolders.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    fixBookmarksAsFolders.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    fixBookmarksAsFolders.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    fixBookmarksAsFolders.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    fixBookmarksAsFolders.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(fixBookmarksAsFolders);

    // D.7 fix wrong item types
    //     Bookmarks should have an fk, if they don't have any, convert them to
    //     folders.
    let fixFoldersAsBookmarks = {
      query:
      `UPDATE moz_bookmarks SET type = :folder_type WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT id FROM moz_bookmarks b
         WHERE type = :bookmark_type
           AND fk IS NULL
       )`,
      params: {}
    };
    fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
    fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
    fixFoldersAsBookmarks.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    fixFoldersAsBookmarks.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    fixFoldersAsBookmarks.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    fixFoldersAsBookmarks.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    fixFoldersAsBookmarks.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(fixFoldersAsBookmarks);

    // D.9 fix wrong parents
    //     Items cannot have separators or other bookmarks
    //     as parent, if they have bad parent move them to unsorted bookmarks.
    let fixInvalidParents = {
      query:
      `UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
       ) AND id IN (
         SELECT id FROM moz_bookmarks b
         WHERE EXISTS
           (SELECT id FROM moz_bookmarks WHERE id = b.parent
             AND type IN (:bookmark_type, :separator_type)
             LIMIT 1)
       )`,
      params: {}
    };
    fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
    fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
    fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
    fixInvalidParents.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
    fixInvalidParents.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
    fixInvalidParents.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
    fixInvalidParents.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
    fixInvalidParents.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
    cleanupStatements.push(fixInvalidParents);

    // D.10 recalculate positions
    //      This requires multiple related statements.
    //      We can detect a folder with bad position values comparing the sum of
    //      all distinct position values (+1 since position is 0-based) with the
    //      triangular numbers obtained by the number of children (n).
    //      SUM(DISTINCT position + 1) == (n * (n + 1) / 2).
    cleanupStatements.push({
      query:
      `CREATE TEMP TABLE IF NOT EXISTS moz_bm_reindex_temp (
         id INTEGER PRIMARY_KEY
       , parent INTEGER
       , position INTEGER
       )`
    });
    cleanupStatements.push({
      query:
      `INSERT INTO moz_bm_reindex_temp
       SELECT id, parent, 0
       FROM moz_bookmarks b
       WHERE parent IN (
         SELECT parent
         FROM moz_bookmarks
         GROUP BY parent
         HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0
       )
       ORDER BY parent ASC, position ASC, ROWID ASC`
    });
    cleanupStatements.push({
      query:
      `CREATE INDEX IF NOT EXISTS moz_bm_reindex_temp_index
       ON moz_bm_reindex_temp(parent)`
    });
    cleanupStatements.push({
      query:
      `UPDATE moz_bm_reindex_temp SET position = (
         ROWID - (SELECT MIN(t.ROWID) FROM moz_bm_reindex_temp t
                  WHERE t.parent = moz_bm_reindex_temp.parent)
       )`
    });
    cleanupStatements.push({
      query:
      `CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_reindex_temp_trigger
       BEFORE DELETE ON moz_bm_reindex_temp
       FOR EACH ROW
       BEGIN
         UPDATE moz_bookmarks SET position = OLD.position WHERE id = OLD.id;
       END`
    });
    cleanupStatements.push({
      query: "DELETE FROM moz_bm_reindex_temp "
    });
    cleanupStatements.push({
      query: "DROP INDEX moz_bm_reindex_temp_index "
    });
    cleanupStatements.push({
      query: "DROP TRIGGER moz_bm_reindex_temp_trigger "
    });
    cleanupStatements.push({
      query: "DROP TABLE moz_bm_reindex_temp "
    });

    // D.12 Fix empty-named tags.
    //      Tags were allowed to have empty names due to a UI bug.  Fix them
    //      replacing their title with "(notitle)".
    let fixEmptyNamedTags = {
      query:
      `UPDATE moz_bookmarks SET title = :empty_title
       WHERE length(title) = 0 AND type = :folder_type
         AND parent = :tags_folder`,
      params: {}
    };
    fixEmptyNamedTags.params["empty_title"] = "(notitle)";
    fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
    fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
    cleanupStatements.push(fixEmptyNamedTags);

    // MOZ_ICONS
    // E.1 remove orphan icon entries.
    let deleteOrphanIconPages = {
      query:
      `DELETE FROM moz_pages_w_icons WHERE page_url_hash NOT IN (
         SELECT url_hash FROM moz_places
       )`
    };
    cleanupStatements.push(deleteOrphanIconPages);

    let deleteOrphanIcons = {
      query:
      `DELETE FROM moz_icons WHERE root = 0 AND id NOT IN (
         SELECT icon_id FROM moz_icons_to_pages
       )`
    };
    cleanupStatements.push(deleteOrphanIcons);

    // MOZ_HISTORYVISITS
    // F.1 remove orphan visits
    let deleteOrphanVisits = {
      query:
      `DELETE FROM moz_historyvisits WHERE id IN (
         SELECT id FROM moz_historyvisits v
         WHERE NOT EXISTS
           (SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteOrphanVisits);

    // MOZ_INPUTHISTORY
    // G.1 remove orphan input history
    let deleteOrphanInputHistory = {
      query:
      `DELETE FROM moz_inputhistory WHERE place_id IN (
         SELECT place_id FROM moz_inputhistory i
         WHERE NOT EXISTS
           (SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteOrphanInputHistory);

    // MOZ_ITEMS_ANNOS
    // H.1 remove item annos with an invalid attribute
    let deleteInvalidAttributeItemsAnnos = {
      query:
      `DELETE FROM moz_items_annos WHERE id IN (
         SELECT id FROM moz_items_annos t
         WHERE NOT EXISTS
           (SELECT id FROM moz_anno_attributes
             WHERE id = t.anno_attribute_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteInvalidAttributeItemsAnnos);

    // H.2 remove orphan item annos
    let deleteOrphanItemsAnnos = {
      query:
      `DELETE FROM moz_items_annos WHERE id IN (
         SELECT id FROM moz_items_annos t
         WHERE NOT EXISTS
           (SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1)
       )`
    };
    cleanupStatements.push(deleteOrphanItemsAnnos);

    // MOZ_KEYWORDS
    // I.1 remove unused keywords
    let deleteUnusedKeywords = {
      query:
      `DELETE FROM moz_keywords WHERE id IN (
         SELECT id FROM moz_keywords k
         WHERE NOT EXISTS
           (SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
       )`
    };
    cleanupStatements.push(deleteUnusedKeywords);

    // MOZ_PLACES
    // L.2 recalculate visit_count and last_visit_date
    let fixVisitStats = {
      query:
      `UPDATE moz_places
       SET visit_count = (SELECT count(*) FROM moz_historyvisits
                          WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8,9)),
           last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits
                              WHERE place_id = moz_places.id)
       WHERE id IN (
         SELECT h.id FROM moz_places h
         WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v
                               WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8,9))
            OR last_visit_date <> (SELECT MAX(visit_date) FROM moz_historyvisits v
                                   WHERE v.place_id = h.id)
       )`
    };
    cleanupStatements.push(fixVisitStats);

    // L.3 recalculate hidden for redirects.
    let fixRedirectsHidden = {
      query:
      `UPDATE moz_places
       SET hidden = 1
       WHERE id IN (
         SELECT h.id FROM moz_places h
         JOIN moz_historyvisits src ON src.place_id = h.id
         JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6)
         LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL
         GROUP BY src.place_id HAVING count(*) = visit_count
       )`
    };
    cleanupStatements.push(fixRedirectsHidden);

    // L.4 recalculate foreign_count.
    let fixForeignCount = {
      query:
      `UPDATE moz_places SET foreign_count =
         (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id ) +
         (SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id )`
    };
    cleanupStatements.push(fixForeignCount);

    // L.5 recalculate missing hashes.
    let fixMissingHashes = {
      query: `UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0`
    };
    cleanupStatements.push(fixMissingHashes);

    // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!

    return cleanupStatements;
  },

  /**
   * Tries to vacuum the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise} resolves when database is vacuumed.
   * @resolves to an array of logs for this task.
   * @rejects if we are unable to vacuum database.
   */
  async vacuum() { // eslint-disable-line require-await
    let logs = [];
    let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
    DBFile.append("places.sqlite");
    logs.push("Initial database size is " +
                parseInt(DBFile.fileSize / 1024) + " KiB");
    return PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: vacuum",
      async (db) => {
        await db.execute("VACUUM");
      }).then(() => {
        logs.push("The database has been vacuumed");
        let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
        vacuumedDBFile.append("places.sqlite");
        logs.push("Final database size is " +
                   parseInt(vacuumedDBFile.fileSize / 1024) + " KiB");
        return logs;
      }).catch(() => {
        PlacesDBUtils.clearPendingTasks();
        throw new Error("Unable to vacuum database");
      });
  },

  /**
   * Forces a full expiration on the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise} resolves when the database in cleaned up.
   * @resolves to an array of logs for this task.
   */
  async expire() { // eslint-disable-line require-await
    let logs = [];

    let expiration = Cc["@mozilla.org/places/expiration;1"]
                       .getService(Ci.nsIObserver);

    let returnPromise = new Promise(res => {
      let observer = (subject, topic, data) => {
        Services.obs.removeObserver(observer, topic);
        logs.push("Database cleaned up");
        res(logs);
      };
      Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
    });

    // Force an orphans expiration step.
    expiration.observe(null, "places-debug-start-expiration", 0);
    return returnPromise;
  },

  /**
   * Collects statistical data on the database.
   *
   * @return {Promise} resolves when statistics are collected.
   * @resolves to an array of logs for this task.
   * @rejects if we are unable to collect stats for some reason.
   */
  async stats() {
    let logs = [];
    let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
    DBFile.append("places.sqlite");
    logs.push("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB");

    // Execute each step async.
    let pragmas = [ "user_version",
                    "page_size",
                    "cache_size",
                    "journal_mode",
                    "synchronous"
                  ].map(p => `pragma_${p}`);
    let pragmaQuery = `SELECT * FROM ${ pragmas.join(", ") }`;
    await PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: pragma for stats",
      async (db) => {
        let row = (await db.execute(pragmaQuery))[0];
        for (let i = 0; i != pragmas.length; i++) {
          logs.push(`${ pragmas[i] } is ${ row.getResultByIndex(i) }`);
        }
      }).catch(() => {
        logs.push("Could not set pragma for stat collection");
      });

    // Get maximum number of unique URIs.
    try {
      let limitURIs = Services.prefs.getIntPref(
        "places.history.expiration.transient_current_max_pages");
      logs.push("History can store a maximum of " + limitURIs + " unique pages");
    } catch (ex) {}

    let query = "SELECT name FROM sqlite_master WHERE type = :type";
    let params = {};
    let _getTableCount = async (tableName) => {
      let db = await PlacesUtils.promiseDBConnection();
      let rows = await db.execute(`SELECT count(*) FROM ${tableName}`);
      logs.push(`Table ${tableName} has ${rows[0].getResultByIndex(0)} records`);
    };

    try {
      params.type = "table";
      let db = await PlacesUtils.promiseDBConnection();
      await db.execute(query, params,
                       r => _getTableCount(r.getResultByIndex(0)));

      params.type = "index";
      await db.execute(query, params, r => {
        logs.push(`Index ${r.getResultByIndex(0)}`);
      });

      params.type = "trigger";
      await db.execute(query, params, r => {
        logs.push(`Trigger ${r.getResultByIndex(0)}`);
      });

    } catch (ex) {
      throw new Error("Unable to collect stats.");
    }

    return logs;
  },

  /**
   * Collects telemetry data and reports it to Telemetry.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   */
  async telemetry() { // eslint-disable-line require-await
    // This will be populated with one integer property for each probe result,
    // using the histogram name as key.
    let probeValues = {};

    // The following array contains an ordered list of entries that are
    // processed to collect telemetry data.  Each entry has these properties:
    //
    //  histogram: Name of the telemetry histogram to update.
    //  query:     This is optional.  If present, contains a database command
    //             that will be executed asynchronously, and whose result will
    //             be added to the telemetry histogram.
    //  callback:  This is optional.  If present, contains a function that must
    //             return the value that will be added to the telemetry
    //             histogram. If a query is also present, its result is passed
    //             as the first argument of the function.  If the function
    //             raises an exception, no data is added to the histogram.
    //
    // Since all queries are executed in order by the database backend, the
    // callbacks can also use the result of previous queries stored in the
    // probeValues object.
    let probes = [
      { histogram: "PLACES_PAGES_COUNT",
        query:     "SELECT count(*) FROM moz_places" },

      { histogram: "PLACES_BOOKMARKS_COUNT",
        query:     `SELECT count(*) FROM moz_bookmarks b
                    JOIN moz_bookmarks t ON t.id = b.parent
                    AND t.parent <> :tags_folder
                    WHERE b.type = :type_bookmark` },

      { histogram: "PLACES_TAGS_COUNT",
        query:     `SELECT count(*) FROM moz_bookmarks
                    WHERE parent = :tags_folder` },

      { histogram: "PLACES_KEYWORDS_COUNT",
        query:     "SELECT count(*) FROM moz_keywords" },

      { histogram: "PLACES_SORTED_BOOKMARKS_PERC",
        query:     `SELECT IFNULL(ROUND((
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent <> :tags_folder AND t.parent > :places_root
                      WHERE b.type  = :type_bookmark
                      ) * 100 / (
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent <> :tags_folder
                      WHERE b.type = :type_bookmark
                    )), 0)` },

      { histogram: "PLACES_TAGGED_BOOKMARKS_PERC",
        query:     `SELECT IFNULL(ROUND((
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent = :tags_folder
                      ) * 100 / (
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent <> :tags_folder
                      WHERE b.type = :type_bookmark
                    )), 0)` },

      { histogram: "PLACES_DATABASE_FILESIZE_MB",
        callback() {
          let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
          DBFile.append("places.sqlite");
          return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE);
        }
      },

      { histogram: "PLACES_DATABASE_PAGESIZE_B",
        query:     "PRAGMA page_size /* PlacesDBUtils.jsm PAGESIZE_B */" },

      { histogram: "PLACES_DATABASE_SIZE_PER_PAGE_B",
        query:     "PRAGMA page_count",
        callback(aDbPageCount) {
          // Note that the database file size would not be meaningful for this
          // calculation, because the file grows in fixed-size chunks.
          let dbPageSize = probeValues.PLACES_DATABASE_PAGESIZE_B;
          let placesPageCount = probeValues.PLACES_PAGES_COUNT;
          return Math.round((dbPageSize * aDbPageCount) / placesPageCount);
        }
      },

      { histogram: "PLACES_ANNOS_BOOKMARKS_COUNT",
        query:     "SELECT count(*) FROM moz_items_annos" },

      { histogram: "PLACES_ANNOS_PAGES_COUNT",
        query:     "SELECT count(*) FROM moz_annos" },

      { histogram: "PLACES_MAINTENANCE_DAYSFROMLAST",
        callback() {
          try {
            let lastMaintenance = Services.prefs.getIntPref("places.database.lastMaintenance");
            let nowSeconds = parseInt(Date.now() / 1000);
            return parseInt((nowSeconds - lastMaintenance) / 86400);
          } catch (ex) {
            return 60;
          }
        }
      },
    ];

    let params = {
      tags_folder: PlacesUtils.tagsFolderId,
      type_folder: PlacesUtils.bookmarks.TYPE_FOLDER,
      type_bookmark: PlacesUtils.bookmarks.TYPE_BOOKMARK,
      places_root: PlacesUtils.placesRootId
    };

    for (let i = 0; i < probes.length; i++) {
      let probe = probes[i];

      let promiseDone = new Promise((resolve, reject) => {
        if (!("query" in probe)) {
          resolve([probe]);
          return;
        }

        let filteredParams = {};
        for (let p in params) {
          if (probe.query.includes(`:${p}`)) {
            filteredParams[p] = params[p];
          }
        }
        PlacesUtils.promiseDBConnection()
          .then(db => db.execute(probe.query, filteredParams))
          .then(rows => resolve([probe, rows[0].getResultByIndex(0)]))
          .catch(ex => reject(new Error("Unable to get telemetry from database.")));
      });
      // Report the result of the probe through Telemetry.
      // The resulting promise cannot reject.
      promiseDone.then(([aProbe, aValue]) => {
        let value = aValue;
        if ("callback" in aProbe) {
          value = aProbe.callback(value);
        }
        probeValues[aProbe.histogram] = value;
        Services.telemetry.getHistogramById(aProbe.histogram).add(value);
      }).catch(Cu.reportError);
    }
  },

  /**
   * Runs a list of tasks, returning a Map when done.
   *
   * @param tasks
   *        Array of tasks to be executed, in form of pointers to methods in
   *        this module.
   * @return {Promise}
   *        A promise that resolves with a Map[taskName(String) -> Object].
   *        The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task
   */
  async runTasks(tasks) {
    PlacesDBUtils._clearTaskQueue = false;
    let tasksMap = new Map();
    for (let task of tasks) {
      if (PlacesDBUtils._isShuttingDown) {
        tasksMap.set(
          task.name,
          { succeeded: false, logs: ["Shutting down, will now schedule the task."] });
        continue;
      }

      if (PlacesDBUtils._clearTaskQueue) {
        tasksMap.set(
          task.name,
          { succeeded: false, logs: ["The task queue was cleared by an error in another task."] });
        continue;
      }

      let result =
          await task().then(logs => { return { succeeded: true, logs }; })
                      .catch(err => { return { succeeded: false, logs: [err.message] }; });
      tasksMap.set(task.name, result);
    }
    return tasksMap;
  }
};
PK
!<0modules/PlacesRemoteTabsAutocompleteProvider.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Provides functions to handle remote tabs (ie, tabs known by Sync) in
 * the awesomebar.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["PlacesRemoteTabsAutocompleteProvider"];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
  return Cc["@mozilla.org/weave/service;1"]
           .getService(Ci.nsISupports)
           .wrappedJSObject;
});

XPCOMUtils.defineLazyGetter(this, "Weave", () => {
  try {
    let {Weave} = Cu.import("resource://services-sync/main.js", {});
    return Weave;
  } catch (ex) {
    // The app didn't build Sync.
  }
  return null;
});

// from MDN...
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// Build the in-memory structure we use.
function buildItems() {
  let clients = new Map(); // keyed by client guid, value is client
  let tabs = new Map(); // keyed by string URL, value is {clientId, tab}

  // If Sync isn't initialized (either due to lag at startup or due to no user
  // being signed in), don't reach in to Weave.Service as that may initialize
  // Sync unnecessarily - we'll get an observer notification later when it
  // becomes ready and has synced a list of tabs.
  if (weaveXPCService.ready) {
    let engine = Weave.Service.engineManager.get("tabs");

    for (let [guid, client] of Object.entries(engine.getAllClients())) {
      clients.set(guid, client);
      for (let tab of client.tabs) {
        let url = tab.urlHistory[0];
        tabs.set(url, { clientId: guid, tab });
      }
    }
  }
  return { clients, tabs };
}

// Manage the cache of the items we use.
// The cache itself.
let _items = null;

// Ensure the cache is good.
function ensureItems() {
  if (!_items) {
    _items = buildItems();
  }
  return _items;
}

// A preference used to disable the showing of icons in remote tab records.
const PREF_SHOW_REMOTE_ICONS = "services.sync.syncedTabs.showRemoteIcons";
let showRemoteIcons;

// An observer to invalidate _items and watch for changed prefs.
function observe(subject, topic, data) {
  switch (topic) {
    case "weave:engine:sync:finish":
      if (data == "tabs") {
        // The tabs engine just finished syncing, so may have a different list
        // of tabs then we previously cached.
        _items = null;
      }
      break;

    case "weave:service:start-over":
      // Sync is being reset due to the user disconnecting - we must invalidate
      // the cache so we don't supply tabs from a different user.
      _items = null;
      break;

    case "nsPref:changed":
      if (data == PREF_SHOW_REMOTE_ICONS) {
        showRemoteIcons = Services.prefs.getBoolPref(PREF_SHOW_REMOTE_ICONS, true);
      }
      break;

    default:
      break;
  }
}

Services.obs.addObserver(observe, "weave:engine:sync:finish");
Services.obs.addObserver(observe, "weave:service:start-over");

// Observe the pref for showing remote icons and prime our bool that reflects its value.
Services.prefs.addObserver(PREF_SHOW_REMOTE_ICONS, observe);
observe(null, "nsPref:changed", PREF_SHOW_REMOTE_ICONS);

// This public object is a static singleton.
this.PlacesRemoteTabsAutocompleteProvider = {
  // a promise that resolves with an array of matching remote tabs.
  getMatches(searchString) {
    // If Sync isn't configured we bail early.
    if (Weave === null ||
        !Services.prefs.prefHasUserValue("services.sync.username")) {
      return Promise.resolve([]);
    }

    let re = new RegExp(escapeRegExp(searchString), "i");
    let matches = [];
    let { tabs, clients } = ensureItems();
    for (let [url, { clientId, tab }] of tabs) {
      let title = tab.title;
      if (url.match(re) || (title && title.match(re))) {
        // lookup the client record.
        let client = clients.get(clientId);
        let icon = showRemoteIcons ? tab.icon : null;
        // create the record we return for auto-complete.
        let record = {
          url, title, icon,
          deviceClass: Weave.Service.clientsEngine.isMobile(clientId) ? "mobile" : "desktop",
          deviceName: client.clientName,
        };
        matches.push(record);
      }
    }
    return Promise.resolve(matches);
  },
}
PK
!<BF'F',modules/PlacesSearchAutocompleteProvider.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Provides functions to handle search engine URLs in the browser history.
 */

"use strict";

this.EXPORTED_SYMBOLS = [ "PlacesSearchAutocompleteProvider" ];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
  "resource://gre/modules/SearchSuggestionController.jsm");

const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";

const SearchAutocompleteProviderInternal = {
  /**
   * Array of objects in the format returned by findMatchByToken.
   */
  priorityMatches: null,

  /**
   * Array of objects in the format returned by findMatchByAlias.
   */
  aliasMatches: null,

  /**
   * Object for the default search match.
   **/
  defaultMatch: null,

  initialize() {
    return new Promise((resolve, reject) => {
      Services.search.init(status => {
        if (!Components.isSuccessCode(status)) {
          reject(new Error("Unable to initialize search service."));
        }

        try {
          // The initial loading of the search engines must succeed.
          this._refresh();

          Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);

          this.initialized = true;
          resolve();
        } catch (ex) {
          reject(ex);
        }
      });
    });
  },

  initialized: false,

  observe(subject, topic, data) {
    switch (data) {
      case "engine-added":
      case "engine-changed":
      case "engine-removed":
      case "engine-current":
        this._refresh();
    }
  },

  _refresh() {
    this.priorityMatches = [];
    this.aliasMatches = [];
    this.defaultMatch = null;

    let currentEngine = Services.search.currentEngine;
    // This can be null in XCPShell.
    if (currentEngine) {
      this.defaultMatch = {
        engineName: currentEngine.name,
        iconUrl: currentEngine.iconURI ? currentEngine.iconURI.spec : null,
      }
    }

    // The search engines will always be processed in the order returned by the
    // search service, which can be defined by the user.
    Services.search.getVisibleEngines().forEach(e => this._addEngine(e));
  },

  _addEngine(engine) {
    if (engine.alias) {
      this.aliasMatches.push({
        alias: engine.alias,
        engineName: engine.name,
        iconUrl: engine.iconURI ? engine.iconURI.spec : null,
      });
    }

    let domain = engine.getResultDomain();
    if (domain) {
      this.priorityMatches.push({
        token: domain,
        // The searchForm property returns a simple URL for the search engine, but
        // we may need an URL which includes an affiliate code (bug 990799).
        url: engine.searchForm,
        engineName: engine.name,
        iconUrl: engine.iconURI ? engine.iconURI.spec : null,
      });
    }
  },

  getSuggestionController(searchToken, inPrivateContext, maxLocalResults,
                          maxRemoteResults, userContextId) {
    let engine = Services.search.currentEngine;
    if (!engine) {
      return null;
    }
    return new SearchSuggestionControllerWrapper(engine, searchToken,
                                                 inPrivateContext,
                                                 maxLocalResults, maxRemoteResults,
                                                 userContextId);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),
}

function SearchSuggestionControllerWrapper(engine, searchToken,
                                           inPrivateContext,
                                           maxLocalResults, maxRemoteResults,
                                           userContextId) {
  this._controller = new SearchSuggestionController();
  this._controller.maxLocalResults = maxLocalResults;
  this._controller.maxRemoteResults = maxRemoteResults;
  let promise = this._controller.fetch(searchToken, inPrivateContext, engine, userContextId);
  this._suggestions = [];
  this._success = false;
  this._promise = promise.then(results => {
    this._success = true;
    this._suggestions = [];
    if (results) {
      this._suggestions = this._suggestions.concat(
        results.local.map(r => ({ suggestion: r, historical: true }))
      );
      this._suggestions = this._suggestions.concat(
        results.remote.map(r => ({ suggestion: r, historical: false }))
      );
    }
  }).catch(err => {
    // fetch() rejects its promise if there's a pending request.
  });
}

SearchSuggestionControllerWrapper.prototype = {

  /**
   * Resolved when all suggestions have been fetched.
   */
  get fetchCompletePromise() {
    return this._promise;
  },

  /**
   * Returns one suggestion, if any are available, otherwise returns null.
   * Note that may be multiple reasons why suggestions are not available:
   *  - all suggestions have already been consumed
   *  - the fetch failed
   *  - the fetch didn't complete yet (should have awaited the promise)
   *
   * @return An object {match, suggestion, historical}.
   */
  consume() {
    if (!this._suggestions.length)
      return null;
    let { suggestion, historical } = this._suggestions.shift();
    return { match: SearchAutocompleteProviderInternal.defaultMatch,
             suggestion,
             historical
           };
  },

  /**
   * Returns the number of fetched suggestions, or -1 if the fetching was
   * incomplete or failed.
   */
  get resultsCount() {
    return this._success ? this._suggestions.length : -1;
  },

  /**
   * Stops the fetch.
   */
  stop() {
    this._controller.stop();
  },
};

var gInitializationPromise = null;

this.PlacesSearchAutocompleteProvider = Object.freeze({
  /**
   * Starts initializing the component and returns a promise that is resolved or
   * rejected when initialization finished.  The same promise is returned if
   * this function is called multiple times.
   */
  ensureInitialized() {
    if (!gInitializationPromise) {
      gInitializationPromise = SearchAutocompleteProviderInternal.initialize();
    }
    return gInitializationPromise;
  },

  /**
   * Matches a given string to an item that should be included by URL search
   * components, like autocomplete in the address bar.
   *
   * @param searchToken
   *        String containing the first part of the matching domain name.
   *
   * @return An object with the following properties, or undefined if the token
   *         does not match any relevant URL:
   *         {
   *           token: The full string used to match the search term to the URL.
   *           url: The URL to navigate to if the match is selected.
   *           engineName: The display name of the search engine.
   *           iconUrl: Icon associated to the match, or null if not available.
   *         }
   */
  async findMatchByToken(searchToken) {
    await this.ensureInitialized();

    // Match at the beginning for now.  In the future, an "options" argument may
    // allow the matching behavior to be tuned.
    return SearchAutocompleteProviderInternal.priorityMatches
                                             .find(m => m.token.startsWith(searchToken));
  },

  /**
   * Matches a given search string to an item that should be included by
   * components wishing to search using search engine aliases, like
   * autocomple.
   *
   * @param searchToken
   *        Search string to match exactly a search engine alias.
   *
   * @return An object with the following properties, or undefined if the token
   *         does not match any relevant URL:
   *         {
   *           alias: The matched search engine's alias.
   *           engineName: The display name of the search engine.
   *           iconUrl: Icon associated to the match, or null if not available.
   *         }
   */
  async findMatchByAlias(searchToken) {
    await this.ensureInitialized();

    return SearchAutocompleteProviderInternal.aliasMatches
             .find(m => m.alias.toLocaleLowerCase() == searchToken.toLocaleLowerCase());
  },

  async getDefaultMatch() {
    await this.ensureInitialized();

    return SearchAutocompleteProviderInternal.defaultMatch;
  },

  /**
   * Synchronously determines if the provided URL represents results from a
   * search engine, and provides details about the match.
   *
   * @param url
   *        String containing the URL to parse.
   *
   * @return An object with the following properties, or null if the URL does
   *         not represent a search result:
   *         {
   *           engineName: The display name of the search engine.
   *           terms: The originally sought terms extracted from the URI.
   *         }
   *
   * @remarks The asynchronous ensureInitialized function must be called before
   *          this synchronous method can be used.
   *
   * @note This API function needs to be synchronous because it is called inside
   *       a row processing callback of Sqlite.jsm, in UnifiedComplete.js.
   */
  parseSubmissionURL(url) {
    if (!SearchAutocompleteProviderInternal.initialized) {
      throw new Error("The component has not been initialized.");
    }

    let parseUrlResult = Services.search.parseSubmissionURL(url);
    return parseUrlResult.engine && {
      engineName: parseUrlResult.engine.name,
      terms: parseUrlResult.terms,
    };
  },

  getSuggestionController(searchToken, inPrivateContext, maxLocalResults,
                          maxRemoteResults, userContextId) {
    if (!SearchAutocompleteProviderInternal.initialized) {
      throw new Error("The component has not been initialized.");
    }
    return SearchAutocompleteProviderInternal.getSuggestionController(
      searchToken, inPrivateContext, maxLocalResults, maxRemoteResults,
      userContextId);
  },
});
PK
!<JJmodules/PlacesSyncUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["PlacesSyncUtils"];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.importGlobalProperties(["URL", "URLSearchParams"]);

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

/**
 * This module exports functions for Sync to use when applying remote
 * records. The calls are similar to those in `Bookmarks.jsm` and
 * `nsINavBookmarksService`, with special handling for smart bookmarks,
 * tags, keywords, synced annotations, and missing parents.
 */
var PlacesSyncUtils = {};

const { SOURCE_SYNC } = Ci.nsINavBookmarksService;

const MICROSECONDS_PER_SECOND = 1000000;
const SQLITE_MAX_VARIABLE_NUMBER = 999;

const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
const ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE = "AllBookmarks";
const ORGANIZER_MOBILE_QUERY_ANNO_VALUE = "MobileBookmarks";
const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";

// These are defined as lazy getters to defer initializing the bookmarks
// service until it's needed.
XPCOMUtils.defineLazyGetter(this, "ROOT_SYNC_ID_TO_GUID", () => ({
  menu: PlacesUtils.bookmarks.menuGuid,
  places: PlacesUtils.bookmarks.rootGuid,
  tags: PlacesUtils.bookmarks.tagsGuid,
  toolbar: PlacesUtils.bookmarks.toolbarGuid,
  unfiled: PlacesUtils.bookmarks.unfiledGuid,
  mobile: PlacesUtils.bookmarks.mobileGuid,
}));

XPCOMUtils.defineLazyGetter(this, "ROOT_GUID_TO_SYNC_ID", () => ({
  [PlacesUtils.bookmarks.menuGuid]: "menu",
  [PlacesUtils.bookmarks.rootGuid]: "places",
  [PlacesUtils.bookmarks.tagsGuid]: "tags",
  [PlacesUtils.bookmarks.toolbarGuid]: "toolbar",
  [PlacesUtils.bookmarks.unfiledGuid]: "unfiled",
  [PlacesUtils.bookmarks.mobileGuid]: "mobile",
}));

XPCOMUtils.defineLazyGetter(this, "ROOTS", () =>
  Object.keys(ROOT_SYNC_ID_TO_GUID)
);

/**
 * Auxiliary generator function that yields an array in chunks
 *
 * @param array
 * @param chunkLength
 * @yields {Array} New Array with the next chunkLength elements of array. If the array has less than chunkLength elements, yields all of them
 */
function* chunkArray(array, chunkLength) {
  let startIndex = 0;
  while (startIndex < array.length) {
    yield array.slice(startIndex, startIndex += chunkLength);
  }
}

const HistorySyncUtils = PlacesSyncUtils.history = Object.freeze({
  /**
   * Fetches the frecency for the URL provided
   *
   * @param url
   * @returns {Number} The frecency of the given url
   */
  async fetchURLFrecency(url) {
    let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);

    let db = await PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(`
      SELECT frecency
      FROM moz_places
      WHERE url_hash = hash(:url) AND url = :url
      LIMIT 1`,
      { url: canonicalURL.href }
    );

    return rows.length ? rows[0].getResultByName("frecency") : -1;
  },

  /**
   * Filters syncable places from a collection of places guids.
   *
   * @param guids
   *
   * @returns {Array} new Array with the guids that aren't syncable
   */
  async determineNonSyncableGuids(guids) {
    // Filter out hidden pages and `TRANSITION_FRAMED_LINK` visits. These are
    // excluded when rendering the history menu, so we use the same constraints
    // for Sync. We also don't want to sync `TRANSITION_EMBED` visits, but those
    // aren't stored in the database.
    let db = await PlacesUtils.promiseDBConnection();
    let nonSyncableGuids = [];
    for (let chunk of chunkArray(guids, SQLITE_MAX_VARIABLE_NUMBER)) {
      let rows = await db.execute(`
        SELECT DISTINCT p.guid FROM moz_places p
        JOIN moz_historyvisits v ON p.id = v.place_id
        WHERE p.guid IN (${new Array(chunk.length).fill("?").join(",")}) AND
            (p.hidden = 1 OR v.visit_type IN (0,
              ${PlacesUtils.history.TRANSITION_FRAMED_LINK}))
      `, chunk);
      nonSyncableGuids = nonSyncableGuids.concat(rows.map(row => row.getResultByName("guid")));
    }
    return nonSyncableGuids;
  },

  /**
   * Change the guid of the given uri
   *
   * @param uri
   * @param guid
   */
  changeGuid(uri, guid) {
      let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(uri);
      let validatedGuid = PlacesUtils.BOOKMARK_VALIDATORS.guid(guid);
      return PlacesUtils.withConnectionWrapper("HistorySyncUtils: changeGuid",
        async function(db) {
          await db.executeCached(`
            UPDATE moz_places
            SET guid = :guid
            WHERE url_hash = hash(:page_url) AND url = :page_url`,
            {guid: validatedGuid, page_url: canonicalURL.href});
        });
  },

  /**
   * Fetch the last 20 visits (date and type of it) corresponding to a given url
   *
   * @param url
   * @returns {Array} Each element of the Array is an object with members: date and type
   */
  async fetchVisitsForURL(url) {
    let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
    let db = await PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(`
      SELECT visit_type type, visit_date date
      FROM moz_historyvisits
      JOIN moz_places h ON h.id = place_id
      WHERE url_hash = hash(:url) AND url = :url
      ORDER BY date DESC LIMIT 20`, { url: canonicalURL.href }
    );
    return rows.map(row => {
      let visitDate = row.getResultByName("date");
      let visitType = row.getResultByName("type");
      return { date: visitDate, type: visitType };
    });
  },

  /**
   * Fetches the guid of a uri
   *
   * @param uri
   * @returns {String} The guid of the given uri
   */
  async fetchGuidForURL(url) {
      let canonicalURL = PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
      let db = await PlacesUtils.promiseDBConnection();
      let rows = await db.executeCached(`
        SELECT guid
        FROM moz_places
        WHERE url_hash = hash(:page_url) AND url = :page_url`,
        { page_url: canonicalURL.href }
      );
      if (rows.length == 0) {
        return null;
      }
      return rows[0].getResultByName("guid");
  },

  /**
   * Fetch information about a guid (url, title and frecency)
   *
   * @param guid
   * @returns {Object} Object with three members: url, title and frecency of the given guid
   */
  async fetchURLInfoForGuid(guid) {
    let db = await PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(`
      SELECT url, title, frecency
      FROM moz_places
      WHERE guid = :guid`,
      { guid }
    );
    if (rows.length === 0) {
      return null;
    }
    return {
      url: rows[0].getResultByName("url"),
      title: rows[0].getResultByName("title"),
      frecency: rows[0].getResultByName("frecency"),
    };
  },

  /**
   * Get all URLs filtered by the limit and since members of the options object.
   *
   * @param options
   *        Options object with two members, since and limit. Both of them must be provided
   * @returns {Array} - Up to limit number of URLs starting from the date provided by since
   */
  async getAllURLs(options) {
    // Check that the limit property is finite number.
    if (!Number.isFinite(options.limit)) {
      throw new Error("The number provided in options.limit is not finite.");
    }
    // Check that the since property is of type Date.
    if (!options.since || Object.prototype.toString.call(options.since) != "[object Date]") {
      throw new Error("The property since of the options object must be of type Date.");
    }
    let db = await PlacesUtils.promiseDBConnection();
    let sinceInMicroseconds = PlacesUtils.toPRTime(options.since);
    let rows = await db.executeCached(`
      SELECT DISTINCT p.url
      FROM moz_places p
      JOIN moz_historyvisits v ON p.id = v.place_id
      WHERE p.last_visit_date > :cutoff_date AND
            p.hidden = 0 AND
            v.visit_type NOT IN (0,
              ${PlacesUtils.history.TRANSITION_FRAMED_LINK})
      ORDER BY frecency DESC
      LIMIT :max_results`,
      { cutoff_date: sinceInMicroseconds, max_results: options.limit }
    );
    return rows.map(row => row.getResultByName("url"));
  },
});

const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
  SMART_BOOKMARKS_ANNO: "Places/SmartBookmark",
  DESCRIPTION_ANNO: "bookmarkProperties/description",
  SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
  SYNC_PARENT_ANNO: "sync/parent",
  SYNC_MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",

  // Jan 23, 1993 in milliseconds since 1970. Corresponds roughly to the release
  // of the original NCSA Mosiac. We can safely assume that any dates before
  // this time are invalid.
  EARLIEST_BOOKMARK_TIMESTAMP: Date.UTC(1993, 0, 23),

  KINDS: {
    BOOKMARK: "bookmark",
    QUERY: "query",
    FOLDER: "folder",
    LIVEMARK: "livemark",
    SEPARATOR: "separator",
  },

  get ROOTS() {
    return ROOTS;
  },

  /**
   * Converts a Places GUID to a Sync ID. Sync IDs are identical to Places
   * GUIDs for all items except roots.
   */
  guidToSyncId(guid) {
    return ROOT_GUID_TO_SYNC_ID[guid] || guid;
  },

  /**
   * Converts a Sync record ID to a Places GUID.
   */
  syncIdToGuid(syncId) {
    return ROOT_SYNC_ID_TO_GUID[syncId] || syncId;
  },

  /**
   * Fetches the sync IDs for a folder's children, ordered by their position
   * within the folder.
   */
  fetchChildSyncIds(parentSyncId) {
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
    let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);

    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: fetchChildSyncIds", async function(db) {
        let childGuids = await fetchChildGuids(db, parentGuid);
        return childGuids.map(guid =>
          BookmarkSyncUtils.guidToSyncId(guid)
        );
      }
    );
  },

  /**
   * Returns an array of `{ syncId, syncable }` tuples for all items in
   * `requestedSyncIds`. If any requested ID is a folder, all its descendants
   * will be included. Ancestors of non-syncable items are not included; if
   * any are missing on the server, the requesting client will need to make
   * another repair request.
   *
   * Sync calls this method to respond to incoming bookmark repair requests
   * and upload items that are missing on the server.
   */
  fetchSyncIdsForRepair(requestedSyncIds) {
    let requestedGuids = requestedSyncIds.map(BookmarkSyncUtils.syncIdToGuid);
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: fetchSyncIdsForRepair", async function(db) {
        let rows = await db.executeCached(`
          WITH RECURSIVE
          syncedItems(id) AS (
            SELECT b.id FROM moz_bookmarks b
            WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                             'mobile______')
            UNION ALL
            SELECT b.id FROM moz_bookmarks b
            JOIN syncedItems s ON b.parent = s.id
          ),
          descendants(id) AS (
            SELECT b.id FROM moz_bookmarks b
            WHERE b.guid IN (${requestedGuids.map(guid => JSON.stringify(guid)).join(",")})
            UNION ALL
            SELECT b.id FROM moz_bookmarks b
            JOIN descendants d ON d.id = b.parent
          )
          SELECT b.guid, s.id NOT NULL AS syncable
          FROM descendants d
          JOIN moz_bookmarks b ON b.id = d.id
          LEFT JOIN syncedItems s ON s.id = d.id
          `);
        return rows.map(row => {
          let syncId = BookmarkSyncUtils.guidToSyncId(row.getResultByName("guid"));
          let syncable = !!row.getResultByName("syncable");
          return { syncId, syncable };
        });
      }
    );
  },

  /**
   * Migrates an array of `{ syncId, modified }` tuples from the old JSON-based
   * tracker to the new sync change counter. `modified` is when the change was
   * added to the old tracker, in milliseconds.
   *
   * Sync calls this method before the first bookmark sync after the Places
   * schema migration.
   */
  migrateOldTrackerEntries(entries) {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: migrateOldTrackerEntries", function(db) {
        return db.executeTransaction(async function() {
          // Mark all existing bookmarks as synced, and clear their change
          // counters to avoid a full upload on the next sync. Note that
          // this means we'll miss changes made between startup and the first
          // post-migration sync, as well as changes made on a new release
          // channel that weren't synced before the user downgraded. This is
          // unfortunate, but no worse than the behavior of the old tracker.
          //
          // We also likely have bookmarks that don't exist on the server,
          // because the old tracker missed them. We'll eventually fix the
          // server once we decide on a repair strategy.
          await db.executeCached(`
            WITH RECURSIVE
            syncedItems(id) AS (
              SELECT b.id FROM moz_bookmarks b
              WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                               'mobile______')
              UNION ALL
              SELECT b.id FROM moz_bookmarks b
              JOIN syncedItems s ON b.parent = s.id
            )
            UPDATE moz_bookmarks SET
              syncStatus = :syncStatus,
              syncChangeCounter = 0
            WHERE id IN syncedItems`,
            { syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL });

          await db.executeCached(`DELETE FROM moz_bookmarks_deleted`);

          await db.executeCached(`CREATE TEMP TABLE moz_bookmarks_tracked (
            guid TEXT PRIMARY KEY,
            time INTEGER
          )`);

          try {
            for (let { syncId, modified } of entries) {
              let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
              if (!PlacesUtils.isValidGuid(guid)) {
                BookmarkSyncLog.warn(`migrateOldTrackerEntries: Ignoring ` +
                                     `change for invalid item ${guid}`);
                continue;
              }
              let time = PlacesUtils.toPRTime(Number.isFinite(modified) ?
                                              modified : Date.now());
              await db.executeCached(`
                INSERT OR IGNORE INTO moz_bookmarks_tracked (guid, time)
                VALUES (:guid, :time)`,
                { guid, time });
            }

            // Bump the change counter for existing tracked items.
            await db.executeCached(`
              INSERT OR REPLACE INTO moz_bookmarks (id, fk, type, parent,
                                                    position, title,
                                                    dateAdded, lastModified,
                                                    guid, syncChangeCounter,
                                                    syncStatus)
              SELECT b.id, b.fk, b.type, b.parent, b.position, b.title,
                     b.dateAdded, MAX(b.lastModified, t.time), b.guid,
                     b.syncChangeCounter + 1, b.syncStatus
              FROM moz_bookmarks b
              JOIN moz_bookmarks_tracked t ON b.guid = t.guid`);

            // Insert tombstones for nonexistent tracked items, using the most
            // recent deletion date for more accurate reconciliation. We assume
            // the tracked item belongs to a synced root.
            await db.executeCached(`
              INSERT OR REPLACE INTO moz_bookmarks_deleted (guid, dateRemoved)
              SELECT t.guid, MAX(IFNULL((SELECT dateRemoved FROM moz_bookmarks_deleted
                                         WHERE guid = t.guid), 0), t.time)
              FROM moz_bookmarks_tracked t
              LEFT JOIN moz_bookmarks b ON t.guid = b.guid
              WHERE b.guid IS NULL`);
          } finally {
            await db.executeCached(`DROP TABLE moz_bookmarks_tracked`);
          }
        });
      }
    );
  },

  /**
   * Reorders a folder's children, based on their order in the array of sync
   * IDs.
   *
   * Sync uses this method to reorder all synced children after applying all
   * incoming records.
   *
   * @return {Promise} resolved when reordering is complete.
   * @rejects if an error happens while reordering.
   * @throws if the arguments are invalid.
   */
  order(parentSyncId, childSyncIds) {
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
    if (!childSyncIds.length) {
      return undefined;
    }
    let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);
    if (parentGuid == PlacesUtils.bookmarks.rootGuid) {
      // Reordering roots doesn't make sense, but Sync will do this on the
      // first sync.
      return undefined;
    }
    let orderedChildrenGuids = childSyncIds.map(BookmarkSyncUtils.syncIdToGuid);
    return PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids,
                                         { source: SOURCE_SYNC });
  },

  /**
   * Resolves to true if there are known sync changes.
   */
  havePendingChanges() {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: havePendingChanges", async function(db) {
        let rows = await db.executeCached(`
          WITH RECURSIVE
          syncedItems(id, guid, syncChangeCounter) AS (
            SELECT b.id, b.guid, b.syncChangeCounter
             FROM moz_bookmarks b
             WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                              'mobile______')
            UNION ALL
            SELECT b.id, b.guid, b.syncChangeCounter
            FROM moz_bookmarks b
            JOIN syncedItems s ON b.parent = s.id
          ),
          changedItems(guid) AS (
            SELECT guid FROM syncedItems
            WHERE syncChangeCounter >= 1
            UNION ALL
            SELECT guid FROM moz_bookmarks_deleted
          )
          SELECT EXISTS(SELECT guid FROM changedItems) AS haveChanges`);
        return !!rows[0].getResultByName("haveChanges");
      }
    );
  },

  /**
   * Returns a changeset containing local bookmark changes since the last sync.
   *
   * @return {Promise} resolved once all items have been fetched.
   * @resolves to an object containing records for changed bookmarks, keyed by
   *           the sync ID.
   * @see pullSyncChanges for the implementation, and markChangesAsSyncing for
   *      an explanation of why we update the sync status.
   */
  pullChanges() {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: pullChanges", pullSyncChanges);
  },

  /**
   * Updates the sync status of all "NEW" bookmarks to "NORMAL", so that Sync
   * can recover correctly after an interrupted sync.
   *
   * @param changeRecords
   *        A changeset containing sync change records, as returned by
   *        `pullChanges`.
   * @return {Promise} resolved once all records have been updated.
   */
  markChangesAsSyncing(changeRecords) {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: markChangesAsSyncing",
      db => markChangesAsSyncing(db, changeRecords)
    );
  },

  /**
   * Decrements the sync change counter, updates the sync status, and cleans up
   * tombstones for successfully synced items. Sync calls this method at the
   * end of each bookmark sync.
   *
   * @param changeRecords
   *        A changeset containing sync change records, as returned by
   *        `pullChanges`.
   * @return {Promise} resolved once all records have been updated.
   */
  pushChanges(changeRecords) {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: pushChanges", async function(db) {
        let skippedCount = 0;
        let syncedTombstoneGuids = [];
        let syncedChanges = [];

        for (let syncId in changeRecords) {
          // Validate change records to catch coding errors.
          let changeRecord = validateChangeRecord(
            "BookmarkSyncUtils: pushChanges",
            changeRecords[syncId], {
              tombstone: { required: true },
              counter: { required: true },
              synced: { required: true },
            }
          );

          // Sync sets the `synced` flag for reconciled or successfully
          // uploaded items. If upload failed, ignore the change; we'll
          // try again on the next sync.
          if (!changeRecord.synced) {
            skippedCount++;
            continue;
          }

          let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
          if (changeRecord.tombstone) {
            syncedTombstoneGuids.push(guid);
          } else {
            syncedChanges.push([guid, changeRecord]);
          }
        }

        if (syncedChanges.length || syncedTombstoneGuids.length) {
          await db.executeTransaction(async function() {
            for (let [guid, changeRecord] of syncedChanges) {
              // Reduce the change counter and update the sync status for
              // reconciled and uploaded items. If the bookmark was updated
              // during the sync, its change counter will still be > 0 for the
              // next sync.
              await db.executeCached(`
                UPDATE moz_bookmarks
                SET syncChangeCounter = MAX(syncChangeCounter - :syncChangeDelta, 0),
                    syncStatus = :syncStatus
                WHERE guid = :guid`,
                { guid, syncChangeDelta: changeRecord.counter,
                  syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL });
            }

            await removeTombstones(db, syncedTombstoneGuids);
          });
        }

        BookmarkSyncLog.debug(`pushChanges: Processed change records`,
                              { skipped: skippedCount,
                                updated: syncedChanges.length,
                                tombstones: syncedTombstoneGuids.length });
      }
    );
  },

  /**
   * Removes items from the database. Sync buffers incoming tombstones, and
   * calls this method to apply them at the end of each sync. Deletion
   * happens in three steps:
   *
   *  1. Remove all non-folder items. Deleting a folder on a remote client
   *     uploads tombstones for the folder and its children at the time of
   *     deletion. This preserves any new children we've added locally since
   *     the last sync.
   *  2. Reparent remaining children to the tombstoned folder's parent. This
   *     bumps the change counter for the children and their new parent.
   *  3. Remove the tombstoned folder. Because we don't do this in a
   *     transaction, the user might move new items into the folder before we
   *     can remove it. In that case, we keep the folder and upload the new
   *     subtree to the server.
   *
   * See the comment above `BookmarksStore::deletePending` for the details on
   * why delete works the way it does.
   */
  remove(syncIds) {
    if (!syncIds.length) {
      return null;
    }

    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: remove",
      async function(db) {
        let folderGuids = [];
        for (let syncId of syncIds) {
          if (syncId in ROOT_SYNC_ID_TO_GUID) {
            BookmarkSyncLog.warn(`remove: Refusing to remove root ${syncId}`);
            continue;
          }
          let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
          let bookmarkItem = await PlacesUtils.bookmarks.fetch(guid);
          if (!bookmarkItem) {
            BookmarkSyncLog.trace(`remove: Item ${guid} already removed`);
            continue;
          }
          let kind = await getKindForItem(db, bookmarkItem);
          if (kind == BookmarkSyncUtils.KINDS.FOLDER) {
            folderGuids.push(bookmarkItem.guid);
            continue;
          }
          let wasRemoved = await deleteSyncedAtom(bookmarkItem);
          if (wasRemoved) {
             BookmarkSyncLog.trace(`remove: Removed item ${guid} with ` +
                                   `kind ${kind}`);
          }
        }

        for (let guid of folderGuids) {
          let bookmarkItem = await PlacesUtils.bookmarks.fetch(guid);
          if (!bookmarkItem) {
            BookmarkSyncLog.trace(`remove: Folder ${guid} already removed`);
            continue;
          }
          let wasRemoved = await deleteSyncedFolder(db, bookmarkItem);
          if (wasRemoved) {
            BookmarkSyncLog.trace(`remove: Removed folder ${bookmarkItem.guid}`);
          }
        }

        // TODO (Bug 1313890): Refactor the bookmarks engine to pull change records
        // before uploading, instead of returning records to merge into the engine's
        // initial changeset.
        return pullSyncChanges(db);
      }
    );
  },

  /**
   * Increments the change counter of a non-folder item and its parent. Sync
   * calls this method to override a remote deletion for an item that's changed
   * locally.
   *
   * @param syncId
   *        The sync ID to revive.
   * @return {Promise} resolved once the change counters have been updated.
   * @resolves to `null` if the item doesn't exist or is a folder. Otherwise,
   *           resolves to an object containing new change records for the item
   *           and its parent. The bookmarks engine merges these records into
   *           the changeset for the current sync.
   */
  async touch(syncId) {
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(syncId);
    let guid = BookmarkSyncUtils.syncIdToGuid(syncId);

    let bookmarkItem = await PlacesUtils.bookmarks.fetch(guid);
    if (!bookmarkItem) {
      return null;
    }
    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: touch",
      async function(db) {
        let kind = await getKindForItem(db, bookmarkItem);
        if (kind == BookmarkSyncUtils.KINDS.FOLDER) {
          // We avoid reviving folders since reviving them properly would require
          // reviving their children as well. Unfortunately, this is the wrong
          // choice in the case of a bookmark restore where the bookmarks engine
          // fails to wipe the server. In that case, if the server has the folder
          // as deleted, we *would* want to reupload this folder. This is mitigated
          // by the fact that `remove` moves any undeleted children to the
          // grandparent when deleting the parent.
          return null;
        }
        return touchSyncBookmark(db, bookmarkItem);
      }
    );
  },

  /**
   * Returns true for sync IDs that are considered roots.
   */
  isRootSyncID(syncID) {
    return ROOT_SYNC_ID_TO_GUID.hasOwnProperty(syncID);
  },

  /**
   * Removes all bookmarks and tombstones from the database. Sync calls this
   * method when it receives a command from a remote client to wipe all stored
   * data, or when replacing stored data with remote data on a first sync.
   *
   * @return {Promise} resolved once all items have been removed.
   */
  async wipe() {
    // Remove all children from all roots.
    await PlacesUtils.bookmarks.eraseEverything({
      source: SOURCE_SYNC,
    });
    // Remove tombstones and reset change tracking info for the roots.
    await BookmarkSyncUtils.reset();
  },

  /**
   * Marks all bookmarks as "NEW" and removes all tombstones. Unlike `wipe`,
   * this keeps all existing bookmarks, and only clears their sync change
   * tracking info.
   *
   * @return {Promise} resolved once all items have been updated.
   */
  reset() {
    return PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: reset", function(db) {
        return db.executeTransaction(async function() {
          // Reset change counters and statuses for all bookmarks.
          await db.executeCached(`
            UPDATE moz_bookmarks
            SET syncChangeCounter = 1,
                syncStatus = :syncStatus`,
            { syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NEW });

          // The orphan anno isn't meaningful when Sync is disconnected.
          await db.execute(`
            DELETE FROM moz_items_annos
            WHERE anno_attribute_id = (SELECT id FROM moz_anno_attributes
                                       WHERE name = :orphanAnno)`,
            { orphanAnno: BookmarkSyncUtils.SYNC_PARENT_ANNO });

          // Drop stale tombstones.
          await db.executeCached("DELETE FROM moz_bookmarks_deleted");
        });
      }
    );
  },

  /**
   * De-dupes an item by changing its sync ID to match the ID on the server.
   * Sync calls this method when it detects an incoming item is a duplicate of
   * an existing local item.
   *
   * Note that this method doesn't move the item if the local and remote sync
   * IDs are different. That happens after de-duping, when the bookmarks engine
   * calls `update` to update the item.
   *
   * @param localSyncId
   *        The local ID to change.
   * @param remoteSyncId
   *        The remote ID that should replace the local ID.
   * @param remoteParentSyncId
   *        The remote record's parent ID.
   * @return {Promise} resolved once the ID has been changed.
   * @resolves to an object containing new change records for the old item,
   *           the local parent, and the remote parent if different from the
   *           local parent. The bookmarks engine merges these records into the
   *           changeset for the current sync.
   */
  dedupe(localSyncId, remoteSyncId, remoteParentSyncId) {
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(localSyncId);
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(remoteSyncId);
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(remoteParentSyncId);

    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: dedupe", db =>
      dedupeSyncBookmark(db, BookmarkSyncUtils.syncIdToGuid(localSyncId),
                         BookmarkSyncUtils.syncIdToGuid(remoteSyncId),
                         BookmarkSyncUtils.syncIdToGuid(remoteParentSyncId))
    );
  },

  /**
   * Updates a bookmark with synced properties. Only Sync should call this
   * method; other callers should use `Bookmarks.update`.
   *
   * The following properties are supported:
   *  - kind: Optional.
   *  - guid: Required.
   *  - parentGuid: Optional; reparents the bookmark if specified.
   *  - title: Optional.
   *  - url: Optional.
   *  - tags: Optional; replaces all existing tags.
   *  - keyword: Optional.
   *  - description: Optional.
   *  - loadInSidebar: Optional.
   *  - query: Optional.
   *
   * @param info
   *        object representing a bookmark-item, as defined above.
   *
   * @return {Promise} resolved when the update is complete.
   * @resolves to an object representing the updated bookmark.
   * @rejects if it's not possible to update the given bookmark.
   * @throws if the arguments are invalid.
   */
  update(info) {
    let updateInfo = validateSyncBookmarkObject("BookmarkSyncUtils: update",
      info, { syncId: { required: true } });

    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: update",
      db => updateSyncBookmark(db, updateInfo));
  },

  /**
   * Inserts a synced bookmark into the tree. Only Sync should call this
   * method; other callers should use `Bookmarks.insert`.
   *
   * The following properties are supported:
   *  - kind: Required.
   *  - guid: Required.
   *  - parentGuid: Required.
   *  - url: Required for bookmarks.
   *  - query: A smart bookmark query string, optional.
   *  - tags: An optional array of tag strings.
   *  - keyword: An optional keyword string.
   *  - description: An optional description string.
   *  - loadInSidebar: An optional boolean; defaults to false.
   *
   * Sync doesn't set the index, since it appends and reorders children
   * after applying all incoming items.
   *
   * @param info
   *        object representing a synced bookmark.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an object representing the created bookmark.
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insert(info) {
    let insertInfo = validateNewBookmark("BookmarkSyncUtils: insert", info);

    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: insert",
      db => insertSyncBookmark(db, insertInfo));
  },

  /**
   * Fetches a Sync bookmark object for an item in the tree. The object contains
   * the following properties, depending on the item's kind:
   *
   *  - kind (all): A string representing the item's kind.
   *  - syncId (all): The item's sync ID.
   *  - parentSyncId (all): The sync ID of the item's parent.
   *  - parentTitle (all): The title of the item's parent, used for de-duping.
   *    Omitted for the Places root and parents with empty titles.
   *  - dateAdded (all): Timestamp in milliseconds, when the bookmark was added
   *    or created on a remote device if known.
   *  - title ("bookmark", "folder", "livemark", "query"): The item's title.
   *    Omitted if empty.
   *  - url ("bookmark", "query"): The item's URL.
   *  - tags ("bookmark", "query"): An array containing the item's tags.
   *  - keyword ("bookmark"): The bookmark's keyword, if one exists.
   *  - description ("bookmark", "folder", "livemark"): The item's description.
   *    Omitted if one isn't set.
   *  - loadInSidebar ("bookmark", "query"): Whether to load the bookmark in
   *    the sidebar. Always `false` for queries.
   *  - feed ("livemark"): A `URL` object pointing to the livemark's feed URL.
   *  - site ("livemark"): A `URL` object pointing to the livemark's site URL,
   *    or `null` if one isn't set.
   *  - childSyncIds ("folder"): An array containing the sync IDs of the item's
   *    children, used to determine child order.
   *  - folder ("query"): The tag folder name, if this is a tag query.
   *  - query ("query"): The smart bookmark query name, if this is a smart
   *    bookmark.
   *  - index ("separator"): The separator's position within its parent.
   */
  async fetch(syncId) {
    let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
    let bookmarkItem = await PlacesUtils.bookmarks.fetch(guid);
    if (!bookmarkItem) {
      return null;
    }
    return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: fetch",
      async function(db) {
        // Convert the Places bookmark object to a Sync bookmark and add
        // kind-specific properties. Titles are required for bookmarks,
        // folders, and livemarks; optional for queries, and omitted for
        // separators.
        let kind = await getKindForItem(db, bookmarkItem);
        let item;
        switch (kind) {
          case BookmarkSyncUtils.KINDS.BOOKMARK:
            item = await fetchBookmarkItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.QUERY:
            item = await fetchQueryItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.FOLDER:
            item = await fetchFolderItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.LIVEMARK:
            item = await fetchLivemarkItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.SEPARATOR:
            item = await placesBookmarkToSyncBookmark(db, bookmarkItem);
            item.index = bookmarkItem.index;
            break;

          default:
            throw new Error(`Unknown bookmark kind: ${kind}`);
        }

        // Sync uses the parent title for de-duping. All Sync bookmark objects
        // except the Places root should have this property.
        if (bookmarkItem.parentGuid) {
          let parent = await PlacesUtils.bookmarks.fetch(bookmarkItem.parentGuid);
          item.parentTitle = parent.title || "";
        }

        return item;
      }
    );
  },

  /**
   * Returns the sync change counter increment for a change source constant.
   */
  determineSyncChangeDelta(source) {
    // Don't bump the change counter when applying changes made by Sync, to
    // avoid sync loops.
    return source == PlacesUtils.bookmarks.SOURCES.SYNC ? 0 : 1;
  },

  /**
   * Returns the sync status for a new item inserted by a change source.
   */
  determineInitialSyncStatus(source) {
    if (source == PlacesUtils.bookmarks.SOURCES.SYNC) {
      // Incoming bookmarks are "NORMAL", since they already exist on the server.
      return PlacesUtils.bookmarks.SYNC_STATUS.NORMAL;
    }
    if (source == PlacesUtils.bookmarks.SOURCES.IMPORT_REPLACE) {
      // If the user restores from a backup, or Places automatically recovers
      // from a corrupt database, all prior sync tracking is lost. Setting the
      // status to "UNKNOWN" allows Sync to reconcile restored bookmarks with
      // those on the server.
      return PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN;
    }
    // For all other sources, mark items as "NEW". We'll update their statuses
    // to "NORMAL" after the first sync.
    return PlacesUtils.bookmarks.SYNC_STATUS.NEW;
  },

  /**
   * An internal helper that bumps the change counter for all bookmarks with
   * a given URL. This is used to update bookmarks when adding or changing a
   * tag or keyword entry.
   *
   * @param db
   *        the Sqlite.jsm connection handle.
   * @param url
   *        the bookmark URL object.
   * @param syncChangeDelta
   *        the sync change counter increment.
   * @return {Promise} resolved when the counters have been updated.
   */
  addSyncChangesForBookmarksWithURL(db, url, syncChangeDelta) {
    if (!url || !syncChangeDelta) {
      return Promise.resolve();
    }
    return db.executeCached(`
      UPDATE moz_bookmarks
        SET syncChangeCounter = syncChangeCounter + :syncChangeDelta
      WHERE type = :type AND
            fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND
                  url = :url)`,
      { syncChangeDelta, type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
        url: url.href });
  },

  /**
   * Returns `undefined` if no sensible timestamp could be found.
   * Otherwise, returns the earliest sensible timestamp between `existingMillis`
   * and `serverMillis`.
   */
  ratchetTimestampBackwards(existingMillis, serverMillis, lowerBound = BookmarkSyncUtils.EARLIEST_BOOKMARK_TIMESTAMP) {
    const possible = [+existingMillis, +serverMillis].filter(n => !isNaN(n) && n > lowerBound);
    if (!possible.length) {
      return undefined;
    }
    return Math.min(...possible);
  },

  /**
   * Rebuilds the left pane query for the mobile root under "All Bookmarks" if
   * necessary. Sync calls this method at the end of each bookmark sync. This
   * code should eventually move to `PlacesUIUtils#maybeRebuildLeftPane`; see
   * bug 647605.
   *
   * - If there are no mobile bookmarks, the query will not be created, or
   *   will be removed if it already exists.
   * - If there are mobile bookmarks, the query will be created if it doesn't
   *   exist, or will be updated with the correct title and URL otherwise.
   */
  async ensureMobileQuery() {
    Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, true);

    let db = await PlacesUtils.promiseDBConnection();

    let maybeAllBookmarksGuids = await fetchGuidsWithAnno(db,
      ORGANIZER_QUERY_ANNO, ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE);
    if (!maybeAllBookmarksGuids.length) {
      return;
    }

    let hasMobileBookmarks = (await db.executeCached(`SELECT EXISTS(
      SELECT 1 FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      WHERE p.guid = :mobileGuid
    ) AS hasMobile`, {
      mobileGuid: PlacesUtils.bookmarks.mobileGuid,
    }))[0].getResultByName("hasMobile");

    let allBookmarksGuid = maybeAllBookmarksGuids[0];
    let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");

    let maybeMobileQueryGuids = await fetchGuidsWithAnno(db,
      ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE);
    if (maybeMobileQueryGuids.length) {
      let mobileQueryGuid = maybeMobileQueryGuids[0];
      if (hasMobileBookmarks) {
        // We have a left pane query for mobile bookmarks, and at least one
        // mobile bookmark. Make sure the query title is correct.
        await PlacesUtils.bookmarks.update({
          guid: mobileQueryGuid,
          url: "place:folder=MOBILE_BOOKMARKS",
          title: mobileTitle,
          source: SOURCE_SYNC,
        });
      } else {
        // We have a left pane query for mobile bookmarks, but no mobile
        // bookmarks. Remove the query.
        await PlacesUtils.bookmarks.remove(mobileQueryGuid, {
          source: SOURCE_SYNC,
        });
      }
    } else if (hasMobileBookmarks) {
      // We have mobile bookmarks, but no left pane query. Create the query.
      let mobileQuery = await PlacesUtils.bookmarks.insert({
        parentGuid: allBookmarksGuid,
        url: "place:folder=MOBILE_BOOKMARKS",
        title: mobileTitle,
        source: SOURCE_SYNC,
      });

      let mobileQueryId = await PlacesUtils.promiseItemId(mobileQuery.guid);

      PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
        ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE, 0,
        PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
      PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
        PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
        PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
    }

    // Make sure the mobile root title matches the query.
    await PlacesUtils.bookmarks.update({
      guid: PlacesUtils.bookmarks.mobileGuid,
      title: mobileTitle,
      source: SOURCE_SYNC,
    });
  },

  /**
   * Fetches an array of GUIDs for items that have an annotation set with the
   * given value.
   */
  async fetchGuidsWithAnno(anno, val) {
    let db = await PlacesUtils.promiseDBConnection();
    return fetchGuidsWithAnno(db, anno, val);
  },
});

XPCOMUtils.defineLazyGetter(this, "BookmarkSyncLog", () => {
  return Log.repository.getLogger("BookmarkSyncUtils");
});

function validateSyncBookmarkObject(name, input, behavior) {
  return PlacesUtils.validateItemProperties(name,
    PlacesUtils.SYNC_BOOKMARK_VALIDATORS, input, behavior);
}

// Validates a sync change record as returned by `pullChanges` and passed to
// `pushChanges`.
function validateChangeRecord(name, changeRecord, behavior) {
  return PlacesUtils.validateItemProperties(name,
    PlacesUtils.SYNC_CHANGE_RECORD_VALIDATORS, changeRecord, behavior);
}

// Similar to the private `fetchBookmarksByParent` implementation in
// `Bookmarks.jsm`.
var fetchChildGuids = async function(db, parentGuid) {
  let rows = await db.executeCached(`
    SELECT guid
    FROM moz_bookmarks
    WHERE parent = (
      SELECT id FROM moz_bookmarks WHERE guid = :parentGuid
    )
    ORDER BY position`,
    { parentGuid }
  );
  return rows.map(row => row.getResultByName("guid"));
};

// A helper for whenever we want to know if a GUID doesn't exist in the places
// database. Primarily used to detect orphans on incoming records.
var GUIDMissing = async function(guid) {
  try {
    await PlacesUtils.promiseItemId(guid);
    return false;
  } catch (ex) {
    if (ex.message == "no item found for the given GUID") {
      return true;
    }
    throw ex;
  }
};

// Tag queries use a `place:` URL that refers to the tag folder ID. When we
// apply a synced tag query from a remote client, we need to update the URL to
// point to the local tag folder.
async function updateTagQueryFolder(db, info) {
  if (info.kind != BookmarkSyncUtils.KINDS.QUERY || !info.folder || !info.url ||
      info.url.protocol != "place:") {
    return info;
  }

  let params = new URLSearchParams(info.url.pathname);
  let type = +params.get("type");

  if (type != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
    return info;
  }

  let id = await getOrCreateTagFolder(db, info.folder);
  BookmarkSyncLog.debug(`updateTagQueryFolder: Tag query folder: ${
    info.folder} = ${id}`);

  // Rewrite the query to reference the new ID.
  params.set("folder", id);
  info.url = new URL(info.url.protocol + params);

  return info;
}

async function annotateOrphan(item, requestedParentSyncId) {
  let guid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
  let itemId = await PlacesUtils.promiseItemId(guid);
  PlacesUtils.annotations.setItemAnnotation(itemId,
    BookmarkSyncUtils.SYNC_PARENT_ANNO, requestedParentSyncId, 0,
    PlacesUtils.annotations.EXPIRE_NEVER,
    SOURCE_SYNC);
}

var reparentOrphans = async function(db, item) {
  if (!item.kind || item.kind != BookmarkSyncUtils.KINDS.FOLDER) {
    return;
  }
  let orphanGuids = await fetchGuidsWithAnno(db,
                                             BookmarkSyncUtils.SYNC_PARENT_ANNO,
                                             item.syncId);
  let folderGuid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
  BookmarkSyncLog.debug(`reparentOrphans: Reparenting ${
    JSON.stringify(orphanGuids)} to ${item.syncId}`);
  for (let i = 0; i < orphanGuids.length; ++i) {
    try {
      // Reparenting can fail if we have a corrupted or incomplete tree
      // where an item's parent is one of its descendants.
      BookmarkSyncLog.trace(`reparentOrphans: Attempting to move item ${
        orphanGuids[i]} to new parent ${item.syncId}`);
      await PlacesUtils.bookmarks.update({
        guid: orphanGuids[i],
        parentGuid: folderGuid,
        index: PlacesUtils.bookmarks.DEFAULT_INDEX,
        source: SOURCE_SYNC,
      });
    } catch (ex) {
      BookmarkSyncLog.error(`reparentOrphans: Failed to reparent item ${
        orphanGuids[i]} to ${item.syncId}`, ex);
    }
  }
}

// Inserts a synced bookmark into the database.
async function insertSyncBookmark(db, insertInfo) {
  let requestedParentSyncId = insertInfo.parentSyncId;
  let requestedParentGuid =
    BookmarkSyncUtils.syncIdToGuid(insertInfo.parentSyncId);
  let isOrphan = await GUIDMissing(requestedParentGuid);

  // Default to "unfiled" for new bookmarks if the parent doesn't exist.
  if (!isOrphan) {
    BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
      insertInfo.syncId} is not an orphan`);
  } else {
    BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
      insertInfo.syncId} is an orphan: parent ${
      insertInfo.parentSyncId} doesn't exist; reparenting to unfiled`);
    insertInfo.parentSyncId = "unfiled";
  }

  // If we're inserting a tag query, make sure the tag exists and fix the
  // folder ID to refer to the local tag folder.
  insertInfo = await updateTagQueryFolder(db, insertInfo);

  let newItem;
  if (insertInfo.kind == BookmarkSyncUtils.KINDS.LIVEMARK) {
    newItem = await insertSyncLivemark(db, insertInfo);
  } else {
    let bookmarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
    let bookmarkItem = await PlacesUtils.bookmarks.insert(bookmarkInfo);
    newItem = await insertBookmarkMetadata(db, bookmarkItem, insertInfo);
  }

  if (!newItem) {
    return null;
  }

  // If the item is an orphan, annotate it with its real parent sync ID.
  if (isOrphan) {
    await annotateOrphan(newItem, requestedParentSyncId);
  }

  // Reparent all orphans that expect this folder as the parent.
  await reparentOrphans(db, newItem);

  return newItem;
}

// Inserts a synced livemark.
async function insertSyncLivemark(db, insertInfo) {
  if (!insertInfo.feed) {
    BookmarkSyncLog.debug(`insertSyncLivemark: ${
      insertInfo.syncId} missing feed URL`);
    return null;
  }
  let livemarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
  let parentIsLivemark = await getAnno(db, livemarkInfo.parentGuid,
                                       PlacesUtils.LMANNO_FEEDURI);
  if (parentIsLivemark) {
    // A livemark can't be a descendant of another livemark.
    BookmarkSyncLog.debug(`insertSyncLivemark: Invalid parent ${
      insertInfo.parentSyncId}; skipping livemark record ${
      insertInfo.syncId}`);
    return null;
  }

  let livemarkItem = await PlacesUtils.livemarks.addLivemark(livemarkInfo);

  return insertBookmarkMetadata(db, livemarkItem, insertInfo);
}

// Keywords are a 1 to 1 mapping between strings and pairs of (URL, postData).
// (the postData is not synced, so we ignore it). Sync associates keywords with
// bookmarks, which is not really accurate. -- We might already have a keyword
// with that name, or we might already have another bookmark with that URL with
// a different keyword, etc.
//
// If we don't handle those cases by removing the conflicting keywords first,
// the insertion  will fail, and the keywords will either be wrong, or missing.
// This function handles those cases.
function removeConflictingKeywords(bookmarkURL, newKeyword) {
  return PlacesUtils.withConnectionWrapper(
    "BookmarkSyncUtils: removeConflictingKeywords", async function(db) {
      let entryForURL = await PlacesUtils.keywords.fetch({
        url: bookmarkURL.href,
      });
      if (entryForURL && entryForURL.keyword !== newKeyword) {
        await PlacesUtils.keywords.remove({
          keyword: entryForURL.keyword,
          source: SOURCE_SYNC,
        });
        // This will cause us to reupload this record for this sync, but without it,
        // we will risk data corruption.
        await BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
          db, entryForURL.url, 1);
      }
      if (!newKeyword) {
        return;
      }
      let entryForNewKeyword = await PlacesUtils.keywords.fetch({
        keyword: newKeyword
      });
      if (entryForNewKeyword) {
        await PlacesUtils.keywords.remove({
          keyword: entryForNewKeyword.keyword,
          source: SOURCE_SYNC,
        });
        await BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
          db, entryForNewKeyword.url, 1);
      }
    }
  );
}

// Sets annotations, keywords, and tags on a new bookmark. Returns a Sync
// bookmark object.
async function insertBookmarkMetadata(db, bookmarkItem, insertInfo) {
  let itemId = await PlacesUtils.promiseItemId(bookmarkItem.guid);
  let newItem = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (insertInfo.query) {
    PlacesUtils.annotations.setItemAnnotation(itemId,
      BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, insertInfo.query, 0,
      PlacesUtils.annotations.EXPIRE_NEVER,
      SOURCE_SYNC);
    newItem.query = insertInfo.query;
  }

  try {
    newItem.tags = tagItem(bookmarkItem, insertInfo.tags);
  } catch (ex) {
    BookmarkSyncLog.warn(`insertBookmarkMetadata: Error tagging item ${
      insertInfo.syncId}`, ex);
  }

  if (insertInfo.keyword) {
    await removeConflictingKeywords(bookmarkItem.url, insertInfo.keyword);
    await PlacesUtils.keywords.insert({
      keyword: insertInfo.keyword,
      url: bookmarkItem.url.href,
      source: SOURCE_SYNC,
    });
    newItem.keyword = insertInfo.keyword;
  }

  if (insertInfo.description) {
    PlacesUtils.annotations.setItemAnnotation(itemId,
      BookmarkSyncUtils.DESCRIPTION_ANNO, insertInfo.description, 0,
      PlacesUtils.annotations.EXPIRE_NEVER,
      SOURCE_SYNC);
    newItem.description = insertInfo.description;
  }

  if (insertInfo.loadInSidebar) {
    PlacesUtils.annotations.setItemAnnotation(itemId,
      BookmarkSyncUtils.SIDEBAR_ANNO, insertInfo.loadInSidebar, 0,
      PlacesUtils.annotations.EXPIRE_NEVER,
      SOURCE_SYNC);
    newItem.loadInSidebar = insertInfo.loadInSidebar;
  }

  return newItem;
}

// Determines the Sync record kind for an existing bookmark.
async function getKindForItem(db, item) {
  switch (item.type) {
    case PlacesUtils.bookmarks.TYPE_FOLDER: {
      let isLivemark = await getAnno(db, item.guid,
                                     PlacesUtils.LMANNO_FEEDURI);
      return isLivemark ? BookmarkSyncUtils.KINDS.LIVEMARK :
                          BookmarkSyncUtils.KINDS.FOLDER;
    }
    case PlacesUtils.bookmarks.TYPE_BOOKMARK:
      return item.url.protocol == "place:" ?
             BookmarkSyncUtils.KINDS.QUERY :
             BookmarkSyncUtils.KINDS.BOOKMARK;

    case PlacesUtils.bookmarks.TYPE_SEPARATOR:
      return BookmarkSyncUtils.KINDS.SEPARATOR;
  }
  return null;
}

// Returns the `nsINavBookmarksService` bookmark type constant for a Sync
// record kind.
function getTypeForKind(kind) {
  switch (kind) {
    case BookmarkSyncUtils.KINDS.BOOKMARK:
    case BookmarkSyncUtils.KINDS.QUERY:
      return PlacesUtils.bookmarks.TYPE_BOOKMARK;

    case BookmarkSyncUtils.KINDS.FOLDER:
    case BookmarkSyncUtils.KINDS.LIVEMARK:
      return PlacesUtils.bookmarks.TYPE_FOLDER;

    case BookmarkSyncUtils.KINDS.SEPARATOR:
      return PlacesUtils.bookmarks.TYPE_SEPARATOR;
  }
  throw new Error(`Unknown bookmark kind: ${kind}`);
}

// Determines if a livemark should be reinserted. Returns true if `updateInfo`
// specifies different feed or site URLs; false otherwise.
var shouldReinsertLivemark = async function(updateInfo) {
  let hasFeed = updateInfo.hasOwnProperty("feed");
  let hasSite = updateInfo.hasOwnProperty("site");
  if (!hasFeed && !hasSite) {
    return false;
  }
  let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
  let livemark = await PlacesUtils.livemarks.getLivemark({
    guid,
  });
  if (hasFeed) {
    let feedURI = PlacesUtils.toURI(updateInfo.feed);
    if (!livemark.feedURI.equals(feedURI)) {
      return true;
    }
  }
  if (hasSite) {
    if (!updateInfo.site) {
      return !!livemark.siteURI;
    }
    let siteURI = PlacesUtils.toURI(updateInfo.site);
    if (!livemark.siteURI || !siteURI.equals(livemark.siteURI)) {
      return true;
    }
  }
  return false;
};

async function updateSyncBookmark(db, updateInfo) {
  let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
  let oldBookmarkItem = await PlacesUtils.bookmarks.fetch(guid);
  if (!oldBookmarkItem) {
    throw new Error(`Bookmark with sync ID ${
      updateInfo.syncId} does not exist`);
  }

  if (updateInfo.hasOwnProperty("dateAdded")) {
    let newDateAdded = BookmarkSyncUtils.ratchetTimestampBackwards(
      oldBookmarkItem.dateAdded, updateInfo.dateAdded);
    if (!newDateAdded || newDateAdded === oldBookmarkItem.dateAdded) {
      delete updateInfo.dateAdded;
    } else {
      updateInfo.dateAdded = newDateAdded;
    }
  }

  let shouldReinsert = false;
  let oldKind = await getKindForItem(db, oldBookmarkItem);
  if (updateInfo.hasOwnProperty("kind") && updateInfo.kind != oldKind) {
    // If the item's aren't the same kind, we can't update the record;
    // we must remove and reinsert.
    shouldReinsert = true;
    if (BookmarkSyncLog.level <= Log.Level.Warn) {
      let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
      BookmarkSyncLog.warn(`updateSyncBookmark: Local ${
        oldSyncId} kind = ${oldKind}; remote ${
        updateInfo.syncId} kind = ${
        updateInfo.kind}. Deleting and recreating`);
    }
  } else if (oldKind == BookmarkSyncUtils.KINDS.LIVEMARK) {
    // Similarly, if we're changing a livemark's site or feed URL, we need to
    // reinsert.
    shouldReinsert = await shouldReinsertLivemark(updateInfo);
    if (BookmarkSyncLog.level <= Log.Level.Debug) {
      let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
      BookmarkSyncLog.debug(`updateSyncBookmark: Local ${
        oldSyncId} and remote ${
        updateInfo.syncId} livemarks have different URLs`);
    }
  }

  if (shouldReinsert) {
    if (!updateInfo.hasOwnProperty("dateAdded")) {
      updateInfo.dateAdded = oldBookmarkItem.dateAdded.getTime();
    }
    let newInfo = validateNewBookmark("BookmarkSyncUtils: reinsert",
                                      updateInfo);
    await PlacesUtils.bookmarks.remove({
      guid,
      source: SOURCE_SYNC,
    });
    // A reinsertion likely indicates a confused client, since there aren't
    // public APIs for changing livemark URLs or an item's kind (e.g., turning
    // a folder into a separator while preserving its annos and position).
    // This might be a good case to repair later; for now, we assume Sync has
    // passed a complete record for the new item, and don't try to merge
    // `oldBookmarkItem` with `updateInfo`.
    return insertSyncBookmark(db, newInfo);
  }

  let isOrphan = false, requestedParentSyncId;
  if (updateInfo.hasOwnProperty("parentSyncId")) {
    requestedParentSyncId = updateInfo.parentSyncId;
    let oldParentSyncId =
      BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.parentGuid);
    if (requestedParentSyncId != oldParentSyncId) {
      let oldId = await PlacesUtils.promiseItemId(oldBookmarkItem.guid);
      if (PlacesUtils.isRootItem(oldId)) {
        throw new Error(`Cannot move Places root ${oldId}`);
      }
      let requestedParentGuid =
        BookmarkSyncUtils.syncIdToGuid(requestedParentSyncId);
      isOrphan = await GUIDMissing(requestedParentGuid);
      if (!isOrphan) {
        BookmarkSyncLog.debug(`updateSyncBookmark: Item ${
          updateInfo.syncId} is not an orphan`);
      } else {
        // Don't move the item if the new parent doesn't exist. Instead, mark
        // the item as an orphan. We'll annotate it with its real parent after
        // updating.
        BookmarkSyncLog.trace(`updateSyncBookmark: Item ${
          updateInfo.syncId} is an orphan: could not find parent ${
          requestedParentSyncId}`);
        delete updateInfo.parentSyncId;
      }
    } else {
      // If the parent is the same, just omit it so that `update` doesn't do
      // extra work.
      delete updateInfo.parentSyncId;
    }
  }

  updateInfo = await updateTagQueryFolder(db, updateInfo);

  let bookmarkInfo = syncBookmarkToPlacesBookmark(updateInfo);
  let newBookmarkItem = shouldUpdateBookmark(bookmarkInfo) ?
                        await PlacesUtils.bookmarks.update(bookmarkInfo) :
                        oldBookmarkItem;
  let newItem = await updateBookmarkMetadata(db, oldBookmarkItem,
                                             newBookmarkItem, updateInfo);

  // If the item is an orphan, annotate it with its real parent sync ID.
  if (isOrphan) {
    await annotateOrphan(newItem, requestedParentSyncId);
  }

  // Reparent all orphans that expect this folder as the parent.
  await reparentOrphans(db, newItem);

  return newItem;
}

// Updates tags, keywords, and annotations for an existing bookmark. Returns a
// Sync bookmark object.
async function updateBookmarkMetadata(db, oldBookmarkItem,
                                      newBookmarkItem,
                                      updateInfo) {
  let itemId = await PlacesUtils.promiseItemId(newBookmarkItem.guid);
  let newItem = await placesBookmarkToSyncBookmark(db, newBookmarkItem);

  try {
    newItem.tags = tagItem(newBookmarkItem, updateInfo.tags);
  } catch (ex) {
    BookmarkSyncLog.warn(`updateBookmarkMetadata: Error tagging item ${
      updateInfo.syncId}`, ex);
  }

  if (updateInfo.hasOwnProperty("keyword")) {
    // Unconditionally remove the old keyword.
    await removeConflictingKeywords(oldBookmarkItem.url, updateInfo.keyword);
    if (updateInfo.keyword) {
      await PlacesUtils.keywords.insert({
        keyword: updateInfo.keyword,
        url: newItem.url.href,
        source: SOURCE_SYNC,
      });
    }
    newItem.keyword = updateInfo.keyword;
  }

  if (updateInfo.hasOwnProperty("description")) {
    if (updateInfo.description) {
      PlacesUtils.annotations.setItemAnnotation(itemId,
        BookmarkSyncUtils.DESCRIPTION_ANNO, updateInfo.description, 0,
        PlacesUtils.annotations.EXPIRE_NEVER,
        SOURCE_SYNC);
    } else {
      PlacesUtils.annotations.removeItemAnnotation(itemId,
        BookmarkSyncUtils.DESCRIPTION_ANNO, SOURCE_SYNC);
    }
    newItem.description = updateInfo.description;
  }

  if (updateInfo.hasOwnProperty("loadInSidebar")) {
    if (updateInfo.loadInSidebar) {
      PlacesUtils.annotations.setItemAnnotation(itemId,
        BookmarkSyncUtils.SIDEBAR_ANNO, updateInfo.loadInSidebar, 0,
        PlacesUtils.annotations.EXPIRE_NEVER,
        SOURCE_SYNC);
    } else {
      PlacesUtils.annotations.removeItemAnnotation(itemId,
        BookmarkSyncUtils.SIDEBAR_ANNO, SOURCE_SYNC);
    }
    newItem.loadInSidebar = updateInfo.loadInSidebar;
  }

  if (updateInfo.hasOwnProperty("query")) {
    PlacesUtils.annotations.setItemAnnotation(itemId,
      BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, updateInfo.query, 0,
      PlacesUtils.annotations.EXPIRE_NEVER,
      SOURCE_SYNC);
    newItem.query = updateInfo.query;
  }

  return newItem;
}

function validateNewBookmark(name, info) {
  let insertInfo = validateSyncBookmarkObject(name, info,
    { kind: { required: true },
      syncId: { required: true },
      url: { requiredIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                                BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind),
            validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                            BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) },
      parentSyncId: { required: true },
      title: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                               BookmarkSyncUtils.KINDS.QUERY,
                               BookmarkSyncUtils.KINDS.FOLDER,
                               BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) ||
                             b.title === "" },
      query: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY },
      folder: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY },
      tags: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                              BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) },
      keyword: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                                 BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) },
      description: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                                     BookmarkSyncUtils.KINDS.QUERY,
                                     BookmarkSyncUtils.KINDS.FOLDER,
                                     BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) },
      loadInSidebar: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK,
                                       BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) },
      feed: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK },
      site: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK },
      dateAdded: { required: false }
    });

  return insertInfo;
}

async function fetchGuidsWithAnno(db, anno, val) {
  let rows = await db.executeCached(`
    SELECT b.guid FROM moz_items_annos a
    JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
    JOIN moz_bookmarks b ON b.id = a.item_id
    WHERE n.name = :anno AND
          a.content = :val`,
    { anno, val });
  return rows.map(row => row.getResultByName("guid"));
}

// Returns the value of an item's annotation, or `null` if it's not set.
async function getAnno(db, guid, anno) {
  let rows = await db.executeCached(`
    SELECT a.content FROM moz_items_annos a
    JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
    JOIN moz_bookmarks b ON b.id = a.item_id
    WHERE b.guid = :guid AND
          n.name = :anno`,
    { guid, anno });
  return rows.length ? rows[0].getResultByName("content") : null;
}

function tagItem(item, tags) {
  if (!item.url) {
    return [];
  }

  // Remove leading and trailing whitespace, then filter out empty tags.
  let newTags = tags ? tags.map(tag => tag.trim()).filter(Boolean) : [];

  // Removing the last tagged item will also remove the tag. To preserve
  // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
  let dummyURI = PlacesUtils.toURI("about:weave#BStore_tagURI");
  let bookmarkURI = PlacesUtils.toURI(item.url.href);
  if (newTags && newTags.length > 0)
    PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
  PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
  if (newTags && newTags.length > 0)
    PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
  PlacesUtils.tagging.untagURI(dummyURI, null, SOURCE_SYNC);

  return newTags;
}

// `PlacesUtils.bookmarks.update` checks if we've supplied enough properties,
// but doesn't know about additional livemark properties. We check this to avoid
// having it throw in case we only pass properties like `{ guid, feedURI }`.
function shouldUpdateBookmark(bookmarkInfo) {
  return bookmarkInfo.hasOwnProperty("parentGuid") ||
         bookmarkInfo.hasOwnProperty("title") ||
         bookmarkInfo.hasOwnProperty("url");
}

// Returns the folder ID for `tag`, or `null` if the tag doesn't exist.
async function getTagFolder(db, tag) {
  let results = await db.executeCached(`
    SELECT id
    FROM moz_bookmarks
    WHERE type = :type AND
          parent = :tagsFolderId AND
          title = :tag`,
    { type: PlacesUtils.bookmarks.TYPE_FOLDER,
      tagsFolderId: PlacesUtils.tagsFolderId, tag });
  return results.length ? results[0].getResultByName("id") : null;
}

// Returns the folder ID for `tag`, creating one if it doesn't exist.
async function getOrCreateTagFolder(db, tag) {
  let id = await getTagFolder(db, tag);
  if (id) {
    return id;
  }
  // Create the tag if it doesn't exist.
  let item = await PlacesUtils.bookmarks.insert({
    type: PlacesUtils.bookmarks.TYPE_FOLDER,
    parentGuid: PlacesUtils.bookmarks.tagsGuid,
    title: tag,
    source: SOURCE_SYNC,
  });
  return PlacesUtils.promiseItemId(item.guid);
}

// Converts a Places bookmark or livemark to a Sync bookmark. This function
// maps Places GUIDs to sync IDs and filters out extra Places properties like
// date added, last modified, and index.
async function placesBookmarkToSyncBookmark(db, bookmarkItem) {
  let item = {};

  for (let prop in bookmarkItem) {
    switch (prop) {
      // Sync IDs are identical to Places GUIDs for all items except roots.
      case "guid":
        item.syncId = BookmarkSyncUtils.guidToSyncId(bookmarkItem.guid);
        break;

      case "parentGuid":
        item.parentSyncId =
          BookmarkSyncUtils.guidToSyncId(bookmarkItem.parentGuid);
        break;

      // Sync uses kinds instead of types, which distinguish between folders,
      // livemarks, bookmarks, and queries.
      case "type":
        item.kind = await getKindForItem(db, bookmarkItem);
        break;

      case "title":
      case "url":
        item[prop] = bookmarkItem[prop];
        break;

      case "dateAdded":
        item[prop] = new Date(bookmarkItem[prop]).getTime();
        break;

      // Livemark objects contain additional properties. The feed URL is
      // required; the site URL is optional.
      case "feedURI":
        item.feed = new URL(bookmarkItem.feedURI.spec);
        break;

      case "siteURI":
        if (bookmarkItem.siteURI) {
          item.site = new URL(bookmarkItem.siteURI.spec);
        }
        break;
    }
  }

  return item;
}

// Converts a Sync bookmark object to a Places bookmark or livemark object.
// This function maps sync IDs to Places GUIDs, and filters out extra Sync
// properties like keywords, tags, and descriptions. Returns an object that can
// be passed to `PlacesUtils.livemarks.addLivemark` or
// `PlacesUtils.bookmarks.{insert, update}`.
function syncBookmarkToPlacesBookmark(info) {
  let bookmarkInfo = {
    source: SOURCE_SYNC,
  };

  for (let prop in info) {
    switch (prop) {
      case "kind":
        bookmarkInfo.type = getTypeForKind(info.kind);
        break;

      // Convert sync IDs to Places GUIDs for roots.
      case "syncId":
        bookmarkInfo.guid = BookmarkSyncUtils.syncIdToGuid(info.syncId);
        break;

      case "dateAdded":
        bookmarkInfo.dateAdded = new Date(info.dateAdded);
        break;

      case "parentSyncId":
        bookmarkInfo.parentGuid =
          BookmarkSyncUtils.syncIdToGuid(info.parentSyncId);
        // Instead of providing an index, Sync reorders children at the end of
        // the sync using `BookmarkSyncUtils.order`. We explicitly specify the
        // default index here to prevent `PlacesUtils.bookmarks.update` and
        // `PlacesUtils.livemarks.addLivemark` from throwing.
        bookmarkInfo.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
        break;

      case "title":
      case "url":
        bookmarkInfo[prop] = info[prop];
        break;

      // Livemark-specific properties.
      case "feed":
        bookmarkInfo.feedURI = PlacesUtils.toURI(info.feed);
        break;

      case "site":
        if (info.site) {
          bookmarkInfo.siteURI = PlacesUtils.toURI(info.site);
        }
        break;
    }
  }

  return bookmarkInfo;
}

// Creates and returns a Sync bookmark object containing the bookmark's
// tags, keyword, description, and whether it loads in the sidebar.
var fetchBookmarkItem = async function(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (!item.title) {
    item.title = "";
  }

  item.tags = PlacesUtils.tagging.getTagsForURI(
    PlacesUtils.toURI(bookmarkItem.url), {});

  let keywordEntry = await PlacesUtils.keywords.fetch({
    url: bookmarkItem.url,
  });
  if (keywordEntry) {
    item.keyword = keywordEntry.keyword;
  }

  let description = await getAnno(db, bookmarkItem.guid,
                                  BookmarkSyncUtils.DESCRIPTION_ANNO);
  if (description) {
    item.description = description;
  }

  item.loadInSidebar = !!(await getAnno(db, bookmarkItem.guid,
                                        BookmarkSyncUtils.SIDEBAR_ANNO));

  return item;
};

// Creates and returns a Sync bookmark object containing the folder's
// description and children.
async function fetchFolderItem(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (!item.title) {
    item.title = "";
  }

  let description = await getAnno(db, bookmarkItem.guid,
                                  BookmarkSyncUtils.DESCRIPTION_ANNO);
  if (description) {
    item.description = description;
  }

  let childGuids = await fetchChildGuids(db, bookmarkItem.guid);
  item.childSyncIds = childGuids.map(guid =>
    BookmarkSyncUtils.guidToSyncId(guid)
  );

  return item;
}

// Creates and returns a Sync bookmark object containing the livemark's
// description, children (none), feed URI, and site URI.
async function fetchLivemarkItem(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (!item.title) {
    item.title = "";
  }

  let description = await getAnno(db, bookmarkItem.guid,
                                  BookmarkSyncUtils.DESCRIPTION_ANNO);
  if (description) {
    item.description = description;
  }

  let feedAnno = await getAnno(db, bookmarkItem.guid,
                               PlacesUtils.LMANNO_FEEDURI);
  item.feed = new URL(feedAnno);

  let siteAnno = await getAnno(db, bookmarkItem.guid,
                               PlacesUtils.LMANNO_SITEURI);
  if (siteAnno) {
    item.site = new URL(siteAnno);
  }

  return item;
}

// Creates and returns a Sync bookmark object containing the query's tag
// folder name and smart bookmark query ID.
async function fetchQueryItem(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  let description = await getAnno(db, bookmarkItem.guid,
                                  BookmarkSyncUtils.DESCRIPTION_ANNO);
  if (description) {
    item.description = description;
  }

  let folder = null;
  let params = new URLSearchParams(bookmarkItem.url.pathname);
  let tagFolderId = +params.get("folder");
  if (tagFolderId) {
    try {
      let tagFolderGuid = await PlacesUtils.promiseItemGuid(tagFolderId);
      let tagFolder = await PlacesUtils.bookmarks.fetch(tagFolderGuid);
      folder = tagFolder.title;
    } catch (ex) {
      BookmarkSyncLog.warn("fetchQueryItem: Query " + bookmarkItem.url.href +
                           " points to nonexistent folder " + tagFolderId, ex);
    }
  }
  if (folder != null) {
    item.folder = folder;
  }

  let query = await getAnno(db, bookmarkItem.guid,
                            BookmarkSyncUtils.SMART_BOOKMARKS_ANNO);
  if (query) {
    item.query = query;
  }

  return item;
}

function addRowToChangeRecords(row, changeRecords) {
  let syncId = BookmarkSyncUtils.guidToSyncId(row.getResultByName("guid"));
  let modifiedAsPRTime = row.getResultByName("modified");
  let modified = modifiedAsPRTime / MICROSECONDS_PER_SECOND;
  if (Number.isNaN(modified) || modified <= 0) {
    BookmarkSyncLog.error("addRowToChangeRecords: Invalid modified date for " +
                          syncId, modifiedAsPRTime);
    modified = 0;
  }
  changeRecords[syncId] = {
    modified,
    counter: row.getResultByName("syncChangeCounter"),
    status: row.getResultByName("syncStatus"),
    tombstone: !!row.getResultByName("tombstone"),
    synced: false,
  };
}

/**
 * Queries the database for synced bookmarks and tombstones, and returns a
 * changeset for the Sync bookmarks engine.
 *
 * @param db
 *        The Sqlite.jsm connection handle.
 * @return {Promise} resolved once all items have been fetched.
 * @resolves to an object containing records for changed bookmarks, keyed by
 *           the sync ID.
 */
var pullSyncChanges = async function(db) {
  let changeRecords = {};

  await db.executeCached(`
    WITH RECURSIVE
    syncedItems(id, guid, modified, syncChangeCounter, syncStatus) AS (
      SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
       FROM moz_bookmarks b
       WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                        'mobile______')
      UNION ALL
      SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
      FROM moz_bookmarks b
      JOIN syncedItems s ON b.parent = s.id
    )
    SELECT guid, modified, syncChangeCounter, syncStatus, 0 AS tombstone
    FROM syncedItems
    WHERE syncChangeCounter >= 1
    UNION ALL
    SELECT guid, dateRemoved AS modified, 1 AS syncChangeCounter,
           :deletedSyncStatus, 1 AS tombstone
    FROM moz_bookmarks_deleted`,
    { deletedSyncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL },
    row => addRowToChangeRecords(row, changeRecords));

  return changeRecords;
};

var touchSyncBookmark = async function(db, bookmarkItem) {
  if (BookmarkSyncLog.level <= Log.Level.Trace) {
    BookmarkSyncLog.trace(
      `touch: Reviving item "${bookmarkItem.guid}" and marking parent ` +
      BookmarkSyncUtils.guidToSyncId(bookmarkItem.parentGuid) + ` as modified`);
  }

  // Bump the change counter of the item and its parent, so that we upload
  // both.
  await db.executeCached(`
    UPDATE moz_bookmarks SET
      syncChangeCounter = syncChangeCounter + 1
    WHERE guid IN (:guid, :parentGuid)`,
    { guid: bookmarkItem.guid, parentGuid: bookmarkItem.parentGuid });

  // TODO (Bug 1313890): Refactor the bookmarks engine to pull change records
  // before uploading, instead of returning records to merge into the engine's
  // initial changeset.
  return pullSyncChanges(db);
};

var dedupeSyncBookmark = async function(db, localGuid, remoteGuid,
                                               remoteParentGuid) {
  let rows = await db.executeCached(`
    SELECT b.id, b.type, p.id AS parentId, p.guid AS parentGuid, b.syncStatus
    FROM moz_bookmarks b
    JOIN moz_bookmarks p ON p.id = b.parent
    WHERE b.guid = :localGuid`,
    { localGuid });
  if (!rows.length) {
    throw new Error(`Local item ${localGuid} does not exist`);
  }

  let localId = rows[0].getResultByName("id");
  let localParentId = rows[0].getResultByName("parentId");
  let bookmarkType = rows[0].getResultByName("type");
  if (PlacesUtils.isRootItem(localId)) {
    throw new Error(`Cannot de-dupe local root ${localGuid}`);
  }

  let localParentGuid = rows[0].getResultByName("parentGuid");
  let sameParent = localParentGuid == remoteParentGuid;
  let modified = PlacesUtils.toPRTime(Date.now());

  await db.executeTransaction(async function() {
    // Change the item's old GUID to the new remote GUID. This will throw a
    // constraint error if the remote GUID already exists locally.
    BookmarkSyncLog.debug("dedupeSyncBookmark: Switching local GUID " +
                          localGuid + " to incoming GUID " + remoteGuid);
    await db.executeCached(`UPDATE moz_bookmarks
      SET guid = :remoteGuid
      WHERE id = :localId`,
      { remoteGuid, localId });
    PlacesUtils.invalidateCachedGuidFor(localId);

    // And mark the parent as being modified. Given we de-dupe based on the
    // parent *name* it's possible the item having its GUID changed has a
    // different parent from the incoming record.
    // So we need to return a change record for the parent, and bump its
    // counter to ensure we don't lose the change if the current sync is
    // interrupted.
    await db.executeCached(`UPDATE moz_bookmarks
      SET syncChangeCounter = syncChangeCounter + 1
      WHERE guid = :localParentGuid`,
      { localParentGuid });

    // And we also add the parent as reflected in the incoming record as the
    // de-dupe process might have used an existing item in a different folder.
    // This statement is a no-op if we don't have the new parent yet, but that's
    // fine: applying the record will add our special SYNC_PARENT_ANNO
    // annotation and move it to unfiled. If the parent arrives in the future
    // (either this Sync or a later one), the item will be reparented. Note that
    // this scenario will still leave us with inconsistent client and server
    // states; the incoming record on the server references a parent that isn't
    // the actual parent locally - see bug 1297955.
    if (!sameParent) {
      await db.executeCached(`UPDATE moz_bookmarks
        SET syncChangeCounter = syncChangeCounter + 1
        WHERE guid = :remoteParentGuid`,
        { remoteParentGuid });
    }

    // The local, duplicate ID is always deleted on the server - but for
    // bookmarks it is a logical delete.
    let localSyncStatus = rows[0].getResultByName("syncStatus");
    if (localSyncStatus == PlacesUtils.bookmarks.SYNC_STATUS.NORMAL) {
      await db.executeCached(`
        INSERT INTO moz_bookmarks_deleted (guid, dateRemoved)
        VALUES (:localGuid, :modified)`,
        { localGuid, modified });
    }
  });

  let observers = PlacesUtils.bookmarks.getObservers();
  notify(observers, "onItemChanged", [ localId, "guid", false,
                                       remoteGuid,
                                       modified,
                                       bookmarkType,
                                       localParentId,
                                       remoteGuid, remoteParentGuid,
                                       localGuid, SOURCE_SYNC
                                     ]);

  // TODO (Bug 1313890): Refactor the bookmarks engine to pull change records
  // before uploading, instead of returning records to merge into the engine's
  // initial changeset.
  let changeRecords = await pullSyncChanges(db);

  if (BookmarkSyncLog.level <= Log.Level.Debug && !sameParent) {
    let remoteParentSyncId = BookmarkSyncUtils.guidToSyncId(remoteParentGuid);
    if (!changeRecords.hasOwnProperty(remoteParentSyncId)) {
      BookmarkSyncLog.debug("dedupeSyncBookmark: Incoming duplicate item " +
                            remoteGuid + " specifies non-existing parent " +
                            remoteParentGuid);
    }
  }

  return changeRecords;
};

// Moves a synced folder's remaining children to its parent, and deletes the
// folder if it's empty.
async function deleteSyncedFolder(db, bookmarkItem) {
  // At this point, any member in the folder that remains is either a folder
  // pending deletion (which we'll get to in this function), or an item that
  // should not be deleted. To avoid deleting these items, we first move them
  // to the parent of the folder we're about to delete.
  let childGuids = await fetchChildGuids(db, bookmarkItem.guid);
  if (!childGuids.length) {
    // No children -- just delete the folder.
    return deleteSyncedAtom(bookmarkItem);
  }

  if (BookmarkSyncLog.level <= Log.Level.Trace) {
    BookmarkSyncLog.trace(
      `deleteSyncedFolder: Moving ${JSON.stringify(childGuids)} children of ` +
      `"${bookmarkItem.guid}" to grandparent
      "${BookmarkSyncUtils.guidToSyncId(bookmarkItem.parentGuid)}" before ` +
      `deletion`);
  }

  // Move children out of the parent and into the grandparent
  for (let guid of childGuids) {
    await PlacesUtils.bookmarks.update({
      guid,
      parentGuid: bookmarkItem.parentGuid,
      index: PlacesUtils.bookmarks.DEFAULT_INDEX,
      // `SYNC_REPARENT_REMOVED_FOLDER_CHILDREN` bumps the change counter for
      // the child and its new parent, without incrementing the bookmark
      // tracker's score.
      //
      // We intentionally don't check if the child is one we'll remove later,
      // so it's possible we'll bump the change counter of the closest living
      // ancestor when it's not needed. This avoids inconsistency if removal
      // is interrupted, since we don't run this operation in a transaction.
      source: PlacesUtils.bookmarks.SOURCES.SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
    });
  }

  // Delete the (now empty) parent
  try {
    await PlacesUtils.bookmarks.remove(bookmarkItem.guid, {
      preventRemovalOfNonEmptyFolders: true,
      // We don't want to bump the change counter for this deletion, because
      // a tombstone for the folder is already on the server.
      source: SOURCE_SYNC,
    });
  } catch (e) {
    // We failed, probably because someone added something to this folder
    // between when we got the children and now (or the database is corrupt,
    // or something else happened...) This is unlikely, but possible. To
    // avoid corruption in this case, we need to reupload the record to the
    // server.
    //
    // (Ideally this whole operation would be done in a transaction, and this
    // wouldn't be possible).
    BookmarkSyncLog.trace(`deleteSyncedFolder: Error removing parent ` +
                          `${bookmarkItem.guid} after reparenting children`, e);
    return false;
  }

  return true;
}

// Removes a synced bookmark or empty folder from the database.
var deleteSyncedAtom = async function(bookmarkItem) {
  try {
    await PlacesUtils.bookmarks.remove(bookmarkItem.guid, {
      preventRemovalOfNonEmptyFolders: true,
      source: SOURCE_SYNC,
    });
  } catch (ex) {
    // Likely already removed.
    BookmarkSyncLog.trace(`deleteSyncedAtom: Error removing ` +
                          bookmarkItem.guid, ex);
    return false;
  }

  return true;
};

/**
 * Updates the sync status on all "NEW" and "UNKNOWN" bookmarks to "NORMAL".
 *
 * We do this when pulling changes instead of in `pushChanges` to make sure
 * we write tombstones if a new item is deleted after an interrupted sync. (For
 * example, if a "NEW" record is uploaded or reconciled, then the app is closed
 * before Sync calls `pushChanges`).
 */
function markChangesAsSyncing(db, changeRecords) {
  let unsyncedGuids = [];
  for (let syncId in changeRecords) {
    if (changeRecords[syncId].tombstone) {
      continue;
    }
    if (changeRecords[syncId].status ==
        PlacesUtils.bookmarks.SYNC_STATUS.NORMAL) {
      continue;
    }
    let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
    unsyncedGuids.push(JSON.stringify(guid));
  }
  if (!unsyncedGuids.length) {
    return Promise.resolve();
  }
  return db.execute(`
    UPDATE moz_bookmarks
    SET syncStatus = :syncStatus
    WHERE guid IN (${unsyncedGuids.join(",")})`,
    { syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL });
}

/**
 * Removes tombstones for successfully synced items.
 *
 * @return {Promise}
 */
var removeTombstones = function(db, guids) {
  if (!guids.length) {
    return Promise.resolve();
  }
  return db.execute(`
    DELETE FROM moz_bookmarks_deleted
    WHERE guid IN (${guids.map(guid => JSON.stringify(guid)).join(",")})`);
};

/**
 * Sends a bookmarks notification through the given observers.
 *
 * @param observers
 *        array of nsINavBookmarkObserver objects.
 * @param notification
 *        the notification name.
 * @param args
 *        array of arguments to pass to the notification.
 */
function notify(observers, notification, args = []) {
  for (let observer of observers) {
    try {
      observer[notification](...args);
    } catch (ex) {}
  }
}
PK
!<Rmodules/PlacesTransactions.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["PlacesTransactions"];

/**
 * Overview
 * --------
 * This modules serves as the transactions manager for Places (hereinafter PTM).
 * It implements all the elementary transactions for its UI commands: creating
 * items, editing their various properties, and so forth.
 *
 * Note that since the effect of invoking a Places command is not limited to the
 * window in which it was performed (e.g. a folder created in the Library may be
 * the parent of a bookmark created in some browser window), PTM is a singleton.
 * It's therefore unnecessary to initialize PTM in any way apart importing this
 * module.
 *
 * PTM shares most of its semantics with common command pattern implementations.
 * However, the asynchronous design of contemporary and future APIs, combined
 * with the commitment to serialize all UI operations, does make things a little
 * bit different.  For example, when |undo| is called in order to undo the top
 * undo entry, the caller cannot tell for sure what entry would it be, because
 * the execution of some transactions is either in process, or enqueued to be.
 *
 * Also note that unlike the nsITransactionManager, for example, this API is by
 * no means generic.  That is, it cannot be used to execute anything but the
 * elementary transactions implemented here (Please file a bug if you find
 * anything uncovered).  More-complex transactions (e.g. creating a folder and
 * moving a bookmark into it) may be implemented as a batch (see below).
 *
 * A note about GUIDs and item-ids
 * -------------------------------
 * There's an ongoing effort (see bug 1071511) to deprecate item-ids in Places
 * in favor of GUIDs.  Both because new APIs (e.g. Bookmark.jsm) expose them to
 * the minimum necessary, and because GUIDs play much better with implementing
 * |redo|, this API doesn't support item-ids at all, and only accepts bookmark
 * GUIDs, both for input (e.g. for setting the parent folder for a new bookmark)
 * and for output (when the GUID for such a bookmark is propagated).
 *
 * When working in conjugation with older Places API which only expose item ids,
 * use PlacesUtils.promiseItemGuid for converting those to GUIDs (note that
 * for result nodes, the guid is available through their bookmarkGuid getter).
 * Should you need to convert GUIDs to item-ids, use PlacesUtils.promiseItemId.
 *
 * Constructing transactions
 * -------------------------
 * At the bottom of this module you will find transactions for all Places UI
 * commands.  They are exposed as constructors set on the PlacesTransactions
 * object (e.g. PlacesTransactions.NewFolder).  The input for this constructors
 * is taken in the form of a single argument, a plain object consisting of the
 * properties for the transaction.  Input properties may be either required or
 * optional (for example, |keyword| is required for the EditKeyword transaction,
 * but optional for the NewBookmark transaction).
 *
 * To make things simple, a given input property has the same basic meaning and
 * valid values across all transactions which accept it in the input object.
 * Here is a list of all supported input properties along with their expected
 * values:
 *  - url: a URL object, an nsIURI object, or a href.
 *  - urls: an array of urls, as above.
 *  - feedUrl: an url (as above), holding the url for a live bookmark.
 *  - siteUrl an url (as above), holding the url for the site with which
 *            a live bookmark is associated.
 *  - tag - a string.
 *  - tags: an array of strings.
 *  - guid, parentGuid, newParentGuid: a valid Places GUID string.
 *  - guids: an array of valid Places GUID strings.
 *  - title: a string
 *  - index, newIndex: the position of an item in its containing folder,
 *    starting from 0.
 *    integer and PlacesUtils.bookmarks.DEFAULT_INDEX
 *  - annotation: see PlacesUtils.setAnnotationsForItem
 *  - annotations: an array of annotation objects as above.
 *  - excludingAnnotation: a string (annotation name).
 *  - excludingAnnotations: an array of string (annotation names).
 *
 * If a required property is missing in the input object (e.g. not specifying
 * parentGuid for NewBookmark), or if the value for any of the input properties
 * is invalid "on the surface" (e.g. a numeric value for GUID, or a string that
 * isn't 12-characters long), the transaction constructor throws right way.
 * More complex errors (e.g. passing a non-existent GUID for parentGuid) only
 * reveal once the transaction is executed.
 *
 * Executing Transactions (the |transact| method of transactions)
 * --------------------------------------------------------------
 * Once a transaction is created, you must call its |transact| method for it to
 * be executed and take effect.  |transact| is an asynchronous method that takes
 * no arguments, and returns a promise that resolves once the transaction is
 * executed.  Executing one of the transactions for creating items (NewBookmark,
 * NewFolder, NewSeparator or NewLivemark) resolve to the new item's GUID.
 * There's no resolution value for other transactions.
 * If a transaction fails to execute, |transact| rejects and the transactions
 * history is not affected.
 *
 * |transact| throws if it's called more than once (successfully or not) on the
 * same transaction object.
 *
 * Batches
 * -------
 * Sometimes it is useful to "batch" or "merge" transactions.  For example,
 * something like "Bookmark All Tabs" may be implemented as one NewFolder
 * transaction followed by numerous NewBookmark transactions - all to be undone
 * or redone in a single undo or redo command.  Use |PlacesTransactions.batch|
 * in such cases.  It can take either an array of transactions which will be
 * executed in the given order and later be treated a a single entry in the
 * transactions history, or a generator function that is passed to Task.spawn,
 * that is to "contain" the batch: once the generator function is called a batch
 * starts, and it lasts until the asynchronous generator iteration is complete
 * All transactions executed by |transact| during this time are to be treated as
 * a single entry in the transactions history.
 *
 * In both modes, |PlacesTransactions.batch| returns a promise that is to be
 * resolved when the batch ends.  In the array-input mode, there's no resolution
 * value.  In the generator mode, the resolution value is whatever the generator
 * function returned (the semantics are the same as in Task.spawn, basically).
 *
 * The array-input mode of |PlacesTransactions.batch| is useful for implementing
 * a batch of mostly-independent transaction (for example, |paste| into a folder
 * can be implemented as a batch of multiple NewBookmark transactions).
 * The generator mode is useful when the resolution value of executing one
 * transaction is the input of one more subsequent transaction.
 *
 * In the array-input mode, if any transactions fails to execute, the batch
 * continues (exceptions are logged).  Only transactions that were executed
 * successfully are added to the transactions history.
 *
 * WARNING: "nested" batches are not supported, if you call batch while another
 * batch is still running, the new batch is enqueued with all other PTM work
 * and thus not run until the running batch ends. The same goes for undo, redo
 * and clearTransactionsHistory (note batches cannot be done partially, meaning
 * undo and redo calls that during a batch are just enqueued).
 *
 * *****************************************************************************
 * IT'S PARTICULARLY IMPORTANT NOT TO await ANY PROMISE RETURNED BY ANY OF
 * THESE METHODS (undo, redo, clearTransactionsHistory) FROM A BATCH FUNCTION.
 * UNTIL WE FIND A WAY TO THROW IN THAT CASE (SEE BUG 1091446) DOING SO WILL
 * COMPLETELY BREAK PTM UNTIL SHUTDOWN, NOT ALLOWING THE EXECUTION OF ANY
 * TRANSACTION!
 * *****************************************************************************
 *
 * Serialization
 * -------------
 * All |PlacesTransaction| operations are serialized.  That is, even though the
 * implementation is asynchronous, the order in which PlacesTransactions methods
 * is called does guarantee the order in which they are to be invoked.
 *
 * The only exception to this rule is |transact| calls done during a batch (see
 * above).  |transact| calls are serialized with each other (and with undo, redo
 * and clearTransactionsHistory), but they  are, of course, not serialized with
 * batches.
 *
 * The transactions-history structure
 * ----------------------------------
 * The transactions-history is a two-dimensional stack of transactions: the
 * transactions are ordered in reverse to the order they were committed.
 * It's two-dimensional because PTM allows batching transactions together for
 * the purpose of undo or redo (see Batches above).
 *
 * The undoPosition property is set to the index of the top entry. If there is
 * no entry at that index, there is nothing to undo.
 * Entries prior to undoPosition, if any, are redo entries, the first one being
 * the top redo entry.
 *
 * [ [2nd redo txn, 1st redo txn],  <= 2nd redo entry
 *   [2nd redo txn, 1st redo txn],  <= 1st redo entry
 *   [1st undo txn, 2nd undo txn],  <= 1st undo entry
 *   [1st undo txn, 2nd undo txn]   <= 2nd undo entry ]
 * undoPostion: 2.
 *
 * Note that when a new entry is created, all redo entries are removed.
 */

const { utils: Cu, classes: Cc, interfaces: Ci } = Components;

const TRANSACTIONS_QUEUE_TIMEOUT_MS = 240000; // 4 Mins.

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
                                  "resource://gre/modules/Console.jsm");

Cu.importGlobalProperties(["URL"]);

function setTimeout(callback, ms) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(callback, ms, timer.TYPE_ONE_SHOT);
}

class TransactionsHistoryArray extends Array {
  constructor() {
    super();

    // The index of the first undo entry (if any) - See the documentation
    // at the top of this file.
    this._undoPosition = 0;
    // Outside of this module, the API of transactions is inaccessible, and so
    // are any internal properties.  To achieve that, transactions are proxified
    // in their constructors.  This maps the proxies to their respective raw
    // objects.
    this.proxifiedToRaw = new WeakMap();
  }

  get undoPosition() {
    return this._undoPosition;
  }

  // Handy shortcuts
  get topUndoEntry() {
    return this.undoPosition < this.length ? this[this.undoPosition] : null;
  }
  get topRedoEntry() {
    return this.undoPosition > 0 ? this[this.undoPosition - 1] : null;
  }

  /**
   * Proxify a transaction object for consumers.
   * @param rawTransaction
   *        the raw transaction object.
   * @return the proxified transaction object.
   * @see getRawTransaction for retrieving the raw transaction.
   */
  proxifyTransaction(rawTransaction) {
    let proxy = Object.freeze({
      transact() {
        return TransactionsManager.transact(this);
      }
    });
    this.proxifiedToRaw.set(proxy, rawTransaction);
    return proxy;
  }

  /**
   * Check if the given object is a the proxy object for some transaction.
   * @param aValue
   *        any JS value.
   * @return true if aValue is the proxy object for some transaction, false
   * otherwise.
   */
  isProxifiedTransactionObject(value) {
    return this.proxifiedToRaw.has(value);
  }

  /**
   * Get the raw transaction for the given proxy.
   * @param aProxy
   *        the proxy object
   * @return the transaction proxified by aProxy; |undefined| is returned if
   * aProxy is not a proxified transaction.
   */
  getRawTransaction(proxy) {
    return this.proxifiedToRaw.get(proxy);
  }

  /**
   * Add a transaction either as a new entry, if forced or if there are no undo
   * entries, or to the top undo entry.
   *
   * @param aProxifiedTransaction
   *        the proxified transaction object to be added to the transaction
   *        history.
   * @param [optional] aForceNewEntry
   *        Force a new entry for the transaction. Default: false.
   *        If false, an entry will we created only if there's no undo entry
   *        to extend.
   */
  add(proxifiedTransaction, forceNewEntry = false) {
    if (!this.isProxifiedTransactionObject(proxifiedTransaction))
      throw new Error("aProxifiedTransaction is not a proxified transaction");

    if (this.length == 0 || forceNewEntry) {
      this.clearRedoEntries();
      this.unshift([proxifiedTransaction]);
    } else {
      this[this.undoPosition].unshift(proxifiedTransaction);
    }
  }

  /**
   * Clear all undo entries.
   */
  clearUndoEntries() {
    if (this.undoPosition < this.length)
      this.splice(this.undoPosition);
  }

  /**
   * Clear all redo entries.
   */
  clearRedoEntries() {
    if (this.undoPosition > 0) {
      this.splice(0, this.undoPosition);
      this._undoPosition = 0;
    }
  }

  /**
   * Clear all entries.
   */
  clearAllEntries() {
    if (this.length > 0) {
      this.splice(0);
      this._undoPosition = 0;
    }
  }
}

XPCOMUtils.defineLazyGetter(this, "TransactionsHistory",
                            () => new TransactionsHistoryArray());

var PlacesTransactions = {
  /**
   * @see Batches in the module documentation.
   */
  batch(transactionsToBatch) {
    if (Array.isArray(transactionsToBatch)) {
      if (transactionsToBatch.length == 0)
        throw new Error("Must pass a non-empty array");

      if (transactionsToBatch.some(
           o => !TransactionsHistory.isProxifiedTransactionObject(o))) {
        throw new Error("Must pass only transaction entries");
      }
      return TransactionsManager.batch(async function() {
        for (let txn of transactionsToBatch) {
          try {
            await txn.transact();
          } catch (ex) {
            console.error(ex);
          }
        }
      });
    }
    if (typeof(transactionsToBatch) == "function") {
      return TransactionsManager.batch(transactionsToBatch);
    }

    throw new Error("Must pass either a function or a transactions array");
  },

  /**
   * Asynchronously undo the transaction immediately after the current undo
   * position in the transactions history in the reverse order, if any, and
   * adjusts the undo position.
   *
   * @return {Promises).  The promise always resolves.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  undo() {
    return TransactionsManager.undo();
  },

  /**
   * Asynchronously redo the transaction immediately before the current undo
   * position in the transactions history, if any, and adjusts the undo
   * position.
   *
   * @return {Promises).  The promise always resolves.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  redo() {
    return TransactionsManager.redo();
  },

  /**
   * Asynchronously clear the undo, redo, or all entries from the transactions
   * history.
   *
   * @param [optional] undoEntries
   *        Whether or not to clear undo entries.  Default: true.
   * @param [optional] redoEntries
   *        Whether or not to clear undo entries.  Default: true.
   *
   * @return {Promises).  The promise always resolves.
   * @throws if both aUndoEntries and aRedoEntries are false.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  clearTransactionsHistory(undoEntries = true, redoEntries = true) {
    return TransactionsManager.clearTransactionsHistory(undoEntries, redoEntries);
  },

  /**
   * The numbers of entries in the transactions history.
   */
  get length() {
    return TransactionsHistory.length;
  },

  /**
   * Get the transaction history entry at a given index.  Each entry consists
   * of one or more transaction objects.
   *
   * @param index
   *        the index of the entry to retrieve.
   * @return an array of transaction objects in their undo order (that is,
   * reversely to the order they were executed).
   * @throw if aIndex is invalid (< 0 or >= length).
   * @note the returned array is a clone of the history entry and is not
   * kept in sync with the original entry if it changes.
   */
  entry(index) {
    if (!Number.isInteger(index) || index < 0 || index >= this.length)
      throw new Error("Invalid index");

    return TransactionsHistory[index];
  },

  /**
   * The index of the top undo entry in the transactions history.
   * If there are no undo entries, it equals to |length|.
   * Entries past this point
   * Entries at and past this point are redo entries.
   */
  get undoPosition() {
    return TransactionsHistory.undoPosition;
  },

  /**
   * Shortcut for accessing the top undo entry in the transaction history.
   */
  get topUndoEntry() {
    return TransactionsHistory.topUndoEntry;
  },

  /**
   * Shortcut for accessing the top redo entry in the transaction history.
   */
  get topRedoEntry() {
    return TransactionsHistory.topRedoEntry;
  }
};

/**
 * Helper for serializing the calls to TransactionsManager methods. It allows
 * us to guarantee that the order in which TransactionsManager asynchronous
 * methods are called also enforces the order in which they're executed, and
 * that they are never executed in parallel.
 *
 * In other words: Enqueuer.enqueue(aFunc1); Enqueuer.enqueue(aFunc2) is roughly
 * the same as Task.spawn(aFunc1).then(Task.spawn(aFunc2)).
 */
function Enqueuer() {
  this._promise = Promise.resolve();
}
Enqueuer.prototype = {
  /**
   * Spawn a functions once all previous functions enqueued are done running,
   * and all promises passed to alsoWaitFor are no longer pending.
   *
   * @param   func
   *          a function returning a promise.
   * @return  a promise that resolves once aFunc is done running. The promise
   *          "mirrors" the promise returned by aFunc.
   */
  enqueue(func) {
    // If a transaction awaits on a never resolved promise, or is mistakenly
    // nested, it could hang the transactions queue forever.  Thus we timeout
    // the execution after a meaningful amount of time, to ensure in any case
    // we'll proceed after a while.
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error("PlacesTransaction timeout, most likely caused by unresolved pending work.")),
                 TRANSACTIONS_QUEUE_TIMEOUT_MS);
    });
    let promise = this._promise.then(() => Promise.race([func(), timeoutPromise]));

    // Propagate exceptions to the caller, but dismiss them internally.
    this._promise = promise.catch(console.error);
    return promise;
  },

  /**
   * Same as above, but for a promise returned by a function that already run.
   * This is useful, for example, for serializing transact calls with undo calls,
   * even though transact has its own Enqueuer.
   *
   * @param otherPromise
   *        any promise.
   */
  alsoWaitFor(otherPromise) {
    // We don't care if aPromise resolves or rejects, but just that is not
    // pending anymore.
    // If a transaction awaits on a never resolved promise, or is mistakenly
    // nested, it could hang the transactions queue forever.  Thus we timeout
    // the execution after a meaningful amount of time, to ensure in any case
    // we'll proceed after a while.
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error("PlacesTransaction timeout, most likely caused by unresolved pending work.")),
                 TRANSACTIONS_QUEUE_TIMEOUT_MS);
    });
    let promise = Promise.race([otherPromise, timeoutPromise])
                         .catch(console.error);
    this._promise = Promise.all([this._promise, promise]);
  },

  /**
   * The promise for this queue.
   */
  get promise() {
    return this._promise;
  }
};

var TransactionsManager = {
  // See the documentation at the top of this file. |transact| calls are not
  // serialized with |batch| calls.
  _mainEnqueuer: new Enqueuer(),
  _transactEnqueuer: new Enqueuer(),

  // Is a batch in progress? set when we enter a batch function and unset when
  // it's execution is done.
  _batching: false,

  // If a batch started, this indicates if we've already created an entry in the
  // transactions history for the batch (i.e. if at least one transaction was
  // executed successfully).
  _createdBatchEntry: false,

  // Transactions object should never be recycled (that is, |execute| should
  // only be called once (or not at all) after they're constructed.
  // This keeps track of all transactions which were executed.
  _executedTransactions: new WeakSet(),

  transact(txnProxy) {
    let rawTxn = TransactionsHistory.getRawTransaction(txnProxy);
    if (!rawTxn)
      throw new Error("|transact| was called with an unexpected object");

    if (this._executedTransactions.has(rawTxn))
      throw new Error("Transactions objects may not be recycled.");

    // Add it in advance so one doesn't accidentally do
    // sameTxn.transact(); sameTxn.transact();
    this._executedTransactions.add(rawTxn);

    let promise = this._transactEnqueuer.enqueue(async () => {
      // Don't try to catch exceptions. If execute fails, we better not add the
      // transaction to the undo stack.
      let retval = await rawTxn.execute();

      let forceNewEntry = !this._batching || !this._createdBatchEntry;
      TransactionsHistory.add(txnProxy, forceNewEntry);
      if (this._batching)
        this._createdBatchEntry = true;

      this._updateCommandsOnActiveWindow();
      return retval;
    });
    this._mainEnqueuer.alsoWaitFor(promise);
    return promise;
  },

  batch(task) {
    return this._mainEnqueuer.enqueue(async () => {
      this._batching = true;
      this._createdBatchEntry = false;
      let rv;
      try {
        rv = await task();
      } finally {
        // We must enqueue clearing batching mode to ensure that any existing
        // transactions have completed before we clear the batching mode.
        this._mainEnqueuer.enqueue(() => {
          this._batching = false;
          this._createdBatchEntry = false;
        });
      }
      return rv;
    });
  },

  /**
   * Undo the top undo entry, if any, and update the undo position accordingly.
   */
  undo() {
    let promise = this._mainEnqueuer.enqueue(async () => {
      let entry = TransactionsHistory.topUndoEntry;
      if (!entry)
        return;

      for (let txnProxy of entry) {
        try {
          await TransactionsHistory.getRawTransaction(txnProxy).undo();
        } catch (ex) {
          // If one transaction is broken, it's not safe to work with any other
          // undo entry.  Report the error and clear the undo history.
          console.error(ex, "Can't undo a transaction, clearing undo entries.");
          TransactionsHistory.clearUndoEntries();
          return;
        }
      }
      TransactionsHistory._undoPosition++;
      this._updateCommandsOnActiveWindow();
    });
    this._transactEnqueuer.alsoWaitFor(promise);
    return promise;
  },

  /**
   * Redo the top redo entry, if any, and update the undo position accordingly.
   */
  redo() {
    let promise = this._mainEnqueuer.enqueue(async () => {
      let entry = TransactionsHistory.topRedoEntry;
      if (!entry)
        return;

      for (let i = entry.length - 1; i >= 0; i--) {
        let transaction = TransactionsHistory.getRawTransaction(entry[i]);
        try {
          if (transaction.redo) {
            await transaction.redo();
          } else {
            await transaction.execute();
          }
        } catch (ex) {
          // If one transaction is broken, it's not safe to work with any other
          // redo entry. Report the error and clear the undo history.
          console.error(ex, "Can't redo a transaction, clearing redo entries.");
          TransactionsHistory.clearRedoEntries();
          return;
        }
      }
      TransactionsHistory._undoPosition--;
      this._updateCommandsOnActiveWindow();
    });

    this._transactEnqueuer.alsoWaitFor(promise);
    return promise;
  },

  clearTransactionsHistory(undoEntries, redoEntries) {
    let promise = this._mainEnqueuer.enqueue(function() {
      if (undoEntries && redoEntries)
        TransactionsHistory.clearAllEntries();
      else if (undoEntries)
        TransactionsHistory.clearUndoEntries();
      else if (redoEntries)
        TransactionsHistory.clearRedoEntries();
      else
        throw new Error("either aUndoEntries or aRedoEntries should be true");
    });

    this._transactEnqueuer.alsoWaitFor(promise);
    return promise;
  },

  // Updates commands in the undo group of the active window commands.
  // Inactive windows commands will be updated on focus.
  _updateCommandsOnActiveWindow() {
    // Updating "undo" will cause a group update including "redo".
    try {
      let win = Services.focus.activeWindow;
      if (win)
        win.updateCommands("undo");
    } catch (ex) {
      console.error(ex, "Couldn't update undo commands.");
    }
  }
};

/**
 * Internal helper for defining the standard transactions and their input.
 * It takes the required and optional properties, and generates the public
 * constructor (which takes the input in the form of a plain object) which,
 * when called, creates the argument-less "public" |execute| method by binding
 * the input properties to the function arguments (required properties first,
 * then the optional properties).
 *
 * If this seems confusing, look at the consumers.
 *
 * This magic serves two purposes:
 * (1) It completely hides the transactions' internals from the module
 *     consumers.
 * (2) It keeps each transaction implementation to what is about, bypassing
 *     all this bureaucracy while still validating input appropriately.
 */
function DefineTransaction(requiredProps = [], optionalProps = []) {
  for (let prop of [...requiredProps, ...optionalProps]) {
    if (!DefineTransaction.inputProps.has(prop))
      throw new Error("Property '" + prop + "' is not defined");
  }

  let ctor = function(input) {
    // We want to support both syntaxes:
    // let t = new PlacesTransactions.NewBookmark(),
    // let t = PlacesTransactions.NewBookmark()
    if (this == PlacesTransactions)
      return new ctor(input);

    if (requiredProps.length > 0 || optionalProps.length > 0) {
      // Bind the input properties to the arguments of execute.
      input = DefineTransaction.verifyInput(input, requiredProps, optionalProps);
      this.execute = this.execute.bind(this, input);
    }
    return TransactionsHistory.proxifyTransaction(this);
  };
  return ctor;
}

function simpleValidateFunc(checkFn) {
  return v => {
    if (!checkFn(v))
      throw new Error("Invalid value");
    return v;
  };
}

DefineTransaction.strValidate = simpleValidateFunc(v => typeof(v) == "string");
DefineTransaction.strOrNullValidate =
  simpleValidateFunc(v => typeof(v) == "string" || v === null);
DefineTransaction.indexValidate =
  simpleValidateFunc(v => Number.isInteger(v) &&
                     v >= PlacesUtils.bookmarks.DEFAULT_INDEX);
DefineTransaction.guidValidate =
  simpleValidateFunc(v => /^[a-zA-Z0-9\-_]{12}$/.test(v));

function isPrimitive(v) {
  return v === null || (typeof(v) != "object" && typeof(v) != "function");
}

DefineTransaction.annotationObjectValidate = function(obj) {
  let checkProperty = (prop, required, checkFn) => {
    if (prop in obj)
      return checkFn(obj[prop]);

    return !required;
  };

  if (obj &&
      checkProperty("name", true, v => typeof(v) == "string" && v.length > 0) &&
      checkProperty("expires", false, Number.isInteger) &&
      checkProperty("flags", false, Number.isInteger) &&
      checkProperty("value", false, isPrimitive) ) {
    // Nothing else should be set
    let validKeys = ["name", "value", "flags", "expires"];
    if (Object.keys(obj).every(k => validKeys.includes(k)))
      return obj;
  }
  throw new Error("Invalid annotation object");
};

DefineTransaction.urlValidate = function(url) {
  if (url instanceof Ci.nsIURI)
    return new URL(url.spec);
  return new URL(url);
};

DefineTransaction.inputProps = new Map();
DefineTransaction.defineInputProps = function(names, validateFn, defaultValue) {
  for (let name of names) {
    this.inputProps.set(name, {
      validateValue(value) {
        if (value === undefined)
          return defaultValue;
        try {
          return validateFn(value);
        } catch (ex) {
          throw new Error(`Invalid value for input property ${name}`);
        }
      },

      validateInput(input, required) {
        if (required && !(name in input))
          throw new Error(`Required input property is missing: ${name}`);
        return this.validateValue(input[name]);
      },

      isArrayProperty: false
    });
  }
};

DefineTransaction.defineArrayInputProp = function(name, basePropertyName) {
  let baseProp = this.inputProps.get(basePropertyName);
  if (!baseProp)
    throw new Error(`Unknown input property: ${basePropertyName}`);

  this.inputProps.set(name, {
    validateValue(aValue) {
      if (aValue == undefined)
        return [];

      if (!Array.isArray(aValue))
        throw new Error(`${name} input property value must be an array`);

      // This also takes care of abandoning the global scope of the input
      // array (through Array.prototype).
      return aValue.map(baseProp.validateValue);
    },

    // We allow setting either the array property itself (e.g. urls), or a
    // single element of it (url, in that example), that is then transformed
    // into a single-element array.
    validateInput(input, required) {
      if (name in input) {
        // It's not allowed to set both though.
        if (basePropertyName in input) {
          throw new Error(`It is not allowed to set both ${name} and
                          ${basePropertyName} as  input properties`);
        }
        let array = this.validateValue(input[name]);
        if (required && array.length == 0) {
          throw new Error(`Empty array passed for required input property:
                           ${name}`);
        }
        return array;
      }
      // If the property is required and it's not set as is, check if the base
      // property is set.
      if (required && !(basePropertyName in input))
        throw new Error(`Required input property is missing: ${name}`);

      if (basePropertyName in input)
        return [baseProp.validateValue(input[basePropertyName])];

      return [];
    },

    isArrayProperty: true
  });
};

DefineTransaction.validatePropertyValue = function(prop, input, required) {
  return this.inputProps.get(prop).validateInput(input, required);
};

DefineTransaction.getInputObjectForSingleValue = function(input,
                                                          requiredProps,
                                                          optionalProps) {
  // The following input forms may be deduced from a single value:
  // * a single required property with or without optional properties (the given
  //   value is set to the required property).
  // * a single optional property with no required properties.
  if (requiredProps.length > 1 ||
      (requiredProps.length == 0 && optionalProps.length > 1)) {
    throw new Error("Transaction input isn't an object");
  }

  let propName = requiredProps.length == 1 ? requiredProps[0]
                                           : optionalProps[0];
  let propValue =
    this.inputProps.get(propName).isArrayProperty && !Array.isArray(input) ?
    [input] : input;
  return { [propName]: propValue };
};

DefineTransaction.verifyInput = function(input,
                                         requiredProps = [],
                                         optionalProps = []) {
  if (requiredProps.length == 0 && optionalProps.length == 0)
    return {};

  // If there's just a single required/optional property, we allow passing it
  // as is, so, for example, one could do PlacesTransactions.Remove(myGuid)
  // rather than PlacesTransactions.Remove({ guid: myGuid}).
  // This shortcut isn't supported for "complex" properties - e.g. one cannot
  // pass an annotation object this way (note there is no use case for this at
  // the moment anyway).
  let isSinglePropertyInput = isPrimitive(input) ||
                              Array.isArray(input) ||
                              (input instanceof Ci.nsISupports);
  if (isSinglePropertyInput) {
    input = this.getInputObjectForSingleValue(input, requiredProps, optionalProps);
  }

  let fixedInput = {};
  for (let prop of requiredProps) {
    fixedInput[prop] = this.validatePropertyValue(prop, input, true);
  }
  for (let prop of optionalProps) {
    fixedInput[prop] = this.validatePropertyValue(prop, input, false);
  }

  return fixedInput;
};

// Update the documentation at the top of this module if you add or
// remove properties.
DefineTransaction.defineInputProps(["url", "feedUrl", "siteUrl"],
                                   DefineTransaction.urlValidate, null);
DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
                                   DefineTransaction.guidValidate);
DefineTransaction.defineInputProps(["title"],
                                   DefineTransaction.strOrNullValidate, null);
DefineTransaction.defineInputProps(["keyword", "oldKeyword", "postData", "tag",
                                    "excludingAnnotation"],
                                   DefineTransaction.strValidate, "");
DefineTransaction.defineInputProps(["index", "newIndex"],
                                   DefineTransaction.indexValidate,
                                   PlacesUtils.bookmarks.DEFAULT_INDEX);
DefineTransaction.defineInputProps(["annotation"],
                                   DefineTransaction.annotationObjectValidate);
DefineTransaction.defineArrayInputProp("guids", "guid");
DefineTransaction.defineArrayInputProp("urls", "url");
DefineTransaction.defineArrayInputProp("tags", "tag");
DefineTransaction.defineArrayInputProp("annotations", "annotation");
DefineTransaction.defineArrayInputProp("excludingAnnotations",
                                       "excludingAnnotation");

/**
 * Creates items (all types) from a bookmarks tree representation, as defined
 * in PlacesUtils.promiseBookmarksTree.
 *
 * @param tree
 *        the bookmarks tree object.  You may pass either a bookmarks tree
 *        returned by promiseBookmarksTree, or a manually defined one.
 * @param [optional] restoring (default: false)
 *        Whether or not the items are restored.  Only in restore mode, are
 *        the guid, dateAdded and lastModified properties honored.
 * @param [optional] excludingAnnotations
 *        Array of annotations names to ignore in aBookmarksTree. This argument
 *        is ignored if aRestoring is set.
 * @note the id, root and charset properties of items in aBookmarksTree are
 *       always ignored.  The index property is ignored for all items but the
 *       root one.
 * @return {Promise}
 * @resolves to the guid of the new item.
 */
// TODO: Replace most of this with insertTree.
function createItemsFromBookmarksTree(tree, restoring = false,
                                      excludingAnnotations = []) {
  function extractLivemarkDetails(annos) {
    let feedURI = null, siteURI = null;
    annos = annos.filter(anno => {
      switch (anno.name) {
        case PlacesUtils.LMANNO_FEEDURI:
          feedURI = Services.io.newURI(anno.value);
          return false;
        case PlacesUtils.LMANNO_SITEURI:
          siteURI = Services.io.newURI(anno.value);
          return false;
        default:
          return true;
      }
    });
    return [feedURI, siteURI, annos];
  }

  async function createItem(item,
                            parentGuid,
                            index = PlacesUtils.bookmarks.DEFAULT_INDEX) {
    let guid;
    let info = { parentGuid, index };
    if (restoring) {
      info.guid = item.guid;
      info.dateAdded = PlacesUtils.toDate(item.dateAdded);
      info.lastModified = PlacesUtils.toDate(item.lastModified);
    }
    let annos = item.annos ? [...item.annos] : [];
    let shouldResetLastModified = false;
    switch (item.type) {
      case PlacesUtils.TYPE_X_MOZ_PLACE: {
        info.url = item.uri;
        if (typeof(item.title) == "string")
          info.title = item.title;

        guid = (await PlacesUtils.bookmarks.insert(info)).guid;

        if ("keyword" in item) {
          let { uri: url, keyword, postData } = item;
          await PlacesUtils.keywords.insert({ url, keyword, postData });
        }
        if ("tags" in item) {
          PlacesUtils.tagging.tagURI(Services.io.newURI(item.uri),
                                     item.tags.split(","));
          if (restoring)
            shouldResetLastModified = true;
        }
        break;
      }
      case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: {
        // Either a folder or a livemark
        let feedURI, siteURI;
        [feedURI, siteURI, annos] = extractLivemarkDetails(annos);
        if (!feedURI) {
          info.type = PlacesUtils.bookmarks.TYPE_FOLDER;
          if (typeof(item.title) == "string")
            info.title = item.title;
          guid = (await PlacesUtils.bookmarks.insert(info)).guid;
          if ("children" in item) {
            for (let child of item.children) {
              await createItem(child, guid);
            }
          }
          if (restoring)
            shouldResetLastModified = true;
        } else {
          info.parentId = await PlacesUtils.promiseItemId(parentGuid);
          info.feedURI = feedURI;
          info.siteURI = siteURI;
          if (info.dateAdded)
            info.dateAdded = PlacesUtils.toPRTime(info.dateAdded);
          if (info.lastModified)
            info.lastModified = PlacesUtils.toPRTime(info.lastModified);
          if (typeof(item.title) == "string")
            info.title = item.title;
          guid = (await PlacesUtils.livemarks.addLivemark(info)).guid;
        }
        break;
      }
      case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: {
        info.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
        guid = (await PlacesUtils.bookmarks.insert(info)).guid;
        break;
      }
    }
    if (annos.length > 0) {
      if (!restoring && excludingAnnotations.length > 0) {
        annos = annos.filter(a => !excludingAnnotations.includes(a.name));
      }

      if (annos.length > 0) {
        let itemId = await PlacesUtils.promiseItemId(guid);
        PlacesUtils.setAnnotationsForItem(itemId, annos);
        if (restoring)
          shouldResetLastModified = true;
      }
    }

    if (shouldResetLastModified) {
      let lastModified = PlacesUtils.toDate(item.lastModified);
      await PlacesUtils.bookmarks.update({ guid, lastModified });
    }

    return guid;
  }
  return createItem(tree,
                    tree.parentGuid,
                    tree.index);
}

/** ***************************************************************************
 * The Standard Places Transactions.
 *
 * See the documentation at the top of this file. The valid values for input
 * are also documented there.
 *****************************************************************************/

var PT = PlacesTransactions;

/**
 * Transaction for creating a bookmark.
 *
 * Required Input Properties: url, parentGuid.
 * Optional Input Properties: index, title, keyword, annotations, tags.
 *
 * When this transaction is executed, it's resolved to the new bookmark's GUID.
 */
PT.NewBookmark = DefineTransaction(["parentGuid", "url"],
                                   ["index", "title", "annotations", "tags"]);
PT.NewBookmark.prototype = Object.seal({
  async execute({ parentGuid, url, index, title, annotations, tags }) {
    let info = { parentGuid, index, url, title };
    // Filter tags to exclude already existing ones.
    if (tags.length > 0) {
      let currentTags = PlacesUtils.tagging
                                   .getTagsForURI(Services.io.newURI(url.href));
      tags = tags.filter(t => !currentTags.includes(t));
    }

    async function createItem() {
      info = await PlacesUtils.bookmarks.insert(info);
      if (annotations.length > 0) {
        let itemId = await PlacesUtils.promiseItemId(info.guid);
        PlacesUtils.setAnnotationsForItem(itemId, annotations);
      }
      if (tags.length > 0) {
        PlacesUtils.tagging.tagURI(Services.io.newURI(url.href), tags);
      }
    }

    await createItem();

    this.undo = async function() {
      // Pick up the removed info so we have the accurate last-modified value,
      // which could be affected by any annotation we set in createItem.
      await PlacesUtils.bookmarks.remove(info);
      if (tags.length > 0) {
        PlacesUtils.tagging.untagURI(Services.io.newURI(url.href), tags);
      }
    };
    this.redo = async function() {
      await createItem();
      // CreateItem will update the lastModified value if tags or annotations
      // are present, but we don't care to restore it. The likely of a user
      // creating a bookmark, undoing and redoing that, and still caring
      // about lastModified is basically non-existant.
    };
    return info.guid;
  }
});

/**
 * Transaction for creating a folder.
 *
 * Required Input Properties: title, parentGuid.
 * Optional Input Properties: index, annotations.
 *
 * When this transaction is executed, it's resolved to the new folder's GUID.
 */
PT.NewFolder = DefineTransaction(["parentGuid", "title"],
                                 ["index", "annotations"]);
PT.NewFolder.prototype = Object.seal({
  async execute({ parentGuid, title, index, annotations }) {
    let info = { type: PlacesUtils.bookmarks.TYPE_FOLDER,
                 parentGuid, index, title };

    async function createItem() {
      info = await PlacesUtils.bookmarks.insert(info);
      if (annotations.length > 0) {
        let itemId = await PlacesUtils.promiseItemId(info.guid);
        PlacesUtils.setAnnotationsForItem(itemId, annotations);
      }
    }
    await createItem();

    this.undo = async function() {
      await PlacesUtils.bookmarks.remove(info);
    };
    this.redo = async function() {
      await createItem();
      // See the reasoning in CreateItem for why we don't care
      // about precisely resetting the lastModified value.
    };
    return info.guid;
  }
});

/**
 * Transaction for creating a separator.
 *
 * Required Input Properties: parentGuid.
 * Optional Input Properties: index.
 *
 * When this transaction is executed, it's resolved to the new separator's
 * GUID.
 */
PT.NewSeparator = DefineTransaction(["parentGuid"], ["index"]);
PT.NewSeparator.prototype = Object.seal({
  async execute(info) {
    info.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
    info = await PlacesUtils.bookmarks.insert(info);
    this.undo = PlacesUtils.bookmarks.remove.bind(PlacesUtils.bookmarks, info);
    this.redo = PlacesUtils.bookmarks.insert.bind(PlacesUtils.bookmarks, info);
    return info.guid;
  }
});

/**
 * Transaction for creating a live bookmark (see mozIAsyncLivemarks for the
 * semantics).
 *
 * Required Input Properties: feedUrl, title, parentGuid.
 * Optional Input Properties: siteUrl, index, annotations.
 *
 * When this transaction is executed, it's resolved to the new livemark's
 * GUID.
 */
PT.NewLivemark = DefineTransaction(["feedUrl", "title", "parentGuid"],
                                   ["siteUrl", "index", "annotations"]);
PT.NewLivemark.prototype = Object.seal({
  async execute({ feedUrl, title, parentGuid, siteUrl, index, annotations }) {
    let livemarkInfo = { title,
                         feedURI: Services.io.newURI(feedUrl.href),
                         siteURI: siteUrl ? Services.io.newURI(siteUrl.href) : null,
                         index,
                         parentGuid};
    let createItem = async function() {
      let livemark = await PlacesUtils.livemarks.addLivemark(livemarkInfo);
      if (annotations.length > 0) {
        PlacesUtils.setAnnotationsForItem(livemark.id, annotations);
      }
      return livemark;
    };

    let livemark = await createItem();
    livemarkInfo.guid = livemark.guid;
    livemarkInfo.dateAdded = livemark.dateAdded;
    livemarkInfo.lastModified = livemark.lastModified;

    this.undo = async function() {
      await PlacesUtils.livemarks.removeLivemark(livemark);
    };
    this.redo = async function() {
      livemark = await createItem();
    };
    return livemark.guid;
  }
});

/**
 * Transaction for moving an item.
 *
 * Required Input Properties: guid, newParentGuid.
 * Optional Input Properties  newIndex.
 */
PT.Move = DefineTransaction(["guid", "newParentGuid"], ["newIndex"]);
PT.Move.prototype = Object.seal({
  async execute({ guid, newParentGuid, newIndex }) {
    let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
    if (!originalInfo)
      throw new Error("Cannot move a non-existent item");
    let updateInfo = { guid, parentGuid: newParentGuid, index: newIndex }
    updateInfo = await PlacesUtils.bookmarks.update(updateInfo);

    // Moving down in the same parent takes in count removal of the item
    // so to revert positions we must move to oldIndex + 1.
    if (newParentGuid == originalInfo.parentGuid &&
        originalInfo.index > updateInfo.index) {
      originalInfo.index++;
    }

    this.undo = PlacesUtils.bookmarks.update.bind(PlacesUtils.bookmarks, originalInfo);
    this.redo = PlacesUtils.bookmarks.update.bind(PlacesUtils.bookmarks, updateInfo);
    return guid;
  }
});

/**
 * Transaction for setting the title for an item.
 *
 * Required Input Properties: guid, title.
 */
PT.EditTitle = DefineTransaction(["guid", "title"]);
PT.EditTitle.prototype = Object.seal({
  async execute({ guid, title }) {
    let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
    if (!originalInfo)
      throw new Error("cannot update a non-existent item");

    let updateInfo = { guid, title };
    updateInfo = await PlacesUtils.bookmarks.update(updateInfo);

    this.undo = PlacesUtils.bookmarks.update.bind(PlacesUtils.bookmarks, originalInfo);
    this.redo = PlacesUtils.bookmarks.update.bind(PlacesUtils.bookmarks, updateInfo);
  }
});

/**
 * Transaction for setting the URI for an item.
 *
 * Required Input Properties: guid, url.
 */
PT.EditUrl = DefineTransaction(["guid", "url"]);
PT.EditUrl.prototype = Object.seal({
  async execute({ guid, url }) {
    let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
    if (!originalInfo)
      throw new Error("cannot update a non-existent item");
    if (originalInfo.type != PlacesUtils.bookmarks.TYPE_BOOKMARK)
      throw new Error("Cannot edit url for non-bookmark items");

    let uri = Services.io.newURI(url.href);
    let originalURI = Services.io.newURI(originalInfo.url.href);
    let originalTags = PlacesUtils.tagging.getTagsForURI(originalURI);
    let updatedInfo = { guid, url };
    let newURIAdditionalTags = null;

    async function updateItem() {
      updatedInfo = await PlacesUtils.bookmarks.update(updatedInfo);
      // Move tags from the original URI to the new URI.
      if (originalTags.length > 0) {
        // Untag the original URI only if this was the only bookmark.
        if (!(await PlacesUtils.bookmarks.fetch({ url: originalInfo.url })))
          PlacesUtils.tagging.untagURI(originalURI, originalTags);
        let currentNewURITags = PlacesUtils.tagging.getTagsForURI(uri);
        newURIAdditionalTags = originalTags.filter(t => !currentNewURITags.includes(t));
        if (newURIAdditionalTags && newURIAdditionalTags.length > 0)
          PlacesUtils.tagging.tagURI(uri, newURIAdditionalTags);
      }
    }
    await updateItem();

    this.undo = async function() {
      await PlacesUtils.bookmarks.update(originalInfo);
      // Move tags from new URI to original URI.
      if (originalTags.length > 0) {
         // Only untag the new URI if this is the only bookmark.
         if (newURIAdditionalTags && newURIAdditionalTags.length > 0 &&
            !(await PlacesUtils.bookmarks.fetch({ url }))) {
          PlacesUtils.tagging.untagURI(uri, newURIAdditionalTags);
         }
        PlacesUtils.tagging.tagURI(originalURI, originalTags);
      }
    };

    this.redo = async function() {
      updatedInfo = await updateItem();
    };
  }
});

/**
 * Transaction for setting annotations for an item.
 *
 * Required Input Properties: guid, annotationObject
 */
PT.Annotate = DefineTransaction(["guids", "annotations"]);
PT.Annotate.prototype = {
  async execute({ guids, annotations }) {
    let undoAnnosForItemId = new Map();
    for (let guid of guids) {
      let itemId = await PlacesUtils.promiseItemId(guid);
      let currentAnnos = PlacesUtils.getAnnotationsForItem(itemId);

      let undoAnnos = [];
      for (let newAnno of annotations) {
        let currentAnno = currentAnnos.find(a => a.name == newAnno.name);
        if (currentAnno) {
          undoAnnos.push(currentAnno);
        } else {
          // An unset value removes the annotation.
          undoAnnos.push({ name: newAnno.name });
        }
      }
      undoAnnosForItemId.set(itemId, undoAnnos);

      PlacesUtils.setAnnotationsForItem(itemId, annotations);
    }

    this.undo = function() {
      for (let [itemId, undoAnnos] of undoAnnosForItemId) {
        PlacesUtils.setAnnotationsForItem(itemId, undoAnnos);
      }
    };
    this.redo = async function() {
      for (let guid of guids) {
        let itemId = await PlacesUtils.promiseItemId(guid);
        PlacesUtils.setAnnotationsForItem(itemId, annotations);
      }
    };
  }
};

/**
 * Transaction for setting the keyword for a bookmark.
 *
 * Required Input Properties: guid, keyword.
 * Optional Input Properties: postData, oldKeyword.
 */
PT.EditKeyword = DefineTransaction(["guid", "keyword"],
                                   ["postData", "oldKeyword"]);
PT.EditKeyword.prototype = Object.seal({
  async execute({ guid, keyword, postData, oldKeyword }) {
    let url;
    let oldKeywordEntry;
    if (oldKeyword) {
      oldKeywordEntry = await PlacesUtils.keywords.fetch(oldKeyword);
      url = oldKeywordEntry.url;
      await PlacesUtils.keywords.remove(oldKeyword);
    }

    if (keyword) {
      if (!url) {
        url = (await PlacesUtils.bookmarks.fetch(guid)).url;
      }
      await PlacesUtils.keywords.insert({
        url,
        keyword,
        postData: postData || (oldKeywordEntry ? oldKeywordEntry.postData : "")
      });
    }

    this.undo = async function() {
      if (keyword) {
        await PlacesUtils.keywords.remove(keyword);
      }
      if (oldKeywordEntry) {
        await PlacesUtils.keywords.insert(oldKeywordEntry);
      }
    };
  }
});

/**
 * Transaction for sorting a folder by name.
 *
 * Required Input Properties: guid.
 */
PT.SortByName = DefineTransaction(["guid"]);
PT.SortByName.prototype = {
  async execute({ guid }) {
    let sortingMethod = (node_a, node_b) => {
      if (PlacesUtils.nodeIsContainer(node_a) && !PlacesUtils.nodeIsContainer(node_b))
        return -1;
      if (!PlacesUtils.nodeIsContainer(node_a) && PlacesUtils.nodeIsContainer(node_b))
        return 1;
      return node_a.title.localeCompare(node_b.title);
    };
    let oldOrderGuids = [];
    let newOrderGuids = [];
    let preSepNodes = [];

    // This is not great, since it does main-thread IO.
    // PromiseBookmarksTree can't be used, since it' won't stop at the first level'.
    let folderId = await PlacesUtils.promiseItemId(guid);
    let root = PlacesUtils.getFolderContents(folderId, false, false).root;
    for (let i = 0; i < root.childCount; ++i) {
      let node = root.getChild(i);
      oldOrderGuids.push(node.bookmarkGuid);
      if (PlacesUtils.nodeIsSeparator(node)) {
        if (preSepNodes.length > 0) {
          preSepNodes.sort(sortingMethod);
          newOrderGuids.push(...preSepNodes.map(n => n.bookmarkGuid));
          preSepNodes = [];
        }
        newOrderGuids.push(node.bookmarkGuid);
      } else {
        preSepNodes.push(node);
      }
    }
    root.containerOpen = false;
    if (preSepNodes.length > 0) {
      preSepNodes.sort(sortingMethod);
      newOrderGuids.push(...preSepNodes.map(n => n.bookmarkGuid));
    }
    await PlacesUtils.bookmarks.reorder(guid, newOrderGuids);

    this.undo = async function() {
      await PlacesUtils.bookmarks.reorder(guid, oldOrderGuids);
    };
    this.redo = async function() {
      await PlacesUtils.bookmarks.reorder(guid, newOrderGuids);
    };
  }
};

/**
 * Transaction for removing an item (any type).
 *
 * Required Input Properties: guids.
 */
PT.Remove = DefineTransaction(["guids"]);
PT.Remove.prototype = {
  async execute({ guids }) {
    let promiseBookmarksTree = async function(guid) {
      let tree;
      try {
        tree = await PlacesUtils.promiseBookmarksTree(guid);
      } catch (ex) {
        throw new Error("Failed to get info for the specified item (guid: " +
                          guid + "): " + ex);
      }
      return tree;
    };
    let removedItems = [];
    for (let guid of guids) {
      removedItems.push(await promiseBookmarksTree(guid));
    }
    let removeThem = async function() {
      for (let info of removedItems) {
        if (info.annos &&
            info.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
          await PlacesUtils.livemarks.removeLivemark({ guid: info.guid });
        } else {
          await PlacesUtils.bookmarks.remove({ guid: info.guid });
        }
      }
    };
    await removeThem();

    this.undo = async function() {
      for (let info of removedItems) {
        await createItemsFromBookmarksTree(info, true);
      }
    };
    this.redo = removeThem;
  }
};

/**
 * Transactions for removing all bookmarks for one or more urls.
 *
 * Required Input Properties: urls.
 */
PT.RemoveBookmarksForUrls = DefineTransaction(["urls"]);
PT.RemoveBookmarksForUrls.prototype = {
  async execute({ urls }) {
    let guids = [];
    for (let url of urls) {
      await PlacesUtils.bookmarks.fetch({ url }, b => guids.push(b.guid));
    }
    let removeTxn = TransactionsHistory.getRawTransaction(PT.Remove(guids));
    await removeTxn.execute();
    this.undo = removeTxn.undo.bind(removeTxn);
    this.redo = removeTxn.redo.bind(removeTxn);
  }
};

/**
 * Transaction for tagging urls.
 *
 * Required Input Properties: urls, tags.
 */
PT.Tag = DefineTransaction(["urls", "tags"]);
PT.Tag.prototype = {
  async execute({ urls, tags }) {
    let onUndo = [], onRedo = [];
    for (let url of urls) {
      if (!(await PlacesUtils.bookmarks.fetch({ url }))) {
        // Tagging is only allowed for bookmarked URIs (but see 424160).
        let createTxn = TransactionsHistory.getRawTransaction(
          PT.NewBookmark({ url,
                           tags,
                           parentGuid: PlacesUtils.bookmarks.unfiledGuid })
        );
        await createTxn.execute();
        onUndo.unshift(createTxn.undo.bind(createTxn));
        onRedo.push(createTxn.redo.bind(createTxn));
      } else {
        let uri = Services.io.newURI(url.href);
        let currentTags = PlacesUtils.tagging.getTagsForURI(uri);
        let newTags = tags.filter(t => !currentTags.includes(t));
        PlacesUtils.tagging.tagURI(uri, newTags);
        onUndo.unshift(() => {
          PlacesUtils.tagging.untagURI(uri, newTags);
        });
        onRedo.push(() => {
          PlacesUtils.tagging.tagURI(uri, newTags);
        });
      }
    }
    this.undo = async function() {
      for (let f of onUndo) {
        await f();
      }
    };
    this.redo = async function() {
      for (let f of onRedo) {
        await f();
      }
    };
  }
};

/**
 * Transaction for removing tags from a URI.
 *
 * Required Input Properties: urls.
 * Optional Input Properties: tags.
 *
 * If |tags| is not set, all tags set for |url| are removed.
 */
PT.Untag = DefineTransaction(["urls"], ["tags"]);
PT.Untag.prototype = {
  execute({ urls, tags }) {
    let onUndo = [], onRedo = [];
    for (let url of urls) {
      let uri = Services.io.newURI(url.href);
      let tagsToRemove;
      let tagsSet = PlacesUtils.tagging.getTagsForURI(uri);
      if (tags.length > 0) {
        tagsToRemove = tags.filter(t => tagsSet.includes(t));
      } else {
        tagsToRemove = tagsSet;
      }
      if (tagsToRemove.length > 0) {
        PlacesUtils.tagging.untagURI(uri, tagsToRemove);
      }
      onUndo.unshift(() => {
        if (tagsToRemove.length > 0) {
          PlacesUtils.tagging.tagURI(uri, tagsToRemove);
        }
      });
      onRedo.push(() => {
        if (tagsToRemove.length > 0) {
          PlacesUtils.tagging.untagURI(uri, tagsToRemove);
        }
      });
    }
    this.undo = async function() {
      for (let f of onUndo) {
        await f();
      }
    };
    this.redo = async function() {
      for (let f of onRedo) {
        await f();
      }
    };
  }
};

/**
 * Transaction for copying an item.
 *
 * Required Input Properties: guid, newParentGuid
 * Optional Input Properties: newIndex, excludingAnnotations.
 */
PT.Copy = DefineTransaction(["guid", "newParentGuid"],
                            ["newIndex", "excludingAnnotations"]);
PT.Copy.prototype = {
  async execute({ guid, newParentGuid, newIndex, excludingAnnotations }) {
    let creationInfo = null;
    try {
      creationInfo = await PlacesUtils.promiseBookmarksTree(guid);
    } catch (ex) {
      throw new Error("Failed to get info for the specified item (guid: " +
                      guid + "). Ex: " + ex);
    }
    creationInfo.parentGuid = newParentGuid;
    creationInfo.index = newIndex;

    let newItemGuid = await createItemsFromBookmarksTree(creationInfo, false,
                                                         excludingAnnotations);
    let newItemInfo = null;
    this.undo = async function() {
      if (!newItemInfo) {
        newItemInfo = await PlacesUtils.promiseBookmarksTree(newItemGuid);
      }
      await PlacesUtils.bookmarks.remove(newItemGuid);
    };
    this.redo = async function() {
      await createItemsFromBookmarksTree(newItemInfo, true);
    }

    return newItemGuid;
  }
};
PK
!<4(ťmodules/PlacesUtils.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "PlacesUtils",
  "PlacesAggregatedTransaction",
  "PlacesCreateFolderTransaction",
  "PlacesCreateBookmarkTransaction",
  "PlacesCreateSeparatorTransaction",
  "PlacesCreateLivemarkTransaction",
  "PlacesMoveItemTransaction",
  "PlacesRemoveItemTransaction",
  "PlacesEditItemTitleTransaction",
  "PlacesEditBookmarkURITransaction",
  "PlacesSetItemAnnotationTransaction",
  "PlacesSetPageAnnotationTransaction",
  "PlacesEditBookmarkKeywordTransaction",
  "PlacesEditBookmarkPostDataTransaction",
  "PlacesEditItemDateAddedTransaction",
  "PlacesEditItemLastModifiedTransaction",
  "PlacesSortFolderByNameTransaction",
  "PlacesTagURITransaction",
  "PlacesUntagURITransaction"
];

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.importGlobalProperties(["URL"]);

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                  "resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Bookmarks",
                                  "resource://gre/modules/Bookmarks.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "History",
                                  "resource://gre/modules/History.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");

// The minimum amount of transactions before starting a batch. Usually we do
// do incremental updates, a batch will cause views to completely
// refresh instead.
const MIN_TRANSACTIONS_FOR_BATCH = 5;

// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where
// we really just want "\n". On other platforms, the transferable system
// converts "\r\n" to "\n".
const NEWLINE = AppConstants.platform == "macosx" ? "\n" : "\r\n";

// Timers resolution is not always good, it can have a 16ms precision on Win.
const TIMERS_RESOLUTION_SKEW_MS = 16;

function QI_node(aNode, aIID) {
  var result = null;
  try {
    result = aNode.QueryInterface(aIID);
  } catch (e) {
  }
  return result;
}
function asContainer(aNode) {
  return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);
}
function asQuery(aNode) {
  return QI_node(aNode, Ci.nsINavHistoryQueryResultNode);
}

/**
 * Sends a bookmarks notification through the given observers.
 *
 * @param observers
 *        array of nsINavBookmarkObserver objects.
 * @param notification
 *        the notification name.
 * @param args
 *        array of arguments to pass to the notification.
 */
function notify(observers, notification, args) {
  for (let observer of observers) {
    try {
      observer[notification](...args);
    } catch (ex) {}
  }
}

/**
 * Sends a keyword change notification.
 *
 * @param url
 *        the url to notify about.
 * @param keyword
 *        The keyword to notify, or empty string if a keyword was removed.
 */
async function notifyKeywordChange(url, keyword, source) {
  // Notify bookmarks about the removal.
  let bookmarks = [];
  await PlacesUtils.bookmarks.fetch({ url }, b => bookmarks.push(b));
  // We don't want to yield in the gIgnoreKeywordNotifications section.
  for (let bookmark of bookmarks) {
    bookmark.id = await PlacesUtils.promiseItemId(bookmark.guid);
    bookmark.parentId = await PlacesUtils.promiseItemId(bookmark.parentGuid);
  }
  let observers = PlacesUtils.bookmarks.getObservers();
  gIgnoreKeywordNotifications = true;
  for (let bookmark of bookmarks) {
    notify(observers, "onItemChanged", [ bookmark.id, "keyword", false,
                                         keyword,
                                         bookmark.lastModified * 1000,
                                         bookmark.type,
                                         bookmark.parentId,
                                         bookmark.guid, bookmark.parentGuid,
                                         "", source
                                       ]);
  }
  gIgnoreKeywordNotifications = false;
}

/**
 * Serializes the given node in JSON format.
 *
 * @param aNode
 *        An nsINavHistoryResultNode
 * @param aIsLivemark
 *        Whether the node represents a livemark.
 */
function serializeNode(aNode, aIsLivemark) {
  let data = {};

  data.title = aNode.title;
  data.id = aNode.itemId;
  data.livemark = aIsLivemark;

  let guid = aNode.bookmarkGuid;
  if (guid) {
    data.itemGuid = guid;
    if (aNode.parent)
      data.parent = aNode.parent.itemId;
    let grandParent = aNode.parent && aNode.parent.parent;
    if (grandParent)
      data.grandParentId = grandParent.itemId;

    data.dateAdded = aNode.dateAdded;
    data.lastModified = aNode.lastModified;

    let annos = PlacesUtils.getAnnotationsForItem(data.id);
    if (annos.length > 0)
      data.annos = annos;
  }

  if (PlacesUtils.nodeIsURI(aNode)) {
    // Check for url validity.
    NetUtil.newURI(aNode.uri);

    // Tag root accepts only folder nodes, not URIs.
    if (data.parent == PlacesUtils.tagsFolderId)
      throw new Error("Unexpected node type");

    data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
    data.uri = aNode.uri;

    if (aNode.tags)
      data.tags = aNode.tags;
  } else if (PlacesUtils.nodeIsContainer(aNode)) {
    // Tag containers accept only uri nodes.
    if (data.grandParentId == PlacesUtils.tagsFolderId)
      throw new Error("Unexpected node type");

    let concreteId = PlacesUtils.getConcreteItemId(aNode);
    if (concreteId != -1) {
      // This is a bookmark or a tag container.
      if (PlacesUtils.nodeIsQuery(aNode) || concreteId != aNode.itemId) {
        // This is a folder shortcut.
        data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
        data.uri = aNode.uri;
        data.concreteId = concreteId;
      } else {
        // This is a bookmark folder.
        data.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
      }
    } else {
      // This is a grouped container query, dynamically generated.
      data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
      data.uri = aNode.uri;
    }
  } else if (PlacesUtils.nodeIsSeparator(aNode)) {
    // Tag containers don't accept separators.
    if (data.parent == PlacesUtils.tagsFolderId ||
        data.grandParentId == PlacesUtils.tagsFolderId)
      throw new Error("Unexpected node type");

    data.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
  }

  return JSON.stringify(data);
}

// Imposed to limit database size.
const DB_URL_LENGTH_MAX = 65536;
const DB_TITLE_LENGTH_MAX = 4096;
const DB_DESCRIPTION_LENGTH_MAX = 1024;

/**
 * List of bookmark object validators, one per each known property.
 * Validators must throw if the property value is invalid and return a fixed up
 * version of the value, if needed.
 */
const BOOKMARK_VALIDATORS = Object.freeze({
  guid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
  parentGuid: simpleValidateFunc(v => typeof(v) == "string" &&
                                      /^[a-zA-Z0-9\-_]{12}$/.test(v)),
  index: simpleValidateFunc(v => Number.isInteger(v) &&
                                 v >= PlacesUtils.bookmarks.DEFAULT_INDEX),
  dateAdded: simpleValidateFunc(v => v.constructor.name == "Date"),
  lastModified: simpleValidateFunc(v => v.constructor.name == "Date"),
  type: simpleValidateFunc(v => Number.isInteger(v) &&
                                [ PlacesUtils.bookmarks.TYPE_BOOKMARK,
                                  PlacesUtils.bookmarks.TYPE_FOLDER,
                                  PlacesUtils.bookmarks.TYPE_SEPARATOR ].includes(v)),
  title: v => {
    if (v === null) {
      return "";
    }
    if (typeof(v) == "string") {
      return v.slice(0, DB_TITLE_LENGTH_MAX);
    }
    throw new Error("Invalid title");
  },
  url: v => {
    simpleValidateFunc(val => (typeof(val) == "string" && val.length <= DB_URL_LENGTH_MAX) ||
                              (val instanceof Ci.nsIURI && val.spec.length <= DB_URL_LENGTH_MAX) ||
                              (val instanceof URL && val.href.length <= DB_URL_LENGTH_MAX)
                      ).call(this, v);
    if (typeof(v) === "string")
      return new URL(v);
    if (v instanceof Ci.nsIURI)
      return new URL(v.spec);
    return v;
  },
  source: simpleValidateFunc(v => Number.isInteger(v) &&
                                  Object.values(PlacesUtils.bookmarks.SOURCES).includes(v)),
  annos: simpleValidateFunc(v => Array.isArray(v) && v.length),
  keyword: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
  charset: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
  postData: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
  tags: simpleValidateFunc(v => Array.isArray(v) && v.length),
});

// Sync bookmark records can contain additional properties.
const SYNC_BOOKMARK_VALIDATORS = Object.freeze({
  // Sync uses Places GUIDs for all records except roots.
  syncId: simpleValidateFunc(v => typeof v == "string" && (
                                  (PlacesSyncUtils.bookmarks.ROOTS.includes(v) ||
                                   PlacesUtils.isValidGuid(v)))),
  parentSyncId: v => SYNC_BOOKMARK_VALIDATORS.syncId(v),
  // Sync uses kinds instead of types, which distinguish between livemarks,
  // queries, and smart bookmarks.
  kind: simpleValidateFunc(v => typeof v == "string" &&
                                Object.values(PlacesSyncUtils.bookmarks.KINDS).includes(v)),
  query: simpleValidateFunc(v => v === null || (typeof v == "string" && v)),
  folder: simpleValidateFunc(v => typeof v == "string" && v &&
                                  v.length <= Ci.nsITaggingService.MAX_TAG_LENGTH),
  tags: v => {
    if (v === null) {
      return [];
    }
    if (!Array.isArray(v)) {
      throw new Error("Invalid tag array");
    }
    for (let tag of v) {
      if (typeof tag != "string" || !tag ||
          tag.length > Ci.nsITaggingService.MAX_TAG_LENGTH) {
        throw new Error(`Invalid tag: ${tag}`);
      }
    }
    return v;
  },
  keyword: simpleValidateFunc(v => v === null || typeof v == "string"),
  description: simpleValidateFunc(v => v === null || typeof v == "string"),
  loadInSidebar: simpleValidateFunc(v => v === true || v === false),
  dateAdded: simpleValidateFunc(v => typeof v === "number"
    && v > PlacesSyncUtils.bookmarks.EARLIEST_BOOKMARK_TIMESTAMP),
  feed: v => v === null ? v : BOOKMARK_VALIDATORS.url(v),
  site: v => v === null ? v : BOOKMARK_VALIDATORS.url(v),
  title: BOOKMARK_VALIDATORS.title,
  url: BOOKMARK_VALIDATORS.url,
});

// Sync change records are passed between `PlacesSyncUtils` and the Sync
// bookmarks engine, and are used to update an item's sync status and change
// counter at the end of a sync.
const SYNC_CHANGE_RECORD_VALIDATORS = Object.freeze({
  modified: simpleValidateFunc(v => typeof v == "number" && v >= 0),
  counter: simpleValidateFunc(v => typeof v == "number" && v >= 0),
  status: simpleValidateFunc(v => typeof v == "number" &&
                                  Object.values(PlacesUtils.bookmarks.SYNC_STATUS).includes(v)),
  tombstone: simpleValidateFunc(v => v === true || v === false),
  synced: simpleValidateFunc(v => v === true || v === false),
});

this.PlacesUtils = {
  // Place entries that are containers, e.g. bookmark folders or queries.
  TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container",
  // Place entries that are bookmark separators.
  TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator",
  // Place entries that are not containers or separators
  TYPE_X_MOZ_PLACE: "text/x-moz-place",
  // Place entries in shortcut url format (url\ntitle)
  TYPE_X_MOZ_URL: "text/x-moz-url",
  // Place entries formatted as HTML anchors
  TYPE_HTML: "text/html",
  // Place entries as raw URL text
  TYPE_UNICODE: "text/unicode",
  // Used to track the action that populated the clipboard.
  TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",

  EXCLUDE_FROM_BACKUP_ANNO: "places/excludeFromBackup",
  LMANNO_FEEDURI: "livemark/feedURI",
  LMANNO_SITEURI: "livemark/siteURI",
  POST_DATA_ANNO: "bookmarkProperties/POSTData",
  READ_ONLY_ANNO: "placesInternal/READ_ONLY",
  CHARSET_ANNO: "URIProperties/characterSet",
  MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",

  TOPIC_SHUTDOWN: "places-shutdown",
  TOPIC_INIT_COMPLETE: "places-init-complete",
  TOPIC_DATABASE_LOCKED: "places-database-locked",
  TOPIC_EXPIRATION_FINISHED: "places-expiration-finished",
  TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated",
  TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
  TOPIC_VACUUM_STARTING: "places-vacuum-starting",
  TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
  TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
  TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",

  asContainer: aNode => asContainer(aNode),
  asQuery: aNode => asQuery(aNode),

  endl: NEWLINE,

  /**
   * Makes a URI from a spec.
   * @param   aSpec
   *          The string spec of the URI
   * @returns A URI object for the spec.
   */
  _uri: function PU__uri(aSpec) {
    return NetUtil.newURI(aSpec);
  },

  /**
   * Is a string a valid GUID?
   *
   * @param guid: (String)
   * @return (Boolean)
   */
  isValidGuid(guid) {
    return typeof guid == "string" && guid &&
           (/^[a-zA-Z0-9\-_]{12}$/.test(guid));
  },

  /**
   * Converts a string or n URL object to an nsIURI.
   *
   * @param url (URL) or (String)
   *        the URL to convert.
   * @return nsIURI for the given URL.
   */
  toURI(url) {
    url = (url instanceof URL) ? url.href : url;

    return NetUtil.newURI(url);
  },

  /**
   * Convert a Date object to a PRTime (microseconds).
   *
   * @param date
   *        the Date object to convert.
   * @return microseconds from the epoch.
   */
  toPRTime(date) {
    if (typeof date != "number" && date.constructor.name != "Date")
      throw new Error("Invalid value passed to toPRTime");
    return date * 1000;
  },

  /**
   * Convert a PRTime to a Date object.
   *
   * @param time
   *        microseconds from the epoch.
   * @return a Date object.
   */
  toDate(time) {
    if (typeof time != "number")
      throw new Error("Invalid value passed to toDate");
    return new Date(parseInt(time / 1000));
  },

  /**
   * Wraps a string in a nsISupportsString wrapper.
   * @param   aString
   *          The string to wrap.
   * @returns A nsISupportsString object containing a string.
   */
  toISupportsString: function PU_toISupportsString(aString) {
    let s = Cc["@mozilla.org/supports-string;1"].
            createInstance(Ci.nsISupportsString);
    s.data = aString;
    return s;
  },

  getFormattedString: function PU_getFormattedString(key, params) {
    return bundle.formatStringFromName(key, params, params.length);
  },

  getString: function PU_getString(key) {
    return bundle.GetStringFromName(key);
  },

  /**
   * Makes a moz-action URI for the given action and set of parameters.
   *
   * @param   type
   *          The action type.
   * @param   params
   *          A JS object of action params.
   * @returns A moz-action URI as a string.
   */
  mozActionURI(type, params) {
    let encodedParams = {};
    for (let key in params) {
      // Strip null or undefined.
      // Regardless, don't encode them or they would be converted to a string.
      if (params[key] === null || params[key] === undefined) {
        continue;
      }
      encodedParams[key] = encodeURIComponent(params[key]);
    }
    return "moz-action:" + type + "," + JSON.stringify(encodedParams);
  },

  /**
   * Determines whether or not a ResultNode is a Bookmark folder.
   * @param   aNode
   *          A result node
   * @returns true if the node is a Bookmark folder, false otherwise
   */
  nodeIsFolder: function PU_nodeIsFolder(aNode) {
    return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
            aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
  },

  /**
   * Determines whether or not a ResultNode represents a bookmarked URI.
   * @param   aNode
   *          A result node
   * @returns true if the node represents a bookmarked URI, false otherwise
   */
  nodeIsBookmark: function PU_nodeIsBookmark(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI &&
           aNode.itemId != -1;
  },

  /**
   * Determines whether or not a ResultNode is a Bookmark separator.
   * @param   aNode
   *          A result node
   * @returns true if the node is a Bookmark separator, false otherwise
   */
  nodeIsSeparator: function PU_nodeIsSeparator(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR;
  },

  /**
   * Determines whether or not a ResultNode is a URL item.
   * @param   aNode
   *          A result node
   * @returns true if the node is a URL item, false otherwise
   */
  nodeIsURI: function PU_nodeIsURI(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
  },

  /**
   * Determines whether or not a ResultNode is a Query item.
   * @param   aNode
   *          A result node
   * @returns true if the node is a Query item, false otherwise
   */
  nodeIsQuery: function PU_nodeIsQuery(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
  },

  /**
   * Generator for a node's ancestors.
   * @param aNode
   *        A result node
   */
  nodeAncestors: function* PU_nodeAncestors(aNode) {
    let node = aNode.parent;
    while (node) {
      yield node;
      node = node.parent;
    }
  },

  /**
   * Checks validity of an object, filling up default values for optional
   * properties.
   *
   * @param {string} name
   *        The operation name. This is included in the error message if
   *        validation fails.
   * @param validators (object)
   *        An object containing input validators. Keys should be field names;
   *        values should be validation functions.
   * @param props (object)
   *        The object to validate.
   * @param behavior (object) [optional]
   *        Object defining special behavior for some of the properties.
   *        The following behaviors may be optionally set:
   *         - required: this property is required.
   *         - replaceWith: this property will be overwritten with the value
   *                        provided
   *         - requiredIf: if the provided condition is satisfied, then this
   *                       property is required.
   *         - validIf: if the provided condition is not satisfied, then this
   *                    property is invalid.
   *         - defaultValue: an undefined property should default to this value.
   *
   * @return a validated and normalized item.
   * @throws if the object contains invalid data.
   * @note any unknown properties are pass-through.
   */
  validateItemProperties(name, validators, props, behavior = {}) {
    if (!props)
      throw new Error(`${name}: Input should be a valid object`);
    // Make a shallow copy of `props` to avoid mutating the original object
    // when filling in defaults.
    let input = Object.assign({}, props);
    let normalizedInput = {};
    let required = new Set();
    for (let prop in behavior) {
      if (behavior[prop].hasOwnProperty("required") && behavior[prop].required) {
        required.add(prop);
      }
      if (behavior[prop].hasOwnProperty("requiredIf") && behavior[prop].requiredIf(input)) {
        required.add(prop);
      }
      if (behavior[prop].hasOwnProperty("validIf") && input[prop] !== undefined &&
          !behavior[prop].validIf(input)) {
        throw new Error(`${name}: Invalid value for property '${prop}': ${JSON.stringify(input[prop])}`);
      }
      if (behavior[prop].hasOwnProperty("defaultValue") && input[prop] === undefined) {
        input[prop] = behavior[prop].defaultValue;
      }
      if (behavior[prop].hasOwnProperty("replaceWith")) {
        input[prop] = behavior[prop].replaceWith;
      }
    }

    for (let prop in input) {
      if (required.has(prop)) {
        required.delete(prop);
      } else if (input[prop] === undefined) {
        // Skip undefined properties that are not required.
        continue;
      }
      if (validators.hasOwnProperty(prop)) {
        try {
          normalizedInput[prop] = validators[prop](input[prop], input);
        } catch (ex) {
          throw new Error(`${name}: Invalid value for property '${prop}': ${JSON.stringify(input[prop])}`);
        }
      }
    }
    if (required.size > 0)
      throw new Error(`${name}: The following properties were expected: ${[...required].join(", ")}`);
    return normalizedInput;
  },

  BOOKMARK_VALIDATORS,
  SYNC_BOOKMARK_VALIDATORS,
  SYNC_CHANGE_RECORD_VALIDATORS,

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsITransactionListener
  ]),

  _shutdownFunctions: [],
  registerShutdownFunction: function PU_registerShutdownFunction(aFunc) {
    // If this is the first registered function, add the shutdown observer.
    if (this._shutdownFunctions.length == 0) {
      Services.obs.addObserver(this, this.TOPIC_SHUTDOWN);
    }
    this._shutdownFunctions.push(aFunc);
  },

  // nsIObserver
  observe: function PU_observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case this.TOPIC_SHUTDOWN:
        Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
        while (this._shutdownFunctions.length > 0) {
          this._shutdownFunctions.shift().apply(this);
        }
        break;
    }
  },

  onPageAnnotationSet() {},
  onPageAnnotationRemoved() {},


  // nsITransactionListener

  didDo: function PU_didDo(aManager, aTransaction, aDoResult) {
    updateCommandsOnActiveWindow();
  },

  didUndo: function PU_didUndo(aManager, aTransaction, aUndoResult) {
    updateCommandsOnActiveWindow();
  },

  didRedo: function PU_didRedo(aManager, aTransaction, aRedoResult) {
    updateCommandsOnActiveWindow();
  },

  didBeginBatch: function PU_didBeginBatch(aManager, aResult) {
    // A no-op transaction is pushed to the stack, in order to make safe and
    // easy to implement "Undo" an unknown number of transactions (including 0),
    // "above" beginBatch and endBatch. Otherwise,implementing Undo that way
    // head to dataloss: for example, if no changes were done in the
    // edit-item panel, the last transaction on the undo stack would be the
    // initial createItem transaction, or even worse, the batched editing of
    // some other item.
    // DO NOT MOVE this to the window scope, that would leak (bug 490068)!
    this.transactionManager.doTransaction({ doTransaction() {},
                                            undoTransaction() {},
                                            redoTransaction() {},
                                            isTransient: false,
                                            merge() { return false; }
                                          });
  },

  willDo: function PU_willDo() {},
  willUndo: function PU_willUndo() {},
  willRedo: function PU_willRedo() {},
  willBeginBatch: function PU_willBeginBatch() {},
  willEndBatch: function PU_willEndBatch() {},
  didEndBatch: function PU_didEndBatch() {},
  willMerge: function PU_willMerge() {},
  didMerge: function PU_didMerge() {},

  /**
   * Determines whether or not a ResultNode is a host container.
   * @param   aNode
   *          A result node
   * @returns true if the node is a host container, false otherwise
   */
  nodeIsHost: function PU_nodeIsHost(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
           aNode.parent &&
           asQuery(aNode.parent).queryOptions.resultType ==
             Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY;
  },

  /**
   * Determines whether or not a ResultNode is a day container.
   * @param   node
   *          A NavHistoryResultNode
   * @returns true if the node is a day container, false otherwise
   */
  nodeIsDay: function PU_nodeIsDay(aNode) {
    var resultType;
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
           aNode.parent &&
           ((resultType = asQuery(aNode.parent).queryOptions.resultType) ==
               Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
             resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY);
  },

  /**
   * Determines whether or not a result-node is a tag container.
   * @param   aNode
   *          A result-node
   * @returns true if the node is a tag container, false otherwise
   */
  nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
    return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
           asQuery(aNode).queryOptions.resultType ==
             Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
  },

  /**
   * Determines whether or not a ResultNode is a container.
   * @param   aNode
   *          A result node
   * @returns true if the node is a container item, false otherwise
   */
  containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
                   Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
                   Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY],
  nodeIsContainer: function PU_nodeIsContainer(aNode) {
    return this.containerTypes.includes(aNode.type);
  },

  /**
   * Determines whether or not a ResultNode is an history related container.
   * @param   node
   *          A result node
   * @returns true if the node is an history related container, false otherwise
   */
  nodeIsHistoryContainer: function PU_nodeIsHistoryContainer(aNode) {
    var resultType;
    return this.nodeIsQuery(aNode) &&
           ((resultType = asQuery(aNode).queryOptions.resultType) ==
              Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY ||
            resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
            resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY ||
            this.nodeIsDay(aNode) ||
            this.nodeIsHost(aNode));
  },

  /**
   * Gets the concrete item-id for the given node. Generally, this is just
   * node.itemId, but for folder-shortcuts that's node.folderItemId.
   */
  getConcreteItemId: function PU_getConcreteItemId(aNode) {
    if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
      return asQuery(aNode).folderItemId;
    else if (PlacesUtils.nodeIsTagQuery(aNode)) {
      // RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
      // so we can still get the concrete itemId for them.
      var queries = aNode.getQueries();
      var folders = queries[0].getFolders();
      return folders[0];
    }
    return aNode.itemId;
  },

  /**
   * Gets the concrete item-guid for the given node. For everything but folder
   * shortcuts, this is just node.bookmarkGuid.  For folder shortcuts, this is
   * node.targetFolderGuid (see nsINavHistoryService.idl for the semantics).
   *
   * @param aNode
   *        a result node.
   * @return the concrete item-guid for aNode.
   * @note unlike getConcreteItemId, this doesn't allow retrieving the guid of a
   *       ta container.
   */
  getConcreteItemGuid(aNode) {
    if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
      return asQuery(aNode).targetFolderGuid;
    return aNode.bookmarkGuid;
  },

  /**
   * Reverse a host based on the moz_places algorithm, that is reverse the host
   * string and add a trailing period.  For example "google.com" becomes
   * "moc.elgoog.".
   *
   * @param url
   *        the URL to generate a rev host for.
   * @return the reversed host string.
   */
  getReversedHost(url) {
    return url.host.split("").reverse().join("") + ".";
  },

  /**
   * String-wraps a result node according to the rules of the specified
   * content type for copy or move operations.
   *
   * @param   aNode
   *          The Result node to wrap (serialize)
   * @param   aType
   *          The content type to serialize as
   * @param   [optional] aFeedURI
   *          Used instead of the node's URI if provided.
   *          This is useful for wrapping a livemark as TYPE_X_MOZ_URL,
   *          TYPE_HTML or TYPE_UNICODE.
   * @return  A string serialization of the node
   */
  wrapNode(aNode, aType, aFeedURI) {
    // when wrapping a node, we want all the items, even if the original
    // query options are excluding them.
    // This can happen when copying from the left hand pane of the bookmarks
    // organizer.
    // @return [node, shouldClose]
    function gatherDataFromNode(node, gatherDataFunc) {
      if (PlacesUtils.nodeIsFolder(node) &&
          node.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT &&
          asQuery(node).queryOptions.excludeItems) {
        let folderRoot = PlacesUtils.getFolderContents(node.itemId, false, true).root;
        try {
          return gatherDataFunc(folderRoot);
        } finally {
          folderRoot.containerOpen = false;
        }
      }
      // If we didn't create our own query, do not alter the node's state.
      return gatherDataFunc(node);
    }

    function gatherDataHtml(node) {
      let htmlEscape = s => s.replace(/&/g, "&amp;")
                             .replace(/>/g, "&gt;")
                             .replace(/</g, "&lt;")
                             .replace(/"/g, "&quot;")
                             .replace(/'/g, "&apos;");

      // escape out potential HTML in the title
      let escapedTitle = node.title ? htmlEscape(node.title) : "";

      if (aFeedURI) {
        return `<A HREF="${aFeedURI}">${escapedTitle}</A>${NEWLINE}`;
      }

      if (PlacesUtils.nodeIsContainer(node)) {
        asContainer(node);
        let wasOpen = node.containerOpen;
        if (!wasOpen)
          node.containerOpen = true;

        let childString = "<DL><DT>" + escapedTitle + "</DT>" + NEWLINE;
        let cc = node.childCount;
        for (let i = 0; i < cc; ++i) {
          childString += "<DD>"
                         + NEWLINE
                         + gatherDataHtml(node.getChild(i))
                         + "</DD>"
                         + NEWLINE;
        }
        node.containerOpen = wasOpen;
        return childString + "</DL>" + NEWLINE;
      }
      if (PlacesUtils.nodeIsURI(node))
        return `<A HREF="${node.uri}">${escapedTitle}</A>${NEWLINE}`;
      if (PlacesUtils.nodeIsSeparator(node))
        return "<HR>" + NEWLINE;
      return "";
    }

    function gatherDataText(node) {
      if (aFeedURI) {
        return aFeedURI;
      }

      if (PlacesUtils.nodeIsContainer(node)) {
        asContainer(node);
        let wasOpen = node.containerOpen;
        if (!wasOpen)
          node.containerOpen = true;

        let childString = node.title + NEWLINE;
        let cc = node.childCount;
        for (let i = 0; i < cc; ++i) {
          let child = node.getChild(i);
          let suffix = i < (cc - 1) ? NEWLINE : "";
          childString += gatherDataText(child) + suffix;
        }
        node.containerOpen = wasOpen;
        return childString;
      }
      if (PlacesUtils.nodeIsURI(node))
        return node.uri;
      if (PlacesUtils.nodeIsSeparator(node))
        return "--------------------";
      return "";
    }

    switch (aType) {
      case this.TYPE_X_MOZ_PLACE:
      case this.TYPE_X_MOZ_PLACE_SEPARATOR:
      case this.TYPE_X_MOZ_PLACE_CONTAINER: {
        // Serialize the node to JSON.
        return serializeNode(aNode, aFeedURI);
      }
      case this.TYPE_X_MOZ_URL: {
        if (aFeedURI || PlacesUtils.nodeIsURI(aNode))
          return (aFeedURI || aNode.uri) + NEWLINE + aNode.title;
        return "";
      }
      case this.TYPE_HTML: {
        return gatherDataFromNode(aNode, gatherDataHtml);
      }
    }

    // Otherwise, we wrap as TYPE_UNICODE.
    return gatherDataFromNode(aNode, gatherDataText);
  },

  /**
   * Unwraps data from the Clipboard or the current Drag Session.
   * @param   blob
   *          A blob (string) of data, in some format we potentially know how
   *          to parse.
   * @param   type
   *          The content type of the blob.
   * @returns An array of objects representing each item contained by the source.
   */
  unwrapNodes: function PU_unwrapNodes(blob, type) {
    // We split on "\n"  because the transferable system converts "\r\n" to "\n"
    var nodes = [];
    switch (type) {
      case this.TYPE_X_MOZ_PLACE:
      case this.TYPE_X_MOZ_PLACE_SEPARATOR:
      case this.TYPE_X_MOZ_PLACE_CONTAINER:
        nodes = JSON.parse("[" + blob + "]");
        break;
      case this.TYPE_X_MOZ_URL: {
        let parts = blob.split("\n");
        // data in this type has 2 parts per entry, so if there are fewer
        // than 2 parts left, the blob is malformed and we should stop
        // but drag and drop of files from the shell has parts.length = 1
        if (parts.length != 1 && parts.length % 2)
          break;
        for (let i = 0; i < parts.length; i = i + 2) {
          let uriString = parts[i];
          let titleString = "";
          if (parts.length > i + 1)
            titleString = parts[i + 1];
          else {
            // for drag and drop of files, try to use the leafName as title
            try {
              titleString = this._uri(uriString).QueryInterface(Ci.nsIURL)
                                .fileName;
            } catch (e) {}
          }
          // note:  this._uri() will throw if uriString is not a valid URI
          if (this._uri(uriString)) {
            nodes.push({ uri: uriString,
                         title: titleString ? titleString : uriString,
                         type: this.TYPE_X_MOZ_URL });
          }
        }
        break;
      }
      case this.TYPE_UNICODE: {
        let parts = blob.split("\n");
        for (let i = 0; i < parts.length; i++) {
          let uriString = parts[i];
          // text/uri-list is converted to TYPE_UNICODE but it could contain
          // comments line prepended by #, we should skip them
          if (uriString.substr(0, 1) == "\x23")
            continue;
          // note: this._uri() will throw if uriString is not a valid URI
          if (uriString != "" && this._uri(uriString))
            nodes.push({ uri: uriString,
                         title: uriString,
                         type: this.TYPE_X_MOZ_URL });
        }
        break;
      }
      default:
        throw Cr.NS_ERROR_INVALID_ARG;
    }
    return nodes;
  },

  /**
   * Validate an input PageInfo object, returning a valid PageInfo object.
   *
   * @param pageInfo: (PageInfo)
   * @return (PageInfo)
   */
  validatePageInfo(pageInfo, validateVisits = true) {
    let info = {
      visits: [],
    };

    if (typeof pageInfo != "object" || !pageInfo) {
      throw new TypeError("pageInfo must be an object");
    }

    if (!pageInfo.url) {
      throw new TypeError("PageInfo object must have a url property");
    }

    info.url = this.normalizeToURLOrGUID(pageInfo.url);

    if (typeof pageInfo.guid === "string" && this.isValidGuid(pageInfo.guid)) {
      info.guid = pageInfo.guid;
    } else if (pageInfo.guid) {
      throw new TypeError(`guid property of PageInfo object: ${pageInfo.guid} is invalid`);
    }

    if (typeof pageInfo.title === "string") {
      info.title = pageInfo.title;
    } else if (pageInfo.title != null && pageInfo.title != undefined) {
      throw new TypeError(`title property of PageInfo object: ${pageInfo.title} must be a string if provided`);
    }

    if (typeof pageInfo.description === "string" || pageInfo.description === null) {
      info.description = pageInfo.description ? pageInfo.description.slice(0, DB_DESCRIPTION_LENGTH_MAX) : null;
    } else if (pageInfo.description !== undefined) {
      throw new TypeError(`description property of pageInfo object: ${pageInfo.description} must be either a string or null if provided`);
    }

    if (pageInfo.previewImageURL || pageInfo.previewImageURL === null) {
      let previewImageURL = pageInfo.previewImageURL;

      if (previewImageURL === null) {
        info.previewImageURL = null;
      } else if (typeof(previewImageURL) === "string" && previewImageURL.length <= DB_URL_LENGTH_MAX) {
        info.previewImageURL = new URL(previewImageURL);
      } else if (previewImageURL instanceof Ci.nsIURI && previewImageURL.spec.length <= DB_URL_LENGTH_MAX) {
        info.previewImageURL = new URL(previewImageURL.spec);
      } else if (previewImageURL instanceof URL && previewImageURL.href.length <= DB_URL_LENGTH_MAX) {
        info.previewImageURL = previewImageURL;
      } else {
        throw new TypeError("previewImageURL property of pageInfo object: ${previewImageURL} is invalid");
      }
    }

    if (!validateVisits) {
      return info;
    }

    if (!pageInfo.visits || !Array.isArray(pageInfo.visits) || !pageInfo.visits.length) {
      throw new TypeError("PageInfo object must have an array of visits");
    }

    for (let inVisit of pageInfo.visits) {
      let visit = {
        date: new Date(),
        transition: inVisit.transition || History.TRANSITIONS.LINK,
      };

      if (!PlacesUtils.history.isValidTransition(visit.transition)) {
        throw new TypeError(`transition: ${visit.transition} is not a valid transition type`);
      }

      if (inVisit.date) {
        PlacesUtils.history.ensureDate(inVisit.date);
        if (inVisit.date > (Date.now() + TIMERS_RESOLUTION_SKEW_MS)) {
          throw new TypeError(`date: ${inVisit.date} cannot be a future date`);
        }
        visit.date = inVisit.date;
      }

      if (inVisit.referrer) {
        visit.referrer = this.normalizeToURLOrGUID(inVisit.referrer);
      }
      info.visits.push(visit);
    }
    return info;
  },

  /**
   * Normalize a key to either a string (if it is a valid GUID) or an
   * instance of `URL` (if it is a `URL`, `nsIURI`, or a string
   * representing a valid url).
   *
   * @throws (TypeError)
   *         If the key is neither a valid guid nor a valid url.
   */
  normalizeToURLOrGUID(key) {
    if (typeof key === "string") {
      // A string may be a URL or a guid
      if (this.isValidGuid(key)) {
        return key;
      }
      return new URL(key);
    }
    if (key instanceof URL) {
      return key;
    }
    if (key instanceof Ci.nsIURI) {
      return new URL(key.spec);
    }
    throw new TypeError("Invalid url or guid: " + key);
  },

  /**
   * Generates a nsINavHistoryResult for the contents of a folder.
   * @param   folderId
   *          The folder to open
   * @param   [optional] excludeItems
   *          True to hide all items (individual bookmarks). This is used on
   *          the left places pane so you just get a folder hierarchy.
   * @param   [optional] expandQueries
   *          True to make query items expand as new containers. For managing,
   *          you want this to be false, for menus and such, you want this to
   *          be true.
   * @returns A nsINavHistoryResult containing the contents of the
   *          folder. The result.root is guaranteed to be open.
   */
  getFolderContents:
  function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) {
    if (typeof aFolderId !== "number") {
      throw new Error("aFolderId should be a number.");
    }
    var query = this.history.getNewQuery();
    query.setFolders([aFolderId], 1);
    var options = this.history.getNewQueryOptions();
    options.excludeItems = aExcludeItems;
    options.expandQueries = aExpandQueries;

    var result = this.history.executeQuery(query, options);
    result.root.containerOpen = true;
    return result;
  },

  /**
   * Fetch all annotations for a URI, including all properties of each
   * annotation which would be required to recreate it.
   * @param aURI
   *        The URI for which annotations are to be retrieved.
   * @return Array of objects, each containing the following properties:
   *         name, flags, expires, value
   */
  getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) {
    var annosvc = this.annotations;
    var annos = [], val = null;
    var annoNames = annosvc.getPageAnnotationNames(aURI);
    for (var i = 0; i < annoNames.length; i++) {
      var flags = {}, exp = {}, storageType = {};
      annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, storageType);
      val = annosvc.getPageAnnotation(aURI, annoNames[i]);
      annos.push({name: annoNames[i],
                  flags: flags.value,
                  expires: exp.value,
                  value: val});
    }
    return annos;
  },

  /**
   * Fetch all annotations for an item, including all properties of each
   * annotation which would be required to recreate it.
   * @param aItemId
   *        The identifier of the itme for which annotations are to be
   *        retrieved.
   * @return Array of objects, each containing the following properties:
   *         name, flags, expires, mimeType, type, value
   */
  getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) {
    var annosvc = this.annotations;
    var annos = [], val = null;
    var annoNames = annosvc.getItemAnnotationNames(aItemId);
    for (var i = 0; i < annoNames.length; i++) {
      var flags = {}, exp = {}, storageType = {};
      annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, storageType);
      val = annosvc.getItemAnnotation(aItemId, annoNames[i]);
      annos.push({name: annoNames[i],
                  flags: flags.value,
                  expires: exp.value,
                  value: val});
    }
    return annos;
  },

  /**
   * Annotate a URI with a batch of annotations.
   * @param aURI
   *        The URI for which annotations are to be set.
   * @param aAnnotations
   *        Array of objects, each containing the following properties:
   *        name, flags, expires.
   *        If the value for an annotation is not set it will be removed.
   */
  setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) {
    var annosvc = this.annotations;
    aAnnos.forEach(function(anno) {
      if (anno.value === undefined || anno.value === null) {
        annosvc.removePageAnnotation(aURI, anno.name);
      } else {
        let flags = ("flags" in anno) ? anno.flags : 0;
        let expires = ("expires" in anno) ?
          anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
        annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires);
      }
    });
  },

  /**
   * Annotate an item with a batch of annotations.
   * @param aItemId
   *        The identifier of the item for which annotations are to be set
   * @param aAnnotations
   *        Array of objects, each containing the following properties:
   *        name, flags, expires.
   *        If the value for an annotation is not set it will be removed.
   */
  setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos, aSource) {
    var annosvc = this.annotations;

    aAnnos.forEach(function(anno) {
      if (anno.value === undefined || anno.value === null) {
        annosvc.removeItemAnnotation(aItemId, anno.name, aSource);
      } else {
        let flags = ("flags" in anno) ? anno.flags : 0;
        let expires = ("expires" in anno) ?
          anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
        annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags,
                                  expires, aSource);
      }
    });
  },

  // Identifier getters for special folders.
  // You should use these everywhere PlacesUtils is available to avoid XPCOM
  // traversal just to get roots' ids.
  get placesRootId() {
    delete this.placesRootId;
    return this.placesRootId = this.bookmarks.placesRoot;
  },

  get bookmarksMenuFolderId() {
    delete this.bookmarksMenuFolderId;
    return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder;
  },

  get toolbarFolderId() {
    delete this.toolbarFolderId;
    return this.toolbarFolderId = this.bookmarks.toolbarFolder;
  },

  get tagsFolderId() {
    delete this.tagsFolderId;
    return this.tagsFolderId = this.bookmarks.tagsFolder;
  },

  get unfiledBookmarksFolderId() {
    delete this.unfiledBookmarksFolderId;
    return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder;
  },

  get mobileFolderId() {
    delete this.mobileFolderId;
    return this.mobileFolderId = this.bookmarks.mobileFolder;
  },

  /**
   * Checks if aItemId is a root.
   *
   *   @param aItemId
   *          item id to look for.
   *   @returns true if aItemId is a root, false otherwise.
   */
  isRootItem: function PU_isRootItem(aItemId) {
    return aItemId == PlacesUtils.bookmarksMenuFolderId ||
           aItemId == PlacesUtils.toolbarFolderId ||
           aItemId == PlacesUtils.unfiledBookmarksFolderId ||
           aItemId == PlacesUtils.tagsFolderId ||
           aItemId == PlacesUtils.placesRootId ||
           aItemId == PlacesUtils.mobileFolderId;
  },

  /**
   * Set the POST data associated with a bookmark, if any.
   * Used by POST keywords.
   *   @param aBookmarkId
   *
   * @deprecated Use PlacesUtils.keywords.insert() API instead.
   */
  setPostDataForBookmark(aBookmarkId, aPostData) {
    if (!aPostData)
      throw new Error("Must provide valid POST data");
    // For now we don't have a unified API to create a keyword with postData,
    // thus here we can just try to complete a keyword that should already exist
    // without any post data.
    let stmt = PlacesUtils.history.DBConnection.createStatement(
      `UPDATE moz_keywords SET post_data = :post_data
       WHERE id = (SELECT k.id FROM moz_keywords k
                   JOIN moz_bookmarks b ON b.fk = k.place_id
                   WHERE b.id = :item_id
                   AND post_data ISNULL
                   LIMIT 1)`);
    stmt.params.item_id = aBookmarkId;
    stmt.params.post_data = aPostData;
    try {
      stmt.execute();
    } finally {
      stmt.finalize();
    }

    // Update the cache.
    return (async function() {
      let guid = await PlacesUtils.promiseItemGuid(aBookmarkId);
      let bm = await PlacesUtils.bookmarks.fetch(guid);

      // Fetch keywords for this href.
      let cache = await gKeywordsCachePromise;
      for (let [ , entry ] of cache) {
        // Set the POST data on keywords not having it.
        if (entry.url.href == bm.url.href && !entry.postData) {
          entry.postData = aPostData;
        }
      }
    })().catch(Cu.reportError);
  },

  /**
   * Get the POST data associated with a bookmark, if any.
   * @param aBookmarkId
   * @returns string of POST data if set for aBookmarkId. null otherwise.
   *
   * @deprecated Use PlacesUtils.keywords.fetch() API instead.
   */
  getPostDataForBookmark(aBookmarkId) {
    let stmt = PlacesUtils.history.DBConnection.createStatement(
      `SELECT k.post_data
       FROM moz_keywords k
       JOIN moz_places h ON h.id = k.place_id
       JOIN moz_bookmarks b ON b.fk = h.id
       WHERE b.id = :item_id`);
    stmt.params.item_id = aBookmarkId;
    try {
      if (!stmt.executeStep())
        return null;
      return stmt.row.post_data;
    } finally {
      stmt.finalize();
    }
  },

  /**
   * Get the URI (and any associated POST data) for a given keyword.
   * @param aKeyword string keyword
   * @returns an array containing a string URL and a string of POST data
   *
   * @deprecated
   */
  getURLAndPostDataForKeyword(aKeyword) {
    Deprecated.warning("getURLAndPostDataForKeyword() is deprecated, please " +
                       "use PlacesUtils.keywords.fetch() instead",
                       "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294");

    let stmt = PlacesUtils.history.DBConnection.createStatement(
      `SELECT h.url, k.post_data
       FROM moz_keywords k
       JOIN moz_places h ON h.id = k.place_id
       WHERE k.keyword = :keyword`);
    stmt.params.keyword = aKeyword.toLowerCase();
    try {
      if (!stmt.executeStep())
        return [ null, null ];
      return [ stmt.row.url, stmt.row.post_data ];
    } finally {
      stmt.finalize();
    }
  },

  /**
   * Get all bookmarks for a URL, excluding items under tags.
   */
  getBookmarksForURI:
  function PU_getBookmarksForURI(aURI) {
    return this.bookmarks.getBookmarkIdsForURI(aURI);
  },

  /**
   * Get the most recently added/modified bookmark for a URL, excluding items
   * under tags.
   *
   * @param aURI
   *        nsIURI of the page we will look for.
   * @returns itemId of the found bookmark, or -1 if nothing is found.
   */
  getMostRecentBookmarkForURI:
  function PU_getMostRecentBookmarkForURI(aURI) {
    let bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI);
    return bmkIds.length ? bmkIds[0] : -1;
  },

  /**
   * Returns a nsNavHistoryContainerResultNode with forced excludeItems and
   * expandQueries.
   * @param   aNode
   *          The node to convert
   * @param   [optional] excludeItems
   *          True to hide all items (individual bookmarks). This is used on
   *          the left places pane so you just get a folder hierarchy.
   * @param   [optional] expandQueries
   *          True to make query items expand as new containers. For managing,
   *          you want this to be false, for menus and such, you want this to
   *          be true.
   * @returns A nsINavHistoryContainerResultNode containing the unfiltered
   *          contents of the container.
   * @note    The returned container node could be open or closed, we don't
   *          guarantee its status.
   */
  getContainerNodeWithOptions:
  function PU_getContainerNodeWithOptions(aNode, aExcludeItems, aExpandQueries) {
    if (!this.nodeIsContainer(aNode))
      throw Cr.NS_ERROR_INVALID_ARG;

    // excludeItems is inherited by child containers in an excludeItems view.
    var excludeItems = asQuery(aNode).queryOptions.excludeItems ||
                       asQuery(aNode.parentResult.root).queryOptions.excludeItems;
    // expandQueries is inherited by child containers in an expandQueries view.
    var expandQueries = asQuery(aNode).queryOptions.expandQueries &&
                        asQuery(aNode.parentResult.root).queryOptions.expandQueries;

    // If our options are exactly what we expect, directly return the node.
    if (excludeItems == aExcludeItems && expandQueries == aExpandQueries)
      return aNode;

    // Otherwise, get contents manually.
    var queries = {}, options = {};
    this.history.queryStringToQueries(aNode.uri, queries, {}, options);
    options.value.excludeItems = aExcludeItems;
    options.value.expandQueries = aExpandQueries;
    return this.history.executeQueries(queries.value,
                                       queries.value.length,
                                       options.value).root;
  },

  /**
   * Returns true if a container has uri nodes in its first level.
   * Has better performance than (getURLsForContainerNode(node).length > 0).
   * @param aNode
   *        The container node to search through.
   * @returns true if the node contains uri nodes, false otherwise.
   */
  hasChildURIs: function PU_hasChildURIs(aNode) {
    if (!this.nodeIsContainer(aNode))
      return false;

    let root = this.getContainerNodeWithOptions(aNode, false, true);
    let result = root.parentResult;
    let didSuppressNotifications = false;
    let wasOpen = root.containerOpen;
    if (!wasOpen) {
      didSuppressNotifications = result.suppressNotifications;
      if (!didSuppressNotifications)
        result.suppressNotifications = true;

      root.containerOpen = true;
    }

    let found = false;
    for (let i = 0; i < root.childCount && !found; i++) {
      let child = root.getChild(i);
      if (this.nodeIsURI(child))
        found = true;
    }

    if (!wasOpen) {
      root.containerOpen = false;
      if (!didSuppressNotifications)
        result.suppressNotifications = false;
    }
    return found;
  },

  /**
   * Returns an array containing all the uris in the first level of the
   * passed in container.
   * If you only need to know if the node contains uris, use hasChildURIs.
   * @param aNode
   *        The container node to search through
   * @returns array of uris in the first level of the container.
   */
  getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) {
    let urls = [];
    if (!this.nodeIsContainer(aNode))
      return urls;

    let root = this.getContainerNodeWithOptions(aNode, false, true);
    let result = root.parentResult;
    let wasOpen = root.containerOpen;
    let didSuppressNotifications = false;
    if (!wasOpen) {
      didSuppressNotifications = result.suppressNotifications;
      if (!didSuppressNotifications)
        result.suppressNotifications = true;

      root.containerOpen = true;
    }

    for (let i = 0; i < root.childCount; ++i) {
      let child = root.getChild(i);
      if (this.nodeIsURI(child))
        urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)});
    }

    if (!wasOpen) {
      root.containerOpen = false;
      if (!didSuppressNotifications)
        result.suppressNotifications = false;
    }
    return urls;
  },

  /**
   * Gets a shared Sqlite.jsm readonly connection to the Places database,
   * usable only for SELECT queries.
   *
   * This is intended to be used mostly internally, components outside of
   * Places should, when possible, use API calls and file bugs to get proper
   * APIs, where they are missing.
   * Keep in mind the Places DB schema is by no means frozen or even stable.
   * Your custom queries can - and will - break overtime.
   *
   * Example:
   * let db = await PlacesUtils.promiseDBConnection();
   * let rows = await db.executeCached(sql, params);
   */
  promiseDBConnection: () => gAsyncDBConnPromised,

  /**
   * Performs a read/write operation on the Places database through a Sqlite.jsm
   * wrapped connection to the Places database.
   *
   * This is intended to be used only by Places itself, always use APIs if you
   * need to modify the Places database. Use promiseDBConnection if you need to
   * SELECT from the database and there's no covering API.
   * Keep in mind the Places DB schema is by no means frozen or even stable.
   * Your custom queries can - and will - break overtime.
   *
   * As all operations on the Places database are asynchronous, if shutdown
   * is initiated while an operation is pending, this could cause dataloss.
   * Using `withConnectionWrapper` ensures that shutdown waits until all
   * operations are complete before proceeding.
   *
   * Example:
   * await withConnectionWrapper("Bookmarks: Remove a bookmark", Task.async(function*(db) {
   *    // Proceed with the db, asynchronously.
   *    // Shutdown will not interrupt operations that take place here.
   * }));
   *
   * @param {string} name The name of the operation. Used for debugging, logging
   *   and crash reporting.
   * @param {function(db)} task A function that takes as argument a Sqlite.jsm
   *   connection and returns a Promise. Shutdown is guaranteed to not interrupt
   *   execution of `task`.
   */
  async withConnectionWrapper(name, task) {
    if (!name) {
      throw new TypeError("Expecting a user-readable name");
    }
    let db = await gAsyncDBWrapperPromised;
    return db.executeBeforeShutdown(name, task);
  },

  /**
   * Lazily adds a bookmarks observer, waiting for the bookmarks service to be
   * alive before registering the observer.  This is especially useful in the
   * startup path, to avoid initializing the service just to add an observer.
   *
   * @param aObserver
   *        Object implementing nsINavBookmarkObserver
   * @param [optional]aWeakOwner
   *        Whether to use weak ownership.
   *
   * @note Correct functionality of lazy observers relies on the fact Places
   *       notifies categories before real observers, and uses
   *       PlacesCategoriesStarter component to kick-off the registration.
   */
  addLazyBookmarkObserver(aObserver, aWeakOwner) {
    Deprecated.warning(`PlacesUtils.addLazyBookmarkObserver() is deprecated.
                        Please use PlacesUtils.bookmarks.addObserver()`,
                       "https://bugzilla.mozilla.org/show_bug.cgi?id=1371677");
    this.bookmarks.addObserver(aObserver, aWeakOwner === true);
  },

  /**
   * Removes a bookmarks observer added through addLazyBookmarkObserver.
   *
   * @param aObserver
   *        Object implementing nsINavBookmarkObserver
   */
  removeLazyBookmarkObserver(aObserver) {
    Deprecated.warning(`PlacesUtils.removeLazyBookmarkObserver() is deprecated.
                        Please use PlacesUtils.bookmarks.removeObserver()`,
                       "https://bugzilla.mozilla.org/show_bug.cgi?id=1371677");
    this.bookmarks.removeObserver(aObserver);
  },

  /**
   * Sets the character-set for a URI.
   *
   * @param {nsIURI} aURI
   * @param {String} aCharset character-set value.
   * @return {Promise}
   */
  setCharsetForURI: function PU_setCharsetForURI(aURI, aCharset) {
    return new Promise(resolve => {

      // Delaying to catch issues with asynchronous behavior while waiting
      // to implement asynchronous annotations in bug 699844.
      Services.tm.dispatchToMainThread(function() {
        if (aCharset && aCharset.length > 0) {
          PlacesUtils.annotations.setPageAnnotation(
            aURI, PlacesUtils.CHARSET_ANNO, aCharset, 0,
            Ci.nsIAnnotationService.EXPIRE_NEVER);
        } else {
          PlacesUtils.annotations.removePageAnnotation(
            aURI, PlacesUtils.CHARSET_ANNO);
        }
        resolve();
      });

    });
  },

  /**
   * Gets the last saved character-set for a URI.
   *
   * @param aURI nsIURI
   * @return {Promise}
   * @resolve a character-set or null.
   */
  getCharsetForURI: function PU_getCharsetForURI(aURI) {
    return new Promise(resolve => {

      Services.tm.dispatchToMainThread(function() {
        let charset = null;

        try {
          charset = PlacesUtils.annotations.getPageAnnotation(aURI,
                                                              PlacesUtils.CHARSET_ANNO);
        } catch (ex) { }

        resolve(charset);
      });

    });
  },

  /**
   * Deprecated wrapper for History.jsm::fetch.
   *
   * @param aPlaceIdentifier
   *        either an URL or a GUID (@see History.jsm::fetch)
   * @return {Promise}.
   * @resolve a PageInfo
   * @reject if there is an error in the place identifier
   */
  promisePlaceInfo(aPlaceIdentifier) {
    Deprecated.warning(`PlacesUtils.promisePlaceInfo() is deprecated.
                        Please use PlacesUtils.history.fetch()`,
                       "https://bugzilla.mozilla.org/show_bug.cgi?id=1350377");
    return PlacesUtils.history.fetch(aPlaceIdentifier);
  },

  /**
   * Gets favicon data for a given page url.
   *
   * @param aPageUrl url of the page to look favicon for.
   * @resolves to an object representing a favicon entry, having the following
   *           properties: { uri, dataLen, data, mimeType }
   * @rejects JavaScript exception if the given url has no associated favicon.
   */
  promiseFaviconData(aPageUrl) {
    return new Promise((resolve, reject) => {
      PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(aPageUrl),
        function(aURI, aDataLen, aData, aMimeType) {
          if (aURI) {
            resolve({ uri: aURI,
                               dataLen: aDataLen,
                               data: aData,
                               mimeType: aMimeType });
          } else {
            reject();
          }
        });
    });
  },

  /**
   * Gets the favicon link url (moz-anno:) for a given page url.
   *
   * @param aPageURL url of the page to lookup the favicon for.
   * @resolves to the nsIURL of the favicon link
   * @rejects if the given url has no associated favicon.
   */
  promiseFaviconLinkUrl(aPageUrl) {
    return new Promise((resolve, reject) => {
      if (!(aPageUrl instanceof Ci.nsIURI))
        aPageUrl = NetUtil.newURI(aPageUrl);

      PlacesUtils.favicons.getFaviconURLForPage(aPageUrl, uri => {
        if (uri) {
          uri = PlacesUtils.favicons.getFaviconLinkForIcon(uri);
          resolve(uri);
        } else {
          reject("favicon not found for uri");
        }
      });
    });
  },

   /**
   * Returns the passed URL with a #size ref for the specified size and
   * devicePixelRatio.
   *
   * @param window
   *        The window where the icon will appear.
   * @param href
   *        The string href we should add the ref to.
   * @param size
   *        The target image size
   * @return The URL with the fragment at the end, in the same formar as input.
   */
  urlWithSizeRef(window, href, size) {
    return href + (href.includes("#") ? "&" : "#") +
           "size=" + (Math.round(size) * window.devicePixelRatio);
  },

  /**
   * Get the unique id for an item (a bookmark, a folder or a separator) given
   * its item id.
   *
   * @param aItemId
   *        an item id
   * @return {Promise}
   * @resolves to the GUID.
   * @rejects if aItemId is invalid.
   */
  promiseItemGuid(aItemId) {
    return GuidHelper.getItemGuid(aItemId)
  },

  /**
   * Get the item id for an item (a bookmark, a folder or a separator) given
   * its unique id.
   *
   * @param aGuid
   *        an item GUID
   * @return {Promise}
   * @resolves to the item id.
   * @rejects if there's no item for the given GUID.
   */
  promiseItemId(aGuid) {
    return GuidHelper.getItemId(aGuid)
  },

  /**
   * Get the item ids for multiple items (a bookmark, a folder or a separator)
   * given the unique ids for each item.
   *
   * @param {Array} aGuids An array of item GUIDs.
   * @return {Promise}
   * @resolves to a Map of item ids.
   * @rejects if not all of the GUIDs could be found.
   */
  promiseManyItemIds(aGuids) {
    return GuidHelper.getManyItemIds(aGuids);
  },

  /**
   * Invalidate the GUID cache for the given itemId.
   *
   * @param aItemId
   *        an item id
   */
  invalidateCachedGuidFor(aItemId) {
    GuidHelper.invalidateCacheForItemId(aItemId)
  },

  /**
   * Asynchronously retrieve a JS-object representation of a places bookmarks
   * item (a bookmark, a folder, or a separator) along with all of its
   * descendants.
   *
   * @param [optional] aItemGuid
   *        the (topmost) item to be queried.  If it's not passed, the places
   *        root is queried: that is, you get a representation of the entire
   *        bookmarks hierarchy.
   * @param [optional] aOptions
   *        Options for customizing the query behavior, in the form of a JS
   *        object with any of the following properties:
   *         - excludeItemsCallback: a function for excluding items, along with
   *           their descendants.  Given an item object (that has everything set
   *           apart its potential children data), it should return true if the
   *           item should be excluded.  Once an item is excluded, the function
   *           isn't called for any of its descendants.  This isn't called for
   *           the root item.
   *           WARNING: since the function may be called for each item, using
   *           this option can slow down the process significantly if the
   *           callback does anything that's not relatively trivial.  It is
   *           highly recommended to avoid any synchronous I/O or DB queries.
   *        - includeItemIds: opt-in to include the deprecated id property.
   *          Use it if you must. It'll be removed once the switch to GUIDs is
   *          complete.
   *
   * @return {Promise}
   * @resolves to a JS object that represents either a single item or a
   * bookmarks tree.  Each node in the tree has the following properties set:
   *  - guid (string): the item's GUID (same as aItemGuid for the top item).
   *  - [deprecated] id (number): the item's id. This is only if
   *    aOptions.includeItemIds is set.
   *  - type (string):  the item's type.  @see PlacesUtils.TYPE_X_*
   *  - title (string): the item's title. If it has no title, this property
   *    isn't set.
   *  - dateAdded (number, microseconds from the epoch): the date-added value of
   *    the item.
   *  - lastModified (number, microseconds from the epoch): the last-modified
   *    value of the item.
   *  - annos (see getAnnotationsForItem): the item's annotations.  This is not
   *    set if there are no annotations set for the item).
   *  - index: the item's index under it's parent.
   *
   * The root object (i.e. the one for aItemGuid) also has the following
   * properties set:
   *  - parentGuid (string): the GUID of the root's parent.  This isn't set if
   *    the root item is the places root.
   *  - itemsCount (number, not enumerable): the number of items, including the
   *    root item itself, which are represented in the resolved object.
   *
   * Bookmark items also have the following properties:
   *  - uri (string): the item's url.
   *  - tags (string): csv string of the bookmark's tags.
   *  - charset (string): the last known charset of the bookmark.
   *  - keyword (string): the bookmark's keyword (unset if none).
   *  - postData (string): the bookmark's keyword postData (unset if none).
   *  - iconuri (string): the bookmark's favicon url.
   * The last four properties are not set at all if they're irrelevant (e.g.
   * |charset| is not set if no charset was previously set for the bookmark
   * url).
   *
   * Folders may also have the following properties:
   *  - children (array): the folder's children information, each of them
   *    having the same set of properties as above.
   *
   * @rejects if the query failed for any reason.
   * @note if aItemGuid points to a non-existent item, the returned promise is
   * resolved to null.
   */
  async promiseBookmarksTree(aItemGuid = "", aOptions = {}) {
    let createItemInfoObject = async function(aRow, aIncludeParentGuid) {
      let item = {};
      let copyProps = (...props) => {
        for (let prop of props) {
          let val = aRow.getResultByName(prop);
          if (val !== null)
            item[prop] = val;
        }
      };
      copyProps("guid", "title", "index", "dateAdded", "lastModified");
      if (aIncludeParentGuid)
        copyProps("parentGuid");

      let itemId = aRow.getResultByName("id");
      if (aOptions.includeItemIds)
        item.id = itemId;

      // Cache it for promiseItemId consumers regardless.
      GuidHelper.updateCache(itemId, item.guid);

      let type = aRow.getResultByName("type");
      if (type == Ci.nsINavBookmarksService.TYPE_BOOKMARK)
        copyProps("charset", "tags", "iconuri");

      // Add annotations.
      if (aRow.getResultByName("has_annos")) {
        try {
          item.annos = PlacesUtils.getAnnotationsForItem(itemId);
        } catch (e) {
          Cu.reportError("Unexpected error while reading annotations " + e);
        }
      }

      switch (type) {
        case Ci.nsINavBookmarksService.TYPE_BOOKMARK:
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE;
          // If this throws due to an invalid url, the item will be skipped.
          item.uri = NetUtil.newURI(aRow.getResultByName("url")).spec;
          // Keywords are cached, so this should be decently fast.
          let entry = await PlacesUtils.keywords.fetch({ url: item.uri });
          if (entry) {
            item.keyword = entry.keyword;
            item.postData = entry.postData;
          }
          break;
        case Ci.nsINavBookmarksService.TYPE_FOLDER:
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
          // Mark root folders.
          if (itemId == PlacesUtils.placesRootId)
            item.root = "placesRoot";
          else if (itemId == PlacesUtils.bookmarksMenuFolderId)
            item.root = "bookmarksMenuFolder";
          else if (itemId == PlacesUtils.unfiledBookmarksFolderId)
            item.root = "unfiledBookmarksFolder";
          else if (itemId == PlacesUtils.toolbarFolderId)
            item.root = "toolbarFolder";
          else if (itemId == PlacesUtils.mobileFolderId)
            item.root = "mobileFolder";
          break;
        case Ci.nsINavBookmarksService.TYPE_SEPARATOR:
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
          break;
        default:
          Cu.reportError("Unexpected bookmark type");
          break;
      }
      return item;
    };

    const QUERY_STR =
      `/* do not warn (bug no): cannot use an index */
       WITH RECURSIVE
       descendants(fk, level, type, id, guid, parent, parentGuid, position,
                   title, dateAdded, lastModified) AS (
         SELECT b1.fk, 0, b1.type, b1.id, b1.guid, b1.parent,
                (SELECT guid FROM moz_bookmarks WHERE id = b1.parent),
                b1.position, b1.title, b1.dateAdded, b1.lastModified
         FROM moz_bookmarks b1 WHERE b1.guid=:item_guid
         UNION ALL
         SELECT b2.fk, level + 1, b2.type, b2.id, b2.guid, b2.parent,
                descendants.guid, b2.position, b2.title, b2.dateAdded,
                b2.lastModified
         FROM moz_bookmarks b2
         JOIN descendants ON b2.parent = descendants.id AND b2.id <> :tags_folder)
       SELECT d.level, d.id, d.guid, d.parent, d.parentGuid, d.type,
              d.position AS [index], IFNULL(d.title, "") AS title, d.dateAdded,
              d.lastModified, h.url, (SELECT icon_url FROM moz_icons i
                      JOIN moz_icons_to_pages ON icon_id = i.id
                      JOIN moz_pages_w_icons pi ON page_id = pi.id
                      WHERE pi.page_url_hash = hash(h.url) AND pi.page_url = h.url
                      ORDER BY width DESC LIMIT 1) AS iconuri,
              (SELECT GROUP_CONCAT(t.title, ',')
               FROM moz_bookmarks b2
               JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder
               WHERE b2.fk = h.id
              ) AS tags,
              EXISTS (SELECT 1 FROM moz_items_annos
                      WHERE item_id = d.id LIMIT 1) AS has_annos,
              (SELECT a.content FROM moz_annos a
               JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
               WHERE place_id = h.id AND n.name = :charset_anno
              ) AS charset
       FROM descendants d
       LEFT JOIN moz_bookmarks b3 ON b3.id = d.parent
       LEFT JOIN moz_places h ON h.id = d.fk
       ORDER BY d.level, d.parent, d.position`;


    if (!aItemGuid)
      aItemGuid = this.bookmarks.rootGuid;

    let hasExcludeItemsCallback =
      aOptions.hasOwnProperty("excludeItemsCallback");
    let excludedParents = new Set();
    let shouldExcludeItem = (aItem, aParentGuid) => {
      let exclude = excludedParents.has(aParentGuid) ||
                    aOptions.excludeItemsCallback(aItem);
      if (exclude) {
        if (aItem.type == this.TYPE_X_MOZ_PLACE_CONTAINER)
          excludedParents.add(aItem.guid);
      }
      return exclude;
    };

    let rootItem = null;
    let parentsMap = new Map();
    let conn = await this.promiseDBConnection();
    let rows = await conn.executeCached(QUERY_STR,
        { tags_folder: PlacesUtils.tagsFolderId,
          charset_anno: PlacesUtils.CHARSET_ANNO,
          item_guid: aItemGuid });
    let yieldCounter = 0;
    for (let row of rows) {
      let item;
      if (!rootItem) {
        try {
          // This is the first row.
          rootItem = item = await createItemInfoObject(row, true);
          Object.defineProperty(rootItem, "itemsCount", { value: 1,
                                                          writable: true,
                                                          enumerable: false,
                                                          configurable: false });
        } catch (ex) {
          throw new Error("Failed to fetch the data for the root item " + ex);
        }
      } else {
        try {
          // Our query guarantees that we always visit parents ahead of their
          // children.
          item = await createItemInfoObject(row, false);
          let parentGuid = row.getResultByName("parentGuid");
          if (hasExcludeItemsCallback && shouldExcludeItem(item, parentGuid))
            continue;

          let parentItem = parentsMap.get(parentGuid);
          if ("children" in parentItem)
            parentItem.children.push(item);
          else
            parentItem.children = [item];

          rootItem.itemsCount++;
        } catch (ex) {
          // This is a bogus child, report and skip it.
          Cu.reportError("Failed to fetch the data for an item " + ex);
          continue;
        }
      }

      if (item.type == this.TYPE_X_MOZ_PLACE_CONTAINER)
        parentsMap.set(item.guid, item);

      // With many bookmarks we end up stealing the CPU - even with yielding!
      // So we let everyone else have a go every few items (bug 1186714).
      if (++yieldCounter % 50 == 0) {
        await new Promise(resolve => {
          Services.tm.dispatchToMainThread(resolve);
        });
      }
    }

    return rootItem;
  }
};

XPCOMUtils.defineLazyGetter(PlacesUtils, "history", function() {
  let hs = Cc["@mozilla.org/browser/nav-history-service;1"]
             .getService(Ci.nsINavHistoryService)
             .QueryInterface(Ci.nsIBrowserHistory)
             .QueryInterface(Ci.nsPIPlacesDatabase);
  return Object.freeze(new Proxy(hs, {
    get(target, name) {
      let property, object;
      if (name in target) {
        property = target[name];
        object = target;
      } else {
        property = History[name];
        object = History;
      }
      if (typeof property == "function") {
        return property.bind(object);
      }
      return property;
    }
  }));
});

XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory",
                                   "@mozilla.org/browser/history;1",
                                   "mozIAsyncHistory");

XPCOMUtils.defineLazyGetter(PlacesUtils, "bhistory", function() {
  return PlacesUtils.history;
});

XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "favicons",
                                   "@mozilla.org/browser/favicon-service;1",
                                   "mozIAsyncFavicons");

XPCOMUtils.defineLazyServiceGetter(this, "bmsvc",
                                   "@mozilla.org/browser/nav-bookmarks-service;1",
                                   "nsINavBookmarksService");
XPCOMUtils.defineLazyGetter(PlacesUtils, "bookmarks", () => {
  return Object.freeze(new Proxy(Bookmarks, {
    get: (target, name) => Bookmarks.hasOwnProperty(name) ? Bookmarks[name]
                                                          : bmsvc[name]
  }));
});

XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "annotations",
                                   "@mozilla.org/browser/annotation-service;1",
                                   "nsIAnnotationService");

XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging",
                                   "@mozilla.org/browser/tagging-service;1",
                                   "nsITaggingService");

XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "livemarks",
                                   "@mozilla.org/browser/livemark-service;2",
                                   "mozIAsyncLivemarks");

XPCOMUtils.defineLazyGetter(PlacesUtils, "keywords", () => {
  gKeywordsCachePromise.catch(Cu.reportError);
  return Keywords;
});

XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() {
  let tm = Cc["@mozilla.org/transactionmanager;1"].
           createInstance(Ci.nsITransactionManager);
  tm.AddListener(PlacesUtils);
  this.registerShutdownFunction(function() {
    // Clear all references to local transactions in the transaction manager,
    // this prevents from leaking it.
    this.transactionManager.RemoveListener(this);
    this.transactionManager.clear();
  });

  // Bug 750269
  // The transaction manager keeps strong references to transactions, and by
  // that, also to the global for each transaction.  A transaction, however,
  // could be either the transaction itself (for which the global is this
  // module) or some js-proxy in another global, usually a window.  The later
  // would leak because the transaction lifetime (in the manager's stacks)
  // is independent of the global from which doTransaction was called.
  // To avoid such a leak, we hide the native doTransaction from callers,
  // and let each doTransaction call go through this module.
  // Doing so ensures that, as long as the transaction is any of the
  // PlacesXXXTransaction objects declared in this module, the object
  // referenced by the transaction manager has the module itself as global.
  return Object.create(tm, {
    "doTransaction": {
      value(aTransaction) {
        tm.doTransaction(aTransaction);
      }
    }
  });
});

XPCOMUtils.defineLazyGetter(this, "bundle", function() {
  const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
  return Cc["@mozilla.org/intl/stringbundle;1"].
         getService(Ci.nsIStringBundleService).
         createBundle(PLACES_STRING_BUNDLE_URI);
});

/**
 * Setup internal databases for closing properly during shutdown.
 *
 * 1. Places initiates shutdown.
 * 2. Before places can move to the step where it closes the low-level connection,
 *   we need to make sure that we have closed `conn`.
 * 3. Before we can close `conn`, we need to make sure that all external clients
 *   have stopped using `conn`.
 * 4. Before we can close Sqlite, we need to close `conn`.
 */
function setupDbForShutdown(conn, name) {
  try {
    let state = "0. Not started.";
    let promiseClosed = new Promise((resolve, reject) => {
      // The service initiates shutdown.
      // Before it can safely close its connection, we need to make sure
      // that we have closed the high-level connection.
      try {
        PlacesUtils.history.connectionShutdownClient.jsclient.addBlocker(
          `${name} closing as part of Places shutdown`,
          async function() {
            state = "1. Service has initiated shutdown";

            // At this stage, all external clients have finished using the
            // database. We just need to close the high-level connection.
            await conn.close();
            state = "2. Closed Sqlite.jsm connection.";

            resolve();
          },
          () => state
        );
      } catch (ex) {
        // It's too late to block shutdown, just close the connection.
        conn.close();
        reject(ex);
      }
    });

    // Make sure that Sqlite.jsm doesn't close until we are done
    // with the high-level connection.
    Sqlite.shutdown.addBlocker(`${name} must be closed before Sqlite.jsm`,
      () => promiseClosed.catch(Cu.reportError),
      () => state
    );
  } catch (ex) {
    // It's too late to block shutdown, just close the connection.
    conn.close();
    throw ex;
  }
}

XPCOMUtils.defineLazyGetter(this, "gAsyncDBConnPromised",
  () => Sqlite.cloneStorageConnection({
    connection: PlacesUtils.history.DBConnection,
    readOnly:   true
  }).then(conn => {
      setupDbForShutdown(conn, "PlacesUtils read-only connection");
      return conn;
  }).catch(Cu.reportError)
);

XPCOMUtils.defineLazyGetter(this, "gAsyncDBWrapperPromised",
  () => Sqlite.wrapStorageConnection({
      connection: PlacesUtils.history.DBConnection,
  }).then(conn => {
    setupDbForShutdown(conn, "PlacesUtils wrapped connection");
    return conn;
  }).catch(Cu.reportError)
);

/**
 * Keywords management API.
 * Sooner or later these keywords will merge with search keywords, this is an
 * interim API that should then be replaced by a unified one.
 * Keywords are associated with URLs and can have POST data.
 * A single URL can have multiple keywords, provided they differ by POST data.
 */
var Keywords = {
  /**
   * Fetches a keyword entry based on keyword or URL.
   *
   * @param keywordOrEntry
   *        Either the keyword to fetch or an entry providing keyword
   *        or url property to find keywords for.  If both properties are set,
   *        this returns their intersection.
   * @param onResult [optional]
   *        Callback invoked for each found entry.
   * @return {Promise}
   * @resolves to an object in the form: { keyword, url, postData },
   *           or null if a keyword entry was not found.
   */
  fetch(keywordOrEntry, onResult = null) {
    if (typeof(keywordOrEntry) == "string")
      keywordOrEntry = { keyword: keywordOrEntry };

    if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
        (("keyword" in keywordOrEntry) && typeof(keywordOrEntry.keyword) != "string"))
      throw new Error("Invalid keyword");

    let hasKeyword = "keyword" in keywordOrEntry;
    let hasUrl = "url" in keywordOrEntry;

    if (!hasKeyword && !hasUrl)
      throw new Error("At least keyword or url must be provided");
    if (onResult && typeof onResult != "function")
      throw new Error("onResult callback must be a valid function");

    if (hasUrl)
      keywordOrEntry.url = new URL(keywordOrEntry.url);
    if (hasKeyword)
      keywordOrEntry.keyword = keywordOrEntry.keyword.trim().toLowerCase();

    let safeOnResult = entry => {
      if (onResult) {
        try {
          onResult(entry);
        } catch (ex) {
          Cu.reportError(ex);
        }
      }
    };

    return gKeywordsCachePromise.then(cache => {
      let entries = [];
      if (hasKeyword) {
        let entry = cache.get(keywordOrEntry.keyword);
        if (entry)
          entries.push(entry);
      }
      if (hasUrl) {
        for (let entry of cache.values()) {
          if (entry.url.href == keywordOrEntry.url.href)
            entries.push(entry);
        }
      }

      entries = entries.filter(e => {
        return (!hasUrl || e.url.href == keywordOrEntry.url.href) &&
               (!hasKeyword || e.keyword == keywordOrEntry.keyword);
      });

      entries.forEach(safeOnResult);
      return entries.length ? entries[0] : null;
    });
  },

  /**
   * Adds a new keyword and postData for the given URL.
   *
   * @param keywordEntry
   *        An object describing the keyword to insert, in the form:
   *        {
   *          keyword: non-empty string,
   *          URL: URL or href to associate to the keyword,
   *          postData: optional POST data to associate to the keyword
   *          source: The change source, forwarded to all bookmark observers.
   *            Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *        }
   * @note Do not define a postData property if there isn't any POST data.
   * @resolves when the addition is complete.
   */
  insert(keywordEntry) {
    if (!keywordEntry || typeof keywordEntry != "object")
      throw new Error("Input should be a valid object");

    if (!("keyword" in keywordEntry) || !keywordEntry.keyword ||
        typeof(keywordEntry.keyword) != "string")
      throw new Error("Invalid keyword");
    if (("postData" in keywordEntry) && keywordEntry.postData &&
                                        typeof(keywordEntry.postData) != "string")
      throw new Error("Invalid POST data");
    if (!("url" in keywordEntry))
      throw new Error("undefined is not a valid URL");

    if (!("source" in keywordEntry)) {
      keywordEntry.source = PlacesUtils.bookmarks.SOURCES.DEFAULT;
    }
    let { keyword, url, source } = keywordEntry;
    keyword = keyword.trim().toLowerCase();
    let postData = keywordEntry.postData || null;
    // This also checks href for validity
    url = new URL(url);

    return PlacesUtils.withConnectionWrapper("Keywords.insert", async function(db) {
        let cache = await gKeywordsCachePromise;

        // Trying to set the same keyword is a no-op.
        let oldEntry = cache.get(keyword);
        if (oldEntry && oldEntry.url.href == url.href &&
                        oldEntry.postData == keywordEntry.postData) {
          return;
        }

        // A keyword can only be associated to a single page.
        // If another page is using the new keyword, we must update the keyword
        // entry.
        // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
        // trigger.
        if (oldEntry) {
          await db.executeCached(
            `UPDATE moz_keywords
             SET place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
                 post_data = :post_data
             WHERE keyword = :keyword
            `, { url: url.href, keyword, post_data: postData });
          await notifyKeywordChange(oldEntry.url.href, "", source);
        } else {
          // An entry for the given page could be missing, in such a case we need to
          // create it.  The IGNORE conflict can trigger on `guid`.
          await db.executeCached(
            `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
             VALUES (:url, hash(:url), :rev_host, 0, :frecency,
                     IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
                            GENERATE_GUID()))
            `, { url: url.href, rev_host: PlacesUtils.getReversedHost(url),
                 frecency: url.protocol == "place:" ? 0 : -1 });
          await db.executeCached(
            `INSERT INTO moz_keywords (keyword, place_id, post_data)
             VALUES (:keyword, (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :post_data)
            `, { url: url.href, keyword, post_data: postData });
        }

        await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
          db, url, PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source));

        cache.set(keyword, { keyword, url, postData });

        // In any case, notify about the new keyword.
        await notifyKeywordChange(url.href, keyword, source);
      }
    );
  },

  /**
   * Removes a keyword.
   *
   * @param keyword
   *        The keyword to remove.
   * @return {Promise}
   * @resolves when the removal is complete.
   */
  remove(keywordOrEntry) {
    if (typeof(keywordOrEntry) == "string") {
      keywordOrEntry = {
        keyword: keywordOrEntry,
        source: Ci.nsINavBookmarksService.SOURCE_DEFAULT
      };
    }

    if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
        !keywordOrEntry.keyword || typeof keywordOrEntry.keyword != "string")
      throw new Error("Invalid keyword");

    let { keyword,
          source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordOrEntry;
    keyword = keywordOrEntry.keyword.trim().toLowerCase();
    return PlacesUtils.withConnectionWrapper("Keywords.remove", async function(db) {
      let cache = await gKeywordsCachePromise;
      if (!cache.has(keyword))
        return;
      let { url } = cache.get(keyword);
      cache.delete(keyword);

      await db.execute(`DELETE FROM moz_keywords WHERE keyword = :keyword`,
                       { keyword });

      await PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
        db, url, PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source));

      // Notify bookmarks about the removal.
      await notifyKeywordChange(url.href, "", source);
    });
  }
};

// Set by the keywords API to distinguish notifications fired by the old API.
// Once the old API will be gone, we can remove this and stop observing.
var gIgnoreKeywordNotifications = false;

XPCOMUtils.defineLazyGetter(this, "gKeywordsCachePromise", () =>
  PlacesUtils.withConnectionWrapper("PlacesUtils: gKeywordsCachePromise",
    async function(db) {
      let cache = new Map();

      // Start observing changes to bookmarks. For now we are going to keep that
      // relation for backwards compatibility reasons, but mostly because we are
      // lacking a UI to manage keywords directly.
      let observer = {
        QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
        onBeginUpdateBatch() {},
        onEndUpdateBatch() {},
        onItemAdded() {},
        onItemVisited() {},
        onItemMoved() {},

        onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
          if (itemType != PlacesUtils.bookmarks.TYPE_BOOKMARK)
            return;

          let keywords = keywordsForHref(uri.spec);
          // This uri has no keywords associated, so there's nothing to do.
          if (keywords.length == 0)
            return;

          (async function() {
            // If the uri is not bookmarked anymore, we can remove this keyword.
            let bookmark = await PlacesUtils.bookmarks.fetch({ url: uri });
            if (!bookmark) {
              for (let keyword of keywords) {
                await PlacesUtils.keywords.remove(keyword);
              }
            }
          })().catch(Cu.reportError);
        },

        onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid,
                      parentGuid, oldVal) {
          if (gIgnoreKeywordNotifications) {
            return;
          }

          if (prop == "keyword") {
            this._onKeywordChanged(guid, val, oldVal);
          } else if (prop == "uri") {
            this._onUrlChanged(guid, val, oldVal).catch(Cu.reportError);
          }
        },

        _onKeywordChanged(guid, keyword, href) {
          if (keyword.length == 0) {
            // We are removing a keyword.
            let keywords = keywordsForHref(href)
            for (let kw of keywords) {
              cache.delete(kw);
            }
          } else {
            // We are adding a new keyword.
            cache.set(keyword, { keyword, url: new URL(href) });
          }
        },

        async _onUrlChanged(guid, url, oldUrl) {
          // Check if the old url is associated with keywords.
          let entries = [];
          await PlacesUtils.keywords.fetch({ url: oldUrl }, e => entries.push(e));
          if (entries.length == 0) {
            return;
          }

          // Move the keywords to the new url.
          for (let entry of entries) {
            await PlacesUtils.keywords.remove(entry.keyword);
            entry.url = new URL(url);
            await PlacesUtils.keywords.insert(entry);
          }
        },
      };

      PlacesUtils.bookmarks.addObserver(observer);
      PlacesUtils.registerShutdownFunction(() => {
        PlacesUtils.bookmarks.removeObserver(observer);
      });

      let rows = await db.execute(
        `SELECT keyword, url, post_data
         FROM moz_keywords k
         JOIN moz_places h ON h.id = k.place_id
        `);
      let brokenKeywords = [];
      for (let row of rows) {
        let keyword = row.getResultByName("keyword");
        try {
          let entry = { keyword,
                        url: new URL(row.getResultByName("url")),
                        postData: row.getResultByName("post_data") };
          cache.set(keyword, entry);
        } catch (ex) {
          // The url is invalid, don't load the keyword and remove it, or it
          // would break the whole keywords API.
          brokenKeywords.push(keyword);
        }
      }

      if (brokenKeywords.length) {
        await db.execute(
          `DELETE FROM moz_keywords
           WHERE keyword IN (${brokenKeywords.map(JSON.stringify).join(",")})
          `);
      }

      // Helper to get a keyword from an href.
      function keywordsForHref(href) {
        let keywords = [];
        for (let [ key, val ] of cache) {
          if (val.url.href == href)
            keywords.push(key);
        }
        return keywords;
      }

      return cache;
    }
));

// Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
// itemIds will be deprecated in favour of GUIDs, which play much better
// with multiple undo/redo operations.  Because these GUIDs are already stored,
// and because we don't want to revise the transactions API once more when this
// happens, transactions are set to work with GUIDs exclusively, in the sense
// that they may never expose itemIds, nor do they accept them as input.
// More importantly, transactions which add or remove items guarantee to
// restore the GUIDs on undo/redo, so that the following transactions that may
// done or undo can assume the items they're interested in are stil accessible
// through the same GUID.
// The current bookmarks API, however, doesn't expose the necessary means for
// working with GUIDs.  So, until it does, this helper object accesses the
// Places database directly in order to switch between GUIDs and itemIds, and
// "restore" GUIDs on items re-created items.
var GuidHelper = {
  // Cache for GUID<->itemId paris.
  guidsForIds: new Map(),
  idsForGuids: new Map(),

  async getItemId(aGuid) {
    let cached = this.idsForGuids.get(aGuid);
    if (cached !== undefined)
      return cached;

    let itemId = await PlacesUtils.withConnectionWrapper("GuidHelper.getItemId",
                                                         async function(db) {
      let rows = await db.executeCached(
        "SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid LIMIT 1",
        { guid: aGuid });
      if (rows.length == 0)
        throw new Error("no item found for the given GUID");

      return rows[0].getResultByName("id");
    });

    this.updateCache(itemId, aGuid);
    return itemId;
  },

  async getManyItemIds(aGuids) {
    let uncachedGuids = aGuids.filter(guid => !this.idsForGuids.has(guid));
    if (uncachedGuids.length) {
      await PlacesUtils.withConnectionWrapper("GuidHelper.getItemId",
                                              async db => {
        while (uncachedGuids.length) {
          let chunk = uncachedGuids.splice(0, 100);
          let rows = await db.executeCached(
            `SELECT b.id, b.guid from moz_bookmarks b WHERE
             b.guid IN (${"?,".repeat(chunk.length - 1) + "?"})
             LIMIT ${chunk.length}`, chunk);
          if (rows.length < chunk.length)
            throw new Error("Not all items were found!");
          for (let row of rows) {
            this.updateCache(row.getResultByIndex(0), row.getResultByIndex(1));
          }
        }
      });
    }
    return new Map(aGuids.map(guid => [guid, this.idsForGuids.get(guid)]));
  },

  async getItemGuid(aItemId) {
    let cached = this.guidsForIds.get(aItemId);
    if (cached !== undefined)
      return cached;

    let guid = await PlacesUtils.withConnectionWrapper("GuidHelper.getItemGuid",
                                                       async function(db) {

      let rows = await db.executeCached(
        "SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id LIMIT 1",
        { id: aItemId });
      if (rows.length == 0)
        throw new Error("no item found for the given itemId");

      return rows[0].getResultByName("guid");
    });

    this.updateCache(aItemId, guid);
    return guid;
  },

  /**
   * Updates the cache.
   *
   * @note This is the only place where the cache should be populated,
   *       invalidation relies on both Maps being populated at the same time.
   */
  updateCache(aItemId, aGuid) {
    if (typeof(aItemId) != "number" || aItemId <= 0)
      throw new Error("Trying to update the GUIDs cache with an invalid itemId");
    if (typeof(aGuid) != "string" || !/^[a-zA-Z0-9\-_]{12}$/.test(aGuid))
      throw new Error("Trying to update the GUIDs cache with an invalid GUID");
    this.ensureObservingRemovedItems();
    this.guidsForIds.set(aItemId, aGuid);
    this.idsForGuids.set(aGuid, aItemId);
  },

  invalidateCacheForItemId(aItemId) {
    let guid = this.guidsForIds.get(aItemId);
    this.guidsForIds.delete(aItemId);
    this.idsForGuids.delete(guid);
  },

  ensureObservingRemovedItems() {
    if (!("observer" in this)) {
      /**
       * This observers serves two purposes:
       * (1) Invalidate cached id<->GUID paris on when items are removed.
       * (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
      *      So, for exmaple, when the NewBookmark needs the new GUID, we already
      *      have it cached.
      */
      this.observer = {
        onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
                      aDateAdded, aGuid, aParentGuid) => {
          this.updateCache(aItemId, aGuid);
          this.updateCache(aParentId, aParentGuid);
        },
        onItemRemoved:
        (aItemId, aParentId, aIndex, aItemTyep, aURI, aGuid, aParentGuid) => {
          this.guidsForIds.delete(aItemId);
          this.idsForGuids.delete(aGuid);
          this.updateCache(aParentId, aParentGuid);
        },

        QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),

        onBeginUpdateBatch() {},
        onEndUpdateBatch() {},
        onItemChanged() {},
        onItemVisited() {},
        onItemMoved() {},
      };
      PlacesUtils.bookmarks.addObserver(this.observer);
      PlacesUtils.registerShutdownFunction(() => {
        PlacesUtils.bookmarks.removeObserver(this.observer);
      });
    }
  }
};

// Transactions handlers.

/**
 * Updates commands in the undo group of the active window commands.
 * Inactive windows commands will be updated on focus.
 */
function updateCommandsOnActiveWindow() {
  let win = Services.focus.activeWindow;
  if (win && win instanceof Ci.nsIDOMWindow) {
    // Updating "undo" will cause a group update including "redo".
    win.updateCommands("undo");
  }
}


/**
 * Used to cache bookmark information in transactions.
 *
 * @note To avoid leaks any non-primitive property should be copied.
 * @note Used internally, DO NOT EXPORT.
 */
function TransactionItemCache() {
}

TransactionItemCache.prototype = {
  set id(v) {
    this._id = (parseInt(v) > 0 ? v : null);
  },
  get id() {
    return this._id || -1;
  },
  set parentId(v) {
    this._parentId = (parseInt(v) > 0 ? v : null);
  },
  get parentId() {
    return this._parentId || -1;
  },
  keyword: null,
  title: null,
  dateAdded: null,
  lastModified: null,
  postData: null,
  itemType: null,
  set uri(v) {
    this._uri = (v instanceof Ci.nsIURI ? v.clone() : null);
  },
  get uri() {
    return this._uri || null;
  },
  set feedURI(v) {
    this._feedURI = (v instanceof Ci.nsIURI ? v.clone() : null);
  },
  get feedURI() {
    return this._feedURI || null;
  },
  set siteURI(v) {
    this._siteURI = (v instanceof Ci.nsIURI ? v.clone() : null);
  },
  get siteURI() {
    return this._siteURI || null;
  },
  set index(v) {
    this._index = (parseInt(v) >= 0 ? v : null);
  },
  // Index can be 0.
  get index() {
    return this._index != null ? this._index : PlacesUtils.bookmarks.DEFAULT_INDEX;
  },
  set annotations(v) {
    this._annotations = Array.isArray(v) ? Cu.cloneInto(v, {}) : null;
  },
  get annotations() {
    return this._annotations || null;
  },
  set tags(v) {
    this._tags = (v && Array.isArray(v) ? Array.prototype.slice.call(v) : null);
  },
  get tags() {
    return this._tags || null;
  },
};


/**
 * Base transaction implementation.
 *
 * @note used internally, DO NOT EXPORT.
 */
function BaseTransaction() {
}

BaseTransaction.prototype = {
  name: null,
  set childTransactions(v) {
    this._childTransactions = (Array.isArray(v) ? Array.prototype.slice.call(v) : null);
  },
  get childTransactions() {
    return this._childTransactions || null;
  },
  doTransaction: function BTXN_doTransaction() {},
  redoTransaction: function BTXN_redoTransaction() {
    return this.doTransaction();
  },
  undoTransaction: function BTXN_undoTransaction() {},
  merge: function BTXN_merge() {
    return false;
  },
  get isTransient() {
    return false;
  },
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsITransaction
  ]),
};


/**
 * Transaction for performing several Places Transactions in a single batch.
 *
 * @param aName
 *        title of the aggregate transactions
 * @param aTransactions
 *        an array of transactions to perform
 *
 * @return nsITransaction object
 */
this.PlacesAggregatedTransaction =
 function PlacesAggregatedTransaction(aName, aTransactions) {
  // Copy the transactions array to decouple it from its prototype, which
  // otherwise keeps alive its associated global object.
  this.childTransactions = aTransactions;
  this.name = aName;
  this.item = new TransactionItemCache();

  // Check child transactions number.  We will batch if we have more than
  // MIN_TRANSACTIONS_FOR_BATCH total number of transactions.
  let countTransactions = function(aTransactions, aTxnCount) {
    for (let i = 0;
         i < aTransactions.length && aTxnCount < MIN_TRANSACTIONS_FOR_BATCH;
         ++i, ++aTxnCount) {
      let txn = aTransactions[i];
      if (txn.childTransactions && txn.childTransactions.length > 0)
        aTxnCount = countTransactions(txn.childTransactions, aTxnCount);
    }
    return aTxnCount;
  }

  let txnCount = countTransactions(this.childTransactions, 0);
  this._useBatch = txnCount >= MIN_TRANSACTIONS_FOR_BATCH;
}

PlacesAggregatedTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function ATXN_doTransaction() {
    this._isUndo = false;
    if (this._useBatch)
      PlacesUtils.bookmarks.runInBatchMode(this, null);
    else
      this.runBatched(false);
  },

  undoTransaction: function ATXN_undoTransaction() {
    this._isUndo = true;
    if (this._useBatch)
      PlacesUtils.bookmarks.runInBatchMode(this, null);
    else
      this.runBatched(true);
  },

  runBatched: function ATXN_runBatched() {
    // Use a copy of the transactions array, so we won't reverse the original
    // one on undoing.
    let transactions = this.childTransactions.slice(0);
    if (this._isUndo)
      transactions.reverse();
    for (let i = 0; i < transactions.length; ++i) {
      let txn = transactions[i];
      if (this.item.parentId != -1)
        txn.item.parentId = this.item.parentId;
      if (this._isUndo)
        txn.undoTransaction();
      else
        txn.doTransaction();
    }
  }
};


/**
 * Transaction for creating a new folder.
 *
 * @param aTitle
 *        the title for the new folder
 * @param aParentId
 *        the id of the parent folder in which the new folder should be added
 * @param [optional] aIndex
 *        the index of the item in aParentId
 * @param [optional] aAnnotations
 *        array of annotations to set for the new folder
 * @param [optional] aChildTransactions
 *        array of transactions for items to be created in the new folder
 *
 * @return nsITransaction object
 */
this.PlacesCreateFolderTransaction =
 function PlacesCreateFolderTransaction(aTitle, aParentId, aIndex, aAnnotations,
                                        aChildTransactions) {
  this.item = new TransactionItemCache();
  this.item.title = aTitle;
  this.item.parentId = aParentId;
  this.item.index = aIndex;
  this.item.annotations = aAnnotations;
  this.childTransactions = aChildTransactions;
}

PlacesCreateFolderTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function CFTXN_doTransaction() {
    this.item.id = PlacesUtils.bookmarks.createFolder(this.item.parentId,
                                                      this.item.title,
                                                      this.item.index);
    if (this.item.annotations && this.item.annotations.length > 0)
      PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);

    if (this.childTransactions && this.childTransactions.length > 0) {
      // Set the new parent id into child transactions.
      for (let i = 0; i < this.childTransactions.length; ++i) {
        this.childTransactions[i].item.parentId = this.item.id;
      }

      let txn = new PlacesAggregatedTransaction("Create folder childTxn",
                                                this.childTransactions);
      txn.doTransaction();
    }
  },

  undoTransaction: function CFTXN_undoTransaction() {
    if (this.childTransactions && this.childTransactions.length > 0) {
      let txn = new PlacesAggregatedTransaction("Create folder childTxn",
                                                this.childTransactions);
      txn.undoTransaction();
    }

    // Remove item only after all child transactions have been reverted.
    PlacesUtils.bookmarks.removeItem(this.item.id);
  }
};


/**
 * Transaction for creating a new bookmark.
 *
 * @param aURI
 *        the nsIURI of the new bookmark
 * @param aParentId
 *        the id of the folder in which the bookmark should be added.
 * @param [optional] aIndex
 *        the index of the item in aParentId
 * @param [optional] aTitle
 *        the title of the new bookmark
 * @param [optional] aKeyword
 *        the keyword for the new bookmark
 * @param [optional] aAnnotations
 *        array of annotations to set for the new bookmark
 * @param [optional] aChildTransactions
 *        child transactions to commit after creating the bookmark. Prefer
 *        using any of the arguments above if possible. In general, a child
 *        transations should be used only if the change it does has to be
 *        reverted manually when removing the bookmark item.
 *        a child transaction must support setting its bookmark-item
 *        identifier via an "id" js setter.
 * @param [optional] aPostData
 *        keyword's POST data, if available.
 *
 * @return nsITransaction object
 */
this.PlacesCreateBookmarkTransaction =
 function PlacesCreateBookmarkTransaction(aURI, aParentId, aIndex, aTitle,
                                          aKeyword, aAnnotations,
                                          aChildTransactions, aPostData) {
  this.item = new TransactionItemCache();
  this.item.uri = aURI;
  this.item.parentId = aParentId;
  this.item.index = aIndex;
  this.item.title = aTitle;
  this.item.keyword = aKeyword;
  this.item.postData = aPostData;
  this.item.annotations = aAnnotations;
  this.childTransactions = aChildTransactions;
}

PlacesCreateBookmarkTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function CITXN_doTransaction() {
    this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
                                                        this.item.uri,
                                                        this.item.index,
                                                        this.item.title);
    if (this.item.keyword) {
      PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
                                                  this.item.keyword);
      if (this.item.postData) {
        PlacesUtils.setPostDataForBookmark(this.item.id,
                                           this.item.postData);
      }
    }
    if (this.item.annotations && this.item.annotations.length > 0)
      PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);

    if (this.childTransactions && this.childTransactions.length > 0) {
      // Set the new item id into child transactions.
      for (let i = 0; i < this.childTransactions.length; ++i) {
        this.childTransactions[i].item.id = this.item.id;
      }
      let txn = new PlacesAggregatedTransaction("Create item childTxn",
                                                this.childTransactions);
      txn.doTransaction();
    }
  },

  undoTransaction: function CITXN_undoTransaction() {
    if (this.childTransactions && this.childTransactions.length > 0) {
      // Undo transactions should always be done in reverse order.
      let txn = new PlacesAggregatedTransaction("Create item childTxn",
                                                this.childTransactions);
      txn.undoTransaction();
    }

    // Remove item only after all child transactions have been reverted.
    PlacesUtils.bookmarks.removeItem(this.item.id);
  }
};


/**
 * Transaction for creating a new separator.
 *
 * @param aParentId
 *        the id of the folder in which the separator should be added
 * @param [optional] aIndex
 *        the index of the item in aParentId
 *
 * @return nsITransaction object
 */
this.PlacesCreateSeparatorTransaction =
 function PlacesCreateSeparatorTransaction(aParentId, aIndex) {
  this.item = new TransactionItemCache();
  this.item.parentId = aParentId;
  this.item.index = aIndex;
}

PlacesCreateSeparatorTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function CSTXN_doTransaction() {
    this.item.id =
      PlacesUtils.bookmarks.insertSeparator(this.item.parentId, this.item.index);
  },

  undoTransaction: function CSTXN_undoTransaction() {
    PlacesUtils.bookmarks.removeItem(this.item.id);
  }
};


/**
 * Transaction for creating a new livemark item.
 *
 * @see mozIAsyncLivemarks for documentation regarding the arguments.
 *
 * @param aFeedURI
 *        nsIURI of the feed
 * @param [optional] aSiteURI
 *        nsIURI of the page serving the feed
 * @param aTitle
 *        title for the livemark
 * @param aParentId
 *        the id of the folder in which the livemark should be added
 * @param [optional]  aIndex
 *        the index of the livemark in aParentId
 * @param [optional] aAnnotations
 *        array of annotations to set for the new livemark.
 *
 * @return nsITransaction object
 */
this.PlacesCreateLivemarkTransaction =
 function PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aTitle, aParentId,
                                          aIndex, aAnnotations) {
  this.item = new TransactionItemCache();
  this.item.feedURI = aFeedURI;
  this.item.siteURI = aSiteURI;
  this.item.title = aTitle;
  this.item.parentId = aParentId;
  this.item.index = aIndex;
  this.item.annotations = aAnnotations;
}

PlacesCreateLivemarkTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function CLTXN_doTransaction() {
    this._promise = PlacesUtils.livemarks.addLivemark(
      { title: this.item.title,
        feedURI: this.item.feedURI,
        parentId: this.item.parentId,
        index: this.item.index,
        siteURI: this.item.siteURI
      }).then(aLivemark => {
        this.item.id = aLivemark.id;
        if (this.item.annotations && this.item.annotations.length > 0) {
          PlacesUtils.setAnnotationsForItem(this.item.id,
                                            this.item.annotations);
        }
      }, Cu.reportError);
  },

  undoTransaction: function CLTXN_undoTransaction() {
    // The getLivemark callback may fail, but it is used just to serialize,
    // so it doesn't matter.
    this._promise = PlacesUtils.livemarks.getLivemark({ id: this.item.id })
      .catch(() => {}).then(() => {
        PlacesUtils.bookmarks.removeItem(this.item.id);
      });
  }
};


/**
 * Transaction for removing a livemark item.
 *
 * @param aLivemarkId
 *        the identifier of the folder for the livemark.
 *
 * @return nsITransaction object
 * @note used internally by PlacesRemoveItemTransaction, DO NOT EXPORT.
 */
function PlacesRemoveLivemarkTransaction(aLivemarkId) {
  this.item = new TransactionItemCache();
  this.item.id = aLivemarkId;
  this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
  this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);

  let annos = PlacesUtils.getAnnotationsForItem(this.item.id);
  // Exclude livemark service annotations, those will be recreated automatically
  let annosToExclude = [PlacesUtils.LMANNO_FEEDURI,
                        PlacesUtils.LMANNO_SITEURI];
  this.item.annotations = annos.filter(function(aValue, aIndex, aArray) {
      return !annosToExclude.includes(aValue.name);
    });
  this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
  this.item.lastModified =
    PlacesUtils.bookmarks.getItemLastModified(this.item.id);
}

PlacesRemoveLivemarkTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function RLTXN_doTransaction() {
    PlacesUtils.livemarks.getLivemark({ id: this.item.id })
      .then(aLivemark => {
        this.item.feedURI = aLivemark.feedURI;
        this.item.siteURI = aLivemark.siteURI;
        PlacesUtils.bookmarks.removeItem(this.item.id);
      }, Cu.reportError);
  },

  undoTransaction: function RLTXN_undoTransaction() {
    // Undo work must be serialized, otherwise won't be able to know the
    // feedURI and siteURI of the livemark.
    // The getLivemark callback is expected to receive a failure status but it
    // is used just to serialize, so doesn't matter.
    PlacesUtils.livemarks.getLivemark({ id: this.item.id })
      .catch(() => {
        PlacesUtils.livemarks.addLivemark({ parentId: this.item.parentId,
                                            title: this.item.title,
                                            siteURI: this.item.siteURI,
                                            feedURI: this.item.feedURI,
                                            index: this.item.index,
                                            lastModified: this.item.lastModified
                                          }).then(
          aLivemark => {
            let itemId = aLivemark.id;
            PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded);
            PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations);
          }, Cu.reportError);
      });
  }
};


/**
 * Transaction for moving an Item.
 *
 * @param aItemId
 *        the id of the item to move
 * @param aNewParentId
 *        id of the new parent to move to
 * @param aNewIndex
 *        index of the new position to move to
 *
 * @return nsITransaction object
 */
this.PlacesMoveItemTransaction =
 function PlacesMoveItemTransaction(aItemId, aNewParentId, aNewIndex) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
  this.new = new TransactionItemCache();
  this.new.parentId = aNewParentId;
  this.new.index = aNewIndex;
}

PlacesMoveItemTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function MITXN_doTransaction() {
    this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);
    PlacesUtils.bookmarks.moveItem(this.item.id,
                                   this.new.parentId, this.new.index);
    this._undoIndex = PlacesUtils.bookmarks.getItemIndex(this.item.id);
  },

  undoTransaction: function MITXN_undoTransaction() {
    // moving down in the same parent takes in count removal of the item
    // so to revert positions we must move to oldIndex + 1
    if (this.new.parentId == this.item.parentId &&
        this.item.index > this._undoIndex) {
      PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
                                     this.item.index + 1);
    } else {
      PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
                                     this.item.index);
    }
  }
};


/**
 * Transaction for removing an Item
 *
 * @param aItemId
 *        id of the item to remove
 *
 * @return nsITransaction object
 */
this.PlacesRemoveItemTransaction =
 function PlacesRemoveItemTransaction(aItemId) {
  if (PlacesUtils.isRootItem(aItemId))
    throw Cr.NS_ERROR_INVALID_ARG;

  // if the item lives within a tag container, use the tagging transactions
  let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
  let grandparent = PlacesUtils.bookmarks.getFolderIdForItem(parent);
  if (grandparent == PlacesUtils.tagsFolderId) {
    let uri = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
    return new PlacesUntagURITransaction(uri, [parent]);
  }

  // if the item is a livemark container we will not save its children.
  if (PlacesUtils.annotations.itemHasAnnotation(aItemId,
                                                PlacesUtils.LMANNO_FEEDURI))
    return new PlacesRemoveLivemarkTransaction(aItemId);

  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.item.itemType = PlacesUtils.bookmarks.getItemType(this.item.id);
  if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
    this.childTransactions = this._getFolderContentsTransactions();
    // Remove this folder itself.
    let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(this.item.id);
    this.childTransactions.push(txn);
  } else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
    this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
    this.item.keyword =
      PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id);
    if (this.item.keyword)
      this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id);
  }

  if (this.item.itemType != Ci.nsINavBookmarksService.TYPE_SEPARATOR)
    this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);

  this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
  this.item.annotations = PlacesUtils.getAnnotationsForItem(this.item.id);
  this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
  this.item.lastModified =
    PlacesUtils.bookmarks.getItemLastModified(this.item.id);
}

PlacesRemoveItemTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function RITXN_doTransaction() {
    this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);

    if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
      let txn = new PlacesAggregatedTransaction("Remove item childTxn",
                                                this.childTransactions);
      txn.doTransaction();
    } else {
      // Before removing the bookmark, save its tags.
      let tags = this.item.uri ?
        PlacesUtils.tagging.getTagsForURI(this.item.uri) : null;

      PlacesUtils.bookmarks.removeItem(this.item.id);

      // If this was the last bookmark (excluding tag-items) for this url,
      // persist the tags.
      if (tags && PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
        this.item.tags = tags;
      }
    }
  },

  undoTransaction: function RITXN_undoTransaction() {
    if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
      this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
                                                          this.item.uri,
                                                          this.item.index,
                                                          this.item.title);
      if (this.item.tags && this.item.tags.length > 0)
        PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
      if (this.item.keyword) {
        PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
                                                    this.item.keyword);
        if (this.item.postData) {
          PlacesUtils.bookmarks.setPostDataForBookmark(this.item.id);
        }
      }
    } else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
      let txn = new PlacesAggregatedTransaction("Remove item childTxn",
                                                this.childTransactions);
      txn.undoTransaction();
    } else { // TYPE_SEPARATOR
      this.item.id = PlacesUtils.bookmarks.insertSeparator(this.item.parentId,
                                                            this.item.index);
    }

    if (this.item.annotations && this.item.annotations.length > 0)
      PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);

    PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
    PlacesUtils.bookmarks.setItemLastModified(this.item.id,
                                              this.item.lastModified);
  },

  /**
  * Returns a flat, ordered list of transactions for a depth-first recreation
  * of items within this folder.
  */
  _getFolderContentsTransactions:
  function RITXN__getFolderContentsTransactions() {
    let transactions = [];
    let contents =
      PlacesUtils.getFolderContents(this.item.id, false, false).root;
    for (let i = 0; i < contents.childCount; ++i) {
      let txn = new PlacesRemoveItemTransaction(contents.getChild(i).itemId);
      transactions.push(txn);
    }
    contents.containerOpen = false;
    // Reverse transactions to preserve parent-child relationship.
    return transactions.reverse();
  }
};


/**
 * Transaction for editting a bookmark's title.
 *
 * @param aItemId
 *        id of the item to edit
 * @param aNewTitle
 *        new title for the item to edit
 *
 * @return nsITransaction object
 */
this.PlacesEditItemTitleTransaction =
 function PlacesEditItemTitleTransaction(aItemId, aNewTitle) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.title = aNewTitle;
}

PlacesEditItemTitleTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function EITTXN_doTransaction() {
    this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
    PlacesUtils.bookmarks.setItemTitle(this.item.id, this.new.title);
  },

  undoTransaction: function EITTXN_undoTransaction() {
    PlacesUtils.bookmarks.setItemTitle(this.item.id, this.item.title);
  }
};


/**
 * Transaction for editing a bookmark's uri.
 *
 * @param aItemId
 *        id of the bookmark to edit
 * @param aNewURI
 *        new uri for the bookmark
 *
 * @return nsITransaction object
 */
this.PlacesEditBookmarkURITransaction =
 function PlacesEditBookmarkURITransaction(aItemId, aNewURI) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.uri = aNewURI;
}

PlacesEditBookmarkURITransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function EBUTXN_doTransaction() {
    this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
    PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.new.uri);
    // move tags from old URI to new URI
    this.item.tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
    if (this.item.tags.length > 0) {
      // only untag the old URI if this is the only bookmark
      if (PlacesUtils.getBookmarksForURI(this.item.uri, {}).length == 0)
        PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
      PlacesUtils.tagging.tagURI(this.new.uri, this.item.tags);
    }
  },

  undoTransaction: function EBUTXN_undoTransaction() {
    PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.item.uri);
    // move tags from new URI to old URI
    if (this.item.tags.length > 0) {
      // only untag the new URI if this is the only bookmark
      if (PlacesUtils.getBookmarksForURI(this.new.uri, {}).length == 0)
        PlacesUtils.tagging.untagURI(this.new.uri, this.item.tags);
      PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
    }
  }
};


/**
 * Transaction for setting/unsetting an item annotation
 *
 * @param aItemId
 *        id of the item where to set annotation
 * @param aAnnotationObject
 *        Object representing an annotation, containing the following
 *        properties: name, flags, expires, value.
 *        If value is null the annotation will be removed
 *
 * @return nsITransaction object
 */
this.PlacesSetItemAnnotationTransaction =
 function PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.annotations = [aAnnotationObject];
}

PlacesSetItemAnnotationTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function SIATXN_doTransaction() {
    let annoName = this.new.annotations[0].name;
    if (PlacesUtils.annotations.itemHasAnnotation(this.item.id, annoName)) {
      // fill the old anno if it is set
      let flags = {}, expires = {}, type = {};
      PlacesUtils.annotations.getItemAnnotationInfo(this.item.id, annoName, flags,
                                                    expires, type);
      let value = PlacesUtils.annotations.getItemAnnotation(this.item.id,
                                                            annoName);
      this.item.annotations = [{ name: annoName,
                                type: type.value,
                                flags: flags.value,
                                value,
                                expires: expires.value }];
    } else {
      // create an empty old anno
      this.item.annotations = [{ name: annoName,
                                flags: 0,
                                value: null,
                                expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
    }

    PlacesUtils.setAnnotationsForItem(this.item.id, this.new.annotations);
  },

  undoTransaction: function SIATXN_undoTransaction() {
    PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
  }
};


/**
 * Transaction for setting/unsetting a page annotation
 *
 * @param aURI
 *        URI of the page where to set annotation
 * @param aAnnotationObject
 *        Object representing an annotation, containing the following
 *        properties: name, flags, expires, value.
 *        If value is null the annotation will be removed
 *
 * @return nsITransaction object
 */
this.PlacesSetPageAnnotationTransaction =
 function PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject) {
  this.item = new TransactionItemCache();
  this.item.uri = aURI;
  this.new = new TransactionItemCache();
  this.new.annotations = [aAnnotationObject];
}

PlacesSetPageAnnotationTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function SPATXN_doTransaction() {
    let annoName = this.new.annotations[0].name;
    if (PlacesUtils.annotations.pageHasAnnotation(this.item.uri, annoName)) {
      // fill the old anno if it is set
      let flags = {}, expires = {}, type = {};
      PlacesUtils.annotations.getPageAnnotationInfo(this.item.uri, annoName, flags,
                                                    expires, type);
      let value = PlacesUtils.annotations.getPageAnnotation(this.item.uri,
                                                            annoName);
      this.item.annotations = [{ name: annoName,
                                flags: flags.value,
                                value,
                                expires: expires.value }];
    } else {
      // create an empty old anno
      this.item.annotations = [{ name: annoName,
                                type: Ci.nsIAnnotationService.TYPE_STRING,
                                flags: 0,
                                value: null,
                                expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
    }

    PlacesUtils.setAnnotationsForURI(this.item.uri, this.new.annotations);
  },

  undoTransaction: function SPATXN_undoTransaction() {
    PlacesUtils.setAnnotationsForURI(this.item.uri, this.item.annotations);
  }
};


/**
 * Transaction for editing a bookmark's keyword.
 *
 * @param aItemId
 *        id of the bookmark to edit
 * @param aNewKeyword
 *        new keyword for the bookmark
 * @param aNewPostData [optional]
 *        new keyword's POST data, if available
 * @param aOldKeyword [optional]
 *        old keyword of the bookmark
 *
 * @return nsITransaction object
 */
this.PlacesEditBookmarkKeywordTransaction =
  function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword,
                                                aNewPostData, aOldKeyword) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.item.keyword = aOldKeyword;
  this.item.href = (PlacesUtils.bookmarks.getBookmarkURI(aItemId)).spec;
  this.new = new TransactionItemCache();
  this.new.keyword = aNewKeyword;
  this.new.postData = aNewPostData
}

PlacesEditBookmarkKeywordTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function EBKTXN_doTransaction() {
    let done = false;
    (async () => {
      if (this.item.keyword) {
        let oldEntry = await PlacesUtils.keywords.fetch(this.item.keyword);
        this.item.postData = oldEntry.postData;
        await PlacesUtils.keywords.remove(this.item.keyword);
      }

      if (this.new.keyword) {
        await PlacesUtils.keywords.insert({
          url: this.item.href,
          keyword: this.new.keyword,
          postData: this.new.postData || this.item.postData
        });
      }
    })().catch(Cu.reportError)
                 .then(() => done = true);
    // TODO: Until we can move to PlacesTransactions.jsm, we must spin the
    // events loop :(
    Services.tm.spinEventLoopUntil(() => done);
  },

  undoTransaction: function EBKTXN_undoTransaction() {

    let done = false;
    (async () => {
      if (this.new.keyword) {
        await PlacesUtils.keywords.remove(this.new.keyword);
      }

      if (this.item.keyword) {
        await PlacesUtils.keywords.insert({
          url: this.item.href,
          keyword: this.item.keyword,
          postData: this.item.postData
        });
      }
    })().catch(Cu.reportError)
                 .then(() => done = true);
    // TODO: Until we can move to PlacesTransactions.jsm, we must spin the
    // events loop :(
    Services.tm.spinEventLoopUntil(() => {
      return done;
    });
  }
};


/**
 * Transaction for editing the post data associated with a bookmark.
 *
 * @param aItemId
 *        id of the bookmark to edit
 * @param aPostData
 *        post data
 *
 * @return nsITransaction object
 */
this.PlacesEditBookmarkPostDataTransaction =
 function PlacesEditBookmarkPostDataTransaction(aItemId, aPostData) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.postData = aPostData;
}

PlacesEditBookmarkPostDataTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction() {
    // Setting null postData is not supported by the current schema.
    if (this.new.postData) {
      this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id);
      PlacesUtils.setPostDataForBookmark(this.item.id, this.new.postData);
    }
  },

  undoTransaction() {
    // Setting null postData is not supported by the current schema.
    if (this.item.postData) {
      PlacesUtils.setPostDataForBookmark(this.item.id, this.item.postData);
    }
  }
};


/**
 * Transaction for editing an item's date added property.
 *
 * @param aItemId
 *        id of the item to edit
 * @param aNewDateAdded
 *        new date added for the item
 *
 * @return nsITransaction object
 */
this.PlacesEditItemDateAddedTransaction =
 function PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.dateAdded = aNewDateAdded;
}

PlacesEditItemDateAddedTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function EIDATXN_doTransaction() {
    // Child transactions have the id set as parentId.
    if (this.item.id == -1 && this.item.parentId != -1)
      this.item.id = this.item.parentId;
    this.item.dateAdded =
      PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
    PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.new.dateAdded);
  },

  undoTransaction: function EIDATXN_undoTransaction() {
    PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
  }
};


/**
 * Transaction for editing an item's last modified time.
 *
 * @param aItemId
 *        id of the item to edit
 * @param aNewLastModified
 *        new last modified date for the item
 *
 * @return nsITransaction object
 */
this.PlacesEditItemLastModifiedTransaction =
 function PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified) {
  this.item = new TransactionItemCache();
  this.item.id = aItemId;
  this.new = new TransactionItemCache();
  this.new.lastModified = aNewLastModified;
}

PlacesEditItemLastModifiedTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction:
  function EILMTXN_doTransaction() {
    // Child transactions have the id set as parentId.
    if (this.item.id == -1 && this.item.parentId != -1)
      this.item.id = this.item.parentId;
    this.item.lastModified =
      PlacesUtils.bookmarks.getItemLastModified(this.item.id);
    PlacesUtils.bookmarks.setItemLastModified(this.item.id,
                                              this.new.lastModified);
  },

  undoTransaction:
  function EILMTXN_undoTransaction() {
    PlacesUtils.bookmarks.setItemLastModified(this.item.id,
                                              this.item.lastModified);
  }
};


/**
 * Transaction for sorting a folder by name
 *
 * @param aFolderId
 *        id of the folder to sort
 *
 * @return nsITransaction object
 */
this.PlacesSortFolderByNameTransaction =
 function PlacesSortFolderByNameTransaction(aFolderId) {
  this.item = new TransactionItemCache();
  this.item.id = aFolderId;
}

PlacesSortFolderByNameTransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function SFBNTXN_doTransaction() {
    this._oldOrder = [];

    let contents =
      PlacesUtils.getFolderContents(this.item.id, false, false).root;
    let count = contents.childCount;

    // sort between separators
    let newOrder = [];
    let preSep = []; // temporary array for sorting each group of items
    let sortingMethod =
      function(a, b) {
        if (PlacesUtils.nodeIsContainer(a) && !PlacesUtils.nodeIsContainer(b))
          return -1;
        if (!PlacesUtils.nodeIsContainer(a) && PlacesUtils.nodeIsContainer(b))
          return 1;
        return a.title.localeCompare(b.title);
      };

    for (let i = 0; i < count; ++i) {
      let item = contents.getChild(i);
      this._oldOrder[item.itemId] = i;
      if (PlacesUtils.nodeIsSeparator(item)) {
        if (preSep.length > 0) {
          preSep.sort(sortingMethod);
          newOrder = newOrder.concat(preSep);
          preSep.splice(0, preSep.length);
        }
        newOrder.push(item);
      } else
        preSep.push(item);
    }
    contents.containerOpen = false;

    if (preSep.length > 0) {
      preSep.sort(sortingMethod);
      newOrder = newOrder.concat(preSep);
    }

    // set the nex indexes
    let callback = {
      runBatched() {
        for (let i = 0; i < newOrder.length; ++i) {
          PlacesUtils.bookmarks.setItemIndex(newOrder[i].itemId, i);
        }
      }
    };
    PlacesUtils.bookmarks.runInBatchMode(callback, null);
  },

  undoTransaction: function SFBNTXN_undoTransaction() {
    let callback = {
      _self: this,
      runBatched() {
        for (let item in this._self._oldOrder)
          PlacesUtils.bookmarks.setItemIndex(item, this._self._oldOrder[item]);
      }
    };
    PlacesUtils.bookmarks.runInBatchMode(callback, null);
  }
};


/**
 * Transaction for tagging a URL with the given set of tags. Current tags set
 * for the URL persist. It's the caller's job to check whether or not aURI
 * was already tagged by any of the tags in aTags, undoing this tags
 * transaction removes them all from aURL!
 *
 * @param aURI
 *        the URL to tag.
 * @param aTags
 *        Array of tags to set for the given URL.
 */
this.PlacesTagURITransaction =
 function PlacesTagURITransaction(aURI, aTags) {
  this.item = new TransactionItemCache();
  this.item.uri = aURI;
  this.item.tags = aTags;
}

PlacesTagURITransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function TUTXN_doTransaction() {
    if (PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
      // There is no bookmark for this uri, but we only allow to tag bookmarks.
      // Force an unfiled bookmark first.
      this.item.id =
        PlacesUtils.bookmarks
                   .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                   this.item.uri,
                                   PlacesUtils.bookmarks.DEFAULT_INDEX,
                                   PlacesUtils.history.getPageTitle(this.item.uri));
    }
    PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
  },

  undoTransaction: function TUTXN_undoTransaction() {
    if (this.item.id != -1) {
      PlacesUtils.bookmarks.removeItem(this.item.id);
      this.item.id = -1;
    }
    PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
  }
};


/**
 * Transaction for removing tags from a URL. It's the caller's job to check
 * whether or not aURI isn't tagged by any of the tags in aTags, undoing this
 * tags transaction adds them all to aURL!
 *
 * @param aURI
 *        the URL to un-tag.
 * @param aTags
 *        Array of tags to unset. pass null to remove all tags from the given
 *        url.
 */
this.PlacesUntagURITransaction =
 function PlacesUntagURITransaction(aURI, aTags) {
  this.item = new TransactionItemCache();
  this.item.uri = aURI;
  if (aTags) {
    // Within this transaction, we cannot rely on tags given by itemId
    // since the tag containers may be gone after we call untagURI.
    // Thus, we convert each tag given by its itemId to name.
    let tags = [];
    for (let i = 0; i < aTags.length; ++i) {
      if (typeof(aTags[i]) == "number")
        tags.push(PlacesUtils.bookmarks.getItemTitle(aTags[i]));
      else
        tags.push(aTags[i]);
    }
    this.item.tags = tags;
  }
}

PlacesUntagURITransaction.prototype = {
  __proto__: BaseTransaction.prototype,

  doTransaction: function UTUTXN_doTransaction() {
    // Filter tags existing on the bookmark, otherwise on undo we may try to
    // set nonexistent tags.
    let tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
    this.item.tags = this.item.tags.filter(function(aTag) {
      return tags.includes(aTag);
    });
    PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
  },

  undoTransaction: function UTUTXN_undoTransaction() {
    PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
  }
};

/**
 * Executes a boolean validate function, throwing if it returns false.
 *
 * @param boolValidateFn
 *        A boolean validate function.
 * @return the input value.
 * @throws if input doesn't pass the validate function.
 */
function simpleValidateFunc(boolValidateFn) {
  return (v, input) => {
    if (!boolValidateFn(v, input))
      throw new Error("Invalid value");
    return v;
  };
}
PK
!<ڈLLmodules/PluralForm.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = [ "PluralForm" ];

/**
 * This module provides the PluralForm object which contains a method to figure
 * out which plural form of a word to use for a given number based on the
 * current localization. There is also a makeGetter method that creates a get
 * function for the desired plural rule. This is useful for extensions that
 * specify their own plural rule instead of relying on the browser default.
 * (I.e., the extension hasn't been localized to the browser's locale.)
 *
 * See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 *
 * List of methods:
 *
 * string pluralForm
 * get(int aNum, string aWords)
 *
 * int numForms
 * numForms()
 *
 * [string pluralForm get(int aNum, string aWords), int numForms numForms()]
 * makeGetter(int aRuleNum)
 * Note: Basically, makeGetter returns 2 functions that do "get" and "numForm"
 */

const Cc = Components.classes;
const Ci = Components.interfaces;

const kIntlProperties = "chrome://global/locale/intl.properties";

// These are the available plural functions that give the appropriate index
// based on the plural rule number specified. The first element is the number
// of plural forms and the second is the function to figure out the index.
var gFunctions = [
  // 0: Chinese
  [1, (n) => 0],
  // 1: English
  [2, (n) => n!=1?1:0],
  // 2: French
  [2, (n) => n>1?1:0],
  // 3: Latvian
  [3, (n) => n%10==1&&n%100!=11?1:n!=0?2:0],
  // 4: Scottish Gaelic
  [4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3],
  // 5: Romanian
  [3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2],
  // 6: Lithuanian
  [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1],
  // 7: Russian
  [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
  // 8: Slovak
  [3, (n) => n==1?0:n>=2&&n<=4?1:2],
  // 9: Polish
  [3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
  // 10: Slovenian
  [4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3],
  // 11: Irish Gaeilge
  [5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4],
  // 12: Arabic
  [6, (n) => n==0?5:n==1?0:n==2?1:n%100>=3&&n%100<=10?2:n%100>=11&&n%100<=99?3:4],
  // 13: Maltese
  [4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3],
  // 14: Macedonian
  [3, (n) => n%10==1?0:n%10==2?1:2],
  // 15: Icelandic
  [2, (n) => n%10==1&&n%100!=11?0:1],
  // 16: Breton
  [5, (n) => n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4],
];

this.PluralForm = {
  /**
   * Get the correct plural form of a word based on the number
   *
   * @param aNum
   *        The number to decide which plural form to use
   * @param aWords
   *        A semi-colon (;) separated string of words to pick the plural form
   * @return The appropriate plural form of the word
   */
  get get()
  {
    // This method will lazily load to avoid perf when it is first needed and
    // creates getPluralForm function. The function it creates is based on the
    // value of pluralRule specified in the intl stringbundle.
    // See: http://developer.mozilla.org/en/docs/Localization_and_Plurals

    // Delete the getters to be overwritten
    delete PluralForm.numForms;
    delete PluralForm.get;

    // Make the plural form get function and set it as the default get
    [PluralForm.get, PluralForm.numForms] = PluralForm.makeGetter(PluralForm.ruleNum);
    return PluralForm.get;
  },

  /**
   * Create a pair of plural form functions for the given plural rule number.
   *
   * @param aRuleNum
   *        The plural rule number to create functions
   * @return A pair: [function that gets the right plural form,
   *                  function that returns the number of plural forms]
   */
  makeGetter: function(aRuleNum)
  {
    // Default to "all plural" if the value is out of bounds or invalid
    if (aRuleNum < 0 || aRuleNum >= gFunctions.length || isNaN(aRuleNum)) {
      log(["Invalid rule number: ", aRuleNum, " -- defaulting to 0"]);
      aRuleNum = 0;
    }

    // Get the desired pluralRule function
    let [numForms, pluralFunc] = gFunctions[aRuleNum];

    // Return functions that give 1) the number of forms and 2) gets the right
    // plural form
    return [function(aNum, aWords) {
      // Figure out which index to use for the semi-colon separated words
      let index = pluralFunc(aNum ? Number(aNum) : 0);
      let words = aWords ? aWords.split(/;/) : [""];

      // Explicitly check bounds to avoid strict warnings
      let ret = index < words.length ? words[index] : undefined;

      // Check for array out of bounds or empty strings
      if ((ret == undefined) || (ret == "")) {
        // Report the caller to help figure out who is causing badness
        let caller = Components.stack.caller ? Components.stack.caller.name : "top";

        // Display a message in the error console
        log(["Index #", index, " of '", aWords, "' for value ", aNum,
            " is invalid -- plural rule #", aRuleNum, "; called by ", caller]);

        // Default to the first entry (which might be empty, but not undefined)
        ret = words[0];
      }

      return ret;
    }, () => numForms];
  },

  /**
   * Get the number of forms for the current plural rule
   *
   * @return The number of forms
   */
  get numForms()
  {
    // We lazily load numForms, so trigger the init logic with get()
    PluralForm.get();
    return PluralForm.numForms;
  },

  /**
   * Get the plural rule number from the intl stringbundle
   *
   * @return The plural rule number
   */
  get ruleNum()
  {
    return Number(Cc["@mozilla.org/intl/stringbundle;1"].
      getService(Ci.nsIStringBundleService).createBundle(kIntlProperties).
      GetStringFromName("pluralRule"));
  }
};

/**
 * Private helper function to log errors to the error console and command line
 *
 * @param aMsg
 *        Error message to log or an array of strings to concat
 */
function log(aMsg)
{
  let msg = "PluralForm.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
    logStringMessage(msg);
  dump(msg + "\n");
}
PK
!<
Lmodules/PopupNotifications.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["PopupNotifications"];

var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");

const NOTIFICATION_EVENT_DISMISSED = "dismissed";
const NOTIFICATION_EVENT_REMOVED = "removed";
const NOTIFICATION_EVENT_SHOWING = "showing";
const NOTIFICATION_EVENT_SHOWN = "shown";
const NOTIFICATION_EVENT_SWAPPING = "swapping";

const ICON_SELECTOR = ".notification-anchor-icon";
const ICON_ATTRIBUTE_SHOWING = "showing";
const ICON_ANCHOR_ATTRIBUTE = "popupnotificationanchor";

const PREF_SECURITY_DELAY = "security.notification_enable_delay";

// Enumerated values for the POPUP_NOTIFICATION_STATS telemetry histogram.
const TELEMETRY_STAT_OFFERED = 0;
const TELEMETRY_STAT_ACTION_1 = 1;
const TELEMETRY_STAT_ACTION_2 = 2;
const TELEMETRY_STAT_ACTION_3 = 3;
const TELEMETRY_STAT_ACTION_LAST = 4;
const TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE = 5;
const TELEMETRY_STAT_DISMISSAL_LEAVE_PAGE = 6;
const TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON = 7;
const TELEMETRY_STAT_OPEN_SUBMENU = 10;
const TELEMETRY_STAT_LEARN_MORE = 11;

const TELEMETRY_STAT_REOPENED_OFFSET = 20;

var popupNotificationsMap = new WeakMap();
var gNotificationParents = new WeakMap;

function getAnchorFromBrowser(aBrowser, aAnchorID) {
  let attrPrefix = aAnchorID ? aAnchorID.replace("notification-icon", "") : "";
  let anchor = aBrowser.getAttribute(attrPrefix + ICON_ANCHOR_ATTRIBUTE) ||
               aBrowser[attrPrefix + ICON_ANCHOR_ATTRIBUTE] ||
               aBrowser.getAttribute(ICON_ANCHOR_ATTRIBUTE) ||
               aBrowser[ICON_ANCHOR_ATTRIBUTE];
  if (anchor) {
    if (anchor instanceof Ci.nsIDOMXULElement) {
      return anchor;
    }
    return aBrowser.ownerDocument.getElementById(anchor);
  }
  return null;
}

function getNotificationFromElement(aElement) {
  // Need to find the associated notification object, which is a bit tricky
  // since it isn't associated with the element directly - this is kind of
  // gross and very dependent on the structure of the popupnotification
  // binding's content.
  let notificationEl;
  let parent = aElement;
  while (parent && (parent = aElement.ownerDocument.getBindingParent(parent)))
    notificationEl = parent;
  return notificationEl;
}

/**
 * Notification object describes a single popup notification.
 *
 * @see PopupNotifications.show()
 */
function Notification(id, message, anchorID, mainAction, secondaryActions,
                      browser, owner, options) {
  this.id = id;
  this.message = message;
  this.anchorID = anchorID;
  this.mainAction = mainAction;
  this.secondaryActions = secondaryActions || [];
  this.browser = browser;
  this.owner = owner;
  this.options = options || {};

  this._dismissed = false;
  // Will become a boolean when manually toggled by the user.
  this._checkboxChecked = null;
  this.wasDismissed = false;
  this.recordedTelemetryStats = new Set();
  this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(
                                        this.browser.ownerGlobal);
  this.timeCreated = this.owner.window.performance.now();
}

Notification.prototype = {

  id: null,
  message: null,
  anchorID: null,
  mainAction: null,
  secondaryActions: null,
  browser: null,
  owner: null,
  options: null,
  timeShown: null,

  /**
   * Indicates whether the notification is currently dismissed.
   */
  set dismissed(value) {
    this._dismissed = value;
    if (value) {
      // Keep the dismissal into account when recording telemetry.
      this.wasDismissed = true;
    }
  },
  get dismissed() {
    return this._dismissed;
  },

  /**
   * Removes the notification and updates the popup accordingly if needed.
   */
  remove: function Notification_remove() {
    this.owner.remove(this);
  },

  get anchorElement() {
    let iconBox = this.owner.iconBox;

    let anchorElement = getAnchorFromBrowser(this.browser, this.anchorID);
    if (!iconBox)
      return anchorElement;

    if (!anchorElement && this.anchorID)
      anchorElement = iconBox.querySelector("#" + this.anchorID);

    // Use a default anchor icon if it's available
    if (!anchorElement)
      anchorElement = iconBox.querySelector("#default-notification-icon") ||
                      iconBox;

    return anchorElement;
  },

  reshow() {
    this.owner._reshowNotifications(this.anchorElement, this.browser);
  },

  /**
   * Adds a value to the specified histogram, that must be keyed by ID.
   */
  _recordTelemetry(histogramId, value) {
    if (this.isPrivate) {
      // The reason why we don't record telemetry in private windows is because
      // the available actions can be different from regular mode. The main
      // difference is that all of the persistent permission options like
      // "Always remember" aren't there, so they really need to be handled
      // separately to avoid skewing results. For notifications with the same
      // choices, there would be no reason not to record in private windows as
      // well, but it's just simpler to use the same check for everything.
      return;
    }
    let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
    histogram.add("(all)", value);
    histogram.add(this.id, value);
  },

  /**
   * Adds an enumerated value to the POPUP_NOTIFICATION_STATS histogram,
   * ensuring that it is recorded at most once for each distinct Notification.
   *
   * Statistics for reopened notifications are recorded in separate buckets.
   *
   * @param value
   *        One of the TELEMETRY_STAT_ constants.
   */
  _recordTelemetryStat(value) {
    if (this.wasDismissed) {
      value += TELEMETRY_STAT_REOPENED_OFFSET;
    }
    if (!this.recordedTelemetryStats.has(value)) {
      this.recordedTelemetryStats.add(value);
      this._recordTelemetry("POPUP_NOTIFICATION_STATS", value);
    }
  },
};

/**
 * The PopupNotifications object manages popup notifications for a given browser
 * window.
 * @param tabbrowser
 *        window's <xul:tabbrowser/>. Used to observe tab switching events and
 *        for determining the active browser element.
 * @param panel
 *        The <xul:panel/> element to use for notifications. The panel is
 *        populated with <popupnotification> children and displayed it as
 *        needed.
 * @param iconBox
 *        Reference to a container element that should be hidden or
 *        unhidden when notifications are hidden or shown. It should be the
 *        parent of anchor elements whose IDs are passed to show().
 *        It is used as a fallback popup anchor if notifications specify
 *        invalid or non-existent anchor IDs.
 * @param options
 *        An optional object with the following optional properties:
 *        {
 *          shouldSuppress:
 *            If this function returns true, then all notifications are
 *            suppressed for this window. This state is checked on construction
 *            and when the "anchorVisibilityChange" method is called.
 *        }
 */
this.PopupNotifications = function PopupNotifications(tabbrowser, panel,
                                                      iconBox, options = {}) {
  if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
    throw "Invalid tabbrowser";
  if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
    throw "Invalid iconBox";
  if (!(panel instanceof Ci.nsIDOMXULElement))
    throw "Invalid panel";

  this._shouldSuppress = options.shouldSuppress || (() => false);
  this._suppress = this._shouldSuppress();

  this.window = tabbrowser.ownerGlobal;
  this.panel = panel;
  this.tabbrowser = tabbrowser;
  this.iconBox = iconBox;
  this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);

  this.panel.addEventListener("popuphidden", this, true);
  this.panel.classList.add("popup-notification-panel");

  // This listener will be attached to the chrome window whenever a notification
  // is showing, to allow the user to dismiss notifications using the escape key.
  this._handleWindowKeyPress = aEvent => {
    if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE) {
      return;
    }

    // Esc key cancels the topmost notification, if there is one.
    let notification = this.panel.firstChild;
    if (!notification) {
      return;
    }

    let doc = this.window.document;
    let focusedElement = Services.focus.focusedElement;

    // If the chrome window has a focused element, let it handle the ESC key instead.
    if (!focusedElement ||
        focusedElement == doc.body ||
        focusedElement == this.tabbrowser.selectedBrowser ||
        // Ignore focused elements inside the notification.
        getNotificationFromElement(focusedElement) == notification ||
        notification.contains(focusedElement)) {
      this._onButtonEvent(aEvent, "secondarybuttoncommand", notification);
    }
  };

  let documentElement = this.window.document.documentElement;
  let locationBarHidden = documentElement.getAttribute("chromehidden").includes("location");
  let isFullscreen = !!this.window.document.fullscreenElement;

  this.panel.setAttribute("followanchor", !locationBarHidden && !isFullscreen);

  // There are no anchor icons in DOM fullscreen mode, but we would
  // still like to show the popup notification. To avoid an infinite
  // loop of showing and hiding, we have to disable followanchor
  // (which hides the element without an anchor) in fullscreen.
  this.window.addEventListener("MozDOMFullscreen:Entered", () => {
    this.panel.setAttribute("followanchor", "false");
  }, true);
  this.window.addEventListener("MozDOMFullscreen:Exited", () => {
    this.panel.setAttribute("followanchor", !locationBarHidden);
  }, true);

  this.window.addEventListener("activate", this, true);
  if (this.tabbrowser.tabContainer)
    this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
}

PopupNotifications.prototype = {

  window: null,
  panel: null,
  tabbrowser: null,

  _iconBox: null,
  set iconBox(iconBox) {
    // Remove the listeners on the old iconBox, if needed
    if (this._iconBox) {
      this._iconBox.removeEventListener("click", this);
      this._iconBox.removeEventListener("keypress", this);
    }
    this._iconBox = iconBox;
    if (iconBox) {
      iconBox.addEventListener("click", this);
      iconBox.addEventListener("keypress", this);
    }
  },
  get iconBox() {
    return this._iconBox;
  },

  /**
   * Retrieve a Notification object associated with the browser/ID pair.
   * @param id
   *        The Notification ID to search for.
   * @param browser
   *        The browser whose notifications should be searched. If null, the
   *        currently selected browser's notifications will be searched.
   *
   * @returns the corresponding Notification object, or null if no such
   *          notification exists.
   */
  getNotification: function PopupNotifications_getNotification(id, browser) {
    let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
    return notifications.find(x => x.id == id) || null;
  },

  /**
   * Adds a new popup notification.
   * @param browser
   *        The <xul:browser> element associated with the notification. Must not
   *        be null.
   * @param id
   *        A unique ID that identifies the type of notification (e.g.
   *        "geolocation"). Only one notification with a given ID can be visible
   *        at a time. If a notification already exists with the given ID, it
   *        will be replaced.
   * @param message
   *        The text to be displayed in the notification.
   * @param anchorID
   *        The ID of the element that should be used as this notification
   *        popup's anchor. May be null, in which case the notification will be
   *        anchored to the iconBox.
   * @param mainAction
   *        A JavaScript object literal describing the notification button's
   *        action. If present, it must have the following properties:
   *          - label (string): the button's label.
   *          - accessKey (string): the button's accessKey.
   *          - callback (function): a callback to be invoked when the button is
   *            pressed, is passed an object that contains the following fields:
   *              - checkboxChecked: (boolean) If the optional checkbox is checked.
   *          - [optional] dismiss (boolean): If this is true, the notification
   *            will be dismissed instead of removed after running the callback.
   *          - [optional] disableHighlight (boolean): If this is true, the button
   *            will not apply the default highlight style.
   *        If null, the notification will have a default "OK" action button
   *        that can be used to dismiss the popup and secondaryActions will be ignored.
   * @param secondaryActions
   *        An optional JavaScript array describing the notification's alternate
   *        actions. The array should contain objects with the same properties
   *        as mainAction. These are used to populate the notification button's
   *        dropdown menu.
   * @param options
   *        An options JavaScript object holding additional properties for the
   *        notification. The following properties are currently supported:
   *        persistence: An integer. The notification will not automatically
   *                     dismiss for this many page loads.
   *        timeout:     A time in milliseconds. The notification will not
   *                     automatically dismiss before this time.
   *        persistWhileVisible:
   *                     A boolean. If true, a visible notification will always
   *                     persist across location changes.
   *        persistent:  A boolean. If true, the notification will always
   *                     persist even across tab and app changes (but not across
   *                     location changes), until the user accepts or rejects
   *                     the request. The notification will never be implicitly
   *                     dismissed.
   *        dismissed:   Whether the notification should be added as a dismissed
   *                     notification. Dismissed notifications can be activated
   *                     by clicking on their anchorElement.
   *        autofocus:   Whether the notification should be autofocused on
   *                     showing, stealing focus from any other focused element.
   *        eventCallback:
   *                     Callback to be invoked when the notification changes
   *                     state. The callback's first argument is a string
   *                     identifying the state change:
   *                     "dismissed": notification has been dismissed by the
   *                                  user (e.g. by clicking away or switching
   *                                  tabs)
   *                     "removed": notification has been removed (due to
   *                                location change or user action)
   *                     "showing": notification is about to be shown
   *                                (this can be fired multiple times as
   *                                 notifications are dismissed and re-shown)
   *                                If the callback returns true, the notification
   *                                will be dismissed.
   *                     "shown": notification has been shown (this can be fired
   *                              multiple times as notifications are dismissed
   *                              and re-shown)
   *                     "swapping": the docshell of the browser that created
   *                                 the notification is about to be swapped to
   *                                 another browser. A second parameter contains
   *                                 the browser that is receiving the docshell,
   *                                 so that the event callback can transfer stuff
   *                                 specific to this notification.
   *                                 If the callback returns true, the notification
   *                                 will be moved to the new browser.
   *                                 If the callback isn't implemented, returns false,
   *                                 or doesn't return any value, the notification
   *                                 will be removed.
   *        neverShow:   Indicate that no popup should be shown for this
   *                     notification. Useful for just showing the anchor icon.
   *        removeOnDismissal:
   *                     Notifications with this parameter set to true will be
   *                     removed when they would have otherwise been dismissed
   *                     (i.e. any time the popup is closed due to user
   *                     interaction).
   *        hideClose:   Indicate that the little close button in the corner of
   *                     the panel should be hidden.
   *        checkbox:    An object that allows you to add a checkbox and
   *                     control its behavior with these fields:
   *                       label:
   *                         (required) Label to be shown next to the checkbox.
   *                       checked:
   *                         (optional) Whether the checkbox should be checked
   *                         by default. Defaults to false.
   *                       checkedState:
   *                         (optional) An object that allows you to customize
   *                         the notification state when the checkbox is checked.
   *                           disableMainAction:
   *                             (optional) Whether the mainAction is disabled.
   *                             Defaults to false.
   *                           warningLabel:
   *                             (optional) A (warning) text that is shown below the
   *                             checkbox. Pass null to hide.
   *                       uncheckedState:
   *                         (optional) An object that allows you to customize
   *                         the notification state when the checkbox is not checked.
   *                         Has the same attributes as checkedState.
   *        popupIconClass:
   *                     A string. A class (or space separated list of classes)
   *                     that will be applied to the icon in the popup so that
   *                     several notifications using the same panel can use
   *                     different icons.
   *        popupIconURL:
   *                     A string. URL of the image to be displayed in the popup.
   *                     Normally specified in CSS using list-style-image and the
   *                     .popup-notification-icon[popupid=...] selector.
   *        learnMoreURL:
   *                     A string URL. Setting this property will make the
   *                     prompt display a "Learn More" link that, when clicked,
   *                     opens the URL in a new tab.
   *        displayURI:
   *                     The nsIURI of the page the notification came
   *                     from. If present, this will be displayed above the message.
   *                     If the nsIURI represents a file, the path will be displayed,
   *                     otherwise the hostPort will be displayed.
   * @returns the Notification object corresponding to the added notification.
   */
  show: function PopupNotifications_show(browser, id, message, anchorID,
                                         mainAction, secondaryActions, options) {
    function isInvalidAction(a) {
      return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
    }

    if (!browser)
      throw "PopupNotifications_show: invalid browser";
    if (!id)
      throw "PopupNotifications_show: invalid ID";
    if (mainAction && isInvalidAction(mainAction))
      throw "PopupNotifications_show: invalid mainAction";
    if (secondaryActions && secondaryActions.some(isInvalidAction))
      throw "PopupNotifications_show: invalid secondaryActions";

    let notification = new Notification(id, message, anchorID, mainAction,
                                        secondaryActions, browser, this, options);

    if (options && options.dismissed)
      notification.dismissed = true;

    let existingNotification = this.getNotification(id, browser);
    if (existingNotification)
      this._remove(existingNotification);

    let notifications = this._getNotificationsForBrowser(browser);
    notifications.push(notification);

    let isActiveBrowser = this._isActiveBrowser(browser);
    let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
    let isActiveWindow = fm.activeWindow == this.window;

    if (isActiveBrowser) {
      if (isActiveWindow) {

        // Autofocus if the notification requests focus.
        if (options && !options.dismissed && options.autofocus) {
          this.panel.removeAttribute("noautofocus");
        } else {
          this.panel.setAttribute("noautofocus", "true");
        }

        // show panel now
        this._update(notifications, new Set([notification.anchorElement]), true);
      } else {
        // indicate attention and update the icon if necessary
        if (!notification.dismissed) {
          this.window.getAttention();
        }
        this._updateAnchorIcons(notifications, this._getAnchorsForNotifications(
          notifications, notification.anchorElement));
        this._notify("backgroundShow");
      }

    } else {
      // Notify observers that we're not showing the popup (useful for testing)
      this._notify("backgroundShow");
    }

    return notification;
  },

  /**
   * Returns true if the notification popup is currently being displayed.
   */
  get isPanelOpen() {
    let panelState = this.panel.state;

    return panelState == "showing" || panelState == "open";
  },

  /**
   * Called by the consumer to indicate that a browser's location has changed,
   * so that we can update the active notifications accordingly.
   */
  locationChange: function PopupNotifications_locationChange(aBrowser) {
    if (!aBrowser)
      throw "PopupNotifications_locationChange: invalid browser";

    let notifications = this._getNotificationsForBrowser(aBrowser);

    notifications = notifications.filter(function(notification) {
      // The persistWhileVisible option allows an open notification to persist
      // across location changes
      if (notification.options.persistWhileVisible &&
          this.isPanelOpen) {
        if ("persistence" in notification.options &&
            notification.options.persistence)
          notification.options.persistence--;
        return true;
      }

      // The persistence option allows a notification to persist across multiple
      // page loads
      if ("persistence" in notification.options &&
          notification.options.persistence) {
        notification.options.persistence--;
        return true;
      }

      // The timeout option allows a notification to persist until a certain time
      if ("timeout" in notification.options &&
          Date.now() <= notification.options.timeout) {
        return true;
      }

      this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
      return false;
    }, this);

    this._setNotificationsForBrowser(aBrowser, notifications);

    if (this._isActiveBrowser(aBrowser)) {
      this.anchorVisibilityChange();
    }
  },

  /**
   * Called by the consumer to indicate that the visibility of the notification
   * anchors may have changed, but the location has not changed. This also
   * checks whether all notifications are suppressed for this window.
   *
   * Calling this method may result in the "showing" and "shown" events for
   * visible notifications to be invoked even if the anchor has not changed.
   */
  anchorVisibilityChange() {
    let suppress = this._shouldSuppress();
    if (!suppress) {
      // If notifications are not suppressed, always update the visibility.
      this._suppress = false;
      let notifications =
        this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
      this._update(notifications, this._getAnchorsForNotifications(notifications,
        getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
      return;
    }

    // Notifications are suppressed, ensure that the panel is hidden.
    if (!this._suppress) {
      this._suppress = true;
      this._hidePanel().catch(Cu.reportError);
    }
  },

  /**
   * Removes a Notification.
   * @param notification
   *        The Notification object to remove.
   */
  remove: function PopupNotifications_remove(notification) {
    this._remove(notification);

    if (this._isActiveBrowser(notification.browser)) {
      let notifications = this._getNotificationsForBrowser(notification.browser);
      this._update(notifications);
    }
  },

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "popuphidden":
        this._onPopupHidden(aEvent);
        break;
      case "activate":
        if (this.isPanelOpen) {
          for (let elt of this.panel.children)
            elt.notification.timeShown = this.window.performance.now();
          break;
        }

      case "TabSelect":
        let self = this;
        // This is where we could detect if the panel is dismissed if the page
        // was switched. Unfortunately, the user usually has clicked elsewhere
        // at this point so this value only gets recorded for programmatic
        // reasons, like the "Learn More" link being clicked and resulting in a
        // tab switch.
        this.nextDismissReason = TELEMETRY_STAT_DISMISSAL_LEAVE_PAGE;
        // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
        // handler results in the popup being hidden again for some reason...
        this.window.setTimeout(function() {
          self._update();
        }, 0);
        break;
      case "click":
      case "keypress":
        this._onIconBoxCommand(aEvent);
        break;
    }
  },

// Utility methods

  _ignoreDismissal: null,
  _currentAnchorElement: null,

  /**
   * Gets notifications for the currently selected browser.
   */
  get _currentNotifications() {
    return this.tabbrowser.selectedBrowser ? this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser) : [];
  },

  _remove: function PopupNotifications_removeHelper(notification) {
    // This notification may already be removed, in which case let's just fail
    // silently.
    let notifications = this._getNotificationsForBrowser(notification.browser);
    if (!notifications)
      return;

    var index = notifications.indexOf(notification);
    if (index == -1)
      return;

    if (this._isActiveBrowser(notification.browser))
      notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);

    // remove the notification
    notifications.splice(index, 1);
    this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
  },

  /**
   * Dismisses the notification without removing it.
   */
  _dismiss: function PopupNotifications_dismiss(event, telemetryReason) {
    if (telemetryReason) {
      this.nextDismissReason = telemetryReason;
    }

    // An explicitly dismissed persistent notification effectively becomes
    // non-persistent.
    if (event && telemetryReason == TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON) {
      let notificationEl = getNotificationFromElement(event.target);
      if (notificationEl) {
        notificationEl.notification.options.persistent = false;
      }
    }

    let browser = this.panel.firstChild &&
                  this.panel.firstChild.notification.browser;
    this.panel.hidePopup();
    if (browser)
      browser.focus();
  },

  /**
   * Hides the notification popup.
   */
  _hidePanel: function PopupNotifications_hide() {
    if (this.panel.state == "closed") {
      return Promise.resolve();
    }
    if (this._ignoreDismissal) {
      return this._ignoreDismissal.promise;
    }
    let deferred = PromiseUtils.defer();
    this._ignoreDismissal = deferred;
    this.panel.hidePopup();
    return deferred.promise;
  },

  /**
   * Removes all notifications from the notification popup.
   */
  _clearPanel() {
    let popupnotification;
    while ((popupnotification = this.panel.lastChild)) {
      this.panel.removeChild(popupnotification);

      // If this notification was provided by the chrome document rather than
      // created ad hoc, move it back to where we got it from.
      let originalParent = gNotificationParents.get(popupnotification);
      if (originalParent) {
        popupnotification.notification = null;

        // Remove nodes dynamically added to the notification's menu button
        // in _refreshPanel.
        let contentNode = popupnotification.lastChild;
        while (contentNode) {
          let previousSibling = contentNode.previousSibling;
          if (contentNode.nodeName == "menuitem" ||
              contentNode.nodeName == "menuseparator")
            popupnotification.removeChild(contentNode);
          contentNode = previousSibling;
        }

        // Re-hide the notification such that it isn't rendered in the chrome
        // document. _refreshPanel will unhide it again when needed.
        popupnotification.hidden = true;

        originalParent.appendChild(popupnotification);
      }
    }
  },

  _refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
    this._clearPanel();

    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

    notificationsToShow.forEach(function(n) {
      let doc = this.window.document;

      // Append "-notification" to the ID to try to avoid ID conflicts with other stuff
      // in the document.
      let popupnotificationID = n.id + "-notification";

      // If the chrome document provides a popupnotification with this id, use
      // that. Otherwise create it ad-hoc.
      let popupnotification = doc.getElementById(popupnotificationID);
      if (popupnotification)
        gNotificationParents.set(popupnotification, popupnotification.parentNode);
      else
        popupnotification = doc.createElementNS(XUL_NS, "popupnotification");

      popupnotification.setAttribute("label", n.message);
      popupnotification.setAttribute("id", popupnotificationID);
      popupnotification.setAttribute("popupid", n.id);
      if (Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton")) {
        popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._onButtonEvent(event, 'secondarybuttoncommand');");
      } else {
        popupnotification.setAttribute("closebuttoncommand", `PopupNotifications._dismiss(event, ${TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON});`);
      }
      if (n.mainAction) {
        popupnotification.setAttribute("buttonlabel", n.mainAction.label);
        popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
        popupnotification.setAttribute("buttonhighlight", !n.mainAction.disableHighlight);
        popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
        popupnotification.setAttribute("dropmarkerpopupshown", "PopupNotifications._onButtonEvent(event, 'dropmarkerpopupshown');");
        popupnotification.setAttribute("learnmoreclick", "PopupNotifications._onButtonEvent(event, 'learnmoreclick');");
        popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
      } else {
        // Enable the default button to let the user close the popup if the close button is hidden
        popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
        popupnotification.setAttribute("buttonhighlight", "true");
        popupnotification.removeAttribute("buttonlabel");
        popupnotification.removeAttribute("buttonaccesskey");
        popupnotification.removeAttribute("dropmarkerpopupshown");
        popupnotification.removeAttribute("learnmoreclick");
        popupnotification.removeAttribute("menucommand");
      }

      if (n.options.popupIconClass) {
        let classes = "popup-notification-icon " + n.options.popupIconClass;
        popupnotification.setAttribute("iconclass", classes);
      }
      if (n.options.popupIconURL)
        popupnotification.setAttribute("icon", n.options.popupIconURL);

      if (n.options.learnMoreURL)
        popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
      else
        popupnotification.removeAttribute("learnmoreurl");

      if (n.options.displayURI) {
        let uri;
        try {
           if (n.options.displayURI instanceof Ci.nsIFileURL) {
            uri = n.options.displayURI.path;
          } else {
            uri = n.options.displayURI.hostPort;
          }
          popupnotification.setAttribute("origin", uri);
        } catch (e) {
          Cu.reportError(e);
          popupnotification.removeAttribute("origin");
        }
      } else
        popupnotification.removeAttribute("origin");

      if (n.options.hideClose)
        popupnotification.setAttribute("closebuttonhidden", "true");

      popupnotification.notification = n;

      if (n.mainAction && n.secondaryActions && n.secondaryActions.length > 0) {
        let telemetryStatId = TELEMETRY_STAT_ACTION_2;

        let secondaryAction = n.secondaryActions[0];
        popupnotification.setAttribute("secondarybuttonlabel", secondaryAction.label);
        popupnotification.setAttribute("secondarybuttonaccesskey", secondaryAction.accessKey);
        popupnotification.setAttribute("secondarybuttoncommand", "PopupNotifications._onButtonEvent(event, 'secondarybuttoncommand');");

        for (let i = 1; i < n.secondaryActions.length; i++) {
          let action = n.secondaryActions[i];
          let item = doc.createElementNS(XUL_NS, "menuitem");
          item.setAttribute("label", action.label);
          item.setAttribute("accesskey", action.accessKey);
          item.notification = n;
          item.action = action;

          popupnotification.appendChild(item);

          // We can only record a limited number of actions in telemetry. If
          // there are more, the latest are all recorded in the last bucket.
          item.action.telemetryStatId = telemetryStatId;
          if (telemetryStatId < TELEMETRY_STAT_ACTION_LAST) {
            telemetryStatId++;
          }
        }

        if (n.secondaryActions.length < 2) {
          popupnotification.setAttribute("dropmarkerhidden", "true");
        }
      } else {
        popupnotification.setAttribute("secondarybuttonhidden", "true");
        popupnotification.setAttribute("dropmarkerhidden", "true");
      }

      let checkbox = n.options.checkbox;
      if (checkbox && checkbox.label) {
        let checked = n._checkboxChecked != null ? n._checkboxChecked : !!checkbox.checked;

        popupnotification.setAttribute("checkboxhidden", "false");
        popupnotification.setAttribute("checkboxchecked", checked);
        popupnotification.setAttribute("checkboxlabel", checkbox.label);

        popupnotification.setAttribute("checkboxcommand", "PopupNotifications._onCheckboxCommand(event);");

        if (checked) {
          this._setNotificationUIState(popupnotification, checkbox.checkedState);
        } else {
          this._setNotificationUIState(popupnotification, checkbox.uncheckedState);
        }
      } else {
        popupnotification.setAttribute("checkboxhidden", "true");
        popupnotification.setAttribute("warninghidden", "true");
      }

      this.panel.appendChild(popupnotification);

      // The popupnotification may be hidden if we got it from the chrome
      // document rather than creating it ad hoc.
      popupnotification.hidden = false;
    }, this);
  },

  _setNotificationUIState(notification, state = {}) {
    if (state.disableMainAction) {
      notification.setAttribute("mainactiondisabled", "true");
    } else {
      notification.removeAttribute("mainactiondisabled");
    }
    if (state.warningLabel) {
      notification.setAttribute("warninglabel", state.warningLabel);
      notification.removeAttribute("warninghidden");
    } else {
      notification.setAttribute("warninghidden", "true");
    }
  },

  _onCheckboxCommand(event) {
    let notificationEl = getNotificationFromElement(event.originalTarget);
    let checked = notificationEl.checkbox.checked;
    let notification = notificationEl.notification;

    // Save checkbox state to be able to persist it when re-opening the doorhanger.
    notification._checkboxChecked = checked;

    if (checked) {
      this._setNotificationUIState(notificationEl, notification.options.checkbox.checkedState);
    } else {
      this._setNotificationUIState(notificationEl, notification.options.checkbox.uncheckedState);
    }
  },

  _showPanel: function PopupNotifications_showPanel(notificationsToShow, anchorElement) {
    this.panel.hidden = false;

    notificationsToShow = notificationsToShow.filter(n => {
      if (anchorElement != n.anchorElement) {
        return false;
      }

      let dismiss = this._fireCallback(n, NOTIFICATION_EVENT_SHOWING);
      if (dismiss)
        n.dismissed = true;
      return !dismiss;
    });
    if (!notificationsToShow.length)
      return;
    let notificationIds = notificationsToShow.map(n => n.id);

    this._refreshPanel(notificationsToShow);

    // If the anchor element is hidden or null, fall back to the identity icon.
    if (!anchorElement || (anchorElement.boxObject.height == 0 &&
                           anchorElement.boxObject.width == 0)) {
      anchorElement = this.window.document.getElementById("identity-icon");

      // If the identity icon is not available in this window, or maybe the
      // entire location bar is hidden for any reason, use the tab as the
      // anchor. We only ever show notifications for the current browser, so we
      // can just use the current tab.
      if (!anchorElement || (anchorElement.boxObject.height == 0 &&
                             anchorElement.boxObject.width == 0)) {
        anchorElement = this.tabbrowser.selectedTab;

        // If we're in an entirely chromeless environment, set the anchorElement
        // to null and let openPopup show the notification at (0,0) later.
        if (!anchorElement || (anchorElement.boxObject.height == 0 &&
                               anchorElement.boxObject.width == 0)) {
          anchorElement = null;
        }
      }
    }

    if (this.isPanelOpen && this._currentAnchorElement == anchorElement) {
      notificationsToShow.forEach(function(n) {
        this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
      }, this);

      // Make sure we update the noautohide attribute on the panel, in case it changed.
      if (notificationsToShow.some(n => n.options.persistent)) {
        this.panel.setAttribute("noautohide", "true");
      } else {
        this.panel.removeAttribute("noautohide");
      }

      // Let tests know that the panel was updated and what notifications it was
      // updated with so that tests can wait for the correct notifications to be
      // added.
      let event = new this.window.CustomEvent("PanelUpdated",
                                              {"detail": notificationIds});
      this.panel.dispatchEvent(event);
      return;
    }

    // If the panel is already open but we're changing anchors, we need to hide
    // it first.  Otherwise it can appear in the wrong spot.  (_hidePanel is
    // safe to call even if the panel is already hidden.)
    this._hidePanel().then(() => {
      this._currentAnchorElement = anchorElement;

      if (notificationsToShow.some(n => n.options.persistent)) {
        this.panel.setAttribute("noautohide", "true");
      } else {
        this.panel.removeAttribute("noautohide");
      }

      // On OS X and Linux we need a different panel arrow color for
      // click-to-play plugins, so copy the popupid and use css.
      this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
      notificationsToShow.forEach(function(n) {
        // Record that the notification was actually displayed on screen.
        // Notifications that were opened a second time or that were originally
        // shown with "options.dismissed" will be recorded in a separate bucket.
        n._recordTelemetryStat(TELEMETRY_STAT_OFFERED);
        // Remember the time the notification was shown for the security delay.
        n.timeShown = this.window.performance.now();
      }, this);

      // Unless the panel closing is triggered by a specific known code path,
      // the next reason will be that the user clicked elsewhere.
      this.nextDismissReason = TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE;

      let target = this.panel;
      if (target.parentNode) {
        // NOTIFICATION_EVENT_SHOWN should be fired for the panel before
        // anyone listening for popupshown on the panel gets run. Otherwise,
        // the panel will not be initialized when the popupshown event
        // listeners run.
        // 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;
      }
      if (this._popupshownListener) {
        target.removeEventListener("popupshown", this._popupshownListener, true);
      }
      this._popupshownListener = function(e) {
        target.removeEventListener("popupshown", this._popupshownListener, true);
        this._popupshownListener = null;

        notificationsToShow.forEach(function(n) {
          this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
        }, this);
        // These notifications are used by tests to know when all the processing
        // required to display the panel has happened.
        this.panel.dispatchEvent(new this.window.CustomEvent("Shown"));
        let event = new this.window.CustomEvent("PanelUpdated",
                                                {"detail": notificationIds});
        this.panel.dispatchEvent(event);
      };
      this._popupshownListener = this._popupshownListener.bind(this);
      target.addEventListener("popupshown", this._popupshownListener, true);

      this.panel.openPopup(anchorElement, "bottomcenter topleft", 0, 0);
    });
  },

  /**
   * Updates the notification state in response to window activation or tab
   * selection changes.
   *
   * @param notifications an array of Notification instances. if null,
   *                      notifications will be retrieved off the current
   *                      browser tab
   * @param anchors       is a XUL element or a Set of XUL elements that the
   *                      notifications panel(s) will be anchored to.
   * @param dismissShowing if true, dismiss any currently visible notifications
   *                       if there are no notifications to show. Otherwise,
   *                       currently displayed notifications will be left alone.
   */
  _update: function PopupNotifications_update(notifications, anchors = new Set(), dismissShowing = false) {
    if (anchors instanceof Ci.nsIDOMXULElement)
      anchors = new Set([anchors]);

    if (!notifications)
      notifications = this._currentNotifications;

    let haveNotifications = notifications.length > 0;
    if (!anchors.size && haveNotifications)
      anchors = this._getAnchorsForNotifications(notifications);

    let useIconBox = !!this.iconBox;
    if (useIconBox && anchors.size) {
      for (let anchor of anchors) {
        if (anchor.parentNode == this.iconBox)
          continue;
        useIconBox = false;
        break;
      }
    }

    // Filter out notifications that have been dismissed, unless they are
    // persistent. Also check if we should not show any notification.
    let notificationsToShow = [];
    if (!this._suppress) {
      notificationsToShow = notifications.filter(
        n => (!n.dismissed || n.options.persistent) && !n.options.neverShow);
    }

    if (useIconBox) {
      // Hide icons of the previous tab.
      this._hideIcons();
    }

    if (haveNotifications) {
      // Also filter out notifications that are for a different anchor.
      notificationsToShow = notificationsToShow.filter(function(n) {
        return anchors.has(n.anchorElement);
      });

      if (useIconBox) {
        this._showIcons(notifications);
        this.iconBox.hidden = false;
        // Make sure that panels can only be attached to anchors of shown
        // notifications inside an iconBox.
        anchors = this._getAnchorsForNotifications(notificationsToShow);
      } else if (anchors.size) {
        this._updateAnchorIcons(notifications, anchors);
      }
    }

    if (notificationsToShow.length > 0) {
      let anchorElement = anchors.values().next().value;
      if (anchorElement) {
        this._showPanel(notificationsToShow, anchorElement);
      }

      // Setup a capturing event listener on the whole window to catch the
      // escape key while persistent notifications are visible.
      this.window.addEventListener("keypress", this._handleWindowKeyPress, true);
    } else {
      // Notify observers that we're not showing the popup (useful for testing)
      this._notify("updateNotShowing");

      // Close the panel if there are no notifications to show.
      // When called from PopupNotifications.show() we should never close the
      // panel, however. It may just be adding a dismissed notification, in
      // which case we want to continue showing any existing notifications.
      if (!dismissShowing)
        this._dismiss();

      // Only hide the iconBox if we actually have no notifications (as opposed
      // to not having any showable notifications)
      if (!haveNotifications) {
        if (useIconBox) {
          this.iconBox.hidden = true;
        } else if (anchors.size) {
          for (let anchorElement of anchors)
            anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
        }
      }

      // Stop listening to keyboard events for notifications.
      this.window.removeEventListener("keypress", this._handleWindowKeyPress, true);
    }
  },

  _updateAnchorIcons: function PopupNotifications_updateAnchorIcons(notifications,
                                                                    anchorElements) {
    for (let anchorElement of anchorElements) {
      anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
      // Use the anchorID as a class along with the default icon class as a
      // fallback if anchorID is not defined in CSS. We always use the first
      // notifications icon, so in the case of multiple notifications we'll
      // only use the default icon.
      if (anchorElement.classList.contains("notification-anchor-icon")) {
        // remove previous icon classes
        let className = anchorElement.className.replace(/([-\w]+-notification-icon\s?)/g, "")
        if (notifications.length > 0) {
          // Find the first notification this anchor used for.
          let notification = notifications[0];
          for (let n of notifications) {
            if (n.anchorElement == anchorElement) {
              notification = n;
              break;
            }
          }
          // With this notification we can better approximate the most fitting
          // style.
          className = notification.anchorID + " " + className;
        }
        anchorElement.className = className;
      }
    }
  },

  _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
    for (let notification of aCurrentNotifications) {
      let anchorElm = notification.anchorElement;
      if (anchorElm) {
        anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");

        if (notification.options.extraAttr) {
          anchorElm.setAttribute("extraAttr", notification.options.extraAttr);
        }

      }
    }
  },

  _hideIcons: function PopupNotifications_hideIcons() {
    let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
    for (let icon of icons) {
      icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
    }
  },

  /**
   * Gets and sets notifications for the browser.
   */
  _getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
    let notifications = popupNotificationsMap.get(browser);
    if (!notifications) {
      // Initialize the WeakMap for the browser so callers can reference/manipulate the array.
      notifications = [];
      popupNotificationsMap.set(browser, notifications);
    }
    return notifications;
  },
  _setNotificationsForBrowser: function PopupNotifications_setNotifications(browser, notifications) {
    popupNotificationsMap.set(browser, notifications);
    return notifications;
  },

  _getAnchorsForNotifications: function PopupNotifications_getAnchorsForNotifications(notifications, defaultAnchor) {
    let anchors = new Set();
    for (let notification of notifications) {
      if (notification.anchorElement)
        anchors.add(notification.anchorElement)
    }
    if (defaultAnchor && !anchors.size)
      anchors.add(defaultAnchor);
    return anchors;
  },

  _isActiveBrowser(browser) {
    // We compare on frameLoader instead of just comparing the
    // selectedBrowser and browser directly because browser tabs in
    // Responsive Design Mode put the actual web content into a
    // mozbrowser iframe and proxy property read/write and method
    // calls from the tab to that iframe. This is so that attempts
    // to reload the tab end up reloading the content in
    // Responsive Design Mode, and not the Responsive Design Mode
    // viewer itself.
    //
    // This means that PopupNotifications can come up from a browser
    // in Responsive Design Mode, but the selectedBrowser will not match
    // the browser being passed into this function, despite the browser
    // actually being within the selected tab. We workaround this by
    // comparing frameLoader instead, which is proxied from the outer
    // <xul:browser> to the inner mozbrowser <iframe>.
    return this.tabbrowser.selectedBrowser.frameLoader == browser.frameLoader;
  },

  _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {
    // Left click, space or enter only
    let type = event.type;
    if (type == "click" && event.button != 0)
      return;

    if (type == "keypress" &&
        !(event.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
          event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN))
      return;

    if (this._currentNotifications.length == 0)
      return;

    event.stopPropagation();

    // Get the anchor that is the immediate child of the icon box
    let anchor = event.target;
    while (anchor && anchor.parentNode != this.iconBox)
      anchor = anchor.parentNode;

    if (!anchor) {
      return;
    }

    // If the panel is not closed, and the anchor is different, immediately mark all
    // active notifications for the previous anchor as dismissed
    if (this.panel.state != "closed" && anchor != this._currentAnchorElement) {
      this._dismissOrRemoveCurrentNotifications();
    }

    // Avoid reshowing notifications that are already shown and have not been dismissed.
    if (this.panel.state == "closed" || anchor != this._currentAnchorElement) {
      // As soon as the panel is shown, focus the first element in the selected notification.
      this.panel.addEventListener("popupshown",
        () => this.window.document.commandDispatcher.advanceFocusIntoSubtree(this.panel),
        {once: true});

      this._reshowNotifications(anchor);
    } else {
      // Focus the first element in the selected notification.
      this.window.document.commandDispatcher.advanceFocusIntoSubtree(this.panel);
    }
  },

  _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
    // Mark notifications anchored to this anchor as un-dismissed
    browser = browser || this.tabbrowser.selectedBrowser;
    let notifications = this._getNotificationsForBrowser(browser);
    notifications.forEach(function(n) {
      if (n.anchorElement == anchor)
        n.dismissed = false;
    });

    if (this._isActiveBrowser(browser)) {
      // ...and then show them.
      this._update(notifications, anchor);
    }
  },

  _swapBrowserNotifications: function PopupNotifications_swapBrowserNoficications(ourBrowser, otherBrowser) {
    // When swaping browser docshells (e.g. dragging tab to new window) we need
    // to update our notification map.

    let ourNotifications = this._getNotificationsForBrowser(ourBrowser);
    let other = otherBrowser.ownerGlobal.PopupNotifications;
    if (!other) {
      if (ourNotifications.length > 0)
        Cu.reportError("unable to swap notifications: otherBrowser doesn't support notifications");
      return;
    }
    let otherNotifications = other._getNotificationsForBrowser(otherBrowser);
    if (ourNotifications.length < 1 && otherNotifications.length < 1) {
      // No notification to swap.
      return;
    }

    otherNotifications = otherNotifications.filter(n => {
      if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, ourBrowser)) {
        n.browser = ourBrowser;
        n.owner = this;
        return true;
      }
      other._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
      return false;
    });

    ourNotifications = ourNotifications.filter(n => {
      if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, otherBrowser)) {
        n.browser = otherBrowser;
        n.owner = other;
        return true;
      }
      this._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
      return false;
    });

    this._setNotificationsForBrowser(otherBrowser, ourNotifications);
    other._setNotificationsForBrowser(ourBrowser, otherNotifications);

    if (otherNotifications.length > 0)
      this._update(otherNotifications);
    if (ourNotifications.length > 0)
      other._update(ourNotifications);
  },

  _fireCallback: function PopupNotifications_fireCallback(n, event, ...args) {
    try {
      if (n.options.eventCallback)
        return n.options.eventCallback.call(n, event, ...args);
    } catch (error) {
      Cu.reportError(error);
    }
    return undefined;
  },

  _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
    if (event.target != this.panel) {
      return;
    }

    // We may have removed the "noautofocus" attribute before showing the panel
    // if the notification specified it wants to autofocus on first show.
    // When the panel is closed, we have to restore the attribute to its default
    // value, so we don't autofocus it if it's subsequently opened from a different code path.
    this.panel.setAttribute("noautofocus", "true");

    // Handle the case where the panel was closed programmatically.
    if (this._ignoreDismissal) {
      this._ignoreDismissal.resolve();
      this._ignoreDismissal = null;
      return;
    }

    this._dismissOrRemoveCurrentNotifications();

    this._clearPanel();

    this._update();
  },

  _dismissOrRemoveCurrentNotifications() {
    let browser = this.panel.firstChild &&
                  this.panel.firstChild.notification.browser;
    if (!browser)
      return;

    let notifications = this._getNotificationsForBrowser(browser);
    // Mark notifications as dismissed and call dismissal callbacks
    Array.forEach(this.panel.childNodes, function(nEl) {
      let notificationObj = nEl.notification;
      // Never call a dismissal handler on a notification that's been removed.
      if (notifications.indexOf(notificationObj) == -1)
        return;

      // Record the time of the first notification dismissal if the main action
      // was not triggered in the meantime.
      let timeSinceShown = this.window.performance.now() - notificationObj.timeShown;
      if (!notificationObj.wasDismissed &&
          !notificationObj.recordedTelemetryMainAction) {
        notificationObj._recordTelemetry("POPUP_NOTIFICATION_DISMISSAL_MS",
                                         timeSinceShown);
      }
      notificationObj._recordTelemetryStat(this.nextDismissReason);

      // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
      // if the notification is removed.
      if (notificationObj.options.removeOnDismissal) {
        this._remove(notificationObj);
      } else {
        notificationObj.dismissed = true;
        this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
      }
    }, this);
  },

  _onButtonEvent(event, type, notificationEl = null) {
    if (!notificationEl) {
      notificationEl = getNotificationFromElement(event.originalTarget);
    }

    if (!notificationEl)
      throw "PopupNotifications._onButtonEvent: couldn't find notification element";

    if (!notificationEl.notification)
      throw "PopupNotifications._onButtonEvent: couldn't find notification";

    let notification = notificationEl.notification;

    if (type == "dropmarkerpopupshown") {
      notification._recordTelemetryStat(TELEMETRY_STAT_OPEN_SUBMENU);
      return;
    }

    if (type == "learnmoreclick") {
      notification._recordTelemetryStat(TELEMETRY_STAT_LEARN_MORE);
      return;
    }

    if (type == "buttoncommand") {
      // Record the total timing of the main action since the notification was
      // created, even if the notification was dismissed in the meantime.
      let timeSinceCreated = this.window.performance.now() - notification.timeCreated;
      if (!notification.recordedTelemetryMainAction) {
        notification.recordedTelemetryMainAction = true;
        notification._recordTelemetry("POPUP_NOTIFICATION_MAIN_ACTION_MS",
                                      timeSinceCreated);
      }
    }

    if (type == "buttoncommand" || type == "secondarybuttoncommand") {
      if (Services.focus.activeWindow != this.window) {
        Services.console.logStringMessage("PopupNotifications._onButtonEvent: " +
                                          "Button click happened before the window was focused");
        this.window.focus();
        return;
      }

      let timeSinceShown = this.window.performance.now() - notification.timeShown;
      if (timeSinceShown < this.buttonDelay) {
        Services.console.logStringMessage("PopupNotifications._onButtonEvent: " +
                                          "Button click happened before the security delay: " +
                                          timeSinceShown + "ms");
        return;
      }
    }

    let action = notification.mainAction;
    let telemetryStatId = TELEMETRY_STAT_ACTION_1;

    if (type == "secondarybuttoncommand") {
      action = notification.secondaryActions[0];
      telemetryStatId = TELEMETRY_STAT_ACTION_2;
    }

    notification._recordTelemetryStat(telemetryStatId);

    if (action) {
      try {
        action.callback.call(undefined, {
          checkboxChecked: notificationEl.checkbox.checked
        });
      } catch (error) {
        Cu.reportError(error);
      }

      if (action.dismiss) {
        this._dismiss();
        return;
      }
    }

    this._remove(notification);
    this._update();
  },

  _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
    let target = event.originalTarget;
    if (!target.action || !target.notification)
      throw "menucommand target has no associated action/notification";

    let notificationEl = target.parentElement;
    event.stopPropagation();

    target.notification._recordTelemetryStat(target.action.telemetryStatId);

    try {
      target.action.callback.call(undefined, {
        checkboxChecked: notificationEl.checkbox.checked
      });
    } catch (error) {
      Cu.reportError(error);
    }

    if (target.action.dismiss) {
      this._dismiss();
      return;
    }

    this._remove(target.notification);
    this._update();
  },

  _notify: function PopupNotifications_notify(topic) {
    Services.obs.notifyObservers(null, "PopupNotifications-" + topic);
  },
};
PK
!<88modules/Preferences.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Preferences"];

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");

// The minimum and maximum integers that can be set as preferences.
// The range of valid values is narrower than the range of valid JS values
// because the native preferences code treats integers as NSPR PRInt32s,
// which are 32-bit signed integers on all platforms.
const MAX_INT = 0x7FFFFFFF; // Math.pow(2, 31) - 1
const MIN_INT = -0x80000000;

this.Preferences =
  function Preferences(args) {
    this._cachedPrefBranch = null;
    if (isObject(args)) {
      if (args.branch)
        this._branchStr = args.branch;
      if (args.defaultBranch)
        this._defaultBranch = args.defaultBranch;
      if (args.privacyContext)
        this._privacyContext = args.privacyContext;
    } else if (args)
      this._branchStr = args;
  };

/**
 * Get the value of a pref, if any; otherwise return the default value.
 *
 * @param   prefName  {String|Array}
 *          the pref to get, or an array of prefs to get
 *
 * @param   defaultValue
 *          the default value, if any, for prefs that don't have one
 *
 * @param   valueType
 *          the XPCOM interface of the pref's complex value type, if any
 *
 * @returns the value of the pref, if any; otherwise the default value
 */
Preferences.get = function(prefName, defaultValue, valueType = Ci.nsISupportsString) {
  if (Array.isArray(prefName))
    return prefName.map(v => this.get(v, defaultValue));

  return this._get(prefName, defaultValue, valueType);
};

Preferences._get = function(prefName, defaultValue, valueType) {
  switch (this._prefBranch.getPrefType(prefName)) {
    case Ci.nsIPrefBranch.PREF_STRING:
      return this._prefBranch.getComplexValue(prefName, valueType).data;

    case Ci.nsIPrefBranch.PREF_INT:
      return this._prefBranch.getIntPref(prefName);

    case Ci.nsIPrefBranch.PREF_BOOL:
      return this._prefBranch.getBoolPref(prefName);

    case Ci.nsIPrefBranch.PREF_INVALID:
      return defaultValue;

    default:
      // This should never happen.
      throw new Error(`Error getting pref ${prefName}; its value's type is ` +
                      `${this._prefBranch.getPrefType(prefName)}, which I don't ` +
                      `know how to handle.`);
  }
};

/**
 * Set a preference to a value.
 *
 * You can set multiple prefs by passing an object as the only parameter.
 * In that case, this method will treat the properties of the object
 * as preferences to set, where each property name is the name of a pref
 * and its corresponding property value is the value of the pref.
 *
 * @param   prefName  {String|Object}
 *          the name of the pref to set; or an object containing a set
 *          of prefs to set
 *
 * @param   prefValue {String|Number|Boolean}
 *          the value to which to set the pref
 *
 * Note: Preferences cannot store non-integer numbers or numbers outside
 * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
 * store it as a string by calling toString() on the number before passing
 * it to this method, i.e.:
 *   Preferences.set("pi", 3.14159.toString())
 *   Preferences.set("big", Math.pow(2, 31).toString()).
 */
Preferences.set = function(prefName, prefValue) {
  if (isObject(prefName)) {
    for (let [name, value] of Object.entries(prefName))
      this.set(name, value);
    return;
  }

  this._set(prefName, prefValue);
};

Preferences._set = function(prefName, prefValue) {
  let prefType;
  if (typeof prefValue != "undefined" && prefValue != null)
    prefType = prefValue.constructor.name;

  switch (prefType) {
    case "String":
      this._prefBranch.setStringPref(prefName, prefValue);
      break;

    case "Number":
      // We throw if the number is outside the range, since the result
      // will never be what the consumer wanted to store, but we only warn
      // if the number is non-integer, since the consumer might not mind
      // the loss of precision.
      if (prefValue > MAX_INT || prefValue < MIN_INT)
        throw new Error(
              `you cannot set the ${prefName} pref to the number ` +
              `${prefValue}, as number pref values must be in the signed ` +
              `32-bit integer range -(2^31-1) to 2^31-1.  To store numbers ` +
              `outside that range, store them as strings.`);
      this._prefBranch.setIntPref(prefName, prefValue);
      if (prefValue % 1 != 0)
        Cu.reportError("Warning: setting the " + prefName + " pref to the " +
                       "non-integer number " + prefValue + " converted it " +
                       "to the integer number " + this.get(prefName) +
                       "; to retain fractional precision, store non-integer " +
                       "numbers as strings.");
      break;

    case "Boolean":
      this._prefBranch.setBoolPref(prefName, prefValue);
      break;

    default:
      throw new Error(`can't set pref ${prefName} to value '${prefValue}'; ` +
                      `it isn't a String, Number, or Boolean`);
  }
};

/**
 * Whether or not the given pref has a value.  This is different from isSet
 * because it returns true whether the value of the pref is a default value
 * or a user-set value, while isSet only returns true if the value
 * is a user-set value.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a value; or, if the caller provided
 *          an array of pref names, an array of booleans indicating whether
 *          or not the prefs have values
 */
Preferences.has = function(prefName) {
  if (Array.isArray(prefName))
    return prefName.map(this.has, this);

  return (this._prefBranch.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
};

/**
 * Whether or not the given pref has a user-set value.  This is different
 * from |has| because it returns true only if the value of the pref is a user-
 * set value, while |has| returns true if the value of the pref is a default
 * value or a user-set value.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a user-set value; or, if the caller
 *          provided an array of pref names, an array of booleans indicating
 *          whether or not the prefs have user-set values
 */
Preferences.isSet = function(prefName) {
  if (Array.isArray(prefName))
    return prefName.map(this.isSet, this);

  return (this.has(prefName) && this._prefBranch.prefHasUserValue(prefName));
},

/**
 * Whether or not the given pref has a user-set value. Use isSet instead,
 * which is equivalent.
 * @deprecated
 */
Preferences.modified = function(prefName) { return this.isSet(prefName) },

Preferences.reset = function(prefName) {
  if (Array.isArray(prefName)) {
    prefName.map(v => this.reset(v));
    return;
  }

  this._prefBranch.clearUserPref(prefName);
};

/**
 * Lock a pref so it can't be changed.
 *
 * @param   prefName  {String|Array}
 *          the pref to lock, or an array of prefs to lock
 */
Preferences.lock = function(prefName) {
  if (Array.isArray(prefName))
    prefName.map(this.lock, this);

  this._prefBranch.lockPref(prefName);
};

/**
 * Unlock a pref so it can be changed.
 *
 * @param   prefName  {String|Array}
 *          the pref to lock, or an array of prefs to lock
 */
Preferences.unlock = function(prefName) {
  if (Array.isArray(prefName))
    prefName.map(this.unlock, this);

  this._prefBranch.unlockPref(prefName);
};

/**
 * Whether or not the given pref is locked against changes.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a user-set value; or, if the caller
 *          provided an array of pref names, an array of booleans indicating
 *          whether or not the prefs have user-set values
 */
Preferences.locked = function(prefName) {
  if (Array.isArray(prefName))
    return prefName.map(this.locked, this);

  return this._prefBranch.prefIsLocked(prefName);
};

/**
 * Start observing a pref.
 *
 * The callback can be a function or any object that implements nsIObserver.
 * When the callback is a function and thisObject is provided, it gets called
 * as a method of thisObject.
 *
 * @param   prefName    {String}
 *          the name of the pref to observe
 *
 * @param   callback    {Function|Object}
 *          the code to notify when the pref changes;
 *
 * @param   thisObject  {Object}  [optional]
 *          the object to use as |this| when calling a Function callback;
 *
 * @returns the wrapped observer
 */
Preferences.observe = function(prefName, callback, thisObject) {
  let fullPrefName = this._branchStr + (prefName || "");

  let observer = new PrefObserver(fullPrefName, callback, thisObject);
  Preferences._prefBranch.addObserver(fullPrefName, observer, true);
  observers.push(observer);

  return observer;
};

/**
 * Stop observing a pref.
 *
 * You must call this method with the same prefName, callback, and thisObject
 * with which you originally registered the observer.  However, you don't have
 * to call this method on the same exact instance of Preferences; you can call
 * it on any instance.  For example, the following code first starts and then
 * stops observing the "foo.bar.baz" preference:
 *
 *   let observer = function() {...};
 *   Preferences.observe("foo.bar.baz", observer);
 *   new Preferences("foo.bar.").ignore("baz", observer);
 *
 * @param   prefName    {String}
 *          the name of the pref being observed
 *
 * @param   callback    {Function|Object}
 *          the code being notified when the pref changes
 *
 * @param   thisObject  {Object}  [optional]
 *          the object being used as |this| when calling a Function callback
 */
Preferences.ignore = function(prefName, callback, thisObject) {
  let fullPrefName = this._branchStr + (prefName || "");

  // This seems fairly inefficient, but I'm not sure how much better we can
  // make it.  We could index by fullBranch, but we can't index by callback
  // or thisObject, as far as I know, since the keys to JavaScript hashes
  // (a.k.a. objects) can apparently only be primitive values.
  let [observer] = observers.filter(v => v.prefName == fullPrefName &&
                                         v.callback == callback &&
                                         v.thisObject == thisObject);

  if (observer) {
    Preferences._prefBranch.removeObserver(fullPrefName, observer);
    observers.splice(observers.indexOf(observer), 1);
  } else {
    Cu.reportError(`Attempt to stop observing a preference "${prefName}" that's not being observed`);
  }
};

Preferences.resetBranch = function(prefBranch = "") {
  try {
    this._prefBranch.resetBranch(prefBranch);
  } catch (ex) {
    // The current implementation of nsIPrefBranch in Mozilla
    // doesn't implement resetBranch, so we do it ourselves.
    if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
      this.reset(this._prefBranch.getChildList(prefBranch, []));
    else
      throw ex;
  }
},

/**
 * A string identifying the branch of the preferences tree to which this
 * instance provides access.
 * @private
 */
Preferences._branchStr = "";

/**
 * The cached preferences branch object this instance encapsulates, or null.
 * Do not use!  Use _prefBranch below instead.
 * @private
 */
Preferences._cachedPrefBranch = null;

/**
 * The preferences branch object for this instance.
 * @private
 */
Object.defineProperty(Preferences, "_prefBranch",
{
  get: function _prefBranch() {
    if (!this._cachedPrefBranch) {
      let prefSvc = Services.prefs;
      this._cachedPrefBranch = this._defaultBranch ?
                               prefSvc.getDefaultBranch(this._branchStr) :
                               prefSvc.getBranch(this._branchStr);
    }
    return this._cachedPrefBranch;
  },
  enumerable: true,
  configurable: true
});

// Constructor-based access (Preferences.get(...) and set) is preferred over
// instance-based access (new Preferences().get(...) and set) and when using the
// root preferences branch, as it's desirable not to allocate the extra object.
// But both forms are acceptable.
Preferences.prototype = Preferences;

/**
 * A cache of pref observers.
 *
 * We use this to remove observers when a caller calls Preferences::ignore.
 *
 * All Preferences instances share this object, because we want callers to be
 * able to remove an observer using a different Preferences object than the one
 * with which they added it.  That means we have to identify the observers
 * in this object by their complete pref name, not just their name relative to
 * the root branch of the Preferences object with which they were created.
 */
var observers = [];

function PrefObserver(prefName, callback, thisObject) {
  this.prefName = prefName;
  this.callback = callback;
  this.thisObject = thisObject;
}

PrefObserver.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),

  observe(subject, topic, data) {
    // The pref service only observes whole branches, but we only observe
    // individual preferences, so we check here that the pref that changed
    // is the exact one we're observing (and not some sub-pref on the branch).
    if (data != this.prefName)
      return;

    if (typeof this.callback == "function") {
      let prefValue = Preferences.get(this.prefName);

      if (this.thisObject)
        this.callback.call(this.thisObject, prefValue);
      else
        this.callback(prefValue);
    } else // typeof this.callback == "object" (nsIObserver)
      this.callback.observe(subject, topic, data);
  }
};

function isObject(val) {
  // We can't check for |val.constructor == Object| here, since the value
  // might be from a different context whose Object constructor is not the same
  // as ours, so instead we match based on the name of the constructor.
  return (typeof val != "undefined" && val != null && typeof val == "object" &&
          val.constructor.name == "Object");
}
PK
!<jODODmodules/Prefetcher.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["Prefetcher"];

const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// Rules are defined at the bottom of this file.
var PrefetcherRules = {};

/*
 * When events that trigger in the content process are forwarded to
 * add-ons in the chrome process, we expect the add-ons to send a lot
 * of CPOWs to query content nodes while processing the events. To
 * speed this up, the prefetching system anticipates which properties
 * will be read and reads them ahead of time. The prefetched
 * properties are passed to the chrome process along with each
 * event. A typical scenario might work like this:
 *
 * 1. "load" event fires in content
 * 2. Content process prefetches:
 *      event.target.defaultView = <win 1>
 *      <win 1>.location = <location obj>
 *      event.target.getElementsByTagName("form") = [<elt 1>, <elt 2>]
 *      <elt 1>.id = "login-form"
 *      <elt 2>.id = "subscribe-form"
 * 3. Content process forwards "load" event to add-on along with
 *    prefetched data
 * 4. Add-on reads:
 *      event.target.defaultView (already prefetched)
 *      event.target.getElementsByTagName("form") (already prefetched)
 *      <elt 1>.id (already prefetched)
 *      <elt 1>.className (not prefetched; CPOW must be sent)
 *
 * The amount of data to prefetch is determined based on the add-on ID
 * and the event type. The specific data to select is determined using
 * a set of Datalog-like rules (http://en.wikipedia.org/wiki/Datalog).
 *
 * Rules operate on a series of "tables" like in a database. Each
 * table contains a set of content-process objects. When an event
 * handler runs, it seeds some initial tables with objects of
 * interest. For example, the Event table might start out containing
 * the event that fired.
 *
 * Objects are added to tables using a set of rules of the form "if X
 * is in table A, then add F(X) to table B", where F(X) is typically a
 * property access or a method call. The most common functions F are:
 *
 * PropertyOp(destTable, sourceTable, property):
 *   For each object X in sourceTable, add X.property to destTable.
 * MethodOp(destTable, sourceTable, method, args):
 *   For each object X in sourceTable, add X.method(args) to destTable.
 * CollectionOp(destTable, sourceTable):
 *   For each object X in sourceTable, add X[i] to destTable for
 *   all i from 0 to X.length - 1.
 *
 * To generate the prefetching in the example above, the following
 * rules would work:
 *
 * 1. PropertyOp("EventTarget", "Event", "target")
 * 2. PropertyOp("Window", "EventTarget", "defaultView")
 * 3. MethodOp("FormCollection", "EventTarget", "getElementsByTagName", "form")
 * 4. CollectionOp("Form", "FormCollection")
 * 5. PropertyOp(null, "Form", "id")
 *
 * Rules are written at the bottom of this file.
 *
 * When a rule runs, it will usually generate some cache entries that
 * will be passed to the chrome process. For example, when PropertyOp
 * prefetches obj.prop and gets the value X, it caches the value of
 * obj and X. When the chrome process receives this data, it creates a
 * two-level map [obj -> [prop -> X]]. When the add-on accesses a
 * property on obj, the add-on shim code consults this map to see if
 * the property has already been cached.
 */

const PREF_PREFETCHING_ENABLED = "extensions.interposition.prefetching";

function isPrimitive(v) {
  if (!v)
    return true;
  let type = typeof(v);
  return type !== "object" && type !== "function";
}

function objAddr(obj) {
/*
  if (!isPrimitive(obj)) {
    return String(obj) + "[" + Cu.getJSTestingFunctions().objectAddress(obj) + "]";
  }
  return String(obj);
*/
}

function log(/* ...args*/) {
/*
  for (let arg of args) {
    dump(arg);
    dump(" ");
  }
  dump("\n");
*/
}

function logPrefetch(/* kind, value1, component, value2*/) {
/*
  log("prefetching", kind, objAddr(value1) + "." + component, "=", objAddr(value2));
*/
}

/*
 * All the Op classes (representing Datalog rules) have the same interface:
 *   outputTable: Table that objects generated by the rule are added to.
 *     Note that this can be null.
 *   inputTable: Table that the rule draws objects from.
 *   addObject(database, obj): Called when an object is added to inputTable.
 *     This code should take care of adding objects to outputTable.
 *     Data to be cached should be stored by calling database.cache.
 *   makeCacheEntry(item, cache):
 *     Called by the chrome process to create the two-level map of
 *     prefetched objects. |item| holds the cached data
 *     generated by the content process. |cache| is the map to be
 *     generated.
 */

function PropertyOp(outputTable, inputTable, prop) {
  this.outputTable = outputTable;
  this.inputTable = inputTable;
  this.prop = prop;
}

PropertyOp.prototype.addObject = function(database, obj) {
  let has = false, propValue;
  try {
    if (this.prop in obj) {
      has = true;
      propValue = obj[this.prop];
    }
  } catch (e) {
    // Don't cache anything if an exception is thrown.
    return;
  }

  logPrefetch("prop", obj, this.prop, propValue);
  database.cache(this.index, obj, has, propValue);
  if (has && !isPrimitive(propValue) && this.outputTable) {
    database.add(this.outputTable, propValue);
  }
}

PropertyOp.prototype.makeCacheEntry = function(item, cache) {
  let [, obj, , propValue] = item;

  let desc = { configurable: false, enumerable: true, writable: false, value: propValue };

  if (!cache.has(obj)) {
    cache.set(obj, new Map());
  }
  let propMap = cache.get(obj);
  propMap.set(this.prop, desc);
}

function MethodOp(outputTable, inputTable, method, ...args) {
  this.outputTable = outputTable;
  this.inputTable = inputTable;
  this.method = method;
  this.args = args;
}

MethodOp.prototype.addObject = function(database, obj) {
  let result;
  try {
    result = obj[this.method].apply(obj, this.args);
  } catch (e) {
    // Don't cache anything if an exception is thrown.
    return;
  }

  logPrefetch("method", obj, this.method + "(" + this.args + ")", result);
  database.cache(this.index, obj, result);
  if (!isPrimitive(result) && this.outputTable) {
    database.add(this.outputTable, result);
  }
}

MethodOp.prototype.makeCacheEntry = function(item, cache) {
  let [, obj, result] = item;

  if (!cache.has(obj)) {
    cache.set(obj, new Map());
  }
  let propMap = cache.get(obj);
  let fallback = propMap.get(this.method);

  let method = this.method;
  let selfArgs = this.args;
  let methodImpl = function(...args) {
    if (args.length == selfArgs.length && args.every((v, i) => v === selfArgs[i])) {
      return result;
    }

    if (fallback) {
      return fallback.value(...args);
    }
    return obj[method](...args);
  };

  let desc = { configurable: false, enumerable: true, writable: false, value: methodImpl };
  propMap.set(this.method, desc);
}

function CollectionOp(outputTable, inputTable) {
  this.outputTable = outputTable;
  this.inputTable = inputTable;
}

CollectionOp.prototype.addObject = function(database, obj) {
  let elements = [];
  try {
    let len = obj.length;
    for (let i = 0; i < len; i++) {
      logPrefetch("index", obj, i, obj[i]);
      elements.push(obj[i]);
    }
  } catch (e) {
    // Don't cache anything if an exception is thrown.
    return;
  }

  database.cache(this.index, obj, ...elements);
  for (let i = 0; i < elements.length; i++) {
    if (!isPrimitive(elements[i]) && this.outputTable) {
      database.add(this.outputTable, elements[i]);
    }
  }
}

CollectionOp.prototype.makeCacheEntry = function(item, cache) {
  let [, obj, ...elements] = item;

  if (!cache.has(obj)) {
    cache.set(obj, new Map());
  }
  let propMap = cache.get(obj);

  let lenDesc = { configurable: false, enumerable: true, writable: false, value: elements.length };
  propMap.set("length", lenDesc);

  for (let i = 0; i < elements.length; i++) {
    let desc = { configurable: false, enumerable: true, writable: false, value: elements[i] };
    propMap.set(i, desc);
  }
}

function CopyOp(outputTable, inputTable) {
  this.outputTable = outputTable;
  this.inputTable = inputTable;
}

CopyOp.prototype.addObject = function(database, obj) {
  database.add(this.outputTable, obj);
}

function Database(trigger, addons) {
  // Create a map of rules that apply to this specific trigger and set
  // of add-ons. The rules are indexed based on their inputTable.
  this.rules = new Map();
  for (let addon of addons) {
    let addonRules = PrefetcherRules[addon] || {};
    let triggerRules = addonRules[trigger] || [];
    for (let rule of triggerRules) {
      let inTable = rule.inputTable;
      if (!this.rules.has(inTable)) {
        this.rules.set(inTable, new Set());
      }
      let set = this.rules.get(inTable);
      set.add(rule);
    }
  }

  // this.tables maps table names to sets of objects contained in them.
  this.tables = new Map();

  // todo is a worklist of items added to tables that have not had
  // rules run on them yet.
  this.todo = [];

  // Cached data to be sent to the chrome process.
  this.cached = [];
}

Database.prototype = {
  // Add an object to a table.
  add(table, obj) {
    if (!this.tables.has(table)) {
      this.tables.set(table, new Set());
    }
    let tableSet = this.tables.get(table);
    if (tableSet.has(obj)) {
      return;
    }
    tableSet.add(obj);

    this.todo.push([table, obj]);
  },

  cache(...args) {
    this.cached.push(args);
  },

  // Run a fixed-point iteration that adds objects to table based on
  // this.rules until there are no more objects to add.
  process() {
    while (this.todo.length) {
      let [table, obj] = this.todo.pop();
      let rules = this.rules.get(table);
      if (!rules) {
        continue;
      }
      for (let rule of rules) {
        rule.addObject(this, obj);
      }
    }
  },
};

var Prefetcher = {
  init() {
    // Give an index to each rule and store it in this.ruleMap based
    // on the index. The index is used to serialize and deserialize
    // data from content to chrome.
    let counter = 0;
    this.ruleMap = new Map();
    for (let addon in PrefetcherRules) {
      for (let trigger in PrefetcherRules[addon]) {
        for (let rule of PrefetcherRules[addon][trigger]) {
          rule.index = counter++;
          this.ruleMap.set(rule.index, rule);
        }
      }
    }

    this.prefetchingEnabled = Services.prefs.getBoolPref(PREF_PREFETCHING_ENABLED, false);
    Services.prefs.addObserver(PREF_PREFETCHING_ENABLED, this);
    Services.obs.addObserver(this, "xpcom-shutdown");
  },

  observe(subject, topic, data) {
    if (topic == "xpcom-shutdown") {
      Services.prefs.removeObserver(PREF_PREFETCHING_ENABLED, this);
      Services.obs.removeObserver(this, "xpcom-shutdown");
    } else if (topic == PREF_PREFETCHING_ENABLED) {
      this.prefetchingEnabled = Services.prefs.getBoolPref(PREF_PREFETCHING_ENABLED, false);
    }
  },

  // Called when an event occurs in the content process. The event is
  // described by the trigger string. |addons| is a list of addons
  // that have listeners installed for the event. |args| is
  // event-specific data (such as the event object).
  prefetch(trigger, addons, args) {
    if (!this.prefetchingEnabled) {
      return [[], []];
    }

    let db = new Database(trigger, addons);
    for (let table in args) {
      log("root", table, "=", objAddr(args[table]));
      db.add(table, args[table]);
    }

    // Prefetch objects and add them to tables.
    db.process();

    // Data passed to sendAsyncMessage must be split into a JSON
    // portion and a CPOW portion. This code splits apart db.cached
    // into these two pieces. Any object in db.cache is added to an
    // array of CPOWs and replaced with {cpow: <index in array>}.
    let cpowIndexes = new Map();
    let prefetched = [];
    let cpows = [];
    for (let item of db.cached) {
      item = item.map((elt) => {
        if (!isPrimitive(elt)) {
          if (!cpowIndexes.has(elt)) {
            let index = cpows.length;
            cpows.push(elt);
            cpowIndexes.set(elt, index);
          }
          return {cpow: cpowIndexes.get(elt)};
        }
        return elt;
      });

      prefetched.push(item);
    }

    return [prefetched, cpows];
  },

  cache: null,

  // Generate a two-level mapping based on cached data received from
  // the content process.
  generateCache(prefetched, cpows) {
    let cache = new Map();
    for (let item of prefetched) {
      // Replace anything of the form {cpow: <index>} with the actual
      // object in |cpows|.
      item = item.map((elt) => {
        if (!isPrimitive(elt)) {
          return cpows[elt.cpow];
        }
        return elt;
      });

      let index = item[0];
      let op = this.ruleMap.get(index);
      op.makeCacheEntry(item, cache);
    }
    return cache;
  },

  // Run |func|, using the prefetched data in |prefetched| and |cpows|
  // as a cache.
  withPrefetching(prefetched, cpows, func) {
    if (!this.prefetchingEnabled) {
      return func();
    }

    this.cache = this.generateCache(prefetched, cpows);

    try {
      log("Prefetching on");
      return func();
    } finally {
      // After we return from this event handler, the content process
      // is free to continue executing, so we invalidate our cache.
      log("Prefetching off");
      this.cache = null;
    }
  },

  // Called by shim code in the chrome process to check if target.prop
  // is cached.
  lookupInCache(addon, target, prop) {
    if (!this.cache || !Cu.isCrossProcessWrapper(target)) {
      return null;
    }

    let propMap = this.cache.get(target);
    if (!propMap) {
      return null;
    }

    return propMap.get(prop);
  },
};

var AdblockId = "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}";
var AdblockRules = {
  "ContentPolicy.shouldLoad": [
    new MethodOp("Node", "InitNode", "QueryInterface", Ci.nsISupports),
    new PropertyOp("Document", "Node", "ownerDocument"),
    new PropertyOp("Window", "Node", "defaultView"),
    new PropertyOp("Window", "Document", "defaultView"),
    new PropertyOp("TopWindow", "Window", "top"),
    new PropertyOp("WindowLocation", "Window", "location"),
    new PropertyOp(null, "WindowLocation", "href"),
    new PropertyOp("Window", "Window", "parent"),
    new PropertyOp(null, "Window", "name"),
    new PropertyOp("Document", "Window", "document"),
    new PropertyOp("TopDocumentElement", "Document", "documentElement"),
    new MethodOp(null, "TopDocumentElement", "getAttribute", "data-adblockkey"),
  ]
};
PrefetcherRules[AdblockId] = AdblockRules;

var LastpassId = "support@lastpass.com";
var LastpassRules = {
  "EventTarget.handleEvent": [
    new PropertyOp("EventTarget", "Event", "target"),
    new PropertyOp("EventOriginalTarget", "Event", "originalTarget"),
    new PropertyOp("Window", "EventOriginalTarget", "defaultView"),

    new CopyOp("Frame", "Window"),
    new PropertyOp("FrameCollection", "Window", "frames"),
    new CollectionOp("Frame", "FrameCollection"),
    new PropertyOp("FrameCollection", "Frame", "frames"),
    new PropertyOp("FrameDocument", "Frame", "document"),
    new PropertyOp(null, "Frame", "window"),
    new PropertyOp(null, "FrameDocument", "defaultView"),

    new PropertyOp("FrameDocumentLocation", "FrameDocument", "location"),
    new PropertyOp(null, "FrameDocumentLocation", "href"),
    new PropertyOp("FrameLocation", "Frame", "location"),
    new PropertyOp(null, "FrameLocation", "href"),

    new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "form"),
    new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "FORM"),
    new CollectionOp("Form", "FormCollection"),
    new PropertyOp("FormElementCollection", "Form", "elements"),
    new CollectionOp("FormElement", "FormElementCollection"),
    new PropertyOp("Style", "Form", "style"),

    new PropertyOp(null, "FormElement", "type"),
    new PropertyOp(null, "FormElement", "name"),
    new PropertyOp(null, "FormElement", "value"),
    new PropertyOp(null, "FormElement", "tagName"),
    new PropertyOp(null, "FormElement", "id"),
    new PropertyOp("Style", "FormElement", "style"),

    new PropertyOp(null, "Style", "visibility"),

    new MethodOp("MetaElementsCollection", "EventOriginalTarget", "getElementsByTagName", "meta"),
    new CollectionOp("MetaElement", "MetaElementsCollection"),
    new PropertyOp(null, "MetaElement", "httpEquiv"),

    new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "input"),
    new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "INPUT"),
    new CollectionOp("InputElement", "InputElementCollection"),
    new PropertyOp(null, "InputElement", "type"),
    new PropertyOp(null, "InputElement", "name"),
    new PropertyOp(null, "InputElement", "tagName"),
    new PropertyOp(null, "InputElement", "form"),

    new PropertyOp("BodyElement", "FrameDocument", "body"),
    new PropertyOp("BodyInnerText", "BodyElement", "innerText"),

    new PropertyOp("DocumentFormCollection", "FrameDocument", "forms"),
    new CollectionOp("DocumentForm", "DocumentFormCollection"),
  ]
};
PrefetcherRules[LastpassId] = LastpassRules;
PK
!<$N modules/PrivateBrowsingUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["PrivateBrowsingUtils"];

Components.utils.import("resource://gre/modules/Services.jsm");

const kAutoStartPref = "browser.privatebrowsing.autostart";

// This will be set to true when the PB mode is autostarted from the command
// line for the current session.
var gTemporaryAutoStartMode = false;

const Cc = Components.classes;
const Ci = Components.interfaces;

this.PrivateBrowsingUtils = {
  // Rather than passing content windows to this function, please use
  // isBrowserPrivate since it works with e10s.
  isWindowPrivate: function pbu_isWindowPrivate(aWindow) {
    if (!(aWindow instanceof Components.interfaces.nsIDOMChromeWindow)) {
      dump("WARNING: content window passed to PrivateBrowsingUtils.isWindowPrivate. " +
           "Use isContentWindowPrivate instead (but only for frame scripts).\n"
           + new Error().stack);
    }

    return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
  },

  // This should be used only in frame scripts.
  isContentWindowPrivate: function pbu_isWindowPrivate(aWindow) {
    return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
  },

  isBrowserPrivate(aBrowser) {
    let chromeWin = aBrowser.ownerGlobal;
    if (chromeWin.gMultiProcessBrowser || !aBrowser.isConnected) {
      // In e10s we have to look at the chrome window's private
      // browsing status since the only alternative is to check the
      // content window, which is in another process.  If the browser
      // is lazy then the content window doesn't exist.
      return this.isWindowPrivate(chromeWin);
    }
    return this.privacyContextFromWindow(aBrowser.contentWindow).usePrivateBrowsing;
  },

  privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) {
    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIWebNavigation)
                  .QueryInterface(Ci.nsILoadContext);
  },

  addToTrackingAllowlist(aURI) {
    let pbmtpWhitelist = Cc["@mozilla.org/pbm-tp-whitelist;1"]
                           .getService(Ci.nsIPrivateBrowsingTrackingProtectionWhitelist);
    pbmtpWhitelist.addToAllowList(aURI);
  },

  removeFromTrackingAllowlist(aURI) {
    let pbmtpWhitelist = Cc["@mozilla.org/pbm-tp-whitelist;1"]
                           .getService(Ci.nsIPrivateBrowsingTrackingProtectionWhitelist);
    pbmtpWhitelist.removeFromAllowList(aURI);
  },

  get permanentPrivateBrowsing() {
    try {
      return gTemporaryAutoStartMode ||
             Services.prefs.getBoolPref(kAutoStartPref);
    } catch (e) {
      // The pref does not exist
      return false;
    }
  },

  // These should only be used from internal code
  enterTemporaryAutoStartMode: function pbu_enterTemporaryAutoStartMode() {
    gTemporaryAutoStartMode = true;
  },
  get isInTemporaryAutoStartMode() {
    return gTemporaryAutoStartMode;
  },
};

PK
!<r9Jmodules/ProfileAge.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ProfileAge"];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/osfile.jsm")
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");

/**
 * Profile access to times.json (eg, creation/reset time).
 * This is separate from the provider to simplify testing and enable extraction
 * to a shared location in the future.
 */
this.ProfileAge = function(profile, log) {
  this.profilePath = profile || OS.Constants.Path.profileDir;
  if (!this.profilePath) {
    throw new Error("No profile directory.");
  }
  if (!log) {
    log = Log.repository.getLogger("Toolkit.ProfileAge");
  }
  this._log = log;
}
this.ProfileAge.prototype = {
  /**
   * There are three ways we can get our creation time:
   *
   * 1. From our own saved value (to avoid redundant work).
   * 2. From the on-disk JSON file.
   * 3. By calculating it from the filesystem.
   *
   * If we have to calculate, we write out the file; if we have
   * to touch the file, we persist in-memory.
   *
   * @return a promise that resolves to the profile's creation time.
   */
  get created() {
    function onSuccess(times) {
      if (times.created) {
        return times.created;
      }
      return onFailure.call(this, null, times);
    }

    function onFailure(err, times) {
      return this.computeAndPersistCreated(times)
                 .then(function onSuccess(created) {
                         return created;
                       });
    }

    return this.getTimes()
               .then(onSuccess.bind(this),
                     onFailure.bind(this));
  },

  /**
   * Explicitly make `file`, a filename, a full path
   * relative to our profile path.
   */
  getPath(file) {
    return OS.Path.join(this.profilePath, file);
  },

  /**
   * Return a promise which resolves to the JSON contents
   * of the time file, using the already read value if possible.
   */
  getTimes(file = "times.json") {
    if (this._times) {
      return Promise.resolve(this._times);
    }
    return this.readTimes(file).then(
      times => {
        return this.times = times || {};
      }
    );
  },

  /**
   * Return a promise which resolves to the JSON contents
   * of the time file in this accessor's profile.
   */
  readTimes(file = "times.json") {
    return CommonUtils.readJSON(this.getPath(file));
  },

  /**
   * Return a promise representing the writing of `contents`
   * to `file` in the specified profile.
   */
  writeTimes(contents, file = "times.json") {
    return CommonUtils.writeJSON(contents, this.getPath(file));
  },

  /**
   * Merge existing contents with a 'created' field, writing them
   * to the specified file. Promise, naturally.
   */
  computeAndPersistCreated(existingContents, file = "times.json") {
    let path = this.getPath(file);
    function onOldest(oldest) {
      let contents = existingContents || {};
      contents.created = oldest;
      this._times = contents;
      return this.writeTimes(contents, path)
                 .then(function onSuccess() {
                   return oldest;
                 });
    }

    return this.getOldestProfileTimestamp()
               .then(onOldest.bind(this));
  },

  /**
   * Traverse the contents of the profile directory, finding the oldest file
   * and returning its creation timestamp.
   */
  getOldestProfileTimestamp() {
    let self = this;
    let oldest = Date.now() + 1000;
    let iterator = new OS.File.DirectoryIterator(this.profilePath);
    self._log.debug("Iterating over profile " + this.profilePath);
    if (!iterator) {
      throw new Error("Unable to fetch oldest profile entry: no profile iterator.");
    }

    function onEntry(entry) {
      function onStatSuccess(info) {
        // OS.File doesn't seem to be behaving. See Bug 827148.
        // Let's do the best we can. This whole function is defensive.
        let date = info.winBirthDate || info.macBirthDate;
        if (!date || !date.getTime()) {
          // OS.File will only return file creation times of any kind on Mac
          // and Windows, where birthTime is defined.
          // That means we're unable to function on Linux, so we use mtime
          // instead.
          self._log.debug("No birth date. Using mtime.");
          date = info.lastModificationDate;
        }

        if (date) {
          let timestamp = date.getTime();
          self._log.debug("Using date: " + entry.path + " = " + date);
          if (timestamp < oldest) {
            oldest = timestamp;
          }
        }
      }

      function onStatFailure(e) {
        // Never mind.
        self._log.debug("Stat failure", e);
      }

      return OS.File.stat(entry.path)
                    .then(onStatSuccess, onStatFailure);
    }

    let promise = iterator.forEach(onEntry);

    function onSuccess() {
      iterator.close();
      return oldest;
    }

    function onFailure(reason) {
      iterator.close();
      throw new Error("Unable to fetch oldest profile entry: " + reason);
    }

    return promise.then(onSuccess, onFailure);
  },

  /**
   * Record (and persist) when a profile reset happened.  We just store a
   * single value - the timestamp of the most recent reset - but there is scope
   * to keep a list of reset times should our health-reporter successor
   * be able to make use of that.
   * Returns a promise that is resolved once the file has been written.
   */
  recordProfileReset(time = Date.now(), file = "times.json") {
    return this.getTimes(file).then(
      times => {
        times.reset = time;
        return this.writeTimes(times, file);
      }
    );
  },

  /* Returns a promise that resolves to the time the profile was reset,
   * or undefined if not recorded.
   */
  get reset() {
    return this.getTimes().then(
      times => times.reset
    );
  },
}
PK
!<ŅLLmodules/Promise-backend.js/* -*- 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 implementation file is imported by the Promise.jsm module, and as a
 * special case by the debugger server.  To support chrome debugging, the
 * debugger server needs to have all its code in one global, so it must use
 * loadSubScript directly.
 *
 * In the general case, this script should be used by importing Promise.jsm:
 *
 * Components.utils.import("resource://gre/modules/Promise.jsm");
 *
 * More documentation can be found in the Promise.jsm module.
 */

// Globals

// Obtain an instance of Cu. How this instance is obtained depends on how this
// file is loaded.
//
// This file can be loaded in three different ways:
// 1. As a CommonJS module, by Loader.jsm, on the main thread.
// 2. As a CommonJS module, by worker-loader.js, on a worker thread.
// 3. As a subscript, by Promise.jsm, on the main thread.
//
// If require is defined, the file is loaded as a CommonJS module. Components
// will not be defined in that case, but we can obtain an instance of Cu from
// the chrome module. Otherwise, this file is loaded as a subscript, and we can
// obtain an instance of Cu from Components directly.
//
// If the file is loaded as a CommonJS module on a worker thread, the instance
// of Cu obtained from the chrome module will be null. The reason for this is
// that Components is not defined in worker threads, so no instance of Cu can
// be obtained.

// As this can be loaded in several ways, allow require and module to be defined.
/* global module:false require:false */
// This is allowed in workers.
/* global setImmediate:false */

var Cu = this.require ? require("chrome").Cu : Components.utils;
var Cc = this.require ? require("chrome").Cc : Components.classes;
var Ci = this.require ? require("chrome").Ci : Components.interfaces;
// If we can access Components, then we use it to capture an async
// parent stack trace; see scheduleWalkerLoop.  However, as it might
// not be available (see above), users of this must check it first.
var Components_ = this.require ? require("chrome").components : Components;

// If Cu is defined, use it to lazily define the FinalizationWitnessService.
if (Cu) {
  Cu.import("resource://gre/modules/Services.jsm");
  Cu.import("resource://gre/modules/XPCOMUtils.jsm");

  XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService",
                                     "@mozilla.org/toolkit/finalizationwitness;1",
                                     "nsIFinalizationWitnessService");

  // For now, we're worried about add-ons using Promises with CPOWs, so we'll
  // permit them in this scope, but this support will go away soon.
  Cu.permitCPOWsInScope(this);
}

const STATUS_PENDING = 0;
const STATUS_RESOLVED = 1;
const STATUS_REJECTED = 2;

// This N_INTERNALS name allow internal properties of the Promise to be
// accessed only by this module, while still being visible on the object
// manually when using a debugger.  This doesn't strictly guarantee that the
// properties are inaccessible by other code, but provide enough protection to
// avoid using them by mistake.
const salt = Math.floor(Math.random() * 100);
const N_INTERNALS = "{private:internals:" + salt + "}";

// We use DOM Promise for scheduling the walker loop.
const DOMPromise = Cu ? Promise : null;

// Warn-upon-finalization mechanism
//
// One of the difficult problems with promises is locating uncaught
// rejections. We adopt the following strategy: if a promise is rejected
// at the time of its garbage-collection *and* if the promise is at the
// end of a promise chain (i.e. |thatPromise.then| has never been
// called), then we print a warning.
//
//  let deferred = Promise.defer();
//  let p = deferred.promise.then();
//  deferred.reject(new Error("I am un uncaught error"));
//  deferred = null;
//  p = null;
//
// In this snippet, since |deferred.promise| is not the last in the
// chain, no error will be reported for that promise. However, since
// |p| is the last promise in the chain, the error will be reported
// for |p|.
//
// Note that this may, in some cases, cause an error to be reported more
// than once. For instance, consider:
//
//   let deferred = Promise.defer();
//   let p1 = deferred.promise.then();
//   let p2 = deferred.promise.then();
//   deferred.reject(new Error("I am an uncaught error"));
//   p1 = p2 = deferred = null;
//
// In this snippet, the error is reported both by p1 and by p2.
//

var PendingErrors = {
  // An internal counter, used to generate unique id.
  _counter: 0,
  // Functions registered to be notified when a pending error
  // is reported as uncaught.
  _observers: new Set(),
  _map: new Map(),

  /**
   * Initialize PendingErrors
   */
  init() {
    Services.obs.addObserver(function observe(aSubject, aTopic, aValue) {
      PendingErrors.report(aValue);
    }, "promise-finalization-witness");
  },

  /**
   * Register an error as tracked.
   *
   * @return The unique identifier of the error.
   */
  register(error) {
    let id = "pending-error-" + (this._counter++);
    //
    // At this stage, ideally, we would like to store the error itself
    // and delay any treatment until we are certain that we will need
    // to report that error. However, in the (unlikely but possible)
    // case the error holds a reference to the promise itself, doing so
    // would prevent the promise from being garbabe-collected, which
    // would both cause a memory leak and ensure that we cannot report
    // the uncaught error.
    //
    // To avoid this situation, we rather extract relevant data from
    // the error and further normalize it to strings.
    //
    let value = {
      date: new Date(),
      message: "" + error,
      fileName: null,
      stack: null,
      lineNumber: null
    };
    try { // Defend against non-enumerable values
      if (error && error instanceof Ci.nsIException) {
        // nsIException does things a little differently.
        try {
          // For starters |.toString()| does not only contain the message, but
          // also the top stack frame, and we don't really want that.
          value.message = error.message;
        } catch (ex) {
          // Ignore field
        }
        try {
          // All lowercase filename. ;)
          value.fileName = error.filename;
        } catch (ex) {
          // Ignore field
        }
        try {
          value.lineNumber = error.lineNumber;
        } catch (ex) {
          // Ignore field
        }
      } else if (typeof error == "object" && error) {
        for (let k of ["fileName", "stack", "lineNumber"]) {
          try { // Defend against fallible getters and string conversions
            let v = error[k];
            value[k] = v ? ("" + v) : null;
          } catch (ex) {
            // Ignore field
          }
        }
      }

      if (!value.stack) {
        // |error| is not an Error (or Error-alike). Try to figure out the stack.
        let stack = null;
        if (error && error.location &&
            error.location instanceof Ci.nsIStackFrame) {
          // nsIException has full stack frames in the |.location| member.
          stack = error.location;
        } else {
          // Components.stack to the rescue!
          stack = Components_.stack;
          // Remove those top frames that refer to Promise.jsm.
          while (stack) {
            if (!stack.filename.endsWith("/Promise.jsm")) {
              break;
            }
            stack = stack.caller;
          }
        }
        if (stack) {
          let frames = [];
          while (stack) {
            frames.push(stack);
            stack = stack.caller;
          }
          value.stack = frames.join("\n");
        }
      }
    } catch (ex) {
      // Ignore value
    }
    this._map.set(id, value);
    return id;
  },

  /**
   * Notify all observers that a pending error is now uncaught.
   *
   * @param id The identifier of the pending error, as returned by
   * |register|.
   */
  report(id) {
    let value = this._map.get(id);
    if (!value) {
      return; // The error has already been reported
    }
    this._map.delete(id);
    for (let obs of this._observers.values()) {
      obs(value);
    }
  },

  /**
   * Mark all pending errors are uncaught, notify the observers.
   */
  flush() {
    // Since we are going to modify the map while walking it,
    // let's copying the keys first.
    for (let key of Array.from(this._map.keys())) {
      this.report(key);
    }
  },

  /**
   * Stop tracking an error, as this error has been caught,
   * eventually.
   */
  unregister(id) {
    this._map.delete(id);
  },

  /**
   * Add an observer notified when an error is reported as uncaught.
   *
   * @param {function} observer A function notified when an error is
   * reported as uncaught. Its arguments are
   *   {message, date, fileName, stack, lineNumber}
   * All arguments are optional.
   */
  addObserver(observer) {
    this._observers.add(observer);
  },

  /**
   * Remove an observer added with addObserver
   */
  removeObserver(observer) {
    this._observers.delete(observer);
  },

  /**
   * Remove all the observers added with addObserver
   */
  removeAllObservers() {
    this._observers.clear();
  }
};

// Initialize the warn-upon-finalization mechanism if and only if Cu is defined.
// Otherwise, FinalizationWitnessService won't be defined (see above).
if (Cu) {
  PendingErrors.init();
}

// Default mechanism for displaying errors
PendingErrors.addObserver(function(details) {
  const generalDescription = "A promise chain failed to handle a rejection." +
    " Did you forget to '.catch', or did you forget to 'return'?\nSee" +
    " https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n\n";

  let error = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
  if (!error || !Services.console) {
    // Too late during shutdown to use the nsIConsole
    dump("*************************\n");
    dump(generalDescription);
    dump("On: " + details.date + "\n");
    dump("Full message: " + details.message + "\n");
    dump("Full stack: " + (details.stack || "not available") + "\n");
    dump("*************************\n");
    return;
  }
  let message = details.message;
  if (details.stack) {
    message += "\nFull Stack: " + details.stack;
  }
  error.init(
             /* message*/ generalDescription +
             "Date: " + details.date + "\nFull Message: " + message,
             /* sourceName*/ details.fileName,
             /* sourceLine*/ details.lineNumber ? ("" + details.lineNumber) : 0,
             /* lineNumber*/ details.lineNumber || 0,
             /* columnNumber*/ 0,
             /* flags*/ Ci.nsIScriptError.errorFlag,
             /* category*/ "chrome javascript");
  Services.console.logMessage(error);
});


// Additional warnings for developers
//
// The following error types are considered programmer errors, which should be
// reported (possibly redundantly) so as to let programmers fix their code.
const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeError"];

// Promise

/**
 * The Promise constructor. Creates a new promise given an executor callback.
 * The executor callback is called with the resolve and reject handlers.
 *
 * @param aExecutor
 *        The callback that will be called with resolve and reject.
 */
this.Promise = function Promise(aExecutor) {
  if (typeof(aExecutor) != "function") {
    throw new TypeError("Promise constructor must be called with an executor.");
  }

  /*
   * Object holding all of our internal values we associate with the promise.
   */
  Object.defineProperty(this, N_INTERNALS, { value: {
    /*
     * Internal status of the promise.  This can be equal to STATUS_PENDING,
     * STATUS_RESOLVED, or STATUS_REJECTED.
     */
    status: STATUS_PENDING,

    /*
     * When the status property is STATUS_RESOLVED, this contains the final
     * resolution value, that cannot be a promise, because resolving with a
     * promise will cause its state to be eventually propagated instead.  When the
     * status property is STATUS_REJECTED, this contains the final rejection
     * reason, that could be a promise, even if this is uncommon.
     */
    value: undefined,

    /*
     * Array of Handler objects registered by the "then" method, and not processed
     * yet.  Handlers are removed when the promise is resolved or rejected.
     */
    handlers: [],

    /**
     * When the status property is STATUS_REJECTED and until there is
     * a rejection callback, this contains an array
     * - {string} id An id for use with |PendingErrors|;
     * - {FinalizationWitness} witness A witness broadcasting |id| on
     *   notification "promise-finalization-witness".
     */
    witness: undefined
  }});

  Object.seal(this);

  let resolve = PromiseWalker.completePromise
                             .bind(PromiseWalker, this, STATUS_RESOLVED);
  let reject = PromiseWalker.completePromise
                            .bind(PromiseWalker, this, STATUS_REJECTED);

  try {
    aExecutor(resolve, reject);
  } catch (ex) {
    reject(ex);
  }
}

/**
 * Calls one of the provided functions as soon as this promise is either
 * resolved or rejected.  A new promise is returned, whose state evolves
 * depending on this promise and the provided callback functions.
 *
 * The appropriate callback is always invoked after this method returns, even
 * if this promise is already resolved or rejected.  You can also call the
 * "then" method multiple times on the same promise, and the callbacks will be
 * invoked in the same order as they were registered.
 *
 * @param aOnResolve
 *        If the promise is resolved, this function is invoked with the
 *        resolution value of the promise as its only argument, and the
 *        outcome of the function determines the state of the new promise
 *        returned by the "then" method.  In case this parameter is not a
 *        function (usually "null"), the new promise returned by the "then"
 *        method is resolved with the same value as the original promise.
 *
 * @param aOnReject
 *        If the promise is rejected, this function is invoked with the
 *        rejection reason of the promise as its only argument, and the
 *        outcome of the function determines the state of the new promise
 *        returned by the "then" method.  In case this parameter is not a
 *        function (usually left "undefined"), the new promise returned by the
 *        "then" method is rejected with the same reason as the original
 *        promise.
 *
 * @return A new promise that is initially pending, then assumes a state that
 *         depends on the outcome of the invoked callback function:
 *          - If the callback returns a value that is not a promise, including
 *            "undefined", the new promise is resolved with this resolution
 *            value, even if the original promise was rejected.
 *          - If the callback throws an exception, the new promise is rejected
 *            with the exception as the rejection reason, even if the original
 *            promise was resolved.
 *          - If the callback returns a promise, the new promise will
 *            eventually assume the same state as the returned promise.
 *
 * @note If the aOnResolve callback throws an exception, the aOnReject
 *       callback is not invoked.  You can register a rejection callback on
 *       the returned promise instead, to process any exception occurred in
 *       either of the callbacks registered on this promise.
 */
Promise.prototype.then = function(aOnResolve, aOnReject) {
  let handler = new Handler(this, aOnResolve, aOnReject);
  this[N_INTERNALS].handlers.push(handler);

  // Ensure the handler is scheduled for processing if this promise is already
  // resolved or rejected.
  if (this[N_INTERNALS].status != STATUS_PENDING) {

    // This promise is not the last in the chain anymore. Remove any watchdog.
    if (this[N_INTERNALS].witness != null) {
      let [id, witness] = this[N_INTERNALS].witness;
      this[N_INTERNALS].witness = null;
      witness.forget();
      PendingErrors.unregister(id);
    }

    PromiseWalker.schedulePromise(this);
  }

  return handler.nextPromise;
};

/**
 * Invokes `promise.then` with undefined for the resolve handler and a given
 * reject handler.
 *
 * @param aOnReject
 *        The rejection handler.
 *
 * @return A new pending promise returned.
 *
 * @see Promise.prototype.then
 */
Promise.prototype.catch = function(aOnReject) {
  return this.then(undefined, aOnReject);
};

/**
 * Creates a new pending promise and provides methods to resolve or reject it.
 *
 * @return A new object, containing the new promise in the "promise" property,
 *         and the methods to change its state in the "resolve" and "reject"
 *         properties.  See the Deferred documentation for details.
 */
Promise.defer = function() {
  return new Deferred();
};

/**
 * Creates a new promise resolved with the specified value, or propagates the
 * state of an existing promise.
 *
 * @param aValue
 *        If this value is not a promise, including "undefined", it becomes
 *        the resolution value of the returned promise.  If this value is a
 *        promise, then the returned promise will eventually assume the same
 *        state as the provided promise.
 *
 * @return A promise that can be pending, resolved, or rejected.
 */
Promise.resolve = function(aValue) {
  if (aValue && typeof(aValue) == "function" && aValue.isAsyncFunction) {
    throw new TypeError(
      "Cannot resolve a promise with an async function. " +
      "You should either invoke the async function first " +
      "or use 'Task.spawn' instead of 'Task.async' to start " +
      "the Task and return its promise.");
  }

  if (aValue instanceof Promise) {
    return aValue;
  }

  return new Promise((aResolve) => aResolve(aValue));
};

/**
 * Creates a new promise rejected with the specified reason.
 *
 * @param aReason
 *        The rejection reason for the returned promise.  Although the reason
 *        can be "undefined", it is generally an Error object, like in
 *        exception handling.
 *
 * @return A rejected promise.
 *
 * @note The aReason argument should not be a promise.  Using a rejected
 *       promise for the value of aReason would make the rejection reason
 *       equal to the rejected promise itself, and not its rejection reason.
 */
Promise.reject = function(aReason) {
  return new Promise((_, aReject) => aReject(aReason));
};

/**
 * Returns a promise that is resolved or rejected when all values are
 * resolved or any is rejected.
 *
 * @param aValues
 *        Iterable of promises that may be pending, resolved, or rejected. When
 *        all are resolved or any is rejected, the returned promise will be
 *        resolved or rejected as well.
 *
 * @return A new promise that is fulfilled when all values are resolved or
 *         that is rejected when any of the values are rejected. Its
 *         resolution value will be an array of all resolved values in the
 *         given order, or undefined if aValues is an empty array. The reject
 *         reason will be forwarded from the first promise in the list of
 *         given promises to be rejected.
 */
Promise.all = function(aValues) {
  if (aValues == null || typeof(aValues[Symbol.iterator]) != "function") {
    throw new Error("Promise.all() expects an iterable.");
  }

  return new Promise((resolve, reject) => {
    let values = Array.isArray(aValues) ? aValues : [...aValues];
    let countdown = values.length;
    let resolutionValues = new Array(countdown);

    if (!countdown) {
      resolve(resolutionValues);
      return;
    }

    function checkForCompletion(aValue, aIndex) {
      resolutionValues[aIndex] = aValue;
      if (--countdown === 0) {
        resolve(resolutionValues);
      }
    }

    for (let i = 0; i < values.length; i++) {
      let index = i;
      let value = values[i];
      let resolver = val => checkForCompletion(val, index);

      if (value && typeof(value.then) == "function") {
        value.then(resolver, reject);
      } else {
        // Given value is not a promise, forward it as a resolution value.
        resolver(value);
      }
    }
  });
};

/**
 * Returns a promise that is resolved or rejected when the first value is
 * resolved or rejected, taking on the value or reason of that promise.
 *
 * @param aValues
 *        Iterable of values or promises that may be pending, resolved, or
 *        rejected. When any is resolved or rejected, the returned promise will
 *        be resolved or rejected as to the given value or reason.
 *
 * @return A new promise that is fulfilled when any values are resolved or
 *         rejected. Its resolution value will be forwarded from the resolution
 *         value or rejection reason.
 */
Promise.race = function(aValues) {
  if (aValues == null || typeof(aValues[Symbol.iterator]) != "function") {
    throw new Error("Promise.race() expects an iterable.");
  }

  return new Promise((resolve, reject) => {
    for (let value of aValues) {
      Promise.resolve(value).then(resolve, reject);
    }
  });
};

Promise.Debugging = {
  /**
   * Add an observer notified when an error is reported as uncaught.
   *
   * @param {function} observer A function notified when an error is
   * reported as uncaught. Its arguments are
   *   {message, date, fileName, stack, lineNumber}
   * All arguments are optional.
   */
  addUncaughtErrorObserver(observer) {
    PendingErrors.addObserver(observer);
  },

  /**
   * Remove an observer added with addUncaughtErrorObserver
   *
   * @param {function} An observer registered with
   * addUncaughtErrorObserver.
   */
  removeUncaughtErrorObserver(observer) {
    PendingErrors.removeObserver(observer);
  },

  /**
   * Remove all the observers added with addUncaughtErrorObserver
   */
  clearUncaughtErrorObservers() {
    PendingErrors.removeAllObservers();
  },

  /**
   * Force all pending errors to be reported immediately as uncaught.
   * Note that this may cause some false positives.
   */
  flushUncaughtErrors() {
    PendingErrors.flush();
  },
};
Object.freeze(Promise.Debugging);

Object.freeze(Promise);

// If module is defined, this file is loaded as a CommonJS module. Make sure
// Promise is exported in that case.
if (this.module) {
  module.exports = Promise;
}

// PromiseWalker

/**
 * This singleton object invokes the handlers registered on resolved and
 * rejected promises, ensuring that processing is not recursive and is done in
 * the same order as registration occurred on each promise.
 *
 * There is no guarantee on the order of execution of handlers registered on
 * different promises.
 */
this.PromiseWalker = {
  /**
   * Singleton array of all the unprocessed handlers currently registered on
   * resolved or rejected promises.  Handlers are removed from the array as soon
   * as they are processed.
   */
  handlers: [],

  /**
   * Called when a promise needs to change state to be resolved or rejected.
   *
   * @param aPromise
   *        Promise that needs to change state.  If this is already resolved or
   *        rejected, this method has no effect.
   * @param aStatus
   *        New desired status, either STATUS_RESOLVED or STATUS_REJECTED.
   * @param aValue
   *        Associated resolution value or rejection reason.
   */
  completePromise(aPromise, aStatus, aValue) {
    // Do nothing if the promise is already resolved or rejected.
    if (aPromise[N_INTERNALS].status != STATUS_PENDING) {
      return;
    }

    // Resolving with another promise will cause this promise to eventually
    // assume the state of the provided promise.
    if (aStatus == STATUS_RESOLVED && aValue &&
        typeof(aValue.then) == "function") {
      aValue.then(this.completePromise.bind(this, aPromise, STATUS_RESOLVED),
                  this.completePromise.bind(this, aPromise, STATUS_REJECTED));
      return;
    }

    // Change the promise status and schedule our handlers for processing.
    aPromise[N_INTERNALS].status = aStatus;
    aPromise[N_INTERNALS].value = aValue;
    if (aPromise[N_INTERNALS].handlers.length > 0) {
      this.schedulePromise(aPromise);
    } else if (Cu && aStatus == STATUS_REJECTED) {
      // This is a rejection and the promise is the last in the chain.
      // For the time being we therefore have an uncaught error.
      let id = PendingErrors.register(aValue);
      let witness =
          FinalizationWitnessService.make("promise-finalization-witness", id);
      aPromise[N_INTERNALS].witness = [id, witness];
    }
  },

  /**
   * Sets up the PromiseWalker loop to start on the next tick of the event loop
   */
  scheduleWalkerLoop() {
    this.walkerLoopScheduled = true;

    // If this file is loaded on a worker thread, DOMPromise will not behave as
    // expected: because native promises are not aware of nested event loops
    // created by the debugger, their respective handlers will not be called
    // until after leaving the nested event loop. The debugger server relies
    // heavily on the use promises, so this could cause the debugger to hang.
    //
    // To work around this problem, any use of native promises in the debugger
    // server should be avoided when it is running on a worker thread. Because
    // it is still necessary to be able to schedule runnables on the event
    // queue, the worker loader defines the function setImmediate as a
    // per-module global for this purpose.
    //
    // If Cu is defined, this file is loaded on the main thread. Otherwise, it
    // is loaded on the worker thread.
    if (Cu) {
      let stack = Components_ ? Components_.stack : null;
      if (stack) {
        DOMPromise.resolve().then(() => {
          Cu.callFunctionWithAsyncStack(this.walkerLoop.bind(this), stack,
                                        "Promise")
        });
      } else {
        DOMPromise.resolve().then(() => this.walkerLoop());
      }
    } else {
      setImmediate(this.walkerLoop);
    }
  },

  /**
   * Schedules the resolution or rejection handlers registered on the provided
   * promise for processing.
   *
   * @param aPromise
   *        Resolved or rejected promise whose handlers should be processed.  It
   *        is expected that this promise has at least one handler to process.
   */
  schedulePromise(aPromise) {
    // Migrate the handlers from the provided promise to the global list.
    for (let handler of aPromise[N_INTERNALS].handlers) {
      this.handlers.push(handler);
    }
    aPromise[N_INTERNALS].handlers.length = 0;

    // Schedule the walker loop on the next tick of the event loop.
    if (!this.walkerLoopScheduled) {
      this.scheduleWalkerLoop();
    }
  },

  /**
   * Indicates whether the walker loop is currently scheduled for execution on
   * the next tick of the event loop.
   */
  walkerLoopScheduled: false,

  /**
   * Processes all the known handlers during this tick of the event loop.  This
   * eager processing is done to avoid unnecessarily exiting and re-entering the
   * JavaScript context for each handler on a resolved or rejected promise.
   *
   * This function is called with "this" bound to the PromiseWalker object.
   */
  walkerLoop() {
    // If there is more than one handler waiting, reschedule the walker loop
    // immediately.  Otherwise, use walkerLoopScheduled to tell schedulePromise()
    // to reschedule the loop if it adds more handlers to the queue.  This makes
    // this walker resilient to the case where one handler does not return, but
    // starts a nested event loop.  In that case, the newly scheduled walker will
    // take over.  In the common case, the newly scheduled walker will be invoked
    // after this one has returned, with no actual handler to process.  This
    // small overhead is required to make nested event loops work correctly, but
    // occurs at most once per resolution chain, thus having only a minor
    // impact on overall performance.
    if (this.handlers.length > 1) {
      this.scheduleWalkerLoop();
    } else {
      this.walkerLoopScheduled = false;
    }

    // Process all the known handlers eagerly.
    while (this.handlers.length > 0) {
      this.handlers.shift().process();
    }
  },
};

// Bind the function to the singleton once.
PromiseWalker.walkerLoop = PromiseWalker.walkerLoop.bind(PromiseWalker);

// Deferred

/**
 * Returned by "Promise.defer" to provide a new promise along with methods to
 * change its state.
 */
function Deferred() {
  this.promise = new Promise((aResolve, aReject) => {
    this.resolve = aResolve;
    this.reject = aReject;
  });
  Object.freeze(this);
}

Deferred.prototype = {
  /**
   * A newly created promise, initially in the pending state.
   */
  promise: null,

  /**
   * Resolves the associated promise with the specified value, or propagates the
   * state of an existing promise.  If the associated promise has already been
   * resolved or rejected, this method does nothing.
   *
   * This function is bound to its associated promise when "Promise.defer" is
   * called, and can be called with any value of "this".
   *
   * @param aValue
   *        If this value is not a promise, including "undefined", it becomes
   *        the resolution value of the associated promise.  If this value is a
   *        promise, then the associated promise will eventually assume the same
   *        state as the provided promise.
   *
   * @note Calling this method with a pending promise as the aValue argument,
   *       and then calling it again with another value before the promise is
   *       resolved or rejected, has unspecified behavior and should be avoided.
   */
  resolve: null,

  /**
   * Rejects the associated promise with the specified reason.  If the promise
   * has already been resolved or rejected, this method does nothing.
   *
   * This function is bound to its associated promise when "Promise.defer" is
   * called, and can be called with any value of "this".
   *
   * @param aReason
   *        The rejection reason for the associated promise.  Although the
   *        reason can be "undefined", it is generally an Error object, like in
   *        exception handling.
   *
   * @note The aReason argument should not generally be a promise.  In fact,
   *       using a rejected promise for the value of aReason would make the
   *       rejection reason equal to the rejected promise itself, not to the
   *       rejection reason of the rejected promise.
   */
  reject: null,
};

// Handler

/**
 * Handler registered on a promise by the "then" function.
 */
function Handler(aThisPromise, aOnResolve, aOnReject) {
  this.thisPromise = aThisPromise;
  this.onResolve = aOnResolve;
  this.onReject = aOnReject;
  this.nextPromise = new Promise(() => {});
}

Handler.prototype = {
  /**
   * Promise on which the "then" method was called.
   */
  thisPromise: null,

  /**
   * Unmodified resolution handler provided to the "then" method.
   */
  onResolve: null,

  /**
   * Unmodified rejection handler provided to the "then" method.
   */
  onReject: null,

  /**
   * New promise that will be returned by the "then" method.
   */
  nextPromise: null,

  /**
   * Called after thisPromise is resolved or rejected, invokes the appropriate
   * callback and propagates the result to nextPromise.
   */
  process() {
    // The state of this promise is propagated unless a handler is defined.
    let nextStatus = this.thisPromise[N_INTERNALS].status;
    let nextValue = this.thisPromise[N_INTERNALS].value;

    try {
      // If a handler is defined for either resolution or rejection, invoke it
      // to determine the state of the next promise, that will be resolved with
      // the returned value, that can also be another promise.
      if (nextStatus == STATUS_RESOLVED) {
        if (typeof(this.onResolve) == "function") {
          nextValue = this.onResolve.call(undefined, nextValue);
        }
      } else if (typeof(this.onReject) == "function") {
        nextValue = this.onReject.call(undefined, nextValue);
        nextStatus = STATUS_RESOLVED;
      }
    } catch (ex) {

      // An exception has occurred in the handler.

      if (ex && typeof ex == "object" && "name" in ex &&
          ERRORS_TO_REPORT.indexOf(ex.name) != -1) {

        // We suspect that the exception is a programmer error, so we now
        // display it using dump().  Note that we do not use Cu.reportError as
        // we assume that this is a programming error, so we do not want end
        // users to see it. Also, if the programmer handles errors correctly,
        // they will either treat the error or log them somewhere.

        dump("*************************\n");
        dump("A coding exception was thrown in a Promise " +
             ((nextStatus == STATUS_RESOLVED) ? "resolution" : "rejection") +
             " callback.\n");
        dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n\n");
        dump("Full message: " + ex + "\n");
        dump("Full stack: " + (("stack" in ex) ? ex.stack : "not available") + "\n");
        dump("*************************\n");

      }

      // Additionally, reject the next promise.
      nextStatus = STATUS_REJECTED;
      nextValue = ex;
    }

    // Propagate the newly determined state to the next promise.
    PromiseWalker.completePromise(this.nextPromise, nextStatus, nextValue);
  },
};
PK
!<TQmodules/Promise.jsm/* -*- 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 = [
  "Promise"
];

/**
 * This module implements the "promise" construct, according to the
 * "Promises/A+" proposal as known in April 2013, documented here:
 *
 * <http://promises-aplus.github.com/promises-spec/>
 *
 * A promise is an object representing a value that may not be available yet.
 * Internally, a promise can be in one of three states:
 *
 * - Pending, when the final value is not available yet.  This is the only state
 *   that may transition to one of the other two states.
 *
 * - Resolved, when and if the final value becomes available.  A resolution
 *   value becomes permanently associated with the promise.  This may be any
 *   value, including "undefined".
 *
 * - Rejected, if an error prevented the final value from being determined.  A
 *   rejection reason becomes permanently associated with the promise.  This may
 *   be any value, including "undefined", though it is generally an Error
 *   object, like in exception handling.
 *
 * A reference to an existing promise may be received by different means, for
 * example as the return value of a call into an asynchronous API.  In this
 * case, the state of the promise can be observed but not directly controlled.
 *
 * To observe the state of a promise, its "then" method must be used.  This
 * method registers callback functions that are called as soon as the promise is
 * either resolved or rejected.  The method returns a new promise, that in turn
 * is resolved or rejected depending on the state of the original promise and on
 * the behavior of the callbacks.  For example, unhandled exceptions in the
 * callbacks cause the new promise to be rejected, even if the original promise
 * is resolved.  See the documentation of the "then" method for details.
 *
 * Promises may also be created using the "Promise.defer" function, the main
 * entry point of this module.  The function, along with the new promise,
 * returns separate methods to change its state to be resolved or rejected.
 * See the documentation of the "Deferred" prototype for details.
 *
 * -----------------------------------------------------------------------------
 *
 * Cu.import("resource://gre/modules/Promise.jsm");
 *
 * // This function creates and returns a new promise.
 * function promiseValueAfterTimeout(aValue, aTimeout)
 * {
 *   let deferred = Promise.defer();
 *
 *   try {
 *     // An asynchronous operation will trigger the resolution of the promise.
 *     // In this example, we don't have a callback that triggers a rejection.
 *     do_timeout(aTimeout, function () {
 *       deferred.resolve(aValue);
 *     });
 *   } catch (ex) {
 *     // Generally, functions returning promises propagate exceptions through
 *     // the returned promise, though they may also choose to fail early.
 *     deferred.reject(ex);
 *   }
 *
 *   // We don't return the deferred to the caller, but only the contained
 *   // promise, so that the caller cannot accidentally change its state.
 *   return deferred.promise;
 * }
 *
 * // This code uses the promise returned be the function above.
 * let promise = promiseValueAfterTimeout("Value", 1000);
 *
 * let newPromise = promise.then(function onResolve(aValue) {
 *   do_print("Resolved with this value: " + aValue);
 * }, function onReject(aReason) {
 *   do_print("Rejected with this reason: " + aReason);
 * });
 *
 * // Unexpected errors should always be reported at the end of a promise chain.
 * newPromise.then(null, Components.utils.reportError);
 *
 * -----------------------------------------------------------------------------
 */

// These constants must be defined on the "this" object for them to be visible
// by subscripts in B2G, since "this" does not match the global scope.
this.Cc = Components.classes;
this.Ci = Components.interfaces;
this.Cu = Components.utils;
this.Cr = Components.results;

this.Cc["@mozilla.org/moz/jssubscript-loader;1"]
    .getService(this.Ci.mozIJSSubScriptLoader)
    .loadSubScript("resource://gre/modules/Promise-backend.js", this);
PK
!<J5

modules/PromiseMessage.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["PromiseMessage"];

var msgId = 0;

var PromiseMessage = {
  send(messageManager, name, data = {}) {
    const id = `${name}_${msgId++}`;

    // Make a copy of data so that the caller doesn't see us setting 'id':
    // To a new object, assign data's props, and then override the id.
    const dataCopy = Object.assign({}, data, {id});

    // Send the message.
    messageManager.sendAsyncMessage(name, dataCopy);

    // Return a promise that resolves when we get a reply (a message of the same name).
    return new Promise(resolve => {
      messageManager.addMessageListener(name, function listener(reply) {
        if (reply.data.id !== id) {
          return;
        }
        messageManager.removeMessageListener(name, listener);
        resolve(reply);
      });
    });
  }
};
PK
!<AY}modules/PromiseUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["PromiseUtils"];

Components.utils.import("resource://gre/modules/Timer.jsm");

this.PromiseUtils = {
  /*
   * Creates a new pending Promise and provide methods to resolve and reject this Promise.
   *
   * @return {Deferred} an object consisting of a pending Promise "promise"
   * and methods "resolve" and "reject" to change its state.
   */
  defer() {
    return new Deferred();
  },
}

/**
 * The definition of Deferred object which is returned by PromiseUtils.defer(),
 * It contains a Promise and methods to resolve/reject it.
 */
function Deferred() {
  /* A method to resolve the associated Promise with the value passed.
   * If the promise is already settled it does nothing.
   *
   * @param {anything} value : This value is used to resolve the promise
   * If the value is a Promise then the associated promise assumes the state
   * of Promise passed as value.
   */
  this.resolve = null;

  /* A method to reject the assocaited Promise with the value passed.
   * If the promise is already settled it does nothing.
   *
   * @param {anything} reason: The reason for the rejection of the Promise.
   * Generally its an Error object. If however a Promise is passed, then the Promise
   * itself will be the reason for rejection no matter the state of the Promise.
   */
  this.reject = null;

  /* A newly created Pomise object.
   * Initially in pending state.
   */
  this.promise = new Promise((resolve, reject) => {
    this.resolve = resolve;
    this.reject = reject;
  });
}
PK
!<?v//modules/PromiseWorker.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A wrapper around ChromeWorker with extended capabilities designed
 * to simplify main thread-to-worker thread asynchronous function calls.
 *
 * This wrapper:
 * - groups requests and responses as a method `post` that returns a `Promise`;
 * - ensures that exceptions thrown on the worker thread are correctly deserialized;
 * - provides some utilities for benchmarking various operations.
 *
 * Generally, you should use PromiseWorker.jsm along with its worker-side
 * counterpart PromiseWorker.js.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["BasePromiseWorker"];

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
  "resource://gre/modules/PromiseUtils.jsm");

/**
 * An implementation of queues (FIFO).
 *
 * The current implementation uses one array, runs in O(n ^ 2), and is optimized
 * for the case in which queues are generally short.
 */
function Queue() {
  this._array = [];
}
Queue.prototype = {
  pop: function pop() {
    return this._array.shift();
  },
  push: function push(x) {
    return this._array.push(x);
  },
  isEmpty: function isEmpty() {
    return this._array.length == 0;
  }
};

/**
 * Constructors for decoding standard exceptions received from the
 * worker.
 */
const EXCEPTION_CONSTRUCTORS = {
  EvalError(error) {
    let result = new EvalError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  InternalError(error) {
    let result = new InternalError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  RangeError(error) {
    let result = new RangeError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  ReferenceError(error) {
    let result = new ReferenceError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  SyntaxError(error) {
    let result = new SyntaxError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  TypeError(error) {
    let result = new TypeError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  URIError(error) {
    let result = new URIError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  StopIteration() {
    return StopIteration;
  }
};

/**
 * An object responsible for dispatching messages to a chrome worker
 * and routing the responses.
 *
 * Instances of this constructor who need logging may provide a method
 * `log: function(...args) { ... }` in charge of printing out (or
 * discarding) logs.
 *
 * Instances of this constructor may add exception handlers to
 * `this.ExceptionHandlers`, if they need to handle custom exceptions.
 *
 * @param {string} url The url containing the source code for this worker,
 * as in constructor ChromeWorker.
 *
 * @constructor
 */
this.BasePromiseWorker = function(url) {
  if (typeof url != "string") {
    throw new TypeError("Expecting a string");
  }
  this._url = url;

  /**
   * A set of methods, with the following
   *
   * ConstructorName: function({message, fileName, lineNumber}) {
   *   // Construct a new instance of ConstructorName based on
   *   // `message`, `fileName`, `lineNumber`
   * }
   *
   * By default, this covers EvalError, InternalError, RangeError,
   * ReferenceError, SyntaxError, TypeError, URIError, StopIteration.
   */
  this.ExceptionHandlers = Object.create(EXCEPTION_CONSTRUCTORS);

  /**
   * The queue of deferred, waiting for the completion of their
   * respective job by the worker.
   *
   * Each item in the list may contain an additional field |closure|,
   * used to store strong references to value that must not be
   * garbage-collected before the reply has been received (e.g.
   * arrays).
   *
   * @type {Queue<{deferred:deferred, closure:*=}>}
   */
  this._queue = new Queue();

  /**
   * The number of the current message.
   *
   * Used for debugging purposes.
   */
  this._id = 0;

  /**
   * The instant at which the worker was launched.
   */
  this.launchTimeStamp = null;

  /**
   * Timestamps provided by the worker for statistics purposes.
   */
  this.workerTimeStamps = null;
};
this.BasePromiseWorker.prototype = {
  log() {
    // By Default, ignore all logs.
  },

  /**
   * Instantiate the worker lazily.
   */
  get _worker() {
    delete this._worker;
    let worker = new ChromeWorker(this._url);
    Object.defineProperty(this, "_worker", {value:
      worker
    });

    // We assume that we call to _worker for the purpose of calling
    // postMessage().
    this.launchTimeStamp = Date.now();

    /**
     * Receive errors that have been serialized by the built-in mechanism
     * of DOM/Chrome Workers.
     *
     * PromiseWorker.js  knows how  to  serialize a  number of  errors
     * without    losing   information.    These   are    treated   by
     * |worker.onmessage|.   However, for  other  errors,  we rely  on
     * DOM's mechanism  for serializing errors, which  transmits these
     * errors through |worker.onerror|.
     *
     * @param {Error} error Some JS error.
     */
    worker.onerror = error => {
      this.log("Received uncaught error from worker", error.message, error.filename, error.lineno);
      error.preventDefault();
      let {deferred} = this._queue.pop();
      deferred.reject(error);
    };

    /**
     * Receive messages from the worker, propagate them to the listeners.
     *
     * Messages must have one of the following shapes:
     * - {ok: some_value} in case of success
     * - {fail: some_error} in case of error, where
     *    some_error is an instance of |PromiseWorker.WorkerError|
     *
     * Messages may also contain a field |id| to help
     * with debugging.
     *
     * Messages may also optionally contain a field |durationMs|, holding
     * the duration of the function call in milliseconds.
     *
     * @param {*} msg The message received from the worker.
     */
    worker.onmessage = msg => {
      this.log("Received message from worker", msg.data);
      let handler = this._queue.pop();
      let deferred = handler.deferred;
      let data = msg.data;
      if (data.id != handler.id) {
        throw new Error("Internal error: expecting msg " + handler.id + ", " +
                        " got " + data.id + ": " + JSON.stringify(msg.data));
      }
      if ("timeStamps" in data) {
        this.workerTimeStamps = data.timeStamps;
      }
      if ("ok" in data) {
        // Pass the data to the listeners.
        deferred.resolve(data);
      } else if ("fail" in data) {
        // We have received an error that was serialized by the
        // worker.
        deferred.reject(new WorkerError(data.fail));
      }
    };
    return worker;
  },

  /**
   * Post a message to a worker.
   *
   * @param {string} fun The name of the function to call.
   * @param {Array} args The arguments to pass to `fun`. If any
   * of the arguments is a Promise, it is resolved before posting the
   * message. If any of the arguments needs to be transfered instead
   * of copied, this may be specified by making the argument an instance
   * of `BasePromiseWorker.Meta` or by using the `transfers` argument.
   * By convention, the last argument may be an object `options`
   * with some of the following fields:
   * - {number|null} outExecutionDuration A parameter to be filled with the
   *   duration of the off main thread execution for this call.
   * @param {*=} closure An object holding references that should not be
   * garbage-collected before the message treatment is complete.
   * @param {Array=} transfers An array of objects that should be transfered
   * to the worker instead of being copied. If any of the objects is a Promise,
   * it is resolved before posting the message.
   *
   * @return {promise}
   */
  post(fun, args, closure, transfers) {
    return (async function postMessage() {
      // Normalize in case any of the arguments is a promise
      if (args) {
        args = await Promise.resolve(Promise.all(args));
      }
      if (transfers) {
        transfers = await Promise.resolve(Promise.all(transfers));
      } else {
        transfers = [];
      }

      if (args) {
        // Extract `Meta` data
        args = args.map(arg => {
          if (arg instanceof BasePromiseWorker.Meta) {
            if (arg.meta && "transfers" in arg.meta) {
              transfers.push(...arg.meta.transfers);
            }
            return arg.data;
          }
          return arg;
        });
      }

      let id = ++this._id;
      let message = {fun, args, id};
      this.log("Posting message", message);
      try {
        this._worker.postMessage(message, ...[transfers]);
      } catch (ex) {
        if (typeof ex == "number") {
          this.log("Could not post message", message, "due to xpcom error", ex);
          // handle raw xpcom errors (see eg bug 961317)
          throw new Components.Exception("Error in postMessage", ex);
        }

        this.log("Could not post message", message, "due to error", ex);
        throw ex;
      }

      let deferred = PromiseUtils.defer();
      this._queue.push({deferred, closure, id});
      this.log("Message posted");

      let reply;
      try {
        this.log("Expecting reply");
        reply = await deferred.promise;
      } catch (error) {
        this.log("Got error", error);
        reply = error;

        if (error instanceof WorkerError) {
          // We know how to deserialize most well-known errors
          throw this.ExceptionHandlers[error.data.exn](error.data);
        }

        if (error instanceof ErrorEvent) {
          // Other errors get propagated as instances of ErrorEvent
          this.log("Error serialized by DOM", error.message, error.filename, error.lineno);
          throw new Error(error.message, error.filename, error.lineno);
        }

        // We don't know about this kind of error
        throw error;
      }

      // By convention, the last argument may be an object `options`.
      let options = null;
      if (args) {
        options = args[args.length - 1];
      }

      // Check for duration and return result.
      if (!options ||
          typeof options !== "object" ||
          !("outExecutionDuration" in options)) {
        return reply.ok;
      }
      // If reply.durationMs is not present, just return the result,
      // without updating durations (there was an error in the method
      // dispatch).
      if (!("durationMs" in reply)) {
        return reply.ok;
      }
      // Bug 874425 demonstrates that two successive calls to Date.now()
      // can actually produce an interval with negative duration.
      // We assume that this is due to an operation that is so short
      // that Date.now() is not monotonic, so we round this up to 0.
      let durationMs = Math.max(0, reply.durationMs);
      // Accumulate (or initialize) outExecutionDuration
      if (typeof options.outExecutionDuration == "number") {
        options.outExecutionDuration += durationMs;
      } else {
        options.outExecutionDuration = durationMs;
      }
      return reply.ok;

    }.bind(this))();
  }
};

/**
 * An error that has been serialized by the worker.
 *
 * @constructor
 */
function WorkerError(data) {
  this.data = data;
}

/**
 * A constructor used to send data to the worker thread while
 * with special treatment (e.g. transmitting data instead of
 * copying it).
 *
 * @param {object=} data The data to send to the caller thread.
 * @param {object=} meta Additional instructions, as an object
 * that may contain the following fields:
 * - {Array} transfers An array of objects that should be transferred
 *   instead of being copied.
 *
 * @constructor
 */
this.BasePromiseWorker.Meta = function(data, meta) {
  this.data = data;
  this.meta = meta;
};
PK
!<=&$$modules/ProxyScriptContext.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ProxyScriptContext"];

/* exported ProxyScriptContext */

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/ExtensionCommon.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
                                  "resource://gre/modules/ExtensionChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                  "resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ProxyService",
                                   "@mozilla.org/network/protocol-proxy-service;1",
                                   "nsIProtocolProxyService");

const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";

// The length of time (seconds) to wait for a proxy to resolve before ignoring it.
const PROXY_TIMEOUT_SEC = 10;

const {
  defineLazyGetter,
} = ExtensionUtils;

const {
  BaseContext,
  CanOfAPIs,
  LocalAPIImplementation,
  SchemaAPIManager,
} = ExtensionCommon;

const PROXY_TYPES = Object.freeze({
  DIRECT: "direct",
  HTTPS: "https",
  PROXY: "proxy",
  HTTP: "http", // Synonym for PROXY_TYPES.PROXY
  SOCKS: "socks",  // SOCKS5
  SOCKS4: "socks4",
});

class ProxyScriptContext extends BaseContext {
  constructor(extension, url, contextInfo = {}) {
    super("proxy_script", extension);
    this.contextInfo = contextInfo;
    this.extension = extension;
    this.messageManager = Services.cpmm;
    this.sandbox = Cu.Sandbox(this.extension.principal, {
      sandboxName: `proxyscript:${extension.id}:${url}`,
      metadata: {addonID: extension.id},
    });
    this.url = url;
    this.FindProxyForURL = null;
  }

  /**
   * Loads and validates a proxy script into the sandbox, and then
   * registers a new proxy filter for the context.
   *
   * @returns {boolean} true if load succeeded; false otherwise.
   */
  load() {
    Schemas.exportLazyGetter(this.sandbox, "browser", () => this.browserObj);

    try {
      Services.scriptloader.loadSubScript(this.url, this.sandbox, "UTF-8");
    } catch (error) {
      this.extension.emit("proxy-error", {
        message: this.normalizeError(error).message,
      });
      return false;
    }

    this.FindProxyForURL = Cu.unwaiveXrays(this.sandbox.FindProxyForURL);
    if (typeof this.FindProxyForURL !== "function") {
      this.extension.emit("proxy-error", {
        message: "The proxy script must define FindProxyForURL as a function",
      });
      return false;
    }

    ProxyService.registerFilter(
      this /* nsIProtocolProxyFilter aFilter */,
      0 /* unsigned long aPosition */
    );

    return true;
  }

  get principal() {
    return this.extension.principal;
  }

  get cloneScope() {
    return this.sandbox;
  }

  /**
   * This method (which is required by the nsIProtocolProxyService interface)
   * is called to apply proxy filter rules for the given URI and proxy object
   * (or list of proxy objects).
   *
   * @param {Object} service A reference to the Protocol Proxy Service.
   * @param {Object} uri The URI for which these proxy settings apply.
   * @param {Object} defaultProxyInfo The proxy (or list of proxies) that
   *     would be used by default for the given URI. This may be null.
   * @returns {Object} The proxy info to apply for the given URI.
   */
  applyFilter(service, uri, defaultProxyInfo) {
    let ret;
    try {
      // Bug 1337001 - provide path and query components to non-https URLs.
      ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
    } catch (e) {
      let error = this.normalizeError(e);
      this.extension.emit("proxy-error", {
        message: error.message,
        fileName: error.fileName,
        lineNumber: error.lineNumber,
        stack: error.stack,
      });
      return defaultProxyInfo;
    }

    if (!ret || typeof ret !== "string") {
      this.extension.emit("proxy-error", {
        message: "FindProxyForURL: Return type must be a string",
      });
      return defaultProxyInfo;
    }

    let rules = ret.split(";");
    let proxyInfo = this.createProxyInfo(rules);

    return proxyInfo || defaultProxyInfo;
  }

  /**
   * Creates a new proxy info object using the return value of FindProxyForURL.
   *
   * @param {Array<string>} rules The list of proxy rules returned by FindProxyForURL.
   *    (e.g. ["PROXY 1.2.3.4:8080", "SOCKS 1.1.1.1:9090", "DIRECT"])
   * @returns {nsIProxyInfo} The proxy info to apply for the given URI.
   */
  createProxyInfo(rules) {
    if (!rules.length) {
      return null;
    }

    let rule = rules[0].trim();

    if (!rule) {
      this.extension.emit("proxy-error", {
        message: "FindProxyForURL: Missing Proxy Rule",
      });
      return null;
    }

    let parts = rule.split(/\s+/);
    if (!parts[0]) {
      this.extension.emit("proxy-error", {
        message: `FindProxyForURL: Too many arguments passed for proxy rule: "${rule}"`,
      });
      return null;
    }

    if (parts.length > 2) {
      this.extension.emit("proxy-error", {
        message: `FindProxyForURL: Too many arguments passed for proxy rule: "${rule}"`,
      });
      return null;
    }

    switch (parts[0].toLowerCase()) {
      case PROXY_TYPES.PROXY:
      case PROXY_TYPES.HTTP:
      case PROXY_TYPES.HTTPS:
      case PROXY_TYPES.SOCKS:
      case PROXY_TYPES.SOCKS4:
        if (!parts[1]) {
          this.extension.emit("proxy-error", {
            message: `FindProxyForURL: Missing argument for proxy type: "${parts[0]}"`,
          });
          return null;
        }

        if (parts.length != 2) {
          this.extension.emit("proxy-error", {
            message: `FindProxyForURL: Too many arguments for proxy rule: "${rule}"`,
          });
          return null;
        }

        let [host, port] = parts[1].split(":");
        if (!host || !port) {
          this.extension.emit("proxy-error", {
            message: `FindProxyForURL: Unable to parse host and port from proxy rule: "${rule}"`,
          });
          return null;
        }

        let type = parts[0];
        if (parts[0].toLowerCase() == PROXY_TYPES.PROXY) {
          // PROXY_TYPES.HTTP and PROXY_TYPES.PROXY are synonyms
          type = PROXY_TYPES.HTTP;
        }

        let failoverProxy = this.createProxyInfo(rules.slice(1));
        return ProxyService.newProxyInfo(type, host, port, 0,
          PROXY_TIMEOUT_SEC, failoverProxy);
      case PROXY_TYPES.DIRECT:
        if (parts.length != 1) {
          this.extension.emit("proxy-error", {
            message: `FindProxyForURL: Invalid argument for proxy type: "${parts[0]}"`,
          });
        }
        return null;
      default:
        this.extension.emit("proxy-error", {
          message: `FindProxyForURL: Unrecognized proxy type: "${parts[0]}"`,
        });
        return null;
    }
  }

  /**
   * Unloads the proxy filter and shuts down the sandbox.
   */
  unload() {
    super.unload();
    ProxyService.unregisterFilter(this);
    Cu.nukeSandbox(this.sandbox);
    this.sandbox = null;
  }
}

class ProxyScriptAPIManager extends SchemaAPIManager {
  constructor() {
    super("proxy");
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(
          CATEGORY_EXTENSION_SCRIPTS_CONTENT)) {
        this.loadScript(value);
      }
      this.initialized = true;
    }
  }
}

class ProxyScriptInjectionContext {
  constructor(context, apiCan) {
    this.context = context;
    this.localAPIs = apiCan.root;
    this.apiCan = apiCan;
  }

  shouldInject(namespace, name, allowedContexts) {
    if (this.context.envType !== "proxy_script") {
      throw new Error(`Unexpected context type "${this.context.envType}"`);
    }

    // Do not generate proxy script APIs unless explicitly allowed.
    return allowedContexts.includes("proxy");
  }

  getImplementation(namespace, name) {
    this.apiCan.findAPIPath(`${namespace}.${name}`);
    let obj = this.apiCan.findAPIPath(namespace);

    if (obj && name in obj) {
      return new LocalAPIImplementation(obj, name, this.context);
    }
  }

  get cloneScope() {
    return this.context.cloneScope;
  }

  get principal() {
    return this.context.principal;
  }
}

defineLazyGetter(ProxyScriptContext.prototype, "messenger", function() {
  let sender = {id: this.extension.id, frameId: this.frameId, url: this.url};
  let filter = {extensionId: this.extension.id, toProxyScript: true};
  return new ExtensionChild.Messenger(this, [this.messageManager], sender, filter);
});

let proxyScriptAPIManager = new ProxyScriptAPIManager();

defineLazyGetter(ProxyScriptContext.prototype, "browserObj", function() {
  let localAPIs = {};
  let can = new CanOfAPIs(this, proxyScriptAPIManager, localAPIs);
  proxyScriptAPIManager.lazyInit();

  let browserObj = Cu.createObjectIn(this.sandbox);
  let injectionContext = new ProxyScriptInjectionContext(this, can);
  Schemas.inject(browserObj, injectionContext);
  return browserObj;
});
PK
!<tSSmodules/PushCrypto.jsm/* jshint moz: true, esnext: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const Cu = Components.utils;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');

XPCOMUtils.defineLazyGetter(this, 'gDOMBundle', () =>
  Services.strings.createBundle('chrome://global/locale/dom/dom.properties'));

Cu.importGlobalProperties(['crypto']);

this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray'];

var UTF8 = new TextEncoder('utf-8');

var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' };
var ECDSA_KEY =  { name: 'ECDSA', namedCurve: 'P-256' };

// A default keyid with a name that won't conflict with a real keyid.
var DEFAULT_KEYID = '';

/** Localized error property names. */

// `Encryption` header missing or malformed.
const BAD_ENCRYPTION_HEADER = 'PushMessageBadEncryptionHeader';
// `Crypto-Key` or legacy `Encryption-Key` header missing.
const BAD_CRYPTO_KEY_HEADER = 'PushMessageBadCryptoKeyHeader';
const BAD_ENCRYPTION_KEY_HEADER = 'PushMessageBadEncryptionKeyHeader';
// `Content-Encoding` header missing or contains unsupported encoding.
const BAD_ENCODING_HEADER = 'PushMessageBadEncodingHeader';
// `dh` parameter of `Crypto-Key` header missing or not base64url-encoded.
const BAD_DH_PARAM = 'PushMessageBadSenderKey';
// `salt` parameter of `Encryption` header missing or not base64url-encoded.
const BAD_SALT_PARAM = 'PushMessageBadSalt';
// `rs` parameter of `Encryption` header not a number or less than pad size.
const BAD_RS_PARAM = 'PushMessageBadRecordSize';
// Invalid or insufficient padding for encrypted chunk.
const BAD_PADDING = 'PushMessageBadPaddingError';
// Generic crypto error.
const BAD_CRYPTO = 'PushMessageBadCryptoError';

class CryptoError extends Error {
  /**
   * Creates an error object indicating an incoming push message could not be
   * decrypted.
   *
   * @param {String} message A human-readable error message. This is only for
   * internal module logging, and doesn't need to be localized.
   * @param {String} property The localized property name from `dom.properties`.
   * @param {String...} params Substitutions to insert into the localized
   *  string.
   */
  constructor(message, property, ...params) {
    super(message);
    this.isCryptoError = true;
    this.property = property;
    this.params = params;
  }

  /**
   * Formats a localized string for reporting decryption errors to the Web
   * Console.
   *
   * @param {String} scope The scope of the service worker receiving the
   *  message, prepended to any other substitutions in the string.
   * @returns {String} The localized string.
   */
  format(scope) {
    let params = [scope, ...this.params].map(String);
    return gDOMBundle.formatStringFromName(this.property, params,
                                           params.length);
  }
}

function getEncryptionKeyParams(encryptKeyField) {
  if (!encryptKeyField) {
    return null;
  }
  var params = encryptKeyField.split(',');
  return params.reduce((m, p) => {
    var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
    if (pmap.keyid && pmap.dh) {
      m[pmap.keyid] = pmap.dh;
    }
    if (!m[DEFAULT_KEYID] && pmap.dh) {
      m[DEFAULT_KEYID] = pmap.dh;
    }
    return m;
  }, {});
}

function getEncryptionParams(encryptField) {
  if (!encryptField) {
    throw new CryptoError('Missing encryption header',
                          BAD_ENCRYPTION_HEADER);
  }
  var p = encryptField.split(',', 1)[0];
  if (!p) {
    throw new CryptoError('Encryption header missing params',
                          BAD_ENCRYPTION_HEADER);
  }
  return p.split(';').reduce(parseHeaderFieldParams, {});
}

// Extracts the sender public key, salt, and record size from the payload for the
// aes128gcm scheme.
function getCryptoParamsFromPayload(payload) {
  if (payload.byteLength < 21) {
    throw new CryptoError('Truncated header', BAD_CRYPTO);
  }
  let rs = (payload[16] << 24) | (payload[17] << 16) | (payload[18] << 8) | payload[19];
  let keyIdLen = payload[20];
  if (keyIdLen != 65) {
    throw new CryptoError('Invalid sender public key', BAD_DH_PARAM);
  }
  if (payload.byteLength <= 21 + keyIdLen) {
    throw new CryptoError('Truncated payload', BAD_CRYPTO);
  }
  return {
    salt: payload.slice(0, 16),
    rs: rs,
    senderKey: payload.slice(21, 21 + keyIdLen),
    ciphertext: payload.slice(21 + keyIdLen),
  };
}

// Extracts the sender public key, salt, and record size from the `Crypto-Key`,
// `Encryption-Key`, and `Encryption` headers for the aesgcm and aesgcm128
// schemes.
function getCryptoParamsFromHeaders(headers) {
  if (!headers) {
    return null;
  }

  var keymap;
  if (headers.encoding == AESGCM_ENCODING) {
    // aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an
    // authentication secret.
    // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-01
    keymap = getEncryptionKeyParams(headers.crypto_key);
    if (!keymap) {
      throw new CryptoError('Missing Crypto-Key header',
                            BAD_CRYPTO_KEY_HEADER);
    }
  } else if (headers.encoding == AESGCM128_ENCODING) {
    // aesgcm128 uses Encryption-Key, 1 byte for the pad length, and no secret.
    // https://tools.ietf.org/html/draft-thomson-http-encryption-02
    keymap = getEncryptionKeyParams(headers.encryption_key);
    if (!keymap) {
      throw new CryptoError('Missing Encryption-Key header',
                            BAD_ENCRYPTION_KEY_HEADER);
    }
  }

  var enc = getEncryptionParams(headers.encryption);
  var dh = keymap[enc.keyid || DEFAULT_KEYID];
  var senderKey = base64URLDecode(dh);
  if (!senderKey) {
    throw new CryptoError('Invalid dh parameter', BAD_DH_PARAM);
  }

  var salt = base64URLDecode(enc.salt);
  if (!salt) {
    throw new CryptoError('Invalid salt parameter', BAD_SALT_PARAM);
  }
  var rs = enc.rs ? parseInt(enc.rs, 10) : 4096;
  if (isNaN(rs)) {
    throw new CryptoError('rs parameter must be a number', BAD_RS_PARAM);
  }
  return {
    salt: salt,
    rs: rs,
    senderKey: senderKey,
  };
}

// Decodes an unpadded, base64url-encoded string.
function base64URLDecode(string) {
  if (!string) {
    return null;
  }
  try {
    return ChromeUtils.base64URLDecode(string, {
      // draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
      padding: 'reject',
    });
  } catch (ex) {}
  return null;
}

var parseHeaderFieldParams = (m, v) => {
  var i = v.indexOf('=');
  if (i >= 0) {
    // A quoted string with internal quotes is invalid for all the possible
    // values of this header field.
    m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
                                   .replace(/^"(.*)"$/, '$1');
  }
  return m;
};

function chunkArray(array, size) {
  var start = array.byteOffset || 0;
  array = array.buffer || array;
  var index = 0;
  var result = [];
  while(index + size <= array.byteLength) {
    result.push(new Uint8Array(array, start + index, size));
    index += size;
  }
  if (index < array.byteLength) {
    result.push(new Uint8Array(array, start + index));
  }
  return result;
}

this.concatArray = function(arrays) {
  var size = arrays.reduce((total, a) => total + a.byteLength, 0);
  var index = 0;
  return arrays.reduce((result, a) => {
    result.set(new Uint8Array(a), index);
    index += a.byteLength;
    return result;
  }, new Uint8Array(size));
};

var HMAC_SHA256 = { name: 'HMAC', hash: 'SHA-256' };

function hmac(key) {
  this.keyPromise = crypto.subtle.importKey('raw', key, HMAC_SHA256,
                                            false, ['sign']);
}

hmac.prototype.hash = function(input) {
  return this.keyPromise.then(k => crypto.subtle.sign('HMAC', k, input));
};

function hkdf(salt, ikm) {
  this.prkhPromise = new hmac(salt).hash(ikm)
    .then(prk => new hmac(prk));
}

hkdf.prototype.extract = function(info, len) {
  var input = concatArray([info, new Uint8Array([1])]);
  return this.prkhPromise
    .then(prkh => prkh.hash(input))
    .then(h => {
      if (h.byteLength < len) {
        throw new CryptoError('HKDF length is too long', BAD_CRYPTO);
      }
      return h.slice(0, len);
    });
};

/* generate a 96-bit nonce for use in GCM, 48-bits of which are populated */
function generateNonce(base, index) {
  if (index >= Math.pow(2, 48)) {
    throw new CryptoError('Nonce index is too large', BAD_CRYPTO);
  }
  var nonce = base.slice(0, 12);
  nonce = new Uint8Array(nonce);
  for (var i = 0; i < 6; ++i) {
    nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
  }
  return nonce;
}

function encodeLength(buffer) {
  return new Uint8Array([0, buffer.byteLength]);
}

var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');

class Decoder {
  /**
   * Creates a decoder for decrypting an incoming push message.
   *
   * @param {JsonWebKey} privateKey The static subscription private key.
   * @param {BufferSource} publicKey The static subscription public key.
   * @param {BufferSource} authenticationSecret The subscription authentication
   *  secret, or `null` if not used by the scheme.
   * @param {Object} cryptoParams An object containing the ephemeral sender
   *  public key, salt, and record size.
   * @param {BufferSource} ciphertext The encrypted message data.
   */
  constructor(privateKey, publicKey, authenticationSecret, cryptoParams,
              ciphertext) {
    this.privateKey = privateKey;
    this.publicKey = publicKey;
    this.authenticationSecret = authenticationSecret;
    this.senderKey = cryptoParams.senderKey;
    this.salt = cryptoParams.salt;
    this.rs = cryptoParams.rs;
    this.ciphertext = ciphertext;
  }

  /**
   * Derives the decryption keys and decodes the push message.
   *
   * @throws {CryptoError} if decryption fails.
   * @returns {Uint8Array} The decrypted message data.
   */
  async decode() {
    if (this.ciphertext.byteLength === 0) {
      // Zero length messages will be passed as null.
      return null;
    }
    try {
      let ikm = await this.computeSharedSecret();
      let [gcmBits, nonce] = await this.deriveKeyAndNonce(ikm);
      let key = await crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
                                              ['decrypt']);

      let r = await Promise.all(chunkArray(this.ciphertext, this.chunkSize)
        .map((slice, index, chunks) => this.decodeChunk(slice, index, nonce,
          key, index >= chunks.length - 1)));

      return concatArray(r);
    } catch (error) {
      if (error.isCryptoError) {
        throw error;
      }
      // Web Crypto returns an unhelpful "operation failed for an
      // operation-specific reason" error if decryption fails. We don't have
      // context about what went wrong, so we throw a generic error instead.
      throw new CryptoError('Bad encryption', BAD_CRYPTO);
    }
  }

  /**
   * Computes the ECDH shared secret, used as the input key material for HKDF.
   *
   * @throws if the static or ephemeral ECDH keys are invalid.
   * @returns {ArrayBuffer} The shared secret.
   */
  async computeSharedSecret() {
    let [appServerKey, subscriptionPrivateKey] = await Promise.all([
      crypto.subtle.importKey('raw', this.senderKey, ECDH_KEY,
                              false, ['deriveBits']),
      crypto.subtle.importKey('jwk', this.privateKey, ECDH_KEY,
                              false, ['deriveBits'])
    ]);
    return crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
                                    subscriptionPrivateKey, 256);
  }

  /**
   * Derives the content encryption key and nonce.
   *
   * @param {BufferSource} ikm The ECDH shared secret.
   * @returns {Array} A `[gcmBits, nonce]` tuple.
   */
  async deriveKeyAndNonce(ikm) {
    throw new Error('Missing `deriveKeyAndNonce` implementation');
  }

  /**
   * Decrypts and removes padding from an encrypted record.
   *
   * @throws {CryptoError} if decryption fails or padding is incorrect.
   * @param {Uint8Array} slice The encrypted record.
   * @param {Number} index The record sequence number.
   * @param {Uint8Array} nonce The nonce base, used to generate the IV.
   * @param {Uint8Array} key The content encryption key.
   * @param {Boolean} last Indicates if this is the final record.
   * @returns {Uint8Array} The decrypted block with padding removed.
   */
  async decodeChunk(slice, index, nonce, key, last) {
    let params = {
      name: 'AES-GCM',
      iv: generateNonce(nonce, index)
    };
    let decoded = await crypto.subtle.decrypt(params, key, slice);
    return this.unpadChunk(new Uint8Array(decoded), last);
  }

  /**
   * Removes padding from a decrypted block.
   *
   * @throws {CryptoError} if padding is missing or invalid.
   * @param {Uint8Array} chunk The decrypted block with padding.
   * @returns {Uint8Array} The block with padding removed.
   */
  unpadChunk(chunk, last) {
    throw new Error('Missing `unpadChunk` implementation');
  }

  /** The record chunking size. */
  get chunkSize() {
    throw new Error('Missing `chunkSize` implementation');
  }
}

class OldSchemeDecoder extends Decoder {
  async decode() {
    // For aesgcm and aesgcm128, the ciphertext length can't fall on a record
    // boundary.
    if (this.ciphertext.byteLength > 0 &&
        this.ciphertext.byteLength % this.chunkSize === 0) {
      throw new CryptoError('Encrypted data truncated', BAD_CRYPTO);
    }
    return super.decode();
  }

  /**
   * For aesgcm, the padding length is a 16-bit unsigned big endian integer.
   * For aesgcm128, the padding is an 8-bit integer.
   */
  unpadChunk(decoded) {
    if (decoded.length < this.padSize) {
      throw new CryptoError('Decoded array is too short!', BAD_PADDING);
    }
    var pad = decoded[0]
    if (this.padSize == 2) {
      pad = (pad << 8) | decoded[1];
    }
    if (pad > decoded.length - this.padSize) {
      throw new CryptoError('Padding is wrong!', BAD_PADDING);
    }
    // All padded bytes must be zero except the first one.
    for (var i = this.padSize; i < this.padSize + pad; i++) {
      if (decoded[i] !== 0) {
        throw new CryptoError('Padding is wrong!', BAD_PADDING);
      }
    }
    return decoded.slice(pad + this.padSize);
  }

  /**
   * aesgcm and aesgcm128 don't account for the authentication tag as part of
   * the record size.
   */
  get chunkSize() {
    return this.rs + 16;
  }

  get padSize() {
    throw new Error('Missing `padSize` implementation');
  }
}

/** New encryption scheme (draft-ietf-httpbis-encryption-encoding-06). */

var AES128GCM_ENCODING = 'aes128gcm';
var AES128GCM_KEY_INFO = UTF8.encode('Content-Encoding: aes128gcm\0');
var AES128GCM_AUTH_INFO = UTF8.encode('WebPush: info\0');

class aes128gcmDecoder extends Decoder {
  /**
   * Derives the aes128gcm decryption key and nonce. The PRK info string for
   * HKDF is "WebPush: info\0", followed by the unprefixed receiver and sender
   * public keys.
   */
  async deriveKeyAndNonce(ikm) {
    let authKdf = new hkdf(this.authenticationSecret, ikm);
    let authInfo = concatArray([
      AES128GCM_AUTH_INFO,
      this.publicKey,
      this.senderKey
    ]);
    let prk = await authKdf.extract(authInfo, 32);
    let prkKdf = new hkdf(this.salt, prk);
    return Promise.all([
      prkKdf.extract(AES128GCM_KEY_INFO, 16),
      prkKdf.extract(concatArray([NONCE_INFO, new Uint8Array([0])]), 12)
    ]);
  }

  unpadChunk(decoded, last) {
    let length = decoded.length;
    while (length--) {
      if (decoded[length] === 0) {
        continue;
      }
      let recordPad = last ? 2 : 1;
      if (decoded[length] != recordPad) {
        throw new CryptoError('Padding is wrong!', BAD_PADDING);
      }
      return decoded.slice(0, length);
    }
    throw new CryptoError('Zero plaintext', BAD_PADDING);
  }

  /** aes128gcm accounts for the authentication tag in the record size. */
  get chunkSize() {
    return this.rs;
  }
}

/** Older encryption scheme (draft-ietf-httpbis-encryption-encoding-01). */

var AESGCM_ENCODING = 'aesgcm';
var AESGCM_KEY_INFO = UTF8.encode('Content-Encoding: aesgcm\0');
var AESGCM_AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
var AESGCM_P256DH_INFO = UTF8.encode('P-256\0');

class aesgcmDecoder extends OldSchemeDecoder {
  /**
   * Derives the aesgcm decryption key and nonce. We mix the authentication
   * secret with the ikm using HKDF. The context string for the PRK is
   * "Content-Encoding: auth\0". The context string for the key and nonce is
   * "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
   * receiver key and sender key.
   */
  async deriveKeyAndNonce(ikm) {
    // Since we are using an authentication secret, we need to run an extra
    // round of HKDF with the authentication secret as salt.
    let authKdf = new hkdf(this.authenticationSecret, ikm);
    let prk = await authKdf.extract(AESGCM_AUTH_INFO, 32);
    let prkKdf = new hkdf(this.salt, prk);
    let keyInfo = concatArray([
      AESGCM_KEY_INFO, AESGCM_P256DH_INFO,
      encodeLength(this.publicKey), this.publicKey,
      encodeLength(this.senderKey), this.senderKey
    ]);
    let nonceInfo = concatArray([
      NONCE_INFO, new Uint8Array([0]), AESGCM_P256DH_INFO,
      encodeLength(this.publicKey), this.publicKey,
      encodeLength(this.senderKey), this.senderKey
    ]);
    return Promise.all([
      prkKdf.extract(keyInfo, 16),
      prkKdf.extract(nonceInfo, 12)
    ]);
  }

  get padSize() {
    return 2;
  }
}

/** Oldest encryption scheme (draft-thomson-http-encryption-02). */

var AESGCM128_ENCODING = 'aesgcm128';
var AESGCM128_KEY_INFO = UTF8.encode('Content-Encoding: aesgcm128');

class aesgcm128Decoder extends OldSchemeDecoder {
  constructor(privateKey, publicKey, cryptoParams, ciphertext) {
    super(privateKey, publicKey, null, cryptoParams, ciphertext);
  }

  /**
   * The aesgcm128 scheme ignores the authentication secret, and uses
   * "Content-Encoding: <blah>" for the context string. It should eventually
   * be removed: bug 1230038.
   */
  deriveKeyAndNonce(ikm) {
    let prkKdf = new hkdf(this.salt, ikm);
    return Promise.all([
      prkKdf.extract(AESGCM128_KEY_INFO, 16),
      prkKdf.extract(NONCE_INFO, 12)
    ]);
  }

  get padSize() {
    return 1;
  }
}

this.PushCrypto = {

  generateAuthenticationSecret() {
    return crypto.getRandomValues(new Uint8Array(16));
  },

  validateAppServerKey(key) {
    return crypto.subtle.importKey('raw', key, ECDSA_KEY,
                                   true, ['verify'])
      .then(_ => key);
  },

  generateKeys() {
    return crypto.subtle.generateKey(ECDH_KEY, true, ['deriveBits'])
      .then(cryptoKey =>
         Promise.all([
           crypto.subtle.exportKey('raw', cryptoKey.publicKey),
           crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
         ]));
  },

  /**
   * Decrypts a push message.
   *
   * @throws {CryptoError} if decryption fails.
   * @param {JsonWebKey} privateKey The ECDH private key of the subscription
   *  receiving the message, in JWK form.
   * @param {BufferSource} publicKey The ECDH public key of the subscription
   *  receiving the message, in raw form.
   * @param {BufferSource} authenticationSecret The 16-byte shared
   *  authentication secret of the subscription receiving the message.
   * @param {Object} headers The encryption headers from the push server.
   * @param {BufferSource} payload The encrypted message payload.
   * @returns {Uint8Array} The decrypted message data.
   */
  async decrypt(privateKey, publicKey, authenticationSecret, headers, payload) {
    if (!headers) {
      return null;
    }

    let encoding = headers.encoding;
    if (!headers.encoding) {
      throw new CryptoError('Missing Content-Encoding header',
                            BAD_ENCODING_HEADER);
    }

    let decoder;
    if (encoding == AES128GCM_ENCODING) {
      // aes128gcm includes the salt, record size, and sender public key in a
      // binary header preceding the ciphertext.
      let cryptoParams = getCryptoParamsFromPayload(new Uint8Array(payload));
      decoder = new aes128gcmDecoder(privateKey, publicKey,
                                     authenticationSecret, cryptoParams,
                                     cryptoParams.ciphertext);
    } else if (encoding == AESGCM128_ENCODING || encoding == AESGCM_ENCODING) {
      // aesgcm and aesgcm128 include the salt, record size, and sender public
      // key in the `Crypto-Key` and `Encryption` HTTP headers.
      let cryptoParams = getCryptoParamsFromHeaders(headers);
      if (headers.encoding == AESGCM_ENCODING) {
        decoder = new aesgcmDecoder(privateKey, publicKey, authenticationSecret,
                                    cryptoParams, payload);
      } else {
        decoder = new aesgcm128Decoder(privateKey, publicKey, cryptoParams,
                                       payload);
      }
    }

    if (!decoder) {
      throw new CryptoError('Unsupported Content-Encoding: ' + encoding,
                            BAD_ENCODING_HEADER);
    }

    return decoder.decode();
  },
};
PK
!<-u22modules/PushDB.jsm/* jshint moz: true, esnext: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["indexedDB"]);

this.EXPORTED_SYMBOLS = ["PushDB"];

XPCOMUtils.defineLazyGetter(this, "console", () => {
  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushDB",
  });
});

this.PushDB = function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) {
  console.debug("PushDB()");
  this._dbStoreName = dbStoreName;
  this._keyPath = keyPath;
  this._model = model;

  // set the indexeddb database
  this.initDBHelper(dbName, dbVersion,
                    [dbStoreName]);
};

this.PushDB.prototype = {
  __proto__: IndexedDBHelper.prototype,

  toPushRecord: function(record) {
    if (!record) {
      return;
    }
    return new this._model(record);
  },

  isValidRecord: function(record) {
    return record && typeof record.scope == "string" &&
           typeof record.originAttributes == "string" &&
           record.quota >= 0 &&
           typeof record[this._keyPath] == "string";
  },

  upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) {
    if (aOldVersion <= 3) {
      //XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old
      //registrations away without even informing the app.
      if (aDb.objectStoreNames.contains(this._dbStoreName)) {
        aDb.deleteObjectStore(this._dbStoreName);
      }

      let objectStore = aDb.createObjectStore(this._dbStoreName,
                                              { keyPath: this._keyPath });

      // index to fetch records based on endpoints. used by unregister
      objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });

      // index to fetch records by identifiers.
      // In the current security model, the originAttributes distinguish between
      // different 'apps' on the same origin. Since ServiceWorkers are
      // same-origin to the scope they are registered for, the attributes and
      // scope are enough to reconstruct a valid principal.
      objectStore.createIndex("identifiers", ["scope", "originAttributes"], { unique: true });
      objectStore.createIndex("originAttributes", "originAttributes", { unique: false });
    }

    if (aOldVersion < 4) {
      let objectStore = aTransaction.objectStore(this._dbStoreName);

      // index to fetch active and expired registrations.
      objectStore.createIndex("quota", "quota", { unique: false });
    }
  },

  /*
   * @param aRecord
   *        The record to be added.
   */

  put: function(aRecord) {
    console.debug("put()", aRecord);
    if (!this.isValidRecord(aRecord)) {
      return Promise.reject(new TypeError(
        "Scope, originAttributes, and quota are required! " +
          JSON.stringify(aRecord)
        )
      );
    }

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.put(aRecord).onsuccess = aEvent => {
            console.debug("put: Request successful. Updated record",
              aEvent.target.result);
            aTxn.result = this.toPushRecord(aRecord);
          };
        },
        resolve,
        reject
      )
    );
  },

  /*
   * @param aKeyID
   *        The ID of record to be deleted.
   */
  delete: function(aKeyID) {
    console.debug("delete()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          console.debug("delete: Removing record", aKeyID);
          aStore.get(aKeyID).onsuccess = event => {
            aTxn.result = this.toPushRecord(event.target.result);
            aStore.delete(aKeyID);
          };
        },
        resolve,
        reject
      )
    );
  },

  // testFn(record) is called with a database record and should return true if
  // that record should be deleted.
  clearIf: function(testFn) {
    console.debug("clearIf()");
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.openCursor().onsuccess = event => {
            let cursor = event.target.result;
            if (cursor) {
              let record = this.toPushRecord(cursor.value);
              if (testFn(record)) {
                let deleteRequest = cursor.delete();
                deleteRequest.onerror = e => {
                  console.error("clearIf: Error removing record",
                    record.keyID, e);
                }
              }
              cursor.continue();
            }
          }
        },
        resolve,
        reject
      )
    );
  },

  getByPushEndpoint: function(aPushEndpoint) {
    console.debug("getByPushEndpoint()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("pushEndpoint");
          index.get(aPushEndpoint).onsuccess = aEvent => {
            let record = this.toPushRecord(aEvent.target.result);
            console.debug("getByPushEndpoint: Got record", record);
            aTxn.result = record;
          };
        },
        resolve,
        reject
      )
    );
  },

  getByKeyID: function(aKeyID) {
    console.debug("getByKeyID()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.get(aKeyID).onsuccess = aEvent => {
            let record = this.toPushRecord(aEvent.target.result);
            console.debug("getByKeyID: Got record", record);
            aTxn.result = record;
          };
        },
        resolve,
        reject
      )
    );
  },

  /**
   * Iterates over all records associated with an origin.
   *
   * @param {String} origin The origin, matched as a prefix against the scope.
   * @param {String} originAttributes Additional origin attributes. Requires
   *  an exact match.
   * @param {Function} callback A function with the signature `(record,
   *  cursor)`, called for each record. `record` is the registration, and
   *  `cursor` is an `IDBCursor`.
   * @returns {Promise} Resolves once all records have been processed.
   */
  forEachOrigin: function(origin, originAttributes, callback) {
    console.debug("forEachOrigin()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("identifiers");
          let range = IDBKeyRange.bound(
            [origin, originAttributes],
            [origin + "\x7f", originAttributes]
          );
          index.openCursor(range).onsuccess = event => {
            let cursor = event.target.result;
            if (!cursor) {
              return;
            }
            callback(this.toPushRecord(cursor.value), cursor);
            cursor.continue();
          };
        },
        resolve,
        reject
      )
    );
  },

  // Perform a unique match against { scope, originAttributes }
  getByIdentifiers: function(aPageRecord) {
    console.debug("getByIdentifiers()", aPageRecord);
    if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) {
      console.error("getByIdentifiers: Scope and originAttributes are required",
        aPageRecord);
      return Promise.reject(new TypeError("Invalid page record"));
    }

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("identifiers");
          let request = index.get(IDBKeyRange.only([aPageRecord.scope, aPageRecord.originAttributes]));
          request.onsuccess = aEvent => {
            aTxn.result = this.toPushRecord(aEvent.target.result);
          };
        },
        resolve,
        reject
      )
    );
  },

  _getAllByKey: function(aKeyName, aKeyValue) {
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index(aKeyName);
          // It seems ok to use getAll here, since unlike contacts or other
          // high storage APIs, we don't expect more than a handful of
          // registrations per domain, and usually only one.
          let getAllReq = index.mozGetAll(aKeyValue);
          getAllReq.onsuccess = aEvent => {
            aTxn.result = aEvent.target.result.map(
              record => this.toPushRecord(record));
          };
        },
        resolve,
        reject
      )
    );
  },

  // aOriginAttributes must be a string!
  getAllByOriginAttributes: function(aOriginAttributes) {
    if (typeof aOriginAttributes !== "string") {
      return Promise.reject("Expected string!");
    }
    return this._getAllByKey("originAttributes", aOriginAttributes);
  },

  getAllKeyIDs: function() {
    console.debug("getAllKeyIDs()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;
          aStore.mozGetAll().onsuccess = event => {
            aTxn.result = event.target.result.map(
              record => this.toPushRecord(record));
          };
        },
        resolve,
        reject
      )
    );
  },

  _getAllByPushQuota: function(range) {
    console.debug("getAllByPushQuota()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = [];

          let index = aStore.index("quota");
          index.openCursor(range).onsuccess = event => {
            let cursor = event.target.result;
            if (cursor) {
              aTxn.result.push(this.toPushRecord(cursor.value));
              cursor.continue();
            }
          };
        },
        resolve,
        reject
      )
    );
  },

  getAllUnexpired: function() {
    console.debug("getAllUnexpired()");
    return this._getAllByPushQuota(IDBKeyRange.lowerBound(1));
  },

  getAllExpired: function() {
    console.debug("getAllExpired()");
    return this._getAllByPushQuota(IDBKeyRange.only(0));
  },

  /**
   * Updates an existing push registration.
   *
   * @param {String} aKeyID The registration ID.
   * @param {Function} aUpdateFunc A function that receives the existing
   *  registration record as its argument, and returns a new record.
   * @returns {Promise} A promise resolved with either the updated record.
   *  Rejects if the record does not exist, or the function returns an invalid
   *  record.
   */
  update: function(aKeyID, aUpdateFunc) {
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aStore.get(aKeyID).onsuccess = aEvent => {
            aTxn.result = undefined;

            let record = aEvent.target.result;
            if (!record) {
              throw new Error("Record " + aKeyID + " does not exist");
            }
            let newRecord = aUpdateFunc(this.toPushRecord(record));
            if (!this.isValidRecord(newRecord)) {
              console.error("update: Ignoring invalid update",
                aKeyID, newRecord);
              throw new Error("Invalid update for record " + aKeyID);
            }
            function putRecord() {
              let req = aStore.put(newRecord);
              req.onsuccess = aEvent => {
                console.debug("update: Update successful", aKeyID, newRecord);
                aTxn.result = newRecord;
              };
            }
            if (aKeyID === newRecord.keyID) {
              putRecord();
            } else {
              // If we changed the primary key, delete the old record to avoid
              // unique constraint errors.
              aStore.delete(aKeyID).onsuccess = putRecord;
            }
          };
        },
        resolve,
        reject
      )
    );
  },

  drop: function() {
    console.debug("drop()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        function txnCb(aTxn, aStore) {
          aStore.clear();
        },
        resolve,
        reject
      )
    );
  },
};
PK
!<P#'#'modules/PushRecord.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
                                  "resource://gre/modules/Messaging.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");


this.EXPORTED_SYMBOLS = ["PushRecord"];

const prefs = Services.prefs.getBranch("dom.push.");

/**
 * The push subscription record, stored in IndexedDB.
 */
function PushRecord(props) {
  this.pushEndpoint = props.pushEndpoint;
  this.scope = props.scope;
  this.originAttributes = props.originAttributes;
  this.pushCount = props.pushCount || 0;
  this.lastPush = props.lastPush || 0;
  this.p256dhPublicKey = props.p256dhPublicKey;
  this.p256dhPrivateKey = props.p256dhPrivateKey;
  this.authenticationSecret = props.authenticationSecret;
  this.systemRecord = !!props.systemRecord;
  this.appServerKey = props.appServerKey;
  this.recentMessageIDs = props.recentMessageIDs;
  this.setQuota(props.quota);
  this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
}

PushRecord.prototype = {
  setQuota(suggestedQuota) {
    if (this.quotaApplies()) {
      let quota = +suggestedQuota;
      this.quota = quota >= 0 ? quota : prefs.getIntPref("maxQuotaPerSubscription");
    } else {
      this.quota = Infinity;
    }
  },

  resetQuota() {
    this.quota = this.quotaApplies() ?
                 prefs.getIntPref("maxQuotaPerSubscription") : Infinity;
  },

  updateQuota(lastVisit) {
    if (this.isExpired() || !this.quotaApplies()) {
      // Ignore updates if the registration is already expired, or isn't
      // subject to quota.
      return;
    }
    if (lastVisit < 0) {
      // If the user cleared their history, but retained the push permission,
      // mark the registration as expired.
      this.quota = 0;
      return;
    }
    if (lastVisit > this.lastPush) {
      // If the user visited the site since the last time we received a
      // notification, reset the quota. `Math.max(0, ...)` ensures the
      // last visit date isn't in the future.
      let daysElapsed =
        Math.max(0, (Date.now() - lastVisit) / 24 / 60 / 60 / 1000);
      this.quota = Math.min(
        Math.round(8 * Math.pow(daysElapsed, -0.8)),
        prefs.getIntPref("maxQuotaPerSubscription")
      );
    }
  },

  receivedPush(lastVisit) {
    this.updateQuota(lastVisit);
    this.pushCount++;
    this.lastPush = Date.now();
  },

  /**
   * Records a message ID sent to this push registration. We track the last few
   * messages sent to each registration to avoid firing duplicate events for
   * unacknowledged messages.
   */
  noteRecentMessageID(id) {
    if (this.recentMessageIDs) {
      this.recentMessageIDs.unshift(id);
    } else {
      this.recentMessageIDs = [id];
    }
    // Drop older message IDs from the end of the list.
    let maxRecentMessageIDs = Math.min(
      this.recentMessageIDs.length,
      Math.max(prefs.getIntPref("maxRecentMessageIDsPerSubscription"), 0)
    );
    this.recentMessageIDs.length = maxRecentMessageIDs || 0;
  },

  hasRecentMessageID(id) {
    return this.recentMessageIDs && this.recentMessageIDs.includes(id);
  },

  reduceQuota() {
    if (!this.quotaApplies()) {
      return;
    }
    this.quota = Math.max(this.quota - 1, 0);
  },

  /**
   * Queries the Places database for the last time a user visited the site
   * associated with a push registration.
   *
   * @returns {Promise} A promise resolved with either the last time the user
   *  visited the site, or `-Infinity` if the site is not in the user's history.
   *  The time is expressed in milliseconds since Epoch.
   */
  async getLastVisit() {
    if (!this.quotaApplies() || this.isTabOpen()) {
      // If the registration isn't subject to quota, or the user already
      // has the site open, skip expensive database queries.
      return Date.now();
    }

    if (AppConstants.MOZ_ANDROID_HISTORY) {
      let result = await EventDispatcher.instance.sendRequestForResult({
        type: "History:GetPrePathLastVisitedTimeMilliseconds",
        prePath: this.uri.prePath,
      });
      return result == 0 ? -Infinity : result;
    }

    // Places History transition types that can fire a
    // `pushsubscriptionchange` event when the user visits a site with expired push
    // registrations. Visits only count if the user sees the origin in the address
    // bar. This excludes embedded resources, downloads, and framed links.
    const QUOTA_REFRESH_TRANSITIONS_SQL = [
      Ci.nsINavHistoryService.TRANSITION_LINK,
      Ci.nsINavHistoryService.TRANSITION_TYPED,
      Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
      Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
      Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY
    ].join(",");

    let db =  await PlacesUtils.promiseDBConnection();
    // We're using a custom query instead of `nsINavHistoryQueryOptions`
    // because the latter doesn't expose a way to filter by transition type:
    // `setTransitions` performs a logical "and," but we want an "or." We
    // also avoid an unneeded left join with favicons, and an `ORDER BY`
    // clause that emits a suboptimal index warning.
    let rows = await db.executeCached(
      `SELECT MAX(visit_date) AS lastVisit
       FROM moz_places p
       JOIN moz_historyvisits ON p.id = place_id
       WHERE rev_host = get_unreversed_host(:host || '.') || '.'
         AND url BETWEEN :prePath AND :prePath || X'FFFF'
         AND visit_type IN (${QUOTA_REFRESH_TRANSITIONS_SQL})
      `,
      {
        // Restrict the query to all pages for this origin.
        host: this.uri.host,
        prePath: this.uri.prePath,
      }
    );

    if (!rows.length) {
      return -Infinity;
    }
    // Places records times in microseconds.
    let lastVisit = rows[0].getResultByName("lastVisit");

    return lastVisit / 1000;
  },

  isTabOpen() {
    let windows = Services.wm.getEnumerator("navigator:browser");
    while (windows.hasMoreElements()) {
      let window = windows.getNext();
      if (window.closed || PrivateBrowsingUtils.isWindowPrivate(window)) {
        continue;
      }
      // `gBrowser` on Desktop; `BrowserApp` on Fennec.
      let tabs = window.gBrowser ? window.gBrowser.tabContainer.children :
                 window.BrowserApp.tabs;
      for (let tab of tabs) {
        // `linkedBrowser` on Desktop; `browser` on Fennec.
        let tabURI = (tab.linkedBrowser || tab.browser).currentURI;
        if (tabURI.prePath == this.uri.prePath) {
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Indicates whether the registration can deliver push messages to its
   * associated service worker. System subscriptions are exempt from the
   * permission check.
   */
  hasPermission() {
    if (this.systemRecord || prefs.getBoolPref("testing.ignorePermission", false)) {
      return true;
    }
    let permission = Services.perms.testExactPermissionFromPrincipal(
      this.principal, "desktop-notification");
    return permission == Ci.nsIPermissionManager.ALLOW_ACTION;
  },

  quotaChanged() {
    if (!this.hasPermission()) {
      return Promise.resolve(false);
    }
    return this.getLastVisit()
      .then(lastVisit => lastVisit > this.lastPush);
  },

  quotaApplies() {
    return !this.systemRecord;
  },

  isExpired() {
    return this.quota === 0;
  },

  matchesOriginAttributes(pattern) {
    if (this.systemRecord) {
      return false;
    }
    return ChromeUtils.originAttributesMatchPattern(
      this.principal.originAttributes, pattern);
  },

  hasAuthenticationSecret() {
    return !!this.authenticationSecret &&
           this.authenticationSecret.byteLength == 16;
  },

  matchesAppServerKey(key) {
    if (!this.appServerKey) {
      return !key;
    }
    if (!key) {
      return false;
    }
    return this.appServerKey.length === key.length &&
           this.appServerKey.every((value, index) => value === key[index]);
  },

  toSubscription() {
    return {
      endpoint: this.pushEndpoint,
      lastPush: this.lastPush,
      pushCount: this.pushCount,
      p256dhKey: this.p256dhPublicKey,
      p256dhPrivateKey: this.p256dhPrivateKey,
      authenticationSecret: this.authenticationSecret,
      appServerKey: this.appServerKey,
      quota: this.quotaApplies() ? this.quota : -1,
      systemRecord: this.systemRecord,
    };
  },
};

// Define lazy getters for the principal and scope URI. IndexedDB can't store
// `nsIPrincipal` objects, so we keep them in a private weak map.
var principals = new WeakMap();
Object.defineProperties(PushRecord.prototype, {
  principal: {
    get() {
      if (this.systemRecord) {
        return Services.scriptSecurityManager.getSystemPrincipal();
      }
      let principal = principals.get(this);
      if (!principal) {
        let uri = Services.io.newURI(this.scope);
        // Allow tests to omit origin attributes.
        let originSuffix = this.originAttributes || "";
        let originAttributes =
        principal = Services.scriptSecurityManager.createCodebasePrincipal(uri,
          ChromeUtils.createOriginAttributesFromOrigin(originSuffix));
        principals.set(this, principal);
      }
      return principal;
    },
    configurable: true,
  },

  uri: {
    get() {
      return this.principal.URI;
    },
    configurable: true,
  },
});
PK
!<modules/PushService.jsm/* jshint moz: true, esnext: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

Cu.import("resource://gre/modules/AppConstants.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");

const {
  PushCrypto,
  getCryptoParams,
  CryptoError,
} = Cu.import("resource://gre/modules/PushCrypto.jsm");
const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");

const CONNECTION_PROTOCOLS = (function() {
  if ('android' != AppConstants.MOZ_WIDGET_TOOLKIT) {
    const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm");
    const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm");
    return [PushServiceWebSocket, PushServiceHttp2];
  } else {
    const {PushServiceAndroidGCM} = Cu.import("resource://gre/modules/PushServiceAndroidGCM.jsm");
    return [PushServiceAndroidGCM];
  }
})();

XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier",
                                   "@mozilla.org/push/Notifier;1",
                                   "nsIPushNotifier");

this.EXPORTED_SYMBOLS = ["PushService"];

XPCOMUtils.defineLazyGetter(this, "console", () => {
  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushService",
  });
});

const prefs = new Preferences("dom.push.");

const PUSH_SERVICE_UNINIT = 0;
const PUSH_SERVICE_INIT = 1; // No serverURI
const PUSH_SERVICE_ACTIVATING = 2;//activating db
const PUSH_SERVICE_CONNECTION_DISABLE = 3;
const PUSH_SERVICE_ACTIVE_OFFLINE = 4;
const PUSH_SERVICE_RUNNING = 5;

/**
 * State is change only in couple of functions:
 *   init - change state to PUSH_SERVICE_INIT if state was PUSH_SERVICE_UNINIT
 *   changeServerURL - change state to PUSH_SERVICE_ACTIVATING if serverURL
 *                     present or PUSH_SERVICE_INIT if not present.
 *   changeStateConnectionEnabledEvent - it is call on pref change or during
 *                                       the service activation and it can
 *                                       change state to
 *                                       PUSH_SERVICE_CONNECTION_DISABLE
 *   changeStateOfflineEvent - it is called when offline state changes or during
 *                             the service activation and it change state to
 *                             PUSH_SERVICE_ACTIVE_OFFLINE or
 *                             PUSH_SERVICE_RUNNING.
 *   uninit - change state to PUSH_SERVICE_UNINIT.
 **/

// This is for starting and stopping service.
const STARTING_SERVICE_EVENT = 0;
const CHANGING_SERVICE_EVENT = 1;
const STOPPING_SERVICE_EVENT = 2;
const UNINIT_EVENT = 3;

/**
 * Annotates an error with an XPCOM result code. We use this helper
 * instead of `Components.Exception` because the latter can assert in
 * `nsXPCComponents_Exception::HasInstance` when inspected at shutdown.
 */
function errorWithResult(message, result = Cr.NS_ERROR_FAILURE) {
  let error = new Error(message);
  error.result = result;
  return error;
}

/**
 * Copied from ForgetAboutSite.jsm.
 *
 * Returns true if the string passed in is part of the root domain of the
 * current string.  For example, if this is "www.mozilla.org", and we pass in
 * "mozilla.org", this will return true.  It would return false the other way
 * around.
 */
function hasRootDomain(str, aDomain)
{
  let index = str.indexOf(aDomain);
  // If aDomain is not found, we know we do not have it as a root domain.
  if (index == -1)
    return false;

  // If the strings are the same, we obviously have a match.
  if (str == aDomain)
    return true;

  // Otherwise, we have aDomain as our root domain iff the index of aDomain is
  // aDomain.length subtracted from our length and (since we do not have an
  // exact match) the character before the index is a dot or slash.
  let prevChar = str[index - 1];
  return (index == (str.length - aDomain.length)) &&
         (prevChar == "." || prevChar == "/");
}

/**
 * The implementation of the push system. It uses WebSockets
 * (PushServiceWebSocket) to communicate with the server and PushDB (IndexedDB)
 * for persistence.
 */
this.PushService = {
  _service: null,
  _state: PUSH_SERVICE_UNINIT,
  _db: null,
  _options: null,
  _visibleNotifications: new Map(),

  // Callback that is called after attempting to
  // reduce the quota for a record. Used for testing purposes.
  _updateQuotaTestCallback: null,

  // Set of timeout ID of tasks to reduce quota.
  _updateQuotaTimeouts: new Set(),

  // When serverURI changes (this is used for testing), db is cleaned up and a
  // a new db is started. This events must be sequential.
  _stateChangeProcessQueue: null,
  _stateChangeProcessEnqueue: function(op) {
    if (!this._stateChangeProcessQueue) {
      this._stateChangeProcessQueue = Promise.resolve();
    }

    this._stateChangeProcessQueue = this._stateChangeProcessQueue
      .then(op)
      .catch(error => {
        console.error(
          "stateChangeProcessEnqueue: Error transitioning state", error);
        return this._shutdownService();
      })
      .catch(error => {
        console.error(
          "stateChangeProcessEnqueue: Error shutting down service", error);
      });
    return this._stateChangeProcessQueue;
  },

  // Pending request. If a worker try to register for the same scope again, do
  // not send a new registration request. Therefore we need queue of pending
  // register requests. This is the list of scopes which pending registration.
  _pendingRegisterRequest: {},
  _notifyActivated: null,
  _activated: null,
  _checkActivated: function() {
    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return Promise.reject(new Error("Push service not active"));
    } else if (this._state > PUSH_SERVICE_ACTIVATING) {
      return Promise.resolve();
    } else {
      return (this._activated) ? this._activated :
                                 this._activated = new Promise((res, rej) =>
                                   this._notifyActivated = {resolve: res,
                                                            reject: rej});
    }
  },

  _makePendingKey: function(aPageRecord) {
    return aPageRecord.scope + "|" + aPageRecord.originAttributes;
  },

  _lookupOrPutPendingRequest: function(aPageRecord) {
    let key = this._makePendingKey(aPageRecord);
    if (this._pendingRegisterRequest[key]) {
      return this._pendingRegisterRequest[key];
    }

    return this._pendingRegisterRequest[key] = this._registerWithServer(aPageRecord);
  },

  _deletePendingRequest: function(aPageRecord) {
    let key = this._makePendingKey(aPageRecord);
    if (this._pendingRegisterRequest[key]) {
      delete this._pendingRegisterRequest[key];
    }
  },

  _setState: function(aNewState) {
    console.debug("setState()", "new state", aNewState, "old state", this._state);

    if (this._state == aNewState) {
      return;
    }

    if (this._state == PUSH_SERVICE_ACTIVATING) {
      // It is not important what is the new state as soon as we leave
      // PUSH_SERVICE_ACTIVATING
      if (this._notifyActivated) {
        if (aNewState < PUSH_SERVICE_ACTIVATING) {
          this._notifyActivated.reject(new Error("Push service not active"));
        } else {
          this._notifyActivated.resolve();
        }
      }
      this._notifyActivated = null;
      this._activated = null;
    }
    this._state = aNewState;
  },

  _changeStateOfflineEvent: function(offline, calledFromConnEnabledEvent) {
    console.debug("changeStateOfflineEvent()", offline);

    if (this._state < PUSH_SERVICE_ACTIVE_OFFLINE &&
        this._state != PUSH_SERVICE_ACTIVATING &&
        !calledFromConnEnabledEvent) {
      return Promise.resolve();
    }

    if (offline) {
      if (this._state == PUSH_SERVICE_RUNNING) {
        this._service.disconnect();
      }
      this._setState(PUSH_SERVICE_ACTIVE_OFFLINE);
      return Promise.resolve();
    }

    if (this._state == PUSH_SERVICE_RUNNING) {
      // PushService was not in the offline state, but got notification to
      // go online (a offline notification has not been sent).
      // Disconnect first.
      this._service.disconnect();
    }
    return this.getAllUnexpired().then(records => {
      this._setState(PUSH_SERVICE_RUNNING);
      if (records.length > 0) {
        // if there are request waiting
        this._service.connect(records);
      }
    });
  },

  _changeStateConnectionEnabledEvent: function(enabled) {
    console.debug("changeStateConnectionEnabledEvent()", enabled);

    if (this._state < PUSH_SERVICE_CONNECTION_DISABLE &&
        this._state != PUSH_SERVICE_ACTIVATING) {
      return Promise.resolve();
    }

    if (enabled) {
      return this._changeStateOfflineEvent(Services.io.offline, true);
    }

    if (this._state == PUSH_SERVICE_RUNNING) {
      this._service.disconnect();
    }
    this._setState(PUSH_SERVICE_CONNECTION_DISABLE);
    return Promise.resolve();
  },

  // Used for testing.
  changeTestServer(url, options = {}) {
    console.debug("changeTestServer()");

    return this._stateChangeProcessEnqueue(_ => {
      if (this._state < PUSH_SERVICE_ACTIVATING) {
        console.debug("changeTestServer: PushService not activated?");
        return Promise.resolve();
      }

      return this._changeServerURL(url, CHANGING_SERVICE_EVENT, options);
    });
  },

  observe: function observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      /*
       * We need to call uninit() on shutdown to clean up things that modules
       * aren't very good at automatically cleaning up, so we don't get shutdown
       * leaks on browser shutdown.
       */
      case "quit-application":
        this.uninit();
        break;
      case "network:offline-status-changed":
        this._stateChangeProcessEnqueue(_ =>
          this._changeStateOfflineEvent(aData === "offline", false)
        );
        break;

      case "nsPref:changed":
        if (aData == "dom.push.serverURL") {
          console.debug("observe: dom.push.serverURL changed for websocket",
                prefs.get("serverURL"));
          this._stateChangeProcessEnqueue(_ =>
            this._changeServerURL(prefs.get("serverURL"),
                                  CHANGING_SERVICE_EVENT)
          );

        } else if (aData == "dom.push.connection.enabled") {
          this._stateChangeProcessEnqueue(_ =>
            this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))
          );
        }
        break;

      case "idle-daily":
        this._dropExpiredRegistrations().catch(error => {
          console.error("Failed to drop expired registrations on idle", error);
        });
        break;

      case "perm-changed":
        this._onPermissionChange(aSubject, aData).catch(error => {
          console.error("onPermissionChange: Error updating registrations:",
            error);
        })
        break;

      case "clear-origin-attributes-data":
        this._clearOriginData(aData).catch(error => {
          console.error("clearOriginData: Error clearing origin data:", error);
        });
        break;
    }
  },

  _clearOriginData: function(data) {
    console.log("clearOriginData()");

    if (!data) {
      return Promise.resolve();
    }

    let pattern = JSON.parse(data);
    return this._dropRegistrationsIf(record =>
      record.matchesOriginAttributes(pattern));
  },

  /**
   * Sends an unregister request to the server in the background. If the
   * service is not connected, this function is a no-op.
   *
   * @param {PushRecord} record The record to unregister.
   * @param {Number} reason An `nsIPushErrorReporter` unsubscribe reason,
   *  indicating why this record was removed.
   */
  _backgroundUnregister(record, reason) {
    console.debug("backgroundUnregister()");

    if (!this._service.isConnected() || !record) {
      return;
    }

    console.debug("backgroundUnregister: Notifying server", record);
    this._sendUnregister(record, reason).then(() => {
      gPushNotifier.notifySubscriptionModified(record.scope, record.principal);
    }).catch(e => {
      console.error("backgroundUnregister: Error notifying server", e);
    });
  },

  _findService: function(serverURL) {
    console.debug("findService()");

    let uri;
    let service;

    if (!serverURL) {
      console.warn("findService: No dom.push.serverURL found");
      return [];
    }

    try {
      uri = Services.io.newURI(serverURL);
    } catch (e) {
      console.warn("findService: Error creating valid URI from",
        "dom.push.serverURL", serverURL);
      return [];
    }

    for (let connProtocol of CONNECTION_PROTOCOLS) {
      if (connProtocol.validServerURI(uri)) {
        service = connProtocol;
        break;
      }
    }
    return [service, uri];
  },

  _changeServerURL: function(serverURI, event, options = {}) {
    console.debug("changeServerURL()");

    switch(event) {
      case UNINIT_EVENT:
        return this._stopService(event);

      case STARTING_SERVICE_EVENT:
      {
        let [service, uri] = this._findService(serverURI);
        if (!service) {
          this._setState(PUSH_SERVICE_INIT);
          return Promise.resolve();
        }
        return this._startService(service, uri, options)
          .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))
          );
      }
      case CHANGING_SERVICE_EVENT:
        let [service, uri] = this._findService(serverURI);
        if (service) {
          if (this._state == PUSH_SERVICE_INIT) {
            this._setState(PUSH_SERVICE_ACTIVATING);
            // The service has not been running - start it.
            return this._startService(service, uri, options)
              .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))
              );

          } else {
            this._setState(PUSH_SERVICE_ACTIVATING);
            // If we already had running service - stop service, start the new
            // one and check connection.enabled and offline state(offline state
            // check is called in changeStateConnectionEnabledEvent function)
            return this._stopService(CHANGING_SERVICE_EVENT)
              .then(_ =>
                 this._startService(service, uri, options)
              )
              .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))
              );

          }
        } else {
          if (this._state == PUSH_SERVICE_INIT) {
            return Promise.resolve();

          } else {
            // The new serverUri is empty or misconfigured - stop service.
            this._setState(PUSH_SERVICE_INIT);
            return this._stopService(STOPPING_SERVICE_EVENT);
          }
        }
      default:
        console.error("Unexpected event in _changeServerURL", event);
        return Promise.reject(new Error(`Unexpected event ${event}`));
    }
  },

  /**
   * PushService initialization is divided into 4 parts:
   * init() - start listening for quit-application and serverURL changes.
   *          state is change to PUSH_SERVICE_INIT
   * startService() - if serverURL is present this function is called. It starts
   *                  listening for broadcasted messages, starts db and
   *                  PushService connection (WebSocket).
   *                  state is change to PUSH_SERVICE_ACTIVATING.
   * startObservers() - start other observers.
   * changeStateConnectionEnabledEvent  - checks prefs and offline state.
   *                                      It changes state to:
   *                                        PUSH_SERVICE_RUNNING,
   *                                        PUSH_SERVICE_ACTIVE_OFFLINE or
   *                                        PUSH_SERVICE_CONNECTION_DISABLE.
   */
  init: function(options = {}) {
    console.debug("init()");

    if (this._state > PUSH_SERVICE_UNINIT) {
      return;
    }

    this._setState(PUSH_SERVICE_ACTIVATING);

    prefs.observe("serverURL", this);
    Services.obs.addObserver(this, "quit-application");

    if (options.serverURI) {
      // this is use for xpcshell test.

      this._stateChangeProcessEnqueue(_ =>
        this._changeServerURL(options.serverURI, STARTING_SERVICE_EVENT, options));

    } else {
      // This is only used for testing. Different tests require connecting to
      // slightly different URLs.
      this._stateChangeProcessEnqueue(_ =>
        this._changeServerURL(prefs.get("serverURL"), STARTING_SERVICE_EVENT));
    }
  },

  _startObservers: function() {
    console.debug("startObservers()");

    if (this._state != PUSH_SERVICE_ACTIVATING) {
      return;
    }

    Services.obs.addObserver(this, "clear-origin-attributes-data");

    // The offline-status-changed event is used to know
    // when to (dis)connect. It may not fire if the underlying OS changes
    // networks; in such a case we rely on timeout.
    Services.obs.addObserver(this, "network:offline-status-changed");

    // Used to monitor if the user wishes to disable Push.
    prefs.observe("connection.enabled", this);

    // Prunes expired registrations and notifies dormant service workers.
    Services.obs.addObserver(this, "idle-daily");

    // Prunes registrations for sites for which the user revokes push
    // permissions.
    Services.obs.addObserver(this, "perm-changed");
  },

  _startService(service, serverURI, options) {
    console.debug("startService()");

    if (this._state != PUSH_SERVICE_ACTIVATING) {
      return Promise.reject();
    }

    this._service = service;

    this._db = options.db;
    if (!this._db) {
      this._db = this._service.newPushDB();
    }

    return this._service.init(options, this, serverURI)
      .then(() => {
        this._startObservers();
        return this._dropExpiredRegistrations();
      });
  },

  /**
   * PushService uninitialization is divided into 3 parts:
   * stopObservers() - stot observers started in startObservers.
   * stopService() - It stops listening for broadcasted messages, stops db and
   *                 PushService connection (WebSocket).
   *                 state is changed to PUSH_SERVICE_INIT.
   * uninit() - stop listening for quit-application and serverURL changes.
   *            state is change to PUSH_SERVICE_UNINIT
   */
  _stopService: function(event) {
    console.debug("stopService()");

    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return Promise.resolve();
    }

    this._stopObservers();

    this._service.disconnect();
    this._service.uninit();
    this._service = null;

    this._updateQuotaTimeouts.forEach((timeoutID) => clearTimeout(timeoutID));
    this._updateQuotaTimeouts.clear();

    if (!this._db) {
      return Promise.resolve();
    }
    if (event == UNINIT_EVENT) {
      // If it is uninitialized just close db.
      this._db.close();
      this._db = null;
      return Promise.resolve();
    }

    return this.dropUnexpiredRegistrations()
       .then(_ => {
         this._db.close();
         this._db = null;
       }, err => {
         this._db.close();
         this._db = null;
       });
  },

  _stopObservers: function() {
    console.debug("stopObservers()");

    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return;
    }

    prefs.ignore("connection.enabled", this);

    Services.obs.removeObserver(this, "network:offline-status-changed");
    Services.obs.removeObserver(this, "clear-origin-attributes-data");
    Services.obs.removeObserver(this, "idle-daily");
    Services.obs.removeObserver(this, "perm-changed");
  },

  _shutdownService() {
    let promiseChangeURL = this._changeServerURL("", UNINIT_EVENT);
    this._setState(PUSH_SERVICE_UNINIT);
    console.debug("shutdownService: shutdown complete!");
    return promiseChangeURL;
  },

  uninit: function() {
    console.debug("uninit()");

    if (this._state == PUSH_SERVICE_UNINIT) {
      return;
    }

    prefs.ignore("serverURL", this);
    Services.obs.removeObserver(this, "quit-application");

    this._stateChangeProcessEnqueue(_ => this._shutdownService());
  },

  /**
   * Drops all active registrations and notifies the associated service
   * workers. This function is called when the user switches Push servers,
   * or when the server invalidates all existing registrations.
   *
   * We ignore expired registrations because they're already handled in other
   * code paths. Registrations that expired after exceeding their quotas are
   * evicted at startup, or on the next `idle-daily` event. Registrations that
   * expired because the user revoked the notification permission are evicted
   * once the permission is reinstated.
   */
  dropUnexpiredRegistrations: function() {
    return this._db.clearIf(record => {
      if (record.isExpired()) {
        return false;
      }
      this._notifySubscriptionChangeObservers(record);
      return true;
    });
  },

  _notifySubscriptionChangeObservers: function(record) {
    if (!record) {
      return;
    }
    gPushNotifier.notifySubscriptionChange(record.scope, record.principal);
  },

  /**
   * Drops a registration and notifies the associated service worker. If the
   * registration does not exist, this function is a no-op.
   *
   * @param {String} keyID The registration ID to remove.
   * @returns {Promise} Resolves once the worker has been notified.
   */
  dropRegistrationAndNotifyApp: function(aKeyID) {
    return this._db.delete(aKeyID)
      .then(record => this._notifySubscriptionChangeObservers(record));
  },

  /**
   * Replaces an existing registration and notifies the associated service
   * worker.
   *
   * @param {String} aOldKey The registration ID to replace.
   * @param {PushRecord} aNewRecord The new record.
   * @returns {Promise} Resolves once the worker has been notified.
   */
  updateRegistrationAndNotifyApp: function(aOldKey, aNewRecord) {
    return this.updateRecordAndNotifyApp(aOldKey, _ => aNewRecord);
  },
  /**
   * Updates a registration and notifies the associated service worker.
   *
   * @param {String} keyID The registration ID to update.
   * @param {Function} updateFunc Returns the updated record.
   * @returns {Promise} Resolves with the updated record once the worker
   *  has been notified.
   */
  updateRecordAndNotifyApp: function(aKeyID, aUpdateFunc) {
    return this._db.update(aKeyID, aUpdateFunc)
      .then(record => {
        this._notifySubscriptionChangeObservers(record);
        return record;
      });
  },

  ensureCrypto: function(record) {
    if (record.hasAuthenticationSecret() &&
        record.p256dhPublicKey &&
        record.p256dhPrivateKey) {
      return Promise.resolve(record);
    }

    let keygen = Promise.resolve([]);
    if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
      keygen = PushCrypto.generateKeys();
    }
    // We do not have a encryption key. so we need to generate it. This
    // is only going to happen on db upgrade from version 4 to higher.
    return keygen
      .then(([pubKey, privKey]) => {
        return this.updateRecordAndNotifyApp(record.keyID, record => {
          if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
            record.p256dhPublicKey = pubKey;
            record.p256dhPrivateKey = privKey;
          }
          if (!record.hasAuthenticationSecret()) {
            record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
          }
          return record;
        });
      }, error => {
        return this.dropRegistrationAndNotifyApp(record.keyID).then(
          () => Promise.reject(error));
      });
  },

  /**
   * Dispatches an incoming message to a service worker, recalculating the
   * quota for the associated push registration. If the quota is exceeded,
   * the registration and message will be dropped, and the worker will not
   * be notified.
   *
   * @param {String} keyID The push registration ID.
   * @param {String} messageID The message ID, used to report service worker
   *  delivery failures. For Web Push messages, this is the version. If empty,
   *  failures will not be reported.
   * @param {Object} headers The encryption headers.
   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
   * @param {Function} updateFunc A function that receives the existing
   *  registration record as its argument, and returns a new record. If the
   *  function returns `null` or `undefined`, the record will not be updated.
   *  `PushServiceWebSocket` uses this to drop incoming updates with older
   *  versions.
   * @returns {Promise} Resolves with an `nsIPushErrorReporter` ack status
   *  code, indicating whether the message was delivered successfully.
   */
  receivedPushMessage(keyID, messageID, headers, data, updateFunc) {
    console.debug("receivedPushMessage()");

    return this._updateRecordAfterPush(keyID, updateFunc).then(record => {
      if (record.quotaApplies()) {
        // Update quota after the delay, at which point
        // we check for visible notifications.
        let timeoutID = setTimeout(_ =>
          {
            this._updateQuota(keyID);
            if (!this._updateQuotaTimeouts.delete(timeoutID)) {
              console.debug("receivedPushMessage: quota update timeout missing?");
            }
          }, prefs.get("quotaUpdateDelay"));
        this._updateQuotaTimeouts.add(timeoutID);
      }
      return this._decryptAndNotifyApp(record, messageID, headers, data);
    }).catch(error => {
      console.error("receivedPushMessage: Error notifying app", error);
      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
    });
  },

  /**
   * Updates a registration record after receiving a push message.
   *
   * @param {String} keyID The push registration ID.
   * @param {Function} updateFunc The function passed to `receivedPushMessage`.
   * @returns {Promise} Resolves with the updated record, or rejects if the
   *  record was not updated.
   */
  _updateRecordAfterPush(keyID, updateFunc) {
    return this.getByKeyID(keyID).then(record => {
      if (!record) {
        throw new Error("No record for key ID " + keyID);
      }
      return record.getLastVisit().then(lastVisit => {
        // As a special case, don't notify the service worker if the user
        // cleared their history.
        if (!isFinite(lastVisit)) {
          throw new Error("Ignoring message sent to unvisited origin");
        }
        return lastVisit;
      }).then(lastVisit => {
        // Update the record, resetting the quota if the user has visited the
        // site since the last push.
        return this._db.update(keyID, record => {
          let newRecord = updateFunc(record);
          if (!newRecord) {
            return null;
          }
          // Because `unregister` is advisory only, we can still receive messages
          // for stale Simple Push registrations from the server. To work around
          // this, we check if the record has expired before *and* after updating
          // the quota.
          if (newRecord.isExpired()) {
            return null;
          }
          newRecord.receivedPush(lastVisit);
          return newRecord;
        });
      });
    }).then(record => {
      gPushNotifier.notifySubscriptionModified(record.scope,
                                               record.principal);
      return record;
    });
  },

  /**
   * Decrypts an incoming message and notifies the associated service worker.
   *
   * @param {PushRecord} record The receiving registration.
   * @param {String} messageID The message ID.
   * @param {Object} headers The encryption headers.
   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
   * @returns {Promise} Resolves with an ack status code.
   */
  _decryptAndNotifyApp(record, messageID, headers, data) {
    return PushCrypto.decrypt(record.p256dhPrivateKey, record.p256dhPublicKey,
                              record.authenticationSecret, headers, data)
      .then(
        message => this._notifyApp(record, messageID, message),
        error => {
          console.warn("decryptAndNotifyApp: Error decrypting message",
            record.scope, messageID, error);

          let message = error.format(record.scope);
          gPushNotifier.notifyError(record.scope, record.principal, message,
                                    Ci.nsIScriptError.errorFlag);
          return Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR;
        });
  },

  _updateQuota: function(keyID) {
    console.debug("updateQuota()");

    this._db.update(keyID, record => {
      // Record may have expired from an earlier quota update.
      if (record.isExpired()) {
        console.debug(
          "updateQuota: Trying to update quota for expired record", record);
        return null;
      }
      // If there are visible notifications, don't apply the quota penalty
      // for the message.
      if (record.uri && !this._visibleNotifications.has(record.uri.prePath)) {
        record.reduceQuota();
      }
      return record;
    }).then(record => {
      if (record.isExpired()) {
        // Drop the registration in the background. If the user returns to the
        // site, the service worker will be notified on the next `idle-daily`
        // event.
        this._backgroundUnregister(record,
          Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
      } else {
        gPushNotifier.notifySubscriptionModified(record.scope,
                                                 record.principal);
      }
      if (this._updateQuotaTestCallback) {
        // Callback so that test may be notified when the quota update is complete.
        this._updateQuotaTestCallback();
      }
    }).catch(error => {
      console.debug("updateQuota: Error while trying to update quota", error);
    });
  },

  notificationForOriginShown(origin) {
    console.debug("notificationForOriginShown()", origin);
    let count;
    if (this._visibleNotifications.has(origin)) {
      count = this._visibleNotifications.get(origin);
    } else {
      count = 0;
    }
    this._visibleNotifications.set(origin, count + 1);
  },

  notificationForOriginClosed(origin) {
    console.debug("notificationForOriginClosed()", origin);
    let count;
    if (this._visibleNotifications.has(origin)) {
      count = this._visibleNotifications.get(origin);
    } else {
      console.debug("notificationForOriginClosed: closing notification that has not been shown?");
      return;
    }
    if (count > 1) {
      this._visibleNotifications.set(origin, count - 1);
    } else {
      this._visibleNotifications.delete(origin);
    }
  },

  reportDeliveryError(messageID, reason) {
    console.debug("reportDeliveryError()", messageID, reason);
    if (this._state == PUSH_SERVICE_RUNNING &&
        this._service.isConnected()) {

      // Only report errors if we're initialized and connected.
      this._service.reportDeliveryError(messageID, reason);
    }
  },

  _notifyApp(aPushRecord, messageID, message) {
    if (!aPushRecord || !aPushRecord.scope ||
        aPushRecord.originAttributes === undefined) {
      console.error("notifyApp: Invalid record", aPushRecord);
      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
    }

    console.debug("notifyApp()", aPushRecord.scope);

    // If permission has been revoked, trash the message.
    if (!aPushRecord.hasPermission()) {
      console.warn("notifyApp: Missing push permission", aPushRecord);
      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
    }

    let payload = ArrayBuffer.isView(message) ?
                  new Uint8Array(message.buffer) : message;

    if (aPushRecord.quotaApplies()) {
      // Don't record telemetry for chrome push messages.
      Services.telemetry.getHistogramById("PUSH_API_NOTIFY").add();
    }

    if (payload) {
      gPushNotifier.notifyPushWithData(aPushRecord.scope,
                                       aPushRecord.principal,
                                       messageID, payload.length, payload);
    } else {
      gPushNotifier.notifyPush(aPushRecord.scope, aPushRecord.principal,
                               messageID);
    }

    return Ci.nsIPushErrorReporter.ACK_DELIVERED;
  },

  getByKeyID: function(aKeyID) {
    return this._db.getByKeyID(aKeyID);
  },

  getAllUnexpired: function() {
    return this._db.getAllUnexpired();
  },

  _sendRequest(action, ...params) {
    if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
      return Promise.reject(new Error("Push service disabled"));
    }
    if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
      return Promise.reject(new Error("Push service offline"));
    }
    // Ensure the backend is ready. `getByPageRecord` already checks this, but
    // we need to check again here in case the service was restarted in the
    // meantime.
    return this._checkActivated().then(_ => {
      switch (action) {
        case "register":
          return this._service.register(...params);
        case "unregister":
          return this._service.unregister(...params);
      }
      return Promise.reject(new Error("Unknown request type: " + action));
    });
  },

  /**
   * Called on message from the child process. aPageRecord is an object sent by
   * the push manager, identifying the sending page and other fields.
   */
  _registerWithServer: function(aPageRecord) {
    console.debug("registerWithServer()", aPageRecord);

    return this._sendRequest("register", aPageRecord)
      .then(record => this._onRegisterSuccess(record),
            err => this._onRegisterError(err))
      .then(record => {
        this._deletePendingRequest(aPageRecord);
        gPushNotifier.notifySubscriptionModified(record.scope,
                                                 record.principal);
        return record.toSubscription();
      }, err => {
        this._deletePendingRequest(aPageRecord);
        throw err;
     });
  },

  _sendUnregister(aRecord, aReason) {
    return this._sendRequest("unregister", aRecord, aReason);
  },

  /**
   * Exceptions thrown in _onRegisterSuccess are caught by the promise obtained
   * from _service.request, causing the promise to be rejected instead.
   */
  _onRegisterSuccess: function(aRecord) {
    console.debug("_onRegisterSuccess()");

    return this._db.put(aRecord)
      .catch(error => {
        // Unable to save. Destroy the subscription in the background.
        this._backgroundUnregister(aRecord,
                                   Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL);
        throw error;
      });
  },

  /**
   * Exceptions thrown in _onRegisterError are caught by the promise obtained
   * from _service.request, causing the promise to be rejected instead.
   */
  _onRegisterError: function(reply) {
    console.debug("_onRegisterError()");

    if (!reply.error) {
      console.warn("onRegisterError: Called without valid error message!",
        reply);
      throw new Error("Registration error");
    }
    throw reply.error;
  },

  notificationsCleared() {
    this._visibleNotifications.clear();
  },

  _getByPageRecord(pageRecord) {
    return this._checkActivated().then(_ =>
      this._db.getByIdentifiers(pageRecord)
    );
  },

  register: function(aPageRecord) {
    console.debug("register()", aPageRecord);

    let keyPromise;
    if (aPageRecord.appServerKey) {
      let keyView = new Uint8Array(aPageRecord.appServerKey);
      keyPromise = PushCrypto.validateAppServerKey(keyView)
        .catch(error => {
          // Normalize Web Crypto exceptions. `nsIPushService` will forward the
          // error result to the DOM API implementation in `PushManager.cpp` or
          // `Push.js`, which will convert it to the correct `DOMException`.
          throw errorWithResult("Invalid app server key",
                                Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
        });
    } else {
      keyPromise = Promise.resolve(null);
    }

    return Promise.all([
      keyPromise,
      this._getByPageRecord(aPageRecord),
    ]).then(([appServerKey, record]) => {
      aPageRecord.appServerKey = appServerKey;
      if (!record) {
        return this._lookupOrPutPendingRequest(aPageRecord);
      }
      if (!record.matchesAppServerKey(appServerKey)) {
        throw errorWithResult("Mismatched app server key",
                              Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR);
      }
      if (record.isExpired()) {
        return record.quotaChanged().then(isChanged => {
          if (isChanged) {
            // If the user revisited the site, drop the expired push
            // registration and re-register.
            return this.dropRegistrationAndNotifyApp(record.keyID);
          }
          throw new Error("Push subscription expired");
        }).then(_ => this._lookupOrPutPendingRequest(aPageRecord));
      }
      return record.toSubscription();
    });
  },

  /**
   * Called on message from the child process.
   *
   * Why is the record being deleted from the local database before the server
   * is told?
   *
   * Unregistration is for the benefit of the app and the AppServer
   * so that the AppServer does not keep pinging a channel the UserAgent isn't
   * watching The important part of the transaction in this case is left to the
   * app, to tell its server of the unregistration.  Even if the request to the
   * PushServer were to fail, it would not affect correctness of the protocol,
   * and the server GC would just clean up the channelID/subscription
   * eventually.  Since the appserver doesn't ping it, no data is lost.
   *
   * If rather we were to unregister at the server and update the database only
   * on success: If the server receives the unregister, and deletes the
   * channelID/subscription, but the response is lost because of network
   * failure, the application is never informed. In addition the application may
   * retry the unregister when it fails due to timeout (websocket) or any other
   * reason at which point the server will say it does not know of this
   * unregistration.  We'll have to make the registration/unregistration phases
   * have retries and attempts to resend messages from the server, and have the
   * client acknowledge. On a server, data is cheap, reliable notification is
   * not.
   */
  unregister: function(aPageRecord) {
    console.debug("unregister()", aPageRecord);

    return this._getByPageRecord(aPageRecord)
      .then(record => {
        if (record === undefined) {
          return false;
        }

        let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL;
        return Promise.all([
          this._sendUnregister(record, reason),
          this._db.delete(record.keyID).then(record => {
            if (record) {
              gPushNotifier.notifySubscriptionModified(record.scope,
                                                       record.principal);
            }
          }),
        ]).then(([success]) => success);
      });
  },

  clear: function(info) {
    return this._checkActivated()
      .then(_ => {
        return this._dropRegistrationsIf(record =>
          info.domain == "*" ||
          (record.uri && hasRootDomain(record.uri.prePath, info.domain))
        );
      })
      .catch(e => {
        console.warn("clear: Error dropping subscriptions for domain",
          info.domain, e);
        return Promise.resolve();
      });
  },

  registration: function(aPageRecord) {
    console.debug("registration()");

    return this._getByPageRecord(aPageRecord)
      .then(record => {
        if (!record) {
          return null;
        }
        if (record.isExpired()) {
          return record.quotaChanged().then(isChanged => {
            if (isChanged) {
              return this.dropRegistrationAndNotifyApp(record.keyID).then(_ => null);
            }
            return null;
          });
        }
        return record.toSubscription();
      });
  },

  _dropExpiredRegistrations: function() {
    console.debug("dropExpiredRegistrations()");

    return this._db.getAllExpired().then(records => {
      return Promise.all(records.map(record =>
        record.quotaChanged().then(isChanged => {
          if (isChanged) {
            // If the user revisited the site, drop the expired push
            // registration and notify the associated service worker.
            return this.dropRegistrationAndNotifyApp(record.keyID);
          }
        }).catch(error => {
          console.error("dropExpiredRegistrations: Error dropping registration",
            record.keyID, error);
        })
      ));
    });
  },

  _onPermissionChange: function(subject, data) {
    console.debug("onPermissionChange()");

    if (data == "cleared") {
      return this._clearPermissions();
    }

    let permission = subject.QueryInterface(Ci.nsIPermission);
    if (permission.type != "desktop-notification") {
      return Promise.resolve();
    }

    return this._updatePermission(permission, data);
  },

  _clearPermissions() {
    console.debug("clearPermissions()");

    return this._db.clearIf(record => {
      if (!record.quotaApplies()) {
        // Only drop registrations that are subject to quota.
        return false;
      }
      this._backgroundUnregister(record,
        Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
      return true;
    });
  },

  _updatePermission: function(permission, type) {
    console.debug("updatePermission()");

    let isAllow = permission.capability ==
                  Ci.nsIPermissionManager.ALLOW_ACTION;
    let isChange = type == "added" || type == "changed";

    if (isAllow && isChange) {
      // Permission set to "allow". Drop all expired registrations for this
      // site, notify the associated service workers, and reset the quota
      // for active registrations.
      return this._forEachPrincipal(
        permission.principal,
        (record, cursor) => this._permissionAllowed(record, cursor)
      );
    } else if (isChange || (isAllow && type == "deleted")) {
      // Permission set to "block" or "always ask," or "allow" permission
      // removed. Expire all registrations for this site.
      return this._forEachPrincipal(
        permission.principal,
        (record, cursor) => this._permissionDenied(record, cursor)
      );
    }

    return Promise.resolve();
  },

  _forEachPrincipal: function(principal, callback) {
    return this._db.forEachOrigin(
      principal.URI.prePath,
      ChromeUtils.originAttributesToSuffix(principal.originAttributes),
      callback
    );
  },

  /**
   * The update function called for each registration record if the push
   * permission is revoked. We only expire the record so we can notify the
   * service worker as soon as the permission is reinstated. If we just
   * deleted the record, the worker wouldn't be notified until the next visit
   * to the site.
   *
   * @param {PushRecord} record The record to expire.
   * @param {IDBCursor} cursor The IndexedDB cursor.
   */
  _permissionDenied: function(record, cursor) {
    console.debug("permissionDenied()");

    if (!record.quotaApplies() || record.isExpired()) {
      // Ignore already-expired records.
      return;
    }
    // Drop the registration in the background.
    this._backgroundUnregister(record,
      Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
    record.setQuota(0);
    cursor.update(record);
  },

  /**
   * The update function called for each registration record if the push
   * permission is granted. If the record has expired, it will be dropped;
   * otherwise, its quota will be reset to the default value.
   *
   * @param {PushRecord} record The record to update.
   * @param {IDBCursor} cursor The IndexedDB cursor.
   */
  _permissionAllowed(record, cursor) {
    console.debug("permissionAllowed()");

    if (!record.quotaApplies()) {
      return;
    }
    if (record.isExpired()) {
      // If the registration has expired, drop and notify the worker
      // unconditionally.
      this._notifySubscriptionChangeObservers(record);
      cursor.delete();
      return;
    }
    record.resetQuota();
    cursor.update(record);
  },

  /**
   * Drops all matching registrations from the database. Notifies the
   * associated service workers if permission is granted, and removes
   * unexpired registrations from the server.
   *
   * @param {Function} predicate A function called for each record.
   * @returns {Promise} Resolves once the registrations have been dropped.
   */
  _dropRegistrationsIf(predicate) {
    return this._db.clearIf(record => {
      if (!predicate(record)) {
        return false;
      }
      if (record.hasPermission()) {
        // "Clear Recent History" and the Forget button remove permissions
        // before clearing registrations, but it's possible for the worker to
        // resubscribe if the "dom.push.testing.ignorePermission" pref is set.
        this._notifySubscriptionChangeObservers(record);
      }
      if (!record.isExpired()) {
        // Only unregister active registrations, since we already told the
        // server about expired ones.
        this._backgroundUnregister(record,
                                   Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL);
      }
      return true;
    });
  },
};
PK
!<aamodules/PushServiceHttp2.jsm/* jshint moz: true, esnext: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.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/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/Timer.jsm");

const {
  PushCrypto,
  concatArray,
} = Cu.import("resource://gre/modules/PushCrypto.jsm");

this.EXPORTED_SYMBOLS = ["PushServiceHttp2"];

XPCOMUtils.defineLazyGetter(this, "console", () => {
  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushServiceHttp2",
  });
});

const prefs = Services.prefs.getBranch("dom.push.");

const kPUSHHTTP2DB_DB_NAME = "pushHttp2";
const kPUSHHTTP2DB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";

/**
 * A proxy between the PushService and connections listening for incoming push
 * messages. The PushService can silence messages from the connections by
 * setting PushSubscriptionListener._pushService to null. This is required
 * because it can happen that there is an outstanding push message that will
 * be send on OnStopRequest but the PushService may not be interested in these.
 * It's easier to stop listening than to have checks at specific points.
 */
var PushSubscriptionListener = function(pushService, uri) {
  console.debug("PushSubscriptionListener()");
  this._pushService = pushService;
  this.uri = uri;
};

PushSubscriptionListener.prototype = {

  QueryInterface: function (aIID) {
    if (aIID.equals(Ci.nsIHttpPushListener) ||
        aIID.equals(Ci.nsIStreamListener)) {
      return this;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  getInterface: function(aIID) {
    return this.QueryInterface(aIID);
  },

  onStartRequest: function(aRequest, aContext) {
    console.debug("PushSubscriptionListener: onStartRequest()");
    // We do not do anything here.
  },

  onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
    console.debug("PushSubscriptionListener: onDataAvailable()");
    // Nobody should send data, but just to be sure, otherwise necko will
    // complain.
    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                        .createInstance(Ci.nsIScriptableInputStream);

    inputStream.init(aStream);
    var data = inputStream.read(aCount);
  },

  onStopRequest: function(aRequest, aContext, aStatusCode) {
    console.debug("PushSubscriptionListener: onStopRequest()");
    if (!this._pushService) {
        return;
    }

    this._pushService.connOnStop(aRequest,
                                 Components.isSuccessCode(aStatusCode),
                                 this.uri);
  },

  onPush: function(associatedChannel, pushChannel) {
    console.debug("PushSubscriptionListener: onPush()");
    var pushChannelListener = new PushChannelListener(this);
    pushChannel.asyncOpen2(pushChannelListener);
  },

  disconnect: function() {
    this._pushService = null;
  }
};

/**
 * The listener for pushed messages. The message data is collected in
 * OnDataAvailable and send to the app in OnStopRequest.
 */
var PushChannelListener = function(pushSubscriptionListener) {
  console.debug("PushChannelListener()");
  this._mainListener = pushSubscriptionListener;
  this._message = [];
  this._ackUri = null;
};

PushChannelListener.prototype = {

  onStartRequest: function(aRequest, aContext) {
    this._ackUri = aRequest.URI.spec;
  },

  onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
    console.debug("PushChannelListener: onDataAvailable()");

    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/binaryinputstream;1"]
                        .createInstance(Ci.nsIBinaryInputStream);

    inputStream.setInputStream(aStream);
    let chunk = new ArrayBuffer(aCount);
    inputStream.readArrayBuffer(aCount, chunk);
    this._message.push(chunk);
  },

  onStopRequest: function(aRequest, aContext, aStatusCode) {
    console.debug("PushChannelListener: onStopRequest()", "status code",
      aStatusCode);
    if (Components.isSuccessCode(aStatusCode) &&
        this._mainListener &&
        this._mainListener._pushService) {
      let headers = {
        encryption_key: getHeaderField(aRequest, "Encryption-Key"),
        crypto_key: getHeaderField(aRequest, "Crypto-Key"),
        encryption: getHeaderField(aRequest, "Encryption"),
        encoding: getHeaderField(aRequest, "Content-Encoding"),
      };
      let msg = concatArray(this._message);

      this._mainListener._pushService._pushChannelOnStop(this._mainListener.uri,
                                                         this._ackUri,
                                                         headers,
                                                         msg);
    }
  }
};

function getHeaderField(aRequest, name) {
  try {
    return aRequest.getRequestHeader(name);
  } catch(e) {
    // getRequestHeader can throw.
    return null;
  }
}

var PushServiceDelete = function(resolve, reject) {
  this._resolve = resolve;
  this._reject = reject;
};

PushServiceDelete.prototype = {

  onStartRequest: function(aRequest, aContext) {},

  onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
    // Nobody should send data, but just to be sure, otherwise necko will
    // complain.
    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                        .createInstance(Ci.nsIScriptableInputStream);

    inputStream.init(aStream);
    var data = inputStream.read(aCount);
  },

  onStopRequest: function(aRequest, aContext, aStatusCode) {

    if (Components.isSuccessCode(aStatusCode)) {
       this._resolve();
    } else {
       this._reject(new Error("Error removing subscription: " + aStatusCode));
    }
  }
};

var SubscriptionListener = function(aSubInfo, aResolve, aReject,
                                    aServerURI, aPushServiceHttp2) {
  console.debug("SubscriptionListener()");
  this._subInfo = aSubInfo;
  this._resolve = aResolve;
  this._reject = aReject;
  this._data = '';
  this._serverURI = aServerURI;
  this._service = aPushServiceHttp2;
  this._ctime = Date.now();
  this._retryTimeoutID = null;
};

SubscriptionListener.prototype = {

  onStartRequest: function(aRequest, aContext) {},

  onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
    console.debug("SubscriptionListener: onDataAvailable()");

    // We do not expect any data, but necko will complain if we do not consume
    // it.
    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                        .createInstance(Ci.nsIScriptableInputStream);

    inputStream.init(aStream);
    this._data.concat(inputStream.read(aCount));
  },

  onStopRequest: function(aRequest, aContext, aStatus) {
    console.debug("SubscriptionListener: onStopRequest()");

    // Check if pushService is still active.
    if (!this._service.hasmainPushService()) {
      this._reject(new Error("Push service unavailable"));
      return;
    }

    if (!Components.isSuccessCode(aStatus)) {
      this._reject(new Error("Error listening for messages: " + aStatus));
      return;
    }

    var statusCode = aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus;

    if (Math.floor(statusCode / 100) == 5) {
      if (this._subInfo.retries < prefs.getIntPref("http2.maxRetries")) {
        this._subInfo.retries++;
        var retryAfter = retryAfterParser(aRequest);
        this._retryTimeoutID = setTimeout(_ =>
          {
            this._reject(
              {
                retry: true,
                subInfo: this._subInfo
              });
            this._service.removeListenerPendingRetry(this);
            this._retryTimeoutID = null;
          }, retryAfter);
        this._service.addListenerPendingRetry(this);
      } else {
        this._reject(new Error("Unexpected server response: " + statusCode));
      }
      return;
    } else if (statusCode != 201) {
      this._reject(new Error("Unexpected server response: " + statusCode));
      return;
    }

    var subscriptionUri;
    try {
      subscriptionUri = aRequest.getResponseHeader("location");
    } catch (err) {
      this._reject(new Error("Missing Location header"));
      return;
    }

    console.debug("onStopRequest: subscriptionUri", subscriptionUri);

    var linkList;
    try {
      linkList = aRequest.getResponseHeader("link");
    } catch (err) {
      this._reject(new Error("Missing Link header"));
      return;
    }

    var linkParserResult;
    try {
      linkParserResult = linkParser(linkList, this._serverURI);
    } catch (e) {
      this._reject(e);
      return;
    }

    if (!subscriptionUri) {
      this._reject(new Error("Invalid Location header"));
      return;
    }
    try {
      let uriTry = Services.io.newURI(subscriptionUri);
    } catch (e) {
      console.error("onStopRequest: Invalid subscription URI",
        subscriptionUri);
      this._reject(new Error("Invalid subscription endpoint: " +
        subscriptionUri));
      return;
    }

    let reply = new PushRecordHttp2({
      subscriptionUri: subscriptionUri,
      pushEndpoint: linkParserResult.pushEndpoint,
      pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
      scope: this._subInfo.record.scope,
      originAttributes: this._subInfo.record.originAttributes,
      systemRecord: this._subInfo.record.systemRecord,
      appServerKey: this._subInfo.record.appServerKey,
      ctime: Date.now(),
    });

    this._resolve(reply);
  },

  abortRetry: function() {
    if (this._retryTimeoutID != null) {
      clearTimeout(this._retryTimeoutID);
      this._retryTimeoutID = null;
    } else {
      console.debug("SubscriptionListener.abortRetry: aborting non-existent retry?");
    }
  },
};

function retryAfterParser(aRequest) {
  var retryAfter = 0;
  try {
    var retryField = aRequest.getResponseHeader("retry-after");
    if (isNaN(retryField)) {
      retryAfter = Date.parse(retryField) - (new Date().getTime());
    } else {
      retryAfter = parseInt(retryField, 10) * 1000;
    }
    retryAfter = (retryAfter > 0) ? retryAfter : 0;
  } catch(e) {}

  return retryAfter;
}

function linkParser(linkHeader, serverURI) {

  var linkList = linkHeader.split(',');
  if ((linkList.length < 1)) {
    throw new Error("Invalid Link header");
  }

  var pushEndpoint;
  var pushReceiptEndpoint;

  linkList.forEach(link => {
    var linkElems = link.split(';');

    if (linkElems.length == 2) {
      if (linkElems[1].trim() === 'rel="urn:ietf:params:push"') {
        pushEndpoint = linkElems[0].substring(linkElems[0].indexOf('<') + 1,
                                              linkElems[0].indexOf('>'));

      } else if (linkElems[1].trim() === 'rel="urn:ietf:params:push:receipt"') {
        pushReceiptEndpoint = linkElems[0].substring(linkElems[0].indexOf('<') + 1,
                                                     linkElems[0].indexOf('>'));
      }
    }
  });

  console.debug("linkParser: pushEndpoint", pushEndpoint);
  console.debug("linkParser: pushReceiptEndpoint", pushReceiptEndpoint);
  // Missing pushReceiptEndpoint is allowed.
  if (!pushEndpoint) {
    throw new Error("Missing push endpoint");
  }

  var pushURI = Services.io.newURI(pushEndpoint, null, serverURI);
  var pushReceiptURI;
  if (pushReceiptEndpoint) {
    pushReceiptURI = Services.io.newURI(pushReceiptEndpoint, null,
                                        serverURI);
  }

  return {
    pushEndpoint: pushURI.spec,
    pushReceiptEndpoint: (pushReceiptURI) ? pushReceiptURI.spec : "",
  };
}

/**
 * The implementation of the WebPush.
 */
this.PushServiceHttp2 = {
  _mainPushService: null,
  _serverURI: null,

  // Keep information about all connections, e.g. the channel, listener...
  _conns: {},
  _started: false,

  // Set of SubscriptionListeners that are pending a subscription retry attempt.
  _listenersPendingRetry: new Set(),

  newPushDB: function() {
    return new PushDB(kPUSHHTTP2DB_DB_NAME,
                      kPUSHHTTP2DB_DB_VERSION,
                      kPUSHHTTP2DB_STORE_NAME,
                      "subscriptionUri",
                      PushRecordHttp2);
  },

  hasmainPushService: function() {
    return this._mainPushService !== null;
  },

  validServerURI: function(serverURI) {
    if (serverURI.scheme == "http") {
      return !!prefs.getBoolPref("testing.allowInsecureServerURL", false);
    }
    return serverURI.scheme == "https";
  },

  connect: function(subscriptions) {
    this.startConnections(subscriptions);
  },

  isConnected: function() {
    return this._mainPushService != null;
  },

  disconnect: function() {
    this._shutdownConnections(false);
  },

  _makeChannel: function(aUri) {
    var chan = NetUtil.newChannel({uri: aUri, loadUsingSystemPrincipal: true})
                      .QueryInterface(Ci.nsIHttpChannel);

    var loadGroup = Cc["@mozilla.org/network/load-group;1"]
                      .createInstance(Ci.nsILoadGroup);
    chan.loadGroup = loadGroup;
    return chan;
  },

  /**
   * Subscribe new resource.
   */
  register: function(aRecord) {
    console.debug("subscribeResource()");

    return this._subscribeResourceInternal({
      record: aRecord,
      retries: 0
    })
    .then(result =>
      PushCrypto.generateKeys()
        .then(([publicKey, privateKey]) => {
        result.p256dhPublicKey = publicKey;
        result.p256dhPrivateKey = privateKey;
        result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
        this._conns[result.subscriptionUri] = {
          channel: null,
          listener: null,
          countUnableToConnect: 0,
          lastStartListening: 0,
          retryTimerID: 0,
        };
        this._listenForMsgs(result.subscriptionUri);
        return result;
      })
    );
  },

  _subscribeResourceInternal: function(aSubInfo) {
    console.debug("subscribeResourceInternal()");

    return new Promise((resolve, reject) => {
      var listener = new SubscriptionListener(aSubInfo,
                                              resolve,
                                              reject,
                                              this._serverURI,
                                              this);

      var chan = this._makeChannel(this._serverURI.spec);
      chan.requestMethod = "POST";
      chan.asyncOpen2(listener);
    })
    .catch(err => {
      if ("retry" in err) {
        return this._subscribeResourceInternal(err.subInfo);
      } else {
        throw err;
      }
    })
  },

  _deleteResource: function(aUri) {

    return new Promise((resolve,reject) => {
      var chan = this._makeChannel(aUri);
      chan.requestMethod = "DELETE";
      chan.asyncOpen2(new PushServiceDelete(resolve, reject));
    });
  },

  /**
   * Unsubscribe the resource with a subscription uri aSubscriptionUri.
   * We can't do anything about it if it fails, so we don't listen for response.
   */
  _unsubscribeResource: function(aSubscriptionUri) {
    console.debug("unsubscribeResource()");

    return this._deleteResource(aSubscriptionUri);
  },

  /**
   * Start listening for messages.
   */
  _listenForMsgs: function(aSubscriptionUri) {
    console.debug("listenForMsgs()", aSubscriptionUri);
    if (!this._conns[aSubscriptionUri]) {
      console.warn("listenForMsgs: We do not have this subscription",
        aSubscriptionUri);
      return;
    }

    var chan = this._makeChannel(aSubscriptionUri);
    var conn = {};
    conn.channel = chan;
    var listener = new PushSubscriptionListener(this, aSubscriptionUri);
    conn.listener = listener;

    chan.notificationCallbacks = listener;

    try {
      chan.asyncOpen2(listener);
    } catch (e) {
      console.error("listenForMsgs: Error connecting to push server.",
        "asyncOpen2 failed", e);
      conn.listener.disconnect();
      chan.cancel(Cr.NS_ERROR_ABORT);
      this._retryAfterBackoff(aSubscriptionUri, -1);
      return;
    }

    this._conns[aSubscriptionUri].lastStartListening = Date.now();
    this._conns[aSubscriptionUri].channel = conn.channel;
    this._conns[aSubscriptionUri].listener = conn.listener;

  },

  _ackMsgRecv: function(aAckUri) {
    console.debug("ackMsgRecv()", aAckUri);
    return this._deleteResource(aAckUri);
  },

  init: function(aOptions, aMainPushService, aServerURL) {
    console.debug("init()");
    this._mainPushService = aMainPushService;
    this._serverURI = aServerURL;

    return Promise.resolve();
  },

  _retryAfterBackoff: function(aSubscriptionUri, retryAfter) {
    console.debug("retryAfterBackoff()");

    var resetRetryCount = prefs.getIntPref("http2.reset_retry_count_after_ms");
    // If it was running for some time, reset retry counter.
    if ((Date.now() - this._conns[aSubscriptionUri].lastStartListening) >
        resetRetryCount) {
      this._conns[aSubscriptionUri].countUnableToConnect = 0;
    }

    let maxRetries = prefs.getIntPref("http2.maxRetries");
    if (this._conns[aSubscriptionUri].countUnableToConnect >= maxRetries) {
      this._shutdownSubscription(aSubscriptionUri);
      this._resubscribe(aSubscriptionUri);
      return;
    }

    if (retryAfter !== -1) {
      // This is a 5xx response.
      this._conns[aSubscriptionUri].countUnableToConnect++;
      this._conns[aSubscriptionUri].retryTimerID =
        setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter);
      return;
    }

    retryAfter = prefs.getIntPref("http2.retryInterval") *
      Math.pow(2, this._conns[aSubscriptionUri].countUnableToConnect);

    retryAfter = retryAfter * (0.8 + Math.random() * 0.4); // add +/-20%.

    this._conns[aSubscriptionUri].countUnableToConnect++;
    this._conns[aSubscriptionUri].retryTimerID =
      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter);

    console.debug("retryAfterBackoff: Retry in", retryAfter);
  },

  // Close connections.
  _shutdownConnections: function(deleteInfo) {
    console.debug("shutdownConnections()");

    for (let subscriptionUri in this._conns) {
      if (this._conns[subscriptionUri]) {
        if (this._conns[subscriptionUri].listener) {
          this._conns[subscriptionUri].listener._pushService = null;
        }

        if (this._conns[subscriptionUri].channel) {
          try {
            this._conns[subscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
          } catch (e) {}
        }
        this._conns[subscriptionUri].listener = null;
        this._conns[subscriptionUri].channel = null;

        if (this._conns[subscriptionUri].retryTimerID > 0) {
          clearTimeout(this._conns[subscriptionUri].retryTimerID);
        }

        if (deleteInfo) {
          delete this._conns[subscriptionUri];
        }
      }
    }
  },

  // Start listening if subscriptions present.
  startConnections: function(aSubscriptions) {
    console.debug("startConnections()", aSubscriptions.length);

    for (let i = 0; i < aSubscriptions.length; i++) {
      let record = aSubscriptions[i];
      this._mainPushService.ensureCrypto(record).then(record => {
        this._startSingleConnection(record);
      }, error => {
        console.error("startConnections: Error updating record",
          record.keyID, error);
      });
    }
  },

  _startSingleConnection: function(record) {
    console.debug("_startSingleConnection()");
    if (typeof this._conns[record.subscriptionUri] != "object") {
      this._conns[record.subscriptionUri] = {channel: null,
                                             listener: null,
                                             countUnableToConnect: 0,
                                             retryTimerID: 0};
    }
    if (!this._conns[record.subscriptionUri].conn) {
      this._listenForMsgs(record.subscriptionUri);
    }
  },

  // Close connection and notify apps that subscription are gone.
  _shutdownSubscription: function(aSubscriptionUri) {
    console.debug("shutdownSubscriptions()");

    if (typeof this._conns[aSubscriptionUri] == "object") {
      if (this._conns[aSubscriptionUri].listener) {
        this._conns[aSubscriptionUri].listener._pushService = null;
      }

      if (this._conns[aSubscriptionUri].channel) {
        try {
          this._conns[aSubscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
        } catch (e) {}
      }
      delete this._conns[aSubscriptionUri];
    }
  },

  uninit: function() {
    console.debug("uninit()");
    this._abortPendingSubscriptionRetries();
    this._shutdownConnections(true);
    this._mainPushService = null;
  },

  _abortPendingSubscriptionRetries: function() {
    this._listenersPendingRetry.forEach((listener) => listener.abortRetry());
    this._listenersPendingRetry.clear();
  },

  unregister: function(aRecord) {
    this._shutdownSubscription(aRecord.subscriptionUri);
    return this._unsubscribeResource(aRecord.subscriptionUri);
  },

  reportDeliveryError: function(messageID, reason) {
    console.warn("reportDeliveryError: Ignoring message delivery error",
      messageID, reason);
  },

  /** Push server has deleted subscription.
   *  Re-subscribe - if it succeeds send update db record and send
   *                 pushsubscriptionchange,
   *               - on error delete record and send pushsubscriptionchange
   *  TODO: maybe pushsubscriptionerror will be included.
   */
  _resubscribe: function(aSubscriptionUri) {
    this._mainPushService.getByKeyID(aSubscriptionUri)
      .then(record => this.register(record)
        .then(recordNew => {
          if (this._mainPushService) {
            this._mainPushService
                .updateRegistrationAndNotifyApp(aSubscriptionUri, recordNew)
                .catch(Cu.reportError);
          }
        }, error => {
          if (this._mainPushService) {
            this._mainPushService
                .dropRegistrationAndNotifyApp(aSubscriptionUri)
                .catch(Cu.reportError);
          }
        })
      );
  },

  connOnStop: function(aRequest, aSuccess,
                       aSubscriptionUri) {
    console.debug("connOnStop() succeeded", aSuccess);

    var conn = this._conns[aSubscriptionUri];
    if (!conn) {
      // there is no connection description that means that we closed
      // connection, so do nothing. But we should have already deleted
      // the listener.
      return;
    }

    conn.channel = null;
    conn.listener = null;

    if (!aSuccess) {
      this._retryAfterBackoff(aSubscriptionUri, -1);

    } else if (Math.floor(aRequest.responseStatus / 100) == 5) {
      var retryAfter = retryAfterParser(aRequest);
      this._retryAfterBackoff(aSubscriptionUri, retryAfter);

    } else if (Math.floor(aRequest.responseStatus / 100) == 4) {
      this._shutdownSubscription(aSubscriptionUri);
      this._resubscribe(aSubscriptionUri);
    } else if (Math.floor(aRequest.responseStatus / 100) == 2) { // This should be 204
      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), 0);
    } else {
      this._retryAfterBackoff(aSubscriptionUri, -1);
    }
  },

  addListenerPendingRetry: function(aListener) {
    this._listenersPendingRetry.add(aListener);
  },

  removeListenerPendingRetry: function(aListener) {
    if (!this._listenersPendingRetry.remove(aListener)) {
      console.debug("removeListenerPendingRetry: listener not in list?");
    }
  },

  _pushChannelOnStop: function(aUri, aAckUri, aHeaders, aMessage) {
    console.debug("pushChannelOnStop()");

    this._mainPushService.receivedPushMessage(
      aUri, "", aHeaders, aMessage, record => {
        // Always update the stored record.
        return record;
      }
    )
    .then(_ => this._ackMsgRecv(aAckUri))
    .catch(err => {
      console.error("pushChannelOnStop: Error receiving message",
        err);
    });
  },
};

function PushRecordHttp2(record) {
  PushRecord.call(this, record);
  this.subscriptionUri = record.subscriptionUri;
  this.pushReceiptEndpoint = record.pushReceiptEndpoint;
}

PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
  keyID: {
    get() {
      return this.subscriptionUri;
    },
  },
});

PushRecordHttp2.prototype.toSubscription = function() {
  let subscription = PushRecord.prototype.toSubscription.call(this);
  subscription.pushReceiptEndpoint = this.pushReceiptEndpoint;
  return subscription;
};
PK
!<ʆʆ modules/PushServiceWebSocket.jsm/* jshint moz: true, esnext: true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

Cu.import("resource://gre/modules/AppConstants.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");

const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");

const kPUSHWSDB_DB_NAME = "pushapi";
const kPUSHWSDB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHWSDB_STORE_NAME = "pushapi";

// WebSocket close code sent by the server to indicate that the client should
// not automatically reconnect.
const kBACKOFF_WS_STATUS_CODE = 4774;

// Maps ack statuses, unsubscribe reasons, and delivery error reasons to codes
// included in request payloads.
const kACK_STATUS_TO_CODE = {
  [Ci.nsIPushErrorReporter.ACK_DELIVERED]: 100,
  [Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR]: 101,
  [Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED]: 102,
};

const kUNREGISTER_REASON_TO_CODE = {
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL]: 200,
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED]: 201,
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED]: 202,
};

const kDELIVERY_REASON_TO_CODE = {
  [Ci.nsIPushErrorReporter.DELIVERY_UNCAUGHT_EXCEPTION]: 301,
  [Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION]: 302,
  [Ci.nsIPushErrorReporter.DELIVERY_INTERNAL_ERROR]: 303,
};

const prefs = new Preferences("dom.push.");

this.EXPORTED_SYMBOLS = ["PushServiceWebSocket"];

XPCOMUtils.defineLazyGetter(this, "console", () => {
  let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushServiceWebSocket",
  });
});

/**
 * A proxy between the PushService and the WebSocket. The listener is used so
 * that the PushService can silence messages from the WebSocket by setting
 * PushWebSocketListener._pushService to null. This is required because
 * a WebSocket can continue to send messages or errors after it has been
 * closed but the PushService may not be interested in these. It's easier to
 * stop listening than to have checks at specific points.
 */
var PushWebSocketListener = function(pushService) {
  this._pushService = pushService;
};

PushWebSocketListener.prototype = {
  onStart: function(context) {
    if (!this._pushService) {
        return;
    }
    this._pushService._wsOnStart(context);
  },

  onStop: function(context, statusCode) {
    if (!this._pushService) {
        return;
    }
    this._pushService._wsOnStop(context, statusCode);
  },

  onAcknowledge: function(context, size) {
    // EMPTY
  },

  onBinaryMessageAvailable: function(context, message) {
    // EMPTY
  },

  onMessageAvailable: function(context, message) {
    if (!this._pushService) {
        return;
    }
    this._pushService._wsOnMessageAvailable(context, message);
  },

  onServerClose: function(context, aStatusCode, aReason) {
    if (!this._pushService) {
        return;
    }
    this._pushService._wsOnServerClose(context, aStatusCode, aReason);
  }
};

// websocket states
// websocket is off
const STATE_SHUT_DOWN = 0;
// Websocket has been opened on client side, waiting for successful open.
// (_wsOnStart)
const STATE_WAITING_FOR_WS_START = 1;
// Websocket opened, hello sent, waiting for server reply (_handleHelloReply).
const STATE_WAITING_FOR_HELLO = 2;
// Websocket operational, handshake completed, begin protocol messaging.
const STATE_READY = 3;

this.PushServiceWebSocket = {
  _mainPushService: null,
  _serverURI: null,

  newPushDB: function() {
    return new PushDB(kPUSHWSDB_DB_NAME,
                      kPUSHWSDB_DB_VERSION,
                      kPUSHWSDB_STORE_NAME,
                      "channelID",
                      PushRecordWebSocket);
  },

  disconnect: function() {
    this._shutdownWS();
  },

  observe: function(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") {
      this._onUAIDChanged();
    } else if (aTopic == "timer-callback") {
      this._onTimerFired(aSubject);
    }
  },

  /**
   * Handles a UAID change. Unlike reconnects, we cancel all pending requests
   * after disconnecting. Existing subscriptions stored in IndexedDB will be
   * dropped on reconnect.
   */
  _onUAIDChanged() {
    console.debug("onUAIDChanged()");

    this._shutdownWS();
    this._startBackoffTimer();
  },

  /** Handles a ping, backoff, or request timeout timer event. */
  _onTimerFired(timer) {
    console.debug("onTimerFired()");

    if (timer == this._pingTimer) {
      this._sendPing();
      return;
    }

    if (timer == this._backoffTimer) {
      console.debug("onTimerFired: Reconnecting after backoff");
      this._beginWSSetup();
      return;
    }

    if (timer == this._requestTimeoutTimer) {
      this._timeOutRequests();
      return;
    }
  },

  /**
   * Sends a ping to the server. Bypasses the request queue, but starts the
   * request timeout timer. If the socket is already closed, or the server
   * does not respond within the timeout, the client will reconnect.
   */
  _sendPing() {
    console.debug("sendPing()");

    this._startRequestTimeoutTimer();
    try {
      this._wsSendMessage({});
      this._lastPingTime = Date.now();
    } catch (e) {
      console.debug("sendPing: Error sending ping", e);
      this._reconnect();
    }
  },

  /** Times out any pending requests. */
  _timeOutRequests() {
    console.debug("timeOutRequests()");

    if (!this._hasPendingRequests()) {
      // Cancel the repeating timer and exit early if we aren't waiting for
      // pongs or requests.
      this._requestTimeoutTimer.cancel();
      return;
    }

    let now = Date.now();

    // Set to true if at least one request timed out, or we're still waiting
    // for a pong after the request timeout.
    let requestTimedOut = false;

    if (this._lastPingTime > 0 &&
        now - this._lastPingTime > this._requestTimeout) {

      console.debug("timeOutRequests: Did not receive pong in time");
      requestTimedOut = true;

    } else {
      for (let [key, request] of this._pendingRequests) {
        let duration = now - request.ctime;
        // If any of the registration requests time out, all the ones after it
        // also made to fail, since we are going to be disconnecting the
        // socket.
        requestTimedOut |= duration > this._requestTimeout;
        if (requestTimedOut) {
          request.reject(new Error("Request timed out: " + key));
          this._pendingRequests.delete(key);
        }
      }
    }

    // The most likely reason for a pong or registration request timing out is
    // that the socket has disconnected. Best to reconnect.
    if (requestTimedOut) {
      this._reconnect();
    }
  },

  validServerURI: function(serverURI) {
    if (serverURI.scheme == "ws") {
      return !!prefs.get("testing.allowInsecureServerURL");
    }
    return serverURI.scheme == "wss";
  },

  get _UAID() {
    return prefs.get("userAgentID");
  },

  set _UAID(newID) {
    if (typeof(newID) !== "string") {
      console.warn("Got invalid, non-string UAID", newID,
        "Not updating userAgentID");
      return;
    }
    console.debug("New _UAID", newID);
    prefs.set("userAgentID", newID);
  },

  _ws: null,
  _pendingRequests: new Map(),
  _currentState: STATE_SHUT_DOWN,
  _requestTimeout: 0,
  _requestTimeoutTimer: null,
  _retryFailCount: 0,

  /**
   * According to the WS spec, servers should immediately close the underlying
   * TCP connection after they close a WebSocket. This causes wsOnStop to be
   * called with error NS_BASE_STREAM_CLOSED. Since the client has to keep the
   * WebSocket up, it should try to reconnect. But if the server closes the
   * WebSocket because it wants the client to back off, then the client
   * shouldn't re-establish the connection. If the server sends the backoff
   * close code, this field will be set to true in wsOnServerClose. It is
   * checked in wsOnStop.
   */
  _skipReconnect: false,

  /** Indicates whether the server supports Web Push-style message delivery. */
  _dataEnabled: false,

  /**
   * The last time the client sent a ping to the server. If non-zero, keeps the
   * request timeout timer active. Reset to zero when the server responds with
   * a pong or pending messages.
   */
  _lastPingTime: 0,

  /**
   * A one-shot timer used to ping the server, to avoid timing out idle
   * connections. Reset to the ping interval on each incoming message.
   */
  _pingTimer: null,

  /** A one-shot timer fired after the reconnect backoff period. */
  _backoffTimer: null,

  /**
   * Sends a message to the Push Server through an open websocket.
   * typeof(msg) shall be an object
   */
  _wsSendMessage: function(msg) {
    if (!this._ws) {
      console.warn("wsSendMessage: No WebSocket initialized.",
        "Cannot send a message");
      return;
    }
    msg = JSON.stringify(msg);
    console.debug("wsSendMessage: Sending message", msg);
    this._ws.sendMsg(msg);
  },

  init: function(options, mainPushService, serverURI) {
    console.debug("init()");

    this._mainPushService = mainPushService;
    this._serverURI = serverURI;

    // Override the default WebSocket factory function. The returned object
    // must be null or satisfy the nsIWebSocketChannel interface. Used by
    // the tests to provide a mock WebSocket implementation.
    if (options.makeWebSocket) {
      this._makeWebSocket = options.makeWebSocket;
    }

    this._requestTimeout = prefs.get("requestTimeout");

    return Promise.resolve();
  },

  _reconnect: function () {
    console.debug("reconnect()");
    this._shutdownWS(false);
    this._startBackoffTimer();
  },

  _shutdownWS: function(shouldCancelPending = true) {
    console.debug("shutdownWS()");

    if (this._currentState == STATE_READY) {
      prefs.ignore("userAgentID", this);
    }

    this._currentState = STATE_SHUT_DOWN;
    this._skipReconnect = false;

    if (this._wsListener) {
      this._wsListener._pushService = null;
    }
    try {
        this._ws.close(0, null);
    } catch (e) {}
    this._ws = null;

    this._lastPingTime = 0;

    if (this._pingTimer) {
      this._pingTimer.cancel();
    }

    if (shouldCancelPending) {
      this._cancelPendingRequests();
    }

    if (this._notifyRequestQueue) {
      this._notifyRequestQueue();
      this._notifyRequestQueue = null;
    }
  },

  uninit: function() {
    // All pending requests (ideally none) are dropped at this point. We
    // shouldn't have any applications performing registration/unregistration
    // or receiving notifications.
    this._shutdownWS();

    if (this._backoffTimer) {
      this._backoffTimer.cancel();
    }
    if (this._requestTimeoutTimer) {
      this._requestTimeoutTimer.cancel();
    }

    this._mainPushService = null;

    this._dataEnabled = false;
  },

  /**
   * How retries work: If the WS is closed due to a socket error,
   * _startBackoffTimer() is called. The retry timer is started and when
   * it times out, beginWSSetup() is called again.
   *
   * If we are in the middle of a timeout (i.e. waiting), but
   * a register/unregister is called, we don't want to wait around anymore.
   * _sendRequest will automatically call beginWSSetup(), which will cancel the
   * timer. In addition since the state will have changed, even if a pending
   * timer event comes in (because the timer fired the event before it was
   * cancelled), so the connection won't be reset.
   */
  _startBackoffTimer() {
    console.debug("startBackoffTimer()");

    // Calculate new timeout, but cap it to pingInterval.
    let retryTimeout = prefs.get("retryBaseInterval") *
                       Math.pow(2, this._retryFailCount);
    retryTimeout = Math.min(retryTimeout, prefs.get("pingInterval"));

    this._retryFailCount++;

    console.debug("startBackoffTimer: Retry in", retryTimeout,
      "Try number", this._retryFailCount);

    if (!this._backoffTimer) {
      this._backoffTimer = Cc["@mozilla.org/timer;1"]
                               .createInstance(Ci.nsITimer);
    }
    this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /** Indicates whether we're waiting for pongs or requests. */
  _hasPendingRequests() {
    return this._lastPingTime > 0 || this._pendingRequests.size > 0;
  },

  /**
   * Starts the request timeout timer unless we're already waiting for a pong
   * or register request.
   */
  _startRequestTimeoutTimer() {
    if (this._hasPendingRequests()) {
      return;
    }
    if (!this._requestTimeoutTimer) {
      this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"]
                                    .createInstance(Ci.nsITimer);
    }
    this._requestTimeoutTimer.init(this,
                                   this._requestTimeout,
                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
  },

  /** Starts or resets the ping timer. */
  _startPingTimer() {
    if (!this._pingTimer) {
      this._pingTimer = Cc["@mozilla.org/timer;1"]
                          .createInstance(Ci.nsITimer);
    }
    this._pingTimer.init(this, prefs.get("pingInterval"),
                         Ci.nsITimer.TYPE_ONE_SHOT);
  },

  _makeWebSocket: function(uri) {
    if (!prefs.get("connection.enabled")) {
      console.warn("makeWebSocket: connection.enabled is not set to true.",
        "Aborting.");
      return null;
    }
    if (Services.io.offline) {
      console.warn("makeWebSocket: Network is offline.");
      return null;
    }
    let contractId = uri.scheme == "ws" ?
                     "@mozilla.org/network/protocol;1?name=ws" :
                     "@mozilla.org/network/protocol;1?name=wss";
    let socket = Cc[contractId].createInstance(Ci.nsIWebSocketChannel);

    socket.initLoadInfo(null, // aLoadingNode
                        Services.scriptSecurityManager.getSystemPrincipal(),
                        null, // aTriggeringPrincipal
                        Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                        Ci.nsIContentPolicy.TYPE_WEBSOCKET);

    return socket;
  },

  _beginWSSetup: function() {
    console.debug("beginWSSetup()");
    if (this._currentState != STATE_SHUT_DOWN) {
      console.error("_beginWSSetup: Not in shutdown state! Current state",
        this._currentState);
      return;
    }

    // Stop any pending reconnects scheduled for the near future.
    if (this._backoffTimer) {
      this._backoffTimer.cancel();
    }

    let uri = this._serverURI;
    if (!uri) {
      return;
    }
    let socket = this._makeWebSocket(uri);
    if (!socket) {
      return;
    }
    this._ws = socket.QueryInterface(Ci.nsIWebSocketChannel);

    console.debug("beginWSSetup: Connecting to", uri.spec);
    this._wsListener = new PushWebSocketListener(this);
    this._ws.protocol = "push-notification";

    try {
      // Grab a wakelock before we open the socket to ensure we don't go to
      // sleep before connection the is opened.
      this._ws.asyncOpen(uri, uri.spec, 0, this._wsListener, null);
      this._currentState = STATE_WAITING_FOR_WS_START;
    } catch(e) {
      console.error("beginWSSetup: Error opening websocket.",
        "asyncOpen failed", e);
      this._reconnect();
    }
  },

  connect: function(records) {
    console.debug("connect()");
    // Check to see if we need to do anything.
    if (records.length > 0) {
      this._beginWSSetup();
    }
  },

  isConnected: function() {
    return !!this._ws;
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleHelloReply: function(reply) {
    console.debug("handleHelloReply()");
    if (this._currentState != STATE_WAITING_FOR_HELLO) {
      console.error("handleHelloReply: Unexpected state", this._currentState,
        "(expected STATE_WAITING_FOR_HELLO)");
      this._shutdownWS();
      return;
    }

    if (typeof reply.uaid !== "string") {
      console.error("handleHelloReply: Received invalid UAID", reply.uaid);
      this._shutdownWS();
      return;
    }

    if (reply.uaid === "") {
      console.error("handleHelloReply: Received empty UAID");
      this._shutdownWS();
      return;
    }

    // To avoid sticking extra large values sent by an evil server into prefs.
    if (reply.uaid.length > 128) {
      console.error("handleHelloReply: UAID received from server was too long",
        reply.uaid);
      this._shutdownWS();
      return;
    }

    let sendRequests = () => {
      if (this._notifyRequestQueue) {
        this._notifyRequestQueue();
        this._notifyRequestQueue = null;
      }
      this._sendPendingRequests();
    };

    function finishHandshake() {
      this._UAID = reply.uaid;
      this._currentState = STATE_READY;
      prefs.observe("userAgentID", this);

      this._dataEnabled = !!reply.use_webpush;
      if (this._dataEnabled) {
        this._mainPushService.getAllUnexpired().then(records =>
          Promise.all(records.map(record =>
            this._mainPushService.ensureCrypto(record).catch(error => {
              console.error("finishHandshake: Error updating record",
                record.keyID, error);
            })
          ))
        ).then(sendRequests);
      } else {
        sendRequests();
      }
    }

    // By this point we've got a UAID from the server that we are ready to
    // accept.
    //
    // We unconditionally drop all existing registrations and notify service
    // workers if we receive a new UAID. This ensures we expunge all stale
    // registrations if the `userAgentID` pref is reset.
    if (this._UAID != reply.uaid) {
      console.debug("handleHelloReply: Received new UAID");

      this._mainPushService.dropUnexpiredRegistrations()
          .then(finishHandshake.bind(this));

      return;
    }

    // otherwise we are good to go
    finishHandshake.bind(this)();
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleRegisterReply: function(reply) {
    console.debug("handleRegisterReply()");

    let tmp = this._takeRequestForReply(reply);
    if (!tmp) {
      return;
    }

    if (reply.status == 200) {
      try {
        Services.io.newURI(reply.pushEndpoint);
      }
      catch (e) {
        tmp.reject(new Error("Invalid push endpoint: " + reply.pushEndpoint));
        return;
      }

      let record = new PushRecordWebSocket({
        channelID: reply.channelID,
        pushEndpoint: reply.pushEndpoint,
        scope: tmp.record.scope,
        originAttributes: tmp.record.originAttributes,
        version: null,
        systemRecord: tmp.record.systemRecord,
        appServerKey: tmp.record.appServerKey,
        ctime: Date.now(),
      });
      tmp.resolve(record);
    } else {
      console.error("handleRegisterReply: Unexpected server response", reply);
      tmp.reject(new Error("Wrong status code for register reply: " +
        reply.status));
    }
  },

  _handleUnregisterReply(reply) {
    console.debug("handleUnregisterReply()");

    let request = this._takeRequestForReply(reply);
    if (!request) {
      return;
    }

    let success = reply.status === 200;
    request.resolve(success);
  },

  _handleDataUpdate: function(update) {
    let promise;
    if (typeof update.channelID != "string") {
      console.warn("handleDataUpdate: Discarding update without channel ID",
        update);
      return;
    }
    function updateRecord(record) {
      // Ignore messages that we've already processed. This can happen if the
      // connection drops between notifying the service worker and acking the
      // the message. In that case, the server will re-send the message on
      // reconnect.
      if (record.hasRecentMessageID(update.version)) {
        console.warn("handleDataUpdate: Ignoring duplicate message",
          update.version);
        return null;
      }
      record.noteRecentMessageID(update.version);
      return record;
    }
    if (typeof update.data != "string") {
      promise = this._mainPushService.receivedPushMessage(
        update.channelID,
        update.version,
        null,
        null,
        updateRecord
      );
    } else {
      let message = ChromeUtils.base64URLDecode(update.data, {
        // The Push server may append padding.
        padding: "ignore",
      });
      promise = this._mainPushService.receivedPushMessage(
        update.channelID,
        update.version,
        update.headers,
        message,
        updateRecord
      );
    }
    promise.then(status => {
      this._sendAck(update.channelID, update.version, status);
    }, err => {
      console.error("handleDataUpdate: Error delivering message", update, err);
      this._sendAck(update.channelID, update.version,
        Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR);
    }).catch(err => {
      console.error("handleDataUpdate: Error acknowledging message", update,
        err);
    });
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleNotificationReply: function(reply) {
    console.debug("handleNotificationReply()");
    if (this._dataEnabled) {
      this._handleDataUpdate(reply);
      return;
    }

    if (typeof reply.updates !== 'object') {
      console.warn("handleNotificationReply: Missing updates", reply.updates);
      return;
    }

    console.debug("handleNotificationReply: Got updates", reply.updates);
    for (let i = 0; i < reply.updates.length; i++) {
      let update = reply.updates[i];
      console.debug("handleNotificationReply: Handling update", update);
      if (typeof update.channelID !== "string") {
        console.debug("handleNotificationReply: Invalid update at index",
          i, update);
        continue;
      }

      if (update.version === undefined) {
        console.debug("handleNotificationReply: Missing version", update);
        continue;
      }

      let version = update.version;

      if (typeof version === "string") {
        version = parseInt(version, 10);
      }

      if (typeof version === "number" && version >= 0) {
        // FIXME(nsm): this relies on app update notification being infallible!
        // eventually fix this
        this._receivedUpdate(update.channelID, version);
      }
    }
  },

  reportDeliveryError(messageID, reason) {
    console.debug("reportDeliveryError()");
    let code = kDELIVERY_REASON_TO_CODE[reason];
    if (!code) {
      throw new Error('Invalid delivery error reason');
    }
    let data = {messageType: 'nack',
                version: messageID,
                code: code};
    this._queueRequest(data);
  },

  _sendAck(channelID, version, status) {
    console.debug("sendAck()");
    let code = kACK_STATUS_TO_CODE[status];
    if (!code) {
      throw new Error('Invalid ack status');
    }
    let data = {messageType: 'ack',
                updates: [{channelID: channelID,
                           version: version,
                           code: code}]};
    this._queueRequest(data);
  },

  _generateID: function() {
    let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                          .getService(Ci.nsIUUIDGenerator);
    // generateUUID() gives a UUID surrounded by {...}, slice them off.
    return uuidGenerator.generateUUID().toString().slice(1, -1);
  },

  register(record) {
    console.debug("register() ", record);

    let data = {channelID: this._generateID(),
                messageType: "register"};

    if (record.appServerKey) {
      data.key = ChromeUtils.base64URLEncode(record.appServerKey, {
        // The Push server requires padding.
        pad: true,
      });
    }

    return this._sendRequestForReply(record, data).then(record => {
      if (!this._dataEnabled) {
        return record;
      }
      return PushCrypto.generateKeys()
        .then(([publicKey, privateKey]) => {
          record.p256dhPublicKey = publicKey;
          record.p256dhPrivateKey = privateKey;
          record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
          return record;
        });
    });
  },

  unregister(record, reason) {
    console.debug("unregister() ", record, reason);

    return Promise.resolve().then(_ => {
      let code = kUNREGISTER_REASON_TO_CODE[reason];
      if (!code) {
        throw new Error('Invalid unregister reason');
      }
      let data = {channelID: record.channelID,
                  messageType: "unregister",
                  code: code};

      return this._sendRequestForReply(record, data);
    });
  },

  _queueStart: Promise.resolve(),
  _notifyRequestQueue: null,
  _queue: null,
  _enqueue: function(op) {
    console.debug("enqueue()");
    if (!this._queue) {
      this._queue = this._queueStart;
    }
    this._queue = this._queue
                    .then(op)
                    .catch(_ => {});
  },

  /** Sends a request to the server. */
  _send(data) {
    if (this._currentState != STATE_READY) {
      console.warn("send: Unexpected state; ignoring message",
        this._currentState);
      return;
    }
    if (!this._requestHasReply(data)) {
      this._wsSendMessage(data);
      return;
    }
    // If we're expecting a reply, check that we haven't cancelled the request.
    let key = this._makePendingRequestKey(data);
    if (!this._pendingRequests.has(key)) {
      console.log("send: Request cancelled; ignoring message", key);
      return;
    }
    this._wsSendMessage(data);
  },

  /** Indicates whether a request has a corresponding reply from the server. */
  _requestHasReply(data) {
    return data.messageType == "register" || data.messageType == "unregister";
  },

  /**
   * Sends all pending requests that expect replies. Called after the connection
   * is established and the handshake is complete.
   */
  _sendPendingRequests() {
    this._enqueue(_ => {
      for (let request of this._pendingRequests.values()) {
        this._send(request.data);
      }
    });
  },

  /** Queues an outgoing request, establishing a connection if necessary. */
  _queueRequest(data) {
    console.debug("queueRequest()", data);

    if (this._currentState == STATE_READY) {
      // If we're ready, no need to queue; just send the request.
      this._send(data);
      return;
    }

    // Otherwise, we're still setting up. If we don't have a request queue,
    // make one now.
    if (!this._notifyRequestQueue) {
      let promise = new Promise((resolve, reject) => {
        this._notifyRequestQueue = resolve;
      });
      this._enqueue(_ => promise);
    }

    let isRequest = this._requestHasReply(data);
    if (!isRequest) {
      // Don't queue requests, since they're stored in `_pendingRequests`, and
      // `_sendPendingRequests` will send them after reconnecting. Without this
      // check, we'd send requests twice.
      this._enqueue(_ => this._send(data));
    }

    if (!this._ws) {
      // This will end up calling notifyRequestQueue().
      this._beginWSSetup();
      // If beginWSSetup does not succeed to make ws, notifyRequestQueue will
      // not be call.
      if (!this._ws && this._notifyRequestQueue) {
        this._notifyRequestQueue();
        this._notifyRequestQueue = null;
      }
    }
  },

  _receivedUpdate: function(aChannelID, aLatestVersion) {
    console.debug("receivedUpdate: Updating", aChannelID, "->", aLatestVersion);

    this._mainPushService.receivedPushMessage(aChannelID, "", null, null, record => {
      if (record.version === null ||
          record.version < aLatestVersion) {
        console.debug("receivedUpdate: Version changed for", aChannelID,
          aLatestVersion);
        record.version = aLatestVersion;
        return record;
      }
      console.debug("receivedUpdate: No significant version change for",
        aChannelID, aLatestVersion);
      return null;
    }).then(status => {
      this._sendAck(aChannelID, aLatestVersion, status);
    }).catch(err => {
      console.error("receivedUpdate: Error acknowledging message", aChannelID,
        aLatestVersion, err);
    });
  },

  // begin Push protocol handshake
  _wsOnStart: function(context) {
    console.debug("wsOnStart()");

    if (this._currentState != STATE_WAITING_FOR_WS_START) {
      console.error("wsOnStart: NOT in STATE_WAITING_FOR_WS_START. Current",
        "state", this._currentState, "Skipping");
      return;
    }

    let data = {
      messageType: "hello",
      use_webpush: true,
    };

    if (this._UAID) {
      data.uaid = this._UAID;
    }

    this._wsSendMessage(data);
    this._currentState = STATE_WAITING_FOR_HELLO;
  },

  /**
   * This statusCode is not the websocket protocol status code, but the TCP
   * connection close status code.
   *
   * If we do not explicitly call ws.close() then statusCode is always
   * NS_BASE_STREAM_CLOSED, even on a successful close.
   */
  _wsOnStop: function(context, statusCode) {
    console.debug("wsOnStop()");

    if (statusCode != Cr.NS_OK && !this._skipReconnect) {
      console.debug("wsOnStop: Reconnecting after socket error", statusCode);
      this._reconnect();
      return;
    }

    this._shutdownWS();
  },

  _wsOnMessageAvailable: function(context, message) {
    console.debug("wsOnMessageAvailable()", message);

    // Clearing the last ping time indicates we're no longer waiting for a pong.
    this._lastPingTime = 0;

    let reply;
    try {
      reply = JSON.parse(message);
    } catch(e) {
      console.warn("wsOnMessageAvailable: Invalid JSON", message, e);
      return;
    }

    // If we receive a message, we know the connection succeeded. Reset the
    // connection attempt and ping interval counters.
    this._retryFailCount = 0;

    let doNotHandle = false;
    if ((message === '{}') ||
        (reply.messageType === undefined) ||
        (reply.messageType === "ping") ||
        (typeof reply.messageType != "string")) {
      console.debug("wsOnMessageAvailable: Pong received");
      doNotHandle = true;
    }

    // Reset the ping timer.  Note: This path is executed at every step of the
    // handshake, so this timer does not need to be set explicitly at startup.
    this._startPingTimer();

    // If it is a ping, do not handle the message.
    if (doNotHandle) {
      return;
    }

    // A whitelist of protocol handlers. Add to these if new messages are added
    // in the protocol.
    let handlers = ["Hello", "Register", "Unregister", "Notification"];

    // Build up the handler name to call from messageType.
    // e.g. messageType == "register" -> _handleRegisterReply.
    let handlerName = reply.messageType[0].toUpperCase() +
                      reply.messageType.slice(1).toLowerCase();

    if (handlers.indexOf(handlerName) == -1) {
      console.warn("wsOnMessageAvailable: No whitelisted handler", handlerName,
        "for message", reply.messageType);
      return;
    }

    let handler = "_handle" + handlerName + "Reply";

    if (typeof this[handler] !== "function") {
      console.warn("wsOnMessageAvailable: Handler", handler,
        "whitelisted but not implemented");
      return;
    }

    this[handler](reply);
  },

  /**
   * The websocket should never be closed. Since we don't call ws.close(),
   * _wsOnStop() receives error code NS_BASE_STREAM_CLOSED (see comment in that
   * function), which calls reconnect and re-establishes the WebSocket
   * connection.
   *
   * If the server requested that we back off, we won't reconnect until the
   * next network state change event, or until we need to send a new register
   * request.
   */
  _wsOnServerClose: function(context, aStatusCode, aReason) {
    console.debug("wsOnServerClose()", aStatusCode, aReason);

    if (aStatusCode == kBACKOFF_WS_STATUS_CODE) {
      console.debug("wsOnServerClose: Skipping automatic reconnect");
      this._skipReconnect = true;
    }
  },

  /**
   * Rejects all pending register requests with errors.
   */
  _cancelPendingRequests() {
    for (let request of this._pendingRequests.values()) {
      request.reject(new Error("Request aborted"));
    }
    this._pendingRequests.clear();
  },

  /** Creates a case-insensitive map key for a request that expects a reply. */
  _makePendingRequestKey(data) {
    return (data.messageType + "|" + data.channelID).toLowerCase();
  },

  /** Sends a request and waits for a reply from the server. */
  _sendRequestForReply(record, data) {
    return Promise.resolve().then(_ => {
      // start the timer since we now have at least one request
      this._startRequestTimeoutTimer();

      let key = this._makePendingRequestKey(data);
      if (!this._pendingRequests.has(key)) {
        let request = {
          data: data,
          record: record,
          ctime: Date.now(),
        };
        request.promise = new Promise((resolve, reject) => {
          request.resolve = resolve;
          request.reject = reject;
        });
        this._pendingRequests.set(key, request);
        this._queueRequest(data);
      }

      return this._pendingRequests.get(key).promise;
    });
  },

  /** Removes and returns a pending request for a server reply. */
  _takeRequestForReply(reply) {
    if (typeof reply.channelID !== "string") {
      return null;
    }
    let key = this._makePendingRequestKey(reply);
    let request = this._pendingRequests.get(key);
    if (!request) {
      return null;
    }
    this._pendingRequests.delete(key);
    if (!this._hasPendingRequests()) {
      this._requestTimeoutTimer.cancel();
    }
    return request;
  },
};

function PushRecordWebSocket(record) {
  PushRecord.call(this, record);
  this.channelID = record.channelID;
  this.version = record.version;
}

PushRecordWebSocket.prototype = Object.create(PushRecord.prototype, {
  keyID: {
    get() {
      return this.channelID;
    },
  },
});

PushRecordWebSocket.prototype.toSubscription = function() {
  let subscription = PushRecord.prototype.toSubscription.call(this);
  subscription.version = this.version;
  return subscription;
};
PK
!<ziOOmodules/ReaderMode.jsm// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

this.EXPORTED_SYMBOLS = ["ReaderMode"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

// Constants for telemetry.
const DOWNLOAD_SUCCESS = 0;
const DOWNLOAD_ERROR_XHR = 1;
const DOWNLOAD_ERROR_NO_DOC = 2;

const PARSE_SUCCESS = 0;
const PARSE_ERROR_TOO_MANY_ELEMENTS = 1;
const PARSE_ERROR_WORKER = 2;
const PARSE_ERROR_NO_ARTICLE = 3;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.importGlobalProperties(["XMLHttpRequest"]);

XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher", "resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector", "resource:///modules/translation/LanguageDetector.jsm");

XPCOMUtils.defineLazyGetter(this, "Readability", function() {
  let scope = {};
  scope.dump = this.dump;
  Services.scriptloader.loadSubScript("resource://gre/modules/reader/Readability.js", scope);
  return scope["Readability"];
});

const gIsFirefoxDesktop = Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";

this.ReaderMode = {
  // Version of the cache schema.
  CACHE_VERSION: 1,

  DEBUG: 0,

  // Don't try to parse the page if it has too many elements (for memory and
  // performance reasons)
  get maxElemsToParse() {
    delete this.parseNodeLimit;

    Services.prefs.addObserver("reader.parse-node-limit", this);
    return this.parseNodeLimit = Services.prefs.getIntPref("reader.parse-node-limit");
  },

  get isEnabledForParseOnLoad() {
    delete this.isEnabledForParseOnLoad;

    // Listen for future pref changes.
    Services.prefs.addObserver("reader.parse-on-load.", this);

    return this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
  },

  _getStateForParseOnLoad() {
    let isEnabled = Services.prefs.getBoolPref("reader.parse-on-load.enabled");
    let isForceEnabled = Services.prefs.getBoolPref("reader.parse-on-load.force-enabled");
    return isForceEnabled || isEnabled;
  },

  observe(aMessage, aTopic, aData) {
    switch (aTopic) {
      case "nsPref:changed":
        if (aData.startsWith("reader.parse-on-load.")) {
          this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
        } else if (aData === "reader.parse-node-limit") {
          this.parseNodeLimit = Services.prefs.getIntPref(aData);
        }
        break;
    }
  },

  /**
   * Enter the reader mode by going forward one step in history if applicable,
   * if not, append the about:reader page in the history instead.
   */
  enterReaderMode(docShell, win) {
    let url = win.document.location.href;
    let readerURL = "about:reader?url=" + encodeURIComponent(url);
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    let sh = webNav.sessionHistory;
    if (webNav.canGoForward) {
      let forwardEntry = sh.getEntryAtIndex(sh.index + 1, false);
      let forwardURL = forwardEntry.URI.spec;
      if (forwardURL && (forwardURL == readerURL || !readerURL)) {
        webNav.goForward();
        return;
      }
    }

    win.document.location = readerURL;
  },

  /**
   * Exit the reader mode by going back one step in history if applicable,
   * if not, append the original page in the history instead.
   */
  leaveReaderMode(docShell, win) {
    let url = win.document.location.href;
    let originalURL = this.getOriginalUrl(url);
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    let sh = webNav.sessionHistory;
    if (webNav.canGoBack) {
      let prevEntry = sh.getEntryAtIndex(sh.index - 1, false);
      let prevURL = prevEntry.URI.spec;
      if (prevURL && (prevURL == originalURL || !originalURL)) {
        webNav.goBack();
        return;
      }
    }

    win.document.location = originalURL;
  },

  /**
   * Returns original URL from an about:reader URL.
   *
   * @param url An about:reader URL.
   * @return The original URL for the article, or null if we did not find
   *         a properly formatted about:reader URL.
   */
  getOriginalUrl(url) {
    if (!url.startsWith("about:reader?")) {
      return null;
    }

    let outerHash = "";
    try {
      let uriObj = Services.io.newURI(url);
      url = uriObj.specIgnoringRef;
      outerHash = uriObj.ref;
    } catch (ex) { /* ignore, use the raw string */ }

    let searchParams = new URLSearchParams(url.substring("about:reader?".length));
    if (!searchParams.has("url")) {
      return null;
    }
    let originalUrl = searchParams.get("url");
    if (outerHash) {
      try {
        let uriObj = Services.io.newURI(originalUrl);
        uriObj = Services.io.newURI("#" + outerHash, null, uriObj);
        originalUrl = uriObj.spec;
      } catch (ex) {}
    }
    return originalUrl;
  },

  getOriginalUrlObjectForDisplay(url) {
    let originalUrl = this.getOriginalUrl(url);
    if (originalUrl) {
      let uriObj;
      try {
        uriObj = Services.uriFixup.createFixupURI(originalUrl, Services.uriFixup.FIXUP_FLAG_NONE);
      } catch (ex) {
        return null;
      }
      try {
        return Services.uriFixup.createExposableURI(uriObj);
      } catch (ex) {
        return null;
      }
    }
    return null;
  },

  /**
   * Decides whether or not a document is reader-able without parsing the whole thing.
   *
   * @param doc A document to parse.
   * @return boolean Whether or not we should show the reader mode button.
   */
  isProbablyReaderable(doc) {
    // Only care about 'real' HTML documents:
    if (doc.mozSyntheticDocument || !(doc instanceof doc.defaultView.HTMLDocument)) {
      return false;
    }

    let uri = Services.io.newURI(doc.location.href);
    if (!this._shouldCheckUri(uri)) {
      return false;
    }

    let utils = this.getUtilsForWin(doc.defaultView);
    // We pass in a helper function to determine if a node is visible, because
    // it uses gecko APIs that the engine-agnostic readability code can't rely
    // upon.
    return new Readability(uri, doc).isProbablyReaderable(this.isNodeVisible.bind(this, utils));
  },

  isNodeVisible(utils, node) {
    let bounds = utils.getBoundsWithoutFlushing(node);
    return bounds.height > 0 && bounds.width > 0;
  },

  getUtilsForWin(win) {
    return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
  },

  /**
   * Gets an article from a loaded browser's document. This method will not attempt
   * to parse certain URIs (e.g. about: URIs).
   *
   * @param doc A document to parse.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  parseDocument(doc) {
    if (!this._shouldCheckUri(doc.documentURIObject) || !this._shouldCheckUri(doc.baseURIObject, true)) {
      this.log("Reader mode disabled for URI");
      return null;
    }

    return this._readerParse(doc);
  },

  /**
   * Downloads and parses a document from a URL.
   *
   * @param url URL to download and parse.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  async downloadAndParseDocument(url) {
    let doc = await this._downloadDocument(url);
    if (!this._shouldCheckUri(doc.documentURIObject) || !this._shouldCheckUri(doc.baseURIObject, true)) {
      this.log("Reader mode disabled for URI");
      return null;
    }

    return this._readerParse(doc);
  },

  _downloadDocument(url) {
    let histogram = Services.telemetry.getHistogramById("READER_MODE_DOWNLOAD_RESULT");
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onerror = evt => reject(evt.error);
      xhr.responseType = "document";
      xhr.onload = evt => {
        if (xhr.status !== 200) {
          reject("Reader mode XHR failed with status: " + xhr.status);
          histogram.add(DOWNLOAD_ERROR_XHR);
          return;
        }

        let doc = xhr.responseXML;
        if (!doc) {
          reject("Reader mode XHR didn't return a document");
          histogram.add(DOWNLOAD_ERROR_NO_DOC);
          return;
        }

        // Manually follow a meta refresh tag if one exists.
        let meta = doc.querySelector("meta[http-equiv=refresh]");
        if (meta) {
          let content = meta.getAttribute("content");
          if (content) {
            let urlIndex = content.toUpperCase().indexOf("URL=");
            if (urlIndex > -1) {
              let baseURI = Services.io.newURI(url);
              let newURI = Services.io.newURI(content.substring(urlIndex + 4), null, baseURI);
              let newURL = newURI.spec;
              let ssm = Services.scriptSecurityManager;
              let flags = ssm.LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
                          ssm.DISALLOW_INHERIT_PRINCIPAL;
              try {
                ssm.checkLoadURIStrWithPrincipal(doc.nodePrincipal, newURL, flags);
              } catch (ex) {
                let errorMsg = "Reader mode disallowed meta refresh (reason: " + ex + ").";

                if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
                  errorMsg += " Refresh target URI: '" + newURL + "'.";
                reject(errorMsg);
                return;
              }
              // Otherwise, pass an object indicating our new URL:
              if (!baseURI.equalsExceptRef(newURI)) {
                reject({newURL});
                return;
              }
            }
          }
        }
        let responseURL = xhr.responseURL;
        let givenURL = url;
        // Convert these to real URIs to make sure the escaping (or lack
        // thereof) is identical:
        try {
          responseURL = Services.io.newURI(responseURL).specIgnoringRef;
        } catch (ex) { /* Ignore errors - we'll use what we had before */ }
        try {
          givenURL = Services.io.newURI(givenURL).specIgnoringRef;
        } catch (ex) { /* Ignore errors - we'll use what we had before */ }

        if (responseURL != givenURL) {
          // We were redirected without a meta refresh tag.
          // Force redirect to the correct place:
          reject({newURL: xhr.responseURL});
          return;
        }
        resolve(doc);
        histogram.add(DOWNLOAD_SUCCESS);
      };
      xhr.send();
    });
  },


  /**
   * Retrieves an article from the cache given an article URI.
   *
   * @param url The article URL.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   * @rejects OS.File.Error
   */
  async getArticleFromCache(url) {
    let path = this._toHashedPath(url);
    try {
      let array = await OS.File.read(path);
      return JSON.parse(new TextDecoder().decode(array));
    } catch (e) {
      if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile)
        throw e;
      return null;
    }
  },

  /**
   * Stores an article in the cache.
   *
   * @param article JS object representing article.
   * @return {Promise}
   * @resolves When the article is stored.
   * @rejects OS.File.Error
   */
  async storeArticleInCache(article) {
    let array = new TextEncoder().encode(JSON.stringify(article));
    let path = this._toHashedPath(article.url);
    await this._ensureCacheDir();
    return OS.File.writeAtomic(path, array, { tmpPath: path + ".tmp" })
      .then(success => {
        OS.File.stat(path).then(info => {
          return EventDispatcher.instance.sendRequest({
            type: "Reader:AddedToCache",
            url: article.url,
            size: info.size,
            path,
          });
        });
      });
  },

  /**
   * Removes an article from the cache given an article URI.
   *
   * @param url The article URL.
   * @return {Promise}
   * @resolves When the article is removed.
   * @rejects OS.File.Error
   */
  async removeArticleFromCache(url) {
    let path = this._toHashedPath(url);
    await OS.File.remove(path);
  },

  log(msg) {
    if (this.DEBUG)
      dump("Reader: " + msg);
  },

  _blockedHosts: [
    "amazon.com",
    "github.com",
    "mail.google.com",
    "pinterest.com",
    "reddit.com",
    "twitter.com",
    "youtube.com",
  ],

  _shouldCheckUri(uri, isBaseUri = false) {
    if (!(uri.schemeIs("http") || uri.schemeIs("https"))) {
      this.log("Not parsing URI scheme: " + uri.scheme);
      return false;
    }

    try {
      uri.QueryInterface(Ci.nsIURL);
    } catch (ex) {
      // If this doesn't work, presumably the URL is not well-formed or something
      return false;
    }
    // Sadly, some high-profile pages have false positives, so bail early for those:
    let asciiHost = uri.asciiHost;
    if (!isBaseUri && this._blockedHosts.some(blockedHost => asciiHost.endsWith(blockedHost))) {
      return false;
    }

    if (!isBaseUri && (!uri.filePath || uri.filePath == "/")) {
      this.log("Not parsing home page: " + uri.spec);
      return false;
    }

    return true;
  },

  /**
   * Attempts to parse a document into an article. Heavy lifting happens
   * in readerWorker.js.
   *
   * @param doc The document to parse.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  async _readerParse(doc) {
    let histogram = Services.telemetry.getHistogramById("READER_MODE_PARSE_RESULT");
    if (this.parseNodeLimit) {
      let numTags = doc.getElementsByTagName("*").length;
      if (numTags > this.parseNodeLimit) {
        this.log("Aborting parse for " + doc.baseURIObject.spec + "; " + numTags + " elements found");
        histogram.add(PARSE_ERROR_TOO_MANY_ELEMENTS);
        return null;
      }
    }

    // Fetch this here before we send `doc` off to the worker thread, as later on the
    // document might be nuked but we will still want the URI.
    let {documentURI} = doc;

    let uriParam = {
      spec: doc.baseURIObject.spec,
      host: doc.baseURIObject.host,
      prePath: doc.baseURIObject.prePath,
      scheme: doc.baseURIObject.scheme,
      pathBase: Services.io.newURI(".", null, doc.baseURIObject).spec
    };

    let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
                     createInstance(Ci.nsIDOMSerializer);
    let serializedDoc = serializer.serializeToString(doc);

    let article = null;
    try {
      article = await ReaderWorker.post("parseDocument", [uriParam, serializedDoc]);
    } catch (e) {
      Cu.reportError("Error in ReaderWorker: " + e);
      histogram.add(PARSE_ERROR_WORKER);
    }

    // Explicitly null out doc to make it clear it might not be available from this
    // point on.
    doc = null;

    if (!article) {
      this.log("Worker did not return an article");
      histogram.add(PARSE_ERROR_NO_ARTICLE);
      return null;
    }

    // Readability returns a URI object based on the baseURI, but we only care
    // about the original document's URL from now on. This also avoids spoofing
    // attempts where the baseURI doesn't match the domain of the documentURI
    article.url = documentURI;
    delete article.uri;

    let flags = Ci.nsIDocumentEncoder.OutputSelectionOnly | Ci.nsIDocumentEncoder.OutputAbsoluteLinks;
    article.title = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils)
                                                    .convertToPlainText(article.title, flags, 0);
    if (gIsFirefoxDesktop) {
      await this._assignLanguage(article);
      this._maybeAssignTextDirection(article);
    }

    this._assignReadTime(article);

    histogram.add(PARSE_SUCCESS);
    return article;
  },

  get _cryptoHash() {
    delete this._cryptoHash;
    return this._cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
  },

  get _unicodeConverter() {
    delete this._unicodeConverter;
    this._unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                              .createInstance(Ci.nsIScriptableUnicodeConverter);
    this._unicodeConverter.charset = "utf8";
    return this._unicodeConverter;
  },

  /**
   * Calculate the hashed path for a stripped article URL.
   *
   * @param url The article URL. This should have referrers removed.
   * @return The file path to the cached article.
   */
  _toHashedPath(url) {
    let value = this._unicodeConverter.convertToByteArray(url);
    this._cryptoHash.init(this._cryptoHash.MD5);
    this._cryptoHash.update(value, value.length);

    let hash = CommonUtils.encodeBase32(this._cryptoHash.finish(false));
    let fileName = hash.substring(0, hash.indexOf("=")) + ".json";
    return OS.Path.join(OS.Constants.Path.profileDir, "readercache", fileName);
  },

  /**
   * Ensures the cache directory exists.
   *
   * @return Promise
   * @resolves When the cache directory exists.
   * @rejects OS.File.Error
   */
  _ensureCacheDir() {
    let dir = OS.Path.join(OS.Constants.Path.profileDir, "readercache");
    return OS.File.exists(dir).then(exists => {
      if (!exists) {
        return OS.File.makeDir(dir);
      }
      return undefined;
    });
  },

  /**
   * Sets a global language string value if the result is confident
   *
   * @return Promise
   * @resolves when the language is detected
   */
  _assignLanguage(article) {
    return LanguageDetector.detectLanguage(article.textContent).then(result => {
      article.language = result.confident ? result.language : null;
    });
  },

  _maybeAssignTextDirection(article) {
    // TODO: Remove the hardcoded language codes below once bug 1320265 is resolved.
    if (!article.dir && ["ar", "fa", "he", "ug", "ur"].includes(article.language)) {
      article.dir = "rtl";
    }
  },

  /**
   * Assigns the estimated reading time range of the article to the article object.
   *
   * @param article the article object to assign the reading time estimate to.
   */
  _assignReadTime(article) {
    let lang = article.language || "en";
    const readingSpeed = this._getReadingSpeedForLanguage(lang);
    const charactersPerMinuteLow = readingSpeed.cpm - readingSpeed.variance;
    const charactersPerMinuteHigh = readingSpeed.cpm + readingSpeed.variance;
    const length = article.length;

    article.readingTimeMinsSlow = Math.ceil(length / charactersPerMinuteLow);
    article.readingTimeMinsFast  = Math.ceil(length / charactersPerMinuteHigh);
  },

  /**
   * Returns the reading speed of a selection of languages with likely variance.
   *
   * Reading speed estimated from a study done on reading speeds in various languages.
   * study can be found here: http://iovs.arvojournals.org/article.aspx?articleid=2166061
   *
   * @return object with characters per minute and variance. Defaults to English
   *         if no suitable language is found in the collection.
   */
  _getReadingSpeedForLanguage(lang) {
    const readingSpeed = new Map([
      [ "en", {cpm: 987,  variance: 118 } ],
      [ "ar", {cpm: 612,  variance: 88 } ],
      [ "de", {cpm: 920,  variance: 86 } ],
      [ "es", {cpm: 1025, variance: 127 } ],
      [ "fi", {cpm: 1078, variance: 121 } ],
      [ "fr", {cpm: 998,  variance: 126 } ],
      [ "he", {cpm: 833,  variance: 130 } ],
      [ "it", {cpm: 950,  variance: 140 } ],
      [ "jw", {cpm: 357,  variance: 56 } ],
      [ "nl", {cpm: 978,  variance: 143 } ],
      [ "pl", {cpm: 916,  variance: 126 } ],
      [ "pt", {cpm: 913,  variance: 145 } ],
      [ "ru", {cpm: 986,  variance: 175 } ],
      [ "sk", {cpm: 885,  variance: 145 } ],
      [ "sv", {cpm: 917,  variance: 156 } ],
      [ "tr", {cpm: 1054, variance: 156 } ],
      [ "zh", {cpm: 255,  variance: 29 } ],
    ]);

    return readingSpeed.get(lang) || readingSpeed.get("en");
  },
};
PK
!<0MMmodules/RemoteAddonsChild.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["RemoteAddonsChild"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
                                  "resource://gre/modules/Prefetcher.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "SystemPrincipal",
                                   "@mozilla.org/systemprincipal;1", "nsIPrincipal");

XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
                                   "@mozilla.org/contentsecuritymanager;1",
                                   "nsIContentSecurityManager");

const TELEMETRY_SHOULD_LOAD_LOADING_KEY = "ADDON_CONTENT_POLICY_SHIM_BLOCKING_LOADING_MS";
const TELEMETRY_SHOULD_LOAD_LOADED_KEY = "ADDON_CONTENT_POLICY_SHIM_BLOCKING_LOADED_MS";

// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_) {
  if (key in dict) {
    return dict[key];
  }
  dict[key] = default_;
  return default_;
}

// This code keeps track of a set of paths of the form [component_1,
// ..., component_n]. The components can be strings or booleans. The
// child is notified whenever a path is added or removed, and new
// children can request the current set of paths. The purpose is to
// keep track of all the observers and events that the child should
// monitor for the parent.
//
// In the child, clients can watch for changes to all paths that start
// with a given component.
var NotificationTracker = {
  init() {
    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
               .getService(Ci.nsISyncMessageSender);
    cpmm.addMessageListener("Addons:ChangeNotification", this);
    this._paths = cpmm.initialProcessData.remoteAddonsNotificationPaths;
    this._registered = new Map();
    this._watchers = {};
  },

  receiveMessage(msg) {
    let path = msg.data.path;
    let count = msg.data.count;

    let tracked = this._paths;
    for (let component of path) {
      tracked = setDefault(tracked, component, {});
    }

    tracked._count = count;

    if (this._watchers[path[0]]) {
      for (let watcher of this._watchers[path[0]]) {
        this.runCallback(watcher, path, count);
      }
    }
  },

  runCallback(watcher, path, count) {
    let pathString = path.join("/");
    let registeredSet = this._registered.get(watcher);
    let registered = registeredSet.has(pathString);
    if (count && !registered) {
      watcher.track(path, true);
      registeredSet.add(pathString);
    } else if (!count && registered) {
      watcher.track(path, false);
      registeredSet.delete(pathString);
    }
  },

  findPaths(prefix) {
    if (!this._paths) {
      return [];
    }

    let tracked = this._paths;
    for (let component of prefix) {
      tracked = setDefault(tracked, component, {});
    }

    let result = [];
    let enumerate = (tracked, curPath) => {
      for (let component in tracked) {
        if (component == "_count") {
          result.push([curPath, tracked._count]);
        } else {
          let path = curPath.slice();
          if (component === "true") {
            component = true;
          } else if (component === "false") {
            component = false;
          }
          path.push(component);
          enumerate(tracked[component], path);
        }
      }
    }
    enumerate(tracked, prefix);

    return result;
  },

  findSuffixes(prefix) {
    let paths = this.findPaths(prefix);
    return paths.map(([path, count]) => path[path.length - 1]);
  },

  watch(component1, watcher) {
    setDefault(this._watchers, component1, []).push(watcher);
    this._registered.set(watcher, new Set());

    let paths = this.findPaths([component1]);
    for (let [path, count] of paths) {
      this.runCallback(watcher, path, count);
    }
  },

  unwatch(component1, watcher) {
    let watchers = this._watchers[component1];
    let index = watchers.lastIndexOf(watcher);
    if (index > -1) {
      watchers.splice(index, 1);
    }

    this._registered.delete(watcher);
  },

  getCount(component1) {
    return this.findPaths([component1]).length;
  },
};

// This code registers an nsIContentPolicy in the child process. When
// it runs, it notifies the parent that it needs to run its own
// nsIContentPolicy list. If any policy in the parent rejects a
// resource load, that answer is returned to the child.
var ContentPolicyChild = {
  _classDescription: "Addon shim content policy",
  _classID: Components.ID("6e869130-635c-11e2-bcfd-0800200c9a66"),
  _contractID: "@mozilla.org/addon-child/policy;1",

  // A weak map of time spent blocked in hooks for a given document.
  // WeakMap[document -> Map[addonId -> timeInMS]]
  timings: new WeakMap(),

  init() {
    let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);

    this.loadingHistogram = Services.telemetry.getKeyedHistogramById(TELEMETRY_SHOULD_LOAD_LOADING_KEY);
    this.loadedHistogram = Services.telemetry.getKeyedHistogramById(TELEMETRY_SHOULD_LOAD_LOADED_KEY);

    NotificationTracker.watch("content-policy", this);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
                                         Ci.nsIChannelEventSink, Ci.nsIFactory,
                                         Ci.nsISupportsWeakReference]),

  track(path, register) {
    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    if (register) {
      catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
    } else {
      catMan.deleteCategoryEntry("content-policy", this._contractID, false);
    }
  },

  // Returns a map of cumulative time spent in shouldLoad hooks for a
  // given add-on in the given node's document. May return null if
  // telemetry recording is disabled, or the given context does not
  // point to a document.
  getTimings(context) {
    if (!Services.telemetry.canRecordExtended) {
      return null;
    }

    let doc;
    if (context instanceof Ci.nsIDOMNode) {
      doc = context.ownerDocument;
    } else if (context instanceof Ci.nsIDOMDocument) {
      doc = context;
    } else if (context instanceof Ci.nsIDOMWindow) {
      doc = context.document;
    }

    if (!doc) {
      return null;
    }

    let map = this.timings.get(doc);
    if (!map) {
      // No timing object exists for this document yet. Create one, and
      // set up a listener to record the final values at the right time.
      map = new Map();
      this.timings.set(doc, map);

      // If the document is still loading, record aggregate pre-load
      // timings when the load event fires. If it's already loaded,
      // record aggregate post-load timings when the page is hidden.
      let eventName = doc.readyState == "complete" ? "pagehide" : "load";

      let listener = event => {
        if (event.target == doc) {
          event.currentTarget.removeEventListener(eventName, listener, true);
          this.logTelemetry(doc, eventName);
        }
      };
      doc.defaultView.addEventListener(eventName, listener, true);
    }
    return map;
  },

  // Logs the accumulated telemetry for the given document, into the
  // appropriate telemetry histogram based on the DOM event name that
  // triggered it.
  logTelemetry(doc, eventName) {
    let map = this.timings.get(doc);
    this.timings.delete(doc);

    let histogram = eventName == "load" ? this.loadingHistogram : this.loadedHistogram;

    for (let [addon, time] of map.entries()) {
      histogram.add(addon, time);
    }
  },

  shouldLoad(contentType, contentLocation, requestOrigin,
                       node, mimeTypeGuess, extra, requestPrincipal) {
    let startTime = Cu.now();

    let addons = NotificationTracker.findSuffixes(["content-policy"]);
    let [prefetched, cpows] = Prefetcher.prefetch("ContentPolicy.shouldLoad",
                                                  addons, {InitNode: node});
    cpows.node = node;

    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
               .getService(Ci.nsISyncMessageSender);
    let rval = cpmm.sendRpcMessage("Addons:ContentPolicy:Run", {
      contentType,
      contentLocation: contentLocation.spec,
      requestOrigin: requestOrigin ? requestOrigin.spec : null,
      mimeTypeGuess,
      requestPrincipal,
      prefetched,
    }, cpows);

    let timings = this.getTimings(node);
    if (timings) {
      let delta = Cu.now() - startTime;

      for (let addon of addons) {
        let old = timings.get(addon) || 0;
        timings.set(addon, old + delta);
      }
    }

    if (rval.length != 1) {
      return Ci.nsIContentPolicy.ACCEPT;
    }

    return rval[0];
  },

  shouldProcess(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
    return Ci.nsIContentPolicy.ACCEPT;
  },

  createInstance(outer, iid) {
    if (outer) {
      throw Cr.NS_ERROR_NO_AGGREGATION;
    }
    return this.QueryInterface(iid);
  },
};

// This is a shim channel whose only purpose is to return some string
// data from an about: protocol handler.
function AboutProtocolChannel(uri, contractID, loadInfo) {
  this.URI = uri;
  this.originalURI = uri;
  this._contractID = contractID;
  this._loadingPrincipal = loadInfo.loadingPrincipal;
  this._securityFlags = loadInfo.securityFlags;
  this._contentPolicyType = loadInfo.externalContentPolicyType;
}

AboutProtocolChannel.prototype = {
  contentCharset: "utf-8",
  contentLength: 0,
  owner: SystemPrincipal,
  securityInfo: null,
  notificationCallbacks: null,
  loadFlags: 0,
  loadGroup: null,
  name: null,
  status: Cr.NS_OK,

  asyncOpen(listener, context) {
    // Ask the parent to synchronously read all the data from the channel.
    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
               .getService(Ci.nsISyncMessageSender);
    let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:OpenChannel", {
      uri: this.URI.spec,
      contractID: this._contractID,
      loadingPrincipal: this._loadingPrincipal,
      securityFlags: this._securityFlags,
      contentPolicyType: this._contentPolicyType
    }, {
      notificationCallbacks: this.notificationCallbacks,
      loadGroupNotificationCallbacks: this.loadGroup ? this.loadGroup.notificationCallbacks : null,
    });

    if (rval.length != 1) {
      throw Cr.NS_ERROR_FAILURE;
    }

    let {data, contentType} = rval[0];
    this.contentType = contentType;

    // Return the data via an nsIStringInputStream.
    let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
    stream.setData(data, data.length);

    let runnable = {
      run: () => {
        try {
          listener.onStartRequest(this, context);
        } catch (e) {}
        try {
          listener.onDataAvailable(this, context, stream, 0, stream.available());
        } catch (e) {}
        try {
          listener.onStopRequest(this, context, Cr.NS_OK);
        } catch (e) {}
      }
    };
    Services.tm.dispatchToMainThread(runnable);
  },

  asyncOpen2(listener) {
    // throws an error if security checks fail
    var outListener = contentSecManager.performSecurityCheck(this, listener);
    this.asyncOpen(outListener, null);
  },

  open() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  open2() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  isPending() {
    return false;
  },

  cancel() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  suspend() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  resume() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};

// This shim protocol handler is used when content fetches an about: URL.
function AboutProtocolInstance(contractID) {
  this._contractID = contractID;
  this._uriFlags = undefined;
}

AboutProtocolInstance.prototype = {
  createInstance(outer, iid) {
    if (outer != null) {
      throw Cr.NS_ERROR_NO_AGGREGATION;
    }

    return this.QueryInterface(iid);
  },

  getURIFlags(uri) {
    // Cache the result to avoid the extra IPC.
    if (this._uriFlags !== undefined) {
      return this._uriFlags;
    }

    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
               .getService(Ci.nsISyncMessageSender);

    let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:GetURIFlags", {
      uri: uri.spec,
      contractID: this._contractID
    });

    if (rval.length != 1) {
      throw Cr.NS_ERROR_FAILURE;
    }

    this._uriFlags = rval[0];
    return this._uriFlags;
  },

  // We take some shortcuts here. Ideally, we would return a CPOW that
  // wraps the add-on's nsIChannel. However, many of the methods
  // related to nsIChannel are marked [noscript], so they're not
  // available to CPOWs. Consequently, we return a shim channel that,
  // when opened, asks the parent to open the channel and read out all
  // the data.
  newChannel(uri, loadInfo) {
    return new AboutProtocolChannel(uri, this._contractID, loadInfo);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
};

var AboutProtocolChild = {
  _classDescription: "Addon shim about: protocol handler",

  init() {
    // Maps contractIDs to instances
    this._instances = new Map();
    // Maps contractIDs to classIDs
    this._classIDs = new Map();
    NotificationTracker.watch("about-protocol", this);
  },

  track(path, register) {
    let contractID = path[1];
    let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    if (register) {
      let instance = new AboutProtocolInstance(contractID);
      let classID = Cc["@mozilla.org/uuid-generator;1"]
                      .getService(Ci.nsIUUIDGenerator)
                      .generateUUID();

      this._instances.set(contractID, instance);
      this._classIDs.set(contractID, classID);
      registrar.registerFactory(classID, this._classDescription, contractID, instance);
    } else {
      let instance = this._instances.get(contractID);
      let classID = this._classIDs.get(contractID);
      registrar.unregisterFactory(classID, instance);
      this._instances.delete(contractID);
      this._classIDs.delete(contractID);
    }
  },
};

// This code registers observers in the child whenever an add-on in
// the parent asks for notifications on the given topic.
var ObserverChild = {
  init() {
    NotificationTracker.watch("observer", this);
  },

  track(path, register) {
    let topic = path[1];
    if (register) {
      Services.obs.addObserver(this, topic);
    } else {
      Services.obs.removeObserver(this, topic);
    }
  },

  observe(subject, topic, data) {
    let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
               .getService(Ci.nsISyncMessageSender);
    cpmm.sendRpcMessage("Addons:Observer:Run", {}, {
      topic,
      subject,
      data
    });
  }
};

// There is one of these objects per browser tab in the child. When an
// add-on in the parent listens for an event, this child object
// listens for that event in the child.
function EventTargetChild(childGlobal) {
  this._childGlobal = childGlobal;
  this.capturingHandler = (event) => this.handleEvent(true, event);
  this.nonCapturingHandler = (event) => this.handleEvent(false, event);
  NotificationTracker.watch("event", this);
}

EventTargetChild.prototype = {
  uninit() {
    NotificationTracker.unwatch("event", this);
  },

  track(path, register) {
    let eventType = path[1];
    let useCapture = path[2];
    let listener = useCapture ? this.capturingHandler : this.nonCapturingHandler;
    if (register) {
      this._childGlobal.addEventListener(eventType, listener, useCapture, true);
    } else {
      this._childGlobal.removeEventListener(eventType, listener, useCapture);
    }
  },

  handleEvent(capturing, event) {
    let addons = NotificationTracker.findSuffixes(["event", event.type, capturing]);
    let [prefetched, cpows] = Prefetcher.prefetch("EventTarget.handleEvent",
                                                  addons,
                                                  {Event: event,
                                                   Window: this._childGlobal.content});
    cpows.event = event;
    cpows.eventTarget = event.target;

    this._childGlobal.sendRpcMessage("Addons:Event:Run",
                                     {type: event.type,
                                      capturing,
                                      isTrusted: event.isTrusted,
                                      prefetched},
                                     cpows);
  }
};

// The parent can create a sandbox to run code in the child
// process. We actually create the sandbox in the child so that the
// code runs there. However, managing the lifetime of these sandboxes
// can be tricky. The parent references these sandboxes using CPOWs,
// which only keep weak references. So we need to create a strong
// reference in the child. For simplicity, we kill off these strong
// references whenever we navigate away from the page for which the
// sandbox was created.
function SandboxChild(chromeGlobal) {
  this.chromeGlobal = chromeGlobal;
  this.sandboxes = [];
}

SandboxChild.prototype = {
  uninit() {
    this.clearSandboxes();
  },

  addListener() {
    let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress);
    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
  },

  removeListener() {
    let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress);
    webProgress.removeProgressListener(this);
  },

  onLocationChange(webProgress, request, location, flags) {
    this.clearSandboxes();
  },

  addSandbox(sandbox) {
    if (this.sandboxes.length == 0) {
      this.addListener();
    }
    this.sandboxes.push(sandbox);
  },

  clearSandboxes() {
    if (this.sandboxes.length) {
      this.removeListener();
    }
    this.sandboxes = [];
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsISupportsWeakReference])
};

var RemoteAddonsChild = {
  _ready: false,

  makeReady() {
    let shims = [
      Prefetcher,
      NotificationTracker,
      ContentPolicyChild,
      AboutProtocolChild,
      ObserverChild,
    ];

    for (let shim of shims) {
      try {
        shim.init();
      } catch (e) {
        Cu.reportError(e);
      }
    }
  },

  init(global) {

    if (!this._ready) {
      if (!Services.cpmm.initialProcessData.remoteAddonsParentInitted) {
        return null;
      }

      this.makeReady();
      this._ready = true;
    }

    global.sendAsyncMessage("Addons:RegisterGlobal", {}, {global});

    let sandboxChild = new SandboxChild(global);
    global.addSandbox = sandboxChild.addSandbox.bind(sandboxChild);

    // Return this so it gets rooted in the content script.
    return [new EventTargetChild(global), sandboxChild];
  },

  uninit(perTabShims) {
    for (let shim of perTabShims) {
      try {
        shim.uninit();
      } catch (e) {
        Cu.reportError(e);
      }
    }
  },

  get useSyncWebProgress() {
    return NotificationTracker.getCount("web-progress") > 0;
  },
};
PK
!<8lmodules/RemoteAddonsParent.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["RemoteAddonsParent"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/RemoteWebProgress.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
                                  "resource://gre/modules/Prefetcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CompatWarning",
                                  "resource://gre/modules/CompatWarning.jsm");

Cu.permitCPOWsInScope(this);

// Similar to Python. Returns dict[key] if it exists. Otherwise,
// sets dict[key] to default_ and returns default_.
function setDefault(dict, key, default_) {
  if (key in dict) {
    return dict[key];
  }
  dict[key] = default_;
  return default_;
}

// This code keeps track of a set of paths of the form [component_1,
// ..., component_n]. The components can be strings or booleans. The
// child is notified whenever a path is added or removed, and new
// children can request the current set of paths. The purpose is to
// keep track of all the observers and events that the child should
// monitor for the parent.
var NotificationTracker = {
  // _paths is a multi-level dictionary. Let's add paths [A, B] and
  // [A, C]. Then _paths will look like this:
  //   { 'A': { 'B': { '_count': 1 }, 'C': { '_count': 1 } } }
  // Each component in a path will be a key in some dictionary. At the
  // end, the _count property keeps track of how many instances of the
  // given path are present in _paths.
  _paths: {},

  init() {
    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.initialProcessData.remoteAddonsNotificationPaths = this._paths;
  },

  add(path) {
    let tracked = this._paths;
    for (let component of path) {
      tracked = setDefault(tracked, component, {});
    }
    let count = tracked._count || 0;
    count++;
    tracked._count = count;

    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.broadcastAsyncMessage("Addons:ChangeNotification", {path, count});
  },

  remove(path) {
    let tracked = this._paths;
    for (let component of path) {
      tracked = setDefault(tracked, component, {});
    }
    tracked._count--;

    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.broadcastAsyncMessage("Addons:ChangeNotification", {path, count: tracked._count});
  },
};
NotificationTracker.init();

// An interposition is an object with three properties: methods,
// getters, and setters. See multiprocessShims.js for an explanation
// of how these are used. The constructor here just allows one
// interposition to inherit members from another.
function Interposition(name, base) {
  this.name = name;
  if (base) {
    this.methods = Object.create(base.methods);
    this.getters = Object.create(base.getters);
    this.setters = Object.create(base.setters);
  } else {
    this.methods = Object.create(null);
    this.getters = Object.create(null);
    this.setters = Object.create(null);
  }
}

// This object is responsible for notifying the child when a new
// content policy is added or removed. It also runs all the registered
// add-on content policies when the child asks it to do so.
var ContentPolicyParent = {
  init() {
    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.addMessageListener("Addons:ContentPolicy:Run", this);

    this._policies = new Map();
  },

  addContentPolicy(addon, name, cid) {
    this._policies.set(name, cid);
    NotificationTracker.add(["content-policy", addon]);
  },

  removeContentPolicy(addon, name) {
    this._policies.delete(name);
    NotificationTracker.remove(["content-policy", addon]);
  },

  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "Addons:ContentPolicy:Run":
        return this.shouldLoad(aMessage.data, aMessage.objects);
    }
    return undefined;
  },

  shouldLoad(aData, aObjects) {
    for (let policyCID of this._policies.values()) {
      let policy;
      try {
        policy = Cc[policyCID].getService(Ci.nsIContentPolicy);
      } catch (e) {
        // Current Gecko behavior is to ignore entries that don't QI.
        continue;
      }
      try {
        let contentLocation = Services.io.newURI(aData.contentLocation);
        let requestOrigin = aData.requestOrigin ? Services.io.newURI(aData.requestOrigin) : null;

        let result = Prefetcher.withPrefetching(aData.prefetched, aObjects, () => {
          return policy.shouldLoad(aData.contentType,
                                   contentLocation,
                                   requestOrigin,
                                   aObjects.node,
                                   aData.mimeTypeGuess,
                                   null,
                                   aData.requestPrincipal);
        });
        if (result != Ci.nsIContentPolicy.ACCEPT && result != 0)
          return result;
      } catch (e) {
        Cu.reportError(e);
      }
    }

    return Ci.nsIContentPolicy.ACCEPT;
  },
};
ContentPolicyParent.init();

// This interposition intercepts calls to add or remove new content
// policies and forwards these requests to ContentPolicyParent.
var CategoryManagerInterposition = new Interposition("CategoryManagerInterposition");

CategoryManagerInterposition.methods.addCategoryEntry =
  function(addon, target, category, entry, value, persist, replace) {
    if (category == "content-policy") {
      CompatWarning.warn("content-policy should be added from the child process only.",
                         addon, CompatWarning.warnings.nsIContentPolicy);
      ContentPolicyParent.addContentPolicy(addon, entry, value);
    }

    target.addCategoryEntry(category, entry, value, persist, replace);
  };

CategoryManagerInterposition.methods.deleteCategoryEntry =
  function(addon, target, category, entry, persist) {
    if (category == "content-policy") {
      CompatWarning.warn("content-policy should be removed from the child process only.",
                         addon, CompatWarning.warnings.nsIContentPolicy);
      ContentPolicyParent.removeContentPolicy(addon, entry);
    }

    target.deleteCategoryEntry(category, entry, persist);
  };

// This shim handles the case where an add-on registers an about:
// protocol handler in the parent and we want the child to be able to
// use it. This code is pretty specific to Adblock's usage.
var AboutProtocolParent = {
  init() {
    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.addMessageListener("Addons:AboutProtocol:GetURIFlags", this);
    ppmm.addMessageListener("Addons:AboutProtocol:OpenChannel", this);
    this._protocols = [];
  },

  registerFactory(addon, class_, className, contractID, factory) {
    this._protocols.push({contractID, factory});
    NotificationTracker.add(["about-protocol", contractID, addon]);
  },

  unregisterFactory(addon, class_, factory) {
    for (let i = 0; i < this._protocols.length; i++) {
      if (this._protocols[i].factory == factory) {
        NotificationTracker.remove(["about-protocol", this._protocols[i].contractID, addon]);
        this._protocols.splice(i, 1);
        break;
      }
    }
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Addons:AboutProtocol:GetURIFlags":
        return this.getURIFlags(msg);
      case "Addons:AboutProtocol:OpenChannel":
        return this.openChannel(msg);
    }
    return undefined;
  },

  getURIFlags(msg) {
    let uri = Services.io.newURI(msg.data.uri);
    let contractID = msg.data.contractID;
    let module = Cc[contractID].getService(Ci.nsIAboutModule);
    try {
      return module.getURIFlags(uri);
    } catch (e) {
      Cu.reportError(e);
      return undefined;
    }
  },

  // We immediately read all the data out of the channel here and
  // return it to the child.
  openChannel(msg) {
    function wrapGetInterface(cpow) {
      return {
        getInterface(intf) { return cpow.getInterface(intf); }
      };
    }

    let uri = Services.io.newURI(msg.data.uri);
    let channelParams;
    if (msg.data.contentPolicyType === Ci.nsIContentPolicy.TYPE_DOCUMENT) {
      // For TYPE_DOCUMENT loads, we cannot recreate the loadinfo here in the
      // parent. In that case, treat this as a chrome (addon)-requested
      // subload. When we use the data in the child, we'll load it into the
      // correctly-principaled document.
      channelParams = {
        uri,
        contractID: msg.data.contractID,
        loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
        securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
        contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
      };
    } else {
      // We can recreate the loadinfo here in the parent for non TYPE_DOCUMENT
      // loads.
      channelParams = {
        uri,
        contractID: msg.data.contractID,
        loadingPrincipal: msg.data.loadingPrincipal,
        securityFlags: msg.data.securityFlags,
        contentPolicyType: msg.data.contentPolicyType
      };
    }

    try {
      let channel = NetUtil.newChannel(channelParams);

      // We're not allowed to set channel.notificationCallbacks to a
      // CPOW, since the setter for notificationCallbacks is in C++,
      // which can't tolerate CPOWs. Instead we just use a JS object
      // that wraps the CPOW.
      channel.notificationCallbacks = wrapGetInterface(msg.objects.notificationCallbacks);
      if (msg.objects.loadGroupNotificationCallbacks) {
        channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
      } else {
        channel.loadGroup = null;
      }
      let stream = channel.open2();
      let data = NetUtil.readInputStreamToString(stream, stream.available(), {});
      return {
        data,
        contentType: channel.contentType
      };
    } catch (e) {
      Cu.reportError(e);
      return undefined;
    }
  },
};
AboutProtocolParent.init();

var ComponentRegistrarInterposition = new Interposition("ComponentRegistrarInterposition");

ComponentRegistrarInterposition.methods.registerFactory =
  function(addon, target, class_, className, contractID, factory) {
    if (contractID && contractID.startsWith("@mozilla.org/network/protocol/about;1?")) {
      CompatWarning.warn("nsIAboutModule should be registered in the content process" +
                         " as well as the chrome process. (If you do that already, ignore" +
                         " this warning.)",
                         addon, CompatWarning.warnings.nsIAboutModule);
      AboutProtocolParent.registerFactory(addon, class_, className, contractID, factory);
    }

    target.registerFactory(class_, className, contractID, factory);
  };

ComponentRegistrarInterposition.methods.unregisterFactory =
  function(addon, target, class_, factory) {
    AboutProtocolParent.unregisterFactory(addon, class_, factory);
    target.unregisterFactory(class_, factory);
  };

// This object manages add-on observers that might fire in the child
// process. Rather than managing the observers itself, it uses the
// parent's observer service. When an add-on listens on topic T,
// ObserverParent asks the child process to listen on T. It also adds
// an observer in the parent for the topic e10s-T. When the T observer
// fires in the child, the parent fires all the e10s-T observers,
// passing them CPOWs for the subject and data. We don't want to use T
// in the parent because there might be non-add-on T observers that
// won't expect to get notified in this case.
var ObserverParent = {
  init() {
    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
               .getService(Ci.nsIMessageBroadcaster);
    ppmm.addMessageListener("Addons:Observer:Run", this);
  },

  addObserver(addon, observer, topic, ownsWeak) {
    Services.obs.addObserver(observer, "e10s-" + topic, ownsWeak);
    NotificationTracker.add(["observer", topic, addon]);
  },

  removeObserver(addon, observer, topic) {
    Services.obs.removeObserver(observer, "e10s-" + topic);
    NotificationTracker.remove(["observer", topic, addon]);
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Addons:Observer:Run":
        this.notify(msg.objects.subject, msg.objects.topic, msg.objects.data);
        break;
    }
  },

  notify(subject, topic, data) {
    let e = Services.obs.enumerateObservers("e10s-" + topic);
    while (e.hasMoreElements()) {
      let obs = e.getNext().QueryInterface(Ci.nsIObserver);
      try {
        obs.observe(subject, topic, data);
      } catch (e) {
        Cu.reportError(e);
      }
    }
  }
};
ObserverParent.init();

// We only forward observers for these topics.
var TOPIC_WHITELIST = [
  "content-document-global-created",
  "document-element-inserted",
  "dom-window-destroyed",
  "inner-window-destroyed",
  "outer-window-destroyed",
  "csp-on-violate-policy",
];

// This interposition listens for
// nsIObserverService.{add,remove}Observer.
var ObserverInterposition = new Interposition("ObserverInterposition");

ObserverInterposition.methods.addObserver =
  function(addon, target, observer, topic, ownsWeak) {
    if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
      CompatWarning.warn(`${topic} observer should be added from the child process only.`,
                         addon, CompatWarning.warnings.observers);

      ObserverParent.addObserver(addon, observer, topic);
    }

    target.addObserver(observer, topic, ownsWeak);
  };

ObserverInterposition.methods.removeObserver =
  function(addon, target, observer, topic) {
    if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
      ObserverParent.removeObserver(addon, observer, topic);
    }

    target.removeObserver(observer, topic);
  };

// This object is responsible for forwarding events from the child to
// the parent.
var EventTargetParent = {
  init() {
    // The _listeners map goes from targets (either <browser> elements
    // or windows) to a dictionary from event types to listeners.
    this._listeners = new WeakMap();

    let mm = Cc["@mozilla.org/globalmessagemanager;1"].
      getService(Ci.nsIMessageListenerManager);
    mm.addMessageListener("Addons:Event:Run", this);
  },

  // If target is not on the path from a <browser> element to the
  // window root, then we return null here to ignore the
  // target. Otherwise, if the target is a browser-specific element
  // (the <browser> or <tab> elements), then we return the
  // <browser>. If it's some generic element, then we return the
  // window itself.
  redirectEventTarget(target) {
    if (Cu.isCrossProcessWrapper(target)) {
      return null;
    }

    if (target instanceof Ci.nsIDOMChromeWindow) {
      return target;
    }

    if (target instanceof Ci.nsIDOMXULElement) {
      if (target.localName == "browser") {
        return target;
      } else if (target.localName == "tab") {
        return target.linkedBrowser;
      }

      // Check if |target| is somewhere on the patch from the
      // <tabbrowser> up to the root element.
      let window = target.ownerGlobal;
      if (window && target.contains(window.gBrowser)) {
        return window;
      }
    }

    return null;
  },

  // When a given event fires in the child, we fire it on the
  // <browser> element and the window since those are the two possible
  // results of redirectEventTarget.
  getTargets(browser) {
    let window = browser.ownerGlobal;
    return [browser, window];
  },

  addEventListener(addon, target, type, listener, useCapture, wantsUntrusted, delayedWarning) {
    let newTarget = this.redirectEventTarget(target);
    if (!newTarget) {
      return;
    }

    useCapture = useCapture || false;
    wantsUntrusted = wantsUntrusted || false;

    NotificationTracker.add(["event", type, useCapture, addon]);

    let listeners = this._listeners.get(newTarget);
    if (!listeners) {
      listeners = {};
      this._listeners.set(newTarget, listeners);
    }
    let forType = setDefault(listeners, type, []);

    // If there's already an identical listener, don't do anything.
    for (let i = 0; i < forType.length; i++) {
      if (forType[i].listener === listener &&
          forType[i].target === target &&
          forType[i].useCapture === useCapture &&
          forType[i].wantsUntrusted === wantsUntrusted) {
        return;
      }
    }

    forType.push({listener,
                  target,
                  wantsUntrusted,
                  useCapture,
                  delayedWarning});
  },

  removeEventListener(addon, target, type, listener, useCapture) {
    let newTarget = this.redirectEventTarget(target);
    if (!newTarget) {
      return;
    }

    useCapture = useCapture || false;

    let listeners = this._listeners.get(newTarget);
    if (!listeners) {
      return;
    }
    let forType = setDefault(listeners, type, []);

    for (let i = 0; i < forType.length; i++) {
      if (forType[i].listener === listener &&
          forType[i].target === target &&
          forType[i].useCapture === useCapture) {
        forType.splice(i, 1);
        NotificationTracker.remove(["event", type, useCapture, addon]);
        break;
      }
    }
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Addons:Event:Run":
        this.dispatch(msg.target, msg.data.type, msg.data.capturing,
                      msg.data.isTrusted, msg.data.prefetched, msg.objects);
        break;
    }
  },

  dispatch(browser, type, capturing, isTrusted, prefetched, cpows) {
    let event = cpows.event;
    let eventTarget = cpows.eventTarget;
    let targets = this.getTargets(browser);
    for (let target of targets) {
      let listeners = this._listeners.get(target);
      if (!listeners) {
        continue;
      }
      let forType = setDefault(listeners, type, []);

      // Make a copy in case they call removeEventListener in the listener.
      let handlers = [];
      for (let {listener, target, wantsUntrusted, useCapture, delayedWarning} of forType) {
        if ((wantsUntrusted || isTrusted) && useCapture == capturing) {
          // Issue a warning for this listener.
          delayedWarning();

          handlers.push([listener, target]);
        }
      }

      for (let [handler, target] of handlers) {
        let EventProxy = {
          get(knownProps, name) {
            if (knownProps.hasOwnProperty(name))
              return knownProps[name];
            return event[name];
          }
        }
        let proxyEvent = new Proxy({
          currentTarget: target,
          target: eventTarget,
          type,
          QueryInterface(iid) {
            if (iid.equals(Ci.nsISupports) ||
                iid.equals(Ci.nsIDOMEventTarget))
              return proxyEvent;
            // If event deson't support the interface this will throw. If it
            // does we want to return the proxy
            event.QueryInterface(iid);
            return proxyEvent;
          }
        }, EventProxy);

        try {
          Prefetcher.withPrefetching(prefetched, cpows, () => {
            if ("handleEvent" in handler) {
              handler.handleEvent(proxyEvent);
            } else {
              handler.call(eventTarget, proxyEvent);
            }
          });
        } catch (e) {
          Cu.reportError(e);
        }
      }
    }
  }
};
EventTargetParent.init();

// This function returns a listener that will remove itself the first time
// it is fired.
var selfRemovingListeners = new WeakMap();
function makeSelfRemovingListener(addon, target, type, listener, useCapture) {
  if (selfRemovingListeners.has(listener)) {
    return selfRemovingListeners.get(listener);
  }

  function selfRemovingListener(event) {
    EventTargetInterposition.methods.removeEventListener(addon, target, type,
                                                         listener, useCapture);
    if ("handleEvent" in listener) {
      listener.handleEvent(event);
    } else {
      listener.call(event.target, event);
    }
  }
  selfRemovingListeners.set(listener, selfRemovingListener);

  return selfRemovingListener;
}

// This function returns a listener that will not fire on events where
// the target is a remote xul:browser element itself. We'd rather let
// the child process handle the event and pass it up via
// EventTargetParent.
var filteringListeners = new WeakMap();
function makeFilteringListener(eventType, listener) {
  // Some events are actually targeted at the <browser> element
  // itself, so we only handle the ones where know that won't happen.
  let eventTypes = ["mousedown", "mouseup", "click"];
  if (!eventTypes.includes(eventType) || !listener ||
      (typeof listener != "object" && typeof listener != "function")) {
    return listener;
  }

  if (filteringListeners.has(listener)) {
    return filteringListeners.get(listener);
  }

  function filter(event) {
    let target = event.originalTarget;
    if (target instanceof Ci.nsIDOMXULElement &&
        target.localName == "browser" &&
        target.isRemoteBrowser) {
      return;
    }

    if ("handleEvent" in listener) {
      listener.handleEvent(event);
    } else {
      listener.call(event.target, event);
    }
  }
  filteringListeners.set(listener, filter);
  return filter;
}

// This interposition redirects addEventListener and
// removeEventListener to EventTargetParent.
var EventTargetInterposition = new Interposition("EventTargetInterposition");

EventTargetInterposition.methods.addEventListener =
  function(addon, target, type, listener, options, wantsUntrusted) {
    let delayed = CompatWarning.delayedWarning(
      `Registering a ${type} event listener on content DOM nodes` +
        " needs to happen in the content process.",
      addon, CompatWarning.warnings.DOM_events);

    let useCapture =
      options === true || (typeof options == "object" && options.capture) || false;
    if (typeof options == "object" && options.once) {
      listener = makeSelfRemovingListener(addon, target, type, listener, useCapture);
    }

    EventTargetParent.addEventListener(addon, target, type, listener,
                                       useCapture, wantsUntrusted, delayed);
    target.addEventListener(type, makeFilteringListener(type, listener),
                            useCapture, wantsUntrusted);
  };

EventTargetInterposition.methods.removeEventListener =
  function(addon, target, type, listener, options) {
    let useCapture =
      options === true || (typeof options == "object" && options.capture) || false;

    if (selfRemovingListeners.has(listener)) {
      listener = selfRemovingListeners.get(listener);
    }

    EventTargetParent.removeEventListener(addon, target, type, listener, useCapture);
    target.removeEventListener(type, makeFilteringListener(type, listener), useCapture);
  };

// This interposition intercepts accesses to |rootTreeItem| on a child
// process docshell. In the child, each docshell is its own
// root. However, add-ons expect the root to be the chrome docshell,
// so we make that happen here.
var ContentDocShellTreeItemInterposition = new Interposition("ContentDocShellTreeItemInterposition");

ContentDocShellTreeItemInterposition.getters.rootTreeItem =
  function(addon, target) {
    // The chrome global in the child.
    let chromeGlobal = target.rootTreeItem
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIContentFrameMessageManager);

    // Map it to a <browser> element and window.
    let browser = RemoteAddonsParent.globalToBrowser.get(chromeGlobal);
    if (!browser) {
      // Somehow we have a CPOW from the child, but it hasn't sent us
      // its global yet. That shouldn't happen, but return null just
      // in case.
      return null;
    }

    let chromeWin = browser.ownerGlobal;

    // Return that window's docshell.
    return chromeWin.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebNavigation)
      .QueryInterface(Ci.nsIDocShellTreeItem);
  };

function chromeGlobalForContentWindow(window) {
    return window
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebNavigation)
      .QueryInterface(Ci.nsIDocShellTreeItem)
      .rootTreeItem
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIContentFrameMessageManager);
}

// This object manages sandboxes created with content principals in
// the parent. We actually create these sandboxes in the child process
// so that the code loaded into them runs there. The resulting sandbox
// object is a CPOW. This is primarly useful for Greasemonkey.
var SandboxParent = {
  componentsMap: new WeakMap(),

  makeContentSandbox(addon, chromeGlobal, principals, ...rest) {
    CompatWarning.warn("This sandbox should be created from the child process.",
                       addon, CompatWarning.warnings.sandboxes);
    if (rest.length) {
      // Do a shallow copy of the options object into the child
      // process. This way we don't have to access it through a Chrome
      // object wrapper, which would require __exposedProps__.
      //
      // The only object property here is sandboxPrototype. We assume
      // it's a child process object (since that's what Greasemonkey
      // does) and leave it alone.
      let options = rest[0];
      let optionsCopy = new chromeGlobal.Object();
      for (let prop in options) {
        optionsCopy[prop] = options[prop];
      }
      rest[0] = optionsCopy;
    }

    // Make a sandbox in the child.
    let cu = chromeGlobal.Components.utils;
    let sandbox = cu.Sandbox(principals, ...rest);

    // We need to save the sandbox in the child so it won't get
    // GCed. The child will drop this reference at the next
    // navigation.
    chromeGlobal.addSandbox(sandbox);

    // The sandbox CPOW will be kept alive by whomever we return it
    // to. Its lifetime is unrelated to that of the sandbox object in
    // the child.
    this.componentsMap.set(sandbox, cu);
    return sandbox;
  },

  evalInSandbox(code, sandbox, ...rest) {
    let cu = this.componentsMap.get(sandbox);
    return cu.evalInSandbox(code, sandbox, ...rest);
  }
};

// This interposition redirects calls to Cu.Sandbox and
// Cu.evalInSandbox to SandboxParent if the principals are content
// principals.
var ComponentsUtilsInterposition = new Interposition("ComponentsUtilsInterposition");

ComponentsUtilsInterposition.methods.Sandbox =
  function(addon, target, principals, ...rest) {
    // principals can be a window object, a list of window objects, or
    // something else (a string, for example).
    if (principals &&
        typeof(principals) == "object" &&
        Cu.isCrossProcessWrapper(principals) &&
        principals instanceof Ci.nsIDOMWindow) {
      let chromeGlobal = chromeGlobalForContentWindow(principals);
      return SandboxParent.makeContentSandbox(addon, chromeGlobal, principals, ...rest);
    } else if (principals &&
               typeof(principals) == "object" &&
               "every" in principals &&
               principals.length &&
               principals.every(e => e instanceof Ci.nsIDOMWindow && Cu.isCrossProcessWrapper(e))) {
      let chromeGlobal = chromeGlobalForContentWindow(principals[0]);

      // The principals we pass to the content process must use an
      // Array object from the content process.
      let array = new chromeGlobal.Array();
      for (let i = 0; i < principals.length; i++) {
        array[i] = principals[i];
      }
      return SandboxParent.makeContentSandbox(addon, chromeGlobal, array, ...rest);
    }
    return Components.utils.Sandbox(principals, ...rest);
  };

ComponentsUtilsInterposition.methods.evalInSandbox =
  function(addon, target, code, sandbox, ...rest) {
    if (sandbox && Cu.isCrossProcessWrapper(sandbox)) {
      return SandboxParent.evalInSandbox(code, sandbox, ...rest);
    }
    return Components.utils.evalInSandbox(code, sandbox, ...rest);
  };

// This interposition handles cases where an add-on tries to import a
// chrome XUL node into a content document. It doesn't actually do the
// import, which we can't support. It just avoids throwing an
// exception.
var ContentDocumentInterposition = new Interposition("ContentDocumentInterposition");

ContentDocumentInterposition.methods.importNode =
  function(addon, target, node, deep) {
    if (!Cu.isCrossProcessWrapper(node)) {
      // Trying to import a node from the parent process into the
      // child process. We don't support this now. Video Download
      // Helper does this in domhook-service.js to add a XUL
      // popupmenu to content.
      Cu.reportError("Calling contentDocument.importNode on a XUL node is not allowed.");
      return node;
    }

    return target.importNode(node, deep);
  };

// This interposition ensures that calling browser.docShell from an
// add-on returns a CPOW around the docshell.
var RemoteBrowserElementInterposition = new Interposition("RemoteBrowserElementInterposition",
                                                          EventTargetInterposition);

RemoteBrowserElementInterposition.getters.docShell = function(addon, target) {
  CompatWarning.warn("Direct access to content docshell will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);
  let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(target);
  if (!remoteChromeGlobal) {
    // We may not have any messages from this tab yet.
    return null;
  }
  return remoteChromeGlobal.docShell;
};

RemoteBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
  CompatWarning.warn("Direct access to browser.sessionHistory will no longer " +
                     "work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  return getSessionHistory(target);
}

// We use this in place of the real browser.contentWindow if we
// haven't yet received a CPOW for the child process's window. This
// happens if the tab has just started loading.
function makeDummyContentWindow(browser) {
  let dummyContentWindow = {
    set location(url) {
      browser.loadURI(url, null, null);
    },
    document: {
      readyState: "loading",
      location: { href: "about:blank" }
    },
    frames: [],
  };
  dummyContentWindow.top = dummyContentWindow;
  dummyContentWindow.document.defaultView = dummyContentWindow;
  browser._contentWindow = dummyContentWindow;
  return dummyContentWindow;
}

RemoteBrowserElementInterposition.getters.contentWindow = function(addon, target) {
  CompatWarning.warn("Direct access to browser.contentWindow will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  // If we don't have a CPOW yet, just return something we can use for
  // setting the location. This is useful for tests that create a tab
  // and immediately set contentWindow.location.
  if (!target.contentWindowAsCPOW) {
    CompatWarning.warn("CPOW to the content window does not exist yet, dummy content window is created.");
    return makeDummyContentWindow(target);
  }
  return target.contentWindowAsCPOW;
};

function getContentDocument(addon, browser) {
  if (!browser.contentWindowAsCPOW) {
    return makeDummyContentWindow(browser).document;
  }

  let doc = Prefetcher.lookupInCache(addon, browser.contentWindowAsCPOW, "document");
  if (doc) {
    return doc;
  }

  return browser.contentWindowAsCPOW.document;
}

function getSessionHistory(browser) {
  let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(browser);
  if (!remoteChromeGlobal) {
    CompatWarning.warn("CPOW for the remote browser docShell hasn't been received yet.");
    // We may not have any messages from this tab yet.
    return null;
  }
  return remoteChromeGlobal.docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
}

RemoteBrowserElementInterposition.getters.contentDocument = function(addon, target) {
  CompatWarning.warn("Direct access to browser.contentDocument will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  return getContentDocument(addon, target);
};

var TabBrowserElementInterposition = new Interposition("TabBrowserElementInterposition",
                                                       EventTargetInterposition);

TabBrowserElementInterposition.getters.contentWindow = function(addon, target) {
  CompatWarning.warn("Direct access to gBrowser.contentWindow will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  if (!target.selectedBrowser.contentWindowAsCPOW) {
    return makeDummyContentWindow(target.selectedBrowser);
  }
  return target.selectedBrowser.contentWindowAsCPOW;
};

TabBrowserElementInterposition.getters.contentDocument = function(addon, target) {
  CompatWarning.warn("Direct access to gBrowser.contentDocument will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  let browser = target.selectedBrowser;
  return getContentDocument(addon, browser);
};

TabBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
  CompatWarning.warn("Direct access to gBrowser.sessionHistory will no " +
                     "longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);
  let browser = target.selectedBrowser;
  if (!browser.isRemoteBrowser) {
    return browser.sessionHistory;
  }
  return getSessionHistory(browser);
};

// This function returns a wrapper around an
// nsIWebProgressListener. When the wrapper is invoked, it calls the
// real listener but passes CPOWs for the nsIWebProgress and
// nsIRequest arguments.
var progressListeners = {global: new WeakMap(), tabs: new WeakMap()};
function wrapProgressListener(kind, listener) {
  if (progressListeners[kind].has(listener)) {
    return progressListeners[kind].get(listener);
  }

  let ListenerHandler = {
    get(target, name) {
      if (name.startsWith("on")) {
        return function(...args) {
          listener[name].apply(listener, RemoteWebProgressManager.argumentsForAddonListener(kind, args));
        };
      }

      return listener[name];
    }
  };
  let listenerProxy = new Proxy(listener, ListenerHandler);

  progressListeners[kind].set(listener, listenerProxy);
  return listenerProxy;
}

TabBrowserElementInterposition.methods.addProgressListener = function(addon, target, listener) {
  if (!target.ownerGlobal.gMultiProcessBrowser) {
    return target.addProgressListener(listener);
  }

  NotificationTracker.add(["web-progress", addon]);
  return target.addProgressListener(wrapProgressListener("global", listener));
};

TabBrowserElementInterposition.methods.removeProgressListener = function(addon, target, listener) {
  if (!target.ownerGlobal.gMultiProcessBrowser) {
    return target.removeProgressListener(listener);
  }

  NotificationTracker.remove(["web-progress", addon]);
  return target.removeProgressListener(wrapProgressListener("global", listener));
};

TabBrowserElementInterposition.methods.addTabsProgressListener = function(addon, target, listener) {
  if (!target.ownerGlobal.gMultiProcessBrowser) {
    return target.addTabsProgressListener(listener);
  }

  NotificationTracker.add(["web-progress", addon]);
  return target.addTabsProgressListener(wrapProgressListener("tabs", listener));
};

TabBrowserElementInterposition.methods.removeTabsProgressListener = function(addon, target, listener) {
  if (!target.ownerGlobal.gMultiProcessBrowser) {
    return target.removeTabsProgressListener(listener);
  }

  NotificationTracker.remove(["web-progress", addon]);
  return target.removeTabsProgressListener(wrapProgressListener("tabs", listener));
};

var ChromeWindowInterposition = new Interposition("ChromeWindowInterposition",
                                                  EventTargetInterposition);

// _content is for older add-ons like pinboard and all-in-one gestures
// that should be using content instead.
ChromeWindowInterposition.getters.content =
ChromeWindowInterposition.getters._content = function(addon, target) {
  CompatWarning.warn("Direct access to chromeWindow.content will no longer work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  let browser = target.gBrowser.selectedBrowser;
  if (!browser.contentWindowAsCPOW) {
    return makeDummyContentWindow(browser);
  }
  return browser.contentWindowAsCPOW;
};

var RemoteWebNavigationInterposition = new Interposition("RemoteWebNavigation");

RemoteWebNavigationInterposition.getters.sessionHistory = function(addon, target) {
  CompatWarning.warn("Direct access to webNavigation.sessionHistory will no longer " +
                     "work in the chrome process.",
                     addon, CompatWarning.warnings.content);

  if (target instanceof Ci.nsIDocShell) {
    // We must have a non-remote browser, so we can go ahead
    // and just return the real sessionHistory.
    return target.sessionHistory;
  }

  let impl = target.wrappedJSObject;
  if (!impl) {
    return null;
  }

  let browser = impl._browser;

  return getSessionHistory(browser);
}

var RemoteAddonsParent = {
  init() {
    let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
    mm.addMessageListener("Addons:RegisterGlobal", this);

    Services.ppmm.initialProcessData.remoteAddonsParentInitted = true;

    Services.ppmm.loadProcessScript("data:,new " + function() {
      Components.utils.import("resource://gre/modules/RemoteAddonsChild.jsm");
    }, true);

    this.globalToBrowser = new WeakMap();
    this.browserToGlobal = new WeakMap();
  },

  getInterfaceInterpositions() {
    let result = {};

    function register(intf, interp) {
      result[intf.number] = interp;
    }

    register(Ci.nsICategoryManager, CategoryManagerInterposition);
    register(Ci.nsIComponentRegistrar, ComponentRegistrarInterposition);
    register(Ci.nsIObserverService, ObserverInterposition);
    register(Ci.nsIXPCComponents_Utils, ComponentsUtilsInterposition);
    register(Ci.nsIWebNavigation, RemoteWebNavigationInterposition);

    return result;
  },

  getTaggedInterpositions() {
    let result = {};

    function register(tag, interp) {
      result[tag] = interp;
    }

    register("EventTarget", EventTargetInterposition);
    register("ContentDocShellTreeItem", ContentDocShellTreeItemInterposition);
    register("ContentDocument", ContentDocumentInterposition);
    register("RemoteBrowserElement", RemoteBrowserElementInterposition);
    register("TabBrowserElement", TabBrowserElementInterposition);
    register("ChromeWindow", ChromeWindowInterposition);

    return result;
  },

  receiveMessage(msg) {
    switch (msg.name) {
    case "Addons:RegisterGlobal":
      this.browserToGlobal.set(msg.target, msg.objects.global);
      this.globalToBrowser.set(msg.objects.global, msg.target);
      break;
    }
  }
};
PK
!<2modules/RemoteController.jsm// -*- 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 = ["RemoteController"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

function RemoteController(browser) {
  this._browser = browser;

  // A map of commands that have had their enabled/disabled state assigned. The
  // value of each key will be true if enabled, and false if disabled.
  this._supportedCommands = { };
}

RemoteController.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIController,
                                         Ci.nsICommandController]),

  isCommandEnabled(aCommand) {
    return this._supportedCommands[aCommand] || false;
  },

  supportsCommand(aCommand) {
    return aCommand in this._supportedCommands;
  },

  doCommand(aCommand) {
    this._browser.messageManager.sendAsyncMessage("ControllerCommands:Do", aCommand);
  },

  getCommandStateWithParams(aCommand, aCommandParams) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  doCommandWithParams(aCommand, aCommandParams) {
    let cmd = {
      cmd: aCommand,
      params: null
    };
    if (aCommand == "cmd_lookUpDictionary") {
      // Although getBoundingClientRect of the element is logical pixel, but
      // x and y parameter of cmd_lookUpDictionary are device pixel.
      // So we need calculate child process's coordinate using correct unit.
      let rect = this._browser.getBoundingClientRect();
      let scale = this._browser.ownerGlobal.devicePixelRatio;
      cmd.params = {
        x:  {
          type: "long",
          value: aCommandParams.getLongValue("x") - rect.left * scale
        },
        y: {
          type: "long",
          value: aCommandParams.getLongValue("y") - rect.top * scale
        }
      };
    } else {
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    }
    this._browser.messageManager.sendAsyncMessage(
      "ControllerCommands:DoWithParams", cmd);
  },

  getSupportedCommands(aCount, aCommands) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  onEvent() {},

  // This is intended to be called from the remote-browser binding to update
  // the enabled and disabled commands.
  enableDisableCommands(aAction,
                                  aEnabledLength, aEnabledCommands,
                                  aDisabledLength, aDisabledCommands) {
    // Clear the list first
    this._supportedCommands = { };

    for (let c = 0; c < aEnabledLength; c++) {
      this._supportedCommands[aEnabledCommands[c]] = true;
    }

    for (let c = 0; c < aDisabledLength; c++) {
      this._supportedCommands[aDisabledCommands[c]] = false;
    }

    this._browser.ownerGlobal.updateCommands(aAction);
  }
};
PK
!<g))modules/RemoteFinder.jsm// -*- 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/.

this.EXPORTED_SYMBOLS = ["RemoteFinder", "RemoteFinderListener"];

const { interfaces: Ci, classes: Cc, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");

XPCOMUtils.defineLazyGetter(this, "GetClipboardSearchString",
  () => Cu.import("resource://gre/modules/Finder.jsm", {}).GetClipboardSearchString
);
XPCOMUtils.defineLazyGetter(this, "Rect",
  () => Cu.import("resource://gre/modules/Geometry.jsm", {}).Rect
);

function RemoteFinder(browser) {
  this._listeners = new Set();
  this._searchString = null;

  this.swapBrowser(browser);
}

RemoteFinder.prototype = {
  destroy() {},

  swapBrowser(aBrowser) {
    if (this._messageManager) {
      this._messageManager.removeMessageListener("Finder:Result", this);
      this._messageManager.removeMessageListener("Finder:MatchesResult", this);
      this._messageManager.removeMessageListener("Finder:CurrentSelectionResult", this);
      this._messageManager.removeMessageListener("Finder:HighlightFinished", this);
    } else {
      aBrowser.messageManager.sendAsyncMessage("Finder:Initialize");
    }

    this._browser = aBrowser;
    this._messageManager = this._browser.messageManager;
    this._messageManager.addMessageListener("Finder:Result", this);
    this._messageManager.addMessageListener("Finder:MatchesResult", this);
    this._messageManager.addMessageListener("Finder:CurrentSelectionResult", this);
    this._messageManager.addMessageListener("Finder:HighlightFinished", this);

    // Ideally listeners would have removed themselves but that doesn't happen
    // right now
    this._listeners.clear();
  },

  addResultListener(aListener) {
    this._listeners.add(aListener);
  },

  removeResultListener(aListener) {
    this._listeners.delete(aListener);
  },

  receiveMessage(aMessage) {
    // Only Finder:Result messages have the searchString field.
    let callback;
    let params;
    switch (aMessage.name) {
      case "Finder:Result":
        this._searchString = aMessage.data.searchString;
        // The rect stops being a Geometry.jsm:Rect over IPC.
        if (aMessage.data.rect) {
          aMessage.data.rect = Rect.fromRect(aMessage.data.rect);
        }
        callback = "onFindResult";
        params = [ aMessage.data ];
        break;
      case "Finder:MatchesResult":
        callback = "onMatchesCountResult";
        params = [ aMessage.data ];
        break;
      case "Finder:CurrentSelectionResult":
        callback = "onCurrentSelection";
        params = [ aMessage.data.selection, aMessage.data.initial ];
        break;
      case "Finder:HighlightFinished":
        callback = "onHighlightFinished";
        params = [ aMessage.data ];
        break;
    }

    for (let l of this._listeners) {
      // Don't let one callback throwing stop us calling the rest
      try {
        l[callback].apply(l, params);
      } catch (e) {
        if (!l[callback]) {
          Cu.reportError(`Missing ${callback} callback on RemoteFinderListener`);
        }
        Cu.reportError(e);
      }
    }
  },

  get searchString() {
    return this._searchString;
  },

  get clipboardSearchString() {
    return GetClipboardSearchString(this._browser.loadContext);
  },

  setSearchStringToSelection() {
    this._browser.messageManager.sendAsyncMessage("Finder:SetSearchStringToSelection", {});
  },

  set caseSensitive(aSensitive) {
    this._browser.messageManager.sendAsyncMessage("Finder:CaseSensitive",
                                                  { caseSensitive: aSensitive });
  },

  set entireWord(aEntireWord) {
    this._browser.messageManager.sendAsyncMessage("Finder:EntireWord",
                                                  { entireWord: aEntireWord });
  },

  getInitialSelection() {
    this._browser.messageManager.sendAsyncMessage("Finder:GetInitialSelection", {});
  },

  fastFind(aSearchString, aLinksOnly, aDrawOutline) {
    this._browser.messageManager.sendAsyncMessage("Finder:FastFind",
                                                  { searchString: aSearchString,
                                                    linksOnly: aLinksOnly,
                                                    drawOutline: aDrawOutline });
  },

  findAgain(aFindBackwards, aLinksOnly, aDrawOutline) {
    this._browser.messageManager.sendAsyncMessage("Finder:FindAgain",
                                                  { findBackwards: aFindBackwards,
                                                    linksOnly: aLinksOnly,
                                                    drawOutline: aDrawOutline });
  },

  highlight(aHighlight, aWord, aLinksOnly) {
    this._browser.messageManager.sendAsyncMessage("Finder:Highlight",
                                                  { highlight: aHighlight,
                                                    linksOnly: aLinksOnly,
                                                    word: aWord });
  },

  enableSelection() {
    this._browser.messageManager.sendAsyncMessage("Finder:EnableSelection");
  },

  removeSelection() {
    this._browser.messageManager.sendAsyncMessage("Finder:RemoveSelection");
  },

  focusContent() {
    // Allow Finder listeners to cancel focusing the content.
    for (let l of this._listeners) {
      try {
        if ("shouldFocusContent" in l &&
            !l.shouldFocusContent())
          return;
      } catch (ex) {
        Cu.reportError(ex);
      }
    }

    this._browser.focus();
    this._browser.messageManager.sendAsyncMessage("Finder:FocusContent");
  },

  onFindbarClose() {
    this._browser.messageManager.sendAsyncMessage("Finder:FindbarClose");
  },

  onFindbarOpen() {
    this._browser.messageManager.sendAsyncMessage("Finder:FindbarOpen");
  },

  onModalHighlightChange(aUseModalHighlight) {
    this._browser.messageManager.sendAsyncMessage("Finder:ModalHighlightChange", {
      useModalHighlight: aUseModalHighlight
    });
  },

  onHighlightAllChange(aHighlightAll) {
    this._browser.messageManager.sendAsyncMessage("Finder:HighlightAllChange", {
      highlightAll: aHighlightAll
    });
  },

  keyPress(aEvent) {
    this._browser.messageManager.sendAsyncMessage("Finder:KeyPress",
                                                  { keyCode: aEvent.keyCode,
                                                    ctrlKey: aEvent.ctrlKey,
                                                    metaKey: aEvent.metaKey,
                                                    altKey: aEvent.altKey,
                                                    shiftKey: aEvent.shiftKey });
  },

  requestMatchesCount(aSearchString, aLinksOnly) {
    this._browser.messageManager.sendAsyncMessage("Finder:MatchesCount",
                                                  { searchString: aSearchString,
                                                    linksOnly: aLinksOnly });
  }
}

function RemoteFinderListener(global) {
  let {Finder} = Cu.import("resource://gre/modules/Finder.jsm", {});
  this._finder = new Finder(global.docShell);
  this._finder.addResultListener(this);
  this._global = global;

  for (let msg of this.MESSAGES) {
    global.addMessageListener(msg, this);
  }
}

RemoteFinderListener.prototype = {
  MESSAGES: [
    "Finder:CaseSensitive",
    "Finder:EntireWord",
    "Finder:FastFind",
    "Finder:FindAgain",
    "Finder:SetSearchStringToSelection",
    "Finder:GetInitialSelection",
    "Finder:Highlight",
    "Finder:HighlightAllChange",
    "Finder:EnableSelection",
    "Finder:RemoveSelection",
    "Finder:FocusContent",
    "Finder:FindbarClose",
    "Finder:FindbarOpen",
    "Finder:KeyPress",
    "Finder:MatchesCount",
    "Finder:ModalHighlightChange"
  ],

  onFindResult(aData) {
    this._global.sendAsyncMessage("Finder:Result", aData);
  },

  // When the child receives messages with results of requestMatchesCount,
  // it passes them forward to the parent.
  onMatchesCountResult(aData) {
    this._global.sendAsyncMessage("Finder:MatchesResult", aData);
  },

  onHighlightFinished(aData) {
    this._global.sendAsyncMessage("Finder:HighlightFinished", aData);
  },

  receiveMessage(aMessage) {
    let data = aMessage.data;

    switch (aMessage.name) {
      case "Finder:CaseSensitive":
        this._finder.caseSensitive = data.caseSensitive;
        break;

      case "Finder:EntireWord":
        this._finder.entireWord = data.entireWord;
        break;

      case "Finder:SetSearchStringToSelection": {
        let selection = this._finder.setSearchStringToSelection();
        this._global.sendAsyncMessage("Finder:CurrentSelectionResult",
                                      { selection,
                                        initial: false });
        break;
      }

      case "Finder:GetInitialSelection": {
        let selection = this._finder.getActiveSelectionText();
        this._global.sendAsyncMessage("Finder:CurrentSelectionResult",
                                      { selection,
                                        initial: true });
        break;
      }

      case "Finder:FastFind":
        this._finder.fastFind(data.searchString, data.linksOnly, data.drawOutline);
        break;

      case "Finder:FindAgain":
        this._finder.findAgain(data.findBackwards, data.linksOnly, data.drawOutline);
        break;

      case "Finder:Highlight":
        this._finder.highlight(data.highlight, data.word, data.linksOnly);
        break;

      case "Finder:HighlightAllChange":
        this._finder.onHighlightAllChange(data.highlightAll);
        break;

      case "Finder:EnableSelection":
        this._finder.enableSelection();
        break;

      case "Finder:RemoveSelection":
        this._finder.removeSelection();
        break;

      case "Finder:FocusContent":
        this._finder.focusContent();
        break;

      case "Finder:FindbarClose":
        this._finder.onFindbarClose();
        break;

      case "Finder:FindbarOpen":
        this._finder.onFindbarOpen();
        break;

      case "Finder:KeyPress":
        this._finder.keyPress(data);
        break;

      case "Finder:MatchesCount":
        this._finder.requestMatchesCount(data.searchString, data.linksOnly);
        break;

      case "Finder:ModalHighlightChange":
        this._finder.onModalHighlightChange(data.useModalHighlight);
        break;
    }
  }
};
PK
!<5Rm?m?modules/RemotePageManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["RemotePages", "RemotePageManager", "PageListener"];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

function MessageListener() {
  this.listeners = new Map();
}

MessageListener.prototype = {
  keys() {
    return this.listeners.keys();
  },

  has(name) {
    return this.listeners.has(name);
  },

  callListeners(message) {
    let listeners = this.listeners.get(message.name);
    if (!listeners) {
      return;
    }

    for (let listener of listeners.values()) {
      try {
        listener(message);
      } catch (e) {
        Cu.reportError(e);
      }
    }
  },

  addMessageListener(name, callback) {
    if (!this.listeners.has(name))
      this.listeners.set(name, new Set([callback]));
    else
      this.listeners.get(name).add(callback);
  },

  removeMessageListener(name, callback) {
    if (!this.listeners.has(name))
      return;

    this.listeners.get(name).delete(callback);
  },
}


/**
 * Creates a RemotePages object which listens for new remote pages of a
 * particular URL. A "RemotePage:Init" message will be dispatched to this object
 * for every page loaded. Message listeners added to this object receive
 * messages from all loaded pages from the requested url.
 */
this.RemotePages = function(url) {
  this.url = url;
  this.messagePorts = new Set();
  this.listener = new MessageListener();
  this.destroyed = false;

  RemotePageManager.addRemotePageListener(url, this.portCreated.bind(this));
  this.portMessageReceived = this.portMessageReceived.bind(this);
}

RemotePages.prototype = {
  url: null,
  messagePorts: null,
  listener: null,
  destroyed: null,

  destroy() {
    RemotePageManager.removeRemotePageListener(this.url);

    for (let port of this.messagePorts.values()) {
      this.removeMessagePort(port);
    }

    this.messagePorts = null;
    this.listener = null;
    this.destroyed = true;
  },

  // Called when a page matching the url has loaded in a frame.
  portCreated(port) {
    this.messagePorts.add(port);

    port.addMessageListener("RemotePage:Unload", this.portMessageReceived);

    for (let name of this.listener.keys()) {
      this.registerPortListener(port, name);
    }

    this.listener.callListeners({ target: port, name: "RemotePage:Init" });
  },

  // A message has been received from one of the pages
  portMessageReceived(message) {
    if (message.name == "RemotePage:Unload")
      this.removeMessagePort(message.target);

    this.listener.callListeners(message);
  },

  // A page has closed
  removeMessagePort(port) {
    for (let name of this.listener.keys()) {
      port.removeMessageListener(name, this.portMessageReceived);
    }

    port.removeMessageListener("RemotePage:Unload", this.portMessageReceived);
    this.messagePorts.delete(port);
  },

  registerPortListener(port, name) {
    port.addMessageListener(name, this.portMessageReceived);
  },

  // Sends a message to all known pages
  sendAsyncMessage(name, data = null) {
    for (let port of this.messagePorts.values()) {
      try {
        port.sendAsyncMessage(name, data);
      } catch (e) {
        // Unless the port is in the process of unloading, something strange
        // happened but allow other ports to receive the message
        if (e.result !== Cr.NS_ERROR_NOT_INITIALIZED)
          Cu.reportError(e);
      }
    }
  },

  addMessageListener(name, callback) {
    if (this.destroyed) {
      throw new Error("RemotePages has been destroyed");
    }

    if (!this.listener.has(name)) {
      for (let port of this.messagePorts.values()) {
        this.registerPortListener(port, name)
      }
    }

    this.listener.addMessageListener(name, callback);
  },

  removeMessageListener(name, callback) {
    if (this.destroyed) {
      throw new Error("RemotePages has been destroyed");
    }

    this.listener.removeMessageListener(name, callback);
  },

  portsForBrowser(browser) {
    return [...this.messagePorts].filter(port => port.browser == browser);
  },
};


// Only exposes the public properties of the MessagePort
function publicMessagePort(port) {
  let properties = ["addMessageListener", "removeMessageListener",
                    "sendAsyncMessage", "destroy"];

  let clean = {};
  for (let property of properties) {
    clean[property] = port[property].bind(port);
  }

  Object.defineProperty(clean, "portID", {
    get() {
      return port.portID;
    }
  });

  if (port instanceof ChromeMessagePort) {
    Object.defineProperty(clean, "browser", {
      get() {
        return port.browser;
      }
    });
  }

  return clean;
}


/*
 * A message port sits on each side of the process boundary for every remote
 * page. Each has a port ID that is unique to the message manager it talks
 * through.
 *
 * We roughly implement the same contract as nsIMessageSender and
 * nsIMessageListenerManager
 */
function MessagePort(messageManager, portID) {
  this.messageManager = messageManager;
  this.portID = portID;
  this.destroyed = false;
  this.listener = new MessageListener();

  this.message = this.message.bind(this);
  this.messageManager.addMessageListener("RemotePage:Message", this.message);
}

MessagePort.prototype = {
  messageManager: null,
  portID: null,
  destroyed: null,
  listener: null,
  _browser: null,
  remotePort: null,

  // Called when the message manager used to connect to the other process has
  // changed, i.e. when a tab is detached.
  swapMessageManager(messageManager) {
    this.messageManager.removeMessageListener("RemotePage:Message", this.message);

    this.messageManager = messageManager;

    this.messageManager.addMessageListener("RemotePage:Message", this.message);
  },

  /* Adds a listener for messages. Many callbacks can be registered for the
   * same message if necessary. An attempt to register the same callback for the
   * same message twice will be ignored. When called the callback is passed an
   * object with these properties:
   *   target: This message port
   *   name:   The message name
   *   data:   Any data sent with the message
   */
  addMessageListener(name, callback) {
    if (this.destroyed) {
      throw new Error("Message port has been destroyed");
    }

    this.listener.addMessageListener(name, callback);
  },

  /*
   * Removes a listener for messages.
   */
  removeMessageListener(name, callback) {
    if (this.destroyed) {
      throw new Error("Message port has been destroyed");
    }

    this.listener.removeMessageListener(name, callback);
  },

  // Sends a message asynchronously to the other process
  sendAsyncMessage(name, data = null) {
    if (this.destroyed) {
      throw new Error("Message port has been destroyed");
    }

    this.messageManager.sendAsyncMessage("RemotePage:Message", {
      portID: this.portID,
      name,
      data,
    });
  },

  // Called to destroy this port
  destroy() {
    try {
      // This can fail in the child process if the tab has already been closed
      this.messageManager.removeMessageListener("RemotePage:Message", this.message);
    } catch (e) { }
    this.messageManager = null;
    this.destroyed = true;
    this.portID = null;
    this.listener = null;
  },
};


// The chome side of a message port
function ChromeMessagePort(browser, portID) {
  MessagePort.call(this, browser.messageManager, portID);

  this._browser = browser;
  this._permanentKey = browser.permanentKey;

  Services.obs.addObserver(this, "message-manager-disconnect");
  this.publicPort = publicMessagePort(this);

  this.swapBrowsers = this.swapBrowsers.bind(this);
  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
}

ChromeMessagePort.prototype = Object.create(MessagePort.prototype);

Object.defineProperty(ChromeMessagePort.prototype, "browser", {
  get() {
    return this._browser;
  }
});

// Called when the docshell is being swapped with another browser. We have to
// update to use the new browser's message manager
ChromeMessagePort.prototype.swapBrowsers = function({ detail: newBrowser }) {
  // We can see this event for the new browser before the swap completes so
  // check that the browser we're tracking has our permanentKey.
  if (this._browser.permanentKey != this._permanentKey)
    return;

  this._browser.removeEventListener("SwapDocShells", this.swapBrowsers);

  this._browser = newBrowser;
  this.swapMessageManager(newBrowser.messageManager);

  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
}

// Called when a message manager has been disconnected indicating that the
// tab has closed or crashed
ChromeMessagePort.prototype.observe = function(messageManager) {
  if (messageManager != this.messageManager)
    return;

  this.listener.callListeners({
    target: this.publicPort,
    name: "RemotePage:Unload",
    data: null,
  });
  this.destroy();
};

// Called when a message is received from the message manager. This could
// have come from any port in the message manager so verify the port ID.
ChromeMessagePort.prototype.message = function({ data: messagedata }) {
  if (this.destroyed || (messagedata.portID != this.portID)) {
    return;
  }

  let message = {
    target: this.publicPort,
    name: messagedata.name,
    data: messagedata.data,
  };
  this.listener.callListeners(message);

  if (messagedata.name == "RemotePage:Unload")
    this.destroy();
};

ChromeMessagePort.prototype.destroy = function() {
  try {
    this._browser.removeEventListener(
        "SwapDocShells", this.swapBrowsers);
  } catch (e) {
    // It's possible the browser instance is already dead so we can just ignore
    // this error.
  }

  this._browser = null;
  Services.obs.removeObserver(this, "message-manager-disconnect");
  MessagePort.prototype.destroy.call(this);
};


// The content side of a message port
function ChildMessagePort(contentFrame, window) {
  let portID = Services.appinfo.processID + ":" + ChildMessagePort.prototype.nextPortID++;
  MessagePort.call(this, contentFrame, portID);

  this.window = window;

  // Add functionality to the content page
  Cu.exportFunction(this.sendAsyncMessage.bind(this), window, {
    defineAs: "sendAsyncMessage",
  });
  Cu.exportFunction(this.addMessageListener.bind(this), window, {
    defineAs: "addMessageListener",
    allowCallbacks: true,
  });
  Cu.exportFunction(this.removeMessageListener.bind(this), window, {
    defineAs: "removeMessageListener",
    allowCallbacks: true,
  });

  // Send a message for load events
  let loadListener = () => {
    this.sendAsyncMessage("RemotePage:Load");
    window.removeEventListener("load", loadListener);
  };
  window.addEventListener("load", loadListener);

  // Destroy the port when the window is unloaded
  window.addEventListener("unload", () => {
    try {
      this.sendAsyncMessage("RemotePage:Unload");
    } catch (e) {
      // If the tab has been closed the frame message manager has already been
      // destroyed
    }
    this.destroy();
  });

  // Tell the main process to set up its side of the message pipe.
  this.messageManager.sendAsyncMessage("RemotePage:InitPort", {
    portID,
    url: window.document.documentURI.replace(/[\#|\?].*$/, ""),
  });
}

ChildMessagePort.prototype = Object.create(MessagePort.prototype);

ChildMessagePort.prototype.nextPortID = 0;

// Called when a message is received from the message manager. This could
// have come from any port in the message manager so verify the port ID.
ChildMessagePort.prototype.message = function({ data: messagedata }) {
  if (this.destroyed || (messagedata.portID != this.portID)) {
    return;
  }

  let message = {
    name: messagedata.name,
    data: messagedata.data,
  };
  this.listener.callListeners(Cu.cloneInto(message, this.window));
};

ChildMessagePort.prototype.destroy = function() {
  this.window = null;
  MessagePort.prototype.destroy.call(this);
}

// Allows callers to register to connect to specific content pages. Registration
// is done through the addRemotePageListener method
var RemotePageManagerInternal = {
  // The currently registered remote pages
  pages: new Map(),

  // Initialises all the needed listeners
  init() {
    Services.ppmm.addMessageListener("RemotePage:InitListener", this.initListener.bind(this));
    Services.mm.addMessageListener("RemotePage:InitPort", this.initPort.bind(this));
  },

  // Registers interest in a remote page. A callback is called with a port for
  // the new page when loading begins (i.e. the page hasn't actually loaded yet).
  // Only one callback can be registered per URL.
  addRemotePageListener(url, callback) {
    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
      throw new Error("RemotePageManager can only be used in the main process.");

    if (this.pages.has(url)) {
      throw new Error("Remote page already registered: " + url);
    }

    this.pages.set(url, callback);

    // Notify all the frame scripts of the new registration
    Services.ppmm.broadcastAsyncMessage("RemotePage:Register", { urls: [url] });
  },

  // Removes any interest in a remote page.
  removeRemotePageListener(url) {
    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
      throw new Error("RemotePageManager can only be used in the main process.");

    if (!this.pages.has(url)) {
      throw new Error("Remote page is not registered: " + url);
    }

    // Notify all the frame scripts of the removed registration
    Services.ppmm.broadcastAsyncMessage("RemotePage:Unregister", { urls: [url] });
    this.pages.delete(url);
  },

  // A listener is requesting the list of currently registered urls
  initListener({ target: messageManager }) {
    messageManager.sendAsyncMessage("RemotePage:Register", { urls: Array.from(this.pages.keys()) })
  },

  // A remote page has been created and a port is ready in the content side
  initPort({ target: browser, data: { url, portID } }) {
    let callback = this.pages.get(url);
    if (!callback) {
      Cu.reportError("Unexpected remote page load: " + url);
      return;
    }

    let port = new ChromeMessagePort(browser, portID);
    callback(port.publicPort);
  }
};

if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
  RemotePageManagerInternal.init();

// The public API for the above object
this.RemotePageManager = {
  addRemotePageListener: RemotePageManagerInternal.addRemotePageListener.bind(RemotePageManagerInternal),
  removeRemotePageListener: RemotePageManagerInternal.removeRemotePageListener.bind(RemotePageManagerInternal),
};

// Listen for pages in any process we're loaded in
var registeredURLs = new Set();

var observer = (window) => {
  // Strip the hash from the URL, because it's not part of the origin.
  let url = window.document.documentURI.replace(/[\#|\?].*$/, "");
  if (!registeredURLs.has(url))
    return;

  // Get the frame message manager for this window so we can associate this
  // page with a browser element
  let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDocShell)
                             .QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIContentFrameMessageManager);
  // Set up the child side of the message port
  new ChildMessagePort(messageManager, window);
};
Services.obs.addObserver(observer, "chrome-document-global-created");
Services.obs.addObserver(observer, "content-document-global-created");

// A message from chrome telling us what pages to listen for
Services.cpmm.addMessageListener("RemotePage:Register", ({ data }) => {
  for (let url of data.urls)
    registeredURLs.add(url);
});

// A message from chrome telling us what pages to stop listening for
Services.cpmm.addMessageListener("RemotePage:Unregister", ({ data }) => {
  for (let url of data.urls)
    registeredURLs.delete(url);
});

Services.cpmm.sendAsyncMessage("RemotePage:InitListener");
PK
!<ަmodules/RemoteSecurityUI.jsm// -*- 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 = ["RemoteSecurityUI"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

function RemoteSecurityUI() {
    this._SSLStatus = null;
    this._state = 0;
}

RemoteSecurityUI.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISSLStatusProvider, Ci.nsISecureBrowserUI]),

  // nsISSLStatusProvider
  get SSLStatus() { return this._SSLStatus; },

  // nsISecureBrowserUI
  get state() { return this._state; },
  get tooltipText() { return ""; },

  _update(aStatus, aState) {
    this._SSLStatus = aStatus;
    this._state = aState;
  }
};
PK
!<lz(z(modules/RemoteWebProgress.jsm// -*- 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 = ["RemoteWebProgressManager"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

function newURI(spec) {
  return Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
                                                .newURI(spec);
}

function RemoteWebProgressRequest(spec, originalSpec, requestCPOW) {
  this.wrappedJSObject = this;

  this._uri = newURI(spec);
  this._originalURI = newURI(originalSpec);
  this._requestCPOW = requestCPOW;
}

RemoteWebProgressRequest.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel]),

  get URI() { return this._uri.clone(); },
  get originalURI() { return this._originalURI.clone(); }
};

function RemoteWebProgress(aManager, aIsTopLevel) {
  this.wrappedJSObject = this;

  this._manager = aManager;

  this._isLoadingDocument = false;
  this._DOMWindow = null;
  this._DOMWindowID = 0;
  this._isTopLevel = aIsTopLevel;
  this._loadType = 0;
}

RemoteWebProgress.prototype = {
  NOTIFY_STATE_REQUEST:  0x00000001,
  NOTIFY_STATE_DOCUMENT: 0x00000002,
  NOTIFY_STATE_NETWORK:  0x00000004,
  NOTIFY_STATE_WINDOW:   0x00000008,
  NOTIFY_STATE_ALL:      0x0000000f,
  NOTIFY_PROGRESS:       0x00000010,
  NOTIFY_STATUS:         0x00000020,
  NOTIFY_SECURITY:       0x00000040,
  NOTIFY_LOCATION:       0x00000080,
  NOTIFY_REFRESH:        0x00000100,
  NOTIFY_ALL:            0x000001ff,

  get isLoadingDocument() { return this._isLoadingDocument },
  get DOMWindow() { return this._DOMWindow; },
  get DOMWindowID() { return this._DOMWindowID; },
  get isTopLevel() { return this._isTopLevel },
  get loadType() { return this._loadType; },

  addProgressListener(aListener, aNotifyMask) {
    this._manager.addProgressListener(aListener, aNotifyMask);
  },

  removeProgressListener(aListener) {
    this._manager.removeProgressListener(aListener);
  }
};

function RemoteWebProgressManager(aBrowser) {
  this._topLevelWebProgress = new RemoteWebProgress(this, true);
  this._progressListeners = [];

  this.swapBrowser(aBrowser);
}

RemoteWebProgressManager.argumentsForAddonListener = function(kind, args) {
  function checkType(arg, typ) {
    if (!arg) {
      return false;
    }
    return (arg instanceof typ) ||
      (arg instanceof Ci.nsISupports && arg.wrappedJSObject instanceof typ);
  }

  // Arguments for a tabs listener are shifted over one since the
  // <browser> element is passed as the first argument.
  let webProgressIndex = 0;
  let requestIndex = 1;
  if (kind == "tabs") {
    webProgressIndex = 1;
    requestIndex = 2;
  }

  if (checkType(args[webProgressIndex], RemoteWebProgress)) {
    args[webProgressIndex] = args[webProgressIndex].wrappedJSObject._webProgressCPOW;
  }

  if (checkType(args[requestIndex], RemoteWebProgressRequest)) {
    args[requestIndex] = args[requestIndex].wrappedJSObject._requestCPOW;
  }

  return args;
};

RemoteWebProgressManager.prototype = {
  swapBrowser(aBrowser) {
    if (this._messageManager) {
      this._messageManager.removeMessageListener("Content:StateChange", this);
      this._messageManager.removeMessageListener("Content:LocationChange", this);
      this._messageManager.removeMessageListener("Content:SecurityChange", this);
      this._messageManager.removeMessageListener("Content:StatusChange", this);
      this._messageManager.removeMessageListener("Content:ProgressChange", this);
      this._messageManager.removeMessageListener("Content:LoadURIResult", this);
    }

    this._browser = aBrowser;
    this._messageManager = aBrowser.messageManager;
    this._messageManager.addMessageListener("Content:StateChange", this);
    this._messageManager.addMessageListener("Content:LocationChange", this);
    this._messageManager.addMessageListener("Content:SecurityChange", this);
    this._messageManager.addMessageListener("Content:StatusChange", this);
    this._messageManager.addMessageListener("Content:ProgressChange", this);
    this._messageManager.addMessageListener("Content:LoadURIResult", this);
  },

  get topLevelWebProgress() {
    return this._topLevelWebProgress;
  },

  addProgressListener(aListener, aNotifyMask) {
    let listener = aListener.QueryInterface(Ci.nsIWebProgressListener);
    this._progressListeners.push({
      listener,
      mask: aNotifyMask || Ci.nsIWebProgress.NOTIFY_ALL
    });
  },

  removeProgressListener(aListener) {
    this._progressListeners =
      this._progressListeners.filter(l => l.listener != aListener);
  },

  _fixSSLStatusAndState(aStatus, aState) {
    let deserialized = null;
    if (aStatus) {
      let helper = Cc["@mozilla.org/network/serialization-helper;1"]
                    .getService(Components.interfaces.nsISerializationHelper);

      deserialized = helper.deserializeObject(aStatus)
      deserialized.QueryInterface(Ci.nsISSLStatus);
    }

    return [deserialized, aState];
  },

  setCurrentURI(aURI) {
    // This function is simpler than nsDocShell::SetCurrentURI since
    // it doesn't have to deal with child docshells.
    let remoteWebNav = this._browser._remoteWebNavigationImpl;
    remoteWebNav._currentURI = aURI;

    let webProgress = this.topLevelWebProgress;
    for (let { listener, mask } of this._progressListeners) {
      if (mask & Ci.nsIWebProgress.NOTIFY_LOCATION) {
        listener.onLocationChange(webProgress, null, aURI);
      }
    }
  },

  _callProgressListeners(type, methodName, ...args) {
    for (let { listener, mask } of this._progressListeners) {
      if ((mask & type) && listener[methodName]) {
        try {
          listener[methodName].apply(listener, args);
        } catch (ex) {
          Cu.reportError("RemoteWebProgress failed to call " + methodName + ": " + ex + "\n");
        }
      }
    }
  },

  receiveMessage(aMessage) {
    let json = aMessage.json;
    let objects = aMessage.objects;
    // This message is a custom one we send as a result of a loadURI call.
    // It shouldn't go through the same processing as all the forwarded
    // webprogresslistener messages.
    if (aMessage.name == "Content:LoadURIResult") {
      this._browser.inLoadURI = false;
      return;
    }

    let webProgress = null;
    let isTopLevel = json.webProgress && json.webProgress.isTopLevel;
    // The top-level WebProgress is always the same, but because we don't
    // really have a concept of subframes/content we always create a new object
    // for those.
    if (json.webProgress) {
      webProgress = isTopLevel ? this._topLevelWebProgress
                               : new RemoteWebProgress(this, false);

      // Update the actual WebProgress fields.
      webProgress._isLoadingDocument = json.webProgress.isLoadingDocument;
      webProgress._DOMWindow = objects.DOMWindow;
      webProgress._DOMWindowID = json.webProgress.DOMWindowID;
      webProgress._loadType = json.webProgress.loadType;
      webProgress._webProgressCPOW = objects.webProgress;
    }

    // The WebProgressRequest object however is always dynamic.
    let request = null;
    if (json.requestURI) {
      request = new RemoteWebProgressRequest(json.requestURI,
                                             json.originalRequestURI,
                                             objects.request);
    }

    if (isTopLevel) {
      this._browser._contentWindow = objects.contentWindow;
      this._browser._documentContentType = json.documentContentType;
      if (typeof json.inLoadURI != "undefined") {
        this._browser.inLoadURI = json.inLoadURI;
      }
      if (json.charset) {
        this._browser._characterSet = json.charset;
        this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu;
      }
    }

    switch (aMessage.name) {
    case "Content:StateChange":
      if (isTopLevel) {
        this._browser._documentURI = newURI(json.documentURI);
      }
      this._callProgressListeners(
        Ci.nsIWebProgress.NOTIFY_STATE_ALL, "onStateChange", webProgress,
        request, json.stateFlags, json.status
      );
      break;

    case "Content:LocationChange":
      let location = newURI(json.location);
      let flags = json.flags;
      let remoteWebNav = this._browser._remoteWebNavigationImpl;

      // These properties can change even for a sub-frame navigation.
      remoteWebNav.canGoBack = json.canGoBack;
      remoteWebNav.canGoForward = json.canGoForward;

      if (isTopLevel) {
        remoteWebNav._currentURI = location;
        this._browser._documentURI = newURI(json.documentURI);
        this._browser._contentTitle = json.title;
        this._browser._imageDocument = null;
        this._browser._contentPrincipal = json.principal;
        this._browser._isSyntheticDocument = json.synthetic;
        this._browser._innerWindowID = json.innerWindowID;
      }

      this._callProgressListeners(
        Ci.nsIWebProgress.NOTIFY_LOCATION, "onLocationChange", webProgress,
        request, location, flags
      );
      break;

    case "Content:SecurityChange":
      let [status, state] = this._fixSSLStatusAndState(json.status, json.state);

      if (isTopLevel) {
        // Invoking this getter triggers the generation of the underlying object,
        // which we need to access with ._securityUI, because .securityUI returns
        // a wrapper that makes _update inaccessible.
        void this._browser.securityUI;
        this._browser._securityUI._update(status, state);
      }

      this._callProgressListeners(
        Ci.nsIWebProgress.NOTIFY_SECURITY, "onSecurityChange", webProgress,
        request, state
      );
      break;

    case "Content:StatusChange":
      this._callProgressListeners(
        Ci.nsIWebProgress.NOTIFY_STATUS, "onStatusChange", webProgress, request,
        json.status, json.message
      );
      break;

    case "Content:ProgressChange":
      this._callProgressListeners(
        Ci.nsIWebProgress.NOTIFY_PROGRESS, "onProgressChange", webProgress,
        request, json.curSelf, json.maxSelf, json.curTotal, json.maxTotal
      );
      break;
    }
  },
};
PK
!<*:4 0	0	modules/ResetProfile.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ResetProfile"];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

const MOZ_APP_NAME = AppConstants.MOZ_APP_NAME;
const MOZ_BUILD_APP = AppConstants.MOZ_BUILD_APP;

this.ResetProfile = {
  /**
   * Check if reset is supported for the currently running profile.
   *
   * @return boolean whether reset is supported.
   */
  resetSupported() {
    // Reset is only supported if the self-migrator used for reset exists.
    let migrator = "@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP +
                   "&type=" + MOZ_APP_NAME;
    if (!(migrator in Cc)) {
      return false;
    }
    // We also need to be using a profile the profile manager knows about.
    let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].
                         getService(Ci.nsIToolkitProfileService);
    let currentProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    let profileEnumerator = profileService.profiles;
    while (profileEnumerator.hasMoreElements()) {
      let profile = profileEnumerator.getNext().QueryInterface(Ci.nsIToolkitProfile);
      if (profile.rootDir && profile.rootDir.equals(currentProfileDir)) {
        return true;
      }
    }
    return false;
  },

  /**
   * Ask the user if they wish to restart the application to reset the profile.
   */
  openConfirmationDialog(window) {
    // Prompt the user to confirm.
    let params = {
      reset: false,
    };
    window.openDialog("chrome://global/content/resetProfile.xul", null,
                      "chrome,modal,centerscreen,titlebar,dialog=yes", params);
    if (!params.reset)
      return;

    // Set the reset profile environment variable.
    let env = Cc["@mozilla.org/process/environment;1"]
                .getService(Ci.nsIEnvironment);
    env.set("MOZ_RESET_PROFILE_RESTART", "1");

    let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
    appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart);
  },
};
PK
!<
!modules/ResponsivenessMonitor.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ResponsivenessMonitor"];

const { classes: Cc, interfaces: Ci } = Components;

function ResponsivenessMonitor(intervalMS = 100) {
  this._intervalMS = intervalMS;
  this._prevTimestamp = Date.now();
  this._accumulatedDelay = 0;
  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  this._timer.initWithCallback(this, this._intervalMS, Ci.nsITimer.TYPE_REPEATING_SLACK);
}

ResponsivenessMonitor.prototype = {
  notify() {
    let now = Date.now();
    this._accumulatedDelay += Math.max(0, now - this._prevTimestamp - this._intervalMS);
    this._prevTimestamp = now;
  },

  abort() {
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  finish() {
    this.abort();
    return this._accumulatedDelay;
  },
};
PK
!<;

modules/RokuApp.jsm// -*- 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 = ["RokuApp"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

function log(msg) {
  // Services.console.logStringMessage(msg);
}

const PROTOCOL_VERSION = 1;

/* RokuApp is a wrapper for interacting with a Roku channel.
 * The basic interactions all use a REST API.
 * spec: http://sdkdocs.roku.com/display/sdkdoc/External+Control+Guide
 */
function RokuApp(service) {
  this.service = service;
  this.resourceURL = this.service.location;
  this.app = AppConstants.RELEASE_OR_BETA ? "Firefox" : "Firefox Nightly";
  this.mediaAppID = -1;
}

RokuApp.prototype = {
  status: function status(callback) {
    // We have no way to know if the app is running, so just return "unknown"
    // but we use this call to fetch the mediaAppID for the given app name
    let url = this.resourceURL + "query/apps";
    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    xhr.open("GET", url, true);
    xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    xhr.overrideMimeType("text/xml");

    xhr.addEventListener("load", () => {
      if (xhr.status == 200) {
        let doc = xhr.responseXML;
        let apps = doc.querySelectorAll("app");
        for (let app of apps) {
          if (app.textContent == this.app) {
            this.mediaAppID = app.id;
          }
        }
      }

      // Since ECP has no way of telling us if an app is running, we always return "unknown"
      if (callback) {
        callback({ state: "unknown" });
      }
    });

    xhr.addEventListener("error", (function() {
      if (callback) {
        callback({ state: "unknown" });
      }
    }));

    xhr.send(null);
  },

  start: function start(callback) {
    // We need to make sure we have cached the mediaAppID
    if (this.mediaAppID == -1) {
      this.status(() => {
        // If we found the mediaAppID, use it to make a new start call
        if (this.mediaAppID != -1) {
          this.start(callback);
        } else {
          // We failed to start the app, so let the caller know
          callback(false);
        }
      });
      return;
    }

    // Start a given app with any extra query data. Each app uses it's own data scheme.
    // NOTE: Roku will also pass "source=external-control" as a param
    let url = this.resourceURL + "launch/" + this.mediaAppID + "?version=" + parseInt(PROTOCOL_VERSION);
    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    xhr.open("POST", url, true);
    xhr.overrideMimeType("text/plain");

    xhr.addEventListener("load", (function() {
      if (callback) {
        callback(xhr.status === 200);
      }
    }));

    xhr.addEventListener("error", (function() {
      if (callback) {
        callback(false);
      }
    }));

    xhr.send(null);
  },

  stop: function stop(callback) {
    // Roku doesn't seem to support stopping an app, so let's just go back to
    // the Home screen
    let url = this.resourceURL + "keypress/Home";
    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    xhr.open("POST", url, true);
    xhr.overrideMimeType("text/plain");

    xhr.addEventListener("load", (function() {
      if (callback) {
        callback(xhr.status === 200);
      }
    }));

    xhr.addEventListener("error", (function() {
      if (callback) {
        callback(false);
      }
    }));

    xhr.send(null);
  },

  remoteMedia: function remoteMedia(callback, listener) {
    if (this.mediaAppID != -1) {
      if (callback) {
        callback(new RemoteMedia(this.resourceURL, listener));
      }
    } else if (callback) {
      callback();
    }
  }
}

/* RemoteMedia provides a wrapper for using TCP socket to control Roku apps.
 * The server implementation must be built into the Roku receiver app.
 */
function RemoteMedia(url, listener) {
  this._url = url;
  this._listener = listener;
  this._status = "uninitialized";

  let serverURI = Services.io.newURI(this._url);
  this._socket = Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService).createTransport(null, 0, serverURI.host, 9191, null);
  this._outputStream = this._socket.openOutputStream(0, 0, 0);

  this._scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);

  this._inputStream = this._socket.openInputStream(0, 0, 0);
  this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump);
  this._pump.init(this._inputStream, -1, -1, 0, 0, true);
  this._pump.asyncRead(this, null);
}

RemoteMedia.prototype = {
  onStartRequest(request, context) {
  },

  onDataAvailable(request, context, stream, offset, count) {
    this._scriptableStream.init(stream);
    let data = this._scriptableStream.read(count);
    if (!data) {
      return;
    }

    let msg = JSON.parse(data);
    if (this._status === msg._s) {
      return;
    }

    this._status = msg._s;

    if (this._listener) {
      // Check to see if we are getting the initial "connected" message
      if (this._status == "connected" && "onRemoteMediaStart" in this._listener) {
        this._listener.onRemoteMediaStart(this);
      }

      if ("onRemoteMediaStatus" in this._listener) {
        this._listener.onRemoteMediaStatus(this);
      }
    }
  },

  onStopRequest(request, context, result) {
    if (this._listener && "onRemoteMediaStop" in this._listener)
      this._listener.onRemoteMediaStop(this);
  },

  _sendMsg: function _sendMsg(data) {
    if (!data)
      return;

    // Add the protocol version
    data["_v"] = PROTOCOL_VERSION;

    let raw = JSON.stringify(data);
    this._outputStream.write(raw, raw.length);
  },

  shutdown: function shutdown() {
    this._outputStream.close();
    this._inputStream.close();
  },

  get active() {
    return (this._socket && this._socket.isAlive());
  },

  play: function play() {
    // TODO: add position support
    this._sendMsg({ type: "PLAY" });
  },

  pause: function pause() {
    this._sendMsg({ type: "STOP" });
  },

  load: function load(data) {
    this._sendMsg({ type: "LOAD", title: data.title, source: data.source, poster: data.poster });
  },

  get status() {
    return this._status;
  }
}
PK
!<K]	G	Gmodules/SafeBrowsing.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["SafeBrowsing"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");

const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";
let loggingEnabled = false;

// Log only if browser.safebrowsing.debug is true
function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" ");
  dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n");
}

function getLists(prefName) {
  log("getLists: " + prefName);
  let pref = Services.prefs.getCharPref(prefName, "");

  // Splitting an empty string returns [''], we really want an empty array.
  if (!pref) {
    return [];
  }

  return pref.split(",").map(value => value.trim());
}

const tablePreferences = [
  "urlclassifier.phishTable",
  "urlclassifier.malwareTable",
  "urlclassifier.downloadBlockTable",
  "urlclassifier.downloadAllowTable",
  "urlclassifier.trackingTable",
  "urlclassifier.trackingWhitelistTable",
  "urlclassifier.blockedTable",
  "urlclassifier.flashAllowTable",
  "urlclassifier.flashAllowExceptTable",
  "urlclassifier.flashTable",
  "urlclassifier.flashExceptTable",
  "urlclassifier.flashSubDocTable",
  "urlclassifier.flashSubDocExceptTable",
  "urlclassifier.flashInfobarTable"
];

this.SafeBrowsing = {

  init: function() {
    if (this.initialized) {
      log("Already initialized");
      return;
    }

    Services.prefs.addObserver("browser.safebrowsing", this);
    Services.prefs.addObserver("privacy.trackingprotection", this);
    Services.prefs.addObserver("urlclassifier", this);
    Services.prefs.addObserver("plugins.flashBlock.enabled", this);

    this.readPrefs();
    this.addMozEntries();

    this.controlUpdateChecking();
    this.initialized = true;

    log("init() finished");
  },

  registerTableWithURLs: function(listname) {
    let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
      getService(Ci.nsIUrlListManager);

    let providerName = this.listToProvider[listname];
    let provider = this.providers[providerName];

    if (!providerName || !provider) {
      log("No provider info found for " + listname);
      log("Check browser.safebrowsing.provider.[google/mozilla].lists");
      return;
    }

    if (!provider.updateURL) {
      log("Invalid update url " + listname);
      return;
    }

    listManager.registerTable(listname, providerName, provider.updateURL, provider.gethashURL);
  },

  registerTables: function() {
    for (let i = 0; i < this.phishingLists.length; ++i) {
      this.registerTableWithURLs(this.phishingLists[i]);
    }
    for (let i = 0; i < this.malwareLists.length; ++i) {
      this.registerTableWithURLs(this.malwareLists[i]);
    }
    for (let i = 0; i < this.downloadBlockLists.length; ++i) {
      this.registerTableWithURLs(this.downloadBlockLists[i]);
    }
    for (let i = 0; i < this.downloadAllowLists.length; ++i) {
      this.registerTableWithURLs(this.downloadAllowLists[i]);
    }
    for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
      this.registerTableWithURLs(this.trackingProtectionLists[i]);
    }
    for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
      this.registerTableWithURLs(this.trackingProtectionWhitelists[i]);
    }
    for (let i = 0; i < this.blockedLists.length; ++i) {
      this.registerTableWithURLs(this.blockedLists[i]);
    }
    for (let i = 0; i < this.flashLists.length; ++i) {
      this.registerTableWithURLs(this.flashLists[i]);
    }
    for (let i = 0; i < this.flashInfobarLists.length; ++i) {
      this.registerTableWithURLs(this.flashInfobarLists[i]);
    }
  },

  unregisterTables(obsoleteLists) {
    let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
      getService(Ci.nsIUrlListManager);

    for (let i = 0; i < obsoleteLists.length; ++i) {
      for (let j = 0; j < obsoleteLists[i].length; ++j) {
        listManager.unregisterTable(obsoleteLists[i][j]);
      }
    }
  },

  initialized:          false,
  phishingEnabled:      false,
  malwareEnabled:       false,
  downloadsEnabled:       false,
  trackingEnabled:      false,
  blockedEnabled:       false,
  trackingAnnotations:  false,
  flashBlockEnabled:    false,
  flashInfobarListEnabled: true,

  phishingLists:                [],
  malwareLists:                 [],
  downloadBlockLists:           [],
  downloadAllowLists:           [],
  trackingProtectionLists:      [],
  trackingProtectionWhitelists: [],
  blockedLists:                 [],
  flashLists:                   [],
  flashInfobarLists:            [],

  updateURL:             null,
  gethashURL:            null,

  reportURL:             null,

  getReportURL: function(kind, info) {
    let pref;
    switch (kind) {
      case "Phish":
        pref = "browser.safebrowsing.reportPhishURL";
        break;

      case "PhishMistake":
      case "MalwareMistake":
        pref = "browser.safebrowsing.provider." + info.provider + ".report" + kind + "URL";
        break;

      default:
        let err = "SafeBrowsing getReportURL() called with unknown kind: " + kind;
        Components.utils.reportError(err);
        throw err;
    }

    // The "Phish" reports are about submitting new phishing URLs to Google so
    // they don't have an associated list URL
    if (kind != "Phish" && (!info.list || !info.uri)) {
      return null;
    }

    let reportUrl = Services.urlFormatter.formatURLPref(pref);
    // formatURLPref might return "about:blank" if getting the pref fails
    if (reportUrl == "about:blank") {
      reportUrl = null;
    }

    if (reportUrl) {
      reportUrl += encodeURIComponent(info.uri);
    }
    return reportUrl;
  },

  observe: function(aSubject, aTopic, aData) {
    // skip nextupdatetime and lastupdatetime
    if (aData.indexOf("lastupdatetime") >= 0 || aData.indexOf("nextupdatetime") >= 0) {
      return;
    }

    if (aData == PREF_DEBUG_ENABLED) {
      loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
      return;
    }

    this.readPrefs();
  },

  readPrefs: function() {
    loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
    log("reading prefs");

    this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
    this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
    this.downloadsEnabled = Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled");
    this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
    this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
    this.trackingAnnotations = Services.prefs.getBoolPref("privacy.trackingprotection.annotate_channels");
    this.flashBlockEnabled = Services.prefs.getBoolPref("plugins.flashBlock.enabled");

    let flashAllowTable, flashAllowExceptTable, flashTable,
        flashExceptTable, flashSubDocTable,
        flashSubDocExceptTable;

    let obsoleteLists;
    // Make a copy of the original lists before we re-read the prefs.
    if (this.initialized) {
      obsoleteLists = [this.phishingLists,
                       this.malwareLists,
                       this.downloadBlockLists,
                       this.downloadAllowLists,
                       this.trackingProtectionLists,
                       this.trackingProtectionWhitelists,
                       this.blockedLists,
                       this.flashLists,
                       this.flashInfobarLists];
    }

    [this.phishingLists,
     this.malwareLists,
     this.downloadBlockLists,
     this.downloadAllowLists,
     this.trackingProtectionLists,
     this.trackingProtectionWhitelists,
     this.blockedLists,
     flashAllowTable,
     flashAllowExceptTable,
     flashTable,
     flashExceptTable,
     flashSubDocTable,
     flashSubDocExceptTable,
     this.flashInfobarLists] = tablePreferences.map(getLists);

    this.flashLists = flashAllowTable.concat(flashAllowExceptTable,
                                             flashTable,
                                             flashExceptTable,
                                             flashSubDocTable,
                                             flashSubDocExceptTable)

    if (obsoleteLists) {
      let newLists = [this.phishingLists,
                      this.malwareLists,
                      this.downloadBlockLists,
                      this.downloadAllowLists,
                      this.trackingProtectionLists,
                      this.trackingProtectionWhitelists,
                      this.blockedLists,
                      this.flashLists,
                      this.flashInfobarLists];

      for (let i = 0; i < obsoleteLists.length; ++i) {
        obsoleteLists[i] = obsoleteLists[i]
          .filter(list => newLists[i].indexOf(list) == -1);
      }
    }

    this.updateProviderURLs();
    this.registerTables();
    if (obsoleteLists) {
      this.unregisterTables(obsoleteLists);
    }

    // XXX The listManager backend gets confused if this is called before the
    // lists are registered. So only call it here when a pref changes, and not
    // when doing initialization. I expect to refactor this later, so pardon the hack.
    if (this.initialized) {
      this.controlUpdateChecking();
    }
  },


  updateProviderURLs: function() {
    try {
      var clientID = Services.prefs.getCharPref("browser.safebrowsing.id");
    } catch(e) {
      clientID = Services.appinfo.name;
    }

    log("initializing safe browsing URLs, client id", clientID);

    // Get the different providers
    let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
    let children = branch.getChildList("", {});
    this.providers = {};
    this.listToProvider = {};

    for (let child of children) {
      log("Child: " + child);
      let prefComponents =  child.split(".");
      let providerName = prefComponents[0];
      this.providers[providerName] = {};
    }

    if (loggingEnabled) {
      let providerStr = "";
      Object.keys(this.providers).forEach(function(provider) {
        if (providerStr === "") {
          providerStr = provider;
        } else {
          providerStr += ", " + provider;
        }
      });
      log("Providers: " + providerStr);
    }

    Object.keys(this.providers).forEach(function(provider) {
      let updateURL = Services.urlFormatter.formatURLPref(
        "browser.safebrowsing.provider." + provider + ".updateURL");
      let gethashURL = Services.urlFormatter.formatURLPref(
        "browser.safebrowsing.provider." + provider + ".gethashURL");
      updateURL = updateURL.replace("SAFEBROWSING_ID", clientID);
      gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID);

      // Disable updates and gethash if the Google API key is missing.
      let googleKey = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
      if ((provider == "google" || provider == "google4") &&
          (!googleKey || googleKey == "no-google-api-key")) {
        updateURL= "";
        gethashURL= "";
      }

      log("Provider: " + provider + " updateURL=" + updateURL);
      log("Provider: " + provider + " gethashURL=" + gethashURL);

      // Urls used to update DB
      this.providers[provider].updateURL  = updateURL;
      this.providers[provider].gethashURL = gethashURL;

      // Get lists this provider manages
      let lists = getLists("browser.safebrowsing.provider." + provider + ".lists");
      if (lists) {
        lists.forEach(function(list) {
          this.listToProvider[list] = provider;
        }, this);
      } else {
        log("Update URL given but no lists managed for provider: " + provider);
      }
    }, this);
  },

  controlUpdateChecking: function() {
    log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
        this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
        "downloadsEnabled:", this.downloadsEnabled,
        "blockedEnabled:", this.blockedEnabled, "trackingAnnotations",
        this.trackingAnnotations, "flashBlockEnabled", this.flashBlockEnabled,
        "flashInfobarListEnabled:", this.flashInfobarListEnabled);

    let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                      getService(Ci.nsIUrlListManager);

    for (let i = 0; i < this.phishingLists.length; ++i) {
      if (this.phishingEnabled) {
        listManager.enableUpdate(this.phishingLists[i]);
      } else {
        listManager.disableUpdate(this.phishingLists[i]);
      }
    }
    for (let i = 0; i < this.malwareLists.length; ++i) {
      if (this.malwareEnabled) {
        listManager.enableUpdate(this.malwareLists[i]);
      } else {
        listManager.disableUpdate(this.malwareLists[i]);
      }
    }
    for (let i = 0; i < this.downloadBlockLists.length; ++i) {
      if (this.downloadsEnabled) {
        listManager.enableUpdate(this.downloadBlockLists[i]);
      } else {
        listManager.disableUpdate(this.downloadBlockLists[i]);
      }
    }
    for (let i = 0; i < this.downloadAllowLists.length; ++i) {
      if (this.downloadsEnabled) {
        listManager.enableUpdate(this.downloadAllowLists[i]);
      } else {
        listManager.disableUpdate(this.downloadAllowLists[i]);
      }
    }
    for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
      if (this.trackingEnabled || this.trackingAnnotations) {
        listManager.enableUpdate(this.trackingProtectionLists[i]);
      } else {
        listManager.disableUpdate(this.trackingProtectionLists[i]);
      }
    }
    for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
      if (this.trackingEnabled || this.trackingAnnotations) {
        listManager.enableUpdate(this.trackingProtectionWhitelists[i]);
      } else {
        listManager.disableUpdate(this.trackingProtectionWhitelists[i]);
      }
    }
    for (let i = 0; i < this.blockedLists.length; ++i) {
      if (this.blockedEnabled) {
        listManager.enableUpdate(this.blockedLists[i]);
      } else {
        listManager.disableUpdate(this.blockedLists[i]);
      }
    }
    for (let i = 0; i < this.flashLists.length; ++i) {
      if (this.flashBlockEnabled) {
        listManager.enableUpdate(this.flashLists[i]);
      } else {
        listManager.disableUpdate(this.flashLists[i]);
      }
    }
    for (let i = 0; i < this.flashInfobarLists.length; ++i) {
      if (this.flashInfobarListEnabled) {
        listManager.enableUpdate(this.flashInfobarLists[i]);
      } else {
        listManager.disableUpdate(this.flashInfobarLists[i]);
      }
    }
    listManager.maybeToggleUpdateChecking();
  },


  addMozEntries: function() {
    // Add test entries to the DB.
    // XXX bug 779008 - this could be done by DB itself?
    const phishURL    = "itisatrap.org/firefox/its-a-trap.html";
    const malwareURL  = "itisatrap.org/firefox/its-an-attack.html";
    const unwantedURL = "itisatrap.org/firefox/unwanted.html";
    const trackerURLs = [
      "trackertest.org/",
      "itisatracker.org/",
    ];
    const whitelistURL  = "itisatrap.org/?resource=itisatracker.org";
    const blockedURL    = "itisatrap.org/firefox/blocked.html";

    let update = "n:1000\ni:test-malware-simple\nad:1\n" +
                 "a:1:32:" + malwareURL.length + "\n" +
                 malwareURL + "\n";
    update += "n:1000\ni:test-phish-simple\nad:1\n" +
              "a:1:32:" + phishURL.length + "\n" +
              phishURL  + "\n";
    update += "n:1000\ni:test-unwanted-simple\nad:1\n" +
              "a:1:32:" + unwantedURL.length + "\n" +
              unwantedURL + "\n";
    update += "n:1000\ni:test-track-simple\n" +
              "ad:" + trackerURLs.length + "\n";
    trackerURLs.forEach((trackerURL, i) => {
      update += "a:" + (i + 1) + ":32:" + trackerURL.length + "\n" +
                trackerURL + "\n";
    });
    update += "n:1000\ni:test-trackwhite-simple\nad:1\n" +
              "a:1:32:" + whitelistURL.length + "\n" +
              whitelistURL;
    update += "n:1000\ni:test-block-simple\nad:1\n" +
              "a:1:32:" + blockedURL.length + "\n" +
              blockedURL;
    log("addMozEntries:", update);

    let db = Cc["@mozilla.org/url-classifier/dbservice;1"].
             getService(Ci.nsIUrlClassifierDBService);

    // nsIUrlClassifierUpdateObserver
    let dummyListener = {
      updateUrlRequested: function() { },
      streamFinished:     function() { },
      // We notify observers when we're done in order to be able to make perf
      // test results more consistent
      updateError:        function() {
        Services.obs.notifyObservers(db, "mozentries-update-finished", "error");
      },
      updateSuccess:      function() {
        Services.obs.notifyObservers(db, "mozentries-update-finished", "success");
      }
    };

    try {
      let tables = "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple";
      db.beginUpdate(dummyListener, tables, "");
      db.beginStream("", "");
      db.updateStream(update);
      db.finishStream();
      db.finishUpdate();
    } catch(ex) {
      // beginUpdate will throw harmlessly if there's an existing update in progress, ignore failures.
      log("addMozEntries failed!", ex);
      Services.obs.notifyObservers(db, "mozentries-update-finished", "exception");
    }
  },

  addMozEntriesFinishedPromise: new Promise(resolve => {
    let finished = (subject, topic, data) => {
      Services.obs.removeObserver(finished, "mozentries-update-finished");
      if (data == "error") {
        Cu.reportError("addMozEntries failed to update the db!");
      }
      resolve();
    };
    Services.obs.addObserver(finished, "mozentries-update-finished");
  }),
};
PK
!<?FFmodules/Schemas.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

const global = this;

Cu.importGlobalProperties(["URL"]);

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/ExtensionUtils.jsm");
var {
  DefaultMap,
  DefaultWeakMap,
  instanceOf,
} = ExtensionUtils;

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                  "resource://gre/modules/ExtensionParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService",
                                   "@mozilla.org/addons/content-policy;1",
                                   "nsIAddonContentPolicy");

XPCOMUtils.defineLazyGetter(this, "StartupCache", () => ExtensionParent.StartupCache);

this.EXPORTED_SYMBOLS = ["Schemas"];

const {DEBUG} = AppConstants;

const isParentProcess = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

function readJSON(url) {
  return new Promise((resolve, reject) => {
    NetUtil.asyncFetch({uri: url, loadUsingSystemPrincipal: true}, (inputStream, status) => {
      if (!Components.isSuccessCode(status)) {
        // Convert status code to a string
        let e = Components.Exception("", status);
        reject(new Error(`Error while loading '${url}' (${e.name})`));
        return;
      }
      try {
        let text = NetUtil.readInputStreamToString(inputStream, inputStream.available());

        // Chrome JSON files include a license comment that we need to
        // strip off for this to be valid JSON. As a hack, we just
        // look for the first '[' character, which signals the start
        // of the JSON content.
        let index = text.indexOf("[");
        text = text.slice(index);

        resolve(JSON.parse(text));
      } catch (e) {
        reject(e);
      }
    });
  });
}

function stripDescriptions(json, stripThis = true) {
  if (Array.isArray(json)) {
    for (let i = 0; i < json.length; i++) {
      if (typeof json[i] === "object" && json[i] !== null) {
        json[i] = stripDescriptions(json[i]);
      }
    }
    return json;
  }

  let result = {};

  // Objects are handled much more efficiently, both in terms of memory and
  // CPU, if they have the same shape as other objects that serve the same
  // purpose. So, normalize the order of properties to increase the chances
  // that the majority of schema objects wind up in large shape groups.
  for (let key of Object.keys(json).sort()) {
    if (stripThis && key === "description" && typeof json[key] === "string") {
      continue;
    }

    if (typeof json[key] === "object" && json[key] !== null) {
      result[key] = stripDescriptions(json[key], key !== "properties");
    } else {
      result[key] = json[key];
    }
  }

  return result;
}

async function readJSONAndBlobbify(url) {
  let json = await readJSON(url);

  // We don't actually use descriptions at runtime, and they make up about a
  // third of the size of our structured clone data, so strip them before
  // blobbifying.
  json = stripDescriptions(json);

  return new StructuredCloneHolder(json);
}

/**
 * Defines a lazy getter for the given property on the given object. Any
 * security wrappers are waived on the object before the property is
 * defined, and the getter and setter methods are wrapped for the target
 * scope.
 *
 * The given getter function is guaranteed to be called only once, even
 * if the target scope retrieves the wrapped getter from the property
 * descriptor and calls it directly.
 *
 * @param {object} object
 *        The object on which to define the getter.
 * @param {string|Symbol} prop
 *        The property name for which to define the getter.
 * @param {function} getter
 *        The function to call in order to generate the final property
 *        value.
 */
function exportLazyGetter(object, prop, getter) {
  object = Cu.waiveXrays(object);

  let redefine = value => {
    if (value === undefined) {
      delete object[prop];
    } else {
      Object.defineProperty(object, prop, {
        enumerable: true,
        configurable: true,
        writable: true,
        value,
      });
    }

    getter = null;

    return value;
  };

  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,

    get: Cu.exportFunction(function() {
      return redefine(getter.call(this));
    }, object),

    set: Cu.exportFunction(value => {
      redefine(value);
    }, object),
  });
}

/**
 * Defines a lazily-instantiated property descriptor on the given
 * object. Any security wrappers are waived on the object before the
 * property is defined.
 *
 * The given getter function is guaranteed to be called only once, even
 * if the target scope retrieves the wrapped getter from the property
 * descriptor and calls it directly.
 *
 * @param {object} object
 *        The object on which to define the getter.
 * @param {string|Symbol} prop
 *        The property name for which to define the getter.
 * @param {function} getter
 *        The function to call in order to generate the final property
 *        descriptor object. This will be called, and the property
 *        descriptor installed on the object, the first time the
 *        property is written or read. The function may return
 *        undefined, which will cause the property to be deleted.
 */
function exportLazyProperty(object, prop, getter) {
  object = Cu.waiveXrays(object);

  let redefine = obj => {
    let desc = getter.call(obj);
    getter = null;

    delete object[prop];
    if (desc) {
      let defaults = {
        configurable: true,
        enumerable: true,
      };

      if (!desc.set && !desc.get) {
        defaults.writable = true;
      }

      Object.defineProperty(object, prop,
                            Object.assign(defaults, desc));
    }
  };

  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,

    get: Cu.exportFunction(function() {
      redefine(this);
      return object[prop];
    }, object),

    set: Cu.exportFunction(function(value) {
      redefine(this);
      object[prop] = value;
    }, object),
  });
}

const POSTPROCESSORS = {
  convertImageDataToURL(imageData, context) {
    let document = context.cloneScope.document;
    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    canvas.getContext("2d").putImageData(imageData, 0, 0);

    return canvas.toDataURL("image/png");
  },
};

// Parses a regular expression, with support for the Python extended
// syntax that allows setting flags by including the string (?im)
function parsePattern(pattern) {
  let flags = "";
  let match = /^\(\?([im]*)\)(.*)/.exec(pattern);
  if (match) {
    [, flags, pattern] = match;
  }
  return new RegExp(pattern, flags);
}

function getValueBaseType(value) {
  let t = typeof(value);
  if (t == "object") {
    if (value === null) {
      return "null";
    } else if (Array.isArray(value)) {
      return "array";
    } else if (Object.prototype.toString.call(value) == "[object ArrayBuffer]") {
      return "binary";
    }
  } else if (t == "number") {
    if (value % 1 == 0) {
      return "integer";
    }
  }
  return t;
}

// Methods of Context that are used by Schemas.normalize. These methods can be
// overridden at the construction of Context.
const CONTEXT_FOR_VALIDATION = [
  "checkLoadURL",
  "hasPermission",
  "logError",
];

// Methods of Context that are used by Schemas.inject.
// Callers of Schemas.inject should implement all of these methods.
const CONTEXT_FOR_INJECTION = [
  ...CONTEXT_FOR_VALIDATION,
  "getImplementation",
  "isPermissionRevokable",
  "shouldInject",
];

/**
 * A context for schema validation and error reporting. This class is only used
 * internally within Schemas.
 */
class Context {
  /**
   * @param {object} params Provides the implementation of this class.
   * @param {Array<string>} overridableMethods
   */
  constructor(params, overridableMethods = CONTEXT_FOR_VALIDATION) {
    this.params = params;

    this.path = [];
    this.preprocessors = {
      localize(value, context) {
        return value;
      },
    };
    this.postprocessors = POSTPROCESSORS;
    this.isChromeCompat = false;

    this.currentChoices = new Set();
    this.choicePathIndex = 0;

    for (let method of overridableMethods) {
      if (method in params) {
        this[method] = params[method].bind(params);
      }
    }

    let props = ["preprocessors", "isChromeCompat"];
    for (let prop of props) {
      if (prop in params) {
        if (prop in this && typeof this[prop] == "object") {
          Object.assign(this[prop], params[prop]);
        } else {
          this[prop] = params[prop];
        }
      }
    }
  }

  get choicePath() {
    let path = this.path.slice(this.choicePathIndex);
    return path.join(".");
  }

  get cloneScope() {
    return this.params.cloneScope;
  }

  get url() {
    return this.params.url;
  }

  get principal() {
    return this.params.principal || Services.scriptSecurityManager.createNullPrincipal({});
  }

  /**
   * Checks whether `url` may be loaded by the extension in this context.
   *
   * @param {string} url The URL that the extension wished to load.
   * @returns {boolean} Whether the context may load `url`.
   */
  checkLoadURL(url) {
    let ssm = Services.scriptSecurityManager;
    try {
      ssm.checkLoadURIStrWithPrincipal(this.principal, url,
                                       ssm.DISALLOW_INHERIT_PRINCIPAL);
    } catch (e) {
      return false;
    }
    return true;
  }

  /**
   * Checks whether this context has the given permission.
   *
   * @param {string} permission
   *        The name of the permission to check.
   *
   * @returns {boolean} True if the context has the given permission.
   */
  hasPermission(permission) {
    return false;
  }

  /**
   * Checks whether the given permission can be dynamically revoked or
   * granted.
   *
   * @param {string} permission
   *        The name of the permission to check.
   *
   * @returns {boolean} True if the given permission is revokable.
   */
  isPermissionRevokable(permission) {
    return false;
  }

  /**
   * Returns an error result object with the given message, for return
   * by Type normalization functions.
   *
   * If the context has a `currentTarget` value, this is prepended to
   * the message to indicate the location of the error.
   *
   * @param {string} errorMessage
   *        The error message which will be displayed when this is the
   *        only possible matching schema.
   * @param {string} choicesMessage
   *        The message describing the valid what constitutes a valid
   *        value for this schema, which will be displayed when multiple
   *        schema choices are available and none match.
   *
   *        A caller may pass `null` to prevent a choice from being
   *        added, but this should *only* be done from code processing a
   *        choices type.
   * @returns {object}
   */
  error(errorMessage, choicesMessage = undefined) {
    if (choicesMessage !== null) {
      let {choicePath} = this;
      if (choicePath) {
        choicesMessage = `.${choicePath} must ${choicesMessage}`;
      }

      this.currentChoices.add(choicesMessage);
    }

    if (this.currentTarget) {
      return {error: `Error processing ${this.currentTarget}: ${errorMessage}`};
    }
    return {error: errorMessage};
  }

  /**
   * Creates an `Error` object belonging to the current unprivileged
   * scope. If there is no unprivileged scope associated with this
   * context, the message is returned as a string.
   *
   * If the context has a `currentTarget` value, this is prepended to
   * the message, in the same way as for the `error` method.
   *
   * @param {string} message
   * @returns {Error}
   */
  makeError(message) {
    let {error} = this.error(message);
    if (this.cloneScope) {
      return new this.cloneScope.Error(error);
    }
    return error;
  }

  /**
   * Logs the given error to the console. May be overridden to enable
   * custom logging.
   *
   * @param {Error|string} error
   */
  logError(error) {
    Cu.reportError(error);
  }

  /**
   * Returns the name of the value currently being normalized. For a
   * nested object, this is usually approximately equivalent to the
   * JavaScript property accessor for that property. Given:
   *
   *   { foo: { bar: [{ baz: x }] } }
   *
   * When processing the value for `x`, the currentTarget is
   * 'foo.bar.0.baz'
   */
  get currentTarget() {
    return this.path.join(".");
  }

  /**
   * Executes the given callback, and returns an array of choice strings
   * passed to {@see #error} during its execution.
   *
   * @param {function} callback
   * @returns {object}
   *          An object with a `result` property containing the return
   *          value of the callback, and a `choice` property containing
   *          an array of choices.
   */
  withChoices(callback) {
    let {currentChoices, choicePathIndex} = this;

    let choices = new Set();
    this.currentChoices = choices;
    this.choicePathIndex = this.path.length;

    try {
      let result = callback();

      return {result, choices: Array.from(choices)};
    } finally {
      this.currentChoices = currentChoices;
      this.choicePathIndex = choicePathIndex;

      choices = Array.from(choices);
      if (choices.length == 1) {
        currentChoices.add(choices[0]);
      } else if (choices.length) {
        let n = choices.length - 1;
        choices[n] = `or ${choices[n]}`;

        this.error(null, `must either [${choices.join(", ")}]`);
      }
    }
  }

  /**
   * Appends the given component to the `currentTarget` path to indicate
   * that it is being processed, calls the given callback function, and
   * then restores the original path.
   *
   * This is used to identify the path of the property being processed
   * when reporting type errors.
   *
   * @param {string} component
   * @param {function} callback
   * @returns {*}
   */
  withPath(component, callback) {
    this.path.push(component);
    try {
      return callback();
    } finally {
      this.path.pop();
    }
  }
}

/**
 * Represents a schema entry to be injected into an object. Handles the
 * injection, revocation, and permissions of said entry.
 *
 * @param {InjectionContext} context
 *        The injection context for the entry.
 * @param {Entry} entry
 *        The entry to inject.
 * @param {object} parentObject
 *        The object into which to inject this entry.
 * @param {string} name
 *        The property name at which to inject this entry.
 * @param {Array<string>} path
 *        The full path from the root entry to this entry.
 * @param {Entry} parentEntry
 *        The parent entry for the injected entry.
 */
class InjectionEntry {
  constructor(context, entry, parentObj, name, path, parentEntry) {
    this.context = context;
    this.entry = entry;
    this.parentObj = parentObj;
    this.name = name;
    this.path = path;
    this.parentEntry = parentEntry;

    this.injected = null;
    this.lazyInjected = null;
  }

  /**
   * @property {Array<string>} allowedContexts
   *        The list of allowed contexts into which the entry may be
   *        injected.
   */
  get allowedContexts() {
    let {allowedContexts} = this.entry;
    if (allowedContexts.length) {
      return allowedContexts;
    }
    return this.parentEntry.defaultContexts;
  }

  /**
   * @property {boolean} isRevokable
   *        Returns true if this entry may be dynamically injected or
   *        revoked based on its permissions.
   */
  get isRevokable() {
    return (this.entry.permissions &&
            this.entry.permissions.some(perm => this.context.isPermissionRevokable(perm)));
  }

  /**
   * @property {boolean} hasPermission
   *        Returns true if the injection context currently has the
   *        appropriate permissions to access this entry.
   */
  get hasPermission() {
    return (!this.entry.permissions ||
            this.entry.permissions.some(perm => this.context.hasPermission(perm)));
  }

  /**
   * @property {boolean} shouldInject
   *        Returns true if this entry should be injected in the given
   *        context, without respect to permissions.
   */
  get shouldInject() {
    return this.context.shouldInject(this.path.join("."), this.name, this.allowedContexts);
  }

  /**
   * Revokes this entry, removing its property from its parent object,
   * and invalidating its wrappers.
   */
  revoke() {
    if (this.lazyInjected) {
      this.lazyInjected = false;
    } else if (this.injected) {
      if (this.injected.revoke) {
        this.injected.revoke();
      }

      try {
        let unwrapped = Cu.waiveXrays(this.parentObj);
        delete unwrapped[this.name];
      } catch (e) {
        Cu.reportError(e);
      }

      let {value} = this.injected.descriptor;
      if (value) {
        this.context.revokeChildren(value);
      }

      this.injected = null;
    }
  }

  /**
   * Returns a property descriptor object for this entry, if it should
   * be injected, or undefined if it should not.
   *
   * @returns {object?}
   *        A property descriptor object, or undefined if the property
   *        should be removed.
   */
  getDescriptor() {
    this.lazyInjected = false;

    if (this.injected) {
      let path = [...this.path, this.name];
      throw new Error(`Attempting to re-inject already injected entry: ${path.join(".")}`);
    }

    if (!this.shouldInject) {
      return;
    }

    if (this.isRevokable) {
      this.context.pendingEntries.add(this);
    }

    if (!this.hasPermission) {
      return;
    }

    this.injected = this.entry.getDescriptor(this.path, this.context);
    if (!this.injected) {
      return undefined;
    }

    return this.injected.descriptor;
  }

  /**
   * Injects a lazy property descriptor into the parent object which
   * checks permissions and eligibility for injection the first time it
   * is accessed.
   */
  lazyInject() {
    if (this.lazyInjected || this.injected) {
      let path = [...this.path, this.name];
      throw new Error(`Attempting to re-lazy-inject already injected entry: ${path.join(".")}`);
    }

    this.lazyInjected = true;
    exportLazyProperty(this.parentObj, this.name, () => {
      if (this.lazyInjected) {
        return this.getDescriptor();
      }
    });
  }

  /**
   * Injects or revokes this entry if its current state does not match
   * the context's current permissions.
   */
  permissionsChanged() {
    if (this.injected) {
      this.maybeRevoke();
    } else {
      this.maybeInject();
    }
  }

  maybeInject() {
    if (!this.injected && !this.lazyInjected) {
      this.lazyInject();
    }
  }

  maybeRevoke() {
    if (this.injected && !this.hasPermission) {
      this.revoke();
    }
  }
}

/**
 * Holds methods that run the actual implementation of the extension APIs. These
 * methods are only called if the extension API invocation matches the signature
 * as defined in the schema. Otherwise an error is reported to the context.
 */
class InjectionContext extends Context {
  constructor(params) {
    super(params, CONTEXT_FOR_INJECTION);

    this.pendingEntries = new Set();
    this.children = new DefaultWeakMap(() => new Map());

    if (params.setPermissionsChangedCallback) {
      params.setPermissionsChangedCallback(
        this.permissionsChanged.bind(this));
    }
  }

  /**
   * Check whether the API should be injected.
   *
   * @abstract
   * @param {string} namespace The namespace of the API. This may contain dots,
   *     e.g. in the case of "devtools.inspectedWindow".
   * @param {string} [name] The name of the property in the namespace.
   *     `null` if we are checking whether the namespace should be injected.
   * @param {Array<string>} allowedContexts A list of additional contexts in which
   *     this API should be available. May include any of:
   *         "main" - The main chrome browser process.
   *         "addon" - An addon process.
   *         "content" - A content process.
   * @returns {boolean} Whether the API should be injected.
   */
  shouldInject(namespace, name, allowedContexts) {
    throw new Error("Not implemented");
  }

  /**
   * Generate the implementation for `namespace`.`name`.
   *
   * @abstract
   * @param {string} namespace The full path to the namespace of the API, minus
   *     the name of the method or property. E.g. "storage.local".
   * @param {string} name The name of the method, property or event.
   * @returns {SchemaAPIInterface} The implementation of the API.
   */
  getImplementation(namespace, name) {
    throw new Error("Not implemented");
  }

  /**
   * Updates all injection entries which may need to be updated after a
   * permission change, revoking or re-injecting them as necessary.
   */
  permissionsChanged() {
    for (let entry of this.pendingEntries) {
      try {
        entry.permissionsChanged();
      } catch (e) {
        Cu.reportError(e);
      }
    }
  }

  /**
   * Recursively revokes all child injection entries of the given
   * object.
   *
   * @param {object} object
   *        The object for which to invoke children.
   */
  revokeChildren(object) {
    if (!this.children.has(object)) {
      return;
    }

    let children = this.children.get(object);
    for (let [name, entry] of children.entries()) {
      try {
        entry.revoke();
      } catch (e) {
        Cu.reportError(e);
      }
      children.delete(name);

      // When we revoke children for an object, we consider that object
      // dead. If the entry is ever reified again, a new object is
      // created, with new child entries.
      this.pendingEntries.delete(entry);
    }
    this.children.delete(object);
  }

  _getInjectionEntry(entry, dest, name, path, parentEntry) {
    let injection = new InjectionEntry(this, entry, dest, name, path, parentEntry);

    this.children.get(dest).set(name, injection);

    return injection;
  }

  /**
   * Returns the property descriptor for the given entry.
   *
   * @param {Entry} entry
   *        The entry instance to return a descriptor for.
   * @param {object} dest
   *        The object into which this entry is being injected.
   * @param {string} name
   *        The property name on the destination object where the entry
   *        will be injected.
   * @param {Array<string>} path
   *        The full path from the root injection object to this entry.
   * @param {Entry} parentEntry
   *        The parent entry for this entry.
   *
   * @returns {object?}
   *        A property descriptor object, or null if the entry should
   *        not be injected.
   */
  getDescriptor(entry, dest, name, path, parentEntry) {
    let injection = this._getInjectionEntry(entry, dest, name, path, parentEntry);

    return injection.getDescriptor();
  }

  /**
   * Lazily injects the given entry into the given object.
   *
   * @param {Entry} entry
   *        The entry instance to lazily inject.
   * @param {object} dest
   *        The object into which to inject this entry.
   * @param {string} name
   *        The property name at which to inject the entry.
   * @param {Array<string>} path
   *        The full path from the root injection object to this entry.
   * @param {Entry} parentEntry
   *        The parent entry for this entry.
   */
  injectInto(entry, dest, name, path, parentEntry) {
    let injection = this._getInjectionEntry(entry, dest, name, path, parentEntry);

    injection.lazyInject();
  }
}

/**
 * The methods in this singleton represent the "format" specifier for
 * JSON Schema string types.
 *
 * Each method either returns a normalized version of the original
 * value, or throws an error if the value is not valid for the given
 * format.
 */
const FORMATS = {
  hostname(string, context) {
    let valid = true;

    try {
      valid = new URL(`http://${string}`).host === string;
    } catch (e) {
      valid = false;
    }

    if (!valid) {
      throw new Error(`Invalid hostname ${string}`);
    }

    return string;
  },

  url(string, context) {
    let url = new URL(string).href;

    if (!context.checkLoadURL(url)) {
      throw new Error(`Access denied for URL ${url}`);
    }
    return url;
  },

  relativeUrl(string, context) {
    if (!context.url) {
      // If there's no context URL, return relative URLs unresolved, and
      // skip security checks for them.
      try {
        new URL(string);
      } catch (e) {
        return string;
      }
    }

    let url = new URL(string, context.url).href;

    if (!context.checkLoadURL(url)) {
      throw new Error(`Access denied for URL ${url}`);
    }
    return url;
  },

  strictRelativeUrl(string, context) {
    // Do not accept a string which resolves as an absolute URL, or any
    // protocol-relative URL.
    if (!string.startsWith("//")) {
      try {
        new URL(string);
      } catch (e) {
        return FORMATS.relativeUrl(string, context);
      }
    }

    throw new SyntaxError(`String ${JSON.stringify(string)} must be a relative URL`);
  },

  contentSecurityPolicy(string, context) {
    let error = contentPolicyService.validateAddonCSP(string);
    if (error != null) {
      throw new SyntaxError(error);
    }
    return string;
  },

  date(string, context) {
    // A valid ISO 8601 timestamp.
    const PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|([-+]\d{2}:?\d{2})))?$/;
    if (!PATTERN.test(string)) {
      throw new Error(`Invalid date string ${string}`);
    }
    // Our pattern just checks the format, we could still have invalid
    // values (e.g., month=99 or month=02 and day=31).  Let the Date
    // constructor do the dirty work of validating.
    if (isNaN(new Date(string))) {
      throw new Error(`Invalid date string ${string}`);
    }
    return string;
  },
};

// Schema files contain namespaces, and each namespace contains types,
// properties, functions, and events. An Entry is a base class for
// types, properties, functions, and events.
class Entry {
  constructor(schema = {}) {
    /**
     * If set to any value which evaluates as true, this entry is
     * deprecated, and any access to it will result in a deprecation
     * warning being logged to the browser console.
     *
     * If the value is a string, it will be appended to the deprecation
     * message. If it contains the substring "${value}", it will be
     * replaced with a string representation of the value being
     * processed.
     *
     * If the value is any other truthy value, a generic deprecation
     * message will be emitted.
     */
    this.deprecated = false;
    if ("deprecated" in schema) {
      this.deprecated = schema.deprecated;
    }

    /**
     * @property {string} [preprocessor]
     * If set to a string value, and a preprocessor of the same is
     * defined in the validation context, it will be applied to this
     * value prior to any normalization.
     */
    this.preprocessor = schema.preprocess || null;

    /**
     * @property {string} [postprocessor]
     * If set to a string value, and a postprocessor of the same is
     * defined in the validation context, it will be applied to this
     * value after any normalization.
     */
    this.postprocessor = schema.postprocess || null;

    /**
     * @property {Array<string>} allowedContexts A list of allowed contexts
     * to consider before generating the API.
     * These are not parsed by the schema, but passed to `shouldInject`.
     */
    this.allowedContexts = schema.allowedContexts || [];
  }

  /**
   * Preprocess the given value with the preprocessor declared in
   * `preprocessor`.
   *
   * @param {*} value
   * @param {Context} context
   * @returns {*}
   */
  preprocess(value, context) {
    if (this.preprocessor) {
      return context.preprocessors[this.preprocessor](value, context);
    }
    return value;
  }

  /**
   * Postprocess the given result with the postprocessor declared in
   * `postprocessor`.
   *
   * @param {object} result
   * @param {Context} context
   * @returns {object}
   */
  postprocess(result, context) {
    if (result.error || !this.postprocessor) {
      return result;
    }

    let value = context.postprocessors[this.postprocessor](result.value, context);
    return {value};
  }

  /**
   * Logs a deprecation warning for this entry, based on the value of
   * its `deprecated` property.
   *
   * @param {Context} context
   * @param {value} [value]
   */
  logDeprecation(context, value = null) {
    let message = "This property is deprecated";
    if (typeof(this.deprecated) == "string") {
      message = this.deprecated;
      if (message.includes("${value}")) {
        try {
          value = JSON.stringify(value);
        } catch (e) {
          value = String(value);
        }
        message = message.replace(/\$\{value\}/g, () => value);
      }
    }

    context.logError(context.makeError(message));
  }

  /**
   * Checks whether the entry is deprecated and, if so, logs a
   * deprecation message.
   *
   * @param {Context} context
   * @param {value} [value]
   */
  checkDeprecated(context, value = null) {
    if (this.deprecated) {
      this.logDeprecation(context, value);
    }
  }

  /**
   * Returns an object containing property descriptor for use when
   * injecting this entry into an API object.
   *
   * @param {Array<string>} path The API path, e.g. `["storage", "local"]`.
   * @param {InjectionContext} context
   *
   * @returns {object?}
   *        An object containing a `descriptor` property, specifying the
   *        entry's property descriptor, and an optional `revoke`
   *        method, to be called when the entry is being revoked.
   */
  getDescriptor(path, context) {
    return undefined;
  }
}

// Corresponds either to a type declared in the "types" section of the
// schema or else to any type object used throughout the schema.
class Type extends Entry {
  /**
   * @property {Array<string>} EXTRA_PROPERTIES
   *        An array of extra properties which may be present for
   *        schemas of this type.
   */
  static get EXTRA_PROPERTIES() {
    return ["description", "deprecated", "preprocess", "postprocess", "allowedContexts"];
  }

  /**
   * Parses the given schema object and returns an instance of this
   * class which corresponds to its properties.
   *
   * @param {object} schema
   *        A JSON schema object which corresponds to a definition of
   *        this type.
   * @param {Array<string>} path
   *        The path to this schema object from the root schema,
   *        corresponding to the property names and array indices
   *        traversed during parsing in order to arrive at this schema
   *        object.
   * @param {Array<string>} [extraProperties]
   *        An array of extra property names which are valid for this
   *        schema in the current context.
   * @returns {Type}
   *        An instance of this type which corresponds to the given
   *        schema object.
   * @static
   */
  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    return new this(schema);
  }

  /**
   * Checks that all of the properties present in the given schema
   * object are valid properties for this type, and throws if invalid.
   *
   * @param {object} schema
   *        A JSON schema object.
   * @param {Array<string>} path
   *        The path to this schema object from the root schema,
   *        corresponding to the property names and array indices
   *        traversed during parsing in order to arrive at this schema
   *        object.
   * @param {Array<string>} [extra]
   *        An array of extra property names which are valid for this
   *        schema in the current context.
   * @throws {Error}
   *        An error describing the first invalid property found in the
   *        schema object.
   */
  static checkSchemaProperties(schema, path, extra = []) {
    if (DEBUG) {
      let allowedSet = new Set([...this.EXTRA_PROPERTIES, ...extra]);

      for (let prop of Object.keys(schema)) {
        if (!allowedSet.has(prop)) {
          throw new Error(`Internal error: Namespace ${path.join(".")} has ` +
                          `invalid type property "${prop}" ` +
                          `in type "${schema.id || JSON.stringify(schema)}"`);
        }
      }
    }
  }

  // Takes a value, checks that it has the correct type, and returns a
  // "normalized" version of the value. The normalized version will
  // include "nulls" in place of omitted optional properties. The
  // result of this function is either {error: "Some type error"} or
  // {value: <normalized-value>}.
  normalize(value, context) {
    return context.error("invalid type");
  }

  // Unlike normalize, this function does a shallow check to see if
  // |baseType| (one of the possible getValueBaseType results) is
  // valid for this type. It returns true or false. It's used to fill
  // in optional arguments to functions before actually type checking

  checkBaseType(baseType) {
    return false;
  }

  // Helper method that simply relies on checkBaseType to implement
  // normalize. Subclasses can choose to use it or not.
  normalizeBase(type, value, context) {
    if (this.checkBaseType(getValueBaseType(value))) {
      this.checkDeprecated(context, value);
      return {value: this.preprocess(value, context)};
    }

    let choice;
    if (/^[aeiou]/.test(type)) {
      choice = `be an ${type} value`;
    } else {
      choice = `be a ${type} value`;
    }

    return context.error(`Expected ${type} instead of ${JSON.stringify(value)}`,
                         choice);
  }
}

// Type that allows any value.
class AnyType extends Type {
  normalize(value, context) {
    this.checkDeprecated(context, value);
    return this.postprocess({value}, context);
  }

  checkBaseType(baseType) {
    return true;
  }
}

// An untagged union type.
class ChoiceType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["choices", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let choices = schema.choices.map(t => Schemas.parseSchema(t, path));
    return new this(schema, choices);
  }

  constructor(schema, choices) {
    super(schema);
    this.choices = choices;
  }

  extend(type) {
    this.choices.push(...type.choices);

    return this;
  }

  normalize(value, context) {
    this.checkDeprecated(context, value);

    let error;
    let {choices, result} = context.withChoices(() => {
      for (let choice of this.choices) {
        let r = choice.normalize(value, context);
        if (!r.error) {
          return r;
        }

        error = r;
      }
    });

    if (result) {
      return result;
    }
    if (choices.length <= 1) {
      return error;
    }

    let n = choices.length - 1;
    choices[n] = `or ${choices[n]}`;

    let message;
    if (typeof value === "object") {
      message = `Value must either: ${choices.join(", ")}`;
    } else {
      message = `Value ${JSON.stringify(value)} must either: ${choices.join(", ")}`;
    }

    return context.error(message, null);
  }

  checkBaseType(baseType) {
    return this.choices.some(t => t.checkBaseType(baseType));
  }
}

// This is a reference to another type--essentially a typedef.
class RefType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["$ref", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let ref = schema.$ref;
    let ns = path[0];
    if (ref.includes(".")) {
      [ns, ref] = ref.split(".");
    }
    return new this(schema, ns, ref);
  }

  // For a reference to a type named T declared in namespace NS,
  // namespaceName will be NS and reference will be T.
  constructor(schema, namespaceName, reference) {
    super(schema);
    this.namespaceName = namespaceName;
    this.reference = reference;
  }

  get targetType() {
    let ns = Schemas.getNamespace(this.namespaceName);
    let type = ns.get(this.reference);
    if (!type) {
      throw new Error(`Internal error: Type ${this.reference} not found`);
    }
    return type;
  }

  normalize(value, context) {
    this.checkDeprecated(context, value);
    return this.targetType.normalize(value, context);
  }

  checkBaseType(baseType) {
    return this.targetType.checkBaseType(baseType);
  }
}

class StringType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["enum", "minLength", "maxLength", "pattern", "format",
            ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let enumeration = schema.enum || null;
    if (enumeration) {
      // The "enum" property is either a list of strings that are
      // valid values or else a list of {name, description} objects,
      // where the .name values are the valid values.
      enumeration = enumeration.map(e => {
        if (typeof(e) == "object") {
          return e.name;
        }
        return e;
      });
    }

    let pattern = null;
    if (schema.pattern) {
      try {
        pattern = parsePattern(schema.pattern);
      } catch (e) {
        throw new Error(`Internal error: Invalid pattern ${JSON.stringify(schema.pattern)}`);
      }
    }

    let format = null;
    if (schema.format) {
      if (!(schema.format in FORMATS)) {
        throw new Error(`Internal error: Invalid string format ${schema.format}`);
      }
      format = FORMATS[schema.format];
    }
    return new this(schema,
                    schema.id || undefined,
                    enumeration,
                    schema.minLength || 0,
                    schema.maxLength || Infinity,
                    pattern,
                    format);
  }

  constructor(schema, name, enumeration, minLength, maxLength, pattern, format) {
    super(schema);
    this.name = name;
    this.enumeration = enumeration;
    this.minLength = minLength;
    this.maxLength = maxLength;
    this.pattern = pattern;
    this.format = format;
  }

  normalize(value, context) {
    let r = this.normalizeBase("string", value, context);
    if (r.error) {
      return r;
    }
    value = r.value;

    if (this.enumeration) {
      if (this.enumeration.includes(value)) {
        return this.postprocess({value}, context);
      }

      let choices = this.enumeration.map(JSON.stringify).join(", ");

      return context.error(`Invalid enumeration value ${JSON.stringify(value)}`,
                           `be one of [${choices}]`);
    }

    if (value.length < this.minLength) {
      return context.error(`String ${JSON.stringify(value)} is too short (must be ${this.minLength})`,
                           `be longer than ${this.minLength}`);
    }
    if (value.length > this.maxLength) {
      return context.error(`String ${JSON.stringify(value)} is too long (must be ${this.maxLength})`,
                           `be shorter than ${this.maxLength}`);
    }

    if (this.pattern && !this.pattern.test(value)) {
      return context.error(`String ${JSON.stringify(value)} must match ${this.pattern}`,
                           `match the pattern ${this.pattern.toSource()}`);
    }

    if (this.format) {
      try {
        r.value = this.format(r.value, context);
      } catch (e) {
        return context.error(String(e), `match the format "${this.format.name}"`);
      }
    }

    return r;
  }

  checkBaseType(baseType) {
    return baseType == "string";
  }

  getDescriptor(path, context) {
    if (this.enumeration) {
      let obj = Cu.createObjectIn(context.cloneScope);

      for (let e of this.enumeration) {
        obj[e.toUpperCase()] = e;
      }

      return {
        descriptor: {value: obj},
      };
    }
  }
}

let FunctionEntry;
let Event;
let SubModuleType;

class ObjectType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["properties", "patternProperties", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    if ("functions" in schema) {
      return SubModuleType.parseSchema(schema, path, extraProperties);
    }

    if (DEBUG && !("$extend" in schema)) {
      // Only allow extending "properties" and "patternProperties".
      extraProperties = ["additionalProperties", "isInstanceOf", ...extraProperties];
    }
    this.checkSchemaProperties(schema, path, extraProperties);

    let parseProperty = (schema, extraProps = []) => {
      return {
        type: Schemas.parseSchema(schema, path,
                                  DEBUG && ["unsupported", "onError", "permissions", "default", ...extraProps]),
        optional: schema.optional || false,
        unsupported: schema.unsupported || false,
        onError: schema.onError || null,
        default: schema.default === undefined ? null : schema.default,
      };
    };

    // Parse explicit "properties" object.
    let properties = Object.create(null);
    for (let propName of Object.keys(schema.properties || {})) {
      properties[propName] = parseProperty(schema.properties[propName], ["optional"]);
    }

    // Parse regexp properties from "patternProperties" object.
    let patternProperties = [];
    for (let propName of Object.keys(schema.patternProperties || {})) {
      let pattern;
      try {
        pattern = parsePattern(propName);
      } catch (e) {
        throw new Error(`Internal error: Invalid property pattern ${JSON.stringify(propName)}`);
      }

      patternProperties.push({
        pattern,
        type: parseProperty(schema.patternProperties[propName]),
      });
    }

    // Parse "additionalProperties" schema.
    let additionalProperties = null;
    if (schema.additionalProperties) {
      let type = schema.additionalProperties;
      if (type === true) {
        type = {"type": "any"};
      }

      additionalProperties = Schemas.parseSchema(type, path);
    }

    return new this(schema, properties, additionalProperties, patternProperties, schema.isInstanceOf || null);
  }

  constructor(schema, properties, additionalProperties, patternProperties, isInstanceOf) {
    super(schema);
    this.properties = properties;
    this.additionalProperties = additionalProperties;
    this.patternProperties = patternProperties;
    this.isInstanceOf = isInstanceOf;
  }

  extend(type) {
    for (let key of Object.keys(type.properties)) {
      if (key in this.properties) {
        throw new Error(`InternalError: Attempt to extend an object with conflicting property "${key}"`);
      }
      this.properties[key] = type.properties[key];
    }

    this.patternProperties.push(...type.patternProperties);

    return this;
  }

  checkBaseType(baseType) {
    return baseType == "object";
  }

  /**
   * Extracts the enumerable properties of the given object, including
   * function properties which would normally be omitted by X-ray
   * wrappers.
   *
   * @param {object} value
   * @param {Context} context
   *        The current parse context.
   * @returns {object}
   *        An object with an `error` or `value` property.
   */
  extractProperties(value, context) {
    // |value| should be a JS Xray wrapping an object in the
    // extension compartment. This works well except when we need to
    // access callable properties on |value| since JS Xrays don't
    // support those. To work around the problem, we verify that
    // |value| is a plain JS object (i.e., not anything scary like a
    // Proxy). Then we copy the properties out of it into a normal
    // object using a waiver wrapper.

    let klass = Cu.getClassName(value, true);
    if (klass != "Object") {
      throw context.error(`Expected a plain JavaScript object, got a ${klass}`,
                          `be a plain JavaScript object`);
    }

    let properties = Object.create(null);

    let waived = Cu.waiveXrays(value);
    for (let prop of Object.getOwnPropertyNames(waived)) {
      let desc = Object.getOwnPropertyDescriptor(waived, prop);
      if (desc.get || desc.set) {
        throw context.error("Objects cannot have getters or setters on properties",
                            "contain no getter or setter properties");
      }
      // Chrome ignores non-enumerable properties.
      if (desc.enumerable) {
        properties[prop] = Cu.unwaiveXrays(desc.value);
      }
    }

    return properties;
  }

  checkProperty(context, prop, propType, result, properties, remainingProps) {
    let {type, optional, unsupported, onError} = propType;
    let error = null;

    if (unsupported) {
      if (prop in properties) {
        error = context.error(`Property "${prop}" is unsupported by Firefox`,
                              `not contain an unsupported "${prop}" property`);
      }
    } else if (prop in properties) {
      if (optional && (properties[prop] === null || properties[prop] === undefined)) {
        result[prop] = propType.default;
      } else {
        let r = context.withPath(prop, () => type.normalize(properties[prop], context));
        if (r.error) {
          error = r;
        } else {
          result[prop] = r.value;
          properties[prop] = r.value;
        }
      }
      remainingProps.delete(prop);
    } else if (!optional) {
      error = context.error(`Property "${prop}" is required`,
                            `contain the required "${prop}" property`);
    } else if (optional !== "omit-key-if-missing") {
      result[prop] = propType.default;
    }

    if (error) {
      if (onError == "warn") {
        context.logError(error.error);
      } else if (onError != "ignore") {
        throw error;
      }

      result[prop] = propType.default;
    }
  }

  normalize(value, context) {
    try {
      let v = this.normalizeBase("object", value, context);
      if (v.error) {
        return v;
      }
      value = v.value;

      if (this.isInstanceOf) {
        if (DEBUG) {
          if (Object.keys(this.properties).length ||
              this.patternProperties.length ||
              !(this.additionalProperties instanceof AnyType)) {
            throw new Error("InternalError: isInstanceOf can only be used " +
                            "with objects that are otherwise unrestricted");
          }
        }

        if (!instanceOf(value, this.isInstanceOf)) {
          return context.error(`Object must be an instance of ${this.isInstanceOf}`,
                               `be an instance of ${this.isInstanceOf}`);
        }

        // This is kind of a hack, but we can't normalize things that
        // aren't JSON, so we just return them.
        return this.postprocess({value}, context);
      }

      let properties = this.extractProperties(value, context);
      let remainingProps = new Set(Object.keys(properties));

      let result = {};
      for (let prop of Object.keys(this.properties)) {
        this.checkProperty(context, prop, this.properties[prop], result,
                           properties, remainingProps);
      }

      for (let prop of Object.keys(properties)) {
        for (let {pattern, type} of this.patternProperties) {
          if (pattern.test(prop)) {
            this.checkProperty(context, prop, type, result,
                               properties, remainingProps);
          }
        }
      }

      if (this.additionalProperties) {
        for (let prop of remainingProps) {
          let type = this.additionalProperties;
          let r = context.withPath(prop, () => type.normalize(properties[prop], context));
          if (r.error) {
            return r;
          }
          result[prop] = r.value;
        }
      } else if (remainingProps.size == 1) {
        return context.error(`Unexpected property "${[...remainingProps]}"`,
                             `not contain an unexpected "${[...remainingProps]}" property`);
      } else if (remainingProps.size) {
        let props = [...remainingProps].sort().join(", ");
        return context.error(`Unexpected properties: ${props}`,
                             `not contain the unexpected properties [${props}]`);
      }

      return this.postprocess({value: result}, context);
    } catch (e) {
      if (e.error) {
        return e;
      }
      throw e;
    }
  }
}

// This type is just a placeholder to be referred to by
// SubModuleProperty. No value is ever expected to have this type.
SubModuleType = class SubModuleType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["functions", "events", "properties", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    // The path we pass in here is only used for error messages.
    path = [...path, schema.id];
    let functions = schema.functions.filter(fun => !fun.unsupported)
                          .map(fun => FunctionEntry.parseSchema(fun, path));

    let events = [];

    if (schema.events) {
      events = schema.events.filter(event => !event.unsupported)
                     .map(event => Event.parseSchema(event, path));
    }

    return new this(functions, events);
  }

  constructor(functions, events) {
    super();
    this.functions = functions;
    this.events = events;
  }
};

class NumberType extends Type {
  normalize(value, context) {
    let r = this.normalizeBase("number", value, context);
    if (r.error) {
      return r;
    }

    if (isNaN(r.value) || !Number.isFinite(r.value)) {
      return context.error("NaN and infinity are not valid",
                           "be a finite number");
    }

    return r;
  }

  checkBaseType(baseType) {
    return baseType == "number" || baseType == "integer";
  }
}

class IntegerType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["minimum", "maximum", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    return new this(schema, schema.minimum || -Infinity, schema.maximum || Infinity);
  }

  constructor(schema, minimum, maximum) {
    super(schema);
    this.minimum = minimum;
    this.maximum = maximum;
  }

  normalize(value, context) {
    let r = this.normalizeBase("integer", value, context);
    if (r.error) {
      return r;
    }
    value = r.value;

    // Ensure it's between -2**31 and 2**31-1
    if (!Number.isSafeInteger(value)) {
      return context.error("Integer is out of range",
                           "be a valid 32 bit signed integer");
    }

    if (value < this.minimum) {
      return context.error(`Integer ${value} is too small (must be at least ${this.minimum})`,
                           `be at least ${this.minimum}`);
    }
    if (value > this.maximum) {
      return context.error(`Integer ${value} is too big (must be at most ${this.maximum})`,
                           `be no greater than ${this.maximum}`);
    }

    return this.postprocess(r, context);
  }

  checkBaseType(baseType) {
    return baseType == "integer";
  }
}

class BooleanType extends Type {
  normalize(value, context) {
    return this.normalizeBase("boolean", value, context);
  }

  checkBaseType(baseType) {
    return baseType == "boolean";
  }
}

class ArrayType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["items", "minItems", "maxItems", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let items = Schemas.parseSchema(schema.items, path, ["onError"]);

    return new this(schema, items, schema.minItems || 0, schema.maxItems || Infinity);
  }

  constructor(schema, itemType, minItems, maxItems) {
    super(schema);
    this.itemType = itemType;
    this.minItems = minItems;
    this.maxItems = maxItems;
    this.onError = schema.items.onError || null;
  }

  normalize(value, context) {
    let v = this.normalizeBase("array", value, context);
    if (v.error) {
      return v;
    }
    value = v.value;

    let result = [];
    for (let [i, element] of value.entries()) {
      element = context.withPath(String(i), () => this.itemType.normalize(element, context));
      if (element.error) {
        if (this.onError == "warn") {
          context.logError(element.error);
        } else if (this.onError != "ignore") {
          return element;
        }
        continue;
      }
      result.push(element.value);
    }

    if (result.length < this.minItems) {
      return context.error(`Array requires at least ${this.minItems} items; you have ${result.length}`,
                           `have at least ${this.minItems} items`);
    }

    if (result.length > this.maxItems) {
      return context.error(`Array requires at most ${this.maxItems} items; you have ${result.length}`,
                           `have at most ${this.maxItems} items`);
    }

    return this.postprocess({value: result}, context);
  }

  checkBaseType(baseType) {
    return baseType == "array";
  }
}

class FunctionType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["parameters", "async", "returns", "requireUserInput",
            ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let isAsync = !!schema.async;
    let isExpectingCallback = typeof schema.async === "string";
    let parameters = null;
    if ("parameters" in schema) {
      parameters = [];
      for (let param of schema.parameters) {
        // Callbacks default to optional for now, because of promise
        // handling.
        let isCallback = isAsync && param.name == schema.async;
        if (isCallback) {
          isExpectingCallback = false;
        }

        parameters.push({
          type: Schemas.parseSchema(param, path, ["name", "optional", "default"]),
          name: param.name,
          optional: param.optional == null ? isCallback : param.optional,
          default: param.default == undefined ? null : param.default,
        });
      }
    }
    let hasAsyncCallback = false;
    if (isAsync) {
      hasAsyncCallback = (parameters &&
                          parameters.length &&
                          parameters[parameters.length - 1].name == schema.async);
    }

    if (DEBUG) {
      if (isExpectingCallback) {
        throw new Error(`Internal error: Expected a callback parameter ` +
                        `with name ${schema.async}`);
      }

      if (isAsync && schema.returns) {
        throw new Error("Internal error: Async functions must not " +
                        "have return values.");
      }
      if (isAsync && schema.allowAmbiguousOptionalArguments && !hasAsyncCallback) {
        throw new Error("Internal error: Async functions with ambiguous " +
                        "arguments must declare the callback as the last parameter");
      }
    }


    return new this(schema, parameters, isAsync, hasAsyncCallback,
                    !!schema.requireUserInput);
  }

  constructor(schema, parameters, isAsync, hasAsyncCallback, requireUserInput) {
    super(schema);
    this.parameters = parameters;
    this.isAsync = isAsync;
    this.hasAsyncCallback = hasAsyncCallback;
    this.requireUserInput = requireUserInput;
  }

  normalize(value, context) {
    return this.normalizeBase("function", value, context);
  }

  checkBaseType(baseType) {
    return baseType == "function";
  }
}

// Represents a "property" defined in a schema namespace with a
// particular value. Essentially this is a constant.
class ValueProperty extends Entry {
  constructor(schema, name, value) {
    super(schema);
    this.name = name;
    this.value = value;
  }

  getDescriptor(path, context) {
    return {
      descriptor: {value: this.value},
    };
  }
}

// Represents a "property" defined in a schema namespace that is not a
// constant.
class TypeProperty extends Entry {
  constructor(schema, path, name, type, writable, permissions) {
    super(schema);
    this.path = path;
    this.name = name;
    this.type = type;
    this.writable = writable;
    this.permissions = permissions;
  }

  throwError(context, msg) {
    throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`);
  }

  getDescriptor(path, context) {
    if (this.unsupported) {
      return;
    }

    let apiImpl = context.getImplementation(path.join("."), this.name);

    let getStub = () => {
      this.checkDeprecated(context);
      return apiImpl.getProperty();
    };

    let descriptor = {
      get: Cu.exportFunction(getStub, context.cloneScope),
    };

    if (this.writable) {
      let setStub = (value) => {
        let normalized = this.type.normalize(value, context);
        if (normalized.error) {
          this.throwError(context, normalized.error);
        }

        apiImpl.setProperty(normalized.value);
      };

      descriptor.set = Cu.exportFunction(setStub, context.cloneScope);
    }

    return {
      descriptor,
      revoke() {
        apiImpl.revoke();
        apiImpl = null;
      },
    };
  }
}

class SubModuleProperty extends Entry {
  // A SubModuleProperty represents a tree of objects and properties
  // to expose to an extension. Currently we support only a limited
  // form of sub-module properties, where "$ref" points to a
  // SubModuleType containing a list of functions and "properties" is
  // a list of additional simple properties.
  //
  // name: Name of the property stuff is being added to.
  // namespaceName: Namespace in which the property lives.
  // reference: Name of the type defining the functions to add to the property.
  // properties: Additional properties to add to the module (unsupported).
  constructor(schema, path, name, reference, properties, permissions) {
    super(schema);
    this.name = name;
    this.path = path;
    this.namespaceName = path.join(".");
    this.reference = reference;
    this.properties = properties;
    this.permissions = permissions;
  }

  getDescriptor(path, context) {
    let obj = Cu.createObjectIn(context.cloneScope);

    let ns = Schemas.getNamespace(this.namespaceName);
    let type = ns.get(this.reference);
    if (!type && this.reference.includes(".")) {
      let [namespaceName, ref] = this.reference.split(".");
      ns = Schemas.getNamespace(namespaceName);
      type = ns.get(ref);
    }

    if (DEBUG) {
      if (!type || !(type instanceof SubModuleType)) {
        throw new Error(`Internal error: ${this.namespaceName}.${this.reference} ` +
                        `is not a sub-module`);
      }
    }
    let subpath = [...path, this.name];

    let functions = type.functions;
    for (let fun of functions) {
      context.injectInto(fun, obj, fun.name, subpath, ns);
    }

    let events = type.events;
    for (let event of events) {
      context.injectInto(event, obj, event.name, subpath, ns);
    }

    // TODO: Inject this.properties.

    return {
      descriptor: {value: obj},
      revoke() {
        let unwrapped = Cu.waiveXrays(obj);
        for (let fun of functions) {
          try {
            delete unwrapped[fun.name];
          } catch (e) {
            Cu.reportError(e);
          }
        }
      },
    };
  }
}

// This class is a base class for FunctionEntrys and Events. It takes
// care of validating parameter lists (i.e., handling of optional
// parameters and parameter type checking).
class CallEntry extends Entry {
  constructor(schema, path, name, parameters, allowAmbiguousOptionalArguments) {
    super(schema);
    this.path = path;
    this.name = name;
    this.parameters = parameters;
    this.allowAmbiguousOptionalArguments = allowAmbiguousOptionalArguments;
  }

  throwError(context, msg) {
    throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`);
  }

  checkParameters(args, context) {
    let fixedArgs = [];

    // First we create a new array, fixedArgs, that is the same as
    // |args| but with default values in place of omitted optional parameters.
    let check = (parameterIndex, argIndex) => {
      if (parameterIndex == this.parameters.length) {
        if (argIndex == args.length) {
          return true;
        }
        return false;
      }

      let parameter = this.parameters[parameterIndex];
      if (parameter.optional) {
        // Try skipping it.
        fixedArgs[parameterIndex] = parameter.default;
        if (check(parameterIndex + 1, argIndex)) {
          return true;
        }
      }

      if (argIndex == args.length) {
        return false;
      }

      let arg = args[argIndex];
      if (!parameter.type.checkBaseType(getValueBaseType(arg))) {
        // For Chrome compatibility, use the default value if null or undefined
        // is explicitly passed but is not a valid argument in this position.
        if (parameter.optional && (arg === null || arg === undefined)) {
          fixedArgs[parameterIndex] = Cu.cloneInto(parameter.default, global);
        } else {
          return false;
        }
      } else {
        fixedArgs[parameterIndex] = arg;
      }

      return check(parameterIndex + 1, argIndex + 1);
    };

    if (this.allowAmbiguousOptionalArguments) {
      // When this option is set, it's up to the implementation to
      // parse arguments.
      // The last argument for asynchronous methods is either a function or null.
      // This is specifically done for runtime.sendMessage.
      if (this.hasAsyncCallback && typeof(args[args.length - 1]) != "function") {
        args.push(null);
      }
      return args;
    }
    let success = check(0, 0);
    if (!success) {
      this.throwError(context, "Incorrect argument types");
    }

    // Now we normalize (and fully type check) all non-omitted arguments.
    fixedArgs = fixedArgs.map((arg, parameterIndex) => {
      if (arg === null) {
        return null;
      }
      let parameter = this.parameters[parameterIndex];
      let r = parameter.type.normalize(arg, context);
      if (r.error) {
        this.throwError(context, `Type error for parameter ${parameter.name} (${r.error})`);
      }
      return r.value;
    });

    return fixedArgs;
  }
}

// Represents a "function" defined in a schema namespace.
FunctionEntry = class FunctionEntry extends CallEntry {
  static parseSchema(schema, path) {
    // When not in DEBUG mode, we just need to know *if* this returns.
    let returns = !!schema.returns;
    if (DEBUG && "returns" in schema) {
      returns = {
        type: Schemas.parseSchema(schema.returns, path, ["optional", "name"]),
        optional: schema.returns.optional || false,
        name: "result",
      };
    }

    return new this(schema, path, schema.name,
                    Schemas.parseSchema(schema, path,
                      ["name", "unsupported", "returns",
                       "permissions",
                       "allowAmbiguousOptionalArguments"]),
                    schema.unsupported || false,
                    schema.allowAmbiguousOptionalArguments || false,
                    returns,
                    schema.permissions || null);
  }

  constructor(schema, path, name, type, unsupported, allowAmbiguousOptionalArguments, returns, permissions) {
    super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments);
    this.unsupported = unsupported;
    this.returns = returns;
    this.permissions = permissions;

    this.isAsync = type.isAsync;
    this.hasAsyncCallback = type.hasAsyncCallback;
    this.requireUserInput = type.requireUserInput;
  }

  checkValue({type, optional, name}, value, context) {
    if (optional && value == null) {
      return;
    }
    if (type.reference === "ExtensionPanel" || type.reference === "Port") {
      // TODO: We currently treat objects with functions as SubModuleType,
      // which is just wrong, and a bigger yak.  Skipping for now.
      return;
    }
    const {error} = type.normalize(value, context);
    if (error) {
      this.throwError(context, `Type error for ${name} value (${error})`);
    }
  }

  checkCallback(args, context) {
    const callback = this.parameters[this.parameters.length - 1];
    for (const [i, param] of callback.type.parameters.entries()) {
      this.checkValue(param, args[i], context);
    }
  }

  getDescriptor(path, context) {
    let apiImpl = context.getImplementation(path.join("."), this.name);

    let stub;
    if (this.isAsync) {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        let callback = null;
        if (this.hasAsyncCallback) {
          callback = actuals.pop();
        }
        if (callback === null && context.isChromeCompat) {
          // We pass an empty stub function as a default callback for
          // the `chrome` API, so promise objects are not returned,
          // and lastError values are reported immediately.
          callback = () => {};
        }
        if (DEBUG && this.hasAsyncCallback && callback) {
          let original = callback;
          callback = (...args) => {
            this.checkCallback(args, context);
            original(...args);
          };
        }
        let result = apiImpl.callAsyncFunction(actuals, callback, this.requireUserInput);
        if (DEBUG && this.hasAsyncCallback && !callback) {
          return result.then(result => {
            this.checkCallback([result], context);
            return result;
          });
        }
        return result;
      };
    } else if (!this.returns) {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        return apiImpl.callFunctionNoReturn(actuals);
      };
    } else {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        let result = apiImpl.callFunction(actuals);
        if (DEBUG && this.returns) {
          this.checkValue(this.returns, result, context);
        }
        return result;
      };
    }

    return {
      descriptor: {value: Cu.exportFunction(stub, context.cloneScope)},
      revoke() {
        apiImpl.revoke();
        apiImpl = null;
      },
    };
  }
};

// Represents an "event" defined in a schema namespace.
//
// TODO(rpl): we should be able to remove the eslint-disable-line that follows
// once Bug 1369722 has been fixed.
Event = class Event extends CallEntry { // eslint-disable-line no-native-reassign
  static parseSchema(event, path) {
    let extraParameters = Array.from(event.extraParameters || [], param => ({
      type: Schemas.parseSchema(param, path, ["name", "optional", "default"]),
      name: param.name,
      optional: param.optional || false,
      default: param.default == undefined ? null : param.default,
    }));

    let extraProperties = ["name", "unsupported", "permissions", "extraParameters",
                           // We ignore these properties for now.
                           "returns", "filters"];

    return new this(event, path, event.name,
                    Schemas.parseSchema(event, path, extraProperties),
                    extraParameters,
                    event.unsupported || false,
                    event.permissions || null);
  }

  constructor(schema, path, name, type, extraParameters, unsupported, permissions) {
    super(schema, path, name, extraParameters);
    this.type = type;
    this.unsupported = unsupported;
    this.permissions = permissions;
  }

  checkListener(listener, context) {
    let r = this.type.normalize(listener, context);
    if (r.error) {
      this.throwError(context, "Invalid listener");
    }
    return r.value;
  }

  getDescriptor(path, context) {
    let apiImpl = context.getImplementation(path.join("."), this.name);

    let addStub = (listener, ...args) => {
      listener = this.checkListener(listener, context);
      let actuals = this.checkParameters(args, context);
      apiImpl.addListener(listener, actuals);
    };

    let removeStub = (listener) => {
      listener = this.checkListener(listener, context);
      apiImpl.removeListener(listener);
    };

    let hasStub = (listener) => {
      listener = this.checkListener(listener, context);
      return apiImpl.hasListener(listener);
    };

    let obj = Cu.createObjectIn(context.cloneScope);

    Cu.exportFunction(addStub, obj, {defineAs: "addListener"});
    Cu.exportFunction(removeStub, obj, {defineAs: "removeListener"});
    Cu.exportFunction(hasStub, obj, {defineAs: "hasListener"});

    return {
      descriptor: {value: obj},
      revoke() {
        apiImpl.revoke();
        apiImpl = null;

        let unwrapped = Cu.waiveXrays(obj);
        delete unwrapped.addListener;
        delete unwrapped.removeListener;
        delete unwrapped.hasListener;
      },
    };
  }
};

const TYPES = Object.freeze(Object.assign(Object.create(null), {
  any: AnyType,
  array: ArrayType,
  boolean: BooleanType,
  function: FunctionType,
  integer: IntegerType,
  number: NumberType,
  object: ObjectType,
  string: StringType,
}));

const LOADERS = {
  events: "loadEvent",
  functions: "loadFunction",
  properties: "loadProperty",
  types: "loadType",
};

class Namespace extends Map {
  constructor(name, path) {
    super();

    this._lazySchemas = [];
    this.initialized = false;

    this.name = name;
    this.path = name ? [...path, name] : [...path];

    this.superNamespace = null;

    this.permissions = null;
    this.allowedContexts = [];
    this.defaultContexts = [];
  }

  /**
   * Adds a JSON Schema object to the set of schemas that represent this
   * namespace.
   *
   * @param {object} schema
   *        A JSON schema object which partially describes this
   *        namespace.
   */
  addSchema(schema) {
    this._lazySchemas.push(schema);

    for (let prop of ["permissions", "allowedContexts", "defaultContexts"]) {
      if (schema[prop]) {
        this[prop] = schema[prop];
      }
    }

    if (schema.$import) {
      this.superNamespace = Schemas.getNamespace(schema.$import);
    }
  }

  /**
   * Initializes the keys of this namespace based on the schema objects
   * added via previous `addSchema` calls.
   */
  init() {
    if (this.initialized) {
      return;
    }

    if (this.superNamespace) {
      this._lazySchemas.unshift(...this.superNamespace._lazySchemas);
    }

    for (let type of Object.keys(LOADERS)) {
      this[type] = new DefaultMap(() => []);
    }

    for (let schema of this._lazySchemas) {
      for (let type of schema.types || []) {
        if (!type.unsupported) {
          this.types.get(type.$extend || type.id).push(type);
        }
      }

      for (let [name, prop] of Object.entries(schema.properties || {})) {
        if (!prop.unsupported) {
          this.properties.get(name).push(prop);
        }
      }

      for (let fun of schema.functions || []) {
        if (!fun.unsupported) {
          this.functions.get(fun.name).push(fun);
        }
      }

      for (let event of schema.events || []) {
        if (!event.unsupported) {
          this.events.get(event.name).push(event);
        }
      }
    }

    // For each type of top-level property in the schema object, iterate
    // over all properties of that type, and create a temporary key for
    // each property pointing to its type. Those temporary properties
    // are later used to instantiate an Entry object based on the actual
    // schema object.
    for (let type of Object.keys(LOADERS)) {
      for (let key of this[type].keys()) {
        this.set(key, type);
      }
    }

    this.initialized = true;

    if (DEBUG) {
      for (let key of this.keys()) {
        this.get(key);
      }
    }
  }

  /**
   * Initializes the value of a given key, by parsing the schema object
   * associated with it and replacing its temporary value with an `Entry`
   * instance.
   *
   * @param {string} key
   *        The name of the property to initialize.
   * @param {string} type
   *        The type of property the key represents. Must have a
   *        corresponding entry in the `LOADERS` object, pointing to the
   *        initialization method for that type.
   *
   * @returns {Entry}
   */
  initKey(key, type) {
    let loader = LOADERS[type];

    for (let schema of this[type].get(key)) {
      this.set(key, this[loader](key, schema));
    }

    return this.get(key);
  }

  loadType(name, type) {
    if ("$extend" in type) {
      return this.extendType(type);
    }
    return Schemas.parseSchema(type, this.path, ["id"]);
  }

  extendType(type) {
    let targetType = this.get(type.$extend);

    // Only allow extending object and choices types for now.
    if (targetType instanceof ObjectType) {
      type.type = "object";
    } else if (DEBUG) {
      if (!targetType) {
        throw new Error(`Internal error: Attempt to extend a nonexistant type ${type.$extend}`);
      } else if (!(targetType instanceof ChoiceType)) {
        throw new Error(`Internal error: Attempt to extend a non-extensible type ${type.$extend}`);
      }
    }

    let parsed = Schemas.parseSchema(type, this.path, ["$extend"]);

    if (DEBUG && parsed.constructor !== targetType.constructor) {
      throw new Error(`Internal error: Bad attempt to extend ${type.$extend}`);
    }

    targetType.extend(parsed);

    return targetType;
  }

  loadProperty(name, prop) {
    if ("$ref" in prop) {
      if (!prop.unsupported) {
        return new SubModuleProperty(prop, this.path, name,
                                     prop.$ref, prop.properties || {},
                                     prop.permissions || null);
      }
    } else if ("value" in prop) {
      return new ValueProperty(prop, name, prop.value);
    } else {
      // We ignore the "optional" attribute on properties since we
      // don't inject anything here anyway.
      let type = Schemas.parseSchema(prop, [this.name], ["optional", "permissions", "writable"]);
      return new TypeProperty(prop, this.path, name, type, prop.writable || false,
                              prop.permissions || null);
    }
  }

  loadFunction(name, fun) {
    return FunctionEntry.parseSchema(fun, this.path);
  }

  loadEvent(name, event) {
    return Event.parseSchema(event, this.path);
  }

  /**
   * Injects the properties of this namespace into the given object.
   *
   * @param {object} dest
   *        The object into which to inject the namespace properties.
   * @param {InjectionContext} context
   *        The injection context with which to inject the properties.
   */
  injectInto(dest, context) {
    for (let name of this.keys()) {
      exportLazyProperty(dest, name, () => {
        let entry = this.get(name);

        return context.getDescriptor(entry, dest, name, this.path, this);
      });
    }
  }

  getDescriptor(path, context) {
    let obj = Cu.createObjectIn(context.cloneScope);

    this.injectInto(obj, context);

    // Only inject the namespace object if it isn't empty.
    if (Object.keys(obj).length) {
      return {
        descriptor: {value: obj},
      };
    }
  }

  keys() {
    this.init();
    return super.keys();
  }

  * entries() {
    for (let key of this.keys()) {
      yield [key, this.get(key)];
    }
  }

  get(key) {
    this.init();
    let value = super.get(key);

    // The initial values of lazily-initialized schema properties are
    // strings, pointing to the type of property, corresponding to one
    // of the entries in the `LOADERS` object.
    if (typeof value === "string") {
      value = this.initKey(key, value);
    }

    return value;
  }

  /**
   * Returns a Namespace object for the given namespace name. If a
   * namespace object with this name does not already exist, it is
   * created. If the name contains any '.' characters, namespaces are
   * recursively created, for each dot-separated component.
   *
   * @param {string} name
   *        The name of the sub-namespace to retrieve.
   *
   * @returns {Namespace}
   */
  getNamespace(name) {
    let subName;

    let idx = name.indexOf(".");
    if (idx > 0) {
      subName = name.slice(idx + 1);
      name = name.slice(0, idx);
    }

    let ns = super.get(name);
    if (!ns) {
      ns = new Namespace(name, this.path);
      this.set(name, ns);
    }

    if (subName) {
      return ns.getNamespace(subName);
    }
    return ns;
  }

  has(key) {
    this.init();
    return super.has(key);
  }
}

this.Schemas = {
  initialized: false,

  REVOKE: Symbol("@@revoke"),

  // Maps a schema URL to the JSON contained in that schema file. This
  // is useful for sending the JSON across processes.
  schemaJSON: new Map(),

  // Map[<schema-name> -> Map[<symbol-name> -> Entry]]
  // This keeps track of all the schemas that have been loaded so far.
  rootNamespace: new Namespace("", []),

  getNamespace(name) {
    return this.rootNamespace.getNamespace(name);
  },

  parseSchema(schema, path, extraProperties = []) {
    let allowedProperties = DEBUG && new Set(extraProperties);

    if ("choices" in schema) {
      return ChoiceType.parseSchema(schema, path, allowedProperties);
    } else if ("$ref" in schema) {
      return RefType.parseSchema(schema, path, allowedProperties);
    }

    let type = TYPES[schema.type];

    if (DEBUG) {
      allowedProperties.add("type");

      if (!("type" in schema)) {
        throw new Error(`Unexpected value for type: ${JSON.stringify(schema)}`);
      }

      if (!type) {
        throw new Error(`Unexpected type ${schema.type}`);
      }
    }

    return type.parseSchema(schema, path, allowedProperties);
  },

  init() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
      let data = Services.cpmm.initialProcessData;
      let schemas = data["Extension:Schemas"];
      if (schemas) {
        this.schemaJSON = schemas;
      }

      Services.cpmm.addMessageListener("Schema:Add", this);
    }

    this.flushSchemas();
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Schema:Add":
        this.schemaJSON.set(msg.data.url, msg.data.schema);
        this.flushSchemas();
        break;

      case "Schema:Delete":
        this.schemaJSON.delete(msg.data.url);
        this.flushSchemas();
        break;
    }
  },

  _needFlush: true,
  flushSchemas() {
    if (this._needFlush) {
      this._needFlush = false;
      XPCOMUtils.defineLazyGetter(this, "rootNamespace",
                                  () => this.parseSchemas());
    }
  },

  parseSchemas() {
    this._needFlush = true;

    Object.defineProperty(this, "rootNamespace", {
      enumerable: true,
      configurable: true,
      value: new Namespace("", []),
    });

    for (let [key, schema] of this.schemaJSON.entries()) {
      try {
        if (typeof schema.deserialize === "function") {
          schema = schema.deserialize(global);

          // If we're in the parent process, we need to keep the
          // StructuredCloneHolder blob around in order to send to future child
          // processes. If we're in a child, we have no further use for it, so
          // just store the deserialized schema data in its place.
          if (!isParentProcess) {
            this.schemaJSON.set(key, schema);
          }
        }

        this.loadSchema(schema);
      } catch (e) {
        Cu.reportError(e);
      }
    }

    return this.rootNamespace;
  },

  loadSchema(json) {
    for (let namespace of json) {
      this.getNamespace(namespace.namespace)
          .addSchema(namespace);
    }
  },

  _loadCachedSchemasPromise: null,
  loadCachedSchemas() {
    if (!this._loadCachedSchemasPromise) {
      this._loadCachedSchemasPromise = StartupCache.schemas.getAll().then(results => {
        return results;
      });
    }

    return this._loadCachedSchemasPromise;
  },

  addSchema(url, schema) {
    this.schemaJSON.set(url, schema);

    let data = Services.ppmm.initialProcessData;
    data["Extension:Schemas"] = this.schemaJSON;

    Services.ppmm.broadcastAsyncMessage("Schema:Add", {url, schema});

    this.flushSchemas();
  },

  async load(url) {
    if (!isParentProcess) {
      return;
    }

    let schemaCache = await this.loadCachedSchemas();

    let blob = (schemaCache.get(url) ||
                await StartupCache.schemas.get(url, readJSONAndBlobbify));

    if (!this.schemaJSON.has(url)) {
      this.addSchema(url, blob);
    }
  },

  unload(url) {
    this.schemaJSON.delete(url);

    let data = Services.ppmm.initialProcessData;
    data["Extension:Schemas"] = this.schemaJSON;

    Services.ppmm.broadcastAsyncMessage("Schema:Delete", {url});

    this.flushSchemas();
  },

  /**
   * Checks whether a given object has the necessary permissions to
   * expose the given namespace.
   *
   * @param {string} namespace
   *        The top-level namespace to check permissions for.
   * @param {object} wrapperFuncs
   *        Wrapper functions for the given context.
   * @param {function} wrapperFuncs.hasPermission
   *        A function which, when given a string argument, returns true
   *        if the context has the given permission.
   * @returns {boolean}
   *        True if the context has permission for the given namespace.
   */
  checkPermissions(namespace, wrapperFuncs) {
    if (!this.initialized) {
      this.init();
    }

    let ns = this.getNamespace(namespace);
    if (ns && ns.permissions) {
      return ns.permissions.some(perm => wrapperFuncs.hasPermission(perm));
    }
    return true;
  },

  exportLazyGetter,

  /**
   * Inject registered extension APIs into `dest`.
   *
   * @param {object} dest The root namespace for the APIs.
   *     This object is usually exposed to extensions as "chrome" or "browser".
   * @param {object} wrapperFuncs An implementation of the InjectionContext
   *     interface, which runs the actual functionality of the generated API.
   */
  inject(dest, wrapperFuncs) {
    if (!this.initialized) {
      this.init();
    }

    let context = new InjectionContext(wrapperFuncs);

    this.rootNamespace.injectInto(dest, context);
  },

  /**
   * Normalize `obj` according to the loaded schema for `typeName`.
   *
   * @param {object} obj The object to normalize against the schema.
   * @param {string} typeName The name in the format namespace.propertyname
   * @param {object} context An implementation of Context. Any validation errors
   *     are reported to the given context.
   * @returns {object} The normalized object.
   */
  normalize(obj, typeName, context) {
    if (!this.initialized) {
      this.init();
    }

    let [namespaceName, prop] = typeName.split(".");
    let ns = this.getNamespace(namespaceName);
    let type = ns.get(prop);

    return type.normalize(obj, new Context(context));
  },
};
PK
!<

modules/ScrollPosition.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If 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 = ["ScrollPosition"];

const Ci = Components.interfaces;

/**
 * It provides methods to collect scroll positions from single frames and to
 * restore scroll positions for frame trees.
 *
 * This is a child process module.
 */
this.ScrollPosition = Object.freeze({
  collect(frame) {
    return ScrollPositionInternal.collect(frame);
  },

  restoreTree(root, data) {
    ScrollPositionInternal.restoreTree(root, data);
  }
});

/**
 * This module's internal API.
 */
var ScrollPositionInternal = {
  /**
   * Collects scroll position data for any given |frame| in the frame hierarchy.
   *
   * @param frame (DOMWindow)
   *
   * @return {scroll: "x,y"} e.g. {scroll: "100,200"}
   *         Returns null when there is no scroll data we want to store for the
   *         given |frame|.
   */
  collect(frame) {
    let ifreq = frame.QueryInterface(Ci.nsIInterfaceRequestor);
    let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
    let scrollX = {}, scrollY = {};
    utils.getScrollXY(false /* no layout flush */, scrollX, scrollY);

    if (scrollX.value || scrollY.value) {
      return {scroll: scrollX.value + "," + scrollY.value};
    }

    return null;
  },

  /**
   * Restores scroll position data for any given |frame| in the frame hierarchy.
   *
   * @param frame (DOMWindow)
   * @param value (object, see collect())
   */
  restore(frame, value) {
    let match;

    if (value && (match = /(\d+),(\d+)/.exec(value))) {
      frame.scrollTo(match[1], match[2]);
    }
  },

  /**
   * Restores scroll position data for the current frame hierarchy starting at
   * |root| using the given scroll position |data|.
   *
   * If the given |root| frame's hierarchy doesn't match that of the given
   * |data| object we will silently discard data for unreachable frames. We
   * may as well assign scroll positions to the wrong frames if some were
   * reordered or removed.
   *
   * @param root (DOMWindow)
   * @param data (object)
   *        {
   *          scroll: "100,200",
   *          children: [
   *            {scroll: "100,200"},
   *            null,
   *            {scroll: "200,300", children: [ ... ]}
   *          ]
   *        }
   */
  restoreTree(root, data) {
    if (data.hasOwnProperty("scroll")) {
      this.restore(root, data.scroll);
    }

    if (!data.hasOwnProperty("children")) {
      return;
    }

    let frames = root.frames;
    data.children.forEach((child, index) => {
      if (child && index < frames.length) {
        this.restoreTree(frames[index], child);
      }
    });
  }
};
PK
!<Nmodules/SearchStaticData.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module contains additional data about default search engines that is the
 * same across all languages.  This information is defined outside of the actual
 * search engine definition files, so that localizers don't need to update them
 * when a change is made.
 *
 * This separate module is also easily overridable, in case a hotfix is needed.
 * No high-level processing logic is applied here.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "SearchStaticData",
];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

// To update this list of known alternate domains, just cut-and-paste from
// https://www.google.com/supported_domains
const gGoogleDomainsSource = ".google.com .google.ad .google.ae .google.com.af .google.com.ag .google.com.ai .google.al .google.am .google.co.ao .google.com.ar .google.as .google.at .google.com.au .google.az .google.ba .google.com.bd .google.be .google.bf .google.bg .google.com.bh .google.bi .google.bj .google.com.bn .google.com.bo .google.com.br .google.bs .google.bt .google.co.bw .google.by .google.com.bz .google.ca .google.cd .google.cf .google.cg .google.ch .google.ci .google.co.ck .google.cl .google.cm .google.cn .google.com.co .google.co.cr .google.com.cu .google.cv .google.com.cy .google.cz .google.de .google.dj .google.dk .google.dm .google.com.do .google.dz .google.com.ec .google.ee .google.com.eg .google.es .google.com.et .google.fi .google.com.fj .google.fm .google.fr .google.ga .google.ge .google.gg .google.com.gh .google.com.gi .google.gl .google.gm .google.gp .google.gr .google.com.gt .google.gy .google.com.hk .google.hn .google.hr .google.ht .google.hu .google.co.id .google.ie .google.co.il .google.im .google.co.in .google.iq .google.is .google.it .google.je .google.com.jm .google.jo .google.co.jp .google.co.ke .google.com.kh .google.ki .google.kg .google.co.kr .google.com.kw .google.kz .google.la .google.com.lb .google.li .google.lk .google.co.ls .google.lt .google.lu .google.lv .google.com.ly .google.co.ma .google.md .google.me .google.mg .google.mk .google.ml .google.com.mm .google.mn .google.ms .google.com.mt .google.mu .google.mv .google.mw .google.com.mx .google.com.my .google.co.mz .google.com.na .google.com.nf .google.com.ng .google.com.ni .google.ne .google.nl .google.no .google.com.np .google.nr .google.nu .google.co.nz .google.com.om .google.com.pa .google.com.pe .google.com.pg .google.com.ph .google.com.pk .google.pl .google.pn .google.com.pr .google.ps .google.pt .google.com.py .google.com.qa .google.ro .google.ru .google.rw .google.com.sa .google.com.sb .google.sc .google.se .google.com.sg .google.sh .google.si .google.sk .google.com.sl .google.sn .google.so .google.sm .google.sr .google.st .google.com.sv .google.td .google.tg .google.co.th .google.com.tj .google.tk .google.tl .google.tm .google.tn .google.to .google.com.tr .google.tt .google.com.tw .google.co.tz .google.com.ua .google.co.ug .google.co.uk .google.com.uy .google.co.uz .google.com.vc .google.co.ve .google.vg .google.co.vi .google.com.vn .google.vu .google.ws .google.rs .google.co.za .google.co.zm .google.co.zw .google.cat";
const gGoogleDomains = gGoogleDomainsSource.split(" ").map(d => "www" + d);

this.SearchStaticData = {
  /**
   * Returns a list of alternate domains for a given search engine domain.
   *
   * @param aDomain
   *        Lowercase host name to look up. For example, if this argument is
   *        "www.google.com" or "www.google.co.uk", the function returns the
   *        full list of supported Google domains.
   *
   * @return Array containing one entry for each alternate host name, or empty
   *         array if none is known.  The returned array should not be modified.
   */
  getAlternateDomains(aDomain) {
    return gGoogleDomains.indexOf(aDomain) == -1 ? [] : gGoogleDomains;
  },
};
PK
!<X6t99&modules/SearchSuggestionController.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["SearchSuggestionController"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NS_ASSERT", "resource://gre/modules/debug.js");

const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json";
const DEFAULT_FORM_HISTORY_PARAM      = "searchbar-history";
const HTTP_OK            = 200;
const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
const REMOTE_TIMEOUT_PREF = "browser.search.suggest.timeout";
const REMOTE_TIMEOUT_DEFAULT = 500; // maximum time (ms) to wait before giving up on a remote suggestions

/**
 * Remote search suggestions will be shown if gRemoteSuggestionsEnabled
 * is true. Global because only one pref observer is needed for all instances.
 */
var gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
Services.prefs.addObserver(BROWSER_SUGGEST_PREF, function(aSubject, aTopic, aData) {
  gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
});

/**
 * SearchSuggestionController.jsm exists as a helper module to allow multiple consumers to request and display
 * search suggestions from a given engine, regardless of the base implementation. Much of this
 * code was originally in nsSearchSuggestions.js until it was refactored to separate it from the
 * nsIAutoCompleteSearch dependency.
 * One instance of SearchSuggestionController should be used per field since form history results are cached.
 */

/**
 * @param {function} [callback] - Callback for search suggestion results. You can use the promise
 *                                returned by the search method instead if you prefer.
 * @constructor
 */
this.SearchSuggestionController = function SearchSuggestionController(callback = null) {
  this._callback = callback;
};

this.SearchSuggestionController.prototype = {
  /**
   * The maximum number of local form history results to return. This limit is
   * only enforced if remote results are also returned.
   */
  maxLocalResults: 5,

  /**
   * The maximum number of remote search engine results to return.
   * We'll actually only display at most
   * maxRemoteResults - <displayed local results count> remote results.
   */
  maxRemoteResults: 10,

  /**
   * The additional parameter used when searching form history.
   */
  formHistoryParam: DEFAULT_FORM_HISTORY_PARAM,

  // Private properties
  /**
   * The last form history result used to improve the performance of subsequent searches.
   * This shouldn't be used for any other purpose as it is never cleared and therefore could be stale.
   */
  _formHistoryResult: null,

  /**
   * The remote server timeout timer, if applicable. The timer starts when form history
   * search is completed.
   */
  _remoteResultTimer: null,

  /**
   * The deferred for the remote results before its promise is resolved.
   */
  _deferredRemoteResult: null,

  /**
   * The optional result callback registered from the constructor.
   */
  _callback: null,

  /**
   * The XMLHttpRequest object for remote results.
   */
  _request: null,

  // Public methods

  /**
   * Fetch search suggestions from all of the providers. Fetches in progress will be stopped and
   * results from them will not be provided.
   *
   * @param {string} searchTerm - the term to provide suggestions for
   * @param {bool} privateMode - whether the request is being made in the context of private browsing
   * @param {nsISearchEngine} engine - search engine for the suggestions.
   * @param {int} userContextId - the userContextId of the selected tab.
   *
   * @return {Promise} resolving to an object containing results or null.
   */
  fetch(searchTerm, privateMode, engine, userContextId) {
    // There is no smart filtering from previous results here (as there is when looking through
    // history/form data) because the result set returned by the server is different for every typed
    // value - e.g. "ocean breathes" does not return a subset of the results returned for "ocean".

    this.stop();

    if (!Services.search.isInitialized) {
      throw new Error("Search not initialized yet (how did you get here?)");
    }
    if (typeof privateMode === "undefined") {
      throw new Error("The privateMode argument is required to avoid unintentional privacy leaks");
    }
    if (!(engine instanceof Ci.nsISearchEngine)) {
      throw new Error("Invalid search engine");
    }
    if (!this.maxLocalResults && !this.maxRemoteResults) {
      throw new Error("Zero results expected, what are you trying to do?");
    }
    if (this.maxLocalResults < 0 || this.maxRemoteResults < 0) {
      throw new Error("Number of requested results must be positive");
    }

    // Array of promises to resolve before returning results.
    let promises = [];
    this._searchString = searchTerm;

    // Remote results
    if (searchTerm && gRemoteSuggestionsEnabled && this.maxRemoteResults &&
        engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON)) {
      this._deferredRemoteResult = this._fetchRemote(searchTerm, engine, privateMode, userContextId);
      promises.push(this._deferredRemoteResult.promise);
    }

    // Local results from form history
    if (this.maxLocalResults) {
      promises.push(this._fetchFormHistory(searchTerm));
    }

    function handleRejection(reason) {
      if (reason == "HTTP request aborted") {
        // Do nothing since this is normal.
        return null;
      }
      Cu.reportError("SearchSuggestionController rejection: " + reason);
      return null;
    }
    return Promise.all(promises).then(this._dedupeAndReturnResults.bind(this), handleRejection);
  },

  /**
   * Stop pending fetches so no results are returned from them.
   *
   * Note: If there was no remote results fetched, the fetching cannot be stopped and local results
   * will still be returned because stopping relies on aborting the XMLHTTPRequest to reject the
   * promise for Promise.all.
   */
  stop() {
    if (this._request) {
      this._request.abort();
    } else if (!this.maxRemoteResults) {
      Cu.reportError("SearchSuggestionController: Cannot stop fetching if remote results were not " +
                     "requested");
    }
    this._reset();
  },

  // Private methods

  _fetchFormHistory(searchTerm) {
    return new Promise(resolve => {
      let acSearchObserver = {
        // Implements nsIAutoCompleteSearch
        onSearchResult: (search, result) => {
          this._formHistoryResult = result;

          if (this._request) {
            this._remoteResultTimer = Cc["@mozilla.org/timer;1"].
                                      createInstance(Ci.nsITimer);
            this._remoteResultTimer.initWithCallback(this._onRemoteTimeout.bind(this),
                                                     this.remoteTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
          }

          switch (result.searchResult) {
            case Ci.nsIAutoCompleteResult.RESULT_SUCCESS:
            case Ci.nsIAutoCompleteResult.RESULT_NOMATCH:
              if (result.searchString !== this._searchString) {
                resolve("Unexpected response, this._searchString does not match form history response");
                return;
              }
              let fhEntries = [];
              for (let i = 0; i < result.matchCount; ++i) {
                fhEntries.push(result.getValueAt(i));
              }
              resolve({
                result: fhEntries,
                formHistoryResult: result,
              });
              break;
            case Ci.nsIAutoCompleteResult.RESULT_FAILURE:
            case Ci.nsIAutoCompleteResult.RESULT_IGNORED:
              resolve("Form History returned RESULT_FAILURE or RESULT_IGNORED");
              break;
          }
        },
      };

      let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"].
                        createInstance(Ci.nsIAutoCompleteSearch);
      formHistory.startSearch(searchTerm, this.formHistoryParam || DEFAULT_FORM_HISTORY_PARAM,
                              this._formHistoryResult,
                              acSearchObserver);
    });
  },

  /**
   * Fetch suggestions from the search engine over the network.
   */
  _fetchRemote(searchTerm, engine, privateMode, userContextId) {
    let deferredResponse = PromiseUtils.defer();
    this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                    createInstance(Ci.nsIXMLHttpRequest);
    let submission = engine.getSubmission(searchTerm,
                                          SEARCH_RESPONSE_SUGGESTION_JSON);
    let method = (submission.postData ? "POST" : "GET");
    this._request.open(method, submission.uri.spec, true);

    this._request.setOriginAttributes({userContextId,
                                       privateBrowsingId: privateMode ? 1 : 0});

    this._request.mozBackgroundRequest = true; // suppress dialogs and fail silently

    this._request.addEventListener("load", this._onRemoteLoaded.bind(this, deferredResponse));
    this._request.addEventListener("error", (evt) => deferredResponse.resolve("HTTP error"));
    // Reject for an abort assuming it's always from .stop() in which case we shouldn't return local
    // or remote results for existing searches.
    this._request.addEventListener("abort", (evt) => deferredResponse.reject("HTTP request aborted"));

    this._request.send(submission.postData);

    return deferredResponse;
  },

  /**
   * Called when the request completed successfully (thought the HTTP status could be anything)
   * so we can handle the response data.
   * @private
   */
  _onRemoteLoaded(deferredResponse) {
    if (!this._request) {
      deferredResponse.resolve("Got HTTP response after the request was cancelled");
      return;
    }

    let status, serverResults;
    try {
      status = this._request.status;
    } catch (e) {
      // The XMLHttpRequest can throw NS_ERROR_NOT_AVAILABLE.
      deferredResponse.resolve("Unknown HTTP status: " + e);
      return;
    }

    if (status != HTTP_OK || this._request.responseText == "") {
      deferredResponse.resolve("Non-200 status or empty HTTP response: " + status);
      return;
    }

    try {
      serverResults = JSON.parse(this._request.responseText);
    } catch (ex) {
      deferredResponse.resolve("Failed to parse suggestion JSON: " + ex);
      return;
    }

    if (!serverResults[0] ||
        this._searchString.localeCompare(serverResults[0], undefined,
                                         { sensitivity: "base" })) {
      // something is wrong here so drop remote results
      deferredResponse.resolve("Unexpected response, this._searchString does not match remote response");
      return;
    }
    let results = serverResults[1] || [];
    deferredResponse.resolve({ result: results });
  },

  /**
   * Called when this._remoteResultTimer fires indicating the remote request took too long.
   */
  _onRemoteTimeout() {
    this._request = null;

    // FIXME: bug 387341
    // Need to break the cycle between us and the timer.
    this._remoteResultTimer = null;

    // The XMLHTTPRequest for suggest results is taking too long
    // so send out the form history results and cancel the request.
    if (this._deferredRemoteResult) {
      this._deferredRemoteResult.resolve("HTTP Timeout");
      this._deferredRemoteResult = null;
    }
  },

  /**
   * @param {Array} suggestResults - an array of result objects from different sources (local or remote)
   * @return {Object}
   */
  _dedupeAndReturnResults(suggestResults) {
    if (this._searchString === null) {
      // _searchString can be null if stop() was called and remote suggestions
      // were disabled (stopping if we are fetching remote suggestions will
      // cause a promise rejection before we reach _dedupeAndReturnResults).
      return null;
    }

    let results = {
      term: this._searchString,
      remote: [],
      local: [],
      formHistoryResult: null,
    };

    for (let result of suggestResults) {
      if (typeof result === "string") { // Failure message
        Cu.reportError("SearchSuggestionController: " + result);
      } else if (result.formHistoryResult) { // Local results have a formHistoryResult property.
        results.formHistoryResult = result.formHistoryResult;
        results.local = result.result || [];
      } else { // Remote result
        results.remote = result.result || [];
      }
    }

    // If we have remote results, cap the number of local results
    if (results.remote.length) {
      results.local = results.local.slice(0, this.maxLocalResults);
    }

    // We don't want things to appear in both history and suggestions so remove entries from
    // remote results that are already in local.
    if (results.remote.length && results.local.length) {
      for (let i = 0; i < results.local.length; ++i) {
        let term = results.local[i];
        let dupIndex = results.remote.indexOf(term);
        if (dupIndex != -1) {
          results.remote.splice(dupIndex, 1);
        }
      }
    }

    // Trim the number of results to the maximum requested (now that we've pruned dupes).
    results.remote =
      results.remote.slice(0, this.maxRemoteResults - results.local.length);

    if (this._callback) {
      this._callback(results);
    }
    this._reset();

    return results;
  },

  _reset() {
    this._request = null;
    if (this._remoteResultTimer) {
      this._remoteResultTimer.cancel();
      this._remoteResultTimer = null;
    }
    this._deferredRemoteResult = null;
    this._searchString = null;
  },
};

/**
 * Determines whether the given engine offers search suggestions.
 *
 * @param {nsISearchEngine} engine - The search engine
 * @return {boolean} True if the engine offers suggestions and false otherwise.
 */
this.SearchSuggestionController.engineOffersSuggestions = function(engine) {
 return engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON);
};

/**
 * The maximum time (ms) to wait before giving up on a remote suggestions.
 */
XPCOMUtils.defineLazyPreferenceGetter(this.SearchSuggestionController.prototype, "remoteTimeout",
                                      REMOTE_TIMEOUT_PREF, REMOTE_TIMEOUT_DEFAULT);
PK
!<|0;0;modules/SelectContentHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                   "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");

const kStateActive = 0x00000001; // NS_EVENT_STATE_ACTIVE
const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER

const SUPPORTED_PROPERTIES = [
  "color",
  "background-color",
  "text-shadow",
];

// A process global state for whether or not content thinks
// that a <select> dropdown is open or not. This is managed
// entirely within this module, and is read-only accessible
// via SelectContentHelper.open.
var gOpen = false;

this.EXPORTED_SYMBOLS = [
  "SelectContentHelper"
];

this.SelectContentHelper = function(aElement, aOptions, aGlobal) {
  this.element = aElement;
  this.initialSelection = aElement[aElement.selectedIndex] || null;
  this.global = aGlobal;
  this.closedWithEnter = false;
  this.isOpenedViaTouch = aOptions.isOpenedViaTouch;
  this._selectBackgroundColor = null;
  this._selectColor = null;
  this._uaBackgroundColor = null;
  this._uaColor = null;
  this._uaSelectBackgroundColor = null;
  this._uaSelectColor = null;
  this._closeAfterBlur = true;
  this._pseudoStylesSetup = false;
  this._lockedDescendants = null;
  this.init();
  this.showDropDown();
  this._updateTimer = new DeferredTask(this._update.bind(this), 0);
}

Object.defineProperty(SelectContentHelper, "open", {
  get() {
    return gOpen;
  },
});

this.SelectContentHelper.prototype = {
  init() {
    this.global.addMessageListener("Forms:SelectDropDownItem", this);
    this.global.addMessageListener("Forms:DismissedDropDown", this);
    this.global.addMessageListener("Forms:MouseOver", this);
    this.global.addMessageListener("Forms:MouseOut", this);
    this.global.addMessageListener("Forms:MouseUp", this);
    this.global.addMessageListener("Forms:SearchFocused", this);
    this.global.addMessageListener("Forms:BlurDropDown-Pong", this);
    this.global.addEventListener("pagehide", this, { mozSystemGroup: true });
    this.global.addEventListener("mozhidedropdown", this, { mozSystemGroup: true });
    this.element.addEventListener("blur", this, { mozSystemGroup: true });
    this.element.addEventListener("transitionend", this, { mozSystemGroup: true });
    let MutationObserver = this.element.ownerGlobal.MutationObserver;
    this.mut = new MutationObserver(mutations => {
      // Something changed the <select> while it was open, so
      // we'll poke a DeferredTask to update the parent sometime
      // in the very near future.
      this._updateTimer.arm();
    });
    this.mut.observe(this.element, {childList: true, subtree: true, attributes: true});
  },

  uninit() {
    this.element.openInParentProcess = false;
    this.global.removeMessageListener("Forms:SelectDropDownItem", this);
    this.global.removeMessageListener("Forms:DismissedDropDown", this);
    this.global.removeMessageListener("Forms:MouseOver", this);
    this.global.removeMessageListener("Forms:MouseOut", this);
    this.global.removeMessageListener("Forms:MouseUp", this);
    this.global.removeMessageListener("Forms:SearchFocused", this);
    this.global.removeMessageListener("Forms:BlurDropDown-Pong", this);
    this.global.removeEventListener("pagehide", this, { mozSystemGroup: true });
    this.global.removeEventListener("mozhidedropdown", this, { mozSystemGroup: true });
    this.element.removeEventListener("blur", this, { mozSystemGroup: true });
    this.element.removeEventListener("transitionend", this, { mozSystemGroup: true });
    this.element = null;
    this.global = null;
    this.mut.disconnect();
    this._updateTimer.disarm();
    this._updateTimer = null;
    gOpen = false;
  },

  showDropDown() {
    this.element.openInParentProcess = true;
    this._setupPseudoClassStyles();
    let rect = this._getBoundingContentRect();
    let computedStyles = getComputedStyles(this.element);
    this._selectBackgroundColor = computedStyles.backgroundColor;
    this._selectColor = computedStyles.color;
    this._selectTextShadow = computedStyles.textShadow;
    this.global.sendAsyncMessage("Forms:ShowDropDown", {
      direction: computedStyles.direction,
      isOpenedViaTouch: this.isOpenedViaTouch,
      options: this._buildOptionList(),
      rect,
      selectedIndex: this.element.selectedIndex,
      selectBackgroundColor: this._selectBackgroundColor,
      selectColor: this._selectColor,
      selectTextShadow: this._selectTextShadow,
      uaBackgroundColor: this.uaBackgroundColor,
      uaColor: this.uaColor,
      uaSelectBackgroundColor: this.uaSelectBackgroundColor,
      uaSelectColor: this.uaSelectColor
    });
    this._clearPseudoClassStyles();
    gOpen = true;
  },

  _setupPseudoClassStyles() {
    if (this._pseudoStylesSetup) {
      throw new Error("pseudo styles must not be set up yet");
    }
    // Do all of the things that change style at once, before we read
    // any styles.
    this._pseudoStylesSetup = true;
    DOMUtils.addPseudoClassLock(this.element, ":focus");
    let lockedDescendants = this._lockedDescendants = this.element.querySelectorAll(":checked");
    for (let child of lockedDescendants) {
      // Selected options have the :checked pseudo-class, which
      // we want to disable before calculating the computed
      // styles since the user agent styles alter the styling
      // based on :checked.
      DOMUtils.addPseudoClassLock(child, ":checked", false);
    }
  },

  _clearPseudoClassStyles() {
    if (!this._pseudoStylesSetup) {
      throw new Error("pseudo styles must be set up already");
    }
    // Undo all of the things that change style at once, after we're
    // done reading styles.
    DOMUtils.clearPseudoClassLocks(this.element);
    let lockedDescendants = this._lockedDescendants;
    for (let child of lockedDescendants) {
      DOMUtils.clearPseudoClassLocks(child);
    }
    this._lockedDescendants = null;
    this._pseudoStylesSetup = false;
  },

  _getBoundingContentRect() {
    return BrowserUtils.getElementBoundingScreenRect(this.element);
  },

  _buildOptionList() {
    if (!this._pseudoStylesSetup) {
      throw new Error("pseudo styles must be set up");
    }
    return buildOptionListForChildren(this.element);
  },

  _update() {
    // The <select> was updated while the dropdown was open.
    // Let's send up a new list of options.
    // Technically we might not need to set this pseudo-class
    // during _update() since the element should organically
    // have :focus, though it is here for belt-and-suspenders.
    this._setupPseudoClassStyles();
    let computedStyles = getComputedStyles(this.element);
    this._selectBackgroundColor = computedStyles.backgroundColor;
    this._selectColor = computedStyles.color;
    this._selectTextShadow = computedStyles.textShadow;
    this.global.sendAsyncMessage("Forms:UpdateDropDown", {
      options: this._buildOptionList(),
      selectedIndex: this.element.selectedIndex,
      selectBackgroundColor: this._selectBackgroundColor,
      selectColor: this._selectColor,
      selectTextShadow: this._selectTextShadow,
      uaBackgroundColor: this.uaBackgroundColor,
      uaColor: this.uaColor,
      uaSelectBackgroundColor: this.uaSelectBackgroundColor,
      uaSelectColor: this.uaSelectColor
    });
    this._clearPseudoClassStyles();
  },

  // Determine user agent background-color and color.
  // This is used to skip applying the custom color if it matches
  // the user agent values.
  _calculateUAColors() {
    let dummyOption = this.element.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml", "option");
    dummyOption.style.setProperty("color", "-moz-comboboxtext", "important");
    dummyOption.style.setProperty("background-color", "-moz-combobox", "important");
    let optionCS = this.element.ownerGlobal.getComputedStyle(dummyOption);
    this._uaBackgroundColor = optionCS.backgroundColor;
    this._uaColor = optionCS.color;
    let dummySelect = this.element.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml", "select");
    dummySelect.style.setProperty("color", "-moz-fieldtext", "important");
    dummySelect.style.setProperty("background-color", "-moz-field", "important");
    let selectCS = this.element.ownerGlobal.getComputedStyle(dummySelect);
    this._uaSelectBackgroundColor = selectCS.backgroundColor;
    this._uaSelectColor = selectCS.color;
  },

  get uaBackgroundColor() {
    if (!this._uaBackgroundColor) {
      this._calculateUAColors();
    }
    return this._uaBackgroundColor;
  },

  get uaColor() {
    if (!this._uaColor) {
      this._calculateUAColors();
    }
    return this._uaColor;
  },

  get uaSelectBackgroundColor() {
    if (!this._selectBackgroundColor) {
      this._calculateUAColors();
    }
    return this._uaSelectBackgroundColor;
  },

  get uaSelectColor() {
    if (!this._selectBackgroundColor) {
      this._calculateUAColors();
    }
    return this._uaSelectColor;
  },

  dispatchMouseEvent(win, target, eventName) {
    let mouseEvent = new win.MouseEvent(eventName, {
      view: win,
      bubbles: true,
      cancelable: true,
    });
    target.dispatchEvent(mouseEvent);
  },

  receiveMessage(message) {
    switch (message.name) {
      case "Forms:SelectDropDownItem":
        this.element.selectedIndex = message.data.value;
        this.closedWithEnter = message.data.closedWithEnter;
        break;

      case "Forms:DismissedDropDown":
        let selectedOption = this.element.item(this.element.selectedIndex);
        if (this.initialSelection === selectedOption) {
          // Clear active document
          DOMUtils.removeContentState(this.element,
                                      kStateActive,
                                      /* aClearActiveDocument */ true);
        } else {
          let win = this.element.ownerGlobal;
          // For ordering of events, we're using non-e10s as our guide here,
          // since the spec isn't exactly clear. In non-e10s, we fire:
          // mousedown, mouseup, input, change, click if the user clicks
          // on an element in the dropdown. If the user uses the keyboard
          // to select an element in the dropdown, we only fire input and
          // change events.
          if (!this.closedWithEnter) {
            this.dispatchMouseEvent(win, selectedOption, "mousedown");
            this.dispatchMouseEvent(win, selectedOption, "mouseup");
          }
          // Clear active document no matter user selects
          // via keyboard or mouse
          DOMUtils.removeContentState(this.element,
                                      kStateActive,
                                      /* aClearActiveDocument */ true);

          let inputEvent = new win.UIEvent("input", {
            bubbles: true,
          });
          this.element.dispatchEvent(inputEvent);

          let changeEvent = new win.Event("change", {
            bubbles: true,
          });
          this.element.dispatchEvent(changeEvent);

          if (!this.closedWithEnter) {
            this.dispatchMouseEvent(win, selectedOption, "click");
          }
        }

        this.uninit();
        break;

      case "Forms:MouseOver":
        DOMUtils.setContentState(this.element, kStateHover);
        break;

      case "Forms:MouseOut":
        DOMUtils.removeContentState(this.element, kStateHover);
        break;

      case "Forms:MouseUp":
        let win = this.element.ownerGlobal;
        if (message.data.onAnchor) {
          this.dispatchMouseEvent(win, this.element, "mouseup");
        }
        DOMUtils.removeContentState(this.element, kStateActive);
        if (message.data.onAnchor) {
          this.dispatchMouseEvent(win, this.element, "click");
        }
        break;

      case "Forms:SearchFocused":
        this._closeAfterBlur = false;
        break;

      case "Forms:BlurDropDown-Pong":
        if (!this._closeAfterBlur || !gOpen) {
          return;
        }
        this.global.sendAsyncMessage("Forms:HideDropDown", {});
        this.uninit();
        break;
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "pagehide":
        if (this.element.ownerDocument === event.target) {
          this.global.sendAsyncMessage("Forms:HideDropDown", {});
          this.uninit();
        }
        break;
      case "blur": {
        if (this.element !== event.target) {
          break;
        }
        this._closeAfterBlur = true;
        // Send a ping-pong message to make sure that we wait for
        // enough cycles to pass from the potential focusing of the
        // search box to disable closing-after-blur.
        this.global.sendAsyncMessage("Forms:BlurDropDown-Ping", {});
        break;
      }
      case "mozhidedropdown":
        if (this.element === event.target) {
          this.global.sendAsyncMessage("Forms:HideDropDown", {});
          this.uninit();
        }
        break;
      case "transitionend":
        if (SUPPORTED_PROPERTIES.indexOf(event.propertyName) != -1) {
          this._updateTimer.arm();
        }
        break;
    }
  }

}

function getComputedStyles(element) {
  return element.ownerGlobal.getComputedStyle(element);
}

function buildOptionListForChildren(node) {
  let result = [];

  for (let child of node.children) {
    let tagName = child.tagName.toUpperCase();

    if (tagName == "OPTION" || tagName == "OPTGROUP") {
      if (child.hidden) {
        continue;
      }

      let textContent =
        tagName == "OPTGROUP" ? child.getAttribute("label")
                              : child.text;
      if (textContent == null) {
        textContent = "";
      }

      let cs = getComputedStyles(child);

      // Note: If you add any more CSS properties support here,
      // please add the property name to the SUPPORTED_PROPERTIES
      // list so that the menu can be correctly updated when CSS
      // transitions are used.
      let info = {
        index: child.index,
        tagName,
        textContent,
        disabled: child.disabled,
        display: cs.display,
        // We need to do this for every option element as each one can have
        // an individual style set for direction
        textDirection: cs.direction,
        tooltip: child.title,
        backgroundColor: cs.backgroundColor,
        color: cs.color,
        children: tagName == "OPTGROUP" ? buildOptionListForChildren(child) : []
      };

      if (cs.textShadow != "none") {
        info.textShadow = cs.textShadow;
      }

      result.push(info);
    }
  }
  return result;
}
PK
!<GFFmodules/SelectParentHelper.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "SelectParentHelper"
];

const {utils: Cu} = Components;
const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});

// Maximum number of rows to display in the select dropdown.
const MAX_ROWS = 20;

// Minimum elements required to show select search
const SEARCH_MINIMUM_ELEMENTS = 40;

// Make sure to clear these objects when the popup closes to avoid leaking.
var currentBrowser = null;
var currentMenulist = null;
var selectRect = null;

var currentZoom = 1;
var closedWithEnter = false;
var customStylingEnabled = Services.prefs.getBoolPref("dom.forms.select.customstyling");
var usedSelectBackgroundColor;

this.SelectParentHelper = {
  populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor,
           uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor, selectColor,
           selectTextShadow) {
    // Clear the current contents of the popup
    menulist.menupopup.textContent = "";
    let stylesheet = menulist.querySelector("#ContentSelectDropdownStylesheet");
    if (stylesheet) {
      stylesheet.remove();
    }

    let doc = menulist.ownerDocument;
    let sheet;
    if (customStylingEnabled) {
      stylesheet = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
      stylesheet.setAttribute("id", "ContentSelectDropdownStylesheet");
      stylesheet.hidden = true;
      stylesheet = menulist.appendChild(stylesheet);
      sheet = stylesheet.sheet;
    }

    let ruleBody = "";

    // Some webpages set the <select> backgroundColor to transparent,
    // but they don't intend to change the popup to transparent.
    if (customStylingEnabled &&
        selectBackgroundColor != uaSelectBackgroundColor &&
        selectBackgroundColor != "rgba(0, 0, 0, 0)") {
      ruleBody = `background-image: linear-gradient(${selectBackgroundColor}, ${selectBackgroundColor});`;
      usedSelectBackgroundColor = selectBackgroundColor;
    } else {
      usedSelectBackgroundColor = uaSelectBackgroundColor;
    }

    if (customStylingEnabled &&
        selectColor != uaSelectColor &&
        selectColor != selectBackgroundColor &&
        (selectBackgroundColor != "rgba(0, 0, 0, 0)" ||
         selectColor != uaSelectBackgroundColor)) {
      ruleBody += `color: ${selectColor};`;
    }

    if (customStylingEnabled &&
        selectTextShadow != "none") {
      ruleBody += `text-shadow: ${selectTextShadow};`;
    }

    if (ruleBody) {
      sheet.insertRule(`#ContentSelectDropdown > menupopup {
        ${ruleBody}
      }`, 0);
      menulist.menupopup.setAttribute("customoptionstyling", "true");
    } else {
      menulist.menupopup.removeAttribute("customoptionstyling");
    }

    currentZoom = zoom;
    currentMenulist = menulist;
    populateChildren(menulist, items, selectedIndex, zoom,
                     uaBackgroundColor, uaColor, sheet);
  },

  open(browser, menulist, rect, isOpenedViaTouch) {
    menulist.hidden = false;
    currentBrowser = browser;
    closedWithEnter = false;
    selectRect = rect;
    this._registerListeners(browser, menulist.menupopup);

    let win = browser.ownerGlobal;

    // Set the maximum height to show exactly MAX_ROWS items.
    let menupopup = menulist.menupopup;
    let firstItem = menupopup.firstChild;
    while (firstItem && firstItem.hidden) {
      firstItem = firstItem.nextSibling;
    }

    if (firstItem) {
      let itemHeight = firstItem.getBoundingClientRect().height;

      // Include the padding and border on the popup.
      let cs = win.getComputedStyle(menupopup);
      let bpHeight = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth) +
                     parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
      menupopup.style.maxHeight = (itemHeight * MAX_ROWS + bpHeight) + "px";
    }

    menupopup.classList.toggle("isOpenedViaTouch", isOpenedViaTouch);

    if (browser.getAttribute("selectmenuconstrained") != "false") {
      let constraintRect = browser.getBoundingClientRect();
      constraintRect = new win.DOMRect(constraintRect.left + win.mozInnerScreenX,
                                       constraintRect.top + win.mozInnerScreenY,
                                       constraintRect.width, constraintRect.height);
      menupopup.setConstraintRect(constraintRect);
    } else {
      menupopup.setConstraintRect(new win.DOMRect(0, 0, 0, 0));
    }
    menupopup.openPopupAtScreenRect(AppConstants.platform == "macosx" ? "selection" : "after_start", rect.left, rect.top, rect.width, rect.height, false, false);
  },

  hide(menulist, browser) {
    if (currentBrowser == browser) {
      menulist.menupopup.hidePopup();
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "mouseup":
        function inRect(rect, x, y) {
          return x >= rect.left && x <= rect.left + rect.width && y >= rect.top && y <= rect.top + rect.height;
        }

        let x = event.screenX, y = event.screenY;
        let onAnchor = !inRect(currentMenulist.menupopup.getOuterScreenRect(), x, y) &&
                        inRect(selectRect, x, y) && currentMenulist.menupopup.state == "open";
        currentBrowser.messageManager.sendAsyncMessage("Forms:MouseUp", { onAnchor });
        break;

      case "mouseover":
        currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOver", {});
        break;

      case "mouseout":
        currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOut", {});
        break;

      case "keydown":
        if (event.keyCode == event.DOM_VK_RETURN) {
          closedWithEnter = true;
        }
        break;

      case "command":
        if (event.target.hasAttribute("value")) {
          currentBrowser.messageManager.sendAsyncMessage("Forms:SelectDropDownItem", {
            value: event.target.value,
            closedWithEnter
          });
        }
        break;

      case "fullscreen":
        if (currentMenulist) {
          currentMenulist.menupopup.hidePopup();
        }
        break;

      case "popuphidden":
        currentBrowser.messageManager.sendAsyncMessage("Forms:DismissedDropDown", {});
        let popup = event.target;
        this._unregisterListeners(currentBrowser, popup);
        popup.parentNode.hidden = true;
        currentBrowser = null;
        currentMenulist = null;
        selectRect = null;
        currentZoom = 1;
        break;
    }
  },

  receiveMessage(msg) {
    if (!currentBrowser) {
      return;
    }

    if (msg.name == "Forms:UpdateDropDown") {
      // Sanity check - we'd better know what the currently
      // opened menulist is, and what browser it belongs to...
      if (!currentMenulist) {
        return;
      }

      let scrollBox = currentMenulist.menupopup.scrollBox;
      let scrollTop = scrollBox.scrollTop;

      let options = msg.data.options;
      let selectedIndex = msg.data.selectedIndex;
      let uaBackgroundColor = msg.data.uaBackgroundColor;
      let uaColor = msg.data.uaColor;
      let uaSelectBackgroundColor = msg.data.uaSelectBackgroundColor;
      let uaSelectColor = msg.data.uaSelectColor;
      let selectBackgroundColor = msg.data.selectBackgroundColor;
      let selectColor = msg.data.selectColor;
      let selectTextShadow = msg.data.selectTextShadow;
      this.populate(currentMenulist, options, selectedIndex,
                    currentZoom, uaBackgroundColor, uaColor,
                    uaSelectBackgroundColor, uaSelectColor,
                    selectBackgroundColor, selectColor, selectTextShadow);

      // Restore scroll position to what it was prior to the update.
      scrollBox.scrollTop = scrollTop;
    } else if (msg.name == "Forms:BlurDropDown-Ping") {
      currentBrowser.messageManager.sendAsyncMessage("Forms:BlurDropDown-Pong", {});
    }
  },

  _registerListeners(browser, popup) {
    popup.addEventListener("command", this);
    popup.addEventListener("popuphidden", this);
    popup.addEventListener("mouseover", this);
    popup.addEventListener("mouseout", this);
    browser.ownerGlobal.addEventListener("mouseup", this, true);
    browser.ownerGlobal.addEventListener("keydown", this, true);
    browser.ownerGlobal.addEventListener("fullscreen", this, true);
    browser.messageManager.addMessageListener("Forms:UpdateDropDown", this);
    browser.messageManager.addMessageListener("Forms:BlurDropDown-Ping", this);
  },

  _unregisterListeners(browser, popup) {
    popup.removeEventListener("command", this);
    popup.removeEventListener("popuphidden", this);
    popup.removeEventListener("mouseover", this);
    popup.removeEventListener("mouseout", this);
    browser.ownerGlobal.removeEventListener("mouseup", this, true);
    browser.ownerGlobal.removeEventListener("keydown", this, true);
    browser.ownerGlobal.removeEventListener("fullscreen", this, true);
    browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
    browser.messageManager.removeMessageListener("Forms:BlurDropDown-Ping", this);
  },

};

function populateChildren(menulist, options, selectedIndex, zoom,
                          uaBackgroundColor, uaColor, sheet,
                          parentElement = null, isGroupDisabled = false,
                          adjustedTextSize = -1, addSearch = true, nthChildIndex = 1) {
  let element = menulist.menupopup;
  let win = element.ownerGlobal;

  // -1 just means we haven't calculated it yet. When we recurse through this function
  // we will pass in adjustedTextSize to save on recalculations.
  if (adjustedTextSize == -1) {
    // Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
    // the popup's text size is matched with the content's. We can't just apply a CSS transform
    // here as the popup's preferred size is calculated pre-transform.
    let textSize = win.getComputedStyle(element).getPropertyValue("font-size");
    adjustedTextSize = (zoom * parseFloat(textSize, 10)) + "px";
  }

  for (let option of options) {
    let isOptGroup = (option.tagName == "OPTGROUP");
    let item = element.ownerDocument.createElement(isOptGroup ? "menucaption" : "menuitem");

    item.setAttribute("label", option.textContent);
    item.style.direction = option.textDirection;
    item.style.fontSize = adjustedTextSize;
    item.hidden = option.display == "none" || (parentElement && parentElement.hidden);
    // Keep track of which options are hidden by page content, so we can avoid showing
    // them on search input
    item.hiddenByContent = item.hidden;
    item.setAttribute("tooltiptext", option.tooltip);

    let ruleBody = "";
    if (customStylingEnabled &&
        option.backgroundColor &&
        option.backgroundColor != "rgba(0, 0, 0, 0)" &&
        option.backgroundColor != usedSelectBackgroundColor) {
      ruleBody = `background-color: ${option.backgroundColor};`;
    }

    if (customStylingEnabled &&
        option.color &&
        option.color != uaColor) {
      ruleBody += `color: ${option.color};`;
    }

    if (customStylingEnabled &&
        option.textShadow) {
      ruleBody += `text-shadow: ${option.textShadow};`;
    }

    if (ruleBody) {
      sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
        ${ruleBody}
      }`, 0);

      if (option.textShadow) {
        // Need to explicitly disable the possibly inherited
        // text-shadow rule when _moz-menuactive=true since
        // _moz-menuactive=true disables custom option styling.
        sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex})[_moz-menuactive="true"] {
          text-shadow: none;
        }`, 0);
      }

      item.setAttribute("customoptionstyling", "true");
    } else {
      item.removeAttribute("customoptionstyling");
    }

    element.appendChild(item);
    nthChildIndex++;

    // A disabled optgroup disables all of its child options.
    let isDisabled = isGroupDisabled || option.disabled;
    if (isDisabled) {
      item.setAttribute("disabled", "true");
    }

    if (isOptGroup) {
      nthChildIndex =
        populateChildren(menulist, option.children, selectedIndex, zoom,
                         uaBackgroundColor, uaColor, sheet,
                         item, isDisabled, adjustedTextSize, false, nthChildIndex);
    } else {
      if (option.index == selectedIndex) {
        // We expect the parent element of the popup to be a <xul:menulist> that
        // has the popuponly attribute set to "true". This is necessary in order
        // for a <xul:menupopup> to act like a proper <html:select> dropdown, as
        // the <xul:menulist> does things like remember state and set the
        // _moz-menuactive attribute on the selected <xul:menuitem>.
        menulist.selectedItem = item;

        // It's hack time. In the event that we've re-populated the menulist due
        // to a mutation in the <select> in content, that means that the -moz_activemenu
        // may have been removed from the selected item. Since that's normally only
        // set for the initially selected on popupshowing for the menulist, and we
        // don't want to close and re-open the popup, we manually set it here.
        menulist.menuBoxObject.activeChild = item;
      }

      item.setAttribute("value", option.index);

      if (parentElement) {
        item.classList.add("contentSelectDropdown-ingroup")
      }
    }
  }

  // Check if search pref is enabled, if this is the first time iterating through
  // the dropdown, and if the list is long enough for a search element to be added.
  if (Services.prefs.getBoolPref("dom.forms.selectSearch") && addSearch
      && element.childElementCount > SEARCH_MINIMUM_ELEMENTS) {

    // Add a search text field as the first element of the dropdown
    let searchbox = element.ownerDocument.createElement("textbox");
    searchbox.setAttribute("type", "search");
    searchbox.addEventListener("input", onSearchInput);
    searchbox.addEventListener("focus", onSearchFocus);
    searchbox.addEventListener("blur", onSearchBlur);
    searchbox.addEventListener("command", onSearchInput);

    // Handle special keys for exiting search
    searchbox.addEventListener("keydown", function(event) {
      if (event.defaultPrevented) {
        return;
      }
      switch (event.key) {
        case "Escape":
          searchbox.parentElement.hidePopup();
          break;
        case "ArrowDown":
        case "Enter":
        case "Tab":
          searchbox.blur();
          if (searchbox.nextSibling.localName == "menuitem" &&
              !searchbox.nextSibling.hidden) {
            menulist.menuBoxObject.activeChild = searchbox.nextSibling;
          } else {
            var currentOption = searchbox.nextSibling;
            while (currentOption && (currentOption.localName != "menuitem" ||
                  currentOption.hidden)) {
              currentOption = currentOption.nextSibling;
            }
            if (currentOption) {
              menulist.menuBoxObject.activeChild = currentOption;
            } else {
              searchbox.focus();
            }
          }
          break;
        default:
          return;
      }
      event.preventDefault();
    }, true);

    element.insertBefore(searchbox, element.childNodes[0]);
  }

  return nthChildIndex;
}

function onSearchInput() {
  let searchObj = this;

  // Get input from search field, set to all lower case for comparison
  let input = searchObj.value.toLowerCase();
  // Get all items in dropdown (could be options or optgroups)
  let menupopup = searchObj.parentElement;
  let menuItems = menupopup.querySelectorAll("menuitem, menucaption");

  // Flag used to detect any group headers with no visible options.
  // These group headers should be hidden.
  let allHidden = true;
  // Keep a reference to the previous group header (menucaption) to go back
  // and set to hidden if all options within are hidden.
  let prevCaption = null;

  for (let currentItem of menuItems) {
    // Make sure we don't show any options that were hidden by page content
    if (!currentItem.hiddenByContent) {
      // Get label and tooltip (title) from option and change to
      // lower case for comparison
      let itemLabel = currentItem.getAttribute("label").toLowerCase();
      let itemTooltip = currentItem.getAttribute("title").toLowerCase();

      // If search input is empty, all options should be shown
      if (!input) {
        currentItem.hidden = false;
      } else if (currentItem.localName == "menucaption") {
        if (prevCaption != null) {
          prevCaption.hidden = allHidden;
        }
        prevCaption = currentItem;
        allHidden = true;
      } else {
        if (!currentItem.classList.contains("contentSelectDropdown-ingroup") &&
            currentItem.previousSibling.classList.contains("contentSelectDropdown-ingroup")) {
          if (prevCaption != null) {
            prevCaption.hidden = allHidden;
          }
          prevCaption = null;
          allHidden = true;
        }
        if (itemLabel.includes(input) || itemTooltip.includes(input)) {
          currentItem.hidden = false;
          allHidden = false;
        } else {
          currentItem.hidden = true;
        }
      }
      if (prevCaption != null) {
        prevCaption.hidden = allHidden;
      }
    }
  }
}

function onSearchFocus() {
  let searchObj = this;
  let menupopup = searchObj.parentElement;
  menupopup.parentElement.menuBoxObject.activeChild = null;
  menupopup.setAttribute("ignorekeys", "true");
  currentBrowser.messageManager.sendAsyncMessage("Forms:SearchFocused", {});
}

function onSearchBlur() {
  let searchObj = this;
  let menupopup = searchObj.parentElement;
  menupopup.setAttribute("ignorekeys", "false");
}
PK
!<11Xwwmodules/ServiceRequest.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 module consolidates various code and data update requests, so flags
  * can be set, Telemetry collected, etc. in a central place.
  */

Cu.import("resource://gre/modules/Log.jsm");
Cu.importGlobalProperties(["XMLHttpRequest"]);

this.EXPORTED_SYMBOLS = [ "ServiceRequest" ];

const logger = Log.repository.getLogger("ServiceRequest");
logger.level = Log.Level.Debug;
logger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));

/**
  * ServiceRequest is intended to be a drop-in replacement for current users
  * of XMLHttpRequest.
  *
  * @param {Object} options - Options for underlying XHR, e.g. { mozAnon: bool }
  */
class ServiceRequest extends XMLHttpRequest {
  constructor(options) {
    super(options);
  }
  /**
    * Opens an XMLHttpRequest, and sets the NSS "beConservative" flag.
    * Requests are always async.
    *
    * @param {String} method - HTTP method to use, e.g. "GET".
    * @param {String} url - URL to open.
    * @param {Object} options - Additional options (reserved for future use).
    */
  open(method, url, options) {
    super.open(method, url, true);

    // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
    if (super.channel instanceof Ci.nsIHttpChannelInternal) {
      super.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
    }
  }
}
PK
!<Cmodules/Services.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Services"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;

Components.utils.import("resource://gre/modules/AppConstants.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

this.Services = {};

XPCOMUtils.defineLazyGetter(Services, "prefs", function() {
  return Cc["@mozilla.org/preferences-service;1"]
           .getService(Ci.nsIPrefService)
           .QueryInterface(Ci.nsIPrefBranch);
});

XPCOMUtils.defineLazyGetter(Services, "appinfo", function() {
  let appinfo = Cc["@mozilla.org/xre/app-info;1"]
                  .getService(Ci.nsIXULRuntime);
  try {
    appinfo.QueryInterface(Ci.nsIXULAppInfo);
  } catch (ex) {
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!(ex instanceof Components.Exception) || ex.result != Cr.NS_NOINTERFACE) {
      throw ex;
    }
  }
  return appinfo;
});

XPCOMUtils.defineLazyGetter(Services, "dirsvc", function() {
  return Cc["@mozilla.org/file/directory_service;1"]
           .getService(Ci.nsIDirectoryService)
           .QueryInterface(Ci.nsIProperties);
});

if (AppConstants.MOZ_CRASHREPORTER) {
  XPCOMUtils.defineLazyGetter(Services, "crashmanager", () => {
    let ns = {};
    Components.utils.import("resource://gre/modules/CrashManager.jsm", ns);

    return ns.CrashManager.Singleton;
  });
}

XPCOMUtils.defineLazyGetter(Services, "mm", () => {
  return Cc["@mozilla.org/globalmessagemanager;1"]
           .getService(Ci.nsIMessageBroadcaster)
           .QueryInterface(Ci.nsIFrameScriptLoader);
});

XPCOMUtils.defineLazyGetter(Services, "ppmm", () => {
  return Cc["@mozilla.org/parentprocessmessagemanager;1"]
           .getService(Ci.nsIMessageBroadcaster)
           .QueryInterface(Ci.nsIProcessScriptLoader);
});

XPCOMUtils.defineLazyGetter(Services, "io", () => {
  return Cc["@mozilla.org/network/io-service;1"]
           .getService(Ci.nsIIOService2)
           .QueryInterface(Ci.nsISpeculativeConnect);
});

var initTable = [
  ["androidBridge", "@mozilla.org/android/bridge;1", "nsIAndroidBridge",
   AppConstants.platform == "android"],
  ["appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
  ["cache", "@mozilla.org/network/cache-service;1", "nsICacheService"],
  ["cache2", "@mozilla.org/netwerk/cache-storage-service;1", "nsICacheStorageService"],
  ["cpmm", "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"],
  ["console", "@mozilla.org/consoleservice;1", "nsIConsoleService"],
  ["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
  ["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],
  ["downloads", "@mozilla.org/download-manager;1", "nsIDownloadManager"],
  ["droppedLinkHandler", "@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
  ["els", "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"],
  ["eTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
  ["intl", "@mozilla.org/mozintl;1", "mozIMozIntl"],
  ["locale", "@mozilla.org/intl/localeservice;1", "mozILocaleService"],
  ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
  ["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
  ["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
  ["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
  ["profiler", "@mozilla.org/tools/profiler;1", "nsIProfiler",
   AppConstants.MOZ_GECKO_PROFILER],
  ["scriptloader", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"],
  ["scriptSecurityManager", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"],
  ["search", "@mozilla.org/browser/search-service;1", "nsIBrowserSearchService",
   AppConstants.MOZ_TOOLKIT_SEARCH],
  ["storage", "@mozilla.org/storage/service;1", "mozIStorageService"],
  ["domStorageManager", "@mozilla.org/dom/localStorage-manager;1", "nsIDOMStorageManager"],
  ["strings", "@mozilla.org/intl/stringbundle;1", "nsIStringBundleService"],
  ["telemetry", "@mozilla.org/base/telemetry;1", "nsITelemetry"],
  ["tm", "@mozilla.org/thread-manager;1", "nsIThreadManager"],
  ["urlFormatter", "@mozilla.org/toolkit/URLFormatterService;1", "nsIURLFormatter"],
  ["vc", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"],
  ["wm", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"],
  ["ww", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"],
  ["startup", "@mozilla.org/toolkit/app-startup;1", "nsIAppStartup"],
  ["sysinfo", "@mozilla.org/system-info;1", "nsIPropertyBag2"],
  ["clipboard", "@mozilla.org/widget/clipboard;1", "nsIClipboard"],
  ["DOMRequest", "@mozilla.org/dom/dom-request-service;1", "nsIDOMRequestService"],
  ["focus", "@mozilla.org/focus-manager;1", "nsIFocusManager"],
  ["uriFixup", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"],
  ["blocklist", "@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
  ["netUtils", "@mozilla.org/network/util;1", "nsINetUtil"],
  ["loadContextInfo", "@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
  ["qms", "@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
];

for (let [name, contract, intf, enabled = true] of initTable) {
  if (enabled) {
    XPCOMUtils.defineLazyServiceGetter(Services, name, contract, intf);
  }
}


initTable = undefined;
PK
!<D~vmodules/SharedPromptUtils.jsmthis.EXPORTED_SYMBOLS = [ "PromptUtils", "EnableDelayHelper" ];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");

this.PromptUtils = {
    // Fire a dialog open/close event. Used by tabbrowser to focus the
    // tab which is triggering a prompt.
    // For remote dialogs, we pass in a different DOM window and a separate
    // target. If the caller doesn't pass in the target, then we'll simply use
    // the passed-in DOM window.
    // The detail may contain information about the principal on which the
    // prompt is triggered, as well as whether or not this is a tabprompt
    // (ie tabmodal alert/prompt/confirm and friends)
    fireDialogEvent(domWin, eventName, maybeTarget, detail) {
        let target = maybeTarget || domWin;
        let eventOptions = {cancelable: true, bubbles: true};
        if (detail) {
          eventOptions.detail = detail;
        }
        let event = new domWin.CustomEvent(eventName, eventOptions);
        let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
        winUtils.dispatchEventToChromeOnly(target, event);
    },

    objectToPropBag(obj) {
        let bag = Cc["@mozilla.org/hash-property-bag;1"].
                  createInstance(Ci.nsIWritablePropertyBag2);
        bag.QueryInterface(Ci.nsIWritablePropertyBag);

        for (let propName in obj)
            bag.setProperty(propName, obj[propName]);

        return bag;
    },

    propBagToObject(propBag, obj) {
        // Here we iterate over the object's original properties, not the bag
        // (ie, the prompt can't return more/different properties than were
        // passed in). This just helps ensure that the caller provides default
        // values, lest the prompt forget to set them.
        for (let propName in obj)
            obj[propName] = propBag.getProperty(propName);
    },
};

/**
 * This helper handles the enabling/disabling of dialogs that might
 * be subject to fast-clicking attacks. It handles the initial delayed
 * enabling of the dialog, as well as disabling it on blur and reapplying
 * the delay when the dialog regains focus.
 *
 * @param enableDialog   A custom function to be called when the dialog
 *                       is to be enabled.
 * @param diableDialog   A custom function to be called when the dialog
 *                       is to be disabled.
 * @param focusTarget    The window used to watch focus/blur events.
 */
this.EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) {
    this.enableDialog = makeSafe(enableDialog);
    this.disableDialog = makeSafe(disableDialog);
    this.focusTarget = focusTarget;

    this.disableDialog();

    this.focusTarget.addEventListener("blur", this);
    this.focusTarget.addEventListener("focus", this);
    this.focusTarget.document.addEventListener("unload", this);

    this.startOnFocusDelay();
};

this.EnableDelayHelper.prototype = {
    get delayTime() {
        return Services.prefs.getIntPref("security.dialog_enable_delay");
    },

    handleEvent(event) {
        if (event.target != this.focusTarget &&
            event.target != this.focusTarget.document)
            return;

        switch (event.type) {
            case "blur":
                this.onBlur();
                break;

            case "focus":
                this.onFocus();
                break;

            case "unload":
                this.onUnload();
                break;
        }
    },

    onBlur() {
        this.disableDialog();
        // If we blur while waiting to enable the buttons, just cancel the
        // timer to ensure the delay doesn't fire while not focused.
        if (this._focusTimer) {
            this._focusTimer.cancel();
            this._focusTimer = null;
        }
    },

    onFocus() {
        this.startOnFocusDelay();
    },

    onUnload() {
        this.focusTarget.removeEventListener("blur", this);
        this.focusTarget.removeEventListener("focus", this);
        this.focusTarget.document.removeEventListener("unload", this);

        if (this._focusTimer) {
            this._focusTimer.cancel();
            this._focusTimer = null;
        }

        this.focusTarget = this.enableDialog = this.disableDialog = null;
    },

    startOnFocusDelay() {
        if (this._focusTimer)
            return;

        this._focusTimer = Cc["@mozilla.org/timer;1"]
                             .createInstance(Ci.nsITimer);
        this._focusTimer.initWithCallback(
            () => { this.onFocusTimeout(); },
            this.delayTime,
            Ci.nsITimer.TYPE_ONE_SHOT
        );
    },

    onFocusTimeout() {
        this._focusTimer = null;
        this.enableDialog();
    },
};

function makeSafe(fn) {
    return function() {
        // The dialog could be gone by now (if the user closed it),
        // which makes it likely that the given fn might throw.
        try {
            fn();
        } catch (e) { }
    };
}
PK
!<	Вmodules/ShimWaiver.jsm// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

this.EXPORTED_SYMBOLS = ["ShimWaiver"];

this.ShimWaiver = {
  getProperty(obj, prop) {
    let rv = obj[prop];
    if (rv instanceof Function) {
      rv = rv.bind(obj);
    }
    return rv;
  }
};
PK
!<>++modules/ShortcutUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ShortcutUtils"];

const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "PlatformKeys", function() {
  return Services.strings.createBundle(
    "chrome://global-platform/locale/platformKeys.properties");
});

XPCOMUtils.defineLazyGetter(this, "Keys", function() {
  return Services.strings.createBundle(
    "chrome://global/locale/keys.properties");
});

var ShortcutUtils = {
  /**
    * Prettifies the modifier keys for an element.
    *
    * @param Node aElemKey
    *        The key element to get the modifiers from.
    * @param boolean aNoCloverLeaf
    *        Pass true to use a descriptive string instead of the cloverleaf symbol. (OS X only)
    * @return string
    *         A prettified and properly separated modifier keys string.
    */
  prettifyShortcut(aElemKey, aNoCloverLeaf) {
    let elemString = "";
    let elemMod = aElemKey.getAttribute("modifiers");
    let haveCloverLeaf = false;

    if (elemMod.match("accel")) {
      if (Services.appinfo.OS == "Darwin") {
        // XXX bug 779642 Use "Cmd-" literal vs. cloverleaf meta-key until
        // Orion adds variable height lines.
        if (aNoCloverLeaf) {
          elemString += "Cmd-";
        } else {
          haveCloverLeaf = true;
        }
      } else {
        elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
          PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      }
    }
    if (elemMod.match("access")) {
      if (Services.appinfo.OS == "Darwin") {
        elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
          PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      } else {
        elemString += PlatformKeys.GetStringFromName("VK_ALT") +
          PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      }
    }
    if (elemMod.match("os")) {
      elemString += PlatformKeys.GetStringFromName("VK_WIN") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("shift")) {
      elemString += PlatformKeys.GetStringFromName("VK_SHIFT") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("alt")) {
      elemString += PlatformKeys.GetStringFromName("VK_ALT") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("ctrl") || elemMod.match("control")) {
      elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("meta")) {
      elemString += PlatformKeys.GetStringFromName("VK_META") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }

    if (haveCloverLeaf) {
      elemString += PlatformKeys.GetStringFromName("VK_META") +
        PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }

    let key;
    let keyCode = aElemKey.getAttribute("keycode");
    if (keyCode) {
      try {
        // Some keys might not exist in the locale file, which will throw:
        key = Keys.GetStringFromName(keyCode.toUpperCase());
      } catch (ex) {
        Cu.reportError("Error finding " + keyCode + ": " + ex);
        key = keyCode.replace(/^VK_/, "");
      }
    } else {
      key = aElemKey.getAttribute("key");
      key = key.toUpperCase();
    }
    return elemString + key;
  },

  findShortcut(aElemCommand) {
    let document = aElemCommand.ownerDocument;
    return document.querySelector("key[command=\"" + aElemCommand.getAttribute("id") + "\"]");
  }
};

Object.freeze(ShortcutUtils);

this.ShortcutUtils = ShortcutUtils;
PK
!<,Bn77"modules/SimpleServiceDiscovery.jsm// -*- 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 = ["SimpleServiceDiscovery"];

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/Timer.jsm");

var log = Cu.reportError;

XPCOMUtils.defineLazyGetter(this, "converter", function() {
  let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
  conv.charset = "utf8";
  return conv;
});

// Spec information:
// https://tools.ietf.org/html/draft-cai-ssdp-v1-03
// http://www.dial-multiscreen.org/dial-protocol-specification
const SSDP_PORT = 1900;
const SSDP_ADDRESS = "239.255.255.250";

const SSDP_DISCOVER_PACKET =
  "M-SEARCH * HTTP/1.1\r\n" +
  "HOST: " + SSDP_ADDRESS + ":" + SSDP_PORT + "\r\n" +
  "MAN: \"ssdp:discover\"\r\n" +
  "MX: 2\r\n" +
  "ST: %SEARCH_TARGET%\r\n\r\n";

const SSDP_DISCOVER_ATTEMPTS = 3;
const SSDP_DISCOVER_DELAY = 500;
const SSDP_DISCOVER_TIMEOUT_MULTIPLIER = 2;
const SSDP_TRANSMISSION_INTERVAL = 1000;

const EVENT_SERVICE_FOUND = "ssdp-service-found";
const EVENT_SERVICE_LOST = "ssdp-service-lost";

/*
 * SimpleServiceDiscovery manages any discovered SSDP services. It uses a UDP
 * broadcast to locate available services on the local network.
 */
var SimpleServiceDiscovery = {
  get EVENT_SERVICE_FOUND() { return EVENT_SERVICE_FOUND; },
  get EVENT_SERVICE_LOST() { return EVENT_SERVICE_LOST; },

  _devices: new Map(),
  _services: new Map(),
  _searchSocket: null,
  _searchInterval: 0,
  _searchTimestamp: 0,
  _searchTimeout: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
  _searchRepeat: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
  _discoveryMethods: [],

  _forceTrailingSlash(aURL) {
    // Cleanup the URL to make it consistent across devices
    try {
      aURL = Services.io.newURI(aURL).spec;
    } catch (e) {}
    return aURL;
  },

  // nsIUDPSocketListener implementation
  onPacketReceived(aSocket, aMessage) {
    // Listen for responses from specific devices. There could be more than one
    // available.
    let response = aMessage.data.split("\n");
    let service = {};
    response.forEach(function(row) {
      let name = row.toUpperCase();
      if (name.startsWith("LOCATION")) {
        service.location = row.substr(10).trim();
      } else if (name.startsWith("ST")) {
        service.target = row.substr(4).trim();
      }
    });

    if (service.location && service.target) {
      service.location = this._forceTrailingSlash(service.location);

      // When we find a valid response, package up the service information
      // and pass it on.
      try {
        this._processService(service);
      } catch (e) {}
    }
  },

  onStopListening(aSocket, aStatus) {
    // This is fired when the socket is closed expectedly or unexpectedly.
    // nsITimer.cancel() is a no-op if the timer is not active.
    this._searchTimeout.cancel();
    this._searchSocket = null;
  },

  // Start a search. Make it continuous by passing an interval (in milliseconds).
  // This will stop a current search loop because the timer resets itself.
  // Returns the existing search interval.
  search: function search(aInterval) {
    let existingSearchInterval = this._searchInterval;
    if (aInterval > 0) {
      this._searchInterval = aInterval || 0;
      this._searchRepeat.initWithCallback(this._search.bind(this), this._searchInterval, Ci.nsITimer.TYPE_REPEATING_SLACK);
    }
    this._search();
    return existingSearchInterval;
  },

  // Stop the current continuous search
  stopSearch: function stopSearch() {
    this._searchRepeat.cancel();
  },

  _usingLAN() {
    let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
    return (network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI ||
            network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
            network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN);
  },

  _search: function _search() {
    // If a search is already active, shut it down.
    this._searchShutdown();

    // We only search if on local network
    if (!this._usingLAN()) {
      return;
    }

    // Update the timestamp so we can use it to clean out stale services the
    // next time we search.
    this._searchTimestamp = Date.now();

    // Look for any fixed IP devices. Some routers might be configured to block
    // UDP broadcasts, so this is a way to skip discovery.
    this._searchFixedDevices();

    // Look for any devices via registered external discovery mechanism.
    this._startExternalDiscovery();

    // Perform a UDP broadcast to search for SSDP devices
    let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(Ci.nsIUDPSocket);
    try {
      socket.init(SSDP_PORT, false, Services.scriptSecurityManager.getSystemPrincipal());
      socket.joinMulticast(SSDP_ADDRESS);
      socket.asyncListen(this);
    } catch (e) {
      // We were unable to create the broadcast socket. Just return, but don't
      // kill the interval timer. This might work next time.
      log("failed to start socket: " + e);
      return;
    }

    // Make the timeout SSDP_DISCOVER_TIMEOUT_MULTIPLIER times as long as the time needed to send out the discovery packets.
    const SSDP_DISCOVER_TIMEOUT = this._devices.size * SSDP_DISCOVER_ATTEMPTS * SSDP_TRANSMISSION_INTERVAL * SSDP_DISCOVER_TIMEOUT_MULTIPLIER;
    this._searchSocket = socket;
    this._searchTimeout.initWithCallback(this._searchShutdown.bind(this), SSDP_DISCOVER_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);

    let data = SSDP_DISCOVER_PACKET;

    // Send discovery packets out at 1 per SSDP_TRANSMISSION_INTERVAL and send each SSDP_DISCOVER_ATTEMPTS times
    // to allow for packet loss on noisy networks.
    let timeout = SSDP_DISCOVER_DELAY;
    for (let attempts = 0; attempts < SSDP_DISCOVER_ATTEMPTS; attempts++) {
      for (let [/* key */, device] of this._devices) {
        let target = device.target;
        setTimeout(function() {
          let msgData = data.replace("%SEARCH_TARGET%", target);
          try {
            let msgRaw = converter.convertToByteArray(msgData);
            socket.send(SSDP_ADDRESS, SSDP_PORT, msgRaw, msgRaw.length);
          } catch (e) {
            log("failed to convert to byte array: " + e);
          }
        }, timeout);
        timeout += SSDP_TRANSMISSION_INTERVAL;
      }
    }
  },

  _searchFixedDevices: function _searchFixedDevices() {
    let fixedDevices = Services.prefs.getCharPref("browser.casting.fixedDevices", "");

    if (!fixedDevices) {
      return;
    }

    fixedDevices = JSON.parse(fixedDevices);
    for (let fixedDevice of fixedDevices) {
      // Verify we have the right data
      if (!("location" in fixedDevice) || !("target" in fixedDevice)) {
        continue;
      }

      fixedDevice.location = this._forceTrailingSlash(fixedDevice.location);

      let service = {
        location: fixedDevice.location,
        target: fixedDevice.target
      };

      // We don't assume the fixed target is ready. We still need to ping it.
      try {
        this._processService(service);
      } catch (e) {}
    }
  },

  // Called when the search timeout is hit. We use it to cleanup the socket and
  // perform some post-processing on the services list.
  _searchShutdown: function _searchShutdown() {
    if (this._searchSocket) {
      // This will call onStopListening.
      this._searchSocket.close();

      // Clean out any stale services
      for (let [/* key */, service] of this._services) {
        if (service.lastPing != this._searchTimestamp) {
          this.removeService(service.uuid);
        }
      }
    }

    this._stopExternalDiscovery();
  },

  getSupportedExtensions() {
    let extensions = [];
    this.services.forEach(function(service) {
        extensions = extensions.concat(service.extensions);
      }, this);
    return extensions.filter(function(extension, pos) {
      return extensions.indexOf(extension) == pos;
    });
  },

  getSupportedMimeTypes() {
    let types = [];
    this.services.forEach(function(service) {
        types = types.concat(service.types);
      }, this);
    return types.filter(function(type, pos) {
      return types.indexOf(type) == pos;
    });
  },

  registerDevice: function registerDevice(aDevice) {
    // We must have "id", "target" and "factory" defined
    if (!("id" in aDevice) || !("target" in aDevice) || !("factory" in aDevice)) {
      // Fatal for registration
      throw "Registration requires an id, a target and a location";
    }

    // Only add if we don't already know about this device
    if (!this._devices.has(aDevice.id)) {
      this._devices.set(aDevice.id, aDevice);
    } else {
      log("device was already registered: " + aDevice.id);
    }
  },

  unregisterDevice: function unregisterDevice(aDevice) {
    // We must have "id", "target" and "factory" defined
    if (!("id" in aDevice) || !("target" in aDevice) || !("factory" in aDevice)) {
      return;
    }

    // Only remove if we know about this device
    if (this._devices.has(aDevice.id)) {
      this._devices.delete(aDevice.id);
    } else {
      log("device was not registered: " + aDevice.id);
    }
  },

  findAppForService: function findAppForService(aService) {
    if (!aService || !aService.deviceID) {
      return null;
    }

    // Find the registration for the device
    if (this._devices.has(aService.deviceID)) {
      return this._devices.get(aService.deviceID).factory(aService);
    }
    return null;
  },

  findServiceForID: function findServiceForID(aUUID) {
    if (this._services.has(aUUID)) {
      return this._services.get(aUUID);
    }
    return null;
  },

  // Returns an array copy of the active services
  get services() {
    let array = [];
    for (let [/* key */, service] of this._services) {
      let target = this._devices.get(service.deviceID);
      service.extensions = target.extensions;
      service.types = target.types;
      array.push(service);
    }
    return array;
  },

  // Returns false if the service does not match the device's filters
  _filterService: function _filterService(aService) {
    // Loop over all the devices, looking for one that matches the service
    for (let [/* key */, device] of this._devices) {
      // First level of match is on the target itself
      if (device.target != aService.target) {
        continue;
      }

      // If we have no filter, everything passes
      if (!("filters" in device)) {
        aService.deviceID = device.id;
        return true;
      }

      // If all the filters pass, we have a match
      let failed = false;
      let filters = device.filters;
      for (let filter in filters) {
        if (filter in aService && aService[filter] != filters[filter]) {
          failed = true;
        }
      }

      // We found a match, so link the service to the device
      if (!failed) {
        aService.deviceID = device.id;
        return true;
      }
    }

    // We didn't find any matches
    return false;
  },

  _processService: function _processService(aService) {
    // Use the REST api to request more information about this service
    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
    xhr.open("GET", aService.location, true);
    xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    xhr.overrideMimeType("text/xml");

    xhr.addEventListener("load", () => {
      if (xhr.status == 200) {
        let doc = xhr.responseXML;
        aService.appsURL = xhr.getResponseHeader("Application-URL");
        if (aService.appsURL && !aService.appsURL.endsWith("/"))
          aService.appsURL += "/";
        aService.friendlyName = doc.querySelector("friendlyName").textContent;
        aService.uuid = doc.querySelector("UDN").textContent;
        aService.manufacturer = doc.querySelector("manufacturer").textContent;
        aService.modelName = doc.querySelector("modelName").textContent;

        this.addService(aService);
      }
    });

    xhr.send(null);
  },

  // Add a service to the WeakMap, even if one already exists with this id.
  // Returns true if this succeeded or false if it failed
  _addService(service) {
    // Filter out services that do not match the device filter
    if (!this._filterService(service)) {
      return false;
    }

    let device = this._devices.get(service.target);
    if (device && device.mirror) {
      service.mirror = true;
    }
    this._services.set(service.uuid, service);
    return true;
  },

  addService(service) {
    // Only add and notify if we don't already know about this service
    if (!this._services.has(service.uuid)) {
      if (!this._addService(service)) {
        return;
      }
      Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, service.uuid);
    }

    // Make sure we remember this service is not stale
    this._services.get(service.uuid).lastPing = this._searchTimestamp;
  },

  removeService(uuid) {
    Services.obs.notifyObservers(null, EVENT_SERVICE_LOST, uuid);
    this._services.delete(uuid);
  },

  updateService(service) {
    if (!this._addService(service)) {
      return;
    }

    // Make sure we remember this service is not stale
    this._services.get(service.uuid).lastPing = this._searchTimestamp;
  },

  addExternalDiscovery(discovery) {
    this._discoveryMethods.push(discovery);
  },

  _startExternalDiscovery() {
    for (let discovery of this._discoveryMethods) {
      discovery.startDiscovery();
    }
  },

  _stopExternalDiscovery() {
    for (let discovery of this._discoveryMethods) {
      discovery.stopDiscovery();
    }
  },
}
PK
!<1~modules/Sqlite.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "Sqlite",
];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

// The time to wait before considering a transaction stuck and rejecting it.
const TRANSACTIONS_QUEUE_TIMEOUT_MS = 240000 // 4 minutes

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                  "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService",
                                   "@mozilla.org/toolkit/finalizationwitness;1",
                                   "nsIFinalizationWitnessService");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
                                  "resource://gre/modules/Console.jsm");

// Regular expression used by isInvalidBoundLikeQuery
var likeSqlRegex = /\bLIKE\b\s(?![@:?])/i;

// Counts the number of created connections per database basename(). This is
// used for logging to distinguish connection instances.
var connectionCounters = new Map();

// Tracks identifiers of wrapped connections, that are Storage connections
// opened through mozStorage and then wrapped by Sqlite.jsm to use its syntactic
// sugar API.  Since these connections have an unknown origin, we use this set
// to differentiate their behavior.
var wrappedConnections = new Set();

/**
 * Once `true`, reject any attempt to open or close a database.
 */
var isClosed = false;

var Debugging = {
  // Tests should fail if a connection auto closes.  The exception is
  // when finalization itself is tested, in which case this flag
  // should be set to false.
  failTestsOnAutoClose: true
};

/**
 * Helper function to check whether LIKE is implemented using proper bindings.
 *
 * @param sql
 *        (string) The SQL query to be verified.
 * @return boolean value telling us whether query was correct or not
*/
function isInvalidBoundLikeQuery(sql) {
  return likeSqlRegex.test(sql);
}

// Displays a script error message
function logScriptError(message) {
  let consoleMessage = Cc["@mozilla.org/scripterror;1"].
                       createInstance(Ci.nsIScriptError);
  let stack = new Error();
  consoleMessage.init(message, stack.fileName, null, stack.lineNumber, 0,
                      Ci.nsIScriptError.errorFlag, "component javascript");
  Services.console.logMessage(consoleMessage);

  // This `Promise.reject` will cause tests to fail.  The debugging
  // flag can be used to suppress this for tests that explicitly
  // test auto closes.
  if (Debugging.failTestsOnAutoClose) {
    Promise.reject(new Error(message));
  }
}

/**
 * Gets connection identifier from its database file name.
 *
 * @param fileName
 *        A database file string name.
 * @return the connection identifier.
 */
function getIdentifierByFileName(fileName) {
  let number = connectionCounters.get(fileName) || 0;
  connectionCounters.set(fileName, number + 1);
  return fileName + "#" + number;
}

/**
 * Barriers used to ensure that Sqlite.jsm is shutdown after all
 * its clients.
 */
XPCOMUtils.defineLazyGetter(this, "Barriers", () => {
  let Barriers = {
    /**
     * Public barrier that clients may use to add blockers to the
     * shutdown of Sqlite.jsm. Triggered by profile-before-change.
     * Once all blockers of this barrier are lifted, we close the
     * ability to open new connections.
     */
    shutdown: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all clients have completed their task"),

    /**
     * Private barrier blocked by connections that are still open.
     * Triggered after Barriers.shutdown is lifted and `isClosed` is
     * set to `true`.
     */
    connections: new AsyncShutdown.Barrier("Sqlite.jsm: wait until all connections are closed"),
  };

  /**
   * Observer for the event which is broadcasted when the finalization
   * witness `_witness` of `OpenedConnection` is garbage collected.
   *
   * The observer is passed the connection identifier of the database
   * connection that is being finalized.
   */
  let finalizationObserver = function(subject, topic, identifier) {
    let connectionData = ConnectionData.byId.get(identifier);

    if (connectionData === undefined) {
      logScriptError("Error: Attempt to finalize unknown Sqlite connection: " +
                     identifier + "\n");
      return;
    }

    ConnectionData.byId.delete(identifier);
    logScriptError("Warning: Sqlite connection '" + identifier +
                   "' was not properly closed. Auto-close triggered by garbage collection.\n");
    connectionData.close();
  };
  Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness");

  /**
   * Ensure that Sqlite.jsm:
   * - informs its clients before shutting down;
   * - lets clients open connections during shutdown, if necessary;
   * - waits for all connections to be closed before shutdown.
   */
  AsyncShutdown.profileBeforeChange.addBlocker("Sqlite.jsm shutdown blocker",
    async function() {
      await Barriers.shutdown.wait();
      // At this stage, all clients have had a chance to open (and close)
      // their databases. Some previous close operations may still be pending,
      // so we need to wait until they are complete before proceeding.

      // Prevent any new opening.
      isClosed = true;

      // Now, wait until all databases are closed
      await Barriers.connections.wait();

      // Everything closed, no finalization events to catch
      Services.obs.removeObserver(finalizationObserver, "sqlite-finalization-witness");
    },

    function status() {
      if (isClosed) {
        // We are waiting for the connections to close. The interesting
        // status is therefore the list of connections still pending.
        return { description: "Waiting for connections to close",
                 state: Barriers.connections.state };
      }

      // We are still in the first stage: waiting for the barrier
      // to be lifted. The interesting status is therefore that of
      // the barrier.
      return { description: "Waiting for the barrier to be lifted",
               state: Barriers.shutdown.state };
  });

  return Barriers;
});

/**
 * Connection data with methods necessary for closing the connection.
 *
 * To support auto-closing in the event of garbage collection, this
 * data structure contains all the connection data of an opened
 * connection and all of the methods needed for sucessfully closing
 * it.
 *
 * By putting this information in its own separate object, it is
 * possible to store an additional reference to it without preventing
 * a garbage collection of a finalization witness in
 * OpenedConnection. When the witness detects a garbage collection,
 * this object can be used to close the connection.
 *
 * This object contains more methods than just `close`.  When
 * OpenedConnection needs to use the methods in this object, it will
 * dispatch its method calls here.
 */
function ConnectionData(connection, identifier, options = {}) {
  this._log = Log.repository.getLoggerWithMessagePrefix("Sqlite.Connection",
                                                        identifier + ": ");
  this._log.info("Opened");

  this._dbConn = connection;

  // This is a unique identifier for the connection, generated through
  // getIdentifierByFileName.  It may be used for logging or as a key in Maps.
  this._identifier = identifier;

  this._open = true;

  this._cachedStatements = new Map();
  this._anonymousStatements = new Map();
  this._anonymousCounter = 0;

  // A map from statement index to mozIStoragePendingStatement, to allow for
  // canceling prior to finalizing the mozIStorageStatements.
  this._pendingStatements = new Map();

  // Increments for each executed statement for the life of the connection.
  this._statementCounter = 0;

  // Increments whenever we request a unique operation id.
  this._operationsCounter = 0;

  this._hasInProgressTransaction = false;
  // Manages a chain of transactions promises, so that new transactions
  // always happen in queue to the previous ones.  It never rejects.
  this._transactionQueue = Promise.resolve();

  this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
  if (this._idleShrinkMS) {
    this._idleShrinkTimer = Cc["@mozilla.org/timer;1"]
                              .createInstance(Ci.nsITimer);
    // We wait for the first statement execute to start the timer because
    // shrinking now would not do anything.
  }

  // Deferred whose promise is resolved when the connection closing procedure
  // is complete.
  this._deferredClose = PromiseUtils.defer();
  this._closeRequested = false;

  // An AsyncShutdown barrier used to make sure that we wait until clients
  // are done before shutting down the connection.
  this._barrier = new AsyncShutdown.Barrier(`${this._identifier}: waiting for clients`);

  Barriers.connections.client.addBlocker(
    this._identifier + ": waiting for shutdown",
    this._deferredClose.promise,
    () => ({
      identifier: this._identifier,
      isCloseRequested: this._closeRequested,
      hasDbConn: !!this._dbConn,
      hasInProgressTransaction: this._hasInProgressTransaction,
      pendingStatements: this._pendingStatements.size,
      statementCounter: this._statementCounter,
    })
  );
}

/**
 * Map of connection identifiers to ConnectionData objects
 *
 * The connection identifier is a human-readable name of the
 * database. Used by finalization witnesses to be able to close opened
 * connections on garbage collection.
 *
 * Key: _identifier of ConnectionData
 * Value: ConnectionData object
 */
ConnectionData.byId = new Map();

ConnectionData.prototype = Object.freeze({
  /**
   * Run a task, ensuring that its execution will not be interrupted by shutdown.
   *
   * As the operations of this module are asynchronous, a sequence of operations,
   * or even an individual operation, can still be pending when the process shuts
   * down. If any of this operations is a write, this can cause data loss, simply
   * because the write has not been completed (or even started) by shutdown.
   *
   * To avoid this risk, clients are encouraged to use `executeBeforeShutdown` for
   * any write operation, as follows:
   *
   * myConnection.executeBeforeShutdown("Bookmarks: Removing a bookmark",
   *   Task.async(function*(db) {
   *     // The connection will not be closed and shutdown will not proceed
   *     // until this task has completed.
   *
   *     // `db` exposes the same API as `myConnection` but provides additional
   *     // logging support to help debug hard-to-catch shutdown timeouts.
   *
   *     yield db.execute(...);
   * }));
   *
   * @param {string} name A human-readable name for the ongoing operation, used
   *  for logging and debugging purposes.
   * @param {function(db)} task A function that takes as argument a Sqlite.jsm
   *  db and returns a Promise.
   */
  executeBeforeShutdown(parent, name, task) {
    if (!name) {
      throw new TypeError("Expected a human-readable name as first argument");
    }
    if (typeof task != "function") {
      throw new TypeError("Expected a function as second argument");
    }
    if (this._closeRequested) {
      throw new Error(`${this._identifier}: cannot execute operation ${name}, the connection is already closing`);
    }

    // Status, used for AsyncShutdown crash reports.
    let status = {
      // The latest command started by `task`, either as a
      // sql string, or as one of "<not started>" or "<closing>".
      command: "<not started>",

      // `true` if `command` was started but not completed yet.
      isPending: false,
    };

    // An object with the same API as `this` but with
    // additional logging. To keep logging simple, we
    // assume that `task` is not running several queries
    // concurrently.
    let loggedDb = Object.create(parent, {
      execute: {
        value: async (sql, ...rest) => {
          status.isPending = true;
          status.command = sql;
          try {
            return (await this.execute(sql, ...rest));
          } finally {
            status.isPending = false;
          }
        }
      },
      close: {
        value: async () => {
          status.isPending = false;
          status.command = "<close>";
          try {
            return (await this.close());
          } finally {
            status.isPending = false;
          }
        }
      },
      executeCached: {
        value: async (sql, ...rest) => {
          status.isPending = false;
          status.command = sql;
          try {
            return (await this.executeCached(sql, ...rest));
          } finally {
            status.isPending = false;
          }
        }
      },
    });

    let promiseResult = task(loggedDb);
    if (!promiseResult || typeof promiseResult != "object" || !("then" in promiseResult)) {
      throw new TypeError("Expected a Promise");
    }
    let key = `${this._identifier}: ${name} (${this._getOperationId()})`;
    let promiseComplete = promiseResult.catch(() => {});
    this._barrier.client.addBlocker(key, promiseComplete, {
      fetchState: () => status
    });

    return (async () => {
      try {
        return (await promiseResult);
      } finally {
        this._barrier.client.removeBlocker(key, promiseComplete)
      }
    })();
  },
  close() {
    this._closeRequested = true;

    if (!this._dbConn) {
      return this._deferredClose.promise;
    }

    this._log.debug("Request to close connection.");
    this._clearIdleShrinkTimer();

    return this._barrier.wait().then(() => {
      if (!this._dbConn) {
        return undefined;
      }
      return this._finalize();
    });
  },

  clone(readOnly = false) {
    this.ensureOpen();

    this._log.debug("Request to clone connection.");

    let options = {
      connection: this._dbConn,
      readOnly,
    };
    if (this._idleShrinkMS)
      options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;

    return cloneStorageConnection(options);
  },
  _getOperationId() {
    return this._operationsCounter++;
  },
  _finalize() {
    this._log.debug("Finalizing connection.");
    // Cancel any pending statements.
    for (let [/* k */, statement] of this._pendingStatements) {
      statement.cancel();
    }
    this._pendingStatements.clear();

    // We no longer need to track these.
    this._statementCounter = 0;

    // Next we finalize all active statements.
    for (let [/* k */, statement] of this._anonymousStatements) {
      statement.finalize();
    }
    this._anonymousStatements.clear();

    for (let [/* k */, statement] of this._cachedStatements) {
      statement.finalize();
    }
    this._cachedStatements.clear();

    // This guards against operations performed between the call to this
    // function and asyncClose() finishing. See also bug 726990.
    this._open = false;

    // We must always close the connection at the Sqlite.jsm-level, not
    // necessarily at the mozStorage-level.
    let markAsClosed = () => {
      this._log.info("Closed");
      // Now that the connection is closed, no need to keep
      // a blocker for Barriers.connections.
      Barriers.connections.client.removeBlocker(this._deferredClose.promise);
      this._deferredClose.resolve();
    }
    if (wrappedConnections.has(this._identifier)) {
      wrappedConnections.delete(this._identifier);
      this._dbConn = null;
      markAsClosed();
    } else {
      this._log.debug("Calling asyncClose().");
      this._dbConn.asyncClose(markAsClosed);
      this._dbConn = null;
    }
    return this._deferredClose.promise;
  },

  executeCached(sql, params = null, onRow = null) {
    this.ensureOpen();

    if (!sql) {
      throw new Error("sql argument is empty.");
    }

    let statement = this._cachedStatements.get(sql);
    if (!statement) {
      statement = this._dbConn.createAsyncStatement(sql);
      this._cachedStatements.set(sql, statement);
    }

    this._clearIdleShrinkTimer();

    return new Promise((resolve, reject) => {
      try {
        this._executeStatement(sql, statement, params, onRow).then(
          result => {
            this._startIdleShrinkTimer();
            resolve(result);
          },
          error => {
            this._startIdleShrinkTimer();
            reject(error);
          }
        );
      } catch (ex) {
        this._startIdleShrinkTimer();
        throw ex;
      }
    });
  },

  execute(sql, params = null, onRow = null) {
    if (typeof(sql) != "string") {
      throw new Error("Must define SQL to execute as a string: " + sql);
    }

    this.ensureOpen();

    let statement = this._dbConn.createAsyncStatement(sql);
    let index = this._anonymousCounter++;

    this._anonymousStatements.set(index, statement);
    this._clearIdleShrinkTimer();

    let onFinished = () => {
      this._anonymousStatements.delete(index);
      statement.finalize();
      this._startIdleShrinkTimer();
    };

    return new Promise((resolve, reject) => {
      try {
        this._executeStatement(sql, statement, params, onRow).then(
          rows => {
            onFinished();
            resolve(rows);
          },
          error => {
            onFinished();
            reject(error);
          }
        );
      } catch (ex) {
        onFinished();
        throw ex;
      }
    });
  },

  get transactionInProgress() {
    return this._open && this._hasInProgressTransaction;
  },

  executeTransaction(func, type) {
    if (typeof type == "undefined") {
      throw new Error("Internal error: expected a type");
    }
    this.ensureOpen();

    this._log.debug("Beginning transaction");

    let promise = this._transactionQueue.then(() => {
      if (this._closeRequested) {
        throw new Error("Transaction canceled due to a closed connection.");
      }

      let transactionPromise = (async () => {
        // At this point we should never have an in progress transaction, since
        // they are enqueued.
        if (this._hasInProgressTransaction) {
          console.error("Unexpected transaction in progress when trying to start a new one.");
        }
        this._hasInProgressTransaction = true;
        try {
          // We catch errors in statement execution to detect nested transactions.
          try {
            await this.execute("BEGIN " + type + " TRANSACTION");
          } catch (ex) {
            // Unfortunately, if we are wrapping an existing connection, a
            // transaction could have been started by a client of the same
            // connection that doesn't use Sqlite.jsm (e.g. C++ consumer).
            // The best we can do is proceed without a transaction and hope
            // things won't break.
            if (wrappedConnections.has(this._identifier)) {
              this._log.warn("A new transaction could not be started cause the wrapped connection had one in progress", ex);
              // Unmark the in progress transaction, since it's managed by
              // some other non-Sqlite.jsm client.  See the comment above.
              this._hasInProgressTransaction = false;
            } else {
              this._log.warn("A transaction was already in progress, likely a nested transaction", ex);
              throw ex;
            }
          }

          let result;
          try {
            // Keep Task.spawn here to preserve API compat; unfortunately
            // func was a generator rather than a task here.
            result = func();
            if (Object.prototype.toString.call(result) == "[object Generator]")
              result = await Task.spawn(func); // eslint-disable-line mozilla/no-task
            else
              result = await result;
          } catch (ex) {
            // It's possible that the exception has been caused by trying to
            // close the connection in the middle of a transaction.
            if (this._closeRequested) {
              this._log.warn("Connection closed while performing a transaction", ex);
            } else {
              this._log.warn("Error during transaction. Rolling back", ex);
              // If we began a transaction, we must rollback it.
              if (this._hasInProgressTransaction) {
                try {
                  await this.execute("ROLLBACK TRANSACTION");
                } catch (inner) {
                  this._log.warn("Could not roll back transaction", inner);
                }
              }
            }
            // Rethrow the exception.
            throw ex;
          }

          // See comment above about connection being closed during transaction.
          if (this._closeRequested) {
            this._log.warn("Connection closed before committing the transaction.");
            throw new Error("Connection closed before committing the transaction.");
          }

          // If we began a transaction, we must commit it.
          if (this._hasInProgressTransaction) {
            try {
              await this.execute("COMMIT TRANSACTION");
            } catch (ex) {
              this._log.warn("Error committing transaction", ex);
              throw ex;
            }
          }

          return result;
        } finally {
          this._hasInProgressTransaction = false;
        }
      })();

      // If a transaction yields on a never resolved promise, or is mistakenly
      // nested, it could hang the transactions queue forever.  Thus we timeout
      // the execution after a meaningful amount of time, to ensure in any case
      // we'll proceed after a while.
      let timeoutPromise = new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error("Transaction timeout, most likely caused by unresolved pending work.")),
                   TRANSACTIONS_QUEUE_TIMEOUT_MS);
      });
      return Promise.race([transactionPromise, timeoutPromise]);
    });
    // Atomically update the queue before anyone else has a chance to enqueue
    // further transactions.
    this._transactionQueue = promise.catch(ex => { console.error(ex) });

    // Make sure that we do not shutdown the connection during a transaction.
    this._barrier.client.addBlocker(`Transaction (${this._getOperationId()})`,
      this._transactionQueue);
    return promise;
  },

  shrinkMemory() {
    this._log.info("Shrinking memory usage.");
    let onShrunk = this._clearIdleShrinkTimer.bind(this);
    return this.execute("PRAGMA shrink_memory").then(onShrunk, onShrunk);
  },

  discardCachedStatements() {
    let count = 0;
    for (let [/* k */, statement] of this._cachedStatements) {
      ++count;
      statement.finalize();
    }
    this._cachedStatements.clear();
    this._log.debug("Discarded " + count + " cached statements.");
    return count;
  },

  /**
   * Helper method to bind parameters of various kinds through
   * reflection.
   */
  _bindParameters(statement, params) {
    if (!params) {
      return;
    }

    function bindParam(obj, key, val) {
      let isBlob = val && typeof val == "object" &&
                   val.constructor.name == "Uint8Array";
      let args = [key, val];
      if (isBlob)
        args.push(val.length);
      let methodName =
        `bind${isBlob ? "Blob" : ""}By${typeof key == "number" ? "Index" : "Name"}`;
      obj[methodName](...args);
    }

    if (Array.isArray(params)) {
      // It's an array of separate params.
      if (params.length && (typeof(params[0]) == "object")) {
        let paramsArray = statement.newBindingParamsArray();
        for (let p of params) {
          let bindings = paramsArray.newBindingParams();
          for (let [key, value] of Object.entries(p)) {
            bindParam(bindings, key, value);
          }
          paramsArray.addParams(bindings);
        }

        statement.bindParameters(paramsArray);
        return;
      }

      // Indexed params.
      for (let i = 0; i < params.length; i++) {
        bindParam(statement, i, params[i]);
      }
      return;
    }

    // Named params.
    if (params && typeof(params) == "object") {
      for (let k in params) {
        bindParam(statement, k, params[k]);
      }
      return;
    }

    throw new Error("Invalid type for bound parameters. Expected Array or " +
                    "object. Got: " + params);
  },

  _executeStatement(sql, statement, params, onRow) {
    if (statement.state != statement.MOZ_STORAGE_STATEMENT_READY) {
      throw new Error("Statement is not ready for execution.");
    }

    if (onRow && typeof(onRow) != "function") {
      throw new Error("onRow must be a function. Got: " + onRow);
    }

    this._bindParameters(statement, params);

    let index = this._statementCounter++;

    let deferred = PromiseUtils.defer();
    let userCancelled = false;
    let errors = [];
    let rows = [];
    let handledRow = false;

    // Don't incur overhead for serializing params unless the messages go
    // somewhere.
    if (this._log.level <= Log.Level.Trace) {
      let msg = "Stmt #" + index + " " + sql;

      if (params) {
        msg += " - " + JSON.stringify(params);
      }
      this._log.trace(msg);
    } else {
      this._log.debug("Stmt #" + index + " starting");
    }

    let self = this;
    let pending = statement.executeAsync({
      handleResult(resultSet) {
        // .cancel() may not be immediate and handleResult() could be called
        // after a .cancel().
        for (let row = resultSet.getNextRow(); row && !userCancelled; row = resultSet.getNextRow()) {
          if (!onRow) {
            rows.push(row);
            continue;
          }

          handledRow = true;

          try {
            onRow(row);
          } catch (e) {
            if (e instanceof StopIteration) {
              userCancelled = true;
              pending.cancel();
              break;
            }

            self._log.warn("Exception when calling onRow callback", e);
          }
        }
      },

      handleError(error) {
        self._log.info("Error when executing SQL (" +
                       error.result + "): " + error.message);
        errors.push(error);
      },

      handleCompletion(reason) {
        self._log.debug("Stmt #" + index + " finished.");
        self._pendingStatements.delete(index);

        switch (reason) {
          case Ci.mozIStorageStatementCallback.REASON_FINISHED:
            // If there is an onRow handler, we always instead resolve to a
            // boolean indicating whether the onRow handler was called or not.
            let result = onRow ? handledRow : rows;
            deferred.resolve(result);
            break;

          case Ci.mozIStorageStatementCallback.REASON_CANCELED:
            // It is not an error if the user explicitly requested cancel via
            // the onRow handler.
            if (userCancelled) {
              let result = onRow ? handledRow : rows;
              deferred.resolve(result);
            } else {
              deferred.reject(new Error("Statement was cancelled."));
            }

            break;

          case Ci.mozIStorageStatementCallback.REASON_ERROR:
            let error = new Error("Error(s) encountered during statement execution: " + errors.map(e => e.message).join(", "));
            error.errors = errors;
            deferred.reject(error);
            break;

          default:
            deferred.reject(new Error("Unknown completion reason code: " +
                                      reason));
            break;
        }
      },
    });

    this._pendingStatements.set(index, pending);
    return deferred.promise;
  },

  ensureOpen() {
    if (!this._open) {
      throw new Error("Connection is not open.");
    }
  },

  _clearIdleShrinkTimer() {
    if (!this._idleShrinkTimer) {
      return;
    }

    this._idleShrinkTimer.cancel();
  },

  _startIdleShrinkTimer() {
    if (!this._idleShrinkTimer) {
      return;
    }

    this._idleShrinkTimer.initWithCallback(this.shrinkMemory.bind(this),
                                           this._idleShrinkMS,
                                           this._idleShrinkTimer.TYPE_ONE_SHOT);
  }
});

/**
 * Opens a connection to a SQLite database.
 *
 * The following parameters can control the connection:
 *
 *   path -- (string) The filesystem path of the database file to open. If the
 *       file does not exist, a new database will be created.
 *
 *   sharedMemoryCache -- (bool) Whether multiple connections to the database
 *       share the same memory cache. Sharing the memory cache likely results
 *       in less memory utilization. However, sharing also requires connections
 *       to obtain a lock, possibly making database access slower. Defaults to
 *       true.
 *
 *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
 *       will attempt to minimize its memory usage after this many
 *       milliseconds of connection idle. The connection is idle when no
 *       statements are executing. There is no default value which means no
 *       automatic memory minimization will occur. Please note that this is
 *       *not* a timer on the idle service and this could fire while the
 *       application is active.
 *
 *   readOnly -- (bool) Whether to open the database with SQLITE_OPEN_READONLY
 *       set. If used, writing to the database will fail. Defaults to false.
 *
 *   ignoreLockingMode -- (bool) Whether to ignore locks on the database held
 *       by other connections. If used, implies readOnly. Defaults to false.
 *       USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
 *       return "false positive" corruption errors if other connections write
 *       to the DB at the same time.
 *
 * FUTURE options to control:
 *
 *   special named databases
 *   pragma TEMP STORE = MEMORY
 *   TRUNCATE JOURNAL
 *   SYNCHRONOUS = full
 *
 * @param options
 *        (Object) Parameters to control connection and open options.
 *
 * @return Promise<OpenedConnection>
 */
function openConnection(options) {
  let log = Log.repository.getLogger("Sqlite.ConnectionOpener");

  if (!options.path) {
    throw new Error("path not specified in connection options.");
  }

  if (isClosed) {
    throw new Error("Sqlite.jsm has been shutdown. Cannot open connection to: " + options.path);
  }

  // Retains absolute paths and normalizes relative as relative to profile.
  let path = OS.Path.join(OS.Constants.Path.profileDir, options.path);

  let sharedMemoryCache = "sharedMemoryCache" in options ?
                            options.sharedMemoryCache : true;

  let openedOptions = {};

  if ("shrinkMemoryOnConnectionIdleMS" in options) {
    if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
      throw new Error("shrinkMemoryOnConnectionIdleMS must be an integer. " +
                      "Got: " + options.shrinkMemoryOnConnectionIdleMS);
    }

    openedOptions.shrinkMemoryOnConnectionIdleMS =
      options.shrinkMemoryOnConnectionIdleMS;
  }

  let file = FileUtils.File(path);
  let identifier = getIdentifierByFileName(OS.Path.basename(path));

  log.info("Opening database: " + path + " (" + identifier + ")");

  return new Promise((resolve, reject) => {
    let dbOptions = Cc["@mozilla.org/hash-property-bag;1"].
                    createInstance(Ci.nsIWritablePropertyBag);
    if (!sharedMemoryCache) {
      dbOptions.setProperty("shared", false);
    }
    if (options.readOnly) {
      dbOptions.setProperty("readOnly", true);
    }
    if (options.ignoreLockingMode) {
      dbOptions.setProperty("ignoreLockingMode", true);
      dbOptions.setProperty("readOnly", true);
    }

    dbOptions = dbOptions.enumerator.hasMoreElements() ? dbOptions : null;

    Services.storage.openAsyncDatabase(file, dbOptions, (status, connection) => {
      if (!connection) {
        log.warn(`Could not open connection to ${path}: ${status}`);
        reject(new Error(`Could not open connection to ${path}: ${status}`));
        return;
      }
      log.info("Connection opened");
      try {
        resolve(
          new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection),
                               identifier, openedOptions));
      } catch (ex) {
        log.warn("Could not open database", ex);
        connection.asyncClose();
        reject(ex);
      }
    });
  });
}

/**
 * Creates a clone of an existing and open Storage connection.  The clone has
 * the same underlying characteristics of the original connection and is
 * returned in form of an OpenedConnection handle.
 *
 * The following parameters can control the cloned connection:
 *
 *   connection -- (mozIStorageAsyncConnection) The original Storage connection
 *       to clone.  It's not possible to clone connections to memory databases.
 *
 *   readOnly -- (boolean) - If true the clone will be read-only.  If the
 *       original connection is already read-only, the clone will be, regardless
 *       of this option.  If the original connection is using the shared cache,
 *       this parameter will be ignored and the clone will be as privileged as
 *       the original connection.
 *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
 *       will attempt to minimize its memory usage after this many
 *       milliseconds of connection idle. The connection is idle when no
 *       statements are executing. There is no default value which means no
 *       automatic memory minimization will occur. Please note that this is
 *       *not* a timer on the idle service and this could fire while the
 *       application is active.
 *
 *
 * @param options
 *        (Object) Parameters to control connection and clone options.
 *
 * @return Promise<OpenedConnection>
 */
function cloneStorageConnection(options) {
  let log = Log.repository.getLogger("Sqlite.ConnectionCloner");

  let source = options && options.connection;
  if (!source) {
    throw new TypeError("connection not specified in clone options.");
  }
  if (!(source instanceof Ci.mozIStorageAsyncConnection)) {
    throw new TypeError("Connection must be a valid Storage connection.");
  }

  if (isClosed) {
    throw new Error("Sqlite.jsm has been shutdown. Cannot clone connection to: " + source.database.path);
  }

  let openedOptions = {};

  if ("shrinkMemoryOnConnectionIdleMS" in options) {
    if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
      throw new TypeError("shrinkMemoryOnConnectionIdleMS must be an integer. " +
                          "Got: " + options.shrinkMemoryOnConnectionIdleMS);
    }
    openedOptions.shrinkMemoryOnConnectionIdleMS =
      options.shrinkMemoryOnConnectionIdleMS;
  }

  let path = source.databaseFile.path;
  let identifier = getIdentifierByFileName(OS.Path.basename(path));

  log.info("Cloning database: " + path + " (" + identifier + ")");

  return new Promise((resolve, reject) => {
    source.asyncClone(!!options.readOnly, (status, connection) => {
      if (!connection) {
        log.warn("Could not clone connection: " + status);
        reject(new Error("Could not clone connection: " + status));
      }
      log.info("Connection cloned");
      try {
        let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
        resolve(new OpenedConnection(conn, identifier, openedOptions));
      } catch (ex) {
        log.warn("Could not clone database", ex);
        connection.asyncClose();
        reject(ex);
      }
    });
  });
}

/**
 * Wraps an existing and open Storage connection with Sqlite.jsm API.  The
 * wrapped connection clone has the same underlying characteristics of the
 * original connection and is returned in form of an OpenedConnection handle.
 *
 * Clients are responsible for closing both the Sqlite.jsm wrapper and the
 * underlying mozStorage connection.
 *
 * The following parameters can control the wrapped connection:
 *
 *   connection -- (mozIStorageAsyncConnection) The original Storage connection
 *       to wrap.
 *
 * @param options
 *        (Object) Parameters to control connection and wrap options.
 *
 * @return Promise<OpenedConnection>
 */
function wrapStorageConnection(options) {
  let log = Log.repository.getLogger("Sqlite.ConnectionWrapper");

  let connection = options && options.connection;
  if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) {
    throw new TypeError("connection not specified or invalid.");
  }

  if (isClosed) {
    throw new Error("Sqlite.jsm has been shutdown. Cannot wrap connection to: " + connection.database.path);
  }

  let identifier = getIdentifierByFileName(connection.databaseFile.leafName);

  log.info("Wrapping database: " + identifier);
  return new Promise(resolve => {
    try {
      let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
      let wrapper = new OpenedConnection(conn, identifier);
      // We must not handle shutdown of a wrapped connection, since that is
      // already handled by the opener.
      wrappedConnections.add(identifier);
      resolve(wrapper);
    } catch (ex) {
      log.warn("Could not wrap database", ex);
      throw ex;
    }
  });
}

/**
 * Handle on an opened SQLite database.
 *
 * This is essentially a glorified wrapper around mozIStorageConnection.
 * However, it offers some compelling advantages.
 *
 * The main functions on this type are `execute` and `executeCached`. These are
 * ultimately how all SQL statements are executed. It's worth explaining their
 * differences.
 *
 * `execute` is used to execute one-shot SQL statements. These are SQL
 * statements that are executed one time and then thrown away. They are useful
 * for dynamically generated SQL statements and clients who don't care about
 * performance (either their own or wasting resources in the overall
 * application). Because of the performance considerations, it is recommended
 * to avoid `execute` unless the statement you are executing will only be
 * executed once or seldomly.
 *
 * `executeCached` is used to execute a statement that will presumably be
 * executed multiple times. The statement is parsed once and stuffed away
 * inside the connection instance. Subsequent calls to `executeCached` will not
 * incur the overhead of creating a new statement object. This should be used
 * in preference to `execute` when a specific SQL statement will be executed
 * multiple times.
 *
 * Instances of this type are not meant to be created outside of this file.
 * Instead, first open an instance of `UnopenedSqliteConnection` and obtain
 * an instance of this type by calling `open`.
 *
 * FUTURE IMPROVEMENTS
 *
 *   Ability to enqueue operations. Currently there can be race conditions,
 *   especially as far as transactions are concerned. It would be nice to have
 *   an enqueueOperation(func) API that serially executes passed functions.
 *
 *   Support for SAVEPOINT (named/nested transactions) might be useful.
 *
 * @param connection
 *        (mozIStorageConnection) Underlying SQLite connection.
 * @param identifier
 *        (string) The unique identifier of this database. It may be used for
 *        logging or as a key in Maps.
 * @param options [optional]
 *        (object) Options to control behavior of connection. See
 *        `openConnection`.
 */
function OpenedConnection(connection, identifier, options = {}) {
  // Store all connection data in a field distinct from the
  // witness. This enables us to store an additional reference to this
  // field without preventing garbage collection of
  // OpenedConnection. On garbage collection, we will still be able to
  // close the database using this extra reference.
  this._connectionData = new ConnectionData(connection, identifier, options);

  // Store the extra reference in a map with connection identifier as
  // key.
  ConnectionData.byId.set(this._connectionData._identifier,
                          this._connectionData);

  // Make a finalization witness. If this object is garbage collected
  // before its `forget` method has been called, an event with topic
  // "sqlite-finalization-witness" is broadcasted along with the
  // connection identifier string of the database.
  this._witness = FinalizationWitnessService.make(
    "sqlite-finalization-witness",
    this._connectionData._identifier);
}

OpenedConnection.prototype = Object.freeze({
  TRANSACTION_DEFERRED: "DEFERRED",
  TRANSACTION_IMMEDIATE: "IMMEDIATE",
  TRANSACTION_EXCLUSIVE: "EXCLUSIVE",

  TRANSACTION_TYPES: ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"],

  /**
   * The integer schema version of the database.
   *
   * This is 0 if not schema version has been set.
   *
   * @return Promise<int>
   */
  getSchemaVersion() {
    return this.execute("PRAGMA user_version").then(
      function onSuccess(result) {
        if (result == null) {
          return 0;
        }
        return JSON.stringify(result[0].getInt32(0));
      }
    );
  },

  setSchemaVersion(value) {
    if (!Number.isInteger(value)) {
      // Guarding against accidental SQLi
      throw new TypeError("Schema version must be an integer. Got " + value);
    }
    this._connectionData.ensureOpen();
    return this.execute("PRAGMA user_version = " + value);
  },

  /**
   * Close the database connection.
   *
   * This must be performed when you are finished with the database.
   *
   * Closing the database connection has the side effect of forcefully
   * cancelling all active statements. Therefore, callers should ensure that
   * all active statements have completed before closing the connection, if
   * possible.
   *
   * The returned promise will be resolved once the connection is closed.
   * Successive calls to close() return the same promise.
   *
   * IMPROVEMENT: Resolve the promise to a closed connection which can be
   * reopened.
   *
   * @return Promise<>
   */
  close() {
    // Unless cleanup has already been done by a previous call to
    // `close`, delete the database entry from map and tell the
    // finalization witness to forget.
    if (ConnectionData.byId.has(this._connectionData._identifier)) {
      ConnectionData.byId.delete(this._connectionData._identifier);
      this._witness.forget();
    }
    return this._connectionData.close();
  },

  /**
   * Clones this connection to a new Sqlite one.
   *
   * The following parameters can control the cloned connection:
   *
   * @param readOnly
   *        (boolean) - If true the clone will be read-only.  If the original
   *        connection is already read-only, the clone will be, regardless of
   *        this option.  If the original connection is using the shared cache,
   *        this parameter will be ignored and the clone will be as privileged as
   *        the original connection.
   *
   * @return Promise<OpenedConnection>
   */
  clone(readOnly = false) {
    return this._connectionData.clone(readOnly);
  },

  executeBeforeShutdown(name, task) {
    return this._connectionData.executeBeforeShutdown(this, name, task);
  },

  /**
   * Execute a SQL statement and cache the underlying statement object.
   *
   * This function executes a SQL statement and also caches the underlying
   * derived statement object so subsequent executions are faster and use
   * less resources.
   *
   * This function optionally binds parameters to the statement as well as
   * optionally invokes a callback for every row retrieved.
   *
   * By default, no parameters are bound and no callback will be invoked for
   * every row.
   *
   * Bound parameters can be defined as an Array of positional arguments or
   * an object mapping named parameters to their values. If there are no bound
   * parameters, the caller can pass nothing or null for this argument.
   *
   * Callers are encouraged to pass objects rather than Arrays for bound
   * parameters because they prevent foot guns. With positional arguments, it
   * is simple to modify the parameter count or positions without fixing all
   * users of the statement. Objects/named parameters are a little safer
   * because changes in order alone won't result in bad things happening.
   *
   * When `onRow` is not specified, all returned rows are buffered before the
   * returned promise is resolved. For INSERT or UPDATE statements, this has
   * no effect because no rows are returned from these. However, it has
   * implications for SELECT statements.
   *
   * If your SELECT statement could return many rows or rows with large amounts
   * of data, for performance reasons it is recommended to pass an `onRow`
   * handler. Otherwise, the buffering may consume unacceptable amounts of
   * resources.
   *
   * If a `StopIteration` is thrown during execution of an `onRow` handler,
   * the execution of the statement is immediately cancelled. Subsequent
   * rows will not be processed and no more `onRow` invocations will be made.
   * The promise is resolved immediately.
   *
   * If a non-`StopIteration` exception is thrown by the `onRow` handler, the
   * exception is logged and processing of subsequent rows occurs as if nothing
   * happened. The promise is still resolved (not rejected).
   *
   * The return value is a promise that will be resolved when the statement
   * has completed fully.
   *
   * The promise will be rejected with an `Error` instance if the statement
   * did not finish execution fully. The `Error` may have an `errors` property.
   * If defined, it will be an Array of objects describing individual errors.
   * Each object has the properties `result` and `message`. `result` is a
   * numeric error code and `message` is a string description of the problem.
   *
   * @param name
   *        (string) The name of the registered statement to execute.
   * @param params optional
   *        (Array or object) Parameters to bind.
   * @param onRow optional
   *        (function) Callback to receive each row from result.
   */
  executeCached(sql, params = null, onRow = null) {
    if (isInvalidBoundLikeQuery(sql)) {
      throw new Error("Please enter a LIKE clause with bindings");
    }
    return this._connectionData.executeCached(sql, params, onRow);
  },

  /**
   * Execute a one-shot SQL statement.
   *
   * If you find yourself feeding the same SQL string in this function, you
   * should *not* use this function and instead use `executeCached`.
   *
   * See `executeCached` for the meaning of the arguments and extended usage info.
   *
   * @param sql
   *        (string) SQL to execute.
   * @param params optional
   *        (Array or Object) Parameters to bind to the statement.
   * @param onRow optional
   *        (function) Callback to receive result of a single row.
   */
  execute(sql, params = null, onRow = null) {
    if (isInvalidBoundLikeQuery(sql)) {
      throw new Error("Please enter a LIKE clause with bindings");
    }
    return this._connectionData.execute(sql, params, onRow);
  },

  /**
   * Whether a transaction is currently in progress.
   */
  get transactionInProgress() {
    return this._connectionData.transactionInProgress;
  },

  /**
   * Perform a transaction.
   *
   * *****************************************************************************
   * YOU SHOULD _NEVER_ NEST executeTransaction CALLS FOR ANY REASON, NOR
   * DIRECTLY, NOR THROUGH OTHER PROMISES.
   * FOR EXAMPLE, NEVER DO SOMETHING LIKE:
   *   yield executeTransaction(function* () {
   *     ...some_code...
   *     yield executeTransaction(function* () { // WRONG!
   *       ...some_code...
   *     })
   *     yield someCodeThatExecuteTransaction(); // WRONG!
   *     yield neverResolvedPromise; // WRONG!
   *   });
   * NESTING CALLS WILL BLOCK ANY FUTURE TRANSACTION UNTIL A TIMEOUT KICKS IN.
   * *****************************************************************************
   *
   * A transaction is specified by a user-supplied function that is a
   * generator function which can be used by Task.jsm's Task.spawn(). The
   * function receives this connection instance as its argument.
   *
   * The supplied function is expected to yield promises. These are often
   * promises created by calling `execute` and `executeCached`. If the
   * generator is exhausted without any errors being thrown, the
   * transaction is committed. If an error occurs, the transaction is
   * rolled back.
   *
   * The returned value from this function is a promise that will be resolved
   * once the transaction has been committed or rolled back. The promise will
   * be resolved to whatever value the supplied function resolves to. If
   * the transaction is rolled back, the promise is rejected.
   *
   * @param func
   *        (function) What to perform as part of the transaction.
   * @param type optional
   *        One of the TRANSACTION_* constants attached to this type.
   */
  executeTransaction(func, type = this.TRANSACTION_DEFERRED) {
    if (this.TRANSACTION_TYPES.indexOf(type) == -1) {
      throw new Error("Unknown transaction type: " + type);
    }

    return this._connectionData.executeTransaction(() => func(this), type);
  },

  /**
   * Whether a table exists in the database (both persistent and temporary tables).
   *
   * @param name
   *        (string) Name of the table.
   *
   * @return Promise<bool>
   */
  tableExists(name) {
    return this.execute(
      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
                        "SELECT * FROM sqlite_temp_master) " +
      "WHERE type = 'table' AND name=?",
      [name])
      .then(function onResult(rows) {
        return Promise.resolve(rows.length > 0);
      }
    );
  },

  /**
   * Whether a named index exists (both persistent and temporary tables).
   *
   * @param name
   *        (string) Name of the index.
   *
   * @return Promise<bool>
   */
  indexExists(name) {
    return this.execute(
      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
                        "SELECT * FROM sqlite_temp_master) " +
      "WHERE type = 'index' AND name=?",
      [name])
      .then(function onResult(rows) {
        return Promise.resolve(rows.length > 0);
      }
    );
  },

  /**
   * Free up as much memory from the underlying database connection as possible.
   *
   * @return Promise<>
   */
  shrinkMemory() {
    return this._connectionData.shrinkMemory();
  },

  /**
   * Discard all cached statements.
   *
   * Note that this relies on us being non-interruptible between
   * the insertion or retrieval of a statement in the cache and its
   * execution: we finalize all statements, which is only safe if
   * they will not be executed again.
   *
   * @return (integer) the number of statements discarded.
   */
  discardCachedStatements() {
    return this._connectionData.discardCachedStatements();
  },
});

this.Sqlite = {
  openConnection,
  cloneStorageConnection,
  wrapStorageConnection,
  /**
   * Shutdown barrier client. May be used by clients to perform last-minute
   * cleanup prior to the shutdown of this module.
   *
   * See the documentation of AsyncShutdown.Barrier.prototype.client.
   */
  get shutdown() {
    return Barriers.shutdown.client;
  },
  failTestsOnAutoClose(enabled) {
    Debugging.failTestsOnAutoClose = enabled;
  },
};
PK
!<3Tssmodules/Subprocess.jsm/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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/. */

/*
 * These modules are loosely based on the subprocess.jsm module created
 * by Jan Gerber and Patrick Brunschwig, though the implementation
 * differs drastically.
 */

"use strict";

let EXPORTED_SYMBOLS = ["Subprocess"];

/* exported Subprocess */

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.importGlobalProperties(["TextEncoder"]);

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/subprocess/subprocess_common.jsm");

if (AppConstants.platform == "win") {
  XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
                                    "resource://gre/modules/subprocess/subprocess_win.jsm");
} else {
  XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
                                    "resource://gre/modules/subprocess/subprocess_unix.jsm");
}

function encodeEnvVar(name, value) {
  if (typeof name === "string" && typeof value === "string") {
    return `${name}=${value}`;
  }

  let encoder = new TextEncoder("utf-8");
  function encode(val) {
    return typeof val === "string" ? encoder.encode(val) : val;
  }

  return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
}

/**
 * Allows for creation of and communication with OS-level sub-processes.
 * @namespace
 */
var Subprocess = {
  /**
   * Launches a process, and returns a handle to it.
   *
   * @param {object} options
   * An object describing the process to launch.
   *
   * @param {string} options.command
   * The full path of the execuable to launch. Relative paths are not
   * accepted, and `$PATH` is not searched.
   *
   * If a path search is necessary, the {@link Subprocess.pathSearch} method may
   * be used to map a bare executable name to a full path.
   *
   * @param {string[]} [options.arguments]
   * A list of strings to pass as arguments to the process.
   *
   * @param {object} [options.environment]
   * An object containing a key and value for each environment variable
   * to pass to the process. Only the object's own, enumerable properties
   * are added to the environment.
   *
   * @param {boolean} [options.environmentAppend]
   * If true, append the environment variables passed in `environment` to
   * the existing set of environment variables. Otherwise, the values in
   * 'environment' constitute the entire set of environment variables
   * passed to the new process.
   *
   * @param {string} [options.stderr]
   * Defines how the process's stderr output is handled. One of:
   *
   * - `"ignore"`: (default) The process's standard error is not redirected.
   * - `"stdout"`: The process's stderr is merged with its stdout.
   * - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
   *   from via its `stderr` property.
   *
   * @param {string} [options.workdir]
   *        The working directory in which to launch the new process.
   *
   * @returns {Promise<Process>}
   *
   * @rejects {Error}
   * May be rejected with an Error object if the process can not be
   * launched. The object will include an `errorCode` property with
   * one of the following values if it was rejected for the
   * corresponding reason:
   *
   * - Subprocess.ERROR_BAD_EXECUTABLE: The given command could not
   *   be found, or the file that it references is not executable.
   *
   * Note that if the process is successfully launched, but exits with
   * a non-zero exit code, the promise will still resolve successfully.
   */
  call(options) {
    options = Object.assign({}, options);

    options.stderr = options.stderr || "ignore";
    options.workdir = options.workdir || null;

    let environment = {};
    if (!options.environment || options.environmentAppend) {
      environment = this.getEnvironment();
    }

    if (options.environment) {
      Object.assign(environment, options.environment);
    }

    options.environment = Object.entries(environment)
                                .map(([key, val]) => encodeEnvVar(key, val));

    options.arguments = Array.from(options.arguments || []);

    return Promise.resolve(SubprocessImpl.isExecutableFile(options.command)).then(isExecutable => {
      if (!isExecutable) {
        let error = new Error(`File at path "${options.command}" does not exist, or is not executable`);
        error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
        throw error;
      }

      options.arguments.unshift(options.command);

      return SubprocessImpl.call(options);
    });
  },

  /**
   * Returns an object with a key-value pair for every variable in the process's
   * current environment.
   *
   * @returns {object}
   */
  getEnvironment() {
    let environment = Object.create(null);
    for (let [k, v] of SubprocessImpl.getEnvironment()) {
      environment[k] = v;
    }
    return environment;
  },

  /**
   * Searches for the given executable file in the system executable
   * file paths as specified by the PATH environment variable.
   *
   * On Windows, if the unadorned filename cannot be found, the
   * extensions in the semicolon-separated list in the PATHSEP
   * environment variable are successively appended to the original
   * name and searched for in turn.
   *
   * @param {string} command
   *        The name of the executable to find.
   * @param {object} [environment]
   *        An object containing a key for each environment variable to be used
   *        in the search. If not provided, full the current process environment
   *        is used.
   * @returns {Promise<string>}
   */
  pathSearch(command, environment = this.getEnvironment()) {
    // Promise.resolve lets us get around returning one of the Promise.jsm
    // pseudo-promises returned by Task.jsm.
    let path = SubprocessImpl.pathSearch(command, environment);
    return Promise.resolve(path);
  },
};

Object.assign(Subprocess, SubprocessConstants);
Object.freeze(Subprocess);
PK
!<6e>DDmodules/Task.jsm/* -*- 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";

/* eslint-disable mozilla/no-task */

this.EXPORTED_SYMBOLS = [
  "Task"
];

/**
 * This module implements a subset of "Task.js" <http://taskjs.org/>.
 *
 * Paraphrasing from the Task.js site, tasks make sequential, asynchronous
 * operations simple, using the power of JavaScript's "yield" operator.
 *
 * Tasks are built upon generator functions and promises, documented here:
 *
 * <https://developer.mozilla.org/en/JavaScript/Guide/Iterators_and_Generators>
 * <http://wiki.commonjs.org/wiki/Promises/A>
 *
 * The "Task.spawn" function takes a generator function and starts running it as
 * a task.  Every time the task yields a promise, it waits until the promise is
 * fulfilled.  "Task.spawn" returns a promise that is resolved when the task
 * completes successfully, or is rejected if an exception occurs.
 *
 * -----------------------------------------------------------------------------
 *
 * Cu.import("resource://gre/modules/Task.jsm");
 *
 * Task.spawn(function* () {
 *
 *   // This is our task. Let's create a promise object, wait on it and capture
 *   // its resolution value.
 *   let myPromise = getPromiseResolvedOnTimeoutWithValue(1000, "Value");
 *   let result = yield myPromise;
 *
 *   // This part is executed only after the promise above is fulfilled (after
 *   // one second, in this imaginary example).  We can easily loop while
 *   // calling asynchronous functions, and wait multiple times.
 *   for (let i = 0; i < 3; i++) {
 *     result += yield getPromiseResolvedOnTimeoutWithValue(50, "!");
 *   }
 *
 *   return "Resolution result for the task: " + result;
 * }).then(function (result) {
 *
 *   // result == "Resolution result for the task: Value!!!"
 *
 *   // The result is undefined if no value was returned.
 *
 * }, function (exception) {
 *
 *   // Failure!  We can inspect or report the exception.
 *
 * });
 *
 * -----------------------------------------------------------------------------
 *
 * This module implements only the "Task.js" interfaces described above, with no
 * additional features to control the task externally, or do custom scheduling.
 * It also provides the following extensions that simplify task usage in the
 * most common cases:
 *
 * - The "Task.spawn" function also accepts an iterator returned by a generator
 *   function, in addition to a generator function.  This way, you can call into
 *   the generator function with the parameters you want, and with "this" bound
 *   to the correct value.  Also, "this" is never bound to the task object when
 *   "Task.spawn" calls the generator function.
 *
 * - In addition to a promise object, a task can yield the iterator returned by
 *   a generator function.  The iterator is turned into a task automatically.
 *   This reduces the syntax overhead of calling "Task.spawn" explicitly when
 *   you want to recurse into other task functions.
 *
 * - The "Task.spawn" function also accepts a primitive value, or a function
 *   returning a primitive value, and treats the value as the result of the
 *   task.  This makes it possible to call an externally provided function and
 *   spawn a task from it, regardless of whether it is an asynchronous generator
 *   or a synchronous function.  This comes in handy when iterating over
 *   function lists where some items have been converted to tasks and some not.
 */

// Globals

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;

// For now, we're worried about add-ons using Tasks with CPOWs, so we'll
// permit them in this scope, but this support will go away soon.
Cu.permitCPOWsInScope(this);

// The following error types are considered programmer errors, which should be
// reported (possibly redundantly) so as to let programmers fix their code.
const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeError"];

/**
 * The Task currently being executed
 */
var gCurrentTask = null;

/**
 * If `true`, capture stacks whenever entering a Task and rewrite the
 * stack any exception thrown through a Task.
 */
var gMaintainStack = false;


/**
 * Iterate through the lines of a string.
 *
 * @return Iterator<string>
 */
function* linesOf(string) {
  let reLine = /([^\r\n])+/g;
  let match;
  while ((match = reLine.exec(string))) {
    yield [match[0], match.index];
  }
}

/**
 * Detect whether a value is a generator.
 *
 * @param aValue
 *        The value to identify.
 * @return A boolean indicating whether the value is a generator.
 */
function isGenerator(aValue) {
  return Object.prototype.toString.call(aValue) == "[object Generator]";
}

// Task

/**
 * This object provides the public module functions.
 */
this.Task = {
  /**
   * Creates and starts a new task.
   *
   * @param aTask
   *        - If you specify a generator function, it is called with no
   *          arguments to retrieve the associated iterator.  The generator
   *          function is a task, that is can yield promise objects to wait
   *          upon.
   *        - If you specify the iterator returned by a generator function you
   *          called, the generator function is also executed as a task.  This
   *          allows you to call the function with arguments.
   *        - If you specify a function that is not a generator, it is called
   *          with no arguments, and its return value is used to resolve the
   *          returned promise.
   *        - If you specify anything else, you get a promise that is already
   *          resolved with the specified value.
   *
   * @return A promise object where you can register completion callbacks to be
   *         called when the task terminates.
   */
  spawn: function Task_spawn(aTask) {
    return createAsyncFunction(aTask)();
  },

  /**
   * Create and return an 'async function' that starts a new task.
   *
   * This is similar to 'spawn' except that it doesn't immediately start
   * the task, it binds the task to the async function's 'this' object and
   * arguments, and it requires the task to be a function.
   *
   * It simplifies the common pattern of implementing a method via a task,
   * like this simple object with a 'greet' method that has a 'name' parameter
   * and spawns a task to send a greeting and return its reply:
   *
   * let greeter = {
   *   message: "Hello, NAME!",
   *   greet: function(name) {
   *     return Task.spawn((function* () {
   *       return yield sendGreeting(this.message.replace(/NAME/, name));
   *     }).bind(this);
   *   })
   * };
   *
   * With Task.async, the method can be declared succinctly:
   *
   * let greeter = {
   *   message: "Hello, NAME!",
   *   greet: Task.async(function* (name) {
   *     return yield sendGreeting(this.message.replace(/NAME/, name));
   *   })
   * };
   *
   * While maintaining identical semantics:
   *
   * greeter.greet("Mitchell").then((reply) => { ... }); // behaves the same
   *
   * @param aTask
   *        The task function to start.
   *
   * @return A function that starts the task function and returns its promise.
   */
  async: function Task_async(aTask) {
    if (typeof(aTask) != "function") {
      throw new TypeError("aTask argument must be a function");
    }

    return createAsyncFunction(aTask);
  },

  /**
   * Constructs a special exception that, when thrown inside a legacy generator
   * function (non-star generator), allows the associated task to be resolved
   * with a specific value.
   *
   * Example: throw new Task.Result("Value");
   */
  Result: function Task_Result(aValue) {
    this.value = aValue;
  }
};

function createAsyncFunction(aTask) {
  let asyncFunction = function() {
    let result = aTask;
    if (aTask && typeof(aTask) == "function") {
      if (aTask.isAsyncFunction) {
        throw new TypeError(
          "Cannot use an async function in place of a promise. " +
          "You should either invoke the async function first " +
          "or use 'Task.spawn' instead of 'Task.async' to start " +
          "the Task and return its promise.");
      }

      try {
        // Let's call into the function ourselves.
        result = aTask.apply(this, arguments);
      } catch (ex) {
        if (ex instanceof Task.Result) {
          return Promise.resolve(ex.value);
        }
        return Promise.reject(ex);
      }
    }

    if (isGenerator(result)) {
      // This is an iterator resulting from calling a generator function.
      return new TaskImpl(result).promise;
    }

    // Just propagate the given value to the caller as a resolved promise.
    return Promise.resolve(result);
  };

  asyncFunction.isAsyncFunction = true;

  return asyncFunction;
}

// TaskImpl

/**
 * Executes the specified iterator as a task, and gives access to the promise
 * that is fulfilled when the task terminates.
 */
function TaskImpl(iterator) {
  if (gMaintainStack) {
    this._stack = (new Error()).stack;
  }
  this.promise = new Promise((resolve, reject) => {
    this._resolve = resolve;
    this._reject = reject;
  });
  this._iterator = iterator;
  this._isStarGenerator = !("send" in iterator);
  this._run(true);
}

TaskImpl.prototype = {
  /**
   * The promise object where task completion callbacks are registered.
   */
  promise: null,

  /**
   * The method to resolve the promise at task completion.
   */
  _resolve: null,

  /**
   * The method to reject the promise at task completion.
   */
  _reject: null,

  /**
   * The iterator returned by the generator function associated with this task.
   */
  _iterator: null,

  /**
   * Whether this Task is using a star generator.
   */
  _isStarGenerator: false,

  /**
   * Main execution routine, that calls into the generator function.
   *
   * @param aSendResolved
   *        If true, indicates that we should continue into the generator
   *        function regularly (if we were waiting on a promise, it was
   *        resolved). If false, indicates that we should cause an exception to
   *        be thrown into the generator function (if we were waiting on a
   *        promise, it was rejected).
   * @param aSendValue
   *        Resolution result or rejection exception, if any.
   */
  _run: function TaskImpl_run(aSendResolved, aSendValue) {

    try {
      gCurrentTask = this;

      if (this._isStarGenerator) {
        if (Cu.isDeadWrapper(this._iterator)) {
          this._resolve(undefined);
        } else {
          try {
            let result = aSendResolved ? this._iterator.next(aSendValue)
                                       : this._iterator.throw(aSendValue);

            if (result.done) {
              // The generator function returned.
              this._resolve(result.value);
            } else {
              // The generator function yielded.
              this._handleResultValue(result.value);
            }
          } catch (ex) {
            // The generator function failed with an uncaught exception.
            this._handleException(ex);
          }
        }
      } else {
        try {
          let yielded = aSendResolved ? this._iterator.send(aSendValue)
                                      : this._iterator.throw(aSendValue);
          this._handleResultValue(yielded);
        } catch (ex) {
          if (ex instanceof Task.Result) {
            // The generator function threw the special exception that allows it to
            // return a specific value on resolution.
            this._resolve(ex.value);
          } else if (ex instanceof StopIteration) {
            // The generator function terminated with no specific result.
            this._resolve(undefined);
          } else {
            // The generator function failed with an uncaught exception.
            this._handleException(ex);
          }
        }
      }
    } finally {
      //
      // At this stage, the Task may have finished executing, or have
      // walked through a `yield` or passed control to a sub-Task.
      // Regardless, if we still own `gCurrentTask`, reset it. If we
      // have not finished execution of this Task, re-entering `_run`
      // will set `gCurrentTask` to `this` as needed.
      //
      // We just need to be careful here in case we hit the following
      // pattern:
      //
      //   Task.spawn(foo);
      //   Task.spawn(bar);
      //
      // Here, `foo` and `bar` may be interleaved, so when we finish
      // executing `foo`, `gCurrentTask` may actually either `foo` or
      // `bar`. If `gCurrentTask` has already been set to `bar`, leave
      // it be and it will be reset to `null` once `bar` is complete.
      //
      if (gCurrentTask == this) {
        gCurrentTask = null;
      }
    }
  },

  /**
   * Handle a value yielded by a generator.
   *
   * @param aValue
   *        The yielded value to handle.
   */
  _handleResultValue: function TaskImpl_handleResultValue(aValue) {
    // If our task yielded an iterator resulting from calling another
    // generator function, automatically spawn a task from it, effectively
    // turning it into a promise that is fulfilled on task completion.
    if (isGenerator(aValue)) {
      aValue = Task.spawn(aValue);
    }

    if (aValue && typeof(aValue.then) == "function") {
      // We have a promise object now. When fulfilled, call again into this
      // function to continue the task, with either a resolution or rejection
      // condition.
      aValue.then(this._run.bind(this, true),
                  this._run.bind(this, false));
    } else {
      // If our task yielded a value that is not a promise, just continue and
      // pass it directly as the result of the yield statement.
      this._run(true, aValue);
    }
  },

  /**
   * Handle an uncaught exception thrown from a generator.
   *
   * @param aException
   *        The uncaught exception to handle.
   */
  _handleException: function TaskImpl_handleException(aException) {

    gCurrentTask = this;

    if (aException && typeof aException == "object" && "stack" in aException) {

      let stack = aException.stack;

      if (gMaintainStack &&
          aException._capturedTaskStack != this._stack &&
          typeof stack == "string") {

        // Rewrite the stack for more readability.

        let bottomStack = this._stack;

        stack = Task.Debugging.generateReadableStack(stack);

        aException.stack = stack;

        // If aException is reinjected in the same task and rethrown,
        // we don't want to perform the rewrite again.
        aException._capturedTaskStack = bottomStack;
      } else if (!stack) {
        stack = "Not available";
      }

      if ("name" in aException &&
          ERRORS_TO_REPORT.indexOf(aException.name) != -1) {

        // We suspect that the exception is a programmer error, so we now
        // display it using dump().  Note that we do not use Cu.reportError as
        // we assume that this is a programming error, so we do not want end
        // users to see it. Also, if the programmer handles errors correctly,
        // they will either treat the error or log them somewhere.

        dump("*************************\n");
        dump("A coding exception was thrown and uncaught in a Task.\n\n");
        dump("Full message: " + aException + "\n");
        dump("Full stack: " + aException.stack + "\n");
        dump("*************************\n");
      }
    }

    this._reject(aException);
  },

  get callerStack() {
    // Cut `this._stack` at the last line of the first block that
    // contains Task.jsm, keep the tail.
    for (let [line, index] of linesOf(this._stack || "")) {
      if (line.indexOf("/Task.jsm:") == -1) {
        return this._stack.substring(index);
      }
    }
    return "";
  }
};


Task.Debugging = {

  /**
   * Control stack rewriting.
   *
   * If `true`, any exception thrown from a Task will be rewritten to
   * provide a human-readable stack trace. Otherwise, stack traces will
   * be left unchanged.
   *
   * There is a (small but existing) runtime cost associated to stack
   * rewriting, so you should probably not activate this in production
   * code.
   *
   * @type {bool}
   */
  get maintainStack() {
    return gMaintainStack;
  },
  set maintainStack(x) {
    if (!x) {
      gCurrentTask = null;
    }
    return gMaintainStack = x;
  },

  /**
   * Generate a human-readable stack for an error raised in
   * a Task.
   *
   * @param {string} topStack The stack provided by the error.
   * @param {string=} prefix Optionally, a prefix for each line.
   */
  generateReadableStack(topStack, prefix = "") {
    if (!gCurrentTask) {
      return topStack;
    }

    // Cut `topStack` at the first line that contains Task.jsm, keep the head.
    let lines = [];
    for (let [line] of linesOf(topStack)) {
      if (line.indexOf("/Task.jsm:") != -1) {
        break;
      }
      lines.push(prefix + line);
    }
    if (!prefix) {
      lines.push(gCurrentTask.callerStack);
    } else {
      for (let [line] of linesOf(gCurrentTask.callerStack)) {
        lines.push(prefix + line);
      }
    }

    return lines.join("\n");
  }
};
PK
!<luYYmodules/TelemetryArchive.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "TelemetryArchive"
];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryArchive::";

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                  "resource://gre/modules/TelemetryStorage.jsm");

this.TelemetryArchive = {
  /**
   * Get a list of the archived pings, sorted by the creation date.
   * Note that scanning the archived pings on disk is delayed on startup,
   * use promizeInitialized() to access this after scanning.
   *
   * @return {Promise<sequence<Object>>}
   *                    A list of the archived ping info in the form:
   *                    { id: <string>,
   *                      timestampCreated: <number>,
   *                      type: <string> }
   */
  promiseArchivedPingList() {
    return TelemetryArchiveImpl.promiseArchivedPingList();
  },

  /**
   * Load an archived ping from disk by id, asynchronously.
   *
   * @param id {String} The pings UUID.
   * @return {Promise<PingData>} A promise resolved with the pings data on success.
   */
  promiseArchivedPingById(id) {
    return TelemetryArchiveImpl.promiseArchivedPingById(id);
  },

  /**
   * Archive a ping and persist it to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  promiseArchivePing(ping) {
    return TelemetryArchiveImpl.promiseArchivePing(ping);
  },
};

/**
 * Checks if pings can be archived. Some products (e.g. Thunderbird) might not want
 * to do that.
 * @return {Boolean} True if pings should be archived, false otherwise.
 */
function shouldArchivePings() {
  return Preferences.get(TelemetryUtils.Preferences.ArchiveEnabled, false);
}

var TelemetryArchiveImpl = {
  _logger: null,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },

  promiseArchivePing(ping) {
    if (!shouldArchivePings()) {
      this._log.trace("promiseArchivePing - archiving is disabled");
      return Promise.resolve();
    }

    for (let field of ["creationDate", "id", "type"]) {
      if (!(field in ping)) {
        this._log.warn("promiseArchivePing - missing field " + field)
        return Promise.reject(new Error("missing field " + field));
      }
    }

    return TelemetryStorage.saveArchivedPing(ping);
  },

  _buildArchivedPingList(archivedPingsMap) {
    let list = Array.from(archivedPingsMap, p => ({
      id: p[0],
      timestampCreated: p[1].timestampCreated,
      type: p[1].type,
    }));

    list.sort((a, b) => a.timestampCreated - b.timestampCreated);

    return list;
  },

  promiseArchivedPingList() {
    this._log.trace("promiseArchivedPingList");

    return TelemetryStorage.loadArchivedPingList().then(loadedInfo => {
      return this._buildArchivedPingList(loadedInfo);
    });
  },

  promiseArchivedPingById(id) {
    this._log.trace("promiseArchivedPingById - id: " + id);
    return TelemetryStorage.loadArchivedPing(id);
  },
};
PK
!<ȱmodules/TelemetryController.jsm/* -*- 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";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const myScope = this;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
Cu.import("resource://gre/modules/DeferredTask.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/AppConstants.jsm");

const Utils = TelemetryUtils;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryController::";

const PREF_BRANCH_LOG = "toolkit.telemetry.log.";

// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false);

const PING_FORMAT_VERSION = 4;

// Delay before intializing telemetry (ms)
const TELEMETRY_DELAY = Services.prefs.getIntPref("toolkit.telemetry.initDelay", 60) * 1000;
// Delay before initializing telemetry if we're testing (ms)
const TELEMETRY_TEST_DELAY = 1;

// How long to wait (ms) before sending the new profile ping on the first
// run of a new profile.
const NEWPROFILE_PING_DEFAULT_DELAY = 30 * 60 * 1000;

// Ping types.
const PING_TYPE_MAIN = "main";
const PING_TYPE_DELETION = "deletion";

// Session ping reasons.
const REASON_GATHER_PAYLOAD = "gather-payload";
const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";

XPCOMUtils.defineLazyModuleGetter(this, "ClientID",
                                  "resource://gre/modules/ClientID.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   "nsITelemetry");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                  "resource://gre/modules/TelemetryStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
                                  "resource://gre/modules/ThirdPartyCookieProbe.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                  "resource://gre/modules/TelemetryEnvironment.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
                                  "resource://gre/modules/TelemetryArchive.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
                                  "resource://gre/modules/TelemetrySession.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                  "resource://gre/modules/TelemetrySend.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                  "resource://gre/modules/TelemetryReportingPolicy.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryModules",
                                  "resource://gre/modules/TelemetryModules.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdatePing",
                                  "resource://gre/modules/UpdatePing.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryHealthPing",
                                  "resource://gre/modules/TelemetryHealthPing.jsm");

/**
 * Setup Telemetry logging. This function also gets called when loggin related
 * preferences change.
 */
var gLogger = null;
var gLogAppenderDump = null;
function configureLogging() {
  if (!gLogger) {
    gLogger = Log.repository.getLogger(LOGGER_NAME);

    // Log messages need to go to the browser console.
    let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
    gLogger.addAppender(consoleAppender);

    Services.prefs.addObserver(PREF_BRANCH_LOG, configureLogging);
  }

  // Make sure the logger keeps up with the logging level preference.
  gLogger.level = Log.Level[Services.prefs.getStringPref(TelemetryUtils.Preferences.LogLevel, "Warn")];

  // If enabled in the preferences, add a dump appender.
  let logDumping = Services.prefs.getBoolPref(TelemetryUtils.Preferences.LogDump, false);
  if (logDumping != !!gLogAppenderDump) {
    if (logDumping) {
      gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
      gLogger.addAppender(gLogAppenderDump);
    } else {
      gLogger.removeAppender(gLogAppenderDump);
      gLogAppenderDump = null;
    }
  }
}

/**
 * This is a policy object used to override behavior for testing.
 */
var Policy = {
  now: () => new Date(),
  generatePingId: () => Utils.generateUUID(),
  getCachedClientID: () => ClientID.getCachedClientID(),
}

this.EXPORTED_SYMBOLS = ["TelemetryController"];

this.TelemetryController = Object.freeze({
  /**
   * Used only for testing purposes.
   */
  testAssemblePing(aType, aPayload, aOptions) {
    return Impl.assemblePing(aType, aPayload, aOptions);
  },

  /**
   * Used only for testing purposes.
   */
  testInitLogging() {
    configureLogging();
  },

  /**
   * Used only for testing purposes.
   */
  testReset() {
    return Impl.reset();
  },

  /**
   * Used only for testing purposes.
   */
  testSetup() {
    return Impl.setupTelemetry(true);
  },

  /**
   * Used only for testing purposes.
   */
  testShutdown() {
    return Impl.shutdown();
  },

  /**
   * Used only for testing purposes.
   */
  testSetupContent() {
    return Impl.setupContentTelemetry(true);
  },

  /**
   * Send a notification.
   */
  observe(aSubject, aTopic, aData) {
    return Impl.observe(aSubject, aTopic, aData);
  },

  /**
   * Submit ping payloads to Telemetry. This will assemble a complete ping, adding
   * environment data, client id and some general info.
   * Depending on configuration, the ping will be sent to the server (immediately or later)
   * and archived locally.
   *
   * To identify the different pings and to be able to query them pings have a type.
   * A type is a string identifier that should be unique to the type ping that is being submitted,
   * it should only contain alphanumeric characters and '-' for separation, i.e. satisfy:
   * /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @returns {Promise} Test-only - a promise that resolves with the ping id once the ping is stored or sent.
   */
  submitExternalPing(aType, aPayload, aOptions = {}) {
    aOptions.addClientId = aOptions.addClientId || false;
    aOptions.addEnvironment = aOptions.addEnvironment || false;
    aOptions.usePingSender = aOptions.usePingSender || false;

    return Impl.submitExternalPing(aType, aPayload, aOptions);
  },

  /**
   * Get the current session ping data as it would be sent out or stored.
   *
   * @param {bool} aSubsession Whether to get subsession data. Optional, defaults to false.
   * @return {object} The current ping data if Telemetry is enabled, null otherwise.
   */
  getCurrentPingData(aSubsession = false) {
    return Impl.getCurrentPingData(aSubsession);
  },

  /**
   * Save a ping to disk.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
   *                  if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  addPendingPing(aType, aPayload, aOptions = {}) {
    let options = aOptions;
    options.addClientId = aOptions.addClientId || false;
    options.addEnvironment = aOptions.addEnvironment || false;
    options.overwrite = aOptions.overwrite || false;

    return Impl.addPendingPing(aType, aPayload, options);
  },

  /**
   * Check if we have an aborted-session ping from a previous session.
   * If so, submit and then remove it.
   *
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  checkAbortedSessionPing() {
    return Impl.checkAbortedSessionPing();
  },

  /**
   * Save an aborted-session ping to disk without adding it to the pending pings.
   *
   * @param {Object} aPayload The ping payload data.
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  saveAbortedSessionPing(aPayload) {
    return Impl.saveAbortedSessionPing(aPayload);
  },

  /**
   * Remove the aborted-session ping if any exists.
   *
   * @return {Promise} Promise that is resolved when the ping was removed.
   */
  removeAbortedSessionPing() {
    return Impl.removeAbortedSessionPing();
  },

  /**
   * Write a ping to a specified location on the disk. Does not add the ping to the
   * pending pings.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {String} aFilePath The path to save the ping to.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
   *                  if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  savePing(aType, aPayload, aFilePath, aOptions = {}) {
    let options = aOptions;
    options.addClientId = aOptions.addClientId || false;
    options.addEnvironment = aOptions.addEnvironment || false;
    options.overwrite = aOptions.overwrite || false;

    return Impl.savePing(aType, aPayload, aFilePath, options);
  },

  /**
   * Allows waiting for TelemetryControllers delayed initialization to complete.
   * The returned promise is guaranteed to resolve before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  promiseInitialized() {
    return Impl.promiseInitialized();
  },
});

var Impl = {
  _initialized: false,
  _initStarted: false, // Whether we started setting up TelemetryController.
  _shuttingDown: false, // Whether the browser is shutting down.
  _shutDown: false, // Whether the browser has shut down.
  _logger: null,
  _prevValues: {},
  // The previous build ID, if this is the first run with a new build.
  // Undefined if this is not the first run, or the previous build ID is unknown.
  _previousBuildID: undefined,
  _clientID: null,
  // A task performing delayed initialization
  _delayedInitTask: null,
  // The deferred promise resolved when the initialization task completes.
  _delayedInitTaskDeferred: null,

  // This is a public barrier Telemetry clients can use to add blockers to the shutdown
  // of TelemetryController.
  // After this barrier, clients can not submit Telemetry pings anymore.
  _shutdownBarrier: new AsyncShutdown.Barrier("TelemetryController: Waiting for clients."),
  // This is a private barrier blocked by pending async ping activity (sending & saving).
  _connectionsBarrier: new AsyncShutdown.Barrier("TelemetryController: Waiting for pending ping activity"),
  // This is true when running in the test infrastructure.
  _testMode: false,
  // The task performing the delayed sending of the "new-profile" ping.
  _delayedNewPingTask: null,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },

  /**
   * Get the data for the "application" section of the ping.
   */
  _getApplicationSection() {
    // Querying architecture and update channel can throw. Make sure to recover and null
    // those fields.
    let arch = null;
    try {
      arch = Services.sysinfo.get("arch");
    } catch (e) {
      this._log.trace("_getApplicationSection - Unable to get system architecture.", e);
    }

    let updateChannel = null;
    try {
      updateChannel = UpdateUtils.getUpdateChannel(false);
    } catch (e) {
      this._log.trace("_getApplicationSection - Unable to get update channel.", e);
    }

    return {
      architecture: arch,
      buildId: Services.appinfo.appBuildID,
      name: Services.appinfo.name,
      version: Services.appinfo.version,
      displayVersion: AppConstants.MOZ_APP_VERSION_DISPLAY,
      vendor: Services.appinfo.vendor,
      platformVersion: Services.appinfo.platformVersion,
      xpcomAbi: Services.appinfo.XPCOMABI,
      channel: updateChannel,
    };
  },

  /**
   * Assemble a complete ping following the common ping format specification.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} aOptions Options object.
   * @param {Boolean} aOptions.addClientId true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   *
   * @returns {Object} An object that contains the assembled ping data.
   */
  assemblePing: function assemblePing(aType, aPayload, aOptions = {}) {
    this._log.trace("assemblePing - Type " + aType + ", aOptions " + JSON.stringify(aOptions));

    // Clone the payload data so we don't race against unexpected changes in subobjects that are
    // still referenced by other code.
    // We can't trust all callers to do this properly on their own.
    let payload = Cu.cloneInto(aPayload, myScope);

    // Fill the common ping fields.
    let pingData = {
      type: aType,
      id: Policy.generatePingId(),
      creationDate: (Policy.now()).toISOString(),
      version: PING_FORMAT_VERSION,
      application: this._getApplicationSection(),
      payload,
    };

    if (aOptions.addClientId) {
      pingData.clientId = this._clientID;
    }

    if (aOptions.addEnvironment) {
      pingData.environment = aOptions.overrideEnvironment || TelemetryEnvironment.currentEnvironment;
    }

    return pingData;
  },

  /**
   * Track any pending ping send and save tasks through the promise passed here.
   * This is needed to block shutdown on any outstanding ping activity.
   */
  _trackPendingPingTask(aPromise) {
    this._connectionsBarrier.client.addBlocker("Waiting for ping task", aPromise);
  },

  /**
   * Internal function to assemble a complete ping, adding environment data, client id
   * and some general info. This waits on the client id to be loaded/generated if it's
   * not yet available. Note that this function is synchronous unless we need to load
   * the client id.
   * Depending on configuration, the ping will be sent to the server (immediately or later)
   * and archived locally.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @returns {Promise} Test-only - a promise that is resolved with the ping id once the ping is stored or sent.
   */
  async _submitPingLogic(aType, aPayload, aOptions) {
    // Make sure to have a clientId if we need one. This cover the case of submitting
    // a ping early during startup, before Telemetry is initialized, if no client id was
    // cached.
    if (!this._clientID && aOptions.addClientId) {
      Telemetry.getHistogramById("TELEMETRY_PING_SUBMISSION_WAITING_CLIENTID").add();
      // We can safely call |getClientID| here and during initialization: we would still
      // spawn and return one single loading task.
      this._clientID = await ClientID.getClientID();
    }

    const pingData = this.assemblePing(aType, aPayload, aOptions);
    this._log.trace("submitExternalPing - ping assembled, id: " + pingData.id);

    // Always persist the pings if we are allowed to. We should not yield on any of the
    // following operations to keep this function synchronous for the majority of the calls.
    let archivePromise = TelemetryArchive.promiseArchivePing(pingData)
      .catch(e => this._log.error("submitExternalPing - Failed to archive ping " + pingData.id, e));
    let p = [ archivePromise ];

    p.push(TelemetrySend.submitPing(pingData, {usePingSender: aOptions.usePingSender}));

    return Promise.all(p).then(() => pingData.id);
  },

  /**
   * Submit ping payloads to Telemetry.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @returns {Promise} Test-only - a promise that is resolved with the ping id once the ping is stored or sent.
   */
  submitExternalPing: function send(aType, aPayload, aOptions) {
    this._log.trace("submitExternalPing - type: " + aType + ", aOptions: " + JSON.stringify(aOptions));

    // Reject pings sent after shutdown.
    if (this._shutDown) {
      const errorMessage = "submitExternalPing - Submission is not allowed after shutdown, discarding ping of type: " + aType;
      this._log.error(errorMessage);
      return Promise.reject(new Error(errorMessage));
    }

    // Enforce the type string to only contain sane characters.
    const typeUuid = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i;
    if (!typeUuid.test(aType)) {
      this._log.error("submitExternalPing - invalid ping type: " + aType);
      let histogram = Telemetry.getKeyedHistogramById("TELEMETRY_INVALID_PING_TYPE_SUBMITTED");
      histogram.add(aType, 1);
      return Promise.reject(new Error("Invalid type string submitted."));
    }
    // Enforce that the payload is an object.
    if (aPayload === null || typeof aPayload !== "object" || Array.isArray(aPayload)) {
      this._log.error("submitExternalPing - invalid payload type: " + typeof aPayload);
      let histogram = Telemetry.getHistogramById("TELEMETRY_INVALID_PAYLOAD_SUBMITTED");
      histogram.add(1);
      return Promise.reject(new Error("Invalid payload type submitted."));
    }

    let promise = this._submitPingLogic(aType, aPayload, aOptions);
    this._trackPendingPingTask(promise);
    return promise;
  },

  /**
   * Save a ping to disk.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} aOptions Options object.
   * @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
   *                  false otherwise.
   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
   *                  environment data.
   * @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  addPendingPing: function addPendingPing(aType, aPayload, aOptions) {
    this._log.trace("addPendingPing - Type " + aType + ", aOptions " + JSON.stringify(aOptions));

    let pingData = this.assemblePing(aType, aPayload, aOptions);

    let savePromise = TelemetryStorage.savePendingPing(pingData);
    let archivePromise = TelemetryArchive.promiseArchivePing(pingData).catch(e => {
      this._log.error("addPendingPing - Failed to archive ping " + pingData.id, e);
    });

    // Wait for both the archiving and ping persistence to complete.
    let promises = [
      savePromise,
      archivePromise,
    ];
    return Promise.all(promises).then(() => pingData.id);
  },

  /**
   * Write a ping to a specified location on the disk. Does not add the ping to the
   * pending pings.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {String} aFilePath The path to save the ping to.
   * @param {Object} aOptions Options object.
   * @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
   *                  false otherwise.
   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
   *                  environment data.
   * @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  savePing: function savePing(aType, aPayload, aFilePath, aOptions) {
    this._log.trace("savePing - Type " + aType + ", File Path " + aFilePath +
                    ", aOptions " + JSON.stringify(aOptions));
    let pingData = this.assemblePing(aType, aPayload, aOptions);
    return TelemetryStorage.savePingToFile(pingData, aFilePath, aOptions.overwrite)
                        .then(() => pingData.id);
  },

  /**
   * Check whether we have an aborted-session ping. If so add it to the pending pings and archive it.
   *
   * @return {Promise} Promise that is resolved when the ping is submitted and archived.
   */
  async checkAbortedSessionPing() {
    let ping = await TelemetryStorage.loadAbortedSessionPing();
    this._log.trace("checkAbortedSessionPing - found aborted-session ping: " + !!ping);
    if (!ping) {
      return;
    }

    try {
      await TelemetryStorage.addPendingPing(ping);
      await TelemetryArchive.promiseArchivePing(ping);
    } catch (e) {
      this._log.error("checkAbortedSessionPing - Unable to add the pending ping", e);
    } finally {
      await TelemetryStorage.removeAbortedSessionPing();
    }
  },

  /**
   * Save an aborted-session ping to disk without adding it to the pending pings.
   *
   * @param {Object} aPayload The ping payload data.
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  saveAbortedSessionPing(aPayload) {
    this._log.trace("saveAbortedSessionPing");
    const options = {addClientId: true, addEnvironment: true};
    const pingData = this.assemblePing(PING_TYPE_MAIN, aPayload, options);
    return TelemetryStorage.saveAbortedSessionPing(pingData);
  },

  removeAbortedSessionPing() {
    return TelemetryStorage.removeAbortedSessionPing();
  },

  /**
   * Perform telemetry initialization for either chrome or content process.
   * @return {Boolean} True if Telemetry is allowed to record at least base (FHR) data,
   *                   false otherwise.
   */
  enableTelemetryRecording: function enableTelemetryRecording() {
    // The thumbnail service also runs in a content process, even with e10s off.
    // We need to check if e10s is on so we don't submit child payloads for it.
    // We still need xpcshell child tests to work, so we skip this if test mode is enabled.
    if (Utils.isContentProcess && !this._testMode && !Services.appinfo.browserTabsRemoteAutostart) {
      this._log.config("enableTelemetryRecording - not enabling Telemetry for non-e10s child process");
      Telemetry.canRecordBase = false;
      Telemetry.canRecordExtended = false;
      return false;
    }

    // Configure base Telemetry recording.
    // Unified Telemetry makes it opt-out. If extended Telemetry is enabled, base recording
    // is always on as well.
    const enabled = Utils.isTelemetryEnabled;
    Telemetry.canRecordBase = enabled || IS_UNIFIED_TELEMETRY;
    Telemetry.canRecordExtended = enabled;

    this._log.config("enableTelemetryRecording - canRecordBase:" + Telemetry.canRecordBase +
                     ", canRecordExtended: " + Telemetry.canRecordExtended);

    return Telemetry.canRecordBase;
  },

  /**
   * This triggers basic telemetry initialization and schedules a full initialized for later
   * for performance reasons.
   *
   * This delayed initialization means TelemetryController init can be in the following states:
   * 1) setupTelemetry was never called
   * or it was called and
   *   2) _delayedInitTask was scheduled, but didn't run yet.
   *   3) _delayedInitTask is currently running.
   *   4) _delayedInitTask finished running and is nulled out.
   *
   * @return {Promise} Resolved when TelemetryController and TelemetrySession are fully
   *                   initialized. This is only used in tests.
   */
  setupTelemetry: function setupTelemetry(testing) {
    this._initStarted = true;
    this._shuttingDown = false;
    this._shutDown = false;
    this._testMode = testing;

    this._log.trace("setupTelemetry");

    if (this._delayedInitTask) {
      this._log.error("setupTelemetry - init task already running");
      return this._delayedInitTaskDeferred.promise;
    }

    if (this._initialized && !this._testMode) {
      this._log.error("setupTelemetry - already initialized");
      return Promise.resolve();
    }

    // This will trigger displaying the datachoices infobar.
    TelemetryReportingPolicy.setup();

    if (!this.enableTelemetryRecording()) {
      this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup.");
      return Promise.resolve();
    }

    this._attachObservers();

    // Perform a lightweight, early initialization for the component, just registering
    // a few observers and initializing the session.
    TelemetrySession.earlyInit(this._testMode);

    // Annotate crash reports so that we get pings for startup crashes
    TelemetrySend.earlyInit();

    // For very short session durations, we may never load the client
    // id from disk.
    // We try to cache it in prefs to avoid this, even though this may
    // lead to some stale client ids.
    this._clientID = ClientID.getCachedClientID();

    // Init the update ping telemetry as early as possible. This won't have
    // an impact on startup.
    UpdatePing.earlyInit();

    // Delay full telemetry initialization to give the browser time to
    // run various late initializers. Otherwise our gathered memory
    // footprint and other numbers would be too optimistic.
    this._delayedInitTaskDeferred = PromiseUtils.defer();
    this._delayedInitTask = new DeferredTask(async () => {
      try {
        // TODO: This should probably happen after all the delayed init here.
        this._initialized = true;
        TelemetryEnvironment.delayedInit();

        // Load the ClientID.
        this._clientID = await ClientID.getClientID();

        await TelemetrySend.setup(this._testMode);

        // Perform TelemetrySession delayed init.
        await TelemetrySession.delayedInit();

        if (Services.prefs.getBoolPref(TelemetryUtils.Preferences.NewProfilePingEnabled, false) &&
            !TelemetrySession.newProfilePingSent) {
          // Kick off the scheduling of the new-profile ping.
          this.scheduleNewProfilePing();
        }

        // Purge the pings archive by removing outdated pings. We don't wait for
        // this task to complete, but TelemetryStorage blocks on it during
        // shutdown.
        TelemetryStorage.runCleanPingArchiveTask();

        // Now that FHR/healthreporter is gone, make sure to remove FHR's DB from
        // the profile directory. This is a temporary measure that we should drop
        // in the future.
        TelemetryStorage.removeFHRDatabase();

        // Report the modules loaded in the Firefox process.
        TelemetryModules.start();

        this._delayedInitTaskDeferred.resolve();
      } catch (e) {
        this._delayedInitTaskDeferred.reject(e);
      } finally {
        this._delayedInitTask = null;
      }
    }, this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY);

    AsyncShutdown.sendTelemetry.addBlocker("TelemetryController: shutting down",
                                           () => this.shutdown(),
                                           () => this._getState());

    this._delayedInitTask.arm();
    return this._delayedInitTaskDeferred.promise;
  },

  /**
   * This triggers basic telemetry initialization for content processes.
   * @param {Boolean} [testing=false] True if we are in test mode, false otherwise.
   */
  setupContentTelemetry(testing = false) {
    this._testMode = testing;

    // We call |enableTelemetryRecording| here to make sure that Telemetry.canRecord* flags
    // are in sync between chrome and content processes.
    if (!this.enableTelemetryRecording()) {
      this._log.trace("setupContentTelemetry - Content process recording disabled.");
      return;
    }
    TelemetrySession.setupContent(testing);
  },

  // Do proper shutdown waiting and cleanup.
  async _cleanupOnShutdown() {
    if (!this._initialized) {
      return;
    }

    this._shuttingDown = true;

    Services.prefs.removeObserver(PREF_BRANCH_LOG, configureLogging);
    this._detachObservers();

    // Now do an orderly shutdown.
    try {
      if (this._delayedNewPingTask) {
        await this._delayedNewPingTask.finalize();
      }

      UpdatePing.shutdown();

      // Stop the datachoices infobar display.
      TelemetryReportingPolicy.shutdown();
      TelemetryEnvironment.shutdown();

      // Stop any ping sending.
      await TelemetrySend.shutdown();

      // Send latest data.
      await TelemetryHealthPing.shutdown();

      await TelemetrySession.shutdown();

      // First wait for clients processing shutdown.
      await this._shutdownBarrier.wait();

      // ... and wait for any outstanding async ping activity.
      await this._connectionsBarrier.wait();

      // Perform final shutdown operations.
      await TelemetryStorage.shutdown();
    } finally {
      // Reset state.
      this._initialized = false;
      this._initStarted = false;
      this._shutDown = true;
    }
  },

  shutdown() {
    this._log.trace("shutdown");

    // We can be in one the following states here:
    // 1) setupTelemetry was never called
    // or it was called and
    //   2) _delayedInitTask was scheduled, but didn't run yet.
    //   3) _delayedInitTask is running now.
    //   4) _delayedInitTask finished running already.

    // This handles 1).
    if (!this._initStarted) {
      this._shuttingDown = true;
      this._shutDown = true;
      return Promise.resolve();
    }

    // This handles 4).
    if (!this._delayedInitTask) {
      // We already ran the delayed initialization.
      return this._cleanupOnShutdown();
    }

    // This handles 2) and 3).
    return this._delayedInitTask.finalize().then(() => this._cleanupOnShutdown());
  },

  /**
   * This observer drives telemetry.
   */
  observe(aSubject, aTopic, aData) {
    // The logger might still be not available at this point.
    if (aTopic == "profile-after-change" || aTopic == "app-startup") {
      // If we don't have a logger, we need to make sure |Log.repository.getLogger()| is
      // called before |getLoggerWithMessagePrefix|. Otherwise logging won't work.
      configureLogging();
    }

    this._log.trace("observe - " + aTopic + " notified.");

    switch (aTopic) {
    case "profile-after-change":
      // profile-after-change is only registered for chrome processes.
      return this.setupTelemetry();
    case "app-startup":
      // app-startup is only registered for content processes.
      return this.setupContentTelemetry();
    case "nsPref:changed":
      if (aData == TelemetryUtils.Preferences.FhrUploadEnabled) {
        return this._onUploadPrefChange();
      }
    }
    return undefined;
  },

  /**
   * Get an object describing the current state of this module for AsyncShutdown diagnostics.
   */
  _getState() {
    return {
      initialized: this._initialized,
      initStarted: this._initStarted,
      haveDelayedInitTask: !!this._delayedInitTask,
      shutdownBarrier: this._shutdownBarrier.state,
      connectionsBarrier: this._connectionsBarrier.state,
      sendModule: TelemetrySend.getShutdownState(),
      haveDelayedNewProfileTask: !!this._delayedNewPingTask,
    };
  },

  /**
   * Called whenever the FHR Upload preference changes (e.g. when user disables FHR from
   * the preferences panel), this triggers sending the deletion ping.
   */
  _onUploadPrefChange() {
    const uploadEnabled = Services.prefs.getBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, false);
    if (uploadEnabled) {
      // There's nothing we should do if we are enabling upload.
      return;
    }

    let p = (async () => {
      try {
        // Clear the current pings.
        await TelemetrySend.clearCurrentPings();

        // Remove all the pending pings, but not the deletion ping.
        await TelemetryStorage.runRemovePendingPingsTask();
      } catch (e) {
        this._log.error("_onUploadPrefChange - error clearing pending pings", e);
      } finally {
        // Always send the deletion ping.
        this._log.trace("_onUploadPrefChange - Sending deletion ping.");
        this.submitExternalPing(PING_TYPE_DELETION, {}, { addClientId: true });
      }
    })();

    this._shutdownBarrier.client.addBlocker(
      "TelemetryController: removing pending pings after data upload was disabled", p);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),

  _attachObservers() {
    if (IS_UNIFIED_TELEMETRY) {
      // Watch the FHR upload setting to trigger deletion pings.
      Services.prefs.addObserver(TelemetryUtils.Preferences.FhrUploadEnabled, this, true);
    }
  },

  /**
   * Remove the preference observer to avoid leaks.
   */
  _detachObservers() {
    if (IS_UNIFIED_TELEMETRY) {
      Services.prefs.removeObserver(TelemetryUtils.Preferences.FhrUploadEnabled, this);
    }
  },

  /**
   * Allows waiting for TelemetryControllers delayed initialization to complete.
   * This will complete before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  promiseInitialized() {
    return this._delayedInitTaskDeferred.promise;
  },

  getCurrentPingData(aSubsession) {
    this._log.trace("getCurrentPingData - subsession: " + aSubsession)

    // Telemetry is disabled, don't gather any data.
    if (!Telemetry.canRecordBase) {
      return null;
    }

    const reason = aSubsession ? REASON_GATHER_SUBSESSION_PAYLOAD : REASON_GATHER_PAYLOAD;
    const type = PING_TYPE_MAIN;
    const payload = TelemetrySession.getPayload(reason);
    const options = { addClientId: true, addEnvironment: true };
    const ping = this.assemblePing(type, payload, options);

    return ping;
  },

  async reset() {
    this._clientID = null;
    this._detachObservers();

    let sessionReset = TelemetrySession.testReset();

    this._connectionsBarrier = new AsyncShutdown.Barrier(
      "TelemetryController: Waiting for pending ping activity"
    );
    this._shutdownBarrier = new AsyncShutdown.Barrier(
      "TelemetryController: Waiting for clients."
    );

    // We need to kick of the controller setup first for tests that check the
    // cached client id.
    let controllerSetup = this.setupTelemetry(true);

    await sessionReset;
    await TelemetrySend.reset();
    await TelemetryStorage.reset();
    await TelemetryEnvironment.testReset();

    await controllerSetup;
  },

  /**
   * Schedule sending the "new-profile" ping.
   */
  scheduleNewProfilePing() {
    this._log.trace("scheduleNewProfilePing");

    const sendDelay =
      Services.prefs.getIntPref(TelemetryUtils.Preferences.NewProfilePingDelay, NEWPROFILE_PING_DEFAULT_DELAY);

    this._delayedNewPingTask = new DeferredTask(async () => {
      try {
        await this.sendNewProfilePing();
      } finally {
        this._delayedNewPingTask = null;
      }
    }, sendDelay);

    this._delayedNewPingTask.arm();
  },

  /**
   * Generate and send the new-profile ping
   */
  async sendNewProfilePing() {
    this._log.trace("sendNewProfilePing - shutting down: " + this._shuttingDown);

    // Generate the payload.
    const payload = {
      "reason": this._shuttingDown ? "shutdown" : "startup",
    };

    // Generate and send the "new-profile" ping. This uses the
    // pingsender if we're shutting down.
    let options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: this._shuttingDown,
    };
    // TODO: we need to be smarter about when to send the ping (and save the
    // state to file). |requestIdleCallback| is currently only accessible
    // through DOM. See bug 1361996.
    await TelemetryController.submitExternalPing("new-profile", payload, options)
                             .then(() => TelemetrySession.markNewProfilePingSent(),
                                   e => this._log.error("sendNewProfilePing - failed to submit new-profile ping", e));
  },
};
PK
!<H)QQ modules/TelemetryEnvironment.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "TelemetryEnvironment",
];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const myScope = this;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/ObjectUtils.jsm");
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/AppConstants.jsm");

const Utils = TelemetryUtils;

const { AddonManager, AddonManagerPrivate } = Cu.import("resource://gre/modules/AddonManager.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "AttributionCode",
                                  "resource:///modules/AttributionCode.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                  "resource://gre/modules/ctypes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                  "resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
                                  "resource://gre/modules/ProfileAge.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                  "resource://gre/modules/WindowsRegistry.jsm");

// The maximum length of a string (e.g. description) in the addons section.
const MAX_ADDON_STRING_LENGTH = 100;
// The maximum length of a string value in the settings.attribution object.
const MAX_ATTRIBUTION_STRING_LENGTH = 100;
// The maximum lengths for the experiment id and branch in the experiments section.
const MAX_EXPERIMENT_ID_LENGTH = 100;
const MAX_EXPERIMENT_BRANCH_LENGTH = 100;
const MAX_EXPERIMENT_TYPE_LENGTH = 20;

/**
 * This is a policy object used to override behavior for testing.
 */
var Policy = {
  now: () => new Date(),
};

// This is used to buffer calls to setExperimentActive and friends, so that we
// don't prematurely initialize our environment if it is called early during
// startup.
var gActiveExperimentStartupBuffer = new Map();

var gGlobalEnvironment;
function getGlobal() {
  if (!gGlobalEnvironment) {
    gGlobalEnvironment = new EnvironmentCache();
  }
  return gGlobalEnvironment;
}

this.TelemetryEnvironment = {
  get currentEnvironment() {
    return getGlobal().currentEnvironment;
  },

  onInitialized() {
    return getGlobal().onInitialized();
  },

  delayedInit() {
    return getGlobal().delayedInit();
  },

  registerChangeListener(name, listener) {
    return getGlobal().registerChangeListener(name, listener);
  },

  unregisterChangeListener(name) {
    return getGlobal().unregisterChangeListener(name);
  },

  /**
   * Add an experiment annotation to the environment.
   * If an annotation with the same id already exists, it will be overwritten.
   * This triggers a new subsession, subject to throttling.
   *
   * @param {String} id The id of the active experiment.
   * @param {String} branch The experiment branch.
   * @param {Object} [options] Optional object with options.
   * @param {String} [options.type=false] The specific experiment type.
   */
  setExperimentActive(id, branch, options = {}) {
    if (gGlobalEnvironment) {
      gGlobalEnvironment.setExperimentActive(id, branch, options);
    } else {
      gActiveExperimentStartupBuffer.set(id, {branch, options});
    }
  },

  /**
   * Remove an experiment annotation from the environment.
   * If the annotation exists, a new subsession will triggered.
   *
   * @param {String} id The id of the active experiment.
   */
  setExperimentInactive(id) {
    if (gGlobalEnvironment) {
      gGlobalEnvironment.setExperimentInactive(id);
    } else {
      gActiveExperimentStartupBuffer.delete(id);
    }
  },

  /**
   * Returns an object containing the data for the active experiments.
   *
   * The returned object is of the format:
   *
   * {
   *   "<experiment id>": { branch: "<branch>" },
   *   // …
   * }
   */
  getActiveExperiments() {
    if (gGlobalEnvironment) {
      return gGlobalEnvironment.getActiveExperiments();
    }

    const result = {};
    for (const [id, {branch}] of gActiveExperimentStartupBuffer.entries()) {
      result[id] = branch;
    }
    return result;
  },

  shutdown() {
    return getGlobal().shutdown();
  },

  // Policy to use when saving preferences. Exported for using them in tests.
  RECORD_PREF_STATE: 1, // Don't record the preference value
  RECORD_PREF_VALUE: 2, // We only record user-set prefs.
  RECORD_DEFAULTPREF_VALUE: 3, // We only record default pref if set

  // Testing method
  testWatchPreferences(prefMap) {
    return getGlobal()._watchPreferences(prefMap);
  },

  /**
   * Intended for use in tests only.
   *
   * In multiple tests we need a way to shut and re-start telemetry together
   * with TelemetryEnvironment. This is problematic due to the fact that
   * TelemetryEnvironment is a singleton. We, therefore, need this helper
   * method to be able to re-set TelemetryEnvironment.
   */
  testReset() {
    return getGlobal().reset();
  },

  /**
   * Intended for use in tests only.
   */
  testCleanRestart() {
    getGlobal().shutdown();
    gGlobalEnvironment = null;
    gActiveExperimentStartupBuffer = new Map();
    return getGlobal();
  },
};

const RECORD_PREF_STATE = TelemetryEnvironment.RECORD_PREF_STATE;
const RECORD_PREF_VALUE = TelemetryEnvironment.RECORD_PREF_VALUE;
const RECORD_DEFAULTPREF_VALUE = TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE;
const DEFAULT_ENVIRONMENT_PREFS = new Map([
  ["app.feedback.baseURL", {what: RECORD_PREF_VALUE}],
  ["app.support.baseURL", {what: RECORD_PREF_VALUE}],
  ["accessibility.browsewithcaret", {what: RECORD_PREF_VALUE}],
  ["accessibility.force_disabled", {what:  RECORD_PREF_VALUE}],
  ["app.update.auto", {what: RECORD_PREF_VALUE}],
  ["app.update.enabled", {what: RECORD_PREF_VALUE}],
  ["app.update.interval", {what: RECORD_PREF_VALUE}],
  ["app.update.service.enabled", {what: RECORD_PREF_VALUE}],
  ["app.update.silent", {what: RECORD_PREF_VALUE}],
  ["app.update.url", {what: RECORD_PREF_VALUE}],
  ["browser.cache.disk.enable", {what: RECORD_PREF_VALUE}],
  ["browser.cache.disk.capacity", {what: RECORD_PREF_VALUE}],
  ["browser.cache.memory.enable", {what: RECORD_PREF_VALUE}],
  ["browser.cache.offline.enable", {what: RECORD_PREF_VALUE}],
  ["browser.formfill.enable", {what: RECORD_PREF_VALUE}],
  ["browser.newtabpage.enabled", {what: RECORD_PREF_VALUE}],
  ["browser.newtabpage.enhanced", {what: RECORD_PREF_VALUE}],
  ["browser.shell.checkDefaultBrowser", {what: RECORD_PREF_VALUE}],
  ["browser.search.ignoredJAREngines", {what: RECORD_DEFAULTPREF_VALUE}],
  ["browser.search.suggest.enabled", {what: RECORD_PREF_VALUE}],
  ["browser.startup.homepage", {what: RECORD_PREF_STATE}],
  ["browser.startup.page", {what: RECORD_PREF_VALUE}],
  ["toolkit.cosmeticAnimations.enabled", {what: RECORD_PREF_VALUE}],
  ["browser.urlbar.suggest.searches", {what: RECORD_PREF_VALUE}],
  ["browser.urlbar.userMadeSearchSuggestionsChoice", {what: RECORD_PREF_VALUE}],
  ["devtools.chrome.enabled", {what: RECORD_PREF_VALUE}],
  ["devtools.debugger.enabled", {what: RECORD_PREF_VALUE}],
  ["devtools.debugger.remote-enabled", {what: RECORD_PREF_VALUE}],
  ["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
  ["dom.ipc.processCount", {what: RECORD_PREF_VALUE}],
  ["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
  ["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
  ["extensions.allow-non-mpc-extensions", {what: RECORD_PREF_VALUE}],
  ["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}],
  ["extensions.enabledScopes", {what: RECORD_PREF_VALUE}],
  ["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}],
  ["extensions.blocklist.url", {what: RECORD_PREF_VALUE}],
  ["extensions.formautofill.addresses.enabled", {what: RECORD_PREF_VALUE}],
  ["extensions.formautofill.creditCards.enabled", {what: RECORD_PREF_VALUE}],
  ["extensions.strictCompatibility", {what: RECORD_PREF_VALUE}],
  ["extensions.update.enabled", {what: RECORD_PREF_VALUE}],
  ["extensions.update.url", {what: RECORD_PREF_VALUE}],
  ["extensions.update.background.url", {what: RECORD_PREF_VALUE}],
  ["extensions.screenshots.disabled", {what: RECORD_PREF_VALUE}],
  ["extensions.screenshots.system-disabled", {what: RECORD_PREF_VALUE}],
  ["general.smoothScroll", {what: RECORD_PREF_VALUE}],
  ["gfx.direct2d.disabled", {what: RECORD_PREF_VALUE}],
  ["gfx.direct2d.force-enabled", {what: RECORD_PREF_VALUE}],
  ["gfx.direct2d.use1_1", {what: RECORD_PREF_VALUE}],
  ["layers.acceleration.disabled", {what: RECORD_PREF_VALUE}],
  ["layers.acceleration.force-enabled", {what: RECORD_PREF_VALUE}],
  ["layers.async-pan-zoom.enabled", {what: RECORD_PREF_VALUE}],
  ["layers.async-video-oop.enabled", {what: RECORD_PREF_VALUE}],
  ["layers.async-video.enabled", {what: RECORD_PREF_VALUE}],
  ["layers.componentalpha.enabled", {what: RECORD_PREF_VALUE}],
  ["layers.d3d11.disable-warp", {what: RECORD_PREF_VALUE}],
  ["layers.d3d11.force-warp", {what: RECORD_PREF_VALUE}],
  ["layers.offmainthreadcomposition.force-disabled", {what: RECORD_PREF_VALUE}],
  ["layers.prefer-d3d9", {what: RECORD_PREF_VALUE}],
  ["layers.prefer-opengl", {what: RECORD_PREF_VALUE}],
  ["layout.css.devPixelsPerPx", {what: RECORD_PREF_VALUE}],
  ["layout.css.servo.enabled", {what: RECORD_PREF_VALUE}],
  ["network.proxy.autoconfig_url", {what: RECORD_PREF_STATE}],
  ["network.proxy.http", {what: RECORD_PREF_STATE}],
  ["network.proxy.ssl", {what: RECORD_PREF_STATE}],
  ["pdfjs.disabled", {what: RECORD_PREF_VALUE}],
  ["places.history.enabled", {what: RECORD_PREF_VALUE}],
  ["plugins.remember_infobar_dismissal", {what: RECORD_PREF_VALUE}],
  ["plugins.show_infobar", {what: RECORD_PREF_VALUE}],
  ["privacy.trackingprotection.enabled", {what: RECORD_PREF_VALUE}],
  ["privacy.donottrackheader.enabled", {what: RECORD_PREF_VALUE}],
  ["security.mixed_content.block_active_content", {what: RECORD_PREF_VALUE}],
  ["security.mixed_content.block_display_content", {what: RECORD_PREF_VALUE}],
  ["xpinstall.signatures.required", {what: RECORD_PREF_VALUE}],
]);

const LOGGER_NAME = "Toolkit.Telemetry";

const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
const PREF_DISTRIBUTION_ID = "distribution.id";
const PREF_DISTRIBUTION_VERSION = "distribution.version";
const PREF_DISTRIBUTOR = "app.distributor";
const PREF_DISTRIBUTOR_CHANNEL = "app.distributor.channel";
const PREF_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion";
const PREF_APP_PARTNER_BRANCH = "app.partner.";
const PREF_PARTNER_ID = "mozilla.partner.id";
const PREF_UPDATE_ENABLED = "app.update.enabled";
const PREF_UPDATE_AUTODOWNLOAD = "app.update.auto";
const PREF_SEARCH_COHORT = "browser.search.cohort";
const PREF_E10S_COHORT = "e10s.rollout.cohort";

const COMPOSITOR_CREATED_TOPIC = "compositor:created";
const COMPOSITOR_PROCESS_ABORTED_TOPIC = "compositor:process-aborted";
const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete";
const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
const SEARCH_SERVICE_TOPIC = "browser-search-service";
const SESSIONSTORE_WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
const PREF_CHANGED_TOPIC = "nsPref:changed";

/**
 * Enforces the parameter to a boolean value.
 * @param aValue The input value.
 * @return {Boolean|Object} If aValue is a boolean or a number, returns its truthfulness
 *         value. Otherwise, return null.
 */
function enforceBoolean(aValue) {
  if (typeof(aValue) !== "number" && typeof(aValue) !== "boolean") {
    return null;
  }
  return (new Boolean(aValue)).valueOf();
}

/**
 * Get the current browser locale.
 * @return a string with the locale or null on failure.
 */
function getBrowserLocale() {
  try {
    return Services.locale.getAppLocaleAsLangTag();
  } catch (e) {
    return null;
  }
}

/**
 * Get the current OS locale.
 * @return a string with the OS locale or null on failure.
 */
function getSystemLocale() {
  try {
    return Cc["@mozilla.org/intl/ospreferences;1"].
             getService(Ci.mozIOSPreferences).
             systemLocale;
  } catch (e) {
    return null;
  }
}

/**
 * Safely get a sysinfo property and return its value. If the property is not
 * available, return aDefault.
 *
 * @param aPropertyName the property name to get.
 * @param aDefault the value to return if aPropertyName is not available.
 * @return The property value, if available, or aDefault.
 */
function getSysinfoProperty(aPropertyName, aDefault) {
  try {
    // |getProperty| may throw if |aPropertyName| does not exist.
    return Services.sysinfo.getProperty(aPropertyName);
  } catch (e) {}

  return aDefault;
}

/**
 * Safely get a gfxInfo field and return its value. If the field is not available, return
 * aDefault.
 *
 * @param aPropertyName the property name to get.
 * @param aDefault the value to return if aPropertyName is not available.
 * @return The property value, if available, or aDefault.
 */
function getGfxField(aPropertyName, aDefault) {
  let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);

  try {
    // Accessing the field may throw if |aPropertyName| does not exist.
    let gfxProp = gfxInfo[aPropertyName];
    if (gfxProp !== undefined && gfxProp !== "") {
      return gfxProp;
    }
  } catch (e) {}

  return aDefault;
}

/**
 * Returns a substring of the input string.
 *
 * @param {String} aString The input string.
 * @param {Integer} aMaxLength The maximum length of the returned substring. If this is
 *        greater than the length of the input string, we return the whole input string.
 * @return {String} The substring or null if the input string is null.
 */
function limitStringToLength(aString, aMaxLength) {
  if (typeof(aString) !== "string") {
    return null;
  }
  return aString.substring(0, aMaxLength);
}

/**
 * Force a value to be a string.
 * Only if the value is null, null is returned instead.
 */
function forceToStringOrNull(aValue) {
  if (aValue === null) {
    return null;
  }

  return String(aValue);
}

/**
 * Get the information about a graphic adapter.
 *
 * @param aSuffix A suffix to add to the properties names.
 * @return An object containing the adapter properties.
 */
function getGfxAdapter(aSuffix = "") {
  // Note that gfxInfo, and so getGfxField, might return "Unknown" for the RAM on failures,
  // not null.
  let memoryMB = parseInt(getGfxField("adapterRAM" + aSuffix, null), 10);
  if (Number.isNaN(memoryMB)) {
    memoryMB = null;
  }

  return {
    description: getGfxField("adapterDescription" + aSuffix, null),
    vendorID: getGfxField("adapterVendorID" + aSuffix, null),
    deviceID: getGfxField("adapterDeviceID" + aSuffix, null),
    subsysID: getGfxField("adapterSubsysID" + aSuffix, null),
    RAM: memoryMB,
    driver: getGfxField("adapterDriver" + aSuffix, null),
    driverVersion: getGfxField("adapterDriverVersion" + aSuffix, null),
    driverDate: getGfxField("adapterDriverDate" + aSuffix, null),
  };
}

/**
 * Gets the service pack and build information on Windows platforms. The initial version
 * was copied from nsUpdateService.js.
 *
 * @return An object containing the service pack major and minor versions, along with the
 *         build number.
 */
function getWindowsVersionInfo() {
  const UNKNOWN_VERSION_INFO = {servicePackMajor: null, servicePackMinor: null, buildNumber: null};

  if (AppConstants.platform !== "win") {
    return UNKNOWN_VERSION_INFO;
  }

  const BYTE = ctypes.uint8_t;
  const WORD = ctypes.uint16_t;
  const DWORD = ctypes.uint32_t;
  const WCHAR = ctypes.char16_t;
  const BOOL = ctypes.int;

  // This structure is described at:
  // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
  const SZCSDVERSIONLENGTH = 128;
  const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW",
      [
      {dwOSVersionInfoSize: DWORD},
      {dwMajorVersion: DWORD},
      {dwMinorVersion: DWORD},
      {dwBuildNumber: DWORD},
      {dwPlatformId: DWORD},
      {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
      {wServicePackMajor: WORD},
      {wServicePackMinor: WORD},
      {wSuiteMask: WORD},
      {wProductType: BYTE},
      {wReserved: BYTE}
      ]);

  let kernel32 = ctypes.open("kernel32");
  try {
    let GetVersionEx = kernel32.declare("GetVersionExW",
                                        ctypes.winapi_abi,
                                        BOOL,
                                        OSVERSIONINFOEXW.ptr);
    let winVer = OSVERSIONINFOEXW();
    winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;

    if (0 === GetVersionEx(winVer.address())) {
      throw ("Failure in GetVersionEx (returned 0)");
    }

    return {
      servicePackMajor: winVer.wServicePackMajor,
      servicePackMinor: winVer.wServicePackMinor,
      buildNumber: winVer.dwBuildNumber,
    };
  } catch (e) {
    return UNKNOWN_VERSION_INFO;
  } finally {
    kernel32.close();
  }
}

/**
 * Encapsulates the asynchronous magic interfacing with the addon manager. The builder
 * is owned by a parent environment object and is an addon listener.
 */
function EnvironmentAddonBuilder(environment) {
  this._environment = environment;

  // The pending task blocks addon manager shutdown. It can either be the initial load
  // or a change load.
  this._pendingTask = null;

  // Set to true once initial load is complete and we're watching for changes.
  this._loaded = false;
}
EnvironmentAddonBuilder.prototype = {
  /**
   * Get the initial set of addons.
   * @returns Promise<void> when the initial load is complete.
   */
  init() {
    // Some tests don't initialize the addon manager. This accounts for the
    // unfortunate reality of life.
    try {
      AddonManager.shutdown.addBlocker("EnvironmentAddonBuilder",
        () => this._shutdownBlocker());
    } catch (err) {
      return Promise.reject(err);
    }

    this._pendingTask = (async () => {
      try {
        // Gather initial addons details
        await this._updateAddons();

        if (!AddonManagerPrivate.isDBLoaded()) {
          // The addon database has not been loaded, so listen for the event
          // triggered by the AddonManager when it is loaded so we can
          // immediately gather full data at that time.
          await new Promise(resolve => {
            const ADDON_LOAD_NOTIFICATION = "xpi-database-loaded";
            Services.obs.addObserver({
              observe(subject, topic, data) {
                Services.obs.removeObserver(this, ADDON_LOAD_NOTIFICATION);
                resolve();
              },
            }, ADDON_LOAD_NOTIFICATION);
          });

          // Now gather complete addons details.
          await this._updateAddons();
        }
      } catch (err) {
        this._environment._log.error("init - Exception in _updateAddons", err);
      } finally {
        this._pendingTask = null;
      }
    })();

    return this._pendingTask;
  },

  /**
   * Register an addon listener and watch for changes.
   */
  watchForChanges() {
    this._loaded = true;
    AddonManager.addAddonListener(this);
    Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC);
  },

  // AddonListener
  onEnabled() {
    this._onAddonChange();
  },
  onDisabled() {
    this._onAddonChange();
  },
  onInstalled() {
    this._onAddonChange();
  },
  onUninstalling() {
    this._onAddonChange();
  },

  _onAddonChange() {
    this._environment._log.trace("_onAddonChange");
    this._checkForChanges("addons-changed");
  },

  // nsIObserver
  observe(aSubject, aTopic, aData) {
    this._environment._log.trace("observe - Topic " + aTopic);
    this._checkForChanges("experiment-changed");
  },

  _checkForChanges(changeReason) {
    if (this._pendingTask) {
      this._environment._log.trace("_checkForChanges - task already pending, dropping change with reason " + changeReason);
      return;
    }

    this._pendingTask = this._updateAddons().then(
      (result) => {
        this._pendingTask = null;
        if (result.changed) {
          this._environment._onEnvironmentChange(changeReason, result.oldEnvironment);
        }
      },
      (err) => {
        this._pendingTask = null;
        this._environment._log.error("_checkForChanges: Error collecting addons", err);
      });
  },

  _shutdownBlocker() {
    if (this._loaded) {
      AddonManager.removeAddonListener(this);
      Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC);
    }

    // At startup, _pendingTask is set to a Promise that does not resolve
    // until the addons database has been read so complete details about
    // addons are available.  Returning it here will cause it to block
    // profileBeforeChange, guranteeing that full information will be
    // available by the time profileBeforeChangeTelemetry is fired.
    return this._pendingTask;
  },

  /**
   * Collect the addon data for the environment.
   *
   * This should only be called from _pendingTask; otherwise we risk
   * running this during addon manager shutdown.
   *
   * @returns Promise<Object> This returns a Promise resolved with a status object with the following members:
   *   changed - Whether the environment changed.
   *   oldEnvironment - Only set if a change occured, contains the environment data before the change.
   */
  async _updateAddons() {
    this._environment._log.trace("_updateAddons");
    let personaId = null;
    let theme = LightweightThemeManager.currentTheme;
    if (theme) {
      personaId = theme.id;
    }

    let addons = {
      activeAddons: await this._getActiveAddons(),
      theme: await this._getActiveTheme(),
      activePlugins: this._getActivePlugins(),
      activeGMPlugins: await this._getActiveGMPlugins(),
      activeExperiment: this._getActiveExperiment(),
      persona: personaId,
    };

    let result = {
      changed: !this._environment._currentEnvironment.addons ||
               !ObjectUtils.deepEqual(addons, this._environment._currentEnvironment.addons),
    };

    if (result.changed) {
      this._environment._log.trace("_updateAddons: addons differ");
      result.oldEnvironment = Cu.cloneInto(this._environment._currentEnvironment, myScope);
      this._environment._currentEnvironment.addons = addons;
    }

    return result;
  },

  /**
   * Get the addon data in object form.
   * @return Promise<object> containing the addon data.
   */
  async _getActiveAddons() {
    // Request addons, asynchronously.
    let allAddons = await AddonManager.getActiveAddons(["extension", "service"]);

    let isDBLoaded = AddonManagerPrivate.isDBLoaded();
    let activeAddons = {};
    for (let addon of allAddons) {
      // Weird addon data in the wild can lead to exceptions while collecting
      // the data.
      try {
        // Make sure to have valid dates.
        let updateDate = new Date(Math.max(0, addon.updateDate));

        activeAddons[addon.id] = {
          version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
          scope: addon.scope,
          type: addon.type,
          updateDay: Utils.millisecondsToDays(updateDate.getTime()),
          isSystem: addon.isSystem,
          isWebExtension: addon.isWebExtension,
          multiprocessCompatible: Boolean(addon.multiprocessCompatible),
        };

        // getActiveAddons() gives limited data during startup and full
        // data after the addons database is loaded.
        if (isDBLoaded) {
          let installDate = new Date(Math.max(0, addon.installDate));
          Object.assign(activeAddons[addon.id], {
            blocklisted: (addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
            description: limitStringToLength(addon.description, MAX_ADDON_STRING_LENGTH),
            name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
            userDisabled: enforceBoolean(addon.userDisabled),
            appDisabled: addon.appDisabled,
            foreignInstall: enforceBoolean(addon.foreignInstall),
            hasBinaryComponents: addon.hasBinaryComponents,
            installDay: Utils.millisecondsToDays(installDate.getTime()),
            signedState: addon.signedState,
          });
        }
      } catch (ex) {
        this._environment._log.error("_getActiveAddons - An addon was discarded due to an error", ex);
        continue;
      }
    }

    return activeAddons;
  },

  /**
   * Get the currently active theme data in object form.
   * @return Promise<object> containing the active theme data.
   */
  async _getActiveTheme() {
    // Request themes, asynchronously.
    let themes = await AddonManager.getActiveAddons(["theme"]);

    let activeTheme = {};
    // We only store information about the active theme.
    let theme = themes.find(theme => theme.isActive);
    if (theme) {
      // Make sure to have valid dates.
      let installDate = new Date(Math.max(0, theme.installDate));
      let updateDate = new Date(Math.max(0, theme.updateDate));

      activeTheme = {
        id: theme.id,
        blocklisted: (theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
        description: limitStringToLength(theme.description, MAX_ADDON_STRING_LENGTH),
        name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
        userDisabled: enforceBoolean(theme.userDisabled),
        appDisabled: theme.appDisabled,
        version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
        scope: theme.scope,
        foreignInstall: enforceBoolean(theme.foreignInstall),
        hasBinaryComponents: theme.hasBinaryComponents,
        installDay: Utils.millisecondsToDays(installDate.getTime()),
        updateDay: Utils.millisecondsToDays(updateDate.getTime()),
      };
    }

    return activeTheme;
  },

  /**
   * Get the plugins data in object form.
   * @return Object containing the plugins data.
   */
  _getActivePlugins() {
    let pluginTags =
      Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost).getPluginTags({});

    let activePlugins = [];
    for (let tag of pluginTags) {
      // Skip plugins which are not active.
      if (tag.disabled) {
        continue;
      }

      try {
        // Make sure to have a valid date.
        let updateDate = new Date(Math.max(0, tag.lastModifiedTime));

        activePlugins.push({
          name: limitStringToLength(tag.name, MAX_ADDON_STRING_LENGTH),
          version: limitStringToLength(tag.version, MAX_ADDON_STRING_LENGTH),
          description: limitStringToLength(tag.description, MAX_ADDON_STRING_LENGTH),
          blocklisted: tag.blocklisted,
          disabled: tag.disabled,
          clicktoplay: tag.clicktoplay,
          mimeTypes: tag.getMimeTypes({}),
          updateDay: Utils.millisecondsToDays(updateDate.getTime()),
        });
      } catch (ex) {
        this._environment._log.error("_getActivePlugins - A plugin was discarded due to an error", ex);
        continue;
      }
    }

    return activePlugins;
  },

  /**
   * Get the GMPlugins data in object form.
   * @return Object containing the GMPlugins data.
   *
   * This should only be called from _pendingTask; otherwise we risk
   * running this during addon manager shutdown.
   */
  async _getActiveGMPlugins() {
    // Request plugins, asynchronously.
    let allPlugins = await AddonManager.getAddonsByTypes(["plugin"]);

    let activeGMPlugins = {};
    for (let plugin of allPlugins) {
      // Only get info for active GMplugins.
      if (!plugin.isGMPlugin || !plugin.isActive) {
        continue;
      }

      try {
        activeGMPlugins[plugin.id] = {
          version: plugin.version,
          userDisabled: enforceBoolean(plugin.userDisabled),
          applyBackgroundUpdates: plugin.applyBackgroundUpdates,
        };
      } catch (ex) {
        this._environment._log.error("_getActiveGMPlugins - A GMPlugin was discarded due to an error", ex);
        continue;
      }
    }

    return activeGMPlugins;
  },

  /**
   * Get the active experiment data in object form.
   * @return Object containing the active experiment data.
   */
  _getActiveExperiment() {
    let experimentInfo = {};
    try {
      let scope = {};
      Cu.import("resource:///modules/experiments/Experiments.jsm", scope);
      let experiments = scope.Experiments.instance();
      let activeExperiment = experiments.getActiveExperimentID();
      if (activeExperiment) {
        experimentInfo.id = activeExperiment;
        experimentInfo.branch = experiments.getActiveExperimentBranch();
      }
    } catch (e) {
      // If this is not Firefox, the import will fail.
    }

    return experimentInfo;
  },
};

function EnvironmentCache() {
  this._log = Log.repository.getLoggerWithMessagePrefix(
    LOGGER_NAME, "TelemetryEnvironment::");
  this._log.trace("constructor");

  this._shutdown = false;
  this._delayedInitFinished = false;
  // Don't allow querying the search service too early to prevent
  // impacting the startup performance.
  this._canQuerySearch = false;
  // To guard against slowing down startup, defer gathering heavy environment
  // entries until the session is restored.
  this._sessionWasRestored = false;

  // A map of listeners that will be called on environment changes.
  this._changeListeners = new Map();

  // A map of watched preferences which trigger an Environment change when
  // modified. Every entry contains a recording policy (RECORD_PREF_*).
  this._watchedPrefs = DEFAULT_ENVIRONMENT_PREFS;

  this._currentEnvironment = {
    build: this._getBuild(),
    partner: this._getPartner(),
    system: this._getSystem(),
  };

  this._updateSettings();
  this._addObservers();

  // Build the remaining asynchronous parts of the environment. Don't register change listeners
  // until the initial environment has been built.

  let p = [];
  this._addonBuilder = new EnvironmentAddonBuilder(this);
  p = [ this._addonBuilder.init() ];

  this._currentEnvironment.profile = {};
  p.push(this._updateProfile());
  if (AppConstants.MOZ_BUILD_APP == "browser") {
    p.push(this._updateAttribution());
  }

  for (const [id, {branch, options}] of gActiveExperimentStartupBuffer.entries()) {
    this.setExperimentActive(id, branch, options);
  }
  gActiveExperimentStartupBuffer = null;

  let setup = () => {
    this._initTask = null;
    this._startWatchingPrefs();
    this._addonBuilder.watchForChanges();
    this._updateGraphicsFeatures();
    return this.currentEnvironment;
  };

  this._initTask = Promise.all(p)
    .then(
      () => setup(),
      (err) => {
        // log errors but eat them for consumers
        this._log.error("EnvironmentCache - error while initializing", err);
        return setup();
      });
}
EnvironmentCache.prototype = {
  /**
   * The current environment data. The returned data is cloned to avoid
   * unexpected sharing or mutation.
   * @returns object
   */
  get currentEnvironment() {
    return Cu.cloneInto(this._currentEnvironment, myScope);
  },

  /**
   * Wait for the current enviroment to be fully initialized.
   * @returns Promise<object>
   */
  onInitialized() {
    if (this._initTask) {
      return this._initTask;
    }
    return Promise.resolve(this.currentEnvironment);
  },

  /**
   * This gets called when the delayed init completes.
   */
  delayedInit() {
    this._delayedInitFinished = true;
  },

  /**
   * Register a listener for environment changes.
   * @param name The name of the listener. If a new listener is registered
   *             with the same name, the old listener will be replaced.
   * @param listener function(reason, oldEnvironment) - Will receive a reason for
                     the change and the environment data before the change.
   */
  registerChangeListener(name, listener) {
    this._log.trace("registerChangeListener for " + name);
    if (this._shutdown) {
      this._log.warn("registerChangeListener - already shutdown");
      return;
    }
    this._changeListeners.set(name, listener);
  },

  /**
   * Unregister from listening to environment changes.
   * It's fine to call this on an unitialized TelemetryEnvironment.
   * @param name The name of the listener to remove.
   */
  unregisterChangeListener(name) {
    this._log.trace("unregisterChangeListener for " + name);
    if (this._shutdown) {
      this._log.warn("registerChangeListener - already shutdown");
      return;
    }
    this._changeListeners.delete(name);
  },

  setExperimentActive(id, branch, options) {
    this._log.trace("setExperimentActive");
    // Make sure both the id and the branch have sane lengths.
    const saneId = limitStringToLength(id, MAX_EXPERIMENT_ID_LENGTH);
    const saneBranch = limitStringToLength(branch, MAX_EXPERIMENT_BRANCH_LENGTH);
    if (!saneId || !saneBranch) {
      this._log.error("setExperimentActive - the provided arguments are not strings.");
      return;
    }

    // Warn the user about any content truncation.
    if (saneId.length != id.length || saneBranch.length != branch.length) {
      this._log.warn("setExperimentActive - the experiment id or branch were truncated.");
    }

    // Truncate the experiment type if present.
    if (options.hasOwnProperty("type")) {
      let type = limitStringToLength(options.type, MAX_EXPERIMENT_TYPE_LENGTH);
      if (type.length != options.type.length) {
        options.type = type;
        this._log.warn("setExperimentActive - the experiment type was truncated.");
      }
    }

    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
    // Add the experiment annotation.
    let experiments = this._currentEnvironment.experiments || {};
    experiments[saneId] = { "branch": saneBranch };
    if (options.hasOwnProperty("type")) {
      experiments[saneId].type = options.type;
    }
    this._currentEnvironment.experiments = experiments;
    // Notify of the change.
    this._onEnvironmentChange("experiment-annotation-changed", oldEnvironment);
  },

  setExperimentInactive(id) {
    this._log.trace("setExperimentInactive");
    let experiments = this._currentEnvironment.experiments || {};
    if (id in experiments) {
      // Only attempt to notify if a previous annotation was found and removed.
      let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
      // Remove the experiment annotation.
      delete this._currentEnvironment.experiments[id];
      // Notify of the change.
      this._onEnvironmentChange("experiment-annotation-changed", oldEnvironment);
    }
  },

  getActiveExperiments() {
    return Cu.cloneInto(this._currentEnvironment.experiments || {}, myScope);
  },

  shutdown() {
    this._log.trace("shutdown");
    this._shutdown = true;
  },

  /**
   * Only used in tests, set the preferences to watch.
   * @param aPreferences A map of preferences names and their recording policy.
   */
  _watchPreferences(aPreferences) {
    this._stopWatchingPrefs();
    this._watchedPrefs = aPreferences;
    this._updateSettings();
    this._startWatchingPrefs();
  },

  /**
   * Get an object containing the values for the watched preferences. Depending on the
   * policy, the value for a preference or whether it was changed by user is reported.
   *
   * @return An object containing the preferences values.
   */
  _getPrefData() {
    let prefData = {};
    for (let [pref, policy] of this._watchedPrefs.entries()) {
      let prefType = Services.prefs.getPrefType(pref);

      if (policy.what == TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE) {
        // For default prefs, make sure they exist
        if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
          continue;
        }
      } else if (!Services.prefs.prefHasUserValue(pref)) {
        // For user prefs, make sure they are set
        continue;
      }

      // Check the policy for the preference and decide if we need to store its value
      // or whether it changed from the default value.
      let prefValue;
      if (policy.what == TelemetryEnvironment.RECORD_PREF_STATE) {
        prefValue = "<user-set>";
      } else if (prefType == Ci.nsIPrefBranch.PREF_STRING) {
        prefValue = Services.prefs.getStringPref(pref);
      } else if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
        prefValue = Services.prefs.getBoolPref(pref);
      } else if (prefType == Ci.nsIPrefBranch.PREF_INT) {
        prefValue = Services.prefs.getIntPref(pref);
      } else if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
        prefValue = null;
      } else {
        throw new Error(`Unexpected preference type ("${prefType}") for "${pref}".`);
      }
      prefData[pref] = prefValue;
    }
    return prefData;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),

  /**
   * Start watching the preferences.
   */
  _startWatchingPrefs() {
    this._log.trace("_startWatchingPrefs - " + this._watchedPrefs);

    for (let [pref, options] of this._watchedPrefs) {
      if (!("requiresRestart" in options) || !options.requiresRestart) {
        Services.prefs.addObserver(pref, this, true);
      }
    }
  },

  _onPrefChanged() {
    this._log.trace("_onPrefChanged");
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
    this._updateSettings();
    this._onEnvironmentChange("pref-changed", oldEnvironment);
  },

  /**
   * Do not receive any more change notifications for the preferences.
   */
  _stopWatchingPrefs() {
    this._log.trace("_stopWatchingPrefs");

    for (let [pref, options] of this._watchedPrefs) {
      if (!("requiresRestart" in options) || !options.requiresRestart) {
        Services.prefs.removeObserver(pref, this);
      }
    }
  },

  _addObservers() {
    // Watch the search engine change and service topics.
    Services.obs.addObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
    Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC);
    Services.obs.addObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
    Services.obs.addObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
    Services.obs.addObserver(this, GFX_FEATURES_READY_TOPIC);
    Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
    Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC);
  },

  _removeObservers() {
    Services.obs.removeObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
    Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC);
    Services.obs.removeObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
    try {
      Services.obs.removeObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
    } catch (ex) {}
    Services.obs.removeObserver(this, GFX_FEATURES_READY_TOPIC);
    Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
    Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC);
  },

  observe(aSubject, aTopic, aData) {
    this._log.trace("observe - aTopic: " + aTopic + ", aData: " + aData);
    switch (aTopic) {
      case SEARCH_ENGINE_MODIFIED_TOPIC:
        if (aData != "engine-current") {
          return;
        }
        // Record the new default search choice and send the change notification.
        this._onSearchEngineChange();
        break;
      case SEARCH_SERVICE_TOPIC:
        if (aData != "init-complete") {
          return;
        }
        // Now that the search engine init is complete, record the default search choice.
        this._canQuerySearch = true;
        this._updateSearchEngine();
        break;
      case GFX_FEATURES_READY_TOPIC:
      case COMPOSITOR_CREATED_TOPIC:
        // Full graphics information is not available until we have created at
        // least one off-main-thread-composited window. Thus we wait for the
        // first compositor to be created and then query nsIGfxInfo again.
        this._updateGraphicsFeatures();
        break;
      case COMPOSITOR_PROCESS_ABORTED_TOPIC:
        // Our compositor process has been killed for whatever reason, so refresh
        // our reported graphics features and trigger an environment change.
        this._onCompositorProcessAborted();
        break;
      case DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC:
        // Distribution customizations are applied after final-ui-startup. query
        // partner prefs again when they are ready.
        this._updatePartner();
        Services.obs.removeObserver(this, aTopic);
        break;
      case SESSIONSTORE_WINDOWS_RESTORED_TOPIC:
        this._sessionWasRestored = true;
        // Make sure to initialize the search service once we've done restoring
        // the windows, so that we don't risk loosing search data.
        Services.search.init();
        // The default browser check could take some time, so just call it after
        // the session was restored.
        this._updateDefaultBrowser();
        break;
      case PREF_CHANGED_TOPIC:
        if (this._watchedPrefs.has(aData)) {
          this._onPrefChanged();
        }
        break;
    }
  },

  /**
   * Get the default search engine.
   * @return {String} Returns the search engine identifier, "NONE" if no default search
   *         engine is defined or "UNDEFINED" if no engine identifier or name can be found.
   */
  _getDefaultSearchEngine() {
    let engine;
    try {
      engine = Services.search.defaultEngine;
    } catch (e) {}

    let name;
    if (!engine) {
      name = "NONE";
    } else if (engine.identifier) {
      name = engine.identifier;
    } else if (engine.name) {
      name = "other-" + engine.name;
    } else {
      name = "UNDEFINED";
    }

    return name;
  },

  /**
   * Update the default search engine value.
   */
  _updateSearchEngine() {
    if (!this._canQuerySearch) {
      this._log.trace("_updateSearchEngine - ignoring early call");
      return;
    }

    if (!Services.search) {
      // Just ignore cases where the search service is not implemented.
      return;
    }

    this._log.trace("_updateSearchEngine - isInitialized: " + Services.search.isInitialized);
    if (!Services.search.isInitialized) {
      return;
    }

    // Make sure we have a settings section.
    this._currentEnvironment.settings = this._currentEnvironment.settings || {};
    // Update the search engine entry in the current environment.
    this._currentEnvironment.settings.defaultSearchEngine = this._getDefaultSearchEngine();
    this._currentEnvironment.settings.defaultSearchEngineData =
      Services.search.getDefaultEngineInfo();

    // Record the cohort identifier used for search defaults A/B testing.
    if (Services.prefs.prefHasUserValue(PREF_SEARCH_COHORT)) {
      const searchCohort = Services.prefs.getCharPref(PREF_SEARCH_COHORT);
      this._currentEnvironment.settings.searchCohort = searchCohort;
      TelemetryEnvironment.setExperimentActive("searchCohort", searchCohort);
    }
  },

  /**
   * Update the default search engine value and trigger the environment change.
   */
  _onSearchEngineChange() {
    this._log.trace("_onSearchEngineChange");

    // Finally trigger the environment change notification.
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
    this._updateSearchEngine();
    this._onEnvironmentChange("search-engine-changed", oldEnvironment);
  },

  /**
   * Refresh the Telemetry environment and trigger an environment change due to
   * a change in compositor process (normally this will mean we've fallen back
   * from out-of-process to in-process compositing).
   */
  _onCompositorProcessAborted() {
    this._log.trace("_onCompositorProcessAborted");

    // Trigger the environment change notification.
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
    this._updateGraphicsFeatures();
    this._onEnvironmentChange("gfx-features-changed", oldEnvironment);
  },

  /**
   * Update the graphics features object.
   */
  _updateGraphicsFeatures() {
    let gfxData = this._currentEnvironment.system.gfx;
    try {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      gfxData.features = gfxInfo.getFeatures();
    } catch (e) {
      this._log.error("nsIGfxInfo.getFeatures() caught error", e);
    }
  },

  /**
   * Update the partner prefs.
   */
  _updatePartner() {
    this._currentEnvironment.partner = this._getPartner();
  },

  /**
   * Get the build data in object form.
   * @return Object containing the build data.
   */
  _getBuild() {
    let buildData = {
      applicationId: Services.appinfo.ID || null,
      applicationName: Services.appinfo.name || null,
      architecture: Services.sysinfo.get("arch"),
      buildId: Services.appinfo.appBuildID || null,
      version: Services.appinfo.version || null,
      vendor: Services.appinfo.vendor || null,
      platformVersion: Services.appinfo.platformVersion || null,
      xpcomAbi: Services.appinfo.XPCOMABI,
      hotfixVersion: Services.prefs.getStringPref(PREF_HOTFIX_LASTVERSION, null),
    };

    // Add |architecturesInBinary| only for Mac Universal builds.
    if ("@mozilla.org/xpcom/mac-utils;1" in Cc) {
      let macUtils = Cc["@mozilla.org/xpcom/mac-utils;1"].getService(Ci.nsIMacUtils);
      if (macUtils && macUtils.isUniversalBinary) {
        buildData.architecturesInBinary = macUtils.architecturesInBinary;
      }
    }

    return buildData;
  },

  /**
   * Determine if we're the default browser.
   * @returns null on error, true if we are the default browser, or false otherwise.
   */
  _isDefaultBrowser() {
    if (!("@mozilla.org/browser/shell-service;1" in Cc)) {
      this._log.info("_isDefaultBrowser - Could not obtain browser shell service");
      return null;
    }

    let shellService;
    try {
      let scope = {};
      Cu.import("resource:///modules/ShellService.jsm", scope);
      shellService = scope.ShellService;
    } catch (ex) {
      this._log.error("_isDefaultBrowser - Could not obtain shell service JSM");
    }

    if (!shellService) {
      try {
        shellService = Cc["@mozilla.org/browser/shell-service;1"]
                         .getService(Ci.nsIShellService);
      } catch (ex) {
        this._log.error("_isDefaultBrowser - Could not obtain shell service", ex);
        return null;
      }
    }

    try {
      // This uses the same set of flags used by the pref pane.
      return !!shellService.isDefaultBrowser(false, true);
    } catch (ex) {
      this._log.error("_isDefaultBrowser - Could not determine if default browser", ex);
      return null;
    }
  },

  _updateDefaultBrowser() {
    if (AppConstants.platform === "android") {
      return;
    }
    // Make sure to have a settings section.
    this._currentEnvironment.settings = this._currentEnvironment.settings || {};
    this._currentEnvironment.settings.isDefaultBrowser =
      this._sessionWasRestored ? this._isDefaultBrowser() : null;
  },

  /**
   * Update the cached settings data.
   */
  _updateSettings() {
    let updateChannel = null;
    try {
      updateChannel = UpdateUtils.getUpdateChannel(false);
    } catch (e) {}

    this._currentEnvironment.settings = {
      blocklistEnabled: Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true),
      e10sEnabled: Services.appinfo.browserTabsRemoteAutostart,
      e10sMultiProcesses: Services.appinfo.maxWebProcessCount,
      e10sCohort: Services.prefs.getStringPref(PREF_E10S_COHORT, "unknown"),
      telemetryEnabled: Utils.isTelemetryEnabled,
      locale: getBrowserLocale(),
      update: {
        channel: updateChannel,
        enabled: Services.prefs.getBoolPref(PREF_UPDATE_ENABLED, true),
        autoDownload: Services.prefs.getBoolPref(PREF_UPDATE_AUTODOWNLOAD, true),
      },
      userPrefs: this._getPrefData(),
      sandbox: this._getSandboxData(),
    };

    this._currentEnvironment.settings.addonCompatibilityCheckEnabled =
      AddonManager.checkCompatibility;

    this._updateDefaultBrowser();
    this._updateSearchEngine();
  },

  _getSandboxData() {
    let effectiveContentProcessLevel = null;
    try {
      let sandboxSettings = Cc["@mozilla.org/sandbox/sandbox-settings;1"].
                            getService(Ci.mozISandboxSettings);
      effectiveContentProcessLevel =
        sandboxSettings.effectiveContentSandboxLevel;
    } catch (e) {}
    return {
      effectiveContentProcessLevel,
    };
  },

  /**
   * Update the cached profile data.
   * @returns Promise<> resolved when the I/O is complete.
   */
  async _updateProfile() {
    const logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "ProfileAge - ");
    let profileAccessor = new ProfileAge(null, logger);

    let creationDate = await profileAccessor.created;
    let resetDate = await profileAccessor.reset;

    this._currentEnvironment.profile.creationDate =
      Utils.millisecondsToDays(creationDate);
    if (resetDate) {
      this._currentEnvironment.profile.resetDate =
        Utils.millisecondsToDays(resetDate);
    }
  },

  /**
   * Update the cached attribution data object.
   * @returns Promise<> resolved when the I/O is complete.
   */
  async _updateAttribution() {
    let data = await AttributionCode.getAttrDataAsync();
    if (Object.keys(data).length > 0) {
      this._currentEnvironment.settings.attribution = {};
      for (let key in data) {
        this._currentEnvironment.settings.attribution[key] =
          limitStringToLength(data[key], MAX_ATTRIBUTION_STRING_LENGTH);
      }
    }
  },

  /**
   * Get the partner data in object form.
   * @return Object containing the partner data.
   */
  _getPartner() {
    let partnerData = {
      distributionId: Services.prefs.getStringPref(PREF_DISTRIBUTION_ID, null),
      distributionVersion: Services.prefs.getStringPref(PREF_DISTRIBUTION_VERSION, null),
      partnerId: Services.prefs.getStringPref(PREF_PARTNER_ID, null),
      distributor: Services.prefs.getStringPref(PREF_DISTRIBUTOR, null),
      distributorChannel: Services.prefs.getStringPref(PREF_DISTRIBUTOR_CHANNEL, null),
    };

    // Get the PREF_APP_PARTNER_BRANCH branch and append its children to partner data.
    let partnerBranch = Services.prefs.getBranch(PREF_APP_PARTNER_BRANCH);
    partnerData.partnerNames = partnerBranch.getChildList("");

    return partnerData;
  },

  /**
   * Get the CPU information.
   * @return Object containing the CPU information data.
   */
  _getCpuData() {
    let cpuData = {
      count: getSysinfoProperty("cpucount", null),
      cores: getSysinfoProperty("cpucores", null),
      vendor: getSysinfoProperty("cpuvendor", null),
      family: getSysinfoProperty("cpufamily", null),
      model: getSysinfoProperty("cpumodel", null),
      stepping: getSysinfoProperty("cpustepping", null),
      l2cacheKB: getSysinfoProperty("cpucachel2", null),
      l3cacheKB: getSysinfoProperty("cpucachel3", null),
      speedMHz: getSysinfoProperty("cpuspeed", null),
    };

    const CPU_EXTENSIONS = ["hasMMX", "hasSSE", "hasSSE2", "hasSSE3", "hasSSSE3",
                            "hasSSE4A", "hasSSE4_1", "hasSSE4_2", "hasAVX", "hasAVX2",
                            "hasAES", "hasEDSP", "hasARMv6", "hasARMv7", "hasNEON"];

    // Enumerate the available CPU extensions.
    let availableExts = [];
    for (let ext of CPU_EXTENSIONS) {
      if (getSysinfoProperty(ext, false)) {
        availableExts.push(ext);
      }
    }

    cpuData.extensions = availableExts;

    return cpuData;
  },

  /**
   * Get the device information, if we are on a portable device.
   * @return Object containing the device information data, or null if
   * not a portable device.
   */
  _getDeviceData() {
    if (AppConstants.platform !== "android") {
      return null;
    }

    return {
      model: getSysinfoProperty("device", null),
      manufacturer: getSysinfoProperty("manufacturer", null),
      hardware: getSysinfoProperty("hardware", null),
      isTablet: getSysinfoProperty("tablet", null),
    };
  },

  /**
   * Get the OS information.
   * @return Object containing the OS data.
   */
  _getOSData() {
    let data = {
      name: forceToStringOrNull(getSysinfoProperty("name", null)),
      version: forceToStringOrNull(getSysinfoProperty("version", null)),
      locale: forceToStringOrNull(getSystemLocale()),
    };

    if (AppConstants.platform == "android") {
      data.kernelVersion = forceToStringOrNull(getSysinfoProperty("kernel_version", null));
    } else if (AppConstants.platform === "win") {
      // The path to the "UBR" key, queried to get additional version details on Windows.
      const WINDOWS_UBR_KEY_PATH = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";

      let versionInfo = getWindowsVersionInfo();
      data.servicePackMajor = versionInfo.servicePackMajor;
      data.servicePackMinor = versionInfo.servicePackMinor;
      data.windowsBuildNumber = versionInfo.buildNumber;
      // We only need the UBR if we're at or above Windows 10.
      if (typeof(data.version) === "string" &&
          Services.vc.compare(data.version, "10") >= 0) {
        // Query the UBR key and only add it to the environment if it's available.
        // |readRegKey| doesn't throw, but rather returns 'undefined' on error.
        let ubr = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                             WINDOWS_UBR_KEY_PATH, "UBR",
                                             Ci.nsIWindowsRegKey.WOW64_64);
        data.windowsUBR = (ubr !== undefined) ? ubr : null;
      }
      data.installYear = getSysinfoProperty("installYear", null);
    }

    return data;
  },

  /**
   * Get the HDD information.
   * @return Object containing the HDD data.
   */
  _getHDDData() {
    return {
      profile: { // hdd where the profile folder is located
        model: getSysinfoProperty("profileHDDModel", null),
        revision: getSysinfoProperty("profileHDDRevision", null),
      },
      binary:  { // hdd where the application binary is located
        model: getSysinfoProperty("binHDDModel", null),
        revision: getSysinfoProperty("binHDDRevision", null),
      },
      system:  { // hdd where the system files are located
        model: getSysinfoProperty("winHDDModel", null),
        revision: getSysinfoProperty("winHDDRevision", null),
      },
    };
  },

  /**
   * Get the GFX information.
   * @return Object containing the GFX data.
   */
  _getGFXData() {
    let gfxData = {
      D2DEnabled: getGfxField("D2DEnabled", null),
      DWriteEnabled: getGfxField("DWriteEnabled", null),
      ContentBackend: getGfxField("ContentBackend", null),
      // The following line is disabled due to main thread jank and will be enabled
      // again as part of bug 1154500.
      // DWriteVersion: getGfxField("DWriteVersion", null),
      adapters: [],
      monitors: [],
      features: {},
    };

    if (!["android", "linux"].includes(AppConstants.platform)) {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      try {
        gfxData.monitors = gfxInfo.getMonitors();
      } catch (e) {
        this._log.error("nsIGfxInfo.getMonitors() caught error", e);
      }
    }

    try {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      gfxData.features = gfxInfo.getFeatures();
    } catch (e) {
      this._log.error("nsIGfxInfo.getFeatures() caught error", e);
    }

    // GfxInfo does not yet expose a way to iterate through all the adapters.
    gfxData.adapters.push(getGfxAdapter(""));
    gfxData.adapters[0].GPUActive = true;

    // If we have a second adapter add it to the gfxData.adapters section.
    let hasGPU2 = getGfxField("adapterDeviceID2", null) !== null;
    if (!hasGPU2) {
      this._log.trace("_getGFXData - Only one display adapter detected.");
      return gfxData;
    }

    this._log.trace("_getGFXData - Two display adapters detected.");

    gfxData.adapters.push(getGfxAdapter("2"));
    gfxData.adapters[1].GPUActive = getGfxField("isGPU2Active", null);

    return gfxData;
  },

  /**
   * Get the system data in object form.
   * @return Object containing the system data.
   */
  _getSystem() {
    let memoryMB = getSysinfoProperty("memsize", null);
    if (memoryMB) {
      // Send RAM size in megabytes. Rounding because sysinfo doesn't
      // always provide RAM in multiples of 1024.
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }

    let virtualMB = getSysinfoProperty("virtualmemsize", null);
    if (virtualMB) {
      // Send the total virtual memory size in megabytes. Rounding because
      // sysinfo doesn't always provide RAM in multiples of 1024.
      virtualMB = Math.round(virtualMB / 1024 / 1024);
    }

    let data = {
      memoryMB,
      virtualMaxMB: virtualMB,
      cpu: this._getCpuData(),
      os: this._getOSData(),
      hdd: this._getHDDData(),
      gfx: this._getGFXData(),
    };

    if (AppConstants.platform === "win") {
      data.isWow64 = getSysinfoProperty("isWow64", null);
    } else if (AppConstants.platform == "android") {
      data.device = this._getDeviceData();
    }

    return data;
  },

  _onEnvironmentChange(what, oldEnvironment) {
    this._log.trace("_onEnvironmentChange for " + what);

    // We are already skipping change events in _checkChanges if there is a pending change task running.
    if (this._shutdown) {
      this._log.trace("_onEnvironmentChange - Already shut down.");
      return;
    }

    for (let [name, listener] of this._changeListeners) {
      try {
        this._log.debug("_onEnvironmentChange - calling " + name);
        listener(what, oldEnvironment);
      } catch (e) {
        this._log.error("_onEnvironmentChange - listener " + name + " caught error", e);
      }
    }
  },

  reset() {
    this._shutdown = false;
    this._delayedInitFinished = false;
  }
};
PK
!<''modules/TelemetryHealthPing.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 collects data on send failures and other critical issues with Telemetry submissions.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "TelemetryHealthPing",
];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController", "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils", "resource://gre/modules/TelemetryUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend", "resource://gre/modules/TelemetrySend.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences", "resource://gre/modules/Preferences.jsm");

const Utils = TelemetryUtils;

const MS_IN_A_MINUTE = 60 * 1000;
const IS_HEALTH_PING_ENABLED = Preferences.get(TelemetryUtils.Preferences.HealthPingEnabled, true);

// Send health ping every hour
const SEND_TICK_DELAY = 60 * MS_IN_A_MINUTE;

// Send top 10 discarded pings only to minimize health ping size
const MAX_SEND_DISCARDED_PINGS = 10;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryHealthPing::";

var Policy = {
  setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: (id) => clearTimeout(id)
};

this.TelemetryHealthPing = {
  Reason: Object.freeze({
    IMMEDIATE: "immediate", // Ping was sent immediately after recording with no delay.
    DELAYED: "delayed",     // Recorded data was sent after a delay.
    SHUT_DOWN: "shutdown",  // Recorded data was sent on shutdown.
  }),

  FailureType: Object.freeze({
    DISCARDED_FOR_SIZE: "pingDiscardedForSize",
    SEND_FAILURE: "sendFailure",
  }),

  OsInfo: Object.freeze({
    "name": Services.appinfo.OS,
    "version": Services.sysinfo.get("kernel_version") || Services.sysinfo.get("version")
  }),

  HEALTH_PING_TYPE: "health",

  _logger: null,

  // The health ping is sent every every SEND_TICK_DELAY.
  // Initialize this so that first failures are sent immediately.
  _lastSendTime: -SEND_TICK_DELAY,

  /**
   * This stores reported send failures with the following structure:
   * {
   *  type1: {
   *    subtype1: value,
   *    ...
   *    subtypeN: value
   *  },
   *  ...
   * }
   */
  _failures: {},
  _timeoutId: null,

  /**
   * Record a failure to send a ping out.
   * @param {String} failureType The type of failure (e.g. "timeout", ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  recordSendFailure(failureType) {
    return this._addToFailure(this.FailureType.SEND_FAILURE, failureType);
  },

  /**
   * Record that a ping was discarded and its type.
   * @param {String} pingType The type of discarded ping (e.g. "main", ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  recordDiscardedPing(pingType) {
    return this._addToFailure(this.FailureType.DISCARDED_FOR_SIZE, pingType);
  },

  /**
   * Assemble payload.
   * @param {String} reason A string indicating the triggering reason (e.g. "immediate", "delayed", "shutdown").
   * @returns {Object} The assembled payload.
   */
  _assemblePayload(reason) {
    this._log.trace("_assemblePayload()");
    let payload = {
      os: this.OsInfo,
      reason
    };

    for (let key of Object.keys(this._failures)) {
      if (key === this.FailureType.DISCARDED_FOR_SIZE) {
        payload[key] = this._getTopDiscardFailures(this._failures[key]);
      } else {
        payload[key] = this._failures[key];
      }
    }

    return payload;
  },

  /**
   * Sort input dictionary descending by value.
   * @param {Object} failures A dictionary of failures subtype and count.
   * @returns {Object} Sorted failures by value.
   */
  _getTopDiscardFailures(failures) {
    this._log.trace("_getTopDiscardFailures()");
    let sortedItems = Object.entries(failures).sort((first, second) => {
      return second[1] - first[1];
    });

    let result = {};
    sortedItems.slice(0, MAX_SEND_DISCARDED_PINGS).forEach(([key, value]) => {
      result[key] = value;
    });

    return result;
  },

  /**
   * Assemble the failure information and submit it.
   * @param {String} reason A string indicating the triggering reason (e.g. "immediate", "delayed", "shutdown").
   * @returns {Promise} Test-only promise that resolves when the ping was stored or sent (if any).
   */
  _submitPing(reason) {
    if (!IS_HEALTH_PING_ENABLED || !this._hasDataToSend()) {
      return Promise.resolve();
    }

    this._log.trace("_submitPing(" + reason + ")");
    let payload = this._assemblePayload(reason);
    this._clearData();
    this._lastSendTime = Utils.monotonicNow();

    let options = {
      addClientId: true,
      usePingSender: reason === this.Reason.SHUT_DOWN
    };

    return new Promise(r =>
      // If we submit the health ping immediately, the send task would be triggered again
      // before discarding oversized pings from the queue.
      // To work around this, we send the ping on the next tick.
      Services.tm.dispatchToMainThread(() => r(
        TelemetryController
          .submitExternalPing(this.HEALTH_PING_TYPE, payload, options))));
  },

  /**
   * Accumulate failure information and trigger a ping immediately or on timeout.
   * @param {String} failureType The type of failure (e.g. "timeout", ...).
   * @param {String} failureSubType The subtype of failure (e.g. ping type, ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  _addToFailure(failureType, failureSubType) {
    this._log.trace("_addToFailure() - with type and subtype: " + failureType + " : " + failureSubType);

    if (!(failureType in this._failures)) {
      this._failures[failureType] = {};
    }

    let current = this._failures[failureType][failureSubType] || 0;
    this._failures[failureType][failureSubType] = current + 1;

    const now = Utils.monotonicNow();
    if ((now - this._lastSendTime) >= SEND_TICK_DELAY) {
      return this._submitPing(this.Reason.IMMEDIATE);
    }

    let submissionDelay = SEND_TICK_DELAY - now - this._lastSendTime;
    this._timeoutId =
      Policy.setSchedulerTickTimeout(() => TelemetryHealthPing._submitPing(this.Reason.DELAYED), submissionDelay);
    return Promise.resolve();
  },

  /**
   * @returns {boolean} Check the availability of recorded failures data.
   */
  _hasDataToSend() {
    return Object.keys(this._failures).length !== 0;
  },

  /**
   * Clear recorded failures data.
   */
  _clearData() {
    this._log.trace("_clearData()");
    this._failures = {};
  },

  /**
   * Clear and reset timeout.
   */
  _resetTimeout() {
    if (this._timeoutId) {
      Policy.clearSchedulerTickTimeout(this._timeoutId);
      this._timeoutId = null;
    }
  },

  /**
   * Submit latest ping on shutdown.
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  shutdown() {
    this._log.trace("shutdown()");
    this._resetTimeout();
    return this._submitPing(this.Reason.SHUT_DOWN);
  },

  /**
   * Test-only, restore to initial state.
   */
  testReset() {
    this._lastSendTime = -SEND_TICK_DELAY;
    this._clearData();
    this._resetTimeout();
  },

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX + "::");
    }

    return this._logger;
  },
};
PK
!<modules/TelemetryLog.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["TelemetryLog"];

const Cc = Components.classes;
const Ci = Components.interfaces;

const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);

const LOG_ENTRY_MAX_COUNT = 1000;

var gLogEntries = [];

this.TelemetryLog = Object.freeze({
  log(id, data) {
    if (gLogEntries.length >= LOG_ENTRY_MAX_COUNT) {
      return;
    }
    id = String(id);
    var ts;
    try {
      ts = Math.floor(Telemetry.msSinceProcessStart());
    } catch (e) {
      // If timestamp is screwed up, we just give up instead of making up
      // data.
      return;
    }

    var entry = [id, ts];
    if (data !== undefined) {
      entry = entry.concat(Array.prototype.map.call(data, String));
    }
    gLogEntries.push(entry);
  },

  entries() {
    return gLogEntries;
  }
});
PK
!<z:modules/TelemetryModules.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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");

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
  "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
  "resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gUpdateTimerManager",
  "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
  "@mozilla.org/base/telemetry;1", "nsITelemetry");

this.EXPORTED_SYMBOLS = [
  "TelemetryModules",
];

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryModules::";

// The default is 1 week.
const MODULES_PING_INTERVAL_SECONDS = 7 * 24 * 60 * 60;
const MODULES_PING_INTERVAL_PREFERENCE = "toolkit.telemetry.modulesPing.interval";

const MAX_MODULES_NUM = 512;
const MAX_NAME_LENGTH = 64;
const TRUNCATION_DELIMITER = "\u2026";

this.TelemetryModules = Object.freeze({
  _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),

  start() {
    // The list of loaded modules is obtainable only when the profiler is enabled.
    // If it isn't, we don't want to send the ping at all.
    if (!AppConstants.MOZ_GECKO_PROFILER) {
      return;
    }

    // Use nsIUpdateTimerManager for a long-duration timer that survives across sessions.
    gUpdateTimerManager.registerTimer(
      "telemetry_modules_ping",
      this,
      Preferences.get(MODULES_PING_INTERVAL_PREFERENCE, MODULES_PING_INTERVAL_SECONDS)
    );
  },

  /**
   * Called when the 'telemetry_modules_ping' timer fires.
   */
  notify() {
    try {
      Telemetry.getLoadedModules().then(
        modules => {
          modules = modules.filter(module => module.name.length > 0);

          // Cut the list of modules to MAX_MODULES_NUM entries.
          if (modules.length > MAX_MODULES_NUM) {
            modules = modules.slice(0, MAX_MODULES_NUM);
          }

          // Cut the file names of the modules to MAX_NAME_LENGTH characters.
          for (let module of modules) {
            if (module.name.length > MAX_NAME_LENGTH) {
              module.name = module.name.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
            }

            if (module.debugName !== null && module.debugName.length > MAX_NAME_LENGTH) {
              module.debugName = module.debugName.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
            }
          }

          TelemetryController.submitExternalPing("modules",
            {
              version: 1,
              modules,
            },
            {
              addClientId: true,
              addEnvironment: true,
            }
          );
        },
        err => this._log.error("notify - promise failed", err)
      );
    } catch (ex) {
      this._log.error("notify - caught exception", ex);
    }
  },
});
PK
!<\!??$modules/TelemetryReportingPolicy.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "TelemetryReportingPolicy"
];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://services-common/observers.js", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                  "resource://gre/modules/TelemetrySend.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryReportingPolicy::";

// Oldest year to allow in date preferences. The FHR infobar was implemented in
// 2012 and no dates older than that should be encountered.
const OLDEST_ALLOWED_ACCEPTANCE_YEAR = 2012;

const PREF_BRANCH = "datareporting.policy.";

// The following preferences are deprecated and will be purged during the preferences
// migration process.
const DEPRECATED_FHR_PREFS = [
  PREF_BRANCH + "dataSubmissionPolicyAccepted",
  PREF_BRANCH + "dataSubmissionPolicyBypassAcceptance",
  PREF_BRANCH + "dataSubmissionPolicyResponseType",
  PREF_BRANCH + "dataSubmissionPolicyResponseTime"
];

// How much time until we display the data choices notification bar, on the first run.
const NOTIFICATION_DELAY_FIRST_RUN_MSEC = 60 * 1000; // 60s
// Same as above, for the next runs.
const NOTIFICATION_DELAY_NEXT_RUNS_MSEC = 10 * 1000; // 10s

/**
 * This is a policy object used to override behavior within this module.
 * Tests override properties on this object to allow for control of behavior
 * that would otherwise be very hard to cover.
 */
var Policy = {
  now: () => new Date(),
  setShowInfobarTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearShowInfobarTimeout: (id) => clearTimeout(id),
};

/**
 * Represents a request to display data policy.
 *
 * Receivers of these instances are expected to call one or more of the on*
 * functions when events occur.
 *
 * When one of these requests is received, the first thing a callee should do
 * is present notification to the user of the data policy. When the notice
 * is displayed to the user, the callee should call `onUserNotifyComplete`.
 *
 * If for whatever reason the callee could not display a notice,
 * it should call `onUserNotifyFailed`.
 *
 * @param {Object} aLog The log object used to log the error in case of failures.
 */
function NotifyPolicyRequest(aLog) {
  this._log = aLog;
}

NotifyPolicyRequest.prototype = Object.freeze({
  /**
   * Called when the user is notified of the policy.
   */
  onUserNotifyComplete() {
    return TelemetryReportingPolicyImpl._userNotified();
   },

  /**
   * Called when there was an error notifying the user about the policy.
   *
   * @param error
   *        (Error) Explains what went wrong.
   */
  onUserNotifyFailed(error) {
    this._log.error("onUserNotifyFailed - " + error);
  },
});

this.TelemetryReportingPolicy = {
  // The current policy version number. If the version number stored in the prefs
  // is smaller than this, data upload will be disabled until the user is re-notified
  // about the policy changes.
  DEFAULT_DATAREPORTING_POLICY_VERSION: 1,

  /**
   * Setup the policy.
   */
  setup() {
    return TelemetryReportingPolicyImpl.setup();
  },

  /**
   * Shutdown and clear the policy.
   */
  shutdown() {
    return TelemetryReportingPolicyImpl.shutdown();
  },

  /**
   * Check if we are allowed to upload data. In order to submit data both these conditions
   * should be true:
   * - The data submission preference should be true.
   * - The datachoices infobar should have been displayed.
   *
   * @return {Boolean} True if we are allowed to upload data, false otherwise.
   */
  canUpload() {
    return TelemetryReportingPolicyImpl.canUpload();
  },

  /**
   * Check if this is the first time the browser ran.
   */
  isFirstRun() {
    return TelemetryReportingPolicyImpl.isFirstRun();
  },

  /**
   * Test only method, restarts the policy.
   */
  reset() {
    return TelemetryReportingPolicyImpl.reset();
  },

  /**
   * Test only method, used to check if user is notified of the policy in tests.
   */
  testIsUserNotified() {
    return TelemetryReportingPolicyImpl.isUserNotifiedOfCurrentPolicy;
  },

  /**
   * Test only method, used to simulate the infobar being shown in xpcshell tests.
   */
  testInfobarShown() {
    return TelemetryReportingPolicyImpl._userNotified();
  },

  /**
   * Test only method, used to trigger an update of the "first run" state.
   */
  testUpdateFirstRun() {
    return TelemetryReportingPolicyImpl.observe(null, "sessionstore-windows-restored", null);
  },
};

var TelemetryReportingPolicyImpl = {
  _logger: null,
  // Keep track of the notification status if user wasn't notified already.
  _notificationInProgress: false,
  // The timer used to show the datachoices notification at startup.
  _startupNotificationTimerId: null,
  // Keep track of the first session state, as the related preference
  // is flipped right after the browser starts.
  _isFirstRun: true,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },

  /**
   * Get the date the policy was notified.
   * @return {Object} A date object or null on errors.
   */
  get dataSubmissionPolicyNotifiedDate() {
    let prefString = Services.prefs.getStringPref(TelemetryUtils.Preferences.AcceptedPolicyDate, "0");
    let valueInteger = parseInt(prefString, 10);

    // Bail out if we didn't store any value yet.
    if (valueInteger == 0) {
      this._log.info("get dataSubmissionPolicyNotifiedDate - No date stored yet.");
      return null;
    }

    // If an invalid value is saved in the prefs, bail out too.
    if (Number.isNaN(valueInteger)) {
      this._log.error("get dataSubmissionPolicyNotifiedDate - Invalid date stored.");
      return null;
    }

    // Make sure the notification date is newer then the oldest allowed date.
    let date = new Date(valueInteger);
    if (date.getFullYear() < OLDEST_ALLOWED_ACCEPTANCE_YEAR) {
      this._log.error("get dataSubmissionPolicyNotifiedDate - The stored date is too old.");
      return null;
    }

    return date;
  },

  /**
   * Set the date the policy was notified.
   * @param {Object} aDate A valid date object.
   */
  set dataSubmissionPolicyNotifiedDate(aDate) {
    this._log.trace("set dataSubmissionPolicyNotifiedDate - aDate: " + aDate);

    if (!aDate || aDate.getFullYear() < OLDEST_ALLOWED_ACCEPTANCE_YEAR) {
      this._log.error("set dataSubmissionPolicyNotifiedDate - Invalid notification date.");
      return;
    }

    Services.prefs.setStringPref(TelemetryUtils.Preferences.AcceptedPolicyDate, aDate.getTime().toString());
  },

  /**
   * Whether submission of data is allowed.
   *
   * This is the master switch for remote server communication. If it is
   * false, we never request upload or deletion.
   */
  get dataSubmissionEnabled() {
    // Default is true because we are opt-out.
    return Services.prefs.getBoolPref(TelemetryUtils.Preferences.DataSubmissionEnabled, true);
  },

  get currentPolicyVersion() {
    return Services.prefs.getIntPref(TelemetryUtils.Preferences.CurrentPolicyVersion,
                                     TelemetryReportingPolicy.DEFAULT_DATAREPORTING_POLICY_VERSION);
  },

  /**
   * The minimum policy version which for dataSubmissionPolicyAccepted to
   * to be valid.
   */
  get minimumPolicyVersion() {
    const minPolicyVersion = Services.prefs.getIntPref(TelemetryUtils.Preferences.MinimumPolicyVersion, 1);

    // First check if the current channel has a specific minimum policy version. If not,
    // use the general minimum policy version.
    let channel = "";
    try {
      channel = UpdateUtils.getUpdateChannel(false);
    } catch (e) {
      this._log.error("minimumPolicyVersion - Unable to retrieve the current channel.");
      return minPolicyVersion;
    }
    const channelPref = TelemetryUtils.Preferences.MinimumPolicyVersion + ".channel-" + channel;
    return Services.prefs.getIntPref(channelPref, minPolicyVersion);
  },

  get dataSubmissionPolicyAcceptedVersion() {
    return Services.prefs.getIntPref(TelemetryUtils.Preferences.AcceptedPolicyVersion, 0);
  },

  set dataSubmissionPolicyAcceptedVersion(value) {
    Services.prefs.setIntPref(TelemetryUtils.Preferences.AcceptedPolicyVersion, value);
  },

  /**
   * Checks to see if the user has been notified about data submission
   * @return {Bool} True if user has been notified and the notification is still valid,
   *         false otherwise.
   */
  get isUserNotifiedOfCurrentPolicy() {
    // If we don't have a sane notification date, the user was not notified yet.
    if (!this.dataSubmissionPolicyNotifiedDate ||
        this.dataSubmissionPolicyNotifiedDate.getTime() <= 0) {
      return false;
    }

    // The accepted policy version should not be less than the minimum policy version.
    if (this.dataSubmissionPolicyAcceptedVersion < this.minimumPolicyVersion) {
      return false;
    }

    // Otherwise the user was already notified.
    return true;
  },

  /**
   * Test only method, restarts the policy.
   */
  reset() {
    this.shutdown();
    return this.setup();
  },

  /**
   * Setup the policy.
   */
  setup() {
    this._log.trace("setup");

    // Migrate the data choices infobar, if needed.
    this._migratePreferences();

    // Add the event observers.
    Services.obs.addObserver(this, "sessionstore-windows-restored");
  },

  /**
   * Clean up the reporting policy.
   */
  shutdown() {
    this._log.trace("shutdown");

    this._detachObservers();

    Policy.clearShowInfobarTimeout(this._startupNotificationTimerId);
  },

  /**
   * Detach the observers that were attached during setup.
   */
  _detachObservers() {
    Services.obs.removeObserver(this, "sessionstore-windows-restored");
  },

  /**
   * Check if we are allowed to upload data. In order to submit data both these conditions
   * should be true:
   * - The data submission preference should be true.
   * - The datachoices infobar should have been displayed.
   *
   * @return {Boolean} True if we are allowed to upload data, false otherwise.
   */
  canUpload() {
    // If data submission is disabled, there's no point in showing the infobar. Just
    // forbid to upload.
    if (!this.dataSubmissionEnabled) {
      return false;
    }

    // Submission is enabled. We enable upload if user is notified or we need to bypass
    // the policy.
    const bypassNotification = Services.prefs.getBoolPref(TelemetryUtils.Preferences.BypassNotification, false);
    return this.isUserNotifiedOfCurrentPolicy || bypassNotification;
  },

  isFirstRun() {
    return this._isFirstRun;
  },

  /**
   * Migrate the data policy preferences, if needed.
   */
  _migratePreferences() {
    // Current prefs are mostly the same than the old ones, except for some deprecated ones.
    for (let pref of DEPRECATED_FHR_PREFS) {
      Services.prefs.clearUserPref(pref);
    }
  },

  /**
   * Determine whether the user should be notified.
   */
  _shouldNotify() {
    if (!this.dataSubmissionEnabled) {
      this._log.trace("_shouldNotify - Data submission disabled by the policy.");
      return false;
    }

    const bypassNotification = Services.prefs.getBoolPref(TelemetryUtils.Preferences.BypassNotification, false);
    if (this.isUserNotifiedOfCurrentPolicy || bypassNotification) {
      this._log.trace("_shouldNotify - User already notified or bypassing the policy.");
      return false;
    }

    if (this._notificationInProgress) {
      this._log.trace("_shouldNotify - User not notified, notification already in progress.");
      return false;
    }

    return true;
  },

  /**
   * Show the data choices infobar if needed.
   */
  _showInfobar() {
    if (!this._shouldNotify()) {
      return;
    }

    this._log.trace("_showInfobar - User not notified, notifying now.");
    this._notificationInProgress = true;
    let request = new NotifyPolicyRequest(this._log);
    Observers.notify("datareporting:notify-data-policy:request", request);
  },

  /**
   * Called when the user is notified with the infobar or otherwise.
   */
  _userNotified() {
    this._log.trace("_userNotified");
    this._recordNotificationData();
    TelemetrySend.notifyCanUpload();
  },

  /**
   * Record date and the version of the accepted policy.
   */
  _recordNotificationData() {
    this._log.trace("_recordNotificationData");
    this.dataSubmissionPolicyNotifiedDate = Policy.now();
    this.dataSubmissionPolicyAcceptedVersion = this.currentPolicyVersion;
    // The user was notified and the notification data saved: the notification
    // is no longer in progress.
    this._notificationInProgress = false;
  },

  /**
   * Try to open the privacy policy in a background tab instead of showing the infobar.
   */
  _openFirstRunPage() {
    if (!this._shouldNotify()) {
      return false;
    }

    let firstRunPolicyURL = Services.prefs.getStringPref(TelemetryUtils.Preferences.FirstRunURL, "");
    if (!firstRunPolicyURL) {
      return false;
    }
    firstRunPolicyURL = Services.urlFormatter.formatURL(firstRunPolicyURL);

    let win;
    try {
      const { RecentWindow } = Cu.import("resource:///modules/RecentWindow.jsm", {});
      win = RecentWindow.getMostRecentBrowserWindow();
    } catch (e) {}

    if (!win) {
      this._log.info("Couldn't find browser window to open first-run page. Falling back to infobar.");
      return false;
    }

    // We'll consider the user notified once the privacy policy has been loaded
    // in a background tab even if that tab hasn't been selected.
    let tab;
    let progressListener = {};
    progressListener.onStateChange =
      (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) => {
        if (aWebProgress.isTopLevel &&
            tab &&
            tab.linkedBrowser == aBrowser &&
            aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
            aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
          let uri = aBrowser.documentURI;
          if (uri && !/^about:(blank|neterror|certerror|blocked)/.test(uri.spec)) {
            this._userNotified();
          } else {
            this._log.info("Failed to load first-run page. Falling back to infobar.");
            this._showInfobar();
          }
          removeListeners();
        }
      };

    let removeListeners = () => {
      win.removeEventListener("unload", removeListeners);
      win.gBrowser.removeTabsProgressListener(progressListener);
    };

    win.addEventListener("unload", removeListeners);
    win.gBrowser.addTabsProgressListener(progressListener);

    tab = win.gBrowser.loadOneTab(firstRunPolicyURL, {
      inBackground: true,
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
    return true;
  },

  observe(aSubject, aTopic, aData) {
    if (aTopic != "sessionstore-windows-restored") {
      return;
    }

    this._isFirstRun = Services.prefs.getBoolPref(TelemetryUtils.Preferences.FirstRun, true);
    if (this._isFirstRun) {
      // We're performing the first run, flip firstRun preference for subsequent runs.
      Services.prefs.setBoolPref(TelemetryUtils.Preferences.FirstRun, false);

      try {
        if (this._openFirstRunPage()) {
          return;
        }
      } catch (e) {
        this._log.error("Failed to open privacy policy tab: " + e);
      }
    }

    // Show the info bar.
    const delay =
      this._isFirstRun ? NOTIFICATION_DELAY_FIRST_RUN_MSEC : NOTIFICATION_DELAY_NEXT_RUNS_MSEC;

    this._startupNotificationTimerId = Policy.setShowInfobarTimeout(
        // Calling |canUpload| eventually shows the infobar, if needed.
        () => this._showInfobar(), delay);
  },
};
PK
!<Zamodules/TelemetrySend.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 responsible for uploading pings to the server and persisting
 * pings that can't be send now.
 * Those pending pings are persisted on disk and sent at the next opportunity,
 * newest first.
 */

"use strict";

this.EXPORTED_SYMBOLS = [
  "TelemetrySend",
];

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/AppConstants.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/ClientID.jsm");
Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/ServiceRequest.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                  "resource://gre/modules/TelemetryStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                  "resource://gre/modules/TelemetryReportingPolicy.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   "nsITelemetry");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryHealthPing",
                                  "resource://gre/modules/TelemetryHealthPing.jsm");


const Utils = TelemetryUtils;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetrySend::";

const TOPIC_IDLE_DAILY = "idle-daily";
// The following topics are notified when Firefox is closing
// because the OS is shutting down.
const TOPIC_QUIT_APPLICATION_GRANTED = "quit-application-granted";
const TOPIC_QUIT_APPLICATION_FORCED = "quit-application-forced";
const PREF_CHANGED_TOPIC = "nsPref:changed";

// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false);

const PING_FORMAT_VERSION = 4;

const MS_IN_A_MINUTE = 60 * 1000;

const PING_TYPE_DELETION = "deletion";

// We try to spread "midnight" pings out over this interval.
const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * MS_IN_A_MINUTE;
// We delay sending "midnight" pings on this client by this interval.
const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;

// Timeout after which we consider a ping submission failed.
const PING_SUBMIT_TIMEOUT_MS = 1.5 * MS_IN_A_MINUTE;

// To keep resource usage in check, we limit ping sending to a maximum number
// of pings per minute.
const MAX_PING_SENDS_PER_MINUTE = 10;

// If we have more pending pings then we can send right now, we schedule the next
// send for after SEND_TICK_DELAY.
const SEND_TICK_DELAY = 1 * MS_IN_A_MINUTE;
// If we had any ping send failures since the last ping, we use a backoff timeout
// for the next ping sends. We increase the delay exponentially up to a limit of
// SEND_MAXIMUM_BACKOFF_DELAY_MS.
// This exponential backoff will be reset by external ping submissions & idle-daily.
const SEND_MAXIMUM_BACKOFF_DELAY_MS = 120 * MS_IN_A_MINUTE;

// The age of a pending ping to be considered overdue (in milliseconds).
const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * MS_IN_A_MINUTE; // 1 week

// Strings to map from XHR.errorCode to TELEMETRY_SEND_FAILURE_TYPE.
// Echoes XMLHttpRequestMainThread's ErrorType enum.
const XHR_ERROR_TYPE = [
  "eOK",
  "eRequest",
  "eUnreachable",
  "eChannelOpen",
  "eRedirect",
];

/**
 * This is a policy object used to override behavior within this module.
 * Tests override properties on this object to allow for control of behavior
 * that would otherwise be very hard to cover.
 */
var Policy = {
  now: () => new Date(),
  midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS,
  setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: (id) => clearTimeout(id),
};

/**
 * Determine if the ping has the new v4 ping format or the legacy v2 one or earlier.
 */
function isV4PingFormat(aPing) {
  return ("id" in aPing) && ("application" in aPing) &&
         ("version" in aPing) && (aPing.version >= 2);
}

/**
 * Check if the provided ping is a deletion ping.
 * @param {Object} aPing The ping to check.
 * @return {Boolean} True if the ping is a deletion ping, false otherwise.
 */
function isDeletionPing(aPing) {
  return isV4PingFormat(aPing) && (aPing.type == PING_TYPE_DELETION);
}

/**
 * Save the provided ping as a pending ping. If it's a deletion ping, save it
 * to a special location.
 * @param {Object} aPing The ping to save.
 * @return {Promise} A promise resolved when the ping is saved.
 */
function savePing(aPing) {
  if (isDeletionPing(aPing)) {
    return TelemetryStorage.saveDeletionPing(aPing);
  }
  return TelemetryStorage.savePendingPing(aPing);
}

/**
 * @return {String} This returns a string with the gzip compressed data.
 */
function gzipCompressString(string) {
  let observer = {
    buffer: "",
    onStreamComplete(loader, context, status, length, result) {
      this.buffer = String.fromCharCode.apply(this, result);
    }
  };

  let scs = Cc["@mozilla.org/streamConverters;1"]
            .getService(Ci.nsIStreamConverterService);
  let listener = Cc["@mozilla.org/network/stream-loader;1"]
                .createInstance(Ci.nsIStreamLoader);
  listener.init(observer);
  let converter = scs.asyncConvertData("uncompressed", "gzip",
                                       listener, null);
  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
                     .createInstance(Ci.nsIStringInputStream);
  stringStream.data = string;
  converter.onStartRequest(null, null);
  converter.onDataAvailable(null, null, stringStream, 0, string.length);
  converter.onStopRequest(null, null, null);
  return observer.buffer;
}

this.TelemetrySend = {

  /**
   * Age in ms of a pending ping to be considered overdue.
   */
  get OVERDUE_PING_FILE_AGE() {
    return OVERDUE_PING_FILE_AGE;
  },

  get pendingPingCount() {
    return TelemetrySendImpl.pendingPingCount;
  },

  testSetTimeoutForPingSubmit(timeoutInMS) {
    TelemetrySendImpl._pingSubmissionTimeout = timeoutInMS;
  },

  testResetTimeOutToDefault() {
    TelemetrySendImpl._pingSubmissionTimeout = PING_SUBMIT_TIMEOUT_MS;
  },

  /**
   * Partial setup that runs immediately at startup. This currently triggers
   * the crash report annotations.
   */
  earlyInit() {
    TelemetrySendImpl.earlyInit();
  },

  /**
   * Initializes this module.
   *
   * @param {Boolean} testing Whether this is run in a test. This changes some behavior
   * to enable proper testing.
   * @return {Promise} Resolved when setup is finished.
   */
  setup(testing = false) {
    return TelemetrySendImpl.setup(testing);
  },

  /**
   * Shutdown this module - this will cancel any pending ping tasks and wait for
   * outstanding async activity like network and disk I/O.
   *
   * @return {Promise} Promise that is resolved when shutdown is finished.
   */
  shutdown() {
    return TelemetrySendImpl.shutdown();
  },

  /**
   * Submit a ping for sending. This will:
   * - send the ping right away if possible or
   * - save the ping to disk and send it at the next opportunity
   *
   * @param {Object} ping The ping data to send, must be serializable to JSON.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [options.usePingSender=false] if true, send the ping using the PingSender.
   * @return {Promise} Test-only - a promise that is resolved when the ping is sent or saved.
   */
  submitPing(ping, options = {}) {
    options.usePingSender = options.usePingSender || false;
    return TelemetrySendImpl.submitPing(ping, options);
  },

  /**
   * Count of pending pings that were found to be overdue at startup.
   */
  get overduePingsCount() {
    return TelemetrySendImpl.overduePingsCount;
  },

  /**
   * Notify that we can start submitting data to the servers.
   */
  notifyCanUpload() {
    return TelemetrySendImpl.notifyCanUpload();
  },

  /**
   * Only used in tests. Used to reset the module data to emulate a restart.
   */
  reset() {
    return TelemetrySendImpl.reset();
  },

  /**
   * Only used in tests.
   */
  setServer(server) {
    return TelemetrySendImpl.setServer(server);
  },

  /**
   * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests.
   */
  clearCurrentPings() {
    return TelemetrySendImpl.clearCurrentPings();
  },

  /**
   * Only used in tests to wait on outgoing pending pings.
   */
  testWaitOnOutgoingPings() {
    return TelemetrySendImpl.promisePendingPingActivity();
  },

  /**
   * Test-only - this allows overriding behavior to enable ping sending in debug builds.
   */
  setTestModeEnabled(testing) {
    TelemetrySendImpl.setTestModeEnabled(testing);
  },

  /**
   * This returns state info for this module for AsyncShutdown timeout diagnostics.
   */
  getShutdownState() {
    return TelemetrySendImpl.getShutdownState();
  },

  /**
   * Send a ping using the ping sender.
   * This method will not wait for the ping to be sent, instead it will return
   * as soon as the pingsender program has been launched.
   *
   * This method is currently exposed here only for testing purposes as it's
   * only used internally.
   *
   * @param {String} aUrl The telemetry server URL
   * @param {String} aPingFilePath The path to the file holding the ping
   *        contents, if if sent successfully the pingsender will delete it.
   *
   * @throws NS_ERROR_FAILURE if we couldn't find or run the pingsender
   *         executable.
   * @throws NS_ERROR_NOT_IMPLEMENTED on Android as the pingsender is not
   *         available.
   */
  testRunPingSender(url, pingPath) {
    TelemetrySendImpl.runPingSender(url, pingPath);
  },
};

var CancellableTimeout = {
  _deferred: null,
  _timer: null,

  /**
   * This waits until either the given timeout passed or the timeout was cancelled.
   *
   * @param {Number} timeoutMs The timeout in ms.
   * @return {Promise<bool>} Promise that is resolved with false if the timeout was cancelled,
   *                         false otherwise.
   */
  promiseWaitOnTimeout(timeoutMs) {
    if (!this._deferred) {
      this._deferred = PromiseUtils.defer();
      this._timer = Policy.setSchedulerTickTimeout(() => this._onTimeout(), timeoutMs);
    }

    return this._deferred.promise;
  },

  _onTimeout() {
    if (this._deferred) {
      this._deferred.resolve(false);
      this._timer = null;
      this._deferred = null;
    }
  },

  cancelTimeout() {
    if (this._deferred) {
      Policy.clearSchedulerTickTimeout(this._timer);
      this._deferred.resolve(true);
      this._timer = null;
      this._deferred = null;
    }
  },
};

/**
 * SendScheduler implements the timer & scheduling behavior for ping sends.
 */
var SendScheduler = {
  // Whether any ping sends failed since the last tick. If yes, we start with our exponential
  // backoff timeout.
  _sendsFailed: false,
  // The current retry delay after ping send failures. We use this for the exponential backoff,
  // increasing this value everytime we had send failures since the last tick.
  _backoffDelay: SEND_TICK_DELAY,
  _shutdown: false,
  _sendTask: null,
  // A string that tracks the last seen send task state, null if it never ran.
  _sendTaskState: null,

  _logger: null,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX + "Scheduler::");
    }

    return this._logger;
  },

  shutdown() {
    this._log.trace("shutdown");
    this._shutdown = true;
    CancellableTimeout.cancelTimeout();
    return Promise.resolve(this._sendTask);
  },

  start() {
    this._log.trace("start");
    this._sendsFailed = false;
    this._backoffDelay = SEND_TICK_DELAY;
    this._shutdown = false;
  },

  /**
   * Only used for testing, resets the state to emulate a restart.
   */
  reset() {
    this._log.trace("reset");
    return this.shutdown().then(() => this.start());
  },

  /**
   * Notify the scheduler of a failure in sending out pings that warrants retrying.
   * This will trigger the exponential backoff timer behavior on the next tick.
   */
  notifySendsFailed() {
    this._log.trace("notifySendsFailed");
    if (this._sendsFailed) {
      return;
    }

    this._sendsFailed = true;
    this._log.trace("notifySendsFailed - had send failures");
  },

  /**
   * Returns whether ping submissions are currently throttled.
   */
  isThrottled() {
    const now = Policy.now();
    const nextPingSendTime = this._getNextPingSendTime(now);
    return (nextPingSendTime > now.getTime());
  },

  waitOnSendTask() {
    return Promise.resolve(this._sendTask);
  },

  triggerSendingPings(immediately) {
    this._log.trace("triggerSendingPings - active send task: " + !!this._sendTask + ", immediately: " + immediately);

    if (!this._sendTask) {
      this._sendTask = this._doSendTask();
      let clear = () => this._sendTask = null;
      this._sendTask.then(clear, clear);
    } else if (immediately) {
      CancellableTimeout.cancelTimeout();
    }

    return this._sendTask;
  },

  async _doSendTask() {
    this._sendTaskState = "send task started";
    this._backoffDelay = SEND_TICK_DELAY;
    this._sendsFailed = false;

    const resetBackoffTimer = () => {
      this._backoffDelay = SEND_TICK_DELAY;
    };

    for (;;) {
      this._log.trace("_doSendTask iteration");
      this._sendTaskState = "start iteration";

      if (this._shutdown) {
        this._log.trace("_doSendTask - shutting down, bailing out");
        this._sendTaskState = "bail out - shutdown check";
        return;
      }

      // Get a list of pending pings, sorted by last modified, descending.
      // Filter out all the pings we can't send now. This addresses scenarios like "deletion" pings
      // which can be send even when upload is disabled.
      let pending = TelemetryStorage.getPendingPingList();
      let current = TelemetrySendImpl.getUnpersistedPings();
      this._log.trace("_doSendTask - pending: " + pending.length + ", current: " + current.length);
      // Note that the two lists contain different kind of data. |pending| only holds ping
      // info, while |current| holds actual ping data.
      if (!TelemetrySendImpl.sendingEnabled()) {
        pending = pending.filter(pingInfo => TelemetryStorage.isDeletionPing(pingInfo.id));
        current = current.filter(p => isDeletionPing(p));
      }
      this._log.trace("_doSendTask - can send - pending: " + pending.length + ", current: " + current.length);

      // Bail out if there is nothing to send.
      if ((pending.length == 0) && (current.length == 0)) {
        this._log.trace("_doSendTask - no pending pings, bailing out");
        this._sendTaskState = "bail out - no pings to send";
        return;
      }

      // If we are currently throttled (e.g. fuzzing to avoid midnight spikes), wait for the next send window.
      const now = Policy.now();
      if (this.isThrottled()) {
        const nextPingSendTime = this._getNextPingSendTime(now);
        this._log.trace("_doSendTask - throttled, delaying ping send to " + new Date(nextPingSendTime));
        this._sendTaskState = "wait for throttling to pass";

        const delay = nextPingSendTime - now.getTime();
        const cancelled = await CancellableTimeout.promiseWaitOnTimeout(delay);
        if (cancelled) {
          this._log.trace("_doSendTask - throttling wait was cancelled, resetting backoff timer");
          resetBackoffTimer();
        }

        continue;
      }

      let sending = pending.slice(0, MAX_PING_SENDS_PER_MINUTE);
      pending = pending.slice(MAX_PING_SENDS_PER_MINUTE);
      this._log.trace("_doSendTask - triggering sending of " + sending.length + " pings now" +
                      ", " + pending.length + " pings waiting");

      this._sendsFailed = false;
      const sendStartTime = Policy.now();
      this._sendTaskState = "wait on ping sends";
      await TelemetrySendImpl.sendPings(current, sending.map(p => p.id));
      if (this._shutdown || (TelemetrySend.pendingPingCount == 0)) {
        this._log.trace("_doSendTask - bailing out after sending, shutdown: " + this._shutdown +
                        ", pendingPingCount: " + TelemetrySend.pendingPingCount);
        this._sendTaskState = "bail out - shutdown & pending check after send";
        return;
      }

      // Calculate the delay before sending the next batch of pings.
      // We start with a delay that makes us send max. 1 batch per minute.
      // If we had send failures in the last batch, we will override this with
      // a backoff delay.
      const timeSinceLastSend = Policy.now() - sendStartTime;
      let nextSendDelay = Math.max(0, SEND_TICK_DELAY - timeSinceLastSend);

      if (!this._sendsFailed) {
        this._log.trace("_doSendTask - had no send failures, resetting backoff timer");
        resetBackoffTimer();
      } else {
        const newDelay = Math.min(SEND_MAXIMUM_BACKOFF_DELAY_MS,
                                  this._backoffDelay * 2);
        this._log.trace("_doSendTask - had send failures, backing off -" +
                        " old timeout: " + this._backoffDelay +
                        ", new timeout: " + newDelay);
        this._backoffDelay = newDelay;
        nextSendDelay = this._backoffDelay;
      }

      this._log.trace("_doSendTask - waiting for next send opportunity, timeout is " + nextSendDelay)
      this._sendTaskState = "wait on next send opportunity";
      const cancelled = await CancellableTimeout.promiseWaitOnTimeout(nextSendDelay);
      if (cancelled) {
        this._log.trace("_doSendTask - batch send wait was cancelled, resetting backoff timer");
        resetBackoffTimer();
      }
    }
  },

  /**
   * This helper calculates the next time that we can send pings at.
   * Currently this mostly redistributes ping sends from midnight until one hour after
   * to avoid submission spikes around local midnight for daily pings.
   *
   * @param now Date The current time.
   * @return Number The next time (ms from UNIX epoch) when we can send pings.
   */
  _getNextPingSendTime(now) {
    // 1. First we check if the time is between 0am and 1am. If it's not, we send
    // immediately.
    // 2. If we confirmed the time is indeed between 0am and 1am in step 1, we disallow
    // sending before (midnight + fuzzing delay), which is a random time between 0am-1am
    // (decided at startup).

    const midnight = Utils.truncateToDays(now);
    // Don't delay pings if we are not within the fuzzing interval.
    if ((now.getTime() - midnight.getTime()) > MIDNIGHT_FUZZING_INTERVAL_MS) {
      return now.getTime();
    }

    // Delay ping send if we are within the midnight fuzzing range.
    // We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|.
    return midnight.getTime() + Policy.midnightPingFuzzingDelay();
  },

  getShutdownState() {
    return {
      shutdown: this._shutdown,
      hasSendTask: !!this._sendTask,
      sendsFailed: this._sendsFailed,
      sendTaskState: this._sendTaskState,
      backoffDelay: this._backoffDelay,
    };
  },
 };

var TelemetrySendImpl = {
  _sendingEnabled: false,
  // Tracks the shutdown state.
  _shutdown: false,
  _logger: null,
  // This tracks all pending ping requests to the server.
  _pendingPingRequests: new Map(),
  // This tracks all the pending async ping activity.
  _pendingPingActivity: new Set(),
  // This is true when running in the test infrastructure.
  _testMode: false,
  // This holds pings that we currently try and haven't persisted yet.
  _currentPings: new Map(),
  // Used to skip spawning the pingsender if OS is shutting down.
  _isOSShutdown: false,
  // Count of pending pings that were overdue.
  _overduePingCount: 0,

  _pingSubmissionTimeout: PING_SUBMIT_TIMEOUT_MS,

  OBSERVER_TOPICS: [
    TOPIC_IDLE_DAILY,
    TOPIC_QUIT_APPLICATION_GRANTED,
    TOPIC_QUIT_APPLICATION_FORCED,
  ],

  OBSERVED_PREFERENCES: [
    TelemetryUtils.Preferences.TelemetryEnabled,
    TelemetryUtils.Preferences.FhrUploadEnabled,
  ],

  // Whether sending pings has been overridden.
  get _overrideOfficialCheck() {
    return Services.prefs.getBoolPref(TelemetryUtils.Preferences.OverrideOfficialCheck, false);
  },

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },

  get overduePingsCount() {
    return this._overduePingCount;
  },

  get pendingPingRequests() {
    return this._pendingPingRequests;
  },

  get pendingPingCount() {
    return TelemetryStorage.getPendingPingList().length + this._currentPings.size;
  },

  setTestModeEnabled(testing) {
    this._testMode = testing;
  },

  earlyInit() {
    this._annotateCrashReport();

    // Install the observer to detect OS shutdown early enough, so
    // that we catch this before the delayed setup happens.
    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_FORCED);
    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_GRANTED);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),

  async setup(testing) {
    this._log.trace("setup");

    this._testMode = testing;
    this._sendingEnabled = true;

    Services.obs.addObserver(this, TOPIC_IDLE_DAILY);

    this._server = Services.prefs.getStringPref(TelemetryUtils.Preferences.Server, undefined);

    // Annotate crash reports so that crash pings are sent correctly and listen
    // to pref changes to adjust the annotations accordingly.
    for (let pref of this.OBSERVED_PREFERENCES) {
      Services.prefs.addObserver(pref, this, true);
    }
    this._annotateCrashReport();

    // Check the pending pings on disk now.
    try {
      await this._checkPendingPings();
    } catch (ex) {
      this._log.error("setup - _checkPendingPings rejected", ex);
    }

    // Enforce the pending pings storage quota. It could take a while so don't
    // block on it.
    TelemetryStorage.runEnforcePendingPingsQuotaTask();

    // Start sending pings, but don't block on this.
    SendScheduler.triggerSendingPings(true);
  },

  /**
   * Triggers the crash report annotations depending on the current
   * configuration. This communicates to the crash reporter if it can send a
   * crash ping or not. This method can be called safely before setup() has
   * been called.
   */
  _annotateCrashReport() {
    try {
      const cr = Cc["@mozilla.org/toolkit/crash-reporter;1"];
      if (cr) {
        const crs = cr.getService(Ci.nsICrashReporter);

        let clientId = ClientID.getCachedClientID();
        let server = this._server || Services.prefs.getStringPref(TelemetryUtils.Preferences.Server, undefined);

        if (!this.sendingEnabled() || !TelemetryReportingPolicy.canUpload()) {
          // If we cannot send pings then clear the crash annotations
          crs.annotateCrashReport("TelemetryClientId", "");
          crs.annotateCrashReport("TelemetryServerURL", "");
        } else {
          crs.annotateCrashReport("TelemetryClientId", clientId);
          crs.annotateCrashReport("TelemetryServerURL", server);
        }
      }
    } catch (e) {
      // Ignore errors when crash reporting is disabled
    }
  },

  /**
   * Discard old pings from the pending pings and detect overdue ones.
   * @return {Boolean} True if we have overdue pings, false otherwise.
   */
  async _checkPendingPings() {
    // Scan the pending pings - that gives us a list sorted by last modified, descending.
    let infos = await TelemetryStorage.loadPendingPingList();
    this._log.info("_checkPendingPings - pending ping count: " + infos.length);
    if (infos.length == 0) {
      this._log.trace("_checkPendingPings - no pending pings");
      return;
    }

    const now = Policy.now();

    // Check for overdue pings.
    const overduePings = infos.filter((info) =>
      (now.getTime() - info.lastModificationDate) > OVERDUE_PING_FILE_AGE);
    this._overduePingCount = overduePings.length;

    // Submit the age of the pending pings.
    for (let pingInfo of infos) {
      const ageInDays =
        Utils.millisecondsToDays(Math.abs(now.getTime() - pingInfo.lastModificationDate));
      Telemetry.getHistogramById("TELEMETRY_PENDING_PINGS_AGE").add(ageInDays);
    }
   },

  async shutdown() {
    this._shutdown = true;

    for (let pref of this.OBSERVED_PREFERENCES) {
      // FIXME: When running tests this causes errors to be printed out if
      // TelemetrySend.shutdown() is called twice in a row without calling
      // TelemetrySend.setup() in-between.
      Services.prefs.removeObserver(pref, this);
    }

    for (let topic of this.OBSERVER_TOPICS) {
      try {
        Services.obs.removeObserver(this, topic);
      } catch (ex) {
        this._log.error("shutdown - failed to remove observer for " + topic, ex);
      }
    }

    // We can't send anymore now.
    this._sendingEnabled = false;

    // Cancel any outgoing requests.
    await this._cancelOutgoingRequests();

    // Stop any active send tasks.
    await SendScheduler.shutdown();

    // Wait for any outstanding async ping activity.
    await this.promisePendingPingActivity();

    // Save any outstanding pending pings to disk.
    await this._persistCurrentPings();
  },

  reset() {
    this._log.trace("reset");

    this._shutdown = false;
    this._currentPings = new Map();
    this._overduePingCount = 0;
    this._isOSShutdown = false;

    const histograms = [
      "TELEMETRY_SUCCESS",
      "TELEMETRY_SEND_SUCCESS",
      "TELEMETRY_SEND_FAILURE",
    ];

    histograms.forEach(h => Telemetry.getHistogramById(h).clear());

    return SendScheduler.reset();
  },

  /**
   * Notify that we can start submitting data to the servers.
   */
  notifyCanUpload() {
    // Let the scheduler trigger sending pings if possible, also inform the
    // crash reporter that it can send crash pings if appropriate.
    SendScheduler.triggerSendingPings(true);
    this._annotateCrashReport();

    return this.promisePendingPingActivity();
  },

  observe(subject, topic, data) {
    let setOSShutdown = () => {
      this._log.trace("setOSShutdown - in OS shutdown");
      this._isOSShutdown = true;
      Telemetry.scalarSet("telemetry.os_shutting_down", true);
    };

    switch (topic) {
    case TOPIC_IDLE_DAILY:
      SendScheduler.triggerSendingPings(true);
      break;
    case TOPIC_QUIT_APPLICATION_FORCED:
      setOSShutdown();
      break;
    case TOPIC_QUIT_APPLICATION_GRANTED:
      if (data == "syncShutdown") {
        setOSShutdown();
      }
      break;
    case PREF_CHANGED_TOPIC:
      if (this.OBSERVED_PREFERENCES.includes(data)) {
        this._annotateCrashReport();
      }
      break;
    }
  },

  /**
   * Spawn the PingSender process that sends a ping. This function does
   * not return an error or throw, it only logs an error.
   *
   * Even if the function doesn't fail, it doesn't mean that the ping was
   * successfully sent, as we have no control over the spawned process. If it,
   * succeeds, the ping is eventually removed from the disk to prevent duplicated
   * submissions.
   *
   * @param {String} pingId The id of the ping to send.
   * @param {String} submissionURL The complete Telemetry-compliant URL for the ping.
   */
  _sendWithPingSender(pingId, submissionURL) {
    this._log.trace("_sendWithPingSender - sending " + pingId + " to " + submissionURL);
    try {
      const pingPath = OS.Path.join(TelemetryStorage.pingDirectoryPath, pingId);
      this.runPingSender(submissionURL, pingPath);
    } catch (e) {
      this._log.error("_sendWithPingSender - failed to submit ping", e);
    }
  },

  submitPing(ping, options) {
    this._log.trace("submitPing - ping id: " + ping.id + ", options: " + JSON.stringify(options));

    if (!this.sendingEnabled(ping)) {
      this._log.trace("submitPing - Telemetry is not allowed to send pings.");
      return Promise.resolve();
    }

    // Send the ping using the PingSender, if requested and the user was
    // notified of our policy. We don't support the pingsender on Android,
    // so ignore this option on that platform (see bug 1335917).
    // Moreover, if the OS is shutting down, we don't want to spawn the
    // pingsender as it could unnecessarily slow down OS shutdown.
    // Additionally, it could be be killed before it can complete its tasks,
    // for example after successfully sending the ping but before removing
    // the copy from the disk, resulting in receiving duplicate pings when
    // Firefox restarts.
    if (options.usePingSender &&
        !this._isOSShutdown &&
        TelemetryReportingPolicy.canUpload() &&
        AppConstants.platform != "android") {
      const url = this._buildSubmissionURL(ping);
      // Serialize the ping to the disk and then spawn the PingSender.
      return savePing(ping).then(() => this._sendWithPingSender(ping.id, url));
    }

    if (!this.canSendNow) {
      // Sending is disabled or throttled, add this to the persisted pending pings.
      this._log.trace("submitPing - can't send ping now, persisting to disk - " +
                      "canSendNow: " + this.canSendNow);
      return savePing(ping);
    }

    // Let the scheduler trigger sending pings if possible.
    // As a safety mechanism, this resets any currently active throttling.
    this._log.trace("submitPing - can send pings, trying to send now");
    this._currentPings.set(ping.id, ping);
    SendScheduler.triggerSendingPings(true);
    return Promise.resolve();
  },

  /**
   * Only used in tests.
   */
  setServer(server) {
    this._log.trace("setServer", server);
    this._server = server;
  },

  /**
   * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests.
   */
  async clearCurrentPings() {
    if (this._shutdown) {
      this._log.trace("clearCurrentPings - in shutdown, bailing out");
      return;
    }

    // Temporarily disable the scheduler. It must not try to reschedule ping sending
    // while we're deleting them.
    await SendScheduler.shutdown();

    // Now that the ping activity has settled, abort outstanding ping requests.
    this._cancelOutgoingRequests();

    // Also, purge current pings.
    this._currentPings.clear();

    // We might have been interrupted and shutdown could have been started.
    // We need to bail out in that case to avoid triggering send activity etc.
    // at unexpected times.
    if (this._shutdown) {
      this._log.trace("clearCurrentPings - in shutdown, not spinning SendScheduler up again");
      return;
    }

    // Enable the scheduler again and spin the send task.
    SendScheduler.start();
    SendScheduler.triggerSendingPings(true);
  },

  _cancelOutgoingRequests() {
    // Abort any pending ping XHRs.
    for (let [id, request] of this._pendingPingRequests) {
      this._log.trace("_cancelOutgoingRequests - aborting ping request for id " + id);
      try {
        request.abort();
      } catch (e) {
        this._log.error("_cancelOutgoingRequests - failed to abort request for id " + id, e);
      }
    }
    this._pendingPingRequests.clear();
  },

  sendPings(currentPings, persistedPingIds) {
    let pingSends = [];

    for (let current of currentPings) {
      let ping = current;
      let p = (async () => {
        try {
          await this._doPing(ping, ping.id, false);
        } catch (ex) {
          this._log.info("sendPings - ping " + ping.id + " not sent, saving to disk", ex);
          // Deletion pings must be saved to a special location.
          await savePing(ping);
        } finally {
          this._currentPings.delete(ping.id);
        }
      })();

      this._trackPendingPingTask(p);
      pingSends.push(p);
    }

    if (persistedPingIds.length > 0) {
      pingSends.push(this._sendPersistedPings(persistedPingIds).catch((ex) => {
        this._log.info("sendPings - persisted pings not sent", ex);
      }));
    }

    return Promise.all(pingSends);
  },

  /**
   * Send the persisted pings to the server.
   *
   * @param {Array<string>} List of ping ids that should be sent.
   *
   * @return Promise A promise that is resolved when all pings finished sending or failed.
   */
  async _sendPersistedPings(pingIds) {
    this._log.trace("sendPersistedPings");

    if (TelemetryStorage.pendingPingCount < 1) {
      this._log.trace("_sendPersistedPings - no pings to send");
      return;
    }

    if (pingIds.length < 1) {
      this._log.trace("sendPersistedPings - no pings to send");
      return;
    }

    // We can send now.
    // If there are any send failures, _doPing() sets up handlers that e.g. trigger backoff timer behavior.
    this._log.trace("sendPersistedPings - sending " + pingIds.length + " pings");
    let pingSendPromises = [];
    for (let pingId of pingIds) {
      const id = pingId;
      pingSendPromises.push(
        TelemetryStorage.loadPendingPing(id)
          .then((data) => this._doPing(data, id, true))
          .catch(e => this._log.error("sendPersistedPings - failed to send ping " + id, e)));
    }

    let promise = Promise.all(pingSendPromises);
    this._trackPendingPingTask(promise);
    await promise;
  },

  _onPingRequestFinished(success, startTime, id, isPersisted) {
    this._log.trace("_onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);

    let sendId = success ? "TELEMETRY_SEND_SUCCESS" : "TELEMETRY_SEND_FAILURE";
    let hsend = Telemetry.getHistogramById(sendId);
    let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");

    hsend.add(Utils.monotonicNow() - startTime);
    hsuccess.add(success);

    if (!success) {
      // Let the scheduler know about send failures for triggering backoff timeouts.
      SendScheduler.notifySendsFailed();
    }

    if (success && isPersisted) {
      if (TelemetryStorage.isDeletionPing(id)) {
        return TelemetryStorage.removeDeletionPing();
      }
      return TelemetryStorage.removePendingPing(id);
    }
    return Promise.resolve();
  },

  _buildSubmissionURL(ping) {
    const version = isV4PingFormat(ping) ? PING_FORMAT_VERSION : 1;
    return this._server + this._getSubmissionPath(ping) + "?v=" + version;
  },

  _getSubmissionPath(ping) {
    // The new ping format contains an "application" section, the old one doesn't.
    let pathComponents;
    if (isV4PingFormat(ping)) {
      // We insert the Ping id in the URL to simplify server handling of duplicated
      // pings.
      let app = ping.application;
      pathComponents = [
        ping.id, ping.type, app.name, app.version, app.channel, app.buildId
      ];
    } else {
      // This is a ping in the old format.
      if (!("slug" in ping)) {
        // That's odd, we don't have a slug. Generate one so that TelemetryStorage.jsm works.
        ping.slug = Utils.generateUUID();
      }

      // Do we have enough info to build a submission URL?
      let payload = ("payload" in ping) ? ping.payload : null;
      if (payload && ("info" in payload)) {
        let info = ping.payload.info;
        pathComponents = [ ping.slug, info.reason, info.appName, info.appVersion,
                           info.appUpdateChannel, info.appBuildID ];
      } else {
        // Only use the UUID as the slug.
        pathComponents = [ ping.slug ];
      }
    }

    let slug = pathComponents.join("/");
    return "/submit/telemetry/" + slug;
  },

  _doPing(ping, id, isPersisted) {
    if (!this.sendingEnabled(ping)) {
      // We can't send the pings to the server, so don't try to.
      this._log.trace("_doPing - Can't send ping " + ping.id);
      return Promise.resolve();
    }

    this._log.trace("_doPing - server: " + this._server + ", persisted: " + isPersisted +
                    ", id: " + id);

    const url = this._buildSubmissionURL(ping);

    let request = new ServiceRequest();
    request.mozBackgroundRequest = true;
    request.timeout = this._pingSubmissionTimeout;

    request.open("POST", url, true);
    request.overrideMimeType("text/plain");
    request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    request.setRequestHeader("Date", Policy.now().toUTCString());

    this._pendingPingRequests.set(id, request);

    // Prevent the request channel from running though URLClassifier (bug 1296802)
    request.channel.loadFlags &= ~Ci.nsIChannel.LOAD_CLASSIFY_URI;

    const monotonicStartTime = Utils.monotonicNow();
    let deferred = PromiseUtils.defer();

    let onRequestFinished = (success, event) => {
      let onCompletion = () => {
        if (success) {
          let histogram = Telemetry.getHistogramById("TELEMETRY_SUCCESSFUL_SEND_PINGS_SIZE_KB");
          histogram.add(compressedPingSizeKB);
          deferred.resolve();
        } else {
          let histogram = Telemetry.getHistogramById("TELEMETRY_FAILED_SEND_PINGS_SIZE_KB");
          histogram.add(compressedPingSizeKB);
          deferred.reject(event);
        }
      };

      this._pendingPingRequests.delete(id);
      this._onPingRequestFinished(success, monotonicStartTime, id, isPersisted)
        .then(() => onCompletion(),
              (error) => {
                this._log.error("_doPing - request success: " + success + ", error: " + error);
                onCompletion();
              });
    };

    let errorhandler = (event) => {
      let failure = event.type;
      if (failure === "error") {
        failure = XHR_ERROR_TYPE[request.errorCode];
      }

      TelemetryHealthPing.recordSendFailure(failure);
      Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").add(failure);

      this._log.error("_doPing - error making request to " + url + ": " + failure);
      onRequestFinished(false, event);
    };
    request.onerror = errorhandler;
    request.ontimeout = errorhandler;
    request.onabort = errorhandler;

    request.onload = (event) => {
      let status = request.status;
      let statusClass = status - (status % 100);
      let success = false;

      if (statusClass === 200) {
        // We can treat all 2XX as success.
        this._log.info("_doPing - successfully loaded, status: " + status);
        success = true;
      } else if (statusClass === 400) {
        // 4XX means that something with the request was broken.
        this._log.error("_doPing - error submitting to " + url + ", status: " + status
                        + " - ping request broken?");
        Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").add();
        // TODO: we should handle this better, but for now we should avoid resubmitting
        // broken requests by pretending success.
        success = true;
      } else if (statusClass === 500) {
        // 5XX means there was a server-side error and we should try again later.
        this._log.error("_doPing - error submitting to " + url + ", status: " + status
                        + " - server error, should retry later");
      } else {
        // We received an unexpected status code.
        this._log.error("_doPing - error submitting to " + url + ", status: " + status
                        + ", type: " + event.type);
      }

      onRequestFinished(success, event);
    };

    // If that's a legacy ping format, just send its payload.
    let networkPayload = isV4PingFormat(ping) ? ping : ping.payload;
    request.setRequestHeader("Content-Encoding", "gzip");
    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
    let startTime = Utils.monotonicNow();
    let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(networkPayload));
    utf8Payload += converter.Finish();
    Telemetry.getHistogramById("TELEMETRY_STRINGIFY").add(Utils.monotonicNow() - startTime);

    // Check the size and drop pings which are too big.
    const pingSizeBytes = utf8Payload.length;
    if (pingSizeBytes > TelemetryStorage.MAXIMUM_PING_SIZE) {
      this._log.error("_doPing - submitted ping exceeds the size limit, size: " + pingSizeBytes);
      Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").add();
      Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB")
               .add(Math.floor(pingSizeBytes / 1024 / 1024));
      // We don't need to call |request.abort()| as it was not sent yet.
      this._pendingPingRequests.delete(id);

      TelemetryHealthPing.recordDiscardedPing(ping.type);
      return TelemetryStorage.removePendingPing(id);
    }

    let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"]
                        .createInstance(Ci.nsIStringInputStream);
    startTime = Utils.monotonicNow();
    payloadStream.data = gzipCompressString(utf8Payload);

    const compressedPingSizeKB = Math.floor(payloadStream.data.length / 1024);
    Telemetry.getHistogramById("TELEMETRY_COMPRESS").add(Utils.monotonicNow() - startTime);
    request.send(payloadStream);

    return deferred.promise;
  },

  /**
   * Check if sending is temporarily disabled.
   * @return {Boolean} True if we can send pings to the server right now, false if
   *         sending is temporarily disabled.
   */
  get canSendNow() {
    // If the reporting policy was not accepted yet, don't send pings.
    if (!TelemetryReportingPolicy.canUpload()) {
      return false;
    }

    return this._sendingEnabled;
  },

  /**
   * Check if sending is disabled. If FHR is not allowed to upload,
   * pings are not sent to the server (Telemetry is a sub-feature of FHR). If trying
   * to send a deletion ping, don't block it.
   * If unified telemetry is off, don't send pings if Telemetry is disabled.
   *
   * @param {Object} [ping=null] A ping to be checked.
   * @return {Boolean} True if pings can be send to the servers, false otherwise.
   */
  sendingEnabled(ping = null) {
    // We only send pings from official builds, but allow overriding this for tests.
    if (!Telemetry.isOfficialTelemetry &&
        !this._testMode &&
        !this._overrideOfficialCheck) {
      return false;
    }

    // With unified Telemetry, the FHR upload setting controls whether we can send pings.
    // The Telemetry pref enables sending extended data sets instead.
    if (IS_UNIFIED_TELEMETRY) {
      // Deletion pings are sent even if the upload is disabled.
      if (ping && isDeletionPing(ping)) {
        return true;
      }
      return Services.prefs.getBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, false);
    }

    // Without unified Telemetry, the Telemetry enabled pref controls ping sending.
    return Utils.isTelemetryEnabled;
  },

  /**
   * Track any pending ping send and save tasks through the promise passed here.
   * This is needed to block shutdown on any outstanding ping activity.
   */
  _trackPendingPingTask(promise) {
    let clear = () => this._pendingPingActivity.delete(promise);
    promise.then(clear, clear);
    this._pendingPingActivity.add(promise);
  },

  /**
   * Return a promise that allows to wait on pending pings.
   * @return {Object<Promise>} A promise resolved when all the pending pings promises
   *         are resolved.
   */
  promisePendingPingActivity() {
    this._log.trace("promisePendingPingActivity - Waiting for ping task");
    let p = Array.from(this._pendingPingActivity, p => p.catch(ex => {
      this._log.error("promisePendingPingActivity - ping activity had an error", ex);
    }));
    p.push(SendScheduler.waitOnSendTask());
    return Promise.all(p);
  },

  async _persistCurrentPings() {
    for (let [id, ping] of this._currentPings) {
      try {
        await savePing(ping);
        this._log.trace("_persistCurrentPings - saved ping " + id);
      } catch (ex) {
        this._log.error("_persistCurrentPings - failed to save ping " + id, ex);
      } finally {
        this._currentPings.delete(id);
      }
    }
  },

  /**
   * Returns the current pending, not yet persisted, pings, newest first.
   */
  getUnpersistedPings() {
    let current = [...this._currentPings.values()];
    current.reverse();
    return current;
  },

  getShutdownState() {
    return {
      sendingEnabled: this._sendingEnabled,
      pendingPingRequestCount: this._pendingPingRequests.size,
      pendingPingActivityCount: this._pendingPingActivity.size,
      unpersistedPingCount: this._currentPings.size,
      persistedPingCount: TelemetryStorage.getPendingPingList().length,
      schedulerState: SendScheduler.getShutdownState(),
    };
  },

  runPingSender(url, pingPath) {
    if (AppConstants.platform === "android") {
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
    }

    const exeName = AppConstants.platform === "win" ? "pingsender.exe"
                                                    : "pingsender";

    let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
    exe.append(exeName);

    let process = Cc["@mozilla.org/process/util;1"]
                  .createInstance(Ci.nsIProcess);
    process.init(exe);
    process.startHidden = true;
    process.run(/* blocking */ false, [url, pingPath], 2);
  },
};
PK
!<bT#;#;modules/TelemetrySession.jsm/* -*- 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";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/DeferredTask.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                  "resource://gre/modules/TelemetrySend.jsm");

const Utils = TelemetryUtils;

const myScope = this;

// When modifying the payload in incompatible ways, please bump this version number
const PAYLOAD_VERSION = 4;
const PING_TYPE_MAIN = "main";
const PING_TYPE_SAVED_SESSION = "saved-session";

const REASON_ABORTED_SESSION = "aborted-session";
const REASON_DAILY = "daily";
const REASON_SAVED_SESSION = "saved-session";
const REASON_GATHER_PAYLOAD = "gather-payload";
const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
const REASON_TEST_PING = "test-ping";
const REASON_ENVIRONMENT_CHANGE = "environment-change";
const REASON_SHUTDOWN = "shutdown";

const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";

const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
const MIN_SUBSESSION_LENGTH_MS = Services.prefs.getIntPref("toolkit.telemetry.minSubsessionLength", 5 * 60) * 1000;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");

const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs";
const MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS = "Telemetry:GetChildThreadHangs";
const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";

const DATAREPORTING_DIRECTORY = "datareporting";
const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";

// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false);

// Maximum number of content payloads that we are willing to store.
const MAX_NUM_CONTENT_PAYLOADS = 10;

// Do not gather data more than once a minute (ms)
const TELEMETRY_INTERVAL = 60 * 1000;
// Delay before intializing telemetry (ms)
const TELEMETRY_DELAY = Services.prefs.getIntPref("toolkit.telemetry.initDelay", 60) * 1000;
// Delay before initializing telemetry if we're testing (ms)
const TELEMETRY_TEST_DELAY = 1;
// Execute a scheduler tick every 5 minutes.
const SCHEDULER_TICK_INTERVAL_MS = Services.prefs.getIntPref("toolkit.telemetry.scheduler.tickInterval", 5 * 60) * 1000;
// When user is idle, execute a scheduler tick every 60 minutes.
const SCHEDULER_TICK_IDLE_INTERVAL_MS = Services.prefs.getIntPref("toolkit.telemetry.scheduler.idleTickInterval", 60 * 60) * 1000;

// The tolerance we have when checking if it's midnight (15 minutes).
const SCHEDULER_MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;

// The maximum time (ms) until the tick should moved from the idle
// queue to the regular queue if it hasn't been executed yet.
const SCHEDULER_TICK_MAX_IDLE_DELAY_MS = 60 * 1000;

// Seconds of idle time before pinging.
// On idle-daily a gather-telemetry notification is fired, during it probes can
// start asynchronous tasks to gather data.
const IDLE_TIMEOUT_SECONDS = Services.prefs.getIntPref("toolkit.telemetry.idleTimeout", 5 * 60);

// The frequency at which we persist session data to the disk to prevent data loss
// in case of aborted sessions (currently 5 minutes).
const ABORTED_SESSION_UPDATE_INTERVAL_MS = 5 * 60 * 1000;

const TOPIC_CYCLE_COLLECTOR_BEGIN = "cycle-collector-begin";

// How long to wait in millis for all the child memory reports to come in
const TOTAL_MEMORY_COLLECTOR_TIMEOUT = 200;

var gLastMemoryPoll = null;

var gWasDebuggerAttached = false;

XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   "nsITelemetry");
XPCOMUtils.defineLazyServiceGetter(this, "idleService",
                                   "@mozilla.org/widget/idleservice;1",
                                   "nsIIdleService");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                   "@mozilla.org/childprocessmessagemanager;1",
                                   "nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "cpml",
                                   "@mozilla.org/childprocessmessagemanager;1",
                                   "nsIMessageListenerManager");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                   "@mozilla.org/parentprocessmessagemanager;1",
                                   "nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "ppml",
                                   "@mozilla.org/parentprocessmessagemanager;1",
                                   "nsIMessageListenerManager");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                                  "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStorage",
                                  "resource://gre/modules/TelemetryStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
                                  "resource://gre/modules/TelemetryLog.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
                                  "resource://gre/modules/ThirdPartyCookieProbe.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
                                  "resource://gre/modules/UITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "GCTelemetry",
                                  "resource://gre/modules/GCTelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                  "resource://gre/modules/TelemetryEnvironment.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                  "resource://gre/modules/TelemetryReportingPolicy.jsm");

function generateUUID() {
  let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
  // strip {}
  return str.substring(1, str.length - 1);
}

function getMsSinceProcessStart() {
  try {
    return Telemetry.msSinceProcessStart();
  } catch (ex) {
    // If this fails return a special value.
    return -1;
  }
}

/**
 * This is a policy object used to override behavior for testing.
 */
var Policy = {
  now: () => new Date(),
  monotonicNow: getMsSinceProcessStart,
  generateSessionUUID: () => generateUUID(),
  generateSubsessionUUID: () => generateUUID(),
  setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: id => clearTimeout(id),
};

/**
 * Get the ping type based on the payload.
 * @param {Object} aPayload The ping payload.
 * @return {String} A string representing the ping type.
 */
function getPingType(aPayload) {
  // To remain consistent with server-side ping handling, set "saved-session" as the ping
  // type for "saved-session" payload reasons.
  if (aPayload.info.reason == REASON_SAVED_SESSION) {
    return PING_TYPE_SAVED_SESSION;
  }

  return PING_TYPE_MAIN;
}

/**
 * Annotate the current session ID with the crash reporter to map potential
 * crash pings with the related main ping.
 */
function annotateCrashReport(sessionId) {
  try {
    const cr = Cc["@mozilla.org/toolkit/crash-reporter;1"];
    if (cr) {
      cr.getService(Ci.nsICrashReporter).setTelemetrySessionId(sessionId);
    }
  } catch (e) {
    // Ignore errors when crash reporting is disabled
  }
}

/**
 * Read current process I/O counters.
 */
var processInfo = {
  _initialized: false,
  _IO_COUNTERS: null,
  _kernel32: null,
  _GetProcessIoCounters: null,
  _GetCurrentProcess: null,
  getCounters() {
    let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
    if (isWindows)
      return this.getCounters_Windows();
    return null;
  },
  getCounters_Windows() {
    if (!this._initialized) {
      Cu.import("resource://gre/modules/ctypes.jsm");
      this._IO_COUNTERS = new ctypes.StructType("IO_COUNTERS", [
        {"readOps": ctypes.unsigned_long_long},
        {"writeOps": ctypes.unsigned_long_long},
        {"otherOps": ctypes.unsigned_long_long},
        {"readBytes": ctypes.unsigned_long_long},
        {"writeBytes": ctypes.unsigned_long_long},
        {"otherBytes": ctypes.unsigned_long_long} ]);
      try {
        this._kernel32 = ctypes.open("Kernel32.dll");
        this._GetProcessIoCounters = this._kernel32.declare("GetProcessIoCounters",
          ctypes.winapi_abi,
          ctypes.bool, // return
          ctypes.voidptr_t, // hProcess
          this._IO_COUNTERS.ptr); // lpIoCounters
        this._GetCurrentProcess = this._kernel32.declare("GetCurrentProcess",
          ctypes.winapi_abi,
          ctypes.voidptr_t); // return
        this._initialized = true;
      } catch (err) {
        return null;
      }
    }
    let io = new this._IO_COUNTERS();
    if (!this._GetProcessIoCounters(this._GetCurrentProcess(), io.address()))
      return null;
    return [parseInt(io.readBytes), parseInt(io.writeBytes)];
  }
};

/**
 * TelemetryScheduler contains a single timer driving all regularly-scheduled
 * Telemetry related jobs. Having a single place with this logic simplifies
 * reasoning about scheduling actions in a single place, making it easier to
 * coordinate jobs and coalesce them.
 */
var TelemetryScheduler = {
  _lastDailyPingTime: 0,
  _lastSessionCheckpointTime: 0,

  // For sanity checking.
  _lastAdhocPingTime: 0,
  _lastTickTime: 0,

  _log: null,

  // The timer which drives the scheduler.
  _schedulerTimer: null,
  // The interval used by the scheduler timer.
  _schedulerInterval: 0,
  _shuttingDown: true,
  _isUserIdle: false,

  /**
   * Initialises the scheduler and schedules the first daily/aborted session pings.
   */
  init() {
    this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "TelemetryScheduler::");
    this._log.trace("init");
    this._shuttingDown = false;
    this._isUserIdle = false;

    // Initialize the last daily ping and aborted session last due times to the current time.
    // Otherwise, we might end up sending daily pings even if the subsession is not long enough.
    let now = Policy.now();
    this._lastDailyPingTime = now.getTime();
    this._lastSessionCheckpointTime = now.getTime();
    this._rescheduleTimeout();

    idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
    Services.obs.addObserver(this, "wake_notification");
  },

  /**
   * Stops the scheduler.
   */
  shutdown() {
    if (this._shuttingDown) {
      if (this._log) {
        this._log.error("shutdown - Already shut down");
      } else {
        Cu.reportError("TelemetryScheduler.shutdown - Already shut down");
      }
      return;
    }

    this._log.trace("shutdown");
    if (this._schedulerTimer) {
      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
      this._schedulerTimer = null;
    }

    idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
    Services.obs.removeObserver(this, "wake_notification");

    this._shuttingDown = true;
  },

  _clearTimeout() {
    if (this._schedulerTimer) {
      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
    }
  },

  /**
   * Reschedules the tick timer.
   */
  _rescheduleTimeout() {
    this._log.trace("_rescheduleTimeout - isUserIdle: " + this._isUserIdle);
    if (this._shuttingDown) {
      this._log.warn("_rescheduleTimeout - already shutdown");
      return;
    }

    this._clearTimeout();

    const now = Policy.now();
    let timeout = SCHEDULER_TICK_INTERVAL_MS;

    // When the user is idle we want to fire the timer less often.
    if (this._isUserIdle) {
      timeout = SCHEDULER_TICK_IDLE_INTERVAL_MS;
      // We need to make sure though that we don't miss sending pings around
      // midnight when we use the longer idle intervals.
      const nextMidnight = Utils.getNextMidnight(now);
      timeout = Math.min(timeout, nextMidnight.getTime() - now.getTime());
    }

    this._log.trace("_rescheduleTimeout - scheduling next tick for " + new Date(now.getTime() + timeout));
    this._schedulerTimer =
      Policy.setSchedulerTickTimeout(() => this._onSchedulerTick(), timeout);
  },

  _sentDailyPingToday(nowDate) {
    // This is today's date and also the previous midnight (0:00).
    const todayDate = Utils.truncateToDays(nowDate);
    // We consider a ping sent for today if it occured after or at 00:00 today.
    return (this._lastDailyPingTime >= todayDate.getTime());
  },

  /**
   * Checks if we can send a daily ping or not.
   * @param {Object} nowDate A date object.
   * @return {Boolean} True if we can send the daily ping, false otherwise.
   */
  _isDailyPingDue(nowDate) {
    // The daily ping is not due if we already sent one today.
    if (this._sentDailyPingToday(nowDate)) {
      this._log.trace("_isDailyPingDue - already sent one today");
      return false;
    }

    // Avoid overly short sessions.
    const timeSinceLastDaily = nowDate.getTime() - this._lastDailyPingTime;
    if (timeSinceLastDaily < MIN_SUBSESSION_LENGTH_MS) {
      this._log.trace("_isDailyPingDue - delaying daily to keep minimum session length");
      return false;
    }

    this._log.trace("_isDailyPingDue - is due");
    return true;
  },

  /**
   * An helper function to save an aborted-session ping.
   * @param {Number} now The current time, in milliseconds.
   * @param {Object} [competingPayload=null] If we are coalescing the daily and the
   *                 aborted-session pings, this is the payload for the former. Note
   *                 that the reason field of this payload will be changed.
   * @return {Promise} A promise resolved when the ping is saved.
   */
  _saveAbortedPing(now, competingPayload = null) {
    this._lastSessionCheckpointTime = now;
    return Impl._saveAbortedSessionPing(competingPayload)
                .catch(e => this._log.error("_saveAbortedPing - Failed", e));
  },

  /**
   * The notifications handler.
   */
  observe(aSubject, aTopic, aData) {
    this._log.trace("observe - aTopic: " + aTopic);
    switch (aTopic) {
      case "idle":
        // If the user is idle, increase the tick interval.
        this._isUserIdle = true;
        return this._onSchedulerTick();
      case "active":
        // User is back to work, restore the original tick interval.
        this._isUserIdle = false;
        return this._onSchedulerTick(true);
      case "wake_notification":
        // The machine woke up from sleep, trigger a tick to avoid sessions
        // spanning more than a day.
        // This is needed because sleep time does not count towards timeouts
        // on Mac & Linux - see bug 1262386, bug 1204823 et al.
        return this._onSchedulerTick(true);
    }
    return undefined;
  },

  /**
   * Performs a scheduler tick. This function manages Telemetry recurring operations.
   * @param {Boolean} [dispatchOnIdle=false] If true, the tick is dispatched in the
   *                  next idle cycle of the main thread.
   * @return {Promise} A promise, only used when testing, resolved when the scheduled
   *                   operation completes.
   */
  _onSchedulerTick(dispatchOnIdle = false) {
    // This call might not be triggered from a timeout. In that case we don't want to
    // leave any previously scheduled timeouts pending.
    this._clearTimeout();

    if (this._shuttingDown) {
      this._log.warn("_onSchedulerTick - already shutdown.");
      return Promise.reject(new Error("Already shutdown."));
    }

    let promise = Promise.resolve();
    try {
      if (dispatchOnIdle) {
        promise = new Promise((resolve, reject) =>
          Services.tm.idleDispatchToMainThread(() => this._schedulerTickLogic().then(resolve, reject)),
                                               SCHEDULER_TICK_MAX_IDLE_DELAY_MS);
      } else {
        promise = this._schedulerTickLogic();
      }
    } catch (e) {
      Telemetry.getHistogramById("TELEMETRY_SCHEDULER_TICK_EXCEPTION").add(1);
      this._log.error("_onSchedulerTick - There was an exception", e);
    } finally {
      this._rescheduleTimeout();
    }

    // This promise is returned to make testing easier.
    return promise;
  },

  /**
   * Implements the scheduler logic.
   * @return {Promise} Resolved when the scheduled task completes. Only used in tests.
   */
  _schedulerTickLogic() {
    this._log.trace("_schedulerTickLogic");

    let nowDate = Policy.now();
    let now = nowDate.getTime();

    if ((now - this._lastTickTime) > (1.1 * SCHEDULER_TICK_INTERVAL_MS) &&
        (this._lastTickTime != 0)) {
      Telemetry.getHistogramById("TELEMETRY_SCHEDULER_WAKEUP").add(1);
      this._log.trace("_schedulerTickLogic - First scheduler tick after sleep.");
    }
    this._lastTickTime = now;

    // Check if the daily ping is due.
    const shouldSendDaily = this._isDailyPingDue(nowDate);

    if (shouldSendDaily) {
      this._log.trace("_schedulerTickLogic - Daily ping due.");
      this._lastDailyPingTime = now;
      return Impl._sendDailyPing();
    }

    // Check if the aborted-session ping is due. If a daily ping was saved above, it was
    // already duplicated as an aborted-session ping.
    const isAbortedPingDue =
      (now - this._lastSessionCheckpointTime) >= ABORTED_SESSION_UPDATE_INTERVAL_MS;
    if (isAbortedPingDue) {
      this._log.trace("_schedulerTickLogic - Aborted session ping due.");
      return this._saveAbortedPing(now);
    }

    // No ping is due.
    this._log.trace("_schedulerTickLogic - No ping due.");
    return Promise.resolve();
  },

  /**
   * Update the scheduled pings if some other ping was sent.
   * @param {String} reason The reason of the ping that was sent.
   * @param {Object} [competingPayload=null] The payload of the ping that was sent. The
   *                 reason of this payload will be changed.
   */
  reschedulePings(reason, competingPayload = null) {
    if (this._shuttingDown) {
      this._log.error("reschedulePings - already shutdown");
      return;
    }

    this._log.trace("reschedulePings - reason: " + reason);
    let now = Policy.now();
    this._lastAdhocPingTime = now.getTime();
    if (reason == REASON_ENVIRONMENT_CHANGE) {
      // We just generated an environment-changed ping, save it as an aborted session and
      // update the schedules.
      this._saveAbortedPing(now.getTime(), competingPayload);
      // If we're close to midnight, skip today's daily ping and reschedule it for tomorrow.
      let nearestMidnight = Utils.getNearestMidnight(now, SCHEDULER_MIDNIGHT_TOLERANCE_MS);
      if (nearestMidnight) {
        this._lastDailyPingTime = now.getTime();
      }
    }

    this._rescheduleTimeout();
  },
};

this.EXPORTED_SYMBOLS = ["TelemetrySession"];

this.TelemetrySession = Object.freeze({
  /**
   * Send a ping to a test server. Used only for testing.
   */
  testPing() {
    return Impl.testPing();
  },
  /**
   * Returns the current telemetry payload.
   * @param reason Optional, the reason to trigger the payload.
   * @param clearSubsession Optional, whether to clear subsession specific data.
   * @returns Object
   */
  getPayload(reason, clearSubsession = false) {
    return Impl.getPayload(reason, clearSubsession);
  },
  /**
   * Returns a promise that resolves to an array of thread hang stats from content processes, one entry per process.
   * The structure of each entry is identical to that of "threadHangStats" in nsITelemetry.
   * While thread hang stats are also part of the child payloads, this function is useful for cheaply getting this information,
   * which is useful for realtime hang monitoring.
   * Child processes that do not respond, or spawn/die during execution of this function are excluded from the result.
   * @returns Promise
   */
  getChildThreadHangs() {
    return Impl.getChildThreadHangs();
  },
  /**
   * Save the session state to a pending file.
   * Used only for testing purposes.
   */
  testSavePendingPing() {
    return Impl.testSavePendingPing();
  },
  /**
   * Collect and store information about startup.
   */
  gatherStartup() {
    return Impl.gatherStartup();
  },
  /**
   * Inform the ping which AddOns are installed.
   *
   * @param aAddOns - The AddOns.
   */
  setAddOns(aAddOns) {
    return Impl.setAddOns(aAddOns);
  },
  /**
   * Descriptive metadata
   *
   * @param  reason
   *         The reason for the telemetry ping, this will be included in the
   *         returned metadata,
   * @return The metadata as a JS object
   */
  getMetadata(reason) {
    return Impl.getMetadata(reason);
  },
  /**
   * Used only for testing purposes.
   */
  testReset() {
    Impl._newProfilePingSent = false;
    Impl._sessionId = null;
    Impl._subsessionId = null;
    Impl._previousSessionId = null;
    Impl._previousSubsessionId = null;
    Impl._subsessionCounter = 0;
    Impl._profileSubsessionCounter = 0;
    Impl._subsessionStartActiveTicks = 0;
    Impl._sessionActiveTicks = 0;
    Impl._isUserActive = true;
    Impl._subsessionStartTimeMonotonic = 0;
    Impl._lastEnvironmentChangeDate = Policy.monotonicNow();
    this.testUninstall();
  },
  /**
   * Triggers shutdown of the module.
   */
  shutdown() {
    return Impl.shutdownChromeProcess();
  },
  /**
   * Sets up components used in the content process.
   */
  setupContent(testing = false) {
    return Impl.setupContentProcess(testing);
  },
  /**
   * Used only for testing purposes.
   */
  testUninstall() {
    try {
      Impl.uninstall();
    } catch (ex) {
      // Ignore errors
    }
  },
  /**
   * Lightweight init function, called as soon as Firefox starts.
   */
  earlyInit(aTesting = false) {
    return Impl.earlyInit(aTesting);
  },
  /**
   * Does the "heavy" Telemetry initialization later on, so we
   * don't impact startup performance.
   * @return {Promise} Resolved when the initialization completes.
   */
  delayedInit() {
    return Impl.delayedInit();
  },
  /**
   * Send a notification.
   */
  observe(aSubject, aTopic, aData) {
    return Impl.observe(aSubject, aTopic, aData);
  },
  /**
   * Marks the "new-profile" ping as sent in the telemetry state file.
   * @return {Promise} A promise resolved when the new telemetry state is saved to disk.
   */
  markNewProfilePingSent() {
    return Impl.markNewProfilePingSent();
  },
  /**
   * Returns if the "new-profile" ping has ever been sent for this profile.
   * Please note that the returned value is trustworthy only after the delayed setup.
   *
   * @return {Boolean} True if the new profile ping was sent on this profile,
   *         false otherwise.
   */
  get newProfilePingSent() {
    return Impl._newProfilePingSent;
  },
});

var Impl = {
  _histograms: {},
  _initialized: false,
  _logger: null,
  _prevValues: {},
  _slowSQLStartup: {},
  // The activity state for the user. If false, don't count the next
  // active tick. Otherwise, increment the active ticks as usual.
  _isUserActive: true,
  _startupIO: {},
  // The previous build ID, if this is the first run with a new build.
  // Null if this is the first run, or the previous build ID is unknown.
  _previousBuildId: null,
  // Telemetry payloads sent by child processes.
  // Each element is in the format {source: <weak-ref>, payload: <object>},
  // where source is a weak reference to the child process,
  // and payload is the telemetry payload from that child process.
  _childTelemetry: [],
  // Thread hangs from child processes.
  // Used for TelemetrySession.getChildThreadHangs(); not sent with Telemetry pings.
  // TelemetrySession.getChildThreadHangs() is used by extensions such as Statuser (https://github.com/chutten/statuser).
  // Each element is in the format {source: <weak-ref>, payload: <object>},
  // where source is a weak reference to the child process,
  // and payload contains the thread hang stats from that child process.
  _childThreadHangs: [],
  // Array of the resolve functions of all the promises that are waiting for the child thread hang stats to arrive, used to resolve all those promises at once.
  _childThreadHangsResolveFunctions: [],
  // Timeout function for child thread hang stats retrieval.
  _childThreadHangsTimeout: null,
  // Unique id that identifies this session so the server can cope with duplicate
  // submissions, orphaning and other oddities. The id is shared across subsessions.
  _sessionId: null,
  // Random subsession id.
  _subsessionId: null,
  // Session id of the previous session, null on first run.
  _previousSessionId: null,
  // Subsession id of the previous subsession (even if it was in a different session),
  // null on first run.
  _previousSubsessionId: null,
  // The running no. of subsessions since the start of the browser session
  _subsessionCounter: 0,
  // The running no. of all subsessions for the whole profile life time
  _profileSubsessionCounter: 0,
  // Date of the last session split
  _subsessionStartDate: null,
  // Start time of the current subsession using a monotonic clock for the subsession
  // length measurements.
  _subsessionStartTimeMonotonic: 0,
  // The active ticks counted when the subsession starts
  _subsessionStartActiveTicks: 0,
  // Active ticks in the whole session.
  _sessionActiveTicks: 0,
  // A task performing delayed initialization of the chrome process
  _delayedInitTask: null,
  // Need a timeout in case children are tardy in giving back their memory reports.
  _totalMemoryTimeout: undefined,
  _testing: false,
  // An accumulator of total memory across all processes. Only valid once the final child reports.
  _totalMemory: null,
  // A Set of outstanding USS report ids
  _childrenToHearFrom: null,
  // monotonically-increasing id for USS reports
  _nextTotalMemoryId: 1,
  _USSFromChildProcesses: null,
  _lastEnvironmentChangeDate: 0,
  // We save whether the "new-profile" ping was sent yet, to
  // survive profile refresh and migrations.
  _newProfilePingSent: false,
  // Keep track of the active observers
  _observedTopics: new Set(),

  addObserver(aTopic) {
    Services.obs.addObserver(this, aTopic)
    this._observedTopics.add(aTopic)
  },

  removeObserver(aTopic) {
    Services.obs.removeObserver(this, aTopic)
    this._observedTopics.delete(aTopic)
  },

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }
    return this._logger;
  },

  /**
   * Gets a series of simple measurements (counters). At the moment, this
   * only returns startup data from nsIAppStartup.getStartupInfo().
   * @param {Boolean} isSubsession True if this is a subsession, false otherwise.
   * @param {Boolean} clearSubsession True if a new subsession is being started, false otherwise.
   *
   * @return simple measurements as a dictionary.
   */
  getSimpleMeasurements: function getSimpleMeasurements(forSavedSession, isSubsession, clearSubsession) {
    let si = Services.startup.getStartupInfo();

    // Measurements common to chrome and content processes.
    let elapsedTime = Date.now() - si.process;
    var ret = {
      totalTime: Math.round(elapsedTime / 1000), // totalTime, in seconds
      uptime: Math.round(elapsedTime / 60000) // uptime in minutes
    }

    // Look for app-specific timestamps
    var appTimestamps = {};
    try {
      let o = {};
      Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", o);
      appTimestamps = o.TelemetryTimestamps.get();
    } catch (ex) {}

    // Only submit this if the extended set is enabled.
    if (!Utils.isContentProcess && Telemetry.canRecordExtended) {
      try {
        ret.addonManager = AddonManagerPrivate.getSimpleMeasures();
        ret.UITelemetry = UITelemetry.getSimpleMeasures();
      } catch (ex) {}
    }

    if (si.process) {
      for (let field of Object.keys(si)) {
        if (field == "process")
          continue;
        ret[field] = si[field] - si.process
      }

      for (let p in appTimestamps) {
        if (!(p in ret) && appTimestamps[p])
          ret[p] = appTimestamps[p] - si.process;
      }
    }

    ret.startupInterrupted = Number(Services.startup.interrupted);

    ret.js = Cu.getJSEngineTelemetryValue();

    let maximalNumberOfConcurrentThreads = Telemetry.maximalNumberOfConcurrentThreads;
    if (maximalNumberOfConcurrentThreads) {
      ret.maximalNumberOfConcurrentThreads = maximalNumberOfConcurrentThreads;
    }

    if (Utils.isContentProcess) {
      return ret;
    }

    // Measurements specific to chrome process

    // Update debuggerAttached flag
    let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
    let isDebuggerAttached = debugService.isDebuggerAttached;
    gWasDebuggerAttached = gWasDebuggerAttached || isDebuggerAttached;
    ret.debuggerAttached = Number(gWasDebuggerAttached);

    let shutdownDuration = Telemetry.lastShutdownDuration;
    if (shutdownDuration)
      ret.shutdownDuration = shutdownDuration;

    let failedProfileLockCount = Telemetry.failedProfileLockCount;
    if (failedProfileLockCount)
      ret.failedProfileLockCount = failedProfileLockCount;

    for (let ioCounter in this._startupIO)
      ret[ioCounter] = this._startupIO[ioCounter];

    ret.savedPings = TelemetryStorage.pendingPingCount;

    let activeTicks = this._sessionActiveTicks;
    if (isSubsession) {
      activeTicks = this._sessionActiveTicks - this._subsessionStartActiveTicks;
    }

    if (clearSubsession) {
      this._subsessionStartActiveTicks = activeTicks;
    }

    ret.activeTicks = activeTicks;

    ret.pingsOverdue = TelemetrySend.overduePingsCount;

    return ret;
  },

  /**
   * When reflecting a histogram into JS, Telemetry hands us an object
   * with the following properties:
   *
   * - min, max, histogram_type, sum, sum_squares_{lo,hi}: simple integers;
   * - counts: array of counts for histogram buckets;
   * - ranges: array of calculated bucket sizes.
   *
   * This format is not straightforward to read and potentially bulky
   * with lots of zeros in the counts array.  Packing histograms makes
   * raw histograms easier to read and compresses the data a little bit.
   *
   * Returns an object:
   * { range: [min, max], bucket_count: <number of buckets>,
   *   histogram_type: <histogram_type>, sum: <sum>,
   *   values: { bucket1: count1, bucket2: count2, ... } }
   */
  packHistogram: function packHistogram(hgram) {
    let r = hgram.ranges;
    let c = hgram.counts;
    let retgram = {
      range: [r[1], r[r.length - 1]],
      bucket_count: r.length,
      histogram_type: hgram.histogram_type,
      values: {},
      sum: hgram.sum
    };

    let first = true;
    let last = 0;

    for (let i = 0; i < c.length; i++) {
      let value = c[i];
      if (!value)
        continue;

      // add a lower bound
      if (i && first) {
        retgram.values[r[i - 1]] = 0;
      }
      first = false;
      last = i + 1;
      retgram.values[r[i]] = value;
    }

    // add an upper bound
    if (last && last < c.length)
      retgram.values[r[last]] = 0;
    return retgram;
  },

  /**
   * Get the type of the dataset that needs to be collected, based on the preferences.
   * @return {Integer} A value from nsITelemetry.DATASET_*.
   */
  getDatasetType() {
    return Telemetry.canRecordExtended ? Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN
                                       : Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
  },

  getHistograms: function getHistograms(subsession, clearSubsession) {
    let registered =
      Telemetry.registeredHistograms(this.getDatasetType(), []);
    if (this._testing == false) {
      // Omit telemetry test histograms outside of tests.
      registered = registered.filter(n => !n.startsWith("TELEMETRY_TEST_"));
    }
    registered = registered.concat(registered.map(n => "STARTUP_" + n));

    let hls = subsession ? Telemetry.snapshotSubsessionHistograms(clearSubsession)
                         : Telemetry.histogramSnapshots;
    let ret = {};

    for (let [process, histograms] of Object.entries(hls)) {
      ret[process] = {};
      for (let [name, value] of Object.entries(histograms)) {
        if (registered.includes(name)) {
          ret[process][name] = this.packHistogram(value);
        }
      }
    }

    return ret;
  },

  getKeyedHistograms(subsession, clearSubsession) {
    let registered =
      Telemetry.registeredKeyedHistograms(this.getDatasetType(), []);
    if (this._testing == false) {
      // Omit telemetry test histograms outside of tests.
      registered = registered.filter(id => !id.startsWith("TELEMETRY_TEST_"));
    }

    let khs = subsession ? Telemetry.snapshotSubsessionKeyedHistograms(clearSubsession)
                         : Telemetry.keyedHistogramSnapshots;
    let ret = {};

    for (let [process, histograms] of Object.entries(khs)) {
      ret[process] = {};
      for (let [name, value] of Object.entries(histograms)) {
        if (registered.includes(name)) {
          let keys = Object.keys(value);
          if (keys.length == 0) {
            // Skip empty keyed histogram
            continue;
          }
          ret[process][name] = {};
          for (let [key, hgram] of Object.entries(value)) {
            ret[process][name][key] = this.packHistogram(hgram);
          }
        }
      }
    }

    return ret;
  },

  /**
   * Get a snapshot of the scalars and clear them.
   * @param {subsession} If true, then we collect the data for a subsession.
   * @param {clearSubsession} If true, we  need to clear the subsession.
   * @param {keyed} Take a snapshot of keyed or non keyed scalars.
   * @return {Object} The scalar data as a Javascript object, including the
   *         data from child processes, in the following format:
   *            {'content': { 'scalarName': ... }, 'gpu': { ... } }
   */
  getScalars(subsession, clearSubsession, keyed) {
    if (!subsession) {
      // We only support scalars for subsessions.
      this._log.trace("getScalars - We only support scalars in subsessions.");
      return {};
    }

    let scalarsSnapshot = keyed ?
      Telemetry.snapshotKeyedScalars(this.getDatasetType(), clearSubsession) :
      Telemetry.snapshotScalars(this.getDatasetType(), clearSubsession);

    // Don't return the test scalars.
    let ret = {};
    for (let processName in scalarsSnapshot) {
      for (let name in scalarsSnapshot[processName]) {
        if (name.startsWith("telemetry.test") && this._testing == false) {
          continue;
        }
        // Finally arrange the data in the returned object.
        if (!(processName in ret)) {
          ret[processName] = {};
        }
        ret[processName][name] = scalarsSnapshot[processName][name];
      }
    }

    return ret;
  },

  getEvents(isSubsession, clearSubsession) {
    if (!isSubsession) {
      // We only support scalars for subsessions.
      this._log.trace("getEvents - We only support events in subsessions.");
      return [];
    }

    let snapshot = Telemetry.snapshotEvents(this.getDatasetType(),
                                            clearSubsession);

    // Don't return the test events outside of test environments.
    if (!this._testing) {
      for (let proc of Object.keys(snapshot)) {
        snapshot[proc] = snapshot[proc].filter(e => !e[1].startsWith("telemetry.test"));
      }
    }

    return snapshot;
  },

  getThreadHangStats: function getThreadHangStats(stats) {
    stats.forEach((thread) => {
      thread.activity = this.packHistogram(thread.activity);
      thread.hangs.forEach((hang) => {
        hang.histogram = this.packHistogram(hang.histogram);
      });
    });
    return stats;
  },

  /**
   * Descriptive metadata
   *
   * @param  reason
   *         The reason for the telemetry ping, this will be included in the
   *         returned metadata,
   * @return The metadata as a JS object
   */
  getMetadata: function getMetadata(reason) {
    const sessionStartDate = Utils.toLocalTimeISOString(Utils.truncateToHours(this._sessionStartDate));
    const subsessionStartDate = Utils.toLocalTimeISOString(Utils.truncateToHours(this._subsessionStartDate));
    const monotonicNow = Policy.monotonicNow();

    let ret = {
      reason,
      revision: AppConstants.SOURCE_REVISION_URL,

      // Date.getTimezoneOffset() unintuitively returns negative values if we are ahead of
      // UTC and vice versa (e.g. -60 for UTC+1). We invert the sign here.
      timezoneOffset: -this._subsessionStartDate.getTimezoneOffset(),
      previousBuildId: this._previousBuildId,

      sessionId: this._sessionId,
      subsessionId: this._subsessionId,
      previousSessionId: this._previousSessionId,
      previousSubsessionId: this._previousSubsessionId,

      subsessionCounter: this._subsessionCounter,
      profileSubsessionCounter: this._profileSubsessionCounter,

      sessionStartDate,
      subsessionStartDate,

      // Compute the session and subsession length in seconds.
      // We use monotonic clocks as Date() is affected by jumping clocks (leading
      // to negative lengths and other issues).
      sessionLength: Math.floor(monotonicNow / 1000),
      subsessionLength:
        Math.floor((monotonicNow - this._subsessionStartTimeMonotonic) / 1000),
    };

    // TODO: Remove this when bug 1201837 lands.
    if (this._addons)
      ret.addons = this._addons;

    // TODO: Remove this when bug 1201837 lands.
    let flashVersion = this.getFlashVersion();
    if (flashVersion)
      ret.flashVersion = flashVersion;

    return ret;
  },

  /**
   * Pull values from about:memory into corresponding histograms
   */
  gatherMemory: function gatherMemory() {
    let mgr;
    try {
      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
            getService(Ci.nsIMemoryReporterManager);
    } catch (e) {
      // OK to skip memory reporters in xpcshell
      return;
    }

    let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS");
    let startTime = new Date();

    // Get memory measurements from distinguished amount attributes.  We used
    // to measure "explicit" too, but it could cause hangs, and the data was
    // always really noisy anyway.  See bug 859657.
    //
    // test_TelemetryController.js relies on some of these histograms being
    // here.  If you remove any of the following histograms from here, you'll
    // have to modify test_TelemetryController.js:
    //
    //   * MEMORY_JS_GC_HEAP, and
    //   * MEMORY_JS_COMPARTMENTS_SYSTEM.
    //
    // The distinguished amount attribute names don't match the telemetry id
    // names in some cases due to a combination of (a) historical reasons, and
    // (b) the fact that we can't change telemetry id names without breaking
    // data continuity.
    //
    let boundHandleMemoryReport = this.handleMemoryReport.bind(this);
    let h = (id, units, amountName) => {
      try {
        // If mgr[amountName] throws an exception, just move on -- some amounts
        // aren't available on all platforms.  But if the attribute simply
        // isn't present, that indicates the distinguished amounts have changed
        // and this file hasn't been updated appropriately.
        let amount = mgr[amountName];
        if (amount === undefined) {
          this._log.error("gatherMemory - telemetry accessed an unknown distinguished amount");
        }
        boundHandleMemoryReport(id, units, amount);
      } catch (e) {
      }
    }
    let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n);
    let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n);
    let cc = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE, n);
    let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n);

    // GHOST_WINDOWS is opt-out as of Firefox 55
    c("GHOST_WINDOWS", "ghostWindows");

    if (!Telemetry.canRecordExtended) {
      return;
    }

    b("MEMORY_VSIZE", "vsize");
    b("MEMORY_VSIZE_MAX_CONTIGUOUS", "vsizeMaxContiguous");
    b("MEMORY_RESIDENT_FAST", "residentFast");
    b("MEMORY_UNIQUE", "residentUnique");
    p("MEMORY_HEAP_OVERHEAD_FRACTION", "heapOverheadFraction");
    b("MEMORY_JS_GC_HEAP", "JSMainRuntimeGCHeap");
    c("MEMORY_JS_COMPARTMENTS_SYSTEM", "JSMainRuntimeCompartmentsSystem");
    c("MEMORY_JS_COMPARTMENTS_USER", "JSMainRuntimeCompartmentsUser");
    b("MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED", "imagesContentUsedUncompressed");
    b("MEMORY_STORAGE_SQLITE", "storageSQLite");
    cc("LOW_MEMORY_EVENTS_VIRTUAL", "lowMemoryEventsVirtual");
    cc("LOW_MEMORY_EVENTS_PHYSICAL", "lowMemoryEventsPhysical");
    cc("PAGE_FAULTS_HARD", "pageFaultsHard");

    try {
      mgr.getHeapAllocatedAsync(heapAllocated => {
        boundHandleMemoryReport("MEMORY_HEAP_ALLOCATED",
                                Ci.nsIMemoryReporter.UNITS_BYTES,
                                heapAllocated);
      });
    } catch (e) {
    }

    if (!Utils.isContentProcess && !this._totalMemoryTimeout) {
      // Only the chrome process should gather total memory
      // total = parent RSS + sum(child USS)
      this._totalMemory = mgr.residentFast;
      if (ppmm.childCount > 1) {
        // Do not report If we time out waiting for the children to call
        this._totalMemoryTimeout = setTimeout(
          () => {
            this._totalMemoryTimeout = undefined;
            this._childrenToHearFrom.clear();
          },
          TOTAL_MEMORY_COLLECTOR_TIMEOUT);
        this._USSFromChildProcesses = [];
        this._childrenToHearFrom = new Set();
        for (let i = 1; i < ppmm.childCount; i++) {
          let child = ppmm.getChildAt(i);
          try {
            child.sendAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_USS, {id: this._nextTotalMemoryId});
            this._childrenToHearFrom.add(this._nextTotalMemoryId);
            this._nextTotalMemoryId++;
          } catch (ex) {
            // If a content process has just crashed, then attempting to send it
            // an async message will throw an exception.
            Cu.reportError(ex);
          }
        }
      } else {
        boundHandleMemoryReport(
          "MEMORY_TOTAL",
          Ci.nsIMemoryReporter.UNITS_BYTES,
          this._totalMemory);
      }
    }

    histogram.add(new Date() - startTime);
  },

  handleMemoryReport(id, units, amount, key) {
    let val;
    if (units == Ci.nsIMemoryReporter.UNITS_BYTES) {
      val = Math.floor(amount / 1024);
    } else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) {
      // UNITS_PERCENTAGE amounts are 100x greater than their raw value.
      val = Math.floor(amount / 100);
    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT) {
      val = amount;
    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) {
      // If the reporter gives us a cumulative count, we'll report the
      // difference in its value between now and our previous ping.

      if (!(id in this._prevValues)) {
        // If this is the first time we're reading this reporter, store its
        // current value but don't report it in the telemetry ping, so we
        // ignore the effect startup had on the reporter.
        this._prevValues[id] = amount;
        return;
      }

      val = amount - this._prevValues[id];
      this._prevValues[id] = amount;
    } else {
      this._log.error("handleMemoryReport - Can't handle memory reporter with units " + units);
      return;
    }

    let h = this._histograms[id];
    if (!h) {
      if (key) {
        h = Telemetry.getKeyedHistogramById(id);
      } else {
        h = Telemetry.getHistogramById(id);
      }
      this._histograms[id] = h;
    }

    if (key) {
      h.add(key, val);
    } else {
      h.add(val);
    }
  },

  getChildPayloads: function getChildPayloads() {
    return this._childTelemetry.map(child => child.payload);
  },

  /**
   * Get the current session's payload using the provided
   * simpleMeasurements and info, which are typically obtained by a call
   * to |this.getSimpleMeasurements| and |this.getMetadata|,
   * respectively.
   */
  assemblePayloadWithMeasurements(simpleMeasurements, info, reason, clearSubsession) {
    const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
    clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
    this._log.trace("assemblePayloadWithMeasurements - reason: " + reason +
                    ", submitting subsession data: " + isSubsession);

    // This allows wrapping data retrieval calls in a try-catch block so that
    // failures don't break the rest of the ping assembly.
    const protect = (fn, defaultReturn = null) => {
      try {
        return fn();
      } catch (ex) {
        this._log.error("assemblePayloadWithMeasurements - caught exception", ex);
        return defaultReturn;
      }
    };

    // Payload common to chrome and content processes.
    let payloadObj = {
      ver: PAYLOAD_VERSION,
      simpleMeasurements,
    };

    // Add extended set measurements common to chrome & content processes
    if (Telemetry.canRecordExtended) {
      payloadObj.chromeHangs = protect(() => Telemetry.chromeHangs);
      payloadObj.threadHangStats = protect(() => this.getThreadHangStats(Telemetry.threadHangStats));
      payloadObj.log = protect(() => TelemetryLog.entries());
      payloadObj.webrtc = protect(() => Telemetry.webrtcStats);
    }

    if (Utils.isContentProcess) {
      return payloadObj;
    }

    // Additional payload for chrome process.
    let histograms = protect(() => this.getHistograms(isSubsession, clearSubsession), {});
    let keyedHistograms = protect(() => this.getKeyedHistograms(isSubsession, clearSubsession), {});
    let scalars = protect(() => this.getScalars(isSubsession, clearSubsession), {});
    let keyedScalars = protect(() => this.getScalars(isSubsession, clearSubsession, true), {});
    let events = protect(() => this.getEvents(isSubsession, clearSubsession))

    payloadObj.histograms = histograms.parent || {};
    payloadObj.keyedHistograms = keyedHistograms.parent || {};
    payloadObj.processes = {
      parent: {
        scalars: scalars["parent"] || {},
        keyedScalars: keyedScalars["parent"] || {},
        events: events["parent"] || [],
      },
      content: {
        scalars: scalars["content"],
        keyedScalars: keyedScalars["content"],
        histograms: histograms["content"],
        keyedHistograms: keyedHistograms["content"],
        events: events["content"] || [],
      },
      extension: {
        scalars: scalars["extension"],
        keyedScalars: keyedScalars["extension"],
        histograms: histograms["extension"],
        keyedHistograms: keyedHistograms["extension"],
        events: events["extension"] || [],
      },
      dynamic: {
        events: events.dynamic || [],
      },
    };

    // Only include the GPU process if we've accumulated data for it.
    if ("gpu" in histograms ||
        "gpu" in keyedHistograms ||
        "gpu" in scalars ||
        "gpu" in keyedScalars) {
      payloadObj.processes.gpu = {
        scalars: scalars["gpu"],
        keyedScalars: keyedScalars["gpu"],
        histograms: histograms["gpu"],
        keyedHistograms: keyedHistograms["gpu"],
        events: events["gpu"] || [],
      };
    }

    payloadObj.info = info;

    // Add extended set measurements for chrome process.
    if (Telemetry.canRecordExtended) {
      payloadObj.slowSQL = protect(() => Telemetry.slowSQL);
      payloadObj.fileIOReports = protect(() => Telemetry.fileIOReports);
      payloadObj.lateWrites = protect(() => Telemetry.lateWrites);

      payloadObj.addonDetails = protect(() => AddonManagerPrivate.getTelemetryDetails());

      let clearUIsession = !(reason == REASON_GATHER_PAYLOAD || reason == REASON_GATHER_SUBSESSION_PAYLOAD);

      if (AppConstants.platform == "android") {
        payloadObj.UIMeasurements = protect(() => UITelemetry.getUIMeasurements(clearUIsession));
      }

      if (this._slowSQLStartup &&
          Object.keys(this._slowSQLStartup).length != 0 &&
          (Object.keys(this._slowSQLStartup.mainThread).length ||
           Object.keys(this._slowSQLStartup.otherThreads).length)) {
        payloadObj.slowSQLStartup = this._slowSQLStartup;
      }

      if (!this._isClassicReason(reason)) {
        payloadObj.processes.parent.gc = protect(() => GCTelemetry.entries("main", clearSubsession));
        payloadObj.processes.content.gc = protect(() => GCTelemetry.entries("content", clearSubsession));
      }

      // Adding captured stacks to the payload only if any exist and clearing
      // captures for this sub-session.
      let stacks = protect(() => Telemetry.snapshotCapturedStacks(true));
      if (stacks && ("captures" in stacks) && (stacks.captures.length > 0)) {
        payloadObj.processes.parent.capturedStacks = stacks;
      }
    }

    if (this._childTelemetry.length) {
      payloadObj.childPayloads = protect(() => this.getChildPayloads());
    }

    return payloadObj;
  },

  /**
   * Start a new subsession.
   */
  startNewSubsession() {
    this._subsessionStartDate = Policy.now();
    this._subsessionStartTimeMonotonic = Policy.monotonicNow();
    this._previousSubsessionId = this._subsessionId;
    this._subsessionId = Policy.generateSubsessionUUID();
    this._subsessionCounter++;
    this._profileSubsessionCounter++;
  },

  getSessionPayload: function getSessionPayload(reason, clearSubsession) {
    this._log.trace("getSessionPayload - reason: " + reason + ", clearSubsession: " + clearSubsession);

    let payload;
    try {
      const isMobile = (AppConstants.platform == "android");
      const isSubsession = isMobile ? false : !this._isClassicReason(reason);

      if (isMobile) {
        clearSubsession = false;
      }

      let measurements =
        this.getSimpleMeasurements(reason == REASON_SAVED_SESSION, isSubsession, clearSubsession);
      let info = !Utils.isContentProcess ? this.getMetadata(reason) : null;
      payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession);
    } catch (ex) {
      Telemetry.getHistogramById("TELEMETRY_ASSEMBLE_PAYLOAD_EXCEPTION").add(1);
      throw ex;
    } finally {
      if (!Utils.isContentProcess && clearSubsession) {
        this.startNewSubsession();
        // Persist session data to disk (don't wait until it completes).
        let sessionData = this._getSessionDataObject();
        TelemetryStorage.saveSessionData(sessionData);

        // Notify that there was a subsession split in the parent process. This is an
        // internal topic and is only meant for internal Telemetry usage.
        Services.obs.notifyObservers(null, "internal-telemetry-after-subsession-split");
      }
    }

    return payload;
  },

  /**
   * Send data to the server. Record success/send-time in histograms
   */
  send: function send(reason) {
    this._log.trace("send - Reason " + reason);
    // populate histograms one last time
    this.gatherMemory();

    const isSubsession = !this._isClassicReason(reason);
    let payload = this.getSessionPayload(reason, isSubsession);
    let options = {
      addClientId: true,
      addEnvironment: true,
    };
    return TelemetryController.submitExternalPing(getPingType(payload), payload, options);
  },

  /**
   * Attaches the needed observers during Telemetry early init, in the
   * chrome process.
   */
  attachEarlyObservers() {
    this.addObserver("sessionstore-windows-restored");
    if (AppConstants.platform === "android") {
      this.addObserver("application-background");
    }
    this.addObserver("xul-window-visible");

    // Attach the active-ticks related observers.
    this.addObserver("user-interaction-active");
    this.addObserver("user-interaction-inactive");
  },

  attachObservers: function attachObservers() {
    if (!this._initialized)
      return;
    this.addObserver("idle-daily");
    if (Telemetry.canRecordExtended) {
      this.addObserver(TOPIC_CYCLE_COLLECTOR_BEGIN);
    }
  },


  /**
   * Lightweight init function, called as soon as Firefox starts.
   */
  earlyInit(testing) {
    this._log.trace("earlyInit");

    this._initStarted = true;
    this._testing = testing;

    if (this._initialized && !testing) {
      this._log.error("earlyInit - already initialized");
      return;
    }

    if (!Telemetry.canRecordBase && !testing) {
      this._log.config("earlyInit - Telemetry recording is disabled, skipping Chrome process setup.");
      return;
    }

    // Generate a unique id once per session so the server can cope with duplicate
    // submissions, orphaning and other oddities. The id is shared across subsessions.
    this._sessionId = Policy.generateSessionUUID();
    this.startNewSubsession();
    // startNewSubsession sets |_subsessionStartDate| to the current date/time. Use
    // the very same value for |_sessionStartDate|.
    this._sessionStartDate = this._subsessionStartDate;

    annotateCrashReport(this._sessionId);

    // Initialize some probes that are kept in their own modules
    this._thirdPartyCookies = new ThirdPartyCookieProbe();
    this._thirdPartyCookies.init();

    // Record old value and update build ID preference if this is the first
    // run with a new build ID.
    let previousBuildId = Services.prefs.getStringPref(TelemetryUtils.Preferences.PreviousBuildID, null);
    let thisBuildID = Services.appinfo.appBuildID;
    // If there is no previousBuildId preference, we send null to the server.
    if (previousBuildId != thisBuildID) {
      this._previousBuildId = previousBuildId;
      Services.prefs.setStringPref(TelemetryUtils.Preferences.PreviousBuildID, thisBuildID);
    }

    this.attachEarlyObservers();

    ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this);
    ppml.addMessageListener(MESSAGE_TELEMETRY_THREAD_HANGS, this);
    ppml.addMessageListener(MESSAGE_TELEMETRY_USS, this);
  },

  /**
   * Does the "heavy" Telemetry initialization later on, so we
   * don't impact startup performance.
   * @return {Promise} Resolved when the initialization completes.
   */
  delayedInit() {
    this._log.trace("delayedInit");

    this._delayedInitTask = (async () => {
      try {
        this._initialized = true;

        await this._loadSessionData();
        // Update the session data to keep track of new subsessions created before
        // the initialization.
        await TelemetryStorage.saveSessionData(this._getSessionDataObject());

        this.attachObservers();
        this.gatherMemory();

        if (Telemetry.canRecordExtended) {
          GCTelemetry.init();
        }

        Telemetry.asyncFetchTelemetryData(function() {});

        if (IS_UNIFIED_TELEMETRY) {
          // Check for a previously written aborted session ping.
          await TelemetryController.checkAbortedSessionPing();

          // Write the first aborted-session ping as early as possible. Just do that
          // if we are not testing, since calling Telemetry.reset() will make a previous
          // aborted ping a pending ping.
          if (!this._testing) {
            await this._saveAbortedSessionPing();
          }

          // The last change date for the environment, used to throttle environment changes.
          this._lastEnvironmentChangeDate = Policy.monotonicNow();
          TelemetryEnvironment.registerChangeListener(ENVIRONMENT_CHANGE_LISTENER,
                                 (reason, data) => this._onEnvironmentChange(reason, data));

          // Start the scheduler.
          // We skip this if unified telemetry is off, so we don't
          // trigger the new unified ping types.
          TelemetryScheduler.init();
        }

        this._delayedInitTask = null;
      } catch (e) {
        this._delayedInitTask = null;
        throw e;
      }
    })();

    return this._delayedInitTask;
  },

  getOpenTabsCount: function getOpenTabsCount() {
    let tabCount = 0;

    let browserEnum = Services.wm.getEnumerator("navigator:browser");
    while (browserEnum.hasMoreElements()) {
      let win = browserEnum.getNext();
      tabCount += win.gBrowser.tabs.length;
    }

    return tabCount;
  },

  /**
   * Initializes telemetry for a content process.
   */
  setupContentProcess: function setupContentProcess(testing) {
    this._log.trace("setupContentProcess");
    this._testing = testing;

    if (!Telemetry.canRecordBase) {
      this._log.trace("setupContentProcess - base recording is disabled, not initializing");
      return;
    }

    this.addObserver("content-child-shutdown");
    cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS, this);
    cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this);

    let delayedTask = new DeferredTask(() => {
      this._initialized = true;

      this.attachObservers();
      this.gatherMemory();

      if (Telemetry.canRecordExtended) {
        GCTelemetry.init();
      }
    }, testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY);

    delayedTask.arm();
  },

  getFlashVersion: function getFlashVersion() {
    let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
    let tags = host.getPluginTags();

    for (let i = 0; i < tags.length; i++) {
      if (tags[i].name == "Shockwave Flash")
        return tags[i].version;
    }

    return null;
  },

  receiveMessage: function receiveMessage(message) {
    this._log.trace("receiveMessage - Message name " + message.name);
    switch (message.name) {
    case MESSAGE_TELEMETRY_PAYLOAD:
    {
      // In parent process, receive Telemetry payload from child
      let source = message.data.childUUID;
      delete message.data.childUUID;

      this._childTelemetry.push({
        source,
        payload: message.data,
      });

      if (this._childTelemetry.length == MAX_NUM_CONTENT_PAYLOADS + 1) {
        this._childTelemetry.shift();
        Telemetry.getHistogramById("TELEMETRY_DISCARDED_CONTENT_PINGS_COUNT").add();
      }

      break;
    }
    case MESSAGE_TELEMETRY_THREAD_HANGS:
    {
      // Accumulate child thread hang stats from this child
      this._childThreadHangs.push(message.data);

      // Check if we've got data from all the children, accounting for child processes dying
      // if it happens before the last response is received and no new child processes are spawned at the exact same time
      // If that happens, we can resolve the promise earlier rather than having to wait for the timeout to expire
      // Basically, the number of replies is at most the number of messages sent out, this._childCount,
      // and also at most the number of child processes that currently exist
      if (this._childThreadHangs.length === Math.min(this._childCount, ppmm.childCount)) {
        clearTimeout(this._childThreadHangsTimeout);

        // Resolve all the promises that are waiting on these thread hang stats
        // We resolve here instead of rejecting because
        for (let resolve of this._childThreadHangsResolveFunctions) {
          resolve(this._childThreadHangs);
        }
        this._childThreadHangsResolveFunctions = [];
      }

      break;
    }
    case MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS:
    {
      // In child process, send the requested child thread hangs
      this.sendContentProcessThreadHangs();
      break;
    }
    case MESSAGE_TELEMETRY_USS:
    {
      // In parent process, receive the USS report from the child
      if (this._totalMemoryTimeout && this._childrenToHearFrom.delete(message.data.id)) {
        let uss = message.data.bytes;
        this._totalMemory += uss;
        this._USSFromChildProcesses.push(uss);
        if (this._childrenToHearFrom.size == 0) {
          clearTimeout(this._totalMemoryTimeout);
          this._totalMemoryTimeout = undefined;
          this.handleMemoryReport(
            "MEMORY_TOTAL",
            Ci.nsIMemoryReporter.UNITS_BYTES,
            this._totalMemory);

          let length = this._USSFromChildProcesses.length;
          if (length > 1) {
            // Mean of the USS of all the content processes.
            let mean = this._USSFromChildProcesses.reduce((a, b) => a + b, 0) / length;
            // Absolute error of USS for each content process, normalized by the mean (*100 to get it in percentage).
            // 20% means for a content process that it is using 20% more or 20% less than the mean.
            let diffs = this._USSFromChildProcesses.map(value => Math.floor(Math.abs(value - mean) * 100 / mean));
            let tabsCount = this.getOpenTabsCount();
            let key;
            if (tabsCount < 11) {
              key = "0 - 10 tabs";
            } else if (tabsCount < 501) {
              key = "11 - 500 tabs";
            } else {
              key = "more tabs";
            }

            diffs.forEach(value => {
              this.handleMemoryReport(
              "MEMORY_DISTRIBUTION_AMONG_CONTENT",
              Ci.nsIMemoryReporter.UNITS_COUNT,
              value,
              key);
            });

            // This notification is for testing only.
            Services.obs.notifyObservers(null, "gather-memory-telemetry-finished");
          }
          this._USSFromChildProcesses = undefined;
        }
      } else {
        this._log.trace("Child USS report was missed");
      }
      break;
    }
    case MESSAGE_TELEMETRY_GET_CHILD_USS:
    {
      // In child process, send the requested USS report
      this.sendContentProcessUSS(message.data.id);
      break
    }
    default:
      throw new Error("Telemetry.receiveMessage: bad message name");
    }
  },

  _processUUID: generateUUID(),

  sendContentProcessUSS: function sendContentProcessUSS(aMessageId) {
    this._log.trace("sendContentProcessUSS");

    let mgr;
    try {
      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
            getService(Ci.nsIMemoryReporterManager);
    } catch (e) {
      // OK to skip memory reporters in xpcshell
      return;
    }

    cpmm.sendAsyncMessage(
      MESSAGE_TELEMETRY_USS,
      {bytes: mgr.residentUnique, id: aMessageId}
    );
  },

  sendContentProcessPing: function sendContentProcessPing(reason) {
    this._log.trace("sendContentProcessPing - Reason " + reason);
    const isSubsession = !this._isClassicReason(reason);
    let payload = this.getSessionPayload(reason, isSubsession);
    payload.childUUID = this._processUUID;
    cpmm.sendAsyncMessage(MESSAGE_TELEMETRY_PAYLOAD, payload);
  },

  sendContentProcessThreadHangs: function sendContentProcessThreadHangs() {
    this._log.trace("sendContentProcessThreadHangs");
    let payload = {
      childUUID: this._processUUID,
      hangs: Telemetry.threadHangStats,
    };
    cpmm.sendAsyncMessage(MESSAGE_TELEMETRY_THREAD_HANGS, payload);
  },

   /**
    * Save both the "saved-session" and the "shutdown" pings to disk.
    * This needs to be called after TelemetrySend shuts down otherwise pings
    * would be sent instead of getting persisted to disk.
    */
  saveShutdownPings() {
    this._log.trace("saveShutdownPings");

    // We don't wait for "shutdown" pings to be written to disk before gathering the
    // "saved-session" payload. Instead we append the promises to this list and wait
    // on both to be saved after kicking off their collection.
    let p = [];

    if (IS_UNIFIED_TELEMETRY) {
      let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);

      // Only send the shutdown ping using the pingsender from the second
      // browsing session on, to mitigate issues with "bot" profiles (see bug 1354482).
      const sendOnThisSession =
        Services.prefs.getBoolPref(Utils.Preferences.ShutdownPingSenderFirstSession, false) ||
        !TelemetryReportingPolicy.isFirstRun();
      let sendWithPingsender = Services.prefs.getBoolPref(TelemetryUtils.Preferences.ShutdownPingSender, false) &&
                               sendOnThisSession;

      let options = {
        addClientId: true,
        addEnvironment: true,
        usePingSender: sendWithPingsender,
      };
      p.push(TelemetryController.submitExternalPing(getPingType(shutdownPayload), shutdownPayload, options)
                                .catch(e => this._log.error("saveShutdownPings - failed to submit shutdown ping", e)));
     }

    // As a temporary measure, we want to submit saved-session too if extended Telemetry is enabled
    // to keep existing performance analysis working.
    if (Telemetry.canRecordExtended) {
      let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);

      let options = {
        addClientId: true,
        addEnvironment: true,
      };
      p.push(TelemetryController.submitExternalPing(getPingType(payload), payload, options)
                                .catch(e => this._log.error("saveShutdownPings - failed to submit saved-session ping", e)));
    }

    // Wait on pings to be saved.
    return Promise.all(p);
  },

  testSavePendingPing() {
    let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
    let options = {
      addClientId: true,
      addEnvironment: true,
      overwrite: true,
    };
    return TelemetryController.addPendingPing(getPingType(payload), payload, options);
  },

  /**
   * Do some shutdown work that is common to all process types.
   */
  uninstall() {
    for (let topic of this._observedTopics) {
      try {
        // Tests may flip Telemetry.canRecordExtended on and off. It can be the case
        // that the observer TOPIC_CYCLE_COLLECTOR_BEGIN was not added.
        this.removeObserver(topic);
      } catch (e) {
        this._log.warn("uninstall - Failed to remove " + topic, e);
      }
    }

    GCTelemetry.shutdown();
  },

  getPayload: function getPayload(reason, clearSubsession) {
    this._log.trace("getPayload - clearSubsession: " + clearSubsession);
    reason = reason || REASON_GATHER_PAYLOAD;
    // This function returns the current Telemetry payload to the caller.
    // We only gather startup info once.
    if (Object.keys(this._slowSQLStartup).length == 0) {
      this._slowSQLStartup = Telemetry.slowSQL;
    }
    this.gatherMemory();
    return this.getSessionPayload(reason, clearSubsession);
  },

  getChildThreadHangs: function getChildThreadHangs() {
    return new Promise((resolve) => {
      // Return immediately if there are no child processes to get stats from
      if (ppmm.childCount === 0) {
        resolve([]);
        return;
      }

      // Register our promise so it will be resolved when we receive the child thread hang stats on the parent process
      // The resolve functions will all be called from "receiveMessage" when a MESSAGE_TELEMETRY_THREAD_HANGS message comes in
      this._childThreadHangsResolveFunctions.push((threadHangStats) => {
        let hangs = threadHangStats.map(child => child.hangs);
        return resolve(hangs);
      });

      // If we (the parent) are not currently in the process of requesting child thread hangs, request them
      // If we are, then the resolve function we registered above will receive the results without needing to request them again
      if (this._childThreadHangsResolveFunctions.length === 1) {
        // We have to cache the number of children we send messages to, in case the child count changes while waiting for messages to arrive
        // This handles the case where the child count increases later on, in which case the new processes won't respond since we never sent messages to them
        this._childCount = ppmm.childCount;

        this._childThreadHangs = []; // Clear the child hangs
        for (let i = 0; i < this._childCount; i++) {
          // If a child dies at exactly while we're running this loop, the message sending will fail but we won't get an exception
          // In this case, since we won't know this has happened, we will simply rely on the timeout to handle it
          ppmm.getChildAt(i).sendAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS);
        }

        // Set up a timeout in case one or more of the content processes never responds
        this._childThreadHangsTimeout = setTimeout(() => {
          // Resolve all the promises that are waiting on these thread hang stats
          // We resolve here instead of rejecting because the purpose of this function is
          // to retrieve the BHR stats from all processes that will give us stats
          // As a result, one process failing simply means it doesn't get included in the result.
          for (let resolve of this._childThreadHangsResolveFunctions) {
            resolve(this._childThreadHangs);
          }
          this._childThreadHangsResolveFunctions = [];
        }, 200);
      }
    });
  },

  gatherStartup: function gatherStartup() {
    this._log.trace("gatherStartup");
    let counters = processInfo.getCounters();
    if (counters) {
      [this._startupIO.startupSessionRestoreReadBytes,
        this._startupIO.startupSessionRestoreWriteBytes] = counters;
    }
    this._slowSQLStartup = Telemetry.slowSQL;
  },

  setAddOns: function setAddOns(aAddOns) {
    this._addons = aAddOns;
  },

  testPing: function testPing() {
    return this.send(REASON_TEST_PING);
  },

  /**
   * Tracks the number of "ticks" the user was active in.
   */
  _onActiveTick(aUserActive) {
    const needsUpdate = aUserActive && this._isUserActive;
    this._isUserActive = aUserActive;

    // Don't count the first active tick after we get out of
    // inactivity, because it is just the start of this active tick.
    if (needsUpdate) {
      this._sessionActiveTicks++;
      Telemetry.scalarAdd("browser.engagement.active_ticks", 1);
    }
  },

  /**
   * This observer drives telemetry.
   */
  observe(aSubject, aTopic, aData) {
    // Prevent the cycle collector begin topic from cluttering the log.
    if (aTopic != TOPIC_CYCLE_COLLECTOR_BEGIN) {
      this._log.trace("observe - " + aTopic + " notified.");
    }

    switch (aTopic) {
    case "content-child-shutdown":
      // content-child-shutdown is only registered for content processes.
      this.uninstall();
      Telemetry.flushBatchedChildTelemetry();
      this.sendContentProcessPing(REASON_SAVED_SESSION);
      break;
    case TOPIC_CYCLE_COLLECTOR_BEGIN:
      let now = new Date();
      if (!gLastMemoryPoll
          || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) {
        gLastMemoryPoll = now;

        this._log.trace("Dispatching idle gatherMemory task");
        Services.tm.idleDispatchToMainThread(() => {
          this._log.trace("Running idle gatherMemory task");
          this.gatherMemory();
          return true;
        });
      }
      break;
    case "xul-window-visible":
      this.removeObserver("xul-window-visible");
      var counters = processInfo.getCounters();
      if (counters) {
        [this._startupIO.startupWindowVisibleReadBytes,
          this._startupIO.startupWindowVisibleWriteBytes] = counters;
      }
      break;
    case "sessionstore-windows-restored":
      this.removeObserver("sessionstore-windows-restored");
      // Check whether debugger was attached during startup
      let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
      gWasDebuggerAttached = debugService.isDebuggerAttached;
      this.gatherStartup();
      break;
    case "idle-daily":
      // Enqueue to main-thread, otherwise components may be inited by the
      // idle-daily category and miss the gather-telemetry notification.
      Services.tm.dispatchToMainThread((function() {
        // Notify that data should be gathered now.
        // TODO: We are keeping this behaviour for now but it will be removed as soon as
        // bug 1127907 lands.
        Services.obs.notifyObservers(null, "gather-telemetry");
      }));
      break;

    case "application-background":
      if (AppConstants.platform !== "android") {
        break;
      }
      // On Android, we can get killed without warning once we are in the background,
      // but we may also submit data and/or come back into the foreground without getting
      // killed. To deal with this, we save the current session data to file when we are
      // put into the background. This handles the following post-backgrounding scenarios:
      // 1) We are killed immediately. In this case the current session data (which we
      //    save to a file) will be loaded and submitted on a future run.
      // 2) We submit the data while in the background, and then are killed. In this case
      //    the file that we saved will be deleted by the usual process in
      //    finishPingRequest after it is submitted.
      // 3) We submit the data, and then come back into the foreground. Same as case (2).
      // 4) We do not submit the data, but come back into the foreground. In this case
      //    we have the option of either deleting the file that we saved (since we will either
      //    send the live data while in the foreground, or create the file again on the next
      //    backgrounding), or not (in which case we will delete it on submit, or overwrite
      //    it on the next backgrounding). Not deleting it is faster, so that's what we do.
      let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
      let options = {
        addClientId: true,
        addEnvironment: true,
        overwrite: true,
      };
      TelemetryController.addPendingPing(getPingType(payload), payload, options);
      break;
    case "user-interaction-active":
      this._onActiveTick(true);
      break;
    case "user-interaction-inactive":
      this._onActiveTick(false);
      break;
    }
    return undefined;
  },

  /**
   * This tells TelemetrySession to uninitialize and save any pending pings.
   */
  shutdownChromeProcess() {
    this._log.trace("shutdownChromeProcess");

    let cleanup = () => {
      if (IS_UNIFIED_TELEMETRY) {
        TelemetryEnvironment.unregisterChangeListener(ENVIRONMENT_CHANGE_LISTENER);
        TelemetryScheduler.shutdown();
      }
      this.uninstall();

      let reset = () => {
        this._initStarted = false;
        this._initialized = false;
      };

      return (async () => {
        await this.saveShutdownPings();

        if (IS_UNIFIED_TELEMETRY) {
          await TelemetryController.removeAbortedSessionPing();
        }

        reset();
      })();
    };

    // We can be in one the following states here:
    // 1) delayedInit was never called
    // or it was called and
    //   2) _delayedInitTask is running now.
    //   3) _delayedInitTask finished running already.

    // This handles 1).
    if (!this._initStarted) {
      return Promise.resolve();
     }

    // This handles 3).
    if (!this._delayedInitTask) {
      // We already ran the delayed initialization.
      return cleanup();
     }

     // This handles 2).
     return this._delayedInitTask.then(cleanup);
   },

  /**
   * Gather and send a daily ping.
   * @return {Promise} Resolved when the ping is sent.
   */
  _sendDailyPing() {
    this._log.trace("_sendDailyPing");
    let payload = this.getSessionPayload(REASON_DAILY, true);

    let options = {
      addClientId: true,
      addEnvironment: true,
    };

    let promise = TelemetryController.submitExternalPing(getPingType(payload), payload, options);

    // Also save the payload as an aborted session. If we delay this, aborted-session can
    // lag behind for the profileSubsessionCounter and other state, complicating analysis.
    if (IS_UNIFIED_TELEMETRY) {
      this._saveAbortedSessionPing(payload)
          .catch(e => this._log.error("_sendDailyPing - Failed to save the aborted session ping", e));
    }

    return promise;
  },

  /** Loads session data from the session data file.
   * @return {Promise<object>} A promise which is resolved with an object when
   *                            loading has completed, with null otherwise.
   */
  async _loadSessionData() {
    let data = await TelemetryStorage.loadSessionData();

    if (!data) {
      return null;
    }

    if (!("profileSubsessionCounter" in data) ||
        !(typeof(data.profileSubsessionCounter) == "number") ||
        !("subsessionId" in data) || !("sessionId" in data)) {
      this._log.error("_loadSessionData - session data is invalid");
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_VALIDATION").add(1);
      return null;
    }

    this._previousSessionId = data.sessionId;
    this._previousSubsessionId = data.subsessionId;
    // Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for
    // new subsession while loading still takes place. This will always be exactly
    // 1 - the current subsessions.
    this._profileSubsessionCounter = data.profileSubsessionCounter +
                                     this._subsessionCounter;
    // If we don't have this flag in the state file, it means that this is an old profile.
    // We don't want to send the "new-profile" ping on new profile, so se this to true.
    this._newProfilePingSent = ("newProfilePingSent" in data) ? data.newProfilePingSent : true;
    return data;
  },

  /**
   * Get the session data object to serialise to disk.
   */
  _getSessionDataObject() {
    return {
      sessionId: this._sessionId,
      subsessionId: this._subsessionId,
      profileSubsessionCounter: this._profileSubsessionCounter,
      newProfilePingSent: this._newProfilePingSent,
    };
  },

  _onEnvironmentChange(reason, oldEnvironment) {
    this._log.trace("_onEnvironmentChange", reason);

    let now = Policy.monotonicNow();
    let timeDelta = now - this._lastEnvironmentChangeDate;
    if (timeDelta <= MIN_SUBSESSION_LENGTH_MS) {
      this._log.trace(`_onEnvironmentChange - throttling; last change was ${Math.round(timeDelta / 1000)}s ago.`);
      return;
    }

    this._lastEnvironmentChangeDate = now;
    let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
    TelemetryScheduler.reschedulePings(REASON_ENVIRONMENT_CHANGE, payload);

    let options = {
      addClientId: true,
      addEnvironment: true,
      overrideEnvironment: oldEnvironment,
    };
    TelemetryController.submitExternalPing(getPingType(payload), payload, options);
  },

  _isClassicReason(reason) {
    const classicReasons = [
      REASON_SAVED_SESSION,
      REASON_GATHER_PAYLOAD,
      REASON_TEST_PING,
    ];
    return classicReasons.includes(reason);
  },

  /**
   * Get an object describing the current state of this module for AsyncShutdown diagnostics.
   */
  _getState() {
    return {
      initialized: this._initialized,
      initStarted: this._initStarted,
      haveDelayedInitTask: !!this._delayedInitTask,
    };
  },

  /**
   * Saves the aborted session ping to disk.
   * @param {Object} [aProvidedPayload=null] A payload object to be used as an aborted
   *                 session ping. The reason of this payload is changed to aborted-session.
   *                 If not provided, a new payload is gathered.
   */
  _saveAbortedSessionPing(aProvidedPayload = null) {
    this._log.trace("_saveAbortedSessionPing");

    let payload = null;
    if (aProvidedPayload) {
      payload = Cu.cloneInto(aProvidedPayload, myScope);
      // Overwrite the original reason.
      payload.info.reason = REASON_ABORTED_SESSION;
    } else {
      payload = this.getSessionPayload(REASON_ABORTED_SESSION, false);
    }

    return TelemetryController.saveAbortedSessionPing(payload);
  },

  async markNewProfilePingSent() {
    this._log.trace("markNewProfilePingSent");
    this._newProfilePingSent = true;
    return TelemetryStorage.saveSessionData(this._getSessionDataObject());
  },
};
PK
!<;;modules/TelemetryStopwatch.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ["TelemetryStopwatch"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
  "resource://gre/modules/Log.jsm");

var Telemetry = Cc["@mozilla.org/base/telemetry;1"]
                  .getService(Ci.nsITelemetry);

// Weak map does not allow using null objects as keys. These objects are used
// as 'null' placeholders.
const NULL_OBJECT = {};
const NULL_KEY = {};

/**
 * Timers is a variation of a Map used for storing information about running
 * Stopwatches. Timers has the following data structure:
 *
 * {
 *    "HISTOGRAM_NAME": WeakMap {
 *      Object || NULL_OBJECT: Map {
 *        "KEY" || NULL_KEY: startTime
 *        ...
 *      }
 *      ...
 *    }
 *    ...
 * }
 *
 *
 * @example
 * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS".
 * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now());
 *
 * @example
 * // Returns information about a simple Stopwatch.
 * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS");
 */
let Timers = {
  _timers: new Map(),

  _validTypes(histogram, obj, key) {
    let nonEmptyString = value => {
      return typeof value === "string" && value !== "" && value.length > 0;
    };
    return nonEmptyString(histogram) &&
            typeof obj == "object" &&
           (key === NULL_KEY || nonEmptyString(key));
  },

  get(histogram, obj, key) {
    key = key === null ? NULL_KEY : key;
    obj = obj || NULL_OBJECT;

    if (!this.has(histogram, obj, key)) {
      return null;
    }

    return this._timers.get(histogram).get(obj).get(key);
  },

  put(histogram, obj, key, startTime) {
    key = key === null ? NULL_KEY : key;
    obj = obj || NULL_OBJECT;

    if (!this._validTypes(histogram, obj, key)) {
      return false;
    }

    let objectMap = this._timers.get(histogram) || new WeakMap();
    let keyedInfo = objectMap.get(obj) || new Map();
    keyedInfo.set(key, startTime);
    objectMap.set(obj, keyedInfo);
    this._timers.set(histogram, objectMap);
    return true;
  },

  has(histogram, obj, key) {
    key = key === null ? NULL_KEY : key;
    obj = obj || NULL_OBJECT;

    return this._timers.has(histogram) &&
      this._timers.get(histogram).has(obj) &&
      this._timers.get(histogram).get(obj).has(key);
  },

  delete(histogram, obj, key) {
    key = key === null ? NULL_KEY : key;
    obj = obj || NULL_OBJECT;

    if (!this.has(histogram, obj, key)) {
      return false;
    }
    let objectMap = this._timers.get(histogram);
    let keyedInfo = objectMap.get(obj);
    if (keyedInfo.size > 1) {
      keyedInfo.delete(key);
      return true;
    }
    objectMap.delete(obj);
    // NOTE:
    // We never delete empty objecMaps from this._timers because there is no
    // nice solution for tracking the number of objects in a WeakMap.
    // WeakMap is not enumerable, so we can't deterministically say when it's
    // empty. We accept that trade-off here, given that entries for short-lived
    // objects will go away when they are no longer referenced
    return true;
  }
};

this.TelemetryStopwatch = {
  /**
   * Starts a timer associated with a telemetry histogram. The timer can be
   * directly associated with a histogram, or with a pair of a histogram and
   * an object.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer is
   *                        associated with this object, meaning that multiple
   *                        timers for the same histogram may be run
   *                        concurrently, as long as they are associated with
   *                        different objects.
   *
   * @returns {Boolean} True if the timer was successfully started, false
   *                    otherwise. If a timer already exists, it can't be
   *                    started again, and the existing one will be cleared in
   *                    order to avoid measurements errors.
   */
  start(aHistogram, aObj) {
    return TelemetryStopwatchImpl.start(aHistogram, aObj, null);
  },

  /**
   * Returns whether a timer associated with a telemetry histogram is currently
   * running. The timer can be directly associated with a histogram, or with a
   * pair of a histogram and an object.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer is
   *                        associated with this object, meaning that multiple
   *                        timers for the same histogram may be run
   *                        concurrently, as long as they are associated with
   *                        different objects.
   *
   * @returns {Boolean} True if the timer exists and is currently running.
   */
  running(aHistogram, aObj) {
    return TelemetryStopwatchImpl.running(aHistogram, aObj, null);
  },

  /**
   * Deletes the timer associated with a telemetry histogram. The timer can be
   * directly associated with a histogram, or with a pair of a histogram and
   * an object. Important: Only use this method when a legitimate cancellation
   * should be done.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer is
   *                        associated with this object, meaning that multiple
   *                        timers or a same histogram may be run concurrently,
   *                        as long as they are associated with different
   *                        objects.
   *
   * @returns {Boolean} True if the timer exist and it was cleared, False
   *                   otherwise.
   */
  cancel(aHistogram, aObj) {
    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null);
  },

  /**
   * Returns the elapsed time for a particular stopwatch. Primarily for
   * debugging purposes. Must be called prior to finish.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *                              If an invalid name is given, the function will
   *                              throw.
   *
   * @param (Object) aObj - Optional parameter which associates the histogram
   *                        timer with the given object.
   *
   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
   *                                  warnings that normally fire when a stopwatch
   *                                  is finished after being cancelled. Defaults
   *                                  to false.
   *
   * @returns {Integer} time in milliseconds or -1 if the stopwatch was not
   *                   found.
   */
  timeElapsed(aHistogram, aObj, aCanceledOkay) {
    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null,
                                              aCanceledOkay);
  },

  /**
   * Stops the timer associated with the given histogram (and object),
   * calculates the time delta between start and finish, and adds the value
   * to the histogram.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {Object} aObj - Optional parameter which associates the histogram
   *                        timer with the given object.
   *
   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
   *                                  warnings that normally fire when a stopwatch
   *                                  is finished after being cancelled. Defaults
   *                                  to false.
   *
   * @returns {Boolean} True if the timer was succesfully stopped and the data
   *                    was added to the histogram, False otherwise.
   */
  finish(aHistogram, aObj, aCanceledOkay) {
    return TelemetryStopwatchImpl.finish(aHistogram, aObj, null, aCanceledOkay);
  },

  /**
   * Starts a timer associated with a keyed telemetry histogram. The timer can
   * be directly associated with a histogram and its key. Similarly to
   * @see{TelemetryStopwatch.stat} the histogram and its key can be associated
   * with an object. Each key may have multiple associated objects and each
   * object can be associated with multiple keys.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {String} aKey - a string which must be a valid histgram key.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer is
   *                        associated with this object, meaning that multiple
   *                        timers for the same histogram may be run
   *                        concurrently,as long as they are associated with
   *                        different objects.
   *
   * @returns {Boolean} True if the timer was successfully started, false
   *                    otherwise. If a timer already exists, it can't be
   *                    started again, and the existing one will be cleared in
   *                    order to avoid measurements errors.
   */
  startKeyed(aHistogram, aKey, aObj) {
    return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey);
  },

  /**
   * Returns whether a timer associated with a telemetry histogram is currently
   * running. Similarly to @see{TelemetryStopwatch.running} the timer and its
   * key can be associated with an object. Each key may have multiple associated
   * objects and each object can be associated with multiple keys.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {String} aKey - a string which must be a valid histgram key.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer is
   *                        associated with this object, meaning that multiple
   *                        timers for the same histogram may be run
   *                        concurrently, as long as they are associated with
   *                        different objects.
   *
   * @returns {Boolean} True if the timer exists and is currently running.
   */
  runningKeyed(aHistogram, aKey, aObj) {
    return TelemetryStopwatchImpl.running(aHistogram, aObj, aKey);
  },

  /**
   * Deletes the timer associated with a keyed histogram. Important: Only use
   * this method when a legitimate cancellation should be done.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {String} aKey - a string which must be a valid histgram key.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer
   *                        associated with this object is deleted.
   *
   * @return {Boolean} True if the timer exist and it was cleared, False
   *                   otherwise.
   */
  cancelKeyed(aHistogram, aKey, aObj) {
    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey);
  },

  /**
   * Returns the elapsed time for a particular stopwatch. Primarily for
   * debugging purposes. Must be called prior to finish.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {String} aKey - a string which must be a valid histgram key.
   *
   * @param {Object} aObj - Optional parameter. If specified, the timer
   *                        associated with this object is used to calculate
   *                        the elapsed time.
   *
   * @return {Integer} time in milliseconds or -1 if the stopwatch was not
   *                   found.
   */
  timeElapsedKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey,
                                              aCanceledOkay);
  },

  /**
   * Stops the timer associated with the given keyed histogram (and object),
   * calculates the time delta between start and finish, and adds the value
   * to the keyed histogram.
   *
   * @param {String} aHistogram - a string which must be a valid histogram name.
   *
   * @param {String} aKey - a string which must be a valid histgram key.
   *
   * @param {Object} aObj - optional parameter which associates the histogram
   *                        timer with the given object.
   *
   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
   *                                  warnings that normally fire when a stopwatch
   *                                  is finished after being cancelled. Defaults
   *                                  to false.
   *
   * @returns {Boolean} True if the timer was succesfully stopped and the data
   *                   was added to the histogram, False otherwise.
   */
  finishKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
    return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey, aCanceledOkay);
  }
};

this.TelemetryStopwatchImpl = {
  start(histogram, object, key) {
    if (Timers.has(histogram, object, key)) {
      Timers.delete(histogram, object, key);
      Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` +
                     "initialized");
      return false;
    }

    return Timers.put(histogram, object, key, Components.utils.now());
  },

  running(histogram, object, key) {
    return Timers.has(histogram, object, key);
  },

  cancel(histogram, object, key) {
    return Timers.delete(histogram, object, key);
  },

  timeElapsed(histogram, object, key, aCanceledOkay) {
    let startTime = Timers.get(histogram, object, key);
    if (startTime === null) {
      if (!aCanceledOkay) {
        Cu.reportError("TelemetryStopwatch: requesting elapsed time for " +
                       `nonexisting stopwatch. Histogram: "${histogram}", ` +
                       `key: "${key}"`);
      }
      return -1;
    }

    try {
      let delta = Components.utils.now() - startTime
      return Math.round(delta);
    } catch (e) {
      Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " +
                     `for Histogram: "${histogram}", key: "${key}", ` +
                     `exception: ${Log.exceptionStr(e)}`);
      return -1;
    }
  },

  finish(histogram, object, key, aCanceledOkay) {
    let delta = this.timeElapsed(histogram, object, key, aCanceledOkay);
    if (delta == -1) {
      return false;
    }

    try {
      if (key) {
        Telemetry.getKeyedHistogramById(histogram).add(key, delta);
      } else {
        Telemetry.getHistogramById(histogram).add(delta);
      }
    } catch (e) {
      Cu.reportError("TelemetryStopwatch: failed to update the Histogram " +
                     `"${histogram}", using key: "${key}", ` +
                     `exception: ${Log.exceptionStr(e)}`);
      return false;
    }

    return Timers.delete(histogram, object, key);
  }
}
PK
!<$//modules/TelemetryStorage.jsm/* -*- 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 = ["TelemetryStorage"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

Cu.import("resource://gre/modules/AppConstants.jsm", this);
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryStorage::";

const Telemetry = Services.telemetry;
const Utils = TelemetryUtils;

// Compute the path of the pings archive on the first use.
const DATAREPORTING_DIR = "datareporting";
const PINGS_ARCHIVE_DIR = "archived";
const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
const DELETION_PING_FILE_NAME = "pending-deletion-ping";
const SESSION_STATE_FILE_NAME = "session-state.json";

XPCOMUtils.defineLazyGetter(this, "gDataReportingDir", function() {
  return OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR);
});
XPCOMUtils.defineLazyGetter(this, "gPingsArchivePath", function() {
  return OS.Path.join(gDataReportingDir, PINGS_ARCHIVE_DIR);
});
XPCOMUtils.defineLazyGetter(this, "gAbortedSessionFilePath", function() {
  return OS.Path.join(gDataReportingDir, ABORTED_SESSION_FILE_NAME);
});
XPCOMUtils.defineLazyGetter(this, "gDeletionPingFilePath", function() {
  return OS.Path.join(gDataReportingDir, DELETION_PING_FILE_NAME);
});
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                  "resource://services-common/utils.js");
// Maxmimum time, in milliseconds, archive pings should be retained.
const MAX_ARCHIVED_PINGS_RETENTION_MS = 60 * 24 * 60 * 60 * 1000;  // 60 days

// Maximum space the archive can take on disk (in Bytes).
const ARCHIVE_QUOTA_BYTES = 120 * 1024 * 1024; // 120 MB
// Maximum space the outgoing pings can take on disk, for Desktop (in Bytes).
const PENDING_PINGS_QUOTA_BYTES_DESKTOP = 15 * 1024 * 1024; // 15 MB
// Maximum space the outgoing pings can take on disk, for Mobile (in Bytes).
const PENDING_PINGS_QUOTA_BYTES_MOBILE = 1024 * 1024; // 1 MB

// The maximum size a pending/archived ping can take on disk.
const PING_FILE_MAXIMUM_SIZE_BYTES = 1024 * 1024; // 1 MB

// This special value is submitted when the archive is outside of the quota.
const ARCHIVE_SIZE_PROBE_SPECIAL_VALUE = 300;

// This special value is submitted when the pending pings is outside of the quota, as
// we don't know the size of the pings above the quota.
const PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE = 17;

const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

/**
 * This is thrown by |TelemetryStorage.loadPingFile| when reading the ping
 * from the disk fails.
 */
function PingReadError(message = "Error reading the ping file", becauseNoSuchFile = false) {
  Error.call(this, message);
  let error = new Error();
  this.name = "PingReadError";
  this.message = message;
  this.stack = error.stack;
  this.becauseNoSuchFile = becauseNoSuchFile;
}
PingReadError.prototype = Object.create(Error.prototype);
PingReadError.prototype.constructor = PingReadError;

/**
 * This is thrown by |TelemetryStorage.loadPingFile| when parsing the ping JSON
 * content fails.
 */
function PingParseError(message = "Error parsing ping content") {
  Error.call(this, message);
  let error = new Error();
  this.name = "PingParseError";
  this.message = message;
  this.stack = error.stack;
}
PingParseError.prototype = Object.create(Error.prototype);
PingParseError.prototype.constructor = PingParseError;

/**
 * This is a policy object used to override behavior for testing.
 */
var Policy = {
  now: () => new Date(),
  getArchiveQuota: () => ARCHIVE_QUOTA_BYTES,
  getPendingPingsQuota: () => (AppConstants.platform == "android")
                                ? PENDING_PINGS_QUOTA_BYTES_MOBILE
                                : PENDING_PINGS_QUOTA_BYTES_DESKTOP,
};

/**
 * Wait for all promises in iterable to resolve or reject. This function
 * always resolves its promise with undefined, and never rejects.
 */
function waitForAll(it) {
  let dummy = () => {};
  let promises = Array.from(it, p => p.catch(dummy));
  return Promise.all(promises);
}

/**
 * Permanently intern the given string. This is mainly used for the ping.type
 * strings that can be excessively duplicated in the _archivedPings map. Do not
 * pass large or temporary strings to this function.
 */
function internString(str) {
  return Symbol.keyFor(Symbol.for(str));
}

this.TelemetryStorage = {
  get pingDirectoryPath() {
    return OS.Path.join(OS.Constants.Path.profileDir, "saved-telemetry-pings");
  },

  /**
   * The maximum size a ping can have, in bytes.
   */
  get MAXIMUM_PING_SIZE() {
    return PING_FILE_MAXIMUM_SIZE_BYTES;
  },
  /**
   * Shutdown & block on any outstanding async activity in this module.
   *
   * @return {Promise} Promise that is resolved when shutdown is complete.
   */
  shutdown() {
    return TelemetryStorageImpl.shutdown();
  },

  /**
   * Save an archived ping to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  saveArchivedPing(ping) {
    return TelemetryStorageImpl.saveArchivedPing(ping);
  },

  /**
   * Load an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @return {promise<object>} Promise that is resolved with the ping data.
   */
  loadArchivedPing(id) {
    return TelemetryStorageImpl.loadArchivedPing(id);
  },

  /**
   * Remove an archived ping from disk.
   *
   * @param {string} id The ping's id.
   * @param {number} timestampCreated The ping's creation timestamp.
   * @param {string} type The ping's type.
   * @return {promise<object>} Promise that is resolved when the ping is removed.
   */
  removeArchivedPing(id, timestampCreated, type) {
    return TelemetryStorageImpl._removeArchivedPing(id, timestampCreated, type);
  },

  /**
   * Get a list of info on the archived pings.
   * This will scan the archive directory and grab basic data about the existing
   * pings out of their filename.
   *
   * @return {promise<sequence<object>>}
   */
  loadArchivedPingList() {
    return TelemetryStorageImpl.loadArchivedPingList();
  },

  /**
   * Clean the pings archive by removing old pings.
   * This will scan the archive directory.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runCleanPingArchiveTask() {
    return TelemetryStorageImpl.runCleanPingArchiveTask();
  },

  /**
   * Run the task to enforce the pending pings quota.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runEnforcePendingPingsQuotaTask() {
    return TelemetryStorageImpl.runEnforcePendingPingsQuotaTask();
  },

  /**
   * Run the task to remove all the pending pings (except the deletion ping).
   *
   * @return {Promise} Resolved when the pings are removed.
   */
  runRemovePendingPingsTask() {
    return TelemetryStorageImpl.runRemovePendingPingsTask();
  },

  /**
   * Reset the storage state in tests.
   */
  reset() {
    return TelemetryStorageImpl.reset();
  },

  /**
   * Test method that allows waiting on the archive clean task to finish.
   */
  testCleanupTaskPromise() {
    return (TelemetryStorageImpl._cleanArchiveTask || Promise.resolve());
  },

  /**
   * Test method that allows waiting on the pending pings quota task to finish.
   */
  testPendingQuotaTaskPromise() {
    return (TelemetryStorageImpl._enforcePendingPingsQuotaTask || Promise.resolve());
  },

  /**
   * Save a pending - outgoing - ping to disk and track it.
   *
   * @param {Object} ping The ping data.
   * @return {Promise} Resolved when the ping was saved.
   */
  savePendingPing(ping) {
    return TelemetryStorageImpl.savePendingPing(ping);
  },

  /**
   * Saves session data to disk.
   * @param {Object}  sessionData The session data.
   * @return {Promise} Resolved when the data was saved.
   */
  saveSessionData(sessionData) {
    return TelemetryStorageImpl.saveSessionData(sessionData);
  },

  /**
   * Loads session data from a session data file.
   * @return {Promise<object>} Resolved with the session data in object form.
   */
  loadSessionData() {
    return TelemetryStorageImpl.loadSessionData();
  },

  /**
   * Load a pending ping from disk by id.
   *
   * @param {String} id The pings id.
   * @return {Promise} Resolved with the loaded ping data.
   */
  loadPendingPing(id) {
    return TelemetryStorageImpl.loadPendingPing(id);
  },

  /**
   * Remove a pending ping from disk by id.
   *
   * @param {String} id The pings id.
   * @return {Promise} Resolved when the ping was removed.
   */
  removePendingPing(id) {
    return TelemetryStorageImpl.removePendingPing(id);
  },

  /**
   * Returns a list of the currently pending pings in the format:
   * {
   *   id: <string>, // The pings UUID.
   *   lastModificationDate: <number>, // Timestamp of the pings last modification.
   * }
   * This populates the list by scanning the disk.
   *
   * @return {Promise<sequence>} Resolved with the ping list.
   */
  loadPendingPingList() {
    return TelemetryStorageImpl.loadPendingPingList();
   },

  /**
   * Returns a list of the currently pending pings in the format:
   * {
   *   id: <string>, // The pings UUID.
   *   lastModificationDate: <number>, // Timestamp of the pings last modification.
   * }
   * This does not scan pending pings on disk.
   *
   * @return {sequence} The current pending ping list.
   */
  getPendingPingList() {
    return TelemetryStorageImpl.getPendingPingList();
   },

  /**
   * Save an aborted-session ping to disk. This goes to a special location so
   * it is not picked up as a pending ping.
   *
   * @param {object} ping The ping data to save.
   * @return {promise} Promise that is resolved when the ping is successfully saved.
   */
  saveAbortedSessionPing(ping) {
    return TelemetryStorageImpl.saveAbortedSessionPing(ping);
  },

  /**
   * Load the aborted-session ping from disk if present.
   *
   * @return {promise<object>} Promise that is resolved with the ping data if found.
   *                           Otherwise returns null.
   */
  loadAbortedSessionPing() {
    return TelemetryStorageImpl.loadAbortedSessionPing();
  },

  /**
   * Save the deletion ping.
   * @param ping The deletion ping.
   * @return {Promise} A promise resolved when the ping is saved.
   */
  saveDeletionPing(ping) {
    return TelemetryStorageImpl.saveDeletionPing(ping);
  },

  /**
   * Remove the deletion ping.
   * @return {Promise} Resolved when the ping is deleted from the disk.
   */
  removeDeletionPing() {
    return TelemetryStorageImpl.removeDeletionPing();
  },

  /**
   * Check if the ping id identifies a deletion ping.
   */
  isDeletionPing(aPingId) {
    return TelemetryStorageImpl.isDeletionPing(aPingId);
  },

  /**
   * Remove the aborted-session ping if present.
   *
   * @return {promise} Promise that is resolved once the ping is removed.
   */
  removeAbortedSessionPing() {
    return TelemetryStorageImpl.removeAbortedSessionPing();
  },

  /**
   * Save a single ping to a file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {string} file The destination file.
   * @param {bool} overwrite If |true|, the file will be overwritten if it exists,
   * if |false| the file will not be overwritten and no error will be reported if
   * the file exists.
   * @returns {promise}
   */
  savePingToFile(ping, file, overwrite) {
    return TelemetryStorageImpl.savePingToFile(ping, file, overwrite);
  },

  /**
   * Save a ping to its file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {bool} overwrite If |true|, the file will be overwritten
   * if it exists.
   * @returns {promise}
   */
  savePing(ping, overwrite) {
    return TelemetryStorageImpl.savePing(ping, overwrite);
  },

  /**
   * Add a ping to the saved pings directory so that it gets saved
   * and sent along with other pings.
   *
   * @param {Object} pingData The ping object.
   * @return {Promise} A promise resolved when the ping is saved to the pings directory.
   */
  addPendingPing(pingData) {
    return TelemetryStorageImpl.addPendingPing(pingData);
  },

  /**
   * Remove the file for a ping
   *
   * @param {object} ping The ping.
   * @returns {promise}
   */
  cleanupPingFile(ping) {
    return TelemetryStorageImpl.cleanupPingFile(ping);
  },

  /**
   * The number of pending pings on disk.
   */
  get pendingPingCount() {
    return TelemetryStorageImpl.pendingPingCount;
  },

  /**
   * Loads a ping file.
   * @param {String} aFilePath The path of the ping file.
   * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
   *                           ping contains invalid data.
   */
  async loadPingFile(aFilePath) {
    return TelemetryStorageImpl.loadPingFile(aFilePath);
  },

  /**
   * Remove FHR database files. This is temporary and will be dropped in
   * the future.
   * @return {Promise} Resolved when the database files are deleted.
   */
  removeFHRDatabase() {
    return TelemetryStorageImpl.removeFHRDatabase();
  },

  /**
   * Only used in tests, builds an archived ping path from the ping metadata.
   * @param {String} aPingId The ping id.
   * @param {Object} aDate The ping creation date.
   * @param {String} aType The ping type.
   * @return {String} The full path to the archived ping.
   */
  _testGetArchivedPingPath(aPingId, aDate, aType) {
    return getArchivedPingPath(aPingId, aDate, aType);
  },

  /**
   * Only used in tests, this helper extracts ping metadata from a given filename.
   *
   * @param fileName {String} The filename.
   * @return {Object} Null if the filename didn't match the expected form.
   *                  Otherwise an object with the extracted data in the form:
   *                  { timestamp: <number>,
   *                    id: <string>,
   *                    type: <string> }
   */
  _testGetArchivedPingDataFromFileName(aFileName) {
    return TelemetryStorageImpl._getArchivedPingDataFromFileName(aFileName);
  },

  /**
   * Only used in tests, this helper allows cleaning up the pending ping storage.
   */
  testClearPendingPings() {
    return TelemetryStorageImpl.runRemovePendingPingsTask();
  }
};

/**
 * This object allows the serialisation of asynchronous tasks. This is particularly
 * useful to serialise write access to the disk in order to prevent race conditions
 * to corrupt the data being written.
 * We are using this to synchronize saving to the file that TelemetrySession persists
 * its state in.
 */
function SaveSerializer() {
  this._queuedOperations = [];
  this._queuedInProgress = false;
  this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
}

SaveSerializer.prototype = {
  /**
   * Enqueues an operation to a list to serialise their execution in order to prevent race
   * conditions. Useful to serialise access to disk.
   *
   * @param {Function} aFunction The task function to enqueue. It must return a promise.
   * @return {Promise} A promise resolved when the enqueued task completes.
   */
  enqueueTask(aFunction) {
    let promise = new Promise((resolve, reject) =>
      this._queuedOperations.push([aFunction, resolve, reject]));

    if (this._queuedOperations.length == 1) {
      this._popAndPerformQueuedOperation();
    }
    return promise;
  },

  /**
   * Make sure to flush all the pending operations.
   * @return {Promise} A promise resolved when all the pending operations have completed.
   */
  flushTasks() {
    let dummyTask = () => new Promise(resolve => resolve());
    return this.enqueueTask(dummyTask);
  },

  /**
   * Pop a task from the queue, executes it and continue to the next one.
   * This function recursively pops all the tasks.
   */
  _popAndPerformQueuedOperation() {
    if (!this._queuedOperations.length || this._queuedInProgress) {
      return;
    }

    this._log.trace("_popAndPerformQueuedOperation - Performing queued operation.");
    let [func, resolve, reject] = this._queuedOperations.shift();
    let promise;

    try {
      this._queuedInProgress = true;
      promise = func();
    } catch (ex) {
      this._log.warn("_popAndPerformQueuedOperation - Queued operation threw during execution. ",
                     ex);
      this._queuedInProgress = false;
      reject(ex);
      this._popAndPerformQueuedOperation();
      return;
    }

    if (!promise || typeof(promise.then) != "function") {
      let msg = "Queued operation did not return a promise: " + func;
      this._log.warn("_popAndPerformQueuedOperation - " + msg);

      this._queuedInProgress = false;
      reject(new Error(msg));
      this._popAndPerformQueuedOperation();
      return;
    }

    promise.then(result => {
        this._queuedInProgress = false;
        resolve(result);
        this._popAndPerformQueuedOperation();
      },
      error => {
        this._log.warn("_popAndPerformQueuedOperation - Failure when performing queued operation.",
                       error);
        this._queuedInProgress = false;
        reject(error);
        this._popAndPerformQueuedOperation();
      });
  },
};

var TelemetryStorageImpl = {
  _logger: null,
  // Used to serialize aborted session ping writes to disk.
  _abortedSessionSerializer: new SaveSerializer(),
  // Used to serialize deletion ping writes to disk.
  _deletionPingSerializer: new SaveSerializer(),
  // Used to serialize session state writes to disk.
  _stateSaveSerializer: new SaveSerializer(),

  // Tracks the archived pings in a Map of (id -> {timestampCreated, type}).
  // We use this to cache info on archived pings to avoid scanning the disk more than once.
  _archivedPings: new Map(),
  // A set of promises for pings currently being archived
  _activelyArchiving: new Set(),
  // Track the archive loading task to prevent multiple tasks from being executed.
  _scanArchiveTask: null,
  // Track the archive cleanup task.
  _cleanArchiveTask: null,
  // Whether we already scanned the archived pings on disk.
  _scannedArchiveDirectory: false,

  // Track the pending ping removal task.
  _removePendingPingsTask: null,

  // This tracks all the pending async ping save activity.
  _activePendingPingSaves: new Set(),

  // Tracks the pending pings in a Map of (id -> {timestampCreated, type}).
  // We use this to cache info on pending pings to avoid scanning the disk more than once.
  _pendingPings: new Map(),

  // Track the pending pings enforce quota task.
  _enforcePendingPingsQuotaTask: null,

  // Track the shutdown process to bail out of the clean up task quickly.
  _shutdown: false,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
    }

    return this._logger;
  },

  /**
   * Shutdown & block on any outstanding async activity in this module.
   *
   * @return {Promise} Promise that is resolved when shutdown is complete.
   */
  async shutdown() {
    this._shutdown = true;

    // If the following tasks are still running, block on them. They will bail out as soon
    // as possible.
    await this._abortedSessionSerializer.flushTasks().catch(ex => {
      this._log.error("shutdown - failed to flush aborted-session writes", ex);
    });

    await this._deletionPingSerializer.flushTasks().catch(ex => {
      this._log.error("shutdown - failed to flush deletion ping writes", ex);
    });

    if (this._cleanArchiveTask) {
      await this._cleanArchiveTask.catch(ex => {
        this._log.error("shutdown - the archive cleaning task failed", ex);
      });
    }

    if (this._enforcePendingPingsQuotaTask) {
      await this._enforcePendingPingsQuotaTask.catch(ex => {
        this._log.error("shutdown - the pending pings quota task failed", ex);
      });
    }

    if (this._removePendingPingsTask) {
      await this._removePendingPingsTask.catch(ex => {
        this._log.error("shutdown - the pending pings removal task failed", ex);
      });
    }

    // Wait on pending pings still being saved. While OS.File should have shutdown
    // blockers in place, we a) have seen weird errors being reported that might
    // indicate a bad shutdown path and b) might have completion handlers hanging
    // off the save operations that don't expect to be late in shutdown.
    await this.promisePendingPingSaves();
  },

  /**
   * Save an archived ping to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  saveArchivedPing(ping) {
    let promise = this._saveArchivedPingTask(ping);
    this._activelyArchiving.add(promise);
    promise.then((r) => { this._activelyArchiving.delete(promise); },
                 (e) => { this._activelyArchiving.delete(promise); });
    return promise;
  },

  async _saveArchivedPingTask(ping) {
    const creationDate = new Date(ping.creationDate);
    if (this._archivedPings.has(ping.id)) {
      const data = this._archivedPings.get(ping.id);
      if (data.timestampCreated > creationDate.getTime()) {
        this._log.error("saveArchivedPing - trying to overwrite newer ping with the same id");
        return Promise.reject(new Error("trying to overwrite newer ping with the same id"));
      }
      this._log.warn("saveArchivedPing - overwriting older ping with the same id");
    }

    // Get the archived ping path and append the lz4 suffix to it (so we have 'jsonlz4').
    const filePath = getArchivedPingPath(ping.id, creationDate, ping.type) + "lz4";
    await OS.File.makeDir(OS.Path.dirname(filePath), { ignoreExisting: true,
                                                       from: OS.Constants.Path.profileDir });
    await this.savePingToFile(ping, filePath, /* overwrite*/ true, /* compressed*/ true);

    this._archivedPings.set(ping.id, {
      timestampCreated: creationDate.getTime(),
      type: internString(ping.type),
    });

    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT").add();
    return undefined;
  },

  /**
   * Load an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @return {promise<object>} Promise that is resolved with the ping data.
   */
  async loadArchivedPing(id) {
    TelemetryStopwatch.start("TELEMETRY_ARCHIVE_LOAD_MS");
    const data = this._archivedPings.get(id);
    if (!data) {
      TelemetryStopwatch.cancel("TELEMETRY_ARCHIVE_LOAD_MS");
      this._log.trace("loadArchivedPing - no ping with id: " + id);
      return Promise.reject(new Error("TelemetryStorage.loadArchivedPing - no ping with id " + id));
    }

    const path = getArchivedPingPath(id, new Date(data.timestampCreated), data.type);
    const pathCompressed = path + "lz4";

    // Purge pings which are too big.
    let checkSize = async function(path) {
      const fileSize = (await OS.File.stat(path)).size;
      if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
        Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB")
                 .add(Math.floor(fileSize / 1024 / 1024));
        Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").add();
        TelemetryStopwatch.cancel("TELEMETRY_ARCHIVE_LOAD_MS");
        await OS.File.remove(path, {ignoreAbsent: true});
        throw new Error("loadArchivedPing - exceeded the maximum ping size: " + fileSize);
      }
    };

    let ping;
    try {
      // Try to load a compressed version of the archived ping first.
      this._log.trace("loadArchivedPing - loading ping from: " + pathCompressed);
      await checkSize(pathCompressed);
      ping = await this.loadPingFile(pathCompressed, /* compressed*/ true);
    } catch (ex) {
      if (!ex.becauseNoSuchFile) {
        TelemetryStopwatch.cancel("TELEMETRY_ARCHIVE_LOAD_MS");
        throw ex;
      }
      // If that fails, look for the uncompressed version.
      this._log.trace("loadArchivedPing - compressed ping not found, loading: " + path);
      await checkSize(path);
      ping = await this.loadPingFile(path, /* compressed*/ false);
    }

    TelemetryStopwatch.finish("TELEMETRY_ARCHIVE_LOAD_MS");
    return ping;
  },

  /**
   * Saves session data to disk.
   */
  saveSessionData(sessionData) {
    return this._stateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData));
  },

  async _saveSessionData(sessionData) {
    let dataDir = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR);
    await OS.File.makeDir(dataDir);

    let filePath = OS.Path.join(gDataReportingDir, SESSION_STATE_FILE_NAME);
    try {
      await CommonUtils.writeJSON(sessionData, filePath);
    } catch (e) {
      this._log.error("_saveSessionData - Failed to write session data to " + filePath, e);
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_SAVE").add(1);
    }
  },

  /**
   * Loads session data from the session data file.
   * @return {Promise<Object>} A promise resolved with an object on success,
   *                           with null otherwise.
   */
  loadSessionData() {
    return this._stateSaveSerializer.enqueueTask(() => this._loadSessionData());
  },

  async _loadSessionData() {
    const dataFile = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR,
                                  SESSION_STATE_FILE_NAME);
    let content;
    try {
      content = await OS.File.read(dataFile, { encoding: "utf-8" });
    } catch (ex) {
      this._log.info("_loadSessionData - can not load session data file", ex);
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_LOAD").add(1);
      return null;
    }

    let data;
    try {
      data = JSON.parse(content);
    } catch (ex) {
      this._log.error("_loadSessionData - failed to parse session data", ex);
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_PARSE").add(1);
      return null;
    }

    return data;
  },

  /**
   * Remove an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @param {number} timestampCreated The pings creation timestamp.
   * @param {string} type The pings type.
   * @return {promise<object>} Promise that is resolved when the pings is removed.
   */
  async _removeArchivedPing(id, timestampCreated, type) {
    this._log.trace("_removeArchivedPing - id: " + id + ", timestampCreated: " + timestampCreated + ", type: " + type);
    const path = getArchivedPingPath(id, new Date(timestampCreated), type);
    const pathCompressed = path + "lz4";

    this._log.trace("_removeArchivedPing - removing ping from: " + path);
    await OS.File.remove(path, {ignoreAbsent: true});
    await OS.File.remove(pathCompressed, {ignoreAbsent: true});
    // Remove the ping from the cache.
    this._archivedPings.delete(id);
  },

  /**
   * Clean the pings archive by removing old pings.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runCleanPingArchiveTask() {
    // If there's an archive cleaning task already running, return it.
    if (this._cleanArchiveTask) {
      return this._cleanArchiveTask;
    }

    // Make sure to clear |_cleanArchiveTask| once done.
    let clear = () => this._cleanArchiveTask = null;
    // Since there's no archive cleaning task running, start it.
    this._cleanArchiveTask = this._cleanArchive().then(clear, clear);
    return this._cleanArchiveTask;
  },

  /**
   * Removes pings which are too old from the pings archive.
   * @return {Promise} Resolved when the ping age check is complete.
   */
  async _purgeOldPings() {
    this._log.trace("_purgeOldPings");

    const nowDate = Policy.now();
    const startTimeStamp = nowDate.getTime();
    let dirIterator = new OS.File.DirectoryIterator(gPingsArchivePath);
    let subdirs = (await dirIterator.nextBatch()).filter(e => e.isDir);
    dirIterator.close();

    // Keep track of the newest removed month to update the cache, if needed.
    let newestRemovedMonthTimestamp = null;
    let evictedDirsCount = 0;
    let maxDirAgeInMonths = 0;

    // Walk through the monthly subdirs of the form <YYYY-MM>/
    for (let dir of subdirs) {
      if (this._shutdown) {
        this._log.trace("_purgeOldPings - Terminating the clean up task due to shutdown");
        return;
      }

      if (!isValidArchiveDir(dir.name)) {
        this._log.warn("_purgeOldPings - skipping invalidly named subdirectory " + dir.path);
        continue;
      }

      const archiveDate = getDateFromArchiveDir(dir.name);
      if (!archiveDate) {
        this._log.warn("_purgeOldPings - skipping invalid subdirectory date " + dir.path);
        continue;
      }

      // If this archive directory is older than 180 days, remove it.
      if ((startTimeStamp - archiveDate.getTime()) > MAX_ARCHIVED_PINGS_RETENTION_MS) {
        try {
          await OS.File.removeDir(dir.path);
          evictedDirsCount++;

          // Update the newest removed month.
          newestRemovedMonthTimestamp = Math.max(archiveDate, newestRemovedMonthTimestamp);
        } catch (ex) {
          this._log.error("_purgeOldPings - Unable to remove " + dir.path, ex);
        }
      } else {
        // We're not removing this directory, so record the age for the oldest directory.
        const dirAgeInMonths = Utils.getElapsedTimeInMonths(archiveDate, nowDate);
        maxDirAgeInMonths = Math.max(dirAgeInMonths, maxDirAgeInMonths);
      }
    }

    // Trigger scanning of the archived pings.
    await this.loadArchivedPingList();

    // Refresh the cache: we could still skip this, but it's cheap enough to keep it
    // to avoid introducing task dependencies.
    if (newestRemovedMonthTimestamp) {
      // Scan the archive cache for pings older than the newest directory pruned above.
      for (let [id, info] of this._archivedPings) {
        const timestampCreated = new Date(info.timestampCreated);
        if (timestampCreated.getTime() > newestRemovedMonthTimestamp) {
          continue;
        }
        // Remove outdated pings from the cache.
        this._archivedPings.delete(id);
      }
    }

    const endTimeStamp = Policy.now().getTime();

    // Save the time it takes to evict old directories and the eviction count.
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS")
             .add(evictedDirsCount);
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_DIRS_MS")
             .add(Math.ceil(endTimeStamp - startTimeStamp));
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE")
             .add(maxDirAgeInMonths);
  },

  /**
   * Enforce a disk quota for the pings archive.
   * @return {Promise} Resolved when the quota check is complete.
   */
  async _enforceArchiveQuota() {
    this._log.trace("_enforceArchiveQuota");
    let startTimeStamp = Policy.now().getTime();

    // Build an ordered list, from newer to older, of archived pings.
    let pingList = Array.from(this._archivedPings, p => ({
      id: p[0],
      timestampCreated: p[1].timestampCreated,
      type: p[1].type,
    }));

    pingList.sort((a, b) => b.timestampCreated - a.timestampCreated);

    // If our archive is too big, we should reduce it to reach 90% of the quota.
    const SAFE_QUOTA = Policy.getArchiveQuota() * 0.9;
    // The index of the last ping to keep. Pings older than this one will be deleted if
    // the archive exceeds the quota.
    let lastPingIndexToKeep = null;
    let archiveSizeInBytes = 0;

    // Find the disk size of the archive.
    for (let i = 0; i < pingList.length; i++) {
      if (this._shutdown) {
        this._log.trace("_enforceArchiveQuota - Terminating the clean up task due to shutdown");
        return;
      }

      let ping = pingList[i];

      // Get the size for this ping.
      const fileSize =
        await getArchivedPingSize(ping.id, new Date(ping.timestampCreated), ping.type);
      if (!fileSize) {
        this._log.warn("_enforceArchiveQuota - Unable to find the size of ping " + ping.id);
        continue;
      }

      // Enforce a maximum file size limit on archived pings.
      if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
        this._log.error("_enforceArchiveQuota - removing file exceeding size limit, size: " + fileSize);
        // We just remove the ping from the disk, we don't bother removing it from pingList
        // since it won't contribute to the quota.
        await this._removeArchivedPing(ping.id, ping.timestampCreated, ping.type)
                  .catch(e => this._log.error("_enforceArchiveQuota - failed to remove archived ping" + ping.id));
        Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB")
                 .add(Math.floor(fileSize / 1024 / 1024));
        Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").add();
        continue;
      }

      archiveSizeInBytes += fileSize;

      if (archiveSizeInBytes < SAFE_QUOTA) {
        // We save the index of the last ping which is ok to keep in order to speed up ping
        // pruning.
        lastPingIndexToKeep = i;
      } else if (archiveSizeInBytes > Policy.getArchiveQuota()) {
        // Ouch, our ping archive is too big. Bail out and start pruning!
        break;
      }
    }

    // Save the time it takes to check if the archive is over-quota.
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_CHECKING_OVER_QUOTA_MS")
             .add(Math.round(Policy.now().getTime() - startTimeStamp));

    let submitProbes = (sizeInMB, evictedPings, elapsedMs) => {
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").add(sizeInMB);
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").add(evictedPings);
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS").add(elapsedMs);
    };

    // Check if we're using too much space. If not, submit the archive size and bail out.
    if (archiveSizeInBytes < Policy.getArchiveQuota()) {
      submitProbes(Math.round(archiveSizeInBytes / 1024 / 1024), 0, 0);
      return;
    }

    this._log.info("_enforceArchiveQuota - archive size: " + archiveSizeInBytes + "bytes"
                   + ", safety quota: " + SAFE_QUOTA + "bytes");

    startTimeStamp = Policy.now().getTime();
    let pingsToPurge = pingList.slice(lastPingIndexToKeep + 1);

    // Remove all the pings older than the last one which we are safe to keep.
    for (let ping of pingsToPurge) {
      if (this._shutdown) {
        this._log.trace("_enforceArchiveQuota - Terminating the clean up task due to shutdown");
        return;
      }

      // This list is guaranteed to be in order, so remove the pings at its
      // beginning (oldest).
      await this._removeArchivedPing(ping.id, ping.timestampCreated, ping.type);
    }

    const endTimeStamp = Policy.now().getTime();
    submitProbes(ARCHIVE_SIZE_PROBE_SPECIAL_VALUE, pingsToPurge.length,
                 Math.ceil(endTimeStamp - startTimeStamp));
  },

  async _cleanArchive() {
    this._log.trace("cleanArchiveTask");

    if (!(await OS.File.exists(gPingsArchivePath))) {
      return;
    }

    // Remove pings older than 180 days.
    try {
      await this._purgeOldPings();
    } catch (ex) {
      this._log.error("_cleanArchive - There was an error removing old directories", ex);
    }

    // Make sure we respect the archive disk quota.
    await this._enforceArchiveQuota();
  },

  /**
   * Run the task to enforce the pending pings quota.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  async runEnforcePendingPingsQuotaTask() {
    // If there's a cleaning task already running, return it.
    if (this._enforcePendingPingsQuotaTask) {
      return this._enforcePendingPingsQuotaTask;
    }

    // Since there's no quota enforcing task running, start it.
    try {
      this._enforcePendingPingsQuotaTask = this._enforcePendingPingsQuota();
      await this._enforcePendingPingsQuotaTask;
    } finally {
      this._enforcePendingPingsQuotaTask = null;
    }
    return undefined;
  },

  /**
   * Enforce a disk quota for the pending pings.
   * @return {Promise} Resolved when the quota check is complete.
   */
  async _enforcePendingPingsQuota() {
    this._log.trace("_enforcePendingPingsQuota");
    let startTimeStamp = Policy.now().getTime();

    // Build an ordered list, from newer to older, of pending pings.
    let pingList = Array.from(this._pendingPings, p => ({
      id: p[0],
      lastModificationDate: p[1].lastModificationDate,
    }));

    pingList.sort((a, b) => b.lastModificationDate - a.lastModificationDate);

    // If our pending pings directory is too big, we should reduce it to reach 90% of the quota.
    const SAFE_QUOTA = Policy.getPendingPingsQuota() * 0.9;
    // The index of the last ping to keep. Pings older than this one will be deleted if
    // the pending pings directory size exceeds the quota.
    let lastPingIndexToKeep = null;
    let pendingPingsSizeInBytes = 0;

    // Find the disk size of the pending pings directory.
    for (let i = 0; i < pingList.length; i++) {
      if (this._shutdown) {
        this._log.trace("_enforcePendingPingsQuota - Terminating the clean up task due to shutdown");
        return;
      }

      let ping = pingList[i];

      // Get the size for this ping.
      const fileSize = await getPendingPingSize(ping.id);
      if (!fileSize) {
        this._log.warn("_enforcePendingPingsQuota - Unable to find the size of ping " + ping.id);
        continue;
      }

      pendingPingsSizeInBytes += fileSize;
      if (pendingPingsSizeInBytes < SAFE_QUOTA) {
        // We save the index of the last ping which is ok to keep in order to speed up ping
        // pruning.
        lastPingIndexToKeep = i;
      } else if (pendingPingsSizeInBytes > Policy.getPendingPingsQuota()) {
        // Ouch, our pending pings directory size is too big. Bail out and start pruning!
        break;
      }
    }

    // Save the time it takes to check if the pending pings are over-quota.
    Telemetry.getHistogramById("TELEMETRY_PENDING_CHECKING_OVER_QUOTA_MS")
             .add(Math.round(Policy.now().getTime() - startTimeStamp));

    let recordHistograms = (sizeInMB, evictedPings, elapsedMs) => {
      Telemetry.getHistogramById("TELEMETRY_PENDING_PINGS_SIZE_MB").add(sizeInMB);
      Telemetry.getHistogramById("TELEMETRY_PENDING_PINGS_EVICTED_OVER_QUOTA").add(evictedPings);
      Telemetry.getHistogramById("TELEMETRY_PENDING_EVICTING_OVER_QUOTA_MS").add(elapsedMs);
    };

    // Check if we're using too much space. If not, bail out.
    if (pendingPingsSizeInBytes < Policy.getPendingPingsQuota()) {
      recordHistograms(Math.round(pendingPingsSizeInBytes / 1024 / 1024), 0, 0);
      return;
    }

    this._log.info("_enforcePendingPingsQuota - size: " + pendingPingsSizeInBytes + "bytes"
                   + ", safety quota: " + SAFE_QUOTA + "bytes");

    startTimeStamp = Policy.now().getTime();
    let pingsToPurge = pingList.slice(lastPingIndexToKeep + 1);

    // Remove all the pings older than the last one which we are safe to keep.
    for (let ping of pingsToPurge) {
      if (this._shutdown) {
        this._log.trace("_enforcePendingPingsQuota - Terminating the clean up task due to shutdown");
        return;
      }

      // This list is guaranteed to be in order, so remove the pings at its
      // beginning (oldest).
      await this.removePendingPing(ping.id);
    }

    const endTimeStamp = Policy.now().getTime();
    // We don't know the size of the pending pings directory if we are above the quota,
    // since we stop scanning once we reach the quota. We use a special value to show
    // this condition.
    recordHistograms(PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE, pingsToPurge.length,
                 Math.ceil(endTimeStamp - startTimeStamp));
  },

  /**
   * Reset the storage state in tests.
   */
  reset() {
    this._shutdown = false;
    this._scannedArchiveDirectory = false;
    this._archivedPings = new Map();
    this._scannedPendingDirectory = false;
    this._pendingPings = new Map();
  },

  /**
   * Get a list of info on the archived pings.
   * This will scan the archive directory and grab basic data about the existing
   * pings out of their filename.
   *
   * @return {promise<sequence<object>>}
   */
  async loadArchivedPingList() {
    // If there's an archive loading task already running, return it.
    if (this._scanArchiveTask) {
      return this._scanArchiveTask;
    }

    await waitForAll(this._activelyArchiving);

    if (this._scannedArchiveDirectory) {
      this._log.trace("loadArchivedPingList - Archive already scanned, hitting cache.");
      return this._archivedPings;
    }

    // Since there's no archive loading task running, start it.
    let result;
    try {
      this._scanArchiveTask = this._scanArchive();
      result = await this._scanArchiveTask;
    } finally {
      this._scanArchiveTask = null;
    }
    return result;
  },

  async _scanArchive() {
    this._log.trace("_scanArchive");

    let submitProbes = (pingCount, dirCount) => {
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT")
               .add(pingCount);
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT")
               .add(dirCount);
    };

    if (!(await OS.File.exists(gPingsArchivePath))) {
      submitProbes(0, 0);
      return new Map();
    }

    let dirIterator = new OS.File.DirectoryIterator(gPingsArchivePath);
    let subdirs =
        (await dirIterator.nextBatch()).filter(e => e.isDir).filter(e => isValidArchiveDir(e.name));
    dirIterator.close();

    // Walk through the monthly subdirs of the form <YYYY-MM>/
    for (let dir of subdirs) {
      this._log.trace("_scanArchive - checking in subdir: " + dir.path);
      let pingIterator = new OS.File.DirectoryIterator(dir.path);
      let pings = (await pingIterator.nextBatch()).filter(e => !e.isDir);
      pingIterator.close();

      // Now process any ping files of the form "<timestamp>.<uuid>.<type>.[json|jsonlz4]".
      for (let p of pings) {
        // data may be null if the filename doesn't match the above format.
        let data = this._getArchivedPingDataFromFileName(p.name);
        if (!data) {
          continue;
        }

        // In case of conflicts, overwrite only with newer pings.
        if (this._archivedPings.has(data.id)) {
          const overwrite = data.timestamp > this._archivedPings.get(data.id).timestampCreated;
          this._log.warn("_scanArchive - have seen this id before: " + data.id +
                         ", overwrite: " + overwrite);
          if (!overwrite) {
            continue;
          }

          await this._removeArchivedPing(data.id, data.timestampCreated, data.type)
                    .catch((e) => this._log.warn("_scanArchive - failed to remove ping", e));
        }

        this._archivedPings.set(data.id, {
          timestampCreated: data.timestamp,
          type: internString(data.type),
        });
      }
    }

    // Mark the archive as scanned, so we no longer hit the disk.
    this._scannedArchiveDirectory = true;
    // Update the ping and directories count histograms.
    submitProbes(this._archivedPings.size, subdirs.length);
    return this._archivedPings;
  },

  /**
   * Save a single ping to a file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {string} file The destination file.
   * @param {bool} overwrite If |true|, the file will be overwritten if it exists,
   * if |false| the file will not be overwritten and no error will be reported if
   * the file exists.
   * @param {bool} [compress=false] If |true|, the file will use lz4 compression. Otherwise no
   * compression will be used.
   * @returns {promise}
   */
  async savePingToFile(ping, filePath, overwrite, compress = false) {
    try {
      this._log.trace("savePingToFile - path: " + filePath);
      let pingString = JSON.stringify(ping);
      let options = { tmpPath: filePath + ".tmp", noOverwrite: !overwrite };
      if (compress) {
        options.compression = "lz4";
      }
      await OS.File.writeAtomic(filePath, pingString, options);
    } catch (e) {
      if (!e.becauseExists) {
        throw e;
      }
    }
  },

  /**
   * Save a ping to its file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {bool} overwrite If |true|, the file will be overwritten
   * if it exists.
   * @returns {promise}
   */
  async savePing(ping, overwrite) {
    await getPingDirectory();
    let file = pingFilePath(ping);
    await this.savePingToFile(ping, file, overwrite);
    return file;
  },

  /**
   * Add a ping to the saved pings directory so that it gets saved
   * and sent along with other pings.
   * Note: that the original ping file will not be modified.
   *
   * @param {Object} ping The ping object.
   * @return {Promise} A promise resolved when the ping is saved to the pings directory.
   */
  addPendingPing(ping) {
    return this.savePendingPing(ping);
  },

  /**
   * Remove the file for a ping
   *
   * @param {object} ping The ping.
   * @returns {promise}
   */
  cleanupPingFile(ping) {
    return OS.File.remove(pingFilePath(ping));
  },

  savePendingPing(ping) {
    let p = this.savePing(ping, true).then((path) => {
      this._pendingPings.set(ping.id, {
        path,
        lastModificationDate: Policy.now().getTime(),
      });
      this._log.trace("savePendingPing - saved ping with id " + ping.id);
    });
    this._trackPendingPingSaveTask(p);
    return p;
  },

  async loadPendingPing(id) {
    this._log.trace("loadPendingPing - id: " + id);
    TelemetryStopwatch.start("TELEMETRY_PENDING_LOAD_MS");
    let info = this._pendingPings.get(id);
    if (!info) {
      TelemetryStopwatch.cancel("TELEMETRY_PENDING_LOAD_MS");
      this._log.trace("loadPendingPing - unknown id " + id);
      throw new Error("TelemetryStorage.loadPendingPing - no ping with id " + id);
    }

    // Try to get the dimension of the ping. If that fails, update the histograms.
    let fileSize = 0;
    try {
      fileSize = (await OS.File.stat(info.path)).size;
    } catch (e) {
      if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile) {
        TelemetryStopwatch.cancel("TELEMETRY_PENDING_LOAD_MS");
        throw e;
      }
      // Fall through and let |loadPingFile| report the error.
    }

    // Purge pings which are too big.
    if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
      await this.removePendingPing(id);
      Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB")
               .add(Math.floor(fileSize / 1024 / 1024));
      Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").add();
      TelemetryStopwatch.cancel("TELEMETRY_PENDING_LOAD_MS");
      throw new Error("loadPendingPing - exceeded the maximum ping size: " + fileSize);
    }

    // Try to load the ping file. Update the related histograms on failure.
    let ping;
    try {
      ping = await this.loadPingFile(info.path, false);
    } catch (e) {
      // If we failed to load the ping, check what happened and update the histogram.
      if (e instanceof PingReadError) {
        Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").add();
      } else if (e instanceof PingParseError) {
        Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_PARSE").add();
      }
      TelemetryStopwatch.cancel("TELEMETRY_PENDING_LOAD_MS");

      // Remove the ping from the cache, so we don't try to load it again.
      this._pendingPings.delete(id);
      // Then propagate the rejection.
      throw e;
    }

    TelemetryStopwatch.finish("TELEMETRY_PENDING_LOAD_MS");
    return ping;
  },

  removePendingPing(id) {
    let info = this._pendingPings.get(id);
    if (!info) {
      this._log.trace("removePendingPing - unknown id " + id);
      return Promise.resolve();
    }

    this._log.trace("removePendingPing - deleting ping with id: " + id +
                    ", path: " + info.path);
    this._pendingPings.delete(id);
    return OS.File.remove(info.path).catch((ex) =>
      this._log.error("removePendingPing - failed to remove ping", ex));
  },

  /**
   * Track any pending ping save tasks through the promise passed here.
   * This is needed to block on any outstanding ping save activity.
   *
   * @param {Object<Promise>} The save promise to track.
   */
  _trackPendingPingSaveTask(promise) {
    let clear = () => this._activePendingPingSaves.delete(promise);
    promise.then(clear, clear);
    this._activePendingPingSaves.add(promise);
  },

  /**
   * Return a promise that allows to wait on pending pings being saved.
   * @return {Object<Promise>} A promise resolved when all the pending pings save promises
   *         are resolved.
   */
  promisePendingPingSaves() {
    // Make sure to wait for all the promises, even if they reject. We don't need to log
    // the failures here, as they are already logged elsewhere.
    return waitForAll(this._activePendingPingSaves);
  },

  /**
   * Run the task to remove all the pending pings (except the deletion ping).
   *
   * @return {Promise} Resolved when the pings are removed.
   */
  async runRemovePendingPingsTask() {
    // If we already have a pending pings removal task active, return that.
    if (this._removePendingPingsTask) {
      return this._removePendingPingsTask;
    }

    // Start the task to remove all pending pings. Also make sure to clear the task once done.
    try {
      this._removePendingPingsTask = this.removePendingPings();
      await this._removePendingPingsTask;
    } finally {
      this._removePendingPingsTask = null;
    }
    return undefined;
  },

  async removePendingPings() {
    this._log.trace("removePendingPings - removing all pending pings");

    // Wait on pending pings still being saved, so so we don't miss removing them.
    await this.promisePendingPingSaves();

    // Individually remove existing pings, so we don't interfere with operations expecting
    // the pending pings directory to exist.
    const directory = TelemetryStorage.pingDirectoryPath;
    let iter = new OS.File.DirectoryIterator(directory);

    try {
      if (!(await iter.exists())) {
        this._log.trace("removePendingPings - the pending pings directory doesn't exist");
        return;
      }

      let files = (await iter.nextBatch()).filter(e => !e.isDir);
      for (let file of files) {
        try {
          await OS.File.remove(file.path);
        } catch (ex) {
          this._log.error("removePendingPings - failed to remove file " + file.path, ex);
          continue;
        }
      }
    } finally {
      await iter.close();
    }
  },

  /**
   * This function migrates pings that are stored in the userApplicationDataDir
   * under the "Pending Pings" sub-directory.
   */
  async _migrateAppDataPings() {
    this._log.trace("_migrateAppDataPings");

    // The tests suites might not create and define the "UAppData" directory.
    // We account for that here instead of manually going through each test using
    // telemetry to manually create the directory and define the constant.
    if (!OS.Constants.Path.userApplicationDataDir) {
      this._log.trace("_migrateAppDataPings - userApplicationDataDir is not defined. Is this a test?");
      return;
    }

    const appDataPendingPings =
      OS.Path.join(OS.Constants.Path.userApplicationDataDir, "Pending Pings");

    // Iterate through the pending ping files.
    let iter = new OS.File.DirectoryIterator(appDataPendingPings);
    try {
      // Check if appDataPendingPings exists and bail out if it doesn't.
      if (!(await iter.exists())) {
        this._log.trace("_migrateAppDataPings - the AppData pending pings directory doesn't exist.");
        return;
      }

      let files = (await iter.nextBatch()).filter(e => !e.isDir);
      for (let file of files) {
        try {
          // Load the ping data from the original file.
          const pingData = await this.loadPingFile(file.path);

          // Save it among the pending pings in the user profile, overwrite on
          // ping id collision.
          await TelemetryStorage.savePing(pingData, true);

          // Finally remove the file.
          await OS.File.remove(file.path);
        } catch (ex) {
          this._log.error("_migrateAppDataPings - failed to remove file " + file.path, ex);
          continue;
        }
      }
    } finally {
      await iter.close();
    }
  },

  loadPendingPingList() {
    // If we already have a pending scanning task active, return that.
    if (this._scanPendingPingsTask) {
      return this._scanPendingPingsTask;
    }

    if (this._scannedPendingDirectory) {
      this._log.trace("loadPendingPingList - Pending already scanned, hitting cache.");
      return Promise.resolve(this._buildPingList());
    }

    // Since there's no pending pings scan task running, start it.
    // Also make sure to clear the task once done.
    this._scanPendingPingsTask = this._scanPendingPings().then(pings => {
      this._scanPendingPingsTask = null;
      return pings;
    }, ex => {
      this._scanPendingPingsTask = null;
      throw ex;
    });
    return this._scanPendingPingsTask;
  },

  getPendingPingList() {
    return this._buildPingList();
  },

  async _scanPendingPings() {
    this._log.trace("_scanPendingPings");

    // Before pruning the pending pings, migrate over the ones from the user
    // application data directory (mainly crash pings that failed to be sent).
    await this._migrateAppDataPings();

    let directory = TelemetryStorage.pingDirectoryPath;
    let iter = new OS.File.DirectoryIterator(directory);
    let exists = await iter.exists();

    try {
      if (!exists) {
        return [];
      }

      let files = (await iter.nextBatch()).filter(e => !e.isDir);

      for (let file of files) {
        if (this._shutdown) {
          return [];
        }

        let info;
        try {
          info = await OS.File.stat(file.path);
        } catch (ex) {
          this._log.error("_scanPendingPings - failed to stat file " + file.path, ex);
          continue;
        }

        // Enforce a maximum file size limit on pending pings.
        if (info.size > PING_FILE_MAXIMUM_SIZE_BYTES) {
          this._log.error("_scanPendingPings - removing file exceeding size limit " + file.path);
          try {
            await OS.File.remove(file.path);
          } catch (ex) {
            this._log.error("_scanPendingPings - failed to remove file " + file.path, ex);
          } finally {
            Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB")
                     .add(Math.floor(info.size / 1024 / 1024));
            Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").add();
          }
          continue;
        }

        let id = OS.Path.basename(file.path);
        if (!UUID_REGEX.test(id)) {
          this._log.trace("_scanPendingPings - filename is not a UUID: " + id);
          id = Utils.generateUUID();
        }

        this._pendingPings.set(id, {
          path: file.path,
          lastModificationDate: info.lastModificationDate.getTime(),
        });
      }
    } finally {
      await iter.close();
    }

    // Explicitly load the deletion ping from its known path, if it's there.
    if (await OS.File.exists(gDeletionPingFilePath)) {
      this._log.trace("_scanPendingPings - Adding pending deletion ping.");
      // We can't get the ping id or the last modification date without hitting the disk.
      // Since deletion has a special handling, we don't really need those.
      this._pendingPings.set(Utils.generateUUID(), {
        path: gDeletionPingFilePath,
        lastModificationDate: Date.now(),
      });
    }

    this._scannedPendingDirectory = true;
    return this._buildPingList();
  },

  _buildPingList() {
    const list = Array.from(this._pendingPings, p => ({
      id: p[0],
      lastModificationDate: p[1].lastModificationDate,
    }));

    list.sort((a, b) => b.lastModificationDate - a.lastModificationDate);
    return list;
  },

  get pendingPingCount() {
    return this._pendingPings.size;
  },

  /**
   * Loads a ping file.
   * @param {String} aFilePath The path of the ping file.
   * @param {Boolean} [aCompressed=false] If |true|, expects the file to be compressed using lz4.
   * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
   *                           ping contains invalid data.
   * @throws {PingReadError} There was an error while reading the ping file from the disk.
   * @throws {PingParseError} There was an error while parsing the JSON content of the ping file.
   */
  async loadPingFile(aFilePath, aCompressed = false) {
    let options = {};
    if (aCompressed) {
      options.compression = "lz4";
    }

    let array;
    try {
      array = await OS.File.read(aFilePath, options);
    } catch (e) {
      this._log.trace("loadPingfile - unreadable ping " + aFilePath, e);
      throw new PingReadError(e.message, e.becauseNoSuchFile);
    }

    let decoder = new TextDecoder();
    let string = decoder.decode(array);
    let ping;
    try {
      ping = JSON.parse(string);
    } catch (e) {
      this._log.trace("loadPingfile - unparseable ping " + aFilePath, e);
      await OS.File.remove(aFilePath).catch((ex) => {
        this._log.error("loadPingFile - failed removing unparseable ping file", ex);
      });
      throw new PingParseError(e.message);
    }

    return ping;
  },

  /**
   * Archived pings are saved with file names of the form:
   * "<timestamp>.<uuid>.<type>.[json|jsonlz4]"
   * This helper extracts that data from a given filename.
   *
   * @param fileName {String} The filename.
   * @return {Object} Null if the filename didn't match the expected form.
   *                  Otherwise an object with the extracted data in the form:
   *                  { timestamp: <number>,
   *                    id: <string>,
   *                    type: <string> }
   */
  _getArchivedPingDataFromFileName(fileName) {
    // Extract the parts.
    let parts = fileName.split(".");
    if (parts.length != 4) {
      this._log.trace("_getArchivedPingDataFromFileName - should have 4 parts");
      return null;
    }

    let [timestamp, uuid, type, extension] = parts;
    if (extension != "json" && extension != "jsonlz4") {
      this._log.trace("_getArchivedPingDataFromFileName - should have 'json' or 'jsonlz4' extension");
      return null;
    }

    // Check for a valid timestamp.
    timestamp = parseInt(timestamp);
    if (Number.isNaN(timestamp)) {
      this._log.trace("_getArchivedPingDataFromFileName - should have a valid timestamp");
      return null;
    }

    // Check for a valid UUID.
    if (!UUID_REGEX.test(uuid)) {
      this._log.trace("_getArchivedPingDataFromFileName - should have a valid id");
      return null;
    }

    // Check for a valid type string.
    const typeRegex = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i;
    if (!typeRegex.test(type)) {
      this._log.trace("_getArchivedPingDataFromFileName - should have a valid type");
      return null;
    }

    return {
      timestamp,
      id: uuid,
      type,
    };
  },

  async saveAbortedSessionPing(ping) {
    this._log.trace("saveAbortedSessionPing - ping path: " + gAbortedSessionFilePath);
    await OS.File.makeDir(gDataReportingDir, { ignoreExisting: true });

    return this._abortedSessionSerializer.enqueueTask(() =>
      this.savePingToFile(ping, gAbortedSessionFilePath, true));
  },

  async loadAbortedSessionPing() {
    let ping = null;
    try {
      ping = await this.loadPingFile(gAbortedSessionFilePath);
    } catch (ex) {
      if (ex.becauseNoSuchFile) {
        this._log.trace("loadAbortedSessionPing - no such file");
      } else {
        this._log.error("loadAbortedSessionPing - error loading ping", ex)
      }
    }
    return ping;
  },

  removeAbortedSessionPing() {
    return this._abortedSessionSerializer.enqueueTask(async () => {
      try {
        await OS.File.remove(gAbortedSessionFilePath, { ignoreAbsent: false });
        this._log.trace("removeAbortedSessionPing - success");
      } catch (ex) {
        if (ex.becauseNoSuchFile) {
          this._log.trace("removeAbortedSessionPing - no such file");
        } else {
          this._log.error("removeAbortedSessionPing - error removing ping", ex)
        }
      }
    });
  },

  /**
   * Save the deletion ping.
   * @param ping The deletion ping.
   * @return {Promise} Resolved when the ping is saved.
   */
  async saveDeletionPing(ping) {
    this._log.trace("saveDeletionPing - ping path: " + gDeletionPingFilePath);
    await OS.File.makeDir(gDataReportingDir, { ignoreExisting: true });

    let p = this._deletionPingSerializer.enqueueTask(() =>
      this.savePingToFile(ping, gDeletionPingFilePath, true));
    this._trackPendingPingSaveTask(p);
    return p;
  },

  /**
   * Remove the deletion ping.
   * @return {Promise} Resolved when the ping is deleted from the disk.
   */
  async removeDeletionPing() {
    return this._deletionPingSerializer.enqueueTask(async () => {
      try {
        await OS.File.remove(gDeletionPingFilePath, { ignoreAbsent: false });
        this._log.trace("removeDeletionPing - success");
      } catch (ex) {
        if (ex.becauseNoSuchFile) {
          this._log.trace("removeDeletionPing - no such file");
        } else {
          this._log.error("removeDeletionPing - error removing ping", ex)
        }
      }
    });
  },

  isDeletionPing(aPingId) {
    let pingInfo = this._pendingPings.get(aPingId);
    if (!pingInfo) {
      return false;
    }

    if (pingInfo.path != gDeletionPingFilePath) {
      return false;
    }

    return true;
  },

  /**
   * Remove FHR database files. This is temporary and will be dropped in
   * the future.
   * @return {Promise} Resolved when the database files are deleted.
   */
  async removeFHRDatabase() {
    this._log.trace("removeFHRDatabase");

    // Let's try to remove the FHR DB with the default filename first.
    const FHR_DB_DEFAULT_FILENAME = "healthreport.sqlite";

    // Even if it's uncommon, there may be 2 additional files: - a "write ahead log"
    // (-wal) file and a "shared memory file" (-shm). We need to remove them as well.
    let FILES_TO_REMOVE = [
      OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME),
      OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME + "-wal"),
      OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME + "-shm"),
    ];

    // FHR could have used either the default DB file name or a custom one
    // through this preference.
    const FHR_DB_CUSTOM_FILENAME =
      Preferences.get("datareporting.healthreport.dbName", undefined);
    if (FHR_DB_CUSTOM_FILENAME) {
      FILES_TO_REMOVE.push(
        OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME),
        OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME + "-wal"),
        OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME + "-shm"));
    }

    for (let f of FILES_TO_REMOVE) {
      await OS.File.remove(f, {ignoreAbsent: true})
                   .catch(e => this._log.error("removeFHRDatabase - failed to remove " + f, e));
    }
  },
};

// Utility functions

function pingFilePath(ping) {
  // Support legacy ping formats, who don't have an "id" field, but a "slug" field.
  let pingIdentifier = (ping.slug) ? ping.slug : ping.id;
  return OS.Path.join(TelemetryStorage.pingDirectoryPath, pingIdentifier);
}

function getPingDirectory() {
  return (async function() {
    let directory = TelemetryStorage.pingDirectoryPath;

    if (!(await OS.File.exists(directory))) {
      await OS.File.makeDir(directory, { unixMode: OS.Constants.S_IRWXU });
    }

    return directory;
  })();
}

/**
 * Build the path to the archived ping.
 * @param {String} aPingId The ping id.
 * @param {Object} aDate The ping creation date.
 * @param {String} aType The ping type.
 * @return {String} The full path to the archived ping.
 */
function getArchivedPingPath(aPingId, aDate, aType) {
  // Get the ping creation date and generate the archive directory to hold it. Note
  // that getMonth returns a 0-based month, so we need to add an offset.
  let month = new String(aDate.getMonth() + 1);
  let archivedPingDir = OS.Path.join(gPingsArchivePath,
    aDate.getFullYear() + "-" + month.padStart(2, "0"));
  // Generate the archived ping file path as YYYY-MM/<TIMESTAMP>.UUID.type.json
  let fileName = [aDate.getTime(), aPingId, aType, "json"].join(".");
  return OS.Path.join(archivedPingDir, fileName);
}

/**
 * Get the size of the ping file on the disk.
 * @return {Integer} The file size, in bytes, of the ping file or 0 on errors.
 */
var getArchivedPingSize = async function(aPingId, aDate, aType) {
  const path = getArchivedPingPath(aPingId, aDate, aType);
  let filePaths = [ path + "lz4", path ];

  for (let path of filePaths) {
    try {
      return (await OS.File.stat(path)).size;
    } catch (e) {}
  }

  // That's odd, this ping doesn't seem to exist.
  return 0;
};

/**
 * Get the size of the pending ping file on the disk.
 * @return {Integer} The file size, in bytes, of the ping file or 0 on errors.
 */
var getPendingPingSize = async function(aPingId) {
  const path = OS.Path.join(TelemetryStorage.pingDirectoryPath, aPingId)
  try {
    return (await OS.File.stat(path)).size;
  } catch (e) {}

  // That's odd, this ping doesn't seem to exist.
  return 0;
};

/**
 * Check if a directory name is in the "YYYY-MM" format.
 * @param {String} aDirName The name of the pings archive directory.
 * @return {Boolean} True if the directory name is in the right format, false otherwise.
 */
function isValidArchiveDir(aDirName) {
  const dirRegEx = /^[0-9]{4}-[0-9]{2}$/;
  return dirRegEx.test(aDirName);
}

/**
 * Gets a date object from an archive directory name.
 * @param {String} aDirName The name of the pings archive directory. Must be in the YYYY-MM
 *        format.
 * @return {Object} A Date object or null if the dir name is not valid.
 */
function getDateFromArchiveDir(aDirName) {
  let [year, month] = aDirName.split("-");
  year = parseInt(year);
  month = parseInt(month);
  // Make sure to have sane numbers.
  if (!Number.isFinite(month) || !Number.isFinite(year) || month < 1 || month > 12) {
    return null;
  }
  return new Date(year, month - 1, 1, 0, 0, 0);
}
PK
!<Q.modules/TelemetryTimestamps.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["TelemetryTimestamps"];

const Cu = Components.utils;

/**
 * This module's purpose is to collect timestamps for important
 * application-specific events.
 *
 * The TelemetryController component attaches the timestamps stored by this module to
 * the telemetry submission, substracting the process lifetime so that the times
 * are relative to process startup. The overall goal is to produce a basic
 * timeline of the startup process.
 */
var timeStamps = {};

this.TelemetryTimestamps = {
  /**
   * Adds a timestamp to the list. The addition of TimeStamps that already have
   * a value stored is ignored.
   *
   * @param name must be a unique, generally "camelCase" descriptor of what the
   *             timestamp represents. e.g.: "delayedStartupStarted"
   * @param value is a timeStamp in milliseconds since the epoch. If omitted,
   *              defaults to Date.now().
   */
  add: function TT_add(name, value) {
    // Default to "now" if not specified
    if (value == null)
      value = Date.now();

    if (isNaN(value))
      throw new Error("Value must be a timestamp");

    // If there's an existing value, just ignore the new value.
    if (timeStamps.hasOwnProperty(name))
      return;

    timeStamps[name] = value;
  },

  /**
   * Returns a JS object containing all of the timeStamps as properties (can be
   * easily serialized to JSON). Used by TelemetryController to retrieve the data
   * to attach to the telemetry submission.
   */
  get: function TT_get() {
    // Return a copy of the object.
    return Cu.cloneInto(timeStamps, {});
  }
};
PK
!<mTTmodules/TelemetryUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "TelemetryUtils"
];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm", this);

const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";

const IS_CONTENT_PROCESS = (function() {
  // We cannot use Services.appinfo here because in telemetry xpcshell tests,
  // appinfo is initially unavailable, and becomes available only later on.
  let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  return runtime.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
})();

this.TelemetryUtils = {
  Preferences: Object.freeze({
    // General Preferences
    ArchiveEnabled: "toolkit.telemetry.archive.enabled",
    CachedClientId: "toolkit.telemetry.cachedClientID",
    FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
    HealthPingEnabled: "toolkit.telemetry.healthping.enabled",
    OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
    Server: "toolkit.telemetry.server",
    ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
    ShutdownPingSenderFirstSession: "toolkit.telemetry.shutdownPingSender.enabledFirstSession",
    TelemetryEnabled: "toolkit.telemetry.enabled",
    Unified: "toolkit.telemetry.unified",
    UpdatePing: "toolkit.telemetry.updatePing.enabled",
    NewProfilePingEnabled: "toolkit.telemetry.newProfilePing.enabled",
    NewProfilePingDelay: "toolkit.telemetry.newProfilePing.delay",
    PreviousBuildID: "toolkit.telemetry.previousBuildID",

    // Log Preferences
    LogLevel: "toolkit.telemetry.log.level",
    LogDump: "toolkit.telemetry.log.dump",

    // Data reporting Preferences
    AcceptedPolicyDate: "datareporting.policy.dataSubmissionPolicyNotifiedTime",
    AcceptedPolicyVersion: "datareporting.policy.dataSubmissionPolicyAcceptedVersion",
    BypassNotification: "datareporting.policy.dataSubmissionPolicyBypassNotification",
    CurrentPolicyVersion: "datareporting.policy.currentPolicyVersion",
    DataSubmissionEnabled: "datareporting.policy.dataSubmissionEnabled",
    FhrUploadEnabled: "datareporting.healthreport.uploadEnabled",
    MinimumPolicyVersion: "datareporting.policy.minimumPolicyVersion",
    FirstRunURL: "datareporting.policy.firstRunURL",
  }),

  /**
   * True if this is a content process.
   */
  get isContentProcess() {
    return IS_CONTENT_PROCESS;
  },

  /**
   * Returns the state of the Telemetry enabled preference, making sure
   * it correctly evaluates to a boolean type.
   */
  get isTelemetryEnabled() {
    return Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED, false) === true;
  },

  /**
   * Turn a millisecond timestamp into a day timestamp.
   *
   * @param aMsec A number of milliseconds since Unix epoch.
   * @return The number of whole days since Unix epoch.
   */
  millisecondsToDays(aMsec) {
    return Math.floor(aMsec / MILLISECONDS_PER_DAY);
  },

  /**
   * Takes a date and returns it truncated to a date with daily precision.
   */
  truncateToDays(date) {
    return new Date(date.getFullYear(),
                    date.getMonth(),
                    date.getDate(),
                    0, 0, 0, 0);
  },

  /**
   * Takes a date and returns it truncated to a date with hourly precision.
   */
  truncateToHours(date) {
    return new Date(date.getFullYear(),
                    date.getMonth(),
                    date.getDate(),
                    date.getHours(),
                    0, 0, 0);
  },

  /**
   * Check if the difference between the times is within the provided tolerance.
   * @param {Number} t1 A time in milliseconds.
   * @param {Number} t2 A time in milliseconds.
   * @param {Number} tolerance The tolerance, in milliseconds.
   * @return {Boolean} True if the absolute time difference is within the tolerance, false
   *                   otherwise.
   */
  areTimesClose(t1, t2, tolerance) {
    return Math.abs(t1 - t2) <= tolerance;
  },

  /**
   * Get the next midnight for a date.
   * @param {Object} date The date object to check.
   * @return {Object} The Date object representing the next midnight.
   */
  getNextMidnight(date) {
    let nextMidnight = new Date(this.truncateToDays(date));
    nextMidnight.setDate(nextMidnight.getDate() + 1);
    return nextMidnight;
  },

  /**
   * Get the midnight which is closer to the provided date.
   * @param {Object} date The date object to check.
   * @param {Number} tolerance The tolerance within we find the closest midnight.
   * @return {Object} The Date object representing the closes midnight, or null if midnight
   *                  is not within the midnight tolerance.
   */
  getNearestMidnight(date, tolerance) {
    let lastMidnight = this.truncateToDays(date);
    if (this.areTimesClose(date.getTime(), lastMidnight.getTime(), tolerance)) {
      return lastMidnight;
    }

    const nextMidnightDate = this.getNextMidnight(date);
    if (this.areTimesClose(date.getTime(), nextMidnightDate.getTime(), tolerance)) {
      return nextMidnightDate;
    }
    return null;
  },

  generateUUID() {
    let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
    // strip {}
    return str.substring(1, str.length - 1);
  },

  /**
   * Find how many months passed between two dates.
   * @param {Object} aStartDate The starting date.
   * @param {Object} aEndDate The ending date.
   * @return {Integer} The number of months between the two dates.
   */
  getElapsedTimeInMonths(aStartDate, aEndDate) {
    return (aEndDate.getMonth() - aStartDate.getMonth())
           + 12 * (aEndDate.getFullYear() - aStartDate.getFullYear());
  },

  /**
   * Date.toISOString() gives us UTC times, this gives us local times in
   * the ISO date format. See http://www.w3.org/TR/NOTE-datetime
   * @param {Object} date The input date.
   * @return {String} The local time ISO string.
   */
  toLocalTimeISOString(date) {
    function padNumber(number, length) {
      return number.toString().padStart(length, "0");
    }

    let sign = (n) => n >= 0 ? "+" : "-";
    // getTimezoneOffset counter-intuitively returns -60 for UTC+1.
    let tzOffset = -date.getTimezoneOffset();

    // YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
    return padNumber(date.getFullYear(), 4)
      + "-" + padNumber(date.getMonth() + 1, 2)
      + "-" + padNumber(date.getDate(), 2)
      + "T" + padNumber(date.getHours(), 2)
      + ":" + padNumber(date.getMinutes(), 2)
      + ":" + padNumber(date.getSeconds(), 2)
      + "." + date.getMilliseconds()
      + sign(tzOffset) + padNumber(Math.floor(Math.abs(tzOffset / 60)), 2)
      + ":" + padNumber(Math.abs(tzOffset % 60), 2);
  },

  /**
   * @returns {number} The monotonic time since the process start
   * or (non-monotonic) Date value if this fails back.
   */
  monotonicNow() {
    try {
      return Services.telemetry.msSinceProcessStart();
    } catch (ex) {
      return Date.now();
    }
  }
};
PK
!<G(!modules/ThirdPartyCookieProbe.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);

this.EXPORTED_SYMBOLS = ["ThirdPartyCookieProbe"];

const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;

/**
 * A probe implementing the measurements detailed at
 * https://wiki.mozilla.org/SecurityEngineering/ThirdPartyCookies/Telemetry
 *
 * This implementation uses only in-memory data.
 */
this.ThirdPartyCookieProbe = function() {
  /**
   * A set of third-party sites that have caused cookies to be
   * rejected. These sites are trimmed down to ETLD + 1
   * (i.e. "x.y.com" and "z.y.com" are both trimmed down to "y.com",
   * "x.y.co.uk" is trimmed down to "y.co.uk").
   *
   * Used to answer the following question: "For each third-party
   * site, how many other first parties embed them and result in
   * cookie traffic?" (see
   * https://wiki.mozilla.org/SecurityEngineering/ThirdPartyCookies/Telemetry#Breadth
   * )
   *
   * @type Map<string, RejectStats> A mapping from third-party site
   * to rejection statistics.
   */
  this._thirdPartyCookies = new Map();
  /**
   * Timestamp of the latest call to flush() in milliseconds since the Epoch.
   */
  this._latestFlush = Date.now();
};

this.ThirdPartyCookieProbe.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  init() {
    Services.obs.addObserver(this, "profile-before-change");
    Services.obs.addObserver(this, "third-party-cookie-accepted");
    Services.obs.addObserver(this, "third-party-cookie-rejected");
  },
  dispose() {
    Services.obs.removeObserver(this, "profile-before-change");
    Services.obs.removeObserver(this, "third-party-cookie-accepted");
    Services.obs.removeObserver(this, "third-party-cookie-rejected");
  },
  /**
   * Observe either
   * - "profile-before-change" (no meaningful subject or data) - time to flush statistics and unregister; or
   * - "third-party-cookie-accepted"/"third-party-cookie-rejected" with
   *    subject: the nsIURI of the third-party that attempted to set the cookie;
   *    data: a string holding the uri of the page seen by the user.
   */
  observe(docURI, topic, referrer) {
    try {
      if (topic == "profile-before-change") {
        // A final flush, then unregister
        this.flush();
        this.dispose();
      }
      if (topic != "third-party-cookie-accepted"
          && topic != "third-party-cookie-rejected") {
        // Not a third-party cookie
        return;
      }
      // Add host to this._thirdPartyCookies
      // Note: nsCookieService passes "?" if the issuer is unknown.  Avoid
      //       normalizing in this case since its not a valid URI.
      let firstParty = (referrer === "?") ? referrer : normalizeHost(referrer);
      let thirdParty = normalizeHost(docURI.QueryInterface(Ci.nsIURI).host);
      let data = this._thirdPartyCookies.get(thirdParty);
      if (!data) {
        data = new RejectStats();
        this._thirdPartyCookies.set(thirdParty, data);
      }
      if (topic == "third-party-cookie-accepted") {
        data.addAccepted(firstParty);
      } else {
        data.addRejected(firstParty);
      }
    } catch (ex) {
      if (ex instanceof Ci.nsIXPCException) {
        if (ex.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
            ex.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
          return;
        }
      }
      // Other errors should not remain silent.
      Services.console.logStringMessage("ThirdPartyCookieProbe: Uncaught error " + ex + "\n" + ex.stack);
    }
  },

  /**
   * Clear internal data, fill up corresponding histograms.
   *
   * @param {number} aNow (optional, used for testing purposes only)
   * The current instant. Used to make tests time-independent.
   */
  flush(aNow = Date.now()) {
    let updays = (aNow - this._latestFlush) / MILLISECONDS_PER_DAY;
    if (updays <= 0) {
      // Unlikely, but regardless, don't risk division by zero
      // or weird stuff.
      return;
    }
    this._latestFlush = aNow;
    this._thirdPartyCookies.clear();
  }
};

/**
 * Data gathered on cookies that a third party site has attempted to set.
 *
 * Privacy note: the only data actually sent to the server is the size of
 * the sets.
 *
 * @constructor
 */
var RejectStats = function() {
  /**
   * The set of all sites for which we have accepted third-party cookies.
   */
  this._acceptedSites = new Set();
  /**
   * The set of all sites for which we have rejected third-party cookies.
   */
  this._rejectedSites = new Set();
  /**
   * Total number of attempts to set a third-party cookie that have
   * been accepted. Two accepted attempts on the same site will both
   * augment this count.
   */
  this._acceptedRequests = 0;
  /**
   * Total number of attempts to set a third-party cookie that have
   * been rejected. Two rejected attempts on the same site will both
   * augment this count.
   */
  this._rejectedRequests = 0;
};
RejectStats.prototype = {
  addAccepted(firstParty) {
    this._acceptedSites.add(firstParty);
    this._acceptedRequests++;
  },
  addRejected(firstParty) {
    this._rejectedSites.add(firstParty);
    this._rejectedRequests++;
  },
  get countAcceptedSites() {
    return this._acceptedSites.size;
  },
  get countRejectedSites() {
    return this._rejectedSites.size;
  },
  get countAcceptedRequests() {
    return this._acceptedRequests;
  },
  get countRejectedRequests() {
    return this._rejectedRequests;
  }
};

/**
 * Normalize a host to its eTLD + 1.
 */
function normalizeHost(host) {
  return Services.eTLD.getBaseDomainFromHost(host);
}
PK
!<RE9zmodules/Timer.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * JS module implementation of setTimeout and clearTimeout.
 */

this.EXPORTED_SYMBOLS = ["setTimeout", "clearTimeout", "setInterval", "clearInterval"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

// This gives us >=2^30 unique timer IDs, enough for 1 per ms for 12.4 days.
var gNextId = 1; // setTimeout and setInterval must return a positive integer

var gTimerTable = new Map(); // int -> nsITimer

this.setTimeout = function setTimeout(aCallback, aMilliseconds) {
  let id = gNextId++;
  let args = Array.slice(arguments, 2);
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(function setTimeout_timer() {
    gTimerTable.delete(id);
    aCallback.apply(null, args);
  }, aMilliseconds, timer.TYPE_ONE_SHOT);

  gTimerTable.set(id, timer);
  return id;
}

this.setInterval = function setInterval(aCallback, aMilliseconds) {
  let id = gNextId++;
  let args = Array.slice(arguments, 2);
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(function setInterval_timer() {
    aCallback.apply(null, args);
  }, aMilliseconds, timer.TYPE_REPEATING_SLACK);

  gTimerTable.set(id, timer);
  return id;
}

this.clearInterval = this.clearTimeout = function clearTimeout(aId) {
  if (gTimerTable.has(aId)) {
    gTimerTable.get(aId).cancel();
    gTimerTable.delete(aId);
  }
}
PK
!< S^[[modules/Troubleshoot.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = [
  "Troubleshoot",
];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

var Experiments;
try {
  Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments;
} catch (e) {
}

const env = Cc["@mozilla.org/process/environment;1"]
              .getService(Ci.nsIEnvironment);

// We use a preferences whitelist to make sure we only show preferences that
// are useful for support and won't compromise the user's privacy.  Note that
// entries are *prefixes*: for example, "accessibility." applies to all prefs
// under the "accessibility.*" branch.
const PREFS_WHITELIST = [
  "accessibility.",
  "apz.",
  "browser.cache.",
  "browser.display.",
  "browser.download.folderList",
  "browser.download.hide_plugins_without_extensions",
  "browser.download.lastDir.savePerSite",
  "browser.download.manager.addToRecentDocs",
  "browser.download.manager.alertOnEXEOpen",
  "browser.download.manager.resumeOnWakeDelay",
  "browser.download.preferred.",
  "browser.download.useDownloadDir",
  "browser.fixup.",
  "browser.history_expire_",
  "browser.link.open_newwindow",
  "browser.places.",
  "browser.privatebrowsing.",
  "browser.search.context.loadInBackground",
  "browser.search.log",
  "browser.search.openintab",
  "browser.search.param",
  "browser.search.searchEnginesURL",
  "browser.search.suggest.enabled",
  "browser.search.update",
  "browser.search.useDBForOrder",
  "browser.sessionstore.",
  "browser.startup.homepage",
  "browser.tabs.",
  "browser.urlbar.",
  "browser.zoom.",
  "dom.",
  "extensions.checkCompatibility",
  "extensions.lastAppVersion",
  "font.",
  "general.autoScroll",
  "general.useragent.",
  "gfx.",
  "html5.",
  "image.",
  "javascript.",
  "keyword.",
  "layers.",
  "layout.css.dpi",
  "layout.css.servo.enabled",
  "media.",
  "mousewheel.",
  "network.",
  "permissions.default.image",
  "places.",
  "plugin.",
  "plugins.",
  "print.",
  "privacy.",
  "security.",
  "services.sync.declinedEngines",
  "services.sync.lastPing",
  "services.sync.lastSync",
  "services.sync.numClients",
  "services.sync.engine.",
  "social.enabled",
  "storage.vacuum.last.",
  "svg.",
  "toolkit.startup.recent_crashes",
  "ui.osk.enabled",
  "ui.osk.detect_physical_keyboard",
  "ui.osk.require_tablet_mode",
  "ui.osk.debug.keyboardDisplayReason",
  "webgl.",
];

// The blacklist, unlike the whitelist, is a list of regular expressions.
const PREFS_BLACKLIST = [
  /^network[.]proxy[.]/,
  /[.]print_to_filename$/,
  /^print[.]macosx[.]pagesetup/,
];

// Table of getters for various preference types.
// It's important to use getComplexValue for strings: it returns Unicode (wchars), getCharPref returns UTF-8 encoded chars.
const PREFS_GETTERS = {};

PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) => prefs.getStringPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) => prefs.getIntPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) => prefs.getBoolPref(name);

// Return the preferences filtered by PREFS_BLACKLIST and PREFS_WHITELIST lists
// and also by the custom 'filter'-ing function.
function getPrefList(filter) {
  filter = filter || (name => true);
  function getPref(name) {
    let type = Services.prefs.getPrefType(name);
    if (!(type in PREFS_GETTERS))
      throw new Error("Unknown preference type " + type + " for " + name);
    return PREFS_GETTERS[type](Services.prefs, name);
  }

  return PREFS_WHITELIST.reduce(function(prefs, branch) {
    Services.prefs.getChildList(branch).forEach(function(name) {
      if (filter(name) && !PREFS_BLACKLIST.some(re => re.test(name)))
        prefs[name] = getPref(name);
    });
    return prefs;
  }, {});
}

this.Troubleshoot = {

  /**
   * Captures a snapshot of data that may help troubleshooters troubleshoot
   * trouble.
   *
   * @param done A function that will be asynchronously called when the
   *             snapshot completes.  It will be passed the snapshot object.
   */
  snapshot: function snapshot(done) {
    let snapshot = {};
    let numPending = Object.keys(dataProviders).length;
    function providerDone(providerName, providerData) {
      snapshot[providerName] = providerData;
      if (--numPending == 0)
        // Ensure that done is always and truly called asynchronously.
        Services.tm.dispatchToMainThread(done.bind(null, snapshot));
    }
    for (let name in dataProviders) {
      try {
        dataProviders[name](providerDone.bind(null, name));
      } catch (err) {
        let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
        Cu.reportError(msg);
        providerDone(name, msg);
      }
    }
  },

  kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
};

// Each data provider is a name => function mapping.  When a snapshot is
// captured, each provider's function is called, and it's the function's job to
// generate the provider's data.  The function is passed a "done" callback, and
// when done, it must pass its data to the callback.  The resulting snapshot
// object will contain a name => data entry for each provider.
var dataProviders = {

  application: function application(done) {

    let sysInfo = Cc["@mozilla.org/system-info;1"].
                  getService(Ci.nsIPropertyBag2);

    let data = {
      name: Services.appinfo.name,
      osVersion: sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"),
      version: AppConstants.MOZ_APP_VERSION_DISPLAY,
      buildID: Services.appinfo.appBuildID,
      userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].
                 getService(Ci.nsIHttpProtocolHandler).
                 userAgent,
      safeMode: Services.appinfo.inSafeMode,
    };

    if (AppConstants.MOZ_UPDATER)
      data.updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;

    // eslint-disable-next-line mozilla/use-default-preference-values
    try {
      data.vendor = Services.prefs.getCharPref("app.support.vendor");
    } catch (e) {}
    let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
                       getService(Ci.nsIURLFormatter);
    try {
      data.supportURL = urlFormatter.formatURLPref("app.support.baseURL");
    } catch (e) {}

    data.numTotalWindows = 0;
    data.numRemoteWindows = 0;
    let winEnumer = Services.wm.getEnumerator("navigator:browser");
    while (winEnumer.hasMoreElements()) {
      data.numTotalWindows++;
      let remote = winEnumer.getNext().
                   QueryInterface(Ci.nsIInterfaceRequestor).
                   getInterface(Ci.nsIWebNavigation).
                   QueryInterface(Ci.nsILoadContext).
                   useRemoteTabs;
      if (remote) {
        data.numRemoteWindows++;
      }
    }

    data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;

    // Services.ppmm.childCount is a count of how many processes currently
    // exist that might respond to messages sent through the ppmm, including
    // the parent process. So we subtract the parent process with the "- 1",
    // and that’s how many content processes we’re waiting for.
    data.currentContentProcesses = Services.ppmm.childCount - 1;
    data.maxContentProcesses = Services.appinfo.maxWebProcessCount;

    try {
      let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"]
                         .createInstance(Ci.nsISupportsPRUint64);
      let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
      appinfo.observe(e10sStatus, "getE10SBlocked", "");
      data.autoStartStatus = e10sStatus.data;
    } catch (e) {
      data.autoStartStatus = -1;
    }

    data.styloBuild = AppConstants.MOZ_STYLO;
    data.styloDefault = Services.prefs.getDefaultBranch(null)
                                .getBoolPref("layout.css.servo.enabled", false);
    data.styloResult =
      AppConstants.MOZ_STYLO &&
      (!!env.get("STYLO_FORCE_ENABLED") ||
       Services.prefs.getBoolPref("layout.css.servo.enabled", false));

    const keyGoogle = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
    data.keyGoogleFound = keyGoogle != "no-google-api-key" && keyGoogle.length > 0;

    const keyMozilla = Services.urlFormatter.formatURL("%MOZILLA_API_KEY%").trim();
    data.keyMozillaFound = keyMozilla != "no-mozilla-api-key" && keyMozilla.length > 0;

    done(data);
  },

  extensions: function extensions(done) {
    AddonManager.getAddonsByTypes(["extension"], function(extensions) {
      extensions = extensions.filter(e => !e.isSystem);
      extensions.sort(function(a, b) {
        if (a.isActive != b.isActive)
          return b.isActive ? 1 : -1;

        // In some unfortunate cases addon names can be null.
        let aname = a.name || null;
        let bname = b.name || null;
        let lc = aname.localeCompare(bname);
        if (lc != 0)
          return lc;
        if (a.version != b.version)
          return a.version > b.version ? 1 : -1;
        return 0;
      });
      let props = ["name", "version", "isActive", "id"];
      done(extensions.map(function(ext) {
        return props.reduce(function(extData, prop) {
          extData[prop] = ext[prop];
          return extData;
        }, {});
      }));
    });
  },

  features: function features(done) {
    AddonManager.getAddonsByTypes(["extension"], function(features) {
      features = features.filter(f => f.isSystem);
      features.sort(function(a, b) {
        // In some unfortunate cases addon names can be null.
        let aname = a.name || null;
        let bname = b.name || null;
        let lc = aname.localeCompare(bname);
        if (lc != 0)
          return lc;
        if (a.version != b.version)
          return a.version > b.version ? 1 : -1;
        return 0;
      });
      let props = ["name", "version", "id"];
      done(features.map(function(f) {
        return props.reduce(function(fData, prop) {
          fData[prop] = f[prop];
          return fData;
        }, {});
      }));
    });
  },

  experiments: function experiments(done) {
    if (Experiments === undefined) {
      done([]);
      return;
    }

    // getExperiments promises experiment history
    Experiments.instance().getExperiments().then(
      experiments => done(experiments)
    );
  },

  modifiedPreferences: function modifiedPreferences(done) {
    done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
  },

  lockedPreferences: function lockedPreferences(done) {
    done(getPrefList(name => Services.prefs.prefIsLocked(name)));
  },

  graphics: function graphics(done) {
    function statusMsgForFeature(feature) {
      // We return an array because in the tryNewerDriver case we need to
      // include the suggested version, which the consumer likely needs to plug
      // into a format string from a localization file.  Rather than returning
      // a string in some cases and an array in others, return an array always.
      let msg = [""];
      try {
        var status = gfxInfo.getFeatureStatus(feature);
      } catch (e) {}
      switch (status) {
      case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
      case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
        msg = ["blockedGfxCard"];
        break;
      case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
        msg = ["blockedOSVersion"];
        break;
      case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
        try {
          var suggestedDriverVersion =
            gfxInfo.getFeatureSuggestedDriverVersion(feature);
        } catch (e) {}
        msg = suggestedDriverVersion ?
              ["tryNewerDriver", suggestedDriverVersion] :
              ["blockedDriver"];
        break;
      case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
        msg = ["blockedMismatchedVersion"];
        break;
      }
      return msg;
    }

    let data = {};

    try {
      // nsIGfxInfo may not be implemented on some platforms.
      var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
    } catch (e) {}

    let promises = [];
    // done will be called upon all pending promises being resolved.
    // add your pending promise to promises when adding new ones.
    function completed() {
      Promise.all(promises).then(() => done(data));
    }

    data.numTotalWindows = 0;
    data.numAcceleratedWindows = 0;
    let winEnumer = Services.ww.getWindowEnumerator();
    while (winEnumer.hasMoreElements()) {
      let winUtils = winEnumer.getNext().
                     QueryInterface(Ci.nsIInterfaceRequestor).
                     getInterface(Ci.nsIDOMWindowUtils);
      try {
        // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
        if (winUtils.layerManagerType == "None" || !winUtils.layerManagerRemote) {
          continue;
        }
        data.numTotalWindows++;
        data.windowLayerManagerType = winUtils.layerManagerType;
        data.windowLayerManagerRemote = winUtils.layerManagerRemote;
        data.windowUsingAdvancedLayers = winUtils.usingAdvancedLayers;
      } catch (e) {
        continue;
      }
      if (data.windowLayerManagerType != "Basic")
        data.numAcceleratedWindows++;
    }

    // If we had no OMTC windows, report back Basic Layers.
    if (!data.windowLayerManagerType) {
      data.windowLayerManagerType = "Basic";
      data.windowLayerManagerRemote = false;
    }

    if (!data.numAcceleratedWindows && gfxInfo) {
      let win = AppConstants.platform == "win";
      let feature = win ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS :
                          gfxInfo.FEATURE_OPENGL_LAYERS;
      data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
    }

    if (!gfxInfo) {
      completed();
      return;
    }

    // keys are the names of attributes on nsIGfxInfo, values become the names
    // of the corresponding properties in our data object.  A null value means
    // no change.  This is needed so that the names of properties in the data
    // object are the same as the names of keys in aboutSupport.properties.
    let gfxInfoProps = {
      adapterDescription: null,
      adapterVendorID: null,
      adapterDeviceID: null,
      adapterSubsysID: null,
      adapterRAM: null,
      adapterDriver: "adapterDrivers",
      adapterDriverVersion: "driverVersion",
      adapterDriverDate: "driverDate",

      adapterDescription2: null,
      adapterVendorID2: null,
      adapterDeviceID2: null,
      adapterSubsysID2: null,
      adapterRAM2: null,
      adapterDriver2: "adapterDrivers2",
      adapterDriverVersion2: "driverVersion2",
      adapterDriverDate2: "driverDate2",
      isGPU2Active: null,

      D2DEnabled: "direct2DEnabled",
      DWriteEnabled: "directWriteEnabled",
      DWriteVersion: "directWriteVersion",
      cleartypeParameters: "clearTypeParameters",
    };

    for (let prop in gfxInfoProps) {
      try {
        data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
      } catch (e) {}
    }

    if (("direct2DEnabled" in data) && !data.direct2DEnabled)
      data.direct2DEnabledMessage =
        statusMsgForFeature(Ci.nsIGfxInfo.FEATURE_DIRECT2D);


    let doc =
      Cc["@mozilla.org/xmlextras/domparser;1"]
      .createInstance(Ci.nsIDOMParser)
      .parseFromString("<html/>", "text/html");

    function GetWebGLInfo(data, keyPrefix, contextType) {
        data[keyPrefix + "Renderer"] = "-";
        data[keyPrefix + "Version"] = "-";
        data[keyPrefix + "DriverExtensions"] = "-";
        data[keyPrefix + "Extensions"] = "-";
        data[keyPrefix + "WSIInfo"] = "-";

        // //

        let canvas = doc.createElement("canvas");
        canvas.width = 1;
        canvas.height = 1;

        // //

        let creationError = null;

        canvas.addEventListener(
            "webglcontextcreationerror",

            function(e) {
                creationError = e.statusMessage;
            }
        );

        let gl = null;
        try {
            gl = canvas.getContext(contextType);
        } catch (e) {
            if (!creationError) {
                creationError = e.toString();
            }
        }
        if (!gl) {
            data[keyPrefix + "Renderer"] = creationError || "(no creation error info)";
            return;
        }

        // //

        data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");

        // //

        let ext = gl.getExtension("MOZ_debug");
        // This extension is unconditionally available to chrome. No need to check.
        let vendor = ext.getParameter(gl.VENDOR);
        let renderer = ext.getParameter(gl.RENDERER);

        data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
        data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
        data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
        data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);

        // //

        // Eagerly free resources.
        let loseExt = gl.getExtension("WEBGL_lose_context");
        loseExt.loseContext();
    }

    GetWebGLInfo(data, "webgl1", "webgl");
    GetWebGLInfo(data, "webgl2", "webgl2");


    let infoInfo = gfxInfo.getInfo();
    if (infoInfo)
      data.info = infoInfo;

    let failureCount = {};
    let failureIndices = {};

    let failures = gfxInfo.getFailures(failureCount, failureIndices);
    if (failures.length) {
      data.failures = failures;
      if (failureIndices.value.length == failures.length) {
        data.indices = failureIndices.value;
      }
    }

    data.featureLog = gfxInfo.getFeatureLog();
    data.crashGuards = gfxInfo.getActiveCrashGuards();

    completed();
  },

  media: function media(done) {
    function convertDevices(devices) {
      if (!devices) {
        return undefined;
      }
      let infos = [];
      for (let i = 0; i < devices.length; ++i) {
        let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
        infos.push({
          name: device.name,
          groupId: device.groupId,
          vendor: device.vendor,
          type: device.type,
          state: device.state,
          preferred: device.preferred,
          supportedFormat: device.supportedFormat,
          defaultFormat: device.defaultFormat,
          maxChannels: device.maxChannels,
          defaultRate: device.defaultRate,
          maxRate: device.maxRate,
          minRate: device.minRate,
          maxLatency: device.maxLatency,
          minLatency: device.minLatency
        });
      }
      return infos;
    }

    let data = {};
    let winUtils = Services.wm.getMostRecentWindow("").
                   QueryInterface(Ci.nsIInterfaceRequestor).
                   getInterface(Ci.nsIDOMWindowUtils);
    data.currentAudioBackend = winUtils.currentAudioBackend;
    data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
    data.currentPreferredChannelLayout = winUtils.currentPreferredChannelLayout;
    data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
    data.audioOutputDevices = convertDevices(winUtils.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT).
                                             QueryInterface(Ci.nsIArray));
    data.audioInputDevices = convertDevices(winUtils.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT).
                                            QueryInterface(Ci.nsIArray));
    done(data);
  },

  javaScript: function javaScript(done) {
    let data = {};
    let winEnumer = Services.ww.getWindowEnumerator();
    if (winEnumer.hasMoreElements())
      data.incrementalGCEnabled = winEnumer.getNext().
                                  QueryInterface(Ci.nsIInterfaceRequestor).
                                  getInterface(Ci.nsIDOMWindowUtils).
                                  isIncrementalGCEnabled();
    done(data);
  },

  accessibility: function accessibility(done) {
    let data = {};
    data.isActive = Services.appinfo.accessibilityEnabled;
    // eslint-disable-next-line mozilla/use-default-preference-values
    try {
      data.forceDisabled =
        Services.prefs.getIntPref("accessibility.force_disabled");
    } catch (e) {}
    data.handlerUsed = Services.appinfo.accessibleHandlerUsed;
    done(data);
  },

  libraryVersions: function libraryVersions(done) {
    let data = {};
    let verInfo = Cc["@mozilla.org/security/nssversion;1"].
                  getService(Ci.nsINSSVersion);
    for (let prop in verInfo) {
      let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
      if (match) {
        let verProp = match[2][0].toLowerCase() + match[2].substr(1);
        data[match[1]] = data[match[1]] || {};
        data[match[1]][verProp] = verInfo[prop];
      }
    }
    done(data);
  },

  userJS: function userJS(done) {
    let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
    userJSFile.append("user.js");
    done({
      exists: userJSFile.exists() && userJSFile.fileSize > 0,
    });
  }
};

if (AppConstants.MOZ_CRASHREPORTER) {
  dataProviders.crashes = function crashes(done) {
    let CrashReports = Cu.import("resource://gre/modules/CrashReports.jsm").CrashReports;
    let reports = CrashReports.getReports();
    let now = new Date();
    let reportsNew = reports.filter(report => (now - report.date < Troubleshoot.kMaxCrashAge));
    let reportsSubmitted = reportsNew.filter(report => (!report.pending));
    let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
    let data = {submitted: reportsSubmitted, pending: reportsPendingCount};
    done(data);
  }
}

if (AppConstants.MOZ_SANDBOX) {
  dataProviders.sandbox = function sandbox(done) {
    let data = {};
    if (AppConstants.platform == "linux") {
      const keys = ["hasSeccompBPF", "hasSeccompTSync",
                    "hasPrivilegedUserNamespaces", "hasUserNamespaces",
                    "canSandboxContent", "canSandboxMedia"];

      let sysInfo = Cc["@mozilla.org/system-info;1"].
                    getService(Ci.nsIPropertyBag2);
      for (let key of keys) {
        if (sysInfo.hasKey(key)) {
          data[key] = sysInfo.getPropertyAsBool(key);
        }
      }

      let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].
                     getService(Ci.mozISandboxReporter);
      const snapshot = reporter.snapshot();
      let syscalls = [];
      for (let index = snapshot.begin; index < snapshot.end; ++index) {
        let report = snapshot.getElement(index);
        let { msecAgo, pid, tid, procType, syscall } = report;
        let args = []
        for (let i = 0; i < report.numArgs; ++i) {
          args.push(report.getArg(i));
        }
        syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
      }
      data.syscallLog = syscalls;
    }

    if (AppConstants.MOZ_CONTENT_SANDBOX) {
      let sandboxSettings = Cc["@mozilla.org/sandbox/sandbox-settings;1"].
                            getService(Ci.mozISandboxSettings);
      data.contentSandboxLevel =
        Services.prefs.getIntPref("security.sandbox.content.level");
      data.effectiveContentSandboxLevel =
        sandboxSettings.effectiveContentSandboxLevel;
    }

    done(data);
  }
}
PK
!<9•modules/UITelemetry.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [
  "UITelemetry",
];

Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);

/**
 * UITelemetry is a helper JSM used to record UI specific telemetry events.
 *
 * It implements nsIUITelemetryObserver, defined in nsIAndroidBridge.idl.
 */
this.UITelemetry = {
  _enabled: undefined,
  _activeSessions: {},
  _measurements: [],

  // Lazily decide whether telemetry is enabled.
  get enabled() {
    if (this._enabled !== undefined) {
      return this._enabled;
    }

    // Set an observer to watch for changes at runtime.
    Services.prefs.addObserver(TelemetryUtils.Preferences.TelemetryEnabled, this);
    Services.obs.addObserver(this, "profile-before-change");

    // Pick up the current value.
    this._enabled = Services.prefs.getBoolPref(TelemetryUtils.Preferences.TelemetryEnabled, false);

    return this._enabled;
  },

  observe(aSubject, aTopic, aData) {
    if (aTopic == "profile-before-change") {
      Services.obs.removeObserver(this, "profile-before-change");
      Services.prefs.removeObserver(TelemetryUtils.Preferences.TelemetryEnabled, this);
      this._enabled = undefined;
      return;
    }

    if (aTopic == "nsPref:changed") {
      switch (aData) {
        case TelemetryUtils.Preferences.TelemetryEnabled:
          let on = Services.prefs.getBoolPref(TelemetryUtils.Preferences.TelemetryEnabled);
          this._enabled = on;

          // Wipe ourselves if we were just disabled.
          if (!on) {
            this._activeSessions = {};
            this._measurements = [];
          }
          break;
      }
    }
  },

  /**
   * This exists exclusively for testing -- our events are not intended to
   * be retrieved via an XPCOM interface.
   */
  get wrappedJSObject() {
    return this;
  },

  /**
   * Holds the functions that provide UITelemetry's simple
   * measurements. Those functions are mapped to unique names,
   * and should be registered with addSimpleMeasureFunction.
   */
  _simpleMeasureFunctions: {},

  /**
   * A hack to generate the relative timestamp from start when we don't have
   * access to the Java timer.
   * XXX: Bug 1007647 - Support realtime and/or uptime in JavaScript.
   */
  uptimeMillis() {
    return Date.now() - Services.startup.getStartupInfo().process;
  },

  /**
   * Adds a single event described by a timestamp, an action, and the calling
   * method.
   *
   * Optionally provide a string 'extras', which will be recorded as part of
   * the event.
   *
   * All extant sessions will be recorded by name for each event.
   */
  addEvent(aAction, aMethod, aTimestamp, aExtras) {
    if (!this.enabled) {
      return;
    }

    let sessions = Object.keys(this._activeSessions);
    let aEvent = {
      type: "event",
      action: aAction,
      method: aMethod,
      sessions,
      timestamp: (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp,
    };

    if (aExtras) {
      aEvent.extras = aExtras;
    }

    this._recordEvent(aEvent);
  },

  /**
   * Begins tracking a session by storing a timestamp for session start.
   */
  startSession(aName, aTimestamp) {
    if (!this.enabled) {
      return;
    }

    if (this._activeSessions[aName]) {
      // Do not overwrite a previous event start if it already exists.
      return;
    }
    this._activeSessions[aName] = (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp;
  },

  /**
   * Tracks the end of a session with a timestamp.
   */
  stopSession(aName, aReason, aTimestamp) {
    if (!this.enabled) {
      return;
    }

    let sessionStart = this._activeSessions[aName];
    delete this._activeSessions[aName];

    if (!sessionStart) {
      return;
    }

    let aEvent = {
      type: "session",
      name: aName,
      reason: aReason,
      start: sessionStart,
      end: (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp,
    };

    this._recordEvent(aEvent);
  },

  _recordEvent(aEvent) {
    this._measurements.push(aEvent);
  },

  /**
   * Called by TelemetrySession to populate the simple measurement
   * blob. This function will iterate over all functions added
   * via addSimpleMeasureFunction and return an object with the
   * results of those functions.
   */
  getSimpleMeasures() {
    if (!this.enabled) {
      return {};
    }

    let result = {};
    for (let name in this._simpleMeasureFunctions) {
      result[name] = this._simpleMeasureFunctions[name]();
    }
    return result;
  },

  /**
   * Allows the caller to register functions that will get called
   * for simple measures during a Telemetry ping. aName is a unique
   * identifier used as they key for the simple measurement in the
   * object that getSimpleMeasures returns.
   *
   * This function throws an exception if aName already has a function
   * registered for it.
   */
  addSimpleMeasureFunction(aName, aFunction) {
    if (!this.enabled) {
      return;
    }

    if (aName in this._simpleMeasureFunctions) {
      throw new Error("A simple measurement function is already registered for " + aName);
    }

    if (!aFunction || typeof aFunction !== "function") {
      throw new Error("addSimpleMeasureFunction called with non-function argument.");
    }

    this._simpleMeasureFunctions[aName] = aFunction;
  },

  removeSimpleMeasureFunction(aName) {
    delete this._simpleMeasureFunctions[aName];
  },

  /**
   * Called by TelemetrySession to populate the UI measurement
   * blob.
   *
   * Optionally clears the set of measurements based on aClear.
   */
  getUIMeasurements(aClear) {
    if (!this.enabled) {
      return [];
    }

    let measurements = this._measurements.slice();
    if (aClear) {
      this._measurements = [];
    }
    return measurements;
  }
};
PK
!<qmodules/UpdateListener.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["UpdateListener"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AppMenuNotifications",
                                  "resource://gre/modules/AppMenuNotifications.jsm");

// Setup the hamburger button badges for updates, if enabled.
var UpdateListener = {
  timeouts: [],

  get enabled() {
    return Services.prefs.getBoolPref("app.update.doorhanger", false);
  },

  get badgeWaitTime() {
    return Services.prefs.getIntPref("app.update.badgeWaitTime", 4 * 24 * 3600); // 4 days
  },

  init() {
  },

  uninit() {
    this.reset();
  },

  reset() {
    AppMenuNotifications.removeNotification(/^update-/);
    this.clearCallbacks();
  },

  clearCallbacks() {
    this.timeouts.forEach(t => clearTimeout(t));
    this.timeouts = [];
  },

  addTimeout(time, callback) {
    this.timeouts.push(setTimeout(() => {
      this.clearCallbacks();
      callback();
    }, time));
  },

  replaceReleaseNotes(doc, update, whatsNewId) {
    let whatsNewLinkId = Services.prefs.getCharPref(`app.update.link.${whatsNewId}`, "");
    if (whatsNewLinkId) {
      let whatsNewLink = doc.getElementById(whatsNewLinkId);
      if (update && update.detailsURL) {
        whatsNewLink.href = update.detailsURL;
      } else {
        whatsNewLink.href = Services.urlFormatter.formatURLPref("app.update.url.details");
      }
    }
  },

  requestRestart() {
    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);
    }
  },

  openManualUpdateUrl(win) {
    let manualUpdateUrl = Services.urlFormatter.formatURLPref("app.update.url.manual");
    win.openURL(manualUpdateUrl);
  },

  showUpdateNotification(type, mainActionDismiss, dismissed, mainAction, beforeShowDoorhanger) {
    let action = {
      callback(win, fromDoorhanger) {
        if (fromDoorhanger) {
          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER").add(type);
        } else {
          Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_MENU").add(type);
        }
        mainAction(win);
      },
      dismiss: mainActionDismiss,
    };

    let secondaryAction = {
      callback() {
        Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_DISMISSED").add(type);
      },
      dismiss: true
    };

    AppMenuNotifications.showNotification("update-" + type,
                                          action,
                                          secondaryAction,
                                          { dismissed, beforeShowDoorhanger });
    if (dismissed) {
      Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_BADGE_SHOWN").add(type);
    } else {
      Services.telemetry.getHistogramById("UPDATE_NOTIFICATION_SHOWN").add(type);
    }
  },

  showRestartNotification(dismissed) {
    this.showUpdateNotification("restart", true, dismissed, () => this.requestRestart());
  },

  showUpdateAvailableNotification(update, dismissed) {
    this.showUpdateNotification("available", false, dismissed, () => {
      let updateService = Cc["@mozilla.org/updates/update-service;1"]
                          .getService(Ci.nsIApplicationUpdateService);
      updateService.downloadUpdate(update, true);
    }, doc => this.replaceReleaseNotes(doc, update, "updateAvailableWhatsNew"));
  },

  showManualUpdateNotification(update, dismissed) {
    this.showUpdateNotification("manual",
                                false,
                                dismissed,
                                win => this.openManualUpdateUrl(win),
                                doc => this.replaceReleaseNotes(doc, update, "updateManualWhatsNew"));
  },

  handleUpdateError(update, status) {
    switch (status) {
      case "download-attempt-failed":
        this.clearCallbacks();
        this.showUpdateAvailableNotification(update, false);
        break;
      case "download-attempts-exceeded":
        this.clearCallbacks();
        this.showManualUpdateNotification(update, false);
        break;
      case "elevation-attempt-failed":
        this.clearCallbacks();
        this.showRestartNotification(update, false);
        break;
      case "elevation-attempts-exceeded":
        this.clearCallbacks();
        this.showManualUpdateNotification(update, false);
        break;
      case "check-attempts-exceeded":
      case "unknown":
        // Background update has failed, let's show the UI responsible for
        // prompting the user to update manually.
        this.clearCallbacks();
        this.showManualUpdateNotification(update, false);
        break;
    }
  },

  handleUpdateStagedOrDownloaded(update, status) {
    switch (status) {
      case "applied":
      case "pending":
      case "applied-service":
      case "pending-service":
      case "pending-elevate":
      case "success":
        this.clearCallbacks();

        let badgeWaitTimeMs = this.badgeWaitTime * 1000;
        let doorhangerWaitTimeMs = update.promptWaitTime * 1000;

        if (badgeWaitTimeMs < doorhangerWaitTimeMs) {
          this.addTimeout(badgeWaitTimeMs, () => {
            this.showRestartNotification(true);

            // doorhangerWaitTimeMs is relative to when we initially received
            // the event. Since we've already waited badgeWaitTimeMs, subtract
            // that from doorhangerWaitTimeMs.
            let remainingTime = doorhangerWaitTimeMs - badgeWaitTimeMs;
            this.addTimeout(remainingTime, () => {
              this.showRestartNotification(false);
            });
          });
        } else {
          this.addTimeout(doorhangerWaitTimeMs, () => {
            this.showRestartNotification(false);
          });
        }
        break;
    }
  },

  handleUpdateAvailable(update, status) {
    switch (status) {
      case "show-prompt":
        // If an update is available and the app.update.auto preference is
        // false, then show an update available doorhanger.
        this.clearCallbacks();
        this.showUpdateAvailableNotification(update, false);
        break;
      case "cant-apply":
        this.clearCallbacks();
        this.showManualUpdateNotification(update, false);
        break;
    }
  },

  observe(subject, topic, status) {
    if (!this.enabled) {
      return;
    }

    let update = subject && subject.QueryInterface(Ci.nsIUpdate);

    switch (topic) {
      case "update-available":
        this.handleUpdateAvailable(update, status);
        break;
      case "update-staged":
      case "update-downloaded":
        this.handleUpdateStagedOrDownloaded(update, status);
        break;
      case "update-error":
        this.handleUpdateError(update, status);
        break;
    }
  }
};
PK
!<B	modules/UpdatePing.jsm/* -*- 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";

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Log.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                                  "resource://gre/modules/TelemetryController.jsm");

const LOGGER_NAME = "Toolkit.Telemetry";
const PING_TYPE = "update";
const UPDATE_DOWNLOADED_TOPIC = "update-downloaded";

this.EXPORTED_SYMBOLS = ["UpdatePing"];

/**
 * This module is responsible for listening to all the relevant update
 * signals, gathering the needed information and assembling the "update"
 * ping.
 */
this.UpdatePing = {
  earlyInit() {
    this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "UpdatePing::");
    this._enabled = Services.prefs.getBoolPref(TelemetryUtils.Preferences.UpdatePing, false);

    this._log.trace("init - enabled: " + this._enabled);

    if (!this._enabled) {
      return;
    }

    Services.obs.addObserver(this, UPDATE_DOWNLOADED_TOPIC);
  },

  /**
   * Generate an "update" ping with reason "ready" and dispatch it
   * to the Telemetry system.
   *
   * @param {String} aUpdateState The state of the downloaded patch. See
   *        nsIUpdateService.idl for a list of possible values.
   */
  _handleUpdateReady(aUpdateState) {
    const ALLOWED_STATES = [
      "applied", "applied-service", "pending", "pending-service", "pending-elevate"
    ];
    if (!ALLOWED_STATES.includes(aUpdateState)) {
      this._log.trace("Unexpected update state: " + aUpdateState);
      return;
    }

    // Get the information about the update we're going to apply from the
    // update manager.
    let updateManager =
      Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
    if (!updateManager || !updateManager.activeUpdate) {
      this._log.trace("Cannot get the update manager or no update is currently active.");
      return;
    }

    let update = updateManager.activeUpdate;

    const payload = {
      reason: "ready",
      targetChannel: update.channel,
      targetVersion: update.appVersion,
      targetBuildId: update.buildID,
    };

    const options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: true,
    };

    TelemetryController.submitExternalPing(PING_TYPE, payload, options)
                       .catch(e => this._log.error("_handleUpdateReady - failed to submit update ping", e));
  },

  /**
   * The notifications handler.
   */
  observe(aSubject, aTopic, aData) {
    this._log.trace("observe - aTopic: " + aTopic);
    if (aTopic == UPDATE_DOWNLOADED_TOPIC) {
      this._handleUpdateReady(aData);
    }
  },

  shutdown() {
    if (!this._enabled) {
      return;
    }
    Services.obs.removeObserver(this, UPDATE_DOWNLOADED_TOPIC);
  },
};
PK
!<hNԜFFmodules/UpdateTelemetry.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [
  "AUSTLMY"
];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm", this);

this.AUSTLMY = {
  // Telemetry for the application update background update check occurs when
  // the background update timer fires after the update interval which is
  // determined by the app.update.interval preference and its telemetry
  // histogram IDs have the suffix '_NOTIFY'.
  // Telemetry for the externally initiated background update check occurs when
  // a call is made to |checkForBackgroundUpdates| which is typically initiated
  // by an application when it has determined that the application should have
  // received an update. This has separate telemetry so it is possible to
  // analyze using the telemetry data systems that have not been updating when
  // they should have.

  // The update check was performed by the call to checkForBackgroundUpdates in
  // nsUpdateService.js.
  EXTERNAL: "EXTERNAL",
  // The update check was performed by the call to notify in nsUpdateService.js.
  NOTIFY: "NOTIFY",

  /**
   * Values for the UPDATE_CHECK_CODE_NOTIFY and UPDATE_CHECK_CODE_EXTERNAL
   * Telemetry histograms.
   */
  // No update found (no notification)
  CHK_NO_UPDATE_FOUND: 0,
  // Update will be downloaded in the background (background download)
  CHK_DOWNLOAD_UPDATE: 1,
  // Showing prompt due to preference (update notification)
  CHK_SHOWPROMPT_PREF: 3,
  // Already has an active update in progress (no notification)
  CHK_HAS_ACTIVEUPDATE: 8,
  // A background download is already in progress (no notification)
  CHK_IS_DOWNLOADING: 9,
  // An update is already staged (no notification)
  CHK_IS_STAGED: 10,
  // An update is already downloaded (no notification)
  CHK_IS_DOWNLOADED: 11,
  // Background checks disabled by preference (no notification)
  CHK_PREF_DISABLED: 12,
  // Update checks disabled by admin locked preference (no notification)
  CHK_ADMIN_DISABLED: 13,
  // Unable to check for updates per hasUpdateMutex() (no notification)
  CHK_NO_MUTEX: 14,
  // Unable to check for updates per gCanCheckForUpdates (no notification). This
  // should be covered by other codes and is recorded just in case.
  CHK_UNABLE_TO_CHECK: 15,
  // Background checks disabled for the current session (no notification)
  CHK_DISABLED_FOR_SESSION: 16,
  // Unable to perform a background check while offline (no notification)
  CHK_OFFLINE: 17,
  // Note: codes 18 - 21 were removed along with the certificate checking code.
  // General update check failure and threshold reached
  // (check failure notification)
  CHK_GENERAL_ERROR_PROMPT: 22,
  // General update check failure and threshold not reached (no notification)
  CHK_GENERAL_ERROR_SILENT: 23,
  // No compatible update found though there were updates (no notification)
  CHK_NO_COMPAT_UPDATE_FOUND: 24,
  // Update found for a previous version (no notification)
  CHK_UPDATE_PREVIOUS_VERSION: 25,
  // Update found without a type attribute (no notification)
  CHK_UPDATE_INVALID_TYPE: 27,
  // The system is no longer supported (system unsupported notification)
  CHK_UNSUPPORTED: 28,
  // Unable to apply updates (manual install to update notification)
  CHK_UNABLE_TO_APPLY: 29,
  // Unable to check for updates due to no OS version (no notification)
  CHK_NO_OS_VERSION: 30,
  // Unable to check for updates due to no OS ABI (no notification)
  CHK_NO_OS_ABI: 31,
  // Invalid url for app.update.url default preference (no notification)
  CHK_INVALID_DEFAULT_URL: 32,
  // Update elevation failures or cancelations threshold reached for this
  // version, OSX only (no notification)
  CHK_ELEVATION_DISABLED_FOR_VERSION: 35,
  // User opted out of elevated updates for the available update version, OSX
  // only (no notification)
  CHK_ELEVATION_OPTOUT_FOR_VERSION: 36,

  /**
   * Submit a telemetry ping for the update check result code or a telemetry
   * ping for a count type histogram count when no update was found. The no
   * update found ping is separate since it is the typical result, is less
   * interesting than the other result codes, and it is easier to analyze the
   * other codes without including it.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_CHECK_CODE_EXTERNAL
   *         UPDATE_CHECK_CODE_NOTIFY
   *         UPDATE_CHECK_NO_UPDATE_EXTERNAL
   *         UPDATE_CHECK_NO_UPDATE_NOTIFY
   * @param  aCode
   *         An integer value as defined by the values that start with CHK_ in
   *         the above section.
   */
  pingCheckCode: function UT_pingCheckCode(aSuffix, aCode) {
    try {
      if (aCode == this.CHK_NO_UPDATE_FOUND) {
        let id = "UPDATE_CHECK_NO_UPDATE_" + aSuffix;
        // count type histogram
        Services.telemetry.getHistogramById(id).add();
      } else {
        let id = "UPDATE_CHECK_CODE_" + aSuffix;
        // enumerated type histogram
        Services.telemetry.getHistogramById(id).add(aCode);
      }
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for a failed update check's unhandled error code
   * when the pingCheckCode is CHK_GENERAL_ERROR_SILENT. The histogram is a
   * keyed count type with key names that are prefixed with 'AUS_CHECK_EX_ERR_'.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_CHK_EXTENDED_ERROR_EXTERNAL
   *         UPDATE_CHK_EXTENDED_ERROR_NOTIFY
   * @param  aCode
   *         The extended error value return by a failed update check.
   */
  pingCheckExError: function UT_pingCheckExError(aSuffix, aCode) {
    try {
      let id = "UPDATE_CHECK_EXTENDED_ERROR_" + aSuffix;
      let val = "AUS_CHECK_EX_ERR_" + aCode;
      // keyed count type histogram
      Services.telemetry.getKeyedHistogramById(id).add(val);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  // The state code and if present the status error code were read on startup.
  STARTUP: "STARTUP",
  // The state code and status error code if present were read after staging.
  STAGE: "STAGE",

  // Patch type Complete
  PATCH_COMPLETE: "COMPLETE",
  // Patch type partial
  PATCH_PARTIAL: "PARTIAL",
  // Patch type unknown
  PATCH_UNKNOWN: "UNKNOWN",

  /**
   * Values for the UPDATE_DOWNLOAD_CODE_COMPLETE and
   * UPDATE_DOWNLOAD_CODE_PARTIAL Telemetry histograms.
   */
  DWNLD_SUCCESS: 0,
  DWNLD_RETRY_OFFLINE: 1,
  DWNLD_RETRY_NET_TIMEOUT: 2,
  DWNLD_RETRY_CONNECTION_REFUSED: 3,
  DWNLD_RETRY_NET_RESET: 4,
  DWNLD_ERR_NO_UPDATE: 5,
  DWNLD_ERR_NO_UPDATE_PATCH: 6,
  DWNLD_ERR_PATCH_SIZE_LARGER: 8,
  DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9,
  DWNLD_ERR_BINDING_ABORTED: 10,
  DWNLD_ERR_ABORT: 11,
  DWNLD_ERR_DOCUMENT_NOT_CACHED: 12,
  DWNLD_ERR_VERIFY_NO_REQUEST: 13,
  DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,

  /**
   * Submit a telemetry ping for the update download result code.
   *
   * @param  aIsComplete
   *         If true the histogram is for a patch type complete, if false the
   *         histogram is for a patch type partial, and when undefined the
   *         histogram is for an unknown patch type. This is used to determine
   *         the histogram ID out of the following histogram IDs:
   *         UPDATE_DOWNLOAD_CODE_COMPLETE
   *         UPDATE_DOWNLOAD_CODE_PARTIAL
   * @param  aCode
   *         An integer value as defined by the values that start with DWNLD_ in
   *         the above section.
   */
  pingDownloadCode: function UT_pingDownloadCode(aIsComplete, aCode) {
    let patchType = this.PATCH_UNKNOWN;
    if (aIsComplete === true) {
      patchType = this.PATCH_COMPLETE;
    } else if (aIsComplete === false) {
      patchType = this.PATCH_PARTIAL;
    }
    try {
      let id = "UPDATE_DOWNLOAD_CODE_" + patchType;
      // enumerated type histogram
      Services.telemetry.getHistogramById(id).add(aCode);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for the update status state code.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_STATE_CODE_COMPLETE_STARTUP
   *         UPDATE_STATE_CODE_PARTIAL_STARTUP
   *         UPDATE_STATE_CODE_UNKNOWN_STARTUP
   *         UPDATE_STATE_CODE_COMPLETE_STAGE
   *         UPDATE_STATE_CODE_PARTIAL_STAGE
   *         UPDATE_STATE_CODE_UNKNOWN_STAGE
   * @param  aCode
   *         An integer value as defined by the values that start with STATE_ in
   *         the above section for the update state from the update.status file.
   */
  pingStateCode: function UT_pingStateCode(aSuffix, aCode) {
    try {
      let id = "UPDATE_STATE_CODE_" + aSuffix;
      // enumerated type histogram
      Services.telemetry.getHistogramById(id).add(aCode);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for the update status error code. This does not
   * submit a success value which can be determined from the state code.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP
   *         UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP
   *         UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP
   *         UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE
   *         UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE
   *         UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE
   * @param  aCode
   *         An integer value for the error code from the update.status file.
   */
  pingStatusErrorCode: function UT_pingStatusErrorCode(aSuffix, aCode) {
    try {
      let id = "UPDATE_STATUS_ERROR_CODE_" + aSuffix;
      // enumerated type histogram
      Services.telemetry.getHistogramById(id).add(aCode);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit the interval in days since the last notification for this background
   * update check or a boolean if the last notification is in the future.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_INVALID_LASTUPDATETIME_EXTERNAL
   *         UPDATE_INVALID_LASTUPDATETIME_NOTIFY
   *         UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL
   *         UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY
   */
  pingLastUpdateTime: function UT_pingLastUpdateTime(aSuffix) {
    const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
      let lastUpdateTimeSeconds = Services.prefs.getIntPref(PREF_APP_UPDATE_LASTUPDATETIME);
      if (lastUpdateTimeSeconds) {
        let currentTimeSeconds = Math.round(Date.now() / 1000);
        if (lastUpdateTimeSeconds > currentTimeSeconds) {
          try {
            let id = "UPDATE_INVALID_LASTUPDATETIME_" + aSuffix;
            // count type histogram
            Services.telemetry.getHistogramById(id).add();
          } catch (e) {
            Cu.reportError(e);
          }
        } else {
          let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
                             (60 * 60 * 24);
          try {
            let id = "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_" + aSuffix;
            // exponential type histogram
            Services.telemetry.getHistogramById(id).add(intervalDays);
          } catch (e) {
            Cu.reportError(e);
          }
        }
      }
    }
  },

  /**
   * Submit a telemetry ping for the last page displayed by the update wizard.
   *
   * @param  aPageID
   *         The page id for the last page displayed.
   */
  pingWizLastPageCode: function UT_pingWizLastPageCode(aPageID) {
    let pageMap = { invalid: 0,
                    dummy: 1,
                    checking: 2,
                    pluginupdatesfound: 3,
                    noupdatesfound: 4,
                    manualUpdate: 5,
                    unsupported: 6,
                    updatesfoundbasic: 8,
                    updatesfoundbillboard: 9,
                    downloading: 12,
                    errors: 13,
                    errorextra: 14,
                    errorpatching: 15,
                    finished: 16,
                    finishedBackground: 17,
                    installed: 18 };
    try {
      let id = "UPDATE_WIZ_LAST_PAGE_CODE";
      // enumerated type histogram
      Services.telemetry.getHistogramById(id).add(pageMap[aPageID] ||
                                                  pageMap.invalid);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for a boolean type histogram that indicates if the
   * service is installed and a telemetry ping for a boolean type histogram that
   * indicates if the service was at some point installed and is now
   * uninstalled.
   *
   * @param  aSuffix
   *         The histogram id suffix for histogram IDs:
   *         UPDATE_SERVICE_INSTALLED_EXTERNAL
   *         UPDATE_SERVICE_INSTALLED_NOTIFY
   *         UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL
   *         UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY
   * @param  aInstalled
   *         Whether the service is installed.
   */
  pingServiceInstallStatus: function UT_PSIS(aSuffix, aInstalled) {
    // Report the error but don't throw since it is more important to
    // successfully update than to throw.
    if (!("@mozilla.org/windows-registry-key;1" in Cc)) {
      Cu.reportError(Cr.NS_ERROR_NOT_AVAILABLE);
      return;
    }

    try {
      let id = "UPDATE_SERVICE_INSTALLED_" + aSuffix;
      // boolean type histogram
      Services.telemetry.getHistogramById(id).add(aInstalled);
    } catch (e) {
      Cu.reportError(e);
    }

    let attempted = 0;
    try {
      let wrk = Cc["@mozilla.org/windows-registry-key;1"].
                createInstance(Ci.nsIWindowsRegKey);
      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
               "SOFTWARE\\Mozilla\\MaintenanceService",
               wrk.ACCESS_READ | wrk.WOW64_64);
      // Was the service at some point installed, but is now uninstalled?
      attempted = wrk.readIntValue("Attempted");
      wrk.close();
    } catch (e) {
      // Since this will throw if the registry key doesn't exist (e.g. the
      // service has never been installed) don't report an error.
    }

    try {
      let id = "UPDATE_SERVICE_MANUALLY_UNINSTALLED_" + aSuffix;
      if (!aInstalled && attempted) {
        // count type histogram
        Services.telemetry.getHistogramById(id).add();
      }
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for a count type histogram when the expected value
   * does not equal the boolean value of a pref or if the pref isn't present
   * when the expected value does not equal default value. This lessens the
   * amount of data submitted to telemetry.
   *
   * @param  aID
   *         The histogram ID to report to.
   * @param  aPref
   *         The preference to check.
   * @param  aDefault
   *         The default value when the preference isn't present.
   * @param  aExpected (optional)
   *         If specified and the value is the same as the value that will be
   *         added the value won't be added to telemetry.
   */
  pingBoolPref: function UT_pingBoolPref(aID, aPref, aDefault, aExpected) {
    try {
      let val = aDefault;
      if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) {
        val = Services.prefs.getBoolPref(aPref);
      }
      if (val != aExpected) {
        // count type histogram
        Services.telemetry.getHistogramById(aID).add();
      }
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for a histogram with the integer value of a
   * preference when it is not the expected value or the default value when it
   * is not the expected value. This lessens the amount of data submitted to
   * telemetry.
   *
   * @param  aID
   *         The histogram ID to report to.
   * @param  aPref
   *         The preference to check.
   * @param  aDefault
   *         The default value when the pref is not set.
   * @param  aExpected (optional)
   *         If specified and the value is the same as the value that will be
   *         added the value won't be added to telemetry.
   */
  pingIntPref: function UT_pingIntPref(aID, aPref, aDefault, aExpected) {
    try {
      let val = aDefault;
      if (Services.prefs.getPrefType(aPref) != Ci.nsIPrefBranch.PREF_INVALID) {
        val = Services.prefs.getIntPref(aPref);
      }
      if (aExpected === undefined || val != aExpected) {
        // enumerated or exponential type histogram
        Services.telemetry.getHistogramById(aID).add(val);
      }
    } catch (e) {
      Cu.reportError(e);
    }
  },

  /**
   * Submit a telemetry ping for all histogram types that take a single
   * parameter to the telemetry add function and the count type histogram when
   * the aExpected parameter is specified. If the aExpected parameter is
   * specified and it equals the value specified by the aValue
   * parameter the telemetry submission will be skipped.
   *
   * @param  aID
   *         The histogram ID to report to.
   * @param  aValue
   *         The value to add when aExpected is not defined or the value to
   *         check if it is equal to when aExpected is defined.
   * @param  aExpected (optional)
   *         If specified and the value is the same as the value specified by
   *         aValue parameter the submission will be skipped.
   */
  pingGeneric: function UT_pingGeneric(aID, aValue, aExpected) {
    try {
      if (aExpected === undefined) {
        Services.telemetry.getHistogramById(aID).add(aValue);
      } else if (aValue != aExpected) {
        // count type histogram
        Services.telemetry.getHistogramById(aID).add();
      }
    } catch (e) {
      Cu.reportError(e);
    }
  }
};
Object.freeze(AUSTLMY);
PK
!<iкL0L0modules/UpdateUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["UpdateUtils"];

const { classes: Cc, interfaces: Ci, utils: Cu } = 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/ctypes.jsm");
Cu.importGlobalProperties(["fetch"]); /* globals fetch */

const FILE_UPDATE_LOCALE                  = "update.locale";
const PREF_APP_DISTRIBUTION               = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";


this.UpdateUtils = {
  _locale: undefined,

  /**
   * Read the update channel from defaults only.  We do this to ensure that
   * the channel is tightly coupled with the application and does not apply
   * to other instances of the application that may use the same profile.
   *
   * @param [optional] aIncludePartners
   *        Whether or not to include the partner bits. Default: true.
   */
  getUpdateChannel(aIncludePartners = true) {
    let defaults = Services.prefs.getDefaultBranch(null);
    let channel = defaults.getCharPref("app.update.channel",
                                       AppConstants.MOZ_UPDATE_CHANNEL);

    if (aIncludePartners) {
      try {
        let partners = Services.prefs.getChildList("app.partner.").sort();
        if (partners.length) {
          channel += "-cck";
          partners.forEach(function(prefName) {
            channel += "-" + Services.prefs.getCharPref(prefName);
          });
        }
      } catch (e) {
        Cu.reportError(e);
      }
    }

    return channel;
  },

  get UpdateChannel() {
    return this.getUpdateChannel();
  },

  /**
   * Formats a URL by replacing %...% values with OS, build and locale specific
   * values.
   *
   * @param  url
   *         The URL to format.
   * @return The formatted URL.
   */
  async formatUpdateURL(url) {
    const locale = await this.getLocale();

    return url.replace(/%(\w+)%/g, (match, name) => {
      switch (name) {
        case "PRODUCT":
          return Services.appinfo.name;
        case "VERSION":
          return Services.appinfo.version;
        case "BUILD_ID":
          return Services.appinfo.appBuildID;
        case "BUILD_TARGET":
          return Services.appinfo.OS + "_" + this.ABI;
        case "OS_VERSION":
          return this.OSVersion;
        case "LOCALE":
          return locale;
        case "CHANNEL":
          return this.UpdateChannel;
        case "PLATFORM_VERSION":
          return Services.appinfo.platformVersion;
        case "SYSTEM_CAPABILITIES":
          return getSystemCapabilities();
        case "CUSTOM":
          return Services.prefs.getStringPref(PREF_APP_UPDATE_CUSTOM, "");
        case "DISTRIBUTION":
          return getDistributionPrefValue(PREF_APP_DISTRIBUTION);
        case "DISTRIBUTION_VERSION":
          return getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION);
      }
      return match;
    }).replace(/\+/g, "%2B");
  },

  /**
   * Gets the locale from the update.locale file for replacing %LOCALE% in the
   * update url. The update.locale file can be located in the application
   * directory or the GRE directory with preference given to it being located in
   * the application directory.
   */
  async getLocale() {
    if (this._locale !== undefined) {
      return this._locale;
    }

    for (let res of ["app", "gre"]) {
      const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE;
      let data;
      try {
        data = await fetch(url);
      } catch (e) {
        continue;
      }
      const locale = await data.text();
      if (locale) {
        return this._locale = locale.trim();
      }
    }

    Cu.reportError(FILE_UPDATE_LOCALE + " file doesn't exist in either the " +
                   "application or GRE directories");

    return this._locale = null;
  }
};

/* Get the distribution pref values, from defaults only */
function getDistributionPrefValue(aPrefName) {
  return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default");
}

function getSystemCapabilities() {
  return "ISET:" + gInstructionSet + ",MEM:" + getMemoryMB() + getJAWS();
}

/**
 * Gets the appropriate update url string for whether a JAWS screen reader that
 * is incompatible with e10s is present on Windows. For platforms other than
 * Windows this returns an empty string which is easier for balrog to detect.
 */
function getJAWS() {
  if (AppConstants.platform != "win") {
    return "";
  }

  return ",JAWS:" + (Services.appinfo.shouldBlockIncompatJaws ? "1" : "0");
}

/**
 * Gets the RAM size in megabytes. This will round the value because sysinfo
 * doesn't always provide RAM in multiples of 1024.
 */
function getMemoryMB() {
  let memoryMB = "unknown";
  try {
    memoryMB = Services.sysinfo.getProperty("memsize");
    if (memoryMB) {
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }
  } catch (e) {
    Cu.reportError("Error getting system info memsize property. " +
                   "Exception: " + e);
  }
  return memoryMB;
}

/**
 * Gets the supported CPU instruction set.
 */
XPCOMUtils.defineLazyGetter(this, "gInstructionSet", function aus_gIS() {
  if (AppConstants.platform == "win") {
    const PF_MMX_INSTRUCTIONS_AVAILABLE = 3; // MMX
    const PF_XMMI_INSTRUCTIONS_AVAILABLE = 6; // SSE
    const PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10; // SSE2
    const PF_SSE3_INSTRUCTIONS_AVAILABLE = 13; // SSE3

    let lib = ctypes.open("kernel32.dll");
    let IsProcessorFeaturePresent = lib.declare("IsProcessorFeaturePresent",
                                                ctypes.winapi_abi,
                                                ctypes.int32_t, /* success */
                                                ctypes.uint32_t); /* DWORD */
    let instructionSet = "unknown";
    try {
      if (IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) {
        instructionSet = "SSE3";
      } else if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) {
        instructionSet = "SSE2";
      } else if (IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
        instructionSet = "SSE";
      } else if (IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE)) {
        instructionSet = "MMX";
      }
    } catch (e) {
      instructionSet = "error";
      Cu.reportError("Error getting processor instruction set. " +
                     "Exception: " + e);
    }

    lib.close();
    return instructionSet;
  }

  if (AppConstants == "linux") {
    let instructionSet = "unknown";
    if (navigator.cpuHasSSE2) {
      instructionSet = "SSE2";
    }
    return instructionSet;
  }

  return "NA"
});

/* Windows only getter that returns the processor architecture. */
XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
  // Get processor architecture
  let arch = "unknown";

  const WORD = ctypes.uint16_t;
  const DWORD = ctypes.uint32_t;

  // This structure is described at:
  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
  const SYSTEM_INFO = new ctypes.StructType("SYSTEM_INFO",
      [
      {wProcessorArchitecture: WORD},
      {wReserved: WORD},
      {dwPageSize: DWORD},
      {lpMinimumApplicationAddress: ctypes.voidptr_t},
      {lpMaximumApplicationAddress: ctypes.voidptr_t},
      {dwActiveProcessorMask: DWORD.ptr},
      {dwNumberOfProcessors: DWORD},
      {dwProcessorType: DWORD},
      {dwAllocationGranularity: DWORD},
      {wProcessorLevel: WORD},
      {wProcessorRevision: WORD}
      ]);

  let kernel32 = false;
  try {
    kernel32 = ctypes.open("Kernel32");
  } catch (e) {
    Cu.reportError("Unable to open kernel32! Exception: " + e);
  }

  if (kernel32) {
    try {
      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
                                                 ctypes.winapi_abi,
                                                 ctypes.void_t,
                                                 SYSTEM_INFO.ptr);
      let winSystemInfo = SYSTEM_INFO();
      // Default to unknown
      winSystemInfo.wProcessorArchitecture = 0xffff;

      GetNativeSystemInfo(winSystemInfo.address());
      switch (winSystemInfo.wProcessorArchitecture) {
        case 9:
          arch = "x64";
          break;
        case 6:
          arch = "IA64";
          break;
        case 0:
          arch = "x86";
          break;
      }
    } catch (e) {
      Cu.reportError("Error getting processor architecture. " +
                     "Exception: " + e);
    } finally {
      kernel32.close();
    }
  }

  return arch;
});

XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() {
  let abi = null;
  try {
    abi = Services.appinfo.XPCOMABI;
  } catch (e) {
    Cu.reportError("XPCOM ABI unknown");
  }

  if (AppConstants.platform == "macosx") {
    // Mac universal build should report a different ABI than either macppc
    // or mactel.
    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
                   getService(Ci.nsIMacUtils);

    if (macutils.isUniversalBinary) {
      abi += "-u-" + macutils.architecturesInBinary;
    }
  } else if (AppConstants.platform == "win") {
    // Windows build should report the CPU architecture that it's running on.
    abi += "-" + gWinCPUArch;
  }
  return abi;
});

XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function() {
  let osVersion;
  try {
    osVersion = Services.sysinfo.getProperty("name") + " " +
                Services.sysinfo.getProperty("version");
  } catch (e) {
    Cu.reportError("OS Version unknown.");
  }

  if (osVersion) {
    if (AppConstants.platform == "win") {
      const BYTE = ctypes.uint8_t;
      const WORD = ctypes.uint16_t;
      const DWORD = ctypes.uint32_t;
      const WCHAR = ctypes.char16_t;
      const BOOL = ctypes.int;

      // This structure is described at:
      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
      const SZCSDVERSIONLENGTH = 128;
      const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW",
          [
          {dwOSVersionInfoSize: DWORD},
          {dwMajorVersion: DWORD},
          {dwMinorVersion: DWORD},
          {dwBuildNumber: DWORD},
          {dwPlatformId: DWORD},
          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
          {wServicePackMajor: WORD},
          {wServicePackMinor: WORD},
          {wSuiteMask: WORD},
          {wProductType: BYTE},
          {wReserved: BYTE}
          ]);

      let kernel32 = false;
      try {
        kernel32 = ctypes.open("Kernel32");
      } catch (e) {
        Cu.reportError("Unable to open kernel32! " + e);
        osVersion += ".unknown (unknown)";
      }

      if (kernel32) {
        try {
          // Get Service pack info
          try {
            let GetVersionEx = kernel32.declare("GetVersionExW",
                                                ctypes.winapi_abi,
                                                BOOL,
                                                OSVERSIONINFOEXW.ptr);
            let winVer = OSVERSIONINFOEXW();
            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;

            if (0 !== GetVersionEx(winVer.address())) {
              osVersion += "." + winVer.wServicePackMajor +
                           "." + winVer.wServicePackMinor;
            } else {
              Cu.reportError("Unknown failure in GetVersionEX (returned 0)");
              osVersion += ".unknown";
            }
          } catch (e) {
            Cu.reportError("Error getting service pack information. Exception: " + e);
            osVersion += ".unknown";
          }
        } finally {
          kernel32.close();
        }

        // Add processor architecture
        osVersion += " (" + gWinCPUArch + ")";
      }
    }

    try {
      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
    } catch (e) {
      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
    }
    osVersion = encodeURIComponent(osVersion);
  }
  return osVersion;
});
PK
!<	{@modules/UserAgentOverrides.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [ "UserAgentOverrides" ];

const Ci = Components.interfaces;
const Cc = Components.classes;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/UserAgentUpdates.jsm");

const PREF_OVERRIDES_ENABLED = "general.useragent.site_specific_overrides";
const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250;

// lazy load nsHttpHandler to improve startup performance.
XPCOMUtils.defineLazyGetter(this, "DEFAULT_UA", function() {
  return Cc["@mozilla.org/network/protocol;1?name=http"]
           .getService(Ci.nsIHttpProtocolHandler)
           .userAgent;
});

var gPrefBranch;
var gOverrides = new Map;
var gUpdatedOverrides;
var gOverrideForHostCache = new Map;
var gInitialized = false;
var gOverrideFunctions = [
  function (aHttpChannel) { return UserAgentOverrides.getOverrideForURI(aHttpChannel.URI); }
];
var gBuiltUAs = new Map;

this.UserAgentOverrides = {
  init: function uao_init() {
    if (gInitialized)
      return;

    gPrefBranch = Services.prefs.getBranch("general.useragent.override.");
    gPrefBranch.addObserver("", buildOverrides);

    Services.prefs.addObserver(PREF_OVERRIDES_ENABLED, buildOverrides);

    try {
      Services.obs.addObserver(HTTP_on_useragent_request, "http-on-useragent-request");
    } catch (x) {
      // The http-on-useragent-request notification is disallowed in content processes.
    }

    try {
      UserAgentUpdates.init(function(overrides) {
        gOverrideForHostCache.clear();
        if (overrides) {
          for (let domain in overrides) {
            overrides[domain] = getUserAgentFromOverride(overrides[domain]);
          }
          overrides.get = function(key) { return this[key]; };
        }
        gUpdatedOverrides = overrides;
      });

      buildOverrides();
    } catch (e) {
      // UserAgentOverrides is initialized before profile is ready.
      // UA override might not work correctly.
    }

    Services.obs.notifyObservers(null, "useragentoverrides-initialized");
    gInitialized = true;
  },

  addComplexOverride: function uao_addComplexOverride(callback) {
    // Add to front of array so complex overrides have precedence
    gOverrideFunctions.unshift(callback);
  },

  getOverrideForURI: function uao_getOverrideForURI(aURI) {
    let host = aURI.asciiHost;
    if (!gInitialized ||
        (!gOverrides.size && !gUpdatedOverrides) ||
        !(host)) {
      return null;
    }

    let override = gOverrideForHostCache.get(host);
    if (override !== undefined)
      return override;

    function findOverride(overrides) {
      let searchHost = host;
      let userAgent = overrides.get(searchHost);

      while (!userAgent) {
        let dot = searchHost.indexOf('.');
        if (dot === -1) {
          return null;
        }
        searchHost = searchHost.slice(dot + 1);
        userAgent = overrides.get(searchHost);
      }
      return userAgent;
    }

    override = (gOverrides.size && findOverride(gOverrides))
            || (gUpdatedOverrides && findOverride(gUpdatedOverrides));

    if (gOverrideForHostCache.size >= MAX_OVERRIDE_FOR_HOST_CACHE_SIZE) {
      gOverrideForHostCache.clear();
    }
    gOverrideForHostCache.set(host, override);

    return override;
  },

  uninit: function uao_uninit() {
    if (!gInitialized)
      return;
    gInitialized = false;

    gPrefBranch.removeObserver("", buildOverrides);

    Services.prefs.removeObserver(PREF_OVERRIDES_ENABLED, buildOverrides);

    Services.obs.removeObserver(HTTP_on_useragent_request, "http-on-useragent-request");
  }
};

function getUserAgentFromOverride(override)
{
  let userAgent = gBuiltUAs.get(override);
  if (userAgent !== undefined) {
    return userAgent;
  }
  let [search, replace] = override.split("#", 2);
  if (search && replace) {
    userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace);
  } else {
    userAgent = override;
  }
  gBuiltUAs.set(override, userAgent);
  return userAgent;
}

function buildOverrides() {
  gOverrides.clear();
  gOverrideForHostCache.clear();

  if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED))
    return;

  let builtUAs = new Map;
  let domains = gPrefBranch.getChildList("");

  for (let domain of domains) {
    let override = gPrefBranch.getCharPref(domain);
    let userAgent = getUserAgentFromOverride(override);

    if (userAgent != DEFAULT_UA) {
      gOverrides.set(domain, userAgent);
    }
  }
}

function HTTP_on_useragent_request(aSubject, aTopic, aData) {
  let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);

  for (let callback of gOverrideFunctions) {
    let modifiedUA = callback(channel, DEFAULT_UA);
    if (modifiedUA) {
      channel.setRequestHeader("User-Agent", modifiedUA, false);
      return;
    }
  }
}
PK
!<&""modules/UserAgentUpdates.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["UserAgentUpdates"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(
  this, "FileUtils", "resource://gre/modules/FileUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(
  this, "NetUtil", "resource://gre/modules/NetUtil.jsm");

XPCOMUtils.defineLazyModuleGetter(
  this, "OS", "resource://gre/modules/osfile.jsm");


XPCOMUtils.defineLazyModuleGetter(
  this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(
  this, "gUpdateTimer", "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");

XPCOMUtils.defineLazyGetter(this, "gApp",
  function() {
    return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo)
                                            .QueryInterface(Ci.nsIXULRuntime);
  });

XPCOMUtils.defineLazyGetter(this, "gDecoder",
  function() { return new TextDecoder(); }
);

XPCOMUtils.defineLazyGetter(this, "gEncoder",
  function() { return new TextEncoder(); }
);

const TIMER_ID = "user-agent-updates-timer";

const PREF_UPDATES = "general.useragent.updates.";
const PREF_UPDATES_ENABLED = PREF_UPDATES + "enabled";
const PREF_UPDATES_URL = PREF_UPDATES + "url";
const PREF_UPDATES_INTERVAL = PREF_UPDATES + "interval";
const PREF_UPDATES_RETRY = PREF_UPDATES + "retry";
const PREF_UPDATES_TIMEOUT = PREF_UPDATES + "timeout";
const PREF_UPDATES_LASTUPDATED = PREF_UPDATES + "lastupdated";

const KEY_PREFDIR = "PrefD";
const KEY_APPDIR = "XCurProcD";
const FILE_UPDATES = "ua-update.json";

const PREF_APP_DISTRIBUTION = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";

var gInitialized = false;

function readChannel(url) {
  return new Promise((resolve, reject) => {
    try {
      let channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
      channel.contentType = "application/json";

      NetUtil.asyncFetch(channel, (inputStream, status) => {
        if (!Components.isSuccessCode(status)) {
          reject();
          return;
        }

        let data = JSON.parse(
          NetUtil.readInputStreamToString(inputStream, inputStream.available())
        );
        resolve(data);
      });
    } catch (ex) {
      reject(new Error("UserAgentUpdates: Could not fetch " + url + " " +
                       ex + "\n" + ex.stack));
    }
  });
}

this.UserAgentUpdates = {
  init: function(callback) {
    if (gInitialized) {
      return;
    }
    gInitialized = true;

    this._callback = callback;
    this._lastUpdated = 0;
    this._applySavedUpdate();

    Services.prefs.addObserver(PREF_UPDATES, this);
  },

  uninit: function() {
    if (!gInitialized) {
      return;
    }
    gInitialized = false;
    Services.prefs.removeObserver(PREF_UPDATES, this);
  },

  _applyUpdate: function(update) {
    // Check pref again in case it has changed
    if (update && this._getPref(PREF_UPDATES_ENABLED, false)) {
      this._callback(update);
    } else {
      this._callback(null);
    }
  },

  _applySavedUpdate: function() {
    if (!this._getPref(PREF_UPDATES_ENABLED, false)) {
      // remove previous overrides
      this._applyUpdate(null);
      return;
    }
    // try loading from profile dir, then from app dir
    let dirs = [KEY_PREFDIR, KEY_APPDIR];

    dirs.reduce((prevLoad, dir) => {
      let file = FileUtils.getFile(dir, [FILE_UPDATES], true).path;
      // tryNext returns promise to read file under dir and parse it
      let tryNext = () => OS.File.read(file).then(
        (bytes) => {
          let update = JSON.parse(gDecoder.decode(bytes));
          if (!update) {
            throw new Error("invalid update");
          }
          return update;
        }
      );
      // try to load next one if the previous load failed
      return prevLoad ? prevLoad.catch(tryNext) : tryNext();
    }, null).catch((ex) => {
      if (AppConstants.platform !== "android") {
        // All previous (non-Android) load attempts have failed, so we bail.
        throw new Error("UserAgentUpdates: Failed to load " + FILE_UPDATES +
                         ex + "\n" + ex.stack);
      }
      // Make one last attempt to read from the Fennec APK root.
      return readChannel("resource://android/" + FILE_UPDATES);
    }).then((update) => {
      // Apply update if loading was successful
      this._applyUpdate(update);
    }).catch(Cu.reportError);
    this._scheduleUpdate();
  },

  _saveToFile: function(update) {
    let file = FileUtils.getFile(KEY_PREFDIR, [FILE_UPDATES], true);
    let path = file.path;
    let bytes = gEncoder.encode(JSON.stringify(update));
    OS.File.writeAtomic(path, bytes, {tmpPath: path + ".tmp"}).then(
      () => {
        this._lastUpdated = Date.now();
        Services.prefs.setCharPref(
          PREF_UPDATES_LASTUPDATED, this._lastUpdated.toString());
      },
      Cu.reportError
    );
  },

  _getPref: function(name, def) {
    try {
      switch (typeof def) {
        case "number": return Services.prefs.getIntPref(name);
        case "boolean": return Services.prefs.getBoolPref(name);
      }
      return Services.prefs.getCharPref(name);
    } catch (e) {
      return def;
    }
  },

  _getParameters() {
    return {
      "%DATE%": function() { return Date.now().toString(); },
      "%PRODUCT%": function() { return gApp.name; },
      "%APP_ID%": function() { return gApp.ID; },
      "%APP_VERSION%": function() { return gApp.version; },
      "%BUILD_ID%": function() { return gApp.appBuildID; },
      "%OS%": function() { return gApp.OS; },
      "%CHANNEL%": function() { return UpdateUtils.UpdateChannel; },
      "%DISTRIBUTION%": function() { return this._getPref(PREF_APP_DISTRIBUTION, ""); },
      "%DISTRIBUTION_VERSION%": function() { return this._getPref(PREF_APP_DISTRIBUTION_VERSION, ""); },
    };
  },

  _getUpdateURL: function() {
    let url = this._getPref(PREF_UPDATES_URL, "");
    let params = this._getParameters();
    return url.replace(/%[A-Z_]+%/g, function(match) {
      let param = params[match];
      // preserve the %FOO% string (e.g. as an encoding) if it's not a valid parameter
      return param ? encodeURIComponent(param()) : match;
    });
  },

  _fetchUpdate: function(url, success, error) {
    let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
                    .createInstance(Ci.nsIXMLHttpRequest);
    request.mozBackgroundRequest = true;
    request.timeout = this._getPref(PREF_UPDATES_TIMEOUT, 60000);
    request.open("GET", url, true);
    request.overrideMimeType("application/json");
    request.responseType = "json";

    request.addEventListener("load", function() {
      let response = request.response;
      response ? success(response) : error();
    });
    request.addEventListener("error", error);
    request.send();
  },

  _update: function() {
    let url = this._getUpdateURL();
    url && this._fetchUpdate(url,
      response => { // success
        // apply update and save overrides to profile
        this._applyUpdate(response);
        this._saveToFile(response);
        this._scheduleUpdate(); // cancel any retries
      },
      response => { // error
        this._scheduleUpdate(true /* retry */);
      });
  },

  _scheduleUpdate: function(retry) {
    // only schedule updates in the main process
    if (gApp.processType !== Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
      return;
    }
    let interval = this._getPref(PREF_UPDATES_INTERVAL, 604800 /* 1 week */);
    if (retry) {
      interval = this._getPref(PREF_UPDATES_RETRY, interval);
    }
    gUpdateTimer.registerTimer(TIMER_ID, this, Math.max(1, interval));
  },

  notify: function(timer) {
    // timer notification
    if (this._getPref(PREF_UPDATES_ENABLED, false)) {
      this._update();
    }
  },

  observe: function(subject, topic, data) {
    switch (topic) {
      case "nsPref:changed":
        if (data === PREF_UPDATES_ENABLED) {
          this._applySavedUpdate();
        } else if (data === PREF_UPDATES_INTERVAL) {
          this._scheduleUpdate();
        } else if (data === PREF_UPDATES_LASTUPDATED) {
          // reload from file if there has been an update
          let lastUpdated = parseInt(
            this._getPref(PREF_UPDATES_LASTUPDATED, "0"), 0);
          if (lastUpdated > this._lastUpdated) {
            this._applySavedUpdate();
            this._lastUpdated = lastUpdated;
          }
        }
        break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsITimerCallback,
  ]),
};
PK
!<Wr 		modules/ValueExtractor.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
 * Helper functions extract values from manifest members
 * and reports conformance violations.
 */
/*globals Components*/
'use strict';
const {
  classes: Cc,
  interfaces: Ci
} = Components;

function ValueExtractor(aConsole, aBundle) {
  this.console = aConsole;
  this.domBundle = aBundle;
}

ValueExtractor.prototype = {
  // This function takes a 'spec' object and destructures
  // it to extract a value. If the value is of th wrong type, it
  // warns the developer and returns undefined.
  //  expectType: is the type of a JS primitive (string, number, etc.)
  //  object: is the object from which to extract the value.
  //  objectName: string used to construct the developer warning.
  //  property: the name of the property being extracted.
  //  trim: boolean, if the value should be trimmed (used by string type).
  extractValue({expectedType, object, objectName, property, trim}) {
    const value = object[property];
    const isArray = Array.isArray(value);
    // We need to special-case "array", as it's not a JS primitive.
    const type = (isArray) ? 'array' : typeof value;
    if (type !== expectedType) {
      if (type !== 'undefined') {
        this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidType",
                                                              [objectName, property, expectedType],
                                                              3));
      }
      return undefined;
    }
    // Trim string and returned undefined if the empty string.
    const shouldTrim = expectedType === 'string' && value && trim;
    if (shouldTrim) {
      return value.trim() || undefined;
    }
    return value;
  },
  extractColorValue(spec) {
    const value = this.extractValue(spec);
    const DOMUtils = Cc['@mozilla.org/inspector/dom-utils;1']
      .getService(Ci.inIDOMUtils);
    let color;
    if (DOMUtils.isValidCSSColor(value)) {
      color = value;
    } else if (value) {
      this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidCSSColor",
                                                            [spec.property, value],
                                                            2));
    }
    return color;
  }
};
this.ValueExtractor = ValueExtractor; // jshint ignore:line
this.EXPORTED_SYMBOLS = ['ValueExtractor']; // jshint ignore:line
PK
!<WS**modules/ViewSourceBrowser.jsm// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { utils: Cu, interfaces: Ci, classes: Cc } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");

const BUNDLE_URL = "chrome://global/locale/viewSource.properties";

const FRAME_SCRIPT = "chrome://global/content/viewSource-content.js";

this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"];

// Keep a set of browsers we've seen before, so we can load our frame script as
// needed into any new ones.
var gKnownBrowsers = new WeakSet();

/**
 * ViewSourceBrowser manages the view source <browser> from the chrome side.
 * It's companion frame script, viewSource-content.js, needs to be loaded as a
 * frame script into the browser being managed.
 *
 * For a view source window using viewSource.xul, the script viewSource.js in
 * the window extends an instance of this with more window specific functions.
 * The page script takes care of loading the companion frame script.
 *
 * For a view source tab (or some other non-window case), an instance of this is
 * created by viewSourceUtils.js to wrap the <browser>.  The frame script will
 * be loaded by this module at construction time.
 */
this.ViewSourceBrowser = function ViewSourceBrowser(aBrowser) {
  this._browser = aBrowser;
  this.init();
}

ViewSourceBrowser.prototype = {
  /**
   * The <browser> that will be displaying the view source content.
   */
  get browser() {
    return this._browser;
  },

  /**
   * Holds the value of the last line found via the "Go to line"
   * command, to pre-populate the prompt the next time it is
   * opened.
   */
  lastLineFound: null,

  /**
   * These are the messages that ViewSourceBrowser will listen for
   * from the frame script it injects. Any message names added here
   * will automatically have ViewSourceBrowser listen for those messages,
   * and remove the listeners on teardown.
   */
  messages: [
    "ViewSource:PromptAndGoToLine",
    "ViewSource:GoToLine:Success",
    "ViewSource:GoToLine:Failed",
    "ViewSource:StoreWrapping",
    "ViewSource:StoreSyntaxHighlighting",
  ],

  /**
   * This should be called as soon as the script loads. When this function
   * executes, we can assume the DOM content has not yet loaded.
   */
  init() {
    this.messages.forEach((msgName) => {
      this.mm.addMessageListener(msgName, this);
    });

    // If we have a known <browser> already, load the frame script here.  This
    // is not true for the window case, as the element does not exist until the
    // XUL document loads.  For that case, the frame script is loaded by
    // viewSource.js.
    if (this._browser) {
      this.loadFrameScript();
    }
  },

  /**
   * This should be called when the window is closing. This function should
   * clean up event and message listeners.
   */
  uninit() {
    this.messages.forEach((msgName) => {
      this.mm.removeMessageListener(msgName, this);
    });
  },

  /**
   * For a new browser we've not seen before, load the frame script.
   */
  loadFrameScript() {
    if (!gKnownBrowsers.has(this.browser)) {
      gKnownBrowsers.add(this.browser);
      this.mm.loadFrameScript(FRAME_SCRIPT, false);
    }
  },

  /**
   * Anything added to the messages array will get handled here, and should
   * get dispatched to a specific function for the message name.
   */
  receiveMessage(message) {
    let data = message.data;

    switch (message.name) {
      case "ViewSource:PromptAndGoToLine":
        this.promptAndGoToLine();
        break;
      case "ViewSource:GoToLine:Success":
        this.onGoToLineSuccess(data.lineNumber);
        break;
      case "ViewSource:GoToLine:Failed":
        this.onGoToLineFailed();
        break;
      case "ViewSource:StoreWrapping":
        this.storeWrapping(data.state);
        break;
      case "ViewSource:StoreSyntaxHighlighting":
        this.storeSyntaxHighlighting(data.state);
        break;
    }
  },

  /**
   * Getter for the message manager of the view source browser.
   */
  get mm() {
    return this.browser.messageManager;
  },

  /**
   * Send a message to the view source browser.
   */
  sendAsyncMessage(...args) {
    this.browser.messageManager.sendAsyncMessage(...args);
  },

  /**
   * A getter for the view source string bundle.
   */
  get bundle() {
    if (this._bundle) {
      return this._bundle;
    }
    return this._bundle = Services.strings.createBundle(BUNDLE_URL);
  },

  /**
   * Loads the source for a URL while applying some optional features if
   * enabled.
   *
   * For the viewSource.xul window, this is called by onXULLoaded above.
   * For view source in a specific browser, this is manually called after
   * this object is constructed.
   *
   * This takes a single object argument containing:
   *
   *   URL (required):
   *     A string URL for the page we'd like to view the source of.
   *   browser:
   *     The browser containing the document that we would like to view the
   *     source of. This argument is optional if outerWindowID is not passed.
   *   outerWindowID (optional):
   *     The outerWindowID of the content window containing the document that
   *     we want to view the source of. This is the only way of attempting to
   *     load the source out of the network cache.
   *   lineNumber (optional):
   *     The line number to focus on once the source is loaded.
   */
  loadViewSource({ URL, browser, outerWindowID, lineNumber }) {
    if (!URL) {
      throw new Error("Must supply a URL when opening view source.");
    }

    if (browser) {
      this.browser.sameProcessAsFrameLoader = browser.frameLoader;

      // If we're dealing with a remote browser, then the browser
      // for view source needs to be remote as well.
      this.updateBrowserRemoteness(browser.isRemoteBrowser, browser.remoteType);
    } else if (outerWindowID) {
      throw new Error("Must supply the browser if passing the outerWindowID");
    }

    this.sendAsyncMessage("ViewSource:LoadSource",
                          { URL, outerWindowID, lineNumber });
  },

  /**
   * Loads a view source selection showing the given view-source url and
   * highlight the selection.
   *
   * @param uri view-source uri to show
   * @param drawSelection true to highlight the selection
   * @param baseURI base URI of the original document
   */
  loadViewSourceFromSelection(URL, drawSelection, baseURI) {
    this.sendAsyncMessage("ViewSource:LoadSourceWithSelection",
                          { URL, drawSelection, baseURI });
  },

  /**
   * Updates the "remote" attribute of the view source browser. This
   * will remove the browser from the DOM, and then re-add it in the
   * same place it was taken from.
   *
   * @param shouldBeRemote
   *        True if the browser should be made remote. If the browsers
   *        remoteness already matches this value, this function does
   *        nothing.
   * @param remoteType
   *        The type of remote browser process.
   */
  updateBrowserRemoteness(shouldBeRemote, remoteType) {
    if (this.browser.isRemoteBrowser != shouldBeRemote ||
        this.browser.remoteType != remoteType) {
      // In this base case, where we are handed a <browser> someone else is
      // managing, we don't know for sure that it's safe to toggle remoteness.
      // For view source in a window, this is overridden to actually do the
      // flip if needed.
      throw new Error("View source browser's remoteness mismatch");
    }
  },

  /**
   * Opens the "Go to line" prompt for a user to hop to a particular line
   * of the source code they're viewing. This will keep prompting until the
   * user either cancels out of the prompt, or enters a valid line number.
   */
  promptAndGoToLine() {
    let input = { value: this.lastLineFound };
    let window = Services.wm.getMostRecentWindow(null);

    let ok = Services.prompt.prompt(
        window,
        this.bundle.GetStringFromName("goToLineTitle"),
        this.bundle.GetStringFromName("goToLineText"),
        input,
        null,
        {value: 0});

    if (!ok)
      return;

    let line = parseInt(input.value, 10);

    if (!(line > 0)) {
      Services.prompt.alert(window,
                            this.bundle.GetStringFromName("invalidInputTitle"),
                            this.bundle.GetStringFromName("invalidInputText"));
      this.promptAndGoToLine();
    } else {
      this.goToLine(line);
    }
  },

  /**
   * Go to a particular line of the source code. This act is asynchronous.
   *
   * @param lineNumber
   *        The line number to try to go to to.
   */
  goToLine(lineNumber) {
    this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
  },

  /**
   * Called when the frame script reports that a line was successfully gotten
   * to.
   *
   * @param lineNumber
   *        The line number that we successfully got to.
   */
  onGoToLineSuccess(lineNumber) {
    // We'll pre-populate the "Go to line" prompt with this value the next
    // time it comes up.
    this.lastLineFound = lineNumber;
  },

  /**
   * Called when the frame script reports that we failed to go to a particular
   * line. This informs the user that their selection was likely out of range,
   * and then reprompts the user to try again.
   */
  onGoToLineFailed() {
    let window = Services.wm.getMostRecentWindow(null);
    Services.prompt.alert(window,
                          this.bundle.GetStringFromName("outOfRangeTitle"),
                          this.bundle.GetStringFromName("outOfRangeText"));
    this.promptAndGoToLine();
  },

  /**
   * Update the wrapping pref based on the child's current state.
   * @param state
   *        Whether wrapping is currently enabled in the child.
   */
  storeWrapping(state) {
    Services.prefs.setBoolPref("view_source.wrap_long_lines", state);
  },

  /**
   * Update the syntax highlighting pref based on the child's current state.
   * @param state
   *        Whether syntax highlighting is currently enabled in the child.
   */
  storeSyntaxHighlighting(state) {
    Services.prefs.setBoolPref("view_source.syntax_highlight", state);
  },

};

/**
 * Helper to decide if a URI maps to view source content.
 * @param uri
 *        String containing the URI
 */
ViewSourceBrowser.isViewSource = function(uri) {
  return uri.startsWith("view-source:") ||
         (uri.startsWith("data:") && uri.includes("MathML"));
};
PK
!<|0g,,modules/WebChannel.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * WebChannel is an abstraction that uses the Message Manager and Custom Events
 * to create a two-way communication channel between chrome and content code.
 */

this.EXPORTED_SYMBOLS = ["WebChannel", "WebChannelBroker"];

const ERRNO_MISSING_PRINCIPAL          = 1;
const ERRNO_NO_SUCH_CHANNEL            = 2;
const ERRNO_UNKNOWN_ERROR              = 999;
const ERROR_UNKNOWN                    = "UNKNOWN_ERROR";


const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");


/**
 * WebChannelBroker is a global object that helps manage WebChannel objects.
 * This object handles channel registration, origin validation and message multiplexing.
 */

var WebChannelBroker = Object.create({
  /**
   * Register a new channel that callbacks messages
   * based on proper origin and channel name
   *
   * @param channel {WebChannel}
   */
  registerChannel(channel) {
    if (!this._channelMap.has(channel)) {
      this._channelMap.set(channel);
    } else {
      Cu.reportError("Failed to register the channel. Channel already exists.");
    }

    // attach the global message listener if needed
    if (!this._messageListenerAttached) {
      this._messageListenerAttached = true;
      this._manager.addMessageListener("WebChannelMessageToChrome", this._listener.bind(this));
    }
  },

  /**
   * Unregister a channel
   *
   * @param channelToRemove {WebChannel}
   *        WebChannel to remove from the channel map
   *
   * Removes the specified channel from the channel map
   */
  unregisterChannel(channelToRemove) {
    if (!this._channelMap.delete(channelToRemove)) {
      Cu.reportError("Failed to unregister the channel. Channel not found.");
    }
  },

  /**
   * @param event {Event}
   *        Message Manager event
   * @private
   */
  _listener(event) {
    let data = event.data;
    let sendingContext = {
      browser: event.target,
      eventTarget: event.objects.eventTarget,
      principal: event.principal,
    };
    // data must be a string except for a few legacy origins allowed by browser-content.js.
    if (typeof data == "string") {
      try {
        data = JSON.parse(data);
      } catch (e) {
        Cu.reportError("Failed to parse WebChannel data as a JSON object");
        return;
      }
    }

    if (data && data.id) {
      if (!event.principal) {
        this._sendErrorEventToContent(data.id, sendingContext, ERRNO_MISSING_PRINCIPAL, "Message principal missing");
      } else {
        let validChannelFound = false;
        data.message = data.message || {};

        for (var channel of this._channelMap.keys()) {
          if (channel.id === data.id &&
            channel._originCheckCallback(event.principal)) {
            validChannelFound = true;
            channel.deliver(data, sendingContext);
          }
        }

        // if no valid origins send an event that there is no such valid channel
        if (!validChannelFound) {
          this._sendErrorEventToContent(data.id, sendingContext, ERRNO_NO_SUCH_CHANNEL, "No Such Channel");
        }
      }
    } else {
      Cu.reportError("WebChannel channel id missing");
    }
  },
  /**
   * The global message manager operates on every <browser>
   */
  _manager: Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager),
  /**
   * Boolean used to detect if the global message manager event is already attached
   */
  _messageListenerAttached: false,
  /**
   * Object to store pairs of message origins and callback functions
   */
  _channelMap: new Map(),
  /**
   *
   * @param id {String}
   *        The WebChannel id to include in the message
   * @param sendingContext {Object}
   *        Message sending context
   * @param [errorMsg] {String}
   *        Error message
   * @private
   */
  _sendErrorEventToContent(id, sendingContext, errorNo, errorMsg) {
    let { browser: targetBrowser, eventTarget, principal: targetPrincipal } = sendingContext;

    errorMsg = errorMsg || "Web Channel Broker error";

    if (targetBrowser && targetBrowser.messageManager) {
      targetBrowser.messageManager.sendAsyncMessage("WebChannelMessageToContent", {
        id,
        message: {
          errno: errorNo,
          error: errorMsg,
        },
      }, { eventTarget }, targetPrincipal);
    } else {
      Cu.reportError("Failed to send a WebChannel error. Target invalid.");
    }
    Cu.reportError(id.toString() + " error message. " + errorMsg);
  },
});


/**
 * Creates a new WebChannel that listens and sends messages over some channel id
 *
 * @param id {String}
 *        WebChannel id
 * @param originOrPermission {nsIURI/string}
 *        If an nsIURI, incoming events will be accepted from any origin matching
 *        that URI's origin.
 *        If a string, it names a permission, and incoming events will be accepted
 *        from any https:// origin that has been granted that permission by the
 *        permission manager.
 * @constructor
 */
this.WebChannel = function(id, originOrPermission) {
  if (!id || !originOrPermission) {
    throw new Error("WebChannel id and originOrPermission are required.");
  }

  this.id = id;
  // originOrPermission can be either an nsIURI or a string representing a
  // permission name.
  if (typeof originOrPermission == "string") {
    this._originCheckCallback = requestPrincipal => {
      // Accept events from any secure origin having the named permission.
      // The permission manager operates on domain names rather than true
      // origins (bug 1066517).  To mitigate that, we explicitly check that
      // the scheme is https://.
      let uri = Services.io.newURI(requestPrincipal.originNoSuffix);
      if (uri.scheme != "https") {
        return false;
      }
      // OK - we have https - now we can check the permission.
      let perm = Services.perms.testExactPermissionFromPrincipal(requestPrincipal,
                                                                 originOrPermission);
      return perm == Ci.nsIPermissionManager.ALLOW_ACTION;
    }
  } else {
    // Accept events from any origin matching the given URI.
    // We deliberately use `originNoSuffix` here because we only want to
    // restrict based on the site's origin, not on other origin attributes
    // such as containers or private browsing.
    this._originCheckCallback = requestPrincipal => {
      return originOrPermission.prePath === requestPrincipal.originNoSuffix;
    }
  }
  this._originOrPermission = originOrPermission;
};

this.WebChannel.prototype = {

  /**
   * WebChannel id
   */
  id: null,

  /**
   * The originOrPermission value passed to the constructor, mainly for
   * debugging and tests.
   */
  _originOrPermission: null,

  /**
   * Callback that will be called with the principal of an incoming message
   * to check if the request should be dispatched to the listeners.
   */
  _originCheckCallback: null,

  /**
   * WebChannelBroker that manages WebChannels
   */
  _broker: WebChannelBroker,

  /**
   * Callback that will be called with the contents of an incoming message
   */
  _deliverCallback: null,

  /**
   * Registers the callback for messages on this channel
   * Registers the channel itself with the WebChannelBroker
   *
   * @param callback {Function}
   *        Callback that will be called when there is a message
   *        @param {String} id
   *        The WebChannel id that was used for this message
   *        @param {Object} message
   *        The message itself
   *        @param sendingContext {Object}
   *        The sending context of the source of the message. Can be passed to
   *        `send` to respond to a message.
   *               @param sendingContext.browser {browser}
   *                      The <browser> object that captured the
   *                      WebChannelMessageToChrome.
   *               @param sendingContext.eventTarget {EventTarget}
   *                      The <EventTarget> where the message was sent.
   *               @param sendingContext.principal {Principal}
   *                      The <Principal> of the EventTarget where the
   *                      message was sent.
   */
  listen(callback) {
    if (this._deliverCallback) {
      throw new Error("Failed to listen. Listener already attached.");
    } else if (!callback) {
      throw new Error("Failed to listen. Callback argument missing.");
    } else {
      this._deliverCallback = callback;
      this._broker.registerChannel(this);
    }
  },

  /**
   * Resets the callback for messages on this channel
   * Removes the channel from the WebChannelBroker
   */
  stopListening() {
    this._broker.unregisterChannel(this);
    this._deliverCallback = null;
  },

  /**
   * Sends messages over the WebChannel id using the "WebChannelMessageToContent" event
   *
   * @param message {Object}
   *        The message object that will be sent
   * @param target {Object}
   *        A <target> with the information of where to send the message.
   *        @param target.browser {browser}
   *               The <browser> object with a "messageManager" that will
   *               be used to send the message.
   *        @param target.principal {Principal}
   *               Principal of the target. Prevents messages from
   *               being dispatched to unexpected origins. The system principal
   *               can be specified to send to any target.
   *        @param [target.eventTarget] {EventTarget}
   *               Optional eventTarget within the browser, use to send to a
   *               specific element, e.g., an iframe.
   */
  send(message, target) {
    let { browser, principal, eventTarget } = target;

    if (message && browser && browser.messageManager && principal) {
      browser.messageManager.sendAsyncMessage("WebChannelMessageToContent", {
        id: this.id,
        message
      }, { eventTarget }, principal);
    } else if (!message) {
      Cu.reportError("Failed to send a WebChannel message. Message not set.");
    } else {
      Cu.reportError("Failed to send a WebChannel message. Target invalid.");
    }
  },

  /**
   * Deliver WebChannel messages to the set "_channelCallback"
   *
   * @param data {Object}
   *        Message data
   * @param sendingContext {Object}
   *        Message sending context.
   *        @param sendingContext.browser {browser}
   *               The <browser> object that captured the
   *               WebChannelMessageToChrome.
   *        @param sendingContext.eventTarget {EventTarget}
   *               The <EventTarget> where the message was sent.
   *        @param sendingContext.principal {Principal}
   *               The <Principal> of the EventTarget where the message was sent.
   *
   */
  deliver(data, sendingContext) {
    if (this._deliverCallback) {
      try {
        this._deliverCallback(data.id, data.message, sendingContext);
      } catch (ex) {
        this.send({
          errno: ERRNO_UNKNOWN_ERROR,
          error: ex.message ? ex.message : ERROR_UNKNOWN
        }, sendingContext);
        Cu.reportError("Failed to execute WebChannel callback:");
        Cu.reportError(ex);
      }
    } else {
      Cu.reportError("No callback set for this channel.");
    }
  }
};
PK
!<PWA-8-8modules/WebNavigation.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["WebNavigation"];

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                  "resource:///modules/RecentWindow.jsm");

// Maximum amount of time that can be passed and still consider
// the data recent (similar to how is done in nsNavHistory,
// e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value).
const RECENT_DATA_THRESHOLD = 5 * 1000000;

var Manager = {
  // Map[string -> Map[listener -> URLFilter]]
  listeners: new Map(),

  init() {
    // Collect recent tab transition data in a WeakMap:
    //   browser -> tabTransitionData
    this.recentTabTransitionData = new WeakMap();

    // Collect the pending created navigation target events that still have to
    // pair the message received from the source tab to the one received from
    // the new tab.
    this.createdNavigationTargetByOuterWindowId = new Map();

    Services.obs.addObserver(this, "autocomplete-did-enter-text", true);

    Services.obs.addObserver(this, "webNavigation-createdNavigationTarget");

    Services.mm.addMessageListener("Content:Click", this);
    Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
    Services.mm.addMessageListener("Extension:StateChange", this);
    Services.mm.addMessageListener("Extension:DocumentChange", this);
    Services.mm.addMessageListener("Extension:HistoryChange", this);
    Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this);

    Services.mm.loadFrameScript("resource://gre/modules/WebNavigationContent.js", true);
  },

  uninit() {
    // Stop collecting recent tab transition data and reset the WeakMap.
    Services.obs.removeObserver(this, "autocomplete-did-enter-text");
    Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");

    Services.mm.removeMessageListener("Content:Click", this);
    Services.mm.removeMessageListener("Extension:StateChange", this);
    Services.mm.removeMessageListener("Extension:DocumentChange", this);
    Services.mm.removeMessageListener("Extension:HistoryChange", this);
    Services.mm.removeMessageListener("Extension:DOMContentLoaded", this);
    Services.mm.removeMessageListener("Extension:CreatedNavigationTarget", this);

    Services.mm.removeDelayedFrameScript("resource://gre/modules/WebNavigationContent.js");
    Services.mm.broadcastAsyncMessage("Extension:DisableWebNavigation");

    this.recentTabTransitionData = new WeakMap();
    this.createdNavigationTargetByOuterWindowId.clear();
  },

  addListener(type, listener, filters) {
    if (this.listeners.size == 0) {
      this.init();
    }

    if (!this.listeners.has(type)) {
      this.listeners.set(type, new Map());
    }
    let listeners = this.listeners.get(type);
    listeners.set(listener, filters);
  },

  removeListener(type, listener) {
    let listeners = this.listeners.get(type);
    if (!listeners) {
      return;
    }
    listeners.delete(listener);
    if (listeners.size == 0) {
      this.listeners.delete(type);
    }

    if (this.listeners.size == 0) {
      this.uninit();
    }
  },

  /**
   * Support nsIObserver interface to observe the urlbar autocomplete events used
   * to keep track of the urlbar user interaction.
   */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),

  /**
   * Observe autocomplete-did-enter-text (to track the user interaction with the awesomebar)
   * and webNavigation-createdNavigationTarget (to fire the onCreatedNavigationTarget
   * related to windows or tabs opened from the main process) topics.
   *
   * @param {nsIAutoCompleteInput|Object} subject
   * @param {string} topic
   * @param {string|undefined} data
   */
  observe: function(subject, topic, data) {
    if (topic == "autocomplete-did-enter-text") {
      this.onURLBarAutoCompletion(subject);
    } else if (topic == "webNavigation-createdNavigationTarget") {
      // The observed notification is coming from privileged JavaScript components running
      // in the main process (e.g. when a new tab or window is opened using the context menu
      // or Ctrl/Shift + click on a link).
      const {
        createdTabBrowser,
        url,
        sourceFrameOuterWindowID,
        sourceTabBrowser,
      } = subject.wrappedJSObject;

      this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
        sourceTabBrowser,
        sourceFrameId: sourceFrameOuterWindowID,
        url,
      });
    }
  },

  /**
   * Recognize the type of urlbar user interaction (e.g. typing a new url,
   * clicking on an url generated from a searchengine or a keyword, or a
   * bookmark found by the urlbar autocompletion).
   *
   * @param {nsIAutoCompleteInput} input
   */
  onURLBarAutoCompletion(input) {
    if (input && input instanceof Ci.nsIAutoCompleteInput) {
      // We are only interested in urlbar autocompletion events
      if (input.id !== "urlbar") {
        return;
      }

      let controller = input.popup.view.QueryInterface(Ci.nsIAutoCompleteController);
      let idx = input.popup.selectedIndex;

      let tabTransistionData = {
        from_address_bar: true,
      };

      if (idx < 0 || idx >= controller.matchCount) {
        // Recognize when no valid autocomplete results has been selected.
        tabTransistionData.typed = true;
      } else {
        let value = controller.getValueAt(idx);
        let action = input._parseActionUrl(value);

        if (action) {
          // Detect keywork and generated and more typed scenarios.
          switch (action.type) {
            case "keyword":
              tabTransistionData.keyword = true;
              break;
            case "searchengine":
            case "searchsuggestion":
              tabTransistionData.generated = true;
              break;
            case "visiturl":
              // Visiturl are autocompletion results related to
              // history suggestions.
              tabTransistionData.typed = true;
              break;
            case "remotetab":
              // Remote tab are autocomplete results related to
              // tab urls from a remote synchronized Firefox.
              tabTransistionData.typed = true;
              break;
            case "switchtab":
              // This "switchtab" autocompletion should be ignored, because
              // it is not related to a navigation.
              return;
            default:
              // Fallback on "typed" if unable to detect a known moz-action type.
              tabTransistionData.typed = true;
          }
        } else {
          // Special handling for bookmark urlbar autocompletion
          // (which happens when we got a null action and a valid selectedIndex)
          let styles = new Set(controller.getStyleAt(idx).split(/\s+/));

          if (styles.has("bookmark")) {
            tabTransistionData.auto_bookmark = true;
          } else {
            // Fallback on "typed" if unable to detect a specific actionType
            // (and when in the styles there are "autofill" or "history").
            tabTransistionData.typed = true;
          }
        }
      }

      this.setRecentTabTransitionData(tabTransistionData);
    }
  },

  /**
   * Keep track of a recent user interaction and cache it in a
   * map associated to the current selected tab.
   *
   * @param {object} tabTransitionData
   * @param {boolean} [tabTransitionData.auto_bookmark]
   * @param {boolean} [tabTransitionData.from_address_bar]
   * @param {boolean} [tabTransitionData.generated]
   * @param {boolean} [tabTransitionData.keyword]
   * @param {boolean} [tabTransitionData.link]
   * @param {boolean} [tabTransitionData.typed]
   */
  setRecentTabTransitionData(tabTransitionData) {
    let window = RecentWindow.getMostRecentBrowserWindow();
    if (window && window.gBrowser && window.gBrowser.selectedTab &&
        window.gBrowser.selectedTab.linkedBrowser) {
      let browser = window.gBrowser.selectedTab.linkedBrowser;

      // Get recent tab transition data to update if any.
      let prevData = this.getAndForgetRecentTabTransitionData(browser);

      let newData = Object.assign(
        {time: Date.now()},
        prevData,
        tabTransitionData
      );
      this.recentTabTransitionData.set(browser, newData);
    }
  },

  /**
   * Retrieve recent data related to a recent user interaction give a
   * given tab's linkedBrowser (only if is is more recent than the
   * `RECENT_DATA_THRESHOLD`).
   *
   * NOTE: this method is used to retrieve the tab transition data
   * collected when one of the `onCommitted`, `onHistoryStateUpdated`
   * or `onReferenceFragmentUpdated` events has been received.
   *
   * @param {XULBrowserElement} browser
   * @returns {object}
   */
  getAndForgetRecentTabTransitionData(browser) {
    let data = this.recentTabTransitionData.get(browser);
    this.recentTabTransitionData.delete(browser);

    // Return an empty object if there isn't any tab transition data
    // or if it's less recent than RECENT_DATA_THRESHOLD.
    if (!data || (data.time - Date.now()) > RECENT_DATA_THRESHOLD) {
      return {};
    }

    return data;
  },

  /**
   * Receive messages from the WebNavigationContent.js framescript
   * over message manager events.
   */
  receiveMessage({name, data, target}) {
    switch (name) {
      case "Extension:StateChange":
        this.onStateChange(target, data);
        break;

      case "Extension:DocumentChange":
        this.onDocumentChange(target, data);
        break;

      case "Extension:HistoryChange":
        this.onHistoryChange(target, data);
        break;

      case "Extension:DOMContentLoaded":
        this.onLoad(target, data);
        break;

      case "Content:Click":
        this.onContentClick(target, data);
        break;

      case "Extension:CreatedNavigationTarget":
        this.onCreatedNavigationTarget(target, data);
        break;
    }
  },

  onContentClick(target, data) {
    // We are interested only on clicks to links which are not "add to bookmark" commands
    if (data.href && !data.bookmark) {
      let ownerWin = target.ownerGlobal;
      let where = ownerWin.whereToOpenLink(data);
      if (where == "current") {
        this.setRecentTabTransitionData({link: true});
      }
    }
  },

  onCreatedNavigationTarget(browser, data) {
    const {isSourceTab, createdWindowId, sourceFrameId, url} = data;

    // We are going to potentially received two message manager messages for a single
    // onCreatedNavigationTarget event that is happening in the child process,
    // we are going to use the generate uuid to pair them together.
    const pairedMessage = this.createdNavigationTargetByOuterWindowId.get(createdWindowId);

    if (!pairedMessage) {
      this.createdNavigationTargetByOuterWindowId.set(createdWindowId, {browser, data});
      return;
    }

    this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);

    let sourceTabBrowser;
    let createdTabBrowser;

    if (isSourceTab) {
      sourceTabBrowser = browser;
      createdTabBrowser = pairedMessage.browser;
    } else {
      sourceTabBrowser = pairedMessage.browser;
      createdTabBrowser = browser;
    }

    this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
      sourceTabBrowser, sourceFrameId, url,
    });
  },

  onStateChange(browser, data) {
    let stateFlags = data.stateFlags;
    if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
      let url = data.requestURL;
      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
        this.fire("onBeforeNavigate", browser, data, {url});
      } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        if (Components.isSuccessCode(data.status)) {
          this.fire("onCompleted", browser, data, {url});
        } else {
          let error = `Error code ${data.status}`;
          this.fire("onErrorOccurred", browser, data, {error, url});
        }
      }
    }
  },

  onDocumentChange(browser, data) {
    let extra = {
      url: data.location,
      // Transition data which is coming from the content process.
      frameTransitionData: data.frameTransitionData,
      tabTransitionData: this.getAndForgetRecentTabTransitionData(browser),
    };

    this.fire("onCommitted", browser, data, extra);
  },

  onHistoryChange(browser, data) {
    let extra = {
      url: data.location,
      // Transition data which is coming from the content process.
      frameTransitionData: data.frameTransitionData,
      tabTransitionData: this.getAndForgetRecentTabTransitionData(browser),
    };

    if (data.isReferenceFragmentUpdated) {
      this.fire("onReferenceFragmentUpdated", browser, data, extra);
    } else if (data.isHistoryStateUpdated) {
      this.fire("onHistoryStateUpdated", browser, data, extra);
    }
  },

  onLoad(browser, data) {
    this.fire("onDOMContentLoaded", browser, data, {url: data.url});
  },

  fire(type, browser, data, extra) {
    let listeners = this.listeners.get(type);
    if (!listeners) {
      return;
    }

    let details = {
      browser,
      frameId: data.frameId,
    };

    if (data.parentFrameId !== undefined) {
      details.parentFrameId = data.parentFrameId;
    }

    for (let prop in extra) {
      details[prop] = extra[prop];
    }

    for (let [listener, filters] of listeners) {
      // Call the listener if the listener has no filter or if its filter matches.
      if (!filters || filters.matches(extra.url)) {
        listener(details);
      }
    }
  },
};

const EVENTS = [
  "onBeforeNavigate",
  "onCommitted",
  "onDOMContentLoaded",
  "onCompleted",
  "onErrorOccurred",
  "onReferenceFragmentUpdated",
  "onHistoryStateUpdated",
  "onCreatedNavigationTarget",
];

var WebNavigation = {};

for (let event of EVENTS) {
  WebNavigation[event] = {
    addListener: Manager.addListener.bind(Manager, event),
    removeListener: Manager.removeListener.bind(Manager, event),
  };
}
PK
!<9MR//modules/WebNavigationContent.js"use strict";

/* eslint-env mozilla/frame-script */

var Ci = Components.interfaces;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
                                  "resource://gre/modules/WebNavigationFrames.jsm");

function loadListener(event) {
  let document = event.target;
  let window = document.defaultView;
  let url = document.documentURI;
  let frameId = WebNavigationFrames.getFrameId(window);
  let parentFrameId = WebNavigationFrames.getParentFrameId(window);
  sendAsyncMessage("Extension:DOMContentLoaded", {frameId, parentFrameId, url});
}

addEventListener("DOMContentLoaded", loadListener);
addMessageListener("Extension:DisableWebNavigation", () => {
  removeEventListener("DOMContentLoaded", loadListener);
});

var CreatedNavigationTargetListener = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),

  init() {
    Services.obs.addObserver(this, "webNavigation-createdNavigationTarget-from-js");
  },
  uninit() {
    Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget-from-js");
  },

  observe(subject, topic, data) {
    if (!(subject instanceof Ci.nsIPropertyBag2)) {
      return;
    }

    let props = subject.QueryInterface(Ci.nsIPropertyBag2);

    const createdDocShell = props.getPropertyAsInterface("createdTabDocShell", Ci.nsIDocShell);
    const sourceDocShell = props.getPropertyAsInterface("sourceTabDocShell", Ci.nsIDocShell);

    const isSourceTabDescendant = sourceDocShell.sameTypeRootTreeItem === docShell;

    if (docShell !== createdDocShell && docShell !== sourceDocShell &&
        !isSourceTabDescendant) {
      // if the createdNavigationTarget is not related to this docShell
      // (this docShell is not the newly created docShell, it is not the source docShell,
      // and the source docShell is not a descendant of it)
      // there is nothing to do here and return early.
      return;
    }

    const isSourceTab = docShell === sourceDocShell || isSourceTabDescendant;
    const sourceFrameId = WebNavigationFrames.getDocShellFrameId(sourceDocShell);
    const createdWindowId = WebNavigationFrames.getDocShellFrameId(createdDocShell);

    let url;
    if (props.hasKey("url")) {
      url = props.getPropertyAsACString("url");
    }

    sendAsyncMessage("Extension:CreatedNavigationTarget", {
      url,
      sourceFrameId,
      createdWindowId,
      isSourceTab,
    });
  },
};

var FormSubmitListener = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsIFormSubmitObserver,
                                         Ci.nsISupportsWeakReference]),
  init() {
    this.formSubmitWindows = new WeakSet();
    Services.obs.addObserver(FormSubmitListener, "earlyformsubmit");
  },

  uninit() {
    Services.obs.removeObserver(FormSubmitListener, "earlyformsubmit");
    this.formSubmitWindows = new WeakSet();
  },

  notify: function(form, window, actionURI) {
    try {
      this.formSubmitWindows.add(window);
    } catch (e) {
      Cu.reportError("Error in FormSubmitListener.notify");
    }
  },

  hasAndForget: function(window) {
    let has = this.formSubmitWindows.has(window);
    this.formSubmitWindows.delete(window);
    return has;
  },
};

var WebProgressListener = {
  init: function() {
    // This WeakMap (DOMWindow -> nsIURI) keeps track of the pathname and hash
    // of the previous location for all the existent docShells.
    this.previousURIMap = new WeakMap();

    // Populate the above previousURIMap by iterating over the docShells tree.
    for (let currentDocShell of WebNavigationFrames.iterateDocShellTree(docShell)) {
      let win = currentDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIDOMWindow);
      let {currentURI} = currentDocShell.QueryInterface(Ci.nsIWebNavigation);

      this.previousURIMap.set(win, currentURI);
    }

    // This WeakSet of DOMWindows keeps track of the attempted refresh.
    this.refreshAttemptedDOMWindows = new WeakSet();

    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebProgress);
    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
                                          Ci.nsIWebProgress.NOTIFY_REFRESH |
                                          Ci.nsIWebProgress.NOTIFY_LOCATION);
  },

  uninit() {
    if (!docShell) {
      return;
    }
    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebProgress);
    webProgress.removeProgressListener(this);
  },

  onRefreshAttempted: function onRefreshAttempted(webProgress, URI, delay, sameURI) {
    this.refreshAttemptedDOMWindows.add(webProgress.DOMWindow);

    // If this function doesn't return true, the attempted refresh will be blocked.
    return true;
  },

  onStateChange: function onStateChange(webProgress, request, stateFlags, status) {
    let {originalURI, URI: locationURI} = request.QueryInterface(Ci.nsIChannel);

    // Prevents "about", "chrome", "resource" and "moz-extension" URI schemes to be
    // reported with the resolved "file" or "jar" URIs. (see Bug 1246125 for rationale)
    if (locationURI.schemeIs("file") || locationURI.schemeIs("jar")) {
      let shouldUseOriginalURI = originalURI.schemeIs("about") ||
                                 originalURI.schemeIs("chrome") ||
                                 originalURI.schemeIs("resource") ||
                                 originalURI.schemeIs("moz-extension");

      locationURI = shouldUseOriginalURI ? originalURI : locationURI;
    }

    this.sendStateChange({webProgress, locationURI, stateFlags, status});

    // Based on the docs of the webNavigation.onCommitted event, it should be raised when:
    // "The document  might still be downloading, but at least part of
    // the document has been received"
    // and for some reason we don't fire onLocationChange for the
    // initial navigation of a sub-frame.
    // For the above two reasons, when the navigation event is related to
    // a sub-frame we process the document change here and
    // then send an "Extension:DocumentChange" message to the main process,
    // where it will be turned into a webNavigation.onCommitted event.
    // (see Bug 1264936 and Bug 125662 for rationale)
    if ((webProgress.DOMWindow.top != webProgress.DOMWindow) &&
        (stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT)) {
      this.sendDocumentChange({webProgress, locationURI, request});
    }
  },

  onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
    let {DOMWindow} = webProgress;

    // Get the previous URI loaded in the DOMWindow.
    let previousURI = this.previousURIMap.get(DOMWindow);

    // Update the URI in the map with the new locationURI.
    this.previousURIMap.set(DOMWindow, locationURI);

    let isSameDocument = (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);

    // When a frame navigation doesn't change the current loaded document
    // (which can be due to history.pushState/replaceState or to a changed hash in the url),
    // it is reported only to the onLocationChange, for this reason
    // we process the history change here and then we are going to send
    // an "Extension:HistoryChange" to the main process, where it will be turned
    // into a webNavigation.onHistoryStateUpdated/onReferenceFragmentUpdated event.
    if (isSameDocument) {
      this.sendHistoryChange({webProgress, previousURI, locationURI, request});
    } else if (webProgress.DOMWindow.top == webProgress.DOMWindow) {
      // We have to catch the document changes from top level frames here,
      // where we can detect the "server redirect" transition.
      // (see Bug 1264936 and Bug 125662 for rationale)
      this.sendDocumentChange({webProgress, locationURI, request});
    }
  },

  sendStateChange({webProgress, locationURI, stateFlags, status}) {
    let data = {
      requestURL: locationURI.spec,
      frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
      parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
      status,
      stateFlags,
    };

    sendAsyncMessage("Extension:StateChange", data);
  },

  sendDocumentChange({webProgress, locationURI, request}) {
    let {loadType, DOMWindow} = webProgress;
    let frameTransitionData = this.getFrameTransitionData({loadType, request, DOMWindow});

    let data = {
      frameTransitionData,
      location: locationURI ? locationURI.spec : "",
      frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
      parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
    };

    sendAsyncMessage("Extension:DocumentChange", data);
  },

  sendHistoryChange({webProgress, previousURI, locationURI, request}) {
    let {loadType, DOMWindow} = webProgress;

    let isHistoryStateUpdated = false;
    let isReferenceFragmentUpdated = false;

    let pathChanged = !(previousURI && locationURI.equalsExceptRef(previousURI));
    let hashChanged = !(previousURI && previousURI.ref == locationURI.ref);

    // When the location changes but the document is the same:
    // - path not changed and hash changed -> |onReferenceFragmentUpdated|
    //   (even if it changed using |history.pushState|)
    // - path not changed and hash not changed -> |onHistoryStateUpdated|
    //   (only if it changes using |history.pushState|)
    // - path changed -> |onHistoryStateUpdated|

    if (!pathChanged && hashChanged) {
      isReferenceFragmentUpdated = true;
    } else if (loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE) {
      isHistoryStateUpdated = true;
    } else if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
      isHistoryStateUpdated = true;
    }

    if (isHistoryStateUpdated || isReferenceFragmentUpdated) {
      let frameTransitionData = this.getFrameTransitionData({loadType, request, DOMWindow});

      let data = {
        frameTransitionData,
        isHistoryStateUpdated, isReferenceFragmentUpdated,
        location: locationURI ? locationURI.spec : "",
        frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
        parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
      };

      sendAsyncMessage("Extension:HistoryChange", data);
    }
  },

  getFrameTransitionData({loadType, request, DOMWindow}) {
    let frameTransitionData = {};

    if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
      frameTransitionData.forward_back = true;
    }

    if (loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
      frameTransitionData.reload = true;
    }

    if (request instanceof Ci.nsIChannel) {
      if (request.loadInfo.redirectChain.length) {
        frameTransitionData.server_redirect = true;
      }
    }

    if (FormSubmitListener.hasAndForget(DOMWindow)) {
      frameTransitionData.form_submit = true;
    }

    if (this.refreshAttemptedDOMWindows.has(DOMWindow)) {
      this.refreshAttemptedDOMWindows.delete(DOMWindow);
      frameTransitionData.client_redirect = true;
    }

    return frameTransitionData;
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIWebProgressListener,
    Ci.nsIWebProgressListener2,
    Ci.nsISupportsWeakReference,
  ]),
};

var disabled = false;
WebProgressListener.init();
FormSubmitListener.init();
CreatedNavigationTargetListener.init();
addEventListener("unload", () => {
  if (!disabled) {
    disabled = true;
    WebProgressListener.uninit();
    FormSubmitListener.uninit();
    CreatedNavigationTargetListener.uninit();
  }
});
addMessageListener("Extension:DisableWebNavigation", () => {
  if (!disabled) {
    disabled = true;
    WebProgressListener.uninit();
    FormSubmitListener.uninit();
    CreatedNavigationTargetListener.uninit();
  }
});
PK
!<7modules/WebNavigationFrames.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["WebNavigationFrames"];

var Ci = Components.interfaces;

/* exported WebNavigationFrames */

/**
 * Retrieve the DOMWindow associated to the docShell passed as parameter.
 *
 * @param    {nsIDocShell}  docShell - the docShell that we want to get the DOMWindow from.
 * @returns  {nsIDOMWindow}          - the DOMWindow associated to the docShell.
 */
function docShellToWindow(docShell) {
  return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIDOMWindow);
}

/**
 * The FrameDetail object which represents a frame in WebExtensions APIs.
 *
 * @typedef  {Object}  FrameDetail
 * @inner
 * @property {number}  frameId        - Represents the numeric id which identify the frame in its tab.
 * @property {number}  parentFrameId  - Represents the numeric id which identify the parent frame.
 * @property {string}  url            - Represents the current location URL loaded in the frame.
 * @property {boolean} errorOccurred  - Indicates whether an error is occurred during the last load
 *                                      happened on this frame (NOT YET SUPPORTED).
 */

/**
 * A generator function which iterates over a docShell tree, given a root docShell.
 *
 * @param   {nsIDocShell} docShell - the root docShell object
 */
function* iterateDocShellTree(docShell) {
  let docShellsEnum = docShell.getDocShellEnumerator(
    docShell.typeContent, docShell.ENUMERATE_FORWARDS);

  while (docShellsEnum.hasMoreElements()) {
    yield docShellsEnum.getNext();
  }
}

/**
 * Returns the frame ID of the given window. If the window is the
 * top-level content window, its frame ID is 0. Otherwise, its frame ID
 * is its outer window ID.
 *
 * @param {Window} window - The window to retrieve the frame ID for.
 * @returns {number}
 */
function getFrameId(window) {
  if (window.parent === window) {
    return 0;
  }

  let utils = window.getInterface(Ci.nsIDOMWindowUtils);
  return utils.outerWindowID;
}

/**
 * Returns the frame ID of the given window's parent.
 *
 * @param {Window} window - The window to retrieve the parent frame ID for.
 * @returns {number}
 */
function getParentFrameId(window) {
  if (window.parent === window) {
    return -1;
  }

  return getFrameId(window.parent);
}

function getDocShellFrameId(docShell) {
  if (!docShell) {
    return undefined;
  }

  return getFrameId(docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindow));
}

/**
 * Convert a docShell object into its internal FrameDetail representation.
 *
 * @param    {nsIDocShell} docShell - the docShell object to be converted into a FrameDetail JSON object.
 * @returns  {FrameDetail} the FrameDetail JSON object which represents the docShell.
 */
function convertDocShellToFrameDetail(docShell) {
  let window = docShellToWindow(docShell);

  return {
    frameId: getFrameId(window),
    parentFrameId: getParentFrameId(window),
    url: window.location.href,
  };
}

/**
 * Search for a frame starting from the passed root docShell and
 * convert it to its related frame detail representation.
 *
 * @param  {number}      frameId - the frame ID of the frame to retrieve, as
 *                                 described in getFrameId.
 * @param   {nsIDocShell} rootDocShell - the root docShell object
 * @returns {nsIDocShell?} the docShell with the given frameId, or null
 *                         if no match.
 */
function findDocShell(frameId, rootDocShell) {
  for (let docShell of iterateDocShellTree(rootDocShell)) {
    if (frameId == getFrameId(docShellToWindow(docShell))) {
      return docShell;
    }
  }

  return null;
}

var WebNavigationFrames = {
  iterateDocShellTree,

  findDocShell,

  getFrame(docShell, frameId) {
    let result = findDocShell(frameId, docShell);
    if (result) {
      return convertDocShellToFrameDetail(result);
    }
    return null;
  },

  getFrameId,
  getParentFrameId,

  getAllFrames(docShell) {
    return Array.from(iterateDocShellTree(docShell), convertDocShellToFrameDetail);
  },

  getDocShellFrameId,
};
PK
!<7Tp?modules/WebRequest.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["WebRequest"];

/* exported WebRequest */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

const {nsIHttpActivityObserver, nsISocketTransport} = Ci;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "NSSErrorsService",
                                   "@mozilla.org/nss_errors_service;1",
                                   "nsINSSErrorsService");

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                  "resource://gre/modules/ExtensionUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
                                  "resource://gre/modules/WebRequestCommon.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestUpload",
                                  "resource://gre/modules/WebRequestUpload.jsm");

XPCOMUtils.defineLazyGetter(this, "ExtensionError", () => ExtensionUtils.ExtensionError);

let WebRequestListener = Components.Constructor("@mozilla.org/webextensions/webRequestListener;1",
                                                "nsIWebRequestListener", "init");

function attachToChannel(channel, key, data) {
  if (channel instanceof Ci.nsIWritablePropertyBag2) {
    let wrapper = {wrappedJSObject: data};
    channel.setPropertyAsInterface(key, wrapper);
  }
  return data;
}

function extractFromChannel(channel, key) {
  if (channel instanceof Ci.nsIPropertyBag2 && channel.hasKey(key)) {
    let data = channel.get(key);
    return data && data.wrappedJSObject;
  }
  return null;
}

function getData(channel) {
  const key = "mozilla.webRequest.data";
  return extractFromChannel(channel, key) || attachToChannel(channel, key, {});
}

function getFinalChannelURI(channel) {
  let {loadInfo} = channel;
  // resultPrincipalURI may be null, but originalURI never will be.
  return (loadInfo && loadInfo.resultPrincipalURI) || channel.originalURI;
}

var RequestId = {
  count: 1,
  create(channel = null) {
    let id = (this.count++).toString();
    if (channel) {
      getData(channel).requestId = id;
    }
    return id;
  },

  get(channel) {
    return (channel && getData(channel).requestId) || this.create(channel);
  },
};

function runLater(job) {
  Services.tm.dispatchToMainThread(job);
}

function parseFilter(filter) {
  if (!filter) {
    filter = {};
  }

  // FIXME: Support windowId filtering.
  return {urls: filter.urls || null, types: filter.types || null};
}

function parseExtra(extra, allowed = []) {
  if (extra) {
    for (let ex of extra) {
      if (allowed.indexOf(ex) == -1) {
        throw new ExtensionError(`Invalid option ${ex}`);
      }
    }
  }

  let result = {};
  for (let al of allowed) {
    if (extra && extra.indexOf(al) != -1) {
      result[al] = true;
    }
  }
  return result;
}

function mergeStatus(data, channel, event) {
  try {
    data.statusCode = channel.responseStatus;
    let statusText = channel.responseStatusText;
    let maj = {};
    let min = {};
    channel.QueryInterface(Ci.nsIHttpChannelInternal).getResponseVersion(maj, min);
    data.statusLine = `HTTP/${maj.value}.${min.value} ${data.statusCode} ${statusText}`;
  } catch (e) {
    // NS_ERROR_NOT_AVAILABLE might be thrown if it's an internal redirect, happening before
    // any actual HTTP traffic. Otherwise, let's report.
    if (event !== "onRedirect" || e.result !== Cr.NS_ERROR_NOT_AVAILABLE) {
      Cu.reportError(`webRequest Error: ${e} trying to merge status in ${event}@${channel.name}`);
    }
  }
}

function isThenable(value) {
  return value && typeof value === "object" && typeof value.then === "function";
}

class HeaderChanger {
  constructor(channel) {
    this.channel = channel;

    this.originalHeaders = new Map();
    this.visitHeaders((name, value) => {
      this.originalHeaders.set(name.toLowerCase(), {name, value});
    });
  }

  toArray() {
    return Array.from(this.originalHeaders,
                      ([key, {name, value}]) => ({name, value}));
  }

  validateHeaders(headers) {
    // We should probably use schema validation for this.

    if (!Array.isArray(headers)) {
      return false;
    }

    return headers.every(header => {
      if (typeof header !== "object" || header === null) {
        return false;
      }

      if (typeof header.name !== "string") {
        return false;
      }

      return (typeof header.value === "string" ||
              Array.isArray(header.binaryValue));
    });
  }

  applyChanges(headers) {
    if (!this.validateHeaders(headers)) {
      /* globals uneval */
      Cu.reportError(`Invalid header array: ${uneval(headers)}`);
      return;
    }

    let newHeaders = new Set(headers.map(
      ({name}) => name.toLowerCase()));

    // Remove missing headers.
    for (let name of this.originalHeaders.keys()) {
      if (!newHeaders.has(name)) {
        this.setHeader(name, "");
      }
    }

    // Set new or changed headers.
    for (let {name, value, binaryValue} of headers) {
      if (binaryValue) {
        value = String.fromCharCode(...binaryValue);
      }
      let original = this.originalHeaders.get(name.toLowerCase());
      if (!original || value !== original.value) {
        this.setHeader(name, value);
      }
    }
  }
}

class RequestHeaderChanger extends HeaderChanger {
  setHeader(name, value) {
    try {
      this.channel.setRequestHeader(name, value, false);
    } catch (e) {
      Cu.reportError(new Error(`Error setting request header ${name}: ${e}`));
    }
  }

  visitHeaders(visitor) {
    if (this.channel instanceof Ci.nsIHttpChannel) {
      this.channel.visitRequestHeaders(visitor);
    }
  }
}

class ResponseHeaderChanger extends HeaderChanger {
  setHeader(name, value) {
    try {
      if (name.toLowerCase() === "content-type" && value) {
        // The Content-Type header value can't be modified, so we
        // set the channel's content type directly, instead, and
        // record that we made the change for the sake of
        // subsequent observers.
        this.channel.contentType = value;

        getData(this.channel).contentType = value;
      } else {
        this.channel.setResponseHeader(name, value, false);
      }
    } catch (e) {
      Cu.reportError(new Error(`Error setting response header ${name}: ${e}`));
    }
  }

  visitHeaders(visitor) {
    if (this.channel instanceof Ci.nsIHttpChannel) {
      try {
        this.channel.visitResponseHeaders((name, value) => {
          if (name.toLowerCase() === "content-type") {
            value = getData(this.channel).contentType || value;
          }

          visitor(name, value);
        });
      } catch (e) {
        // Throws if response headers aren't available yet.
      }
    }
  }
}

var HttpObserverManager;

var ContentPolicyManager = {
  policyData: new Map(),
  policies: new Map(),
  idMap: new Map(),
  nextId: 0,

  init() {
    Services.ppmm.initialProcessData.webRequestContentPolicies = this.policyData;

    Services.ppmm.addMessageListener("WebRequest:ShouldLoad", this);
    Services.mm.addMessageListener("WebRequest:ShouldLoad", this);
  },

  receiveMessage(msg) {
    let browser = msg.target instanceof Ci.nsIDOMXULElement ? msg.target : null;

    let requestId = RequestId.create();
    for (let id of msg.data.ids) {
      let callback = this.policies.get(id);
      if (!callback) {
        // It's possible that this listener has been removed and the
        // child hasn't learned yet.
        continue;
      }
      let response = null;
      let listenerKind = "onStop";
      let data = Object.assign({requestId, browser}, msg.data);
      delete data.ids;
      try {
        response = callback(data);
        if (response) {
          if (response.cancel) {
            listenerKind = "onError";
            data.error = "NS_ERROR_ABORT";
            return {cancel: true};
          }
          // FIXME: Need to handle redirection here (for non-HTTP URIs only)
        }
      } catch (e) {
        Cu.reportError(e);
      } finally {
        runLater(() => this.runChannelListener(listenerKind, data));
      }
    }

    return {};
  },

  runChannelListener(kind, data) {
    let listeners = HttpObserverManager.listeners[kind];
    let uri = Services.io.newURI(data.url);
    let policyType = data.type;
    for (let [callback, opts] of listeners.entries()) {
      if (!HttpObserverManager.shouldRunListener(policyType, uri, opts.filter)) {
        continue;
      }
      callback(data);
    }
  },

  addListener(callback, opts) {
    // Clone opts, since we're going to modify them for IPC.
    opts = Object.assign({}, opts);
    let id = this.nextId++;
    opts.id = id;
    if (opts.filter.urls) {
      opts.filter = Object.assign({}, opts.filter);
      opts.filter.urls = opts.filter.urls.patterns.map(url => url.pattern);
    }
    Services.ppmm.broadcastAsyncMessage("WebRequest:AddContentPolicy", opts);

    this.policyData.set(id, opts);

    this.policies.set(id, callback);
    this.idMap.set(callback, id);
  },

  removeListener(callback) {
    let id = this.idMap.get(callback);
    Services.ppmm.broadcastAsyncMessage("WebRequest:RemoveContentPolicy", {id});

    this.policyData.delete(id);
    this.idMap.delete(callback);
    this.policies.delete(id);
  },
};
ContentPolicyManager.init();

function StartStopListener(manager, channel, loadContext) {
  this.manager = manager;
  this.loadContext = loadContext;
  new WebRequestListener(this, channel);
}

StartStopListener.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
                                         Ci.nsIStreamListener]),

  onStartRequest: function(request, context) {
    this.manager.onStartRequest(request, this.loadContext);
  },

  onStopRequest(request, context, statusCode) {
    this.manager.onStopRequest(request, this.loadContext);
  },
};

var ChannelEventSink = {
  _classDescription: "WebRequest channel event sink",
  _classID: Components.ID("115062f8-92f1-11e5-8b7f-080027b0f7ec"),
  _contractID: "@mozilla.org/webrequest/channel-event-sink;1",

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink,
                                         Ci.nsIFactory]),

  init() {
    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
      .registerFactory(this._classID, this._classDescription, this._contractID, this);
  },

  register() {
    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMan.addCategoryEntry("net-channel-event-sinks", this._contractID, this._contractID, false, true);
  },

  unregister() {
    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMan.deleteCategoryEntry("net-channel-event-sinks", this._contractID, false);
  },

  // nsIChannelEventSink implementation
  asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
    runLater(() => redirectCallback.onRedirectVerifyCallback(Cr.NS_OK));
    try {
      HttpObserverManager.onChannelReplaced(oldChannel, newChannel);
    } catch (e) {
      // we don't wanna throw: it would abort the redirection
    }
  },

  // nsIFactory implementation
  createInstance(outer, iid) {
    if (outer) {
      throw Cr.NS_ERROR_NO_AGGREGATION;
    }
    return this.QueryInterface(iid);
  },
};

ChannelEventSink.init();

// nsIAuthPrompt2 implementation for onAuthRequired
class AuthRequestor {
  constructor(channel, httpObserver) {
    this.notificationCallbacks = channel.notificationCallbacks;
    this.loadGroupCallbacks = channel.loadGroup && channel.loadGroup.notificationCallbacks;
    this.httpObserver = httpObserver;
  }

  QueryInterface(iid) {
    if (iid.equals(Ci.nsISupports) ||
        iid.equals(Ci.nsIInterfaceRequestor) ||
        iid.equals(Ci.nsIAuthPromptProvider) ||
        iid.equals(Ci.nsIAuthPrompt2)) {
      return this;
    }
    try {
      return this.notificationCallbacks.QueryInterface(iid);
    } catch (e) {}
    throw Cr.NS_ERROR_NO_INTERFACE;
  }

  getInterface(iid) {
    if (iid.equals(Ci.nsIAuthPromptProvider) || iid.equals(Ci.nsIAuthPrompt2)) {
      return this;
    }
    try {
      return this.notificationCallbacks.getInterface(iid);
    } catch (e) {}
    throw Cr.NS_ERROR_NO_INTERFACE;
  }

  _getForwardedInterface(iid) {
    try {
      return this.notificationCallbacks.getInterface(iid);
    } catch (e) {
      return this.loadGroupCallbacks.getInterface(iid);
    }
  }

  // nsIAuthPromptProvider getAuthPrompt
  getAuthPrompt(reason, iid) {
    // This should never get called without getInterface having been called first.
    if (iid.equals(Ci.nsIAuthPrompt2)) {
      return this;
    }
    return this._getForwardedInterface(Ci.nsIAuthPromptProvider).getAuthPrompt(reason, iid);
  }

  // nsIAuthPrompt2 promptAuth
  promptAuth(channel, level, authInfo) {
    this._getForwardedInterface(Ci.nsIAuthPrompt2).promptAuth(channel, level, authInfo);
  }

  _getForwardPrompt(data) {
    let reason = data.isProxy ? Ci.nsIAuthPromptProvider.PROMPT_PROXY : Ci.nsIAuthPromptProvider.PROMPT_NORMAL;
    for (let callbacks of [this.notificationCallbacks, this.loadGroupCallbacks]) {
      try {
        return callbacks.getInterface(Ci.nsIAuthPromptProvider).getAuthPrompt(reason, Ci.nsIAuthPrompt2);
      } catch (e) {}
      try {
        return callbacks.getInterface(Ci.nsIAuthPrompt2);
      } catch (e) {}
    }
    throw Cr.NS_ERROR_NO_INTERFACE;
  }

  // nsIAuthPrompt2 asyncPromptAuth
  asyncPromptAuth(channel, callback, context, level, authInfo) {
    let uri = channel.URI;
    let data = {
      scheme: authInfo.authenticationScheme,
      realm: authInfo.realm,
      isProxy: !!(authInfo.flags & authInfo.AUTH_PROXY),
      challenger: {
        host: uri.host,
        port: uri.port,
      },
    };

    let channelData = getData(channel);
    // In the case that no listener provides credentials, we fallback to the
    // previously set callback class for authentication.
    channelData.authPromptForward = () => {
      try {
        let prompt = this._getForwardPrompt(data);
        prompt.asyncPromptAuth(channel, callback, context, level, authInfo);
      } catch (e) {
        Cu.reportError(`webRequest asyncPromptAuth failure ${e}`);
        callback.onAuthCancelled(context, false);
      }
      channelData.authPromptForward = null;
      channelData.authPromptCallback = null;
    };
    channelData.authPromptCallback = (authCredentials) => {
      // The API allows for canceling the request, providing credentials or
      // doing nothing, so we do not provide a way to call onAuthCanceled.
      // Canceling the request will result in canceling the authentication.
      if (authCredentials &&
          typeof authCredentials.username === "string" &&
          typeof authCredentials.password === "string") {
        authInfo.username = authCredentials.username;
        authInfo.password = authCredentials.password;
        try {
          callback.onAuthAvailable(context, authInfo);
        } catch (e) {
          Cu.reportError(`webRequest onAuthAvailable failure ${e}`);
        }
        // At least one addon has responded, so we wont forward to the regular
        // prompt handlers.
        channelData.authPromptForward = null;
        channelData.authPromptCallback = null;
      }
    };

    let loadContext = this.httpObserver.getLoadContext(channel);
    this.httpObserver.runChannelListener(channel, loadContext, "authRequired", data);

    return {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
      cancel() {
        try {
          callback.onAuthCancelled(context, false);
        } catch (e) {
          Cu.reportError(`webRequest onAuthCancelled failure ${e}`);
        }
        channelData.authPromptForward = null;
        channelData.authPromptCallback = null;
      },
    };
  }
}

HttpObserverManager = {
  modifyInitialized: false,
  examineInitialized: false,
  redirectInitialized: false,
  activityInitialized: false,
  needTracing: false,

  listeners: {
    opening: new Map(),
    modify: new Map(),
    afterModify: new Map(),
    headersReceived: new Map(),
    authRequired: new Map(),
    onRedirect: new Map(),
    onStart: new Map(),
    onError: new Map(),
    onStop: new Map(),
  },

  get activityDistributor() {
    return Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor);
  },

  addOrRemove() {
    let needModify = this.listeners.opening.size || this.listeners.modify.size || this.listeners.afterModify.size;
    if (needModify && !this.modifyInitialized) {
      this.modifyInitialized = true;
      Services.obs.addObserver(this, "http-on-modify-request");
    } else if (!needModify && this.modifyInitialized) {
      this.modifyInitialized = false;
      Services.obs.removeObserver(this, "http-on-modify-request");
    }
    this.needTracing = this.listeners.onStart.size ||
                       this.listeners.onError.size ||
                       this.listeners.onStop.size;

    let needExamine = this.needTracing ||
                      this.listeners.headersReceived.size ||
                      this.listeners.authRequired.size;

    if (needExamine && !this.examineInitialized) {
      this.examineInitialized = true;
      Services.obs.addObserver(this, "http-on-examine-response");
      Services.obs.addObserver(this, "http-on-examine-cached-response");
      Services.obs.addObserver(this, "http-on-examine-merged-response");
    } else if (!needExamine && this.examineInitialized) {
      this.examineInitialized = false;
      Services.obs.removeObserver(this, "http-on-examine-response");
      Services.obs.removeObserver(this, "http-on-examine-cached-response");
      Services.obs.removeObserver(this, "http-on-examine-merged-response");
    }

    let needRedirect = this.listeners.onRedirect.size;
    if (needRedirect && !this.redirectInitialized) {
      this.redirectInitialized = true;
      ChannelEventSink.register();
    } else if (!needRedirect && this.redirectInitialized) {
      this.redirectInitialized = false;
      ChannelEventSink.unregister();
    }

    let needActivity = this.listeners.onError.size;
    if (needActivity && !this.activityInitialized) {
      this.activityInitialized = true;
      this.activityDistributor.addObserver(this);
    } else if (!needActivity && this.activityInitialized) {
      this.activityInitialized = false;
      this.activityDistributor.removeObserver(this);
    }
  },

  addListener(kind, callback, opts) {
    this.listeners[kind].set(callback, opts);
    this.addOrRemove();
  },

  removeListener(kind, callback) {
    this.listeners[kind].delete(callback);
    this.addOrRemove();
  },

  getLoadContext(channel) {
    try {
      return channel.QueryInterface(Ci.nsIChannel)
                    .notificationCallbacks
                    .getInterface(Components.interfaces.nsILoadContext);
    } catch (e) {
      try {
        return channel.loadGroup
                      .notificationCallbacks
                      .getInterface(Components.interfaces.nsILoadContext);
      } catch (e) {
        return null;
      }
    }
  },

  observe(subject, topic, data) {
    let channel = subject.QueryInterface(Ci.nsIHttpChannel);
    switch (topic) {
      case "http-on-modify-request":
        let loadContext = this.getLoadContext(channel);

        this.runChannelListener(channel, loadContext, "opening");
        break;
      case "http-on-examine-cached-response":
      case "http-on-examine-merged-response":
        getData(channel).fromCache = true;
        // falls through
      case "http-on-examine-response":
        this.examine(channel, topic, data);
        break;
    }
  },

  // We map activity values with tentative error names, e.g. "STATUS_RESOLVING" => "NS_ERROR_NET_ON_RESOLVING".
  get activityErrorsMap() {
    let prefix = /^(?:ACTIVITY_SUBTYPE_|STATUS_)/;
    let map = new Map();
    for (let iface of [nsIHttpActivityObserver, nsISocketTransport]) {
      for (let c of Object.keys(iface).filter(name => prefix.test(name))) {
        map.set(iface[c], c.replace(prefix, "NS_ERROR_NET_ON_"));
      }
    }
    delete this.activityErrorsMap;
    this.activityErrorsMap = map;
    return this.activityErrorsMap;
  },
  GOOD_LAST_ACTIVITY: nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
  observeActivity(channel, activityType, activitySubtype /* , aTimestamp, aExtraSizeData, aExtraStringData */) {
    let channelData = getData(channel);

    // StartStopListener has to be activated early in the request to catch
    // SSL connection issues which do not get reported via nsIHttpActivityObserver.
    if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION &&
        activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
      this.attachStartStopListener(channel, channelData);
    }

    let lastActivity = channelData.lastActivity || 0;
    if (activitySubtype === nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE &&
        lastActivity && lastActivity !== this.GOOD_LAST_ACTIVITY) {
      let loadContext = this.getLoadContext(channel);
      if (!this.errorCheck(channel, loadContext, channelData)) {
        this.runChannelListener(channel, loadContext, "onError",
          {error: this.activityErrorsMap.get(lastActivity) ||
                  `NS_ERROR_NET_UNKNOWN_${lastActivity}`});
      }
    } else if (lastActivity !== this.GOOD_LAST_ACTIVITY &&
               lastActivity !== nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) {
      channelData.lastActivity = activitySubtype;
    }
  },

  shouldRunListener(policyType, uri, filter) {
    // force the protocol to be ws again.
    if (policyType == Ci.nsIContentPolicy.TYPE_WEBSOCKET && ["http", "https"].includes(uri.scheme)) {
      uri = Services.io.newURI(`ws${uri.spec.substring(4)}`);
    }
    return WebRequestCommon.typeMatches(policyType, filter.types) &&
           WebRequestCommon.urlMatches(uri, filter.urls);
  },

  get resultsMap() {
    delete this.resultsMap;
    this.resultsMap = new Map(Object.keys(Cr).map(name => [Cr[name], name]));
    return this.resultsMap;
  },
  maybeError(channel, extraData = null, channelData = null) {
    if (!(extraData && extraData.error) && channel.securityInfo) {
      let securityInfo = channel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
      if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
        let nsresult = NSSErrorsService.getXPCOMFromNSSError(securityInfo.errorCode);
        extraData = {error: NSSErrorsService.getErrorMessage(nsresult)};
      }
    }
    if (!(extraData && extraData.error)) {
      if (!Components.isSuccessCode(channel.status)) {
        extraData = {error: this.resultsMap.get(channel.status) || "NS_ERROR_NET_UNKNOWN"};
      }
    }
    return extraData;
  },
  errorCheck(channel, loadContext, channelData = null) {
    let errorData = this.maybeError(channel, null, channelData);
    if (errorData) {
      this.runChannelListener(channel, loadContext, "onError", errorData);
    }
    return errorData;
  },

  /**
   * Resumes the channel if it is currently suspended due to this
   * listener.
   *
   * @param {nsIChannel} channel
   *        The channel to possibly suspend.
   */
  maybeResume(channel) {
    let data = getData(channel);
    if (data.suspended) {
      channel.resume();
      data.suspended = false;
    }
  },

  /**
   * Suspends the channel if it is not currently suspended due to this
   * listener. Returns true if the channel was suspended as a result of
   * this call.
   *
   * @param {nsIChannel} channel
   *        The channel to possibly suspend.
   * @returns {boolean}
   *        True if this call resulted in the channel being suspended.
   */
  maybeSuspend(channel) {
    let data = getData(channel);
    if (!data.suspended) {
      channel.suspend();
      data.suspended = true;
      return true;
    }
  },

  getRequestData(channel, loadContext, policyType, extraData) {
    let {loadInfo} = channel;

    let URI = getFinalChannelURI(channel);
    let data = {
      requestId: RequestId.get(channel),
      url: URI.spec,
      method: channel.requestMethod,
      browser: loadContext && loadContext.topFrameElement,
      type: WebRequestCommon.typeForPolicyType(policyType),
      fromCache: getData(channel).fromCache,
      // Defaults for a top level request
      windowId: 0,
      parentWindowId: -1,
    };

    // force the protocol to be ws again.
    if (data.type == "websocket" && data.url.startsWith("http")) {
      data.url = `ws${data.url.substring(4)}`;
    }

    if (loadInfo) {
      let originPrincipal = loadInfo.triggeringPrincipal;
      if (originPrincipal.URI) {
        data.originUrl = originPrincipal.URI.spec;
      }
      let docPrincipal = loadInfo.loadingPrincipal;
      if (docPrincipal && docPrincipal.URI) {
        data.documentUrl = docPrincipal.URI.spec;
      }

      // If there is no loadingPrincipal, check that the request is not going to
      // inherit a system principal.  triggeringPrincipal is the context that
      // initiated the load, but is not necessarily the principal that the
      // request results in, only rely on that if no other principal is available.
      let {isSystemPrincipal} = Services.scriptSecurityManager;
      let isTopLevel = !loadInfo.loadingPrincipal && !!data.browser;
      data.isSystemPrincipal = !isTopLevel &&
                               isSystemPrincipal(loadInfo.loadingPrincipal ||
                                                 loadInfo.principalToInherit ||
                                                 loadInfo.triggeringPrincipal);

      // Handle window and parent id values for sub_frame requests or requests
      // inside a sub_frame.
      if (loadInfo.frameOuterWindowID != 0) {
        // This is a sub_frame.  Only frames (ie. iframe; a request with a frameloader)
        // have a non-zero frameOuterWindowID.  For a sub_frame, outerWindowID
        // points at the frames parent.  The parent frame is the main_frame if
        // outerWindowID == parentOuterWindowID, in which case set parentWindowId
        // to zero.
        Object.assign(data, {
          windowId: loadInfo.frameOuterWindowID,
          parentWindowId: loadInfo.outerWindowID == loadInfo.parentOuterWindowID ? 0 : loadInfo.outerWindowID,
        });
      } else if (loadInfo.outerWindowID != loadInfo.parentOuterWindowID) {
        // This is a non-frame (e.g. script, image, etc) request within a
        // sub_frame.  We have to check parentOuterWindowID against the browser
        // to see if it is the main_frame in which case the parenteWindowId
        // available to the caller must be set to zero.
        let parentMainFrame = data.browser && data.browser.outerWindowID == loadInfo.parentOuterWindowID;
        Object.assign(data, {
          windowId: loadInfo.outerWindowID,
          parentWindowId: parentMainFrame ? 0 : loadInfo.parentOuterWindowID,
        });
      }
    }

    if (channel instanceof Ci.nsIHttpChannelInternal) {
      try {
        data.ip = channel.remoteAddress;
      } catch (e) {
        // The remoteAddress getter throws if the address is unavailable,
        // but ip is an optional property so just ignore the exception.
      }
    }

    return Object.assign(data, extraData);
  },

  canModify(channel) {
    let {isHostPermitted} = AddonManagerPermissions;

    // Bug 1334550 introduced the possibility of having a JAR uri here,
    // use the result uri if possible in that case.
    let URI = getFinalChannelURI(channel);
    if (URI && isHostPermitted(URI.host)) {
      return false;
    }

    let {loadInfo} = channel;
    if (loadInfo && loadInfo.loadingPrincipal) {
      let {loadingPrincipal} = loadInfo;
      try {
        return loadingPrincipal.URI && !isHostPermitted(loadingPrincipal.URI.host);
      } catch (e) {
        // about:newtab and other non-host URIs will throw.  Those wont be in
        // the host permitted list, so we pass on the error.
      }
    }

    return true;
  },

  runChannelListener(channel, loadContext = null, kind, extraData = null) {
    let handlerResults = [];
    let requestHeaders;
    let responseHeaders;

    try {
      if (this.activityInitialized) {
        let channelData = getData(channel);
        if (kind === "onError") {
          if (channelData.errorNotified) {
            return;
          }
          channelData.errorNotified = true;
        } else if (this.errorCheck(channel, loadContext, channelData)) {
          return;
        }
      }

      let {loadInfo} = channel;
      let policyType = (loadInfo ? loadInfo.externalContentPolicyType
                                 : Ci.nsIContentPolicy.TYPE_OTHER);

      let includeStatus = (["headersReceived", "authRequired", "onRedirect", "onStart", "onStop"].includes(kind) &&
                           channel instanceof Ci.nsIHttpChannel);

      let canModify = this.canModify(channel);
      let commonData = null;
      let uri = getFinalChannelURI(channel);
      let requestBody;
      for (let [callback, opts] of this.listeners[kind].entries()) {
        if (!this.shouldRunListener(policyType, uri, opts.filter)) {
          continue;
        }

        if (!commonData) {
          commonData = this.getRequestData(channel, loadContext, policyType, extraData);
        }
        let data = Object.assign({}, commonData);

        if (opts.requestHeaders) {
          requestHeaders = requestHeaders || new RequestHeaderChanger(channel);
          data.requestHeaders = requestHeaders.toArray();
        }

        if (opts.responseHeaders) {
          responseHeaders = responseHeaders || new ResponseHeaderChanger(channel);
          data.responseHeaders = responseHeaders.toArray();
        }

        if (opts.requestBody) {
          requestBody = requestBody || WebRequestUpload.createRequestBody(channel);
          data.requestBody = requestBody;
        }

        if (includeStatus) {
          mergeStatus(data, channel, kind);
        }

        try {
          let result = callback(data);

          if (canModify && result && typeof result === "object" && opts.blocking) {
            handlerResults.push({opts, result});
          }
        } catch (e) {
          Cu.reportError(e);
        }
      }
    } catch (e) {
      Cu.reportError(e);
    }

    return this.applyChanges(kind, channel, loadContext, handlerResults,
                             requestHeaders, responseHeaders);
  },

  async applyChanges(kind, channel, loadContext, handlerResults, requestHeaders, responseHeaders) {
    let asyncHandlers = handlerResults.filter(({result}) => isThenable(result));
    let isAsync = asyncHandlers.length > 0;
    let shouldResume = false;

    try {
      if (isAsync) {
        shouldResume = this.maybeSuspend(channel);

        for (let value of asyncHandlers) {
          try {
            value.result = await value.result;
          } catch (e) {
            Cu.reportError(e);
            value.result = {};
          }
        }
      }

      for (let {opts, result} of handlerResults) {
        if (!result || typeof result !== "object") {
          continue;
        }

        if (result.cancel) {
          this.maybeResume(channel);
          channel.cancel(Cr.NS_ERROR_ABORT);

          this.errorCheck(channel, loadContext);
          return;
        }

        if (result.redirectUrl) {
          try {
            this.maybeResume(channel);

            channel.redirectTo(Services.io.newURI(result.redirectUrl));
            return;
          } catch (e) {
            Cu.reportError(e);
          }
        }

        if (opts.requestHeaders && result.requestHeaders && requestHeaders) {
          requestHeaders.applyChanges(result.requestHeaders);
        }

        if (opts.responseHeaders && result.responseHeaders && responseHeaders) {
          responseHeaders.applyChanges(result.responseHeaders);
        }

        if (kind === "authRequired" && opts.blocking && result.authCredentials) {
          let channelData = getData(channel);
          if (channelData.authPromptCallback) {
            channelData.authPromptCallback(result.authCredentials);
          }
        }
      }
      // If a listener did not cancel the request or provide credentials, we
      // forward the auth request to the base handler.
      if (kind === "authRequired") {
        let channelData = getData(channel);
        if (channelData.authPromptForward) {
          channelData.authPromptForward();
        }
      }

      if (kind === "opening") {
        await this.runChannelListener(channel, loadContext, "modify");
      } else if (kind === "modify") {
        await this.runChannelListener(channel, loadContext, "afterModify");
      }
    } catch (e) {
      Cu.reportError(e);
    }

    // Only resume the channel if it was suspended by this call.
    if (shouldResume) {
      this.maybeResume(channel);
    }
  },

  shouldHookListener(listener, channel) {
    if (listener.size == 0) {
      return false;
    }

    let {loadInfo} = channel;
    let policyType = (loadInfo ? loadInfo.externalContentPolicyType
                               : Ci.nsIContentPolicy.TYPE_OTHER);
    let uri = channel.URI;
    for (let opts of listener.values()) {
      if (this.shouldRunListener(policyType, uri, opts.filter)) {
        return true;
      }
    }
    return false;
  },

  attachStartStopListener(channel, channelData) {
    // Check whether we've already added a listener to this channel,
    // so we don't wind up chaining multiple listeners.
    if (!this.needTracing || channelData.hasListener || !(channel instanceof Ci.nsITraceableChannel)) {
      return;
    }
    let responseStatus = 0;
    try {
      responseStatus = channel.QueryInterface(Ci.nsIHttpChannel).responseStatus;
    } catch (e) {
      /* NS_ERROR_NOT_AVAILABLE if checked prior to onStartRequest. */
    }
    // skip redirections, https://bugzilla.mozilla.org/show_bug.cgi?id=728901#c8
    if (responseStatus < 300 || responseStatus >= 400) {
      let loadContext = this.getLoadContext(channel);
      new StartStopListener(this, channel, loadContext);
      channelData.hasListener = true;
    }
  },

  examine(channel, topic, data) {
    let channelData = getData(channel);
    this.attachStartStopListener(channel, channelData);

    if (this.listeners.headersReceived.size) {
      this.runChannelListener(channel, this.getLoadContext(channel), "headersReceived");
    }

    if (!channelData.hasAuthRequestor && this.shouldHookListener(this.listeners.authRequired, channel)) {
      channel.notificationCallbacks = new AuthRequestor(channel, this);
      channelData.hasAuthRequestor = true;
    }
  },

  onChannelReplaced(oldChannel, newChannel) {
    // We want originalURI, this will provide a moz-ext rather than jar or file
    // uri on redirects.
    this.runChannelListener(oldChannel, this.getLoadContext(oldChannel),
                            "onRedirect", {redirectUrl: newChannel.originalURI.spec});
  },

  onStartRequest(channel, loadContext) {
    this.runChannelListener(channel, loadContext, "onStart");
  },

  onStopRequest(channel, loadContext) {
    this.runChannelListener(channel, loadContext, "onStop");
  },
};

var onBeforeRequest = {
  allowedOptions: ["blocking", "requestBody"],

  addListener(callback, filter = null, opt_extraInfoSpec = null) {
    let opts = parseExtra(opt_extraInfoSpec, this.allowedOptions);
    opts.filter = parseFilter(filter);
    ContentPolicyManager.addListener(callback, opts);
    HttpObserverManager.addListener("opening", callback, opts);
  },

  removeListener(callback) {
    HttpObserverManager.removeListener("opening", callback);
    ContentPolicyManager.removeListener(callback);
  },
};

function HttpEvent(internalEvent, options) {
  this.internalEvent = internalEvent;
  this.options = options;
}

HttpEvent.prototype = {
  addListener(callback, filter = null, opt_extraInfoSpec = null) {
    let opts = parseExtra(opt_extraInfoSpec, this.options);
    opts.filter = parseFilter(filter);
    HttpObserverManager.addListener(this.internalEvent, callback, opts);
  },

  removeListener(callback) {
    HttpObserverManager.removeListener(this.internalEvent, callback);
  },
};

var onBeforeSendHeaders = new HttpEvent("modify", ["requestHeaders", "blocking"]);
var onSendHeaders = new HttpEvent("afterModify", ["requestHeaders"]);
var onHeadersReceived = new HttpEvent("headersReceived", ["blocking", "responseHeaders"]);
var onAuthRequired = new HttpEvent("authRequired", ["blocking", "responseHeaders"]); // TODO asyncBlocking
var onBeforeRedirect = new HttpEvent("onRedirect", ["responseHeaders"]);
var onResponseStarted = new HttpEvent("onStart", ["responseHeaders"]);
var onCompleted = new HttpEvent("onStop", ["responseHeaders"]);
var onErrorOccurred = new HttpEvent("onError");

var WebRequest = {
  // http-on-modify observer for HTTP(S), content policy for the other protocols (notably, data:)
  onBeforeRequest: onBeforeRequest,

  // http-on-modify observer.
  onBeforeSendHeaders: onBeforeSendHeaders,

  // http-on-modify observer.
  onSendHeaders: onSendHeaders,

  // http-on-examine-*observer.
  onHeadersReceived: onHeadersReceived,

  // http-on-examine-*observer.
  onAuthRequired: onAuthRequired,

  // nsIChannelEventSink.
  onBeforeRedirect: onBeforeRedirect,

  // OnStartRequest channel listener.
  onResponseStarted: onResponseStarted,

  // OnStopRequest channel listener.
  onCompleted: onCompleted,

  // nsIHttpActivityObserver.
  onErrorOccurred: onErrorOccurred,
};

Services.ppmm.loadProcessScript("resource://gre/modules/WebRequestContent.js", true);
PK
!<a"ڮmodules/WebRequestCommon.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["WebRequestCommon"];

/* exported WebRequestCommon */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

var WebRequestCommon = {
  typeForPolicyType(type) {
    switch (type) {
      case Ci.nsIContentPolicy.TYPE_DOCUMENT: return "main_frame";
      case Ci.nsIContentPolicy.TYPE_SUBDOCUMENT: return "sub_frame";
      case Ci.nsIContentPolicy.TYPE_STYLESHEET: return "stylesheet";
      case Ci.nsIContentPolicy.TYPE_SCRIPT: return "script";
      case Ci.nsIContentPolicy.TYPE_IMAGE: return "image";
      case Ci.nsIContentPolicy.TYPE_OBJECT: return "object";
      case Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST: return "object_subrequest";
      case Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST: return "xmlhttprequest";
      // TYPE_FETCH returns xmlhttprequest for cross-browser compatibility.
      case Ci.nsIContentPolicy.TYPE_FETCH: return "xmlhttprequest";
      case Ci.nsIContentPolicy.TYPE_XBL: return "xbl";
      case Ci.nsIContentPolicy.TYPE_XSLT: return "xslt";
      case Ci.nsIContentPolicy.TYPE_PING: return "ping";
      case Ci.nsIContentPolicy.TYPE_BEACON: return "beacon";
      case Ci.nsIContentPolicy.TYPE_DTD: return "xml_dtd";
      case Ci.nsIContentPolicy.TYPE_FONT: return "font";
      case Ci.nsIContentPolicy.TYPE_MEDIA: return "media";
      case Ci.nsIContentPolicy.TYPE_WEBSOCKET: return "websocket";
      case Ci.nsIContentPolicy.TYPE_CSP_REPORT: return "csp_report";
      case Ci.nsIContentPolicy.TYPE_IMAGESET: return "imageset";
      case Ci.nsIContentPolicy.TYPE_WEB_MANIFEST: return "web_manifest";
      default: return "other";
    }
  },

  typeMatches(policyType, filterTypes) {
    if (filterTypes === null) {
      return true;
    }

    return filterTypes.indexOf(this.typeForPolicyType(policyType)) != -1;
  },

  urlMatches(uri, urlFilter) {
    if (urlFilter === null) {
      return true;
    }

    return urlFilter.matches(uri);
  },
};
PK
!<vmodules/WebRequestContent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Managed via the message managers.
/* global initialProcessData */

"use strict";

var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                  "resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
                                  "resource://gre/modules/WebRequestCommon.jsm");

// Websockets will get handled via httpchannel notifications same as http
// requests, treat them the same as http in ContentPolicy.
const IS_HTTP = /^https?:|wss?:/;

var ContentPolicy = {
  _classDescription: "WebRequest content policy",
  _classID: Components.ID("938e5d24-9ccc-4b55-883e-c252a41f7ce9"),
  _contractID: "@mozilla.org/webrequest/policy;1",

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy,
                                         Ci.nsIFactory,
                                         Ci.nsISupportsWeakReference]),

  init() {
    let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);

    this.contentPolicies = new Map();
    Services.cpmm.addMessageListener("WebRequest:AddContentPolicy", this);
    Services.cpmm.addMessageListener("WebRequest:RemoveContentPolicy", this);

    if (initialProcessData && initialProcessData.webRequestContentPolicies) {
      for (let data of initialProcessData.webRequestContentPolicies.values()) {
        this.addContentPolicy(data);
      }
    }
  },

  addContentPolicy({id, blocking, filter}) {
    if (this.contentPolicies.size == 0) {
      this.register();
    }
    if (filter.urls) {
      filter.urls = new MatchPattern(filter.urls);
    }
    this.contentPolicies.set(id, {blocking, filter});
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "WebRequest:AddContentPolicy":
        this.addContentPolicy(msg.data);
        break;

      case "WebRequest:RemoveContentPolicy":
        this.contentPolicies.delete(msg.data.id);
        if (this.contentPolicies.size == 0) {
          this.unregister();
        }
        break;
    }
  },

  register() {
    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
  },

  unregister() {
    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
    catMan.deleteCategoryEntry("content-policy", this._contractID, false);
  },

  shouldLoad(policyType, contentLocation, requestOrigin,
             node, mimeTypeGuess, extra, requestPrincipal) {
    // Loads of TYPE_DOCUMENT and TYPE_SUBDOCUMENT perform a ConPol check
    // within docshell as well as within the ContentSecurityManager. To avoid
    // duplicate evaluations we ignore ConPol checks performed within docShell.
    if (extra instanceof Ci.nsISupportsString) {
      if (extra.data === "conPolCheckFromDocShell") {
        return Ci.nsIContentPolicy.ACCEPT;
      }
    }

    if (requestPrincipal &&
        Services.scriptSecurityManager.isSystemPrincipal(requestPrincipal)) {
      return Ci.nsIContentPolicy.ACCEPT;
    }
    let url = contentLocation.spec;
    if (IS_HTTP.test(url)) {
      // We'll handle this in our parent process HTTP observer.
      return Ci.nsIContentPolicy.ACCEPT;
    }

    let block = false;
    let ids = [];
    for (let [id, {blocking, filter}] of this.contentPolicies.entries()) {
      if (WebRequestCommon.typeMatches(policyType, filter.types) &&
          WebRequestCommon.urlMatches(contentLocation, filter.urls)) {
        if (blocking) {
          block = true;
        }
        ids.push(id);
      }
    }

    if (!ids.length) {
      return Ci.nsIContentPolicy.ACCEPT;
    }

    let windowId = 0;
    let parentWindowId = -1;
    let mm = Services.cpmm;

    function getWindowId(window) {
      return window.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils)
        .outerWindowID;
    }

    if (policyType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT ||
        (node instanceof Ci.nsIDOMXULElement && node.localName == "browser")) {
      // Chrome sets frameId to the ID of the sub-window. But when
      // Firefox loads an iframe, it sets |node| to the <iframe>
      // element, whose window is the parent window. We adopt the
      // Chrome behavior here.
      node = node.contentWindow;
    }

    if (node) {
      let window;
      if (node instanceof Ci.nsIDOMWindow) {
        window = node;
      } else {
        let doc;
        if (node.ownerDocument) {
          doc = node.ownerDocument;
        } else {
          doc = node;
        }
        window = doc.defaultView;
      }

      windowId = getWindowId(window);
      if (window.parent !== window) {
        parentWindowId = getWindowId(window.parent);
      }

      let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDocShell)
                     .QueryInterface(Ci.nsIInterfaceRequestor);
      try {
        // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
        mm = ir.getInterface(Ci.nsIContentFrameMessageManager);
      } catch (e) {
        if (e.result != Cr.NS_NOINTERFACE) {
          throw e;
        }
      }
    }

    let data = {ids,
                url,
                type: WebRequestCommon.typeForPolicyType(policyType),
                windowId,
                parentWindowId};
    if (requestOrigin) {
      data.originUrl = requestOrigin.spec;
    }
    if (block) {
      let rval = mm.sendSyncMessage("WebRequest:ShouldLoad", data);
      if (rval.length == 1 && rval[0].cancel) {
        return Ci.nsIContentPolicy.REJECT;
      }
    } else {
      mm.sendAsyncMessage("WebRequest:ShouldLoad", data);
    }

    return Ci.nsIContentPolicy.ACCEPT;
  },

  shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
    return Ci.nsIContentPolicy.ACCEPT;
  },

  createInstance: function(outer, iid) {
    if (outer) {
      throw Cr.NS_ERROR_NO_AGGREGATION;
    }
    return this.QueryInterface(iid);
  },
};

ContentPolicy.init();
PK
!<S=;;modules/WebRequestUpload.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["WebRequestUpload"];

/* exported WebRequestUpload */

const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;

Cu.importGlobalProperties(["TextEncoder"]);

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");

const {
  DefaultMap,
} = ExtensionUtils;

XPCOMUtils.defineLazyServiceGetter(this, "mimeHeader", "@mozilla.org/network/mime-hdrparam;1",
                                   "nsIMIMEHeaderParam");

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream",
  "setInputStream");
const ConverterInputStream = Components.Constructor(
  "@mozilla.org/intl/converter-input-stream;1", "nsIConverterInputStream",
  "init");

var WebRequestUpload;

/**
 * Parses the given raw header block, and stores the value of each
 * lower-cased header name in the resulting map.
 */
class Headers extends Map {
  constructor(headerText) {
    super();

    if (headerText) {
      this.parseHeaders(headerText);
    }
  }

  parseHeaders(headerText) {
    let lines = headerText.split("\r\n");

    let lastHeader;
    for (let line of lines) {
      // The first empty line indicates the end of the header block.
      if (line === "") {
        return;
      }

      // Lines starting with whitespace are appended to the previous
      // header.
      if (/^\s/.test(line)) {
        if (lastHeader) {
          let val = this.get(lastHeader);
          this.set(lastHeader, `${val}\r\n${line}`);
        }
        continue;
      }

      let match = /^(.*?)\s*:\s+(.*)/.exec(line);
      if (match) {
        lastHeader = match[1].toLowerCase();
        this.set(lastHeader, match[2]);
      }
    }
  }

  /**
   * If the given header exists, and contains the given parameter,
   * returns the value of that parameter.
   *
   * @param {string} name
   *        The lower-cased header name.
   * @param {string} paramName
   *        The name of the parameter to retrieve, or empty to retrieve
   *        the first (possibly unnamed) parameter.
   * @returns {string | null}
   */
  getParam(name, paramName) {
    return Headers.getParam(this.get(name), paramName);
  }

  /**
   * If the given header value is non-null, and contains the given
   * parameter, returns the value of that parameter.
   *
   * @param {string | null} header
   *        The text of the header from which to retrieve the param.
   * @param {string} paramName
   *        The name of the parameter to retrieve, or empty to retrieve
   *        the first (possibly unnamed) parameter.
   * @returns {string | null}
   */
  static getParam(header, paramName) {
    if (header) {
      // The service expects this to be a raw byte string, so convert to
      // UTF-8.
      let bytes = new TextEncoder().encode(header);
      let binHeader = String.fromCharCode(...bytes);

      return mimeHeader.getParameterHTTP(binHeader, paramName, null,
                                         false, {});
    }

    return null;
  }
}

/**
 * Creates a new Object with a corresponding property for every
 * key-value pair in the given Map.
 *
 * @param {Map} map
 *        The map to convert.
 * @returns {Object}
 */
function mapToObject(map) {
  let result = {};
  for (let [key, value] of map) {
    result[key] = value;
  }
  return result;
}

/**
 * Rewinds the given seekable input stream to its beginning, and catches
 * any resulting errors.
 *
 * @param {nsISeekableStream} stream
 *        The stream to rewind.
 */
function rewind(stream) {
  // Do this outside the try-catch so that we throw if the stream is not
  // actually seekable.
  stream.QueryInterface(Ci.nsISeekableStream);

  try {
    stream.seek(0, 0);
  } catch (e) {
    // It might be already closed, e.g. because of a previous error.
    Cu.reportError(e);
  }
}

/**
 * Iterates over all of the sub-streams that make up the given stream,
 * or yields the stream itself if it is not a multi-part stream.
 *
 * @param {nsIIMultiplexInputStream|nsIStreamBufferAccess<nsIMultiplexInputStream>|nsIInputStream} outerStream
 *        The outer stream over which to iterate.
 */
function* getStreams(outerStream) {
  // If this is a multi-part stream, we need to iterate over its sub-streams,
  // rather than treating it as a simple input stream. Since it may be wrapped
  // in a buffered input stream, unwrap it before we do any checks.
  let unbuffered = outerStream;
  if (outerStream instanceof Ci.nsIStreamBufferAccess) {
    unbuffered = outerStream.unbufferedStream;
  }

  if (unbuffered instanceof Ci.nsIMultiplexInputStream) {
    let count = unbuffered.count;
    for (let i = 0; i < count; i++) {
      yield unbuffered.getStream(i);
    }
  } else {
    yield outerStream;
  }
}

/**
 * Parses the form data of the given stream as either multipart/form-data or
 * x-www-form-urlencoded, and returns a map of its fields.
 *
 * @param {nsIInputStream} stream
 *        The input stream from which to parse the form data.
 * @param {nsIHttpChannel} channel
 *        The channel to which the stream belongs.
 * @param {boolean} [lenient = false]
 *        If true, the operation will succeed even if there are UTF-8
 *        decoding errors.
 *
 * @returns {Map<string, Array<string>> | null}
 */
function parseFormData(stream, channel, lenient = false) {
  const BUFFER_SIZE = 8192;

  let touchedStreams = new Set();

  /**
   * Creates a converter input stream from the given raw input stream,
   * and adds it to the list of streams to be rewound at the end of
   * parsing.
   *
   * Returns null if the given raw stream cannot be rewound.
   *
   * @param {nsIInputStream} stream
   *        The base stream from which to create a converter.
   * @returns {ConverterInputStream | null}
   */
  function createTextStream(stream) {
    if (!(stream instanceof Ci.nsISeekableStream)) {
      return null;
    }

    touchedStreams.add(stream);
    return ConverterInputStream(
      stream, "UTF-8", 0,
      lenient ? Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER
              : 0);
  }

  /**
   * Reads a string of no more than the given length from the given text
   * stream.
   *
   * @param {ConverterInputStream} stream
   *        The stream to read.
   * @param {integer} [length = BUFFER_SIZE]
   *        The maximum length of data to read.
   * @returns {string}
   */
  function readString(stream, length = BUFFER_SIZE) {
    let data = {};
    stream.readString(length, data);
    return data.value;
  }

  /**
   * Iterates over all of the sub-streams of the given (possibly multi-part)
   * input stream, and yields a ConverterInputStream for each
   * nsIStringInputStream among them.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
   *        The multi-part stream over which to iterate.
   */
  function* getTextStreams(outerStream) {
    for (let stream of getStreams(outerStream)) {
      if (stream instanceof Ci.nsIStringInputStream) {
        touchedStreams.add(outerStream);
        yield createTextStream(stream);
      }
    }
  }

  /**
   * Iterates over all of the string streams of the given (possibly
   * multi-part) input stream, and yields all of the available data in each as
   * chunked strings, each no more than BUFFER_SIZE in length.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
   *        The multi-part stream over which to iterate.
   */
  function* readAllStrings(outerStream) {
    for (let textStream of getTextStreams(outerStream)) {
      let str;
      while ((str = readString(textStream))) {
        yield str;
      }
    }
  }

  /**
   * Iterates over the text contents of all of the string streams in the given
   * (possibly multi-part) input stream, splits them at occurrences of the
   * given boundary string, and yields each part.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} stream
   *        The multi-part stream over which to iterate.
   * @param {string} boundary
   *        The boundary at which to split the parts.
   * @param {string} [tail = ""]
   *        Any initial data to prepend to the start of the stream data.
   */
  function* getParts(stream, boundary, tail = "") {
    for (let chunk of readAllStrings(stream)) {
      chunk = tail + chunk;

      let parts = chunk.split(boundary);
      tail = parts.pop();

      yield* parts;
    }

    if (tail) {
      yield tail;
    }
  }

  /**
   * Parses the given stream as multipart/form-data and returns a map of its fields.
   *
   * @param {nsIMultiplexInputStream|nsIInputStream} stream
   *        The (possibly multi-part) stream to parse.
   * @param {string} boundary
   *        The boundary at which to split the parts.
   * @returns {Map<string, Array<string>>}
   */
  function parseMultiPart(stream, boundary) {
    let formData = new DefaultMap(() => []);

    for (let part of getParts(stream, boundary, "\r\n")) {
      if (part === "") {
        // The first part will always be empty.
        continue;
      }
      if (part === "--\r\n") {
        // This indicates the end of the stream.
        break;
      }

      let end = part.indexOf("\r\n\r\n");

      // All valid parts must begin with \r\n, and we can't process form
      // fields without any header block.
      if (!part.startsWith("\r\n") || end <= 0) {
        throw new Error("Invalid MIME stream");
      }

      let content = part.slice(end + 4);
      let headerText = part.slice(2, end);
      let headers = new Headers(headerText);

      let name = headers.getParam("content-disposition", "name");
      if (!name || headers.getParam("content-disposition", "") !== "form-data") {
        throw new Error("Invalid MIME stream: No valid Content-Disposition header");
      }

      if (headers.has("content-type")) {
        // For file upload fields, we return the filename, rather than the
        // file data.
        let filename = headers.getParam("content-disposition", "filename");
        content = filename || "";
      }
      formData.get(name).push(content);
    }

    return formData;
  }

  /**
   * Parses the given stream as x-www-form-urlencoded, and returns a map of its fields.
   *
   * @param {nsIInputStream} stream
   *        The stream to parse.
   * @returns {Map<string, Array<string>>}
   */
  function parseUrlEncoded(stream) {
    let formData = new DefaultMap(() => []);

    for (let part of getParts(stream, "&")) {
      let [name, value] = part.replace(/\+/g, " ").split("=").map(decodeURIComponent);
      formData.get(name).push(value);
    }

    return formData;
  }

  try {
    if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
      stream = stream.data;
    }

    let contentType = channel.getRequestHeader("Content-Type");

    switch (Headers.getParam(contentType, "")) {
      case "multipart/form-data":
        let boundary = Headers.getParam(contentType, "boundary");
        return parseMultiPart(stream, `\r\n--${boundary}`);

      case "application/x-www-form-urlencoded":
        return parseUrlEncoded(stream);
    }
  } finally {
    for (let stream of touchedStreams) {
      rewind(stream);
    }
  }

  return null;
}

/**
 * Parses the form data of the given stream as either multipart/form-data or
 * x-www-form-urlencoded, and returns a map of its fields.
 *
 * Returns null if the stream is not seekable.
 *
 * @param {nsIMultiplexInputStream|nsIInputStream} stream
 *        The (possibly multi-part) stream from which to create the form data.
 * @param {nsIChannel} channel
 *        The channel to which the stream belongs.
 * @param {boolean} [lenient = false]
 *        If true, the operation will succeed even if there are UTF-8
 *        decoding errors.
 * @returns {Map<string, Array<string>> | null}
 */
function createFormData(stream, channel, lenient) {
  if (!(stream instanceof Ci.nsISeekableStream)) {
    return null;
  }

  try {
    let formData = parseFormData(stream, channel, lenient);
    if (formData) {
      return mapToObject(formData);
    }
  } catch (e) {
    Cu.reportError(e);
  } finally {
    rewind(stream);
  }
  return null;
}

/**
 * Iterates over all of the sub-streams of the given (possibly multi-part)
 * input stream, and yields an object containing the data for each chunk, up
 * to a total of `maxRead` bytes.
 *
 * @param {nsIMultiplexInputStream|nsIInputStream} outerStream
 *        The stream for which to return data.
 * @param {integer} [maxRead = WebRequestUpload.MAX_RAW_BYTES]
 *        The maximum total bytes to read.
 */
function* getRawDataChunked(outerStream, maxRead = WebRequestUpload.MAX_RAW_BYTES) {
  for (let stream of getStreams(outerStream)) {
    // We need to inspect the stream to make sure it's not a file input
    // stream. If it's wrapped in a buffered input stream, unwrap it first,
    // so we can inspect the inner stream directly.
    let unbuffered = stream;
    if (stream instanceof Ci.nsIStreamBufferAccess) {
      unbuffered = stream.unbufferedStream;
    }

    // For file fields, we return an object containing the full path of
    // the file, rather than its data.
    if (unbuffered instanceof Ci.nsIFileInputStream) {
      // But this is not actually supported yet.
      yield {file: "<file>"};
      continue;
    }

    try {
      let binaryStream = BinaryInputStream(stream);
      let available;
      while ((available = binaryStream.available())) {
        let buffer = new ArrayBuffer(Math.min(maxRead, available));
        binaryStream.readArrayBuffer(buffer.byteLength, buffer);

        maxRead -= buffer.byteLength;

        let chunk = {bytes: buffer};

        if (buffer.byteLength < available) {
          chunk.truncated = true;
          chunk.originalSize = available;
        }

        yield chunk;

        if (maxRead <= 0) {
          return;
        }
      }
    } finally {
      rewind(stream);
    }
  }
}

WebRequestUpload = {
  createRequestBody(channel) {
    if (!(channel instanceof Ci.nsIUploadChannel) || !channel.uploadStream) {
      return null;
    }

    if (channel instanceof Ci.nsIUploadChannel2 && channel.uploadStreamHasHeaders) {
      return {error: "Upload streams with headers are unsupported"};
    }

    try {
      let stream = channel.uploadStream;

      let formData = createFormData(stream, channel);
      if (formData) {
        return {formData};
      }

      // If we failed to parse the stream as form data, return it as a
      // sequence of raw data chunks, along with a leniently-parsed form
      // data object, which ignores encoding errors.
      return {
        raw: Array.from(getRawDataChunked(stream)),
        lenientFormData: createFormData(stream, channel, true),
      };
    } catch (e) {
      Cu.reportError(e);
      return {error: e.message || String(e)};
    }
  },
};

XPCOMUtils.defineLazyPreferenceGetter(WebRequestUpload, "MAX_RAW_BYTES",
                                      "webextensions.webRequest.requestBodyMaxRawBytes");
PK
!<s
s
modules/WindowDraggingUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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");

const HAVE_CSS_WINDOW_DRAG_SUPPORT = ["win", "macosx"].includes(AppConstants.platform);

this.EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];

this.WindowDraggingElement = function WindowDraggingElement(elem) {
  this._elem = elem;
  this._window = elem.ownerGlobal;
  if (HAVE_CSS_WINDOW_DRAG_SUPPORT && !this.isPanel()) {
    return;
  }

  this._elem.addEventListener("mousedown", this);
};

WindowDraggingElement.prototype = {
  mouseDownCheck(e) { return true; },
  dragTags: ["box", "hbox", "vbox", "spacer", "label", "statusbarpanel", "stack",
             "toolbaritem", "toolbarseparator", "toolbarspring", "toolbarspacer",
             "radiogroup", "deck", "scrollbox", "arrowscrollbox", "tabs"],
  shouldDrag(aEvent) {
    if (aEvent.button != 0 ||
        this._window.fullScreen ||
        !this.mouseDownCheck.call(this._elem, aEvent) ||
        aEvent.defaultPrevented)
      return false;

    let target = aEvent.originalTarget, parent = aEvent.originalTarget;

    // The target may be inside an embedded iframe or browser. (bug 615152)
    if (target.ownerGlobal != this._window)
      return false;

    while (parent != this._elem) {
      let mousethrough = parent.getAttribute("mousethrough");
      if (mousethrough == "always")
        target = parent.parentNode;
      else if (mousethrough == "never")
        break;
      parent = parent.parentNode;
    }
    while (target != this._elem) {
      if (this.dragTags.indexOf(target.localName) == -1)
        return false;
      target = target.parentNode;
    }
    return true;
  },
  isPanel() {
    return this._elem instanceof Components.interfaces.nsIDOMXULElement &&
           this._elem.localName == "panel";
  },
  handleEvent(aEvent) {
    let isPanel = this.isPanel();
    switch (aEvent.type) {
      case "mousedown":
        if (!this.shouldDrag(aEvent))
          return;

        if (/^gtk/i.test(AppConstants.MOZ_WIDGET_TOOLKIT)) {
          // On GTK, there is a toolkit-level function which handles
          // window dragging, which must be used.
          this._window.beginWindowMove(aEvent, isPanel ? this._elem : null);
          break;
        }
        if (isPanel) {
          let screenRect = this._elem.getOuterScreenRect();
          this._deltaX = aEvent.screenX - screenRect.left;
          this._deltaY = aEvent.screenY - screenRect.top;
        } else {
          this._deltaX = aEvent.screenX - this._window.screenX;
          this._deltaY = aEvent.screenY - this._window.screenY;
        }
        this._draggingWindow = true;
        this._window.addEventListener("mousemove", this);
        this._window.addEventListener("mouseup", this);
        break;
      case "mousemove":
        if (this._draggingWindow) {
          let toDrag = this.isPanel() ? this._elem : this._window;
          toDrag.moveTo(aEvent.screenX - this._deltaX, aEvent.screenY - this._deltaY);
        }
        break;
      case "mouseup":
        if (this._draggingWindow) {
          this._draggingWindow = false;
          this._window.removeEventListener("mousemove", this);
          this._window.removeEventListener("mouseup", this);
        }
        break;
    }
  }
}
PK
!<>modules/WindowsRegistry.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["WindowsRegistry"];

var WindowsRegistry = {
  /**
   * Safely reads a value from the registry.
   *
   * @param aRoot
   *        The root registry to use.
   * @param aPath
   *        The registry path to the key.
   * @param aKey
   *        The key name.
   * @param [aRegistryNode=0]
   *        Optionally set to nsIWindowsRegKey.WOW64_64 (or nsIWindowsRegKey.WOW64_32)
   *        to access a 64-bit (32-bit) key from either a 32-bit or 64-bit application.
   * @return The key value or undefined if it doesn't exist.  If the key is
   *         a REG_MULTI_SZ, an array is returned.
   */
  readRegKey(aRoot, aPath, aKey, aRegistryNode = 0) {
    const kRegMultiSz = 7;
    const kMode = Ci.nsIWindowsRegKey.ACCESS_READ | aRegistryNode;
    let registry = Cc["@mozilla.org/windows-registry-key;1"].
                   createInstance(Ci.nsIWindowsRegKey);
    try {
      registry.open(aRoot, aPath, kMode);
      if (registry.hasValue(aKey)) {
        let type = registry.getValueType(aKey);
        switch (type) {
          case kRegMultiSz:
            // nsIWindowsRegKey doesn't support REG_MULTI_SZ type out of the box.
            let str = registry.readStringValue(aKey);
            return str.split("\0").filter(v => v);
          case Ci.nsIWindowsRegKey.TYPE_STRING:
            return registry.readStringValue(aKey);
          case Ci.nsIWindowsRegKey.TYPE_INT:
            return registry.readIntValue(aKey);
          default:
            throw new Error("Unsupported registry value.");
        }
      }
    } catch (ex) {
    } finally {
      registry.close();
    }
    return undefined;
  },

  /**
   * Safely removes a key from the registry.
   *
   * @param aRoot
   *        The root registry to use.
   * @param aPath
   *        The registry path to the key.
   * @param aKey
   *        The key name.
   * @param [aRegistryNode=0]
   *        Optionally set to nsIWindowsRegKey.WOW64_64 (or nsIWindowsRegKey.WOW64_32)
   *        to access a 64-bit (32-bit) key from either a 32-bit or 64-bit application.
   * @return True if the key was removed or never existed, false otherwise.
   */
  removeRegKey(aRoot, aPath, aKey, aRegistryNode = 0) {
    let registry = Cc["@mozilla.org/windows-registry-key;1"].
                   createInstance(Ci.nsIWindowsRegKey);
    let result = false;
    try {
      let mode = Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE |
                 Ci.nsIWindowsRegKey.ACCESS_SET_VALUE |
                 aRegistryNode;
      registry.open(aRoot, aPath, mode);
      if (registry.hasValue(aKey)) {
        registry.removeValue(aKey);
        result = !registry.hasValue(aKey);
      } else {
        result = true;
      }
    } catch (ex) {
    } finally {
      registry.close();
    }
    return result;
  }
};
PK
!<SLLmodules/XPCOMUtils.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 et filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Utilities for JavaScript components loaded by the JS component
 * loader.
 *
 * Import into a JS component using
 * 'Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");'
 *
 * Exposing a JS 'class' as a component using these utility methods consists
 * of several steps:
 * 0. Import XPCOMUtils, as described above.
 * 1. Declare the 'class' (or multiple classes) implementing the component(s):
 *  function MyComponent() {
 *    // constructor
 *  }
 *  MyComponent.prototype = {
 *    // properties required for XPCOM registration:
 *    classID:          Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"),
 *
 *    // [optional] custom factory (an object implementing nsIFactory). If not
 *    // provided, the default factory is used, which returns
 *    // |(new MyComponent()).QueryInterface(iid)| in its createInstance().
 *    _xpcom_factory: { ... },
 *
 *    // QueryInterface implementation, e.g. using the generateQI helper
 *    QueryInterface: XPCOMUtils.generateQI(
 *      [Components.interfaces.nsIObserver,
 *       Components.interfaces.nsIMyInterface,
 *       "nsIFoo",
 *       "nsIBar" ]),
 *
 *    // [optional] classInfo implementation, e.g. using the generateCI helper.
 *    // Will be automatically returned from QueryInterface if that was
 *    // generated with the generateQI helper.
 *    classInfo: XPCOMUtils.generateCI(
 *      {classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"),
 *       contractID: "@example.com/xxx;1",
 *       classDescription: "unique text description",
 *       interfaces: [Components.interfaces.nsIObserver,
 *                    Components.interfaces.nsIMyInterface,
 *                    "nsIFoo",
 *                    "nsIBar"],
 *       flags: Ci.nsIClassInfo.SINGLETON}),
 *
 *    // The following properties were used prior to Mozilla 2, but are no
 *    // longer supported. They may still be included for compatibility with
 *    // prior versions of XPCOMUtils. In Mozilla 2, this information is
 *    // included in the .manifest file which registers this JS component.
 *    classDescription: "unique text description",
 *    contractID:       "@example.com/xxx;1",
 *
 *    // [optional] an array of categories to register this component in.
 *    _xpcom_categories: [{
 *      // Each object in the array specifies the parameters to pass to
 *      // nsICategoryManager.addCategoryEntry(). 'true' is passed for
 *      // both aPersist and aReplace params.
 *      category: "some-category",
 *      // optional, defaults to the object's classDescription
 *      entry: "entry name",
 *      // optional, defaults to the object's contractID (unless
 *      // 'service' is specified)
 *      value: "...",
 *      // optional, defaults to false. When set to true, and only if 'value'
 *      // is not specified, the concatenation of the string "service," and the
 *      // object's contractID is passed as aValue parameter of addCategoryEntry.
 *      service: true,
 *      // optional, it can be an array of applications' IDs in the form:
 *      // [ "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", ... ]
 *      // If defined the component will be registered in this category only for
 *      // the provided applications.
 *      apps: [...]
 *    }],
 *
 *    // ...component implementation...
 *  };
 *
 * 2. Create an array of component constructors (like the one
 * created in step 1):
 *  var components = [MyComponent];
 *
 * 3. Define the NSGetFactory entry point:
 *  this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
 */


this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

this.XPCOMUtils = {
  /**
   * Generate a QueryInterface implementation. The returned function must be
   * assigned to the 'QueryInterface' property of a JS object. When invoked on
   * that object, it checks if the given iid is listed in the |interfaces|
   * param, and if it is, returns |this| (the object it was called on).
   * If the JS object has a classInfo property it'll be returned for the
   * nsIClassInfo IID, generateCI can be used to generate the classInfo
   * property.
   */
  generateQI: function XPCU_generateQI(interfaces) {
    /* Note that Ci[Ci.x] == Ci.x for all x */
    let a = [];
    if (interfaces) {
      for (let i = 0; i < interfaces.length; i++) {
        let iface = interfaces[i];
        let name = (iface && iface.name) || String(iface);
        if (name in Ci) {
          a.push(name);
        }
      }
    }
    return makeQI(a);
  },

  /**
   * Generate a ClassInfo implementation for a component. The returned object
   * must be assigned to the 'classInfo' property of a JS object. The first and
   * only argument should be an object that contains a number of optional
   * properties: "interfaces", "contractID", "classDescription", "classID" and
   * "flags". The values of the properties will be returned as the values of the
   * various properties of the nsIClassInfo implementation.
   */
  generateCI: function XPCU_generateCI(classInfo)
  {
    if (QueryInterface in classInfo)
      throw Error("In generateCI, don't use a component for generating classInfo");
    /* Note that Ci[Ci.x] == Ci.x for all x */
    let _interfaces = [];
    for (let i = 0; i < classInfo.interfaces.length; i++) {
      let iface = classInfo.interfaces[i];
      if (Ci[iface]) {
        _interfaces.push(Ci[iface]);
      }
    }
    return {
      getInterfaces: function XPCU_getInterfaces(countRef) {
        countRef.value = _interfaces.length;
        return _interfaces;
      },
      getScriptableHelper: function XPCU_getScriptableHelper() {
        return null;
      },
      contractID: classInfo.contractID,
      classDescription: classInfo.classDescription,
      classID: classInfo.classID,
      flags: classInfo.flags,
      QueryInterface: this.generateQI([Ci.nsIClassInfo])
    };
  },

  /**
   * Generate a NSGetFactory function given an array of components.
   */
  generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) {
    let classes = {};
    for (let i = 0; i < componentsArray.length; i++) {
        let component = componentsArray[i];
        if (!(component.prototype.classID instanceof Components.ID))
          throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component);

        classes[component.prototype.classID] = this._getFactory(component);
    }
    return function NSGetFactory(cid) {
      let cidstring = cid.toString();
      if (cidstring in classes)
        return classes[cidstring];
      throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED;
    }
  },

  /**
   * Defines a getter on a specified object that will be created upon first use.
   *
   * @param aObject
   *        The object to define the lazy getter on.
   * @param aName
   *        The name of the getter to define on aObject.
   * @param aLambda
   *        A function that returns what the getter should return.  This will
   *        only ever be called once.
   */
  defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda)
  {
    Object.defineProperty(aObject, aName, {
      get: function () {
        // Redefine this accessor property as a data property.
        // Delete it first, to rule out "too much recursion" in case aObject is
        // a proxy whose defineProperty handler might unwittingly trigger this
        // getter again.
        delete aObject[aName];
        let value = aLambda.apply(aObject);
        Object.defineProperty(aObject, aName, {
          value,
          writable: true,
          configurable: true,
          enumerable: true
        });
        return value;
      },
      configurable: true,
      enumerable: true
    });
  },

  /**
   * Defines a getter on a specified object for a script.  The script will not
   * be loaded until first use.
   *
   * @param aObject
   *        The object to define the lazy getter on.
   * @param aNames
   *        The name of the getter to define on aObject for the script.
   *        This can be a string if the script exports only one symbol,
   *        or an array of strings if the script can be first accessed
   *        from several different symbols.
   * @param aResource
   *        The URL used to obtain the script.
   */
  defineLazyScriptGetter: function XPCU_defineLazyScriptGetter(aObject, aNames,
                                                               aResource)
  {
    if (!Array.isArray(aNames)) {
      aNames = [aNames];
    }
    for (let name of aNames) {
      Object.defineProperty(aObject, name, {
        get: function() {
          for (let n of aNames) {
            delete aObject[n];
          }
          Services.scriptloader.loadSubScript(aResource, aObject);
          return aObject[name];
        },
        configurable: true,
        enumerable: true
      });
    }
  },

  /**
   * Defines a getter on a specified object for a service.  The service will not
   * be obtained until first use.
   *
   * @param aObject
   *        The object to define the lazy getter on.
   * @param aName
   *        The name of the getter to define on aObject for the service.
   * @param aContract
   *        The contract used to obtain the service.
   * @param aInterfaceName
   *        The name of the interface to query the service to.
   */
  defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName,
                                                                 aContract,
                                                                 aInterfaceName)
  {
    this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
      return Cc[aContract].getService(Ci[aInterfaceName]);
    });
  },

  /**
   * Defines a getter on a specified object for a module.  The module will not
   * be imported until first use. The getter allows to execute setup and
   * teardown code (e.g.  to register/unregister to services) and accepts
   * a proxy object which acts on behalf of the module until it is imported.
   *
   * @param aObject
   *        The object to define the lazy getter on.
   * @param aName
   *        The name of the getter to define on aObject for the module.
   * @param aResource
   *        The URL used to obtain the module.
   * @param aSymbol
   *        The name of the symbol exported by the module.
   *        This parameter is optional and defaults to aName.
   * @param aPreLambda
   *        A function that is executed when the proxy is set up.
   *        This will only ever be called once.
   * @param aPostLambda
   *        A function that is executed when the module has been imported to
   *        run optional teardown procedures on the proxy object.
   *        This will only ever be called once.
   * @param aProxy
   *        An object which acts on behalf of the module to be imported until
   *        the module has been imported.
   */
  defineLazyModuleGetter: function XPCU_defineLazyModuleGetter(
                                   aObject, aName, aResource, aSymbol,
                                   aPreLambda, aPostLambda, aProxy)
  {
    let proxy = aProxy || {};

    if (typeof(aPreLambda) === "function") {
      aPreLambda.apply(proxy);
    }

    this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
      var temp = {};
      try {
        Cu.import(aResource, temp);

        if (typeof(aPostLambda) === "function") {
          aPostLambda.apply(proxy);
        }
      } catch (ex) {
        Cu.reportError("Failed to load module " + aResource + ".");
        throw ex;
      }
      return temp[aSymbol || aName];
    });
  },

  /**
   * Defines a getter on a specified object for preference value. The
   * preference is read the first time that the property is accessed,
   * and is thereafter kept up-to-date using a preference observer.
   *
   * @param aObject
   *        The object to define the lazy getter on.
   * @param aName
   *        The name of the getter property to define on aObject.
   * @param aPreference
   *        The name of the preference to read.
   * @param aDefaultValue
   *        The default value to use, if the preference is not defined.
   * @param aOnUpdate
   *        A function to call upon update. Receives as arguments
   *         `(aPreference, previousValue, newValue)`
   * @param aTransform
   *        An optional function to transform the value.  If provided,
   *        this function receives the new preference value as an argument
   *        and its return value is used by the getter.
   */
  defineLazyPreferenceGetter: function XPCU_defineLazyPreferenceGetter(
                                   aObject, aName, aPreference,
                                   aDefaultValue = null,
                                   aOnUpdate = null,
                                   aTransform = val => val)
  {
    // Note: We need to keep a reference to this observer alive as long
    // as aObject is alive. This means that all of our getters need to
    // explicitly close over the variable that holds the object, and we
    // cannot define a value in place of a getter after we read the
    // preference.
    let observer = {
      QueryInterface: this.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),

      value: undefined,

      observe(subject, topic, data) {
        if (data == aPreference) {
          if (aOnUpdate) {
            let previous = this.value;

            // Fetch and cache value.
            this.value = undefined;
            let latest = lazyGetter();
            aOnUpdate(data, previous, latest);
          } else {

            // Empty cache, next call to the getter will cause refetch.
            this.value = undefined;
          }
        }
      },
    }

    let defineGetter = get => {
      Object.defineProperty(aObject, aName, {
        configurable: true,
        enumerable: true,
        get,
      });
    };

    function lazyGetter() {
      if (observer.value === undefined) {
        let prefValue;
        switch (Services.prefs.getPrefType(aPreference)) {
          case Ci.nsIPrefBranch.PREF_STRING:
            prefValue = Services.prefs.getStringPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_INT:
            prefValue = Services.prefs.getIntPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_BOOL:
            prefValue = Services.prefs.getBoolPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_INVALID:
            prefValue = aDefaultValue;
            break;

          default:
            // This should never happen.
            throw new Error(`Error getting pref ${aPreference}; its value's type is ` +
                            `${Services.prefs.getPrefType(aPreference)}, which I don't ` +
                            `know how to handle.`);
        }

        observer.value = aTransform(prefValue);
      }
      return observer.value;
    }

    defineGetter(() => {
      Services.prefs.addObserver(aPreference, observer, true);

      defineGetter(lazyGetter);
      return lazyGetter();
    });
  },

  /**
   * Helper which iterates over a nsISimpleEnumerator.
   * @param e The nsISimpleEnumerator to iterate over.
   * @param i The expected interface for each element.
   */
  IterSimpleEnumerator: function* XPCU_IterSimpleEnumerator(e, i)
  {
    while (e.hasMoreElements())
      yield e.getNext().QueryInterface(i);
  },

  /**
   * Helper which iterates over a string enumerator.
   * @param e The string enumerator (nsIUTF8StringEnumerator or
   *          nsIStringEnumerator) over which to iterate.
   */
  IterStringEnumerator: function* XPCU_IterStringEnumerator(e)
  {
    while (e.hasMore())
      yield e.getNext();
  },

  /**
   * Helper which iterates over the entries in a category.
   * @param aCategory The name of the category over which to iterate.
   */
  enumerateCategoryEntries: function* XPCOMUtils_enumerateCategoryEntries(aCategory)
  {
    let category = this.categoryManager.enumerateCategory(aCategory);
    for (let entry of this.IterSimpleEnumerator(category, Ci.nsISupportsCString)) {
      yield [entry.data, this.categoryManager.getCategoryEntry(aCategory, entry.data)];
    }
  },

  /**
   * Returns an nsIFactory for |component|.
   */
  _getFactory: function XPCOMUtils__getFactory(component) {
    var factory = component.prototype._xpcom_factory;
    if (!factory) {
      factory = {
        createInstance: function(outer, iid) {
          if (outer)
            throw Cr.NS_ERROR_NO_AGGREGATION;
          return (new component()).QueryInterface(iid);
        },
        QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
      }
    }
    return factory;
  },

  /**
   * Allows you to fake a relative import. Expects the global object from the
   * module that's calling us, and the relative filename that we wish to import.
   */
  importRelative: function XPCOMUtils__importRelative(that, path, scope) {
    if (!("__URI__" in that))
      throw Error("importRelative may only be used from a JSM, and its first argument "+
                  "must be that JSM's global object (hint: use this)");

    Cu.importGlobalProperties(["URL"]);
    Components.utils.import(new URL(path, that.__URI__).href, scope || that);
  },

  /**
   * generates a singleton nsIFactory implementation that can be used as
   * the _xpcom_factory of the component.
   * @param aServiceConstructor
   *        Constructor function of the component.
   */
  generateSingletonFactory:
  function XPCOMUtils_generateSingletonFactory(aServiceConstructor) {
    return {
      _instance: null,
      createInstance: function XPCU_SF_createInstance(aOuter, aIID) {
        if (aOuter !== null) {
          throw Cr.NS_ERROR_NO_AGGREGATION;
        }
        if (this._instance === null) {
          this._instance = new aServiceConstructor();
        }
        return this._instance.QueryInterface(aIID);
      },
      lockFactory: function XPCU_SF_lockFactory(aDoLock) {
        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
      },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
    };
  },

  /**
   * Defines a non-writable property on an object.
   */
  defineConstant: function XPCOMUtils__defineConstant(aObj, aName, aValue) {
    Object.defineProperty(aObj, aName, {
      value: aValue,
      enumerable: true,
      writable: false
    });
  },
};

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(XPCOMUtils, "categoryManager",
                                   "@mozilla.org/categorymanager;1",
                                   "nsICategoryManager");

/**
 * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1
 */
function makeQI(interfaceNames) {
  return function XPCOMUtils_QueryInterface(iid) {
    if (iid.equals(Ci.nsISupports))
      return this;
    if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this)
      return this.classInfo;
    for (let i = 0; i < interfaceNames.length; i++) {
      if (Ci[interfaceNames[i]].equals(iid))
        return this;
    }

    throw Cr.NS_ERROR_NO_INTERFACE;
  };
}
PK
!<^yymodules/ZipUtils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = [ "ZipUtils" ];

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");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");


// The maximum amount of file data to buffer at a time during file extraction
const EXTRACTION_BUFFER               = 1024 * 512;


/**
 * Asynchronously writes data from an nsIInputStream to an OS.File instance.
 * The source stream and OS.File are closed regardless of whether the operation
 * succeeds or fails.
 * Returns a promise that will be resolved when complete.
 *
 * @param  aPath
 *         The name of the file being extracted for logging purposes.
 * @param  aStream
 *         The source nsIInputStream.
 * @param  aFile
 *         The open OS.File instance to write to.
 */
function saveStreamAsync(aPath, aStream, aFile) {
  return new Promise((resolve, reject) => {

    // Read the input stream on a background thread
    let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
              getService(Ci.nsIStreamTransportService);
    let transport = sts.createInputTransport(aStream, -1, -1, true);
    let input = transport.openInputStream(0, 0, 0)
                         .QueryInterface(Ci.nsIAsyncInputStream);
    let source = Cc["@mozilla.org/binaryinputstream;1"].
                 createInstance(Ci.nsIBinaryInputStream);
    source.setInputStream(input);


    function readFailed(error) {
      try {
        aStream.close();
      } catch (e) {
        Cu.reportError("Failed to close JAR stream for " + aPath);
      }

      aFile.close().then(function() {
        reject(error);
      }, function(e) {
        Cu.reportError("Failed to close file for " + aPath);
        reject(error);
      });
    }

    function readData() {
      try {
        let count = Math.min(source.available(), EXTRACTION_BUFFER);
        let data = new Uint8Array(count);
        source.readArrayBuffer(count, data.buffer);

        aFile.write(data, { bytes: count }).then(function() {
          input.asyncWait(readData, 0, 0, Services.tm.currentThread);
        }, readFailed);
      } catch (e) {
        if (e.result == Cr.NS_BASE_STREAM_CLOSED)
          resolve(aFile.close());
        else
          readFailed(e);
      }
    }

    input.asyncWait(readData, 0, 0, Services.tm.currentThread);

  });
}


this.ZipUtils = {

  /**
   * Asynchronously extracts files from a ZIP file into a directory.
   * Returns a promise that will be resolved when the extraction is complete.
   *
   * @param  aZipFile
   *         The source ZIP file that contains the add-on.
   * @param  aDir
   *         The nsIFile to extract to.
   */
  extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) {
    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                    createInstance(Ci.nsIZipReader);

    try {
      zipReader.open(aZipFile);
    } catch (e) {
      return Promise.reject(e);
    }

    return (async function() {
      // Get all of the entries in the zip and sort them so we create directories
      // before files
      let entries = zipReader.findEntries(null);
      let names = [];
      while (entries.hasMore())
        names.push(entries.getNext());
      names.sort();

      for (let name of names) {
        let entryName = name;
        let zipentry = zipReader.getEntry(name);
        let path = OS.Path.join(aDir.path, ...name.split("/"));

        if (zipentry.isDirectory) {
          try {
            await OS.File.makeDir(path);
          } catch (e) {
            dump("extractFilesAsync: failed to create directory " + path + "\n");
            throw e;
          }
        } else {
          let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
          try {
            let file = await OS.File.open(path, { truncate: true }, options);
            if (zipentry.realSize == 0)
              await file.close();
            else
              await saveStreamAsync(path, zipReader.getInputStream(entryName), file);
          } catch (e) {
            dump("extractFilesAsync: failed to extract file " + path + "\n");
            throw e;
          }
        }
      }

      zipReader.close();
    })().catch((e) => {
      zipReader.close();
      throw e;
    });
  },

  /**
   * Extracts files from a ZIP file into a directory.
   *
   * @param  aZipFile
   *         The source ZIP file that contains the add-on.
   * @param  aDir
   *         The nsIFile to extract to.
   */
  extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) {
    function getTargetFile(aDir, entry) {
      let target = aDir.clone();
      entry.split("/").forEach(function(aPart) {
        target.append(aPart);
      });
      return target;
    }

    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                    createInstance(Ci.nsIZipReader);
    zipReader.open(aZipFile);

    try {
      // create directories first
      let entries = zipReader.findEntries("*/");
      while (entries.hasMore()) {
        let entryName = entries.getNext();
        let target = getTargetFile(aDir, entryName);
        if (!target.exists()) {
          try {
            target.create(Ci.nsIFile.DIRECTORY_TYPE,
                          FileUtils.PERMS_DIRECTORY);
          } catch (e) {
            dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n");
          }
        }
      }

      entries = zipReader.findEntries(null);
      while (entries.hasMore()) {
        let entryName = entries.getNext();
        let target = getTargetFile(aDir, entryName);
        if (target.exists())
          continue;

        zipReader.extract(entryName, target);
        try {
          target.permissions |= FileUtils.PERMS_FILE;
        } catch (e) {
          dump("Failed to set permissions " + FileUtils.PERMS_FILE.toString(8) + " on " + target.path + " " + e + "\n");
        }
      }
    } finally {
      zipReader.close();
    }
  }

};
PK
!<@h$Azz"modules/accessibility/AccessFu.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 AccessFu */

'use strict';

const {utils: Cu, interfaces: Ci} = Components;

this.EXPORTED_SYMBOLS = ['AccessFu']; // jshint ignore:line

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/accessibility/Utils.jsm');

if (Utils.MozBuildApp === 'mobile/android') {
  Cu.import('resource://gre/modules/Messaging.jsm');
}

const ACCESSFU_DISABLE = 0; // jshint ignore:line
const ACCESSFU_ENABLE = 1;
const ACCESSFU_AUTO = 2;

const SCREENREADER_SETTING = 'accessibility.screenreader';
const QUICKNAV_MODES_PREF = 'accessibility.accessfu.quicknav_modes';
const QUICKNAV_INDEX_PREF = 'accessibility.accessfu.quicknav_index';

this.AccessFu = { // jshint ignore:line
  /**
   * Initialize chrome-layer accessibility functionality.
   * If accessibility is enabled on the platform, then a special accessibility
   * mode is started.
   */
  attach: function attach(aWindow) {
    Utils.init(aWindow);

    if (Utils.MozBuildApp === 'mobile/android') {
      EventDispatcher.instance.dispatch('Accessibility:Ready');
      EventDispatcher.instance.registerListener(this, 'Accessibility:Settings');
    }

    this._activatePref = new PrefCache(
      'accessibility.accessfu.activate', this._enableOrDisable.bind(this));

    this._enableOrDisable();
  },

  /**
   * Shut down chrome-layer accessibility functionality from the outside.
   */
  detach: function detach() {
    // Avoid disabling twice.
    if (this._enabled) {
      this._disable();
    }
    if (Utils.MozBuildApp === 'mobile/android') {
      EventDispatcher.instance.unregisterListener(this, 'Accessibility:Settings');
    }
    delete this._activatePref;
    Utils.uninit();
  },

  /**
   * A lazy getter for event handler that binds the scope to AccessFu object.
   */
  get handleEvent() {
    delete this.handleEvent;
    this.handleEvent = this._handleEvent.bind(this);
    return this.handleEvent;
  },

  /**
   * Start AccessFu mode, this primarily means controlling the virtual cursor
   * with arrow keys.
   */
  _enable: function _enable() {
    if (this._enabled) {
      return;
    }
    this._enabled = true;

    Cu.import('resource://gre/modules/accessibility/Utils.jsm');
    Cu.import('resource://gre/modules/accessibility/PointerAdapter.jsm');
    Cu.import('resource://gre/modules/accessibility/Presentation.jsm');

    for (let mm of Utils.AllMessageManagers) {
      this._addMessageListeners(mm);
      this._loadFrameScript(mm);
    }

    // Add stylesheet
    let stylesheetURL = 'chrome://global/content/accessibility/AccessFu.css';
    let stylesheet = Utils.win.document.createProcessingInstruction(
      'xml-stylesheet', 'href="' + stylesheetURL + '" type="text/css"');
    Utils.win.document.insertBefore(stylesheet, Utils.win.document.firstChild);
    this.stylesheet = Cu.getWeakReference(stylesheet);


    // Populate quicknav modes
    this._quicknavModesPref =
      new PrefCache(QUICKNAV_MODES_PREF, (aName, aValue, aFirstRun) => {
        this.Input.quickNavMode.updateModes(aValue);
        if (!aFirstRun) {
          // If the modes change, reset the current mode index to 0.
          Services.prefs.setIntPref(QUICKNAV_INDEX_PREF, 0);
        }
      }, true);

    this._quicknavCurrentModePref =
      new PrefCache(QUICKNAV_INDEX_PREF, (aName, aValue) => {
        this.Input.quickNavMode.updateCurrentMode(Number(aValue));
      }, true);

    // Check for output notification
    this._notifyOutputPref =
      new PrefCache('accessibility.accessfu.notify_output');


    this.Input.start();
    Output.start();
    PointerAdapter.start();

    if (Utils.MozBuildApp === 'mobile/android') {
      EventDispatcher.instance.registerListener(this, [
        'Accessibility:ActivateObject',
        'Accessibility:Focus',
        'Accessibility:LongPress',
        'Accessibility:MoveByGranularity',
        'Accessibility:NextObject',
        'Accessibility:PreviousObject',
        'Accessibility:ScrollBackward',
        'Accessibility:ScrollForward',
      ]);
    }

    Services.obs.addObserver(this, 'remote-browser-shown');
    Services.obs.addObserver(this, 'inprocess-browser-shown');
    Utils.win.addEventListener('TabOpen', this);
    Utils.win.addEventListener('TabClose', this);
    Utils.win.addEventListener('TabSelect', this);

    if (this.readyCallback) {
      this.readyCallback();
      delete this.readyCallback;
    }

    Logger.info('AccessFu:Enabled');
  },

  /**
   * Disable AccessFu and return to default interaction mode.
   */
  _disable: function _disable() {
    if (!this._enabled) {
      return;
    }

    this._enabled = false;

    Utils.win.document.removeChild(this.stylesheet.get());

    for (let mm of Utils.AllMessageManagers) {
      mm.sendAsyncMessage('AccessFu:Stop');
      this._removeMessageListeners(mm);
    }

    this.Input.stop();
    Output.stop();
    PointerAdapter.stop();

    Utils.win.removeEventListener('TabOpen', this);
    Utils.win.removeEventListener('TabClose', this);
    Utils.win.removeEventListener('TabSelect', this);

    Services.obs.removeObserver(this, 'remote-browser-shown');
    Services.obs.removeObserver(this, 'inprocess-browser-shown');

    if (Utils.MozBuildApp === 'mobile/android') {
      EventDispatcher.instance.unregisterListener(this, [
        'Accessibility:ActivateObject',
        'Accessibility:Focus',
        'Accessibility:LongPress',
        'Accessibility:MoveByGranularity',
        'Accessibility:NextObject',
        'Accessibility:PreviousObject',
        'Accessibility:ScrollBackward',
        'Accessibility:ScrollForward',
      ]);
    }

    delete this._quicknavModesPref;
    delete this._notifyOutputPref;

    if (this.doneCallback) {
      this.doneCallback();
      delete this.doneCallback;
    }

    Logger.info('AccessFu:Disabled');
  },

  _enableOrDisable: function _enableOrDisable() {
    try {
      if (!this._activatePref) {
        return;
      }
      let activatePref = this._activatePref.value;
      if (activatePref == ACCESSFU_ENABLE ||
          this._systemPref && activatePref == ACCESSFU_AUTO) {
        this._enable();
      } else {
        this._disable();
      }
    } catch (x) {
      dump('Error ' + x.message + ' ' + x.fileName + ':' + x.lineNumber);
    }
  },

  receiveMessage: function receiveMessage(aMessage) {
    Logger.debug(() => {
      return ['Recieved', aMessage.name, JSON.stringify(aMessage.json)];
    });

    switch (aMessage.name) {
      case 'AccessFu:Ready':
        let mm = Utils.getMessageManager(aMessage.target);
        if (this._enabled) {
          mm.sendAsyncMessage('AccessFu:Start',
                              {method: 'start', buildApp: Utils.MozBuildApp});
        }
        break;
      case 'AccessFu:Present':
        this._output(aMessage.json, aMessage.target);
        break;
      case 'AccessFu:Input':
        this.Input.setEditState(aMessage.json);
        break;
      case 'AccessFu:DoScroll':
        this.Input.doScroll(aMessage.json);
        break;
    }
  },

  _output: function _output(aPresentationData, aBrowser) {
    if (!Utils.isAliveAndVisible(
      Utils.AccService.getAccessibleFor(aBrowser))) {
      return;
    }
    for (let presenter of aPresentationData) {
      if (!presenter) {
        continue;
      }

      try {
        Output[presenter.type](presenter.details, aBrowser);
      } catch (x) {
        Logger.logException(x);
      }
    }

    if (this._notifyOutputPref.value) {
      Services.obs.notifyObservers(null, 'accessibility-output',
                                   JSON.stringify(aPresentationData));
    }
  },

  _loadFrameScript: function _loadFrameScript(aMessageManager) {
    if (this._processedMessageManagers.indexOf(aMessageManager) < 0) {
      aMessageManager.loadFrameScript(
        'chrome://global/content/accessibility/content-script.js', true);
      this._processedMessageManagers.push(aMessageManager);
    } else if (this._enabled) {
      // If the content-script is already loaded and AccessFu is enabled,
      // send an AccessFu:Start message.
      aMessageManager.sendAsyncMessage('AccessFu:Start',
        {method: 'start', buildApp: Utils.MozBuildApp});
    }
  },

  _addMessageListeners: function _addMessageListeners(aMessageManager) {
    aMessageManager.addMessageListener('AccessFu:Present', this);
    aMessageManager.addMessageListener('AccessFu:Input', this);
    aMessageManager.addMessageListener('AccessFu:Ready', this);
    aMessageManager.addMessageListener('AccessFu:DoScroll', this);
  },

  _removeMessageListeners: function _removeMessageListeners(aMessageManager) {
    aMessageManager.removeMessageListener('AccessFu:Present', this);
    aMessageManager.removeMessageListener('AccessFu:Input', this);
    aMessageManager.removeMessageListener('AccessFu:Ready', this);
    aMessageManager.removeMessageListener('AccessFu:DoScroll', this);
  },

  _handleMessageManager: function _handleMessageManager(aMessageManager) {
    if (this._enabled) {
      this._addMessageListeners(aMessageManager);
    }
    this._loadFrameScript(aMessageManager);
  },

  onEvent: function (event, data, callback) {
    switch (event) {
      case 'Accessibility:Settings':
        this._systemPref = data.enabled;
        this._enableOrDisable();
        break;
      case 'Accessibility:NextObject':
      case 'Accessibility:PreviousObject': {
        let rule = 'Simple';
        if (data && data.rule && data.rule.length) {
          rule = data.rule.substr(0, 1).toUpperCase() +
            data.rule.substr(1).toLowerCase();
        }
        let method = event.replace(/Accessibility:(\w+)Object/, 'move$1');
        this.Input.moveCursor(method, rule, 'gesture');
        break;
      }
      case 'Accessibility:ActivateObject':
        this.Input.activateCurrent(data);
        break;
      case 'Accessibility:LongPress':
        this.Input.sendContextMenuMessage();
        break;
      case 'Accessibility:ScrollForward':
        this.Input.androidScroll('forward');
        break;
      case 'Accessibility:ScrollBackward':
        this.Input.androidScroll('backward');
        break;
      case 'Accessibility:Focus':
        this._focused = data.gainFocus;
        if (this._focused) {
          this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
        }
        break;
      case 'Accessibility:MoveByGranularity':
        this.Input.moveByGranularity(data);
        break;
    }
  },

  observe: function observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case 'remote-browser-shown':
      case 'inprocess-browser-shown':
      {
        // Ignore notifications that aren't from a Browser
        let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
        if (!frameLoader.ownerIsMozBrowserFrame) {
          return;
        }
        this._handleMessageManager(frameLoader.messageManager);
        break;
      }
    }
  },

  _handleEvent: function _handleEvent(aEvent) {
    switch (aEvent.type) {
      case 'TabOpen':
      {
        let mm = Utils.getMessageManager(aEvent.target);
        this._handleMessageManager(mm);
        break;
      }
      case 'TabClose':
      {
        let mm = Utils.getMessageManager(aEvent.target);
        let mmIndex = this._processedMessageManagers.indexOf(mm);
        if (mmIndex > -1) {
          this._removeMessageListeners(mm);
          this._processedMessageManagers.splice(mmIndex, 1);
        }
        break;
      }
      case 'TabSelect':
      {
        if (this._focused) {
          // We delay this for half a second so the awesomebar could close,
          // and we could use the current coordinates for the content item.
          // XXX TODO figure out how to avoid magic wait here.
          this.autoMove({
            delay: 500,
            forcePresent: true,
            noOpIfOnScreen: true,
            moveMethod: 'moveFirst' });
        }
        break;
      }
      default:
      {
        // A settings change, it does not have an event type
        if (aEvent.settingName == SCREENREADER_SETTING) {
          this._systemPref = aEvent.settingValue;
          this._enableOrDisable();
        }
        break;
      }
    }
  },

  autoMove: function autoMove(aOptions) {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    mm.sendAsyncMessage('AccessFu:AutoMove', aOptions);
  },

  announce: function announce(aAnnouncement) {
    this._output(Presentation.announce(aAnnouncement), Utils.CurrentBrowser);
  },

  // So we don't enable/disable twice
  _enabled: false,

  // Layerview is focused
  _focused: false,

  // Keep track of message managers tha already have a 'content-script.js'
  // injected.
  _processedMessageManagers: [],

  /**
   * Adjusts the given bounds relative to the given browser.
   * @param {Rect} aJsonBounds the bounds to adjust
   * @param {browser} aBrowser the browser we want the bounds relative to
   * @param {bool} aToCSSPixels whether to convert to CSS pixels (as opposed to
   *               device pixels)
   */
  adjustContentBounds:
    function(aJsonBounds, aBrowser, aToCSSPixels) {
      let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
                            aJsonBounds.right - aJsonBounds.left,
                            aJsonBounds.bottom - aJsonBounds.top);
      let win = Utils.win;
      let dpr = win.devicePixelRatio;
      let offset = { left: -win.mozInnerScreenX, top: -win.mozInnerScreenY };

      // Add the offset; the offset is in CSS pixels, so multiply the
      // devicePixelRatio back in before adding to preserve unit consistency.
      bounds = bounds.translate(offset.left * dpr, offset.top * dpr);

      // If we want to get to CSS pixels from device pixels, this needs to be
      // further divided by the devicePixelRatio due to widget scaling.
      if (aToCSSPixels) {
        bounds = bounds.scale(1 / dpr, 1 / dpr);
      }

      return bounds.expandToIntegers();
    }
};

var Output = {
  brailleState: {
    startOffset: 0,
    endOffset: 0,
    text: '',
    selectionStart: 0,
    selectionEnd: 0,

    init: function init(aOutput) {
      if (aOutput && 'output' in aOutput) {
        this.startOffset = aOutput.startOffset;
        this.endOffset = aOutput.endOffset;
        // We need to append a space at the end so that the routing key
        // corresponding to the end of the output (i.e. the space) can be hit to
        // move the caret there.
        this.text = aOutput.output + ' ';
        this.selectionStart = typeof aOutput.selectionStart === 'number' ?
                              aOutput.selectionStart : this.selectionStart;
        this.selectionEnd = typeof aOutput.selectionEnd === 'number' ?
                            aOutput.selectionEnd : this.selectionEnd;

        return { text: this.text,
                 selectionStart: this.selectionStart,
                 selectionEnd: this.selectionEnd };
      }

      return null;
    },

    adjustText: function adjustText(aText) {
      let newBraille = [];
      let braille = {};

      let prefix = this.text.substring(0, this.startOffset).trim();
      if (prefix) {
        prefix += ' ';
        newBraille.push(prefix);
      }

      newBraille.push(aText);

      let suffix = this.text.substring(this.endOffset).trim();
      if (suffix) {
        suffix = ' ' + suffix;
        newBraille.push(suffix);
      }

      this.startOffset = braille.startOffset = prefix.length;
      this.text = braille.text = newBraille.join('') + ' ';
      this.endOffset = braille.endOffset = braille.text.length - suffix.length;
      braille.selectionStart = this.selectionStart;
      braille.selectionEnd = this.selectionEnd;

      return braille;
    },

    adjustSelection: function adjustSelection(aSelection) {
      let braille = {};

      braille.startOffset = this.startOffset;
      braille.endOffset = this.endOffset;
      braille.text = this.text;
      this.selectionStart = braille.selectionStart =
        aSelection.selectionStart + this.startOffset;
      this.selectionEnd = braille.selectionEnd =
        aSelection.selectionEnd + this.startOffset;

      return braille;
    }
  },

  start: function start() {
    Cu.import('resource://gre/modules/Geometry.jsm');
  },

  stop: function stop() {
    if (this.highlightBox) {
      let highlightBox = this.highlightBox.get();
      if (highlightBox) {
        highlightBox.remove();
      }
      delete this.highlightBox;
    }
  },

  B2G: function B2G(aDetails) {
    Utils.dispatchChromeEvent('accessibility-output', aDetails);
  },

  Visual: function Visual(aDetail, aBrowser) {
    switch (aDetail.eventType) {
      case 'viewport-change':
      case 'vc-change':
      {
        let highlightBox = null;
        if (!this.highlightBox) {
          let doc = Utils.win.document;
          // Add highlight box
          highlightBox = Utils.win.document.
            createElementNS('http://www.w3.org/1999/xhtml', 'div');
          let parent = doc.body || doc.documentElement;
          parent.appendChild(highlightBox);
          highlightBox.id = 'virtual-cursor-box';

          // Add highlight inset for inner shadow
          highlightBox.appendChild(
            doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'));

          this.highlightBox = Cu.getWeakReference(highlightBox);
        } else {
          highlightBox = this.highlightBox.get();
        }

        let padding = aDetail.padding;
        let r = AccessFu.adjustContentBounds(aDetail.bounds, aBrowser, true);

        // First hide it to avoid flickering when changing the style.
        highlightBox.classList.remove('show');
        highlightBox.style.top = (r.top - padding) + 'px';
        highlightBox.style.left = (r.left - padding) + 'px';
        highlightBox.style.width = (r.width + padding*2) + 'px';
        highlightBox.style.height = (r.height + padding*2) + 'px';
        highlightBox.classList.add('show');

        break;
      }
      case 'tabstate-change':
      {
        let highlightBox = this.highlightBox ? this.highlightBox.get() : null;
        if (highlightBox) {
          highlightBox.classList.remove('show');
        }
        break;
      }
    }
  },

  Android: function Android(aDetails, aBrowser) {
    const ANDROID_VIEW_TEXT_CHANGED = 0x10;
    const ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;

    for (let androidEvent of aDetails) {
      androidEvent.type = 'Accessibility:Event';
      if (androidEvent.bounds) {
        androidEvent.bounds = AccessFu.adjustContentBounds(
          androidEvent.bounds, aBrowser);
      }

      switch (androidEvent.eventType) {
        case ANDROID_VIEW_TEXT_CHANGED:
          androidEvent.brailleOutput = this.brailleState.adjustText(
            androidEvent.text);
          break;
        case ANDROID_VIEW_TEXT_SELECTION_CHANGED:
          androidEvent.brailleOutput = this.brailleState.adjustSelection(
            androidEvent.brailleOutput);
          break;
        default:
          androidEvent.brailleOutput = this.brailleState.init(
            androidEvent.brailleOutput);
          break;
      }

      Utils.win.WindowEventDispatcher.sendRequest(androidEvent);
    }
  },

  Braille: function Braille(aDetails) {
    Logger.debug('Braille output: ' + aDetails.output);
  }
};

var Input = {
  editState: {},

  start: function start() {
    // XXX: This is too disruptive on desktop for now.
    // Might need to add special modifiers.
    if (Utils.MozBuildApp != 'browser') {
      Utils.win.document.addEventListener('keypress', this, true);
    }
    Utils.win.addEventListener('mozAccessFuGesture', this, true);
  },

  stop: function stop() {
    if (Utils.MozBuildApp != 'browser') {
      Utils.win.document.removeEventListener('keypress', this, true);
    }
    Utils.win.removeEventListener('mozAccessFuGesture', this, true);
  },

  handleEvent: function Input_handleEvent(aEvent) {
    try {
      switch (aEvent.type) {
      case 'keypress':
        this._handleKeypress(aEvent);
        break;
      case 'mozAccessFuGesture':
        this._handleGesture(aEvent.detail);
        break;
      }
    } catch (x) {
      Logger.logException(x);
    }
  },

  _handleGesture: function _handleGesture(aGesture) {
    let gestureName = aGesture.type + aGesture.touches.length;
    Logger.debug('Gesture', aGesture.type,
                 '(fingers: ' + aGesture.touches.length + ')');

    switch (gestureName) {
      case 'dwell1':
      case 'explore1':
        this.moveToPoint('Simple', aGesture.touches[0].x,
          aGesture.touches[0].y);
        break;
      case 'doubletap1':
        this.activateCurrent();
        break;
      case 'doubletaphold1':
        Utils.dispatchChromeEvent('accessibility-control', 'quicknav-menu');
        break;
      case 'swiperight1':
        this.moveCursor('moveNext', 'Simple', 'gestures');
        break;
      case 'swipeleft1':
        this.moveCursor('movePrevious', 'Simple', 'gesture');
        break;
      case 'swipeup1':
        this.moveCursor(
          'movePrevious', this.quickNavMode.current, 'gesture', true);
        break;
      case 'swipedown1':
        this.moveCursor('moveNext', this.quickNavMode.current, 'gesture', true);
        break;
      case 'exploreend1':
      case 'dwellend1':
        this.activateCurrent(null, true);
        break;
      case 'swiperight2':
        if (aGesture.edge) {
          Utils.dispatchChromeEvent('accessibility-control',
            'edge-swipe-right');
          break;
        }
        this.sendScrollMessage(-1, true);
        break;
      case 'swipedown2':
        if (aGesture.edge) {
          Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-down');
          break;
        }
        this.sendScrollMessage(-1);
        break;
      case 'swipeleft2':
        if (aGesture.edge) {
          Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-left');
          break;
        }
        this.sendScrollMessage(1, true);
        break;
      case 'swipeup2':
        if (aGesture.edge) {
          Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-up');
          break;
        }
        this.sendScrollMessage(1);
        break;
      case 'explore2':
        Utils.CurrentBrowser.contentWindow.scrollBy(
          -aGesture.deltaX, -aGesture.deltaY);
        break;
      case 'swiperight3':
        this.moveCursor('moveNext', this.quickNavMode.current, 'gesture');
        break;
      case 'swipeleft3':
        this.moveCursor('movePrevious', this.quickNavMode.current, 'gesture');
        break;
      case 'swipedown3':
        this.quickNavMode.next();
        AccessFu.announce('quicknav_' + this.quickNavMode.current);
        break;
      case 'swipeup3':
        this.quickNavMode.previous();
        AccessFu.announce('quicknav_' + this.quickNavMode.current);
        break;
      case 'tripletap3':
        Utils.dispatchChromeEvent('accessibility-control', 'toggle-shade');
        break;
      case 'tap2':
        Utils.dispatchChromeEvent('accessibility-control', 'toggle-pause');
        break;
    }
  },

  _handleKeypress: function _handleKeypress(aEvent) {
    let target = aEvent.target;

    // Ignore keys with modifiers so the content could take advantage of them.
    if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
      return;
    }

    switch (aEvent.keyCode) {
      case 0:
        // an alphanumeric key was pressed, handle it separately.
        // If it was pressed with either alt or ctrl, just pass through.
        // If it was pressed with meta, pass the key on without the meta.
        if (this.editState.editing) {
          return;
        }

        let key = String.fromCharCode(aEvent.charCode);
        try {
          let [methodName, rule] = this.keyMap[key];
          this.moveCursor(methodName, rule, 'keyboard');
        } catch (x) {
          return;
        }
        break;
      case aEvent.DOM_VK_RIGHT:
        if (this.editState.editing) {
          if (!this.editState.atEnd) {
            // Don't move forward if caret is not at end of entry.
            // XXX: Fix for rtl
            return;
          } else {
            target.blur();
          }
        }
        this.moveCursor(aEvent.shiftKey ?
          'moveLast' : 'moveNext', 'Simple', 'keyboard');
        break;
      case aEvent.DOM_VK_LEFT:
        if (this.editState.editing) {
          if (!this.editState.atStart) {
            // Don't move backward if caret is not at start of entry.
            // XXX: Fix for rtl
            return;
          } else {
            target.blur();
          }
        }
        this.moveCursor(aEvent.shiftKey ?
          'moveFirst' : 'movePrevious', 'Simple', 'keyboard');
        break;
      case aEvent.DOM_VK_UP:
        if (this.editState.multiline) {
          if (!this.editState.atStart) {
            // Don't blur content if caret is not at start of text area.
            return;
          } else {
            target.blur();
          }
        }

        if (Utils.MozBuildApp == 'mobile/android') {
          // Return focus to native Android browser chrome.
          Utils.win.WindowEventDispatcher.dispatch('ToggleChrome:Focus');
        }
        break;
      case aEvent.DOM_VK_RETURN:
        if (this.editState.editing) {
          return;
        }
        this.activateCurrent();
        break;
    default:
      return;
    }

    aEvent.preventDefault();
    aEvent.stopPropagation();
  },

  moveToPoint: function moveToPoint(aRule, aX, aY) {
    // XXX: Bug 1013408 - There is no alignment between the chrome window's
    // viewport size and the content viewport size in Android. This makes
    // sending mouse events beyond its bounds impossible.
    if (Utils.MozBuildApp === 'mobile/android') {
      let mm = Utils.getMessageManager(Utils.CurrentBrowser);
      mm.sendAsyncMessage('AccessFu:MoveToPoint',
        {rule: aRule, x: aX, y: aY, origin: 'top'});
    } else {
      let win = Utils.win;
      Utils.winUtils.sendMouseEvent('mousemove',
        aX - win.mozInnerScreenX, aY - win.mozInnerScreenY, 0, 0, 0);
    }
  },

  moveCursor: function moveCursor(aAction, aRule, aInputType, aAdjustRange) {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    mm.sendAsyncMessage('AccessFu:MoveCursor',
                        { action: aAction, rule: aRule,
                          origin: 'top', inputType: aInputType,
                          adjustRange: aAdjustRange });
  },

  androidScroll: function androidScroll(aDirection) {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    mm.sendAsyncMessage('AccessFu:AndroidScroll',
                        { direction: aDirection, origin: 'top' });
  },

  moveByGranularity: function moveByGranularity(aDetails) {
    const GRANULARITY_PARAGRAPH = 8;
    const GRANULARITY_LINE = 4;

    if (!this.editState.editing) {
      if (aDetails.granularity & (GRANULARITY_PARAGRAPH | GRANULARITY_LINE)) {
        this.moveCursor('move' + aDetails.direction, 'Simple', 'gesture');
        return;
      }
    } else {
      aDetails.atStart = this.editState.atStart;
      aDetails.atEnd = this.editState.atEnd;
    }

    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    let type = this.editState.editing ? 'AccessFu:MoveCaret' :
                                        'AccessFu:MoveByGranularity';
    mm.sendAsyncMessage(type, aDetails);
  },

  activateCurrent: function activateCurrent(aData, aActivateIfKey = false) {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    let offset = aData && typeof aData.keyIndex === 'number' ?
                 aData.keyIndex - Output.brailleState.startOffset : -1;

    mm.sendAsyncMessage('AccessFu:Activate',
                        {offset: offset, activateIfKey: aActivateIfKey});
  },

  sendContextMenuMessage: function sendContextMenuMessage() {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    mm.sendAsyncMessage('AccessFu:ContextMenu', {});
  },

  setEditState: function setEditState(aEditState) {
    Logger.debug(() => { return ['setEditState', JSON.stringify(aEditState)] });
    this.editState = aEditState;
  },

  // XXX: This is here for backwards compatability with screen reader simulator
  // it should be removed when the extension is updated on amo.
  scroll: function scroll(aPage, aHorizontal) {
    this.sendScrollMessage(aPage, aHorizontal);
  },

  sendScrollMessage: function sendScrollMessage(aPage, aHorizontal) {
    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
    mm.sendAsyncMessage('AccessFu:Scroll',
      {page: aPage, horizontal: aHorizontal, origin: 'top'});
  },

  doScroll: function doScroll(aDetails) {
    let horizontal = aDetails.horizontal;
    let page = aDetails.page;
    let p = AccessFu.adjustContentBounds(
      aDetails.bounds, Utils.CurrentBrowser, true).center();
    Utils.winUtils.sendWheelEvent(p.x, p.y,
      horizontal ? page : 0, horizontal ? 0 : page, 0,
      Utils.win.WheelEvent.DOM_DELTA_PAGE, 0, 0, 0, 0);
  },

  get keyMap() {
    delete this.keyMap;
    this.keyMap = {
      a: ['moveNext', 'Anchor'],
      A: ['movePrevious', 'Anchor'],
      b: ['moveNext', 'Button'],
      B: ['movePrevious', 'Button'],
      c: ['moveNext', 'Combobox'],
      C: ['movePrevious', 'Combobox'],
      d: ['moveNext', 'Landmark'],
      D: ['movePrevious', 'Landmark'],
      e: ['moveNext', 'Entry'],
      E: ['movePrevious', 'Entry'],
      f: ['moveNext', 'FormElement'],
      F: ['movePrevious', 'FormElement'],
      g: ['moveNext', 'Graphic'],
      G: ['movePrevious', 'Graphic'],
      h: ['moveNext', 'Heading'],
      H: ['movePrevious', 'Heading'],
      i: ['moveNext', 'ListItem'],
      I: ['movePrevious', 'ListItem'],
      k: ['moveNext', 'Link'],
      K: ['movePrevious', 'Link'],
      l: ['moveNext', 'List'],
      L: ['movePrevious', 'List'],
      p: ['moveNext', 'PageTab'],
      P: ['movePrevious', 'PageTab'],
      r: ['moveNext', 'RadioButton'],
      R: ['movePrevious', 'RadioButton'],
      s: ['moveNext', 'Separator'],
      S: ['movePrevious', 'Separator'],
      t: ['moveNext', 'Table'],
      T: ['movePrevious', 'Table'],
      x: ['moveNext', 'Checkbox'],
      X: ['movePrevious', 'Checkbox']
    };

    return this.keyMap;
  },

  quickNavMode: {
    get current() {
      return this.modes[this._currentIndex];
    },

    previous: function quickNavMode_previous() {
      Services.prefs.setIntPref(QUICKNAV_INDEX_PREF,
        this._currentIndex > 0 ?
          this._currentIndex - 1 : this.modes.length - 1);
    },

    next: function quickNavMode_next() {
      Services.prefs.setIntPref(QUICKNAV_INDEX_PREF,
        this._currentIndex + 1 >= this.modes.length ?
          0 : this._currentIndex + 1);
    },

    updateModes: function updateModes(aModes) {
      if (aModes) {
        this.modes = aModes.split(',');
      } else {
        this.modes = [];
      }
    },

    updateCurrentMode: function updateCurrentMode(aModeIndex) {
      Logger.debug('Quicknav mode:', this.modes[aModeIndex]);
      this._currentIndex = aModeIndex;
    }
  }
};
AccessFu.Input = Input;
PK
!<_|((#modules/accessibility/Constants.jsmconst Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');

this.EXPORTED_SYMBOLS = ['Roles', 'Events', 'Relations',
                         'Filters', 'States', 'Prefilters'];

function ConstantsMap (aObject, aPrefix, aMap = {}, aModifier = null) {
  let offset = aPrefix.length;
  for (var name in aObject) {
    if (name.indexOf(aPrefix) === 0) {
      aMap[name.slice(offset)] = aModifier ?
        aModifier(aObject[name]) : aObject[name];
    }
  }

  return aMap;
}

XPCOMUtils.defineLazyGetter(
  this, 'Roles',
  function() {
    return ConstantsMap(Ci.nsIAccessibleRole, 'ROLE_');
  });

XPCOMUtils.defineLazyGetter(
  this, 'Events',
  function() {
    return ConstantsMap(Ci.nsIAccessibleEvent, 'EVENT_');
  });

XPCOMUtils.defineLazyGetter(
  this, 'Relations',
  function() {
    return ConstantsMap(Ci.nsIAccessibleRelation, 'RELATION_');
  });

XPCOMUtils.defineLazyGetter(
  this, 'Prefilters',
  function() {
    return ConstantsMap(Ci.nsIAccessibleTraversalRule, 'PREFILTER_');
  });

XPCOMUtils.defineLazyGetter(
  this, 'Filters',
  function() {
    return ConstantsMap(Ci.nsIAccessibleTraversalRule, 'FILTER_');
  });

XPCOMUtils.defineLazyGetter(
  this, 'States',
  function() {
    let statesMap = ConstantsMap(Ci.nsIAccessibleStates, 'STATE_', {},
                                 (val) => { return { base: val, extended: 0 }; });
    ConstantsMap(Ci.nsIAccessibleStates, 'EXT_STATE_', statesMap,
                 (val) => { return { base: 0, extended: val }; });
    return statesMap;
  });
PK
!<a[E[E(modules/accessibility/ContentControl.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
  'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
  'resource://gre/modules/accessibility/Traversal.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'TraversalHelper',
  'resource://gre/modules/accessibility/Traversal.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
  'resource://gre/modules/accessibility/Presentation.jsm');

this.EXPORTED_SYMBOLS = ['ContentControl'];

const MOVEMENT_GRANULARITY_CHARACTER = 1;
const MOVEMENT_GRANULARITY_WORD = 2;
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;

this.ContentControl = function ContentControl(aContentScope) {
  this._contentScope = Cu.getWeakReference(aContentScope);
  this._childMessageSenders = new WeakMap();
};

this.ContentControl.prototype = {
  messagesOfInterest: ['AccessFu:MoveCursor',
                       'AccessFu:ClearCursor',
                       'AccessFu:MoveToPoint',
                       'AccessFu:AutoMove',
                       'AccessFu:Activate',
                       'AccessFu:MoveCaret',
                       'AccessFu:MoveByGranularity',
                       'AccessFu:AndroidScroll'],

  start: function cc_start() {
    let cs = this._contentScope.get();
    for (let message of this.messagesOfInterest) {
      cs.addMessageListener(message, this);
    }
    cs.addEventListener('mousemove', this);
  },

  stop: function cc_stop() {
    let cs = this._contentScope.get();
    for (let message of this.messagesOfInterest) {
      cs.removeMessageListener(message, this);
    }
    cs.removeEventListener('mousemove', this);
  },

  get document() {
    return this._contentScope.get().content.document;
  },

  get window() {
    return this._contentScope.get().content;
  },

  get vc() {
    return Utils.getVirtualCursor(this.document);
  },

  receiveMessage: function cc_receiveMessage(aMessage) {
    Logger.debug(() => {
      return ['ContentControl.receiveMessage',
        aMessage.name,
        JSON.stringify(aMessage.json)];
    });

    // If we get an explicit message, we should immediately cancel any autoMove
    this.cancelAutoMove();

    try {
      let func = this['handle' + aMessage.name.slice(9)]; // 'AccessFu:'.length
      if (func) {
        func.bind(this)(aMessage);
      } else {
        Logger.warning('ContentControl: Unhandled message:', aMessage.name);
      }
    } catch (x) {
      Logger.logException(
        x, 'Error handling message: ' + JSON.stringify(aMessage.json));
    }
  },

  handleAndroidScroll: function cc_handleAndroidScroll(aMessage) {
    let vc = this.vc;
    let position = vc.position;

    if (aMessage.json.origin != 'child' && this.sendToChild(vc, aMessage)) {
      // Forwarded succesfully to child cursor.
      return;
    }

    // Counter-intuitive, but scrolling backward (ie. up), actually should
    // increase range values.
    if (this.adjustRange(position, aMessage.json.direction === 'backward')) {
      return;
    }

    this._contentScope.get().sendAsyncMessage('AccessFu:DoScroll',
      { bounds: Utils.getBounds(position, true),
        page: aMessage.json.direction === 'forward' ? 1 : -1,
        horizontal: false });
  },

  handleMoveCursor: function cc_handleMoveCursor(aMessage) {
    let origin = aMessage.json.origin;
    let action = aMessage.json.action;
    let adjustRange = aMessage.json.adjustRange;
    let vc = this.vc;

    if (origin != 'child' && this.sendToChild(vc, aMessage)) {
      // Forwarded succesfully to child cursor.
      return;
    }

    if (adjustRange && this.adjustRange(vc.position, action === 'moveNext')) {
      return;
    }

    let moved = TraversalHelper.move(vc, action, aMessage.json.rule);

    if (moved) {
      if (origin === 'child') {
        // We just stepped out of a child, clear child cursor.
        Utils.getMessageManager(aMessage.target).sendAsyncMessage(
          'AccessFu:ClearCursor', {});
      } else {
        // We potentially landed on a new child cursor. If so, we want to
        // either be on the first or last item in the child doc.
        let childAction = action;
        if (action === 'moveNext') {
          childAction = 'moveFirst';
        } else if (action === 'movePrevious') {
          childAction = 'moveLast';
        }

        // Attempt to forward move to a potential child cursor in our
        // new position.
        this.sendToChild(vc, aMessage, { action: childAction }, true);
      }
    } else if (!this._childMessageSenders.has(aMessage.target) &&
               origin !== 'top') {
      // We failed to move, and the message is not from a parent, so forward
      // to it.
      this.sendToParent(aMessage);
    } else {
      this._contentScope.get().sendAsyncMessage('AccessFu:Present',
        Presentation.noMove(action));
    }
  },

  handleEvent: function cc_handleEvent(aEvent) {
    if (aEvent.type === 'mousemove') {
      this.handleMoveToPoint(
        { json: { x: aEvent.screenX, y: aEvent.screenY, rule: 'Simple' } });
    }
    if (!Utils.getMessageManager(aEvent.target)) {
      aEvent.preventDefault();
    } else {
      aEvent.target.focus();
    }
  },

  handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
    let [x, y] = [aMessage.json.x, aMessage.json.y];
    let rule = TraversalRules[aMessage.json.rule];

    let dpr = this.window.devicePixelRatio;
    this.vc.moveToPoint(rule, x * dpr, y * dpr, true);
  },

  handleClearCursor: function cc_handleClearCursor(aMessage) {
    let forwarded = this.sendToChild(this.vc, aMessage);
    this.vc.position = null;
    if (!forwarded) {
      this._contentScope.get().sendAsyncMessage('AccessFu:CursorCleared');
    }
    this.document.activeElement.blur();
  },

  handleAutoMove: function cc_handleAutoMove(aMessage) {
    this.autoMove(null, aMessage.json);
  },

  handleActivate: function cc_handleActivate(aMessage) {
    let activateAccessible = (aAccessible) => {
      Logger.debug(() => {
        return ['activateAccessible', Logger.accessibleToString(aAccessible)];
      });
      try {
        if (aMessage.json.activateIfKey &&
          !Utils.isActivatableOnFingerUp(aAccessible)) {
          // Only activate keys, don't do anything on other objects.
          return;
        }
      } catch (e) {
        // accessible is invalid. Silently fail.
        return;
      }

      if (aAccessible.actionCount > 0) {
        aAccessible.doAction(0);
      } else {
        let control = Utils.getEmbeddedControl(aAccessible);
        if (control && control.actionCount > 0) {
          control.doAction(0);
        }

        // XXX Some mobile widget sets do not expose actions properly
        // (via ARIA roles, etc.), so we need to generate a click.
        // Could possibly be made simpler in the future. Maybe core
        // engine could expose nsCoreUtiles::DispatchMouseEvent()?
        let docAcc = Utils.AccService.getAccessibleFor(this.document);
        let docX = {}, docY = {}, docW = {}, docH = {};
        docAcc.getBounds(docX, docY, docW, docH);

        let objX = {}, objY = {}, objW = {}, objH = {};
        aAccessible.getBounds(objX, objY, objW, objH);

        let x = Math.round((objX.value - docX.value) + objW.value / 2);
        let y = Math.round((objY.value - docY.value) + objH.value / 2);

        let node = aAccessible.DOMNode || aAccessible.parent.DOMNode;

        for (let eventType of ['mousedown', 'mouseup']) {
          let evt = this.document.createEvent('MouseEvents');
          evt.initMouseEvent(eventType, true, true, this.window,
            x, y, 0, 0, 0, false, false, false, false, 0, null);
          node.dispatchEvent(evt);
        }
      }

      if (!Utils.isActivatableOnFingerUp(aAccessible)) {
        // Keys will typically have a sound of their own.
        this._contentScope.get().sendAsyncMessage('AccessFu:Present',
          Presentation.actionInvoked(aAccessible, 'click'));
      }
    };

    let focusedAcc = Utils.AccService.getAccessibleFor(
      this.document.activeElement);
    if (focusedAcc && this.vc.position === focusedAcc
        && focusedAcc.role === Roles.ENTRY) {
      let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
      let oldOffset = accText.caretOffset;
      let newOffset = aMessage.json.offset;
      let text = accText.getText(0, accText.characterCount);

      if (newOffset >= 0 && newOffset <= accText.characterCount) {
        accText.caretOffset = newOffset;
      }

      this.presentCaretChange(text, oldOffset, accText.caretOffset);
      return;
    }

    // recursively find a descendant that is activatable.
    let getActivatableDescendant = (aAccessible) => {
      if (aAccessible.actionCount > 0) {
        return aAccessible;
      }

      for (let acc = aAccessible.firstChild; acc; acc = acc.nextSibling) {
        let activatable = getActivatableDescendant(acc);
        if (activatable) {
          return activatable;
        }
      }

      return null;
    };

    let vc = this.vc;
    if (!this.sendToChild(vc, aMessage, null, true)) {
      let position = vc.position;
      activateAccessible(getActivatableDescendant(position) || position);
    }
  },

  adjustRange: function cc_adjustRange(aAccessible, aStepUp) {
    let acc = Utils.getEmbeddedControl(aAccessible) || aAccessible;
    try {
      acc.QueryInterface(Ci.nsIAccessibleValue);
    } catch (x) {
      // This is not an adjustable, return false.
      return false;
    }

    let elem = acc.DOMNode;
    if (!elem) {
      return false;
    }

    if (elem.tagName === 'INPUT' && elem.type === 'range') {
      elem[aStepUp ? 'stepDown' : 'stepUp']();
      let evt = this.document.createEvent('UIEvent');
      evt.initEvent('change', true, true);
      elem.dispatchEvent(evt);
    } else {
      let evt = this.document.createEvent('KeyboardEvent');
      let keycode = aStepUp ? evt.DOM_VK_DOWN : evt.DOM_VK_UP;
      evt.initKeyEvent(
        "keypress", false, true, null, false, false, false, false, keycode, 0);
      elem.dispatchEvent(evt);
    }

    return true;
  },

  handleMoveByGranularity: function cc_handleMoveByGranularity(aMessage) {
    // XXX: Add sendToChild. Right now this is only used in Android, so no need.
    let direction = aMessage.json.direction;
    let granularity;

    switch (aMessage.json.granularity) {
      case MOVEMENT_GRANULARITY_CHARACTER:
        granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
        break;
      case MOVEMENT_GRANULARITY_WORD:
        granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
        break;
      default:
        return;
    }

    if (direction === 'Previous') {
      this.vc.movePreviousByText(granularity);
    } else if (direction === 'Next') {
      this.vc.moveNextByText(granularity);
    }
  },

  presentCaretChange: function cc_presentCaretChange(
    aText, aOldOffset, aNewOffset) {
    if (aOldOffset !== aNewOffset) {
      let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
        aOldOffset, aOldOffset, true);
      this._contentScope.get().sendAsyncMessage('AccessFu:Present', msg);
    }
  },

  handleMoveCaret: function cc_handleMoveCaret(aMessage) {
    let direction = aMessage.json.direction;
    let granularity = aMessage.json.granularity;
    let accessible = this.vc.position;
    let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
    let oldOffset = accText.caretOffset;
    let text = accText.getText(0, accText.characterCount);

    let start = {}, end = {};
    if (direction === 'Previous' && !aMessage.json.atStart) {
      switch (granularity) {
        case MOVEMENT_GRANULARITY_CHARACTER:
          accText.caretOffset--;
          break;
        case MOVEMENT_GRANULARITY_WORD:
          accText.getTextBeforeOffset(accText.caretOffset,
            Ci.nsIAccessibleText.BOUNDARY_WORD_START, start, end);
          accText.caretOffset = end.value === accText.caretOffset ?
            start.value : end.value;
          break;
        case MOVEMENT_GRANULARITY_PARAGRAPH:
          let startOfParagraph = text.lastIndexOf('\n', accText.caretOffset - 1);
          accText.caretOffset = startOfParagraph !== -1 ? startOfParagraph : 0;
          break;
      }
    } else if (direction === 'Next' && !aMessage.json.atEnd) {
      switch (granularity) {
        case MOVEMENT_GRANULARITY_CHARACTER:
          accText.caretOffset++;
          break;
        case MOVEMENT_GRANULARITY_WORD:
          accText.getTextAtOffset(accText.caretOffset,
                                  Ci.nsIAccessibleText.BOUNDARY_WORD_END, start, end);
          accText.caretOffset = end.value;
          break;
        case MOVEMENT_GRANULARITY_PARAGRAPH:
          accText.caretOffset = text.indexOf('\n', accText.caretOffset + 1);
          break;
      }
    }

    this.presentCaretChange(text, oldOffset, accText.caretOffset);
  },

  getChildCursor: function cc_getChildCursor(aAccessible) {
    let acc = aAccessible || this.vc.position;
    if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
      let domNode = acc.DOMNode;
      let mm = this._childMessageSenders.get(domNode, null);
      if (!mm) {
        mm = Utils.getMessageManager(domNode);
        mm.addWeakMessageListener('AccessFu:MoveCursor', this);
        this._childMessageSenders.set(domNode, mm);
      }

      return mm;
    }

    return null;
  },

  sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer,
                                       aFocus) {
    let position = aVirtualCursor.position;
    let mm = this.getChildCursor(position);
    if (!mm) {
      return false;
    }

    if (aFocus) {
      position.takeFocus();
    }

    // XXX: This is a silly way to make a deep copy
    let newJSON = JSON.parse(JSON.stringify(aMessage.json));
    newJSON.origin = 'parent';
    for (let attr in aReplacer) {
      newJSON[attr] = aReplacer[attr];
    }

    mm.sendAsyncMessage(aMessage.name, newJSON);
    return true;
  },

  sendToParent: function cc_sendToParent(aMessage) {
    // XXX: This is a silly way to make a deep copy
    let newJSON = JSON.parse(JSON.stringify(aMessage.json));
    newJSON.origin = 'child';
    aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
  },

  /**
   * Move cursor and/or present its location.
   * aOptions could have any of these fields:
   * - delay: in ms, before actual move is performed. Another autoMove call
   *    would cancel it. Useful if we want to wait for a possible trailing
   *    focus move. Default 0.
   * - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
   * - forcePresent: present cursor location, whether we move or don't.
   * - moveToFocused: if there is a focused accessible move to that. This takes
   *    precedence over given anchor.
   * - moveMethod: pivot move method to use, default is 'moveNext',
   */
  autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
    this.cancelAutoMove();

    let moveFunc = () => {
      let vc = this.vc;
      let acc = aAnchor;
      let rule = aOptions.onScreenOnly ?
        TraversalRules.SimpleOnScreen : TraversalRules.Simple;
      let forcePresentFunc = () => {
        if (aOptions.forcePresent) {
          this._contentScope.get().sendAsyncMessage(
            'AccessFu:Present', Presentation.pivotChanged(
              vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
              vc.startOffset, vc.endOffset, false));
        }
      };

      if (aOptions.noOpIfOnScreen &&
        Utils.isAliveAndVisible(vc.position, true)) {
        forcePresentFunc();
        return;
      }

      if (aOptions.moveToFocused) {
        acc = Utils.AccService.getAccessibleFor(
          this.document.activeElement) || acc;
      }

      let moved = false;
      let moveMethod = aOptions.moveMethod || 'moveNext'; // default is moveNext
      let moveFirstOrLast = moveMethod in ['moveFirst', 'moveLast'];
      if (!moveFirstOrLast || acc) {
        // We either need next/previous or there is an anchor we need to use.
        moved = vc[moveFirstOrLast ? 'moveNext' : moveMethod](rule, acc, true,
                                                              false);
      }
      if (moveFirstOrLast && !moved) {
        // We move to first/last after no anchor move happened or succeeded.
        moved = vc[moveMethod](rule, false);
      }

      let sentToChild = this.sendToChild(vc, {
        name: 'AccessFu:AutoMove',
        json: {
          moveMethod: aOptions.moveMethod,
          moveToFocused: aOptions.moveToFocused,
          noOpIfOnScreen: true,
          forcePresent: true
        }
      }, null, true);

      if (!moved && !sentToChild) {
        forcePresentFunc();
      }
    };

    if (aOptions.delay) {
      this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
    } else {
      moveFunc();
    }
  },

  cancelAutoMove: function cc_cancelAutoMove() {
    this.window.clearTimeout(this._autoMove);
    this._autoMove = 0;
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
    Ci.nsIMessageListener
  ])
};
PK
!<󂨗]]&modules/accessibility/EventManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const Ci = Components.interfaces;
const Cu = Components.utils;

const TEXT_NODE = 3;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
  'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
  'resource://gre/modules/accessibility/Presentation.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Events',
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States',
  'resource://gre/modules/accessibility/Constants.jsm');

this.EXPORTED_SYMBOLS = ['EventManager'];

this.EventManager = function EventManager(aContentScope, aContentControl) {
  this.contentScope = aContentScope;
  this.contentControl = aContentControl;
  this.addEventListener = this.contentScope.addEventListener.bind(
    this.contentScope);
  this.removeEventListener = this.contentScope.removeEventListener.bind(
    this.contentScope);
  this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
    this.contentScope);
  this.webProgress = this.contentScope.docShell.
    QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIWebProgress);
};

this.EventManager.prototype = {
  editState: { editing: false },

  start: function start() {
    try {
      if (!this._started) {
        Logger.debug('EventManager.start');

        this._started = true;

        AccessibilityEventObserver.addListener(this);

        this.webProgress.addProgressListener(this,
          (Ci.nsIWebProgress.NOTIFY_STATE_ALL |
           Ci.nsIWebProgress.NOTIFY_LOCATION));
        this.addEventListener('wheel', this, true);
        this.addEventListener('scroll', this, true);
        this.addEventListener('resize', this, true);
        this._preDialogPosition = new WeakMap();
      }
      this.present(Presentation.tabStateChanged(null, 'newtab'));

    } catch (x) {
      Logger.logException(x, 'Failed to start EventManager');
    }
  },

  // XXX: Stop is not called when the tab is closed (|TabClose| event is too
  // late). It is only called when the AccessFu is disabled explicitly.
  stop: function stop() {
    if (!this._started) {
      return;
    }
    Logger.debug('EventManager.stop');
    AccessibilityEventObserver.removeListener(this);
    try {
      this._preDialogPosition = new WeakMap();
      this.webProgress.removeProgressListener(this);
      this.removeEventListener('wheel', this, true);
      this.removeEventListener('scroll', this, true);
      this.removeEventListener('resize', this, true);
    } catch (x) {
      // contentScope is dead.
    } finally {
      this._started = false;
    }
  },

  handleEvent: function handleEvent(aEvent) {
    Logger.debug(() => {
      return ['DOMEvent', aEvent.type];
    });

    try {
      switch (aEvent.type) {
      case 'wheel':
      {
        let delta = aEvent.deltaX || aEvent.deltaY;
        this.contentControl.autoMove(
         null,
         { moveMethod: delta > 0 ? 'moveNext' : 'movePrevious',
           onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
        break;
      }
      case 'scroll':
      case 'resize':
      {
        // the target could be an element, document or window
        let window = null;
        if (aEvent.target instanceof Ci.nsIDOMWindow)
          window = aEvent.target;
        else if (aEvent.target instanceof Ci.nsIDOMDocument)
          window = aEvent.target.defaultView;
        else if (aEvent.target instanceof Ci.nsIDOMElement)
          window = aEvent.target.ownerGlobal;
        this.present(Presentation.viewportChanged(window));
        break;
      }
      }
    } catch (x) {
      Logger.logException(x, 'Error handling DOM event');
    }
  },

  handleAccEvent: function handleAccEvent(aEvent) {
    Logger.debug(() => {
      return ['A11yEvent', Logger.eventToString(aEvent),
              Logger.accessibleToString(aEvent.accessible)];
    });

    // Don't bother with non-content events in firefox.
    if (Utils.MozBuildApp == 'browser' &&
        aEvent.eventType != Events.VIRTUALCURSOR_CHANGED &&
        // XXX Bug 442005 results in DocAccessible::getDocType returning
        // NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType ==
        // 'window' does not currently work.
        (aEvent.accessibleDocument.DOMDocument.doctype &&
         aEvent.accessibleDocument.DOMDocument.doctype.name === 'window')) {
      return;
    }

    switch (aEvent.eventType) {
      case Events.VIRTUALCURSOR_CHANGED:
      {
        let pivot = aEvent.accessible.
          QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
        let position = pivot.position;
        if (position && position.role == Roles.INTERNAL_FRAME)
          break;
        let event = aEvent.
          QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
        let reason = event.reason;
        let oldAccessible = event.oldAccessible;

        if (this.editState.editing &&
            !Utils.getState(position).contains(States.FOCUSED)) {
          aEvent.accessibleDocument.takeFocus();
        }
        this.present(
          Presentation.pivotChanged(position, oldAccessible, reason,
                                    pivot.startOffset, pivot.endOffset,
                                    aEvent.isFromUserInput));

        break;
      }
      case Events.STATE_CHANGE:
      {
        let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
        let state = Utils.getState(event);
        if (state.contains(States.CHECKED)) {
          if (aEvent.accessible.role === Roles.SWITCH) {
            this.present(
              Presentation.
                actionInvoked(aEvent.accessible,
                              event.isEnabled ? 'on' : 'off'));
          } else {
            this.present(
              Presentation.
                actionInvoked(aEvent.accessible,
                              event.isEnabled ? 'check' : 'uncheck'));
          }
        } else if (state.contains(States.SELECTED)) {
          this.present(
            Presentation.
              actionInvoked(aEvent.accessible,
                            event.isEnabled ? 'select' : 'unselect'));
        }
        break;
      }
      case Events.NAME_CHANGE:
      {
        let acc = aEvent.accessible;
        if (acc === this.contentControl.vc.position) {
          this.present(Presentation.nameChanged(acc));
        } else {
          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
            ['text', 'all']);
          if (liveRegion) {
            this.present(Presentation.nameChanged(acc, isPolite));
          }
        }
        break;
      }
      case Events.SCROLLING_START:
      {
        this.contentControl.autoMove(aEvent.accessible);
        break;
      }
      case Events.TEXT_CARET_MOVED:
      {
        let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
        let caretOffset = aEvent.
          QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;

        // We could get a caret move in an accessible that is not focused,
        // it doesn't mean we are not on any editable accessible. just not
        // on this one..
        let state = Utils.getState(acc);
        if (state.contains(States.FOCUSED)) {
          this._setEditingMode(aEvent, caretOffset);
          if (state.contains(States.EDITABLE)) {
            this.present(Presentation.textSelectionChanged(acc.getText(0, -1),
              caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput));
          }
        }
        break;
      }
      case Events.OBJECT_ATTRIBUTE_CHANGED:
      {
        let evt = aEvent.QueryInterface(
          Ci.nsIAccessibleObjectAttributeChangedEvent);
        if (evt.changedAttribute.toString() !== 'aria-hidden') {
          // Only handle aria-hidden attribute change.
          break;
        }
        let hidden = Utils.isHidden(aEvent.accessible);
        this[hidden ? '_handleHide' : '_handleShow'](evt);
        if (this.inTest) {
          this.sendMsgFunc("AccessFu:AriaHidden", { hidden: hidden });
        }
        break;
      }
      case Events.SHOW:
      {
        this._handleShow(aEvent);
        break;
      }
      case Events.HIDE:
      {
        let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
        this._handleHide(evt);
        break;
      }
      case Events.TEXT_INSERTED:
      case Events.TEXT_REMOVED:
      {
        let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
          ['text', 'all']);
        if (aEvent.isFromUserInput || liveRegion) {
          // Handle all text mutations coming from the user or if they happen
          // on a live region.
          this._handleText(aEvent, liveRegion, isPolite);
        }
        break;
      }
      case Events.FOCUS:
      {
        // Put vc where the focus is at
        let acc = aEvent.accessible;
        this._setEditingMode(aEvent);
        if ([Roles.CHROME_WINDOW,
             Roles.DOCUMENT,
             Roles.APPLICATION].indexOf(acc.role) < 0) {
          this.contentControl.autoMove(acc);
       }

       if (this.inTest) {
        this.sendMsgFunc("AccessFu:Focused");
       }
       break;
      }
      case Events.DOCUMENT_LOAD_COMPLETE:
      {
        let position = this.contentControl.vc.position;
        // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
        // event's dialog accessible or accessible document
        let subtreeRoot = aEvent.accessible.role === Roles.DIALOG ?
          aEvent.accessible : aEvent.accessibleDocument;
        if (aEvent.accessible === aEvent.accessibleDocument ||
            (position && Utils.isInSubtree(position, subtreeRoot))) {
          // Do not automove into the document if the virtual cursor is already
          // positioned inside it.
          break;
        }
        this._preDialogPosition.set(aEvent.accessible.DOMNode, position);
        this.contentControl.autoMove(aEvent.accessible, { delay: 500 });
        break;
      }
      case Events.VALUE_CHANGE:
      case Events.TEXT_VALUE_CHANGE:
      {
        let position = this.contentControl.vc.position;
        let target = aEvent.accessible;
        if (position === target ||
            Utils.getEmbeddedControl(position) === target) {
          this.present(Presentation.valueChanged(target));
        } else {
          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
            ['text', 'all']);
          if (liveRegion) {
            this.present(Presentation.valueChanged(target, isPolite));
          }
        }
      }
    }
  },

  _setEditingMode: function _setEditingMode(aEvent, aCaretOffset) {
    let acc = aEvent.accessible;
    let accText, characterCount;
    let caretOffset = aCaretOffset;

    try {
      accText = acc.QueryInterface(Ci.nsIAccessibleText);
    } catch (e) {
      // No text interface on this accessible.
    }

    if (accText) {
      characterCount = accText.characterCount;
      if (caretOffset === undefined) {
        caretOffset = accText.caretOffset;
      }
    }

    // Update editing state, both for presenter and other things
    let state = Utils.getState(acc);

    let editState = {
      editing: state.contains(States.EDITABLE) &&
        state.contains(States.FOCUSED),
      multiline: state.contains(States.MULTI_LINE),
      atStart: caretOffset === 0,
      atEnd: caretOffset === characterCount
    };

    // Not interesting
    if (!editState.editing && editState.editing === this.editState.editing) {
      return;
    }

    if (editState.editing !== this.editState.editing) {
      this.present(Presentation.editingModeChanged(editState.editing));
    }

    if (editState.editing !== this.editState.editing ||
        editState.multiline !== this.editState.multiline ||
        editState.atEnd !== this.editState.atEnd ||
        editState.atStart !== this.editState.atStart) {
      this.sendMsgFunc("AccessFu:Input", editState);
    }

    this.editState = editState;
  },

  _handleShow: function _handleShow(aEvent) {
    let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
      ['additions', 'all']);
    // Only handle show if it is a relevant live region.
    if (!liveRegion) {
      return;
    }
    // Show for text is handled by the EVENT_TEXT_INSERTED handler.
    if (aEvent.accessible.role === Roles.TEXT_LEAF) {
      return;
    }
    this._dequeueLiveEvent(Events.HIDE, liveRegion);
    this.present(Presentation.liveRegion(liveRegion, isPolite, false));
  },

  _handleHide: function _handleHide(aEvent) {
    let {liveRegion, isPolite} = this._handleLiveRegion(
      aEvent, ['removals', 'all']);
    let acc = aEvent.accessible;
    if (liveRegion) {
      // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
      if (acc.role === Roles.TEXT_LEAF) {
        return;
      }
      this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
    } else {
      let vc = Utils.getVirtualCursor(this.contentScope.content.document);
      if (vc.position &&
        (Utils.getState(vc.position).contains(States.DEFUNCT) ||
          Utils.isInSubtree(vc.position, acc))) {
        let position = this._preDialogPosition.get(aEvent.accessible.DOMNode) ||
          aEvent.targetPrevSibling || aEvent.targetParent;
        if (!position) {
          try {
            position = acc.previousSibling;
          } catch (x) {
            // Accessible is unattached from the accessible tree.
            position = acc.parent;
          }
        }
        this.contentControl.autoMove(position,
          { moveToFocused: true, delay: 500 });
      }
    }
  },

  _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
    let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
    let isInserted = event.isInserted;
    let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);

    let text = '';
    try {
      text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
    } catch (x) {
      // XXX we might have gotten an exception with of a
      // zero-length text. If we did, ignore it (bug #749810).
      if (txtIface.characterCount) {
        throw x;
      }
    }
    // If there are embedded objects in the text, ignore them.
    // Assuming changes to the descendants would already be handled by the
    // show/hide event.
    let modifiedText = event.modifiedText.replace(/\uFFFC/g, '');
    if (modifiedText != event.modifiedText && !modifiedText.trim()) {
      return;
    }

    if (aLiveRegion) {
      if (aEvent.eventType === Events.TEXT_REMOVED) {
        this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
          modifiedText);
      } else {
        this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion);
        this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false,
          modifiedText));
      }
    } else {
      this.present(Presentation.textChanged(aEvent.accessible, isInserted,
        event.start, event.length, text, modifiedText));
    }
  },

  _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) {
    if (aEvent.isFromUserInput) {
      return {};
    }
    let parseLiveAttrs = function parseLiveAttrs(aAccessible) {
      let attrs = Utils.getAttributes(aAccessible);
      if (attrs['container-live']) {
        return {
          live: attrs['container-live'],
          relevant: attrs['container-relevant'] || 'additions text',
          busy: attrs['container-busy'],
          atomic: attrs['container-atomic'],
          memberOf: attrs['member-of']
        };
      }
      return null;
    };
    // XXX live attributes are not set for hidden accessibles yet. Need to
    // climb up the tree to check for them.
    let getLiveAttributes = function getLiveAttributes(aEvent) {
      let liveAttrs = parseLiveAttrs(aEvent.accessible);
      if (liveAttrs) {
        return liveAttrs;
      }
      let parent = aEvent.targetParent;
      while (parent) {
        liveAttrs = parseLiveAttrs(parent);
        if (liveAttrs) {
          return liveAttrs;
        }
        parent = parent.parent
      }
      return {};
    };
    let {live, relevant, /* busy, atomic, memberOf */ } = getLiveAttributes(aEvent);
    // If container-live is not present or is set to |off| ignore the event.
    if (!live || live === 'off') {
      return {};
    }
    // XXX: support busy and atomic.

    // Determine if the type of the mutation is relevant. Default is additions
    // and text.
    let isRelevant = Utils.matchAttributeValue(relevant, aRelevant);
    if (!isRelevant) {
      return {};
    }
    return {
      liveRegion: aEvent.accessible,
      isPolite: live === 'polite'
    };
  },

  _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) {
    let domNode = aLiveRegion.DOMNode;
    if (this._liveEventQueue && this._liveEventQueue.has(domNode)) {
      let queue = this._liveEventQueue.get(domNode);
      let nextEvent = queue[0];
      if (nextEvent.eventType === aEventType) {
        Utils.win.clearTimeout(nextEvent.timeoutID);
        queue.shift();
        if (queue.length === 0) {
          this._liveEventQueue.delete(domNode)
        }
      }
    }
  },

  _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) {
    if (!this._liveEventQueue) {
      this._liveEventQueue = new WeakMap();
    }
    let eventHandler = {
      eventType: aEventType,
      timeoutID: Utils.win.setTimeout(this.present.bind(this),
        20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
        Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
    };

    let domNode = aLiveRegion.DOMNode;
    if (this._liveEventQueue.has(domNode)) {
      this._liveEventQueue.get(domNode).push(eventHandler);
    } else {
      this._liveEventQueue.set(domNode, [eventHandler]);
    }
  },

  present: function present(aPresentationData) {
    this.sendMsgFunc("AccessFu:Present", aPresentationData);
  },

  onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    let tabstate = '';

    let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
      Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
    let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
      Ci.nsIWebProgressListener.STATE_IS_NETWORK;

    if ((aStateFlags & loadingState) == loadingState) {
      tabstate = 'loading';
    } else if ((aStateFlags & loadedState) == loadedState &&
               !aWebProgress.isLoadingDocument) {
      tabstate = 'loaded';
    }

    if (tabstate) {
      let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
      this.present(Presentation.tabStateChanged(docAcc, tabstate));
    }
  },

  onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
    this.present(Presentation.tabStateChanged(docAcc, 'newdoc'));
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsISupports,
                                         Ci.nsIObserver])
};

const AccessibilityEventObserver = {

  /**
   * A WeakMap containing [content, EventManager] pairs.
   */
  eventManagers: new WeakMap(),

  /**
   * A total number of registered eventManagers.
   */
  listenerCount: 0,

  /**
   * An indicator of an active 'accessible-event' observer.
   */
  started: false,

  /**
   * Start an AccessibilityEventObserver.
   */
  start: function start() {
    if (this.started || this.listenerCount === 0) {
      return;
    }
    Services.obs.addObserver(this, 'accessible-event');
    this.started = true;
  },

  /**
   * Stop an AccessibilityEventObserver.
   */
  stop: function stop() {
    if (!this.started) {
      return;
    }
    Services.obs.removeObserver(this, 'accessible-event');
    // Clean up all registered event managers.
    this.eventManagers = new WeakMap();
    this.listenerCount = 0;
    this.started = false;
  },

  /**
   * Register an EventManager and start listening to the
   * 'accessible-event' messages.
   *
   * @param aEventManager EventManager
   *        An EventManager object that was loaded into the specific content.
   */
  addListener: function addListener(aEventManager) {
    let content = aEventManager.contentScope.content;
    if (!this.eventManagers.has(content)) {
      this.listenerCount++;
    }
    this.eventManagers.set(content, aEventManager);
    // Since at least one EventManager was registered, start listening.
    Logger.debug('AccessibilityEventObserver.addListener. Total:',
      this.listenerCount);
    this.start();
  },

  /**
   * Unregister an EventManager and, optionally, stop listening to the
   * 'accessible-event' messages.
   *
   * @param aEventManager EventManager
   *        An EventManager object that was stopped in the specific content.
   */
  removeListener: function removeListener(aEventManager) {
    let content = aEventManager.contentScope.content;
    if (!this.eventManagers.delete(content)) {
      return;
    }
    this.listenerCount--;
    Logger.debug('AccessibilityEventObserver.removeListener. Total:',
      this.listenerCount);
    if (this.listenerCount === 0) {
      // If there are no EventManagers registered at the moment, stop listening
      // to the 'accessible-event' messages.
      this.stop();
    }
  },

  /**
   * Lookup an EventManager for a specific content. If the EventManager is not
   * found, walk up the hierarchy of parent windows.
   * @param content Window
   *        A content Window used to lookup the corresponding EventManager.
   */
  getListener: function getListener(content) {
    let eventManager = this.eventManagers.get(content);
    if (eventManager) {
      return eventManager;
    }
    let parent = content.parent;
    if (parent === content) {
      // There is no parent or the parent is of a different type.
      return null;
    }
    return this.getListener(parent);
  },

  /**
   * Handle the 'accessible-event' message.
   */
  observe: function observe(aSubject, aTopic, aData) {
    if (aTopic !== 'accessible-event') {
      return;
    }
    let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
    if (!event.accessibleDocument) {
      Logger.warning(
        'AccessibilityEventObserver.observe: no accessible document:',
        Logger.eventToString(event), "accessible:",
        Logger.accessibleToString(event.accessible));
      return;
    }
    let content = event.accessibleDocument.window;
    // Match the content window to its EventManager.
    let eventManager = this.getListener(content);
    if (!eventManager || !eventManager._started) {
      if (Utils.MozBuildApp === 'browser' &&
          !(content instanceof Ci.nsIDOMChromeWindow)) {
        Logger.warning(
          'AccessibilityEventObserver.observe: ignored event:',
          Logger.eventToString(event), "accessible:",
          Logger.accessibleToString(event.accessible), "document:",
          Logger.accessibleToString(event.accessibleDocument));
      }
      return;
    }
    try {
      eventManager.handleAccEvent(event);
    } catch (x) {
      Logger.logException(x, 'Error handing accessible event');
    } finally {
      return;
    }
  }
};
PK
!<C}}"modules/accessibility/Gestures.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 GestureSettings, GestureTracker */

/******************************************************************************
  All gestures have the following pathways when being resolved(v)/rejected(x):
               Tap -> DoubleTap        (x)
                   -> Dwell            (x)
                   -> Swipe            (x)

         DoubleTap -> TripleTap        (x)
                   -> TapHold          (x)

         TripleTap -> DoubleTapHold    (x)

             Dwell -> DwellEnd         (v)

             Swipe -> Explore          (x)

           TapHold -> TapHoldEnd       (v)

     DoubleTapHold -> DoubleTapHoldEnd (v)

          DwellEnd -> Explore          (x)

        TapHoldEnd -> Explore          (x)

  DoubleTapHoldEnd -> Explore          (x)

        ExploreEnd -> Explore          (x)

           Explore -> ExploreEnd       (v)
******************************************************************************/

'use strict';

const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line

Cu.import('resource://gre/modules/XPCOMUtils.jsm');

XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line
  'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line
  'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PromiseUtils', // jshint ignore:line
  'resource://gre/modules/PromiseUtils.jsm');

// Default maximum duration of swipe
const SWIPE_MAX_DURATION = 200;
// Default maximum amount of time allowed for a gesture to be considered a
// multitouch
const MAX_MULTITOUCH = 125;
// Default maximum consecutive pointer event timeout
const MAX_CONSECUTIVE_GESTURE_DELAY = 200;
// Default delay before tap turns into dwell
const DWELL_THRESHOLD = 250;
// Minimal swipe distance in inches
const SWIPE_MIN_DISTANCE = 0.4;
// Maximum distance the pointer could move during a tap in inches
const TAP_MAX_RADIUS = 0.2;
// Directness coefficient. It is based on the maximum 15 degree angle between
// consequent pointer move lines.
const DIRECTNESS_COEFF = 1.44;
// Amount in inches from the edges of the screen for it to be an edge swipe
const EDGE = 0.1;
// Multiply timeouts by this constant, x2 works great too for slower users.
const TIMEOUT_MULTIPLIER = 1;
// A single pointer down/up sequence periodically precedes the tripple swipe
// gesture on Android. This delay acounts for that.
const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
  Utils.AndroidSdkVersion >= 14;

/**
 * A point object containing distance travelled data.
 * @param {Object} aPoint A point object that looks like: {
 *   x: x coordinate in pixels,
 *   y: y coordinate in pixels
 * }
 */
function Point(aPoint) {
  this.startX = this.x = aPoint.x;
  this.startY = this.y = aPoint.y;
  this.distanceTraveled = 0;
  this.totalDistanceTraveled = 0;
}

Point.prototype = {
  /**
   * Update the current point coordiates.
   * @param  {Object} aPoint A new point coordinates.
   */
  update: function Point_update(aPoint) {
    let lastX = this.x;
    let lastY = this.y;
    this.x = aPoint.x;
    this.y = aPoint.y;
    this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
    this.totalDistanceTraveled += this.distanceTraveled;
  },

  reset: function Point_reset() {
    this.distanceTraveled = 0;
    this.totalDistanceTraveled = 0;
  },

  /**
   * Get distance between the current point coordinates and the given ones.
   * @param  {Number} aX A pixel value for the x coordinate.
   * @param  {Number} aY A pixel value for the y coordinate.
   * @return {Number} A distance between point's current and the given
   * coordinates.
   */
  getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
    return Math.hypot(this.x - aX, this.y - aY);
  },

  /**
   * Get the direct distance travelled by the point so far.
   */
  get directDistanceTraveled() {
    return this.getDistanceToCoord(this.startX, this.startY);
  }
};

/**
 * An externally accessible collection of settings used in gesture resolition.
 * @type {Object}
 */
this.GestureSettings = { // jshint ignore:line
  /**
   * Maximum duration of swipe
   * @type {Number}
   */
  swipeMaxDuration: SWIPE_MAX_DURATION * TIMEOUT_MULTIPLIER,

  /**
   * Maximum amount of time allowed for a gesture to be considered a multitouch.
   * @type {Number}
   */
  maxMultitouch: MAX_MULTITOUCH * TIMEOUT_MULTIPLIER,

  /**
   * Maximum consecutive pointer event timeout.
   * @type {Number}
   */
  maxConsecutiveGestureDelay:
    MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,

  /**
   * A maximum time we wait for a next pointer down event to consider a sequence
   * a multi-action gesture.
   * @type {Number}
   */
  maxGestureResolveTimeout:
    MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,

  /**
   * Delay before tap turns into dwell
   * @type {Number}
   */
  dwellThreshold: DWELL_THRESHOLD * TIMEOUT_MULTIPLIER,

  /**
   * Minimum distance that needs to be travelled for the pointer move to be
   * fired.
   * @type {Number}
   */
  travelThreshold: 0.025
};

/**
 * An interface that handles the pointer events and calculates the appropriate
 * gestures.
 * @type {Object}
 */
this.GestureTracker = { // jshint ignore:line
  /**
   * Reset GestureTracker to its initial state.
   * @return {[type]} [description]
   */
  reset: function GestureTracker_reset() {
    if (this.current) {
      this.current.clearTimer();
    }
    delete this.current;
  },

  /**
   * Create a new gesture object and attach resolution handler to it as well as
   * handle the incoming pointer event.
   * @param  {Object} aDetail A new pointer event detail.
   * @param  {Number} aTimeStamp A new pointer event timeStamp.
   * @param  {Function} aGesture A gesture constructor (default: Tap).
   */
  _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture) {
    // Only create a new gesture on |pointerdown| event.
    if (aDetail.type !== 'pointerdown') {
      return;
    }
    let GestureConstructor = aGesture || (IS_ANDROID ? DoubleTap : Tap);
    this._create(GestureConstructor);
    this._update(aDetail, aTimeStamp);
  },

  /**
   * Handle the incoming pointer event with the existing gesture object(if
   * present) or with the newly created one.
   * @param  {Object} aDetail A new pointer event detail.
   * @param  {Number} aTimeStamp A new pointer event timeStamp.
   */
  handle: function GestureTracker_handle(aDetail, aTimeStamp) {
    Logger.gesture(() => {
      return ['Pointer event', Utils.dpi, 'at:', aTimeStamp, JSON.stringify(aDetail)];
    });
    this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
  },

  /**
   * Create a new gesture object and attach resolution handler to it.
   * @param  {Function} aGesture A gesture constructor.
   * @param  {Number} aTimeStamp An original pointer event timeStamp.
   * @param  {Array} aPoints All changed points associated with the new pointer
   * event.
   * @param {?String} aLastEvent Last pointer event type.
   */
  _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
    this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
    this.current.then(this._onFulfill.bind(this));
  },

  /**
   * Handle the incoming pointer event with the existing gesture object.
   * @param  {Object} aDetail A new pointer event detail.
   * @param  {Number} aTimeStamp A new pointer event timeStamp.
   */
  _update: function GestureTracker_update(aDetail, aTimeStamp) {
    this.current[aDetail.type](aDetail.points, aTimeStamp);
  },

  /**
   * A resolution handler function for the current gesture promise.
   * @param  {Object} aResult A resolution payload with the relevant gesture id
   * and an optional new gesture contructor.
   */
  _onFulfill: function GestureTracker__onFulfill(aResult) {
    let {id, gestureType} = aResult;
    let current = this.current;
    // Do nothing if there's no existing gesture or there's already a newer
    // gesture.
    if (!current || current.id !== id) {
      return;
    }
    // Only create a gesture if we got a constructor.
    if (gestureType) {
      this._create(gestureType, current.startTime, current.points,
        current.lastEvent);
    } else {
      this.current.clearTimer();
      delete this.current;
    }
  }
};

/**
 * Compile a mozAccessFuGesture detail structure.
 * @param  {String} aType A gesture type.
 * @param  {Object} aPoints Gesture's points.
 * @param  {String} xKey A default key for the x coordinate. Default is
 * 'startX'.
 * @param  {String} yKey A default key for the y coordinate. Default is
 * 'startY'.
 * @return {Object} a mozAccessFuGesture detail structure.
 */
function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
  let touches = [];
  let maxDeltaX = 0;
  let maxDeltaY = 0;
  for (let identifier in aPoints) {
    let point = aPoints[identifier];
    let touch = {};
    for (let key in keyMap) {
      touch[key] = point[keyMap[key]];
    }
    touches.push(touch);
    let deltaX = point.x - point.startX;
    let deltaY = point.y - point.startY;
    // Determine the maximum x and y travel intervals.
    if (Math.abs(maxDeltaX) < Math.abs(deltaX)) {
      maxDeltaX = deltaX;
    }
    if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
      maxDeltaY = deltaY;
    }
    // Since the gesture is resolving, reset the points' distance information
    // since they are passed to the next potential gesture.
    point.reset();
  }
  return {
    type: aType,
    touches: touches,
    deltaX: maxDeltaX,
    deltaY: maxDeltaY
  };
}

/**
 * A general gesture object.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * Default is an empty object.
 * @param {?String} aLastEvent Last pointer event type.
 */
function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
  this.startTime = Date.now();
  Logger.gesture('Creating', this.id, 'gesture.');
  this.points = aPoints;
  this.lastEvent = aLastEvent;
  this._deferred = PromiseUtils.defer();
  // Call this._handleResolve or this._handleReject when the promise is
  // fulfilled with either resolve or reject.
  this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
    this._handleReject.bind(this));
  this.startTimer(aTimeStamp);
}

Gesture.prototype = {
  /**
   * Get the gesture timeout delay.
   * @return {Number}
   */
  _getDelay: function Gesture__getDelay() {
    // If nothing happens withing the
    // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
    // more pointer events and consider them the part of the same gesture -
    // reject this gesture promise.
    return GestureSettings.maxConsecutiveGestureDelay;
  },

  /**
   * Clear the existing timer.
   */
  clearTimer: function Gesture_clearTimer() {
    Logger.gesture('clearTimeout', this.type);
    clearTimeout(this._timer);
    delete this._timer;
  },

  /**
   * Start the timer for gesture timeout.
   * @param {Number} aTimeStamp An original pointer event's timeStamp that
   * started the gesture resolution sequence.
   */
  startTimer: function Gesture_startTimer(aTimeStamp) {
    Logger.gesture('startTimer', this.type);
    this.clearTimer();
    let delay = this._getDelay(aTimeStamp);
    let handler = () => {
      Logger.gesture('timer handler');
      this.clearTimer();
      if (!this._inProgress) {
        this._deferred.reject();
      } else if (this._rejectToOnWait) {
        this._deferred.reject(this._rejectToOnWait);
      }
    };
    if (delay <= 0) {
      handler();
    } else {
      this._timer = setTimeout(handler, delay);
    }
  },

  /**
   * Add a gesture promise resolution callback.
   * @param  {Function} aCallback
   */
  then: function Gesture_then(aCallback) {
    this.promise.then(aCallback);
  },

  /**
   * Update gesture's points. Test the points set with the optional gesture test
   * function.
   * @param  {Array} aPoints An array with the changed points from the new
   * pointer event.
   * @param {String} aType Pointer event type.
   * @param  {Boolean} aCanCreate A flag that enables including the new points.
   * Default is false.
   * @param  {Boolean} aNeedComplete A flag that indicates that the gesture is
   * completing. Default is false.
   * @return {Boolean} Indicates whether the gesture can be complete (it is
   * set to true iff the aNeedComplete is true and there was a change to at
   * least one point that belongs to the gesture).
   */
  _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
    let complete;
    let lastEvent;
    for (let point of aPoints) {
      let identifier = point.identifier;
      let gesturePoint = this.points[identifier];
      if (gesturePoint) {
        if (aType === 'pointerdown' && aCanCreate) {
          // scratch the previous pointer with that id.
          this.points[identifier] = new Point(point);
        } else {
          gesturePoint.update(point);
        }
        if (aNeedComplete) {
          // Since the gesture is completing and at least one of the gesture
          // points is updated, set the return value to true.
          complete = true;
        }
        lastEvent = lastEvent || aType;
      } else if (aCanCreate) {
        // Only create a new point if aCanCreate is true.
        this.points[identifier] =
          new Point(point);
        lastEvent = lastEvent || aType;
      }
    }
    this.lastEvent = lastEvent || this.lastEvent;
    // If test function is defined test the points.
    if (this.test) {
      this.test(complete);
    }
    return complete;
  },

  /**
   * Emit a mozAccessFuGesture (when the gesture is resolved).
   * @param  {Object} aDetail a compiled mozAccessFuGesture detail structure.
   */
  _emit: function Gesture__emit(aDetail) {
    let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
      bubbles: true,
      cancelable: true,
      detail: aDetail
    });
    Utils.win.dispatchEvent(evt);
  },

  /**
   * Handle the pointer down event.
   * @param  {Array} aPoints A new pointer down points.
   * @param  {Number} aTimeStamp A new pointer down timeStamp.
   */
  pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
    this._inProgress = true;
    this._update(aPoints, 'pointerdown',
      aTimeStamp - this.startTime < GestureSettings.maxMultitouch);
  },

  /**
   * Handle the pointer move event.
   * @param  {Array} aPoints A new pointer move points.
   */
  pointermove: function Gesture_pointermove(aPoints) {
    this._update(aPoints, 'pointermove');
  },

  /**
   * Handle the pointer up event.
   * @param  {Array} aPoints A new pointer up points.
   */
  pointerup: function Gesture_pointerup(aPoints) {
    let complete = this._update(aPoints, 'pointerup', false, true);
    if (complete) {
      this._deferred.resolve();
    }
  },

  /**
   * A subsequent gesture constructor to resolve the current one to. E.g.
   * tap->doubletap, dwell->dwellend, etc.
   * @type {Function}
   */
  resolveTo: null,

  /**
   * A unique id for the gesture. Composed of the type + timeStamp.
   */
  get id() {
    delete this._id;
    this._id = this.type + this.startTime;
    return this._id;
  },

  /**
   * A gesture promise resolve callback. Compile and emit the gesture.
   * @return {Object} Returns a structure to the gesture handler that looks like
   * this: {
   *   id: current gesture id,
   *   gestureType: an optional subsequent gesture constructor.
   * }
   */
  _handleResolve: function Gesture__handleResolve() {
    if (this.isComplete) {
      return;
    }
    Logger.gesture('Resolving', this.id, 'gesture.');
    this.isComplete = true;
    this.clearTimer();
    let detail = this.compile();
    if (detail) {
      this._emit(detail);
    }
    return {
      id: this.id,
      gestureType: this.resolveTo
    };
  },

  /**
   * A gesture promise reject callback.
   * @return {Object} Returns a structure to the gesture handler that looks like
   * this: {
   *   id: current gesture id,
   *   gestureType: an optional subsequent gesture constructor.
   * }
   */
  _handleReject: function Gesture__handleReject(aRejectTo) {
    if (this.isComplete) {
      return;
    }
    Logger.gesture('Rejecting', this.id, 'gesture.');
    this.isComplete = true;
    this.clearTimer();
    return {
      id: this.id,
      gestureType: aRejectTo
    };
  },

  /**
   * A default compilation function used to build the mozAccessFuGesture event
   * detail. The detail always includes the type and the touches associated
   * with the gesture.
   * @return {Object} Gesture event detail.
   */
  compile: function Gesture_compile() {
    return compileDetail(this.type, this.points);
  }
};

/**
 * A mixin for an explore related object.
 */
function ExploreGesture() {
  this.compile = () => {
    // Unlike most of other gestures explore based gestures compile using the
    // current point position and not the start one.
    return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
  };
}

/**
 * Check the in progress gesture for completion.
 */
function checkProgressGesture(aGesture) {
  aGesture._inProgress = true;
  if (aGesture.lastEvent === 'pointerup') {
    if (aGesture.test) {
      aGesture.test(true);
    }
    aGesture._deferred.resolve();
  }
}

/**
 * A common travel gesture. When the travel gesture is created, all subsequent
 * pointer events' points are tested for their total distance traveled. If that
 * distance exceeds the _threshold distance, the gesture will be rejected to a
 * _travelTo gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 * @param {Function} aTravelTo A contructor for the gesture to reject to when
 * travelling (default: Explore).
 * @param {Number} aThreshold Travel threshold (default:
 * GestureSettings.travelThreshold).
 */
function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
  this._travelTo = aTravelTo;
  this._threshold = aThreshold;
}

TravelGesture.prototype = Object.create(Gesture.prototype);

/**
 * Test the gesture points for travel. The gesture will be rejected to
 * this._travelTo gesture iff at least one point crosses this._threshold.
 */
TravelGesture.prototype.test = function TravelGesture_test() {
  if (!this._travelTo) {
    return;
  }
  for (let identifier in this.points) {
    let point = this.points[identifier];
    if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
      this._deferred.reject(this._travelTo);
      return;
    }
  }
};

/**
 * DwellEnd gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function DwellEnd(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  // If the pointer travels, reject to Explore.
  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
  checkProgressGesture(this);
}

DwellEnd.prototype = Object.create(TravelGesture.prototype);
DwellEnd.prototype.type = 'dwellend';

/**
 * TapHoldEnd gesture. This gesture can be represented as the following diagram:
 * pointerdown-pointerup-pointerdown-*wait*-pointerup.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  // If the pointer travels, reject to Explore.
  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
  checkProgressGesture(this);
}

TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
TapHoldEnd.prototype.type = 'tapholdend';

/**
 * DoubleTapHoldEnd gesture. This gesture can be represented as the following
 * diagram:
 * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  // If the pointer travels, reject to Explore.
  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
  checkProgressGesture(this);
}

DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
DoubleTapHoldEnd.prototype.type = 'doubletapholdend';

/**
 * A common tap gesture object.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 * @param {Function} aRejectToOnWait A constructor for the next gesture to
 * reject to in case no pointermove or pointerup happens within the
 * GestureSettings.dwellThreshold.
 * @param {Function} aTravelTo An optional constuctor for the next gesture to
 * reject to in case the the TravelGesture test fails.
 * @param {Function} aRejectToOnPointerDown A constructor for the gesture to
 * reject to if a finger comes down immediately after the tap.
 */
function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectToOnWait, aTravelTo, aRejectToOnPointerDown) {
  this._rejectToOnWait = aRejectToOnWait;
  this._rejectToOnPointerDown = aRejectToOnPointerDown;
  // If the pointer travels, reject to aTravelTo.
  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
    TAP_MAX_RADIUS);
}

TapGesture.prototype = Object.create(TravelGesture.prototype);
TapGesture.prototype._getDelay = function TapGesture__getDelay() {
  // If, for TapGesture, no pointermove or pointerup happens within the
  // GestureSettings.dwellThreshold, reject.
  // Note: the original pointer event's timeStamp is irrelevant here.
  return GestureSettings.dwellThreshold;
};

TapGesture.prototype.pointerup = function TapGesture_pointerup(aPoints) {
    if (this._rejectToOnPointerDown) {
      let complete = this._update(aPoints, 'pointerup', false, true);
      if (complete) {
        this.clearTimer();
        if (GestureSettings.maxGestureResolveTimeout) {
          this._pointerUpTimer = setTimeout(() => {
            clearTimeout(this._pointerUpTimer);
            delete this._pointerUpTimer;
            this._deferred.resolve();
          }, GestureSettings.maxGestureResolveTimeout);
        } else {
          this._deferred.resolve();
        }
      }
    } else {
      TravelGesture.prototype.pointerup.call(this, aPoints);
    }
};

TapGesture.prototype.pointerdown = function TapGesture_pointerdown(aPoints, aTimeStamp) {
  if (this._pointerUpTimer) {
    clearTimeout(this._pointerUpTimer);
    delete this._pointerUpTimer;
    this._deferred.reject(this._rejectToOnPointerDown);
  } else {
    TravelGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
  }
};


/**
 * Tap gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function Tap(aTimeStamp, aPoints, aLastEvent) {
  // If the pointer travels, reject to Swipe.
  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe, DoubleTap);
}

Tap.prototype = Object.create(TapGesture.prototype);
Tap.prototype.type = 'tap';


/**
 * Double Tap gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, null, TripleTap);
}

DoubleTap.prototype = Object.create(TapGesture.prototype);
DoubleTap.prototype.type = 'doubletap';

/**
 * Triple Tap gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function TripleTap(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold, null, null);
}

TripleTap.prototype = Object.create(TapGesture.prototype);
TripleTap.prototype.type = 'tripletap';

/**
 * Common base object for gestures that are created as resolved.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
  // Resolve the guesture right away.
  this._deferred.resolve();
}

ResolvedGesture.prototype = Object.create(Gesture.prototype);

/**
 * Dwell gesture
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function Dwell(aTimeStamp, aPoints, aLastEvent) {
  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
}

Dwell.prototype = Object.create(ResolvedGesture.prototype);
Dwell.prototype.type = 'dwell';
Dwell.prototype.resolveTo = DwellEnd;

/**
 * TapHold gesture
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function TapHold(aTimeStamp, aPoints, aLastEvent) {
  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
}

TapHold.prototype = Object.create(ResolvedGesture.prototype);
TapHold.prototype.type = 'taphold';
TapHold.prototype.resolveTo = TapHoldEnd;

/**
 * DoubleTapHold gesture
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
}

DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
DoubleTapHold.prototype.type = 'doubletaphold';
DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;

/**
 * Explore gesture
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function Explore(aTimeStamp, aPoints, aLastEvent) {
  ExploreGesture.call(this);
  ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
}

Explore.prototype = Object.create(ResolvedGesture.prototype);
Explore.prototype.type = 'explore';
Explore.prototype.resolveTo = ExploreEnd;

/**
 * ExploreEnd gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  ExploreGesture.call(this);
  // If the pointer travels, reject to Explore.
  TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
  checkProgressGesture(this);
}

ExploreEnd.prototype = Object.create(TravelGesture.prototype);
ExploreEnd.prototype.type = 'exploreend';

/**
 * Swipe gesture.
 * @param {Number} aTimeStamp An original pointer event's timeStamp that started
 * the gesture resolution sequence.
 * @param {Object} aPoints An existing set of points (from previous events).
 * @param {?String} aLastEvent Last pointer event type.
 */
function Swipe(aTimeStamp, aPoints, aLastEvent) {
  this._inProgress = true;
  this._rejectToOnWait = Explore;
  Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
  checkProgressGesture(this);
}

Swipe.prototype = Object.create(Gesture.prototype);
Swipe.prototype.type = 'swipe';
Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
  // Swipe should be completed within the GestureSettings.swipeMaxDuration from
  // the initial pointer down event.
  return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
};

/**
 * Determine wither the gesture was Swipe or Explore.
 * @param  {Booler} aComplete A flag that indicates whether the gesture is and
 * will be complete after the test.
 */
Swipe.prototype.test = function Swipe_test(aComplete) {
  if (!aComplete) {
    // No need to test if the gesture is not completing or can't be complete.
    return;
  }
  let reject = true;
  // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
  // direct enough, consider it a Swipe.
  for (let identifier in this.points) {
    let point = this.points[identifier];
    let directDistance = point.directDistanceTraveled;
    if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
      directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
      reject = false;
    }
  }
  if (reject) {
    this._deferred.reject(Explore);
  }
};

/**
 * Compile a swipe related mozAccessFuGesture event detail.
 * @return {Object} A mozAccessFuGesture detail object.
 */
Swipe.prototype.compile = function Swipe_compile() {
  let type = this.type;
  let detail = compileDetail(type, this.points,
    {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
  let deltaX = detail.deltaX;
  let deltaY = detail.deltaY;
  let edge = EDGE * Utils.dpi;
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
    // Horizontal swipe.
    let startPoints = detail.touches.map(touch => touch.x1);
    if (deltaX > 0) {
      detail.type = type + 'right';
      detail.edge = Math.min.apply(null, startPoints) <= edge;
    } else {
      detail.type = type + 'left';
      detail.edge =
        Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
    }
  } else {
    // Vertical swipe.
    let startPoints = detail.touches.map(touch => touch.y1);
    if (deltaY > 0) {
      detail.type = type + 'down';
      detail.edge = Math.min.apply(null, startPoints) <= edge;
    } else {
      detail.type = type + 'up';
      detail.edge =
        Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;
    }
  }
  return detail;
};
PK
!<7⺅)modules/accessibility/OutputGenerator.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 UtteranceGenerator, BrailleGenerator */

'use strict';

const {utils: Cu, interfaces: Ci} = Components;

const INCLUDE_DESC = 0x01;
const INCLUDE_NAME = 0x02;
const INCLUDE_VALUE = 0x04;
const NAME_FROM_SUBTREE_RULE = 0x10;
const IGNORE_EXPLICIT_NAME = 0x20;

const OUTPUT_DESC_FIRST = 0;
const OUTPUT_DESC_LAST = 1;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');

this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator']; // jshint ignore:line

var OutputGenerator = {

  defaultOutputOrder: OUTPUT_DESC_LAST,

  /**
   * Generates output for a PivotContext.
   * @param {PivotContext} aContext object that generates and caches
   *    context information for a given accessible and its relationship with
   *    another accessible.
   * @return {Object} An array of speech data. Depending on the utterance order,
   *    the data describes the context for an accessible object either
   *    starting from the accessible's ancestry or accessible's subtree.
   */
  genForContext: function genForContext(aContext) {
    let output = [];
    let self = this;
    let addOutput = function addOutput(aAccessible) {
      output.push.apply(output, self.genForObject(aAccessible, aContext));
    };
    let ignoreSubtree = function ignoreSubtree(aAccessible) {
      let roleString = Utils.AccService.getStringRole(aAccessible.role);
      let nameRule = self.roleRuleMap[roleString] || 0;
      // Ignore subtree if the name is explicit and the role's name rule is the
      // NAME_FROM_SUBTREE_RULE.
      return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
              ((nameRule & NAME_FROM_SUBTREE_RULE) &&
               (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
               !(nameRule & IGNORE_EXPLICIT_NAME))));
    };

    let contextStart = this._getContextStart(aContext);

    if (this.outputOrder === OUTPUT_DESC_FIRST) {
      contextStart.forEach(addOutput);
      addOutput(aContext.accessible);
      for (let node of aContext.subtreeGenerator(true, ignoreSubtree)) {
        addOutput(node);
      }
    } else {
      for (let node of aContext.subtreeGenerator(false, ignoreSubtree)) {
        addOutput(node);
      }
      addOutput(aContext.accessible);

      // If there are any documents in new ancestry, find a first one and place
      // it in the beginning of the utterance.
      let doc, docIndex = contextStart.findIndex(
        ancestor => ancestor.role === Roles.DOCUMENT);

      if (docIndex > -1) {
        doc = contextStart.splice(docIndex, 1)[0];
      }

      contextStart.reverse().forEach(addOutput);
      if (doc) {
        output.unshift.apply(output, self.genForObject(doc, aContext));
      }
    }

    return output;
  },


  /**
   * Generates output for an object.
   * @param {nsIAccessible} aAccessible accessible object to generate output
   *    for.
   * @param {PivotContext} aContext object that generates and caches
   *    context information for a given accessible and its relationship with
   *    another accessible.
   * @return {Array} A 2 element array of speech data. The first element
   *    describes the object and its state. The second element is the object's
   *    name. Whether the object's description or it's role is included is
   *    determined by {@link roleRuleMap}.
   */
  genForObject: function genForObject(aAccessible, aContext) {
    let roleString = Utils.AccService.getStringRole(aAccessible.role);
    let func = this.objectOutputFunctions[
      OutputGenerator._getOutputName(roleString)] ||
      this.objectOutputFunctions.defaultFunc;

    let flags = this.roleRuleMap[roleString] || 0;

    if (aAccessible.childCount === 0) {
      flags |= INCLUDE_NAME;
    }

    return func.apply(this, [aAccessible, roleString,
                             Utils.getState(aAccessible), flags, aContext]);
  },

  /**
   * Generates output for an action performed.
   * @param {nsIAccessible} aAccessible accessible object that the action was
   *    invoked in.
   * @param {string} aActionName the name of the action, one of the keys in
   *    {@link gActionMap}.
   * @return {Array} A one element array with action data.
   */
  genForAction: function genForAction(aObject, aActionName) {}, // jshint ignore:line

  /**
   * Generates output for an announcement.
   * @param {string} aAnnouncement unlocalized announcement.
   * @return {Array} An announcement speech data to be localized.
   */
  genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, // jshint ignore:line

  /**
   * Generates output for a tab state change.
   * @param {nsIAccessible} aAccessible accessible object of the tab's attached
   *    document.
   * @param {string} aTabState the tab state name, see
   *    {@link Presenter.tabStateChanged}.
   * @return {Array} The tab state utterace.
   */
  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, // jshint ignore:line

  /**
   * Generates output for announcing entering and leaving editing mode.
   * @param {aIsEditing} boolean true if we are in editing mode
   * @return {Array} The mode utterance
   */
  genForEditingMode: function genForEditingMode(aIsEditing) {}, // jshint ignore:line

  _getContextStart: function getContextStart(aContext) {}, // jshint ignore:line

  /**
   * Adds an accessible name and description to the output if available.
   * @param {Array} aOutput Output array.
   * @param {nsIAccessible} aAccessible current accessible object.
   * @param {Number} aFlags output flags.
   */
  _addName: function _addName(aOutput, aAccessible, aFlags) {
    let name;
    if ((Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
         !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
      name = aAccessible.name;
    }

    let description = aAccessible.description;
    if (description) {
      // Compare against the calculated name unconditionally, regardless of name rule,
      // so we can make sure we don't speak duplicated descriptions
      let tmpName = name || aAccessible.name;
      if (tmpName && (description !== tmpName)) {
        name = name || '';
        name = this.outputOrder === OUTPUT_DESC_FIRST ?
          description + ' - ' + name :
          name + ' - ' + description;
      }
    }

    if (!name || !name.trim()) {
      return;
    }
    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'](name);
  },

  /**
   * Adds a landmark role to the output if available.
   * @param {Array} aOutput Output array.
   * @param {nsIAccessible} aAccessible current accessible object.
   */
  _addLandmark: function _addLandmark(aOutput, aAccessible) {
    let landmarkName = Utils.getLandmarkName(aAccessible);
    if (!landmarkName) {
      return;
    }
    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'unshift' : 'push']({
      string: landmarkName
    });
  },

  /**
   * Adds math roles to the output, for a MathML accessible.
   * @param {Array} aOutput Output array.
   * @param {nsIAccessible} aAccessible current accessible object.
   * @param {String} aRoleStr aAccessible's role string.
   */
  _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
    // First, determine the actual role to use (e.g. mathmlfraction).
    let roleStr = aRoleStr;
    switch (aAccessible.role) {
      case Roles.MATHML_CELL:
      case Roles.MATHML_ENCLOSED:
      case Roles.MATHML_LABELED_ROW:
      case Roles.MATHML_ROOT:
      case Roles.MATHML_SQUARE_ROOT:
      case Roles.MATHML_TABLE:
      case Roles.MATHML_TABLE_ROW:
        // Use the default role string.
        break;
      case Roles.MATHML_MULTISCRIPTS:
      case Roles.MATHML_OVER:
      case Roles.MATHML_SUB:
      case Roles.MATHML_SUB_SUP:
      case Roles.MATHML_SUP:
      case Roles.MATHML_UNDER:
      case Roles.MATHML_UNDER_OVER:
        // For scripted accessibles, use the string 'mathmlscripted'.
        roleStr = 'mathmlscripted';
        break;
      case Roles.MATHML_FRACTION:
        // From a semantic point of view, the only important point is to
        // distinguish between fractions that have a bar and those that do not.
        // Per the MathML 3 spec, the latter happens iff the linethickness
        // attribute is of the form [zero-float][optional-unit]. In that case,
        // we use the string 'mathmlfractionwithoutbar'.
        let linethickness = Utils.getAttributes(aAccessible).linethickness;
        if (linethickness) {
            let numberMatch = linethickness.match(/^(?:\d|\.)+/);
            if (numberMatch && !parseFloat(numberMatch[0])) {
                roleStr += 'withoutbar';
            }
        }
        break;
      default:
        // Otherwise, do not output the actual role.
        roleStr = null;
        break;
    }

    // Get the math role based on the position in the parent accessible
    // (e.g. numerator for the first child of a mathmlfraction).
    let mathRole = Utils.getMathRole(aAccessible);
    if (mathRole) {
      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']({
        string: this._getOutputName(mathRole)});
    }
    if (roleStr) {
      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']({
        string: this._getOutputName(roleStr)});
    }
  },

  /**
   * Adds MathML menclose notations to the output.
   * @param {Array} aOutput Output array.
   * @param {nsIAccessible} aAccessible current accessible object.
   */
  _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
    let notations = Utils.getAttributes(aAccessible).notation || 'longdiv';
    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'].apply(
      aOutput, notations.split(' ').map(notation => {
        return { string: this._getOutputName('notation-' + notation) };
      }));
  },

  /**
   * Adds an entry type attribute to the description if available.
   * @param {Array} aOutput Output array.
   * @param {nsIAccessible} aAccessible current accessible object.
   * @param {String} aRoleStr aAccessible's role string.
   */
  _addType: function _addType(aOutput, aAccessible, aRoleStr) {
    if (aRoleStr !== 'entry') {
      return;
    }

    let typeName = Utils.getAttributes(aAccessible)['text-input-type'];
    // Ignore the the input type="text" case.
    if (!typeName || typeName === 'text') {
      return;
    }
    aOutput.push({string: 'textInputType_' + typeName});
  },

  _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line

  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {}, // jshint ignore:line

  get outputOrder() {
    if (!this._utteranceOrder) {
      this._utteranceOrder = new PrefCache('accessibility.accessfu.utterance');
    }
    return typeof this._utteranceOrder.value === 'number' ?
      this._utteranceOrder.value : this.defaultOutputOrder;
  },

  _getOutputName: function _getOutputName(aName) {
    return aName.replace(/\s/g, '');
  },

  roleRuleMap: {
    'menubar': INCLUDE_DESC,
    'scrollbar': INCLUDE_DESC,
    'grip': INCLUDE_DESC,
    'alert': INCLUDE_DESC | INCLUDE_NAME,
    'menupopup': INCLUDE_DESC,
    'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'columnheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'rowheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'column': NAME_FROM_SUBTREE_RULE,
    'row': NAME_FROM_SUBTREE_RULE,
    'cell': INCLUDE_DESC | INCLUDE_NAME,
    'application': INCLUDE_NAME,
    'document': INCLUDE_NAME,
    'grouping': INCLUDE_DESC | INCLUDE_NAME,
    'toolbar': INCLUDE_DESC,
    'table': INCLUDE_DESC | INCLUDE_NAME,
    'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'helpballoon': NAME_FROM_SUBTREE_RULE,
    'list': INCLUDE_DESC | INCLUDE_NAME,
    'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'outline': INCLUDE_DESC,
    'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'graphic': INCLUDE_DESC,
    'switch': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'buttondropdown': NAME_FROM_SUBTREE_RULE,
    'combobox': INCLUDE_DESC | INCLUDE_VALUE,
    'droplist': INCLUDE_DESC,
    'progressbar': INCLUDE_DESC | INCLUDE_VALUE,
    'slider': INCLUDE_DESC | INCLUDE_VALUE,
    'spinbutton': INCLUDE_DESC | INCLUDE_VALUE,
    'diagram': INCLUDE_DESC,
    'animation': INCLUDE_DESC,
    'equation': INCLUDE_DESC,
    'buttonmenu': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'buttondropdowngrid': NAME_FROM_SUBTREE_RULE,
    'pagetablist': INCLUDE_DESC,
    'canvas': INCLUDE_DESC,
    'check menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'label': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'password text': INCLUDE_DESC,
    'popup menu': INCLUDE_DESC,
    'radio menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'table column header': NAME_FROM_SUBTREE_RULE,
    'table row header': NAME_FROM_SUBTREE_RULE,
    'tear off menu item': NAME_FROM_SUBTREE_RULE,
    'toggle button': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'parent menuitem': NAME_FROM_SUBTREE_RULE,
    'header': INCLUDE_DESC,
    'footer': INCLUDE_DESC,
    'entry': INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
    'caption': INCLUDE_DESC,
    'document frame': INCLUDE_DESC,
    'heading': INCLUDE_DESC,
    'calendar': INCLUDE_DESC | INCLUDE_NAME,
    'combobox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'listbox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
    'listbox rich option': NAME_FROM_SUBTREE_RULE,
    'gridcell': NAME_FROM_SUBTREE_RULE,
    'check rich option': NAME_FROM_SUBTREE_RULE,
    'term': NAME_FROM_SUBTREE_RULE,
    'definition': NAME_FROM_SUBTREE_RULE,
    'key': NAME_FROM_SUBTREE_RULE,
    'image map': INCLUDE_DESC,
    'option': INCLUDE_DESC,
    'listbox': INCLUDE_DESC,
    'definitionlist': INCLUDE_DESC | INCLUDE_NAME,
    'dialog': INCLUDE_DESC | INCLUDE_NAME,
    'chrome window': IGNORE_EXPLICIT_NAME,
    'app root': IGNORE_EXPLICIT_NAME,
    'statusbar': NAME_FROM_SUBTREE_RULE,
    'mathml table': INCLUDE_DESC | INCLUDE_NAME,
    'mathml labeled row': NAME_FROM_SUBTREE_RULE,
    'mathml table row': NAME_FROM_SUBTREE_RULE,
    'mathml cell': INCLUDE_DESC | INCLUDE_NAME,
    'mathml fraction': INCLUDE_DESC,
    'mathml square root': INCLUDE_DESC,
    'mathml root': INCLUDE_DESC,
    'mathml enclosed': INCLUDE_DESC,
    'mathml sub': INCLUDE_DESC,
    'mathml sup': INCLUDE_DESC,
    'mathml sub sup': INCLUDE_DESC,
    'mathml under': INCLUDE_DESC,
    'mathml over': INCLUDE_DESC,
    'mathml under over': INCLUDE_DESC,
    'mathml multiscripts': INCLUDE_DESC,
    'mathml identifier': INCLUDE_DESC,
    'mathml number': INCLUDE_DESC,
    'mathml operator': INCLUDE_DESC,
    'mathml text': INCLUDE_DESC,
    'mathml string literal': INCLUDE_DESC,
    'mathml row': INCLUDE_DESC,
    'mathml style': INCLUDE_DESC,
    'mathml error': INCLUDE_DESC },

  mathmlRolesSet: new Set([
    Roles.MATHML_MATH,
    Roles.MATHML_IDENTIFIER,
    Roles.MATHML_NUMBER,
    Roles.MATHML_OPERATOR,
    Roles.MATHML_TEXT,
    Roles.MATHML_STRING_LITERAL,
    Roles.MATHML_GLYPH,
    Roles.MATHML_ROW,
    Roles.MATHML_FRACTION,
    Roles.MATHML_SQUARE_ROOT,
    Roles.MATHML_ROOT,
    Roles.MATHML_FENCED,
    Roles.MATHML_ENCLOSED,
    Roles.MATHML_STYLE,
    Roles.MATHML_SUB,
    Roles.MATHML_SUP,
    Roles.MATHML_SUB_SUP,
    Roles.MATHML_UNDER,
    Roles.MATHML_OVER,
    Roles.MATHML_UNDER_OVER,
    Roles.MATHML_MULTISCRIPTS,
    Roles.MATHML_TABLE,
    Roles.LABELED_ROW,
    Roles.MATHML_TABLE_ROW,
    Roles.MATHML_CELL,
    Roles.MATHML_ACTION,
    Roles.MATHML_ERROR,
    Roles.MATHML_STACK,
    Roles.MATHML_LONG_DIVISION,
    Roles.MATHML_STACK_GROUP,
    Roles.MATHML_STACK_ROW,
    Roles.MATHML_STACK_CARRIES,
    Roles.MATHML_STACK_CARRY,
    Roles.MATHML_STACK_LINE
  ]),

  objectOutputFunctions: {
    _generateBaseOutput:
      function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
        let output = [];

        if (aFlags & INCLUDE_DESC) {
          this._addState(output, aState, aRoleStr);
          this._addType(output, aAccessible, aRoleStr);
          this._addRole(output, aAccessible, aRoleStr);
        }

        if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
          output[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'](
            aAccessible.value);
        }

        this._addName(output, aAccessible, aFlags);
        this._addLandmark(output, aAccessible);

        return output;
      },

    label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
      if (aContext.isNestedControl ||
          aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
        // If we are on a nested control, or a nesting label,
        // we don't need the context.
        return [];
      }

      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
    },

    entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
      let rolestr = aState.contains(States.MULTI_LINE) ? 'textarea' : 'entry';
      return this.objectOutputFunctions.defaultFunc.apply(
        this, [aAccessible, rolestr, aState, aFlags]);
    },

    pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
      let itemno = {};
      let itemof = {};
      aAccessible.groupPosition({}, itemof, itemno);
      let output = [];
      this._addState(output, aState);
      this._addRole(output, aAccessible, aRoleStr);
      output.push({
        string: 'objItemOfN',
        args: [itemno.value, itemof.value]
      });

      this._addName(output, aAccessible, aFlags);
      this._addLandmark(output, aAccessible);

      return output;
    },

    table: function table(aAccessible, aRoleStr, aState, aFlags) {
      let output = [];
      let table;
      try {
        table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
      } catch (x) {
        Logger.logException(x);
        return output;
      } finally {
        // Check if it's a layout table, and bail out if true.
        // We don't want to speak any table information for layout tables.
        if (table.isProbablyForLayout()) {
          return output;
        }
        this._addRole(output, aAccessible, aRoleStr);
        output.push.call(output, {
          string: this._getOutputName('tblColumnInfo'),
          count: table.columnCount
        }, {
          string: this._getOutputName('tblRowInfo'),
          count: table.rowCount
        });
        this._addName(output, aAccessible, aFlags);
        this._addLandmark(output, aAccessible);
        return output;
      }
    },

    gridcell: function gridcell(aAccessible, aRoleStr, aState, aFlags) {
      let output = [];
      this._addState(output, aState);
      this._addName(output, aAccessible, aFlags);
      this._addLandmark(output, aAccessible);
      return output;
    },

    // Use the table output functions for MathML tabular elements.
    mathmltable: function mathmltable() {
      return this.objectOutputFunctions.table.apply(this, arguments);
    },

    mathmlcell: function mathmlcell() {
      return this.objectOutputFunctions.cell.apply(this, arguments);
    },

    mathmlenclosed: function mathmlenclosed(aAccessible, aRoleStr, aState,
                                            aFlags, aContext) {
      let output = this.objectOutputFunctions.defaultFunc.
        apply(this, [aAccessible, aRoleStr, aState, aFlags, aContext]);
      this._addMencloseNotations(output, aAccessible);
      return output;
    }
  }
};

/**
 * Generates speech utterances from objects, actions and state changes.
 * An utterance is an array of speech data.
 *
 * It should not be assumed that flattening an utterance array would create a
 * gramatically correct sentence. For example, {@link genForObject} might
 * return: ['graphic', 'Welcome to my home page'].
 * Each string element in an utterance should be gramatically correct in itself.
 * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
 *
 * An utterance is ordered from the least to the most important. Speaking the
 * last string usually makes sense, but speaking the first often won't.
 * For example {@link genForAction} might return ['button', 'clicked'] for a
 * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
 * not.
 */
this.UtteranceGenerator = {  // jshint ignore:line
  __proto__: OutputGenerator, // jshint ignore:line

  gActionMap: {
    jump: 'jumpAction',
    press: 'pressAction',
    check: 'checkAction',
    uncheck: 'uncheckAction',
    on: 'onAction',
    off: 'offAction',
    select: 'selectAction',
    unselect: 'unselectAction',
    open: 'openAction',
    close: 'closeAction',
    switch: 'switchAction',
    click: 'clickAction',
    collapse: 'collapseAction',
    expand: 'expandAction',
    activate: 'activateAction',
    cycle: 'cycleAction'
  },

  //TODO: May become more verbose in the future.
  genForAction: function genForAction(aObject, aActionName) {
    return [{string: this.gActionMap[aActionName]}];
  },

  genForLiveRegion:
    function genForLiveRegion(aContext, aIsHide, aModifiedText) {
      let utterance = [];
      if (aIsHide) {
        utterance.push({string: 'hidden'});
      }
      return utterance.concat(aModifiedText || this.genForContext(aContext));
    },

  genForAnnouncement: function genForAnnouncement(aAnnouncement) {
    return [{
      string: aAnnouncement
    }];
  },

  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
    switch (aTabState) {
      case 'newtab':
        return [{string: 'tabNew'}];
      case 'loading':
        return [{string: 'tabLoading'}];
      case 'loaded':
        return [aObject.name, {string: 'tabLoaded'}];
      case 'loadstopped':
        return [{string: 'tabLoadStopped'}];
      case 'reload':
        return [{string: 'tabReload'}];
      default:
        return [];
    }
  },

  genForEditingMode: function genForEditingMode(aIsEditing) {
    return [{string: aIsEditing ? 'editingMode' : 'navigationMode'}];
  },

  objectOutputFunctions: {

    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line

    defaultFunc: function defaultFunc() {
      return this.objectOutputFunctions._generateBaseOutput.apply(
        this, arguments);
    },

    heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
      let level = {};
      aAccessible.groupPosition(level, {}, {});
      let utterance = [{string: 'headingLevel', args: [level.value]}];

      this._addName(utterance, aAccessible, aFlags);
      this._addLandmark(utterance, aAccessible);

      return utterance;
    },

    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
      let itemno = {};
      let itemof = {};
      aAccessible.groupPosition({}, itemof, itemno);
      let utterance = [];
      if (itemno.value == 1) {
        // Start of list
        utterance.push({string: 'listStart'});
      }
      else if (itemno.value == itemof.value) {
        // last item
        utterance.push({string: 'listEnd'});
      }

      this._addName(utterance, aAccessible, aFlags);
      this._addLandmark(utterance, aAccessible);

      return utterance;
    },

    list: function list(aAccessible, aRoleStr, aState, aFlags) {
      return this._getListUtterance(aAccessible, aRoleStr, aFlags,
        aAccessible.childCount);
    },

    definitionlist:
      function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
        return this._getListUtterance(aAccessible, aRoleStr, aFlags,
          aAccessible.childCount / 2);
      },

    application: function application(aAccessible, aRoleStr, aState, aFlags) {
      // Don't utter location of applications, it gets tiring.
      if (aAccessible.name != aAccessible.DOMNode.location) {
        return this.objectOutputFunctions.defaultFunc.apply(this,
          [aAccessible, aRoleStr, aState, aFlags]);
      }

      return [];
    },

    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
      let utterance = [];
      let cell = aContext.getCellInfo(aAccessible);
      if (cell) {
        let addCellChanged =
          function addCellChanged(aUtterance, aChanged, aString, aIndex) {
            if (aChanged) {
              aUtterance.push({string: aString, args: [aIndex + 1]});
            }
          };
        let addExtent = function addExtent(aUtterance, aExtent, aString) {
          if (aExtent > 1) {
            aUtterance.push({string: aString, args: [aExtent]});
          }
        };
        let addHeaders = function addHeaders(aUtterance, aHeaders) {
          if (aHeaders.length > 0) {
            aUtterance.push.apply(aUtterance, aHeaders);
          }
        };

        addCellChanged(utterance, cell.columnChanged, 'columnInfo',
          cell.columnIndex);
        addCellChanged(utterance, cell.rowChanged, 'rowInfo', cell.rowIndex);

        addExtent(utterance, cell.columnExtent, 'spansColumns');
        addExtent(utterance, cell.rowExtent, 'spansRows');

        addHeaders(utterance, cell.columnHeaders);
        addHeaders(utterance, cell.rowHeaders);
      }

      this._addName(utterance, aAccessible, aFlags);
      this._addLandmark(utterance, aAccessible);

      return utterance;
    },

    columnheader: function columnheader() {
      return this.objectOutputFunctions.cell.apply(this, arguments);
    },

    rowheader: function rowheader() {
      return this.objectOutputFunctions.cell.apply(this, arguments);
    },

    statictext: function statictext(aAccessible) {
      if (Utils.isListItemDecorator(aAccessible, true)) {
        return [];
      }

      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
    }
  },

  _getContextStart: function _getContextStart(aContext) {
    return aContext.newAncestry;
  },

  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {
    if (this.mathmlRolesSet.has(aAccessible.role)) {
      this._addMathRoles(aOutput, aAccessible, aRoleStr);
    } else {
      aOutput.push({string: this._getOutputName(aRoleStr)});
    }
  },

  _addState: function _addState(aOutput, aState, aRoleStr) {

    if (aState.contains(States.UNAVAILABLE)) {
      aOutput.push({string: 'stateUnavailable'});
    }

    if (aState.contains(States.READONLY)) {
      aOutput.push({string: 'stateReadonly'});
    }

    // Don't utter this in Jelly Bean, we let TalkBack do it for us there.
    // This is because we expose the checked information on the node itself.
    // XXX: this means the checked state is always appended to the end,
    // regardless of the utterance ordering preference.
    if ((Utils.AndroidSdkVersion < 16 || Utils.MozBuildApp === 'browser') &&
      aState.contains(States.CHECKABLE)) {
      let checked = aState.contains(States.CHECKED);
      let statetr;
      if (aRoleStr === 'switch') {
        statetr = checked ? 'stateOn' : 'stateOff';
      } else {
        statetr = checked ? 'stateChecked' : 'stateNotChecked';
      }
      aOutput.push({string: statetr});
    }

    if (aState.contains(States.PRESSED)) {
      aOutput.push({string: 'statePressed'});
    }

    if (aState.contains(States.EXPANDABLE)) {
      let statetr = aState.contains(States.EXPANDED) ?
        'stateExpanded' : 'stateCollapsed';
      aOutput.push({string: statetr});
    }

    if (aState.contains(States.REQUIRED)) {
      aOutput.push({string: 'stateRequired'});
    }

    if (aState.contains(States.TRAVERSED)) {
      aOutput.push({string: 'stateTraversed'});
    }

    if (aState.contains(States.HASPOPUP)) {
      aOutput.push({string: 'stateHasPopup'});
    }

    if (aState.contains(States.SELECTED)) {
      aOutput.push({string: 'stateSelected'});
    }
  },

  _getListUtterance:
    function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
      let utterance = [];
      this._addRole(utterance, aAccessible, aRoleStr);
      utterance.push({
        string: this._getOutputName('listItemsCount'),
        count: aItemCount
      });

      this._addName(utterance, aAccessible, aFlags);
      this._addLandmark(utterance, aAccessible);

      return utterance;
    }
};

this.BrailleGenerator = {  // jshint ignore:line
  __proto__: OutputGenerator, // jshint ignore:line

  genForContext: function genForContext(aContext) {
    let output = OutputGenerator.genForContext.apply(this, arguments);

    let acc = aContext.accessible;

    // add the static text indicating a list item; do this for both listitems or
    // direct first children of listitems, because these are both common
    // browsing scenarios
    let addListitemIndicator = function addListitemIndicator(indicator = '*') {
      output.unshift(indicator);
    };

    if (acc.indexInParent === 1 &&
        acc.parent.role == Roles.LISTITEM &&
        acc.previousSibling.role == Roles.STATICTEXT) {
      if (acc.parent.parent && acc.parent.parent.DOMNode &&
          acc.parent.parent.DOMNode.nodeName == 'UL') {
        addListitemIndicator();
      } else {
        addListitemIndicator(acc.previousSibling.name.trim());
      }
    } else if (acc.role == Roles.LISTITEM && acc.firstChild &&
               acc.firstChild.role == Roles.STATICTEXT) {
      if (acc.parent.DOMNode.nodeName == 'UL') {
        addListitemIndicator();
      } else {
        addListitemIndicator(acc.firstChild.name.trim());
      }
    }

    return output;
  },

  objectOutputFunctions: {

    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line

    defaultFunc: function defaultFunc() {
      return this.objectOutputFunctions._generateBaseOutput.apply(
        this, arguments);
    },

    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
      let braille = [];

      this._addName(braille, aAccessible, aFlags);
      this._addLandmark(braille, aAccessible);

      return braille;
    },

    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
      let braille = [];
      let cell = aContext.getCellInfo(aAccessible);
      if (cell) {
        let addHeaders = function addHeaders(aBraille, aHeaders) {
          if (aHeaders.length > 0) {
            aBraille.push.apply(aBraille, aHeaders);
          }
        };

        braille.push({
          string: this._getOutputName('cellInfo'),
          args: [cell.columnIndex + 1, cell.rowIndex + 1]
        });

        addHeaders(braille, cell.columnHeaders);
        addHeaders(braille, cell.rowHeaders);
      }

      this._addName(braille, aAccessible, aFlags);
      this._addLandmark(braille, aAccessible);
      return braille;
    },

    columnheader: function columnheader() {
      return this.objectOutputFunctions.cell.apply(this, arguments);
    },

    rowheader: function rowheader() {
      return this.objectOutputFunctions.cell.apply(this, arguments);
    },

    statictext: function statictext(aAccessible) {
      // Since we customize the list bullet's output, we add the static
      // text from the first node in each listitem, so skip it here.
      if (Utils.isListItemDecorator(aAccessible)) {
        return [];
      }

      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
    },

    _useStateNotRole:
      function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) {
        let braille = [];
        this._addState(braille, aState, aRoleStr);
        this._addName(braille, aAccessible, aFlags);
        this._addLandmark(braille, aAccessible);

        return braille;
      },

    switch: function braille_generator_object_output_functions_switch() {
      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
    },

    checkbutton: function checkbutton() {
      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
    },

    radiobutton: function radiobutton() {
      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
    },

    togglebutton: function togglebutton() {
      return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
    }
  },

  _getContextStart: function _getContextStart(aContext) {
    if (aContext.accessible.parent.role == Roles.LINK) {
      return [aContext.accessible.parent];
    }

    return [];
  },

  _getOutputName: function _getOutputName(aName) {
    return OutputGenerator._getOutputName(aName) + 'Abbr';
  },

  _addRole: function _addRole(aBraille, aAccessible, aRoleStr) {
    if (this.mathmlRolesSet.has(aAccessible.role)) {
      this._addMathRoles(aBraille, aAccessible, aRoleStr);
    } else {
      aBraille.push({string: this._getOutputName(aRoleStr)});
    }
  },

  _addState: function _addState(aBraille, aState, aRoleStr) {
    if (aState.contains(States.CHECKABLE)) {
      aBraille.push({
        string: aState.contains(States.CHECKED) ?
          this._getOutputName('stateChecked') :
          this._getOutputName('stateUnchecked')
      });
    }
    if (aRoleStr === 'toggle button') {
      aBraille.push({
        string: aState.contains(States.PRESSED) ?
          this._getOutputName('statePressed') :
          this._getOutputName('stateUnpressed')
      });
    }
  }
};
PK
!<5{{(modules/accessibility/PointerAdapter.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 PointerRelay, PointerAdapter */

'use strict';

const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line

Cu.import('resource://gre/modules/XPCOMUtils.jsm');

XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line
  'resource://gre/modules/accessibility/Gestures.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line
  'resource://gre/modules/accessibility/Gestures.jsm');

// The virtual touch ID generated by a mouse event.
const MOUSE_ID = 'mouse';
// Synthesized touch ID.
const SYNTH_ID = -1;

var PointerRelay = { // jshint ignore:line
  /**
   * A mapping of events we should be intercepting. Entries with a value of
   * |true| are used for compiling high-level gesture events. Entries with a
   * value of |false| are cancelled and do not propogate to content.
   */
  get _eventsOfInterest() {
    delete this._eventsOfInterest;

    switch (Utils.widgetToolkit) {
      case 'android':
        this._eventsOfInterest = {
          'touchstart': true,
          'touchmove': true,
          'touchend': true };
        break;

      case 'gonk':
        this._eventsOfInterest = {
          'touchstart': true,
          'touchmove': true,
          'touchend': true,
          'mousedown': false,
          'mousemove': false,
          'mouseup': false,
          'click': false };
        break;

      default:
        // Desktop.
        this._eventsOfInterest = {
          'mousemove': true,
          'mousedown': true,
          'mouseup': true,
          'click': false
        };
        if ('ontouchstart' in Utils.win) {
          for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
            this._eventsOfInterest[eventType] = true;
          }
        }
        break;
    }

    return this._eventsOfInterest;
  },

  _eventMap: {
    'touchstart': 'pointerdown',
    'mousedown': 'pointerdown',
    'touchmove': 'pointermove',
    'mousemove': 'pointermove',
    'touchend': 'pointerup',
    'mouseup': 'pointerup'
  },

  start: function PointerRelay_start(aOnPointerEvent) {
    Logger.debug('PointerRelay.start');
    this.onPointerEvent = aOnPointerEvent;
    for (let eventType in this._eventsOfInterest) {
      Utils.win.addEventListener(eventType, this, true, true);
    }
  },

  stop: function PointerRelay_stop() {
    Logger.debug('PointerRelay.stop');
    delete this.lastPointerMove;
    delete this.onPointerEvent;
    for (let eventType in this._eventsOfInterest) {
      Utils.win.removeEventListener(eventType, this, true, true);
    }
  },

  handleEvent: function PointerRelay_handleEvent(aEvent) {
    // Don't bother with chrome mouse events.
    if (Utils.MozBuildApp === 'browser' &&
      aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
      return;
    }
    if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN ||
        aEvent.isSynthesized) {
      // Ignore events that are scripted or clicks from the a11y API.
      return;
    }

    let changedTouches = aEvent.changedTouches || [{
      identifier: MOUSE_ID,
      screenX: aEvent.screenX,
      screenY: aEvent.screenY,
      target: aEvent.target
    }];

    if (Utils.widgetToolkit === 'android' &&
      changedTouches.length === 1 && changedTouches[0].identifier === 1) {
      return;
    }

    if (changedTouches.length === 1 &&
        changedTouches[0].identifier === SYNTH_ID) {
      return;
    }

    aEvent.preventDefault();
    aEvent.stopImmediatePropagation();

    let type = aEvent.type;
    if (!this._eventsOfInterest[type]) {
      return;
    }
    let pointerType = this._eventMap[type];
    this.onPointerEvent({
      type: pointerType,
      points: Array.prototype.map.call(changedTouches,
        function mapTouch(aTouch) {
          return {
            identifier: aTouch.identifier,
            x: aTouch.screenX,
            y: aTouch.screenY
          };
        }
      )
    });
  }
};

this.PointerAdapter = { // jshint ignore:line
  start: function PointerAdapter_start() {
    Logger.debug('PointerAdapter.start');
    GestureTracker.reset();
    PointerRelay.start(this.handleEvent);
  },

  stop: function PointerAdapter_stop() {
    Logger.debug('PointerAdapter.stop');
    PointerRelay.stop();
    GestureTracker.reset();
  },

  handleEvent: function PointerAdapter_handleEvent(aDetail) {
    let timeStamp = Date.now();
    GestureTracker.handle(aDetail, timeStamp);
  }
};
PK
!<[7M``&modules/accessibility/Presentation.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 Presentation */

'use strict';

const {utils: Cu, interfaces: Ci} = Components;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext', // jshint ignore:line
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator', // jshint ignore:line
  'resource://gre/modules/accessibility/OutputGenerator.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator', // jshint ignore:line
  'resource://gre/modules/accessibility/OutputGenerator.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');

this.EXPORTED_SYMBOLS = ['Presentation']; // jshint ignore:line

/**
 * The interface for all presenter classes. A presenter could be, for example,
 * a speech output module, or a visual cursor indicator.
 */
function Presenter() {}

Presenter.prototype = {
  /**
   * The type of presenter. Used for matching it with the appropriate output method.
   */
  type: 'Base',

  /**
   * The virtual cursor's position changed.
   * @param {PivotContext} aContext the context object for the new pivot
   *   position.
   * @param {int} aReason the reason for the pivot change.
   *   See nsIAccessiblePivot.
   * @param {bool} aIsFromUserInput the pivot change was invoked by the user
   */
  pivotChanged: function pivotChanged(aContext, aReason, aIsFromUserInput) {}, // jshint ignore:line

  /**
   * An object's action has been invoked.
   * @param {nsIAccessible} aObject the object that has been invoked.
   * @param {string} aActionName the name of the action.
   */
  actionInvoked: function actionInvoked(aObject, aActionName) {}, // jshint ignore:line

  /**
   * Text has changed, either by the user or by the system. TODO.
   */
  textChanged: function textChanged(aAccessible, aIsInserted, aStartOffset, // jshint ignore:line
                                    aLength, aText, aModifiedText) {}, // jshint ignore:line

  /**
   * Text selection has changed. TODO.
   */
  textSelectionChanged: function textSelectionChanged(
    aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {}, // jshint ignore:line

  /**
   * Selection has changed. TODO.
   * @param {nsIAccessible} aObject the object that has been selected.
   */
  selectionChanged: function selectionChanged(aObject) {}, // jshint ignore:line

  /**
   * Name has changed.
   * @param {nsIAccessible} aAccessible the object whose value has changed.
   */
  nameChanged: function nameChanged(aAccessible) {}, // jshint ignore: line

  /**
   * Value has changed.
   * @param {nsIAccessible} aAccessible the object whose value has changed.
   */
  valueChanged: function valueChanged(aAccessible) {}, // jshint ignore:line

  /**
   * The tab, or the tab's document state has changed.
   * @param {nsIAccessible} aDocObj the tab document accessible that has had its
   *    state changed, or null if the tab has no associated document yet.
   * @param {string} aPageState the state name for the tab, valid states are:
   *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
   */
  tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, // jshint ignore:line

  /**
   * The current tab has changed.
   * @param {PivotContext} aDocContext context object for tab's
   *   document.
   * @param {PivotContext} aVCContext context object for tab's current
   *   virtual cursor position.
   */
  tabSelected: function tabSelected(aDocContext, aVCContext) {}, // jshint ignore:line

  /**
   * The viewport has changed, either a scroll, pan, zoom, or
   *    landscape/portrait toggle.
   * @param {Window} aWindow window of viewport that changed.
   * @param {PivotContext} aCurrentContext context of last pivot change.
   */
  viewportChanged: function viewportChanged(aWindow, aCurrentContext) {}, // jshint ignore:line

  /**
   * We have entered or left text editing mode.
   */
  editingModeChanged: function editingModeChanged(aIsEditing) {}, // jshint ignore:line

  /**
   * Announce something. Typically an app state change.
   */
  announce: function announce(aAnnouncement) {}, // jshint ignore:line


  /**
   * User tried to move cursor forward or backward with no success.
   * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
   */
  noMove: function noMove(aMoveMethod) {},

  /**
   * Announce a live region.
   * @param  {PivotContext} aContext context object for an accessible.
   * @param  {boolean} aIsPolite A politeness level for a live region.
   * @param  {boolean} aIsHide An indicator of hide/remove event.
   * @param  {string} aModifiedText Optional modified text.
   */
  liveRegion: function liveRegionShown(aContext, aIsPolite, aIsHide, // jshint ignore:line
    aModifiedText) {} // jshint ignore:line
};

/**
 * Visual presenter. Draws a box around the virtual cursor's position.
 */
function VisualPresenter() {}

VisualPresenter.prototype = Object.create(Presenter.prototype);

VisualPresenter.prototype.type = 'Visual';

/**
 * The padding in pixels between the object and the highlight border.
 */
VisualPresenter.prototype.BORDER_PADDING = 2;

VisualPresenter.prototype.viewportChanged =
  function VisualPresenter_viewportChanged(aWindow, aCurrentContext) {
    if (!aCurrentContext) {
      return null;
    }

    let currentAcc = aCurrentContext.accessibleForBounds;
    let start = aCurrentContext.startOffset;
    let end = aCurrentContext.endOffset;
    if (Utils.isAliveAndVisible(currentAcc)) {
      let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) :
                   Utils.getTextBounds(currentAcc, start, end);

      return {
        type: this.type,
        details: {
          eventType: 'viewport-change',
          bounds: bounds,
          padding: this.BORDER_PADDING
        }
      };
    }

    return null;
  };

VisualPresenter.prototype.pivotChanged =
  function VisualPresenter_pivotChanged(aContext) {
    if (!aContext.accessible) {
      // XXX: Don't hide because another vc may be using the highlight.
      return null;
    }

    try {
      aContext.accessibleForBounds.scrollTo(
        Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);

      let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ?
            aContext.bounds : Utils.getTextBounds(aContext.accessibleForBounds,
                                                  aContext.startOffset,
                                                  aContext.endOffset);

      return {
        type: this.type,
        details: {
          eventType: 'vc-change',
          bounds: bounds,
          padding: this.BORDER_PADDING
        }
      };
    } catch (e) {
      Logger.logException(e, 'Failed to get bounds');
      return null;
    }
  };

VisualPresenter.prototype.tabSelected =
  function VisualPresenter_tabSelected(aDocContext, aVCContext) {
    return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
  };

VisualPresenter.prototype.tabStateChanged =
  function VisualPresenter_tabStateChanged(aDocObj, aPageState) {
    if (aPageState == 'newdoc') {
      return {type: this.type, details: {eventType: 'tabstate-change'}};
    }

    return null;
  };

/**
 * Android presenter. Fires Android a11y events.
 */
function AndroidPresenter() {}

AndroidPresenter.prototype = Object.create(Presenter.prototype);

AndroidPresenter.prototype.type = 'Android';

// Android AccessibilityEvent type constants.
AndroidPresenter.prototype.ANDROID_VIEW_CLICKED = 0x01;
AndroidPresenter.prototype.ANDROID_VIEW_LONG_CLICKED = 0x02;
AndroidPresenter.prototype.ANDROID_VIEW_SELECTED = 0x04;
AndroidPresenter.prototype.ANDROID_VIEW_FOCUSED = 0x08;
AndroidPresenter.prototype.ANDROID_VIEW_TEXT_CHANGED = 0x10;
AndroidPresenter.prototype.ANDROID_WINDOW_STATE_CHANGED = 0x20;
AndroidPresenter.prototype.ANDROID_VIEW_HOVER_ENTER = 0x80;
AndroidPresenter.prototype.ANDROID_VIEW_HOVER_EXIT = 0x100;
AndroidPresenter.prototype.ANDROID_VIEW_SCROLLED = 0x1000;
AndroidPresenter.prototype.ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
AndroidPresenter.prototype.ANDROID_ANNOUNCEMENT = 0x4000;
AndroidPresenter.prototype.ANDROID_VIEW_ACCESSIBILITY_FOCUSED = 0x8000;
AndroidPresenter.prototype.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY =
  0x20000;

AndroidPresenter.prototype.pivotChanged =
  function AndroidPresenter_pivotChanged(aContext, aReason) {
    if (!aContext.accessible) {
      return null;
    }

    let androidEvents = [];

    let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT &&
                            Utils.AndroidSdkVersion >= 14);
    let focusEventType = (Utils.AndroidSdkVersion >= 16) ?
      this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED :
      this.ANDROID_VIEW_FOCUSED;

    if (isExploreByTouch) {
      // This isn't really used by TalkBack so this is a half-hearted attempt
      // for now.
      androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
    }

    let brailleOutput = {};
    if (Utils.AndroidSdkVersion >= 16) {
      if (!this._braillePresenter) {
        this._braillePresenter = new BraillePresenter();
      }
      brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason).
                         details;
    }

    if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
      if (Utils.AndroidSdkVersion >= 16) {
        let adjustedText = aContext.textAndAdjustedOffsets;

        androidEvents.push({
          eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
          text: [adjustedText.text],
          fromIndex: adjustedText.startOffset,
          toIndex: adjustedText.endOffset
        });
      }
    } else {
      let state = Utils.getState(aContext.accessible);
      androidEvents.push({eventType: (isExploreByTouch) ?
                           this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
                         text: Utils.localize(UtteranceGenerator.genForContext(
                           aContext)),
                         bounds: aContext.bounds,
                         clickable: aContext.accessible.actionCount > 0,
                         checkable: state.contains(States.CHECKABLE),
                         checked: state.contains(States.CHECKED),
                         brailleOutput: brailleOutput});
    }


    return {
      type: this.type,
      details: androidEvents
    };
  };

AndroidPresenter.prototype.actionInvoked =
  function AndroidPresenter_actionInvoked(aObject, aActionName) {
    let state = Utils.getState(aObject);

    // Checkable objects use TalkBack's text derived from the event state,
    // so we don't populate the text here.
    let text = null;
    if (!state.contains(States.CHECKABLE)) {
      text = Utils.localize(UtteranceGenerator.genForAction(aObject,
        aActionName));
    }

    return {
      type: this.type,
      details: [{
        eventType: this.ANDROID_VIEW_CLICKED,
        text: text,
        checked: state.contains(States.CHECKED)
      }]
    };
  };

AndroidPresenter.prototype.tabSelected =
  function AndroidPresenter_tabSelected(aDocContext, aVCContext) {
    // Send a pivot change message with the full context utterance for this doc.
    return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
  };

AndroidPresenter.prototype.tabStateChanged =
  function AndroidPresenter_tabStateChanged(aDocObj, aPageState) {
    return this.announce(
      UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
  };

AndroidPresenter.prototype.textChanged = function AndroidPresenter_textChanged(
  aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
    let eventDetails = {
      eventType: this.ANDROID_VIEW_TEXT_CHANGED,
      text: [aText],
      fromIndex: aStart,
      removedCount: 0,
      addedCount: 0
    };

    if (aIsInserted) {
      eventDetails.addedCount = aLength;
      eventDetails.beforeText =
        aText.substring(0, aStart) + aText.substring(aStart + aLength);
    } else {
      eventDetails.removedCount = aLength;
      eventDetails.beforeText =
        aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
    }

    return {type: this.type, details: [eventDetails]};
  };

AndroidPresenter.prototype.textSelectionChanged =
  function AndroidPresenter_textSelectionChanged(aText, aStart, aEnd, aOldStart,
                                                 aOldEnd, aIsFromUserInput) {
    let androidEvents = [];

    if (Utils.AndroidSdkVersion >= 14 && !aIsFromUserInput) {
      if (!this._braillePresenter) {
        this._braillePresenter = new BraillePresenter();
      }
      let brailleOutput = this._braillePresenter.textSelectionChanged(
        aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput).details;

      androidEvents.push({
        eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED,
        text: [aText],
        fromIndex: aStart,
        toIndex: aEnd,
        itemCount: aText.length,
        brailleOutput: brailleOutput
      });
    }

    if (Utils.AndroidSdkVersion >= 16 && aIsFromUserInput) {
      let [from, to] = aOldStart < aStart ?
        [aOldStart, aStart] : [aStart, aOldStart];
      androidEvents.push({
        eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
        text: [aText],
        fromIndex: from,
        toIndex: to
      });
    }

    return {
      type: this.type,
      details: androidEvents
    };
  };

AndroidPresenter.prototype.viewportChanged =
  function AndroidPresenter_viewportChanged(aWindow, aCurrentContext) {
    if (Utils.AndroidSdkVersion < 14) {
      return null;
    }

    let events = [{
      eventType: this.ANDROID_VIEW_SCROLLED,
      text: [],
      scrollX: aWindow.scrollX,
      scrollY: aWindow.scrollY,
      maxScrollX: aWindow.scrollMaxX,
      maxScrollY: aWindow.scrollMaxY
    }];

    if (Utils.AndroidSdkVersion >= 16 && aCurrentContext) {
      let currentAcc = aCurrentContext.accessibleForBounds;
      if (Utils.isAliveAndVisible(currentAcc)) {
        events.push({
          eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
          bounds: Utils.getBounds(currentAcc)
        });
      }
    }

    return {
      type: this.type,
      details: events
    };
  };

AndroidPresenter.prototype.editingModeChanged =
  function AndroidPresenter_editingModeChanged(aIsEditing) {
    return this.announce(UtteranceGenerator.genForEditingMode(aIsEditing));
  };

AndroidPresenter.prototype.announce =
  function AndroidPresenter_announce(aAnnouncement) {
    let localizedAnnouncement = Utils.localize(aAnnouncement).join(' ');
    return {
      type: this.type,
      details: [{
        eventType: (Utils.AndroidSdkVersion >= 16) ?
          this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED,
        text: [localizedAnnouncement],
        addedCount: localizedAnnouncement.length,
        removedCount: 0,
        fromIndex: 0
      }]
    };
  };

AndroidPresenter.prototype.liveRegion =
  function AndroidPresenter_liveRegion(aContext, aIsPolite,
    aIsHide, aModifiedText) {
    return this.announce(
      UtteranceGenerator.genForLiveRegion(aContext, aIsHide, aModifiedText));
  };

AndroidPresenter.prototype.noMove =
  function AndroidPresenter_noMove(aMoveMethod) {
    return {
      type: this.type,
      details: [
      { eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
        exitView: aMoveMethod,
        text: ['']
      }]
    };
  };

/**
 * A B2G presenter for Gaia.
 */
function B2GPresenter() {}

B2GPresenter.prototype = Object.create(Presenter.prototype);

B2GPresenter.prototype.type = 'B2G';

B2GPresenter.prototype.keyboardEchoSetting =
  new PrefCache('accessibility.accessfu.keyboard_echo');
B2GPresenter.prototype.NO_ECHO = 0;
B2GPresenter.prototype.CHARACTER_ECHO = 1;
B2GPresenter.prototype.WORD_ECHO = 2;
B2GPresenter.prototype.CHARACTER_AND_WORD_ECHO = 3;

/**
 * A pattern used for haptic feedback.
 * @type {Array}
 */
B2GPresenter.prototype.PIVOT_CHANGE_HAPTIC_PATTERN = [40];

/**
 * Pivot move reasons.
 * @type {Array}
 */
B2GPresenter.prototype.pivotChangedReasons = ['none', 'next', 'prev', 'first',
                                              'last', 'text', 'point'];

B2GPresenter.prototype.pivotChanged =
  function B2GPresenter_pivotChanged(aContext, aReason, aIsUserInput) {
    if (!aContext.accessible) {
      return null;
    }

    return {
      type: this.type,
      details: {
        eventType: 'vc-change',
        data: UtteranceGenerator.genForContext(aContext),
        options: {
          pattern: this.PIVOT_CHANGE_HAPTIC_PATTERN,
          isKey: Utils.isActivatableOnFingerUp(aContext.accessible),
          reason: this.pivotChangedReasons[aReason],
          isUserInput: aIsUserInput,
          hints: aContext.interactionHints
        }
      }
    };
  };

B2GPresenter.prototype.nameChanged =
  function B2GPresenter_nameChanged(aAccessible, aIsPolite = true) {
    return {
      type: this.type,
      details: {
        eventType: 'name-change',
        data: aAccessible.name,
        options: {enqueue: aIsPolite}
      }
    };
  };

B2GPresenter.prototype.valueChanged =
  function B2GPresenter_valueChanged(aAccessible, aIsPolite = true) {

    // the editable value changes are handled in the text changed presenter
    if (Utils.getState(aAccessible).contains(States.EDITABLE)) {
      return null;
    }

    return {
      type: this.type,
      details: {
        eventType: 'value-change',
        data: aAccessible.value,
        options: {enqueue: aIsPolite}
      }
    };
  };

B2GPresenter.prototype.textChanged = function B2GPresenter_textChanged(
  aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
    let echoSetting = this.keyboardEchoSetting.value;
    let text = '';

    if (echoSetting == this.CHARACTER_ECHO ||
        echoSetting == this.CHARACTER_AND_WORD_ECHO) {
      text = aModifiedText;
    }

    // add word if word boundary is added
    if ((echoSetting == this.WORD_ECHO ||
        echoSetting == this.CHARACTER_AND_WORD_ECHO) &&
        aIsInserted && aLength === 1) {
      let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
      let startBefore = {}, endBefore = {};
      let startAfter = {}, endAfter = {};
      accText.getTextBeforeOffset(aStart,
        Ci.nsIAccessibleText.BOUNDARY_WORD_END, startBefore, endBefore);
      let maybeWord = accText.getTextBeforeOffset(aStart + 1,
        Ci.nsIAccessibleText.BOUNDARY_WORD_END, startAfter, endAfter);
      if (endBefore.value !== endAfter.value) {
        text += maybeWord;
      }
    }

    return {
      type: this.type,
      details: {
        eventType: 'text-change',
        data: text
      }
    };

  };

B2GPresenter.prototype.actionInvoked =
  function B2GPresenter_actionInvoked(aObject, aActionName) {
    return {
      type: this.type,
      details: {
        eventType: 'action',
        data: UtteranceGenerator.genForAction(aObject, aActionName)
      }
    };
  };

B2GPresenter.prototype.liveRegion = function B2GPresenter_liveRegion(aContext,
  aIsPolite, aIsHide, aModifiedText) {
    return {
      type: this.type,
      details: {
        eventType: 'liveregion-change',
        data: UtteranceGenerator.genForLiveRegion(aContext, aIsHide,
          aModifiedText),
        options: {enqueue: aIsPolite}
      }
    };
  };

B2GPresenter.prototype.announce =
  function B2GPresenter_announce(aAnnouncement) {
    return {
      type: this.type,
      details: {
        eventType: 'announcement',
        data: aAnnouncement
      }
    };
  };

B2GPresenter.prototype.noMove =
  function B2GPresenter_noMove(aMoveMethod) {
    return {
      type: this.type,
      details: {
        eventType: 'no-move',
        data: aMoveMethod
      }
    };
  };

/**
 * A braille presenter
 */
function BraillePresenter() {}

BraillePresenter.prototype = Object.create(Presenter.prototype);

BraillePresenter.prototype.type = 'Braille';

BraillePresenter.prototype.pivotChanged =
  function BraillePresenter_pivotChanged(aContext) {
    if (!aContext.accessible) {
      return null;
    }

    return {
      type: this.type,
      details: {
        output: Utils.localize(BrailleGenerator.genForContext(aContext)).join(
          ' '),
        selectionStart: 0,
        selectionEnd: 0
      }
    };
  };

BraillePresenter.prototype.textSelectionChanged =
  function BraillePresenter_textSelectionChanged(aText, aStart, aEnd) {
    return {
      type: this.type,
      details: {
        selectionStart: aStart,
        selectionEnd: aEnd
      }
    };
  };

this.Presentation = { // jshint ignore:line
  get presenters() {
    delete this.presenters;
    let presenterMap = {
      'mobile/android': [VisualPresenter, AndroidPresenter],
      'b2g': [VisualPresenter, B2GPresenter],
      'browser': [VisualPresenter, B2GPresenter, AndroidPresenter]
    };
    this.presenters = presenterMap[Utils.MozBuildApp].map(P => new P());
    return this.presenters;
  },

  get displayedAccessibles() {
    delete this.displayedAccessibles;
    this.displayedAccessibles = new WeakMap();
    return this.displayedAccessibles;
  },

  pivotChanged: function Presentation_pivotChanged(
    aPosition, aOldPosition, aReason, aStartOffset, aEndOffset, aIsUserInput) {
    let context = new PivotContext(
      aPosition, aOldPosition, aStartOffset, aEndOffset);
    if (context.accessible) {
      this.displayedAccessibles.set(context.accessible.document.window, context);
    }

    return this.presenters.map(p => p.pivotChanged(context, aReason, aIsUserInput));
  },

  actionInvoked: function Presentation_actionInvoked(aObject, aActionName) {
    return this.presenters.map(p => p.actionInvoked(aObject, aActionName));
  },

  textChanged: function Presentation_textChanged(aAccessible, aIsInserted,
                                    aStartOffset, aLength, aText,
                                    aModifiedText) {
    return this.presenters.map(p => p.textChanged(aAccessible, aIsInserted,
                                                  aStartOffset, aLength,
                                                  aText, aModifiedText));
  },

  textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd,
                                                      aOldStart, aOldEnd,
                                                      aIsFromUserInput) {
    return this.presenters.map(p => p.textSelectionChanged(aText, aStart, aEnd,
                                                           aOldStart, aOldEnd,
                                                           aIsFromUserInput));
  },

  nameChanged: function nameChanged(aAccessible) {
    return this.presenters.map(p => p.nameChanged(aAccessible));
  },

  valueChanged: function valueChanged(aAccessible) {
    return this.presenters.map(p => p.valueChanged(aAccessible));
  },

  tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) {
    return this.presenters.map(p => p.tabStateChanged(aDocObj, aPageState));
  },

  viewportChanged: function Presentation_viewportChanged(aWindow) {
    let context = this.displayedAccessibles.get(aWindow);
    return this.presenters.map(p => p.viewportChanged(aWindow, context));
  },

  editingModeChanged: function Presentation_editingModeChanged(aIsEditing) {
    return this.presenters.map(p => p.editingModeChanged(aIsEditing));
  },

  announce: function Presentation_announce(aAnnouncement) {
    // XXX: Typically each presenter uses the UtteranceGenerator,
    // but there really isn't a point here.
    return this.presenters.map(p => p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement)));
  },

  noMove: function Presentation_noMove(aMoveMethod) {
    return this.presenters.map(p => p.noMove(aMoveMethod));
  },

  liveRegion: function Presentation_liveRegion(aAccessible, aIsPolite, aIsHide,
    aModifiedText) {
    let context;
    if (!aModifiedText) {
      context = new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide);
    }
    return this.presenters.map(p => p.liveRegion(context, aIsPolite, aIsHide,
                                                 aModifiedText));
  }
};
PK
!<*f&F0F0#modules/accessibility/Traversal.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 TraversalRules, TraversalHelper */

'use strict';

const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ['TraversalRules', 'TraversalHelper']; // jshint ignore:line

Cu.import('resource://gre/modules/accessibility/Utils.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',  // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Filters',  // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States',  // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters',  // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');

var gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');

function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter, aContainerRule) {
  this._explicitMatchRoles = new Set(aRoles);
  this._matchRoles = aRoles;
  if (aRoles.length) {
    if (aRoles.indexOf(Roles.LABEL) < 0) {
      this._matchRoles.push(Roles.LABEL);
    }
    if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
      // Used for traversing in to child OOP frames.
      this._matchRoles.push(Roles.INTERNAL_FRAME);
    }
  }
  this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
  this.preFilter = aPreFilter || gSimplePreFilter;
  this.containerRule = aContainerRule;
}

BaseTraversalRule.prototype = {
    getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
      aRoles.value = this._matchRoles;
      return aRoles.value.length;
    },

    match: function BaseTraversalRule_match(aAccessible)
    {
      let role = aAccessible.role;
      if (role == Roles.INTERNAL_FRAME) {
        return (Utils.getMessageManager(aAccessible.DOMNode)) ?
          Filters.MATCH  | Filters.IGNORE_SUBTREE : Filters.IGNORE;
      }

      let matchResult =
        (this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ?
        this._matchFunc(aAccessible) : Filters.IGNORE;

      // If we are on a label that nests a checkbox/radio we should land on it.
      // It is a bigger touch target, and it reduces clutter.
      if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
        let control = Utils.getEmbeddedControl(aAccessible);
        if (control && this._explicitMatchRoles.has(control.role)) {
          matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
        }
      }

      return matchResult;
    },

    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
};

var gSimpleTraversalRoles =
  [Roles.MENUITEM,
   Roles.LINK,
   Roles.PAGETAB,
   Roles.GRAPHIC,
   Roles.STATICTEXT,
   Roles.TEXT_LEAF,
   Roles.PUSHBUTTON,
   Roles.CHECKBUTTON,
   Roles.RADIOBUTTON,
   Roles.COMBOBOX,
   Roles.PROGRESSBAR,
   Roles.BUTTONDROPDOWN,
   Roles.BUTTONMENU,
   Roles.CHECK_MENU_ITEM,
   Roles.PASSWORD_TEXT,
   Roles.RADIO_MENU_ITEM,
   Roles.TOGGLE_BUTTON,
   Roles.ENTRY,
   Roles.KEY,
   Roles.HEADER,
   Roles.HEADING,
   Roles.SLIDER,
   Roles.SPINBUTTON,
   Roles.OPTION,
   Roles.LISTITEM,
   Roles.GRID_CELL,
   Roles.COLUMNHEADER,
   Roles.ROWHEADER,
   Roles.STATUSBAR,
   Roles.SWITCH,
   Roles.MATHML_MATH];

var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
  // An object is simple, if it either has a single child lineage,
  // or has a flat subtree.
  function isSingleLineage(acc) {
    for (let child = acc; child; child = child.firstChild) {
      if (Utils.visibleChildCount(child) > 1) {
        return false;
      }
    }
    return true;
  }

  function isFlatSubtree(acc) {
    for (let child = acc.firstChild; child; child = child.nextSibling) {
      // text leafs inherit the actionCount of any ancestor that has a click
      // listener.
      if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
        continue;
      }
      if (Utils.visibleChildCount(child) > 0 || child.actionCount > 0) {
        return false;
      }
    }
    return true;
  }

  switch (aAccessible.role) {
  case Roles.COMBOBOX:
    // We don't want to ignore the subtree because this is often
    // where the list box hangs out.
    return Filters.MATCH;
  case Roles.TEXT_LEAF:
    {
      // Nameless text leaves are boring, skip them.
      let name = aAccessible.name;
      return (name && name.trim()) ? Filters.MATCH : Filters.IGNORE;
    }
  case Roles.STATICTEXT:
    // Ignore prefix static text in list items. They are typically bullets or numbers.
    return Utils.isListItemDecorator(aAccessible) ?
      Filters.IGNORE : Filters.MATCH;
  case Roles.GRAPHIC:
    return TraversalRules._shouldSkipImage(aAccessible);
  case Roles.HEADER:
  case Roles.HEADING:
  case Roles.COLUMNHEADER:
  case Roles.ROWHEADER:
  case Roles.STATUSBAR:
    if ((aAccessible.childCount > 0 || aAccessible.name) &&
        (isSingleLineage(aAccessible) || isFlatSubtree(aAccessible))) {
      return Filters.MATCH | Filters.IGNORE_SUBTREE;
    }
    return Filters.IGNORE;
  case Roles.GRID_CELL:
    return isSingleLineage(aAccessible) || isFlatSubtree(aAccessible) ?
      Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
  case Roles.LISTITEM:
    {
      let item = aAccessible.childCount === 2 &&
        aAccessible.firstChild.role === Roles.STATICTEXT ?
        aAccessible.lastChild : aAccessible;
        return isSingleLineage(item) || isFlatSubtree(item) ?
          Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
    }
  default:
    // Ignore the subtree, if there is one. So that we don't land on
    // the same content that was already presented by its parent.
    return Filters.MATCH |
      Filters.IGNORE_SUBTREE;
  }
};

var gSimplePreFilter = Prefilters.DEFUNCT |
  Prefilters.INVISIBLE |
  Prefilters.ARIA_HIDDEN |
  Prefilters.TRANSPARENT;

this.TraversalRules = { // jshint ignore:line
  Simple: new BaseTraversalRule(gSimpleTraversalRoles, gSimpleMatchFunc),

  SimpleOnScreen: new BaseTraversalRule(
    gSimpleTraversalRoles, gSimpleMatchFunc,
    Prefilters.DEFUNCT | Prefilters.INVISIBLE | Prefilters.ARIA_HIDDEN |
    Prefilters.TRANSPARENT | Prefilters.OFFSCREEN),

  Anchor: new BaseTraversalRule(
    [Roles.LINK],
    function Anchor_match(aAccessible)
    {
      // We want to ignore links, only focus named anchors.
      if (Utils.getState(aAccessible).contains(States.LINKED)) {
        return Filters.IGNORE;
      } else {
        return Filters.MATCH;
      }
    }),

  Button: new BaseTraversalRule(
    [Roles.PUSHBUTTON,
     Roles.SPINBUTTON,
     Roles.TOGGLE_BUTTON,
     Roles.BUTTONDROPDOWN,
     Roles.BUTTONDROPDOWNGRID]),

  Combobox: new BaseTraversalRule(
    [Roles.COMBOBOX,
     Roles.LISTBOX]),

  Landmark: new BaseTraversalRule(
    [],
    function Landmark_match(aAccessible) {
      return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
        Filters.IGNORE;
    }, null, true),

  /* A rule for Android's section navigation, lands on landmarks, regions, and
     on headings to aid navigation of traditionally structured documents */
  Section: new BaseTraversalRule(
    [],
    function Section_match(aAccessible) {
      if (aAccessible.role === Roles.HEADING) {
        return Filters.MATCH;
      }

      let matchedRole = Utils.matchRoles(aAccessible, [
        'banner',
        'complementary',
        'contentinfo',
        'main',
        'navigation',
        'search',
        'region'
        ]);

      return matchedRole ? Filters.MATCH : Filters.IGNORE;
    }, null, true),

  Entry: new BaseTraversalRule(
    [Roles.ENTRY,
     Roles.PASSWORD_TEXT]),

  FormElement: new BaseTraversalRule(
    [Roles.PUSHBUTTON,
     Roles.SPINBUTTON,
     Roles.TOGGLE_BUTTON,
     Roles.BUTTONDROPDOWN,
     Roles.BUTTONDROPDOWNGRID,
     Roles.COMBOBOX,
     Roles.LISTBOX,
     Roles.ENTRY,
     Roles.PASSWORD_TEXT,
     Roles.PAGETAB,
     Roles.RADIOBUTTON,
     Roles.RADIO_MENU_ITEM,
     Roles.SLIDER,
     Roles.CHECKBUTTON,
     Roles.CHECK_MENU_ITEM,
     Roles.SWITCH]),

  Graphic: new BaseTraversalRule(
    [Roles.GRAPHIC],
    function Graphic_match(aAccessible) {
      return TraversalRules._shouldSkipImage(aAccessible);
    }),

  Heading: new BaseTraversalRule(
    [Roles.HEADING],
    function Heading_match(aAccessible) {
      return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
    }),

  ListItem: new BaseTraversalRule(
    [Roles.LISTITEM,
     Roles.TERM]),

  Link: new BaseTraversalRule(
    [Roles.LINK],
    function Link_match(aAccessible)
    {
      // We want to ignore anchors, only focus real links.
      if (Utils.getState(aAccessible).contains(States.LINKED)) {
        return Filters.MATCH;
      } else {
        return Filters.IGNORE;
      }
    }),

  /* For TalkBack's "Control" granularity. Form conrols and links */
  Control: new BaseTraversalRule(
    [Roles.PUSHBUTTON,
     Roles.SPINBUTTON,
     Roles.TOGGLE_BUTTON,
     Roles.BUTTONDROPDOWN,
     Roles.BUTTONDROPDOWNGRID,
     Roles.COMBOBOX,
     Roles.LISTBOX,
     Roles.ENTRY,
     Roles.PASSWORD_TEXT,
     Roles.PAGETAB,
     Roles.RADIOBUTTON,
     Roles.RADIO_MENU_ITEM,
     Roles.SLIDER,
     Roles.CHECKBUTTON,
     Roles.CHECK_MENU_ITEM,
     Roles.SWITCH,
     Roles.LINK,
     Roles.MENUITEM],
    function Control_match(aAccessible)
    {
      // We want to ignore anchors, only focus real links.
      if (aAccessible.role == Roles.LINK &&
          !Utils.getState(aAccessible).contains(States.LINKED)) {
        return Filters.IGNORE;
      }
      return Filters.MATCH;
    }),

  List: new BaseTraversalRule(
    [Roles.LIST,
     Roles.DEFINITION_LIST],
    null, null, true),

  PageTab: new BaseTraversalRule(
    [Roles.PAGETAB]),

  Paragraph: new BaseTraversalRule(
    [Roles.PARAGRAPH,
     Roles.SECTION],
    function Paragraph_match(aAccessible) {
      for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
        if (child.role === Roles.TEXT_LEAF) {
          return Filters.MATCH | Filters.IGNORE_SUBTREE;
        }
      }

      return Filters.IGNORE;
    }),

  RadioButton: new BaseTraversalRule(
    [Roles.RADIOBUTTON,
     Roles.RADIO_MENU_ITEM]),

  Separator: new BaseTraversalRule(
    [Roles.SEPARATOR]),

  Table: new BaseTraversalRule(
    [Roles.TABLE]),

  Checkbox: new BaseTraversalRule(
    [Roles.CHECKBUTTON,
     Roles.CHECK_MENU_ITEM,
     Roles.SWITCH /* A type of checkbox that represents on/off values */]),

  _shouldSkipImage: function _shouldSkipImage(aAccessible) {
    if (gSkipEmptyImages.value && aAccessible.name === '') {
      return Filters.IGNORE;
    }
    return Filters.MATCH;
  }
};

this.TraversalHelper = {
  _helperPivotCache: null,

  get helperPivotCache() {
    delete this.helperPivotCache;
    this.helperPivotCache = new WeakMap();
    return this.helperPivotCache;
  },

  getHelperPivot: function TraversalHelper_getHelperPivot(aRoot) {
    let pivot = this.helperPivotCache.get(aRoot.DOMNode);
    if (!pivot) {
      pivot = Utils.AccService.createAccessiblePivot(aRoot);
      this.helperPivotCache.set(aRoot.DOMNode, pivot);
    }

    return pivot;
  },

  move: function TraversalHelper_move(aVirtualCursor, aMethod, aRule) {
    let rule = TraversalRules[aRule];

    if (rule.containerRule) {
      let moved = false;
      let helperPivot = this.getHelperPivot(aVirtualCursor.root);
      helperPivot.position = aVirtualCursor.position;

      // We continue to step through containers until there is one with an
      // atomic child (via 'Simple') on which we could land.
      while (!moved) {
        if (helperPivot[aMethod](rule)) {
          aVirtualCursor.modalRoot = helperPivot.position;
          moved = aVirtualCursor.moveFirst(TraversalRules.Simple);
          aVirtualCursor.modalRoot = null;
        } else {
          // If we failed to step to another container, break and return false.
          break;
        }
      }

      return moved;
    } else {
      return aVirtualCursor[aMethod](rule);
    }
  }

};
PK
!<npAyAymodules/accessibility/Utils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 Utils, Logger, PivotContext, PrefCache */

'use strict';

const {classes: Cc, utils: Cu, interfaces: Ci} = Components;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services', // jshint ignore:line
  'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Rect', // jshint ignore:line
  'resource://gre/modules/Geometry.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Events', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Relations', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm', // jshint ignore:line
  'resource://gre/modules/PluralForm.jsm');

this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache']; // jshint ignore:line

this.Utils = { // jshint ignore:line
  _buildAppMap: {
    '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
    '{d1bfe7d9-c01e-4237-998b-7b5f960a4314}': 'graphene',
    '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
    '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
    '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul'
  },

  init: function Utils_init(aWindow) {
    if (this._win) {
      // XXX: only supports attaching to one window now.
      throw new Error('Only one top-level window could used with AccessFu');
    }
    this._win = Cu.getWeakReference(aWindow);
  },

  uninit: function Utils_uninit() {
    if (!this._win) {
      return;
    }
    delete this._win;
  },

  get win() {
    if (!this._win) {
      return null;
    }
    return this._win.get();
  },

  get winUtils() {
    let win = this.win;
    if (!win) {
      return null;
    }
    return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
      Ci.nsIDOMWindowUtils);
  },

  get AccService() {
    if (!this._AccService) {
      this._AccService = Cc['@mozilla.org/accessibilityService;1'].
        getService(Ci.nsIAccessibilityService);
    }

    return this._AccService;
  },

  set MozBuildApp(value) {
    this._buildApp = value;
  },

  get MozBuildApp() {
    if (!this._buildApp) {
      this._buildApp = this._buildAppMap[Services.appinfo.ID];
    }
    return this._buildApp;
  },

  get OS() {
    if (!this._OS) {
      this._OS = Services.appinfo.OS;
    }
    return this._OS;
  },

  get widgetToolkit() {
    if (!this._widgetToolkit) {
      this._widgetToolkit = Services.appinfo.widgetToolkit;
    }
    return this._widgetToolkit;
  },

  get ScriptName() {
    if (!this._ScriptName) {
      this._ScriptName =
        (Services.appinfo.processType == 2) ? 'AccessFuContent' : 'AccessFu';
    }
    return this._ScriptName;
  },

  get AndroidSdkVersion() {
    if (!this._AndroidSdkVersion) {
      if (Services.appinfo.OS == 'Android') {
        this._AndroidSdkVersion = Services.sysinfo.getPropertyAsInt32(
          'version');
      } else {
        // Most useful in desktop debugging.
        this._AndroidSdkVersion = 16;
      }
    }
    return this._AndroidSdkVersion;
  },

  set AndroidSdkVersion(value) {
    // When we want to mimic another version.
    this._AndroidSdkVersion = value;
  },

  get BrowserApp() {
    if (!this.win) {
      return null;
    }
    switch (this.MozBuildApp) {
      case 'mobile/android':
        return this.win.BrowserApp;
      case 'browser':
        return this.win.gBrowser;
      case 'b2g':
        return this.win.shell;
      default:
        return null;
    }
  },

  get CurrentBrowser() {
    if (!this.BrowserApp) {
      return null;
    }
    if (this.MozBuildApp == 'b2g') {
      return this.BrowserApp.contentBrowser;
    }
    return this.BrowserApp.selectedBrowser;
  },

  get CurrentContentDoc() {
    let browser = this.CurrentBrowser;
    return browser ? browser.contentDocument : null;
  },

  get AllMessageManagers() {
    let messageManagers = new Set();

    function collectLeafMessageManagers(mm) {
      for (let i = 0; i < mm.childCount; i++) {
        let childMM = mm.getChildAt(i);

        if ('sendAsyncMessage' in childMM) {
          messageManagers.add(childMM);
        } else {
          collectLeafMessageManagers(childMM);
        }
      }
    }

    collectLeafMessageManagers(this.win.messageManager);

    let document = this.CurrentContentDoc;

    if (document) {
      if (document.location.host === 'b2g') {
        // The document is a b2g app chrome (ie. Mulet).
        let contentBrowser = this.win.content.shell.contentBrowser;
        messageManagers.add(this.getMessageManager(contentBrowser));
        document = contentBrowser.contentDocument;
      }

      let remoteframes = document.querySelectorAll('iframe');

      for (let i = 0; i < remoteframes.length; ++i) {
        let mm = this.getMessageManager(remoteframes[i]);
        if (mm) {
          messageManagers.add(mm);
        }
      }

    }

    return messageManagers;
  },

  get isContentProcess() {
    delete this.isContentProcess;
    this.isContentProcess =
      Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
    return this.isContentProcess;
  },

  localize: function localize(aOutput) {
    let outputArray = Array.isArray(aOutput) ? aOutput : [aOutput];
    let localized =
      outputArray.map(details => this.stringBundle.get(details));
    // Clean up the white space.
    return localized.filter(word => word).map(word => word.trim()).
      filter(trimmed => trimmed);
  },

  get stringBundle() {
    delete this.stringBundle;
    let bundle = Services.strings.createBundle(
      'chrome://global/locale/AccessFu.properties');
    this.stringBundle = {
      get: function stringBundle_get(aDetails = {}) {
        if (!aDetails || typeof aDetails === 'string') {
          return aDetails;
        }
        let str = '';
        let string = aDetails.string;
        if (!string) {
          return str;
        }
        try {
          let args = aDetails.args;
          let count = aDetails.count;
          if (args) {
            str = bundle.formatStringFromName(string, args, args.length);
          } else {
            str = bundle.GetStringFromName(string);
          }
          if (count) {
            str = PluralForm.get(count, str);
            str = str.replace('#1', count);
          }
        } catch (e) {
          Logger.debug('Failed to get a string from a bundle for', string);
        } finally {
          return str;
        }
      }
    };
    return this.stringBundle;
  },

  getMessageManager: function getMessageManager(aBrowser) {
    try {
      return aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).
         frameLoader.messageManager;
    } catch (x) {
      return null;
    }
  },

  getState: function getState(aAccessibleOrEvent) {
    if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) {
      return new State(
        aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state,
        aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0);
    } else {
      let state = {};
      let extState = {};
      aAccessibleOrEvent.getState(state, extState);
      return new State(state.value, extState.value);
    }
  },

  getAttributes: function getAttributes(aAccessible) {
    let attributes = {};

    if (aAccessible && aAccessible.attributes) {
      let attributesEnum = aAccessible.attributes.enumerate();

      // Populate |attributes| object with |aAccessible|'s attribute key-value
      // pairs.
      while (attributesEnum.hasMoreElements()) {
        let attribute = attributesEnum.getNext().QueryInterface(
          Ci.nsIPropertyElement);
        attributes[attribute.key] = attribute.value;
      }
    }

    return attributes;
  },

  getVirtualCursor: function getVirtualCursor(aDocument) {
    let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
      this.AccService.getAccessibleFor(aDocument);

    return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
  },

  getContentResolution: function _getContentResolution(aAccessible) {
    let res = { value: 1 };
    aAccessible.document.window.QueryInterface(
      Ci.nsIInterfaceRequestor).getInterface(
      Ci.nsIDOMWindowUtils).getResolution(res);
    return res.value;
  },

  getBounds: function getBounds(aAccessible, aPreserveContentScale) {
    let objX = {}, objY = {}, objW = {}, objH = {};
    aAccessible.getBounds(objX, objY, objW, objH);

    let scale = aPreserveContentScale ? 1 :
      this.getContentResolution(aAccessible);

    return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
      scale, scale);
  },

  getTextBounds: function getTextBounds(aAccessible, aStart, aEnd,
                                        aPreserveContentScale) {
    let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
    let objX = {}, objY = {}, objW = {}, objH = {};
    accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
      Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);

    let scale = aPreserveContentScale ? 1 :
      this.getContentResolution(aAccessible);

    return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
      scale, scale);
  },

  /**
   * Get current display DPI.
   */
  get dpi() {
    delete this.dpi;
    this.dpi = this.winUtils.displayDPI;
    return this.dpi;
  },

  isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
    let acc = aAccessible;

    // If aSubTreeRoot is an accessible document, we will only walk up the
    // ancestry of documents and skip everything else.
    if (aSubTreeRoot instanceof Ci.nsIAccessibleDocument) {
      while (acc) {
        let parentDoc = acc instanceof Ci.nsIAccessibleDocument ?
          acc.parentDocument : acc.document;
        if (parentDoc === aSubTreeRoot) {
          return true;
        }
        acc = parentDoc;
      }
      return false;
    }

    while (acc) {
      if (acc == aSubTreeRoot) {
        return true;
      }

      try {
        acc = acc.parent;
      } catch (x) {
        Logger.debug('Failed to get parent:', x);
        acc = null;
      }
    }

    return false;
  },

  isHidden: function isHidden(aAccessible) {
    // Need to account for aria-hidden, so can't just check for INVISIBLE
    // state.
    let hidden = Utils.getAttributes(aAccessible).hidden;
    return hidden && hidden === 'true';
  },

  visibleChildCount: function visibleChildCount(aAccessible) {
    let count = 0;
    for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
      if (!this.isHidden(child)) {
        ++count;
      }
    }
    return count;
  },

  inHiddenSubtree: function inHiddenSubtree(aAccessible) {
    for (let acc=aAccessible; acc; acc=acc.parent) {
      if (this.isHidden(acc)) {
        return true;
      }
    }
    return false;
  },

  isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) {
    if (!aAccessible) {
      return false;
    }

    try {
      let state = this.getState(aAccessible);
      if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) ||
          (aIsOnScreen && state.contains(States.OFFSCREEN)) ||
          Utils.inHiddenSubtree(aAccessible)) {
        return false;
      }
    } catch (x) {
      return false;
    }

    return true;
  },

  matchAttributeValue: function matchAttributeValue(aAttributeValue, values) {
    let attrSet = new Set(aAttributeValue.split(' '));
    for (let value of values) {
      if (attrSet.has(value)) {
        return value;
      }
    }
  },

  getLandmarkName: function getLandmarkName(aAccessible) {
    return this.matchRoles(aAccessible, [
      'banner',
      'complementary',
      'contentinfo',
      'main',
      'navigation',
      'search'
    ]);
  },

  getMathRole: function getMathRole(aAccessible) {
    return this.matchRoles(aAccessible, [
      'base',
      'close-fence',
      'denominator',
      'numerator',
      'open-fence',
      'overscript',
      'presubscript',
      'presuperscript',
      'root-index',
      'subscript',
      'superscript',
      'underscript'
    ]);
  },

  matchRoles: function matchRoles(aAccessible, aRoles) {
    let roles = this.getAttributes(aAccessible)['xml-roles'];
    if (!roles) {
      return;
    }

    // Looking up a role that would match any in the provided roles.
    return this.matchAttributeValue(roles, aRoles);
  },

  getEmbeddedControl: function getEmbeddedControl(aLabel) {
    if (aLabel) {
      let relation = aLabel.getRelationByType(Relations.LABEL_FOR);
      for (let i = 0; i < relation.targetsCount; i++) {
        let target = relation.getTarget(i);
        if (target.parent === aLabel) {
          return target;
        }
      }
    }

    return null;
  },

  isListItemDecorator: function isListItemDecorator(aStaticText,
                                                    aExcludeOrdered) {
    let parent = aStaticText.parent;
    if (aExcludeOrdered && parent.parent.DOMNode.nodeName === 'OL') {
      return false;
    }

    return parent.role === Roles.LISTITEM && parent.childCount > 1 &&
      aStaticText.indexInParent === 0;
  },

  dispatchChromeEvent: function dispatchChromeEvent(aType, aDetails) {
    let details = {
      type: aType,
      details: JSON.stringify(
        typeof aDetails === 'string' ? { eventType: aDetails } : aDetails)
    };
    let window = this.win;
    let shell = window.shell || window.content.shell;
    if (shell) {
      // On B2G device.
      shell.sendChromeEvent(details);
    } else {
      // Dispatch custom event to have support for desktop and screen reader
      // emulator add-on.
      window.dispatchEvent(new window.CustomEvent(aType, {
        bubbles: true,
        cancelable: true,
        detail: details
      }));
    }

  },

  isActivatableOnFingerUp: function isActivatableOnFingerUp(aAccessible) {
    if (aAccessible.role === Roles.KEY) {
      return true;
    }
    let quick_activate = this.getAttributes(aAccessible)['moz-quick-activate'];
    return quick_activate && JSON.parse(quick_activate);
  }
};

/**
 * State object used internally to process accessible's states.
 * @param {Number} aBase     Base state.
 * @param {Number} aExtended Extended state.
 */
function State(aBase, aExtended) {
  this.base = aBase;
  this.extended = aExtended;
}

State.prototype = {
  contains: function State_contains(other) {
    return !!(this.base & other.base || this.extended & other.extended);
  },
  toString: function State_toString() {
    let stateStrings = Utils.AccService.
      getStringStates(this.base, this.extended);
    let statesArray = new Array(stateStrings.length);
    for (let i = 0; i < statesArray.length; i++) {
      statesArray[i] = stateStrings.item(i);
    }
    return '[' + statesArray.join(', ') + ']';
  }
};

this.Logger = { // jshint ignore:line
  GESTURE: -1,
  DEBUG: 0,
  INFO: 1,
  WARNING: 2,
  ERROR: 3,
  _LEVEL_NAMES: ['GESTURE', 'DEBUG', 'INFO', 'WARNING', 'ERROR'],

  logLevel: 1, // INFO;

  test: false,

  log: function log(aLogLevel) {
    if (aLogLevel < this.logLevel) {
      return;
    }

    let args = Array.prototype.slice.call(arguments, 1);
    let message = (typeof(args[0]) === 'function' ? args[0]() : args).join(' ');
    message = '[' + Utils.ScriptName + '] ' + this._LEVEL_NAMES[aLogLevel + 1] +
      ' ' + message + '\n';
    dump(message);
    // Note: used for testing purposes. If |this.test| is true, also log to
    // the console service.
    if (this.test) {
      try {
        Services.console.logStringMessage(message);
      } catch (ex) {
        // There was an exception logging to the console service.
      }
    }
  },

  info: function info() {
    this.log.apply(
      this, [this.INFO].concat(Array.prototype.slice.call(arguments)));
  },

  gesture: function gesture() {
    this.log.apply(
      this, [this.GESTURE].concat(Array.prototype.slice.call(arguments)));
  },

  debug: function debug() {
    this.log.apply(
      this, [this.DEBUG].concat(Array.prototype.slice.call(arguments)));
  },

  warning: function warning() {
    this.log.apply(
      this, [this.WARNING].concat(Array.prototype.slice.call(arguments)));
  },

  error: function error() {
    this.log.apply(
      this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
  },

  logException: function logException(
    aException, aErrorMessage = 'An exception has occured') {
    try {
      let stackMessage = '';
      if (aException.stack) {
        stackMessage = '  ' + aException.stack.replace(/\n/g, '\n  ');
      } else if (aException.location) {
        let frame = aException.location;
        let stackLines = [];
        while (frame && frame.lineNumber) {
          stackLines.push(
            '  ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber);
          frame = frame.caller;
        }
        stackMessage = stackLines.join('\n');
      } else {
        stackMessage =
          '(' + aException.fileName + ':' + aException.lineNumber + ')';
      }
      this.error(aErrorMessage + ':\n ' +
                 aException.message + '\n' +
                 stackMessage);
    } catch (x) {
      this.error(x);
    }
  },

  accessibleToString: function accessibleToString(aAccessible) {
    if (!aAccessible) {
      return '[ null ]';
    }

    try {
      return '[ ' + Utils.AccService.getStringRole(aAccessible.role) +
        ' | ' + aAccessible.name + ' ]';
    } catch (x) {
      return '[ defunct ]';
    }
  },

  eventToString: function eventToString(aEvent) {
    let str = Utils.AccService.getStringEventType(aEvent.eventType);
    if (aEvent.eventType == Events.STATE_CHANGE) {
      let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
      let stateStrings = event.isExtraState ?
        Utils.AccService.getStringStates(0, event.state) :
        Utils.AccService.getStringStates(event.state, 0);
      str += ' (' + stateStrings.item(0) + ')';
    }

    if (aEvent.eventType == Events.VIRTUALCURSOR_CHANGED) {
      let event = aEvent.QueryInterface(
        Ci.nsIAccessibleVirtualCursorChangeEvent);
      let pivot = aEvent.accessible.QueryInterface(
        Ci.nsIAccessibleDocument).virtualCursor;
      str += ' (' + this.accessibleToString(event.oldAccessible) + ' -> ' +
        this.accessibleToString(pivot.position) + ')';
    }

    return str;
  },

  statesToString: function statesToString(aAccessible) {
    return Utils.getState(aAccessible).toString();
  },

  dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
    if (aLogLevel < this.logLevel) {
      return;
    }

    this._dumpTreeInternal(aLogLevel, aRootAccessible, 0);
  },

  _dumpTreeInternal:
    function _dumpTreeInternal(aLogLevel, aAccessible, aIndent) {
      let indentStr = '';
      for (let i = 0; i < aIndent; i++) {
        indentStr += ' ';
      }
      this.log(aLogLevel, indentStr,
               this.accessibleToString(aAccessible),
               '(' + this.statesToString(aAccessible) + ')');
      for (let i = 0; i < aAccessible.childCount; i++) {
        this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i),
          aIndent + 1);
      }
    }
};

/**
 * PivotContext: An object that generates and caches context information
 * for a given accessible and its relationship with another accessible.
 *
 * If the given accessible is a label for a nested control, then this
 * context will represent the nested control instead of the label.
 * With the exception of bounds calculation, which will use the containing
 * label. In this case the |accessible| field would be the embedded control,
 * and the |accessibleForBounds| field would be the label.
 */
this.PivotContext = function PivotContext(aAccessible, aOldAccessible, // jshint ignore:line
  aStartOffset, aEndOffset, aIgnoreAncestry = false,
  aIncludeInvisible = false) {
  this._accessible = aAccessible;
  this._nestedControl = Utils.getEmbeddedControl(aAccessible);
  this._oldAccessible =
    this._isDefunct(aOldAccessible) ? null : aOldAccessible;
  this.startOffset = aStartOffset;
  this.endOffset = aEndOffset;
  this._ignoreAncestry = aIgnoreAncestry;
  this._includeInvisible = aIncludeInvisible;
};

PivotContext.prototype = {
  get accessible() {
    // If the current pivot accessible has a nested control,
    // make this context use it publicly.
    return this._nestedControl || this._accessible;
  },

  get oldAccessible() {
    return this._oldAccessible;
  },

  get isNestedControl() {
    return !!this._nestedControl;
  },

  get accessibleForBounds() {
    return this._accessible;
  },

  get textAndAdjustedOffsets() {
    if (this.startOffset === -1 && this.endOffset === -1) {
      return null;
    }

    if (!this._textAndAdjustedOffsets) {
      let result = {startOffset: this.startOffset,
                    endOffset: this.endOffset,
                    text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
                          getText(0,
                            Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
      let hypertextAcc = this._accessible.QueryInterface(
        Ci.nsIAccessibleHyperText);

      // Iterate through the links in backwards order so text replacements don't
      // affect the offsets of links yet to be processed.
      for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
        let link = hypertextAcc.getLinkAt(i);
        let linkText = '';
        if (link instanceof Ci.nsIAccessibleText) {
          linkText = link.QueryInterface(Ci.nsIAccessibleText).
                          getText(0,
                            Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
        }

        let start = link.startIndex;
        let end = link.endIndex;
        for (let offset of ['startOffset', 'endOffset']) {
          if (this[offset] >= end) {
            result[offset] += linkText.length - (end - start);
          }
        }
        result.text = result.text.substring(0, start) + linkText +
                      result.text.substring(end);
      }

      this._textAndAdjustedOffsets = result;
    }

    return this._textAndAdjustedOffsets;
  },

  /**
   * Get a list of |aAccessible|'s ancestry up to the root.
   * @param  {nsIAccessible} aAccessible.
   * @return {Array} Ancestry list.
   */
  _getAncestry: function _getAncestry(aAccessible) {
    let ancestry = [];
    let parent = aAccessible;
    try {
      while (parent && (parent = parent.parent)) {
       ancestry.push(parent);
      }
    } catch (x) {
      // A defunct accessible will raise an exception geting parent.
      Logger.debug('Failed to get parent:', x);
    }
    return ancestry.reverse();
  },

  /**
   * A list of the old accessible's ancestry.
   */
  get oldAncestry() {
    if (!this._oldAncestry) {
      if (!this._oldAccessible || this._ignoreAncestry) {
        this._oldAncestry = [];
      } else {
        this._oldAncestry = this._getAncestry(this._oldAccessible);
        this._oldAncestry.push(this._oldAccessible);
      }
    }
    return this._oldAncestry;
  },

  /**
   * A list of the current accessible's ancestry.
   */
  get currentAncestry() {
    if (!this._currentAncestry) {
      this._currentAncestry = this._ignoreAncestry ? [] :
        this._getAncestry(this.accessible);
    }
    return this._currentAncestry;
  },

  /*
   * This is a list of the accessible's ancestry up to the common ancestor
   * of the accessible and the old accessible. It is useful for giving the
   * user context as to where they are in the heirarchy.
   */
  get newAncestry() {
    if (!this._newAncestry) {
      this._newAncestry = this._ignoreAncestry ? [] :
        this.currentAncestry.filter(
          (currentAncestor, i) => currentAncestor !== this.oldAncestry[i]);
    }
    return this._newAncestry;
  },

  /*
   * Traverse the accessible's subtree in pre or post order.
   * It only includes the accessible's visible chidren.
   * Note: needSubtree is a function argument that can be used to determine
   * whether aAccessible's subtree is required.
   */
  _traverse: function* _traverse(aAccessible, aPreorder, aStop) {
    if (aStop && aStop(aAccessible)) {
      return;
    }
    let child = aAccessible.firstChild;
    while (child) {
      let include;
      if (this._includeInvisible) {
        include = true;
      } else {
        include = !Utils.isHidden(child);
      }
      if (include) {
        if (aPreorder) {
          yield child;
          for (let node of this._traverse(child, aPreorder, aStop)) {
            yield node;
          }
        } else {
          for (let node of this._traverse(child, aPreorder, aStop)) {
            yield node;
          }
          yield child;
        }
      }
      child = child.nextSibling;
    }
  },

  /**
   * Get interaction hints for the context ancestry.
   * @return {Array} Array of interaction hints.
   */
  get interactionHints() {
    let hints = [];
    this.newAncestry.concat(this.accessible).reverse().forEach(aAccessible => {
      let hint = Utils.getAttributes(aAccessible)['moz-hint'];
      if (hint) {
        hints.push(hint);
      } else if (aAccessible.actionCount > 0) {
        hints.push({
          string: Utils.AccService.getStringRole(
            aAccessible.role).replace(/\s/g, '') + '-hint'
        });
      }
    });
    return hints;
  },

  /*
   * A subtree generator function, used to generate a flattened
   * list of the accessible's subtree in pre or post order.
   * It only includes the accessible's visible chidren.
   * @param {boolean} aPreorder A flag for traversal order. If true, traverse
   * in preorder; if false, traverse in postorder.
   * @param {function} aStop An optional function, indicating whether subtree
   * traversal should stop.
   */
  subtreeGenerator: function subtreeGenerator(aPreorder, aStop) {
    return this._traverse(this.accessible, aPreorder, aStop);
  },

  getCellInfo: function getCellInfo(aAccessible) {
    if (!this._cells) {
      this._cells = new WeakMap();
    }

    let domNode = aAccessible.DOMNode;
    if (this._cells.has(domNode)) {
      return this._cells.get(domNode);
    }

    let cellInfo = {};
    let getAccessibleCell = function getAccessibleCell(aAccessible) {
      if (!aAccessible) {
        return null;
      }
      if ([
            Roles.CELL,
            Roles.COLUMNHEADER,
            Roles.ROWHEADER,
            Roles.MATHML_CELL
          ].indexOf(aAccessible.role) < 0) {
          return null;
      }
      try {
        return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
      } catch (x) {
        Logger.logException(x);
        return null;
      }
    };
    let getHeaders = function* getHeaders(aHeaderCells) {
      let enumerator = aHeaderCells.enumerate();
      while (enumerator.hasMoreElements()) {
        yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name;
      }
    };

    cellInfo.current = getAccessibleCell(aAccessible);

    if (!cellInfo.current) {
      Logger.warning(aAccessible,
        'does not support nsIAccessibleTableCell interface.');
      this._cells.set(domNode, null);
      return null;
    }

    let table = cellInfo.current.table;
    if (table.isProbablyForLayout()) {
      this._cells.set(domNode, null);
      return null;
    }

    cellInfo.previous = null;
    let oldAncestry = this.oldAncestry.reverse();
    let ancestor = oldAncestry.shift();
    while (!cellInfo.previous && ancestor) {
      let cell = getAccessibleCell(ancestor);
      if (cell && cell.table === table) {
        cellInfo.previous = cell;
      }
      ancestor = oldAncestry.shift();
    }

    if (cellInfo.previous) {
      cellInfo.rowChanged = cellInfo.current.rowIndex !==
        cellInfo.previous.rowIndex;
      cellInfo.columnChanged = cellInfo.current.columnIndex !==
        cellInfo.previous.columnIndex;
    } else {
      cellInfo.rowChanged = true;
      cellInfo.columnChanged = true;
    }

    cellInfo.rowExtent = cellInfo.current.rowExtent;
    cellInfo.columnExtent = cellInfo.current.columnExtent;
    cellInfo.columnIndex = cellInfo.current.columnIndex;
    cellInfo.rowIndex = cellInfo.current.rowIndex;

    cellInfo.columnHeaders = [];
    if (cellInfo.columnChanged && cellInfo.current.role !==
      Roles.COLUMNHEADER) {
      cellInfo.columnHeaders = [...getHeaders(cellInfo.current.columnHeaderCells)];
    }
    cellInfo.rowHeaders = [];
    if (cellInfo.rowChanged &&
        (cellInfo.current.role === Roles.CELL ||
         cellInfo.current.role === Roles.MATHML_CELL)) {
      cellInfo.rowHeaders = [...getHeaders(cellInfo.current.rowHeaderCells)];
    }

    this._cells.set(domNode, cellInfo);
    return cellInfo;
  },

  get bounds() {
    if (!this._bounds) {
      this._bounds = Utils.getBounds(this.accessibleForBounds);
    }

    return this._bounds.clone();
  },

  _isDefunct: function _isDefunct(aAccessible) {
    try {
      return Utils.getState(aAccessible).contains(States.DEFUNCT);
    } catch (x) {
      return true;
    }
  }
};

this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) { // jshint ignore:line
  this.name = aName;
  this.callback = aCallback;

  let branch = Services.prefs;
  this.value = this._getValue(branch);

  if (this.callback && aRunCallbackNow) {
    try {
      this.callback(this.name, this.value, true);
    } catch (x) {
      Logger.logException(x);
    }
  }

  branch.addObserver(aName, this, true);
};

PrefCache.prototype = {
  _getValue: function _getValue(aBranch) {
    try {
      if (!this.type) {
        this.type = aBranch.getPrefType(this.name);
      }
      switch (this.type) {
        case Ci.nsIPrefBranch.PREF_STRING:
          return aBranch.getCharPref(this.name);
        case Ci.nsIPrefBranch.PREF_INT:
          return aBranch.getIntPref(this.name);
        case Ci.nsIPrefBranch.PREF_BOOL:
          return aBranch.getBoolPref(this.name);
        default:
          return null;
      }
    } catch (x) {
      // Pref does not exist.
      return null;
    }
  },

  observe: function observe(aSubject) {
    this.value = this._getValue(aSubject.QueryInterface(Ci.nsIPrefBranch));
    Logger.info('pref changed', this.name, this.value);
    if (this.callback) {
      try {
        this.callback(this.name, this.value, false);
      } catch (x) {
        Logger.logException(x);
      }
    }
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
};
PK
!<>TMM'modules/addons/APIExtensionBootstrap.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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, ExtensionAPIs */

Components.utils.import("resource://gre/modules/ExtensionAPI.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

var namespace;
var resource;
var resProto;

function install(data, reason) {
}

function startup(data, reason) {
  namespace = data.id.replace(/@.*/, "");
  resource = `extension-${namespace}-api`;

  resProto = Services.io.getProtocolHandler("resource")
                     .QueryInterface(Components.interfaces.nsIResProtocolHandler);

  resProto.setSubstitution(resource, data.resourceURI);

  ExtensionAPIs.register(
    namespace,
    `resource://${resource}/schema.json`,
    `resource://${resource}/api.js`);
}

function shutdown(data, reason) {
  resProto.setSubstitution(resource, null);

  ExtensionAPIs.unregister(namespace);
}

function uninstall(data, reason) {
}
PK
!<"modules/addons/AddonRepository.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
                                  "resource://gre/modules/DeferredSave.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository_SQLiteMigrator",
                                  "resource://gre/modules/addons/AddonRepository_SQLiteMigrator.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
                                  "resource://gre/modules/ServiceRequest.jsm");


this.EXPORTED_SYMBOLS = [ "AddonRepository" ];

const PREF_GETADDONS_CACHE_ENABLED       = "extensions.getAddons.cache.enabled";
const PREF_GETADDONS_CACHE_TYPES         = "extensions.getAddons.cache.types";
const PREF_GETADDONS_CACHE_ID_ENABLED    = "extensions.%ID%.getAddons.cache.enabled"
const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
const PREF_GETADDONS_BYIDS_PERFORMANCE   = "extensions.getAddons.getWithPerformance.url";
const PREF_GETADDONS_BROWSERECOMMENDED   = "extensions.getAddons.recommended.browseURL";
const PREF_GETADDONS_GETRECOMMENDED      = "extensions.getAddons.recommended.url";
const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
const PREF_GETADDONS_GETSEARCHRESULTS    = "extensions.getAddons.search.url";
const PREF_GETADDONS_DB_SCHEMA           = "extensions.getAddons.databaseSchema"

const PREF_METADATA_LASTUPDATE           = "extensions.getAddons.cache.lastUpdate";
const PREF_METADATA_UPDATETHRESHOLD_SEC  = "extensions.getAddons.cache.updateThreshold";
const DEFAULT_METADATA_UPDATETHRESHOLD_SEC = 172800;  // two days

const XMLURI_PARSE_ERROR  = "http://www.mozilla.org/newlayout/xml/parsererror.xml";

const API_VERSION = "1.5";
const DEFAULT_CACHE_TYPES = "extension,theme,locale,dictionary";

const FILE_DATABASE         = "addons.json";
const DB_SCHEMA             = 5;
const DB_MIN_JSON_SCHEMA    = 5;
const DB_BATCH_TIMEOUT_MS   = 50;

const BLANK_DB = function() {
  return {
    addons: new Map(),
    schema: DB_SCHEMA
  };
}

const TOOLKIT_ID     = "toolkit@mozilla.org";

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.repository";

// Create a new logger for use by the Addons Repository
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

// A map between XML keys to AddonSearchResult keys for string values
// that require no extra parsing from XML
const STRING_KEY_MAP = {
  name:               "name",
  version:            "version",
  homepage:           "homepageURL",
  support:            "supportURL"
};

// A map between XML keys to AddonSearchResult keys for string values
// that require parsing from HTML
const HTML_KEY_MAP = {
  summary:            "description",
  description:        "fullDescription",
  developer_comments: "developerComments",
  eula:               "eula"
};

// A map between XML keys to AddonSearchResult keys for integer values
// that require no extra parsing from XML
const INTEGER_KEY_MAP = {
  total_downloads:  "totalDownloads",
  weekly_downloads: "weeklyDownloads",
  daily_users:      "dailyUsers"
};

function convertHTMLToPlainText(html) {
  if (!html)
    return html;
  var converter = Cc["@mozilla.org/widget/htmlformatconverter;1"].
                  createInstance(Ci.nsIFormatConverter);

  var input = Cc["@mozilla.org/supports-string;1"].
              createInstance(Ci.nsISupportsString);
  input.data = html.replace(/\n/g, "<br>");

  var output = {};
  converter.convert("text/html", input, input.data.length, "text/unicode",
                    output, {});

  if (output.value instanceof Ci.nsISupportsString)
    return output.value.data.replace(/\r\n/g, "\n");
  return html;
}

async function getAddonsToCache(aIds) {
  let types = Preferences.get(PREF_GETADDONS_CACHE_TYPES) || DEFAULT_CACHE_TYPES;

  types = types.split(",");

  let addons = await AddonManager.getAddonsByIDs(aIds)
  let enabledIds = [];

  for (let [i, addon] of addons.entries()) {
    var preference = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", aIds[i]);
    // If the preference doesn't exist caching is enabled by default
    if (!Preferences.get(preference, true))
      continue;

    // The add-ons manager may not know about this ID yet if it is a pending
    // install. In that case we'll just cache it regardless

    // Don't cache add-ons of the wrong types
    if (addon && !types.includes(addon.type)) {
      continue;
    }

    // Don't cache system add-ons
    if (addon && addon.isSystem) {
      continue;
    }

    enabledIds.push(aIds[i]);
  }

  return enabledIds;
}

function AddonSearchResult(aId) {
  this.id = aId;
  this.icons = {};
  this._unsupportedProperties = {};
}

AddonSearchResult.prototype = {
  /**
   * The ID of the add-on
   */
  id: null,

  /**
   * The add-on type (e.g. "extension" or "theme")
   */
  type: null,

  /**
   * The name of the add-on
   */
  name: null,

  /**
   * The version of the add-on
   */
  version: null,

  /**
   * The creator of the add-on
   */
  creator: null,

  /**
   * The developers of the add-on
   */
  developers: null,

  /**
   * A short description of the add-on
   */
  description: null,

  /**
   * The full description of the add-on
   */
  fullDescription: null,

  /**
   * The developer comments for the add-on. This includes any information
   * that may be helpful to end users that isn't necessarily applicable to
   * the add-on description (e.g. known major bugs)
   */
  developerComments: null,

  /**
   * The end-user licensing agreement (EULA) of the add-on
   */
  eula: null,

  /**
   * The url of the add-on's icon
   */
  get iconURL() {
    return this.icons && this.icons[32];
  },

   /**
   * The URLs of the add-on's icons, as an object with icon size as key
   */
  icons: null,

  /**
   * An array of screenshot urls for the add-on
   */
  screenshots: null,

  /**
   * The homepage for the add-on
   */
  homepageURL: null,

  /**
   * The homepage for the add-on
   */
  learnmoreURL: null,

  /**
   * The support URL for the add-on
   */
  supportURL: null,

  /**
   * The contribution url of the add-on
   */
  contributionURL: null,

  /**
   * The suggested contribution amount
   */
  contributionAmount: null,

  /**
   * The URL to visit in order to purchase the add-on
   */
  purchaseURL: null,

  /**
   * The numerical cost of the add-on in some currency, for sorting purposes
   * only
   */
  purchaseAmount: null,

  /**
   * The display cost of the add-on, for display purposes only
   */
  purchaseDisplayAmount: null,

  /**
   * The rating of the add-on, 0-5
   */
  averageRating: null,

  /**
   * The number of reviews for this add-on
   */
  reviewCount: null,

  /**
   * The URL to the list of reviews for this add-on
   */
  reviewURL: null,

  /**
   * The total number of times the add-on was downloaded
   */
  totalDownloads: null,

  /**
   * The number of times the add-on was downloaded the current week
   */
  weeklyDownloads: null,

  /**
   * The number of daily users for the add-on
   */
  dailyUsers: null,

  /**
   * AddonInstall object generated from the add-on XPI url
   */
  install: null,

  /**
   * nsIURI storing where this add-on was installed from
   */
  sourceURI: null,

  /**
   * The status of the add-on in the repository (e.g. 4 = "Public")
   */
  repositoryStatus: null,

  /**
   * The size of the add-on's files in bytes. For an add-on that have not yet
   * been downloaded this may be an estimated value.
   */
  size: null,

  /**
   * The Date that the add-on was most recently updated
   */
  updateDate: null,

  /**
   * True or false depending on whether the add-on is compatible with the
   * current version of the application
   */
  isCompatible: true,

  /**
   * True or false depending on whether the add-on is compatible with the
   * current platform
   */
  isPlatformCompatible: true,

  /**
   * Array of AddonCompatibilityOverride objects, that describe overrides for
   * compatibility with an application versions.
   **/
  compatibilityOverrides: null,

  /**
   * True if the add-on has a secure means of updating
   */
  providesUpdatesSecurely: true,

  /**
   * The current blocklist state of the add-on
   */
  blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,

  /**
   * True if this add-on cannot be used in the application based on version
   * compatibility, dependencies and blocklisting
   */
  appDisabled: false,

  /**
   * True if the user wants this add-on to be disabled
   */
  userDisabled: false,

  /**
   * Indicates what scope the add-on is installed in, per profile, user,
   * system or application
   */
  scope: AddonManager.SCOPE_PROFILE,

  /**
   * True if the add-on is currently functional
   */
  isActive: true,

  /**
   * A bitfield holding all of the current operations that are waiting to be
   * performed for this add-on
   */
  pendingOperations: AddonManager.PENDING_NONE,

  /**
   * A bitfield holding all the the operations that can be performed on
   * this add-on
   */
  permissions: 0,

  /**
   * Tests whether this add-on is known to be compatible with a
   * particular application and platform version.
   *
   * @param  appVersion
   *         An application version to test against
   * @param  platformVersion
   *         A platform version to test against
   * @return Boolean representing if the add-on is compatible
   */
  isCompatibleWith(aAppVersion, aPlatformVersion) {
    return true;
  },

  /**
   * Starts an update check for this add-on. This will perform
   * asynchronously and deliver results to the given listener.
   *
   * @param  aListener
   *         An UpdateListener for the update process
   * @param  aReason
   *         A reason code for performing the update
   * @param  aAppVersion
   *         An application version to check for updates for
   * @param  aPlatformVersion
   *         A platform version to check for updates for
   */
  findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
    if ("onNoCompatibilityUpdateAvailable" in aListener)
      aListener.onNoCompatibilityUpdateAvailable(this);
    if ("onNoUpdateAvailable" in aListener)
      aListener.onNoUpdateAvailable(this);
    if ("onUpdateFinished" in aListener)
      aListener.onUpdateFinished(this);
  },

  toJSON() {
    let json = {};

    for (let property of Object.keys(this)) {
      let value = this[property];
      if (property.startsWith("_") ||
          typeof(value) === "function")
        continue;

      try {
        switch (property) {
          case "sourceURI":
            json.sourceURI = value ? value.spec : "";
            break;

          case "updateDate":
            json.updateDate = value ? value.getTime() : "";
            break;

          default:
            json[property] = value;
        }
      } catch (ex) {
        logger.warn("Error writing property value for " + property);
      }
    }

    for (let property of Object.keys(this._unsupportedProperties)) {
      let value = this._unsupportedProperties[property];
      if (!property.startsWith("_"))
        json[property] = value;
    }

    return json;
  }
}

/**
 * The add-on repository is a source of add-ons that can be installed. It can
 * be searched in three ways. The first takes a list of IDs and returns a
 * list of the corresponding add-ons. The second returns a list of add-ons that
 * come highly recommended. This list should change frequently. The third is to
 * search for specific search terms entered by the user. Searches are
 * asynchronous and results should be passed to the provided callback object
 * when complete. The results passed to the callback should only include add-ons
 * that are compatible with the current application and are not already
 * installed.
 */
this.AddonRepository = {
  /**
   * Whether caching is currently enabled
   */
  get cacheEnabled() {
    let preference = PREF_GETADDONS_CACHE_ENABLED;
    return Services.prefs.getBoolPref(preference, false);
  },

  // A cache of the add-ons stored in the database
  _addons: null,

  // Whether a search is currently in progress
  _searching: false,

  // XHR associated with the current request
  _request: null,

  /*
   * Addon search results callback object that contains two functions
   *
   * searchSucceeded - Called when a search has suceeded.
   *
   * @param  aAddons
   *         An array of the add-on results. In the case of searching for
   *         specific terms the ordering of results may be determined by
   *         the search provider.
   * @param  aAddonCount
   *         The length of aAddons
   * @param  aTotalResults
   *         The total results actually available in the repository
   *
   *
   * searchFailed - Called when an error occurred when performing a search.
   */
  _callback: null,

  // Maximum number of results to return
  _maxResults: null,

  /**
   * Shut down AddonRepository
   * return: promise{integer} resolves with the result of flushing
   *         the AddonRepository database
   */
  shutdown() {
    this.cancelSearch();

    this._addons = null;
    return AddonDatabase.shutdown(false);
  },

  metadataAge() {
    let now = Math.round(Date.now() / 1000);

    let lastUpdate = Services.prefs.getIntPref(PREF_METADATA_LASTUPDATE, 0);

    // Handle clock jumps
    if (now < lastUpdate) {
      return now;
    }
    return now - lastUpdate;
  },

  isMetadataStale() {
    let threshold = Services.prefs.getIntPref(PREF_METADATA_UPDATETHRESHOLD_SEC, DEFAULT_METADATA_UPDATETHRESHOLD_SEC);
    return (this.metadataAge() > threshold);
  },

  /**
   * Asynchronously get a cached add-on by id. The add-on (or null if the
   * add-on is not found) is passed to the specified callback. If caching is
   * disabled, null is passed to the specified callback.
   *
   * @param  aId
   *         The id of the add-on to get
   * @param  aCallback
   *         The callback to pass the result back to
   */
  async getCachedAddonByID(aId, aCallback) {
    if (!aId || !this.cacheEnabled) {
      aCallback(null);
      return;
    }

    function getAddon(aAddons) {
      aCallback(aAddons.get(aId) || null);
    }

    if (this._addons == null) {
      AddonDatabase.retrieveStoredData().then(aAddons => {
        this._addons = aAddons;
        getAddon(aAddons);
      });

      return;
    }

    getAddon(this._addons);
  },

  /**
   * Asynchronously repopulate cache so it only contains the add-ons
   * corresponding to the specified ids. If caching is disabled,
   * the cache is completely removed.
   *
   * @param  aTimeout
   *         (Optional) timeout in milliseconds to abandon the XHR request
   *         if we have not received a response from the server.
   * @return Promise{null}
   *         Resolves when the metadata ping is complete
   */
  repopulateCache(aTimeout) {
    return this._repopulateCacheInternal(false, aTimeout);
  },

  /*
   * Clear and delete the AddonRepository database
   * @return Promise{null} resolves when the database is deleted
   */
  _clearCache() {
    this._addons = null;
    return AddonDatabase.delete().then(() =>
      AddonManagerPrivate.updateAddonRepositoryData());
  },

  async _repopulateCacheInternal(aSendPerformance, aTimeout) {
    let allAddons = await AddonManager.getAllAddons();

    // Filter the hotfix out of our list of add-ons
    allAddons = allAddons.filter(a => a.id != AddonManager.hotfixID);

    // Completely remove cache if caching is not enabled
    if (!this.cacheEnabled) {
      logger.debug("Clearing cache because it is disabled");
      await this._clearCache();
      return;
    }

    let ids = allAddons.map(a => a.id);
    logger.debug("Repopulate add-on cache with " + ids.toSource());

    let addonsToCache = await getAddonsToCache(ids);

    // Completely remove cache if there are no add-ons to cache
    if (addonsToCache.length == 0) {
      logger.debug("Clearing cache because 0 add-ons were requested");
      await this._clearCache();
      return;
    }

    await new Promise((resolve, reject) =>
      this._beginGetAddons(addonsToCache, {
        searchSucceeded: aAddons => {
          this._addons = new Map();
          for (let addon of aAddons) {
            this._addons.set(addon.id, addon);
          }
          AddonDatabase.repopulate(aAddons, resolve);
        },
        searchFailed: () => {
          logger.warn("Search failed when repopulating cache");
          resolve();
        }
      }, aSendPerformance, aTimeout));

    // Always call AddonManager updateAddonRepositoryData after we refill the cache
    await AddonManagerPrivate.updateAddonRepositoryData();
  },

  /**
   * Asynchronously add add-ons to the cache corresponding to the specified
   * ids. If caching is disabled, the cache is unchanged and the callback is
   * immediately called if it is defined.
   *
   * @param  aIds
   *         The array of add-on ids to add to the cache
   * @param  aCallback
   *         The optional callback to call once complete
   */
  cacheAddons(aIds, aCallback) {
    logger.debug("cacheAddons: enabled " + this.cacheEnabled + " IDs " + aIds.toSource());
    if (!this.cacheEnabled) {
      if (aCallback)
        aCallback();
      return;
    }

    getAddonsToCache(aIds).then(aAddons => {
      // If there are no add-ons to cache, act as if caching is disabled
      if (aAddons.length == 0) {
        if (aCallback)
          aCallback();
        return;
      }

      this.getAddonsByIDs(aAddons, {
        searchSucceeded: aAddons => {
          for (let addon of aAddons) {
            this._addons.set(addon.id, addon);
          }
          AddonDatabase.insertAddons(aAddons, aCallback);
        },
        searchFailed: () => {
          logger.warn("Search failed when adding add-ons to cache");
          if (aCallback)
            aCallback();
        }
      });
    });
  },

  /**
   * The homepage for visiting this repository. If the corresponding preference
   * is not defined, defaults to about:blank.
   */
  get homepageURL() {
    let url = this._formatURLPref(PREF_GETADDONS_BROWSEADDONS, {});
    return (url != null) ? url : "about:blank";
  },

  /**
   * Returns whether this instance is currently performing a search. New
   * searches will not be performed while this is the case.
   */
  get isSearching() {
    return this._searching;
  },

  /**
   * The url that can be visited to see recommended add-ons in this repository.
   * If the corresponding preference is not defined, defaults to about:blank.
   */
  getRecommendedURL() {
    let url = this._formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED, {});
    return (url != null) ? url : "about:blank";
  },

  /**
   * Retrieves the url that can be visited to see search results for the given
   * terms. If the corresponding preference is not defined, defaults to
   * about:blank.
   *
   * @param  aSearchTerms
   *         Search terms used to search the repository
   */
  getSearchURL(aSearchTerms) {
    let url = this._formatURLPref(PREF_GETADDONS_BROWSESEARCHRESULTS, {
      TERMS: encodeURIComponent(aSearchTerms)
    });
    return (url != null) ? url : "about:blank";
  },

  /**
   * Cancels the search in progress. If there is no search in progress this
   * does nothing.
   */
  cancelSearch() {
    this._searching = false;
    if (this._request) {
      this._request.abort();
      this._request = null;
    }
    this._callback = null;
  },

  /**
   * Begins a search for add-ons in this repository by ID. Results will be
   * passed to the given callback.
   *
   * @param  aIDs
   *         The array of ids to search for
   * @param  aCallback
   *         The callback to pass results to
   */
  getAddonsByIDs(aIDs, aCallback) {
    return this._beginGetAddons(aIDs, aCallback, false);
  },

  /**
   * Begins a search of add-ons, potentially sending performance data.
   *
   * @param  aIDs
   *         Array of ids to search for.
   * @param  aCallback
   *         Function to pass results to.
   * @param  aSendPerformance
   *         Boolean indicating whether to send performance data with the
   *         request.
   * @param  aTimeout
   *         (Optional) timeout in milliseconds to abandon the XHR request
   *         if we have not received a response from the server.
   */
  _beginGetAddons(aIDs, aCallback, aSendPerformance, aTimeout) {
    let ids = aIDs.slice(0);

    let params = {
      API_VERSION,
      IDS: ids.map(encodeURIComponent).join(",")
    };

    let pref = PREF_GETADDONS_BYIDS;

    if (aSendPerformance) {
      let type = Services.prefs.getPrefType(PREF_GETADDONS_BYIDS_PERFORMANCE);
      if (type == Services.prefs.PREF_STRING) {
        pref = PREF_GETADDONS_BYIDS_PERFORMANCE;

        let startupInfo = Cc["@mozilla.org/toolkit/app-startup;1"].
                          getService(Ci.nsIAppStartup).
                          getStartupInfo();

        params.TIME_MAIN = "";
        params.TIME_FIRST_PAINT = "";
        params.TIME_SESSION_RESTORED = "";
        if (startupInfo.process) {
          if (startupInfo.main) {
            params.TIME_MAIN = startupInfo.main - startupInfo.process;
          }
          if (startupInfo.firstPaint) {
            params.TIME_FIRST_PAINT = startupInfo.firstPaint -
                                      startupInfo.process;
          }
          if (startupInfo.sessionRestored) {
            params.TIME_SESSION_RESTORED = startupInfo.sessionRestored -
                                           startupInfo.process;
          }
        }
      }
    }

    let url = this._formatURLPref(pref, params);

    let handleResults = (aElements, aTotalResults, aCompatData) => {
      // Don't use this._parseAddons() so that, for example,
      // incompatible add-ons are not filtered out
      let results = [];
      for (let i = 0; i < aElements.length && results.length < this._maxResults; i++) {
        let result = this._parseAddon(aElements[i], null, aCompatData);
        if (result == null)
          continue;

        // Ignore add-on if it wasn't actually requested
        let idIndex = ids.indexOf(result.addon.id);
        if (idIndex == -1)
          continue;

        // Ignore add-on if the add-on manager doesn't know about its type:
        if (!(result.addon.type in AddonManager.addonTypes)) {
          continue;
        }

        results.push(result);
        // Ignore this add-on from now on
        ids.splice(idIndex, 1);
      }

      // Include any compatibility overrides for addons not hosted by the
      // remote repository.
      for (let id in aCompatData) {
        let addonCompat = aCompatData[id];
        if (addonCompat.hosted)
          continue;

        let addon = new AddonSearchResult(addonCompat.id);
        // Compatibility overrides can only be for extensions.
        addon.type = "extension";
        addon.compatibilityOverrides = addonCompat.compatRanges;
        let result = {
          addon,
          xpiURL: null,
          xpiHash: null
        };
        results.push(result);
      }

      // aTotalResults irrelevant
      this._reportSuccess(results, -1);
    }

    this._beginSearch(url, ids.length, aCallback, handleResults, aTimeout);
  },

  /**
   * Performs the daily background update check.
   *
   * This API both searches for the add-on IDs specified and sends performance
   * data. It is meant to be called as part of the daily update ping. It should
   * not be used for any other purpose. Use repopulateCache instead.
   *
   * @return Promise{null} Resolves when the metadata update is complete.
   */
  backgroundUpdateCheck() {
    return this._repopulateCacheInternal(true);
  },

  /**
   * Begins a search for recommended add-ons in this repository. Results will
   * be passed to the given callback.
   *
   * @param  aMaxResults
   *         The maximum number of results to return
   * @param  aCallback
   *         The callback to pass results to
   */
  retrieveRecommendedAddons(aMaxResults, aCallback) {
    let url = this._formatURLPref(PREF_GETADDONS_GETRECOMMENDED, {
      API_VERSION,

      // Get twice as many results to account for potential filtering
      MAX_RESULTS: 2 * aMaxResults
    });

    let handleResults = (aElements, aTotalResults) => {
      this._getLocalAddonIds(aLocalAddonIds => {
        // aTotalResults irrelevant
        this._parseAddons(aElements, -1, aLocalAddonIds);
      });
    }

    this._beginSearch(url, aMaxResults, aCallback, handleResults);
  },

  /**
   * Begins a search for add-ons in this repository. Results will be passed to
   * the given callback.
   *
   * @param  aSearchTerms
   *         The terms to search for
   * @param  aMaxResults
   *         The maximum number of results to return
   * @param  aCallback
   *         The callback to pass results to
   */
  searchAddons(aSearchTerms, aMaxResults, aCallback) {
    let compatMode = "normal";
    if (!AddonManager.checkCompatibility)
      compatMode = "ignore";
    else if (AddonManager.strictCompatibility)
      compatMode = "strict";

    let substitutions = {
      API_VERSION,
      TERMS: encodeURIComponent(aSearchTerms),
      // Get twice as many results to account for potential filtering
      MAX_RESULTS: 2 * aMaxResults,
      COMPATIBILITY_MODE: compatMode,
    };

    let url = this._formatURLPref(PREF_GETADDONS_GETSEARCHRESULTS, substitutions);

    let handleResults = (aElements, aTotalResults) => {
      this._getLocalAddonIds(aLocalAddonIds => {
        this._parseAddons(aElements, aTotalResults, aLocalAddonIds);
      });
    }

    this._beginSearch(url, aMaxResults, aCallback, handleResults);
  },

  // Posts results to the callback
  _reportSuccess(aResults, aTotalResults) {
    this._searching = false;
    this._request = null;
    // The callback may want to trigger a new search so clear references early
    let addons = aResults.map(result => result.addon);
    let callback = this._callback;
    this._callback = null;
    callback.searchSucceeded(addons, addons.length, aTotalResults);
  },

  // Notifies the callback of a failure
  _reportFailure() {
    this._searching = false;
    this._request = null;
    // The callback may want to trigger a new search so clear references early
    let callback = this._callback;
    this._callback = null;
    callback.searchFailed();
  },

  // Get descendant by unique tag name. Returns null if not unique tag name.
  _getUniqueDescendant(aElement, aTagName) {
    let elementsList = aElement.getElementsByTagName(aTagName);
    return (elementsList.length == 1) ? elementsList[0] : null;
  },

  // Get direct descendant by unique tag name.
  // Returns null if not unique tag name.
  _getUniqueDirectDescendant(aElement, aTagName) {
    let elementsList = Array.filter(aElement.children,
                                    aChild => aChild.tagName == aTagName);
    return (elementsList.length == 1) ? elementsList[0] : null;
  },

  // Parse out trimmed text content. Returns null if text content empty.
  _getTextContent(aElement) {
    let textContent = aElement.textContent.trim();
    return (textContent.length > 0) ? textContent : null;
  },

  // Parse out trimmed text content of a descendant with the specified tag name
  // Returns null if the parsing unsuccessful.
  _getDescendantTextContent(aElement, aTagName) {
    let descendant = this._getUniqueDescendant(aElement, aTagName);
    return (descendant != null) ? this._getTextContent(descendant) : null;
  },

  // Parse out trimmed text content of a direct descendant with the specified
  // tag name.
  // Returns null if the parsing unsuccessful.
  _getDirectDescendantTextContent(aElement, aTagName) {
    let descendant = this._getUniqueDirectDescendant(aElement, aTagName);
    return (descendant != null) ? this._getTextContent(descendant) : null;
  },

  /*
   * Creates an AddonSearchResult by parsing an <addon> element
   *
   * @param  aElement
   *         The <addon> element to parse
   * @param  aSkip
   *         Object containing ids and sourceURIs of add-ons to skip.
   * @param  aCompatData
   *         Array of parsed addon_compatibility elements to accosiate with the
   *         resulting AddonSearchResult. Optional.
   * @return Result object containing the parsed AddonSearchResult, xpiURL and
   *         xpiHash if the parsing was successful. Otherwise returns null.
   */
  _parseAddon(aElement, aSkip, aCompatData) {
    let skipIDs = (aSkip && aSkip.ids) ? aSkip.ids : [];
    let skipSourceURIs = (aSkip && aSkip.sourceURIs) ? aSkip.sourceURIs : [];

    let guid = this._getDescendantTextContent(aElement, "guid");
    if (guid == null || skipIDs.indexOf(guid) != -1)
      return null;

    let addon = new AddonSearchResult(guid);
    let result = {
      addon,
      xpiURL: null,
      xpiHash: null
    };

    if (aCompatData && guid in aCompatData)
      addon.compatibilityOverrides = aCompatData[guid].compatRanges;

    for (let node = aElement.firstChild; node; node = node.nextSibling) {
      if (!(node instanceof Ci.nsIDOMElement))
        continue;

      let localName = node.localName;

      // Handle case where the wanted string value is located in text content
      // but only if the content is not empty
      if (localName in STRING_KEY_MAP) {
        addon[STRING_KEY_MAP[localName]] = this._getTextContent(node) || addon[STRING_KEY_MAP[localName]];
        continue;
      }

      // Handle case where the wanted string value is html located in text content
      if (localName in HTML_KEY_MAP) {
        addon[HTML_KEY_MAP[localName]] = convertHTMLToPlainText(this._getTextContent(node));
        continue;
      }

      // Handle case where the wanted integer value is located in text content
      if (localName in INTEGER_KEY_MAP) {
        let value = parseInt(this._getTextContent(node));
        if (value >= 0)
          addon[INTEGER_KEY_MAP[localName]] = value;
        continue;
      }

      // Handle cases that aren't as simple as grabbing the text content
      switch (localName) {
        case "type":
          // Map AMO's type id to corresponding string
          // https://github.com/mozilla/olympia/blob/master/apps/constants/base.py#L127
          // These definitions need to be updated whenever AMO adds a new type.
          let id = parseInt(node.getAttribute("id"));
          switch (id) {
            case 1:
              addon.type = "extension";
              break;
            case 2:
              addon.type = "theme";
              break;
            case 3:
              addon.type = "dictionary";
              break;
            case 4:
              addon.type = "search";
              break;
            case 5:
            case 6:
              addon.type = "locale";
              break;
            case 7:
              addon.type = "plugin";
              break;
            case 8:
              addon.type = "api";
              break;
            case 9:
              addon.type = "lightweight-theme";
              break;
            case 11:
              addon.type = "webapp";
              break;
            default:
              logger.info("Unknown type id " + id + " found when parsing response for GUID " + guid);
          }
          break;
        case "authors":
          let authorNodes = node.getElementsByTagName("author");
          for (let authorNode of authorNodes) {
            let name = this._getDescendantTextContent(authorNode, "name");
            let link = this._getDescendantTextContent(authorNode, "link");
            if (name == null || link == null)
              continue;

            let author = new AddonManagerPrivate.AddonAuthor(name, link);
            if (addon.creator == null)
              addon.creator = author;
            else {
              if (addon.developers == null)
                addon.developers = [];

              addon.developers.push(author);
            }
          }
          break;
        case "previews":
          let previewNodes = node.getElementsByTagName("preview");
          for (let previewNode of previewNodes) {
            let full = this._getUniqueDescendant(previewNode, "full");
            if (full == null)
              continue;

            let fullURL = this._getTextContent(full);
            let fullWidth = full.getAttribute("width");
            let fullHeight = full.getAttribute("height");

            let thumbnailURL, thumbnailWidth, thumbnailHeight;
            let thumbnail = this._getUniqueDescendant(previewNode, "thumbnail");
            if (thumbnail) {
              thumbnailURL = this._getTextContent(thumbnail);
              thumbnailWidth = thumbnail.getAttribute("width");
              thumbnailHeight = thumbnail.getAttribute("height");
            }
            let caption = this._getDescendantTextContent(previewNode, "caption");
            let screenshot = new AddonManagerPrivate.AddonScreenshot(fullURL, fullWidth, fullHeight,
                                                                     thumbnailURL, thumbnailWidth,
                                                                     thumbnailHeight, caption);

            if (addon.screenshots == null)
              addon.screenshots = [];

            if (previewNode.getAttribute("primary") == 1)
              addon.screenshots.unshift(screenshot);
            else
              addon.screenshots.push(screenshot);
          }
          break;
        case "learnmore":
          addon.learnmoreURL = this._getTextContent(node);
          addon.homepageURL = addon.homepageURL || addon.learnmoreURL;
          break;
        case "contribution_data":
          let meetDevelopers = this._getDescendantTextContent(node, "meet_developers");
          let suggestedAmount = this._getDescendantTextContent(node, "suggested_amount");
          if (meetDevelopers != null) {
            addon.contributionURL = meetDevelopers;
            addon.contributionAmount = suggestedAmount;
          }
          break
        case "payment_data":
          let link = this._getDescendantTextContent(node, "link");
          let amountTag = this._getUniqueDescendant(node, "amount");
          let amount = parseFloat(amountTag.getAttribute("amount"));
          let displayAmount = this._getTextContent(amountTag);
          if (link != null && amount != null && displayAmount != null) {
            addon.purchaseURL = link;
            addon.purchaseAmount = amount;
            addon.purchaseDisplayAmount = displayAmount;
          }
          break
        case "rating":
          let averageRating = parseInt(this._getTextContent(node));
          if (averageRating >= 0)
            addon.averageRating = Math.min(5, averageRating);
          break;
        case "reviews":
          let url = this._getTextContent(node);
          let num = parseInt(node.getAttribute("num"));
          if (url != null && num >= 0) {
            addon.reviewURL = url;
            addon.reviewCount = num;
          }
          break;
        case "status":
          let repositoryStatus = parseInt(node.getAttribute("id"));
          if (!isNaN(repositoryStatus))
            addon.repositoryStatus = repositoryStatus;
          break;
        case "all_compatible_os":
          let nodes = node.getElementsByTagName("os");
          addon.isPlatformCompatible = Array.some(nodes, function(aNode) {
            let text = aNode.textContent.toLowerCase().trim();
            return text == "all" || text == Services.appinfo.OS.toLowerCase();
          });
          break;
        case "install":
          // No os attribute means the xpi is compatible with any os
          if (node.hasAttribute("os")) {
            let os = node.getAttribute("os").trim().toLowerCase();
            // If the os is not ALL and not the current OS then ignore this xpi
            if (os != "all" && os != Services.appinfo.OS.toLowerCase())
              break;
          }

          let xpiURL = this._getTextContent(node);
          if (xpiURL == null)
            break;

          if (skipSourceURIs.indexOf(xpiURL) != -1)
            return null;

          result.xpiURL = xpiURL;
          addon.sourceURI = NetUtil.newURI(xpiURL);

          let size = parseInt(node.getAttribute("size"));
          addon.size = (size >= 0) ? size : null;

          let xpiHash = node.getAttribute("hash");
          if (xpiHash != null)
            xpiHash = xpiHash.trim();
          result.xpiHash = xpiHash ? xpiHash : null;
          break;
        case "last_updated":
          let epoch = parseInt(node.getAttribute("epoch"));
          if (!isNaN(epoch))
            addon.updateDate = new Date(1000 * epoch);
          break;
        case "icon":
          addon.icons[node.getAttribute("size")] = this._getTextContent(node);
          break;
      }
    }

    return result;
  },

  _parseAddons(aElements, aTotalResults, aSkip) {
    let results = [];

    let isSameApplication = aAppNode => this._getTextContent(aAppNode) == Services.appinfo.ID;

    for (let i = 0; i < aElements.length && results.length < this._maxResults; i++) {
      let element = aElements[i];

      let tags = this._getUniqueDescendant(element, "compatible_applications");
      if (tags == null)
        continue;

      let applications = tags.getElementsByTagName("appID");
      let compatible = Array.some(applications, aAppNode => {
        if (!isSameApplication(aAppNode))
          return false;

        let parent = aAppNode.parentNode;
        let minVersion = this._getDescendantTextContent(parent, "min_version");
        let maxVersion = this._getDescendantTextContent(parent, "max_version");
        if (minVersion == null || maxVersion == null)
          return false;

        let currentVersion = Services.appinfo.version;
        return (Services.vc.compare(minVersion, currentVersion) <= 0 &&
                ((!AddonManager.strictCompatibility) ||
                 Services.vc.compare(currentVersion, maxVersion) <= 0));
      });

      // Ignore add-ons not compatible with this Application
      if (!compatible) {
        if (AddonManager.checkCompatibility)
          continue;

        if (!Array.some(applications, isSameApplication))
          continue;
      }

      // Add-on meets all requirements, so parse out data.
      // Don't pass in compatiblity override data, because that's only returned
      // in GUID searches, which don't use _parseAddons().
      let result = this._parseAddon(element, aSkip);
      if (result == null)
        continue;

      // Ignore add-on missing a required attribute
      let requiredAttributes = ["id", "name", "version", "type", "creator"];
      if (requiredAttributes.some(aAttribute => !result.addon[aAttribute]))
        continue;

      // Ignore add-on with a type AddonManager doesn't understand:
      if (!(result.addon.type in AddonManager.addonTypes))
        continue;

      // Add only if the add-on is compatible with the platform
      if (!result.addon.isPlatformCompatible)
        continue;

      // Add only if there was an xpi compatible with this OS or there was a
      // way to purchase the add-on
      if (!result.xpiURL && !result.addon.purchaseURL)
        continue;

      result.addon.isCompatible = compatible;

      results.push(result);
      // Ignore this add-on from now on by adding it to the skip array
      aSkip.ids.push(result.addon.id);
    }

    // Immediately report success if no AddonInstall instances to create
    let pendingResults = results.length;
    if (pendingResults == 0) {
      this._reportSuccess(results, aTotalResults);
      return;
    }

    // Create an AddonInstall for each result
    for (let result of results) {
      let addon = result.addon;
      let callback = aInstall => {
        addon.install = aInstall;
        pendingResults--;
        if (pendingResults == 0)
          this._reportSuccess(results, aTotalResults);
      }

      if (result.xpiURL) {
        AddonManager.getInstallForURL(result.xpiURL, callback,
                                      "application/x-xpinstall", result.xpiHash,
                                      addon.name, addon.icons, addon.version);
      } else {
        callback(null);
      }
    }
  },

  // Parses addon_compatibility nodes, that describe compatibility overrides.
  _parseAddonCompatElement(aResultObj, aElement) {
    let guid = this._getDescendantTextContent(aElement, "guid");
    if (!guid) {
        logger.debug("Compatibility override is missing guid.");
      return;
    }

    let compat = {id: guid};
    compat.hosted = aElement.getAttribute("hosted") != "false";

    function findMatchingAppRange(aNodes) {
      let toolkitAppRange = null;
      for (let node of aNodes) {
        let appID = this._getDescendantTextContent(node, "appID");
        if (appID != Services.appinfo.ID && appID != TOOLKIT_ID)
          continue;

        let minVersion = this._getDescendantTextContent(node, "min_version");
        let maxVersion = this._getDescendantTextContent(node, "max_version");
        if (minVersion == null || maxVersion == null)
          continue;

        let appRange = { appID,
                         appMinVersion: minVersion,
                         appMaxVersion: maxVersion };

        // Only use Toolkit app ranges if no ranges match the application ID.
        if (appID == TOOLKIT_ID)
          toolkitAppRange = appRange;
        else
          return appRange;
      }
      return toolkitAppRange;
    }

    function parseRangeNode(aNode) {
      let type = aNode.getAttribute("type");
      // Only "incompatible" (blacklisting) is supported for now.
      if (type != "incompatible") {
        logger.debug("Compatibility override of unsupported type found.");
        return null;
      }

      let override = new AddonManagerPrivate.AddonCompatibilityOverride(type);

      override.minVersion = this._getDirectDescendantTextContent(aNode, "min_version");
      override.maxVersion = this._getDirectDescendantTextContent(aNode, "max_version");

      if (!override.minVersion) {
        logger.debug("Compatibility override is missing min_version.");
        return null;
      }
      if (!override.maxVersion) {
        logger.debug("Compatibility override is missing max_version.");
        return null;
      }

      let appRanges = aNode.querySelectorAll("compatible_applications > application");
      let appRange = findMatchingAppRange.bind(this)(appRanges);
      if (!appRange) {
        logger.debug("Compatibility override is missing a valid application range.");
        return null;
      }

      override.appID = appRange.appID;
      override.appMinVersion = appRange.appMinVersion;
      override.appMaxVersion = appRange.appMaxVersion;

      return override;
    }

    let rangeNodes = aElement.querySelectorAll("version_ranges > version_range");
    compat.compatRanges = Array.map(rangeNodes, parseRangeNode.bind(this))
                               .filter(aItem => !!aItem);
    if (compat.compatRanges.length == 0)
      return;

    aResultObj[compat.id] = compat;
  },

  // Parses addon_compatibility elements.
  _parseAddonCompatData(aElements) {
    let compatData = {};
    Array.forEach(aElements, this._parseAddonCompatElement.bind(this, compatData));
    return compatData;
  },

  // Begins a new search if one isn't currently executing
  _beginSearch(aURI, aMaxResults, aCallback, aHandleResults, aTimeout) {
    if (this._searching || aURI == null || aMaxResults <= 0) {
      logger.warn("AddonRepository search failed: searching " + this._searching + " aURI " + aURI +
                  " aMaxResults " + aMaxResults);
      aCallback.searchFailed();
      return;
    }

    this._searching = true;
    this._callback = aCallback;
    this._maxResults = aMaxResults;

    logger.debug("Requesting " + aURI);

    this._request = new ServiceRequest();
    this._request.mozBackgroundRequest = true;
    this._request.open("GET", aURI, true);
    this._request.overrideMimeType("text/xml");
    if (aTimeout) {
      this._request.timeout = aTimeout;
    }

    this._request.addEventListener("error", aEvent => this._reportFailure());
    this._request.addEventListener("timeout", aEvent => this._reportFailure());
    this._request.addEventListener("load", aEvent => {
      logger.debug("Got metadata search load event");
      let request = aEvent.target;
      let responseXML = request.responseXML;

      if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
          (request.status != 200 && request.status != 0)) {
        this._reportFailure();
        return;
      }

      let documentElement = responseXML.documentElement;
      let elements = documentElement.getElementsByTagName("addon");
      let totalResults = elements.length;
      let parsedTotalResults = parseInt(documentElement.getAttribute("total_results"));
      // Parsed value of total results only makes sense if >= elements.length
      if (parsedTotalResults >= totalResults)
        totalResults = parsedTotalResults;

      let compatElements = documentElement.getElementsByTagName("addon_compatibility");
      let compatData = this._parseAddonCompatData(compatElements);

      aHandleResults(elements, totalResults, compatData);
    });
    this._request.send(null);
  },

  // Gets the id's of local add-ons, and the sourceURI's of local installs,
  // passing the results to aCallback
  _getLocalAddonIds(aCallback) {
    let localAddonIds = {ids: null, sourceURIs: null};

    AddonManager.getAllAddons(function(aAddons) {
      localAddonIds.ids = aAddons.map(a => a.id);
      if (localAddonIds.sourceURIs)
        aCallback(localAddonIds);
    });

    AddonManager.getAllInstalls(function(aInstalls) {
      localAddonIds.sourceURIs = [];
      for (let install of aInstalls) {
        if (install.state != AddonManager.STATE_AVAILABLE)
          localAddonIds.sourceURIs.push(install.sourceURI.spec);
      }

      if (localAddonIds.ids)
        aCallback(localAddonIds);
    });
  },

  // Create url from preference, returning null if preference does not exist
  _formatURLPref(aPreference, aSubstitutions) {
    let url = Services.prefs.getCharPref(aPreference, "");
    if (!url) {
      logger.warn("_formatURLPref: Couldn't get pref: " + aPreference);
      return null;
    }

    url = url.replace(/%([A-Z_]+)%/g, function(aMatch, aKey) {
      return (aKey in aSubstitutions) ? aSubstitutions[aKey] : aMatch;
    });

    return Services.urlFormatter.formatURL(url);
  },

  // Find a AddonCompatibilityOverride that matches a given aAddonVersion and
  // application/platform version.
  findMatchingCompatOverride(aAddonVersion,
                                                                     aCompatOverrides,
                                                                     aAppVersion,
                                                                     aPlatformVersion) {
    for (let override of aCompatOverrides) {

      let appVersion = null;
      if (override.appID == TOOLKIT_ID)
        appVersion = aPlatformVersion || Services.appinfo.platformVersion;
      else
        appVersion = aAppVersion || Services.appinfo.version;

      if (Services.vc.compare(override.minVersion, aAddonVersion) <= 0 &&
          Services.vc.compare(aAddonVersion, override.maxVersion) <= 0 &&
          Services.vc.compare(override.appMinVersion, appVersion) <= 0 &&
          Services.vc.compare(appVersion, override.appMaxVersion) <= 0) {
        return override;
      }
    }
    return null;
  },

  flush() {
    return AddonDatabase.flush();
  }
};

var AddonDatabase = {
  connectionPromise: null,
  // the in-memory database
  DB: BLANK_DB(),

  /**
   * A getter to retrieve the path to the DB
   */
  get jsonFile() {
    return OS.Path.join(OS.Constants.Path.profileDir, FILE_DATABASE);
 },

  /**
   * Asynchronously opens a new connection to the database file.
   *
   * @return {Promise} a promise that resolves to the database.
   */
  openConnection() {
    if (!this.connectionPromise) {
     this.connectionPromise = (async () => {
       this.DB = BLANK_DB();

       let inputDB, schema;

       try {
         let data = await OS.File.read(this.jsonFile, { encoding: "utf-8"})
         inputDB = JSON.parse(data);

         if (!inputDB.hasOwnProperty("addons") ||
             !Array.isArray(inputDB.addons)) {
           throw new Error("No addons array.");
         }

         if (!inputDB.hasOwnProperty("schema")) {
           throw new Error("No schema specified.");
         }

         schema = parseInt(inputDB.schema, 10);

         if (!Number.isInteger(schema) ||
             schema < DB_MIN_JSON_SCHEMA) {
           throw new Error("Invalid schema value.");
         }
       } catch (e) {
         if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
           logger.debug("No " + FILE_DATABASE + " found.");
         } else {
           logger.error(`Malformed ${FILE_DATABASE}: ${e} - resetting to empty`);
         }

         // Create a blank addons.json file
         this._saveDBToDisk();

         let dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA, 0);

         if (dbSchema < DB_MIN_JSON_SCHEMA) {
           let results = await new Promise((resolve, reject) => {
             AddonRepository_SQLiteMigrator.migrate(resolve);
           });

           if (results.length) {
             await this._insertAddons(results);
           }

         }

         Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
         return this.DB;
       }

       Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);

       // We use _insertAddon manually instead of calling
       // insertAddons to avoid the write to disk which would
       // be a waste since this is the data that was just read.
       for (let addon of inputDB.addons) {
         this._insertAddon(addon);
       }

       return this.DB;
     })();
    }

    return this.connectionPromise;
  },

  /**
   * A lazy getter for the database connection.
   */
  get connection() {
    return this.openConnection();
  },

  /**
   * Asynchronously shuts down the database connection and releases all
   * cached objects
   *
   * @param  aCallback
   *         An optional callback to call once complete
   * @param  aSkipFlush
   *         An optional boolean to skip flushing data to disk. Useful
   *         when the database is going to be deleted afterwards.
   */
  shutdown(aSkipFlush) {
    if (!this.connectionPromise) {
      return Promise.resolve();
    }

    this.connectionPromise = null;

    if (aSkipFlush) {
      return Promise.resolve();
    }
    return this.Writer.flush();
  },

  /**
   * Asynchronously deletes the database, shutting down the connection
   * first if initialized
   *
   * @param  aCallback
   *         An optional callback to call once complete
   * @return Promise{null} resolves when the database has been deleted
   */
  delete(aCallback) {
    this.DB = BLANK_DB();

    this._deleting = this.Writer.flush()
      .catch(() => {})
      // shutdown(true) never rejects
      .then(() => this.shutdown(true))
      .then(() => OS.File.remove(this.jsonFile, {}))
      .catch(error => logger.error("Unable to delete Addon Repository file " +
                                 this.jsonFile, error))
      .then(() => this._deleting = null)
      .then(aCallback);
    return this._deleting;
  },

  toJSON() {
    let json = {
      schema: this.DB.schema,
      addons: []
    }

    for (let [, value] of this.DB.addons)
      json.addons.push(value);

    return json;
  },

  /*
   * This is a deferred task writer that is used
   * to batch operations done within 50ms of each
   * other and thus generating only one write to disk
   */
  get Writer() {
    delete this.Writer;
    this.Writer = new DeferredSave(
      this.jsonFile,
      () => { return JSON.stringify(this); },
      DB_BATCH_TIMEOUT_MS
    );
    return this.Writer;
  },

  /**
   * Flush any pending I/O on the addons.json file
   * @return: Promise{null}
   *          Resolves when the pending I/O (writing out or deleting
   *          addons.json) completes
   */
  flush() {
    if (this._deleting) {
      return this._deleting;
    }
    return this.Writer.flush();
  },

  /**
   * Asynchronously retrieve all add-ons from the database
   * @return: Promise{Map}
   *          Resolves when the add-ons are retrieved from the database
   */
  retrieveStoredData() {
    return this.openConnection().then(db => db.addons);
  },

  /**
   * Asynchronously repopulates the database so it only contains the
   * specified add-ons
   *
   * @param  aAddons
   *         The array of add-ons to repopulate the database with
   * @param  aCallback
   *         An optional callback to call once complete
   */
  repopulate(aAddons, aCallback) {
    this.DB.addons.clear();
    this.insertAddons(aAddons, function() {
      let now = Math.round(Date.now() / 1000);
      logger.debug("Cache repopulated, setting " + PREF_METADATA_LASTUPDATE + " to " + now);
      Services.prefs.setIntPref(PREF_METADATA_LASTUPDATE, now);
      if (aCallback)
        aCallback();
    });
  },

  /**
   * Asynchronously inserts an array of add-ons into the database
   *
   * @param  aAddons
   *         The array of add-ons to insert
   * @param  aCallback
   *         An optional callback to call once complete
   */
  async insertAddons(aAddons, aCallback) {
    await this.openConnection();
    await this._insertAddons(aAddons, aCallback);
  },

  async _insertAddons(aAddons, aCallback) {
    for (let addon of aAddons) {
      this._insertAddon(addon);
    }

    await this._saveDBToDisk();
    aCallback && aCallback();
  },

  /**
   * Inserts an individual add-on into the database. If the add-on already
   * exists in the database (by id), then the specified add-on will not be
   * inserted.
   *
   * @param  aAddon
   *         The add-on to insert into the database
   * @param  aCallback
   *         The callback to call once complete
   */
  _insertAddon(aAddon) {
    let newAddon = this._parseAddon(aAddon);
    if (!newAddon ||
        !newAddon.id ||
        this.DB.addons.has(newAddon.id))
      return;

    this.DB.addons.set(newAddon.id, newAddon);
  },

  /*
   * Creates an AddonSearchResult by parsing an object structure
   * retrieved from the DB JSON representation.
   *
   * @param  aObj
   *         The object to parse
   * @return Returns an AddonSearchResult object.
   */
  _parseAddon(aObj) {
    if (aObj instanceof AddonSearchResult)
      return aObj;

    let id = aObj.id;
    if (!aObj.id)
      return null;

    let addon = new AddonSearchResult(id);

    for (let expectedProperty of Object.keys(AddonSearchResult.prototype)) {
      if (!(expectedProperty in aObj) ||
          typeof(aObj[expectedProperty]) === "function")
        continue;

      let value = aObj[expectedProperty];

      try {
        switch (expectedProperty) {
          case "sourceURI":
            addon.sourceURI = value ? NetUtil.newURI(value) : null;
            break;

          case "creator":
            addon.creator = value
                            ? this._makeDeveloper(value)
                            : null;
            break;

          case "updateDate":
            addon.updateDate = value ? new Date(value) : null;
            break;

          case "developers":
            if (!addon.developers) addon.developers = [];
            for (let developer of value) {
              addon.developers.push(this._makeDeveloper(developer));
            }
            break;

          case "screenshots":
            if (!addon.screenshots) addon.screenshots = [];
            for (let screenshot of value) {
              addon.screenshots.push(this._makeScreenshot(screenshot));
            }
            break;

          case "compatibilityOverrides":
            if (!addon.compatibilityOverrides) addon.compatibilityOverrides = [];
            for (let override of value) {
              addon.compatibilityOverrides.push(
                this._makeCompatOverride(override)
              );
            }
            break;

          case "icons":
            if (!addon.icons) addon.icons = {};
            for (let size of Object.keys(aObj.icons)) {
              addon.icons[size] = aObj.icons[size];
            }
            break;

          case "iconURL":
            break;

          default:
            addon[expectedProperty] = value;
        }
      } catch (ex) {
        logger.warn("Error in parsing property value for " + expectedProperty + " | " + ex);
      }

      // delete property from obj to indicate we've already
      // handled it. The remaining public properties will
      // be stored separately and just passed through to
      // be written back to the DB.
      delete aObj[expectedProperty];
    }

    // Copy remaining properties to a separate object
    // to prevent accidental access on downgraded versions.
    // The properties will be merged in the same object
    // prior to being written back through toJSON.
    for (let remainingProperty of Object.keys(aObj)) {
      switch (typeof(aObj[remainingProperty])) {
        case "boolean":
        case "number":
        case "string":
        case "object":
          // these types are accepted
          break;
        default:
          continue;
      }

      if (!remainingProperty.startsWith("_"))
        addon._unsupportedProperties[remainingProperty] =
          aObj[remainingProperty];
    }

    return addon;
  },

  /**
   * Write the in-memory DB to disk, after waiting for
   * the DB_BATCH_TIMEOUT_MS timeout.
   *
   * @return Promise A promise that resolves after the
   *                 write to disk has completed.
   */
  _saveDBToDisk() {
    return this.Writer.saveChanges().catch(
      e => logger.error("SaveDBToDisk failed", e));
  },

  /**
   * Make a developer object from a vanilla
   * JS object from the JSON database
   *
   * @param  aObj
   *         The JS object to use
   * @return The created developer
   */
  _makeDeveloper(aObj) {
    let name = aObj.name;
    let url = aObj.url;
    return new AddonManagerPrivate.AddonAuthor(name, url);
  },

  /**
   * Make a screenshot object from a vanilla
   * JS object from the JSON database
   *
   * @param  aObj
   *         The JS object to use
   * @return The created screenshot
   */
  _makeScreenshot(aObj) {
    let url = aObj.url;
    let width = aObj.width;
    let height = aObj.height;
    let thumbnailURL = aObj.thumbnailURL;
    let thumbnailWidth = aObj.thumbnailWidth;
    let thumbnailHeight = aObj.thumbnailHeight;
    let caption = aObj.caption;
    return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
                                                   thumbnailWidth, thumbnailHeight, caption);
  },

  /**
   * Make a CompatibilityOverride from a vanilla
   * JS object from the JSON database
   *
   * @param  aObj
   *         The JS object to use
   * @return The created CompatibilityOverride
   */
  _makeCompatOverride(aObj) {
    let type = aObj.type;
    let minVersion = aObj.minVersion;
    let maxVersion = aObj.maxVersion;
    let appID = aObj.appID;
    let appMinVersion = aObj.appMinVersion;
    let appMaxVersion = aObj.appMaxVersion;
    return new AddonManagerPrivate.AddonCompatibilityOverride(type,
                                                              minVersion,
                                                              maxVersion,
                                                              appID,
                                                              appMinVersion,
                                                              appMaxVersion);
  },
};
PK
!<\SGG1modules/addons/AddonRepository_SQLiteMigrator.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/FileUtils.jsm");

const KEY_PROFILEDIR  = "ProfD";
const FILE_DATABASE   = "addons.sqlite";
const LAST_DB_SCHEMA   = 4;

// Add-on properties present in the columns of the database
const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
                     "fullDescription", "developerComments", "eula",
                     "homepageURL", "supportURL", "contributionURL",
                     "contributionAmount", "averageRating", "reviewCount",
                     "reviewURL", "totalDownloads", "weeklyDownloads",
                     "dailyUsers", "sourceURI", "repositoryStatus", "size",
                     "updateDate"];

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.repository.sqlmigrator";

// Create a new logger for use by the Addons Repository SQL Migrator
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];


this.AddonRepository_SQLiteMigrator = {

  /**
   * Migrates data from a previous SQLite version of the
   * database to the JSON version.
   *
   * @param structFunctions an object that contains functions
   *                        to create the various objects used
   *                        in the new JSON format
   * @param aCallback       A callback to be called when migration
   *                        finishes, with the results in an array
   * @returns bool          True if a migration will happen (DB was
   *                        found and succesfully opened)
   */
  migrate(aCallback) {
    if (!this._openConnection()) {
      this._closeConnection();
      aCallback([]);
      return false;
    }

    logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");

    this._retrieveStoredData((results) => {
      this._closeConnection();
      let resultArray = Object.keys(results).map(k => results[k]);
      logger.debug(resultArray.length + " addons imported.")
      aCallback(resultArray);
    });

    return true;
  },

  /**
   * Synchronously opens a new connection to the database file.
   *
   * @return bool           Whether the DB was opened successfully.
   */
  _openConnection() {
    delete this.connection;

    let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
    if (!dbfile.exists())
      return false;

    try {
      this.connection = Services.storage.openUnsharedDatabase(dbfile);
    } catch (e) {
      return false;
    }

    this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");

    // Any errors in here should rollback
    try {
      this.connection.beginTransaction();

      switch (this.connection.schemaVersion) {
        case 0:
          return false;

        case 1:
          logger.debug("Upgrading database schema to version 2");
          this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
          this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
          this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
          this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
        case 2:
          logger.debug("Upgrading database schema to version 3");
          this.connection.createTable("compatibility_override",
                                      "addon_internal_id INTEGER, " +
                                      "num INTEGER, " +
                                      "type TEXT, " +
                                      "minVersion TEXT, " +
                                      "maxVersion TEXT, " +
                                      "appID TEXT, " +
                                      "appMinVersion TEXT, " +
                                      "appMaxVersion TEXT, " +
                                      "PRIMARY KEY (addon_internal_id, num)");
        case 3:
          logger.debug("Upgrading database schema to version 4");
          this.connection.createTable("icon",
                                      "addon_internal_id INTEGER, " +
                                      "size INTEGER, " +
                                      "url TEXT, " +
                                      "PRIMARY KEY (addon_internal_id, size)");
          this._createIndices();
          this._createTriggers();
          this.connection.schemaVersion = LAST_DB_SCHEMA;
        case LAST_DB_SCHEMA:
          break;
        default:
          return false;
      }
      this.connection.commitTransaction();
    } catch (e) {
      logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
      this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
      this.connection.rollbackTransaction();
      return false;
    }

    return true;
  },

  _closeConnection() {
    for (let key in this.asyncStatementsCache) {
      let stmt = this.asyncStatementsCache[key];
      stmt.finalize();
    }
    this.asyncStatementsCache = {};

    if (this.connection)
      this.connection.asyncClose();

    delete this.connection;
  },

  /**
   * Asynchronously retrieve all add-ons from the database, and pass it
   * to the specified callback
   *
   * @param  aCallback
   *         The callback to pass the add-ons back to
   */
  _retrieveStoredData(aCallback) {
    let addons = {};

    // Retrieve all data from the addon table
    let getAllAddons = () => {
      this.getAsyncStatement("getAllAddons").executeAsync({
        handleResult: aResults => {
          let row = null;
          while ((row = aResults.getNextRow())) {
            let internal_id = row.getResultByName("internal_id");
            addons[internal_id] = this._makeAddonFromAsyncRow(row);
          }
        },

        handleError: this.asyncErrorLogger,

        handleCompletion(aReason) {
          if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
            logger.error("Error retrieving add-ons from database. Returning empty results");
            aCallback({});
            return;
          }

          getAllDevelopers();
        }
      });
    }

    // Retrieve all data from the developer table
    let getAllDevelopers = () => {
      this.getAsyncStatement("getAllDevelopers").executeAsync({
        handleResult: aResults => {
          let row = null;
          while ((row = aResults.getNextRow())) {
            let addon_internal_id = row.getResultByName("addon_internal_id");
            if (!(addon_internal_id in addons)) {
              logger.warn("Found a developer not linked to an add-on in database");
              continue;
            }

            let addon = addons[addon_internal_id];
            if (!addon.developers)
              addon.developers = [];

            addon.developers.push(this._makeDeveloperFromAsyncRow(row));
          }
        },

        handleError: this.asyncErrorLogger,

        handleCompletion(aReason) {
          if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
            logger.error("Error retrieving developers from database. Returning empty results");
            aCallback({});
            return;
          }

          getAllScreenshots();
        }
      });
    }

    // Retrieve all data from the screenshot table
    let getAllScreenshots = () => {
      this.getAsyncStatement("getAllScreenshots").executeAsync({
        handleResult: aResults => {
          let row = null;
          while ((row = aResults.getNextRow())) {
            let addon_internal_id = row.getResultByName("addon_internal_id");
            if (!(addon_internal_id in addons)) {
              logger.warn("Found a screenshot not linked to an add-on in database");
              continue;
            }

            let addon = addons[addon_internal_id];
            if (!addon.screenshots)
              addon.screenshots = [];
            addon.screenshots.push(this._makeScreenshotFromAsyncRow(row));
          }
        },

        handleError: this.asyncErrorLogger,

        handleCompletion(aReason) {
          if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
            logger.error("Error retrieving screenshots from database. Returning empty results");
            aCallback({});
            return;
          }

          getAllCompatOverrides();
        }
      });
    }

    let getAllCompatOverrides = () => {
      this.getAsyncStatement("getAllCompatOverrides").executeAsync({
        handleResult: aResults => {
          let row = null;
          while ((row = aResults.getNextRow())) {
            let addon_internal_id = row.getResultByName("addon_internal_id");
            if (!(addon_internal_id in addons)) {
              logger.warn("Found a compatibility override not linked to an add-on in database");
              continue;
            }

            let addon = addons[addon_internal_id];
            if (!addon.compatibilityOverrides)
              addon.compatibilityOverrides = [];
            addon.compatibilityOverrides.push(this._makeCompatOverrideFromAsyncRow(row));
          }
        },

        handleError: this.asyncErrorLogger,

        handleCompletion(aReason) {
          if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
            logger.error("Error retrieving compatibility overrides from database. Returning empty results");
            aCallback({});
            return;
          }

          getAllIcons();
        }
      });
    }

    let getAllIcons = () => {
      this.getAsyncStatement("getAllIcons").executeAsync({
        handleResult: aResults => {
          let row = null;
          while ((row = aResults.getNextRow())) {
            let addon_internal_id = row.getResultByName("addon_internal_id");
            if (!(addon_internal_id in addons)) {
              logger.warn("Found an icon not linked to an add-on in database");
              continue;
            }

            let addon = addons[addon_internal_id];
            let { size, url } = this._makeIconFromAsyncRow(row);
            addon.icons[size] = url;
            if (size == 32)
              addon.iconURL = url;
          }
        },

        handleError: this.asyncErrorLogger,

        handleCompletion(aReason) {
          if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
            logger.error("Error retrieving icons from database. Returning empty results");
            aCallback({});
            return;
          }

          let returnedAddons = {};
          for (let id in addons) {
            let addon = addons[id];
            returnedAddons[addon.id] = addon;
          }
          aCallback(returnedAddons);
        }
      });
    }

    // Begin asynchronous process
    getAllAddons();
  },

  // A cache of statements that are used and need to be finalized on shutdown
  asyncStatementsCache: {},

  /**
   * Gets a cached async statement or creates a new statement if it doesn't
   * already exist.
   *
   * @param  aKey
   *         A unique key to reference the statement
   * @return a mozIStorageAsyncStatement for the SQL corresponding to the
   *         unique key
   */
  getAsyncStatement(aKey) {
    if (aKey in this.asyncStatementsCache)
      return this.asyncStatementsCache[aKey];

    let sql = this.queries[aKey];
    try {
      return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
    } catch (e) {
      logger.error("Error creating statement " + aKey + " (" + sql + ")");
      throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
                                 e.result);
    }
  },

  // The queries used by the database
  queries: {
    getAllAddons: "SELECT internal_id, id, type, name, version, " +
                  "creator, creatorURL, description, fullDescription, " +
                  "developerComments, eula, homepageURL, supportURL, " +
                  "contributionURL, contributionAmount, averageRating, " +
                  "reviewCount, reviewURL, totalDownloads, weeklyDownloads, " +
                  "dailyUsers, sourceURI, repositoryStatus, size, updateDate " +
                  "FROM addon",

    getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " +
                      "ORDER BY addon_internal_id, num",

    getAllScreenshots: "SELECT addon_internal_id, url, width, height, " +
                       "thumbnailURL, thumbnailWidth, thumbnailHeight, caption " +
                       "FROM screenshot ORDER BY addon_internal_id, num",

    getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " +
                           "maxVersion, appID, appMinVersion, appMaxVersion " +
                           "FROM compatibility_override " +
                           "ORDER BY addon_internal_id, num",

    getAllIcons: "SELECT addon_internal_id, size, url FROM icon " +
                 "ORDER BY addon_internal_id, size",
  },

  /**
   * Make add-on structure from an asynchronous row.
   *
   * @param  aRow
   *         The asynchronous row to use
   * @return The created add-on
   */
  _makeAddonFromAsyncRow(aRow) {
    // This is intentionally not an AddonSearchResult object in order
    // to allow AddonDatabase._parseAddon to parse it, same as if it
    // was read from the JSON database.

    let addon = { icons: {} };

    for (let prop of PROP_SINGLE) {
      addon[prop] = aRow.getResultByName(prop)
    }

    return addon;
  },

  /**
   * Make a developer from an asynchronous row
   *
   * @param  aRow
   *         The asynchronous row to use
   * @return The created developer
   */
  _makeDeveloperFromAsyncRow(aRow) {
    let name = aRow.getResultByName("name");
    let url = aRow.getResultByName("url")
    return new AddonManagerPrivate.AddonAuthor(name, url);
  },

  /**
   * Make a screenshot from an asynchronous row
   *
   * @param  aRow
   *         The asynchronous row to use
   * @return The created screenshot
   */
  _makeScreenshotFromAsyncRow(aRow) {
    let url = aRow.getResultByName("url");
    let width = aRow.getResultByName("width");
    let height = aRow.getResultByName("height");
    let thumbnailURL = aRow.getResultByName("thumbnailURL");
    let thumbnailWidth = aRow.getResultByName("thumbnailWidth");
    let thumbnailHeight = aRow.getResultByName("thumbnailHeight");
    let caption = aRow.getResultByName("caption");
    return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
                                                   thumbnailWidth, thumbnailHeight, caption);
  },

  /**
   * Make a CompatibilityOverride from an asynchronous row
   *
   * @param  aRow
   *         The asynchronous row to use
   * @return The created CompatibilityOverride
   */
  _makeCompatOverrideFromAsyncRow(aRow) {
    let type = aRow.getResultByName("type");
    let minVersion = aRow.getResultByName("minVersion");
    let maxVersion = aRow.getResultByName("maxVersion");
    let appID = aRow.getResultByName("appID");
    let appMinVersion = aRow.getResultByName("appMinVersion");
    let appMaxVersion = aRow.getResultByName("appMaxVersion");
    return new AddonManagerPrivate.AddonCompatibilityOverride(type,
                                                              minVersion,
                                                              maxVersion,
                                                              appID,
                                                              appMinVersion,
                                                              appMaxVersion);
  },

  /**
   * Make an icon from an asynchronous row
   *
   * @param  aRow
   *         The asynchronous row to use
   * @return An object containing the size and URL of the icon
   */
  _makeIconFromAsyncRow(aRow) {
    let size = aRow.getResultByName("size");
    let url = aRow.getResultByName("url");
    return { size, url };
  },

  /**
   * A helper function to log an SQL error.
   *
   * @param  aError
   *         The storage error code associated with the error
   * @param  aErrorString
   *         An error message
   */
  logSQLError(aError, aErrorString) {
    logger.error("SQL error " + aError + ": " + aErrorString);
  },

  /**
   * A helper function to log any errors that occur during async statements.
   *
   * @param  aError
   *         A mozIStorageError to log
   */
  asyncErrorLogger(aError) {
    logger.error("Async SQL error " + aError.result + ": " + aError.message);
  },

  /**
   * Synchronously creates the triggers in the database.
   */
  _createTriggers() {
    this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
    this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
      "ON addon BEGIN " +
      "DELETE FROM developer WHERE addon_internal_id=old.internal_id; " +
      "DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " +
      "DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " +
      "DELETE FROM icon WHERE addon_internal_id=old.internal_id; " +
      "END");
  },

  /**
   * Synchronously creates the indices in the database.
   */
  _createIndices() {
    this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " +
                                     "ON developer (addon_internal_id)");
    this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " +
                                     "ON screenshot (addon_internal_id)");
    this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " +
                                     "ON compatibility_override (addon_internal_id)");
    this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS icon_idx " +
                                     "ON icon (addon_internal_id)");
  }
}
PK
!<
ܧ modules/addons/AddonSettings.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [ "AddonSettings" ];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

const PREF_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_ALLOW_LEGACY = "extensions.legacy.enabled";

this.AddonSettings = {};

// Make a non-changable property that can't be manipulated from other
// code in the app.
function makeConstant(name, value) {
  Object.defineProperty(AddonSettings, name, {
    configurable: false,
    enumerable: false,
    writable: false,
    value,
  });
}

makeConstant("ADDON_SIGNING", AppConstants.MOZ_ADDON_SIGNING);

if (AppConstants.MOZ_REQUIRE_SIGNING && !Cu.isInAutomation) {
  makeConstant("REQUIRE_SIGNING", true);
} else {
  XPCOMUtils.defineLazyPreferenceGetter(AddonSettings, "REQUIRE_SIGNING",
                                        PREF_SIGNATURES_REQUIRED, false);
}

if (AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS || Cu.isInAutomation) {
  XPCOMUtils.defineLazyPreferenceGetter(AddonSettings, "ALLOW_LEGACY_EXTENSIONS",
                                        PREF_ALLOW_LEGACY, true);
} else {
  makeConstant("ALLOW_LEGACY_EXTENSIONS", false);
}
PK
!<I%modules/addons/AddonUpdateChecker.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The AddonUpdateChecker is responsible for retrieving the update information
 * from an add-on's remote update manifest.
 */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ];

const TIMEOUT               = 60 * 1000;
const PREFIX_NS_RDF         = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
const PREFIX_NS_EM          = "http://www.mozilla.org/2004/em-rdf#";
const PREFIX_ITEM           = "urn:mozilla:item:";
const PREFIX_EXTENSION      = "urn:mozilla:extension:";
const PREFIX_THEME          = "urn:mozilla:theme:";
const TOOLKIT_ID            = "toolkit@mozilla.org"
const XMLURI_PARSE_ERROR    = "http://www.mozilla.org/newlayout/xml/parsererror.xml"

const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
                                  "resource://gre/modules/ServiceRequest.jsm");


// Shared code for suppressing bad cert dialogs.
XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
  let certUtils = {};
  Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
  return certUtils;
});

var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
           getService(Ci.nsIRDFService);

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.update-checker";

// Create a new logger for use by the Addons Update Checker
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

/**
 * A serialisation method for RDF data that produces an identical string
 * for matching RDF graphs.
 * The serialisation is not complete, only assertions stemming from a given
 * resource are included, multiple references to the same resource are not
 * permitted, and the RDF prolog and epilog are not included.
 * RDF Blob and Date literals are not supported.
 */
function RDFSerializer() {
  this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
                getService(Ci.nsIRDFContainerUtils);
  this.resources = [];
}

RDFSerializer.prototype = {
  INDENT: "  ",      // The indent used for pretty-printing
  resources: null,   // Array of the resources that have been found

  /**
   * Escapes characters from a string that should not appear in XML.
   *
   * @param  aString
   *         The string to be escaped
   * @return a string with all characters invalid in XML character data
   *         converted to entity references.
   */
  escapeEntities(aString) {
    aString = aString.replace(/&/g, "&amp;");
    aString = aString.replace(/</g, "&lt;");
    aString = aString.replace(/>/g, "&gt;");
    return aString.replace(/"/g, "&quot;");
  },

  /**
   * Serializes all the elements of an RDF container.
   *
   * @param  aDs
   *         The RDF datasource
   * @param  aContainer
   *         The RDF container to output the child elements of
   * @param  aIndent
   *         The current level of indent for pretty-printing
   * @return a string containing the serialized elements.
   */
  serializeContainerItems(aDs, aContainer, aIndent) {
    var result = "";
    var items = aContainer.GetElements();
    while (items.hasMoreElements()) {
      var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
      result += aIndent + "<RDF:li>\n"
      result += this.serializeResource(aDs, item, aIndent + this.INDENT);
      result += aIndent + "</RDF:li>\n"
    }
    return result;
  },

  /**
   * Serializes all em:* (see EM_NS) properties of an RDF resource except for
   * the em:signature property. As this serialization is to be compared against
   * the manifest signature it cannot contain the em:signature property itself.
   *
   * @param  aDs
   *         The RDF datasource
   * @param  aResource
   *         The RDF resource that contains the properties to serialize
   * @param  aIndent
   *         The current level of indent for pretty-printing
   * @return a string containing the serialized properties.
   * @throws if the resource contains a property that cannot be serialized
   */
  serializeResourceProperties(aDs, aResource, aIndent) {
    var result = "";
    var items = [];
    var arcs = aDs.ArcLabelsOut(aResource);
    while (arcs.hasMoreElements()) {
      var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
      if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
        continue;
      var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
      if (prop == "signature")
        continue;

      var targets = aDs.GetTargets(aResource, arc, true);
      while (targets.hasMoreElements()) {
        var target = targets.getNext();
        if (target instanceof Ci.nsIRDFResource) {
          var item = aIndent + "<em:" + prop + ">\n";
          item += this.serializeResource(aDs, target, aIndent + this.INDENT);
          item += aIndent + "</em:" + prop + ">\n";
          items.push(item);
        } else if (target instanceof Ci.nsIRDFLiteral) {
          items.push(aIndent + "<em:" + prop + ">" +
                     this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
        } else if (target instanceof Ci.nsIRDFInt) {
          items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" +
                     target.Value + "</em:" + prop + ">\n");
        } else {
          throw Components.Exception("Cannot serialize unknown literal type");
        }
      }
    }
    items.sort();
    result += items.join("");
    return result;
  },

  /**
   * Recursively serializes an RDF resource and all resources it links to.
   * This will only output EM_NS properties and will ignore any em:signature
   * property.
   *
   * @param  aDs
   *         The RDF datasource
   * @param  aResource
   *         The RDF resource to serialize
   * @param  aIndent
   *         The current level of indent for pretty-printing. If undefined no
   *         indent will be added
   * @return a string containing the serialized resource.
   * @throws if the RDF data contains multiple references to the same resource.
   */
  serializeResource(aDs, aResource, aIndent) {
    if (this.resources.indexOf(aResource) != -1 ) {
      // We cannot output multiple references to the same resource.
      throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
    }
    if (aIndent === undefined)
      aIndent = "";

    this.resources.push(aResource);
    var container = null;
    var type = "Description";
    if (this.cUtils.IsSeq(aDs, aResource)) {
      type = "Seq";
      container = this.cUtils.MakeSeq(aDs, aResource);
    } else if (this.cUtils.IsAlt(aDs, aResource)) {
      type = "Alt";
      container = this.cUtils.MakeAlt(aDs, aResource);
    } else if (this.cUtils.IsBag(aDs, aResource)) {
      type = "Bag";
      container = this.cUtils.MakeBag(aDs, aResource);
    }

    var result = aIndent + "<RDF:" + type;
    if (!gRDF.IsAnonymousResource(aResource))
      result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\"";
    result += ">\n";

    if (container)
      result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT);

    result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT);

    result += aIndent + "</RDF:" + type + ">\n";
    return result;
  }
}

/**
 * Sanitizes the update URL in an update item, as returned by
 * parseRDFManifest and parseJSONManifest. Ensures that:
 *
 * - The URL is secure, or secured by a strong enough hash.
 * - The security principal of the update manifest has permission to
 *   load the URL.
 *
 * @param aUpdate
 *        The update item to sanitize.
 * @param aRequest
 *        The XMLHttpRequest used to load the manifest.
 * @param aHashPattern
 *        The regular expression used to validate the update hash.
 * @param aHashString
 *        The human-readable string specifying which hash functions
 *        are accepted.
 */
function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
  if (aUpdate.updateURL) {
    let scriptSecurity = Services.scriptSecurityManager;
    let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel);
    try {
      // This logs an error on failure, so no need to log it a second time
      scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL,
                                                  scriptSecurity.DISALLOW_SCRIPT);
    } catch (e) {
      delete aUpdate.updateURL;
      return;
    }

    if (AddonManager.checkUpdateSecurity &&
        !aUpdate.updateURL.startsWith("https:") &&
        !aHashPattern.test(aUpdate.updateHash)) {
      logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` +
                  `by a strong enough hash (needs to be ${aHashString}).`);
      delete aUpdate.updateURL;
      delete aUpdate.updateHash;
    }
  }
}

/**
 * Parses an RDF style update manifest into an array of update objects.
 *
 * @param  aId
 *         The ID of the add-on being checked for updates
 * @param  aUpdateKey
 *         An optional update key for the add-on
 * @param  aRequest
 *         The XMLHttpRequest that has retrieved the update manifest
 * @param  aManifestData
 *         The pre-parsed manifest, as a bare XML DOM document
 * @return an array of update objects
 * @throws if the update manifest is invalid in any way
 */
function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
  if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) {
    throw Components.Exception("Update manifest had an unrecognised namespace: " +
                               aManifestData.documentElement.namespaceURI);
  }

  function EM_R(aProp) {
    return gRDF.GetResource(PREFIX_NS_EM + aProp);
  }

  function getValue(aLiteral) {
    if (aLiteral instanceof Ci.nsIRDFLiteral)
      return aLiteral.Value;
    if (aLiteral instanceof Ci.nsIRDFResource)
      return aLiteral.Value;
    if (aLiteral instanceof Ci.nsIRDFInt)
      return aLiteral.Value;
    return null;
  }

  function getProperty(aDs, aSource, aProperty) {
    return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true));
  }

  function getBooleanProperty(aDs, aSource, aProperty) {
    let propValue = aDs.GetTarget(aSource, EM_R(aProperty), true);
    if (!propValue)
      return undefined;
    return getValue(propValue) == "true";
  }

  function getRequiredProperty(aDs, aSource, aProperty) {
    let value = getProperty(aDs, aSource, aProperty);
    if (!value)
      throw Components.Exception("Update manifest is missing a required " + aProperty + " property.");
    return value;
  }

  let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
                  createInstance(Ci.nsIRDFXMLParser);
  let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
           createInstance(Ci.nsIRDFDataSource);
  rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);

  // Differentiating between add-on types is deprecated
  let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
  let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
  let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
  let addonRes;
  if (ds.ArcLabelsOut(extensionRes).hasMoreElements())
    addonRes = extensionRes;
  else if (ds.ArcLabelsOut(themeRes).hasMoreElements())
    addonRes = themeRes;
  else
    addonRes = itemRes;

  // If we have an update key then the update manifest must be signed
  if (aUpdateKey) {
    let signature = getProperty(ds, addonRes, "signature");
    if (!signature)
      throw Components.Exception("Update manifest for " + aId + " does not contain a required signature");
    let serializer = new RDFSerializer();
    let updateString = null;

    try {
      updateString = serializer.serializeResource(ds, addonRes);
    } catch (e) {
      throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e,
                                 e.result);
    }

    let result = false;

    try {
      let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
                     getService(Ci.nsIDataSignatureVerifier);
      result = verifier.verifyData(updateString, signature, aUpdateKey);
    } catch (e) {
      throw Components.Exception("The signature or updateKey for " + aId + " is malformed." +
                                 "Verifier threw " + e, e.result);
    }

    if (!result)
      throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
  }

  let updates = ds.GetTarget(addonRes, EM_R("updates"), true);

  // A missing updates property doesn't count as a failure, just as no avialable
  // update information
  if (!updates) {
    logger.warn("Update manifest for " + aId + " did not contain an updates property");
    return [];
  }

  if (!(updates instanceof Ci.nsIRDFResource))
    throw Components.Exception("Missing updates property for " + addonRes.Value);

  let cu = Cc["@mozilla.org/rdf/container-utils;1"].
           getService(Ci.nsIRDFContainerUtils);
  if (!cu.IsContainer(ds, updates))
    throw Components.Exception("Updates property was not an RDF container");

  let results = [];
  let ctr = Cc["@mozilla.org/rdf/container;1"].
            createInstance(Ci.nsIRDFContainer);
  ctr.Init(ds, updates);
  let items = ctr.GetElements();
  while (items.hasMoreElements()) {
    let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
    let version = getProperty(ds, item, "version");
    if (!version) {
      logger.warn("Update manifest is missing a required version property.");
      continue;
    }

    logger.debug("Found an update entry for " + aId + " version " + version);

    let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
    while (targetApps.hasMoreElements()) {
      let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);

      let appEntry = {};
      try {
        appEntry.id = getRequiredProperty(ds, targetApp, "id");
        appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
        appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
      } catch (e) {
        logger.warn(e);
        continue;
      }

      let result = {
        id: aId,
        version,
        multiprocessCompatible: getBooleanProperty(ds, item, "multiprocessCompatible"),
        updateURL: getProperty(ds, targetApp, "updateLink"),
        updateHash: getProperty(ds, targetApp, "updateHash"),
        updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
        strictCompatibility: !!getBooleanProperty(ds, targetApp, "strictCompatibility"),
        targetApplications: [appEntry]
      };

      // The JSON update protocol requires an SHA-2 hash. RDF still
      // supports SHA-1, for compatibility reasons.
      sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger");

      results.push(result);
    }
  }
  return results;
}

/**
 * Parses an JSON update manifest into an array of update objects.
 *
 * @param  aId
 *         The ID of the add-on being checked for updates
 * @param  aUpdateKey
 *         An optional update key for the add-on
 * @param  aRequest
 *         The XMLHttpRequest that has retrieved the update manifest
 * @param  aManifestData
 *         The pre-parsed manifest, as a JSON object tree
 * @return an array of update objects
 * @throws if the update manifest is invalid in any way
 */
function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) {
  if (aUpdateKey)
    throw Components.Exception("Update keys are not supported for JSON update manifests");

  let TYPE_CHECK = {
    "array": val => Array.isArray(val),
    "object": val => val && typeof val == "object" && !Array.isArray(val),
  };

  function getProperty(aObj, aProperty, aType, aDefault = undefined) {
    if (!(aProperty in aObj))
      return aDefault;

    let value = aObj[aProperty];

    let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType;
    if (!matchesType)
      throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`);

    return value;
  }

  function getRequiredProperty(aObj, aProperty, aType) {
    let value = getProperty(aObj, aProperty, aType);
    if (value === undefined)
      throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`);
    return value;
  }

  let manifest = aManifestData;

  if (!TYPE_CHECK["object"](manifest))
    throw Components.Exception("Root element of update manifest must be a JSON object literal");

  // The set of add-ons this manifest has updates for
  let addons = getRequiredProperty(manifest, "addons", "object");

  // The entry for this particular add-on
  let addon = getProperty(addons, aId, "object");

  // A missing entry doesn't count as a failure, just as no avialable update
  // information
  if (!addon) {
    logger.warn("Update manifest did not contain an entry for " + aId);
    return [];
  }

  // The list of available updates
  let updates = getProperty(addon, "updates", "array", []);

  let results = [];

  for (let update of updates) {
    let version = getRequiredProperty(update, "version", "string");

    logger.debug(`Found an update entry for ${aId} version ${version}`);

    let applications = getProperty(update, "applications", "object",
                                   { gecko: {} });

    // "gecko" is currently the only supported application entry. If
    // it's missing, skip this update.
    if (!("gecko" in applications)) {
      logger.debug("gecko not in application entry, skipping update of ${addon}")
      continue;
    }

    let app = getProperty(applications, "gecko", "object");

    let appEntry = {
      id: TOOLKIT_ID,
      minVersion: getProperty(app, "strict_min_version", "string",
                              AddonManagerPrivate.webExtensionsMinPlatformVersion),
      maxVersion: "*",
    };

    let result = {
      id: aId,
      version,
      multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true),
      updateURL: getProperty(update, "update_link", "string"),
      updateHash: getProperty(update, "update_hash", "string"),
      updateInfoURL: getProperty(update, "update_info_url", "string"),
      strictCompatibility: false,
      targetApplications: [appEntry],
    };

    if ("strict_max_version" in app) {
      if ("advisory_max_version" in app) {
        logger.warn("Ignoring 'advisory_max_version' update manifest property for " +
                    aId + " property since 'strict_max_version' also present");
      }

      appEntry.maxVersion = getProperty(app, "strict_max_version", "string");
      result.strictCompatibility = appEntry.maxVersion != "*";
    } else if ("advisory_max_version" in app) {
      appEntry.maxVersion = getProperty(app, "advisory_max_version", "string");
    }

    // The JSON update protocol requires an SHA-2 hash. RDF still
    // supports SHA-1, for compatibility reasons.
    sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512");

    results.push(result);
  }
  return results;
}

/**
 * Starts downloading an update manifest and then passes it to an appropriate
 * parser to convert to an array of update objects
 *
 * @param  aId
 *         The ID of the add-on being checked for updates
 * @param  aUpdateKey
 *         An optional update key for the add-on
 * @param  aUrl
 *         The URL of the update manifest
 * @param  aObserver
 *         An observer to pass results to
 */
function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
  this.id = aId;
  this.updateKey = aUpdateKey;
  this.observer = aObserver;
  this.url = aUrl;

  let requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS, true);

  logger.debug("Requesting " + aUrl);
  try {
    this.request = new ServiceRequest();
    this.request.open("GET", this.url, true);
    this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
    this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    // Prevent the request from writing to cache.
    this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    this.request.overrideMimeType("text/plain");
    this.request.setRequestHeader("Moz-XPI-Update", "1", true);
    this.request.timeout = TIMEOUT;
    this.request.addEventListener("load", () => this.onLoad());
    this.request.addEventListener("error", () => this.onError());
    this.request.addEventListener("timeout", () => this.onTimeout());
    this.request.send(null);
  } catch (e) {
    logger.error("Failed to request update manifest", e);
  }
}

UpdateParser.prototype = {
  id: null,
  updateKey: null,
  observer: null,
  request: null,
  url: null,

  /**
   * Called when the manifest has been successfully loaded.
   */
  onLoad() {
    let request = this.request;
    this.request = null;
    this._doneAt = new Error("place holder");

    let requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS, true);

    try {
      CertUtils.checkCert(request.channel, !requireBuiltIn);
    } catch (e) {
      logger.warn("Request failed: " + this.url + " - " + e);
      this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
      return;
    }

    if (!Components.isSuccessCode(request.status)) {
      logger.warn("Request failed: " + this.url + " - " + request.status);
      this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
      return;
    }

    let channel = request.channel;
    if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
      logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
           ": " + channel.responseStatusText);
      this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
      return;
    }

    // Detect the manifest type by first attempting to parse it as
    // JSON, and falling back to parsing it as XML if that fails.
    let parser;
    try {
      try {
        let json = JSON.parse(request.responseText);

        parser = () => parseJSONManifest(this.id, this.updateKey, request, json);
      } catch (e) {
        if (!(e instanceof SyntaxError))
          throw e;
        let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
        let xml = domParser.parseFromString(request.responseText, "text/xml");

        if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
          throw new Error("Update manifest was not valid XML or JSON");

        parser = () => parseRDFManifest(this.id, this.updateKey, request, xml);
      }
    } catch (e) {
      logger.warn("onUpdateCheckComplete failed to determine manifest type");
      this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
      return;
    }

    let results;
    try {
      results = parser();
    } catch (e) {
      logger.warn("onUpdateCheckComplete failed to parse update manifest", e);
      this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
      return;
    }

    if ("onUpdateCheckComplete" in this.observer) {
      try {
        this.observer.onUpdateCheckComplete(results);
      } catch (e) {
        logger.warn("onUpdateCheckComplete notification failed", e);
      }
    } else {
      logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
    }
  },

  /**
   * Called when the request times out
   */
  onTimeout() {
    this.request = null;
    this._doneAt = new Error("Timed out");
    logger.warn("Request for " + this.url + " timed out");
    this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
  },

  /**
   * Called when the manifest failed to load.
   */
  onError() {
    if (!Components.isSuccessCode(this.request.status)) {
      logger.warn("Request failed: " + this.url + " - " + this.request.status);
    } else if (this.request.channel instanceof Ci.nsIHttpChannel) {
      try {
        if (this.request.channel.requestSucceeded) {
          logger.warn("Request failed: " + this.url + " - " +
               this.request.channel.responseStatus + ": " +
               this.request.channel.responseStatusText);
        }
      } catch (e) {
        logger.warn("HTTP Request failed for an unknown reason");
      }
    } else {
      logger.warn("Request failed for an unknown reason");
    }

    this.request = null;
    this._doneAt = new Error("UP_onError");

    this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
  },

  /**
   * Helper method to notify the observer that an error occured.
   */
  notifyError(aStatus) {
    if ("onUpdateCheckError" in this.observer) {
      try {
        this.observer.onUpdateCheckError(aStatus);
      } catch (e) {
        logger.warn("onUpdateCheckError notification failed", e);
      }
    }
  },

  /**
   * Called to cancel an in-progress update check.
   */
  cancel() {
    if (!this.request) {
      logger.error("Trying to cancel already-complete request", this._doneAt);
      return;
    }
    this.request.abort();
    this.request = null;
    this._doneAt = new Error("UP_cancel");
    this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
  }
};

/**
 * Tests if an update matches a version of the application or platform
 *
 * @param  aUpdate
 *         The available update
 * @param  aAppVersion
 *         The application version to use
 * @param  aPlatformVersion
 *         The platform version to use
 * @param  aIgnoreMaxVersion
 *         Ignore maxVersion when testing if an update matches. Optional.
 * @param  aIgnoreStrictCompat
 *         Ignore strictCompatibility when testing if an update matches. Optional.
 * @param  aCompatOverrides
 *         AddonCompatibilityOverride objects to match against. Optional.
 * @return true if the update is compatible with the application/platform
 */
function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
                         aIgnoreMaxVersion, aIgnoreStrictCompat,
                         aCompatOverrides) {
  if (aCompatOverrides) {
    let override = AddonRepository.findMatchingCompatOverride(aUpdate.version,
                                                              aCompatOverrides,
                                                              aAppVersion,
                                                              aPlatformVersion);
    if (override && override.type == "incompatible")
      return false;
  }

  if (aUpdate.strictCompatibility && !aIgnoreStrictCompat)
    aIgnoreMaxVersion = false;

  let result = false;
  for (let app of aUpdate.targetApplications) {
    if (app.id == Services.appinfo.ID) {
      return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
             (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
    }
    if (app.id == TOOLKIT_ID) {
      result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
               (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
    }
  }
  return result;
}

this.AddonUpdateChecker = {
  // These must be kept in sync with AddonManager
  // The update check timed out
  ERROR_TIMEOUT: -1,
  // There was an error while downloading the update information.
  ERROR_DOWNLOAD_ERROR: -2,
  // The update information was malformed in some way.
  ERROR_PARSE_ERROR: -3,
  // The update information was not in any known format.
  ERROR_UNKNOWN_FORMAT: -4,
  // The update information was not correctly signed or there was an SSL error.
  ERROR_SECURITY_ERROR: -5,
  // The update was cancelled
  ERROR_CANCELLED: -6,

  /**
   * Retrieves the best matching compatibility update for the application from
   * a list of available update objects.
   *
   * @param  aUpdates
   *         An array of update objects
   * @param  aVersion
   *         The version of the add-on to get new compatibility information for
   * @param  aIgnoreCompatibility
   *         An optional parameter to get the first compatibility update that
   *         is compatible with any version of the application or toolkit
   * @param  aAppVersion
   *         The version of the application or null to use the current version
   * @param  aPlatformVersion
   *         The version of the platform or null to use the current version
   * @param  aIgnoreMaxVersion
   *         Ignore maxVersion when testing if an update matches. Optional.
   * @param  aIgnoreStrictCompat
   *         Ignore strictCompatibility when testing if an update matches. Optional.
   * @return an update object if one matches or null if not
   */
  getCompatibilityUpdate(aUpdates, aVersion, aIgnoreCompatibility,
                                   aAppVersion, aPlatformVersion,
                                   aIgnoreMaxVersion, aIgnoreStrictCompat) {
    if (!aAppVersion)
      aAppVersion = Services.appinfo.version;
    if (!aPlatformVersion)
      aPlatformVersion = Services.appinfo.platformVersion;

    for (let update of aUpdates) {
      if (Services.vc.compare(update.version, aVersion) == 0) {
        if (aIgnoreCompatibility) {
          for (let targetApp of update.targetApplications) {
            let id = targetApp.id;
            if (id == Services.appinfo.ID || id == TOOLKIT_ID)
              return update;
          }
        } else if (matchesVersions(update, aAppVersion, aPlatformVersion,
                                 aIgnoreMaxVersion, aIgnoreStrictCompat)) {
          return update;
        }
      }
    }
    return null;
  },

  /**
   * Returns the newest available update from a list of update objects.
   *
   * @param  aUpdates
   *         An array of update objects
   * @param  aAppVersion
   *         The version of the application or null to use the current version
   * @param  aPlatformVersion
   *         The version of the platform or null to use the current version
   * @param  aIgnoreMaxVersion
   *         When determining compatible updates, ignore maxVersion. Optional.
   * @param  aIgnoreStrictCompat
   *         When determining compatible updates, ignore strictCompatibility. Optional.
   * @param  aCompatOverrides
   *         Array of AddonCompatibilityOverride to take into account. Optional.
   * @return an update object if one matches or null if not
   */
  getNewestCompatibleUpdate(aUpdates, aAppVersion, aPlatformVersion,
                                      aIgnoreMaxVersion, aIgnoreStrictCompat,
                                      aCompatOverrides) {
    if (!aAppVersion)
      aAppVersion = Services.appinfo.version;
    if (!aPlatformVersion)
      aPlatformVersion = Services.appinfo.platformVersion;

    let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                    getService(Ci.nsIBlocklistService);

    let newest = null;
    for (let update of aUpdates) {
      if (!update.updateURL)
        continue;
      let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
      if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
        continue;
      if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
          matchesVersions(update, aAppVersion, aPlatformVersion,
                          aIgnoreMaxVersion, aIgnoreStrictCompat,
                          aCompatOverrides)) {
        newest = update;
      }
    }
    return newest;
  },

  /**
   * Starts an update check.
   *
   * @param  aId
   *         The ID of the add-on being checked for updates
   * @param  aUpdateKey
   *         An optional update key for the add-on
   * @param  aUrl
   *         The URL of the add-on's update manifest
   * @param  aObserver
   *         An observer to notify of results
   * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
   *         down in-progress update requests
   */
  checkForUpdates(aId, aUpdateKey, aUrl, aObserver) {
    return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
  }
};
PK
!<ۄJJmodules/addons/Content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals addMessageListener*/

"use strict";

(function() {

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});

const MSG_JAR_FLUSH = "AddonJarFlush";
const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";


try {
  if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
    // Propagate JAR cache flush notifications across process boundaries.
    addMessageListener(MSG_JAR_FLUSH, function(message) {
      Services.obs.notifyObservers(null, "flush-cache-entry", message.data);
    });
    // Propagate message manager caches flush notifications across processes.
    addMessageListener(MSG_MESSAGE_MANAGER_CACHES_FLUSH, function() {
      Services.obs.notifyObservers(null, "message-manager-flush-caches");
    });
  }
} catch (e) {
  Cu.reportError(e);
}

})();
PK
!<P@$modules/addons/E10SAddonsRollout.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = [ "isAddonPartOfE10SRollout" ];

const Cu = Components.utils;
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
const PREF_E10S_ADDON_POLICY    = "extensions.e10s.rollout.policy";

// NOTE: Do not modify policies after they have already been
// published to users. They must remain unchanged to provide valid data.

// We use these named policies to correlate the telemetry
// data with them, in order to understand how each set
// is behaving in the wild.
const RolloutPolicy = {
  // Beta testing on 50
  "50allmpc": { webextensions: true, mpc: true },

  // ESR
  "esrA": { mpc: true, webextensions: true },
  "esrB": { mpc: true, webextensions: false },
  "esrC": { mpc: false, webextensions: true },

  "xpcshell-test": { mpc: true, webextensions: false },
};

Object.defineProperty(this, "isAddonPartOfE10SRollout", {
  configurable: false,
  enumerable: false,
  writable: false,
  value: function isAddonPartOfE10SRollout(aAddon) {
    let blocklist = Preferences.get(PREF_E10S_ADDON_BLOCKLIST, "");
    let policyId = Preferences.get(PREF_E10S_ADDON_POLICY, "");

    if (!policyId || !RolloutPolicy.hasOwnProperty(policyId)) {
      return false;
    }

    if (blocklist && blocklist.indexOf(aAddon.id) > -1) {
      return false;
    }

    let policy = RolloutPolicy[policyId];

    if (aAddon.mpcOptedOut == true) {
      return false;
    }

    if (policy.webextensions && (aAddon.type == "webextension" || aAddon.type == "webextension-theme")) {
      return true;
    }

    if (policy.mpc && aAddon.multiprocessCompatible) {
      return true;
    }

    return false;
  },
});
PK
!<=V[[modules/addons/GMPProvider.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
/* globals OS*/
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/GMPUtils.jsm");
/* globals GMP_PLUGIN_IDS, GMPPrefs, GMPUtils, OPEN_H264_ID, WIDEVINE_ID */
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/UpdateUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(
  this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(
  this, "setTimeout", "resource://gre/modules/Timer.jsm");

const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME       = "type.%ID%.name";

const SEC_IN_A_DAY           = 24 * 60 * 60;
// How long to wait after a user enabled EME before attempting to download CDMs.
const GMP_CHECK_DELAY        = 10 * 1000; // milliseconds

const NS_GRE_DIR             = "GreD";
const CLEARKEY_PLUGIN_ID     = "gmp-clearkey";
const CLEARKEY_VERSION       = "0.1";

const GMP_LICENSE_INFO       = "gmp_license_info";
const GMP_PRIVACY_INFO       = "gmp_privacy_info";
const GMP_LEARN_MORE         = "learn_more_label";

const GMP_PLUGINS = [
  {
    id:              OPEN_H264_ID,
    name:            "openH264_name",
    description:     "openH264_description2",
    // The following licenseURL is part of an awful hack to include the OpenH264
    // license without having bug 624602 fixed yet, and intentionally ignores
    // localisation.
    licenseURL:      "chrome://mozapps/content/extensions/OpenH264-license.txt",
    homepageURL:     "http://www.openh264.org/",
    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
  },
  {
    id:              WIDEVINE_ID,
    name:            "widevine_description",
    // Describe the purpose of both CDMs in the same way.
    description:     "cdm_description",
    licenseURL:      "https://www.google.com/policies/privacy/",
    homepageURL:     "https://www.widevine.com/",
    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
    isEME:           true
  }];
XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS);

XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
  () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
XPCOMUtils.defineLazyGetter(this, "gmpService",
  () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));

var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                       .getService(Ci.nsIMessageListenerManager);

var gLogger;
var gLogAppenderDump = null;

function configureLogging() {
  if (!gLogger) {
    gLogger = Log.repository.getLogger("Toolkit.GMP");
    gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
  }
  gLogger.level = GMPPrefs.getInt(GMPPrefs.KEY_LOGGING_LEVEL, Log.Level.Warn);

  let logDumping = GMPPrefs.getBool(GMPPrefs.KEY_LOGGING_DUMP, false);
  if (logDumping != !!gLogAppenderDump) {
    if (logDumping) {
      gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
      gLogger.addAppender(gLogAppenderDump);
    } else {
      gLogger.removeAppender(gLogAppenderDump);
      gLogAppenderDump = null;
    }
  }
}



/**
 * The GMPWrapper provides the info for the various GMP plugins to public
 * callers through the API.
 */
function GMPWrapper(aPluginInfo) {
  this._plugin = aPluginInfo;
  this._log =
    Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
                                              "GMPWrapper(" +
                                              this._plugin.id + ") ");
  Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
                                          this._plugin.id),
                      this.onPrefEnabledChanged, this);
  Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
                                          this._plugin.id),
                      this.onPrefVersionChanged, this);
  if (this._plugin.isEME) {
    Preferences.observe(GMPPrefs.KEY_EME_ENABLED,
                        this.onPrefEMEGlobalEnabledChanged, this);
    messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", this);
  }
}

GMPWrapper.prototype = {
  // An active task that checks for plugin updates and installs them.
  _updateTask: null,
  _gmpPath: null,
  _isUpdateCheckPending: false,

  optionsType: AddonManager.OPTIONS_TYPE_INLINE,
  get optionsURL() { return this._plugin.optionsURL; },

  set gmpPath(aPath) { this._gmpPath = aPath; },
  get gmpPath() {
    if (!this._gmpPath && this.isInstalled) {
      this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
                                   this._plugin.id,
                                   GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION,
                                                      null, this._plugin.id));
    }
    return this._gmpPath;
  },

  get id() { return this._plugin.id; },
  get type() { return "plugin"; },
  get isGMPlugin() { return true; },
  get name() { return this._plugin.name; },
  get creator() { return null; },
  get homepageURL() { return this._plugin.homepageURL; },

  get description() { return this._plugin.description; },
  get fullDescription() { return this._plugin.fullDescription; },

  get version() {
    return GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id);
  },

  get isActive() {
    return !this.appDisabled &&
           !this.userDisabled &&
           !GMPUtils.isPluginHidden(this._plugin);
  },
  get appDisabled() {
    if (this._plugin.isEME && !GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
      // If "media.eme.enabled" is false, all EME plugins are disabled.
      return true;
    }
    return false;
  },

  get userDisabled() {
    return !GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_ENABLED, true, this._plugin.id);
  },
  set userDisabled(aVal) {
    GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_ENABLED, aVal === false, this._plugin.id);
  },

  get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
  get size() { return 0; },
  get scope() { return AddonManager.SCOPE_APPLICATION; },
  get pendingOperations() { return AddonManager.PENDING_NONE; },

  get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },

  get permissions() {
    let permissions = 0;
    if (!this.appDisabled) {
      permissions |= AddonManager.PERM_CAN_UPGRADE;
      permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
                                         AddonManager.PERM_CAN_DISABLE;
    }
    return permissions;
  },

  get updateDate() {
    let time = Number(GMPPrefs.getInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, 0,
                                      this._plugin.id));
    if (this.isInstalled) {
      return new Date(time * 1000)
    }
    return null;
  },

  get isCompatible() {
    return true;
  },

  get isPlatformCompatible() {
    return true;
  },

  get providesUpdatesSecurely() {
    return true;
  },

  get foreignInstall() {
    return false;
  },

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    return true;
  },

  get applyBackgroundUpdates() {
    if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
      return AddonManager.AUTOUPDATE_DEFAULT;
    }

    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
      AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
  },

  set applyBackgroundUpdates(aVal) {
    if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
      GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
    } else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
      GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
    } else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
      GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
    }
  },

  findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
    this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
                    aReason);

    AddonManagerPrivate.callNoUpdateListeners(this, aListener);

    if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
      if (!AddonManager.shouldAutoUpdate(this)) {
        this._log.trace("findUpdates() - " + this._plugin.id +
                        " - no autoupdate");
        return Promise.resolve(false);
      }

      let secSinceLastCheck =
        Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
      if (secSinceLastCheck <= SEC_IN_A_DAY) {
        this._log.trace("findUpdates() - " + this._plugin.id +
                        " - last check was less then a day ago");
        return Promise.resolve(false);
      }
    } else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
      this._log.trace("findUpdates() - " + this._plugin.id +
                      " - the given reason to update is not supported");
      return Promise.resolve(false);
    }

    if (this._updateTask !== null) {
      this._log.trace("findUpdates() - " + this._plugin.id +
                      " - update task already running");
      return this._updateTask;
    }

    this._updateTask = (async () => {
      this._log.trace("findUpdates() - updateTask");
      try {
        let installManager = new GMPInstallManager();
        let res = await installManager.checkForAddons();
        let update = res.gmpAddons.find(addon => addon.id === this._plugin.id);
        if (update && update.isValid && !update.isInstalled) {
          this._log.trace("findUpdates() - found update for " +
                          this._plugin.id + ", installing");
          await installManager.installAddon(update);
        } else {
          this._log.trace("findUpdates() - no updates for " + this._plugin.id);
        }
        this._log.info("findUpdates() - updateTask succeeded for " +
                       this._plugin.id);
      } catch (e) {
        this._log.error("findUpdates() - updateTask for " + this._plugin.id +
                        " threw", e);
        throw e;
      } finally {
        this._updateTask = null;
      }
      return true;
    })();

    return this._updateTask;
  },

  get pluginMimeTypes() { return []; },
  get pluginLibraries() {
    if (this.isInstalled) {
      let path = this.version;
      return [path];
    }
    return [];
  },
  get pluginFullpath() {
    if (this.isInstalled) {
      let path = OS.Path.join(OS.Constants.Path.profileDir,
                              this._plugin.id,
                              this.version);
      return [path];
    }
    return [];
  },

  get isInstalled() {
    return this.version && this.version.length > 0;
  },

  _handleEnabledChanged() {
    this._log.info("_handleEnabledChanged() id=" +
      this._plugin.id + " isActive=" + this.isActive);

    AddonManagerPrivate.callAddonListeners(this.isActive ?
                                           "onEnabling" : "onDisabling",
                                           this, false);
    if (this._gmpPath) {
      if (this.isActive) {
        this._log.info("onPrefEnabledChanged() - adding gmp directory " +
                       this._gmpPath);
        gmpService.addPluginDirectory(this._gmpPath);
      } else {
        this._log.info("onPrefEnabledChanged() - removing gmp directory " +
                       this._gmpPath);
        gmpService.removePluginDirectory(this._gmpPath);
      }
    }
    AddonManagerPrivate.callAddonListeners(this.isActive ?
                                           "onEnabled" : "onDisabled",
                                           this);
  },

  onPrefEMEGlobalEnabledChanged() {
    this._log.info("onPrefEMEGlobalEnabledChanged() id=" + this._plugin.id +
      " appDisabled=" + this.appDisabled + " isActive=" + this.isActive +
      " hidden=" + GMPUtils.isPluginHidden(this._plugin));

    AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
                                           ["appDisabled"]);
    // If EME or the GMP itself are disabled, uninstall the GMP.
    // Otherwise, check for updates, so we download and install the GMP.
    if (this.appDisabled) {
      this.uninstallPlugin();
    } else if (!GMPUtils.isPluginHidden(this._plugin)) {
      AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
                                               null, false);
      AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
      AddonManagerPrivate.callAddonListeners("onInstalled", this);
      this.checkForUpdates(GMP_CHECK_DELAY);
    }
    if (!this.userDisabled) {
      this._handleEnabledChanged();
    }
  },

  checkForUpdates(delay) {
    if (this._isUpdateCheckPending) {
      return;
    }
    this._isUpdateCheckPending = true;
    GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
    // Delay this in case the user changes his mind and doesn't want to
    // enable EME after all.
    setTimeout(() => {
      if (!this.appDisabled) {
        let gmpInstallManager = new GMPInstallManager();
        // We don't really care about the results, if someone is interested
        // they can check the log.
        gmpInstallManager.simpleCheckAndInstall().catch(() => {});
      }
      this._isUpdateCheckPending = false;
    }, delay);
  },

  receiveMessage({target: browser, data: data}) {
    this._log.trace("receiveMessage() data=" + data);
    let parsedData;
    try {
      parsedData = JSON.parse(data);
    } catch (ex) {
      this._log.error("Malformed EME video message with data: " + data);
      return;
    }
    let {status} = parsedData;
    if (status == "cdm-not-installed") {
      this.checkForUpdates(0);
    }
  },

  onPrefEnabledChanged() {
    if (!this._plugin.isEME || !this.appDisabled) {
      this._handleEnabledChanged();
    }
  },

  onPrefVersionChanged() {
    AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
    if (this._gmpPath) {
      this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
                     this._gmpPath);
      gmpService.removeAndDeletePluginDirectory(this._gmpPath, true /* can defer */);
    }
    AddonManagerPrivate.callAddonListeners("onUninstalled", this);

    AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
                                             null, false);
    AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
    this._gmpPath = null;
    if (this.isInstalled) {
      this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
                                   this._plugin.id,
                                   GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION,
                                                      null, this._plugin.id));
    }
    if (this._gmpPath && this.isActive) {
      this._log.info("onPrefVersionChanged() - registering gmp directory " +
                     this._gmpPath);
      gmpService.addPluginDirectory(this._gmpPath);
    }
    AddonManagerPrivate.callAddonListeners("onInstalled", this);
  },

  uninstallPlugin() {
    AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
    if (this.gmpPath) {
      this._log.info("uninstallPlugin() - unregistering gmp directory " +
                     this.gmpPath);
      gmpService.removeAndDeletePluginDirectory(this.gmpPath);
    }
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_ABI, this.id);
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, this.id);
    AddonManagerPrivate.callAddonListeners("onUninstalled", this);
  },

  shutdown() {
    Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
                                           this._plugin.id),
                       this.onPrefEnabledChanged, this);
    Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
                                           this._plugin.id),
                       this.onPrefVersionChanged, this);
    if (this._plugin.isEME) {
      Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
                         this.onPrefEMEGlobalEnabledChanged, this);
      messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
    }
    return this._updateTask;
  },

  _arePluginFilesOnDisk() {
    let fileExists = function(aGmpPath, aFileName) {
      let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      let path = OS.Path.join(aGmpPath, aFileName);
      f.initWithPath(path);
      return f.exists();
    };

    let id = this._plugin.id.substring(4);
    let libName = AppConstants.DLL_PREFIX + id + AppConstants.DLL_SUFFIX;
    let infoName;
    if (this._plugin.id == WIDEVINE_ID) {
      infoName = "manifest.json";
    } else {
      infoName = id + ".info";
    }

    return fileExists(this.gmpPath, libName) &&
           fileExists(this.gmpPath, infoName);
  },

  validate() {
    if (!this.isInstalled) {
      // Not installed -> Valid.
      return {
        installed: false,
        valid: true
      };
    }

    let abi = GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_ABI, UpdateUtils.ABI,
                                 this._plugin.id);
    if (abi != UpdateUtils.ABI) {
      // ABI doesn't match. Possibly this is a profile migrated across platforms
      // or from 32 -> 64 bit.
      return {
        installed: true,
        mismatchedABI: true,
        valid: false
      };
    }

    // Installed -> Check if files are missing.
    let filesOnDisk = this._arePluginFilesOnDisk();
    return {
      installed: true,
      valid: filesOnDisk
    };
  },
};

var GMPProvider = {
  get name() { return "GMPProvider"; },

  _plugins: null,

  startup() {
    configureLogging();
    this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
                                                          "GMPProvider.");
    this.buildPluginList();
    this.ensureProperCDMInstallState();

    Services.prefs.addObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);

    for (let plugin of this._plugins.values()) {
      let wrapper = plugin.wrapper;
      let gmpPath = wrapper.gmpPath;
      let isEnabled = wrapper.isActive;
      this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
                      gmpPath);

      if (gmpPath && isEnabled) {
        let validation = wrapper.validate();
        if (validation.mismatchedABI) {
          this._log.info("startup - gmp " + plugin.id +
                         " mismatched ABI, uninstalling");
          wrapper.uninstallPlugin();
          continue;
        }
        if (!validation.valid) {
          this._log.info("startup - gmp " + plugin.id +
                         " invalid, uninstalling");
          wrapper.uninstallPlugin();
          continue;
        }
        this._log.info("startup - adding gmp directory " + gmpPath);
        try {
          gmpService.addPluginDirectory(gmpPath);
        } catch (e) {
          if (e.name != "NS_ERROR_NOT_AVAILABLE")
            throw e;
          this._log.warn("startup - adding gmp directory failed with " +
                         e.name + " - sandboxing not available?", e);
        }
      }
    }

    try {
      let greDir = Services.dirsvc.get(NS_GRE_DIR,
                                       Ci.nsILocalFile);
      let clearkeyPath = OS.Path.join(greDir.path,
                                      CLEARKEY_PLUGIN_ID,
                                      CLEARKEY_VERSION);
      this._log.info("startup - adding clearkey CDM directory " +
                     clearkeyPath);
      gmpService.addPluginDirectory(clearkeyPath);
    } catch (e) {
      this._log.warn("startup - adding clearkey CDM failed", e);
    }
  },

  shutdown() {
    this._log.trace("shutdown");
    Services.prefs.removeObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);

    let shutdownTask = (async () => {
      this._log.trace("shutdown - shutdownTask");
      let shutdownSucceeded = true;

      for (let plugin of this._plugins.values()) {
        try {
          await plugin.wrapper.shutdown();
        } catch (e) {
          shutdownSucceeded = false;
        }
      }

      this._plugins = null;

      if (!shutdownSucceeded) {
        throw new Error("Shutdown failed");
      }
    })();

    return shutdownTask;
  },

  getAddonByID(aId, aCallback) {
    if (!this.isEnabled) {
      aCallback(null);
      return;
    }

    let plugin = this._plugins.get(aId);
    if (plugin && !GMPUtils.isPluginHidden(plugin)) {
      aCallback(plugin.wrapper);
    } else {
      aCallback(null);
    }
  },

  getAddonsByTypes(aTypes, aCallback) {
    if (!this.isEnabled ||
        (aTypes && aTypes.indexOf("plugin") < 0)) {
      aCallback([]);
      return;
    }

    let results = Array.from(this._plugins.values())
      .filter(p => !GMPUtils.isPluginHidden(p))
      .map(p => p.wrapper);

    aCallback(results);
  },

  get isEnabled() {
    return GMPPrefs.getBool(GMPPrefs.KEY_PROVIDER_ENABLED, false);
  },

  generateFullDescription(aPlugin) {
    let rv = [];
    for (let [urlProp, labelId] of [["learnMoreURL", GMP_LEARN_MORE],
                                    ["licenseURL", aPlugin.id == WIDEVINE_ID ?
                                     GMP_PRIVACY_INFO : GMP_LICENSE_INFO]]) {
      if (aPlugin[urlProp]) {
        let label = pluginsBundle.GetStringFromName(labelId);
        rv.push(`<xhtml:a href="${aPlugin[urlProp]}" target="_blank">${label}</xhtml:a>.`);
      }
    }
    return rv.length ? rv.join("<xhtml:br /><xhtml:br />") : undefined;
  },

  buildPluginList() {
    this._plugins = new Map();
    for (let aPlugin of GMP_PLUGINS) {
      let plugin = {
        id: aPlugin.id,
        name: pluginsBundle.GetStringFromName(aPlugin.name),
        description: pluginsBundle.GetStringFromName(aPlugin.description),
        homepageURL: aPlugin.homepageURL,
        optionsURL: aPlugin.optionsURL,
        wrapper: null,
        isEME: aPlugin.isEME,
      };
      plugin.fullDescription = this.generateFullDescription(aPlugin);
      plugin.wrapper = new GMPWrapper(plugin);
      this._plugins.set(plugin.id, plugin);
    }
  },

  ensureProperCDMInstallState() {
    if (!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
      for (let plugin of this._plugins.values()) {
        if (plugin.isEME && plugin.wrapper.isInstalled) {
          gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
          plugin.wrapper.uninstallPlugin();
        }
      }
    }
  },
};

AddonManagerPrivate.registerProvider(GMPProvider, [
  new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 6000,
                                    AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);
PK
!<}1modules/addons/LightweightThemeImageOptimizer.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["LightweightThemeImageOptimizer"];

const Cu = Components.utils;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
  "resource://gre/modules/FileUtils.jsm");

const ORIGIN_TOP_RIGHT = 1;
const ORIGIN_BOTTOM_LEFT = 2;

this.LightweightThemeImageOptimizer = {
  optimize(aThemeData, aScreen) {
    let data = Object.assign({}, aThemeData);
    if (!data.headerURL) {
      return data;
    }

    data.headerURL = ImageCropper.getCroppedImageURL(
      data.headerURL, aScreen, ORIGIN_TOP_RIGHT);

    if (data.footerURL) {
      data.footerURL = ImageCropper.getCroppedImageURL(
        data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT);
    }

    return data;
  },

  purge() {
    let dir = FileUtils.getDir("ProfD", ["lwtheme"]);
    dir.followLinks = false;
    try {
      dir.remove(true);
    } catch (e) {}
  }
};

Object.freeze(LightweightThemeImageOptimizer);

var ImageCropper = {
  _inProgress: {},

  getCroppedImageURL(aImageURL, aScreen, aOrigin) {
    // We can crop local files, only.
    if (!aImageURL.startsWith("file://")) {
      return aImageURL;
    }

    // Generate the cropped image's file name using its
    // base name and the current screen size.
    let uri = Services.io.newURI(aImageURL);
    let file = uri.QueryInterface(Ci.nsIFileURL).file;

    // Make sure the source file exists.
    if (!file.exists()) {
      return aImageURL;
    }

    let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height;
    let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]);

    // If we have a local file that is not in progress, return it.
    if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) {
      let fileURI = Services.io.newFileURI(croppedFile);

      // Copy the query part to avoid wrong caching.
      fileURI.QueryInterface(Ci.nsIURL).query = uri.query;
      return fileURI.spec;
    }

    // Crop the given image in the background.
    this._crop(uri, croppedFile, aScreen, aOrigin);

    // Return the original image while we're waiting for the cropped version
    // to be written to disk.
    return aImageURL;
  },

  _crop(aURI, aTargetFile, aScreen, aOrigin) {
    let inProgress = this._inProgress;
    inProgress[aTargetFile.path] = true;

    function resetInProgress() {
      delete inProgress[aTargetFile.path];
    }

    ImageFile.read(aURI, function(aInputStream, aContentType) {
      if (aInputStream && aContentType) {
        let image = ImageTools.decode(aInputStream, aContentType);
        if (image && image.width && image.height) {
          let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType);
          if (stream) {
            ImageFile.write(aTargetFile, stream, resetInProgress);
            return;
          }
        }
      }

      resetInProgress();
    });
  }
};

var ImageFile = {
  read(aURI, aCallback) {
    this._netUtil.asyncFetch({
      uri: aURI,
      loadUsingSystemPrincipal: true,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
    }, function(aInputStream, aStatus, aRequest) {
        if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) {
          let channel = aRequest.QueryInterface(Ci.nsIChannel);
          aCallback(aInputStream, channel.contentType);
        } else {
          aCallback();
        }
      });
  },

  write(aFile, aInputStream, aCallback) {
    let fos = FileUtils.openSafeFileOutputStream(aFile);
    this._netUtil.asyncCopy(aInputStream, fos, function(aResult) {
      FileUtils.closeSafeFileOutputStream(fos);

      // Remove the file if writing was not successful.
      if (!Components.isSuccessCode(aResult)) {
        try {
          aFile.remove(false);
        } catch (e) {}
      }

      aCallback();
    });
  }
};

XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil",
  "resource://gre/modules/NetUtil.jsm", "NetUtil");

var ImageTools = {
  decode(aInputStream, aContentType) {
    let outParam = {value: null};

    try {
      this._imgTools.decodeImageData(aInputStream, aContentType, outParam);
    } catch (e) {}

    return outParam.value;
  },

  encode(aImage, aScreen, aOrigin, aContentType) {
    let stream;
    let width = Math.min(aImage.width, aScreen.width);
    let height = Math.min(aImage.height, aScreen.height);
    let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0;

    try {
      stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0,
                                                 width, height);
    } catch (e) {}

    return stream;
  }
};

XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools",
  "@mozilla.org/image/tools;1", "imgITools");

PK
!<HAA!modules/addons/PluginProvider.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 logger */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [];

Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/Services.jsm");

const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME       = "type.%ID%.name";
const LIST_UPDATED_TOPIC     = "plugins-list-updated";
const FLASH_MIME_TYPE        = "application/x-shockwave-flash";

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.plugins";

// Create a new logger for use by the Addons Plugin Provider
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

var PluginProvider = {
  get name() {
    return "PluginProvider";
  },

  // A dictionary mapping IDs to names and descriptions
  plugins: null,

  startup() {
    Services.obs.addObserver(this, LIST_UPDATED_TOPIC);
    Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
  },

  /**
   * Called when the application is shutting down. Only necessary for tests
   * to be able to simulate a shutdown.
   */
  shutdown() {
    this.plugins = null;
    Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
    Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
  },

  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
    case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
      this.getAddonByID(aData, function(plugin) {
        if (!plugin)
          return;

        let libLabel = aSubject.getElementById("pluginLibraries");
        libLabel.textContent = plugin.pluginLibraries.join(", ");

        let typeLabel = aSubject.getElementById("pluginMimeTypes"), types = [];
        for (let type of plugin.pluginMimeTypes) {
          let extras = [type.description.trim(), type.suffixes].
                       filter(x => x).join(": ");
          types.push(type.type + (extras ? " (" + extras + ")" : ""));
        }
        typeLabel.textContent = types.join(",\n");
        let showProtectedModePref = canDisableFlashProtectedMode(plugin);
        aSubject.getElementById("pluginEnableProtectedMode")
          .setAttribute("collapsed", showProtectedModePref ? "" : "true");
      });
      break;
    case LIST_UPDATED_TOPIC:
      if (this.plugins)
        this.updatePluginList();
      break;
    }
  },

  /**
   * Creates a PluginWrapper for a plugin object.
   */
  buildWrapper(aPlugin) {
    return new PluginWrapper(aPlugin.id,
                             aPlugin.name,
                             aPlugin.description,
                             aPlugin.tags);
  },

  /**
   * Called to get an Addon with a particular ID.
   *
   * @param  aId
   *         The ID of the add-on to retrieve
   * @param  aCallback
   *         A callback to pass the Addon to
   */
  getAddonByID(aId, aCallback) {
    if (!this.plugins)
      this.buildPluginList();

    if (aId in this.plugins)
      aCallback(this.buildWrapper(this.plugins[aId]));
    else
      aCallback(null);
  },

  /**
   * Called to get Addons of a particular type.
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types.
   * @param  callback
   *         A callback to pass an array of Addons to
   */
  getAddonsByTypes(aTypes, aCallback) {
    if (aTypes && aTypes.indexOf("plugin") < 0) {
      aCallback([]);
      return;
    }

    if (!this.plugins)
      this.buildPluginList();

    let results = [];

    for (let id in this.plugins)
      this.getAddonByID(id, (addon) => results.push(addon));

    aCallback(results);
  },

  /**
   * Called to get Addons that have pending operations.
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types
   * @param  aCallback
   *         A callback to pass an array of Addons to
   */
  getAddonsWithOperationsByTypes(aTypes, aCallback) {
    aCallback([]);
  },

  /**
   * Called to get the current AddonInstalls, optionally restricting by type.
   *
   * @param  aTypes
   *         An array of types or null to get all types
   * @param  aCallback
   *         A callback to pass the array of AddonInstalls to
   */
  getInstallsByTypes(aTypes, aCallback) {
    aCallback([]);
  },

  /**
   * Builds a list of the current plugins reported by the plugin host
   *
   * @return a dictionary of plugins indexed by our generated ID
   */
  getPluginList() {
    let tags = Cc["@mozilla.org/plugin/host;1"].
               getService(Ci.nsIPluginHost).
               getPluginTags({});

    let list = {};
    let seenPlugins = {};
    for (let tag of tags) {
      if (!(tag.name in seenPlugins))
        seenPlugins[tag.name] = {};
      if (!(tag.description in seenPlugins[tag.name])) {
        let plugin = {
          id: tag.name + tag.description,
          name: tag.name,
          description: tag.description,
          tags: [tag]
        };

        seenPlugins[tag.name][tag.description] = plugin;
        list[plugin.id] = plugin;
      } else {
        seenPlugins[tag.name][tag.description].tags.push(tag);
      }
    }

    return list;
  },

  /**
   * Builds the list of known plugins from the plugin host
   */
  buildPluginList() {
    this.plugins = this.getPluginList();
  },

  /**
   * Updates the plugins from the plugin host by comparing the current plugins
   * to the last known list sending out any necessary API notifications for
   * changes.
   */
  updatePluginList() {
    let newList = this.getPluginList();

    let lostPlugins = Object.keys(this.plugins).filter(id => !(id in newList)).
                      map(id => this.buildWrapper(this.plugins[id]));
    let newPlugins = Object.keys(newList).filter(id => !(id in this.plugins)).
                     map(id => this.buildWrapper(newList[id]));
    let matchedIDs = Object.keys(newList).filter(id => id in this.plugins);

    // The plugin host generates new tags for every plugin after a scan and
    // if the plugin's filename has changed then the disabled state won't have
    // been carried across, send out notifications for anything that has
    // changed (see bug 830267).
    let changedWrappers = [];
    for (let id of matchedIDs) {
      let oldWrapper = this.buildWrapper(this.plugins[id]);
      let newWrapper = this.buildWrapper(newList[id]);

      if (newWrapper.isActive != oldWrapper.isActive) {
        AddonManagerPrivate.callAddonListeners(newWrapper.isActive ?
                                               "onEnabling" : "onDisabling",
                                               newWrapper, false);
        changedWrappers.push(newWrapper);
      }
    }

    // Notify about new installs
    for (let plugin of newPlugins) {
      AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
                                               plugin, null, false);
      AddonManagerPrivate.callAddonListeners("onInstalling", plugin, false);
    }

    // Notify for any plugins that have vanished.
    for (let plugin of lostPlugins)
      AddonManagerPrivate.callAddonListeners("onUninstalling", plugin, false);

    this.plugins = newList;

    // Signal that new installs are complete
    for (let plugin of newPlugins)
      AddonManagerPrivate.callAddonListeners("onInstalled", plugin);

    // Signal that enables/disables are complete
    for (let wrapper of changedWrappers) {
      AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
                                             "onEnabled" : "onDisabled",
                                             wrapper);
    }

    // Signal that uninstalls are complete
    for (let plugin of lostPlugins)
      AddonManagerPrivate.callAddonListeners("onUninstalled", plugin);
  }
};

function isFlashPlugin(aPlugin) {
  for (let type of aPlugin.pluginMimeTypes) {
    if (type.type == FLASH_MIME_TYPE) {
      return true;
    }
  }
  return false;
}
// Protected mode is win32-only, not win64
function canDisableFlashProtectedMode(aPlugin) {
  return isFlashPlugin(aPlugin) && Services.appinfo.XPCOMABI == "x86-msvc";
}

const wrapperMap = new WeakMap();
let pluginFor = wrapper => wrapperMap.get(wrapper);

/**
 * The PluginWrapper wraps a set of nsIPluginTags to provide the data visible to
 * public callers through the API.
 */
function PluginWrapper(id, name, description, tags) {
  wrapperMap.set(this, { id, name, description, tags });
}

PluginWrapper.prototype = {
  get id() {
    return pluginFor(this).id;
  },

  get type() {
    return "plugin";
  },

  get name() {
    return pluginFor(this).name;
  },

  get creator() {
    return null;
  },

  get description() {
    return pluginFor(this).description.replace(/<\/?[a-z][^>]*>/gi, " ");
  },

  get version() {
    let { tags: [tag] } = pluginFor(this);
    return tag.version;
  },

  get homepageURL() {
    let { description } = pluginFor(this);
    if (/<A\s+HREF=[^>]*>/i.test(description))
      return /<A\s+HREF=["']?([^>"'\s]*)/i.exec(description)[1];
    return null;
  },

  get isActive() {
    let { tags: [tag] } = pluginFor(this);
    return !tag.blocklisted && !tag.disabled;
  },

  get appDisabled() {
    let { tags: [tag] } = pluginFor(this);
    return tag.blocklisted;
  },

  get userDisabled() {
    let { tags: [tag] } = pluginFor(this);
    if (tag.disabled)
      return true;

    if ((Services.prefs.getBoolPref("plugins.click_to_play") && tag.clicktoplay) ||
        this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
        this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
      return AddonManager.STATE_ASK_TO_ACTIVATE;

    return false;
  },

  set userDisabled(val) {
    let previousVal = this.userDisabled;
    if (val === previousVal)
      return val;

    let { tags } = pluginFor(this);

    for (let tag of tags) {
      if (val === true)
        tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
      else if (val === false)
        tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
      else if (val == AddonManager.STATE_ASK_TO_ACTIVATE)
        tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
    }

    // If 'userDisabled' was 'true' and we're going to a state that's not
    // that, we're enabling, so call those listeners.
    if (previousVal === true && val !== true) {
      AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
      AddonManagerPrivate.callAddonListeners("onEnabled", this);
    }

    // If 'userDisabled' was not 'true' and we're going to a state where
    // it is, we're disabling, so call those listeners.
    if (previousVal !== true && val === true) {
      AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
      AddonManagerPrivate.callAddonListeners("onDisabled", this);
    }

    // If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
    // call the onPropertyChanged listeners.
    if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
        val == AddonManager.STATE_ASK_TO_ACTIVATE) {
      AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
    }

    return val;
  },

  get blocklistState() {
    let { tags: [tag] } = pluginFor(this);
    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
             getService(Ci.nsIBlocklistService);
    return bs.getPluginBlocklistState(tag);
  },

  get blocklistURL() {
    let { tags: [tag] } = pluginFor(this);
    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
             getService(Ci.nsIBlocklistService);
    return bs.getPluginBlocklistURL(tag);
  },

  get size() {
    function getDirectorySize(aFile) {
      let size = 0;
      let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
      let entry;
      while ((entry = entries.nextFile)) {
        if (entry.isSymlink() || !entry.isDirectory())
          size += entry.fileSize;
        else
          size += getDirectorySize(entry);
      }
      entries.close();
      return size;
    }

    let size = 0;
    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    for (let tag of pluginFor(this).tags) {
      file.initWithPath(tag.fullpath);
      if (file.isDirectory())
        size += getDirectorySize(file);
      else
        size += file.fileSize;
    }
    return size;
  },

  get pluginLibraries() {
    let libs = [];
    for (let tag of pluginFor(this).tags)
      libs.push(tag.filename);
    return libs;
  },

  get pluginFullpath() {
    let paths = [];
    for (let tag of pluginFor(this).tags)
      paths.push(tag.fullpath);
    return paths;
  },

  get pluginMimeTypes() {
    let types = [];
    for (let tag of pluginFor(this).tags) {
      let mimeTypes = tag.getMimeTypes({});
      let mimeDescriptions = tag.getMimeDescriptions({});
      let extensions = tag.getExtensions({});
      for (let i = 0; i < mimeTypes.length; i++) {
        let type = {};
        type.type = mimeTypes[i];
        type.description = mimeDescriptions[i];
        type.suffixes = extensions[i];

        types.push(type);
      }
    }
    return types;
  },

  get installDate() {
    let date = 0;
    for (let tag of pluginFor(this).tags) {
      date = Math.max(date, tag.lastModifiedTime);
    }
    return new Date(date);
  },

  get scope() {
    let { tags: [tag] } = pluginFor(this);
    let path = tag.fullpath;
    // Plugins inside the application directory are in the application scope
    let dir = Services.dirsvc.get("APlugns", Ci.nsIFile);
    if (path.startsWith(dir.path))
      return AddonManager.SCOPE_APPLICATION;

    // Plugins inside the profile directory are in the profile scope
    dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    if (path.startsWith(dir.path))
      return AddonManager.SCOPE_PROFILE;

    // Plugins anywhere else in the user's home are in the user scope,
    // but not all platforms have a home directory.
    try {
      dir = Services.dirsvc.get("Home", Ci.nsIFile);
      if (path.startsWith(dir.path))
        return AddonManager.SCOPE_USER;
    } catch (e) {
      if (!e.result || e.result != Components.results.NS_ERROR_FAILURE)
        throw e;
      // Do nothing: missing "Home".
    }

    // Any other locations are system scope
    return AddonManager.SCOPE_SYSTEM;
  },

  get pendingOperations() {
    return AddonManager.PENDING_NONE;
  },

  get operationsRequiringRestart() {
    return AddonManager.OP_NEEDS_RESTART_NONE;
  },

  get permissions() {
    let { tags: [tag] } = pluginFor(this);
    let permissions = 0;
    if (tag.isEnabledStateLocked) {
      return permissions;
    }
    if (!this.appDisabled) {

      if (this.userDisabled !== true)
        permissions |= AddonManager.PERM_CAN_DISABLE;

      let blocklistState = this.blocklistState;
      let isCTPBlocklisted =
        (blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
         blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);

      if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
          (Services.prefs.getBoolPref("plugins.click_to_play") ||
           isCTPBlocklisted)) {
        permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
      }

      if (this.userDisabled !== false && !isCTPBlocklisted) {
        permissions |= AddonManager.PERM_CAN_ENABLE;
      }
    }
    return permissions;
  },

  get optionsType() {
    return AddonManager.OPTIONS_TYPE_INLINE;
  },

  get optionsURL() {
    return "chrome://mozapps/content/extensions/pluginPrefs.xul";
  },

  get updateDate() {
    return this.installDate;
  },

  get isCompatible() {
    return true;
  },

  get isPlatformCompatible() {
    return true;
  },

  get providesUpdatesSecurely() {
    return true;
  },

  get foreignInstall() {
    return true;
  },

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    return true;
  },

  findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
    if ("onNoCompatibilityUpdateAvailable" in aListener)
      aListener.onNoCompatibilityUpdateAvailable(this);
    if ("onNoUpdateAvailable" in aListener)
      aListener.onNoUpdateAvailable(this);
    if ("onUpdateFinished" in aListener)
      aListener.onUpdateFinished(this);
  }
};

AddonManagerPrivate.registerProvider(PluginProvider, [
  new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 6000,
                                    AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);
PK
!<99&modules/addons/ProductAddonChecker.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 ProductAddonChecker */

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

const LOCAL_EME_SOURCES = [{
  "id": "gmp-gmpopenh264",
  "src": "chrome://global/content/gmp-sources/openh264.json"
}, {
  "id": "gmp-widevinecdm",
  "src": "chrome://global/content/gmp-sources/widevinecdm.json"
}];

this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];

Cu.importGlobalProperties(["XMLHttpRequest"]);

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/CertUtils.jsm");
/* globals checkCert, BadCertHandler*/
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");

/* globals GMPPrefs */
XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
                                  "resource://gre/modules/GMPUtils.jsm");

/* globals OS */

XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
                                  "resource://gre/modules/ServiceRequest.jsm");

// This exists so that tests can override the XHR behaviour for downloading
// the addon update XML file.
var CreateXHR = function() {
  return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
    createInstance(Ci.nsISupports);
}

var logger = Log.repository.getLogger("addons.productaddons");

/**
 * Number of milliseconds after which we need to cancel `downloadXML`.
 *
 * Bug 1087674 suggests that the XHR we use in `downloadXML` may
 * never terminate in presence of network nuisances (e.g. strange
 * antivirus behavior). This timeout is a defensive measure to ensure
 * that we fail cleanly in such case.
 */
const TIMEOUT_DELAY_MS = 20000;
// How much of a file to read into memory at a time for hashing
const HASH_CHUNK_SIZE = 8192;

/**
 * Gets the status of an XMLHttpRequest either directly or from its underlying
 * channel.
 *
 * @param  request
 *         The XMLHttpRequest.
 * @return an integer status value.
 */
function getRequestStatus(request) {
  let status = null;
  try {
    status = request.status;
  } catch (e) {
  }

  if (status != null) {
    return status;
  }

  return request.channel.QueryInterface(Ci.nsIRequest).status;
}

/**
 * Downloads an XML document from a URL optionally testing the SSL certificate
 * for certain attributes.
 *
 * @param  url
 *         The url to download from.
 * @param  allowNonBuiltIn
 *         Whether to trust SSL certificates without a built-in CA issuer.
 * @param  allowedCerts
 *         The list of certificate attributes to match the SSL certificate
 *         against or null to skip checks.
 * @return a promise that resolves to the DOM document downloaded or rejects
 *         with a JS exception in case of error.
 */
function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
  return new Promise((resolve, reject) => {
    let request = CreateXHR();
    // This is here to let unit test code override XHR
    if (request.wrappedJSObject) {
      request = request.wrappedJSObject;
    }
    request.open("GET", url, true);
    request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
    // Prevent the request from reading from the cache.
    request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    // Prevent the request from writing to the cache.
    request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    // Use conservative TLS settings. See bug 1325501.
    // TODO move to ServiceRequest.
    if (request.channel instanceof Ci.nsIHttpChannelInternal) {
      request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
    }
    request.timeout = TIMEOUT_DELAY_MS;

    request.overrideMimeType("text/xml");
    // The Cache-Control header is only interpreted by proxies and the
    // final destination. It does not help if a resource is already
    // cached locally.
    request.setRequestHeader("Cache-Control", "no-cache");
    // HTTP/1.0 servers might not implement Cache-Control and
    // might only implement Pragma: no-cache
    request.setRequestHeader("Pragma", "no-cache");

    let fail = (event) => {
      let request = event.target;
      let status = getRequestStatus(request);
      let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
      logger.warn(message);
      let ex = new Error(message);
      ex.status = status;
      reject(ex);
    };

    let success = (event) => {
      logger.info("Completed downloading document");
      let request = event.target;

      try {
        checkCert(request.channel, allowNonBuiltIn, allowedCerts);
      } catch (ex) {
        logger.error("Request failed certificate checks: " + ex);
        ex.status = getRequestStatus(request);
        reject(ex);
        return;
      }

      resolve(request.responseXML);
    };

    request.addEventListener("error", fail);
    request.addEventListener("abort", fail);
    request.addEventListener("timeout", fail);
    request.addEventListener("load", success);

    logger.info("sending request to: " + url);
    request.send(null);
  });
}

function downloadJSON(uri) {
  logger.info("fetching config from: " + uri);
  return new Promise((resolve, reject) => {
    let xmlHttp = new ServiceRequest({mozAnon: true});

    xmlHttp.onload = function(aResponse) {
      resolve(JSON.parse(this.responseText));
    };

    xmlHttp.onerror = function(e) {
      reject("Fetching " + uri + " results in error code: " + e.target.status);
    };

    xmlHttp.open("GET", uri);
    xmlHttp.overrideMimeType("application/json");
    xmlHttp.send();
  });
}


/**
 * Parses a list of add-ons from a DOM document.
 *
 * @param  document
 *         The DOM document to parse.
 * @return null if there is no <addons> element otherwise an object containing
 *         an array of the addons listed and a field notifying whether the
 *         fallback was used.
 */
function parseXML(document) {
  // Check that the root element is correct
  if (document.documentElement.localName != "updates") {
    throw new Error("got node name: " + document.documentElement.localName +
                    ", expected: updates");
  }

  // Check if there are any addons elements in the updates element
  let addons = document.querySelector("updates:root > addons");
  if (!addons) {
    return null;
  }

  let results = [];
  let addonList = document.querySelectorAll("updates:root > addons > addon");
  for (let addonElement of addonList) {
    let addon = {};

    for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
      if (addonElement.hasAttribute(name)) {
        addon[name] = addonElement.getAttribute(name);
      }
    }
    addon.size = Number(addon.size) || undefined;

    results.push(addon);
  }

  return {
    usedFallback: false,
    gmpAddons: results
  };
}

/**
 * If downloading from the network fails (AUS server is down),
 * load the sources from local build configuration.
 */
function downloadLocalConfig() {

  if (!GMPPrefs.getBool(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
    logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
    return Promise.resolve({usedFallback: true, gmpAddons: []});
  }

  return Promise.all(LOCAL_EME_SOURCES.map(conf => {
    return downloadJSON(conf.src).then(addons => {

      let platforms = addons.vendors[conf.id].platforms;
      let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
      let details = null;

      while (!details) {
        if (!(target in platforms)) {
          // There was no matching platform so return false, this addon
          // will be filtered from the results below
          logger.info("no details found for: " + target);
          return false;
        }
        // Field either has the details of the binary or is an alias
        // to another build target key that does
        if (platforms[target].alias) {
          target = platforms[target].alias;
        } else {
          details = platforms[target];
        }
      }

      logger.info("found plugin: " + conf.id);
      return {
        "id": conf.id,
        "URL": details.fileUrl,
        "hashFunction": addons.hashFunction,
        "hashValue": details.hashValue,
        "version": addons.vendors[conf.id].version,
        "size": details.filesize
      };
    });
  })).then(addons => {

    // Some filters may not match this platform so
    // filter those out
    addons = addons.filter(x => x !== false);

    return {
      usedFallback: true,
      gmpAddons: addons
    };
  });
}

/**
 * Downloads file from a URL using XHR.
 *
 * @param  url
 *         The url to download from.
 * @return a promise that resolves to the path of a temporary file or rejects
 *         with a JS exception in case of error.
 */
function downloadFile(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.onload = function(response) {
      logger.info("downloadXHR File download. status=" + xhr.status);
      if (xhr.status != 200 && xhr.status != 206) {
        reject(Components.Exception("File download failed", xhr.status));
        return;
      }
      (async function() {
        let f = await OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"));
        let path = f.path;
        logger.info(`Downloaded file will be saved to ${path}`);
        await f.file.close();
        await OS.File.writeAtomic(path, new Uint8Array(xhr.response));
        return path;
      })().then(resolve, reject);
    };

    let fail = (event) => {
      let request = event.target;
      let status = getRequestStatus(request);
      let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type;
      logger.warn(message);
      let ex = new Error(message);
      ex.status = status;
      reject(ex);
    };
    xhr.addEventListener("error", fail);
    xhr.addEventListener("abort", fail);

    xhr.responseType = "arraybuffer";
    try {
      xhr.open("GET", url);
      // Use conservative TLS settings. See bug 1325501.
      // TODO move to ServiceRequest.
      if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
        xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
      }
      xhr.send(null);
    } catch (ex) {
      reject(ex);
    }
  });
}

/**
 * Convert a string containing binary values to hex.
 */
function binaryToHex(input) {
  let result = "";
  for (let i = 0; i < input.length; ++i) {
    let hex = input.charCodeAt(i).toString(16);
    if (hex.length == 1) {
      hex = "0" + hex;
    }
    result += hex;
  }
  return result;
}

/**
 * Calculates the hash of a file.
 *
 * @param  hashFunction
 *         The type of hash function to use, must be supported by nsICryptoHash.
 * @param  path
 *         The path of the file to hash.
 * @return a promise that resolves to hash of the file or rejects with a JS
 *         exception in case of error.
 */
var computeHash = async function(hashFunction, path) {
  let file = await OS.File.open(path, { existing: true, read: true });
  try {
    let hasher = Cc["@mozilla.org/security/hash;1"].
                 createInstance(Ci.nsICryptoHash);
    hasher.initWithString(hashFunction);

    let bytes;
    do {
      bytes = await file.read(HASH_CHUNK_SIZE);
      hasher.update(bytes, bytes.length);
    } while (bytes.length == HASH_CHUNK_SIZE);

    return binaryToHex(hasher.finish(false));
  } finally {
    await file.close();
  }
};

/**
 * Verifies that a downloaded file matches what was expected.
 *
 * @param  properties
 *         The properties to check, `size` and `hashFunction` with `hashValue`
 *         are supported. Any properties missing won't be checked.
 * @param  path
 *         The path of the file to check.
 * @return a promise that resolves if the file matched or rejects with a JS
 *         exception in case of error.
 */
var verifyFile = async function(properties, path) {
  if (properties.size !== undefined) {
    let stat = await OS.File.stat(path);
    if (stat.size != properties.size) {
      throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
    }
  }

  if (properties.hashFunction !== undefined) {
    let expectedDigest = properties.hashValue.toLowerCase();
    let digest = await computeHash(properties.hashFunction, path);
    if (digest != expectedDigest) {
      throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
    }
  }
};

const ProductAddonChecker = {
  /**
   * Downloads a list of add-ons from a URL optionally testing the SSL
   * certificate for certain attributes.
   *
   * @param  url
   *         The url to download from.
   * @param  allowNonBuiltIn
   *         Whether to trust SSL certificates without a built-in CA issuer.
   * @param  allowedCerts
   *         The list of certificate attributes to match the SSL certificate
   *         against or null to skip checks.
   * @return a promise that resolves to an object containing the list of add-ons
   *         and whether the local fallback was used, or rejects with a JS
   *         exception in case of error.
   */
  getProductAddonList(url, allowNonBuiltIn = false, allowedCerts = null) {
    if (!GMPPrefs.getBool(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
      logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
      return Promise.resolve({usedFallback: true, gmpAddons: []});
    }

    return downloadXML(url, allowNonBuiltIn, allowedCerts)
      .then(parseXML)
      .catch(downloadLocalConfig);
  },

  /**
   * Downloads an add-on to a local file and checks that it matches the expected
   * file. The caller is responsible for deleting the temporary file returned.
   *
   * @param  addon
   *         The addon to download.
   * @return a promise that resolves to the temporary file downloaded or rejects
   *         with a JS exception in case of error.
   */
  async downloadAddon(addon) {
    let path = await downloadFile(addon.URL);
    try {
      await verifyFile(addon, path);
      return path;
    } catch (e) {
      await OS.File.remove(path);
      throw e;
    }
  }
}
PK
!<c=YPP/modules/addons/SpellCheckDictionaryBootstrap.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 startup, shutdown */

var hunspell, dir;

function startup(data) {
  hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
                       .getService(Components.interfaces.mozISpellCheckingEngine);
  dir = data.installPath.clone();
  dir.append("dictionaries");
  hunspell.addDirectory(dir);
}

function shutdown() {
  hunspell.removeDirectory(dir);
}
PK
!<  'modules/addons/WebExtensionBootstrap.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 */

Components.utils.import("resource://gre/modules/Extension.jsm");

var extension;

const BOOTSTRAP_REASON_TO_STRING_MAP = {
  [this.APP_STARTUP]: "APP_STARTUP",
  [this.APP_SHUTDOWN]: "APP_SHUTDOWN",
  [this.ADDON_ENABLE]: "ADDON_ENABLE",
  [this.ADDON_DISABLE]: "ADDON_DISABLE",
  [this.ADDON_INSTALL]: "ADDON_INSTALL",
  [this.ADDON_UNINSTALL]: "ADDON_UNINSTALL",
  [this.ADDON_UPGRADE]: "ADDON_UPGRADE",
  [this.ADDON_DOWNGRADE]: "ADDON_DOWNGRADE",
};

function install(data, reason) {
}

function startup(data, reason) {
  extension = new Extension(data, BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
  extension.startup();
}

function shutdown(data, reason) {
  extension.shutdown(BOOTSTRAP_REASON_TO_STRING_MAP[reason]);
  extension = null;
}

function uninstall(data, reason) {
}
PK
!<e\!xxmodules/addons/XPIInstall.jsm /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [
  "DownloadAddonInstall",
  "LocalAddonInstall",
  "StagedAddonInstall",
  "UpdateChecker",
  "loadManifestFromFile",
  "verifyBundleSignedState",
];

/* globals DownloadAddonInstall, LocalAddonInstall */

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonSettings",
                                  "resource://gre/modules/addons/AddonSettings.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyGetter(this, "CertUtils",
                            () => Cu.import("resource://gre/modules/CertUtils.jsm", {}));
XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
                                  "resource://gre/modules/ChromeManifestParser.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                  "resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                  "resource://gre/modules/Locale.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
  return Cu.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails;
});
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                  "resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
                                  "resource://gre/modules/ZipUtils.jsm");

const {nsIBlocklistService} = Ci;

const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                       "initWithPath");

XPCOMUtils.defineLazyServiceGetter(this, "gCertDB",
                                   "@mozilla.org/security/x509certdb;1",
                                   "nsIX509CertDB");
XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
                                   Ci.nsIRDFService);


XPCOMUtils.defineLazyModuleGetter(this, "XPIInternal",
                                  "resource://gre/modules/addons/XPIProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "XPIProvider",
                                  "resource://gre/modules/addons/XPIProvider.jsm");

/* globals AddonInternal, BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, TEMPORARY_ADDON_SUFFIX, TOOLKIT_ID, XPIDatabase, XPIStates, getExternalType, isTheme, isUsableAddon, isWebExtension, recordAddonTelemetry */
const XPI_INTERNAL_SYMBOLS = [
  "AddonInternal",
  "BOOTSTRAP_REASONS",
  "KEY_APP_SYSTEM_ADDONS",
  "KEY_APP_SYSTEM_DEFAULTS",
  "KEY_APP_TEMPORARY",
  "TEMPORARY_ADDON_SUFFIX",
  "TOOLKIT_ID",
  "XPIDatabase",
  "XPIStates",
  "getExternalType",
  "isTheme",
  "isUsableAddon",
  "isWebExtension",
  "recordAddonTelemetry",
];

for (let name of XPI_INTERNAL_SYMBOLS) {
  XPCOMUtils.defineLazyGetter(this, name, () => XPIInternal[name]);
}

/**
 * Returns a nsIFile instance for the given path, relative to the given
 * base file, if provided.
 *
 * @param {string} path
 *        The (possibly relative) path of the file.
 * @param {nsIFile} [base]
 *        An optional file to use as a base path if `path` is relative.
 * @returns {nsIFile}
 */
function getFile(path, base = null) {
  // First try for an absolute path, as we get in the case of proxy
  // files. Ideally we would try a relative path first, but on Windows,
  // paths which begin with a drive letter are valid as relative paths,
  // and treated as such.
  try {
    return new nsIFile(path);
  } catch (e) {
    // Ignore invalid relative paths. The only other error we should see
    // here is EOM, and either way, any errors that we care about should
    // be re-thrown below.
  }

  // If the path isn't absolute, we must have a base path.
  let file = base.clone();
  file.appendRelativePath(path);
  return file;
}

const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
const PREF_EM_UPDATE_URL              = "extensions.update.url";
const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";

const FILE_RDF_MANIFEST               = "install.rdf";
const FILE_WEB_MANIFEST               = "manifest.json";

const KEY_TEMPDIR                     = "TmpD";

const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";

// Properties that exist in the install manifest
const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                            "updateKey", "optionsURL", "optionsType", "aboutURL",
                            "iconURL", "icon64URL"];
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];

// Map new string type identifiers to old style nsIUpdateItem types
// Type 32 was previously used for multipackage xpi files so it should
// not be re-used since old files with that type may be floating around.
const TYPES = {
  extension: 2,
  theme: 4,
  locale: 8,
  dictionary: 64,
  experiment: 128,
  apiextension: 256,
};

const COMPATIBLE_BY_DEFAULT_TYPES = {
  extension: true,
  dictionary: true,
};

const RESTARTLESS_TYPES = new Set([
  "apiextension",
  "dictionary",
  "experiment",
  "locale",
  "webextension",
  "webextension-theme",
]);

const SIGNED_TYPES = new Set([
  "apiextension",
  "extension",
  "experiment",
  "webextension",
  "webextension-theme",
]);


// This is a random number array that can be used as "salt" when generating
// an automatic ID based on the directory path of an add-on. It will prevent
// someone from creating an ID for a permanent add-on that could be replaced
// by a temporary add-on (because that would be confusing, I guess).
const TEMP_INSTALL_ID_GEN_SESSION =
  new Uint8Array(Float64Array.of(Math.random()).buffer);

// Whether add-on signing is required.
function mustSign(aType) {
  if (!SIGNED_TYPES.has(aType))
    return false;

  return AddonSettings.REQUIRE_SIGNING;
}

const MSG_JAR_FLUSH = "AddonJarFlush";
const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";


/**
 * Valid IDs fit this pattern.
 */
var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi";

// Create a new logger for use by all objects in this Addons XPI Provider module
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);


/**
 * Sets permissions on a file
 *
 * @param  aFile
 *         The file or directory to operate on.
 * @param  aPermissions
 *         The permisions to set
 */
function setFilePermissions(aFile, aPermissions) {
  try {
    aFile.permissions = aPermissions;
  } catch (e) {
    logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
         aFile.path, e);
  }
}

/**
 * Write a given string to a file
 *
 * @param  file
 *         The nsIFile instance to write into
 * @param  string
 *         The string to write
 */
function writeStringToFile(file, string) {
  let stream = Cc["@mozilla.org/network/file-output-stream;1"].
               createInstance(Ci.nsIFileOutputStream);
  let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
                  createInstance(Ci.nsIConverterOutputStream);

  try {
    stream.init(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                            FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,
                           0);
    converter.init(stream, "UTF-8");
    converter.writeString(string);
  } finally {
    converter.close();
    stream.close();
  }
}

function EM_R(aProperty) {
  return gRDF.GetResource(PREFIX_NS_EM + aProperty);
}

function getManifestFileForDir(aDir) {
  let file = getFile(FILE_RDF_MANIFEST, aDir);
  if (file.exists() && file.isFile())
    return file;
  file.leafName = FILE_WEB_MANIFEST;
  if (file.exists() && file.isFile())
    return file;
  return null;
}

function getManifestEntryForZipReader(aZipReader) {
  if (aZipReader.hasEntry(FILE_RDF_MANIFEST))
    return FILE_RDF_MANIFEST;
  if (aZipReader.hasEntry(FILE_WEB_MANIFEST))
    return FILE_WEB_MANIFEST;
  return null;
}

/**
 * Converts an RDF literal, resource or integer into a string.
 *
 * @param  aLiteral
 *         The RDF object to convert
 * @return a string if the object could be converted or null
 */
function getRDFValue(aLiteral) {
  if (aLiteral instanceof Ci.nsIRDFLiteral)
    return aLiteral.Value;
  if (aLiteral instanceof Ci.nsIRDFResource)
    return aLiteral.Value;
  if (aLiteral instanceof Ci.nsIRDFInt)
    return aLiteral.Value;
  return null;
}

/**
 * Gets an RDF property as a string
 *
 * @param  aDs
 *         The RDF datasource to read the property from
 * @param  aResource
 *         The RDF resource to read the property from
 * @param  aProperty
 *         The property to read
 * @return a string if the property existed or null
 */
function getRDFProperty(aDs, aResource, aProperty) {
  return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
}

/**
 * Reads an AddonInternal object from a manifest stream.
 *
 * @param  aUri
 *         A |file:| or |jar:| URL for the manifest
 * @return an AddonInternal object
 * @throws if the install manifest in the stream is corrupt or could not
 *         be read
 */
async function loadManifestFromWebManifest(aUri) {
  // We're passed the URI for the manifest file. Get the URI for its
  // parent directory.
  let uri = Services.io.newURI("./", null, aUri);

  let extension = new ExtensionData(uri);

  let manifest = await extension.loadManifest();

  // Read the list of available locales, and pre-load messages for
  // all locales.
  let locales = (extension.errors.length == 0) ?
                await extension.initAllLocales() : null;

  if (extension.errors.length > 0) {
    throw new Error("Extension is invalid");
  }

  let theme = Boolean(manifest.theme);

  let bss = (manifest.browser_specific_settings && manifest.browser_specific_settings.gecko)
      || (manifest.applications && manifest.applications.gecko) || {};
  if (manifest.browser_specific_settings && manifest.applications) {
    logger.warn("Ignoring applications property in manifest");
  }

  // A * is illegal in strict_min_version
  if (bss.strict_min_version && bss.strict_min_version.split(".").some(part => part == "*")) {
    throw new Error("The use of '*' in strict_min_version is invalid");
  }

  let addon = new AddonInternal();
  addon.id = bss.id;
  addon.version = manifest.version;
  addon.type = "webextension" + (theme ? "-theme" : "");
  addon.unpack = false;
  addon.strictCompatibility = true;
  addon.bootstrap = true;
  addon.hasBinaryComponents = false;
  addon.multiprocessCompatible = true;
  addon.internalName = null;
  addon.updateURL = bss.update_url;
  addon.updateKey = null;
  addon.optionsBrowserStyle = true;
  addon.optionsURL = null;
  addon.optionsType = null;
  addon.aboutURL = null;
  addon.dependencies = Object.freeze(Array.from(extension.dependencies));

  if (manifest.options_ui) {
    // Store just the relative path here, the AddonWrapper getURL
    // wrapper maps this to a full URL.
    addon.optionsURL = manifest.options_ui.page;
    if (manifest.options_ui.open_in_tab)
      addon.optionsType = AddonManager.OPTIONS_TYPE_TAB;
    else
      addon.optionsType = AddonManager.OPTIONS_TYPE_INLINE_BROWSER;

    if (manifest.options_ui.browser_style === null)
      logger.warn("Please specify whether you want browser_style " +
          "or not in your options_ui options.");
    else
      addon.optionsBrowserStyle = manifest.options_ui.browser_style;
  }

  // WebExtensions don't use iconURLs
  addon.iconURL = null;
  addon.icon64URL = null;
  addon.icons = manifest.icons || {};
  addon.userPermissions = extension.userPermissions;

  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;

  function getLocale(aLocale) {
    // Use the raw manifest, here, since we need values with their
    // localization placeholders still in place.
    let rawManifest = extension.rawManifest;

    // As a convenience, allow author to be set if its a string bug 1313567.
    let creator = typeof(rawManifest.author) === "string" ? rawManifest.author : null;
    let homepageURL = rawManifest.homepage_url;

    // Allow developer to override creator and homepage_url.
    if (rawManifest.developer) {
      if (rawManifest.developer.name) {
        creator = rawManifest.developer.name;
      }
      if (rawManifest.developer.url) {
        homepageURL = rawManifest.developer.url;
      }
    }

    let result = {
      name: extension.localize(rawManifest.name, aLocale),
      description: extension.localize(rawManifest.description, aLocale),
      creator: extension.localize(creator, aLocale),
      homepageURL: extension.localize(homepageURL, aLocale),

      developers: null,
      translators: null,
      contributors: null,
      locales: [aLocale],
    };
    return result;
  }

  addon.defaultLocale = getLocale(extension.defaultLocale);
  addon.locales = Array.from(locales.keys(), getLocale);

  delete addon.defaultLocale.locales;

  addon.targetApplications = [{
    id: TOOLKIT_ID,
    minVersion: bss.strict_min_version,
    maxVersion: bss.strict_max_version,
  }];

  addon.targetPlatforms = [];
  // Themes are disabled by default, except when they're installed from a web page.
  addon.userDisabled = theme;
  addon.softDisabled = addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;

  return addon;
}

/**
 * Reads an AddonInternal object from an RDF stream.
 *
 * @param  aUri
 *         The URI that the manifest is being read from
 * @param  aStream
 *         An open stream to read the RDF from
 * @return an AddonInternal object
 * @throws if the install manifest in the RDF stream is corrupt or could not
 *         be read
 */
async function loadManifestFromRDF(aUri, aStream) {
  function getPropertyArray(aDs, aSource, aProperty) {
    let values = [];
    let targets = aDs.GetTargets(aSource, EM_R(aProperty), true);
    while (targets.hasMoreElements())
      values.push(getRDFValue(targets.getNext()));

    return values;
  }

  /**
   * Reads locale properties from either the main install manifest root or
   * an em:localized section in the install manifest.
   *
   * @param  aDs
   *         The nsIRDFDatasource to read from
   * @param  aSource
   *         The nsIRDFResource to read the properties from
   * @param  isDefault
   *         True if the locale is to be read from the main install manifest
   *         root
   * @param  aSeenLocales
   *         An array of locale names already seen for this install manifest.
   *         Any locale names seen as a part of this function will be added to
   *         this array
   * @return an object containing the locale properties
   */
  function readLocale(aDs, aSource, isDefault, aSeenLocales) {
    let locale = { };
    if (!isDefault) {
      locale.locales = [];
      let targets = ds.GetTargets(aSource, EM_R("locale"), true);
      while (targets.hasMoreElements()) {
        let localeName = getRDFValue(targets.getNext());
        if (!localeName) {
          logger.warn("Ignoring empty locale in localized properties");
          continue;
        }
        if (aSeenLocales.indexOf(localeName) != -1) {
          logger.warn("Ignoring duplicate locale in localized properties");
          continue;
        }
        aSeenLocales.push(localeName);
        locale.locales.push(localeName);
      }

      if (locale.locales.length == 0) {
        logger.warn("Ignoring localized properties with no listed locales");
        return null;
      }
    }

    for (let prop of PROP_LOCALE_SINGLE) {
      locale[prop] = getRDFProperty(aDs, aSource, prop);
    }

    for (let prop of PROP_LOCALE_MULTI) {
      // Don't store empty arrays
      let props = getPropertyArray(aDs, aSource,
                                   prop.substring(0, prop.length - 1));
      if (props.length > 0)
        locale[prop] = props;
    }

    return locale;
  }

  let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
                  createInstance(Ci.nsIRDFXMLParser)
  let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
           createInstance(Ci.nsIRDFDataSource);
  let listener = rdfParser.parseAsync(ds, aUri);
  let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
                createInstance(Ci.nsIInputStreamChannel);
  channel.setURI(aUri);
  channel.contentStream = aStream;
  channel.QueryInterface(Ci.nsIChannel);
  channel.contentType = "text/xml";

  listener.onStartRequest(channel, null);

  try {
    let pos = 0;
    let count = aStream.available();
    while (count > 0) {
      listener.onDataAvailable(channel, null, aStream, pos, count);
      pos += count;
      count = aStream.available();
    }
    listener.onStopRequest(channel, null, Components.results.NS_OK);
  } catch (e) {
    listener.onStopRequest(channel, null, e.result);
    throw e;
  }

  let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  let addon = new AddonInternal();
  for (let prop of PROP_METADATA) {
    addon[prop] = getRDFProperty(ds, root, prop);
  }
  addon.unpack = getRDFProperty(ds, root, "unpack") == "true";

  if (!addon.type) {
    addon.type = addon.internalName ? "theme" : "extension";
  } else {
    let type = addon.type;
    addon.type = null;
    for (let name in TYPES) {
      if (TYPES[name] == type) {
        addon.type = name;
        break;
      }
    }
  }

  if (!(addon.type in TYPES))
    throw new Error("Install manifest specifies unknown type: " + addon.type);

  if (!addon.id)
    throw new Error("No ID in install manifest");
  if (!gIDTest.test(addon.id))
    throw new Error("Illegal add-on ID " + addon.id);
  if (!addon.version)
    throw new Error("No version in install manifest");

  addon.strictCompatibility = !(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
                              getRDFProperty(ds, root, "strictCompatibility") == "true";

  // Only read these properties for extensions.
  if (addon.type == "extension") {
    addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";

    let mpcValue = getRDFProperty(ds, root, "multiprocessCompatible");
    addon.multiprocessCompatible = mpcValue == "true";
    addon.mpcOptedOut = mpcValue == "false";

    addon.hasEmbeddedWebExtension = getRDFProperty(ds, root, "hasEmbeddedWebExtension") == "true";

    if (addon.optionsType &&
        addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
        addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE &&
        addon.optionsType != AddonManager.OPTIONS_TYPE_TAB &&
        addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_INFO) {
      throw new Error("Install manifest specifies unknown type: " + addon.optionsType);
    }

    if (addon.hasEmbeddedWebExtension) {
      let uri = Services.io.newURI("webextension/manifest.json", null, aUri);
      let embeddedAddon = await loadManifestFromWebManifest(uri);
      if (embeddedAddon.optionsURL) {
        if (addon.optionsType || addon.optionsURL)
          logger.warn(`Addon ${addon.id} specifies optionsType or optionsURL ` +
                      `in both install.rdf and manifest.json`);

        addon.optionsURL = embeddedAddon.optionsURL;
        addon.optionsType = embeddedAddon.optionsType;
      }
    }
  } else {
    // Some add-on types are always restartless.
    if (RESTARTLESS_TYPES.has(addon.type)) {
      addon.bootstrap = true;
    }

    // Only extensions are allowed to provide an optionsURL, optionsType,
    // optionsBrowserStyle, or aboutURL. For all other types they are silently ignored
    addon.aboutURL = null;
    addon.optionsBrowserStyle = null;
    addon.optionsType = null;
    addon.optionsURL = null;

    if (addon.type == "theme") {
      if (!addon.internalName)
        throw new Error("Themes must include an internalName property");
      addon.skinnable = getRDFProperty(ds, root, "skinnable") == "true";
    }
  }

  addon.defaultLocale = readLocale(ds, root, true);

  let seenLocales = [];
  addon.locales = [];
  let targets = ds.GetTargets(root, EM_R("localized"), true);
  while (targets.hasMoreElements()) {
    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
    let locale = readLocale(ds, target, false, seenLocales);
    if (locale)
      addon.locales.push(locale);
  }

  let dependencies = new Set();
  targets = ds.GetTargets(root, EM_R("dependency"), true);
  while (targets.hasMoreElements()) {
    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
    let id = getRDFProperty(ds, target, "id");
    dependencies.add(id);
  }
  addon.dependencies = Object.freeze(Array.from(dependencies));

  let seenApplications = [];
  addon.targetApplications = [];
  targets = ds.GetTargets(root, EM_R("targetApplication"), true);
  while (targets.hasMoreElements()) {
    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
    let targetAppInfo = {};
    for (let prop of PROP_TARGETAPP) {
      targetAppInfo[prop] = getRDFProperty(ds, target, prop);
    }
    if (!targetAppInfo.id || !targetAppInfo.minVersion ||
        !targetAppInfo.maxVersion) {
      logger.warn("Ignoring invalid targetApplication entry in install manifest");
      continue;
    }
    if (seenApplications.indexOf(targetAppInfo.id) != -1) {
      logger.warn("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
           " in install manifest");
      continue;
    }
    seenApplications.push(targetAppInfo.id);
    addon.targetApplications.push(targetAppInfo);
  }

  // Note that we don't need to check for duplicate targetPlatform entries since
  // the RDF service coalesces them for us.
  let targetPlatforms = getPropertyArray(ds, root, "targetPlatform");
  addon.targetPlatforms = [];
  for (let targetPlatform of targetPlatforms) {
    let platform = {
      os: null,
      abi: null
    };

    let pos = targetPlatform.indexOf("_");
    if (pos != -1) {
      platform.os = targetPlatform.substring(0, pos);
      platform.abi = targetPlatform.substring(pos + 1);
    } else {
      platform.os = targetPlatform;
    }

    addon.targetPlatforms.push(platform);
  }

  // A theme's userDisabled value is true if the theme is not the selected skin
  // or if there is an active lightweight theme. We ignore whether softblocking
  // is in effect since it would change the active theme.
  if (isTheme(addon.type)) {
    addon.userDisabled = !!LightweightThemeManager.currentTheme ||
                         addon.internalName != XPIProvider.selectedSkin;
  } else if (addon.type == "experiment") {
    // Experiments are disabled by default. It is up to the Experiments Manager
    // to enable them (it drives installation).
    addon.userDisabled = true;
  } else {
    addon.userDisabled = false;
  }

  addon.softDisabled = addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;
  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;

  // Experiments are managed and updated through an external "experiments
  // manager." So disable some built-in mechanisms.
  if (addon.type == "experiment") {
    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
    addon.updateURL = null;
    addon.updateKey = null;
  }

  // icons will be filled by the calling function
  addon.icons = {};
  addon.userPermissions = null;

  return addon;
}

function defineSyncGUID(aAddon) {
  // Define .syncGUID as a lazy property which is also settable
  Object.defineProperty(aAddon, "syncGUID", {
    get: () => {
      // Generate random GUID used for Sync.
      let guid = Cc["@mozilla.org/uuid-generator;1"]
          .getService(Ci.nsIUUIDGenerator)
          .generateUUID().toString();

      delete aAddon.syncGUID;
      aAddon.syncGUID = guid;
      return guid;
    },
    set: (val) => {
      delete aAddon.syncGUID;
      aAddon.syncGUID = val;
    },
    configurable: true,
    enumerable: true,
  });
}

// Generate a unique ID based on the path to this temporary add-on location.
function generateTemporaryInstallID(aFile) {
  const hasher = Cc["@mozilla.org/security/hash;1"]
        .createInstance(Ci.nsICryptoHash);
  hasher.init(hasher.SHA1);
  const data = new TextEncoder().encode(aFile.path);
  // Make it so this ID cannot be guessed.
  const sess = TEMP_INSTALL_ID_GEN_SESSION;
  hasher.update(sess, sess.length);
  hasher.update(data, data.length);
  let id = `${getHashStringForCrypto(hasher)}${TEMPORARY_ADDON_SUFFIX}`;
  logger.info(`Generated temp id ${id} (${sess.join("")}) for ${aFile.path}`);
  return id;
}

/**
 * Loads an AddonInternal object from an add-on extracted in a directory.
 *
 * @param  aDir
 *         The nsIFile directory holding the add-on
 * @return an AddonInternal object
 * @throws if the directory does not contain a valid install manifest
 */
var loadManifestFromDir = async function(aDir, aInstallLocation) {
  function getFileSize(aFile) {
    if (aFile.isSymlink())
      return 0;

    if (!aFile.isDirectory())
      return aFile.fileSize;

    let size = 0;
    let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
    let entry;
    while ((entry = entries.nextFile))
      size += getFileSize(entry);
    entries.close();
    return size;
  }

  async function loadFromRDF(aUri) {
    let fis = Cc["@mozilla.org/network/file-input-stream;1"].
              createInstance(Ci.nsIFileInputStream);
    fis.init(aUri.file, -1, -1, false);
    let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
              createInstance(Ci.nsIBufferedInputStream);
    bis.init(fis, 4096);
    try {
      var addon = await loadManifestFromRDF(aUri, bis);
    } finally {
      bis.close();
      fis.close();
    }

    let iconFile = getFile("icon.png", aDir);

    if (iconFile.exists()) {
      addon.icons[32] = "icon.png";
      addon.icons[48] = "icon.png";
    }

    let icon64File = getFile("icon64.png", aDir);

    if (icon64File.exists()) {
      addon.icons[64] = "icon64.png";
    }

    let file = getFile("chrome.manifest", aDir);
    let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
    addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
                                                             "binary-component");
    return addon;
  }

  let file = getManifestFileForDir(aDir);
  if (!file) {
    throw new Error("Directory " + aDir.path + " does not contain a valid " +
                    "install manifest");
  }

  let uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);

  let addon;
  if (file.leafName == FILE_WEB_MANIFEST) {
    addon = await loadManifestFromWebManifest(uri);
    if (!addon.id) {
      if (aInstallLocation.name == KEY_APP_TEMPORARY) {
        addon.id = generateTemporaryInstallID(aDir);
      } else {
        addon.id = aDir.leafName;
      }
    }
  } else {
    addon = await loadFromRDF(uri);
  }

  addon._sourceBundle = aDir.clone();
  addon._installLocation = aInstallLocation;
  addon.size = getFileSize(aDir);
  addon.signedState = await verifyDirSignedState(aDir, addon)
    .then(({signedState}) => signedState);
  addon.updateBlocklistState();
  addon.appDisabled = !isUsableAddon(addon);

  defineSyncGUID(addon);

  return addon;
};

/**
 * Loads an AddonInternal object from an nsIZipReader for an add-on.
 *
 * @param  aZipReader
 *         An open nsIZipReader for the add-on's files
 * @return an AddonInternal object
 * @throws if the XPI file does not contain a valid install manifest
 */
var loadManifestFromZipReader = async function(aZipReader, aInstallLocation) {
  async function loadFromRDF(aUri) {
    let zis = aZipReader.getInputStream(entry);
    let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
              createInstance(Ci.nsIBufferedInputStream);
    bis.init(zis, 4096);
    try {
      var addon = await loadManifestFromRDF(aUri, bis);
    } finally {
      bis.close();
      zis.close();
    }

    if (aZipReader.hasEntry("icon.png")) {
      addon.icons[32] = "icon.png";
      addon.icons[48] = "icon.png";
    }

    if (aZipReader.hasEntry("icon64.png")) {
      addon.icons[64] = "icon64.png";
    }

    // Binary components can only be loaded from unpacked addons.
    if (addon.unpack) {
      let uri = buildJarURI(aZipReader.file, "chrome.manifest");
      let chromeManifest = ChromeManifestParser.parseSync(uri);
      addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
                                                               "binary-component");
    } else {
      addon.hasBinaryComponents = false;
    }

    return addon;
  }

  let entry = getManifestEntryForZipReader(aZipReader);
  if (!entry) {
    throw new Error("File " + aZipReader.file.path + " does not contain a valid " +
                    "install manifest");
  }

  let uri = buildJarURI(aZipReader.file, entry);

  let isWebExtension = (entry == FILE_WEB_MANIFEST);

  let addon = isWebExtension ?
              await loadManifestFromWebManifest(uri) :
              await loadFromRDF(uri);

  addon._sourceBundle = aZipReader.file;
  addon._installLocation = aInstallLocation;

  addon.size = 0;
  let entries = aZipReader.findEntries(null);
  while (entries.hasMore())
    addon.size += aZipReader.getEntry(entries.getNext()).realSize;

  let {signedState, cert} = await verifyZipSignedState(aZipReader.file, addon);
  addon.signedState = signedState;
  if (isWebExtension && !addon.id) {
    if (cert) {
      addon.id = cert.commonName;
      if (!gIDTest.test(addon.id)) {
        throw new Error(`Webextension is signed with an invalid id (${addon.id})`);
      }
    }
    if (!addon.id && aInstallLocation.name == KEY_APP_TEMPORARY) {
      addon.id = generateTemporaryInstallID(aZipReader.file);
    }
  }
  addon.updateBlocklistState();
  addon.appDisabled = !isUsableAddon(addon);

  defineSyncGUID(addon);

  return addon;
};

/**
 * Loads an AddonInternal object from an add-on in an XPI file.
 *
 * @param  aXPIFile
 *         An nsIFile pointing to the add-on's XPI file
 * @return an AddonInternal object
 * @throws if the XPI file does not contain a valid install manifest
 */
var loadManifestFromZipFile = async function(aXPIFile, aInstallLocation) {
  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                  createInstance(Ci.nsIZipReader);
  try {
    zipReader.open(aXPIFile);

    // Can't return this promise because that will make us close the zip reader
    // before it has finished loading the manifest. Wait for the result and then
    // return.
    let manifest = await loadManifestFromZipReader(zipReader, aInstallLocation);
    return manifest;
  } finally {
    zipReader.close();
  }
};

this.loadManifestFromFile = function(aFile, aInstallLocation) {
  if (aFile.isFile())
    return loadManifestFromZipFile(aFile, aInstallLocation);
  return loadManifestFromDir(aFile, aInstallLocation);
}

/**
 * Creates a jar: URI for a file inside a ZIP file.
 *
 * @param  aJarfile
 *         The ZIP file as an nsIFile
 * @param  aPath
 *         The path inside the ZIP file
 * @return an nsIURI for the file
 */
function buildJarURI(aJarfile, aPath) {
  let uri = Services.io.newFileURI(aJarfile);
  uri = "jar:" + uri.spec + "!/" + aPath;
  return Services.io.newURI(uri);
}

/**
 * Sends local and remote notifications to flush a JAR file cache entry
 *
 * @param aJarFile
 *        The ZIP/XPI/JAR file as a nsIFile
 */
function flushJarCache(aJarFile) {
  Services.obs.notifyObservers(aJarFile, "flush-cache-entry");
  Services.mm.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}

function flushChromeCaches() {
  // Init this, so it will get the notification.
  Services.obs.notifyObservers(null, "startupcache-invalidate");
  // Flush message manager cached scripts
  Services.obs.notifyObservers(null, "message-manager-flush-caches");
  // Also dispatch this event to child processes
  Services.mm.broadcastAsyncMessage(MSG_MESSAGE_MANAGER_CACHES_FLUSH, null);
}

/**
 * Creates and returns a new unique temporary file. The caller should delete
 * the file when it is no longer needed.
 *
 * @return an nsIFile that points to a randomly named, initially empty file in
 *         the OS temporary files directory
 */
function getTemporaryFile() {
  let file = FileUtils.getDir(KEY_TEMPDIR, []);
  let random = Math.random().toString(36).replace(/0./, "").substr(-3);
  file.append("tmp-" + random + ".xpi");
  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);

  return file;
}

/**
 * Verifies that a zip file's contents are all signed by the same principal.
 * Directory entries and anything in the META-INF directory are not checked.
 *
 * @param  aZip
 *         A nsIZipReader to check
 * @param  aCertificate
 *         The nsIX509Cert to compare against
 * @return true if all the contents that should be signed were signed by the
 *         principal
 */
function verifyZipSigning(aZip, aCertificate) {
  var count = 0;
  var entries = aZip.findEntries(null);
  while (entries.hasMore()) {
    var entry = entries.getNext();
    // Nothing in META-INF is in the manifest.
    if (entry.substr(0, 9) == "META-INF/")
      continue;
    // Directory entries aren't in the manifest.
    if (entry.substr(-1) == "/")
      continue;
    count++;
    var entryCertificate = aZip.getSigningCert(entry);
    if (!entryCertificate || !aCertificate.equals(entryCertificate)) {
      return false;
    }
  }
  return aZip.manifestEntriesCount == count;
}

/**
 * Returns the signedState for a given return code and certificate by verifying
 * it against the expected ID.
 */
function getSignedStatus(aRv, aCert, aAddonID) {
  let expectedCommonName = aAddonID;
  if (aAddonID && aAddonID.length > 64) {
    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                    createInstance(Ci.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
    let data = converter.convertToByteArray(aAddonID, {});

    let crypto = Cc["@mozilla.org/security/hash;1"].
                 createInstance(Ci.nsICryptoHash);
    crypto.init(Ci.nsICryptoHash.SHA256);
    crypto.update(data, data.length);
    expectedCommonName = getHashStringForCrypto(crypto);
  }

  switch (aRv) {
    case Cr.NS_OK:
      if (expectedCommonName && expectedCommonName != aCert.commonName)
        return AddonManager.SIGNEDSTATE_BROKEN;

      let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
      if (hotfixID && hotfixID == aAddonID && Preferences.get(PREF_EM_CERT_CHECKATTRIBUTES, false)) {
        // The hotfix add-on has some more rigorous certificate checks
        try {
          CertUtils.validateCert(aCert,
                                 CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS));
        } catch (e) {
          logger.warn("The hotfix add-on was not signed by the expected " +
                      "certificate and so will not be installed.", e);
          return AddonManager.SIGNEDSTATE_BROKEN;
        }
      }

      if (aCert.organizationalUnit == "Mozilla Components") {
        return AddonManager.SIGNEDSTATE_SYSTEM;
      }

      if (aCert.organizationalUnit == "Mozilla Extensions") {
        return AddonManager.SIGNEDSTATE_PRIVILEGED;
      }

      return /preliminary/i.test(aCert.organizationalUnit)
               ? AddonManager.SIGNEDSTATE_PRELIMINARY
               : AddonManager.SIGNEDSTATE_SIGNED;
    case Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED:
      return AddonManager.SIGNEDSTATE_MISSING;
    case Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE:
    case Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY:
    case Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY:
      return AddonManager.SIGNEDSTATE_BROKEN;
    default:
      // Any other error indicates that either the add-on isn't signed or it
      // is signed by a signature that doesn't chain to the trusted root.
      return AddonManager.SIGNEDSTATE_UNKNOWN;
  }
}

function shouldVerifySignedState(aAddon) {
  // Updated system add-ons should always have their signature checked
  if (aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS)
    return true;

  // We don't care about signatures for default system add-ons
  if (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS)
    return false;

  // Hotfixes should always have their signature checked
  let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
  if (hotfixID && aAddon.id == hotfixID)
    return true;

  // Otherwise only check signatures if signing is enabled and the add-on is one
  // of the signed types.
  return AddonSettings.ADDON_SIGNING && SIGNED_TYPES.has(aAddon.type);
}

/**
 * Verifies that a zip file's contents are all correctly signed by an
 * AMO-issued certificate
 *
 * @param  aFile
 *         the xpi file to check
 * @param  aAddon
 *         the add-on object to verify
 * @return a Promise that resolves to an object with properties:
 *         signedState: an AddonManager.SIGNEDSTATE_* constant
 *         cert: an nsIX509Cert
 */
function verifyZipSignedState(aFile, aAddon) {
  if (!shouldVerifySignedState(aAddon))
    return Promise.resolve({
      signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
      cert: null
    });

  let root = Ci.nsIX509CertDB.AddonsPublicRoot;
  if (!AppConstants.MOZ_REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
    root = Ci.nsIX509CertDB.AddonsStageRoot;

  return new Promise(resolve => {
    let callback = {
      openSignedAppFileFinished(aRv, aZipReader, aCert) {
        if (aZipReader)
          aZipReader.close();
        resolve({
          signedState: getSignedStatus(aRv, aCert, aAddon.id),
          cert: aCert
        });
      }
    };
    // This allows the certificate DB to get the raw JS callback object so the
    // test code can pass through objects that XPConnect would reject.
    callback.wrappedJSObject = callback;

    gCertDB.openSignedAppFileAsync(root, aFile, callback);
  });
}

/**
 * Verifies that a directory's contents are all correctly signed by an
 * AMO-issued certificate
 *
 * @param  aDir
 *         the directory to check
 * @param  aAddon
 *         the add-on object to verify
 * @return a Promise that resolves to an object with properties:
 *         signedState: an AddonManager.SIGNEDSTATE_* constant
 *         cert: an nsIX509Cert
 */
function verifyDirSignedState(aDir, aAddon) {
  if (!shouldVerifySignedState(aAddon))
    return Promise.resolve({
      signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
      cert: null,
    });

  let root = Ci.nsIX509CertDB.AddonsPublicRoot;
  if (!AppConstants.MOZ_REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
    root = Ci.nsIX509CertDB.AddonsStageRoot;

  return new Promise(resolve => {
    let callback = {
      verifySignedDirectoryFinished(aRv, aCert) {
        resolve({
          signedState: getSignedStatus(aRv, aCert, aAddon.id),
          cert: null,
        });
      }
    };
    // This allows the certificate DB to get the raw JS callback object so the
    // test code can pass through objects that XPConnect would reject.
    callback.wrappedJSObject = callback;

    gCertDB.verifySignedDirectoryAsync(root, aDir, callback);
  });
}

/**
 * Verifies that a bundle's contents are all correctly signed by an
 * AMO-issued certificate
 *
 * @param  aBundle
 *         the nsIFile for the bundle to check, either a directory or zip file
 * @param  aAddon
 *         the add-on object to verify
 * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
 */
this.verifyBundleSignedState = function(aBundle, aAddon) {
  let promise = aBundle.isFile() ? verifyZipSignedState(aBundle, aAddon)
      : verifyDirSignedState(aBundle, aAddon);
  return promise.then(({signedState}) => signedState);
}

/**
 * Replaces %...% strings in an addon url (update and updateInfo) with
 * appropriate values.
 *
 * @param  aAddon
 *         The AddonInternal representing the add-on
 * @param  aUri
 *         The uri to escape
 * @param  aUpdateType
 *         An optional number representing the type of update, only applicable
 *         when creating a url for retrieving an update manifest
 * @param  aAppVersion
 *         The optional application version to use for %APP_VERSION%
 * @return the appropriately escaped uri.
 */
function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) {
  let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion);

  // If there is an updateType then replace the UPDATE_TYPE string
  if (aUpdateType)
    uri = uri.replace(/%UPDATE_TYPE%/g, aUpdateType);

  // If this add-on has compatibility information for either the current
  // application or toolkit then replace the ITEM_MAXAPPVERSION with the
  // maxVersion
  let app = aAddon.matchingTargetApplication;
  if (app)
    var maxVersion = app.maxVersion;
  else
    maxVersion = "";
  uri = uri.replace(/%ITEM_MAXAPPVERSION%/g, maxVersion);

  let compatMode = "normal";
  if (!AddonManager.checkCompatibility)
    compatMode = "ignore";
  else if (AddonManager.strictCompatibility)
    compatMode = "strict";
  uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);

  return uri;
}

async function removeAsync(aFile) {
  let info = null;
  try {
    info = await OS.File.stat(aFile.path);
    if (info.isDir)
      await OS.File.removeDir(aFile.path);
    else
      await OS.File.remove(aFile.path);
  } catch (e) {
    if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile)
      throw e;
    // The file has already gone away
  }
}

/**
 * Recursively removes a directory or file fixing permissions when necessary.
 *
 * @param  aFile
 *         The nsIFile to remove
 */
function recursiveRemove(aFile) {
  let isDir = null;

  try {
    isDir = aFile.isDirectory();
  } catch (e) {
    // If the file has already gone away then don't worry about it, this can
    // happen on OSX where the resource fork is automatically moved with the
    // data fork for the file. See bug 733436.
    if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
      return;
    if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND)
      return;

    throw e;
  }

  setFilePermissions(aFile, isDir ? FileUtils.PERMS_DIRECTORY
                                  : FileUtils.PERMS_FILE);

  try {
    aFile.remove(true);
    return;
  } catch (e) {
    if (!aFile.isDirectory() || aFile.isSymlink()) {
      logger.error("Failed to remove file " + aFile.path, e);
      throw e;
    }
  }

  // Use a snapshot of the directory contents to avoid possible issues with
  // iterating over a directory while removing files from it (the YAFFS2
  // embedded filesystem has this issue, see bug 772238), and to remove
  // normal files before their resource forks on OSX (see bug 733436).
  let entries = getDirectoryEntries(aFile, true);
  entries.forEach(recursiveRemove);

  try {
    aFile.remove(true);
  } catch (e) {
    logger.error("Failed to remove empty directory " + aFile.path, e);
    throw e;
  }
}

/**
 * Gets a snapshot of directory entries.
 *
 * @param  aDir
 *         Directory to look at
 * @param  aSortEntries
 *         True to sort entries by filename
 * @return An array of nsIFile, or an empty array if aDir is not a readable directory
 */
function getDirectoryEntries(aDir, aSortEntries) {
  let dirEnum;
  try {
    dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
    let entries = [];
    while (dirEnum.hasMoreElements())
      entries.push(dirEnum.nextFile);

    if (aSortEntries) {
      entries.sort(function(a, b) {
        return a.path > b.path ? -1 : 1;
      });
    }

    return entries
  } catch (e) {
    if (aDir.exists()) {
      logger.warn("Can't iterate directory " + aDir.path, e);
    }
    return [];
  } finally {
    if (dirEnum) {
      dirEnum.close();
    }
  }
}

function getHashStringForCrypto(aCrypto) {
  // return the two-digit hexadecimal code for a byte
  let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);

  // convert the binary hash data to a hex string.
  let binary = aCrypto.finish(false);
  let hash = Array.from(binary, c => toHexString(c.charCodeAt(0)))
  return hash.join("").toLowerCase();
}

/**
 * Base class for objects that manage the installation of an addon.
 * This class isn't instantiated directly, see the derived classes below.
 */
class AddonInstall {
  /**
   * Instantiates an AddonInstall.
   *
   * @param  installLocation
   *         The install location the add-on will be installed into
   * @param  url
   *         The nsIURL to get the add-on from. If this is an nsIFileURL then
   *         the add-on will not need to be downloaded
   * @param  options
   *         Additional options for the install
   * @param  options.hash
   *         An optional hash for the add-on
   * @param  options.existingAddon
   *         The add-on this install will update if known
   * @param  options.name
   *         An optional name for the add-on
   * @param  options.type
   *         An optional type for the add-on
   * @param  options.icons
   *         Optional icons for the add-on
   * @param  options.version
   *         An optional version for the add-on
   * @param  options.promptHandler
   *         A callback to prompt the user before installing.
   */
  constructor(installLocation, url, options = {}) {
    this.wrapper = new AddonInstallWrapper(this);
    this.installLocation = installLocation;
    this.sourceURI = url;

    if (options.hash) {
      let hashSplit = options.hash.toLowerCase().split(":");
      this.originalHash = {
        algorithm: hashSplit[0],
        data: hashSplit[1]
      };
    }
    this.hash = this.originalHash;
    this.existingAddon = options.existingAddon || null;
    this.promptHandler = options.promptHandler || (() => Promise.resolve());
    this.releaseNotesURI = null;

    this.listeners = [];
    this.icons = options.icons || {};
    this.error = 0;

    this.progress = 0;
    this.maxProgress = -1;

    // Giving each instance of AddonInstall a reference to the logger.
    this.logger = logger;

    this.name = options.name || null;
    this.type = options.type || null;
    this.version = options.version || null;

    this.file = null;
    this.ownsTempFile = null;
    this.certificate = null;
    this.certName = null;

    this.addon = null;
    this.state = null;

    XPIProvider.installs.add(this);
  }

  /**
   * Starts installation of this add-on from whatever state it is currently at
   * if possible.
   *
   * Note this method is overridden to handle additional state in
   * the subclassses below.
   *
   * @throws if installation cannot proceed from the current state
   */
  install() {
    switch (this.state) {
    case AddonManager.STATE_DOWNLOADED:
      this.checkPrompt();
      break;
    case AddonManager.STATE_PROMPTS_DONE:
      this.checkForBlockers();
      break;
    case AddonManager.STATE_READY:
      this.startInstall();
      break;
    case AddonManager.STATE_POSTPONED:
      logger.debug(`Postponing install of ${this.addon.id}`);
      break;
    case AddonManager.STATE_DOWNLOADING:
    case AddonManager.STATE_CHECKING:
    case AddonManager.STATE_INSTALLING:
      // Installation is already running
      return;
    default:
      throw new Error("Cannot start installing from this state");
    }
  }

  /**
   * Cancels installation of this add-on.
   *
   * Note this method is overridden to handle additional state in
   * the subclass DownloadAddonInstall.
   *
   * @throws if installation cannot be cancelled from the current state
   */
  cancel() {
    switch (this.state) {
    case AddonManager.STATE_AVAILABLE:
    case AddonManager.STATE_DOWNLOADED:
      logger.debug("Cancelling download of " + this.sourceURI.spec);
      this.state = AddonManager.STATE_CANCELLED;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                               this.listeners, this.wrapper);
      this.removeTemporaryFile();
      break;
    case AddonManager.STATE_INSTALLED:
      logger.debug("Cancelling install of " + this.addon.id);
      let xpi = getFile(`${this.addon.id}.xpi`, this.installLocation.getStagingDir());
      flushJarCache(xpi);
      this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi",
                                            this.addon.id + ".json"]);
      this.state = AddonManager.STATE_CANCELLED;
      XPIProvider.removeActiveInstall(this);

      if (this.existingAddon) {
        delete this.existingAddon.pendingUpgrade;
        this.existingAddon.pendingUpgrade = null;
      }

      AddonManagerPrivate.callAddonListeners("onOperationCancelled", this.addon.wrapper);

      AddonManagerPrivate.callInstallListeners("onInstallCancelled",
                                               this.listeners, this.wrapper);
      break;
    case AddonManager.STATE_POSTPONED:
      logger.debug(`Cancelling postponed install of ${this.addon.id}`);
      this.state = AddonManager.STATE_CANCELLED;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onInstallCancelled",
                                               this.listeners, this.wrapper);
      this.removeTemporaryFile();

      let stagingDir = this.installLocation.getStagingDir();
      let stagedAddon = stagingDir.clone();

      this.unstageInstall(stagedAddon);
    default:
      throw new Error("Cannot cancel install of " + this.sourceURI.spec +
                      " from this state (" + this.state + ")");
    }
  }

  /**
   * Adds an InstallListener for this instance if the listener is not already
   * registered.
   *
   * @param  aListener
   *         The InstallListener to add
   */
  addListener(aListener) {
    if (!this.listeners.some(function(i) { return i == aListener; }))
      this.listeners.push(aListener);
  }

  /**
   * Removes an InstallListener for this instance if it is registered.
   *
   * @param  aListener
   *         The InstallListener to remove
   */
  removeListener(aListener) {
    this.listeners = this.listeners.filter(function(i) {
      return i != aListener;
    });
  }

  /**
   * Removes the temporary file owned by this AddonInstall if there is one.
   */
  removeTemporaryFile() {
    // Only proceed if this AddonInstall owns its XPI file
    if (!this.ownsTempFile) {
      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file");
      return;
    }

    try {
      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " +
          this.file.path);
      this.file.remove(true);
      this.ownsTempFile = false;
    } catch (e) {
      this.logger.warn("Failed to remove temporary file " + this.file.path + " for addon " +
          this.sourceURI.spec,
          e);
    }
  }

  /**
   * Updates the sourceURI and releaseNotesURI values on the Addon being
   * installed by this AddonInstall instance.
   */
  updateAddonURIs() {
    this.addon.sourceURI = this.sourceURI.spec;
    if (this.releaseNotesURI)
      this.addon.releaseNotesURI = this.releaseNotesURI.spec;
  }

  /**
   * Called after the add-on is a local file and the signature and install
   * manifest can be read.
   *
   * @param  aCallback
   *         A function to call when the manifest has been loaded
   * @throws if the add-on does not contain a valid install manifest or the
   *         XPI is incorrectly signed
   */
  async loadManifest(file) {
    let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
        createInstance(Ci.nsIZipReader);
    try {
      zipreader.open(file);
    } catch (e) {
      zipreader.close();
      return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
    }

    try {
      // loadManifestFromZipReader performs the certificate verification for us
      this.addon = await loadManifestFromZipReader(zipreader, this.installLocation);
    } catch (e) {
      zipreader.close();
      return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
    }

    if (!this.addon.id) {
      let err = new Error(`Cannot find id for addon ${file.path}`);
      return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, err]);
    }

    if (this.existingAddon) {
      // Check various conditions related to upgrades
      if (this.addon.id != this.existingAddon.id) {
        zipreader.close();
        return Promise.reject([AddonManager.ERROR_INCORRECT_ID,
                               `Refusing to upgrade addon ${this.existingAddon.id} to different ID ${this.addon.id}`]);
      }

      if (isWebExtension(this.existingAddon.type) && !isWebExtension(this.addon.type)) {
        zipreader.close();
        return Promise.reject([AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
                               "WebExtensions may not be upated to other extension types"]);
      }
    }

    if (mustSign(this.addon.type)) {
      if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
        // This add-on isn't properly signed by a signature that chains to the
        // trusted root.
        let state = this.addon.signedState;
        this.addon = null;
        zipreader.close();

        if (state == AddonManager.SIGNEDSTATE_MISSING)
          return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
                                 "signature is required but missing"])

        return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
                               "signature verification failed"])
      }
    } else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN ||
             this.addon.signedState == AddonManager.SIGNEDSTATE_NOT_REQUIRED) {
      // Check object signing certificate, if any
      let x509 = zipreader.getSigningCert(null);
      if (x509) {
        logger.debug("Verifying XPI signature");
        if (verifyZipSigning(zipreader, x509)) {
          this.certificate = x509;
          if (this.certificate.commonName.length > 0) {
            this.certName = this.certificate.commonName;
          } else {
            this.certName = this.certificate.organization;
          }
        } else {
          zipreader.close();
          return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
                                 "XPI is incorrectly signed"]);
        }
      }
    }

    zipreader.close();

    this.updateAddonURIs();

    this.addon._install = this;
    this.name = this.addon.selectedLocale.name;
    this.type = this.addon.type;
    this.version = this.addon.version;

    // Setting the iconURL to something inside the XPI locks the XPI and
    // makes it impossible to delete on Windows.

    // Try to load from the existing cache first
    let repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));

    // It wasn't there so try to re-download it
    if (!repoAddon) {
      await new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve));
      repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
    }

    this.addon._repositoryAddon = repoAddon;
    this.name = this.name || this.addon._repositoryAddon.name;
    this.addon.compatibilityOverrides = repoAddon ?
      repoAddon.compatibilityOverrides :
      null;
    this.addon.appDisabled = !isUsableAddon(this.addon);
    return undefined;
  }

  getIcon(desiredSize = 64) {
    if (!this.addon.icons || !this.file) {
      return null;
    }

    let {icon} = IconDetails.getPreferredIcon(this.addon.icons, null, desiredSize);
    if (icon.startsWith("chrome://")) {
      return icon;
    }
    return buildJarURI(this.file, icon).spec;
  }

  /**
   * This method should be called when the XPI is ready to be installed,
   * i.e., when a download finishes or when a local file has been verified.
   * It should only be called from install() when the install is in
   * STATE_DOWNLOADED (which actually means that the file is available
   * and has been verified).
   */
  checkPrompt() {
    (async () => {
      if (this.promptHandler) {
        let info = {
          existingAddon: this.existingAddon ? this.existingAddon.wrapper : null,
          addon: this.addon.wrapper,
          icon: this.getIcon(),
        };

        try {
          await this.promptHandler(info);
        } catch (err) {
          logger.info(`Install of ${this.addon.id} cancelled by user`);
          this.state = AddonManager.STATE_CANCELLED;
          XPIProvider.removeActiveInstall(this);
          AddonManagerPrivate.callInstallListeners("onInstallCancelled",
                                                   this.listeners, this.wrapper);
          return;
        }
      }
      this.state = AddonManager.STATE_PROMPTS_DONE;
      this.install();
    })();
  }

  /**
   * This method should be called when we have the XPI and any needed
   * permissions prompts have been completed.  If there are any upgrade
   * listeners, they are invoked and the install moves into STATE_POSTPONED.
   * Otherwise, the install moves into STATE_INSTALLING
   */
  checkForBlockers() {
    // If an upgrade listener is registered for this add-on, pass control
    // over the upgrade to the add-on.
    if (AddonManagerPrivate.hasUpgradeListener(this.addon.id)) {
      logger.info(`add-on ${this.addon.id} has an upgrade listener, postponing upgrade until restart`);
      let resumeFn = () => {
        logger.info(`${this.addon.id} has resumed a previously postponed upgrade`);
        this.state = AddonManager.STATE_READY;
        this.install();
      }
      this.postpone(resumeFn);
      return;
    }

    this.state = AddonManager.STATE_READY;
    this.install();
  }

  // TODO This relies on the assumption that we are always installing into the
  // highest priority install location so the resulting add-on will be visible
  // overriding any existing copy in another install location (bug 557710).
  /**
   * Installs the add-on into the install location.
   */
  startInstall() {
    this.state = AddonManager.STATE_INSTALLING;
    if (!AddonManagerPrivate.callInstallListeners("onInstallStarted",
                                                  this.listeners, this.wrapper)) {
      this.state = AddonManager.STATE_DOWNLOADED;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onInstallCancelled",
                                               this.listeners, this.wrapper)
      return;
    }

    // Find and cancel any pending installs for the same add-on in the same
    // install location
    for (let aInstall of XPIProvider.installs) {
      if (aInstall.state == AddonManager.STATE_INSTALLED &&
          aInstall.installLocation == this.installLocation &&
          aInstall.addon.id == this.addon.id) {
        logger.debug("Cancelling previous pending install of " + aInstall.addon.id);
        aInstall.cancel();
      }
    }

    let isUpgrade = this.existingAddon &&
                    this.existingAddon._installLocation == this.installLocation;
    let requiresRestart = XPIProvider.installRequiresRestart(this.addon);

    logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
    AddonManagerPrivate.callAddonListeners("onInstalling",
                                           this.addon.wrapper,
                                           requiresRestart);

    let stagedAddon = this.installLocation.getStagingDir();

    (async () => {
      let installedUnpacked = 0;

      await this.installLocation.requestStagingDir();

      // remove any previously staged files
      await this.unstageInstall(stagedAddon);

      stagedAddon.append(`${this.addon.id}.xpi`);

      installedUnpacked = await this.stageInstall(requiresRestart, stagedAddon, isUpgrade);

      if (requiresRestart) {
        this.state = AddonManager.STATE_INSTALLED;
        AddonManagerPrivate.callInstallListeners("onInstallEnded",
                                                 this.listeners, this.wrapper,
                                                 this.addon.wrapper);
      } else {
        // The install is completed so it should be removed from the active list
        XPIProvider.removeActiveInstall(this);

        // Deactivate and remove the old add-on as necessary
        let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
        if (this.existingAddon) {
          if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
            reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
          else
            reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;

          if (this.existingAddon.bootstrap) {
            let file = this.existingAddon._sourceBundle;
            if (this.existingAddon.active) {
              XPIProvider.callBootstrapMethod(this.existingAddon, file,
                                              "shutdown", reason,
                                              { newVersion: this.addon.version });
            }

            XPIProvider.callBootstrapMethod(this.existingAddon, file,
                                            "uninstall", reason,
                                            { newVersion: this.addon.version });
            XPIProvider.unloadBootstrapScope(this.existingAddon.id);
            flushChromeCaches();
          }

          if (!isUpgrade && this.existingAddon.active) {
            XPIDatabase.updateAddonActive(this.existingAddon, false);
          }
        }

        // Install the new add-on into its final location
        let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
        let file = this.installLocation.installAddon({
          id: this.addon.id,
          source: stagedAddon,
          existingAddonID
        });

        // Update the metadata in the database
        this.addon._sourceBundle = file;
        this.addon.visible = true;

        if (isUpgrade) {
          this.addon =  XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
                                                        file.persistentDescriptor);
          let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
          if (state) {
            state.syncWithDB(this.addon, true);
          } else {
            logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
          }
        } else {
          this.addon.active = (this.addon.visible && !this.addon.disabled);
          this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
          XPIStates.addAddon(this.addon);
          this.addon.installDate = this.addon.updateDate;
          XPIDatabase.saveChanges();
        }
        XPIStates.save();

        let extraParams = {};
        if (this.existingAddon) {
          extraParams.oldVersion = this.existingAddon.version;
        }

        if (this.addon.bootstrap) {
          XPIProvider.callBootstrapMethod(this.addon, file, "install",
                                          reason, extraParams);
        }

        AddonManagerPrivate.callAddonListeners("onInstalled",
                                               this.addon.wrapper);

        logger.debug("Install of " + this.sourceURI.spec + " completed.");
        this.state = AddonManager.STATE_INSTALLED;
        AddonManagerPrivate.callInstallListeners("onInstallEnded",
                                                 this.listeners, this.wrapper,
                                                 this.addon.wrapper);

        if (this.addon.bootstrap) {
          if (this.addon.active) {
            XPIProvider.callBootstrapMethod(this.addon, file, "startup",
                                            reason, extraParams);
          } else {
            // XXX this makes it dangerous to do some things in onInstallEnded
            // listeners because important cleanup hasn't been done yet
            XPIProvider.unloadBootstrapScope(this.addon.id);
          }
        }
        XPIProvider.setTelemetry(this.addon.id, "unpacked", installedUnpacked);
        recordAddonTelemetry(this.addon);

        // Notify providers that a new theme has been enabled.
        if (isTheme(this.addon.type) && this.addon.active)
          AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type, requiresRestart);
      }
    })().catch((e) => {
      logger.warn(`Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`, e);

      if (stagedAddon.exists())
        recursiveRemove(stagedAddon);
      this.state = AddonManager.STATE_INSTALL_FAILED;
      this.error = AddonManager.ERROR_FILE_ACCESS;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callAddonListeners("onOperationCancelled",
                                             this.addon.wrapper);
      AddonManagerPrivate.callInstallListeners("onInstallFailed",
                                               this.listeners,
                                               this.wrapper);
    }).then(() => {
      this.removeTemporaryFile();
      return this.installLocation.releaseStagingDir();
    });
  }

  /**
   * Stages an upgrade for next application restart.
   */
  async stageInstall(restartRequired, stagedAddon, isUpgrade) {
    let stagedJSON = stagedAddon.clone();
    stagedJSON.leafName = this.addon.id + ".json";

    let installedUnpacked = 0;

    // First stage the file regardless of whether restarting is necessary
    if (this.addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
      logger.debug("Addon " + this.addon.id + " will be installed as " +
                   "an unpacked directory");
      stagedAddon.leafName = this.addon.id;
      await OS.File.makeDir(stagedAddon.path);
      await ZipUtils.extractFilesAsync(this.file, stagedAddon);
      installedUnpacked = 1;
    } else {
      logger.debug(`Addon ${this.addon.id} will be installed as a packed xpi`);
      stagedAddon.leafName = this.addon.id + ".xpi";

      await OS.File.copy(this.file.path, stagedAddon.path);
    }

    if (restartRequired) {
      // Point the add-on to its extracted files as the xpi may get deleted
      this.addon._sourceBundle = stagedAddon;

      // Cache the AddonInternal as it may have updated compatibility info
      writeStringToFile(stagedJSON, JSON.stringify(this.addon));

      logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart.");
      if (isUpgrade) {
        delete this.existingAddon.pendingUpgrade;
        this.existingAddon.pendingUpgrade = this.addon;
      }
    }

    return installedUnpacked;
  }

  /**
   * Removes any previously staged upgrade.
   */
  async unstageInstall(stagedAddon) {
    let stagedJSON = getFile(`${this.addon.id}.json`, stagedAddon);
    if (stagedJSON.exists()) {
      stagedJSON.remove(true);
    }

    await removeAsync(getFile(this.addon.id, stagedAddon));

    await removeAsync(getFile(`${this.addon.id}.xpi`, stagedAddon));
  }

  /**
    * Postone a pending update, until restart or until the add-on resumes.
    *
    * @param {Function} resumeFn - a function for the add-on to run
    *                                    when resuming.
    */
  async postpone(resumeFn) {
    this.state = AddonManager.STATE_POSTPONED;

    let stagingDir = this.installLocation.getStagingDir();

    await this.installLocation.requestStagingDir();
    await this.unstageInstall(stagingDir);

    let stagedAddon = getFile(`${this.addon.id}.xpi`, stagingDir);

    await this.stageInstall(true, stagedAddon, true);

    AddonManagerPrivate.callInstallListeners("onInstallPostponed",
                                             this.listeners, this.wrapper)

    // upgrade has been staged for restart, provide a way for it to call the
    // resume function.
    let callback = AddonManagerPrivate.getUpgradeListener(this.addon.id);
    if (callback) {
      callback({
        version: this.version,
        install: () => {
          switch (this.state) {
          case AddonManager.STATE_POSTPONED:
            if (resumeFn) {
              resumeFn();
            }
            break;
          default:
            logger.warn(`${this.addon.id} cannot resume postponed upgrade from state (${this.state})`);
            break;
          }
        },
      });
    }
    // Release the staging directory lock, but since the staging dir is populated
    // it will not be removed until resumed or installed by restart.
    // See also cleanStagingDir()
    this.installLocation.releaseStagingDir();
  }
}

this.LocalAddonInstall = class extends AddonInstall {
  /**
   * Initialises this install to be an install from a local file.
   *
   * @returns Promise
   *          A Promise that resolves when the object is ready to use.
   */
  async init() {
    this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;

    if (!this.file.exists()) {
      logger.warn("XPI file " + this.file.path + " does not exist");
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_NETWORK_FAILURE;
      XPIProvider.removeActiveInstall(this);
      return;
    }

    this.state = AddonManager.STATE_DOWNLOADED;
    this.progress = this.file.fileSize;
    this.maxProgress = this.file.fileSize;

    if (this.hash) {
      let crypto = Cc["@mozilla.org/security/hash;1"].
          createInstance(Ci.nsICryptoHash);
      try {
        crypto.initWithString(this.hash.algorithm);
      } catch (e) {
        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
        this.error = AddonManager.ERROR_INCORRECT_HASH;
        XPIProvider.removeActiveInstall(this);
        return;
      }

      let fis = Cc["@mozilla.org/network/file-input-stream;1"].
          createInstance(Ci.nsIFileInputStream);
      fis.init(this.file, -1, -1, false);
      crypto.updateFromStream(fis, this.file.fileSize);
      let calculatedHash = getHashStringForCrypto(crypto);
      if (calculatedHash != this.hash.data) {
        logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
                    this.hash.data + ")");
        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
        this.error = AddonManager.ERROR_INCORRECT_HASH;
        XPIProvider.removeActiveInstall(this);
        return;
      }
    }

    try {
      await this.loadManifest(this.file);
    } catch ([error, message]) {
      logger.warn("Invalid XPI", message);
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = error;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onNewInstall",
                                               this.listeners,
                                               this.wrapper);
      flushJarCache(this.file);
      return;
    }

    let addon = await new Promise(resolve => {
      XPIDatabase.getVisibleAddonForID(this.addon.id, resolve);
    });

    this.existingAddon = addon;
    this.addon.updateBlocklistState({oldAddon: this.existingAddon});
    this.addon.updateDate = Date.now();
    this.addon.installDate = addon ? addon.installDate : this.addon.updateDate;

    if (!this.addon.isCompatible) {
      this.state = AddonManager.STATE_CHECKING;

      await new Promise(resolve => {
        new UpdateChecker(this.addon, {
          onUpdateFinished: aAddon => {
            this.state = AddonManager.STATE_DOWNLOADED;
            AddonManagerPrivate.callInstallListeners("onNewInstall",
                                                     this.listeners,
                                                     this.wrapper);
            resolve();
          }
        }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
      });
    } else {
      AddonManagerPrivate.callInstallListeners("onNewInstall",
                                               this.listeners,
                                               this.wrapper);

    }
  }

  install() {
    if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
      // For a local install, this state means that verification of the
      // file failed (e.g., the hash or signature or manifest contents
      // were invalid).  It doesn't make sense to retry anything in this
      // case but we have callers who don't know if their AddonInstall
      // object is a local file or a download so accomodate them here.
      AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                               this.listeners, this.wrapper);
      return;
    }
    super.install();
  }
}

this.DownloadAddonInstall = class extends AddonInstall {
  /**
   * Instantiates a DownloadAddonInstall
   *
   * @param  installLocation
   *         The InstallLocation the add-on will be installed into
   * @param  url
   *         The nsIURL to get the add-on from
   * @param  options
   *         Additional options for the install
   * @param  options.hash
   *         An optional hash for the add-on
   * @param  options.existingAddon
   *         The add-on this install will update if known
   * @param  options.browser
   *         The browser performing the install, used to display
   *         authentication prompts.
   * @param  options.name
   *         An optional name for the add-on
   * @param  options.type
   *         An optional type for the add-on
   * @param  options.icons
   *         Optional icons for the add-on
   * @param  options.version
   *         An optional version for the add-on
   * @param  options.promptHandler
   *         A callback to prompt the user before installing.
   */
  constructor(installLocation, url, options = {}) {
    super(installLocation, url, options);

    this.browser = options.browser;

    this.state = AddonManager.STATE_AVAILABLE;

    this.stream = null;
    this.crypto = null;
    this.badCertHandler = null;
    this.restartDownload = false;

    AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
                                            this.wrapper);
  }

  install() {
    switch (this.state) {
    case AddonManager.STATE_AVAILABLE:
      this.startDownload();
      break;
    case AddonManager.STATE_DOWNLOAD_FAILED:
    case AddonManager.STATE_INSTALL_FAILED:
    case AddonManager.STATE_CANCELLED:
      this.removeTemporaryFile();
      this.state = AddonManager.STATE_AVAILABLE;
      this.error = 0;
      this.progress = 0;
      this.maxProgress = -1;
      this.hash = this.originalHash;
      this.startDownload();
      break;
    default:
      super.install();
    }
  }

  cancel() {
    if (this.state == AddonManager.STATE_DOWNLOADING) {
      if (this.channel) {
        logger.debug("Cancelling download of " + this.sourceURI.spec);
        this.channel.cancel(Cr.NS_BINDING_ABORTED);
      }
    } else {
      super.cancel();
    }
  }

  observe(aSubject, aTopic, aData) {
    // Network is going offline
    this.cancel();
  }

  /**
   * Starts downloading the add-on's XPI file.
   */
  startDownload() {
    this.state = AddonManager.STATE_DOWNLOADING;
    if (!AddonManagerPrivate.callInstallListeners("onDownloadStarted",
                                                  this.listeners, this.wrapper)) {
      logger.debug("onDownloadStarted listeners cancelled installation of addon " + this.sourceURI.spec);
      this.state = AddonManager.STATE_CANCELLED;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                               this.listeners, this.wrapper)
      return;
    }

    // If a listener changed our state then do not proceed with the download
    if (this.state != AddonManager.STATE_DOWNLOADING)
      return;

    if (this.channel) {
      // A previous download attempt hasn't finished cleaning up yet, signal
      // that it should restart when complete
      logger.debug("Waiting for previous download to complete");
      this.restartDownload = true;
      return;
    }

    this.openChannel();
  }

  openChannel() {
    this.restartDownload = false;

    try {
      this.file = getTemporaryFile();
      this.ownsTempFile = true;
      this.stream = Cc["@mozilla.org/network/file-output-stream;1"].
                    createInstance(Ci.nsIFileOutputStream);
      this.stream.init(this.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                       FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE, 0);
    } catch (e) {
      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_FILE_ACCESS;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                               this.listeners, this.wrapper);
      return;
    }

    let listener = Cc["@mozilla.org/network/stream-listener-tee;1"].
                   createInstance(Ci.nsIStreamListenerTee);
    listener.init(this, this.stream);
    try {
      let requireBuiltIn = Preferences.get(PREF_INSTALL_REQUIREBUILTINCERTS, true);
      this.badCertHandler = new CertUtils.BadCertHandler(!requireBuiltIn);

      this.channel = NetUtil.newChannel({
        uri: this.sourceURI,
        loadUsingSystemPrincipal: true
      });
      this.channel.notificationCallbacks = this;
      if (this.channel instanceof Ci.nsIHttpChannel) {
        this.channel.setRequestHeader("Moz-XPI-Update", "1", true);
        if (this.channel instanceof Ci.nsIHttpChannelInternal)
          this.channel.forceAllowThirdPartyCookie = true;
      }
      this.channel.asyncOpen2(listener);

      Services.obs.addObserver(this, "network:offline-about-to-go-offline");
    } catch (e) {
      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_NETWORK_FAILURE;
      XPIProvider.removeActiveInstall(this);
      AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                               this.listeners, this.wrapper);
    }
  }

  /**
   * Update the crypto hasher with the new data and call the progress listeners.
   *
   * @see nsIStreamListener
   */
  onDataAvailable(aRequest, aContext, aInputstream, aOffset, aCount) {
    this.crypto.updateFromStream(aInputstream, aCount);
    this.progress += aCount;
    if (!AddonManagerPrivate.callInstallListeners("onDownloadProgress",
                                                  this.listeners, this.wrapper)) {
      // TODO cancel the download and make it available again (bug 553024)
    }
  }

  /**
   * Check the redirect response for a hash of the target XPI and verify that
   * we don't end up on an insecure channel.
   *
   * @see nsIChannelEventSink
   */
  asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback) {
    if (!this.hash && aOldChannel.originalURI.schemeIs("https") &&
        aOldChannel instanceof Ci.nsIHttpChannel) {
      try {
        let hashStr = aOldChannel.getResponseHeader("X-Target-Digest");
        let hashSplit = hashStr.toLowerCase().split(":");
        this.hash = {
          algorithm: hashSplit[0],
          data: hashSplit[1]
        };
      } catch (e) {
      }
    }

    // Verify that we don't end up on an insecure channel if we haven't got a
    // hash to verify with (see bug 537761 for discussion)
    if (!this.hash)
      this.badCertHandler.asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback);
    else
      aCallback.onRedirectVerifyCallback(Cr.NS_OK);

    this.channel = aNewChannel;
  }

  /**
   * This is the first chance to get at real headers on the channel.
   *
   * @see nsIStreamListener
   */
  onStartRequest(aRequest, aContext) {
    this.crypto = Cc["@mozilla.org/security/hash;1"].
                  createInstance(Ci.nsICryptoHash);
    if (this.hash) {
      try {
        this.crypto.initWithString(this.hash.algorithm);
      } catch (e) {
        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
        this.error = AddonManager.ERROR_INCORRECT_HASH;
        XPIProvider.removeActiveInstall(this);
        AddonManagerPrivate.callInstallListeners("onDownloadFailed",
                                                 this.listeners, this.wrapper);
        aRequest.cancel(Cr.NS_BINDING_ABORTED);
        return;
      }
    } else {
      // We always need something to consume data from the inputstream passed
      // to onDataAvailable so just create a dummy cryptohasher to do that.
      this.crypto.initWithString("sha1");
    }

    this.progress = 0;
    if (aRequest instanceof Ci.nsIChannel) {
      try {
        this.maxProgress = aRequest.contentLength;
      } catch (e) {
      }
      logger.debug("Download started for " + this.sourceURI.spec + " to file " +
          this.file.path);
    }
  }

  /**
   * The download is complete.
   *
   * @see nsIStreamListener
   */
  onStopRequest(aRequest, aContext, aStatus) {
    this.stream.close();
    this.channel = null;
    this.badCerthandler = null;
    Services.obs.removeObserver(this, "network:offline-about-to-go-offline");

    // If the download was cancelled then update the state and send events
    if (aStatus == Cr.NS_BINDING_ABORTED) {
      if (this.state == AddonManager.STATE_DOWNLOADING) {
        logger.debug("Cancelled download of " + this.sourceURI.spec);
        this.state = AddonManager.STATE_CANCELLED;
        XPIProvider.removeActiveInstall(this);
        AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                                 this.listeners, this.wrapper);
        // If a listener restarted the download then there is no need to
        // remove the temporary file
        if (this.state != AddonManager.STATE_CANCELLED)
          return;
      }

      this.removeTemporaryFile();
      if (this.restartDownload)
        this.openChannel();
      return;
    }

    logger.debug("Download of " + this.sourceURI.spec + " completed.");

    if (Components.isSuccessCode(aStatus)) {
      if (!(aRequest instanceof Ci.nsIHttpChannel) || aRequest.requestSucceeded) {
        if (!this.hash && (aRequest instanceof Ci.nsIChannel)) {
          try {
            CertUtils.checkCert(aRequest,
                                !Preferences.get(PREF_INSTALL_REQUIREBUILTINCERTS, true));
          } catch (e) {
            this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, e);
            return;
          }
        }

        // convert the binary hash data to a hex string.
        let calculatedHash = getHashStringForCrypto(this.crypto);
        this.crypto = null;
        if (this.hash && calculatedHash != this.hash.data) {
          this.downloadFailed(AddonManager.ERROR_INCORRECT_HASH,
                              "Downloaded file hash (" + calculatedHash +
                              ") did not match provided hash (" + this.hash.data + ")");
          return;
        }

        this.loadManifest(this.file).then(() => {
          if (this.addon.isCompatible) {
            this.downloadCompleted();
          } else {
            // TODO Should we send some event here (bug 557716)?
            this.state = AddonManager.STATE_CHECKING;
            new UpdateChecker(this.addon, {
              onUpdateFinished: aAddon => this.downloadCompleted(),
            }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
          }
        }, ([error, message]) => {
          this.removeTemporaryFile();
          this.downloadFailed(error, message);
        });
      } else if (aRequest instanceof Ci.nsIHttpChannel) {
        this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE,
                            aRequest.responseStatus + " " +
                            aRequest.responseStatusText);
      } else {
        this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
      }
    } else {
      this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
    }
  }

  /**
   * Notify listeners that the download failed.
   *
   * @param  aReason
   *         Something to log about the failure
   * @param  error
   *         The error code to pass to the listeners
   */
  downloadFailed(aReason, aError) {
    logger.warn("Download of " + this.sourceURI.spec + " failed", aError);
    this.state = AddonManager.STATE_DOWNLOAD_FAILED;
    this.error = aReason;
    XPIProvider.removeActiveInstall(this);
    AddonManagerPrivate.callInstallListeners("onDownloadFailed", this.listeners,
                                             this.wrapper);

    // If the listener hasn't restarted the download then remove any temporary
    // file
    if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
      logger.debug("downloadFailed: removing temp file for " + this.sourceURI.spec);
      this.removeTemporaryFile();
    } else
      logger.debug("downloadFailed: listener changed AddonInstall state for " +
          this.sourceURI.spec + " to " + this.state);
  }

  /**
   * Notify listeners that the download completed.
   */
  downloadCompleted() {
    XPIDatabase.getVisibleAddonForID(this.addon.id, aAddon => {
      if (aAddon)
        this.existingAddon = aAddon;

      this.state = AddonManager.STATE_DOWNLOADED;
      this.addon.updateDate = Date.now();

      if (this.existingAddon) {
        this.addon.existingAddonID = this.existingAddon.id;
        this.addon.installDate = this.existingAddon.installDate;
      } else {
        this.addon.installDate = this.addon.updateDate;
      }
      this.addon.updateBlocklistState({oldAddon: this.existingAddon});

      if (AddonManagerPrivate.callInstallListeners("onDownloadEnded",
                                                   this.listeners,
                                                   this.wrapper)) {
        // If a listener changed our state then do not proceed with the install
        if (this.state != AddonManager.STATE_DOWNLOADED)
          return;

        // proceed with the install state machine.
        this.install();
      }
    });
  }

  getInterface(iid) {
    if (iid.equals(Ci.nsIAuthPrompt2)) {
      let win = null;
      if (this.browser) {
        win = this.browser.contentWindow || this.browser.ownerGlobal;
      }

      let factory = Cc["@mozilla.org/prompter;1"].
                    getService(Ci.nsIPromptFactory);
      let prompt = factory.getPrompt(win, Ci.nsIAuthPrompt2);

      if (this.browser && prompt instanceof Ci.nsILoginManagerPrompter)
        prompt.browser = this.browser;

      return prompt;
    } else if (iid.equals(Ci.nsIChannelEventSink)) {
      return this;
    }

    return this.badCertHandler.getInterface(iid);
  }
}

/**
 * This class exists just for the specific case of staged add-ons that
 * fail to install at startup.  When that happens, the add-on remains
 * staged but we want to keep track of it like other installs so that we
 * can clean it up if the same add-on is installed again (see the comment
 * about "pending installs for the same add-on" in AddonInstall.startInstall)
 */
this.StagedAddonInstall = class extends AddonInstall {
  constructor(installLocation, dir, manifest) {
    super(installLocation, dir);

    this.name = manifest.name;
    this.type = manifest.type;
    this.version = manifest.version;
    this.icons = manifest.icons;
    this.releaseNotesURI = manifest.releaseNotesURI ?
                           Services.io.newURI(manifest.releaseNotesURI) :
                           null;
    this.sourceURI = manifest.sourceURI ?
                     Services.io.newURI(manifest.sourceURI) :
                     null;
    this.file = null;
    this.addon = manifest;

    this.state = AddonManager.STATE_INSTALLED;
  }
}

/**
 * Creates a new AddonInstall for an update.
 *
 * @param  aCallback
 *         The callback to pass the new AddonInstall to
 * @param  aAddon
 *         The add-on being updated
 * @param  aUpdate
 *         The metadata about the new version from the update manifest
 */
function createUpdate(aCallback, aAddon, aUpdate) {
  let url = Services.io.newURI(aUpdate.updateURL);

  (async function() {
    let opts = {
      hash: aUpdate.updateHash,
      existingAddon: aAddon,
      name: aAddon.selectedLocale.name,
      type: aAddon.type,
      icons: aAddon.icons,
      version: aUpdate.version,
    };
    let install;
    if (url instanceof Ci.nsIFileURL) {
      install = new LocalAddonInstall(aAddon._installLocation, url, opts);
      await install.init();
    } else {
      install = new DownloadAddonInstall(aAddon._installLocation, url, opts);
    }
    try {
      if (aUpdate.updateInfoURL)
        install.releaseNotesURI = Services.io.newURI(escapeAddonURI(aAddon, aUpdate.updateInfoURL));
    } catch (e) {
      // If the releaseNotesURI cannot be parsed then just ignore it.
    }

    aCallback(install);
  })();
}

// Maps instances of AddonInstall to AddonInstallWrapper
const wrapperMap = new WeakMap();
let installFor = wrapper => wrapperMap.get(wrapper);

/**
 * Creates a wrapper for an AddonInstall that only exposes the public API
 *
 * @param  install
 *         The AddonInstall to create a wrapper for
 */
function AddonInstallWrapper(aInstall) {
  wrapperMap.set(this, aInstall);
}

AddonInstallWrapper.prototype = {
  get __AddonInstallInternal__() {
    return AppConstants.DEBUG ? installFor(this) : undefined;
  },

  get type() {
    return getExternalType(installFor(this).type);
  },

  get iconURL() {
    return installFor(this).icons[32];
  },

  get existingAddon() {
    let install = installFor(this);
    return install.existingAddon ? install.existingAddon.wrapper : null;
  },

  get addon() {
    let install = installFor(this);
    return install.addon ? install.addon.wrapper : null;
  },

  get sourceURI() {
    return installFor(this).sourceURI;
  },

  set promptHandler(handler) {
    installFor(this).promptHandler = handler;
  },

  install() {
    installFor(this).install();
  },

  cancel() {
    installFor(this).cancel();
  },

  addListener(listener) {
    installFor(this).addListener(listener);
  },

  removeListener(listener) {
    installFor(this).removeListener(listener);
  },
};

["name", "version", "icons", "releaseNotesURI", "file", "state", "error",
 "progress", "maxProgress", "certificate", "certName"].forEach(function(aProp) {
  Object.defineProperty(AddonInstallWrapper.prototype, aProp, {
    get() {
      return installFor(this)[aProp];
    },
    enumerable: true,
  });
});

/**
 * Creates a new update checker.
 *
 * @param  aAddon
 *         The add-on to check for updates
 * @param  aListener
 *         An UpdateListener to notify of updates
 * @param  aReason
 *         The reason for the update check
 * @param  aAppVersion
 *         An optional application version to check for updates for
 * @param  aPlatformVersion
 *         An optional platform version to check for updates for
 * @throws if the aListener or aReason arguments are not valid
 */
this.UpdateChecker = function(aAddon, aListener, aReason, aAppVersion, aPlatformVersion) {
  if (!aListener || !aReason)
    throw Cr.NS_ERROR_INVALID_ARG;

  Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");

  this.addon = aAddon;
  aAddon._updateCheck = this;
  XPIProvider.doing(this);
  this.listener = aListener;
  this.appVersion = aAppVersion;
  this.platformVersion = aPlatformVersion;
  this.syncCompatibility = (aReason == AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);

  let updateURL = aAddon.updateURL;
  if (!updateURL) {
    if (aReason == AddonManager.UPDATE_WHEN_PERIODIC_UPDATE &&
        Services.prefs.getPrefType(PREF_EM_UPDATE_BACKGROUND_URL) == Services.prefs.PREF_STRING) {
      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL);
    } else {
      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_URL);
    }
  }

  const UPDATE_TYPE_COMPATIBILITY = 32;
  const UPDATE_TYPE_NEWVERSION = 64;

  aReason |= UPDATE_TYPE_COMPATIBILITY;
  if ("onUpdateAvailable" in this.listener)
    aReason |= UPDATE_TYPE_NEWVERSION;

  let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
  this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
                                                    url, this);
}

UpdateChecker.prototype = {
  addon: null,
  listener: null,
  appVersion: null,
  platformVersion: null,
  syncCompatibility: null,

  /**
   * Calls a method on the listener passing any number of arguments and
   * consuming any exceptions.
   *
   * @param  aMethod
   *         The method to call on the listener
   */
  callListener(aMethod, ...aArgs) {
    if (!(aMethod in this.listener))
      return;

    try {
      this.listener[aMethod].apply(this.listener, aArgs);
    } catch (e) {
      logger.warn("Exception calling UpdateListener method " + aMethod, e);
    }
  },

  /**
   * Called when AddonUpdateChecker completes the update check
   *
   * @param  updates
   *         The list of update details for the add-on
   */
  onUpdateCheckComplete(aUpdates) {
    XPIProvider.done(this.addon._updateCheck);
    this.addon._updateCheck = null;
    let AUC = AddonUpdateChecker;

    let ignoreMaxVersion = false;
    let ignoreStrictCompat = false;
    if (!AddonManager.checkCompatibility) {
      ignoreMaxVersion = true;
      ignoreStrictCompat = true;
    } else if (this.addon.type in COMPATIBLE_BY_DEFAULT_TYPES &&
               !AddonManager.strictCompatibility &&
               !this.addon.strictCompatibility &&
               !this.addon.hasBinaryComponents) {
      ignoreMaxVersion = true;
    }

    // Always apply any compatibility update for the current version
    let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
                                                  this.syncCompatibility,
                                                  null, null,
                                                  ignoreMaxVersion,
                                                  ignoreStrictCompat);
    // Apply the compatibility update to the database
    if (compatUpdate)
      this.addon.applyCompatibilityUpdate(compatUpdate, this.syncCompatibility);

    // If the request is for an application or platform version that is
    // different to the current application or platform version then look for a
    // compatibility update for those versions.
    if ((this.appVersion &&
         Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) ||
        (this.platformVersion &&
         Services.vc.compare(this.platformVersion, Services.appinfo.platformVersion) != 0)) {
      compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
                                                false, this.appVersion,
                                                this.platformVersion,
                                                ignoreMaxVersion,
                                                ignoreStrictCompat);
    }

    if (compatUpdate)
      this.callListener("onCompatibilityUpdateAvailable", this.addon.wrapper);
    else
      this.callListener("onNoCompatibilityUpdateAvailable", this.addon.wrapper);

    function sendUpdateAvailableMessages(aSelf, aInstall) {
      if (aInstall) {
        aSelf.callListener("onUpdateAvailable", aSelf.addon.wrapper,
                           aInstall.wrapper);
      } else {
        aSelf.callListener("onNoUpdateAvailable", aSelf.addon.wrapper);
      }
      aSelf.callListener("onUpdateFinished", aSelf.addon.wrapper,
                         AddonManager.UPDATE_STATUS_NO_ERROR);
    }

    let compatOverrides = AddonManager.strictCompatibility ?
                            null :
                            this.addon.compatibilityOverrides;

    let update = AUC.getNewestCompatibleUpdate(aUpdates,
                                           this.appVersion,
                                           this.platformVersion,
                                           ignoreMaxVersion,
                                           ignoreStrictCompat,
                                           compatOverrides);

    if (update && Services.vc.compare(this.addon.version, update.version) < 0
        && !this.addon._installLocation.locked) {
      for (let currentInstall of XPIProvider.installs) {
        // Skip installs that don't match the available update
        if (currentInstall.existingAddon != this.addon ||
            currentInstall.version != update.version)
          continue;

        // If the existing install has not yet started downloading then send an
        // available update notification. If it is already downloading then
        // don't send any available update notification
        if (currentInstall.state == AddonManager.STATE_AVAILABLE) {
          logger.debug("Found an existing AddonInstall for " + this.addon.id);
          sendUpdateAvailableMessages(this, currentInstall);
        } else
          sendUpdateAvailableMessages(this, null);
        return;
      }

      createUpdate(aInstall => {
        sendUpdateAvailableMessages(this, aInstall);
      }, this.addon, update);
    } else {
      sendUpdateAvailableMessages(this, null);
    }
  },

  /**
   * Called when AddonUpdateChecker fails the update check
   *
   * @param  aError
   *         An error status
   */
  onUpdateCheckError(aError) {
    XPIProvider.done(this.addon._updateCheck);
    this.addon._updateCheck = null;
    this.callListener("onNoCompatibilityUpdateAvailable", this.addon.wrapper);
    this.callListener("onNoUpdateAvailable", this.addon.wrapper);
    this.callListener("onUpdateFinished", this.addon.wrapper, aError);
  },

  /**
   * Called to cancel an in-progress update check
   */
  cancel() {
    let parser = this._parser;
    if (parser) {
      this._parser = null;
      // This will call back to onUpdateCheckError with a CANCELLED error
      parser.cancel();
    }
  }
};
PK
!<Wrrmodules/addons/XPIProvider.jsm /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ["XPIProvider", "XPIInternal"];

/* globals WebExtensionPolicy */

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/Preferences.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonSettings",
                                  "resource://gre/modules/addons/AddonSettings.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
                                  "resource://gre/modules/ChromeManifestParser.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                  "resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                  "resource://gre/modules/Locale.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
                                  "resource://gre/modules/ZipUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
                                  "resource://gre/modules/PermissionsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                  "resource://gre/modules/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ProductAddonChecker",
                                  "resource://gre/modules/addons/ProductAddonChecker.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "isAddonPartOfE10SRollout",
                                  "resource://gre/modules/addons/E10SAddonsRollout.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                  "resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LegacyExtensionsUtils",
                                  "resource://gre/modules/LegacyExtensionsUtils.jsm");

/* globals DownloadAddonInstall, LocalAddonInstall, StagedAddonInstall, UpdateChecker, loadManifestFromFile, verifyBundleSignedState */
for (let sym of [
  "DownloadAddonInstall",
  "LocalAddonInstall",
  "StagedAddonInstall",
  "UpdateChecker",
  "loadManifestFromFile",
  "verifyBundleSignedState",
]) {
  XPCOMUtils.defineLazyModuleGetter(this, sym, "resource://gre/modules/addons/XPIInstall.jsm");
}

const {nsIBlocklistService} = Ci;
XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
                                   "@mozilla.org/extensions/blocklist;1",
                                   "nsIBlocklistService");
XPCOMUtils.defineLazyServiceGetter(this,
                                   "ChromeRegistry",
                                   "@mozilla.org/chrome/chrome-registry;1",
                                   "nsIChromeRegistry");
XPCOMUtils.defineLazyServiceGetter(this,
                                   "ResProtocolHandler",
                                   "@mozilla.org/network/protocol;1?name=resource",
                                   "nsIResProtocolHandler");
XPCOMUtils.defineLazyServiceGetter(this,
                                   "AddonPolicyService",
                                   "@mozilla.org/addons/policy-service;1",
                                   "nsIAddonPolicyService");
XPCOMUtils.defineLazyServiceGetter(this,
                                   "AddonPathService",
                                   "@mozilla.org/addon-path-service;1",
                                   "amIAddonPathService");
XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
                                   "@mozilla.org/addons/addon-manager-startup;1",
                                   "amIAddonManagerStartup");

Cu.importGlobalProperties(["URL"]);

const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                       "initWithPath");

const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
const PREF_XPI_STATE                  = "extensions.xpiState";
const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
const PREF_SKIN_SWITCHPENDING         = "extensions.dss.switchPending";
const PREF_SKIN_TO_SELECT             = "extensions.lastSelectedSkin";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_EXTENSION_FORMAT        = "extensions.";
const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
const PREF_EM_STARTUP_SCAN_SCOPES     = "extensions.startupScanScopes";
const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
const PREF_XPI_ENABLED                = "xpinstall.enabled";
const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
// xpinstall.signatures.required only supported in dev builds
const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
const PREF_E10S_BLOCK_ENABLE          = "extensions.e10sBlocksEnabling";
const PREF_E10S_ADDON_BLOCKLIST       = "extensions.e10s.rollout.blocklist";
const PREF_E10S_ADDON_POLICY          = "extensions.e10s.rollout.policy";
const PREF_E10S_HAS_NONEXEMPT_ADDON   = "extensions.e10s.rollout.hasAddon";
const PREF_ALLOW_LEGACY               = "extensions.legacy.enabled";
const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";

const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";

const PREF_CHECKCOMAT_THEMEOVERRIDE   = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";

const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
const PREF_EM_LAST_APP_BUILD_ID       = "extensions.lastAppBuildId";

const OBSOLETE_PREFERENCES = [
  "extensions.bootstrappedAddons",
  "extensions.enabledAddons",
  "extensions.xpiState",
  "extensions.installCache",
];

const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";

const STRING_TYPE_NAME                = "type.%ID%.name";

const DIR_EXTENSIONS                  = "extensions";
const DIR_SYSTEM_ADDONS               = "features";
const DIR_STAGE                       = "staged";
const DIR_TRASH                       = "trash";

const FILE_XPI_STATES                 = "addonStartup.json.lz4";
const FILE_DATABASE                   = "extensions.json";
const FILE_OLD_CACHE                  = "extensions.cache";
const FILE_RDF_MANIFEST               = "install.rdf";
const FILE_WEB_MANIFEST               = "manifest.json";
const FILE_XPI_ADDONS_LIST            = "extensions.ini";

const ADDON_ID_DEFAULT_THEME          = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";

const KEY_PROFILEDIR                  = "ProfD";
const KEY_ADDON_APP_DIR               = "XREAddonAppDir";
const KEY_APP_DISTRIBUTION            = "XREAppDist";
const KEY_APP_FEATURES                = "XREAppFeat";

const KEY_APP_PROFILE                 = "app-profile";
const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
const KEY_APP_GLOBAL                  = "app-global";
const KEY_APP_SYSTEM_LOCAL            = "app-system-local";
const KEY_APP_SYSTEM_SHARE            = "app-system-share";
const KEY_APP_SYSTEM_USER             = "app-system-user";
const KEY_APP_TEMPORARY               = "app-temporary";

const TEMPORARY_ADDON_SUFFIX = "@temporary-addon";

const STARTUP_MTIME_SCOPES = [KEY_APP_GLOBAL,
                              KEY_APP_SYSTEM_LOCAL,
                              KEY_APP_SYSTEM_SHARE,
                              KEY_APP_SYSTEM_USER];

const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
const XPI_PERMISSION                  = "install";

const TOOLKIT_ID                      = "toolkit@mozilla.org";

const XPI_SIGNATURE_CHECK_PERIOD      = 24 * 60 * 60;

XPCOMUtils.defineConstant(this, "DB_SCHEMA", 22);

XPCOMUtils.defineLazyPreferenceGetter(this, "ALLOW_NON_MPC", PREF_ALLOW_NON_MPC);

const NOTIFICATION_TOOLBOX_CONNECTION_CHANGE      = "toolbox-connection-change";

// Properties that exist in the install manifest
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];

// Properties to cache and reload when an addon installation is pending
const PENDING_INSTALL_METADATA =
    ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
     "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
     "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];

// Note: When adding/changing/removing items here, remember to change the
// DB schema version to ensure changes are picked up ASAP.
const STATIC_BLOCKLIST_PATTERNS = [
  { creator: "Mozilla Corp.",
    level: nsIBlocklistService.STATE_BLOCKED,
    blockID: "i162" },
  { creator: "Mozilla.org",
    level: nsIBlocklistService.STATE_BLOCKED,
    blockID: "i162" }
];

function encoded(strings, ...values) {
  let result = [];

  for (let [i, string] of strings.entries()) {
    result.push(string);
    if (i < values.length)
      result.push(encodeURIComponent(values[i]));
  }

  return result.join("");
}

const BOOTSTRAP_REASONS = {
  APP_STARTUP: 1,
  APP_SHUTDOWN: 2,
  ADDON_ENABLE: 3,
  ADDON_DISABLE: 4,
  ADDON_INSTALL: 5,
  ADDON_UNINSTALL: 6,
  ADDON_UPGRADE: 7,
  ADDON_DOWNGRADE: 8
};

// Some add-on types that we track internally are presented as other types
// externally
const TYPE_ALIASES = {
  "apiextension": "extension",
  "webextension": "extension",
  "webextension-theme": "theme",
};

const CHROME_TYPES = new Set([
  "extension",
  "locale",
  "experiment",
]);

const SIGNED_TYPES = new Set([
  "apiextension",
  "extension",
  "experiment",
  "webextension",
  "webextension-theme",
]);

const LEGACY_TYPES = new Set([
  "apiextension",
  "extension",
  "theme",
]);

const ALL_EXTERNAL_TYPES = new Set([
  "dictionary",
  "extension",
  "experiment",
  "locale",
  "theme",
]);

// Whether add-on signing is required.
function mustSign(aType) {
  if (!SIGNED_TYPES.has(aType))
    return false;

  return AddonSettings.REQUIRE_SIGNING;
}

// Keep track of where we are in startup for telemetry
// event happened during XPIDatabase.startup()
const XPI_STARTING = "XPIStarting";
// event happened after startup() but before the final-ui-startup event
const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup";
// event happened after final-ui-startup
const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup";

const COMPATIBLE_BY_DEFAULT_TYPES = {
  extension: true,
  dictionary: true
};

const MSG_JAR_FLUSH = "AddonJarFlush";
const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";

var gGlobalScope = this;

/**
 * Valid IDs fit this pattern.
 */
var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi";

// Create a new logger for use by all objects in this Addons XPI Provider module
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"];
/* globals XPIDatabase, XPIDatabaseReconcile*/

var gLazyObjectsLoaded = false;

XPCOMUtils.defineLazyGetter(this, "gStartupScanScopes", () => {
  let appBuildID = Services.appinfo.appBuildID;
  let oldAppBuildID = Services.prefs.getCharPref(PREF_EM_LAST_APP_BUILD_ID, "");
  Services.prefs.setCharPref(PREF_EM_LAST_APP_BUILD_ID, appBuildID);
  if (appBuildID !== oldAppBuildID) {
    // If the build id changed, scan all scopes
    return AddonManager.SCOPE_ALL;
  }

  return Services.prefs.getIntPref(PREF_EM_STARTUP_SCAN_SCOPES, 0);
});

function loadLazyObjects() {
  let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
  let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
    sandboxName: uri,
    wantGlobalProperties: ["TextDecoder"],
  });

  Object.assign(scope, {
    ADDON_SIGNING: AddonSettings.ADDON_SIGNING,
    SIGNED_TYPES,
    BOOTSTRAP_REASONS,
    DB_SCHEMA,
    AddonInternal,
    XPIProvider,
    XPIStates,
    syncLoadManifestFromFile,
    isUsableAddon,
    recordAddonTelemetry,
    flushChromeCaches,
    descriptorToPath,
  });

  Services.scriptloader.loadSubScript(uri, scope);

  for (let name of LAZY_OBJECTS) {
    delete gGlobalScope[name];
    gGlobalScope[name] = scope[name];
  }
  gLazyObjectsLoaded = true;
  return scope;
}

LAZY_OBJECTS.forEach(name => {
  Object.defineProperty(gGlobalScope, name, {
    get() {
      let objs = loadLazyObjects();
      return objs[name];
    },
    configurable: true
  });
});

/**
 * Returns a nsIFile instance for the given path, relative to the given
 * base file, if provided.
 *
 * @param {string} path
 *        The (possibly relative) path of the file.
 * @param {nsIFile} [base]
 *        An optional file to use as a base path if `path` is relative.
 * @returns {nsIFile}
 */
function getFile(path, base = null) {
  // First try for an absolute path, as we get in the case of proxy
  // files. Ideally we would try a relative path first, but on Windows,
  // paths which begin with a drive letter are valid as relative paths,
  // and treated as such.
  try {
    return new nsIFile(path);
  } catch (e) {
    // Ignore invalid relative paths. The only other error we should see
    // here is EOM, and either way, any errors that we care about should
    // be re-thrown below.
  }

  // If the path isn't absolute, we must have a base path.
  let file = base.clone();
  file.appendRelativePath(path);
  return file;
}

/**
 * Returns the modification time of the given file, or 0 if the file
 * does not exist, or cannot be accessed.
 *
 * @param {nsIFile} file
 *        The file to retrieve the modification time for.
 * @returns {double}
 *        The file's modification time, in seconds since the Unix epoch.
 */
function tryGetMtime(file) {
  try {
    return file.lastModifiedTime;
  } catch (e) {
    return 0;
  }
}

/**
 * Returns the path to `file` relative to the directory `dir`, or an
 * absolute path to the file if no directory is passed, or the file is
 * not a descendant of it.
 *
 * @param {nsIFile} file
 *        The file to return a relative path to.
 * @param {nsIFile} [dir]
 *        If passed, return a path relative to this directory.
 * @returns {string}
 */
function getRelativePath(file, dir) {
  if (dir && dir.contains(file)) {
    let path = file.getRelativePath(dir);
    if (AppConstants.platform == "win") {
      path = path.replace(/\//g, "\\");
    }
    return path;
  }
  return file.path;
}

/**
 * Converts the given opaque descriptor string into an ordinary path
 * string. In practice, the path string is always exactly equal to the
 * descriptor string, but theoretically may not have been on some legacy
 * systems.
 *
 * @param {string} descriptor
 *        The opaque descriptor string to convert.
 * @param {nsIFile} [dir]
 *        If passed, return a path relative to this directory.
 * @returns {string}
 *        The file's path.
 */
function descriptorToPath(descriptor, dir) {
  try {
    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    file.persistentDescriptor = descriptor;
    return getRelativePath(file, dir);
  } catch (e) {
    return null;
  }
}


// Behaves like Promise.all except waits for all promises to resolve/reject
// before resolving/rejecting itself
function waitForAllPromises(promises) {
  return new Promise((resolve, reject) => {
    let shouldReject = false;
    let rejectValue = null;

    let newPromises = promises.map(
      p => p.catch(value => {
        shouldReject = true;
        rejectValue = value;
      })
    );
    Promise.all(newPromises)
           .then((results) => shouldReject ? reject(rejectValue) : resolve(results));
  });
}

function findMatchingStaticBlocklistItem(aAddon) {
  for (let item of STATIC_BLOCKLIST_PATTERNS) {
    if ("creator" in item && typeof item.creator == "string") {
      if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
          (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) {
        return item;
      }
    }
  }
  return null;
}

/**
 * Converts an iterable of addon objects into a map with the add-on's ID as key.
 */
function addonMap(addons) {
  return new Map(addons.map(a => [a.id, a]));
}

/**
 * Helper function that determines whether an addon of a certain type is a
 * WebExtension.
 *
 * @param  {String} type
 * @return {Boolean}
 */
function isWebExtension(type) {
  return type == "webextension" || type == "webextension-theme";
}

var gThemeAliases = null;
/**
 * Helper function that determines whether an addon of a certain type is a
 * theme.
 *
 * @param  {String} type
 * @return {Boolean}
 */
function isTheme(type) {
  if (!gThemeAliases)
    gThemeAliases = getAllAliasesForTypes(["theme"]);
  return gThemeAliases.includes(type);
}

/**
 * Sets permissions on a file
 *
 * @param  aFile
 *         The file or directory to operate on.
 * @param  aPermissions
 *         The permisions to set
 */
function setFilePermissions(aFile, aPermissions) {
  try {
    aFile.permissions = aPermissions;
  } catch (e) {
    logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
         aFile.path, e);
  }
}

/**
 * Write a given string to a file
 *
 * @param  file
 *         The nsIFile instance to write into
 * @param  string
 *         The string to write
 */
function writeStringToFile(file, string) {
  let stream = Cc["@mozilla.org/network/file-output-stream;1"].
               createInstance(Ci.nsIFileOutputStream);
  let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
                  createInstance(Ci.nsIConverterOutputStream);

  try {
    stream.init(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                            FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,
                           0);
    converter.init(stream, "UTF-8");
    converter.writeString(string);
  } finally {
    converter.close();
    stream.close();
  }
}

/**
 * A safe way to install a file or the contents of a directory to a new
 * directory. The file or directory is moved or copied recursively and if
 * anything fails an attempt is made to rollback the entire operation. The
 * operation may also be rolled back to its original state after it has
 * completed by calling the rollback method.
 *
 * Operations can be chained. Calling move or copy multiple times will remember
 * the whole set and if one fails all of the operations will be rolled back.
 */
function SafeInstallOperation() {
  this._installedFiles = [];
  this._createdDirs = [];
}

SafeInstallOperation.prototype = {
  _installedFiles: null,
  _createdDirs: null,

  _installFile(aFile, aTargetDirectory, aCopy) {
    let oldFile = aCopy ? null : aFile.clone();
    let newFile = aFile.clone();
    try {
      if (aCopy) {
        newFile.copyTo(aTargetDirectory, null);
        // copyTo does not update the nsIFile with the new.
        newFile = getFile(aFile.leafName, aTargetDirectory);
        // Windows roaming profiles won't properly sync directories if a new file
        // has an older lastModifiedTime than a previous file, so update.
        newFile.lastModifiedTime = Date.now();
      } else {
        newFile.moveTo(aTargetDirectory, null);
      }
    } catch (e) {
      logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
            " to " + aTargetDirectory.path, e);
      throw e;
    }
    this._installedFiles.push({ oldFile, newFile });
  },

  _installDirectory(aDirectory, aTargetDirectory, aCopy) {
    if (aDirectory.contains(aTargetDirectory)) {
      let err = new Error(`Not installing ${aDirectory} into its own descendent ${aTargetDirectory}`);
      logger.error(err);
      throw err;
    }

    let newDir = getFile(aDirectory.leafName, aTargetDirectory);
    try {
      newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
    } catch (e) {
      logger.error("Failed to create directory " + newDir.path, e);
      throw e;
    }
    this._createdDirs.push(newDir);

    // Use a snapshot of the directory contents to avoid possible issues with
    // iterating over a directory while removing files from it (the YAFFS2
    // embedded filesystem has this issue, see bug 772238), and to remove
    // normal files before their resource forks on OSX (see bug 733436).
    let entries = getDirectoryEntries(aDirectory, true);
    for (let entry of entries) {
      try {
        this._installDirEntry(entry, newDir, aCopy);
      } catch (e) {
        logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " +
                     entry.path, e);
        throw e;
      }
    }

    // If this is only a copy operation then there is nothing else to do
    if (aCopy)
      return;

    // The directory should be empty by this point. If it isn't this will throw
    // and all of the operations will be rolled back
    try {
      setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY);
      aDirectory.remove(false);
    } catch (e) {
      logger.error("Failed to remove directory " + aDirectory.path, e);
      throw e;
    }

    // Note we put the directory move in after all the file moves so the
    // directory is recreated before all the files are moved back
    this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
  },

  _installDirEntry(aDirEntry, aTargetDirectory, aCopy) {
    let isDir = null;

    try {
      isDir = aDirEntry.isDirectory() && !aDirEntry.isSymlink();
    } catch (e) {
      // If the file has already gone away then don't worry about it, this can
      // happen on OSX where the resource fork is automatically moved with the
      // data fork for the file. See bug 733436.
      if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
        return;

      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
            " to " + aTargetDirectory.path);
      throw e;
    }

    try {
      if (isDir)
        this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
      else
        this._installFile(aDirEntry, aTargetDirectory, aCopy);
    } catch (e) {
      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
            " to " + aTargetDirectory.path);
      throw e;
    }
  },

  /**
   * Moves a file or directory into a new directory. If an error occurs then all
   * files that have been moved will be moved back to their original location.
   *
   * @param  aFile
   *         The file or directory to be moved.
   * @param  aTargetDirectory
   *         The directory to move into, this is expected to be an empty
   *         directory.
   */
  moveUnder(aFile, aTargetDirectory) {
    try {
      this._installDirEntry(aFile, aTargetDirectory, false);
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Renames a file to a new location.  If an error occurs then all
   * files that have been moved will be moved back to their original location.
   *
   * @param  aOldLocation
   *         The old location of the file.
   * @param  aNewLocation
   *         The new location of the file.
   */
  moveTo(aOldLocation, aNewLocation) {
    try {
      let oldFile = aOldLocation.clone(), newFile = aNewLocation.clone();
      oldFile.moveTo(newFile.parent, newFile.leafName);
      this._installedFiles.push({ oldFile, newFile, isMoveTo: true});
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Copies a file or directory into a new directory. If an error occurs then
   * all new files that have been created will be removed.
   *
   * @param  aFile
   *         The file or directory to be copied.
   * @param  aTargetDirectory
   *         The directory to copy into, this is expected to be an empty
   *         directory.
   */
  copy(aFile, aTargetDirectory) {
    try {
      this._installDirEntry(aFile, aTargetDirectory, true);
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Rolls back all the moves that this operation performed. If an exception
   * occurs here then both old and new directories are left in an indeterminate
   * state
   */
  rollback() {
    while (this._installedFiles.length > 0) {
      let move = this._installedFiles.pop();
      if (move.isMoveTo) {
        move.newFile.moveTo(move.oldDir.parent, move.oldDir.leafName);
      } else if (move.newFile.isDirectory() && !move.newFile.isSymlink()) {
        let oldDir = getFile(move.oldFile.leafName, move.oldFile.parent);
        oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
      } else if (!move.oldFile) {
        // No old file means this was a copied file
        move.newFile.remove(true);
      } else {
        move.newFile.moveTo(move.oldFile.parent, null);
      }
    }

    while (this._createdDirs.length > 0)
      recursiveRemove(this._createdDirs.pop());
  }
};

/**
 * Evaluates whether an add-on is allowed to run in safe mode.
 *
 * @param  aAddon
 *         The add-on to check
 * @return true if the add-on should run in safe mode
 */
function canRunInSafeMode(aAddon) {
  // Even though the updated system add-ons aren't generally run in safe mode we
  // include them here so their uninstall functions get called when switching
  // back to the default set.

  // TODO product should make the call about temporary add-ons running
  // in safe mode. assuming for now that they are.
  if (aAddon._installLocation.name == KEY_APP_TEMPORARY)
    return true;

  return aAddon._installLocation.isSystem;
}

/**
 * Calculates whether an add-on should be appDisabled or not.
 *
 * @param  aAddon
 *         The add-on to check
 * @return true if the add-on should not be appDisabled
 */
function isUsableAddon(aAddon) {
  // Hack to ensure the default theme is always usable
  if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
    return true;

  if (mustSign(aAddon.type) && !aAddon.isCorrectlySigned) {
    logger.warn(`Add-on ${aAddon.id} is not correctly signed.`);
    if (Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false)) {
      logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`);
    }
    return false;
  }

  if (aAddon.blocklistState == nsIBlocklistService.STATE_BLOCKED) {
    logger.warn(`Add-on ${aAddon.id} is blocklisted.`);
    return false;
  }

  // Experiments are installed through an external mechanism that
  // limits target audience to compatible clients. We trust it knows what
  // it's doing and skip compatibility checks.
  //
  // This decision does forfeit defense in depth. If the experiments system
  // is ever wrong about targeting an add-on to a specific application
  // or platform, the client will likely see errors.
  if (aAddon.type == "experiment")
    return true;

  if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely) {
    logger.warn(`Updates for add-on ${aAddon.id} must be provided over HTTPS.`);
    return false;
  }


  if (!aAddon.isPlatformCompatible) {
    logger.warn(`Add-on ${aAddon.id} is not compatible with platform.`);
    return false;
  }

  if (aAddon.dependencies.length) {
    let isActive = id => {
      let active = XPIProvider.activeAddons.get(id);
      return active && !active.disable;
    };

    if (aAddon.dependencies.some(id => !isActive(id)))
      return false;
  }

  if (!AddonSettings.ALLOW_LEGACY_EXTENSIONS && LEGACY_TYPES.has(aAddon.type) &&
      !aAddon._installLocation.isSystem &&
      aAddon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED) {
    logger.warn(`disabling legacy extension ${aAddon.id}`);
    return false;
  }

  if (!ALLOW_NON_MPC && aAddon.type == "extension" &&
      aAddon.multiprocessCompatible !== true) {
    logger.warn(`disabling ${aAddon.id} since it is not multiprocess compatible`);
    return false;
  }

  if (AddonManager.checkCompatibility) {
    if (!aAddon.isCompatible) {
      logger.warn(`Add-on ${aAddon.id} is not compatible with application version.`);
      return false;
    }
  } else {
    let app = aAddon.matchingTargetApplication;
    if (!app) {
      logger.warn(`Add-on ${aAddon.id} is not compatible with target application.`);
      return false;
    }

    // XXX Temporary solution to let applications opt-in to make themes safer
    //     following significant UI changes even if checkCompatibility=false has
    //     been set, until we get bug 962001.
    if (aAddon.type == "theme" && app.id == Services.appinfo.ID) {
      try {
        let minCompatVersion = Services.prefs.getCharPref(PREF_CHECKCOMAT_THEMEOVERRIDE);
        if (minCompatVersion &&
            Services.vc.compare(minCompatVersion, app.maxVersion) > 0) {
          logger.warn(`Theme ${aAddon.id} is not compatible with application version.`);
          return false;
        }
      } catch (e) {}
    }
  }

  return true;
}

function createAddonDetails(id, aAddon) {
  return {
    id: id || aAddon.id,
    type: aAddon.type,
    version: aAddon.version,
    multiprocessCompatible: aAddon.multiprocessCompatible,
    mpcOptedOut: aAddon.mpcOptedOut,
    runInSafeMode: aAddon.runInSafeMode,
    dependencies: aAddon.dependencies,
    hasEmbeddedWebExtension: aAddon.hasEmbeddedWebExtension,
  };
}

/**
 * Converts an internal add-on type to the type presented through the API.
 *
 * @param  aType
 *         The internal add-on type
 * @return an external add-on type
 */
function getExternalType(aType) {
  if (aType in TYPE_ALIASES)
    return TYPE_ALIASES[aType];
  return aType;
}

function getManifestFileForDir(aDir) {
  let file = getFile(FILE_RDF_MANIFEST, aDir);
  if (file.exists() && file.isFile())
    return file;
  file.leafName = FILE_WEB_MANIFEST;
  if (file.exists() && file.isFile())
    return file;
  return null;
}

/**
 * Converts a list of API types to a list of API types and any aliases for those
 * types.
 *
 * @param  aTypes
 *         An array of types or null for all types
 * @return an array of types or null for all types
 */
function getAllAliasesForTypes(aTypes) {
  if (!aTypes)
    return null;

  // Build a set of all requested types and their aliases
  let typeset = new Set(aTypes);

  for (let alias of Object.keys(TYPE_ALIASES)) {
    // Ignore any requested internal types
    typeset.delete(alias);

    // Add any alias for the internal type
    if (typeset.has(TYPE_ALIASES[alias]))
      typeset.add(alias);
  }

  return [...typeset];
}

/**
 * A synchronous method for loading an add-on's manifest. This should only ever
 * be used during startup or a sync load of the add-ons DB
 */
function syncLoadManifestFromFile(aFile, aInstallLocation) {
  let success = undefined;
  let result = null;

  loadManifestFromFile(aFile, aInstallLocation).then(val => {
    success = true;
    result = val;
  }, val => {
    success = false;
    result = val
  });

  Services.tm.spinEventLoopUntil(() => success !== undefined);

  if (!success)
    throw result;
  return result;
}

/**
 * Gets an nsIURI for a file within another file, either a directory or an XPI
 * file. If aFile is a directory then this will return a file: URI, if it is an
 * XPI file then it will return a jar: URI.
 *
 * @param  aFile
 *         The file containing the resources, must be either a directory or an
 *         XPI file
 * @param  aPath
 *         The path to find the resource at, "/" separated. If aPath is empty
 *         then the uri to the root of the contained files will be returned
 * @return an nsIURI pointing at the resource
 */
function getURIForResourceInFile(aFile, aPath) {
  if (aFile.exists() && aFile.isDirectory()) {
    let resource = aFile.clone();
    if (aPath)
      aPath.split("/").forEach(part => resource.append(part));

    return Services.io.newFileURI(resource);
  }

  return buildJarURI(aFile, aPath);
}

/**
 * Creates a jar: URI for a file inside a ZIP file.
 *
 * @param  aJarfile
 *         The ZIP file as an nsIFile
 * @param  aPath
 *         The path inside the ZIP file
 * @return an nsIURI for the file
 */
function buildJarURI(aJarfile, aPath) {
  let uri = Services.io.newFileURI(aJarfile);
  uri = "jar:" + uri.spec + "!/" + aPath;
  return Services.io.newURI(uri);
}

/**
 * Sends local and remote notifications to flush a JAR file cache entry
 *
 * @param aJarFile
 *        The ZIP/XPI/JAR file as a nsIFile
 */
function flushJarCache(aJarFile) {
  Services.obs.notifyObservers(aJarFile, "flush-cache-entry");
  Services.mm.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}

function flushChromeCaches() {
  // Init this, so it will get the notification.
  Services.obs.notifyObservers(null, "startupcache-invalidate");
  // Flush message manager cached scripts
  Services.obs.notifyObservers(null, "message-manager-flush-caches");
  // Also dispatch this event to child processes
  Services.mm.broadcastAsyncMessage(MSG_MESSAGE_MANAGER_CACHES_FLUSH, null);
}

/**
 * Recursively removes a directory or file fixing permissions when necessary.
 *
 * @param  aFile
 *         The nsIFile to remove
 */
function recursiveRemove(aFile) {
  let isDir = null;

  try {
    isDir = aFile.isDirectory();
  } catch (e) {
    // If the file has already gone away then don't worry about it, this can
    // happen on OSX where the resource fork is automatically moved with the
    // data fork for the file. See bug 733436.
    if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
      return;
    if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND)
      return;

    throw e;
  }

  setFilePermissions(aFile, isDir ? FileUtils.PERMS_DIRECTORY
                                  : FileUtils.PERMS_FILE);

  try {
    aFile.remove(true);
    return;
  } catch (e) {
    if (!aFile.isDirectory() || aFile.isSymlink()) {
      logger.error("Failed to remove file " + aFile.path, e);
      throw e;
    }
  }

  // Use a snapshot of the directory contents to avoid possible issues with
  // iterating over a directory while removing files from it (the YAFFS2
  // embedded filesystem has this issue, see bug 772238), and to remove
  // normal files before their resource forks on OSX (see bug 733436).
  let entries = getDirectoryEntries(aFile, true);
  entries.forEach(recursiveRemove);

  try {
    aFile.remove(true);
  } catch (e) {
    logger.error("Failed to remove empty directory " + aFile.path, e);
    throw e;
  }
}

/**
 * Gets a snapshot of directory entries.
 *
 * @param  aDir
 *         Directory to look at
 * @param  aSortEntries
 *         True to sort entries by filename
 * @return An array of nsIFile, or an empty array if aDir is not a readable directory
 */
function getDirectoryEntries(aDir, aSortEntries) {
  let dirEnum;
  try {
    dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
    let entries = [];
    while (dirEnum.hasMoreElements())
      entries.push(dirEnum.nextFile);

    if (aSortEntries) {
      entries.sort(function(a, b) {
        return a.path > b.path ? -1 : 1;
      });
    }

    return entries
  } catch (e) {
    if (aDir.exists()) {
      logger.warn("Can't iterate directory " + aDir.path, e);
    }
    return [];
  } finally {
    if (dirEnum) {
      dirEnum.close();
    }
  }
}

/**
 * Record a bit of per-addon telemetry
 * @param aAddon the addon to record
 */
function recordAddonTelemetry(aAddon) {
  let locale = aAddon.defaultLocale;
  if (locale) {
    if (locale.name)
      XPIProvider.setTelemetry(aAddon.id, "name", locale.name);
    if (locale.creator)
      XPIProvider.setTelemetry(aAddon.id, "creator", locale.creator);
  }
}

/**
 * The on-disk state of an individual XPI, created from an Object
 * as stored in the addonStartup.json file.
 */
const JSON_FIELDS = Object.freeze([
  "bootstrapped",
  "changed",
  "dependencies",
  "enabled",
  "enableShims",
  "file",
  "hasEmbeddedWebExtension",
  "lastModifiedTime",
  "path",
  "runInSafeMode",
  "type",
  "version",
]);

const BOOTSTRAPPED_FIELDS = Object.freeze([
  "dependencies",
  "hasEmbeddedWebExtension",
  "runInSafeMode",
  "type",
  "version",
]);

class XPIState {
  constructor(location, id, saved = {}) {
    this.location = location;
    this.id = id;

    // Set default values.
    this.type = "extension";
    this.bootstrapped = false;
    this.enableShims = false;

    for (let prop of JSON_FIELDS) {
      if (prop in saved) {
        this[prop] = saved[prop];
      }
    }

    if (saved.currentModifiedTime && saved.currentModifiedTime != this.lastModifiedTime) {
      this.lastModifiedTime = saved.currentModifiedTime;
      this.changed = true;
    }

    if (this.enabled) {
      XPIProvider._addURIMapping(id, this.file);
    }
  }

  /**
   * Migrates an add-on's data from xpiState and bootstrappedAddons
   * preferences, and returns an XPIState object for it.
   *
   * @param {XPIStateLocation} location
   *        The location of the add-on.
   * @param {string} id
   *        The ID of the add-on to migrate.
   * @param {object} state
   *        The add-on's data from the xpiState preference.
   * @param {object} [bootstrapped]
   *        The add-on's data from the bootstrappedAddons preference, if
   *        applicable.
   */
  static migrate(location, id, saved, bootstrapped) {
    let data = {
      enabled: saved.e,
      path: descriptorToPath(saved.d, location.dir),
      lastModifiedTime: saved.mt || saved.st,
      version: saved.v,
      enableShims: false,
    };

    if (bootstrapped) {
      data.bootstrapped = true;
      data.enabled = true;
      data.enableShims = !bootstrapped.multiprocessCompatible;
      data.path = descriptorToPath(bootstrapped.descriptor, location.dir);

      for (let field of BOOTSTRAPPED_FIELDS) {
        if (field in bootstrapped) {
          data[field] = bootstrapped[field];
        }
      }
    }

    return new XPIState(location, id, data);
  }

  // Compatibility shim getters for legacy callers in XPIProviderUtils:
  get mtime() {
    return this.lastModifiedTime;
  }
  get active() {
    return this.enabled;
  }
  get multiprocessCompatible() {
    return !this.enableShims;
  }


  /**
   * @property {string} path
   *        The full on-disk path of the add-on.
   */
  get path() {
    return this.file && this.file.path;
  }
  set path(path) {
    this.file = getFile(path, this.location.dir)
  }

  /**
   * @property {string} relativePath
   *        The path to the add-on relative to its parent location, or
   *        the full path if its parent location has no on-disk path.
   */
  get relativePath() {
    if (this.location.dir && this.location.dir.contains(this.file)) {
      let path = this.file.getRelativePath(this.location.dir);
      if (AppConstants.platform == "win") {
        path = path.replace(/\//g, "\\");
      }
      return path;
    }
    return this.path;
  }

  /**
   * Returns a JSON-compatible representation of this add-on's state
   * data, to be saved to addonStartup.json.
   */
  toJSON() {
    let json = {
      enabled: this.enabled,
      lastModifiedTime: this.lastModifiedTime,
      path: this.relativePath,
      version: this.version,
    };
    if (this.type != "extension") {
      json.type = this.type;
    }
    if (this.enableShims) {
      json.enableShims = true;
    }
    if (this.bootstrapped) {
      json.bootstrapped = true;
      json.dependencies = this.dependencies;
      json.runInSafeMode = this.runInSafeMode;
      json.hasEmbeddedWebExtension = this.hasEmbeddedWebExtension;
    }
    return json;
  }

  /**
   * Update the last modified time for an add-on on disk.
   * @param aFile: nsIFile path of the add-on.
   * @param aId: The add-on ID.
   * @return True if the time stamp has changed.
   */
  getModTime(aFile, aId) {
    // Modified time is the install manifest time, if any. If no manifest
    // exists, we assume this is a packed .xpi and use the time stamp of
    // {path}
    let mtime = (tryGetMtime(getManifestFileForDir(aFile)) ||
                 tryGetMtime(aFile));
    if (!mtime) {
      logger.warn("Can't get modified time of ${file}", {file: aFile.path});
    }

    this.changed = mtime != this.lastModifiedTime;
    this.lastModifiedTime = mtime;
    return this.changed;
  }

  /**
   * Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true,
   * update the last-modified time. This should probably be made async, but for now we
   * don't want to maintain parallel sync and async versions of the scan.
   * Caller is responsible for doing XPIStates.save() if necessary.
   * @param aDBAddon The DBAddonInternal for this add-on.
   * @param aUpdated The add-on was updated, so we must record new modified time.
   */
  syncWithDB(aDBAddon, aUpdated = false) {
    logger.debug("Updating XPIState for " + JSON.stringify(aDBAddon));
    // If the add-on changes from disabled to enabled, we should re-check the modified time.
    // If this is a newly found add-on, it won't have an 'enabled' field but we
    // did a full recursive scan in that case, so we don't need to do it again.
    // We don't use aDBAddon.active here because it's not updated until after restart.
    let mustGetMod = (aDBAddon.visible && !aDBAddon.disabled && !this.enabled);

    // We need to treat XUL themes specially here, since lightweight
    // themes require the default theme's chrome to be registered even
    // though we report it as disabled for UI purposes.
    if (aDBAddon.type == "theme") {
      this.enabled = aDBAddon.internalName == XPIProvider.selectedSkin;
    } else {
      this.enabled = aDBAddon.visible && !aDBAddon.disabled;
    }

    this.version = aDBAddon.version;
    this.type = aDBAddon.type;
    this.enableShims = this.type == "extension" && !aDBAddon.multiprocessCompatible;

    this.bootstrapped = !!aDBAddon.bootstrap;
    if (this.bootstrapped) {
      this.hasEmbeddedWebExtension = aDBAddon.hasEmbeddedWebExtension;
      this.dependencies = aDBAddon.dependencies;
      this.runInSafeMode = canRunInSafeMode(aDBAddon);
    }

    if (aUpdated || mustGetMod) {
      this.getModTime(this.file, aDBAddon.id);
      if (this.lastModifiedTime != aDBAddon.updateDate) {
        aDBAddon.updateDate = this.lastModifiedTime;
        if (XPIDatabase.initialized) {
          XPIDatabase.saveChanges();
        }
      }
    }
  }
}

/**
 * Manages the state data for add-ons in a given install location.
 *
 * @param {string} name
 *        The name of the install location (e.g., "app-profile").
 * @param {string?} path
 *        The on-disk path of the install location. May be null for some
 *        locations which do not map to a specific on-disk path.
 * @param {object} [saved = {}]
 *        The persisted JSON state data to restore.
 */
class XPIStateLocation extends Map {
  constructor(name, path, saved = {}) {
    super();

    this.name = name;
    this.path = path || saved.path || null;
    this.dir = this.path && new nsIFile(this.path);

    for (let [id, data] of Object.entries(saved.addons || {})) {
      let xpiState = this._addState(id, data);
      // Make a note that this state was restored from saved data.
      xpiState.wasRestored = true;
    }
  }

  /**
   * Returns a JSON-compatible representation of this location's state
   * data, to be saved to addonStartup.json.
   */
  toJSON() {
    let json = { addons: {} };

    if (this.path) {
      json.path = this.path;
    }

    if (STARTUP_MTIME_SCOPES.includes(this.name)) {
      json.checkStartupModifications = true;
    }

    for (let [id, addon] of this.entries()) {
      if (addon.type != "experiment") {
        json.addons[id] = addon;
      }
    }
    return json;
  }

  _addState(addonId, saved) {
    let xpiState = new XPIState(this, addonId, saved);
    this.set(addonId, xpiState);
    return xpiState;
  }

  /**
   * Adds state data for the given DB add-on to the DB.
   *
   * @param {DBAddon} addon
   *        The DBAddon to add.
   */
  addAddon(addon) {
    logger.debug("XPIStates adding add-on ${id} in ${location}: ${path}", addon);

    let xpiState = this._addState(addon.id, {file: addon._sourceBundle});
    xpiState.syncWithDB(addon, true);

    XPIProvider.setTelemetry(addon.id, "location", this.name);
  }

  /**
   * Adds stub state data for the local file to the DB.
   *
   * @param {string} addonId
   *        The ID of the add-on represented by the given file.
   * @param {nsIFile} file
   *        The local file or directory containing the add-on.
   * @returns {XPIState}
   */
  addFile(addonId, file) {
    let xpiState = this._addState(addonId, {enabled: true, file: file.clone()});
    xpiState.getModTime(xpiState.file, addonId);
    return xpiState;
  }

  /**
   * Migrates saved state data for the given add-on from the values
   * stored in xpiState and bootstrappedAddons preferences, and adds it to
   * the DB.
   *
   * @param {string} id
   *        The ID of the add-on to migrate.
   * @param {object} state
   *        The add-on's data from the xpiState preference.
   * @param {object} [bootstrapped]
   *        The add-on's data from the bootstrappedAddons preference, if
   *        applicable.
   */
  migrateAddon(id, state, bootstrapped) {
    this.set(id, XPIState.migrate(this, id, state, bootstrapped));
  }
}

/**
 * Keeps track of the state of XPI add-ons on the file system.
 */
this.XPIStates = {
  // Map(location name -> Map(add-on ID -> XPIState))
  db: null,

  _jsonFile: null,

  /**
   * @property {Map<string, XPIState>} sideLoadedAddons
   *        A map of new add-ons detected during install location
   *        directory scans. Keys are add-on IDs, values are XPIState
   *        objects corresponding to those add-ons.
   */
  sideLoadedAddons: new Map(),

  get size() {
    let count = 0;
    if (this.db) {
      for (let location of this.db.values()) {
        count += location.size;
      }
    }
    return count;
  },

  /**
   * Migrates state data from the xpiState and bootstrappedAddons
   * preferences and adds it to the DB. Returns a JSON-compatible
   * representation of the current state of the DB.
   *
   * @returns {object}
   */
  migrateStateFromPrefs() {
    logger.info("No addonStartup.json found. Attempting to migrate data from preferences");

    let state;
    // Try to migrate state data from old storage locations.
    let bootstrappedAddons;
    try {
      state = JSON.parse(Preferences.get(PREF_XPI_STATE));
      bootstrappedAddons = JSON.parse(Preferences.get(PREF_BOOTSTRAP_ADDONS, "{}"));
    } catch (e) {
      logger.warn("Error parsing extensions.xpiState and " +
                  "extensions.bootstrappedAddons: ${error}",
                  {error: e});

    }

    for (let [locName, addons] of Object.entries(state)) {
      for (let [id, addon] of Object.entries(addons)) {
        let loc = this.getLocation(locName);
        if (loc) {
          loc.migrateAddon(id, addon, bootstrappedAddons[id] || null);
        }
      }
    }

    // Clear out old state data.
    for (let pref of OBSOLETE_PREFERENCES) {
      Preferences.reset(pref);
    }
    OS.File.remove(OS.Path.join(OS.Constants.Path.profileDir,
                                FILE_XPI_ADDONS_LIST));

    // Serialize and deserialize so we get the expected JSON data.
    let data = JSON.parse(JSON.stringify(this));

    logger.debug("Migrated data: ${}", data);

    return data;
  },

  /**
   * Load extension state data from addonStartup.json, or migrates it
   * from legacy state preferences, if they exist.
   */
  loadExtensionState() {
    let state;
    try {
      state = aomStartup.readStartupData();
    } catch (e) {
      logger.warn("Error parsing extensions state: ${error}",
                  {error: e});
    }

    if (!state && Preferences.has(PREF_XPI_STATE)) {
      try {
        state = this.migrateStateFromPrefs();
      } catch (e) {
        logger.warn("Error migrating extensions.xpiState and " +
                    "extensions.bootstrappedAddons: ${error}",
                    {error: e});
      }
    }

    logger.debug("Loaded add-on state: ${}", state);
    return state || {};
  },

  /**
   * Walk through all install locations, highest priority first,
   * comparing the on-disk state of extensions to what is stored in prefs.
   *
   * @param {bool} [ignoreSideloads = true]
   *        If true, ignore changes in scopes where we don't accept
   *        side-loads.
   *
   * @return true if anything has changed.
   */
  getInstallState(ignoreSideloads = true) {
    if (!this.db) {
      this.db = new Map();
    }

    let oldState = this.initialStateData || this.loadExtensionState();
    this.initialStateData = oldState;

    let changed = false;
    let oldLocations = new Set(Object.keys(oldState));

    for (let location of XPIProvider.installLocations) {
      oldLocations.delete(location.name);

      // The results of scanning this location.
      let loc = this.getLocation(location.name, location.path || null,
                                 oldState[location.name]);
      changed = changed || loc.changed;

      // Don't bother checking scopes where we don't accept side-loads.
      if (ignoreSideloads && !(location.scope & gStartupScanScopes)) {
        continue;
      }

      if (location.name == KEY_APP_TEMPORARY) {
        continue;
      }

      let knownIds = new Set(loc.keys());
      for (let [id, file] of location.getAddonLocations(true)) {
        knownIds.delete(id);

        let xpiState = loc.get(id);
        if (!xpiState) {
          logger.debug("New add-on ${id} in ${location}", {id, location: location.name});

          changed = true;
          xpiState = loc.addFile(id, file);
          if (!location.isSystem) {
            this.sideLoadedAddons.set(id, xpiState);
          }
        } else {
          let addonChanged = (xpiState.getModTime(file, id) ||
                              file.path != xpiState.path);
          xpiState.file = file.clone();

          if (addonChanged) {
            changed = true;
            logger.debug("Changed add-on ${id} in ${location}", {id, location: location.name});
          } else {
            logger.debug("Existing add-on ${id} in ${location}", {id, location: location.name});
          }
        }
        XPIProvider.setTelemetry(id, "location", location.name);
      }

      // Anything left behind in oldState was removed from the file system.
      for (let id of knownIds) {
        loc.delete(id);
        changed = true;
      }
    }

    // If there's anything left in oldState, an install location that held add-ons
    // was removed from the browser configuration.
    changed = changed || oldLocations.size > 0;

    logger.debug("getInstallState changed: ${rv}, state: ${state}",
        {rv: changed, state: this.db});
    return changed;
  },

  /**
   * Get the Map of XPI states for a particular location.
   * @param name The name of the install location.
   * @return XPIStateLocation (id -> XPIState) or null if there are no add-ons in the location.
   */
  getLocation(name, path, saved) {
    let location = this.db.get(name);

    if (path && location && location.path != path) {
      location = null;
      saved = null;
    }

    if (!location || (path && location.path != path)) {
      let loc = XPIProvider.installLocationsByName[name];
      if (loc) {
        location = new XPIStateLocation(name, path || loc.path || null, saved);
        this.db.set(name, location);
      }
    }
    return location;
  },

  /**
   * Get the XPI state for a specific add-on in a location.
   * If the state is not in our cache, return null.
   * @param aLocation The name of the location where the add-on is installed.
   * @param aId       The add-on ID
   * @return The XPIState entry for the add-on, or null.
   */
  getAddon(aLocation, aId) {
    let location = this.db.get(aLocation);
    return location && location.get(aId);
  },

  /**
   * Find the highest priority location of an add-on by ID and return the
   * location and the XPIState.
   * @param aId   The add-on ID
   * @return {XPIState?}
   */
  findAddon(aId) {
    // Fortunately the Map iterator returns in order of insertion, which is
    // also our highest -> lowest priority order.
    for (let location of this.db.values()) {
      if (location.has(aId)) {
        return location.get(aId);
      }
    }
    return undefined;
  },

  /**
   * Iterates over the list of all enabled add-ons in any location.
   */
  * enabledAddons() {
    for (let location of this.db.values()) {
      for (let entry of location.values()) {
        if (entry.enabled) {
          yield entry;
        }
      }
    }
  },

  /**
   * Iterates over the list of all add-ons which were initially restored
   * from the startup state cache.
   */
  * initialEnabledAddons() {
    for (let addon of this.enabledAddons()) {
      if (addon.wasRestored) {
        yield addon;
      }
    }
  },

  /**
   * Iterates over all enabled bootstrapped add-ons, in any location.
   */
  * bootstrappedAddons() {
    for (let addon of this.enabledAddons()) {
      if (addon.bootstrapped) {
        yield addon;
      }
    }
  },

  /**
   * Add a new XPIState for an add-on and synchronize it with the DBAddonInternal.
   * @param aAddon DBAddonInternal for the new add-on.
   */
  addAddon(aAddon) {
    let location = this.getLocation(aAddon._installLocation.name);
    location.addAddon(aAddon);
  },

  /**
   * Save the current state of installed add-ons.
   */
  save() {
    if (!this._jsonFile) {
      this._jsonFile = new JSONFile({
        path: OS.Path.join(OS.Constants.Path.profileDir, FILE_XPI_STATES),
        finalizeAt: AddonManager.shutdown,
        compression: "lz4",
      })
      this._jsonFile.data = this;
    }

    this._jsonFile.saveSoon();
  },

  toJSON() {
    let data = {};
    for (let [key, loc] of this.db.entries()) {
      if (key != TemporaryInstallLocation.name && loc.size) {
        data[key] = loc;
      }
    }
    return data;
  },

  /**
   * Remove the XPIState for an add-on and save the new state.
   * @param aLocation  The name of the add-on location.
   * @param aId        The ID of the add-on.
   */
  removeAddon(aLocation, aId) {
    logger.debug("Removing XPIState for " + aLocation + ":" + aId);
    let location = this.db.get(aLocation);
    if (location) {
      location.delete(aId);
      if (location.size == 0) {
        this.db.delete(aLocation);
      }
      this.save();
    }
  },

  /**
   * Disable the XPIState for an add-on.
   */
  disableAddon(aId) {
    logger.debug(`Disabling XPIState for ${aId}`);
    let state = this.findAddon(aId);
    if (state) {
      state.enabled = false;
    }
  },
};

this.XPIProvider = {
  get name() {
    return "XPIProvider";
  },

  // An array of known install locations
  installLocations: null,
  // A dictionary of known install locations by name
  installLocationsByName: null,
  // An array of currently active AddonInstalls
  installs: null,
  // The default skin for the application
  defaultSkin: "classic/1.0",
  // The current skin used by the application
  currentSkin: null,
  // The selected skin to be used by the application when it is restarted. This
  // will be the same as currentSkin when it is the skin to be used when the
  // application is restarted
  selectedSkin: null,
  // The value of the minCompatibleAppVersion preference
  minCompatibleAppVersion: null,
  // The value of the minCompatiblePlatformVersion preference
  minCompatiblePlatformVersion: null,
  // A Map of active addons to their bootstrapScope by ID
  activeAddons: new Map(),
  // True if the platform could have activated extensions
  extensionsActive: false,
  // True if all of the add-ons found during startup were installed in the
  // application install location
  allAppGlobal: true,
  // Keep track of startup phases for telemetry
  runPhase: XPI_STARTING,
  // Per-addon telemetry information
  _telemetryDetails: {},
  // A Map from an add-on install to its ID
  _addonFileMap: new Map(),
  // Have we started shutting down bootstrap add-ons?
  _closing: false,

  // Check if the XPIDatabase has been loaded (without actually
  // triggering unwanted imports or I/O)
  get isDBLoaded() {
    return gLazyObjectsLoaded && XPIDatabase.initialized;
  },

  /**
   * Returns true if the add-on with the given ID is currently active,
   * without forcing the add-ons database to load.
   *
   * @param {string} addonId
   *        The ID of the add-on to check.
   * @returns {boolean}
   */
  addonIsActive(addonId) {
    let state = XPIStates.findAddon(addonId);
    return state && state.enabled;
  },

  /**
   * Returns an array of the add-on values in `bootstrappedAddons`,
   * sorted so that all of an add-on's dependencies appear in the array
   * before itself.
   *
   * @returns {Array<object>}
   *   A sorted array of add-on objects. Each value is a copy of the
   *   corresponding value in the `bootstrappedAddons` object, with an
   *   additional `id` property, which corresponds to the key in that
   *   object, which is the same as the add-ons ID.
   */
  sortBootstrappedAddons() {
    function compare(a, b) {
      if (a === b) {
        return 0;
      }
      return (a < b) ? -1 : 1;
    }

    // Sort the list so that ordering is deterministic.
    let list = Array.from(XPIStates.bootstrappedAddons());
    list.sort((a, b) => compare(a.id, b.id));

    let addons = {};
    for (let entry of list) {
      addons[entry.id] = entry;
    }

    let res = new Set();
    let seen = new Set();

    let add = addon => {
      seen.add(addon.id);

      for (let id of addon.dependencies || []) {
        if (id in addons && !seen.has(id)) {
          add(addons[id]);
        }
      }

      res.add(addon.id);
    }

    Object.values(addons).forEach(add);

    return Array.from(res, id => addons[id]);
  },

  /*
   * Set a value in the telemetry hash for a given ID
   */
  setTelemetry(aId, aName, aValue) {
    if (!this._telemetryDetails[aId])
      this._telemetryDetails[aId] = {};
    this._telemetryDetails[aId][aName] = aValue;
  },

  // Keep track of in-progress operations that support cancel()
  _inProgress: [],

  doing(aCancellable) {
    this._inProgress.push(aCancellable);
  },

  done(aCancellable) {
    let i = this._inProgress.indexOf(aCancellable);
    if (i != -1) {
      this._inProgress.splice(i, 1);
      return true;
    }
    return false;
  },

  cancelAll() {
    // Cancelling one may alter _inProgress, so don't use a simple iterator
    while (this._inProgress.length > 0) {
      let c = this._inProgress.shift();
      try {
        c.cancel();
      } catch (e) {
        logger.warn("Cancel failed", e);
      }
    }
  },

  /**
   * Adds or updates a URI mapping for an Addon.id.
   *
   * Mappings should not be removed at any point. This is so that the mappings
   * will be still valid after an add-on gets disabled or uninstalled, as
   * consumers may still have URIs of (leaked) resources they want to map.
   */
  _addURIMapping(aID, aFile) {
    logger.info("Mapping " + aID + " to " + aFile.path);
    this._addonFileMap.set(aID, aFile.path);

    AddonPathService.insertPath(aFile.path, aID);
  },

  /**
   * Resolve a URI back to physical file.
   *
   * Of course, this works only for URIs pointing to local resources.
   *
   * @param  aURI
   *         URI to resolve
   * @return
   *         resolved nsIFileURL
   */
  _resolveURIToFile(aURI) {
    switch (aURI.scheme) {
      case "jar":
      case "file":
        if (aURI instanceof Ci.nsIJARURI) {
          return this._resolveURIToFile(aURI.JARFile);
        }
        return aURI;

      case "chrome":
        aURI = ChromeRegistry.convertChromeURL(aURI);
        return this._resolveURIToFile(aURI);

      case "resource":
        aURI = Services.io.newURI(ResProtocolHandler.resolveURI(aURI));
        return this._resolveURIToFile(aURI);

      case "view-source":
        aURI = Services.io.newURI(aURI.path);
        return this._resolveURIToFile(aURI);

      case "about":
        if (aURI.spec == "about:blank") {
          // Do not attempt to map about:blank
          return null;
        }

        let chan;
        try {
          chan = NetUtil.newChannel({
            uri: aURI,
            loadUsingSystemPrincipal: true
          });
        } catch (ex) {
          return null;
        }
        // Avoid looping
        if (chan.URI.equals(aURI)) {
          return null;
        }
        // We want to clone the channel URI to avoid accidentially keeping
        // unnecessary references to the channel or implementation details
        // around.
        return this._resolveURIToFile(chan.URI.clone());

      default:
        return null;
    }
  },

  /**
   * Starts the XPI provider initializes the install locations and prefs.
   *
   * @param  aAppChanged
   *         A tri-state value. Undefined means the current profile was created
   *         for this session, true means the profile already existed but was
   *         last used with an application with a different version number,
   *         false means that the profile was last used by this version of the
   *         application.
   * @param  aOldAppVersion
   *         The version of the application last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aOldPlatformVersion
   *         The version of the platform last run with this profile or null
   *         if it is a new profile or the version is unknown
   */
  startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
      try {
        var dir = FileUtils.getDir(aKey, aPaths);
      } catch (e) {
        // Some directories aren't defined on some platforms, ignore them
        logger.debug("Skipping unavailable install location " + aName);
        return;
      }

      try {
        var location = aLocked ? new DirectoryInstallLocation(aName, dir, aScope)
                               : new MutableDirectoryInstallLocation(aName, dir, aScope);
      } catch (e) {
        logger.warn("Failed to add directory install location " + aName, e);
        return;
      }

      XPIProvider.installLocations.push(location);
      XPIProvider.installLocationsByName[location.name] = location;
    }

    function addSystemAddonInstallLocation(aName, aKey, aPaths, aScope) {
      try {
        var dir = FileUtils.getDir(aKey, aPaths);
      } catch (e) {
        // Some directories aren't defined on some platforms, ignore them
        logger.debug("Skipping unavailable install location " + aName);
        return;
      }

      try {
        var location = new SystemAddonInstallLocation(aName, dir, aScope, aAppChanged !== false);
      } catch (e) {
        logger.warn("Failed to add system add-on install location " + aName, e);
        return;
      }

      XPIProvider.installLocations.push(location);
      XPIProvider.installLocationsByName[location.name] = location;
    }

    function addRegistryInstallLocation(aName, aRootkey, aScope) {
      try {
        var location = new WinRegInstallLocation(aName, aRootkey, aScope);
      } catch (e) {
        logger.warn("Failed to add registry install location " + aName, e);
        return;
      }

      XPIProvider.installLocations.push(location);
      XPIProvider.installLocationsByName[location.name] = location;
    }

    try {
      AddonManagerPrivate.recordTimestamp("XPI_startup_begin");

      logger.debug("startup");
      this.runPhase = XPI_STARTING;
      this.installs = new Set();
      this.installLocations = [];
      this.installLocationsByName = {};
      // Hook for tests to detect when saving database at shutdown time fails
      this._shutdownError = null;
      // Clear this at startup for xpcshell test restarts
      this._telemetryDetails = {};
      // Register our details structure with AddonManager
      AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);

      let hasRegistry = ("nsIWindowsRegKey" in Ci);

      let enabledScopes = Preferences.get(PREF_EM_ENABLED_SCOPES,
                                          AddonManager.SCOPE_ALL);

      // These must be in order of priority, highest to lowest,
      // for processFileChanges etc. to work

      XPIProvider.installLocations.push(TemporaryInstallLocation);
      XPIProvider.installLocationsByName[TemporaryInstallLocation.name] =
        TemporaryInstallLocation;

      // The profile location is always enabled
      addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
                                  [DIR_EXTENSIONS],
                                  AddonManager.SCOPE_PROFILE, false);

      addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR,
                                    [DIR_SYSTEM_ADDONS],
                                    AddonManager.SCOPE_PROFILE);

      addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_FEATURES,
                                  [], AddonManager.SCOPE_PROFILE, true);

      if (enabledScopes & AddonManager.SCOPE_USER) {
        addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
                                    [Services.appinfo.ID],
                                    AddonManager.SCOPE_USER, true);
        if (hasRegistry) {
          addRegistryInstallLocation("winreg-app-user",
                                     Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
                                     AddonManager.SCOPE_USER);
        }
      }

      addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_ADDON_APP_DIR,
                                  [DIR_EXTENSIONS],
                                  AddonManager.SCOPE_APPLICATION, true);

      if (enabledScopes & AddonManager.SCOPE_SYSTEM) {
        addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD",
                                    [Services.appinfo.ID],
                                    AddonManager.SCOPE_SYSTEM, true);
        addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD",
                                    [Services.appinfo.ID],
                                    AddonManager.SCOPE_SYSTEM, true);
        if (hasRegistry) {
          addRegistryInstallLocation("winreg-app-global",
                                     Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                     AddonManager.SCOPE_SYSTEM);
        }
      }

      let defaultPrefs = new Preferences({ defaultBranch: true });
      this.defaultSkin = defaultPrefs.get(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                          "classic/1.0");
      this.currentSkin = Preferences.get(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                         this.defaultSkin);
      this.selectedSkin = this.currentSkin;
      this.applyThemeChange();

      this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
                                                     null);
      this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                          null);

      Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this);
      Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this);
      Services.prefs.addObserver(PREF_E10S_ADDON_BLOCKLIST, this);
      Services.prefs.addObserver(PREF_E10S_ADDON_POLICY, this);
      if (!AppConstants.MOZ_REQUIRE_SIGNING || Cu.isInAutomation)
        Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this);
      Services.prefs.addObserver(PREF_ALLOW_LEGACY, this);
      Services.prefs.addObserver(PREF_ALLOW_NON_MPC, this);
      Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS);
      Services.obs.addObserver(this, NOTIFICATION_TOOLBOX_CONNECTION_CHANGE);


      let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
                                             aOldPlatformVersion);

      // Changes to installed extensions may have changed which theme is selected
      this.applyThemeChange();

      AddonManagerPrivate.markProviderSafe(this);

      if (aAppChanged && !this.allAppGlobal &&
          Preferences.get(PREF_EM_SHOW_MISMATCH_UI, true)) {
        let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
        if (addonsToUpdate) {
          this.showUpgradeUI(addonsToUpdate);
          flushCaches = true;
        }
      }

      if (flushCaches) {
        Services.obs.notifyObservers(null, "startupcache-invalidate");
        // UI displayed early in startup (like the compatibility UI) may have
        // caused us to cache parts of the skin or locale in memory. These must
        // be flushed to allow extension provided skins and locales to take full
        // effect
        Services.obs.notifyObservers(null, "chrome-flush-skin-caches");
        Services.obs.notifyObservers(null, "chrome-flush-caches");
      }

      if ("nsICrashReporter" in Ci &&
          Services.appinfo instanceof Ci.nsICrashReporter) {
        // Annotate the crash report with relevant add-on information.
        try {
          Services.appinfo.annotateCrashReport("Theme", this.currentSkin);
        } catch (e) { }
        try {
          Services.appinfo.annotateCrashReport("EMCheckCompatibility",
                                               AddonManager.checkCompatibility);
        } catch (e) { }
        this.addAddonsToCrashReporter();
      }

      try {
        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");

        for (let addon of this.sortBootstrappedAddons()) {
          try {
            let reason = BOOTSTRAP_REASONS.APP_STARTUP;
            // Eventually set INSTALLED reason when a bootstrap addon
            // is dropped in profile folder and automatically installed
            if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
                            .indexOf(addon.id) !== -1)
              reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
            this.callBootstrapMethod(createAddonDetails(addon.id, addon),
                                     addon.file, "startup", reason);
          } catch (e) {
            logger.error("Failed to load bootstrap addon " + addon.id + " from " +
                         addon.descriptor, e);
          }
        }
        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
      } catch (e) {
        logger.error("bootstrap startup failed", e);
        AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
      }

      // Let these shutdown a little earlier when they still have access to most
      // of XPCOM
      Services.obs.addObserver({
        observe(aSubject, aTopic, aData) {
          XPIProvider._closing = true;
          for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
            // If no scope has been loaded for this add-on then there is no need
            // to shut it down (should only happen when a bootstrapped add-on is
            // pending enable)
            if (!XPIProvider.activeAddons.has(addon.id))
              continue;

            let addonDetails = createAddonDetails(addon.id, addon);

            // If the add-on was pending disable then shut it down and remove it
            // from the persisted data.
            if (addon.disable) {
              XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown",
                                              BOOTSTRAP_REASONS.ADDON_DISABLE);
            } else {
              XPIProvider.callBootstrapMethod(addonDetails, addon.file, "shutdown",
                                              BOOTSTRAP_REASONS.APP_SHUTDOWN);
            }
          }
          Services.obs.removeObserver(this, "quit-application-granted");
        }
      }, "quit-application-granted");

      // Detect final-ui-startup for telemetry reporting
      Services.obs.addObserver({
        observe(aSubject, aTopic, aData) {
          AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
          XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
          Services.obs.removeObserver(this, "final-ui-startup");
        }
      }, "final-ui-startup");

      // If we haven't yet loaded the XPI database, schedule loading it
      // to occur once other important startup work is finished.  We want
      // this to happen relatively quickly after startup so the telemetry
      // environment has complete addon information.
      //
      // Unfortunately we have to use a variety of ways do detect when it
      // is time to load.  In a regular browser process we just wait for
      // sessionstore-windows-restored.  In a browser toolbox process
      // we wait for the toolbox to show up, based on xul-window-visible
      // and a visible toolbox window.
      // Finally, we have a test-only event called test-load-xpi-database
      // as a temporary workaround for bug 1372845.  The latter can be
      // cleaned up when that bug is resolved.
      if (!this.isDBLoaded) {
        const EVENTS = [ "sessionstore-windows-restored", "xul-window-visible", "test-load-xpi-database" ];
        let observer = {
          observe(subject, topic, data) {
            if (topic == "xul-window-visible" &&
                !Services.wm.getMostRecentWindow("devtools:toolbox")) {
              return;
            }

            for (let event of EVENTS) {
              Services.obs.removeObserver(observer, event);
            }

            // It would be nice to defer some of the work here until we
            // have idle time but we can't yet use requestIdleCallback()
            // from chrome.  See bug 1358476.
            XPIDatabase.asyncLoadDB();
          },
        };
        for (let event of EVENTS) {
          Services.obs.addObserver(observer, event);
        }
      }

      AddonManagerPrivate.recordTimestamp("XPI_startup_end");

      this.extensionsActive = true;
      this.runPhase = XPI_BEFORE_UI_STARTUP;

      let timerManager = Cc["@mozilla.org/updates/timer-manager;1"].
                         getService(Ci.nsIUpdateTimerManager);
      timerManager.registerTimer("xpi-signature-verification", () => {
        this.verifySignatures();
      }, XPI_SIGNATURE_CHECK_PERIOD);
    } catch (e) {
      logger.error("startup failed", e);
      AddonManagerPrivate.recordException("XPI", "startup failed", e);
    }
  },

  /**
   * Shuts down the database and releases all references.
   * Return: Promise{integer} resolves / rejects with the result of
   *                          flushing the XPI Database if it was loaded,
   *                          0 otherwise.
   */
  async shutdown() {
    logger.debug("shutdown");

    // Stop anything we were doing asynchronously
    this.cancelAll();

    // Uninstall any temporary add-ons.
    let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
    if (tempLocation) {
      for (let [id, addon] of tempLocation.entries()) {
        tempLocation.delete(id);

        this.callBootstrapMethod(createAddonDetails(id, addon),
                                 addon.file, "uninstall",
                                 BOOTSTRAP_REASONS.ADDON_UNINSTALL);
        this.unloadBootstrapScope(id);
        TemporaryInstallLocation.uninstallAddon(id);

        let state = XPIStates.findAddon(id);
        if (state) {
          let newAddon = XPIDatabase.makeAddonLocationVisible(id, state.location.name);

          let file = new nsIFile(newAddon.path);

          this.callBootstrapMethod(createAddonDetails(id, newAddon),
                                   file, "install",
                                   BOOTSTRAP_REASONS.ADDON_INSTALL);
        }
      }
    }

    this.activeAddons.clear();
    this.allAppGlobal = true;

    // If there are pending operations then we must update the list of active
    // add-ons
    if (Preferences.get(PREF_PENDING_OPERATIONS, false)) {
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_pending_ops", 1);
      XPIDatabase.updateActiveAddons();
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
    }

    // Ugh, if we reach this point without loading the xpi database,
    // we need to load it know, otherwise the telemetry shutdown blocker
    // will never resolve.
    if (!XPIDatabase.initialized) {
      await XPIDatabase.asyncLoadDB();
    }

    this.installs = null;
    this.installLocations = null;
    this.installLocationsByName = null;

    // This is needed to allow xpcshell tests to simulate a restart
    this.extensionsActive = false;
    this._addonFileMap.clear();

    try {
      await XPIDatabase.shutdown();
    } catch (err) {
      this._shutdownError = err;
    }
  },

  /**
   * Applies any pending theme change to the preferences.
   */
  applyThemeChange() {
    if (!Preferences.get(PREF_SKIN_SWITCHPENDING, false))
      return;

    // Tell the Chrome Registry which Skin to select
    try {
      this.selectedSkin = Preferences.get(PREF_SKIN_TO_SELECT);
      Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                 this.selectedSkin);
      Services.prefs.clearUserPref(PREF_SKIN_TO_SELECT);
      logger.debug("Changed skin to " + this.selectedSkin);
      this.currentSkin = this.selectedSkin;
    } catch (e) {
      logger.error("Error applying theme change", e);
    }
    Services.prefs.clearUserPref(PREF_SKIN_SWITCHPENDING);
  },

  /**
   * If the application has been upgraded and there are add-ons outside the
   * application directory then we may need to synchronize compatibility
   * information but only if the mismatch UI isn't disabled.
   *
   * @returns False if no update check is needed, otherwise an array of add-on
   *          IDs to check for updates. Array may be empty if no add-ons can be/need
   *           to be updated, but the metadata check needs to be performed.
   */
  shouldForceUpdateCheck(aAppChanged) {
    AddonManagerPrivate.recordSimpleMeasure("XPIDB_metadata_age", AddonRepository.metadataAge());

    let startupChanges = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
    logger.debug("shouldForceUpdateCheck startupChanges: " + startupChanges.toSource());
    AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_disabled", startupChanges.length);

    let forceUpdate = [];
    if (startupChanges.length > 0) {
    let addons = XPIDatabase.getAddons();
      for (let addon of addons) {
        if ((startupChanges.indexOf(addon.id) != -1) &&
            (addon.permissions() & AddonManager.PERM_CAN_UPGRADE) &&
            !addon.isCompatible) {
          logger.debug("shouldForceUpdateCheck: can upgrade disabled add-on " + addon.id);
          forceUpdate.push(addon.id);
        }
      }
    }

    if (AddonRepository.isMetadataStale()) {
      logger.debug("shouldForceUpdateCheck: metadata is stale");
      return forceUpdate;
    }
    if (forceUpdate.length > 0) {
      return forceUpdate;
    }

    return false;
  },

  /**
   * Shows the "Compatibility Updates" UI.
   *
   * @param  aAddonIDs
   *         Array opf addon IDs that were disabled by the application update, and
   *         should therefore be checked for updates.
   */
  showUpgradeUI(aAddonIDs) {
    logger.debug("XPI_showUpgradeUI: " + aAddonIDs.toSource());
    Services.telemetry.getHistogramById("ADDON_MANAGER_UPGRADE_UI_SHOWN").add(1);

    // Flip a flag to indicate that we interrupted startup with an interactive prompt
    Services.startup.interrupted = true;

    var variant = Cc["@mozilla.org/variant;1"].
                  createInstance(Ci.nsIWritableVariant);
    variant.setFromVariant(aAddonIDs);

    // This *must* be modal as it has to block startup.
    var features = "chrome,centerscreen,dialog,titlebar,modal";
    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
             getService(Ci.nsIWindowWatcher);
    ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);

    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
  },

  async updateSystemAddons() {
    let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
    if (!systemAddonLocation)
      return;

    // Don't do anything in safe mode
    if (Services.appinfo.inSafeMode)
      return;

    // Download the list of system add-ons
    let url = Preferences.get(PREF_SYSTEM_ADDON_UPDATE_URL, null);
    if (!url) {
      await systemAddonLocation.cleanDirectories();
      return;
    }

    url = await UpdateUtils.formatUpdateURL(url);

    logger.info(`Starting system add-on update check from ${url}.`);
    let res = await ProductAddonChecker.getProductAddonList(url);

    // If there was no list then do nothing.
    if (!res || !res.gmpAddons) {
      logger.info("No system add-ons list was returned.");
      await systemAddonLocation.cleanDirectories();
      return;
    }

    let addonList = new Map(
      res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));

    let getAddonsInLocation = (location) => {
      return new Promise(resolve => {
        XPIDatabase.getAddonsInLocation(location, resolve);
      });
    };

    let setMatches = (wanted, existing) => {
      if (wanted.size != existing.size)
        return false;

      for (let [id, addon] of existing) {
        let wantedInfo = wanted.get(id);

        if (!wantedInfo)
          return false;
        if (wantedInfo.spec.version != addon.version)
          return false;
      }

      return true;
    };

    // If this matches the current set in the profile location then do nothing.
    let updatedAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
    if (setMatches(addonList, updatedAddons)) {
      logger.info("Retaining existing updated system add-ons.");
      await systemAddonLocation.cleanDirectories();
      return;
    }

    // If this matches the current set in the default location then reset the
    // updated set.
    let defaultAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
    if (setMatches(addonList, defaultAddons)) {
      logger.info("Resetting system add-ons.");
      systemAddonLocation.resetAddonSet();
      await systemAddonLocation.cleanDirectories();
      return;
    }

    // Download all the add-ons
    async function downloadAddon(item) {
      try {
        let sourceAddon = updatedAddons.get(item.spec.id);
        if (sourceAddon && sourceAddon.version == item.spec.version) {
          // Copying the file to a temporary location has some benefits. If the
          // file is locked and cannot be read then we'll fall back to
          // downloading a fresh copy. It also means we don't have to remember
          // whether to delete the temporary copy later.
          try {
            let path = OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon");
            let unique = await OS.File.openUnique(path);
            unique.file.close();
            await OS.File.copy(sourceAddon._sourceBundle.path, unique.path);
            // Make sure to update file modification times so this is detected
            // as a new add-on.
            await OS.File.setDates(unique.path);
            item.path = unique.path;
          } catch (e) {
            logger.warn(`Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`, e);
          }
        }
        if (!item.path) {
          item.path = await ProductAddonChecker.downloadAddon(item.spec);
        }
        item.addon = await loadManifestFromFile(nsIFile(item.path), systemAddonLocation);
      } catch (e) {
        logger.error(`Failed to download system add-on ${item.spec.id}`, e);
      }
    }
    await Promise.all(Array.from(addonList.values()).map(downloadAddon));

    // The download promises all resolve regardless, now check if they all
    // succeeded
    let validateAddon = (item) => {
      if (item.spec.id != item.addon.id) {
        logger.warn(`Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`);
        return false;
      }

      if (item.spec.version != item.addon.version) {
        logger.warn(`Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`);
        return false;
      }

      if (!systemAddonLocation.isValidAddon(item.addon))
        return false;

      return true;
    }

    if (!Array.from(addonList.values()).every(item => item.path && item.addon && validateAddon(item))) {
      throw new Error("Rejecting updated system add-on set that either could not " +
                      "be downloaded or contained unusable add-ons.");
    }

    // Install into the install location
    logger.info("Installing new system add-on set");
    await systemAddonLocation.installAddonSet(Array.from(addonList.values())
      .map(a => a.addon));
  },

  /**
   * Verifies that all installed add-ons are still correctly signed.
   */
  async verifySignatures() {
    try {
      let addons = await XPIDatabase.getAddonList(a => true);

      let changes = {
        enabled: [],
        disabled: []
      };

      for (let addon of addons) {
        // The add-on might have vanished, we'll catch that on the next startup
        if (!addon._sourceBundle.exists())
          continue;

        let signedState = await verifyBundleSignedState(addon._sourceBundle, addon);

        if (signedState != addon.signedState) {
          addon.signedState = signedState;
          AddonManagerPrivate.callAddonListeners("onPropertyChanged",
                                                 addon.wrapper,
                                                 ["signedState"]);
        }

        let disabled = XPIProvider.updateAddonDisabledState(addon);
        if (disabled !== undefined)
          changes[disabled ? "disabled" : "enabled"].push(addon.id);
      }

      XPIDatabase.saveChanges();

      Services.obs.notifyObservers(null, "xpi-signature-changed", JSON.stringify(changes));
    } catch (err) {
      logger.error("XPI_verifySignature: " + err);
    }
  },

  /**
   * Adds a list of currently active add-ons to the next crash report.
   */
  addAddonsToCrashReporter() {
    if (!("nsICrashReporter" in Ci) ||
        !(Services.appinfo instanceof Ci.nsICrashReporter))
      return;

    // In safe mode no add-ons are loaded so we should not include them in the
    // crash report
    if (Services.appinfo.inSafeMode)
      return;

    let data = Array.from(XPIStates.enabledAddons(),
                          a => encoded`${a.id}:${a.version}`).join(",");

    try {
      Services.appinfo.annotateCrashReport("Add-ons", data);
    } catch (e) { }

    let TelemetrySession =
      Cu.import("resource://gre/modules/TelemetrySession.jsm", {}).TelemetrySession;
    TelemetrySession.setAddOns(data);
  },

  /**
   * Check the staging directories of install locations for any add-ons to be
   * installed or add-ons to be uninstalled.
   *
   * @param  aManifests
   *         A dictionary to add detected install manifests to for the purpose
   *         of passing through updated compatibility information
   * @return true if an add-on was installed or uninstalled
   */
  processPendingFileChanges(aManifests) {
    let changed = false;
    for (let location of this.installLocations) {
      aManifests[location.name] = {};
      // We can't install or uninstall anything in locked locations
      if (location.locked) {
        continue;
      }

      let stagingDir = location.getStagingDir();

      try {
        if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
          continue;
      } catch (e) {
        logger.warn("Failed to find staging directory", e);
        continue;
      }

      let seenFiles = [];
      // Use a snapshot of the directory contents to avoid possible issues with
      // iterating over a directory while removing files from it (the YAFFS2
      // embedded filesystem has this issue, see bug 772238), and to remove
      // normal files before their resource forks on OSX (see bug 733436).
      let stagingDirEntries = getDirectoryEntries(stagingDir, true);
      for (let stageDirEntry of stagingDirEntries) {
        let id = stageDirEntry.leafName;

        let isDir;
        try {
          isDir = stageDirEntry.isDirectory();
        } catch (e) {
          if (e.result != Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
            throw e;
          // If the file has already gone away then don't worry about it, this
          // can happen on OSX where the resource fork is automatically moved
          // with the data fork for the file. See bug 733436.
          continue;
        }

        if (!isDir) {
          if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
            id = id.substring(0, id.length - 4);
          } else {
            if (id.substring(id.length - 5).toLowerCase() != ".json") {
              logger.warn("Ignoring file: " + stageDirEntry.path);
              seenFiles.push(stageDirEntry.leafName);
            }
            continue;
          }
        }

        // Check that the directory's name is a valid ID.
        if (!gIDTest.test(id)) {
          logger.warn("Ignoring directory whose name is not a valid add-on ID: " +
               stageDirEntry.path);
          seenFiles.push(stageDirEntry.leafName);
          continue;
        }

        changed = true;

        if (isDir) {
          // Check if the directory contains an install manifest.
          let manifest = getManifestFileForDir(stageDirEntry);

          // If the install manifest doesn't exist uninstall this add-on in this
          // install location.
          if (!manifest) {
            logger.debug("Processing uninstall of " + id + " in " + location.name);

            try {
              let addonFile = location.getLocationForID(id);
              let addonToUninstall = syncLoadManifestFromFile(addonFile, location);
              if (addonToUninstall.bootstrap) {
                this.callBootstrapMethod(addonToUninstall, addonToUninstall._sourceBundle,
                                         "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
              }
            } catch (e) {
              logger.warn("Failed to call uninstall for " + id, e);
            }

            try {
              location.uninstallAddon(id);
              XPIStates.removeAddon(location.name, id);
              seenFiles.push(stageDirEntry.leafName);
            } catch (e) {
              logger.error("Failed to uninstall add-on " + id + " in " + location.name, e);
            }
            // The file check later will spot the removal and cleanup the database
            continue;
          }
        }

        aManifests[location.name][id] = null;
        let existingAddonID = id;

        let jsonfile = getFile(`${id}.json`, stagingDir);
        // Assume this was a foreign install if there is no cached metadata file
        let foreignInstall = !jsonfile.exists();
        let addon;

        try {
          addon = syncLoadManifestFromFile(stageDirEntry, location);
        } catch (e) {
          logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
          // This add-on can't be installed so just remove it now
          seenFiles.push(stageDirEntry.leafName);
          seenFiles.push(jsonfile.leafName);
          continue;
        }

        if (mustSign(addon.type) &&
            addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
          logger.warn("Refusing to install staged add-on " + id + " with signed state " + addon.signedState);
          seenFiles.push(stageDirEntry.leafName);
          seenFiles.push(jsonfile.leafName);
          continue;
        }

        // Check for a cached metadata for this add-on, it may contain updated
        // compatibility information
        if (!foreignInstall) {
          logger.debug("Found updated metadata for " + id + " in " + location.name);
          let fis = Cc["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Ci.nsIFileInputStream);
          let json = Cc["@mozilla.org/dom/json;1"].
                     createInstance(Ci.nsIJSON);

          try {
            fis.init(jsonfile, -1, 0, 0);
            let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
            addon.importMetadata(metadata);

            // Pass this through to addMetadata so it knows this add-on was
            // likely installed through the UI
            aManifests[location.name][id] = addon;
          } catch (e) {
            // If some data can't be recovered from the cached metadata then it
            // is unlikely to be a problem big enough to justify throwing away
            // the install, just log an error and continue
            logger.error("Unable to read metadata from " + jsonfile.path, e);
          } finally {
            fis.close();
          }
        }
        seenFiles.push(jsonfile.leafName);

        existingAddonID = addon.existingAddonID || id;

        var oldBootstrap = null;
        logger.debug("Processing install of " + id + " in " + location.name);
        let existingAddon = XPIStates.findAddon(existingAddonID);
        if (existingAddon && existingAddon.bootstrapped) {
          try {
            var file = existingAddon.file;
            if (file.exists()) {
              oldBootstrap = existingAddon;

              // We'll be replacing a currently active bootstrapped add-on so
              // call its uninstall method
              let newVersion = addon.version;
              let oldVersion = existingAddon;
              let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
                                    BOOTSTRAP_REASONS.ADDON_UPGRADE :
                                    BOOTSTRAP_REASONS.ADDON_DOWNGRADE;

              this.callBootstrapMethod(createAddonDetails(existingAddonID, existingAddon),
                                       file, "uninstall", uninstallReason,
                                       { newVersion });
              this.unloadBootstrapScope(existingAddonID);
              flushChromeCaches();
            }
          } catch (e) {
          }
        }

        try {
          addon._sourceBundle = location.installAddon({
            id,
            source: stageDirEntry,
            existingAddonID
          });
          XPIStates.addAddon(addon);
        } catch (e) {
          logger.error("Failed to install staged add-on " + id + " in " + location.name,
                e);
          // Re-create the staged install
          new StagedAddonInstall(location, stageDirEntry, addon);
          // Make sure not to delete the cached manifest json file
          seenFiles.pop();

          delete aManifests[location.name][id];

          if (oldBootstrap) {
            // Re-install the old add-on
            this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap),
                                     existingAddon, "install",
                                     BOOTSTRAP_REASONS.ADDON_INSTALL);
          }
          continue;
        }
      }

      try {
        location.cleanStagingDir(seenFiles);
      } catch (e) {
        // Non-critical, just saves some perf on startup if we clean this up.
        logger.debug("Error cleaning staging dir " + stagingDir.path, e);
      }
    }
    return changed;
  },

  /**
   * Installs any add-ons located in the extensions directory of the
   * application's distribution specific directory into the profile unless a
   * newer version already exists or the user has previously uninstalled the
   * distributed add-on.
   *
   * @param  aManifests
   *         A dictionary to add new install manifests to to save having to
   *         reload them later
   * @param  aAppChanged
   *         See checkForChanges
   * @return true if any new add-ons were installed
   */
  installDistributionAddons(aManifests, aAppChanged) {
    let distroDir;
    try {
      distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
    } catch (e) {
      return false;
    }

    if (!distroDir.exists())
      return false;

    if (!distroDir.isDirectory())
      return false;

    let changed = false;
    let profileLocation = this.installLocationsByName[KEY_APP_PROFILE];

    let entries = distroDir.directoryEntries
                           .QueryInterface(Ci.nsIDirectoryEnumerator);
    let entry;
    while ((entry = entries.nextFile)) {

      let id = entry.leafName;

      if (entry.isFile()) {
        if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
          id = id.substring(0, id.length - 4);
        } else {
          logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
          continue;
        }
      } else if (!entry.isDirectory()) {
        logger.debug("Ignoring distribution add-on that isn't a file or directory: " +
            entry.path);
        continue;
      }

      if (!gIDTest.test(id)) {
        logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " +
            entry.path);
        continue;
      }

      /* If this is not an upgrade and we've already handled this extension
       * just continue */
      if (!aAppChanged && Preferences.isSet(PREF_BRANCH_INSTALLED_ADDON + id)) {
        continue;
      }

      let addon;
      try {
        addon = syncLoadManifestFromFile(entry, profileLocation);
      } catch (e) {
        logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
        continue;
      }

      if (addon.id != id) {
        logger.warn("File entry " + entry.path + " contains an add-on with an " +
             "incorrect ID")
        continue;
      }

      let existingEntry = null;
      try {
        existingEntry = profileLocation.getLocationForID(id);
      } catch (e) {
      }

      if (existingEntry) {
        let existingAddon;
        try {
          existingAddon = syncLoadManifestFromFile(existingEntry, profileLocation);

          if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
            continue;
        } catch (e) {
          // Bad add-on in the profile so just proceed and install over the top
          logger.warn("Profile contains an add-on with a bad or missing install " +
               "manifest at " + existingEntry.path + ", overwriting", e);
        }
      } else if (Preferences.get(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
        continue;
      }

      // Install the add-on
      try {
        addon._sourceBundle = profileLocation.installAddon({ id, source: entry, action: "copy" });
        XPIStates.addAddon(addon);
        logger.debug("Installed distribution add-on " + id);

        Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)

        // aManifests may contain a copy of a newly installed add-on's manifest
        // and we'll have overwritten that so instead cache our install manifest
        // which will later be put into the database in processFileChanges
        if (!(KEY_APP_PROFILE in aManifests))
          aManifests[KEY_APP_PROFILE] = {};
        aManifests[KEY_APP_PROFILE][id] = addon;
        changed = true;
      } catch (e) {
        logger.error("Failed to install distribution add-on " + entry.path, e);
      }
    }

    entries.close();

    return changed;
  },

  /**
   * Imports the xpinstall permissions from preferences into the permissions
   * manager for the user to change later.
   */
  importPermissions() {
    PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
                                     XPI_PERMISSION);
  },

  getDependentAddons(aAddon) {
    return Array.from(XPIDatabase.getAddons())
                .filter(addon => addon.dependencies.includes(aAddon.id));
  },

  /**
   * Returns the add-on state data for the restartful extensions which
   * should be available in safe mode. In particular, this means the
   * default theme, and only the default theme.
   *
   * @returns {object}
   */
  getSafeModeExtensions() {
    let loc = XPIStates.getLocation(KEY_APP_GLOBAL);
    let state = loc.get(ADDON_ID_DEFAULT_THEME);

    // Use the default state data for the default theme, but always mark
    // it enabled, in case another theme is enabled in normal mode.
    let addonData = state.toJSON();
    addonData.enabled = true;

    return {
      [KEY_APP_GLOBAL]: {
        path: loc.path,
        addons: { [ADDON_ID_DEFAULT_THEME]: addonData },
      },
    };
  },

  /**
   * Checks for any changes that have occurred since the last time the
   * application was launched.
   *
   * @param  aAppChanged
   *         A tri-state value. Undefined means the current profile was created
   *         for this session, true means the profile already existed but was
   *         last used with an application with a different version number,
   *         false means that the profile was last used by this version of the
   *         application.
   * @param  aOldAppVersion
   *         The version of the application last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aOldPlatformVersion
   *         The version of the platform last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @return true if a change requiring a restart was detected
   */
  checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    logger.debug("checkForChanges");

    // Keep track of whether and why we need to open and update the database at
    // startup time.
    let updateReasons = [];
    if (aAppChanged) {
      updateReasons.push("appChanged");
    }

    let installChanged = XPIStates.getInstallState(aAppChanged === false);
    if (installChanged) {
      updateReasons.push("directoryState");
    }

    // First install any new add-ons into the locations, if there are any
    // changes then we must update the database with the information in the
    // install locations
    let manifests = {};
    let updated = this.processPendingFileChanges(manifests);
    if (updated) {
      updateReasons.push("pendingFileChanges");
    }

    // This will be true if the previous session made changes that affect the
    // active state of add-ons but didn't commit them properly (normally due
    // to the application crashing)
    let hasPendingChanges = Preferences.get(PREF_PENDING_OPERATIONS);
    if (hasPendingChanges) {
      updateReasons.push("hasPendingChanges");
    }

    // If the application has changed then check for new distribution add-ons
    if (Preferences.get(PREF_INSTALL_DISTRO_ADDONS, true)) {
      updated = this.installDistributionAddons(manifests, aAppChanged);
      if (updated) {
        updateReasons.push("installDistributionAddons");
      }
    }

    let haveAnyAddons = (XPIStates.size > 0);

    // If the schema appears to have changed then we should update the database
    if (DB_SCHEMA != Preferences.get(PREF_DB_SCHEMA, 0)) {
      // If we don't have any add-ons, just update the pref, since we don't need to
      // write the database
      if (!haveAnyAddons) {
        logger.debug("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
        Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
      } else {
        updateReasons.push("schemaChanged");
      }
    }

    // If the database doesn't exist and there are add-ons installed then we
    // must update the database however if there are no add-ons then there is
    // no need to update the database.
    let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
    if (!dbFile.exists() && haveAnyAddons) {
      updateReasons.push("needNewDatabase");
    }

    // Catch and log any errors during the main startup
    try {
      let extensionListChanged = false;
      // If the database needs to be updated then open it and then update it
      // from the filesystem
      if (updateReasons.length > 0) {
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_load_reasons", updateReasons);
        XPIDatabase.syncLoadDB(false);
        try {
          extensionListChanged = XPIDatabaseReconcile.processFileChanges(manifests,
                                                                         aAppChanged,
                                                                         aOldAppVersion,
                                                                         aOldPlatformVersion,
                                                                         updateReasons.includes("schemaChanged"));
        } catch (e) {
          logger.error("Failed to process extension changes at startup", e);
        }
      }

      if (aAppChanged) {
        // When upgrading the app and using a custom skin make sure it is still
        // compatible otherwise switch back the default
        if (this.currentSkin != this.defaultSkin) {
          let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
          if (!oldSkin || oldSkin.disabled)
            this.enableDefaultTheme();
        }

        // When upgrading remove the old extensions cache to force older
        // versions to rescan the entire list of extensions
        let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
        try {
          if (oldCache.exists())
            oldCache.remove(true);
        } catch (e) {
          logger.warn("Unable to remove old extension cache " + oldCache.path, e);
        }
      }

      if (Services.appinfo.inSafeMode) {
        aomStartup.initializeExtensions(this.getSafeModeExtensions());
        logger.debug("Initialized safe mode add-ons");
        return false;
      }

      // If the application crashed before completing any pending operations then
      // we should perform them now.
      if (extensionListChanged || hasPendingChanges) {
        this._updateActiveAddons();

        // Serialize and deserialize so we get the expected JSON data.
        let state = JSON.parse(JSON.stringify(XPIStates));
        aomStartup.initializeExtensions(state);
        return true;
      }

      aomStartup.initializeExtensions(XPIStates.initialStateData);

      logger.debug("No changes found");
    } catch (e) {
      logger.error("Error during startup file checks", e);
    }

    return false;
  },

  _updateActiveAddons() {
    logger.debug("Updating database with changes to installed add-ons");
    XPIDatabase.updateActiveAddons();
    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
  },

  /**
   * Gets an array of add-ons which were placed in a known install location
   * prior to startup of the current session, were detected by a directory scan
   * of those locations, and are currently disabled.
   *
   * @returns {Promise<Array<Addon>>}
   */
  async getNewSideloads() {
    if (XPIStates.getInstallState(false)) {
      // We detected changes. Update the database to account for them.
      await XPIDatabase.asyncLoadDB(false);
      XPIDatabaseReconcile.processFileChanges({}, false);
      this._updateActiveAddons();
    }

    let addons = await Promise.all(
      Array.from(XPIStates.sideLoadedAddons.keys(),
                 id => AddonManager.getAddonByID(id)));

    return addons.filter(addon => (addon.seen === false &&
                                   addon.permissions & AddonManager.PERM_CAN_ENABLE));
  },

  /**
   * Called to test whether this provider supports installing a particular
   * mimetype.
   *
   * @param  aMimetype
   *         The mimetype to check for
   * @return true if the mimetype is application/x-xpinstall
   */
  supportsMimetype(aMimetype) {
    return aMimetype == "application/x-xpinstall";
  },

  /**
   * Called to test whether installing XPI add-ons is enabled.
   *
   * @return true if installing is enabled
   */
  isInstallEnabled() {
    // Default to enabled if the preference does not exist
    return Preferences.get(PREF_XPI_ENABLED, true);
  },

  /**
   * Called to test whether installing XPI add-ons by direct URL requests is
   * whitelisted.
   *
   * @return true if installing by direct requests is whitelisted
   */
  isDirectRequestWhitelisted() {
    // Default to whitelisted if the preference does not exist.
    return Preferences.get(PREF_XPI_DIRECT_WHITELISTED, true);
  },

  /**
   * Called to test whether installing XPI add-ons from file referrers is
   * whitelisted.
   *
   * @return true if installing from file referrers is whitelisted
   */
  isFileRequestWhitelisted() {
    // Default to whitelisted if the preference does not exist.
    return Preferences.get(PREF_XPI_FILE_WHITELISTED, true);
  },

  /**
   * Called to test whether installing XPI add-ons from a URI is allowed.
   *
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @return true if installing is allowed
   */
  isInstallAllowed(aInstallingPrincipal) {
    if (!this.isInstallEnabled())
      return false;

    let uri = aInstallingPrincipal.URI;

    // Direct requests without a referrer are either whitelisted or blocked.
    if (!uri)
      return this.isDirectRequestWhitelisted();

    // Local referrers can be whitelisted.
    if (this.isFileRequestWhitelisted() &&
        (uri.schemeIs("chrome") || uri.schemeIs("file")))
      return true;

    this.importPermissions();

    let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION);
    if (permission == Ci.nsIPermissionManager.DENY_ACTION)
      return false;

    let requireWhitelist = Preferences.get(PREF_XPI_WHITELIST_REQUIRED, true);
    if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
      return false;

    let requireSecureOrigin = Preferences.get(PREF_INSTALL_REQUIRESECUREORIGIN, true);
    let safeSchemes = ["https", "chrome", "file"];
    if (requireSecureOrigin && safeSchemes.indexOf(uri.scheme) == -1)
      return false;

    return true;
  },

  // Identify temporary install IDs.
  isTemporaryInstallID(id) {
    return id.endsWith(TEMPORARY_ADDON_SUFFIX);
  },

  /**
   * Called to get an AddonInstall to download and install an add-on from a URL.
   *
   * @param  aUrl
   *         The URL to be installed
   * @param  aHash
   *         A hash for the install
   * @param  aName
   *         A name for the install
   * @param  aIcons
   *         Icon URLs for the install
   * @param  aVersion
   *         A version for the install
   * @param  aBrowser
   *         The browser performing the install
   * @param  aCallback
   *         A callback to pass the AddonInstall to
   */
  getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser,
                             aCallback) {
    let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
    let url = Services.io.newURI(aUrl);

    let options = {
      hash: aHash,
      browser: aBrowser,
      name: aName,
      icons: aIcons,
      version: aVersion,
    };

    if (url instanceof Ci.nsIFileURL) {
      let install = new LocalAddonInstall(location, url, options);
      install.init().then(() => { aCallback(install.wrapper); });
    } else {
      let install = new DownloadAddonInstall(location, url, options);
      aCallback(install.wrapper);
    }
  },

  /**
   * Called to get an AddonInstall to install an add-on from a local file.
   *
   * @param  aFile
   *         The file to be installed
   * @param  aCallback
   *         A callback to pass the AddonInstall to
   */
  getInstallForFile(aFile, aCallback) {
    createLocalInstall(aFile).then(install => {
      aCallback(install ? install.wrapper : null);
    });
  },

  /**
   * Temporarily installs add-on from a local XPI file or directory.
   * As this is intended for development, the signature is not checked and
   * the add-on does not persist on application restart.
   *
   * @param aFile
   *        An nsIFile for the unpacked add-on directory or XPI file.
   *
   * @return See installAddonFromLocation return value.
   */
  installTemporaryAddon(aFile) {
    return this.installAddonFromLocation(aFile, TemporaryInstallLocation);
  },

  /**
   * Permanently installs add-on from a local XPI file or directory.
   * The signature is checked but the add-on persist on application restart.
   *
   * @param aFile
   *        An nsIFile for the unpacked add-on directory or XPI file.
   *
   * @return See installAddonFromLocation return value.
   */
  async installAddonFromSources(aFile) {
    let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
    return this.installAddonFromLocation(aFile, location, "proxy");
  },

  /**
   * Installs add-on from a local XPI file or directory.
   *
   * @param aFile
   *        An nsIFile for the unpacked add-on directory or XPI file.
   * @param aInstallLocation
   *        Define a custom install location object to use for the install.
   * @param aInstallAction
   *        Optional action mode to use when installing the addon
   *        (see MutableDirectoryInstallLocation.installAddon)
   *
   * @return a Promise that resolves to an Addon object on success, or rejects
   *         if the add-on is not a valid restartless add-on or if the
   *         same ID is already installed.
   */
  async installAddonFromLocation(aFile, aInstallLocation, aInstallAction) {
    if (aFile.exists() && aFile.isFile()) {
      flushJarCache(aFile);
    }
    let addon = await loadManifestFromFile(aFile, aInstallLocation);

    aInstallLocation.installAddon({ id: addon.id, source: aFile, action: aInstallAction });

    if (addon.appDisabled) {
      let message = `Add-on ${addon.id} is not compatible with application version.`;

      let app = addon.matchingTargetApplication;
      if (app) {
        if (app.minVersion) {
          message += ` add-on minVersion: ${app.minVersion}.`;
        }
        if (app.maxVersion) {
          message += ` add-on maxVersion: ${app.maxVersion}.`;
        }
      }
      throw new Error(message);
    }

    if (!addon.bootstrap) {
      throw new Error("Only restartless (bootstrap) add-ons"
                    + " can be installed from sources:", addon.id);
    }
    let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
    let oldAddon = await new Promise(
                   resolve => XPIDatabase.getVisibleAddonForID(addon.id, resolve));

    let extraParams = {};
    extraParams.temporarilyInstalled = aInstallLocation === TemporaryInstallLocation;
    if (oldAddon) {
      if (!oldAddon.bootstrap) {
        logger.warn("Non-restartless Add-on is already installed", addon.id);
        throw new Error("Non-restartless add-on with ID "
                        + oldAddon.id + " is already installed");
      } else {
        logger.warn("Addon with ID " + oldAddon.id + " already installed,"
                    + " older version will be disabled");

        let existingAddonID = oldAddon.id;
        let existingAddon = oldAddon._sourceBundle;

        // We'll be replacing a currently active bootstrapped add-on so
        // call its uninstall method
        let newVersion = addon.version;
        let oldVersion = oldAddon.version;

        if (Services.vc.compare(newVersion, oldVersion) >= 0) {
          installReason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
        } else {
          installReason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
        }
        let uninstallReason = installReason;

        extraParams.newVersion = newVersion;
        extraParams.oldVersion = oldVersion;

        if (oldAddon.active) {
          XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
                                          "shutdown", uninstallReason,
                                          extraParams);
        }
        this.callBootstrapMethod(oldAddon, existingAddon,
                                 "uninstall", uninstallReason, extraParams);
        this.unloadBootstrapScope(existingAddonID);
        flushChromeCaches();
      }
    } else {
      addon.installDate = Date.now();
    }

    let file = addon._sourceBundle;

    XPIProvider._addURIMapping(addon.id, file);
    XPIProvider.callBootstrapMethod(addon, file, "install", installReason, extraParams);
    addon.state = AddonManager.STATE_INSTALLED;
    logger.debug("Install of temporary addon in " + aFile.path + " completed.");
    addon.visible = true;
    addon.enabled = true;
    addon.active = true;
    // WebExtension themes are installed as disabled, fix that here.
    addon.userDisabled = false;

    addon = XPIDatabase.addAddonMetadata(addon, file.persistentDescriptor);

    XPIStates.addAddon(addon);
    XPIDatabase.saveChanges();
    XPIStates.save();

    AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper,
                                           false);
    XPIProvider.callBootstrapMethod(addon, file, "startup", installReason, extraParams);
    AddonManagerPrivate.callInstallListeners("onExternalInstall",
                                             null, addon.wrapper,
                                             oldAddon ? oldAddon.wrapper : null,
                                             false);
    AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper);

    // Notify providers that a new theme has been enabled.
    if (isTheme(addon.type))
      AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);

    return addon.wrapper;
  },

  /**
   * Returns an Addon corresponding to an instance ID.
   * @param aInstanceID
   *        An Addon Instance ID
   * @return {Promise}
   * @resolves The found Addon or null if no such add-on exists.
   * @rejects  Never
   * @throws if the aInstanceID argument is not specified
   */
   getAddonByInstanceID(aInstanceID) {
     if (!aInstanceID || typeof aInstanceID != "symbol")
       throw Components.Exception("aInstanceID must be a Symbol()",
                                  Cr.NS_ERROR_INVALID_ARG);

     for (let [id, val] of this.activeAddons) {
       if (aInstanceID == val.instanceID) {
         return new Promise(resolve => this.getAddonByID(id, resolve));
       }
     }

     return Promise.resolve(null);
   },

  /**
   * Removes an AddonInstall from the list of active installs.
   *
   * @param  install
   *         The AddonInstall to remove
   */
  removeActiveInstall(aInstall) {
    this.installs.delete(aInstall);
  },

  /**
   * Called to get an Addon with a particular ID.
   *
   * @param  aId
   *         The ID of the add-on to retrieve
   * @param  aCallback
   *         A callback to pass the Addon to
   */
  getAddonByID(aId, aCallback) {
    XPIDatabase.getVisibleAddonForID(aId, function(aAddon) {
      aCallback(aAddon ? aAddon.wrapper : null);
    });
  },

  /**
   * Called to get Addons of a particular type.
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types.
   * @param  aCallback
   *         A callback to pass an array of Addons to
   */
  getAddonsByTypes(aTypes, aCallback) {
    let typesToGet = getAllAliasesForTypes(aTypes);
    if (typesToGet && !typesToGet.some(type => ALL_EXTERNAL_TYPES.has(type))) {
      aCallback([]);
      return;
    }

    XPIDatabase.getVisibleAddons(typesToGet, function(aAddons) {
      aCallback(aAddons.map(a => a.wrapper));
    });
  },

  /**
   * Called to get active Addons of a particular type
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types.
   * @returns {Promise<Array<Addon>>}
   */
  getActiveAddons(aTypes) {
    // If we already have the database loaded, returning full info is fast.
    if (this.isDBLoaded) {
      return new Promise(resolve => {
        this.getAddonsByTypes(aTypes, addons => {
          // The thing with experiments is an ugly hack but we want
          // Experiments.jsm to use this interface instead of getAddonsByTypes.
          // They'll go away at some point and we can forget this ever happened.
          resolve(addons.filter(addon => addon.isActive ||
                                       (addon.type == "experiment" && !addon.appDisabled)));
        });
      });
    }

    // Construct addon-like objects with the information we already
    // have in memory.
    if (!XPIStates.db) {
      return Promise.reject(new Error("XPIStates not yet initialized"));
    }

    let result = [];
    for (let addon of XPIStates.enabledAddons()) {
      if (aTypes && !aTypes.includes(addon.type)) {
        continue;
      }
      let location = this.installLocationsByName[addon.location.name];
      let scope, isSystem;
      if (location) {
        ({scope, isSystem} = location);
      }
      result.push({
        id: addon.id,
        version: addon.version,
        type: addon.type,
        updateDate: addon.lastModifiedTime,
        scope,
        isSystem,
        isWebExtension: isWebExtension(addon),
        multiprocessCompatible: addon.multiprocessCompatible,
      });
    }

    return Promise.resolve(result);
  },


  /**
   * Obtain an Addon having the specified Sync GUID.
   *
   * @param  aGUID
   *         String GUID of add-on to retrieve
   * @param  aCallback
   *         A callback to pass the Addon to. Receives null if not found.
   */
  getAddonBySyncGUID(aGUID, aCallback) {
    XPIDatabase.getAddonBySyncGUID(aGUID, function(aAddon) {
      aCallback(aAddon ? aAddon.wrapper : null);
    });
  },

  /**
   * Called to get Addons that have pending operations.
   *
   * @param  aTypes
   *         An array of types to fetch. Can be null to get all types
   * @param  aCallback
   *         A callback to pass an array of Addons to
   */
  getAddonsWithOperationsByTypes(aTypes, aCallback) {
    let typesToGet = getAllAliasesForTypes(aTypes);

    XPIDatabase.getVisibleAddonsWithPendingOperations(typesToGet, function(aAddons) {
      let results = aAddons.map(a => a.wrapper);
      for (let install of XPIProvider.installs) {
        if (install.state == AddonManager.STATE_INSTALLED &&
            !(install.addon.inDatabase))
          results.push(install.addon.wrapper);
      }
      aCallback(results);
    });
  },

  /**
   * Called to get the current AddonInstalls, optionally limiting to a list of
   * types.
   *
   * @param  aTypes
   *         An array of types or null to get all types
   * @param  aCallback
   *         A callback to pass the array of AddonInstalls to
   */
  getInstallsByTypes(aTypes, aCallback) {
    let results = [...this.installs];
    if (aTypes) {
      results = results.filter(install => {
        return aTypes.includes(getExternalType(install.type));
      });
    }

    aCallback(results.map(install => install.wrapper));
  },

  /**
   * Synchronously map a URI to the corresponding Addon ID.
   *
   * Mappable URIs are limited to in-application resources belonging to the
   * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
   * but do not include URIs from meta data, such as the add-on homepage.
   *
   * @param  aURI
   *         nsIURI to map or null
   * @return string containing the Addon ID
   * @see    AddonManager.mapURIToAddonID
   * @see    amIAddonManager.mapURIToAddonID
   */
  mapURIToAddonID(aURI) {
    // Returns `null` instead of empty string if the URI can't be mapped.
    return AddonPathService.mapURIToAddonId(aURI) || null;
  },

  /**
   * Called when a new add-on has been enabled when only one add-on of that type
   * can be enabled.
   *
   * @param  aId
   *         The ID of the newly enabled add-on
   * @param  aType
   *         The type of the newly enabled add-on
   * @param  aPendingRestart
   *         true if the newly enabled add-on will only become enabled after a
   *         restart
   */
  addonChanged(aId, aType, aPendingRestart) {
    // We only care about themes in this provider
    if (!isTheme(aType))
      return;

    if (!aId) {
      // Fallback to the default theme when no theme was enabled
      this.enableDefaultTheme();
      return;
    }

    // Look for the previously enabled theme and find the internalName of the
    // currently selected theme
    let previousTheme = null;
    let newSkin = this.defaultSkin;
    let addons = XPIDatabase.getAddonsByType("theme", "webextension-theme");
    for (let theme of addons) {
      if (!theme.visible)
        return;
      let isChangedAddon = (theme.id == aId);
      if (isWebExtension(theme.type)) {
        if (!isChangedAddon)
          this.updateAddonDisabledState(theme, true, undefined, aPendingRestart);
      } else if (isChangedAddon) {
        newSkin = theme.internalName;
      } else if (theme.userDisabled == false && !theme.pendingUninstall) {
        previousTheme = theme;
      }
    }

    if (aPendingRestart) {
      Services.prefs.setBoolPref(PREF_SKIN_SWITCHPENDING, true);
      Services.prefs.setCharPref(PREF_SKIN_TO_SELECT, newSkin);
    } else if (newSkin == this.currentSkin) {
      try {
        Services.prefs.clearUserPref(PREF_SKIN_SWITCHPENDING);
      } catch (e) { }
      try {
        Services.prefs.clearUserPref(PREF_SKIN_TO_SELECT);
      } catch (e) { }
    } else {
      Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, newSkin);
      this.currentSkin = newSkin;
    }
    this.selectedSkin = newSkin;

    // Flush the preferences to disk so they don't get out of sync with the
    // database
    Services.prefs.savePrefFile(null);

    // Mark the previous theme as disabled. This won't cause recursion since
    // only enabled calls notifyAddonChanged.
    if (previousTheme)
      this.updateAddonDisabledState(previousTheme, true, undefined, aPendingRestart);
  },

  /**
   * Update the appDisabled property for all add-ons.
   */
  updateAddonAppDisabledStates() {
    let addons = XPIDatabase.getAddons();
    for (let addon of addons) {
      this.updateAddonDisabledState(addon);
    }
  },

  /**
   * Update the repositoryAddon property for all add-ons.
   *
   * @param  aCallback
   *         Function to call when operation is complete.
   */
  updateAddonRepositoryData(aCallback) {
    XPIDatabase.getVisibleAddons(null, aAddons => {
      let pending = aAddons.length;
      logger.debug("updateAddonRepositoryData found " + pending + " visible add-ons");
      if (pending == 0) {
        aCallback();
        return;
      }

      function notifyComplete() {
        if (--pending == 0)
          aCallback();
      }

      for (let addon of aAddons) {
        AddonRepository.getCachedAddonByID(addon.id, aRepoAddon => {
          if (aRepoAddon) {
            logger.debug("updateAddonRepositoryData got info for " + addon.id);
            addon._repositoryAddon = aRepoAddon;
            addon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
            this.updateAddonDisabledState(addon);
          }

          notifyComplete();
        });
      }
    });
  },

  /**
   * When the previously selected theme is removed this method will be called
   * to enable the default theme.
   */
  enableDefaultTheme() {
    logger.debug("Activating default theme");
    let addon = XPIDatabase.getVisibleAddonForInternalName(this.defaultSkin);
    if (addon) {
      if (addon.userDisabled) {
        this.updateAddonDisabledState(addon, false);
      } else if (!this.extensionsActive) {
        // During startup we may end up trying to enable the default theme when
        // the database thinks it is already enabled (see f.e. bug 638847). In
        // this case just force the theme preferences to be correct
        Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
                                   addon.internalName);
        this.currentSkin = this.selectedSkin = addon.internalName;
        Preferences.reset(PREF_SKIN_TO_SELECT);
        Preferences.reset(PREF_SKIN_SWITCHPENDING);
      } else {
        logger.warn("Attempting to activate an already active default theme");
      }
    } else {
      logger.warn("Unable to activate the default theme");
    }
  },

  onDebugConnectionChange({what, connection}) {
    if (what != "opened")
      return;

    for (let [id, val] of this.activeAddons) {
      connection.setAddonOptions(
        id, { global: val.bootstrapScope });
    }
  },

  /**
   * Notified when a preference we're interested in has changed.
   *
   * @see nsIObserver
   */
  observe(aSubject, aTopic, aData) {
    if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
      if (!aData || aData == XPI_PERMISSION) {
        this.importPermissions();
      }
      return;
    } else if (aTopic == NOTIFICATION_TOOLBOX_CONNECTION_CHANGE) {
      this.onDebugConnectionChange(aSubject.wrappedJSObject);
      return;
    }

    if (aTopic == "nsPref:changed") {
      switch (aData) {
      case PREF_EM_MIN_COMPAT_APP_VERSION:
        this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
                                                       null);
        this.updateAddonAppDisabledStates();
        break;
      case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
        this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                            null);
        this.updateAddonAppDisabledStates();
        break;
      case PREF_XPI_SIGNATURES_REQUIRED:
      case PREF_ALLOW_LEGACY:
      case PREF_ALLOW_NON_MPC:
        this.updateAddonAppDisabledStates();
        break;

      case PREF_E10S_ADDON_BLOCKLIST:
      case PREF_E10S_ADDON_POLICY:
        XPIDatabase.updateAddonsBlockingE10s();
        break;
      }
    }
  },

  /**
   * Determine if an add-on should be blocking e10s if enabled.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if enabling the add-on should block e10s
   */
  isBlockingE10s(aAddon) {
    if (aAddon.type != "extension" &&
        aAddon.type != "theme" &&
        aAddon.type != "webextension" &&
        aAddon.type != "webextension-theme")
      return false;

    // The hotfix is exempt
    let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
    if (hotfixID && hotfixID == aAddon.id)
      return false;

    // The default theme is exempt
    if (aAddon.type == "theme" &&
        aAddon.internalName == XPIProvider.defaultSkin)
      return false;

    // System add-ons are exempt
    let loc = aAddon._installLocation;
    if (loc && loc.isSystem)
      return false;

    if (isAddonPartOfE10SRollout(aAddon)) {
      Preferences.set(PREF_E10S_HAS_NONEXEMPT_ADDON, true);
      return false;
    }

    logger.debug("Add-on " + aAddon.id + " blocks e10s rollout.");
    return true;
  },

  /**
   * Determine if an add-on should be blocking multiple content processes.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if enabling the add-on should block multiple content processes.
   */
  isBlockingE10sMulti(aAddon) {
    // WebExtensions have type = "webextension" or type="webextension-theme",
    // so they won't block multi.
    if (aAddon.type != "extension")
      return false;

    // The hotfix is exempt
    let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
    if (hotfixID && hotfixID == aAddon.id)
      return false;

    // System add-ons are exempt
    let loc = aAddon._installLocation;
    if (loc && loc.isSystem)
      return false;

    return true;
  },

  /**
   * In some cases having add-ons active blocks e10s but turning off e10s
   * requires a restart so some add-ons that are normally restartless will
   * require a restart to install or enable.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if enabling the add-on requires a restart
   */
  e10sBlocksEnabling(aAddon) {
    // If the preference isn't set then don't block anything
    if (!Preferences.get(PREF_E10S_BLOCK_ENABLE, false))
      return false;

    // If e10s isn't active then don't block anything
    if (!Services.appinfo.browserTabsRemoteAutostart)
      return false;

    return this.isBlockingE10s(aAddon);
  },

  /**
   * Tests whether enabling an add-on will require a restart.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if the operation requires a restart
   */
  enableRequiresRestart(aAddon) {
    // If the platform couldn't have activated extensions then we can make
    // changes without any restart.
    if (!this.extensionsActive)
      return false;

    // If the application is in safe mode then any change can be made without
    // restarting
    if (Services.appinfo.inSafeMode)
      return false;

    // Anything that is active is already enabled
    if (aAddon.active)
      return false;

    if (isTheme(aAddon.type)) {
      if (isWebExtension(aAddon.type)) {
        // Enabling a WebExtension type theme requires a restart ONLY when the
        // theme-to-be-disabled requires a restart.
        let theme = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
        return !theme || this.disableRequiresRestart(theme);
      }

      // If the theme is already the theme in use then no restart is necessary.
      // This covers the case where the default theme is in use but a
      // lightweight theme is considered active.
      return aAddon.internalName != this.currentSkin;
    }

    if (this.e10sBlocksEnabling(aAddon))
      return true;

    return !aAddon.bootstrap;
  },

  /**
   * Tests whether disabling an add-on will require a restart.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if the operation requires a restart
   */
  disableRequiresRestart(aAddon) {
    // If the platform couldn't have activated up extensions then we can make
    // changes without any restart.
    if (!this.extensionsActive)
      return false;

    // If the application is in safe mode then any change can be made without
    // restarting
    if (Services.appinfo.inSafeMode)
      return false;

    // Anything that isn't active is already disabled
    if (!aAddon.active)
      return false;

    if (aAddon.type == "theme") {
      // Non-default themes always require a restart to disable since it will
      // be switching from one theme to another or to the default theme and a
      // lightweight theme.
      if (aAddon.internalName != this.defaultSkin)
        return true;

      // The default theme requires a restart to disable if we are in the
      // process of switching to a different theme. Note that this makes the
      // disabled flag of operationsRequiringRestart incorrect for the default
      // theme (it will be false most of the time). Bug 520124 would be required
      // to fix it. For the UI this isn't a problem since we never try to
      // disable or uninstall the default theme.
      return this.selectedSkin != this.currentSkin;
    }

    return !aAddon.bootstrap;
  },

  /**
   * Tests whether installing an add-on will require a restart.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if the operation requires a restart
   */
  installRequiresRestart(aAddon) {
    // If the platform couldn't have activated up extensions then we can make
    // changes without any restart.
    if (!this.extensionsActive)
      return false;

    // If the application is in safe mode then any change can be made without
    // restarting
    if (Services.appinfo.inSafeMode)
      return false;

    // Add-ons that are already installed don't require a restart to install.
    // This wouldn't normally be called for an already installed add-on (except
    // for forming the operationsRequiringRestart flags) so is really here as
    // a safety measure.
    if (aAddon.inDatabase)
      return false;

    // If we have an AddonInstall for this add-on then we can see if there is
    // an existing installed add-on with the same ID
    if ("_install" in aAddon && aAddon._install) {
      // If there is an existing installed add-on and uninstalling it would
      // require a restart then installing the update will also require a
      // restart
      let existingAddon = aAddon._install.existingAddon;
      if (existingAddon && this.uninstallRequiresRestart(existingAddon))
        return true;
    }

    // If the add-on is not going to be active after installation then it
    // doesn't require a restart to install.
    if (aAddon.disabled)
      return false;

    if (this.e10sBlocksEnabling(aAddon))
      return true;

    // Themes will require a restart (even if dynamic switching is enabled due
    // to some caching issues) and non-bootstrapped add-ons will require a
    // restart
    return aAddon.type == "theme" || !aAddon.bootstrap;
  },

  /**
   * Tests whether uninstalling an add-on will require a restart.
   *
   * @param  aAddon
   *         The add-on to test
   * @return true if the operation requires a restart
   */
  uninstallRequiresRestart(aAddon) {
    // If the platform couldn't have activated up extensions then we can make
    // changes without any restart.
    if (!this.extensionsActive)
      return false;

    // If the application is in safe mode then any change can be made without
    // restarting
    if (Services.appinfo.inSafeMode)
      return false;

    // If the add-on can be disabled without a restart then it can also be
    // uninstalled without a restart
    return this.disableRequiresRestart(aAddon);
  },

  /**
   * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
   * values as constants in the scope. This will also add information about the
   * add-on to the bootstrappedAddons dictionary and notify the crash reporter
   * that new add-ons have been loaded.
   *
   * @param  aId
   *         The add-on's ID
   * @param  aFile
   *         The nsIFile for the add-on
   * @param  aVersion
   *         The add-on's version
   * @param  aType
   *         The type for the add-on
   * @param  aMultiprocessCompatible
   *         Boolean indicating whether the add-on is compatible with electrolysis.
   * @param  aRunInSafeMode
   *         Boolean indicating whether the add-on can run in safe mode.
   * @param  aDependencies
   *         An array of add-on IDs on which this add-on depends.
   * @param  hasEmbeddedWebExtension
   *         Boolean indicating whether the add-on has an embedded webextension.
   * @return a JavaScript scope
   */
  loadBootstrapScope(aId, aFile, aVersion, aType,
                               aMultiprocessCompatible, aRunInSafeMode,
                               aDependencies, hasEmbeddedWebExtension) {
    this.activeAddons.set(aId, {
      bootstrapScope: null,
      // a Symbol passed to this add-on, which it can use to identify itself
      instanceID: Symbol(aId),
    });

    // Mark the add-on as active for the crash reporter before loading
    this.addAddonsToCrashReporter();

    let activeAddon = this.activeAddons.get(aId);

    // Locales only contain chrome and can't have bootstrap scripts
    if (aType == "locale") {
      return;
    }

    logger.debug("Loading bootstrap scope from " + aFile.path);

    let principal = Cc["@mozilla.org/systemprincipal;1"].
                    createInstance(Ci.nsIPrincipal);
    if (!aMultiprocessCompatible && Preferences.get(PREF_INTERPOSITION_ENABLED, false)) {
      let interposition = Cc["@mozilla.org/addons/multiprocess-shims;1"].
        getService(Ci.nsIAddonInterposition);
      Cu.setAddonInterposition(aId, interposition);
      Cu.allowCPOWsInAddon(aId, true);
    }

    if (!aFile.exists()) {
      activeAddon.bootstrapScope =
        new Cu.Sandbox(principal, { sandboxName: aFile.path,
                                    wantGlobalProperties: ["indexedDB"],
                                    addonId: aId,
                                    metadata: { addonID: aId } });
      logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
      return;
    }

    let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
    if (aType == "dictionary")
      uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
    else if (isWebExtension(aType))
      uri = "resource://gre/modules/addons/WebExtensionBootstrap.js"
    else if (aType == "apiextension")
      uri = "resource://gre/modules/addons/APIExtensionBootstrap.js"

    activeAddon.bootstrapScope =
      new Cu.Sandbox(principal, { sandboxName: uri,
                                  wantGlobalProperties: ["indexedDB"],
                                  addonId: aId,
                                  metadata: { addonID: aId, URI: uri } });

    try {
      // Copy the reason values from the global object into the bootstrap scope.
      for (let name in BOOTSTRAP_REASONS)
        activeAddon.bootstrapScope[name] = BOOTSTRAP_REASONS[name];

      // Add other stuff that extensions want.
      Object.assign(activeAddon.bootstrapScope, {Worker, ChromeWorker});

      // Define a console for the add-on
      XPCOMUtils.defineLazyGetter(
        activeAddon.bootstrapScope, "console",
        () => new ConsoleAPI({ consoleID: "addon/" + aId }));

      activeAddon.bootstrapScope.__SCRIPT_URI_SPEC__ = uri;
      Services.scriptloader.loadSubScript(uri, activeAddon.bootstrapScope);
    } catch (e) {
      logger.warn("Error loading bootstrap.js for " + aId, e);
    }

    // Notify the BrowserToolboxProcess that a new addon has been loaded.
    let wrappedJSObject = { id: aId, options: { global: activeAddon.bootstrapScope }};
    Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
  },

  /**
   * Unloads a bootstrap scope by dropping all references to it and then
   * updating the list of active add-ons with the crash reporter.
   *
   * @param  aId
   *         The add-on's ID
   */
  unloadBootstrapScope(aId) {
    // In case the add-on was not multiprocess-compatible, deregister
    // any interpositions for it.
    Cu.setAddonInterposition(aId, null);
    Cu.allowCPOWsInAddon(aId, false);

    this.activeAddons.delete(aId);
    this.addAddonsToCrashReporter();

    // Notify the BrowserToolboxProcess that an addon has been unloaded.
    let wrappedJSObject = { id: aId, options: { global: null }};
    Services.obs.notifyObservers({ wrappedJSObject }, "toolbox-update-addon-options");
  },

  /**
   * Calls a bootstrap method for an add-on.
   *
   * @param  aAddon
   *         An object representing the add-on, with `id`, `type` and `version`
   * @param  aFile
   *         The nsIFile for the add-on
   * @param  aMethod
   *         The name of the bootstrap method to call
   * @param  aReason
   *         The reason flag to pass to the bootstrap's startup method
   * @param  aExtraParams
   *         An object of additional key/value pairs to pass to the method in
   *         the params argument
   */
  callBootstrapMethod(aAddon, aFile, aMethod, aReason, aExtraParams) {
    if (!aAddon.id || !aAddon.version || !aAddon.type) {
      throw new Error("aAddon must include an id, version, and type");
    }

    // Only run in safe mode if allowed to
    let runInSafeMode = "runInSafeMode" in aAddon ? aAddon.runInSafeMode : canRunInSafeMode(aAddon);
    if (Services.appinfo.inSafeMode && !runInSafeMode)
      return;

    let timeStart = new Date();
    if (CHROME_TYPES.has(aAddon.type) && aMethod == "startup") {
      logger.debug("Registering manifest for " + aFile.path);
      Components.manager.addBootstrappedManifestLocation(aFile);
    }

    try {
      // Load the scope if it hasn't already been loaded
      let activeAddon = this.activeAddons.get(aAddon.id);
      if (!activeAddon) {
        this.loadBootstrapScope(aAddon.id, aFile, aAddon.version, aAddon.type,
                                aAddon.multiprocessCompatible || false,
                                runInSafeMode, aAddon.dependencies,
                                aAddon.hasEmbeddedWebExtension || false);
        activeAddon = this.activeAddons.get(aAddon.id);
      }

      if (aMethod == "startup" || aMethod == "shutdown") {
        if (!aExtraParams) {
          aExtraParams = {};
        }
        aExtraParams["instanceID"] = this.activeAddons.get(aAddon.id).instanceID;
      }

      // Nothing to call for locales
      if (aAddon.type == "locale")
        return;

      let method = undefined;
      try {
        let scope = activeAddon.bootstrapScope;
        method = scope[aMethod] || Cu.evalInSandbox(`${aMethod};`, scope);
      } catch (e) {
        // An exception will be caught if the expected method is not defined.
        // That will be logged below.
      }

      // Extensions are automatically deinitialized in the correct order at shutdown.
      if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
        activeAddon.disable = true;
        for (let addon of this.getDependentAddons(aAddon)) {
          if (addon.active)
            this.updateAddonDisabledState(addon);
        }
      }

      let params = {
        id: aAddon.id,
        version: aAddon.version,
        installPath: aFile.clone(),
        resourceURI: getURIForResourceInFile(aFile, "")
      };

      if (aExtraParams) {
        for (let key in aExtraParams) {
          params[key] = aExtraParams[key];
        }
      }

      if (aAddon.hasEmbeddedWebExtension) {
        let reason = Object.keys(BOOTSTRAP_REASONS).find(
          key => BOOTSTRAP_REASONS[key] == aReason
        );

        if (aMethod == "startup") {
          const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor(params);
          params.webExtension = {
            startup: () => webExtension.startup(reason),
          };
        } else if (aMethod == "shutdown") {
          LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown(reason);
        }
      }

      if (!method) {
        logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
      } else {
        logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
                     aAddon.version);
        try {
          method(params, aReason);
        } catch (e) {
          logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
        }
      }
    } finally {
      // Extensions are automatically initialized in the correct order at startup.
      if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
        for (let addon of this.getDependentAddons(aAddon))
          this.updateAddonDisabledState(addon);
      }

      if (CHROME_TYPES.has(aAddon.type) && aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
        logger.debug("Removing manifest for " + aFile.path);
        Components.manager.removeBootstrappedManifestLocation(aFile);

        let manifest = getURIForResourceInFile(aFile, "chrome.manifest");
        for (let line of ChromeManifestParser.parseSync(manifest)) {
          if (line.type == "resource") {
            ResProtocolHandler.setSubstitution(line.args[0], null);
          }
        }
      }
      this.setTelemetry(aAddon.id, aMethod + "_MS", new Date() - timeStart);
    }
  },

  /**
   * Updates the disabled state for an add-on. Its appDisabled property will be
   * calculated and if the add-on is changed the database will be saved and
   * appropriate notifications will be sent out to the registered AddonListeners.
   *
   * @param  aAddon
   *         The DBAddonInternal to update
   * @param  aUserDisabled
   *         Value for the userDisabled property. If undefined the value will
   *         not change
   * @param  aSoftDisabled
   *         Value for the softDisabled property. If undefined the value will
   *         not change. If true this will force userDisabled to be true
   * @param  aPendingRestart
   *         If the addon is updated whilst the disabled state of another non-
   *         restartless addon is also set, we need to carry that forward.
   * @return a tri-state indicating the action taken for the add-on:
   *           - undefined: The add-on did not change state
   *           - true: The add-on because disabled
   *           - false: The add-on became enabled
   * @throws if addon is not a DBAddonInternal
   */
  updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled, aPendingRestart = false) {
    if (!(aAddon.inDatabase))
      throw new Error("Can only update addon states for installed addons.");
    if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
      throw new Error("Cannot change userDisabled and softDisabled at the " +
                      "same time");
    }

    if (aUserDisabled === undefined) {
      aUserDisabled = aAddon.userDisabled;
    } else if (!aUserDisabled) {
      // If enabling the add-on then remove softDisabled
      aSoftDisabled = false;
    }

    // If not changing softDisabled or the add-on is already userDisabled then
    // use the existing value for softDisabled
    if (aSoftDisabled === undefined || aUserDisabled)
      aSoftDisabled = aAddon.softDisabled;

    let appDisabled = !isUsableAddon(aAddon);
    // No change means nothing to do here
    if (aAddon.userDisabled == aUserDisabled &&
        aAddon.appDisabled == appDisabled &&
        aAddon.softDisabled == aSoftDisabled)
      return undefined;

    let wasDisabled = aAddon.disabled;
    let isDisabled = aUserDisabled || aSoftDisabled || appDisabled;

    // If appDisabled changes but addon.disabled doesn't,
    // no onDisabling/onEnabling is sent - so send a onPropertyChanged.
    let appDisabledChanged = aAddon.appDisabled != appDisabled;

    // Update the properties in the database.
    XPIDatabase.setAddonProperties(aAddon, {
      userDisabled: aUserDisabled,
      appDisabled,
      softDisabled: aSoftDisabled
    });

    let wrapper = aAddon.wrapper;

    if (appDisabledChanged) {
      AddonManagerPrivate.callAddonListeners("onPropertyChanged",
                                             wrapper,
                                             ["appDisabled"]);
    }

    // If the add-on is not visible or the add-on is not changing state then
    // there is no need to do anything else
    if (!aAddon.visible || (wasDisabled == isDisabled))
      return undefined;

    // Flag that active states in the database need to be updated on shutdown
    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);

    // Sync with XPIStates.
    let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
    if (xpiState) {
      xpiState.syncWithDB(aAddon);
      XPIStates.save();
    } else {
      // There should always be an xpiState
      logger.warn("No XPIState for ${id} in ${location}", aAddon);
    }

    // Have we just gone back to the current state?
    if (isDisabled != aAddon.active) {
      AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
    } else {
      if (isDisabled) {
        var needsRestart = aPendingRestart || this.disableRequiresRestart(aAddon);
        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper,
                                               needsRestart);
      } else {
        needsRestart = this.enableRequiresRestart(aAddon);
        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
                                               needsRestart);
      }

      if (!needsRestart) {
        XPIDatabase.updateAddonActive(aAddon, !isDisabled);

        if (isDisabled) {
          if (aAddon.bootstrap && this.activeAddons.has(aAddon.id)) {
            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                                     BOOTSTRAP_REASONS.ADDON_DISABLE);
            this.unloadBootstrapScope(aAddon.id);
          }
          AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
        } else {
          if (aAddon.bootstrap) {
            this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
                                     BOOTSTRAP_REASONS.ADDON_ENABLE);
          }
          AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
        }
      }
    }

    // Notify any other providers that a new theme has been enabled
    if (isTheme(aAddon.type) && !isDisabled) {
      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart);

      if (xpiState) {
        xpiState.syncWithDB(aAddon);
        XPIStates.save();
      }
    }

    return isDisabled;
  },

  /**
   * Uninstalls an add-on, immediately if possible or marks it as pending
   * uninstall if not.
   *
   * @param  aAddon
   *         The DBAddonInternal to uninstall
   * @param  aForcePending
   *         Force this addon into the pending uninstall state, even if
   *         it isn't marked as requiring a restart (used e.g. while the
   *         add-on manager is open and offering an "undo" button)
   * @throws if the addon cannot be uninstalled because it is in an install
   *         location that does not allow it
   */
  uninstallAddon(aAddon, aForcePending) {
    if (!(aAddon.inDatabase))
      throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed");

    if (aAddon._installLocation.locked)
      throw new Error("Cannot uninstall addon " + aAddon.id
          + " from locked install location " + aAddon._installLocation.name);

    // Inactive add-ons don't require a restart to uninstall
    let requiresRestart = this.uninstallRequiresRestart(aAddon);

    // if makePending is true, we don't actually apply the uninstall,
    // we just mark the addon as having a pending uninstall
    let makePending = aForcePending || requiresRestart;

    if (makePending && aAddon.pendingUninstall)
      throw new Error("Add-on is already marked to be uninstalled");

    aAddon._hasResourceCache.clear();

    if (aAddon._updateCheck) {
      logger.debug("Cancel in-progress update check for " + aAddon.id);
      aAddon._updateCheck.cancel();
    }

    let wasPending = aAddon.pendingUninstall;

    if (makePending) {
      // We create an empty directory in the staging directory to indicate
      // that an uninstall is necessary on next startup. Temporary add-ons are
      // automatically uninstalled on shutdown anyway so there is no need to
      // do this for them.
      if (aAddon._installLocation.name != KEY_APP_TEMPORARY) {
        let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir());
        if (!stage.exists())
          stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
      }

      XPIDatabase.setAddonProperties(aAddon, {
        pendingUninstall: true
      });
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
      let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
      if (xpiState) {
        xpiState.enabled = false;
        XPIStates.save();
      } else {
        logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon);
      }
    }

    // If the add-on is not visible then there is no need to notify listeners.
    if (!aAddon.visible)
      return;

    let wrapper = aAddon.wrapper;

    // If the add-on wasn't already pending uninstall then notify listeners.
    if (!wasPending) {
      // Passing makePending as the requiresRestart parameter is a little
      // strange as in some cases this operation can complete without a restart
      // so really this is now saying that the uninstall isn't going to happen
      // immediately but will happen later.
      AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
                                             makePending);
    }

    // Reveal the highest priority add-on with the same ID
    function revealAddon(aAddon) {
      XPIDatabase.makeAddonVisible(aAddon);

      let wrappedAddon = aAddon.wrapper;
      AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);

      if (!aAddon.disabled && !XPIProvider.enableRequiresRestart(aAddon)) {
        XPIDatabase.updateAddonActive(aAddon, true);
      }

      if (aAddon.bootstrap) {
        XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
                                        "install", BOOTSTRAP_REASONS.ADDON_INSTALL);

        if (aAddon.active) {
          XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
                                          "startup", BOOTSTRAP_REASONS.ADDON_INSTALL);
        } else {
          XPIProvider.unloadBootstrapScope(aAddon.id);
        }
      }

      // We always send onInstalled even if a restart is required to enable
      // the revealed add-on
      AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
    }

    function findAddonAndReveal(aId) {
      let state = XPIStates.findAddon(aId);
      if (state) {
        XPIDatabase.getAddonInLocation(aId, state.location.name, revealAddon);
      }
    }

    if (!makePending) {
      if (aAddon.bootstrap) {
        if (aAddon.active) {
          this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                                   BOOTSTRAP_REASONS.ADDON_UNINSTALL);
        }

        this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
                                 BOOTSTRAP_REASONS.ADDON_UNINSTALL);
        XPIStates.disableAddon(aAddon.id);
        this.unloadBootstrapScope(aAddon.id);
        flushChromeCaches();
      }
      aAddon._installLocation.uninstallAddon(aAddon.id);
      XPIDatabase.removeAddonMetadata(aAddon);
      XPIStates.removeAddon(aAddon.location, aAddon.id);
      AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);

      findAddonAndReveal(aAddon.id);
    } else if (aAddon.bootstrap && aAddon.active && !this.disableRequiresRestart(aAddon)) {
      this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
                               BOOTSTRAP_REASONS.ADDON_UNINSTALL);
      XPIStates.disableAddon(aAddon.id);
      this.unloadBootstrapScope(aAddon.id);
      XPIDatabase.updateAddonActive(aAddon, false);
    }

    // Notify any other providers that a new theme has been enabled
    if (isTheme(aAddon.type) && aAddon.active)
      AddonManagerPrivate.notifyAddonChanged(null, aAddon.type, requiresRestart);
  },

  /**
   * Cancels the pending uninstall of an add-on.
   *
   * @param  aAddon
   *         The DBAddonInternal to cancel uninstall for
   */
  cancelUninstallAddon(aAddon) {
    if (!(aAddon.inDatabase))
      throw new Error("Can only cancel uninstall for installed addons.");
    if (!aAddon.pendingUninstall)
      throw new Error("Add-on is not marked to be uninstalled");

    if (aAddon._installLocation.name != KEY_APP_TEMPORARY)
      aAddon._installLocation.cleanStagingDir([aAddon.id]);

    XPIDatabase.setAddonProperties(aAddon, {
      pendingUninstall: false
    });

    if (!aAddon.visible)
      return;

    XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon);
    XPIStates.save();

    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);

    // TODO hide hidden add-ons (bug 557710)
    let wrapper = aAddon.wrapper;
    AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);

    if (aAddon.bootstrap && !aAddon.disabled && !this.enableRequiresRestart(aAddon)) {
      this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
                               BOOTSTRAP_REASONS.ADDON_INSTALL);
      XPIDatabase.updateAddonActive(aAddon, true);
    }

    // Notify any other providers that this theme is now enabled again.
    if (isTheme(aAddon.type) && aAddon.active)
      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
  }
};

/**
 * Creates a new AddonInstall to install an add-on from a local file.
 *
 * @param  file
 *         The file to install
 * @param  location
 *         The location to install to
 * @returns Promise
 *          A Promise that resolves with the new install object.
 */
function createLocalInstall(file, location) {
  if (!location) {
    location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
  }
  let url = Services.io.newFileURI(file);

  try {
    let install = new LocalAddonInstall(location, url);
    return install.init().then(() => install);
  } catch (e) {
    logger.error("Error creating install", e);
    XPIProvider.removeActiveInstall(this);
    return Promise.resolve(null);
  }
}

// Maps instances of AddonInternal to AddonWrapper
const wrapperMap = new WeakMap();
let addonFor = wrapper => wrapperMap.get(wrapper);

/**
 * The AddonInternal is an internal only representation of add-ons. It may
 * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
 * or an install manifest.
 */
function AddonInternal() {
  this._hasResourceCache = new Map();

  XPCOMUtils.defineLazyGetter(this, "wrapper", () => {
    return new AddonWrapper(this);
  });
}

AddonInternal.prototype = {
  _selectedLocale: null,
  _hasResourceCache: null,
  active: false,
  visible: false,
  userDisabled: false,
  appDisabled: false,
  softDisabled: false,
  blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
  blocklistURL: null,
  sourceURI: null,
  releaseNotesURI: null,
  foreignInstall: false,
  seen: true,
  skinnable: false,

  /**
   * @property {Array<string>} dependencies
   *   An array of bootstrapped add-on IDs on which this add-on depends.
   *   The add-on will remain appDisabled if any of the dependent
   *   add-ons is not installed and enabled.
   */
  dependencies: Object.freeze([]),
  hasEmbeddedWebExtension: false,

  get selectedLocale() {
    if (this._selectedLocale)
      return this._selectedLocale;
    let locale = Locale.findClosestLocale(this.locales);
    this._selectedLocale = locale ? locale : this.defaultLocale;
    return this._selectedLocale;
  },

  get providesUpdatesSecurely() {
    return !!(this.updateKey || !this.updateURL ||
              this.updateURL.substring(0, 6) == "https:");
  },

  get isCorrectlySigned() {
    switch (this._installLocation.name) {
      case KEY_APP_SYSTEM_ADDONS:
        // System add-ons must be signed by the system key.
        return this.signedState == AddonManager.SIGNEDSTATE_SYSTEM

      case KEY_APP_SYSTEM_DEFAULTS:
      case KEY_APP_TEMPORARY:
        // Temporary and built-in system add-ons do not require signing.
        return true;

      case KEY_APP_SYSTEM_SHARE:
      case KEY_APP_SYSTEM_LOCAL:
        // On UNIX platforms except OSX, an additional location for system
        // add-ons exists in /usr/{lib,share}/mozilla/extensions. Add-ons
        // installed there do not require signing.
        if (Services.appinfo.OS != "Darwin")
          return true;
        break;
    }

    if (this.signedState === AddonManager.SIGNEDSTATE_NOT_REQUIRED)
      return true;
    return this.signedState > AddonManager.SIGNEDSTATE_MISSING;
  },

  get isCompatible() {
    return this.isCompatibleWith();
  },

  get disabled() {
    return (this.userDisabled || this.appDisabled || this.softDisabled);
  },

  get isPlatformCompatible() {
    if (this.targetPlatforms.length == 0)
      return true;

    let matchedOS = false;

    // If any targetPlatform matches the OS and contains an ABI then we will
    // only match a targetPlatform that contains both the current OS and ABI
    let needsABI = false;

    // Some platforms do not specify an ABI, test against null in that case.
    let abi = null;
    try {
      abi = Services.appinfo.XPCOMABI;
    } catch (e) { }

    // Something is causing errors in here
    try {
      for (let platform of this.targetPlatforms) {
        if (platform.os == Services.appinfo.OS) {
          if (platform.abi) {
            needsABI = true;
            if (platform.abi === abi)
              return true;
          } else {
            matchedOS = true;
          }
        }
      }
    } catch (e) {
      let message = "Problem with addon " + this.id + " targetPlatforms "
                    + JSON.stringify(this.targetPlatforms);
      logger.error(message, e);
      AddonManagerPrivate.recordException("XPI", message, e);
      // don't trust this add-on
      return false;
    }

    return matchedOS && !needsABI;
  },

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    let app = this.matchingTargetApplication;
    if (!app)
      return false;

    // set reasonable defaults for minVersion and maxVersion
    let minVersion = app.minVersion || "0";
    let maxVersion = app.maxVersion || "*";

    if (!aAppVersion)
      aAppVersion = Services.appinfo.version;
    if (!aPlatformVersion)
      aPlatformVersion = Services.appinfo.platformVersion;

    let version;
    if (app.id == Services.appinfo.ID)
      version = aAppVersion;
    else if (app.id == TOOLKIT_ID)
      version = aPlatformVersion

    // Only extensions and dictionaries can be compatible by default; themes
    // and language packs always use strict compatibility checking.
    if (this.type in COMPATIBLE_BY_DEFAULT_TYPES &&
        !AddonManager.strictCompatibility && !this.strictCompatibility &&
        !this.hasBinaryComponents) {

      // The repository can specify compatibility overrides.
      // Note: For now, only blacklisting is supported by overrides.
      if (this._repositoryAddon &&
          this._repositoryAddon.compatibilityOverrides) {
        let overrides = this._repositoryAddon.compatibilityOverrides;
        let override = AddonRepository.findMatchingCompatOverride(this.version,
                                                                  overrides);
        if (override && override.type == "incompatible")
          return false;
      }

      // Extremely old extensions should not be compatible by default.
      let minCompatVersion;
      if (app.id == Services.appinfo.ID)
        minCompatVersion = XPIProvider.minCompatibleAppVersion;
      else if (app.id == TOOLKIT_ID)
        minCompatVersion = XPIProvider.minCompatiblePlatformVersion;

      if (minCompatVersion &&
          Services.vc.compare(minCompatVersion, maxVersion) > 0)
        return false;

      return Services.vc.compare(version, minVersion) >= 0;
    }

    return (Services.vc.compare(version, minVersion) >= 0) &&
           (Services.vc.compare(version, maxVersion) <= 0)
  },

  get matchingTargetApplication() {
    let app = null;
    for (let targetApp of this.targetApplications) {
      if (targetApp.id == Services.appinfo.ID)
        return targetApp;
      if (targetApp.id == TOOLKIT_ID)
        app = targetApp;
    }
    return app;
  },

  findBlocklistEntry() {
    let staticItem = findMatchingStaticBlocklistItem(this);
    if (staticItem) {
      let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
      return {
        state: staticItem.level,
        url: url.replace(/%blockID%/g, staticItem.blockID)
      };
    }

    return Blocklist.getAddonBlocklistEntry(this.wrapper);
  },

  updateBlocklistState(options = {}) {
    let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options;

    if (oldAddon) {
      this.userDisabled = oldAddon.userDisabled;
      this.softDisabled = oldAddon.softDisabled;
      this.blocklistState = oldAddon.blocklistState;
    }
    let oldState = this.blocklistState;

    let entry = this.findBlocklistEntry();
    let newState = entry ? entry.state : Blocklist.STATE_NOT_BLOCKED;

    this.blocklistState = newState;
    this.blocklistURL = entry && entry.url;

    let userDisabled, softDisabled;
    // After a blocklist update, the blocklist service manually applies
    // new soft blocks after displaying a UI, in which cases we need to
    // skip updating it here.
    if (applySoftBlock && oldState != newState) {
      if (newState == Blocklist.STATE_SOFTBLOCKED) {
        if (this.type == "theme") {
          userDisabled = true;
        } else {
          softDisabled = !this.userDisabled;
        }
      } else {
        softDisabled = false;
      }
    }

    if (this.inDatabase && updateDatabase) {
      XPIProvider.updateAddonDisabledState(this, userDisabled, softDisabled);
      XPIDatabase.saveChanges();
    } else {
      this.appDisabled = !isUsableAddon(this);
      if (userDisabled !== undefined) {
        this.userDisabled = userDisabled;
      }
      if (softDisabled !== undefined) {
        this.softDisabled = softDisabled;
      }
    }
  },

  applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
    for (let targetApp of this.targetApplications) {
      for (let updateTarget of aUpdate.targetApplications) {
        if (targetApp.id == updateTarget.id && (aSyncCompatibility ||
            Services.vc.compare(targetApp.maxVersion, updateTarget.maxVersion) < 0)) {
          targetApp.minVersion = updateTarget.minVersion;
          targetApp.maxVersion = updateTarget.maxVersion;
        }
      }
    }
    if (aUpdate.multiprocessCompatible !== undefined)
      this.multiprocessCompatible = aUpdate.multiprocessCompatible;
    this.appDisabled = !isUsableAddon(this);
  },

  /**
   * getDataDirectory tries to execute the callback with two arguments:
   * 1) the path of the data directory within the profile,
   * 2) any exception generated from trying to build it.
   */
  getDataDirectory(callback) {
    let parentPath = OS.Path.join(OS.Constants.Path.profileDir, "extension-data");
    let dirPath = OS.Path.join(parentPath, this.id);

    (async function() {
      await OS.File.makeDir(parentPath, {ignoreExisting: true});
      await OS.File.makeDir(dirPath, {ignoreExisting: true});
    })().then(() => callback(dirPath, null),
            e => callback(dirPath, e));
  },

  /**
   * toJSON is called by JSON.stringify in order to create a filtered version
   * of this object to be serialized to a JSON file. A new object is returned
   * with copies of all non-private properties. Functions, getters and setters
   * are not copied.
   *
   * @param  aKey
   *         The key that this object is being serialized as in the JSON.
   *         Unused here since this is always the main object serialized
   *
   * @return an object containing copies of the properties of this object
   *         ignoring private properties, functions, getters and setters
   */
  toJSON(aKey) {
    let obj = {};
    for (let prop in this) {
      // Ignore the wrapper property
      if (prop == "wrapper")
        continue;

      // Ignore private properties
      if (prop.substring(0, 1) == "_")
        continue;

      // Ignore getters
      if (this.__lookupGetter__(prop))
        continue;

      // Ignore setters
      if (this.__lookupSetter__(prop))
        continue;

      // Ignore functions
      if (typeof this[prop] == "function")
        continue;

      obj[prop] = this[prop];
    }

    return obj;
  },

  /**
   * When an add-on install is pending its metadata will be cached in a file.
   * This method reads particular properties of that metadata that may be newer
   * than that in the install manifest, like compatibility information.
   *
   * @param  aObj
   *         A JS object containing the cached metadata
   */
  importMetadata(aObj) {
    for (let prop of PENDING_INSTALL_METADATA) {
      if (!(prop in aObj))
        continue;

      this[prop] = aObj[prop];
    }

    // Compatibility info may have changed so update appDisabled
    this.appDisabled = !isUsableAddon(this);
  },

  permissions() {
    let permissions = 0;

    // Add-ons that aren't installed cannot be modified in any way
    if (!(this.inDatabase))
      return permissions;

    if (!this.appDisabled) {
      if (this.userDisabled || this.softDisabled) {
        permissions |= AddonManager.PERM_CAN_ENABLE;
      } else if (this.type != "theme") {
        permissions |= AddonManager.PERM_CAN_DISABLE;
      }
    }

    // Add-ons that are in locked install locations, or are pending uninstall
    // cannot be upgraded or uninstalled
    if (!this._installLocation.locked && !this.pendingUninstall) {
      // Experiments cannot be upgraded.
      // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons())
      let isSystem = this._installLocation.isSystem;
      // Add-ons that are installed by a file link cannot be upgraded.
      if (this.type != "experiment" &&
          !this._installLocation.isLinkedAddon(this.id) && !isSystem) {
        permissions |= AddonManager.PERM_CAN_UPGRADE;
      }

      permissions |= AddonManager.PERM_CAN_UNINSTALL;
    }

    return permissions;
  },
};

/**
 * The AddonWrapper wraps an Addon to provide the data visible to consumers of
 * the public API.
 */
function AddonWrapper(aAddon) {
  wrapperMap.set(this, aAddon);
}

AddonWrapper.prototype = {
  get __AddonInternal__() {
    return AppConstants.DEBUG ? addonFor(this) : undefined;
  },

  get seen() {
    return addonFor(this).seen;
  },

  get hasEmbeddedWebExtension() {
    return addonFor(this).hasEmbeddedWebExtension;
  },

  markAsSeen() {
    addonFor(this).seen = true;
    XPIDatabase.saveChanges();
  },

  get type() {
    return getExternalType(addonFor(this).type);
  },

  get isWebExtension() {
    return isWebExtension(addonFor(this).type);
  },

  get temporarilyInstalled() {
    return addonFor(this)._installLocation == TemporaryInstallLocation;
  },

  get aboutURL() {
    return this.isActive ? addonFor(this)["aboutURL"] : null;
  },

  get optionsURL() {
    if (!this.isActive) {
      return null;
    }

    let addon = addonFor(this);
    if (addon.optionsURL) {
      if (this.isWebExtension || this.hasEmbeddedWebExtension) {
        // The internal object's optionsURL property comes from the addons
        // DB and should be a relative URL.  However, extensions with
        // options pages installed before bug 1293721 was fixed got absolute
        // URLs in the addons db.  This code handles both cases.
        let policy = WebExtensionPolicy.getByID(addon.id);
        if (!policy) {
          return null;
        }
        let base = policy.getURL();
        return new URL(addon.optionsURL, base).href;
      }
      return addon.optionsURL;
    }

    if (this.hasResource("options.xul"))
      return this.getResourceURI("options.xul").spec;

    return null;
  },

  get optionsType() {
    if (!this.isActive)
      return null;

    let addon = addonFor(this);
    let hasOptionsXUL = this.hasResource("options.xul");
    let hasOptionsURL = !!this.optionsURL;

    if (addon.optionsType) {
      switch (parseInt(addon.optionsType, 10)) {
      case AddonManager.OPTIONS_TYPE_DIALOG:
      case AddonManager.OPTIONS_TYPE_TAB:
        return hasOptionsURL ? addon.optionsType : null;
      case AddonManager.OPTIONS_TYPE_INLINE:
      case AddonManager.OPTIONS_TYPE_INLINE_INFO:
      case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
        return (hasOptionsXUL || hasOptionsURL) ? addon.optionsType : null;
      }
      return null;
    }

    if (hasOptionsXUL)
      return AddonManager.OPTIONS_TYPE_INLINE;

    if (hasOptionsURL)
      return AddonManager.OPTIONS_TYPE_DIALOG;

    return null;
  },

  get optionsBrowserStyle() {
    let addon = addonFor(this);
    return addon.optionsBrowserStyle;
  },

  get iconURL() {
    return AddonManager.getPreferredIconURL(this, 48);
  },

  get icon64URL() {
    return AddonManager.getPreferredIconURL(this, 64);
  },

  get icons() {
    let addon = addonFor(this);
    let icons = {};

    if (addon._repositoryAddon) {
      for (let size in addon._repositoryAddon.icons) {
        icons[size] = addon._repositoryAddon.icons[size];
      }
    }

    if (addon.icons) {
      for (let size in addon.icons) {
        icons[size] = this.getResourceURI(addon.icons[size]).spec;
      }
    } else {
      // legacy add-on that did not update its icon data yet
      if (this.hasResource("icon.png")) {
        icons[32] = icons[48] = this.getResourceURI("icon.png").spec;
      }
      if (this.hasResource("icon64.png")) {
        icons[64] = this.getResourceURI("icon64.png").spec;
      }
    }

    let canUseIconURLs = this.isActive ||
      (addon.type == "theme" && addon.internalName == XPIProvider.defaultSkin);
    if (canUseIconURLs && addon.iconURL) {
      icons[32] = addon.iconURL;
      icons[48] = addon.iconURL;
    }

    if (canUseIconURLs && addon.icon64URL) {
      icons[64] = addon.icon64URL;
    }

    Object.freeze(icons);
    return icons;
  },

  get screenshots() {
    let addon = addonFor(this);
    let repositoryAddon = addon._repositoryAddon;
    if (repositoryAddon && ("screenshots" in repositoryAddon)) {
      let repositoryScreenshots = repositoryAddon.screenshots;
      if (repositoryScreenshots && repositoryScreenshots.length > 0)
        return repositoryScreenshots;
    }

    if (isTheme(addon.type) && this.hasResource("preview.png")) {
      let url = this.getResourceURI("preview.png").spec;
      return [new AddonManagerPrivate.AddonScreenshot(url)];
    }

    return null;
  },

  get applyBackgroundUpdates() {
    return addonFor(this).applyBackgroundUpdates;
  },
  set applyBackgroundUpdates(val) {
    let addon = addonFor(this);
    if (this.type == "experiment") {
      logger.warn("Setting applyBackgroundUpdates on an experiment is not supported.");
      return addon.applyBackgroundUpdates;
    }

    if (val != AddonManager.AUTOUPDATE_DEFAULT &&
        val != AddonManager.AUTOUPDATE_DISABLE &&
        val != AddonManager.AUTOUPDATE_ENABLE) {
      val = val ? AddonManager.AUTOUPDATE_DEFAULT :
                  AddonManager.AUTOUPDATE_DISABLE;
    }

    if (val == addon.applyBackgroundUpdates)
      return val;

    XPIDatabase.setAddonProperties(addon, {
      applyBackgroundUpdates: val
    });
    AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);

    return val;
  },

  set syncGUID(val) {
    let addon = addonFor(this);
    if (addon.syncGUID == val)
      return val;

    if (addon.inDatabase)
      XPIDatabase.setAddonSyncGUID(addon, val);

    addon.syncGUID = val;

    return val;
  },

  get install() {
    let addon = addonFor(this);
    if (!("_install" in addon) || !addon._install)
      return null;
    return addon._install.wrapper;
  },

  get pendingUpgrade() {
    let addon = addonFor(this);
    return addon.pendingUpgrade ? addon.pendingUpgrade.wrapper : null;
  },

  get scope() {
    let addon = addonFor(this);
    if (addon._installLocation)
      return addon._installLocation.scope;

    return AddonManager.SCOPE_PROFILE;
  },

  get pendingOperations() {
    let addon = addonFor(this);
    let pending = 0;
    if (!(addon.inDatabase)) {
      // Add-on is pending install if there is no associated install (shouldn't
      // happen here) or if the install is in the process of or has successfully
      // completed the install. If an add-on is pending install then we ignore
      // any other pending operations.
      if (!addon._install || addon._install.state == AddonManager.STATE_INSTALLING ||
          addon._install.state == AddonManager.STATE_INSTALLED)
        return AddonManager.PENDING_INSTALL;
    } else if (addon.pendingUninstall) {
      // If an add-on is pending uninstall then we ignore any other pending
      // operations
      return AddonManager.PENDING_UNINSTALL;
    }

    if (addon.active && addon.disabled)
      pending |= AddonManager.PENDING_DISABLE;
    else if (!addon.active && !addon.disabled)
      pending |= AddonManager.PENDING_ENABLE;

    if (addon.pendingUpgrade)
      pending |= AddonManager.PENDING_UPGRADE;

    return pending;
  },

  get operationsRequiringRestart() {
    let addon = addonFor(this);
    let ops = 0;
    if (XPIProvider.installRequiresRestart(addon))
      ops |= AddonManager.OP_NEEDS_RESTART_INSTALL;
    if (XPIProvider.uninstallRequiresRestart(addon))
      ops |= AddonManager.OP_NEEDS_RESTART_UNINSTALL;
    if (XPIProvider.enableRequiresRestart(addon))
      ops |= AddonManager.OP_NEEDS_RESTART_ENABLE;
    if (XPIProvider.disableRequiresRestart(addon))
      ops |= AddonManager.OP_NEEDS_RESTART_DISABLE;

    return ops;
  },

  get isDebuggable() {
    return this.isActive && addonFor(this).bootstrap;
  },

  get permissions() {
    return addonFor(this).permissions();
  },

  get isActive() {
    let addon = addonFor(this);
    if (!addon.active)
      return false;
    if (!Services.appinfo.inSafeMode)
      return true;
    return addon.bootstrap && canRunInSafeMode(addon);
  },

  updateBlocklistState(applySoftBlock = true) {
    addonFor(this).updateBlocklistState({applySoftBlock});
  },

  get userDisabled() {
    let addon = addonFor(this);
    return addon.softDisabled || addon.userDisabled;
  },
  set userDisabled(val) {
    let addon = addonFor(this);
    if (val == this.userDisabled) {
      return val;
    }

    if (addon.inDatabase) {
      let theme = isTheme(addon.type);
      if (theme && val) {
        if (addon.internalName == XPIProvider.defaultSkin)
          throw new Error("Cannot disable the default theme");
        XPIProvider.enableDefaultTheme();
      }
      if (!(theme && val) || isWebExtension(addon.type)) {
        // hidden and system add-ons should not be user disasbled,
        // as there is no UI to re-enable them.
        if (this.hidden) {
          throw new Error(`Cannot disable hidden add-on ${addon.id}`);
        }
        XPIProvider.updateAddonDisabledState(addon, val);
      }
    } else {
      addon.userDisabled = val;
      // When enabling remove the softDisabled flag
      if (!val)
        addon.softDisabled = false;
    }

    return val;
  },

  set softDisabled(val) {
    let addon = addonFor(this);
    if (val == addon.softDisabled)
      return val;

    if (addon.inDatabase) {
      // When softDisabling a theme just enable the active theme
      if (isTheme(addon.type) && val && !addon.userDisabled) {
        if (addon.internalName == XPIProvider.defaultSkin)
          throw new Error("Cannot disable the default theme");
        XPIProvider.enableDefaultTheme();
        if (isWebExtension(addon.type))
          XPIProvider.updateAddonDisabledState(addon, undefined, val);
      } else {
        XPIProvider.updateAddonDisabledState(addon, undefined, val);
      }
    } else if (!addon.userDisabled) {
      // Only set softDisabled if not already disabled
      addon.softDisabled = val;
    }

    return val;
  },

  get hidden() {
    let addon = addonFor(this);
    if (addon._installLocation.name == KEY_APP_TEMPORARY)
      return false;

    return addon._installLocation.isSystem;
  },

  get isSystem() {
    let addon = addonFor(this);
    return addon._installLocation.isSystem;
  },

  // Returns true if Firefox Sync should sync this addon. Only non-hotfixes
  // directly in the profile are considered syncable.
  get isSyncable() {
    let addon = addonFor(this);
    let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
    if (hotfixID && hotfixID == addon.id) {
      return false;
    }
    return (addon._installLocation.name == KEY_APP_PROFILE);
  },

  get userPermissions() {
    return addonFor(this).userPermissions;
  },

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
  },

  uninstall(alwaysAllowUndo) {
    let addon = addonFor(this);
    XPIProvider.uninstallAddon(addon, alwaysAllowUndo);
  },

  cancelUninstall() {
    let addon = addonFor(this);
    XPIProvider.cancelUninstallAddon(addon);
  },

  findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
    // Short-circuit updates for experiments because updates are handled
    // through the Experiments Manager.
    if (this.type == "experiment") {
      AddonManagerPrivate.callNoUpdateListeners(this, aListener, aReason,
                                                aAppVersion, aPlatformVersion);
      return;
    }

    new UpdateChecker(addonFor(this), aListener, aReason, aAppVersion, aPlatformVersion);
  },

  // Returns true if there was an update in progress, false if there was no update to cancel
  cancelUpdate() {
    let addon = addonFor(this);
    if (addon._updateCheck) {
      addon._updateCheck.cancel();
      return true;
    }
    return false;
  },

  hasResource(aPath) {
    let addon = addonFor(this);
    if (addon._hasResourceCache.has(aPath))
      return addon._hasResourceCache.get(aPath);

    let bundle = addon._sourceBundle.clone();

    // Bundle may not exist any more if the addon has just been uninstalled,
    // but explicitly first checking .exists() results in unneeded file I/O.
    try {
      var isDir = bundle.isDirectory();
    } catch (e) {
      addon._hasResourceCache.set(aPath, false);
      return false;
    }

    if (isDir) {
      if (aPath)
        aPath.split("/").forEach(part => bundle.append(part));
      let result = bundle.exists();
      addon._hasResourceCache.set(aPath, result);
      return result;
    }

    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                    createInstance(Ci.nsIZipReader);
    try {
      zipReader.open(bundle);
      let result = zipReader.hasEntry(aPath);
      addon._hasResourceCache.set(aPath, result);
      return result;
    } catch (e) {
      addon._hasResourceCache.set(aPath, false);
      return false;
    } finally {
      zipReader.close();
    }
  },

  /**
   * Reloads the add-on.
   *
   * For temporarily installed add-ons, this uninstalls and re-installs the
   * add-on. Otherwise, the addon is disabled and then re-enabled, and the cache
   * is flushed.
   *
   * @return Promise
   */
  reload() {
    return new Promise((resolve) => {
      const addon = addonFor(this);

      logger.debug(`reloading add-on ${addon.id}`);

      if (!this.temporarilyInstalled) {
        let addonFile = addon.getResourceURI;
        XPIProvider.updateAddonDisabledState(addon, true);
        Services.obs.notifyObservers(addonFile, "flush-cache-entry");
        XPIProvider.updateAddonDisabledState(addon, false)
        resolve();
      } else {
        // This function supports re-installing an existing add-on.
        resolve(AddonManager.installTemporaryAddon(addon._sourceBundle));
      }
    });
  },

  /**
   * Returns a URI to the selected resource or to the add-on bundle if aPath
   * is null. URIs to the bundle will always be file: URIs. URIs to resources
   * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
   * still an XPI file.
   *
   * @param  aPath
   *         The path in the add-on to get the URI for or null to get a URI to
   *         the file or directory the add-on is installed as.
   * @return an nsIURI
   */
  getResourceURI(aPath) {
    let addon = addonFor(this);
    if (!aPath)
      return Services.io.newFileURI(addon._sourceBundle);

    return getURIForResourceInFile(addon._sourceBundle, aPath);
  }
};

function chooseValue(aAddon, aObj, aProp) {
  let repositoryAddon = aAddon._repositoryAddon;
  let objValue = aObj[aProp];

  if (repositoryAddon && (aProp in repositoryAddon) &&
      (objValue === undefined || objValue === null)) {
    return [repositoryAddon[aProp], true];
  }

  return [objValue, false];
}

function defineAddonWrapperProperty(name, getter) {
  Object.defineProperty(AddonWrapper.prototype, name, {
    get: getter,
    enumerable: true,
  });
}

["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible",
 "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
 "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
 "strictCompatibility", "compatibilityOverrides", "updateURL", "dependencies",
 "getDataDirectory", "multiprocessCompatible", "signedState", "mpcOptedOut",
 "isCorrectlySigned"].forEach(function(aProp) {
   defineAddonWrapperProperty(aProp, function() {
     let addon = addonFor(this);
     return (aProp in addon) ? addon[aProp] : undefined;
   });
});

["fullDescription", "developerComments", "eula", "supportURL",
 "contributionURL", "contributionAmount", "averageRating", "reviewCount",
 "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
 "repositoryStatus"].forEach(function(aProp) {
  defineAddonWrapperProperty(aProp, function() {
    let addon = addonFor(this);
    if (addon._repositoryAddon)
      return addon._repositoryAddon[aProp];

    return null;
  });
});

["installDate", "updateDate"].forEach(function(aProp) {
  defineAddonWrapperProperty(aProp, function() {
    return new Date(addonFor(this)[aProp]);
  });
});

["sourceURI", "releaseNotesURI"].forEach(function(aProp) {
  defineAddonWrapperProperty(aProp, function() {
    let addon = addonFor(this);

    // Temporary Installed Addons do not have a "sourceURI",
    // But we can use the "_sourceBundle" as an alternative,
    // which points to the path of the addon xpi installed
    // or its source dir (if it has been installed from a
    // directory).
    if (aProp == "sourceURI" && this.temporarilyInstalled) {
      return Services.io.newFileURI(addon._sourceBundle);
    }

    let [target, fromRepo] = chooseValue(addon, addon, aProp);
    if (!target)
      return null;
    if (fromRepo)
      return target;
    return Services.io.newURI(target);
  });
});

PROP_LOCALE_SINGLE.forEach(function(aProp) {
  defineAddonWrapperProperty(aProp, function() {
    let addon = addonFor(this);
    // Override XPI creator if repository creator is defined
    if (aProp == "creator" &&
        addon._repositoryAddon && addon._repositoryAddon.creator) {
      return addon._repositoryAddon.creator;
    }

    let result = null;

    if (addon.active) {
      try {
        let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." + aProp;
        let value = Preferences.get(pref, null, Ci.nsIPrefLocalizedString);
        if (value)
          result = value;
      } catch (e) {
      }
    }

    if (result == null)
      [result] = chooseValue(addon, addon.selectedLocale, aProp);

    if (aProp == "creator")
      return result ? new AddonManagerPrivate.AddonAuthor(result) : null;

    return result;
  });
});

PROP_LOCALE_MULTI.forEach(function(aProp) {
  defineAddonWrapperProperty(aProp, function() {
    let addon = addonFor(this);
    let results = null;
    let usedRepository = false;

    if (addon.active) {
      let pref = PREF_EM_EXTENSION_FORMAT + addon.id + "." +
                 aProp.substring(0, aProp.length - 1);
      let list = Services.prefs.getChildList(pref, {});
      if (list.length > 0) {
        list.sort();
        results = [];
        for (let childPref of list) {
          let value = Preferences.get(childPref, null, Ci.nsIPrefLocalizedString);
          if (value)
            results.push(value);
        }
      }
    }

    if (results == null)
      [results, usedRepository] = chooseValue(addon, addon.selectedLocale, aProp);

    if (results && !usedRepository) {
      results = results.map(function(aResult) {
        return new AddonManagerPrivate.AddonAuthor(aResult);
      });
    }

    return results;
  });
});

/**
 * An object which identifies a directory install location for add-ons. The
 * location consists of a directory which contains the add-ons installed in the
 * location.
 *
 */
class DirectoryInstallLocation {
  /**
   * Each add-on installed in the location is either a directory containing the
   * add-on's files or a text file containing an absolute path to the directory
   * containing the add-ons files. The directory or text file must have the same
   * name as the add-on's ID.
   *
   * @param  aName
   *         The string identifier for the install location
   * @param  aDirectory
   *         The nsIFile directory for the install location
   * @param  aScope
   *         The scope of add-ons installed in this location
  */
  constructor(aName, aDirectory, aScope) {
    this._name = aName;
    this.locked = true;
    this._directory = aDirectory;
    this._scope = aScope
    this._IDToFileMap = {};
    this._linkedAddons = [];

    this.isSystem = (aName == KEY_APP_SYSTEM_ADDONS ||
                     aName == KEY_APP_SYSTEM_DEFAULTS);

    if (!aDirectory || !aDirectory.exists())
      return;
    if (!aDirectory.isDirectory())
      throw new Error("Location must be a directory.");

    this.initialized = false;
  }

  get path() {
    return this._directory && this._directory.path;
  }

  /**
   * Reads a directory linked to in a file.
   *
   * @param   file
   *          The file containing the directory path
   * @return  An nsIFile object representing the linked directory.
   */
  _readDirectoryFromFile(aFile) {
    let linkedDirectory;
    if (aFile.isSymlink()) {
      linkedDirectory = aFile.clone();
      try {
        linkedDirectory.normalize();
      } catch (e) {
        logger.warn("Symbolic link " + aFile.path + " points to a path" +
             " which does not exist");
        return null;
      }
    } else {
      let fis = Cc["@mozilla.org/network/file-input-stream;1"].
                createInstance(Ci.nsIFileInputStream);
      fis.init(aFile, -1, -1, false);
      let line = { value: "" };
      if (fis instanceof Ci.nsILineInputStream)
        fis.readLine(line);
      fis.close();
      if (line.value) {
        linkedDirectory = Cc["@mozilla.org/file/local;1"].
                              createInstance(Ci.nsIFile);

        try {
          linkedDirectory.initWithPath(line.value);
        } catch (e) {
          linkedDirectory.setRelativeDescriptor(aFile.parent, line.value);
        }
      }
    }

    if (linkedDirectory) {
      if (!linkedDirectory.exists()) {
        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
             " which does not exist");
        return null;
      }

      if (!linkedDirectory.isDirectory()) {
        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
             " which is not a directory");
        return null;
      }

      return linkedDirectory;
    }

    logger.warn("File pointer " + aFile.path + " does not contain a path");
    return null;
  }

  /**
   * Finds all the add-ons installed in this location.
   */
  _readAddons(rescan = false) {
    if ((this.initialized && !rescan) || !this._directory) {
      return;
    }
    this.initialized = true;

    // Use a snapshot of the directory contents to avoid possible issues with
    // iterating over a directory while removing files from it (the YAFFS2
    // embedded filesystem has this issue, see bug 772238).
    let entries = getDirectoryEntries(this._directory);
    for (let entry of entries) {
      let id = entry.leafName;

      if (id == DIR_STAGE || id == DIR_TRASH)
        continue;

      let directLoad = false;
      if (entry.isFile() &&
          id.substring(id.length - 4).toLowerCase() == ".xpi") {
        directLoad = true;
        id = id.substring(0, id.length - 4);
      }

      if (!gIDTest.test(id)) {
        logger.debug("Ignoring file entry whose name is not a valid add-on ID: " +
             entry.path);
        continue;
      }

      if (!directLoad && (entry.isFile() || entry.isSymlink())) {
        let newEntry = this._readDirectoryFromFile(entry);
        if (!newEntry) {
          logger.debug("Deleting stale pointer file " + entry.path);
          try {
            entry.remove(true);
          } catch (e) {
            logger.warn("Failed to remove stale pointer file " + entry.path, e);
            // Failing to remove the stale pointer file is ignorable
          }
          continue;
        }

        entry = newEntry;
        this._linkedAddons.push(id);
      }

      this._IDToFileMap[id] = entry;
      XPIProvider._addURIMapping(id, entry);
    }
  }

  /**
   * Gets the name of this install location.
   */
  get name() {
    return this._name;
  }

  /**
   * Gets the scope of this install location.
   */
  get scope() {
    return this._scope;
  }

  /**
   * Gets an array of nsIFiles for add-ons installed in this location.
   */
  getAddonLocations(rescan = false) {
    this._readAddons(rescan);

    let locations = new Map();
    for (let id in this._IDToFileMap) {
      locations.set(id, this._IDToFileMap[id].clone());
    }
    return locations;
  }

  /**
   * Gets the directory that the add-on with the given ID is installed in.
   *
   * @param  aId
   *         The ID of the add-on
   * @return The nsIFile
   * @throws if the ID does not match any of the add-ons installed
   */
  getLocationForID(aId) {
    if (!(aId in this._IDToFileMap))
      this._readAddons();

    if (aId in this._IDToFileMap)
      return this._IDToFileMap[aId].clone();
    throw new Error("Unknown add-on ID " + aId);
  }

  /**
   * Returns true if the given addon was installed in this location by a text
   * file pointing to its real path.
   *
   * @param aId
   *        The ID of the addon
   */
  isLinkedAddon(aId) {
    return this._linkedAddons.indexOf(aId) != -1;
  }
}

/**
 * An extension of DirectoryInstallLocation which adds methods to installing
 * and removing add-ons from the directory at runtime.
 */
class MutableDirectoryInstallLocation extends DirectoryInstallLocation {
  /**
   * @param  aName
   *         The string identifier for the install location
   * @param  aDirectory
   *         The nsIFile directory for the install location
   * @param  aScope
   *         The scope of add-ons installed in this location
   */
  constructor(aName, aDirectory, aScope) {
    super(aName, aDirectory, aScope);

    this.locked = false;
    this._stagingDirLock = 0;
  }

  /**
   * Gets the staging directory to put add-ons that are pending install and
   * uninstall into.
   *
   * @return an nsIFile
   */
  getStagingDir() {
    return getFile(DIR_STAGE, this._directory);
  }

  requestStagingDir() {
    this._stagingDirLock++;

    if (this._stagingDirPromise)
      return this._stagingDirPromise;

    OS.File.makeDir(this._directory.path);
    let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
    return this._stagingDirPromise = OS.File.makeDir(stagepath).catch((e) => {
      if (e instanceof OS.File.Error && e.becauseExists)
        return;
      logger.error("Failed to create staging directory", e);
      throw e;
    });
  }

  releaseStagingDir() {
    this._stagingDirLock--;

    if (this._stagingDirLock == 0) {
      this._stagingDirPromise = null;
      this.cleanStagingDir();
    }

    return Promise.resolve();
  }

  /**
   * Removes the specified files or directories in the staging directory and
   * then if the staging directory is empty attempts to remove it.
   *
   * @param  aLeafNames
   *         An array of file or directory to remove from the directory, the
   *         array may be empty
   */
  cleanStagingDir(aLeafNames = []) {
    let dir = this.getStagingDir();

    for (let name of aLeafNames) {
      let file = getFile(name, dir);
      recursiveRemove(file);
    }

    if (this._stagingDirLock > 0)
      return;

    let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
    try {
      if (dirEntries.nextFile)
        return;
    } finally {
      dirEntries.close();
    }

    try {
      setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
      dir.remove(false);
    } catch (e) {
      logger.warn("Failed to remove staging dir", e);
      // Failing to remove the staging directory is ignorable
    }
  }

  /**
   * Returns a directory that is normally on the same filesystem as the rest of
   * the install location and can be used for temporarily storing files during
   * safe move operations. Calling this method will delete the existing trash
   * directory and its contents.
   *
   * @return an nsIFile
   */
  getTrashDir() {
    let trashDir = getFile(DIR_TRASH, this._directory);
    let trashDirExists = trashDir.exists();
    try {
      if (trashDirExists)
        recursiveRemove(trashDir);
      trashDirExists = false;
    } catch (e) {
      logger.warn("Failed to remove trash directory", e);
    }
    if (!trashDirExists)
      trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

    return trashDir;
  }

  /**
   * Installs an add-on into the install location.
   *
   * @param  id
   *         The ID of the add-on to install
   * @param  source
   *         The source nsIFile to install from
   * @param  existingAddonID
   *         The ID of an existing add-on to uninstall at the same time
   * @param  action
   *         What to we do with the given source file:
   *           "move"
   *           Default action, the source files will be moved to the new
   *           location,
   *           "copy"
   *           The source files will be copied,
   *           "proxy"
   *           A "proxy file" is going to refer to the source file path
   * @return an nsIFile indicating where the add-on was installed to
   */
  installAddon({ id, source, existingAddonID, action = "move" }) {
    let trashDir = this.getTrashDir();

    let transaction = new SafeInstallOperation();

    let moveOldAddon = aId => {
      let file = getFile(aId, this._directory);
      if (file.exists())
        transaction.moveUnder(file, trashDir);

      file = getFile(`${aId}.xpi`, this._directory);
      if (file.exists()) {
        flushJarCache(file);
        transaction.moveUnder(file, trashDir);
      }
    }

    // If any of these operations fails the finally block will clean up the
    // temporary directory
    try {
      moveOldAddon(id);
      if (existingAddonID && existingAddonID != id) {
        moveOldAddon(existingAddonID);

        {
          // Move the data directories.
          /* XXX ajvincent We can't use OS.File:  installAddon isn't compatible
           * with Promises, nor is SafeInstallOperation.  Bug 945540 has been filed
           * for porting to OS.File.
           */
          let oldDataDir = FileUtils.getDir(
            KEY_PROFILEDIR, ["extension-data", existingAddonID], false, true
          );

          if (oldDataDir.exists()) {
            let newDataDir = FileUtils.getDir(
              KEY_PROFILEDIR, ["extension-data", id], false, true
            );
            if (newDataDir.exists()) {
              let trashData = getFile("data-directory", trashDir);
              transaction.moveUnder(newDataDir, trashData);
            }

            transaction.moveTo(oldDataDir, newDataDir);
          }
        }
      }

      if (action == "copy") {
        transaction.copy(source, this._directory);
      } else if (action == "move") {
        if (source.isFile())
          flushJarCache(source);

        transaction.moveUnder(source, this._directory);
      }
      // Do nothing for the proxy file as we sideload an addon permanently
    } finally {
      // It isn't ideal if this cleanup fails but it isn't worth rolling back
      // the install because of it.
      try {
        recursiveRemove(trashDir);
      } catch (e) {
        logger.warn("Failed to remove trash directory when installing " + id, e);
      }
    }

    let newFile = this._directory.clone();

    if (action == "proxy") {
      // When permanently installing sideloaded addon, we just put a proxy file
      // refering to the addon sources
      newFile.append(id);

      writeStringToFile(newFile, source.path);
    } else {
      newFile.append(source.leafName);
    }

    try {
      newFile.lastModifiedTime = Date.now();
    } catch (e) {
      logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
    }
    this._IDToFileMap[id] = newFile;
    XPIProvider._addURIMapping(id, newFile);

    if (existingAddonID && existingAddonID != id &&
        existingAddonID in this._IDToFileMap) {
      delete this._IDToFileMap[existingAddonID];
    }

    return newFile;
  }

  /**
   * Uninstalls an add-on from this location.
   *
   * @param  aId
   *         The ID of the add-on to uninstall
   * @throws if the ID does not match any of the add-ons installed
   */
  uninstallAddon(aId) {
    let file = this._IDToFileMap[aId];
    if (!file) {
      logger.warn("Attempted to remove " + aId + " from " +
           this._name + " but it was already gone");
      return;
    }

    file = getFile(aId, this._directory);
    if (!file.exists())
      file.leafName += ".xpi";

    if (!file.exists()) {
      logger.warn("Attempted to remove " + aId + " from " +
           this._name + " but it was already gone");

      delete this._IDToFileMap[aId];
      return;
    }

    let trashDir = this.getTrashDir();

    if (file.leafName != aId) {
      logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
      flushJarCache(file);
    }

    let transaction = new SafeInstallOperation();

    try {
      transaction.moveUnder(file, trashDir);
    } finally {
      // It isn't ideal if this cleanup fails, but it is probably better than
      // rolling back the uninstall at this point
      try {
        recursiveRemove(trashDir);
      } catch (e) {
        logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
      }
    }

    XPIStates.removeAddon(this.name, aId);

    delete this._IDToFileMap[aId];
  }
}

/**
 * An object which identifies a directory install location for system add-ons
 * updates.
 */
class SystemAddonInstallLocation extends MutableDirectoryInstallLocation {
  /**
    * The location consists of a directory which contains the add-ons installed.
    *
    * @param  aName
    *         The string identifier for the install location
    * @param  aDirectory
    *         The nsIFile directory for the install location
    * @param  aScope
    *         The scope of add-ons installed in this location
    * @param  aResetSet
    *         True to throw away the current add-on set
    */
  constructor(aName, aDirectory, aScope, aResetSet) {
    let addonSet = SystemAddonInstallLocation._loadAddonSet();
    let directory = null;

    // The system add-on update directory is stored in a pref.
    // Therefore, this is looked up before calling the
    // constructor on the superclass.
    if (addonSet.directory) {
      directory = getFile(addonSet.directory, aDirectory);
      logger.info("SystemAddonInstallLocation scanning directory " + directory.path);
    } else {
      logger.info("SystemAddonInstallLocation directory is missing");
    }

    super(aName, directory, aScope);

    this._addonSet = addonSet;
    this._baseDir = aDirectory;
    this._nextDir = null;
    this._directory = directory;

    this._stagingDirLock = 0;

    if (aResetSet) {
      this.resetAddonSet();
    }

    this.locked = false;
  }

  /**
   * Gets the staging directory to put add-ons that are pending install and
   * uninstall into.
   *
   * @return {nsIFile} - staging directory for system add-on upgrades.
   */
  getStagingDir() {
    this._addonSet = SystemAddonInstallLocation._loadAddonSet();
    let dir = null;
    if (this._addonSet.directory) {
      this._directory = getFile(this._addonSet.directory, this._baseDir);
      dir = getFile(DIR_STAGE, this._directory);
    } else {
      logger.info("SystemAddonInstallLocation directory is missing");
    }

    return dir;
  }

  requestStagingDir() {
    this._addonSet = SystemAddonInstallLocation._loadAddonSet();
    if (this._addonSet.directory) {
      this._directory = getFile(this._addonSet.directory, this._baseDir);
    }
    return super.requestStagingDir();
  }

  /**
   * Reads the current set of system add-ons
   */
  static _loadAddonSet() {
    try {
      let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
      if (setStr) {
        let addonSet = JSON.parse(setStr);
        if ((typeof addonSet == "object") && addonSet.schema == 1) {
          return addonSet;
        }
      }
    } catch (e) {
      logger.error("Malformed system add-on set, resetting.");
    }

    return { schema: 1, addons: {} };
  }

  /**
   * Saves the current set of system add-ons
   *
   * @param {Object} aAddonSet - object containing schema, directory and set
   *                 of system add-on IDs and versions.
   */
  static _saveAddonSet(aAddonSet) {
    Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
  }

  getAddonLocations() {
    // Updated system add-ons are ignored in safe mode
    if (Services.appinfo.inSafeMode) {
      return new Map();
    }

    let addons = super.getAddonLocations();

    // Strip out any unexpected add-ons from the list
    for (let id of addons.keys()) {
      if (!(id in this._addonSet.addons)) {
        addons.delete(id);
      }
    }

    return addons;
  }

  /**
   * Tests whether updated system add-ons are expected.
   */
  isActive() {
    return this._directory != null;
  }

  isValidAddon(aAddon) {
    if (aAddon.appDisabled) {
      logger.warn(`System add-on ${aAddon.id} isn't compatible with the application.`);
      return false;
    }

    if (aAddon.unpack) {
      logger.warn(`System add-on ${aAddon.id} isn't a packed add-on.`);
      return false;
    }

    if (!aAddon.bootstrap) {
      logger.warn(`System add-on ${aAddon.id} isn't restartless.`);
      return false;
    }

    if (!aAddon.multiprocessCompatible) {
      logger.warn(`System add-on ${aAddon.id} isn't multiprocess compatible.`);
      return false;
    }

    return true;
  }

  /**
   * Tests whether the loaded add-on information matches what is expected.
   */
  isValid(aAddons) {
    for (let id of Object.keys(this._addonSet.addons)) {
      if (!aAddons.has(id)) {
        logger.warn(`Expected add-on ${id} is missing from the system add-on location.`);
        return false;
      }

      let addon = aAddons.get(id);
      if (addon.version != this._addonSet.addons[id].version) {
        logger.warn(`Expected system add-on ${id} to be version ${this._addonSet.addons[id].version} but was ${addon.version}.`);
        return false;
      }

      if (!this.isValidAddon(addon))
        return false;
    }

    return true;
  }

  /**
   * Resets the add-on set so on the next startup the default set will be used.
   */
  resetAddonSet() {
    logger.info("Removing all system add-on upgrades.");

    // remove everything from the pref first, if uninstall
    // fails then at least they will not be re-activated on
    // next restart.
    this._addonSet = { schema: 1, addons: {} };
    SystemAddonInstallLocation._saveAddonSet(this._addonSet);

    // If this is running at app startup, the pref being cleared
    // will cause later stages of startup to notice that the
    // old updates are now gone.
    //
    // Updates will only be explicitly uninstalled if they are
    // removed restartlessly, for instance if they are no longer
    // part of the latest update set.
    if (this._addonSet) {
      for (let id of Object.keys(this._addonSet.addons)) {
        AddonManager.getAddonByID(id, addon => {
          if (addon) {
            addon.uninstall();
          }
        });
      }
    }
  }

  /**
   * Removes any directories not currently in use or pending use after a
   * restart. Any errors that happen here don't really matter as we'll attempt
   * to cleanup again next time.
   */
  async cleanDirectories() {
    // System add-ons directory does not exist
    if (!(await OS.File.exists(this._baseDir.path))) {
      return;
    }

    let iterator;
    try {
      iterator = new OS.File.DirectoryIterator(this._baseDir.path);
    } catch (e) {
      logger.error("Failed to clean updated system add-ons directories.", e);
      return;
    }

    try {
      for (let promise in iterator) {
        let entry = await promise;

        // Skip the directory currently in use
        if (this._directory && this._directory.path == entry.path) {
          continue;
        }

        // Skip the next directory
        if (this._nextDir && this._nextDir.path == entry.path) {
          continue;
        }

        if (entry.isDir) {
           await OS.File.removeDir(entry.path, {
             ignoreAbsent: true,
             ignorePermissions: true,
           });
         } else {
           await OS.File.remove(entry.path, {
             ignoreAbsent: true,
           });
         }
       }

    } catch (e) {
      logger.error("Failed to clean updated system add-ons directories.", e);
    } finally {
      iterator.close();
    }
  }

  /**
   * Installs a new set of system add-ons into the location and updates the
   * add-on set in prefs.
   *
   * @param {Array} aAddons - An array of addons to install.
   */
  async installAddonSet(aAddons) {
    // Make sure the base dir exists
    await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });

    let addonSet = SystemAddonInstallLocation._loadAddonSet();

    // Remove any add-ons that are no longer part of the set.
    for (let addonID of Object.keys(addonSet.addons)) {
      if (!aAddons.includes(addonID)) {
        AddonManager.getAddonByID(addonID, a => a.uninstall());
      }
    }

    let newDir = this._baseDir.clone();

    let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
                  getService(Ci.nsIUUIDGenerator);
    newDir.append("blank");

    while (true) {
      newDir.leafName = uuidGen.generateUUID().toString();

      try {
        await OS.File.makeDir(newDir.path, { ignoreExisting: false });
        break;
      } catch (e) {
        logger.debug("Could not create new system add-on updates dir, retrying", e);
      }
    }

    // Record the new upgrade directory.
    let state = { schema: 1, directory: newDir.leafName, addons: {} };
    SystemAddonInstallLocation._saveAddonSet(state);

    this._nextDir = newDir;
    let location = this;

    let installs = [];
    for (let addon of aAddons) {
      let install = await createLocalInstall(addon._sourceBundle, location);
      installs.push(install);
    }

    async function installAddon(install) {
      // Make the new install own its temporary file.
      install.ownsTempFile = true;
      install.install();
    }

    async function postponeAddon(install) {
      let resumeFn;
      if (AddonManagerPrivate.hasUpgradeListener(install.addon.id)) {
        logger.info(`system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`);
        resumeFn = () => {
          logger.info(`${install.addon.id} has resumed a previously postponed addon set`);
          install.installLocation.resumeAddonSet(installs);
        }
      }
      await install.postpone(resumeFn);
    }

    let previousState;

    try {
      // All add-ons in position, create the new state and store it in prefs
      state = { schema: 1, directory: newDir.leafName, addons: {} };
      for (let addon of aAddons) {
        state.addons[addon.id] = {
          version: addon.version
        }
      }

      previousState = SystemAddonInstallLocation._loadAddonSet();
      SystemAddonInstallLocation._saveAddonSet(state);

      let blockers = aAddons.filter(
        addon => AddonManagerPrivate.hasUpgradeListener(addon.id)
      );

      if (blockers.length > 0) {
        await waitForAllPromises(installs.map(postponeAddon));
      } else {
        await waitForAllPromises(installs.map(installAddon));
      }
    } catch (e) {
      // Roll back to previous upgrade set (if present) on restart.
      if (previousState) {
        SystemAddonInstallLocation._saveAddonSet(previousState);
      }
      // Otherwise, roll back to built-in set on restart.
      // TODO try to do these restartlessly
      this.resetAddonSet();

      try {
        await OS.File.removeDir(newDir.path, { ignorePermissions: true });
      } catch (e) {
        logger.warn(`Failed to remove failed system add-on directory ${newDir.path}.`, e);
      }
      throw e;
    }
  }

 /**
  * Resumes upgrade of a previously-delayed add-on set.
  */
  async resumeAddonSet(installs) {
    async function resumeAddon(install) {
      install.state = AddonManager.STATE_DOWNLOADED;
      install.installLocation.releaseStagingDir();
      install.install();
    }

    let blockers = installs.filter(
      install => AddonManagerPrivate.hasUpgradeListener(install.addon.id)
    );

    if (blockers.length > 1) {
      logger.warn("Attempted to resume system add-on install but upgrade blockers are still present");
    } else {
      await waitForAllPromises(installs.map(resumeAddon));
    }
  }

  /**
   * Returns a directory that is normally on the same filesystem as the rest of
   * the install location and can be used for temporarily storing files during
   * safe move operations. Calling this method will delete the existing trash
   * directory and its contents.
   *
   * @return an nsIFile
   */
  getTrashDir() {
    let trashDir = getFile(DIR_TRASH, this._directory);
    let trashDirExists = trashDir.exists();
    try {
      if (trashDirExists)
        recursiveRemove(trashDir);
      trashDirExists = false;
    } catch (e) {
      logger.warn("Failed to remove trash directory", e);
    }
    if (!trashDirExists)
      trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);

    return trashDir;
  }

  /**
   * Installs an add-on into the install location.
   *
   * @param  id
   *         The ID of the add-on to install
   * @param  source
   *         The source nsIFile to install from
   * @return an nsIFile indicating where the add-on was installed to
   */
  installAddon({id, source}) {
    let trashDir = this.getTrashDir();
    let transaction = new SafeInstallOperation();

    // If any of these operations fails the finally block will clean up the
    // temporary directory
    try {
      if (source.isFile()) {
        flushJarCache(source);
      }

      transaction.moveUnder(source, this._directory);
    } finally {
      // It isn't ideal if this cleanup fails but it isn't worth rolling back
      // the install because of it.
      try {
        recursiveRemove(trashDir);
      } catch (e) {
        logger.warn("Failed to remove trash directory when installing " + id, e);
      }
    }

    let newFile = getFile(source.leafName, this._directory);

    try {
      newFile.lastModifiedTime = Date.now();
    } catch (e) {
      logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
    }
    this._IDToFileMap[id] = newFile;
    XPIProvider._addURIMapping(id, newFile);

    return newFile;
  }

  // old system add-on upgrade dirs get automatically removed
  uninstallAddon(aAddon) {}
}

/**
 * An object which identifies an install location for temporary add-ons.
 */
const TemporaryInstallLocation = {
  locked: false,
  name: KEY_APP_TEMPORARY,
  scope: AddonManager.SCOPE_TEMPORARY,
  getAddonLocations: () => [],
  isLinkedAddon: () => false,
  installAddon: () => {},
  uninstallAddon: (aAddon) => {},
  getStagingDir: () => {},
}

/**
 * An object that identifies a registry install location for add-ons. The location
 * consists of a registry key which contains string values mapping ID to the
 * path where an add-on is installed
 *
 */
class WinRegInstallLocation extends DirectoryInstallLocation {
  /**
    * @param  aName
    *         The string identifier of this Install Location.
    * @param  aRootKey
    *         The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
    * @param  scope
    *         The scope of add-ons installed in this location
    */
  constructor(aName, aRootKey, aScope) {
    super(aName, undefined, aScope);

    this.locked = true;
    this._name = aName;
    this._rootKey = aRootKey;
    this._scope = aScope;
    this._IDToFileMap = {};

    let path = this._appKeyPath + "\\Extensions";
    let key = Cc["@mozilla.org/windows-registry-key;1"].
              createInstance(Ci.nsIWindowsRegKey);

    // Reading the registry may throw an exception, and that's ok.  In error
    // cases, we just leave ourselves in the empty state.
    try {
      key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
    } catch (e) {
      return;
    }

    this._readAddons(key);
    key.close();
  }

  /**
   * Retrieves the path of this Application's data key in the registry.
   */
  get _appKeyPath() {
    let appVendor = Services.appinfo.vendor;
    let appName = Services.appinfo.name;

    // XXX Thunderbird doesn't specify a vendor string
    if (AppConstants.MOZ_APP_NAME == "thunderbird" && appVendor == "")
      appVendor = "Mozilla";

    // XULRunner-based apps may intentionally not specify a vendor
    if (appVendor != "")
      appVendor += "\\";

    return "SOFTWARE\\" + appVendor + appName;
  }

  /**
   * Read the registry and build a mapping between ID and path for each
   * installed add-on.
   *
   * @param  key
   *         The key that contains the ID to path mapping
   */
  _readAddons(aKey) {
    let count = aKey.valueCount;
    for (let i = 0; i < count; ++i) {
      let id = aKey.getValueName(i);

      let file = new nsIFile(aKey.readStringValue(id));

      if (!file.exists()) {
        logger.warn("Ignoring missing add-on in " + file.path);
        continue;
      }

      this._IDToFileMap[id] = file;
      XPIProvider._addURIMapping(id, file);
    }
  }

  /**
   * Gets the name of this install location.
   */
  get name() {
    return this._name;
  }

  /**
   * @see DirectoryInstallLocation
   */
  isLinkedAddon(aId) {
    return true;
  }
}

this.XPIInternal = {
  AddonInternal,
  BOOTSTRAP_REASONS,
  KEY_APP_SYSTEM_ADDONS,
  KEY_APP_SYSTEM_DEFAULTS,
  KEY_APP_TEMPORARY,
  TEMPORARY_ADDON_SUFFIX,
  TOOLKIT_ID,
  XPIStates,
  getExternalType,
  isTheme,
  isUsableAddon,
  isWebExtension,
  recordAddonTelemetry,

  get XPIDatabase() { return gGlobalScope.XPIDatabase; },
};

var addonTypes = [
  new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 4000,
                                    AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
  new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 5000),
  new AddonManagerPrivate.AddonType("dictionary", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 7000,
                                    AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
  new AddonManagerPrivate.AddonType("locale", URI_EXTENSION_STRINGS,
                                    STRING_TYPE_NAME,
                                    AddonManager.VIEW_TYPE_LIST, 8000,
                                    AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
];

// We only register experiments support if the application supports them.
// Ideally, we would install an observer to watch the pref. Installing
// an observer for this pref is not necessary here and may be buggy with
// regards to registering this XPIProvider twice.
if (Preferences.get("experiments.supported", false)) {
  addonTypes.push(
    new AddonManagerPrivate.AddonType("experiment",
                                      URI_EXTENSION_STRINGS,
                                      STRING_TYPE_NAME,
                                      AddonManager.VIEW_TYPE_LIST, 11000,
                                      AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL));
}

AddonManagerPrivate.registerProvider(XPIProvider, addonTypes);
PK
!<VԘ"modules/addons/XPIProviderUtils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// These are injected from XPIProvider.jsm
/* globals ADDON_SIGNING, SIGNED_TYPES, BOOTSTRAP_REASONS, DB_SCHEMA,
          AddonInternal, XPIProvider, XPIStates, syncLoadManifestFromFile,
          isUsableAddon, recordAddonTelemetry,
          flushChromeCaches, descriptorToPath */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
/* globals AddonManagerPrivate*/
Cu.import("resource://gre/modules/Preferences.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
                                  "resource://gre/modules/DeferredSave.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
                                   "@mozilla.org/extensions/blocklist;1",
                                   Ci.nsIBlocklistService);

XPCOMUtils.defineLazyPreferenceGetter(this, "ALLOW_NON_MPC",
                                      "extensions.allow-non-mpc-extensions");

Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi-utils";

const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                       "initWithPath");

// Create a new logger for use by the Addons XPI Provider Utils
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);

const KEY_PROFILEDIR                  = "ProfD";
const FILE_JSON_DB                    = "extensions.json";

// The last version of DB_SCHEMA implemented in SQLITE
const LAST_SQLITE_DB_SCHEMA           = 14;
const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
const PREF_E10S_BLOCKED_BY_ADDONS     = "extensions.e10sBlockedByAddons";
const PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons";
const PREF_E10S_HAS_NONEXEMPT_ADDON   = "extensions.e10s.rollout.hasAddon";

const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
const KEY_APP_GLOBAL                  = "app-global";
const KEY_APP_TEMPORARY               = "app-temporary";

// Properties to save in JSON file
const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
                          "internalName", "updateURL", "updateKey", "optionsURL",
                          "optionsType", "optionsBrowserStyle", "aboutURL",
                          "defaultLocale", "visible", "active", "userDisabled",
                          "appDisabled", "pendingUninstall", "installDate",
                          "updateDate", "applyBackgroundUpdates", "bootstrap", "path",
                          "skinnable", "size", "sourceURI", "releaseNotesURI",
                          "softDisabled", "foreignInstall", "hasBinaryComponents",
                          "strictCompatibility", "locales", "targetApplications",
                          "targetPlatforms", "multiprocessCompatible", "signedState",
                          "seen", "dependencies", "hasEmbeddedWebExtension", "mpcOptedOut",
                          "userPermissions", "icons", "iconURL", "icon64URL",
                          "blocklistState", "blocklistURL"];

// Time to wait before async save of XPI JSON database, in milliseconds
const ASYNC_SAVE_DELAY_MS = 20;

/**
 * Asynchronously fill in the _repositoryAddon field for one addon
 */
function getRepositoryAddon(aAddon, aCallback) {
  if (!aAddon) {
    aCallback(aAddon);
    return;
  }
  function completeAddon(aRepositoryAddon) {
    aAddon._repositoryAddon = aRepositoryAddon;
    aAddon.compatibilityOverrides = aRepositoryAddon ?
                                      aRepositoryAddon.compatibilityOverrides :
                                      null;
    aCallback(aAddon);
  }
  AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
}

/**
 * Wrap an API-supplied function in an exception handler to make it safe to call
 */
function makeSafe(aCallback) {
  return function(...aArgs) {
    try {
      aCallback(...aArgs);
    } catch (ex) {
      logger.warn("XPI Database callback failed", ex);
    }
  }
}

/**
 * A helper method to asynchronously call a function on an array of objects.
 * Returns a promise that resolves with the results for each function call in
 * the same order as the aObjects array.
 * WARNING: not currently error-safe; if the async function does not call its
 * callback for any of the array elements, asyncMap will never resolve.
 *
 * @param  aObjects
 *         The array of objects to process asynchronously
 * @param  aMethod
 *         Function with signature function(object, function(f_of_object))
 */
function asyncMap(aObjects, aMethod) {
  let methodCalls = aObjects.map(obj => {
    return new Promise(resolve => {
      try {
        aMethod(obj, resolve);
      } catch (e) {
        logger.error("Async map function failed", e);
        resolve(undefined);
      }
    });
  });

  return Promise.all(methodCalls);
}

/**
 * Copies properties from one object to another. If no target object is passed
 * a new object will be created and returned.
 *
 * @param  aObject
 *         An object to copy from
 * @param  aProperties
 *         An array of properties to be copied
 * @param  aTarget
 *         An optional target object to copy the properties to
 * @return the object that the properties were copied onto
 */
function copyProperties(aObject, aProperties, aTarget) {
  if (!aTarget)
    aTarget = {};
  aProperties.forEach(function(aProp) {
    if (aProp in aObject)
      aTarget[aProp] = aObject[aProp];
  });
  return aTarget;
}

/**
 * The DBAddonInternal is a special AddonInternal that has been retrieved from
 * the database. The constructor will initialize the DBAddonInternal with a set
 * of fields, which could come from either the JSON store or as an
 * XPIProvider.AddonInternal created from an addon's manifest
 * @constructor
 * @param aLoaded
 *        Addon data fields loaded from JSON or the addon manifest.
 */
function DBAddonInternal(aLoaded) {
  AddonInternal.call(this);

  if (aLoaded.descriptor) {
    if (!aLoaded.path) {
      aLoaded.path = descriptorToPath(aLoaded.descriptor);
    }
    delete aLoaded.descriptor;
  }

  copyProperties(aLoaded, PROP_JSON_FIELDS, this);

  if (!this.dependencies)
    this.dependencies = [];
  Object.freeze(this.dependencies);

  if (aLoaded._installLocation) {
    this._installLocation = aLoaded._installLocation;
    this.location = aLoaded._installLocation.name;
  } else if (aLoaded.location) {
    this._installLocation = XPIProvider.installLocationsByName[this.location];
  }

  this._key = this.location + ":" + this.id;

  if (!aLoaded._sourceBundle) {
    throw new Error("Expected passed argument to contain a path");
  }

  this._sourceBundle = aLoaded._sourceBundle;

  XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", function() {
      for (let install of XPIProvider.installs) {
        if (install.state == AddonManager.STATE_INSTALLED &&
            !(install.addon.inDatabase) &&
            install.addon.id == this.id &&
            install.installLocation == this._installLocation) {
          delete this.pendingUpgrade;
          return this.pendingUpgrade = install.addon;
        }
      }
      return null;
    });
}

DBAddonInternal.prototype = Object.create(AddonInternal.prototype);
Object.assign(DBAddonInternal.prototype, {
  applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
    let wasCompatible = this.isCompatible;

    this.targetApplications.forEach(function(aTargetApp) {
      aUpdate.targetApplications.forEach(function(aUpdateTarget) {
        if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
            Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
          aTargetApp.minVersion = aUpdateTarget.minVersion;
          aTargetApp.maxVersion = aUpdateTarget.maxVersion;
          XPIDatabase.saveChanges();
        }
      });
    });
    if (aUpdate.multiprocessCompatible !== undefined &&
        aUpdate.multiprocessCompatible != this.multiprocessCompatible) {
      this.multiprocessCompatible = aUpdate.multiprocessCompatible;
      XPIDatabase.saveChanges();
    }

    if (wasCompatible != this.isCompatible)
      XPIProvider.updateAddonDisabledState(this);
  },

  toJSON() {
    let jsonData = copyProperties(this, PROP_JSON_FIELDS);

    // Experiments are serialized as disabled so they aren't run on the next
    // startup.
    if (this.type == "experiment") {
      jsonData.userDisabled = true;
      jsonData.active = false;
    }

    return jsonData;
  },

  get inDatabase() {
    return true;
  }
});

/**
 * Internal interface: find an addon from an already loaded addonDB
 */
function _findAddon(addonDB, aFilter) {
  for (let addon of addonDB.values()) {
    if (aFilter(addon)) {
      return addon;
    }
  }
  return null;
}

/**
 * Internal interface to get a filtered list of addons from a loaded addonDB
 */
function _filterDB(addonDB, aFilter) {
  return Array.from(addonDB.values()).filter(aFilter);
}

this.XPIDatabase = {
  // true if the database connection has been opened
  initialized: false,
  // The database file
  jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true),
  // Migration data loaded from an old version of the database.
  migrateData: null,
  // Active add-on directories loaded from extensions.ini and prefs at startup.
  activeBundles: null,

  // Saved error object if we fail to read an existing database
  _loadError: null,

  // Error reported by our most recent attempt to read or write the database, if any
  get lastError() {
    if (this._loadError)
      return this._loadError;
    if (this._deferredSave)
      return this._deferredSave.lastError;
    return null;
  },

  /**
   * Mark the current stored data dirty, and schedule a flush to disk
   */
  saveChanges() {
    if (!this.initialized) {
      throw new Error("Attempt to use XPI database when it is not initialized");
    }

    if (XPIProvider._closing) {
      // use an Error here so we get a stack trace.
      let err = new Error("XPI database modified after shutdown began");
      logger.warn(err);
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_late_stack", Log.stackTrace(err));
    }

    if (!this._deferredSave) {
      this._deferredSave = new DeferredSave(this.jsonFile.path,
                                            () => JSON.stringify(this),
                                            ASYNC_SAVE_DELAY_MS);
    }

    this.updateAddonsBlockingE10s();
    this.updateAddonsBlockingE10sMulti();
    let promise = this._deferredSave.saveChanges();
    if (!this._schemaVersionSet) {
      this._schemaVersionSet = true;
      promise = promise.then(
        count => {
          // Update the XPIDB schema version preference the first time we successfully
          // save the database.
          logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
          Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
          // Reading the DB worked once, so we don't need the load error
          this._loadError = null;
        },
        error => {
          // Need to try setting the schema version again later
          this._schemaVersionSet = false;
          // this._deferredSave.lastError has the most recent error so we don't
          // need this any more
          this._loadError = null;

          throw error;
        });
    }

    promise.catch(error => {
      logger.warn("Failed to save XPI database", error);
    });
  },

  flush() {
    // handle the "in memory only" and "saveChanges never called" cases
    if (!this._deferredSave) {
      return Promise.resolve(0);
    }

    return this._deferredSave.flush();
  },

  /**
   * Converts the current internal state of the XPI addon database to
   * a JSON.stringify()-ready structure
   */
  toJSON() {
    if (!this.addonDB) {
      // We never loaded the database?
      throw new Error("Attempt to save database without loading it first");
    }

    let toSave = {
      schemaVersion: DB_SCHEMA,
      addons: Array.from(this.addonDB.values())
                   .filter(addon => addon.location != KEY_APP_TEMPORARY),
    };
    return toSave;
  },

  /**
   * Synchronously opens and reads the database file, upgrading from old
   * databases or making a new DB if needed.
   *
   * The possibilities, in order of priority, are:
   * 1) Perfectly good, up to date database
   * 2) Out of date JSON database needs to be upgraded => upgrade
   * 3) JSON database exists but is mangled somehow => build new JSON
   * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade
   * 5) useless SQLITE DB => build new JSON
   * 6) useable RDF DB => upgrade
   * 7) useless RDF DB => build new JSON
   * 8) Nothing at all => build new JSON
   * @param  aRebuildOnError
   *         A boolean indicating whether add-on information should be loaded
   *         from the install locations if the database needs to be rebuilt.
   *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
   */
  syncLoadDB(aRebuildOnError) {
    this.migrateData = null;
    let fstream = null;
    let data = "";
    try {
      let readTimer = AddonManagerPrivate.simpleTimer("XPIDB_syncRead_MS");
      logger.debug("Opening XPI database " + this.jsonFile.path);
      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
              createInstance(Components.interfaces.nsIFileInputStream);
      fstream.init(this.jsonFile, -1, 0, 0);
      let cstream = null;
      try {
        cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
                createInstance(Components.interfaces.nsIConverterInputStream);
        cstream.init(fstream, "UTF-8", 0, 0);

        let str = {};
        let read = 0;
        do {
          read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
          data += str.value;
        } while (read != 0);

        readTimer.done();
        this.parseDB(data, aRebuildOnError);
      } catch (e) {
        logger.error("Failed to load XPI JSON data from profile", e);
        let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildReadFailed_MS");
        this.rebuildDatabase(aRebuildOnError);
        rebuildTimer.done();
      } finally {
        if (cstream)
          cstream.close();
      }
    } catch (e) {
      if (e.result === Cr.NS_ERROR_FILE_NOT_FOUND) {
        this.upgradeDB(aRebuildOnError);
      } else {
        this.rebuildUnreadableDB(e, aRebuildOnError);
      }
    } finally {
      if (fstream)
        fstream.close();
    }
    // If an async load was also in progress, record in telemetry.
    if (this._dbPromise) {
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_overlapped_load", 1);
    }
    this._dbPromise = Promise.resolve(this.addonDB);
    Services.obs.notifyObservers(this.addonDB, "xpi-database-loaded");
  },

  /**
   * Parse loaded data, reconstructing the database if the loaded data is not valid
   * @param aRebuildOnError
   *        If true, synchronously reconstruct the database from installed add-ons
   */
  parseDB(aData, aRebuildOnError) {
    let parseTimer = AddonManagerPrivate.simpleTimer("XPIDB_parseDB_MS");
    try {
      // dump("Loaded JSON:\n" + aData + "\n");
      let inputAddons = JSON.parse(aData);
      // Now do some sanity checks on our JSON db
      if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
        parseTimer.done();
        // Content of JSON file is bad, need to rebuild from scratch
        logger.error("bad JSON file contents");
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "badJSON");
        let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildBadJSON_MS");
        this.rebuildDatabase(aRebuildOnError);
        rebuildTimer.done();
        return;
      }
      if (inputAddons.schemaVersion != DB_SCHEMA) {
        // Handle mismatched JSON schema version. For now, we assume
        // compatibility for JSON data, though we throw away any fields we
        // don't know about (bug 902956)
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError",
                                                "schemaMismatch-" + inputAddons.schemaVersion);
        logger.debug("JSON schema mismatch: expected " + DB_SCHEMA +
            ", actual " + inputAddons.schemaVersion);
        // When we rev the schema of the JSON database, we need to make sure we
        // force the DB to save so that the DB_SCHEMA value in the JSON file and
        // the preference are updated.
      }
      // If we got here, we probably have good data
      // Make AddonInternal instances from the loaded data and save them
      let addonDB = new Map();
      for (let loadedAddon of inputAddons.addons) {
        try {
          if (!loadedAddon.path) {
            loadedAddon.path = descriptorToPath(loadedAddon.descriptor);
          }
          loadedAddon._sourceBundle = new nsIFile(loadedAddon.path);
        } catch (e) {
          // We can fail here when the path is invalid, usually from the
          // wrong OS
          logger.warn("Could not find source bundle for add-on " + loadedAddon.id, e);
        }

        let newAddon = new DBAddonInternal(loadedAddon);
        addonDB.set(newAddon._key, newAddon);
      }
      parseTimer.done();
      this.addonDB = addonDB;
      logger.debug("Successfully read XPI database");
      this.initialized = true;
    } catch (e) {
      // If we catch and log a SyntaxError from the JSON
      // parser, the xpcshell test harness fails the test for us: bug 870828
      parseTimer.done();
      if (e.name == "SyntaxError") {
        logger.error("Syntax error parsing saved XPI JSON data");
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "syntax");
      } else {
        logger.error("Failed to load XPI JSON data from profile", e);
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "other");
      }
      let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildReadFailed_MS");
      this.rebuildDatabase(aRebuildOnError);
      rebuildTimer.done();
    }
  },

  /**
   * Upgrade database from earlier (sqlite or RDF) version if available
   */
  upgradeDB(aRebuildOnError) {
    let upgradeTimer = AddonManagerPrivate.simpleTimer("XPIDB_upgradeDB_MS");

    let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA, 0);
    if (schemaVersion > LAST_SQLITE_DB_SCHEMA) {
      // we've upgraded before but the JSON file is gone, fall through
      // and rebuild from scratch
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "dbMissing");
    }

    this.rebuildDatabase(aRebuildOnError);
    upgradeTimer.done();
  },

  /**
   * Reconstruct when the DB file exists but is unreadable
   * (for example because read permission is denied)
   */
  rebuildUnreadableDB(aError, aRebuildOnError) {
    let rebuildTimer = AddonManagerPrivate.simpleTimer("XPIDB_rebuildUnreadableDB_MS");
    logger.warn("Extensions database " + this.jsonFile.path +
        " exists but is not readable; rebuilding", aError);
    // Remember the error message until we try and write at least once, so
    // we know at shutdown time that there was a problem
    this._loadError = aError;
    AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", "unreadable");
    this.rebuildDatabase(aRebuildOnError);
    rebuildTimer.done();
  },

  /**
   * Open and read the XPI database asynchronously, upgrading if
   * necessary. If any DB load operation fails, we need to
   * synchronously rebuild the DB from the installed extensions.
   *
   * @return Promise<Map> resolves to the Map of loaded JSON data stored
   *         in this.addonDB; never rejects.
   */
  asyncLoadDB() {
    // Already started (and possibly finished) loading
    if (this._dbPromise) {
      return this._dbPromise;
    }

    logger.debug("Starting async load of XPI database " + this.jsonFile.path);
    AddonManagerPrivate.recordSimpleMeasure("XPIDB_async_load", XPIProvider.runPhase);
    let readOptions = {
      outExecutionDuration: 0
    };
    this._dbPromise = OS.File.read(this.jsonFile.path, null, readOptions).then(
      byteArray => {
        logger.debug("Async JSON file read took " + readOptions.outExecutionDuration + " MS");
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_asyncRead_MS",
          readOptions.outExecutionDuration);

        if (this.addonDB) {
          logger.debug("Synchronous load completed while waiting for async load");
          return this.addonDB;
        }
        logger.debug("Finished async read of XPI database, parsing...");
        let decodeTimer = AddonManagerPrivate.simpleTimer("XPIDB_decode_MS");
        let decoder = new TextDecoder();
        let data = decoder.decode(byteArray);
        decodeTimer.done();
        this.parseDB(data, true);
        return this.addonDB;
      })
    .catch(
      error => {
        if (this.addonDB) {
          logger.debug("Synchronous load completed while waiting for async load");
          return this.addonDB;
        }
        if (error.becauseNoSuchFile) {
          this.upgradeDB(true);
        } else {
          // it's there but unreadable
          this.rebuildUnreadableDB(error, true);
        }
        return this.addonDB;
      });

    this._dbPromise.then(() => {
      Services.obs.notifyObservers(this.addonDB, "xpi-database-loaded");
    });

    return this._dbPromise;
  },

  /**
   * Rebuild the database from addon install directories. If this.migrateData
   * is available, uses migrated information for settings on the addons found
   * during rebuild
   * @param aRebuildOnError
   *         A boolean indicating whether add-on information should be loaded
   *         from the install locations if the database needs to be rebuilt.
   *         (if false, caller is XPIProvider.checkForChanges() which will rebuild)
   */
  rebuildDatabase(aRebuildOnError) {
    this.addonDB = new Map();
    this.initialized = true;

    if (XPIStates.size == 0) {
      // No extensions installed, so we're done
      logger.debug("Rebuilding XPI database with no extensions");
      return;
    }

    // If there is no migration data then load the list of add-on directories
    // that were active during the last run
    if (!this.migrateData) {
      this.activeBundles = Array.from(XPIStates.initialEnabledAddons(),
                                      addon => addon.path);
      if (!this.activeBundles.length)
        this.activeBundles = null;
    }


    if (aRebuildOnError) {
      logger.warn("Rebuilding add-ons database from installed extensions.");
      try {
        XPIDatabaseReconcile.processFileChanges({}, false);
      } catch (e) {
        logger.error("Failed to rebuild XPI database from installed extensions", e);
      }
      // Make sure to update the active add-ons and add-ons list on shutdown
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
    }
  },

  /**
   * Shuts down the database connection and releases all cached objects.
   * Return: Promise{integer} resolves / rejects with the result of the DB
   *                          flush after the database is flushed and
   *                          all cleanup is done
   */
  async shutdown() {
    logger.debug("shutdown");
    if (this.initialized) {
      // If our last database I/O had an error, try one last time to save.
      if (this.lastError)
        this.saveChanges();

      this.initialized = false;

      if (this._deferredSave) {
        AddonManagerPrivate.recordSimpleMeasure(
            "XPIDB_saves_total", this._deferredSave.totalSaves);
        AddonManagerPrivate.recordSimpleMeasure(
            "XPIDB_saves_overlapped", this._deferredSave.overlappedSaves);
        AddonManagerPrivate.recordSimpleMeasure(
            "XPIDB_saves_late", this._deferredSave.dirty ? 1 : 0);
      }

      // If we're shutting down while still loading, finish loading
      // before everything else!
      if (this._dbPromise) {
        await this._dbPromise;
      }

      // Await and pending DB writes and finish cleaning up.
      try {
        await this.flush();
      } catch (error) {
        logger.error("Flush of XPI database failed", error);
        AddonManagerPrivate.recordSimpleMeasure("XPIDB_shutdownFlush_failed", 1);
        // If our last attempt to read or write the DB failed, force a new
        // extensions.ini to be written to disk on the next startup
        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);

        throw error;
      }

      // Clear out the cached addons data loaded from JSON
      delete this.addonDB;
      delete this._dbPromise;
      // same for the deferred save
      delete this._deferredSave;
      // re-enable the schema version setter
      delete this._schemaVersionSet;
    }
  },

  /**
   * Asynchronously list all addons that match the filter function
   * @param  aFilter
   *         Function that takes an addon instance and returns
   *         true if that addon should be included in the selected array
   * @param  aCallback
   *         Optional and will be called with an array of addons matching
   *         aFilter or an empty array if none match.
   * @return a Promise that resolves to the list of add-ons matching aFilter or
   *         an empty array if none match
   */
  async getAddonList(aFilter, aCallback) {
    try {
      let addonDB = await this.asyncLoadDB();
      let addonList = _filterDB(addonDB, aFilter);
      let addons = await asyncMap(addonList, getRepositoryAddon);
      if (aCallback) {
        makeSafe(aCallback)(addons);
      }
      return addons;
    } catch (error) {
      logger.error("getAddonList failed", error);
      if (aCallback) {
        makeSafe(aCallback)([]);
      }
      return [];
    }
  },

  /**
   * (Possibly asynchronously) get the first addon that matches the filter function
   * @param  aFilter
   *         Function that takes an addon instance and returns
   *         true if that addon should be selected
   * @param  aCallback
   *         Called back with the addon, or null if no matching addon is found
   */
  getAddon(aFilter, aCallback) {
    return this.asyncLoadDB().then(
      addonDB => {
        getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
      })
    .catch(
        error => {
          logger.error("getAddon failed", error);
          makeSafe(aCallback)(null);
        });
  },

  /**
   * Asynchronously gets an add-on with a particular ID in a particular
   * install location.
   *
   * @param  aId
   *         The ID of the add-on to retrieve
   * @param  aLocation
   *         The name of the install location
   * @param  aCallback
   *         A callback to pass the DBAddonInternal to
   */
  getAddonInLocation(aId, aLocation, aCallback) {
    this.asyncLoadDB().then(
        addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
                                      makeSafe(aCallback)));
  },

  /**
   * Asynchronously get all the add-ons in a particular install location.
   *
   * @param  aLocation
   *         The name of the install location
   * @param  aCallback
   *         A callback to pass the array of DBAddonInternals to
   */
  getAddonsInLocation(aLocation, aCallback) {
    this.getAddonList(aAddon => aAddon._installLocation.name == aLocation, aCallback);
  },

  /**
   * Asynchronously gets the add-on with the specified ID that is visible.
   *
   * @param  aId
   *         The ID of the add-on to retrieve
   * @param  aCallback
   *         A callback to pass the DBAddonInternal to
   */
  getVisibleAddonForID(aId, aCallback) {
    this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible),
                  aCallback);
  },

  /**
   * Asynchronously gets the visible add-ons, optionally restricting by type.
   *
   * @param  aTypes
   *         An array of types to include or null to include all types
   * @param  aCallback
   *         A callback to pass the array of DBAddonInternals to
   */
  getVisibleAddons(aTypes, aCallback) {
    this.getAddonList(aAddon => (aAddon.visible &&
                                 (!aTypes || (aTypes.length == 0) ||
                                  (aTypes.indexOf(aAddon.type) > -1))),
                      aCallback);
  },

  /**
   * Synchronously gets all add-ons of a particular type(s).
   *
   * @param  aType, aType2, ...
   *         The type(s) of add-on to retrieve
   * @return an array of DBAddonInternals
   */
  getAddonsByType(...aTypes) {
    if (!this.addonDB) {
      // jank-tastic! Must synchronously load DB if the theme switches from
      // an XPI theme to a lightweight theme before the DB has loaded,
      // because we're called from sync XPIProvider.addonChanged
      logger.warn(`Synchronous load of XPI database due to ` +
                  `getAddonsByType([${aTypes.join(", ")}]) ` +
                  `Stack: ${Error().stack}`);
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase);
      this.syncLoadDB(true);
    }

    return _filterDB(this.addonDB, aAddon => aTypes.includes(aAddon.type));
  },

  /**
   * Synchronously gets an add-on with a particular internalName.
   *
   * @param  aInternalName
   *         The internalName of the add-on to retrieve
   * @return a DBAddonInternal
   */
  getVisibleAddonForInternalName(aInternalName) {
    if (!this.addonDB) {
      // This may be called when the DB hasn't otherwise been loaded
      logger.warn(`Synchronous load of XPI database due to ` +
                  `getVisibleAddonForInternalName. Stack: ${Error().stack}`);
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_forInternalName",
          XPIProvider.runPhase);
      this.syncLoadDB(true);
    }

    return _findAddon(this.addonDB,
                      aAddon => aAddon.visible &&
                                (aAddon.internalName == aInternalName));
  },

  /**
   * Asynchronously gets all add-ons with pending operations.
   *
   * @param  aTypes
   *         The types of add-ons to retrieve or null to get all types
   * @param  aCallback
   *         A callback to pass the array of DBAddonInternal to
   */
  getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
    this.getAddonList(
        aAddon => (aAddon.visible &&
                   (aAddon.pendingUninstall ||
                    // Logic here is tricky. If we're active but disabled,
                    // we're pending disable; !active && !disabled, we're pending enable
                    (aAddon.active == aAddon.disabled)) &&
                   (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))),
        aCallback);
  },

  /**
   * Asynchronously get an add-on by its Sync GUID.
   *
   * @param  aGUID
   *         Sync GUID of add-on to fetch
   * @param  aCallback
   *         A callback to pass the DBAddonInternal record to. Receives null
   *         if no add-on with that GUID is found.
   *
   */
  getAddonBySyncGUID(aGUID, aCallback) {
    this.getAddon(aAddon => aAddon.syncGUID == aGUID,
                  aCallback);
  },

  /**
   * Synchronously gets all add-ons in the database.
   * This is only called from the preference observer for the default
   * compatibility version preference, so we can return an empty list if
   * we haven't loaded the database yet.
   *
   * @return  an array of DBAddonInternals
   */
  getAddons() {
    if (!this.addonDB) {
      return [];
    }
    return _filterDB(this.addonDB, aAddon => true);
  },

  /**
   * Synchronously adds an AddonInternal's metadata to the database.
   *
   * @param  aAddon
   *         AddonInternal to add
   * @param  aPath
   *         The file path of the add-on
   * @return The DBAddonInternal that was added to the database
   */
  addAddonMetadata(aAddon, aPath) {
    if (!this.addonDB) {
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_addMetadata",
          XPIProvider.runPhase);
      this.syncLoadDB(false);
    }

    let newAddon = new DBAddonInternal(aAddon);
    newAddon.path = aPath;
    this.addonDB.set(newAddon._key, newAddon);
    if (newAddon.visible) {
      this.makeAddonVisible(newAddon);
    }

    this.saveChanges();
    return newAddon;
  },

  /**
   * Synchronously updates an add-on's metadata in the database. Currently just
   * removes and recreates.
   *
   * @param  aOldAddon
   *         The DBAddonInternal to be replaced
   * @param  aNewAddon
   *         The new AddonInternal to add
   * @param  aPath
   *         The file path of the add-on
   * @return The DBAddonInternal that was added to the database
   */
  updateAddonMetadata(aOldAddon, aNewAddon, aPath) {
    this.removeAddonMetadata(aOldAddon);
    aNewAddon.syncGUID = aOldAddon.syncGUID;
    aNewAddon.installDate = aOldAddon.installDate;
    aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
    aNewAddon.foreignInstall = aOldAddon.foreignInstall;
    aNewAddon.seen = aOldAddon.seen;
    aNewAddon.active = (aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall);

    // addAddonMetadata does a saveChanges()
    return this.addAddonMetadata(aNewAddon, aPath);
  },

  /**
   * Synchronously removes an add-on from the database.
   *
   * @param  aAddon
   *         The DBAddonInternal being removed
   */
  removeAddonMetadata(aAddon) {
    this.addonDB.delete(aAddon._key);
    this.saveChanges();
  },

  updateXPIStates(addon) {
    let xpiState = XPIStates.getAddon(addon.location, addon.id);
    if (xpiState) {
      xpiState.syncWithDB(addon);
      XPIStates.save();
    }
  },

  /**
   * Synchronously marks a DBAddonInternal as visible marking all other
   * instances with the same ID as not visible.
   *
   * @param  aAddon
   *         The DBAddonInternal to make visible
   */
  makeAddonVisible(aAddon) {
    logger.debug("Make addon " + aAddon._key + " visible");
    for (let [, otherAddon] of this.addonDB) {
      if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
        logger.debug("Hide addon " + otherAddon._key);
        otherAddon.visible = false;
        otherAddon.active = false;

        this.updateXPIStates(otherAddon);
      }
    }
    aAddon.visible = true;
    this.updateXPIStates(aAddon);
    this.saveChanges();
  },

  /**
   * Synchronously marks a given add-on ID visible in a given location,
   * instances with the same ID as not visible.
   *
   * @param  aAddon
   *         The DBAddonInternal to make visible
   */
  makeAddonLocationVisible(aId, aLocation) {
    logger.debug(`Make addon ${aId} visible in location ${aLocation}`);
    let result;
    for (let [, addon] of this.addonDB) {
      if (addon.id != aId) {
        continue;
      }
      if (addon.location == aLocation) {
        logger.debug("Reveal addon " + addon._key);
        addon.visible = true;
        addon.active = true;
        this.updateXPIStates(addon);
        result = addon;
      } else {
        logger.debug("Hide addon " + addon._key);
        addon.visible = false;
        addon.active = false;
        this.updateXPIStates(addon);
      }
    }
    this.saveChanges();
    return result;
  },

  /**
   * Synchronously sets properties for an add-on.
   *
   * @param  aAddon
   *         The DBAddonInternal being updated
   * @param  aProperties
   *         A dictionary of properties to set
   */
  setAddonProperties(aAddon, aProperties) {
    for (let key in aProperties) {
      aAddon[key] = aProperties[key];
    }
    this.saveChanges();
  },

  /**
   * Synchronously sets the Sync GUID for an add-on.
   * Only called when the database is already loaded.
   *
   * @param  aAddon
   *         The DBAddonInternal being updated
   * @param  aGUID
   *         GUID string to set the value to
   * @throws if another addon already has the specified GUID
   */
  setAddonSyncGUID(aAddon, aGUID) {
    // Need to make sure no other addon has this GUID
    function excludeSyncGUID(otherAddon) {
      return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID);
    }
    let otherAddon = _findAddon(this.addonDB, excludeSyncGUID);
    if (otherAddon) {
      throw new Error("Addon sync GUID conflict for addon " + aAddon._key +
          ": " + otherAddon._key + " already has GUID " + aGUID);
    }
    aAddon.syncGUID = aGUID;
    this.saveChanges();
  },

  /**
   * Synchronously updates an add-on's active flag in the database.
   *
   * @param  aAddon
   *         The DBAddonInternal to update
   */
  updateAddonActive(aAddon, aActive) {
    logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);

    aAddon.active = aActive;
    this.saveChanges();
  },

  updateAddonsBlockingE10s() {
    if (!this.addonDB) {
      // jank-tastic! Must synchronously load DB if the theme switches from
      // an XPI theme to a lightweight theme before the DB has loaded,
      // because we're called from sync XPIProvider.addonChanged
      logger.warn("Synchronous load of XPI database due to updateAddonsBlockingE10s()");
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase);
      this.syncLoadDB(true);
    }

    let blockE10s = false;

    Preferences.set(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
    for (let [, addon] of this.addonDB) {
      let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);

      if (active && XPIProvider.isBlockingE10s(addon)) {
        blockE10s = true;
        break;
      }
    }
    Preferences.set(PREF_E10S_BLOCKED_BY_ADDONS, blockE10s);
  },

  updateAddonsBlockingE10sMulti() {
    let blockMulti = false;

    for (let [, addon] of this.addonDB) {
      let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);

      if (active && XPIProvider.isBlockingE10sMulti(addon)) {
        blockMulti = true;
        break;
      }
    }
    Preferences.set(PREF_E10S_MULTI_BLOCKED_BY_ADDONS, blockMulti);
  },

  /**
   * Synchronously calculates and updates all the active flags in the database.
   */
  updateActiveAddons() {
    if (!this.addonDB) {
      logger.warn("updateActiveAddons called when DB isn't loaded");
      // force the DB to load
      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_updateActive",
          XPIProvider.runPhase);
      this.syncLoadDB(true);
    }
    logger.debug("Updating add-on states");
    for (let [, addon] of this.addonDB) {
      let newActive = (addon.visible && !addon.disabled && !addon.pendingUninstall);
      if (newActive != addon.active) {
        addon.active = newActive;
        this.saveChanges();
      }
    }
  },
};

this.XPIDatabaseReconcile = {
  /**
   * Returns a map of ID -> add-on. When the same add-on ID exists in multiple
   * install locations the highest priority location is chosen.
   */
  flattenByID(addonMap, hideLocation) {
    let map = new Map();

    for (let installLocation of XPIProvider.installLocations) {
      if (installLocation.name == hideLocation)
        continue;

      let locationMap = addonMap.get(installLocation.name);
      if (!locationMap)
        continue;

      for (let [id, addon] of locationMap) {
        if (!map.has(id))
          map.set(id, addon);
      }
    }

    return map;
  },

  /**
   * Finds the visible add-ons from the map.
   */
  getVisibleAddons(addonMap) {
    let map = new Map();

    for (let addons of addonMap.values()) {
      for (let [id, addon] of addons) {
        if (!addon.visible)
          continue;

        if (map.has(id)) {
          logger.warn("Previous database listed more than one visible add-on with id " + id);
          continue;
        }

        map.set(id, addon);
      }
    }

    return map;
  },

  /**
   * Called to add the metadata for an add-on in one of the install locations
   * to the database. This can be called in three different cases. Either an
   * add-on has been dropped into the location from outside of Firefox, or
   * an add-on has been installed through the application, or the database
   * has been upgraded or become corrupt and add-on data has to be reloaded
   * into it.
   *
   * @param  aInstallLocation
   *         The install location containing the add-on
   * @param  aId
   *         The ID of the add-on
   * @param  aAddonState
   *         The new state of the add-on
   * @param  aNewAddon
   *         The manifest for the new add-on if it has already been loaded
   * @param  aOldAppVersion
   *         The version of the application last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aOldPlatformVersion
   *         The version of the platform last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @return a boolean indicating if flushing caches is required to complete
   *         changing this add-on
   */
  addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion,
              aOldPlatformVersion) {
    logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);

    // If we had staged data for this add-on or we aren't recovering from a
    // corrupt database and we don't have migration data for this add-on then
    // this must be a new install.
    let isNewInstall = !!aNewAddon || !XPIDatabase.activeBundles;

    // If it's a new install and we haven't yet loaded the manifest then it
    // must be something dropped directly into the install location
    let isDetectedInstall = isNewInstall && !aNewAddon;

    // Load the manifest if necessary and sanity check the add-on ID
    try {
      if (!aNewAddon) {
        // Load the manifest from the add-on.
        let file = new nsIFile(aAddonState.path);
        aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);
      }
      // The add-on in the manifest should match the add-on ID.
      if (aNewAddon.id != aId) {
        throw new Error("Invalid addon ID: expected addon ID " + aId +
                        ", found " + aNewAddon.id + " in manifest");
      }
    } catch (e) {
      logger.warn("addMetadata: Add-on " + aId + " is invalid", e);

      // Remove the invalid add-on from the install location if the install
      // location isn't locked, no restart will be necessary
      if (aInstallLocation.isLinkedAddon(aId))
        logger.warn("Not uninstalling invalid item because it is a proxy file");
      else if (aInstallLocation.locked)
        logger.warn("Could not uninstall invalid item from locked install location");
      else
        aInstallLocation.uninstallAddon(aId);
      return null;
    }

    // Update the AddonInternal properties.
    aNewAddon.installDate = aAddonState.mtime;
    aNewAddon.updateDate = aAddonState.mtime;

    // Assume that add-ons in the system add-ons install location aren't
    // foreign and should default to enabled.
    aNewAddon.foreignInstall = isDetectedInstall &&
                               aInstallLocation.name != KEY_APP_SYSTEM_ADDONS &&
                               aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS;

    // appDisabled depends on whether the add-on is a foreignInstall so update
    aNewAddon.appDisabled = !isUsableAddon(aNewAddon);

    // The default theme is never a foreign install
    if (aNewAddon.type == "theme" && aNewAddon.internalName == XPIProvider.defaultSkin)
      aNewAddon.foreignInstall = false;

    if (isDetectedInstall && aNewAddon.foreignInstall) {
      // If the add-on is a foreign install and is in a scope where add-ons
      // that were dropped in should default to disabled then disable it
      let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
      if (aInstallLocation.scope & disablingScopes) {
        logger.warn("Disabling foreign installed add-on " + aNewAddon.id + " in "
            + aInstallLocation.name);
        aNewAddon.userDisabled = true;
        aNewAddon.seen = false;
      }
    }

    return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.path);
  },

  /**
   * Called when an add-on has been removed.
   *
   * @param  aOldAddon
   *         The AddonInternal as it appeared the last time the application
   *         ran
   * @return a boolean indicating if flushing caches is required to complete
   *         changing this add-on
   */
  removeMetadata(aOldAddon) {
    // This add-on has disappeared
    logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
    XPIDatabase.removeAddonMetadata(aOldAddon);
  },

  /**
   * Updates an add-on's metadata and determines if a restart of the
   * application is necessary. This is called when either the add-on's
   * install directory path or last modified time has changed.
   *
   * @param  aInstallLocation
   *         The install location containing the add-on
   * @param  aOldAddon
   *         The AddonInternal as it appeared the last time the application
   *         ran
   * @param  aAddonState
   *         The new state of the add-on
   * @param  aNewAddon
   *         The manifest for the new add-on if it has already been loaded
   * @return a boolean indicating if flushing caches is required to complete
   *         changing this add-on
   */
  updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) {
    logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);

    try {
      // If there isn't an updated install manifest for this add-on then load it.
      if (!aNewAddon) {
        let file = new nsIFile(aAddonState.path);
        aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);

        // Carry over any pendingUninstall state to add-ons modified directly
        // in the profile. This is important when the attempt to remove the
        // add-on in processPendingFileChanges failed and caused an mtime
        // change to the add-ons files.
        aNewAddon.pendingUninstall = aOldAddon.pendingUninstall;

        aNewAddon.updateBlocklistState({oldAddon: aOldAddon});
      }

      // The ID in the manifest that was loaded must match the ID of the old
      // add-on.
      if (aNewAddon.id != aOldAddon.id)
        throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
    } catch (e) {
      logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
      XPIDatabase.removeAddonMetadata(aOldAddon);
      XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
      if (!aInstallLocation.locked)
        aInstallLocation.uninstallAddon(aOldAddon.id);
      else
        logger.warn("Could not uninstall invalid item from locked install location");

      return null;
    }

    // Set the additional properties on the new AddonInternal
    aNewAddon.updateDate = aAddonState.mtime;

    // Update the database
    return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.path);
  },

  /**
   * Updates an add-on's path for when the add-on has moved in the
   * filesystem but hasn't changed in any other way.
   *
   * @param  aInstallLocation
   *         The install location containing the add-on
   * @param  aOldAddon
   *         The AddonInternal as it appeared the last time the application
   *         ran
   * @param  aAddonState
   *         The new state of the add-on
   * @return a boolean indicating if flushing caches is required to complete
   *         changing this add-on
   */
  updatePath(aInstallLocation, aOldAddon, aAddonState) {
    logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.path);
    aOldAddon.path = aAddonState.path;
    aOldAddon._sourceBundle = new nsIFile(aAddonState.path);

    return aOldAddon;
  },

  /**
   * Called when no change has been detected for an add-on's metadata but the
   * application has changed so compatibility may have changed.
   *
   * @param  aInstallLocation
   *         The install location containing the add-on
   * @param  aOldAddon
   *         The AddonInternal as it appeared the last time the application
   *         ran
   * @param  aAddonState
   *         The new state of the add-on
   * @param  aOldAppVersion
   *         The version of the application last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aOldPlatformVersion
   *         The version of the platform last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aReloadMetadata
   *         A boolean which indicates whether metadata should be reloaded from
   *         the addon manifests. Default to false.
   * @return the new addon.
   */
  updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aOldAppVersion,
                      aOldPlatformVersion, aReloadMetadata) {
    logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name);

    // If updating from a version of the app that didn't support signedState
    // then fetch that property now
    if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
        SIGNED_TYPES.has(aOldAddon.type)) {
      let file = new nsIFile(aAddonState.path);
      let manifest = syncLoadManifestFromFile(file, aInstallLocation);
      aOldAddon.signedState = manifest.signedState;
    }

    // May be updating from a version of the app that didn't support all the
    // properties of the currently-installed add-ons.
    if (aReloadMetadata) {
      let file = new nsIFile(aAddonState.path);
      let manifest = syncLoadManifestFromFile(file, aInstallLocation);

      // Avoid re-reading these properties from manifest,
      // use existing addon instead.
      // TODO - consider re-scanning for targetApplications.
      let remove = ["syncGUID", "foreignInstall", "visible", "active",
                    "userDisabled", "applyBackgroundUpdates", "sourceURI",
                    "releaseNotesURI", "targetApplications"];

      let props = PROP_JSON_FIELDS.filter(a => !remove.includes(a));
      copyProperties(manifest, props, aOldAddon);
    }

    aOldAddon.updateBlocklistState({updateDatabase: false});
    aOldAddon.appDisabled = !isUsableAddon(aOldAddon);

    return aOldAddon;
  },

  /**
   * Compares the add-ons that are currently installed to those that were
   * known to be installed when the application last ran and applies any
   * changes found to the database. Also sends "startupcache-invalidate" signal to
   * observerservice if it detects that data may have changed.
   * Always called after XPIProviderUtils.js and extensions.json have been loaded.
   *
   * @param  aManifests
   *         A dictionary of cached AddonInstalls for add-ons that have been
   *         installed
   * @param  aUpdateCompatibility
   *         true to update add-ons appDisabled property when the application
   *         version has changed
   * @param  aOldAppVersion
   *         The version of the application last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aOldPlatformVersion
   *         The version of the platform last run with this profile or null
   *         if it is a new profile or the version is unknown
   * @param  aSchemaChange
   *         The schema has changed and all add-on manifests should be re-read.
   * @return a boolean indicating if a change requiring flushing the caches was
   *         detected
   */
  processFileChanges(aManifests, aUpdateCompatibility, aOldAppVersion, aOldPlatformVersion,
                     aSchemaChange) {
    let loadedManifest = (aInstallLocation, aId) => {
      if (!(aInstallLocation.name in aManifests))
        return null;
      if (!(aId in aManifests[aInstallLocation.name]))
        return null;
      return aManifests[aInstallLocation.name][aId];
    };

    // Add-ons loaded from the database can have an uninitialized _sourceBundle
    // if the path was invalid. Swallow that error and say they don't exist.
    let exists = (aAddon) => {
      try {
        return aAddon._sourceBundle.exists();
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_INITIALIZED)
          return false;
        throw e;
      }
    };

    // Get the previous add-ons from the database and put them into maps by location
    let previousAddons = new Map();
    for (let a of XPIDatabase.getAddons()) {
      let locationAddonMap = previousAddons.get(a.location);
      if (!locationAddonMap) {
        locationAddonMap = new Map();
        previousAddons.set(a.location, locationAddonMap);
      }
      locationAddonMap.set(a.id, a);
    }

    // Build the list of current add-ons into similar maps. When add-ons are still
    // present we re-use the add-on objects from the database and update their
    // details directly
    let currentAddons = new Map();
    for (let installLocation of XPIProvider.installLocations) {
      let locationAddonMap = new Map();
      currentAddons.set(installLocation.name, locationAddonMap);

      // Get all the on-disk XPI states for this location, and keep track of which
      // ones we see in the database.
      let states = XPIStates.getLocation(installLocation.name);

      // Iterate through the add-ons installed the last time the application
      // ran
      let dbAddons = previousAddons.get(installLocation.name);
      if (dbAddons) {
        for (let [id, oldAddon] of dbAddons) {
          // Check if the add-on is still installed
          let xpiState = states && states.get(id);
          if (xpiState) {
            // Here the add-on was present in the database and on disk
            recordAddonTelemetry(oldAddon);

            // Check if the add-on has been changed outside the XPI provider
            if (oldAddon.updateDate != xpiState.mtime) {
              // Did time change in the wrong direction?
              if (xpiState.mtime < oldAddon.updateDate) {
                XPIProvider.setTelemetry(oldAddon.id, "olderFile", {
                  mtime: xpiState.mtime,
                  oldtime: oldAddon.updateDate
                });
              }
            }

            let wasDisabled = oldAddon.appDisabled;
            let oldPath = oldAddon.path || descriptorToPath(oldAddon.descriptor);

            // The add-on has changed if the modification time has changed, if
            // we have an updated manifest for it, or if the schema version has
            // changed.
            //
            // Also reload the metadata for add-ons in the application directory
            // when the application version has changed.
            let newAddon = loadedManifest(installLocation, id);
            if (newAddon || oldAddon.updateDate != xpiState.mtime ||
                (aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL ||
                                          installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) {
              newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon);
            } else if (oldPath != xpiState.path) {
              newAddon = this.updatePath(installLocation, oldAddon, xpiState);
            } else if (aUpdateCompatibility || aSchemaChange) {
              // Check compatility when the application version and/or schema
              // version has changed. A schema change also reloads metadata from
              // the manifests.
              newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
                                                  aOldAppVersion, aOldPlatformVersion,
                                                  aSchemaChange);
            } else {
              // No change
              newAddon = oldAddon;
            }

            // If an extension has just become appDisabled and it appears to
            // be due to the ALLOW_NON_MPC pref, show a notification.  If the
            // extension is also disabled for some other reason(s), don't
            // bother with the notification since flipping the pref will leave
            // the extension disabled.
            if (!wasDisabled && newAddon.appDisabled &&
                !ALLOW_NON_MPC && !newAddon.multiprocessCompatible &&
                (newAddon.blocklistState != Ci.nsIBlocklistService.STATE_BLOCKED) &&
                newAddon.isPlatformCompatible && newAddon.isCompatible) {
              AddonManagerPrivate.nonMpcDisabled = true;
            }

            if (newAddon)
              locationAddonMap.set(newAddon.id, newAddon);
          } else {
            // The add-on is in the DB, but not in xpiState (and thus not on disk).
            this.removeMetadata(oldAddon);
          }
        }
      }

      // Any add-on in our current location that we haven't seen needs to
      // be added to the database.
      // Get the migration data for this install location so we can include that as
      // we add, in case this is a database upgrade or rebuild.
      let locMigrateData = {};
      if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
        locMigrateData = XPIDatabase.migrateData[installLocation.name];

      if (states) {
        for (let [id, xpiState] of states) {
          if (locationAddonMap.has(id))
            continue;
          let migrateData = id in locMigrateData ? locMigrateData[id] : null;
          let newAddon = loadedManifest(installLocation, id);
          let addon = this.addMetadata(installLocation, id, xpiState, newAddon,
                                       aOldAppVersion, aOldPlatformVersion, migrateData);
          if (addon)
            locationAddonMap.set(addon.id, addon);
        }
      }
    }

    // previousAddons may contain locations where the database contains add-ons
    // but the browser is no longer configured to use that location. The metadata
    // for those add-ons must be removed from the database.
    for (let [locationName, addons] of previousAddons) {
      if (!currentAddons.has(locationName)) {
        for (let oldAddon of addons.values())
          this.removeMetadata(oldAddon);
      }
    }

    // Validate the updated system add-ons
    let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
    let addons = currentAddons.get(KEY_APP_SYSTEM_ADDONS) || new Map();

    let hideLocation;

    if (!systemAddonLocation.isValid(addons)) {
      // Hide the system add-on updates if any are invalid.
      logger.info("One or more updated system add-ons invalid, falling back to defaults.");
      hideLocation = KEY_APP_SYSTEM_ADDONS;
    }

    let previousVisible = this.getVisibleAddons(previousAddons);
    let currentVisible = this.flattenByID(currentAddons, hideLocation);
    let sawActiveTheme = false;

    // Pass over the new set of visible add-ons, record any changes that occured
    // during startup and call bootstrap install/uninstall scripts as necessary
    for (let [id, currentAddon] of currentVisible) {
      let previousAddon = previousVisible.get(id);

      // Note if any visible add-on is not in the application install location
      if (currentAddon._installLocation.name != KEY_APP_GLOBAL)
        XPIProvider.allAppGlobal = false;

      let isActive = !currentAddon.disabled && !currentAddon.pendingUninstall;
      let wasActive = previousAddon ? previousAddon.active : currentAddon.active

      if (!previousAddon) {
        // If we had a manifest for this add-on it was a staged install and
        // so wasn't something recovered from a corrupt database
        let wasStaged = !!loadedManifest(currentAddon._installLocation, id);

        // We might be recovering from a corrupt database, if so use the
        // list of known active add-ons to update the new add-on
        if (!wasStaged && XPIDatabase.activeBundles) {
          // For themes we know which is active by the current skin setting
          if (currentAddon.type == "theme")
            isActive = currentAddon.internalName == XPIProvider.currentSkin;
          else
            isActive = XPIDatabase.activeBundles.includes(currentAddon.path);

          // If the add-on wasn't active and it isn't already disabled in some way
          // then it was probably either softDisabled or userDisabled
          if (!isActive && !currentAddon.disabled) {
            // If the add-on is softblocked then assume it is softDisabled
            if (currentAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
              currentAddon.softDisabled = true;
            else
              currentAddon.userDisabled = true;
          }
        } else {
          // This is a new install
          if (currentAddon.foreignInstall)
            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);

          if (currentAddon.bootstrap) {
            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);
            // Visible bootstrapped add-ons need to have their install method called
            XPIProvider.callBootstrapMethod(currentAddon, currentAddon._sourceBundle,
                                            "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
            if (!isActive)
              XPIProvider.unloadBootstrapScope(currentAddon.id);
          }
        }
      } else {
        if (previousAddon !== currentAddon) {
          // This is an add-on that has changed, either the metadata was reloaded
          // or the version in a different location has become visible
          AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);

          let installReason = Services.vc.compare(previousAddon.version, currentAddon.version) < 0 ?
                              BOOTSTRAP_REASONS.ADDON_UPGRADE :
                              BOOTSTRAP_REASONS.ADDON_DOWNGRADE;

          // If the previous add-on was in a different path, bootstrapped
          // and still exists then call its uninstall method.
          if (previousAddon.bootstrap && previousAddon._installLocation &&
              exists(previousAddon) &&
              currentAddon._sourceBundle.path != previousAddon._sourceBundle.path) {

            XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
                                            "uninstall", installReason,
                                            { newVersion: currentAddon.version });
            XPIProvider.unloadBootstrapScope(previousAddon.id);
          }

          // Make sure to flush the cache when an old add-on has gone away
          flushChromeCaches();

          if (currentAddon.bootstrap) {
            // Visible bootstrapped add-ons need to have their install method called
            let file = currentAddon._sourceBundle.clone();
            XPIProvider.callBootstrapMethod(currentAddon, file,
                                            "install", installReason,
                                            { oldVersion: previousAddon.version });
            if (currentAddon.disabled)
              XPIProvider.unloadBootstrapScope(currentAddon.id);
          }
        }

        if (isActive != wasActive) {
          let change = isActive ? AddonManager.STARTUP_CHANGE_ENABLED
                                : AddonManager.STARTUP_CHANGE_DISABLED;
          AddonManagerPrivate.addStartupChange(change, id);
        }
      }

      XPIDatabase.makeAddonVisible(currentAddon);
      currentAddon.active = isActive;

      if (currentAddon.active && currentAddon.internalName == XPIProvider.selectedSkin)
        sawActiveTheme = true;
    }

    // Pass over the set of previously visible add-ons that have now gone away
    // and record the change.
    for (let [id, previousAddon] of previousVisible) {
      if (currentVisible.has(id))
        continue;

      // This add-on vanished

      // If the previous add-on was bootstrapped and still exists then call its
      // uninstall method.
      if (previousAddon.bootstrap && exists(previousAddon)) {
        XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
                                        "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
        XPIProvider.unloadBootstrapScope(previousAddon.id);
      }
      AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
      XPIStates.removeAddon(previousAddon.location, id);

      // Make sure to flush the cache when an old add-on has gone away
      flushChromeCaches();
    }

    // Make sure add-ons from hidden locations are marked invisible and inactive
    let locationAddonMap = currentAddons.get(hideLocation);
    if (locationAddonMap) {
      for (let addon of locationAddonMap.values()) {
        addon.visible = false;
        addon.active = false;
      }
    }

    // If a custom theme is selected and it wasn't seen in the new list of
    // active add-ons then enable the default theme
    if (XPIProvider.selectedSkin != XPIProvider.defaultSkin && !sawActiveTheme) {
      logger.info("Didn't see selected skin " + XPIProvider.selectedSkin);
      XPIProvider.enableDefaultTheme();
    }

    // Finally update XPIStates to match everything
    for (let [locationName, locationAddonMap] of currentAddons) {
      for (let [id, addon] of locationAddonMap) {
        let xpiState = XPIStates.getAddon(locationName, id);
        xpiState.syncWithDB(addon);
      }
    }
    XPIStates.save();

    // Clear out any cached migration data.
    XPIDatabase.migrateData = null;
    XPIDatabase.saveChanges();

    return true;
  },
}
PK
!<<=

 modules/commonjs/dev/debuggee.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { MessagePort, MessageChannel } = require("../sdk/messaging");
const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});

const outputs = new WeakMap();
const inputs = new WeakMap();
const targets = new WeakMap();
const transports = new WeakMap();

const inputFor = port => inputs.get(port);
const outputFor = port => outputs.get(port);
const transportFor = port => transports.get(port);

const fromTarget = target => {
  const debuggee = new Debuggee();
  const { port1, port2 } = new MessageChannel();
  inputs.set(debuggee, port1);
  outputs.set(debuggee, port2);
  targets.set(debuggee, target);

  return debuggee;
};
exports.fromTarget = fromTarget;

const Debuggee = Class({
  extends: MessagePort.prototype,
  close: function() {
    const server = transportFor(this);
    if (server) {
      transports.delete(this);
      server.close();
    }
    outputFor(this).close();
  },
  start: function() {
    const target = targets.get(this);
    if (target.isLocalTab) {
      // Since a remote protocol connection will be made, let's start the
      // DebuggerServer here, once and for all tools.
      let transport = DevToolsShim.connectDebuggerServer();
      transports.set(this, transport);
    }
    // TODO: Implement support for remote connections (See Bug 980421)
    else {
      throw Error("Remote targets are not yet supported");
    }

    // pipe messages send to the debuggee to an actual
    // server via remote debugging protocol transport.
    inputFor(this).addEventListener("message", ({data}) =>
      transportFor(this).send(data));

    // pipe messages received from the remote debugging
    // server transport onto the this debuggee.
    transportFor(this).hooks = {
      onPacket: packet => inputFor(this).postMessage(packet),
      onClosed: () => inputFor(this).close()
    };

    inputFor(this).start();
    outputFor(this).start();
  },
  postMessage: function(data) {
    return outputFor(this).postMessage(data);
  },
  get onmessage() {
    return outputFor(this).onmessage;
  },
  set onmessage(onmessage) {
    outputFor(this).onmessage = onmessage;
  },
  addEventListener: function(...args) {
    return outputFor(this).addEventListener(...args);
  },
  removeEventListener: function(...args) {
    return outputFor(this).removeEventListener(...args);
  }
});
exports.Debuggee = Debuggee;
PK
!<a$modules/commonjs/dev/frame-script.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
(function({content, sendSyncMessage, addMessageListener, sendAsyncMessage}) {

const Cc = Components.classes;
const Ci = Components.interfaces;
const observerService = Cc["@mozilla.org/observer-service;1"]
                        .getService(Ci.nsIObserverService);

const channels = new Map();
const handles = new WeakMap();

// Takes remote port handle and creates a local one.
// also set's up a messaging channel between them.
// This is temporary workaround until Bug 914974 is fixed
// and port can be transfered through message manager.
const demarshal = (handle) => {
  if (handle.type === "MessagePort") {
    if (!channels.has(handle.id)) {
      const channel = new content.MessageChannel();
      channels.set(handle.id, channel);
      handles.set(channel.port1, handle);
      channel.port1.onmessage = onOutPort;
    }
    return channels.get(handle.id).port2;
  }
  return null;
};

const onOutPort = event => {
  const handle = handles.get(event.target);
  sendAsyncMessage("sdk/port/message", {
    port: handle,
    message: event.data
  });
};

const onInPort = ({data}) => {
  const channel = channels.get(data.port.id);
  if (channel)
    channel.port1.postMessage(data.message);
};

const onOutEvent = event =>
  sendSyncMessage("sdk/event/" + event.type,
                  { type: event.type,
                    data: event.data });

const onInMessage = (message) => {
  const {type, data, origin, bubbles, cancelable, ports} = message.data;

  const event = new content.MessageEvent(type, {
    bubbles: bubbles,
    cancelable: cancelable,
    data: data,
    origin: origin,
    target: content,
    source: content,
    ports: ports.map(demarshal)
  });
  content.dispatchEvent(event);
};

const onReady = event => {
  channels.clear();
};

addMessageListener("sdk/event/message", onInMessage);
addMessageListener("sdk/port/message", onInPort);

const observer = {
  handleEvent: ({target, type}) => {
    observer.observe(target, type);
  },
  observe: (document, topic, data) => {
    // When frame associated with message manager is removed from document `docShell`
    // is set to `null` but observer is still kept alive. At this point accesing
    // `content.document` throws "can't access dead object" exceptions. In order to
    // avoid leaking observer and logged errors observer is going to be removed when
    // `docShell` is set to `null`.
    if (!docShell) {
      observerService.removeObserver(observer, topic);
    }
    else if (document === content.document) {
      if (topic.endsWith("-document-interactive")) {
        sendAsyncMessage("sdk/event/ready", {
          type: "ready",
          readyState: document.readyState,
          uri: document.documentURI
        });
      }
      if (topic.endsWith("-document-loaded")) {
        sendAsyncMessage("sdk/event/load", {
          type: "load",
          readyState: document.readyState,
          uri: document.documentURI
        });
      }
      if (topic === "unload") {
        channels.clear();
        sendAsyncMessage("sdk/event/unload", {
          type: "unload",
          readyState: "uninitialized",
          uri: document.documentURI
        });
      }
    }
  }
};

observerService.addObserver(observer, "content-document-interactive");
observerService.addObserver(observer, "content-document-loaded");
observerService.addObserver(observer, "chrome-document-interactive");
observerService.addObserver(observer, "chrome-document-loaded");
addEventListener("unload", observer, false);

})(this);
PK
!<5Ԙ"modules/commonjs/dev/panel/view.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { method } = require("method/core");

const createView = method("dev/panel/view#createView");
exports.createView = createView;
PK
!< modules/commonjs/dev/panel.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cu } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { curry } = require("../sdk/lang/functional");
const { EventTarget } = require("../sdk/event/target");
const { Disposable, setup, dispose } = require("../sdk/core/disposable");
const { emit, off, setListeners } = require("../sdk/event/core");
const { when } = require("../sdk/event/utils");
const { getFrameElement } = require("../sdk/window/utils");
const { contract, validate } = require("../sdk/util/contract");
const { data: { url: resolve }} = require("../sdk/self");
const { identify } = require("../sdk/ui/id");
const { isLocalURL, URL } = require("../sdk/url");
const { encode } = require("../sdk/base64");
const { marshal, demarshal } = require("./ports");
const { fromTarget } = require("./debuggee");
const { removed } = require("../sdk/dom/events");
const { id: addonID } = require("../sdk/self");
const { viewFor } = require("../sdk/view/core");
const { createView } = require("./panel/view");

const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
const FRAME_SCRIPT = module.uri.replace("/panel.js", "/frame-script.js");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const HTML_NS = "http://www.w3.org/1999/xhtml";

const makeID = name =>
  ("dev-panel-" + addonID + "-" + name).
  split("/").join("-").
  split(".").join("-").
  split(" ").join("-").
  replace(/[^A-Za-z0-9_\-]/g, "");


// Weak mapping between `Panel` instances and their frame's
// `nsIMessageManager`.
const managers = new WeakMap();
// Return `nsIMessageManager` for the given `Panel` instance.
const managerFor = x => managers.get(x);

// Weak mappinging between iframe's and their owner
// `Panel` instances.
const panels = new WeakMap();
const panelFor = frame => panels.get(frame);

// Weak mapping between panels and debugees they're targeting.
const debuggees = new WeakMap();
const debuggeeFor = panel => debuggees.get(panel);

const frames = new WeakMap();
const frameFor = panel => frames.get(panel);

const setAttributes = (node, attributes) => {
  for (var key in attributes)
    node.setAttribute(key, attributes[key]);
};

const onStateChange = ({target, data}) => {
  const panel = panelFor(target);
  panel.readyState = data.readyState;
  emit(panel, data.type, { target: panel, type: data.type });
};

// port event listener on the message manager that demarshalls
// and forwards to the actual receiver. This is a workaround
// until Bug 914974 is fixed.
const onPortMessage = ({data, target}) => {
  const port = demarshal(target, data.port);
  if (port)
    port.postMessage(data.message);
};

// When frame is removed from the toolbox destroy panel
// associated with it to release all the resources.
const onFrameRemove = frame => {
  panelFor(frame).destroy();
};

const onFrameInited = frame => {
  frame.style.visibility = "visible";
}

const inited = frame => new Promise(resolve => {
  const { messageManager } = frame.frameLoader;
  const listener = message => {
    messageManager.removeMessageListener("sdk/event/ready", listener);
    resolve(frame);
  };
  messageManager.addMessageListener("sdk/event/ready", listener);
});

const getTarget = ({target}) => target;

const Panel = Class({
  extends: Disposable,
  implements: [EventTarget],
  get id() {
    return makeID(this.name || this.label);
  },
  readyState: "uninitialized",
  ready: function() {
    const { readyState } = this;
    const isReady = readyState === "complete" ||
                    readyState === "interactive";
    return isReady ? Promise.resolve(this) :
           when(this, "ready").then(getTarget);
  },
  loaded: function() {
    const { readyState } = this;
    const isLoaded = readyState === "complete";
    return isLoaded ? Promise.resolve(this) :
           when(this, "load").then(getTarget);
  },
  unloaded: function() {
    const { readyState } = this;
    const isUninitialized = readyState === "uninitialized";
    return isUninitialized ? Promise.resolve(this) :
           when(this, "unload").then(getTarget);
  },
  postMessage: function(data, ports=[]) {
    const manager = managerFor(this);
    manager.sendAsyncMessage("sdk/event/message", {
      type: "message",
      bubbles: false,
      cancelable: false,
      data: data,
      origin: this.url,
      ports: ports.map(marshal(manager))
    });
  }
});
exports.Panel = Panel;

validate.define(Panel, contract({
  label: {
    is: ["string"],
    msg: "The `option.label` must be a provided"
  },
  tooltip: {
    is: ["string", "undefined"],
    msg: "The `option.tooltip` must be a string"
  },
  icon: {
    is: ["string"],
    map: x => x && resolve(x),
    ok: x => isLocalURL(x),
    msg: "The `options.icon` must be a valid local URI."
  },
  url: {
    map: x => resolve(x.toString()),
    is: ["string"],
    ok: x => isLocalURL(x),
    msg: "The `options.url` must be a valid local URI."
  },
  invertIconForLightTheme: {
    is: ["boolean", "undefined"],
    msg: "The `options.invertIconForLightTheme` must be a boolean."
  },
  invertIconForDarkTheme: {
    is: ["boolean", "undefined"],
    msg: "The `options.invertIconForDarkTheme` must be a boolean."
  }
}));

setup.define(Panel, (panel, {window, toolbox, url}) => {
  // Hack: Given that iframe created by devtools API is no good for us,
  // we obtain original iframe and replace it with the one that has
  // desired configuration.
  const original = getFrameElement(window);
  const container = original.parentNode;
  original.remove();
  const frame = createView(panel, container.ownerDocument);

  // Following modifications are a temporary workaround until Bug 1049188
  // is fixed.
  // Enforce certain iframe customizations regardless of users request.
  setAttributes(frame, {
    "id": original.id,
    "src": url,
    "flex": 1,
    "forceOwnRefreshDriver": "",
    "tooltip": "aHTMLTooltip"
  });
  frame.style.visibility = "hidden";
  frame.classList.add("toolbox-panel-iframe");
  // Inject iframe into designated node until add-on author decides
  // to inject it elsewhere instead.
  if (!frame.parentNode)
    container.appendChild(frame);

  // associate view with a panel
  frames.set(panel, frame);

  // associate panel model with a frame view.
  panels.set(frame, panel);

  const debuggee = fromTarget(toolbox.target);
  // associate debuggee with a panel.
  debuggees.set(panel, debuggee);


  // Setup listeners for the frame message manager.
  const { messageManager } = frame.frameLoader;
  messageManager.addMessageListener("sdk/event/ready", onStateChange);
  messageManager.addMessageListener("sdk/event/load", onStateChange);
  messageManager.addMessageListener("sdk/event/unload", onStateChange);
  messageManager.addMessageListener("sdk/port/message", onPortMessage);
  messageManager.loadFrameScript(FRAME_SCRIPT, false);

  managers.set(panel, messageManager);

  // destroy panel if frame is removed.
  removed(frame).then(onFrameRemove);
  // show frame when it is initialized.
  inited(frame).then(onFrameInited);


  // set listeners if there are ones defined on the prototype.
  setListeners(panel, Object.getPrototypeOf(panel));


  panel.setup({ debuggee: debuggee });
});

createView.define(Panel, (panel, document) => {
  const frame = document.createElement("iframe");
  setAttributes(frame, {
    "sandbox": "allow-scripts",
    // We end up using chrome iframe with forced message manager
    // as fixing a swapFrameLoader seemed like a giant task (see
    // Bug 1075490).
    "type": "chrome",
    "forcemessagemanager": true,
    "transparent": true,
    "seamless": "seamless",
  });
  return frame;
});

dispose.define(Panel, function(panel) {
  debuggeeFor(panel).close();

  debuggees.delete(panel);
  managers.delete(panel);
  frames.delete(panel);
  panel.readyState = "destroyed";
  panel.dispose();
});

viewFor.define(Panel, frameFor);
PK
!<=smodules/commonjs/dev/ports.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

// This module provides `marshal` and `demarshal` functions
// that can be used to send  MessagePort's over `nsIFrameMessageManager`
// until Bug 914974 is fixed.

const { add, iterator } = require("../sdk/lang/weak-set");
const { curry } = require("../sdk/lang/functional");

var id = 0;
const ports = new WeakMap();

// Takes `nsIFrameMessageManager` and `MessagePort` instances
// and returns a handle representing given `port`. Messages
// received on given `port` will be forwarded to a message
// manager under `sdk/port/message` and messages like:
// { port: { type: "MessagePort", id: 2}, data: data }
// Where id is an identifier associated with a given `port`
// and `data` is an `event.data` received on port.
const marshal = curry((manager, port) => {
  if (!ports.has(port)) {
    id = id + 1;
    const handle = {type: "MessagePort", id: id};
    // Bind id to the given port
    ports.set(port, handle);

    // Obtain a weak reference to a port.
    add(exports, port);

    port.onmessage = event => {
      manager.sendAsyncMessage("sdk/port/message", {
        port: handle,
        message: event.data
      });
    };

    return handle;
  }
  return ports.get(port);
});
exports.marshal = marshal;

// Takes `nsIFrameMessageManager` instance and a handle returned
// `marshal(manager, port)` returning a `port` that was passed
// to it. Note that `port` may be GC-ed in which case returned
// value will be `null`.
const demarshal = curry((manager, {type, id}) => {
  if (type === "MessagePort") {
    for (let port of iterator(exports)) {
      if (id === ports.get(port).id)
        return port;
    }
  }
  return null;
});
exports.demarshal = demarshal;
PK
!<#modules/commonjs/dev/theme/hooks.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { method } = require("method/core");

const onEnable = method("dev/theme/hooks#onEnable");
const onDisable = method("dev/theme/hooks#onDisable");

exports.onEnable = onEnable;
exports.onDisable = onDisable;
PK
!<c3xmodules/commonjs/dev/theme.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Class } = require("../sdk/core/heritage");
const { EventTarget } = require("../sdk/event/target");
const { Disposable, setup, dispose } = require("../sdk/core/disposable");
const { contract, validate } = require("../sdk/util/contract");
const { id: addonID } = require("../sdk/self");
const { onEnable, onDisable } = require("dev/theme/hooks");
const { isString, instanceOf, isFunction } = require("sdk/lang/type");
const { add } = require("sdk/util/array");
const { data } = require("../sdk/self");
const { isLocalURL } = require("../sdk/url");

const makeID = name =>
  ("dev-theme-" + addonID + (name ? "-" + name : "")).
  split(/[ . /]/).join("-").
  replace(/[^A-Za-z0-9_\-]/g, "");

const Theme = Class({
  extends: Disposable,
  implements: [EventTarget],

  initialize: function(options) {
    this.name = options.name;
    this.label = options.label;
    this.styles = options.styles;

    // Event handlers
    this.onEnable = options.onEnable;
    this.onDisable = options.onDisable;
  },
  get id() {
    return makeID(this.name || this.label);
  },
  setup: function() {
    // Any initialization steps done at the registration time.
  },
  getStyles: function() {
    if (!this.styles) {
      return [];
    }

    if (isString(this.styles)) {
      if (isLocalURL(this.styles)) {
        return [data.url(this.styles)];
      }
    }

    let result = [];
    for (let style of this.styles) {
      if (isString(style)) {
        if (isLocalURL(style)) {
          style = data.url(style);
        }
        add(result, style);
      } else if (instanceOf(style, Theme)) {
        result = result.concat(style.getStyles());
      }
    }
    return result;
  },
  getClassList: function() {
    let result = [];
    for (let style of this.styles) {
      if (instanceOf(style, Theme)) {
        result = result.concat(style.getClassList());
      }
    }

    if (this.name) {
      add(result, this.name);
    }

    return result;
  }
});

exports.Theme = Theme;

// Initialization & dispose

setup.define(Theme, (theme) => {
  theme.classList = [];
  theme.setup();
});

dispose.define(Theme, function(theme) {
  theme.dispose();
});

// Validation

validate.define(Theme, contract({
  label: {
    is: ["string"],
    msg: "The `option.label` must be a provided"
  },
}));

// Support theme events: apply and unapply the theme.

onEnable.define(Theme, (theme, {window, oldTheme}) => {
  if (isFunction(theme.onEnable)) {
    theme.onEnable(window, oldTheme);
  }
});

onDisable.define(Theme, (theme, {window, newTheme}) => {
  if (isFunction(theme.onDisable)) {
    theme.onDisable(window, newTheme);
  }
});

// Support for built-in themes

const LightTheme = Theme({
  name: "theme-light",
  styles: "chrome://devtools/skin/light-theme.css",
});

const DarkTheme = Theme({
  name: "theme-dark",
  styles: "chrome://devtools/skin/dark-theme.css",
});

exports.LightTheme = LightTheme;
exports.DarkTheme = DarkTheme;
PK
!<")
)
modules/commonjs/dev/toolbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cu, Cc, Ci } = require("chrome");
const { Class } = require("../sdk/core/heritage");
const { Disposable, setup } = require("../sdk/core/disposable");
const { contract, validate } = require("../sdk/util/contract");
const { each, pairs, values } = require("../sdk/util/sequence");
const { onEnable, onDisable } = require("../dev/theme/hooks");

const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});

// This is temporary workaround to allow loading of the developer tools client - volcan
// into a toolbox panel, this hack won't be necessary as soon as devtools patch will be
// shipped in nightly, after which it can be removed. Bug 1038517
const registerSDKURI = () => {
  const ioService = Cc['@mozilla.org/network/io-service;1']
                      .getService(Ci.nsIIOService);
  const resourceHandler = ioService.getProtocolHandler("resource")
                                   .QueryInterface(Ci.nsIResProtocolHandler);

  const uri = module.uri.replace("dev/toolbox.js", "");
  resourceHandler.setSubstitution("sdk", ioService.newURI(uri));
};

registerSDKURI();

const Tool = Class({
  extends: Disposable,
  setup: function(params={}) {
    const { panels } = validate(this, params);
    const { themes } = validate(this, params);

    this.panels = panels;
    this.themes = themes;

    each(([key, Panel]) => {
      const { url, label, tooltip, icon, invertIconForLightTheme,
              invertIconForDarkTheme } = validate(Panel.prototype);
      const { id } = Panel.prototype;

      DevToolsShim.registerTool({
        id: id,
        url: "about:blank",
        label: label,
        tooltip: tooltip,
        icon: icon,
        invertIconForLightTheme: invertIconForLightTheme,
        invertIconForDarkTheme: invertIconForDarkTheme,
        isTargetSupported: target => target.isLocalTab,
        build: (window, toolbox) => {
          const panel = new Panel();
          setup(panel, { window: window,
                         toolbox: toolbox,
                         url: url });

          return panel.ready();
        }
      });
    }, pairs(panels));

    each(([key, theme]) => {
      validate(theme);
      setup(theme);

      DevToolsShim.registerTheme({
        id: theme.id,
        label: theme.label,
        stylesheets: theme.getStyles(),
        classList: theme.getClassList(),
        onApply: (window, oldTheme) => {
          onEnable(theme, { window: window,
                            oldTheme: oldTheme });
        },
        onUnapply: (window, newTheme) => {
          onDisable(theme, { window: window,
                            newTheme: newTheme });
        }
      });
    }, pairs(themes));
  },
  dispose: function() {
    each(Panel => DevToolsShim.unregisterTool(Panel.prototype.id),
         values(this.panels));

    each(Theme => DevToolsShim.unregisterTheme(Theme.prototype.id),
         values(this.themes));
  }
});

validate.define(Tool, contract({
  panels: {
    is: ["object", "undefined"]
  },
  themes: {
    is: ["object", "undefined"]
  }
}));

exports.Tool = Tool;
PK
!<4;;modules/commonjs/dev/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { Cu } = require("chrome");
const { DevToolsShim } = Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});

const { getActiveTab } = require("../sdk/tabs/utils");
const { getMostRecentBrowserWindow } = require("../sdk/window/utils");

const targetFor = target => {
  target = target || getActiveTab(getMostRecentBrowserWindow());
  return DevToolsShim.getTargetForTab(target);
};

const getId = id => ((id.prototype && id.prototype.id) || id.id || id);

const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
exports.getCurrentPanel = getCurrentPanel;

const openToolbox = (id, tab) => {
  id = getId(id);
  return DevToolsShim.showToolbox(targetFor(tab), id);
};
exports.openToolbox = openToolbox;

const closeToolbox = tab => DevToolsShim.closeToolbox(targetFor(tab));
exports.closeToolbox = closeToolbox;

const getToolbox = tab => DevToolsShim.getToolbox(targetFor(tab));
exports.getToolbox = getToolbox;

const openToolboxPanel = (id, tab) => {
  id = getId(id);
  return DevToolsShim.showToolbox(targetFor(tab), id).then(getCurrentPanel);
};
exports.openToolboxPanel = openToolboxPanel;
PK
!<km11modules/commonjs/dev/volcan.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
"use strict";

var Client = _dereq_("../client").Client;

function connect(port) {
  var client = new Client();
  return client.connect(port);
}
exports.connect = connect;

},{"../client":4}],2:[function(_dereq_,module,exports){
"use strict";

exports.Promise = Promise;

},{}],3:[function(_dereq_,module,exports){
"use strict";

var describe = Object.getOwnPropertyDescriptor;
var Class = function(fields) {
  var names = Object.keys(fields);
  var constructor = names.indexOf("constructor") >= 0 ? fields.constructor :
                    function() {};
  var ancestor = fields.extends || Object;

  var descriptor = names.reduce(function(descriptor, key) {
    descriptor[key] = describe(fields, key);
    return descriptor;
  }, {});

  var prototype = Object.create(ancestor.prototype, descriptor);

  constructor.prototype = prototype;
  prototype.constructor = constructor;

  return constructor;
};
exports.Class = Class;

},{}],4:[function(_dereq_,module,exports){
"use strict";

var Class = _dereq_("./class").Class;
var TypeSystem = _dereq_("./type-system").TypeSystem;
var values = _dereq_("./util").values;
var Promise = _dereq_("es6-promise").Promise;
var MessageEvent = _dereq_("./event").MessageEvent;

var specification = _dereq_("./specification/core.json");

function recoverActorDescriptions(error) {
  console.warn("Failed to fetch protocol specification (see reason below). " +
               "Using a fallback protocal specification!",
               error);
  return _dereq_("./specification/protocol.json");
}

// Type to represent superviser actor relations to actors they supervise
// in terms of lifetime management.
var Supervisor = Class({
  constructor: function(id) {
    this.id = id;
    this.workers = [];
  }
});

var Telemetry = Class({
  add: function(id, ms) {
    console.log("telemetry::", id, ms)
  }
});

// Consider making client a root actor.

var Client = Class({
  constructor: function() {
    this.root = null;
    this.telemetry = new Telemetry();

    this.setupConnection();
    this.setupLifeManagement();
    this.setupTypeSystem();
  },

  setupConnection: function() {
    this.requests = [];
  },
  setupLifeManagement: function() {
    this.cache = Object.create(null);
    this.graph = Object.create(null);
    this.get = this.get.bind(this);
    this.release = this.release.bind(this);
  },
  setupTypeSystem: function() {
    this.typeSystem = new TypeSystem(this);
    this.typeSystem.registerTypes(specification);
  },

  connect: function(port) {
    var client = this;
    return new Promise(function(resolve, reject) {
      client.port = port;
      port.onmessage = client.receive.bind(client);
      client.onReady = resolve;
      client.onFail = reject;

      port.start();
    });
  },
  send: function(packet) {
    this.port.postMessage(packet);
  },
  request: function(packet) {
    var client = this;
    return new Promise(function(resolve, reject) {
      client.requests.push(packet.to, { resolve: resolve, reject: reject });
      client.send(packet);
    });
  },

  receive: function(event) {
    var packet = event.data;
    if (!this.root) {
      if (packet.from !== "root")
        throw Error("Initial packet must be from root");
      if (!("applicationType" in packet))
        throw Error("Initial packet must contain applicationType field");

      this.root = this.typeSystem.read("root", null, "root");
      this.root
          .protocolDescription()
          .catch(recoverActorDescriptions)
          .then(this.typeSystem.registerTypes.bind(this.typeSystem))
          .then(this.onReady.bind(this, this.root), this.onFail);
    } else {
      var actor = this.get(packet.from) || this.root;
      var event = actor.events[packet.type];
      if (event) {
        var message = new MessageEvent(packet.type, {
          data: event.read(packet)
        });
        actor.dispatchEvent(message);
      } else {
        var index = this.requests.indexOf(actor.id);
        if (index >= 0) {
          var request = this.requests.splice(index, 2).pop();
          if (packet.error)
            request.reject(packet);
          else
            request.resolve(packet);
        } else {
          console.error(Error("Unexpected packet " + JSON.stringify(packet, 2, 2)),
                        packet,
                        this.requests.slice(0));
        }
      }
    }
  },

  get: function(id) {
    return this.cache[id];
  },
  supervisorOf: function(actor) {
    for (var id in this.graph) {
      if (this.graph[id].indexOf(actor.id) >= 0) {
        return id;
      }
    }
  },
  workersOf: function(actor) {
    return this.graph[actor.id];
  },
  supervise: function(actor, worker) {
    var workers = this.workersOf(actor)
    if (workers.indexOf(worker.id) < 0) {
      workers.push(worker.id);
    }
  },
  unsupervise: function(actor, worker) {
    var workers = this.workersOf(actor);
    var index = workers.indexOf(worker.id)
    if (index >= 0) {
      workers.splice(index, 1)
    }
  },

  register: function(actor) {
    var registered = this.get(actor.id);
    if (!registered) {
      this.cache[actor.id] = actor;
      this.graph[actor.id] = [];
    } else if (registered !== actor) {
      throw new Error("Different actor with same id is already registered");
    }
  },
  unregister: function(actor) {
    if (this.get(actor.id)) {
      delete this.cache[actor.id];
      delete this.graph[actor.id];
    }
  },

  release: function(actor) {
    var supervisor = this.supervisorOf(actor);
    if (supervisor)
      this.unsupervise(supervisor, actor);

    var workers = this.workersOf(actor)

    if (workers) {
      workers.map(this.get).forEach(this.release)
    }
    this.unregister(actor);
  }
});
exports.Client = Client;

},{"./class":3,"./event":5,"./specification/core.json":23,"./specification/protocol.json":24,"./type-system":25,"./util":26,"es6-promise":2}],5:[function(_dereq_,module,exports){
"use strict";

var Symbol = _dereq_("es6-symbol")
var EventEmitter = _dereq_("events").EventEmitter;
var Class = _dereq_("./class").Class;

var $bound = Symbol("EventTarget/handleEvent");
var $emitter = Symbol("EventTarget/emitter");

function makeHandler(handler) {
  return function(event) {
    handler.handleEvent(event);
  }
}

var EventTarget = Class({
  constructor: function() {
    Object.defineProperty(this, $emitter, {
      enumerable: false,
      configurable: true,
      writable: true,
      value: new EventEmitter()
    });
  },
  addEventListener: function(type, handler) {
    if (typeof(handler) === "function") {
      this[$emitter].on(type, handler);
    }
    else if (handler && typeof(handler) === "object") {
      if (!handler[$bound]) handler[$bound] = makeHandler(handler);
      this[$emitter].on(type, handler[$bound]);
    }
  },
  removeEventListener: function(type, handler) {
    if (typeof(handler) === "function")
      this[$emitter].removeListener(type, handler);
    else if (handler && handler[$bound])
      this[$emitter].removeListener(type, handler[$bound]);
  },
  dispatchEvent: function(event) {
    event.target = this;
    this[$emitter].emit(event.type, event);
  }
});
exports.EventTarget = EventTarget;

var MessageEvent = Class({
  constructor: function(type, options) {
    options = options || {};
    this.type = type;
    this.data = options.data === void(0) ? null : options.data;

    this.lastEventId = options.lastEventId || "";
    this.origin = options.origin || "";
    this.bubbles = options.bubbles || false;
    this.cancelable = options.cancelable || false;
  },
  source: null,
  ports: null,
  preventDefault: function() {
  },
  stopPropagation: function() {
  },
  stopImmediatePropagation: function() {
  }
});
exports.MessageEvent = MessageEvent;

},{"./class":3,"es6-symbol":7,"events":6}],6:[function(_dereq_,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

function EventEmitter() {
  this._events = this._events || {};
  this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
  if (!isNumber(n) || n < 0 || isNaN(n))
    throw TypeError('n must be a positive number');
  this._maxListeners = n;
  return this;
};

EventEmitter.prototype.emit = function(type) {
  var er, handler, len, args, i, listeners;

  if (!this._events)
    this._events = {};

  // If there is no 'error' event listener then throw.
  if (type === 'error') {
    if (!this._events.error ||
        (isObject(this._events.error) && !this._events.error.length)) {
      er = arguments[1];
      if (er instanceof Error) {
        throw er; // Unhandled 'error' event
      } else {
        throw TypeError('Uncaught, unspecified "error" event.');
      }
      return false;
    }
  }

  handler = this._events[type];

  if (isUndefined(handler))
    return false;

  if (isFunction(handler)) {
    switch (arguments.length) {
      // fast cases
      case 1:
        handler.call(this);
        break;
      case 2:
        handler.call(this, arguments[1]);
        break;
      case 3:
        handler.call(this, arguments[1], arguments[2]);
        break;
      // slower
      default:
        len = arguments.length;
        args = new Array(len - 1);
        for (i = 1; i < len; i++)
          args[i - 1] = arguments[i];
        handler.apply(this, args);
    }
  } else if (isObject(handler)) {
    len = arguments.length;
    args = new Array(len - 1);
    for (i = 1; i < len; i++)
      args[i - 1] = arguments[i];

    listeners = handler.slice();
    len = listeners.length;
    for (i = 0; i < len; i++)
      listeners[i].apply(this, args);
  }

  return true;
};

EventEmitter.prototype.addListener = function(type, listener) {
  var m;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  if (this._events.newListener)
    this.emit('newListener', type,
              isFunction(listener.listener) ?
              listener.listener : listener);

  if (!this._events[type])
    // Optimize the case of one listener. Don't need the extra array object.
    this._events[type] = listener;
  else if (isObject(this._events[type]))
    // If we've already got an array, just append.
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  if (isObject(this._events[type]) && !this._events[type].warned) {
    var m;
    if (!isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }

    if (m && m > 0 && this._events[type].length > m) {
      this._events[type].warned = true;
      console.error('(node) warning: possible EventEmitter memory ' +
                    'leak detected. %d listeners added. ' +
                    'Use emitter.setMaxListeners() to increase limit.',
                    this._events[type].length);
      if (typeof console.trace === 'function') {
        // not supported in IE 10
        console.trace();
      }
    }
  }

  return this;
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.once = function(type, listener) {
  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  var fired = false;

  function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);

  return this;
};

// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
  var list, position, length, i;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events || !this._events[type])
    return this;

  list = this._events[type];
  length = list.length;
  position = -1;

  if (list === listener ||
      (isFunction(list.listener) && list.listener === listener)) {
    delete this._events[type];
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);

  } else if (isObject(list)) {
    for (i = length; i-- > 0;) {
      if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
        position = i;
        break;
      }
    }

    if (position < 0)
      return this;

    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
      list.splice(position, 1);
    }

    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};

EventEmitter.prototype.removeAllListeners = function(type) {
  var key, listeners;

  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  if (!this._events.removeListener) {
    if (arguments.length === 0)
      this._events = {};
    else if (this._events[type])
      delete this._events[type];
    return this;
  }

  // emit removeListener for all listeners on all events
  if (arguments.length === 0) {
    for (key in this._events) {
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }

  listeners = this._events[type];

  if (isFunction(listeners)) {
    this.removeListener(type, listeners);
  } else {
    // LIFO order
    while (listeners.length)
      this.removeListener(type, listeners[listeners.length - 1]);
  }
  delete this._events[type];

  return this;
};

EventEmitter.prototype.listeners = function(type) {
  var ret;
  if (!this._events || !this._events[type])
    ret = [];
  else if (isFunction(this._events[type]))
    ret = [this._events[type]];
  else
    ret = this._events[type].slice();
  return ret;
};

EventEmitter.listenerCount = function(emitter, type) {
  var ret;
  if (!emitter._events || !emitter._events[type])
    ret = 0;
  else if (isFunction(emitter._events[type]))
    ret = 1;
  else
    ret = emitter._events[type].length;
  return ret;
};

function isFunction(arg) {
  return typeof arg === 'function';
}

function isNumber(arg) {
  return typeof arg === 'number';
}

function isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

function isUndefined(arg) {
  return arg === void 0;
}

},{}],7:[function(_dereq_,module,exports){
'use strict';

module.exports = _dereq_('./is-implemented')() ? Symbol : _dereq_('./polyfill');

},{"./is-implemented":8,"./polyfill":22}],8:[function(_dereq_,module,exports){
'use strict';

module.exports = function () {
	var symbol;
	if (typeof Symbol !== 'function') return false;
	symbol = Symbol('test symbol');
	try {
		if (String(symbol) !== 'Symbol (test symbol)') return false;
	} catch (e) { return false; }
	if (typeof Symbol.iterator === 'symbol') return true;

	// Return 'true' for polyfills
	if (typeof Symbol.isConcatSpreadable !== 'object') return false;
	if (typeof Symbol.isRegExp !== 'object') return false;
	if (typeof Symbol.iterator !== 'object') return false;
	if (typeof Symbol.toPrimitive !== 'object') return false;
	if (typeof Symbol.toStringTag !== 'object') return false;
	if (typeof Symbol.unscopables !== 'object') return false;

	return true;
};

},{}],9:[function(_dereq_,module,exports){
'use strict';

var assign        = _dereq_('es5-ext/object/assign')
  , normalizeOpts = _dereq_('es5-ext/object/normalize-options')
  , isCallable    = _dereq_('es5-ext/object/is-callable')
  , contains      = _dereq_('es5-ext/string/#/contains')

  , d;

d = module.exports = function (dscr, value/*, options*/) {
	var c, e, w, options, desc;
	if ((arguments.length < 2) || (typeof dscr !== 'string')) {
		options = value;
		value = dscr;
		dscr = null;
	} else {
		options = arguments[2];
	}
	if (dscr == null) {
		c = w = true;
		e = false;
	} else {
		c = contains.call(dscr, 'c');
		e = contains.call(dscr, 'e');
		w = contains.call(dscr, 'w');
	}

	desc = { value: value, configurable: c, enumerable: e, writable: w };
	return !options ? desc : assign(normalizeOpts(options), desc);
};

d.gs = function (dscr, get, set/*, options*/) {
	var c, e, options, desc;
	if (typeof dscr !== 'string') {
		options = set;
		set = get;
		get = dscr;
		dscr = null;
	} else {
		options = arguments[3];
	}
	if (get == null) {
		get = undefined;
	} else if (!isCallable(get)) {
		options = get;
		get = set = undefined;
	} else if (set == null) {
		set = undefined;
	} else if (!isCallable(set)) {
		options = set;
		set = undefined;
	}
	if (dscr == null) {
		c = true;
		e = false;
	} else {
		c = contains.call(dscr, 'c');
		e = contains.call(dscr, 'e');
	}

	desc = { get: get, set: set, configurable: c, enumerable: e };
	return !options ? desc : assign(normalizeOpts(options), desc);
};

},{"es5-ext/object/assign":10,"es5-ext/object/is-callable":13,"es5-ext/object/normalize-options":17,"es5-ext/string/#/contains":19}],10:[function(_dereq_,module,exports){
'use strict';

module.exports = _dereq_('./is-implemented')()
	? Object.assign
	: _dereq_('./shim');

},{"./is-implemented":11,"./shim":12}],11:[function(_dereq_,module,exports){
'use strict';

module.exports = function () {
	var assign = Object.assign, obj;
	if (typeof assign !== 'function') return false;
	obj = { foo: 'raz' };
	assign(obj, { bar: 'dwa' }, { trzy: 'trzy' });
	return (obj.foo + obj.bar + obj.trzy) === 'razdwatrzy';
};

},{}],12:[function(_dereq_,module,exports){
'use strict';

var keys  = _dereq_('../keys')
  , value = _dereq_('../valid-value')

  , max = Math.max;

module.exports = function (dest, src/*, …srcn*/) {
	var error, i, l = max(arguments.length, 2), assign;
	dest = Object(value(dest));
	assign = function (key) {
		try { dest[key] = src[key]; } catch (e) {
			if (!error) error = e;
		}
	};
	for (i = 1; i < l; ++i) {
		src = arguments[i];
		keys(src).forEach(assign);
	}
	if (error !== undefined) throw error;
	return dest;
};

},{"../keys":14,"../valid-value":18}],13:[function(_dereq_,module,exports){
// Deprecated

'use strict';

module.exports = function (obj) { return typeof obj === 'function'; };

},{}],14:[function(_dereq_,module,exports){
'use strict';

module.exports = _dereq_('./is-implemented')()
	? Object.keys
	: _dereq_('./shim');

},{"./is-implemented":15,"./shim":16}],15:[function(_dereq_,module,exports){
'use strict';

module.exports = function () {
	try {
		Object.keys('primitive');
		return true;
	} catch (e) { return false; }
};

},{}],16:[function(_dereq_,module,exports){
'use strict';

var keys = Object.keys;

module.exports = function (object) {
	return keys(object == null ? object : Object(object));
};

},{}],17:[function(_dereq_,module,exports){
'use strict';

var assign = _dereq_('./assign')

  , forEach = Array.prototype.forEach
  , create = Object.create, getPrototypeOf = Object.getPrototypeOf

  , process;

process = function (src, obj) {
	var proto = getPrototypeOf(src);
	return assign(proto ? process(proto, obj) : obj, src);
};

module.exports = function (options/*, …options*/) {
	var result = create(null);
	forEach.call(arguments, function (options) {
		if (options == null) return;
		process(Object(options), result);
	});
	return result;
};

},{"./assign":10}],18:[function(_dereq_,module,exports){
'use strict';

module.exports = function (value) {
	if (value == null) throw new TypeError("Cannot use null or undefined");
	return value;
};

},{}],19:[function(_dereq_,module,exports){
'use strict';

module.exports = _dereq_('./is-implemented')()
	? String.prototype.contains
	: _dereq_('./shim');

},{"./is-implemented":20,"./shim":21}],20:[function(_dereq_,module,exports){
'use strict';

var str = 'razdwatrzy';

module.exports = function () {
	if (typeof str.contains !== 'function') return false;
	return ((str.contains('dwa') === true) && (str.contains('foo') === false));
};

},{}],21:[function(_dereq_,module,exports){
'use strict';

var indexOf = String.prototype.indexOf;

module.exports = function (searchString/*, position*/) {
	return indexOf.call(this, searchString, arguments[1]) > -1;
};

},{}],22:[function(_dereq_,module,exports){
'use strict';

var d = _dereq_('d')

  , create = Object.create, defineProperties = Object.defineProperties
  , generateName, Symbol;

generateName = (function () {
	var created = create(null);
	return function (desc) {
		var postfix = 0;
		while (created[desc + (postfix || '')]) ++postfix;
		desc += (postfix || '');
		created[desc] = true;
		return '@@' + desc;
	};
}());

module.exports = Symbol = function (description) {
	var symbol;
	if (this instanceof Symbol) {
		throw new TypeError('TypeError: Symbol is not a constructor');
	}
	symbol = create(Symbol.prototype);
	description = (description === undefined ? '' : String(description));
	return defineProperties(symbol, {
		__description__: d('', description),
		__name__: d('', generateName(description))
	});
};

Object.defineProperties(Symbol, {
	create: d('', Symbol('create')),
	hasInstance: d('', Symbol('hasInstance')),
	isConcatSpreadable: d('', Symbol('isConcatSpreadable')),
	isRegExp: d('', Symbol('isRegExp')),
	iterator: d('', Symbol('iterator')),
	toPrimitive: d('', Symbol('toPrimitive')),
	toStringTag: d('', Symbol('toStringTag')),
	unscopables: d('', Symbol('unscopables'))
});

defineProperties(Symbol.prototype, {
	properToString: d(function () {
		return 'Symbol (' + this.__description__ + ')';
	}),
	toString: d('', function () { return this.__name__; })
});
Object.defineProperty(Symbol.prototype, Symbol.toPrimitive, d('',
	function (hint) {
		throw new TypeError("Conversion of symbol objects is not allowed");
	}));
Object.defineProperty(Symbol.prototype, Symbol.toStringTag, d('c', 'Symbol'));

},{"d":9}],23:[function(_dereq_,module,exports){
module.exports={
  "types": {
    "root": {
      "category": "actor",
      "typeName": "root",
      "methods": [
        {
          "name": "echo",
          "request": {
            "string": { "_arg": 0, "type": "string" }
          },
          "response": {
            "string": { "_retval": "string" }
          }
        },
        {
          "name": "listTabs",
          "request": {},
          "response": { "_retval": "tablist" }
        },
        {
          "name": "protocolDescription",
          "request": {},
          "response": { "_retval": "json" }
        }
      ],
      "events": {
        "tabListChanged": {}
      }
    },
    "tablist": {
      "category": "dict",
      "typeName": "tablist",
      "specializations": {
        "selected": "number",
        "tabs": "array:tab",
        "url": "string",
        "consoleActor": "console",
        "inspectorActor": "inspector",
        "styleSheetsActor": "stylesheets",
        "styleEditorActor": "styleeditor",
        "memoryActor": "memory",
        "eventLoopLagActor": "eventLoopLag",
        "preferenceActor": "preference",
        "deviceActor": "device",

        "profilerActor": "profiler",
        "chromeDebugger": "chromeDebugger",
        "webappsActor": "webapps"
      }
    },
    "tab": {
      "category": "actor",
      "typeName": "tab",
      "fields": {
        "title": "string",
        "url": "string",
        "outerWindowID": "number",
        "inspectorActor": "inspector",
        "callWatcherActor": "call-watcher",
        "canvasActor": "canvas",
        "webglActor": "webgl",
        "webaudioActor": "webaudio",
        "storageActor": "storage",
        "gcliActor": "gcli",
        "memoryActor": "memory",
        "eventLoopLag": "eventLoopLag",
        "styleSheetsActor": "stylesheets",
        "styleEditorActor": "styleeditor",

        "consoleActor": "console",
        "traceActor": "trace"
      },
      "methods": [
         {
          "name": "attach",
          "request": {},
          "response": { "_retval": "json" }
         }
      ],
      "events": {
        "tabNavigated": {
           "typeName": "tabNavigated"
        }
      }
    },
    "console": {
      "category": "actor",
      "typeName": "console",
      "methods": [
        {
          "name": "evaluateJS",
          "request": {
            "text": {
              "_option": 0,
              "type": "string"
            },
            "url": {
              "_option": 1,
              "type": "string"
            },
            "bindObjectActor": {
              "_option": 2,
              "type": "nullable:string"
            },
            "frameActor": {
              "_option": 2,
              "type": "nullable:string"
            },
            "selectedNodeActor": {
              "_option": 2,
              "type": "nullable:string"
            }
          },
          "response": {
            "_retval": "evaluatejsresponse"
          }
        }
      ],
      "events": {}
    },
    "evaluatejsresponse": {
      "category": "dict",
      "typeName": "evaluatejsresponse",
      "specializations": {
        "result": "object",
        "exception": "object",
        "exceptionMessage": "string",
        "input": "string"
      }
    },
    "object": {
      "category": "actor",
      "typeName": "object",
      "methods": [
         {
           "name": "property",
           "request": {
              "name": {
                "_arg": 0,
                "type": "string"
              }
           },
           "response": {
              "descriptor": {
                "_retval": "json"
              }
           }
         }
      ]
    }
  }
}

},{}],24:[function(_dereq_,module,exports){
module.exports={
  "types": {
    "longstractor": {
      "category": "actor",
      "typeName": "longstractor",
      "methods": [
        {
          "name": "substring",
          "request": {
            "type": "substring",
            "start": {
              "_arg": 0,
              "type": "primitive"
            },
            "end": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {
            "substring": {
              "_retval": "primitive"
            }
          }
        },
        {
          "name": "release",
          "release": true,
          "request": {
            "type": "release"
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "stylesheet": {
      "category": "actor",
      "typeName": "stylesheet",
      "methods": [
        {
          "name": "toggleDisabled",
          "request": {
            "type": "toggleDisabled"
          },
          "response": {
            "disabled": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "getText",
          "request": {
            "type": "getText"
          },
          "response": {
            "text": {
              "_retval": "longstring"
            }
          }
        },
        {
          "name": "getOriginalSources",
          "request": {
            "type": "getOriginalSources"
          },
          "response": {
            "originalSources": {
              "_retval": "nullable:array:originalsource"
            }
          }
        },
        {
          "name": "getOriginalLocation",
          "request": {
            "type": "getOriginalLocation",
            "line": {
              "_arg": 0,
              "type": "number"
            },
            "column": {
              "_arg": 1,
              "type": "number"
            }
          },
          "response": {
            "_retval": "originallocationresponse"
          }
        },
        {
          "name": "update",
          "request": {
            "type": "update",
            "text": {
              "_arg": 0,
              "type": "string"
            },
            "transition": {
              "_arg": 1,
              "type": "boolean"
            }
          },
          "response": {}
        }
      ],
      "events": {
        "property-change": {
          "type": "propertyChange",
          "property": {
            "_arg": 0,
            "type": "string"
          },
          "value": {
            "_arg": 1,
            "type": "json"
          }
        },
        "style-applied": {
          "type": "styleApplied"
        }
      }
    },
    "originalsource": {
      "category": "actor",
      "typeName": "originalsource",
      "methods": [
        {
          "name": "getText",
          "request": {
            "type": "getText"
          },
          "response": {
            "text": {
              "_retval": "longstring"
            }
          }
        }
      ],
      "events": {}
    },
    "stylesheets": {
      "category": "actor",
      "typeName": "stylesheets",
      "methods": [
        {
          "name": "getStyleSheets",
          "request": {
            "type": "getStyleSheets"
          },
          "response": {
            "styleSheets": {
              "_retval": "array:stylesheet"
            }
          }
        },
        {
          "name": "addStyleSheet",
          "request": {
            "type": "addStyleSheet",
            "text": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "styleSheet": {
              "_retval": "stylesheet"
            }
          }
        }
      ],
      "events": {}
    },
    "originallocationresponse": {
      "category": "dict",
      "typeName": "originallocationresponse",
      "specializations": {
        "source": "string",
        "line": "number",
        "column": "number"
      }
    },
    "domnode": {
      "category": "actor",
      "typeName": "domnode",
      "methods": [
        {
          "name": "getNodeValue",
          "request": {
            "type": "getNodeValue"
          },
          "response": {
            "value": {
              "_retval": "longstring"
            }
          }
        },
        {
          "name": "setNodeValue",
          "request": {
            "type": "setNodeValue",
            "value": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "getImageData",
          "request": {
            "type": "getImageData",
            "maxDim": {
              "_arg": 0,
              "type": "nullable:number"
            }
          },
          "response": {
            "_retval": "imageData"
          }
        },
        {
          "name": "modifyAttributes",
          "request": {
            "type": "modifyAttributes",
            "modifications": {
              "_arg": 0,
              "type": "array:json"
            }
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "appliedstyle": {
      "category": "dict",
      "typeName": "appliedstyle",
      "specializations": {
        "rule": "domstylerule#actorid",
        "inherited": "nullable:domnode#actorid"
      }
    },
    "matchedselector": {
      "category": "dict",
      "typeName": "matchedselector",
      "specializations": {
        "rule": "domstylerule#actorid",
        "selector": "string",
        "value": "string",
        "status": "number"
      }
    },
    "matchedselectorresponse": {
      "category": "dict",
      "typeName": "matchedselectorresponse",
      "specializations": {
        "rules": "array:domstylerule",
        "sheets": "array:stylesheet",
        "matched": "array:matchedselector"
      }
    },
    "appliedStylesReturn": {
      "category": "dict",
      "typeName": "appliedStylesReturn",
      "specializations": {
        "entries": "array:appliedstyle",
        "rules": "array:domstylerule",
        "sheets": "array:stylesheet"
      }
    },
    "pagestyle": {
      "category": "actor",
      "typeName": "pagestyle",
      "methods": [
        {
          "name": "getComputed",
          "request": {
            "type": "getComputed",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "markMatched": {
              "_option": 1,
              "type": "boolean"
            },
            "onlyMatched": {
              "_option": 1,
              "type": "boolean"
            },
            "filter": {
              "_option": 1,
              "type": "string"
            }
          },
          "response": {
            "computed": {
              "_retval": "json"
            }
          }
        },
        {
          "name": "getMatchedSelectors",
          "request": {
            "type": "getMatchedSelectors",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "property": {
              "_arg": 1,
              "type": "string"
            },
            "filter": {
              "_option": 2,
              "type": "string"
            }
          },
          "response": {
            "_retval": "matchedselectorresponse"
          }
        },
        {
          "name": "getApplied",
          "request": {
            "type": "getApplied",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "inherited": {
              "_option": 1,
              "type": "boolean"
            },
            "matchedSelectors": {
              "_option": 1,
              "type": "boolean"
            },
            "filter": {
              "_option": 1,
              "type": "string"
            }
          },
          "response": {
            "_retval": "appliedStylesReturn"
          }
        },
        {
          "name": "getLayout",
          "request": {
            "type": "getLayout",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "autoMargins": {
              "_option": 1,
              "type": "boolean"
            }
          },
          "response": {
            "_retval": "json"
          }
        }
      ],
      "events": {}
    },
    "domstylerule": {
      "category": "actor",
      "typeName": "domstylerule",
      "methods": [
        {
          "name": "modifyProperties",
          "request": {
            "type": "modifyProperties",
            "modifications": {
              "_arg": 0,
              "type": "array:json"
            }
          },
          "response": {
            "rule": {
              "_retval": "domstylerule"
            }
          }
        }
      ],
      "events": {}
    },
    "highlighter": {
      "category": "actor",
      "typeName": "highlighter",
      "methods": [
        {
          "name": "showBoxModel",
          "request": {
            "type": "showBoxModel",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "region": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "hideBoxModel",
          "request": {
            "type": "hideBoxModel"
          },
          "response": {}
        },
        {
          "name": "pick",
          "request": {
            "type": "pick"
          },
          "response": {}
        },
        {
          "name": "cancelPick",
          "request": {
            "type": "cancelPick"
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "imageData": {
      "category": "dict",
      "typeName": "imageData",
      "specializations": {
        "data": "nullable:longstring",
        "size": "json"
      }
    },
    "disconnectedNode": {
      "category": "dict",
      "typeName": "disconnectedNode",
      "specializations": {
        "node": "domnode",
        "newParents": "array:domnode"
      }
    },
    "disconnectedNodeArray": {
      "category": "dict",
      "typeName": "disconnectedNodeArray",
      "specializations": {
        "nodes": "array:domnode",
        "newParents": "array:domnode"
      }
    },
    "dommutation": {
      "category": "dict",
      "typeName": "dommutation",
      "specializations": {}
    },
    "domnodelist": {
      "category": "actor",
      "typeName": "domnodelist",
      "methods": [
        {
          "name": "item",
          "request": {
            "type": "item",
            "item": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {
            "_retval": "disconnectedNode"
          }
        },
        {
          "name": "items",
          "request": {
            "type": "items",
            "start": {
              "_arg": 0,
              "type": "nullable:number"
            },
            "end": {
              "_arg": 1,
              "type": "nullable:number"
            }
          },
          "response": {
            "_retval": "disconnectedNodeArray"
          }
        },
        {
          "name": "release",
          "release": true,
          "request": {
            "type": "release"
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "domtraversalarray": {
      "category": "dict",
      "typeName": "domtraversalarray",
      "specializations": {
        "nodes": "array:domnode"
      }
    },
    "domwalker": {
      "category": "actor",
      "typeName": "domwalker",
      "methods": [
        {
          "name": "release",
          "release": true,
          "request": {
            "type": "release"
          },
          "response": {}
        },
        {
          "name": "pick",
          "request": {
            "type": "pick"
          },
          "response": {
            "_retval": "disconnectedNode"
          }
        },
        {
          "name": "cancelPick",
          "request": {
            "type": "cancelPick"
          },
          "response": {}
        },
        {
          "name": "highlight",
          "request": {
            "type": "highlight",
            "node": {
              "_arg": 0,
              "type": "nullable:domnode"
            }
          },
          "response": {}
        },
        {
          "name": "document",
          "request": {
            "type": "document",
            "node": {
              "_arg": 0,
              "type": "nullable:domnode"
            }
          },
          "response": {
            "node": {
              "_retval": "domnode"
            }
          }
        },
        {
          "name": "documentElement",
          "request": {
            "type": "documentElement",
            "node": {
              "_arg": 0,
              "type": "nullable:domnode"
            }
          },
          "response": {
            "node": {
              "_retval": "domnode"
            }
          }
        },
        {
          "name": "parents",
          "request": {
            "type": "parents",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "sameDocument": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {
            "nodes": {
              "_retval": "array:domnode"
            }
          }
        },
        {
          "name": "retainNode",
          "request": {
            "type": "retainNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {}
        },
        {
          "name": "unretainNode",
          "request": {
            "type": "unretainNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {}
        },
        {
          "name": "releaseNode",
          "request": {
            "type": "releaseNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "force": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "children",
          "request": {
            "type": "children",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "maxNodes": {
              "_option": 1,
              "type": "primitive"
            },
            "center": {
              "_option": 1,
              "type": "domnode"
            },
            "start": {
              "_option": 1,
              "type": "domnode"
            },
            "whatToShow": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {
            "_retval": "domtraversalarray"
          }
        },
        {
          "name": "siblings",
          "request": {
            "type": "siblings",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "maxNodes": {
              "_option": 1,
              "type": "primitive"
            },
            "center": {
              "_option": 1,
              "type": "domnode"
            },
            "start": {
              "_option": 1,
              "type": "domnode"
            },
            "whatToShow": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {
            "_retval": "domtraversalarray"
          }
        },
        {
          "name": "nextSibling",
          "request": {
            "type": "nextSibling",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "whatToShow": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {
            "node": {
              "_retval": "nullable:domnode"
            }
          }
        },
        {
          "name": "previousSibling",
          "request": {
            "type": "previousSibling",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "whatToShow": {
              "_option": 1,
              "type": "primitive"
            }
          },
          "response": {
            "node": {
              "_retval": "nullable:domnode"
            }
          }
        },
        {
          "name": "querySelector",
          "request": {
            "type": "querySelector",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "selector": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {
            "_retval": "disconnectedNode"
          }
        },
        {
          "name": "querySelectorAll",
          "request": {
            "type": "querySelectorAll",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "selector": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {
            "list": {
              "_retval": "domnodelist"
            }
          }
        },
        {
          "name": "getSuggestionsForQuery",
          "request": {
            "type": "getSuggestionsForQuery",
            "query": {
              "_arg": 0,
              "type": "primitive"
            },
            "completing": {
              "_arg": 1,
              "type": "primitive"
            },
            "selectorState": {
              "_arg": 2,
              "type": "primitive"
            }
          },
          "response": {
            "list": {
              "_retval": "array:array:string"
            }
          }
        },
        {
          "name": "addPseudoClassLock",
          "request": {
            "type": "addPseudoClassLock",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "pseudoClass": {
              "_arg": 1,
              "type": "primitive"
            },
            "parents": {
              "_option": 2,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "hideNode",
          "request": {
            "type": "hideNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {}
        },
        {
          "name": "unhideNode",
          "request": {
            "type": "unhideNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {}
        },
        {
          "name": "removePseudoClassLock",
          "request": {
            "type": "removePseudoClassLock",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "pseudoClass": {
              "_arg": 1,
              "type": "primitive"
            },
            "parents": {
              "_option": 2,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "clearPseudoClassLocks",
          "request": {
            "type": "clearPseudoClassLocks",
            "node": {
              "_arg": 0,
              "type": "nullable:domnode"
            }
          },
          "response": {}
        },
        {
          "name": "innerHTML",
          "request": {
            "type": "innerHTML",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {
            "value": {
              "_retval": "longstring"
            }
          }
        },
        {
          "name": "outerHTML",
          "request": {
            "type": "outerHTML",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {
            "value": {
              "_retval": "longstring"
            }
          }
        },
        {
          "name": "setOuterHTML",
          "request": {
            "type": "setOuterHTML",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "value": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "removeNode",
          "request": {
            "type": "removeNode",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {
            "nextSibling": {
              "_retval": "nullable:domnode"
            }
          }
        },
        {
          "name": "insertBefore",
          "request": {
            "type": "insertBefore",
            "node": {
              "_arg": 0,
              "type": "domnode"
            },
            "parent": {
              "_arg": 1,
              "type": "domnode"
            },
            "sibling": {
              "_arg": 2,
              "type": "nullable:domnode"
            }
          },
          "response": {}
        },
        {
          "name": "getMutations",
          "request": {
            "type": "getMutations",
            "cleanup": {
              "_option": 0,
              "type": "primitive"
            }
          },
          "response": {
            "mutations": {
              "_retval": "array:dommutation"
            }
          }
        },
        {
          "name": "isInDOMTree",
          "request": {
            "type": "isInDOMTree",
            "node": {
              "_arg": 0,
              "type": "domnode"
            }
          },
          "response": {
            "attached": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "getNodeActorFromObjectActor",
          "request": {
            "type": "getNodeActorFromObjectActor",
            "objectActorID": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "nodeFront": {
              "_retval": "nullable:disconnectedNode"
            }
          }
        }
      ],
      "events": {
        "new-mutations": {
          "type": "newMutations"
        },
        "picker-node-picked": {
          "type": "pickerNodePicked",
          "node": {
            "_arg": 0,
            "type": "disconnectedNode"
          }
        },
        "picker-node-hovered": {
          "type": "pickerNodeHovered",
          "node": {
            "_arg": 0,
            "type": "disconnectedNode"
          }
        },
        "highlighter-ready": {
          "type": "highlighter-ready"
        },
        "highlighter-hide": {
          "type": "highlighter-hide"
        }
      }
    },
    "inspector": {
      "category": "actor",
      "typeName": "inspector",
      "methods": [
        {
          "name": "getWalker",
          "request": {
            "type": "getWalker"
          },
          "response": {
            "walker": {
              "_retval": "domwalker"
            }
          }
        },
        {
          "name": "getPageStyle",
          "request": {
            "type": "getPageStyle"
          },
          "response": {
            "pageStyle": {
              "_retval": "pagestyle"
            }
          }
        },
        {
          "name": "getHighlighter",
          "request": {
            "type": "getHighlighter",
            "autohide": {
              "_arg": 0,
              "type": "boolean"
            }
          },
          "response": {
            "highligter": {
              "_retval": "highlighter"
            }
          }
        },
        {
          "name": "getImageDataFromURL",
          "request": {
            "type": "getImageDataFromURL",
            "url": {
              "_arg": 0,
              "type": "primitive"
            },
            "maxDim": {
              "_arg": 1,
              "type": "nullable:number"
            }
          },
          "response": {
            "_retval": "imageData"
          }
        }
      ],
      "events": {}
    },
    "call-stack-item": {
      "category": "dict",
      "typeName": "call-stack-item",
      "specializations": {
        "name": "string",
        "file": "string",
        "line": "number"
      }
    },
    "call-details": {
      "category": "dict",
      "typeName": "call-details",
      "specializations": {
        "type": "number",
        "name": "string",
        "stack": "array:call-stack-item"
      }
    },
    "function-call": {
      "category": "actor",
      "typeName": "function-call",
      "methods": [
        {
          "name": "getDetails",
          "request": {
            "type": "getDetails"
          },
          "response": {
            "info": {
              "_retval": "call-details"
            }
          }
        }
      ],
      "events": {}
    },
    "call-watcher": {
      "category": "actor",
      "typeName": "call-watcher",
      "methods": [
        {
          "name": "setup",
          "oneway": true,
          "request": {
            "type": "setup",
            "tracedGlobals": {
              "_option": 0,
              "type": "nullable:array:string"
            },
            "tracedFunctions": {
              "_option": 0,
              "type": "nullable:array:string"
            },
            "startRecording": {
              "_option": 0,
              "type": "boolean"
            },
            "performReload": {
              "_option": 0,
              "type": "boolean"
            }
          },
          "response": {}
        },
        {
          "name": "finalize",
          "oneway": true,
          "request": {
            "type": "finalize"
          },
          "response": {}
        },
        {
          "name": "isRecording",
          "request": {
            "type": "isRecording"
          },
          "response": {
            "_retval": "boolean"
          }
        },
        {
          "name": "resumeRecording",
          "request": {
            "type": "resumeRecording"
          },
          "response": {}
        },
        {
          "name": "pauseRecording",
          "request": {
            "type": "pauseRecording"
          },
          "response": {
            "calls": {
              "_retval": "array:function-call"
            }
          }
        },
        {
          "name": "eraseRecording",
          "request": {
            "type": "eraseRecording"
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "snapshot-image": {
      "category": "dict",
      "typeName": "snapshot-image",
      "specializations": {
        "index": "number",
        "width": "number",
        "height": "number",
        "flipped": "boolean",
        "pixels": "uint32-array"
      }
    },
    "snapshot-overview": {
      "category": "dict",
      "typeName": "snapshot-overview",
      "specializations": {
        "calls": "array:function-call",
        "thumbnails": "array:snapshot-image",
        "screenshot": "snapshot-image"
      }
    },
    "frame-snapshot": {
      "category": "actor",
      "typeName": "frame-snapshot",
      "methods": [
        {
          "name": "getOverview",
          "request": {
            "type": "getOverview"
          },
          "response": {
            "overview": {
              "_retval": "snapshot-overview"
            }
          }
        },
        {
          "name": "generateScreenshotFor",
          "request": {
            "type": "generateScreenshotFor",
            "call": {
              "_arg": 0,
              "type": "function-call"
            }
          },
          "response": {
            "screenshot": {
              "_retval": "snapshot-image"
            }
          }
        }
      ],
      "events": {}
    },
    "canvas": {
      "category": "actor",
      "typeName": "canvas",
      "methods": [
        {
          "name": "setup",
          "oneway": true,
          "request": {
            "type": "setup",
            "reload": {
              "_option": 0,
              "type": "boolean"
            }
          },
          "response": {}
        },
        {
          "name": "finalize",
          "oneway": true,
          "request": {
            "type": "finalize"
          },
          "response": {}
        },
        {
          "name": "isInitialized",
          "request": {
            "type": "isInitialized"
          },
          "response": {
            "initialized": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "recordAnimationFrame",
          "request": {
            "type": "recordAnimationFrame"
          },
          "response": {
            "snapshot": {
              "_retval": "frame-snapshot"
            }
          }
        }
      ],
      "events": {}
    },
    "gl-shader": {
      "category": "actor",
      "typeName": "gl-shader",
      "methods": [
        {
          "name": "getText",
          "request": {
            "type": "getText"
          },
          "response": {
            "text": {
              "_retval": "string"
            }
          }
        },
        {
          "name": "compile",
          "request": {
            "type": "compile",
            "text": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "error": {
              "_retval": "nullable:json"
            }
          }
        }
      ],
      "events": {}
    },
    "gl-program": {
      "category": "actor",
      "typeName": "gl-program",
      "methods": [
        {
          "name": "getVertexShader",
          "request": {
            "type": "getVertexShader"
          },
          "response": {
            "shader": {
              "_retval": "gl-shader"
            }
          }
        },
        {
          "name": "getFragmentShader",
          "request": {
            "type": "getFragmentShader"
          },
          "response": {
            "shader": {
              "_retval": "gl-shader"
            }
          }
        },
        {
          "name": "highlight",
          "oneway": true,
          "request": {
            "type": "highlight",
            "tint": {
              "_arg": 0,
              "type": "array:number"
            }
          },
          "response": {}
        },
        {
          "name": "unhighlight",
          "oneway": true,
          "request": {
            "type": "unhighlight"
          },
          "response": {}
        },
        {
          "name": "blackbox",
          "oneway": true,
          "request": {
            "type": "blackbox"
          },
          "response": {}
        },
        {
          "name": "unblackbox",
          "oneway": true,
          "request": {
            "type": "unblackbox"
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "webgl": {
      "category": "actor",
      "typeName": "webgl",
      "methods": [
        {
          "name": "setup",
          "oneway": true,
          "request": {
            "type": "setup",
            "reload": {
              "_option": 0,
              "type": "boolean"
            }
          },
          "response": {}
        },
        {
          "name": "finalize",
          "oneway": true,
          "request": {
            "type": "finalize"
          },
          "response": {}
        },
        {
          "name": "getPrograms",
          "request": {
            "type": "getPrograms"
          },
          "response": {
            "programs": {
              "_retval": "array:gl-program"
            }
          }
        }
      ],
      "events": {
        "program-linked": {
          "type": "programLinked",
          "program": {
            "_arg": 0,
            "type": "gl-program"
          }
        }
      }
    },
    "audionode": {
      "category": "actor",
      "typeName": "audionode",
      "methods": [
        {
          "name": "getType",
          "request": {
            "type": "getType"
          },
          "response": {
            "type": {
              "_retval": "string"
            }
          }
        },
        {
          "name": "isSource",
          "request": {
            "type": "isSource"
          },
          "response": {
            "source": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "setParam",
          "request": {
            "type": "setParam",
            "param": {
              "_arg": 0,
              "type": "string"
            },
            "value": {
              "_arg": 1,
              "type": "nullable:primitive"
            }
          },
          "response": {
            "error": {
              "_retval": "nullable:json"
            }
          }
        },
        {
          "name": "getParam",
          "request": {
            "type": "getParam",
            "param": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "text": {
              "_retval": "nullable:primitive"
            }
          }
        },
        {
          "name": "getParamFlags",
          "request": {
            "type": "getParamFlags",
            "param": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "flags": {
              "_retval": "nullable:primitive"
            }
          }
        },
        {
          "name": "getParams",
          "request": {
            "type": "getParams"
          },
          "response": {
            "params": {
              "_retval": "json"
            }
          }
        }
      ],
      "events": {}
    },
    "webaudio": {
      "category": "actor",
      "typeName": "webaudio",
      "methods": [
        {
          "name": "setup",
          "oneway": true,
          "request": {
            "type": "setup",
            "reload": {
              "_option": 0,
              "type": "boolean"
            }
          },
          "response": {}
        },
        {
          "name": "finalize",
          "oneway": true,
          "request": {
            "type": "finalize"
          },
          "response": {}
        }
      ],
      "events": {
        "start-context": {
          "type": "startContext"
        },
        "connect-node": {
          "type": "connectNode",
          "source": {
            "_option": 0,
            "type": "audionode"
          },
          "dest": {
            "_option": 0,
            "type": "audionode"
          }
        },
        "disconnect-node": {
          "type": "disconnectNode",
          "source": {
            "_arg": 0,
            "type": "audionode"
          }
        },
        "connect-param": {
          "type": "connectParam",
          "source": {
            "_arg": 0,
            "type": "audionode"
          },
          "param": {
            "_arg": 1,
            "type": "string"
          }
        },
        "change-param": {
          "type": "changeParam",
          "source": {
            "_option": 0,
            "type": "audionode"
          },
          "param": {
            "_option": 0,
            "type": "string"
          },
          "value": {
            "_option": 0,
            "type": "string"
          }
        },
        "create-node": {
          "type": "createNode",
          "source": {
            "_arg": 0,
            "type": "audionode"
          }
        }
      }
    },
    "old-stylesheet": {
      "category": "actor",
      "typeName": "old-stylesheet",
      "methods": [
        {
          "name": "toggleDisabled",
          "request": {
            "type": "toggleDisabled"
          },
          "response": {
            "disabled": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "fetchSource",
          "request": {
            "type": "fetchSource"
          },
          "response": {}
        },
        {
          "name": "update",
          "request": {
            "type": "update",
            "text": {
              "_arg": 0,
              "type": "string"
            },
            "transition": {
              "_arg": 1,
              "type": "boolean"
            }
          },
          "response": {}
        }
      ],
      "events": {
        "property-change": {
          "type": "propertyChange",
          "property": {
            "_arg": 0,
            "type": "string"
          },
          "value": {
            "_arg": 1,
            "type": "json"
          }
        },
        "source-load": {
          "type": "sourceLoad",
          "source": {
            "_arg": 0,
            "type": "string"
          }
        },
        "style-applied": {
          "type": "styleApplied"
        }
      }
    },
    "styleeditor": {
      "category": "actor",
      "typeName": "styleeditor",
      "methods": [
        {
          "name": "newDocument",
          "request": {
            "type": "newDocument"
          },
          "response": {}
        },
        {
          "name": "newStyleSheet",
          "request": {
            "type": "newStyleSheet",
            "text": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "styleSheet": {
              "_retval": "old-stylesheet"
            }
          }
        }
      ],
      "events": {
        "document-load": {
          "type": "documentLoad",
          "styleSheets": {
            "_arg": 0,
            "type": "array:old-stylesheet"
          }
        }
      }
    },
    "cookieobject": {
      "category": "dict",
      "typeName": "cookieobject",
      "specializations": {
        "name": "string",
        "value": "longstring",
        "path": "nullable:string",
        "host": "string",
        "isDomain": "boolean",
        "isSecure": "boolean",
        "isHttpOnly": "boolean",
        "creationTime": "number",
        "lastAccessed": "number",
        "expires": "number"
      }
    },
    "cookiestoreobject": {
      "category": "dict",
      "typeName": "cookiestoreobject",
      "specializations": {
        "total": "number",
        "offset": "number",
        "data": "array:nullable:cookieobject"
      }
    },
    "storageobject": {
      "category": "dict",
      "typeName": "storageobject",
      "specializations": {
        "name": "string",
        "value": "longstring"
      }
    },
    "storagestoreobject": {
      "category": "dict",
      "typeName": "storagestoreobject",
      "specializations": {
        "total": "number",
        "offset": "number",
        "data": "array:nullable:storageobject"
      }
    },
    "idbobject": {
      "category": "dict",
      "typeName": "idbobject",
      "specializations": {
        "name": "nullable:string",
        "db": "nullable:string",
        "objectStore": "nullable:string",
        "origin": "nullable:string",
        "version": "nullable:number",
        "objectStores": "nullable:number",
        "keyPath": "nullable:string",
        "autoIncrement": "nullable:boolean",
        "indexes": "nullable:string",
        "value": "nullable:longstring"
      }
    },
    "idbstoreobject": {
      "category": "dict",
      "typeName": "idbstoreobject",
      "specializations": {
        "total": "number",
        "offset": "number",
        "data": "array:nullable:idbobject"
      }
    },
    "storeUpdateObject": {
      "category": "dict",
      "typeName": "storeUpdateObject",
      "specializations": {
        "changed": "nullable:json",
        "deleted": "nullable:json",
        "added": "nullable:json"
      }
    },
    "cookies": {
      "category": "actor",
      "typeName": "cookies",
      "methods": [
        {
          "name": "getStoreObjects",
          "request": {
            "type": "getStoreObjects",
            "host": {
              "_arg": 0,
              "type": "primitive"
            },
            "names": {
              "_arg": 1,
              "type": "nullable:array:string"
            },
            "options": {
              "_arg": 2,
              "type": "nullable:json"
            }
          },
          "response": {
            "_retval": "cookiestoreobject"
          }
        }
      ],
      "events": {}
    },
    "localStorage": {
      "category": "actor",
      "typeName": "localStorage",
      "methods": [
        {
          "name": "getStoreObjects",
          "request": {
            "type": "getStoreObjects",
            "host": {
              "_arg": 0,
              "type": "primitive"
            },
            "names": {
              "_arg": 1,
              "type": "nullable:array:string"
            },
            "options": {
              "_arg": 2,
              "type": "nullable:json"
            }
          },
          "response": {
            "_retval": "storagestoreobject"
          }
        }
      ],
      "events": {}
    },
    "sessionStorage": {
      "category": "actor",
      "typeName": "sessionStorage",
      "methods": [
        {
          "name": "getStoreObjects",
          "request": {
            "type": "getStoreObjects",
            "host": {
              "_arg": 0,
              "type": "primitive"
            },
            "names": {
              "_arg": 1,
              "type": "nullable:array:string"
            },
            "options": {
              "_arg": 2,
              "type": "nullable:json"
            }
          },
          "response": {
            "_retval": "storagestoreobject"
          }
        }
      ],
      "events": {}
    },
    "indexedDB": {
      "category": "actor",
      "typeName": "indexedDB",
      "methods": [
        {
          "name": "getStoreObjects",
          "request": {
            "type": "getStoreObjects",
            "host": {
              "_arg": 0,
              "type": "primitive"
            },
            "names": {
              "_arg": 1,
              "type": "nullable:array:string"
            },
            "options": {
              "_arg": 2,
              "type": "nullable:json"
            }
          },
          "response": {
            "_retval": "idbstoreobject"
          }
        }
      ],
      "events": {}
    },
    "storelist": {
      "category": "dict",
      "typeName": "storelist",
      "specializations": {
        "cookies": "cookies",
        "localStorage": "localStorage",
        "sessionStorage": "sessionStorage",
        "indexedDB": "indexedDB"
      }
    },
    "storage": {
      "category": "actor",
      "typeName": "storage",
      "methods": [
        {
          "name": "listStores",
          "request": {
            "type": "listStores"
          },
          "response": {
            "_retval": "storelist"
          }
        }
      ],
      "events": {
        "stores-update": {
          "type": "storesUpdate",
          "data": {
            "_arg": 0,
            "type": "storeUpdateObject"
          }
        },
        "stores-cleared": {
          "type": "storesCleared",
          "data": {
            "_arg": 0,
            "type": "json"
          }
        },
        "stores-reloaded": {
          "type": "storesRelaoded",
          "data": {
            "_arg": 0,
            "type": "json"
          }
        }
      }
    },
    "gcli": {
      "category": "actor",
      "typeName": "gcli",
      "methods": [
        {
          "name": "specs",
          "request": {
            "type": "specs"
          },
          "response": {
            "_retval": "json"
          }
        },
        {
          "name": "execute",
          "request": {
            "type": "execute",
            "typed": {
              "_arg": 0,
              "type": "string"
            }
          },
          "response": {
            "_retval": "json"
          }
        },
        {
          "name": "state",
          "request": {
            "type": "state",
            "typed": {
              "_arg": 0,
              "type": "string"
            },
            "start": {
              "_arg": 1,
              "type": "number"
            },
            "rank": {
              "_arg": 2,
              "type": "number"
            }
          },
          "response": {
            "_retval": "json"
          }
        },
        {
          "name": "typeparse",
          "request": {
            "type": "typeparse",
            "typed": {
              "_arg": 0,
              "type": "string"
            },
            "param": {
              "_arg": 1,
              "type": "string"
            }
          },
          "response": {
            "_retval": "json"
          }
        },
        {
          "name": "typeincrement",
          "request": {
            "type": "typeincrement",
            "typed": {
              "_arg": 0,
              "type": "string"
            },
            "param": {
              "_arg": 1,
              "type": "string"
            }
          },
          "response": {
            "_retval": "string"
          }
        },
        {
          "name": "typedecrement",
          "request": {
            "type": "typedecrement",
            "typed": {
              "_arg": 0,
              "type": "string"
            },
            "param": {
              "_arg": 1,
              "type": "string"
            }
          },
          "response": {
            "_retval": "string"
          }
        },
        {
          "name": "selectioninfo",
          "request": {
            "type": "selectioninfo",
            "typed": {
              "_arg": 0,
              "type": "string"
            },
            "param": {
              "_arg": 1,
              "type": "string"
            },
            "action": {
              "_arg": 1,
              "type": "string"
            }
          },
          "response": {
            "_retval": "json"
          }
        }
      ],
      "events": {}
    },
    "memory": {
      "category": "actor",
      "typeName": "memory",
      "methods": [
        {
          "name": "measure",
          "request": {
            "type": "measure"
          },
          "response": {
            "_retval": "json"
          }
        }
      ],
      "events": {}
    },
    "eventLoopLag": {
      "category": "actor",
      "typeName": "eventLoopLag",
      "methods": [
        {
          "name": "start",
          "request": {
            "type": "start"
          },
          "response": {
            "success": {
              "_retval": "number"
            }
          }
        },
        {
          "name": "stop",
          "request": {
            "type": "stop"
          },
          "response": {}
        }
      ],
      "events": {
        "event-loop-lag": {
          "type": "event-loop-lag",
          "time": {
            "_arg": 0,
            "type": "number"
          }
        }
      }
    },
    "preference": {
      "category": "actor",
      "typeName": "preference",
      "methods": [
        {
          "name": "getBoolPref",
          "request": {
            "type": "getBoolPref",
            "value": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {
            "value": {
              "_retval": "boolean"
            }
          }
        },
        {
          "name": "getCharPref",
          "request": {
            "type": "getCharPref",
            "value": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {
            "value": {
              "_retval": "string"
            }
          }
        },
        {
          "name": "getIntPref",
          "request": {
            "type": "getIntPref",
            "value": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {
            "value": {
              "_retval": "number"
            }
          }
        },
        {
          "name": "getAllPrefs",
          "request": {
            "type": "getAllPrefs"
          },
          "response": {
            "value": {
              "_retval": "json"
            }
          }
        },
        {
          "name": "setBoolPref",
          "request": {
            "type": "setBoolPref",
            "name": {
              "_arg": 0,
              "type": "primitive"
            },
            "value": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "setCharPref",
          "request": {
            "type": "setCharPref",
            "name": {
              "_arg": 0,
              "type": "primitive"
            },
            "value": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "setIntPref",
          "request": {
            "type": "setIntPref",
            "name": {
              "_arg": 0,
              "type": "primitive"
            },
            "value": {
              "_arg": 1,
              "type": "primitive"
            }
          },
          "response": {}
        },
        {
          "name": "clearUserPref",
          "request": {
            "type": "clearUserPref",
            "name": {
              "_arg": 0,
              "type": "primitive"
            }
          },
          "response": {}
        }
      ],
      "events": {}
    },
    "device": {
      "category": "actor",
      "typeName": "device",
      "methods": [
        {
          "name": "getDescription",
          "request": {
            "type": "getDescription"
          },
          "response": {
            "value": {
              "_retval": "json"
            }
          }
        },
        {
          "name": "getWallpaper",
          "request": {
            "type": "getWallpaper"
          },
          "response": {
            "value": {
              "_retval": "longstring"
            }
          }
        },
        {
          "name": "screenshotToDataURL",
          "request": {
            "type": "screenshotToDataURL"
          },
          "response": {
            "value": {
              "_retval": "longstring"
            }
          }
        }
      ],
      "events": {}
    }
  },
  "from": "root"
}

},{}],25:[function(_dereq_,module,exports){
"use strict";

var Class = _dereq_("./class").Class;
var util = _dereq_("./util");
var keys = util.keys;
var values = util.values;
var pairs = util.pairs;
var query = util.query;
var findPath = util.findPath;
var EventTarget = _dereq_("./event").EventTarget;

var TypeSystem = Class({
  constructor: function(client) {
    var types = Object.create(null);
    var specification = Object.create(null);

    this.specification = specification;
    this.types = types;

    var typeFor = function typeFor(typeName) {
      typeName = typeName || "primitive";
      if (!types[typeName]) {
        defineType(typeName);
      }

      return types[typeName];
    };
    this.typeFor = typeFor;

    var defineType = function(descriptor) {
      var type = void(0);
      if (typeof(descriptor) === "string") {
        if (descriptor.indexOf(":") > 0)
          type = makeCompoundType(descriptor);
        else if (descriptor.indexOf("#") > 0)
          type = new ActorDetail(descriptor);
          else if (specification[descriptor])
            type = makeCategoryType(specification[descriptor]);
      } else {
        type = makeCategoryType(descriptor);
      }

      if (type)
        types[type.name] = type;
      else
        throw TypeError("Invalid type: " + descriptor);
    };
    this.defineType = defineType;


    var makeCompoundType = function(name) {
      var index = name.indexOf(":");
      var baseType = name.slice(0, index);
      var subType = name.slice(index + 1);

      return baseType === "array" ? new ArrayOf(subType) :
      baseType === "nullable" ? new Maybe(subType) :
      null;
    };

    var makeCategoryType = function(descriptor) {
      var category = descriptor.category;
      return category === "dict" ? new Dictionary(descriptor) :
      category === "actor" ? new Actor(descriptor) :
      null;
    };

    var read = function(input, context, typeName) {
      return typeFor(typeName).read(input, context);
    }
    this.read = read;

    var write = function(input, context, typeName) {
      return typeFor(typeName).write(input);
    };
    this.write = write;


    var Type = Class({
      constructor: function() {
      },
      get name() {
        return this.category ? this.category + ":" + this.type :
        this.type;
      },
      read: function(input, context) {
        throw new TypeError("`Type` subclass must implement `read`");
      },
      write: function(input, context) {
        throw new TypeError("`Type` subclass must implement `write`");
      }
    });

    var Primitve = Class({
      extends: Type,
      constuctor: function(type) {
        this.type = type;
      },
      read: function(input, context) {
        return input;
      },
      write: function(input, context) {
        return input;
      }
    });

    var Maybe = Class({
      extends: Type,
      category: "nullable",
      constructor: function(type) {
        this.type = type;
      },
      read: function(input, context) {
        return input === null ? null :
        input === void(0) ? void(0) :
        read(input, context, this.type);
      },
      write: function(input, context) {
        return input === null ? null :
        input === void(0) ? void(0) :
        write(input, context, this.type);
      }
    });

    var ArrayOf = Class({
      extends: Type,
      category: "array",
      constructor: function(type) {
        this.type = type;
      },
      read: function(input, context) {
        var type = this.type;
        return input.map(function($) { return read($, context, type) });
      },
      write: function(input, context) {
        var type = this.type;
        return input.map(function($) { return write($, context, type) });
      }
    });

    var makeField = function makeField(name, type) {
      return {
        enumerable: true,
        configurable: true,
        get: function() {
          Object.defineProperty(this, name, {
            configurable: false,
            value: read(this.state[name], this.context, type)
          });
          return this[name];
        }
      }
    };

    var makeFields = function(descriptor) {
      return pairs(descriptor).reduce(function(fields, pair) {
        var name = pair[0], type = pair[1];
        fields[name] = makeField(name, type);
        return fields;
      }, {});
    }

    var DictionaryType = Class({});

    var Dictionary = Class({
      extends: Type,
      category: "dict",
      get name() { return this.type; },
      constructor: function(descriptor) {
        this.type = descriptor.typeName;
        this.types = descriptor.specializations;

        var proto = Object.defineProperties({
          extends: DictionaryType,
          constructor: function(state, context) {
            Object.defineProperties(this, {
              state: {
                enumerable: false,
                writable: true,
                configurable: true,
                value: state
              },
              context: {
                enumerable: false,
                writable: false,
                configurable: true,
                value: context
              }
            });
          }
        }, makeFields(this.types));

        this.class = new Class(proto);
      },
      read: function(input, context) {
        return new this.class(input, context);
      },
      write: function(input, context) {
        var output = {};
        for (var key in input) {
          output[key] = write(value, context, types[key]);
        }
        return output;
      }
    });

    var makeMethods = function(descriptors) {
      return descriptors.reduce(function(methods, descriptor) {
        methods[descriptor.name] = {
          enumerable: true,
          configurable: true,
          writable: false,
          value: makeMethod(descriptor)
        };
        return methods;
      }, {});
    };

    var makeEvents = function(descriptors) {
      return pairs(descriptors).reduce(function(events, pair) {
        var name = pair[0], descriptor = pair[1];
        var event = new Event(name, descriptor);
        events[event.eventType] = event;
        return events;
      }, Object.create(null));
    };

    var Actor = Class({
      extends: Type,
      category: "actor",
      get name() { return this.type; },
      constructor: function(descriptor) {
        this.type = descriptor.typeName;

        var events = makeEvents(descriptor.events || {});
        var fields = makeFields(descriptor.fields || {});
        var methods = makeMethods(descriptor.methods || []);


        var proto = {
          extends: Front,
          constructor: function() {
            Front.apply(this, arguments);
          },
          events: events
        };
        Object.defineProperties(proto, fields);
        Object.defineProperties(proto, methods);

        this.class = Class(proto);
      },
      read: function(input, context, detail) {
        var state = typeof(input) === "string" ? { actor: input } : input;

        var actor = client.get(state.actor) || new this.class(state, context);
        actor.form(state, detail, context);

        return actor;
      },
      write: function(input, context, detail) {
        return input.id;
      }
    });
    exports.Actor = Actor;


    var ActorDetail = Class({
      extends: Actor,
      constructor: function(name) {
        var parts = name.split("#")
        this.actorType = parts[0]
        this.detail = parts[1];
      },
      read: function(input, context) {
        return typeFor(this.actorType).read(input, context, this.detail);
      },
      write: function(input, context) {
        return typeFor(this.actorType).write(input, context, this.detail);
      }
    });
    exports.ActorDetail = ActorDetail;

    var Method = Class({
      extends: Type,
      constructor: function(descriptor) {
        this.type = descriptor.name;
        this.path = findPath(descriptor.response, "_retval");
        this.responseType = this.path && query(descriptor.response, this.path)._retval;
        this.requestType = descriptor.request.type;

        var params = [];
        for (var key in descriptor.request) {
          if (key !== "type") {
            var param = descriptor.request[key];
            var index = "_arg" in param ? param._arg : param._option;
            var isParam = param._option === index;
            var isArgument = param._arg === index;
            params[index] = {
              type: param.type,
              key: key,
              index: index,
              isParam: isParam,
              isArgument: isArgument
            };
          }
        }
        this.params = params;
      },
      read: function(input, context) {
        return read(query(input, this.path), context, this.responseType);
      },
      write: function(input, context) {
        return this.params.reduce(function(result, param) {
          result[param.key] = write(input[param.index], context, param.type);
          return result;
        }, {type: this.type});
      }
    });
    exports.Method = Method;

    var profiler = function(method, id) {
      return function() {
        var start = new Date();
        return method.apply(this, arguments).then(function(result) {
          var end = new Date();
          client.telemetry.add(id, +end - start);
          return result;
        });
      };
    };

    var destructor = function(method) {
      return function() {
        return method.apply(this, arguments).then(function(result) {
          client.release(this);
          return result;
        });
      };
    };

    function makeMethod(descriptor) {
      var type = new Method(descriptor);
      var method = descriptor.oneway ? makeUnidirecationalMethod(descriptor, type) :
                   makeBidirectionalMethod(descriptor, type);

      if (descriptor.telemetry)
        method = profiler(method);
      if (descriptor.release)
        method = destructor(method);

      return method;
    }

    var makeUnidirecationalMethod = function(descriptor, type) {
      return function() {
        var packet = type.write(arguments, this);
        packet.to = this.id;
        client.send(packet);
        return Promise.resolve(void(0));
      };
    };

    var makeBidirectionalMethod = function(descriptor, type) {
      return function() {
        var context = this.context;
        var packet = type.write(arguments, context);
        var context = this.context;
        packet.to = this.id;
        return client.request(packet).then(function(packet) {
          return type.read(packet, context);
        });
      };
    };

    var Event = Class({
      constructor: function(name, descriptor) {
        this.name = descriptor.type || name;
        this.eventType = descriptor.type || name;
        this.types = Object.create(null);

        var types = this.types;
        for (var key in descriptor) {
          if (key === "type") {
            types[key] = "string";
          } else {
            types[key] = descriptor[key].type;
          }
        }
      },
      read: function(input, context) {
        var output = {};
        var types = this.types;
        for (var key in input) {
          output[key] = read(input[key], context, types[key]);
        }
        return output;
      },
      write: function(input, context) {
        var output = {};
        var types = this.types;
        for (var key in this.types) {
          output[key] = write(input[key], context, types[key]);
        }
        return output;
      }
    });

    var Front = Class({
      extends: EventTarget,
      EventTarget: EventTarget,
      constructor: function(state) {
        this.EventTarget();
        Object.defineProperties(this,  {
          state: {
            enumerable: false,
            writable: true,
            configurable: true,
            value: state
          }
        });

        client.register(this);
      },
      get id() {
        return this.state.actor;
      },
      get context() {
        return this;
      },
      form: function(state, detail, context) {
        if (this.state !== state) {
          if (detail) {
            this.state[detail] = state[detail];
          } else {
            pairs(state).forEach(function(pair) {
              var key = pair[0], value = pair[1];
              this.state[key] = value;
            }, this);
          }
        }

        if (context) {
          client.supervise(context, this);
        }
      },
      requestTypes: function() {
        return client.request({
          to: this.id,
          type: "requestTypes"
        }).then(function(packet) {
          return packet.requestTypes;
        });
      }
    });
    types.primitive = new Primitve("primitive");
    types.string = new Primitve("string");
    types.number = new Primitve("number");
    types.boolean = new Primitve("boolean");
    types.json = new Primitve("json");
    types.array = new Primitve("array");
  },
  registerTypes: function(descriptor) {
    var specification = this.specification;
    values(descriptor.types).forEach(function(descriptor) {
      specification[descriptor.typeName] = descriptor;
    });
  }
});
exports.TypeSystem = TypeSystem;

},{"./class":3,"./event":5,"./util":26}],26:[function(_dereq_,module,exports){
"use strict";

var keys = Object.keys;
exports.keys = keys;

// Returns array of values for the given object.
var values = function(object) {
  return keys(object).map(function(key) {
    return object[key]
  });
};
exports.values = values;

// Returns [key, value] pairs for the given object.
var pairs = function(object) {
  return keys(object).map(function(key) {
    return [key, object[key]]
  });
};
exports.pairs = pairs;


// Queries an object for the field nested with in it.
var query = function(object, path) {
  return path.reduce(function(object, entry) {
    return object && object[entry]
  }, object);
};
exports.query = query;

var isObject = function(x) {
  return x && typeof(x) === "object"
}

var findPath = function(object, key) {
  var path = void(0);
  if (object && typeof(object) === "object") {
    var names = keys(object);
    if (names.indexOf(key) >= 0) {
      path = [];
    } else {
      var index = 0;
      var count = names.length;
      while (index < count && !path){
        var head = names[index];
        var tail = findPath(object[head], key);
        path = tail ? [head].concat(tail) : tail;
        index = index + 1
      }
    }
  }
  return path;
};
exports.findPath = findPath;

},{}]},{},[1])
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlcyI6WyIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vYnJvd3Nlci9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2Jyb3dzZXIvcHJvbWlzZS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2NsYXNzLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vY2xpZW50LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vZXZlbnQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvZXZlbnRzL2V2ZW50cy5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL2luZGV4LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvaXMtaW1wbGVtZW50ZWQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZC9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9hc3NpZ24vaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvYXNzaWduL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2Fzc2lnbi9zaGltLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2lzLWNhbGxhYmxlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2tleXMvaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qva2V5cy9pcy1pbXBsZW1lbnRlZC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9rZXlzL3NoaW0uanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qvbm9ybWFsaXplLW9wdGlvbnMuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvdmFsaWQtdmFsdWUuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9zdHJpbmcvIy9jb250YWlucy9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvc3RyaW5nLyMvY29udGFpbnMvc2hpbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL3BvbHlmaWxsLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vc3BlY2lmaWNhdGlvbi9jb3JlLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi90eXBlLXN5c3RlbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL3V0aWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9TQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNSQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1BBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ05BO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNQQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzdUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcmRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpfXZhciBmPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChmLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGYsZi5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsaWVudCA9IHJlcXVpcmUoXCIuLi9jbGllbnRcIikuQ2xpZW50O1xuXG5mdW5jdGlvbiBjb25uZWN0KHBvcnQpIHtcbiAgdmFyIGNsaWVudCA9IG5ldyBDbGllbnQoKTtcbiAgcmV0dXJuIGNsaWVudC5jb25uZWN0KHBvcnQpO1xufVxuZXhwb3J0cy5jb25uZWN0ID0gY29ubmVjdDtcbiIsIlwidXNlIHN0cmljdFwiO1xuXG5leHBvcnRzLlByb21pc2UgPSBQcm9taXNlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBkZXNjcmliZSA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3I7XG52YXIgQ2xhc3MgPSBmdW5jdGlvbihmaWVsZHMpIHtcbiAgdmFyIG5hbWVzID0gT2JqZWN0LmtleXMoZmllbGRzKTtcbiAgdmFyIGNvbnN0cnVjdG9yID0gbmFtZXMuaW5kZXhPZihcImNvbnN0cnVjdG9yXCIpID49IDAgPyBmaWVsZHMuY29uc3RydWN0b3IgOlxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbigpIHt9O1xuICB2YXIgYW5jZXN0b3IgPSBmaWVsZHMuZXh0ZW5kcyB8fCBPYmplY3Q7XG5cbiAgdmFyIGRlc2NyaXB0b3IgPSBuYW1lcy5yZWR1Y2UoZnVuY3Rpb24oZGVzY3JpcHRvciwga2V5KSB7XG4gICAgZGVzY3JpcHRvcltrZXldID0gZGVzY3JpYmUoZmllbGRzLCBrZXkpO1xuICAgIHJldHVybiBkZXNjcmlwdG9yO1xuICB9LCB7fSk7XG5cbiAgdmFyIHByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoYW5jZXN0b3IucHJvdG90eXBlLCBkZXNjcmlwdG9yKTtcblxuICBjb25zdHJ1Y3Rvci5wcm90b3R5cGUgPSBwcm90b3R5cGU7XG4gIHByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IGNvbnN0cnVjdG9yO1xuXG4gIHJldHVybiBjb25zdHJ1Y3Rvcjtcbn07XG5leHBvcnRzLkNsYXNzID0gQ2xhc3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG52YXIgVHlwZVN5c3RlbSA9IHJlcXVpcmUoXCIuL3R5cGUtc3lzdGVtXCIpLlR5cGVTeXN0ZW07XG52YXIgdmFsdWVzID0gcmVxdWlyZShcIi4vdXRpbFwiKS52YWx1ZXM7XG52YXIgUHJvbWlzZSA9IHJlcXVpcmUoXCJlczYtcHJvbWlzZVwiKS5Qcm9taXNlO1xudmFyIE1lc3NhZ2VFdmVudCA9IHJlcXVpcmUoXCIuL2V2ZW50XCIpLk1lc3NhZ2VFdmVudDtcblxudmFyIHNwZWNpZmljYXRpb24gPSByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL2NvcmUuanNvblwiKTtcblxuZnVuY3Rpb24gcmVjb3ZlckFjdG9yRGVzY3JpcHRpb25zKGVycm9yKSB7XG4gIGNvbnNvbGUud2FybihcIkZhaWxlZCB0byBmZXRjaCBwcm90b2NvbCBzcGVjaWZpY2F0aW9uIChzZWUgcmVhc29uIGJlbG93KS4gXCIgK1xuICAgICAgICAgICAgICAgXCJVc2luZyBhIGZhbGxiYWNrIHByb3RvY2FsIHNwZWNpZmljYXRpb24hXCIsXG4gICAgICAgICAgICAgICBlcnJvcik7XG4gIHJldHVybiByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb25cIik7XG59XG5cbi8vIFR5cGUgdG8gcmVwcmVzZW50IHN1cGVydmlzZXIgYWN0b3IgcmVsYXRpb25zIHRvIGFjdG9ycyB0aGV5IHN1cGVydmlzZVxuLy8gaW4gdGVybXMgb2YgbGlmZXRpbWUgbWFuYWdlbWVudC5cbnZhciBTdXBlcnZpc29yID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oaWQpIHtcbiAgICB0aGlzLmlkID0gaWQ7XG4gICAgdGhpcy53b3JrZXJzID0gW107XG4gIH1cbn0pO1xuXG52YXIgVGVsZW1ldHJ5ID0gQ2xhc3Moe1xuICBhZGQ6IGZ1bmN0aW9uKGlkLCBtcykge1xuICAgIGNvbnNvbGUubG9nKFwidGVsZW1ldHJ5OjpcIiwgaWQsIG1zKVxuICB9XG59KTtcblxuLy8gQ29uc2lkZXIgbWFraW5nIGNsaWVudCBhIHJvb3QgYWN0b3IuXG5cbnZhciBDbGllbnQgPSBDbGFzcyh7XG4gIGNvbnN0cnVjdG9yOiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLnJvb3QgPSBudWxsO1xuICAgIHRoaXMudGVsZW1ldHJ5ID0gbmV3IFRlbGVtZXRyeSgpO1xuXG4gICAgdGhpcy5zZXR1cENvbm5lY3Rpb24oKTtcbiAgICB0aGlzLnNldHVwTGlmZU1hbmFnZW1lbnQoKTtcbiAgICB0aGlzLnNldHVwVHlwZVN5c3RlbSgpO1xuICB9LFxuXG4gIHNldHVwQ29ubmVjdGlvbjogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5yZXF1ZXN0cyA9IFtdO1xuICB9LFxuICBzZXR1cExpZmVNYW5hZ2VtZW50OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLmNhY2hlID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdyYXBoID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdldCA9IHRoaXMuZ2V0LmJpbmQodGhpcyk7XG4gICAgdGhpcy5yZWxlYXNlID0gdGhpcy5yZWxlYXNlLmJpbmQodGhpcyk7XG4gIH0sXG4gIHNldHVwVHlwZVN5c3RlbTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy50eXBlU3lzdGVtID0gbmV3IFR5cGVTeXN0ZW0odGhpcyk7XG4gICAgdGhpcy50eXBlU3lzdGVtLnJlZ2lzdGVyVHlwZXMoc3BlY2lmaWNhdGlvbik7XG4gIH0sXG5cbiAgY29ubmVjdDogZnVuY3Rpb24ocG9ydCkge1xuICAgIHZhciBjbGllbnQgPSB0aGlzO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgIGNsaWVudC5wb3J0ID0gcG9ydDtcbiAgICAgIHBvcnQub25tZXNzYWdlID0gY2xpZW50LnJlY2VpdmUuYmluZChjbGllbnQpO1xuICAgICAgY2xpZW50Lm9uUmVhZHkgPSByZXNvbHZlO1xuICAgICAgY2xpZW50Lm9uRmFpbCA9IHJlamVjdDtcblxuICAgICAgcG9ydC5zdGFydCgpO1xuICAgIH0pO1xuICB9LFxuICBzZW5kOiBmdW5jdGlvbihwYWNrZXQpIHtcbiAgICB0aGlzLnBvcnQucG9zdE1lc3NhZ2UocGFja2V0KTtcbiAgfSxcbiAgcmVxdWVzdDogZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgdmFyIGNsaWVudCA9IHRoaXM7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgY2xpZW50LnJlcXVlc3RzLnB1c2gocGFja2V0LnRvLCB7IHJlc29sdmU6IHJlc29sdmUsIHJlamVjdDogcmVqZWN0IH0pO1xuICAgICAgY2xpZW50LnNlbmQocGFja2V0KTtcbiAgICB9KTtcbiAgfSxcblxuICByZWNlaXZlOiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBwYWNrZXQgPSBldmVudC5kYXRhO1xuICAgIGlmICghdGhpcy5yb290KSB7XG4gICAgICBpZiAocGFja2V0LmZyb20gIT09IFwicm9vdFwiKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgYmUgZnJvbSByb290XCIpO1xuICAgICAgaWYgKCEoXCJhcHBsaWNhdGlvblR5cGVcIiBpbiBwYWNrZXQpKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgY29udGFpbiBhcHBsaWNhdGlvblR5cGUgZmllbGRcIik7XG5cbiAgICAgIHRoaXMucm9vdCA9IHRoaXMudHlwZVN5c3RlbS5yZWFkKFwicm9vdFwiLCBudWxsLCBcInJvb3RcIik7XG4gICAgICB0aGlzLnJvb3RcbiAgICAgICAgICAucHJvdG9jb2xEZXNjcmlwdGlvbigpXG4gICAgICAgICAgLmNhdGNoKHJlY292ZXJBY3RvckRlc2NyaXB0aW9ucylcbiAgICAgICAgICAudGhlbih0aGlzLnR5cGVTeXN0ZW0ucmVnaXN0ZXJUeXBlcy5iaW5kKHRoaXMudHlwZVN5c3RlbSkpXG4gICAgICAgICAgLnRoZW4odGhpcy5vblJlYWR5LmJpbmQodGhpcywgdGhpcy5yb290KSwgdGhpcy5vbkZhaWwpO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgYWN0b3IgPSB0aGlzLmdldChwYWNrZXQuZnJvbSkgfHwgdGhpcy5yb290O1xuICAgICAgdmFyIGV2ZW50ID0gYWN0b3IuZXZlbnRzW3BhY2tldC50eXBlXTtcbiAgICAgIGlmIChldmVudCkge1xuICAgICAgICB2YXIgbWVzc2FnZSA9IG5ldyBNZXNzYWdlRXZlbnQocGFja2V0LnR5cGUsIHtcbiAgICAgICAgICBkYXRhOiBldmVudC5yZWFkKHBhY2tldClcbiAgICAgICAgfSk7XG4gICAgICAgIGFjdG9yLmRpc3BhdGNoRXZlbnQobWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgaW5kZXggPSB0aGlzLnJlcXVlc3RzLmluZGV4T2YoYWN0b3IuaWQpO1xuICAgICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICAgIHZhciByZXF1ZXN0ID0gdGhpcy5yZXF1ZXN0cy5zcGxpY2UoaW5kZXgsIDIpLnBvcCgpO1xuICAgICAgICAgIGlmIChwYWNrZXQuZXJyb3IpXG4gICAgICAgICAgICByZXF1ZXN0LnJlamVjdChwYWNrZXQpO1xuICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgIHJlcXVlc3QucmVzb2x2ZShwYWNrZXQpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoRXJyb3IoXCJVbmV4cGVjdGVkIHBhY2tldCBcIiArIEpTT04uc3RyaW5naWZ5KHBhY2tldCwgMiwgMikpLFxuICAgICAgICAgICAgICAgICAgICAgICAgcGFja2V0LFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5yZXF1ZXN0cy5zbGljZSgwKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH0sXG5cbiAgZ2V0OiBmdW5jdGlvbihpZCkge1xuICAgIHJldHVybiB0aGlzLmNhY2hlW2lkXTtcbiAgfSxcbiAgc3VwZXJ2aXNvck9mOiBmdW5jdGlvbihhY3Rvcikge1xuICAgIGZvciAodmFyIGlkIGluIHRoaXMuZ3JhcGgpIHtcbiAgICAgIGlmICh0aGlzLmdyYXBoW2lkXS5pbmRleE9mKGFjdG9yLmlkKSA+PSAwKSB7XG4gICAgICAgIHJldHVybiBpZDtcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIHdvcmtlcnNPZjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICByZXR1cm4gdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gIH0sXG4gIHN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG4gICAgaWYgKHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpIDwgMCkge1xuICAgICAgd29ya2Vycy5wdXNoKHdvcmtlci5pZCk7XG4gICAgfVxuICB9LFxuICB1bnN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpO1xuICAgIHZhciBpbmRleCA9IHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpXG4gICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgIHdvcmtlcnMuc3BsaWNlKGluZGV4LCAxKVxuICAgIH1cbiAgfSxcblxuICByZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICB2YXIgcmVnaXN0ZXJlZCA9IHRoaXMuZ2V0KGFjdG9yLmlkKTtcbiAgICBpZiAoIXJlZ2lzdGVyZWQpIHtcbiAgICAgIHRoaXMuY2FjaGVbYWN0b3IuaWRdID0gYWN0b3I7XG4gICAgICB0aGlzLmdyYXBoW2FjdG9yLmlkXSA9IFtdO1xuICAgIH0gZWxzZSBpZiAocmVnaXN0ZXJlZCAhPT0gYWN0b3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkRpZmZlcmVudCBhY3RvciB3aXRoIHNhbWUgaWQgaXMgYWxyZWFkeSByZWdpc3RlcmVkXCIpO1xuICAgIH1cbiAgfSxcbiAgdW5yZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICBpZiAodGhpcy5nZXQoYWN0b3IuaWQpKSB7XG4gICAgICBkZWxldGUgdGhpcy5jYWNoZVthY3Rvci5pZF07XG4gICAgICBkZWxldGUgdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gICAgfVxuICB9LFxuXG4gIHJlbGVhc2U6IGZ1bmN0aW9uKGFjdG9yKSB7XG4gICAgdmFyIHN1cGVydmlzb3IgPSB0aGlzLnN1cGVydmlzb3JPZihhY3Rvcik7XG4gICAgaWYgKHN1cGVydmlzb3IpXG4gICAgICB0aGlzLnVuc3VwZXJ2aXNlKHN1cGVydmlzb3IsIGFjdG9yKTtcblxuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG5cbiAgICBpZiAod29ya2Vycykge1xuICAgICAgd29ya2Vycy5tYXAodGhpcy5nZXQpLmZvckVhY2godGhpcy5yZWxlYXNlKVxuICAgIH1cbiAgICB0aGlzLnVucmVnaXN0ZXIoYWN0b3IpO1xuICB9XG59KTtcbmV4cG9ydHMuQ2xpZW50ID0gQ2xpZW50O1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBTeW1ib2wgPSByZXF1aXJlKFwiZXM2LXN5bWJvbFwiKVxudmFyIEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoXCJldmVudHNcIikuRXZlbnRFbWl0dGVyO1xudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG5cbnZhciAkYm91bmQgPSBTeW1ib2woXCJFdmVudFRhcmdldC9oYW5kbGVFdmVudFwiKTtcbnZhciAkZW1pdHRlciA9IFN5bWJvbChcIkV2ZW50VGFyZ2V0L2VtaXR0ZXJcIik7XG5cbmZ1bmN0aW9uIG1ha2VIYW5kbGVyKGhhbmRsZXIpIHtcbiAgcmV0dXJuIGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgaGFuZGxlci5oYW5kbGVFdmVudChldmVudCk7XG4gIH1cbn1cblxudmFyIEV2ZW50VGFyZ2V0ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsICRlbWl0dGVyLCB7XG4gICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgICAgdmFsdWU6IG5ldyBFdmVudEVtaXR0ZXIoKVxuICAgIH0pO1xuICB9LFxuICBhZGRFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICB0aGlzWyRlbWl0dGVyXS5vbih0eXBlLCBoYW5kbGVyKTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiB0eXBlb2YoaGFuZGxlcikgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgIGlmICghaGFuZGxlclskYm91bmRdKSBoYW5kbGVyWyRib3VuZF0gPSBtYWtlSGFuZGxlcihoYW5kbGVyKTtcbiAgICAgIHRoaXNbJGVtaXR0ZXJdLm9uKHR5cGUsIGhhbmRsZXJbJGJvdW5kXSk7XG4gICAgfVxuICB9LFxuICByZW1vdmVFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKVxuICAgICAgdGhpc1skZW1pdHRlcl0ucmVtb3ZlTGlzdGVuZXIodHlwZSwgaGFuZGxlcik7XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiBoYW5kbGVyWyRib3VuZF0pXG4gICAgICB0aGlzWyRlbWl0dGVyXS5yZW1vdmVMaXN0ZW5lcih0eXBlLCBoYW5kbGVyWyRib3VuZF0pO1xuICB9LFxuICBkaXNwYXRjaEV2ZW50OiBmdW5jdGlvbihldmVudCkge1xuICAgIGV2ZW50LnRhcmdldCA9IHRoaXM7XG4gICAgdGhpc1skZW1pdHRlcl0uZW1pdChldmVudC50eXBlLCBldmVudCk7XG4gIH1cbn0pO1xuZXhwb3J0cy5FdmVudFRhcmdldCA9IEV2ZW50VGFyZ2V0O1xuXG52YXIgTWVzc2FnZUV2ZW50ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSwgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuICAgIHRoaXMudHlwZSA9IHR5cGU7XG4gICAgdGhpcy5kYXRhID0gb3B0aW9ucy5kYXRhID09PSB2b2lkKDApID8gbnVsbCA6IG9wdGlvbnMuZGF0YTtcblxuICAgIHRoaXMubGFzdEV2ZW50SWQgPSBvcHRpb25zLmxhc3RFdmVudElkIHx8IFwiXCI7XG4gICAgdGhpcy5vcmlnaW4gPSBvcHRpb25zLm9yaWdpbiB8fCBcIlwiO1xuICAgIHRoaXMuYnViYmxlcyA9IG9wdGlvbnMuYnViYmxlcyB8fCBmYWxzZTtcbiAgICB0aGlzLmNhbmNlbGFibGUgPSBvcHRpb25zLmNhbmNlbGFibGUgfHwgZmFsc2U7XG4gIH0sXG4gIHNvdXJjZTogbnVsbCxcbiAgcG9ydHM6IG51bGwsXG4gIHByZXZlbnREZWZhdWx0OiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfVxufSk7XG5leHBvcnRzLk1lc3NhZ2VFdmVudCA9IE1lc3NhZ2VFdmVudDtcbiIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG5mdW5jdGlvbiBFdmVudEVtaXR0ZXIoKSB7XG4gIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gdGhpcy5fbWF4TGlzdGVuZXJzIHx8IHVuZGVmaW5lZDtcbn1cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyO1xuXG4vLyBCYWNrd2FyZHMtY29tcGF0IHdpdGggbm9kZSAwLjEwLnhcbkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX2V2ZW50cyA9IHVuZGVmaW5lZDtcbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDtcblxuLy8gQnkgZGVmYXVsdCBFdmVudEVtaXR0ZXJzIHdpbGwgcHJpbnQgYSB3YXJuaW5nIGlmIG1vcmUgdGhhbiAxMCBsaXN0ZW5lcnMgYXJlXG4vLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLlxuRXZlbnRFbWl0dGVyLmRlZmF1bHRNYXhMaXN0ZW5lcnMgPSAxMDtcblxuLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzXG4vLyB0aGF0IHRvIGJlIGluY3JlYXNlZC4gU2V0IHRvIHplcm8gZm9yIHVubGltaXRlZC5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuc2V0TWF4TGlzdGVuZXJzID0gZnVuY3Rpb24obikge1xuICBpZiAoIWlzTnVtYmVyKG4pIHx8IG4gPCAwIHx8IGlzTmFOKG4pKVxuICAgIHRocm93IFR5cGVFcnJvcignbiBtdXN0IGJlIGEgcG9zaXRpdmUgbnVtYmVyJyk7XG4gIHRoaXMuX21heExpc3RlbmVycyA9IG47XG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzKVxuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuXG4gIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy5cbiAgaWYgKHR5cGUgPT09ICdlcnJvcicpIHtcbiAgICBpZiAoIXRoaXMuX2V2ZW50cy5lcnJvciB8fFxuICAgICAgICAoaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkpIHtcbiAgICAgIGVyID0gYXJndW1lbnRzWzFdO1xuICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXI7IC8vIFVuaGFuZGxlZCAnZXJyb3InIGV2ZW50XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQuJyk7XG4gICAgICB9XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlciA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpXG4gICAgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGhhbmRsZXIpKSB7XG4gICAgc3dpdGNoIChhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgICAvLyBmYXN0IGNhc2VzXG4gICAgICBjYXNlIDE6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDI6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMzpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSwgYXJndW1lbnRzWzJdKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICAvLyBzbG93ZXJcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxlbiA9IGFyZ3VtZW50cy5sZW5ndGg7XG4gICAgICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgICAgIGZvciAoaSA9IDE7IGkgPCBsZW47IGkrKylcbiAgICAgICAgICBhcmdzW2kgLSAxXSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgICAgaGFuZGxlci5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAoaXNPYmplY3QoaGFuZGxlcikpIHtcbiAgICBsZW4gPSBhcmd1bWVudHMubGVuZ3RoO1xuICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgZm9yIChpID0gMTsgaSA8IGxlbjsgaSsrKVxuICAgICAgYXJnc1tpIC0gMV0gPSBhcmd1bWVudHNbaV07XG5cbiAgICBsaXN0ZW5lcnMgPSBoYW5kbGVyLnNsaWNlKCk7XG4gICAgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDtcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspXG4gICAgICBsaXN0ZW5lcnNbaV0uYXBwbHkodGhpcywgYXJncyk7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXIgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICB2YXIgbTtcblxuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgdGhpcy5fZXZlbnRzID0ge307XG5cbiAgLy8gVG8gYXZvaWQgcmVjdXJzaW9uIGluIHRoZSBjYXNlIHRoYXQgdHlwZSA9PT0gXCJuZXdMaXN0ZW5lclwiISBCZWZvcmVcbiAgLy8gYWRkaW5nIGl0IHRvIHRoZSBsaXN0ZW5lcnMsIGZpcnN0IGVtaXQgXCJuZXdMaXN0ZW5lclwiLlxuICBpZiAodGhpcy5fZXZlbnRzLm5ld0xpc3RlbmVyKVxuICAgIHRoaXMuZW1pdCgnbmV3TGlzdGVuZXInLCB0eXBlLFxuICAgICAgICAgICAgICBpc0Z1bmN0aW9uKGxpc3RlbmVyLmxpc3RlbmVyKSA/XG4gICAgICAgICAgICAgIGxpc3RlbmVyLmxpc3RlbmVyIDogbGlzdGVuZXIpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIC8vIE9wdGltaXplIHRoZSBjYXNlIG9mIG9uZSBsaXN0ZW5lci4gRG9uJ3QgbmVlZCB0aGUgZXh0cmEgYXJyYXkgb2JqZWN0LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IGxpc3RlbmVyO1xuICBlbHNlIGlmIChpc09iamVjdCh0aGlzLl9ldmVudHNbdHlwZV0pKVxuICAgIC8vIElmIHdlJ3ZlIGFscmVhZHkgZ290IGFuIGFycmF5LCBqdXN0IGFwcGVuZC5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gIGVsc2VcbiAgICAvLyBBZGRpbmcgdGhlIHNlY29uZCBlbGVtZW50LCBuZWVkIHRvIGNoYW5nZSB0byBhcnJheS5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0gPSBbdGhpcy5fZXZlbnRzW3R5cGVdLCBsaXN0ZW5lcl07XG5cbiAgLy8gQ2hlY2sgZm9yIGxpc3RlbmVyIGxlYWtcbiAgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkgJiYgIXRoaXMuX2V2ZW50c1t0eXBlXS53YXJuZWQpIHtcbiAgICB2YXIgbTtcbiAgICBpZiAoIWlzVW5kZWZpbmVkKHRoaXMuX21heExpc3RlbmVycykpIHtcbiAgICAgIG0gPSB0aGlzLl9tYXhMaXN0ZW5lcnM7XG4gICAgfSBlbHNlIHtcbiAgICAgIG0gPSBFdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVycztcbiAgICB9XG5cbiAgICBpZiAobSAmJiBtID4gMCAmJiB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoID4gbSkge1xuICAgICAgdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCA9IHRydWU7XG4gICAgICBjb25zb2xlLmVycm9yKCcobm9kZSkgd2FybmluZzogcG9zc2libGUgRXZlbnRFbWl0dGVyIG1lbW9yeSAnICtcbiAgICAgICAgICAgICAgICAgICAgJ2xlYWsgZGV0ZWN0ZWQuICVkIGxpc3RlbmVycyBhZGRlZC4gJyArXG4gICAgICAgICAgICAgICAgICAgICdVc2UgZW1pdHRlci5zZXRNYXhMaXN0ZW5lcnMoKSB0byBpbmNyZWFzZSBsaW1pdC4nLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoKTtcbiAgICAgIGlmICh0eXBlb2YgY29uc29sZS50cmFjZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAvLyBub3Qgc3VwcG9ydGVkIGluIElFIDEwXG4gICAgICAgIGNvbnNvbGUudHJhY2UoKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub24gPSBFdmVudEVtaXR0ZXIucHJvdG90eXBlLmFkZExpc3RlbmVyO1xuXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLm9uY2UgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgdmFyIGZpcmVkID0gZmFsc2U7XG5cbiAgZnVuY3Rpb24gZygpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGcpO1xuXG4gICAgaWYgKCFmaXJlZCkge1xuICAgICAgZmlyZWQgPSB0cnVlO1xuICAgICAgbGlzdGVuZXIuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICB9XG4gIH1cblxuICBnLmxpc3RlbmVyID0gbGlzdGVuZXI7XG4gIHRoaXMub24odHlwZSwgZyk7XG5cbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vLyBlbWl0cyBhICdyZW1vdmVMaXN0ZW5lcicgZXZlbnQgaWZmIHRoZSBsaXN0ZW5lciB3YXMgcmVtb3ZlZFxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBsaXN0LCBwb3NpdGlvbiwgbGVuZ3RoLCBpO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldHVybiB0aGlzO1xuXG4gIGxpc3QgPSB0aGlzLl9ldmVudHNbdHlwZV07XG4gIGxlbmd0aCA9IGxpc3QubGVuZ3RoO1xuICBwb3NpdGlvbiA9IC0xO1xuXG4gIGlmIChsaXN0ID09PSBsaXN0ZW5lciB8fFxuICAgICAgKGlzRnVuY3Rpb24obGlzdC5saXN0ZW5lcikgJiYgbGlzdC5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcblxuICB9IGVsc2UgaWYgKGlzT2JqZWN0KGxpc3QpKSB7XG4gICAgZm9yIChpID0gbGVuZ3RoOyBpLS0gPiAwOykge1xuICAgICAgaWYgKGxpc3RbaV0gPT09IGxpc3RlbmVyIHx8XG4gICAgICAgICAgKGxpc3RbaV0ubGlzdGVuZXIgJiYgbGlzdFtpXS5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgICAgIHBvc2l0aW9uID0gaTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHBvc2l0aW9uIDwgMClcbiAgICAgIHJldHVybiB0aGlzO1xuXG4gICAgaWYgKGxpc3QubGVuZ3RoID09PSAxKSB7XG4gICAgICBsaXN0Lmxlbmd0aCA9IDA7XG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIH0gZWxzZSB7XG4gICAgICBsaXN0LnNwbGljZShwb3NpdGlvbiwgMSk7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcilcbiAgICAgIHRoaXMuZW1pdCgncmVtb3ZlTGlzdGVuZXInLCB0eXBlLCBsaXN0ZW5lcik7XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUucmVtb3ZlQWxsTGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIga2V5LCBsaXN0ZW5lcnM7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgLy8gbm90IGxpc3RlbmluZyBmb3IgcmVtb3ZlTGlzdGVuZXIsIG5vIG5lZWQgdG8gZW1pdFxuICBpZiAoIXRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcikge1xuICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKVxuICAgICAgdGhpcy5fZXZlbnRzID0ge307XG4gICAgZWxzZSBpZiAodGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIC8vIGVtaXQgcmVtb3ZlTGlzdGVuZXIgZm9yIGFsbCBsaXN0ZW5lcnMgb24gYWxsIGV2ZW50c1xuICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIGZvciAoa2V5IGluIHRoaXMuX2V2ZW50cykge1xuICAgICAgaWYgKGtleSA9PT0gJ3JlbW92ZUxpc3RlbmVyJykgY29udGludWU7XG4gICAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycyhrZXkpO1xuICAgIH1cbiAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycygncmVtb3ZlTGlzdGVuZXInKTtcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNGdW5jdGlvbihsaXN0ZW5lcnMpKSB7XG4gICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnMpO1xuICB9IGVsc2Uge1xuICAgIC8vIExJRk8gb3JkZXJcbiAgICB3aGlsZSAobGlzdGVuZXJzLmxlbmd0aClcbiAgICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgbGlzdGVuZXJzW2xpc3RlbmVycy5sZW5ndGggLSAxXSk7XG4gIH1cbiAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUubGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IFtdO1xuICBlbHNlIGlmIChpc0Z1bmN0aW9uKHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgcmV0ID0gW3RoaXMuX2V2ZW50c1t0eXBlXV07XG4gIGVsc2VcbiAgICByZXQgPSB0aGlzLl9ldmVudHNbdHlwZV0uc2xpY2UoKTtcbiAgcmV0dXJuIHJldDtcbn07XG5cbkV2ZW50RW1pdHRlci5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24oZW1pdHRlciwgdHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIWVtaXR0ZXIuX2V2ZW50cyB8fCAhZW1pdHRlci5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IDA7XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24oZW1pdHRlci5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSAxO1xuICBlbHNlXG4gICAgcmV0ID0gZW1pdHRlci5fZXZlbnRzW3R5cGVdLmxlbmd0aDtcbiAgcmV0dXJuIHJldDtcbn07XG5cbmZ1bmN0aW9uIGlzRnVuY3Rpb24oYXJnKSB7XG4gIHJldHVybiB0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nO1xufVxuXG5mdW5jdGlvbiBpc051bWJlcihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInO1xufVxuXG5mdW5jdGlvbiBpc09iamVjdChhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdvYmplY3QnICYmIGFyZyAhPT0gbnVsbDtcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQoYXJnKSB7XG4gIHJldHVybiBhcmcgPT09IHZvaWQgMDtcbn1cbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKSA/IFN5bWJvbCA6IHJlcXVpcmUoJy4vcG9seWZpbGwnKTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdHZhciBzeW1ib2w7XG5cdGlmICh0eXBlb2YgU3ltYm9sICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHN5bWJvbCA9IFN5bWJvbCgndGVzdCBzeW1ib2wnKTtcblx0dHJ5IHtcblx0XHRpZiAoU3RyaW5nKHN5bWJvbCkgIT09ICdTeW1ib2wgKHRlc3Qgc3ltYm9sKScpIHJldHVybiBmYWxzZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgPT09ICdzeW1ib2wnKSByZXR1cm4gdHJ1ZTtcblxuXHQvLyBSZXR1cm4gJ3RydWUnIGZvciBwb2x5ZmlsbHNcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXNDb25jYXRTcHJlYWRhYmxlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC5pc1JlZ0V4cCAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cdGlmICh0eXBlb2YgU3ltYm9sLnRvUHJpbWl0aXZlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC50b1N0cmluZ1RhZyAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wudW5zY29wYWJsZXMgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cblx0cmV0dXJuIHRydWU7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYXNzaWduICAgICAgICA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L2Fzc2lnbicpXG4gICwgbm9ybWFsaXplT3B0cyA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L25vcm1hbGl6ZS1vcHRpb25zJylcbiAgLCBpc0NhbGxhYmxlICAgID0gcmVxdWlyZSgnZXM1LWV4dC9vYmplY3QvaXMtY2FsbGFibGUnKVxuICAsIGNvbnRhaW5zICAgICAgPSByZXF1aXJlKCdlczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zJylcblxuICAsIGQ7XG5cbmQgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChkc2NyLCB2YWx1ZS8qLCBvcHRpb25zKi8pIHtcblx0dmFyIGMsIGUsIHcsIG9wdGlvbnMsIGRlc2M7XG5cdGlmICgoYXJndW1lbnRzLmxlbmd0aCA8IDIpIHx8ICh0eXBlb2YgZHNjciAhPT0gJ3N0cmluZycpKSB7XG5cdFx0b3B0aW9ucyA9IHZhbHVlO1xuXHRcdHZhbHVlID0gZHNjcjtcblx0XHRkc2NyID0gbnVsbDtcblx0fSBlbHNlIHtcblx0XHRvcHRpb25zID0gYXJndW1lbnRzWzJdO1xuXHR9XG5cdGlmIChkc2NyID09IG51bGwpIHtcblx0XHRjID0gdyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0XHR3ID0gY29udGFpbnMuY2FsbChkc2NyLCAndycpO1xuXHR9XG5cblx0ZGVzYyA9IHsgdmFsdWU6IHZhbHVlLCBjb25maWd1cmFibGU6IGMsIGVudW1lcmFibGU6IGUsIHdyaXRhYmxlOiB3IH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuXG5kLmdzID0gZnVuY3Rpb24gKGRzY3IsIGdldCwgc2V0LyosIG9wdGlvbnMqLykge1xuXHR2YXIgYywgZSwgb3B0aW9ucywgZGVzYztcblx0aWYgKHR5cGVvZiBkc2NyICE9PSAnc3RyaW5nJykge1xuXHRcdG9wdGlvbnMgPSBzZXQ7XG5cdFx0c2V0ID0gZ2V0O1xuXHRcdGdldCA9IGRzY3I7XG5cdFx0ZHNjciA9IG51bGw7XG5cdH0gZWxzZSB7XG5cdFx0b3B0aW9ucyA9IGFyZ3VtZW50c1szXTtcblx0fVxuXHRpZiAoZ2V0ID09IG51bGwpIHtcblx0XHRnZXQgPSB1bmRlZmluZWQ7XG5cdH0gZWxzZSBpZiAoIWlzQ2FsbGFibGUoZ2V0KSkge1xuXHRcdG9wdGlvbnMgPSBnZXQ7XG5cdFx0Z2V0ID0gc2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKHNldCA9PSBudWxsKSB7XG5cdFx0c2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKCFpc0NhbGxhYmxlKHNldCkpIHtcblx0XHRvcHRpb25zID0gc2V0O1xuXHRcdHNldCA9IHVuZGVmaW5lZDtcblx0fVxuXHRpZiAoZHNjciA9PSBudWxsKSB7XG5cdFx0YyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0fVxuXG5cdGRlc2MgPSB7IGdldDogZ2V0LCBzZXQ6IHNldCwgY29uZmlndXJhYmxlOiBjLCBlbnVtZXJhYmxlOiBlIH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vaXMtaW1wbGVtZW50ZWQnKSgpXG5cdD8gT2JqZWN0LmFzc2lnblxuXHQ6IHJlcXVpcmUoJy4vc2hpbScpO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcblx0dmFyIGFzc2lnbiA9IE9iamVjdC5hc3NpZ24sIG9iajtcblx0aWYgKHR5cGVvZiBhc3NpZ24gIT09ICdmdW5jdGlvbicpIHJldHVybiBmYWxzZTtcblx0b2JqID0geyBmb286ICdyYXonIH07XG5cdGFzc2lnbihvYmosIHsgYmFyOiAnZHdhJyB9LCB7IHRyenk6ICd0cnp5JyB9KTtcblx0cmV0dXJuIChvYmouZm9vICsgb2JqLmJhciArIG9iai50cnp5KSA9PT0gJ3JhemR3YXRyenknO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGtleXMgID0gcmVxdWlyZSgnLi4va2V5cycpXG4gICwgdmFsdWUgPSByZXF1aXJlKCcuLi92YWxpZC12YWx1ZScpXG5cbiAgLCBtYXggPSBNYXRoLm1heDtcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoZGVzdCwgc3JjLyosIOKApnNyY24qLykge1xuXHR2YXIgZXJyb3IsIGksIGwgPSBtYXgoYXJndW1lbnRzLmxlbmd0aCwgMiksIGFzc2lnbjtcblx0ZGVzdCA9IE9iamVjdCh2YWx1ZShkZXN0KSk7XG5cdGFzc2lnbiA9IGZ1bmN0aW9uIChrZXkpIHtcblx0XHR0cnkgeyBkZXN0W2tleV0gPSBzcmNba2V5XTsgfSBjYXRjaCAoZSkge1xuXHRcdFx0aWYgKCFlcnJvcikgZXJyb3IgPSBlO1xuXHRcdH1cblx0fTtcblx0Zm9yIChpID0gMTsgaSA8IGw7ICsraSkge1xuXHRcdHNyYyA9IGFyZ3VtZW50c1tpXTtcblx0XHRrZXlzKHNyYykuZm9yRWFjaChhc3NpZ24pO1xuXHR9XG5cdGlmIChlcnJvciAhPT0gdW5kZWZpbmVkKSB0aHJvdyBlcnJvcjtcblx0cmV0dXJuIGRlc3Q7XG59O1xuIiwiLy8gRGVwcmVjYXRlZFxuXG4ndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iaikgeyByZXR1cm4gdHlwZW9mIG9iaiA9PT0gJ2Z1bmN0aW9uJzsgfTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IE9iamVjdC5rZXlzXG5cdDogcmVxdWlyZSgnLi9zaGltJyk7XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKCkge1xuXHR0cnkge1xuXHRcdE9iamVjdC5rZXlzKCdwcmltaXRpdmUnKTtcblx0XHRyZXR1cm4gdHJ1ZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBrZXlzID0gT2JqZWN0LmtleXM7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iamVjdCkge1xuXHRyZXR1cm4ga2V5cyhvYmplY3QgPT0gbnVsbCA/IG9iamVjdCA6IE9iamVjdChvYmplY3QpKTtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBhc3NpZ24gPSByZXF1aXJlKCcuL2Fzc2lnbicpXG5cbiAgLCBmb3JFYWNoID0gQXJyYXkucHJvdG90eXBlLmZvckVhY2hcbiAgLCBjcmVhdGUgPSBPYmplY3QuY3JlYXRlLCBnZXRQcm90b3R5cGVPZiA9IE9iamVjdC5nZXRQcm90b3R5cGVPZlxuXG4gICwgcHJvY2VzcztcblxucHJvY2VzcyA9IGZ1bmN0aW9uIChzcmMsIG9iaikge1xuXHR2YXIgcHJvdG8gPSBnZXRQcm90b3R5cGVPZihzcmMpO1xuXHRyZXR1cm4gYXNzaWduKHByb3RvID8gcHJvY2Vzcyhwcm90bywgb2JqKSA6IG9iaiwgc3JjKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9wdGlvbnMvKiwg4oCmb3B0aW9ucyovKSB7XG5cdHZhciByZXN1bHQgPSBjcmVhdGUobnVsbCk7XG5cdGZvckVhY2guY2FsbChhcmd1bWVudHMsIGZ1bmN0aW9uIChvcHRpb25zKSB7XG5cdFx0aWYgKG9wdGlvbnMgPT0gbnVsbCkgcmV0dXJuO1xuXHRcdHByb2Nlc3MoT2JqZWN0KG9wdGlvbnMpLCByZXN1bHQpO1xuXHR9KTtcblx0cmV0dXJuIHJlc3VsdDtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHZhbHVlKSB7XG5cdGlmICh2YWx1ZSA9PSBudWxsKSB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2Fubm90IHVzZSBudWxsIG9yIHVuZGVmaW5lZFwiKTtcblx0cmV0dXJuIHZhbHVlO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IFN0cmluZy5wcm90b3R5cGUuY29udGFpbnNcblx0OiByZXF1aXJlKCcuL3NoaW0nKTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIHN0ciA9ICdyYXpkd2F0cnp5JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdGlmICh0eXBlb2Ygc3RyLmNvbnRhaW5zICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHJldHVybiAoKHN0ci5jb250YWlucygnZHdhJykgPT09IHRydWUpICYmIChzdHIuY29udGFpbnMoJ2ZvbycpID09PSBmYWxzZSkpO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGluZGV4T2YgPSBTdHJpbmcucHJvdG90eXBlLmluZGV4T2Y7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHNlYXJjaFN0cmluZy8qLCBwb3NpdGlvbiovKSB7XG5cdHJldHVybiBpbmRleE9mLmNhbGwodGhpcywgc2VhcmNoU3RyaW5nLCBhcmd1bWVudHNbMV0pID4gLTE7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgZCA9IHJlcXVpcmUoJ2QnKVxuXG4gICwgY3JlYXRlID0gT2JqZWN0LmNyZWF0ZSwgZGVmaW5lUHJvcGVydGllcyA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzXG4gICwgZ2VuZXJhdGVOYW1lLCBTeW1ib2w7XG5cbmdlbmVyYXRlTmFtZSA9IChmdW5jdGlvbiAoKSB7XG5cdHZhciBjcmVhdGVkID0gY3JlYXRlKG51bGwpO1xuXHRyZXR1cm4gZnVuY3Rpb24gKGRlc2MpIHtcblx0XHR2YXIgcG9zdGZpeCA9IDA7XG5cdFx0d2hpbGUgKGNyZWF0ZWRbZGVzYyArIChwb3N0Zml4IHx8ICcnKV0pICsrcG9zdGZpeDtcblx0XHRkZXNjICs9IChwb3N0Zml4IHx8ICcnKTtcblx0XHRjcmVhdGVkW2Rlc2NdID0gdHJ1ZTtcblx0XHRyZXR1cm4gJ0BAJyArIGRlc2M7XG5cdH07XG59KCkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFN5bWJvbCA9IGZ1bmN0aW9uIChkZXNjcmlwdGlvbikge1xuXHR2YXIgc3ltYm9sO1xuXHRpZiAodGhpcyBpbnN0YW5jZW9mIFN5bWJvbCkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ1R5cGVFcnJvcjogU3ltYm9sIGlzIG5vdCBhIGNvbnN0cnVjdG9yJyk7XG5cdH1cblx0c3ltYm9sID0gY3JlYXRlKFN5bWJvbC5wcm90b3R5cGUpO1xuXHRkZXNjcmlwdGlvbiA9IChkZXNjcmlwdGlvbiA9PT0gdW5kZWZpbmVkID8gJycgOiBTdHJpbmcoZGVzY3JpcHRpb24pKTtcblx0cmV0dXJuIGRlZmluZVByb3BlcnRpZXMoc3ltYm9sLCB7XG5cdFx0X19kZXNjcmlwdGlvbl9fOiBkKCcnLCBkZXNjcmlwdGlvbiksXG5cdFx0X19uYW1lX186IGQoJycsIGdlbmVyYXRlTmFtZShkZXNjcmlwdGlvbikpXG5cdH0pO1xufTtcblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoU3ltYm9sLCB7XG5cdGNyZWF0ZTogZCgnJywgU3ltYm9sKCdjcmVhdGUnKSksXG5cdGhhc0luc3RhbmNlOiBkKCcnLCBTeW1ib2woJ2hhc0luc3RhbmNlJykpLFxuXHRpc0NvbmNhdFNwcmVhZGFibGU6IGQoJycsIFN5bWJvbCgnaXNDb25jYXRTcHJlYWRhYmxlJykpLFxuXHRpc1JlZ0V4cDogZCgnJywgU3ltYm9sKCdpc1JlZ0V4cCcpKSxcblx0aXRlcmF0b3I6IGQoJycsIFN5bWJvbCgnaXRlcmF0b3InKSksXG5cdHRvUHJpbWl0aXZlOiBkKCcnLCBTeW1ib2woJ3RvUHJpbWl0aXZlJykpLFxuXHR0b1N0cmluZ1RhZzogZCgnJywgU3ltYm9sKCd0b1N0cmluZ1RhZycpKSxcblx0dW5zY29wYWJsZXM6IGQoJycsIFN5bWJvbCgndW5zY29wYWJsZXMnKSlcbn0pO1xuXG5kZWZpbmVQcm9wZXJ0aWVzKFN5bWJvbC5wcm90b3R5cGUsIHtcblx0cHJvcGVyVG9TdHJpbmc6IGQoZnVuY3Rpb24gKCkge1xuXHRcdHJldHVybiAnU3ltYm9sICgnICsgdGhpcy5fX2Rlc2NyaXB0aW9uX18gKyAnKSc7XG5cdH0pLFxuXHR0b1N0cmluZzogZCgnJywgZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpcy5fX25hbWVfXzsgfSlcbn0pO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1ByaW1pdGl2ZSwgZCgnJyxcblx0ZnVuY3Rpb24gKGhpbnQpIHtcblx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ29udmVyc2lvbiBvZiBzeW1ib2wgb2JqZWN0cyBpcyBub3QgYWxsb3dlZFwiKTtcblx0fSkpO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1N0cmluZ1RhZywgZCgnYycsICdTeW1ib2wnKSk7XG4iLCJtb2R1bGUuZXhwb3J0cz17XG4gIFwidHlwZXNcIjoge1xuICAgIFwicm9vdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJyb290XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZWNob1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInN0cmluZ1wiOiB7IFwiX2FyZ1wiOiAwLCBcInR5cGVcIjogXCJzdHJpbmdcIiB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3RyaW5nXCI6IHsgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCIgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImxpc3RUYWJzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHt9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjogeyBcIl9yZXR2YWxcIjogXCJ0YWJsaXN0XCIgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicHJvdG9jb2xEZXNjcmlwdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7fSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHsgXCJfcmV0dmFsXCI6IFwianNvblwiIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJ0YWJMaXN0Q2hhbmdlZFwiOiB7fVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJ0YWJsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwidGFibGlzdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNlbGVjdGVkXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwidGFic1wiOiBcImFycmF5OnRhYlwiLFxuICAgICAgICBcInVybFwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImNvbnNvbGVBY3RvclwiOiBcImNvbnNvbGVcIixcbiAgICAgICAgXCJpbnNwZWN0b3JBY3RvclwiOiBcImluc3BlY3RvclwiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuICAgICAgICBcIm1lbW9yeUFjdG9yXCI6IFwibWVtb3J5XCIsXG4gICAgICAgIFwiZXZlbnRMb29wTGFnQWN0b3JcIjogXCJldmVudExvb3BMYWdcIixcbiAgICAgICAgXCJwcmVmZXJlbmNlQWN0b3JcIjogXCJwcmVmZXJlbmNlXCIsXG4gICAgICAgIFwiZGV2aWNlQWN0b3JcIjogXCJkZXZpY2VcIixcblxuICAgICAgICBcInByb2ZpbGVyQWN0b3JcIjogXCJwcm9maWxlclwiLFxuICAgICAgICBcImNocm9tZURlYnVnZ2VyXCI6IFwiY2hyb21lRGVidWdnZXJcIixcbiAgICAgICAgXCJ3ZWJhcHBzQWN0b3JcIjogXCJ3ZWJhcHBzXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwidGFiXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInRhYlwiLFxuICAgICAgXCJmaWVsZHNcIjoge1xuICAgICAgICBcInRpdGxlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidXJsXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwib3V0ZXJXaW5kb3dJRFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImluc3BlY3RvckFjdG9yXCI6IFwiaW5zcGVjdG9yXCIsXG4gICAgICAgIFwiY2FsbFdhdGNoZXJBY3RvclwiOiBcImNhbGwtd2F0Y2hlclwiLFxuICAgICAgICBcImNhbnZhc0FjdG9yXCI6IFwiY2FudmFzXCIsXG4gICAgICAgIFwid2ViZ2xBY3RvclwiOiBcIndlYmdsXCIsXG4gICAgICAgIFwid2ViYXVkaW9BY3RvclwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICAgIFwic3RvcmFnZUFjdG9yXCI6IFwic3RvcmFnZVwiLFxuICAgICAgICBcImdjbGlBY3RvclwiOiBcImdjbGlcIixcbiAgICAgICAgXCJtZW1vcnlBY3RvclwiOiBcIm1lbW9yeVwiLFxuICAgICAgICBcImV2ZW50TG9vcExhZ1wiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuXG4gICAgICAgIFwiY29uc29sZUFjdG9yXCI6IFwiY29uc29sZVwiLFxuICAgICAgICBcInRyYWNlQWN0b3JcIjogXCJ0cmFjZVwiXG4gICAgICB9LFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhdHRhY2hcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge30sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7IFwiX3JldHZhbFwiOiBcImpzb25cIiB9XG4gICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInRhYk5hdmlnYXRlZFwiOiB7XG4gICAgICAgICAgIFwidHlwZU5hbWVcIjogXCJ0YWJOYXZpZ2F0ZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImNvbnNvbGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY29uc29sZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImV2YWx1YXRlSlNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ1cmxcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImJpbmRPYmplY3RBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZnJhbWVBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwic2VsZWN0ZWROb2RlQWN0b3JcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZXZhbHVhdGVqc3Jlc3BvbnNlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJldmFsdWF0ZWpzcmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJldmFsdWF0ZWpzcmVzcG9uc2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJyZXN1bHRcIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25cIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25NZXNzYWdlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiaW5wdXRcIjogXCJzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib2JqZWN0XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICAge1xuICAgICAgICAgICBcIm5hbWVcIjogXCJwcm9wZXJ0eVwiLFxuICAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfSxcbiAgICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICAgIFwiZGVzY3JpcHRvclwiOiB7XG4gICAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfVxuICAgICAgICAgfVxuICAgICAgXVxuICAgIH1cbiAgfVxufVxuIiwibW9kdWxlLmV4cG9ydHM9e1xuICBcInR5cGVzXCI6IHtcbiAgICBcImxvbmdzdHJhY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJsb25nc3RyYWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdWJzdHJpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3Vic3RyaW5nXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWJzdHJpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJlbGVhc2VcIixcbiAgICAgICAgICBcInJlbGVhc2VcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlc2hlZXRcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZGlzYWJsZWRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPcmlnaW5hbFNvdXJjZXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0T3JpZ2luYWxTb3VyY2VzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJvcmlnaW5hbFNvdXJjZXNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTphcnJheTpvcmlnaW5hbHNvdXJjZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0T3JpZ2luYWxMb2NhdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPcmlnaW5hbExvY2F0aW9uXCIsXG4gICAgICAgICAgICBcImxpbmVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbHVtblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJvcmlnaW5hbGxvY2F0aW9ucmVzcG9uc2VcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInN0eWxlLWFwcGxpZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0eWxlQXBwbGllZFwiXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIFwib3JpZ2luYWxzb3VyY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib3JpZ2luYWxzb3VyY2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0c1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0eWxlU2hlZXRzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN0eWxlU2hlZXRzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0c1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImFkZFN0eWxlU2hlZXRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkU3R5bGVTaGVldFwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3R5bGVTaGVldFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNvdXJjZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJjb2x1bW5cIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21ub2RlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW5vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXROb2RlVmFsdWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Tm9kZVZhbHVlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE5vZGVWYWx1ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXROb2RlVmFsdWVcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEltYWdlRGF0YVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRJbWFnZURhdGFcIixcbiAgICAgICAgICAgIFwibWF4RGltXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImltYWdlRGF0YVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5QXR0cmlidXRlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlBdHRyaWJ1dGVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJhcHBsaWVkc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhcHBsaWVkc3R5bGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJpbmhlcml0ZWRcIjogXCJudWxsYWJsZTpkb21ub2RlI2FjdG9yaWRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJtYXRjaGVkc2VsZWN0b3JcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJzZWxlY3RvclwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInZhbHVlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwic3RhdHVzXCI6IFwibnVtYmVyXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwibWF0Y2hlZHNlbGVjdG9ycmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInJ1bGVzXCI6IFwiYXJyYXk6ZG9tc3R5bGVydWxlXCIsXG4gICAgICAgIFwic2hlZXRzXCI6IFwiYXJyYXk6c3R5bGVzaGVldFwiLFxuICAgICAgICBcIm1hdGNoZWRcIjogXCJhcnJheTptYXRjaGVkc2VsZWN0b3JcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJhcHBsaWVkU3R5bGVzUmV0dXJuXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiYXBwbGllZFN0eWxlc1JldHVyblwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImVudHJpZXNcIjogXCJhcnJheTphcHBsaWVkc3R5bGVcIixcbiAgICAgICAgXCJydWxlc1wiOiBcImFycmF5OmRvbXN0eWxlcnVsZVwiLFxuICAgICAgICBcInNoZWV0c1wiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJwYWdlc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicGFnZXN0eWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1hcmtNYXRjaGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwib25seU1hdGNoZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJmaWx0ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjb21wdXRlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE1hdGNoZWRTZWxlY3RvcnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TWF0Y2hlZFNlbGVjdG9yc1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHJvcGVydHlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZpbHRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QXBwbGllZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRBcHBsaWVkXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJpbmhlcml0ZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXRjaGVkU2VsZWN0b3JzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZmlsdGVyXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFwcGxpZWRTdHlsZXNSZXR1cm5cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldExheW91dFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRMYXlvdXRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImF1dG9NYXJnaW5zXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21zdHlsZXJ1bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZG9tc3R5bGVydWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5UHJvcGVydGllc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlQcm9wZXJ0aWVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwicnVsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbXN0eWxlcnVsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiaGlnaGxpZ2h0ZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiaGlnaGxpZ2h0ZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzaG93Qm94TW9kZWxcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2hvd0JveE1vZGVsXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJyZWdpb25cIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZGVCb3hNb2RlbFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWRlQm94TW9kZWxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNhbmNlbFBpY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2FuY2VsUGlja1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJpbWFnZURhdGFcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbWFnZURhdGFcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJkYXRhXCI6IFwibnVsbGFibGU6bG9uZ3N0cmluZ1wiLFxuICAgICAgICBcInNpemVcIjogXCJqc29uXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJub2RlXCI6IFwiZG9tbm9kZVwiLFxuICAgICAgICBcIm5ld1BhcmVudHNcIjogXCJhcnJheTpkb21ub2RlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwibm9kZXNcIjogXCJhcnJheTpkb21ub2RlXCIsXG4gICAgICAgIFwibmV3UGFyZW50c1wiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21tdXRhdGlvblwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW11dGF0aW9uXCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21ub2RlbGlzdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb21ub2RlbGlzdFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXRlbVwiLFxuICAgICAgICAgICAgXCJpdGVtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZG9tdHJhdmVyc2FsYXJyYXlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5vZGVzXCI6IFwiYXJyYXk6ZG9tbm9kZVwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImRvbXdhbGtlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb213YWxrZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiY2FuY2VsUGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjYW5jZWxQaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWdobGlnaHRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50RWxlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudEVsZW1lbnRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGFyZW50c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwYXJlbnRzXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzYW1lRG9jdW1lbnRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2Rlc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJldGFpbk5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5yZXRhaW5Ob2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVucmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZvcmNlXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibWF4Tm9kZXNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNlbnRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwid2hhdFRvU2hvd1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1heE5vZGVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJjZW50ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tdHJhdmVyc2FsYXJyYXlcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ3aGF0VG9TaG93XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInByZXZpb3VzU2libGluZ1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwcmV2aW91c1NpYmxpbmdcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicXVlcnlTZWxlY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJxdWVyeVNlbGVjdG9yXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzZWxlY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkaXNjb25uZWN0ZWROb2RlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJxdWVyeVNlbGVjdG9yQWxsXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInF1ZXJ5U2VsZWN0b3JBbGxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibGlzdFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVsaXN0XCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdWdnZXN0aW9uc0ZvclF1ZXJ5XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN1Z2dlc3Rpb25zRm9yUXVlcnlcIixcbiAgICAgICAgICAgIFwicXVlcnlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbXBsZXRpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yU3RhdGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJsaXN0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhZGRQc2V1ZG9DbGFzc0xvY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwc2V1ZG9DbGFzc1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyZW50c1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVuaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidW5oaWRlTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVtb3ZlUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZVBzZXVkb0NsYXNzTG9ja1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHNldWRvQ2xhc3NcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudHNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyUHNldWRvQ2xhc3NMb2Nrc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjbGVhclBzZXVkb0NsYXNzTG9ja3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlubmVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbm5lckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJvdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRPdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZW1vdmVOb2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJuZXh0U2libGluZ1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImluc2VydEJlZm9yZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbnNlcnRCZWZvcmVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNpYmxpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRNdXRhdGlvbnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TXV0YXRpb25zXCIsXG4gICAgICAgICAgICBcImNsZWFudXBcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJtdXRhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJhcnJheTpkb21tdXRhdGlvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJhdHRhY2hlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE5vZGVBY3RvckZyb21PYmplY3RBY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXROb2RlQWN0b3JGcm9tT2JqZWN0QWN0b3JcIixcbiAgICAgICAgICAgIFwib2JqZWN0QWN0b3JJRFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIm5vZGVGcm9udFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJuZXctbXV0YXRpb25zXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJuZXdNdXRhdGlvbnNcIlxuICAgICAgICB9LFxuICAgICAgICBcInBpY2tlci1ub2RlLXBpY2tlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwicGlja2VyTm9kZVBpY2tlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJwaWNrZXItbm9kZS1ob3ZlcmVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwaWNrZXJOb2RlSG92ZXJlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJoaWdobGlnaHRlci1yZWFkeVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiaGlnaGxpZ2h0ZXItcmVhZHlcIlxuICAgICAgICB9LFxuICAgICAgICBcImhpZ2hsaWdodGVyLWhpZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodGVyLWhpZGVcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImluc3BlY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbnNwZWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRXYWxrZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0V2Fsa2VyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ3YWxrZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb213YWxrZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhZ2VTdHlsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYWdlU3R5bGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhZ2VTdHlsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInBhZ2VzdHlsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICAgIFwiYXV0b2hpZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiaGlnaGxpZ3RlclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImhpZ2hsaWdodGVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbWFnZURhdGFGcm9tVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEltYWdlRGF0YUZyb21VUkxcIixcbiAgICAgICAgICAgIFwidXJsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXhEaW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiaW1hZ2VEYXRhXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXN0YWNrLWl0ZW1cIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLXN0YWNrLWl0ZW1cIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiZmlsZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjYWxsLWRldGFpbHNcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLWRldGFpbHNcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwibmFtZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInN0YWNrXCI6IFwiYXJyYXk6Y2FsbC1zdGFjay1pdGVtXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnVuY3Rpb24tY2FsbFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJmdW5jdGlvbi1jYWxsXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGV0YWlsc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXREZXRhaWxzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJpbmZvXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiY2FsbC1kZXRhaWxzXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXdhdGNoZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY2FsbC13YXRjaGVyXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJ0cmFjZWRHbG9iYWxzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ0cmFjZWRGdW5jdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0UmVjb3JkaW5nXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGVyZm9ybVJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNSZWNvcmRpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZXN1bWVSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVzdW1lUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInBhdXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInBhdXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjYWxsc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImVyYXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImVyYXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcInNuYXBzaG90LWltYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic25hcHNob3QtaW1hZ2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJpbmRleFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcIndpZHRoXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiaGVpZ2h0XCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiZmxpcHBlZFwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJwaXhlbHNcIjogXCJ1aW50MzItYXJyYXlcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJzbmFwc2hvdC1vdmVydmlld1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInNuYXBzaG90LW92ZXJ2aWV3XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY2FsbHNcIjogXCJhcnJheTpmdW5jdGlvbi1jYWxsXCIsXG4gICAgICAgIFwidGh1bWJuYWlsc1wiOiBcImFycmF5OnNuYXBzaG90LWltYWdlXCIsXG4gICAgICAgIFwic2NyZWVuc2hvdFwiOiBcInNuYXBzaG90LWltYWdlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnJhbWUtc25hcHNob3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZnJhbWUtc25hcHNob3RcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPdmVydmlld1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPdmVydmlld1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwib3ZlcnZpZXdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1vdmVydmlld1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2VuZXJhdGVTY3JlZW5zaG90Rm9yXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdlbmVyYXRlU2NyZWVuc2hvdEZvclwiLFxuICAgICAgICAgICAgXCJjYWxsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNjcmVlbnNob3RcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1pbWFnZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiY2FudmFzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNhbnZhc1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0dXBcIixcbiAgICAgICAgICAgIFwicmVsb2FkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZmluYWxpemVcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJmaW5hbGl6ZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJpc0luaXRpYWxpemVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzSW5pdGlhbGl6ZWRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcImluaXRpYWxpemVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNuYXBzaG90XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZnJhbWUtc25hcHNob3RcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImdsLXNoYWRlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJnbC1zaGFkZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNvbXBpbGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY29tcGlsZVwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZXJyb3JcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpqc29uXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJnbC1wcm9ncmFtXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImdsLXByb2dyYW1cIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRWZXJ0ZXhTaGFkZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0VmVydGV4U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgICAgXCJ0aW50XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImFycmF5Om51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ1bmhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuaGlnaGxpZ2h0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImJsYWNrYm94XCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5ibGFja2JveFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwid2ViZ2xcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwid2ViZ2xcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgICBcInJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UHJvZ3JhbXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0UHJvZ3JhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInByb2dyYW1zXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6Z2wtcHJvZ3JhbVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInByb2dyYW0tbGlua2VkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwcm9ncmFtTGlua2VkXCIsXG4gICAgICAgICAgXCJwcm9ncmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2wtcHJvZ3JhbVwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImF1ZGlvbm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhdWRpb25vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUeXBlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFR5cGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlzU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6cHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJlcnJvclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOnByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UGFyYW1GbGFnc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbUZsYWdzXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZmxhZ3NcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhcmFtc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIndlYmF1ZGlvXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJyZWxvYWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJmaW5hbGl6ZVwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZpbmFsaXplXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJzdGFydC1jb250ZXh0XCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdGFydENvbnRleHRcIlxuICAgICAgICB9LFxuICAgICAgICBcImNvbm5lY3Qtbm9kZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiY29ubmVjdE5vZGVcIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcImRlc3RcIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJkaXNjb25uZWN0LW5vZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3ROb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJjb25uZWN0LXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjb25uZWN0UGFyYW1cIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIFwiY2hhbmdlLXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjaGFuZ2VQYXJhbVwiLFxuICAgICAgICAgIFwic291cmNlXCI6IHtcbiAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXVkaW9ub2RlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcImNyZWF0ZS1ub2RlXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjcmVhdGVOb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvbGQtc3R5bGVzaGVldFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJvbGQtc3R5bGVzaGVldFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInRvZ2dsZURpc2FibGVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInRvZ2dsZURpc2FibGVkXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJkaXNhYmxlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZldGNoU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZldGNoU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInNvdXJjZS1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzb3VyY2VMb2FkXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdHlsZS1hcHBsaWVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdHlsZUFwcGxpZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInN0eWxlZWRpdG9yXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlZWRpdG9yXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3RG9jdW1lbnRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwibmV3RG9jdW1lbnRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3U3R5bGVTaGVldFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJuZXdTdHlsZVNoZWV0XCIsXG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwib2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJkb2N1bWVudC1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudExvYWRcIixcbiAgICAgICAgICBcInN0eWxlU2hlZXRzXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6b2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjb29raWVvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCIsXG4gICAgICAgIFwicGF0aFwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcImhvc3RcIjogXCJzdHJpbmdcIixcbiAgICAgICAgXCJpc0RvbWFpblwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc1NlY3VyZVwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc0h0dHBPbmx5XCI6IFwiYm9vbGVhblwiLFxuICAgICAgICBcImNyZWF0aW9uVGltZVwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImxhc3RBY2Nlc3NlZFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImV4cGlyZXNcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6Y29va2llb2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0b3JhZ2VvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZXN0b3Jlb2JqZWN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmFnZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6c3RvcmFnZW9iamVjdFwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImlkYm9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYm9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5hbWVcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJkYlwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcIm9iamVjdFN0b3JlXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwib3JpZ2luXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwidmVyc2lvblwiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcIm9iamVjdFN0b3Jlc1wiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcImtleVBhdGhcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJhdXRvSW5jcmVtZW50XCI6IFwibnVsbGFibGU6Ym9vbGVhblwiLFxuICAgICAgICBcImluZGV4ZXNcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJ2YWx1ZVwiOiBcIm51bGxhYmxlOmxvbmdzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJpZGJzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYnN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6aWRib2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmVVcGRhdGVPYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yZVVwZGF0ZU9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImNoYW5nZWRcIjogXCJudWxsYWJsZTpqc29uXCIsXG4gICAgICAgIFwiZGVsZXRlZFwiOiBcIm51bGxhYmxlOmpzb25cIixcbiAgICAgICAgXCJhZGRlZFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXNcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJjb29raWVzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwibG9jYWxTdG9yYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImxvY2FsU3RvcmFnZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0b3JlT2JqZWN0c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICAgIFwiaG9zdFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibmFtZXNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm9wdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0b3JhZ2VzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic2Vzc2lvblN0b3JhZ2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic2Vzc2lvblN0b3JhZ2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdG9yYWdlc3RvcmVvYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImluZGV4ZWREQlwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbmRleGVkREJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJpZGJzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic3RvcmVsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmVsaXN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY29va2llc1wiOiBcImNvb2tpZXNcIixcbiAgICAgICAgXCJsb2NhbFN0b3JhZ2VcIjogXCJsb2NhbFN0b3JhZ2VcIixcbiAgICAgICAgXCJzZXNzaW9uU3RvcmFnZVwiOiBcInNlc3Npb25TdG9yYWdlXCIsXG4gICAgICAgIFwiaW5kZXhlZERCXCI6IFwiaW5kZXhlZERCXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yYWdlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibGlzdFN0b3Jlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJsaXN0U3RvcmVzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RvcmVsaXN0XCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7XG4gICAgICAgIFwic3RvcmVzLXVwZGF0ZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzVXBkYXRlXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVVcGRhdGVPYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtY2xlYXJlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzQ2xlYXJlZFwiLFxuICAgICAgICAgIFwiZGF0YVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtcmVsb2FkZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0b3Jlc1JlbGFvZGVkXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImdjbGlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZ2NsaVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNwZWNzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNwZWNzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZXhlY3V0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJleGVjdXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicmFua1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlcGFyc2VcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidHlwZXBhcnNlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlaW5jcmVtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInR5cGVpbmNyZW1lbnRcIixcbiAgICAgICAgICAgIFwidHlwZWRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidHlwZWRlY3JlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0eXBlZGVjcmVtZW50XCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNlbGVjdGlvbmluZm9cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2VsZWN0aW9uaW5mb1wiLFxuICAgICAgICAgICAgXCJ0eXBlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImFjdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJtZW1vcnlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwibWVtb3J5XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibWVhc3VyZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtZWFzdXJlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZXZlbnRMb29wTGFnXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXJ0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXJ0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWNjZXNzXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdG9wXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0b3BcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcImV2ZW50LWxvb3AtbGFnXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJldmVudC1sb29wLWxhZ1wiLFxuICAgICAgICAgIFwidGltZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bWJlclwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInByZWZlcmVuY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicHJlZmVyZW5jZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEludFByZWZcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QWxsUHJlZnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0QWxsUHJlZnNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldEludFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyVXNlclByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2xlYXJVc2VyUHJlZlwiLFxuICAgICAgICAgICAgXCJuYW1lXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkZXZpY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGV2aWNlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGVzY3JpcHRpb25cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0RGVzY3JpcHRpb25cIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0V2FsbHBhcGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFdhbGxwYXBlclwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzY3JlZW5zaG90VG9EYXRhVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNjcmVlbnNob3RUb0RhdGFVUkxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibG9uZ3N0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UmF3UGVybWlzc2lvbnNUYWJsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRSYXdQZXJtaXNzaW9uc1RhYmxlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfVxuICB9LFxuICBcImZyb21cIjogXCJyb290XCJcbn1cbiIsIlwidXNlIHN0cmljdFwiO1xuXG52YXIgQ2xhc3MgPSByZXF1aXJlKFwiLi9jbGFzc1wiKS5DbGFzcztcbnZhciB1dGlsID0gcmVxdWlyZShcIi4vdXRpbFwiKTtcbnZhciBrZXlzID0gdXRpbC5rZXlzO1xudmFyIHZhbHVlcyA9IHV0aWwudmFsdWVzO1xudmFyIHBhaXJzID0gdXRpbC5wYWlycztcbnZhciBxdWVyeSA9IHV0aWwucXVlcnk7XG52YXIgZmluZFBhdGggPSB1dGlsLmZpbmRQYXRoO1xudmFyIEV2ZW50VGFyZ2V0ID0gcmVxdWlyZShcIi4vZXZlbnRcIikuRXZlbnRUYXJnZXQ7XG5cbnZhciBUeXBlU3lzdGVtID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oY2xpZW50KSB7XG4gICAgdmFyIHR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB2YXIgc3BlY2lmaWNhdGlvbiA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG5cbiAgICB0aGlzLnNwZWNpZmljYXRpb24gPSBzcGVjaWZpY2F0aW9uO1xuICAgIHRoaXMudHlwZXMgPSB0eXBlcztcblxuICAgIHZhciB0eXBlRm9yID0gZnVuY3Rpb24gdHlwZUZvcih0eXBlTmFtZSkge1xuICAgICAgdHlwZU5hbWUgPSB0eXBlTmFtZSB8fCBcInByaW1pdGl2ZVwiO1xuICAgICAgaWYgKCF0eXBlc1t0eXBlTmFtZV0pIHtcbiAgICAgICAgZGVmaW5lVHlwZSh0eXBlTmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0eXBlc1t0eXBlTmFtZV07XG4gICAgfTtcbiAgICB0aGlzLnR5cGVGb3IgPSB0eXBlRm9yO1xuXG4gICAgdmFyIGRlZmluZVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IHZvaWQoMCk7XG4gICAgICBpZiAodHlwZW9mKGRlc2NyaXB0b3IpID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIGlmIChkZXNjcmlwdG9yLmluZGV4T2YoXCI6XCIpID4gMClcbiAgICAgICAgICB0eXBlID0gbWFrZUNvbXBvdW5kVHlwZShkZXNjcmlwdG9yKTtcbiAgICAgICAgZWxzZSBpZiAoZGVzY3JpcHRvci5pbmRleE9mKFwiI1wiKSA+IDApXG4gICAgICAgICAgdHlwZSA9IG5ldyBBY3RvckRldGFpbChkZXNjcmlwdG9yKTtcbiAgICAgICAgICBlbHNlIGlmIChzcGVjaWZpY2F0aW9uW2Rlc2NyaXB0b3JdKVxuICAgICAgICAgICAgdHlwZSA9IG1ha2VDYXRlZ29yeVR5cGUoc3BlY2lmaWNhdGlvbltkZXNjcmlwdG9yXSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0eXBlID0gbWFrZUNhdGVnb3J5VHlwZShkZXNjcmlwdG9yKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGUpXG4gICAgICAgIHR5cGVzW3R5cGUubmFtZV0gPSB0eXBlO1xuICAgICAgZWxzZVxuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoXCJJbnZhbGlkIHR5cGU6IFwiICsgZGVzY3JpcHRvcik7XG4gICAgfTtcbiAgICB0aGlzLmRlZmluZVR5cGUgPSBkZWZpbmVUeXBlO1xuXG5cbiAgICB2YXIgbWFrZUNvbXBvdW5kVHlwZSA9IGZ1bmN0aW9uKG5hbWUpIHtcbiAgICAgIHZhciBpbmRleCA9IG5hbWUuaW5kZXhPZihcIjpcIik7XG4gICAgICB2YXIgYmFzZVR5cGUgPSBuYW1lLnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIHZhciBzdWJUeXBlID0gbmFtZS5zbGljZShpbmRleCArIDEpO1xuXG4gICAgICByZXR1cm4gYmFzZVR5cGUgPT09IFwiYXJyYXlcIiA/IG5ldyBBcnJheU9mKHN1YlR5cGUpIDpcbiAgICAgIGJhc2VUeXBlID09PSBcIm51bGxhYmxlXCIgPyBuZXcgTWF5YmUoc3ViVHlwZSkgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VDYXRlZ29yeVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgY2F0ZWdvcnkgPSBkZXNjcmlwdG9yLmNhdGVnb3J5O1xuICAgICAgcmV0dXJuIGNhdGVnb3J5ID09PSBcImRpY3RcIiA/IG5ldyBEaWN0aW9uYXJ5KGRlc2NyaXB0b3IpIDpcbiAgICAgIGNhdGVnb3J5ID09PSBcImFjdG9yXCIgPyBuZXcgQWN0b3IoZGVzY3JpcHRvcikgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIHJlYWQgPSBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgdHlwZU5hbWUpIHtcbiAgICAgIHJldHVybiB0eXBlRm9yKHR5cGVOYW1lKS5yZWFkKGlucHV0LCBjb250ZXh0KTtcbiAgICB9XG4gICAgdGhpcy5yZWFkID0gcmVhZDtcblxuICAgIHZhciB3cml0ZSA9IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0LCB0eXBlTmFtZSkge1xuICAgICAgcmV0dXJuIHR5cGVGb3IodHlwZU5hbWUpLndyaXRlKGlucHV0KTtcbiAgICB9O1xuICAgIHRoaXMud3JpdGUgPSB3cml0ZTtcblxuXG4gICAgdmFyIFR5cGUgPSBDbGFzcyh7XG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgICB9LFxuICAgICAgZ2V0IG5hbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmNhdGVnb3J5ID8gdGhpcy5jYXRlZ29yeSArIFwiOlwiICsgdGhpcy50eXBlIDpcbiAgICAgICAgdGhpcy50eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJgVHlwZWAgc3ViY2xhc3MgbXVzdCBpbXBsZW1lbnQgYHJlYWRgXCIpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiYFR5cGVgIHN1YmNsYXNzIG11c3QgaW1wbGVtZW50IGB3cml0ZWBcIik7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgUHJpbWl0dmUgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBUeXBlLFxuICAgICAgY29uc3R1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dDtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIGlucHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIE1heWJlID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcIm51bGxhYmxlXCIsXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dCA9PT0gbnVsbCA/IG51bGwgOlxuICAgICAgICBpbnB1dCA9PT0gdm9pZCgwKSA/IHZvaWQoMCkgOlxuICAgICAgICByZWFkKGlucHV0LCBjb250ZXh0LCB0aGlzLnR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gaW5wdXQgPT09IG51bGwgPyBudWxsIDpcbiAgICAgICAgaW5wdXQgPT09IHZvaWQoMCkgPyB2b2lkKDApIDpcbiAgICAgICAgd3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMudHlwZSk7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgQXJyYXlPZiA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhcnJheVwiLFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKHR5cGUpIHtcbiAgICAgICAgdGhpcy50eXBlID0gdHlwZTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiByZWFkKCQsIGNvbnRleHQsIHR5cGUpIH0pO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiB3cml0ZSgkLCBjb250ZXh0LCB0eXBlKSB9KTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHZhciBtYWtlRmllbGQgPSBmdW5jdGlvbiBtYWtlRmllbGQobmFtZSwgdHlwZSkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICBnZXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBuYW1lLCB7XG4gICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgdmFsdWU6IHJlYWQodGhpcy5zdGF0ZVtuYW1lXSwgdGhpcy5jb250ZXh0LCB0eXBlKVxuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJldHVybiB0aGlzW25hbWVdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIHZhciBtYWtlRmllbGRzID0gZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgcmV0dXJuIHBhaXJzKGRlc2NyaXB0b3IpLnJlZHVjZShmdW5jdGlvbihmaWVsZHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCB0eXBlID0gcGFpclsxXTtcbiAgICAgICAgZmllbGRzW25hbWVdID0gbWFrZUZpZWxkKG5hbWUsIHR5cGUpO1xuICAgICAgICByZXR1cm4gZmllbGRzO1xuICAgICAgfSwge30pO1xuICAgIH1cblxuICAgIHZhciBEaWN0aW9uYXJ5VHlwZSA9IENsYXNzKHt9KTtcblxuICAgIHZhciBEaWN0aW9uYXJ5ID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcImRpY3RcIixcbiAgICAgIGdldCBuYW1lKCkgeyByZXR1cm4gdGhpcy50eXBlOyB9LFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgICAgdGhpcy50eXBlID0gZGVzY3JpcHRvci50eXBlTmFtZTtcbiAgICAgICAgdGhpcy50eXBlcyA9IGRlc2NyaXB0b3Iuc3BlY2lhbGl6YXRpb25zO1xuXG4gICAgICAgIHZhciBwcm90byA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHtcbiAgICAgICAgICBleHRlbmRzOiBEaWN0aW9uYXJ5VHlwZSxcbiAgICAgICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oc3RhdGUsIGNvbnRleHQpIHtcbiAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsIHtcbiAgICAgICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogdHJ1ZSxcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IHN0YXRlXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBjb250ZXh0XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSwgbWFrZUZpZWxkcyh0aGlzLnR5cGVzKSk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IG5ldyBDbGFzcyhwcm90byk7XG4gICAgICB9LFxuICAgICAgcmVhZDogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyB0aGlzLmNsYXNzKGlucHV0LCBjb250ZXh0KTtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgdmFyIG91dHB1dCA9IHt9O1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gaW5wdXQpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKHZhbHVlLCBjb250ZXh0LCB0eXBlc1trZXldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3V0cHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIG1ha2VNZXRob2RzID0gZnVuY3Rpb24oZGVzY3JpcHRvcnMpIHtcbiAgICAgIHJldHVybiBkZXNjcmlwdG9ycy5yZWR1Y2UoZnVuY3Rpb24obWV0aG9kcywgZGVzY3JpcHRvcikge1xuICAgICAgICBtZXRob2RzW2Rlc2NyaXB0b3IubmFtZV0gPSB7XG4gICAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgd3JpdGFibGU6IGZhbHNlLFxuICAgICAgICAgIHZhbHVlOiBtYWtlTWV0aG9kKGRlc2NyaXB0b3IpXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBtZXRob2RzO1xuICAgICAgfSwge30pO1xuICAgIH07XG5cbiAgICB2YXIgbWFrZUV2ZW50cyA9IGZ1bmN0aW9uKGRlc2NyaXB0b3JzKSB7XG4gICAgICByZXR1cm4gcGFpcnMoZGVzY3JpcHRvcnMpLnJlZHVjZShmdW5jdGlvbihldmVudHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCBkZXNjcmlwdG9yID0gcGFpclsxXTtcbiAgICAgICAgdmFyIGV2ZW50ID0gbmV3IEV2ZW50KG5hbWUsIGRlc2NyaXB0b3IpO1xuICAgICAgICBldmVudHNbZXZlbnQuZXZlbnRUeXBlXSA9IGV2ZW50O1xuICAgICAgICByZXR1cm4gZXZlbnRzO1xuICAgICAgfSwgT2JqZWN0LmNyZWF0ZShudWxsKSk7XG4gICAgfTtcblxuICAgIHZhciBBY3RvciA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhY3RvclwiLFxuICAgICAgZ2V0IG5hbWUoKSB7IHJldHVybiB0aGlzLnR5cGU7IH0sXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgICB0aGlzLnR5cGUgPSBkZXNjcmlwdG9yLnR5cGVOYW1lO1xuXG4gICAgICAgIHZhciBldmVudHMgPSBtYWtlRXZlbnRzKGRlc2NyaXB0b3IuZXZlbnRzIHx8IHt9KTtcbiAgICAgICAgdmFyIGZpZWxkcyA9IG1ha2VGaWVsZHMoZGVzY3JpcHRvci5maWVsZHMgfHwge30pO1xuICAgICAgICB2YXIgbWV0aG9kcyA9IG1ha2VNZXRob2RzKGRlc2NyaXB0b3IubWV0aG9kcyB8fCBbXSk7XG5cblxuICAgICAgICB2YXIgcHJvdG8gPSB7XG4gICAgICAgICAgZXh0ZW5kczogRnJvbnQsXG4gICAgICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgRnJvbnQuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgICAgICB9LFxuICAgICAgICAgIGV2ZW50czogZXZlbnRzXG4gICAgICAgIH07XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHByb3RvLCBmaWVsZHMpO1xuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydGllcyhwcm90bywgbWV0aG9kcyk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IENsYXNzKHByb3RvKTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgZGV0YWlsKSB7XG4gICAgICAgIHZhciBzdGF0ZSA9IHR5cGVvZihpbnB1dCkgPT09IFwic3RyaW5nXCIgPyB7IGFjdG9yOiBpbnB1dCB9IDogaW5wdXQ7XG5cbiAgICAgICAgdmFyIGFjdG9yID0gY2xpZW50LmdldChzdGF0ZS5hY3RvcikgfHwgbmV3IHRoaXMuY2xhc3Moc3RhdGUsIGNvbnRleHQpO1xuICAgICAgICBhY3Rvci5mb3JtKHN0YXRlLCBkZXRhaWwsIGNvbnRleHQpO1xuXG4gICAgICAgIHJldHVybiBhY3RvcjtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQsIGRldGFpbCkge1xuICAgICAgICByZXR1cm4gaW5wdXQuaWQ7XG4gICAgICB9XG4gICAgfSk7XG4gICAgZXhwb3J0cy5BY3RvciA9IEFjdG9yO1xuXG5cbiAgICB2YXIgQWN0b3JEZXRhaWwgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBBY3RvcixcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IG5hbWUuc3BsaXQoXCIjXCIpXG4gICAgICAgIHRoaXMuYWN0b3JUeXBlID0gcGFydHNbMF1cbiAgICAgICAgdGhpcy5kZXRhaWwgPSBwYXJ0c1sxXTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkucmVhZChpbnB1dCwgY29udGV4dCwgdGhpcy5kZXRhaWwpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkud3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMuZGV0YWlsKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBleHBvcnRzLkFjdG9yRGV0YWlsID0gQWN0b3JEZXRhaWw7XG5cbiAgICB2YXIgTWV0aG9kID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMudHlwZSA9IGRlc2NyaXB0b3IubmFtZTtcbiAgICAgICAgdGhpcy5wYXRoID0gZmluZFBhdGgoZGVzY3JpcHRvci5yZXNwb25zZSwgXCJfcmV0dmFsXCIpO1xuICAgICAgICB0aGlzLnJlc3BvbnNlVHlwZSA9IHRoaXMucGF0aCAmJiBxdWVyeShkZXNjcmlwdG9yLnJlc3BvbnNlLCB0aGlzLnBhdGgpLl9yZXR2YWw7XG4gICAgICAgIHRoaXMucmVxdWVzdFR5cGUgPSBkZXNjcmlwdG9yLnJlcXVlc3QudHlwZTtcblxuICAgICAgICB2YXIgcGFyYW1zID0gW107XG4gICAgICAgIGZvciAodmFyIGtleSBpbiBkZXNjcmlwdG9yLnJlcXVlc3QpIHtcbiAgICAgICAgICBpZiAoa2V5ICE9PSBcInR5cGVcIikge1xuICAgICAgICAgICAgdmFyIHBhcmFtID0gZGVzY3JpcHRvci5yZXF1ZXN0W2tleV07XG4gICAgICAgICAgICB2YXIgaW5kZXggPSBcIl9hcmdcIiBpbiBwYXJhbSA/IHBhcmFtLl9hcmcgOiBwYXJhbS5fb3B0aW9uO1xuICAgICAgICAgICAgdmFyIGlzUGFyYW0gPSBwYXJhbS5fb3B0aW9uID09PSBpbmRleDtcbiAgICAgICAgICAgIHZhciBpc0FyZ3VtZW50ID0gcGFyYW0uX2FyZyA9PT0gaW5kZXg7XG4gICAgICAgICAgICBwYXJhbXNbaW5kZXhdID0ge1xuICAgICAgICAgICAgICB0eXBlOiBwYXJhbS50eXBlLFxuICAgICAgICAgICAgICBrZXk6IGtleSxcbiAgICAgICAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICAgICAgICBpc1BhcmFtOiBpc1BhcmFtLFxuICAgICAgICAgICAgICBpc0FyZ3VtZW50OiBpc0FyZ3VtZW50XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLnBhcmFtcyA9IHBhcmFtcztcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gcmVhZChxdWVyeShpbnB1dCwgdGhpcy5wYXRoKSwgY29udGV4dCwgdGhpcy5yZXNwb25zZVR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdGhpcy5wYXJhbXMucmVkdWNlKGZ1bmN0aW9uKHJlc3VsdCwgcGFyYW0pIHtcbiAgICAgICAgICByZXN1bHRbcGFyYW0ua2V5XSA9IHdyaXRlKGlucHV0W3BhcmFtLmluZGV4XSwgY29udGV4dCwgcGFyYW0udHlwZSk7XG4gICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgfSwge3R5cGU6IHRoaXMudHlwZX0pO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGV4cG9ydHMuTWV0aG9kID0gTWV0aG9kO1xuXG4gICAgdmFyIHByb2ZpbGVyID0gZnVuY3Rpb24obWV0aG9kLCBpZCkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc3RhcnQgPSBuZXcgRGF0ZSgpO1xuICAgICAgICByZXR1cm4gbWV0aG9kLmFwcGx5KHRoaXMsIGFyZ3VtZW50cykudGhlbihmdW5jdGlvbihyZXN1bHQpIHtcbiAgICAgICAgICB2YXIgZW5kID0gbmV3IERhdGUoKTtcbiAgICAgICAgICBjbGllbnQudGVsZW1ldHJ5LmFkZChpZCwgK2VuZCAtIHN0YXJ0KTtcbiAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBkZXN0cnVjdG9yID0gZnVuY3Rpb24obWV0aG9kKSB7XG4gICAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiBtZXRob2QuYXBwbHkodGhpcywgYXJndW1lbnRzKS50aGVuKGZ1bmN0aW9uKHJlc3VsdCkge1xuICAgICAgICAgIGNsaWVudC5yZWxlYXNlKHRoaXMpO1xuICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgIH0pO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgZnVuY3Rpb24gbWFrZU1ldGhvZChkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IG5ldyBNZXRob2QoZGVzY3JpcHRvcik7XG4gICAgICB2YXIgbWV0aG9kID0gZGVzY3JpcHRvci5vbmV3YXkgPyBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kKGRlc2NyaXB0b3IsIHR5cGUpIDpcbiAgICAgICAgICAgICAgICAgICBtYWtlQmlkaXJlY3Rpb25hbE1ldGhvZChkZXNjcmlwdG9yLCB0eXBlKTtcblxuICAgICAgaWYgKGRlc2NyaXB0b3IudGVsZW1ldHJ5KVxuICAgICAgICBtZXRob2QgPSBwcm9maWxlcihtZXRob2QpO1xuICAgICAgaWYgKGRlc2NyaXB0b3IucmVsZWFzZSlcbiAgICAgICAgbWV0aG9kID0gZGVzdHJ1Y3RvcihtZXRob2QpO1xuXG4gICAgICByZXR1cm4gbWV0aG9kO1xuICAgIH1cblxuICAgIHZhciBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgcGFja2V0ID0gdHlwZS53cml0ZShhcmd1bWVudHMsIHRoaXMpO1xuICAgICAgICBwYWNrZXQudG8gPSB0aGlzLmlkO1xuICAgICAgICBjbGllbnQuc2VuZChwYWNrZXQpO1xuICAgICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHZvaWQoMCkpO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VCaWRpcmVjdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgY29udGV4dCA9IHRoaXMuY29udGV4dDtcbiAgICAgICAgdmFyIHBhY2tldCA9IHR5cGUud3JpdGUoYXJndW1lbnRzLCBjb250ZXh0KTtcbiAgICAgICAgdmFyIGNvbnRleHQgPSB0aGlzLmNvbnRleHQ7XG4gICAgICAgIHBhY2tldC50byA9IHRoaXMuaWQ7XG4gICAgICAgIHJldHVybiBjbGllbnQucmVxdWVzdChwYWNrZXQpLnRoZW4oZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgICAgICAgcmV0dXJuIHR5cGUucmVhZChwYWNrZXQsIGNvbnRleHQpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBFdmVudCA9IENsYXNzKHtcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lLCBkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLmV2ZW50VHlwZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLnR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcblxuICAgICAgICB2YXIgdHlwZXMgPSB0aGlzLnR5cGVzO1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gZGVzY3JpcHRvcikge1xuICAgICAgICAgIGlmIChrZXkgPT09IFwidHlwZVwiKSB7XG4gICAgICAgICAgICB0eXBlc1trZXldID0gXCJzdHJpbmdcIjtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdHlwZXNba2V5XSA9IGRlc2NyaXB0b3Jba2V5XS50eXBlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIGlucHV0KSB7XG4gICAgICAgICAgb3V0cHV0W2tleV0gPSByZWFkKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9LFxuICAgICAgd3JpdGU6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIHRoaXMudHlwZXMpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgRnJvbnQgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBFdmVudFRhcmdldCxcbiAgICAgIEV2ZW50VGFyZ2V0OiBFdmVudFRhcmdldCxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihzdGF0ZSkge1xuICAgICAgICB0aGlzLkV2ZW50VGFyZ2V0KCk7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsICB7XG4gICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICB2YWx1ZTogc3RhdGVcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNsaWVudC5yZWdpc3Rlcih0aGlzKTtcbiAgICAgIH0sXG4gICAgICBnZXQgaWQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXRlLmFjdG9yO1xuICAgICAgfSxcbiAgICAgIGdldCBjb250ZXh0KCkge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgIH0sXG4gICAgICBmb3JtOiBmdW5jdGlvbihzdGF0ZSwgZGV0YWlsLCBjb250ZXh0KSB7XG4gICAgICAgIGlmICh0aGlzLnN0YXRlICE9PSBzdGF0ZSkge1xuICAgICAgICAgIGlmIChkZXRhaWwpIHtcbiAgICAgICAgICAgIHRoaXMuc3RhdGVbZGV0YWlsXSA9IHN0YXRlW2RldGFpbF07XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHBhaXJzKHN0YXRlKS5mb3JFYWNoKGZ1bmN0aW9uKHBhaXIpIHtcbiAgICAgICAgICAgICAgdmFyIGtleSA9IHBhaXJbMF0sIHZhbHVlID0gcGFpclsxXTtcbiAgICAgICAgICAgICAgdGhpcy5zdGF0ZVtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICB9LCB0aGlzKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY29udGV4dCkge1xuICAgICAgICAgIGNsaWVudC5zdXBlcnZpc2UoY29udGV4dCwgdGhpcyk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICByZXF1ZXN0VHlwZXM6IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gY2xpZW50LnJlcXVlc3Qoe1xuICAgICAgICAgIHRvOiB0aGlzLmlkLFxuICAgICAgICAgIHR5cGU6IFwicmVxdWVzdFR5cGVzXCJcbiAgICAgICAgfSkudGhlbihmdW5jdGlvbihwYWNrZXQpIHtcbiAgICAgICAgICByZXR1cm4gcGFja2V0LnJlcXVlc3RUeXBlcztcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgdHlwZXMucHJpbWl0aXZlID0gbmV3IFByaW1pdHZlKFwicHJpbWl0aXZlXCIpO1xuICAgIHR5cGVzLnN0cmluZyA9IG5ldyBQcmltaXR2ZShcInN0cmluZ1wiKTtcbiAgICB0eXBlcy5udW1iZXIgPSBuZXcgUHJpbWl0dmUoXCJudW1iZXJcIik7XG4gICAgdHlwZXMuYm9vbGVhbiA9IG5ldyBQcmltaXR2ZShcImJvb2xlYW5cIik7XG4gICAgdHlwZXMuanNvbiA9IG5ldyBQcmltaXR2ZShcImpzb25cIik7XG4gICAgdHlwZXMuYXJyYXkgPSBuZXcgUHJpbWl0dmUoXCJhcnJheVwiKTtcbiAgfSxcbiAgcmVnaXN0ZXJUeXBlczogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgIHZhciBzcGVjaWZpY2F0aW9uID0gdGhpcy5zcGVjaWZpY2F0aW9uO1xuICAgIHZhbHVlcyhkZXNjcmlwdG9yLnR5cGVzKS5mb3JFYWNoKGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgIHNwZWNpZmljYXRpb25bZGVzY3JpcHRvci50eXBlTmFtZV0gPSBkZXNjcmlwdG9yO1xuICAgIH0pO1xuICB9XG59KTtcbmV4cG9ydHMuVHlwZVN5c3RlbSA9IFR5cGVTeXN0ZW07XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIGtleXMgPSBPYmplY3Qua2V5cztcbmV4cG9ydHMua2V5cyA9IGtleXM7XG5cbi8vIFJldHVybnMgYXJyYXkgb2YgdmFsdWVzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHZhbHVlcyA9IGZ1bmN0aW9uKG9iamVjdCkge1xuICByZXR1cm4ga2V5cyhvYmplY3QpLm1hcChmdW5jdGlvbihrZXkpIHtcbiAgICByZXR1cm4gb2JqZWN0W2tleV1cbiAgfSk7XG59O1xuZXhwb3J0cy52YWx1ZXMgPSB2YWx1ZXM7XG5cbi8vIFJldHVybnMgW2tleSwgdmFsdWVdIHBhaXJzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHBhaXJzID0gZnVuY3Rpb24ob2JqZWN0KSB7XG4gIHJldHVybiBrZXlzKG9iamVjdCkubWFwKGZ1bmN0aW9uKGtleSkge1xuICAgIHJldHVybiBba2V5LCBvYmplY3Rba2V5XV1cbiAgfSk7XG59O1xuZXhwb3J0cy5wYWlycyA9IHBhaXJzO1xuXG5cbi8vIFF1ZXJpZXMgYW4gb2JqZWN0IGZvciB0aGUgZmllbGQgbmVzdGVkIHdpdGggaW4gaXQuXG52YXIgcXVlcnkgPSBmdW5jdGlvbihvYmplY3QsIHBhdGgpIHtcbiAgcmV0dXJuIHBhdGgucmVkdWNlKGZ1bmN0aW9uKG9iamVjdCwgZW50cnkpIHtcbiAgICByZXR1cm4gb2JqZWN0ICYmIG9iamVjdFtlbnRyeV1cbiAgfSwgb2JqZWN0KTtcbn07XG5leHBvcnRzLnF1ZXJ5ID0gcXVlcnk7XG5cbnZhciBpc09iamVjdCA9IGZ1bmN0aW9uKHgpIHtcbiAgcmV0dXJuIHggJiYgdHlwZW9mKHgpID09PSBcIm9iamVjdFwiXG59XG5cbnZhciBmaW5kUGF0aCA9IGZ1bmN0aW9uKG9iamVjdCwga2V5KSB7XG4gIHZhciBwYXRoID0gdm9pZCgwKTtcbiAgaWYgKG9iamVjdCAmJiB0eXBlb2Yob2JqZWN0KSA9PT0gXCJvYmplY3RcIikge1xuICAgIHZhciBuYW1lcyA9IGtleXMob2JqZWN0KTtcbiAgICBpZiAobmFtZXMuaW5kZXhPZihrZXkpID49IDApIHtcbiAgICAgIHBhdGggPSBbXTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGluZGV4ID0gMDtcbiAgICAgIHZhciBjb3VudCA9IG5hbWVzLmxlbmd0aDtcbiAgICAgIHdoaWxlIChpbmRleCA8IGNvdW50ICYmICFwYXRoKXtcbiAgICAgICAgdmFyIGhlYWQgPSBuYW1lc1tpbmRleF07XG4gICAgICAgIHZhciB0YWlsID0gZmluZFBhdGgob2JqZWN0W2hlYWRdLCBrZXkpO1xuICAgICAgICBwYXRoID0gdGFpbCA/IFtoZWFkXS5jb25jYXQodGFpbCkgOiB0YWlsO1xuICAgICAgICBpbmRleCA9IGluZGV4ICsgMVxuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gcGF0aDtcbn07XG5leHBvcnRzLmZpbmRQYXRoID0gZmluZFBhdGg7XG4iXX0=
(1)
});
PK
!<$modules/commonjs/diffpatcher/diff.js"use strict";

var method = require("../method/core")

// Method is designed to work with data structures representing application
// state. Calling it with a state should return object representing `delta`
// that has being applied to a previous state to get to a current state.
//
// Example
//
// diff(state) // => { "item-id-1": { title: "some title" } "item-id-2": null }
var diff = method("diff@diffpatcher")

// diff between `null` / `undefined` to any hash is a hash itself.
diff.define(null, function(from, to) { return to })
diff.define(undefined, function(from, to) { return to })
diff.define(Object, function(from, to) {
  return calculate(from, to || {}) || {}
})

function calculate(from, to) {
  var diff = {}
  var changes = 0
  Object.keys(from).forEach(function(key) {
    changes = changes + 1
    if (!(key in to) && from[key] != null) diff[key] = null
    else changes = changes - 1
  })
  Object.keys(to).forEach(function(key) {
    changes = changes + 1
    var previous = from[key]
    var current = to[key]
    if (previous === current) return (changes = changes - 1)
    if (typeof(current) !== "object") return diff[key] = current
    if (typeof(previous) !== "object") return diff[key] = current
    var delta = calculate(previous, current)
    if (delta) diff[key] = delta
    else changes = changes - 1
  })
  return changes ? diff : null
}

diff.calculate = calculate

module.exports = diff
PK
!<xx%modules/commonjs/diffpatcher/index.js"use strict";

exports.diff = require("./diff")
exports.patch = require("./patch")
exports.rebase = require("./rebase")
PK
!< OaZZ%modules/commonjs/diffpatcher/patch.js"use strict";

var method = require("../method/core")
var rebase = require("./rebase")

// Method is designed to work with data structures representing application
// state. Calling it with a state and delta should return object representing
// new state, with changes in `delta` being applied to previous.
//
// ## Example
//
// patch(state, {
//   "item-id-1": { completed: false }, // update
//   "item-id-2": null                  // delete
// })
var patch = method("patch@diffpatcher")
patch.define(Object, function patch(hash, delta) {
  return rebase({}, hash, delta)
})

module.exports = patch
PK
!<HR&modules/commonjs/diffpatcher/rebase.js"use strict";

var nil = {}
var owns = ({}).hasOwnProperty

function rebase(result, parent, delta) {
  var key, current, previous, update
  for (key in parent) {
    if (owns.call(parent, key)) {
      previous = parent[key]
      update = owns.call(delta, key) ? delta[key] : nil
      if (previous === null) continue
      else if (previous === void(0)) continue
      else if (update === null) continue
      else if (update === void(0)) continue
      else result[key] = previous
    }
  }
  for (key in delta) {
    if (owns.call(delta, key)) {
      update = delta[key]
      current = owns.call(result, key) ? result[key] : nil
      if (current === update) continue
      else if (update === null) continue
      else if (update === void(0)) continue
      else if (current === nil) result[key] = update
      else if (typeof(update) !== "object") result[key] = update
      else if (typeof(current) !== "object") result[key] = update
      else result[key]= rebase({}, current, update)
    }
  }

  return result
}

module.exports = rebase
PK
!<Vaѯ77+modules/commonjs/diffpatcher/test/common.js"use strict";

require("test").run(require("./index"))
PK
!<(ܦ		)modules/commonjs/diffpatcher/test/diff.js"use strict";

var diff = require("../diff")

exports["test diff from null"] = function(assert) {
  var to = { a: 1, b: 2 }
  assert.equal(diff(null, to), to, "diff null to x returns x")
  assert.equal(diff(void(0), to), to, "diff undefined to x returns x")

}

exports["test diff to null"] = function(assert) {
  var from = { a: 1, b: 2 }
  assert.deepEqual(diff({ a: 1, b: 2 }, null),
                   { a: null, b: null },
                   "diff x null returns x with all properties nullified")
}

exports["test diff identical"] = function(assert) {
  assert.deepEqual(diff({}, {}), {}, "diff on empty objects is {}")

  assert.deepEqual(diff({ a: 1, b: 2 }, { a: 1, b: 2 }), {},
                   "if properties match diff is {}")

  assert.deepEqual(diff({ a: 1, b: { c: { d: 3, e: 4 } } },
                        { a: 1, b: { c: { d: 3, e: 4 } } }), {},
                   "diff between identical nested hashes is {}")

}

exports["test diff delete"] = function(assert) {
  assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2 }), { a: null },
                   "missing property is deleted")
  assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2 }), { a: 2, b: null },
                   "missing property is deleted another updated")
  assert.deepEqual(diff({ a: 1, b: 2 }, {}), { a: null, b: null },
                   "missing propertes are deleted")
  assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, {}),
                   { a: null, b: null },
                   "missing deep propertes are deleted")
  assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { b: { c: {} } }),
                   { a: null, b: { c: { d: null } } },
                   "missing nested propertes are deleted")
}

exports["test add update"] = function(assert) {
  assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2, c: 3 }), { a: null, c: 3 },
                   "delete and add")
  assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2, c: 3 }), { a: 2, b: null, c: 3 },
                   "delete and adds")
  assert.deepEqual(diff({}, { a: 1, b: 2 }), { a: 1, b: 2 },
                   "diff on empty objcet returns equivalen of to")
  assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { d: 3 }),
                   { a: null, b: null, d: 3 },
                   "missing deep propertes are deleted")
  assert.deepEqual(diff({ b: { c: {} }, d: null }, { a: 1, b: { c: { d: 2 } } }),
                   { a: 1, b: { c: { d: 2 } } },
                   "missing nested propertes are deleted")
}
PK
!<k||*modules/commonjs/diffpatcher/test/index.js"use strict";

var diff = require("../diff")
var patch = require("../patch")

exports["test diff"] = require("./diff")
exports["test patch"] = require("./patch")

exports["test patch(a, diff(a, b)) => b"] = function(assert) {
  var a = { a: { b: 1 }, c: { d: 2 } }
  var b = { a: { e: 3 }, c: { d: 4 } }

  assert.deepEqual(patch(a, diff(a, b)), b, "patch(a, diff(a, b)) => b")
}
PK
!<		*modules/commonjs/diffpatcher/test/patch.js"use strict";

var patch = require("../patch")

exports["test patch delete"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, { a: null }), { b: 2 }, "null removes property")
}

exports["test patch delete with void"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
                   "void(0) removes property")
}

exports["test patch delete missing"] = function(assert) {
  assert.deepEqual(patch({ a: 1, b: 2 }, { c: null }),
                   { a: 1, b: 2 },
                   "null removes property if exists");

  assert.deepEqual(patch({ a: 1, b: 2 }, { c: void(0) }),
                   { a: 1, b: 2 },
                   "void removes property if exists");
}

exports["test delete deleted"] = function(assert) {
  assert.deepEqual(patch({ a: null, b: 2, c: 3, d: void(0)},
                         { a: void(0), b: null, d: null }),
                   {c: 3},
                  "removed all existing and non existing");
}

exports["test update deleted"] = function(assert) {
  assert.deepEqual(patch({ a: null, b: void(0), c: 3},
                         { a: { b: 2 } }),
                   { a: { b: 2 }, c: 3 },
                   "replace deleted");
}

exports["test patch delete with void"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
                   "void(0) removes property")
}


exports["test patch addition"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
                   "new properties are added")
}

exports["test patch addition"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
                   "new properties are added")
}

exports["test hash on itself"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, hash), hash,
                   "applying hash to itself returns hash itself")
}

exports["test patch with empty delta"] = function(assert) {
  var hash = { a: 1, b: 2 }

  assert.deepEqual(patch(hash, {}), hash,
                   "applying empty delta results in no changes")
}

exports["test patch nested data"] = function(assert) {
  assert.deepEqual(patch({ a: { b: 1 }, c: { d: 2 } },
                         { a: { b: null, e: 3 }, c: { d: 4 } }),
                   { a: { e: 3 }, c: { d: 4 } },
                   "nested structures can also be patched")
}
PK
!<,55(modules/commonjs/diffpatcher/test/tap.js"use strict";

require("retape")(require("./index"))
PK
!<TmW`3modules/commonjs/framescript/FrameScriptManager.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const globalMM = Components.classes["@mozilla.org/globalmessagemanager;1"].
                 getService(Components.interfaces.nsIMessageListenerManager);

// Load frame scripts from the same dir as this module.
// Since this JSM will be loaded using require(), PATH will be
// overridden while running tests, just like any other module.
const PATH = __URI__.replace('framescript/FrameScriptManager.jsm', '');

// Builds a unique loader ID for this runtime. We prefix with the SDK path so
// overriden versions of the SDK don't conflict
var LOADER_ID = 0;
this.getNewLoaderID = () => {
  return PATH + ":" + LOADER_ID++;
}

const frame_script = function(contentFrame, PATH) {
  let { registerContentFrame } = Components.utils.import(PATH + 'framescript/content.jsm', {});
  registerContentFrame(contentFrame);
}
globalMM.loadFrameScript("data:,(" + frame_script.toString() + ")(this, " + JSON.stringify(PATH) + ");", true);

this.EXPORTED_SYMBOLS = ['getNewLoaderID'];
PK
!<-#

(modules/commonjs/framescript/content.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import('resource://gre/modules/Services.jsm');

const cpmm = Cc['@mozilla.org/childprocessmessagemanager;1'].
             getService(Ci.nsISyncMessageSender);

this.EXPORTED_SYMBOLS = ["registerContentFrame"];

// This may be an overriden version of the SDK so use the PATH as a key for the
// initial messages before we have a loaderID.
const PATH = __URI__.replace('framescript/content.jsm', '');

const { Loader } = Cu.import(PATH + 'toolkit/loader.js', {});

// one Loader instance per addon (per @loader/options to be precise)
var addons = new Map();

// Tell the parent that a new process is ready
cpmm.sendAsyncMessage('sdk/remote/process/start', {
  modulePath: PATH
});

// Load a child process module loader with the given loader options
cpmm.addMessageListener('sdk/remote/process/load', ({ data: { modulePath, loaderID, options, reason } }) => {
  if (modulePath != PATH)
    return;

  // During startup races can mean we get a second load message
  if (addons.has(loaderID))
    return;

  options.waiveInterposition = true;

  let loader = Loader.Loader(options);
  let addon = {
    loader,
    require: Loader.Require(loader, { id: 'LoaderHelper' }),
  }
  addons.set(loaderID, addon);

  cpmm.sendAsyncMessage('sdk/remote/process/attach', {
    loaderID,
    processID: Services.appinfo.processID,
    isRemote: Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
  });

  addon.child = addon.require('sdk/remote/child');

  for (let contentFrame of frames.values())
    addon.child.registerContentFrame(contentFrame);
});

// Unload a child process loader
cpmm.addMessageListener('sdk/remote/process/unload', ({ data: { loaderID, reason } }) => {
  if (!addons.has(loaderID))
    return;

  let addon = addons.get(loaderID);
  Loader.unload(addon.loader, reason);

  // We want to drop the reference to the loader but never allow creating a new
  // loader with the same ID
  addons.set(loaderID, {});
})


var frames = new Set();

this.registerContentFrame = contentFrame => {
  contentFrame.addEventListener("unload", () => {
    unregisterContentFrame(contentFrame);
  });

  frames.add(contentFrame);

  for (let addon of addons.values()) {
    if ("child" in addon)
      addon.child.registerContentFrame(contentFrame);
  }
};

function unregisterContentFrame(contentFrame) {
  frames.delete(contentFrame);

  for (let addon of addons.values()) {
    if ("child" in addon)
      addon.child.unregisterContentFrame(contentFrame);
  }
}
PK
!</!!,modules/commonjs/framescript/context-menu.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { query, constant, cache } = require("sdk/lang/functional");
const { pairs, each, map, object } = require("sdk/util/sequence");
const { nodeToMessageManager } = require("./util");

// Decorator function that takes `f` function and returns one that attempts
// to run `f` with given arguments. In case of exception error is logged
// and `fallback` is returned instead.
const Try = (fn, fallback=null) => (...args) => {
  try {
    return fn(...args);
  } catch(error) {
    console.error(error);
    return fallback;
  }
};

// Decorator funciton that takes `f` function and returns one that returns
// JSON cloned result of whatever `f` returns for given arguments.
const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));

const Null = constant(null);

// Table of readers mapped to field names they're going to be reading.
const readers = Object.create(null);
// Read function takes "contextmenu" event target `node` and returns table of
// read field names mapped to appropriate values. Read uses above defined read
// table to read data for all registered readers.
const read = node =>
  object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));

// Table of built-in readers, each takes a descriptor and returns a reader:
// descriptor -> node -> JSON
const parsers = Object.create(null)
// Function takes a descriptor of the remotely defined reader and parsese it
// to construct a local reader that's going to read out data from context menu
// target.
const parse = descriptor => {
  const parser = parsers[descriptor.category];
  if (!parser) {
    console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
    return Null
  }
  return Try(parser(descriptor));
}

// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const SVG_NS = "http://www.w3.org/2000/svg";

// Firefox always creates a HTMLVideoElement when loading an ogg file
// directly. If the media is actually audio, be smarter and provide a
// context menu with audio operations.
// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
const isVideoLoadingAudio = node =>
  node.readyState >= node.HAVE_METADATA &&
    (node.videoWidth == 0 || node.videoHeight == 0)

const isVideo = node =>
  node instanceof node.ownerGlobal.HTMLVideoElement &&
  !isVideoLoadingAudio(node);

const isAudio = node => {
  const {HTMLVideoElement, HTMLAudioElement} = node.ownerGlobal;
  return node instanceof HTMLAudioElement ? true :
         node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
         false;
};

const isImage = ({namespaceURI, localName}) =>
  namespaceURI === HTML_NS && localName === "img" ? true :
  namespaceURI === XUL_NS && localName === "image" ? true :
  namespaceURI === SVG_NS && localName === "image" ? true :
  false;

parsers["reader/MediaType()"] = constant(node =>
  isImage(node) ? "image" :
  isAudio(node) ? "audio" :
  isVideo(node) ? "video" :
  null);


const readLink = node =>
  node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
  readLink(node.parentNode);

parsers["reader/LinkURL()"] = constant(node =>
  node.matches("a, a *") ? readLink(node) : null);

// Reader that reads out `true` if "contextmenu" `event.target` matches
// `descriptor.selector` and `false` if it does not.
parsers["reader/SelectorMatch()"] = ({selector}) =>
  node => node.matches(selector);

// Accessing `selectionStart` and `selectionEnd` properties on non
// editable input nodes throw exceptions, there for we need this util
// function to guard us against them.
const getInputSelection = node => {
  try {
    if ("selectionStart" in node && "selectionEnd" in node) {
      const {selectionStart, selectionEnd} = node;
      return {selectionStart, selectionEnd}
    }
  }
  catch(_) {}

  return null;
}

// Selection reader does not really cares about descriptor so it is
// a constant function returning selection reader. Selection reader
// returns string of the selected text or `null` if there is no selection.
parsers["reader/Selection()"] = constant(node => {
  const selection = node.ownerDocument.getSelection();
  if (!selection.isCollapsed) {
    return selection.toString();
  }
  // If target node is editable (text, input, textarea, etc..) document does
  // not really handles selections there. There for we fallback to checking
  // `selectionStart` `selectionEnd` properties and if they are present we
  // extract selections manually from the `node.value`.
  else {
    const selection = getInputSelection(node);
    const isSelected = selection &&
                       Number.isInteger(selection.selectionStart) &&
                       Number.isInteger(selection.selectionEnd) &&
                       selection.selectionStart !== selection.selectionEnd;
    return  isSelected ? node.value.substring(selection.selectionStart,
                                              selection.selectionEnd) :
            null;
  }
});

// Query reader just reads out properties from the node, so we just use `query`
// utility function.
parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
// Attribute reader just reads attribute of the event target node.
parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);

// Extractor reader defines generates a reader out of serialized function, who's
// return value is JSON cloned. Note: We do know source will evaluate to function
// as that's what we serialized on the other end, it's also ok if generated function
// is going to throw as registered readers are wrapped in try catch to avoid breakting
// unrelated readers.
parsers["reader/Extractor()"] = ({source}) =>
  JSONReturn(new Function("return (" + source + ")")());

// If the context-menu target node or any of its ancestors is one of these,
// Firefox uses a tailored context menu, and so the page context doesn't apply.
// There for `reader/isPage()` will read `false` in that case otherwise it's going
// to read `true`.
const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
                         "embed", "img", "input", "map", "video", "audio", "menu",
                         "option", "select", "textarea", "[contenteditable=true]"];
const nonPageSelector = nonPageElements.
                          concat(nonPageElements.map(tag => `${tag} *`)).
                          join(", ");

// Note: isPageContext implementation could have actually used SelectorMatch reader,
// but old implementation was also checked for collapsed selection there for to keep
// the behavior same we end up implementing a new reader.
parsers["reader/isPage()"] = constant(node =>
  node.ownerGlobal.getSelection().isCollapsed &&
  !node.matches(nonPageSelector));

// Reads `true` if node is in an iframe otherwise returns true.
parsers["reader/isFrame()"] = constant(node =>
  !!node.ownerGlobal.frameElement);

parsers["reader/isEditable()"] = constant(node => {
  const selection = getInputSelection(node);
  return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
});


// TODO: Add some reader to read out tab id.

const onReadersUpdate = message => {
  each(([id, descriptor]) => {
    if (descriptor) {
      readers[id] = parse(descriptor);
    }
    else {
      delete readers[id];
    }
  }, pairs(message.data));
};
exports.onReadersUpdate = onReadersUpdate;


const onContextMenu = event => {
  if (!event.defaultPrevented) {
    const manager = nodeToMessageManager(event.target);
    manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
  }
};
exports.onContextMenu = onContextMenu;


const onContentFrame = (frame) => {
  // Listen for contextmenu events in on this frame.
  frame.addEventListener("contextmenu", onContextMenu);
  // Listen to registered reader changes and update registry.
  frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);

  // Request table of readers (if this is loaded in a new process some table
  // changes may be missed, this is way to sync up).
  frame.sendAsyncMessage("sdk/context-menu/readers?");
};
exports.onContentFrame = onContentFrame;
PK
!<
	'modules/commonjs/framescript/manager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const mime = "application/javascript";
const requireURI = module.uri.replace("framescript/manager.js",
                                      "toolkit/require.js");

const requireLoadURI = `data:${mime},this["Components"].utils.import("${requireURI}")`

// Loads module with given `id` into given `messageManager` via shared module loader. If `init`
// string is passed, will call module export with that name and pass frame script environment
// of the `messageManager` into it. Since module will load only once per process (which is
// once for chrome proces & second for content process) it is useful to have an init function
// to setup event listeners on each content frame.
const loadModule = (messageManager, id, allowDelayed, init) => {
  const moduleLoadURI = `${requireLoadURI}.require("${id}")`
  const uri = init ? `${moduleLoadURI}.${init}(this)` : moduleLoadURI;
  messageManager.loadFrameScript(uri, allowDelayed);
};
exports.loadModule = loadModule;
PK
!<cA$modules/commonjs/framescript/util.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};


const { Ci } = require("chrome");

const windowToMessageManager = window =>
  window.
    QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIDocShell).
    sameTypeRootTreeItem.
    QueryInterface(Ci.nsIDocShell).
    QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIContentFrameMessageManager);
exports.windowToMessageManager = windowToMessageManager;

const nodeToMessageManager = node =>
  windowToMessageManager(node.ownerGlobal);
exports.nodeToMessageManager = nodeToMessageManager;
PK
!<Qmodules/commonjs/index.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
PK
!<B$modules/commonjs/jetpack-id/index.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Takes parsed `package.json` manifest and returns
 * valid add-on id for it.
 */
function getID(manifest) {
  manifest = manifest || {};

  if (manifest.id) {

    if (typeof manifest.id !== "string") {
      return null;
    }

    // If manifest.id is already valid (as domain or GUID), use it
    if (isValidAOMName(manifest.id)) {
      return manifest.id;
    }
    // Otherwise, this ID is invalid so return `null`
    return null;
  }

  // If no `id` defined, turn `name` into a domain ID,
  // as we transition to `name` being an id, similar to node/npm, but
  // append a '@' to make it compatible with Firefox requirements
  if (manifest.name) {

    if (typeof manifest.name !== "string") {
      return null;
    }

    var modifiedName = "@" + manifest.name;
    return isValidAOMName(modifiedName) ? modifiedName : null;
  }

  // If no `id` or `name` property, return null as this manifest
  // is invalid
  return null;
}

module.exports = getID;

/**
 * Regex taken from XPIProvider.jsm in the Addon Manager to validate proper
 * IDs that are able to be used.
 * http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#209
 */
function isValidAOMName (s) {
  return /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i.test(s || "");
}
PK
!<p!!modules/commonjs/method/core.js"use strict";

var defineProperty = Object.defineProperty || function(object, name, property) {
  object[name] = property.value
  return object
}

// Shortcut for `Object.prototype.toString` for faster access.
var typefy = Object.prototype.toString

// Map to for jumping from typeof(value) to associated type prefix used
// as a hash in the map of builtin implementations.
var types = { "function": "Object", "object": "Object" }

// Array is used to save method implementations for the host objects in order
// to avoid extending them with non-primitive values that could cause leaks.
var host = []
// Hash map is used to save method implementations for builtin types in order
// to avoid extending their prototypes. This also allows to share method
// implementations for types across diff contexts / frames / compartments.
var builtin = {}

function Primitive() {}
function ObjectType() {}
ObjectType.prototype = new Primitive()
function ErrorType() {}
ErrorType.prototype = new ObjectType()

var Default = builtin.Default = Primitive.prototype
var Null = builtin.Null = new Primitive()
var Void = builtin.Void = new Primitive()
builtin.String = new Primitive()
builtin.Number = new Primitive()
builtin.Boolean = new Primitive()

builtin.Object = ObjectType.prototype
builtin.Error = ErrorType.prototype

builtin.EvalError = new ErrorType()
builtin.InternalError = new ErrorType()
builtin.RangeError = new ErrorType()
builtin.ReferenceError = new ErrorType()
builtin.StopIteration = new ErrorType()
builtin.SyntaxError = new ErrorType()
builtin.TypeError = new ErrorType()
builtin.URIError = new ErrorType()


function Method(hint) {
  /**
  Private Method is a callable private name that dispatches on the first
  arguments same named Method:

      method(object, ...rest) => object[method](...rest)

  Optionally hint string may be provided that will be used in generated names
  to ease debugging.

  ## Example

      var foo = Method()

      // Implementation for any types
      foo.define(function(value, arg1, arg2) {
        // ...
      })

      // Implementation for a specific type
      foo.define(BarType, function(bar, arg1, arg2) {
        // ...
      })
  **/

  // Create an internal unique name if `hint` is provided it is used to
  // prefix name to ease debugging.
  var name = (hint || "") + "#" + Math.random().toString(32).substr(2)

  function dispatch(value) {
    // Method dispatches on type of the first argument.
    // If first argument is `null` or `void` associated implementation is
    // looked up in the `builtin` hash where implementations for built-ins
    // are stored.
    var type = null
    var method = value === null ? Null[name] :
                 value === void(0) ? Void[name] :
                 // Otherwise attempt to use method with a generated private
                 // `name` that is supposedly in the prototype chain of the
                 // `target`.
                 value[name] ||
                 // Otherwise assume it's one of the built-in type instances,
                 // in which case implementation is stored in a `builtin` hash.
                 // Attempt to find a implementation for the given built-in
                 // via constructor name and method name.
                 ((type = builtin[(value.constructor || "").name]) &&
                  type[name]) ||
                 // Otherwise assume it's a host object. For host objects
                 // actual method implementations are stored in the `host`
                 // array and only index for the implementation is stored
                 // in the host object's prototype chain. This avoids memory
                 // leaks that otherwise could happen when saving JS objects
                 // on host object.
                 host[value["!" + name] || void(0)] ||
                 // Otherwise attempt to lookup implementation for builtins by
                 // a type of the value. This basically makes sure that all
                 // non primitive values will delegate to an `Object`.
                 ((type = builtin[types[typeof(value)]]) && type[name])


    // If method implementation for the type is still not found then
    // just fallback for default implementation.
    method = method || Default[name]


    // If implementation is still not found (which also means there is no
    // default) just throw an error with a descriptive message.
    if (!method) throw TypeError("Type does not implements method: " + name)

    // If implementation was found then just delegate.
    return method.apply(method, arguments)
  }

  // Make `toString` of the dispatch return a private name, this enables
  // method definition without sugar:
  //
  //    var method = Method()
  //    object[method] = function() { /***/ }
  dispatch.toString = function toString() { return name }

  // Copy utility methods for convenient API.
  dispatch.implement = implementMethod
  dispatch.define = defineMethod

  return dispatch
}

// Create method shortcuts form functions.
var defineMethod = function defineMethod(Type, lambda) {
  return define(this, Type, lambda)
}
var implementMethod = function implementMethod(object, lambda) {
  return implement(this, object, lambda)
}

// Define `implement` and `define` polymorphic methods to allow definitions
// and implementations through them.
var implement = Method("implement")
var define = Method("define")


function _implement(method, object, lambda) {
  /**
  Implements `Method` for the given `object` with a provided `implementation`.
  Calling `Method` with `object` as a first argument will dispatch on provided
  implementation.
  **/
  return defineProperty(object, method.toString(), {
    enumerable: false,
    configurable: false,
    writable: false,
    value: lambda
  })
}

function _define(method, Type, lambda) {
  /**
  Defines `Method` for the given `Type` with a provided `implementation`.
  Calling `Method` with a first argument of this `Type` will dispatch on
  provided `implementation`. If `Type` is a `Method` default implementation
  is defined. If `Type` is a `null` or `undefined` `Method` is implemented
  for that value type.
  **/

  // Attempt to guess a type via `Object.prototype.toString.call` hack.
  var type = Type && typefy.call(Type.prototype)

  // If only two arguments are passed then `Type` is actually an implementation
  // for a default type.
  if (!lambda) Default[method] = Type
  // If `Type` is `null` or `void` store implementation accordingly.
  else if (Type === null) Null[method] = lambda
  else if (Type === void(0)) Void[method] = lambda
  // If `type` hack indicates built-in type and type has a name us it to
  // store a implementation into associated hash. If hash for this type does
  // not exists yet create one.
  else if (type !== "[object Object]" && Type.name) {
    var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType())
    Bulitin[method] = lambda
  }
  // If `type` hack indicates an object, that may be either object or any
  // JS defined "Class". If name of the constructor is `Object`, assume it's
  // built-in `Object` and store implementation accordingly.
  else if (Type.name === "Object")
    builtin.Object[method] = lambda
  // Host objects are pain!!! Every browser does some crazy stuff for them
  // So far all browser seem to not implement `call` method for host object
  // constructors. If that is a case here, assume it's a host object and
  // store implementation in a `host` array and store `index` in the array
  // in a `Type.prototype` itself. This avoids memory leaks that could be
  // caused by storing JS objects on a host objects.
  else if (Type.call === void(0)) {
    var index = host.indexOf(lambda)
    if (index < 0) index = host.push(lambda) - 1
    // Prefix private name with `!` so it can be dispatched from the method
    // without type checks.
    implement("!" + method, Type.prototype, index)
  }
  // If Got that far `Type` is user defined JS `Class`. Define private name
  // as hidden property on it's prototype.
  else
    implement(method, Type.prototype, lambda)
}

// And provided implementations for a polymorphic equivalents.
_define(define, _define)
_define(implement, _implement)

// Define exports on `Method` as it's only thing being exported.
Method.implement = implement
Method.define = define
Method.Method = Method
Method.method = Method
Method.builtin = builtin
Method.host = host

module.exports = Method
PK
!<?TT'modules/commonjs/method/test/browser.js"use strict";

exports["test common"] = require("./common")

var Method = require("../core")

exports["test host objects"] = function(assert) {
  var isElement = Method("is-element")
  isElement.define(function() { return false })

  isElement.define(Element, function() { return true })

  assert.notDeepEqual(typeof(Element.prototype[isElement]), "number",
                     "Host object's prototype is extended with a number value")

  assert.ok(!isElement({}), "object is not an Element")
  assert.ok(document.createElement("div"), "Element is an element")
}

require("test").run(exports)
PK
!<_1&modules/commonjs/method/test/common.js"use strict";

var Method = require("../core")

function type(value) {
  return Object.prototype.toString.call(value).
    split(" ").
    pop().
    split("]").
    shift().
    toLowerCase()
}

var values = [
  null,                   // 0
  undefined,              // 1
  Infinity,               // 2
  NaN,                    // 3
  5,                      // 4
  {},                     // 5
  Object.create({}),      // 6
  Object.create(null),    // 7
  [],                     // 8
  /foo/,                  // 9
  new Date(),             // 10
  Function,               // 11
  function() {},          // 12
  true,                   // 13
  false,                  // 14
  "string"                // 15
]

function True() { return true }
function False() { return false }

var trues = values.map(True)
var falses = values.map(False)

exports["test throws if not implemented"] = function(assert) {
  var method = Method("nope")

  assert.throws(function() {
    method({})
  }, /not implement/i, "method throws if not implemented")

  assert.throws(function() {
    method(null)
  }, /not implement/i, "method throws on null")
}

exports["test all types inherit from default"] = function(assert) {
  var isImplemented = Method("isImplemented")
  isImplemented.define(function() { return true })

  values.forEach(function(value) {
    assert.ok(isImplemented(value),
              type(value) + " inherits deafult implementation")
  })
}

exports["test default can be implemented later"] = function(assert) {
  var isImplemented = Method("isImplemented")
  isImplemented.define(function() {
    return true
  })

  values.forEach(function(value) {
    assert.ok(isImplemented(value),
              type(value) + " inherits deafult implementation")
  })
}

exports["test dispatch not-implemented"] = function(assert) {
  var isDefault = Method("isDefault")
  values.forEach(function(value) {
    assert.throws(function() {
      isDefault(value)
    }, /not implement/, type(value) + " throws if not implemented")
  })
}

exports["test dispatch default"] = function(assert) {
  var isDefault = Method("isDefault")

  // Implement default
  isDefault.define(True)
  assert.deepEqual(values.map(isDefault), trues,
                   "all implementation inherit from default")

}

exports["test dispatch null"] = function(assert) {
  var isNull = Method("isNull")

  // Implement default
  isNull.define(False)
  isNull.define(null, True)
  assert.deepEqual(values.map(isNull),
                   [ true ].
                   concat(falses.slice(1)),
                   "only null gets methods defined for null")
}

exports["test dispatch undefined"] = function(assert) {
  var isUndefined = Method("isUndefined")

  // Implement default
  isUndefined.define(False)
  isUndefined.define(undefined, True)
  assert.deepEqual(values.map(isUndefined),
                   [ false, true ].
                   concat(falses.slice(2)),
                   "only undefined gets methods defined for undefined")
}

exports["test dispatch object"] = function(assert) {
  var isObject = Method("isObject")

  // Implement default
  isObject.define(False)
  isObject.define(Object, True)
  assert.deepEqual(values.map(isObject),
                   [ false, false, false, false, false ].
                   concat(trues.slice(5, 13)).
                   concat([false, false, false]),
                   "all values except primitives inherit Object methods")

}

exports["test dispatch number"] = function(assert) {
  var isNumber = Method("isNumber")
  isNumber.define(False)
  isNumber.define(Number, True)

  assert.deepEqual(values.map(isNumber),
                  falses.slice(0, 2).
                  concat(true, true, true).
                  concat(falses.slice(5)),
                  "all numbers inherit from Number method")
}

exports["test dispatch string"] = function(assert) {
  var isString = Method("isString")
  isString.define(False)
  isString.define(String, True)

  assert.deepEqual(values.map(isString),
                  falses.slice(0, 15).
                  concat(true),
                  "all strings inherit from String method")
}

exports["test dispatch function"] = function(assert) {
  var isFunction = Method("isFunction")
  isFunction.define(False)
  isFunction.define(Function, True)

  assert.deepEqual(values.map(isFunction),
                  falses.slice(0, 11).
                  concat(true, true).
                  concat(falses.slice(13)),
                  "all functions inherit from Function method")
}

exports["test dispatch date"] = function(assert) {
  var isDate = Method("isDate")
  isDate.define(False)
  isDate.define(Date, True)

  assert.deepEqual(values.map(isDate),
                  falses.slice(0, 10).
                  concat(true).
                  concat(falses.slice(11)),
                  "all dates inherit from Date method")
}

exports["test dispatch RegExp"] = function(assert) {
  var isRegExp = Method("isRegExp")
  isRegExp.define(False)
  isRegExp.define(RegExp, True)

  assert.deepEqual(values.map(isRegExp),
                  falses.slice(0, 9).
                  concat(true).
                  concat(falses.slice(10)),
                  "all regexps inherit from RegExp method")
}

exports["test redefine for descendant"] = function(assert) {
  var isFoo = Method("isFoo")
  var ancestor = {}
  isFoo.implement(ancestor, function() { return true })
  var descendant = Object.create(ancestor)
  isFoo.implement(descendant, function() { return false })

  assert.ok(isFoo(ancestor), "defined on ancestor")
  assert.ok(!isFoo(descendant), "overrided for descendant")
}

exports["test on custom types"] = function(assert) {
  function Bar() {}
  var isBar = Method("isBar")

  isBar.define(function() { return false })
  isBar.define(Bar, function() { return true })

  assert.ok(!isBar({}), "object is get's default implementation")
  assert.ok(isBar(new Bar()), "Foo type objects get own implementation")

  var isObject = Method("isObject")
  isObject.define(function() { return false })
  isObject.define(Object, function() { return true })

  assert.ok(isObject(new Bar()), "foo inherits implementation from object")


  isObject.define(Bar, function() { return false })

  assert.ok(!isObject(new Bar()),
            "implementation inherited form object can be overrided")
}


exports["test error types"] = function(assert) {
  var isError = Method("isError")
  isError.define(function() { return false })
  isError.define(Error, function() { return true })

  assert.ok(isError(Error("boom")), "error is error")
  assert.ok(isError(TypeError("boom")), "type error is an error")
  assert.ok(isError(EvalError("boom")), "eval error is an error")
  assert.ok(isError(RangeError("boom")), "range error is an error")
  assert.ok(isError(ReferenceError("boom")), "reference error is an error")
  assert.ok(isError(SyntaxError("boom")), "syntax error is an error")
  assert.ok(isError(URIError("boom")), "URI error is an error")
}

exports["test override define polymorphic method"] = function(assert) {
  var define = Method.define
  var implement = Method.implement

  var fn = Method("fn")
  var methods = {}
  implement(define, fn, function(method, label, implementation) {
    methods[label] = implementation
  })

  function foo() {}

  define(fn, "foo-case", foo)

  assert.equal(methods["foo-case"], foo, "define set property")
}

exports["test override define via method API"] = function(assert) {
  var define = Method.define
  var implement = Method.implement

  var fn = Method("fn")
  var methods = {}
  define.implement(fn, function(method, label, implementation) {
    methods[label] = implementation
  })

  function foo() {}

  define(fn, "foo-case", foo)

  assert.equal(methods["foo-case"], foo, "define set property")
}

require("test").run(exports)
PK
!<$bxx4modules/commonjs/mozilla-toolkit-versioning/index.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var versionParse = require('./lib/utils').versionParse;

var COMPARATORS = ['>=', '<=', '>', '<', '=', '~', '^'];

exports.parse = function (input) {
  input = input || '';
  input = input.trim();
  if (!input)
    throw new Error('`parse` argument must be a populated string.');

  // Handle the "*" case
  if (input === "*") {
    return { min: undefined, max: undefined };
  }

  var inputs = input.split(' ');
  var min;
  var max;

  // 1.2.3 - 2.3.4
  if (inputs.length === 3 && inputs[1] === '-') {
    return { min: inputs[0], max: inputs[2] };
  }

  inputs.forEach(function (input) {
    var parsed = parseExpression(input);
    var version = parsed.version;
    var comparator = parsed.comparator;

    // 1.2.3
    if (inputs.length === 1 && !comparator)
      min = max = version;

    // Parse min
    if (~comparator.indexOf('>')) {
      if (~comparator.indexOf('='))
        min = version; // >=1.2.3
      else
        min = increment(version); // >1.2.3
    }
    else if (~comparator.indexOf('<')) {
      if (~comparator.indexOf('='))
        max = version; // <=1.2.3
      else
        max = decrement(version); // <1.2.3
    }
  });

  return {
    min: min,
    max : max
  };
};

function parseExpression (input) {
  for (var i = 0; i < COMPARATORS.length; i++)
    if (~input.indexOf(COMPARATORS[i]))
      return {
        comparator: COMPARATORS[i],
        version: input.substr(COMPARATORS[i].length)
      };
  return { version: input, comparator: '' };
}

/**
 * Takes a version string ('1.2.3') and returns a version string
 * that'll parse as one less than the input string ('1.2.3.-1').
 *
 * @param {String} vString
 * @return {String}
 */
function decrement (vString) {
  return vString + (vString.charAt(vString.length - 1) === '.' ? '' : '.') + '-1';
}
exports.decrement = decrement;

/**
 * Takes a version string ('1.2.3') and returns a version string
 * that'll parse as greater than the input string by the smallest margin
 * possible ('1.2.3.1').
 * listed as number-A, string-B, number-C, string-D in
 * Mozilla's Toolkit Format.
 * https://developer.mozilla.org/en-US/docs/Toolkit_version_format
 *
 * @param {String} vString
 * @return {String}
 */
function increment (vString) {
  var match = versionParse(vString);
  var a = match[1];
  var b = match[2];
  var c = match[3];
  var d = match[4];
  var lastPos = vString.length - 1;
  var lastChar = vString.charAt(lastPos);

  if (!b) {
    return vString + (lastChar === '.' ? '' : '.') + '1';
  }
  if (!c) {
    return vString + '1';
  }
  if (!d) {
    return vString.substr(0, lastPos) + (++lastChar);
  }
  return vString.substr(0, lastPos) + String.fromCharCode(lastChar.charCodeAt(0) + 1);
}
exports.increment = increment;
PK
!<q8modules/commonjs/mozilla-toolkit-versioning/lib/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Breaks up a version string into the 4 components
 * defined in:
 * https://developer.mozilla.org/en-US/docs/Toolkit_version_format
 * @params {String} val
 * @return {String}
 */
function versionParse (val) {
  return val.match(/^([0-9\.]*)([a-zA-Z]*)([0-9\.]*)([a-zA-Z]*)$/);
}
exports.versionParse = versionParse;
PK
!< 	 	modules/commonjs/node/os.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require('chrome');
const system = require('../sdk/system');
const runtime = require('../sdk/system/runtime');
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const isWindows = system.platform === 'win32';
const endianness = ((new Uint32Array((new Uint8Array([1,2,3,4])).buffer))[0] === 0x04030201) ? 'LE' : 'BE';

XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
  try {
    return Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
  } catch (e) {
    return "";
  }
});

XPCOMUtils.defineLazyGetter(this, "hostname", () => {
  try {
    // On some platforms (Linux according to try), this service does not exist and fails.
    return Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
  } catch (e) {
    return "";
  }
});

/**
 * Returns a path to a temp directory
 */
exports.tmpdir = () => system.pathFor('TmpD');

/**
 * Returns the endianness of the architecture: either 'LE' or 'BE'
 */
exports.endianness = () => endianness;

/**
 * Returns hostname of the machine
 */
exports.hostname = () => hostname;

/**
 * Name of the OS type
 * Possible values:
 * https://developer.mozilla.org/en/OS_TARGET
 */
exports.type = () => runtime.OS;

/**
 * Name of the OS Platform in lower case string.
 * Possible values:
 * https://developer.mozilla.org/en/OS_TARGET
 */
exports.platform = () => system.platform;

/**
 * Type of processor architecture running:
 * 'arm', 'ia32', 'x86', 'x64'
 */
exports.arch = () => system.architecture;

/**
 * Returns the operating system release.
 */
exports.release = () => {
  let match = oscpu.match(/(\d[\.\d]*)/);
  return match && match.length > 1 ? match[1] : oscpu;
};

/**
 * Returns EOL character for the OS
 */
exports.EOL = isWindows ? '\r\n' : '\n';

/**
 * Returns [0, 0, 0], as this is not implemented.
 */
exports.loadavg = () => [0, 0, 0];

['uptime', 'totalmem', 'freemem', 'cpus'].forEach(method => {
  exports[method] = () => { throw new Error('os.' + method + ' is not supported.'); };
});
PK
!<$'modules/commonjs/sdk/addon/bootstrap.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cu } = require("chrome");
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
const { readURI } = require("sdk/net/url");
const { mount, unmount } = require("sdk/uri/resource");
const { setTimeout } = require("sdk/timers");
const { Loader, Require, Module, main, unload } = require("toolkit/loader");
const prefs = require("sdk/preferences/service");

// load below now, so that it can be used by sdk/addon/runner
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});

const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
                 "install", "uninstall", "upgrade", "downgrade" ];

const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
// Takes add-on ID and normalizes it to a domain name so that add-on
// can be mapped to resource://domain/
const readDomain = id =>
  // If only `@` character is the first one, than just substract it,
  // otherwise fallback to legacy normalization code path. Note: `.`
  // is valid character for resource substitutaiton & we intend to
  // make add-on URIs intuitive, so it's best to just stick to an
  // add-on author typed input.
  id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
  id.toLowerCase().
     replace(/@/g, "-at-").
     replace(/\./g, "-dot-").
     replace(UUID_PATTERN, "$1");

const readPaths = id => {
  const base = `extensions.modules.${id}.path.`;
  const domain = readDomain(id);
  return prefs.keys(base).reduce((paths, key) => {
    const value = prefs.get(key);
    const name = key.replace(base, "");
    const path = name.split(".").join("/");
    const prefix = path.length ? `${path}/` : path;
    const uri = value.endsWith("/") ? value : `${value}/`;
    const root = `extensions.modules.${domain}.commonjs.path.${name}`;

    mount(root, uri);

    paths[prefix] = `resource://${root}/`;
    return paths;
  }, {});
};

const Bootstrap = function(mountURI) {
  this.mountURI = mountURI;
  this.install = this.install.bind(this);
  this.uninstall = this.uninstall.bind(this);
  this.startup = this.startup.bind(this);
  this.shutdown = this.shutdown.bind(this);
};
Bootstrap.prototype = {
  constructor: Bootstrap,
  mount(domain, rootURI) {
    mount(domain, rootURI);
    this.domain = domain;
  },
  unmount() {
    if (this.domain) {
      unmount(this.domain);
      this.domain = null;
    }
  },
  install(addon, reason) {
    return new Promise(resolve => resolve());
  },
  uninstall(addon, reason) {
    return new Promise(resolve => {
      const {id} = addon;

      prefs.reset(`extensions.${id}.sdk.domain`);
      prefs.reset(`extensions.${id}.sdk.version`);
      prefs.reset(`extensions.${id}.sdk.rootURI`);
      prefs.reset(`extensions.${id}.sdk.baseURI`);
      prefs.reset(`extensions.${id}.sdk.load.reason`);

      resolve();
    });
  },
  startup(addon, reasonCode) {
    const { id, version, resourceURI: { spec: addonURI } } = addon;
    const rootURI = this.mountURI || addonURI;
    const reason = REASON[reasonCode];
    const self = this;

    return spawn(function*() {
      const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
      const domain = readDomain(id);
      const baseURI = `resource://${domain}/`;

      this.mount(domain, rootURI);

      prefs.set(`extensions.${id}.sdk.domain`, domain);
      prefs.set(`extensions.${id}.sdk.version`, version);
      prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
      prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
      prefs.set(`extensions.${id}.sdk.load.reason`, reason);

      const command = prefs.get(`extensions.${id}.sdk.load.command`);

      const loader = Loader({
        id,
        isNative: true,
        checkCompatibility: true,
        prefixURI: baseURI,
        rootURI: baseURI,
        name: metadata.name,
        paths: Object.assign({
          "": "resource://gre/modules/commonjs/",
          "devtools/": "resource://devtools/",
          "./": baseURI
        }, readPaths(id)),
        manifest: metadata,
        metadata: metadata,
        modules: {
          "@test/options": {},
        },
        noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
      });
      self.loader = loader;

      const module = Module("package.json", `${baseURI}package.json`);
      const require = Require(loader, module);
      const main = command === "test" ? "sdk/test/runner" : null;
      const prefsURI = `${baseURI}defaults/preferences/prefs.js`;

      // Init the 'sdk/webextension' module from the bootstrap addon parameter.
      if (addon.webExtension)
        require("sdk/webextension").initFromBootstrapAddonParam(addon);

      const { startup } = require("sdk/addon/runner");
      startup(reason, {loader, main, prefsURI});
    }.bind(this)).catch(error => {
      console.error(`Failed to start ${id} addon`, error);
      throw error;
    });
  },
  shutdown(addon, code) {
    this.unmount();
    return this.unload(REASON[code]);
  },
  unload(reason) {
    return new Promise(resolve => {
      const { loader } = this;
      if (loader) {
        this.loader = null;
        unload(loader, reason);

        setTimeout(() => {
          for (let uri of Object.keys(loader.sandboxes)) {
            let sandbox = loader.sandboxes[uri];
            if (Cu.getClassName(sandbox, true) == "Sandbox")
              Cu.nukeSandbox(sandbox);
            delete loader.sandboxes[uri];
            delete loader.modules[uri];
          }

          try {
            Cu.nukeSandbox(loader.sharedGlobalSandbox);
          } catch (e) {
            Cu.reportError(e);
          }

          resolve();
        }, 1000);
      }
      else {
        resolve();
      }
    });
  }
};
exports.Bootstrap = Bootstrap;
PK
!<fw$modules/commonjs/sdk/addon/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'experimental'
};

var { request: hostReq, response: hostRes } = require('./host');
var { defer: async } = require('../lang/functional');
var { defer } = require('../core/promise');
var { emit: emitSync, on, off } = require('../event/core');
var { uuid } = require('../util/uuid');
var emit = async(emitSync);

// Map of IDs to deferreds
var requests = new Map();

// May not be necessary to wrap this in `async`
// once promises are async via bug 881047
var receive = async(function ({data, id, error}) {
  let request = requests.get(id);
  if (request) {
    if (error) request.reject(error);
    else request.resolve(clone(data));
    requests.delete(id);
  }
});
on(hostRes, 'data', receive);

/*
 * Send is a helper to be used in client APIs to send
 * a request to host
 */
function send (eventName, data) {
  let id = uuid();
  let deferred = defer();
  requests.set(id, deferred);
  emit(hostReq, 'data', {
    id: id,
    data: clone(data),
    event: eventName
  });
  return deferred.promise;
}
exports.send = send;

/*
 * Implement internal structured cloning algorithm in the future?
 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#internal-structured-cloning-algorithm
 */
function clone (obj) {
  return JSON.parse(JSON.stringify(obj || {}));
}
PK
!<J@@"modules/commonjs/sdk/addon/host.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

exports.request = {};
exports.response = {};
PK
!<,@(

'modules/commonjs/sdk/addon/installer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cu } = require("chrome");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
const { defer } = require("../core/promise");
const { setTimeout } = require("../timers");

/**
 * `install` method error codes:
 *
 * https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors
 */
exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE;
exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH;
exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE;
exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS;

/**
 * Immediatly install an addon.
 *
 * @param {String} xpiPath
 *   file path to an xpi file to install
 * @return {Promise}
 *   A promise resolved when the addon is finally installed.
 *   Resolved with addon id as value or rejected with an error code.
 */
exports.install = function install(xpiPath) {
  let { promise, resolve, reject } = defer();

  // Create nsIFile for the xpi file
  let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
  try {
    file.initWithPath(xpiPath);
  }
  catch(e) {
    reject(exports.ERROR_FILE_ACCESS);
    return promise;
  }

  // Listen for installation end
  let listener = {
    onInstallEnded: function(aInstall, aAddon) {
      aInstall.removeListener(listener);
      // Bug 749745: on FF14+, onInstallEnded is called just before `startup()`
      // is called, but we expect to resolve the promise only after it.
      // As startup is called synchronously just after onInstallEnded,
      // a simple setTimeout(0) is enough
      setTimeout(resolve, 0, aAddon.id);
    },
    onInstallFailed: function (aInstall) {
      aInstall.removeListener(listener);
      reject(aInstall.error);
    },
    onDownloadFailed: function(aInstall) {
      this.onInstallFailed(aInstall);
    }
  };

  // Order AddonManager to install the addon
  AddonManager.getInstallForFile(file, function(install) {
    if (install.error == 0) {
      install.addListener(listener);
      install.install();
    } else {
      reject(install.error);
    }
  });

  return promise;
};

exports.uninstall = function uninstall(addonId) {
  let { promise, resolve, reject } = defer();

  // Listen for uninstallation end
  let listener = {
    onUninstalled: function onUninstalled(aAddon) {
      if (aAddon.id != addonId)
        return;
      AddonManager.removeAddonListener(listener);
      resolve();
    }
  };
  AddonManager.addAddonListener(listener);

  // Order Addonmanager to uninstall the addon
  getAddon(addonId).then(addon => addon.uninstall(), reject);

  return promise;
};

exports.disable = function disable(addonId) {
  return getAddon(addonId).then(addon => {
    addon.userDisabled = true;
    return addonId;
  });
};

exports.enable = function enabled(addonId) {
  return getAddon(addonId).then(addon => {
    addon.userDisabled = false;
    return addonId;
  });
};

exports.isActive = function isActive(addonId) {
  return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled);
};

const getAddon = function getAddon (id) {
  let { promise, resolve, reject } = defer();
  AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject());
  return promise;
}
exports.getAddon = getAddon;
PK
!<I33%modules/commonjs/sdk/addon/manager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const { defer } = require("../core/promise");

function getAddonByID(id) {
  let { promise, resolve } = defer();
  AddonManager.getAddonByID(id, resolve);
  return promise;
}
exports.getAddonByID = getAddonByID;
PK
!<oj$modules/commonjs/sdk/addon/runner.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cu } = require('chrome');
const { rootURI, metadata, isNative } = require('@loader/options');
const { id, loadReason } = require('../self');
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
const { exit, env, staticArgs } = require('../system');
const { when: unload } = require('../system/unload');
const globals = require('../system/globals');
const { get } = require('../preferences/service');
const { preferences } = metadata;

const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "DevToolsShim", function () {
  return Cu.import("chrome://devtools-shim/content/DevToolsShim.jsm", {}).
         DevToolsShim;
});

// Initializes default preferences
function setDefaultPrefs(prefsURI) {
  const prefs = Cc['@mozilla.org/preferences-service;1'].
                getService(Ci.nsIPrefService).
                QueryInterface(Ci.nsIPrefBranch2);
  const branch = prefs.getDefaultBranch('');
  const sandbox = Sandbox({
    name: prefsURI,
    prototype: {
      pref: function(key, val) {
        switch (typeof val) {
          case 'boolean':
            branch.setBoolPref(key, val);
            break;
          case 'number':
            if (val % 1 == 0) // number must be a integer, otherwise ignore it
              branch.setIntPref(key, val);
            break;
          case 'string':
            branch.setCharPref(key, val);
            break;
        }
      }
    }
  });
  // load preferences.
  evaluate(sandbox, prefsURI);
}

function definePseudo(loader, id, exports) {
  let uri = resolveURI(id, loader.mapping);
  loader.modules[uri] = { exports: exports };
}

function startup(reason, options) {
  return Startup.onceInitialized.then(() => {
    // Inject globals ASAP in order to have console API working ASAP
    Object.defineProperties(options.loader.globals, descriptor(globals));

    // NOTE: Module is intentionally required only now because it relies
    // on existence of hidden window, which does not exists until startup.
    let { ready } = require('../addon/window');
    // Load localization manifest and .properties files.
    // Run the addon even in case of error (best effort approach)
    require('../l10n/loader').
      load(rootURI).
      catch(function failure(error) {
        if (!isNative)
          console.info("Error while loading localization: " + error.message);
      }).
      then(function onLocalizationReady(data) {
        // Exports data to a pseudo module so that api-utils/l10n/core
        // can get access to it
        definePseudo(options.loader, '@l10n/data', data ? data : null);
        return ready;
      }).then(function() {
        run(options);
      }).catch(console.exception);
    return void 0; // otherwise we raise a warning, see bug 910304
  });
}

function run(options) {
  try {
    // Try initializing HTML localization before running main module. Just print
    // an exception in case of error, instead of preventing addon to be run.
    try {
      // Do not enable HTML localization while running test as it is hard to
      // disable. Because unit tests are evaluated in a another Loader who
      // doesn't have access to this current loader.
      if (options.main !== 'sdk/test/runner') {
        require('../l10n/html').enable();
      }
    }
    catch(error) {
      console.exception(error);
    }

    // native-options does stuff directly with preferences key from package.json
    if (preferences && preferences.length > 0) {
      try {
        require('../preferences/native-options').
          enable({ preferences: preferences, id: id }).
          catch(console.exception);
      }
      catch (error) {
        console.exception(error);
      }
    }
    else {
      // keeping support for addons packaged with older SDK versions,
      // when cfx didn't include the 'preferences' key in @loader/options

      // Initialize inline options localization, without preventing addon to be
      // run in case of error
      try {
        require('../l10n/prefs').enable();
      }
      catch(error) {
        console.exception(error);
      }

      // TODO: When bug 564675 is implemented this will no longer be needed
      // Always set the default prefs, because they disappear on restart
      if (options.prefsURI) {
        // Only set if `prefsURI` specified
        try {
          setDefaultPrefs(options.prefsURI);
        }
        catch (err) {
          // cfx bootstrap always passes prefsURI, even in addons without prefs
        }
      }
    }

    // this is where the addon's main.js finally run.
    let program = main(options.loader, options.main);

    if (typeof(program.onUnload) === 'function')
      unload(program.onUnload);

    if (typeof(program.main) === 'function') {
      program.main({
        loadReason: loadReason,
        staticArgs: staticArgs
      }, {
        print: function print(_) { dump(_ + '\n') },
        quit: exit
      });
    }

    if (get("extensions." + id + ".sdk.debug.show", false)) {
      DevToolsShim.initBrowserToolboxProcessForAddon(id);
    }
  } catch (error) {
    console.exception(error);
    throw error;
  }
}
exports.startup = startup;

// If add-on is lunched via `cfx run` we need to use `system.exit` to let
// cfx know we're done (`cfx test` will take care of exit so we don't do
// anything here).
if (env.CFX_COMMAND === 'run') {
  unload(function(reason) {
    if (reason === 'shutdown')
      exit(0);
  });
}
PK
!<0({{$modules/commonjs/sdk/addon/window.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Ci, Cc, Cu } = require("chrome");
const { when: unload } = require("../system/unload");
const prefs = require("../preferences/service");

if (!prefs.get("extensions.usehiddenwindow", false)) {
  const {HiddenFrame} = require("resource://gre/modules/HiddenFrame.jsm", {});
  let hiddenFrame = new HiddenFrame();
  exports.window = hiddenFrame.getWindow();
  exports.ready = hiddenFrame.get();

  // Still destroy frame on unload to claim memory back early.
  // NOTE: this doesn't seem to work and just doesn't get called. :-\
  unload(function() {
    hiddenFrame.destroy();
    hiddenFrame = null;
  });
} else {
  const { make: makeWindow, getHiddenWindow } = require("../window/utils");
  const { create: makeFrame, getDocShell } = require("../frame/utils");
  const { defer } = require("../core/promise");
  const cfxArgs = require("../test/options");

  var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"].
                       createInstance(Ci.nsIPrincipal);

  var hiddenWindow = getHiddenWindow();

  if (cfxArgs.parseable) {
    console.info("hiddenWindow document.documentURI:" +
      hiddenWindow.document.documentURI);
    console.info("hiddenWindow document.readyState:" +
      hiddenWindow.document.readyState);
  }

  // Once Bug 565388 is fixed and shipped we'll be able to make invisible,
  // permanent docShells. Meanwhile we create hidden top level window and
  // use it's docShell.
  var frame = makeFrame(hiddenWindow.document, {
    nodeName: "iframe",
    namespaceURI: "http://www.w3.org/1999/xhtml",
    allowJavascript: true,
    allowPlugins: true
  })
  var docShell = getDocShell(frame);
  var eventTarget = docShell.chromeEventHandler;

  // We need to grant docShell system principals in order to load XUL document
  // from data URI into it.
  docShell.createAboutBlankContentViewer(addonPrincipal);

  // Get a reference to the DOM window of the given docShell and load
  // such document into that would allow us to create XUL iframes, that
  // are necessary for hidden frames etc..
  var window = docShell.contentViewer.DOMDocument.defaultView;
  window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>";

  // Create a promise that is delivered once add-on window is interactive,
  // used by add-on runner to defer add-on loading until window is ready.
  var { promise, resolve } = defer();
  eventTarget.addEventListener("DOMContentLoaded", function(event) {
    resolve();
  }, {once: true});

  exports.ready = promise;
  exports.window = window;

  // Still close window on unload to claim memory back early.
  unload(function() {
    window.close()
    frame.remove();
  });
}
PK
!<oxmodules/commonjs/sdk/base64.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cu } = require("chrome");

// Passing an empty object as second argument to avoid scope's pollution
// (devtools loader injects these symbols as global and prevent using
// const here)
var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});

function isUTF8(charset) {
  let type = typeof charset;

  if (type === "undefined")
    return false;

  if (type === "string" && charset.toLowerCase() === "utf-8")
    return true;

  throw new Error("The charset argument can be only 'utf-8'");
}

function toOctetChar(c) {
  return String.fromCharCode(c.charCodeAt(0) & 0xFF);
}

exports.decode = function (data, charset) {
  if (isUTF8(charset))
    return decodeURIComponent(escape(atob(data)))

  return atob(data);
}

exports.encode = function (data, charset) {
  if (isUTF8(charset))
    return btoa(unescape(encodeURIComponent(data)))

  data = data.replace(/[^\x00-\xFF]/g, toOctetChar);
  return btoa(data);
}
PK
!<Bv22&modules/commonjs/sdk/browser/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { events } = require("../window/events");
const { filter } = require("../event/utils");
const { isBrowser } = require("../window/utils");

// TODO: `isBrowser` detects weather window is a browser by checking
// `windowtype` attribute, which means that all 'open' events will be
// filtered out since document is not loaded yet. Maybe we can find a better
// implementation for `isBrowser`. Either way it's not really needed yet
// neither window tracker provides this event.

exports.events = filter(events, ({target}) => isBrowser(target));
PK
!<&\&&!modules/commonjs/sdk/clipboard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable",
  "engines": {
    // TODO Fennec Support 789757
    "Firefox": "*",
    "SeaMonkey": "*",
    "Thunderbird": "*"
  }
};

const { Cc, Ci } = require("chrome");
const { DataURL } = require("./url");
const apiUtils = require("./deprecated/api-utils");
/*
While these data flavors resemble Internet media types, they do
no directly map to them.
*/
const kAllowableFlavors = [
  "text/unicode",
  "text/html",
  "image/png"
  /* CURRENTLY UNSUPPORTED FLAVORS
  "text/plain",
  "image/jpg",
  "image/jpeg",
  "image/gif",
  "text/x-moz-text-internal",
  "AOLMAIL",
  "application/x-moz-file",
  "text/x-moz-url",
  "text/x-moz-url-data",
  "text/x-moz-url-desc",
  "text/x-moz-url-priv",
  "application/x-moz-nativeimage",
  "application/x-moz-nativehtml",
  "application/x-moz-file-promise-url",
  "application/x-moz-file-promise-dest-filename",
  "application/x-moz-file-promise",
  "application/x-moz-file-promise-dir"
  */
];

/*
Aliases for common flavors. Not all flavors will
get an alias. New aliases must be approved by a
Jetpack API druid.
*/
const kFlavorMap = [
  { short: "text", long: "text/unicode" },
  { short: "html", long: "text/html" },
  { short: "image", long: "image/png" }
];

var clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
                       getService(Ci.nsIClipboard);

var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
                      getService(Ci.nsIClipboardHelper);

var imageTools = Cc["@mozilla.org/image/tools;1"].
               getService(Ci.imgITools);

exports.set = function(aData, aDataType) {

  let options = {
    data: aData,
    datatype: aDataType || "text"
  };

  // If `aDataType` is not given or if it's "image", the data is parsed as
  // data URL to detect a better datatype
  if (aData && (!aDataType || aDataType === "image")) {
    try {
      let dataURL = new DataURL(aData);

      options.datatype = dataURL.mimeType;
      options.data = dataURL.data;
    }
    catch (e) {
      // Ignore invalid URIs
      if (e.name !== "URIError") {
        throw e;
      }
    }
  }

  options = apiUtils.validateOptions(options, {
    data: {
      is: ["string"]
    },
    datatype: {
      is: ["string"]
    }
  });

  let flavor = fromJetpackFlavor(options.datatype);

  if (!flavor)
    throw new Error("Invalid flavor for " + options.datatype);

  // Additional checks for using the simple case
  if (flavor == "text/unicode") {
    clipboardHelper.copyString(options.data);
    return true;
  }

  // Below are the more complex cases where we actually have to work with a
  // nsITransferable object
  var xferable = Cc["@mozilla.org/widget/transferable;1"].
                 createInstance(Ci.nsITransferable);
  if (!xferable)
    throw new Error("Couldn't set the clipboard due to an internal error " +
                    "(couldn't create a Transferable object).");
  // Bug 769440: Starting with FF16, transferable have to be inited
  if ("init" in xferable)
    xferable.init(null);

  switch (flavor) {
    case "text/html":
      // add text/html flavor
      let str = Cc["@mozilla.org/supports-string;1"].
                 createInstance(Ci.nsISupportsString);

      str.data = options.data;
      xferable.addDataFlavor(flavor);
      xferable.setTransferData(flavor, str, str.data.length * 2);

      // add a text/unicode flavor (html converted to plain text)
      str = Cc["@mozilla.org/supports-string;1"].
               createInstance(Ci.nsISupportsString);
      let converter = Cc["@mozilla.org/feed-textconstruct;1"].
                     createInstance(Ci.nsIFeedTextConstruct);

      converter.type = "html";
      converter.text = options.data;
      str.data = converter.plainText();
      xferable.addDataFlavor("text/unicode");
      xferable.setTransferData("text/unicode", str, str.data.length * 2);
      break;

    // Set images to the clipboard is not straightforward, to have an idea how
    // it works on platform side, see:
    // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530
    case "image/png":
      let image = options.data;

      let container = {};

      try {
        let input = Cc["@mozilla.org/io/string-input-stream;1"].
                      createInstance(Ci.nsIStringInputStream);

        input.setData(image, image.length);

        imageTools.decodeImageData(input, flavor, container);
      }
      catch (e) {
        throw new Error("Unable to decode data given in a valid image.");
      }

      // Store directly the input stream makes the cliboard's data available
      // for Firefox but not to the others application or to the OS. Therefore,
      // a `nsISupportsInterfacePointer` object that reference an `imgIContainer`
      // with the image is needed.
      var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].
                     createInstance(Ci.nsISupportsInterfacePointer);

      imgPtr.data = container.value;

      xferable.addDataFlavor(flavor);
      xferable.setTransferData(flavor, imgPtr, -1);

      break;
    default:
      throw new Error("Unable to handle the flavor " + flavor + ".");
  }

  // TODO: Not sure if this will ever actually throw. -zpao
  try {
    clipboardService.setData(
      xferable,
      null,
      clipboardService.kGlobalClipboard
    );
  } catch (e) {
    throw new Error("Couldn't set clipboard data due to an internal error: " + e);
  }
  return true;
};


exports.get = function(aDataType) {
  let options = {
    datatype: aDataType
  };

  // Figure out the best data type for the clipboard's data, if omitted
  if (!aDataType) {
    if (~currentFlavors().indexOf("image"))
      options.datatype = "image";
    else
      options.datatype = "text";
  }

  options = apiUtils.validateOptions(options, {
    datatype: {
      is: ["string"]
    }
  });

  var xferable = Cc["@mozilla.org/widget/transferable;1"].
                 createInstance(Ci.nsITransferable);
  if (!xferable)
    throw new Error("Couldn't set the clipboard due to an internal error " +
                    "(couldn't create a Transferable object).");
  // Bug 769440: Starting with FF16, transferable have to be inited
  if ("init" in xferable)
    xferable.init(null);

  var flavor = fromJetpackFlavor(options.datatype);

  // Ensure that the user hasn't requested a flavor that we don't support.
  if (!flavor)
    throw new Error("Getting the clipboard with the flavor '" + flavor +
                    "' is not supported.");

  // TODO: Check for matching flavor first? Probably not worth it.

  xferable.addDataFlavor(flavor);
  // Get the data into our transferable.
  clipboardService.getData(
    xferable,
    clipboardService.kGlobalClipboard
  );

  var data = {};
  var dataLen = {};
  try {
    xferable.getTransferData(flavor, data, dataLen);
  } catch (e) {
    // Clipboard doesn't contain data in flavor, return null.
    return null;
  }

  // There's no data available, return.
  if (data.value === null)
    return null;

  // TODO: Add flavors here as we support more in kAllowableFlavors.
  switch (flavor) {
    case "text/unicode":
    case "text/html":
      data = data.value.QueryInterface(Ci.nsISupportsString).data;
      break;
    case "image/png":
      let dataURL = new DataURL();

      dataURL.mimeType = flavor;
      dataURL.base64 = true;

      let image = data.value;

      // Due to the differences in how images could be stored in the clipboard
      // the checks below are needed. The clipboard could already provide the
      // image as byte streams, but also as pointer, or as image container.
      // If it's not possible obtain a byte stream, the function returns `null`.
      if (image instanceof Ci.nsISupportsInterfacePointer)
        image = image.data;

      if (image instanceof Ci.imgIContainer)
        image = imageTools.encodeImage(image, flavor);

      if (image instanceof Ci.nsIInputStream) {
        let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
                              createInstance(Ci.nsIBinaryInputStream);

        binaryStream.setInputStream(image);

        dataURL.data = binaryStream.readBytes(binaryStream.available());

        data = dataURL.toString();
      }
      else
        data = null;

      break;
    default:
      data = null;
  }

  return data;
};

function currentFlavors() {
  // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
  // This doesn't seem like the most efficient way, but we can't get
  // confirmation for specific flavors any other way. This is supposed to be
  // an inexpensive call, so performance shouldn't be impacted (much).
  var currentFlavors = [];
  for (var flavor of kAllowableFlavors) {
    var matches = clipboardService.hasDataMatchingFlavors(
      [flavor],
      1,
      clipboardService.kGlobalClipboard
    );
    if (matches)
      currentFlavors.push(toJetpackFlavor(flavor));
  }
  return currentFlavors;
};

Object.defineProperty(exports, "currentFlavors", { get : currentFlavors });

// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////

function toJetpackFlavor(aFlavor) {
  for (let flavorMap of kFlavorMap)
    if (flavorMap.long == aFlavor)
      return flavorMap.short;
  // Return null in the case where we don't match
  return null;
}

function fromJetpackFlavor(aJetpackFlavor) {
  // TODO: Handle proper flavors better
  for (let flavorMap of kFlavorMap)
    if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
      return flavorMap.long;
  // Return null in the case where we don't match.
  return null;
}
PK
!<<_vS	S	*modules/commonjs/sdk/console/plain-text.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, Cu, Cr } = require("chrome");
const self = require("../self");
const prefs = require("../preferences/service");
const { merge } = require("../util/object");
const { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});

const DEFAULT_LOG_LEVEL = "error";
const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel";
const SDK_LOG_LEVEL_PREF = "extensions.sdk.console.logLevel";

var logLevel = DEFAULT_LOG_LEVEL;
function setLogLevel() {
  logLevel = prefs.get(ADDON_LOG_LEVEL_PREF,
                           prefs.get(SDK_LOG_LEVEL_PREF,
                                     DEFAULT_LOG_LEVEL));
}
setLogLevel();

var logLevelObserver = {
  QueryInterface: function(iid) {
    if (!iid.equals(Ci.nsIObserver) &&
        !iid.equals(Ci.nsISupportsWeakReference) &&
        !iid.equals(Ci.nsISupports))
      throw Cr.NS_ERROR_NO_INTERFACE;
    return this;
  },
  observe: function(subject, topic, data) {
    setLogLevel();
  }
};
var branch = Cc["@mozilla.org/preferences-service;1"].
             getService(Ci.nsIPrefService).
             getBranch(null);
branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true);
branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true);

function PlainTextConsole(print, innerID) {

  let consoleOptions = {
    prefix: self.name,
    maxLogLevel: logLevel,
    dump: print,
    innerID: innerID,
    consoleID: "addon/" + self.id
  };
  let console = new ConsoleAPI(consoleOptions);

  // As we freeze the console object, we can't modify this property afterward
  Object.defineProperty(console, "maxLogLevel", {
    get: function() {
      return logLevel;
    }
  });

  // We defined the `__exposedProps__` in our console chrome object.
  //
  // Meanwhile we're investigating with the platform team if `__exposedProps__`
  // are needed, or are just a left-over.

  console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
    exposed[prop] = "r";
    return exposed;
  }, {});

  Object.freeze(console);
  return console;
};
exports.PlainTextConsole = PlainTextConsole;
PK
!<G`
E	E	)modules/commonjs/sdk/console/traceback.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Ci, components } = require("chrome");
const { parseStack, sourceURI } = require("toolkit/loader");
lazyRequire(this, "../net/url", "readURISync");

function safeGetFileLine(path, line) {
  try {
    var scheme = require("../url").URL(path).scheme;
    // TODO: There should be an easier, more accurate way to figure out
    // what's the case here.
    if (!(scheme == "http" || scheme == "https"))
      return readURISync(path).split("\n")[line - 1];
  } catch (e) {}
  return null;
}

function nsIStackFramesToJSON(frame) {
  var stack = [];

  while (frame) {
    if (frame.filename) {
      stack.unshift({
        fileName: sourceURI(frame.filename),
        lineNumber: frame.lineNumber,
        name: frame.name
      });
    }
    frame = frame.caller;
  }

  return stack;
};

var fromException = exports.fromException = function fromException(e) {
  if (e instanceof Ci.nsIException)
    return nsIStackFramesToJSON(e.location);
  if (e.stack && e.stack.length)
    return parseStack(e.stack);
  if (e.fileName && typeof(e.lineNumber == "number"))
    return [{fileName: sourceURI(e.fileName),
             lineNumber: e.lineNumber,
             name: null}];
  return [];
};

var get = exports.get = function get() {
  return nsIStackFramesToJSON(components.stack.caller);
};

var format = exports.format = function format(tbOrException) {
  if (tbOrException === undefined) {
    tbOrException = get();
    tbOrException.pop();
  }

  var tb;
  if (typeof(tbOrException) == "object" &&
      tbOrException.constructor.name == "Array")
    tb = tbOrException;
  else
    tb = fromException(tbOrException);

  var lines = ["Traceback (most recent call last):"];

  tb.forEach(
    function(frame) {
      if (!(frame.fileName || frame.lineNumber || frame.name))
      	return;

      lines.push('  File "' + frame.fileName + '", line ' +
                 frame.lineNumber + ', in ' + frame.name);
      var sourceLine = safeGetFileLine(frame.fileName, frame.lineNumber);
      if (sourceLine)
        lines.push('    ' + sourceLine.trim());
    });

  return lines.join("\n");
};
PK
!<Vɛ ( (.modules/commonjs/sdk/content/content-worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Object.freeze({
  // TODO: Bug 727854 Use same implementation than common JS modules,
  // i.e. EventEmitter module

  /**
   * Create an EventEmitter instance.
   */
  createEventEmitter: function createEventEmitter(emit) {
    let listeners = Object.create(null);
    let eventEmitter = Object.freeze({
      emit: emit,
      on: function on(name, callback) {
        if (typeof callback !== "function")
          return this;
        if (!(name in listeners))
          listeners[name] = [];
        listeners[name].push(callback);
        return this;
      },
      once: function once(name, callback) {
        eventEmitter.on(name, function onceCallback() {
          eventEmitter.removeListener(name, onceCallback);
          callback.apply(callback, arguments);
        });
      },
      removeListener: function removeListener(name, callback) {
        if (!(name in listeners))
          return;
        let index = listeners[name].indexOf(callback);
        if (index == -1)
          return;
        listeners[name].splice(index, 1);
      }
    });
    function onEvent(name) {
      if (!(name in listeners))
        return [];
      let args = Array.slice(arguments, 1);
      let results = [];
      for (let callback of listeners[name]) {
        results.push(callback.apply(null, args));
      }
      return results;
    }
    return {
      eventEmitter: eventEmitter,
      emit: onEvent
    };
  },

  /**
   * Create an EventEmitter instance to communicate with chrome module
   * by passing only strings between compartments.
   * This function expects `emitToChrome` function, that allows to send
   * events to the chrome module. It returns the EventEmitter as `pipe`
   * attribute, and, `onChromeEvent` a function that allows chrome module
   * to send event into the EventEmitter.
   *
   *                  pipe.emit --> emitToChrome
   *              onChromeEvent --> callback registered through pipe.on
   */
  createPipe: function createPipe(emitToChrome) {
    let ContentWorker = this;
    function onEvent(type, ...args) {
      // JSON.stringify is buggy with cross-sandbox values,
      // it may return "{}" on functions. Use a replacer to match them correctly.
      let replacer = (k, v) =>
        typeof(v) === "function"
          ? (type === "console" ? Function.toString.call(v) : void(0))
          : v;

      let str = JSON.stringify([type, ...args], replacer);
      emitToChrome(str);
    }

    let { eventEmitter, emit } =
      ContentWorker.createEventEmitter(onEvent);

    return {
      pipe: eventEmitter,
      onChromeEvent: function onChromeEvent(array) {
        // We either receive a stringified array, or a real array.
        // We still allow to pass an array of objects, in WorkerSandbox.emitSync
        // in order to allow sending DOM node reference between content script
        // and modules (only used for context-menu API)
        let args = typeof array == "string" ? JSON.parse(array) : array;
        return emit.apply(null, args);
      }
    };
  },

  injectConsole: function injectConsole(exports, pipe) {
    exports.console = Object.freeze({
      log: pipe.emit.bind(null, "console", "log"),
      info: pipe.emit.bind(null, "console", "info"),
      warn: pipe.emit.bind(null, "console", "warn"),
      error: pipe.emit.bind(null, "console", "error"),
      debug: pipe.emit.bind(null, "console", "debug"),
      exception: pipe.emit.bind(null, "console", "exception"),
      trace: pipe.emit.bind(null, "console", "trace"),
      time: pipe.emit.bind(null, "console", "time"),
      timeEnd: pipe.emit.bind(null, "console", "timeEnd")
    });
  },

  injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
    // wrapped functions from `'timer'` module.
    // Wrapper adds `try catch` blocks to the callbacks in order to
    // emit `error` event if exception is thrown in
    // the Worker global scope.
    // @see http://www.w3.org/TR/workers/#workerutils

    // List of all living timeouts/intervals
    let _timers = Object.create(null);

    // Keep a reference to original timeout functions
    let {
      setTimeout: chromeSetTimeout,
      setInterval: chromeSetInterval,
      clearTimeout: chromeClearTimeout,
      clearInterval: chromeClearInterval
    } = chromeAPI.timers;

    function registerTimer(timer) {
      let registerMethod = null;
      if (timer.kind == "timeout")
        registerMethod = chromeSetTimeout;
      else if (timer.kind == "interval")
        registerMethod = chromeSetInterval;
      else
        throw new Error("Unknown timer kind: " + timer.kind);

      if (typeof timer.fun == 'string') {
        let code = timer.fun;
        timer.fun = () => chromeAPI.sandbox.evaluate(exports, code);
      } else if (typeof timer.fun != 'function') {
        throw new Error('Unsupported callback type' + typeof timer.fun);
      }

      let id = registerMethod(onFire, timer.delay);
      function onFire() {
        try {
          if (timer.kind == "timeout")
            delete _timers[id];
          timer.fun.apply(null, timer.args);
        } catch(e) {
          console.exception(e);
          let wrapper = {
            instanceOfError: instanceOf(e, Error),
            value: e,
          };
          if (wrapper.instanceOfError) {
            wrapper.value = {
              message: e.message,
              fileName: e.fileName,
              lineNumber: e.lineNumber,
              stack: e.stack,
              name: e.name,
            };
          }
          pipe.emit('error', wrapper);
        }
      }
      _timers[id] = timer;
      return id;
    }

    // copied from sdk/lang/type.js since modules are not available here
    function instanceOf(value, Type) {
      var isConstructorNameSame;
      var isConstructorSourceSame;

      // If `instanceof` returned `true` we know result right away.
      var isInstanceOf = value instanceof Type;

      // If `instanceof` returned `false` we do ducktype check since `Type` may be
      // from a different sandbox. If a constructor of the `value` or a constructor
      // of the value's prototype has same name and source we assume that it's an
      // instance of the Type.
      if (!isInstanceOf && value) {
        isConstructorNameSame = value.constructor.name === Type.name;
        isConstructorSourceSame = String(value.constructor) == String(Type);
        isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
                        instanceOf(Object.getPrototypeOf(value), Type);
      }
      return isInstanceOf;
    }

    function unregisterTimer(id) {
      if (!(id in _timers))
        return;
      let { kind } = _timers[id];
      delete _timers[id];
      if (kind == "timeout")
        chromeClearTimeout(id);
      else if (kind == "interval")
        chromeClearInterval(id);
      else
        throw new Error("Unknown timer kind: " + kind);
    }

    function disableAllTimers() {
      Object.keys(_timers).forEach(unregisterTimer);
    }

    exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
      return registerTimer({
        kind: "timeout",
        fun: callback,
        delay: delay,
        args: Array.slice(arguments, 2)
      });
    };
    exports.clearTimeout = function ContentScriptClearTimeout(id) {
      unregisterTimer(id);
    };

    exports.setInterval = function ContentScriptSetInterval(callback, delay) {
      return registerTimer({
        kind: "interval",
        fun: callback,
        delay: delay,
        args: Array.slice(arguments, 2)
      });
    };
    exports.clearInterval = function ContentScriptClearInterval(id) {
      unregisterTimer(id);
    };

    // On page-hide, save a list of all existing timers before disabling them,
    // in order to be able to restore them on page-show.
    // These events are fired when the page goes in/out of bfcache.
    // https://developer.mozilla.org/En/Working_with_BFCache
    let frozenTimers = [];
    pipe.on("pageshow", function onPageShow() {
      frozenTimers.forEach(registerTimer);
    });
    pipe.on("pagehide", function onPageHide() {
      frozenTimers = [];
      for (let id in _timers)
        frozenTimers.push(_timers[id]);
      disableAllTimers();
      // Some other pagehide listeners may register some timers that won't be
      // frozen as this particular pagehide listener is called first.
      // So freeze these timers on next cycle.
      chromeSetTimeout(function () {
        for (let id in _timers)
          frozenTimers.push(_timers[id]);
        disableAllTimers();
      }, 0);
    });

    // Unregister all timers when the page is destroyed
    // (i.e. when it is removed from bfcache)
    pipe.on("detach", function clearTimeouts() {
      disableAllTimers();
      _timers = {};
      frozenTimers = [];
    });
  },

  injectMessageAPI: function injectMessageAPI(exports, pipe, console) {

    let ContentWorker = this;
    let { eventEmitter: port, emit : portEmit } =
      ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
    pipe.on("event", portEmit);

    let self = {
      port: port,
      postMessage: pipe.emit.bind(null, "message"),
      on: pipe.on.bind(null),
      once: pipe.once.bind(null),
      removeListener: pipe.removeListener.bind(null),
    };
    Object.defineProperty(exports, "self", {
      value: self
    });
  },

  injectOptions: function (exports, options) {
    Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
  },

  inject: function (exports, chromeAPI, emitToChrome, options) {
    let ContentWorker = this;
    let { pipe, onChromeEvent } =
      ContentWorker.createPipe(emitToChrome);

    ContentWorker.injectConsole(exports, pipe);
    ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
    ContentWorker.injectMessageAPI(exports, pipe, exports.console);
    if ( options !== undefined ) {
      ContentWorker.injectOptions(exports, options);
    }

    Object.freeze( exports.self );

    return onChromeEvent;
  }
});
PK
!<<|'modules/commonjs/sdk/content/content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "deprecated"
};

const { deprecateUsage } = require('../util/deprecate');

Object.defineProperty(exports, "Worker", {
  get: function() {
    deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.');
    return require('./worker').Worker;
  }
});
PK
!<t,t,,modules/commonjs/sdk/content/context-menu.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Class } = require("../core/heritage");
const self = require("../self");
const { WorkerChild } = require("./worker-child");
const { getInnerId } = require("../window/utils");
const { Ci } = require("chrome");
const { Services } = require("resource://gre/modules/Services.jsm");
const system = require('../system/events');
const { process } = require('../remote/child');

// These functions are roughly copied from sdk/selection which doesn't work
// in the content process
function getElementWithSelection(window) {
  let element = Services.focus.getFocusedElementForWindow(window, false, {});
  if (!element)
    return null;

  try {
    // Accessing selectionStart and selectionEnd on e.g. a button
    // results in an exception thrown as per the HTML5 spec.  See
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection

    let { value, selectionStart, selectionEnd } = element;

    let hasSelection = typeof value === "string" &&
                      !isNaN(selectionStart) &&
                      !isNaN(selectionEnd) &&
                      selectionStart !== selectionEnd;

    return hasSelection ? element : null;
  }
  catch (err) {
    console.exception(err);
    return null;
  }
}

function safeGetRange(selection, rangeNumber) {
  try {
    let { rangeCount } = selection;
    let range = null;

    for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
      range = selection.getRangeAt(rangeNumber);

      if (range && range.toString())
        break;

      range = null;
    }

    return range;
  }
  catch (e) {
    return null;
  }
}

function getSelection(window) {
  let selection = window.getSelection();
  let range = safeGetRange(selection);
  if (range)
    return range.toString();

  let node = getElementWithSelection(window);
  if (!node)
    return null;

  return node.value.substring(node.selectionStart, node.selectionEnd);
}

//These are used by PageContext.isCurrent below. If the popupNode or any of
//its ancestors is one of these, Firefox uses a tailored context menu, and so
//the page context doesn't apply.
const NON_PAGE_CONTEXT_ELTS = [
  Ci.nsIDOMHTMLAnchorElement,
  Ci.nsIDOMHTMLAreaElement,
  Ci.nsIDOMHTMLButtonElement,
  Ci.nsIDOMHTMLCanvasElement,
  Ci.nsIDOMHTMLEmbedElement,
  Ci.nsIDOMHTMLImageElement,
  Ci.nsIDOMHTMLInputElement,
  Ci.nsIDOMHTMLMapElement,
  Ci.nsIDOMHTMLMediaElement,
  Ci.nsIDOMHTMLMenuElement,
  Ci.nsIDOMHTMLObjectElement,
  Ci.nsIDOMHTMLOptionElement,
  Ci.nsIDOMHTMLSelectElement,
  Ci.nsIDOMHTMLTextAreaElement,
];

// List all editable types of inputs.  Or is it better to have a list
// of non-editable inputs?
var editableInputs = {
  email: true,
  number: true,
  password: true,
  search: true,
  tel: true,
  text: true,
  textarea: true,
  url: true
};

var CONTEXTS = {};

var Context = Class({
  initialize: function(id) {
    this.id = id;
  },

  adjustPopupNode: function adjustPopupNode(popupNode) {
    return popupNode;
  },

  // Gets state to pass through to the parent process for the node the user
  // clicked on
  getState: function(popupNode) {
    return false;
  }
});

// Matches when the context-clicked node doesn't have any of
// NON_PAGE_CONTEXT_ELTS in its ancestors
CONTEXTS.PageContext = Class({
  extends: Context,

  getState: function(popupNode) {
    // If there is a selection in the window then this context does not match
    if (!popupNode.ownerGlobal.getSelection().isCollapsed)
      return false;

    // If the clicked node or any of its ancestors is one of the blocked
    // NON_PAGE_CONTEXT_ELTS then this context does not match
    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
      if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type))
        return false;

      popupNode = popupNode.parentNode;
    }

    return true;
  }
});

// Matches when there is an active selection in the window
CONTEXTS.SelectionContext = Class({
  extends: Context,

  getState: function(popupNode) {
    if (!popupNode.ownerGlobal.getSelection().isCollapsed)
      return true;

    try {
      // The node may be a text box which has selectionStart and selectionEnd
      // properties. If not this will throw.
      let { selectionStart, selectionEnd } = popupNode;
      return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
             selectionStart !== selectionEnd;
    }
    catch (e) {
      return false;
    }
  }
});

// Matches when the context-clicked node or any of its ancestors matches the
// selector given
CONTEXTS.SelectorContext = Class({
  extends: Context,

  initialize: function initialize(id, selector) {
    Context.prototype.initialize.call(this, id);
    this.selector = selector;
  },

  adjustPopupNode: function adjustPopupNode(popupNode) {
    let selector = this.selector;

    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
      if (popupNode.matches(selector))
        return popupNode;

      popupNode = popupNode.parentNode;
    }

    return null;
  },

  getState: function(popupNode) {
    return !!this.adjustPopupNode(popupNode);
  }
});

// Matches when the page url matches any of the patterns given
CONTEXTS.URLContext = Class({
  extends: Context,

  getState: function(popupNode) {
    return popupNode.ownerDocument.URL;
  }
});

// Matches when the user-supplied predicate returns true
CONTEXTS.PredicateContext = Class({
  extends: Context,

  getState: function(node) {
    let window = node.ownerGlobal;
    let data = {};

    data.documentType = node.ownerDocument.contentType;

    data.documentURL = node.ownerDocument.location.href;
    data.targetName = node.nodeName.toLowerCase();
    data.targetID = node.id || null ;

    if ((data.targetName === 'input' && editableInputs[node.type]) ||
        data.targetName === 'textarea') {
      data.isEditable = !node.readOnly && !node.disabled;
    }
    else {
      data.isEditable = node.isContentEditable;
    }

    data.selectionText = getSelection(window, "TEXT");

    data.srcURL = node.src || null;
    data.value = node.value || null;

    while (!data.linkURL && node) {
      data.linkURL = node.href || null;
      node = node.parentNode;
    }

    return data;
  },
});

function instantiateContext({ id, type, args }) {
  if (!(type in CONTEXTS)) {
    console.error("Attempt to use unknown context " + type);
    return;
  }
  return new CONTEXTS[type](id, ...args);
}

var ContextWorker = Class({
  implements: [ WorkerChild ],

  // Calls the context workers context listeners and returns the first result
  // that is either a string or a value that evaluates to true. If all of the
  // listeners returned false then returns false. If there are no listeners,
  // returns true (show the menu item by default).
  getMatchedContext: function getCurrentContexts(popupNode) {
    let results = this.sandbox.emitSync("context", popupNode);
    if (!results.length)
      return true;
    return results.reduce((val, result) => val || result);
  },

  // Emits a click event in the worker's port. popupNode is the node that was
  // context-clicked, and clickedItemData is the data of the item that was
  // clicked.
  fireClick: function fireClick(popupNode, clickedItemData) {
    this.sandbox.emitSync("click", popupNode, clickedItemData);
  }
});

// Gets the item's content script worker for a window, creating one if necessary
// Once created it will be automatically destroyed when the window unloads.
// If there is not content scripts for the item then null will be returned.
function getItemWorkerForWindow(item, window) {
  if (!item.contentScript && !item.contentScriptFile)
    return null;

  let id = getInnerId(window);
  let worker = item.workerMap.get(id);

  if (worker)
    return worker;

  worker = ContextWorker({
    id: item.id,
    window,
    manager: item.manager,
    contentScript: item.contentScript,
    contentScriptFile: item.contentScriptFile,
    onDetach: function() {
      item.workerMap.delete(id);
    }
  });

  item.workerMap.set(id, worker);

  return worker;
}

// A very simple remote proxy for every item. It's job is to provide data for
// the main process to use to determine visibility state and to call into
// content scripts when clicked.
var RemoteItem = Class({
  initialize: function(options, manager) {
    this.id = options.id;
    this.contexts = options.contexts.map(instantiateContext);
    this.contentScript = options.contentScript;
    this.contentScriptFile = options.contentScriptFile;

    this.manager = manager;

    this.workerMap = new Map();
    keepAlive.set(this.id, this);
  },

  destroy: function() {
    for (let worker of this.workerMap.values()) {
      worker.destroy();
    }
    keepAlive.delete(this.id);
  },

  activate: function(popupNode, data) {
    let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
    if (!worker)
      return;

    for (let context of this.contexts)
      popupNode = context.adjustPopupNode(popupNode);

    worker.fireClick(popupNode, data);
  },

  // Fills addonInfo with state data to send through to the main process
  getContextState: function(popupNode, addonInfo) {
    if (!(self.id in addonInfo)) {
      addonInfo[self.id] = {
        processID: process.id,
        items: {}
      };
    }

    let worker = getItemWorkerForWindow(this, popupNode.ownerGlobal);
    let contextStates = {};
    for (let context of this.contexts)
      contextStates[context.id] = context.getState(popupNode);

    addonInfo[self.id].items[this.id] = {
      // It isn't ideal to create a PageContext for every item but there isn't
      // a good shared place to do it.
      pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
      contextStates,
      hasWorker: !!worker,
      workerContext: worker ? worker.getMatchedContext(popupNode) : true
    }
  }
});
exports.RemoteItem = RemoteItem;

// Holds remote items for this frame.
var keepAlive = new Map();

// Called to create remote proxies for items. If they already exist we destroy
// and recreate. This can happen if the item changes in some way or in odd
// timing cases where the frame script is create around the same time as the
// item is created in the main process
process.port.on('sdk/contextmenu/createitems', (process, items) => {
  for (let itemoptions of items) {
    let oldItem = keepAlive.get(itemoptions.id);
    if (oldItem) {
      oldItem.destroy();
    }

    let item = new RemoteItem(itemoptions, this);
  }
});

process.port.on('sdk/contextmenu/destroyitems', (process, items) => {
  for (let id of items) {
    let item = keepAlive.get(id);
    item.destroy();
  }
});

var lastPopupNode = null;

system.on('content-contextmenu', ({ subject }) => {
  let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
  lastPopupNode = popupNode;

  for (let item of keepAlive.values()) {
    item.getContextState(popupNode, addonInfo);
  }
}, true);

process.port.on('sdk/contextmenu/activateitems', (process, items, data) => {
  for (let id of items) {
    let item = keepAlive.get(id);
    if (!item)
      continue;

    item.activate(lastPopupNode, data);
  }
});
PK
!<	ii&modules/commonjs/sdk/content/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Ci } = require("chrome");
lazyRequire(this, "../event/dom", "open");
const { observe } = require("../event/chrome");
const { filter, merge, map, expand } = require("../event/utils");
const { windows } = require("../window/utils");
const { events: windowEvents } = require("sdk/window/events");

// Note: Please note that even though pagehide event is included
// it's not observable reliably since it's not always triggered
// when closing tabs. Implementation can be imrpoved once that
// event will be necessary.
var TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"];

var insert = observe("document-element-inserted");
var windowCreate = merge([
  observe("content-document-global-created"),
  observe("chrome-document-global-created")
]);
var create = map(windowCreate, function({target, data, type}) {
  return { target: target.document, type: type, data: data }
});

function streamEventsFrom({document}) {
  // Map supported event types to a streams of those events on the given
  // `window` for the inserted document and than merge these streams into
  // single form stream off all window state change events.
  let stateChanges = TYPES.map(function(type) {
    return open(document, type, { capture: true });
  });

  // Since load events on document occur for every loded resource
  return filter(merge(stateChanges), function({target}) {
    return target instanceof Ci.nsIDOMDocument
  })
}
exports.streamEventsFrom = streamEventsFrom;

var opened = windows(null, { includePrivate: true });
var state = merge(opened.map(streamEventsFrom));


var futureReady = filter(windowEvents, ({type}) =>
                                        type === "DOMContentLoaded");
var futureWindows = map(futureReady, ({target}) => target);
var futureState = expand(futureWindows, streamEventsFrom);

exports.events = merge([insert, create, state, futureState]);
PK
!<c2V^^)modules/commonjs/sdk/content/l10n-html.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Ci, Cc, Cu } = require("chrome");
lazyRequireModule(this, "../l10n/core", "core");
lazyRequire(this, "../stylesheet/utils", "loadSheet", "removeSheet");
const { process, frames } = require("../remote/child");

var observerService = Cc["@mozilla.org/observer-service;1"]
                      .getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");

const assetsURI = require('../self').data.url();

const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}";

function translateElementAttributes(element) {
  // Translateable attributes
  const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder'];
  const ariaAttrMap = {
          'ariaLabel': 'aria-label',
          'ariaValueText': 'aria-valuetext',
          'ariaMozHint': 'aria-moz-hint'
        };
  const attrSeparator = '.';
  
  // Try to translate each of the attributes
  for (let attribute of attrList) {
    const data = core.get(element.dataset.l10nId + attrSeparator + attribute);
    if (data)
      element.setAttribute(attribute, data);
  }
  
  // Look for the aria attribute translations that match fxOS's aliases
  for (let attrAlias in ariaAttrMap) {
    const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias);
    if (data)
      element.setAttribute(ariaAttrMap[attrAlias], data);
  }
}

// Taken from Gaia:
// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
function translateElement(element) {
  element = element || document;

  // check all translatable children (= w/ a `data-l10n-id' attribute)
  var children = element.querySelectorAll('*[data-l10n-id]');
  var elementCount = children.length;
  for (var i = 0; i < elementCount; i++) {
    var child = children[i];

    // translate the child
    var key = child.dataset.l10nId;
    var data = core.get(key);
    if (data)
      child.textContent = data;

    translateElementAttributes(child);
  }
}
exports.translateElement = translateElement;

function onDocumentReady2Translate(event) {
  let document = event.target;
  document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate);

  translateElement(document);

  try {
    // Finally display document when we finished replacing all text content
    if (document.defaultView)
      removeSheet(document.defaultView, hideSheetUri, 'user');
  }
  catch(e) {
    console.exception(e);
  }
}

function onContentWindow(document) {
  // Accept only HTML documents
  if (!(document instanceof Ci.nsIDOMHTMLDocument))
    return;

  // Bug 769483: data:URI documents instanciated with nsIDOMParser
  // have a null `location` attribute at this time
  if (!document.location)
    return;

  // Accept only document from this addon
  if (document.location.href.indexOf(assetsURI) !== 0)
    return;

  try {
    // First hide content of the document in order to have content blinking
    // between untranslated and translated states
    loadSheet(document.defaultView, hideSheetUri, 'user');
  }
  catch(e) {
    console.exception(e);
  }
  // Wait for DOM tree to be built before applying localization
  document.addEventListener("DOMContentLoaded", onDocumentReady2Translate);
}

// Listen to creation of content documents in order to translate them as soon
// as possible in their loading process
const ON_CONTENT = "document-element-inserted";
let enabled = false;
function enable() {
  if (enabled)
    return;
  addObserver(onContentWindow, ON_CONTENT, false);
  enabled = true;
}
process.port.on("sdk/l10n/html/enable", enable);

function disable() {
  if (!enabled)
    return;
  removeObserver(onContentWindow, ON_CONTENT);
  enabled = false;
}
process.port.on("sdk/l10n/html/disable", disable);
PK
!<8&modules/commonjs/sdk/content/loader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { isValidURI, isLocalURL, URL } = require('../url');
const { contract } = require('../util/contract');
const { isString, isNil, instanceOf, isJSONable } = require('../lang/type');
const { validateOptions,
  string, array, object, either, required } = require('../deprecated/api-utils');

const isValidScriptFile = (value) =>
  (isString(value) || instanceOf(value, URL)) && isLocalURL(value);

// map of property validations
const valid = {
  contentURL: {
    is: either(string, object),
    ok: url => isNil(url) || isLocalURL(url) || isValidURI(url),
    msg: 'The `contentURL` option must be a valid URL.'
  },
  contentScriptFile: {
    is: either(string, object, array),
    ok: value => isNil(value) || [].concat(value).every(isValidScriptFile),
    msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
  },
  contentScript: {
    is: either(string, array),
    ok: value => isNil(value) || [].concat(value).every(isString),
    msg: 'The `contentScript` option must be a string or an array of strings.'
  },
  contentScriptWhen: {
    is: required(string),
    map: value => value || 'end',
    ok: value => ~['start', 'ready', 'end'].indexOf(value),
    msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
  },
  contentScriptOptions: {
    ok: value => isNil(value) || isJSONable(value),
    msg: 'The contentScriptOptions should be a jsonable value.'
  }
};
exports.validationAttributes = valid;

/**
 * Shortcut function to validate property with validation.
 * @param {Object|Number|String} suspect
 *    value to validate
 * @param {Object} validation
 *    validation rule passed to `api-utils`
 */
function validate(suspect, validation) {
  return validateOptions(
    { $: suspect },
    { $: validation }
  ).$;
}

function Allow(script) {
  return {
    get script() {
      return script;
    },
    set script(value) {
      script = !!value;
    }
  };
}

exports.contract = contract(valid);
PK
!<Ns#modules/commonjs/sdk/content/mod.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Ci } = require("chrome");
const { dispatcher } = require("../util/dispatcher");
const { add, remove, iterator } = require("../lang/weak-set");

var getTargetWindow = dispatcher("getTargetWindow");

getTargetWindow.define(function (target) {
  if (target instanceof Ci.nsIDOMWindow)
    return target;
  if (target instanceof Ci.nsIDOMDocument)
    return target.defaultView || null;

  return null;
});

exports.getTargetWindow = getTargetWindow;

var attachTo = dispatcher("attachTo");
exports.attachTo = attachTo;

var detachFrom = dispatcher("detatchFrom");
exports.detachFrom = detachFrom;

function attach(modification, target) {
  if (!modification)
    return;

  let window = getTargetWindow(target);

  attachTo(modification, window);

  // modification are stored per content; `window` reference can still be the
  // same even if the content is changed, therefore `document` is used instead.
  add(modification, window.document);
}
exports.attach = attach;

function detach(modification, target) {
  if (!modification)
    return;

  if (target) {
    let window = getTargetWindow(target);
    detachFrom(modification, window);
    remove(modification, window.document);
  }
  else {
    let documents = iterator(modification);
    for (let document of documents) {
      let window = document.defaultView;
      // The window might have already gone away
      if (!window)
        continue;
      detachFrom(modification, document.defaultView);
      remove(modification, document);
    }
  }
}
exports.detach = detach;
PK
!<*ep(modules/commonjs/sdk/content/page-mod.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

lazyRequire(this, '../content/utils', 'getAttachEventType');
const { Class } = require('../core/heritage');
const { Disposable } = require('../core/disposable');
lazyRequire(this, './worker-child', 'WorkerChild');
const { EventTarget } = require('../event/target');
const { on, emit, once, setListeners } = require('../event/core');
lazyRequire(this, '../dom/events',{'on': 'domOn', 'removeListener': 'domOff'});
lazyRequire(this, '../util/object', "merge");
lazyRequire(this, '../window/utils', "getFrames");
lazyRequire(this, '../private-browsing/utils', "ignoreWindow");
lazyRequire(this, '../stylesheet/style', 'Style');
lazyRequire(this, '../content/mod', 'attach', 'detach');
lazyRequire(this, '../util/rules', 'Rules');
lazyRequire(this, '../util/uuid', 'uuid');
const { frames, process } = require('../remote/child');

const pagemods = new Map();
const styles = new WeakMap();
var styleFor = (mod) => styles.get(mod);

// Helper functions
var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri);

/**
 * PageMod constructor (exported below).
 * @constructor
 */
const ChildPageMod = Class({
  implements: [
    EventTarget,
    Disposable,
  ],
  setup: function PageMod(model) {
    merge(this, model);

    // Set listeners on {PageMod} itself, not the underlying worker,
    // like `onMessage`, as it'll get piped.
    setListeners(this, model);

    function* deserializeRules(rules) {
      for (let rule of rules) {
        yield rule.type == "string" ? rule.value
                                    : new RegExp(rule.pattern, rule.flags);
      }
    }

    let include = [...deserializeRules(this.include)];
    this.include = Rules();
    this.include.add.apply(this.include, include);

    let exclude = [...deserializeRules(this.exclude)];
    this.exclude = Rules();
    this.exclude.add.apply(this.exclude, exclude);

    if (this.contentStyle || this.contentStyleFile) {
      styles.set(this, Style({
        uri: this.contentStyleFile,
        source: this.contentStyle
      }));
    }

    pagemods.set(this.id, this);
    this.seenDocuments = new WeakMap();

    // `applyOnExistingDocuments` has to be called after `pagemods.add()`
    // otherwise its calls to `onContent` method won't do anything.
    if (this.attachTo.includes('existing'))
      applyOnExistingDocuments(this);
  },

  dispose: function() {
    let style = styleFor(this);
    if (style)
      detach(style);

    for (let i in this.include)
      this.include.remove(this.include[i]);

    pagemods.delete(this.id);
  }
});

function onContentWindow({ target: document }) {
  // Return if we have no pagemods
  if (pagemods.size === 0)
    return;

  let window = document.defaultView;
  // XML documents don't have windows, and we don't yet support them.
  if (!window)
    return;

  // Frame event listeners are bound to the frame the event came from by default
  let frame = this;
  // We apply only on documents in tabs of Firefox
  if (!frame.isTab)
    return;

  // When the tab is private, only addons with 'private-browsing' flag in
  // their package.json can apply content script to private documents
  if (ignoreWindow(window))
    return;

  for (let pagemod of pagemods.values()) {
    if (modMatchesURI(pagemod, window.location.href))
      onContent(pagemod, window);
  }
}
frames.addEventListener("DOMDocElementInserted", onContentWindow, true);

function applyOnExistingDocuments (mod) {
  for (let frame of frames) {
    // Fake a newly created document
    let window = frame.content;
    // on startup with e10s, contentWindow might not exist yet,
    // in which case we will get notified by "document-element-inserted".
    if (!window || !window.frames)
      return;
    let uri = window.location.href;
    if (mod.attachTo.includes("top") && modMatchesURI(mod, uri))
      onContent(mod, window);
    if (mod.attachTo.includes("frame"))
      getFrames(window).
        filter(iframe => modMatchesURI(mod, iframe.location.href)).
        forEach(frame => onContent(mod, frame));
  }
}

function createWorker(mod, window) {
  let workerId = String(uuid());

  // Instruct the parent to connect to this worker. Do this first so the parent
  // side is connected before the worker attempts to send any messages there
  let frame = frames.getFrameForWindow(window.top);
  frame.port.emit('sdk/page-mod/worker-create', mod.id, {
    id: workerId,
    url: window.location.href
  });

  // Create a child worker and notify the parent
  let worker = WorkerChild({
    id: workerId,
    window: window,
    contentScript: mod.contentScript,
    contentScriptFile: mod.contentScriptFile,
    contentScriptOptions: mod.contentScriptOptions
  });

  once(worker, 'detach', () => worker.destroy());
}

function onContent (mod, window) {
  let isTopDocument = window.top === window;
  // Is a top level document and `top` is not set, ignore
  if (isTopDocument && !mod.attachTo.includes("top"))
    return;
  // Is a frame document and `frame` is not set, ignore
  if (!isTopDocument && !mod.attachTo.includes("frame"))
    return;

  // ensure we attach only once per document
  let seen = mod.seenDocuments;
  if (seen.has(window.document))
    return;
  seen.set(window.document, true);

  let style = styleFor(mod);
  if (style)
    attach(style, window);

  // Immediately evaluate content script if the document state is already
  // matching contentScriptWhen expectations
  if (isMatchingAttachState(mod, window)) {
    createWorker(mod, window);
    return;
  }

  let eventName = getAttachEventType(mod) || 'load';
  domOn(window, eventName, function onReady (e) {
    if (e.target.defaultView !== window)
      return;
    domOff(window, eventName, onReady, true);
    createWorker(mod, window);

    // Attaching is asynchronous so if the document is already loaded we will
    // miss the pageshow event so send a synthetic one.
    if (window.document.readyState == "complete") {
      mod.on('attach', worker => {
        try {
          worker.send('pageshow');
          emit(worker, 'pageshow');
        }
        catch (e) {
          // This can fail if an earlier attach listener destroyed the worker
        }
      });
    }
  }, true);
}

function isMatchingAttachState (mod, window) {
  let state = window.document.readyState;
  return 'start' === mod.contentScriptWhen ||
      // Is `load` event already dispatched?
      'complete' === state ||
      // Is DOMContentLoaded already dispatched and waiting for it?
      ('ready' === mod.contentScriptWhen && state === 'interactive')
}

process.port.on('sdk/page-mod/create', (process, model) => {
  if (pagemods.has(model.id))
    return;

  new ChildPageMod(model);
});

process.port.on('sdk/page-mod/destroy', (process, id) => {
  let mod = pagemods.get(id);
  if (mod)
    mod.destroy();
});
PK
!<>>+modules/commonjs/sdk/content/page-worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { frames } = require("../remote/child");
const { Class } = require("../core/heritage");
const { Disposable } = require('../core/disposable');
lazyRequire(this, "../self", "data");
lazyRequire(this, "../dom/events", "once");
lazyRequire(this, "./utils", "getAttachEventType");
lazyRequire(this, '../util/rules', "Rules");
lazyRequire(this, '../util/uuid', "uuid");
lazyRequire(this, "./worker-child", "WorkerChild");
const { Cc, Ci, Cu } = require("chrome");
const { on: onSystemEvent } = require("../system/events");

const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyServiceGetter(this, 'appShell',
                                   "@mozilla.org/appshell/appShellService;1",
                                   "nsIAppShellService");

const pages = new Map();

const DOC_INSERTED = "document-element-inserted";

function isValidURL(page, url) {
  return !page.rules || page.rules.matchesAny(url);
}

const ChildPage = Class({
  implements: [ Disposable ],
  setup: function(frame, id, options) {
    this.id = id;
    this.frame = frame;
    this.options = options;

    this.webNav = appShell.createWindowlessBrowser(false);
    this.docShell.allowJavascript = this.options.allow.script;

    // Accessing the browser's window forces the initial about:blank document to
    // be created before we start listening for notifications
    this.contentWindow;

    this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);

    pages.set(this.id, this);

    this.contentURL = options.contentURL;

    if (options.include) {
      this.rules = Rules();
      this.rules.add.apply(this.rules, [].concat(options.include));
    }
  },

  dispose: function() {
    pages.delete(this.id);
    this.webProgress.removeProgressListener(this);
    this.webNav.close();
    this.webNav = null;
  },

  attachWorker: function() {
    if (!isValidURL(this, this.contentWindow.location.href))
      return;

    this.options.id = uuid().toString();
    this.options.window = this.contentWindow;
    this.frame.port.emit("sdk/frame/connect", this.id, {
      id: this.options.id,
      url: this.contentWindow.document.documentURIObject.spec
    });
    new WorkerChild(this.options);
  },

  get docShell() {
    return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor)
                      .getInterface(Ci.nsIDocShell);
  },

  get webProgress() {
    return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIWebProgress);
  },

  get contentWindow() {
    return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIDOMWindow);
  },

  get contentURL() {
    return this.options.contentURL;
  },
  set contentURL(url) {
    this.options.contentURL = url;

    url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank";

    this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
  },

  onLocationChange: function(progress, request, location, flags) {
    // Ignore inner-frame events
    if (progress != this.webProgress)
      return;
    // Ignore events that don't change the document
    if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
      return;

    let event = getAttachEventType(this.options);
    // Attaching at the start of the load is handled by the
    // document-element-inserted listener.
    if (event == DOC_INSERTED)
      return;

    once(this.contentWindow, event, () => {
      this.attachWorker();
    }, false);
  },

  QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"])
});

onSystemEvent(DOC_INSERTED, ({type, subject, data}) => {
  let page = Array.from(pages.values()).find(p => p.contentWindow.document === subject);

  if (!page)
    return;

  if (getAttachEventType(page.options) == DOC_INSERTED)
    page.attachWorker();
}, true);

frames.port.on("sdk/frame/create", (frame, id, options) => {
  new ChildPage(frame, id, options);
});

frames.port.on("sdk/frame/set", (frame, id, params) => {
  let page = pages.get(id);
  if (!page)
    return;

  if ("allowScript" in params)
    page.docShell.allowJavascript = params.allowScript;
  if ("contentURL" in params)
    page.contentURL = params.contentURL;
});

frames.port.on("sdk/frame/destroy", (frame, id) => {
  let page = pages.get(id);
  if (!page)
    return;

  page.destroy();
});
PK
!<u??.modules/commonjs/sdk/content/sandbox/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const events = {};
exports.events = events;
PK
!<d99'modules/commonjs/sdk/content/sandbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
lazyRequire(this, '../event/core', "on", "off", "emit");
lazyRequire(this, './sandbox/events', "events");
lazyRequire(this, './utils', "requiresAddonGlobal");
lazyRequire(this, '../lang/functional', {"delay": "async"});
const { Ci, Cu, Cc } = require('chrome');
lazyRequireModule(this, "../timers", "timer");
lazyRequire(this, '../url', "URL");
lazyRequire(this, '../loader/sandbox', "sandbox", "evaluate", "load");
lazyRequire(this, '../util/object', "merge");
lazyRequire(this, '../tabs/utils', "getTabForContentWindowNoShim");
lazyRequire(this, '../window/utils', "getInnerId");
lazyRequire(this, '../console/plain-text', "PlainTextConsole");

lazyRequire(this, '../self', "data");
lazyRequire(this, '../remote/core', "isChildLoader");

// WeakMap of sandboxes so we can access private values
const sandboxes = new WeakMap();

/* Trick the linker in order to ensure shipping these files in the XPI.
  require('./content-worker.js');
  Then, retrieve URL of these files in the XPI:
*/
var prefix = module.uri.split('sandbox.js')[0];
const CONTENT_WORKER_URL = prefix + 'content-worker.js';
const metadata = require('@loader/options').metadata;

// Fetch additional list of domains to authorize access to for each content
// script. It is stored in manifest `metadata` field which contains
// package.json data. This list is originaly defined by authors in
// `permissions` attribute of their package.json addon file.
const permissions = (metadata && metadata['permissions']) || {};
const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];

const waiveSecurityMembrane = !!permissions['unsafe-content-script'];

const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager;
const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
  getService(Ci.nsIScriptSecurityManager);

const JS_VERSION = '1.8';

// Tests whether this window is loaded in a tab
function isWindowInTab(window) {
  if (isChildLoader) {
    let { frames } = require('../remote/child');
    let frame = frames.getFrameForWindow(window.top);
    return frame && frame.isTab;
  }
  else {
    // The deprecated sync worker API still does everything in the main process
    return getTabForContentWindowNoShim(window);
  }
}

const WorkerSandbox = Class({
  implements: [ EventTarget ],

  /**
   * Emit a message to the worker content sandbox
   */
  emit: function emit(type, ...args) {
    // JSON.stringify is buggy with cross-sandbox values,
    // it may return "{}" on functions. Use a replacer to match them correctly.
    let replacer = (k, v) =>
      typeof(v) === "function"
        ? (type === "console" ? Function.toString.call(v) : void(0))
        : v;

    // Ensure having an asynchronous behavior
    async(() =>
      emitToContent(this, JSON.stringify([type, ...args], replacer))
    );
  },

  /**
   * Synchronous version of `emit`.
   * /!\ Should only be used when it is strictly mandatory /!\
   *     Doesn't ensure passing only JSON values.
   *     Mainly used by context-menu in order to avoid breaking it.
   */
  emitSync: function emitSync(...args) {
    // because the arguments could be also non JSONable values,
    // we need to ensure the array instance is created from
    // the content's sandbox
    return emitToContent(this, new modelFor(this).sandbox.Array(...args));
  },

  /**
   * Configures sandbox and loads content scripts into it.
   * @param {Worker} worker
   *    content worker
   */
  initialize: function WorkerSandbox(worker, window) {
    let model = {};
    sandboxes.set(this, model);
    model.worker = worker;
    // We receive a wrapped window, that may be an xraywrapper if it's content
    let proto = window;

    // TODO necessary?
    // Ensure that `emit` has always the right `this`
    this.emit = this.emit.bind(this);
    this.emitSync = this.emitSync.bind(this);

    // Use expanded principal for content-script if the content is a
    // regular web content for better isolation.
    // (This behavior can be turned off for now with the unsafe-content-script
    // flag to give addon developers time for making the necessary changes)
    // But prevent it when the Worker isn't used for a content script but for
    // injecting `addon` object into a Panel scope, for example.
    // That's because:
    // 1/ It is useless to use multiple domains as the worker is only used
    // to communicate with the addon,
    // 2/ By using it it would prevent the document to have access to any JS
    // value of the worker. As JS values coming from multiple domain principals
    // can't be accessed by 'mono-principals' (principal with only one domain).
    // Even if this principal is for a domain that is specified in the multiple
    // domain principal.
    let principals = window;
    let wantGlobalProperties = [];
    let isSystemPrincipal = secMan.isSystemPrincipal(
      window.document.nodePrincipal);
    if (!isSystemPrincipal && !requiresAddonGlobal(worker)) {
      if (EXPANDED_PRINCIPALS.length > 0) {
        // We have to replace XHR constructor of the content document
        // with a custom cross origin one, automagically added by platform code:
        delete proto.XMLHttpRequest;
        wantGlobalProperties.push('XMLHttpRequest');
      }
      if (!waiveSecurityMembrane)
        principals = EXPANDED_PRINCIPALS.concat(window);
    }

    // Create the sandbox and bind it to window in order for content scripts to
    // have access to all standard globals (window, document, ...)
    let content = sandbox(principals, {
      sandboxPrototype: proto,
      wantXrays: !requiresAddonGlobal(worker),
      wantGlobalProperties: wantGlobalProperties,
      wantExportHelpers: true,
      sameZoneAs: window,
      metadata: {
        SDKContentScript: true,
        'inner-window-id': getInnerId(window)
      }
    });
    model.sandbox = content;

    // We have to ensure that window.top and window.parent are the exact same
    // object than window object, i.e. the sandbox global object. But not
    // always, in case of iframes, top and parent are another window object.
    let top = window.top === window ? content : content.top;
    let parent = window.parent === window ? content : content.parent;
    merge(content, {
      // We need 'this === window === top' to be true in toplevel scope:
      get window() {
        return content;
      },
      get top() {
        return top;
      },
      get parent() {
        return parent;
      }
    });

    // Use the Greasemonkey naming convention to provide access to the
    // unwrapped window object so the content script can access document
    // JavaScript values.
    // NOTE: this functionality is experimental and may change or go away
    // at any time!
    //
    // Note that because waivers aren't propagated between origins, we
    // need the unsafeWindow getter to live in the sandbox.
    var unsafeWindowGetter =
      new content.Function('return window.wrappedJSObject || window;');
    Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter});

    // Load trusted code that will inject content script API.
    let ContentWorker = load(content, CONTENT_WORKER_URL);

    // prepare a clean `self.options`
    let options = 'contentScriptOptions' in worker ?
      JSON.stringify(worker.contentScriptOptions) :
      undefined;

    // Then call `inject` method and communicate with this script
    // by trading two methods that allow to send events to the other side:
    //   - `onEvent` called by content script
    //   - `result.emitToContent` called by addon script
    let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker);
    let chromeAPI = createChromeAPI(ContentWorker);
    let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);

    // Merge `emitToContent` into our private model of the
    // WorkerSandbox so we can communicate with content script
    model.emitToContent = result;

    let console = new PlainTextConsole(null, getInnerId(window));

    // Handle messages send by this script:
    setListeners(this, console);

    // Inject `addon` global into target document if document is trusted,
    // `addon` in document is equivalent to `self` in content script.
    if (requiresAddonGlobal(worker)) {
      Object.defineProperty(getUnsafeWindow(window), 'addon', {
          value: content.self,
          configurable: true
        }
      );
    }

    // Inject our `console` into target document if worker doesn't have a tab
    // (e.g Panel, PageWorker).
    // `worker.tab` can't be used because bug 804935.
    if (!isWindowInTab(window)) {
      let win = getUnsafeWindow(window);

      // export our chrome console to content window, as described here:
      // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
      let con = Cu.createObjectIn(win);

      let genPropDesc = function genPropDesc(fun) {
        return { enumerable: true, configurable: true, writable: true,
          value: console[fun] };
      }

      const properties = {
        log: genPropDesc('log'),
        info: genPropDesc('info'),
        warn: genPropDesc('warn'),
        error: genPropDesc('error'),
        debug: genPropDesc('debug'),
        trace: genPropDesc('trace'),
        dir: genPropDesc('dir'),
        group: genPropDesc('group'),
        groupCollapsed: genPropDesc('groupCollapsed'),
        groupEnd: genPropDesc('groupEnd'),
        time: genPropDesc('time'),
        timeEnd: genPropDesc('timeEnd'),
        profile: genPropDesc('profile'),
        profileEnd: genPropDesc('profileEnd'),
        exception: genPropDesc('exception'),
        assert: genPropDesc('assert'),
        count: genPropDesc('count'),
        table: genPropDesc('table'),
        clear: genPropDesc('clear'),
        dirxml: genPropDesc('dirxml'),
        timeStamp: genPropDesc('timeStamp'),
      };

      Object.defineProperties(con, properties);
      Cu.makeObjectPropsNormal(con);

      win.console = con;
    };

    emit(events, "content-script-before-inserted", {
      window: window,
      worker: worker
    });

    // The order of `contentScriptFile` and `contentScript` evaluation is
    // intentional, so programs can load libraries like jQuery from script URLs
    // and use them in scripts.
    let contentScriptFile = ('contentScriptFile' in worker)
          ? worker.contentScriptFile
          : null,
        contentScript = ('contentScript' in worker)
          ? worker.contentScript
          : null;

    if (contentScriptFile)
      importScripts.apply(null, [this].concat(contentScriptFile));

    if (contentScript) {
      evaluateIn(
        this,
        Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
      );
    }
  },
  destroy: function destroy(reason) {
    if (typeof reason != 'string')
      reason = '';
    this.emitSync('event', 'detach', reason);
    let model = modelFor(this);
    model.sandbox = null
    model.worker = null;
  },

});

exports.WorkerSandbox = WorkerSandbox;

/**
 * Imports scripts to the sandbox by reading files under urls and
 * evaluating its source. If exception occurs during evaluation
 * `'error'` event is emitted on the worker.
 * This is actually an analog to the `importScript` method in web
 * workers but in our case it's not exposed even though content
 * scripts may be able to do it synchronously since IO operation
 * takes place in the UI process.
 */
function importScripts (workerSandbox, ...urls) {
  let { worker, sandbox } = modelFor(workerSandbox);
  for (let i in urls) {
    let contentScriptFile = data.url(urls[i]);

    try {
      let uri = URL(contentScriptFile);
      if (uri.scheme === 'resource')
        load(sandbox, String(uri));
      else
        throw Error('Unsupported `contentScriptFile` url: ' + String(uri));
    }
    catch(e) {
      emit(worker, 'error', e);
    }
  }
}

function setListeners (workerSandbox, console) {
  let { worker } = modelFor(workerSandbox);
  // console.xxx calls
  workerSandbox.on('console', function consoleListener (kind, ...args) {
    console[kind].apply(console, args);
  });

  // self.postMessage calls
  workerSandbox.on('message', function postMessage(data) {
    // destroyed?
    if (worker)
      emit(worker, 'message', data);
  });

  // self.port.emit calls
  workerSandbox.on('event', function portEmit (...eventArgs) {
    // If not destroyed, emit event information to worker
    // `eventArgs` has the event name as first element,
    // and remaining elements are additional arguments to pass
    if (worker)
      emit.apply(null, [worker.port].concat(eventArgs));
  });

  // unwrap, recreate and propagate async Errors thrown from content-script
  workerSandbox.on('error', function onError({instanceOfError, value}) {
    if (worker) {
      let error = value;
      if (instanceOfError) {
        error = new Error(value.message, value.fileName, value.lineNumber);
        error.stack = value.stack;
        error.name = value.name;
      }
      emit(worker, 'error', error);
    }
  });
}

/**
 * Evaluates code in the sandbox.
 * @param {String} code
 *    JavaScript source to evaluate.
 * @param {String} [filename='javascript:' + code]
 *    Name of the file
 */
function evaluateIn (workerSandbox, code, filename) {
  let { worker, sandbox } = modelFor(workerSandbox);
  try {
    evaluate(sandbox, code, filename || 'javascript:' + code);
  }
  catch(e) {
    emit(worker, 'error', e);
  }
}

/**
 * Method called by the worker sandbox when it needs to send a message
 */
function onContentEvent (workerSandbox, args) {
  // As `emit`, we ensure having an asynchronous behavior
  async(function () {
    // We emit event to chrome/addon listeners
    emit.apply(null, [workerSandbox].concat(JSON.parse(args)));
  });
}


function modelFor (workerSandbox) {
  return sandboxes.get(workerSandbox);
}

function getUnsafeWindow (win) {
  return win.wrappedJSObject || win;
}

function emitToContent (workerSandbox, args) {
  return modelFor(workerSandbox).emitToContent(args);
}

function createChromeAPI (scope) {
  return Cu.cloneInto({
    timers: {
      setTimeout: timer.setTimeout.bind(timer),
      setInterval: timer.setInterval.bind(timer),
      clearTimeout: timer.clearTimeout.bind(timer),
      clearInterval: timer.clearInterval.bind(timer),
    },
    sandbox: {
      evaluate: evaluate,
    },
  }, scope, {cloneFunctions: true});
}
PK
!<ϱ*modules/commonjs/sdk/content/tab-events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Ci } = require('chrome');
const system = require('sdk/system/events');
const { frames } = require('sdk/remote/child');
const { WorkerChild } = require('sdk/content/worker-child');

// map observer topics to tab event names
const EVENTS = {
  'content-document-global-created': 'create',
  'chrome-document-global-created': 'create',
  'content-document-interactive': 'ready',
  'chrome-document-interactive': 'ready',
  'content-document-loaded': 'load',
  'chrome-document-loaded': 'load',
// 'content-page-shown': 'pageshow', // bug 1024105
}

function topicListener({ subject, type }) {
  // NOTE detect the window from the subject:
  // - on *-global-created the subject is the window
  // - in the other cases it is the document object
  let window = subject instanceof Ci.nsIDOMWindow ? subject : subject.defaultView;
  if (!window){
    return;
  }
  let frame = frames.getFrameForWindow(window);
  if (frame) {
    let readyState = frame.content.document.readyState;
    frame.port.emit('sdk/tab/event', EVENTS[type], { readyState });
  }
}

for (let topic in EVENTS)
  system.on(topic, topicListener, true);

// bug 1024105 - content-page-shown notification doesn't pass persisted param
function eventListener({target, type, persisted}) {
  let frame = this;
  if (target === frame.content.document) {
    frame.port.emit('sdk/tab/event', type, persisted);
  }
}
frames.addEventListener('pageshow', eventListener, true);

frames.port.on('sdk/tab/attach', (frame, options) => {
  options.window = frame.content;
  new WorkerChild(options);
});

// Forward the existent frames's readyState.
for (let frame of frames) {
  let readyState = frame.content.document.readyState;
  frame.port.emit('sdk/tab/event', 'init', { readyState });
}
PK
!<&@@)modules/commonjs/sdk/content/thumbnail.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

const { Cc, Ci, Cu } = require('chrome');
const AppShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                        getService(Ci.nsIAppShellService);

const NS = 'http://www.w3.org/1999/xhtml';
const COLOR = 'rgb(255,255,255)';

/**
 * Creates canvas element with a thumbnail of the passed window.
 * @param {Window} window
 * @returns {Element}
 */
function getThumbnailCanvasForWindow(window) {
  let aspectRatio = 0.5625; // 16:9
  let thumbnail = AppShellService.hiddenDOMWindow.document
                    .createElementNS(NS, 'canvas');
  thumbnail.mozOpaque = true;
  thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
  thumbnail.height = Math.round(thumbnail.width * aspectRatio);
  let ctx = thumbnail.getContext('2d');
  let snippetWidth = window.innerWidth * .6;
  let scale = thumbnail.width / snippetWidth;
  ctx.scale(scale, scale);
  ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
                snippetWidth * aspectRatio, COLOR);
  return thumbnail;
}
exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;

/**
 * Creates Base64 encoded data URI of the thumbnail for the passed window.
 * @param {Window} window
 * @returns {String}
 */
exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
  return getThumbnailCanvasForWindow(window).toDataURL()
};

// default 80x45 blank when not available
exports.BLANK = 'data:image/png;base64,' +
  'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+
  'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';
PK
!<{%modules/commonjs/sdk/content/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

lazyRequire(this, '../util/object', "merge");
lazyRequire(this, '../self', "data");
var assetsURI = data.url();
var isArray = Array.isArray;
var method = require('../../method/core');
lazyRequire(this, '../util/uuid', "uuid");

const isAddonContent = ({ contentURL }) =>
  contentURL && data.url(contentURL).startsWith(assetsURI);

exports.isAddonContent = isAddonContent;

function hasContentScript({ contentScript, contentScriptFile }) {
  return (isArray(contentScript) ? contentScript.length > 0 :
         !!contentScript) ||
         (isArray(contentScriptFile) ? contentScriptFile.length > 0 :
         !!contentScriptFile);
}
exports.hasContentScript = hasContentScript;

function requiresAddonGlobal(model) {
  return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model));
}
exports.requiresAddonGlobal = requiresAddonGlobal;

function getAttachEventType(model) {
  if (!model) return null;
  let when = model.contentScriptWhen;
  return requiresAddonGlobal(model) ? 'document-element-inserted' :
         when === 'start' ? 'document-element-inserted' :
         when === 'ready' ? 'DOMContentLoaded' :
         when === 'end' ? 'load' :
         null;
}
exports.getAttachEventType = getAttachEventType;

var attach = method('worker-attach');
exports.attach = attach;

var connect = method('worker-connect');
exports.connect = connect;

var detach = method('worker-detach');
exports.detach = detach;

var destroy = method('worker-destroy');
exports.destroy = destroy;

function WorkerHost (workerFor) {
  // Define worker properties that just proxy to underlying worker
  return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) {
    // Use descriptor properties instead so we can call
    // the worker function in the context of the worker so we
    // don't have to create new functions with `fn.bind(worker)`
    let descriptorProp = {
      value: function (...args) {
        let worker = workerFor(this);
        return worker[name].apply(worker, args);
      }
    };
    
    let accessorProp = {
      get: function () { return workerFor(this)[name]; },
      set: function (value) { workerFor(this)[name] = value; }
    };

    Object.defineProperty(proto, name, merge({
      enumerable: true,
      configurable: false,
    }, isDescriptor(name) ? descriptorProp : accessorProp));
    return proto;
  }, {});
  
  function isDescriptor (prop) {
    return ~['postMessage'].indexOf(prop);
  }
}
exports.WorkerHost = WorkerHost;

function makeChildOptions(options) {
  function makeStringArray(arrayOrValue) {
    if (!arrayOrValue)
      return [];
    return [].concat(arrayOrValue).map(String);
  }

  return {
    id: String(uuid()),
    contentScript: makeStringArray(options.contentScript),
    contentScriptFile: makeStringArray(options.contentScriptFile),
    contentScriptOptions: options.contentScriptOptions ?
                          JSON.stringify(options.contentScriptOptions) :
                          null,
  }
}
exports.makeChildOptions = makeChildOptions;
PK
!<xx,modules/commonjs/sdk/content/worker-child.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

lazyRequire(this, '../util/object', 'merge');
const { Class } = require('../core/heritage');
lazyRequire(this, '../event/core', 'emit');
const { EventTarget } = require('../event/target');
lazyRequire(this, '../window/utils', 'getInnerId');
lazyRequire(this, '../lang/type', 'instanceOf', 'isObject');
lazyRequireModule(this, '../system/events', 'system');
const { when } = require('../system/unload');
lazyRequire(this, './sandbox', 'WorkerSandbox');
const { Ci } = require('chrome');
const { process, frames } = require('../remote/child');

const EVENTS = {
  'chrome-page-shown': 'pageshow',
  'content-page-shown': 'pageshow',
  'chrome-page-hidden': 'pagehide',
  'content-page-hidden': 'pagehide',
  'inner-window-destroyed': 'detach',
}

// The parent Worker must have been created (or an async message sent to spawn
// its creation) before creating the WorkerChild or messages from the content
// script to the parent will get lost.
const WorkerChild = Class({
  implements: [EventTarget],

  initialize(options) {
    merge(this, options);
    keepAlive.set(this.id, this);

    this.windowId = getInnerId(this.window);
    if (this.contentScriptOptions)
      this.contentScriptOptions = JSON.parse(this.contentScriptOptions);

    this.port = EventTarget();
    this.port.on('*', this.send.bind(this, 'event'));
    this.on('*', this.send.bind(this));

    this.observe = this.observe.bind(this);

    for (let topic in EVENTS)
      system.on(topic, this.observe);

    this.receive = this.receive.bind(this);
    process.port.on('sdk/worker/message', this.receive);

    this.sandbox = WorkerSandbox(this, this.window);

    // If the document has an unexpected readyState, its worker-child instance is initialized
    // as frozen until one of the known readyState is reached.
    let initialDocumentReadyState = this.window.document.readyState;
    this.frozen = [
      "loading", "interactive", "complete"
    ].includes(initialDocumentReadyState) ? false : true;

    if (this.frozen) {
      console.warn("SDK worker-child started as frozen on unexpected initial document.readyState", {
        initialDocumentReadyState, windowLocation: this.window.location.href,
      });
    }

    this.frozenMessages = [];
    this.on('pageshow', () => {
      this.frozen = false;
      this.frozenMessages.forEach(args => this.sandbox.emit(...args));
      this.frozenMessages = [];
    });
    this.on('pagehide', () => {
      this.frozen = true;
    });
  },

  // messages
  receive(process, id, args) {
    if (id !== this.id)
      return;
    args = JSON.parse(args);

    if (this.frozen)
      this.frozenMessages.push(args);
    else
      this.sandbox.emit(...args);

    if (args[0] === 'detach')
      this.destroy(args[1]);
  },

  send(...args) {
    process.port.emit('sdk/worker/event', this.id, JSON.stringify(args, exceptions));
  },

  // notifications
  observe({ type, subject }) {
    if (!this.sandbox)
      return;

    if (subject.defaultView && getInnerId(subject.defaultView) === this.windowId) {
      this.sandbox.emitSync(EVENTS[type]);
      emit(this, EVENTS[type]);
    }

    if (type === 'inner-window-destroyed' &&
        subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.windowId) {
      this.destroy();
    }
  },

  get frame() {
    return frames.getFrameForWindow(this.window.top);
  },

  // detach/destroy: unload and release the sandbox
  destroy(reason) {
    if (!this.sandbox)
      return;

    for (let topic in EVENTS)
      system.off(topic, this.observe);
    process.port.off('sdk/worker/message', this.receive);

    this.sandbox.destroy(reason);
    this.sandbox = null;
    keepAlive.delete(this.id);

    this.send('detach');
  }
})
exports.WorkerChild = WorkerChild;

// Error instances JSON poorly
function exceptions(key, value) {
  if (!isObject(value) || !instanceOf(value, Error))
    return value;
  let _errorType = value.constructor.name;
  let { message, fileName, lineNumber, stack, name } = value;
  return { _errorType, message, fileName, lineNumber, stack, name };
}

// workers for windows in this tab
var keepAlive = new Map();

process.port.on('sdk/worker/create', (process, options, cpows) => {
  options.window = cpows.window;
  let worker = new WorkerChild(options);

  let frame = frames.getFrameForWindow(options.window.top);
  frame.port.emit('sdk/worker/connect', options.id, options.window.location.href);
});

when(reason => {
  for (let worker of keepAlive.values())
    worker.destroy(reason);
});
PK
!<ɽ!&modules/commonjs/sdk/content/worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

lazyRequire(this, '../event/core', "emit");
const { omit, merge } = require('../util/object');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
lazyRequire(this, '../window/utils', "getInnerId");
const { EventTarget } = require('../event/target');
lazyRequire(this, '../private-browsing/utils', "isPrivate");
lazyRequire(this, '../tabs/utils', "getTabForBrowser", "getTabForContentWindowNoShim", "getBrowserForTab");
lazyRequire(this, './utils', "attach", "connect", "detach", "destroy", "makeChildOptions");
const { ensure } = require('../system/unload');
lazyRequire(this, '../system/events', {"on": "observe"});
const { Ci, Cu } = require('chrome');
lazyRequire(this, 'sdk/model/core', {"modelFor": "tabFor"});
const { remoteRequire, processes, frames } = require('../remote/parent');
remoteRequire('sdk/content/worker-child');

const workers = new WeakMap();
var modelFor = (worker) => workers.get(worker);

const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
  "The script may not be initialized yet, or may already have been unloaded.";

// a handle for communication between content script and addon code
const Worker = Class({
  implements: [EventTarget],

  initialize(options = {}) {
    ensure(this, 'detach');

    let model = {
      attached: false,
      destroyed: false,
      earlyEvents: [],        // fired before worker was attached
      frozen: true,           // document is not yet active
      options,
    };
    workers.set(this, model);

    this.on('detach', this.detach);
    EventTarget.prototype.initialize.call(this, options);

    this.receive = this.receive.bind(this);

    this.port = EventTarget();
    this.port.emit = this.send.bind(this, 'event');
    this.postMessage = this.send.bind(this, 'message');

    if ('window' in options) {
      let window = options.window;
      delete options.window;
      attach(this, window);
    }
  },

  // messages
  receive(process, id, args) {
    let model = modelFor(this);
    if (id !== model.id || !model.attached)
      return;
    args = JSON.parse(args);
    if (model.destroyed && args[0] != 'detach')
      return;

    if (args[0] === 'event')
      emit(this.port, ...args.slice(1))
    else
      emit(this, ...args);
  },

  send(...args) {
    let model = modelFor(this);
    if (model.destroyed && args[0] !== 'detach')
      throw new Error(ERR_DESTROYED);

    if (!model.attached) {
      model.earlyEvents.push(args);
      return;
    }

    processes.port.emit('sdk/worker/message', model.id, JSON.stringify(args));
  },

  // properties
  get url() {
    let { url } = modelFor(this);
    return url;
  },

  get contentURL() {
    return this.url;
  },

  get tab() {
    require('sdk/tabs');
    let { frame } = modelFor(this);
    if (!frame)
      return null;
    let rawTab = getTabForBrowser(frame.frameElement);
    return rawTab && tabFor(rawTab);
  },

  toString: () => '[object Worker]',

  detach: method(detach),
  destroy: method(destroy),
})
exports.Worker = Worker;

attach.define(Worker, function(worker, window) {
  let model = modelFor(worker);
  if (model.attached)
    detach(worker);

  let childOptions = makeChildOptions(model.options);
  processes.port.emitCPOW('sdk/worker/create', [childOptions], { window });

  let listener = (frame, id, url) => {
    if (id != childOptions.id)
      return;
    frames.port.off('sdk/worker/connect', listener);
    connect(worker, frame, { id, url });
  };
  frames.port.on('sdk/worker/connect', listener);
});

connect.define(Worker, function(worker, frame, { id, url }) {
  let model = modelFor(worker);
  if (model.attached)
    detach(worker);

  model.id = id;
  model.frame = frame;
  model.url = url;

  // Messages from content -> chrome come through the process message manager
  // since that lives longer than the frame message manager
  processes.port.on('sdk/worker/event', worker.receive);

  model.attached = true;
  model.destroyed = false;
  model.frozen = false;

  model.earlyEvents.forEach(args => worker.send(...args));
  model.earlyEvents = [];
  emit(worker, 'attach');
});

// unload and release the child worker, release window reference
detach.define(Worker, function(worker) {
  let model = modelFor(worker);
  if (!model.attached)
    return;

  processes.port.off('sdk/worker/event', worker.receive);
  model.attached = false;
  model.destroyed = true;
  emit(worker, 'detach');
});

isPrivate.define(Worker, ({ tab }) => isPrivate(tab));

// Something in the parent side has destroyed the worker, tell the child to
// detach, the child will respond when it has detached
destroy.define(Worker, function(worker, reason) {
  let model = modelFor(worker);
  model.destroyed = true;
  if (!model.attached)
    return;

  worker.send('detach', reason);
});
PK
!<99,modules/commonjs/sdk/context-menu/context.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Class } = require("../core/heritage");
lazyRequire(this, "../util/match-pattern", "MatchPattern");
const readers = require("./readers");

// Context class is required to implement a single `isCurrent(target)` method
// that must return boolean value indicating weather given target matches a
// context or not. Most context implementations below will have an associated
// reader that way context implementation can setup a reader to extract necessary
// information to make decision if target is matching a context.
const Context = Class({
  isRequired: false,
  isCurrent(target) {
    throw Error("Context class must implement isCurrent(target) method");
  },
  get required() {
    Object.defineProperty(this, "required", {
      value: Object.assign(Object.create(Object.getPrototypeOf(this)),
                           this,
                           {isRequired: true})
    });
    return this.required;
  }
});
Context.required = function(...params) {
  return Object.assign(new this(...params), {isRequired: true});
};
exports.Context = Context;


// Next few context implementations use an associated reader to extract info
// from the context target and story it to a private symbol associtaed with
// a context implementation. That way name collisions are avoided while required
// information is still carried along.
const isPage = Symbol("context/page?")
const PageContext = Class({
  extends: Context,
  read: {[isPage]: new readers.isPage()},
  isCurrent: target => target[isPage]
});
exports.Page = PageContext;

const isFrame = Symbol("context/frame?");
const FrameContext = Class({
  extends: Context,
  read: {[isFrame]: new readers.isFrame()},
  isCurrent: target => target[isFrame]
});
exports.Frame = FrameContext;

const selection = Symbol("context/selection")
const SelectionContext = Class({
  read: {[selection]: new readers.Selection()},
  isCurrent: target => !!target[selection]
});
exports.Selection = SelectionContext;

const link = Symbol("context/link");
const LinkContext = Class({
  extends: Context,
  read: {[link]: new readers.LinkURL()},
  isCurrent: target => !!target[link]
});
exports.Link = LinkContext;

const isEditable = Symbol("context/editable?")
const EditableContext = Class({
  extends: Context,
  read: {[isEditable]: new readers.isEditable()},
  isCurrent: target => target[isEditable]
});
exports.Editable = EditableContext;


const mediaType = Symbol("context/mediaType")

const ImageContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "image"
});
exports.Image = ImageContext;


const VideoContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "video"
});
exports.Video = VideoContext;


const AudioContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "audio"
});
exports.Audio = AudioContext;

const isSelectorMatch = Symbol("context/selector/mathches?")
const SelectorContext = Class({
  extends: Context,
  initialize(selector) {
    this.selector = selector;
    // Each instance of selector context will need to store read
    // data into different field, so that case with multilpe selector
    // contexts won't cause a conflicts.
    this[isSelectorMatch] = Symbol(selector);
    this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
  },
  isCurrent(target) {
    return target[this[isSelectorMatch]];
  }
});
exports.Selector = SelectorContext;

const url = Symbol("context/url");
const URLContext = Class({
  extends: Context,
  initialize(pattern) {
    this.pattern = new MatchPattern(pattern);
  },
  read: {[url]: new readers.PageURL()},
  isCurrent(target) {
    return this.pattern.test(target[url]);
  }
});
exports.URL = URLContext;

var PredicateContext = Class({
  extends: Context,
  initialize(isMatch) {
    if (typeof(isMatch) !== "function") {
      throw TypeError("Predicate context mus be passed a function");
    }

    this.isMatch = isMatch
  },
  isCurrent(target) {
    return this.isMatch(target);
  }
});
exports.Predicate = PredicateContext;
PK
!<Zƪ33)modules/commonjs/sdk/context-menu/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const Contexts = require("./context");
const Readers = require("./readers");
const Component = require("../ui/component");
const { Class } = require("../core/heritage");
const { map, filter, object, reduce, keys, symbols,
        pairs, values, each, some, isEvery, count } = require("../util/sequence");
const { loadModule } = require("framescript/manager");
const { Cu, Cc, Ci } = require("chrome");
const prefs = require("sdk/preferences/service");

const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                              .getService(Ci.nsIMessageListenerManager);
const preferencesService = Cc["@mozilla.org/preferences-service;1"].
                            getService(Ci.nsIPrefService).
                            getBranch(null);


const readTable = Symbol("context-menu/read-table");
const nameTable = Symbol("context-menu/name-table");
const onContext = Symbol("context-menu/on-context");
const isMatching = Symbol("context-menu/matching-handler?");

exports.onContext = onContext;
exports.readTable = readTable;
exports.nameTable = nameTable;


const propagateOnContext = (item, data) =>
  each(child => child[onContext](data), item.state.children);

const isContextMatch = item => !item[isMatching] || item[isMatching]();

// For whatever reason addWeakMessageListener does not seems to work as our
// instance seems to dropped even though it's alive. This is simple workaround
// to avoid dead object excetptions.
const WeakMessageListener = function(receiver, handler="receiveMessage") {
  this.receiver = receiver
  this.handler = handler
};
WeakMessageListener.prototype = {
  constructor: WeakMessageListener,
  receiveMessage(message) {
    if (Cu.isDeadWrapper(this.receiver)) {
      message.target.messageManager.removeMessageListener(message.name, this);
    }
    else {
      this.receiver[this.handler](message);
    }
  }
};

const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
const onMessage = Symbol("context-menu/message-listener");
const onPreferceChange = Symbol("context-menu/preference-change");
const ContextMenuExtension = Class({
  extends: Component,
  initialize: Component,
  setup() {
    const messageListener = new WeakMessageListener(this, onMessage);
    loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
    globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
    globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);

    preferencesService.addObserver(OVERFLOW_THRESH, this);
  },
  observe(_, __, name) {
    if (name === OVERFLOW_THRESH) {
      const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
      this[Component.patch]({overflowThreshold});
    }
  },
  [onMessage]({name, data, target}) {
    if (name === "sdk/context-menu/read")
      this[onContext]({target, data});
    if (name === "sdk/context-menu/readers?")
      target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
                                             JSON.parse(JSON.stringify(this.state.readers)));
  },
  [Component.initial](options={}, children) {
    const element = options.element || null;
    const target = options.target || null;
    const readers = Object.create(null);
    const users = Object.create(null);
    const registry = new WeakSet();
    const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);

    return { target, children: [], readers, users, element,
             registry, overflowThreshold };
  },
  [Component.isUpdated](before, after) {
    // Update only if target changed, since there is no point in re-rendering
    // when children are. Also new items added won't be in sync with a latest
    // context target so we should really just render before drawing context
    // menu.
    return before.target !== after.target;
  },
  [Component.render]({element, children, overflowThreshold}) {
    if (!element) return null;

    const items = children.filter(isContextMatch);
    const body = items.length === 0 ? items :
                 items.length < overflowThreshold ? [new Separator(),
                                                     ...items] :
                 [{tagName: "menu",
                   className: "sdk-context-menu-overflow-menu",
                   label: "Add-ons",
                   accesskey: "A",
                   children: [{tagName: "menupopup",
                               children: items}]}];
    return {
      element: element,
      tagName: "menugroup",
      style: "-moz-box-orient: vertical;",
      className: "sdk-context-menu-extension",
      children: body
    }
  },
  // Adds / remove child to it's own list.
  add(item) {
    this[Component.patch]({children: this.state.children.concat(item)});
  },
  remove(item) {
    this[Component.patch]({
      children: this.state.children.filter(x => x !== item)
    });
  },
  register(item) {
    const { users, registry } = this.state;
    if (registry.has(item)) return;
    registry.add(item);

    // Each (ContextHandler) item has a readTable that is a
    // map of keys to readers extracting them from the content.
    // During the registraction we update intrnal record of unique
    // readers and users per reader. Most context will have a reader
    // shared across all instances there for map of users per reader
    // is stored separately from the reader so that removing reader
    // will occur only when no users remain.
    const table = item[readTable];
    // Context readers store data in private symbols so we need to
    // collect both table keys and private symbols.
    const names = [...keys(table), ...symbols(table)];
    const readers = map(name => table[name], names);
    // Create delta for registered readers that will be merged into
    // internal readers table.
    const added = filter(x => !users[x.id], readers);
    const delta = object(...map(x => [x.id, x], added));

    const update = reduce((update, reader) => {
      const n = update[reader.id] || 0;
      update[reader.id] = n + 1;
      return update;
    }, Object.assign({}, users), readers);

    // Patch current state with a changes that registered item caused.
    this[Component.patch]({users: update,
                           readers: Object.assign(this.state.readers, delta)});

    if (count(added)) {
      globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
                                                 JSON.parse(JSON.stringify(delta)));
    }
  },
  unregister(item) {
    const { users, registry } = this.state;
    if (!registry.has(item)) return;
    registry.delete(item);

    const table = item[readTable];
    const names = [...keys(table), ...symbols(table)];
    const readers = map(name => table[name], names);
    const update = reduce((update, reader) => {
      update[reader.id] = update[reader.id] - 1;
      return update;
    }, Object.assign({}, users), readers);
    const removed = filter(id => !update[id], keys(update));
    const delta = object(...map(x => [x, null], removed));

    this[Component.patch]({users: update,
                           readers: Object.assign(this.state.readers, delta)});

    if (count(removed)) {
      globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
                                                 JSON.parse(JSON.stringify(delta)));
    }
  },

  [onContext]({data, target}) {
    propagateOnContext(this, data);
    const document = target.ownerDocument;
    const element = document.getElementById("contentAreaContextMenu");

    this[Component.patch]({target: data, element: element});
  }
});this,
exports.ContextMenuExtension = ContextMenuExtension;

// Takes an item options and
const makeReadTable = ({context, read}) => {
  // Result of this function is a tuple of all readers &
  // name, reader id pairs.

  // Filter down to contexts that have a reader associated.
  const contexts = filter(context => context.read, context);
  // Merge all contexts read maps to a single hash, note that there should be
  // no name collisions as context implementations expect to use private
  // symbols for storing it's read data.
  return Object.assign({}, ...map(({read}) => read, contexts), read);
}

const readTarget = (nameTable, data) =>
  object(...map(([name, id]) => [name, data[id]], nameTable))

const ContextHandler = Class({
  extends: Component,
  initialize: Component,
  get context() {
    return this.state.options.context;
  },
  get read() {
    return this.state.options.read;
  },
  [Component.initial](options) {
    return {
      table: makeReadTable(options),
      requiredContext: filter(context => context.isRequired, options.context),
      optionalContext: filter(context => !context.isRequired, options.context)
    }
  },
  [isMatching]() {
    const {target, requiredContext, optionalContext} = this.state;
    return isEvery(context => context.isCurrent(target), requiredContext) &&
            (count(optionalContext) === 0 ||
             some(context => context.isCurrent(target), optionalContext));
  },
  setup() {
    const table = makeReadTable(this.state.options);
    this[readTable] = table;
    this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
                       ...map(name => [name, table[name].id], keys(table))];


    contextMenu.register(this);

    each(child => contextMenu.remove(child), this.state.children);
    contextMenu.add(this);
  },
  dispose() {
    contextMenu.remove(this);

    each(child => contextMenu.unregister(child), this.state.children);
    contextMenu.unregister(this);
  },
  // Internal `Symbol("onContext")` method is invoked when "contextmenu" event
  // occurs in content process. Context handles with children delegate to each
  // child and patch it's internal state to reflect new contextmenu target.
  [onContext](data) {
    propagateOnContext(this, data);
    this[Component.patch]({target: readTarget(this[nameTable], data)});
  }
});
const isContextHandler = item => item instanceof ContextHandler;

exports.ContextHandler = ContextHandler;

const Menu = Class({
  extends: ContextHandler,
  [isMatching]() {
    return ContextHandler.prototype[isMatching].call(this) &&
           this.state.children.filter(isContextHandler)
                              .some(isContextMatch);
  },
  [Component.render]({children, options}) {
    const items = children.filter(isContextMatch);
    return {tagName: "menu",
            className: "sdk-context-menu menu-iconic",
            label: options.label,
            accesskey: options.accesskey,
            image: options.icon,
            children: [{tagName: "menupopup",
                        children: items}]};
  }
});
exports.Menu = Menu;

const onCommand = Symbol("context-menu/item/onCommand");
const Item = Class({
  extends: ContextHandler,
  get onClick() {
    return this.state.options.onClick;
  },
  [Component.render]({options}) {
    const {label, icon, accesskey} = options;
    return {tagName: "menuitem",
            className: "sdk-context-menu-item menuitem-iconic",
            label,
            accesskey,
            image: icon,
            oncommand: this};
  },
  handleEvent(event) {
    if (this.onClick)
      this.onClick(this.state.target);
  }
});
exports.Item = Item;

var Separator = Class({
  extends: Component,
  initialize: Component,
  [Component.render]() {
    return {tagName: "menuseparator",
            className: "sdk-context-menu-separator"}
  },
  [onContext]() {

  }
});
exports.Separator = Separator;

exports.Contexts = Contexts;
exports.Readers = Readers;

const createElement = (vnode, {document}) => {
   const node = vnode.namespace ?
              document.createElementNS(vnode.namespace, vnode.tagName) :
              document.createElement(vnode.tagName);

   node.setAttribute("data-component-path", vnode[Component.path]);

   each(([key, value]) => {
     if (key === "tagName") {
       return;
     }
     if (key === "children") {
       return;
     }

     if (key.startsWith("on")) {
       node.addEventListener(key.substr(2), value)
       return;
     }

     if (typeof(value) !== "object" &&
         typeof(value) !== "function" &&
         value !== void(0) &&
         value !== null)
    {
       if (key === "className") {
         node[key] = value;
       }
       else {
         node.setAttribute(key, value);
       }
       return;
     }
   }, pairs(vnode));

  each(child => node.appendChild(createElement(child, {document})), vnode.children);
  return node;
};

const htmlWriter = tree => {
  if (tree !== null) {
    const root = tree.element;
    const node = createElement(tree, {document: root.ownerDocument});
    const before = root.querySelector("[data-component-path='/']");
    if (before) {
      root.replaceChild(node, before);
    } else {
      root.appendChild(node);
    }
  }
};


const contextMenu = ContextMenuExtension();
exports.contextMenu = contextMenu;
Component.mount(contextMenu, htmlWriter);
PK
!<,UB,modules/commonjs/sdk/context-menu/readers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { memoize, method, identity } = require("../lang/functional");

const serializeCategory = ({type}) => ({ category: `reader/${type}()` });

const Reader = Class({
  initialize() {
    this.id = `reader/${this.type}()`
  },
  toJSON() {
    return serializeCategory(this);
  }
});


const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
exports.MediaType = MediaTypeReader;

const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
exports.LinkURL = LinkURLReader;

const SelectionReader = Class({ extends: Reader, type: "Selection" });
exports.Selection = SelectionReader;

const isPageReader = Class({ extends: Reader, type: "isPage" });
exports.isPage = isPageReader;

const isFrameReader = Class({ extends: Reader, type: "isFrame" });
exports.isFrame = isFrameReader;

const isEditable = Class({ extends: Reader, type: "isEditable"});
exports.isEditable = isEditable;



const ParameterizedReader = Class({
  extends: Reader,
  readParameter: function(value) {
    return value;
  },
  toJSON: function() {
    var json = serializeCategory(this);
    json[this.parameter] = this[this.parameter];
    return json;
  },
  initialize(...params) {
    if (params.length) {
      this[this.parameter] = this.readParameter(...params);
    }
    this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
  }
});
exports.ParameterizedReader = ParameterizedReader;


const QueryReader = Class({
  extends: ParameterizedReader,
  type: "Query",
  parameter: "path"
});
exports.Query = QueryReader;


const AttributeReader = Class({
  extends: ParameterizedReader,
  type: "Attribute",
  parameter: "name"
});
exports.Attribute = AttributeReader;

const SrcURLReader = Class({
  extends: AttributeReader,
  name: "src",
});
exports.SrcURL = SrcURLReader;

const PageURLReader = Class({
  extends: QueryReader,
  path: "ownerDocument.URL",
});
exports.PageURL = PageURLReader;

const SelectorMatchReader = Class({
  extends: ParameterizedReader,
  type: "SelectorMatch",
  parameter: "selector"
});
exports.SelectorMatch = SelectorMatchReader;

const extractors = new WeakMap();
extractors.id = 0;


var Extractor = Class({
  extends: ParameterizedReader,
  type: "Extractor",
  parameter: "source",
  initialize: function(f) {
    this[this.parameter] = String(f);
    if (!extractors.has(f)) {
      extractors.id = extractors.id + 1;
      extractors.set(f, extractors.id);
    }

    this.id = `reader/${this.type}.for(${extractors.get(f)})`
  }
});
exports.Extractor = Extractor;
PK
!<oςς$modules/commonjs/sdk/context-menu.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable",
  "engines": {
    // TODO Fennec support Bug 788334
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Class, mix } = require("./core/heritage");
const { ns } = require("./core/namespace");
lazyRequire(this, "./deprecated/api-utils", "validateOptions", "getTypeOf");
lazyRequire(this, "./url", "URL", "isValidURI");
lazyRequire(this, "./deprecated/window-utils", "WindowTracker", "browserWindowIterator");
lazyRequire(this, "./window/utils", "isBrowser", "getInnerId");
lazyRequire(this, "./util/match-pattern", "MatchPattern");
const { EventTarget } = require("./event/target");
lazyRequire(this, './event/core', "emit");
const { when } = require('./system/unload');
const { contract: loaderContract } = require('./content/loader');
const { omit } = require('./util/object');
lazyRequireModule(this, './self', "self");
const { remoteRequire, processes } = require('./remote/parent');
remoteRequire('sdk/content/context-menu');

// All user items we add have this class.
const ITEM_CLASS = "addon-context-menu-item";

// Items in the top-level context menu also have this class.
const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel";

// Items in the overflow submenu also have this class.
const OVERFLOW_ITEM_CLASS = "addon-context-menu-item-overflow";

// The class of the menu separator that separates standard context menu items
// from our user items.
const SEPARATOR_CLASS = "addon-context-menu-separator";

// If more than this number of items are added to the context menu, all items
// overflow into a "Jetpack" submenu.
const OVERFLOW_THRESH_DEFAULT = 10;
const OVERFLOW_THRESH_PREF =
  "extensions.addon-sdk.context-menu.overflowThreshold";

// The label of the overflow sub-xul:menu.
//
// TODO: Localize these.
const OVERFLOW_MENU_LABEL = "Add-ons";
const OVERFLOW_MENU_ACCESSKEY = "A";

// The class of the overflow sub-xul:menu.
const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";

// The class of the overflow submenu's xul:menupopup.
const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";

// Holds private properties for API objects
var internal = ns();

// A little hacky but this is the last process ID that last opened the context
// menu
var lastContextProcessId = null;

var uuidModule = require('./util/uuid');
function uuid() {
  return uuidModule.uuid().toString();
}

function getScheme(spec) {
  try {
    return URL(spec).scheme;
  }
  catch(e) {
    return null;
  }
}

var Context = Class({
  initialize: function() {
    internal(this).id = uuid();
  },

  // Returns the node that made this context current
  adjustPopupNode: function adjustPopupNode(popupNode) {
    return popupNode;
  },

  // Returns whether this context is current for the current node
  isCurrent: function isCurrent(state) {
    return state;
  }
});

// Matches when the context-clicked node doesn't have any of
// NON_PAGE_CONTEXT_ELTS in its ancestors
var PageContext = Class({
  extends: Context,

  serialize: function() {
    return {
      id: internal(this).id,
      type: "PageContext",
      args: []
    }
  }
});
exports.PageContext = PageContext;

// Matches when there is an active selection in the window
var SelectionContext = Class({
  extends: Context,

  serialize: function() {
    return {
      id: internal(this).id,
      type: "SelectionContext",
      args: []
    }
  }
});
exports.SelectionContext = SelectionContext;

// Matches when the context-clicked node or any of its ancestors matches the
// selector given
var SelectorContext = Class({
  extends: Context,

  initialize: function initialize(selector) {
    Context.prototype.initialize.call(this);
    let options = validateOptions({ selector: selector }, {
      selector: {
        is: ["string"],
        msg: "selector must be a string."
      }
    });
    internal(this).selector = options.selector;
  },

  serialize: function() {
    return {
      id: internal(this).id,
      type: "SelectorContext",
      args: [internal(this).selector]
    }
  }
});
exports.SelectorContext = SelectorContext;

// Matches when the page url matches any of the patterns given
var URLContext = Class({
  extends: Context,

  initialize: function initialize(patterns) {
    Context.prototype.initialize.call(this);
    patterns = Array.isArray(patterns) ? patterns : [patterns];

    try {
      internal(this).patterns = patterns.map(p => new MatchPattern(p));
    }
    catch (err) {
      throw new Error("Patterns must be a string, regexp or an array of " +
                      "strings or regexps: " + err);
    }
  },

  isCurrent: function isCurrent(url) {
    return internal(this).patterns.some(p => p.test(url));
  },

  serialize: function() {
    return {
      id: internal(this).id,
      type: "URLContext",
      args: []
    }
  }
});
exports.URLContext = URLContext;

// Matches when the user-supplied predicate returns true
var PredicateContext = Class({
  extends: Context,

  initialize: function initialize(predicate) {
    Context.prototype.initialize.call(this);
    let options = validateOptions({ predicate: predicate }, {
      predicate: {
        is: ["function"],
        msg: "predicate must be a function."
      }
    });
    internal(this).predicate = options.predicate;
  },

  isCurrent: function isCurrent(state) {
    return internal(this).predicate(state);
  },

  serialize: function() {
    return {
      id: internal(this).id,
      type: "PredicateContext",
      args: []
    }
  }
});
exports.PredicateContext = PredicateContext;

function removeItemFromArray(array, item) {
  return array.filter(i => i !== item);
}

// Converts anything that isn't false, null or undefined into a string
function stringOrNull(val) {
  return val ? String(val) : val;
}

// Shared option validation rules for Item, Menu, and Separator
var baseItemRules = {
  parentMenu: {
    is: ["object", "undefined"],
    ok: function (v) {
      if (!v)
        return true;
      return (v instanceof ItemContainer) || (v instanceof Menu);
    },
    msg: "parentMenu must be a Menu or not specified."
  },
  context: {
    is: ["undefined", "object", "array"],
    ok: function (v) {
      if (!v)
        return true;
      let arr = Array.isArray(v) ? v : [v];
      return arr.every(o => o instanceof Context);
    },
    msg: "The 'context' option must be a Context object or an array of " +
         "Context objects."
  },
  onMessage: {
    is: ["function", "undefined"]
  },
  contentScript: loaderContract.rules.contentScript,
  contentScriptFile: loaderContract.rules.contentScriptFile
};

var labelledItemRules =  mix(baseItemRules, {
  label: {
    map: stringOrNull,
    is: ["string"],
    ok: v => !!v,
    msg: "The item must have a non-empty string label."
  },
  accesskey: {
    map: stringOrNull,
    is: ["string", "undefined", "null"],
    ok: (v) => {
      if (!v) {
        return true;
      }
      return typeof v == "string" && v.length === 1;
    },
    msg: "The item must have a single character accesskey, or no accesskey."
  },
  image: {
    map: stringOrNull,
    is: ["string", "undefined", "null"],
    ok: function (url) {
      if (!url)
        return true;
      return isValidURI(url);
    },
    msg: "Image URL validation failed"
  }
});

// Additional validation rules for Item
var itemRules = mix(labelledItemRules, {
  data: {
    map: stringOrNull,
    is: ["string", "undefined", "null"]
  }
});

// Additional validation rules for Menu
var menuRules = mix(labelledItemRules, {
  items: {
    is: ["array", "undefined"],
    ok: function (v) {
      if (!v)
        return true;
      return v.every(function (item) {
        return item instanceof BaseItem;
      });
    },
    msg: "items must be an array, and each element in the array must be an " +
         "Item, Menu, or Separator."
  }
});

// Returns true if any contexts match. If there are no contexts then a
// PageContext is tested instead
function hasMatchingContext(contexts, addonInfo) {
  for (let context of contexts) {
    if (!(internal(context).id in addonInfo.contextStates)) {
      console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules.");
      return false;
    }
    if (!context.isCurrent(addonInfo.contextStates[internal(context).id]))
      return false;
  }

  return true;
}

// Tests whether an item should be visible or not based on its contexts and
// content scripts
function isItemVisible(item, addonInfo, usePageWorker) {
  if (!item.context.length) {
    if (!addonInfo.hasWorker)
      return usePageWorker ? addonInfo.pageContext : true;
  }

  if (!hasMatchingContext(item.context, addonInfo))
    return false;

  let context = addonInfo.workerContext;
  if (typeof(context) === "string" && context != "")
    item.label = context;

  return !!context;
}

// Called when an item is clicked to send out click events to the content
// scripts
function itemActivated(item, clickedNode) {
  let items = [internal(item).id];
  let data = item.data;

  while (item.parentMenu) {
    item = item.parentMenu;
    items.push(internal(item).id);
  }

  let process = processes.getById(lastContextProcessId);
  if (process)
    process.port.emit('sdk/contextmenu/activateitems', items, data);
}

function serializeItem(item) {
  return {
    id: internal(item).id,
    contexts: item.context.map(c => c.serialize()),
    contentScript: item.contentScript,
    contentScriptFile: item.contentScriptFile,
  };
}

// All things that appear in the context menu extend this
var BaseItem = Class({
  initialize: function initialize() {
    internal(this).id = uuid();

    internal(this).contexts = [];
    if ("context" in internal(this).options && internal(this).options.context) {
      let contexts = internal(this).options.context;
      if (Array.isArray(contexts)) {
        for (let context of contexts)
          internal(this).contexts.push(context);
      }
      else {
        internal(this).contexts.push(contexts);
      }
    }

    let parentMenu = internal(this).options.parentMenu;
    if (!parentMenu)
      parentMenu = contentContextMenu;

    parentMenu.addItem(this);

    Object.defineProperty(this, "contentScript", {
      enumerable: true,
      value: internal(this).options.contentScript
    });

    // Resolve URIs here as tests may have overriden self
    let files = internal(this).options.contentScriptFile;
    if (files) {
      if (!Array.isArray(files))
        files = [files];
      files = files.map(self.data.url);
    }
    internal(this).options.contentScriptFile = files;
    Object.defineProperty(this, "contentScriptFile", {
      enumerable: true,
      value: internal(this).options.contentScriptFile
    });

    // Notify all frames of this new item
    sendItems([serializeItem(this)]);
  },

  destroy: function destroy() {
    if (internal(this).destroyed)
      return;

    // Tell all existing frames that this item has been destroyed
    processes.port.emit("sdk/contextmenu/destroyitems", [internal(this).id]);

    if (this.parentMenu)
      this.parentMenu.removeItem(this);

    internal(this).destroyed = true;
  },

  get context() {
    let contexts = internal(this).contexts.slice(0);
    contexts.add = (context) => {
      internal(this).contexts.push(context);
      // Notify all frames that this item has changed
      sendItems([serializeItem(this)]);
    };
    contexts.remove = (context) => {
      internal(this).contexts = internal(this).contexts.filter(c => {
        return c != context;
      });
      // Notify all frames that this item has changed
      sendItems([serializeItem(this)]);
    };
    return contexts;
  },

  set context(val) {
    internal(this).contexts = val.slice(0);
    // Notify all frames that this item has changed
    sendItems([serializeItem(this)]);
  },

  get parentMenu() {
    return internal(this).parentMenu;
  },
});

function workerMessageReceived(process, id, args) {
  if (internal(this).id != id)
    return;

  emit(this, ...JSON.parse(args));
}

// All things that have a label on the context menu extend this
var LabelledItem = Class({
  extends: BaseItem,
  implements: [ EventTarget ],

  initialize: function initialize(options) {
    BaseItem.prototype.initialize.call(this);
    EventTarget.prototype.initialize.call(this, options);

    internal(this).messageListener = workerMessageReceived.bind(this);
    processes.port.on('sdk/worker/event', internal(this).messageListener);
  },

  destroy: function destroy() {
    if (internal(this).destroyed)
      return;

    processes.port.off('sdk/worker/event', internal(this).messageListener);

    BaseItem.prototype.destroy.call(this);
  },

  get label() {
    return internal(this).options.label;
  },

  set label(val) {
    internal(this).options.label = val;

    MenuManager.updateItem(this);
  },

  get accesskey() {
    return internal(this).options.accesskey;
  },

  set accesskey(val) {
    internal(this).options.accesskey = val;

    MenuManager.updateItem(this);
  },

  get image() {
    return internal(this).options.image;
  },

  set image(val) {
    internal(this).options.image = val;

    MenuManager.updateItem(this);
  },

  get data() {
    return internal(this).options.data;
  },

  set data(val) {
    internal(this).options.data = val;
  }
});

var Item = Class({
  extends: LabelledItem,

  initialize: function initialize(options) {
    internal(this).options = validateOptions(options, itemRules);

    LabelledItem.prototype.initialize.call(this, options);
  },

  toString: function toString() {
    return "[object Item \"" + this.label + "\"]";
  },

  get data() {
    return internal(this).options.data;
  },

  set data(val) {
    internal(this).options.data = val;

    MenuManager.updateItem(this);
  },
});
exports.Item = Item;

var ItemContainer = Class({
  initialize: function initialize() {
    internal(this).children = [];
  },

  destroy: function destroy() {
    // Destroys the entire hierarchy
    for (let item of internal(this).children)
      item.destroy();
  },

  addItem: function addItem(item) {
    let oldParent = item.parentMenu;

    // Don't just call removeItem here as that would remove the corresponding
    // UI element which is more costly than just moving it to the right place
    if (oldParent)
      internal(oldParent).children = removeItemFromArray(internal(oldParent).children, item);

    let after = null;
    let children = internal(this).children;
    if (children.length > 0)
      after = children[children.length - 1];

    children.push(item);
    internal(item).parentMenu = this;

    // If there was an old parent then we just have to move the item, otherwise
    // it needs to be created
    if (oldParent)
      MenuManager.moveItem(item, after);
    else
      MenuManager.createItem(item, after);
  },

  removeItem: function removeItem(item) {
    // If the item isn't a child of this menu then ignore this call
    if (item.parentMenu !== this)
      return;

    MenuManager.removeItem(item);

    internal(this).children = removeItemFromArray(internal(this).children, item);
    internal(item).parentMenu = null;
  },

  get items() {
    return internal(this).children.slice(0);
  },

  set items(val) {
    // Validate the arguments before making any changes
    if (!Array.isArray(val))
      throw new Error(menuOptionRules.items.msg);

    for (let item of val) {
      if (!(item instanceof BaseItem))
        throw new Error(menuOptionRules.items.msg);
    }

    // Remove the old items and add the new ones
    for (let item of internal(this).children)
      this.removeItem(item);

    for (let item of val)
      this.addItem(item);
  },
});

var Menu = Class({
  extends: LabelledItem,
  implements: [ItemContainer],

  initialize: function initialize(options) {
    internal(this).options = validateOptions(options, menuRules);

    LabelledItem.prototype.initialize.call(this, options);
    ItemContainer.prototype.initialize.call(this);

    if (internal(this).options.items) {
      for (let item of internal(this).options.items)
        this.addItem(item);
    }
  },

  destroy: function destroy() {
    ItemContainer.prototype.destroy.call(this);
    LabelledItem.prototype.destroy.call(this);
  },

  toString: function toString() {
    return "[object Menu \"" + this.label + "\"]";
  },
});
exports.Menu = Menu;

var Separator = Class({
  extends: BaseItem,

  initialize: function initialize(options) {
    internal(this).options = validateOptions(options, baseItemRules);

    BaseItem.prototype.initialize.call(this);
  },

  toString: function toString() {
    return "[object Separator]";
  }
});
exports.Separator = Separator;

// Holds items for the content area context menu
var contentContextMenu = ItemContainer();
exports.contentContextMenu = contentContextMenu;

function getContainerItems(container) {
  let items = [];
  for (let item of internal(container).children) {
    items.push(serializeItem(item));
    if (item instanceof Menu)
      items = items.concat(getContainerItems(item));
  }
  return items;
}

// Notify all frames of these new or changed items
function sendItems(items) {
  processes.port.emit("sdk/contextmenu/createitems", items);
}

// Called when a new process is created and needs to get the current list of items
function remoteItemRequest(process) {
  let items = getContainerItems(contentContextMenu);
  if (items.length == 0)
    return;

  process.port.emit("sdk/contextmenu/createitems", items);
}
processes.forEvery(remoteItemRequest);

when(function() {
  contentContextMenu.destroy();
});

// App specific UI code lives here, it should handle populating the context
// menu and passing clicks etc. through to the items.

function countVisibleItems(nodes) {
  return Array.reduce(nodes, function(sum, node) {
    return node.hidden ? sum : sum + 1;
  }, 0);
}

var MenuWrapper = Class({
  initialize: function initialize(winWrapper, items, contextMenu) {
    this.winWrapper = winWrapper;
    this.window = winWrapper.window;
    this.items = items;
    this.contextMenu = contextMenu;
    this.populated = false;
    this.menuMap = new Map();

    // updateItemVisibilities will run first, updateOverflowState will run after
    // all other instances of this module have run updateItemVisibilities
    this._updateItemVisibilities = this.updateItemVisibilities.bind(this);
    this.contextMenu.addEventListener("popupshowing", this._updateItemVisibilities, true);
    this._updateOverflowState = this.updateOverflowState.bind(this);
    this.contextMenu.addEventListener("popupshowing", this._updateOverflowState);
  },

  destroy: function destroy() {
    this.contextMenu.removeEventListener("popupshowing", this._updateOverflowState);
    this.contextMenu.removeEventListener("popupshowing", this._updateItemVisibilities, true);

    if (!this.populated)
      return;

    // If we're getting unloaded at runtime then we must remove all the
    // generated XUL nodes
    let oldParent = null;
    for (let item of internal(this.items).children) {
      let xulNode = this.getXULNodeForItem(item);
      oldParent = xulNode.parentNode;
      oldParent.removeChild(xulNode);
    }

    if (oldParent)
      this.onXULRemoved(oldParent);
  },

  get separator() {
    return this.contextMenu.querySelector("." + SEPARATOR_CLASS);
  },

  get overflowMenu() {
    return this.contextMenu.querySelector("." + OVERFLOW_MENU_CLASS);
  },

  get overflowPopup() {
    return this.contextMenu.querySelector("." + OVERFLOW_POPUP_CLASS);
  },

  get topLevelItems() {
    return this.contextMenu.querySelectorAll("." + TOPLEVEL_ITEM_CLASS);
  },

  get overflowItems() {
    return this.contextMenu.querySelectorAll("." + OVERFLOW_ITEM_CLASS);
  },

  getXULNodeForItem: function getXULNodeForItem(item) {
    return this.menuMap.get(item);
  },

  // Recurses through the item hierarchy creating XUL nodes for everything
  populate: function populate(menu) {
    for (let i = 0; i < internal(menu).children.length; i++) {
      let item = internal(menu).children[i];
      let after = i === 0 ? null : internal(menu).children[i - 1];
      this.createItem(item, after);

      if (item instanceof Menu)
        this.populate(item);
    }
  },

  // Recurses through the menu setting the visibility of items. Returns true
  // if any of the items in this menu were visible
  setVisibility: function setVisibility(menu, addonInfo, usePageWorker) {
    let anyVisible = false;

    for (let item of internal(menu).children) {
      let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker);

      // Recurse through Menus, if none of the sub-items were visible then the
      // menu is hidden too.
      if (visible && (item instanceof Menu))
        visible = this.setVisibility(item, addonInfo, false);

      let xulNode = this.getXULNodeForItem(item);
      xulNode.hidden = !visible;

      anyVisible = anyVisible || visible;
    }

    return anyVisible;
  },

  // Works out where to insert a XUL node for an item in a browser window
  insertIntoXUL: function insertIntoXUL(item, node, after) {
    let menupopup = null;
    let before = null;

    let menu = item.parentMenu;
    if (menu === this.items) {
      // Insert into the overflow popup if it exists, otherwise the normal
      // context menu
      menupopup = this.overflowPopup;
      if (!menupopup)
        menupopup = this.contextMenu;
    }
    else {
      let xulNode = this.getXULNodeForItem(menu);
      menupopup = xulNode.firstChild;
    }

    if (after) {
      let afterNode = this.getXULNodeForItem(after);
      before = afterNode.nextSibling;
    }
    else if (menupopup === this.contextMenu) {
      let topLevel = this.topLevelItems;
      if (topLevel.length > 0)
        before = topLevel[topLevel.length - 1].nextSibling;
      else
        before = this.separator.nextSibling;
    }

    menupopup.insertBefore(node, before);
  },

  // Sets the right class for XUL nodes
  updateXULClass: function updateXULClass(xulNode) {
    if (xulNode.parentNode == this.contextMenu)
      xulNode.classList.add(TOPLEVEL_ITEM_CLASS);
    else
      xulNode.classList.remove(TOPLEVEL_ITEM_CLASS);

    if (xulNode.parentNode == this.overflowPopup)
      xulNode.classList.add(OVERFLOW_ITEM_CLASS);
    else
      xulNode.classList.remove(OVERFLOW_ITEM_CLASS);
  },

  // Creates a XUL node for an item
  createItem: function createItem(item, after) {
    if (!this.populated)
      return;

    // Create the separator if it doesn't already exist
    if (!this.separator) {
      let separator = this.window.document.createElement("menuseparator");
      separator.setAttribute("class", SEPARATOR_CLASS);

      // Insert before the separator created by the old context-menu if it
      // exists to avoid bug 832401
      let oldSeparator = this.window.document.getElementById("jetpack-context-menu-separator");
      if (oldSeparator && oldSeparator.parentNode != this.contextMenu)
        oldSeparator = null;
      this.contextMenu.insertBefore(separator, oldSeparator);
    }

    let type = "menuitem";
    if (item instanceof Menu)
      type = "menu";
    else if (item instanceof Separator)
      type = "menuseparator";

    let xulNode = this.window.document.createElement(type);
    xulNode.setAttribute("class", ITEM_CLASS);
    if (item instanceof LabelledItem) {
      xulNode.setAttribute("label", item.label);
      if (item.accesskey)
        xulNode.setAttribute("accesskey", item.accesskey);
      if (item.image) {
        xulNode.setAttribute("image", item.image);
        if (item instanceof Menu)
          xulNode.classList.add("menu-iconic");
        else
          xulNode.classList.add("menuitem-iconic");
      }
      if (item.data)
        xulNode.setAttribute("value", item.data);

      let self = this;
      xulNode.addEventListener("command", function(event) {
        // Only care about clicks directly on this item
        if (event.target !== xulNode)
          return;

        itemActivated(item, xulNode);
      });
    }

    this.insertIntoXUL(item, xulNode, after);
    this.updateXULClass(xulNode);
    xulNode.data = item.data;

    if (item instanceof Menu) {
      let menupopup = this.window.document.createElement("menupopup");
      xulNode.appendChild(menupopup);
    }

    this.menuMap.set(item, xulNode);
  },

  // Updates the XUL node for an item in this window
  updateItem: function updateItem(item) {
    if (!this.populated)
      return;

    let xulNode = this.getXULNodeForItem(item);

    // TODO figure out why this requires setAttribute
    xulNode.setAttribute("label", item.label);
    xulNode.setAttribute("accesskey", item.accesskey || "");

    if (item.image) {
      xulNode.setAttribute("image", item.image);
      if (item instanceof Menu)
        xulNode.classList.add("menu-iconic");
      else
        xulNode.classList.add("menuitem-iconic");
    }
    else {
      xulNode.removeAttribute("image");
      xulNode.classList.remove("menu-iconic");
      xulNode.classList.remove("menuitem-iconic");
    }

    if (item.data)
      xulNode.setAttribute("value", item.data);
    else
      xulNode.removeAttribute("value");
  },

  // Moves the XUL node for an item in this window to its new place in the
  // hierarchy
  moveItem: function moveItem(item, after) {
    if (!this.populated)
      return;

    let xulNode = this.getXULNodeForItem(item);
    let oldParent = xulNode.parentNode;

    this.insertIntoXUL(item, xulNode, after);
    this.updateXULClass(xulNode);
    this.onXULRemoved(oldParent);
  },

  // Removes the XUL nodes for an item in every window we've ever populated.
  removeItem: function removeItem(item) {
    if (!this.populated)
      return;

    let xulItem = this.getXULNodeForItem(item);

    let oldParent = xulItem.parentNode;

    oldParent.removeChild(xulItem);
    this.menuMap.delete(item);

    this.onXULRemoved(oldParent);
  },

  // Called when any XUL nodes have been removed from a menupopup. This handles
  // making sure the separator and overflow are correct
  onXULRemoved: function onXULRemoved(parent) {
    if (parent == this.contextMenu) {
      let toplevel = this.topLevelItems;

      // If there are no more items then remove the separator
      if (toplevel.length == 0) {
        let separator = this.separator;
        if (separator)
          separator.remove();
      }
    }
    else if (parent == this.overflowPopup) {
      // If there are no more items then remove the overflow menu and separator
      if (parent.childNodes.length == 0) {
        let separator = this.separator;
        separator.remove();
        this.contextMenu.removeChild(parent.parentNode);
      }
    }
  },

  // Recurses through all the items owned by this module and sets their hidden
  // state
  updateItemVisibilities: function updateItemVisibilities(event) {
    try {
      if (event.type != "popupshowing")
        return;
      if (event.target != this.contextMenu)
        return;

      if (internal(this.items).children.length == 0)
        return;

      if (!this.populated) {
        this.populated = true;
        this.populate(this.items);
      }

      let mainWindow = event.target.ownerGlobal;
      this.contextMenuContentData = mainWindow.gContextMenuContentData
      if (!(self.id in this.contextMenuContentData.addonInfo)) {
        console.warn("No context menu state data was provided.");
        return;
      }
      let addonInfo = this.contextMenuContentData.addonInfo[self.id];
      lastContextProcessId = addonInfo.processID;
      this.setVisibility(this.items, addonInfo.items, true);
    }
    catch (e) {
      console.exception(e);
    }
  },

  // Counts the number of visible items across all modules and makes sure they
  // are in the right place between the top level context menu and the overflow
  // menu
  updateOverflowState: function updateOverflowState(event) {
    try {
      if (event.type != "popupshowing")
        return;
      if (event.target != this.contextMenu)
        return;

      // The main items will be in either the top level context menu or the
      // overflow menu at this point. Count the visible ones and if they are in
      // the wrong place move them
      let toplevel = this.topLevelItems;
      let overflow = this.overflowItems;
      let visibleCount = countVisibleItems(toplevel) +
                         countVisibleItems(overflow);

      if (visibleCount == 0) {
        let separator = this.separator;
        if (separator)
          separator.hidden = true;
        let overflowMenu = this.overflowMenu;
        if (overflowMenu)
          overflowMenu.hidden = true;
      }
      else if (visibleCount > MenuManager.overflowThreshold) {
        this.separator.hidden = false;
        let overflowPopup = this.overflowPopup;
        if (overflowPopup)
          overflowPopup.parentNode.hidden = false;

        if (toplevel.length > 0) {
          // The overflow menu shouldn't exist here but let's play it safe
          if (!overflowPopup) {
            let overflowMenu = this.window.document.createElement("menu");
            overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS);
            overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL);
            overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY);
            this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling);

            overflowPopup = this.window.document.createElement("menupopup");
            overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS);
            overflowMenu.appendChild(overflowPopup);
          }

          for (let xulNode of toplevel) {
            overflowPopup.appendChild(xulNode);
            this.updateXULClass(xulNode);
          }
        }
      }
      else {
        this.separator.hidden = false;

        if (overflow.length > 0) {
          // Move all the overflow nodes out of the overflow menu and position
          // them immediately before it
          for (let xulNode of overflow) {
            this.contextMenu.insertBefore(xulNode, xulNode.parentNode.parentNode);
            this.updateXULClass(xulNode);
          }
          this.contextMenu.removeChild(this.overflowMenu);
        }
      }
    }
    catch (e) {
      console.exception(e);
    }
  }
});

// This wraps every window that we've seen
var WindowWrapper = Class({
  initialize: function initialize(window) {
    this.window = window;
    this.menus = [
      new MenuWrapper(this, contentContextMenu, window.document.getElementById("contentAreaContextMenu")),
    ];
  },

  destroy: function destroy() {
    for (let menuWrapper of this.menus)
      menuWrapper.destroy();
  },

  getMenuWrapperForItem: function getMenuWrapperForItem(item) {
    let root = item.parentMenu;
    while (root.parentMenu)
      root = root.parentMenu;

    for (let wrapper of this.menus) {
      if (wrapper.items === root)
        return wrapper;
    }

    return null;
  }
});

var MenuManager = {
  windowMap: new Map(),

  get overflowThreshold() {
    let prefs = require("./preferences/service");
    return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
  },

  // When a new window is added start watching it for context menu shows
  onTrack: function onTrack(window) {
    if (!isBrowser(window))
      return;

    // Generally shouldn't happen, but just in case
    if (this.windowMap.has(window)) {
      console.warn("Already seen this window");
      return;
    }

    let winWrapper = WindowWrapper(window);
    this.windowMap.set(window, winWrapper);
  },

  onUntrack: function onUntrack(window) {
    if (!isBrowser(window))
      return;

    let winWrapper = this.windowMap.get(window);
    // This shouldn't happen but protect against it anyway
    if (!winWrapper)
      return;
    winWrapper.destroy();

    this.windowMap.delete(window);
  },

  // Creates a XUL node for an item in every window we've already populated
  createItem: function createItem(item, after) {
    for (let [window, winWrapper] of this.windowMap) {
      let menuWrapper = winWrapper.getMenuWrapperForItem(item);
      if (menuWrapper)
        menuWrapper.createItem(item, after);
    }
  },

  // Updates the XUL node for an item in every window we've already populated
  updateItem: function updateItem(item) {
    for (let [window, winWrapper] of this.windowMap) {
      let menuWrapper = winWrapper.getMenuWrapperForItem(item);
      if (menuWrapper)
        menuWrapper.updateItem(item);
    }
  },

  // Moves the XUL node for an item in every window we've ever populated to its
  // new place in the hierarchy
  moveItem: function moveItem(item, after) {
    for (let [window, winWrapper] of this.windowMap) {
      let menuWrapper = winWrapper.getMenuWrapperForItem(item);
      if (menuWrapper)
        menuWrapper.moveItem(item, after);
    }
  },

  // Removes the XUL nodes for an item in every window we've ever populated.
  removeItem: function removeItem(item) {
    for (let [window, winWrapper] of this.windowMap) {
      let menuWrapper = winWrapper.getMenuWrapperForItem(item);
      if (menuWrapper)
        menuWrapper.removeItem(item);
    }
  }
};

WindowTracker(MenuManager);
PK
!<+pEE&modules/commonjs/sdk/context-menu@2.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const shared = require("toolkit/require");
const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
const { Class } = require("sdk/core/heritage")

const makeDisposable = Type => Class({
  extends: Type,
  implements: [Disposable],
  initialize: Type.prototype.initialize,
  setup(...params) {
    Type.prototype.setup.call(this, ...params);
    setupDisposable(this);
  },
  dispose(...params) {
    disposeDisposable(this);
    Type.prototype.dispose.call(this, ...params);
  }
});

exports.Separator = Separator;
exports.Contexts = Contexts;
exports.Readers = Readers;

// Subclass Item & Menu shared classes so their items
// will be unloaded when add-on is unloaded.
exports.Item = makeDisposable(Item);
exports.Menu = makeDisposable(Menu);
PK
!<nA'modules/commonjs/sdk/core/disposable.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Class } = require("./heritage");
const { Observer, subscribe, unsubscribe, observe } = require("./observer");

lazyRequire(this, "./reference", "isWeak");
lazyRequireModule(this, "../lang/weak-set", "SDKWeakSet");

const method = require("../../method/core");

const unloadSubject = require('@loader/unload');
const addonUnloadTopic = "sdk:loader:destroy";

const uninstall = method("disposable/uninstall");
exports.uninstall = uninstall;

const shutdown = method("disposable/shutdown");
exports.shutdown = shutdown;

const disable = method("disposable/disable");
exports.disable = disable;

const upgrade = method("disposable/upgrade");
exports.upgrade = upgrade;

const downgrade = method("disposable/downgrade");
exports.downgrade = downgrade;

const unload = method("disposable/unload");
exports.unload = unload;

const dispose = method("disposable/dispose");
exports.dispose = dispose;
dispose.define(Object, object => object.dispose());

const setup = method("disposable/setup");
exports.setup = setup;
setup.define(Object, (object, ...args) => object.setup(...args));

// DisposablesUnloadObserver is the class which subscribe the
// Observer Service to be notified when the add-on loader is
// unloading to be able to dispose all the existent disposables.
const DisposablesUnloadObserver = Class({
  implements: [Observer],
  initialize: function(...args) {
    // Set of the non-weak disposables registered to be disposed.
    this.disposables = new Set();
    // Target of the weak disposables registered to be disposed
    // (and tracked on this target using the SDK weak-set module).
    this.weakDisposables = {};
  },
  subscribe(disposable) {
    if (isWeak(disposable)) {
      SDKWeakSet.add(this.weakDisposables, disposable);
    } else {
      this.disposables.add(disposable);
    }
  },
  unsubscribe(disposable) {
    if (isWeak(disposable)) {
      SDKWeakSet.remove(this.weakDisposables, disposable);
    } else {
      this.disposables.delete(disposable);
    }
  },
  tryUnloadDisposable(disposable) {
    try {
      if (disposable) {
        unload(disposable);
      }
    } catch(e) {
      console.error("Error unloading a",
                    isWeak(disposable) ? "weak disposable" : "disposable",
                    disposable, e);
    }
  },
  unloadAll() {
    // Remove all the subscribed disposables.
    for (let disposable of this.disposables) {
      this.tryUnloadDisposable(disposable);
    }

    this.disposables.clear();

    // Remove all the subscribed weak disposables.
    for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) {
      this.tryUnloadDisposable(disposable);
    }

    SDKWeakSet.clear(this.weakDisposables);
  }
});
const disposablesUnloadObserver = new DisposablesUnloadObserver();

// The DisposablesUnloadObserver instance is the only object which subscribes
// the Observer Service directly, it observes add-on unload notifications in
// order to trigger `unload` on all its subscribed disposables.
observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => {
  const isUnloadTopic = topic === addonUnloadTopic;
  const isUnloadSubject = subject.wrappedJSObject === unloadSubject;
  if (isUnloadTopic && isUnloadSubject) {
    unsubscribe(disposablesUnloadObserver, addonUnloadTopic);
    disposablesUnloadObserver.unloadAll();
  }
});

subscribe(disposablesUnloadObserver, addonUnloadTopic, false);

// Set's up disposable instance.
const setupDisposable = disposable => {
  disposablesUnloadObserver.subscribe(disposable);
};
exports.setupDisposable = setupDisposable;

// Tears down disposable instance.
const disposeDisposable = disposable => {
  disposablesUnloadObserver.unsubscribe(disposable);
};
exports.disposeDisposable = disposeDisposable;

// Base type that takes care of disposing it's instances on add-on unload.
// Also makes sure to remove unload listener if it's already being disposed.
const Disposable = Class({
  initialize: function(...args) {
    // First setup instance before initializing it's disposal. If instance
    // fails to initialize then there is no instance to be disposed at the
    // unload.
    setup(this, ...args);
    setupDisposable(this);
  },
  destroy: function(reason) {
    // Destroying disposable removes unload handler so that attempt to dispose
    // won't be made at unload & delegates to dispose.
    disposeDisposable(this);
    unload(this, reason);
  },
  setup: function() {
    // Implement your initialize logic here.
  },
  dispose: function() {
    // Implement your cleanup logic here.
  }
});
exports.Disposable = Disposable;

const unloaders = {
  destroy: dispose,
  uninstall: uninstall,
  shutdown: shutdown,
  disable: disable,
  upgrade: upgrade,
  downgrade: downgrade
};

const unloaded = new WeakMap();
unload.define(Disposable, (disposable, reason) => {
  if (!unloaded.get(disposable)) {
    unloaded.set(disposable, true);
    // Pick an unload handler associated with an unload
    // reason (falling back to destroy if not found) and
    // delegate unloading to it.
    const unload = unloaders[reason] || unloaders.destroy;
    unload(disposable);
  }
});

// If add-on is disabled manually, it's being upgraded, downgraded
// or uninstalled `dispose` is invoked to undo any changes that
// has being done by it in this session.
disable.define(Disposable, dispose);
downgrade.define(Disposable, dispose);
upgrade.define(Disposable, dispose);
uninstall.define(Disposable, dispose);

// If application is shut down no dispose is invoked as undo-ing
// changes made by instance is likely to just waste of resources &
// increase shutdown time. Although specefic components may choose
// to implement shutdown handler that does something better.
shutdown.define(Disposable, disposable => {});
PK
!<Cd}%modules/commonjs/sdk/core/heritage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

var getPrototypeOf = Object.getPrototypeOf;
function* getNames(x) {
  yield* Object.getOwnPropertyNames(x);
  yield* Object.getOwnPropertySymbols(x);
}
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var freeze = Object.freeze;

// This shortcut makes sure that we do perform desired operations, even if
// associated methods have being overridden on the used object.
var hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);

// Utility function to get own properties descriptor map.
function getOwnPropertyDescriptors(...objects) {
  let descriptors = {};
  for (let object of objects)
    for (let name of getNames(object))
      descriptors[name] = getOwnPropertyDescriptor(object, name);
  return descriptors;
}

function isDataProperty(property) {
  var type = typeof(property.value);
  return "value" in property &&
         type !== "function" &&
         (type !== "object" || property.value === null);
}

function getDataProperties(object) {
  var properties = getOwnPropertyDescriptors(object);
  let result = {};
  for (let name of getNames(properties)) {
    var property = properties[name];
    if (isDataProperty(property)) {
      result[name] = {
        value: property.value,
        writable: true,
        configurable: true,
        enumerable: false
      };
    }
  }
  return result;
}

/**
 * Takes `source` object as an argument and returns identical object
 * with the difference that all own properties will be non-enumerable
 */
function obscure(source, prototype = getPrototypeOf(source)) {
  let descriptors = {};
  for (let name of getNames(source)) {
    let property = getOwnPropertyDescriptor(source, name);
    property.enumerable = false;
    descriptors[name] = property;
  }
  return Object.create(prototype, descriptors);
}
exports.obscure = obscure;

/**
 * Takes arbitrary number of source objects and returns fresh one, that
 * inherits from the same prototype as a first argument and implements all
 * own properties of all argument objects. If two or more argument objects
 * have own properties with the same name, the property is overridden, with
 * precedence from right to left, implying, that properties of the object on
 * the left are overridden by a same named property of the object on the right.
 */
var mix = function(...sources) {
  return Object.create(getPrototypeOf(sources[0]),
                       getOwnPropertyDescriptors(...sources));
};
exports.mix = mix;

/**
 * Returns a frozen object with that inherits from the given `prototype` and
 * implements all own properties of the given `properties` object.
 */
function extend(prototype, properties) {
  return Object.create(prototype,
                       getOwnPropertyDescriptors(properties));
}
exports.extend = extend;

function prototypeOf(input) {
  return typeof(input) === 'function' ? input.prototype : input;
}

/**
 * Returns a constructor function with a proper `prototype` setup. Returned
 * constructor's `prototype` inherits from a given `options.extends` or
 * `Class.prototype` if omitted and implements all the properties of the
 * given `option`. If `options.implemens` array is passed, it's elements
 * will be mixed into prototype as well. Also, `options.extends` can be
 * a function or a prototype. If function than it's prototype is used as
 * an ancestor of the prototype, if it's an object that it's used directly.
 * Also `options.implements` may contain functions or objects, in case of
 * functions their prototypes are used for mixing.
 */
function Class(options) {
  // Create descriptor with normalized `options.extends` and
  // `options.implements`.
  var descriptor = {
    // Normalize extends property of `options.extends` to a prototype object
    // in case it's constructor. If property is missing that fallback to
    // `Type.prototype`.
    extends: hasOwnProperty(options, 'extends') ?
             prototypeOf(options.extends) : Class.prototype,

    // Normalize `options.implements` to make sure that it's array of
    // prototype objects instead of constructor functions.
    implements: freeze(hasOwnProperty(options, 'implements') ?
                       options.implements.map(prototypeOf) : []),
  };

  // Create array of property descriptors who's properties will be defined
  // on the resulting prototype.
  var descriptors = [].concat(descriptor.implements, options, descriptor,
                              { constructor });

  // Note: we use reflection `apply` in the constructor instead of method
  // call since later may be overridden.
  function constructor() {
    var instance = Object.create(prototype, attributes);
    if (initialize)
      Reflect.apply(initialize, instance, arguments);
    return instance;
  }
  // Create `prototype` that inherits from given ancestor passed as
  // `options.extends`, falling back to `Type.prototype`, implementing all
  // properties of given `options.implements` and `options` itself.
  var prototype = Object.create(descriptor.extends,
                                getOwnPropertyDescriptors(...descriptors));
  var initialize = prototype.initialize;

  // Combine ancestor attributes with prototype's attributes so that
  // ancestors attributes also become initializeable.
  var attributes = mix(descriptor.extends.constructor.attributes || {},
                       getDataProperties(prototype));

  constructor.attributes = attributes;
  Object.defineProperty(constructor, 'prototype', {
    configurable: false,
    writable: false,
    value: prototype
  });
  return constructor;
}
Class.prototype = obscure({
  constructor: function constructor() {
    this.initialize.apply(this, arguments);
    return this;
  },
  initialize: function initialize() {
    // Do your initialization logic here
  },
  // Copy useful properties from `Object.prototype`.
  toString: Object.prototype.toString,
  toLocaleString: Object.prototype.toLocaleString,
  toSource: Object.prototype.toSource,
  valueOf: Object.prototype.valueOf,
  isPrototypeOf: Object.prototype.isPrototypeOf
}, null);
exports.Class = freeze(Class);
PK
!<&modules/commonjs/sdk/core/namespace.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const create = Object.create;
const prototypeOf = Object.getPrototypeOf;

/**
 * Returns a new namespace, function that may can be used to access an
 * namespaced object of the argument argument. Namespaced object are associated
 * with owner objects via weak references. Namespaced objects inherit from the
 * owners ancestor namespaced object. If owner's ancestor is `null` then
 * namespaced object inherits from given `prototype`. Namespaces can be used
 * to define internal APIs that can be shared via enclosing `namespace`
 * function.
 * @examples
 *    const internals = ns();
 *    internals(object).secret = secret;
 */
function ns() {
  const map = new WeakMap();
  return function namespace(target) {
    if (!target)        // If `target` is not an object return `target` itself.
      return target;
    // If target has no namespaced object yet, create one that inherits from
    // the target prototype's namespaced object.
    if (!map.has(target))
      map.set(target, create(namespace(prototypeOf(target) || null)));

    return map.get(target);
  };
};

// `Namespace` is a e4x function in the scope, so we export the function also as
// `ns` as alias to avoid clashing.
exports.ns = ns;
exports.Namespace = ns;
PK
!<J{%modules/commonjs/sdk/core/observer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};


const { Cc, Ci, Cr, Cu } = require("chrome");
const { Class } = require("./heritage");
lazyRequire(this, "./reference", "isWeak");
const method = require("../../method/core");

const observerService = Cc['@mozilla.org/observer-service;1'].
                          getService(Ci.nsIObserverService);

const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");

// This is a method that will be invoked when notification observer
// subscribed to occurs.
const observe = method("observer/observe");
exports.observe = observe;

// Method to subscribe to the observer notification.
const subscribe = method("observe/subscribe");
exports.subscribe = subscribe;


// Method to unsubscribe from the observer notifications.
const unsubscribe = method("observer/unsubscribe");
exports.unsubscribe = unsubscribe;


// This is wrapper class that takes a `delegate` and produces
// instance of `nsIObserver` which will delegate to a given
// object when observer notification occurs.
const ObserverDelegee = Class({
  initialize: function(delegate) {
    this.delegate = delegate;
  },
  QueryInterface: function(iid) {
    if (!iid.equals(Ci.nsIObserver) &&
        !iid.equals(Ci.nsISupportsWeakReference) &&
        !iid.equals(Ci.nsISupports))
      throw Cr.NS_ERROR_NO_INTERFACE;

    return this;
  },
  observe: function(subject, topic, data) {
    observe(this.delegate, subject, topic, data);
  }
});


// Class that can be either mixed in or inherited from in
// order to subscribe / unsubscribe for observer notifications.
const Observer = Class({});
exports.Observer = Observer;

// Weak maps that associates instance of `ObserverDelegee` with
// an actual observer. It ensures that `ObserverDelegee` instance
// won't be GC-ed until given `observer` is.
const subscribers = new WeakMap();

// Implementation of `subscribe` for `Observer` type just registers
// observer for an observer service. If `isWeak(observer)` is `true`
// observer service won't hold strong reference to a given `observer`.
subscribe.define(Observer, (observer, topic) => {
  if (!subscribers.has(observer)) {
    const delegee = new ObserverDelegee(observer);
    subscribers.set(observer, delegee);
    addObserver(delegee, topic, isWeak(observer));
  }
});

// Unsubscribes `observer` from observer notifications for the
// given `topic`.
unsubscribe.define(Observer, (observer, topic) => {
  const delegee = subscribers.get(observer);
  if (delegee) {
    subscribers.delete(observer);
    removeObserver(delegee, topic);
  }
});
PK
!<H

$modules/commonjs/sdk/core/promise.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

/*
 * Uses `Promise.jsm` as a core implementation, with additional sugar
 * from previous implementation, with inspiration from `Q` and `when`
 *
 * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
 * https://github.com/cujojs/when
 * https://github.com/kriskowal/q
 */
const PROMISE_URI = 'resource://gre/modules/Promise.jsm';

getEnvironment.call(this, function ({ require, exports, module, Cu }) {

const Promise = Cu.import(PROMISE_URI, {}).Promise;
const { Debugging, defer, resolve, all, reject, race } = Promise;

module.metadata = {
  'stability': 'unstable'
};

var promised = (function() {
  // Note: Define shortcuts and utility functions here in order to avoid
  // slower property accesses and unnecessary closure creations on each
  // call of this popular function.

  var call = Function.call;
  var concat = Array.prototype.concat;

  // Utility function that does following:
  // execute([ f, self, args...]) => f.apply(self, args)
  function execute (args) {
    return call.apply(call, args);
  }

  // Utility function that takes promise of `a` array and maybe promise `b`
  // as arguments and returns promise for `a.concat(b)`.
  function promisedConcat(promises, unknown) {
    return promises.then(function (values) {
      return resolve(unknown)
        .then(value => values.concat([value]));
    });
  }

  return function promised(f, prototype) {
    /**
    Returns a wrapped `f`, which when called returns a promise that resolves to
    `f(...)` passing all the given arguments to it, which by the way may be
    promises. Optionally second `prototype` argument may be provided to be used
    a prototype for a returned promise.

    ## Example

    var promise = promised(Array)(1, promise(2), promise(3))
    promise.then(console.log) // => [ 1, 2, 3 ]
    **/

    return function promised(...args) {
      // create array of [ f, this, args... ]
      return [f, this, ...args].
        // reduce it via `promisedConcat` to get promised array of fulfillments
        reduce(promisedConcat, resolve([], prototype)).
        // finally map that to promise of `f.apply(this, args...)`
        then(execute);
    };
  };
})();

exports.promised = promised;
exports.all = all;
exports.defer = defer;
exports.resolve = resolve;
exports.reject = reject;
exports.race = race;
exports.Promise = Promise;
exports.Debugging = Debugging;
});

function getEnvironment (callback) {
  let Cu, _exports, _module, _require;

  // CommonJS / SDK
  if (typeof(require) === 'function') {
    Cu = require('chrome').Cu;
    _exports = exports;
    _module = module;
    _require = require;
  }
  // JSM
  else if (String(this).indexOf('BackstagePass') >= 0) {
    Cu = this['Components'].utils;
    _exports = this.Promise = {};
    _module = { uri: __URI__, id: 'promise/core' };
    _require = uri => {
      let imports = {};
      Cu.import(uri, imports);
      return imports;
    };
    this.EXPORTED_SYMBOLS = ['Promise'];
  // mozIJSSubScriptLoader.loadSubscript
  } else if (~String(this).indexOf('Sandbox')) {
    Cu = this['Components'].utils;
    _exports = this;
    _module = { id: 'promise/core' };
    _require = uri => {};
  }

  callback({
    Cu: Cu,
    exports: _exports,
    module: _module,
    require: _require
  });
}

PK
!<	&modules/commonjs/sdk/core/reference.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const method = require("../../method/core");
const { Class } = require("./heritage");

// Object that inherit or mix WeakRefence inn will register
// weak observes for system notifications.
const WeakReference = Class({});
exports.WeakReference = WeakReference;


// If `isWeak(object)` is `true` observer installed
// for such `object` will be weak, meaning that it will
// be GC-ed if nothing else but observer is observing it.
// By default everything except `WeakReference` will return
// `false`.
const isWeak = method("reference/weak?");
exports.isWeak = isWeak;

isWeak.define(Object, _ => false);
isWeak.define(WeakReference, _ => true);
PK
!<i[=[[,modules/commonjs/sdk/deprecated/api-utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "deprecated"
};

const { merge } = require("../util/object");
const { union } = require("../util/array");
const { isNil, isRegExp } = require("../lang/type");

// The possible return values of getTypeOf.
const VALID_TYPES = [
  "array",
  "boolean",
  "function",
  "null",
  "number",
  "object",
  "string",
  "undefined",
  "regexp"
];

const { isArray } = Array;

/**
 * Returns a validated options dictionary given some requirements.  If any of
 * the requirements are not met, an exception is thrown.
 *
 * @param  options
 *         An object, the options dictionary to validate.  It's not modified.
 *         If it's null or otherwise falsey, an empty object is assumed.
 * @param  requirements
 *         An object whose keys are the expected keys in options.  Any key in
 *         options that is not present in requirements is ignored.  Each value
 *         in requirements is itself an object describing the requirements of
 *         its key.  There are four optional keys in this object:
 *           map: A function that's passed the value of the key in options.
 *                map's return value is taken as the key's value in the final
 *                validated options, is, and ok.  If map throws an exception
 *                it's caught and discarded, and the key's value is its value in
 *                options.
 *           is:  An array containing any number of the typeof type names.  If
 *                the key's value is none of these types, it fails validation.
 *                Arrays, null and regexps are identified by the special type names
 *                "array", "null", "regexp"; "object" will not match either.  No type
 *                coercion is done.
 *           ok:  A function that's passed the key's value.  If it returns
 *                false, the value fails validation.
 *           msg: If the key's value fails validation, an exception is thrown.
 *                This string will be used as its message.  If undefined, a
 *                generic message is used, unless is is defined, in which case
 *                the message will state that the value needs to be one of the
 *                given types.
 * @return An object whose keys are those keys in requirements that are also in
 *         options and whose values are the corresponding return values of map
 *         or the corresponding values in options.  Note that any keys not
 *         shared by both requirements and options are not in the returned
 *         object.
 */
exports.validateOptions = function validateOptions(options, requirements) {
  options = options || {};
  let validatedOptions = {};

  for (let key in requirements) {
    let isOptional = false;
    let mapThrew = false;
    let req = requirements[key];
    let [optsVal, keyInOpts] = (key in options) ?
                               [options[key], true] :
                               [undefined, false];
    if (req.map) {
      try {
        optsVal = req.map(optsVal);
      }
      catch (err) {
        if (err instanceof RequirementError)
          throw err;

        mapThrew = true;
      }
    }
    if (req.is) {
      let types = req.is;

      if (!isArray(types) && isArray(types.is))
        types = types.is;

      if (isArray(types)) {
        isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v));

        // Sanity check the caller's type names.
        types.forEach(function (typ) {
          if (VALID_TYPES.indexOf(typ) < 0) {
            let msg = 'Internal error: invalid requirement type "' + typ + '".';
            throw new Error(msg);
          }
        });
        if (types.indexOf(getTypeOf(optsVal)) < 0)
          throw new RequirementError(key, req);
      }
    }

    if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal)))
      throw new RequirementError(key, req);

    if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined))
      validatedOptions[key] = optsVal;
  }

  return validatedOptions;
};

exports.addIterator = function addIterator(obj, keysValsGenerator) {
  obj.__iterator__ = function(keysOnly, keysVals) {
    let keysValsIterator = keysValsGenerator.call(this);

    // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
    // and "for (.. in Iterator(..))" gets [key, value] pairs.
    let index = keysOnly ? 0 : 1;
    while (true)
      yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
  };
};

// Similar to typeof, except arrays, null and regexps are identified by "array" and
// "null" and "regexp", not "object".
var getTypeOf = exports.getTypeOf = function getTypeOf(val) {
  let typ = typeof(val);
  if (typ === "object") {
    if (!val)
      return "null";
    if (isArray(val))
      return "array";
    if (isRegExp(val))
      return "regexp";
  }
  return typ;
}

function RequirementError(key, requirement) {
  Error.call(this);

  this.name = "RequirementError";

  let msg = requirement.msg;
  if (!msg) {
    msg = 'The option "' + key + '" ';
    msg += requirement.is ?
           "must be one of the following types: " + requirement.is.join(", ") :
           "is invalid.";
  }

  this.message = msg;
}
RequirementError.prototype = Object.create(Error.prototype);

var string = { is: ['string', 'undefined', 'null'] };
exports.string = string;

var number = { is: ['number', 'undefined', 'null'] };
exports.number = number;

var boolean = { is: ['boolean', 'undefined', 'null'] };
exports.boolean = boolean;

var object = { is: ['object', 'undefined', 'null'] };
exports.object = object;

var array = { is: ['array', 'undefined', 'null'] };
exports.array = array;

var isTruthyType = type => !(type === 'undefined' || type === 'null');
var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };

function required(req) {
  let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType);

  return merge({}, req, {is: types});
}
exports.required = required;

function optional(req) {
  req = merge({is: []}, req);
  req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null');

  return req;
}
exports.optional = optional;

function either(...types) {
  return union.apply(null, types.map(findTypes));
}
exports.either = either;
PK
!<jVъ3modules/commonjs/sdk/deprecated/events/assembler.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Class } = require("../../core/heritage");
const { removeListener, on } = require("../../dom/events");

/**
 * Event targets
 * can be added / removed by calling `observe / ignore` methods. Composer should
 * provide array of event types it wishes to handle as property
 * `supportedEventsTypes` and function for handling all those events as
 * `handleEvent` property.
 */
exports.DOMEventAssembler = Class({
  /**
   * Function that is supposed to handle all the supported events (that are
   * present in the `supportedEventsTypes`) from all the observed
   * `eventTargets`.
   * @param {Event} event
   *    Event being dispatched.
   */
  handleEvent() {
    throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
  },
  /**
   * Array of supported event names.
   * @type {String[]}
   */
  get supportedEventsTypes() {
    throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
  },
  /**
   * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
   * supported events will be registered on the given `eventTarget`.
   * @param {EventTarget} eventTarget
   */
  observe: function observe(eventTarget) {
    this.supportedEventsTypes.forEach(function(eventType) {
      on(eventTarget, eventType, this);
    }, this);
  },
  /**
   * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
   * for all supported events will be unregistered from the given `eventTarget`.
   * @param {EventTarget} eventTarget
   */
  ignore: function ignore(eventTarget) {
    this.supportedEventsTypes.forEach(function(eventType) {
      removeListener(eventTarget, eventType, this);
    }, this);
  }
});
PK
!<"".modules/commonjs/sdk/deprecated/sync-worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 *
 * `deprecated/sync-worker` was previously `content/worker`, that was
 * incompatible with e10s. we are in the process of switching to the new
 * asynchronous `Worker`, which behaves slightly differently in some edge
 * cases, so we are keeping this one around for a short period.
 * try to switch to the new one as soon as possible..
 *
 */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
const { on, off, emit, setListeners } = require('../event/core');
const {
  attach, detach, destroy
} = require('../content/utils');
const { method } = require('../lang/functional');
const { Ci, Cu, Cc } = require('chrome');
const unload = require('../system/unload');
const events = require('../system/events');
const { getInnerId } = require("../window/utils");
const { WorkerSandbox } = require('../content/sandbox');
const { isPrivate } = require('../private-browsing/utils');

// A weak map of workers to hold private attributes that
// should not be exposed
const workers = new WeakMap();

var modelFor = (worker) => workers.get(worker);

const ERR_DESTROYED =
  "Couldn't find the worker to receive this message. " +
  "The script may not be initialized yet, or may already have been unloaded.";

const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
                   "until it is visible again.";

/**
 * Message-passing facility for communication between code running
 * in the content and add-on process.
 * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
 */
const Worker = Class({
  implements: [EventTarget],
  initialize: function WorkerConstructor (options) {
    // Save model in weak map to not expose properties
    let model = createModel();
    workers.set(this, model);

    options = options || {};

    if ('contentScriptFile' in options)
      this.contentScriptFile = options.contentScriptFile;
    if ('contentScriptOptions' in options)
      this.contentScriptOptions = options.contentScriptOptions;
    if ('contentScript' in options)
      this.contentScript = options.contentScript;
    if ('injectInDocument' in options)
      this.injectInDocument = !!options.injectInDocument;

    setListeners(this, options);

    unload.ensure(this, "destroy");

    // Ensure that worker.port is initialized for contentWorker to be able
    // to send events during worker initialization.
    this.port = createPort(this);

    model.documentUnload = documentUnload.bind(this);
    model.pageShow = pageShow.bind(this);
    model.pageHide = pageHide.bind(this);

    if ('window' in options)
      attach(this, options.window);
  },

  /**
   * Sends a message to the worker's global scope. Method takes single
   * argument, which represents data to be sent to the worker. The data may
   * be any primitive type value or `JSON`. Call of this method asynchronously
   * emits `message` event with data value in the global scope of this
   * worker.
   *
   * `message` event listeners can be set either by calling
   * `self.on` with a first argument string `"message"` or by
   * implementing `onMessage` function in the global scope of this worker.
   * @param {Number|String|JSON} data
   */
  postMessage: function (...data) {
    let model = modelFor(this);
    let args = ['message'].concat(data);
    if (!model.inited) {
      model.earlyEvents.push(args);
      return;
    }
    processMessage.apply(null, [this].concat(args));
  },

  get url () {
    let model = modelFor(this);
    // model.window will be null after detach
    return model.window ? model.window.document.location.href : null;
  },

  get contentURL () {
    let model = modelFor(this);
    return model.window ? model.window.document.URL : null;
  },

  // Implemented to provide some of the previous features of exposing sandbox
  // so that Worker can be extended
  getSandbox: function () {
    return modelFor(this).contentWorker;
  },

  toString: function () { return '[object Worker]'; },
  attach: method(attach),
  detach: method(detach),
  destroy: method(destroy)
});
exports.Worker = Worker;

attach.define(Worker, function (worker, window) {
  let model = modelFor(worker);
  model.window = window;
  // Track document unload to destroy this worker.
  // We can't watch for unload event on page's window object as it
  // prevents bfcache from working:
  // https://developer.mozilla.org/En/Working_with_BFCache
  model.windowID = getInnerId(model.window);
  events.on("inner-window-destroyed", model.documentUnload);

  // will set model.contentWorker pointing to the private API:
  model.contentWorker = WorkerSandbox(worker, model.window);

  // Listen to pagehide event in order to freeze the content script
  // while the document is frozen in bfcache:
  model.window.addEventListener("pageshow", model.pageShow, true);
  model.window.addEventListener("pagehide", model.pageHide, true);

  // Mainly enable worker.port.emit to send event to the content worker
  model.inited = true;
  model.frozen = false;

  // Fire off `attach` event
  emit(worker, 'attach', window);

  // Process all events and messages that were fired before the
  // worker was initialized.
  model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
});

/**
 * Remove all internal references to the attached document
 * Tells _port to unload itself and removes all the references from itself.
 */
detach.define(Worker, function (worker, reason) {
  let model = modelFor(worker);

  // maybe unloaded before content side is created
  if (model.contentWorker) {
    model.contentWorker.destroy(reason);
  }

  model.contentWorker = null;
  if (model.window) {
    model.window.removeEventListener("pageshow", model.pageShow, true);
    model.window.removeEventListener("pagehide", model.pageHide, true);
  }
  model.window = null;
  // This method may be called multiple times,
  // avoid dispatching `detach` event more than once
  if (model.windowID) {
    model.windowID = null;
    events.off("inner-window-destroyed", model.documentUnload);
    model.earlyEvents.length = 0;
    emit(worker, 'detach');
  }
  model.inited = false;
});

isPrivate.define(Worker, ({ tab }) => isPrivate(tab));

/**
 * Tells content worker to unload itself and
 * removes all the references from itself.
 */
destroy.define(Worker, function (worker, reason) {
  detach(worker, reason);
  modelFor(worker).inited = true;
  // Specifying no type or listener removes all listeners
  // from target
  off(worker);
  off(worker.port);
});

/**
 * Events fired by workers
 */
function documentUnload ({ subject, data }) {
  let model = modelFor(this);
  let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
  if (innerWinID != model.windowID) return false;
  detach(this);
  return true;
}

function pageShow () {
  let model = modelFor(this);
  model.contentWorker.emitSync('pageshow');
  emit(this, 'pageshow');
  model.frozen = false;
}

function pageHide () {
  let model = modelFor(this);
  model.contentWorker.emitSync('pagehide');
  emit(this, 'pagehide');
  model.frozen = true;
}

/**
 * Fired from postMessage and emitEventToContent, or from the earlyMessage
 * queue when fired before the content is loaded. Sends arguments to
 * contentWorker if able
 */

function processMessage (worker, ...args) {
  let model = modelFor(worker) || {};
  if (!model.contentWorker)
    throw new Error(ERR_DESTROYED);
  if (model.frozen)
    throw new Error(ERR_FROZEN);
  model.contentWorker.emit.apply(null, args);
}

function createModel () {
  return {
    // List of messages fired before worker is initialized
    earlyEvents: [],
    // Is worker connected to the content worker sandbox ?
    inited: false,
    // Is worker being frozen? i.e related document is frozen in bfcache.
    // Content script should not be reachable if frozen.
    frozen: true,
    /**
     * Reference to the content side of the worker.
     * @type {WorkerGlobalScope}
     */
    contentWorker: null,
    /**
     * Reference to the window that is accessible from
     * the content scripts.
     * @type {Object}
     */
    window: null
  };
}

function createPort (worker) {
  let port = EventTarget();
  port.emit = emitEventToContent.bind(null, worker);
  return port;
}

/**
 * Emit a custom event to the content script,
 * i.e. emit this event on `self.port`
 */
function emitEventToContent (worker, ...eventArgs) {
  let model = modelFor(worker);
  let args = ['event'].concat(eventArgs);
  if (!model.inited) {
    model.earlyEvents.push(args);
    return;
  }
  processMessage.apply(null, [worker].concat(args));
}
PK
!<pQ113modules/commonjs/sdk/deprecated/unit-test-finder.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "deprecated"
};

const file = require("../io/file");
const { Loader } = require("../test/loader");

const { isNative } = require('@loader/options');

const cuddlefish = isNative ? require("toolkit/loader") : require("../loader/cuddlefish");

const { defer, resolve } = require("../core/promise");
const { getAddon } = require("../addon/installer");
const { id } = require("sdk/self");
const { newURI } = require('sdk/url/utils');
const { getZipReader } = require("../zip/utils");

const { Cc, Ci, Cu } = require("chrome");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var ios = Cc['@mozilla.org/network/io-service;1']
          .getService(Ci.nsIIOService);

const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/;
const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/;

const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence");

const toFile = x => x.QueryInterface(Ci.nsIFile);
const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js";
const getFileURI = x => ios.newFileURI(x).spec;

const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries));
const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory));
const getTestURIs = directory => map(getFileURI, getTestFiles(directory));

const isDirectory = x => x.isDirectory();
const getTestEntries = directory => mapcat(entry =>
  /^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry),
  filter(isDirectory, getDirectoryEntries(directory)));

const removeDups = (array) => array.reduce((result, value) => {
  if (value != result[result.length - 1]) {
    result.push(value);
  }
  return result;
}, []);

const getSuites = function getSuites({ id, filter }) {
  const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX;

  return getAddon(id).then(addon => {
    let fileURI = addon.getResourceURI("tests/");
    let isPacked = fileURI.scheme == "jar";
    let xpiURI = addon.getResourceURI();
    let file = xpiURI.QueryInterface(Ci.nsIFileURL).file;
    let suites = [];
    let addEntry = (entry) => {
      if (filter(entry) && TEST_REGEX.test(entry)) {
        let suite = (isNative ? "./" : "") + (RegExp.$2 || "") + RegExp.$3;
        suites.push(suite);
      }
    }

    if (isPacked) {
      return getZipReader(file).then(zip => {
        let entries = zip.findEntries(null);
        while (entries.hasMore()) {
          let entry = entries.getNext();
          addEntry(entry);
        }
        zip.close();

        // sort and remove dups
        suites = removeDups(suites.sort());
        return suites;
      })
    }
    else {
      let tests = [...getTestEntries(file)];
      let rootURI = addon.getResourceURI("/");
      tests.forEach((entry) => {
        addEntry(entry.replace(rootURI.spec, ""));
      });
    }

    // sort and remove dups
    suites = removeDups(suites.sort());
    return suites;
  });
}
exports.getSuites = getSuites;

const makeFilters = function makeFilters(options) {
  options = options || {};

  // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
  // optionally separates a regex for the test fileName from a regex for the
  // testName.
  if (options.filter) {
    let colonPos = options.filter.indexOf(':');
    let filterFileRegex, filterNameRegex;

    if (colonPos === -1) {
      filterFileRegex = new RegExp(options.filter);
      filterNameRegex = { test: () => true }
    }
    else {
      filterFileRegex = new RegExp(options.filter.substr(0, colonPos));
      filterNameRegex = new RegExp(options.filter.substr(colonPos + 1));
    }

    return {
      fileFilter: (name) => filterFileRegex.test(name),
      testFilter: (name) => filterNameRegex.test(name)
    }
  }

  return {
    fileFilter: () => true,
    testFilter: () => true
  };
}
exports.makeFilters = makeFilters;

var loader = Loader(module);
const NOT_TESTS = ['setup', 'teardown'];

var TestFinder = exports.TestFinder = function TestFinder(options) {
  this.filter = options.filter;
  this.testInProcess = options.testInProcess === false ? false : true;
  this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
};

TestFinder.prototype = {
  findTests: function findTests() {
    let { fileFilter, testFilter } = makeFilters({ filter: this.filter });

    return getSuites({ id: id, filter: fileFilter }).then(suites => {
      let testsRemaining = [];

      let getNextTest = () => {
        if (testsRemaining.length) {
          return testsRemaining.shift();
        }

        if (!suites.length) {
          return null;
        }

        let suite = suites.shift();

        // Load each test file as a main module in its own loader instance
        // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
        let suiteModule;

        try {
          suiteModule = cuddlefish.main(loader, suite);
        }
        catch (e) {
          if (/Unsupported Application/i.test(e.message)) {
            // If `Unsupported Application` error thrown during test,
            // skip the test suite
            suiteModule = {
              'test suite skipped': assert => assert.pass(e.message)
            };
          }
          else {
            console.exception(e);
            throw e;
          }
        }

        if (this.testInProcess) {
          for (let name of Object.keys(suiteModule).sort()) {
            if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) {
              testsRemaining.push({
                setup: suiteModule.setup,
                teardown: suiteModule.teardown,
                testFunction: suiteModule[name],
                name: suite + "." + name
              });
            }
          }
        }

        return getNextTest();
      };

      return {
        getNext: () => resolve(getNextTest())
      };
    });
  }
};
PK
!<RBB,modules/commonjs/sdk/deprecated/unit-test.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "deprecated"
};

lazyRequireModule(this, "../timers", "timer");
const cfxArgs = require("../test/options");
lazyRequire(this, "../tabs/utils", "getTabs", "closeTab", "getURI", "getTabId", "getSelectedTab");
lazyRequire(this, "../window/utils", "windows", "isBrowser", "getMostRecentBrowserWindow");
const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
lazyRequire(this, "../window/utils", "getInnerId");
const { cleanUI } = require("../test/utils");

const findAndRunTests = function findAndRunTests(options) {
  var TestFinder = require("./unit-test-finder").TestFinder;
  var finder = new TestFinder({
    filter: options.filter,
    testInProcess: options.testInProcess,
    testOutOfProcess: options.testOutOfProcess
  });
  var runner = new TestRunner({fs: options.fs});
  finder.findTests().then(tests => {
    runner.startMany({
      tests: tests,
      stopOnError: options.stopOnError,
      onDone: options.onDone
    });
  });
};
exports.findAndRunTests = findAndRunTests;

var runnerWindows = new WeakMap();
var runnerTabs = new WeakMap();

const TestRunner = function TestRunner(options) {
  options = options || {};

  // remember the id's for the open window and tab
  let window = getMostRecentBrowserWindow();
  runnerWindows.set(this, getInnerId(window));
  runnerTabs.set(this, getTabId(getSelectedTab(window)));

  this.fs = options.fs;
  this.console = options.console || console;
  this.passed = 0;
  this.failed = 0;
  this.testRunSummary = [];
  this.expectFailNesting = 0;
  this.done = TestRunner.prototype.done.bind(this);
};

TestRunner.prototype = {
  toString: function toString() {
    return "[object TestRunner]";
  },

  DEFAULT_PAUSE_TIMEOUT: (cfxArgs.parseable ? 300000 : 15000), //Five minutes (5*60*1000ms)
  PAUSE_DELAY: 500,

  _logTestFailed: function _logTestFailed(why) {
    if (!(why in this.test.errors))
      this.test.errors[why] = 0;
    this.test.errors[why]++;
  },

  _uncaughtErrorObserver: function({message, date, fileName, stack, lineNumber}) {
    this.fail("There was an uncaught Promise rejection: " + message + " @ " +
              fileName + ":" + lineNumber + "\n" + stack);
  },

  pass: function pass(message) {
    if(!this.expectFailure) {
      if ("testMessage" in this.console)
        this.console.testMessage(true, true, this.test.name, message);
      else
        this.console.info("pass:", message);
      this.passed++;
      this.test.passed++;
      this.test.last = message;
    }
    else {
      this.expectFailure = false;
      this._logTestFailed("failure");
      if ("testMessage" in this.console) {
        this.console.testMessage(true, false, this.test.name, message);
      }
      else {
        this.console.error("fail:", 'Failure Expected: ' + message)
        this.console.trace();
      }
      this.failed++;
      this.test.failed++;
    }
  },

  fail: function fail(message) {
    if(!this.expectFailure) {
      this._logTestFailed("failure");
      if ("testMessage" in this.console) {
        this.console.testMessage(false, false, this.test.name, message);
      }
      else {
        this.console.error("fail:", message)
        this.console.trace();
      }
      this.failed++;
      this.test.failed++;
    }
    else {
      this.expectFailure = false;
      if ("testMessage" in this.console)
        this.console.testMessage(false, true, this.test.name, message);
      else
        this.console.info("pass:", message);
      this.passed++;
      this.test.passed++;
      this.test.last = message;
    }
  },

  expectFail: function(callback) {
    this.expectFailure = true;
    callback();
    this.expectFailure = false;
  },

  exception: function exception(e) {
    this._logTestFailed("exception");
    if (cfxArgs.parseable)
      this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n");
    this.console.exception(e);
    this.failed++;
    this.test.failed++;
  },

  assertMatches: function assertMatches(string, regexp, message) {
    if (regexp.test(string)) {
      if (!message)
        message = uneval(string) + " matches " + uneval(regexp);
      this.pass(message);
    } else {
      var no = uneval(string) + " doesn't match " + uneval(regexp);
      if (!message)
        message = no;
      else
        message = message + " (" + no + ")";
      this.fail(message);
    }
  },

  assertRaises: function assertRaises(func, predicate, message) {
    try {
      func();
      if (message)
        this.fail(message + " (no exception thrown)");
      else
        this.fail("function failed to throw exception");
    } catch (e) {
      var errorMessage;
      if (typeof(e) == "string")
        errorMessage = e;
      else
        errorMessage = e.message;
      if (typeof(predicate) == "string")
        this.assertEqual(errorMessage, predicate, message);
      else
        this.assertMatches(errorMessage, predicate, message);
    }
  },

  assert: function assert(a, message) {
    if (!a) {
      if (!message)
        message = "assertion failed, value is " + a;
      this.fail(message);
    } else
      this.pass(message || "assertion successful");
  },

  assertNotEqual: function assertNotEqual(a, b, message) {
    if (a != b) {
      if (!message)
        message = "a != b != " + uneval(a);
      this.pass(message);
    } else {
      var equality = uneval(a) + " == " + uneval(b);
      if (!message)
        message = equality;
      else
        message += " (" + equality + ")";
      this.fail(message);
    }
  },

  assertEqual: function assertEqual(a, b, message) {
    if (a == b) {
      if (!message)
        message = "a == b == " + uneval(a);
      this.pass(message);
    } else {
      var inequality = uneval(a) + " != " + uneval(b);
      if (!message)
        message = inequality;
      else
        message += " (" + inequality + ")";
      this.fail(message);
    }
  },

  assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
    if (a !== b) {
      if (!message)
        message = "a !== b !== " + uneval(a);
      this.pass(message);
    } else {
      var equality = uneval(a) + " === " + uneval(b);
      if (!message)
        message = equality;
      else
        message += " (" + equality + ")";
      this.fail(message);
    }
  },

  assertStrictEqual: function assertStrictEqual(a, b, message) {
    if (a === b) {
      if (!message)
        message = "a === b === " + uneval(a);
      this.pass(message);
    } else {
      var inequality = uneval(a) + " !== " + uneval(b);
      if (!message)
        message = inequality;
      else
        message += " (" + inequality + ")";
      this.fail(message);
    }
  },

  assertFunction: function assertFunction(a, message) {
    this.assertStrictEqual('function', typeof a, message);
  },

  assertUndefined: function(a, message) {
    this.assertStrictEqual('undefined', typeof a, message);
  },

  assertNotUndefined: function(a, message) {
    this.assertNotStrictEqual('undefined', typeof a, message);
  },

  assertNull: function(a, message) {
    this.assertStrictEqual(null, a, message);
  },

  assertNotNull: function(a, message) {
    this.assertNotStrictEqual(null, a, message);
  },

  assertObject: function(a, message) {
    this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
  },

  assertString: function(a, message) {
    this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
  },

  assertArray: function(a, message) {
    this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
  },

  assertNumber: function(a, message) {
    this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
  },

  done: function done() {
    if (this.isDone) {
      return resolve();
    }

    this.isDone = true;
    this.pass("This test is done.");

    if (this.test.teardown) {
      this.test.teardown(this);
    }

    if (this.waitTimeout !== null) {
      timer.clearTimeout(this.waitTimeout);
      this.waitTimeout = null;
    }

    // Do not leave any callback set when calling to `waitUntil`
    this.waitUntilCallback = null;
    if (this.test.passed == 0 && this.test.failed == 0) {
      this._logTestFailed("empty test");

      if ("testMessage" in this.console) {
        this.console.testMessage(false, false, this.test.name, "Empty test");
      }
      else {
        this.console.error("fail:", "Empty test")
      }

      this.failed++;
      this.test.failed++;
    }

    let wins = windows(null, { includePrivate: true });
    let winPromises = wins.map(win => {
      return new Promise(resolve => {
        if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) {
          resolve()
        }
        else {
          win.addEventListener("DOMContentLoaded", function() {
            resolve();
          }, {once: true});
        }
      });
    });

    PromiseDebugging.flushUncaughtErrors();
    PromiseDebugging.removeUncaughtErrorObserver(this._uncaughtErrorObserver);


    return all(winPromises).then(() => {
      let browserWins = wins.filter(isBrowser);
      let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
      let newTabID = getTabId(getSelectedTab(wins[0]));
      let oldTabID = runnerTabs.get(this);
      let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
      let failure = false;

      if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
        failure = true;
        this.fail("Should not be any unexpected windows open");
      }
      else if (hasMoreTabsOpen) {
        failure = true;
        this.fail("Should not be any unexpected tabs open");
      }
      else if (oldTabID != newTabID) {
        failure = true;
        runnerTabs.set(this, newTabID);
        this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
      }

      if (failure) {
        console.log("Windows open:");
        for (let win of wins) {
          if (isBrowser(win)) {
            tabs = getTabs(win);
            console.log(win.location + " - " + tabs.map(getURI).join(", "));
          }
          else {
            console.log(win.location);
          }
        }
      }

      return failure;
    }).
    then(failure => {
      if (!failure) {
        this.pass("There was a clean UI.");
        return null;
      }
      return cleanUI().then(() => {
        this.pass("There is a clean UI.");
      });
    }).
    then(() => {
      this.testRunSummary.push({
        name: this.test.name,
        passed: this.test.passed,
        failed: this.test.failed,
        errors: Object.keys(this.test.errors).join(", ")
      });

      if (this.onDone !== null) {
        let onDone = this.onDone;
        this.onDone = null;
        timer.setTimeout(_ => onDone(this));
      }
    }).
    catch(console.exception);
  },

  // Set of assertion functions to wait for an assertion to become true
  // These functions take the same arguments as the TestRunner.assert* methods.
  waitUntil: function waitUntil() {
    return this._waitUntil(this.assert, arguments);
  },

  waitUntilNotEqual: function waitUntilNotEqual() {
    return this._waitUntil(this.assertNotEqual, arguments);
  },

  waitUntilEqual: function waitUntilEqual() {
    return this._waitUntil(this.assertEqual, arguments);
  },

  waitUntilMatches: function waitUntilMatches() {
    return this._waitUntil(this.assertMatches, arguments);
  },

  /**
   * Internal function that waits for an assertion to become true.
   * @param {Function} assertionMethod
   *    Reference to a TestRunner assertion method like test.assert,
   *    test.assertEqual, ...
   * @param {Array} args
   *    List of arguments to give to the previous assertion method.
   *    All functions in this list are going to be called to retrieve current
   *    assertion values.
   */
  _waitUntil: function waitUntil(assertionMethod, args) {
    let { promise, resolve } = defer();
    let count = 0;
    let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;

    // We need to ensure that test is asynchronous
    if (!this.waitTimeout)
      this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);

    let finished = false;
    let test = this;

    // capture a traceback before we go async.
    let traceback = require("../console/traceback");
    let stack = traceback.get();
    stack.splice(-2, 2);
    let currentWaitStack = traceback.format(stack);
    let timeout = null;

    function loop(stopIt) {
      timeout = null;

      // Build a mockup object to fake TestRunner API and intercept calls to
      // pass and fail methods, in order to retrieve nice error messages
      // and assertion result
      let mock = {
        pass: function (msg) {
          test.pass(msg);
          test.waitUntilCallback = null;
          if (!stopIt)
            resolve();
        },
        fail: function (msg) {
          // If we are called on test timeout, we stop the loop
          // and print which test keeps failing:
          if (stopIt) {
            test.console.error("test assertion never became true:\n",
                               msg + "\n",
                               currentWaitStack);
            if (timeout)
              timer.clearTimeout(timeout);
            return;
          }
          timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
        }
      };

      // Automatically call args closures in order to build arguments for
      // assertion function
      let appliedArgs = [];
      for (let i = 0, l = args.length; i < l; i++) {
        let a = args[i];
        if (typeof a == "function") {
          try {
            a = a();
          }
          catch(e) {
            test.fail("Exception when calling asynchronous assertion: " + e +
                      "\n" + e.stack);
            return resolve();
          }
        }
        appliedArgs.push(a);
      }

      // Finally call assertion function with current assertion values
      assertionMethod.apply(mock, appliedArgs);
    }
    loop();
    this.waitUntilCallback = loop;

    return promise;
  },

  waitUntilDone: function waitUntilDone(ms) {
    if (ms === undefined)
      ms = this.DEFAULT_PAUSE_TIMEOUT;

    var self = this;

    function tiredOfWaiting() {
      self._logTestFailed("timed out");
      if ("testMessage" in self.console) {
        self.console.testMessage(false, false, self.test.name,
          `Test timed out (after: ${self.test.last})`);
      }
      else {
        self.console.error("fail:", `Timed out (after: ${self.test.last})`)
      }
      if (self.waitUntilCallback) {
        self.waitUntilCallback(true);
        self.waitUntilCallback = null;
      }
      self.failed++;
      self.test.failed++;
      self.done();
    }

    // We may already have registered a timeout callback
    if (this.waitTimeout)
      timer.clearTimeout(this.waitTimeout);

    this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
  },

  startMany: function startMany(options) {
    function runNextTest(self) {
      let { tests, onDone } = options;

      return tests.getNext().then((test) => {
        if (options.stopOnError && self.test && self.test.failed) {
          self.console.error("aborted: test failed and --stop-on-error was specified");
          onDone(self);
        }
        else if (test) {
          self.start({test: test, onDone: runNextTest});
        }
        else {
          onDone(self);
        }
      });
    }

    return runNextTest(this).catch(console.exception);
  },

  start: function start(options) {
    this.test = options.test;
    this.test.passed = 0;
    this.test.failed = 0;
    this.test.errors = {};
    this.test.last = 'START';
    PromiseDebugging.clearUncaughtErrorObservers();
    this._uncaughtErrorObserver = this._uncaughtErrorObserver.bind(this);
    PromiseDebugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);

    this.isDone = false;
    this.onDone = function(self) {
      if (cfxArgs.parseable)
        self.console.print("TEST-END | " + self.test.name + "\n");
      options.onDone(self);
    }
    this.waitTimeout = null;

    try {
      if (cfxArgs.parseable)
        this.console.print("TEST-START | " + this.test.name + "\n");
      else
        this.console.info("executing '" + this.test.name + "'");

      if(this.test.setup) {
        this.test.setup(this);
      }
      this.test.testFunction(this);
    } catch (e) {
      this.exception(e);
    }
    if (this.waitTimeout === null)
      this.done();
  }
};
exports.TestRunner = TestRunner;
PK
!<Kkk/modules/commonjs/sdk/deprecated/window-utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'deprecated'
};

const { Cc, Ci } = require('chrome');
const events = require('../system/events');
const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
        getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils');
const { deprecateFunction } = require('../util/deprecate');
const { ignoreWindow } = require('sdk/private-browsing/utils');
const { isPrivateBrowsingSupported } = require('../self');

const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                       getService(Ci.nsIWindowWatcher);
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                        getService(Ci.nsIAppShellService);

// Bug 834961: ignore private windows when they are not supported
function getWindows() {
  return windows(null, { includePrivate: isPrivateBrowsingSupported });
}

/**
 * An iterator for XUL windows currently in the application.
 *
 * @return A generator that yields XUL windows exposing the
 *         nsIDOMWindow interface.
 */
function windowIterator() {
  // Bug 752631: We only pass already loaded window in order to avoid
  // breaking XUL windows DOM. DOM is broken when some JS code try
  // to access DOM during "uninitialized" state of the related document.
  let list = getWindows().filter(isDocumentLoaded);
  for (let i = 0, l = list.length; i < l; i++) {
    yield list[i];
  }
};
exports.windowIterator = windowIterator;

/**
 * An iterator for browser windows currently open in the application.
 * @returns {Function}
 *    A generator that yields browser windows exposing the `nsIDOMWindow`
 *    interface.
 */
function browserWindowIterator() {
  for (let window of windowIterator()) {
    if (isBrowser(window))
      yield window;
  }
}
exports.browserWindowIterator = browserWindowIterator;

function WindowTracker(delegate) {
   if (!(this instanceof WindowTracker)) {
     return new WindowTracker(delegate);
   }

  this._delegate = delegate;

  for (let window of getWindows())
    this._regWindow(window);
  windowWatcher.registerNotification(this);
  this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this);
  events.on('toplevel-window-ready', this._onToplevelWindowReady);

  require('../system/unload').ensure(this);

  return this;
};

WindowTracker.prototype = {
  _regLoadingWindow: function _regLoadingWindow(window) {
    // Bug 834961: ignore private windows when they are not supported
    if (ignoreWindow(window))
      return;

    window.addEventListener('load', this, true);
  },

  _unregLoadingWindow: function _unregLoadingWindow(window) {
    // This may have no effect if we ignored the window in _regLoadingWindow().
    window.removeEventListener('load', this, true);
  },

  _regWindow: function _regWindow(window) {
    // Bug 834961: ignore private windows when they are not supported
    if (ignoreWindow(window))
      return;

    if (window.document.readyState == 'complete') {
      this._unregLoadingWindow(window);
      this._delegate.onTrack(window);
    } else
      this._regLoadingWindow(window);
  },

  _unregWindow: function _unregWindow(window) {
    if (window.document.readyState == 'complete') {
      if (this._delegate.onUntrack)
        this._delegate.onUntrack(window);
    } else {
      this._unregLoadingWindow(window);
    }
  },

  unload: function unload() {
    windowWatcher.unregisterNotification(this);
    events.off('toplevel-window-ready', this._onToplevelWindowReady);
    for (let window of getWindows())
      this._unregWindow(window);
  },

  handleEvent: function handleEvent(event) {
    try {
      if (event.type == 'load' && event.target) {
        var window = event.target.defaultView;
        if (window)
          this._regWindow(getToplevelWindow(window));
      }
    }
    catch(e) {
      console.exception(e);
    }
  },

  _onToplevelWindowReady: function _onToplevelWindowReady({subject}) {
    let window = getToplevelWindow(subject);
    // ignore private windows if they are not supported
    if (ignoreWindow(window))
      return;
    this._regWindow(window);
  },

  observe: function observe(subject, topic, data) {
    try {
      var window = subject.QueryInterface(Ci.nsIDOMWindow);
      // ignore private windows if they are not supported
      if (ignoreWindow(window))
        return;
      if (topic == 'domwindowclosed')
      this._unregWindow(window);
    }
    catch(e) {
      console.exception(e);
    }
  }
};
exports.WindowTracker = WindowTracker;

Object.defineProperties(exports, {
  activeWindow: {
    enumerable: true,
    get: function() {
      return getMostRecentWindow(null);
    },
    set: function(window) {
      try {
        window.focus();
      } catch (e) {}
    }
  },
  activeBrowserWindow: {
    enumerable: true,
    get: getMostRecentBrowserWindow
  }
});


/**
 * Returns the ID of the window's current inner window.
 */
exports.getInnerId = deprecateFunction(getInnerId,
  'require("window-utils").getInnerId is deprecated, ' +
  'please use require("sdk/window/utils").getInnerId instead'
);

exports.getOuterId = deprecateFunction(getOuterId,
  'require("window-utils").getOuterId is deprecated, ' +
  'please use require("sdk/window/utils").getOuterId instead'
);

exports.isBrowser = deprecateFunction(isBrowser,
  'require("window-utils").isBrowser is deprecated, ' +
  'please use require("sdk/window/utils").isBrowser instead'
);

exports.hiddenWindow = appShellService.hiddenDOMWindow;
PK
!<uUl'modules/commonjs/sdk/dom/events/keys.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { emit } = require("../events");
const { getCodeForKey, toJSON } = require("../../keyboard/utils");
const { has } = require("../../util/array");
const { isString } = require("../../lang/type");

const INITIALIZER = "initKeyEvent";
const CATEGORY = "KeyboardEvent";

function Options(options) {
  if (!isString(options))
    return options;

  var { key, modifiers } = toJSON(options);
  return {
    key: key,
    control: has(modifiers, "control"),
    alt: has(modifiers, "alt"),
    shift: has(modifiers, "shift"),
    meta: has(modifiers, "meta")
  };
}

var keyEvent = exports.keyEvent = function keyEvent(element, type, options) {

  emit(element, type, {
    initializer: INITIALIZER,
    category: CATEGORY,
    settings: [
      !("bubbles" in options) || options.bubbles !== false,
      !("cancelable" in options) || options.cancelable !== false,
      "window" in options && options.window ? options.window : null,
      "control" in options && !!options.control,
      "alt" in options && !!options.alt,
      "shift" in options && !!options.shift,
      "meta" in options && !!options.meta,
      getCodeForKey(options.key) || 0,
      options.key.length === 1 ? options.key.charCodeAt(0) : 0
    ]
  });
}

exports.keyDown = function keyDown(element, options) {
  keyEvent(element, "keydown", Options(options));
};

exports.keyUp = function keyUp(element, options) {
  keyEvent(element, "keyup", Options(options));
};

exports.keyPress = function keyPress(element, options) {
  keyEvent(element, "keypress", Options(options));
};

PK
!<gg*modules/commonjs/sdk/dom/events-shimmed.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'unstable'
};

const events = require('./events.js');

exports.emit = (element, type, obj) => events.emit(element, type, obj, true);
exports.on = (element, type, listener, capture) => events.on(element, type, listener, capture, true);
exports.once = (element, type, listener, capture) => events.once(element, type, listener, capture, true);
exports.removeListener = (element, type, listener, capture) => events.removeListener(element, type, listener, capture, true);
exports.removed = events.removed;
exports.when = (element, eventName, capture) => events.when(element, eventName, capture ? capture : false, true);
PK
!<I"modules/commonjs/sdk/dom/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cu } = require("chrome");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");

// Utility function that returns copy of the given `text` with last character
// removed if it is `"s"`.
function singularify(text) {
  return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text;
}

// Utility function that takes event type, argument is passed to
// `document.createEvent` and returns name of the initializer method of the
// given event. Please note that there are some event types whose initializer
// methods can't be guessed by this function. For more details see following
// link: https://developer.mozilla.org/En/DOM/Document.createEvent
function getInitializerName(category) {
  return "init" + singularify(category);
}

/**
 * Registers an event `listener` on a given `element`, that will be called
 * when events of specified `type` is dispatched on the `element`.
 * @param {Element} element
 *    Dom element to register listener on.
 * @param {String} type
 *    A string representing the
 *    [event type](https://developer.mozilla.org/en/DOM/event.type) to
 *    listen for.
 * @param {Function} listener
 *    Function that is called whenever an event of the specified `type`
 *    occurs.
 * @param {Boolean} capture
 *    If true, indicates that the user wishes to initiate capture. After
 *    initiating capture, all events of the specified type will be dispatched
 *    to the registered listener before being dispatched to any `EventTarget`s
 *    beneath it in the DOM tree. Events which are bubbling upward through
 *    the tree will not trigger a listener designated to use capture.
 *    See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
 *    for a detailed explanation.
 */
function on(element, type, listener, capture, shimmed = false) {
  // `capture` defaults to `false`.
  capture = capture || false;
  if (shimmed) {
    element.addEventListener(type, listener, capture);
  } else {
    ShimWaiver.getProperty(element, "addEventListener")(type, listener, capture);
  }
}
exports.on = on;

/**
 * Registers an event `listener` on a given `element`, that will be called
 * only once, next time event of specified `type` is dispatched on the
 * `element`.
 * @param {Element} element
 *    Dom element to register listener on.
 * @param {String} type
 *    A string representing the
 *    [event type](https://developer.mozilla.org/en/DOM/event.type) to
 *    listen for.
 * @param {Function} listener
 *    Function that is called whenever an event of the specified `type`
 *    occurs.
 * @param {Boolean} capture
 *    If true, indicates that the user wishes to initiate capture. After
 *    initiating capture, all events of the specified type will be dispatched
 *    to the registered listener before being dispatched to any `EventTarget`s
 *    beneath it in the DOM tree. Events which are bubbling upward through
 *    the tree will not trigger a listener designated to use capture.
 *    See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
 *    for a detailed explanation.
 */
function once(element, type, listener, capture, shimmed = false) {
  on(element, type, function selfRemovableListener(event) {
    removeListener(element, type, selfRemovableListener, capture, shimmed);
    listener.apply(this, arguments);
  }, capture, shimmed);
}
exports.once = once;

/**
 * Unregisters an event `listener` on a given `element` for the events of the
 * specified `type`.
 *
 * @param {Element} element
 *    Dom element to unregister listener from.
 * @param {String} type
 *    A string representing the
 *    [event type](https://developer.mozilla.org/en/DOM/event.type) to
 *    listen for.
 * @param {Function} listener
 *    Function that is called whenever an event of the specified `type`
 *    occurs.
 * @param {Boolean} capture
 *    If true, indicates that the user wishes to initiate capture. After
 *    initiating capture, all events of the specified type will be dispatched
 *    to the registered listener before being dispatched to any `EventTarget`s
 *    beneath it in the DOM tree. Events which are bubbling upward through
 *    the tree will not trigger a listener designated to use capture.
 *    See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
 *    for a detailed explanation.
 */
function removeListener(element, type, listener, capture, shimmed = false) {
  if (shimmed) {
    element.removeEventListener(type, listener, capture);
  } else {
    ShimWaiver.getProperty(element, "removeEventListener")(type, listener, capture);
  }
}
exports.removeListener = removeListener;

/**
 * Emits event of the specified `type` and `category` on the given `element`.
 * Specified `settings` are used to initialize event before dispatching it.
 * @param {Element} element
 *    Dom element to dispatch event on.
 * @param {String} type
 *    A string representing the
 *    [event type](https://developer.mozilla.org/en/DOM/event.type).
 * @param {Object} options
 *    Options object containing following properties:
 *    - `category`: String passed to the `document.createEvent`. Option is
 *      optional and defaults to "UIEvents".
 *    - `initializer`: If passed it will be used as name of the method used
 *      to initialize event. If omitted name will be generated from the
 *      `category` field by prefixing it with `"init"` and removing last
 *      character if it matches `"s"`.
 *    - `settings`: Array of settings that are forwarded to the event
 *      initializer after firs `type` argument.
 * @see https://developer.mozilla.org/En/DOM/Document.createEvent
 */
function emit(element, type, { category, initializer, settings }, shimmed = false) {
  category = category || "UIEvents";
  initializer = initializer || getInitializerName(category);
  let document = element.ownerDocument;
  let event = document.createEvent(category);
  event[initializer].apply(event, [type].concat(settings));
  if (shimmed) {
    element.dispatchEvent(event);
  } else {
    ShimWaiver.getProperty(element, "dispatchEvent")(event);
  }
};
exports.emit = emit;

// Takes DOM `element` and returns promise which is resolved
// when given element is removed from it's parent node.
const removed = element => {
  return new Promise(resolve => {
    const { MutationObserver } = element.ownerGlobal;
    const observer = new MutationObserver(mutations => {
      for (let mutation of mutations) {
        for (let node of mutation.removedNodes || []) {
          if (node === element) {
            observer.disconnect();
            resolve(element);
          }
        }
      }
    });
    observer.observe(element.parentNode, {childList: true});
  });
};
exports.removed = removed;

const when = (element, eventName, capture=false, shimmed=false) => new Promise(resolve => {
  const listener = event => {
    if (shimmed) {
      element.removeEventListener(eventName, listener, capture);
    } else {
      ShimWaiver.getProperty(element, "removeEventListener")(eventName, listener, capture);
    }
    resolve(event);
  };

  if (shimmed) {
    element.addEventListener(eventName, listener, capture);
  } else {
    ShimWaiver.getProperty(element, "addEventListener")(eventName, listener, capture);
  }
});
exports.when = when;
PK
!<=o8$modules/commonjs/sdk/event/chrome.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, Cr, Cu } = require("chrome");
const { emit, on, off } = require("./core");
var observerService = Cc["@mozilla.org/observer-service;1"]
                      .getService(Ci.nsIObserverService);

const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");

const { when: unload } = require("../system/unload");

// Simple class that can be used to instantiate event channel that
// implements `nsIObserver` interface. It's will is used by `observe`
// function as observer + event target. It basically proxies observer
// notifications as to it's registered listeners.
function ObserverChannel() {}
Object.freeze(Object.defineProperties(ObserverChannel.prototype, {
  QueryInterface: {
    value: function(iid) {
      if (!iid.equals(Ci.nsIObserver) &&
          !iid.equals(Ci.nsISupportsWeakReference) &&
          !iid.equals(Ci.nsISupports))
        throw Cr.NS_ERROR_NO_INTERFACE;
      return this;
    }
  },
  observe: {
    value: function(subject, topic, data) {
      emit(this, "data", {
        type: topic,
        target: subject,
        data: data
      });
    }
  }
}));

function observe(topic) {
  let observerChannel = new ObserverChannel();

  // Note: `nsIObserverService` will not hold a weak reference to a
  // observerChannel (since third argument is `true`). There for if it
  // will be GC-ed with all it's event listeners once no other references
  // will be held.
  addObserver(observerChannel, topic, true);

  // We need to remove any observer added once the add-on is unloaded;
  // otherwise we'll get a "dead object" exception.
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833
  unload(() => removeObserver(observerChannel, topic));

  return observerChannel;
}

exports.observe = observe;
PK
!<oG+WW"modules/commonjs/sdk/event/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
const BAD_LISTENER = 'The event listener must be a function.';

const { DefaultMap, DefaultWeakMap } = require('../util/object');

const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;

// Count of total listeners ever added.
// This is used to keep track of when a listener was added, which can
// have an effect on when it is and isn't dispatched. See comments in
// emitOnObject for more details.
let listenerCount = 0;

const observers = new DefaultWeakMap(() => {
  return new DefaultMap(() => new Map());
});

/**
 * Registers an event `listener` that is called every time events of
 * specified `type` is emitted on the given event `target`.
 * @param {Object} target
 *    Event target object.
 * @param {String} type
 *    The type of event.
 * @param {Function} listener
 *    The listener function that processes the event.
 */
function on(target, type, listener) {
  if (typeof(listener) !== 'function')
    throw new Error(BAD_LISTENER);

  observers.get(target).get(type).set(listener, listenerCount++);
}
exports.on = on;


// Map of wrapper functions for listeners added using `once`.
var onceWeakMap = new WeakMap();

/**
 * Registers an event `listener` that is called only the next time an event
 * of the specified `type` is emitted on the given event `target`.
 * @param {Object} target
 *    Event target object.
 * @param {String} type
 *    The type of the event.
 * @param {Function} listener
 *    The listener function that processes the event.
 */
function once(target, type, listener) {
  function replacement(...args) {
    off(target, type, replacement);
    onceWeakMap.delete(listener);
    listener.apply(target, args);
  };
  onceWeakMap.set(listener, replacement);
  on(target, type, replacement);
}
exports.once = once;

/**
 * Execute each of the listeners in order with the supplied arguments.
 * All the exceptions that are thrown by listeners during the emit
 * are caught and can be handled by listeners of 'error' event. Thrown
 * exceptions are passed as an argument to an 'error' event listener.
 * If no 'error' listener is registered exception will be logged into an
 * error console.
 * @param {Object} target
 *    Event target object.
 * @param {String} type
 *    The type of event.
 * @params {Object|Number|String|Boolean} args
 *    Arguments that will be passed to listeners.
 */
function emit (target, type, ...args) {
  emitOnObject(target, type, target, ...args);
}
exports.emit = emit;

/**
 * A variant of emit that allows setting the this property for event listeners
 */
function emitOnObject(target, type, thisArg, ...args) {
  let allListeners = observers.get(target);
  let listeners = allListeners.get(type);

  // If error event and there are no handlers (explicit or catch-all)
  // then print error message to the console.
  if (type === 'error' && !listeners.size && !allListeners.get('*').size)
    console.exception(args[0]);

  let count = listenerCount;
  for (let [listener, added] of listeners)
    try {
      // Since our contract unfortunately requires that we not dispatch to
      // this event to listeners that were either added or removed during this
      // dispatch, we need to check when each listener was added.
      if (added >= count)
        break;
      listener.apply(thisArg, args);
    }
    catch (error) {
      // If exception is not thrown by a error listener and error listener is
      // registered emit `error` event. Otherwise dump exception to the console.
      if (type !== 'error')
        emitOnObject(target, 'error', target, error);
      else
        console.exception(error);
    }

  // Also emit on `"*"` so that one could listen for all events.
  if (type !== '*' && allListeners.get('*').size)
    emitOnObject(target, '*', target, type, ...args);
}
exports.emitOnObject = emitOnObject;

/**
 * Removes an event `listener` for the given event `type` on the given event
 * `target`. If no `listener` is passed removes all listeners of the given
 * `type`. If `type` is not passed removes all the listeners of the given
 * event `target`.
 * @param {Object} target
 *    The event target object.
 * @param {String} type
 *    The type of event.
 * @param {Function} listener
 *    The listener function that processes the event.
 */
function off(target, type, listener) {
  let length = arguments.length;
  if (length === 3) {
    if (onceWeakMap.has(listener)) {
      observers.get(target).get(type)
               .delete(onceWeakMap.get(listener));
      onceWeakMap.delete(listener);
    }

    observers.get(target).get(type).delete(listener);
  }
  else if (length === 2) {
    observers.get(target).get(type).clear();
    observers.get(target).delete(type);
  }
  else if (length === 1) {
    for (let listeners of observers.get(target).values())
      listeners.clear();
    observers.delete(target);
  }
}
exports.off = off;

/**
 * Returns a number of event listeners registered for the given event `type`
 * on the given event `target`.
 */
function count(target, type) {
  return observers.get(target).get(type).size;
}
exports.count = count;

/**
 * Registers listeners on the given event `target` from the given `listeners`
 * dictionary. Iterates over the listeners and if property name matches name
 * pattern `onEventType` and property is a function, then registers it as
 * an `eventType` listener on `target`.
 *
 * @param {Object} target
 *    The type of event.
 * @param {Object} listeners
 *    Dictionary of listeners.
 */
function setListeners(target, listeners) {
  Object.keys(listeners || {}).forEach(key => {
    let match = EVENT_TYPE_PATTERN.exec(key);
    let type = match && match[1].toLowerCase();
    if (!type)
      return;

    let listener = listeners[key];
    if (typeof(listener) === 'function')
      on(target, type, listener);
  });
}
exports.setListeners = setListeners;
PK
!<o+H

!modules/commonjs/sdk/event/dom.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Ci } = require("chrome");

lazyRequire(this, "./core", "emit");
var { when: unload } = require("../system/unload");
var listeners = new WeakMap();

const { Cu } = require("chrome");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {});

var getWindowFrom = x =>
                    x instanceof Ci.nsIDOMWindow ? x :
                    x instanceof Ci.nsIDOMDocument ? x.defaultView :
                    x instanceof Ci.nsIDOMNode ? x.ownerGlobal :
                    null;

function removeFromListeners() {
  ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners);
  for (let cleaner of listeners.get(this))
    cleaner();

  listeners.delete(this);
}

// Simple utility function takes event target, event type and optional
// `options.capture` and returns node style event stream that emits "data"
// events every time event of that type occurs on the given `target`.
function open(target, type, options) {
  let output = {};
  let capture = options && options.capture ? true : false;
  let listener = (event) => emit(output, "data", event);

  // `open` is currently used only on DOM Window objects, however it was made
  // to be used to any kind of `target` that supports `addEventListener`,
  // therefore is safer get the `window` from the `target` instead assuming
  // that `target` is the `window`.
  let window = getWindowFrom(target);

  // If we're not able to get a `window` from `target`, there is something
  // wrong. We cannot add listeners that can leak later, or results in
  // "dead object" exception.
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833
  if (!window)
    throw new Error("Unable to obtain the owner window from the target given.");

  let cleaners = listeners.get(window);
  if (!cleaners) {
    cleaners = [];
    listeners.set(window, cleaners);

    // We need to remove from our map the `window` once is closed, to prevent
    // memory leak
    ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners);
  }

  cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture));
  ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture);

  return output;
}

unload(() => {
  let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners)
  for (let window of keys)
    removeFromListeners.call(window);
});

exports.open = open;
PK
!<U-
-
$modules/commonjs/sdk/event/target.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "stable"
};

const { on, once, off, setListeners } = require('./core');
const { method, chainable } = require('../lang/functional/core');
const { Class } = require('../core/heritage');

/**
 * `EventTarget` is an exemplar for creating an objects that can be used to
 * add / remove event listeners on them. Events on these objects may be emitted
 * via `emit` function exported by 'event/core' module.
 */
const EventTarget = Class({
  /**
   * Method initializes `this` event source. It goes through properties of a
   * given `options` and registers listeners for the ones that look like an
   * event listeners.
   */
  /**
   * Method initializes `this` event source. It goes through properties of a
   * given `options` and registers listeners for the ones that look like an
   * event listeners.
   */
  initialize: function initialize(options) {
    setListeners(this, options);
  },
  /**
   * Registers an event `listener` that is called every time events of
   * specified `type` are emitted.
   * @param {String} type
   *    The type of event.
   * @param {Function} listener
   *    The listener function that processes the event.
   * @example
   *      worker.on('message', function (data) {
   *        console.log('data received: ' + data)
   *      })
   */
  on: chainable(method(on)),
  /**
   * Registers an event `listener` that is called once the next time an event
   * of the specified `type` is emitted.
   * @param {String} type
   *    The type of the event.
   * @param {Function} listener
   *    The listener function that processes the event.
   */
  once: chainable(method(once)),
  /**
   * Removes an event `listener` for the given event `type`.
   * @param {String} type
   *    The type of event.
   * @param {Function} listener
   *    The listener function that processes the event.
   */
  removeListener: function removeListener(type, listener) {
    // Note: We can't just wrap `off` in `method` as we do it for other methods
    // cause skipping a second or third argument will behave very differently
    // than intended. This way we make sure all arguments are passed and only
    // one listener is removed at most.
    off(this, type, listener);
    return this;
  },
  // but we can wrap `off` here, as the semantics are the same
  off: chainable(method(off))

});
exports.EventTarget = EventTarget;
PK
!<Zr#r##modules/commonjs/sdk/event/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

lazyRequire(this, "./core", "emit", "on", "once", "off", "EVENT_TYPE_PATTERN");
const { Cu } = require("chrome");

// This module provides set of high order function for working with event
// streams (streams in a NodeJS style that dispatch data, end and error
// events).

// Function takes a `target` object and returns set of implicit references
// (non property references) it keeps. This basically allows defining
// references between objects without storing the explicitly. See transform for
// more details.
var refs = (function() {
  let refSets = new WeakMap();
  return function refs(target) {
    if (!refSets.has(target)) refSets.set(target, new Set());
    return refSets.get(target);
  };
})();

function transform(input, f) {
  let output = new Output();

  // Since event listeners don't prevent `input` to be GC-ed we wanna presrve
  // it until `output` can be GC-ed. There for we add implicit reference which
  // is removed once `input` ends.
  refs(output).add(input);

  const next = data => receive(output, data);
  once(output, "start", () => start(input));
  on(input, "error", error => emit(output, "error", error));
  on(input, "end", function() {
    refs(output).delete(input);
    end(output);
  });
  on(input, "data", data => f(data, next));
  return output;
}

// High order event transformation function that takes `input` event channel
// and returns transformation containing only events on which `p` predicate
// returns `true`.
function filter(input, predicate) {
  return transform(input, function(data, next) {
    if (predicate(data))
      next(data);
  });
}
exports.filter = filter;

// High order function that takes `input` and returns input of it's values
// mapped via given `f` function.
const map = (input, f) => transform(input, (data, next) => next(f(data)));
exports.map = map;

// High order function that takes `input` stream of streams and merges them
// into single event stream. Like flatten but time based rather than order
// based.
function merge(inputs) {
  let output = new Output();
  let open = 1;
  let state = [];
  output.state = state;
  refs(output).add(inputs);

  function end(input) {
    open = open - 1;
    refs(output).delete(input);
    if (open === 0) emit(output, "end");
  }
  const error = e => emit(output, "error", e);
  function forward(input) {
    state.push(input);
    open = open + 1;
    on(input, "end", () => end(input));
    on(input, "error", error);
    on(input, "data", data => emit(output, "data", data));
  }

  // If `inputs` is an array treat it as a stream.
  if (Array.isArray(inputs)) {
    inputs.forEach(forward);
    end(inputs);
  }
  else {
    on(inputs, "end", () => end(inputs));
    on(inputs, "error", error);
    on(inputs, "data", forward);
  }

  return output;
}
exports.merge = merge;

const expand = (inputs, f) => merge(map(inputs, f));
exports.expand = expand;

const pipe = (from, to) => on(from, "*", emit.bind(emit, to));
exports.pipe = pipe;


// Shim signal APIs so other modules can be used as is.
const receive = (input, message) => {
  if (input[receive])
    input[receive](input, message);
  else
    emit(input, "data", message);

  // Ideally our input will extend Input and already provide a weak value
  // getter.  If not, opportunistically shim the weak value getter on
  // other types passed as the input.
  if (!("value" in input)) {
    Object.defineProperty(input, "value", WeakValueGetterSetter);
  }
  input.value = message;
};
receive.toString = () => "@@receive";
exports.receive = receive;
exports.send = receive;

const end = input => {
  if (input[end])
    input[end](input);
  else
    emit(input, "end", input);
};
end.toString = () => "@@end";
exports.end = end;

const stop = input => {
  if (input[stop])
    input[stop](input);
  else
    emit(input, "stop", input);
};
stop.toString = () => "@@stop";
exports.stop = stop;

const start = input => {
  if (input[start])
    input[start](input);
  else
    emit(input, "start", input);
};
start.toString = () => "@@start";
exports.start = start;

const lift = (step, ...inputs) => {
  let args = null;
  let opened = inputs.length;
  let started = false;
  const output = new Output();
  const init = () => {
    args = [...inputs.map(input => input.value)];
    output.value = step(...args);
  };

  inputs.forEach((input, index) => {
    on(input, "data", data => {
      args[index] = data;
      receive(output, step(...args));
    });
    on(input, "end", () => {
      opened = opened - 1;
      if (opened <= 0)
        end(output);
    });
  });

  once(output, "start", () => {
    inputs.forEach(start);
    init();
  });

  init();

  return output;
};
exports.lift = lift;

const merges = inputs => {
  let opened = inputs.length;
  let output = new Output();
  output.value = inputs[0].value;
  inputs.forEach((input, index) => {
    on(input, "data", data => receive(output, data));
    on(input, "end", () => {
      opened = opened - 1;
      if (opened <= 0)
        end(output);
    });
  });

  once(output, "start", () => {
    inputs.forEach(start);
    output.value = inputs[0].value;
  });

  return output;
};
exports.merges = merges;

const foldp = (step, initial, input) => {
  let output = map(input, x => step(output.value, x));
  output.value = initial;
  return output;
};
exports.foldp = foldp;

const keepIf = (p, base, input) => {
  let output = filter(input, p);
  output.value = base;
  return output;
};
exports.keepIf = keepIf;

function Input() {}
Input.start = input => emit(input, "start", input);
Input.prototype.start = Input.start;

Input.end = input => {
  emit(input, "end", input);
  stop(input);
};
Input.prototype[end] = Input.end;

// The event channel system caches the last event seen as input.value.
// Unfortunately, if the last event is a DOM object this is a great way
// leak windows.  Mitigate this by storing input.value using a weak
// reference.  This allows the system to work for normal event processing
// while also allowing the objects to be reclaimed.  It means, however,
// input.value cannot be accessed long after the event was dispatched.
const WeakValueGetterSetter = {
  get: function() {
    return this._weakValue ? this._weakValue.get() : this._simpleValue
  },
  set: function(v) {
    if (v && typeof v === "object") {
      try {
        // Try to set a weak reference.  This can throw for some values.
        // For example, if the value is a native object that does not
        // implement nsISupportsWeakReference.
        this._weakValue = Cu.getWeakReference(v)
        this._simpleValue = undefined;
        return;
      } catch (e) {
        // Do nothing.  Fall through to setting _simpleValue below.
      }
    }
    this._simpleValue = v;
    this._weakValue = undefined;
  },
}
Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter);

exports.Input = Input;

// Define an Output type with a weak value getter for the transformation
// functions that produce new channels.
function Output() { }
Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter);
exports.Output = Output;

const $source = "@@source";
const $outputs = "@@outputs";
exports.outputs = $outputs;

// NOTE: Passing DOM objects through a Reactor can cause them to leak
// when they get cached in this.value.  We cannot use a weak reference
// in this case because the Reactor design expects to always have both the
// past and present value.  If we allow past values to be collected the
// system breaks.

function Reactor(options={}) {
  const {onStep, onStart, onEnd} = options;
  if (onStep)
    this.onStep = onStep;
  if (onStart)
    this.onStart = onStart;
  if (onEnd)
    this.onEnd = onEnd;
}
Reactor.prototype.onStep = _ => void(0);
Reactor.prototype.onStart = _ => void(0);
Reactor.prototype.onEnd = _ => void(0);
Reactor.prototype.onNext = function(present, past) {
  this.value = present;
  this.onStep(present, past);
};
Reactor.prototype.run = function(input) {
  on(input, "data", message => this.onNext(message, input.value));
  on(input, "end", () => this.onEnd(input.value));
  start(input);
  this.value = input.value;
  this.onStart(input.value);
};
exports.Reactor = Reactor;

/**
 * Takes an object used as options with potential keys like 'onMessage',
 * used to be called `require('sdk/event/core').setListeners` on.
 * This strips all keys that would trigger a listener to be set.
 *
 * @params {Object} object
 * @return {Object}
 */

function stripListeners (object) {
  return Object.keys(object || {}).reduce((agg, key) => {
    if (!EVENT_TYPE_PATTERN.test(key))
      agg[key] = object[key];
    return agg;
  }, {});
}
exports.stripListeners = stripListeners;

const when = (target, type) => new Promise(resolve => {
  once(target, type, resolve);
});
exports.when = when;
PK
!<gT
T
*modules/commonjs/sdk/frame/hidden-frame.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci } = require("chrome");
const { Class } = require("../core/heritage");
const { List, addListItem, removeListItem } = require("../util/list");
const { EventTarget } = require("../event/target");
lazyRequire(this, "../event/core", "emit");
lazyRequire(this, "./utils", { "create": "makeFrame" });
lazyRequire(this, "../core/promise", "defer");
const { when: unload } = require("../system/unload");
lazyRequire(this, "../deprecated/api-utils", "validateOptions", "getTypeOf");
lazyRequire(this, "../addon/window", "window");
lazyRequire(this, "../util/array", "fromIterator");

// This cache is used to access friend properties between functions
// without exposing them on the public API.
var cache = new Set();
var elements = new WeakMap();

function contentLoaded(target) {
  var deferred = defer();
  target.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
    // "DOMContentLoaded" events from nested frames propagate up to target,
    // ignore events unless it's DOMContentLoaded for the given target.
    if (event.target === target || event.target === target.contentDocument) {
      target.removeEventListener("DOMContentLoaded", DOMContentLoaded);
      deferred.resolve(target);
    }
  });
  return deferred.promise;
}

function FrameOptions(options) {
  options = options || {}
  return validateOptions(options, FrameOptions.validator);
}
FrameOptions.validator = {
  onReady: {
    is: ["undefined", "function", "array"],
    ok: function(v) {
      if (getTypeOf(v) === "array") {
        // make sure every item is a function
        return v.every(item => typeof(item) === "function")
      }
      return true;
    }
  },
  onUnload: {
    is: ["undefined", "function"]
  }
};

var HiddenFrame = Class({
  extends: EventTarget,
  initialize: function initialize(options) {
    options = FrameOptions(options);
    EventTarget.prototype.initialize.call(this, options);
  },
  get element() {
    return elements.get(this);
  },
  toString: function toString() {
    return "[object Frame]"
  }
});
exports.HiddenFrame = HiddenFrame

function addHidenFrame(frame) {
  if (!(frame instanceof HiddenFrame))
    throw Error("The object to be added must be a HiddenFrame.");

  // This instance was already added.
  if (cache.has(frame)) return frame;
  else cache.add(frame);

  let element = makeFrame(window.document, {
    nodeName: "iframe",
    type: "content",
    allowJavascript: true,
    allowPlugins: true,
    allowAuth: true,
  });
  elements.set(frame, element);

  contentLoaded(element).then(function onFrameReady(element) {
    emit(frame, "ready");
  }, console.exception);

  return frame;
}
exports.add = addHidenFrame

function removeHiddenFrame(frame) {
  if (!(frame instanceof HiddenFrame))
    throw Error("The object to be removed must be a HiddenFrame.");

  if (!cache.has(frame)) return;

  // Remove from cache before calling in order to avoid loop
  cache.delete(frame);
  emit(frame, "unload")
  let element = frame.element
  if (element) element.remove()
}
exports.remove = removeHiddenFrame;

unload(() => fromIterator(cache).forEach(removeHiddenFrame));
PK
!<QX'

#modules/commonjs/sdk/frame/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "experimental"
};

const { Ci } = require("chrome");
const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';

function eventTarget(frame) {
  return getDocShell(frame).chromeEventHandler;
}
exports.eventTarget = eventTarget;

function getDocShell(frame) {
  let { frameLoader } = frame.QueryInterface(Ci.nsIFrameLoaderOwner);
  return frameLoader && frameLoader.docShell;
}
exports.getDocShell = getDocShell;

/**
 * Creates a XUL `browser` element in a privileged document.
 * @params {nsIDOMDocument} document
 * @params {String} options.type
 *    By default is 'content' for possible values see:
 *    https://developer.mozilla.org/en/XUL/iframe#a-browser.type
 * @params {String} options.uri
 *    URI of the document to be loaded into created frame.
 * @params {Boolean} options.remote
 *    If `true` separate process will be used for this frame, also in such
 *    case all the following options are ignored.
 * @params {Boolean} options.allowAuth
 *    Whether to allow auth dialogs. Defaults to `false`.
 * @params {Boolean} options.allowJavascript
 *    Whether to allow Javascript execution. Defaults to `false`.
 * @params {Boolean} options.allowPlugins
 *    Whether to allow plugin execution. Defaults to `false`.
 */
function create(target, options) {
  target = target instanceof Ci.nsIDOMDocument ? target.documentElement :
           target instanceof Ci.nsIDOMWindow ? target.document.documentElement :
           target;
  options = options || {};
  let remote = options.remote || false;
  let namespaceURI = options.namespaceURI || XUL;
  let isXUL = namespaceURI === XUL;
  let nodeName = isXUL && options.browser ? 'browser' : 'iframe';
  let document = target.ownerDocument;

  let frame = document.createElementNS(namespaceURI, nodeName);
  // Type="content" is mandatory to enable stuff here:
  // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
  frame.setAttribute('type', options.type || 'content');
  frame.setAttribute('src', options.uri || 'about:blank');

  // Must set the remote attribute before attaching the frame to the document
  if (remote && isXUL) {
    // We remove XBL binding to avoid execution of code that is not going to
    // work because browser has no docShell attribute in remote mode
    // (for example)
    frame.setAttribute('style', '-moz-binding: none;');
    frame.setAttribute('remote', 'true');
  }

  target.appendChild(frame);

  // Load in separate process if `options.remote` is `true`.
  // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
  if (remote && !isXUL) {
    frame.QueryInterface(Ci.nsIMozBrowserFrame);
    frame.createRemoteFrameLoader(null);
  }

  // If browser is remote it won't have a `docShell`.
  if (!remote) {
    let docShell = getDocShell(frame);
    docShell.allowAuth = options.allowAuth || false;
    docShell.allowJavascript = options.allowJavascript || false;
    docShell.allowPlugins = options.allowPlugins || false;
    docShell.allowWindowControl = options.allowWindowControl || false;
  }

  return frame;
}
exports.create = create;

function swapFrameLoaders(from, to) {
  return from.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(to);
}
exports.swapFrameLoaders = swapFrameLoaders;
PK
!<Rl99modules/commonjs/sdk/fs/path.js// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

// Adapted version of:
// https://github.com/joyent/node/blob/v0.11.3/lib/path.js

// Shim process global from node.
var process = Object.create(require('../system'));
process.cwd = process.pathFor.bind(process, 'CurProcD');

// Update original check in node `process.platform === 'win32'` since in SDK it's `winnt`.
var isWindows = process.platform.indexOf('win') === 0;



// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
  // if the path tries to go above the root, `up` ends up > 0
  var up = 0;
  for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last === '.') {
      parts.splice(i, 1);
    } else if (last === '..') {
      parts.splice(i, 1);
      up++;
    } else if (up) {
      parts.splice(i, 1);
      up--;
    }
  }

  // if the path is allowed to go above the root, restore leading ..s
  if (allowAboveRoot) {
    for (; up--; up) {
      parts.unshift('..');
    }
  }

  return parts;
}


if (isWindows) {
  // Regex to split a windows path into three parts: [*, device, slash,
  // tail] windows-only
  var splitDeviceRe =
      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;

  // Regex to split the tail part of the above into [*, dir, basename, ext]
  var splitTailRe =
      /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;

  // Function to split a filename into [root, dir, basename, ext]
  // windows version
  var splitPath = function(filename) {
    // Separate device+slash from tail
    var result = splitDeviceRe.exec(filename),
        device = (result[1] || '') + (result[2] || ''),
        tail = result[3] || '';
    // Split the tail into dir, basename and extension
    var result2 = splitTailRe.exec(tail),
        dir = result2[1],
        basename = result2[2],
        ext = result2[3];
    return [device, dir, basename, ext];
  };

  var normalizeUNCRoot = function(device) {
    return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
  };

  // path.resolve([from ...], to)
  // windows version
  exports.resolve = function() {
    var resolvedDevice = '',
        resolvedTail = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1; i--) {
      var path;
      if (i >= 0) {
        path = arguments[i];
      } else if (!resolvedDevice) {
        path = process.cwd();
      } else {
        // Windows has the concept of drive-specific current working
        // directories. If we've resolved a drive letter but not yet an
        // absolute path, get cwd for that drive. We're sure the device is not
        // an unc path at this points, because unc paths are always absolute.
        path = process.env['=' + resolvedDevice];
        // Verify that a drive-local cwd was found and that it actually points
        // to our drive. If not, default to the drive's root.
        if (!path || path.substr(0, 3).toLowerCase() !==
            resolvedDevice.toLowerCase() + '\\') {
          path = resolvedDevice + '\\';
        }
      }

      // Skip empty and invalid entries
      if (typeof path !== 'string') {
        throw new TypeError('Arguments to path.resolve must be strings');
      } else if (!path) {
        continue;
      }

      var result = splitDeviceRe.exec(path),
          device = result[1] || '',
          isUnc = device && device.charAt(1) !== ':',
          isAbsolute = exports.isAbsolute(path),
          tail = result[3];

      if (device &&
          resolvedDevice &&
          device.toLowerCase() !== resolvedDevice.toLowerCase()) {
        // This path points to another device so it is not applicable
        continue;
      }

      if (!resolvedDevice) {
        resolvedDevice = device;
      }
      if (!resolvedAbsolute) {
        resolvedTail = tail + '\\' + resolvedTail;
        resolvedAbsolute = isAbsolute;
      }

      if (resolvedDevice && resolvedAbsolute) {
        break;
      }
    }

    // Convert slashes to backslashes when `resolvedDevice` points to an UNC
    // root. Also squash multiple slashes into a single one where appropriate.
    if (isUnc) {
      resolvedDevice = normalizeUNCRoot(resolvedDevice);
    }

    // At this point the path should be resolved to a full absolute path,
    // but handle relative paths to be safe (might happen when process.cwd()
    // fails)

    // Normalize the tail path

    function f(p) {
      return !!p;
    }

    resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
                                  !resolvedAbsolute).join('\\');

    return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
           '.';
  };

  // windows version
  exports.normalize = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = device && device.charAt(1) !== ':',
        isAbsolute = exports.isAbsolute(path),
        tail = result[3],
        trailingSlash = /[\\\/]$/.test(tail);

    // If device is a drive letter, we'll normalize to lower case.
    if (device && device.charAt(1) === ':') {
      device = device[0].toLowerCase() + device.substr(1);
    }

    // Normalize the tail path
    tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
      return !!p;
    }), !isAbsolute).join('\\');

    if (!tail && !isAbsolute) {
      tail = '.';
    }
    if (tail && trailingSlash) {
      tail += '\\';
    }

    // Convert slashes to backslashes when `device` points to an UNC root.
    // Also squash multiple slashes into a single one where appropriate.
    if (isUnc) {
      device = normalizeUNCRoot(device);
    }

    return device + (isAbsolute ? '\\' : '') + tail;
  };

  // windows version
  exports.isAbsolute = function(path) {
    var result = splitDeviceRe.exec(path),
        device = result[1] || '',
        isUnc = device && device.charAt(1) !== ':';
    // UNC paths are always absolute
    return !!result[2] || isUnc;
  };

  // windows version
  exports.join = function() {
    function f(p) {
      if (typeof p !== 'string') {
        throw new TypeError('Arguments to path.join must be strings');
      }
      return p;
    }

    var paths = Array.prototype.filter.call(arguments, f);
    var joined = paths.join('\\');

    // Make sure that the joined path doesn't start with two slashes, because
    // normalize() will mistake it for an UNC path then.
    //
    // This step is skipped when it is very clear that the user actually
    // intended to point at an UNC path. This is assumed when the first
    // non-empty string arguments starts with exactly two slashes followed by
    // at least one more non-slash character.
    //
    // Note that for normalize() to treat a path as an UNC path it needs to
    // have at least 2 components, so we don't filter for that here.
    // This means that the user can use join to construct UNC paths from
    // a server name and a share name; for example:
    //   path.join('//server', 'share') -> '\\\\server\\share\')
    if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
      joined = joined.replace(/^[\\\/]{2,}/, '\\');
    }

    return exports.normalize(joined);
  };

  // path.relative(from, to)
  // it will solve the relative path from 'from' to 'to', for instance:
  // from = 'C:\\orandea\\test\\aaa'
  // to = 'C:\\orandea\\impl\\bbb'
  // The output of the function should be: '..\\..\\impl\\bbb'
  // windows version
  exports.relative = function(from, to) {
    from = exports.resolve(from);
    to = exports.resolve(to);

    // windows is not case sensitive
    var lowerFrom = from.toLowerCase();
    var lowerTo = to.toLowerCase();

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var toParts = trim(to.split('\\'));

    var lowerFromParts = trim(lowerFrom.split('\\'));
    var lowerToParts = trim(lowerTo.split('\\'));

    var length = Math.min(lowerFromParts.length, lowerToParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (lowerFromParts[i] !== lowerToParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    if (samePartsLength == 0) {
      return to;
    }

    var outputParts = [];
    for (var i = samePartsLength; i < lowerFromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('\\');
  };

  exports.sep = '\\';
  exports.delimiter = ';';

} else /* posix */ {

  // Split a filename into [root, dir, basename, ext], unix version
  // 'root' is just a slash, or nothing.
  var splitPathRe =
      /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
  var splitPath = function(filename) {
    return splitPathRe.exec(filename).slice(1);
  };

  // path.resolve([from ...], to)
  // posix version
  exports.resolve = function() {
    var resolvedPath = '',
        resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
      var path = (i >= 0) ? arguments[i] : process.cwd();

      // Skip empty and invalid entries
      if (typeof path !== 'string') {
        throw new TypeError('Arguments to path.resolve must be strings');
      } else if (!path) {
        continue;
      }

      resolvedPath = path + '/' + resolvedPath;
      resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
      return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
  };

  // path.normalize(path)
  // posix version
  exports.normalize = function(path) {
    var isAbsolute = exports.isAbsolute(path),
        trailingSlash = path.substr(-1) === '/';

    // Normalize the path
    path = normalizeArray(path.split('/').filter(function(p) {
      return !!p;
    }), !isAbsolute).join('/');

    if (!path && !isAbsolute) {
      path = '.';
    }
    if (path && trailingSlash) {
      path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
  };

  // posix version
  exports.isAbsolute = function(path) {
    return path.charAt(0) === '/';
  };

  // posix version
  exports.join = function() {
    var paths = Array.prototype.slice.call(arguments, 0);
    return exports.normalize(paths.filter(function(p, index) {
      if (typeof p !== 'string') {
        throw new TypeError('Arguments to path.join must be strings');
      }
      return p;
    }).join('/'));
  };


  // path.relative(from, to)
  // posix version
  exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
      var start = 0;
      for (; start < arr.length; start++) {
        if (arr[start] !== '') break;
      }

      var end = arr.length - 1;
      for (; end >= 0; end--) {
        if (arr[end] !== '') break;
      }

      if (start > end) return [];
      return arr.slice(start, end - start + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
      if (fromParts[i] !== toParts[i]) {
        samePartsLength = i;
        break;
      }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
      outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
  };

  exports.sep = '/';
  exports.delimiter = ':';
}

exports.dirname = function(path) {
  var result = splitPath(path),
      root = result[0],
      dir = result[1];

  if (!root && !dir) {
    // No dirname whatsoever
    return '.';
  }

  if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
  }

  return root + dir;
};


exports.basename = function(path, ext) {
  var f = splitPath(path)[2];
  // TODO: make this comparison case-insensitive on windows?
  if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
  }
  return f;
};


exports.extname = function(path) {
  return splitPath(path)[3];
};

if (isWindows) {
  exports._makeLong = function(path) {
    // Note: this will *probably* throw somewhere.
    if (typeof path !== 'string')
      return path;

    if (!path) {
      return '';
    }

    var resolvedPath = exports.resolve(path);

    if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
      // path is local filesystem path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\' + resolvedPath;
    } else if (/^\\\\[^?.]/.test(resolvedPath)) {
      // path is network UNC path, which needs to be converted
      // to long UNC path.
      return '\\\\?\\UNC\\' + resolvedPath.substring(2);
    }

    return path;
  };
} else {
  exports._makeLong = function(path) {
    return path;
  };
}PK
!<^lmodules/commonjs/sdk/hotkeys.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable"
};

const INVALID_HOTKEY = "Hotkey must have at least one modifier.";

const { toJSON: jsonify, toString: stringify,
        isFunctionKey } = require("./keyboard/utils");
const { register, unregister } = require("./keyboard/hotkeys");

const Hotkey = exports.Hotkey = function Hotkey(options) {
  if (!(this instanceof Hotkey))
    return new Hotkey(options);

  // Parsing key combination string.
  let hotkey = jsonify(options.combo);
  if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) {
    throw new TypeError(INVALID_HOTKEY);
  }

  this.onPress = options.onPress && options.onPress.bind(this);
  this.toString = stringify.bind(null, hotkey);
  // Registering listener on keyboard combination enclosed by this hotkey.
  // Please note that `this.toString()` is a normalized version of
  // `options.combination` where order of modifiers is sorted and `accel` is
  // replaced with platform specific key.
  register(this.toString(), this.onPress);
  // We freeze instance before returning it in order to make it's properties
  // read-only.
  return Object.freeze(this);
};
Hotkey.prototype.destroy = function destroy() {
  unregister(this.toString(), this.onPress);
};
PK
!<<"modules/commonjs/sdk/indexed-db.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci } = require("chrome");
const { id } = require("./self");

// placeholder, copied from bootstrap.js
var sanitizeId = function(id){
  let uuidRe =
    /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;

  let domain = id.
    toLowerCase().
    replace(/@/g, "-at-").
    replace(/\./g, "-dot-").
    replace(uuidRe, "$1");

  return domain
};

const PSEUDOURI = "indexeddb://" + sanitizeId(id) // https://bugzilla.mozilla.org/show_bug.cgi?id=779197

// Use XPCOM because `require("./url").URL` doesn't expose the raw uri object.
var principaluri = Cc["@mozilla.org/network/io-service;1"]
                     .getService(Ci.nsIIOService).newURI(PSEUDOURI);

var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
            .getService(Ci.nsIScriptSecurityManager);
var principal = ssm.createCodebasePrincipal(principaluri, {});

function toArray(args) {
  return Array.prototype.slice.call(args);
}

function openInternal(args, forPrincipal, deleting) {
  if (forPrincipal) {
    args = toArray(args);
  } else {
    args = [principal].concat(toArray(args));
  }
  if (args.length == 2) {
    args.push({ storage: "persistent" });
  } else if (!deleting && args.length >= 3 && typeof args[2] === "number") {
    args[2] = { version: args[2], storage: "persistent" };
  }

  if (deleting) {
    return indexedDB.deleteForPrincipal.apply(indexedDB, args);
  }

  return indexedDB.openForPrincipal.apply(indexedDB, args);
}

exports.indexedDB = Object.freeze({
  open: function () {
    return openInternal(arguments, false, false);
  },
  deleteDatabase: function () {
    return openInternal(arguments, false, true);
  },
  openForPrincipal: function () {
    return openInternal(arguments, true, false);
  },
  deleteForPrincipal: function () {
    return openInternal(arguments, true, true);
  },
  cmp: indexedDB.cmp.bind(indexedDB)
});

exports.IDBKeyRange = IDBKeyRange;
exports.DOMException = Ci.nsIDOMDOMException;
PK
!<l%modules/commonjs/sdk/input/browser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { windows, isBrowser, isInteractive, isDocumentLoaded,
        getOuterId } = require("../window/utils");
const { InputPort } = require("./system");
const { lift, merges, foldp, keepIf, start, Input } = require("../event/utils");
const { patch } = require("diffpatcher/index");
const { Sequence, seq, filter, object, pairs } = require("../util/sequence");


// Create lazy iterators from the regular arrays, although
// once https://github.com/mozilla/addon-sdk/pull/1314 lands
// `windows` will be transforme to lazy iterators.
// When iterated over belowe sequences items will represent
// state of windows at the time of iteration.
const opened = seq(function*() {
  const items = windows("navigator:browser", {includePrivate: true});
  for (let item of items) {
      yield [getOuterId(item), item];
  }
});
const interactive = filter(([_, window]) => isInteractive(window), opened);
const loaded = filter(([_, window]) => isDocumentLoaded(window), opened);

// Helper function that converts given argument to a delta.
const Update = window => window && object([getOuterId(window), window]);
const Delete = window => window && object([getOuterId(window), null]);


// Signal represents delta for last top level window close.
const LastClosed = lift(Delete,
                        keepIf(isBrowser, null,
                               new InputPort({topic: "domwindowclosed"})));
exports.LastClosed = LastClosed;

const windowFor = document => document && document.defaultView;

// Signal represent delta for last top level window document becoming interactive.
const InteractiveDoc = new InputPort({topic: "chrome-document-interactive"});
const InteractiveWin = lift(windowFor, InteractiveDoc);
const LastInteractive = lift(Update, keepIf(isBrowser, null, InteractiveWin));
exports.LastInteractive = LastInteractive;

// Signal represent delta for last top level window loaded.
const LoadedDoc = new InputPort({topic: "chrome-document-loaded"});
const LoadedWin = lift(windowFor, LoadedDoc);
const LastLoaded = lift(Update, keepIf(isBrowser, null, LoadedWin));
exports.LastLoaded = LastLoaded;


const initialize = input => {
  if (!input.initialized) {
    input.value = object(...input.value);
    Input.start(input);
    input.initialized = true;
  }
};

// Signal represents set of top level interactive windows, updated any
// time new window becomes interactive or one get's closed.
const Interactive = foldp(patch, interactive, merges([LastInteractive,
                                                      LastClosed]));
Interactive[start] = initialize;
exports.Interactive = Interactive;

// Signal represents set of top level loaded window, updated any time
// new window becomes interactive or one get's closed.
const Loaded = foldp(patch, loaded, merges([LastLoaded, LastClosed]));
Loaded[start] = initialize;
exports.Loaded = Loaded;
PK
!<.i-modules/commonjs/sdk/input/customizable-ui.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cu } = require("chrome");
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { receive } = require("../event/utils");
const { InputPort } = require("./system");
const { object} = require("../util/sequence");
const { getOuterId } = require("../window/utils");

const Input = function() {};
Input.prototype = Object.create(InputPort.prototype);

Input.prototype.onCustomizeStart = function (window) {
  receive(this, object([getOuterId(window), true]));
}

Input.prototype.onCustomizeEnd = function (window) {
  receive(this, object([getOuterId(window), null]));
}

Input.prototype.addListener = input => CustomizableUI.addListener(input);

Input.prototype.removeListener = input => CustomizableUI.removeListener(input);

exports.CustomizationInput = Input;
PK
!< km

#modules/commonjs/sdk/input/frame.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Ci } = require("chrome");
const { InputPort } = require("./system");
const { getFrameElement, getOuterId,
        getOwnerBrowserWindow } = require("../window/utils");
const { isnt } = require("../lang/functional");
const { foldp, lift, merges, keepIf } = require("../event/utils");
const { object } = require("../util/sequence");
const { compose } = require("../lang/functional");
const { LastClosed } = require("./browser");
const { patch } = require("diffpatcher/index");

const Document = Ci.nsIDOMDocument;

const isntNull = isnt(null);

const frameID = frame => frame.id;
const browserID = compose(getOuterId, getOwnerBrowserWindow);

const isInnerFrame = frame =>
  frame && frame.hasAttribute("data-is-sdk-inner-frame");

// Utility function that given content window loaded in our frame views returns
// an actual frame. This basically takes care of fact that actual frame document
// is loaded in the nested iframe. If content window is not loaded in the nested
// frame of the frame view it returs null.
const getFrame = document =>
  document && document.defaultView && getFrameElement(document.defaultView);

const FrameInput = function(options) {
  const input = keepIf(isInnerFrame, null,
                       lift(getFrame, new InputPort(options)));
  return lift(frame => {
    if (!frame) return frame;
    const [id, owner] = [frameID(frame), browserID(frame)];
    return object([id, {owners: object([owner, options.update])}]);
  }, input);
};

const LastLoading = new FrameInput({topic: "document-element-inserted",
                                    update: {readyState: "loading"}});
exports.LastLoading = LastLoading;

const LastInteractive = new FrameInput({topic: "content-document-interactive",
                                        update: {readyState: "interactive"}});
exports.LastInteractive = LastInteractive;

const LastLoaded = new FrameInput({topic: "content-document-loaded",
                                   update: {readyState: "complete"}});
exports.LastLoaded = LastLoaded;

const LastUnloaded = new FrameInput({topic: "content-page-hidden",
                                    update: null});
exports.LastUnloaded = LastUnloaded;

// Represents state of SDK frames in form of data structure:
// {"frame#1": {"id": "frame#1",
//              "inbox": {"data": "ping",
//                        "target": {"id": "frame#1", "owner": "outerWindowID#2"},
//                        "source": {"id": "frame#1"}}
//              "url": "resource://addon-1/data/index.html",
//              "owners": {"outerWindowID#1": {"readyState": "loading"},
//                         "outerWindowID#2": {"readyState": "complete"}}
//
//
//  frame#2: {"id": "frame#2",
//            "url": "resource://addon-1/data/main.html",
//            "outbox": {"data": "pong",
//                       "source": {"id": "frame#2", "owner": "outerWindowID#1"}
//                       "target": {"id": "frame#2"}}
//            "owners": {outerWindowID#1: {readyState: "interacitve"}}}}
const Frames = foldp(patch, {}, merges([
  LastLoading,
  LastInteractive,
  LastLoaded,
  LastUnloaded,
  new InputPort({ id: "frame-mailbox" }),
  new InputPort({ id: "frame-change" }),
  new InputPort({ id: "frame-changed" })
]));
exports.Frames = Frames;
PK
!<V$modules/commonjs/sdk/input/system.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cc, Ci, Cr, Cu } = require("chrome");
const { Input, start, stop, end, receive, outputs } = require("../event/utils");
const { once, off } = require("../event/core");
const { id: addonID } = require("../self");

const unloadMessage = require("@loader/unload");
const observerService = Cc['@mozilla.org/observer-service;1'].
                          getService(Ci.nsIObserverService);
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");


const addonUnloadTopic = "sdk:loader:destroy";

const isXrayWrapper = Cu.isXrayWrapper;
// In the past SDK used to double-wrap notifications dispatched, which
// made them awkward to use outside of SDK. At present they no longer
// do that, although we still supported for legacy reasons.
const isLegacyWrapper = x =>
    x && x.wrappedJSObject &&
    "observersModuleSubjectWrapper" in x.wrappedJSObject;

const unwrapLegacy = x => x.wrappedJSObject.object;

// `InputPort` provides a way to create a signal out of the observer
// notification subject's for the given `topic`. If `options.initial`
// is provided it is used as initial value otherwise `null` is used.
// Constructor can be given `options.id` that will be used to create
// a `topic` which is namespaced to an add-on (this avoids conflicts
// when multiple add-on are used, although in a future host probably
// should just be shared across add-ons). It is also possible to
// specify a specific `topic` via `options.topic` which is used as
// without namespacing. Created signal ends whenever add-on is
// unloaded.
const InputPort = function InputPort({id, topic, initial}) {
  this.id = id || topic;
  this.topic = topic || "sdk:" + addonID + ":" + id;
  this.value = initial === void(0) ? null : initial;
  this.observing = false;
  this[outputs] = [];
};

// InputPort type implements `Input` signal interface.
InputPort.prototype = new Input();
InputPort.prototype.constructor = InputPort;

// When port is started (which is when it's subgraph get's
// first subscriber) actual observer is registered.
InputPort.start = input => {
  input.addListener(input);
  // Also register add-on unload observer to end this signal
  // when that happens.
  addObserver(input, addonUnloadTopic, false);
};
InputPort.prototype[start] = InputPort.start;

InputPort.addListener = input => addObserver(input, input.topic, false);
InputPort.prototype.addListener = InputPort.addListener;

// When port is stopped (which is when it's subgraph has no
// no subcribers left) an actual observer unregistered.
// Note that port stopped once it ends as well (which is when
// add-on is unloaded).
InputPort.stop = input => {
  input.removeListener(input);
  removeObserver(input, addonUnloadTopic);
};
InputPort.prototype[stop] = InputPort.stop;

InputPort.removeListener = input => removeObserver(input, input.topic);
InputPort.prototype.removeListener = InputPort.removeListener;

// `InputPort` also implements `nsIObserver` interface and
// `nsISupportsWeakReference` interfaces as it's going to be used as such.
InputPort.prototype.QueryInterface = function(iid) {
  if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
    throw Cr.NS_ERROR_NO_INTERFACE;

  return this;
};

// `InputPort` instances implement `observe` method, which is invoked when
// observer notifications are dispatched. The `subject` of that notification
// are received on this signal.
InputPort.prototype.observe = function(subject, topic, data) {
  // Unwrap message from the subject. SDK used to have it's own version of
  // wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
  // and it's not an XrayWrapper use it as message. Otherwise use subject as
  // is.
  const message = subject === null ? null :
        isLegacyWrapper(subject) ? unwrapLegacy(subject) :
        isXrayWrapper(subject) ? subject :
        subject.wrappedJSObject ? subject.wrappedJSObject :
        subject;

  // If observer topic matches topic of the input port receive a message.
  if (topic === this.topic) {
    receive(this, message);
  }

  // If observe topic is add-on unload topic we create an end message.
  if (topic === addonUnloadTopic && message === unloadMessage) {
    end(this);
  }
};

exports.InputPort = InputPort;
PK
!<**!modules/commonjs/sdk/io/buffer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'experimental'
};

/*
 * Encodings supported by TextEncoder/Decoder:
 * utf-8, utf-16le, utf-16be
 * http://encoding.spec.whatwg.org/#interface-textencoder
 *
 * Node however supports the following encodings:
 * ascii, utf-8, utf-16le, usc2, base64, hex
 */

const { Cu } = require('chrome');
lazyRequire(this, 'sdk/lang/type', "isNumber");
Cu.importGlobalProperties(["TextEncoder", "TextDecoder"]);

exports.TextEncoder = TextEncoder;
exports.TextDecoder = TextDecoder;

/**
 * Use WeakMaps to work around Bug 929146, which prevents us from adding
 * getters or values to typed arrays
 * https://bugzilla.mozilla.org/show_bug.cgi?id=929146
 */
const parents = new WeakMap();
const views = new WeakMap();

function Buffer(subject, encoding /*, bufferLength */) {

  // Allow invocation without `new` constructor
  if (!(this instanceof Buffer))
    return new Buffer(subject, encoding, arguments[2]);

  var type = typeof(subject);

  switch (type) {
    case 'number':
      // Create typed array of the given size if number.
      try {
        let buffer = new Uint8Array(subject > 0 ? Math.floor(subject) : 0);
        return buffer;
      } catch (e) {
        if (/invalid array length/.test(e.message) ||
            /invalid arguments/.test(e.message))
          throw new RangeError('Could not instantiate buffer: size of buffer may be too large');
        else
          throw new Error('Could not instantiate buffer');
      }
      break;
    case 'string':
      // If string encode it and use buffer for the returned Uint8Array
      // to create a local patched version that acts like node buffer.
      encoding = encoding || 'utf8';
      return new Uint8Array(new TextEncoder(encoding).encode(subject).buffer);
    case 'object':
      // This form of the constructor uses the form of
      // new Uint8Array(buffer, offset, length);
      // So we can instantiate a typed array within the constructor
      // to inherit the appropriate properties, where both the
      // `subject` and newly instantiated buffer share the same underlying
      // data structure.
      if (arguments.length === 3)
        return new Uint8Array(subject, encoding, arguments[2]);
      // If array or alike just make a copy with a local patched prototype.
      else
        return new Uint8Array(subject);
    default:
      throw new TypeError('must start with number, buffer, array or string');
  }
}
exports.Buffer = Buffer;

// Tests if `value` is a Buffer.
Buffer.isBuffer = value => value instanceof Buffer

// Returns true if the encoding is a valid encoding argument & false otherwise
Buffer.isEncoding = function (encoding) {
  if (!encoding) return false;
  try {
    new TextDecoder(encoding);
  } catch(e) {
    return false;
  }
  return true;
}

// Gives the actual byte length of a string. encoding defaults to 'utf8'.
// This is not the same as String.prototype.length since that returns the
// number of characters in a string.
Buffer.byteLength = (value, encoding = 'utf8') =>
  new TextEncoder(encoding).encode(value).byteLength

// Direct copy of the nodejs's buffer implementation:
// https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
Buffer.concat = function(list, length) {
  if (!Array.isArray(list))
    throw new TypeError('Usage: Buffer.concat(list[, length])');

  if (typeof length === 'undefined') {
    length = 0;
    for (var i = 0; i < list.length; i++)
      length += list[i].length;
  } else {
    length = ~~length;
  }

  if (length < 0)
    length = 0;

  if (list.length === 0)
    return new Buffer(0);
  else if (list.length === 1)
    return list[0];

  if (length < 0)
    throw new RangeError('length is not a positive number');

  var buffer = new Buffer(length);
  var pos = 0;
  for (var i = 0; i < list.length; i++) {
    var buf = list[i];
    buf.copy(buffer, pos);
    pos += buf.length;
  }

  return buffer;
};

// Node buffer is very much like Uint8Array although it has bunch of methods
// that typically can be used in combination with `DataView` while preserving
// access by index. Since in SDK each module has it's own set of bult-ins it
// ok to patch ours to make it nodejs Buffer compatible.
const Uint8ArraySet = Uint8Array.prototype.set
Buffer.prototype = Uint8Array.prototype;
Object.defineProperties(Buffer.prototype, {
  parent: {
    get: function() { return parents.get(this, undefined); }
  },
  view: {
    get: function () {
      let view = views.get(this, undefined);
      if (view) return view;
      view = new DataView(this.buffer);
      views.set(this, view);
      return view;
    }
  },
  toString: {
    value: function(encoding, start, end) {
      encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';
      start = Math.max(0, ~~start);
      end = Math.min(this.length, end === void(0) ? this.length : ~~end);
      return new TextDecoder(encoding).decode(this.subarray(start, end));
    }
  },
  toJSON: {
    value: function() {
      return { type: 'Buffer', data: Array.slice(this, 0) };
    }
  },
  get: {
    value: function(offset) {
      return this[offset];
    }
  },
  set: {
    value: function(offset, value) { this[offset] = value; }
  },
  copy: {
    value: function(target, offset, start, end) {
      let length = this.length;
      let targetLength = target.length;
      offset = isNumber(offset) ? offset : 0;
      start = isNumber(start) ? start : 0;

      if (start < 0)
        throw new RangeError('sourceStart is outside of valid range');
      if (end < 0)
        throw new RangeError('sourceEnd is outside of valid range');

      // If sourceStart > sourceEnd, or targetStart > targetLength,
      // zero bytes copied
      if (start > end ||
          offset > targetLength
          )
        return 0;

      // If `end` is not defined, or if it is defined
      // but would overflow `target`, redefine `end`
      // so we can copy as much as we can
      if (end - start > targetLength - offset ||
          end == null) {
        let remainingTarget = targetLength - offset;
        let remainingSource = length - start;
        if (remainingSource <= remainingTarget)
          end = length;
        else
          end = start + remainingTarget;
      }

      Uint8ArraySet.call(target, this.subarray(start, end), offset);
      return end - start;
    }
  },
  slice: {
    value: function(start, end) {
      let length = this.length;
      start = ~~start;
      end = end != null ? end : length;

      if (start < 0) {
        start += length;
        if (start < 0) start = 0;
      } else if (start > length)
        start = length;

      if (end < 0) {
        end += length;
        if (end < 0) end = 0;
      } else if (end > length)
        end = length;

      if (end < start)
        end = start;

      // This instantiation uses the new Uint8Array(buffer, offset, length) version
      // of construction to share the same underling data structure
      let buffer = new Buffer(this.buffer, start, end - start);

      // If buffer has a value, assign its parent value to the
      // buffer it shares its underlying structure with. If a slice of
      // a slice, then use the root structure
      if (buffer.length > 0)
        parents.set(buffer, this.parent || this);

      return buffer;
    }
  },
  write: {
    value: function(string, offset, length, encoding = 'utf8') {
      // write(string, encoding);
      if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
        [offset, length, encoding] = [0, null, offset];
      }
      // write(string, offset, encoding);
      else if (typeof(length) === 'string')
        [length, encoding] = [null, length];

      if (offset < 0 || offset > this.length)
        throw new RangeError('offset is outside of valid range');

      offset = ~~offset;

      // Clamp length if it would overflow buffer, or if its
      // undefined
      if (length == null || length + offset > this.length)
        length = this.length - offset;

      let buffer = new TextEncoder(encoding).encode(string);
      let result = Math.min(buffer.length, length);
      if (buffer.length !== length)
        buffer = buffer.subarray(0, length);

      Uint8ArraySet.call(this, buffer, offset);
      return result;
    }
  },
  fill: {
    value: function fill(value, start, end) {
      let length = this.length;
      value = value || 0;
      start = start || 0;
      end = end || length;

      if (typeof(value) === 'string')
        value = value.charCodeAt(0);
      if (typeof(value) !== 'number' || isNaN(value))
        throw TypeError('value is not a number');
      if (end < start)
        throw new RangeError('end < start');

      // Fill 0 bytes; we're done
      if (end === start)
        return 0;
      if (length == 0)
        return 0;

      if (start < 0 || start >= length)
        throw RangeError('start out of bounds');

      if (end < 0 || end > length)
        throw RangeError('end out of bounds');

      let index = start;
      while (index < end) this[index++] = value;
    }
  }
});

// Define nodejs Buffer's getter and setter functions that just proxy
// to internal DataView's equivalent methods.

// TODO do we need to check architecture to see if it's default big/little endian?
[['readUInt16LE', 'getUint16', true],
 ['readUInt16BE', 'getUint16', false],
 ['readInt16LE', 'getInt16', true],
 ['readInt16BE', 'getInt16', false],
 ['readUInt32LE', 'getUint32', true],
 ['readUInt32BE', 'getUint32', false],
 ['readInt32LE', 'getInt32', true],
 ['readInt32BE', 'getInt32', false],
 ['readFloatLE', 'getFloat32', true],
 ['readFloatBE', 'getFloat32', false],
 ['readDoubleLE', 'getFloat64', true],
 ['readDoubleBE', 'getFloat64', false],
 ['readUInt8', 'getUint8'],
 ['readInt8', 'getInt8']].forEach(([alias, name, littleEndian]) => {
  Object.defineProperty(Buffer.prototype, alias, {
    value: function(offset) {
      return this.view[name](offset, littleEndian);
    }
  });
});

[['writeUInt16LE', 'setUint16', true],
 ['writeUInt16BE', 'setUint16', false],
 ['writeInt16LE', 'setInt16', true],
 ['writeInt16BE', 'setInt16', false],
 ['writeUInt32LE', 'setUint32', true],
 ['writeUInt32BE', 'setUint32', false],
 ['writeInt32LE', 'setInt32', true],
 ['writeInt32BE', 'setInt32', false],
 ['writeFloatLE', 'setFloat32', true],
 ['writeFloatBE', 'setFloat32', false],
 ['writeDoubleLE', 'setFloat64', true],
 ['writeDoubleBE', 'setFloat64', false],
 ['writeUInt8', 'setUint8'],
 ['writeInt8', 'setInt8']].forEach(([alias, name, littleEndian]) => {
  Object.defineProperty(Buffer.prototype, alias, {
    value: function(value, offset) {
      return this.view[name](offset, value, littleEndian);
    }
  });
});
PK
!<P

'modules/commonjs/sdk/io/byte-streams.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

exports.ByteReader = ByteReader;
exports.ByteWriter = ByteWriter;

const {Cc, Ci} = require("chrome");

// This just controls the maximum number of bytes we read in at one time.
const BUFFER_BYTE_LEN = 0x8000;

function ByteReader(inputStream) {
  const self = this;

  let stream = Cc["@mozilla.org/binaryinputstream;1"].
               createInstance(Ci.nsIBinaryInputStream);
  stream.setInputStream(inputStream);

  let manager = new StreamManager(this, stream);

  this.read = function ByteReader_read(numBytes) {
    manager.ensureOpened();
    if (typeof(numBytes) !== "number")
      numBytes = Infinity;

    let data = "";
    let read = 0;
    try {
      while (true) {
        let avail = stream.available();
        let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
        if (toRead <= 0)
          break;
        data += stream.readBytes(toRead);
        read += toRead;
      }
    }
    catch (err) {
      throw new Error("Error reading from stream: " + err);
    }

    return data;
  };
}

function ByteWriter(outputStream) {
  const self = this;

  let stream = Cc["@mozilla.org/binaryoutputstream;1"].
               createInstance(Ci.nsIBinaryOutputStream);
  stream.setOutputStream(outputStream);

  let manager = new StreamManager(this, stream);

  this.write = function ByteWriter_write(str) {
    manager.ensureOpened();
    try {
      stream.writeBytes(str, str.length);
    }
    catch (err) {
      throw new Error("Error writing to stream: " + err);
    }
  };
}


// This manages the lifetime of stream, a ByteReader or ByteWriter.  It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened.  It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
  const self = this;
  this.rawStream = rawStream;
  this.opened = true;

  stream.__defineGetter__("closed", function stream_closed() {
    return !self.opened;
  });

  stream.close = function stream_close() {
    self.ensureOpened();
    self.unload();
  };

  require("../system/unload").ensure(this);
}

StreamManager.prototype = {
  ensureOpened: function StreamManager_ensureOpened() {
    if (!this.opened)
      throw new Error("The stream is closed and cannot be used.");
  },
  unload: function StreamManager_unload() {
    this.rawStream.close();
    this.opened = false;
  }
};
PK
!<-F::modules/commonjs/sdk/io/file.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "deprecated"
};

const {Cc,Ci,Cr} = require("chrome");

lazyRequireModule(this, "./byte-streams", "byteStreams");
lazyRequireModule(this, "./text-streams", "textStreams");

// Flags passed when opening a file.  See nsprpub/pr/include/prio.h.
const OPEN_FLAGS = {
  RDONLY: parseInt("0x01"),
  WRONLY: parseInt("0x02"),
  CREATE_FILE: parseInt("0x08"),
  APPEND: parseInt("0x10"),
  TRUNCATE: parseInt("0x20"),
  EXCL: parseInt("0x80")
};

var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
             .getService(Ci.nsIProperties);

function MozFile(path) {
  var file = Cc['@mozilla.org/file/local;1']
             .createInstance(Ci.nsILocalFile);
  file.initWithPath(path);
  return file;
}

function ensureReadable(file) {
  if (!file.isReadable())
    throw new Error("path is not readable: " + file.path);
}

function ensureDir(file) {
  ensureExists(file);
  if (!file.isDirectory())
    throw new Error("path is not a directory: " + file.path);
}

function ensureFile(file) {
  ensureExists(file);
  if (!file.isFile())
    throw new Error("path is not a file: " + file.path);
}

function ensureExists(file) {
  if (!file.exists())
    throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path);
}

function friendlyError(errOrResult, filename) {
  var isResult = typeof(errOrResult) === "number";
  var result = isResult ? errOrResult : errOrResult.result;
  switch (result) {
  case Cr.NS_ERROR_FILE_NOT_FOUND:
    return new Error("path does not exist: " + filename);
  }
  return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult;
}

exports.exists = function exists(filename) {
  return MozFile(filename).exists();
};

exports.isFile = function isFile(filename) {
  return MozFile(filename).isFile();
};

exports.read = function read(filename, mode) {
  if (typeof(mode) !== "string")
    mode = "";

  // Ensure mode is read-only.
  mode = /b/.test(mode) ? "b" : "";

  var stream = exports.open(filename, mode);
  try {
    var str = stream.read();
  }
  finally {
    stream.close();
  }

  return str;
};

exports.join = function join(base) {
  if (arguments.length < 2)
    throw new Error("need at least 2 args");
  base = MozFile(base);
  for (var i = 1; i < arguments.length; i++)
    base.append(arguments[i]);
  return base.path;
};

exports.dirname = function dirname(path) {
  var parent = MozFile(path).parent;
  return parent ? parent.path : "";
};

exports.basename = function basename(path) {
  var leafName = MozFile(path).leafName;

  // On Windows, leafName when the path is a volume letter and colon ("c:") is
  // the path itself.  But such a path has no basename, so we want the empty
  // string.
  return leafName == path ? "" : leafName;
};

exports.list = function list(path) {
  var file = MozFile(path);
  ensureDir(file);
  ensureReadable(file);

  var entries = file.directoryEntries;
  var entryNames = [];
  while(entries.hasMoreElements()) {
    var entry = entries.getNext();
    entry.QueryInterface(Ci.nsIFile);
    entryNames.push(entry.leafName);
  }
  return entryNames;
};

exports.open = function open(filename, mode) {
  var file = MozFile(filename);
  if (typeof(mode) !== "string")
    mode = "";

  // File opened for write only.
  if (/w/.test(mode)) {
    if (file.exists())
      ensureFile(file);
    var stream = Cc['@mozilla.org/network/file-output-stream;1'].
                 createInstance(Ci.nsIFileOutputStream);
    var openFlags = OPEN_FLAGS.WRONLY |
                    OPEN_FLAGS.CREATE_FILE |
                    OPEN_FLAGS.TRUNCATE;
    var permFlags = 0o644; // u+rw go+r
    try {
      stream.init(file, openFlags, permFlags, 0);
    }
    catch (err) {
      throw friendlyError(err, filename);
    }
    return /b/.test(mode) ?
           new byteStreams.ByteWriter(stream) :
           new textStreams.TextWriter(stream);
  }

  // File opened for read only, the default.
  ensureFile(file);
  stream = Cc['@mozilla.org/network/file-input-stream;1'].
           createInstance(Ci.nsIFileInputStream);
  try {
    stream.init(file, OPEN_FLAGS.RDONLY, 0, 0);
  }
  catch (err) {
    throw friendlyError(err, filename);
  }
  return /b/.test(mode) ?
         new byteStreams.ByteReader(stream) :
         new textStreams.TextReader(stream);
};

exports.remove = function remove(path) {
  var file = MozFile(path);
  ensureFile(file);
  file.remove(false);
};

exports.mkpath = function mkpath(path) {
  var file = MozFile(path);
  if (!file.exists())
    file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); // u+rwx go+rx
  else if (!file.isDirectory())
    throw new Error("The path already exists and is not a directory: " + path);
};

exports.rmdir = function rmdir(path) {
  var file = MozFile(path);
  ensureDir(file);
  try {
    file.remove(false);
  }
  catch (err) {
    // Bug 566950 explains why we're not catching a specific exception here.
    throw new Error("The directory is not empty: " + path);
  }
};
PK
!<w==jjmodules/commonjs/sdk/io/fs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, CC } = require("chrome");

lazyRequire(this, "../timers", "setTimeout");
lazyRequire(this, "./stream", "Stream", "InputStream", "OutputStream");
lazyRequire(this, "../event/core", "emit", "on");
lazyRequire(this, "./buffer", "Buffer");

const { ns } = require("../core/namespace");
const { Class } = require("../core/heritage");


const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
                        "initWithPath");
const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
                            "nsIFileOutputStream", "init");
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
                           "nsIFileInputStream", "init");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                             "nsIBinaryInputStream", "setInputStream");
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
                              "nsIBinaryOutputStream", "setOutputStream");
const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
                      "nsIInputStreamPump", "init");

const { createOutputTransport, createInputTransport } =
  Cc["@mozilla.org/network/stream-transport-service;1"].
  getService(Ci.nsIStreamTransportService);

const { OPEN_UNBUFFERED } = Ci.nsITransport;


const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;

const FILE_PERMISSION = 0o666;
const PR_UINT32_MAX = 0xfffffff;
// Values taken from:
// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
const PR_RDONLY =       0x01;
const PR_WRONLY =       0x02;
const PR_RDWR =         0x04;
const PR_CREATE_FILE =  0x08;
const PR_APPEND =       0x10;
const PR_TRUNCATE =     0x20;
const PR_SYNC =         0x40;
const PR_EXCL =         0x80;

const FLAGS = {
  "r":                  PR_RDONLY,
  "r+":                 PR_RDWR,
  "w":                  PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
  "w+":                 PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
  "a":                  PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
  "a+":                 PR_APPEND | PR_CREATE_FILE | PR_RDWR
};

function accessor() {
  let map = new WeakMap();
  return function(fd, value) {
    if (value === null) map.delete(fd);
    if (value !== undefined) map.set(fd, value);
    return map.get(fd);
  }
}

var nsIFile = accessor();
var nsIFileInputStream = accessor();
var nsIFileOutputStream = accessor();
var nsIBinaryInputStream = accessor();
var nsIBinaryOutputStream = accessor();

// Just a contstant object used to signal that all of the file
// needs to be read.
const ALL = new String("Read all of the file");

function isWritable(mode) {
  return !!(mode & PR_WRONLY || mode & PR_RDWR);
}
function isReadable(mode) {
  return !!(mode & PR_RDONLY || mode & PR_RDWR);
}

function isString(value) {
  return typeof(value) === "string";
}
function isFunction(value) {
  return typeof(value) === "function";
}

function toArray(enumerator) {
  let value = [];
  while(enumerator.hasMoreElements())
    value.push(enumerator.getNext())
  return value
}

function getFileName(file) {
  return file.QueryInterface(Ci.nsIFile).leafName;
}


function remove(path, recursive) {
  let fd = new nsILocalFile(path)
  if (fd.exists()) {
    fd.remove(recursive || false);
  }
  else {
    throw FSError("remove", "ENOENT", 34, path);
  }
}

/**
 * Utility function to convert either an octal number or string
 * into an octal number
 * 0777 => 0o777
 * "0644" => 0o644
 */
function Mode(mode, fallback) {
  return isString(mode) ? parseInt(mode, 8) : mode || fallback;
}
function Flags(flag) {
  return !isString(flag) ? flag :
         FLAGS[flag] || Error("Unknown file open flag: " + flag);
}


function FSError(op, code, errno, path, file, line) {
  let error = Error(code + ", " + op + " " + path, file, line);
  error.code = code;
  error.path = path;
  error.errno = errno;
  return error;
}

const ReadStream = Class({
  extends: InputStream,
  initialize: function initialize(path, options) {
    this.position = -1;
    this.length = -1;
    this.flags = "r";
    this.mode = FILE_PERMISSION;
    this.bufferSize = 64 * 1024;

    options = options || {};

    if ("flags" in options && options.flags)
      this.flags = options.flags;
    if ("bufferSize" in options && options.bufferSize)
      this.bufferSize = options.bufferSize;
    if ("length" in options && options.length)
      this.length = options.length;
    if ("position" in options && options.position !== undefined)
      this.position = options.position;

    let { flags, mode, position, length } = this;
    let fd = isString(path) ? openSync(path, flags, mode) : path;
    this.fd = fd;

    let input = nsIFileInputStream(fd);
    // Setting a stream position, unless it"s `-1` which means current position.
    if (position >= 0)
      input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
    // We use `nsIStreamTransportService` service to transform blocking
    // file input stream into a fully asynchronous stream that can be written
    // without blocking the main thread.
    let transport = createInputTransport(input, position, length, false);
    // Open an input stream on a transport. We don"t pass flags to guarantee
    // non-blocking stream semantics. Also we use defaults for segment size &
    // count.
    InputStream.prototype.initialize.call(this, {
      asyncInputStream: transport.openInputStream(null, 0, 0)
    });

    // Close file descriptor on end and destroy the stream.
    on(this, "end", _ => {
      this.destroy();
      emit(this, "close");
    });

    this.read();
  },
  destroy: function() {
    closeSync(this.fd);
    InputStream.prototype.destroy.call(this);
  }
});
exports.ReadStream = ReadStream;
exports.createReadStream = function createReadStream(path, options) {
  return new ReadStream(path, options);
};

const WriteStream = Class({
  extends: OutputStream,
  initialize: function initialize(path, options) {
    this.drainable = true;
    this.flags = "w";
    this.position = -1;
    this.mode = FILE_PERMISSION;

    options = options || {};

    if ("flags" in options && options.flags)
      this.flags = options.flags;
    if ("mode" in options && options.mode)
      this.mode = options.mode;
    if ("position" in options && options.position !== undefined)
      this.position = options.position;

    let { position, flags, mode } = this;
    // If pass was passed we create a file descriptor out of it. Otherwise
    // we just use given file descriptor.
    let fd = isString(path) ? openSync(path, flags, mode) : path;
    this.fd = fd;

    let output = nsIFileOutputStream(fd);
    // Setting a stream position, unless it"s `-1` which means current position.
    if (position >= 0)
      output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
    // We use `nsIStreamTransportService` service to transform blocking
    // file output stream into a fully asynchronous stream that can be written
    // without blocking the main thread.
    let transport = createOutputTransport(output, position, -1, false);
    // Open an output stream on a transport. We don"t pass flags to guarantee
    // non-blocking stream semantics. Also we use defaults for segment size &
    // count.
    OutputStream.prototype.initialize.call(this, {
      asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0),
      output: output
    });

    // For write streams "finish" basically means close.
    on(this, "finish", _ => {
       this.destroy();
       emit(this, "close");
    });
  },
  destroy: function() {
    OutputStream.prototype.destroy.call(this);
    closeSync(this.fd);
  }
});
exports.WriteStream = WriteStream;
exports.createWriteStream = function createWriteStream(path, options) {
  return new WriteStream(path, options);
};

const Stats = Class({
  initialize: function initialize(path) {
    let file = new nsILocalFile(path);
    if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
    nsIFile(this, file);
  },
  isDirectory: function() {
    return nsIFile(this).isDirectory();
  },
  isFile: function() {
    return nsIFile(this).isFile();
  },
  isSymbolicLink: function() {
    return nsIFile(this).isSymlink();
  },
  get mode() {
    return nsIFile(this).permissions;
  },
  get size() {
    return nsIFile(this).fileSize;
  },
  get mtime() {
    return nsIFile(this).lastModifiedTime;
  },
  isBlockDevice: function() {
    return nsIFile(this).isSpecial();
  },
  isCharacterDevice: function() {
    return nsIFile(this).isSpecial();
  },
  isFIFO: function() {
    return nsIFile(this).isSpecial();
  },
  isSocket: function() {
    return nsIFile(this).isSpecial();
  },
  // non standard
  get exists() {
    return nsIFile(this).exists();
  },
  get hidden() {
    return nsIFile(this).isHidden();
  },
  get writable() {
    return nsIFile(this).isWritable();
  },
  get readable() {
    return nsIFile(this).isReadable();
  }
});
exports.Stats = Stats;

const LStats = Class({
  extends: Stats,
  get size() {
    return this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
                                   nsIFile(this).fileSize;
  },
  get mtime() {
    return this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
                                   nsIFile(this).lastModifiedTime;
  },
  // non standard
  get permissions() {
    return this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
                                   nsIFile(this).permissions;
  }
});

const FStat = Class({
  extends: Stats,
  initialize: function initialize(fd) {
    nsIFile(this, nsIFile(fd));
  }
});

function noop() {}
function Async(wrapped) {
  return function (path, callback) {
    let args = Array.slice(arguments);
    callback = args.pop();
    // If node is not given a callback argument
    // it just does not calls it.
    if (typeof(callback) !== "function") {
      args.push(callback);
      callback = noop;
    }
    setTimeout(function() {
      try {
        var result = wrapped.apply(this, args);
        if (result === undefined) callback(null);
        else callback(null, result);
      } catch (error) {
        callback(error);
      }
    }, 0);
  }
}


/**
 * Synchronous rename(2)
 */
function renameSync(oldPath, newPath) {
  let source = new nsILocalFile(oldPath);
  let target = new nsILocalFile(newPath);
  if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
  return source.moveTo(target.parent, target.leafName);
};
exports.renameSync = renameSync;

/**
 * Asynchronous rename(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var rename = Async(renameSync);
exports.rename = rename;

/**
 * Test whether or not the given path exists by checking with the file system.
 */
function existsSync(path) {
  return new nsILocalFile(path).exists();
}
exports.existsSync = existsSync;

var exists = Async(existsSync);
exports.exists = exists;

/**
 * Synchronous ftruncate(2).
 */
function truncateSync(path, length) {
  let fd = openSync(path, "w");
  ftruncateSync(fd, length);
  closeSync(fd);
}
exports.truncateSync = truncateSync;

/**
 * Asynchronous ftruncate(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
function truncate(path, length, callback) {
  open(path, "w", function(error, fd) {
    if (error) return callback(error);
    ftruncate(fd, length, function(error) {
      if (error) {
        closeSync(fd);
        callback(error);
      }
      else {
        close(fd, callback);
      }
    });
  });
}
exports.truncate = truncate;

function ftruncate(fd, length, callback) {
  write(fd, new Buffer(length), 0, length, 0, function(error) {
    callback(error);
  });
}
exports.ftruncate = ftruncate;

function ftruncateSync(fd, length = 0) {
  writeSync(fd, new Buffer(length), 0, length, 0);
}
exports.ftruncateSync = ftruncateSync;

function chownSync(path, uid, gid) {
  throw Error("Not implemented yet!!");
}
exports.chownSync = chownSync;

var chown = Async(chownSync);
exports.chown = chown;

function lchownSync(path, uid, gid) {
  throw Error("Not implemented yet!!");
}
exports.lchownSync = chownSync;

var lchown = Async(lchown);
exports.lchown = lchown;

/**
 * Synchronous chmod(2).
 */
function chmodSync (path, mode) {
  let file;
  try {
    file = new nsILocalFile(path);
  } catch(e) {
    throw FSError("chmod", "ENOENT", 34, path);
  }

  file.permissions = Mode(mode);
}
exports.chmodSync = chmodSync;
/**
 * Asynchronous chmod(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var chmod = Async(chmodSync);
exports.chmod = chmod;

/**
 * Synchronous chmod(2).
 */
function fchmodSync(fd, mode) {
  throw Error("Not implemented yet!!");
};
exports.fchmodSync = fchmodSync;
/**
 * Asynchronous chmod(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var fchmod = Async(fchmodSync);
exports.fchmod = fchmod;


/**
 * Synchronous stat(2). Returns an instance of `fs.Stats`
 */
function statSync(path) {
  return new Stats(path);
};
exports.statSync = statSync;

/**
 * Asynchronous stat(2). The callback gets two arguments (err, stats) where
 * stats is a `fs.Stats` object. It looks like this:
 */
var stat = Async(statSync);
exports.stat = stat;

/**
 * Synchronous lstat(2). Returns an instance of `fs.Stats`.
 */
function lstatSync(path) {
  return new LStats(path);
};
exports.lstatSync = lstatSync;

/**
 * Asynchronous lstat(2). The callback gets two arguments (err, stats) where
 * stats is a fs.Stats object. lstat() is identical to stat(), except that if
 * path is a symbolic link, then the link itself is stat-ed, not the file that
 * it refers to.
 */
var lstat = Async(lstatSync);
exports.lstat = lstat;

/**
 * Synchronous fstat(2). Returns an instance of `fs.Stats`.
 */
function fstatSync(fd) {
  return new FStat(fd);
};
exports.fstatSync = fstatSync;

/**
 * Asynchronous fstat(2). The callback gets two arguments (err, stats) where
 * stats is a fs.Stats object.
 */
var fstat = Async(fstatSync);
exports.fstat = fstat;

/**
 * Synchronous link(2).
 */
function linkSync(source, target) {
  throw Error("Not implemented yet!!");
};
exports.linkSync = linkSync;

/**
 * Asynchronous link(2). No arguments other than a possible exception are given
 * to the completion callback.
 */
var link = Async(linkSync);
exports.link = link;

/**
 * Synchronous symlink(2).
 */
function symlinkSync(source, target) {
  throw Error("Not implemented yet!!");
};
exports.symlinkSync = symlinkSync;

/**
 * Asynchronous symlink(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var symlink = Async(symlinkSync);
exports.symlink = symlink;

/**
 * Synchronous readlink(2). Returns the resolved path.
 */
function readlinkSync(path) {
  return new nsILocalFile(path).target;
};
exports.readlinkSync = readlinkSync;

/**
 * Asynchronous readlink(2). The callback gets two arguments
 * `(error, resolvedPath)`.
 */
var readlink = Async(readlinkSync);
exports.readlink = readlink;

/**
 * Synchronous realpath(2). Returns the resolved path.
 */
function realpathSync(path) {
  return new nsILocalFile(path).path;
};
exports.realpathSync = realpathSync;

/**
 * Asynchronous realpath(2). The callback gets two arguments
 * `(err, resolvedPath)`.
 */
var realpath = Async(realpathSync);
exports.realpath = realpath;

/**
 * Synchronous unlink(2).
 */
var unlinkSync = remove;
exports.unlinkSync = unlinkSync;

/**
 * Asynchronous unlink(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var unlink = Async(remove);
exports.unlink = unlink;

/**
 * Synchronous rmdir(2).
 */
var rmdirSync = remove;
exports.rmdirSync = rmdirSync;

/**
 * Asynchronous rmdir(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var rmdir = Async(rmdirSync);
exports.rmdir = rmdir;

/**
 * Synchronous mkdir(2).
 */
function mkdirSync(path, mode) {
  try {
    return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
  } catch (error) {
    // Adjust exception thorw to match ones thrown by node.
    if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
      let { fileName, lineNumber } = error;
      error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
    }
    throw error;
  }
};
exports.mkdirSync = mkdirSync;

/**
 * Asynchronous mkdir(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var mkdir = Async(mkdirSync);
exports.mkdir = mkdir;

/**
 * Synchronous readdir(3). Returns an array of filenames excluding `"."` and
 * `".."`.
 */
function readdirSync(path) {
  try {
    return toArray(new nsILocalFile(path).directoryEntries).map(getFileName);
  }
  catch (error) {
    // Adjust exception thorw to match ones thrown by node.
    if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
        error.name === "NS_ERROR_FILE_NOT_FOUND")
    {
      let { fileName, lineNumber } = error;
      error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
    }
    throw error;
  }
};
exports.readdirSync = readdirSync;

/**
 * Asynchronous readdir(3). Reads the contents of a directory. The callback
 * gets two arguments `(error, files)` where `files` is an array of the names
 * of the files in the directory excluding `"."` and `".."`.
 */
var readdir = Async(readdirSync);
exports.readdir = readdir;

/**
 * Synchronous close(2).
 */
 function closeSync(fd) {
   let input = nsIFileInputStream(fd);
   let output = nsIFileOutputStream(fd);

   // Closing input stream and removing reference.
   if (input) input.close();
   // Closing output stream and removing reference.
   if (output) output.close();

   nsIFile(fd, null);
   nsIFileInputStream(fd, null);
   nsIFileOutputStream(fd, null);
   nsIBinaryInputStream(fd, null);
   nsIBinaryOutputStream(fd, null);
};
exports.closeSync = closeSync;
/**
 * Asynchronous close(2). No arguments other than a possible exception are
 * given to the completion callback.
 */
var close = Async(closeSync);
exports.close = close;

/**
 * Synchronous open(2).
 */
function openSync(aPath, aFlag, aMode) {
  let [ fd, flags, mode, file ] =
      [ { path: aPath }, Flags(aFlag), Mode(aMode), nsILocalFile(aPath) ];

  nsIFile(fd, file);

  // If trying to open file for just read that does not exists
  // need to throw exception as node does.
  if (!file.exists() && !isWritable(flags))
    throw FSError("open", "ENOENT", 34, aPath);

  // If we want to open file in read mode we initialize input stream.
  if (isReadable(flags)) {
    let input = FileInputStream(file, flags, mode, DEFER_OPEN);
    nsIFileInputStream(fd, input);
  }

  // If we want to open file in write mode we initialize output stream for it.
  if (isWritable(flags)) {
    let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
    nsIFileOutputStream(fd, output);
  }

  return fd;
}
exports.openSync = openSync;
/**
 * Asynchronous file open. See open(2). Flags can be
 * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
 * The callback gets two arguments `(error, fd).
 */
var open = Async(openSync);
exports.open = open;

/**
 * Synchronous version of buffer-based fs.write(). Returns the number of bytes
 * written.
 */
function writeSync(fd, buffer, offset, length, position) {
  if (length + offset > buffer.length) {
    throw Error("Length is extends beyond buffer");
  }
  else if (length + offset !== buffer.length) {
    buffer = buffer.slice(offset, offset + length);
  }

  let output = BinaryOutputStream(nsIFileOutputStream(fd));
  nsIBinaryOutputStream(fd, output);
  // We write content as a byte array as this will avoid any transcoding
  // if content was a buffer.
  output.writeByteArray(buffer.valueOf(), buffer.length);
  output.flush();
};
exports.writeSync = writeSync;

/**
 * Write buffer to the file specified by fd.
 *
 * `offset` and `length` determine the part of the buffer to be written.
 *
 * `position` refers to the offset from the beginning of the file where this
 * data should be written. If `position` is `null`, the data will be written
 * at the current position. See pwrite(2).
 *
 * The callback will be given three arguments `(error, written, buffer)` where
 * written specifies how many bytes were written into buffer.
 *
 * Note that it is unsafe to use `fs.write` multiple times on the same file
 * without waiting for the callback.
 */
function write(fd, buffer, offset, length, position, callback) {
  if (!Buffer.isBuffer(buffer)) {
    // (fd, data, position, encoding, callback)
    let encoding = null;
    [ position, encoding, callback ] = Array.slice(arguments, 1);
    buffer = new Buffer(String(buffer), encoding);
    offset = 0;
  } else if (length + offset > buffer.length) {
    throw Error("Length is extends beyond buffer");
  } else if (length + offset !== buffer.length) {
    buffer = buffer.slice(offset, offset + length);
  }

  let writeStream = new WriteStream(fd, { position: position,
                                          length: length });
  writeStream.on("error", callback);
  writeStream.write(buffer, function onEnd() {
    writeStream.destroy();
    if (callback)
      callback(null, buffer.length, buffer);
  });
};
exports.write = write;

/**
 * Synchronous version of string-based fs.read. Returns the number of
 * bytes read.
 */
function readSync(fd, buffer, offset, length, position) {
  let input = nsIFileInputStream(fd);
  // Setting a stream position, unless it"s `-1` which means current position.
  if (position >= 0)
    input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
  // We use `nsIStreamTransportService` service to transform blocking
  // file input stream into a fully asynchronous stream that can be written
  // without blocking the main thread.
  let binaryInputStream = BinaryInputStream(input);
  let count = length === ALL ? binaryInputStream.available() : length;
  if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer);
  else {
    let chunk = new Buffer(count);
    binaryInputStream.readArrayBuffer(count, chunk.buffer);
    chunk.copy(buffer, offset);
  }

  return buffer.slice(offset, offset + count);
};
exports.readSync = readSync;

/**
 * Read data from the file specified by `fd`.
 *
 * `buffer` is the buffer that the data will be written to.
 * `offset` is offset within the buffer where writing will start.
 *
 * `length` is an integer specifying the number of bytes to read.
 *
 * `position` is an integer specifying where to begin reading from in the file.
 * If `position` is `null`, data will be read from the current file position.
 *
 * The callback is given the three arguments, `(error, bytesRead, buffer)`.
 */
function read(fd, buffer, offset, length, position, callback) {
  let bytesRead = 0;
  let readStream = new ReadStream(fd, { position: position, length: length });
  readStream.on("data", function onData(data) {
      data.copy(buffer, offset + bytesRead);
      bytesRead += data.length;
  });
  readStream.on("end", function onEnd() {
    callback(null, bytesRead, buffer);
    readStream.destroy();
  });
};
exports.read = read;

/**
 * Asynchronously reads the entire contents of a file.
 * The callback is passed two arguments `(error, data)`, where data is the
 * contents of the file.
 */
function readFile(path, encoding, callback) {
  if (isFunction(encoding)) {
    callback = encoding
    encoding = null
  }

  let buffer = null;
  try {
    let readStream = new ReadStream(path);
    readStream.on("data", function(data) {
      if (!buffer) buffer = data;
      else buffer = Buffer.concat([buffer, data], 2);
    });
    readStream.on("error", function onError(error) {
      callback(error);
    });
    readStream.on("end", function onEnd() {
      // Note: Need to destroy before invoking a callback
      // so that file descriptor is released.
      readStream.destroy();
      callback(null, buffer);
    });
  }
  catch (error) {
    setTimeout(callback, 0, error);
  }
};
exports.readFile = readFile;

/**
 * Synchronous version of `fs.readFile`. Returns the contents of the path.
 * If encoding is specified then this function returns a string.
 * Otherwise it returns a buffer.
 */
function readFileSync(path, encoding) {
  let fd = openSync(path, "r");
  let size = fstatSync(fd).size;
  let buffer = new Buffer(size);
  try {
    readSync(fd, buffer, 0, ALL, 0);
  }
  finally {
    closeSync(fd);
  }
  return buffer;
};
exports.readFileSync = readFileSync;

/**
 * Asynchronously writes data to a file, replacing the file if it already
 * exists. data can be a string or a buffer.
 */
function writeFile(path, content, encoding, callback) {
  if (!isString(path))
    throw new TypeError('path must be a string');

  try {
    if (isFunction(encoding)) {
      callback = encoding
      encoding = null
    }
    if (isString(content))
      content = new Buffer(content, encoding);

    let writeStream = new WriteStream(path);
    let error = null;

    writeStream.end(content, function() {
      writeStream.destroy();
      callback(error);
    });

    writeStream.on("error", function onError(reason) {
      error = reason;
      writeStream.destroy();
    });
  } catch (error) {
    callback(error);
  }
};
exports.writeFile = writeFile;

/**
 * The synchronous version of `fs.writeFile`.
 */
function writeFileSync(filename, data, encoding) {
  // TODO: Implement this in bug 1148209 https://bugzilla.mozilla.org/show_bug.cgi?id=1148209
  throw Error("Not implemented");
};
exports.writeFileSync = writeFileSync;


function utimesSync(path, atime, mtime) {
  throw Error("Not implemented");
}
exports.utimesSync = utimesSync;

var utimes = Async(utimesSync);
exports.utimes = utimes;

function futimesSync(fd, atime, mtime, callback) {
  throw Error("Not implemented");
}
exports.futimesSync = futimesSync;

var futimes = Async(futimesSync);
exports.futimes = futimes;

function fsyncSync(fd, atime, mtime, callback) {
  throw Error("Not implemented");
}
exports.fsyncSync = fsyncSync;

var fsync = Async(fsyncSync);
exports.fsync = fsync;


/**
 * Watch for changes on filename. The callback listener will be called each
 * time the file is accessed.
 *
 * The second argument is optional. The options if provided should be an object
 * containing two members a boolean, persistent, and interval, a polling value
 * in milliseconds. The default is { persistent: true, interval: 0 }.
 */
function watchFile(path, options, listener) {
  throw Error("Not implemented");
};
exports.watchFile = watchFile;


function unwatchFile(path, listener) {
  throw Error("Not implemented");
}
exports.unwatchFile = unwatchFile;

function watch(path, options, listener) {
  throw Error("Not implemented");
}
exports.watch = watch;
PK
!</66!modules/commonjs/sdk/io/stream.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { CC, Cc, Ci, Cu, Cr, components } = require("chrome");
const { EventTarget } = require("../event/target");
const { Class } = require("../core/heritage");

lazyRequire(this, "../event/core", "emit");
lazyRequire(this, "./buffer", "Buffer");
lazyRequire(this, "../timers", "setTimeout");


const MultiplexInputStream = CC("@mozilla.org/io/multiplex-input-stream;1",
                                "nsIMultiplexInputStream");
const AsyncStreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
                             "nsIAsyncStreamCopier", "init");
const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
                             "nsIStringInputStream");
const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
                                  "nsIArrayBufferInputStream");

const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                             "nsIBinaryInputStream", "setInputStream");
const InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
                           "nsIInputStreamPump", "init");

const threadManager = Cc["@mozilla.org/thread-manager;1"].
                      getService(Ci.nsIThreadManager);

const eventTarget = Cc["@mozilla.org/network/stream-transport-service;1"].
                    getService(Ci.nsIEventTarget);

var isFunction = value => typeof(value) === "function"

function accessor() {
  let map = new WeakMap();
  return function(target, value) {
    if (value)
      map.set(target, value);
    return map.get(target);
  }
}

const Stream = Class({
  extends: EventTarget,
  initialize: function() {
    this.readable = false;
    this.writable = false;
    this.encoding = null;
  },
  setEncoding: function setEncoding(encoding) {
    this.encoding = String(encoding).toUpperCase();
  },
  pipe: function pipe(target, options) {
    let source = this;
    function onData(chunk) {
      if (target.writable) {
        if (false === target.write(chunk))
          source.pause();
      }
    }
    function onDrain() {
      if (source.readable)
        source.resume();
    }
    function onEnd() {
      target.end();
    }
    function onPause() {
      source.pause();
    }
    function onResume() {
      if (source.readable)
        source.resume();
    }

    function cleanup() {
      source.removeListener("data", onData);
      target.removeListener("drain", onDrain);
      source.removeListener("end", onEnd);

      target.removeListener("pause", onPause);
      target.removeListener("resume", onResume);

      source.removeListener("end", cleanup);
      source.removeListener("close", cleanup);

      target.removeListener("end", cleanup);
      target.removeListener("close", cleanup);
    }

    if (!options || options.end !== false)
      target.on("end", onEnd);

    source.on("data", onData);
    target.on("drain", onDrain);
    target.on("resume", onResume);
    target.on("pause", onPause);

    source.on("end", cleanup);
    source.on("close", cleanup);

    target.on("end", cleanup);
    target.on("close", cleanup);

    emit(target, "pipe", source);
  },
  pause: function pause() {
    emit(this, "pause");
  },
  resume: function resume() {
    emit(this, "resume");
  },
  destroySoon: function destroySoon() {
    this.destroy();
  }
});
exports.Stream = Stream;


var nsIStreamListener = accessor();
var nsIInputStreamPump = accessor();
var nsIAsyncInputStream = accessor();
var nsIBinaryInputStream = accessor();

const StreamListener = Class({
  initialize: function(stream) {
    this.stream = stream;
  },

  // Next three methods are part of `nsIStreamListener` interface and are
  // invoked by `nsIInputStreamPump.asyncRead`.
  onDataAvailable: function(request, context, input, offset, count) {
    let stream = this.stream;
    let buffer = new ArrayBuffer(count);
    nsIBinaryInputStream(stream).readArrayBuffer(count, buffer);
    emit(stream, "data", new Buffer(buffer));
  },

  // Next two methods implement `nsIRequestObserver` interface and are invoked
  // by `nsIInputStreamPump.asyncRead`.
  onStartRequest: function() {},
  // Called to signify the end of an asynchronous request. We only care to
  // discover errors.
  onStopRequest: function(request, context, status) {
    let stream = this.stream;
    stream.readable = false;
    if (!components.isSuccessCode(status))
      emit(stream, "error", status);
    else
      emit(stream, "end");
  }
});


const InputStream = Class({
  extends: Stream,
  readable: false,
  paused: false,
  initialize: function initialize(options) {
    let { asyncInputStream } = options;

    this.readable = true;

    let binaryInputStream = new BinaryInputStream(asyncInputStream);
    let inputStreamPump = new InputStreamPump(asyncInputStream,
                                              -1, -1, 0, 0, false);
    let streamListener = new StreamListener(this);

    nsIAsyncInputStream(this, asyncInputStream);
    nsIInputStreamPump(this, inputStreamPump);
    nsIBinaryInputStream(this, binaryInputStream);
    nsIStreamListener(this, streamListener);

    this.asyncInputStream = asyncInputStream;
    this.inputStreamPump = inputStreamPump;
    this.binaryInputStream = binaryInputStream;
  },
  get status() {
    return nsIInputStreamPump(this).status;
  },
  read: function() {
    nsIInputStreamPump(this).asyncRead(nsIStreamListener(this), null);
  },
  pause: function pause() {
    this.paused = true;
    nsIInputStreamPump(this).suspend();
    emit(this, "paused");
  },
  resume: function resume() {
    this.paused = false;
    if (nsIInputStreamPump(this).isPending()) {
        nsIInputStreamPump(this).resume();
        emit(this, "resume");
    }
  },
  close: function close() {
    this.readable = false;
    nsIInputStreamPump(this).cancel(Cr.NS_OK);
    nsIBinaryInputStream(this).close();
    nsIAsyncInputStream(this).close();
  },
  destroy: function destroy() {
    this.close();

    nsIInputStreamPump(this);
    nsIAsyncInputStream(this);
    nsIBinaryInputStream(this);
    nsIStreamListener(this);
  }
});
exports.InputStream = InputStream;



var nsIRequestObserver = accessor();
var nsIAsyncOutputStream = accessor();
var nsIAsyncStreamCopier = accessor();
var nsIMultiplexInputStream = accessor();

const RequestObserver = Class({
  initialize: function(stream) {
    this.stream = stream;
  },
  // Method is part of `nsIRequestObserver` interface that is
  // invoked by `nsIAsyncStreamCopier.asyncCopy`.
  onStartRequest: function() {},
  // Method is part of `nsIRequestObserver` interface that is
  // invoked by `nsIAsyncStreamCopier.asyncCopy`.
  onStopRequest: function(request, context, status) {
    let stream = this.stream;
    stream.drained = true;

    // Remove copied chunk.
    let multiplexInputStream = nsIMultiplexInputStream(stream);
    multiplexInputStream.removeStream(0);

    // If there was an error report.
    if (!components.isSuccessCode(status))
      emit(stream, "error", status);

    // If there more chunks in queue then flush them.
    else if (multiplexInputStream.count)
      stream.flush();

    // If stream is still writable notify that queue has drained.
    else if (stream.writable)
      emit(stream, "drain");

    // If stream is no longer writable close it.
    else {
      nsIAsyncStreamCopier(stream).cancel(Cr.NS_OK);
      nsIMultiplexInputStream(stream).close();
      nsIAsyncOutputStream(stream).close();
      nsIAsyncOutputStream(stream).flush();
    }
  }
});

const OutputStreamCallback = Class({
  initialize: function(stream) {
    this.stream = stream;
  },
  // Method is part of `nsIOutputStreamCallback` interface that
  // is invoked by `nsIAsyncOutputStream.asyncWait`. It is registered
  // with `WAIT_CLOSURE_ONLY` flag that overrides the default behavior,
  // causing the `onOutputStreamReady` notification to be suppressed until
  // the stream becomes closed.
  onOutputStreamReady: function(nsIAsyncOutputStream) {
    emit(this.stream, "finish");
  }
});

const OutputStream = Class({
  extends: Stream,
  writable: false,
  drained: true,
  get bufferSize() {
    let multiplexInputStream = nsIMultiplexInputStream(this);
    return multiplexInputStream && multiplexInputStream.available();
  },
  initialize: function initialize(options) {
    let { asyncOutputStream, output } = options;
    this.writable = true;

    // Ensure that `nsIAsyncOutputStream` was provided.
    asyncOutputStream.QueryInterface(Ci.nsIAsyncOutputStream);

    // Create a `nsIMultiplexInputStream` and `nsIAsyncStreamCopier`. Former
    // is used to queue written data chunks that `asyncStreamCopier` will
    // asynchronously drain into `asyncOutputStream`.
    let multiplexInputStream = MultiplexInputStream();
    let asyncStreamCopier = AsyncStreamCopier(multiplexInputStream,
                                              output || asyncOutputStream,
                                              eventTarget,
                                              // nsIMultiplexInputStream
                                              // implemnts .readSegments()
                                              true,
                                              // nsIOutputStream may or
                                              // may not implemnet
                                              // .writeSegments().
                                              false,
                                              // Use default buffer size.
                                              null,
                                              // Should not close an input.
                                              false,
                                              // Should not close an output.
                                              false);

    // Create `requestObserver` implementing `nsIRequestObserver` interface
    // in the constructor that's gonna be reused across several flushes.
    let requestObserver = RequestObserver(this);


    // Create observer that implements `nsIOutputStreamCallback` and register
    // using `WAIT_CLOSURE_ONLY` flag. That way it will be notfied once
    // `nsIAsyncOutputStream` is closed.
    asyncOutputStream.asyncWait(OutputStreamCallback(this),
                                asyncOutputStream.WAIT_CLOSURE_ONLY,
                                0,
                                threadManager.currentThread);

    nsIRequestObserver(this, requestObserver);
    nsIAsyncOutputStream(this, asyncOutputStream);
    nsIMultiplexInputStream(this, multiplexInputStream);
    nsIAsyncStreamCopier(this, asyncStreamCopier);

    this.asyncOutputStream = asyncOutputStream;
    this.multiplexInputStream = multiplexInputStream;
    this.asyncStreamCopier = asyncStreamCopier;
  },
  write: function write(content, encoding, callback) {
    if (isFunction(encoding)) {
      callback = encoding;
      encoding = callback;
    }

    // If stream is not writable we throw an error.
    if (!this.writable) throw Error("stream is not writable");

    let chunk = null;

    // If content is not a buffer then we create one out of it.
    if (Buffer.isBuffer(content)) {
      chunk = new ArrayBufferInputStream();
      chunk.setData(content.buffer, 0, content.length);
    }
    else {
      chunk = new StringInputStream();
      chunk.setData(content, content.length);
    }

    if (callback)
      this.once("drain", callback);

    // Queue up chunk to be copied to output sync.
    nsIMultiplexInputStream(this).appendStream(chunk);
    this.flush();

    return this.drained;
  },
  flush: function() {
    if (this.drained) {
      this.drained = false;
      nsIAsyncStreamCopier(this).asyncCopy(nsIRequestObserver(this), null);
    }
  },
  end: function end(content, encoding, callback) {
    if (isFunction(content)) {
      callback = content
      content = callback
    }
    if (isFunction(encoding)) {
      callback = encoding
      encoding = callback
    }

    // Setting a listener to "finish" event if passed.
    if (isFunction(callback))
      this.once("finish", callback);


    if (content)
      this.write(content, encoding);
    this.writable = false;

    // Close `asyncOutputStream` only if output has drained. If it's
    // not drained than `asyncStreamCopier` is busy writing, so let
    // it finish. Note that since `this.writable` is false copier will
    // close `asyncOutputStream` once output drains.
    if (this.drained)
      nsIAsyncOutputStream(this).close();
  },
  destroy: function destroy() {
    nsIAsyncOutputStream(this).close();
    nsIAsyncOutputStream(this);
    nsIMultiplexInputStream(this);
    nsIAsyncStreamCopier(this);
    nsIRequestObserver(this);
  }
});
exports.OutputStream = OutputStream;

const DuplexStream = Class({
  extends: Stream,
  implements: [InputStream, OutputStream],
  allowHalfOpen: true,
  initialize: function initialize(options) {
    options = options || {};
    let { readable, writable, allowHalfOpen } = options;

    InputStream.prototype.initialize.call(this, options);
    OutputStream.prototype.initialize.call(this, options);

    if (readable === false)
      this.readable = false;

    if (writable === false)
      this.writable = false;

    if (allowHalfOpen === false)
      this.allowHalfOpen = false;

    // If in a half open state and it's disabled enforce end.
    this.once("end", () => {
      if (!this.allowHalfOpen && (!this.readable || !this.writable))
        this.end();
    });
  },
  destroy: function destroy(error) {
    InputStream.prototype.destroy.call(this);
    OutputStream.prototype.destroy.call(this);
  }
});
exports.DuplexStream = DuplexStream;
PK
!<{Vgg'modules/commonjs/sdk/io/text-streams.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cu, components } = require("chrome");
const { ensure } = require("../system/unload");
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});

// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
// performance we use it, too.
const BUFFER_BYTE_LEN = 0x8000;
const PR_UINT32_MAX = 0xffffffff;
const DEFAULT_CHARSET = "UTF-8";


/**
 * An input stream that reads text from a backing stream using a given text
 * encoding.
 *
 * @param inputStream
 *        The stream is backed by this nsIInputStream.  It must already be
 *        opened.
 * @param charset
 *        Text in inputStream is expected to be in this character encoding.  If
 *        not given, "UTF-8" is assumed.  See nsICharsetConverterManager.idl for
 *        documentation on how to determine other valid values for this.
 */
function TextReader(inputStream, charset) {
  charset = checkCharset(charset);

  let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
               createInstance(Ci.nsIConverterInputStream);
  stream.init(inputStream, charset, BUFFER_BYTE_LEN,
              Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

  let manager = new StreamManager(this, stream);

  /**
   * Reads a string from the stream.  If the stream is closed, an exception is
   * thrown.
   *
   * @param  numChars
   *         The number of characters to read.  If not given, the remainder of
   *         the stream is read.
   * @return The string read.  If the stream is already at EOS, returns the
   *         empty string.
   */
  this.read = function TextReader_read(numChars) {
    manager.ensureOpened();

    let readAll = false;
    if (typeof(numChars) === "number")
      numChars = Math.max(numChars, 0);
    else
      readAll = true;

    let str = "";
    let totalRead = 0;
    let chunkRead = 1;

    // Read in numChars or until EOS, whichever comes first.  Note that the
    // units here are characters, not bytes.
    while (true) {
      let chunk = {};
      let toRead = readAll ?
                   PR_UINT32_MAX :
                   Math.min(numChars - totalRead, PR_UINT32_MAX);
      if (toRead <= 0 || chunkRead <= 0)
        break;

      // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
      // to readString, enough to fill its byte buffer.  chunkRead will be the
      // number of characters encoded by the bytes in that buffer.
      chunkRead = stream.readString(toRead, chunk);
      str += chunk.value;
      totalRead += chunkRead;
    }

    return str;
  };
}
exports.TextReader = TextReader;

/**
 * A buffered output stream that writes text to a backing stream using a given
 * text encoding.
 *
 * @param outputStream
 *        The stream is backed by this nsIOutputStream.  It must already be
 *        opened.
 * @param charset
 *        Text will be written to outputStream using this character encoding.
 *        If not given, "UTF-8" is assumed.  See nsICharsetConverterManager.idl
 *        for documentation on how to determine other valid values for this.
 */
function TextWriter(outputStream, charset) {
  charset = checkCharset(charset);

  let stream = outputStream;

  // Buffer outputStream if it's not already.
  let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
  if (!ioUtils.outputStreamIsBuffered(outputStream)) {
    stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
             createInstance(Ci.nsIBufferedOutputStream);
    stream.init(outputStream, BUFFER_BYTE_LEN);
  }

  // I'd like to use nsIConverterOutputStream.  But NetUtil.asyncCopy(), which
  // we use below in writeAsync(), naturally expects its sink to be an instance
  // of nsIOutputStream, which nsIConverterOutputStream's only implementation is
  // not.  So we use uconv and manually convert all strings before writing to
  // outputStream.
  let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
              createInstance(Ci.nsIScriptableUnicodeConverter);
  uconv.charset = charset;

  let manager = new StreamManager(this, stream);

  /**
   * Flushes the backing stream's buffer.
   */
  this.flush = function TextWriter_flush() {
    manager.ensureOpened();
    stream.flush();
  };

  /**
   * Writes a string to the stream.  If the stream is closed, an exception is
   * thrown.
   *
   * @param str
   *        The string to write.
   */
  this.write = function TextWriter_write(str) {
    manager.ensureOpened();
    let istream = uconv.convertToInputStream(str);
    let len = istream.available();
    while (len > 0) {
      stream.writeFrom(istream, len);
      len = istream.available();
    }
    istream.close();
  };

  /**
   * Writes a string on a background thread.  After the write completes, the
   * backing stream's buffer is flushed, and both the stream and the backing
   * stream are closed, also on the background thread.  If the stream is already
   * closed, an exception is thrown immediately.
   *
   * @param str
   *        The string to write.
   * @param callback
   *        An optional function.  If given, it's called as callback(error) when
   *        the write completes.  error is an Error object or undefined if there
   *        was no error.  Inside callback, |this| is the stream object.
   */
  this.writeAsync = function TextWriter_writeAsync(str, callback) {
    manager.ensureOpened();
    let istream = uconv.convertToInputStream(str);
    NetUtil.asyncCopy(istream, stream, (result) => {
        let err = components.isSuccessCode(result) ? undefined :
        new Error("An error occured while writing to the stream: " + result);
      if (err)
        console.error(err);

      // asyncCopy() closes its output (and input) stream.
      manager.opened = false;

      if (typeof(callback) === "function") {
        try {
          callback.call(this, err);
        }
        catch (exc) {
          console.exception(exc);
        }
      }
    });
  };
}
exports.TextWriter = TextWriter;

// This manages the lifetime of stream, a TextReader or TextWriter.  It defines
// closed and close() on stream and registers an unload listener that closes
// rawStream if it's still opened.  It also provides ensureOpened(), which
// throws an exception if the stream is closed.
function StreamManager(stream, rawStream) {
  this.rawStream = rawStream;
  this.opened = true;

  /**
   * True iff the stream is closed.
   */
  stream.__defineGetter__("closed", () => !this.opened);

  /**
   * Closes both the stream and its backing stream.  If the stream is already
   * closed, an exception is thrown.  For TextWriters, this first flushes the
   * backing stream's buffer.
   */
  stream.close = () => {
    this.ensureOpened();
    this.unload();
  };

  ensure(this);
}

StreamManager.prototype = {
  ensureOpened: function StreamManager_ensureOpened() {
    if (!this.opened)
      throw new Error("The stream is closed and cannot be used.");
  },
  unload: function StreamManager_unload() {
    // TextWriter.writeAsync() causes rawStream to close and therefore sets
    // opened to false, so check that we're still opened.
    if (this.opened) {
      // Calling close() on both an nsIUnicharInputStream and
      // nsIBufferedOutputStream closes their backing streams.  It also forces
      // nsIOutputStreams to flush first.
      this.rawStream.close();
      this.opened = false;
    }
  }
};

function checkCharset(charset) {
  return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
}
PK
!<#=btt(modules/commonjs/sdk/keyboard/hotkeys.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { observer: keyboardObserver } = require("./observer");
const { getKeyForCode, normalize, isFunctionKey,
        MODIFIERS } = require("./utils");

/**
 * Register a global `hotkey` that executes `listener` when the key combination
 * in `hotkey` is pressed. If more then one `listener` is registered on the same
 * key combination only last one will be executed.
 *
 * @param {string} hotkey
 *    Key combination in the format of 'modifier key'.
 *
 * Examples:
 *
 *     "accel s"
 *     "meta shift i"
 *     "control alt d"
 *
 * Modifier keynames:
 *
 *  - **shift**: The Shift key.
 *  - **alt**: The Alt key. On the Macintosh, this is the Option key. On
 *    Macintosh this can only be used in conjunction with another modifier,
 *    since `Alt+Letter` combinations are reserved for entering special
 *    characters in text.
 *  - **meta**: The Meta key. On the Macintosh, this is the Command key.
 *  - **control**: The Control key.
 *  - **accel**: The key used for keyboard shortcuts on the user's platform,
 *    which is Control on Windows and Linux, and Command on Mac. Usually, this
 *    would be the value you would use.
 *
 * @param {function} listener
 *    Function to execute when the `hotkey` is executed.
 */
exports.register = function register(hotkey, listener) {
  hotkey = normalize(hotkey);
  hotkeys[hotkey] = listener;
};

/**
 * Unregister a global `hotkey`. If passed `listener` is not the one registered
 * for the given `hotkey`, the call to this function will be ignored.
 *
 * @param {string} hotkey
 *    Key combination in the format of 'modifier key'.
 * @param {function} listener
 *    Function that will be invoked when the `hotkey` is pressed.
 */
exports.unregister = function unregister(hotkey, listener) {
  hotkey = normalize(hotkey);
  if (hotkeys[hotkey] === listener)
    delete hotkeys[hotkey];
};

/**
 * Map of hotkeys and associated functions.
 */
const hotkeys = exports.hotkeys = {};

keyboardObserver.on("keydown", function onKeypress(event, window) {
  let key, modifiers = [];
  let isChar = "isChar" in event && event.isChar;
  let which = "which" in event ? event.which : null;
  let keyCode = "keyCode" in event ? event.keyCode : null;

  if ("shiftKey" in event && event.shiftKey)
    modifiers.push("shift");
  if ("altKey" in event && event.altKey)
    modifiers.push("alt");
  if ("ctrlKey" in event && event.ctrlKey)
    modifiers.push("control");
  if ("metaKey" in event && event.metaKey)
    modifiers.push("meta");

  // If it's not a printable character then we fall back to a human readable
  // equivalent of one of the following constants.
  // http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
  key = getKeyForCode(keyCode);

  // If only non-function (f1 - f24) key or only modifiers are pressed we don't
  // have a valid combination so we return immediately (Also, sometimes
  // `keyCode` may be one for the modifier which means we do not have a
  // modifier).
  if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS)
    return;

  let combination = normalize({ key: key, modifiers: modifiers });
  let hotkey = hotkeys[combination];

  if (hotkey) {
    try {
      hotkey();
    } catch (exception) {
      console.exception(exception);
    } finally {
      // Work around bug 582052 by preventing the (nonexistent) default action.
      event.preventDefault();
    }
  }
});
PK
!<>"0``)modules/commonjs/sdk/keyboard/observer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Class } = require("../core/heritage");
const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { browserWindowIterator } = require('../deprecated/window-utils');
const { isBrowser } = require('../window/utils');
const { observer: windowObserver } = require("../windows/observer");

// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
  implements: [DOMEventAssembler, EventTarget],
  initialize() {
    // Adding each opened window to a list of observed windows.
    windowObserver.on("open", window => {
      if (isBrowser(window))
        this.observe(window);
    });

    // Removing each closed window form the list of observed windows.
    windowObserver.on("close", window => {
      if (isBrowser(window))
        this.ignore(window);
    });

    // Making observer aware of already opened windows.
    for (let window of browserWindowIterator()) {
      this.observe(window);
    }
  },
  /**
   * Events that are supported and emitted by the module.
   */
  supportedEventsTypes: [ "keydown", "keyup", "keypress" ],
  /**
   * Function handles all the supported events on all the windows that are
   * observed. Method is used to proxy events to the listeners registered on
   * this event emitter.
   * @param {Event} event
   *    Keyboard event being emitted.
   */
  handleEvent(event) {
    emit(this, event.type, event, event.target.ownerGlobal || undefined);
  }
});

exports.observer = new Observer();
PK
!</LL&modules/commonjs/sdk/keyboard/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require("chrome");
const runtime = require("../system/runtime");
const { isString } = require("../lang/type");
const array = require("../util/array");


const SWP = "{{SEPARATOR}}";
const SEPARATOR = "-"
const INVALID_COMBINATION = "Hotkey key combination must contain one or more " +
                            "modifiers and only one key";

// Map of modifier key mappings.
const MODIFIERS = exports.MODIFIERS = {
  'accel': runtime.OS === "Darwin" ? 'meta' : 'control',
  'meta': 'meta',
  'control': 'control',
  'ctrl': 'control',
  'option': 'alt',
  'command': 'meta',
  'alt': 'alt',
  'shift': 'shift'
};

// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`.
// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names.
// @See: http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
const CODES = exports.CODES = new function Codes() {
  let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent;
  // Names that will be substituted with a shorter analogs.
  let aliases = {
    'subtract':     '-',
    'add':          '+',
    'equals':       '=',
    'slash':        '/',
    'backslash':    '\\',
    'openbracket':  '[',
    'closebracket': ']',
    'quote':        '\'',
    'backquote':    '`',
    'period':       '.',
    'semicolon':    ';',
    'comma':        ','
  };

  // Normalizing keys and copying values to `this` object.
  Object.keys(nsIDOMKeyEvent).filter(function(key) {
    // Filter out only key codes.
    return key.indexOf('DOM_VK') === 0;
  }).map(function(key) {
    // Map to key:values
    return [ key, nsIDOMKeyEvent[key] ];
  }).map(function([key, value]) {
    return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ];
  }).forEach(function ([ key, value ]) {
    this[aliases[key] || key] = value;
  }, this);
};

// Inverted `CODES` hash of `code:key`.
const KEYS = exports.KEYS = new function Keys() {
  Object.keys(CODES).forEach(function(key) {
    this[CODES[key]] = key;
  }, this)
}

exports.getKeyForCode = function getKeyForCode(code) {
  return (code in KEYS) && KEYS[code];
};
exports.getCodeForKey = function getCodeForKey(key) {
  return (key in CODES) && CODES[key];
};

/**
 * Utility function that takes string or JSON that defines a `hotkey` and
 * returns normalized string version of it.
 * @param {JSON|String} hotkey
 * @param {String} [separator=" "]
 *    Optional string that represents separator used to concatenate keys in the
 *    given `hotkey`.
 * @returns {String}
 * @examples
 *
 *    require("keyboard/hotkeys").normalize("b Shift accel");
 *    // 'control shift b' -> on windows & linux
 *    // 'meta shift b'    -> on mac
 *    require("keyboard/hotkeys").normalize("alt-d-shift", "-");
 *    // 'alt shift d'
 */
var normalize = exports.normalize = function normalize(hotkey, separator) {
  if (!isString(hotkey))
    hotkey = toString(hotkey, separator);
  return toString(toJSON(hotkey, separator), separator);
};

/*
 * Utility function that splits a string of characters that defines a `hotkey`
 * into modifier keys and the defining key.
 * @param {String} hotkey
 * @param {String} [separator=" "]
 *    Optional string that represents separator used to concatenate keys in the
 *    given `hotkey`.
 * @returns {JSON}
 * @examples
 *
 *    require("keyboard/hotkeys").toJSON("accel shift b");
 *    // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux
 *    // { key: 'b', modifiers: [ 'meta', 'shift' ] }    -> on mac
 *
 *    require("keyboard/hotkeys").normalize("alt-d-shift", "-");
 *    // { key: 'd', modifiers: [ 'alt', 'shift' ] }
 */
var toJSON = exports.toJSON = function toJSON(hotkey, separator) {
  separator = separator || SEPARATOR;
  // Since default separator is `-`, combination may take form of `alt--`. To
  // avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where
  // `{{SEPARATOR}}` can be swapped later.
  hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP);

  let value = {};
  let modifiers = [];
  let keys = hotkey.split(separator);
  keys.forEach(function(name) {
    // If name is `SEPARATOR` than we swap it back.
    if (name === SWP)
      name = separator;
    if (name in MODIFIERS) {
      array.add(modifiers, MODIFIERS[name]);
    } else {
      if (!value.key)
        value.key = name;
      else
        throw new TypeError(INVALID_COMBINATION);
    }
  });

  if (!value.key)
      throw new TypeError(INVALID_COMBINATION);

  value.modifiers = modifiers.sort();
  return value;
};

/**
 * Utility function that takes object that defines a `hotkey` and returns
 * string representation of it.
 *
 * _Please note that this function does not validates data neither it normalizes
 * it, if you are unsure that data is well formed use `normalize` function
 * instead.
 *
 * @param {JSON} hotkey
 * @param {String} [separator=" "]
 *    Optional string that represents separator used to concatenate keys in the
 *    given `hotkey`.
 * @returns {String}
 * @examples
 *
 *    require("keyboard/hotkeys").toString({
 *      key: 'b',
 *      modifiers: [ 'control', 'shift' ]
 *    }, '+');
 *    // 'control+shift+b
 *
 */
var toString = exports.toString = function toString(hotkey, separator) {
  let keys = hotkey.modifiers.slice();
  keys.push(hotkey.key);
  return keys.join(separator || SEPARATOR);
};

/**
 * Utility function takes `key` name and returns `true` if it's function key
 * (F1, ..., F24) and `false` if it's not.
 */
var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) {
  var $
  return key[0].toLowerCase() === 'f' &&
         ($ = parseInt(key.substr(1)), 0 < $ && $ < 25);
};
PK
!<J0V9ZZ!modules/commonjs/sdk/l10n/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

lazyRequireModule(this, "./json/core", "json");
lazyRequireModule(this, "./properties/core", "properties");

const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "get",
                            () => json.usingJSON ? json.get : properties.get);

module.exports = Object.freeze({
  get get() { return get; }, // ... yeah.
});
PK
!<U!modules/commonjs/sdk/l10n/html.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { processes, remoteRequire } = require("../remote/parent");
remoteRequire("sdk/content/l10n-html");

var enabled = false;
function enable() {
  if (!enabled) {
    processes.port.emit("sdk/l10n/html/enable");
    enabled = true;
  }
}
exports.enable = enable;

function disable() {
  if (enabled) {
    processes.port.emit("sdk/l10n/html/disable");
    enabled = false;
  }
}
exports.disable = disable;

processes.forEvery(process => {
  process.port.emit(enabled ? "sdk/l10n/html/enable" : "sdk/l10n/html/disable");
});
PK
!<<KN&modules/commonjs/sdk/l10n/json/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";

module.metadata = {
  "stability": "unstable"
};

var usingJSON = false;
var hash = {}, bestMatchingLocale = null;
try {
  let data = require("@l10n/data");
  hash = data.hash;
  bestMatchingLocale = data.bestMatchingLocale;
  usingJSON = true;
}
catch(e) {}

exports.usingJSON = usingJSON;

// Returns the translation for a given key, if available.
exports.get = function get(k) {
  return k in hash ? hash[k] : null;
}

// Returns the full length locale code: ja-JP-mac, en-US or fr
exports.locale = function locale() {
  return bestMatchingLocale;
}

// Returns the short locale code: ja, en, fr
exports.language = function language() {
  return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
                            : "en";
}
PK
!<p[		#modules/commonjs/sdk/l10n/loader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require("chrome");
lazyRequire(this, "./locale", "getPreferedLocales", "findClosestLocale");
lazyRequire(this, "../net/url", "readURI");
lazyRequire(this, "../core/promise", "resolve");

function parseJsonURI(uri) {
  return readURI(uri).
    then(JSON.parse).
    catch(function (error) {
      throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
    });
}

// Returns the array stored in `locales.json` manifest that list available
// locales files
function getAvailableLocales(rootURI) {
  let uri = rootURI + "locales.json";
  return parseJsonURI(uri).then(function (manifest) {
    return "locales" in manifest &&
           Array.isArray(manifest.locales) ?
           manifest.locales : [];
  });
}

// Returns URI of the best locales file to use from the XPI
function getBestLocale(rootURI) {
  // Read localization manifest file that contains list of available languages
  return getAvailableLocales(rootURI).then(function (availableLocales) {
    // Retrieve list of prefered locales to use
    let preferedLocales = getPreferedLocales();

    // Compute the most preferable locale to use by using these two lists
    return findClosestLocale(availableLocales, preferedLocales);
  });
}

/**
 * Read localization files and returns a promise of data to put in `@l10n/data`
 * pseudo module, in order to allow l10n/json/core to fetch it.
 */
exports.load = function load(rootURI) {
  // First, search for a locale file:
  return getBestLocale(rootURI).then(function (bestMatchingLocale) {
    // It may be null if the addon doesn't have any locale file
    if (!bestMatchingLocale)
      return resolve(null);

    let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json";

    // Locale files only contains one big JSON object that is used as
    // an hashtable of: "key to translate" => "translated key"
    // TODO: We are likely to change this in order to be able to overload
    //       a specific key translation. For a specific package, module or line?
    return parseJsonURI(localeURI).then(function (json) {
      return {
        hash: json,
        bestMatchingLocale: bestMatchingLocale
      };
    });
  });
}
PK
!<\E#modules/commonjs/sdk/l10n/locale.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const prefs = require("../preferences/service");
const { Cu, Cc, Ci } = require("chrome");
const { Services } = Cu.import("resource://gre/modules/Services.jsm");

function getPreferedLocales(caseSensitve) {
  const locales = Services.locale.getRequestedLocales();

  // This API expects to always append en-US fallback, so for compatibility
  // reasons, we're going to inject it here.
  // See bug 1373061 for details.
  if (!locales.includes('en-US')) {
    locales.push('en-US');
  }
  return locales;
}
exports.getPreferedLocales = getPreferedLocales;

/**
 * Selects the closest matching locale from a list of locales.
 *
 * @param  aLocales
 *         An array of available locales
 * @param  aMatchLocales
 *         An array of prefered locales, ordered by priority. Most wanted first.
 *         Locales have to be in lowercase.
 *         If null, uses getPreferedLocales() results
 * @return the best match for the currently selected locale
 *
 * Stolen from http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm
 */
exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
  aMatchLocales = aMatchLocales || getPreferedLocales();

  // Holds the best matching localized resource
  let bestmatch = null;
  // The number of locale parts it matched with
  let bestmatchcount = 0;
  // The number of locale parts in the match
  let bestpartcount = 0;

  for (let locale of aMatchLocales) {
    let lparts = locale.split("-");
    for (let localized of aLocales) {
      let found = localized.toLowerCase();
      // Exact match is returned immediately
      if (locale == found)
        return localized;

      let fparts = found.split("-");
      /* If we have found a possible match and this one isn't any longer
         then we dont need to check further. */
      if (bestmatch && fparts.length < bestmatchcount)
        continue;

      // Count the number of parts that match
      let maxmatchcount = Math.min(fparts.length, lparts.length);
      let matchcount = 0;
      while (matchcount < maxmatchcount &&
             fparts[matchcount] == lparts[matchcount])
        matchcount++;

      /* If we matched more than the last best match or matched the same and
         this locale is less specific than the last best match. */
      if (matchcount > bestmatchcount ||
         (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
        bestmatch = localized;
        bestmatchcount = matchcount;
        bestpartcount = fparts.length;
      }
    }
    // If we found a valid match for this locale return it
    if (bestmatch)
      return bestmatch;
  }
  return null;
}
PK
!<))modules/commonjs/sdk/l10n/plural-rules.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file is automatically generated with /python-lib/plural-rules-generator.py
// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml

// Mapping of short locale name == to == > rule index in following list

module.metadata = {
  "stability": "unstable"
};

const LOCALES_TO_RULES = {
  "af": 3, 
  "ak": 4, 
  "am": 4, 
  "ar": 1, 
  "asa": 3, 
  "az": 0, 
  "be": 11, 
  "bem": 3, 
  "bez": 3, 
  "bg": 3, 
  "bh": 4, 
  "bm": 0, 
  "bn": 3, 
  "bo": 0, 
  "br": 20, 
  "brx": 3, 
  "bs": 11, 
  "ca": 3, 
  "cgg": 3, 
  "chr": 3, 
  "cs": 12, 
  "cy": 17, 
  "da": 3, 
  "de": 3, 
  "dv": 3, 
  "dz": 0, 
  "ee": 3, 
  "el": 3, 
  "en": 3, 
  "eo": 3, 
  "es": 3, 
  "et": 3, 
  "eu": 3, 
  "fa": 0, 
  "ff": 5, 
  "fi": 3, 
  "fil": 4, 
  "fo": 3, 
  "fr": 5, 
  "fur": 3, 
  "fy": 3, 
  "ga": 8, 
  "gd": 24, 
  "gl": 3, 
  "gsw": 3, 
  "gu": 3, 
  "guw": 4, 
  "gv": 23, 
  "ha": 3, 
  "haw": 3, 
  "he": 2, 
  "hi": 4, 
  "hr": 11, 
  "hu": 0, 
  "id": 0, 
  "ig": 0, 
  "ii": 0, 
  "is": 3, 
  "it": 3, 
  "iu": 7, 
  "ja": 0, 
  "jmc": 3, 
  "jv": 0, 
  "ka": 0, 
  "kab": 5, 
  "kaj": 3, 
  "kcg": 3, 
  "kde": 0, 
  "kea": 0, 
  "kk": 3, 
  "kl": 3, 
  "km": 0, 
  "kn": 0, 
  "ko": 0, 
  "ksb": 3, 
  "ksh": 21, 
  "ku": 3, 
  "kw": 7, 
  "lag": 18, 
  "lb": 3, 
  "lg": 3, 
  "ln": 4, 
  "lo": 0, 
  "lt": 10, 
  "lv": 6, 
  "mas": 3, 
  "mg": 4, 
  "mk": 16, 
  "ml": 3, 
  "mn": 3, 
  "mo": 9, 
  "mr": 3, 
  "ms": 0, 
  "mt": 15, 
  "my": 0, 
  "nah": 3, 
  "naq": 7, 
  "nb": 3, 
  "nd": 3, 
  "ne": 3, 
  "nl": 3, 
  "nn": 3, 
  "no": 3, 
  "nr": 3, 
  "nso": 4, 
  "ny": 3, 
  "nyn": 3, 
  "om": 3, 
  "or": 3, 
  "pa": 3, 
  "pap": 3, 
  "pl": 13, 
  "ps": 3, 
  "pt": 3, 
  "rm": 3, 
  "ro": 9, 
  "rof": 3, 
  "ru": 11, 
  "rwk": 3, 
  "sah": 0, 
  "saq": 3, 
  "se": 7, 
  "seh": 3, 
  "ses": 0, 
  "sg": 0, 
  "sh": 11, 
  "shi": 19, 
  "sk": 12, 
  "sl": 14, 
  "sma": 7, 
  "smi": 7, 
  "smj": 7, 
  "smn": 7, 
  "sms": 7, 
  "sn": 3, 
  "so": 3, 
  "sq": 3, 
  "sr": 11, 
  "ss": 3, 
  "ssy": 3, 
  "st": 3, 
  "sv": 3, 
  "sw": 3, 
  "syr": 3, 
  "ta": 3, 
  "te": 3, 
  "teo": 3, 
  "th": 0, 
  "ti": 4, 
  "tig": 3, 
  "tk": 3, 
  "tl": 4, 
  "tn": 3, 
  "to": 0, 
  "tr": 0, 
  "ts": 3, 
  "tzm": 22, 
  "uk": 11, 
  "ur": 3, 
  "ve": 3, 
  "vi": 0, 
  "vun": 3, 
  "wa": 4, 
  "wae": 3, 
  "wo": 0, 
  "xh": 3, 
  "xog": 3, 
  "yo": 0, 
  "zh": 0, 
  "zu": 3
};

// Utility functions for plural rules methods
function isIn(n, list) {
  return list.indexOf(n) !== -1;
}
function isBetween(n, start, end) {
  return start <= n && n <= end;
}

// List of all plural rules methods, that maps an integer to the plural form name to use
const RULES = {
  "0": function (n) {
    
    return "other"
  },
  "1": function (n) {
    if ((isBetween((n % 100), 3, 10)))
      return "few";
    if (n == 0)
      return "zero";
    if ((isBetween((n % 100), 11, 99)))
      return "many";
    if (n == 2)
      return "two";
    if (n == 1)
      return "one";
    return "other"
  },
  "2": function (n) {
    if (n != 0 && (n % 10) == 0)
      return "many";
    if (n == 2)
      return "two";
    if (n == 1)
      return "one";
    return "other"
  },
  "3": function (n) {
    if (n == 1)
      return "one";
    return "other"
  },
  "4": function (n) {
    if ((isBetween(n, 0, 1)))
      return "one";
    return "other"
  },
  "5": function (n) {
    if ((isBetween(n, 0, 2)) && n != 2)
      return "one";
    return "other"
  },
  "6": function (n) {
    if (n == 0)
      return "zero";
    if ((n % 10) == 1 && (n % 100) != 11)
      return "one";
    return "other"
  },
  "7": function (n) {
    if (n == 2)
      return "two";
    if (n == 1)
      return "one";
    return "other"
  },
  "8": function (n) {
    if ((isBetween(n, 3, 6)))
      return "few";
    if ((isBetween(n, 7, 10)))
      return "many";
    if (n == 2)
      return "two";
    if (n == 1)
      return "one";
    return "other"
  },
  "9": function (n) {
    if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
      return "few";
    if (n == 1)
      return "one";
    return "other"
  },
  "10": function (n) {
    if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
      return "few";
    if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
      return "one";
    return "other"
  },
  "11": function (n) {
    if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
      return "few";
    if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
      return "many";
    if ((n % 10) == 1 && (n % 100) != 11)
      return "one";
    return "other"
  },
  "12": function (n) {
    if ((isBetween(n, 2, 4)))
      return "few";
    if (n == 1)
      return "one";
    return "other"
  },
  "13": function (n) {
    if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
      return "few";
    if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
      return "many";
    if (n == 1)
      return "one";
    return "other"
  },
  "14": function (n) {
    if ((isBetween((n % 100), 3, 4)))
      return "few";
    if ((n % 100) == 2)
      return "two";
    if ((n % 100) == 1)
      return "one";
    return "other"
  },
  "15": function (n) {
    if (n == 0 || (isBetween((n % 100), 2, 10)))
      return "few";
    if ((isBetween((n % 100), 11, 19)))
      return "many";
    if (n == 1)
      return "one";
    return "other"
  },
  "16": function (n) {
    if ((n % 10) == 1 && n != 11)
      return "one";
    return "other"
  },
  "17": function (n) {
    if (n == 3)
      return "few";
    if (n == 0)
      return "zero";
    if (n == 6)
      return "many";
    if (n == 2)
      return "two";
    if (n == 1)
      return "one";
    return "other"
  },
  "18": function (n) {
    if (n == 0)
      return "zero";
    if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
      return "one";
    return "other"
  },
  "19": function (n) {
    if ((isBetween(n, 2, 10)))
      return "few";
    if ((isBetween(n, 0, 1)))
      return "one";
    return "other"
  },
  "20": function (n) {
    if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
      return "few";
    if ((n % 1000000) == 0 && n != 0)
      return "many";
    if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
      return "two";
    if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
      return "one";
    return "other"
  },
  "21": function (n) {
    if (n == 0)
      return "zero";
    if (n == 1)
      return "one";
    return "other"
  },
  "22": function (n) {
    if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
      return "one";
    return "other"
  },
  "23": function (n) {
    if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
      return "one";
    return "other"
  },
  "24": function (n) {
    if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
      return "few";
    if (isIn(n, [2, 12]))
      return "two";
    if (isIn(n, [1, 11]))
      return "one";
    return "other"
  },
};

/**
  * Return a function that gives the plural form name for a given integer
  * for the specified `locale`
  *   let fun = getRulesForLocale('en');
  *   fun(1)    -> 'one'
  *   fun(0)    -> 'other'
  *   fun(1000) -> 'other'
  */
exports.getRulesForLocale = function getRulesForLocale(locale) {
  let index = LOCALES_TO_RULES[locale];
  if (!(index in RULES)) {
    console.warn('Plural form unknown for locale "' + locale + '"');
    return function () { return "other"; };
  }
  return RULES[index];
}

PK
!<ߨ"modules/commonjs/sdk/l10n/prefs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

lazyRequire(this, "../system/events", "on");
lazyRequireModule(this, "./core", "core");
const { id: jetpackId } = require('../self');

const OPTIONS_DISPLAYED = "addon-options-displayed";

function enable() {
  on(OPTIONS_DISPLAYED, onOptionsDisplayed);  
}
exports.enable = enable;

function onOptionsDisplayed({ subject: document, data: addonId }) {
  if (addonId !== jetpackId)
    return;
  localizeInlineOptions(document);
}

function localizeInlineOptions(document) {
  let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
              'button[data-jetpack-id="' + jetpackId + '"][pref-name]';
  let nodes = document.querySelectorAll(query);
  for (let node of nodes) {
    let name = node.getAttribute("pref-name");
    if (node.tagName == "setting") {
      let desc = core.get(name + "_description");
      if (desc)
        node.setAttribute("desc", desc);
      let title = core.get(name + "_title");
      if (title)
        node.setAttribute("title", title);

      for (let item of node.querySelectorAll("menuitem, radio")) {
          let key = name + "_options." + item.getAttribute("label");
          let label = core.get(key);
          if (label)
            item.setAttribute("label", label);
      }
    }
    else if (node.tagName == "button") {
      let label = core.get(name + "_label");
      if (label)
        node.setAttribute("label", label);
    }
  }
}
exports.localizeInlineOptions = localizeInlineOptions;
PK
!<Q	^	^	,modules/commonjs/sdk/l10n/properties/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cu } = require("chrome");
lazyRequire(this, '../../url/utils', 'newURI');
lazyRequire(this, "../plural-rules", 'getRulesForLocale');
lazyRequire(this, '../locale', 'getPreferedLocales');
const { rootURI } = require("@loader/options");
const { Services } = require("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");

const baseURI = rootURI + "locale/";

XPCOMUtils.defineLazyGetter(this, "preferedLocales", () => getPreferedLocales(true));

// Make sure we don't get stale data after an update
// (See Bug 1300735 for rationale).
Services.strings.flushBundles();

function getLocaleURL(locale) {
  // if the locale is a valid chrome URI, return it
  try {
    let uri = newURI(locale);
    if (uri.scheme == 'chrome')
      return uri.spec;
  }
  catch(_) {}
  // otherwise try to construct the url
  return baseURI + locale + ".properties";
}

function getKey(locale, key) {
  let bundle = Services.strings.createBundle(getLocaleURL(locale));
  try {
    return bundle.GetStringFromName(key) + "";
  }
  catch (_) {}
  return undefined;
}

function get(key, n, locales) {
  // try this locale
  let locale = locales.shift();
  let localized;

  if (typeof n == 'number') {
    if (n == 0) {
      localized = getKey(locale, key + '[zero]');
    }
    else if (n == 1) {
      localized = getKey(locale, key + '[one]');
    }
    else if (n == 2) {
      localized = getKey(locale, key + '[two]');
    }

    if (!localized) {
      // Retrieve the plural mapping function
      let pluralForm = (getRulesForLocale(locale.split("-")[0].toLowerCase()) ||
                        getRulesForLocale("en"))(n);
      localized = getKey(locale, key + '[' + pluralForm + ']');
    }

    if (!localized) {
      localized = getKey(locale, key + '[other]');
    }
  }

  if (!localized) {
    localized = getKey(locale, key);
  }

  if (!localized) {
    localized = getKey(locale, key + '[other]');
  }

  if (localized) {
    return localized;
  }

  // try next locale
  if (locales.length)
    return get(key, n, locales);

  return undefined;
}
exports.get = (k, n) => get(k, n, Array.slice(preferedLocales));
PK
!<TUmodules/commonjs/sdk/l10n.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

lazyRequireModule(this, "./l10n/json/core", "json");
lazyRequire(this, "./l10n/core", {"get": "getKey"});
lazyRequireModule(this, "./l10n/properties/core", "properties");
lazyRequire(this, "./l10n/plural-rules", "getRulesForLocale");

const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");

// Retrieve the plural mapping function
XPCOMUtils.defineLazyGetter(this, "pluralMappingFunction",
                            () => getRulesForLocale(json.language()) ||
                                  getRulesForLocale("en"));

exports.get = function get(k) {
  // For now, we only accept a "string" as first argument
  // TODO: handle plural forms in gettext pattern
  if (typeof k !== "string")
    throw new Error("First argument of localization method should be a string");
  let n = arguments[1];

  // Get translation from big hashmap or default to hard coded string:
  let localized = getKey(k, n) || k;

  // # Simplest usecase:
  //   // String hard coded in source code:
  //   _("Hello world")
  //   // Identifier of a key stored in properties file
  //   _("helloString")
  if (arguments.length <= 1)
    return localized;

  let args = Array.slice(arguments);
  let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)];

  if (typeof localized == "object" && "other" in localized) {
    // # Plural form:
    //   // Strings hard coded in source code:
    //   _(["One download", "%d downloads"], 10);
    //   // Identifier of a key stored in properties file
    //   _("downloadNumber", 0);
    let n = arguments[1];

    // First handle simple universal forms that may not be mandatory
    // for each language, (i.e. not different than 'other' form,
    // but still usefull for better phrasing)
    // For example 0 in english is the same form than 'other'
    // but we accept 'zero' form if specified in localization file
    if (n === 0 && "zero" in localized)
      localized = localized["zero"];
    else if (n === 1 && "one" in localized)
      localized = localized["one"];
    else if (n === 2 && "two" in localized)
      localized = localized["two"];
    else {
      let pluralForm = pluralMappingFunction(n);
      if (pluralForm in localized)
        localized = localized[pluralForm];
      else // Fallback in case of error: missing plural form
        localized = localized["other"];
    }

    // Simulate a string with one placeholder:
    args = [null, n];
  }

  // # String with placeholders:
  //   // Strings hard coded in source code:
  //   _("Hello %s", username)
  //   // Identifier of a key stored in properties file
  //   _("helloString", username)
  // * We supports `%1s`, `%2s`, ... pattern in order to change arguments order
  // in translation.
  // * In case of plural form, we has `%d` instead of `%s`.
  let offset = 1;
  if (placeholders.length > 1) {
    args = placeholders;
  }

  localized = localized.replace(/%(\d*)[sd]/g, (v, n) => {
    let rv = args[n != "" ? n : offset];
    offset++;
    return rv;
  });

  return localized;
}
PK
!<tR	
	
2modules/commonjs/sdk/lang/functional/concurrent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { arity, name, derive, invoke } = require("./helpers");

lazyRequire(this, "sdk/timers", "setTimeout", "clearTimeout", "setImmediate");

/**
 * Takes a function and returns a wrapped one instead, calling which will call
 * original function in the next turn of event loop. This is basically utility
 * to do `setImmediate(function() { ... })`, with a difference that returned
 * function is reused, instead of creating a new one each time. This also allows
 * to use this functions as event listeners.
 */
const defer = f => derive(function(...args) {
  setImmediate(invoke, f, args, this);
}, f);
exports.defer = defer;
// Exporting `remit` alias as `defer` may conflict with promises.
exports.remit = defer;

/**
 * Much like setTimeout, invokes function after wait milliseconds. If you pass
 * the optional arguments, they will be forwarded on to the function when it is
 * invoked.
 */
const delay = function delay(f, ms, ...args) {
  setTimeout(() => f.apply(this, args), ms);
};
exports.delay = delay;

/**
 * From underscore's `_.debounce`
 * http://underscorejs.org
 * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Underscore may be freely distributed under the MIT license.
 */
const debounce = function debounce (fn, wait) {
  let timeout, args, context, timestamp, result;

  let later = function () {
    let last = Date.now() - timestamp;
    if (last < wait) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      result = fn.apply(context, args);
      context = args = null;
    }
  };

  return function (...aArgs) {
    context = this;
    args = aArgs;
    timestamp  = Date.now();
    if (!timeout) {
      timeout = setTimeout(later, wait);
    }

    return result;
  };
};
exports.debounce = debounce;

/**
 * From underscore's `_.throttle`
 * http://underscorejs.org
 * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Underscore may be freely distributed under the MIT license.
 */
const throttle = function throttle (func, wait, options) {
  let context, args, result;
  let timeout = null;
  let previous = 0;
  options || (options = {});
  let later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    context = args = null;
  };
  return function() {
    let now = Date.now();
    if (!previous && options.leading === false) previous = now;
    let remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0) {
      clearTimeout(timeout);
      timeout = null;
      previous = now;
      result = func.apply(context, args);
      context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};
exports.throttle = throttle;
PK
!<
W|%|%,modules/commonjs/sdk/lang/functional/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.

"use strict";

module.metadata = {
  "stability": "unstable"
}
const { arity, name, derive, invoke } = require("./helpers");

/**
 * Takes variadic numeber of functions and returns composed one.
 * Returned function pushes `this` pseudo-variable to the head
 * of the passed arguments and invokes all the functions from
 * left to right passing same arguments to them. Composite function
 * returns return value of the right most funciton.
 */
const method = (...lambdas) => {
  return function method(...args) {
    args.unshift(this);
    return lambdas.reduce((_, lambda) => lambda.apply(this, args),
                          void(0));
  };
};
exports.method = method;

/**
 * Invokes `callee` by passing `params` as an arguments and `self` as `this`
 * pseudo-variable. Returns value that is returned by a callee.
 * @param {Function} callee
 *    Function to invoke.
 * @param {Array} params
 *    Arguments to invoke function with.
 * @param {Object} self
 *    Object to be passed as a `this` pseudo variable.
 */
exports.invoke = invoke;

/**
 * Takes a function and bind values to one or more arguments, returning a new
 * function of smaller arity.
 *
 * @param {Function} fn
 *    The function to partial
 *
 * @returns The new function with binded values
 */
const partial = (f, ...curried) => {
  if (typeof(f) !== "function")
    throw new TypeError(String(f) + " is not a function");

  let fn = derive(function(...args) {
    return f.apply(this, curried.concat(args));
  }, f);
  fn.arity = arity(f) - curried.length;
  return fn;
};
exports.partial = partial;

/**
 * Returns function with implicit currying, which will continue currying until
 * expected number of argument is collected. Expected number of arguments is
 * determined by `fn.length`. Using this with variadic functions is stupid,
 * so don't do it.
 *
 * @examples
 *
 * var sum = curry(function(a, b) {
 *   return a + b
 * })
 * console.log(sum(2, 2)) // 4
 * console.log(sum(2)(4)) // 6
 */
const curry = new function() {
  const currier = (fn, arity, params) => {
    // Function either continues to curry arguments or executes function
    // if desired arguments have being collected.
    const curried = function(...input) {
      // Prepend all curried arguments to the given arguments.
      if (params) input.unshift.apply(input, params);
      // If expected number of arguments has being collected invoke fn,
      // othrewise return curried version Otherwise continue curried.
      return (input.length >= arity) ? fn.apply(this, input) :
             currier(fn, arity, input);
    };
    curried.arity = arity - (params ? params.length : 0);

    return curried;
  };

  return fn => currier(fn, arity(fn));
};
exports.curry = curry;

/**
 * Returns the composition of a list of functions, where each function consumes
 * the return value of the function that follows. In math terms, composing the
 * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
 * @example
 *
 *   var greet = function(name) { return "hi: " + name; };
 *   var exclaim = function(statement) { return statement + "!"; };
 *   var welcome = compose(exclaim, greet);
 *
 *   welcome('moe');    // => 'hi: moe!'
 */
function compose(...lambdas) {
  return function composed(...args) {
    let index = lambdas.length;
    while (0 <= --index)
      args = [lambdas[index].apply(this, args)];

    return args[0];
  };
}
exports.compose = compose;

/*
 * Returns the first function passed as an argument to the second,
 * allowing you to adjust arguments, run code before and after, and
 * conditionally execute the original function.
 * @example
 *
 *  var hello = function(name) { return "hello: " + name; };
 *  hello = wrap(hello, function(f) {
 *    return "before, " + f("moe") + ", after";
 *  });
 *
 *  hello();    // => 'before, hello: moe, after'
 */
const wrap = (f, wrapper) => derive(function wrapped(...args) {
  return wrapper.apply(this, [f].concat(args));
}, f);
exports.wrap = wrap;

/**
 * Returns the same value that is used as the argument. In math: f(x) = x
 */
const identity = value => value;
exports.identity = identity;

/**
 * Memoizes a given function by caching the computed result. Useful for
 * speeding up slow-running computations. If passed an optional hashFunction,
 * it will be used to compute the hash key for storing the result, based on
 * the arguments to the original function. The default hashFunction just uses
 * the first argument to the memoized function as the key.
 */
const memoize = (f, hasher) => {
  let memo = Object.create(null);
  let cache = new WeakMap();
  hasher = hasher || identity;
  return derive(function memoizer(...args) {
    const key = hasher.apply(this, args);
    const type = typeof(key);
    if (key && (type === "object" || type === "function")) {
      if (!cache.has(key))
        cache.set(key, f.apply(this, args));
      return cache.get(key);
    }
    else {
      if (!(key in memo))
        memo[key] = f.apply(this, args);
      return memo[key];
    }
  }, f);
};
exports.memoize = memoize;

/*
 * Creates a version of the function that can only be called one time. Repeated
 * calls to the modified function will have no effect, returning the value from
 * the original call. Useful for initialization functions, instead of having to
 * set a boolean flag and then check it later.
 */
const once = f => {
  let ran = false, cache;
  return derive(function(...args) {
    return ran ? cache : (ran = true, cache = f.apply(this, args));
  }, f);
};
exports.once = once;
// export cache as once will may be conflicting with event once a lot.
exports.cache = once;

// Takes a `f` function and returns a function that takes the same
// arguments as `f`, has the same effects, if any, and returns the
// opposite truth value.
const complement = f => derive(function(...args) {
  return args.length < arity(f) ? complement(partial(f, ...args)) :
         !f.apply(this, args);
}, f);
exports.complement = complement;

// Constructs function that returns `x` no matter what is it
// invoked with.
const constant = x => _ => x;
exports.constant = constant;

// Takes `p` predicate, `consequent` function and an optional
// `alternate` function and composes function that returns
// application of arguments over `consequent` if application over
// `p` is `true` otherwise returns application over `alternate`.
// If `alternate` is not a function returns `undefined`.
const when = (p, consequent, alternate) => {
  if (typeof(alternate) !== "function" && alternate !== void(0))
    throw TypeError("alternate must be a function");
  if (typeof(consequent) !== "function")
    throw TypeError("consequent must be a function");

  return function(...args) {
    return p.apply(this, args) ?
           consequent.apply(this, args) :
           alternate && alternate.apply(this, args);
  };
};
exports.when = when;

// Apply function that behaves as `apply` does in lisp:
// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
// apply(f, x) => f.apply(f, [x])
const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
exports.apply = apply;

// Returns function identical to given `f` but with flipped order
// of arguments.
const flip = f => derive(function(...args) {
  return f.apply(this, args.reverse());
}, f);
exports.flip = flip;

// Takes field `name` and `target` and returns value of that field.
// If `target` is `null` or `undefined` it would be returned back
// instead of attempt to access it's field. Function is implicitly
// curried, this allows accessor function generation by calling it
// with only `name` argument.
const field = curry((name, target) =>
  // Note: Permisive `==` is intentional.
  target == null ? target : target[name]);
exports.field = field;

// Takes `.` delimited string representing `path` to a nested field
// and a `target` to get it from. For convinience function is
// implicitly curried, there for accessors can be created by invoking
// it with just a `path` argument.
const query = curry((path, target) => {
  const names = path.split(".");
  const count = names.length;
  let index = 0;
  let result = target;
  // Note: Permisive `!=` is intentional.
  while (result != null && index < count) {
    result = result[names[index]];
    index = index + 1;
  }
  return result;
});
exports.query = query;

// Takes `Type` (constructor function) and a `value` and returns
// `true` if `value` is instance of the given `Type`. Function is
// implicitly curried this allows predicate generation by calling
// function with just first argument.
const isInstance = curry((Type, value) => value instanceof Type);
exports.isInstance = isInstance;

/*
 * Takes a funtion and returns a wrapped function that returns `this`
 */
const chainable = f => derive(function(...args) {
  f.apply(this, args);
  return this;
}, f);
exports.chainable = chainable;

// Functions takes `expected` and `actual` values and returns `true` if
// `expected === actual`. Returns curried function if called with less then
// two arguments.
//
// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
const is = curry((expected, actual) => actual === expected);
exports.is = is;

const isnt = complement(is);
exports.isnt = isnt;
PK
!<	M/modules/commonjs/sdk/lang/functional/helpers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.

"use strict";

module.metadata = {
  "stability": "unstable"
}

const arity = f => f.arity || f.length;
exports.arity = arity;

const name = f => f.displayName || f.name;
exports.name = name;

const derive = (f, source) => {
  f.displayName = name(source);
  f.arity = arity(source);
  return f;
};
exports.derive = derive;

const invoke = (callee, params, self) => callee.apply(self, params);
exports.invoke = invoke;
PK
!<#L'modules/commonjs/sdk/lang/functional.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { defer, remit, delay, debounce,
        throttle } = require("./functional/concurrent");
const { method, invoke, partial, curry, compose, wrap, identity, memoize, once,
        cache, complement, constant, when, apply, flip, field, query,
        isInstance, chainable, is, isnt } = require("./functional/core");

exports.defer = defer;
exports.remit = remit;
exports.delay = delay;
exports.debounce = debounce;
exports.throttle = throttle;

exports.method = method;
exports.invoke = invoke;
exports.partial = partial;
exports.curry = curry;
exports.compose = compose;
exports.wrap = wrap;
exports.identity = identity;
exports.memoize = memoize;
exports.once = once;
exports.cache = cache;
exports.complement = complement;
exports.constant = constant;
exports.when = when;
exports.apply = apply;
exports.flip = flip;
exports.field = field;
exports.query = query;
exports.isInstance = isInstance;
exports.chainable = chainable;
exports.is = is;
exports.isnt = isnt;
PK
!<kbl"+"+!modules/commonjs/sdk/lang/type.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

/**
 * Returns `true` if `value` is `undefined`.
 * @examples
 *    var foo; isUndefined(foo); // true
 *    isUndefined(0); // false
 */
function isUndefined(value) {
  return value === undefined;
}
exports.isUndefined = isUndefined;

/**
 * Returns `true` if value is `null`.
 * @examples
 *    isNull(null); // true
 *    isNull(undefined); // false
 */
function isNull(value) {
  return value === null;
}
exports.isNull = isNull;

/**
 * Returns `true` if value is `null` or `undefined`.
 * It's equivalent to `== null`, but resolve the ambiguity of the writer
 * intention, makes clear that he's clearly checking both `null` and `undefined`
 * values, and it's not a typo for `=== null`.
 */
function isNil(value) {
  return value === null || value === undefined;
}
exports.isNil = isNil;

function isBoolean(value) {
  return typeof value === "boolean";
}
exports.isBoolean = isBoolean;
/**
 * Returns `true` if value is a string.
 * @examples
 *    isString("moe"); // true
 */
function isString(value) {
  return typeof value === "string";
}
exports.isString = isString;

/**
 * Returns `true` if `value` is a number.
 * @examples
 *    isNumber(8.4 * 5); // true
 */
function isNumber(value) {
  return typeof value === "number";
}
exports.isNumber = isNumber;

/**
 * Returns `true` if `value` is a `RegExp`.
 * @examples
 *    isRegExp(/moe/); // true
 */
function isRegExp(value) {
  return isObject(value) && instanceOf(value, RegExp);
}
exports.isRegExp = isRegExp;

/**
 * Returns true if `value` is a `Date`.
 * @examples
 *    isDate(new Date()); // true
 */
function isDate(value) {
  return isObject(value) && instanceOf(value, Date);
}
exports.isDate = isDate;

/**
 * Returns true if object is a Function.
 * @examples
 *    isFunction(function foo(){}) // true
 */
function isFunction(value) {
    return typeof value === "function";
}
exports.isFunction = isFunction;

/**
 * Returns `true` if `value` is an object (please note that `null` is considered
 * to be an atom and not an object).
 * @examples
 *    isObject({}) // true
 *    isObject(null) // false
 */
function isObject(value) {
    return typeof value === "object" && value !== null;
}
exports.isObject = isObject;

/**
 * Detect whether a value is a generator.
 *
 * @param aValue
 *        The value to identify.
 * @return A boolean indicating whether the value is a generator.
 */
function isGenerator(aValue) {
  return !!(aValue && aValue.isGenerator && aValue.isGenerator());
}
exports.isGenerator = isGenerator;

/**
 * Returns true if `value` is an Array.
 * @examples
 *    isArray([1, 2, 3])  // true
 *    isArray({ 0: 'foo', length: 1 }) // false
 */
var isArray = Array.isArray;
exports.isArray = isArray;

/**
 * Returns `true` if `value` is an Arguments object.
 * @examples
 *    (function(){ return isArguments(arguments); })(1, 2, 3); // true
 *    isArguments([1,2,3]); // false
 */
function isArguments(value) {
  return Object.prototype.toString.call(value) === "[object Arguments]";
}
exports.isArguments = isArguments;

var isMap = value => Object.prototype.toString.call(value) === "[object Map]"
exports.isMap = isMap;

var isSet = value => Object.prototype.toString.call(value) === "[object Set]"
exports.isSet = isSet;

/**
 * Returns true if it is a primitive `value`. (null, undefined, number,
 * boolean, string)
 * @examples
 *    isPrimitive(3) // true
 *    isPrimitive('foo') // true
 *    isPrimitive({ bar: 3 }) // false
 */
function isPrimitive(value) {
  return !isFunction(value) && !isObject(value);
}
exports.isPrimitive = isPrimitive;

/**
 * Returns `true` if given `object` is flat (it is direct decedent of
 * `Object.prototype` or `null`).
 * @examples
 *    isFlat({}) // true
 *    isFlat(new Type()) // false
 */
function isFlat(object) {
  return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
                              isNull(Object.getPrototypeOf(
                                     Object.getPrototypeOf(object))));
}
exports.isFlat = isFlat;

/**
 * Returns `true` if object contains no values.
 */
function isEmpty(object) {
  if (isObject(object)) {
    for (var key in object)
      return false;
    return true;
  }
  return false;
}
exports.isEmpty = isEmpty;

/**
 * Returns `true` if `value` is an array / flat object containing only atomic
 * values and other flat objects.
 */
function isJSON(value, visited) {
    // Adding value to array of visited values.
    (visited || (visited = [])).push(value);
            // If `value` is an atom return `true` cause it's valid JSON.
    return  isPrimitive(value) ||
            // If `value` is an array of JSON values that has not been visited
            // yet.
            (isArray(value) &&  value.every(function(element) {
                                  return isJSON(element, visited);
                                })) ||
            // If `value` is a plain object containing properties with a JSON
            // values it's a valid JSON.
            (isFlat(value) && Object.keys(value).every(function(key) {
                var $ = Object.getOwnPropertyDescriptor(value, key);
                // Check every proprety of a plain object to verify that
                // it's neither getter nor setter, but a JSON value, that
                // has not been visited yet.
                return  ((!isObject($.value) || !~visited.indexOf($.value)) &&
                        !('get' in $) && !('set' in $) &&
                        isJSON($.value, visited));
            }));
}
exports.isJSON = function (value) {
  return isJSON(value);
};

/**
 * Returns `true` if `value` is JSONable
 */
const isJSONable = (value) => {
  try {
    JSON.parse(JSON.stringify(value));
  }
  catch (e) {
    return false;
  }
  return true;
};
exports.isJSONable = isJSONable;

/**
 * Returns if `value` is an instance of a given `Type`. This is exactly same as
 * `value instanceof Type` with a difference that `Type` can be from a scope
 * that has a different top level object. (Like in case where `Type` is a
 * function from different iframe / jetpack module / sandbox).
 */
function instanceOf(value, Type) {
  var isConstructorNameSame;
  var isConstructorSourceSame;

  // If `instanceof` returned `true` we know result right away.
  var isInstanceOf = value instanceof Type;

  // If `instanceof` returned `false` we do ducktype check since `Type` may be
  // from a different sandbox. If a constructor of the `value` or a constructor
  // of the value's prototype has same name and source we assume that it's an
  // instance of the Type.
  if (!isInstanceOf && value) {
    isConstructorNameSame = value.constructor.name === Type.name;
    isConstructorSourceSame = String(value.constructor) == String(Type);
    isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
                    instanceOf(Object.getPrototypeOf(value), Type);
  }
  return isInstanceOf;
}
exports.instanceOf = instanceOf;

/**
 * Function returns textual representation of a value passed to it. Function
 * takes additional `indent` argument that is used for indentation. Also
 * optional `limit` argument may be passed to limit amount of detail returned.
 * @param {Object} value
 * @param {String} [indent="    "]
 * @param {Number} [limit]
 */
function source(value, indent, limit, offset, visited) {
  var result;
  var names;
  var nestingIndex;
  var isCompact = !isUndefined(limit);

  indent = indent || "    ";
  offset = (offset || "");
  result = "";
  visited = visited || [];

  if (isUndefined(value)) {
    result += "undefined";
  }
  else if (isNull(value)) {
    result += "null";
  }
  else if (isString(value)) {
    result += '"' + value + '"';
  }
  else if (isFunction(value)) {
    value = String(value).split("\n");
    if (isCompact && value.length > 2) {
      value = value.splice(0, 2);
      value.push("...}");
    }
    result += value.join("\n" + offset);
  }
  else if (isArray(value)) {
    if ((nestingIndex = (visited.indexOf(value) + 1))) {
      result = "#" + nestingIndex + "#";
    }
    else {
      visited.push(value);

      if (isCompact)
        value = value.slice(0, limit);

      result += "[\n";
      result += value.map(function(value) {
        return offset + indent + source(value, indent, limit, offset + indent,
                                        visited);
      }).join(",\n");
      result += isCompact && value.length > limit ?
                ",\n" + offset + "...]" : "\n" + offset + "]";
    }
  }
  else if (isObject(value)) {
    if ((nestingIndex = (visited.indexOf(value) + 1))) {
      result = "#" + nestingIndex + "#"
    }
    else {
      visited.push(value)

      names = Object.keys(value);

      result += "{ // " + value + "\n";
      result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
        var _limit = isCompact ? limit - 1 : limit;
        var descriptor = Object.getOwnPropertyDescriptor(value, name);
        var result = offset + indent + "// ";
        var accessor;
        if (0 <= name.indexOf(" "))
          name = '"' + name + '"';

        if (descriptor.writable)
          result += "writable ";
        if (descriptor.configurable)
          result += "configurable ";
        if (descriptor.enumerable)
          result += "enumerable ";

        result += "\n";
        if ("value" in descriptor) {
          result += offset + indent + name + ": ";
          result += source(descriptor.value, indent, _limit, indent + offset,
                           visited);
        }
        else {

          if (descriptor.get) {
            result += offset + indent + "get " + name + " ";
            accessor = source(descriptor.get, indent, _limit, indent + offset,
                              visited);
            result += accessor.substr(accessor.indexOf("{"));
          }

          if (descriptor.set) {
            result += offset + indent + "set " + name + " ";
            accessor = source(descriptor.set, indent, _limit, indent + offset,
                              visited);
            result += accessor.substr(accessor.indexOf("{"));
          }
        }
        return result;
      }).join(",\n");

      if (isCompact) {
        if (names.length > limit && limit > 0) {
          result += ",\n" + offset  + indent + "//...";
        }
      }
      else {
        if (names.length)
          result += ",";

        result += "\n" + offset + indent + '"__proto__": ';
        result += source(Object.getPrototypeOf(value), indent, 0,
                         offset + indent);
      }

      result += "\n" + offset + "}";
    }
  }
  else {
    result += String(value);
  }
  return result;
}
exports.source = function (value, indentation, limit) {
  return source(value, indentation, limit);
};
PK
!<y5++%modules/commonjs/sdk/lang/weak-set.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.metadata = {
  "stability": "experimental"
};

"use strict";

const { Cu } = require("chrome");

function makeGetterFor(Type) {
  let cache = new WeakMap();

  return {
    getFor(target) {
      if (!cache.has(target))
        cache.set(target, new Type());

      return cache.get(target);
    },
    clearFor(target) {
      return cache.delete(target)
    }
  }
}

var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap);
var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set);

function add(target, value) {
  if (has(target, value))
    return;

  getLookupFor(target).set(value, true);
  getRefsFor(target).add(Cu.getWeakReference(value));
}
exports.add = add;

function remove(target, value) {
  getLookupFor(target).delete(value);
}
exports.remove = remove;

function has(target, value) {
  return getLookupFor(target).has(value);
}
exports.has = has;

function clear(target) {
  clearLookupFor(target);
  clearRefsFor(target);
}
exports.clear = clear;

function iterator(target) {
  let refs = getRefsFor(target);

  for (let ref of refs) {
    let value = ref.get();

    // If `value` is already gc'ed, it would be `null`.
    // The `has` function is using a WeakMap as lookup table, so passing `null`
    // would raise an exception because WeakMap accepts as value only non-null
    // object.
    // Plus, if `value` is already gc'ed, we do not have to take it in account
    // during the iteration, and remove it from the references.
    if (value !== null && has(target, value))
      yield value;
    else
      refs.delete(ref);
  }
}
exports.iterator = iterator;
PK
!<I **)modules/commonjs/sdk/loader/cuddlefish.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

// This module is manually loaded by bootstrap.js in a sandbox and immediatly
// put in module cache so that it is never loaded in any other way.

/* Workarounds to include dependencies in the manifest
require('chrome')                  // Otherwise CFX will complain about Components
require('toolkit/loader')          // Otherwise CFX will stip out loader.js
require('sdk/addon/runner')        // Otherwise CFX will stip out addon/runner.js
*/

const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;

// `loadSandbox` is exposed by bootstrap.js
const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
                                     "toolkit/loader.js");
const xulappURI = module.uri.replace("loader/cuddlefish.js",
                                     "system/xul-app.jsm");
// We need to keep a reference to the sandbox in order to unload it in
// bootstrap.js

var loaderSandbox = loadSandbox(loaderURI);
const loaderModule = loaderSandbox.exports;

const { incompatibility } = Cu.import(xulappURI, {}).XulApp;

const { override, load } = loaderModule;

function CuddlefishLoader(options) {
  let { manifest } = options;

  options = override(options, {
    // Put `api-utils/loader` and `api-utils/cuddlefish` loaded as JSM to module
    // cache to avoid subsequent loads via `require`.
    modules: override({
      'toolkit/loader': loaderModule,
      'sdk/loader/cuddlefish': exports
    }, options.modules),
    resolve: function resolve(id, requirer) {
      let entry = requirer && requirer in manifest && manifest[requirer];
      let uri = null;

      // If manifest entry for this requirement is present we follow manifest.
      // Note: Standard library modules like 'panel' will be present in
      // manifest unless they were moved to platform.
      if (entry) {
        let requirement = entry.requirements[id];
        // If requirer entry is in manifest and it's requirement is not, than
        // it has no authority to load since linker was not able to find it.
        if (!requirement)
          throw Error('Module: ' + requirer + ' has no authority to load: '
                      + id, requirer);

        uri = requirement;
      } else {
        // If requirer is off manifest than it's a system module and we allow it
        // to go off manifest by resolving a relative path.
        uri = loaderModule.resolve(id, requirer);
      }
      return uri;
    },
    load: function(loader, module) {
      let result;
      let error;

      // In order to get the module's metadata, we need to load the module.
      // if an exception is raised here, it could be that is due to application
      // incompatibility. Therefore the exception is stored, and thrown again
      // only if the module seems be compatible with the application currently
      // running. Otherwise the incompatibility message takes the precedence.
      try {
        result = load(loader, module);
      }
      catch (e) {
        error = e;
      }

      error = incompatibility(module) || error;

      if (error)
        throw error;

      return result;
    }
  });

  let loader = loaderModule.Loader(options);
  // Hack to allow loading from `toolkit/loader`.
  loader.modules[loaderURI] = loaderSandbox;
  return loader;
}

exports = override(loaderModule, {
  Loader: CuddlefishLoader
});
PK
!<׼&modules/commonjs/sdk/loader/sandbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, CC, Cu } = require('chrome');
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                     getService(Ci.mozIJSSubScriptLoader);
const self = require('sdk/self');
const { getTabId } = require('../tabs/utils');
const { getInnerId } = require('../window/utils');

/**
 * Make a new sandbox that inherits given `source`'s principals. Source can be
 * URI string, DOMWindow or `null` for system principals.
 */
function sandbox(target, options) {
  options = options || {};
  options.metadata = options.metadata ? options.metadata : {};
  options.metadata.addonID = options.metadata.addonID ?
    options.metadata.addonID : self.id;

  let sandbox = Cu.Sandbox(target || systemPrincipal, options);
  Cu.setSandboxMetadata(sandbox, options.metadata);
  return sandbox;
}
exports.sandbox = sandbox;

/**
 * Evaluates given `source` in a given `sandbox` and returns result.
 */
function evaluate(sandbox, code, uri, line, version) {
  return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1);
}
exports.evaluate = evaluate;

/**
 * Evaluates code under the given `uri` in the given `sandbox`.
 *
 * @param {String} uri
 *    The URL pointing to the script to load.
 *    It must be a local chrome:, resource:, file: or data: URL.
 */
function load(sandbox, uri) {
  if (uri.indexOf('data:') === 0) {
    let source = uri.substr(uri.indexOf(',') + 1);

    return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0);
  } else {
    return scriptLoader.loadSubScriptWithOptions(uri, {target: sandbox,
                                                       charset: 'UTF-8',
                                                       wantReturnValue: true});
  }
}
exports.load = load;

/**
 * Forces the given `sandbox` to be freed immediately.
 */
exports.nuke = Cu.nukeSandbox
PK
!<fY!modules/commonjs/sdk/messaging.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { window } = require("sdk/addon/window");
exports.MessageChannel = window.MessageChannel;
exports.MessagePort = window.MessagePort;
PK
!<5'AWW"modules/commonjs/sdk/model/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { dispatcher } = require("../util/dispatcher");


// Define `modelFor` accessor function that can be implemented
// for different types of views. Since view's we'll be dealing
// with types that don't really play well with `instanceof`
// operator we're gonig to use `dispatcher` that is slight
// extension over polymorphic dispatch provided by method.
// This allows models to extend implementations of this by
// providing predicates:
//
// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id))
const modelFor = dispatcher("modelFor");
exports.modelFor = modelFor;
PK
!<(QN
N
modules/commonjs/sdk/net/url.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Ci, Cu, components } = require("chrome");

lazyRequire(this, "../core/promise", "defer");

const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

/**
 * Reads a URI and returns a promise.
 *
 * @param uri {string} The URI to read
 * @param [options] {object} This parameter can have any or all of the following
 * fields: `charset`. By default the `charset` is set to 'UTF-8'.
 *
 * @returns {promise}  The promise that will be resolved with the content of the
 *          URL given.
 *
 * @example
 *  let promise = readURI('resource://gre/modules/NetUtil.jsm', {
 *    charset: 'US-ASCII'
 *  });
 */
function readURI(uri, options) {
  options = options || {};
  let charset = options.charset || 'UTF-8';

  let channel = NetUtil.newChannel({
    uri: NetUtil.newURI(uri, charset),
    loadUsingSystemPrincipal: true});

  let { promise, resolve, reject } = defer();

  try {
    NetUtil.asyncFetch(channel, function (stream, result) {
      if (components.isSuccessCode(result)) {
        let count = stream.available();
        let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });

        resolve(data);
      } else {
        reject("Failed to read: '" + uri + "' (Error Code: " + result + ")");
      }
    });
  }
  catch (e) {
    reject("Failed to read: '" + uri + "' (Error: " + e.message + ")");
  }

  return promise;
}

exports.readURI = readURI;

/**
 * Reads a URI synchronously.
 * This function is intentionally undocumented to favorites the `readURI` usage.
 *
 * @param uri {string} The URI to read
 * @param [charset] {string} The character set to use when read the content of
 *        the `uri` given.  By default is set to 'UTF-8'.
 *
 * @returns {string} The content of the URI given.
 *
 * @example
 *  let data = readURISync('resource://gre/modules/NetUtil.jsm');
 */
function readURISync(uri, charset) {
  charset = typeof charset === "string" ? charset : "UTF-8";

  let channel = NetUtil.newChannel({
    uri: NetUtil.newURI(uri, charset),
    loadUsingSystemPrincipal: true});
  let stream = channel.open2();

  let count = stream.available();
  let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });

  stream.close();

  return data;
}

exports.readURISync = readURISync;
PK
!<modules/commonjs/sdk/net/xhr.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

const { deprecateFunction } = require("../util/deprecate");
const { Ci, Cu } = require("chrome");

Cu.importGlobalProperties(["XMLHttpRequest"]);

Object.defineProperties(XMLHttpRequest.prototype, {
  mozBackgroundRequest: {
    value: true,
  },
  forceAllowThirdPartyCookie: {
    configurable: true,
    value: deprecateFunction(function() {
      forceAllowThirdPartyCookie(this);

    }, "`xhr.forceAllowThirdPartyCookie()` is deprecated, please use" +
       "`require('sdk/net/xhr').forceAllowThirdPartyCookie(request)` instead")
  }
});
exports.XMLHttpRequest = XMLHttpRequest;

function forceAllowThirdPartyCookie(xhr) {
  if (xhr.channel instanceof Ci.nsIHttpChannelInternal)
    xhr.channel.forceAllowThirdPartyCookie = true;
}
exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie;

// No need to handle add-on unloads as addon/window is closed at unload
// and it will take down all the associated requests.
PK
!<YN%modules/commonjs/sdk/notifications.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable"
};

const { Cc, Ci, Cr } = require("chrome");
const apiUtils = require("./deprecated/api-utils");
const { isString, isUndefined, instanceOf } = require('./lang/type');
const { URL, isLocalURL } = require('./url');
const { data } = require('./self');

const NOTIFICATION_DIRECTIONS  = ["auto", "ltr", "rtl"];

try {
  let alertServ = Cc["@mozilla.org/alerts-service;1"].
                  getService(Ci.nsIAlertsService);

  // The unit test sets this to a mock notification function.
  var notify = alertServ.showAlertNotification.bind(alertServ);
}
catch (err) {
  // An exception will be thrown if the platform doesn't provide an alert
  // service, e.g., if Growl is not installed on OS X.  In that case, use a
  // mock notification function that just logs to the console.
  notify = notifyUsingConsole;
}

exports.notify = function notifications_notify(options) {
  let valOpts = validateOptions(options);
  let clickObserver = !valOpts.onClick ? null : {
    observe: (subject, topic, data) => {
      if (topic === "alertclickcallback") {
        try {
          valOpts.onClick.call(exports, valOpts.data);
        }
        catch(e) {
          console.exception(e);
        }
      }
    }
  };
  function notifyWithOpts(notifyFn) {
    let { iconURL } = valOpts;
    iconURL = iconURL && isLocalURL(iconURL) ? data.url(iconURL) : iconURL;

    notifyFn(iconURL, valOpts.title, valOpts.text, !!clickObserver,
             valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang);
  }
  try {
    notifyWithOpts(notify);
  }
  catch (err) {
    if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
      console.warn("The notification icon named by " + iconURL +
                   " does not exist.  A default icon will be used instead.");
      delete valOpts.iconURL;
      notifyWithOpts(notify);
    }
    else {
      notifyWithOpts(notifyUsingConsole);
    }
  }
};

function notifyUsingConsole(iconURL, title, text) {
  title = title ? "[" + title + "]" : "";
  text = text || "";
  let str = [title, text].filter(s => s).join(" ");
  console.log(str);
}

function validateOptions(options) {
  return apiUtils.validateOptions(options, {
    data: {
      is: ["string", "undefined"]
    },
    iconURL: {
      is: ["string", "undefined", "object"],
      ok: function(value) {
        return isUndefined(value) || isString(value) || (value instanceof URL);
      },
      msg: "`iconURL` must be a string or an URL instance."
    },
    onClick: {
      is: ["function", "undefined"]
    },
    text: {
      is: ["string", "undefined", "number"]
    },
    title: {
      is: ["string", "undefined", "number"]
    },
    tag: {
      is: ["string", "undefined", "number"]
    },
    dir: {
      is: ["string", "undefined"],
      ok: function(value) {
        return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value);
      },
      msg: '`dir` option must be one of: "auto", "ltr" or "rtl".'
    },
    lang: {
      is: ["string", "undefined"]
    }
  });
}
PK
!<%modules/commonjs/sdk/output/system.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cc, Ci, Cr } = require("chrome");
const { Input, start, stop, receive, outputs } = require("../event/utils");
const { id: addonID } = require("../self");
const { setImmediate } = require("../timers");
const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                             getService(Ci.nsIObserverService);

const NOT_AN_INPUT = "OutputPort can be used only for sending messages";

// `OutputPort` creates a port to which messages can be send. Those
// messages are actually disptached as `subject`'s of the observer
// notifications. This is handy for communicating between different
// components of the SDK. By default messages are dispatched
// asynchronously, although `options.sync` can be used to make them
// synchronous. If `options.id` is given `topic` for observer
// notifications is generated by namespacing it, to avoid spamming
// other SDK add-ons. It's also possible to provide `options.topic`
// to use excat `topic` without namespacing it.
//
// Note: Symmetric `new InputPort({ id: "x" })` instances can be used to
// receive messages send to the instances of `new OutputPort({ id: "x" })`.
const OutputPort = function({id, topic, sync}) {
  this.id = id || topic;
  this.sync = !!sync;
  this.topic = topic || "sdk:" + addonID + ":" + id;
};
// OutputPort extends base signal type to implement same message
// receiving interface.
OutputPort.prototype = new Input();
OutputPort.constructor = OutputPort;

// OutputPort can not be consumed there for starting or stopping it
// is not supported.
OutputPort.prototype[start] = _ => { throw TypeError(NOT_AN_INPUT); };
OutputPort.prototype[stop] = _ => { throw TypeError(NOT_AN_INPUT); };

// Port reecives message send to it, which will be dispatched via
// observer notification service.
OutputPort.receive = ({topic, sync}, message) => {
  const type = typeof(message);
  const supported = message === null ||
                    type === "object" ||
                    type === "function";

  // There is no sensible way to wrap JS primitives that would make sense
  // for general observer notification users. It's also probably not very
  // useful to dispatch JS primitives as subject of observer service, there
  // for we do not support those use cases.
  if (!supported)
    throw new TypeError("Unsupproted message type: `" + type + "`");

  // Normalize `message` to create a valid observer notification `subject`.
  // If `message` is `null`, implements `nsISupports` interface or already
  // represents wrapped JS object use it as is. Otherwise create a wrapped
  // object so that observers could receive it.
  const subject = message === null ? null :
                  message instanceof Ci.nsISupports ? message :
                  message.wrappedJSObject ? message :
                  {wrappedJSObject: message};
  if (sync)
    notifyObservers(subject, topic, null);
  else
    setImmediate(notifyObservers, subject, topic, null);
};
OutputPort.prototype[receive] = OutputPort.receive;
exports.OutputPort = OutputPort;
PK
!<7$.modules/commonjs/sdk/page-mod/match-pattern.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";

var { deprecateUsage } = require("../util/deprecate");

deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");

module.exports = require("../util/match-pattern");
PK
!<** modules/commonjs/sdk/page-mod.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

const { contract: loaderContract } = require('./content/loader');
const { contract } = require('./util/contract');
const { WorkerHost, connect } = require('./content/utils');
const { Class } = require('./core/heritage');
const { Disposable } = require('./core/disposable');
lazyRequire(this, './content/worker', "Worker");
const { EventTarget } = require('./event/target');
lazyRequire(this, './event/core', "on", "emit", "once", "setListeners");
lazyRequire(this, './lang/type', "isRegExp", "isUndefined");
const { merge, omit } = require('./util/object');
lazyRequire(this, "./util/array", "remove", "has", "hasAny");
lazyRequire(this, "./util/rules", "Rules");
const { processes, frames, remoteRequire } = require('./remote/parent');
remoteRequire('sdk/content/page-mod');

const pagemods = new Map();
const workers = new Map();
const models = new WeakMap();
var modelFor = (mod) => models.get(mod);
var workerFor = (mod) => workers.get(mod)[0];

// Helper functions
var isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string';

var PAGEMOD_ID = 0;

// Validation Contracts
const modOptions = {
  // contentStyle* / contentScript* are sharing the same validation constraints,
  // so they can be mostly reused, except for the messages.
  contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
    msg: 'The `contentStyle` option must be a string or an array of strings.'
  }),
  contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
    msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
  }),
  include: {
    is: ['string', 'array', 'regexp'],
    ok: (rule) => {
      if (isRegExpOrString(rule))
        return true;
      if (Array.isArray(rule) && rule.length > 0)
        return rule.every(isRegExpOrString);
      return false;
    },
    msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.'
  },
  exclude: {
    is: ['string', 'array', 'regexp', 'undefined'],
    ok: (rule) => {
      if (isRegExpOrString(rule) || isUndefined(rule))
        return true;
      if (Array.isArray(rule) && rule.length > 0)
        return rule.every(isRegExpOrString);
      return false;
    },
    msg: 'If set, the `exclude` option must always contain at least one ' +
      'rule as a string, regular expression, or an array of strings and ' +
      'regular expressions.'
  },
  attachTo: {
    is: ['string', 'array', 'undefined'],
    map: function (attachTo) {
      if (!attachTo) return ['top', 'frame'];
      if (typeof attachTo === 'string') return [attachTo];
      return attachTo;
    },
    ok: function (attachTo) {
      return hasAny(attachTo, ['top', 'frame']) &&
        attachTo.every(has.bind(null, ['top', 'frame', 'existing']));
    },
    msg: 'The `attachTo` option must be a string or an array of strings. ' +
      'The only valid options are "existing", "top" and "frame", and must ' +
      'contain at least "top" or "frame" values.'
  },
};

const modContract = contract(merge({}, loaderContract.rules, modOptions));

/**
 * PageMod constructor (exported below).
 * @constructor
 */
const PageMod = Class({
  implements: [
    modContract.properties(modelFor),
    EventTarget,
    Disposable,
  ],
  extends: WorkerHost(workerFor),
  setup: function PageMod(options) {
    let mod = this;
    let model = modContract(options);
    models.set(this, model);
    model.id = PAGEMOD_ID++;

    let include = model.include;
    model.include = Rules();
    model.include.add.apply(model.include, [].concat(include));

    let exclude = isUndefined(model.exclude) ? [] : model.exclude;
    model.exclude = Rules();
    model.exclude.add.apply(model.exclude, [].concat(exclude));

    // Set listeners on {PageMod} itself, not the underlying worker,
    // like `onMessage`, as it'll get piped.
    setListeners(this, options);

    pagemods.set(model.id, this);
    workers.set(this, []);

    function* serializeRules(rules) {
      for (let rule of rules) {
        yield isRegExp(rule) ? { type: "regexp", pattern: rule.source, flags: rule.flags }
                             : { type: "string", value: rule };
      }
    }

    model.childOptions = omit(model, ["include", "exclude", "contentScriptOptions"]);
    model.childOptions.include = [...serializeRules(model.include)];
    model.childOptions.exclude = [...serializeRules(model.exclude)];
    model.childOptions.contentScriptOptions = model.contentScriptOptions ?
                                              JSON.stringify(model.contentScriptOptions) :
                                              null;

    processes.port.emit('sdk/page-mod/create', model.childOptions);
  },

  dispose: function(reason) {
    processes.port.emit('sdk/page-mod/destroy', modelFor(this).id);
    pagemods.delete(modelFor(this).id);
    workers.delete(this);
  },

  destroy: function(reason) {
    // Explicit destroy call, i.e. not via unload so destroy the workers
    let list = workers.get(this);
    if (!list)
      return;

    // Triggers dispose which will cause the child page-mod to be destroyed
    Disposable.prototype.destroy.call(this, reason);

    // Destroy any active workers
    for (let worker of list)
      worker.destroy(reason);
  }
});
exports.PageMod = PageMod;

// Whenever a new process starts send over the list of page-mods
processes.forEvery(process => {
  for (let mod of pagemods.values())
    process.port.emit('sdk/page-mod/create', modelFor(mod).childOptions);
});

frames.port.on('sdk/page-mod/worker-create', (frame, modId, workerOptions) => {
  let mod = pagemods.get(modId);
  if (!mod)
    return;

  // Attach the parent side of the worker to the child
  let worker = Worker();

  workers.get(mod).unshift(worker);
  worker.on('*', (event, ...args) => {
    // page-mod's "attach" event needs to be passed a worker
    if (event === 'attach')
      emit(mod, event, worker)
    else
      emit(mod, event, ...args);
  });

  worker.on('detach', () => {
    let array = workers.get(mod);
    if (array)
      remove(array, worker);
  });

  connect(worker, frame, workerOptions);
});
PK
!<i2ss#modules/commonjs/sdk/page-worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

const { Class } = require('./core/heritage');
const { ns } = require('./core/namespace');
lazyRequire(this, './event/utils', "pipe", "stripListeners");
const { connect, destroy, WorkerHost } = require('./content/utils');
lazyRequire(this, './content/worker', "Worker");
const { Disposable } = require('./core/disposable');
const { EventTarget } = require('./event/target');
lazyRequire(this, './event/core', "setListeners");
lazyRequire(this, './addon/window', "window");
lazyRequire(this, './frame/utils', { "create": "makeFrame" }, "getDocShell");
const { contract } = require('./util/contract');
const { contract: loaderContract } = require('./content/loader');
lazyRequire(this, './util/rules', "Rules");
const { merge } = require('./util/object');
lazyRequire(this, './util/uuid', "uuid");
const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent");
remoteRequire("sdk/content/page-worker");

const workers = new WeakMap();
const pages = new Map();

const internal = ns();

let workerFor = (page) => workers.get(page);
let isDisposed = (page) => !pages.has(internal(page).id);

// The frame is used to ensure we have a remote process to load workers in
let remoteFrame = null;
let framePromise = null;
function getFrame() {
  if (framePromise)
    return framePromise;

  framePromise = new Promise(resolve => {
    let view = makeFrame(window.document, {
      namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
      nodeName: "iframe",
      type: "content",
      remote: useRemoteProcesses,
      uri: "about:blank"
    });

    // Wait for the remote side to connect
    let listener = (frame) => {
      if (frame.frameElement != view)
        return;
      frames.off("attach", listener);
      remoteFrame = frame;
      resolve(frame);
    }
    frames.on("attach", listener);
  });
  return framePromise;
}

var pageContract = contract(merge({
  allow: {
    is: ['object', 'undefined', 'null'],
    map: function (allow) { return { script: !allow || allow.script !== false }}
  },
  onMessage: {
    is: ['function', 'undefined']
  },
  include: {
    is: ['string', 'array', 'regexp', 'undefined']
  },
  contentScriptWhen: {
    is: ['string', 'undefined'],
    map: (when) => when || "end"
  }
}, loaderContract.rules));

function enableScript (page) {
  getDocShell(viewFor(page)).allowJavascript = true;
}

function disableScript (page) {
  getDocShell(viewFor(page)).allowJavascript = false;
}

function Allow (page) {
  return {
    get script() {
      return internal(page).options.allow.script;
    },
    set script(value) {
      internal(page).options.allow.script = value;

      if (isDisposed(page))
        return;

      remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value });
    }
  };
}

function isValidURL(page, url) {
  return !page.rules || page.rules.matchesAny(url);
}

const Page = Class({
  implements: [
    EventTarget,
    Disposable
  ],
  extends: WorkerHost(workerFor),
  setup: function Page(options) {
    options = pageContract(options);
    // Sanitize the options
    if ("contentScriptOptions" in options)
      options.contentScriptOptions = JSON.stringify(options.contentScriptOptions);

    internal(this).id = uuid().toString();
    internal(this).options = options;

    for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) {
      this[prop] = options[prop];
    }

    pages.set(internal(this).id, this);

    // Set listeners on the {Page} object itself, not the underlying worker,
    // like `onMessage`, as it gets piped
    setListeners(this, options);
    let worker = new Worker(stripListeners(options));
    workers.set(this, worker);
    pipe(worker, this);

    if (options.include) {
      this.rules = Rules();
      this.rules.add.apply(this.rules, [].concat(options.include));
    }

    getFrame().then(frame => {
      if (isDisposed(this))
        return;

      frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options));
    });
  },
  get allow() { return Allow(this); },
  set allow(value) {
    if (isDisposed(this))
      return;
    this.allow.script = pageContract({ allow: value }).allow.script;
  },
  get contentURL() {
    return internal(this).options.contentURL;
  },
  set contentURL(value) {
    if (!isValidURL(this, value))
      return;
    internal(this).options.contentURL = value;
    if (isDisposed(this))
      return;

    remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value });
  },
  dispose: function () {
    if (isDisposed(this))
      return;
    pages.delete(internal(this).id);
    let worker = workerFor(this);
    if (worker)
      destroy(worker);
    remoteFrame.port.emit("sdk/frame/destroy", internal(this).id);

    // Destroy the remote frame if all the pages have been destroyed
    if (pages.size == 0) {
      framePromise = null;
      remoteFrame.frameElement.remove();
      remoteFrame = null;
    }
  },
  toString: function () { return '[object Page]' }
});

exports.Page = Page;

frames.port.on("sdk/frame/connect", (frame, id, params) => {
  let page = pages.get(id);
  if (!page)
    return;
  connect(workerFor(page), frame, params);
});
PK
!<PLL$modules/commonjs/sdk/panel/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This module basically translates system/events to a SDK standard events
// so that `map`, `filter` and other utilities could be used with them.

module.metadata = {
  "stability": "experimental"
};

const events = require("../system/events");
lazyRequire(this, "../event/core", "emit");

var channel = {};

function forward({ subject, type, data }) {
  return emit(channel, "data", { target: subject, type: type, data: data });
}

["popupshowing", "popuphiding", "popupshown", "popuphidden",
"document-element-inserted", "DOMContentLoaded", "load"
].forEach(type => events.on(type, forward));

exports.events = channel;
PK
!<dҧF9F9#modules/commonjs/sdk/panel/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require("chrome");
const { Services } = require("resource://gre/modules/Services.jsm");
lazyRequire(this, "../timers", "setTimeout");
lazyRequire(this, "../system", "platform");
lazyRequire(this, "../window/utils", "getMostRecentBrowserWindow", "getOwnerBrowserWindow",
            "getScreenPixelsPerCSSPixel");

lazyRequire(this, "../frame/utils", { "create": "createFrame" }, "swapFrameLoaders", "getDocShell");
lazyRequire(this, "../addon/window", { "window": "addonWindow" });
lazyRequire(this, "../lang/type", "isNil");
lazyRequire(this, '../self', "data");

lazyRequireModule(this, "../system/events", "events");


const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
  position = position || {};

  let x, y;

  let hasTop = !isNil(position.top);
  let hasRight = !isNil(position.right);
  let hasBottom = !isNil(position.bottom);
  let hasLeft = !isNil(position.left);
  let hasWidth = !isNil(width);
  let hasHeight = !isNil(height);

  // if width is not specified by constructor or show's options, then get
  // the default width
  if (!hasWidth)
    width = defaultWidth;

  // if height is not specified by constructor or show's options, then get
  // the default height
  if (!hasHeight)
    height = defaultHeight;

  // default position is centered
  x = (rect.right - width) / 2;
  y = (rect.top + rect.bottom - height) / 2;

  if (hasTop) {
    y = rect.top + position.top;

    if (hasBottom && !hasHeight)
      height = rect.bottom - position.bottom - y;
  }
  else if (hasBottom) {
    y = rect.bottom - position.bottom - height;
  }

  if (hasLeft) {
    x = position.left;

    if (hasRight && !hasWidth)
      width = rect.right - position.right - x;
  }
  else if (hasRight) {
    x = rect.right - width - position.right;
  }

  return {x: x, y: y, width: width, height: height};
}

function open(panel, options, anchor) {
  // Wait for the XBL binding to be constructed
  if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor);
  else display(panel, options, anchor);
}
exports.open = open;

function isOpen(panel) {
  return panel.state === "open"
}
exports.isOpen = isOpen;

function isOpening(panel) {
  return panel.state === "showing"
}
exports.isOpening = isOpening

function close(panel) {
  // Sometimes "TypeError: panel.hidePopup is not a function" is thrown
  // when quitting the host application while a panel is visible.  To suppress
  // these errors, check for "hidePopup" in panel before calling it.
  // It's not clear if there's an issue or it's expected behavior.
  // See Bug 1151796.

  return panel.hidePopup && panel.hidePopup();
}
exports.close = close


function resize(panel, width, height) {
  // Resize the iframe instead of using panel.sizeTo
  // because sizeTo doesn't work with arrow panels
  if (panel.firstChild) {
    panel.firstChild.style.width = width + "px";
    panel.firstChild.style.height = height + "px";
  }
}
exports.resize = resize

function display(panel, options, anchor) {
  let document = panel.ownerDocument;

  let x, y;
  let { width, height, defaultWidth, defaultHeight } = options;

  let popupPosition = null;

  // Panel XBL has some SDK incompatible styling decisions. We shim panel
  // instances until proper fix for Bug 859504 is shipped.
  shimDefaultStyle(panel);

  if (!anchor) {
    // The XUL Panel doesn't have an arrow, so the margin needs to be reset
    // in order to, be positioned properly
    panel.style.margin = "0";

    let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();

    ({x, y, width, height} = calculateRegion(options, viewportRect));
  }
  else {
    // The XUL Panel has an arrow, so the margin needs to be reset
    // to the default value.
    panel.style.margin = "";
    let { CustomizableUI, window } = anchor.ownerGlobal;

    // In Australis, widgets may be positioned in an overflow panel or the
    // menu panel.
    // In such cases clicking this widget will hide the overflow/menu panel,
    // and the widget's panel will show instead.
    // If `CustomizableUI` is not available, it means the anchor is not in a
    // chrome browser window, and therefore there is no need for this check.
    if (CustomizableUI) {
      let node = anchor;
      ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));

      // if `node` is not the `anchor` itself, it means the widget is
      // positioned in a panel, therefore we have to hide it before show
      // the widget's panel in the same anchor
      if (node !== anchor)
        CustomizableUI.hidePanelForNode(anchor);
    }

    width = width || defaultWidth;
    height = height || defaultHeight;

    // Open the popup by the anchor.
    let rect = anchor.getBoundingClientRect();

    let zoom = getScreenPixelsPerCSSPixel(window);
    let screenX = rect.left + window.mozInnerScreenX * zoom;
    let screenY = rect.top + window.mozInnerScreenY * zoom;

    // Set up the vertical position of the popup relative to the anchor
    // (always display the arrow on anchor center)
    let horizontal, vertical;
    if (screenY > window.screen.availHeight / 2 + height)
      vertical = "top";
    else
      vertical = "bottom";

    if (screenY > window.screen.availWidth / 2 + width)
      horizontal = "left";
    else
      horizontal = "right";

    let verticalInverse = vertical == "top" ? "bottom" : "top";
    popupPosition = vertical + "center " + verticalInverse + horizontal;

    // Allow panel to flip itself if the panel can't be displayed at the
    // specified position (useful if we compute a bad position or if the
    // user moves the window and panel remains visible)
    panel.setAttribute("flip", "both");
  }

  if (!panel.viewFrame) {
    panel.viewFrame = document.importNode(panel.backgroundFrame, false);
    panel.appendChild(panel.viewFrame);

    let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes();
    let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId});
    getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal);
  }

  // Resize the iframe instead of using panel.sizeTo
  // because sizeTo doesn't work with arrow panels
  panel.firstChild.style.width = width + "px";
  panel.firstChild.style.height = height + "px";

  panel.openPopup(anchor, popupPosition, x, y);
}
exports.display = display;

// This utility function is just a workaround until Bug 859504 has shipped.
function shimDefaultStyle(panel) {
  let document = panel.ownerDocument;
  // Please note that `panel` needs to be part of document in order to reach
  // it's anonymous nodes. One of the anonymous node has a big padding which
  // doesn't work well since panel frame needs to fill all of the panel.
  // XBL binding is a not the best option as it's applied asynchronously, and
  // makes injected frames behave in strange way. Also this feels a lot
  // cheaper to do.
  ["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
    let node = document.getAnonymousElementByAttribute(panel, "class", value);
      if (node) node.style.padding = 0;
  });
}

function show(panel, options, anchor) {
  // Prevent the panel from getting focus when showing up
  // if focus is set to false
  panel.setAttribute("noautofocus", !options.focus);

  let window = anchor && getOwnerBrowserWindow(anchor);
  let { document } = window ? window : getMostRecentBrowserWindow();
  attach(panel, document);

  open(panel, options, anchor);
}
exports.show = show

function onPanelClick(event) {
  let { target, metaKey, ctrlKey, shiftKey, button } = event;
  let accel = platform === "darwin" ? metaKey : ctrlKey;
  let isLeftClick = button === 0;
  let isMiddleClick = button === 1;

  if ((isLeftClick && (accel || shiftKey)) || isMiddleClick) {
    let link = target.closest('a');

    if (link && link.href)
       getMostRecentBrowserWindow().openUILink(link.href, event)
  }
}

function setupPanelFrame(frame) {
  frame.setAttribute("flex", 1);
  frame.setAttribute("transparent", "transparent");
  frame.setAttribute("autocompleteenabled", true);
  frame.setAttribute("tooltip", "aHTMLTooltip");
  if (platform === "darwin") {
    frame.style.borderRadius = "var(--arrowpanel-border-radius, 3.5px)";
    frame.style.padding = "1px";
  }
}

function make(document, options) {
  document = document || getMostRecentBrowserWindow().document;
  let panel = document.createElementNS(XUL_NS, "panel");
  panel.setAttribute("type", "arrow");
  panel.setAttribute("sdkscriptenabled", options.allowJavascript);

  // The panel needs to be attached to a browser window in order for us
  // to copy browser styles to the content document when it loads.
  attach(panel, document);

  let frameOptions =  {
    allowJavascript: options.allowJavascript,
    allowPlugins: true,
    allowAuth: true,
    allowWindowControl: false,
    // Need to override `nodeName` to use `iframe` as `browsers` save session
    // history and in consequence do not dispatch "inner-window-destroyed"
    // notifications.
    browser: false,
  };

  let backgroundFrame = createFrame(addonWindow, frameOptions);
  setupPanelFrame(backgroundFrame);

  getDocShell(backgroundFrame).inheritPrivateBrowsingId = false;

  function onPopupShowing({type, target}) {
    if (target === this) {
      let attrs = getDocShell(backgroundFrame).getOriginAttributes();
      getDocShell(panel.viewFrame).setOriginAttributes(attrs);

      swapFrameLoaders(backgroundFrame, panel.viewFrame);
    }
  }

  function onPopupHiding({type, target}) {
    if (target === this) {
      swapFrameLoaders(backgroundFrame, panel.viewFrame);

      panel.viewFrame.remove();
      panel.viewFrame = null;
    }
  }

  function onContentReady({target, type}) {
    if (target === getContentDocument(panel)) {
      style(panel);
      events.emit(type, { subject: panel });
    }
  }

  function onContentLoad({target, type}) {
    if (target === getContentDocument(panel))
      events.emit(type, { subject: panel });
  }

  function onContentChange({subject: document, type}) {
    if (document === getContentDocument(panel) && document.defaultView)
      events.emit(type, { subject: panel });
  }

  function onPanelStateChange({target, type}) {
    if (target === this)
      events.emit(type, { subject: panel })
  }

  panel.addEventListener("popupshowing", onPopupShowing);
  panel.addEventListener("popuphiding", onPopupHiding);
  for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"])
    panel.addEventListener(event, onPanelStateChange);

  panel.addEventListener("click", onPanelClick);

  // Panel content document can be either in panel `viewFrame` or in
  // a `backgroundFrame` depending on panel state. Listeners are set
  // on both to avoid setting and removing listeners on panel state changes.

  panel.addEventListener("DOMContentLoaded", onContentReady, true);
  backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);

  panel.addEventListener("load", onContentLoad, true);
  backgroundFrame.addEventListener("load", onContentLoad, true);

  events.on("document-element-inserted", onContentChange);

  panel.backgroundFrame = backgroundFrame;
  panel.viewFrame = null;

  // Store event listener on the panel instance so that it won't be GC-ed
  // while panel is alive.
  panel.onContentChange = onContentChange;

  return panel;
}
exports.make = make;

function attach(panel, document) {
  document = document || getMostRecentBrowserWindow().document;
  let container = document.getElementById("mainPopupSet");
  if (container !== panel.parentNode) {
    detach(panel);
    document.getElementById("mainPopupSet").appendChild(panel);
  }
}
exports.attach = attach;

function detach(panel) {
  if (panel.parentNode) panel.remove();
}
exports.detach = detach;

function dispose(panel) {
  panel.backgroundFrame.remove();
  panel.backgroundFrame = null;
  events.off("document-element-inserted", panel.onContentChange);
  panel.onContentChange = null;
  detach(panel);
}
exports.dispose = dispose;

function style(panel) {
  /**
  Injects default OS specific panel styles into content document that is loaded
  into given panel. Optionally `document` of the browser window can be
  given to inherit styles from it, by default it will use either panel owner
  document or an active browser's document. It should not matter though unless
  Firefox decides to style windows differently base on profile or mode like
  chrome for example.
  **/

  try {
    let document = panel.ownerDocument;
    let contentDocument = getContentDocument(panel);
    let window = document.defaultView;
    let node = document.getAnonymousElementByAttribute(panel, "class",
                                                       "panel-arrowcontent");

    let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);

    let style = contentDocument.createElement("style");
    style.id = "sdk-panel-style";
    style.textContent = "body { " +
      "color: " + color + ";" +
      "font-family: " + fontFamily + ";" +
      "font-weight: " + fontWeight + ";" +
      "font-size: " + fontSize + ";" +
    "}";

    let container = contentDocument.head ? contentDocument.head :
                    contentDocument.documentElement;

    if (container.firstChild)
      container.insertBefore(style, container.firstChild);
    else
      container.appendChild(style);
  }
  catch (error) {
    console.error("Unable to apply panel style");
    console.exception(error);
  }
}
exports.style = style;

var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame;
exports.getContentFrame = getContentFrame;

function getContentDocument(panel) {
  return getContentFrame(panel).contentDocument;
}
exports.getContentDocument = getContentDocument;

function setURL(panel, url) {
  let frame = getContentFrame(panel);
  let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation);

  webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null);
}

exports.setURL = setURL;

function allowContextMenu(panel, allow) {
  if (allow) {
    panel.setAttribute("context", "contentAreaContextMenu");
  }
  else {
    panel.removeAttribute("context");
  }
}
exports.allowContextMenu = allowContextMenu;
PK
!<`44modules/commonjs/sdk/panel.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// The panel module currently supports only Firefox and SeaMonkey.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
  "stability": "stable",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cu, Ci } = require("chrome");
lazyRequire(this, './timers', "setTimeout");
const { Class } = require("./core/heritage");
const { DefaultWeakMap, merge } = require("./util/object");
const { WorkerHost } = require("./content/utils");
lazyRequire(this, "./deprecated/sync-worker", "Worker");
const { Disposable } = require("./core/disposable");
const { WeakReference } = require('./core/reference');
const { contract: loaderContract } = require("./content/loader");
const { contract } = require("./util/contract");
lazyRequire(this, "./event/core", "on", "off", "emit", "setListeners");
const { EventTarget } = require("./event/target");
lazyRequireModule(this, "./panel/utils", "domPanel");
lazyRequire(this, './frame/utils', "getDocShell");
const { events } = require("./panel/events");
const { filter, pipe, stripListeners } = require("./event/utils");
lazyRequire(this, "./view/core", "getNodeView", "getActiveView");
lazyRequire(this, "./lang/type", "isNil", "isObject", "isNumber");
lazyRequire(this, "./content/utils", "getAttachEventType");
const { number, boolean, object } = require('./deprecated/api-utils');
lazyRequire(this, "./stylesheet/style", "Style");
lazyRequire(this, "./content/mod", "attach", "detach");

var isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
  some(value => isNumber(value) && !isNaN(value));

var isSDKObj = obj => obj instanceof Class;

var rectContract = contract({
  top: number,
  right: number,
  bottom: number,
  left: number
});

var position = {
  is: object,
  map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
  ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
  msg: 'The option "position" must be a SDK object registered as anchor; ' +
        'or an object with one or more of the following keys set to numeric ' +
        'values: top, right, bottom, left.'
}

var displayContract = contract({
  width: number,
  height: number,
  focus: boolean,
  position: position
});

var panelContract = contract(merge({
  // contentStyle* / contentScript* are sharing the same validation constraints,
  // so they can be mostly reused, except for the messages.
  contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
    msg: 'The `contentStyle` option must be a string or an array of strings.'
  }),
  contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
    msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
  }),
  contextMenu: boolean,
  allow: {
    is: ['object', 'undefined', 'null'],
    map: function (allow) { return { script: !allow || allow.script !== false }}
  },
}, displayContract.rules, loaderContract.rules));

function Allow(panel) {
  return {
    get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; },
    set script(value) { return setScriptState(panel, value); },
  };
}

function setScriptState(panel, value) {
  let view = viewFor(panel);
  getDocShell(view.backgroundFrame).allowJavascript = value;
  getDocShell(view.viewFrame).allowJavascript = value;
  view.setAttribute("sdkscriptenabled", "" + value);
}

function isDisposed(panel) {
  return !views.has(panel);
}

var optionsMap = new WeakMap();
var panels = new WeakMap();
var models = new WeakMap();
var views = new DefaultWeakMap(panel => {
  let model = models.get(panel);

  // Setup view
  let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)};
  let view = domPanel.make(null, viewOptions);
  panels.set(view, panel);

  // Load panel content.
  domPanel.setURL(view, model.contentURL);

  // Allow context menu
  domPanel.allowContextMenu(view, model.contextMenu);

  return view;
});
var workers = new DefaultWeakMap(panel => {
  let options = optionsMap.get(panel);

  let worker = new Worker(stripListeners(options));
  workers.set(panel, worker);

  // pipe events from worker to a panel.
  pipe(worker, panel);

  return worker;
});
var styles = new WeakMap();

const viewFor = (panel) => views.get(panel);
const modelFor = (panel) => models.get(panel);
const panelFor = (view) => panels.get(view);
const workerFor = (panel) => workers.get(panel);
const styleFor = (panel) => styles.get(panel);

function getPanelFromWeakRef(weakRef) {
  if (!weakRef) {
    return null;
  }
  let panel = weakRef.get();
  if (!panel) {
    return null;
  }
  if (isDisposed(panel)) {
    return null;
  }
  return panel;
}

var SinglePanelManager = {
  visiblePanel: null,
  enqueuedPanel: null,
  enqueuedPanelCallback: null,
  // Calls |callback| with no arguments when the panel may be shown.
  requestOpen: function(panelToOpen, callback) {
    let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
    if (currentPanel || SinglePanelManager.enqueuedPanel) {
      SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
      SinglePanelManager.enqueuedPanelCallback = callback;
      if (currentPanel && currentPanel.isShowing) {
        currentPanel.hide();
      }
    } else {
      SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
    }
  },
  notifyPanelCanOpen: function(panel, callback) {
    let view = viewFor(panel);
    // Can't pass an arrow function as the event handler because we need to be
    // able to call |removeEventListener| later.
    view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
    view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
    SinglePanelManager.enqueuedPanel = null;
    SinglePanelManager.enqueuedPanelCallback = null;
    SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
    callback();
  },
  onVisiblePanelShown: function(event) {
    let panel = panelFor(event.target);
    if (SinglePanelManager.enqueuedPanel) {
      // Another panel started waiting for |panel| to close before |panel| was
      // even done opening.
      panel.hide();
    }
  },
  onVisiblePanelHidden: function(event) {
    let view = event.target;
    let panel = panelFor(view);
    let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
    if (currentPanel && currentPanel != panel) {
      return;
    }
    SinglePanelManager.visiblePanel = null;
    view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
    view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown);
    let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
    let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
    if (nextPanel) {
      SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
    }
  }
};

const Panel = Class({
  implements: [
    // Generate accessors for the validated properties that update model on
    // set and return values from model on get.
    panelContract.properties(modelFor),
    EventTarget,
    Disposable,
    WeakReference
  ],
  extends: WorkerHost(workerFor),
  setup: function setup(options) {
    let model = merge({
      defaultWidth: 320,
      defaultHeight: 240,
      focus: true,
      position: Object.freeze({}),
      contextMenu: false
    }, panelContract(options));
    model.ready = false;
    models.set(this, model);

    if (model.contentStyle || model.contentStyleFile) {
      styles.set(this, Style({
        uri: model.contentStyleFile,
        source: model.contentStyle
      }));
    }

    optionsMap.set(this, options);

    // Setup listeners.
    setListeners(this, options);
  },
  dispose: function dispose() {
    if (views.has(this))
      this.hide();
    off(this);

    workerFor(this).destroy();
    detach(styleFor(this));

    if (views.has(this))
      domPanel.dispose(viewFor(this));

    views.delete(this);
  },
  /* Public API: Panel.width */
  get width() {
    return modelFor(this).width;
  },
  set width(value) {
    this.resize(value, this.height);
  },
  /* Public API: Panel.height */
  get height() {
    return modelFor(this).height;
  },
  set height(value) {
    this.resize(this.width, value);
  },

  /* Public API: Panel.focus */
  get focus() {
    return modelFor(this).focus;
  },

  /* Public API: Panel.position */
  get position() {
    return modelFor(this).position;
  },

  /* Public API: Panel.contextMenu */
  get contextMenu() {
    return modelFor(this).contextMenu;
  },
  set contextMenu(allow) {
    let model = modelFor(this);
    model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
    domPanel.allowContextMenu(viewFor(this), model.contextMenu);
  },

  get contentURL() {
    return modelFor(this).contentURL;
  },
  set contentURL(value) {
    let model = modelFor(this);
    model.contentURL = panelContract({ contentURL: value }).contentURL;
    domPanel.setURL(viewFor(this), model.contentURL);
    // Detach worker so that messages send will be queued until it's
    // reatached once panel content is ready.
    workerFor(this).detach();
  },

  get allow() { return Allow(this); },
  set allow(value) {
    let allowJavascript = panelContract({ allow: value }).allow.script;
    return setScriptState(this, value);
  },

  /* Public API: Panel.isShowing */
  get isShowing() {
    return !isDisposed(this) && domPanel.isOpen(viewFor(this));
  },

  /* Public API: Panel.show */
  show: function show(options={}, anchor) {
    let view = viewFor(this);
    SinglePanelManager.requestOpen(this, () => {
      if (options instanceof Ci.nsIDOMElement) {
        [anchor, options] = [options, null];
      }

      if (anchor instanceof Ci.nsIDOMElement) {
        console.warn(
          "Passing a DOM node to Panel.show() method is an unsupported " +
          "feature that will be soon replaced. " +
          "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
        );
      }

      let model = modelFor(this);
      let anchorView = getNodeView(anchor || options.position || model.position);

      options = merge({
        position: model.position,
        width: model.width,
        height: model.height,
        defaultWidth: model.defaultWidth,
        defaultHeight: model.defaultHeight,
        focus: model.focus,
        contextMenu: model.contextMenu
      }, displayContract(options));

      if (!isDisposed(this)) {
        domPanel.show(view, options, anchorView);
      }
    });
    return this;
  },

  /* Public API: Panel.hide */
  hide: function hide() {
    // Quit immediately if panel is disposed or there is no state change.
    domPanel.close(viewFor(this));

    return this;
  },

  /* Public API: Panel.resize */
  resize: function resize(width, height) {
    let model = modelFor(this);
    let view = viewFor(this);
    let change = panelContract({
      width: width || model.width || model.defaultWidth,
      height: height || model.height || model.defaultHeight
    });

    model.width = change.width
    model.height = change.height

    domPanel.resize(view, model.width, model.height);

    return this;
  }
});
exports.Panel = Panel;

// Note must be defined only after value to `Panel` is assigned.
getActiveView.define(Panel, viewFor);

// Filter panel events to only panels that are create by this module.
var panelEvents = filter(events, ({target}) => panelFor(target));

// Panel events emitted after panel has being shown.
var shows = filter(panelEvents, ({type}) => type === "popupshown");

// Panel events emitted after panel became hidden.
var hides = filter(panelEvents, ({type}) => type === "popuphidden");

// Panel events emitted after content inside panel is ready. For different
// panels ready may mean different state based on `contentScriptWhen` attribute.
// Weather given event represents readyness is detected by `getAttachEventType`
// helper function.
var ready = filter(panelEvents, ({type, target}) =>
  getAttachEventType(modelFor(panelFor(target))) === type);

// Panel event emitted when the contents of the panel has been loaded.
var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded");

// Styles should be always added as soon as possible, and doesn't makes them
// depends on `contentScriptWhen`
var start = filter(panelEvents, ({type}) => type === "document-element-inserted");

// Forward panel show / hide events to panel's own event listeners.
on(shows, "data", ({target}) => {
  let panel = panelFor(target);
  if (modelFor(panel).ready)
    emit(panel, "show");
});

on(hides, "data", ({target}) => {
  let panel = panelFor(target);
  if (modelFor(panel).ready)
    emit(panel, "hide");
});

on(ready, "data", ({target}) => {
  let panel = panelFor(target);
  let window = domPanel.getContentDocument(target).defaultView;

  workerFor(panel).attach(window);
});

on(readyToShow, "data", ({target}) => {
  let panel = panelFor(target);

  if (!modelFor(panel).ready) {
    modelFor(panel).ready = true;

    if (viewFor(panel).state == "open")
      emit(panel, "show");
  }
});

on(start, "data", ({target}) => {
  let panel = panelFor(target);
  let window = domPanel.getContentDocument(target).defaultView;

  attach(styleFor(panel), window);
});
PK
!<	J}|SS'modules/commonjs/sdk/passwords/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, CC } = require("chrome");
const { uri: ADDON_URI } = require("../self");
const loginManager = Cc["@mozilla.org/login-manager;1"].
                     getService(Ci.nsILoginManager);
const { URL: parseURL } = require("../url");
const LoginInfo = CC("@mozilla.org/login-manager/loginInfo;1",
                     "nsILoginInfo", "init");

function filterMatchingLogins(loginInfo) {
  return Object.keys(this).every(key => loginInfo[key] === this[key], this);
}

/**
 * Removes `user`, `password` and `path` fields from the given `url` if it's
 * 'http', 'https' or 'ftp'. All other URLs are returned unchanged.
 * @example
 * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com
 */
function normalizeURL(url) {
  let { scheme, host, port } = parseURL(url);
  // We normalize URL only if it's `http`, `https` or `ftp`. All other types of
  // URLs (`resource`, `chrome`, etc..) should not be normalized as they are
  // used with add-on associated credentials path.
  return scheme === "http" || scheme === "https" || scheme === "ftp" ?
         scheme + "://" + (host || "") + (port ? ":" + port : "") :
         url
}

function Login(options) {
  let login = Object.create(Login.prototype);
  Object.keys(options || {}).forEach(function(key) {
    if (key === 'url')
      login.hostname = normalizeURL(options.url);
    else if (key === 'formSubmitURL')
      login.formSubmitURL = options.formSubmitURL ?
                            normalizeURL(options.formSubmitURL) : null;
    else if (key === 'realm')
      login.httpRealm = options.realm;
    else 
      login[key] = options[key];
  });

  return login;
}
Login.prototype.toJSON = function toJSON() {
  return {
    url: this.hostname || ADDON_URI,
    realm: this.httpRealm || null,
    formSubmitURL: this.formSubmitURL || null,
    username: this.username || null,
    password: this.password || null,
    usernameField: this.usernameField || '',
    passwordField: this.passwordField || '',
  }
};
Login.prototype.toLoginInfo = function toLoginInfo() {
  let { url, realm, formSubmitURL, username, password, usernameField,
        passwordField } = this.toJSON();

  return new LoginInfo(url, formSubmitURL, realm, username, password,
                       usernameField, passwordField);
};

function loginToJSON(value) {
  return Login(value).toJSON();
}

/**
 * Returns array of `nsILoginInfo` objects that are stored in the login manager
 * and have all the properties with matching values as a given `options` object.
 * @param {Object} options
 * @returns {nsILoginInfo[]}
 */
exports.search = function search(options) {
  return loginManager.getAllLogins()
                     .filter(filterMatchingLogins, Login(options))
                     .map(loginToJSON);
};

/**
 * Stores login info created from the given `options` to the applications
 * built-in login management system.
 * @param {Object} options.
 */
exports.store = function store(options) {
  loginManager.addLogin(Login(options).toLoginInfo());
};

/**
 * Removes login info from the applications built-in login management system.
 * _Please note: When removing a login info the specified properties must
 * exactly match to the one that is already stored or exception will be thrown._
 * @param {Object} options.
 */
exports.remove = function remove(options) {
  loginManager.removeLogin(Login(options).toLoginInfo());
};
PK
!<Kę!modules/commonjs/sdk/passwords.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "stable"
};

const { search, remove, store } = require("./passwords/utils");
const { defer, delay } = require("./lang/functional");

/**
 * Utility function that returns `onComplete` and `onError` callbacks form the
 * given `options` objects. Also properties are removed from the passed
 * `options` objects.
 * @param {Object} options
 *    Object that is passed to the exported functions of this module.
 * @returns {Function[]}
 *    Array with two elements `onComplete` and `onError` functions.
 */
function getCallbacks(options) {
  let value = [
    'onComplete' in options ? options.onComplete : null,
    'onError' in options ? defer(options.onError) : console.exception
  ];

  delete options.onComplete;
  delete options.onError;

  return value;
};

/**
 * Creates a wrapper function that tries to call `onComplete` with a return
 * value of the wrapped function or falls back to `onError` if wrapped function
 * throws an exception.
 */
function createWrapperMethod(wrapped) {
  return function (options) {
    let [ onComplete, onError ] = getCallbacks(options);
    try {
      let value = wrapped(options);
      if (onComplete) {
        delay(function() {
          try {
            onComplete(value);
          } catch (exception) {
            onError(exception);
          }
        });
      }
    } catch (exception) {
      onError(exception);
    }
  };
}

exports.search = createWrapperMethod(search);
exports.store = createWrapperMethod(store);
exports.remove = createWrapperMethod(remove);
PK
!<XH+H+(modules/commonjs/sdk/places/bookmarks.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

/*
 * Requiring hosts so they can subscribe to client messages
 */
require('./host/host-bookmarks');
require('./host/host-tags');
require('./host/host-query');

const { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage');
const { send } = require('../addon/events');
const { defer, reject, all, resolve, promised } = require('../core/promise');
const { EventTarget } = require('../event/target');
const { emit } = require('../event/core');
const { identity, defer:async } = require('../lang/functional');
const { extend, merge } = require('../util/object');
const { fromIterator } = require('../util/array');
const {
  constructTree, fetchItem, createQuery,
  isRootGroup, createQueryOptions
} = require('./utils');
const {
  bookmarkContract, groupContract, separatorContract
} = require('./contract');
const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                getService(Ci.nsINavBookmarksService);

/*
 * Mapping of uncreated bookmarks with their created
 * counterparts
 */
const itemMap = new WeakMap();

/*
 * Constant used by nsIHistoryQuery; 1 is a bookmark query
 * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
 */
const BOOKMARK_QUERY = 1;

/*
 * Bookmark Item classes
 */

const Bookmark = Class({
  extends: [
    bookmarkContract.properties(identity)
  ],
  initialize: function initialize (options) {
    merge(this, bookmarkContract(extend(defaults, options)));
  },
  type: 'bookmark',
  toString: () => '[object Bookmark]'
});
exports.Bookmark = Bookmark;

const Group = Class({
  extends: [
    groupContract.properties(identity)
  ],
  initialize: function initialize (options) {
    // Don't validate if root group
    if (isRootGroup(options))
      merge(this, options);
    else
      merge(this, groupContract(extend(defaults, options)));
  },
  type: 'group',
  toString: () => '[object Group]'
});
exports.Group = Group;

const Separator = Class({
  extends: [
    separatorContract.properties(identity)
  ],
  initialize: function initialize (options) {
    merge(this, separatorContract(extend(defaults, options)));
  },
  type: 'separator',
  toString: () => '[object Separator]'
});
exports.Separator = Separator;

/*
 * Functions
 */

function save (items, options) {
  items = [].concat(items);
  options = options || {};
  let emitter = EventTarget();
  let results = [];
  let errors = [];
  let root = constructTree(items);
  let cache = new Map();

  let isExplicitSave = item => !!~items.indexOf(item);
  // `walk` returns an aggregate promise indicating the completion
  // of the `commitItem` on each node, not whether or not that
  // commit was successful

  // Force this to be async, as if a ducktype fails validation,
  // the promise implementation will fire an error event, which will
  // not trigger the handler as it's not yet bound
  //
  // Can remove after `Promise.jsm` is implemented in Bug 881047,
  // which will guarantee next tick execution
  async(() => root.walk(preCommitItem).then(commitComplete))();

  function preCommitItem ({value:item}) {
    // Do nothing if tree root, default group (unsavable),
    // or if it's a dependency and not explicitly saved (in the list
    // of items to be saved), and not needed to be saved
    if (item === null || // node is the tree root
        isRootGroup(item) ||
        (getId(item) && !isExplicitSave(item)))
      return;

    return promised(validate)(item)
      .then(() => commitItem(item, options))
      .then(data => construct(data, cache))
      .then(savedItem => {
        // If item was just created, make a map between
        // the creation object and created object,
        // so we can reference the item that doesn't have an id
        if (!getId(item))
          saveId(item, savedItem.id);

        // Emit both the processed item, and original item
        // so a mapping can be understood in handler
        emit(emitter, 'data', savedItem, item);
       
        // Push to results iff item was explicitly saved
        if (isExplicitSave(item))
          results[items.indexOf(item)] = savedItem;
      }, reason => {
        // Force reason to be a string for consistency
        reason = reason + '';
        // Emit both the reason, and original item
        // so a mapping can be understood in handler
        emit(emitter, 'error', reason + '', item);
        // Store unsaved item in results list
        results[items.indexOf(item)] = item;
        errors.push(reason);
      });
  }

  // Called when traversal of the node tree is completed and all
  // items have been committed
  function commitComplete () {
    emit(emitter, 'end', results);
  }

  return emitter;
}
exports.save = save;

function search (queries, options) {
  queries = [].concat(queries);
  let emitter = EventTarget();
  let cache = new Map();
  let queryObjs = queries.map(createQuery.bind(null, BOOKMARK_QUERY));
  let optionsObj = createQueryOptions(BOOKMARK_QUERY, options);

  // Can remove after `Promise.jsm` is implemented in Bug 881047,
  // which will guarantee next tick execution
  async(() => {
    send('sdk-places-query', { queries: queryObjs, options: optionsObj })
      .then(handleQueryResponse);
  })();
    
  function handleQueryResponse (data) {
    let deferreds = data.map(item => {
      return construct(item, cache).then(bookmark => {
        emit(emitter, 'data', bookmark);
        return bookmark;
      }, reason => {
        emit(emitter, 'error', reason);
        errors.push(reason);
      });
    });

    all(deferreds).then(data => {
      emit(emitter, 'end', data);
    }, () => emit(emitter, 'end', []));
  }

  return emitter;
}
exports.search = search;

function remove (items) {
  return [].concat(items).map(item => {
    item.remove = true;
    return item;
  });
}

exports.remove = remove;

/*
 * Internal Utilities
 */

function commitItem (item, options) {
  // Get the item's ID, or getId it's saved version if it exists
  let id = getId(item);
  let data = normalize(item);
  let promise;

  data.id = id;

  if (!id) {
    promise = send('sdk-places-bookmarks-create', data);
  } else if (item.remove) {
    promise = send('sdk-places-bookmarks-remove', { id: id });
  } else {
    promise = send('sdk-places-bookmarks-last-updated', {
      id: id
    }).then(function (updated) {
      // If attempting to save an item that is not the 
      // latest snapshot of a bookmark item, execute
      // the resolution function
      if (updated !== item.updated && options.resolve)
        return fetchItem(id)
          .then(options.resolve.bind(null, data));
      else
        return data;
    }).then(send.bind(null, 'sdk-places-bookmarks-save'));
  }

  return promise;
}

/*
 * Turns a bookmark item into a plain object,
 * converts `tags` from Set to Array, group instance to an id
 */
function normalize (item) {
  let data = merge({}, item);
  // Circumvent prototype property of `type`
  delete data.type;
  data.type = item.type;
  data.tags = [];
  if (item.tags) {
    data.tags = fromIterator(item.tags);
  }
  data.group = getId(data.group) || exports.UNSORTED.id;

  return data;
}

/*
 * Takes a data object and constructs a BookmarkItem instance
 * of it, recursively generating parent instances as well.
 *
 * Pass in a `cache` Map to reuse instances of
 * bookmark items to reduce overhead;
 * The cache object is a map of id to a deferred with a 
 * promise that resolves to the bookmark item.
 */
function construct (object, cache, forced) {
  let item = instantiate(object);
  let deferred = defer();

  // Item could not be instantiated
  if (!item)
    return resolve(null);

  // Return promise for item if found in the cache,
  // and not `forced`. `forced` indicates that this is the construct
  // call that should not read from cache, but should actually perform
  // the construction, as it was set before several async calls
  if (cache.has(item.id) && !forced)
    return cache.get(item.id).promise;
  else if (cache.has(item.id))
    deferred = cache.get(item.id);
  else
    cache.set(item.id, deferred);

  // When parent group is found in cache, use
  // the same deferred value
  if (item.group && cache.has(item.group)) {
    cache.get(item.group).promise.then(group => {
      item.group = group;
      deferred.resolve(item);
    });

  // If not in the cache, and a root group, return
  // the premade instance
  } else if (rootGroups.get(item.group)) {
    item.group = rootGroups.get(item.group);
    deferred.resolve(item);

  // If not in the cache or a root group, fetch the parent
  } else {
    cache.set(item.group, defer());
    fetchItem(item.group).then(group => {
      return construct(group, cache, true);
    }).then(group => {
      item.group = group;
      deferred.resolve(item);
    }, deferred.reject);
  }

  return deferred.promise;
}

function instantiate (object) {
  if (object.type === 'bookmark')
    return Bookmark(object);
  if (object.type === 'group')
    return Group(object);
  if (object.type === 'separator')
    return Separator(object);
  return null;
}

/**
 * Validates a bookmark item; will throw an error if ininvalid,
 * to be used with `promised`. As bookmark items check on their class,
 * this only checks ducktypes
 */
function validate (object) {
  if (!isDuckType(object)) return true;
  let contract = object.type === 'bookmark' ? bookmarkContract :
                 object.type === 'group' ? groupContract :
                 object.type === 'separator' ? separatorContract :
                 null;
  if (!contract) {
    throw Error('No type specified');
  }

  // If object has a property set, and undefined,
  // manually override with default as it'll fail otherwise
  let withDefaults = Object.keys(defaults).reduce((obj, prop) => {
    if (obj[prop] == null) obj[prop] = defaults[prop];
    return obj;
  }, extend(object));

  contract(withDefaults);
}

function isDuckType (item) {
  return !(item instanceof Bookmark) &&
    !(item instanceof Group) &&
    !(item instanceof Separator);
}

function saveId (unsaved, id) {
  itemMap.set(unsaved, id);
}

// Fetches an item's ID from itself, or from the mapped items
function getId (item) {
  return typeof item === 'number' ? item :
    item ? item.id || itemMap.get(item) :
    null;
}

/*
 * Set up the default, root groups
 */

var defaultGroupMap = {
  MENU: bmsrv.bookmarksMenuFolder,
  TOOLBAR: bmsrv.toolbarFolder,
  UNSORTED: bmsrv.unfiledBookmarksFolder
};

var rootGroups = new Map();

for (let i in defaultGroupMap) {
  let group = Object.freeze(Group({ title: i, id: defaultGroupMap[i] }));
  rootGroups.set(defaultGroupMap[i], group);
  exports[i] = group;
}

var defaults = {
  group: exports.UNSORTED,
  index: -1
};
PK
!<ԕ44'modules/commonjs/sdk/places/contract.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require('chrome');
const { isValidURI, URL } = require('../url');
const { contract } = require('../util/contract');
const { extend } = require('../util/object');

// map of property validations
const validItem = {
  id: {
    is: ['number', 'undefined', 'null'],
  },
  group: {
    is: ['object', 'number', 'undefined', 'null'],
    ok: function (value) {
      return value &&
        (value.toString && value.toString() === '[object Group]') ||
        typeof value === 'number' ||
        value.type === 'group';
    },
    msg: 'The `group` property must be a valid Group object'
  },
  index: {
    is: ['undefined', 'null', 'number'],
    map: value => value == null ? -1 : value,
    msg: 'The `index` property must be a number.'
  },
  updated: {
    is: ['number', 'undefined']
  }
};

const validTitle = {
  title: {
    is: ['string'],
    msg: 'The `title` property must be defined.'
  }
};

const validURL = {
  url: {
    is: ['string'],
    ok: isValidURI,
    msg: 'The `url` property must be a valid URL.'
  }
};

const validTags = {
  tags: {
    is: ['object'],
    ok: tags => tags instanceof Set,
    map: function (tags) {
      if (Array.isArray(tags))
        return new Set(tags);
      if (tags == null)
        return new Set();
      return tags;
    },
    msg: 'The `tags` property must be a Set, or an array'
  }
};

exports.bookmarkContract = contract(
  extend(validItem, validTitle, validURL, validTags));
exports.separatorContract = contract(validItem);
exports.groupContract = contract(extend(validItem, validTitle));
PK
!<΂%modules/commonjs/sdk/places/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '*',
    "SeaMonkey": '*'
  }
};

const { Cc, Ci } = require('chrome');
const { Unknown } = require('../platform/xpcom');
const { Class } = require('../core/heritage');
const { merge } = require('../util/object');
const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
                        .getService(Ci.nsINavBookmarksService);
const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
                       .getService(Ci.nsINavHistoryService);
const { mapBookmarkItemType } = require('./utils');
const { EventTarget } = require('../event/target');
const { emit } = require('../event/core');
const { when } = require('../system/unload');

const emitter = EventTarget();

var HISTORY_ARGS = {
  onBeginUpdateBatch: [],
  onEndUpdateBatch: [],
  onClearHistory: [],
  onDeleteURI: ['url'],
  onDeleteVisits: ['url', 'visitTime'],
  onPageChanged: ['url', 'property', 'value'],
  onTitleChanged: ['url', 'title'],
  onVisit: [
    'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType'
  ]
};

var HISTORY_EVENTS = {
  onBeginUpdateBatch: 'history-start-batch',
  onEndUpdateBatch: 'history-end-batch',
  onClearHistory: 'history-start-clear',
  onDeleteURI: 'history-delete-url',
  onDeleteVisits: 'history-delete-visits',
  onPageChanged: 'history-page-changed',
  onTitleChanged: 'history-title-changed',
  onVisit: 'history-visit'
};

var BOOKMARK_ARGS = {
  onItemAdded: [
    'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded'
  ],
  onItemChanged: [
    'id', 'property', null, 'value', 'lastModified', 'type', 'parentId'
  ],
  onItemMoved: [
    'id', 'previousParentId', 'previousIndex', 'currentParentId',
    'currentIndex', 'type'
  ],
  onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'],
  onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId']
};

var BOOKMARK_EVENTS = {
  onItemAdded: 'bookmark-item-added',
  onItemChanged: 'bookmark-item-changed',
  onItemMoved: 'bookmark-item-moved',
  onItemRemoved: 'bookmark-item-removed',
  onItemVisited: 'bookmark-item-visited',
};

function createHandler (type, propNames) {
  propNames = propNames || [];
  return function (...args) {
    let data = propNames.reduce((acc, prop, i) => {
      if (prop)
        acc[prop] = formatValue(prop, args[i]);
      return acc;
    }, {});

    emit(emitter, 'data', {
      type: type,
      data: data
    });
  };
}

/*
 * Creates an observer, creating handlers based off of
 * the `events` names, and ordering arguments from `propNames` hash
 */
function createObserverInstance (events, propNames) {
  let definition = Object.keys(events).reduce((prototype, eventName) => {
    prototype[eventName] = createHandler(events[eventName], propNames[eventName]);
    return prototype;
  }, {});

  return Class(merge(definition, { extends: Unknown }))();
}

/*
 * Formats `data` based off of the value of `type`
 */
function formatValue (type, data) {
  if (type === 'type')
    return mapBookmarkItemType(data);
  if (type === 'url' && data)
    return data.spec;
  return data;
}

var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS);
// Hack alert: we sometimes need to send extra title-changed notifications
// ourselves for backwards compat. Replace the original onVisit callback to
// add on that logic:
historyObserver.realOnVisit = historyObserver.onVisit;
historyObserver.onVisit = function(url, visitId, time, sessionId,
                                   referringId, transitionType, guid, hidden,
                                   visitCount, typed, lastKnownTitle) {
  // If this is the first visit we're adding, fire title-changed
  // in case anyone cares.
  if (visitCount == 1) {
    historyObserver.onTitleChanged(url, lastKnownTitle);
  }
  this.realOnVisit(url, visitId, time, sessionId, referringId, transitionType);
};
historyService.addObserver(historyObserver);

var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS);
bookmarkService.addObserver(bookmarkObserver);

when(() => {
  historyService.removeObserver(historyObserver);
  bookmarkService.removeObserver(bookmarkObserver);
});

exports.events = emitter;
PK
!<3&modules/commonjs/sdk/places/favicon.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cc, Ci, Cu } = require("chrome");
const { defer, reject } = require("../core/promise");
const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
                          getService(Ci.nsIFaviconService);
const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons);
const { isValidURI } = require("../url");
const { newURI, getURL } = require("../url/utils");

/**
 * Takes an object of several possible types and
 * returns a promise that resolves to the page's favicon URI.
 * @param {String|Tab} object
 * @param {Function} (callback)
 * @returns {Promise}
 */

function getFavicon (object, callback) {
  let url = getURL(object);
  let deferred = defer();

  if (url && isValidURI(url)) {
    AsyncFavicons.getFaviconURLForPage(newURI(url), function (aURI) {
      if (aURI && aURI.spec)
        deferred.resolve(aURI.spec.toString());
      else
        deferred.reject(null);
    });
  } else {
    deferred.reject(null);
  }

  if (callback) deferred.promise.then(callback, callback);
  return deferred.promise;
}
exports.getFavicon = getFavicon;
PK
!<~&modules/commonjs/sdk/places/history.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

/*
 * Requiring hosts so they can subscribe to client messages
 */
require('./host/host-bookmarks');
require('./host/host-tags');
require('./host/host-query');

const { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage');
const { events, send } = require('../addon/events');
const { defer, reject, all } = require('../core/promise');
const { uuid } = require('../util/uuid');
const { flatten } = require('../util/array');
const { has, extend, merge, pick } = require('../util/object');
const { emit } = require('../event/core');
const { defer: async } = require('../lang/functional');
const { EventTarget } = require('../event/target');
const {
  urlQueryParser, createQuery, createQueryOptions
} = require('./utils');

/*
 * Constant used by nsIHistoryQuery; 0 is a history query
 * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
 */
const HISTORY_QUERY = 0;

var search = function query (queries, options) {
  queries = [].concat(queries);
  let emitter = EventTarget();
  let queryObjs = queries.map(createQuery.bind(null, HISTORY_QUERY));
  let optionsObj = createQueryOptions(HISTORY_QUERY, options);

  // Can remove after `Promise.jsm` is implemented in Bug 881047,
  // which will guarantee next tick execution
  async(() => {
    send('sdk-places-query', {
      query: queryObjs,
      options: optionsObj
    }).then(results => {
      results.map(item => emit(emitter, 'data', item));
      emit(emitter, 'end', results);
    }, reason => {
      emit(emitter, 'error', reason);
      emit(emitter, 'end', []);
    });
  })();

  return emitter;
};
exports.search = search;
PK
!<&nJ2modules/commonjs/sdk/places/host/host-bookmarks.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cc, Ci } = require('chrome');
const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"].
                       getService(Ci.nsIBrowserHistory);
const asyncHistory = Cc["@mozilla.org/browser/history;1"].
                     getService(Ci.mozIAsyncHistory);
const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                        getService(Ci.nsINavBookmarksService);
const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
                       getService(Ci.nsITaggingService);
const ios = Cc['@mozilla.org/network/io-service;1'].
            getService(Ci.nsIIOService);
const { query } = require('./host-query');
const {
  defer, all, resolve, promised, reject
} = require('../../core/promise');
const { request, response } = require('../../addon/host');
const { send } = require('../../addon/events');
const { on, emit } = require('../../event/core');
const { filter } = require('../../event/utils');
const { URL, isValidURI } = require('../../url');
const { newURI } = require('../../url/utils');

const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX;
const UNSORTED_ID = bmsrv.unfiledBookmarksFolder;
const ROOT_FOLDERS = [
  bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder,
  bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder
];

const EVENT_MAP = {
  'sdk-places-bookmarks-create': createBookmarkItem,
  'sdk-places-bookmarks-save': saveBookmarkItem,
  'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated,
  'sdk-places-bookmarks-get': getBookmarkItem,
  'sdk-places-bookmarks-remove': removeBookmarkItem,
  'sdk-places-bookmarks-get-all': getAllBookmarks,
  'sdk-places-bookmarks-get-children': getChildren
};

function typeMap (type) {
  if (typeof type === 'number') {
    if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
    if (bmsrv.TYPE_FOLDER === type) return 'group';
    if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
  } else {
    if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
    if ('group' === type) return bmsrv.TYPE_FOLDER;
    if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
  }
}

function getBookmarkLastUpdated ({id}) {
  return resolve(bmsrv.getItemLastModified(id));
}
exports.getBookmarkLastUpdated;

function createBookmarkItem (data) {
  let error;

  if (data.group == null) data.group = UNSORTED_ID;
  if (data.index == null) data.index = DEFAULT_INDEX;

  if (data.type === 'group')
    data.id = bmsrv.createFolder(
      data.group, data.title, data.index
    );
  else if (data.type === 'separator')
    data.id = bmsrv.insertSeparator(
      data.group, data.index
    );
  else
    data.id = bmsrv.insertBookmark(
      data.group, newURI(data.url), data.index, data.title
    );

  // In the event where default or no index is provided (-1),
  // query the actual index for the response
  if (data.index === -1)
    data.index = bmsrv.getItemIndex(data.id);

  try {
    data.updated = bmsrv.getItemLastModified(data.id);
  }
  catch (e) {
    console.exception(e);
  }

  return tag(data, true).then(() => data);
}
exports.createBookmarkItem = createBookmarkItem;

function saveBookmarkItem (data) {
  let id = data.id;
  if (!id)
    reject('Item is missing id');

  let group = bmsrv.getFolderIdForItem(id);
  let index = bmsrv.getItemIndex(id);
  let type = bmsrv.getItemType(id);
  let title = typeMap(type) !== 'separator' ?
    bmsrv.getItemTitle(id) :
    undefined;
  let url = typeMap(type) === 'bookmark' ?
    bmsrv.getBookmarkURI(id).spec :
    undefined;

  if (url != data.url)
    bmsrv.changeBookmarkURI(id, newURI(data.url));
  else if (typeMap(type) === 'bookmark')
    data.url = url;

  if (title != data.title)
    bmsrv.setItemTitle(id, data.title);
  else if (typeMap(type) !== 'separator')
    data.title = title;

  if (data.group && data.group !== group)
    bmsrv.moveItem(id, data.group, data.index || -1);
  else if (data.index != null && data.index !== index) {
    // We use moveItem here instead of setItemIndex
    // so we don't have to manage the indicies of the siblings
    bmsrv.moveItem(id, group, data.index);
  } else if (data.index == null)
    data.index = index;

  data.updated = bmsrv.getItemLastModified(data.id);

  return tag(data).then(() => data);
}
exports.saveBookmarkItem = saveBookmarkItem;

function removeBookmarkItem (data) {
  let id = data.id;

  if (!id)
    reject('Item is missing id');

  bmsrv.removeItem(id);
  return resolve(null);
}
exports.removeBookmarkItem = removeBookmarkItem;

function getBookmarkItem (data) {
  let id = data.id;

  if (!id)
    reject('Item is missing id');

  let type = bmsrv.getItemType(id);

  data.type = typeMap(type);

  if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER)
    data.title = bmsrv.getItemTitle(id);

  if (type === bmsrv.TYPE_BOOKMARK) {
    data.url = bmsrv.getBookmarkURI(id).spec;
    // Should be moved into host-tags as a method
    data.tags = taggingService.getTagsForURI(newURI(data.url), {});
  }

  data.group = bmsrv.getFolderIdForItem(id);
  data.index = bmsrv.getItemIndex(id);
  data.updated = bmsrv.getItemLastModified(data.id);

  return resolve(data);
}
exports.getBookmarkItem = getBookmarkItem;

function getAllBookmarks () {
  return query({}, { queryType: 1 }).then(bookmarks =>
    all(bookmarks.map(getBookmarkItem)));
}
exports.getAllBookmarks = getAllBookmarks;

function getChildren ({ id }) {
  if (typeMap(bmsrv.getItemType(id)) !== 'group') return [];
  let ids = [];
  for (let i = 0; ids[ids.length - 1] !== -1; i++)
    ids.push(bmsrv.getIdForItemAt(id, i));
  ids.pop();
  return all(ids.map(id => getBookmarkItem({ id: id })));
}
exports.getChildren = getChildren;

/*
 * Hook into host
 */

var reqStream = filter(request, (data) => /sdk-places-bookmarks/.test(data.event));
on(reqStream, 'data', ({ event, id, data }) => {
  if (!EVENT_MAP[event]) return;

  let resData = { id: id, event: event };

  promised(EVENT_MAP[event])(data).
  then(res => resData.data = res, e => resData.error = e).
  then(() => emit(response, 'data', resData));
});

function tag (data, isNew) {
  // If a new item, we can skip checking what other tags
  // are on the item
  if (data.type !== 'bookmark') {
    return resolve();
  }
  else if (!isNew) {
    return send('sdk-places-tags-get-tags-by-url', { url: data.url })
      .then(tags => {
        return send('sdk-places-tags-untag', {
          tags: tags.filter(tag => !~data.tags.indexOf(tag)),
          url: data.url
        });
      }).then(() => send('sdk-places-tags-tag', {
        url: data.url, tags: data.tags
      }));
  }
  else if (data.tags && data.tags.length) {
    return send('sdk-places-tags-tag', { url: data.url, tags: data.tags });
  }
  else
    return resolve();
}

PK
!<
'"".modules/commonjs/sdk/places/host/host-query.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cc, Ci } = require('chrome');
const { all } = require('../../core/promise');
const { safeMerge, omit } = require('../../util/object');
const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
                     .getService(Ci.nsINavHistoryService);
const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
                         .getService(Ci.nsINavBookmarksService);
const { request, response } = require('../../addon/host');
const { newURI } = require('../../url/utils');
const { send } = require('../../addon/events');
const { on, emit } = require('../../event/core');
const { filter } = require('../../event/utils');

const ROOT_FOLDERS = [
  bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder,
  bookmarksService.bookmarksMenuFolder
];

const EVENT_MAP = {
  'sdk-places-query': queryReceiver
};

// Properties that need to be manually
// copied into a nsINavHistoryQuery object
const MANUAL_QUERY_PROPERTIES = [
  'uri', 'folder', 'tags', 'url', 'folder'
];

const PLACES_PROPERTIES = [
  'uri', 'title', 'accessCount', 'time'
];

function execute (queries, options) {
  return new Promise(resolve => {
    let root = historyService
        .executeQueries(queries, queries.length, options).root;
    // Let's extract an eventual uri wildcard, if both domain and uri are set.
    // See utils.js::urlQueryParser() for more details.
    // In case of multiple queries, we only retain the first found wildcard.
    let uriWildcard = queries.reduce((prev, query) => {
      if (query.uri && query.domain) {
        if (!prev)
          prev = query.uri.spec;
        query.uri = null;
      }
      return prev;
    }, "");
    resolve(collect([], root, uriWildcard));
  });
}

function collect (acc, node, uriWildcard) {
  node.containerOpen = true;
  for (let i = 0; i < node.childCount; i++) {
    let child = node.getChild(i);

    if (!uriWildcard || child.uri.startsWith(uriWildcard)) {
      acc.push(child);
    }
    if (child.type === child.RESULT_TYPE_FOLDER) {
      let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode);
      collect(acc, container, uriWildcard);
    }
  }
  node.containerOpen = false;
  return acc;
}

function query (queries, options) {
  return new Promise((resolve, reject) => {
    queries = queries || [];
    options = options || {};
    let optionsObj, queryObjs;

    optionsObj = historyService.getNewQueryOptions();
    queryObjs = [].concat(queries).map(createQuery);
    if (!queryObjs.length) {
      queryObjs = [historyService.getNewQuery()];
    }
    safeMerge(optionsObj, options);

    /*
     * Currently `places:` queries are not supported
     */
    optionsObj.excludeQueries = true;

    execute(queryObjs, optionsObj).then((results) => {
      if (optionsObj.queryType === 0) {
        return results.map(normalize);
      }
      else if (optionsObj.queryType === 1) {
        // Formats query results into more standard
        // data structures for returning
        return all(results.map(({itemId}) =>
          send('sdk-places-bookmarks-get', { id: itemId })));
      }
    }).then(resolve, reject);
  });
}
exports.query = query;

function createQuery (query) {
  query = query || {};
  let queryObj = historyService.getNewQuery();

  safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES));

  if (query.tags && Array.isArray(query.tags))
    queryObj.tags = query.tags;
  if (query.uri || query.url)
    queryObj.uri = newURI(query.uri || query.url);
  if (query.folder)
    queryObj.setFolders([query.folder], 1);
  return queryObj;
}

function queryReceiver (message) {
  let queries = message.data.queries || message.data.query;
  let options = message.data.options;
  let resData = {
    id: message.id,
    event: message.event
  };

  query(queries, options).then(results => {
    resData.data = results;
    respond(resData);
  }, reason => {
    resData.error = reason;
    respond(resData);
  });
}

/*
 * Converts a nsINavHistoryResultNode into a plain object
 *
 * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
 */
function normalize (historyObj) {
  return PLACES_PROPERTIES.reduce((obj, prop) => {
    if (prop === 'uri')
      obj.url = historyObj.uri;
    else if (prop === 'time') {
      // Cast from microseconds to milliseconds
      obj.time = Math.floor(historyObj.time / 1000)
    }
    else if (prop === 'accessCount')
      obj.visitCount = historyObj[prop];
    else
      obj[prop] = historyObj[prop];
    return obj;
  }, {});
}

/*
 * Hook into host
 */

var reqStream = filter(request, data => /sdk-places-query/.test(data.event));
on(reqStream, 'data', function (e) {
  if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e);
});

function respond (data) {
  emit(response, 'data', data);
}
PK
!<PK-modules/commonjs/sdk/places/host/host-tags.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cc, Ci } = require('chrome');
const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
                       getService(Ci.nsITaggingService);
const ios = Cc['@mozilla.org/network/io-service;1'].
            getService(Ci.nsIIOService);
const { URL } = require('../../url');
const { newURI } = require('../../url/utils');
const { request, response } = require('../../addon/host');
const { on, emit } = require('../../event/core');
const { filter } = require('../../event/utils');

const EVENT_MAP = {
  'sdk-places-tags-tag': tag,
  'sdk-places-tags-untag': untag,
  'sdk-places-tags-get-tags-by-url': getTagsByURL,
  'sdk-places-tags-get-urls-by-tag': getURLsByTag
};

function tag (message) {
  let data = message.data;
  let resData = {
    id: message.id,
    event: message.event
  };

  if (data.tags && data.tags.length > 0)
    resData.data = taggingService.tagURI(newURI(data.url), data.tags);
  respond(resData);
}

function untag (message) {
  let data = message.data;
  let resData = {
    id: message.id,
    event: message.event
  };

  if (!data.tags || data.tags.length > 0)
    resData.data = taggingService.untagURI(newURI(data.url), data.tags);
  respond(resData);
}

function getURLsByTag (message) {
  let data = message.data;
  let resData = {
    id: message.id,
    event: message.event
  };

  resData.data = taggingService
    .getURIsForTag(data.tag).map(uri => uri.spec);
  respond(resData);
}

function getTagsByURL (message) {
  let data = message.data;
  let resData = {
    id: message.id,
    event: message.event
  };

  resData.data = taggingService.getTagsForURI(newURI(data.url), {});
  respond(resData);
}

/*
 * Hook into host
 */

var reqStream = filter(request, function (data) {
  return /sdk-places-tags/.test(data.event);
});

on(reqStream, 'data', function (e) {
  if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e);
});

function respond (data) {
  emit(response, 'data', data);
}
PK
!<&SS--$modules/commonjs/sdk/places/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Cc, Ci, Cu } = require('chrome');
const { Class } = require('../core/heritage');
const { method } = require('../lang/functional');
const { defer, promised, all } = require('../core/promise');
const { send } = require('../addon/events');
const { EventTarget } = require('../event/target');
const { merge } = require('../util/object');
const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                getService(Ci.nsINavBookmarksService);

Cu.importGlobalProperties(["URL"]);

/*
 * TreeNodes are used to construct dependency trees
 * for BookmarkItems
 */
var TreeNode = Class({
  initialize: function (value) {
    this.value = value;
    this.children = [];
  },
  add: function (values) {
    [].concat(values).forEach(value => {
      this.children.push(value instanceof TreeNode ? value : TreeNode(value));
    });
  },
  get length () {
    let count = 0;
    this.walk(() => count++);
    // Do not count the current node
    return --count;
  },
  get: method(get),
  walk: method(walk),
  toString: () => '[object TreeNode]'
});
exports.TreeNode = TreeNode;

/*
 * Descends down from `node` applying `fn` to each in order.
 * `fn` can return values or promises -- if promise returned,
 * children are not processed until resolved. `fn` is passed
 * one argument, the current node, `curr`.
 */
function walk (curr, fn) {
  return promised(fn)(curr).then(val => {
    return all(curr.children.map(child => walk(child, fn)));
  });
}

/*
 * Descends from the TreeNode `node`, returning
 * the node with value `value` if found or `null`
 * otherwise
 */
function get (node, value) {
  if (node.value === value) return node;
  for (let child of node.children) {
    let found = get(child, value);
    if (found) return found;
  }
  return null;
}

/*
 * Constructs a tree of bookmark nodes
 * returning the root (value: null);
 */

function constructTree (items) {
  let root = TreeNode(null);
  items.forEach(treeify.bind(null, root));

  function treeify (root, item) {
    // If node already exists, skip
    let node = root.get(item);
    if (node) return node;
    node = TreeNode(item);

    let parentNode = item.group ? treeify(root, item.group) : root;
    parentNode.add(node);

    return node;
  }

  return root;
}
exports.constructTree = constructTree;

/*
 * Shortcut for converting an id, or an object with an id, into
 * an object with corresponding bookmark data
 */
function fetchItem (item) {
  return send('sdk-places-bookmarks-get', { id: item.id || item });
}
exports.fetchItem = fetchItem;

/*
 * Takes an ID or an object with ID and checks it against
 * the root bookmark folders
 */
function isRootGroup (id) {
  id = id && id.id;
  return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder,
    bmsrv.unfiledBookmarksFolder
  ].indexOf(id);
}
exports.isRootGroup = isRootGroup;

/*
 * Merges appropriate options into query based off of url
 * 4 scenarios:
 *
 * 'moz.com' // domain: moz.com, domainIsHost: true
 *    --> 'http://moz.com', 'http://moz.com/thunderbird'
 * '*.moz.com' // domain: moz.com, domainIsHost: false
 *    --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test'
 * 'http://moz.com' // uri: http://moz.com/
 *    --> 'http://moz.com/'
 * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true
 *    --> 'http://moz.com/', 'http://moz.com/thunderbird'
 */

function urlQueryParser (query, url) {
  if (!url) return;
  if (/^https?:\/\//.test(url)) {
    query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/';
    if (/\*$/.test(url)) {
      // Wildcard searches on URIs are not supported, so try to extract a
      // domain and filter the data later.
      url = url.replace(/\*$/, '');
      try {
        query.domain = new URL(url).hostname;
        query.domainIsHost = true;
        // Unfortunately here we cannot use an expando to store the wildcard,
        // cause the query is a wrapped native XPCOM object, so we reuse uri.
        // We clearly don't want to query for both uri and domain, thus we'll
        // have to handle this in host-query.js::execute()
        query.uri = url;
      } catch (ex) {
        // Cannot extract an host cause it's not a valid uri, the query will
        // just return nothing.
      }
    }
  } else {
    if (/^\*/.test(url)) {
      query.domain = url.replace(/^\*\./, '');
      query.domainIsHost = false;
    } else {
      query.domain = url;
      query.domainIsHost = true;
    }
  }
}
exports.urlQueryParser = urlQueryParser;

/*
 * Takes an EventEmitter and returns a promise that
 * aggregates results and handles a bulk resolve and reject
 */

function promisedEmitter (emitter) {
  let { promise, resolve, reject } = defer();
  let errors = [];
  emitter.on('error', error => errors.push(error));
  emitter.on('end', (items) => {
    if (errors.length) reject(errors[0]);
    else resolve(items);
  });
  return promise;
}
exports.promisedEmitter = promisedEmitter;


// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
function createQuery (type, query) {
  query = query || {};
  let qObj = {
    searchTerms: query.query
  };

  urlQueryParser(qObj, query.url);

  // 0 === history
  if (type === 0) {
    // PRTime used by query is in microseconds, not milliseconds
    qObj.beginTime = (query.from || 0) * 1000;
    qObj.endTime = (query.to || new Date()) * 1000;

    // Set reference time to Epoch
    qObj.beginTimeReference = 0;
    qObj.endTimeReference = 0;
  }
  // 1 === bookmarks
  else if (type === 1) {
    qObj.tags = query.tags;
    qObj.folder = query.group && query.group.id;
  }
  // 2 === unified (not implemented on platform)
  else if (type === 2) {

  }

  return qObj;
}
exports.createQuery = createQuery;

// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions

const SORT_MAP = {
  title: 1,
  date: 3, // sort by visit date
  url: 5,
  visitCount: 7,
  // keywords currently unsupported
  // keyword: 9,
  dateAdded: 11, // bookmarks only
  lastModified: 13 // bookmarks only
};

function createQueryOptions (type, options) {
  options = options || {};
  let oObj = {};
  oObj.sortingMode = SORT_MAP[options.sort] || 0;
  if (options.descending && options.sort)
    oObj.sortingMode++;

  // Resolve to default sort if ineligible based on query type
  if (type === 0 && // history
      (options.sort === 'dateAdded' || options.sort === 'lastModified'))
    oObj.sortingMode = 0;

  oObj.maxResults = typeof options.count === 'number' ? options.count : 0;

  oObj.queryType = type;

  return oObj;
}
exports.createQueryOptions = createQueryOptions;


function mapBookmarkItemType (type) {
  if (typeof type === 'number') {
    if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
    if (bmsrv.TYPE_FOLDER === type) return 'group';
    if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
  } else {
    if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
    if ('group' === type) return bmsrv.TYPE_FOLDER;
    if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
  }
}
exports.mapBookmarkItemType = mapBookmarkItemType;
PK
!<!5""&modules/commonjs/sdk/platform/xpcom.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome');
const { registerFactory, unregisterFactory, isCIDRegistered } =
      Cm.QueryInterface(Ci.nsIComponentRegistrar);

const { Class, extend, mix } = require('../core/heritage');
lazyRequire(this, '../util/object', 'merge');
lazyRequire(this, '../util/uuid', 'uuid');

// This is a base prototype, that provides bare bones of XPCOM. JS based
// components can be easily implement by extending it.
const Unknown = new function() {
  function hasInterface(component, iid) {
    return component && component.interfaces &&
      ( component.interfaces.some(id => iid.equals(Ci[id])) ||
        component.implements.some($ => hasInterface($, iid)) ||
        hasInterface(Object.getPrototypeOf(component), iid));
  }

  return Class({
    /**
     * The `QueryInterface` method provides runtime type discovery used by XPCOM.
     * This method return queried instance of `this` if given `iid` is listed in
     * the `interfaces` property or in equivalent properties of objects in it's
     * prototype chain. In addition it will look up in the prototypes under
     * `implements` array property, this ways compositions made via `Class`
     * utility will carry interfaces implemented by composition components.
     */
    QueryInterface: function QueryInterface(iid) {
      // For some reason there are cases when `iid` is `null`. In such cases we
      // just return `this`. Otherwise we verify that component implements given
      // `iid` interface. This will be no longer necessary once Bug 748003 is
      // fixed.
      if (iid && !hasInterface(this, iid))
        throw Cr.NS_ERROR_NO_INTERFACE;

      return this;
    },
    /**
     * Array of `XPCOM` interfaces (as strings) implemented by this component.
     * All components implement `nsISupports` by default which is default value
     * here. Provide array of interfaces implemented by an object when
     * extending, to append them to this list (Please note that there is no
     * need to repeat interfaces implemented by super as they will be added
     * automatically).
     */
    interfaces: Object.freeze([ 'nsISupports' ])
  });
}
exports.Unknown = Unknown;

// Base exemplar for creating instances implementing `nsIFactory` interface,
// that maybe registered into runtime via `register` function. Instances of
// this factory create instances of enclosed component on `createInstance`.
const Factory = Class({
  extends: Unknown,
  interfaces: [ 'nsIFactory' ],
  /**
   * All the descendants will get auto generated `id` (also known as `classID`
   * in XPCOM world) unless one is manually provided.
   */
  get id() { throw Error('Factory must implement `id` property') },
  /**
   * XPCOM `contractID` may optionally  be provided to associate this factory
   * with it. `contract` is a unique string that has a following format:
   * '@vendor.com/unique/id;1'.
   */
  contract: null,
  /**
   * Class description that is being registered. This value is intended as a
   * human-readable description for the given class and does not needs to be
   * globally unique.
   */
  description: 'Jetpack generated factory',
  /**
   * This method is required by `nsIFactory` interfaces, but as in most
   * implementations it does nothing interesting.
   */
  lockFactory: function lockFactory(lock) {
    return undefined;
  },
  /**
   * If property is `true` XPCOM service / factory will be registered
   * automatically on creation.
   */
  register: true,
  /**
   * If property is `true` XPCOM factory will be unregistered prior to add-on
   * unload.
   */
  unregister: true,
  /**
   * Method is called on `Service.new(options)` passing given `options` to
   * it. Options is expected to have `component` property holding XPCOM
   * component implementation typically decedent of `Unknown` or any custom
   * implementation with a `new` method and optional `register`, `unregister`
   * flags. Unless `register` is `false` Service / Factory will be
   * automatically registered. Unless `unregister` is `false` component will
   * be automatically unregistered on add-on unload.
   */
  initialize: function initialize(options) {
    merge(this, {
      id: 'id' in options ? options.id : uuid(),
      register: 'register' in options ? options.register : this.register,
      unregister: 'unregister' in options ? options.unregister : this.unregister,
      contract: 'contract' in options ? options.contract : null,
      Component: options.Component
    });

    // If service / factory has auto registration enabled then register.
    if (this.register)
      register(this);
  },
  /**
   * Creates an instance of the class associated with this factory.
   */
  createInstance: function createInstance(outer, iid) {
    try {
      if (outer)
        throw Cr.NS_ERROR_NO_AGGREGATION;
      return this.create().QueryInterface(iid);
    }
    catch (error) {
      throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE;
    }
  },
  create: function create() {
    return this.Component();
  }
});
exports.Factory = Factory;

// Exemplar for creating services that implement `nsIFactory` interface, that
// can be registered into runtime via call to `register`. This services return
// enclosed `component` on `getService`.
const Service = Class({
  extends: Factory,
  initialize: function initialize(options) {
    this.component = options.Component();
    Factory.prototype.initialize.call(this, options);
  },
  description: 'Jetpack generated service',
  /**
   * Creates an instance of the class associated with this factory.
   */
  create: function create() {
    return this.component;
  }
});
exports.Service = Service;

function isRegistered({ id }) {
  return isCIDRegistered(id);
}
exports.isRegistered = isRegistered;

/**
 * Registers given `component` object to be used to instantiate a particular
 * class identified by `component.id`, and creates an association of class
 * name and `component.contract` with the class.
 */
function register(factory) {
  if (!(factory instanceof Factory)) {
    throw new Error("xpcom.register() expect a Factory instance.\n" +
                    "Please refactor your code to new xpcom module if you" +
                    " are repacking an addon from SDK <= 1.5:\n" +
                    "https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/platform_xpcom");
  }

  registerFactory(factory.id, factory.description, factory.contract, factory);

  if (factory.unregister)
    require('../system/unload').when(unregister.bind(null, factory));
}
exports.register = register;

/**
 * Unregister a factory associated with a particular class identified by
 * `factory.classID`.
 */
function unregister(factory) {
  if (isRegistered(factory))
    unregisterFactory(factory.id, factory);
}
exports.unregister = unregister;

function autoRegister(path) {
  // TODO: This assumes that the url points to a directory
  // that contains subdirectories corresponding to OS/ABI and then
  // further subdirectories corresponding to Gecko platform version.
  // we should probably either behave intelligently here or allow
  // the caller to pass-in more options if e.g. there aren't
  // Gecko-specific binaries for a component (which will be the case
  // if only frozen interfaces are used).

  var runtime = require("../system/runtime");
  var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
  var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5);

  var file = Cc['@mozilla.org/file/local;1']
             .createInstance(Ci.nsILocalFile);
  file.initWithPath(path);
  file.append(osDirName);
  file.append(platformVersion);

  if (!(file.exists() && file.isDirectory()))
    throw new Error("component not available for OS/ABI " +
                    osDirName + " and platform " + platformVersion);

  Cm.QueryInterface(Ci.nsIComponentRegistrar);
  Cm.autoRegister(file);
}
exports.autoRegister = autoRegister;

/**
 * Returns registered factory that has a given `id` or `null` if not found.
 */
function factoryByID(id) {
  return classesByID[id] || null;
}
exports.factoryByID = factoryByID;

/**
 * Returns factory registered with a given `contract` or `null` if not found.
 * In contrast to `Cc[contract]` that does ignores new factory registration
 * with a given `contract` this will return a factory currently associated
 * with a `contract`.
 */
function factoryByContract(contract) {
  return factoryByID(Cm.contractIDToCID(contract));
}
exports.factoryByContract = factoryByContract;
PK
!<MK50modules/commonjs/sdk/preferences/event-target.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage');
const { EventTarget } = require('../event/target');
const { Branch } = require('./service');
const { emit, off } = require('../event/core');
const { when: unload } = require('../system/unload');

const prefTargetNS = require('../core/namespace').ns();

const PrefsTarget = Class({
  extends: EventTarget,
  initialize: function(options) {
    options = options || {};
    EventTarget.prototype.initialize.call(this, options);

    let branchName = options.branchName || '';
    let branch = Cc["@mozilla.org/preferences-service;1"].
        getService(Ci.nsIPrefService).
        getBranch(branchName).
        QueryInterface(Ci.nsIPrefBranch2);
    prefTargetNS(this).branch = branch;

    // provides easy access to preference values
    this.prefs = Branch(branchName);

    // start listening to preference changes
    let observer = prefTargetNS(this).observer = onChange.bind(this);
    branch.addObserver('', observer);

    // Make sure to destroy this on unload
    unload(destroy.bind(this));
  }
});
exports.PrefsTarget = PrefsTarget;

/* HELPERS */

function onChange(subject, topic, name) {
  if (topic === 'nsPref:changed') {
    emit(this, name, name);
    emit(this, '', name);
  }
}

function destroy() {
  off(this);

  // stop listening to preference changes
  let branch = prefTargetNS(this).branch;
  branch.removeObserver('', prefTargetNS(this).observer);
  prefTargetNS(this).observer = null;
}
PK
!<fxa2modules/commonjs/sdk/preferences/native-options.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, Cu } = require('chrome');
lazyRequire(this, '../system/events', "on");
lazyRequire(this, '../self', "preferencesBranch");
lazyRequire(this, '../l10n/prefs', "localizeInlineOptions");

lazyRequire(this, "resource://gre/modules/Services.jsm", "Services");
lazyRequire(this, "resource://gre/modules/AddonManager.jsm", "AddonManager");
lazyRequire(this, "resource://gre/modules/Preferences.jsm", "Preferences");

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";;
const DEFAULT_OPTIONS_URL = 'data:text/xml,<placeholder/>';

const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color',
                          'file', 'directory', 'control', 'menulist', 'radio'];

const isFennec = require("sdk/system/xul-app").is("Fennec");

function enable({ preferences, id }) {
  return new Promise(resolve => {
    validate(preferences);

    setDefaults(preferences, preferencesBranch);

    // allow the use of custom options.xul
    AddonManager.getAddonByID(id, (addon) => {
      on('addon-options-displayed', onAddonOptionsDisplayed, true);
      resolve({ id });
    });

    function onAddonOptionsDisplayed({ subject: doc, data }) {
      if (data === id) {
        let parent;

        if (isFennec) {
          parent = doc.querySelector('.options-box');

          // NOTE: This disable the CSS rule that makes the options invisible
          let item = doc.querySelector('#addons-details .addon-item');
          item.removeAttribute("optionsURL");
        } else {
          parent = doc.getElementById('detail-downloads').parentNode;
        }

        if (parent) {
          injectOptions({
            preferences: preferences,
            preferencesBranch: preferencesBranch,
            document: doc,
            parent: parent,
            id: id
          });
          localizeInlineOptions(doc);
        } else {
          throw Error("Preferences parent node not found in Addon Details. The configured custom preferences will not be visible.");
        }
      }
    }
  });
}
exports.enable = enable;

// centralized sanity checks
function validate(preferences) {
  for (let { name, title, type, label, options } of preferences) {
    // make sure the title is set and non-empty
    if (!title)
      throw Error("The '" + name + "' pref requires a title");

    // make sure that pref type is a valid inline option type
    if (!~VALID_PREF_TYPES.indexOf(type))
      throw Error("The '" + name + "' pref must be of valid type");

    // if it's a control, make sure it has a label
    if (type === 'control' && !label)
      throw Error("The '" + name + "' control requires a label");

    // if it's a menulist or radio, make sure it has options
    if (type === 'menulist' || type === 'radio') {
      if (!options)
        throw Error("The '" + name + "' pref requires options");

      // make sure each option has a value and a label
      for (let item of options) {
        if (!('value' in item) || !('label' in item))
          throw Error("Each option requires both a value and a label");
      }
    }

    // TODO: check that pref type matches default value type
  }
}
exports.validate = validate;

// initializes default preferences, emulates defaults/prefs.js
function setDefaults(preferences, preferencesBranch) {
  let prefs = new Preferences({
    branch: `extensions.${preferencesBranch}.`,
    defaultBranch: true,
  });

  for (let { name, value } of preferences)
    if (value !== undefined)
      prefs.set(name, value);
}
exports.setDefaults = setDefaults;

// dynamically injects inline options into about:addons page at runtime
// NOTE: on Firefox Desktop the about:addons page is a xul page document,
// on Firefox for Android the about:addons page is an xhtml page, to support both
// the XUL xml namespace have to be enforced.
function injectOptions({ preferences, preferencesBranch, document, parent, id }) {
  preferences.forEach(({name, type, hidden, title, description, label, options, on, off}) => {
    if (hidden) {
      return;
    }

    let setting = document.createElementNS(XUL_NS, 'setting');
    setting.setAttribute('pref-name', name);
    setting.setAttribute('data-jetpack-id', id);
    setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
    setting.setAttribute('type', type);
    setting.setAttribute('title', title);
    if (description)
      setting.setAttribute('desc', description);

    if (type === 'file' || type === 'directory') {
      setting.setAttribute('fullpath', 'true');
    }
    else if (type === 'control') {
      let button = document.createElementNS(XUL_NS, 'button');
      button.setAttribute('pref-name', name);
      button.setAttribute('data-jetpack-id', id);
      button.setAttribute('label', label);
      button.addEventListener('command', function() {
        Services.obs.notifyObservers(null, `${id}-cmdPressed`, name);
      }, true);
      setting.appendChild(button);
    }
    else if (type === 'boolint') {
      setting.setAttribute('on', on);
      setting.setAttribute('off', off);
    }
    else if (type === 'menulist') {
      let menulist = document.createElementNS(XUL_NS, 'menulist');
      let menupopup = document.createElementNS(XUL_NS, 'menupopup');
      for (let { value, label } of options) {
        let menuitem = document.createElementNS(XUL_NS, 'menuitem');
        menuitem.setAttribute('value', value);
        menuitem.setAttribute('label', label);
        menupopup.appendChild(menuitem);
      }
      menulist.appendChild(menupopup);
      setting.appendChild(menulist);
    }
    else if (type === 'radio') {
      let radiogroup = document.createElementNS(XUL_NS, 'radiogroup');
      for (let { value, label } of options) {
        let radio = document.createElementNS(XUL_NS, 'radio');
        radio.setAttribute('value', value);
        radio.setAttribute('label', label);
        radiogroup.appendChild(radio);
      }
      setting.appendChild(radiogroup);
    }

    parent.appendChild(setting);
  });
}
exports.injectOptions = injectOptions;
PK
!<Zww+modules/commonjs/sdk/preferences/service.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

// The minimum and maximum integers that can be set as preferences.
// The range of valid values is narrower than the range of valid JS values
// because the native preferences code treats integers as NSPR PRInt32s,
// which are 32-bit signed integers on all platforms.
const MAX_INT = 0x7FFFFFFF;
const MIN_INT = -0x80000000;

const {Cc,Ci,Cr} = require("chrome");

const prefService = Cc["@mozilla.org/preferences-service;1"].
                getService(Ci.nsIPrefService);
const prefSvc = prefService.getBranch(null);
const defaultBranch = prefService.getDefaultBranch(null);

const { Preferences } = require("resource://gre/modules/Preferences.jsm");
const prefs = new Preferences({});

const branchKeys = branchName =>
  keys(branchName).map($ => $.replace(branchName, ""));

const Branch = function(branchName) {
  return new Proxy(Branch.prototype, {
    getOwnPropertyDescriptor(target, name, receiver) {
      return {
        configurable: true,
        enumerable: true,
        writable: false,
        value: this.get(target, name, receiver)
      };
    },
    ownKeys(target) {
      return branchKeys(branchName);
    },
    get(target, name, receiver) {
      return get(`${branchName}${name}`);
    },
    set(target, name, value, receiver) {
      set(`${branchName}${name}`, value);
      return true;
    },
    has(target, name) {
      return this.hasOwn(target, name);
    },
    hasOwn(target, name) {
      return has(`${branchName}${name}`);
    },
    deleteProperty(target, name) {
      reset(`${branchName}${name}`);
      return true;
    }
  });
}


function get(name, defaultValue) {
  return prefs.get(name, defaultValue);
}
exports.get = get;


function set(name, value) {
  var prefType;
  if (typeof value != "undefined" && value != null)
    prefType = value.constructor.name;

  switch (prefType) {
  case "Number":
    if (value % 1 != 0)
      throw new Error("cannot store non-integer number: " + value);
  }

  prefs.set(name, value);
}
exports.set = set;

const has = prefs.has.bind(prefs)
exports.has = has;

function keys(root) {
  return prefSvc.getChildList(root);
}
exports.keys = keys;

const isSet = prefs.isSet.bind(prefs);
exports.isSet = isSet;

function reset(name) {
  try {
    prefSvc.clearUserPref(name);
  }
  catch (e) {
    // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
    // to reset a pref that doesn't exist or is already set to its default
    // value.  This interface fails silently in those cases, so callers
    // can unconditionally reset a pref without having to check if it needs
    // resetting first or trap exceptions after the fact.  It passes through
    // other exceptions, however, so callers know about them, since we don't
    // know what other exceptions might be thrown and what they might mean.
    if (e.result != Cr.NS_ERROR_UNEXPECTED) {
      throw e;
    }
  }
}
exports.reset = reset;

function getLocalized(name, defaultValue) {
  let value = null;
  try {
    value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
  }
  finally {
    return value || defaultValue;
  }
}
exports.getLocalized = getLocalized;

function setLocalized(name, value) {
  // We can't use `prefs.set` here as we have to use `getDefaultBranch`
  // (instead of `getBranch`) in order to have `mIsDefault` set to true, here:
  // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233
  // Otherwise, we do not enter into this expected condition:
  // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244
  defaultBranch.setCharPref(name, value);
}
exports.setLocalized = setLocalized;

exports.Branch = Branch;

PK
!<)modules/commonjs/sdk/preferences/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
const { on, off } = require("sdk/system/events");
const { getMostRecentBrowserWindow } = require('../window/utils');

// Opens about:addons in a new tab, then displays the inline
// preferences of the provided add-on
const open = ({ id }) => new Promise((resolve, reject) => {
  // opening the about:addons page in a new tab
  let tab = openTab(getMostRecentBrowserWindow(), "about:addons");
  let browser = getBrowserForTab(tab);

  // waiting for the about:addons page to load
  browser.addEventListener("load", function() {
    let window = browser.contentWindow;

    // wait for the add-on's "addon-options-displayed"
    on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
      if (data === id) {
        off("addon-options-displayed", onPrefDisplayed);
        resolve({
          id: id,
          tabId: getTabId(tab),
          "document": doc
        });
      }
    }, true);

    // display the add-on inline preferences page
    window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
  }, {capture: true, once: true});
});
exports.open = open;
PK
!<Yp.modules/commonjs/sdk/private-browsing/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, Cu } = require('chrome');
const { is } = require('../system/xul-app');
const { dispatcher } = require("../util/dispatcher");

lazyRequire(this, '../window/utils', "isWindowPrivate");
lazyRequire(this, '../self', "isPrivateBrowsingSupported");

var PrivateBrowsingUtils;

// Private browsing is only supported in Fx
try {
  PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils;
}
catch (e) {}

exports.isGlobalPBSupported = false;

// checks that per-window private browsing is implemented
var isWindowPBSupported = exports.isWindowPBSupported =
                          !!PrivateBrowsingUtils && is('Firefox');

// checks that per-tab private browsing is implemented
var isTabPBSupported = exports.isTabPBSupported =
                       !!PrivateBrowsingUtils && is('Fennec');

function isPermanentPrivateBrowsing() {
 return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing);
}
exports.isPermanentPrivateBrowsing = isPermanentPrivateBrowsing;

function ignoreWindow(window) {
  return !isPrivateBrowsingSupported && isWindowPrivate(window);
}
exports.ignoreWindow = ignoreWindow;

var getMode = function getMode(chromeWin) {
  return (chromeWin !== undefined && isWindowPrivate(chromeWin));
};
exports.getMode = getMode;

const isPrivate = dispatcher("isPrivate");
isPrivate.when(isPermanentPrivateBrowsing, _ => true);
isPrivate.when(x => x instanceof Ci.nsIDOMWindow, isWindowPrivate);
isPrivate.when(x => Ci.nsIPrivateBrowsingChannel && x instanceof Ci.nsIPrivateBrowsingChannel, x => x.isChannelPrivate);
isPrivate.define(() => false);
exports.isPrivate = isPrivate;
PK
!<Pgg(modules/commonjs/sdk/private-browsing.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "stable"
};

const { isPrivate } = require('./private-browsing/utils');

exports.isPrivate = isPrivate;
PK
!<[p99#modules/commonjs/sdk/querystring.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "unstable"
};

var unescape = decodeURIComponent;
exports.unescape = unescape;

// encodes a string safely for application/x-www-form-urlencoded
// adheres to RFC 3986
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent
function escape(query) {
  return encodeURIComponent(query).
    replace(/%20/g, '+').
    replace(/!/g, '%21').
    replace(/'/g, '%27').
    replace(/\(/g, '%28').
    replace(/\)/g, '%29').
    replace(/\*/g, '%2A');
}
exports.escape = escape;

// Converts an object of unordered key-vals to a string that can be passed
// as part of a request
function stringify(options, separator, assigner) {
  separator = separator || '&';
  assigner = assigner || '=';
  // Explicitly return null if we have null, and empty string, or empty object.
  if (!options)
    return '';

  // If content is already a string, just return it as is.
  if (typeof(options) == 'string')
    return options;

  // At this point we have a k:v object. Iterate over it and encode each value.
  // Arrays and nested objects will get encoded as needed. For example...
  //
  //   { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' }
  //
  // will be encoded as
  //
  //   foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
  //
  // Keys (including '[' and ']') and values will be encoded with
  // `escape` before returning.
  //
  // Execution was inspired by jQuery, but some details have changed and numeric
  // array keys are included (whereas they are not in jQuery).

  let encodedContent = [];
  function add(key, val) {
    encodedContent.push(escape(key) + assigner + escape(val));
  }

  function make(key, value) {
    if (value && typeof(value) === 'object')
      Object.keys(value).forEach(function(name) {
        make(key + '[' + name + ']', value[name]);
      });
    else
      add(key, value);
  }

  Object.keys(options).forEach(function(name) { make(name, options[name]); });
  return encodedContent.join(separator);

  //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
  //        trouble getting that working. It would also be nice to stay
  //        backwards-compat as long as possible. Keeping this in for now...
  // let formData = Cc['@mozilla.org/files/formdata;1'].
  //                createInstance(Ci.nsIDOMFormData);
  // for ([k, v] in Iterator(content)) {
  //   formData.append(k, v);
  // }
  // return formData;
}
exports.stringify = stringify;

// Exporting aliases that nodejs implements just for the sake of
// interoperability.
exports.encode = stringify;
exports.serialize = stringify;

// Note: That `stringify` and `parse` aren't bijective as we use `stringify`
// as it was implement in request module, but implement `parse` to match nodejs
// behavior.
// TODO: Make `stringify` implement API as in nodejs and figure out backwards
// compatibility.
function parse(query, separator, assigner) {
  separator = separator || '&';
  assigner = assigner || '=';
  let result = {};

  if (typeof query !== 'string' || query.length === 0)
    return result;

  query.split(separator).forEach(function(chunk) {
    let pair = chunk.split(assigner);
    let key = unescape(pair[0]);
    let value = unescape(pair.slice(1).join(assigner));

    if (!(key in result))
      result[key] = value;
    else if (Array.isArray(result[key]))
      result[key].push(value);
    else
      result[key] = [result[key], value];
  });

  return result;
};
exports.parse = parse;
// Exporting aliases that nodejs implements just for the sake of
// interoperability.
exports.decode = parse;
PK
!<0͈$modules/commonjs/sdk/remote/child.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { isChildLoader } = require('./core');
if (!isChildLoader)
  throw new Error("Cannot load sdk/remote/child in a main process loader.");

const { Ci, Cc, Cu } = require('chrome');
const runtime = require('../system/runtime');
const { Class } = require('../core/heritage');
const { Namespace } = require('../core/namespace');
const { omit } = require('../util/object');
const { when } = require('../system/unload');
const { EventTarget } = require('../event/target');
const { emit } = require('../event/core');
const { Disposable } = require('../core/disposable');
const { EventParent } = require('./utils');
const { addListItem, removeListItem } = require('../util/list');

const loaderID = require('@loader/options').loaderID;

const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

const mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
           getService(Ci.nsISyncMessageSender);

const ns = Namespace();

const process = {
  port: new EventTarget(),
  get id() {
    return runtime.processID;
  },
  get isRemote() {
    return runtime.processType != MAIN_PROCESS;
  }
};
exports.process = process;

function definePort(obj, name) {
  obj.port.emit = (event, ...args) => {
    let manager = ns(obj).messageManager;
    if (!manager)
      return;

    manager.sendAsyncMessage(name, { loaderID, event, args });
  };
}

function messageReceived({ data, objects }) {
  // Ignore messages from other loaders
  if (data.loaderID != loaderID)
    return;

  let keys = Object.keys(objects);
  if (keys.length) {
    // If any objects are CPOWs then ignore this message. We don't want child
    // processes interracting with CPOWs
    if (!keys.every(name => !Cu.isCrossProcessWrapper(objects[name])))
      return;

    data.args.push(objects);
  }

  emit(this.port, data.event, this, ...data.args);
}

ns(process).messageManager = mm;
definePort(process, 'sdk/remote/process/message');
let processMessageReceived = messageReceived.bind(process);
mm.addMessageListener('sdk/remote/process/message', processMessageReceived);

when(() => {
  mm.removeMessageListener('sdk/remote/process/message', processMessageReceived);
  frames = null;
});

process.port.on('sdk/remote/require', (process, uri) => {
  require(uri);
});

function listenerEquals(a, b) {
  for (let prop of ["type", "callback", "isCapturing"]) {
    if (a[prop] != b[prop])
      return false;
  }
  return true;
}

function listenerFor(type, callback, isCapturing = false) {
  return {
    type,
    callback,
    isCapturing,
    registeredCallback: undefined,
    get args() {
      return [
        this.type,
        this.registeredCallback ? this.registeredCallback : this.callback,
        this.isCapturing
      ];
    }
  };
}

function removeListenerFromArray(array, listener) {
  let index = array.findIndex(l => listenerEquals(l, listener));
  if (index < 0)
    return;
  array.splice(index, 1);
}

function getListenerFromArray(array, listener) {
  return array.find(l => listenerEquals(l, listener));
}

function arrayContainsListener(array, listener) {
  return !!getListenerFromArray(array, listener);
}

function makeFrameEventListener(frame, callback) {
  return callback.bind(frame);
}

var FRAME_ID = 0;
var tabMap = new Map();

const Frame = Class({
  implements: [ Disposable ],
  extends: EventTarget,
  setup: function(contentFrame) {
    // This ID should be unique for this loader across all processes
    let priv = ns(this);

    priv.id = runtime.processID + ":" + FRAME_ID++;

    priv.contentFrame = contentFrame;
    priv.messageManager = contentFrame;
    priv.domListeners = [];

    tabMap.set(contentFrame.docShell, this);

    priv.messageReceived = messageReceived.bind(this);
    priv.messageManager.addMessageListener('sdk/remote/frame/message', priv.messageReceived);

    this.port = new EventTarget();
    definePort(this, 'sdk/remote/frame/message');

    priv.messageManager.sendAsyncMessage('sdk/remote/frame/attach', {
      loaderID,
      frameID: priv.id,
      processID: runtime.processID
    });

    frames.attachItem(this);
  },

  dispose: function() {
    let priv = ns(this);

    emit(this, 'detach', this);

    for (let listener of priv.domListeners)
      priv.contentFrame.removeEventListener(...listener.args);

    priv.messageManager.removeMessageListener('sdk/remote/frame/message', priv.messageReceived);
    tabMap.delete(priv.contentFrame.docShell);
    priv.contentFrame = null;
  },

  get content() {
    return ns(this).contentFrame.content;
  },

  get isTab() {
    let docShell = ns(this).contentFrame.docShell;
    if (process.isRemote) {
      // We don't want to roundtrip to the main process to get this property.
      // This hack relies on the host app having defined webBrowserChrome only
      // in frames that are part of the tabs. Since only Firefox has remote
      // processes right now and does this this works.
      let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsITabChild);
      return !!tabchild.webBrowserChrome;
    }
    else {
      // This is running in the main process so we can break out to the browser
      // And check we can find a tab for the browser element directly.
      let browser = docShell.chromeEventHandler;
      let tab = require('../tabs/utils').getTabForBrowser(browser);
      return !!tab;
    }
  },

  addEventListener: function(...args) {
    let priv = ns(this);

    let listener = listenerFor(...args);
    if (arrayContainsListener(priv.domListeners, listener))
      return;

    listener.registeredCallback = makeFrameEventListener(this, listener.callback);

    priv.domListeners.push(listener);
    priv.contentFrame.addEventListener(...listener.args);
  },

  removeEventListener: function(...args) {
    let priv = ns(this);

    let listener = getListenerFromArray(priv.domListeners, listenerFor(...args));
    if (!listener)
      return;

    removeListenerFromArray(priv.domListeners, listener);
    priv.contentFrame.removeEventListener(...listener.args);
  }
});

const FrameList = Class({
  implements: [ EventParent, Disposable ],
  extends: EventTarget,
  setup: function() {
    EventParent.prototype.initialize.call(this);

    this.port = new EventTarget();
    ns(this).domListeners = [];

    this.on('attach', frame => {
      for (let listener of ns(this).domListeners)
        frame.addEventListener(...listener.args);
    });
  },

  dispose: function() {
    // The only case where we get destroyed is when the loader is unloaded in
    // which case each frame will clean up its own event listeners.
    ns(this).domListeners = null;
  },

  getFrameForWindow: function(window) {
    let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDocShell);

    return tabMap.get(docShell) || null;
  },

  addEventListener: function(...args) {
    let listener = listenerFor(...args);
    if (arrayContainsListener(ns(this).domListeners, listener))
      return;

    ns(this).domListeners.push(listener);
    for (let frame of this)
      frame.addEventListener(...listener.args);
  },

  removeEventListener: function(...args) {
    let listener = listenerFor(...args);
    if (!arrayContainsListener(ns(this).domListeners, listener))
      return;

    removeListenerFromArray(ns(this).domListeners, listener);
    for (let frame of this)
      frame.removeEventListener(...listener.args);
  }
});
var frames = exports.frames = new FrameList();

function registerContentFrame(contentFrame) {
  let frame = new Frame(contentFrame);
}
exports.registerContentFrame = registerContentFrame;

function unregisterContentFrame(contentFrame) {
  let frame = tabMap.get(contentFrame.docShell);
  if (!frame)
    return;

  frame.destroy();
}
exports.unregisterContentFrame = unregisterContentFrame;
PK
!<b66#modules/commonjs/sdk/remote/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const options = require("@loader/options");

exports.isChildLoader = options.childLoader;
PK
!<p>*(*(%modules/commonjs/sdk/remote/parent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { isChildLoader } = require('./core');
if (isChildLoader)
  throw new Error("Cannot load sdk/remote/parent in a child loader.");

const { Cu, Ci, Cc } = require('chrome');
const runtime = require('../system/runtime');

const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

if (runtime.processType != MAIN_PROCESS) {
  throw new Error('Cannot use sdk/remote/parent in a child process.');
}

const { Class } = require('../core/heritage');
const { Namespace } = require('../core/namespace');
const { Disposable } = require('../core/disposable');
const { omit } = require('../util/object');
const { when } = require('../system/unload');
const { EventTarget } = require('../event/target');
const { emit } = require('../event/core');
const system = require('../system/events');
const { EventParent } = require('./utils');
const options = require('@loader/options');
const loaderModule = require('toolkit/loader');

lazyRequire(this, '../tabs/utils', "getTabForBrowser");

const appInfo = Cc["@mozilla.org/xre/app-info;1"].
                getService(Ci.nsIXULRuntime);

exports.useRemoteProcesses = appInfo.browserTabsRemoteAutostart;

// Chose the right function for resolving relative a module id
var moduleResolve;
if (options.isNative) {
  moduleResolve = (id, requirer) => loaderModule.nodeResolve(id, requirer, { rootURI: options.rootURI });
}
else {
  moduleResolve = loaderModule.resolve;
}

// Load the scripts in the child processes
var { getNewLoaderID } = require('../../framescript/FrameScriptManager.jsm');
var PATH = options.paths[''];

const childOptions = omit(options, ['modules', 'globals', 'resolve', 'load']);
childOptions.modules = {};
// @l10n/data is just JSON data and can be safely sent across to the child loader
try {
  childOptions.modules["@l10n/data"] = require("@l10n/data");
}
catch (e) {
  // There may be no l10n data
}
const loaderID = getNewLoaderID();
childOptions.loaderID = loaderID;
childOptions.childLoader = true;

const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
             getService(Ci.nsIMessageBroadcaster);
const gmm = Cc['@mozilla.org/globalmessagemanager;1'].
            getService(Ci.nsIMessageBroadcaster);

const ns = Namespace();

var processMap = new Map();

function definePort(obj, name) {
  obj.port.emitCPOW = (event, args, cpows = {}) => {
    let manager = ns(obj).messageManager;
    if (!manager)
      return;

    let method = manager instanceof Ci.nsIMessageBroadcaster ?
                 "broadcastAsyncMessage" : "sendAsyncMessage";

    manager[method](name, { loaderID, event, args }, cpows);
  };

  obj.port.emit = (event, ...args) => obj.port.emitCPOW(event, args);
}

function messageReceived({ target, data }) {
  // Ignore messages from other loaders
  if (data.loaderID != loaderID)
    return;

  emit(this.port, data.event, this, ...data.args);
}

// Process represents a gecko process that can load webpages. Each process
// contains a number of Frames. This class is used to send and receive messages
// from a single process.
const Process = Class({
  implements: [ Disposable ],
  extends: EventTarget,
  setup: function(id, messageManager, isRemote) {
    ns(this).id = id;
    ns(this).isRemote = isRemote;
    ns(this).messageManager = messageManager;
    ns(this).messageReceived = messageReceived.bind(this);
    this.destroy = this.destroy.bind(this);
    ns(this).messageManager.addMessageListener('sdk/remote/process/message', ns(this).messageReceived);
    ns(this).messageManager.addMessageListener('child-process-shutdown', this.destroy);

    this.port = new EventTarget();
    definePort(this, 'sdk/remote/process/message');

    // Load any remote modules
    for (let module of remoteModules.values())
      this.port.emit('sdk/remote/require', module);

    processMap.set(ns(this).id, this);
    processes.attachItem(this);
  },

  dispose: function() {
    emit(this, 'detach', this);
    processMap.delete(ns(this).id);
    ns(this).messageManager.removeMessageListener('sdk/remote/process/message', ns(this).messageReceived);
    ns(this).messageManager.removeMessageListener('child-process-shutdown', this.destroy);
    ns(this).messageManager = null;
  },

  // Returns true if this process is a child process
  get isRemote() {
    return ns(this).isRemote;
  }
});

// Processes gives an API for enumerating an sending and receiving messages from
// all processes as well as detecting when a new process starts.
const Processes = Class({
  implements: [ EventParent ],
  extends: EventTarget,
  initialize: function() {
    EventParent.prototype.initialize.call(this);
    ns(this).messageManager = ppmm;

    this.port = new EventTarget();
    definePort(this, 'sdk/remote/process/message');
  },

  getById: function(id) {
    return processMap.get(id);
  }
});
var processes = exports.processes = new Processes();

var frameMap = new Map();

function setFrameProcess(frame, process) {
  ns(frame).process = process;
  frames.attachItem(frame);
}

// Frames display webpages in a process. In the main process every Frame is
// linked with a <browser> or <iframe> element. 
const Frame = Class({
  implements: [ Disposable ],
  extends: EventTarget,
  setup: function(id, node) {
    ns(this).id = id;
    ns(this).node = node;

    let frameLoader = node.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
    ns(this).messageManager = frameLoader.messageManager;

    ns(this).messageReceived = messageReceived.bind(this);
    ns(this).messageManager.addMessageListener('sdk/remote/frame/message', ns(this).messageReceived);

    this.port = new EventTarget();
    definePort(this, 'sdk/remote/frame/message');

    frameMap.set(ns(this).messageManager, this);
  },

  dispose: function() {
    emit(this, 'detach', this);
    ns(this).messageManager.removeMessageListener('sdk/remote/frame/message', ns(this).messageReceived);

    frameMap.delete(ns(this).messageManager);
    ns(this).messageManager = null;
  },

  // Returns the browser or iframe element this frame displays in
  get frameElement() {
    return ns(this).node;
  },

  // Returns the process that this frame loads in
  get process() {
    return ns(this).process;
  },

  // Returns true if this frame is a tab in a main browser window
  get isTab() {
    let tab = getTabForBrowser(ns(this).node);
    return !!tab;
  }
});

function managerDisconnected({ subject: manager }) {
  let frame = frameMap.get(manager);
  if (frame)
    frame.destroy();
}
system.on('message-manager-disconnect', managerDisconnected);

// Provides an API for enumerating and sending and receiving messages from all
// Frames
const FrameList = Class({
  implements: [ EventParent ],
  extends: EventTarget,
  initialize: function() {
    EventParent.prototype.initialize.call(this);
    ns(this).messageManager = gmm;

    this.port = new EventTarget();
    definePort(this, 'sdk/remote/frame/message');
  },

  // Returns the frame for a browser element
  getFrameForBrowser: function(browser) {
    for (let frame of this) {
      if (frame.frameElement == browser)
        return frame;
    }
    return null;
  },
});
var frames = exports.frames = new FrameList();

// Create the module loader in any existing processes
ppmm.broadcastAsyncMessage('sdk/remote/process/load', {
  modulePath: PATH,
  loaderID,
  options: childOptions,
  reason: "broadcast"
});

// A loader has started in a remote process
function processLoaderStarted({ target, data }) {
  if (data.loaderID != loaderID)
    return;

  if (processMap.has(data.processID)) {
    console.error("Saw the same process load the same loader twice. This is a bug in the SDK.");
    return;
  }

  let process = new Process(data.processID, target, data.isRemote);

  if (pendingFrames.has(data.processID)) {
    for (let frame of pendingFrames.get(data.processID))
      setFrameProcess(frame, process);
    pendingFrames.delete(data.processID);
  }
}

// A new process has started
function processStarted({ target, data: { modulePath } }) {
  if (modulePath != PATH)
    return;

  // Have it load a loader if it hasn't already
  target.sendAsyncMessage('sdk/remote/process/load', {
    modulePath,
    loaderID,
    options: childOptions,
    reason: "response"
  });
}

var pendingFrames = new Map();

// A new frame has been created in the remote process
function frameAttached({ target, data }) {
  if (data.loaderID != loaderID)
    return;

  let frame = new Frame(data.frameID, target);

  let process = processMap.get(data.processID);
  if (process) {
    setFrameProcess(frame, process);
    return;
  }

  // In some cases frame messages can arrive earlier than process messages
  // causing us to see a new frame appear before its process. In this case
  // cache the frame data until we see the process. See bug 1131375.
  if (!pendingFrames.has(data.processID))
    pendingFrames.set(data.processID, [frame]);
  else
    pendingFrames.get(data.processID).push(frame);
}

// Wait for new processes and frames
ppmm.addMessageListener('sdk/remote/process/attach', processLoaderStarted);
ppmm.addMessageListener('sdk/remote/process/start', processStarted);
gmm.addMessageListener('sdk/remote/frame/attach', frameAttached);

when(reason => {
  ppmm.removeMessageListener('sdk/remote/process/attach', processLoaderStarted);
  ppmm.removeMessageListener('sdk/remote/process/start', processStarted);
  gmm.removeMessageListener('sdk/remote/frame/attach', frameAttached);

  ppmm.broadcastAsyncMessage('sdk/remote/process/unload', { loaderID, reason });
});

var remoteModules = new Set();

// Ensures a module is loaded in every child process. It is safe to send 
// messages to this module immediately after calling this.
// Pass a module to resolve the id relatively.
function remoteRequire(id, module = null) {
  // Resolve relative to calling module if passed
  if (module)
    id = moduleResolve(id, module.id);

  // Don't reload the same module
  if (remoteModules.has(id))
    return;

  remoteModules.add(id);
  processes.port.emit('sdk/remote/require', id);
}
exports.remoteRequire = remoteRequire;
PK
!<UAj$modules/commonjs/sdk/remote/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Class } = require('../core/heritage');
const { List, addListItem, removeListItem } = require('../util/list');
lazyRequire(this, '../event/core', 'emit');
lazyRequire(this, '../event/utils', 'pipe');

// A helper class that maintains a list of EventTargets. Any events emitted
// to an EventTarget are also emitted by the EventParent. Likewise for an
// EventTarget's port property.
const EventParent = Class({
  implements: [ List ],

  attachItem: function(item) {
    addListItem(this, item);

    pipe(item.port, this.port);
    pipe(item, this);

    item.once('detach', () => {
      removeListItem(this, item);
    })

    emit(this, 'attach', item);
  },

  // Calls listener for every object already in the list and every object
  // subsequently added to the list.
  forEvery: function(listener) {
    for (let item of this)
      listener(item);

    this.on('attach', listener);
  }
});
exports.EventParent = EventParent;
PK
!<˝yymodules/commonjs/sdk/request.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable"
};

const { ns } = require("./core/namespace");
const { emit } = require("./event/core");
const { merge } = require("./util/object");
const { stringify } = require("./querystring");
const { EventTarget } = require("./event/target");
const { Class } = require("./core/heritage");
const { XMLHttpRequest, forceAllowThirdPartyCookie } = require("./net/xhr");
const apiUtils = require("./deprecated/api-utils");
const { isValidURI } = require("./url.js");

const response = ns();
const request = ns();

// Instead of creating a new validator for each request, just make one and
// reuse it.
const { validateOptions, validateSingleOption } = new OptionsValidator({
  url: {
    // Also converts a URL instance to string, bug 857902
    map: url => url.toString(),
    ok: isValidURI
  },
  headers: {
    map: v => v || {},
    is:  ["object"],
  },
  content: {
    map: v => v || null,
    is:  ["string", "object", "null"],
  },
  contentType: {
    map: v => v || "application/x-www-form-urlencoded",
    is:  ["string"],
  },
  overrideMimeType: {
    map: v => v || null,
    is: ["string", "null"],
  },
  anonymous: {
    map: v => v || false,
    is: ["boolean", "null"],
  }
});

const REUSE_ERROR = "This request object has been used already. You must " +
                    "create a new one to make a new request."

// Utility function to prep the request since it's the same between
// request types
function runRequest(mode, target) {
  let source = request(target)
  let { xhr, url, content, contentType, headers, overrideMimeType, anonymous } = source;

  let isGetOrHead = (mode == "GET" || mode == "HEAD");

  // If this request has already been used, then we can't reuse it.
  // Throw an error.
  if (xhr)
    throw new Error(REUSE_ERROR);

  xhr = source.xhr = new XMLHttpRequest({
    mozAnon: anonymous
  });

  // Build the data to be set. For GET or HEAD requests, we want to append that
  // to the URL before opening the request.
  let data = stringify(content);
  // If the URL already has ? in it, then we want to just use &
  if (isGetOrHead && data)
    url = url + (/\?/.test(url) ? "&" : "?") + data;

  // open the request
  xhr.open(mode, url);


  forceAllowThirdPartyCookie(xhr);

  // request header must be set after open, but before send
  xhr.setRequestHeader("Content-Type", contentType);

  // set other headers
  Object.keys(headers).forEach(function(name) {
    xhr.setRequestHeader(name, headers[name]);
  });

  // set overrideMimeType
  if (overrideMimeType)
    xhr.overrideMimeType(overrideMimeType);

  // handle the readystate, create the response, and call the callback
  xhr.onreadystatechange = function onreadystatechange() {
    if (xhr.readyState === 4) {
      let response = Response(xhr);
      source.response = response;
      emit(target, 'complete', response);
    }
  };

  // actually send the request.
  // We don't want to send data on GET or HEAD requests.
  xhr.send(!isGetOrHead ? data : null);
}

const Request = Class({
  extends: EventTarget,
  initialize: function initialize(options) {
    // `EventTarget.initialize` will set event listeners that are named
    // like `onEvent` in this case `onComplete` listener will be set to
    // `complete` event.
    EventTarget.prototype.initialize.call(this, options);

    // Copy normalized options.
    merge(request(this), validateOptions(options));
  },
  get url() { return request(this).url; },
  set url(value) { request(this).url = validateSingleOption('url', value); },
  get headers() { return request(this).headers; },
  set headers(value) {
    return request(this).headers = validateSingleOption('headers', value);
  },
  get content() { return request(this).content; },
  set content(value) {
    request(this).content = validateSingleOption('content', value);
  },
  get contentType() { return request(this).contentType; },
  set contentType(value) {
    request(this).contentType = validateSingleOption('contentType', value);
  },
  get anonymous() { return request(this).anonymous; },
  get response() { return request(this).response; },
  delete: function() {
    runRequest('DELETE', this);
    return this;
  },
  get: function() {
    runRequest('GET', this);
    return this;
  },
  post: function() {
    runRequest('POST', this);
    return this;
  },
  put: function() {
    runRequest('PUT', this);
    return this;
  },
  head: function() {
    runRequest('HEAD', this);
    return this;
  }
});
exports.Request = Request;

const Response = Class({
  initialize: function initialize(request) {
    response(this).request = request;
  },
  // more about responseURL: https://bugzilla.mozilla.org/show_bug.cgi?id=998076
  get url() {
    return response(this).request.responseURL;
  },
  get text() {
    return response(this).request.responseText;
  },
  get xml() {
    throw new Error("Sorry, the 'xml' property is no longer available. " +
                    "see bug 611042 for more information.");
  },
  get status() {
    return response(this).request.status;
  },
  get statusText() {
    return response(this).request.statusText;
  },
  get json() {
    try {
      return JSON.parse(this.text);
    } catch(error) {
      return null;
    }
  },
  get headers() {
    let headers = {}, lastKey;
    // Since getAllResponseHeaders() will return null if there are no headers,
    // defend against it by defaulting to ""
    let rawHeaders = response(this).request.getAllResponseHeaders() || "";
    rawHeaders.split("\n").forEach(function (h) {
      // According to the HTTP spec, the header string is terminated by an empty
      // line, so we can just skip it.
      if (!h.length) {
        return;
      }

      let index = h.indexOf(":");
      // The spec allows for leading spaces, so instead of assuming a single
      // leading space, just trim the values.
      let key = h.substring(0, index).trim(),
          val = h.substring(index + 1).trim();

      // For empty keys, that means that the header value spanned multiple lines.
      // In that case we should append the value to the value of lastKey with a
      // new line. We'll assume lastKey will be set because there should never
      // be an empty key on the first pass.
      if (key) {
        headers[key] = val;
        lastKey = key;
      }
      else {
        headers[lastKey] += "\n" + val;
      }
    });
    return headers;
  },
  get anonymous() {
    return response(this).request.mozAnon;
  }
});

// apiUtils.validateOptions doesn't give the ability to easily validate single
// options, so this is a wrapper that provides that ability.
function OptionsValidator(rules) {
  return {
    validateOptions: function (options) {
      return apiUtils.validateOptions(options, rules);
    },
    validateSingleOption: function (field, value) {
      // We need to create a single rule object from our listed rules. To avoid
      // JavaScript String warnings, check for the field & default to an empty object.
      let singleRule = {};
      if (field in rules) {
        singleRule[field] = rules[field];
      }
      let singleOption = {};
      singleOption[field] = value;
      // This should throw if it's invalid, which will bubble up & out.
      return apiUtils.validateOptions(singleOption, singleRule)[field];
    }
  };
}
PK
!<>]99!modules/commonjs/sdk/selection.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable",
  "engines": {
    "Firefox": "*",
    "SeaMonkey": "*"
  }
};

const { Ci, Cc } = require("chrome");
lazyRequire(this, "./timers", "setTimeout");
lazyRequire(this, "./event/core", "emit", "off");
const { Class, obscure } = require("./core/heritage");
const { EventTarget } = require("./event/target");
const { ns } = require("./core/namespace");
const { when: unload } = require("./system/unload");
lazyRequire(this, './private-browsing/utils', "ignoreWindow");
lazyRequire(this, './tabs/utils', "getTabs", "getTabForContentWindow", "getAllTabContentWindows");
lazyRequireModule(this, "./window/utils", "winUtils");
const events = require("./system/events");

// The selection types
const HTML = 0x01,
      TEXT = 0x02,
      DOM  = 0x03; // internal use only

// A more developer-friendly message than the caught exception when is not
// possible change a selection.
const ERR_CANNOT_CHANGE_SELECTION =
  "It isn't possible to change the selection, as there isn't currently a selection";

const selections = ns();

const Selection = Class({
  /**
   * Creates an object from which a selection can be set, get, etc. Each
   * object has an associated with a range number. Range numbers are the
   * 0-indexed counter of selection ranges as explained at
   * https://developer.mozilla.org/en/DOM/Selection.
   *
   * @param rangeNumber
   *        The zero-based range index into the selection
   */
  initialize: function initialize(rangeNumber) {
    // In order to hide the private `rangeNumber` argument from API consumers
    // while still enabling Selection getters/setters to access it, we define
    // it as non enumerable, non configurable property. While consumers still
    // may discover it they won't be able to do any harm which is good enough
    // in this case.
    Object.defineProperties(this, {
      rangeNumber: {
        enumerable: false,
        configurable: false,
        value: rangeNumber
      }
    });
  },
  get text() { return getSelection(TEXT, this.rangeNumber); },
  set text(value) { setSelection(TEXT, value, this.rangeNumber); },
  get html() { return getSelection(HTML, this.rangeNumber); },
  set html(value) { setSelection(HTML, value, this.rangeNumber); },
  get isContiguous() {

    // If there are multiple non empty ranges, the selection is definitely
    // discontiguous. It returns `false` also if there are no valid selection.
    let count = 0;
    for (let sel in selectionIterator)
      if (++count > 1)
        break;

    return count === 1;
  }
});

const selectionListener = {
  notifySelectionChanged: function (document, selection, reason) {
    if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(type => reason &
      Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
        return;

    this.onSelect();
  },

  onSelect: function() {
    emit(module.exports, "select");
  }
}

/**
 * Defines iterators so that discontiguous selections can be iterated.
 * Empty selections are skipped - see `safeGetRange` for further details.
 *
 * If discontiguous selections are in a text field, only the first one
 * is returned because the text field selection APIs doesn't support
 * multiple selections.
 */
function* forOfIterator() {
  let selection = getSelection(DOM);
  let count = 0;

  if (selection)
    count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);

  for (let i = 0; i < count; i++) {
    let sel = Selection(i);

    if (sel.text)
      yield Selection(i);
  }
}

const selectionIteratorOptions = {
  __iterator__: function() {
      for (let item of this)
          yield item;
  }
}
selectionIteratorOptions[Symbol.iterator] = forOfIterator;
const selectionIterator = obscure(selectionIteratorOptions);

/**
 * Returns the most recent focused window.
 * if private browsing window is most recent and not supported,
 * then ignore it and return `null`, because the focused window
 * can't be targeted.
 */
function getFocusedWindow() {
  let window = winUtils.getFocusedWindow();

  return ignoreWindow(window) ? null : window;
}

/**
 * Returns the focused element in the most recent focused window
 * if private browsing window is most recent and not supported,
 * then ignore it and return `null`, because the focused element
 * can't be targeted.
 */
function getFocusedElement() {
  let element = winUtils.getFocusedElement();

  if (!element || ignoreWindow(element.ownerGlobal))
    return null;

  return element;
}

/**
 * Returns the current selection from most recent content window. Depending on
 * the specified |type|, the value returned can be a string of text, stringified
 * HTML, or a DOM selection object as described at
 * https://developer.mozilla.org/en/DOM/Selection.
 *
 * @param type
 *        Specifies the return type of the selection. Valid values are the one
 *        of the constants HTML, TEXT, or DOM.
 *
 * @param rangeNumber
 *        Specifies the zero-based range index of the returned selection.
 */
function getSelection(type, rangeNumber) {
  let window, selection;
  try {
    window = getFocusedWindow();
    selection = window.getSelection();
  }
  catch (e) {
    return null;
  }

  // Get the selected content as the specified type
  if (type == DOM) {
    return selection;
  }
  else if (type == TEXT) {
    let range = safeGetRange(selection, rangeNumber);

    if (range)
      return range.toString();

    let node = getElementWithSelection();

    if (!node)
      return null;

    return node.value.substring(node.selectionStart, node.selectionEnd);
  }
  else if (type == HTML) {
    let range = safeGetRange(selection, rangeNumber);
    // Another way, but this includes the xmlns attribute for all elements in
    // Gecko 1.9.2+ :
    // return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
    //   createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
    //     cloneContents());
    if (!range)
      return null;

    let node = window.document.createElement("span");
    node.appendChild(range.cloneContents());
    return node.innerHTML;
  }

  throw new Error("Type " + type + " is unrecognized.");
}

/**
 * Sets the current selection of the most recent content document by changing
 * the existing selected text/HTML range to the specified value.
 *
 * @param val
 *        The value for the new selection
 *
 * @param rangeNumber
 *        The zero-based range index of the selection to be set
 *
 */
function setSelection(type, val, rangeNumber) {
  // Make sure we have a window context & that there is a current selection.
  // Selection cannot be set unless there is an existing selection.
  let window, selection;

  try {
    window = getFocusedWindow();
    selection = window.getSelection();
  }
  catch (e) {
    throw new Error(ERR_CANNOT_CHANGE_SELECTION);
  }

  let range = safeGetRange(selection, rangeNumber);

  if (range) {
    let fragment;

    if (type === HTML)
      fragment = range.createContextualFragment(val);
    else {
      fragment = range.createContextualFragment("");
      fragment.textContent = val;
    }

    range.deleteContents();
    range.insertNode(fragment);
  }
  else {
    let node = getElementWithSelection();

    if (!node)
      throw new Error(ERR_CANNOT_CHANGE_SELECTION);

    let { value, selectionStart, selectionEnd } = node;

    let newSelectionEnd = selectionStart + val.length;

    node.value = value.substring(0, selectionStart) +
                  val +
                  value.substring(selectionEnd, value.length);

    node.setSelectionRange(selectionStart, newSelectionEnd);
  }
}

/**
 * Returns the specified range in a selection without throwing an exception.
 *
 * @param selection
 *        A selection object as described at
 *         https://developer.mozilla.org/en/DOM/Selection
 *
 * @param [rangeNumber]
 *        Specifies the zero-based range index of the returned selection.
 *        If it's not provided the function will return the first non empty
 *        range, if any.
 */
function safeGetRange(selection, rangeNumber) {
  try {
    let { rangeCount } = selection;
    let range = null;

    if (typeof rangeNumber === "undefined")
      rangeNumber = 0;
    else
      rangeCount = rangeNumber + 1;

    for (; rangeNumber < rangeCount; rangeNumber++ ) {
      range = selection.getRangeAt(rangeNumber);

      if (range && range.toString())
        break;

      range = null;
    }

    return range;
  }
  catch (e) {
    return null;
  }
}

/**
 * Returns a reference of the DOM's active element for the window given, if it
 * supports the text field selection API and has a text selected.
 *
 * Note:
 *   we need this method because window.getSelection doesn't return a selection
 *   for text selected in a form field (see bug 85686)
 */
function getElementWithSelection() {
  let element = getFocusedElement();

  if (!element)
    return null;

  try {
    // Accessing selectionStart and selectionEnd on e.g. a button
    // results in an exception thrown as per the HTML5 spec.  See
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection

    let { value, selectionStart, selectionEnd } = element;

    let hasSelection = typeof value === "string" &&
                      !isNaN(selectionStart) &&
                      !isNaN(selectionEnd) &&
                      selectionStart !== selectionEnd;

    return hasSelection ? element : null;
  }
  catch (err) {
    return null;
  }

}

/**
 * Adds the Selection Listener to the content's window given
 */
function addSelectionListener(window) {
  let selection = window.getSelection();

  // Don't add the selection's listener more than once to the same window,
  // if the selection object is the same
  if ("selection" in selections(window) && selections(window).selection === selection)
    return;

  // We ensure that the current selection is an instance of
  // `nsISelectionPrivate` before working on it, in case is `null`.
  //
  // If it's `null` it's likely too early to add the listener, and we demand
  // that operation to `document-shown` - it can easily happens for frames
  if (selection instanceof Ci.nsISelectionPrivate)
    selection.addSelectionListener(selectionListener);

  // nsISelectionListener implementation seems not fire a notification if
  // a selection is in a text field, therefore we need to add a listener to
  // window.onselect, that is fired only for text fields.
  // For consistency, we add it only when the nsISelectionListener is added.
  //
  // https://developer.mozilla.org/en/DOM/window.onselect
  window.addEventListener("select", selectionListener.onSelect, true);

  selections(window).selection = selection;
};

/**
 * Removes the Selection Listener to the content's window given
 */
function removeSelectionListener(window) {
  // Don't remove the selection's listener to a window that wasn't handled.
  if (!("selection" in selections(window)))
    return;

  let selection = window.getSelection();
  let isSameSelection = selection === selections(window).selection;

  // Before remove the listener, we ensure that the current selection is an
  // instance of `nsISelectionPrivate` (it could be `null`), and that is still
  // the selection we managed for this window (it could be detached).
  if (selection instanceof Ci.nsISelectionPrivate && isSameSelection)
    selection.removeSelectionListener(selectionListener);

  window.removeEventListener("select", selectionListener.onSelect, true);

  delete selections(window).selection;
};

function onContent(event) {
  let window = event.subject.defaultView;

  // We are not interested in documents without valid defaultView (e.g. XML)
  // that aren't in a tab (e.g. Panel); or in private windows
   if (window && getTabForContentWindow(window) && !ignoreWindow(window)) {
    addSelectionListener(window);
  }
}

// Adds Selection listener to new documents
// Note that strong reference is needed for documents that are loading slowly or
// where the server didn't close the connection (e.g. "comet").
events.on("document-element-inserted", onContent, true);

// Adds Selection listeners to existing documents
getAllTabContentWindows().forEach(addSelectionListener);

// When a document is not visible anymore the selection object is detached, and
// a new selection object is created when it becomes visible again.
// That makes the previous selection's listeners added previously totally
// useless – the listeners are not notified anymore.
// To fix that we're listening for `document-shown` event in order to add
// the listeners to the new selection object created.
//
// See bug 665386 for further details.

function onShown(event) {
  let window = event.subject.defaultView;

  // We are not interested in documents without valid defaultView.
  // For example XML documents don't have windows and we don't yet support them.
  if (!window)
    return;

  // We want to handle only the windows where we added selection's listeners
  if ("selection" in selections(window)) {
    let currentSelection = window.getSelection();
    let { selection } = selections(window);

    // If the current selection for the window given is different from the one
    // stored in the namespace, we need to add the listeners again, and replace
    // the previous selection in our list with the new one.
    //
    // Notice that we don't have to remove the listeners from the old selection,
    // because is detached. An attempt to remove the listener, will raise an
    // error (see http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#5343 )
    //
    // We ensure that the current selection is an instance of
    // `nsISelectionPrivate` before working on it, in case is `null`.
    if (currentSelection instanceof Ci.nsISelectionPrivate &&
      currentSelection !== selection) {

      window.addEventListener("select", selectionListener.onSelect, true);
      currentSelection.addSelectionListener(selectionListener);
      selections(window).selection = currentSelection;
    }
  }
}

events.on("document-shown", onShown, true);

// Removes Selection listeners when the add-on is unloaded
unload(function(){
  getAllTabContentWindows().forEach(removeSelectionListener);

  events.off("document-element-inserted", onContent);
  events.off("document-shown", onShown);

  off(exports);
});

const selection = Class({
  extends: EventTarget,
  implements: [ Selection, selectionIterator ]
})();

module.exports = selection;
PK
!<@7?modules/commonjs/sdk/self.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "stable"
};

const { CC } = require('chrome');
const options = require('@loader/options');

const { get } = require("./preferences/service");
lazyRequire(this, './net/url', "readURISync");

const id = options.id;

const readPref = key => get("extensions." + id + ".sdk." + key);

const name = readPref("name") || options.name;
const version = readPref("version") || options.version;
const loadReason = readPref("load.reason") || options.loadReason;
const rootURI = readPref("rootURI") || options.rootURI || "";
const baseURI = readPref("baseURI") || options.prefixURI + name + "/"
const addonDataURI = baseURI + "data/";
const metadata = options.metadata || {};
const permissions = metadata.permissions || {};
const isPacked = rootURI && rootURI.indexOf("jar:") === 0;

const isPrivateBrowsingSupported = 'private-browsing' in permissions &&
                                   permissions['private-browsing'] === true;

const uri = (path="") =>
  path.includes(":") ? path : addonDataURI + path.replace(/^\.\//, "");

var preferencesBranch = ("preferences-branch" in metadata)
                            ? metadata["preferences-branch"]
                            : options.preferencesBranch

if (/[^\w{@}.-]/.test(preferencesBranch)) {
  preferencesBranch = id;
  console.warn("Ignoring preferences-branch (not a valid branch name)");
}

// Some XPCOM APIs require valid URIs as an argument for certain operations
// (see `nsILoginManager` for example). This property represents add-on
// associated unique URI string that can be used for that.
exports.uri = 'addon:' + id;
exports.id = id;
exports.preferencesBranch = preferencesBranch || id;
exports.name = name;
exports.loadReason = loadReason;
exports.version = version;
exports.packed = isPacked;
exports.data = Object.freeze({
  url: uri,
  load: function read(path) {
    return readURISync(uri(path));
  }
});
exports.isPrivateBrowsingSupported = isPrivateBrowsingSupported;
PK
!<Y+$modules/commonjs/sdk/simple-prefs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "experimental"
};

const { emit, off } = require("./event/core");
const { PrefsTarget } = require("./preferences/event-target");
const { preferencesBranch, id } = require("./self");
const { on } = require("./system/events");

const ADDON_BRANCH = "extensions." + preferencesBranch + ".";
const BUTTON_PRESSED = id + "-cmdPressed";

const target = PrefsTarget({ branchName: ADDON_BRANCH });

// Listen to clicks on buttons
function buttonClick({ data }) {
  emit(target, data);
}
on(BUTTON_PRESSED, buttonClick);

module.exports = target;
PK
!<$$&modules/commonjs/sdk/simple-storage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "stable"
};

const { Cc, Ci } = require("chrome");
const jpSelf = require("./self");
lazyRequireModule(this, "./timers", "timer");
lazyRequireModule(this, "./io/file", "file");
lazyRequireModule(this, "./preferences/service", "prefs");
const unload = require("./system/unload");
lazyRequire(this, "./event/core", "emit", "on", "off");

const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes

const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
const QUOTA_DEFAULT = 5242880; // 5 MiB

const JETPACK_DIR_BASENAME = "jetpack";

Object.defineProperties(exports, {
  storage: {
    enumerable: true,
    get: function() { return manager.root; },
    set: function(value) { manager.root = value; }
  },
  quotaUsage: {
    get: function() { return manager.quotaUsage; }
  }
});

// A generic JSON store backed by a file on disk.  This should be isolated
// enough to move to its own module if need be...
function JsonStore(options) {
  this.filename = options.filename;
  this.quota = options.quota;
  this.writePeriod = options.writePeriod;
  this.onOverQuota = options.onOverQuota;
  this.onWrite = options.onWrite;

  unload.ensure(this);

  this.writeTimer = timer.setInterval(this.write.bind(this),
                                      this.writePeriod);
}

JsonStore.prototype = {
  // The store's root.
  get root() {
    return this.isRootInited ? this._root : {};
  },

  // Performs some type checking.
  set root(val) {
    let types = ["array", "boolean", "null", "number", "object", "string"];
    if (types.indexOf(typeof(val)) < 0) {
      throw new Error("storage must be one of the following types: " +
                      types.join(", "));
    }
    this._root = val;
    return val;
  },

  // True if the root has ever been set (either via the root setter or by the
  // backing file's having been read).
  get isRootInited() {
    return this._root !== undefined;
  },

  // Percentage of quota used, as a number [0, Inf).  > 1 implies over quota.
  // Undefined if there is no quota.
  get quotaUsage() {
    return this.quota > 0 ?
           JSON.stringify(this.root).length / this.quota :
           undefined;
  },

  // Removes the backing file and all empty subdirectories.
  purge: function JsonStore_purge() {
    try {
      // This'll throw if the file doesn't exist.
      file.remove(this.filename);
      let parentPath = this.filename;
      do {
        parentPath = file.dirname(parentPath);
        // This'll throw if the dir isn't empty.
        file.rmdir(parentPath);
      } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
    }
    catch (err) {}
  },

  // Initializes the root by reading the backing file.
  read: function JsonStore_read() {
    try {
      let str = file.read(this.filename);

      // Ideally we'd log the parse error with console.error(), but logged
      // errors cause tests to fail.  Supporting "known" errors in the test
      // harness appears to be non-trivial.  Maybe later.
      this.root = JSON.parse(str);
    }
    catch (err) {
      this.root = {};
    }
  },

  // If the store is under quota, writes the root to the backing file.
  // Otherwise quota observers are notified and nothing is written.
  write: function JsonStore_write() {
    if (this.quotaUsage > 1)
      this.onOverQuota(this);
    else
      this._write();
  },

  // Cleans up on unload.  If unloading because of uninstall, the store is
  // purged; otherwise it's written.
  unload: function JsonStore_unload(reason) {
    timer.clearInterval(this.writeTimer);
    this.writeTimer = null;

    if (reason === "uninstall")
      this.purge();
    else
      this._write();
  },

  // True if the root is an empty object.
  get _isEmpty() {
    if (this.root && typeof(this.root) === "object") {
      let empty = true;
      for (let key in this.root) {
        empty = false;
        break;
      }
      return empty;
    }
    return false;
  },

  // Writes the root to the backing file, notifying write observers when
  // complete.  If the store is over quota or if it's empty and the store has
  // never been written, nothing is written and write observers aren't notified.
  _write: function JsonStore__write() {
    // Don't write if the root is uninitialized or if the store is empty and the
    // backing file doesn't yet exist.
    if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
      return;

    // If the store is over quota, don't write.  The current under-quota state
    // should persist.
    if (this.quotaUsage > 1)
      return;

    // Finally, write.
    let stream = file.open(this.filename, "w");
    try {
      stream.writeAsync(JSON.stringify(this.root), err => {
        if (err)
          console.error("Error writing simple storage file: " + this.filename);
        else if (this.onWrite)
          this.onWrite(this);
      });
    }
    catch (err) {
      // writeAsync closes the stream after it's done, so only close on error.
      stream.close();
    }
  }
};


// This manages a JsonStore singleton and tailors its use to simple storage.
// The root of the JsonStore is lazy-loaded:  The backing file is only read the
// first time the root's gotten.
var manager = ({
  jsonStore: null,

  // The filename of the store, based on the profile dir and extension ID.
  get filename() {
    let storeFile = Cc["@mozilla.org/file/directory_service;1"].
                    getService(Ci.nsIProperties).
                    get("ProfD", Ci.nsIFile);
    storeFile.append(JETPACK_DIR_BASENAME);
    storeFile.append(jpSelf.id);
    storeFile.append("simple-storage");
    file.mkpath(storeFile.path);
    storeFile.append("store.json");
    return storeFile.path;
  },

  get quotaUsage() {
    return this.jsonStore.quotaUsage;
  },

  get root() {
    if (!this.jsonStore.isRootInited)
      this.jsonStore.read();
    return this.jsonStore.root;
  },

  set root(val) {
    return this.jsonStore.root = val;
  },

  unload: function manager_unload() {
    off(this);
  },

  new: function manager_constructor() {
    let manager = Object.create(this);
    unload.ensure(manager);

    manager.jsonStore = new JsonStore({
      filename: manager.filename,
      writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
      quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
      onOverQuota: emit.bind(null, exports, "OverQuota")
    });

    return manager;
  }
}).new();

exports.on = on.bind(null, exports);
exports.removeListener = function(type, listener) {
  off(exports, type, listener);
};
PK
!<U(modules/commonjs/sdk/stylesheet/style.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Class } = require("../core/heritage");
lazyRequire(this, "../url", "isLocalURL");
lazyRequire(this, "./utils", "loadSheet", "removeSheet", "isTypeValid");
lazyRequire(this, "../lang/type", "isString");
const { attachTo, detachFrom } = require("../content/mod");
lazyRequire(this, '../self', "data");

const { freeze, create } = Object;

function Style({ source, uri, type }) {
  source = source == null ? null : freeze([].concat(source));
  uri = uri == null ? null : freeze([].concat(uri));
  type = type == null ? "author" : type;

  if (source && !source.every(isString))
    throw new Error('Style.source must be a string or an array of strings.');

  if (uri && !uri.every(isLocalURL))
    throw new Error('Style.uri must be a local URL or an array of local URLs');

  if (type && !isTypeValid(type))
    throw new Error('Style.type must be "agent", "user" or "author"');

  return freeze(create(Style.prototype, {
    "source": { value: source, enumerable: true },
    "uri": { value: uri, enumerable: true },
    "type": { value: type, enumerable: true }
  }));
};

exports.Style = Style;

attachTo.define(Style, function (style, window) {
  if (style.uri) {
    for (let uri of style.uri)
      loadSheet(window, data.url(uri), style.type);
  }

  if (style.source) {
    let uri = "data:text/css;charset=utf-8,";

    uri += encodeURIComponent(style.source.join(""));

    loadSheet(window, uri, style.type);
  }
});

detachFrom.define(Style, function (style, window) {
  if (style.uri)
    for (let uri of style.uri)
      removeSheet(window, data.url(uri));

  if (style.source) {
    let uri = "data:text/css;charset=utf-8,";

    uri += encodeURIComponent(style.source.join(""));

    removeSheet(window, uri, style.type);
  }
});
PK
!<[[(modules/commonjs/sdk/stylesheet/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata =  {
  "stability": "experimental"
};

const { Ci } = require("chrome");

const SHEET_TYPE = {
  "agent": "AGENT_SHEET",
  "user": "USER_SHEET",
  "author": "AUTHOR_SHEET"
};

function getDOMWindowUtils(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
                getInterface(Ci.nsIDOMWindowUtils);
};

/**
 * Synchronously loads a style sheet from `uri` and adds it to the list of
 * additional style sheets of the document.
 * The sheets added takes effect immediately, and only on the document of the
 * `window` given.
 */
function loadSheet(window, url, type) {
  if (!(type && type in SHEET_TYPE))
    type = "author";

  type = SHEET_TYPE[type];

  if (url instanceof Ci.nsIURI)
    url = url.spec;

  let winUtils = getDOMWindowUtils(window);
  try {
    winUtils.loadSheetUsingURIString(url, winUtils[type]);
  }
  catch (e) {};
};
exports.loadSheet = loadSheet;

/**
 * Remove the document style sheet at `sheetURI` from the list of additional
 * style sheets of the document.  The removal takes effect immediately.
 */
function removeSheet(window, url, type) {
  if (!(type && type in SHEET_TYPE))
    type = "author";

  type = SHEET_TYPE[type];

  if (url instanceof Ci.nsIURI)
    url = url.spec;

  let winUtils = getDOMWindowUtils(window);

  try {
    winUtils.removeSheetUsingURIString(url, winUtils[type]);
  }
  catch (e) {};
};
exports.removeSheet = removeSheet;

/**
 * Returns `true` if the `type` given is valid, otherwise `false`.
 * The values currently accepted are: "agent", "user" and "author".
 */
function isTypeValid(type) {
  return type in SHEET_TYPE;
}
exports.isTypeValid = isTypeValid;
PK
!<!N7modules/commonjs/sdk/system/child_process/subprocess.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const { Ci, Cu } = require("chrome");

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Subprocess.jsm");

const Runtime = require("sdk/system/runtime");
const Environment = require("sdk/system/environment").env;
const DEFAULT_ENVIRONMENT = [];
if (Runtime.OS == "Linux" && "DISPLAY" in Environment) {
  DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY);
}

function awaitPromise(promise) {
  let value;
  let resolved = null;
  promise.then(val => {
    resolved = true;
    value = val;
  }, val => {
    resolved = false;
    value = val;
  });

  Services.tm.spinEventLoopUntil(() => resolved !== null);

  if (resolved === true)
    return value;
  throw value;
}

let readAllData = async function(pipe, read, callback) {
  let string;
  while (string = await read(pipe))
    callback(string);
};

let write = (pipe, data) => {
  let buffer = new Uint8Array(Array.from(data, c => c.charCodeAt(0)));
  return pipe.write(data);
};

var subprocess = {
  call: function(options) {
    var result;

    let procPromise = (async function() {
      let opts = {};

      if (options.mergeStderr) {
        opts.stderr = "stdout"
      } else if (options.stderr) {
        opts.stderr = "pipe";
      }

      if (options.command instanceof Ci.nsIFile) {
        opts.command = options.command.path;
      } else {
        opts.command = await Subprocess.pathSearch(options.command);
      }

      if (options.workdir) {
        opts.workdir = options.workdir;
      }

      opts.arguments = options.arguments || [];


      // Set up environment

      let envVars = options.environment || DEFAULT_ENVIRONMENT;
      if (envVars.length) {
        let environment = {};
        for (let val of envVars) {
          let idx = val.indexOf("=");
          if (idx >= 0)
            environment[val.slice(0, idx)] = val.slice(idx + 1);
        }

        opts.environment = environment;
      }


      let proc = await Subprocess.call(opts);

      Object.defineProperty(result, "pid", {
        value: proc.pid,
        enumerable: true,
        configurable: true,
      });


      let promises = [];

      // Set up IO handlers.

      let read = pipe => pipe.readString();
      if (options.charset === null) {
        read = pipe => {
          return pipe.read().then(buffer => {
            return String.fromCharCode(...buffer);
          });
        };
      }

      if (options.stdout)
        promises.push(readAllData(proc.stdout, read, options.stdout));

      if (options.stderr && proc.stderr)
        promises.push(readAllData(proc.stderr, read, options.stderr));

      // Process stdin

      if (typeof options.stdin === "string") {
        write(proc.stdin, options.stdin);
        proc.stdin.close();
      }


      // Handle process completion

      if (options.done)
        Promise.all(promises)
          .then(() => proc.wait())
          .then(options.done);

      return proc;
    })();

    procPromise.catch(e => {
      if (options.done)
        options.done({exitCode: -1}, e);
      else
        Cu.reportError(e instanceof Error ? e : e.message || e);
    });

    if (typeof options.stdin === "function") {
      // Unfortunately, some callers (child_process.js) depend on this
      // being called synchronously.
      options.stdin({
        write(val) {
          procPromise.then(proc => {
            write(proc.stdin, val);
          });
        },

        close() {
          procPromise.then(proc => {
            proc.stdin.close();
          });
        },
      });
    }

    result = {
      get pid() {
        return awaitPromise(procPromise.then(proc => {
          return proc.pid;
        }));
      },

      wait() {
        return awaitPromise(procPromise.then(proc => {
          return proc.wait().then(({exitCode}) => exitCode);
        }));
      },

      kill(hard = false) {
        procPromise.then(proc => {
          proc.kill(hard ? 0 : undefined);
        });
      },
    };

    return result;
  },
};

module.exports = subprocess;
PK
!<t&##,modules/commonjs/sdk/system/child_process.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental'
};

var { Ci } = require('chrome');
var subprocess = require('./child_process/subprocess');
var { EventTarget } = require('../event/target');
var { Stream } = require('../io/stream');
var { on, emit, off } = require('../event/core');
var { Class } = require('../core/heritage');
var { platform } = require('../system');
var { isFunction, isArray } = require('../lang/type');
var { delay } = require('../lang/functional');
var { merge } = require('../util/object');
var { setTimeout, clearTimeout } = require('../timers');
var isWindows = platform.indexOf('win') === 0;

var processes = new WeakMap();


/**
 * The `Child` class wraps a subprocess command, exposes
 * the stdio streams, and methods to manipulate the subprocess
 */
var Child = Class({
  implements: [EventTarget],
  initialize: function initialize (options) {
    let child = this;
    let proc;

    this.killed = false;
    this.exitCode = undefined;
    this.signalCode = undefined;

    this.stdin = Stream();
    this.stdout = Stream();
    this.stderr = Stream();

    try {
      proc = subprocess.call({
        command: options.file,
        arguments: options.cmdArgs,
        environment: serializeEnv(options.env),
        workdir: options.cwd,
        charset: options.encoding,
        stdout: data => emit(child.stdout, 'data', data),
        stderr: data => emit(child.stderr, 'data', data),
        stdin: stream => {
          child.stdin.on('data', pumpStdin);
          child.stdin.on('end', function closeStdin () {
            child.stdin.off('data', pumpStdin);
            child.stdin.off('end', closeStdin);
            stream.close();
          });
          function pumpStdin (data) {
            stream.write(data);
          }
        },
        done: function (result, error) {
          if (error)
            return handleError(error);

          // Only emit if child is not killed; otherwise,
          // the `kill` method will handle this
          if (!child.killed) {
            child.exitCode = result.exitCode;
            child.signalCode = null;

            // If process exits with < 0, there was an error
            if (child.exitCode < 0) {
              handleError(new Error('Process exited with exit code ' + child.exitCode));
            }
            else {
              // Also do 'exit' event as there's not much of
              // a difference in our implementation as we're not using
              // node streams
              emit(child, 'exit', child.exitCode, child.signalCode);
            }

            // Emit 'close' event with exit code and signal,
            // which is `null`, as it was not a killed process
            emit(child, 'close', child.exitCode, child.signalCode);
          }
        }
      });
      processes.set(child, proc);
    } catch (e) {
      // Delay the error handling so an error handler can be set
      // during the same tick that the Child was created
      delay(() => handleError(e));
    }

    // `handleError` is called when process could not even
    // be spawned
    function handleError (e) {
      // If error is an nsIObject, make a fresh error object
      // so we're not exposing nsIObjects, and we can modify it
      // with additional process information, like node
      let error = e;
      if (e instanceof Ci.nsISupports) {
        error = new Error(e.message, e.filename, e.lineNumber);
      }
      emit(child, 'error', error);
      child.exitCode = -1;
      child.signalCode = null;
      emit(child, 'close', child.exitCode, child.signalCode);
    }
  },
  kill: function kill (signal) {
    let proc = processes.get(this);
    proc.kill(signal);
    this.killed = true;
    this.exitCode = null;
    this.signalCode = signal;
    emit(this, 'exit', this.exitCode, this.signalCode);
    emit(this, 'close', this.exitCode, this.signalCode);
  },
  get pid() { return processes.get(this, {}).pid || -1; }
});

function spawn (file, ...args) {
  let cmdArgs = [];
  // Default options
  let options = {
    cwd: null,
    env: null,
    encoding: 'UTF-8'
  };

  if (args[1]) {
    merge(options, args[1]);
    cmdArgs = args[0];
  }
  else {
    if (isArray(args[0]))
      cmdArgs = args[0];
    else
      merge(options, args[0]);
  }

  if ('gid' in options)
    console.warn('`gid` option is not yet supported for `child_process`');
  if ('uid' in options)
    console.warn('`uid` option is not yet supported for `child_process`');
  if ('detached' in options)
    console.warn('`detached` option is not yet supported for `child_process`');

  options.file = file;
  options.cmdArgs = cmdArgs;

  return Child(options);
}

exports.spawn = spawn;

/**
 * exec(command, options, callback)
 */
function exec (cmd, ...args) {
  let file, cmdArgs, callback, options = {};

  if (isFunction(args[0]))
    callback = args[0];
  else {
    merge(options, args[0]);
    callback = args[1];
  }

  if (isWindows) {
    file = 'C:\\Windows\\System32\\cmd.exe';
    cmdArgs = ['/S/C', cmd || ''];
  }
  else {
    file = '/bin/sh';
    cmdArgs = ['-c', cmd];
  }

  // Undocumented option from node being able to specify shell
  if (options && options.shell)
    file = options.shell;

  return execFile(file, cmdArgs, options, callback);
}
exports.exec = exec;
/**
 * execFile (file, args, options, callback)
 */
function execFile (file, ...args) {
  let cmdArgs = [], callback;
  // Default options
  let options = {
    cwd: null,
    env: null,
    encoding: 'utf8',
    timeout: 0,
    maxBuffer: 204800,    //200 KB (200*1024 bytes)
    killSignal: 'SIGTERM'
  };

  if (isFunction(args[args.length - 1]))
    callback = args[args.length - 1];

  if (isArray(args[0])) {
    cmdArgs = args[0];
    merge(options, args[1]);
  } else if (!isFunction(args[0]))
    merge(options, args[0]);

  let child = spawn(file, cmdArgs, options);
  let exited = false;
  let stdout = '';
  let stderr = '';
  let error = null;
  let timeoutId = null;

  child.stdout.setEncoding(options.encoding);
  child.stderr.setEncoding(options.encoding);

  on(child.stdout, 'data', pumpStdout);
  on(child.stderr, 'data', pumpStderr);
  on(child, 'close', exitHandler);
  on(child, 'error', errorHandler);

  if (options.timeout > 0) {
    setTimeout(() => {
      kill();
      timeoutId = null;
    }, options.timeout);
  }

  function exitHandler (code, signal) {

    // Return if exitHandler called previously, occurs
    // when multiple maxBuffer errors thrown and attempt to kill multiple
    // times
    if (exited) return;
    exited = true;

    if (!isFunction(callback)) return;

    if (timeoutId) {
      clearTimeout(timeoutId);
      timeoutId = null;
    }

    if (!error && (code !== 0 || signal !== null))
      error = createProcessError(new Error('Command failed: ' + stderr), {
        code: code,
        signal: signal,
        killed: !!child.killed
      });

    callback(error, stdout, stderr);

    off(child.stdout, 'data', pumpStdout);
    off(child.stderr, 'data', pumpStderr);
    off(child, 'close', exitHandler);
    off(child, 'error', errorHandler);
  }

  function errorHandler (e) {
    error = e;
    exitHandler();
  }

  function kill () {
    try {
      child.kill(options.killSignal);
    } catch (e) {
      // In the scenario where the kill signal happens when
      // the process is already closing, just abort the kill fail
      if (/library is not open/.test(e))
        return;
      error = e;
      exitHandler(-1, options.killSignal);
    }
  }

  function pumpStdout (data) {
    stdout += data;
    if (stdout.length > options.maxBuffer) {
      error = new Error('stdout maxBuffer exceeded');
      kill();
    }
  }

  function pumpStderr (data) {
    stderr += data;
    if (stderr.length > options.maxBuffer) {
      error = new Error('stderr maxBuffer exceeded');
      kill();
    }
  }

  return child;
}
exports.execFile = execFile;

exports.fork = function fork () {
  throw new Error("child_process#fork is not currently supported");
};

function serializeEnv (obj) {
  return Object.keys(obj || {}).map(prop => prop + '=' + obj[prop]);
}

function createProcessError (err, options = {}) {
  // If code and signal look OK, this was probably a failure
  // attempting to spawn the process (like ENOENT in node) -- use
  // the code from the error message
  if (!options.code && !options.signal) {
    let match = err.message.match(/(NS_ERROR_\w*)/);
    if (match && match.length > 1)
      err.code = match[1];
    else {
      // If no good error message found, use the passed in exit code;
      // this occurs when killing a process that's already closing,
      // where we want both a valid exit code (0) and the error
      err.code = options.code != null ? options.code : null;
    }
  }
  else
    err.code = options.code != null ? options.code : null;
  err.signal = options.signal || null;
  err.killed = options.killed || false;
  return err;
}
PK
!<"*modules/commonjs/sdk/system/environment.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "stable"
};

const { Cc, Ci } = require('chrome');
const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
                             getService(Ci.nsIEnvironment);

exports.env = new Proxy({}, {
  deleteProperty(target, property) {
    set(property, null);
    return true;
  },

  get(target, property, receiver) {
    return get(property) || undefined;
  },

  has(target, property) {
    return exists(property);
  },

  set(target, property, value, receiver) {
    set(property, value);
    return true;
  }
});
PK
!<'7PSS-modules/commonjs/sdk/system/events-shimmed.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'unstable'
};

const events = require('./events.js');

exports.emit = (type, event) => events.emit(type, event, true);
exports.on = (type, listener, strong) => events.on(type, listener, strong, true);
exports.once = (type, listener) => events.once(type, listener, true);
exports.off = (type, listener) => events.off(type, listener, true);
PK
!<!FS3!!%modules/commonjs/sdk/system/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'unstable'
};

const { Cc, Ci, Cu } = require('chrome');
const { Unknown } = require('../platform/xpcom');
const { Class } = require('../core/heritage');
const { ns } = require('../core/namespace');
const observerService =
  Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const { addObserver, removeObserver, notifyObservers } = observerService;
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
const addObserverNoShim = ShimWaiver.getProperty(observerService, "addObserver");
const removeObserverNoShim = ShimWaiver.getProperty(observerService, "removeObserver");
const notifyObserversNoShim = ShimWaiver.getProperty(observerService, "notifyObservers");
const unloadSubject = require('@loader/unload');

const Subject = Class({
  extends: Unknown,
  initialize: function initialize(object) {
    // Double-wrap the object and set a property identifying the
    // wrappedJSObject as one of our wrappers to distinguish between
    // subjects that are one of our wrappers (which we should unwrap
    // when notifying our observers) and those that are real JS XPCOM
    // components (which we should pass through unaltered).
    this.wrappedJSObject = {
      observersModuleSubjectWrapper: true,
      object: object
    };
  },
  getScriptableHelper: function() {},
  getInterfaces: function() {}
});

function emit(type, event, shimmed = false) {
  // From bug 910599
  // We must test to see if 'subject' or 'data' is a defined property
  // of the event object, but also allow primitives to be passed in,
  // which the `in` operator breaks, yet `null` is an object, hence
  // the long conditional
  let subject = event && typeof event === 'object' && 'subject' in event ?
    Subject(event.subject) :
    null;
  let data = event && typeof event === 'object' ?
    // An object either returns its `data` property or null
    ('data' in event ? event.data : null) :
    // All other types return themselves (and cast to strings/null
    // via observer service)
    event;
  if (shimmed) {
    notifyObservers(subject, type, data);
  } else {
    notifyObserversNoShim(subject, type, data);
  }
}
exports.emit = emit;

const Observer = Class({
  extends: Unknown,
  initialize: function initialize(listener) {
    this.listener = listener;
  },
  interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ],
  observe: function(subject, topic, data) {
    // Extract the wrapped object for subjects that are one of our
    // wrappers around a JS object.  This way we support both wrapped
    // subjects created using this module and those that are real
    // XPCOM components.
    if (subject && typeof(subject) == 'object' &&
        ('wrappedJSObject' in subject) &&
        ('observersModuleSubjectWrapper' in subject.wrappedJSObject))
      subject = subject.wrappedJSObject.object;

    try {
      this.listener({
        type: topic,
        subject: subject,
        data: data
      });
    }
    catch (error) {
      console.exception(error);
    }
  }
});

const subscribers = ns();

function on(type, listener, strong, shimmed = false) {
  // Unless last optional argument is `true` we use a weak reference to a
  // listener.
  let weak = !strong;
  // Take list of observers associated with given `listener` function.
  let observers = subscribers(listener);
  // If `observer` for the given `type` is not registered yet, then
  // associate an `observer` and register it.
  if (!(type in observers)) {
    let observer = Observer(listener);
    observers[type] = observer;
    if (shimmed) {
      addObserver(observer, type, weak);
    } else {
      addObserverNoShim(observer, type, weak);
    }
    // WeakRef gymnastics to remove all alive observers on unload
    let ref = Cu.getWeakReference(observer);
    weakRefs.set(observer, ref);
    stillAlive.set(ref, type);
    wasShimmed.set(ref, shimmed);
  }
}
exports.on = on;

function once(type, listener, shimmed = false) {
  // Note: this code assumes order in which listeners are called, which is fine
  // as long as dispatch happens in same order as listener registration which
  // is the case now. That being said we should be aware that this may break
  // in a future if order will change.
  on(type, listener, shimmed);
  on(type, function cleanup() {
    off(type, listener, shimmed);
    off(type, cleanup, shimmed);
  }, true, shimmed);
}
exports.once = once;

function off(type, listener, shimmed = false) {
  // Take list of observers as with the given `listener`.
  let observers = subscribers(listener);
  // If `observer` for the given `type` is registered, then
  // remove it & unregister.
  if (type in observers) {
    let observer = observers[type];
    delete observers[type];
    if (shimmed) {
      removeObserver(observer, type);
    } else {
      removeObserverNoShim(observer, type);
    }
    stillAlive.delete(weakRefs.get(observer));
    wasShimmed.delete(weakRefs.get(observer));
  }
}
exports.off = off;

// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115
var weakRefs = new WeakMap();

// and we're out of beta, we're releasing on time!
var stillAlive = new Map();

var wasShimmed = new Map();

on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
  // using logic from ./unload, to avoid a circular module reference
  if (subject.wrappedJSObject === unloadSubject) {
    off('sdk:loader:destroy', onunload, false);

    // don't bother
    if (reason === 'shutdown')
      return;

    // Wait until the next tick to let other shutdown handlers finish first.
    Promise.resolve().then(() => {
      stillAlive.forEach((type, ref) => {
        let observer = ref.get();
        if (observer) {
          if (wasShimmed.get(ref)) {
            removeObserver(observer, type);
          } else {
            removeObserverNoShim(observer, type);
          }
        }
      });
    });
  }
}, true, false);
PK
!<ZSS&modules/commonjs/sdk/system/globals.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

defineLazyGetter(this, "console", () => new (require('../console/plain-text').PlainTextConsole)());

exports = module.exports = {};

defineLazyGetter(exports, "console", () => console);

// Provide CommonJS `define` to allow authoring modules in a format that can be
// loaded both into jetpack and into browser via AMD loaders.

// `define` is provided as a lazy getter that binds below defined `define`
// function to the module scope, so that require, exports and module
// variables remain accessible.
defineLazyGetter(exports, 'define', function() {
  return (...args) => {
    let factory = args.pop();
    factory.call(this, this.require, this.exports, this.module);
  };
});

function defineLazyGetter(object, prop, getter) {
  let redefine = (obj, value) => {
    Object.defineProperty(obj, prop, {
      configurable: true,
      writable: true,
      value,
    });
    return value;
  };

  Object.defineProperty(object, prop, {
    configurable: true,
    get() {
      return redefine(this, getter.call(this));
    },
    set(value) {
      redefine(this, value);
    }
  });
}
PK
!<\-&modules/commonjs/sdk/system/process.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const {
  exit, version, stdout, stderr, platform, architecture
} = require("../system");

/**
 * Supported
 */

exports.stdout = stdout;
exports.stderr = stderr;
exports.version = version;
exports.versions = {};
exports.config = {};
exports.arch = architecture;
exports.platform = platform;
exports.exit = exit;

/**
 * Partial support
 */

// An alias to `setTimeout(fn, 0)`, which isn't the same as node's `nextTick`,
// but atleast ensures it'll occur asynchronously
exports.nextTick = (callback) => setTimeout(callback, 0);

/**
 * Unsupported
 */

exports.maxTickDepth = 1000;
exports.pid = 0;
exports.title = "";
exports.stdin = {};
exports.argv = [];
exports.execPath = "";
exports.execArgv = [];
exports.abort = function () {};
exports.chdir = function () {};
exports.cwd = function () {};
exports.env = {};
exports.getgid = function () {};
exports.setgid = function () {};
exports.getuid = function () {};
exports.setuid = function () {};
exports.getgroups = function () {};
exports.setgroups = function () {};
exports.initgroups = function () {};
exports.kill = function () {};
exports.memoryUsage = function () {};
exports.umask = function () {};
exports.uptime = function () {};
exports.hrtime = function () {};
PK
!<ii&modules/commonjs/sdk/system/runtime.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci } = require("chrome");
const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);

exports.inSafeMode = runtime.inSafeMode;
exports.OS = runtime.OS;
exports.processType = runtime.processType;
exports.widgetToolkit = runtime.widgetToolkit;
exports.processID = runtime.processID;

// Attempt to access `XPCOMABI` may throw exception, in which case exported
// `XPCOMABI` will be set to `null`.
// https://mxr.mozilla.org/mozilla-central/source/toolkit/xre/nsAppRunner.cpp#732
try {
  exports.XPCOMABI = runtime.XPCOMABI;
}
catch (error) {
  exports.XPCOMABI = null;
}
PK
!<T>

%modules/commonjs/sdk/system/unload.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Parts of this module were taken from narwhal:
//
// http://narwhaljs.org

module.metadata = {
  "stability": "experimental"
};

const { Cu } = require('chrome');
const { on, off } = require('./events');
const unloadSubject = require('@loader/unload');

const observers = [];
const unloaders = [];

function WeakObserver(inner) {
  this._inner = Cu.getWeakReference(inner);
}

Object.defineProperty(WeakObserver.prototype, 'value', {
  get: function() { this._inner.get() }
});

var when = exports.when = function when(observer, opts) {
  opts = opts || {};
  for (var i = 0; i < observers.length; ++i) {
    if (observers[i] === observer || observers[i].value === observer) {
      return;
    }
  }
  if (opts.weak) {
    observers.unshift(new WeakObserver(observer));
  } else {
    observers.unshift(observer);
  }
};

var ensure = exports.ensure = function ensure(obj, destructorName) {
  if (!destructorName)
    destructorName = "unload";
  if (!(destructorName in obj))
    throw new Error("object has no '" + destructorName + "' property");

  let called = false;
  let originalDestructor = obj[destructorName];

  function unloadWrapper(reason) {
    if (!called) {
      called = true;
      let index = unloaders.indexOf(unloadWrapper);
      if (index == -1)
        throw new Error("internal error: unloader not found");
      unloaders.splice(index, 1);
      originalDestructor.call(obj, reason);
      originalDestructor = null;
      destructorName = null;
      obj = null;
    }
  };

  // TODO: Find out why the order is inverted here. It seems that
  // it may be causing issues!
  unloaders.push(unloadWrapper);

  obj[destructorName] = unloadWrapper;
};

function unload(reason) {
  observers.forEach(function(observer) {
    try {
      if (observer instanceof WeakObserver) {
        observer = observer.value;
      }
      if (typeof observer === 'function') {
        observer(reason);
      }
    }
    catch (error) {
      console.exception(error);
    }
  });
}

when(function(reason) {
  unloaders.slice().forEach(function(unloadWrapper) {
    unloadWrapper(reason);
  });
});

on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
  // If this loader is unload then `subject.wrappedJSObject` will be
  // `destructor`.
  if (subject.wrappedJSObject === unloadSubject) {
    off('sdk:loader:destroy', onunload);
    unload(reason);
  }
// Note that we use strong reference to listener here to make sure it's not
// GC-ed, which may happen otherwise since nothing keeps reference to `onunolad`
// function.
}, true);
PK
!<zz&modules/commonjs/sdk/system/xul-app.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { XulApp } = require("./xul-app.jsm");

Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
PK
!<$kk'modules/commonjs/sdk/system/xul-app.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

this.EXPORTED_SYMBOLS = [ "XulApp" ];

var { classes: Cc, interfaces: Ci } = Components;

var exports = {};
this.XulApp = exports;

var appInfo;

// NOTE: below is required to avoid failing xpcshell tests,
//       which do not implement nsIXULAppInfo
// See Bug 1114752 https://bugzilla.mozilla.org/show_bug.cgi?id=1114752
try {
 appInfo = Cc["@mozilla.org/xre/app-info;1"]
              .getService(Ci.nsIXULAppInfo);
}
catch (e) {
  // xpcshell test case
  appInfo = {};
}
var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
         .getService(Ci.nsIVersionComparator);

var ID = exports.ID = appInfo.ID;
var name = exports.name = appInfo.name;
var version = exports.version = appInfo.version;
var platformVersion = exports.platformVersion = appInfo.platformVersion;

// The following mapping of application names to GUIDs was taken from:
//
//   https://addons.mozilla.org/en-US/firefox/pages/appversions
//
// Using the GUID instead of the app's name is preferable because sometimes
// re-branded versions of a product have different names: for instance,
// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
// GUID.

var ids = exports.ids = {
  Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
  Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
  SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
  Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
  Instantbird: "{33cb9019-c295-46dd-be21-8c4936574bee}"
};

function is(name) {
  if (!(name in ids))
    throw new Error("Unkown Mozilla Application: " + name);
  return ID == ids[name];
};
exports.is = is;

function isOneOf(names) {
  for (var i = 0; i < names.length; i++)
    if (is(names[i]))
      return true;
  return false;
};
exports.isOneOf = isOneOf;

/**
 * Use this to check whether the given version (e.g. xulApp.platformVersion)
 * is in the given range. Versions must be in version comparator-compatible
 * format. See MDC for details:
 * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
 */
var versionInRange = exports.versionInRange =
function versionInRange(version, lowInclusive, highExclusive) {
  return (vc.compare(version, lowInclusive) >= 0) &&
         (vc.compare(version, highExclusive) < 0);
}

const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
const reSubInfinity = /\.[*x]/g;
const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
const reRangeSeparator = /\s*\|\|\s*/;

const compares = {
  "=": function (c) { return c === 0 },
  ">=": function (c) { return c >= 0 },
  "<=": function (c) { return c <= 0},
  "<": function (c) { return c < 0 },
  ">": function (c) { return c > 0 }
}

function normalizeRange(range) {
    return range
        .replace(reOnlyInifinity, "")
        .replace(reSubInfinity, ".*")
        .replace(reHyphenRange, ">=$1 <=$2")
}

/**
 * Compare the versions given, using the comparison operator provided.
 * Internal use only.
 *
 * @example
 *  compareVersion("1.2", "<=", "1.*") // true
 *
 * @param {String} version
 *  A version to compare
 *
 * @param {String} comparison
 *  The comparison operator
 *
 * @param {String} compareVersion
 *  A version to compare
 */
function compareVersion(version, comparison, compareVersion) {
  let hasWildcard = compareVersion.indexOf("*") !== -1;

  comparison = comparison || "=";

  if (hasWildcard) {
    switch (comparison) {
      case "=":
        let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
        return versionInRange(version, zeroVersion, compareVersion);
      case ">=":
        compareVersion = compareVersion.replace(reSubInfinity, ".0");
        break;
    }
  }

  let compare = compares[comparison];

  return typeof compare === "function" && compare(vc.compare(version, compareVersion));
}

/**
 * Returns `true` if `version` satisfies the `versionRange` given.
 * If only an argument is passed, is used as `versionRange` and compared against
 * `xulApp.platformVersion`.
 *
 * `versionRange` is either a string which has one or more space-separated
 * descriptors, or a range like "fromVersion - toVersion".
 * Version range descriptors may be any of the following styles:
 *
 * - "version" Must match `version` exactly
 * - "=version" Same as just `version`
 * - ">version" Must be greater than `version`
 * - ">=version" Must be greater or equal than `version`
 * - "<version" Must be less than `version`
 * - "<=version" Must be less or equal than `version`
 * - "1.2.x" or "1.2.*" See 'X version ranges' below
 * - "*" or "" (just an empty string) Matches any version
 * - "version1 - version2" Same as ">=version1 <=version2"
 * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
 *
 * For example, these are all valid:
 * - "1.0.0 - 2.9999.9999"
 * - ">=1.0.2 <2.1.2"
 * - ">1.0.2 <=2.3.4"
 * - "2.0.1"
 * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
 * - "2.x" (equivalent to "2.*")
 * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
 */
function satisfiesVersion(version, versionRange) {
  if (arguments.length === 1) {
    versionRange = version;
    version = appInfo.version;
  }

  let ranges = versionRange.trim().split(reRangeSeparator);

  return ranges.some(function(range) {
    range = normalizeRange(range);

    // No versions' range specified means that any version satisfies the
    // requirements.
    if (range === "")
      return true;

    let matches = range.match(reVersionRange);

    if (!matches)
      return false;

    let [, lowMod, lowVer, highMod, highVer] = matches;

    return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
      ? compareVersion(version, highMod, highVer)
      : true);
  });
}
exports.satisfiesVersion = satisfiesVersion;

/**
 * Ensure the current application satisfied the requirements specified in the
 * module given. If not, an exception related to the incompatibility is
 * returned; `null` otherwise.
 *
 * @param {Object} module
 *  The module to check
 * @returns {Error}
 */
function incompatibility(module) {
  let { metadata, id } = module;

  // if metadata or engines are not specified we assume compatibility is not
  // an issue.
  if (!metadata || !("engines" in metadata))
    return null;

  let { engines } = metadata;

  if (engines === null || typeof(engines) !== "object")
    return new Error("Malformed engines' property in metadata");

  let applications = Object.keys(engines);

  let versionRange;
  applications.forEach(function(name) {
    if (is(name)) {
      versionRange = engines[name];
      // Continue iteration. We want to ensure the module doesn't
      // contain a typo in the applications' name or some unknown
      // application - `is` function throws an exception in that case.
    }
  });

  if (typeof(versionRange) === "string") {
    if (satisfiesVersion(versionRange))
      return null;

    return new Error("Unsupported Application version: The module " + id +
            " currently supports only version " + versionRange + " of " +
            name + ".");
  }

  return new Error("Unsupported Application: The module " + id +
            " currently supports only " + applications.join(", ") + ".")
}
exports.incompatibility = incompatibility;
PK
!<&modules/commonjs/sdk/system.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, CC } = require('chrome');
const options = require('@loader/options');
const runtime = require("./system/runtime");
const { when: unload } = require("./system/unload");

const { XPCOMUtils } = require('resource://gre/modules/XPCOMUtils.jsm');

XPCOMUtils.defineLazyServiceGetter(this, 'appStartup',
                                   '@mozilla.org/toolkit/app-startup;1',
                                   'nsIAppStartup');
XPCOMUtils.defineLazyServiceGetter(this, 'directoryService',
                                   '@mozilla.org/file/directory_service;1',
                                   'nsIProperties');

const appInfo = Cc["@mozilla.org/xre/app-info;1"].
                getService(Ci.nsIXULAppInfo);

const PR_WRONLY = parseInt("0x02");
const PR_CREATE_FILE = parseInt("0x08");
const PR_APPEND = parseInt("0x10");
const PR_TRUNCATE = parseInt("0x20");

function openFile(path, mode) {
  let file = Cc["@mozilla.org/file/local;1"].
             createInstance(Ci.nsILocalFile);
  file.initWithPath(path);
  let stream = Cc["@mozilla.org/network/file-output-stream;1"].
               createInstance(Ci.nsIFileOutputStream);
  stream.init(file, mode, -1, 0);
  return stream
}

/**
 * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"`
 */
exports.staticArgs = options.staticArgs;

/**
 * Environment variables. Environment variables are non-enumerable properties
 * of this object (key is name and value is value).
 */
exports.env = require('./system/environment').env;

/**
 * Ends the process with the specified `code`. If omitted, exit uses the
 * 'success' code 0. To exit with failure use `1`.
 * TODO: Improve platform to actually quit with an exit code.
 */
var forcedExit = false;
exports.exit = function exit(code) {
  if (forcedExit) {
    // a forced exit was already tried
    // NOTE: exit(0) is called twice sometimes (ex when using cfx testaddons)
    return;
  }

  let resultsFile = 'resultFile' in options && options.resultFile;
  function unloader() {
    if (!options.resultFile) {
      return;
    }

    // This is used by 'cfx' to find out exit code.
    let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
    let stream = openFile(options.resultFile, mode);
    let status = code ? 'FAIL' : 'OK';
    stream.write(status, status.length);
    stream.flush();
    stream.close();
    return;
  }

  if (code == 0) {
    forcedExit = true;
  }

  // Bug 856999: Prevent automatic kill of Firefox when running tests
  if (options.noQuit) {
    return unload(unloader);
  }

  unloader();
  appStartup.quit(code ? appStartup.eAttemptQuit : appStartup.eForceQuit);
};

// Adapter for nodejs's stdout & stderr:
// http://nodejs.org/api/process.html#process_process_stdout
var stdout = Object.freeze({ write: dump, end: dump });
exports.stdout = stdout;
exports.stderr = stdout;

/**
 * Returns a path of the system's or application's special directory / file
 * associated with a given `id`. For list of possible `id`s please see:
 * https://developer.mozilla.org/en-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories
 * http://dxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
 * @example
 *
 *    // get firefox profile path
 *    let profilePath = require('system').pathFor('ProfD');
 *    // get OS temp files directory (/tmp)
 *    let temps = require('system').pathFor('TmpD');
 *    // get OS desktop path for an active user (~/Desktop on linux
 *    // or C:\Documents and Settings\username\Desktop on windows).
 *    let desktopPath = require('system').pathFor('Desk');
 */
exports.pathFor = function pathFor(id) {
  return directoryService.get(id, Ci.nsIFile).path;
};

/**
 * What platform you're running on (all lower case string).
 * For possible values see:
 * https://developer.mozilla.org/en/OS_TARGET
 */
exports.platform = runtime.OS.toLowerCase();

const [, architecture, compiler] = runtime.XPCOMABI ?
                                   runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
                                   [, null, null];

/**
 * What processor architecture you're running on:
 * `'arm', 'ia32', or 'x64'`.
 */
exports.architecture = architecture;

/**
 * What compiler used for build:
 * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
 */
exports.compiler = compiler;

/**
 * The application's build ID/date, for example "2004051604".
 */
exports.build = appInfo.appBuildID;

/**
 * The XUL application's UUID.
 * This has traditionally been in the form
 * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
 * be: "appname@vendor.tld".
 */
exports.id = appInfo.ID;

/**
 * The name of the application.
 */
exports.name = appInfo.name;

/**
 * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
 */
exports.version = appInfo.version;

/**
 * XULRunner version.
 */
exports.platformVersion = appInfo.platformVersion;


/**
 * The name of the application vendor, for example "Mozilla".
 */
exports.vendor = appInfo.vendor;
PK
!<4p
"modules/commonjs/sdk/tab/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This module provides temporary shim until Bug 843901 is shipped.
// It basically registers tab event listeners on all windows that get
// opened and forwards them through observer notifications.

module.metadata = {
  "stability": "experimental"
};

const { Ci } = require("chrome");
const { windows, isInteractive } = require("../window/utils");
const { events } = require("../browser/events");
const { open } = require("../event/dom");
const { filter, map, merge, expand } = require("../event/utils");
const isFennec = require("sdk/system/xul-app").is("Fennec");

// Module provides event stream (in nodejs style) that emits data events
// for all the tab events that happen in running firefox. At the moment
// it does it by registering listeners on all browser windows and then
// forwarding events when they occur to a stream. This will become obsolete
// once Bug 843901 is fixed, and we'll just leverage observer notifications.

// Set of tab events that this module going to aggregate and expose.
const TYPES = ["TabOpen","TabClose","TabSelect","TabMove","TabPinned",
               "TabUnpinned"];

// Utility function that given a browser `window` returns stream of above
// defined tab events for all tabs on the given window.
function tabEventsFor(window) {
  // Map supported event types to a streams of those events on the given
  // `window` and than merge these streams into single form stream off
  // all events.
  let channels = TYPES.map(type => open(window, type));
  return merge(channels);
}

// Create our event channels.  We do this in a separate function to
// minimize the chance of leaking intermediate objects on the global.
function makeEvents() {
  // Filter DOMContentLoaded events from all the browser events.
  var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
  // Map DOMContentLoaded events to it's target browser windows.
  var futureWindows = map(readyEvents, e => e.target);
  // Expand all browsers that will become interactive to supported tab events
  // on these windows. Result will be a tab events from all tabs of all windows
  // that will become interactive.
  var eventsFromFuture = expand(futureWindows, tabEventsFor);

  // Above covers only windows that will become interactive in a future, but some
  // windows may already be interactive so we pick those and expand to supported
  // tab events for them too.
  var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
                           filter(isInteractive);
  var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));


  // Finally merge stream of tab events from future windows and current windows
  // to cover all tab events on all windows that will open.
  return merge([eventsFromInteractive, eventsFromFuture]);
}

// Map events to Fennec format if necessary
exports.events = map(makeEvents(), function (event) {
  return !isFennec ? event : {
    type: event.type,
    target: event.target.ownerGlobal.BrowserApp
            .getTabForBrowser(event.target)
  };
});
PK
!<ԦVuu#modules/commonjs/sdk/tabs/common.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { validateOptions } = require("../deprecated/api-utils");
const { data } = require("../self");

function Options(options) {
  if ('string' === typeof options)
    options = { url: options };

  return validateOptions(options, {
    url: {
      is: ["string"],
      map: (v) => v ? data.url(v) : v
    },
    inBackground: {
      map: Boolean,
      is: ["undefined", "boolean"]
    },
    isPinned: { is: ["undefined", "boolean"] },
    isPrivate: { is: ["undefined", "boolean"] },
    inNewWindow: { is: ["undefined", "boolean"] },
    onOpen: { is: ["undefined", "function"] },
    onClose: { is: ["undefined", "function"] },
    onReady: { is: ["undefined", "function"] },
    onLoad: { is: ["undefined", "function"] },
    onPageShow: { is: ["undefined", "function"] },
    onActivate: { is: ["undefined", "function"] },
    onDeactivate: { is: ["undefined", "function"] }
  });
}
exports.Options = Options;
PK
!<}ʰ#modules/commonjs/sdk/tabs/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const ON_PREFIX = "on";
const TAB_PREFIX = "Tab";

const EVENTS = {
  ready: "DOMContentLoaded",
  load: "load", // Used for non-HTML content
  pageshow: "pageshow", // Used for cached content
  open: "TabOpen",
  close: "TabClose",
  activate: "TabSelect",
  deactivate: null,
  pinned: "TabPinned",
  unpinned: "TabUnpinned"
}
exports.EVENTS = EVENTS;

Object.keys(EVENTS).forEach(function(name) {
  EVENTS[name] = {
    name: name,
    listener: createListenerName(name),
    dom: EVENTS[name]
  }
});

function createListenerName (name) {
  if (name === 'pageshow')
    return 'onPageShow';
  else
    return ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1);
}
PK
!<}tt$modules/commonjs/sdk/tabs/helpers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};


// NOTE: This file should only export Tab instances


lazyRequire(this, './utils', { "getTabForBrowser": "getRawTabForBrowser" });
const { modelFor } = require('../model/core');

exports.getTabForRawTab = modelFor;

function getTabForBrowser(browser) {
  return modelFor(getRawTabForBrowser(browser)) || null;
}
exports.getTabForBrowser = getTabForBrowser;
PK
!<nNN&modules/commonjs/sdk/tabs/namespace.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

var { ns } = require('../core/namespace');

exports.tabsNS = ns();
exports.tabNS = ns();
exports.rawTabNS = ns();
PK
!<%modules/commonjs/sdk/tabs/observer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "unstable"
};

const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { getActiveTab, getTabs } = require("./utils");
const { browserWindowIterator } = require("../deprecated/window-utils");
const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
const { observer: windowObserver } = require("../windows/observer");
const { when } = require("../system/unload");

const EVENTS = {
  "TabOpen": "open",
  "TabClose": "close",
  "TabSelect": "select",
  "TabMove": "move",
  "TabPinned": "pinned",
  "TabUnpinned": "unpinned"
};

const selectedTab = Symbol("observer/state/selectedTab");

// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
  implements: [EventTarget, DOMEventAssembler],
  initialize() {
    this[selectedTab] = null;
    // Currently Gecko does not dispatch any event on the previously selected
    // tab before / after "TabSelect" is dispatched. In order to work around this
    // limitation we keep track of selected tab and emit "deactivate" event with
    // that before emitting "activate" on selected tab.
    this.on("select", tab => {
      const selected = this[selectedTab];
      if (selected !== tab) {
        if (selected) {
          emit(this, 'deactivate', selected);
        }

        if (tab) {
          this[selectedTab] = tab;
          emit(this, 'activate', this[selectedTab]);
        }
      }
    });


    // We also observe opening / closing windows in order to add / remove it's
    // containers to the observed list.
    windowObserver.on("open", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        this.observe(chromeWindow);
      }
    });

    windowObserver.on("close", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        // Bug 751546: Emit `deactivate` event on window close immediatly
        // Otherwise we are going to face "dead object" exception on `select` event
        if (getActiveTab(chromeWindow) === this[selectedTab]) {
          emit(this, "deactivate", this[selectedTab]);
          this[selectedTab] = null;
        }
        this.ignore(chromeWindow);
      }
    });


    // Currently gecko does not dispatches "TabSelect" events when different
    // window gets activated. To work around this limitation we emulate "select"
    // event for this case.
    windowObserver.on("activate", chromeWindow => {
      if (isBrowser(chromeWindow)) {
        emit(this, "select", getActiveTab(chromeWindow));
      }
    });

    // We should synchronize state, since probably we already have at least one
    // window open.
    for (let chromeWindow of browserWindowIterator()) {
      this.observe(chromeWindow);
    }

    when(_ => {
      // Don't dispatch a deactivate event during unload.
      this[selectedTab] = null;
    });
  },
  /**
   * Events that are supported and emitted by the module.
   */
  supportedEventsTypes: Object.keys(EVENTS),
  /**
   * Function handles all the supported events on all the windows that are
   * observed. Method is used to proxy events to the listeners registered on
   * this event emitter.
   * @param {Event} event
   *    Keyboard event being emitted.
   */
  handleEvent: function handleEvent(event) {
    emit(this, EVENTS[event.type], event.target, event);
  }
});

exports.observer = new Observer();
PK
!<GzAh'modules/commonjs/sdk/tabs/tab-fennec.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage');
const { tabNS, rawTabNS } = require('./namespace');
const { EventTarget } = require('../event/target');
const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
        getTabContentWindow, getTabForBrowser, setTabURL, getOwnerWindow,
        getTabContentDocument, getTabContentType, getTabId, isTab } = require('./utils');
const { emit } = require('../event/core');
const { isPrivate } = require('../private-browsing/utils');
const { isWindowPrivate } = require('../window/utils');
const { when: unload } = require('../system/unload');
const { BLANK } = require('../content/thumbnail');
const { viewFor } = require('../view/core');
const { EVENTS } = require('./events');
const { modelFor } = require('../model/core');

const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';

const Tab = Class({
  extends: EventTarget,
  initialize: function initialize(options) {
    options = options.tab ? options : { tab: options };
    let tab = options.tab;

    EventTarget.prototype.initialize.call(this, options);
    let tabInternals = tabNS(this);
    rawTabNS(tab).tab = this;

    let window = tabInternals.window = options.window || getOwnerWindow(tab);
    tabInternals.tab = tab;

    // TabReady
    let onReady = tabInternals.onReady = onTabReady.bind(this);
    tab.browser.addEventListener(EVENTS.ready.dom, onReady);

    // TabPageShow
    let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this);
    tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow);

    // TabLoad
    let onLoad = tabInternals.onLoad = onTabLoad.bind(this);
    tab.browser.addEventListener(EVENTS.load.dom, onLoad, true);

    // TabClose
    let onClose = tabInternals.onClose = onTabClose.bind(this);
    window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onClose);

    unload(cleanupTab.bind(null, this));
  },

  /**
   * The title of the page currently loaded in the tab.
   * Changing this property changes an actual title.
   * @type {String}
   */
  get title() {
    return getTabTitle(tabNS(this).tab);
  },
  set title(title) {
    setTabTitle(tabNS(this).tab, title);
  },

  /**
   * Location of the page currently loaded in this tab.
   * Changing this property will loads page under under the specified location.
   * @type {String}
   */
  get url() {
    return tabNS(this).closed ? undefined : getTabURL(tabNS(this).tab);
  },
  set url(url) {
    setTabURL(tabNS(this).tab, url);
  },

  getThumbnail: function() {
    // TODO: implement!
    console.error(ERR_FENNEC_MSG);

    // return 80x45 blank default
    return BLANK;
  },

  /**
   * tab's document readyState, or 'uninitialized' if it doesn't even exist yet.
   */
  get readyState() {
    let doc = getTabContentDocument(tabNS(this).tab);
    return doc && doc.readyState || 'uninitialized';
  },

  get id() {
    return getTabId(tabNS(this).tab);
  },

  /**
   * The index of the tab relative to other tabs in the application window.
   * Changing this property will change order of the actual position of the tab.
   * @type {Number}
   */
  get index() {
    if (tabNS(this).closed) return undefined;

    let tabs = tabNS(this).window.BrowserApp.tabs;
    let tab = tabNS(this).tab;
    for (var i = tabs.length; i >= 0; i--) {
      if (tabs[i] === tab)
        return i;
    }
    return null;
  },
  set index(value) {
    console.error(ERR_FENNEC_MSG); // TODO
  },

  /**
   * Whether or not tab is pinned (Is an app-tab).
   * @type {Boolean}
   */
  get isPinned() {
    console.error(ERR_FENNEC_MSG); // TODO
    return false; // TODO
  },
  pin: function pin() {
    console.error(ERR_FENNEC_MSG); // TODO
  },
  unpin: function unpin() {
    console.error(ERR_FENNEC_MSG); // TODO
  },

  /**
   * Returns the MIME type that the document loaded in the tab is being
   * rendered as.
   * @type {String}
   */
  get contentType() {
    return getTabContentType(tabNS(this).tab);
  },

  /**
   * Create a worker for this tab, first argument is options given to Worker.
   * @type {Worker}
   */
  attach: function attach(options) {
    // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
    // TODO: fix this circular dependency
    let { Worker } = require('./worker');
    return Worker(options, getTabContentWindow(tabNS(this).tab));
  },

  /**
   * Make this tab active.
   */
  activate: function activate() {
    activateTab(tabNS(this).tab, tabNS(this).window);
  },

  /**
   * Close the tab
   */
  close: function close(callback) {
    let tab = this;
    this.once(EVENTS.close.name, function () {
      tabNS(tab).closed = true;
      if (callback) callback();
    });

    closeTab(tabNS(this).tab);
  },

  /**
   * Reload the tab
   */
  reload: function reload() {
    tabNS(this).tab.browser.reload();
  }
});
exports.Tab = Tab;

// Implement `viewFor` polymorphic function for the Tab
// instances.
viewFor.define(Tab, x => tabNS(x).tab);

function cleanupTab(tab) {
  let tabInternals = tabNS(tab);
  if (!tabInternals.tab)
    return;

  if (tabInternals.tab.browser) {
    tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady);
    tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow);
    tabInternals.tab.browser.removeEventListener(EVENTS.load.dom, tabInternals.onLoad, true);
  }
  tabInternals.onReady = null;
  tabInternals.onPageShow = null;
  tabInternals.onLoad = null;
  tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose);
  tabInternals.onClose = null;
  rawTabNS(tabInternals.tab).tab = null;
  tabInternals.tab = null;
  tabInternals.window = null;
}

function onTabReady(event) {
  let win = event.target.defaultView;

  // ignore frames
  if (win === win.top) {
    emit(this, 'ready', this);
  }
}

function onTabLoad (event) {
  let win = event.target.defaultView;

  // ignore frames
  if (win === win.top) {
    emit(this, 'load', this);
  }
}

function onTabPageShow(event) {
  let win = event.target.defaultView;
  if (win === win.top)
    emit(this, 'pageshow', this, event.persisted);
}

// TabClose
function onTabClose(event) {
  let rawTab = getTabForBrowser(event.target);
  if (tabNS(this).tab !== rawTab)
    return;

  emit(this, EVENTS.close.name, this);
  cleanupTab(this);
};

isPrivate.implement(Tab, tab => {
  return isWindowPrivate(getTabContentWindow(tabNS(tab).tab));
});

// Implement `modelFor` function for the Tab instances.
modelFor.when(isTab, rawTab => {
  return rawTabNS(rawTab).tab;
});
PK
!<0##(modules/commonjs/sdk/tabs/tab-firefox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Class } = require('../core/heritage');
const { observer } = require('./observer');
const { observer: windowObserver } = require('../windows/observer');
const { addListItem, removeListItem } = require('../util/list');
const { viewFor } = require('../view/core');
const { modelFor } = require('../model/core');
const { emit, setListeners } = require('../event/core');
const { EventTarget } = require('../event/target');
const { getBrowserForTab, setTabURL, getTabId, getTabURL, getTabForBrowser,
        getTabs, getTabTitle, setTabTitle, getIndex, closeTab, reload, move,
        activateTab, pin, unpin, isTab } = require('./utils');
const { isBrowser, getInnerId, isWindowPrivate } = require('../window/utils');
const { getThumbnailURIForWindow, BLANK } = require("../content/thumbnail");
const { when } = require('../system/unload');
const { ignoreWindow, isPrivate } = require('../private-browsing/utils')
const { defer } = require('../lang/functional');
const { getURL } = require('../url/utils');
const { frames, remoteRequire } = require('../remote/parent');
remoteRequire('sdk/content/tab-events');

const modelsFor = new WeakMap();
const viewsFor = new WeakMap();
const destroyed = new WeakMap();

const tabEvents = {};
exports.tabEvents = tabEvents;

function browser(tab) {
  return getBrowserForTab(viewsFor.get(tab));
}

function isDestroyed(tab) {
  return destroyed.has(tab);
}

function isClosed(tab) {
  if (!viewsFor.has(tab))
    return true;
  return viewsFor.get(tab).closing;
}

// private tab attribute where the remote cached value is stored
const remoteReadyStateCached = Symbol("remoteReadyStateCached");

const Tab = Class({
  implements: [EventTarget],
  initialize: function(tabElement, options = null) {
    modelsFor.set(tabElement, this);
    viewsFor.set(this, tabElement);

    if (options) {
      EventTarget.prototype.initialize.call(this, options);

      if (options.isPinned)
        this.pin();

      // Note that activate is defered and so will run after any open event
      // is sent out
      if (!options.inBackground)
        this.activate();
    }

    getURL.implement(this, tab => tab.url);
    isPrivate.implement(this, tab => {
      return isWindowPrivate(viewsFor.get(tab).ownerGlobal);
    });
  },

  get id() {
    return isDestroyed(this) ? undefined : getTabId(viewsFor.get(this));
  },

  get title() {
    return isDestroyed(this) ? undefined : getTabTitle(viewsFor.get(this));
  },

  set title(val) {
    if (isDestroyed(this))
      return;

    setTabTitle(viewsFor.get(this), val);
  },

  get url() {
    return isDestroyed(this) ? undefined : getTabURL(viewsFor.get(this));
  },

  set url(val) {
    if (isDestroyed(this))
      return;

    setTabURL(viewsFor.get(this), val);
  },

  get contentType() {
    return isDestroyed(this) ? undefined : browser(this).documentContentType;
  },

  get index() {
    return isDestroyed(this) ? undefined : getIndex(viewsFor.get(this));
  },

  set index(val) {
    if (isDestroyed(this))
      return;

    move(viewsFor.get(this), val);
  },

  get isPinned() {
    return isDestroyed(this) ? undefined : viewsFor.get(this).pinned;
  },

  get window() {
    if (isClosed(this))
      return undefined;

    // TODO: Remove the dependency on the windows module, see bug 792670
    require('../windows');
    let tabElement = viewsFor.get(this);
    let domWindow = tabElement.ownerGlobal;
    return modelFor(domWindow);
  },

  get readyState() {
    return isDestroyed(this) ? undefined : this[remoteReadyStateCached] || "uninitialized";
  },

  pin: function() {
    if (isDestroyed(this))
      return;

    pin(viewsFor.get(this));
  },

  unpin: function() {
    if (isDestroyed(this))
      return;

    unpin(viewsFor.get(this));
  },

  close: function(callback) {
    let tabElement = viewsFor.get(this);

    if (isDestroyed(this) || !tabElement || !tabElement.parentNode) {
      if (callback)
        callback();
      return;
    }

    this.once('close', () => {
      this.destroy();
      if (callback)
        callback();
    });

    closeTab(tabElement);
  },

  reload: function() {
    if (isDestroyed(this))
      return;

    reload(viewsFor.get(this));
  },

  activate: defer(function() {
    if (isDestroyed(this))
      return;

    activateTab(viewsFor.get(this));
  }),

  getThumbnail: function() {
    if (isDestroyed(this))
      return BLANK;

    // TODO: This is unimplemented in e10s: bug 1148601
    if (browser(this).isRemoteBrowser) {
      console.error('This method is not supported with E10S');
      return BLANK;
    }
    return getThumbnailURIForWindow(browser(this).contentWindow);
  },

  attach: function(options) {
    if (isDestroyed(this))
      return;

    let { Worker } = require('../content/worker');
    let { connect, makeChildOptions } = require('../content/utils');

    let worker = Worker(options);
    worker.once("detach", () => {
      worker.destroy();
    });

    let attach = frame => {
      let childOptions = makeChildOptions(options);
      frame.port.emit("sdk/tab/attach", childOptions);
      connect(worker, frame, { id: childOptions.id, url: this.url });
    };

    // Do this synchronously if possible
    let frame = frames.getFrameForBrowser(browser(this));
    if (frame) {
      attach(frame);
    }
    else {
      let listener = (frame) => {
        if (frame.frameElement != browser(this))
          return;

        frames.off("attach", listener);
        attach(frame);
      };
      frames.on("attach", listener);
    }

    return worker;
  },

  destroy: function() {
    if (isDestroyed(this))
      return;

    destroyed.set(this, true);
  }
});
exports.Tab = Tab;

viewFor.define(Tab, tab => viewsFor.get(tab));

// Returns the high-level window for this DOM window if the windows module has
// ever been loaded otherwise returns null
function maybeWindowFor(domWindow) {
  try {
    return modelFor(domWindow);
  }
  catch (e) {
    return null;
  }
}

function tabEmit(tab, event, ...args) {
  // Don't emit events for destroyed tabs
  if (isDestroyed(tab))
    return;

  // If the windows module was never loaded this will return null. We don't need
  // to emit to the window.tabs object in this case as nothing can be listening.
  let tabElement = viewsFor.get(tab);
  let window = maybeWindowFor(tabElement.ownerGlobal);
  if (window)
    emit(window.tabs, event, tab, ...args);

  emit(tabEvents, event, tab, ...args);
  emit(tab, event, tab, ...args);
}

function windowClosed(domWindow) {
  if (!isBrowser(domWindow))
    return;

  for (let tabElement of getTabs(domWindow)) {
    tabEventListener("close", tabElement);
  }
}
windowObserver.on('close', windowClosed);

// Don't want to send close events after unloaded
when(_ => {
  windowObserver.off('close', windowClosed);
});

// Listen for tabbrowser events
function tabEventListener(event, tabElement, ...args) {
  let domWindow = tabElement.ownerGlobal;

  if (ignoreWindow(domWindow))
    return;

  // Don't send events for tabs that are already closing
  if (event != "close" && (tabElement.closing || !tabElement.parentNode))
    return;

  let tab = modelsFor.get(tabElement);
  if (!tab)
    tab = new Tab(tabElement);

  let window = maybeWindowFor(domWindow);

  if (event == "open") {
    // Note, add to the window tabs first because if this is the first access to
    // window.tabs it will be prefilling itself with everything from tabs
    if (window)
      addListItem(window.tabs, tab);
    // The tabs module will take care of adding to its internal list
  }
  else if (event == "close") {
    if (window)
      removeListItem(window.tabs, tab);
    // The tabs module will take care of removing from its internal list
  }
  else if (event == "init" || event == "create" || event == "ready" || event == "load") {
    // Ignore load events from before browser windows have fully loaded, these
    // are for about:blank in the initial tab
    if (isBrowser(domWindow) && !domWindow.gBrowserInit.delayedStartupFinished)
      return;

    // update the cached remote readyState value
    let { readyState } = args[0] || {};
    tab[remoteReadyStateCached] = readyState;
  }

  if (event == "init") {
    // Do not emit events for the detected existent tabs, we only need to cache
    // their current document.readyState value.
    return;
  }

  tabEmit(tab, event, ...args);

  // The tab object shouldn't be reachable after closed
  if (event == "close") {
    viewsFor.delete(tab);
    modelsFor.delete(tabElement);
  }
}
observer.on('*', tabEventListener);

// Listen for tab events from content
frames.port.on('sdk/tab/event', (frame, event, ...args) => {
  if (!frame.isTab)
    return;

  let tabElement = getTabForBrowser(frame.frameElement);
  if (!tabElement)
    return;

  tabEventListener(event, tabElement, ...args);
});

// Implement `modelFor` function for the Tab instances..
modelFor.when(isTab, view => {
  return modelsFor.get(view);
});
PK
!<Vd0 modules/commonjs/sdk/tabs/tab.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

const { getTargetWindow } = require("../content/mod");
lazyRequire(this, "./utils", "getTabContentWindow", "isTab");
lazyRequire(this, "../view/core", "viewFor");

if (require('../system/xul-app').name == 'Fennec') {
  module.exports = require('./tab-fennec');
}
else {
  module.exports = require('./tab-firefox');
}

getTargetWindow.when(isTab, tab => getTabContentWindow(tab));

getTargetWindow.when(x => x instanceof module.exports.Tab,
  tab => getTabContentWindow(viewFor(tab)));
PK
!<0)modules/commonjs/sdk/tabs/tabs-firefox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Class } = require('../core/heritage');
const { Tab, tabEvents } = require('./tab');
const { EventTarget } = require('../event/target');
lazyRequire(this, '../event/core', "emit", "setListeners");
const { pipe } = require('../event/utils');
const { observer: windowObserver } = require('../windows/observer');
const { List, addListItem, removeListItem } = require('../util/list');
lazyRequire(this, '../model/core', "modelFor");
lazyRequire(this, '../view/core', "viewFor");
lazyRequire(this, './utils', "getTabs", "getSelectedTab");
lazyRequire(this, '../window/utils', "getMostRecentBrowserWindow", "isBrowser");
lazyRequire(this, './common', "Options");
lazyRequire(this, '../private-browsing', "isPrivate");
lazyRequire(this, '../private-browsing/utils', "ignoreWindow", "isWindowPBSupported")
const { isPrivateBrowsingSupported } = require('sdk/self');

const supportPrivateTabs = isPrivateBrowsingSupported && isWindowPBSupported;

const Tabs = Class({
  implements: [EventTarget],
  extends: List,
  initialize: function() {
    List.prototype.initialize.call(this);

    // We must do the list manipulation here where the object is extensible
    this.on("open", tab => {
      addListItem(this, tab);
    });

    this.on("close", tab => {
      removeListItem(this, tab);
    });
  },

  get activeTab() {
    let activeDomWin = getMostRecentBrowserWindow();
    if (!activeDomWin)
      return null;
    return modelFor(getSelectedTab(activeDomWin));
  },

  open: function(options) {
    options = Options(options);

    // TODO: Remove the dependency on the windows module: bug 792670
    let windows = require('../windows').browserWindows;
    let activeWindow = windows.activeWindow;

    let privateState = supportPrivateTabs && options.isPrivate;
    // When no isPrivate option was passed use the private state of the active
    // window
    if (activeWindow && privateState === undefined)
      privateState = isPrivate(activeWindow);

    function getWindow(privateState) {
      for (let window of windows) {
        if (privateState === isPrivate(window)) {
          return window;
        }
      }
      return null;
    }

    function openNewWindowWithTab() {
      windows.open({
        url: options.url,
        isPrivate: privateState,
        onOpen: function(newWindow) {
          let tab = newWindow.tabs[0];
          setListeners(tab, options);

          if (options.isPinned)
            tab.pin();

          // We don't emit the open event for the first tab in a new window so
          // do it now the listeners are attached
          emit(tab, "open", tab);
        }
      });
    }

    if (options.inNewWindow)
      return openNewWindowWithTab();

    // if the active window is in the state that we need then use it
    if (activeWindow && (privateState === isPrivate(activeWindow)))
      return activeWindow.tabs.open(options);

    // find a window in the state that we need
    let window = getWindow(privateState);
    if (window)
      return window.tabs.open(options);

    return openNewWindowWithTab();
  }
});

const allTabs = new Tabs();
module.exports = allTabs;
pipe(tabEvents, allTabs);

function addWindowTab(window, tabElement) {
  let tab = new Tab(tabElement);
  if (window)
    addListItem(window.tabs, tab);
  addListItem(allTabs, tab);
  emit(allTabs, "open", tab);
}

// Find tabs in already open windows
for (let tabElement of getTabs())
  addWindowTab(null, tabElement);

// Detect tabs in new windows
windowObserver.on('open', domWindow => {
  if (!isBrowser(domWindow) || ignoreWindow(domWindow))
    return;

  let window = null;
  try {
    modelFor(domWindow);
  }
  catch (e) { }

  for (let tabElement of getTabs(domWindow)) {
    addWindowTab(window, tabElement);
  }
});
PK
!<[:4&&"modules/commonjs/sdk/tabs/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};


// NOTE: This file should only deal with xul/native tabs


const { Ci, Cu } = require('chrome');
lazyRequire(this, "../lang/functional", "defer");
lazyRequire(this, '../window/utils', "windows", "isBrowser");
lazyRequire(this, '../self', "isPrivateBrowsingSupported");
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");

// Bug 834961: ignore private windows when they are not supported
function getWindows() {
  return windows(null, { includePrivate: isPrivateBrowsingSupported });
}

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

// Define predicate functions that can be used to detech weather
// we deal with fennec tabs or firefox tabs.

// Predicate to detect whether tab is XUL "Tab" node.
const isXULTab = tab =>
  tab instanceof Ci.nsIDOMNode &&
  tab.nodeName === "tab" &&
  tab.namespaceURI === XUL_NS;
exports.isXULTab = isXULTab;

// Predicate to detecet whether given tab is a fettec tab.
// Unfortunately we have to guess via duck typinng of:
// http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583
const isFennecTab = tab =>
  tab &&
  tab.QueryInterface &&
  Ci.nsIBrowserTab &&
  tab.QueryInterface(Ci.nsIBrowserTab) === tab;
exports.isFennecTab = isFennecTab;

const isTab = x => isXULTab(x) || isFennecTab(x);
exports.isTab = isTab;

function activateTab(tab, window) {
  let gBrowser = getTabBrowserForTab(tab);

  // normal case
  if (gBrowser) {
    gBrowser.selectedTab = tab;
  }
  // fennec ?
  else if (window && window.BrowserApp) {
    window.BrowserApp.selectTab(tab);
  }
  return null;
}
exports.activateTab = activateTab;

function getTabBrowser(window) {
  // bug 1009938 - may be null in SeaMonkey
  return window.gBrowser || window.getBrowser();
}
exports.getTabBrowser = getTabBrowser;

function getTabContainer(window) {
  return getTabBrowser(window).tabContainer;
}
exports.getTabContainer = getTabContainer;

/**
 * Returns the tabs for the `window` if given, or the tabs
 * across all the browser's windows otherwise.
 *
 * @param {nsIWindow} [window]
 *    A reference to a window
 *
 * @returns {Array} an array of Tab objects
 */
function getTabs(window) {
  if (arguments.length === 0) {
    return getWindows().
               filter(isBrowser).
               reduce((tabs, window) => tabs.concat(getTabs(window)), []);
  }

  // fennec
  if (window.BrowserApp)
    return window.BrowserApp.tabs;

  // firefox - default
  return Array.filter(getTabContainer(window).children, t => !t.closing);
}
exports.getTabs = getTabs;

function getActiveTab(window) {
  return getSelectedTab(window);
}
exports.getActiveTab = getActiveTab;

function getOwnerWindow(tab) {
  // normal case
  if (tab.ownerDocument)
    return tab.ownerGlobal;

  // try fennec case
  return getWindowHoldingTab(tab);
}
exports.getOwnerWindow = getOwnerWindow;

// fennec
function getWindowHoldingTab(rawTab) {
  for (let window of getWindows()) {
    // this function may be called when not using fennec,
    // but BrowserApp is only defined on Fennec
    if (!window.BrowserApp)
      continue;

    for (let tab of window.BrowserApp.tabs) {
      if (tab === rawTab)
        return window;
    }
  }

  return null;
}

function openTab(window, url, options) {
  options = options || {};

  // fennec?
  if (window.BrowserApp) {
    return window.BrowserApp.addTab(url, {
      selected: options.inBackground ? false : true,
      pinned: options.isPinned || false,
      isPrivate: options.isPrivate || false,
      parentId: window.BrowserApp.selectedTab.id
    });
  }

  // firefox
  let newTab = window.gBrowser.addTab(url);
  if (!options.inBackground) {
    activateTab(newTab);
  }
  return newTab;
};
exports.openTab = openTab;

function isTabOpen(tab) {
  // try normal case then fennec case
  return !!((tab.linkedBrowser) || getWindowHoldingTab(tab));
}
exports.isTabOpen = isTabOpen;

function closeTab(tab) {
  let gBrowser = getTabBrowserForTab(tab);
  // normal case?
  if (gBrowser) {
    // Bug 699450: the tab may already have been detached
    if (!tab.parentNode)
      return;
    return gBrowser.removeTab(tab);
  }

  let window = getWindowHoldingTab(tab);
  // fennec?
  if (window && window.BrowserApp) {
    // Bug 699450: the tab may already have been detached
    if (!tab.browser)
      return;
    return window.BrowserApp.closeTab(tab);
  }
  return null;
}
exports.closeTab = closeTab;

function getURI(tab) {
  if (tab.browser) // fennec
    return tab.browser.currentURI.spec;
  return tab.linkedBrowser.currentURI.spec;
}
exports.getURI = getURI;

function getTabBrowserForTab(tab) {
  let outerWin = getOwnerWindow(tab);
  if (outerWin)
    return getOwnerWindow(tab).gBrowser;
  return null;
}
exports.getTabBrowserForTab = getTabBrowserForTab;

function getBrowserForTab(tab) {
  if (tab.browser) // fennec
    return tab.browser;

  return tab.linkedBrowser;
}
exports.getBrowserForTab = getBrowserForTab;

function getTabId(tab) {
  if (tab.browser) // fennec
    return tab.id

  return String(tab.linkedPanel).split('panel').pop();
}
exports.getTabId = getTabId;

function getTabForId(id) {
  return getTabs().find(tab => getTabId(tab) === id) || null;
}
exports.getTabForId = getTabForId;

function getTabTitle(tab) {
  return getBrowserForTab(tab).contentTitle || tab.label || "";
}
exports.getTabTitle = getTabTitle;

function setTabTitle(tab, title) {
  title = String(title);
  if (tab.browser) {
    // Fennec
    tab.browser.contentDocument.title = title;
  }
  else {
    let browser = getBrowserForTab(tab);
    // Note that we aren't actually setting the document title in e10s, just
    // the title the browser thinks the content has
    if (browser.isRemoteBrowser)
      browser._contentTitle = title;
    else
      browser.contentDocument.title = title;
  }
  tab.label = String(title);
}
exports.setTabTitle = setTabTitle;

function getTabContentDocument(tab) {
  return getBrowserForTab(tab).contentDocument;
}
exports.getTabContentDocument = getTabContentDocument;

function getTabContentWindow(tab) {
  return getBrowserForTab(tab).contentWindow;
}
exports.getTabContentWindow = getTabContentWindow;

/**
 * Returns all tabs' content windows across all the browsers' windows
 */
function getAllTabContentWindows() {
  return getTabs().map(getTabContentWindow);
}
exports.getAllTabContentWindows = getAllTabContentWindows;

// gets the tab containing the provided window
function getTabForContentWindow(window) {
  return getTabs().find(tab => getTabContentWindow(tab) === window.top) || null;
}
exports.getTabForContentWindow = getTabForContentWindow;

// only sdk/selection.js is relying on shims
function getTabForContentWindowNoShim(window) {
  function getTabContentWindowNoShim(tab) {
    let browser = getBrowserForTab(tab);
    return ShimWaiver.getProperty(browser, "contentWindow");
  }
  return getTabs().find(tab => getTabContentWindowNoShim(tab) === window.top) || null;
}
exports.getTabForContentWindowNoShim = getTabForContentWindowNoShim;

function getTabURL(tab) {
  return String(getBrowserForTab(tab).currentURI.spec);
}
exports.getTabURL = getTabURL;

function setTabURL(tab, url) {
  let browser = getBrowserForTab(tab);
  browser.loadURI(String(url));
}
// "TabOpen" event is fired when it's still "about:blank" is loaded in the
// changing `location` property of the `contentDocument` has no effect since
// seems to be either ignored or overridden by internal listener, there for
// location change is enqueued for the next turn of event loop.
exports.setTabURL = defer(setTabURL);

function getTabContentType(tab) {
  return getBrowserForTab(tab).contentDocument.contentType;
}
exports.getTabContentType = getTabContentType;

function getSelectedTab(window) {
  if (window.BrowserApp) // fennec?
    return window.BrowserApp.selectedTab;
  if (window.gBrowser)
    return window.gBrowser.selectedTab;
  return null;
}
exports.getSelectedTab = getSelectedTab;


function getTabForBrowser(browser) {
  for (let window of getWindows()) {
    // this function may be called when not using fennec
    if (!window.BrowserApp)
      continue;

    for  (let tab of window.BrowserApp.tabs) {
      if (tab.browser === browser)
        return tab;
    }
  }

  let tabbrowser = browser.getTabBrowser && browser.getTabBrowser()
  return !!tabbrowser && tabbrowser.getTabForBrowser(browser);
}
exports.getTabForBrowser = getTabForBrowser;

function pin(tab) {
  let gBrowser = getTabBrowserForTab(tab);
  // TODO: Implement Fennec support
  if (gBrowser) gBrowser.pinTab(tab);
}
exports.pin = pin;

function unpin(tab) {
  let gBrowser = getTabBrowserForTab(tab);
  // TODO: Implement Fennec support
  if (gBrowser) gBrowser.unpinTab(tab);
}
exports.unpin = unpin;

function isPinned(tab) {
  return !!tab.pinned;
}
exports.isPinned = isPinned;

function reload(tab) {
  getBrowserForTab(tab).reload();
}
exports.reload = reload

function getIndex(tab) {
  let gBrowser = getTabBrowserForTab(tab);
  // Firefox
  if (gBrowser) {
    return tab._tPos;
  }
  // Fennec
  else {
    let window = getWindowHoldingTab(tab)
    let tabs = window.BrowserApp.tabs;
    for (let i = tabs.length; i >= 0; i--)
      if (tabs[i] === tab) return i;
  }
}
exports.getIndex = getIndex;

function move(tab, index) {
  let gBrowser = getTabBrowserForTab(tab);
  // Firefox
  if (gBrowser) gBrowser.moveTabTo(tab, index);
  // TODO: Implement fennec support
}
exports.move = move;
PK
!<3#modules/commonjs/sdk/tabs/worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const ContentWorker = require('../content/worker').Worker;

function Worker(options, window) {
  options.window = window;

  let worker = ContentWorker(options);
  worker.once("detach", function detach() {
    worker.destroy();
  });
  return worker;
}
exports.Worker = Worker;PK
!<Omodules/commonjs/sdk/tabs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

if (require("./system/xul-app").is("Fennec")) {
  module.exports = require("./windows/tabs-fennec").tabs;
}
else {
  module.exports = require("./tabs/tabs-firefox");
}

const tabs = module.exports;
PK
!<mZ&&&&#modules/commonjs/sdk/test/assert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { isFunction, isNull, isObject, isString,
        isRegExp, isArray, isDate, isPrimitive,
        isUndefined, instanceOf, source } = require("../lang/type");

/**
 * The `AssertionError` is defined in assert.
 * @extends Error
 * @example
 *  new assert.AssertionError({
 *    message: message,
 *    actual: actual,
 *    expected: expected
 *  })
 */
function AssertionError(options) {
  let assertionError = Object.create(AssertionError.prototype);

  if (isString(options))
    options = { message: options };
  if ("actual" in options)
    assertionError.actual = options.actual;
  if ("expected" in options)
    assertionError.expected = options.expected;
  if ("operator" in options)
    assertionError.operator = options.operator;

  assertionError.message = options.message;
  assertionError.stack = new Error().stack;
  return assertionError;
}
AssertionError.prototype = Object.create(Error.prototype, {
  constructor: { value: AssertionError },
  name: { value: "AssertionError", enumerable: true },
  toString: { value: function toString() {
    let value;
    if (this.message) {
      value = this.name + " : " + this.message;
    }
    else {
      value = [
        this.name + " : ",
        source(this.expected),
        this.operator,
        source(this.actual)
      ].join(" ");
    }
    return value;
  }}
});
exports.AssertionError = AssertionError;

function Assert(logger) {
  let assert = Object.create(Assert.prototype, { _log: { value: logger }});

  assert.fail = assert.fail.bind(assert);
  assert.pass = assert.pass.bind(assert);

  return assert;
}

Assert.prototype = {
  fail: function fail(e) {
    if (!e || typeof(e) !== 'object') {
      this._log.fail(e);
      return;
    }
    let message = e.message;
    try {
      if ('operator' in e) {
        message += [
          " -",
          source(e.actual),
          e.operator,
          source(e.expected)
        ].join(" ");
      }
    }
    catch(e) {}
    this._log.fail(message);
  },
  pass: function pass(message) {
    this._log.pass(message);
    return true;
  },
  error: function error(e) {
    this._log.exception(e);
  },
  ok: function ok(value, message) {
    if (!!!value) {
      this.fail({
        actual: value,
        expected: true,
        message: message,
        operator: "=="
      });
      return false;
    }

    this.pass(message);
    return true;
  },

  /**
   * The equality assertion tests shallow, coercive equality with `==`.
   * @example
   *    assert.equal(1, 1, "one is one");
   */
  equal: function equal(actual, expected, message) {
    if (actual == expected) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "=="
    });
    return false;
  },

  /**
   * The non-equality assertion tests for whether two objects are not equal
   * with `!=`.
   * @example
   *    assert.notEqual(1, 2, "one is not two");
   */
  notEqual: function notEqual(actual, expected, message) {
    if (actual != expected) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "!=",
    });
    return false;
  },

  /**
   * The equivalence assertion tests a deep (with `===`) equality relation.
   * @example
   *    assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
   */
   deepEqual: function deepEqual(actual, expected, message) {
    if (isDeepEqual(actual, expected)) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "deepEqual"
    });
    return false;
  },

  /**
   * The non-equivalence assertion tests for any deep (with `===`) inequality.
   * @example
   *    assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
   *                        "object's inherit from different prototypes");
   */
  notDeepEqual: function notDeepEqual(actual, expected, message) {
    if (!isDeepEqual(actual, expected)) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "notDeepEqual"
    });
    return false;
  },

  /**
   * The strict equality assertion tests strict equality, as determined by
   * `===`.
   * @example
   *    assert.strictEqual(null, null, "`null` is `null`")
   */
  strictEqual: function strictEqual(actual, expected, message) {
    if (actual === expected) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "==="
    });
    return false;
  },

  /**
   * The strict non-equality assertion tests for strict inequality, as
   * determined by `!==`.
   * @example
   *    assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
   */
  notStrictEqual: function notStrictEqual(actual, expected, message) {
    if (actual !== expected) {
      this.pass(message);
      return true;
    }

    this.fail({
      actual: actual,
      expected: expected,
      message: message,
      operator: "!=="
    });
    return false;
  },

  /**
   * The assertion whether or not given `block` throws an exception. If optional
   * `Error` argument is provided and it's type of function thrown error is
   * asserted to be an instance of it, if type of `Error` is string then message
   * of throw exception is asserted to contain it.
   * @param {Function} block
   *    Function that is expected to throw.
   * @param {Error|RegExp} [Error]
   *    Error constructor that is expected to be thrown or a string that
   *    must be contained by a message of the thrown exception, or a RegExp
   *    matching a message of the thrown exception.
   * @param {String} message
   *    Description message
   *
   * @examples
   *
   *    assert.throws(function block() {
   *      doSomething(4)
   *    }, "Object is expected", "Incorrect argument is passed");
   *
   *    assert.throws(function block() {
   *      Object.create(5)
   *    }, TypeError, "TypeError is thrown");
   */
  throws: function throws(block, Error, message) {
    let threw = false;
    let exception = null;

    // If third argument is not provided and second argument is a string it
    // means that optional `Error` argument was not passed, so we shift
    // arguments.
    if (isString(Error) && isUndefined(message)) {
      message = Error;
      Error = undefined;
    }

    // Executing given `block`.
    try {
      block();
    }
    catch (e) {
      threw = true;
      exception = e;
    }

    // If exception was thrown and `Error` argument was not passed assert is
    // passed.
    if (threw && (isUndefined(Error) ||
                 // If passed `Error` is RegExp using it's test method to
                 // assert thrown exception message.
                 (isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) ||
                 // If passed `Error` is a constructor function testing if
                 // thrown exception is an instance of it.
                 (isFunction(Error) && instanceOf(exception, Error))))
    {
      this.pass(message);
      return true;
    }

    // Otherwise we report assertion failure.
    let failure = {
      message: message,
      operator: "matches"
    };

    if (exception) {
      failure.actual = exception.message || exception.toString();
    }

    if (Error) {
      failure.expected = Error.toString();
    }

    this.fail(failure);
    return false;
  }
};
exports.Assert = Assert;

function isDeepEqual(actual, expected) {
  // 7.1. All identical values are equivalent, as determined by ===.
  if (actual === expected) {
    return true;
  }

  // 7.2. If the expected value is a Date object, the actual value is
  // equivalent if it is also a Date object that refers to the same time.
  else if (isDate(actual) && isDate(expected)) {
    return actual.getTime() === expected.getTime();
  }

  // XXX specification bug: this should be specified
  else if (isPrimitive(actual) || isPrimitive(expected)) {
    return expected === actual;
  }

  // 7.3. Other pairs that do not both pass typeof value == "object",
  // equivalence is determined by ==.
  else if (!isObject(actual) && !isObject(expected)) {
    return actual == expected;
  }

  // 7.4. For all other Object pairs, including Array objects, equivalence is
  // determined by having the same number of owned properties (as verified
  // with Object.prototype.hasOwnProperty.call), the same set of keys
  // (although not necessarily the same order), equivalent values for every
  // corresponding key, and an identical "prototype" property. Note: this
  // accounts for both named and indexed properties on Arrays.
  else {
    return actual.prototype === expected.prototype &&
           isEquivalent(actual, expected);
  }
}

function isEquivalent(a, b, stack) {
  let aKeys = Object.keys(a);
  let bKeys = Object.keys(b);

  return aKeys.length === bKeys.length &&
          isArrayEquivalent(aKeys.sort(), bKeys.sort()) &&
          aKeys.every(function(key) {
            return isDeepEqual(a[key], b[key], stack)
          });
}

function isArrayEquivalent(a, b, stack) {
  return isArray(a) && isArray(b) &&
         a.every(function(value, index) {
           return isDeepEqual(value, b[index]);
         });
}
PK
!<gGG$modules/commonjs/sdk/test/harness.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cu } = require("chrome");
const { Loader } = require('./loader');
const { serializeStack, parseStack  } = require("toolkit/loader");
const { setTimeout } = require('../timers');
const { PlainTextConsole } = require("../console/plain-text");
const { when: unload } = require("../system/unload");
lazyRequire(this, "../console/traceback", "format", "fromException");
const system = require("../system");
const { gc: gcPromise } = require('./memory');
const { defer } = require('../core/promise');
const { extend } = require('../core/heritage');

// Trick manifest builder to make it think we need these modules ?
const unit = require("../deprecated/unit-test");
const test = require("../../test");
const url = require("../url");

function emptyPromise() {
  let { promise, resolve } = defer();
  resolve();
  return promise;
}

var cService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService);

// The console used to log messages
var testConsole;

// Cuddlefish loader in which we load and execute tests.
var loader;

// Function to call when we're done running tests.
var onDone;

// Function to print text to a console, w/o CR at the end.
var print;

// How many more times to run all tests.
var iterationsLeft;

// Whether to report memory profiling information.
var profileMemory;

// Whether we should stop as soon as a test reports a failure.
var stopOnError;

// Function to call to retrieve a list of tests to execute
var findAndRunTests;

// Combined information from all test runs.
var results;

// A list of the compartments and windows loaded after startup
var startLeaks;

// JSON serialization of last memory usage stats; we keep it stringified
// so we don't actually change the memory usage stats (in terms of objects)
// of the JSRuntime we're profiling.
var lastMemoryUsage;

function analyzeRawProfilingData(data) {
  var graph = data.graph;
  var shapes = {};

  // Convert keys in the graph from strings to ints.
  // TODO: Can we get rid of this ridiculousness?
  var newGraph = {};
  for (id in graph) {
    newGraph[parseInt(id)] = graph[id];
  }
  graph = newGraph;

  var modules = 0;
  var moduleIds = [];
  var moduleObjs = {UNKNOWN: 0};
  for (let name in data.namedObjects) {
    moduleObjs[name] = 0;
    moduleIds[data.namedObjects[name]] = name;
    modules++;
  }

  var count = 0;
  for (id in graph) {
    var parent = graph[id].parent;
    while (parent) {
      if (parent in moduleIds) {
        var name = moduleIds[parent];
        moduleObjs[name]++;
        break;
      }
      if (!(parent in graph)) {
        moduleObjs.UNKNOWN++;
        break;
      }
      parent = graph[parent].parent;
    }
    count++;
  }

  print("\nobject count is " + count + " in " + modules + " modules" +
        " (" + data.totalObjectCount + " across entire JS runtime)\n");
  if (lastMemoryUsage) {
    var last = JSON.parse(lastMemoryUsage);
    var diff = {
      moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
      totalObjectClasses: dictDiff(last.totalObjectClasses,
                                   data.totalObjectClasses)
    };

    for (let name in diff.moduleObjs)
      print("  " + diff.moduleObjs[name] + " in " + name + "\n");
    for (let name in diff.totalObjectClasses)
      print("  " + diff.totalObjectClasses[name] + " instances of " +
            name + "\n");
  }
  lastMemoryUsage = JSON.stringify(
    {moduleObjs: moduleObjs,
     totalObjectClasses: data.totalObjectClasses}
  );
}

function dictDiff(last, curr) {
  var diff = {};

  for (let name in last) {
    var result = (curr[name] || 0) - last[name];
    if (result)
      diff[name] = (result > 0 ? "+" : "") + result;
  }
  for (let name in curr) {
    var result = curr[name] - (last[name] || 0);
    if (result)
      diff[name] = (result > 0 ? "+" : "") + result;
  }
  return diff;
}

function reportMemoryUsage() {
  if (!profileMemory) {
    return emptyPromise();
  }

  return gcPromise().then((() => {
    var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
              .getService(Ci.nsIMemoryReporterManager);
    let count = 0;
    function logReporter(process, path, kind, units, amount, description) {
      print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n");
    }
    mgr.getReportsForThisProcess(logReporter, null, /* anonymize = */ false);
  }));
}

var gWeakrefInfo;

function checkMemory() {
  return gcPromise().then(_ => {
    let leaks = getPotentialLeaks();

    let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
      return !(url in startLeaks.compartments);
    });

    let windowURLs = Object.keys(leaks.windows).filter(function(url) {
      return !(url in startLeaks.windows);
    });

    for (let url of compartmentURLs)
      console.warn("LEAKED", leaks.compartments[url]);

    for (let url of windowURLs)
      console.warn("LEAKED", leaks.windows[url]);
  }).then(showResults);
}

function showResults() {
  let { promise, resolve } = defer();

  if (gWeakrefInfo) {
    gWeakrefInfo.forEach(
      function(info) {
        var ref = info.weakref.get();
        if (ref !== null) {
          var data = ref.__url__ ? ref.__url__ : ref;
          var warning = data == "[object Object]"
            ? "[object " + data.constructor.name + "(" +
              Object.keys(data).join(", ") + ")]"
            : data;
          console.warn("LEAK", warning, info.bin);
        }
      }
    );
  }

  onDone(results);

  resolve();
  return promise;
}

function cleanup() {
  let coverObject = {};
  try {
    loader.unload();

    if (loader.globals.console.errorsLogged && !results.failed) {
      results.failed++;
      console.error("warnings and/or errors were logged.");
    }

    if (consoleListener.errorsLogged && !results.failed) {
      console.warn(consoleListener.errorsLogged + " " +
                   "warnings or errors were logged to the " +
                   "platform's nsIConsoleService, which could " +
                   "be of no consequence; however, they could also " +
                   "be indicative of aberrant behavior.");
    }

    // read the code coverage object, if it exists, from CoverJS-moz
    if (typeof loader.globals.global == "object") {
      coverObject = loader.globals.global['__$coverObject'] || {};
    }

    consoleListener.errorsLogged = 0;
    loader = null;

    consoleListener.unregister();

    Cu.forceGC();
  }
  catch (e) {
    results.failed++;
    console.error("unload.send() threw an exception.");
    console.exception(e);
  };

  setTimeout(require("./options").checkMemory ? checkMemory : showResults, 1);

  // dump the coverobject
  if (Object.keys(coverObject).length){
    const self = require('sdk/self');
    const {pathFor} = require("sdk/system");
    let file = require('sdk/io/file');
    const {env} = require('sdk/system/environment');
    console.log("CWD:", env.PWD);
    let out = file.join(env.PWD,'coverstats-'+self.id+'.json');
    console.log('coverstats:', out);
    let outfh = file.open(out,'w');
    outfh.write(JSON.stringify(coverObject,null,2));
    outfh.flush();
    outfh.close();
  }
}

function getPotentialLeaks() {
  Cu.forceGC();

  // Things we can assume are part of the platform and so aren't leaks
  let GOOD_BASE_URLS = [
    "chrome://",
    "resource:///",
    "resource://app/",
    "resource://gre/",
    "resource://gre-resources/",
    "resource://pdf.js/",
    "resource://pdf.js.components/",
    "resource://services-common/",
    "resource://services-crypto/",
    "resource://services-sync/"
  ];

  let ioService = Cc["@mozilla.org/network/io-service;1"].
                 getService(Ci.nsIIOService);
  let uri = ioService.newURI("chrome://global/content/", "UTF-8");
  let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
                  getService(Ci.nsIChromeRegistry);
  uri = chromeReg.convertChromeURL(uri);
  let spec = uri.spec;
  let pos = spec.indexOf("!/");
  GOOD_BASE_URLS.push(spec.substring(0, pos + 2));

  let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)");
  let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/");
  let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$");
  let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active");
  let windowDetails = new RegExp("^(.*), id=.*$");

  function isPossibleLeak(item) {
    if (!item.location)
      return false;

    for (let url of GOOD_BASE_URLS) {
      if (item.location.substring(0, url.length) == url) {
        return false;
      }
    }

    return true;
  }

  let compartments = {};
  let windows = {};
  function logReporter(process, path, kind, units, amount, description) {
    let matches;

    if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) {
      if (matches[1] in compartments)
        return;

      let details = compartmentDetails.exec(matches[1]);
      if (!details) {
        console.error("Unable to parse compartment detail " + matches[1]);
        return;
      }

      let item = {
        path: matches[1],
        principal: details[1],
        location: details[2] ? details[2].replace(/\\/g, "/") : undefined,
        source: details[3] ? details[3].split(" -> ").reverse() : undefined,
        toString: function() {
          return this.location;
        }
      };

      if (!isPossibleLeak(item))
        return;

      compartments[matches[1]] = item;
      return;
    }

    if ((matches = windowRegexp.exec(path))) {
      if (matches[1] in windows)
        return;

      let details = windowDetails.exec(matches[1]);
      if (!details) {
        console.error("Unable to parse window detail " + matches[1]);
        return;
      }

      let item = {
        path: matches[1],
        location: details[1].replace(/\\/g, "/"),
        source: [details[1].replace(/\\/g, "/")],
        toString: function() {
          return this.location;
        }
      };

      if (!isPossibleLeak(item))
        return;

      windows[matches[1]] = item;
    }
  }

  Cc["@mozilla.org/memory-reporter-manager;1"]
    .getService(Ci.nsIMemoryReporterManager)
    .getReportsForThisProcess(logReporter, null, /* anonymize = */ false);

  return { compartments: compartments, windows: windows };
}

function nextIteration(tests) {
  if (tests) {
    results.passed += tests.passed;
    results.failed += tests.failed;

    reportMemoryUsage().then(_ => {
      let testRun = [];
      for (let test of tests.testRunSummary) {
        let testCopy = {};
        for (let info in test) {
          testCopy[info] = test[info];
        }
        testRun.push(testCopy);
      }

      results.testRuns.push(testRun);
      iterationsLeft--;

      checkForEnd();
    })
  }
  else {
    checkForEnd();
  }
}

function checkForEnd() {
  if (iterationsLeft && (!stopOnError || results.failed == 0)) {
    // Pass the loader which has a hooked console that doesn't dispatch
    // errors to the JS console and avoid firing false alarm in our
    // console listener
    findAndRunTests(loader, nextIteration);
  }
  else {
    setTimeout(cleanup, 0);
  }
}

var POINTLESS_ERRORS = [
  'Invalid chrome URI:',
  'OpenGL LayerManager Initialized Succesfully.',
  '[JavaScript Error: "TelemetryStopwatch:',
  'reference to undefined property',
  '[JavaScript Error: "The character encoding of the HTML document was ' +
    'not declared.',
  '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
    'native weak map key',
  '[JavaScript Warning: "Duplicate resource declaration for',
  'file: "chrome://browser/content/',
  'file: "chrome://global/content/',
  '[JavaScript Warning: "The character encoding of a framed document was ' +
    'not declared.',
  'file: "chrome://browser/skin/'
];

// These are messages that will cause a test to fail if logged through the
// console service
var IMPORTANT_ERRORS = [
  'Sending message that cannot be cloned. Are you trying to send an XPCOM object?',
];

var consoleListener = {
  registered: false,

  register: function() {
    if (this.registered)
      return;
    cService.registerListener(this);
    this.registered = true;
  },

  unregister: function() {
    if (!this.registered)
      return;
    cService.unregisterListener(this);
    this.registered = false;
  },

  errorsLogged: 0,

  observe: function(object) {
    if (!(object instanceof Ci.nsIScriptError))
      return;
    this.errorsLogged++;
    var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
    if (IMPORTANT_ERRORS.find(msg => message.indexOf(msg) >= 0)) {
      testConsole.error(message);
      return;
    }
    var pointless = POINTLESS_ERRORS.filter(err => message.indexOf(err) >= 0);
    if (pointless.length == 0 && message)
      testConsole.log(message);
  }
};

function TestRunnerConsole(base, options) {
  let proto = extend(base, {
    errorsLogged: 0,
    warn: function warn() {
      this.errorsLogged++;
      base.warn.apply(base, arguments);
    },
    error: function error() {
      this.errorsLogged++;
      base.error.apply(base, arguments);
    },
    info: function info(first) {
      if (options.verbose)
        base.info.apply(base, arguments);
      else
        if (first == "pass:")
          print(".");
    },
  });
  return Object.create(proto);
}

function stringify(arg) {
  try {
    return String(arg);
  }
  catch(ex) {
    return "<toString() error>";
  }
}

function stringifyArgs(args) {
  return Array.map(args, stringify).join(" ");
}

function TestRunnerTinderboxConsole(base, options) {
  this.base = base;
  this.print = options.print;
  this.verbose = options.verbose;
  this.errorsLogged = 0;

  // Binding all the public methods to an instance so that they can be used
  // as callback / listener functions straightaway.
  this.log = this.log.bind(this);
  this.info = this.info.bind(this);
  this.warn = this.warn.bind(this);
  this.error = this.error.bind(this);
  this.debug = this.debug.bind(this);
  this.exception = this.exception.bind(this);
  this.trace = this.trace.bind(this);
};

TestRunnerTinderboxConsole.prototype = {
  testMessage: function testMessage(pass, expected, test, message) {
    let type = "TEST-";
    if (expected) {
      if (pass)
        type += "PASS";
      else
        type += "KNOWN-FAIL";
    }
    else {
      this.errorsLogged++;
      if (pass)
        type += "UNEXPECTED-PASS";
      else
        type += "UNEXPECTED-FAIL";
    }

    this.print(type + " | " + test + " | " + message + "\n");
    if (!expected)
      this.trace();
  },

  log: function log() {
    this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
  },

  info: function info(first) {
    this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
  },

  warn: function warn() {
    this.errorsLogged++;
    this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
  },

  error: function error() {
    this.errorsLogged++;
    this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
    this.base.error.apply(this.base, arguments);
  },

  debug: function debug() {
    this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
  },

  exception: function exception(e) {
    this.print("An exception occurred.\n" +
               require("../console/traceback").format(e) + "\n" + e + "\n");
  },

  trace: function trace() {
    var traceback = require("../console/traceback");
    var stack = traceback.get();
    stack.splice(-1, 1);
    this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n");
  }
};

var runTests = exports.runTests = function runTests(options) {
  iterationsLeft = options.iterations;
  profileMemory = options.profileMemory;
  stopOnError = options.stopOnError;
  onDone = options.onDone;
  print = options.print;
  findAndRunTests = options.findAndRunTests;

  results = {
    passed: 0,
    failed: 0,
    testRuns: []
  };

  try {
    consoleListener.register();
    print("Running tests on " + system.name + " " + system.version +
          "/Gecko " + system.platformVersion + " (Build " +
          system.build + ") (" + system.id + ") under " +
          system.platform + "/" + system.architecture + ".\n");

    if (options.parseable)
      testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options);
    else
      testConsole = new TestRunnerConsole(new PlainTextConsole(), options);

    loader = Loader(module, {
      console: testConsole,
      global: {} // useful for storing things like coverage testing.
    });

    // Load these before getting initial leak stats as they will still be in
    // memory when we check later
    require("../deprecated/unit-test");
    require("../deprecated/unit-test-finder");
    if (profileMemory)
      startLeaks = getPotentialLeaks();

    nextIteration();
  } catch (e) {
    let frames = fromException(e).reverse().reduce(function(frames, frame) {
      if (frame.fileName.split("/").pop() === "unit-test-finder.js")
        frames.done = true
      if (!frames.done) frames.push(frame)

      return frames
    }, [])

    let prototype = typeof(e) === "object" ? e.constructor.prototype :
                    Error.prototype;
    let stack = serializeStack(frames.reverse());

    let error = Object.create(prototype, {
      message: { value: e.message, writable: true, configurable: true },
      fileName: { value: e.fileName, writable: true, configurable: true },
      lineNumber: { value: e.lineNumber, writable: true, configurable: true },
      stack: { value: stack, writable: true, configurable: true },
      toString: { value: () => String(e), writable: true, configurable: true },
    });

    print("Error: " + error + " \n " + format(error));
    onDone({passed: 0, failed: 1});
  }
};

unload(_ => consoleListener.unregister());
PK
!<F[["modules/commonjs/sdk/test/httpd.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

throw new Error(`This file was removed. A copy can be obtained from:
  https://github.com/mozilla/addon-sdk/blob/master/test/lib/httpd.js`);
PK
!<#modules/commonjs/sdk/test/loader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { resolveURI, Require,
        unload, override, descriptor } = require('../../toolkit/loader');
const { ensure } = require('../system/unload');
const addonWindow = require('../addon/window');
const { PlainTextConsole } = require('sdk/console/plain-text');

var defaultGlobals = override(require('../system/globals'), {
  console: console
});

function CustomLoader(module, globals, packaging, overrides={}) {
  let options = packaging || require("@loader/options");
  options = override(options, {
    id: overrides.id || options.id,
    globals: override(defaultGlobals, globals || {}),
    modules: override(override(options.modules || {}, overrides.modules || {}), {
      'sdk/addon/window': addonWindow
    })
  });

  let loaderModule = options.isNative ? '../../toolkit/loader' : '../loader/cuddlefish';
  let { Loader } = require(loaderModule);
  let loader = Loader(options);
  let wrapper = Object.create(loader, descriptor({
    require: Require(loader, module),
    sandbox: function(id) {
      let requirement = loader.resolve(id, module.id);
      if (!requirement)
        requirement = id;
      let uri = resolveURI(requirement, loader.mapping);
      return loader.sandboxes[uri];
    },
    unload: function(reason) {
      unload(loader, reason);
    }
  }));
  ensure(wrapper);
  return wrapper;
};
exports.Loader = CustomLoader;

function HookedPlainTextConsole(hook, print, innerID) {
  this.log = hook.bind(null, "log", innerID);
  this.info = hook.bind(null, "info", innerID);
  this.warn = hook.bind(null, "warn", innerID);
  this.error = hook.bind(null, "error", innerID);
  this.debug = hook.bind(null, "debug", innerID);
  this.exception = hook.bind(null, "exception", innerID);
  this.time = hook.bind(null, "time", innerID);
  this.timeEnd = hook.bind(null, "timeEnd", innerID);

  this.__exposedProps__ = {
    log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
    exception: "rw", time: "rw", timeEnd: "rw"
  };
}

// Creates a custom loader instance whose console module is hooked in order
// to avoid printing messages to the console, and instead, expose them in the
// returned `messages` array attribute
exports.LoaderWithHookedConsole = function (module, callback) {
  let messages = [];
  function hook(type, innerID, msg) {
    messages.push({ type: type, msg: msg, innerID: innerID });
    if (callback)
      callback(type, msg, innerID);
  }

  return {
    loader: CustomLoader(module, {
      console: new HookedPlainTextConsole(hook, null, null)
    }, null, {
      modules: {
        'sdk/console/plain-text': {
          PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
        }
      }
    }),
    messages: messages
  };
}

// Same than LoaderWithHookedConsole with lower level, instead we get what is
// actually printed to the command line console
exports.LoaderWithHookedConsole2 = function (module, callback) {
  let messages = [];
  return {
    loader: CustomLoader(module, {
      console: new PlainTextConsole(function (msg) {
        messages.push(msg);
        if (callback)
          callback(msg);
      })
    }),
    messages: messages
  };
}

// Creates a custom loader with a filtered console. The callback is passed every
// console message type and message and if it returns false the message will
// not be logged normally
exports.LoaderWithFilteredConsole = function (module, callback) {
  function hook(type, innerID, msg) {
    if (callback && callback(type, msg, innerID) == false)
      return;
    console[type](msg);
  }

  return CustomLoader(module, {
    console: new HookedPlainTextConsole(hook, null, null)
  }, null, {
    modules: {
      'sdk/console/plain-text': {
        PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
      }
    }
  });
}
PK
!<fDbb#modules/commonjs/sdk/test/memory.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Cu } = require("chrome");

function gc() {
  return new Promise(resolve => Cu.schedulePreciseGC(resolve));
}
exports.gc = gc;
PK
!<qI=		$modules/commonjs/sdk/test/options.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const options = require("@test/options");
const { id } = require("../self");
const { get } = require("../preferences/service");

const readPref = (key) => get("extensions." + id + ".sdk." + key);

exports.iterations = readPref("test.iterations") || options.iterations;
exports.filter = readPref("test.filter") || options.filter;
exports.profileMemory = readPref("profile.memory") || options.profileMemory;
exports.stopOnError = readPref("test.stop") || options.stopOnError;
exports.keepOpen = readPref("test.keepOpen") || false;
exports.verbose = (readPref("output.logLevel") == "verbose") || options.verbose;
exports.parseable = (readPref("output.format") == "tbpl") || options.parseable;
exports.checkMemory = readPref("profile.leaks") || options.check_memory;
PK
!<,П#modules/commonjs/sdk/test/runner.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

var { exit, stdout } = require("../system");
var cfxArgs = require("../test/options");
var events = require("../system/events");
const { resolve } = require("../core/promise");

function runTests(findAndRunTests) {
  var harness = require("./harness");

  function onDone(tests) {
    stdout.write("\n");
    var total = tests.passed + tests.failed;
    stdout.write(tests.passed + " of " + total + " tests passed.\n");

    events.emit("sdk:test:results", { data: JSON.stringify(tests) });

    if (tests.failed == 0) {
      if (tests.passed === 0)
        stdout.write("No tests were run\n");
      if (!cfxArgs.keepOpen)
        exit(0);
    } else {
      if (cfxArgs.verbose || cfxArgs.parseable)
        printFailedTests(tests, stdout.write);
      if (!cfxArgs.keepOpen)
        exit(1);
    }
  };

  // We may have to run test on next cycle, otherwise XPCOM components
  // are not correctly updated.
  // For ex: nsIFocusManager.getFocusedElementForWindow may throw
  // NS_ERROR_ILLEGAL_VALUE exception.
  require("../timers").setTimeout(_ => harness.runTests({
    findAndRunTests: findAndRunTests,
    iterations: cfxArgs.iterations || 1,
    filter: cfxArgs.filter,
    profileMemory: cfxArgs.profileMemory,
    stopOnError: cfxArgs.stopOnError,
    verbose: cfxArgs.verbose,
    parseable: cfxArgs.parseable,
    print: stdout.write,
    onDone: onDone
  }));
}

function printFailedTests(tests, print) {
  let iterationNumber = 0;
  let singleIteration = (tests.testRuns || []).length == 1;
  let padding = singleIteration ? "" : "  ";

  print("\nThe following tests failed:\n");

  for (let testRun of tests.testRuns) {
    iterationNumber++;

    if (!singleIteration)
      print("  Iteration " + iterationNumber + ":\n");

    for (let test of testRun) {
      if (test.failed > 0) {
        print(padding + "  " + test.name + ": " + test.errors +"\n");
      }
    }
    print("\n");
  }
}

function main() {
  var testsStarted = false;

  if (!testsStarted) {
    testsStarted = true;
    runTests(function findAndRunTests(loader, nextIteration) {
      loader.require("../deprecated/unit-test").findAndRunTests({
        testOutOfProcess: false,
        testInProcess: true,
        stopOnError: cfxArgs.stopOnError,
        filter: cfxArgs.filter,
        onDone: nextIteration
      });
    });
  }
};

if (require.main === module)
  main();

exports.runTestsFromModule = function runTestsFromModule(module) {
  let id = module.id;
  // Make a copy of exports as it may already be frozen by module loader
  let exports = {};
  Object.keys(module.exports).forEach(key => {
    exports[key] = module.exports[key];
  });

  runTests(function findAndRunTests(loader, nextIteration) {
    // Consider that all these tests are CommonJS ones
    loader.require('../../test').run(exports);

    // Reproduce what is done in sdk/deprecated/unit-test-finder.findTests()
    let tests = [];
    for (let name of Object.keys(exports).sort()) {
      tests.push({
        setup: exports.setup,
        teardown: exports.teardown,
        testFunction: exports[name],
        name: id + "." + name
      });
    }

    // Reproduce what is done by unit-test.findAndRunTests()
    var { TestRunner } = loader.require("../deprecated/unit-test");
    var runner = new TestRunner();
    runner.startMany({
      tests: {
        getNext: () => resolve(tests.shift())
      },
      stopOnError: cfxArgs.stopOnError,
      onDone: nextIteration
    });
  });
}
PK
!<*bv,,"modules/commonjs/sdk/test/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

lazyRequire(this, '../core/promise', "defer");
lazyRequire(this, '../timers', "setInterval", "clearInterval");
lazyRequire(this, "../tabs/utils", "getTabs", "closeTab");
lazyRequire(this, "../window/utils", {"windows": "getWindows"});
lazyRequire(this, "../window/helpers", {"close": "closeWindow"});
lazyRequire(this, "../lang/type", "isGenerator");
lazyRequire(this, "../system/environment", "env");
const { Task } = require("resource://gre/modules/Task.jsm");

const getTestNames = (exports) =>
  Object.keys(exports).filter(name => /^test/.test(name));

const isTestAsync = ({length}) => length > 1;
const isHelperAsync = ({length}) => length > 2;

/*
 * Takes an `exports` object of a test file and a function `beforeFn`
 * to be run before each test. `beforeFn` is called with a `name` string
 * as the first argument of the test name, and may specify a second
 * argument function `done` to indicate that this function should
 * resolve asynchronously
 */
function before (exports, beforeFn) {
  getTestNames(exports).map(name => {
    let testFn = exports[name];

    // GENERATOR TESTS
    if (isGenerator(testFn) && isGenerator(beforeFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(beforeFn.bind(null, name, assert));
        yield Task.spawn(testFn.bind(null, assert));
      }
    }
    else if (isGenerator(testFn) && !isHelperAsync(beforeFn)) {
      exports[name] = function*(assert) {
        beforeFn(name, assert);
        yield Task.spawn(testFn.bind(null, assert));
      }
    }
    else if (isGenerator(testFn) && isHelperAsync(beforeFn)) {
      exports[name] = function*(assert) {
        yield new Promise(resolve => beforeFn(name, assert, resolve));
        yield Task.spawn(testFn.bind(null, assert));
      }
    }
    // SYNC TESTS
    else if (!isTestAsync(testFn) && isGenerator(beforeFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(beforeFn.bind(null, name, assert));
        testFn(assert);
      };
    }
    else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
      exports[name] = function (assert) {
        beforeFn(name, assert);
        testFn(assert);
      };
    }
    else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
      exports[name] = function (assert, done) {
        beforeFn(name, assert, () => {
          testFn(assert);
          done();
        });
      };
    }
    // ASYNC TESTS
    else if (isTestAsync(testFn) && isGenerator(beforeFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(beforeFn.bind(null, name, assert));
        yield new Promise(resolve => testFn(assert, resolve));
      };
    }
    else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
      exports[name] = function (assert, done) {
        beforeFn(name, assert);
        testFn(assert, done);
      };
    }
    else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
      exports[name] = function (assert, done) {
        beforeFn(name, assert, () => {
          testFn(assert, done);
        });
      };
    }
  });
}
exports.before = before;

/*
 * Takes an `exports` object of a test file and a function `afterFn`
 * to be run after each test. `afterFn` is called with a `name` string
 * as the first argument of the test name, and may specify a second
 * argument function `done` to indicate that this function should
 * resolve asynchronously
 */
function after (exports, afterFn) {
  getTestNames(exports).map(name => {
    let testFn = exports[name];

    // GENERATOR TESTS
    if (isGenerator(testFn) && isGenerator(afterFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(testFn.bind(null, assert));
        yield Task.spawn(afterFn.bind(null, name, assert));
      }
    }
    else if (isGenerator(testFn) && !isHelperAsync(afterFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(testFn.bind(null, assert));
        afterFn(name, assert);
      }
    }
    else if (isGenerator(testFn) && isHelperAsync(afterFn)) {
      exports[name] = function*(assert) {
        yield Task.spawn(testFn.bind(null, assert));
        yield new Promise(resolve => afterFn(name, assert, resolve));
      }
    }
    // SYNC TESTS
    else if (!isTestAsync(testFn) && isGenerator(afterFn)) {
      exports[name] = function*(assert) {
        testFn(assert);
        yield Task.spawn(afterFn.bind(null, name, assert));
      };
    }
    else if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
      exports[name] = function (assert) {
        testFn(assert);
        afterFn(name, assert);
      };
    }
    else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) {
      exports[name] = function (assert, done) {
        testFn(assert);
        afterFn(name, assert, done);
      };
    }
    // ASYNC TESTS
    else if (isTestAsync(testFn) && isGenerator(afterFn)) {
      exports[name] = function*(assert) {
        yield new Promise(resolve => testFn(assert, resolve));
        yield Task.spawn(afterFn.bind(null, name, assert));
      };
    }
    else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
      exports[name] = function*(assert) {
        yield new Promise(resolve => testFn(assert, resolve));
        afterFn(name, assert);
      };
    }
    else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
      exports[name] = function*(assert) {
        yield new Promise(resolve => testFn(assert, resolve));
        yield new Promise(resolve => afterFn(name, assert, resolve));
      };
    }
  });
}
exports.after = after;

function waitUntil (predicate, delay) {
  let { promise, resolve } = defer();
  let interval = setInterval(() => {
    if (!predicate()) return;
    clearInterval(interval);
    resolve();
  }, delay || 10);
  return promise;
}
exports.waitUntil = waitUntil;

var cleanUI = function cleanUI() {
  let { promise, resolve } = defer();

  let windows = getWindows(null, { includePrivate: true });
  if (windows.length > 1) {
    return closeWindow(windows[1]).then(cleanUI);
  }

  getTabs(windows[0]).slice(1).forEach(closeTab);

  resolve();

  return promise;
}
exports.cleanUI = cleanUI;

exports.isTravisCI = ("TRAVIS" in env && "CI" in env);
PK
!<`0ttmodules/commonjs/sdk/test.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cu } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm", {});
const { defer } = require("sdk/core/promise");
const BaseAssert = require("sdk/test/assert").Assert;
const { isFunction, isObject, isGenerator } = require("sdk/lang/type");
const { extend } = require("sdk/util/object");

exports.Assert = BaseAssert;

/**
 * Function takes test `suite` object in CommonJS format and defines all of the
 * tests from that suite and nested suites in a jetpack format on a given
 * `target` object. Optionally third argument `prefix` can be passed to prefix
 * all the test names.
 */
function defineTestSuite(target, suite, prefix) {
  prefix = prefix || "";
  // If suite defines `Assert` that's what `assert` object have to be created
  // from and passed to a test function (This allows custom assertion functions)
  // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
  let Assert = suite.Assert || BaseAssert;
  // Going through each item in the test suite and wrapping it into a
  // Jetpack test format.
  Object.keys(suite).forEach(function(key) {
     // If name starts with test then it's a test function or suite.
    if (key.indexOf("test") === 0) {
      let test = suite[key];

      // For each test function so we create a wrapper test function in a
      // jetpack format and copy that to a `target` exports.
      if (isFunction(test)) {

        // Since names of the test may match across suites we use full object
        // path as a name to avoid overriding same function.
        target[prefix + key] = function(options) {

          // Creating `assert` functions for this test.
          let assert = Assert(options);
          assert.end = () => options.done();

          // If test function is a generator use a task JS to allow yield-ing
          // style test runs.
          if (isGenerator(test)) {
            options.waitUntilDone();
            Task.spawn(test.bind(null, assert)).
                catch(assert.fail).
                then(assert.end);
          }

          // If CommonJS test function expects more than one argument
          // it means that test is async and second argument is a callback
          // to notify that test is finished.
          else if (1 < test.length) {
            // Letting test runner know that test is executed async and
            // creating a callback function that CommonJS tests will call
            // once it's done.
            options.waitUntilDone();
            test(assert, function() {
              options.done();
            });
          }

          // Otherwise CommonJS test is synchronous so we call it only with
          // one argument.
          else {
            test(assert);
          }
        }
      }

      // If it's an object then it's a test suite containing test function
      // and / or nested test suites. In that case we just extend prefix used
      // and call this function to copy and wrap tests from nested suite.
      else if (isObject(test)) {
        // We need to clone `tests` instead of modifying it, since it's very
        // likely that it is frozen (usually test suites imported modules).
        test = extend(Object.prototype, test, {
          Assert: test.Assert || Assert
        });
        defineTestSuite(target, test, prefix + key + ".");
      }
    }
  });
}

/**
 * This function is a CommonJS test runner function, but since Jetpack test
 * runner and test format is different from CommonJS this function shims given
 * `exports` with all its tests into a Jetpack test format so that the built-in
 * test runner will be able to run CommonJS test without manual changes.
 */
exports.run = function run(exports) {
  // We can't leave old properties on exports since those are test in a CommonJS
  // format that why we move everything to a new `suite` object.
  let suite = {};
  Object.keys(exports).forEach(function(key) {
    suite[key] = exports[key];
    delete exports[key];
  });

  // Now we wrap all the CommonJS tests to a Jetpack format and define
  // those to a given `exports` object since that where jetpack test runner
  // will look for them.
  defineTestSuite(exports, suite);
};
PK
!<*NNmodules/commonjs/sdk/timers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "stable"
};

const { CC, Cc, Ci } = require("chrome");
const { when: unload } = require("./system/unload");

const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
const Timer = CC("@mozilla.org/timer;1", "nsITimer");
const timers = Object.create(null);
const threadManager = Cc["@mozilla.org/thread-manager;1"].
                      getService(Ci.nsIThreadManager);
const prefBranch = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefService).
                    QueryInterface(Ci.nsIPrefBranch);

var MIN_DELAY = 4;
// Try to get min timeout delay used by browser.
try { MIN_DELAY = prefBranch.getIntPref("dom.min_timeout_value"); } finally {}


// Last timer id.
var lastID = 0;

// Sets typer either by timeout or by interval
// depending on a given type.
function setTimer(type, callback, delay, ...args) {
  let id = ++ lastID;
  let timer = timers[id] = Timer();
  timer.initWithCallback({
    notify: function notify() {
      try {
        if (type === TYPE_ONE_SHOT)
          delete timers[id];
        callback.apply(null, args);
      }
      catch(error) {
        console.exception(error);
      }
    }
  }, Math.max(delay || MIN_DELAY), type);
  return id;
}

function unsetTimer(id) {
  let timer = timers[id];
  delete timers[id];
  if (timer) timer.cancel();
}

var immediates = new Map();

var dispatcher = _ => {
  // Allow scheduling of a new dispatch loop.
  dispatcher.scheduled = false;
  // Take a snapshot of timer `id`'s that have being present before
  // starting a dispatch loop, in order to ignore timers registered
  // in side effect to dispatch while also skipping immediates that
  // were removed in side effect.
  let ids = [...immediates.keys()];
  for (let id of ids) {
    let immediate = immediates.get(id);
    if (immediate) {
      immediates.delete(id);
      try { immediate(); }
      catch (error) { console.exception(error); }
    }
  }
}

function setImmediate(callback, ...params) {
  let id = ++ lastID;
  // register new immediate timer with curried params.
  immediates.set(id, _ => callback.apply(callback, params));
  // if dispatch loop is not scheduled schedule one. Own scheduler
  if (!dispatcher.scheduled) {
    dispatcher.scheduled = true;
    threadManager.dispatchToMainThread(dispatcher);
  }
  return id;
}

function clearImmediate(id) {
  immediates.delete(id);
}

// Bind timers so that toString-ing them looks same as on native timers.
exports.setImmediate = setImmediate.bind(null);
exports.clearImmediate = clearImmediate.bind(null);
exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT);
exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK);
exports.clearTimeout = unsetTimer.bind(null);
exports.clearInterval = unsetTimer.bind(null);

// all timers are cleared out on unload.
unload(function() {
  immediates.clear();
  Object.keys(timers).forEach(unsetTimer)
});
PK
!<DqБ(modules/commonjs/sdk/ui/button/action.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '> 28'
  }
};

const { Class } = require('../../core/heritage');
const { merge } = require('../../util/object');
const { Disposable } = require('../../core/disposable');
lazyRequire(this, '../../event/core', "on", "off", "emit", "setListeners");
const { EventTarget } = require('../../event/target');
lazyRequire(this, '../../view/core', "getNodeView");

lazyRequireModule(this, './view', "view");
const { buttonContract, stateContract } = require('./contract');
lazyRequire(this, '../state', "properties", "render", "state", "register",
            "unregister", "getDerivedStateFor");
lazyRequire(this, '../state/events', { "events": "stateEvents" });
lazyRequire(this, './view/events', { "events": "viewEvents" });
lazyRequireModule(this, '../../event/utils', "events");

lazyRequire(this, '../../tabs/utils', "getActiveTab");

lazyRequire(this, '../../self', { "id": "addonID" });
lazyRequire(this, '../id', "identify");

const buttons = new Map();

const toWidgetId = id =>
  ('action-button--' + addonID.toLowerCase()+ '-' + id).
    replace(/[^a-z0-9_-]/g, '');

const ActionButton = Class({
  extends: EventTarget,
  implements: [
    properties(stateContract),
    state(stateContract),
    Disposable
  ],
  setup: function setup(options) {
    let state = merge({
      disabled: false
    }, buttonContract(options));

    let id = toWidgetId(options.id);

    register(this, state);

    // Setup listeners.
    setListeners(this, options);

    buttons.set(id, this);

    view.create(merge({}, state, { id: id }));
  },

  dispose: function dispose() {
    let id = toWidgetId(this.id);
    buttons.delete(id);

    off(this);

    view.dispose(id);

    unregister(this);
  },

  get id() {
    return this.state().id;
  },

  click: function click() { view.click(toWidgetId(this.id)) }
});
exports.ActionButton = ActionButton;

identify.define(ActionButton, ({id}) => toWidgetId(id));

getNodeView.define(ActionButton, button =>
  view.nodeFor(toWidgetId(button.id))
);

var actionButtonStateEvents = events.filter(stateEvents,
  e => e.target instanceof ActionButton);

var actionButtonViewEvents = events.filter(viewEvents,
  e => buttons.has(e.target));

var clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click');
var updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');

on(clickEvents, 'data', ({target: id, window}) => {
  let button = buttons.get(id);
  let state = getDerivedStateFor(button, getActiveTab(window));

  emit(button, 'click', state);
});

on(updateEvents, 'data', ({target: id, window}) => {
  render(buttons.get(id), window);
});

on(actionButtonStateEvents, 'data', ({target, window, state}) => {
  let id = toWidgetId(target.id);
  view.setIcon(id, window, state.icon);
  view.setLabel(id, window, state.label);
  view.setDisabled(id, window, state.disabled);
  view.setBadge(id, window, state.badge, state.badgeColor);
});
PK
!<ɕ*modules/commonjs/sdk/ui/button/contract.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { contract } = require('../../util/contract');
lazyRequire(this, '../../url', "isLocalURL");
lazyRequire(this, '../../lang/type', "isNil", "isObject", "isString");
const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils');
const { merge } = require('../../util/object');
const { freeze } = Object;

const isIconSet = (icons) =>
  Object.keys(icons).
    every(size => String(size >>> 0) === size && isLocalURL(icons[size]));

var iconSet = {
  is: either(object, string),
  map: v => isObject(v) ? freeze(merge({}, v)) : v,
  ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
  msg: 'The option "icon" must be a local URL or an object with ' +
    'numeric keys / local URL values pair.'
}

var id = {
  is: string,
  ok: v => /^[a-z-_][a-z0-9-_]*$/i.test(v),
  msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
        'underscores are allowed).'
};

var label = {
  is: string,
  ok: v => isNil(v) || v.trim().length > 0,
  msg: 'The option "label" must be a non empty string'
}

var badge = {
  is: either(string, number),
  msg: 'The option "badge" must be a string or a number'
}

var badgeColor = {
  is: string,
  msg: 'The option "badgeColor" must be a string'
}

var stateContract = contract({
  label: label,
  icon: iconSet,
  disabled: boolean,
  badge: badge,
  badgeColor: badgeColor
});

exports.stateContract = stateContract;

var buttonContract = contract(merge({}, stateContract.rules, {
  id: required(id),
  label: required(label),
  icon: required(iconSet)
}));

exports.buttonContract = buttonContract;

exports.toggleStateContract = contract(merge({
  checked: boolean
}, stateContract.rules));

exports.toggleButtonContract = contract(merge({
  checked: boolean
}, buttonContract.rules));

PK
!<,e(modules/commonjs/sdk/ui/button/toggle.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '> 28'
  }
};

const { Class } = require('../../core/heritage');
lazyRequire(this, '../../util/object', "merge");
const { Disposable } = require('../../core/disposable');
lazyRequire(this, '../../event/core', "on", "off", "emit", "setListeners");
const { EventTarget } = require('../../event/target');
lazyRequire(this, '../../view/core', "getNodeView");

lazyRequireModule(this, "./view", "view");
const { toggleButtonContract, toggleStateContract } = require('./contract');
lazyRequire(this, '../state', "properties", "render", "state", "register", "unregister",
            "setStateFor", "getStateFor", "getDerivedStateFor");
lazyRequire(this, '../state/events', { "events": "stateEvents" });
lazyRequire(this, './view/events', { "events": "viewEvents" });
lazyRequireModule(this, '../../event/utils', "events");

lazyRequire(this, '../../tabs/utils', "getActiveTab");

lazyRequire(this, '../../self', { "id": "addonID" });
lazyRequire(this, '../id', "identify");

const buttons = new Map();

const toWidgetId = id =>
  ('toggle-button--' + addonID.toLowerCase()+ '-' + id).
    replace(/[^a-z0-9_-]/g, '');

const ToggleButton = Class({
  extends: EventTarget,
  implements: [
    properties(toggleStateContract),
    state(toggleStateContract),
    Disposable
  ],
  setup: function setup(options) {
    let state = merge({
      disabled: false,
      checked: false
    }, toggleButtonContract(options));

    let id = toWidgetId(options.id);

    register(this, state);

    // Setup listeners.
    setListeners(this, options);

    buttons.set(id, this);

    view.create(merge({ type: 'checkbox' }, state, { id: id }));
  },

  dispose: function dispose() {
    let id = toWidgetId(this.id);
    buttons.delete(id);

    off(this);

    view.dispose(id);

    unregister(this);
  },

  get id() {
    return this.state().id;
  },

  click: function click() {
    return view.click(toWidgetId(this.id));
  }
});
exports.ToggleButton = ToggleButton;

identify.define(ToggleButton, ({id}) => toWidgetId(id));

getNodeView.define(ToggleButton, button =>
  view.nodeFor(toWidgetId(button.id))
);

var toggleButtonStateEvents = events.filter(stateEvents,
  e => e.target instanceof ToggleButton);

var toggleButtonViewEvents = events.filter(viewEvents,
  e => buttons.has(e.target));

var clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click');
var updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');

on(toggleButtonStateEvents, 'data', ({target, window, state}) => {
  let id = toWidgetId(target.id);

  view.setIcon(id, window, state.icon);
  view.setLabel(id, window, state.label);
  view.setDisabled(id, window, state.disabled);
  view.setChecked(id, window, state.checked);
  view.setBadge(id, window, state.badge, state.badgeColor);
});

on(clickEvents, 'data', ({target: id, window, checked }) => {
  let button = buttons.get(id);
  let windowState = getStateFor(button, window);

  let newWindowState = merge({}, windowState, { checked: checked });

  setStateFor(button, window, newWindowState);

  let state = getDerivedStateFor(button, getActiveTab(window));

  emit(button, 'click', state);

  emit(button, 'change', state);
});

on(updateEvents, 'data', ({target: id, window}) => {
  render(buttons.get(id), window);
});
PK
!<?-modules/commonjs/sdk/ui/button/view/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '*',
    'SeaMonkey': '*',
    'Thunderbird': '*'
  }
};

var channel = {};

exports.events = channel;
PK
!<{ww&modules/commonjs/sdk/ui/button/view.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '> 28'
  }
};

const { Cu } = require('chrome');
lazyRequire(this, '../../event/core', "on", "off", "emit");

lazyRequire(this, 'sdk/self', "data");

lazyRequire(this, '../../lang/type', "isObject", "isNil");

lazyRequire(this, '../../window/utils', "getMostRecentBrowserWindow");
lazyRequire(this, '../../private-browsing/utils', "ignoreWindow");
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;

lazyRequire(this, './view/events', { "events": "viewEvents" });

const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';

const views = new Map();
const customizedWindows = new WeakMap();

const buttonListener = {
  onCustomizeStart: window => {
    for (let [id, view] of views) {
      setIcon(id, window, view.icon);
      setLabel(id, window, view.label);
    }

    customizedWindows.set(window, true);
  },
  onCustomizeEnd: window => {
    customizedWindows.delete(window);

    for (let [id, ] of views) {
      let placement = CustomizableUI.getPlacementOfWidget(id);

      if (placement)
        emit(viewEvents, 'data', { type: 'update', target: id, window: window });
    }
  },
  onWidgetAfterDOMChange: (node, nextNode, container) => {
    let { id } = node;
    let view = views.get(id);
    let window = node.ownerGlobal;

    if (view) {
      emit(viewEvents, 'data', { type: 'update', target: id, window: window });
    }
  }
};

CustomizableUI.addListener(buttonListener);

require('../../system/unload').when( _ =>
  CustomizableUI.removeListener(buttonListener)
);

function getNode(id, window) {
  let view = views.get(id);
  return view && view.nodes.get(window);
};

function isInToolbar(id) {
  let placement = CustomizableUI.getPlacementOfWidget(id);

  return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
}


function getImage(icon, isInToolbar, pixelRatio) {
  let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
  let bestSize = 0;
  let image = icon;

  if (isObject(icon)) {
    for (let size of Object.keys(icon)) {
      size = +size;
      let offset = targetSize - size;

      if (offset === 0) {
        bestSize = size;
        break;
      }

      let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);

      if (delta < 0)
        bestSize = size;
    }

    image = icon[bestSize];
  }

  if (image.indexOf('./') === 0)
    return data.url(image.substr(2));

  return image;
}

function nodeFor(id, window=getMostRecentBrowserWindow()) {
  return customizedWindows.has(window) ? null : getNode(id, window);
};
exports.nodeFor = nodeFor;

function create(options) {
  let { id, label, icon, type, badge } = options;

  if (views.has(id))
    throw new Error('The ID "' + id + '" seems already used.');

  CustomizableUI.createWidget({
    id: id,
    type: 'custom',
    removable: true,
    defaultArea: AREA_NAVBAR,
    allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],

    onBuild: function(document) {
      let window = document.defaultView;

      let node = document.createElementNS(XUL_NS, 'toolbarbutton');

      let image = getImage(icon, true, window.devicePixelRatio);

      node.setAttribute('id', this.id);
      node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
      node.setAttribute('type', type);
      node.setAttribute('label', label);
      node.setAttribute('tooltiptext', label);
      node.setAttribute('image', image);
      node.setAttribute('constrain-size', 'true');

      if (!views.get(id)) {
        views.set(id, {
          nodes: new WeakMap(),
        });
      }

      let view = views.get(id);
      Object.assign(view, {
        area: this.currentArea,
        icon: icon,
        label: label
      });

      if (ignoreWindow(window))
        node.style.display = 'none';
      else
        view.nodes.set(window, node);

      node.addEventListener('command', function(event) {
        if (views.has(id)) {
          emit(viewEvents, 'data', {
            type: 'click',
            target: id,
            window: event.view,
            checked: node.checked
          });
        }
      });

      return node;
    }
  });
};
exports.create = create;

function dispose(id) {
  if (!views.has(id)) return;

  views.delete(id);
  CustomizableUI.destroyWidget(id);
}
exports.dispose = dispose;

function setIcon(id, window, icon) {
  let node = getNode(id, window);

  if (node) {
    icon = customizedWindows.has(window) ? views.get(id).icon : icon;
    let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);

    node.setAttribute('image', image);
  }
}
exports.setIcon = setIcon;

function setLabel(id, window, label) {
  let node = nodeFor(id, window);

  if (node) {
    node.setAttribute('label', label);
    node.setAttribute('tooltiptext', label);
  }
}
exports.setLabel = setLabel;

function setDisabled(id, window, disabled) {
  let node = nodeFor(id, window);

  if (node)
    node.disabled = disabled;
}
exports.setDisabled = setDisabled;

function setChecked(id, window, checked) {
  let node = nodeFor(id, window);

  if (node)
    node.checked = checked;
}
exports.setChecked = setChecked;

function setBadge(id, window, badge, color) {
  let node = nodeFor(id, window);

  if (node) {
    // `Array.from` is needed to handle unicode symbol properly:
    // '𝐀𝐁'.length is 4 where Array.from('𝐀𝐁').length is 2
    let text = badge == null
                  ? ''
                  : Array.from(String(badge)).slice(0, 4).join('');

    node.setAttribute('badge', text);

    let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
                                        'class', 'toolbarbutton-badge');

    if (badgeNode)
      badgeNode.style.backgroundColor = color == null ? '' : color;
  }
}
exports.setBadge = setBadge;

function click(id) {
  let node = nodeFor(id);

  if (node)
    node.click();
}
exports.click = click;
PK
!<_j;;$modules/commonjs/sdk/ui/component.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// Internal properties not exposed to the public.
const cache = Symbol("component/cache");
const writer = Symbol("component/writer");
const isFirstWrite = Symbol("component/writer/first-write?");
const currentState = Symbol("component/state/current");
const pendingState = Symbol("component/state/pending");
const isWriting = Symbol("component/writing?");

const isntNull = x => x !== null;

const Component = function(options, children) {
  this[currentState] = null;
  this[pendingState] = null;
  this[writer] = null;
  this[cache] = null;
  this[isFirstWrite] = true;

  this[Component.construct](options, children);
}
Component.Component = Component;
// Constructs component.
Component.construct = Symbol("component/construct");
// Called with `options` and `children` and must return
// initial state back.
Component.initial = Symbol("component/initial");

// Function patches current `state` with a given update.
Component.patch = Symbol("component/patch");
// Function that replaces current `state` with a passed state.
Component.reset = Symbol("component/reset");

// Function that must return render tree from passed state.
Component.render = Symbol("component/render");

// Path of the component with in the mount point.
Component.path = Symbol("component/path");

Component.isMounted = component => !!component[writer];
Component.isWriting = component => !!component[isWriting];

// Internal method that mounts component to a writer.
// Mounts component to a writer.
Component.mount = (component, write) => {
  if (Component.isMounted(component)) {
    throw Error("Can not mount already mounted component");
  }

  component[writer] = write;
  Component.write(component);

  if (component[Component.mounted]) {
    component[Component.mounted]();
  }
}

// Unmounts component from a writer.
Component.unmount = (component) => {
  if (Component.isMounted(component)) {
    component[writer] = null;
    if (component[Component.unmounted]) {
      component[Component.unmounted]();
    }
  } else {
    console.warn("Unmounting component that is not mounted is redundant");
  }
};
 // Method invoked once after inital write occurs.
Component.mounted = Symbol("component/mounted");
// Internal method that unmounts component from the writer.
Component.unmounted = Symbol("component/unmounted");
// Function that must return true if component is changed
Component.isUpdated = Symbol("component/updated?");
Component.update = Symbol("component/update");
Component.updated = Symbol("component/updated");

const writeChild = base => (child, index) => Component.write(child, base, index)
Component.write = (component, base, index) => {
  if (component === null) {
    return component;
  }

  if (!(component instanceof Component)) {
    const path = base ? `${base}${component.key || index}/` : `/`;
    return Object.assign({}, component, {
      [Component.path]: path,
      children: component.children && component.children.
                                        map(writeChild(path)).
                                        filter(isntNull)
    });
  }

  component[isWriting] = true;

  try {

    const current = component[currentState];
    const pending = component[pendingState] || current;
    const isUpdated = component[Component.isUpdated];
    const isInitial = component[isFirstWrite];

    if (isUpdated(current, pending) || isInitial) {
      if (!isInitial && component[Component.update]) {
        component[Component.update](pending, current)
      }

      // Note: [Component.update] could have caused more updates so can't use
      // `pending` as `component[pendingState]` may have changed.
      component[currentState] = component[pendingState] || current;
      component[pendingState] = null;

      const tree = component[Component.render](component[currentState]);
      component[cache] = Component.write(tree, base, index);
      if (component[writer]) {
        component[writer].call(null, component[cache]);
      }

      if (!isInitial && component[Component.updated]) {
        component[Component.updated](current, pending);
      }
    }

    component[isFirstWrite] = false;

    return component[cache];
  } finally {
    component[isWriting] = false;
  }
};

Component.prototype = Object.freeze({
  constructor: Component,

  [Component.mounted]: null,
  [Component.unmounted]: null,
  [Component.update]: null,
  [Component.updated]: null,

  get state() {
    return this[pendingState] || this[currentState];
  },


  [Component.construct](settings, items) {
    const initial = this[Component.initial];
    const base = initial(settings, items);
    const options = Object.assign(Object.create(null), base.options, settings);
    const children = base.children || items || null;
    const state = Object.assign(Object.create(null), base, {options, children});
    this[currentState] = state;

    if (this.setup) {
      this.setup(state);
    }
  },
  [Component.initial](options, children) {
    return Object.create(null);
  },
  [Component.patch](update) {
    this[Component.reset](Object.assign({}, this.state, update));
  },
  [Component.reset](state) {
    this[pendingState] = state;
    if (Component.isMounted(this) && !Component.isWriting(this)) {
      Component.write(this);
    }
  },

  [Component.isUpdated](before, after) {
    return before != after
  },

  [Component.render](state) {
    throw Error("Component must implement [Component.render] member");
  }
});

module.exports = Component;
PK
!<skgg&modules/commonjs/sdk/ui/frame/model.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Class } = require("../../core/heritage");
const { EventTarget } = require("../../event/target");
lazyRequire(this, "../../event/core", "emit", "off", "setListeners");
const { Reactor, foldp, send, merges } = require("../../event/utils");
const { Disposable } = require("../../core/disposable");
const { OutputPort } = require("../../output/system");
lazyRequire(this, "../id", "identify");
const { pairs, object, each } = require("../../util/sequence");
lazyRequire(this, "diffpatcher/index", "patch", "diff");
lazyRequire(this, "../../url", "isLocalURL");
const { compose } = require("../../lang/functional");
const { contract } = require("../../util/contract");
const { id: addonID, data: { url: resolve }} = require("../../self");
const { Frames } = require("../../input/frame");
require("./view");


const output = new OutputPort({ id: "frame-change" });
const mailbox = new OutputPort({ id: "frame-mailbox" });
const input = Frames;


const makeID = url =>
  ("frame-" + addonID + "-" + url).
    split("/").join("-").
    split(".").join("-").
    replace(/[^A-Za-z0-9_\-]/g, "");

const validate = contract({
  name: {
    is: ["string", "undefined"],
    ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
    msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
         "underscores are allowed) starting with letter."
  },
  url: {
    map: x => x.toString(),
    is: ["string"],
    ok: x => isLocalURL(x),
    msg: "The `options.url` must be a valid local URI."
  }
});

const Source = function({id, ownerID}) {
  this.id = id;
  this.ownerID = ownerID;
};
Source.postMessage = ({id, ownerID}, data, origin) => {
  send(mailbox, object([id, {
    inbox: {
      target: {id: id, ownerID: ownerID},
      timeStamp: Date.now(),
      data: data,
      origin: origin
    }
  }]));
};
Source.prototype.postMessage = function(data, origin) {
  Source.postMessage(this, data, origin);
};

const Message = function({type, data, source, origin, timeStamp}) {
  this.type = type;
  this.data = data;
  this.origin = origin;
  this.timeStamp = timeStamp;
  this.source = new Source(source);
};


const frames = new Map();
const sources = new Map();

const Frame = Class({
  extends: EventTarget,
  implements: [Disposable, Source],
  initialize: function(params={}) {
    const options = validate(params);
    const id = makeID(options.name || options.url);

    if (frames.has(id))
      throw Error("Frame with this id already exists: " + id);

    const initial = { id: id, url: resolve(options.url) };
    this.id = id;

    setListeners(this, params);

    frames.set(this.id, this);

    send(output, object([id, initial]));
  },
  get url() {
    const state = reactor.value[this.id];
    return state && state.url;
  },
  destroy: function() {
    send(output, object([this.id, null]));
    frames.delete(this.id);
    off(this);
  },
  // `JSON.stringify` serializes objects based of the return
  // value of this method. For convinienc we provide this method
  // to serialize actual state data.
  toJSON: function() {
    return { id: this.id, url: this.url };
  }
});
identify.define(Frame, frame => frame.id);

exports.Frame = Frame;

const reactor = new Reactor({
  onStep: (present, past) => {
    const delta = diff(past, present);

    each(([id, update]) => {
      const frame = frames.get(id);
      if (update) {
        if (!past[id])
          emit(frame, "register");

        if (update.outbox)
          emit(frame, "message", new Message(present[id].outbox));

        each(([ownerID, state]) => {
          const readyState = state ? state.readyState : "detach";
          const type = readyState === "loading" ? "attach" :
                       readyState === "interactive" ? "ready" :
                       readyState === "complete" ? "load" :
                       readyState;

          // TODO: Cache `Source` instances somewhere to preserve
          // identity.
          emit(frame, type, {type: type,
                             source: new Source({id: id, ownerID: ownerID})});
        }, pairs(update.owners));
      }
    }, pairs(delta));
  }
});
reactor.run(input);
PK
!<AKb``'modules/commonjs/sdk/ui/frame/view.html<!DOCTYPE html>
<html>
  <head>
    <script>
      // HACK: This is not an ideal way to deliver chrome messages
      // to an inner frame content but seems only way that would
      // make `event.source` this (outer frame) window.
      window.onmessage = function(event) {
        var frame = document.querySelector("iframe");
        var content = frame.contentWindow;
        // If message is posted from chrome it has no `event.source`.
        if (event.source === null)
          content.postMessage(event.data, "*");
      };
    </script>
  </head>
  <body style="overflow: hidden"></body>
</html>
PK
!<p;%modules/commonjs/sdk/ui/frame/view.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Cu, Ci } = require("chrome");
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { send, Reactor } = require("../../event/utils");
const { OutputPort } = require("../../output/system");
lazyRequire(this, "../../util/sequence", "pairs", "keys", "object", "each");
const { curry, compose } = require("../../lang/functional");
lazyRequire(this, "../../window/utils", "getFrameElement", "getOuterId", "getByOuterId", "getOwnerBrowserWindow");
lazyRequire(this, "diffpatcher/index", "patch", "diff");
lazyRequire(this, "../../base64", "encode");
const { Frames } = require("../../input/frame");

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");

const mailbox = new OutputPort({ id: "frame-mailbox" });

const frameID = frame => frame.id.replace("outer-", "");
const windowID = compose(getOuterId, getOwnerBrowserWindow);

const getOuterFrame = (windowID, frameID) =>
    getByOuterId(windowID).document.getElementById("outer-" + frameID);

const listener = ({target, source, data, origin, timeStamp}) => {
  // And sent received message to outbox so that frame API model
  // will deal with it.
  if (source && source !== target) {
    const frame = getFrameElement(target);
    const id = frameID(frame);
    send(mailbox, object([id, {
      outbox: {type: "message",
               source: {id: id, ownerID: windowID(frame)},
               data: data,
               origin: origin,
               timeStamp: timeStamp}}]));
  }
};

// Utility function used to create frame with a given `state` and
// inject it into given `window`.
const registerFrame = ({id, url}) => {
  CustomizableUI.createWidget({
    id: id,
    type: "custom",
    removable: true,
    onBuild: document => {
      let view = document.createElementNS(XUL_NS, "toolbaritem");
      view.setAttribute("id", id);
      view.setAttribute("flex", 2);

      let outerFrame = document.createElementNS(XUL_NS, "iframe");
      outerFrame.setAttribute("src", OUTER_FRAME_URI);
      outerFrame.setAttribute("id", "outer-" + id);
      outerFrame.setAttribute("data-is-sdk-outer-frame", true);
      outerFrame.setAttribute("type", "content");
      outerFrame.setAttribute("transparent", true);
      outerFrame.setAttribute("flex", 2);
      outerFrame.setAttribute("style", "overflow: hidden;");
      outerFrame.setAttribute("scrolling", "no");
      outerFrame.setAttribute("disablehistory", true);
      outerFrame.setAttribute("seamless", "seamless");
      outerFrame.addEventListener("load", function() {
        let doc = outerFrame.contentDocument;

        let innerFrame = doc.createElementNS(HTML_NS, "iframe");
        innerFrame.setAttribute("id", id);
        innerFrame.setAttribute("src", url);
        innerFrame.setAttribute("seamless", "seamless");
        innerFrame.setAttribute("sandbox", "allow-scripts");
        innerFrame.setAttribute("scrolling", "no");
        innerFrame.setAttribute("data-is-sdk-inner-frame", true);
        innerFrame.setAttribute("style", [ "border:none",
          "position:absolute", "width:100%", "top: 0",
          "left: 0", "overflow: hidden"].join(";"));

        doc.body.appendChild(innerFrame);
      }, {capture: true, once: true});

      view.appendChild(outerFrame);

      return view;
    }
  });
};

const unregisterFrame = CustomizableUI.destroyWidget;

const deliverMessage = curry((frameID, data, windowID) => {
  const frame = getOuterFrame(windowID, frameID);
  const content = frame && frame.contentWindow;

  if (content)
    content.postMessage(data, content.location.origin);
});

const updateFrame = (id, {inbox, owners}, present) => {
  if (inbox) {
    const { data, target:{ownerID}, source } = present[id].inbox;
    if (ownerID)
      deliverMessage(id, data, ownerID);
    else
      each(deliverMessage(id, data), keys(present[id].owners));
  }

  each(setupView(id), pairs(owners));
};

const setupView = curry((frameID, [windowID, state]) => {
  if (state && state.readyState === "loading") {
    const frame = getOuterFrame(windowID, frameID);
    // Setup a message listener on contentWindow.
    frame.contentWindow.addEventListener("message", listener);
  }
});


const reactor = new Reactor({
  onStep: (present, past) => {
    const delta = diff(past, present);

    // Apply frame changes
    each(([id, update]) => {
      if (update === null)
        unregisterFrame(id);
      else if (past[id])
        updateFrame(id, update, present);
      else
        registerFrame(update);
    }, pairs(delta));
  },
  onEnd: state => each(unregisterFrame, keys(state))
});
reactor.run(Frames);
PK
!<*̀ modules/commonjs/sdk/ui/frame.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Frame } = require("./frame/model");

exports.Frame = Frame;
PK
!<Omodules/commonjs/sdk/ui/id.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental'
};

const method = require('../../method/core');
lazyRequire(this, '../util/uuid', "uuid");

// NOTE: use lang/functional memoize when it is updated to use WeakMap
function memoize(f) {
  const memo = new WeakMap();

  return function memoizer(o) {
    let key = o;
    if (!memo.has(key))
      memo.set(key, f.apply(this, arguments));
    return memo.get(key);
  };
}

var identify = method('identify');
identify.define(Object, memoize(function() { return uuid(); }));
exports.identify = identify;
PK
!<nn*modules/commonjs/sdk/ui/sidebar/actions.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const method = require('../../../method/core');

exports.show = method('show');
exports.hide = method('hide');
exports.toggle = method('toggle');
PK
!<LEE+modules/commonjs/sdk/ui/sidebar/contract.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { contract } = require('../../util/contract');
const { isValidURI, URL, isLocalURL } = require('../../url');
const { isNil, isObject, isString } = require('../../lang/type');

exports.contract = contract({
  id: {
  	is: [ 'string', 'undefined' ],
  	ok: v => /^[a-z0-9-_]+$/i.test(v),
    msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
         'underscores are allowed).'
  },
  title: {
  	is: [ 'string' ],
  	ok: v => v.length
  },
  url: {
    is: [ 'string' ],
    ok: v => isLocalURL(v),
    map: v => v.toString(),
    msg: 'The option "url" must be a valid local URI.'
  }
});
PK
!<H9,modules/commonjs/sdk/ui/sidebar/namespace.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const models = exports.models = new WeakMap();
const views = exports.views = new WeakMap();
exports.buttons = new WeakMap();

exports.viewsFor = function viewsFor(sidebar) {
  return views.get(sidebar);
};
exports.modelFor = function modelFor(sidebar) {
  return models.get(sidebar);
};
PK
!<766(modules/commonjs/sdk/ui/sidebar/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const method = require('../../../method/core');

exports.isShowing = method('isShowing');
PK
!<I=I'modules/commonjs/sdk/ui/sidebar/view.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable',
  'engines': {
    'Firefox': '*'
  }
};

lazyRequire(this, './namespace', "models", "buttons", "views", "viewsFor", "modelFor");
lazyRequire(this, '../../window/utils', "isBrowser", "getMostRecentBrowserWindow", "windows", "isWindowPrivate");
lazyRequire(this, '../state', "setStateFor");
lazyRequire(this, '../../core/promise', "defer");
lazyRequire(this, '../../self', "isPrivateBrowsingSupported", "data");

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';

const resolveURL = (url) => url ? data.url(url) : url;

function create(window, details) {
  let id = makeID(details.id);
  let { document } = window;

  if (document.getElementById(id))
    throw new Error('The ID "' + details.id + '" seems already used.');

  let menuitem = document.createElementNS(XUL_NS, 'menuitem');
  menuitem.setAttribute('id', id);
  menuitem.setAttribute('label', details.title);
  menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl));
  menuitem.setAttribute('checked', 'false');
  menuitem.setAttribute('type', 'checkbox');
  menuitem.setAttribute('group', 'sidebar');
  menuitem.setAttribute('autoCheck', 'false');

  document.getElementById('viewSidebarMenu').appendChild(menuitem);

  return menuitem;
}
exports.create = create;

function dispose(menuitem) {
  menuitem.remove();
}
exports.dispose = dispose;

function updateTitle(sidebar, title) {
  let button = buttons.get(sidebar);

  for (let window of windows(null, { includePrivate: true })) {
  	let { document } = window;

    // update the button
    if (button) {
      setStateFor(button, window, { label: title });
    }

    // update the menuitem
    let mi = document.getElementById(makeID(sidebar.id));
    if (mi) {
      mi.setAttribute('label', title)
    }

    // update sidebar, if showing
    if (isSidebarShowing(window, sidebar)) {
      document.getElementById('sidebar-title').setAttribute('value', title);
    }
  }
}
exports.updateTitle = updateTitle;

function updateURL(sidebar, url) {
  let eleID = makeID(sidebar.id);

  url = resolveURL(url);

  for (let window of windows(null, { includePrivate: true })) {
    // update the menuitem
    let mi = window.document.getElementById(eleID);
    if (mi) {
      mi.setAttribute('sidebarurl', url)
    }

    // update sidebar, if showing
    if (isSidebarShowing(window, sidebar)) {
      showSidebar(window, sidebar, url);
    }
  }
}
exports.updateURL = updateURL;

function isSidebarShowing(window, sidebar) {
  let win = window || getMostRecentBrowserWindow();

  // make sure there is a window
  if (!win) {
    return false;
  }

  // make sure there is a sidebar for the window
  let sb = win.document.getElementById('sidebar');
  let sidebarTitle = win.document.getElementById('sidebar-title');
  if (!(sb && sidebarTitle)) {
    return false;
  }

  // checks if the sidebar box is hidden
  let sbb = win.document.getElementById('sidebar-box');
  if (!sbb || sbb.hidden) {
    return false;
  }

  if (sidebarTitle.value == modelFor(sidebar).title) {
    let url = resolveURL(modelFor(sidebar).url);

    // checks if the sidebar is loading
    if (win.gWebPanelURI == url) {
      return true;
    }

    // checks if the sidebar loaded already
    let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
    if (!ele) {
      return false;
    }

    if (ele.getAttribute('cachedurl') == url) {
      return true;
    }

    if (ele && ele.contentWindow && ele.contentWindow.location == url) {
      return true;
    }
  }

  // default
  return false;
}
exports.isSidebarShowing = isSidebarShowing;

function showSidebar(window, sidebar, newURL) {
  window = window || getMostRecentBrowserWindow();

  let { promise, resolve, reject } = defer();
  let model = modelFor(sidebar);

  if (!newURL && isSidebarShowing(window, sidebar)) {
    resolve({});
  }
  else if (!isPrivateBrowsingSupported && isWindowPrivate(window)) {
    reject(Error('You cannot show a sidebar on private windows'));
  }
  else {
    sidebar.once('show', resolve);

    let menuitem = window.document.getElementById(makeID(model.id));
    menuitem.setAttribute('checked', true);

    window.openWebPanel(model.title, resolveURL(newURL || model.url));
  }

  return promise;
}
exports.showSidebar = showSidebar;


function hideSidebar(window, sidebar) {
  window = window || getMostRecentBrowserWindow();

  let { promise, resolve, reject } = defer();

  if (!isSidebarShowing(window, sidebar)) {
    reject(Error('The sidebar is already hidden'));
  }
  else {
    sidebar.once('hide', resolve);

    // Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775
    // the code for window.todggleSideBar()..
    let { document } = window;
    let sidebarEle = document.getElementById('sidebar');
    let sidebarTitle = document.getElementById('sidebar-title');
    let sidebarBox = document.getElementById('sidebar-box');
    let sidebarSplitter = document.getElementById('sidebar-splitter');
    let commandID = sidebarBox.getAttribute('sidebarcommand');
    let sidebarBroadcaster = document.getElementById(commandID);

    sidebarBox.hidden = true;
    sidebarSplitter.hidden = true;

    sidebarEle.setAttribute('src', 'about:blank');
    //sidebarEle.docShell.createAboutBlankContentViewer(null);

    sidebarBroadcaster.removeAttribute('checked');
    sidebarBox.setAttribute('sidebarcommand', '');
    sidebarTitle.value = '';
    sidebarBox.hidden = true;
    sidebarSplitter.hidden = true;

    // TODO: perhaps this isn't necessary if the window is not most recent?
    window.gBrowser.selectedBrowser.focus();
  }

  return promise;
}
exports.hideSidebar = hideSidebar;

function makeID(id) {
  return 'jetpack-sidebar-' + id;
}
PK
!<D(("modules/commonjs/sdk/ui/sidebar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '*'
  }
};

const { Class } = require('../core/heritage');
const { merge } = require('../util/object');
const { Disposable } = require('../core/disposable');
lazyRequire(this, '../event/core', "off", "emit", "setListeners");
const { EventTarget } = require('../event/target');
lazyRequire(this, '../url', "URL");
lazyRequire(this, '../self', { "id": "addonID" }, "data");
lazyRequire(this, '../deprecated/window-utils', 'WindowTracker');
lazyRequire(this, './sidebar/utils', "isShowing");
lazyRequire(this, '../window/utils', "isBrowser", "getMostRecentBrowserWindow", "windows", "isWindowPrivate");
const { ns } = require('../core/namespace');
lazyRequire(this, '../util/array', { "remove": "removeFromArray" });
lazyRequire(this, './sidebar/actions', "show", "hide", "toggle");
lazyRequire(this, '../deprecated/sync-worker', "Worker");
const { contract: sidebarContract } = require('./sidebar/contract');
lazyRequire(this, './sidebar/view', "create", "dispose", "updateTitle", "updateURL", "isSidebarShowing", "showSidebar", "hideSidebar");
lazyRequire(this, '../core/promise', "defer");
lazyRequire(this, './sidebar/namespace', "models", "views", "viewsFor", "modelFor");
lazyRequire(this, '../url', "isLocalURL");
const { ensure } = require('../system/unload');
lazyRequire(this, './id', "identify");
lazyRequire(this, '../util/uuid', "uuid");
lazyRequire(this, '../view/core', "viewFor");

const resolveURL = (url) => url ? data.url(url) : url;

const sidebarNS = ns();

const WEB_PANEL_BROWSER_ID = 'web-panels-browser';

const Sidebar = Class({
  implements: [ Disposable ],
  extends: EventTarget,
  setup: function(options) {
    // inital validation for the model information
    let model = sidebarContract(options);

    // save the model information
    models.set(this, model);

    // generate an id if one was not provided
    model.id = model.id || addonID + '-' + uuid();

    // further validation for the title and url
    validateTitleAndURLCombo({}, this.title, this.url);

    const self = this;
    const internals = sidebarNS(self);
    const windowNS = internals.windowNS = ns();

    // see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
    ensure(this, 'destroy');

    setListeners(this, options);

    let bars = [];
    internals.tracker = WindowTracker({
      onTrack: function(window) {
        if (!isBrowser(window))
          return;

        let sidebar = window.document.getElementById('sidebar');
        let sidebarBox = window.document.getElementById('sidebar-box');

        let bar = create(window, {
          id: self.id,
          title: self.title,
          sidebarurl: self.url
        });
        bars.push(bar);
        windowNS(window).bar = bar;

        bar.addEventListener('command', function() {
          if (isSidebarShowing(window, self)) {
            hideSidebar(window, self).catch(() => {});
            return;
          }

          showSidebar(window, self);
        });

        function onSidebarLoad() {
          // check if the sidebar is ready
          let isReady = sidebar.docShell && sidebar.contentDocument;
          if (!isReady)
            return;

          // check if it is a web panel
          let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
          if (!panelBrowser) {
            bar.removeAttribute('checked');
            return;
          }

          let sbTitle = window.document.getElementById('sidebar-title');
          function onWebPanelSidebarCreated() {
            if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
                sbTitle.value != model.title) {
              return;
            }

            let worker = windowNS(window).worker = Worker({
              window: panelBrowser.contentWindow,
              injectInDocument: true
            });

            function onWebPanelSidebarUnload() {
              windowNS(window).onWebPanelSidebarUnload = null;

              // uncheck the associated menuitem
              bar.setAttribute('checked', 'false');

              emit(self, 'hide', {});
              emit(self, 'detach', worker);
              windowNS(window).worker = null;
            }
            windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
            panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);

            // check the associated menuitem
            bar.setAttribute('checked', 'true');

            function onWebPanelSidebarReady() {
              panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady);
              windowNS(window).onWebPanelSidebarReady = null;

              emit(self, 'ready', worker);
            }
            windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
            panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady);

            function onWebPanelSidebarLoad() {
              panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
              windowNS(window).onWebPanelSidebarLoad = null;

              // TODO: decide if returning worker is acceptable..
              //emit(self, 'show', { worker: worker });
              emit(self, 'show', {});
            }
            windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
            panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);

            emit(self, 'attach', worker);
          }
          windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
          panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
        }
        windowNS(window).onSidebarLoad = onSidebarLoad;
        sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
      },
      onUntrack: function(window) {
        if (!isBrowser(window))
          return;

        // hide the sidebar if it is showing
        hideSidebar(window, self).catch(() => {});

        // kill the menu item
        let { bar } = windowNS(window);
        if (bar) {
          removeFromArray(viewsFor(self), bar);
          dispose(bar);
        }

        // kill listeners
        let sidebar = window.document.getElementById('sidebar');

        if (windowNS(window).onSidebarLoad) {
          sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
          windowNS(window).onSidebarLoad = null;
        }

        let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
        if (windowNS(window).onWebPanelSidebarCreated) {
          panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
          windowNS(window).onWebPanelSidebarCreated = null;
        }

        if (windowNS(window).onWebPanelSidebarReady) {
          panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady);
          windowNS(window).onWebPanelSidebarReady = null;
        }

        if (windowNS(window).onWebPanelSidebarLoad) {
          panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
          windowNS(window).onWebPanelSidebarLoad = null;
        }

        if (windowNS(window).onWebPanelSidebarUnload) {
          panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
          windowNS(window).onWebPanelSidebarUnload();
        }
      }
    });

    views.set(this, bars);
  },
  get id() {
    return (modelFor(this) || {}).id;
  },
  get title() {
    return (modelFor(this) || {}).title;
  },
  set title(v) {
    // destroyed?
    if (!modelFor(this))
      return;
    // validation
    if (typeof v != 'string')
      throw Error('title must be a string');
    validateTitleAndURLCombo(this, v, this.url);
    // do update
    updateTitle(this, v);
    return modelFor(this).title = v;
  },
  get url() {
    return (modelFor(this) || {}).url;
  },
  set url(v) {
    // destroyed?
    if (!modelFor(this))
      return;

    // validation
    if (!isLocalURL(v))
      throw Error('the url must be a valid local url');

    validateTitleAndURLCombo(this, this.title, v);

    // do update
    updateURL(this, v);
    modelFor(this).url = v;
  },
  show: function(window) {
    return showSidebar(viewFor(window), this);
  },
  hide: function(window) {
    return hideSidebar(viewFor(window), this);
  },
  dispose: function() {
    const internals = sidebarNS(this);

    off(this);

    // stop tracking windows
    if (internals.tracker) {
      internals.tracker.unload();
    }

    internals.tracker = null;
    internals.windowNS = null;

    views.delete(this);
    models.delete(this);
  }
});
exports.Sidebar = Sidebar;

function validateTitleAndURLCombo(sidebar, title, url) {
  url = resolveURL(url);

  if (sidebar.title == title && sidebar.url == url) {
    return false;
  }

  for (let window of windows(null, { includePrivate: true })) {
    let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
    if (sidebar) {
      throw Error('The provided title and url combination is invalid (already used).');
    }
  }

  return false;
}

isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
show.define(Sidebar, showSidebar.bind(null, null));
hide.define(Sidebar, hideSidebar.bind(null, null));

identify.define(Sidebar, function(sidebar) {
  return sidebar.id;
});

function toggleSidebar(window, sidebar) {
  // TODO: make sure this is not private
  window = window || getMostRecentBrowserWindow();
  if (isSidebarShowing(window, sidebar)) {
    return hideSidebar(window, sidebar);
  }
  return showSidebar(window, sidebar);
}
toggle.define(Sidebar, toggleSidebar.bind(null, null));
PK
!<?'modules/commonjs/sdk/ui/state/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '*',
    'SeaMonkey': '*',
    'Thunderbird': '*'
  }
};

var channel = {};

exports.events = channel;
PK
!<
H modules/commonjs/sdk/ui/state.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

// The Button module currently supports only Firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '*',
    'SeaMonkey': '*',
    'Thunderbird': '*'
  }
};

const { Ci } = require('chrome');

const events = require('../event/utils');
const { events: browserEvents } = require('../browser/events');
const { events: tabEvents } = require('../tab/events');
const { events: stateEvents } = require('./state/events');

lazyRequire(this, '../window/utils', "windows", "isInteractive", "getFocusedBrowser");
lazyRequire(this, '../tabs/utils', "getActiveTab", "getOwnerWindow");

lazyRequire(this, '../private-browsing/utils', "ignoreWindow");

const { freeze } = Object;
const { merge } = require('../util/object');
lazyRequire(this, '../event/core', "on", "off", "emit");

lazyRequire(this, '../lang/weak-set', "add", "remove", "has", "clear", "iterator");
lazyRequire(this, '../lang/type', "isNil");

lazyRequire(this, '../view/core', "viewFor");

const components = new WeakMap();

const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
  'The object may be not be registered, or may already have been unloaded.';

const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' +
  'Only window, tab and registered component are valid targets.';

const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
const isEnumerable = window => !ignoreWindow(window);
const browsers = _ =>
  windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());

function getStateFor(component, target) {
  if (!isRegistered(component))
    throw new Error(ERR_UNREGISTERED);

  if (!components.has(component))
    return null;

  let states = components.get(component);

  if (target) {
    if (isTab(target) || isWindow(target) || target === component)
      return states.get(target) || null;
    else
      throw new Error(ERR_INVALID_TARGET);
  }

  return null;
}
exports.getStateFor = getStateFor;

function getDerivedStateFor(component, target) {
  if (!isRegistered(component))
    throw new Error(ERR_UNREGISTERED);

  if (!components.has(component))
    return null;

  let states = components.get(component);

  let componentState = states.get(component);
  let windowState = null;
  let tabState = null;

  if (target) {
    // has a target
    if (isTab(target)) {
      windowState = states.get(getOwnerWindow(target), null);

      if (states.has(target)) {
        // we have a tab state
        tabState = states.get(target);
      }
    }
    else if (isWindow(target) && states.has(target)) {
      // we have a window state
      windowState = states.get(target);
    }
  }

  return freeze(merge({}, componentState, windowState, tabState));
}
exports.getDerivedStateFor = getDerivedStateFor;

function setStateFor(component, target, state) {
  if (!isRegistered(component))
    throw new Error(ERR_UNREGISTERED);

  let isComponentState = target === component;
  let targetWindows = isWindow(target) ? [target] :
                      isActiveTab(target) ? [getOwnerWindow(target)] :
                      isComponentState ? browsers() :
                      isTab(target) ? [] :
                      null;

  if (!targetWindows)
    throw new Error(ERR_INVALID_TARGET);

  // initialize the state's map
  if (!components.has(component))
    components.set(component, new WeakMap());

  let states = components.get(component);

  if (state === null && !isComponentState) // component state can't be deleted
    states.delete(target);
  else {
    let base = isComponentState ? states.get(target) : null;
    states.set(target, freeze(merge({}, base, state)));
  }

  render(component, targetWindows);
}
exports.setStateFor = setStateFor;

function render(component, targetWindows) {
  targetWindows = targetWindows ? [].concat(targetWindows) : browsers();

  for (let window of targetWindows.filter(isEnumerable)) {
    let tabState = getDerivedStateFor(component, getActiveTab(window));

    emit(stateEvents, 'data', {
      type: 'render',
      target: component,
      window: window,
      state: tabState
    });

  }
}
exports.render = render;

function properties(contract) {
  let { rules } = contract;
  let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
    descriptor[name] = {
      get: function() { return getDerivedStateFor(this)[name] },
      set: function(value) {
        let changed = {};
        changed[name] = value;

        setStateFor(this, this, contract(changed));
      }
    }
    return descriptor;
  }, {});

  return Object.create(Object.prototype, descriptor);
}
exports.properties = properties;

function state(contract) {
  return {
    state: function state(target, state) {
      let nativeTarget = target === 'window' ? getFocusedBrowser()
                          : target === 'tab' ? getMostRecentTab()
                          : target === this ? null
                          : viewFor(target);

      if (!nativeTarget && target !== this && !isNil(target))
        throw new Error(ERR_INVALID_TARGET);

      target = nativeTarget || target;

      // jquery style
      return arguments.length < 2
        ? getDerivedStateFor(this, target)
        : setStateFor(this, target, contract(state))
    }
  }
}
exports.state = state;

const register = (component, state) => {
  add(components, component);
  setStateFor(component, component, state);
}
exports.register = register;

const unregister = component => {
  remove(components, component);
}
exports.unregister = unregister;

const isRegistered = component => has(components, component);
exports.isRegistered = isRegistered;

var tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect');
var tabClose = events.filter(tabEvents, e => e.type === 'TabClose');
var windowOpen = events.filter(browserEvents, e => e.type === 'load');
var windowClose = events.filter(browserEvents, e => e.type === 'close');

var close = events.merge([tabClose, windowClose]);
var activate = events.merge([windowOpen, tabSelect]);

on(activate, 'data', ({target}) => {
  let [window, tab] = isWindow(target)
                        ? [target, getActiveTab(target)]
                        : [getOwnerWindow(target), target];

  if (ignoreWindow(window)) return;

  for (let component of iterator(components)) {
    emit(stateEvents, 'data', {
      type: 'render',
      target: component,
      window: window,
      state: getDerivedStateFor(component, tab)
    });
  }
});

on(close, 'data', function({target}) {
  for (let component of iterator(components)) {
    components.get(component).delete(target);
  }
});
PK
!<Cq(modules/commonjs/sdk/ui/toolbar/model.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Class } = require("../../core/heritage");
const { EventTarget } = require("../../event/target");
const { off, setListeners, emit } = require("../../event/core");
const { Reactor, foldp, merges, send } = require("../../event/utils");
const { Disposable } = require("../../core/disposable");
const { InputPort } = require("../../input/system");
const { OutputPort } = require("../../output/system");
const { identify } = require("../id");
const { pairs, object, map, each } = require("../../util/sequence");
const { patch, diff } = require("diffpatcher/index");
const { contract } = require("../../util/contract");
const { id: addonID } = require("../../self");

// Input state is accumulated from the input received form the toolbar
// view code & local output. Merging local output reflects local state
// changes without complete roundloop.
const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
const output = new OutputPort({ id: "toolbar-change" });

// Takes toolbar title and normalizes is to an
// identifier, also prefixes with add-on id.
const titleToId = title =>
  ("toolbar-" + addonID + "-" + title).
    toLowerCase().
    replace(/\s/g, "-").
    replace(/[^A-Za-z0-9_\-]/g, "");

const validate = contract({
  title: {
    is: ["string"],
    ok: x => x.length > 0,
    msg: "The `option.title` string must be provided"
  },
  items: {
    is:["undefined", "object", "array"],
    msg: "The `options.items` must be iterable sequence of items"
  },
  hidden: {
    is: ["boolean", "undefined"],
    msg: "The `options.hidden` must be boolean"
  }
});

// Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
// which is used to find intstance for dispatching events.
var toolbars = new Map();

const Toolbar = Class({
  extends: EventTarget,
  implements: [Disposable],
  initialize: function(params={}) {
    const options = validate(params);
    const id = titleToId(options.title);

    if (toolbars.has(id))
      throw Error("Toolbar with this id already exists: " + id);

    // Set of the items in the toolbar isn't mutable, as a matter of fact
    // it just defines desired set of items, actual set is under users
    // control. Conver test to an array and freeze to make sure users won't
    // try mess with it.
    const items = Object.freeze(options.items ? [...options.items] : []);

    const initial = {
      id: id,
      title: options.title,
      // By default toolbars are visible when add-on is installed, unless
      // add-on authors decides it should be hidden. From that point on
      // user is in control.
      collapsed: !!options.hidden,
      // In terms of state only identifiers of items matter.
      items: items.map(identify)
    };

    this.id = id;
    this.items = items;

    toolbars.set(id, this);
    setListeners(this, params);

    // Send initial state to the host so it can reflect it
    // into a user interface.
    send(output, object([id, initial]));
  },

  get title() {
    const state = reactor.value[this.id];
    return state && state.title;
  },
  get hidden() {
    const state = reactor.value[this.id];
    return state && state.collapsed;
  },

  destroy: function() {
    send(output, object([this.id, null]));
  },
  // `JSON.stringify` serializes objects based of the return
  // value of this method. For convinienc we provide this method
  // to serialize actual state data. Note: items will also be
  // serialized so they should probably implement `toJSON`.
  toJSON: function() {
    return {
      id: this.id,
      title: this.title,
      hidden: this.hidden,
      items: this.items
    };
  }
});
exports.Toolbar = Toolbar;
identify.define(Toolbar, toolbar => toolbar.id);

const dispose = toolbar => {
  toolbars.delete(toolbar.id);
  emit(toolbar, "detach");
  off(toolbar);
};

const reactor = new Reactor({
  onStep: (present, past) => {
    const delta = diff(past, present);

    each(([id, update]) => {
      const toolbar = toolbars.get(id);

      // Remove
      if (!update)
        dispose(toolbar);
      // Add
      else if (!past[id])
        emit(toolbar, "attach");
      // Update
      else
        emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
    }, pairs(delta));
  }
});
reactor.run(input);
PK
!<I5%%'modules/commonjs/sdk/ui/toolbar/view.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Cu } = require("chrome");
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils");
const { InputPort } = require("../../input/system");
const { OutputPort } = require("../../output/system");
const { Interactive } = require("../../input/browser");
const { CustomizationInput } = require("../../input/customizable-ui");
const { pairs, map, isEmpty, object,
        each, keys, values } = require("../../util/sequence");
const { curry, flip } = require("../../lang/functional");
lazyRequire(this, "diffpatcher/index", "patch", "diff");
const prefs = require("../../preferences/service");
lazyRequire(this, "../../window/utils", "getByOuterId");
lazyRequire(this, '../../private-browsing/utils', "ignoreWindow");

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";


// There are two output ports one for publishing changes that occured
// and the other for change requests. Later is synchronous and is only
// consumed here. Note: it needs to be synchronous to avoid race conditions
// when `collapsed` attribute changes are caused by user interaction and
// toolbar is destroyed between the ticks.
const output = new OutputPort({ id: "toolbar-changed" });
const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });

// Merge disptached changes and recevied changes from models to keep state up to
// date.
const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
                                          new InputPort({ id: "toolbar-change" })]));
const State = lift((toolbars, windows, customizable) =>
  ({windows: windows, toolbars: toolbars, customizable: customizable}),
  Toolbars, Interactive, new CustomizationInput());

// Shared event handler that makes `event.target.parent` collapsed.
// Used as toolbar's close buttons click handler.
const collapseToolbar = event => {
  const toolbar = event.target.parentNode;
  toolbar.collapsed = true;
};

const parseAttribute = x =>
  x === "true" ? true :
  x === "false" ? false :
  x === "" ? null :
  x;

// Shared mutation observer that is used to observe `toolbar` node's
// attribute mutations. Mutations are aggregated in the `delta` hash
// and send to `ToolbarStateChanged` channel to let model know state
// has changed.
const attributesChanged = mutations => {
  const delta = mutations.reduce((changes, {attributeName, target}) => {
    const id = target.id;
    const field = attributeName === "toolbarname" ? "title" : attributeName;
    let change = changes[id] || (changes[id] = {});
    change[field] = parseAttribute(target.getAttribute(attributeName));
    return changes;
  }, {});

  // Calculate what are the updates from the current state and if there are
  // any send them.
  const updates = diff(reactor.value, patch(reactor.value, delta));

  if (!isEmpty(pairs(updates))) {
    // TODO: Consider sending sync to make sure that there won't be a new
    // update doing a delete in the meantime.
    send(syncoutput, updates);
  }
};


// Utility function creates `toolbar` with a "close" button and returns
// it back. In addition it set's up a listener and observer to communicate
// state changes.
const addView = curry((options, {document, window}) => {
  if (ignoreWindow(window))
    return;

  let view = document.createElementNS(XUL_NS, "toolbar");
  view.setAttribute("id", options.id);
  view.setAttribute("collapsed", options.collapsed);
  view.setAttribute("toolbarname", options.title);
  view.setAttribute("pack", "end");
  view.setAttribute("customizable", "false");
  view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
  view.setAttribute("mode", "icons");
  view.setAttribute("iconsize", "small");
  view.setAttribute("context", "toolbar-context-menu");
  view.setAttribute("class", "chromeclass-toolbar");

  let label = document.createElementNS(XUL_NS, "label");
  label.setAttribute("value", options.title);
  label.setAttribute("collapsed", "true");
  view.appendChild(label);

  let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
  closeButton.setAttribute("id", "close-" + options.id);
  closeButton.setAttribute("class", "close-icon");
  closeButton.setAttribute("customizable", false);
  closeButton.addEventListener("command", collapseToolbar);

  view.appendChild(closeButton);

  // In order to have a close button not costumizable, aligned on the right,
  // leaving the customizable capabilities of Australis, we need to create
  // a toolbar inside a toolbar.
  // This is should be a temporary hack, we should have a proper XBL for toolbar
  // instead. See:
  // https://bugzilla.mozilla.org/show_bug.cgi?id=982005
  let toolbar = document.createElementNS(XUL_NS, "toolbar");
  toolbar.setAttribute("id", "inner-" + options.id);
  toolbar.setAttribute("defaultset", options.items.join(","));
  toolbar.setAttribute("customizable", "true");
  toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden; border: 0;");
  toolbar.setAttribute("mode", "icons");
  toolbar.setAttribute("iconsize", "small");
  toolbar.setAttribute("context", "toolbar-context-menu");
  toolbar.setAttribute("flex", "1");

  view.insertBefore(toolbar, closeButton);

  const observer = new document.defaultView.MutationObserver(attributesChanged);
  observer.observe(view, { attributes: true,
                           attributeFilter: ["collapsed", "toolbarname"] });

  const toolbox = document.getElementById("navigator-toolbox");
  toolbox.appendChild(view);
});
const viewAdd = curry(flip(addView));

const removeView = curry((id, {document}) => {
  const view = document.getElementById(id);
  if (view) view.remove();
});

const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
  const view = document.getElementById(id);

  if (!view)
    return;

  if (title)
    view.setAttribute("toolbarname", title);

  if (collapsed !== void(0))
    view.setAttribute("collapsed", Boolean(collapsed));

  if (isCustomizing !== void(0)) {
    view.querySelector("label").collapsed = !isCustomizing;
    view.querySelector("toolbar").style.visibility = isCustomizing
                                                ? "hidden" : "visible";
  }
});

const viewUpdate = curry(flip(updateView));

// Utility function used to register toolbar into CustomizableUI.
const registerToolbar = state => {
  // If it's first additon register toolbar as customizableUI component.
  CustomizableUI.registerArea("inner-" + state.id, {
    type: CustomizableUI.TYPE_TOOLBAR,
    legacy: true,
    defaultPlacements: [...state.items]
  });
};
// Utility function used to unregister toolbar from the CustomizableUI.
const unregisterToolbar = CustomizableUI.unregisterArea;

const reactor = new Reactor({
  onStep: (present, past) => {
    const delta = diff(past, present);

    each(([id, update]) => {
      // If update is `null` toolbar is removed, in such case
      // we unregister toolbar and remove it from each window
      // it was added to.
      if (update === null) {
        unregisterToolbar("inner-" + id);
        each(removeView(id), values(past.windows));

        send(output, object([id, null]));
      }
      else if (past.toolbars[id]) {
        // If `collapsed` state for toolbar was updated, persist
        // it for a future sessions.
        if (update.collapsed !== void(0))
          prefs.set(PREF_ROOT + id, update.collapsed);

        // Reflect update in each window it was added to.
        each(updateView(id, update), values(past.windows));

        send(output, object([id, update]));
      }
      // Hack: Mutation observers are invoked async, which means that if
      // client does `hide(toolbar)` & then `toolbar.destroy()` by the
      // time we'll get update for `collapsed` toolbar will be removed.
      // For now we check if `update.id` is present which will be undefined
      // in such cases.
      else if (update.id) {
        // If it is a new toolbar we create initial state by overriding
        // `collapsed` filed with value persisted in previous sessions.
        const state = patch(update, {
          collapsed: prefs.get(PREF_ROOT + id, update.collapsed),
        });

        // Register toolbar and add it each window known in the past
        // (note that new windows if any will be handled in loop below).
        registerToolbar(state);
        each(addView(state), values(past.windows));

        send(output, object([state.id, state]));
      }
    }, pairs(delta.toolbars));

    // Add views to every window that was added.
    each(window => {
      if (window)
        each(viewAdd(window), values(past.toolbars));
    }, values(delta.windows));

    each(([id, isCustomizing]) => {
      each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
        keys(present.toolbars));

    }, pairs(delta.customizable))
  },
  onEnd: state => {
    each(id => {
      unregisterToolbar("inner-" + id);
      each(removeView(id), values(state.windows));
    }, keys(state.toolbars));
  }
});
reactor.run(State);
PK
!<<h"modules/commonjs/sdk/ui/toolbar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental",
  "engines": {
    "Firefox": "> 28"
  }
};

const { Toolbar } = require("./toolbar/model");
require("./toolbar/view");

exports.Toolbar = Toolbar;
PK
!<LN^##modules/commonjs/sdk/ui.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'experimental',
  'engines': {
    'Firefox': '> 28'
  }
};

lazyRequire(this, './ui/button/action', 'ActionButton');
lazyRequire(this, './ui/button/toggle', 'ToggleButton');
lazyRequire(this, './ui/sidebar', 'Sidebar');
lazyRequire(this, './ui/frame', 'Frame');
lazyRequire(this, './ui/toolbar', 'Toolbar');

module.exports = Object.freeze({
  get ActionButton() { return ActionButton; },
  get ToggleButton() { return ToggleButton; },
  get Sidebar() { return Sidebar; },
  get Frame() { return Frame; },
  get Toolbar() { return Toolbar; },
});
PK
!<#)$modules/commonjs/sdk/uri/resource.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const {Cc, Ci} = require("chrome");
const ioService = Cc["@mozilla.org/network/io-service;1"].
                  getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler("resource").
                        QueryInterface(Ci.nsIResProtocolHandler);

const URI = (uri, base=null) =>
  ioService.newURI(uri, null, base && URI(base))

const mount = (domain, uri) =>
  resourceHandler.setSubstitution(domain, ioService.newURI(uri));
exports.mount = mount;

const unmount = (domain, uri) =>
  resourceHandler.setSubstitution(domain, null);
exports.unmount = unmount;

const domain = 1;
const path = 2;
const resolve = (uri) => {
  const match = /resource\:\/\/([^\/]+)\/{0,1}([\s\S]*)/.exec(uri);
  const domain = match && match[1];
  const path = match && match[2];
  return !match ? null :
         !resourceHandler.hasSubstitution(domain) ? null :
  resourceHandler.resolveURI(URI(`/${path}`, `resource://${domain}/`));
}
exports.resolve = resolve;
PK
!<7I[77!modules/commonjs/sdk/url/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cr } = require("chrome");
const IOService = Cc["@mozilla.org/network/io-service;1"].
                    getService(Ci.nsIIOService);
const { isValidURI } = require("../url");
const { method } = require("../../method/core");

function newURI (uri) {
  if (!isValidURI(uri))
    throw new Error("malformed URI: " + uri);
  return IOService.newURI(uri);
}
exports.newURI = newURI;

var getURL = method('sdk/url:getURL');
getURL.define(String, url => url);
getURL.define(function (object) {
  return null;
});
exports.getURL = getURL;
PK
!<W""modules/commonjs/sdk/url.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const { Cc, Ci, Cr, Cu } = require("chrome");

const { Class } = require("./core/heritage");
const base64 = require("./base64");
var tlds = Cc["@mozilla.org/network/effective-tld-service;1"]
          .getService(Ci.nsIEffectiveTLDService);

var ios = Cc['@mozilla.org/network/io-service;1']
          .getService(Ci.nsIIOService);

var resProt = ios.getProtocolHandler("resource")
              .QueryInterface(Ci.nsIResProtocolHandler);

var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
                .getService(Ci.nsIURLParser);

const { Services } = Cu.import("resource://gre/modules/Services.jsm");

function newURI(uriStr, base) {
  try {
    let baseURI = base ? ios.newURI(base) : null;
    return ios.newURI(uriStr, null, baseURI);
  }
  catch (e) {
    if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
      throw new Error("malformed URI: " + uriStr);
    }
    if (e.result == Cr.NS_ERROR_FAILURE ||
        e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
      throw new Error("invalid URI: " + uriStr);
    }
  }
}

function resolveResourceURI(uri) {
  var resolved;
  try {
    resolved = resProt.resolveURI(uri);
  }
  catch (e) {
    if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
      throw new Error("resource does not exist: " + uri.spec);
    }
  }
  return resolved;
}

var fromFilename = exports.fromFilename = function fromFilename(path) {
  var file = Cc['@mozilla.org/file/local;1']
             .createInstance(Ci.nsILocalFile);
  file.initWithPath(path);
  return ios.newFileURI(file).spec;
};

var toFilename = exports.toFilename = function toFilename(url) {
  var uri = newURI(url);
  if (uri.scheme == "resource")
    uri = newURI(resolveResourceURI(uri));
  if (uri.scheme == "chrome") {
    var channel = ios.newChannelFromURI2(uri,
                                         null,      // aLoadingNode
                                         Services.scriptSecurityManager.getSystemPrincipal(),
                                         null,      // aTriggeringPrincipal
                                         Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                                         Ci.nsIContentPolicy.TYPE_OTHER);
    try {
      channel = channel.QueryInterface(Ci.nsIFileChannel);
      return channel.file.path;
    }
    catch (e) {
      if (e.result == Cr.NS_NOINTERFACE) {
        throw new Error("chrome url isn't on filesystem: " + url);
      }
    }
  }
  if (uri.scheme == "file") {
    var file = uri.QueryInterface(Ci.nsIFileURL).file;
    return file.path;
  }
  throw new Error("cannot map to filename: " + url);
};

function URL(url, base) {
  if (!(this instanceof URL)) {
     return new URL(url, base);
  }

  var uri = newURI(url, base);

  var userPass = null;
  try {
    userPass = uri.userPass ? uri.userPass : null;
  }
  catch (e) {
    if (e.result != Cr.NS_ERROR_FAILURE) {
      throw e;
    }
  }

  var host = null;
  try {
    host = uri.host;
  }
  catch (e) {
    if (e.result != Cr.NS_ERROR_FAILURE) {
      throw e;
    }
  }

  var port = null;
  try {
    port = uri.port == -1 ? null : uri.port;
  }
  catch (e) {
    if (e.result != Cr.NS_ERROR_FAILURE) {
      throw e;
    }
  }

  let fileName = "/";
  try {
    fileName = uri.QueryInterface(Ci.nsIURL).fileName;
  } catch (e) {
    if (e.result != Cr.NS_NOINTERFACE) {
      throw e;
    }
  }

  let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
  URLParser.parsePath.apply(URLParser, uriData);
  let [{ value: filepathPos }, { value: filepathLen },
    { value: queryPos }, { value: queryLen },
    { value: refPos }, { value: refLen }] = uriData.slice(2);

  let hash = uri.ref ? "#" + uri.ref : "";
  let pathname = uri.path.substr(filepathPos, filepathLen);
  let search = uri.path.substr(queryPos, queryLen);
  search = search ? "?" + search : "";

  this.__defineGetter__("fileName", () => fileName);
  this.__defineGetter__("scheme", () => uri.scheme);
  this.__defineGetter__("userPass", () => userPass);
  this.__defineGetter__("host", () => host);
  this.__defineGetter__("hostname", () => host);
  this.__defineGetter__("port", () => port);
  this.__defineGetter__("path", () => uri.path);
  this.__defineGetter__("pathname", () => pathname);
  this.__defineGetter__("hash", () => hash);
  this.__defineGetter__("href", () => uri.spec);
  this.__defineGetter__("origin", () => uri.prePath);
  this.__defineGetter__("protocol", () => uri.scheme + ":");
  this.__defineGetter__("search", () => search);

  Object.defineProperties(this, {
    toString: {
      value() {
        return new String(uri.spec).toString();
      },
      enumerable: false
    },
    valueOf: {
      value() {
        return new String(uri.spec).valueOf();
      },
      enumerable: false
    },
    toSource: {
      value() {
        return new String(uri.spec).toSource();
      },
      enumerable: false
    },
    // makes more sense to flatten to string, easier to travel across JSON
    toJSON: {
      value() {
        return new String(uri.spec).toString();
      },
      enumerable: false
    }
  });

  return this;
};

URL.prototype = Object.create(String.prototype);
exports.URL = URL;

/**
 * Parse and serialize a Data URL.
 *
 * See: http://tools.ietf.org/html/rfc2397
 *
 * Note: Could be extended in the future to decode / encode automatically binary
 * data.
 */
const DataURL = Class({

  get base64 () {
    return "base64" in this.parameters;
  },

  set base64 (value) {
    if (value)
      this.parameters["base64"] = "";
    else
      delete this.parameters["base64"];
  },
  /**
  * Initialize the Data URL object. If a uri is given, it will be parsed.
  *
  * @param {String} [uri] The uri to parse
  *
  * @throws {URIError} if the Data URL is malformed
   */
  initialize: function(uri) {
    // Due to bug 751834 it is not possible document and define these
    // properties in the prototype.

    /**
     * An hashmap that contains the parameters of the Data URL. By default is
     * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"}
     */
    this.parameters = {};

    /**
     * The MIME type of the data. By default is empty, that accordingly to RFC
     * is equivalent to "text/plain"
     */
    this.mimeType = "";

    /**
     * The string that represent the data in the Data URL
     */
    this.data = "";

    if (typeof uri === "undefined")
      return;

    uri = String(uri);

    let matches = uri.match(/^data:([^,]*),(.*)$/i);

    if (!matches)
      throw new URIError("Malformed Data URL: " + uri);

    let mediaType = matches[1].trim();

    this.data = decodeURIComponent(matches[2].trim());

    if (!mediaType)
      return;

    let parametersList = mediaType.split(";");

    this.mimeType = parametersList.shift().trim();

    for (let parameter, i = 0; parameter = parametersList[i++];) {
      let pairs = parameter.split("=");
      let name = pairs[0].trim();
      let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : "";

      this.parameters[name] = value;
    }

    if (this.base64)
      this.data = base64.decode(this.data);

  },

  /**
   * Returns the object as a valid Data URL string
   *
   * @returns {String} The Data URL
   */
  toString : function() {
    let parametersList = [];

    for (let name in this.parameters) {
      let encodedParameter = encodeURIComponent(name);
      let value = this.parameters[name];

      if (value)
        encodedParameter += "=" + encodeURIComponent(value);

      parametersList.push(encodedParameter);
    }

    // If there is at least a parameter, add an empty string in order
    // to start with a `;` on join call.
    if (parametersList.length > 0)
      parametersList.unshift("");

    let data = this.base64 ? base64.encode(this.data) : this.data;

    return "data:" +
      this.mimeType +
      parametersList.join(";") + "," +
      encodeURIComponent(data);
  }
});

exports.DataURL = DataURL;

var getTLD = exports.getTLD = function getTLD (url) {
  let uri = newURI(url.toString());
  let tld = null;
  try {
    tld = tlds.getPublicSuffix(uri);
  }
  catch (e) {
    if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
        e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
      throw e;
    }
  }
  return tld;
};

var isValidURI = exports.isValidURI = function (uri) {
  try {
    newURI(uri);
  }
  catch(e) {
    return false;
  }
  return true;
}

function isLocalURL(url) {
  if (String(url).indexOf('./') === 0)
    return true;

  try {
    return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1;
  }
  catch(e) {}

  return false;
}
exports.isLocalURL = isLocalURL;
PK
!<144"modules/commonjs/sdk/util/array.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

/**
 * Returns `true` if given `array` contain given `element` or `false`
 * otherwise.
 * @param {Array} array
 *    Target array.
 * @param {Object|String|Number|Boolean} element
 *    Element being looked up.
 * @returns {Boolean}
 */
var has = exports.has = function has(array, element) {
  // shorter and faster equivalent of `array.indexOf(element) >= 0`
  return !!~array.indexOf(element);
};
var hasAny = exports.hasAny = function hasAny(array, elements) {
  if (arguments.length < 2)
    return false;
  if (!Array.isArray(elements))
    elements = [ elements ];
  return array.some(function (element) {
      return has(elements, element);
  });
};

/**
 * Adds given `element` to the given `array` if it does not contain it yet.
 * `true` is returned if element was added otherwise `false` is returned.
 * @param {Array} array
 *    Target array.
 * @param {Object|String|Number|Boolean} element
 *    Element to be added.
 * @returns {Boolean}
 */
var add = exports.add = function add(array, element) {
  var result;
  if ((result = !has(array, element)))
    array.push(element);

  return result;
};

/**
 * Removes first occurrence of the given `element` from the given `array`. If
 * `array` does not contain given `element` `false` is returned otherwise
 * `true` is returned.
 * @param {Array} array
 *    Target array.
 * @param {Object|String|Number|Boolean} element
 *    Element to be removed.
 * @returns {Boolean}
 */
exports.remove = function remove(array, element) {
  var result;
  if ((result = has(array, element)))
    array.splice(array.indexOf(element), 1);

  return result;
};

/**
 * Produces a duplicate-free version of the given `array`.
 * @param {Array} array
 *    Source array.
 * @returns {Array}
 */
function unique(array) {
  return array.reduce(function(result, item) {
    add(result, item);
    return result;
  }, []);
};
exports.unique = unique;

/**
 * Produce an array that contains the union: each distinct element from all
 * of the passed-in arrays.
 */
function union() {
  return unique(Array.concat.apply(null, arguments));
};
exports.union = union;

exports.flatten = function flatten(array){
   var flat = [];
   for (var i = 0, l = array.length; i < l; i++) {
    flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]);
   }
   return flat;
};

function fromIterator(iterator) {
  let array = [];
  if (iterator.__iterator__) {
    for (let item of iterator)
      array.push(item);
  }
  else {
    for (let item of iterator)
      array.push(item);
  }
  return array;
}
exports.fromIterator = fromIterator;

function find(array, predicate, fallback) {
  var index = 0;
  var count = array.length;
  while (index < count) {
    var value = array[index];
    if (predicate(value)) return value;
    else index = index + 1;
  }
  return fallback;
}
exports.find = find;
PK
!<u~'modules/commonjs/sdk/util/collection.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

exports.Collection = Collection;

/**
 * Adds a collection property to the given object.  Setting the property to a
 * scalar value empties the collection and adds the value.  Setting it to an
 * array empties the collection and adds all the items in the array.
 *
 * @param obj
 *        The property will be defined on this object.
 * @param propName
 *        The name of the property.
 * @param array
 *        If given, this will be used as the collection's backing array.
 */
exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
  array = array || [];
  let publicIface = new Collection(array);

  Object.defineProperty(obj, propName, {
    configurable: true,
    enumerable: true,

    set: function set(itemOrItems) {
      array.splice(0, array.length);
      publicIface.add(itemOrItems);
    },

    get: function get() {
      return publicIface;
    }
  });
};

/**
 * A collection is ordered, like an array, but its items are unique, like a set.
 *
 * @param array
 *        The collection is backed by an array.  If this is given, it will be
 *        used as the backing array.  This way the caller can fully control the
 *        collection.  Otherwise a new empty array will be used, and no one but
 *        the collection will have access to it.
 */
function Collection(array) {
  array = array || [];

  /**
   * Provides iteration over the collection.  Items are yielded in the order
   * they were added.
   */
  this.__iterator__ = function Collection___iterator__() {
    let items = array.slice();
    for (let i = 0; i < items.length; i++)
      yield items[i];
  };

  /**
   * The number of items in the collection.
   */
  this.__defineGetter__("length", function Collection_get_length() {
    return array.length;
  });

  /**
   * Adds a single item or an array of items to the collection.  Any items
   * already contained in the collection are ignored.
   *
   * @param  itemOrItems
   *         An item or array of items.
   * @return The collection.
   */
  this.add = function Collection_add(itemOrItems) {
    let items = toArray(itemOrItems);
    for (let i = 0; i < items.length; i++) {
      let item = items[i];
      if (array.indexOf(item) < 0)
        array.push(item);
    }
    return this;
  };

  /**
   * Removes a single item or an array of items from the collection.  Any items
   * not contained in the collection are ignored.
   *
   * @param  itemOrItems
   *         An item or array of items.
   * @return The collection.
   */
  this.remove = function Collection_remove(itemOrItems) {
    let items = toArray(itemOrItems);
    for (let i = 0; i < items.length; i++) {
      let idx = array.indexOf(items[i]);
      if (idx >= 0)
        array.splice(idx, 1);
    }
    return this;
  };
};

function toArray(itemOrItems) {
  let isArr = itemOrItems &&
              itemOrItems.constructor &&
              itemOrItems.constructor.name === "Array";
  return isArr ? itemOrItems : [itemOrItems];
}
PK
!<
q%modules/commonjs/sdk/util/contract.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { validateOptions: valid } = require("../deprecated/api-utils");
const method = require("method/core");

// Function takes property validation rules and returns function that given
// an `options` object will return validated / normalized options back. If
// option(s) are invalid validator will throw exception described by rules.
// Returned will also have contain `rules` property with a given validation
// rules and `properties` function that can be used to generate validated
// property getter and setters can be mixed into prototype. For more details
// see `properties` function below.
function contract(rules) {
  const validator = (instance, options) => {
    return valid(options || instance || {}, rules);
  };
  validator.rules = rules
  validator.properties = function(modelFor) {
    return properties(modelFor, rules);
  }
  return validator;
}
exports.contract = contract

// Function takes `modelFor` instance state model accessor functions and
// a property validation rules and generates object with getters and setters
// that can be mixed into prototype. Property accessors update model for the
// given instance. If you wish to react to property updates you can always
// override setters to put specific logic.
function properties(modelFor, rules) {
  let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
    descriptor[name] = {
      get: function() { return modelFor(this)[name] },
      set: function(value) {
        let change = {};
        change[name] = value;
        modelFor(this)[name] = valid(change, rules)[name];
      }
    }
    return descriptor
  }, {});
  return Object.create(Object.prototype, descriptor);
}
exports.properties = properties;

const validate = method("contract/validate");
exports.validate = validate;
PK
!<M&modules/commonjs/sdk/util/deprecate.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

lazyRequire(this, "../console/traceback", "get", "format");
lazyRequire(this, "../preferences/service", {"get": "getPref"});
const PREFERENCE = "devtools.errorconsole.deprecation_warnings";

function deprecateUsage(msg) {
  // Print caller stacktrace in order to help figuring out which code
  // does use deprecated thing
  let stack = get().slice(2);

  if (getPref(PREFERENCE)) 
    console.error("DEPRECATED: " + msg + "\n" + format(stack));
}
exports.deprecateUsage = deprecateUsage;

function deprecateFunction(fun, msg) {
  return function deprecated() {
    deprecateUsage(msg);
    return fun.apply(this, arguments);
  };
}
exports.deprecateFunction = deprecateFunction;

function deprecateEvent(fun, msg, evtTypes) {
  return function deprecateEvent(evtType) {
  	if (evtTypes.indexOf(evtType) >= 0)
      deprecateUsage(msg);
    return fun.apply(this, arguments);
  };
}
exports.deprecateEvent = deprecateEvent;
PK
!<bb'modules/commonjs/sdk/util/dispatcher.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

const method = require("method/core");

// Utility function that is just an enhancement over `method` to
// allow predicate based dispatch in addition to polymorphic
// dispatch. Unfortunately polymorphic dispatch does not quite
// cuts it in the world of XPCOM where no types / classes exist
// and all the XUL nodes share same type / prototype.
// Probably this is more generic and belongs some place else, but
// we can move it later once this will be relevant.
var dispatcher = hint => {
  const base = method(hint);
  // Make a map for storing predicate, implementation mappings.
  let implementations = new Map();

  // Dispatcher function goes through `predicate, implementation`
  // pairs to find predicate that matches first argument and
  // returns application of arguments on the associated
  // `implementation`. If no matching predicate is found delegates
  // to a `base` polymorphic function.
  let dispatch = (value, ...rest) => {
    for (let [predicate, implementation] of implementations) {
      if (predicate(value))
        return implementation(value, ...rest);
    }

    return base(value, ...rest);
  };

  // Expose base API.
  dispatch.define = base.define;
  dispatch.implement = base.implement;
  dispatch.toString = base.toString;

  // Add a `when` function to allow extending function via
  // predicates.
  dispatch.when = (predicate, implementation) => {
    if (implementations.has(predicate))
      throw TypeError("Already implemented for the given predicate");
    implementations.set(predicate, implementation);
  };

  return dispatch;
};

exports.dispatcher = dispatcher;
PK
!<\F		!modules/commonjs/sdk/util/list.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  "stability": "experimental"
};

const { Class } = require('../core/heritage');
const listNS = require('../core/namespace').ns();

const listOptions = {
  /**
   * List constructor can take any number of element to populate itself.
   * @params {Object|String|Number} element
   * @example
   *    List(1,2,3).length == 3 // true
   */
  initialize: function List() {
    listNS(this).keyValueMap = [];

    for (let i = 0, ii = arguments.length; i < ii; i++)
      addListItem(this, arguments[i]);
  },
  /**
   * Number of elements in this list.
   * @type {Number}
   */
  get length() {
    return listNS(this).keyValueMap.length;
  },
   /**
    * Returns a string representing this list.
    * @returns {String}
    */
  toString: function toString() {
    return 'List(' + listNS(this).keyValueMap + ')';
  },
  /**
   * Custom iterator providing `List`s enumeration behavior.
   * We cant reuse `_iterator` that is defined by `Iterable` since it provides
   * iteration in an arbitrary order.
   * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
   * @param {Boolean} onKeys
   */
  __iterator__: function __iterator__(onKeys, onKeyValue) {
    let array = listNS(this).keyValueMap.slice(0),
                i = -1;
    for (let element of array)
      yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
  },
};
listOptions[Symbol.iterator] = function iterator() {
    return listNS(this).keyValueMap.slice(0)[Symbol.iterator]();
};
const List = Class(listOptions);
exports.List = List;

function addListItem(that, value) {
  let list = listNS(that).keyValueMap,
      index = list.indexOf(value);

  if (-1 === index) {
    try {
      that[that.length] = value;
    }
    catch (e) {}
    list.push(value);
  }
}
exports.addListItem = addListItem;

function removeListItem(that, element) {
  let list = listNS(that).keyValueMap,
      index = list.indexOf(element);

  if (0 <= index) {
    list.splice(index, 1);
    try {
      for (let length = list.length; index < length; index++)
        that[index] = list[index];
      that[list.length] = undefined;
    }
    catch(e){}
  }
}
exports.removeListItem = removeListItem;

exports.listNS = listNS;
PK
!<&S*modules/commonjs/sdk/util/match-pattern.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

lazyRequire(this, '../url', "URL");
const cache = {};

function MatchPattern(pattern) {
  if (cache[pattern]) return cache[pattern];

  if (typeof pattern.test == "function") {
    // For compatibility with -moz-document rules, we require the RegExp's
    // global, ignoreCase, and multiline flags to be set to false.
    if (pattern.global) {
      throw new Error("A RegExp match pattern cannot be set to `global` " +
                      "(i.e. //g).");
    }
    if (pattern.multiline) {
      throw new Error("A RegExp match pattern cannot be set to `multiline` " +
                      "(i.e. //m).");
    }

    this.regexp = pattern;
  }
  else {
    let firstWildcardPosition = pattern.indexOf("*");
    let lastWildcardPosition = pattern.lastIndexOf("*");
    if (firstWildcardPosition != lastWildcardPosition)
      throw new Error("There can be at most one '*' character in a wildcard.");

    if (firstWildcardPosition == 0) {
      if (pattern.length == 1)
        this.anyWebPage = true;
      else if (pattern[1] != ".")
        throw new Error("Expected a *.<domain name> string, got: " + pattern);
      else
        this.domain = pattern.substr(2);
    }
    else {
      if (pattern.indexOf(":") == -1) {
        throw new Error("When not using *.example.org wildcard, the string " +
                        "supplied is expected to be either an exact URL to " +
                        "match or a URL prefix. The provided string ('" +
                        pattern + "') is unlikely to match any pages.");
      }

      if (firstWildcardPosition == -1)
        this.exactURL = pattern;
      else if (firstWildcardPosition == pattern.length - 1)
        this.urlPrefix = pattern.substr(0, pattern.length - 1);
      else {
        throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
                        "in an unexpected position. It is expected to be the " +
                        "first or the last character in the wildcard.");
      }
    }
  }

  cache[pattern] = this;
}

MatchPattern.prototype = {
  test: function MatchPattern_test(urlStr) {
    try {
      var url = URL(urlStr);
    }
    catch (err) {
      return false;
    }

    // Test the URL against a RegExp pattern.  For compatibility with
    // -moz-document rules, we require the RegExp to match the entire URL,
    // so we not only test for a match, we also make sure the matched string
    // is the entire URL string.
    //
    // Assuming most URLs don't match most match patterns, we call `test` for
    // speed when determining whether or not the URL matches, then call `exec`
    // for the small subset that match to make sure the entire URL matches.
    if (this.regexp && this.regexp.test(urlStr) &&
        this.regexp.exec(urlStr)[0] == urlStr)
      return true;

    if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
      return true;

    if (this.exactURL && this.exactURL == urlStr)
      return true;

    // Tests the urlStr against domain and check if
    // wildcard submitted (*.domain.com), it only allows
    // subdomains (sub.domain.com) or from the root (http://domain.com)
    // and reject non-matching domains (otherdomain.com)
    // bug 856913
    if (this.domain && url.host &&
         (url.host === this.domain ||
          url.host.slice(-this.domain.length - 1) === "." + this.domain))
      return true;

    if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
      return true;

    return false;
  },

  toString: () => '[object MatchPattern]'
};

exports.MatchPattern = MatchPattern;
PK
!<33#modules/commonjs/sdk/util/object.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

lazyRequire(this, './array', "flatten");

// Create a shortcut for Array.prototype.slice.call().
const unbind = Function.call.bind(Function.bind, Function.call);
const slice = unbind(Array.prototype.slice);

class DefaultWeakMap extends WeakMap {
  constructor(createItem, items = undefined) {
    super(items);

    this.createItem = createItem;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.createItem(key));
    }

    return super.get(key);
  }
}

class DefaultMap extends Map {
  constructor(createItem, items = undefined) {
    super(items);

    this.createItem = createItem;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.createItem(key));
    }

    return super.get(key);
  }
}

Object.assign(exports, {DefaultMap, DefaultWeakMap});

/**
 * Merges all the properties of all arguments into first argument. If two or
 * more argument objects have own properties with the same name, the property
 * is overridden, with precedence from right to left, implying, that properties
 * of the object on the left are overridden by a same named property of the
 * object on the right.
 *
 * Any argument given with "falsy" value - commonly `null` and `undefined` in
 * case of objects - are skipped.
 *
 * @examples
 *    var a = { bar: 0, a: 'a' }
 *    var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' });
 *    b === a   // true
 *    b.a       // 'a'
 *    b.foo     // 'bar'
 *    b.bar     // 1
 *    b.name    // 'b'
 */
function merge(source) {
  let descriptor = {};

  // `Boolean` converts the first parameter to a boolean value. Any object is
  // converted to `true` where `null` and `undefined` becames `false`. Therefore
  // the `filter` method will keep only objects that are defined and not null.
  slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
    getOwnPropertyIdentifiers(properties).forEach(function(name) {
      descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
    });
  });
  return Object.defineProperties(source, descriptor);
}
exports.merge = merge;

/**
 * Returns an object that inherits from the first argument and contains all the
 * properties from all following arguments.
 * `extend(source1, source2, source3)` is equivalent of
 * `merge(Object.create(source1), source2, source3)`.
 */
function extend(source) {
  let rest = slice(arguments, 1);
  rest.unshift(Object.create(source));
  return merge.apply(null, rest);
}
exports.extend = extend;

function has(obj, key) {
  return obj.hasOwnProperty(key);
}
exports.has = has;

function each(obj, fn) {
  for (let key in obj) has(obj, key) && fn(obj[key], key, obj);
}
exports.each = each;

/**
 * Like `merge`, except no property descriptors are manipulated, for use
 * with platform objects. Identical to underscore's `extend`. Useful for
 * merging XPCOM objects
 */
function safeMerge(source) {
  slice(arguments, 1).forEach(function onEach (obj) {
    for (let prop in obj) source[prop] = obj[prop];
  });
  return source;
}
exports.safeMerge = safeMerge;

/*
 * Returns a copy of the object without omitted properties
 */
function omit(source, ...values) {
  let copy = {};
  let keys = flatten(values);
  for (let prop in source)
    if (!~keys.indexOf(prop))
      copy[prop] = source[prop];
  return copy;
}
exports.omit = omit;

// get object's own property Symbols and/or Names, including nonEnumerables by default
function getOwnPropertyIdentifiers(object, options = { names: true, symbols: true, nonEnumerables: true }) {
  const symbols = !options.symbols ? [] :
                  Object.getOwnPropertySymbols(object);
  const names = !options.names ? [] :
                options.nonEnumerables ? Object.getOwnPropertyNames(object) :
                Object.keys(object);
  return [...names, ...symbols];
}
exports.getOwnPropertyIdentifiers = getOwnPropertyIdentifiers;
PK
!< "modules/commonjs/sdk/util/rules.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Class } = require('../core/heritage');
lazyRequire(this, './match-pattern', "MatchPattern");
lazyRequire(this, '../event/core', "emit");
const { EventTarget } = require('../event/target');
const { List, addListItem, removeListItem } = require('./list');

// Should deprecate usage of EventEmitter/compose
const Rules = Class({
  implements: [
    EventTarget,
    List
  ],
  add: function(...rules) {
    return [].concat(rules).forEach(function onAdd(rule) {
      addListItem(this, rule);
      emit(this, 'add', rule);
    }, this);
  },
  remove: function(...rules) {
    return [].concat(rules).forEach(function onRemove(rule) {
      removeListItem(this, rule);
      emit(this, 'remove', rule);
    }, this);
  },
  get: function(rule) {
    let found = false;
    for (let i in this) if (this[i] === rule) found = true;
    return found;
  },
  // Returns true if uri matches atleast one stored rule
  matchesAny: function(uri) {
    return !!filterMatches(this, uri).length;
  },
  toString: () => '[object Rules]'
});
exports.Rules = Rules;

function filterMatches(instance, uri) {
  let matches = [];
  for (let i in instance) {
    if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
  }
  return matches;
}
PK
!<zE@@%modules/commonjs/sdk/util/sequence.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "experimental"
};

// Disclamer:
// In this module we'll have some common argument / variable names
// to hint their type or behavior.
//
// - `f` stands for "function" that is intended to be side effect
//   free.
// - `p` stands for "predicate" that is function which returns logical
//   true or false and is intended to be side effect free.
// - `x` / `y` single item of the sequence.
// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
//    type of the items in sequence, so sequence is not of the same item.
// - `_` used for argument(s) or variable(s) who's values are ignored.

const { complement, flip, identity } = require("../lang/functional");
lazyRequire(this, "../lang/type", "isArray", "isArguments", "isMap", "isSet",
            "isGenerator", "isString", "isBoolean", "isNumber");

const Sequence = function Sequence(iterator) {
  if (!isGenerator(iterator)) {
    throw TypeError("Expected generator argument");
  }

  this[Symbol.iterator] = iterator;
};
exports.Sequence = Sequence;

const polymorphic = dispatch => x =>
  x === null ? dispatch.null(null) :
  x === void(0) ? dispatch.void(void(0)) :
  isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
  isString(x) ? (dispatch.string || dispatch.indexed)(x) :
  isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
  isMap(x) ? dispatch.map(x) :
  isSet(x) ? dispatch.set(x) :
  isNumber(x) ? dispatch.number(x) :
  isBoolean(x) ? dispatch.boolean(x) :
  dispatch.default(x);

const nogen = function*() {};
const empty = () => new Sequence(nogen);
exports.empty = empty;

const seq = polymorphic({
  null: empty,
  void: empty,
  array: identity,
  string: identity,
  arguments: identity,
  map: identity,
  set: identity,
  default: x => x instanceof Sequence ? x : new Sequence(x)
});
exports.seq = seq;

// Function to cast seq to string.
const string = (...etc) => "".concat(...etc);
exports.string = string;

// Function for casting seq to plain object.
const object = (...pairs) => {
  let result = {};
  for (let [key, value] of pairs)
    result[key] = value;

  return result;
};
exports.object = object;

// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
// and creates lazy sequence of it's items. Note that function does
// not take `nsISimpleEnumerator` itslef because that would allow
// single iteration, which would not be consistent with rest of the
// lazy sequences.
const fromEnumerator = getEnumerator => seq(function* () {
  const enumerator = getEnumerator();
  while (enumerator.hasMoreElements())
   yield enumerator.getNext();
});
exports.fromEnumerator = fromEnumerator;

// Takes `object` and returns lazy sequence of own `[key, value]`
// pairs (does not include inherited and non enumerable keys).
const pairs = polymorphic({
  null: empty,
  void: empty,
  map: identity,
  indexed: indexed => seq(function* () {
    const count = indexed.length;
    let index = 0;
    while (index < count) {
      yield [index, indexed[index]];
      index = index + 1;
    }
  }),
  default: object => seq(function* () {
    for (let key of Object.keys(object))
      yield [key, object[key]];
  })
});
exports.pairs = pairs;

const names = polymorphic({
  null: empty,
  void: empty,
  default: object => seq(function*() {
    for (let name of Object.getOwnPropertyNames(object)) {
      yield name;
    }
  })
});
exports.names = names;

const symbols = polymorphic({
  null: empty,
  void: empty,
  default: object => seq(function* () {
    for (let symbol of Object.getOwnPropertySymbols(object)) {
      yield symbol;
    }
  })
});
exports.symbols = symbols;

const keys = polymorphic({
  null: empty,
  void: empty,
  indexed: indexed => seq(function* () {
    const count = indexed.length;
    let index = 0;
    while (index < count) {
      yield index;
      index = index + 1;
    }
  }),
  map: map => seq(function* () {
    for (let [key, _] of map)
      yield key;
  }),
  default: object => seq(function* () {
    for (let key of Object.keys(object))
      yield key;
  })
});
exports.keys = keys;


const values = polymorphic({
  null: empty,
  void: empty,
  set: identity,
  indexed: indexed => seq(function* () {
    const count = indexed.length;
    let index = 0;
    while (index < count) {
      yield indexed[index];
      index = index + 1;
    }
  }),
  map: map => seq(function* () {
    for (let [_, value] of map) yield value;
  }),
  default: object => seq(function* () {
    for (let key of Object.keys(object)) yield object[key];
  })
});
exports.values = values;



// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
// `f` must be free of side-effects. Note that returned
// sequence is infinite so it must be consumed partially.
//
// Implements clojure iterate:
// http://clojuredocs.org/clojure_core/clojure.core/iterate
const iterate = (f, x) => seq(function* () {
  let state = x;
  while (true) {
    yield state;
    state = f(state);
  }
});
exports.iterate = iterate;

// Returns a lazy sequence of the items in sequence for which `p(item)`
// returns `true`. `p` must be free of side-effects.
//
// Implements clojure filter:
// http://clojuredocs.org/clojure_core/clojure.core/filter
const filter = (p, sequence) => seq(function* () {
  if (sequence !== null && sequence !== void(0)) {
    for (let item of sequence) {
      if (p(item))
        yield item;
    }
  }
});
exports.filter = filter;

// Returns a lazy sequence consisting of the result of applying `f` to the
// set of first items of each sequence, followed by applying f to the set
// of second items in each sequence, until any one of the sequences is
// exhausted. Any remaining items in other sequences are ignored. Function
// `f` should accept number-of-sequences arguments.
//
// Implements clojure map:
// http://clojuredocs.org/clojure_core/clojure.core/map
const map = (f, ...sequences) => seq(function* () {
  const count = sequences.length;
  // Optimize a single sequence case
  if (count === 1) {
    let [sequence] = sequences;
    if (sequence !== null && sequence !== void(0)) {
      for (let item of sequence)
        yield f(item);
    }
  }
  else {
    // define args array that will be recycled on each
    // step to aggregate arguments to be passed to `f`.
    let args = [];
    // define inputs to contain started generators.
    let inputs = [];

    let index = 0;
    while (index < count) {
      inputs[index] = sequences[index][Symbol.iterator]();
      index = index + 1;
    }

    // Run loop yielding of applying `f` to the set of
    // items at each step until one of the `inputs` is
    // exhausted.
    let done = false;
    while (!done) {
      let index = 0;
      let value = void(0);
      while (index < count && !done) {
        ({ done, value } = inputs[index].next());

        // If input is not exhausted yet store value in args.
        if (!done) {
          args[index] = value;
          index = index + 1;
        }
      }

      // If none of the inputs is exhasted yet, `args` contain items
      // from each input so we yield application of `f` over them.
      if (!done)
        yield f(...args);
    }
  }
});
exports.map = map;

// Returns a lazy sequence of the intermediate values of the reduction (as
// per reduce) of sequence by `f`, starting with `initial` value if provided.
//
// Implements clojure reductions:
// http://clojuredocs.org/clojure_core/clojure.core/reductions
const reductions = (...params) => {
  const count = params.length;
  let hasInitial = false;
  let f, initial, source;
  if (count === 2) {
    [f, source] = params;
  }
  else if (count === 3) {
    [f, initial, source] = params;
    hasInitial = true;
  }
  else {
    throw Error("Invoked with wrong number of arguments: " + count);
  }

  const sequence = seq(source);

  return seq(function* () {
    let started = hasInitial;
    let result = void(0);

    // If initial is present yield it.
    if (hasInitial)
      yield (result = initial);

    // For each item of the sequence accumulate new result.
    for (let item of sequence) {
      // If nothing has being yield yet set result to first
      // item and yield it.
      if (!started) {
        started = true;
        yield (result = item);
      }
      // Otherwise accumulate new result and yield it.
      else {
        yield (result = f(result, item));
      }
    }

    // If nothing has being yield yet it's empty sequence and no
    // `initial` was provided in which case we need to yield `f()`.
    if (!started)
      yield f();
  });
};
exports.reductions = reductions;

// `f` should be a function of 2 arguments. If `initial` is not supplied,
// returns the result of applying `f` to the first 2 items in sequence, then
// applying `f` to that result and the 3rd item, etc. If sequence contains no
// items, `f` must accept no arguments as well, and reduce returns the
// result of calling f with no arguments. If sequence has only 1 item, it
// is returned and `f` is not called. If `initial` is supplied, returns the
// result of applying `f` to `initial` and the first item in  sequence, then
// applying `f` to that result and the 2nd item, etc. If sequence contains no
// items, returns `initial` and `f` is not called.
//
// Implements clojure reduce:
// http://clojuredocs.org/clojure_core/clojure.core/reduce
const reduce = (...args) => {
  const xs = reductions(...args);
  let x;
  for (x of xs) void(0);
  return x;
};
exports.reduce = reduce;

const each = (f, sequence) => {
  for (let x of seq(sequence)) void(f(x));
};
exports.each = each;


const inc = x => x + 1;
// Returns the number of items in the sequence. `count(null)` && `count()`
// returns `0`. Also works on strings, arrays, Maps & Sets.

// Implements clojure count:
// http://clojuredocs.org/clojure_core/clojure.core/count
const count = polymorphic({
  null: _ => 0,
  void: _ => 0,
  indexed: indexed => indexed.length,
  map: map => map.size,
  set: set => set.size,
  default: xs => reduce(inc, 0, xs)
});
exports.count = count;

// Returns `true` if sequence has no items.

// Implements clojure empty?:
// http://clojuredocs.org/clojure_core/clojure.core/empty_q
const isEmpty = sequence => {
  // Treat `null` and `undefined` as empty sequences.
  if (sequence === null || sequence === void(0))
    return true;

  // If contains any item non empty so return `false`.
  for (let _ of sequence)
    return false;

  // If has not returned yet, there was nothing to iterate
  // so it's empty.
  return true;
};
exports.isEmpty = isEmpty;

const and = (a, b) => a && b;

// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
// `false`.
//
// Implements clojure every?:
// http://clojuredocs.org/clojure_core/clojure.core/every_q
const isEvery = (p, sequence) => {
  if (sequence !== null && sequence !== void(0)) {
    for (let item of sequence) {
      if (!p(item))
        return false;
    }
  }
  return true;
};
exports.isEvery = isEvery;

// Returns the first logical true value of (p x) for any x in sequence,
// else `null`.
//
// Implements clojure some:
// http://clojuredocs.org/clojure_core/clojure.core/some
const some = (p, sequence) => {
  if (sequence !== null && sequence !== void(0)) {
    for (let item of sequence) {
      if (p(item))
        return true;
    }
  }
  return null;
};
exports.some = some;

// Returns a lazy sequence of the first `n` items in sequence, or all items if
// there are fewer than `n`.
//
// Implements clojure take:
// http://clojuredocs.org/clojure_core/clojure.core/take
const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
  let count = n;
  for (let item of sequence) {
    yield item;
    count = count - 1;
    if (count === 0) break;
  }
});
exports.take = take;

// Returns a lazy sequence of successive items from sequence while
// `p(item)` returns `true`. `p` must be free of side-effects.
//
// Implements clojure take-while:
// http://clojuredocs.org/clojure_core/clojure.core/take-while
const takeWhile = (p, sequence) => seq(function* () {
  for (let item of sequence) {
    if (!p(item))
      break;

    yield item;
  }
});
exports.takeWhile = takeWhile;

// Returns a lazy sequence of all but the first `n` items in
// sequence.
//
// Implements clojure drop:
// http://clojuredocs.org/clojure_core/clojure.core/drop
const drop = (n, sequence) => seq(function* () {
  if (sequence !== null && sequence !== void(0)) {
    let count = n;
    for (let item of sequence) {
      if (count > 0)
        count = count - 1;
      else
        yield item;
    }
  }
});
exports.drop = drop;

// Returns a lazy sequence of the items in sequence starting from the
// first item for which `p(item)` returns falsy value.
//
// Implements clojure drop-while:
// http://clojuredocs.org/clojure_core/clojure.core/drop-while
const dropWhile = (p, sequence) => seq(function* () {
  let keep = false;
  for (let item of sequence) {
    keep = keep || !p(item);
    if (keep) yield item;
  }
});
exports.dropWhile = dropWhile;

// Returns a lazy sequence representing the concatenation of the
// suplied sequences.
//
// Implements clojure conact:
// http://clojuredocs.org/clojure_core/clojure.core/concat
const concat = (...sequences) => seq(function* () {
  for (let sequence of sequences)
    for (let item of sequence)
      yield item;
});
exports.concat = concat;

// Returns the first item in the sequence.
//
// Implements clojure first:
// http://clojuredocs.org/clojure_core/clojure.core/first
const first = sequence => {
  if (sequence !== null && sequence !== void(0)) {
    for (let item of sequence)
      return item;
  }
  return null;
};
exports.first = first;

// Returns a possibly empty sequence of the items after the first.
//
// Implements clojure rest:
// http://clojuredocs.org/clojure_core/clojure.core/rest
const rest = sequence => drop(1, sequence);
exports.rest = rest;

// Returns the value at the index. Returns `notFound` or `undefined`
// if index is out of bounds.
const nth = (xs, n, notFound) => {
  if (n >= 0) {
    if (isArray(xs) || isArguments(xs) || isString(xs)) {
      return n < xs.length ? xs[n] : notFound;
    }
    else if (xs !== null && xs !== void(0)) {
      let count = n;
      for (let x of xs) {
        if (count <= 0)
          return x;

        count = count - 1;
      }
    }
  }
  return notFound;
};
exports.nth = nth;

// Return the last item in sequence, in linear time.
// If `sequence` is an array or string or arguments
// returns in constant time.
// Implements clojure last:
// http://clojuredocs.org/clojure_core/clojure.core/last
const last = polymorphic({
  null: _ => null,
  void: _ => null,
  indexed: indexed => indexed[indexed.length - 1],
  map: xs => reduce((_, x) => x, xs),
  set: xs => reduce((_, x) => x, xs),
  default: xs => reduce((_, x) => x, xs)
});
exports.last = last;

// Return a lazy sequence of all but the last `n` (default 1) items
// from the give `xs`.
//
// Implements clojure drop-last:
// http://clojuredocs.org/clojure_core/clojure.core/drop-last
const dropLast = flip((xs, n=1) => seq(function* () {
  let ys = [];
  for (let x of xs) {
    ys.push(x);
    if (ys.length > n)
      yield ys.shift();
  }
}));
exports.dropLast = dropLast;

// Returns a lazy sequence of the elements of `xs` with duplicates
// removed
//
// Implements clojure distinct
// http://clojuredocs.org/clojure_core/clojure.core/distinct
const distinct = sequence => seq(function* () {
  let items = new Set();
  for (let item of sequence) {
    if (!items.has(item)) {
      items.add(item);
      yield item;
    }
  }
});
exports.distinct = distinct;

// Returns a lazy sequence of the items in `xs` for which
// `p(x)` returns false. `p` must be free of side-effects.
//
// Implements clojure remove
// http://clojuredocs.org/clojure_core/clojure.core/remove
const remove = (p, xs) => filter(complement(p), xs);
exports.remove = remove;

// Returns the result of applying concat to the result of
// `map(f, xs)`. Thus function `f` should return a sequence.
//
// Implements clojure mapcat
// http://clojuredocs.org/clojure_core/clojure.core/mapcat
const mapcat = (f, sequence) => seq(function* () {
  const sequences = map(f, sequence);
  for (let sequence of sequences)
    for (let item of sequence)
      yield item;
});
exports.mapcat = mapcat;
PK
!<_!modules/commonjs/sdk/util/uuid.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Cc, Ci, components: { ID: parseUUID } } = require('chrome');
const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
                          getService(Ci.nsIUUIDGenerator);

// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned
// if not then new one is generated.
exports.uuid = function uuid(id) {
  return id ? parseUUID(id) : generateUUID();
};
PK
!<+!modules/commonjs/sdk/view/core.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

var { Ci } = require("chrome");
var method = require("../../method/core");

// Returns DOM node associated with a view for
// the given `value`. If `value` has no view associated
// it returns `null`. You can implement this method for
// this type to define what the result should be for it.
var getNodeView = method("getNodeView");
getNodeView.define(x =>
                     x instanceof Ci.nsIDOMNode ? x :
                     x instanceof Ci.nsIDOMWindow ? x :
                     null);
exports.getNodeView = getNodeView;
exports.viewFor = getNodeView;

var getActiveView = method("getActiveView");
exports.getActiveView = getActiveView;
PK
!<9ʃ$modules/commonjs/sdk/webextension.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "experimental"
};

let webExtension;
let waitForWebExtensionAPI;

module.exports = {
  initFromBootstrapAddonParam(data) {
    if (webExtension) {
      throw new Error("'sdk/webextension' module has been already initialized");
    }

    webExtension = data.webExtension;
  },

  startup() {
    if (!webExtension) {
      return Promise.reject(new Error(
        "'sdk/webextension' module is currently disabled. " +
        "('hasEmbeddedWebExtension' option is missing or set to false)"
      ));
    }

    // NOTE: calling `startup` more than once raises an "Embedded Extension already started"
    // error, but given that SDK addons are going to have access to the startup method through
    // an SDK module that can be required in any part of the addon, it will be nicer if any
    // additional startup calls return the startup promise instead of raising an exception,
    // so that the SDK addon can access the API object in the other addon modules without the
    // need to manually pass this promise around.
    if (!waitForWebExtensionAPI) {
      waitForWebExtensionAPI = webExtension.startup();
    }

    return waitForWebExtensionAPI;
  }
};
PK
!<oUU&modules/commonjs/sdk/window/browser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Class } = require('../core/heritage');
const { windowNS } = require('./namespace');
const { on, off, once } = require('../event/core');
const { method } = require('../lang/functional');
const { getWindowTitle } = require('./utils');
const unload = require('../system/unload');
const { EventTarget } = require('../event/target');
const { isPrivate } = require('../private-browsing/utils');
const { isWindowPrivate, isFocused } = require('../window/utils');
const { viewFor } = require('../view/core');

const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';

const BrowserWindow = Class({
  initialize: function initialize(options) {
    EventTarget.prototype.initialize.call(this, options);
    windowNS(this).window = options.window;
  },
  activate: function activate() {
    // TODO
    return null;
  },
  close: function() {
    throw new Error(ERR_FENNEC_MSG);
    return null;
  },
  get title() {
    return getWindowTitle(windowNS(this).window);
  },
  // NOTE: Fennec only has one window, which is assumed below
  // TODO: remove assumption below
  // NOTE: tabs requires windows
  get tabs() {
    return require('../tabs');
  },
  get activeTab() {
    return require('../tabs').activeTab;
  },
  on: method(on),
  removeListener: method(off),
  once: method(once)
});
exports.BrowserWindow = BrowserWindow;

const getWindowView = window => windowNS(window).window;

viewFor.define(BrowserWindow, getWindowView);
isPrivate.define(BrowserWindow, (window) => isWindowPrivate(viewFor(window).window));
isFocused.define(BrowserWindow, (window) => isFocused(viewFor(window).window));
PK
!<0ak
k
%modules/commonjs/sdk/window/events.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { Ci, Cu } = require("chrome");
const { observe } = require("../event/chrome");
const { open } = require("../event/dom");
const { windows } = require("../window/utils");
const { filter, merge, map, expand } = require("../event/utils");

function documentMatches(weakWindow, event) {
  let window = weakWindow.get();
  return window && event.target === window.document;
}

function makeStrictDocumentFilter(window) {
  // Note: Do not define a closure within this function.  Otherwise
  //       you may leak the window argument.
  let weak = Cu.getWeakReference(window);
  return documentMatches.bind(null, weak);
}

function toEventWithDefaultViewTarget({type, target}) {
  return { type: type, target: target.defaultView }
}

// Function registers single shot event listeners for relevant window events
// that forward events to exported event stream.
function eventsFor(window) {
  // NOTE: Do no use pass a closure from this function into a stream
  //       transform function.  You will capture the window in the
  //       closure and leak the window until the event stream is
  //       completely closed.
  let interactive = open(window, "DOMContentLoaded", { capture: true });
  let complete = open(window, "load", { capture: true });
  let states = merge([interactive, complete]);
  let changes = filter(states, makeStrictDocumentFilter(window));
  return map(changes, toEventWithDefaultViewTarget);
}

// Create our event channels.  We do this in a separate function to
// minimize the chance of leaking intermediate objects on the global.
function makeEvents() {
  // In addition to observing windows that are open we also observe windows
  // that are already already opened in case they're in process of loading.
  var opened = windows(null, { includePrivate: true });
  var currentEvents = merge(opened.map(eventsFor));

  // Register system event listeners for top level window open / close.
  function rename({type, target, data}) {
    return { type: rename[type], target: target, data: data }
  }
  rename.domwindowopened = "open";
  rename.domwindowclosed = "close";

  var openEvents = map(observe("domwindowopened"), rename);
  var closeEvents = map(observe("domwindowclosed"), rename);
  var futureEvents = expand(openEvents, ({target}) => eventsFor(target));

  return merge([currentEvents, futureEvents, openEvents, closeEvents]);
}

exports.events = makeEvents();
PK
!<^r&modules/commonjs/sdk/window/helpers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { defer, all } = require('../core/promise');
const events = require('../system/events');
const { open: openWindow, onFocus, getToplevelWindow,
        isInteractive, isStartupFinished, getOuterId } = require('./utils');
const { Ci } = require("chrome");

function open(uri, options) {
  return promise(openWindow.apply(null, arguments), 'load').then(focus);
}
exports.open = open;

function close(window) {
  let deferred = defer();
  let toplevelWindow = getToplevelWindow(window);
  let outerId = getOuterId(toplevelWindow);
  events.on("outer-window-destroyed", function onclose({subject}) {
    let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
    if (id == outerId) {
      events.off("outer-window-destroyed", onclose);
      deferred.resolve();
    }
  }, true);
  window.close();
  return deferred.promise;
}
exports.close = close;

function focus(window) {
  let p = onFocus(window);
  window.focus();
  return p;
}
exports.focus = focus;

function ready(window) {
  let { promise: result, resolve } = defer();

  if (isInteractive(window))
    resolve(window);
  else
    resolve(promise(window, 'DOMContentLoaded'));

  return result;
}
exports.ready = ready;

function startup(window) {
  let { promise: result, resolve } = defer();

  if (isStartupFinished(window)) {
    resolve(window);
  } else {
    events.on("browser-delayed-startup-finished", function listener({subject}) {
      if (subject === window) {
        events.off("browser-delayed-startup-finished", listener);
        resolve(window);
      }
    });
  }

  return result;
}
exports.startup = startup;

function promise(target, evt, capture) {
  let deferred = defer();
  capture = !!capture;

  target.addEventListener(evt, function() {
    deferred.resolve(target);
  }, {capture, once: true});

  return deferred.promise;
}
exports.promise = promise;
PK
!<h(modules/commonjs/sdk/window/namespace.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

exports.windowNS = require('../core/namespace').ns();
PK
!<sko6o6$modules/commonjs/sdk/window/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'unstable'
};

const { Cc, Ci } = require('chrome');
lazyRequireModule(this, "../util/array", "array");
const { defer } = require('sdk/core/promise');
const { dispatcher } = require("../util/dispatcher");

const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                       getService(Ci.nsIWindowWatcher);
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                        getService(Ci.nsIAppShellService);
const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
           getService(Ci.nsIWindowMediator);
const io = Cc['@mozilla.org/network/io-service;1'].
           getService(Ci.nsIIOService);
const FM = Cc["@mozilla.org/focus-manager;1"].
              getService(Ci.nsIFocusManager);

const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';

const prefs = require("../preferences/service");
const BROWSER = 'navigator:browser',
      URI_BROWSER = prefs.get('browser.chromeURL', null),
      NAME = '_blank',
      FEATURES = 'chrome,all,dialog=no,non-private';

function isWindowPrivate(win) {
  if (!win)
    return false;

  // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
  // and the app is Firefox, then assume per-window private browsing is
  // enabled.
  try {
    return win.QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIWebNavigation)
                  .QueryInterface(Ci.nsILoadContext)
                  .usePrivateBrowsing;
  }
  catch(e) {}

  // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw.
  try {
    return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
  }
  catch (e) {}

  return false;
}
exports.isWindowPrivate = isWindowPrivate;

function getMostRecentBrowserWindow() {
  return getMostRecentWindow(BROWSER);
}
exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;

function getHiddenWindow() {
  return appShellService.hiddenDOMWindow;
}
exports.getHiddenWindow = getHiddenWindow;

function getMostRecentWindow(type) {
  return WM.getMostRecentWindow(type);
}
exports.getMostRecentWindow = getMostRecentWindow;

/**
 * Returns the ID of the window's current inner window.
 */
function getInnerId(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
                getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
};
exports.getInnerId = getInnerId;

/**
 * Returns the ID of the window's outer window.
 */
function getOuterId(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
                getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
};
exports.getOuterId = getOuterId;

/**
 * Returns window by the outer window id.
 */
const getByOuterId = WM.getOuterWindowWithId;
exports.getByOuterId = getByOuterId;

const getByInnerId = WM.getCurrentInnerWindowWithId;
exports.getByInnerId = getByInnerId;

/**
 * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
 */
function getXULWindow(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIWebNavigation).
    QueryInterface(Ci.nsIDocShellTreeItem).
    treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIXULWindow);
};
exports.getXULWindow = getXULWindow;

function getDOMWindow(xulWindow) {
  return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIDOMWindow);
}
exports.getDOMWindow = getDOMWindow;

/**
 * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
 */
function getBaseWindow(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIWebNavigation).
    QueryInterface(Ci.nsIDocShell).
    QueryInterface(Ci.nsIDocShellTreeItem).
    treeOwner.
    QueryInterface(Ci.nsIBaseWindow);
}
exports.getBaseWindow = getBaseWindow;

/**
 * Returns the `nsIDOMWindow` toplevel window for any child/inner window
 */
function getToplevelWindow(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsIDocShellTreeItem)
               .rootTreeItem
               .QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindow);
}
exports.getToplevelWindow = getToplevelWindow;

function getWindowDocShell(window) {
  return window.gBrowser.docShell;
}
exports.getWindowDocShell = getWindowDocShell;

function getWindowLoadingContext(window) {
  return getWindowDocShell(window).
         QueryInterface(Ci.nsILoadContext);
}
exports.getWindowLoadingContext = getWindowLoadingContext;

const isTopLevel = window => window && getToplevelWindow(window) === window;
exports.isTopLevel = isTopLevel;

/**
 * Takes hash of options and serializes it to a features string that
 * can be used passed to `window.open`. For more details on features string see:
 * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
 */
function serializeFeatures(options) {
  return Object.keys(options).reduce(function(result, name) {
    let value = options[name];

    // the chrome and private features are special
    if ((name == 'private' || name == 'chrome' || name == 'all'))
      return result + ((value === true) ? ',' + name : '');

    return result + ',' + name + '=' +
           (value === true ? 'yes' : value === false ? 'no' : value);
  }, '').substr(1);
}

/**
 * Opens a top level window and returns it's `nsIDOMWindow` representation.
 * @params {String} uri
 *    URI of the document to be loaded into window.
 * @params {nsIDOMWindow} options.parent
 *    Used as parent for the created window.
 * @params {String} options.name
 *    Optional name that is assigned to the window.
 * @params {Object} options.features
 *    Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
 */
function open(uri, options) {
  uri = uri || URI_BROWSER;
  options = options || {};

  if (!uri)
    throw new Error('browser.chromeURL is undefined, please provide an explicit uri');

  if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri).scheme) < 0)
    throw new Error('only chrome, resource and data uris are allowed');

  let newWindow = windowWatcher.
    openWindow(options.parent || null,
               uri,
               options.name || null,
               options.features ? serializeFeatures(options.features) : null,
               options.args || null);

  return newWindow;
}
exports.open = open;

function onFocus(window) {
  let { resolve, promise } = defer();

  if (isFocused(window)) {
    resolve(window);
  }
  else {
    window.addEventListener("focus", function() {
      resolve(window);
    }, {capture: true, once: true});
  }

  return promise;
}
exports.onFocus = onFocus;

var isFocused = dispatcher("window-isFocused");
isFocused.when(x => x instanceof Ci.nsIDOMWindow, (window) => {
  const FM = Cc["@mozilla.org/focus-manager;1"].
                getService(Ci.nsIFocusManager);

  let childTargetWindow = {};
  FM.getFocusedElementForWindow(window, true, childTargetWindow);
  childTargetWindow = childTargetWindow.value;

  let focusedChildWindow = {};
  if (FM.activeWindow) {
    FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow);
    focusedChildWindow = focusedChildWindow.value;
  }

  return (focusedChildWindow === childTargetWindow);
});
exports.isFocused = isFocused;

/**
 * Opens a top level window and returns it's `nsIDOMWindow` representation.
 * Same as `open` but with more features
 * @param {Object} options
 *
 */
function openDialog(options) {
  options = options || {};

  let features = options.features || FEATURES;
  let featureAry = features.toLowerCase().split(',');

  if (!!options.private) {
    // add private flag if private window is desired
    if (!array.has(featureAry, 'private')) {
      featureAry.push('private');
    }

    // remove the non-private flag ig a private window is desired
    let nonPrivateIndex = featureAry.indexOf('non-private');
    if (nonPrivateIndex >= 0) {
      featureAry.splice(nonPrivateIndex, 1);
    }

    features = featureAry.join(',');
  }

  let browser = getMostRecentBrowserWindow();

  // if there is no browser then do nothing
  if (!browser)
    return undefined;

  let newWindow = browser.openDialog.apply(
      browser,
      array.flatten([
        options.url || URI_BROWSER,
        options.name || NAME,
        features,
        options.args || null
      ])
  );

  return newWindow;
}
exports.openDialog = openDialog;

/**
 * Returns an array of all currently opened windows.
 * Note that these windows may still be loading.
 */
function windows(type, options) {
  options = options || {};
  let list = [];
  let winEnum = WM.getEnumerator(type);
  while (winEnum.hasMoreElements()) {
    let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
    // Only add non-private windows when pb permission isn't set,
    // unless an option forces the addition of them.
    if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) {
      list.push(window);
    }
  }
  return list;
}
exports.windows = windows;

/**
 * Check if the given window is interactive.
 * i.e. if its "DOMContentLoaded" event has already been fired.
 * @params {nsIDOMWindow} window
 */
const isInteractive = window =>
  window.document.readyState === "interactive" ||
  isDocumentLoaded(window) ||
  // XUL documents stays '"uninitialized"' until it's `readyState` becomes
  // `"complete"`.
  isXULDocumentWindow(window) && window.document.readyState === "interactive";
exports.isInteractive = isInteractive;

/**
 * Check if the given browser window has finished the startup.
 * @params {nsIDOMWindow} window
 */
const isStartupFinished = (window) =>
  isBrowser(window) &&
  window.gBrowserInit &&
  window.gBrowserInit.delayedStartupFinished;

exports.isStartupFinished = isStartupFinished;

const isXULDocumentWindow = ({document}) =>
  document.documentElement &&
  document.documentElement.namespaceURI === XUL_NS;

/**
 * Check if the given window is completely loaded.
 * i.e. if its "load" event has already been fired and all possible DOM content
 * is done loading (the whole DOM document, images content, ...)
 * @params {nsIDOMWindow} window
 */
function isDocumentLoaded(window) {
  return window.document.readyState == "complete";
}
exports.isDocumentLoaded = isDocumentLoaded;

function isBrowser(window) {
  try {
    return window.document.documentElement.getAttribute("windowtype") === BROWSER;
  }
  catch (e) {}
  return false;
};
exports.isBrowser = isBrowser;

function getWindowTitle(window) {
  return window && window.document ? window.document.title : null;
}
exports.getWindowTitle = getWindowTitle;

function isXULBrowser(window) {
  return !!(isBrowser(window) && window.XULBrowserWindow);
}
exports.isXULBrowser = isXULBrowser;

/**
 * Returns the most recent focused window
 */
function getFocusedWindow() {
  let window = WM.getMostRecentWindow(BROWSER);

  return window ? window.document.commandDispatcher.focusedWindow : null;
}
exports.getFocusedWindow = getFocusedWindow;

/**
 * Returns the focused browser window if any, or the most recent one.
 * Opening new window, updates most recent window, but focus window
 * changes later; so most recent window and focused window are not always
 * the same.
 */
function getFocusedBrowser() {
  let window = FM.activeWindow;
  return isBrowser(window) ? window : getMostRecentBrowserWindow()
}
exports.getFocusedBrowser = getFocusedBrowser;

/**
 * Returns the focused element in the most recent focused window
 */
function getFocusedElement() {
  let window = WM.getMostRecentWindow(BROWSER);

  return window ? window.document.commandDispatcher.focusedElement : null;
}
exports.getFocusedElement = getFocusedElement;

function getFrames(window) {
  return Array.slice(window.frames).reduce(function(frames, frame) {
    return frames.concat(frame, getFrames(frame));
  }, []);
}
exports.getFrames = getFrames;

function getScreenPixelsPerCSSPixel(window) {
  return window.QueryInterface(Ci.nsIInterfaceRequestor).
                getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel;
}
exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel;

function getOwnerBrowserWindow(node) {
  /**
  Takes DOM node and returns browser window that contains it.
  **/
  let window = getToplevelWindow(node.ownerGlobal);
  // If anchored window is browser then it's target browser window.
  return isBrowser(window) ? window : null;
}
exports.getOwnerBrowserWindow = getOwnerBrowserWindow;

function getParentWindow(window) {
  try {
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebNavigation)
      .QueryInterface(Ci.nsIDocShellTreeItem).parent
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindow);
  }
  catch (e) {}
  return null;
}
exports.getParentWindow = getParentWindow;


function getParentFrame(window) {
  try {
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebNavigation)
      .QueryInterface(Ci.nsIDocShellTreeItem).parent
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIDOMWindow);
  }
  catch (e) {}
  return null;
}
exports.getParentWindow = getParentWindow;

// The element in which the window is embedded, or `null`
// if the window is top-level. Similar to `window.frameElement`
// but can cross chrome-content boundries.
const getFrameElement = target =>
  (target instanceof Ci.nsIDOMDocument ? target.defaultView : target).
  QueryInterface(Ci.nsIInterfaceRequestor).
  getInterface(Ci.nsIDOMWindowUtils).
  containerElement;
exports.getFrameElement = getFrameElement;
PK
!<%')
)
&modules/commonjs/sdk/windows/fennec.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Class } = require('../core/heritage');
const { BrowserWindow } = require('../window/browser');
const { WindowTracker } = require('../deprecated/window-utils');
const { isBrowser, getMostRecentBrowserWindow } = require('../window/utils');
const { windowNS } = require('../window/namespace');
const { on, off, once, emit } = require('../event/core');
const { method } = require('../lang/functional');
const { EventTarget } = require('../event/target');
const { List, addListItem } = require('../util/list');

const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';

// NOTE: On Fennec there is only one window.

var BrowserWindows = Class({
  implements: [ List ],
  extends: EventTarget,
  initialize: function() {
    List.prototype.initialize.apply(this);
  },
  get activeWindow() {
    let window = getMostRecentBrowserWindow();
    return window ? getBrowserWindow({window: window}) : null;
  },
  open: function open(options) {
    throw new Error(ERR_FENNEC_MSG);
    return null;
  }
});
const browserWindows = exports.browserWindows = BrowserWindows();


/**
 * Gets a `BrowserWindow` for the given `chromeWindow` if previously
 * registered, `null` otherwise.
 */
function getRegisteredWindow(chromeWindow) {
  for (let window of browserWindows) {
    if (chromeWindow === windowNS(window).window)
      return window;
  }

  return null;
}

/**
 * Gets a `BrowserWindow` for the provided window options obj
 * @params {Object} options
 *    Options that are passed to the the `BrowserWindow`
 * @returns {BrowserWindow}
 */
function getBrowserWindow(options) {
  let window = null;

  // if we have a BrowserWindow already then use it
  if ('window' in options)
    window = getRegisteredWindow(options.window);
  if (window)
    return window;

  // we don't have a BrowserWindow yet, so create one
  window = BrowserWindow(options);
  addListItem(browserWindows, window);
  return window;
}

WindowTracker({
  onTrack: function onTrack(chromeWindow) {
    if (!isBrowser(chromeWindow)) return;
    let window = getBrowserWindow({ window: chromeWindow });
    emit(browserWindows, 'open', window);
  },
  onUntrack: function onUntrack(chromeWindow) {
    if (!isBrowser(chromeWindow)) return;
    let window = getBrowserWindow({ window: chromeWindow });
    emit(browserWindows, 'close', window);
  }
});
PK
!<.<iZss'modules/commonjs/sdk/windows/firefox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Class } = require('../core/heritage');
const { observer } = require('./observer');
const { isBrowser, getMostRecentBrowserWindow, windows, open, getInnerId,
        getWindowTitle, getToplevelWindow, isFocused, isWindowPrivate } = require('../window/utils');
const { List, addListItem, removeListItem } = require('../util/list');
const { viewFor } = require('../view/core');
const { modelFor } = require('../model/core');
const { emit, emitOnObject, setListeners } = require('../event/core');
const { once } = require('../dom/events');
const { EventTarget } = require('../event/target');
const { getSelectedTab } = require('../tabs/utils');
const { Cc, Ci } = require('chrome');
const { Options } = require('../tabs/common');
const system = require('../system/events');
const { ignoreWindow, isPrivate, isWindowPBSupported } = require('../private-browsing/utils');
const { data, isPrivateBrowsingSupported } = require('../self');
const { setImmediate } = require('../timers');

const supportPrivateWindows = isPrivateBrowsingSupported && isWindowPBSupported;

const modelsFor = new WeakMap();
const viewsFor = new WeakMap();

const Window = Class({
  implements: [EventTarget],
  initialize: function(domWindow) {
    modelsFor.set(domWindow, this);
    viewsFor.set(this, domWindow);
  },

  get title() {
    return getWindowTitle(viewsFor.get(this));
  },

  activate: function() {
    viewsFor.get(this).focus();
  },

  close: function(callback) {
    let domWindow = viewsFor.get(this);

    if (callback) {
      // We want to catch the close event immediately after the close events are
      // emitted everywhere but without letting the event loop spin. Registering
      // for the same events as windowEventListener but afterwards does this
      let listener = (event, closedWin) => {
        if (event != "close" || closedWin != domWindow)
          return;

        observer.off("*", listener);
        callback();
      }

      observer.on("*", listener);
    }

    domWindow.close();
  }
});

const windowTabs = new WeakMap();

const BrowserWindow = Class({
  extends: Window,

  get tabs() {
    let tabs = windowTabs.get(this);
    if (tabs)
      return tabs;

    return new WindowTabs(this);
  }
});

const WindowTabs = Class({
  implements: [EventTarget],
  extends: List,
  initialize: function(window) {
    List.prototype.initialize.call(this);
    windowTabs.set(window, this);
    viewsFor.set(this, viewsFor.get(window));

    // Make sure the tabs module has loaded and found all existing tabs
    const tabs = require('../tabs');

    for (let tab of tabs) {
      if (tab.window == window)
        addListItem(this, tab);
    }
  },

  get activeTab() {
    return modelFor(getSelectedTab(viewsFor.get(this)));
  },

  open: function(options) {
    options = Options(options);

    let domWindow = viewsFor.get(this);
    let { Tab } = require('../tabs/tab-firefox');

    // The capturing listener will see the TabOpen event before
    // sdk/tabs/observer giving us time to set up the tab and listeners before
    // the real open event is fired
    let listener = event => {
      new Tab(event.target, options);
    };

    once(domWindow, "TabOpen", listener, true);
    domWindow.gBrowser.addTab(options.url);
  }
});

const BrowserWindows = Class({
  implements: [EventTarget],
  extends: List,
  initialize: function() {
    List.prototype.initialize.call(this);
  },

  get activeWindow() {
    let domWindow = getMostRecentBrowserWindow();
    if (ignoreWindow(domWindow))
      return null;
    return modelsFor.get(domWindow);
  },

  open: function(options) {
    if (typeof options == "string")
      options = { url: options };

    let { url, isPrivate } = options;
    if (url)
      url = data.url(url);

    let args = Cc["@mozilla.org/supports-string;1"].
               createInstance(Ci.nsISupportsString);
    args.data = url;

    let features = {
      chrome: true,
      all: true,
      dialog: false
    };
    features.private = supportPrivateWindows && isPrivate;

    let domWindow = open(null, {
      parent: null,
      name: "_blank",
      features,
      args
    })

    let window = makeNewWindow(domWindow, true);
    setListeners(window, options);
    return window;
  }
});

const browserWindows = new BrowserWindows();
exports.browserWindows = browserWindows;

function windowEmit(window, event, ...args) {
  if (window instanceof BrowserWindow && (event == "open" || event == "close"))
    emitOnObject(window, event, browserWindows, window, ...args);
  else
    emit(window, event, window, ...args);

  if (window instanceof BrowserWindow)
    emit(browserWindows, event, window, ...args);
}

function makeNewWindow(domWindow, browserHint = false) {
  if (browserHint || isBrowser(domWindow))
    return new BrowserWindow(domWindow);
  else
    return new Window(domWindow);
}

for (let domWindow of windows(null, {includePrivate: supportPrivateWindows})) {
  let window = makeNewWindow(domWindow);
  if (window instanceof BrowserWindow)
    addListItem(browserWindows, window);
}

var windowEventListener = (event, domWindow, ...args) => {
  let toplevelWindow = getToplevelWindow(domWindow);

  if (ignoreWindow(toplevelWindow))
    return;

  let window = modelsFor.get(toplevelWindow);
  if (!window)
    window = makeNewWindow(toplevelWindow);

  if (isBrowser(toplevelWindow)) {
    if (event == "open")
      addListItem(browserWindows, window);
    else if (event == "close")
      removeListItem(browserWindows, window);
  }

  windowEmit(window, event, ...args);

  // The window object shouldn't be reachable after closed
  if (event == "close") {
    viewsFor.delete(window);
    modelsFor.delete(toplevelWindow);
  }
};
observer.on("*", windowEventListener);

viewFor.define(BrowserWindow, window => {
  return viewsFor.get(window);
})

const isBrowserWindow = (x) => x instanceof BrowserWindow;
isPrivate.when(isBrowserWindow, (w) => isWindowPrivate(viewsFor.get(w)));
isFocused.when(isBrowserWindow, (w) => isFocused(viewsFor.get(w)));
PK
!<U
(modules/commonjs/sdk/windows/observer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

module.metadata = {
  "stability": "unstable"
};

const { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { Cu } = require("chrome");

// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
  initialize() {
    // Using `WindowTracker` to track window events.
    WindowTracker({
      onTrack: chromeWindow => {
        emit(this, "open", chromeWindow);
        this.observe(chromeWindow);
      },
      onUntrack: chromeWindow => {
        emit(this, "close", chromeWindow);
        this.ignore(chromeWindow);
      }
    });
  },
  implements: [EventTarget, DOMEventAssembler],
  /**
   * Events that are supported and emitted by the module.
   */
  supportedEventsTypes: [ "activate", "deactivate" ],
  /**
   * Function handles all the supported events on all the windows that are
   * observed. Method is used to proxy events to the listeners registered on
   * this event emitter.
   * @param {Event} event
   *    Keyboard event being emitted.
   */
  handleEvent(event) {
    // Ignore events from windows in the child process as they can't be top-level
    if (Cu.isCrossProcessWrapper(event.target))
      return;
    emit(this, event.type, event.target, event);
  }
});

exports.observer = new Observer();
PK
!<m//+modules/commonjs/sdk/windows/tabs-fennec.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

const { Class } = require('../core/heritage');
const { Tab } = require('../tabs/tab');
const { browserWindows } = require('./fennec');
const { windowNS } = require('../window/namespace');
const { tabsNS, tabNS } = require('../tabs/namespace');
const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser,
        getTabContentWindow } = require('../tabs/utils');
const { Options } = require('../tabs/common');
const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers');
const { on, once, off, emit } = require('../event/core');
const { method } = require('../lang/functional');
const { EVENTS } = require('../tabs/events');
const { EventTarget } = require('../event/target');
const { when: unload } = require('../system/unload');
const { windowIterator } = require('../deprecated/window-utils');
const { List, addListItem, removeListItem } = require('../util/list');
const { isPrivateBrowsingSupported, data } = require('../self');
const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils');

const mainWindow = windowNS(browserWindows.activeWindow).window;

const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';

const supportPrivateTabs = isPrivateBrowsingSupported && isTabPBSupported;

const Tabs = Class({
  implements: [ List ],
  extends: EventTarget,
  initialize: function initialize(options) {
    let tabsInternals = tabsNS(this);
    let window = tabsNS(this).window = options.window || mainWindow;

    EventTarget.prototype.initialize.call(this, options);
    List.prototype.initialize.apply(this, getTabs(window).map(Tab));

    // TabOpen event
    window.BrowserApp.deck.addEventListener(EVENTS.open.dom, onTabOpen);

    // TabSelect
    window.BrowserApp.deck.addEventListener(EVENTS.activate.dom, onTabSelect);
  },
  get activeTab() {
    return getTabForRawTab(getSelectedTab(tabsNS(this).window));
  },
  open: function(options) {
    options = Options(options);
    let activeWin = browserWindows.activeWindow;

    if (options.isPinned) {
      console.error(ERR_FENNEC_MSG); // TODO
    }

    let url = options.url ? data.url(options.url) : options.url;
    let rawTab = openTab(windowNS(activeWin).window, url, {
      inBackground: options.inBackground,
      isPrivate: supportPrivateTabs && options.isPrivate
    });

    // by now the tab has been created
    let tab = getTabForRawTab(rawTab);

    if (options.onClose)
      tab.on('close', options.onClose);

    if (options.onOpen) {
      // NOTE: on Fennec this will be true
      if (tabNS(tab).opened)
        options.onOpen(tab);

      tab.on('open', options.onOpen);
    }

    if (options.onReady)
      tab.on('ready', options.onReady);

    if (options.onLoad)
      tab.on('load', options.onLoad);

    if (options.onPageShow)
      tab.on('pageshow', options.onPageShow);

    if (options.onActivate)
      tab.on('activate', options.onActivate);

    return tab;
  }
});
var gTabs = exports.tabs = Tabs(mainWindow);

function tabsUnloader(event, window) {
  window = window || (event && event.target);
  if (!(window && window.BrowserApp))
    return;
  window.BrowserApp.deck.removeEventListener(EVENTS.open.dom, onTabOpen);
  window.BrowserApp.deck.removeEventListener(EVENTS.activate.dom, onTabSelect);
}

// unload handler
unload(function() {
  for (let window in windowIterator()) {
    tabsUnloader(null, window);
  }
});

function addTab(tab) {
  addListItem(gTabs, tab);
  return tab;
}

function removeTab(tab) {
  removeListItem(gTabs, tab);
  return tab;
}

// TabOpen
function onTabOpen(event) {
  let browser = event.target;

  // Eventually ignore private tabs
  if (ignoreWindow(browser.contentWindow))
    return;

  let tab = getTabForBrowser(browser);
  if (tab === null) {
    let rawTab = getRawTabForBrowser(browser);

    // create a Tab instance for this new tab
    tab = addTab(Tab(rawTab));
  }

  tabNS(tab).opened = true;

  tab.on('ready', () => emit(gTabs, 'ready', tab));
  tab.once('close', onTabClose);

  tab.on('pageshow', (_tab, persisted) =>
    emit(gTabs, 'pageshow', tab, persisted));

  emit(tab, 'open', tab);
  emit(gTabs, 'open', tab);
}

// TabSelect
function onTabSelect(event) {
  let browser = event.target;

  // Eventually ignore private tabs
  if (ignoreWindow(browser.contentWindow))
    return;

  // Set value whenever new tab becomes active.
  let tab = getTabForBrowser(browser);
  emit(tab, 'activate', tab);
  emit(gTabs, 'activate', tab);

  for (let t of gTabs) {
    if (t === tab) continue;
    emit(t, 'deactivate', t);
    emit(gTabs, 'deactivate', t);
  }
}

// TabClose
function onTabClose(tab) {
  removeTab(tab);
  emit(gTabs, EVENTS.close.name, tab);
}
PK
!<WTmodules/commonjs/sdk/windows.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

module.metadata = {
  'stability': 'stable'
};

const { isBrowser } = require('./window/utils');
const { modelFor } = require('./model/core');
const { viewFor } = require('./view/core');


if (require('./system/xul-app').is('Fennec')) {
  module.exports = require('./windows/fennec');
}
else {
  module.exports = require('./windows/firefox');
}


const browsers = module.exports.browserWindows;

//
modelFor.when(isBrowser, view => {
  for (let model of browsers) {
    if (viewFor(model) === view)
      return model;
  }
  return null;
});
PK
!<Jw$modules/commonjs/sdk/worker/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  'stability': 'deprecated'
};

const {
  requiresAddonGlobal, attach, detach, destroy, WorkerHost
} = require('../content/utils');

exports.WorkerHost = WorkerHost;
exports.detach = detach;
exports.attach = attach;
exports.destroy = destroy;
exports.requiresAddonGlobal = requiresAddonGlobal;
PK
!<̭!modules/commonjs/sdk/zip/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { Cc, Ci } = require("chrome");

function getZipReader(aFile) {
  return new Promise(resolve => {
    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
                        createInstance(Ci.nsIZipReader);
    zipReader.open(aFile);
    resolve(zipReader);
  });
};
exports.getZipReader = getZipReader;
PK
!<S55modules/commonjs/test.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
  "stability": "unstable"
};

module.exports = require("sdk/test");
PK
!<8*3E"modules/commonjs/toolkit/loader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

;((factory) => { // Module boilerplate :(
  if (typeof(require) === 'function') { // CommonJS
    require("chrome").Cu.import(module.uri, exports);
  }
  else if (~String(this).indexOf('BackstagePass')) { // JSM
    let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
    factory(module);
    Object.assign(this, module.exports);
    this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
  }
  else {
    throw Error("Loading environment is not supported");
  }
})(module => {

'use strict';

module.metadata = {
  "stability": "unstable"
};

const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
        results: Cr, manager: Cm } = Components;
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                     getService(Ci.mozIJSSubScriptLoader);
const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                        getService(Ci.nsIObserverService);
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const { normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "resProto",
                                   "@mozilla.org/network/protocol;1?name=resource",
                                   "nsIResProtocolHandler");
XPCOMUtils.defineLazyServiceGetter(this, "zipCache",
                                   "@mozilla.org/libjar/zip-reader-cache;1",
                                   "nsIZipReaderCache");

const { defineLazyGetter } = XPCOMUtils;

defineLazyGetter(this, "XulApp", () => {
  let xulappURI = module.uri.replace("toolkit/loader.js",
                                     "sdk/system/xul-app.jsm");
  return Cu.import(xulappURI, {});
});

// Define some shortcuts.
const bind = Function.call.bind(Function.bind);
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const prototypeOf = Object.getPrototypeOf;
function* getOwnIdentifiers(x) {
  yield* Object.getOwnPropertyNames(x);
  yield* Object.getOwnPropertySymbols(x);
}

const NODE_MODULES = new Set([
  "assert",
  "buffer_ieee754",
  "buffer",
  "child_process",
  "cluster",
  "console",
  "constants",
  "crypto",
  "_debugger",
  "dgram",
  "dns",
  "domain",
  "events",
  "freelist",
  "fs",
  "http",
  "https",
  "_linklist",
  "module",
  "net",
  "os",
  "path",
  "punycode",
  "querystring",
  "readline",
  "repl",
  "stream",
  "string_decoder",
  "sys",
  "timers",
  "tls",
  "tty",
  "url",
  "util",
  "vm",
  "zlib",
]);

const COMPONENT_ERROR = '`Components` is not available in this context.\n' +
  'Functionality provided by Components may be available in an SDK\n' +
  'module: https://developer.mozilla.org/en-US/Add-ons/SDK \n\n' +
  'However, if you still need to import Components, you may use the\n' +
  '`chrome` module\'s properties for shortcuts to Component properties:\n\n' +
  'Shortcuts: \n' +
  '    Cc = Components' + '.classes \n' +
  '    Ci = Components' + '.interfaces \n' +
  '    Cu = Components' + '.utils \n' +
  '    CC = Components' + '.Constructor \n' +
  'Example: \n' +
  '    let { Cc, Ci } = require(\'chrome\');\n';

// Workaround for bug 674195. Freezing objects from other compartments fail,
// so we use `Object.freeze` from the same component instead.
function freeze(object) {
  if (prototypeOf(object) === null) {
      Object.freeze(object);
  }
  else {
    prototypeOf(prototypeOf(object.isPrototypeOf)).
      constructor. // `Object` from the owner compartment.
      freeze(object);
  }
  return object;
}

// Returns map of given `object`-s own property descriptors.
const descriptor = iced(function descriptor(object) {
  let value = {};
  for (let name of getOwnIdentifiers(object))
    value[name] = getOwnPropertyDescriptor(object, name)
  return value;
});
Loader.descriptor = descriptor;

// Freeze important built-ins so they can't be used by untrusted code as a
// message passing channel.
freeze(Object);
freeze(Object.prototype);
freeze(Function);
freeze(Function.prototype);
freeze(Array);
freeze(Array.prototype);
freeze(String);
freeze(String.prototype);

// This function takes `f` function sets it's `prototype` to undefined and
// freezes it. We need to do this kind of deep freeze with all the exposed
// functions so that untrusted code won't be able to use them a message
// passing channel.
function iced(f) {
  if (!Object.isFrozen(f)) {
    f.prototype = undefined;
  }
  return freeze(f);
}

// Defines own properties of given `properties` object on the given
// target object overriding any existing property with a conflicting name.
// Returns `target` object. Note we only export this function because it's
// useful during loader bootstrap when other util modules can't be used &
// thats only case where this export should be used.
const override = iced(function override(target, source) {
  let properties = descriptor(target);

  for (let name of getOwnIdentifiers(source || {}))
    properties[name] = getOwnPropertyDescriptor(source, name);

  return Object.defineProperties({}, properties);
});
Loader.override = override;

function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
Loader.sourceURI = iced(sourceURI);

function isntLoaderFrame(frame) { return frame.fileName !== module.uri }

function parseURI(uri) { return String(uri).split(" -> ").pop(); }
Loader.parseURI = parseURI;

function parseStack(stack) {
  let lines = String(stack).split("\n");
  return lines.reduce(function(frames, line) {
    if (line) {
      let atIndex = line.indexOf("@");
      let columnIndex = line.lastIndexOf(":");
      let lineIndex = line.lastIndexOf(":", columnIndex - 1);
      let fileName = parseURI(line.slice(atIndex + 1, lineIndex));
      let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex));
      let columnNumber = parseInt(line.slice(columnIndex + 1));
      let name = line.slice(0, atIndex).split("(").shift();
      frames.unshift({
        fileName: fileName,
        name: name,
        lineNumber: lineNumber,
        columnNumber: columnNumber
      });
    }
    return frames;
  }, []);
}
Loader.parseStack = parseStack;

function serializeStack(frames) {
  return frames.reduce(function(stack, frame) {
    return frame.name + "@" +
           frame.fileName + ":" +
           frame.lineNumber + ":" +
           frame.columnNumber + "\n" +
           stack;
  }, "");
}
Loader.serializeStack = serializeStack;

class DefaultMap extends Map {
  constructor(createItem, items = undefined) {
    super(items);

    this.createItem = createItem;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.createItem(key));
    }

    return super.get(key);
  }
}

const urlCache = {
  /**
   * Returns a list of fully-qualified URLs for entries within the zip
   * file at the given URI which are either directories or files with a
   * .js or .json extension.
   *
   * @param {nsIJARURI} uri
   * @param {string} baseURL
   *        The original base URL, prior to resolution.
   *
   * @returns {Set<string>}
   */
  getZipFileContents(uri, baseURL) {
    // Make sure the path has a trailing slash, and strip off the leading
    // slash, so that we can easily check whether it is a path prefix.
    let basePath = addTrailingSlash(uri.JAREntry).slice(1);
    let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file;

    let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)");

    let results = new Set();
    for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) {
      if (entry.startsWith(basePath)) {
        let path = entry.slice(basePath.length);

        results.add(baseURL + path);
      }
    }

    return results;
  },

  zipContentsCache: new DefaultMap(baseURL => {
    let uri = NetUtil.newURI(baseURL);

    if (baseURL.startsWith("resource:")) {
      uri = NetUtil.newURI(resProto.resolveURI(uri));
    }

    if (uri instanceof Ci.nsIJARURI) {
      return urlCache.getZipFileContents(uri, baseURL);
    }

    return null;
  }),

  filesCache: new DefaultMap(url => {
    try {
      let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL);

      return uri.file.exists();
    } catch (e) {
      return false;
    }
  }),

  resolutionCache: new DefaultMap(fullId => {
    return (resolveAsFile(fullId) ||
            resolveAsDirectory(fullId));
  }),

  nodeModulesCache: new Map(),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),

  observe() {
    // Clear any module resolution caches when the startup cache is flushed,
    // since it probably means we're loading new copies of extensions.
    this.zipContentsCache.clear();
    this.filesCache.clear();
    this.resolutionCache.clear();
    this.nodeModulesCache.clear();
  },

  getNodeModulePaths(rootURI, start) {
    let url = join(rootURI, start);

    if (this.nodeModulesCache.has(url))
      return this.nodeModulesCache.get(url);

    let result = Array.from(getNodeModulePaths(rootURI, start));
    this.nodeModulesCache.set(url, result);
    return result;
  },

  /**
   * Returns the base URL for the given URL, if one can be determined. For
   * a resource: URL, this is the root of the resource package. For a jar:
   * URL, it is the root of the JAR file. Otherwise, null is returned.
   *
   * @param {string} url
   * @returns {string?}
   */
  getBaseURL(url) {
    // By using simple string matching for the common case of resource: URLs
    // backed by jar: URLs, we can avoid creating any nsIURI objects for the
    // common case where the JAR contents are already cached.
    if (url.startsWith("resource://")) {
      return /^resource:\/\/[^\/]+\//.exec(url)[0];
    }

    let uri = NetUtil.newURI(url);
    if (uri instanceof Ci.nsIJARURI) {
      return `jar:${uri.JARFile.spec}!/`;
    }

    return null;
  },

  /**
   * Returns true if the target of the given URL exists as a local file,
   * or as an entry in a local zip file.
   *
   * @param {string} url
   * @returns {boolean}
   */
  exists(url) {
    if (!/\.(?:js|json)$/.test(url)) {
      url = addTrailingSlash(url);
    }

    let baseURL = this.getBaseURL(url);
    let scripts = baseURL && this.zipContentsCache.get(baseURL);
    if (scripts) {
      return scripts.has(url);
    }

    return this.filesCache.get(url);
  },
}
addObserver(urlCache, "startupcache-invalidate", true);

function readURI(uri) {
  let nsURI = NetUtil.newURI(uri);
  if (nsURI.scheme == "resource") {
    // Resolve to a real URI, this will catch any obvious bad paths without
    // logging assertions in debug builds, see bug 1135219
    uri = resProto.resolveURI(nsURI);
  }

  let stream = NetUtil.newChannel({
    uri: NetUtil.newURI(uri, 'UTF-8'),
    loadUsingSystemPrincipal: true}
  ).open2();
  let count = stream.available();
  let data = NetUtil.readInputStreamToString(stream, count, {
    charset: 'UTF-8'
  });

  stream.close();

  return data;
}

// Combines all arguments into a resolved, normalized path
function join(base, ...paths) {
  // If this is an absolute URL, we need to normalize only the path portion,
  // or we wind up stripping too many slashes and producing invalid URLs.
  let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base);
  if (match) {
    return match[1] + normalize([match[2], ...paths].join("/"));
  }

  return normalize([base, ...paths].join("/"));
}
Loader.join = join;

// Function takes set of options and returns a JS sandbox. Function may be
// passed set of options:
//  - `name`: A string value which identifies the sandbox in about:memory. Will
//    throw exception if omitted.
// - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
//    system principal.
// - `prototype`: Ancestor for the sandbox that will be created. Defaults to
//    `{}`.
// - `wantXrays`: A Boolean value indicating whether code outside the sandbox
//    wants X-ray vision with respect to objects inside the sandbox. Defaults
//    to `true`.
// - `sandbox`: A sandbox to share JS compartment with. If omitted new
//    compartment will be created.
// - `metadata`: A metadata object associated with the sandbox. It should
//    be JSON-serializable.
// For more details see:
// https://developer.mozilla.org/en/Components.utils.Sandbox
const Sandbox = iced(function Sandbox(options) {
  // Normalize options and rename to match `Cu.Sandbox` expectations.
  options = {
    // Do not expose `Components` if you really need them (bad idea!) you
    // still can expose via prototype.
    wantComponents: false,
    sandboxName: options.name,
    principal: 'principal' in options ? options.principal : systemPrincipal,
    wantXrays: 'wantXrays' in options ? options.wantXrays : true,
    wantGlobalProperties: 'wantGlobalProperties' in options ?
                          options.wantGlobalProperties : [],
    sandboxPrototype: 'prototype' in options ? options.prototype : {},
    invisibleToDebugger: 'invisibleToDebugger' in options ?
                         options.invisibleToDebugger : false,
    metadata: 'metadata' in options ? options.metadata : {},
    waiveIntereposition: !!options.waiveIntereposition
  };

  if (options.metadata && options.metadata.addonID) {
    options.addonId = options.metadata.addonID;
  }

  let sandbox = Cu.Sandbox(options.principal, options);

  // Each sandbox at creation gets set of own properties that will be shadowing
  // ones from it's prototype. We override delete such `sandbox` properties
  // to avoid shadowing.
  delete sandbox.Iterator;
  delete sandbox.Components;
  delete sandbox.importFunction;
  delete sandbox.debug;

  return sandbox;
});
Loader.Sandbox = Sandbox;

// Evaluates code from the given `uri` into given `sandbox`. If
// `options.source` is passed, then that code is evaluated instead.
// Optionally following options may be given:
// - `options.encoding`: Source encoding, defaults to 'UTF-8'.
// - `options.line`: Line number to start count from for stack traces.
//    Defaults to 1.
// - `options.version`: Version of JS used, defaults to '1.8'.
const evaluate = iced(function evaluate(sandbox, uri, options) {
  let { source, line, version, encoding } = override({
    encoding: 'UTF-8',
    line: 1,
    version: '1.8',
    source: null
  }, options);

  return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
                : loadSubScript(uri, sandbox, encoding);
});
Loader.evaluate = evaluate;

// Populates `exports` of the given CommonJS `module` object, in the context
// of the given `loader` by evaluating code associated with it.
const load = iced(function load(loader, module) {
  let { sandboxes, globals, loadModuleHook } = loader;
  let require = Require(loader, module);

  // We expose set of properties defined by `CommonJS` specification via
  // prototype of the sandbox. Also globals are deeper in the prototype
  // chain so that each module has access to them as well.
  let descriptors = descriptor({
    require: require,
    module: module,
    exports: module.exports,
    get Components() {
      // Expose `Components` property to throw error on usage with
      // additional information
      throw new ReferenceError(COMPONENT_ERROR);
    }
  });

  let sandbox;
  if ((loader.useSharedGlobalSandbox || isSystemURI(module.uri)) &&
      loader.sharedGlobalBlocklist.indexOf(module.id) == -1) {
    // Create a new object in this sandbox, that will be used as
    // the scope object for this particular module
    sandbox = new loader.sharedGlobalSandbox.Object();
    descriptors.lazyRequire = {
      configurable: true,
      value: lazyRequire.bind(sandbox),
    };
    descriptors.lazyRequireModule = {
      configurable: true,
      value: lazyRequireModule.bind(sandbox),
    };

    if ("console" in globals) {
      descriptors.console = {
        configurable: true,
        get() {
          return globals.console;
        },
      };
    }
    let define = Object.getOwnPropertyDescriptor(globals, "define");
    if (define && define.value)
      descriptors.define = define;
    if ("DOMParser" in globals)
      descriptors.DOMParser = Object.getOwnPropertyDescriptor(globals, "DOMParser");
    Object.defineProperties(sandbox, descriptors);
  }
  else {
    sandbox = Sandbox({
      name: module.uri,
      prototype: Object.create(globals, descriptors),
      wantXrays: false,
      wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
      invisibleToDebugger: loader.invisibleToDebugger,
      metadata: {
        addonID: loader.id,
        URI: module.uri
      }
    });
  }
  sandboxes[module.uri] = sandbox;

  let originalExports = module.exports;
  try {
    evaluate(sandbox, module.uri);
  }
  catch (error) {
    let { message, fileName, lineNumber } = error;
    let stack = error.stack || Error().stack;
    let frames = parseStack(stack).filter(isntLoaderFrame);
    let toString = String(error);
    let file = sourceURI(fileName);

    // Note that `String(error)` where error is from subscript loader does
    // not puts `:` after `"Error"` unlike regular errors thrown by JS code.
    // If there is a JS stack then this error has already been handled by an
    // inner module load.
    if (/^Error opening input stream/.test(String(error))) {
      let caller = frames.slice(0).pop();
      fileName = caller.fileName;
      lineNumber = caller.lineNumber;
      message = "Module `" + module.id + "` is not found at " + module.uri;
      toString = message;
    }
    // Workaround for a Bug 910653. Errors thrown by subscript loader
    // do not include `stack` field and above created error won't have
    // fileName or lineNumber of the module being loaded, so we ensure
    // it does.
    else if (frames[frames.length - 1].fileName !== file) {
      frames.push({ fileName: file, lineNumber: lineNumber, name: "" });
    }

    let prototype = typeof(error) === "object" ? error.constructor.prototype :
                    Error.prototype;

    throw Object.create(prototype, {
      message: { value: message, writable: true, configurable: true },
      fileName: { value: fileName, writable: true, configurable: true },
      lineNumber: { value: lineNumber, writable: true, configurable: true },
      stack: { value: serializeStack(frames), writable: true, configurable: true },
      toString: { value: () => toString, writable: true, configurable: true },
    });
  }

  if (loadModuleHook) {
    module = loadModuleHook(module, require);
  }

  if (loader.checkCompatibility) {
    let err = XulApp.incompatibility(module);
    if (err) {
      throw err;
    }
  }

  // Only freeze the exports object if we created it ourselves. Modules
  // which completely replace the exports object and still want it
  // frozen need to freeze it themselves.
  if (module.exports === originalExports)
    Object.freeze(module.exports);

  return module;
});
Loader.load = load;

// Utility function to normalize module `uri`s so they have `.js` extension.
function normalizeExt(uri) {
  return isJSURI(uri) ? uri :
         isJSONURI(uri) ? uri :
         isJSMURI(uri) ? uri :
         uri + '.js';
}

// Utility function to join paths. In common case `base` is a
// `requirer.uri` but in some cases it may be `baseURI`. In order to
// avoid complexity we require `baseURI` with a trailing `/`.
const resolve = iced(function resolve(id, base) {
  if (!isRelative(id))
    return id;

  let baseDir = dirname(base);

  let resolved;
  if (baseDir.includes(":"))
    resolved = join(baseDir, id);
  else
    resolved = normalize(`${baseDir}/${id}`);

  // Joining and normalizing removes the './' from relative files.
  // We need to ensure the resolution still has the root
  if (base.startsWith('./'))
    resolved = './' + resolved;

  return resolved;
});
Loader.resolve = resolve;

// Attempts to load `path` and then `path.js`
// Returns `path` with valid file, or `undefined` otherwise
function resolveAsFile(path) {
  // Append '.js' to path name unless it's another support filetype
  path = normalizeExt(path);
  if (urlCache.exists(path)) {
    return path;
  }

  return null;
}

// Attempts to load `path/package.json`'s `main` entry,
// followed by `path/index.js`, or `undefined` otherwise
function resolveAsDirectory(path) {
  try {
    // If `path/package.json` exists, parse the `main` entry
    // and attempt to load that
    let manifestPath = addTrailingSlash(path) + 'package.json';

    let main = (urlCache.exists(manifestPath) &&
                getManifestMain(JSON.parse(readURI(manifestPath))));
    if (main) {
      let found = resolveAsFile(join(path, main));
      if (found) {
        return found
      }
    }
  } catch (e) {}

  return resolveAsFile(addTrailingSlash(path) + 'index.js');
}

function resolveRelative(rootURI, modulesDir, id) {
  let fullId = join(rootURI, modulesDir, id);

  let resolvedPath = urlCache.resolutionCache.get(fullId);
  if (resolvedPath) {
    return './' + resolvedPath.slice(rootURI.length);
  }

  return null;
}

// From `resolve` module
// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
function* getNodeModulePaths(rootURI, start) {
  let moduleDir = 'node_modules';

  let parts = start.split('/');
  while (parts.length) {
    let leaf = parts.pop();
    let path = [...parts, leaf, moduleDir].join("/");
    if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) {
      yield path;
    }
  }

  if (urlCache.exists(join(rootURI, moduleDir))) {
    yield moduleDir;
  }
}

// Node-style module lookup
// Takes an id and path and attempts to load a file using node's resolving
// algorithm.
// `id` should already be resolved relatively at this point.
// http://nodejs.org/api/modules.html#modules_all_together
const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
  // Resolve again
  id = Loader.resolve(id, requirer);

  // If this is already an absolute URI then there is no resolution to do
  if (isAbsoluteURI(id)) {
    return null;
  }

  // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
  // and a js file isn't named 'file.json.js'
  let resolvedPath;

  if ((resolvedPath = resolveRelative(rootURI, "", id))) {
    return resolvedPath;
  }

  // If the requirer is an absolute URI then the node module resolution below
  // won't work correctly as we prefix everything with rootURI
  if (isAbsoluteURI(requirer)) {
    return null;
  }

  // If manifest has dependencies, attempt to look up node modules
  // in the `dependencies` list
  for (let modulesDir of urlCache.getNodeModulePaths(rootURI, dirname(requirer))) {
    if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) {
      return resolvedPath;
    }
  }

  // We would not find lookup for things like `sdk/tabs`, as that's part of
  // the alias mapping. If during `generateMap`, the runtime lookup resolves
  // with `resolveURI` -- if during runtime, then `resolve` will throw.
  return null;
});

Loader.nodeResolve = nodeResolve;

function addTrailingSlash(path) {
  return path.replace(/\/*$/, "/");
}

function compileMapping(paths) {
  // Make mapping array that is sorted from longest path to shortest path.
  let mapping = Object.keys(paths)
                      .sort((a, b) => b.length - a.length)
                      .map(path => [path, paths[path]]);

  const PATTERN = /([.\\?+*(){}[\]^$])/g;
  const escapeMeta = str => str.replace(PATTERN, '\\$1')

  let patterns = [];
  paths = {};

  for (let [path, uri] of mapping) {
    // Strip off any trailing slashes to make comparisons simpler
    if (path.endsWith("/")) {
      path = path.slice(0, -1);
      uri = uri.replace(/\/+$/, "");
    }

    paths[path] = uri;

    // We only want to match path segments explicitly. Examples:
    // * "foo/bar" matches for "foo/bar"
    // * "foo/bar" matches for "foo/bar/baz"
    // * "foo/bar" does not match for "foo/bar-1"
    // * "foo/bar/" does not match for "foo/bar"
    // * "foo/bar/" matches for "foo/bar/baz"
    //
    // Check for an empty path, an exact match, or a substring match
    // with the next character being a forward slash.
    if (path == "")
      patterns.push("");
    else
      patterns.push(`${escapeMeta(path)}(?=$|/)`);
  }

  let pattern = new RegExp(`^(${patterns.join('|')})`);

  // This will replace the longest matching path mapping at the start of
  // the ID string with its mapped value.
  return id => {
    return id.replace(pattern, (m0, m1) => paths[m1]);
  };
}

const resolveURI = iced(function resolveURI(id, mapping) {
  // Do not resolve if already a resource URI
  if (isAbsoluteURI(id))
    return normalizeExt(id);

  return normalizeExt(mapping(id))
});
Loader.resolveURI = resolveURI;

/**
 * Defines lazy getters on the given object, which lazily require the
 * given module the first time they are accessed, and then resolve that
 * module's exported properties.
 *
 * @param {object} obj
 *        The target object on which to define the lazy getters.
 * @param {string} moduleId
 *        The ID of the module to require, as passed to require().
 * @param {Array<string | object>} args
 *        Any number of properties to import from the module. A string
 *        will cause the property to be defined which resolves to the
 *        same property in the module's exports. An object will define a
 *        lazy getter for every value in the object which corresponds to
 *        the given key in the module's exports, as in an ordinary
 *        destructuring assignment.
 */
function lazyRequire(obj, moduleId, ...args) {
  let module;
  let getModule = () => {
    if (!module)
      module = this.require(moduleId);
    return module;
  };

  for (let props of args) {
    if (typeof props !== "object")
      props = {[props]: props};

    for (let [fromName, toName] of Object.entries(props))
      defineLazyGetter(obj, toName, () => getModule()[fromName]);
  }
}

/**
 * Defines a lazy getter on the given object which causes a module to be
 * lazily imported the first time it is accessed.
 *
 * @param {object} obj
 *        The target object on which to define the lazy getter.
 * @param {string} moduleId
 *        The ID of the module to require, as passed to require().
 * @param {string} [prop = moduleId]
 *        The name of the lazy getter property to define.
 */
function lazyRequireModule(obj, moduleId, prop = moduleId) {
  defineLazyGetter(obj, prop, () => this.require(moduleId));
}


// Creates version of `require` that will be exposed to the given `module`
// in the context of the given `loader`. Each module gets own limited copy
// of `require` that is allowed to load only a modules that are associated
// with it during link time.
const Require = iced(function Require(loader, requirer) {
  let {
    modules, mapping, mappingCache, resolve: loaderResolve, load,
    manifest, rootURI, isNative, requireHook
  } = loader;

  if (isSystemURI(requirer.uri)) {
    // Built-in modules don't require the expensive module resolution
    // algorithm used by SDK add-ons, so give them the more efficient standard
    // resolve instead.
    isNative = false;
    loaderResolve = Loader.resolve;
  }

  function require(id) {
    if (!id) // Throw if `id` is not passed.
      throw Error('You must provide a module name when calling require() from '
                  + requirer.id, requirer.uri);

    if (requireHook) {
      return requireHook(id, _require);
    }

    return _require(id);
  }

  function _require(id) {
    let { uri, requirement } = getRequirements(id);

    let module = null;
    // If module is already cached by loader then just use it.
    if (uri in modules) {
      module = modules[uri];
    }
    else if (isJSMURI(uri)) {
      module = modules[uri] = Module(requirement, uri);
      module.exports = Cu.import(uri, {});
      freeze(module);
    }
    else if (isJSONURI(uri)) {
      let data;

      // First attempt to load and parse json uri
      // ex: `test.json`
      // If that doesn't exist, check for `test.json.js`
      // for node parity
      try {
        data = JSON.parse(readURI(uri));
        module = modules[uri] = Module(requirement, uri);
        module.exports = data;
        freeze(module);
      }
      catch (err) {
        // If error thrown from JSON parsing, throw that, do not
        // attempt to find .json.js file
        if (err && /JSON\.parse/.test(err.message))
          throw err;
        uri = uri + '.js';
      }
    }

    // If not yet cached, load and cache it.
    // We also freeze module to prevent it from further changes
    // at runtime.
    if (!(uri in modules)) {
      // Many of the loader's functionalities are dependent
      // on modules[uri] being set before loading, so we set it and
      // remove it if we have any errors.
      module = modules[uri] = Module(requirement, uri);
      try {
        Object.freeze(load(loader, module));
      }
      catch (e) {
        // Clear out modules cache so we can throw on a second invalid require
        delete modules[uri];
        // Also clear out the Sandbox that was created
        delete loader.sandboxes[uri];
        throw e;
      }
    }

    return module.exports;
  }

  // Resolution function taking a module name/path and
  // returning a resourceURI and a `requirement` used by the loader.
  // Used by both `require` and `require.resolve`.
  function getRequirements(id) {
    if (!id) // Throw if `id` is not passed.
      throw Error('you must provide a module name when calling require() from '
                  + requirer.id, requirer.uri);

    let requirement, uri;

    // TODO should get native Firefox modules before doing node-style lookups
    // to save on loading time
    if (isNative) {
      let { overrides } = manifest.jetpack;
      for (let key in overrides) {
        // ignore any overrides using relative keys
        if (/^[.\/]/.test(key)) {
          continue;
        }

        // If the override is for x -> y,
        // then using require("x/lib/z") to get reqire("y/lib/z")
        // should also work
        if (id == key || id.startsWith(key + "/")) {
          id = overrides[key] + id.substr(key.length);
          id = id.replace(/^[.\/]+/, "");
        }
      }

      // For native modules, we want to check if it's a module specified
      // in 'modules', like `chrome`, or `@loader` -- if it exists,
      // just set the uri to skip resolution
      if (!requirement && modules[id])
        uri = requirement = id;

      if (!requirement && !NODE_MODULES.has(id)) {
        // If `isNative` defined, this is using the new, native-style
        // loader, not cuddlefish, so lets resolve using node's algorithm
        // and get back a path that needs to be resolved via paths mapping
        // in `resolveURI`
        requirement = loaderResolve(id, requirer.id, {
          manifest: manifest,
          rootURI: rootURI
        });
      }

      // If not found in the map, not a node module, and wasn't able to be
      // looked up, it's something
      // found in the paths most likely, like `sdk/tabs`, which should
      // be resolved relatively if needed using traditional resolve
      if (!requirement) {
        requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
      }
    }
    else if (modules[id]) {
      uri = requirement = id;
    }
    else if (requirer) {
      // Resolve `id` to its requirer if it's relative.
      requirement = loaderResolve(id, requirer.id);
    }
    else {
      requirement = id;
    }

    // Resolves `uri` of module using loaders resolve function.
    if (!uri) {
      if (mappingCache.has(requirement)) {
        uri = mappingCache.get(requirement);
      } else {
        uri = resolveURI(requirement, mapping);
        mappingCache.set(requirement, uri);
      }
    }

    // Throw if `uri` can not be resolved.
    if (!uri) {
      throw Error('Module: Can not resolve "' + id + '" module required by ' +
                  requirer.id + ' located at ' + requirer.uri, requirer.uri);
    }

    return { uri: uri, requirement: requirement };
  }

  // Expose the `resolve` function for this `Require` instance
  require.resolve = _require.resolve = function resolve(id) {
    let { uri } = getRequirements(id);
    return uri;
  }

  // This is like webpack's require.context.  It returns a new require
  // function that prepends the prefix to any requests.
  require.context = prefix => {
    return id => {
      return require(prefix + id);
    };
  };

  // Make `require.main === module` evaluate to true in main module scope.
  require.main = loader.main === requirer ? requirer : undefined;
  return iced(require);
});
Loader.Require = Require;

const main = iced(function main(loader, id) {
  // If no main entry provided, and native loader is used,
  // read the entry in the manifest
  if (!id && loader.isNative)
    id = getManifestMain(loader.manifest);
  let uri = resolveURI(id, loader.mapping);
  let module = loader.main = loader.modules[uri] = Module(id, uri);
  return loader.load(loader, module).exports;
});
Loader.main = main;

// Makes module object that is made available to CommonJS modules when they
// are evaluated, along with `exports` and `require`.
const Module = iced(function Module(id, uri) {
  return Object.create(null, {
    id: { enumerable: true, value: id },
    exports: { enumerable: true, writable: true, value: Object.create(null),
               configurable: true },
    uri: { value: uri }
  });
});
Loader.Module = Module;

// Takes `loader`, and unload `reason` string and notifies all observers that
// they should cleanup after them-self.
const unload = iced(function unload(loader, reason) {
  // subject is a unique object created per loader instance.
  // This allows any code to cleanup on loader unload regardless of how
  // it was loaded. To handle unload for specific loader subject may be
  // asserted against loader.destructor or require('@loader/unload')
  // Note: We don not destroy loader's module cache or sandboxes map as
  // some modules may do cleanup in subsequent turns of event loop. Destroying
  // cache may cause module identity problems in such cases.
  let subject = { wrappedJSObject: loader.destructor };
  notifyObservers(subject, 'sdk:loader:destroy', reason);
});
Loader.unload = unload;

// Function makes new loader that can be used to load CommonJS modules
// described by a given `options.manifest`. Loader takes following options:
// - `globals`: Optional map of globals, that all module scopes will inherit
//   from. Map is also exposed under `globals` property of the returned loader
//   so it can be extended further later. Defaults to `{}`.
// - `modules` Optional map of built-in module exports mapped by module id.
//   These modules will incorporated into module cache. Each module will be
//   frozen.
// - `resolve` Optional module `id` resolution function. If given it will be
//   used to resolve module URIs, by calling it with require term, requirer
//   module object (that has `uri` property) and `baseURI` of the loader.
//   If `resolve` does not returns `uri` string exception will be thrown by
//   an associated `require` call.
function Loader(options) {
  function normalizeRootURI(uri) {
    return addTrailingSlash(join(uri));
  }

  if (options.sharedGlobalBlacklist && !options.sharedGlobalBlocklist) {
    options.sharedGlobalBlocklist = options.sharedGlobalBlacklist;
  }
  let {
    modules, globals, resolve, paths, rootURI, manifest, isNative,
    metadata, sharedGlobal, sharedGlobalBlocklist, checkCompatibility, waiveIntereposition
  } = override({
    paths: {},
    modules: {},
    globals: {
      get console() {
        // Import Console.jsm from here to prevent loading it until someone uses it
        let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm");
        let console = new ConsoleAPI({
          consoleID: options.id ? "addon/" + options.id : ""
        });
        Object.defineProperty(this, "console", { value: console });
        return this.console;
      }
    },
    checkCompatibility: false,
    resolve: options.isNative ?
      // Make the returned resolve function have the same signature
      (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: normalizeRootURI(rootURI) }) :
      Loader.resolve,
    sharedGlobalBlocklist: ["sdk/indexed-db"],
    waiveIntereposition: false
  }, options);

  // Create overrides defaults, none at the moment
  if (typeof manifest != "object" || !manifest) {
    manifest = {};
  }
  if (typeof manifest.jetpack != "object" || !manifest.jetpack) {
    manifest.jetpack = {
      overrides: {}
    };
  }
  if (typeof manifest.jetpack.overrides != "object" || !manifest.jetpack.overrides) {
    manifest.jetpack.overrides = {};
  }

  // We create an identity object that will be dispatched on an unload
  // event as subject. This way unload listeners will be able to assert
  // which loader is unloaded. Please note that we intentionally don't
  // use `loader` as subject to prevent a loader access leakage through
  // observer notifications.
  let destructor = freeze(Object.create(null));

  let mapping = compileMapping(paths);

  // Define pseudo modules.
  modules = override({
    '@loader/unload': destructor,
    '@loader/options': options,
    'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
                CC: bind(CC, Components), components: Components,
                // `ChromeWorker` has to be inject in loader global scope.
                // It is done by bootstrap.js:loadSandbox for the SDK.
                ChromeWorker: ChromeWorker
    }
  }, modules);

  const builtinModuleExports = modules;
  modules = {};
  for (let id of Object.keys(builtinModuleExports)) {
    // We resolve `uri` from `id` since modules are cached by `uri`.
    let uri = resolveURI(id, mapping);
    // In native loader, the mapping will not contain values for
    // pseudomodules -- store them as their ID rather than the URI
    if (isNative && !uri)
      uri = id;
    let module = Module(id, uri);

    // Lazily expose built-in modules in order to
    // allow them to be loaded lazily.
    Object.defineProperty(module, "exports", {
      enumerable: true,
      get: function() {
        return builtinModuleExports[id];
      }
    });

    modules[uri] = freeze(module);
  }

  // Create the unique sandbox we will be using for all modules,
  // so that we prevent creating a new comportment per module.
  // The side effect is that all modules will share the same
  // global objects.
  let sharedGlobalSandbox = Sandbox({
    name: options.sandboxName || "Addon-SDK",
    wantXrays: false,
    wantGlobalProperties: [],
    invisibleToDebugger: options.invisibleToDebugger || false,
    metadata: {
      addonID: options.noSandboxAddonId ? undefined : options.id,
      URI: options.sandboxName || "Addon-SDK"
    },
    prototype: options.sandboxPrototype || globals,
  });

  if (options.sandboxPrototype) {
    // If we were given a sandboxPrototype, we have to define the globals on
    // the sandbox directly. Note that this will not work for callers who
    // depend on being able to add globals after the loader was created.
    for (let name of getOwnIdentifiers(globals))
      Object.defineProperty(sharedGlobalSandbox, name,
                            getOwnPropertyDescriptor(globals, name));
  }

  // Loader object is just a representation of a environment
  // state. We freeze it and mark make it's properties non-enumerable
  // as they are pure implementation detail that no one should rely upon.
  let returnObj = {
    destructor: { enumerable: false, value: destructor },
    globals: { enumerable: false, value: globals },
    mapping: { enumerable: false, value: mapping },
    mappingCache: { enumerable: false, value: new Map() },
    // Map of module objects indexed by module URIs.
    modules: { enumerable: false, value: modules },
    metadata: { enumerable: false, value: metadata },
    useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal },
    sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
    sharedGlobalBlocklist: { enumerable: false, value: sharedGlobalBlocklist },
    sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlocklist },
    // Map of module sandboxes indexed by module URIs.
    sandboxes: { enumerable: false, value: {} },
    resolve: { enumerable: false, value: resolve },
    // ID of the addon, if provided.
    id: { enumerable: false, value: options.id },
    // Whether the modules loaded should be ignored by the debugger
    invisibleToDebugger: { enumerable: false,
                           value: options.invisibleToDebugger || false },
    load: { enumerable: false, value: options.load || load },
    checkCompatibility: { enumerable: false, value: checkCompatibility },
    requireHook: { enumerable: false, value: options.requireHook },
    loadModuleHook: { enumerable: false, value: options.loadModuleHook },
    // Main (entry point) module, it can be set only once, since loader
    // instance can have only one main module.
    main: new function() {
      let main;
      return {
        enumerable: false,
        get: function() { return main; },
        // Only set main if it has not being set yet!
        set: function(module) { main = main || module; }
      }
    }
  };

  if (isNative) {
    returnObj.isNative = { enumerable: false, value: true };
    returnObj.manifest = { enumerable: false, value: manifest };
    returnObj.rootURI = { enumerable: false, value: normalizeRootURI(rootURI) };
  }

  return freeze(Object.create(null, returnObj));
};
Loader.Loader = Loader;

var isSystemURI = uri => /^resource:\/\/(gre|devtools|testing-common)\//.test(uri);

var isJSONURI = uri => uri.endsWith('.json');
var isJSMURI = uri => uri.endsWith('.jsm');
var isJSURI = uri => uri.endsWith('.js');
var isAbsoluteURI = uri => /^(resource|chrome|file|jar):/.test(uri);
var isRelative = id => id.startsWith(".");

// Default `main` entry to './index.js' and ensure is relative,
// since node allows 'lib/index.js' without relative `./`
function getManifestMain(manifest) {
  let main = manifest.main || './index.js';
  return isRelative(main) ? main : './' + main;
}

module.exports = iced(Loader);
});
PK
!<Np#modules/commonjs/toolkit/require.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const make = (exports, rootURI, components) => {
  const { Loader: { Loader, Require, Module, main } } =
        components.utils.import(rootURI + "toolkit/loader.js", {});

  const loader = Loader({
    id: "toolkit/require",
    rootURI: rootURI,
    isNative: true,
    paths: {
     "": rootURI,
     "devtools/": "resource://devtools/"
    }
  });

  // Implement require.unload(uri) that can be used to unload
  // already loaded module which is convinient during development phase.
  const unload = uri => {
    delete loader.sandboxes[uri];
    delete loader.modules[uri];
  };

  const builtins = new Set(Object.keys(loader.modules));

  // Below we define `require` & `require.resolve` that resolve passed
  // module id relative to the caller URI. This is not perfect but good
  // enough for common case & there is always an option to pass absolute
  // id when that
  // but presumably well enough to cover

  const require = (id, options={}) => {
    const { reload, all } = options;
    const requirerURI = components.stack.caller.filename;
    const requirer = Module(requirerURI, requirerURI);
    const require = Require(loader, requirer);
    if (reload) {
      // To load JS code into modules, loader uses `mozIJSSubScriptLoader`
      // which uses startup cache to avoid reading source from the same URI
      // more than once. Unless we invalidate statup cache changes to a module
      // won't be reflected even after reload. Therefor we must dispatch an
      // nsIObserverService notification that causes cache invalidation.
      // Note: This is not ideal since it destroys whole cache, but since there
      // is no way to invalidate individual entries, we assume performance hit
      // during development is acceptable.
      components.classes["@mozilla.org/observer-service;1"].
        getService(components.interfaces.nsIObserverService).
        notifyObservers({}, "startupcache-invalidate");

      if (all) {
        for (let uri of Object.keys(loader.sandboxes)) {
          unload(uri);
        }
      }
      else {
        unload(require.resolve(id));
      }
    }
    return require(id);
  };

  require.resolve = id => {
    const requirerURI = components.stack.caller.filename;
    const requirer = Module(requirerURI, requirerURI);
    return Require(loader, requirer).resolve(id);
  };

  exports.require = require;
}

// If loaded in the context of commonjs module, reload as JSM into an
// exports object.
if (typeof(require) === "function" && typeof(module) === "object") {
  require("chrome").Cu.import(module.uri, module.exports);
}
// If loaded in the context of JSM make a loader & require and define
// new symbols as exported ones.
else if (typeof(__URI__) === "string" && this["Components"]) {
  const builtin = Object.keys(this);
  const uri = __URI__.replace("toolkit/require.js", "");
  make(this, uri, this["Components"]);

  this.EXPORTED_SYMBOLS = Object.
                            keys(this).
                            filter($ => builtin.indexOf($) < 0);
}
else {
  throw Error("Loading require.js in this environment isn't supported")
}
PK
!<d#

modules/css-selector.js/* -*- 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/. */

"use strict";

this.EXPORTED_SYMBOLS = ["findCssSelector"];

/**
 * Traverse getBindingParent until arriving upon the bound element
 * responsible for the generation of the specified node.
 * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
 *
 * @param {DOMNode} node
 * @return {DOMNode}
 *         If node is not anonymous, this will return node. Otherwise,
 *         it will return the bound element
 *
 */
function getRootBindingParent(node) {
  let parent;
  let doc = node.ownerDocument;
  if (!doc) {
    return node;
  }
  while ((parent = doc.getBindingParent(node))) {
    node = parent;
  }
  return node;
}

/**
 * Find the position of [element] in [nodeList].
 * @returns an index of the match, or -1 if there is no match
 */
function positionInNodeList(element, nodeList) {
  for (let i = 0; i < nodeList.length; i++) {
    if (element === nodeList[i]) {
      return i;
    }
  }
  return -1;
}

/**
 * Find a unique CSS selector for a given element
 * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
 * and ele.ownerDocument.querySelectorAll(reply).length === 1
 */
const findCssSelector = function(ele) {
  ele = getRootBindingParent(ele);
  let document = ele.ownerDocument;
  if (!document || !document.contains(ele)) {
    throw new Error("findCssSelector received element not inside document");
  }

  let cssEscape = ele.ownerGlobal.CSS.escape;

  // document.querySelectorAll("#id") returns multiple if elements share an ID
  if (ele.id &&
      document.querySelectorAll("#" + cssEscape(ele.id)).length === 1) {
    return "#" + cssEscape(ele.id);
  }

  // Inherently unique by tag name
  let tagName = ele.localName;
  if (tagName === "html") {
    return "html";
  }
  if (tagName === "head") {
    return "head";
  }
  if (tagName === "body") {
    return "body";
  }

  // We might be able to find a unique class name
  let selector, index, matches;
  if (ele.classList.length > 0) {
    for (let i = 0; i < ele.classList.length; i++) {
      // Is this className unique by itself?
      selector = "." + cssEscape(ele.classList.item(i));
      matches = document.querySelectorAll(selector);
      if (matches.length === 1) {
        return selector;
      }
      // Maybe it's unique with a tag name?
      selector = cssEscape(tagName) + selector;
      matches = document.querySelectorAll(selector);
      if (matches.length === 1) {
        return selector;
      }
      // Maybe it's unique using a tag name and nth-child
      index = positionInNodeList(ele, ele.parentNode.children) + 1;
      selector = selector + ":nth-child(" + index + ")";
      matches = document.querySelectorAll(selector);
      if (matches.length === 1) {
        return selector;
      }
    }
  }

  // Not unique enough yet.  As long as it's not a child of the document,
  // continue recursing up until it is unique enough.
  if (ele.parentNode !== document) {
    index = positionInNodeList(ele, ele.parentNode.children) + 1;
    selector = findCssSelector(ele.parentNode) + " > " +
      cssEscape(tagName) + ":nth-child(" + index + ")";
  }

  return selector;
}
PK
!<KmUUmodules/ctypes.jsm/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "ctypes" ];

/*
 * This is the js module for ctypes. Import it like so:
 *   Components.utils.import("resource://gre/modules/ctypes.jsm");
 *
 * This will create a 'ctypes' object, which provides an interface to describe
 * and instantiate C types and call C functions from a dynamic library.
 *
 * For documentation on the API, see:
 * https://developer.mozilla.org/en/js-ctypes/js-ctypes_reference
 *
 */

// Initialize the ctypes object. You do not need to do this yourself.
const init = Components.classes["@mozilla.org/jsctypes;1"].createInstance();
init();

PK
!<H3u

modules/debug.js/* vim:set ts=2 sw=2 sts=2 ci et: */
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file contains functions that are useful for debugging purposes from
// within JavaScript code.

this.EXPORTED_SYMBOLS = ["NS_ASSERT"];

var gTraceOnAssert = true;

/**
 * This function provides a simple assertion function for JavaScript.
 * If the condition is true, this function will do nothing.  If the
 * condition is false, then the message will be printed to the console
 * and an alert will appear showing a stack trace, so that the (alpha
 * or nightly) user can file a bug containing it.  For future enhancements,
 * see bugs 330077 and 330078.
 *
 * To suppress the dialogs, you can run with the environment variable
 * XUL_ASSERT_PROMPT set to 0 (if unset, this defaults to 1).
 *
 * @param condition represents the condition that we're asserting to be
 *                  true when we call this function--should be
 *                  something that can be evaluated as a boolean.
 * @param message   a string to be displayed upon failure of the assertion
 */

this.NS_ASSERT = function NS_ASSERT(condition, message) {
  if (condition)
    return;

  var releaseBuild = true;
  var defB = Components.classes["@mozilla.org/preferences-service;1"]
                       .getService(Components.interfaces.nsIPrefService)
                       .getDefaultBranch(null);
  try {
    switch (defB.getCharPref("app.update.channel")) {
      case "nightly":
      case "aurora":
      case "beta":
      case "default":
        releaseBuild = false;
    }
  } catch (ex) {}

  var caller = arguments.callee.caller;
  var assertionText = "ASSERT: " + message + "\n";

  // Report the error to the console
  Components.utils.reportError(assertionText);

  if (releaseBuild) {
    return;
  }

  // dump the stack to stdout too in non-release builds
  var stackText = "";
  if (gTraceOnAssert) {
    stackText = "Stack Trace: \n";
    var count = 0;
    while (caller) {
      stackText += count++ + ":" + caller.name + "(";
      for (var i = 0; i < caller.arguments.length; ++i) {
        var arg = caller.arguments[i];
        stackText += arg;
        if (i < caller.arguments.length - 1)
          stackText += ",";
      }
      stackText += ")\n";
      caller = caller.arguments.callee.caller;
    }
  }

  dump(assertionText + stackText);
}
PK
!<Y{ޙ--modules/devtools/Console.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

const Cu = Components.utils;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const WARNING_PREF = "devtools.migration.warnings";
if (Services.prefs.getBoolPref(WARNING_PREF)) {
  const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
  Deprecated.warning("This path to Console.jsm is deprecated.  Please use " +
                     "Cu.import(\"resource://gre/modules/Console.jsm\") " +
                     "to load this module.",
                     "https://bugzil.la/912121");
}

this.EXPORTED_SYMBOLS = [
  "console",
  "ConsoleAPI"
];

const module =
  Cu.import("resource://gre/modules/Console.jsm", {});

for (let symbol of this.EXPORTED_SYMBOLS) {
  this[symbol] = module[symbol];
}
PK
!<_%eemodules/devtools/Loader.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

const Cu = Components.utils;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const WARNING_PREF = "devtools.migration.warnings";
if (Services.prefs.getBoolPref(WARNING_PREF)) {
  const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
  Deprecated.warning("This path to Loader.jsm is deprecated.  Please use " +
                     "Cu.import(\"resource://devtools/shared/" +
                     "Loader.jsm\") to load this module.",
                     "https://bugzil.la/912121");
}

this.EXPORTED_SYMBOLS = [
  "DevToolsLoader",
  "devtools",
  "BuiltinProvider",
  "require",
  "loader"
];

const module =
  Cu.import("resource://devtools/shared/Loader.jsm", {});

for (let symbol of this.EXPORTED_SYMBOLS) {
  this[symbol] = module[symbol];
}
PK
!<H+modules/devtools/dbg-client.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

const Cu = Components.utils;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const WARNING_PREF = "devtools.migration.warnings";
if (Services.prefs.getBoolPref(WARNING_PREF)) {
  const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
  Deprecated.warning("dbg-client.jsm is deprecated.  Please use " +
                     "require(\"devtools/shared/client/main\") to load this " +
                     "module.", "https://bugzil.la/912121");
}

const { require } =
  Cu.import("resource://devtools/shared/Loader.jsm", {});

this.EXPORTED_SYMBOLS = ["DebuggerTransport",
                         "DebuggerClient",
                         "RootClient",
                         "LongStringClient",
                         "EnvironmentClient",
                         "ObjectClient"];

var client = require("devtools/shared/client/main");

this.DebuggerClient = client.DebuggerClient;
this.RootClient = client.RootClient;
this.LongStringClient = client.LongStringClient;
this.EnvironmentClient = client.EnvironmentClient;
this.ObjectClient = client.ObjectClient;

this.DebuggerTransport =
  require("devtools/shared/transport/transport").DebuggerTransport;
PK
!<o(hhmodules/devtools/dbg-server.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

const Cu = Components.utils;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const WARNING_PREF = "devtools.migration.warnings";
if (Services.prefs.getBoolPref(WARNING_PREF)) {
  const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
  Deprecated.warning("dbg-server.jsm is deprecated.  Please use " +
                     "require(\"devtools/server/main\") to load this " +
                     "module.",
                     "https://bugzil.la/912121");
}

this.EXPORTED_SYMBOLS = [
  "DebuggerServer",
  "ActorPool",
  "OriginalLocation",
];

const { require } =
  Cu.import("resource://devtools/shared/Loader.jsm", {});
const module = require("devtools/server/main");

for (let symbol of this.EXPORTED_SYMBOLS) {
  this[symbol] = module[symbol];
}
PK
!<w!modules/devtools/event-emitter.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

(function (factory) {
  // Module boilerplate
  if (this.module && module.id.indexOf("event-emitter") >= 0) {
    // require
    factory.call(this, require, exports, module);
  } else {
    // Cu.import
    const Cu = Components.utils;
    const { require } =
      Cu.import("resource://devtools/shared/Loader.jsm", {});
    this.isWorker = false;
    this.promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
    factory.call(this, require, this, { exports: this });
    this.EXPORTED_SYMBOLS = ["EventEmitter"];
  }
}).call(this, function (require, exports, module) {
  const { Cu } = require("chrome");
  const Services = require("Services");
  const WARNING_PREF = "devtools.migration.warnings";
  // Cu and Services aren't accessible from workers
  if (Cu && Services && Services.prefs &&
      Services.prefs.getBoolPref(WARNING_PREF)) {
    const { Deprecated } =
      Cu.import("resource://gre/modules/Deprecated.jsm", {});
    Deprecated.warning("This path to event-emitter.js is deprecated.  Please " +
                       "use require(\"devtools/shared/event-emitter\") to " +
                       "load this module.",
                       "https://bugzil.la/912121");
  }

  const EventEmitter = require("devtools/shared/event-emitter");
  this.EventEmitter = EventEmitter;
  module.exports = EventEmitter;
});
PK
!<_%ee"modules/devtools/shared/Loader.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 only exists to support add-ons which import this module at a
 * specific path.
 */

const Cu = Components.utils;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const WARNING_PREF = "devtools.migration.warnings";
if (Services.prefs.getBoolPref(WARNING_PREF)) {
  const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
  Deprecated.warning("This path to Loader.jsm is deprecated.  Please use " +
                     "Cu.import(\"resource://devtools/shared/" +
                     "Loader.jsm\") to load this module.",
                     "https://bugzil.la/912121");
}

this.EXPORTED_SYMBOLS = [
  "DevToolsLoader",
  "devtools",
  "BuiltinProvider",
  "require",
  "loader"
];

const module =
  Cu.import("resource://devtools/shared/Loader.jsm", {});

for (let symbol of this.EXPORTED_SYMBOLS) {
  this[symbol] = module[symbol];
}
PK
!<8Z
modules/jsdebugger.jsm/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "addDebuggerToGlobal" ];

/*
 * This is the js module for Debugger. Import it like so:
 *   Components.utils.import("resource://gre/modules/jsdebugger.jsm");
 *   addDebuggerToGlobal(this);
 *
 * This will create a 'Debugger' object, which provides an interface to debug
 * JavaScript code running in other compartments in the same process, on the
 * same thread.
 *
 * For documentation on the API, see:
 *   https://developer.mozilla.org/en-US/docs/Tools/Debugger-API
 */

const init = Components.classes["@mozilla.org/jsdebugger;1"].createInstance(Components.interfaces.IJSDebugger);
this.addDebuggerToGlobal = function addDebuggerToGlobal(global) {
  init.addClass(global);
  initPromiseDebugging(global);
};

function initPromiseDebugging(global) {
  if (global.Debugger.Object.prototype.PromiseDebugging) {
    return;
  }

  // If the PromiseDebugging object doesn't have all legacy functions, we're
  // using the new accessors on Debugger.Object already.
  if (!PromiseDebugging.getDependentPromises) {
    return;
  }

  // Otherwise, polyfill them using PromiseDebugging.
  global.Debugger.Object.prototype.PromiseDebugging = PromiseDebugging;
  global.eval(polyfillSource);
}

let polyfillSource = `
  Object.defineProperty(Debugger.Object.prototype, "promiseState", {
    get() {
      const state = this.PromiseDebugging.getState(this.unsafeDereference());
      return {
        state: state.state,
        value: this.makeDebuggeeValue(state.value),
        reason: this.makeDebuggeeValue(state.reason)
      };
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseLifetime", {
    get() {
      return this.PromiseDebugging.getPromiseLifetime(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseTimeToResolution", {
    get() {
      return this.PromiseDebugging.getTimeToSettle(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseDependentPromises", {
    get() {
      let promises = this.PromiseDebugging.getDependentPromises(this.unsafeDereference());
      return promises.map(p => this.makeDebuggeeValue(p));
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseAllocationSite", {
    get() {
      return this.PromiseDebugging.getAllocationStack(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseResolutionSite", {
    get() {
      let state = this.promiseState.state;
      if (state === "fulfilled") {
        return this.PromiseDebugging.getFullfillmentStack(this.unsafeDereference());
      } else {
        return this.PromiseDebugging.getRejectionStack(this.unsafeDereference());
      }
    }
  });
`;
PK
!<Ðmodules/lz4.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var SharedAll;
var Primitives;
if (typeof Components != "undefined") {
  let Cu = Components.utils;
  SharedAll = {};
  Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
  Cu.import("resource://gre/modules/lz4_internal.js");
  Cu.import("resource://gre/modules/ctypes.jsm");

  this.EXPORTED_SYMBOLS = [
    "Lz4"
  ];
  this.exports = {};
} else if (typeof module != "undefined" && typeof require != "undefined") {
  /* eslint-env commonjs */
  SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
  Primitives = require("resource://gre/modules/lz4_internal.js");
} else {
  throw new Error("Please load this module with Component.utils.import or with require()");
}

const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0"

const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size;

const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER;

/**
 * An error during (de)compression
 *
 * @param {string} operation The name of the operation ("compress", "decompress")
 * @param {string} reason A reason to be used when matching errors. Must start
 * with "because", e.g. "becauseInvalidContent".
 * @param {string} message A human-readable message.
 */
function LZError(operation, reason, message) {
  SharedAll.OSError.call(this);
  this.operation = operation;
  this[reason] = true;
  this.message = message;
}
LZError.prototype = Object.create(SharedAll.OSError);
LZError.prototype.toString = function toString() {
  return this.message;
};
exports.Error = LZError;

/**
 * Compress a block to a form suitable for writing to disk.
 *
 * Compatibility note: For the moment, we are basing our code on lz4
 * 1.3, which does not specify a *file* format. Therefore, we define
 * our own format. Once lz4 defines a complete file format, we will
 * migrate both |compressFileContent| and |decompressFileContent| to this file
 * format. For backwards-compatibility, |decompressFileContent| will however
 * keep the ability to decompress files provided with older versions of
 * |compressFileContent|.
 *
 * Compressed files have the following layout:
 *
 * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress |
 *
 * @param {TypedArray|void*} buffer The buffer to write to the disk.
 * @param {object=} options An object that may contain the following fields:
 *  - {number} bytes The number of bytes to read from |buffer|. If |buffer|
 *    is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If
 *    |buffer| is a |void*|, |bytes| MUST be provided.
 * @return {Uint8Array} An array of bytes suitable for being written to the
 * disk.
 */
function compressFileContent(array, options = {}) {
  // Prepare the output array
  let inputBytes;
  if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) {
    inputBytes = array.byteLength;
  } else if (options && options.bytes) {
    inputBytes = options.bytes;
  } else {
    throw new TypeError("compressFileContent requires a size");
  }
  let maxCompressedSize = Primitives.maxCompressedSize(inputBytes);
  let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize);

  // Compress to output array
  let payload = new Uint8Array(outputArray.buffer, outputArray.byteOffset + HEADER_SIZE);
  let compressedSize = Primitives.compress(array, inputBytes, payload);

  // Add headers
  outputArray.set(MAGIC_NUMBER);
  let view = new DataView(outputArray.buffer);
  view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true);

  return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize);
}
exports.compressFileContent = compressFileContent;

function decompressFileContent(array, options = {}) {
  let bytes = SharedAll.normalizeBufferArgs(array, options.bytes || null);
  if (bytes < HEADER_SIZE) {
    throw new LZError("decompress", "becauseLZNoHeader",
      `Buffer is too short (no header) - Data: ${ options.path || array }`);
  }

  // Read headers
  let expectMagicNumber = new DataView(array.buffer, 0, MAGIC_NUMBER.byteLength);
  for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) {
    if (expectMagicNumber.getUint8(i) != MAGIC_NUMBER[i]) {
      throw new LZError("decompress", "becauseLZWrongMagicNumber",
        `Invalid header (no magic number) - Data: ${ options.path || array }`);
    }
  }

  let sizeBuf = new DataView(array.buffer, MAGIC_NUMBER.byteLength, BYTES_IN_SIZE_HEADER);
  let expectDecompressedSize =
    sizeBuf.getUint8(0) +
    (sizeBuf.getUint8(1) << 8) +
    (sizeBuf.getUint8(2) << 16) +
    (sizeBuf.getUint8(3) << 24);
  if (expectDecompressedSize == 0) {
    // The underlying algorithm cannot handle a size of 0
    return new Uint8Array(0);
  }

  // Prepare the input buffer
  let inputData = new DataView(array.buffer, HEADER_SIZE);

  // Prepare the output buffer
  let outputBuffer = new Uint8Array(expectDecompressedSize);
  let decompressedBytes = (new SharedAll.Type.size_t.implementation(0));

  // Decompress
  let success = Primitives.decompress(inputData, bytes - HEADER_SIZE,
                                      outputBuffer, outputBuffer.byteLength,
                                      decompressedBytes.address());
  if (!success) {
    throw new LZError("decompress", "becauseLZInvalidContent",
      `Invalid content: Decompression stopped at ${decompressedBytes.value} - Data: ${ options.path || array }`);
  }
  return new Uint8Array(outputBuffer.buffer, outputBuffer.byteOffset, decompressedBytes.value);
}
exports.decompressFileContent = decompressFileContent;

if (typeof Components != "undefined") {
  this.Lz4 = {
    compressFileContent,
    decompressFileContent
  };
}
PK
!<ʞd>HHmodules/lz4_internal.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

 /* eslint-env commonjs */

"use strict";

var Primitives = {};

var SharedAll;
if (typeof Components != "undefined") {
  let Cu = Components.utils;
  SharedAll = {};
  Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);

  this.EXPORTED_SYMBOLS = [
    "Primitives"
  ];
  this.Primitives = Primitives;
  this.exports = {};
} else if (typeof module != "undefined" && typeof require != "undefined") {
  SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
} else {
  throw new Error("Please load this module with Component.utils.import or with require()");
}

var libxul = new SharedAll.Library("libxul", SharedAll.Constants.Path.libxul);
var Type = SharedAll.Type;

libxul.declareLazyFFI(Primitives, "compress",
  "workerlz4_compress",
  null,
  /* return*/ Type.size_t,
  /* const source*/ Type.void_t.in_ptr,
  /* inputSize*/ Type.size_t,
  /* dest*/ Type.void_t.out_ptr
);

libxul.declareLazyFFI(Primitives, "decompress",
  "workerlz4_decompress",
  null,
  /* return*/ Type.int,
  /* const source*/ Type.void_t.in_ptr,
  /* inputSize*/ Type.size_t,
  /* dest*/ Type.void_t.out_ptr,
  /* maxOutputSize*/ Type.size_t,
  /* actualOutputSize*/ Type.size_t.out_ptr
);

libxul.declareLazyFFI(Primitives, "maxCompressedSize",
  "workerlz4_maxCompressedSize",
  null,
  /* return*/ Type.size_t,
  /* inputSize*/ Type.size_t
);

if (typeof module != "undefined") {
  module.exports = {
    get compress() {
      return Primitives.compress;
    },
    get decompress() {
      return Primitives.decompress;
    },
    get maxCompressedSize() {
      return Primitives.maxCompressedSize;
    }
  };
}
PK
!<MbS[""modules/media/IdpSandbox.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {
  classes: Cc,
  interfaces: Ci,
  utils: Cu,
  results: Cr
} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

/** This little class ensures that redirects maintain an https:// origin */
function RedirectHttpsOnly() {}

RedirectHttpsOnly.prototype = {
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    if (newChannel.URI.scheme !== "https") {
      callback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
    } else {
      callback.onRedirectVerifyCallback(Cr.NS_OK);
    }
  },

  getInterface(iid) {
    return this.QueryInterface(iid);
  },
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink])
};

/** This class loads a resource into a single string. ResourceLoader.load() is
 * the entry point. */
function ResourceLoader(res, rej) {
  this.resolve = res;
  this.reject = rej;
  this.data = "";
}

/** Loads the identified https:// URL. */
ResourceLoader.load = function(uri, doc) {
  return new Promise((resolve, reject) => {
    let listener = new ResourceLoader(resolve, reject);
    let ioChannel = NetUtil.newChannel({
      uri,
      loadingNode: doc,
      securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_SCRIPT
    });

    ioChannel.loadGroup = doc.documentLoadGroup.QueryInterface(Ci.nsILoadGroup);
    ioChannel.notificationCallbacks = new RedirectHttpsOnly();
    ioChannel.asyncOpen2(listener);
  });
};

ResourceLoader.prototype = {
  onDataAvailable(request, context, input, offset, count) {
    let stream = Cc["@mozilla.org/scriptableinputstream;1"]
      .createInstance(Ci.nsIScriptableInputStream);
    stream.init(input);
    this.data += stream.read(count);
  },

  onStartRequest(request, context) {},

  onStopRequest(request, context, status) {
    if (Components.isSuccessCode(status)) {
      var statusCode = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
      if (statusCode === 200) {
        this.resolve({ request, data: this.data });
      } else {
        this.reject(new Error("Non-200 response from server: " + statusCode));
      }
    } else {
      this.reject(new Error("Load failed: " + status));
    }
  },

  getInterface(iid) {
    return this.QueryInterface(iid);
  },
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener])
};

/**
 * A simple implementation of the WorkerLocation interface.
 */
function createLocationFromURI(uri) {
  return {
    href: uri.spec,
    protocol: uri.scheme + ":",
    host: uri.host + ((uri.port >= 0) ?
                      (":" + uri.port) : ""),
    port: uri.port,
    hostname: uri.host,
    pathname: uri.path.replace(/[#\?].*/, ""),
    search: uri.path.replace(/^[^\?]*/, "").replace(/#.*/, ""),
    hash: uri.hasRef ? ("#" + uri.ref) : "",
    origin: uri.prePath,
    toString() {
      return uri.spec;
    }
  };
}

/**
 * A javascript sandbox for running an IdP.
 *
 * @param domain (string) the domain of the IdP
 * @param protocol (string?) the protocol of the IdP [default: 'default']
 * @param win (obj) the current window
 * @throws if the domain or protocol aren't valid
 */
function IdpSandbox(domain, protocol, win) {
  this.source = IdpSandbox.createIdpUri(domain, protocol || "default");
  this.active = null;
  this.sandbox = null;
  this.window = win;
}

IdpSandbox.checkDomain = function(domain) {
  if (!domain || typeof domain !== "string") {
    throw new Error("Invalid domain for identity provider: " +
                    "must be a non-zero length string");
  }
};

/**
 * Checks that the IdP protocol is superficially sane.  In particular, we don't
 * want someone adding relative paths (e.g., '../../myuri'), which could be used
 * to move outside of /.well-known/ and into space that they control.
 */
IdpSandbox.checkProtocol = function(protocol) {
  let message = "Invalid protocol for identity provider: ";
  if (!protocol || typeof protocol !== "string") {
    throw new Error(message + "must be a non-zero length string");
  }
  if (decodeURIComponent(protocol).match(/[\/\\]/)) {
    throw new Error(message + "must not include '/' or '\\'");
  }
};

/**
 * Turns a domain and protocol into a URI.  This does some aggressive checking
 * to make sure that we aren't being fooled somehow.  Throws on fooling.
 */
IdpSandbox.createIdpUri = function(domain, protocol) {
  IdpSandbox.checkDomain(domain);
  IdpSandbox.checkProtocol(protocol);

  let message = "Invalid IdP parameters: ";
  try {
    let wkIdp = "https://" + domain + "/.well-known/idp-proxy/" + protocol;
    let ioService = Components.classes["@mozilla.org/network/io-service;1"]
                    .getService(Ci.nsIIOService);
    let uri = ioService.newURI(wkIdp);

    if (uri.hostPort !== domain) {
      throw new Error(message + "domain is invalid");
    }
    if (uri.path.indexOf("/.well-known/idp-proxy/") !== 0) {
      throw new Error(message + "must produce a /.well-known/idp-proxy/ URI");
    }

    return uri;
  } catch (e) {
    if (typeof e.result !== "undefined" &&
                   e.result === Cr.NS_ERROR_MALFORMED_URI) {
      throw new Error(message + "must produce a valid URI");
    }
    throw e;
  }
};

IdpSandbox.prototype = {
  isSame(domain, protocol) {
    return this.source.spec === IdpSandbox.createIdpUri(domain, protocol).spec;
  },

  start() {
    if (!this.active) {
      this.active = ResourceLoader.load(this.source, this.window.document)
        .then(result => this._createSandbox(result));
    }
    return this.active;
  },

  // Provides the sandbox with some useful facilities.  Initially, this is only
  // a minimal set; it is far easier to add more as the need arises, than to
  // take them back if we discover a mistake.
  _populateSandbox(uri) {
    this.sandbox.location = Cu.cloneInto(createLocationFromURI(uri),
                                         this.sandbox,
                                         { cloneFunctions: true });
  },

  _createSandbox(result) {
    let principal = Services.scriptSecurityManager
      .getChannelResultPrincipal(result.request);

    this.sandbox = Cu.Sandbox(principal, {
      sandboxName: "IdP-" + this.source.host,
      wantComponents: false,
      wantExportHelpers: false,
      wantGlobalProperties: [
        "indexedDB", "XMLHttpRequest", "TextEncoder", "TextDecoder",
        "URL", "URLSearchParams", "atob", "btoa", "Blob", "crypto",
        "rtcIdentityProvider", "fetch"
      ]
    });
    let registrar = this.sandbox.rtcIdentityProvider;
    if (!Cu.isXrayWrapper(registrar)) {
      throw new Error("IdP setup failed");
    }

    // have to use the ultimate URI, not the starting one to avoid
    // that origin stealing from the one that redirected to it
    this._populateSandbox(result.request.URI);
    try {
      Cu.evalInSandbox(result.data, this.sandbox,
                       "latest", result.request.URI.spec, 1);
    } catch (e) {
      // These can be passed straight on, because they are explicitly labelled
      // as being IdP errors by the IdP and we drop line numbers as a result.
      if (e.name === "IdpError" || e.name === "IdpLoginError") {
        throw e;
      }
      this._logError(e);
      throw new Error("Error in IdP, check console for details");
    }

    if (!registrar.hasIdp) {
      throw new Error("IdP failed to call rtcIdentityProvider.register()");
    }
    return registrar;
  },

  // Capture all the details from the error and log them to the console.  This
  // can't rethrow anything else because that could leak information about the
  // internal workings of the IdP across origins.
  _logError(e) {
    let winID = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
    let scriptError = Cc["@mozilla.org/scripterror;1"]
        .createInstance(Ci.nsIScriptError);
    scriptError.initWithWindowID(e.message, e.fileName, null,
                                 e.lineNumber, e.columnNumber,
                                 Ci.nsIScriptError.errorFlag,
                                 "content javascript", winID);
    let consoleService = Cc["@mozilla.org/consoleservice;1"]
        .getService(Ci.nsIConsoleService);
    consoleService.logMessage(scriptError);
  },

  stop() {
    if (this.sandbox) {
      Cu.nukeSandbox(this.sandbox);
    }
    this.sandbox = null;
    this.active = null;
  },

  toString() {
    return this.source.spec;
  }
};

this.EXPORTED_SYMBOLS = ["IdpSandbox"];
this.IdpSandbox = IdpSandbox;
PK
!<e++#modules/media/PeerConnectionIdp.jsm/* jshint moz:true, browser:true */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["PeerConnectionIdp"];

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, "IdpSandbox",
  "resource://gre/modules/media/IdpSandbox.jsm");

/**
 * Creates an IdP helper.
 *
 * @param win (object) the window we are working for
 * @param timeout (int) the timeout in milliseconds
 */
function PeerConnectionIdp(win, timeout) {
  this._win = win;
  this._timeout = timeout || 5000;

  this.provider = null;
  this._resetAssertion();
}

(function() {
  PeerConnectionIdp._mLinePattern = new RegExp("^m=", "m");
  // attributes are funny, the 'a' is case sensitive, the name isn't
  let pattern = "^a=[iI][dD][eE][nN][tT][iI][tT][yY]:(\\S+)";
  PeerConnectionIdp._identityPattern = new RegExp(pattern, "m");
  pattern = "^a=[fF][iI][nN][gG][eE][rR][pP][rR][iI][nN][tT]:(\\S+) (\\S+)";
  PeerConnectionIdp._fingerprintPattern = new RegExp(pattern, "m");
})();

PeerConnectionIdp.prototype = {
  get enabled() {
    return !!this._idp;
  },

  _resetAssertion() {
    this.assertion = null;
    this.idpLoginUrl = null;
  },

  setIdentityProvider(provider, protocol, username) {
    this._resetAssertion();
    this.provider = provider;
    this.protocol = protocol || "default";
    this.username = username;
    if (this._idp) {
      if (this._idp.isSame(provider, protocol)) {
        return; // noop
      }
      this._idp.stop();
    }
    this._idp = new IdpSandbox(provider, protocol, this._win);
  },

  // start the IdP and do some error fixup
  start() {
    return this._idp.start()
      .catch(e => {
        throw new this._win.DOMException(e.message, "IdpError");
      });
  },

  close() {
    this._resetAssertion();
    this.provider = null;
    this.protocol = null;
    if (this._idp) {
      this._idp.stop();
      this._idp = null;
    }
  },

  _getFingerprintsFromSdp(sdp) {
    let fingerprints = {};
    let m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    while (m) {
      fingerprints[m[0]] = { algorithm: m[1], digest: m[2] };
      sdp = sdp.substring(m.index + m[0].length);
      m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    }

    return Object.keys(fingerprints).map(k => fingerprints[k]);
  },

  _isValidAssertion(assertion) {
    return assertion && assertion.idp &&
      typeof assertion.idp.domain === "string" &&
      (!assertion.idp.protocol ||
       typeof assertion.idp.protocol === "string") &&
      typeof assertion.assertion === "string";
  },

  _getIdentityFromSdp(sdp) {
    // a=identity is session level
    let idMatch;
    let mLineMatch = sdp.match(PeerConnectionIdp._mLinePattern);
    if (mLineMatch) {
      let sessionLevel = sdp.substring(0, mLineMatch.index);
      idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
    }
    if (!idMatch) {
      return undefined; // undefined === no identity
    }

    let assertion;
    try {
      assertion = JSON.parse(atob(idMatch[1]));
    } catch (e) {
      throw new this._win.DOMException("invalid identity assertion: " + e,
                                       "InvalidSessionDescriptionError");
    }
    if (!this._isValidAssertion(assertion)) {
      throw new this._win.DOMException("assertion missing idp/idp.domain/assertion",
                                       "InvalidSessionDescriptionError");
    }
    return assertion;
  },

  /**
   * Verifies the a=identity line the given SDP contains, if any.
   * If the verification succeeds callback is called with the message from the
   * IdP proxy as parameter, else (verification failed OR no a=identity line in
   * SDP at all) null is passed to callback.
   *
   * Note that this only verifies that the SDP is coherent.  We still rely on
   * the fact that the RTCPeerConnection won't connect to a peer if the
   * fingerprint of the certificate they offer doesn't appear in the SDP.
   */
  verifyIdentityFromSDP(sdp, origin) {
    let identity = this._getIdentityFromSdp(sdp);
    let fingerprints = this._getFingerprintsFromSdp(sdp);
    if (!identity || fingerprints.length <= 0) {
      return this._win.Promise.resolve(); // undefined result = no identity
    }

    this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
    return this._verifyIdentity(identity.assertion, fingerprints, origin);
  },

  /**
   * Checks that the name in the identity provided by the IdP is OK.
   *
   * @param name (string) the name to validate
   * @throws if the name isn't valid
   */
  _validateName(name) {
    let error = msg => {
        throw new this._win.DOMException("assertion name error: " + msg,
                                         "IdpError");
    };

    if (typeof name !== "string") {
      error("name not a string");
    }
    let atIdx = name.indexOf("@");
    if (atIdx <= 0) {
      error("missing authority in name from IdP");
    }

    // no third party assertions... for now
    let tail = name.substring(atIdx + 1);

    // strip the port number, if present
    let provider = this.provider;
    let providerPortIdx = provider.indexOf(":");
    if (providerPortIdx > 0) {
      provider = provider.substring(0, providerPortIdx);
    }
    let idnService = Components.classes["@mozilla.org/network/idn-service;1"]
        .getService(Components.interfaces.nsIIDNService);
    if (idnService.convertUTF8toACE(tail) !==
        idnService.convertUTF8toACE(provider)) {
      error('name "' + name +
            '" doesn\'t match IdP: "' + this.provider + '"');
    }
  },

  /**
   * Check the validation response.  We are very defensive here when handling
   * the message from the IdP proxy.  That way, broken IdPs aren't likely to
   * cause catastrophic damage.
   */
  _checkValidation(validation, sdpFingerprints) {
    let error = msg => {
      throw new this._win.DOMException("IdP validation error: " + msg,
                                       "IdpError");
    };

    if (!this.provider) {
      error("IdP closed");
    }

    if (typeof validation !== "object" ||
        typeof validation.contents !== "string" ||
        typeof validation.identity !== "string") {
      error("no payload in validation response");
    }

    let fingerprints;
    try {
      fingerprints = JSON.parse(validation.contents).fingerprint;
    } catch (e) {
      error("invalid JSON");
    }

    let isFingerprint = f =>
        (typeof f.digest === "string") &&
        (typeof f.algorithm === "string");
    if (!Array.isArray(fingerprints) || !fingerprints.every(isFingerprint)) {
      error("fingerprints must be an array of objects" +
            " with digest and algorithm attributes");
    }

    // everything in `innerSet` is found in `outerSet`
    let isSubsetOf = (outerSet, innerSet, comparator) => {
      return innerSet.every(i => {
        return outerSet.some(o => comparator(i, o));
      });
    };
    let compareFingerprints = (a, b) => {
      return (a.digest === b.digest) && (a.algorithm === b.algorithm);
    };
    if (!isSubsetOf(fingerprints, sdpFingerprints, compareFingerprints)) {
      error("the fingerprints must be covered by the assertion");
    }
    this._validateName(validation.identity);
    return validation;
  },

  /**
   * Asks the IdP proxy to verify an identity assertion.
   */
  _verifyIdentity(assertion, fingerprints, origin) {
    let p = this.start()
        .then(idp => this._wrapCrossCompartmentPromise(
          idp.validateAssertion(assertion, origin)))
        .then(validation => this._checkValidation(validation, fingerprints));

    return this._applyTimeout(p);
  },

  /**
   * Enriches the given SDP with an `a=identity` line.  getIdentityAssertion()
   * must have already run successfully, otherwise this does nothing to the sdp.
   */
  addIdentityAttribute(sdp) {
    if (!this.assertion) {
      return sdp;
    }

    // yes, we assume that this matches; if it doesn't something is *wrong*
    let match = sdp.match(PeerConnectionIdp._mLinePattern);
    return sdp.substring(0, match.index) +
      "a=identity:" + this.assertion + "\r\n" +
      sdp.substring(match.index);
  },

  /**
   * Asks the IdP proxy for an identity assertion.  Don't call this unless you
   * have checked .enabled, or you really like exceptions.  Also, don't call
   * this when another call is still running, because it's not certain which
   * call will finish first and the final state will be similarly uncertain.
   */
  getIdentityAssertion(fingerprint, origin) {
    if (!this.enabled) {
      throw new this._win.DOMException(
        "no IdP set, call setIdentityProvider() to set one", "InvalidStateError");
    }

    let [algorithm, digest] = fingerprint.split(" ", 2);
    let content = {
      fingerprint: [{
        algorithm,
        digest
      }]
    };

    this._resetAssertion();
    let p = this.start()
        .then(idp => this._wrapCrossCompartmentPromise(
          idp.generateAssertion(JSON.stringify(content),
                                origin, this.username)))
        .then(assertion => {
          if (!this._isValidAssertion(assertion)) {
            throw new this._win.DOMException("IdP generated invalid assertion",
                                             "IdpError");
          }
          // save the base64+JSON assertion, since that is all that is used
          this.assertion = btoa(JSON.stringify(assertion));
          return this.assertion;
        });

    return this._applyTimeout(p);
  },

  /**
   * Promises generated by the sandbox need to be very carefully treated so that
   * they can chain into promises in the `this._win` compartment.  Results need
   * to be cloned across; errors need to be converted.
   */
  _wrapCrossCompartmentPromise(sandboxPromise) {
    return new this._win.Promise((resolve, reject) => {
      sandboxPromise.then(
        result => resolve(Cu.cloneInto(result, this._win)),
        e => {
          let message = "" + (e.message || JSON.stringify(e) || "IdP error");
          if (e.name === "IdpLoginError") {
            if (typeof e.loginUrl === "string") {
              this.idpLoginUrl = e.loginUrl;
            }
            reject(new this._win.DOMException(message, "IdpLoginError"));
          } else {
            reject(new this._win.DOMException(message, "IdpError"));
          }
        });
    });
  },

  /**
   * Wraps a promise, adding a timeout guard on it so that it can't take longer
   * than the specified time.  Returns a promise that rejects if the timeout
   * elapses before `p` resolves.
   */
  _applyTimeout(p) {
    let timeout = new this._win.Promise(
      r => this._win.setTimeout(r, this._timeout))
        .then(() => {
          throw new this._win.DOMException("IdP timed out", "IdpError");
        });
    return this._win.Promise.race([ timeout, p ]);
  }
};

this.PeerConnectionIdp = PeerConnectionIdp;
PK
!<FG modules/media/RTCStatsReport.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["convertToRTCStatsReport"];

function convertToRTCStatsReport(dict) {
  function appendStats(stats, report) {
    stats.forEach(function(stat) {
        report[stat.id] = stat;
      });
  }
  let report = {};
  appendStats(dict.inboundRTPStreamStats, report);
  appendStats(dict.outboundRTPStreamStats, report);
  appendStats(dict.rtpContributingSourceStats, report);
  appendStats(dict.mediaStreamTrackStats, report);
  appendStats(dict.mediaStreamStats, report);
  appendStats(dict.transportStats, report);
  appendStats(dict.iceComponentStats, report);
  appendStats(dict.iceCandidatePairStats, report);
  appendStats(dict.iceCandidateStats, report);
  appendStats(dict.codecStats, report);
  return report;
}

this.convertToRTCStatsReport = convertToRTCStatsReport;
PK
!<2c۰##modules/microformat-shiv.js/*
   Modern
   microformat-shiv - v1.4.0
   Built: 2016-03-02 10:03 - http://microformat-shiv.com
   Copyright (c) 2016 Glenn Jones
   Licensed MIT
*/


var Microformats; // jshint ignore:line

(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], factory);
    } else if (typeof exports === "object") {
        module.exports = factory();
    } else {
        root.Microformats = factory();
  }
}(this, function() {

    var modules = {};


    modules.version = "1.4.0";
    modules.livingStandard = "2015-09-25T12:26:04Z";

    /**
     * constructor
     *
     */
    modules.Parser = function() {
        this.rootPrefix = "h-";
        this.propertyPrefixes = ["p-", "dt-", "u-", "e-"];
        this.excludeTags = ["br", "hr"];
    };


    // create objects incase the v1 map modules don't load
    modules.maps = (modules.maps) ? modules.maps : {};
    modules.rels = (modules.rels) ? modules.rels : {};


    modules.Parser.prototype = {

        init() {
            this.rootNode = null;
            this.document = null;
            this.options = {
                "baseUrl": "",
                "filters": [],
                "textFormat": "whitespacetrimmed",
                "dateFormat": "auto", // html5 for testing
                "overlappingVersions": false,
                "impliedPropertiesByVersion": true,
                "parseLatLonGeo": false
            };
            this.rootID = 0;
            this.errors = [];
            this.noContentErr = "No options.node or options.html was provided and no document object could be found.";
        },


        /**
         * internal parse function
         *
         * @param  {Object} options
         * @return {Object}
         */
        get(options) {
            var out = this.formatEmpty(),
                data = [],
                rels;

            this.init();
            options = (options) ? options : {};
            this.mergeOptions(options);
            this.getDOMContext( options );

            // if we do not have any context create error
            if (!this.rootNode || !this.document) {
                this.errors.push(this.noContentErr);
            } else {

                // only parse h-* microformats if we need to
                // this is added to speed up parsing
                if (this.hasMicroformats(this.rootNode, options)) {
                    this.prepareDOM( options );

                    if (this.options.filters.length > 0) {
                        // parse flat list of items
                        var newRootNode = this.findFilterNodes(this.rootNode, this.options.filters);
                        data = this.walkRoot(newRootNode);
                    } else {
                        // parse whole document from root
                        data = this.walkRoot(this.rootNode);
                    }

                    out.items = data;
                    // don't clear-up DOM if it was cloned
                    if (modules.domUtils.canCloneDocument(this.document) === false) {
                        this.clearUpDom(this.rootNode);
                    }
                }

                // find any rels
                if (this.findRels) {
                    rels = this.findRels(this.rootNode);
                    out.rels = rels.rels;
                    out["rel-urls"] = rels["rel-urls"];
                }

            }

            if (this.errors.length > 0) {
                return this.formatError();
            }
            return out;
        },


        /**
         * parse to get parent microformat of passed node
         *
         * @param  {DOM Node} node
         * @param  {Object} options
         * @return {Object}
         */
        getParent(node, options) {
            this.init();
            options = (options) ? options : {};

            if (node) {
                return this.getParentTreeWalk(node, options);
            }
            this.errors.push(this.noContentErr);
            return this.formatError();
        },


        /**
         * get the count of microformats
         *
         * @param  {DOM Node} rootNode
         * @return {Int}
         */
        count( options ) {
            var out = {},
                items,
                classItems,
                x,
                i;

            this.init();
            options = (options) ? options : {};
            this.getDOMContext( options );

            // if we do not have any context create error
            if (!this.rootNode || !this.document) {
                return {"errors": [this.noContentErr]};
            }
            items = this.findRootNodes( this.rootNode, true );
            i = items.length;
            while (i--) {
                classItems = modules.domUtils.getAttributeList(items[i], "class");
                x = classItems.length;
                while (x--) {
                    // find v2 names
                    if (modules.utils.startWith( classItems[x], "h-" )) {
                        this.appendCount(classItems[x], 1, out);
                    }
                    // find v1 names
                    for (var key in modules.maps) {
                        // dont double count if v1 and v2 roots are present
                        if (modules.maps[key].root === classItems[x] && classItems.indexOf(key) === -1) {
                            this.appendCount(key, 1, out);
                        }
                    }
                }
            }
            var relCount = this.countRels( this.rootNode );
            if (relCount > 0) {
                out.rels = relCount;
            }

            return out;
        },


        /**
         * does a node have a class that marks it as a microformats root
         *
         * @param  {DOM Node} node
         * @param  {Objecte} options
         * @return {Boolean}
         */
        isMicroformat( node, options ) {
            var classes,
                i;

            if (!node) {
                return false;
            }

            // if documemt gets topmost node
            node = modules.domUtils.getTopMostNode( node );

            // look for h-* microformats
            classes = this.getUfClassNames(node);
            if (options && options.filters && modules.utils.isArray(options.filters)) {
                i = options.filters.length;
                while (i--) {
                    if (classes.root.indexOf(options.filters[i]) > -1) {
                        return true;
                    }
                }
                return false;
            }
            return (classes.root.length > 0);
        },


        /**
         * does a node or its children have microformats
         *
         * @param  {DOM Node} node
         * @param  {Objecte} options
         * @return {Boolean}
         */
        hasMicroformats( node, options ) {
            var items,
                i;

            if (!node) {
                return false;
            }

            // if browser based documemt get topmost node
            node = modules.domUtils.getTopMostNode( node );

            // returns all microformat roots
            items = this.findRootNodes( node, true );
            if (options && options.filters && modules.utils.isArray(options.filters)) {
                i = items.length;
                while (i--) {
                    if ( this.isMicroformat( items[i], options ) ) {
                        return true;
                    }
                }
                return false;
            }
            return (items.length > 0);
        },


        /**
         * add a new v1 mapping object to parser
         *
         * @param  {Array} maps
         */
        add( maps ) {
            maps.forEach(function(map) {
                if (map && map.root && map.name && map.properties) {
                modules.maps[map.name] = JSON.parse(JSON.stringify(map));
                }
            });
        },


        /**
         * internal parse to get parent microformats by walking up the tree
         *
         * @param  {DOM Node} node
         * @param  {Object} options
         * @param  {Int} recursive
         * @return {Object}
         */
        getParentTreeWalk(node, options, recursive) {
            options = (options) ? options : {};

            // recursive calls
            if (recursive === undefined) {
                if (node.parentNode && node.nodeName !== "HTML") {
                    return this.getParentTreeWalk(node.parentNode, options, true);
                }
                return this.formatEmpty();
            }
            if (node !== null && node !== undefined && node.parentNode) {
                if (this.isMicroformat( node, options )) {
                    // if we have a match return microformat
                    options.node = node;
                    return this.get( options );
                }
                return this.getParentTreeWalk(node.parentNode, options, true);
            }
            return this.formatEmpty();
        },



        /**
         * configures what are the base DOM objects for parsing
         *
         * @param  {Object} options
         */
        getDOMContext( options ) {
            var nodes = modules.domUtils.getDOMContext( options );
            this.rootNode = nodes.rootNode;
            this.document = nodes.document;
        },


        /**
         * prepares DOM before the parse begins
         *
         * @param  {Object} options
         * @return {Boolean}
         */
        prepareDOM( options ) {
            var baseTag,
                href;

            // use current document to define baseUrl, try/catch needed for IE10+ error
            try {
                if (!options.baseUrl && this.document && this.document.location) {
                    this.options.baseUrl = this.document.location.href;
                }
            } catch (e) {
                // there is no alt action
            }


            // find base tag to set baseUrl
            baseTag = modules.domUtils.querySelector(this.document, "base");
            if (baseTag) {
                href = modules.domUtils.getAttribute(baseTag, "href");
                if (href) {
                    this.options.baseUrl = href;
                }
            }

            // get path to rootNode
            // then clone document
            // then reset the rootNode to its cloned version in a new document
            var path,
                newDocument,
                newRootNode;

            path = modules.domUtils.getNodePath(this.rootNode);
            newDocument = modules.domUtils.cloneDocument(this.document);
            newRootNode = modules.domUtils.getNodeByPath(newDocument, path);

            // check results as early IE fails
            if (newDocument && newRootNode) {
                this.document = newDocument;
                this.rootNode = newRootNode;
            }

            // add includes
            if (this.addIncludes) {
                this.addIncludes( this.document );
            }

            return (this.rootNode && this.document);
        },


        /**
         * returns an empty structure with errors
         *
         *   @return {Object}
         */
        formatError() {
            var out = this.formatEmpty();
            out.errors = this.errors;
            return out;
        },


        /**
         * returns an empty structure
         *
         *   @return {Object}
         */
        formatEmpty() {
            return {
                "items": [],
                "rels": {},
                "rel-urls": {}
            };
        },


        // find microformats of a given type and return node structures
        findFilterNodes(rootNode, filters) {
            if (modules.utils.isString(filters)) {
                filters = [filters];
            }
            var newRootNode = modules.domUtils.createNode("div"),
                items = this.findRootNodes(rootNode, true),
                i = 0,
                x = 0,
                y = 0;

            // add v1 names
            y = filters.length;
            while (y--) {
                if (this.getMapping(filters[y])) {
                    var v1Name = this.getMapping(filters[y]).root;
                    filters.push(v1Name);
                }
            }

            if (items) {
                i = items.length;
                while (x < i) {
                    // append matching nodes into newRootNode
                    y = filters.length;
                    while (y--) {
                        if (modules.domUtils.hasAttributeValue(items[x], "class", filters[y])) {
                            var clone = modules.domUtils.clone(items[x]);
                            modules.domUtils.appendChild(newRootNode, clone);
                            break;
                        }
                    }
                    x++;
                }
            }

            return newRootNode;
        },


        /**
         * appends data to output object for count
         *
         * @param  {string} name
         * @param  {Int} count
         * @param  {Object}
         */
        appendCount(name, count, out) {
            if (out[name]) {
                out[name] = out[name] + count;
            } else {
                out[name] = count;
            }
        },


        /**
         * is the microformats type in the filter list
         *
         * @param  {Object} uf
         * @param  {Array} filters
         * @return {Boolean}
         */
        shouldInclude(uf, filters) {
            var i;

            if (modules.utils.isArray(filters) && filters.length > 0) {
                i = filters.length;
                while (i--) {
                    if (uf.type[0] === filters[i]) {
                        return true;
                    }
                }
                return false;
            }
            return true;
        },


        /**
         * finds all microformat roots in a rootNode
         *
         * @param  {DOM Node} rootNode
         * @param  {Boolean} includeRoot
         * @return {Array}
         */
        findRootNodes(rootNode, includeRoot) {
            var arr = null,
                out = [],
                classList = [],
                items,
                x,
                i,
                y,
                key;


            // build an array of v1 root names
            for (key in modules.maps) {
                if (modules.maps.hasOwnProperty(key)) {
                    classList.push(modules.maps[key].root);
                }
            }

            // get all elements that have a class attribute
            includeRoot = (includeRoot) ? includeRoot : false;
            if (includeRoot && rootNode.parentNode) {
                arr = modules.domUtils.getNodesByAttribute(rootNode.parentNode, "class");
            } else {
                arr = modules.domUtils.getNodesByAttribute(rootNode, "class");
            }

            // loop elements that have a class attribute
            x = 0;
            i = arr.length;
            while (x < i) {

                items = modules.domUtils.getAttributeList(arr[x], "class");

                // loop classes on an element
                y = items.length;
                while (y--) {
                    // match v1 root names
                    if (classList.indexOf(items[y]) > -1) {
                        out.push(arr[x]);
                        break;
                    }

                    // match v2 root name prefix
                    if (modules.utils.startWith(items[y], "h-")) {
                        out.push(arr[x]);
                        break;
                    }
                }

                x++;
            }
            return out;
        },


        /**
         * starts the tree walk to find microformats
         *
         * @param  {DOM Node} node
         * @return {Array}
         */
        walkRoot(node) {
            var context = this,
                children = [],
                child,
                classes,
                items = [],
                out = [];

            classes = this.getUfClassNames(node);
            // if it is a root microformat node
            if (classes && classes.root.length > 0) {
                items = this.walkTree(node);

                if (items.length > 0) {
                    out = out.concat(items);
                }
            } else {
                // check if there are children and one of the children has a root microformat
                children = modules.domUtils.getChildren( node );
                if (children && children.length > 0 && this.findRootNodes(node, true).length > -1) {
                    for (var i = 0; i < children.length; i++) {
                        child = children[i];
                        items = context.walkRoot(child);
                        if (items.length > 0) {
                            out = out.concat(items);
                        }
                    }
                }
            }
            return out;
        },


        /**
         * starts the tree walking for a single microformat
         *
         * @param  {DOM Node} node
         * @return {Array}
         */
        walkTree(node) {
            var classes,
                out = [],
                obj,
                itemRootID;

            // loop roots found on one element
            classes = this.getUfClassNames(node);
            if (classes && classes.root.length && classes.root.length > 0) {

                this.rootID++;
                itemRootID = this.rootID;
                obj = this.createUfObject(classes.root, classes.typeVersion);

                this.walkChildren(node, obj, classes.root, itemRootID, classes);
                if (this.impliedRules) {
                    this.impliedRules(node, obj, classes);
                }
                out.push( this.cleanUfObject(obj) );


            }
            return out;
        },


        /**
         * finds child properties of microformat
         *
         * @param  {DOM Node} node
         * @param  {Object} out
         * @param  {String} ufName
         * @param  {Int} rootID
         * @param  {Object} parentClasses
         */
        walkChildren(node, out, ufName, rootID, parentClasses) {
            var context = this,
                children = [],
                rootItem,
                itemRootID,
                value,
                propertyName,
                propertyVersion,
                i,
                x,
                y,
                z,
                child;

            children = modules.domUtils.getChildren( node );

            y = 0;
            z = children.length;
            while (y < z) {
                child = children[y];

                // get microformat classes for this single element
                var classes = context.getUfClassNames(child, ufName);

                // a property which is a microformat
                if (classes.root.length > 0 && classes.properties.length > 0 && !child.addedAsRoot) {
                    // create object with type, property and value
                    rootItem = context.createUfObject(
                        classes.root,
                        classes.typeVersion,
                        modules.text.parse(this.document, child, context.options.textFormat)
                    );

                    // add the microformat as an array of properties
                    propertyName = context.removePropPrefix(classes.properties[0][0]);

                    // modifies value with "implied value rule"
                    if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
                        if (context.impliedValueRule) {
                            out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[0][0], value);
                        }
                    }

                    if (out.properties[propertyName]) {
                        out.properties[propertyName].push(rootItem);
                    } else {
                        out.properties[propertyName] = [rootItem];
                    }

                    context.rootID++;
                    // used to stop duplication in heavily nested structures
                    child.addedAsRoot = true;


                    x = 0;
                    i = rootItem.type.length;
                    itemRootID = context.rootID;
                    while (x < i) {
                        context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
                        x++;
                    }
                    if (this.impliedRules) {
                        context.impliedRules(child, rootItem, classes);
                    }
                    this.cleanUfObject(rootItem);

                }

                // a property which is NOT a microformat and has not been used for a given root element
                if (classes.root.length === 0 && classes.properties.length > 0) {

                    x = 0;
                    i = classes.properties.length;
                    while (x < i) {

                        value = context.getValue(child, classes.properties[x][0], out);
                        propertyName = context.removePropPrefix(classes.properties[x][0]);
                        propertyVersion = classes.properties[x][1];

                        // modifies value with "implied value rule"
                        if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
                            if (context.impliedValueRule) {
                                out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[x][0], value);
                            }
                        }

                        // if we have not added this value into a property with the same name already
                        if (!context.hasRootID(child, rootID, propertyName)) {
                            // check the root and property is the same version or if overlapping versions are allowed
                            if ( context.isAllowedPropertyVersion( out.typeVersion, propertyVersion ) ) {
                                // add the property as an array of properties
                                if (out.properties[propertyName]) {
                                    out.properties[propertyName].push(value);
                                } else {
                                    out.properties[propertyName] = [value];
                                }
                                // add rootid to node so we can track its use
                                context.appendRootID(child, rootID, propertyName);
                            }
                        }

                        x++;
                    }

                    context.walkChildren(child, out, ufName, rootID, classes);
                }

                // if the node has no microformat classes, see if its children have
                if (classes.root.length === 0 && classes.properties.length === 0) {
                    context.walkChildren(child, out, ufName, rootID, classes);
                }

                // if the node is a child root add it to the children tree
                if (classes.root.length > 0 && classes.properties.length === 0) {

                    // create object with type, property and value
                    rootItem = context.createUfObject(
                        classes.root,
                        classes.typeVersion,
                        modules.text.parse(this.document, child, context.options.textFormat)
                    );

                    // add the microformat as an array of properties
                    if (!out.children) {
                        out.children =  [];
                    }

                    if (!context.hasRootID(child, rootID, "child-root")) {
                        out.children.push( rootItem );
                        context.appendRootID(child, rootID, "child-root");
                        context.rootID++;
                    }

                    x = 0;
                    i = rootItem.type.length;
                    itemRootID = context.rootID;
                    while (x < i) {
                        context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
                        x++;
                    }
                    if (this.impliedRules) {
                        context.impliedRules(child, rootItem, classes);
                    }
                    context.cleanUfObject( rootItem );

                }



                y++;
            }

        },




        /**
         * gets the value of a property from a node
         *
         * @param  {DOM Node} node
         * @param  {String} className
         * @param  {Object} uf
         * @return {String || Object}
         */
        getValue(node, className, uf) {
            var value = "";

            if (modules.utils.startWith(className, "p-")) {
                value = this.getPValue(node, true);
            }

            if (modules.utils.startWith(className, "e-")) {
                value = this.getEValue(node);
            }

            if (modules.utils.startWith(className, "u-")) {
                value = this.getUValue(node, true);
            }

            if (modules.utils.startWith(className, "dt-")) {
                value = this.getDTValue(node, className, uf, true);
            }
            return value;
        },


        /**
         * gets the value of a node which contains a 'p-' property
         *
         * @param  {DOM Node} node
         * @param  {Boolean} valueParse
         * @return {String}
         */
        getPValue(node, valueParse) {
            var out = "";
            if (valueParse) {
                out = this.getValueClass(node, "p");
            }

            if (!out && valueParse) {
                out = this.getValueTitle(node);
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["abbr"], "title");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["data", "input"], "value");
            }

            if (node.name === "br" || node.name === "hr") {
                out = "";
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["img", "area"], "alt");
            }

            if (!out) {
                out = modules.text.parse(this.document, node, this.options.textFormat);
            }

            return (out) ? out : "";
        },


        /**
         * gets the value of a node which contains the 'e-' property
         *
         * @param  {DOM Node} node
         * @return {Object}
         */
        getEValue(node) {

            var out = {value: "", html: ""};

            this.expandURLs(node, "src", this.options.baseUrl);
            this.expandURLs(node, "href", this.options.baseUrl);

            out.value = modules.text.parse(this.document, node, this.options.textFormat);
            out.html = modules.html.parse(node);

            return out;
        },


        /**
         * gets the value of a node which contains the 'u-' property
         *
         * @param  {DOM Node} node
         * @param  {Boolean} valueParse
         * @return {String}
         */
        getUValue(node, valueParse) {
            var out = "";
            if (valueParse) {
                out = this.getValueClass(node, "u");
            }

            if (!out && valueParse) {
                out = this.getValueTitle(node);
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["a", "area"], "href");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["img", "audio", "video", "source"], "src");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["object"], "data");
            }

            // if we have no protocol separator, turn relative url to absolute url
            if (out && out !== "" && out.indexOf("://") === -1) {
                out = modules.url.resolve(out, this.options.baseUrl);
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["abbr"], "title");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["data", "input"], "value");
            }

            if (!out) {
                out = modules.text.parse(this.document, node, this.options.textFormat);
            }

            return (out) ? out : "";
        },


        /**
         * gets the value of a node which contains the 'dt-' property
         *
         * @param  {DOM Node} node
         * @param  {String} className
         * @param  {Object} uf
         * @param  {Boolean} valueParse
         * @return {String}
         */
        getDTValue(node, className, uf, valueParse) {
            var out = "";

            if (valueParse) {
                out = this.getValueClass(node, "dt");
            }

            if (!out && valueParse) {
                out = this.getValueTitle(node);
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["time", "ins", "del"], "datetime");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["abbr"], "title");
            }

            if (!out) {
                out = modules.domUtils.getAttrValFromTagList(node, ["data", "input"], "value");
            }

            if (!out) {
                out = modules.text.parse(this.document, node, this.options.textFormat);
            }

            if (out) {
                if (modules.dates.isDuration(out)) {
                    // just duration
                    return out;
                } else if (modules.dates.isTime(out)) {
                    // just time or time+timezone
                    if (uf) {
                        uf.times.push([className, modules.dates.parseAmPmTime(out, this.options.dateFormat)]);
                    }
                    return modules.dates.parseAmPmTime(out, this.options.dateFormat);
                }
                // returns a date - microformat profile
                if (uf) {
                    uf.dates.push([className, new modules.ISODate(out).toString( this.options.dateFormat )]);
                }
                return new modules.ISODate(out).toString( this.options.dateFormat );
            }
            return "";
        },


        /**
         * appends a new rootid to a given node
         *
         * @param  {DOM Node} node
         * @param  {String} id
         * @param  {String} propertyName
         */
        appendRootID(node, id, propertyName) {
            if (this.hasRootID(node, id, propertyName) === false) {
                var rootids = [];
                if (modules.domUtils.hasAttribute(node, "rootids")) {
                    rootids = modules.domUtils.getAttributeList(node, "rootids");
                }
                rootids.push("id" + id + "-" + propertyName);
                modules.domUtils.setAttribute(node, "rootids", rootids.join(" "));
            }
        },


        /**
         * does a given node already have a rootid
         *
         * @param  {DOM Node} node
         * @param  {String} id
         * @param  {String} propertyName
         * @return {Boolean}
         */
        hasRootID(node, id, propertyName) {
            var rootids = [];
            if (!modules.domUtils.hasAttribute(node, "rootids")) {
                return false;
            }
            rootids = modules.domUtils.getAttributeList(node, "rootids");
            return (rootids.indexOf("id" + id + "-" + propertyName) > -1);
        },



        /**
         * gets the text of any child nodes with a class value
         *
         * @param  {DOM Node} node
         * @param  {String} propertyName
         * @return {String || null}
         */
        getValueClass(node, propertyType) {
            var context = this,
                children = [],
                out = [],
                child,
                x,
                i;

            children = modules.domUtils.getChildren( node );

            x = 0;
            i = children.length;
            while (x < i) {
                child = children[x];
                var value = null;
                if (modules.domUtils.hasAttributeValue(child, "class", "value")) {
                    switch (propertyType) {
                    case "p":
                        value = context.getPValue(child, false);
                        break;
                    case "u":
                        value = context.getUValue(child, false);
                        break;
                    case "dt":
                        value = context.getDTValue(child, "", null, false);
                        break;
                    }
                    if (value) {
                        out.push(modules.utils.trim(value));
                    }
                }
                x++;
            }
            if (out.length > 0) {
                if (propertyType === "p") {
                    return modules.text.parseText( this.document, out.join(" "), this.options.textFormat);
                }
                if (propertyType === "u") {
                    return out.join("");
                }
                if (propertyType === "dt") {
                    return modules.dates.concatFragments(out, this.options.dateFormat).toString(this.options.dateFormat);
                }
                return undefined;
            }
            return null;
        },


        /**
         * returns a single string of the 'title' attr from all
         * the child nodes with the class 'value-title'
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        getValueTitle(node) {
            var out = [],
                items,
                i,
                x;

            items = modules.domUtils.getNodesByAttributeValue(node, "class", "value-title");
            x = 0;
            i = items.length;
            while (x < i) {
                if (modules.domUtils.hasAttribute(items[x], "title")) {
                    out.push(modules.domUtils.getAttribute(items[x], "title"));
                }
                x++;
            }
            return out.join("");
        },


       /**
         * finds out whether a node has h-* class v1 and v2
         *
         * @param  {DOM Node} node
         * @return {Boolean}
         */
        hasHClass(node) {
            var classes = this.getUfClassNames(node);
            if (classes.root && classes.root.length > 0) {
                return true;
            }
            return false;
        },


        /**
         * get both the root and property class names from a node
         *
         * @param  {DOM Node} node
         * @param  {Array} ufNameArr
         * @return {Object}
         */
        getUfClassNames(node, ufNameArr) {
            var context = this,
                out = {
                    "root": [],
                    "properties": []
                },
                classNames,
                key,
                items,
                item,
                i,
                x,
                z,
                y,
                map,
                prop,
                propName,
                v2Name,
                impiedRel,
                ufName;

            // don't get classes from excluded list of tags
            if (modules.domUtils.hasTagName(node, this.excludeTags) === false) {

                // find classes for node
                classNames = modules.domUtils.getAttribute(node, "class");
                if (classNames) {
                    items = classNames.split(" ");
                    x = 0;
                    i = items.length;
                    while (x < i) {

                        item = modules.utils.trim(items[x]);

                        // test for root prefix - v2
                        if (modules.utils.startWith(item, context.rootPrefix)) {
                            if (out.root.indexOf(item) === -1) {
                                out.root.push(item);
                            }
                            out.typeVersion = "v2";
                        }

                        // test for property prefix - v2
                        z = context.propertyPrefixes.length;
                        while (z--) {
                            if (modules.utils.startWith(item, context.propertyPrefixes[z])) {
                                out.properties.push([item, "v2"]);
                            }
                        }

                        // test for mapped root classnames v1
                        for (key in modules.maps) {
                            if (modules.maps.hasOwnProperty(key)) {
                                // only add a root once
                                if (modules.maps[key].root === item && out.root.indexOf(key) === -1) {
                                    // if root map has subTree set to true
                                    // test to see if we should create a property or root
                                    if (modules.maps[key].subTree) {
                                        out.properties.push(["p-" + modules.maps[key].root, "v1"]);
                                    } else {
                                        out.root.push(key);
                                        if (!out.typeVersion) {
                                            out.typeVersion = "v1";
                                        }
                                    }
                                }
                            }
                        }


                        // test for mapped property classnames v1
                        if (ufNameArr) {
                            for (var a = 0; a < ufNameArr.length; a++) {
                                ufName = ufNameArr[a];
                                // get mapped property v1 microformat
                                map = context.getMapping(ufName);
                                if (map) {
                                    for (key in map.properties) {
                                        if (map.properties.hasOwnProperty(key)) {

                                            prop = map.properties[key];
                                            propName = (prop.map) ? prop.map : "p-" + key;

                                            if (key === item) {
                                                if (prop.uf) {
                                                    // loop all the classList make sure
                                                    //   1. this property is a root
                                                    //   2. that there is not already an equivalent v2 property i.e. url and u-url on the same element
                                                    y = 0;
                                                    while (y < i) {
                                                        v2Name = context.getV2RootName(items[y]);
                                                        // add new root
                                                        if (prop.uf.indexOf(v2Name) > -1 && out.root.indexOf(v2Name) === -1) {
                                                            out.root.push(v2Name);
                                                            out.typeVersion = "v1";
                                                        }
                                                        y++;
                                                    }
                                                    // only add property once
                                                    if (out.properties.indexOf(propName) === -1) {
                                                        out.properties.push([propName, "v1"]);
                                                    }
                                                } else if (out.properties.indexOf(propName) === -1) {
                                                    out.properties.push([propName, "v1"]);
                                                }
                                            }
                                        }

                                    }
                                }
                            }

                        }

                        x++;

                    }
                }
            }


            // finds any alt rel=* mappings for a given node/microformat
            if (ufNameArr && this.findRelImpied) {
                for (var b = 0; b < ufNameArr.length; b++) {
                    ufName = ufNameArr[b];
                    impiedRel = this.findRelImpied(node, ufName);
                    if (impiedRel && out.properties.indexOf(impiedRel) === -1) {
                        out.properties.push([impiedRel, "v1"]);
                    }
                }
            }


            // if(out.root.length === 1 && out.properties.length === 1) {
            //  if(out.root[0].replace('h-','') === this.removePropPrefix(out.properties[0][0])) {
            //      out.typeVersion = 'v2';
            //  }
            // }

            return out;
        },


        /**
         * given a v1 or v2 root name, return mapping object
         *
         * @param  {String} name
         * @return {Object || null}
         */
        getMapping(name) {
            var key;
            for (key in modules.maps) {
                if (modules.maps[key].root === name || key === name) {
                    return modules.maps[key];
                }
            }
            return null;
        },


        /**
         * given a v1 root name returns a v2 root name i.e. vcard >>> h-card
         *
         * @param  {String} name
         * @return {String || null}
         */
        getV2RootName(name) {
            var key;
            for (key in modules.maps) {
                if (modules.maps[key].root === name) {
                    return key;
                }
            }
            return null;
        },


        /**
         * whether a property is the right microformats version for its root type
         *
         * @param  {String} typeVersion
         * @param  {String} propertyVersion
         * @return {Boolean}
         */
        isAllowedPropertyVersion(typeVersion, propertyVersion) {
            if (this.options.overlappingVersions === true) {
                return true;
            }
            return (typeVersion === propertyVersion);
        },


        /**
         * creates a blank microformats object
         *
         * @param  {String} name
         * @param  {String} value
         * @return {Object}
         */
        createUfObject(names, typeVersion, value) {
            var out = {};

            // is more than just whitespace
            if (value && modules.utils.isOnlyWhiteSpace(value) === false) {
                out.value = value;
            }
            // add type i.e. ["h-card", "h-org"]
            if (modules.utils.isArray(names)) {
                out.type = names;
            } else {
                out.type = [names];
            }
            out.properties = {};
            // metadata properties for parsing
            out.typeVersion = typeVersion;
            out.times = [];
            out.dates = [];
            out.altValue = null;

            return out;
        },


        /**
         * removes unwanted microformats property before output
         *
         * @param  {Object} microformat
         */
        cleanUfObject( microformat ) {
            delete microformat.times;
            delete microformat.dates;
            delete microformat.typeVersion;
            delete microformat.altValue;
            return microformat;
        },



        /**
         * removes microformat property prefixes from text
         *
         * @param  {String} text
         * @return {String}
         */
        removePropPrefix(text) {
            var i;

            i = this.propertyPrefixes.length;
            while (i--) {
                var prefix = this.propertyPrefixes[i];
                if (modules.utils.startWith(text, prefix)) {
                    text = text.substr(prefix.length);
                }
            }
            return text;
        },


        /**
         * expands all relative URLs to absolute ones where it can
         *
         * @param  {DOM Node} node
         * @param  {String} attrName
         * @param  {String} baseUrl
         */
        expandURLs(node, attrName, baseUrl) {
            var i,
                nodes,
                attr;

            nodes = modules.domUtils.getNodesByAttribute(node, attrName);
            i = nodes.length;
            while (i--) {
                try {
                    // the url parser can blow up if the format is not right
                    attr = modules.domUtils.getAttribute(nodes[i], attrName);
                    if (attr && attr !== "" && baseUrl !== "" && attr.indexOf("://") === -1) {
                        // attr = urlParser.resolve(baseUrl, attr);
                        attr = modules.url.resolve(attr, baseUrl);
                        modules.domUtils.setAttribute(nodes[i], attrName, attr);
                    }
                } catch (err) {
                    // do nothing - convert only the urls we can, leave the rest as they are
                }
            }
        },



        /**
         * merges passed and default options -single level clone of properties
         *
         * @param  {Object} options
         */
        mergeOptions(options) {
            var key;
            for (key in options) {
                if (options.hasOwnProperty(key)) {
                    this.options[key] = options[key];
                }
            }
        },


        /**
         * removes all rootid attributes
         *
         * @param  {DOM Node} rootNode
         */
        removeRootIds(rootNode) {
            var arr,
                i;

            arr = modules.domUtils.getNodesByAttribute(rootNode, "rootids");
            i = arr.length;
            while (i--) {
                modules.domUtils.removeAttribute(arr[i], "rootids");
            }
        },


        /**
         * removes all changes made to the DOM
         *
         * @param  {DOM Node} rootNode
         */
        clearUpDom(rootNode) {
            if (this.removeIncludes) {
                this.removeIncludes(rootNode);
            }
            this.removeRootIds(rootNode);
        }


    };


    modules.Parser.prototype.constructor = modules.Parser;


    // check parser module is loaded
    if (modules.Parser) {

        /**
         * applies "implied rules" microformat output structure i.e. feed-title, name, photo, url and date
         *
         * @param  {DOM Node} node
         * @param  {Object} uf (microformat output structure)
         * @param  {Object} parentClasses (classes structure)
         * @param  {Boolean} impliedPropertiesByVersion
         * @return {Object}
         */
         modules.Parser.prototype.impliedRules = function(node, uf, parentClasses) {
            var typeVersion = (uf.typeVersion) ? uf.typeVersion : "v2";

            // TEMP: override to allow v1 implied properties while spec changes
            if (this.options.impliedPropertiesByVersion === false) {
                typeVersion = "v2";
            }

            if (node && uf && uf.properties) {
                uf = this.impliedBackwardComp( node, uf, parentClasses );
                if (typeVersion === "v2") {
                    uf = this.impliedhFeedTitle( uf );
                    uf = this.impliedName( node, uf );
                    uf = this.impliedPhoto( node, uf );
                    uf = this.impliedUrl( node, uf );
                }
                uf = this.impliedValue( node, uf, parentClasses );
                uf = this.impliedDate( uf );

                // TEMP: flagged while spec changes are put forward
                if (this.options.parseLatLonGeo === true) {
                    uf = this.impliedGeo( uf );
                }
            }

            return uf;
        };


        /**
         * apply implied name rule
         *
         * @param  {DOM Node} node
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedName = function(node, uf) {
            // implied name rule
            /*
                img.h-x[alt]                                        <img class="h-card" src="glenn.htm" alt="Glenn Jones"></a>
                area.h-x[alt]                                       <area class="h-card" href="glenn.htm" alt="Glenn Jones"></area>
                abbr.h-x[title]                                     <abbr class="h-card" title="Glenn Jones"GJ</abbr>

                .h-x>img:only-child[alt]:not[.h-*]                  <div class="h-card"><a src="glenn.htm" alt="Glenn Jones"></a></div>
                .h-x>area:only-child[alt]:not[.h-*]                 <div class="h-card"><area href="glenn.htm" alt="Glenn Jones"></area></div>
                .h-x>abbr:only-child[title]                         <div class="h-card"><abbr title="Glenn Jones">GJ</abbr></div>

                .h-x>:only-child>img:only-child[alt]:not[.h-*]      <div class="h-card"><span><img src="jane.html" alt="Jane Doe"/></span></div>
                .h-x>:only-child>area:only-child[alt]:not[.h-*]     <div class="h-card"><span><area href="jane.html" alt="Jane Doe"></area></span></div>
                .h-x>:only-child>abbr:only-child[title]             <div class="h-card"><span><abbr title="Jane Doe">JD</abbr></span></div>
            */
            var name,
                value;

            if (!uf.properties.name) {
                value = this.getImpliedProperty(node, ["img", "area", "abbr"], this.getNameAttr);
                var textFormat = this.options.textFormat;
                // if no value for tags/properties use text
                if (!value) {
                    name = [modules.text.parse(this.document, node, textFormat)];
                } else {
                    name = [modules.text.parseText(this.document, value, textFormat)];
                }
                if (name && name[0] !== "") {
                    uf.properties.name = name;
                }
            }

            return uf;
        };


        /**
         * apply implied photo rule
         *
         * @param  {DOM Node} node
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedPhoto = function(node, uf) {
            // implied photo rule
            /*
                img.h-x[src]                                                <img class="h-card" alt="Jane Doe" src="jane.jpeg"/>
                object.h-x[data]                                            <object class="h-card" data="jane.jpeg"/>Jane Doe</object>
                .h-x>img[src]:only-of-type:not[.h-*]                        <div class="h-card"><img alt="Jane Doe" src="jane.jpeg"/></div>
                .h-x>object[data]:only-of-type:not[.h-*]                    <div class="h-card"><object data="jane.jpeg"/>Jane Doe</object></div>
                .h-x>:only-child>img[src]:only-of-type:not[.h-*]            <div class="h-card"><span><img alt="Jane Doe" src="jane.jpeg"/></span></div>
                .h-x>:only-child>object[data]:only-of-type:not[.h-*]        <div class="h-card"><span><object data="jane.jpeg"/>Jane Doe</object></span></div>
            */
            var value;
            if (!uf.properties.photo) {
                value = this.getImpliedProperty(node, ["img", "object"], this.getPhotoAttr);
                if (value) {
                    // relative to absolute URL
                    if (value && value !== "" && this.options.baseUrl !== "" && value.indexOf("://") === -1) {
                        value = modules.url.resolve(value, this.options.baseUrl);
                    }
                    uf.properties.photo = [modules.utils.trim(value)];
                }
            }
            return uf;
        };


        /**
         * apply implied URL rule
         *
         * @param  {DOM Node} node
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedUrl = function(node, uf) {
            // implied URL rule
            /*
                a.h-x[href]                             <a class="h-card" href="glenn.html">Glenn</a>
                area.h-x[href]                          <area class="h-card" href="glenn.html">Glenn</area>
                .h-x>a[href]:only-of-type:not[.h-*]     <div class="h-card" ><a href="glenn.html">Glenn</a><p>...</p></div>
                .h-x>area[href]:only-of-type:not[.h-*]  <div class="h-card" ><area href="glenn.html">Glenn</area><p>...</p></div>
            */
            var value;
            if (!uf.properties.url) {
                value = this.getImpliedProperty(node, ["a", "area"], this.getURLAttr);
                if (value) {
                    // relative to absolute URL
                    if (value && value !== "" && this.options.baseUrl !== "" && value.indexOf("://") === -1) {
                        value = modules.url.resolve(value, this.options.baseUrl);
                    }
                    uf.properties.url = [modules.utils.trim(value)];
                }
            }
            return uf;
        };


        /**
         * apply implied date rule - if there is a time only property try to concat it with any date property
         *
         * @param  {DOM Node} node
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedDate = function(uf) {
            // implied date rule
            // http://microformats.org/wiki/value-class-pattern#microformats2_parsers
            // http://microformats.org/wiki/microformats2-parsing-issues#implied_date_for_dt_properties_both_mf2_and_backcompat
            var newDate;
            if (uf.times.length > 0 && uf.dates.length > 0) {
                newDate = modules.dates.dateTimeUnion(uf.dates[0][1], uf.times[0][1], this.options.dateFormat);
                uf.properties[this.removePropPrefix(uf.times[0][0])][0] = newDate.toString(this.options.dateFormat);
            }
            // clean-up object
            delete uf.times;
            delete uf.dates;
            return uf;
        };


        /**
         * get an implied property value from pre-defined tag/attriubte combinations
         *
         * @param  {DOM Node} node
         * @param  {String} tagList (Array of tags from which an implied value can be pulled)
         * @param  {String} getAttrFunction (Function which can extract implied value)
         * @return {String || null}
         */
        modules.Parser.prototype.getImpliedProperty = function(node, tagList, getAttrFunction) {
            // i.e. img.h-card
            var value = getAttrFunction(node),
                descendant,
                child;

            if (!value) {
                // i.e. .h-card>img:only-of-type:not(.h-card)
                descendant = modules.domUtils.getSingleDescendantOfType( node, tagList);
                if (descendant && this.hasHClass(descendant) === false) {
                    value = getAttrFunction(descendant);
                }
                if (node.children.length > 0 ) {
                    // i.e.  .h-card>:only-child>img:only-of-type:not(.h-card)
                    child = modules.domUtils.getSingleDescendant(node);
                    if (child && this.hasHClass(child) === false) {
                        descendant = modules.domUtils.getSingleDescendantOfType(child, tagList);
                        if (descendant && this.hasHClass(descendant) === false) {
                            value = getAttrFunction(descendant);
                        }
                    }
                }
            }

            return value;
        };


        /**
         * get an implied name value from a node
         *
         * @param  {DOM Node} node
         * @return {String || null}
         */
        modules.Parser.prototype.getNameAttr = function(node) {
            var value = modules.domUtils.getAttrValFromTagList(node, ["img", "area"], "alt");
            if (!value) {
                value = modules.domUtils.getAttrValFromTagList(node, ["abbr"], "title");
            }
            return value;
        };


        /**
         * get an implied photo value from a node
         *
         * @param  {DOM Node} node
         * @return {String || null}
         */
        modules.Parser.prototype.getPhotoAttr = function(node) {
            var value = modules.domUtils.getAttrValFromTagList(node, ["img"], "src");
            if (!value && modules.domUtils.hasAttributeValue(node, "class", "include") === false) {
                value = modules.domUtils.getAttrValFromTagList(node, ["object"], "data");
            }
            return value;
        };


        /**
         * get an implied photo value from a node
         *
         * @param  {DOM Node} node
         * @return {String || null}
         */
        modules.Parser.prototype.getURLAttr = function(node) {
            var value = null;
            if (modules.domUtils.hasAttributeValue(node, "class", "include") === false) {

                value = modules.domUtils.getAttrValFromTagList(node, ["a"], "href");
                if (!value) {
                    value = modules.domUtils.getAttrValFromTagList(node, ["area"], "href");
                }

            }
            return value;
        };


        /**
         *
         *
         * @param  {DOM Node} node
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedValue = function(node, uf, parentClasses) {

            // intersection of implied name and implied value rules
            if (uf.properties.name) {
                if (uf.value && parentClasses.root.length > 0 && parentClasses.properties.length === 1) {
                    uf = this.getAltValue(uf, parentClasses.properties[0][0], "p-name", uf.properties.name[0]);
                }
            }

            // intersection of implied URL and implied value rules
            if (uf.properties.url) {
                if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
                    uf = this.getAltValue(uf, parentClasses.properties[0][0], "u-url", uf.properties.url[0]);
                }
            }

            // apply alt value
            if (uf.altValue !== null) {
                uf.value = uf.altValue.value;
            }
            delete uf.altValue;


            return uf;
        };


        /**
         * get alt value based on rules about parent property prefix
         *
         * @param  {Object} uf
         * @param  {String} parentPropertyName
         * @param  {String} propertyName
         * @param  {String} value
         * @return {Object}
         */
        modules.Parser.prototype.getAltValue = function(uf, parentPropertyName, propertyName, value) {
            if (uf.value && !uf.altValue) {
                // first p-name of the h-* child
                if (modules.utils.startWith(parentPropertyName, "p-") && propertyName === "p-name") {
                    uf.altValue = {name: propertyName, value};
                }
                // if it's an e-* property element
                if (modules.utils.startWith(parentPropertyName, "e-") && modules.utils.startWith(propertyName, "e-")) {
                    uf.altValue = {name: propertyName, value};
                }
                // if it's an u-* property element
                if (modules.utils.startWith(parentPropertyName, "u-") && propertyName === "u-url") {
                    uf.altValue = {name: propertyName, value};
                }
            }
            return uf;
        };


        /**
         * if a h-feed does not have a title use the title tag of a page
         *
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedhFeedTitle = function( uf ) {
            if (uf.type && uf.type.indexOf("h-feed") > -1) {
                // has no name property
                if (uf.properties.name === undefined || uf.properties.name[0] === "" ) {
                    // use the text from the title tag
                    var title = modules.domUtils.querySelector(this.document, "title");
                    if (title) {
                        uf.properties.name = [modules.domUtils.textContent(title)];
                    }
                }
            }
            return uf;
        };



        /**
         * implied Geo from pattern <abbr class="p-geo" title="37.386013;-122.082932">
         *
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedGeo = function( uf ) {
            var geoPair,
                parts,
                longitude,
                latitude,
                valid = true;

            if (uf.type && uf.type.indexOf("h-geo") > -1) {

                // has no latitude or longitude property
                if (uf.properties.latitude === undefined || uf.properties.longitude === undefined ) {

                    geoPair = (uf.properties.name) ? uf.properties.name[0] : null;
                    geoPair = (!geoPair && uf.properties.value) ? uf.properties.value : geoPair;

                    if (geoPair) {
                        // allow for the use of a ';' as in microformats and also ',' as in Geo URL
                        geoPair = geoPair.replace(";", ",");

                        // has sep char
                        if (geoPair.indexOf(",") > -1 ) {
                            parts = geoPair.split(",");

                            // only correct if we have two or more parts
                            if (parts.length > 1) {

                                // latitude no value outside the range -90 or 90
                                latitude = parseFloat( parts[0] );
                                if (modules.utils.isNumber(latitude) && latitude > 90 || latitude < -90) {
                                    valid = false;
                                }

                                // longitude no value outside the range -180 to 180
                                longitude = parseFloat( parts[1] );
                                if (modules.utils.isNumber(longitude) && longitude > 180 || longitude < -180) {
                                    valid = false;
                                }

                                if (valid) {
                                    uf.properties.latitude = [latitude];
                                    uf.properties.longitude  = [longitude];
                                }
                            }

                        }
                    }
                }
            }
            return uf;
        };


        /**
         * if a backwards compat built structure has no properties add name through this.impliedName
         *
         * @param  {Object} uf
         * @return {Object}
         */
        modules.Parser.prototype.impliedBackwardComp = function(node, uf, parentClasses) {

            // look for pattern in parent classes like "p-geo h-geo"
            // these are structures built from backwards compat parsing of geo
            if (parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
                if (parentClasses.root[0].replace("h-", "") === this.removePropPrefix(parentClasses.properties[0][0])) {

                    // if microformat has no properties apply the impliedName rule to get value from containing node
                    // this will get value from html such as <abbr class="geo" title="30.267991;-97.739568">Brighton</abbr>
                    if ( modules.utils.hasProperties(uf.properties) === false ) {
                        uf = this.impliedName( node, uf );
                    }
                }
            }

            return uf;
        };



    }


    // check parser module is loaded
    if (modules.Parser) {


        /**
         * appends clones of include Nodes into the DOM structure
         *
         * @param  {DOM node} rootNode
         */
        modules.Parser.prototype.addIncludes = function(rootNode) {
            this.addAttributeIncludes(rootNode, "itemref");
            this.addAttributeIncludes(rootNode, "headers");
            this.addClassIncludes(rootNode);
        };


        /**
         * appends clones of include Nodes into the DOM structure for attribute based includes
         *
         * @param  {DOM node} rootNode
         * @param  {String} attributeName
         */
        modules.Parser.prototype.addAttributeIncludes = function(rootNode, attributeName) {
            var arr,
                idList,
                i,
                x,
                z,
                y;

            arr = modules.domUtils.getNodesByAttribute(rootNode, attributeName);
            x = 0;
            i = arr.length;
            while (x < i) {
                idList = modules.domUtils.getAttributeList(arr[x], attributeName);
                if (idList) {
                    z = 0;
                    y = idList.length;
                    while (z < y) {
                        this.apppendInclude(arr[x], idList[z]);
                        z++;
                    }
                }
                x++;
            }
        };


        /**
         * appends clones of include Nodes into the DOM structure for class based includes
         *
         * @param  {DOM node} rootNode
         */
        modules.Parser.prototype.addClassIncludes = function(rootNode) {
            var id,
                arr,
                x = 0,
                i;

            arr = modules.domUtils.getNodesByAttributeValue(rootNode, "class", "include");
            i = arr.length;
            while (x < i) {
                id = modules.domUtils.getAttrValFromTagList(arr[x], ["a"], "href");
                if (!id) {
                    id = modules.domUtils.getAttrValFromTagList(arr[x], ["object"], "data");
                }
                this.apppendInclude(arr[x], id);
                x++;
            }
        };


        /**
         * appends a clone of an include into another Node using Id
         *
         * @param  {DOM node} rootNode
         * @param  {Stringe} id
         */
        modules.Parser.prototype.apppendInclude = function(node, id) {
            var include,
                clone;

            id = modules.utils.trim(id.replace("#", ""));
            include = modules.domUtils.getElementById(this.document, id);
            if (include) {
                clone = modules.domUtils.clone(include);
                this.markIncludeChildren(clone);
                modules.domUtils.appendChild(node, clone);
            }
        };


        /**
         * adds an attribute marker to all the child microformat roots
         *
         * @param  {DOM node} rootNode
         */
        modules.Parser.prototype.markIncludeChildren = function(rootNode) {
            var arr,
                x,
                i;

            // loop the array and add the attribute
            arr = this.findRootNodes(rootNode);
            x = 0;
            i = arr.length;
            modules.domUtils.setAttribute(rootNode, "data-include", "true");
            modules.domUtils.setAttribute(rootNode, "style", "display:none");
            while (x < i) {
                modules.domUtils.setAttribute(arr[x], "data-include", "true");
                x++;
            }
        };


        /**
         * removes all appended include clones from DOM
         *
         * @param  {DOM node} rootNode
         */
        modules.Parser.prototype.removeIncludes = function(rootNode) {
            var arr,
                i;

            // remove all the items that were added as includes
            arr = modules.domUtils.getNodesByAttribute(rootNode, "data-include");
            i = arr.length;
            while (i--) {
                modules.domUtils.removeChild(rootNode, arr[i]);
            }
        };


    }


    // check parser module is loaded
    if (modules.Parser) {

        /**
         * finds rel=* structures
         *
         * @param  {DOM node} rootNode
         * @return {Object}
         */
        modules.Parser.prototype.findRels = function(rootNode) {
            var out = {
                    "items": [],
                    "rels": {},
                    "rel-urls": {}
                },
                x,
                i,
                y,
                z,
                relList,
                items,
                item,
                value,
                arr;

            arr = modules.domUtils.getNodesByAttribute(rootNode, "rel");
            x = 0;
            i = arr.length;
            while (x < i) {
                relList = modules.domUtils.getAttribute(arr[x], "rel");

                if (relList) {
                    items = relList.split(" ");


                    // add rels
                    z = 0;
                    y = items.length;
                    while (z < y) {
                        item = modules.utils.trim(items[z]);

                        // get rel value
                        value = modules.domUtils.getAttrValFromTagList(arr[x], ["a", "area"], "href");
                        if (!value) {
                            value = modules.domUtils.getAttrValFromTagList(arr[x], ["link"], "href");
                        }

                        // create the key
                        if (!out.rels[item]) {
                            out.rels[item] = [];
                        }

                        if (typeof this.options.baseUrl === "string" && typeof value === "string") {

                            var resolved = modules.url.resolve(value, this.options.baseUrl);
                            // do not add duplicate rels - based on resolved URLs
                            if (out.rels[item].indexOf(resolved) === -1) {
                                out.rels[item].push( resolved );
                            }
                        }
                        z++;
                    }


                    var url = null;
                    if (modules.domUtils.hasAttribute(arr[x], "href")) {
                        url = modules.domUtils.getAttribute(arr[x], "href");
                        if (url) {
                            url = modules.url.resolve(url, this.options.baseUrl );
                        }
                    }


                    // add to rel-urls
                    var relUrl = this.getRelProperties(arr[x]);
                    relUrl.rels = items;
                    // do not add duplicate rel-urls - based on resolved URLs
                    if (url && out["rel-urls"][url] === undefined) {
                        out["rel-urls"][url] = relUrl;
                    }


                }
                x++;
            }
            return out;
        };


        /**
         * gets the properties of a rel=*
         *
         * @param  {DOM node} node
         * @return {Object}
         */
        modules.Parser.prototype.getRelProperties = function(node) {
            var obj = {};

            if (modules.domUtils.hasAttribute(node, "media")) {
                obj.media = modules.domUtils.getAttribute(node, "media");
            }
            if (modules.domUtils.hasAttribute(node, "type")) {
                obj.type = modules.domUtils.getAttribute(node, "type");
            }
            if (modules.domUtils.hasAttribute(node, "hreflang")) {
                obj.hreflang = modules.domUtils.getAttribute(node, "hreflang");
            }
            if (modules.domUtils.hasAttribute(node, "title")) {
                obj.title = modules.domUtils.getAttribute(node, "title");
            }
            if (modules.utils.trim(this.getPValue(node, false)) !== "") {
                obj.text = this.getPValue(node, false);
            }

            return obj;
        };


        /**
         * finds any alt rel=* mappings for a given node/microformat
         *
         * @param  {DOM node} node
         * @param  {String} ufName
         * @return {String || undefined}
         */
        modules.Parser.prototype.findRelImpied = function(node, ufName) {
            var out,
                map,
                i;

            map = this.getMapping(ufName);
            if (map) {
                for (var key in map.properties) {
                    if (map.properties.hasOwnProperty(key)) {
                        var prop = map.properties[key],
                            propName = (prop.map) ? prop.map : "p-" + key,
                            relCount = 0;

                        // is property an alt rel=* mapping
                        if (prop.relAlt && modules.domUtils.hasAttribute(node, "rel")) {
                            i = prop.relAlt.length;
                            while (i--) {
                                if (modules.domUtils.hasAttributeValue(node, "rel", prop.relAlt[i])) {
                                    relCount++;
                                }
                            }
                            if (relCount === prop.relAlt.length) {
                                out = propName;
                            }
                        }
                    }
                }
            }
            return out;
        };


        /**
         * returns whether a node or its children has rel=* microformat
         *
         * @param  {DOM node} node
         * @return {Boolean}
         */
        modules.Parser.prototype.hasRel = function(node) {
            return (this.countRels(node) > 0);
        };


        /**
         * returns the number of rel=* microformats
         *
         * @param  {DOM node} node
         * @return {Int}
         */
        modules.Parser.prototype.countRels = function(node) {
            if (node) {
                return modules.domUtils.getNodesByAttribute(node, "rel").length;
            }
            return 0;
        };



    }


    modules.utils = {

        /**
         * is the object a string
         *
         * @param  {Object} obj
         * @return {Boolean}
         */
        isString( obj ) {
            return typeof( obj ) === "string";
        },

        /**
         * is the object a number
         *
         * @param  {Object} obj
         * @return {Boolean}
         */
        isNumber( obj ) {
            return !isNaN(parseFloat( obj )) && isFinite( obj );
        },


        /**
         * is the object an array
         *
         * @param  {Object} obj
         * @return {Boolean}
         */
        isArray( obj ) {
            return obj && !( obj.propertyIsEnumerable( "length" ) ) && typeof obj === "object" && typeof obj.length === "number";
        },


        /**
         * is the object a function
         *
         * @param  {Object} obj
         * @return {Boolean}
         */
        isFunction(obj) {
            return !!(obj && obj.constructor && obj.call && obj.apply);
        },


        /**
         * does the text start with a test string
         *
         * @param  {String} text
         * @param  {String} test
         * @return {Boolean}
         */
        startWith( text, test ) {
            return (text.indexOf(test) === 0);
        },


        /**
         * removes spaces at front and back of text
         *
         * @param  {String} text
         * @return {String}
         */
        trim( text ) {
            if (text && this.isString(text)) {
                return (text.trim()) ? text.trim() : text.replace(/^\s+|\s+$/g, "");
            }
            return "";
        },


        /**
         * replaces a character in text
         *
         * @param  {String} text
         * @param  {Int} index
         * @param  {String} character
         * @return {String}
         */
        replaceCharAt( text, index, character ) {
            if (text && text.length > index) {
               return text.substr(0, index) + character + text.substr(index + character.length);
            }
            return text;
        },


        /**
         * removes whitespace, tabs and returns from start and end of text
         *
         * @param  {String} text
         * @return {String}
         */
        trimWhitespace( text ) {
            if (text && text.length) {
                var i = text.length,
                    x = 0;

                // turn all whitespace chars at end into spaces
                while (i--) {
                    if (this.isOnlyWhiteSpace(text[i])) {
                        text = this.replaceCharAt( text, i, " " );
                    } else {
                        break;
                    }
                }

                // turn all whitespace chars at start into spaces
                i = text.length;
                while (x < i) {
                    if (this.isOnlyWhiteSpace(text[x])) {
                        text = this.replaceCharAt( text, i, " " );
                    } else {
                        break;
                    }
                    x++;
                }
            }
            return this.trim(text);
        },


        /**
         * does text only contain whitespace characters
         *
         * @param  {String} text
         * @return {Boolean}
         */
        isOnlyWhiteSpace( text ) {
            return !(/[^\t\n\r ]/.test( text ));
        },


        /**
         * removes whitespace from text (leaves a single space)
         *
         * @param  {String} text
         * @return {Sring}
         */
        collapseWhiteSpace( text ) {
            return text.replace(/[\t\n\r ]+/g, " ");
        },


        /**
         * does an object have any of its own properties
         *
         * @param  {Object} obj
         * @return {Boolean}
         */
        hasProperties( obj ) {
            var key;
            for (key in obj) {
                if ( obj.hasOwnProperty( key ) ) {
                    return true;
                }
            }
            return false;
        },


        /**
         * a sort function - to sort objects in an array by a given property
         *
         * @param  {String} property
         * @param  {Boolean} reverse
         * @return {Int}
         */
        sortObjects(property, reverse) {
            reverse = (reverse) ? -1 : 1;
            return function(a, b) {
                a = a[property];
                b = b[property];
                if (a < b) {
                    return reverse * -1;
                }
                if (a > b) {
                    return reverse * 1;
                }
                return 0;
            };
        }

    };


    modules.domUtils = {

        // blank objects for DOM
        document: null,
        rootNode: null,


         /**
         * gets DOMParser object
         *
         * @return {Object || undefined}
         */
        getDOMParser() {
            if (typeof DOMParser === "undefined") {
                try {
                    return Components.classes["@mozilla.org/xmlextras/domparser;1"]
                        .createInstance(Components.interfaces.nsIDOMParser);
                } catch (e) {
                    return undefined;
                }
            } else {
                return new DOMParser();
            }
        },


         /**
         * configures what are the base DOM objects for parsing
         *
         * @param  {Object} options
         * @return {DOM Node} node
         */
        getDOMContext( options ) {

            // if a node is passed
            if (options.node) {
                this.rootNode = options.node;
            }


            // if a html string is passed
            if (options.html) {
                // var domParser = new DOMParser();
                var domParser = this.getDOMParser();
                this.rootNode = domParser.parseFromString( options.html, "text/html" );
            }


            // find top level document from rootnode
            if (this.rootNode !== null) {
                if (this.rootNode.nodeType === 9) {
                    this.document = this.rootNode;
                    this.rootNode = modules.domUtils.querySelector(this.rootNode, "html");
                } else {
                    // if it's DOM node get parent DOM Document
                    this.document = modules.domUtils.ownerDocument(this.rootNode);
                }
            }


            // use global document object
            if (!this.rootNode && document) {
                this.rootNode = modules.domUtils.querySelector(document, "html");
                this.document = document;
            }


            if (this.rootNode && this.document) {
                return {document: this.document, rootNode: this.rootNode};
            }

            return {document: null, rootNode: null};
        },



        /**
        * gets the first DOM node
        *
        * @param  {Dom Document}
        * @return {DOM Node} node
        */
        getTopMostNode( node ) {
            // var doc = this.ownerDocument(node);
            // if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){
            //  return doc.documentElement;
            // }
            return node;
        },



         /**
         * abstracts DOM ownerDocument
         *
         * @param  {DOM Node} node
         * @return {Dom Document}
         */
        ownerDocument(node) {
            return node.ownerDocument;
        },


        /**
         * abstracts DOM textContent
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        textContent(node) {
            if (node.textContent) {
                return node.textContent;
            } else if (node.innerText) {
                return node.innerText;
            }
            return "";
        },


        /**
         * abstracts DOM innerHTML
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        innerHTML(node) {
            return node.innerHTML;
        },


        /**
         * abstracts DOM hasAttribute
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @return {Boolean}
         */
        hasAttribute(node, attributeName) {
            return node.hasAttribute(attributeName);
        },


        /**
         * does an attribute contain a value
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @param  {String} value
         * @return {Boolean}
         */
        hasAttributeValue(node, attributeName, value) {
            return (this.getAttributeList(node, attributeName).indexOf(value) > -1);
        },


        /**
         * abstracts DOM getAttribute
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @return {String || null}
         */
        getAttribute(node, attributeName) {
            return node.getAttribute(attributeName);
        },


        /**
         * abstracts DOM setAttribute
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @param  {String} attributeValue
         */
        setAttribute(node, attributeName, attributeValue) {
            node.setAttribute(attributeName, attributeValue);
        },


        /**
         * abstracts DOM removeAttribute
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         */
        removeAttribute(node, attributeName) {
            node.removeAttribute(attributeName);
        },


        /**
         * abstracts DOM getElementById
         *
         * @param  {DOM Node || DOM Document} node
         * @param  {String} id
         * @return {DOM Node}
         */
        getElementById(docNode, id) {
            return docNode.querySelector( "#" + id );
        },


        /**
         * abstracts DOM querySelector
         *
         * @param  {DOM Node || DOM Document} node
         * @param  {String} selector
         * @return {DOM Node}
         */
        querySelector(docNode, selector) {
            return docNode.querySelector( selector );
        },


        /**
         * get value of a Node attribute as an array
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @return {Array}
         */
        getAttributeList(node, attributeName) {
            var out = [],
                attList;

            attList = node.getAttribute(attributeName);
            if (attList && attList !== "") {
                if (attList.indexOf(" ") > -1) {
                    out = attList.split(" ");
                } else {
                    out.push(attList);
                }
            }
            return out;
        },


        /**
         * gets all child nodes with a given attribute
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @return {NodeList}
         */
        getNodesByAttribute(node, attributeName) {
            var selector = "[" + attributeName + "]";
            return node.querySelectorAll(selector);
        },


        /**
         * gets all child nodes with a given attribute containing a given value
         *
         * @param  {DOM Node} node
         * @param  {String} attributeName
         * @return {DOM NodeList}
         */
        getNodesByAttributeValue(rootNode, name, value) {
            var arr = [],
                x = 0,
                i,
                out = [];

            arr = this.getNodesByAttribute(rootNode, name);
            if (arr) {
                i = arr.length;
                while (x < i) {
                    if (this.hasAttributeValue(arr[x], name, value)) {
                        out.push(arr[x]);
                    }
                    x++;
                }
            }
            return out;
        },


        /**
         * gets attribute value from controlled list of tags
         *
         * @param  {Array} tagNames
         * @param  {String} attributeName
         * @return {String || null}
         */
        getAttrValFromTagList(node, tagNames, attributeName) {
            var i = tagNames.length;

            while (i--) {
                if (node.tagName.toLowerCase() === tagNames[i]) {
                    var attrValue = this.getAttribute(node, attributeName);
                    if (attrValue && attrValue !== "") {
                        return attrValue;
                    }
                }
            }
            return null;
        },


       /**
         * get node if it has no siblings. CSS equivalent is :only-child
         *
         * @param  {DOM Node} rootNode
         * @param  {Array} tagNames
         * @return {DOM Node || null}
         */
        getSingleDescendant(node) {
            return this.getDescendant( node, null, false );
        },


        /**
         * get node if it has no siblings of the same type. CSS equivalent is :only-of-type
         *
         * @param  {DOM Node} rootNode
         * @param  {Array} tagNames
         * @return {DOM Node || null}
         */
        getSingleDescendantOfType(node, tagNames) {
            return this.getDescendant( node, tagNames, true );
        },


        /**
         * get child node limited by presence of siblings - either CSS :only-of-type or :only-child
         *
         * @param  {DOM Node} rootNode
         * @param  {Array} tagNames
         * @return {DOM Node || null}
         */
        getDescendant( node, tagNames, onlyOfType ) {
            var i = node.children.length,
                countAll = 0,
                countOfType = 0,
                child,
                out = null;

            while (i--) {
                child = node.children[i];
                if (child.nodeType === 1) {
                    if (tagNames) {
                        // count just only-of-type
                        if (this.hasTagName(child, tagNames)) {
                            out = child;
                            countOfType++;
                        }
                    } else {
                        // count all elements
                        out = child;
                        countAll++;
                    }
                }
            }
            if (onlyOfType === true) {
                return (countOfType === 1) ? out : null;
            }
            return (countAll === 1) ? out : null;
        },


       /**
         * is a node one of a list of tags
         *
         * @param  {DOM Node} rootNode
         * @param  {Array} tagNames
         * @return {Boolean}
         */
        hasTagName(node, tagNames) {
            var i = tagNames.length;
            while (i--) {
                if (node.tagName.toLowerCase() === tagNames[i]) {
                    return true;
                }
            }
            return false;
        },


       /**
         * abstracts DOM appendChild
         *
         * @param  {DOM Node} node
         * @param  {DOM Node} childNode
         * @return {DOM Node}
         */
        appendChild(node, childNode) {
            return node.appendChild(childNode);
        },


       /**
         * abstracts DOM removeChild
         *
         * @param  {DOM Node} childNode
         * @return {DOM Node || null}
         */
        removeChild(childNode) {
            if (childNode.parentNode) {
                return childNode.remove();
            }
            return null;
        },


        /**
         * abstracts DOM cloneNode
         *
         * @param  {DOM Node} node
         * @return {DOM Node}
         */
        clone(node) {
            var newNode = node.cloneNode(true);
            newNode.removeAttribute("id");
            return newNode;
        },


        /**
         * gets the text of a node
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        getElementText( node ) {
            if (node && node.data) {
                return node.data;
            }
            return "";
        },


        /**
         * gets the attributes of a node - ordered by sequence in html
         *
         * @param  {DOM Node} node
         * @return {Array}
         */
        getOrderedAttributes( node ) {
            var nodeStr = node.outerHTML,
                attrs = [];

            for (var i = 0; i < node.attributes.length; i++) {
                var attr = node.attributes[i];
                    attr.indexNum = nodeStr.indexOf(attr.name);

                attrs.push( attr );
            }
            return attrs.sort( modules.utils.sortObjects( "indexNum" ) );
        },


        /**
         * decodes html entities in given text
         *
         * @param  {DOM Document} doc
         * @param  String} text
         * @return {String}
         */
        decodeEntities( doc, text ) {
            // return text;
            return doc.createTextNode( text ).nodeValue;
        },


        /**
         * clones a DOM document
         *
         * @param  {DOM Document} document
         * @return {DOM Document}
         */
        cloneDocument( document ) {
            var newNode,
                newDocument = null;

            if ( this.canCloneDocument( document )) {
                newDocument = document.implementation.createHTMLDocument("");
                newNode = newDocument.importNode( document.documentElement, true );
                newDocument.replaceChild(newNode, newDocument.querySelector("html"));
            }
            return (newNode && newNode.nodeType && newNode.nodeType === 1) ? newDocument : document;
        },


        /**
         * can environment clone a DOM document
         *
         * @param  {DOM Document} document
         * @return {Boolean}
         */
        canCloneDocument( document ) {
            return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument);
        },


        /**
         * get the child index of a node. Used to create a node path
         *
         *   @param  {DOM Node} node
         *   @return {Int}
         */
        getChildIndex(node) {
            var parent = node.parentNode,
                i = -1,
                child;
            while (parent && (child = parent.childNodes[++i])) {
                 if (child === node) {
                     return i;
                 }
            }
            return -1;
        },


        /**
         * get a node's path
         *
         *   @param  {DOM Node} node
         *   @return {Array}
         */
        getNodePath(node) {
            var parent = node.parentNode,
                path = [],
                index = this.getChildIndex(node);

          if (parent && (path = this.getNodePath(parent))) {
               if (index > -1) {
                   path.push(index);
               }
          }
          return path;
        },


        /**
         * get a node from a path.
         *
         *   @param  {DOM document} document
         *   @param  {Array} path
         *   @return {DOM Node}
         */
        getNodeByPath(document, path) {
            var node = document.documentElement,
                i = 0,
                index;
          while ((index = path[++i]) > -1) {
              node = node.childNodes[index];
          }
          return node;
        },


        /**
        * get an array/nodeList of child nodes
        *
        *   @param  {DOM node} node
        *   @return {Array}
        */
        getChildren( node ) {
            return node.children;
        },


        /**
        * create a node
        *
        *   @param  {String} tagName
        *   @return {DOM node}
        */
        createNode( tagName ) {
            return this.document.createElement(tagName);
        },


        /**
        * create a node with text content
        *
        *   @param  {String} tagName
        *   @param  {String} text
        *   @return {DOM node}
        */
        createNodeWithText( tagName, text ) {
            var node = this.document.createElement(tagName);
            node.innerHTML = text;
            return node;
        }



    };


    modules.url = {


        /**
         * creates DOM objects needed to resolve URLs
         */
        init() {
            // this._domParser = new DOMParser();
            this._domParser = modules.domUtils.getDOMParser();
            // do not use a head tag it does not work with IE9
            this._html = '<base id="base" href=""></base><a id="link" href=""></a>';
            this._nodes = this._domParser.parseFromString( this._html, "text/html" );
            this._baseNode =  modules.domUtils.getElementById(this._nodes, "base");
            this._linkNode =  modules.domUtils.getElementById(this._nodes, "link");
        },


        /**
         * resolves url to absolute version using baseUrl
         *
         * @param  {String} url
         * @param  {String} baseUrl
         * @return {String}
         */
        resolve(url, baseUrl) {
            // use modern URL web API where we can
            if (modules.utils.isString(url) && modules.utils.isString(baseUrl) && url.indexOf("://") === -1) {
                // this try catch is required as IE has an URL object but no constuctor support
                // http://glennjones.net/articles/the-problem-with-window-url
                try {
                    var resolved = new URL(url, baseUrl).toString();
                    // deal with early Webkit not throwing an error - for Safari
                    if (resolved === "[object URL]") {
                        resolved = URI.resolve(baseUrl, url);
                    }
                    return resolved;
                } catch (e) {
                    // otherwise fallback to DOM
                    if (this._domParser === undefined) {
                        this.init();
                    }

                    // do not use setAttribute it does not work with IE9
                    this._baseNode.href = baseUrl;
                    this._linkNode.href = url;

                    // dont use getAttribute as it returns orginal value not resolved
                    return this._linkNode.href;
                }
            } else {
                if (modules.utils.isString(url)) {
                    return url;
                }
                return "";
            }
        },

    };


    /**
     * constructor
     * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
     *
     * @param  {String} dateString
     * @param  {String} format
     * @return {String}
     */
    modules.ISODate = function( dateString, format ) {
        this.clear();

        this.format = (format) ? format : "auto"; // auto or W3C or RFC3339 or HTML5
        this.setFormatSep();

        // optional should be full iso date/time string
        if (arguments[0]) {
            this.parse(dateString, format);
        }
    };


    modules.ISODate.prototype = {


        /**
         * clear all states
         *
         */
        clear() {
            this.clearDate();
            this.clearTime();
            this.clearTimeZone();
            this.setAutoProfileState();
        },


        /**
         * clear date states
         *
         */
        clearDate() {
            this.dY = -1;
            this.dM = -1;
            this.dD = -1;
            this.dDDD = -1;
        },


        /**
         * clear time states
         *
         */
        clearTime() {
            this.tH = -1;
            this.tM = -1;
            this.tS = -1;
            this.tD = -1;
        },


        /**
         * clear timezone states
         *
         */
        clearTimeZone() {
            this.tzH = -1;
            this.tzM = -1;
            this.tzPN = "+";
            this.z = false;
        },


        /**
         * resets the auto profile state
         *
         */
        setAutoProfileState() {
            this.autoProfile = {
               sep: "T",
               dsep: "-",
               tsep: ":",
               tzsep: ":",
               tzZulu: "Z"
            };
        },


        /**
         * parses text to find ISO date/time string i.e. 2008-05-01T15:45:19Z
         *
         * @param  {String} dateString
         * @param  {String} format
         * @return {String}
         */
        parse( dateString, format ) {
            this.clear();

            var parts = [],
                tzArray = [],
                position = 0,
                datePart = "",
                timePart = "",
                timeZonePart = "";

            if (format) {
                this.format = format;
            }



            // discover date time separtor for auto profile
            // Set to 'T' by default
            if (dateString.indexOf("t") > -1) {
                this.autoProfile.sep = "t";
            }
            if (dateString.indexOf("z") > -1) {
                this.autoProfile.tzZulu = "z";
            }
            if (dateString.indexOf("Z") > -1) {
                this.autoProfile.tzZulu = "Z";
            }
            if (dateString.toUpperCase().indexOf("T") === -1) {
                this.autoProfile.sep = " ";
            }


            dateString = dateString.toUpperCase().replace(" ", "T");

            // break on 'T' divider or space
            if (dateString.indexOf("T") > -1) {
                parts = dateString.split("T");
                datePart = parts[0];
                timePart = parts[1];

                // zulu UTC
                if (timePart.indexOf( "Z" ) > -1) {
                    this.z = true;
                }

                // timezone
                if (timePart.indexOf( "+" ) > -1 || timePart.indexOf( "-" ) > -1) {
                    tzArray = timePart.split( "Z" ); // incase of incorrect use of Z
                    timePart = tzArray[0];
                    timeZonePart = tzArray[1];

                    // timezone
                    if (timePart.indexOf( "+" ) > -1 || timePart.indexOf( "-" ) > -1) {
                        position = 0;

                        if (timePart.indexOf( "+" ) > -1) {
                            position = timePart.indexOf( "+" );
                        } else {
                            position = timePart.indexOf( "-" );
                        }

                        timeZonePart = timePart.substring( position, timePart.length );
                        timePart = timePart.substring( 0, position );
                    }
                }

            } else {
                datePart = dateString;
            }

            if (datePart !== "") {
                this.parseDate( datePart );
                if (timePart !== "") {
                    this.parseTime( timePart );
                    if (timeZonePart !== "") {
                        this.parseTimeZone( timeZonePart );
                    }
                }
            }
            return this.toString( format );
        },


        /**
         * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
         *
         * @param  {String} dateString
         * @param  {String} format
         * @return {String}
         */
        parseDate( dateString, format ) {
            this.clearDate();

            var parts = [];

            // discover timezone separtor for auto profile // default is ':'
            if (dateString.indexOf("-") === -1) {
                this.autoProfile.tsep = "";
            }

            // YYYY-DDD
            parts = dateString.match( /(\d\d\d\d)-(\d\d\d)/ );
            if (parts) {
                if (parts[1]) {
                    this.dY = parts[1];
                }
                if (parts[2]) {
                    this.dDDD = parts[2];
                }
            }

            if (this.dDDD === -1) {
                // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501
                parts = dateString.match( /(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/ );
                if (parts[1]) {
                    this.dY = parts[1];
                }
                if (parts[2]) {
                    this.dM = parts[2];
                }
                if (parts[3]) {
                    this.dD = parts[3];
                }
            }
            return this.toString(format);
        },


        /**
         * parses text to find just the time element of an ISO date/time string i.e. 13:30:45
         *
         * @param  {String} timeString
         * @param  {String} format
         * @return {String}
         */
        parseTime( timeString, format ) {
            this.clearTime();
            var parts = [];

            // discover date separtor for auto profile // default is ':'
            if (timeString.indexOf(":") === -1) {
                this.autoProfile.tsep = "";
            }

            // finds timezone HH:MM:SS and HHMMSS  ie 13:30:45, 133045 and 13:30:45.0135
            parts = timeString.match( /(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/ );
            if (parts[1]) {
                this.tH = parts[1];
            }
            if (parts[2]) {
                this.tM = parts[2];
            }
            if (parts[3]) {
                this.tS = parts[3];
            }
            if (parts[4]) {
                this.tD = parts[4];
            }
            return this.toTimeString(format);
        },


        /**
         * parses text to find just the time element of an ISO date/time string i.e. +08:00
         *
         * @param  {String} timeString
         * @param  {String} format
         * @return {String}
         */
        parseTimeZone( timeString, format ) {
            this.clearTimeZone();
            var parts = [];

            if (timeString.toLowerCase() === "z") {
                this.z = true;
                // set case for z
                this.autoProfile.tzZulu = (timeString === "z") ? "z" : "Z";
            } else {

                // discover timezone separtor for auto profile // default is ':'
                if (timeString.indexOf(":") === -1) {
                    this.autoProfile.tzsep = "";
                }

                // finds timezone +HH:MM and +HHMM  ie +13:30 and +1330
                parts = timeString.match( /([\-\+]{1})?(\d\d)?:?(\d\d)?/ );
                if (parts[1]) {
                    this.tzPN = parts[1];
                }
                if (parts[2]) {
                    this.tzH = parts[2];
                }
                if (parts[3]) {
                    this.tzM = parts[3];
                }


            }
            this.tzZulu = "z";
            return this.toTimeString( format );
        },


        /**
         * returns ISO date/time string in W3C Note, RFC 3339, HTML5, or auto profile
         *
         * @param  {String} format
         * @return {String}
         */
        toString( format ) {
            var output = "";

            if (format) {
                this.format = format;
            }
            this.setFormatSep();

            if (this.dY > -1) {
                output = this.dY;
                if (this.dM > 0 && this.dM < 13) {
                    output += this.dsep + this.dM;
                    if (this.dD > 0 && this.dD < 32) {
                        output += this.dsep + this.dD;
                        if (this.tH > -1 && this.tH < 25) {
                            output += this.sep + this.toTimeString( format );
                        }
                    }
                }
                if (this.dDDD > -1) {
                    output += this.dsep + this.dDDD;
                }
            } else if (this.tH > -1) {
                output += this.toTimeString( format );
            }

            return output;
        },


        /**
         * returns just the time string element of an ISO date/time
         * in W3C Note, RFC 3339, HTML5, or auto profile
         *
         * @param  {String} format
         * @return {String}
         */
        toTimeString( format ) {
            var out = "";

            if (format) {
                this.format = format;
            }
            this.setFormatSep();

            // time can only be created with a full date
            if (this.tH) {
                if (this.tH > -1 && this.tH < 25) {
                    out += this.tH;
                    if (this.tM > -1 && this.tM < 61) {
                        out += this.tsep + this.tM;
                        if (this.tS > -1 && this.tS < 61) {
                            out += this.tsep + this.tS;
                            if (this.tD > -1) {
                                out += "." + this.tD;
                            }
                        }
                    }



                    // time zone offset
                    if (this.z) {
                        out += this.tzZulu;
                    } else if (this.tzH && this.tzH > -1 && this.tzH < 25) {
                        out += this.tzPN + this.tzH;
                        if (this.tzM > -1 && this.tzM < 61) {
                            out += this.tzsep + this.tzM;
                        }
                    }
                }
            }
            return out;
        },


        /**
         * set the current profile to W3C Note, RFC 3339, HTML5, or auto profile
         *
         */
        setFormatSep() {
            switch ( this.format.toLowerCase() ) {
                case "rfc3339":
                    this.sep = "T";
                    this.dsep = "";
                    this.tsep = "";
                    this.tzsep = "";
                    this.tzZulu = "Z";
                    break;
                case "w3c":
                    this.sep = "T";
                    this.dsep = "-";
                    this.tsep = ":";
                    this.tzsep = ":";
                    this.tzZulu = "Z";
                    break;
                case "html5":
                    this.sep = " ";
                    this.dsep = "-";
                    this.tsep = ":";
                    this.tzsep = ":";
                    this.tzZulu = "Z";
                    break;
                default:
                    // auto - defined by format of input string
                    this.sep = this.autoProfile.sep;
                    this.dsep = this.autoProfile.dsep;
                    this.tsep = this.autoProfile.tsep;
                    this.tzsep = this.autoProfile.tzsep;
                    this.tzZulu = this.autoProfile.tzZulu;
            }
        },


        /**
         * does current data contain a full date i.e. 2015-03-23
         *
         * @return {Boolean}
         */
        hasFullDate() {
            return (this.dY !== -1 && this.dM !== -1 && this.dD !== -1);
        },


        /**
         * does current data contain a minimum date which is just a year number i.e. 2015
         *
         * @return {Boolean}
         */
        hasDate() {
            return (this.dY !== -1);
        },


        /**
         * does current data contain a minimum time which is just a hour number i.e. 13
         *
         * @return {Boolean}
         */
        hasTime() {
            return (this.tH !== -1);
        },

        /**
         * does current data contain a minimum timezone i.e. -1 || +1 || z
         *
         * @return {Boolean}
         */
        hasTimeZone() {
            return (this.tzH !== -1);
        }

    };

    modules.ISODate.prototype.constructor = modules.ISODate;


    modules.dates = {


        /**
         * does text contain am
         *
         * @param  {String} text
         * @return {Boolean}
         */
        hasAM( text ) {
            text = text.toLowerCase();
            return (text.indexOf("am") > -1 || text.indexOf("a.m.") > -1);
        },


        /**
         * does text contain pm
         *
         * @param  {String} text
         * @return {Boolean}
         */
        hasPM( text ) {
            text = text.toLowerCase();
            return (text.indexOf("pm") > -1 || text.indexOf("p.m.") > -1);
        },


        /**
         * remove am and pm from text and return it
         *
         * @param  {String} text
         * @return {String}
         */
        removeAMPM( text ) {
            return text.replace("pm", "").replace("p.m.", "").replace("am", "").replace("a.m.", "");
        },


       /**
         * simple test of whether ISO date string is a duration  i.e.  PY17M or PW12
         *
         * @param  {String} text
         * @return {Boolean}
         */
        isDuration( text ) {
            if (modules.utils.isString( text )) {
                text = text.toLowerCase();
                if (modules.utils.startWith(text, "p") ) {
                    return true;
                }
            }
            return false;
        },


       /**
         * is text a time or timezone
         * i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843
         *
         * @param  {String} text
         * @return {Boolean}
         */
        isTime( text ) {
            if (modules.utils.isString(text)) {
                text = text.toLowerCase();
                text = modules.utils.trim( text );
                // start with timezone char
                if ( text.match(":") && ( modules.utils.startWith(text, "z") || modules.utils.startWith(text, "-") || modules.utils.startWith(text, "+") )) {
                    return true;
                }
                // has ante meridiem or post meridiem
                if ( text.match(/^[0-9]/) &&
                    ( this.hasAM(text) || this.hasPM(text) )) {
                    return true;
                }
                // contains time delimiter but not datetime delimiter
                if ( text.match(":") && !text.match(/t|\s/) ) {
                    return true;
                }

                // if it's a number of 2, 4 or 6 chars
                if (modules.utils.isNumber(text)) {
                    if (text.length === 2 || text.length === 4 || text.length === 6) {
                        return true;
                    }
                }
            }
            return false;
        },


        /**
         * parses a time from text and returns 24hr time string
         * i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04
         *
         * @param  {String} text
         * @return {String}
         */
        parseAmPmTime( text ) {
            var out = text,
                times = [];

            // if the string has a text : or am or pm
            if (modules.utils.isString(out)) {
                // text = text.toLowerCase();
                text = text.replace(/[ ]+/g, "");

                if (text.match(":") || this.hasAM(text) || this.hasPM(text)) {

                    if (text.match(":")) {
                        times = text.split(":");
                    } else {
                        // single number text i.e. 5pm
                        times[0] = text;
                        times[0] = this.removeAMPM(times[0]);
                    }

                    // change pm hours to 24hr number
                    if (this.hasPM(text)) {
                        if (times[0] < 12) {
                            times[0] = parseInt(times[0], 10) + 12;
                        }
                    }

                    // add leading zero's where needed
                    if (times[0] && times[0].length === 1) {
                        times[0] = "0" + times[0];
                    }

                    // rejoin text elements together
                    if (times[0]) {
                        text = times.join(":");
                    }
                }
            }

            // remove am/pm strings
            return this.removeAMPM(text);
        },


       /**
         * overlays a time on a date to return the union of the two
         *
         * @param  {String} date
         * @param  {String} time
         * @param  {String} format ( Modules.ISODate profile format )
         * @return {Object} Modules.ISODate
         */
        dateTimeUnion(date, time, format) {
            var isodate = new modules.ISODate(date, format),
                isotime = new modules.ISODate();

            isotime.parseTime(this.parseAmPmTime(time), format);
            if (isodate.hasFullDate() && isotime.hasTime()) {
                isodate.tH = isotime.tH;
                isodate.tM = isotime.tM;
                isodate.tS = isotime.tS;
                isodate.tD = isotime.tD;
                return isodate;
            }
            if (isodate.hasFullDate()) {
                return isodate;
            }
            return new modules.ISODate();
        },


       /**
         * concatenate an array of date and time text fragments to create an ISODate object
         * used for microformat value and value-title rules
         *
         * @param  {Array} arr ( Array of Strings )
         * @param  {String} format ( Modules.ISODate profile format )
         * @return {Object} Modules.ISODate
         */
        concatFragments(arr, format) {
            var out = new modules.ISODate(),
                i = 0,
                value = "";

            // if the fragment already contains a full date just return it once
            if (arr[0].toUpperCase().match("T")) {
                return new modules.ISODate(arr[0], format);
            }
            for (i = 0; i < arr.length; i++) {
                value = arr[i];

                // date pattern
                if ( value.charAt(4) === "-" && out.hasFullDate() === false ) {
                    out.parseDate(value);
                }

                // time pattern
                if ( (value.indexOf(":") > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime() === false ) {
                    // split time and timezone
                    var items = this.splitTimeAndZone(value);
                    value = items[0];

                    // parse any use of am/pm
                    value = this.parseAmPmTime(value);
                    out.parseTime(value);

                    // parse any timezone
                    if (items.length > 1) {
                         out.parseTimeZone(items[1], format);
                    }
                }

                // timezone pattern
                if (value.charAt(0) === "-" || value.charAt(0) === "+" || value.toUpperCase() === "Z") {
                    if ( out.hasTimeZone() === false ) {
                        out.parseTimeZone(value);
                    }
                }

            }
            return out;
        },


       /**
         * parses text by splitting it into an array of time and timezone strings
         *
         * @param  {String} text
         * @return {Array} Modules.ISODate
         */
        splitTimeAndZone( text ) {
           var out = [text],
               chars = ["-", "+", "z", "Z"],
               i = chars.length;

            while (i--) {
              if (text.indexOf(chars[i]) > -1) {
                  out[0] = text.slice( 0, text.indexOf(chars[i]) );
                  out.push( text.slice( text.indexOf(chars[i]) ) );
                  break;
               }
            }
           return out;
        }

    };


    modules.text = {

        // normalised or whitespace or whitespacetrimmed
        textFormat: "whitespacetrimmed",

        // block level tags, used to add line returns
        blockLevelTags: ["h1", "h2", "h3", "h4", "h5", "h6", "p", "hr", "pre", "table",
            "address", "article", "aside", "blockquote", "caption", "col", "colgroup", "dd", "div",
            "dt", "dir", "fieldset", "figcaption", "figure", "footer", "form",  "header", "hgroup", "hr",
            "li", "map", "menu", "nav", "optgroup", "option", "section", "tbody", "testarea",
            "tfoot", "th", "thead", "tr", "td", "ul", "ol", "dl", "details"],

        // tags to exclude
        excludeTags: ["noframe", "noscript", "template", "script", "style", "frames", "frameset"],


        /**
         * parses the text from the DOM Node
         *
         * @param  {DOM Node} node
         * @param  {String} textFormat
         * @return {String}
         */
        parse(doc, node, textFormat) {
            var out;
            this.textFormat = (textFormat) ? textFormat : this.textFormat;
            if (this.textFormat === "normalised") {
                out = this.walkTreeForText( node );
                if (out !== undefined) {
                    return this.normalise( doc, out );
                }
                return "";
            }
            return this.formatText( doc, modules.domUtils.textContent(node), this.textFormat );
        },


        /**
         * parses the text from a html string
         *
         * @param  {DOM Document} doc
         * @param  {String} text
         * @param  {String} textFormat
         * @return {String}
         */
        parseText( doc, text, textFormat ) {
           var node = modules.domUtils.createNodeWithText( "div", text );
           return this.parse( doc, node, textFormat );
        },


        /**
         * parses the text from a html string - only for whitespace or whitespacetrimmed formats
         *
         * @param  {String} text
         * @param  {String} textFormat
         * @return {String}
         */
        formatText( doc, text, textFormat ) {
           this.textFormat = (textFormat) ? textFormat : this.textFormat;
           if (text) {
              var out = "",
                  regex = /(<([^>]+)>)/ig;

              out = text.replace(regex, "");
              if (this.textFormat === "whitespacetrimmed") {
                 out = modules.utils.trimWhitespace( out );
              }

              // return entities.decode( out, 2 );
              return modules.domUtils.decodeEntities( doc, out );
           }
           return "";
        },


        /**
         * normalises whitespace in given text
         *
         * @param  {String} text
         * @return {String}
         */
        normalise( doc, text ) {
            text = text.replace( /&nbsp;/g, " ") ;    // exchanges html entity for space into space char
            text = modules.utils.collapseWhiteSpace( text );     // removes linefeeds, tabs and addtional spaces
            text = modules.domUtils.decodeEntities( doc, text );  // decode HTML entities
            text = text.replace( "–", "-" );          // correct dash decoding
            return modules.utils.trim( text );
        },


        /**
         * walks DOM tree parsing the text from DOM Nodes
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        walkTreeForText( node ) {
            var out = "",
                j = 0;

            if (node.tagName && this.excludeTags.indexOf( node.tagName.toLowerCase() ) > -1) {
                return out;
            }

            // if node is a text node get its text
            if (node.nodeType && node.nodeType === 3) {
                out += modules.domUtils.getElementText( node );
            }

            // get the text of the child nodes
            if (node.childNodes && node.childNodes.length > 0) {
                for (j = 0; j < node.childNodes.length; j++) {
                    var text = this.walkTreeForText( node.childNodes[j] );
                    if (text !== undefined) {
                        out += text;
                    }
                }
            }

            // if it's a block level tag add an additional space at the end
            if (node.tagName && this.blockLevelTags.indexOf( node.tagName.toLowerCase() ) !== -1) {
                out += " ";
            }

            return (out === "") ? undefined : out ;
        }

    };


    modules.html = {

        // elements which are self-closing
        selfClosingElt: ["area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command", "keygen", "source"],


        /**
         * parse the html string from DOM Node
         *
         * @param  {DOM Node} node
         * @return {String}
         */
        parse( node ) {
            var out = "",
                j = 0;

            // we do not want the outer container
            if (node.childNodes && node.childNodes.length > 0) {
                for (j = 0; j < node.childNodes.length; j++) {
                    var text = this.walkTreeForHtml( node.childNodes[j] );
                    if (text !== undefined) {
                        out += text;
                    }
                }
            }

            return out;
        },


        /**
         * walks the DOM tree parsing the html string from the nodes
         *
         * @param  {DOM Document} doc
         * @param  {DOM Node} node
         * @return {String}
         */
        walkTreeForHtml( node ) {
            var out = "",
                j = 0;

            // if node is a text node get its text
            if (node.nodeType && node.nodeType === 3) {
                out += modules.domUtils.getElementText( node );
            }


            // exclude text which has been added with include pattern  -
            if (node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, "data-include") === false) {

                // begin tag
                out += "<" + node.tagName.toLowerCase();

                // add attributes
                var attrs = modules.domUtils.getOrderedAttributes(node);
                for (j = 0; j < attrs.length; j++) {
                    out += " " + attrs[j].name + "=" + '"' + attrs[j].value + '"';
                }

                if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) === -1) {
                    out += ">";
                }

                // get the text of the child nodes
                if (node.childNodes && node.childNodes.length > 0) {

                    for (j = 0; j < node.childNodes.length; j++) {
                        var text = this.walkTreeForHtml( node.childNodes[j] );
                        if (text !== undefined) {
                            out += text;
                        }
                    }
                }

                // end tag
                if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) > -1) {
                    out += " />";
                } else {
                    out += "</" + node.tagName.toLowerCase() + ">";
                }
            }

            return (out === "") ? undefined : out;
        }


    };


    modules.maps["h-adr"] = {
        root: "adr",
        name: "h-adr",
        properties: {
            "post-office-box": {},
            "street-address": {},
            "extended-address": {},
            "locality": {},
            "region": {},
            "postal-code": {},
            "country-name": {}
        }
    };


    modules.maps["h-card"] =  {
        root: "vcard",
        name: "h-card",
        properties: {
            "fn": {
                "map": "p-name"
            },
            "adr": {
                "map": "p-adr",
                "uf": ["h-adr"]
            },
            "agent": {
                "uf": ["h-card"]
            },
            "bday": {
                "map": "dt-bday"
            },
            "class": {},
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "email": {
                "map": "u-email"
            },
            "geo": {
                "map": "p-geo",
                "uf": ["h-geo"]
            },
            "key": {
                "map": "u-key"
            },
            "label": {},
            "logo": {
                "map": "u-logo"
            },
            "mailer": {},
            "honorific-prefix": {},
            "given-name": {},
            "additional-name": {},
            "family-name": {},
            "honorific-suffix": {},
            "nickname": {},
            "note": {}, // could be html i.e. e-note
            "org": {},
            "p-organization-name": {},
            "p-organization-unit": {},
            "photo": {
                "map": "u-photo"
            },
            "rev": {
                "map": "dt-rev"
            },
            "role": {},
            "sequence": {},
            "sort-string": {},
            "sound": {
                "map": "u-sound"
            },
            "title": {
                "map": "p-job-title"
            },
            "tel": {},
            "tz": {},
            "uid": {
                "map": "u-uid"
            },
            "url": {
                "map": "u-url"
            }
        }
    };


    modules.maps["h-entry"] = {
        root: "hentry",
        name: "h-entry",
        properties: {
            "entry-title": {
                "map": "p-name"
            },
            "entry-summary": {
                "map": "p-summary"
            },
            "entry-content": {
                "map": "e-content"
            },
            "published": {
                "map": "dt-published"
            },
            "updated": {
                "map": "dt-updated"
            },
            "author": {
                "uf": ["h-card"]
            },
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "geo": {
                "map": "p-geo",
                "uf": ["h-geo"]
            },
            "latitude": {},
            "longitude": {},
            "url": {
                "map": "u-url",
                "relAlt": ["bookmark"]
            }
        }
    };


    modules.maps["h-event"] = {
        root: "vevent",
        name: "h-event",
        properties: {
            "summary": {
                "map": "p-name"
            },
            "dtstart": {
                "map": "dt-start"
            },
            "dtend": {
                "map": "dt-end"
            },
            "description": {},
            "url": {
                "map": "u-url"
            },
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "location": {
                "uf": ["h-card"]
            },
            "geo": {
                "uf": ["h-geo"]
            },
            "latitude": {},
            "longitude": {},
            "duration": {
                "map": "dt-duration"
            },
            "contact": {
                "uf": ["h-card"]
            },
            "organizer": {
                "uf": ["h-card"]},
            "attendee": {
                "uf": ["h-card"]},
            "uid": {
                "map": "u-uid"
            },
            "attach": {
                "map": "u-attach"
            },
            "status": {},
            "rdate": {},
            "rrule": {}
        }
    };


    modules.maps["h-feed"] = {
        root: "hfeed",
        name: "h-feed",
        properties: {
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "summary": {
                "map": "p-summary"
            },
            "author": {
                "uf": ["h-card"]
            },
            "url": {
                "map": "u-url"
            },
            "photo": {
                "map": "u-photo"
            },
        }
    };


    modules.maps["h-geo"] = {
        root: "geo",
        name: "h-geo",
        properties: {
            "latitude": {},
            "longitude": {}
        }
    };


    modules.maps["h-item"] = {
        root: "item",
        name: "h-item",
        subTree: false,
        properties: {
            "fn": {
                "map": "p-name"
            },
            "url": {
                "map": "u-url"
            },
            "photo": {
                "map": "u-photo"
            }
        }
    };


    modules.maps["h-listing"] = {
            root: "hlisting",
            name: "h-listing",
            properties: {
                "version": {},
                "lister": {
                    "uf": ["h-card"]
                },
                "dtlisted": {
                    "map": "dt-listed"
                },
                "dtexpired": {
                    "map": "dt-expired"
                },
                "location": {},
                "price": {},
                "item": {
                    "uf": ["h-card", "a-adr", "h-geo"]
                },
                "summary": {
                    "map": "p-name"
                },
                "description": {
                    "map": "e-description"
                },
                "listing": {}
            }
        };


    modules.maps["h-news"] = {
            root: "hnews",
            name: "h-news",
            properties: {
                "entry": {
                    "uf": ["h-entry"]
                },
                "geo": {
                    "uf": ["h-geo"]
                },
                "latitude": {},
                "longitude": {},
                "source-org": {
                    "uf": ["h-card"]
                },
                "dateline": {
                    "uf": ["h-card"]
                },
                "item-license": {
                    "map": "u-item-license"
                },
                "principles": {
                    "map": "u-principles",
                    "relAlt": ["principles"]
                }
            }
        };


    modules.maps["h-org"] = {
        root: "h-x-org",  // drop this from v1 as it causes issue with fn org hcard pattern
        name: "h-org",
        childStructure: true,
        properties: {
            "organization-name": {},
            "organization-unit": {}
        }
    };


    modules.maps["h-product"] = {
            root: "hproduct",
            name: "h-product",
            properties: {
                "brand": {
                    "uf": ["h-card"]
                },
                "category": {
                    "map": "p-category",
                    "relAlt": ["tag"]
                },
                "price": {},
                "description": {
                    "map": "e-description"
                },
                "fn": {
                    "map": "p-name"
                },
                "photo": {
                    "map": "u-photo"
                },
                "url": {
                    "map": "u-url"
                },
                "review": {
                    "uf": ["h-review", "h-review-aggregate"]
                },
                "listing": {
                    "uf": ["h-listing"]
                },
                "identifier": {
                    "map": "u-identifier"
                }
            }
        };


    modules.maps["h-recipe"] = {
            root: "hrecipe",
            name: "h-recipe",
            properties: {
                "fn": {
                    "map": "p-name"
                },
                "ingredient": {
                    "map": "e-ingredient"
                },
                "yield": {},
                "instructions": {
                    "map": "e-instructions"
                },
                "duration": {
                    "map": "dt-duration"
                },
                "photo": {
                    "map": "u-photo"
                },
                "summary": {},
                "author": {
                    "uf": ["h-card"]
                },
                "published": {
                    "map": "dt-published"
                },
                "nutrition": {},
                "category": {
                    "map": "p-category",
                    "relAlt": ["tag"]
                },
            }
        };


    modules.maps["h-resume"] = {
        root: "hresume",
        name: "h-resume",
        properties: {
            "summary": {},
            "contact": {
                "uf": ["h-card"]
            },
            "education": {
                "uf": ["h-card", "h-event"]
            },
            "experience": {
                "uf": ["h-card", "h-event"]
            },
            "skill": {},
            "affiliation": {
                "uf": ["h-card"]
            }
        }
    };


    modules.maps["h-review-aggregate"] = {
        root: "hreview-aggregate",
        name: "h-review-aggregate",
        properties: {
            "summary": {
                "map": "p-name"
            },
            "item": {
                "map": "p-item",
                "uf": ["h-item", "h-geo", "h-adr", "h-card", "h-event", "h-product"]
            },
            "rating": {},
            "average": {},
            "best": {},
            "worst": {},
            "count": {},
            "votes": {},
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "url": {
                "map": "u-url",
                "relAlt": ["self", "bookmark"]
            }
        }
    };


    modules.maps["h-review"] = {
        root: "hreview",
        name: "h-review",
        properties: {
            "summary": {
                "map": "p-name"
            },
            "description": {
                "map": "e-description"
            },
            "item": {
                "map": "p-item",
                "uf": ["h-item", "h-geo", "h-adr", "h-card", "h-event", "h-product"]
            },
            "reviewer": {
                "uf": ["h-card"]
            },
            "dtreviewer": {
                "map": "dt-reviewer"
            },
            "rating": {},
            "best": {},
            "worst": {},
            "category": {
                "map": "p-category",
                "relAlt": ["tag"]
            },
            "url": {
                "map": "u-url",
                "relAlt": ["self", "bookmark"]
            }
        }
    };


    modules.rels = {
        // xfn
        "friend": [ "yes", "external"],
        "acquaintance": [ "yes", "external"],
        "contact": [ "yes", "external"],
        "met": [ "yes", "external"],
        "co-worker": [ "yes", "external"],
        "colleague": [ "yes", "external"],
        "co-resident": [ "yes", "external"],
        "neighbor": [ "yes", "external"],
        "child": [ "yes", "external"],
        "parent": [ "yes", "external"],
        "sibling": [ "yes", "external"],
        "spouse": [ "yes", "external"],
        "kin": [ "yes", "external"],
        "muse": [ "yes", "external"],
        "crush": [ "yes", "external"],
        "date": [ "yes", "external"],
        "sweetheart": [ "yes", "external"],
        "me": [ "yes", "external"],

        // other rel=*
        "license": [ "yes", "yes"],
        "nofollow": [ "no", "external"],
        "tag": [ "no", "yes"],
        "self": [ "no", "external"],
        "bookmark": [ "no", "external"],
        "author": [ "no", "external"],
        "home": [ "no", "external"],
        "directory": [ "no", "external"],
        "enclosure": [ "no", "external"],
        "pronunciation": [ "no", "external"],
        "payment": [ "no", "external"],
        "principles": [ "no", "external"]

    };



    var External = {
        version: modules.version,
        livingStandard: modules.livingStandard
    };


    External.get = function(options) {
        var parser = new modules.Parser();
        addV1(parser, options);
        return parser.get( options );
    };


    External.getParent = function(node, options) {
        var parser = new modules.Parser();
        addV1(parser, options);
        return parser.getParent( node, options );
    };


    External.count = function(options) {
        var parser = new modules.Parser();
        addV1(parser, options);
        return parser.count( options );
    };


    External.isMicroformat = function( node, options ) {
        var parser = new modules.Parser();
        addV1(parser, options);
        return parser.isMicroformat( node, options );
    };


    External.hasMicroformats = function( node, options ) {
        var parser = new modules.Parser();
        addV1(parser, options);
        return parser.hasMicroformats( node, options );
    };


    function addV1(parser, options) {
        if (options && options.maps) {
            if (Array.isArray(options.maps)) {
                parser.add(options.maps);
            } else {
                parser.add([options.maps]);
            }
        }
    }


    return External;


}));
try {
    // mozilla jsm support
    Components.utils.importGlobalProperties(["URL"]);
} catch (e) {}
this.EXPORTED_SYMBOLS = ["Microformats"];
PK
!<?5L.L.#modules/narrate/NarrateControls.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;

Cu.import("resource://gre/modules/narrate/VoiceSelect.jsm");
Cu.import("resource://gre/modules/narrate/Narrator.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AsyncPrefs.jsm");
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");

this.EXPORTED_SYMBOLS = ["NarrateControls"];

var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties");

function NarrateControls(mm, win, languagePromise) {
  this._mm = mm;
  this._winRef = Cu.getWeakReference(win);
  this._languagePromise = languagePromise;

  win.addEventListener("unload", this);

  // Append content style sheet in document head
  let style = win.document.createElement("link");
  style.rel = "stylesheet";
  style.href = "chrome://global/skin/narrate.css";
  win.document.head.appendChild(style);

  function localize(pieces, ...substitutions) {
    let result = pieces[0];
    for (let i = 0; i < substitutions.length; ++i) {
      result += gStrings.GetStringFromName(substitutions[i]) + pieces[i + 1];
    }
    return result;
  }

  let dropdown = win.document.createElement("ul");
  dropdown.className = "dropdown";
  dropdown.id = "narrate-dropdown";
  // We need inline svg here for the animation to work (bug 908634 & 1190881).
  // The style animation can't be scoped (bug 830056).
  // eslint-disable-next-line no-unsanitized/property
  dropdown.innerHTML =
    localize`<style scoped>
      @import url("chrome://global/skin/narrateControls.css");
    </style>
    <li>
       <button class="dropdown-toggle button" id="narrate-toggle"
               title="${"narrate"}" hidden>
         <svg xmlns="http://www.w3.org/2000/svg"
              xmlns:xlink="http://www.w3.org/1999/xlink"
              width="24" height="24" viewBox="0 0 24 24">
          <style>
            @keyframes grow {
              0%   { transform: scaleY(1);   }
              15%  { transform: scaleY(1.5); }
              15%  { transform: scaleY(1.5); }
              30%  { transform: scaleY(1);   }
              100% { transform: scaleY(1);   }
            }

            #waveform > rect {
              fill: #808080;
            }

            .speaking #waveform > rect {
              fill: #58bf43;
              transform-box: fill-box;
              transform-origin: 50% 50%;
              animation-name: grow;
              animation-duration: 1750ms;
              animation-iteration-count: infinite;
              animation-timing-function: linear;
            }

            #waveform > rect:nth-child(2) { animation-delay: 250ms; }
            #waveform > rect:nth-child(3) { animation-delay: 500ms; }
            #waveform > rect:nth-child(4) { animation-delay: 750ms; }
            #waveform > rect:nth-child(5) { animation-delay: 1000ms; }
            #waveform > rect:nth-child(6) { animation-delay: 1250ms; }
            #waveform > rect:nth-child(7) { animation-delay: 1500ms; }

          </style>
          <g id="waveform">
            <rect x="1"  y="8" width="2" height="8"  rx=".5" ry=".5" />
            <rect x="4"  y="5" width="2" height="14" rx=".5" ry=".5" />
            <rect x="7"  y="8" width="2" height="8"  rx=".5" ry=".5" />
            <rect x="10" y="4" width="2" height="16" rx=".5" ry=".5" />
            <rect x="13" y="2" width="2" height="20" rx=".5" ry=".5" />
            <rect x="16" y="4" width="2" height="16" rx=".5" ry=".5" />
            <rect x="19" y="7" width="2" height="10" rx=".5" ry=".5" />
          </g>
         </svg>
        </button>
    </li>
    <li class="dropdown-popup">
      <div id="narrate-control" class="narrate-row">
        <button disabled id="narrate-skip-previous"
                title="${"back"}"></button>
        <button id="narrate-start-stop" title="${"start"}"></button>
        <button disabled id="narrate-skip-next"
                title="${"forward"}"></button>
      </div>
      <div id="narrate-rate" class="narrate-row">
        <input id="narrate-rate-input" value="0" title="${"speed"}"
               step="5" max="100" min="-100" type="range">
      </div>
      <div id="narrate-voices" class="narrate-row"></div>
      <div class="dropdown-arrow"></div>
    </li>`;

  this.narrator = new Narrator(win, languagePromise);

  let branch = Services.prefs.getBranch("narrate.");
  let selectLabel = gStrings.GetStringFromName("selectvoicelabel");
  this.voiceSelect = new VoiceSelect(win, selectLabel);
  this.voiceSelect.element.addEventListener("change", this);
  this.voiceSelect.element.id = "voice-select";
  win.speechSynthesis.addEventListener("voiceschanged", this);
  dropdown.querySelector("#narrate-voices").appendChild(
    this.voiceSelect.element);

  dropdown.addEventListener("click", this, true);

  let rateRange = dropdown.querySelector("#narrate-rate > input");
  rateRange.addEventListener("change", this);

  // The rate is stored as an integer.
  rateRange.value = branch.getIntPref("rate");

  this._setupVoices();

  let tb = win.document.getElementById("reader-toolbar");
  tb.appendChild(dropdown);
}

NarrateControls.prototype = {
  handleEvent(evt) {
    switch (evt.type) {
      case "change":
        if (evt.target.id == "narrate-rate-input") {
          this._onRateInput(evt);
        } else {
          this._onVoiceChange();
        }
        break;
      case "click":
        this._onButtonClick(evt);
        break;
      case "voiceschanged":
        this._setupVoices();
        break;
      case "unload":
        if (this.narrator.speaking) {
          TelemetryStopwatch.finish("NARRATE_CONTENT_SPEAKTIME_MS", this);
        }
        break;
    }
  },

  /**
   * Returns true if synth voices are available.
   */
  _setupVoices() {
    return this._languagePromise.then(language => {
      this.voiceSelect.clear();
      let win = this._win;
      let voicePrefs = this._getVoicePref();
      let selectedVoice = voicePrefs[language || "default"];
      let comparer = win.Intl ?
        (new Intl.Collator()).compare : (a, b) => a.localeCompare(b);
      let filter = !Services.prefs.getBoolPref("narrate.filter-voices");
      let options = win.speechSynthesis.getVoices().filter(v => {
        return filter || !language || v.lang.split("-")[0] == language;
      }).map(v => {
        return {
          label: this._createVoiceLabel(v),
          value: v.voiceURI,
          selected: selectedVoice == v.voiceURI
        };
      }).sort((a, b) => comparer(a.label, b.label));

      if (options.length) {
        options.unshift({
          label: gStrings.GetStringFromName("defaultvoice"),
          value: "automatic",
          selected: selectedVoice == "automatic"
        });
        this.voiceSelect.addOptions(options);
      }

      let narrateToggle = win.document.getElementById("narrate-toggle");
      let histogram = Services.telemetry.getKeyedHistogramById(
        "NARRATE_CONTENT_BY_LANGUAGE_2");
      let initial = !this._voicesInitialized;
      this._voicesInitialized = true;

      if (initial) {
        histogram.add(language, 0);
      }

      if (options.length && narrateToggle.hidden) {
        // About to show for the first time..
        histogram.add(language, 1);
      }

      // We disable this entire feature if there are no available voices.
      narrateToggle.hidden = !options.length;
    });
  },

  _getVoicePref() {
    let voicePref = Services.prefs.getCharPref("narrate.voice");
    try {
      return JSON.parse(voicePref);
    } catch (e) {
      return { default: voicePref };
    }
  },

  _onRateInput(evt) {
    AsyncPrefs.set("narrate.rate", parseInt(evt.target.value, 10));
    this.narrator.setRate(this._convertRate(evt.target.value));
  },

  _onVoiceChange() {
    let voice = this.voice;
    this.narrator.setVoice(voice);
    this._languagePromise.then(language => {
      if (language) {
        let voicePref = this._getVoicePref();
        voicePref[language || "default"] = voice;
        AsyncPrefs.set("narrate.voice", JSON.stringify(voicePref));
      }
    });
  },

  _onButtonClick(evt) {
    switch (evt.target.id) {
      case "narrate-skip-previous":
        this.narrator.skipPrevious();
        break;
      case "narrate-skip-next":
        this.narrator.skipNext();
        break;
      case "narrate-start-stop":
        if (this.narrator.speaking) {
          this.narrator.stop();
        } else {
          this._updateSpeechControls(true);
          let options = { rate: this.rate, voice: this.voice };
          this.narrator.start(options).then(() => {
            this._updateSpeechControls(false);
          }, err => {
            Cu.reportError(`Narrate failed: ${err}.`);
            this._updateSpeechControls(false);
          });
        }
        break;
    }
  },

  _updateSpeechControls(speaking) {
    let dropdown = this._doc.getElementById("narrate-dropdown");
    dropdown.classList.toggle("keep-open", speaking);
    dropdown.classList.toggle("speaking", speaking);

    let startStopButton = this._doc.getElementById("narrate-start-stop");
    startStopButton.title =
      gStrings.GetStringFromName(speaking ? "stop" : "start");

    this._doc.getElementById("narrate-skip-previous").disabled = !speaking;
    this._doc.getElementById("narrate-skip-next").disabled = !speaking;

    if (speaking) {
      TelemetryStopwatch.start("NARRATE_CONTENT_SPEAKTIME_MS", this);
    } else {
      TelemetryStopwatch.finish("NARRATE_CONTENT_SPEAKTIME_MS", this);
    }
  },

  _createVoiceLabel(voice) {
    // This is a highly imperfect method of making human-readable labels
    // for system voices. Because each platform has a different naming scheme
    // for voices, we use a different method for each platform.
    switch (Services.appinfo.OS) {
      case "WINNT":
        // On windows the language is included in the name, so just use the name
        return voice.name;
      case "Linux":
        // On Linux, the name is usually the unlocalized language name.
        // Use a localized language name, and have the language tag in
        // parenthisis. This is to avoid six languages called "English".
        return gStrings.formatStringFromName("voiceLabel",
          [this._getLanguageName(voice.lang) || voice.name, voice.lang], 2);
      default:
        // On Mac the language is not included in the name, find a localized
        // language name or show the tag if none exists.
        // This is the ideal naming scheme so it is also the "default".
        return gStrings.formatStringFromName("voiceLabel",
          [voice.name, this._getLanguageName(voice.lang) || voice.lang], 2);
    }
  },

  _getLanguageName(lang) {
    if (!this._langStrings) {
      this._langStrings = Services.strings.createBundle(
        "chrome://global/locale/languageNames.properties ");
    }

    try {
      // language tags will be lower case ascii between 2 and 3 characters long.
      return this._langStrings.GetStringFromName(lang.match(/^[a-z]{2,3}/)[0]);
    } catch (e) {
      return "";
    }
  },

  _convertRate(rate) {
    // We need to convert a relative percentage value to a fraction rate value.
    // eg. -100 is half the speed, 100 is twice the speed in percentage,
    // 0.5 is half the speed and 2 is twice the speed in fractions.
    return Math.pow(Math.abs(rate / 100) + 1, rate < 0 ? -1 : 1);
  },

  get _win() {
    return this._winRef.get();
  },

  get _doc() {
    return this._win.document;
  },

  get rate() {
    return this._convertRate(
      this._doc.getElementById("narrate-rate-input").value);
  },

  get voice() {
    return this.voiceSelect.value;
  }
};
PK
!<P|11modules/narrate/Narrator.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

this.EXPORTED_SYMBOLS = [ "Narrator" ];

// Maximum time into paragraph when pressing "skip previous" will go
// to previous paragraph and not the start of current one.
const PREV_THRESHOLD = 2000;
// All text-related style rules that we should copy over to the highlight node.
const kTextStylesRules = ["font-family", "font-kerning", "font-size",
  "font-size-adjust", "font-stretch", "font-variant", "font-weight",
  "line-height", "letter-spacing", "text-orientation",
  "text-transform", "word-spacing"];

function Narrator(win, languagePromise) {
  this._winRef = Cu.getWeakReference(win);
  this._languagePromise = languagePromise;
  this._inTest = Services.prefs.getBoolPref("narrate.test");
  this._speechOptions = {};
  this._startTime = 0;
  this._stopped = false;
}

Narrator.prototype = {
  get _doc() {
    return this._winRef.get().document;
  },

  get _win() {
    return this._winRef.get();
  },

  get _treeWalker() {
    if (!this._treeWalkerRef) {
      let wu = this._win.QueryInterface(
        Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
      let nf = this._win.NodeFilter;

      let filter = {
        _matches: new Set(),

        // We want high-level elements that have non-empty text nodes.
        // For example, paragraphs. But nested anchors and other elements
        // are not interesting since their text already appears in their
        // parent's textContent.
        acceptNode(node) {
          if (this._matches.has(node.parentNode)) {
            // Reject sub-trees of accepted nodes.
            return nf.FILTER_REJECT;
          }

          if (!/\S/.test(node.textContent)) {
            // Reject nodes with no text.
            return nf.FILTER_REJECT;
          }

          let bb = wu.getBoundsWithoutFlushing(node);
          if (!bb.width || !bb.height) {
            // Skip non-rendered nodes. We don't reject because a zero-sized
            // container can still have visible, "overflowed", content.
            return nf.FILTER_SKIP;
          }

          for (let c = node.firstChild; c; c = c.nextSibling) {
            if (c.nodeType == c.TEXT_NODE && /\S/.test(c.textContent)) {
              // If node has a non-empty text child accept it.
              this._matches.add(node);
              return nf.FILTER_ACCEPT;
            }
          }

          return nf.FILTER_SKIP;
        }
      };

      this._treeWalkerRef = new WeakMap();

      // We can't hold a weak reference on the treewalker, because there
      // are no other strong references, and it will be GC'ed. Instead,
      // we rely on the window's lifetime and use it as a weak reference.
      this._treeWalkerRef.set(this._win,
        this._doc.createTreeWalker(this._doc.getElementById("container"),
          nf.SHOW_ELEMENT, filter, false));
    }

    return this._treeWalkerRef.get(this._win);
  },

  get _timeIntoParagraph() {
    let rv = Date.now() - this._startTime;
    return rv;
  },

  get speaking() {
    return this._win.speechSynthesis.speaking ||
      this._win.speechSynthesis.pending;
  },

  _getVoice(voiceURI) {
    if (!this._voiceMap || !this._voiceMap.has(voiceURI)) {
      this._voiceMap = new Map(
        this._win.speechSynthesis.getVoices().map(v => [v.voiceURI, v]));
    }

    return this._voiceMap.get(voiceURI);
  },

  _isParagraphInView(paragraph) {
    if (!paragraph) {
      return false;
    }

    let bb = paragraph.getBoundingClientRect();
    return bb.top >= 0 && bb.top < this._win.innerHeight;
  },

  _sendTestEvent(eventType, detail) {
    let win = this._win;
    win.dispatchEvent(new win.CustomEvent(eventType,
      { detail: Cu.cloneInto(detail, win.document) }));
  },

  _speakInner() {
    this._win.speechSynthesis.cancel();
    let tw = this._treeWalker;
    let paragraph = tw.currentNode;
    if (paragraph == tw.root) {
      this._sendTestEvent("paragraphsdone", {});
      return Promise.resolve();
    }

    let utterance = new this._win.SpeechSynthesisUtterance(
      paragraph.textContent);
    utterance.rate = this._speechOptions.rate;
    if (this._speechOptions.voice) {
      utterance.voice = this._speechOptions.voice;
    } else {
      utterance.lang = this._speechOptions.lang;
    }

    this._startTime = Date.now();

    let highlighter = new Highlighter(paragraph);

    if (this._inTest) {
      let onTestSynthEvent = e => {
        if (e.detail.type == "boundary") {
          let args = Object.assign({ utterance }, e.detail.args);
          let evt = new this._win.SpeechSynthesisEvent(e.detail.type, args);
          utterance.dispatchEvent(evt);
        }
      };

      let removeListeners = () => {
        this._win.removeEventListener("testsynthevent", onTestSynthEvent);
      };

      this._win.addEventListener("testsynthevent", onTestSynthEvent);
      utterance.addEventListener("end", removeListeners);
      utterance.addEventListener("error", removeListeners);
    }

    return new Promise((resolve, reject) => {
      utterance.addEventListener("start", () => {
        paragraph.classList.add("narrating");
        let bb = paragraph.getBoundingClientRect();
        if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
          paragraph.scrollIntoView({ behavior: "smooth", block: "start"});
        }

        if (this._inTest) {
          this._sendTestEvent("paragraphstart", {
            voice: utterance.chosenVoiceURI,
            rate: utterance.rate,
            paragraph: paragraph.textContent,
            tag: paragraph.localName
          });
        }
      });

      utterance.addEventListener("end", () => {
        if (!this._win) {
          // page got unloaded, don't do anything.
          return;
        }

        highlighter.remove();
        paragraph.classList.remove("narrating");
        this._startTime = 0;
        if (this._inTest) {
          this._sendTestEvent("paragraphend", {});
        }

        if (this._stopped) {
          // User pressed stopped.
          resolve();
        } else {
          tw.currentNode = tw.nextNode() || tw.root;
          this._speakInner().then(resolve, reject);
        }
      });

      utterance.addEventListener("error", () => {
        reject("speech synthesis failed");
      });

      utterance.addEventListener("boundary", e => {
        if (e.name != "word") {
          // We are only interested in word boundaries for now.
          return;
        }

        if (e.charLength) {
          highlighter.highlight(e.charIndex, e.charLength);
          if (this._inTest) {
            this._sendTestEvent("wordhighlight", {
              start: e.charIndex,
              end: e.charIndex + e.charLength
            });
          }
        }
      });

      this._win.speechSynthesis.speak(utterance);
    });
  },

  start(speechOptions) {
    this._speechOptions = {
      rate: speechOptions.rate,
      voice: this._getVoice(speechOptions.voice)
    };

    this._stopped = false;
    return this._languagePromise.then(language => {
      if (!this._speechOptions.voice) {
        this._speechOptions.lang = language;
      }

      let tw = this._treeWalker;
      if (!this._isParagraphInView(tw.currentNode)) {
        tw.currentNode = tw.root;
        while (tw.nextNode()) {
          if (this._isParagraphInView(tw.currentNode)) {
            break;
          }
        }
      }
      if (tw.currentNode == tw.root) {
        tw.nextNode();
      }

      return this._speakInner();
    });
  },

  stop() {
    this._stopped = true;
    this._win.speechSynthesis.cancel();
  },

  skipNext() {
    this._win.speechSynthesis.cancel();
  },

  skipPrevious() {
    this._goBackParagraphs(this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1);
  },

  setRate(rate) {
    this._speechOptions.rate = rate;
    /* repeat current paragraph */
    this._goBackParagraphs(1);
  },

  setVoice(voice) {
    this._speechOptions.voice = this._getVoice(voice);
    /* repeat current paragraph */
    this._goBackParagraphs(1);
  },

  _goBackParagraphs(count) {
    let tw = this._treeWalker;
    for (let i = 0; i < count; i++) {
      if (!tw.previousNode()) {
        tw.currentNode = tw.root;
      }
    }
    this._win.speechSynthesis.cancel();
  }
};

/**
 * The Highlighter class is used to highlight a range of text in a container.
 *
 * @param {nsIDOMElement} container a text container
 */
function Highlighter(container) {
  this.container = container;
}

Highlighter.prototype = {
  /**
   * Highlight the range within offsets relative to the container.
   *
   * @param {Number} startOffset the start offset
   * @param {Number} length the length in characters of the range
   */
  highlight(startOffset, length) {
    let containerRect = this.container.getBoundingClientRect();
    let range = this._getRange(startOffset, startOffset + length);
    let rangeRects = range.getClientRects();
    let win = this.container.ownerGlobal;
    let computedStyle = win.getComputedStyle(range.endContainer.parentNode);
    let nodes = this._getFreshHighlightNodes(rangeRects.length);

    let textStyle = {};
    for (let textStyleRule of kTextStylesRules) {
      textStyle[textStyleRule] = computedStyle[textStyleRule];
    }

    for (let i = 0; i < rangeRects.length; i++) {
      let r = rangeRects[i];
      let node = nodes[i];

      let style = Object.assign({
        "top": `${r.top - containerRect.top + r.height / 2}px`,
        "left": `${r.left - containerRect.left + r.width / 2}px`,
        "width": `${r.width}px`,
        "height": `${r.height}px`
      }, textStyle);

      // Enables us to vary the CSS transition on a line change.
      node.classList.toggle("newline", style.top != node.dataset.top);
      node.dataset.top = style.top;

      // Enables CSS animations.
      node.classList.remove("animate");
      win.requestAnimationFrame(() => {
        node.classList.add("animate");
      });

      // Enables alternative word display with a CSS pseudo-element.
      node.dataset.word = range.toString();

      // Apply style
      node.style = Object.entries(style).map(
        s => `${s[0]}: ${s[1]};`).join(" ");
    }
  },

  /**
   * Releases reference to container and removes all highlight nodes.
   */
  remove() {
    for (let node of this._nodes) {
      node.remove();
    }

    this.container = null;
  },

  /**
   * Returns specified amount of highlight nodes. Creates new ones if necessary
   * and purges any additional nodes that are not needed.
   *
   * @param {Number} count number of nodes needed
   */
  _getFreshHighlightNodes(count) {
    let doc = this.container.ownerDocument;
    let nodes = Array.from(this._nodes);

    // Remove nodes we don't need anymore (nodes.length - count > 0).
    for (let toRemove = 0; toRemove < nodes.length - count; toRemove++) {
      nodes.shift().remove();
    }

    // Add additional nodes if we need them (count - nodes.length > 0).
    for (let toAdd = 0; toAdd < count - nodes.length; toAdd++) {
      let node = doc.createElement("div");
      node.className = "narrate-word-highlight";
      this.container.appendChild(node);
      nodes.push(node);
    }

    return nodes;
  },

  /**
   * Create and return a range object with the start and end offsets relative
   * to the container node.
   *
   * @param {Number} startOffset the start offset
   * @param {Number} endOffset the end offset
   */
  _getRange(startOffset, endOffset) {
    let doc = this.container.ownerDocument;
    let i = 0;
    let treeWalker = doc.createTreeWalker(
      this.container, doc.defaultView.NodeFilter.SHOW_TEXT);
    let node = treeWalker.nextNode();

    function _findNodeAndOffset(offset) {
      do {
        let length = node.data.length;
        if (offset >= i && offset <= i + length) {
          return [node, offset - i];
        }
        i += length;
      } while ((node = treeWalker.nextNode()));

      // Offset is out of bounds, return last offset of last node.
      node = treeWalker.lastChild();
      return [node, node.data.length];
    }

    let range = doc.createRange();
    range.setStart(..._findNodeAndOffset(startOffset));
    range.setEnd(..._findNodeAndOffset(endOffset));

    return range;
  },

  /*
   * Get all existing highlight nodes for container.
   */
  get _nodes() {
    return this.container.querySelectorAll(".narrate-word-highlight");
  }
};
PK
!<W:77modules/narrate/VoiceSelect.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ["VoiceSelect"];

function VoiceSelect(win, label) {
  this._winRef = Cu.getWeakReference(win);

  let element = win.document.createElement("div");
  element.classList.add("voiceselect");
  // eslint-disable-next-line no-unsanitized/property
  element.innerHTML =
   `<button class="select-toggle" aria-controls="voice-options">
      <span class="label">${label}</span> <span class="current-voice"></span>
    </button>
    <div class="options" id="voice-options" role="listbox"></div>`;

  this._elementRef = Cu.getWeakReference(element);

  let button = this.selectToggle;
  button.addEventListener("click", this);
  button.addEventListener("keypress", this);

  let listbox = this.listbox;
  listbox.addEventListener("click", this);
  listbox.addEventListener("mousemove", this);
  listbox.addEventListener("keypress", this);
  listbox.addEventListener("wheel", this, true);

  win.addEventListener("resize", () => {
    this._updateDropdownHeight();
  });
}

VoiceSelect.prototype = {
  add(label, value) {
    let option = this._doc.createElement("button");
    option.dataset.value = value;
    option.classList.add("option");
    option.tabIndex = "-1";
    option.setAttribute("role", "option");
    option.textContent = label;
    this.listbox.appendChild(option);
    return option;
  },

  addOptions(options) {
    let selected = null;
    for (let option of options) {
      if (option.selected) {
        selected = this.add(option.label, option.value);
      } else {
        this.add(option.label, option.value);
      }
    }

    this._select(selected || this.options[0], true);
  },

  clear() {
    this.listbox.innerHTML = "";
  },

  toggleList(force, focus = true) {
    if (this.element.classList.toggle("open", force)) {
      if (focus) {
        (this.selected || this.options[0]).focus();
      }

      this._updateDropdownHeight(true);
      this.listbox.setAttribute("aria-expanded", true);
      this._win.addEventListener("focus", this, true);
    } else {
      if (focus) {
        this.element.querySelector(".select-toggle").focus();
      }

      this.listbox.setAttribute("aria-expanded", false);
      this._win.removeEventListener("focus", this, true);
    }
  },

  handleEvent(evt) {
    let target = evt.target;

    switch (evt.type) {
      case "click":
        if (target.classList.contains("option")) {
          if (!target.classList.contains("selected")) {
            this.selected = target;
          }

          this.toggleList(false);
        } else if (target.classList.contains("select-toggle")) {
          this.toggleList();
        }
        break;

      case "mousemove":
        this.listbox.classList.add("hovering");
        break;

      case "keypress":
        if (target.classList.contains("select-toggle")) {
          if (evt.altKey) {
            this.toggleList(true);
          } else {
            this._keyPressedButton(evt);
          }
        } else {
          this.listbox.classList.remove("hovering");
          this._keyPressedInBox(evt);
        }
        break;

      case "wheel":
        // Don't let wheel events bubble to document. It will scroll the page
        // and close the entire narrate dialog.
        evt.stopPropagation();
        break;

      case "focus":
        if (!target.closest(".voiceselect")) {
          this.toggleList(false, false);
        }
        break;
    }
  },

  _getPagedOption(option, up) {
    let height = elem => elem.getBoundingClientRect().height;
    let listboxHeight = height(this.listbox);

    let next = option;
    for (let delta = 0; delta < listboxHeight; delta += height(next)) {
      let sibling = up ? next.previousElementSibling : next.nextElementSibling;
      if (!sibling) {
        break;
      }

      next = sibling;
    }

    return next;
  },

  _keyPressedButton(evt) {
    if (evt.altKey && (evt.key === "ArrowUp" || evt.key === "ArrowUp")) {
      this.toggleList(true);
      return;
    }

    let toSelect;
    switch (evt.key) {
      case "PageUp":
      case "ArrowUp":
        toSelect = this.selected.previousElementSibling;
        break;
      case "PageDown":
      case "ArrowDown":
        toSelect = this.selected.nextElementSibling;
        break;
      case "Home":
        toSelect = this.selected.parentNode.firstElementChild;
        break;
      case "End":
        toSelect = this.selected.parentNode.lastElementChild;
        break;
    }

    if (toSelect && toSelect.classList.contains("option")) {
      evt.preventDefault();
      this.selected = toSelect;
    }
  },

  _keyPressedInBox(evt) {
    let toFocus;
    let cur = this._doc.activeElement;

    switch (evt.key) {
      case "ArrowUp":
        toFocus = cur.previousElementSibling || this.listbox.lastElementChild;
        break;
      case "ArrowDown":
        toFocus = cur.nextElementSibling || this.listbox.firstElementChild;
        break;
      case "PageUp":
        toFocus = this._getPagedOption(cur, true);
        break;
      case "PageDown":
        toFocus = this._getPagedOption(cur, false);
        break;
      case "Home":
        toFocus = cur.parentNode.firstElementChild;
        break;
      case "End":
        toFocus = cur.parentNode.lastElementChild;
        break;
      case "Escape":
        this.toggleList(false);
        break;
    }

    if (toFocus && toFocus.classList.contains("option")) {
      evt.preventDefault();
      toFocus.focus();
    }
  },

  _select(option, suppressEvent = false) {
    let oldSelected = this.selected;
    if (oldSelected) {
      oldSelected.removeAttribute("aria-selected");
      oldSelected.classList.remove("selected");
    }

    if (option) {
      option.setAttribute("aria-selected", true);
      option.classList.add("selected");
      this.element.querySelector(".current-voice").textContent =
        option.textContent;
    }

    if (!suppressEvent) {
      let evt = this.element.ownerDocument.createEvent("Event");
      evt.initEvent("change", true, true);
      this.element.dispatchEvent(evt);
    }
  },

  _updateDropdownHeight(now) {
    let updateInner = () => {
      let winHeight = this._win.innerHeight;
      let listbox = this.listbox;
      let listboxTop = listbox.getBoundingClientRect().top;
      listbox.style.maxHeight = (winHeight - listboxTop - 10) + "px";
    };

    if (now) {
      updateInner();
    } else if (!this._pendingDropdownUpdate) {
      this._pendingDropdownUpdate = true;
      this._win.requestAnimationFrame(() => {
        updateInner();
        delete this._pendingDropdownUpdate;
      });
    }
  },

  _getOptionFromValue(value) {
    return Array.from(this.options).find(o => o.dataset.value === value);
  },

  get element() {
    return this._elementRef.get();
  },

  get listbox() {
    return this._elementRef.get().querySelector(".options");
  },

  get selectToggle() {
    return this._elementRef.get().querySelector(".select-toggle");
  },

  get _win() {
    return this._winRef.get();
  },

  get _doc() {
    return this._win.document;
  },

  set selected(option) {
    this._select(option);
  },

  get selected() {
    return this.element.querySelector(".options > .option.selected");
  },

  get options() {
    return this.element.querySelectorAll(".options > .option");
  },

  set value(value) {
    this._select(this._getOptionFromValue(value));
  },

  get value() {
    let selected = this.selected;
    return selected ? selected.dataset.value : "";
  }
};
PK
!<:ۅ$modules/nsFormAutoCompleteResult.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["FormAutoCompleteResult"];

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.FormAutoCompleteResult = function FormAutoCompleteResult(searchString,
                                                              searchResult,
                                                              defaultIndex,
                                                              errorDescription,
                                                              values,
                                                              labels,
                                                              comments,
                                                              prevResult) {
  this.searchString = searchString;
  this._searchResult = searchResult;
  this._defaultIndex = defaultIndex;
  this._errorDescription = errorDescription;
  this._values = values;
  this._labels = labels;
  this._comments = comments;
  this._formHistResult = prevResult;
  this.entries = prevResult ? prevResult.wrappedJSObject.entries : [];
};

FormAutoCompleteResult.prototype = {

  // The user's query string
  searchString: "",

  // The result code of this result object, see |get searchResult| for possible values.
  _searchResult: 0,

  // The default item that should be entered if none is selected
  _defaultIndex: 0,

  // The reason the search failed
  _errorDescription: "",

  /**
   * A reference to the form history nsIAutocompleteResult that we're wrapping.
   * We use this to forward removeEntryAt calls as needed.
   */
  _formHistResult: null,

  entries: null,

  get wrappedJSObject() {
    return this;
  },

  /**
   * @returns {number} the result code of this result object, either:
   *         RESULT_IGNORED   (invalid searchString)
   *         RESULT_FAILURE   (failure)
   *         RESULT_NOMATCH   (no matches found)
   *         RESULT_SUCCESS   (matches found)
   */
  get searchResult() {
    return this._searchResult;
  },

  /**
   * @returns {number} the default item that should be entered if none is selected
   */
  get defaultIndex() {
    return this._defaultIndex;
  },

  /**
   * @returns {string} the reason the search failed
   */
  get errorDescription() {
    return this._errorDescription;
  },

  /**
   * @returns {number} the number of results
   */
  get matchCount() {
    return this._values.length;
  },

  _checkIndexBounds(index) {
    if (index < 0 || index >= this._values.length) {
      throw Components.Exception("Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  },

  /**
   * Retrieves a result
   * @param   {number} index   the index of the result requested
   * @returns {string}         the result at the specified index
   */
  getValueAt(index) {
    this._checkIndexBounds(index);
    return this._values[index];
  },

  getLabelAt(index) {
    this._checkIndexBounds(index);
    return this._labels[index] || this._values[index];
  },

  /**
   * Retrieves a comment (metadata instance)
   * @param {number} index    the index of the comment requested
   * @returns {Object}        the comment at the specified index
   */
  getCommentAt(index) {
    this._checkIndexBounds(index);
    return this._comments[index];
  },

  /**
   * Retrieves a style hint specific to a particular index.
   * @param   {number} index   the index of the style hint requested
   * @returns {string|null}    the style hint at the specified index
   */
  getStyleAt(index) {
    this._checkIndexBounds(index);

    if (this._formHistResult && index < this._formHistResult.matchCount) {
      return "fromhistory";
    }

    if (this._formHistResult &&
        this._formHistResult.matchCount > 0 &&
        index == this._formHistResult.matchCount) {
      return "datalist-first";
    }

    return null;
  },

  /**
   * Retrieves an image url.
   * @param   {number} index  the index of the image url requested
   * @returns {string}        the image url at the specified index
   */
  getImageAt(index) {
    this._checkIndexBounds(index);
    return "";
  },

  /**
   * Retrieves a result
   * @param   {number} index   the index of the result requested
   * @returns {string}         the result at the specified index
   */
  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  },

  /**
   * Removes a result from the resultset
   * @param {number}  index    the index of the result to remove
   * @param {boolean} removeFromDatabase
   */
  removeValueAt(index, removeFromDatabase) {
    this._checkIndexBounds(index);
    // Forward the removeValueAt call to the underlying result if we have one
    // Note: this assumes that the form history results were added to the top
    // of our arrays.
    if (removeFromDatabase && this._formHistResult &&
        index < this._formHistResult.matchCount) {
      // Delete the history result from the DB
      this._formHistResult.removeValueAt(index, true);
    }
    this._values.splice(index, 1);
    this._labels.splice(index, 1);
    this._comments.splice(index, 1);
  },

  // nsISupports
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult]),
};
PK
!<Tg%modules/osfile/osfile_async_front.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Asynchronous front-end for OS.File.
 *
 * This front-end is meant to be imported from the main thread. In turn,
 * it spawns one worker (perhaps more in the future) and delegates all
 * disk I/O to this worker.
 *
 * Documentation note: most of the functions and methods in this module
 * return promises. For clarity, we denote as follows a promise that may resolve
 * with type |A| and some value |value| or reject with type |B| and some
 * reason |reason|
 * @resolves {A} value
 * @rejects {B} reason
 */

"use strict";

this.EXPORTED_SYMBOLS = ["OS"];

const Cu = Components.utils;
const Ci = Components.interfaces;

var SharedAll = {};
Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);


// Boilerplate, to simplify the transition to require()
var LOG = SharedAll.LOG.bind(SharedAll, "Controller");
var isTypedArray = SharedAll.isTypedArray;

// The constructor for file errors.
var SysAll = {};
if (SharedAll.Constants.Win) {
  Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
} else if (SharedAll.Constants.libc) {
  Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
} else {
  throw new Error("I am neither under Windows nor under a Posix system");
}
var OSError = SysAll.Error;
var Type = SysAll.Type;

var Path = {};
Cu.import("resource://gre/modules/osfile/ospath.jsm", Path);

// The library of promises.
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                  "resource://gre/modules/Task.jsm");

// The implementation of communications
Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
var Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});


// It's possible for osfile.jsm to get imported before the profile is
// set up. In this case, some path constants aren't yet available.
// Here, we make them lazy loaders.

function lazyPathGetter(constProp, dirKey) {
  return function () {
    let path;
    try {
      path = Services.dirsvc.get(dirKey, Ci.nsIFile).path;
      delete SharedAll.Constants.Path[constProp];
      SharedAll.Constants.Path[constProp] = path;
    } catch (ex) {
      // Ignore errors if the value still isn't available. Hopefully
      // the next access will return it.
    }

    return path;
  };
}

for (let [constProp, dirKey] of [
  ["localProfileDir", "ProfLD"],
  ["profileDir", "ProfD"],
  ["userApplicationDataDir", "UAppData"],
  ["winAppDataDir", "AppData"],
  ["winStartMenuProgsDir", "Progs"],
  ]) {

  if (constProp in SharedAll.Constants.Path) {
    continue;
  }

  LOG("Installing lazy getter for OS.Constants.Path." + constProp +
      " because it isn't defined and profile may not be loaded.");
  Object.defineProperty(SharedAll.Constants.Path, constProp, {
    get: lazyPathGetter(constProp, dirKey),
  });
}

/**
 * Return a shallow clone of the enumerable properties of an object.
 */
var clone = SharedAll.clone;

/**
 * Extract a shortened version of an object, fit for logging.
 *
 * This function returns a copy of the original object in which all
 * long strings, Arrays, TypedArrays, ArrayBuffers are removed and
 * replaced with placeholders. Use this function to sanitize objects
 * if you wish to log them or to keep them in memory.
 *
 * @param {*} obj The obj to shorten.
 * @return {*} array A shorter object, fit for logging.
 */
function summarizeObject(obj) {
  if (!obj) {
    return null;
  }
  if (typeof obj == "string") {
    if (obj.length > 1024) {
      return {"Long string": obj.length};
    }
    return obj;
  }
  if (typeof obj == "object") {
    if (Array.isArray(obj)) {
      if (obj.length > 32) {
        return {"Long array": obj.length};
      }
      return obj.map(summarizeObject);
    }
    if ("byteLength" in obj) {
      // Assume TypedArray or ArrayBuffer
      return {"Binary Data": obj.byteLength};
    }
    let result = {};
    for (let k of Object.keys(obj)) {
      result[k] = summarizeObject(obj[k]);
    }
    return result;
  }
  return obj;
}

// In order to expose Scheduler to the unfiltered Cu.import return value variant
// on B2G we need to save it to `this`.  This does not make it public;
// EXPORTED_SYMBOLS still controls that in all cases.
var Scheduler = this.Scheduler = {

  /**
   * |true| once we have sent at least one message to the worker.
   * This field is unaffected by resetting the worker.
   */
  launched: false,

  /**
   * |true| once shutdown has begun i.e. we should reject any
   * message, including resets.
   */
  shutdown: false,

  /**
   * A promise resolved once all currently pending operations are complete.
   *
   * This promise is never rejected and the result is always undefined.
   */
  queue: Promise.resolve(),

  /**
   * A promise resolved once all currently pending `kill` operations
   * are complete.
   *
   * This promise is never rejected and the result is always undefined.
   */
  _killQueue: Promise.resolve(),

  /**
   * Miscellaneous debugging information
   */
  Debugging: {
    /**
     * The latest message sent and still waiting for a reply.
     */
    latestSent: undefined,

    /**
     * The latest reply received, or null if we are waiting for a reply.
     */
    latestReceived: undefined,

    /**
     * Number of messages sent to the worker. This includes the
     * initial SET_DEBUG, if applicable.
     */
    messagesSent: 0,

    /**
     * Total number of messages ever queued, including the messages
     * sent.
     */
    messagesQueued: 0,

    /**
     * Number of messages received from the worker.
     */
    messagesReceived: 0,
  },

  /**
   * A timer used to automatically shut down the worker after some time.
   */
  resetTimer: null,

  /**
   * A flag indicating whether we had some activities when waiting the
   * timer and if it's not we can shut down the worker.
   */
  hasRecentActivity: false,

  /**
   * The worker to which to send requests.
   *
   * If the worker has never been created or has been reset, this is a
   * fresh worker, initialized with osfile_async_worker.js.
   *
   * @type {PromiseWorker}
   */
  get worker() {
    if (!this._worker) {
      // Either the worker has never been created or it has been
      // reset.  In either case, it is time to instantiate the worker.
      this._worker = new BasePromiseWorker("resource://gre/modules/osfile/osfile_async_worker.js");
      this._worker.log = LOG;
      this._worker.ExceptionHandlers["OS.File.Error"] = OSError.fromMsg;

      let delay = Services.prefs.getIntPref("osfile.reset_worker_delay", 0);
      if (delay) {
        this.resetTimer = setInterval(
          () => {
            if (this.hasRecentActivity) {
              this.hasRecentActivity = false;
              return;
            }
            clearInterval(this.resetTimer);
            Scheduler.kill({reset: true, shutdown: false});
          },
          delay);
      }
    }
    return this._worker;
  },

  _worker: null,

  /**
   * Restart the OS.File worker killer timer.
   */
  restartTimer: function(arg) {
    this.hasRecentActivity = true;
  },

  /**
   * Shutdown OS.File.
   *
   * @param {*} options
   * - {boolean} shutdown If |true|, reject any further request. Otherwise,
   *   further requests will resurrect the worker.
   * - {boolean} reset If |true|, instruct the worker to shutdown if this
   *   would not cause leaks. Otherwise, assume that the worker will be shutdown
   *   through some other mean.
   */
  kill: function({shutdown, reset}) {
    // Grab the kill queue to make sure that we
    // cannot be interrupted by another call to `kill`.
    let killQueue = this._killQueue;

    // Deactivate the queue, to ensure that no message is sent
    // to an obsolete worker (we reactivate it in the `finally`).
    // This needs to be done right now so that we maintain relative
    // ordering with calls to post(), etc.
    let deferred = PromiseUtils.defer();
    let savedQueue = this.queue;
    this.queue = deferred.promise;

    return this._killQueue = (async () => {

      await killQueue;
      // From this point, and until the end of the Task, we are the
      // only call to `kill`, regardless of any `yield`.

      await savedQueue;

      try {
        // Enter critical section: no yield in this block
        // (we want to make sure that we remain the only
        // request in the queue).

        if (!this.launched || this.shutdown || !this._worker) {
          // Nothing to kill
          this.shutdown = this.shutdown || shutdown;
          this._worker = null;
          return null;
        }

        // Exit critical section

        let message = ["Meta_shutdown", [reset]];

        Scheduler.latestReceived = [];
        let stack = new Error().stack;
        // Avoid loading Task.jsm if there's no task on the stack.
        if (stack.includes("/Task.jsm:"))
          stack = Task.Debugging.generateReadableStack(stack);
        Scheduler.latestSent = [Date.now(), stack, ...message];

        // Wait for result
        let resources;
        try {
          resources = await this._worker.post(...message);

          Scheduler.latestReceived = [Date.now(), message];
        } catch (ex) {
          LOG("Could not dispatch Meta_reset", ex);
          // It's most likely a programmer error, but we'll assume that
          // the worker has been shutdown, as it's less risky than the
          // opposite stance.
          resources = {openedFiles: [], openedDirectoryIterators: [], killed: true};

          Scheduler.latestReceived = [Date.now(), message, ex];
        }

        let {openedFiles, openedDirectoryIterators, killed} = resources;
        if (!reset
          && (openedFiles && openedFiles.length
            || ( openedDirectoryIterators && openedDirectoryIterators.length))) {
          // The worker still holds resources. Report them.

          let msg = "";
          if (openedFiles.length > 0) {
            msg += "The following files are still open:\n" +
              openedFiles.join("\n");
          }
          if (openedDirectoryIterators.length > 0) {
            msg += "The following directory iterators are still open:\n" +
              openedDirectoryIterators.join("\n");
          }

          LOG("WARNING: File descriptors leaks detected.\n" + msg);
        }

        // Make sure that we do not leave an invalid |worker| around.
        if (killed || shutdown) {
          this._worker = null;
        }

        this.shutdown = shutdown;

        return resources;

      } finally {
        // Resume accepting messages. If we have set |shutdown| to |true|,
        // any pending/future request will be rejected. Otherwise, any
        // pending/future request will spawn a new worker if necessary.
        deferred.resolve();
      }

    })();
  },

  /**
   * Push a task at the end of the queue.
   *
   * @param {function} code A function returning a Promise.
   * This function will be executed once all the previously
   * pushed tasks have completed.
   * @return {Promise} A promise with the same behavior as
   * the promise returned by |code|.
   */
  push: function(code) {
    let promise = this.queue.then(code);
    // By definition, |this.queue| can never reject.
    this.queue = promise.catch(() => undefined);
    // Fork |promise| to ensure that uncaught errors are reported
    return promise.then();
  },

  /**
   * Post a message to the worker thread.
   *
   * @param {string} method The name of the method to call.
   * @param {...} args The arguments to pass to the method. These arguments
   * must be clonable.
   * The last argument by convention may be an object `options`, with some of
   * the following fields:
   *   - {number|null} outSerializationDuration A parameter to be filled with
   *     duration of the `this.worker.post` method.
   * @return {Promise} A promise conveying the result/error caused by
   * calling |method| with arguments |args|.
   */
  post: function post(method, args = undefined, closure = undefined) {
    if (this.shutdown) {
      LOG("OS.File is not available anymore. The following request has been rejected.",
        method, args);
      return Promise.reject(new Error("OS.File has been shut down. Rejecting post to " + method));
    }
    let firstLaunch = !this.launched;
    this.launched = true;

    if (firstLaunch && SharedAll.Config.DEBUG) {
      // If we have delayed sending SET_DEBUG, do it now.
      this.worker.post("SET_DEBUG", [true]);
      Scheduler.Debugging.messagesSent++;
    }

    Scheduler.Debugging.messagesQueued++;
    return this.push(async () => {
      if (this.shutdown) {
	LOG("OS.File is not available anymore. The following request has been rejected.",
	  method, args);
	throw new Error("OS.File has been shut down. Rejecting request to " + method);
      }

      // Update debugging information. As |args| may be quite
      // expensive, we only keep a shortened version of it.
      Scheduler.Debugging.latestReceived = null;
      Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(args)];

      // Don't kill the worker just yet
      Scheduler.restartTimer();

      // The last object inside the args may be an options object.
      let options = null;
      if (args && args.length >= 1 && typeof args[args.length-1] === "object") {
        options = args[args.length - 1];
      }

      let reply;
      try {
        try {
          Scheduler.Debugging.messagesSent++;
          Scheduler.Debugging.latestSent = Scheduler.Debugging.latestSent.slice(0, 2);
          let serializationStartTimeMs = Date.now();
          reply = await this.worker.post(method, args, closure);
          let serializationEndTimeMs = Date.now();
          Scheduler.Debugging.latestReceived = [Date.now(), summarizeObject(reply)];

          // There were no options for recording the serialization duration.
          if (options && "outSerializationDuration" in options) {
            // The difference might be negative for very fast operations, since Date.now() may not be monotonic.
            let serializationDurationMs = Math.max(0, serializationEndTimeMs - serializationStartTimeMs);

            if (typeof options.outSerializationDuration === "number") {
              options.outSerializationDuration += serializationDurationMs;
            } else {
              options.outSerializationDuration = serializationDurationMs;
            }
          }
          return reply;
        } finally {
          Scheduler.Debugging.messagesReceived++;
        }
      } catch (error) {
        Scheduler.Debugging.latestReceived = [Date.now(), error.message, error.fileName, error.lineNumber];
        throw error;
      } finally {
        if (firstLaunch) {
          Scheduler._updateTelemetry();
        }
        Scheduler.restartTimer();
      }
    });
  },

  /**
   * Post Telemetry statistics.
   *
   * This is only useful on first launch.
   */
  _updateTelemetry: function() {
    let worker = this.worker;
    let workerTimeStamps = worker.workerTimeStamps;
    if (!workerTimeStamps) {
      // If the first call to OS.File results in an uncaught errors,
      // the timestamps are absent. As this case is a developer error,
      // let's not waste time attempting to extract telemetry from it.
      return;
    }
    let HISTOGRAM_LAUNCH = Services.telemetry.getHistogramById("OSFILE_WORKER_LAUNCH_MS");
    HISTOGRAM_LAUNCH.add(worker.workerTimeStamps.entered - worker.launchTimeStamp);

    let HISTOGRAM_READY = Services.telemetry.getHistogramById("OSFILE_WORKER_READY_MS");
    HISTOGRAM_READY.add(worker.workerTimeStamps.loaded - worker.launchTimeStamp);
  }
};

const PREF_OSFILE_LOG = "toolkit.osfile.log";
const PREF_OSFILE_LOG_REDIRECT = "toolkit.osfile.log.redirect";

/**
 * Safely read a PREF_OSFILE_LOG preference.
 * Returns a value read or, in case of an error, oldPref or false.
 *
 * @param bool oldPref
 *        An optional value that the DEBUG flag was set to previously.
 */
function readDebugPref(prefName, oldPref = false) {
  // If neither pref nor oldPref were set, default it to false.
  return Services.prefs.getBoolPref(prefName, oldPref);
};

/**
 * Listen to PREF_OSFILE_LOG changes and update gShouldLog flag
 * appropriately.
 */
Services.prefs.addObserver(PREF_OSFILE_LOG,
  function prefObserver(aSubject, aTopic, aData) {
    SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, SharedAll.Config.DEBUG);
    if (Scheduler.launched) {
      // Don't start the worker just to set this preference.
      Scheduler.post("SET_DEBUG", [SharedAll.Config.DEBUG]);
    }
  });
SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, false);

Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
  function prefObserver(aSubject, aTopic, aData) {
    SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
  });
SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false);


/**
 * If |true|, use the native implementaiton of OS.File methods
 * whenever possible. Otherwise, force the use of the JS version.
 */
var nativeWheneverAvailable = true;
const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
Services.prefs.addObserver(PREF_OSFILE_NATIVE,
  function prefObserver(aSubject, aTopic, aData) {
    nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
  });


// Update worker's DEBUG flag if it's true.
// Don't start the worker just for this, though.
if (SharedAll.Config.DEBUG && Scheduler.launched) {
  Scheduler.post("SET_DEBUG", [true]);
}

// Observer topics used for monitoring shutdown
const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";

// Preference used to configure test shutdown observer.
const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
  "toolkit.osfile.test.shutdown.observer";

AsyncShutdown.webWorkersShutdown.addBlocker(
  "OS.File: flush pending requests, warn about unclosed files, shut down service.",
  async function() {
    // Give clients a last chance to enqueue requests.
    await Barriers.shutdown.wait({crashAfterMS: null});

    // Wait until all requests are complete and kill the worker.
    await Scheduler.kill({reset: false, shutdown: true});
  },
  () => {
    let details = Barriers.getDetails();
    details.clients = Barriers.shutdown.state;
    return details;
  }
);


// Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or
// disable the test shutdown event observer.
// Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset.
// Note: This is meant to be used for testing purposes only.
Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
  function prefObserver() {
    // The temporary phase topic used to trigger the unclosed
    // phase warning.
    let TOPIC = Services.prefs.getCharPref(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
                                           "");
    if (TOPIC) {
      // Generate a phase, add a blocker.
      // Note that this can work only if AsyncShutdown itself has been
      // configured for testing by the testsuite.
      let phase = AsyncShutdown._getPhase(TOPIC);
      phase.addBlocker(
        "(for testing purposes) OS.File: warn about unclosed files",
        () => Scheduler.kill({shutdown: false, reset: false})
      );
    }
  });

/**
 * Representation of a file, with asynchronous methods.
 *
 * @param {*} fdmsg The _message_ representing the platform-specific file
 * handle.
 *
 * @constructor
 */
var File = function File(fdmsg) {
  // FIXME: At the moment, |File| does not close on finalize
  // (see bug 777715)
  this._fdmsg = fdmsg;
  this._closeResult = null;
  this._closed = null;
};


File.prototype = {
  /**
   * Close a file asynchronously.
   *
   * This method is idempotent.
   *
   * @return {promise}
   * @resolves {null}
   * @rejects {OS.File.Error}
   */
  close: function close() {
    if (this._fdmsg != null) {
      let msg = this._fdmsg;
      this._fdmsg = null;
      return this._closeResult =
        Scheduler.post("File_prototype_close", [msg], this);
    }
    return this._closeResult;
  },

  /**
   * Fetch information about the file.
   *
   * @return {promise}
   * @resolves {OS.File.Info} The latest information about the file.
   * @rejects {OS.File.Error}
   */
  stat: function stat() {
    return Scheduler.post("File_prototype_stat", [this._fdmsg], this).then(
      File.Info.fromMsg
    );
  },

  /**
   * Write bytes from a buffer to this file.
   *
   * Note that, by default, this function may perform several I/O
   * operations to ensure that the buffer is fully written.
   *
   * @param {Typed array | C pointer} buffer The buffer in which the
   * the bytes are stored. The buffer must be large enough to
   * accomodate |bytes| bytes. Using the buffer before the operation
   * is complete is a BAD IDEA.
   * @param {*=} options Optionally, an object that may contain the
   * following fields:
   * - {number} bytes The number of |bytes| to write from the buffer. If
   * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
   * if |buffer| is a C pointer.
   *
   * @return {number} The number of bytes actually written.
   */
  write: function write(buffer, options = {}) {
    // If |buffer| is a typed array and there is no |bytes| options,
    // we need to extract the |byteLength| now, as it will be lost
    // by communication.
    // Options might be a nullish value, so better check for that before using
    // the |in| operator.
    if (isTypedArray(buffer) && !(options && "bytes" in options)) {
      // Preserve reference to option |outExecutionDuration|, |outSerializationDuration|, if it is passed.
      options = clone(options, ["outExecutionDuration", "outSerializationDuration"]);
      options.bytes = buffer.byteLength;
    }
    return Scheduler.post("File_prototype_write",
      [this._fdmsg,
       Type.void_t.in_ptr.toMsg(buffer),
       options],
       buffer/*Ensure that |buffer| is not gc-ed*/);
  },

  /**
   * Read bytes from this file to a new buffer.
   *
   * @param {number=} bytes If unspecified, read all the remaining bytes from
   * this file. If specified, read |bytes| bytes, or less if the file does not
   * contain that many bytes.
   * @param {JSON} options
   * @return {promise}
   * @resolves {Uint8Array} An array containing the bytes read.
   */
  read: function read(nbytes, options = {}) {
    let promise = Scheduler.post("File_prototype_read",
      [this._fdmsg,
       nbytes, options]);
    return promise.then(
      function onSuccess(data) {
        return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
      });
  },

  /**
   * Return the current position in the file, as bytes.
   *
   * @return {promise}
   * @resolves {number} The current position in the file,
   * as a number of bytes since the start of the file.
   */
  getPosition: function getPosition() {
    return Scheduler.post("File_prototype_getPosition",
      [this._fdmsg]);
  },

  /**
   * Set the current position in the file, as bytes.
   *
   * @param {number} pos A number of bytes.
   * @param {number} whence The reference position in the file,
   * which may be either POS_START (from the start of the file),
   * POS_END (from the end of the file) or POS_CUR (from the
   * current position in the file).
   *
   * @return {promise}
   */
  setPosition: function setPosition(pos, whence) {
    return Scheduler.post("File_prototype_setPosition",
      [this._fdmsg, pos, whence]);
  },

  /**
   * Flushes the file's buffers and causes all buffered data
   * to be written.
   * Disk flushes are very expensive and therefore should be used carefully,
   * sparingly and only in scenarios where it is vital that data survives
   * system crashes. Even though the function will be executed off the
   * main-thread, it might still affect the overall performance of any running
   * application.
   *
   * @return {promise}
   */
  flush: function flush() {
    return Scheduler.post("File_prototype_flush",
      [this._fdmsg]);
  },

  /**
   * Set the file's access permissions.  This does nothing on Windows.
   *
   * This operation is likely to fail if applied to a file that was
   * not created by the currently running program (more precisely,
   * if it was created by a program running under a different OS-level
   * user account).  It may also fail, or silently do nothing, if the
   * filesystem containing the file does not support access permissions.
   *
   * @param {*=} options Object specifying the requested permissions:
   *
   * - {number} unixMode The POSIX file mode to set on the file.  If omitted,
   *  the POSIX file mode is reset to the default used by |OS.file.open|.  If
   *  specified, the permissions will respect the process umask as if they
   *  had been specified as arguments of |OS.File.open|, unless the
   *  |unixHonorUmask| parameter tells otherwise.
   * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
   *  modified by the process umask, as |OS.File.open| would have done.  If
   *  false, the exact value of |unixMode| will be applied.
   */
  setPermissions: function setPermissions(options = {}) {
    return Scheduler.post("File_prototype_setPermissions",
                          [this._fdmsg, options]);
  }
};


if (SharedAll.Constants.Sys.Name != "Android" && SharedAll.Constants.Sys.Name != "Gonk") {
   /**
   * Set the last access and modification date of the file.
   * The time stamp resolution is 1 second at best, but might be worse
   * depending on the platform.
   *
   * WARNING: This method is not implemented on Android/B2G. On Android/B2G,
   * you should use File.setDates instead.
   *
   * @return {promise}
   * @rejects {TypeError}
   * @rejects {OS.File.Error}
   */
  File.prototype.setDates = function(accessDate, modificationDate) {
    return Scheduler.post("File_prototype_setDates",
      [this._fdmsg, accessDate, modificationDate], this);
  };
}


/**
 * Open a file asynchronously.
 *
 * @return {promise}
 * @resolves {OS.File}
 * @rejects {OS.Error}
 */
File.open = function open(path, mode, options) {
  return Scheduler.post(
    "open", [Type.path.toMsg(path), mode, options],
    path
  ).then(
    function onSuccess(msg) {
      return new File(msg);
    }
  );
};

/**
 * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
 *
 * @param {string} path The path to the file.
 * @param {*=} options Additional options for file opening. This
 * implementation interprets the following fields:
 *
 * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
 *  If |false| use HEX numbers ie: filename-A65BC0.ext
 * - {number} maxReadableNumber Used to limit the amount of tries after a failed
 *  file creation. Default is 20.
 *
 * @return {Object} contains A file object{file} and the path{path}.
 * @throws {OS.File.Error} If the file could not be opened.
 */
File.openUnique = function openUnique(path, options) {
  return Scheduler.post(
      "openUnique", [Type.path.toMsg(path), options],
      path
    ).then(
    function onSuccess(msg) {
      return {
        path: msg.path,
        file: new File(msg.file)
      };
    }
  );
};

/**
 * Get the information on the file.
 *
 * @return {promise}
 * @resolves {OS.File.Info}
 * @rejects {OS.Error}
 */
File.stat = function stat(path, options) {
  return Scheduler.post(
    "stat", [Type.path.toMsg(path), options],
    path).then(File.Info.fromMsg);
};


/**
 * Set the last access and modification date of the file.
 * The time stamp resolution is 1 second at best, but might be worse
 * depending on the platform.
 *
 * @return {promise}
 * @rejects {TypeError}
 * @rejects {OS.File.Error}
 */
File.setDates = function setDates(path, accessDate, modificationDate) {
  return Scheduler.post("setDates",
                        [Type.path.toMsg(path), accessDate, modificationDate],
                        this);
};

/**
 * Set the file's access permissions.  This does nothing on Windows.
 *
 * This operation is likely to fail if applied to a file that was
 * not created by the currently running program (more precisely,
 * if it was created by a program running under a different OS-level
 * user account).  It may also fail, or silently do nothing, if the
 * filesystem containing the file does not support access permissions.
 *
 * @param {string} path The path to the file.
 * @param {*=} options Object specifying the requested permissions:
 *
 * - {number} unixMode The POSIX file mode to set on the file.  If omitted,
 *  the POSIX file mode is reset to the default used by |OS.file.open|.  If
 *  specified, the permissions will respect the process umask as if they
 *  had been specified as arguments of |OS.File.open|, unless the
 *  |unixHonorUmask| parameter tells otherwise.
 * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
 *  modified by the process umask, as |OS.File.open| would have done.  If
 *  false, the exact value of |unixMode| will be applied.
 */
File.setPermissions = function setPermissions(path, options = {}) {
  return Scheduler.post("setPermissions",
                        [Type.path.toMsg(path), options]);
};

/**
 * Fetch the current directory
 *
 * @return {promise}
 * @resolves {string} The current directory, as a path usable with OS.Path
 * @rejects {OS.Error}
 */
File.getCurrentDirectory = function getCurrentDirectory() {
  return Scheduler.post(
    "getCurrentDirectory"
  ).then(Type.path.fromMsg);
};

/**
 * Change the current directory
 *
 * @param {string} path The OS-specific path to the current directory.
 * You should use the methods of OS.Path and the constants of OS.Constants.Path
 * to build OS-specific paths in a portable manner.
 *
 * @return {promise}
 * @resolves {null}
 * @rejects {OS.Error}
 */
File.setCurrentDirectory = function setCurrentDirectory(path) {
  return Scheduler.post(
    "setCurrentDirectory", [Type.path.toMsg(path)], path
  );
};

/**
 * Copy a file to a destination.
 *
 * @param {string} sourcePath The platform-specific path at which
 * the file may currently be found.
 * @param {string} destPath The platform-specific path at which the
 * file should be copied.
 * @param {*=} options An object which may contain the following fields:
 *
 * @option {bool} noOverwrite - If true, this function will fail if
 * a file already exists at |destPath|. Otherwise, if this file exists,
 * it will be erased silently.
 *
 * @rejects {OS.File.Error} In case of any error.
 *
 * General note: The behavior of this function is defined only when
 * it is called on a single file. If it is called on a directory, the
 * behavior is undefined and may not be the same across all platforms.
 *
 * General note: The behavior of this function with respect to metadata
 * is unspecified. Metadata may or may not be copied with the file. The
 * behavior may not be the same across all platforms.
*/
File.copy = function copy(sourcePath, destPath, options) {
  return Scheduler.post("copy", [Type.path.toMsg(sourcePath),
    Type.path.toMsg(destPath), options], [sourcePath, destPath]);
};

/**
 * Move a file to a destination.
 *
 * @param {string} sourcePath The platform-specific path at which
 * the file may currently be found.
 * @param {string} destPath The platform-specific path at which the
 * file should be moved.
 * @param {*=} options An object which may contain the following fields:
 *
 * @option {bool} noOverwrite - If set, this function will fail if
 * a file already exists at |destPath|. Otherwise, if this file exists,
 * it will be erased silently.
 *
 * @returns {Promise}
 * @rejects {OS.File.Error} In case of any error.
 *
 * General note: The behavior of this function is defined only when
 * it is called on a single file. If it is called on a directory, the
 * behavior is undefined and may not be the same across all platforms.
 *
 * General note: The behavior of this function with respect to metadata
 * is unspecified. Metadata may or may not be moved with the file. The
 * behavior may not be the same across all platforms.
 */
File.move = function move(sourcePath, destPath, options) {
  return Scheduler.post("move", [Type.path.toMsg(sourcePath),
    Type.path.toMsg(destPath), options], [sourcePath, destPath]);
};

/**
 * Create a symbolic link to a source.
 *
 * @param {string} sourcePath The platform-specific path to which
 * the symbolic link should point.
 * @param {string} destPath The platform-specific path at which the
 * symbolic link should be created.
 *
 * @returns {Promise}
 * @rejects {OS.File.Error} In case of any error.
 */
if (!SharedAll.Constants.Win) {
  File.unixSymLink = function unixSymLink(sourcePath, destPath) {
    return Scheduler.post("unixSymLink", [Type.path.toMsg(sourcePath),
      Type.path.toMsg(destPath)], [sourcePath, destPath]);
  };
}

/**
 * Gets the number of bytes available on disk to the current user.
 *
 * @param {string} Platform-specific path to a directory on the disk to
 * query for free available bytes.
 *
 * @return {number} The number of bytes available for the current user.
 * @throws {OS.File.Error} In case of any error.
 */
File.getAvailableFreeSpace = function getAvailableFreeSpace(sourcePath) {
  return Scheduler.post("getAvailableFreeSpace",
    [Type.path.toMsg(sourcePath)], sourcePath
  ).then(Type.uint64_t.fromMsg);
};

/**
 * Remove an empty directory.
 *
 * @param {string} path The name of the directory to remove.
 * @param {*=} options Additional options.
 *   - {bool} ignoreAbsent If |true|, do not fail if the
 *     directory does not exist yet.
 */
File.removeEmptyDir = function removeEmptyDir(path, options) {
  return Scheduler.post("removeEmptyDir",
    [Type.path.toMsg(path), options], path);
};

/**
 * Remove an existing file.
 *
 * @param {string} path The name of the file.
 * @param {*=} options Additional options.
 *   - {bool} ignoreAbsent If |false|, throw an error if the file does
 *     not exist. |true| by default.
 *
 * @throws {OS.File.Error} In case of I/O error.
 */
File.remove = function remove(path, options) {
  return Scheduler.post("remove",
    [Type.path.toMsg(path), options], path);
};



/**
 * Create a directory and, optionally, its parent directories.
 *
 * @param {string} path The name of the directory.
 * @param {*=} options Additional options.
 *
 * - {string} from If specified, the call to |makeDir| creates all the
 * ancestors of |path| that are descendants of |from|. Note that |path|
 * must be a descendant of |from|, and that |from| and its existing
 * subdirectories present in |path|  must be user-writeable.
 * Example:
 *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
 *  creates directories profileDir/foo, profileDir/foo/bar
 * - {bool} ignoreExisting If |false|, throw an error if the directory
 * already exists. |true| by default. Ignored if |from| is specified.
 * - {number} unixMode Under Unix, if specified, a file creation mode,
 * as per libc function |mkdir|. If unspecified, dirs are
 * created with a default mode of 0700 (dir is private to
 * the user, the user can read, write and execute). Ignored under Windows
 * or if the file system does not support file creation modes.
 * - {C pointer} winSecurity Under Windows, if specified, security
 * attributes as per winapi function |CreateDirectory|. If
 * unspecified, use the default security descriptor, inherited from
 * the parent directory. Ignored under Unix or if the file system
 * does not support security descriptors.
 */
File.makeDir = function makeDir(path, options) {
  return Scheduler.post("makeDir",
    [Type.path.toMsg(path), options], path);
};

/**
 * Return the contents of a file
 *
 * @param {string} path The path to the file.
 * @param {number=} bytes Optionally, an upper bound to the number of bytes
 * to read. DEPRECATED - please use options.bytes instead.
 * @param {JSON} options Additional options.
 * - {boolean} sequential A flag that triggers a population of the page cache
 * with data from a file so that subsequent reads from that file would not
 * block on disk I/O. If |true| or unspecified, inform the system that the
 * contents of the file will be read in order. Otherwise, make no such
 * assumption. |true| by default.
 * - {number} bytes An upper bound to the number of bytes to read.
 * - {string} compression If "lz4" and if the file is compressed using the lz4
 * compression algorithm, decompress the file contents on the fly.
 *
 * @resolves {Uint8Array} A buffer holding the bytes
 * read from the file.
 */
File.read = function read(path, bytes, options = {}) {
  if (typeof bytes == "object") {
    // Passing |bytes| as an argument is deprecated.
    // We should now be passing it as a field of |options|.
    options = bytes || {};
  } else {
    options = clone(options, ["outExecutionDuration", "outSerializationDuration"]);
    if (typeof bytes != "undefined") {
      options.bytes = bytes;
    }
  }

  if (options.compression || !nativeWheneverAvailable) {
    // We need to use the JS implementation.
    let promise = Scheduler.post("read",
      [Type.path.toMsg(path), bytes, options], path);
    return promise.then(
      function onSuccess(data) {
        if (typeof data == "string") {
          return data;
        }
        return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
      });
  }

  // Otherwise, use the native implementation.
  return Scheduler.push(() => Native.read(path, options));
};

/**
 * Find outs if a file exists.
 *
 * @param {string} path The path to the file.
 *
 * @return {bool} true if the file exists, false otherwise.
 */
File.exists = function exists(path) {
  return Scheduler.post("exists",
    [Type.path.toMsg(path)], path);
};

/**
 * Write a file, atomically.
 *
 * By opposition to a regular |write|, this operation ensures that,
 * until the contents are fully written, the destination file is
 * not modified.
 *
 * Limitation: In a few extreme cases (hardware failure during the
 * write, user unplugging disk during the write, etc.), data may be
 * corrupted. If your data is user-critical (e.g. preferences,
 * application data, etc.), you may wish to consider adding options
 * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
 * detailed below. Note that no combination of options can be
 * guaranteed to totally eliminate the risk of corruption.
 *
 * @param {string} path The path of the file to modify.
 * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
 * @param {*=} options Optionally, an object determining the behavior
 * of this function. This object may contain the following fields:
 * - {number} bytes The number of bytes to write. If unspecified,
 * |buffer.byteLength|. Required if |buffer| is a C pointer.
 * - {string} tmpPath If |null| or unspecified, write all data directly
 * to |path|. If specified, write all data to a temporary file called
 * |tmpPath| and, once this write is complete, rename the file to
 * replace |path|. Performing this additional operation is a little
 * slower but also a little safer.
 * - {bool} noOverwrite - If set, this function will fail if a file already
 * exists at |path|.
 * - {bool} flush - If |false| or unspecified, return immediately once the
 * write is complete. If |true|, before writing, force the operating system
 * to write its internal disk buffers to the disk. This is considerably slower
 * (not just for the application but for the whole system) but also safer:
 * if the system shuts down improperly (typically due to a kernel freeze
 * or a power failure) or if the device is disconnected before the buffer
 * is flushed, the file has more chances of not being corrupted.
 * - {string} backupTo - If specified, backup the destination file as |backupTo|.
 * Note that this function renames the destination file before overwriting it.
 * If the process or the operating system freezes or crashes
 * during the short window between these operations,
 * the destination file will have been moved to its backup.
 *
 * @return {promise}
 * @resolves {number} The number of bytes actually written.
 */
File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
  // Copy |options| to avoid modifying the original object but preserve the
  // reference to |outExecutionDuration|, |outSerializationDuration| option if it is passed.
  options = clone(options, ["outExecutionDuration", "outSerializationDuration"]);
  // As options.tmpPath is a path, we need to encode it as |Type.path| message
  if ("tmpPath" in options) {
    options.tmpPath = Type.path.toMsg(options.tmpPath);
  };
  if (isTypedArray(buffer) && (!("bytes" in options))) {
    options.bytes = buffer.byteLength;
  };
  let refObj = {};
  TelemetryStopwatch.start("OSFILE_WRITEATOMIC_JANK_MS", refObj);
  let promise = Scheduler.post("writeAtomic",
    [Type.path.toMsg(path),
     Type.void_t.in_ptr.toMsg(buffer),
     options], [options, buffer, path]);
  TelemetryStopwatch.finish("OSFILE_WRITEATOMIC_JANK_MS", refObj);
  return promise;
};

File.removeDir = function(path, options = {}) {
  return Scheduler.post("removeDir",
    [Type.path.toMsg(path), options], path);
};

/**
 * Information on a file, as returned by OS.File.stat or
 * OS.File.prototype.stat
 *
 * @constructor
 */
File.Info = function Info(value) {
  // Note that we can't just do this[k] = value[k] because our
  // prototype defines getters for all of these fields.
  for (let k in value) {
    if (k != "creationDate") {
      Object.defineProperty(this, k, {value: value[k]});
    }
  }
  Object.defineProperty(this, "_deprecatedCreationDate", {value: value["creationDate"]});
};
File.Info.prototype = SysAll.AbstractInfo.prototype;

// Deprecated
Object.defineProperty(File.Info.prototype, "creationDate", {
  get: function creationDate() {
    let {Deprecated} = Cu.import("resource://gre/modules/Deprecated.jsm", {});
    Deprecated.warning("Field 'creationDate' is deprecated.", "https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File.Info#Cross-platform_Attributes");
    return this._deprecatedCreationDate;
  }
});

File.Info.fromMsg = function fromMsg(value) {
  return new File.Info(value);
};

/**
 * Get worker's current DEBUG flag.
 * Note: This is used for testing purposes.
 */
File.GET_DEBUG = function GET_DEBUG() {
  return Scheduler.post("GET_DEBUG");
};

/**
 * Iterate asynchronously through a directory
 *
 * @constructor
 */
var DirectoryIterator = function DirectoryIterator(path, options) {
  /**
   * Open the iterator on the worker thread
   *
   * @type {Promise}
   * @resolves {*} A message accepted by the methods of DirectoryIterator
   * in the worker thread
   * @rejects {StopIteration} If all entries have already been visited
   * or the iterator has been closed.
   */
  this.__itmsg = Scheduler.post(
    "new_DirectoryIterator", [Type.path.toMsg(path), options],
    path
  );
  this._isClosed = false;
};
DirectoryIterator.prototype = {
  iterator: function () {
    return this;
  },
  __iterator__: function () {
    return this;
  },

  // Once close() is called, _itmsg should reject with a
  // StopIteration. However, we don't want to create the promise until
  // it's needed because it might never be used. In that case, we
  // would get a warning on the console.
  get _itmsg() {
    if (!this.__itmsg) {
      this.__itmsg = Promise.reject(StopIteration);
    }
    return this.__itmsg;
  },

  /**
   * Determine whether the directory exists.
   *
   * @resolves {boolean}
   */
  exists: function exists() {
    return this._itmsg.then(
      function onSuccess(iterator) {
        return Scheduler.post("DirectoryIterator_prototype_exists", [iterator]);
      }
    );
  },
  /**
   * Get the next entry in the directory.
   *
   * @return {Promise}
   * @resolves {OS.File.Entry}
   * @rejects {StopIteration} If all entries have already been visited.
   */
  next: function next() {
    let self = this;
    let promise = this._itmsg;

    // Get the iterator, call _next
    promise = promise.then(
      function withIterator(iterator) {
        return self._next(iterator);
      });

    return promise;
  },
  /**
   * Get several entries at once.
   *
   * @param {number=} length If specified, the number of entries
   * to return. If unspecified, return all remaining entries.
   * @return {Promise}
   * @resolves {Array} An array containing the |length| next entries.
   */
  nextBatch: function nextBatch(size) {
    if (this._isClosed) {
      return Promise.resolve([]);
    }
    let promise = this._itmsg;
    promise = promise.then(
      function withIterator(iterator) {
        return Scheduler.post("DirectoryIterator_prototype_nextBatch", [iterator, size]);
      });
    promise = promise.then(
      function withEntries(array) {
        return array.map(DirectoryIterator.Entry.fromMsg);
      });
    return promise;
  },
  /**
   * Apply a function to all elements of the directory sequentially.
   *
   * @param {Function} cb This function will be applied to all entries
   * of the directory. It receives as arguments
   *  - the OS.File.Entry corresponding to the entry;
   *  - the index of the entry in the enumeration;
   *  - the iterator itself - return |iterator.close()| to stop the loop.
   *
   * If the callback returns a promise, iteration waits until the
   * promise is resolved before proceeding.
   *
   * @return {Promise} A promise resolved once the loop has reached
   * its end.
   */
  forEach: function forEach(cb, options) {
    if (this._isClosed) {
      return Promise.resolve();
    }

    let self = this;
    let position = 0;
    let iterator;

    // Grab iterator
    let promise = this._itmsg.then(
      function(aIterator) {
        iterator = aIterator;
      }
    );

    // Then iterate
    let loop = function loop() {
      if (self._isClosed) {
        return Promise.resolve();
      }
      return self._next(iterator).then(
        function onSuccess(value) {
          return Promise.resolve(cb(value, position++, self)).then(loop);
        },
        function onFailure(reason) {
          if (reason == StopIteration) {
            return;
          }
          throw reason;
        }
      );
    };

    return promise.then(loop);
  },
  /**
   * Auxiliary method: fetch the next item
   *
   * @rejects {StopIteration} If all entries have already been visited
   * or the iterator has been closed.
   */
  _next: function _next(iterator) {
    if (this._isClosed) {
      return this._itmsg;
    }
    let self = this;
    let promise = Scheduler.post("DirectoryIterator_prototype_next", [iterator]);
    promise = promise.then(
      DirectoryIterator.Entry.fromMsg,
      function onReject(reason) {
        if (reason == StopIteration) {
          self.close();
          throw StopIteration;
        }
        throw reason;
      });
    return promise;
  },
  /**
   * Close the iterator
   */
  close: function close() {
    if (this._isClosed) {
      return Promise.resolve();
    }
    this._isClosed = true;
    let self = this;
    return this._itmsg.then(
      function withIterator(iterator) {
        // Set __itmsg to null so that the _itmsg getter returns a
        // rejected StopIteration promise if it's ever used.
        self.__itmsg = null;
        return Scheduler.post("DirectoryIterator_prototype_close", [iterator]);
      }
    );
  }
};

DirectoryIterator.Entry = function Entry(value) {
  return value;
};
DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);

DirectoryIterator.Entry.fromMsg = function fromMsg(value) {
  return new DirectoryIterator.Entry(value);
};

File.resetWorker = function() {
  return (async function() {
    let resources = await Scheduler.kill({shutdown: false, reset: true});
    if (resources && !resources.killed) {
        throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
    }
  })();
};

// Constants
File.POS_START = SysAll.POS_START;
File.POS_CURRENT = SysAll.POS_CURRENT;
File.POS_END = SysAll.POS_END;

// Exports
File.Error = OSError;
File.DirectoryIterator = DirectoryIterator;

this.OS = {};
this.OS.File = File;
this.OS.Constants = SharedAll.Constants;
this.OS.Shared = {
  LOG: SharedAll.LOG,
  Type: SysAll.Type,
  get DEBUG() {
    return SharedAll.Config.DEBUG;
  },
  set DEBUG(x) {
    return SharedAll.Config.DEBUG = x;
  }
};
Object.freeze(this.OS.Shared);
this.OS.Path = Path;

// Returns a resolved promise when all the queued operation have been completed.
Object.defineProperty(OS.File, "queue", {
  get: function() {
    return Scheduler.queue;
  }
});

// `true` if this is a content process, `false` otherwise.
// It would be nicer to go through `Services.appInfo`, but some tests need to be
// able to replace that field with a custom implementation before it is first
// called.
const isContent = Components.classes["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;

/**
 * Shutdown barriers, to let clients register to be informed during shutdown.
 */
var Barriers = {
  shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"),
  /**
   * Return the shutdown state of OS.File
   */
  getDetails: function() {
    let result = {
      launched: Scheduler.launched,
      shutdown: Scheduler.shutdown,
      worker: !!Scheduler._worker,
      pendingReset: !!Scheduler.resetTimer,
      latestSent: Scheduler.Debugging.latestSent,
      latestReceived: Scheduler.Debugging.latestReceived,
      messagesSent: Scheduler.Debugging.messagesSent,
      messagesReceived: Scheduler.Debugging.messagesReceived,
      messagesQueued: Scheduler.Debugging.messagesQueued,
      DEBUG: SharedAll.Config.DEBUG,
    };
    // Convert dates to strings for better readability
    for (let key of ["latestSent", "latestReceived"]) {
      if (result[key] && typeof result[key][0] == "number") {
        result[key][0] = Date(result[key][0]);
      }
    }
    return result;
  }
};

function setupShutdown(phaseName) {
  Barriers[phaseName] = new AsyncShutdown.Barrier(`OS.File: Waiting for clients before ${phaseName}`),
  File[phaseName] = Barriers[phaseName].client;

  // Auto-flush OS.File during `phaseName`. This ensures that any I/O
  // that has been queued *before* `phaseName` is properly completed.
  // To ensure that I/O queued *during* `phaseName` change is completed,
  // clients should register using AsyncShutdown.addBlocker.
  AsyncShutdown[phaseName].addBlocker(
    `OS.File: flush I/O queued before ${phaseName}`,
    async function() {
      // Give clients a last chance to enqueue requests.
      await Barriers[phaseName].wait({crashAfterMS: null});

      // Wait until all currently enqueued requests are completed.
      await Scheduler.queue;
    },
    () => {
      let details = Barriers.getDetails();
      details.clients = Barriers[phaseName].state;
      return details;
    }
  );
}

// profile-before-change only exists in the parent, and OS.File should
// not be used in the child process anyways.
if (!isContent) {
  setupShutdown("profileBeforeChange")
}
File.shutdown = Barriers.shutdown.client;
PK
!<[2[2%modules/osfile/osfile_async_worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


if (this.Components) {
  throw new Error("This worker can only be loaded from a worker thread");
}

// Worker thread for osfile asynchronous front-end

(function(exports) {
  "use strict";

  // Timestamps, for use in Telemetry.
  // The object is set to |null| once it has been sent
  // to the main thread.
  let timeStamps = {
    entered: Date.now(),
    loaded: null
  };

  importScripts("resource://gre/modules/osfile.jsm");

  let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
  let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
  let LOG = SharedAll.LOG.bind(SharedAll, "Agent");

  let worker = new PromiseWorker.AbstractWorker();
  worker.dispatch = function(method, args = []) {
    return Agent[method](...args);
  },
  worker.log = LOG;
  worker.postMessage = function(message, ...transfers) {
    if (timeStamps) {
      message.timeStamps = timeStamps;
      timeStamps = null;
    }
    self.postMessage(message, ...transfers);
  };
  worker.close = function() {
    self.close();
  };
  let Meta = PromiseWorker.Meta;

  self.addEventListener("message", msg => worker.handleMessage(msg));

 /**
  * A data structure used to track opened resources
  */
  let ResourceTracker = function ResourceTracker() {
   // A number used to generate ids
   this._idgen = 0;
   // A map from id to resource
   this._map = new Map();
  };
  ResourceTracker.prototype = {
   /**
    * Get a resource from its unique identifier.
    */
   get: function(id) {
     let result = this._map.get(id);
     if (result == null) {
       return result;
     }
     return result.resource;
   },
   /**
    * Remove a resource from its unique identifier.
    */
   remove: function(id) {
     if (!this._map.has(id)) {
       throw new Error("Cannot find resource id " + id);
     }
     this._map.delete(id);
   },
   /**
    * Add a resource, return a new unique identifier
    *
    * @param {*} resource A resource.
    * @param {*=} info Optional information. For debugging purposes.
    *
    * @return {*} A unique identifier. For the moment, this is a number,
    * but this might not remain the case forever.
    */
   add: function(resource, info) {
     let id = this._idgen++;
     this._map.set(id, {resource:resource, info:info});
     return id;
   },
   /**
    * Return a list of all open resources i.e. the ones still present in
    * ResourceTracker's _map.
    */
   listOpenedResources: function listOpenedResources() {
     return Array.from(this._map, ([id, resource]) => resource.info.path);
   }
  };

 /**
  * A map of unique identifiers to opened files.
  */
  let OpenedFiles = new ResourceTracker();

 /**
  * Execute a function in the context of a given file.
  *
  * @param {*} id A unique identifier, as used by |OpenFiles|.
  * @param {Function} f A function to call.
  * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception.
  * @return The return value of |f()|
  *
  * This function attempts to get the file matching |id|. If
  * the file exists, it executes |f| within the |this| set
  * to the corresponding file. Otherwise, it throws an error.
  */
  let withFile = function withFile(id, f, ignoreAbsent) {
   let file = OpenedFiles.get(id);
   if (file == null) {
     if (!ignoreAbsent) {
       throw OS.File.Error.closed("accessing file");
     }
     return undefined;
   }
   return f.call(file);
  };

  let OpenedDirectoryIterators = new ResourceTracker();
  let withDir = function withDir(fd, f, ignoreAbsent) {
   let file = OpenedDirectoryIterators.get(fd);
   if (file == null) {
     if (!ignoreAbsent) {
       throw OS.File.Error.closed("accessing directory");
     }
     return undefined;
   }
   if (!(file instanceof File.DirectoryIterator)) {
     throw new Error("file is not a directory iterator " + file.__proto__.toSource());
   }
   return f.call(file);
  };

  let Type = exports.OS.Shared.Type;

  let File = exports.OS.File;

 /**
  * The agent.
  *
  * It is in charge of performing method-specific deserialization
  * of messages, calling the function/method of OS.File and serializing
  * back the results.
  */
  let Agent = {
   // Update worker's OS.Shared.DEBUG flag message from controller.
   SET_DEBUG: function(aDEBUG) {
     SharedAll.Config.DEBUG = aDEBUG;
   },
   // Return worker's current OS.Shared.DEBUG value to controller.
   // Note: This is used for testing purposes.
   GET_DEBUG: function() {
     return SharedAll.Config.DEBUG;
   },
   /**
    * Execute shutdown sequence, returning data on leaked file descriptors.
    *
    * @param {bool} If |true|, kill the worker if this would not cause
    * leaks.
    */
   Meta_shutdown: function(kill) {
     let result = {
       openedFiles: OpenedFiles.listOpenedResources(),
       openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
       killed: false // Placeholder
     };

     // Is it safe to kill the worker?
     let safe = result.openedFiles.length == 0
           && result.openedDirectoryIterators.length == 0;
     result.killed = safe && kill;

     return new Meta(result, {shutdown: result.killed});
   },
   // Functions of OS.File
   stat: function stat(path, options) {
     return exports.OS.File.Info.toMsg(
       exports.OS.File.stat(Type.path.fromMsg(path), options));
   },
   setPermissions: function setPermissions(path, options = {}) {
     return exports.OS.File.setPermissions(Type.path.fromMsg(path), options);
   },
   setDates: function setDates(path, accessDate, modificationDate) {
     return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate,
                                     modificationDate);
   },
   getCurrentDirectory: function getCurrentDirectory() {
     return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
   },
   setCurrentDirectory: function setCurrentDirectory(path) {
     File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path));
   },
   copy: function copy(sourcePath, destPath, options) {
     return File.copy(Type.path.fromMsg(sourcePath),
       Type.path.fromMsg(destPath), options);
   },
   move: function move(sourcePath, destPath, options) {
     return File.move(Type.path.fromMsg(sourcePath),
       Type.path.fromMsg(destPath), options);
   },
   getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
     return Type.uint64_t.toMsg(
       File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
   },
   makeDir: function makeDir(path, options) {
     return File.makeDir(Type.path.fromMsg(path), options);
   },
   removeEmptyDir: function removeEmptyDir(path, options) {
     return File.removeEmptyDir(Type.path.fromMsg(path), options);
   },
   remove: function remove(path, options) {
     return File.remove(Type.path.fromMsg(path), options);
   },
   open: function open(path, mode, options) {
     let filePath = Type.path.fromMsg(path);
     let file = File.open(filePath, mode, options);
     return OpenedFiles.add(file, {
       // Adding path information to keep track of opened files
       // to report leaks when debugging.
       path: filePath
     });
   },
   openUnique: function openUnique(path, options) {
     let filePath = Type.path.fromMsg(path);
     let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options);
     let resourceId = OpenedFiles.add(openedFile.file, {
       // Adding path information to keep track of opened files
       // to report leaks when debugging.
       path: openedFile.path
     });

     return {
       path: openedFile.path,
       file: resourceId
     };
   },
   read: function read(path, bytes, options) {
     let data = File.read(Type.path.fromMsg(path), bytes, options);
     if (typeof data == "string") {
       return data;
     }
     return new Meta({
         buffer: data.buffer,
         byteOffset: data.byteOffset,
         byteLength: data.byteLength
     }, {
       transfers: [data.buffer]
     });
   },
   exists: function exists(path) {
     return File.exists(Type.path.fromMsg(path));
   },
   writeAtomic: function writeAtomic(path, buffer, options) {
     if (options.tmpPath) {
       options.tmpPath = Type.path.fromMsg(options.tmpPath);
     }
     return File.writeAtomic(Type.path.fromMsg(path),
                             Type.voidptr_t.fromMsg(buffer),
                             options
                            );
   },
   removeDir: function(path, options) {
     return File.removeDir(Type.path.fromMsg(path), options);
   },
   new_DirectoryIterator: function new_DirectoryIterator(path, options) {
     let directoryPath = Type.path.fromMsg(path);
     let iterator = new File.DirectoryIterator(directoryPath, options);
     return OpenedDirectoryIterators.add(iterator, {
       // Adding path information to keep track of opened directory
       // iterators to report leaks when debugging.
       path: directoryPath
     });
   },
   // Methods of OS.File
   File_prototype_close: function close(fd) {
     return withFile(fd,
       function do_close() {
         try {
           return this.close();
         } finally {
           OpenedFiles.remove(fd);
         }
     });
   },
   File_prototype_stat: function stat(fd) {
     return withFile(fd,
       function do_stat() {
         return exports.OS.File.Info.toMsg(this.stat());
       });
   },
   File_prototype_setPermissions: function setPermissions(fd, options = {}) {
     return withFile(fd,
       function do_setPermissions() {
         return this.setPermissions(options);
       });
   },
   File_prototype_setDates: function setDates(fd, accessTime, modificationTime) {
     return withFile(fd,
       function do_setDates() {
         return this.setDates(accessTime, modificationTime);
       });
   },
   File_prototype_read: function read(fd, nbytes, options) {
     return withFile(fd,
       function do_read() {
         let data = this.read(nbytes, options);
         return new Meta({
             buffer: data.buffer,
             byteOffset: data.byteOffset,
             byteLength: data.byteLength
         }, {
           transfers: [data.buffer]
         });
       }
     );
   },
   File_prototype_readTo: function readTo(fd, buffer, options) {
     return withFile(fd,
       function do_readTo() {
         return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
         options);
       });
   },
   File_prototype_write: function write(fd, buffer, options) {
     return withFile(fd,
       function do_write() {
         return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
         options);
       });
   },
   File_prototype_setPosition: function setPosition(fd, pos, whence) {
     return withFile(fd,
       function do_setPosition() {
         return this.setPosition(pos, whence);
       });
   },
   File_prototype_getPosition: function getPosition(fd) {
     return withFile(fd,
       function do_getPosition() {
         return this.getPosition();
       });
   },
   File_prototype_flush: function flush(fd) {
     return withFile(fd,
       function do_flush() {
         return this.flush();
       });
   },
   // Methods of OS.File.DirectoryIterator
   DirectoryIterator_prototype_next: function next(dir) {
     return withDir(dir,
       function do_next() {
         try {
           return File.DirectoryIterator.Entry.toMsg(this.next());
         } catch (x) {
           if (x == StopIteration) {
             OpenedDirectoryIterators.remove(dir);
           }
           throw x;
         }
       }, false);
   },
   DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) {
     return withDir(dir,
       function do_nextBatch() {
         let result;
         try {
           result = this.nextBatch(size);
         } catch (x) {
           OpenedDirectoryIterators.remove(dir);
           throw x;
         }
         return result.map(File.DirectoryIterator.Entry.toMsg);
       }, false);
   },
   DirectoryIterator_prototype_close: function close(dir) {
     return withDir(dir,
       function do_close() {
         this.close();
         OpenedDirectoryIterators.remove(dir);
       }, true);// ignore error to support double-closing |DirectoryIterator|
   },
   DirectoryIterator_prototype_exists: function exists(dir) {
     return withDir(dir,
       function do_exists() {
         return this.exists();
       });
   }
  };
  if (!SharedAll.Constants.Win) {
    Agent.unixSymLink = function unixSymLink(sourcePath, destPath) {
      return File.unixSymLink(Type.path.fromMsg(sourcePath),
        Type.path.fromMsg(destPath));
    };
  }

  timeStamps.loaded = Date.now();
})(this);
PK
!<2 modules/osfile/osfile_native.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Native (xpcom) implementation of key OS.File functions
 */

"use strict";

this.EXPORTED_SYMBOLS = ["read"];

var {results: Cr, utils: Cu, interfaces: Ci} = Components;

var SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {});

var SysAll = {};
if (SharedAll.Constants.Win) {
  Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
} else if (SharedAll.Constants.libc) {
  Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
} else {
  throw new Error("I am neither under Windows nor under a Posix system");
}
var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});

/**
 * The native service holding the implementation of the functions.
 */
XPCOMUtils.defineLazyServiceGetter(this,
  "Internals",
  "@mozilla.org/toolkit/osfile/native-internals;1",
  "nsINativeOSFileInternalsService");

/**
 * Native implementation of OS.File.read
 *
 * This implementation does not handle option |compression|.
 */
this.read = function(path, options = {}) {
  // Sanity check on types of options
  if ("encoding" in options && typeof options.encoding != "string") {
    return Promise.reject(new TypeError("Invalid type for option encoding"));
  }
  if ("compression" in options && typeof options.compression != "string") {
    return Promise.reject(new TypeError("Invalid type for option compression"));
  }
  if ("bytes" in options && typeof options.bytes != "number") {
    return Promise.reject(new TypeError("Invalid type for option bytes"));
  }

  return new Promise((resolve, reject) => {
    Internals.read(path,
      options,
      function onSuccess(success) {
        success.QueryInterface(Ci.nsINativeOSFileResult);
        if ("outExecutionDuration" in options) {
          options.outExecutionDuration =
            success.executionDurationMS +
            (options.outExecutionDuration || 0);
        }
        resolve(success.result);
      },
      function onError(operation, oserror) {
        reject(new SysAll.Error(operation, oserror, path));
      }
    );
  });
};
PK
!<2{MZZ+modules/osfile/osfile_shared_allthreads.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * OS.File utilities used by all threads.
 *
 * This module defines:
 * - logging;
 * - the base constants;
 * - base types and primitives for declaring new types;
 * - primitives for importing C functions;
 * - primitives for dealing with integers, pointers, typed arrays;
 * - the base class OSError;
 * - a few additional utilities.
 */

// Boilerplate used to be able to import this module both from the main
// thread and from worker threads.

// Since const is lexically scoped, hoist the
// conditionally-useful definition ourselves.
const Cu = typeof Components != "undefined" ? Components.utils : undefined;
const Ci = typeof Components != "undefined" ? Components.interfaces : undefined;
const Cc = typeof Components != "undefined" ? Components.classes : undefined;

/**
 * A constructor for messages that require transfers instead of copies.
 *
 * See BasePromiseWorker.Meta.
 *
 * @constructor
 */
var Meta;
if (typeof Components != "undefined") {
  // Global definition of |exports|, to keep everybody happy.
  // In non-main thread, |exports| is provided by the module
  // loader.
  this.exports = {};

  Cu.import("resource://gre/modules/Services.jsm", this);
  Meta = Cu.import("resource://gre/modules/PromiseWorker.jsm", {}).BasePromiseWorker.Meta;
} else {
  importScripts("resource://gre/modules/workers/require.js");
  Meta = require("resource://gre/modules/workers/PromiseWorker.js").Meta;
}

var EXPORTED_SYMBOLS = [
  "LOG",
  "clone",
  "Config",
  "Constants",
  "Type",
  "HollowStructure",
  "OSError",
  "Library",
  "declareFFI",
  "declareLazy",
  "declareLazyFFI",
  "normalizeBufferArgs",
  "projectValue",
  "isArrayBuffer",
  "isTypedArray",
  "defineLazyGetter",
  "OS" // Warning: this exported symbol will disappear
];

////////////////////// Configuration of OS.File

var Config = {
  /**
   * If |true|, calls to |LOG| are shown. Otherwise, they are hidden.
   *
   * This configuration option is controlled by preference "toolkit.osfile.log".
   */
  DEBUG: false,

  /**
   * TEST
   */
  TEST: false
};
exports.Config = Config;

////////////////////// OS Constants

if (typeof Components != "undefined") {
  // On the main thread, OS.Constants is defined by a xpcom
  // component. On other threads, it is available automatically
  Cu.import("resource://gre/modules/ctypes.jsm");
  Cc["@mozilla.org/net/osfileconstantsservice;1"].
    getService(Ci.nsIOSFileConstantsService).init();
}

exports.Constants = OS.Constants;

///////////////////// Utilities

// Define a lazy getter for a property
var defineLazyGetter = function defineLazyGetter(object, name, getter) {
  Object.defineProperty(object, name, {
    configurable: true,
    get: function lazy() {
      delete this[name];
      let value = getter.call(this);
      Object.defineProperty(object, name, {
        value: value
      });
      return value;
    }
  });
};
exports.defineLazyGetter = defineLazyGetter;


///////////////////// Logging

/**
 * The default implementation of the logger.
 *
 * The choice of logger can be overridden with Config.TEST.
 */
var gLogger;
if (typeof window != "undefined" && window.console && console.log) {
  gLogger = console.log.bind(console, "OS");
} else {
  gLogger = function(...args) {
    dump("OS " + args.join(" ") + "\n");
  };
}

/**
 * Attempt to stringify an argument into something useful for
 * debugging purposes, by using |.toString()| or |JSON.stringify|
 * if available.
 *
 * @param {*} arg An argument to be stringified if possible.
 * @return {string} A stringified version of |arg|.
 */
var stringifyArg = function stringifyArg(arg) {
  if (typeof arg === "string") {
    return arg;
  }
  if (arg && typeof arg === "object") {
    let argToString = "" + arg;

    /**
     * The only way to detect whether this object has a non-default
     * implementation of |toString| is to check whether it returns
     * '[object Object]'. Unfortunately, we cannot simply compare |arg.toString|
     * and |Object.prototype.toString| as |arg| typically comes from another
     * compartment.
     */
    if (argToString === "[object Object]") {
      return JSON.stringify(arg, function(key, value) {
        if (isTypedArray(value)) {
          return "["+ value.constructor.name + " " + value.byteOffset + " " + value.byteLength + "]";
        }
        if (isArrayBuffer(arg)) {
          return "[" + value.constructor.name + " " + value.byteLength + "]";
        }
        return value;
      });
    } else {
      return argToString;
    }
  }
  return arg;
};

var LOG = function (...args) {
  if (!Config.DEBUG) {
    // If logging is deactivated, don't log
    return;
  }

  let logFunc = gLogger;
  if (Config.TEST && typeof Components != "undefined") {
    // If _TESTING_LOGGING is set, and if we are on the main thread,
    // redirect logs to Services.console, for testing purposes
    logFunc = function logFunc(...args) {
      let message = ["TEST", "OS"].concat(args).join(" ");
      Services.console.logStringMessage(message + "\n");
    };
  }
  logFunc.apply(null, args.map(stringifyArg));
};

exports.LOG = LOG;

/**
 * Return a shallow clone of the enumerable properties of an object.
 *
 * Utility used whenever normalizing options requires making (shallow)
 * changes to an option object. The copy ensures that we do not modify
 * a client-provided object by accident.
 *
 * Note: to reference and not copy specific fields, provide an optional
 * |refs| argument containing their names.
 *
 * @param {JSON} object Options to be cloned.
 * @param {Array} refs An optional array of field names to be passed by
 * reference instead of copying.
 */
var clone = function (object, refs = []) {
  let result = {};
  // Make a reference between result[key] and object[key].
  let refer = function refer(result, key, object) {
    Object.defineProperty(result, key, {
      enumerable: true,
      get: function() {
        return object[key];
      },
      set: function(value) {
        object[key] = value;
      }
    });
  };
  for (let k in object) {
    if (refs.indexOf(k) < 0) {
      result[k] = object[k];
    } else {
      refer(result, k, object);
    }
  }
  return result;
};

exports.clone = clone;

///////////////////// Abstractions above js-ctypes

/**
 * Abstraction above js-ctypes types.
 *
 * Use values of this type to register FFI functions. In addition to the
 * usual features of js-ctypes, values of this type perform the necessary
 * transformations to ensure that C errors are handled nicely, to connect
 * resources with their finalizer, etc.
 *
 * @param {string} name The name of the type. Must be unique.
 * @param {CType} implementation The js-ctypes implementation of the type.
 *
 * @constructor
 */
function Type(name, implementation) {
  if (!(typeof name == "string")) {
    throw new TypeError("Type expects as first argument a name, got: "
                        + name);
  }
  if (!(implementation instanceof ctypes.CType)) {
    throw new TypeError("Type expects as second argument a ctypes.CType"+
                        ", got: " + implementation);
  }
  Object.defineProperty(this, "name", { value: name });
  Object.defineProperty(this, "implementation", { value: implementation });
}
Type.prototype = {
  /**
   * Serialize a value of |this| |Type| into a format that can
   * be transmitted as a message (not necessarily a string).
   *
   * In the default implementation, the method returns the
   * value unchanged.
   */
  toMsg: function default_toMsg(value) {
    return value;
  },
  /**
   * Deserialize a message to a value of |this| |Type|.
   *
   * In the default implementation, the method returns the
   * message unchanged.
   */
  fromMsg: function default_fromMsg(msg) {
    return msg;
  },
  /**
   * Import a value from C.
   *
   * In this default implementation, return the value
   * unchanged.
   */
  importFromC: function default_importFromC(value) {
    return value;
  },

  /**
   * A pointer/array used to pass data to the foreign function.
   */
  get in_ptr() {
    delete this.in_ptr;
    let ptr_t = new PtrType(
      "[in] " + this.name + "*",
      this.implementation.ptr,
      this);
    Object.defineProperty(this, "in_ptr",
    {
      get: function() {
        return ptr_t;
      }
    });
    return ptr_t;
  },

  /**
   * A pointer/array used to receive data from the foreign function.
   */
  get out_ptr() {
    delete this.out_ptr;
    let ptr_t = new PtrType(
      "[out] " + this.name + "*",
      this.implementation.ptr,
      this);
    Object.defineProperty(this, "out_ptr",
    {
      get: function() {
        return ptr_t;
      }
    });
    return ptr_t;
  },

  /**
   * A pointer/array used to both pass data to the foreign function
   * and receive data from the foreign function.
   *
   * Whenever possible, prefer using |in_ptr| or |out_ptr|, which
   * are generally faster.
   */
  get inout_ptr() {
    delete this.inout_ptr;
    let ptr_t = new PtrType(
      "[inout] " + this.name + "*",
      this.implementation.ptr,
      this);
    Object.defineProperty(this, "inout_ptr",
    {
      get: function() {
        return ptr_t;
      }
    });
    return ptr_t;
  },

  /**
   * Attach a finalizer to a type.
   */
  releaseWith: function releaseWith(finalizer) {
    let parent = this;
    let type = this.withName("[auto " + this.name + ", " + finalizer + "] ");
    type.importFromC = function importFromC(value, operation) {
      return ctypes.CDataFinalizer(
        parent.importFromC(value, operation),
        finalizer);
    };
    return type;
  },

  /**
   * Lazy variant of releaseWith.
   * Attach a finalizer lazily to a type.
   *
   * @param {function} getFinalizer The function that
   * returns finalizer lazily.
   */
  releaseWithLazy: function releaseWithLazy(getFinalizer) {
    let parent = this;
    let type = this.withName("[auto " + this.name + ", (lazy)] ");
    type.importFromC = function importFromC(value, operation) {
      return ctypes.CDataFinalizer(
        parent.importFromC(value, operation),
        getFinalizer());
    };
    return type;
  },

  /**
   * Return an alias to a type with a different name.
   */
  withName: function withName(name) {
    return Object.create(this, {name: {value: name}});
  },

  /**
   * Cast a C value to |this| type.
   *
   * Throw an error if the value cannot be casted.
   */
  cast: function cast(value) {
    return ctypes.cast(value, this.implementation);
  },

  /**
   * Return the number of bytes in a value of |this| type.
   *
   * This may not be defined, e.g. for |void_t|, array types
   * without length, etc.
   */
  get size() {
    return this.implementation.size;
  }
};

/**
 * Utility function used to determine whether an object is a typed array
 */
var isTypedArray = function isTypedArray(obj) {
  return obj != null && typeof obj == "object"
    && "byteOffset" in obj;
};
exports.isTypedArray = isTypedArray;

/**
 * Utility function used to determine whether an object is an ArrayBuffer.
 */
var isArrayBuffer = function(obj) {
  return obj != null && typeof obj == "object" &&
    obj.constructor.name == "ArrayBuffer";
};
exports.isArrayBuffer = isArrayBuffer;

/**
 * A |Type| of pointers.
 *
 * @param {string} name The name of this type.
 * @param {CType} implementation The type of this pointer.
 * @param {Type} targetType The target type.
 */
function PtrType(name, implementation, targetType) {
  Type.call(this, name, implementation);
  if (targetType == null || !targetType instanceof Type) {
    throw new TypeError("targetType must be an instance of Type");
  }
  /**
   * The type of values targeted by this pointer type.
   */
  Object.defineProperty(this, "targetType", {
    value: targetType
  });
}
PtrType.prototype = Object.create(Type.prototype);

/**
 * Convert a value to a pointer.
 *
 * Protocol:
 * - |null| returns |null|
 * - a string returns |{string: value}|
 * - a typed array returns |{ptr: address_of_buffer}|
 * - a C array returns |{ptr: address_of_buffer}|
 * everything else raises an error
 */
PtrType.prototype.toMsg = function ptr_toMsg(value) {
  if (value == null) {
    return null;
  }
  if (typeof value == "string") {
    return { string: value };
  }
  if (isTypedArray(value)) {
    // Automatically transfer typed arrays
    return new Meta({data: value}, {transfers: [value.buffer]});
  }
  if (isArrayBuffer(value)) {
    // Automatically transfer array buffers
    return new Meta({data: value}, {transfers: [value]});
  }
  let normalized;
  if ("addressOfElement" in value) { // C array
    normalized = value.addressOfElement(0);
  } else if ("isNull" in value) { // C pointer
    normalized = value;
  } else {
    throw new TypeError("Value " + value +
      " cannot be converted to a pointer");
  }
  let cast = Type.uintptr_t.cast(normalized);
  return {ptr: cast.value.toString()};
};

/**
 * Convert a message back to a pointer.
 */
PtrType.prototype.fromMsg = function ptr_fromMsg(msg) {
  if (msg == null) {
    return null;
  }
  if ("string" in msg) {
    return msg.string;
  }
  if ("data" in msg) {
    return msg.data;
  }
  if ("ptr" in msg) {
    let address = ctypes.uintptr_t(msg.ptr);
    return this.cast(address);
  }
  throw new TypeError("Message " + msg.toSource() +
    " does not represent a pointer");
};

exports.Type = Type;


/*
 * Some values are large integers on 64 bit platforms. Unfortunately,
 * in practice, 64 bit integers cannot be manipulated in JS. We
 * therefore project them to regular numbers whenever possible.
 */

var projectLargeInt = function projectLargeInt(x) {
  let str = x.toString();
  let rv = parseInt(str, 10);
  if (rv.toString() !== str) {
    throw new TypeError("Number " + str + " cannot be projected to a double");
  }
  return rv;
};
var projectLargeUInt = function projectLargeUInt(x) {
  return projectLargeInt(x);
};
var projectValue = function projectValue(x) {
  if (!(x instanceof ctypes.CData)) {
    return x;
  }
  if (!("value" in x)) { // Sanity check
    throw new TypeError("Number " + x.toSource() + " has no field |value|");
  }
  return x.value;
};

function projector(type, signed) {
  LOG("Determining best projection for", type,
    "(size: ", type.size, ")", signed?"signed":"unsigned");
  if (type instanceof Type) {
    type = type.implementation;
  }
  if (!type.size) {
    throw new TypeError("Argument is not a proper C type");
  }
  // Determine if type is projected to Int64/Uint64
  if (type.size == 8           // Usual case
      // The following cases have special treatment in js-ctypes
      // Regardless of their size, the value getter returns
      // a Int64/Uint64
      || type == ctypes.size_t // Special cases
      || type == ctypes.ssize_t
      || type == ctypes.intptr_t
      || type == ctypes.uintptr_t
      || type == ctypes.off_t) {
    if (signed) {
      LOG("Projected as a large signed integer");
      return projectLargeInt;
    } else {
      LOG("Projected as a large unsigned integer");
      return projectLargeUInt;
    }
  }
  LOG("Projected as a regular number");
  return projectValue;
};
exports.projectValue = projectValue;

/**
 * Get the appropriate type for an unsigned int of the given size.
 *
 * This function is useful to define types such as |mode_t| whose
 * actual width depends on the OS/platform.
 *
 * @param {number} size The number of bytes requested.
 */
Type.uintn_t = function uintn_t(size) {
  switch (size) {
  case 1: return Type.uint8_t;
  case 2: return Type.uint16_t;
  case 4: return Type.uint32_t;
  case 8: return Type.uint64_t;
  default:
    throw new Error("Cannot represent unsigned integers of " + size + " bytes");
  }
};

/**
 * Get the appropriate type for an signed int of the given size.
 *
 * This function is useful to define types such as |mode_t| whose
 * actual width depends on the OS/platform.
 *
 * @param {number} size The number of bytes requested.
 */
Type.intn_t = function intn_t(size) {
  switch (size) {
  case 1: return Type.int8_t;
  case 2: return Type.int16_t;
  case 4: return Type.int32_t;
  case 8: return Type.int64_t;
  default:
    throw new Error("Cannot represent integers of " + size + " bytes");
  }
};

/**
 * Actual implementation of common C types.
 */

/**
 * The void value.
 */
Type.void_t =
  new Type("void",
           ctypes.void_t);

/**
 * Shortcut for |void*|.
 */
Type.voidptr_t =
  new PtrType("void*",
              ctypes.voidptr_t,
              Type.void_t);

// void* is a special case as we can cast any pointer to/from it
// so we have to shortcut |in_ptr|/|out_ptr|/|inout_ptr| and
// ensure that js-ctypes' casting mechanism is invoked directly
["in_ptr", "out_ptr", "inout_ptr"].forEach(function(key) {
  Object.defineProperty(Type.void_t, key,
  {
    value: Type.voidptr_t
  });
});

/**
 * A Type of integers.
 *
 * @param {string} name The name of this type.
 * @param {CType} implementation The underlying js-ctypes implementation.
 * @param {bool} signed |true| if this is a type of signed integers,
 * |false| otherwise.
 *
 * @constructor
 */
function IntType(name, implementation, signed) {
  Type.call(this, name, implementation);
  this.importFromC = projector(implementation, signed);
  this.project = this.importFromC;
};
IntType.prototype = Object.create(Type.prototype);
IntType.prototype.toMsg = function toMsg(value) {
  if (typeof value == "number") {
    return value;
  }
  return this.project(value);
};

/**
 * A C char (one byte)
 */
Type.char =
  new Type("char",
           ctypes.char);

/**
 * A C wide char (two bytes)
 */
Type.char16_t =
  new Type("char16_t",
           ctypes.char16_t);

 /**
  * Base string types.
  */
Type.cstring = Type.char.in_ptr.withName("[in] C string");
Type.wstring = Type.char16_t.in_ptr.withName("[in] wide string");
Type.out_cstring = Type.char.out_ptr.withName("[out] C string");
Type.out_wstring = Type.char16_t.out_ptr.withName("[out] wide string");

/**
 * A C integer (8-bits).
 */
Type.int8_t =
  new IntType("int8_t", ctypes.int8_t, true);

Type.uint8_t =
  new IntType("uint8_t", ctypes.uint8_t, false);

/**
 * A C integer (16-bits).
 *
 * Also known as WORD under Windows.
 */
Type.int16_t =
  new IntType("int16_t", ctypes.int16_t, true);

Type.uint16_t =
  new IntType("uint16_t", ctypes.uint16_t, false);

/**
 * A C integer (32-bits).
 *
 * Also known as DWORD under Windows.
 */
Type.int32_t =
  new IntType("int32_t", ctypes.int32_t, true);

Type.uint32_t =
  new IntType("uint32_t", ctypes.uint32_t, false);

/**
 * A C integer (64-bits).
 */
Type.int64_t =
  new IntType("int64_t", ctypes.int64_t, true);

Type.uint64_t =
  new IntType("uint64_t", ctypes.uint64_t, false);

 /**
 * A C integer
 *
 * Size depends on the platform.
 */
Type.int = Type.intn_t(ctypes.int.size).
  withName("int");

Type.unsigned_int = Type.intn_t(ctypes.unsigned_int.size).
  withName("unsigned int");

/**
 * A C long integer.
 *
 * Size depends on the platform.
 */
Type.long =
  Type.intn_t(ctypes.long.size).withName("long");

Type.unsigned_long =
  Type.intn_t(ctypes.unsigned_long.size).withName("unsigned long");

/**
 * An unsigned integer with the same size as a pointer.
 *
 * Used to cast a pointer to an integer, whenever necessary.
 */
Type.uintptr_t =
  Type.uintn_t(ctypes.uintptr_t.size).withName("uintptr_t");

/**
 * A boolean.
 * Implemented as a C integer.
 */
Type.bool = Type.int.withName("bool");
Type.bool.importFromC = function projectBool(x) {
  return !!(x.value);
};

/**
 * A user identifier.
 *
 * Implemented as a C integer.
 */
Type.uid_t =
  Type.int.withName("uid_t");

/**
 * A group identifier.
 *
 * Implemented as a C integer.
 */
Type.gid_t =
  Type.int.withName("gid_t");

/**
 * An offset (positive or negative).
 *
 * Implemented as a C integer.
 */
Type.off_t =
  new IntType("off_t", ctypes.off_t, true);

/**
 * A size (positive).
 *
 * Implemented as a C size_t.
 */
Type.size_t =
  new IntType("size_t", ctypes.size_t, false);

/**
 * An offset (positive or negative).
 * Implemented as a C integer.
 */
Type.ssize_t =
  new IntType("ssize_t", ctypes.ssize_t, true);

/**
 * Encoding/decoding strings
 */
Type.uencoder =
  new Type("uencoder", ctypes.StructType("uencoder"));
Type.udecoder =
  new Type("udecoder", ctypes.StructType("udecoder"));

/**
 * Utility class, used to build a |struct| type
 * from a set of field names, types and offsets.
 *
 * @param {string} name The name of the |struct| type.
 * @param {number} size The total size of the |struct| type in bytes.
 */
function HollowStructure(name, size) {
  if (!name) {
    throw new TypeError("HollowStructure expects a name");
  }
  if (!size || size < 0) {
    throw new TypeError("HollowStructure expects a (positive) size");
  }

  // A mapping from offsets in the struct to name/type pairs
  // (or nothing if no field starts at that offset).
  this.offset_to_field_info = [];

  // The name of the struct
  this.name = name;

  // The size of the struct, in bytes
  this.size = size;

  // The number of paddings inserted so far.
  // Used to give distinct names to padding fields.
  this._paddings = 0;
}
HollowStructure.prototype = {
  /**
   * Add a field at a given offset.
   *
   * @param {number} offset The offset at which to insert the field.
   * @param {string} name The name of the field.
   * @param {CType|Type} type The type of the field.
   */
  add_field_at: function add_field_at(offset, name, type) {
    if (offset == null) {
      throw new TypeError("add_field_at requires a non-null offset");
    }
    if (!name) {
      throw new TypeError("add_field_at requires a non-null name");
    }
    if (!type) {
      throw new TypeError("add_field_at requires a non-null type");
    }
    if (type instanceof Type) {
      type = type.implementation;
    }
    if (this.offset_to_field_info[offset]) {
      throw new Error("HollowStructure " + this.name +
                      " already has a field at offset " + offset);
    }
    if (offset + type.size > this.size) {
      throw new Error("HollowStructure " + this.name +
                      " cannot place a value of type " + type +
                      " at offset " + offset +
                      " without exceeding its size of " + this.size);
    }
    let field = {name: name, type:type};
    this.offset_to_field_info[offset] = field;
  },

  /**
   * Create a pseudo-field that will only serve as padding.
   *
   * @param {number} size The number of bytes in the field.
   * @return {Object} An association field-name => field-type,
   * as expected by |ctypes.StructType|.
   */
  _makePaddingField: function makePaddingField(size) {
    let field = ({});
    field["padding_" + this._paddings] =
      ctypes.ArrayType(ctypes.uint8_t, size);
    this._paddings++;
    return field;
  },

  /**
   * Convert this |HollowStructure| into a |Type|.
   */
  getType: function getType() {
    // Contents of the structure, in the format expected
    // by ctypes.StructType.
    let struct = [];

    let i = 0;
    while (i < this.size) {
      let currentField = this.offset_to_field_info[i];
      if (!currentField) {
        // No field was specified at this offset, we need to
        // introduce some padding.

        // Firstly, determine how many bytes of padding
        let padding_length = 1;
        while (i + padding_length < this.size
            && !this.offset_to_field_info[i + padding_length]) {
          ++padding_length;
        }

        // Then add the padding
        struct.push(this._makePaddingField(padding_length));

        // And proceed
        i += padding_length;
      } else {
        // We have a field at this offset.

        // Firstly, ensure that we do not have two overlapping fields
        for (let j = 1; j < currentField.type.size; ++j) {
          let candidateField = this.offset_to_field_info[i + j];
          if (candidateField) {
            throw new Error("Fields " + currentField.name +
              " and " + candidateField.name +
              " overlap at position " + (i + j));
          }
        }

        // Then add the field
        let field = ({});
        field[currentField.name] = currentField.type;
        struct.push(field);

        // And proceed
        i += currentField.type.size;
      }
    }
    let result = new Type(this.name, ctypes.StructType(this.name, struct));
    if (result.implementation.size != this.size) {
      throw new Error("Wrong size for type " + this.name +
          ": expected " + this.size +
          ", found " + result.implementation.size +
          " (" + result.implementation.toSource() + ")");
    }
    return result;
  }
};
exports.HollowStructure = HollowStructure;

/**
 * Representation of a native library.
 *
 * The native library is opened lazily, during the first call to its
 * field |library| or whenever accessing one of the methods imported
 * with declareLazyFFI.
 *
 * @param {string} name A human-readable name for the library. Used
 * for debugging and error reporting.
 * @param {string...} candidates A list of system libraries that may
 * represent this library. Used e.g. to try different library names
 * on distinct operating systems ("libxul", "XUL", etc.).
 *
 * @constructor
 */
function Library(name, ...candidates) {
  this.name = name;
  this._candidates = candidates;
};
Library.prototype = Object.freeze({
  /**
   * The native library as a js-ctypes object.
   *
   * @throws {Error} If none of the candidate libraries could be opened.
   */
  get library() {
    let library;
    delete this.library;
    for (let candidate of this._candidates) {
      try {
        library = ctypes.open(candidate);
        break;
      } catch (ex) {
        LOG("Could not open library", candidate, ex);
      }
    }
    this._candidates = null;
    if (library) {
      Object.defineProperty(this, "library", {
        value: library
      });
      Object.freeze(this);
      return library;
    }
    let error = new Error("Could not open library " + this.name);
    Object.defineProperty(this, "library", {
      get: function() {
        throw error;
      }
    });
    Object.freeze(this);
    throw error;
  },

  /**
   * Declare a function, lazily.
   *
   * @param {object} The object containing the function as a field.
   * @param {string} The name of the field containing the function.
   * @param {string} symbol The name of the function, as defined in the
   * library.
   * @param {ctypes.abi} abi The abi to use, or |null| for default.
   * @param {Type} returnType The type of values returned by the function.
   * @param {...Type} argTypes The type of arguments to the function.
   */
  declareLazyFFI: function(object, field, ...args) {
    let lib = this;
    Object.defineProperty(object, field, {
      get: function() {
        delete this[field];
        let ffi = declareFFI(lib.library, ...args);
        if (ffi) {
          return this[field] = ffi;
        }
        return undefined;
      },
      configurable: true,
      enumerable: true
    });
  },

  /**
   * Define a js-ctypes function lazily using ctypes method declare.
   *
   * @param {object} The object containing the function as a field.
   * @param {string} The name of the field containing the function.
   * @param {string} symbol The name of the function, as defined in the
   * library.
   * @param {ctypes.abi} abi The abi to use, or |null| for default.
   * @param {ctypes.CType} returnType The type of values returned by the function.
   * @param {...ctypes.CType} argTypes The type of arguments to the function.
   */
  declareLazy: function(object, field, ...args) {
    let lib = this;
    Object.defineProperty(object, field, {
      get: function() {
        delete this[field];
        let ffi = lib.library.declare(...args);
        if (ffi) {
          return this[field] = ffi;
        }
        return undefined;
      },
      configurable: true,
      enumerable: true
    });
  },

  /**
   * Define a js-ctypes function lazily using ctypes method declare,
   * with a fallback library to use if this library can't be opened
   * or the function cannot be declared.
   *
   * @param {fallbacklibrary} The fallback Library object.
   * @param {object} The object containing the function as a field.
   * @param {string} The name of the field containing the function.
   * @param {string} symbol The name of the function, as defined in the
   * library.
   * @param {ctypes.abi} abi The abi to use, or |null| for default.
   * @param {ctypes.CType} returnType The type of values returned by the function.
   * @param {...ctypes.CType} argTypes The type of arguments to the function.
   */
  declareLazyWithFallback: function(fallbacklibrary, object, field, ...args) {
    let lib = this;
    Object.defineProperty(object, field, {
      get: function() {
        delete this[field];
        try {
          let ffi = lib.library.declare(...args);
          if (ffi) {
            return this[field] = ffi;
          }
        } catch (ex) {
          // Use the fallback library and get the symbol from there.
          fallbacklibrary.declareLazy(object, field, ...args);
          return object[field];
        }
        return undefined;
      },
      configurable: true,
      enumerable: true
    });
  },

  toString: function() {
    return "[Library " + this.name + "]";
  }
});
exports.Library = Library;

/**
 * Declare a function through js-ctypes
 *
 * @param {ctypes.library} lib The ctypes library holding the function.
 * @param {string} symbol The name of the function, as defined in the
 * library.
 * @param {ctypes.abi} abi The abi to use, or |null| for default.
 * @param {Type} returnType The type of values returned by the function.
 * @param {...Type} argTypes The type of arguments to the function.
 *
 * @return null if the function could not be defined (generally because
 * it does not exist), or a JavaScript wrapper performing the call to C
 * and any type conversion required.
 */
var declareFFI = function declareFFI(lib, symbol, abi,
                                     returnType /*, argTypes ...*/) {
  LOG("Attempting to declare FFI ", symbol);
  // We guard agressively, to avoid any late surprise
  if (typeof symbol != "string") {
    throw new TypeError("declareFFI expects as first argument a string");
  }
  abi = abi || ctypes.default_abi;
  if (Object.prototype.toString.call(abi) != "[object CABI]") {
    // Note: This is the only known manner of checking whether an object
    // is an abi.
    throw new TypeError("declareFFI expects as second argument an abi or null");
  }
  if (!returnType.importFromC) {
    throw new TypeError("declareFFI expects as third argument an instance of Type");
  }
  let signature = [symbol, abi];
  let argtypes  = [];
  for (let i = 3; i < arguments.length; ++i) {
    let current = arguments[i];
    if (!current) {
      throw new TypeError("Missing type for argument " + ( i - 3 ) +
                          " of symbol " + symbol);
    }
    if (!current.implementation) {
      throw new TypeError("Missing implementation for argument " + (i - 3)
                          + " of symbol " + symbol
                          + " ( " + current.name + " )" );
    }
    signature.push(current.implementation);
  }
  try {
    let fun = lib.declare.apply(lib, signature);
    let result = function ffi(...args) {
      for (let i = 0; i < args.length; i++) {
        if (typeof args[i] == "undefined") {
          throw new TypeError("Argument " + i + " of " + symbol + " is undefined");
        }
      }
      let result = fun.apply(fun, args);
      return returnType.importFromC(result, symbol);
    };
    LOG("Function", symbol, "declared");
    return result;
  } catch (x) {
    // Note: Not being able to declare a function is normal.
    // Some functions are OS (or OS version)-specific.
    LOG("Could not declare function ", symbol, x);
    return null;
  }
};
exports.declareFFI = declareFFI;

/**
 * Define a lazy getter to a js-ctypes function using declareFFI.
 *
 * @param {object} The object containing the function as a field.
 * @param {string} The name of the field containing the function.
 * @param {ctypes.library} lib The ctypes library holding the function.
 * @param {string} symbol The name of the function, as defined in the
 * library.
 * @param {ctypes.abi} abi The abi to use, or |null| for default.
 * @param {Type} returnType The type of values returned by the function.
 * @param {...Type} argTypes The type of arguments to the function.
 */
function declareLazyFFI(object, field, ...declareFFIArgs) {
  Object.defineProperty(object, field, {
    get: function() {
      delete this[field];
      let ffi = declareFFI(...declareFFIArgs);
      if (ffi) {
        return this[field] = ffi;
      }
      return undefined;
    },
    configurable: true,
    enumerable: true
  });
}
exports.declareLazyFFI = declareLazyFFI;

/**
 * Define a lazy getter to a js-ctypes function using ctypes method declare.
 *
 * @param {object} The object containing the function as a field.
 * @param {string} The name of the field containing the function.
 * @param {ctypes.library} lib The ctypes library holding the function.
 * @param {string} symbol The name of the function, as defined in the
 * library.
 * @param {ctypes.abi} abi The abi to use, or |null| for default.
 * @param {ctypes.CType} returnType The type of values returned by the function.
 * @param {...ctypes.CType} argTypes The type of arguments to the function.
 */
function declareLazy(object, field, lib, ...declareArgs) {
  Object.defineProperty(object, field, {
    get: function() {
      delete this[field];
      try {
        let ffi = lib.declare(...declareArgs);
        return this[field] = ffi;
      } catch (ex) {
        // The symbol doesn't exist
        return undefined;
      }
    },
    configurable: true
  });
}
exports.declareLazy = declareLazy;

/**
 * Utility function used to sanity check buffer and length arguments.  The
 * buffer must be a Typed Array.
 *
 * @param {Typed array} candidate The buffer.
 * @param {number} bytes The number of bytes that |candidate| should contain.
 *
 * @return number The bytes argument clamped to the length of the buffer.
 */
function normalizeBufferArgs(candidate, bytes) {
  if (!candidate) {
    throw new TypeError("Expecting a Typed Array");
  }
  if (!isTypedArray(candidate)) {
    throw new TypeError("Expecting a Typed Array");
  }
  if (bytes == null) {
    bytes = candidate.byteLength;
  } else if (candidate.byteLength < bytes) {
    throw new TypeError("Buffer is too short. I need at least " +
                       bytes +
                       " bytes but I have only " +
                       candidate.byteLength +
                        "bytes");
  }
  return bytes;
};
exports.normalizeBufferArgs = normalizeBufferArgs;

///////////////////// OS interactions

/**
 * An OS error.
 *
 * This class is provided mostly for type-matching. If you need more
 * details about an error, you should use the platform-specific error
 * codes provided by subclasses of |OS.Shared.Error|.
 *
 * @param {string} operation The operation that failed.
 * @param {string=} path The path of the file on which the operation failed,
 * or nothing if there was no file involved in the failure.
 *
 * @constructor
 */
function OSError(operation, path = "") {
  Error.call(this);
  this.operation = operation;
  this.path = path;
}
OSError.prototype = Object.create(Error.prototype);
exports.OSError = OSError;


///////////////////// Temporary boilerplate
// Boilerplate, to simplify the transition to require()
// Do not rely upon this symbol, it will disappear with
// bug 883050.
exports.OS = {
  Constants: exports.Constants,
  Shared: {
    LOG: LOG,
    clone: clone,
    Type: Type,
    HollowStructure: HollowStructure,
    Error: OSError,
    declareFFI: declareFFI,
    projectValue: projectValue,
    isTypedArray: isTypedArray,
    defineLazyGetter: defineLazyGetter
  }
};

Object.defineProperty(exports.OS.Shared, "DEBUG", {
  get: function() {
    return Config.DEBUG;
  },
  set: function(x) {
    return Config.DEBUG = x;
  }
});
Object.defineProperty(exports.OS.Shared, "TEST", {
  get: function() {
    return Config.TEST;
  },
  set: function(x) {
    return Config.TEST = x;
  }
});


///////////////////// Permanent boilerplate
if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
  for (let symbol of EXPORTED_SYMBOLS) {
    this[symbol] = exports[symbol];
  }
}
PK
!<
(>J>J&modules/osfile/osfile_shared_front.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Code shared by OS.File front-ends.
 *
 * This code is meant to be included by another library. It is also meant to
 * be executed only on a worker thread.
 */

if (typeof Components != "undefined") {
  throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
}
(function(exports) {

var SharedAll =
  require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
var Path = require("resource://gre/modules/osfile/ospath.jsm");
var Lz4 =
  require("resource://gre/modules/lz4.js");
var LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end");
var clone = SharedAll.clone;

/**
 * Code shared by implementations of File.
 *
 * @param {*} fd An OS-specific file handle.
 * @param {string} path File path of the file handle, used for error-reporting.
 * @constructor
 */
var AbstractFile = function AbstractFile(fd, path) {
  this._fd = fd;
  if (!path) {
    throw new TypeError("path is expected");
  }
  this._path = path;
};

AbstractFile.prototype = {
  /**
   * Return the file handle.
   *
   * @throw OS.File.Error if the file has been closed.
   */
  get fd() {
    if (this._fd) {
      return this._fd;
    }
    throw OS.File.Error.closed("accessing file", this._path);
  },
  /**
   * Read bytes from this file to a new buffer.
   *
   * @param {number=} maybeBytes (deprecated, please use options.bytes)
   * @param {JSON} options
   * @return {Uint8Array} An array containing the bytes read.
   */
  read: function read(maybeBytes, options = {}) {
    if (typeof maybeBytes === "object") {
    // Caller has skipped `maybeBytes` and provided an options object.
      options = clone(maybeBytes);
      maybeBytes = null;
    } else {
      options = options || {};
    }
    let bytes = options.bytes || undefined;
    if (bytes === undefined) {
      bytes = maybeBytes == null ? this.stat().size : maybeBytes;
    }
    let buffer = new Uint8Array(bytes);
    let pos = 0;
    while (pos < bytes) {
      let length = bytes - pos;
      let view = new DataView(buffer.buffer, pos, length);
      let chunkSize = this._read(view, length, options);
      if (chunkSize == 0) {
        break;
      }
      pos += chunkSize;
    }
    if (pos == bytes) {
      return buffer;
    } else {
      return buffer.subarray(0, pos);
    }
  },

  /**
   * Write bytes from a buffer to this file.
   *
   * Note that, by default, this function may perform several I/O
   * operations to ensure that the buffer is fully written.
   *
   * @param {Typed array} buffer The buffer in which the the bytes are
   * stored. The buffer must be large enough to accomodate |bytes| bytes.
   * @param {*=} options Optionally, an object that may contain the
   * following fields:
   * - {number} bytes The number of |bytes| to write from the buffer. If
   * unspecified, this is |buffer.byteLength|.
   *
   * @return {number} The number of bytes actually written.
   */
  write: function write(buffer, options = {}) {
    let bytes =
      SharedAll.normalizeBufferArgs(buffer, ("bytes" in options) ? options.bytes : undefined);
    let pos = 0;
    while (pos < bytes) {
      let length = bytes - pos;
      let view = new DataView(buffer.buffer, buffer.byteOffset + pos, length);
      let chunkSize = this._write(view, length, options);
      pos += chunkSize;
    }
    return pos;
  }
};

/**
 * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
 *
 * @param {string} path The path to the file.
 * @param {*=} options Additional options for file opening. This
 * implementation interprets the following fields:
 *
 * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
 *  If |false| use HEX numbers ie: filename-A65BC0.ext
 * - {number} maxReadableNumber Used to limit the amount of tries after a failed
 *  file creation. Default is 20.
 *
 * @return {Object} contains A file object{file} and the path{path}.
 * @throws {OS.File.Error} If the file could not be opened.
 */
AbstractFile.openUnique = function openUnique(path, options = {}) {
  let mode = {
    create : true
  };

  let dirName = Path.dirname(path);
  let leafName = Path.basename(path);
  let lastDotCharacter = leafName.lastIndexOf('.');
  let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length);
  let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "");
  let uniquePath = "";
  let maxAttempts = options.maxAttempts || 99;
  let humanReadable = !!options.humanReadable;
  const HEX_RADIX = 16;
  // We produce HEX numbers between 0 and 2^24 - 1.
  const MAX_HEX_NUMBER = 16777215;

  try {
    return {
      path: path,
      file: OS.File.open(path, mode)
    };
  } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
    for (let i = 0; i < maxAttempts; ++i) {
      try {
        if (humanReadable) {
          uniquePath = Path.join(dirName, fileName + "-" + (i + 1) + suffix);
        } else {
          let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX);
          uniquePath = Path.join(dirName, fileName + "-" + hexNumber + suffix);
        }
        return {
          path: uniquePath,
          file: OS.File.open(uniquePath, mode)
        };
      } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
        // keep trying ...
      }
    }
    throw OS.File.Error.exists("could not find an unused file name.", path);
  }
};

/**
 * Code shared by iterators.
 */
AbstractFile.AbstractIterator = function AbstractIterator() {
};
AbstractFile.AbstractIterator.prototype = {
  /**
   * Allow iterating with |for|
   */
  __iterator__: function __iterator__() {
    return this;
  },
  /**
   * Apply a function to all elements of the directory sequentially.
   *
   * @param {Function} cb This function will be applied to all entries
   * of the directory. It receives as arguments
   *  - the OS.File.Entry corresponding to the entry;
   *  - the index of the entry in the enumeration;
   *  - the iterator itself - calling |close| on the iterator stops
   *   the loop.
   */
  forEach: function forEach(cb) {
    let index = 0;
    for (let entry in this) {
      cb(entry, index++, this);
    }
  },
  /**
   * Return several entries at once.
   *
   * Entries are returned in the same order as a walk with |forEach| or
   * |for(...)|.
   *
   * @param {number=} length If specified, the number of entries
   * to return. If unspecified, return all remaining entries.
   * @return {Array} An array containing the next |length| entries, or
   * less if the iteration contains less than |length| entries left.
   */
  nextBatch: function nextBatch(length) {
    let array = [];
    let i = 0;
    for (let entry in this) {
      array.push(entry);
      if (++i >= length) {
        return array;
      }
    }
    return array;
  }
};

/**
 * Utility function shared by implementations of |OS.File.open|:
 * extract read/write/trunc/create/existing flags from a |mode|
 * object.
 *
 * @param {*=} mode An object that may contain fields |read|,
 * |write|, |truncate|, |create|, |existing|. These fields
 * are interpreted only if true-ish.
 * @return {{read:bool, write:bool, trunc:bool, create:bool,
 * existing:bool}} an object recapitulating the options set
 * by |mode|.
 * @throws {TypeError} If |mode| contains other fields, or
 * if it contains both |create| and |truncate|, or |create|
 * and |existing|.
 */
AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
  let result = {
    read: false,
    write: false,
    trunc: false,
    create: false,
    existing: false,
    append: true
  };
  for (let key in mode) {
    let val = !!mode[key]; // bool cast.
    switch (key) {
    case "read":
      result.read = val;
      break;
    case "write":
      result.write = val;
      break;
    case "truncate": // fallthrough
    case "trunc":
      result.trunc = val;
      result.write |= val;
      break;
    case "create":
      result.create = val;
      result.write |= val;
      break;
    case "existing": // fallthrough
    case "exist":
      result.existing = val;
      break;
    case "append":
      result.append = val;
      break;
    default:
      throw new TypeError("Mode " + key + " not understood");
    }
  }
  // Reject opposite modes
  if (result.existing && result.create) {
    throw new TypeError("Cannot specify both existing:true and create:true");
  }
  if (result.trunc && result.create) {
    throw new TypeError("Cannot specify both trunc:true and create:true");
  }
  // Handle read/write
  if (!result.write) {
    result.read = true;
  }
  return result;
};

/**
 * Return the contents of a file.
 *
 * @param {string} path The path to the file.
 * @param {number=} bytes Optionally, an upper bound to the number of bytes
 * to read. DEPRECATED - please use options.bytes instead.
 * @param {object=} options Optionally, an object with some of the following
 * fields:
 * - {number} bytes An upper bound to the number of bytes to read.
 * - {string} compression If "lz4" and if the file is compressed using the lz4
 *   compression algorithm, decompress the file contents on the fly.
 *
 * @return {Uint8Array} A buffer holding the bytes
 * and the number of bytes read from the file.
 */
AbstractFile.read = function read(path, bytes, options = {}) {
  if (bytes && typeof bytes == "object") {
    options = bytes;
    bytes = options.bytes || null;
  }
  if ("encoding" in options && typeof options.encoding != "string") {
    throw new TypeError("Invalid type for option encoding");
  }
  if ("compression" in options && typeof options.compression != "string") {
    throw new TypeError("Invalid type for option compression: " + options.compression);
  }
  if ("bytes" in options && typeof options.bytes != "number") {
    throw new TypeError("Invalid type for option bytes");
  }
  let file = exports.OS.File.open(path);
  try {
    let buffer = file.read(bytes, options);
    if ("compression" in options) {
      if (options.compression == "lz4") {
        options = Object.create(options);
        options.path = path;
        buffer = Lz4.decompressFileContent(buffer, options);
      } else {
        throw OS.File.Error.invalidArgument("Compression");
      }
    }
    if (!("encoding" in options)) {
      return buffer;
    }
    let decoder;
    try {
      decoder = new TextDecoder(options.encoding);
    } catch (ex if ex instanceof RangeError) {
      throw OS.File.Error.invalidArgument("Decode");
    }
    return decoder.decode(buffer);
  } finally {
    file.close();
  }
};

/**
 * Write a file, atomically.
 *
 * By opposition to a regular |write|, this operation ensures that,
 * until the contents are fully written, the destination file is
 * not modified.
 *
 * Limitation: In a few extreme cases (hardware failure during the
 * write, user unplugging disk during the write, etc.), data may be
 * corrupted. If your data is user-critical (e.g. preferences,
 * application data, etc.), you may wish to consider adding options
 * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
 * detailed below. Note that no combination of options can be
 * guaranteed to totally eliminate the risk of corruption.
 *
 * @param {string} path The path of the file to modify.
 * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
 * @param {*=} options Optionally, an object determining the behavior
 * of this function. This object may contain the following fields:
 * - {number} bytes The number of bytes to write. If unspecified,
 * |buffer.byteLength|. Required if |buffer| is a C pointer.
 * - {string} tmpPath If |null| or unspecified, write all data directly
 * to |path|. If specified, write all data to a temporary file called
 * |tmpPath| and, once this write is complete, rename the file to
 * replace |path|. Performing this additional operation is a little
 * slower but also a little safer.
 * - {bool} noOverwrite - If set, this function will fail if a file already
 * exists at |path|.
 * - {bool} flush - If |false| or unspecified, return immediately once the
 * write is complete. If |true|, before writing, force the operating system
 * to write its internal disk buffers to the disk. This is considerably slower
 * (not just for the application but for the whole system) but also safer:
 * if the system shuts down improperly (typically due to a kernel freeze
 * or a power failure) or if the device is disconnected before the buffer
 * is flushed, the file has more chances of not being corrupted.
 * - {string} compression - If empty or unspecified, do not compress the file.
 * If "lz4", compress the contents of the file atomically using lz4. For the
 * time being, the container format is specific to Mozilla and cannot be read
 * by means other than OS.File.read(..., { compression: "lz4"})
 * - {string} backupTo - If specified, backup the destination file as |backupTo|.
 * Note that this function renames the destination file before overwriting it.
 * If the process or the operating system freezes or crashes
 * during the short window between these operations,
 * the destination file will have been moved to its backup.
 *
 * @return {number} The number of bytes actually written.
 */
AbstractFile.writeAtomic =
     function writeAtomic(path, buffer, options = {}) {

  // Verify that path is defined and of the correct type
  if (typeof path != "string" || path == "") {
    throw new TypeError("File path should be a (non-empty) string");
  }
  let noOverwrite = options.noOverwrite;
  if (noOverwrite && OS.File.exists(path)) {
    throw OS.File.Error.exists("writeAtomic", path);
  }

  if (typeof buffer == "string") {
    // Normalize buffer to a C buffer by encoding it
    let encoding = options.encoding || "utf-8";
    buffer = new TextEncoder(encoding).encode(buffer);
  }

  if ("compression" in options && options.compression == "lz4") {
    buffer = Lz4.compressFileContent(buffer, options);
    options = Object.create(options);
    options.bytes = buffer.byteLength;
  }

  let bytesWritten = 0;

  if (!options.tmpPath) {
    if (options.backupTo) {
      try {
        OS.File.move(path, options.backupTo, {noCopy: true});
      } catch (ex if ex.becauseNoSuchFile) {
        // The file doesn't exist, nothing to backup.
      }
    }
    // Just write, without any renaming trick
    let dest = OS.File.open(path, {write: true, truncate: true});
    try {
      bytesWritten = dest.write(buffer, options);
      if (options.flush) {
        dest.flush();
      }
    } finally {
      dest.close();
    }
    return bytesWritten;
  }

  let tmpFile = OS.File.open(options.tmpPath, {write: true, truncate: true});
  try {
    bytesWritten = tmpFile.write(buffer, options);
    if (options.flush) {
      tmpFile.flush();
    }
  } catch (x) {
    OS.File.remove(options.tmpPath);
    throw x;
  } finally {
    tmpFile.close();
  }

  if (options.backupTo) {
    try {
      OS.File.move(path, options.backupTo, {noCopy: true});
    } catch (ex if ex.becauseNoSuchFile) {
      // The file doesn't exist, nothing to backup.
    }
  }

  OS.File.move(options.tmpPath, path, {noCopy: true});
  return bytesWritten;
};

/**
 * This function is used by removeDir to avoid calling lstat for each
 * files under the specified directory. External callers should not call
 * this function directly.
 */
AbstractFile.removeRecursive = function(path, options = {}) {
  let iterator = new OS.File.DirectoryIterator(path);
  if (!iterator.exists()) {
    if (!("ignoreAbsent" in options) || options.ignoreAbsent) {
      return;
    }
  }

  try {
    for (let entry in iterator) {
      if (entry.isDir) {
        if (entry.isLink) {
          // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
          // directories are directories themselves. OS.File.remove()
          // will not work for them.
          OS.File.removeEmptyDir(entry.path, options);
        } else {
          // Normal directories.
          AbstractFile.removeRecursive(entry.path, options);
        }
      } else {
        // NTFS symlinks to files, Unix symlinks, or regular files.
        OS.File.remove(entry.path, options);
      }
    }
  } finally {
    iterator.close();
  }

  OS.File.removeEmptyDir(path);
};

/**
 * Create a directory and, optionally, its parent directories.
 *
 * @param {string} path The name of the directory.
 * @param {*=} options Additional options.
 *
 * - {string} from If specified, the call to |makeDir| creates all the
 * ancestors of |path| that are descendants of |from|. Note that |path|
 * must be a descendant of |from|, and that |from| and its existing
 * subdirectories present in |path|  must be user-writeable.
 * Example:
 *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
 *  creates directories profileDir/foo, profileDir/foo/bar
 * - {bool} ignoreExisting If |false|, throw an error if the directory
 * already exists. |true| by default. Ignored if |from| is specified.
 * - {number} unixMode Under Unix, if specified, a file creation mode,
 * as per libc function |mkdir|. If unspecified, dirs are
 * created with a default mode of 0700 (dir is private to
 * the user, the user can read, write and execute). Ignored under Windows
 * or if the file system does not support file creation modes.
 * - {C pointer} winSecurity Under Windows, if specified, security
 * attributes as per winapi function |CreateDirectory|. If
 * unspecified, use the default security descriptor, inherited from
 * the parent directory. Ignored under Unix or if the file system
 * does not support security descriptors.
 */
AbstractFile.makeDir = function(path, options = {}) {
  let from = options.from;
  if (!from) {
    OS.File._makeDir(path, options);
    return;
  }
  if (!path.startsWith(from)) {
    // Apparently, `from` is not a parent of `path`. However, we may
    // have false negatives due to non-normalized paths, e.g.
    // "foo//bar" is a parent of "foo/bar/sna".
    path = Path.normalize(path);
    from = Path.normalize(from);
    if (!path.startsWith(from)) {
      throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + from);
    }
  }
  let innerOptions = Object.create(options, {
    ignoreExisting: {
      value: true
    }
  });
  // Compute the elements that appear in |path| but not in |from|.
  let items = Path.split(path).components.slice(Path.split(from).components.length);
  let current = from;
  for (let item of items) {
    current = Path.join(current, item);
    OS.File._makeDir(current, innerOptions);
  }
};

if (!exports.OS.Shared) {
  exports.OS.Shared = {};
}
exports.OS.Shared.AbstractFile = AbstractFile;
})(this);
PK
!<I.I.(modules/osfile/osfile_win_allthreads.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module defines the thread-agnostic components of the Win version
 * of OS.File. It depends on the thread-agnostic cross-platform components
 * of OS.File.
 *
 * It serves the following purposes:
 * - open kernel32;
 * - define OS.Shared.Win.Error;
 * - define a few constants and types that need to be defined on all platforms.
 *
 * This module can be:
 * - opened from the main thread as a jsm module;
 * - opened from a chrome worker through require().
 */

"use strict";

var SharedAll;
if (typeof Components != "undefined") {
  let Cu = Components.utils;
  // Module is opened as a jsm module
  Cu.import("resource://gre/modules/ctypes.jsm", this);

  SharedAll = {};
  Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
  this.exports = {};
} else if (typeof module != "undefined" && typeof require != "undefined") {
  // Module is loaded with require()
  SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
} else {
  throw new Error("Please open this module with Component.utils.import or with require()");
}

var LOG = SharedAll.LOG.bind(SharedAll, "Win", "allthreads");
var Const = SharedAll.Constants.Win;

// Open libc
var libc = new SharedAll.Library("libc", "kernel32.dll");
exports.libc = libc;

// Define declareFFI
var declareFFI = SharedAll.declareFFI.bind(null, libc);
exports.declareFFI = declareFFI;

var Scope = {};

// Define Error
libc.declareLazy(Scope, "FormatMessage",
                 "FormatMessageW", ctypes.winapi_abi,
                 /*return*/ ctypes.uint32_t,
                 /*flags*/  ctypes.uint32_t,
                 /*source*/ ctypes.voidptr_t,
                 /*msgid*/  ctypes.uint32_t,
                 /*langid*/ ctypes.uint32_t,
                 /*buf*/    ctypes.char16_t.ptr,
                 /*size*/   ctypes.uint32_t,
                 /*Arguments*/ctypes.voidptr_t);

/**
 * A File-related error.
 *
 * To obtain a human-readable error message, use method |toString|.
 * To determine the cause of the error, use the various |becauseX|
 * getters. To determine the operation that failed, use field
 * |operation|.
 *
 * Additionally, this implementation offers a field
 * |winLastError|, which holds the OS-specific error
 * constant. If you need this level of detail, you may match the value
 * of this field against the error constants of |OS.Constants.Win|.
 *
 * @param {string=} operation The operation that failed. If unspecified,
 * the name of the calling function is taken to be the operation that
 * failed.
 * @param {number=} lastError The OS-specific constant detailing the
 * reason of the error. If unspecified, this is fetched from the system
 * status.
 * @param {string=} path The file path that manipulated. If unspecified,
 * assign the empty string.
 *
 * @constructor
 * @extends {OS.Shared.Error}
 */
var OSError = function OSError(operation = "unknown operation",
                               lastError = ctypes.winLastError, path = "") {
  operation = operation;
  SharedAll.OSError.call(this, operation, path);
  this.winLastError = lastError;
};
OSError.prototype = Object.create(SharedAll.OSError.prototype);
OSError.prototype.toString = function toString() {
  let buf = new (ctypes.ArrayType(ctypes.char16_t, 1024))();
  let result = Scope.FormatMessage(
    Const.FORMAT_MESSAGE_FROM_SYSTEM |
    Const.FORMAT_MESSAGE_IGNORE_INSERTS,
    null,
    /* The error number */ this.winLastError,
    /* Default language */ 0,
    /* Output buffer*/     buf,
    /* Minimum size of buffer */ 1024,
    /* Format args*/       null
  );
  if (!result) {
    buf = "additional error " +
      ctypes.winLastError +
      " while fetching system error message";
  }
  return "Win error " + this.winLastError + " during operation "
    + this.operation + (this.path? " on file " + this.path : "") +
    " (" + buf.readString() + ")";
};
OSError.prototype.toMsg = function toMsg() {
  return OSError.toMsg(this);
};

/**
 * |true| if the error was raised because a file or directory
 * already exists, |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseExists", {
  get: function becauseExists() {
    return this.winLastError == Const.ERROR_FILE_EXISTS ||
      this.winLastError == Const.ERROR_ALREADY_EXISTS;
  }
});
/**
 * |true| if the error was raised because a file or directory
 * does not exist, |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseNoSuchFile", {
  get: function becauseNoSuchFile() {
    return this.winLastError == Const.ERROR_FILE_NOT_FOUND ||
      this.winLastError == Const.ERROR_PATH_NOT_FOUND;
  }
});
/**
 * |true| if the error was raised because a directory is not empty
 * does not exist, |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
  get: function becauseNotEmpty() {
    return this.winLastError == Const.ERROR_DIR_NOT_EMPTY;
  }
});
/**
 * |true| if the error was raised because a file or directory
 * is closed, |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseClosed", {
  get: function becauseClosed() {
    return this.winLastError == Const.ERROR_INVALID_HANDLE;
  }
});
/**
 * |true| if the error was raised because permission is denied to
 * access a file or directory, |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
  get: function becauseAccessDenied() {
    return this.winLastError == Const.ERROR_ACCESS_DENIED;
  }
});
/**
 * |true| if the error was raised because some invalid argument was passed,
 * |false| otherwise.
 */
Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
  get: function becauseInvalidArgument() {
    return this.winLastError == Const.ERROR_NOT_SUPPORTED ||
           this.winLastError == Const.ERROR_BAD_ARGUMENTS;
  }
});

/**
 * Serialize an instance of OSError to something that can be
 * transmitted across threads (not necessarily a string).
 */
OSError.toMsg = function toMsg(error) {
  return {
    exn: "OS.File.Error",
    fileName: error.moduleName,
    lineNumber: error.lineNumber,
    stack: error.moduleStack,
    operation: error.operation,
    winLastError: error.winLastError,
    path: error.path
  };
};

/**
 * Deserialize a message back to an instance of OSError
 */
OSError.fromMsg = function fromMsg(msg) {
  let error = new OSError(msg.operation, msg.winLastError, msg.path);
  error.stack = msg.stack;
  error.fileName = msg.fileName;
  error.lineNumber = msg.lineNumber;
  return error;
};
exports.Error = OSError;

/**
 * Code shared by implementation of File.Info on Windows
 *
 * @constructor
 */
var AbstractInfo = function AbstractInfo(path, isDir, isSymLink, size,
                                         winBirthDate,
                                         lastAccessDate, lastWriteDate,
                                         winAttributes) {
  this._path = path;
  this._isDir = isDir;
  this._isSymLink = isSymLink;
  this._size = size;
  this._winBirthDate = winBirthDate;
  this._lastAccessDate = lastAccessDate;
  this._lastModificationDate = lastWriteDate;
  this._winAttributes = winAttributes;
};

AbstractInfo.prototype = {
  /**
   * The path of the file, used for error-reporting.
   *
   * @type {string}
   */
  get path() {
    return this._path;
  },
  /**
   * |true| if this file is a directory, |false| otherwise
   */
  get isDir() {
    return this._isDir;
  },
  /**
   * |true| if this file is a symbolic link, |false| otherwise
   */
  get isSymLink() {
    return this._isSymLink;
  },
  /**
   * The size of the file, in bytes.
   *
   * Note that the result may be |NaN| if the size of the file cannot be
   * represented in JavaScript.
   *
   * @type {number}
   */
  get size() {
    return this._size;
  },
  // Deprecated
  get creationDate() {
    return this._winBirthDate;
  },
  /**
   * The date of creation of this file.
   *
   * @type {Date}
   */
  get winBirthDate() {
    return this._winBirthDate;
  },
  /**
   * The date of last access to this file.
   *
   * Note that the definition of last access may depend on the underlying
   * operating system and file system.
   *
   * @type {Date}
   */
  get lastAccessDate() {
    return this._lastAccessDate;
  },
  /**
   * The date of last modification of this file.
   *
   * Note that the definition of last access may depend on the underlying
   * operating system and file system.
   *
   * @type {Date}
   */
  get lastModificationDate() {
    return this._lastModificationDate;
  },
  /**
   * The Object with following boolean properties of this file.
   * {readOnly, system, hidden}
   *
   * @type {object}
   */
  get winAttributes() {
    return this._winAttributes;
  }
};
exports.AbstractInfo = AbstractInfo;

/**
 * Code shared by implementation of File.DirectoryIterator.Entry on Windows
 *
 * @constructor
 */
var AbstractEntry = function AbstractEntry(isDir, isSymLink, name,
                                           winCreationDate, winLastWriteDate,
                                           winLastAccessDate, path) {
  this._isDir = isDir;
  this._isSymLink = isSymLink;
  this._name = name;
  this._winCreationDate = winCreationDate;
  this._winLastWriteDate = winLastWriteDate;
  this._winLastAccessDate = winLastAccessDate;
  this._path = path;
};

AbstractEntry.prototype = {
  /**
   * |true| if the entry is a directory, |false| otherwise
   */
  get isDir() {
    return this._isDir;
  },
  /**
   * |true| if the entry is a symbolic link, |false| otherwise
   */
  get isSymLink() {
    return this._isSymLink;
  },
  /**
   * The name of the entry.
   * @type {string}
   */
  get name() {
    return this._name;
  },
  /**
   * The creation time of this file.
   * @type {Date}
   */
  get winCreationDate() {
    return this._winCreationDate;
  },
  /**
   * The last modification time of this file.
   * @type {Date}
   */
  get winLastWriteDate() {
    return this._winLastWriteDate;
  },
  /**
   * The last access time of this file.
   * @type {Date}
   */
  get winLastAccessDate() {
    return this._winLastAccessDate;
  },
  /**
   * The full path of the entry
   * @type {string}
   */
  get path() {
    return this._path;
  }
};
exports.AbstractEntry = AbstractEntry;

// Special constants that need to be defined on all platforms

exports.POS_START = Const.FILE_BEGIN;
exports.POS_CURRENT = Const.FILE_CURRENT;
exports.POS_END = Const.FILE_END;

// Special types that need to be defined for communication
// between threads
var Type = Object.create(SharedAll.Type);
exports.Type = Type;

/**
 * Native paths
 *
 * Under Windows, expressed as wide strings
 */
Type.path = Type.wstring.withName("[in] path");
Type.out_path = Type.out_wstring.withName("[out] path");

// Special constructors that need to be defined on all threads
OSError.closed = function closed(operation, path) {
  return new OSError(operation, Const.ERROR_INVALID_HANDLE, path);
};

OSError.exists = function exists(operation, path) {
  return new OSError(operation, Const.ERROR_FILE_EXISTS, path);
};

OSError.noSuchFile = function noSuchFile(operation, path) {
  return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path);
};

OSError.invalidArgument = function invalidArgument(operation) {
  return new OSError(operation, Const.ERROR_NOT_SUPPORTED);
};

var EXPORTED_SYMBOLS = [
  "declareFFI",
  "libc",
  "Error",
  "AbstractInfo",
  "AbstractEntry",
  "Type",
  "POS_START",
  "POS_CURRENT",
  "POS_END"
];

//////////// Boilerplate
if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
  for (let symbol of EXPORTED_SYMBOLS) {
    this[symbol] = exports[symbol];
  }
}
PK
!<:;TJWDWD"modules/osfile/osfile_win_back.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file can be used in the following contexts:
 *
 *  1. included from a non-osfile worker thread using importScript
 *   (it serves to define a synchronous API for that worker thread)
 *   (bug 707681)
 *
 *  2. included from the main thread using Components.utils.import
 *   (it serves to define the asynchronous API, whose implementation
 *    resides in the worker thread)
 *   (bug 729057)
 *
 * 3. included from the osfile worker thread using importScript
 *   (it serves to define the implementation of the asynchronous API)
 *   (bug 729057)
 */

{
  if (typeof Components != "undefined") {
    // We do not wish osfile_win.jsm to be used directly as a main thread
    // module yet. When time comes, it will be loaded by a combination of
    // a main thread front-end/worker thread implementation that makes sure
    // that we are not executing synchronous IO code in the main thread.

    throw new Error("osfile_win.jsm cannot be used from the main thread yet");
  }

  (function(exports) {
     "use strict";
     if (exports.OS && exports.OS.Win && exports.OS.Win.File) {
       return; // Avoid double initialization
     }

     let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
     let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
     let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
     let libc = SysAll.libc;
     let advapi32 = new SharedAll.Library("advapi32", "advapi32.dll");
     let Const = SharedAll.Constants.Win;

     /**
      * Initialize the Windows module.
      *
      * @param {function=} declareFFI
      */
     // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
     let init = function init(aDeclareFFI) {
       let declareFFI;
       if (aDeclareFFI) {
         declareFFI = aDeclareFFI.bind(null, libc);
       } else {
         declareFFI = SysAll.declareFFI;
       }
       let declareLazyFFI = SharedAll.declareLazyFFI;

       // Initialize types that require additional OS-specific
       // support - either finalization or matching against
       // OS-specific constants.
       let Type = Object.create(SysAll.Type);
       let SysFile = exports.OS.Win.File = { Type: Type };

       // Initialize types

       /**
        * A C integer holding INVALID_HANDLE_VALUE in case of error or
        * a file descriptor in case of success.
        */
       Type.HANDLE =
         Type.voidptr_t.withName("HANDLE");
       Type.HANDLE.importFromC = function importFromC(maybe) {
         if (Type.int.cast(maybe).value == INVALID_HANDLE) {
           // Ensure that API clients can effectively compare against
           // Const.INVALID_HANDLE_VALUE. Without this cast,
           // == would always return |false|.
           return INVALID_HANDLE;
         }
         return ctypes.CDataFinalizer(maybe, this.finalizeHANDLE);
       };
       Type.HANDLE.finalizeHANDLE = function placeholder() {
         throw new Error("finalizeHANDLE should be implemented");
       };
       let INVALID_HANDLE = Const.INVALID_HANDLE_VALUE;

       Type.file_HANDLE = Type.HANDLE.withName("file HANDLE");
       SharedAll.defineLazyGetter(Type.file_HANDLE,
         "finalizeHANDLE",
         function() {
           return SysFile._CloseHandle;
         });

       Type.find_HANDLE = Type.HANDLE.withName("find HANDLE");
       SharedAll.defineLazyGetter(Type.find_HANDLE,
         "finalizeHANDLE",
         function() {
           return SysFile._FindClose;
         });

       Type.DWORD = Type.uint32_t.withName("DWORD");

       /* A special type used to represent flags passed as DWORDs to a function.
        * In JavaScript, bitwise manipulation of numbers, such as or-ing flags,
        * can produce negative numbers. Since DWORD is unsigned, these negative
        * numbers simply cannot be converted to DWORD. For this reason, whenever
        * bit manipulation is called for, you should rather use DWORD_FLAGS,
        * which is represented as a signed integer, hence has the correct
        * semantics.
        */
       Type.DWORD_FLAGS = Type.int32_t.withName("DWORD_FLAGS");

       /**
        * A C integer holding 0 in case of error or a positive integer
        * in case of success.
        */
       Type.zero_or_DWORD =
         Type.DWORD.withName("zero_or_DWORD");

       /**
        * A C integer holding 0 in case of error, any other value in
        * case of success.
        */
       Type.zero_or_nothing =
         Type.int.withName("zero_or_nothing");

       /**
        * A C integer holding flags related to NTFS security.
        */
       Type.SECURITY_ATTRIBUTES =
         Type.void_t.withName("SECURITY_ATTRIBUTES");

       /**
        * A C integer holding pointers related to NTFS security.
        */
       Type.PSID =
         Type.voidptr_t.withName("PSID");

       Type.PACL =
         Type.voidptr_t.withName("PACL");

       Type.PSECURITY_DESCRIPTOR =
         Type.voidptr_t.withName("PSECURITY_DESCRIPTOR");

       /**
        * A C integer holding Win32 local memory handle.
        */
       Type.HLOCAL =
         Type.voidptr_t.withName("HLOCAL");

       Type.FILETIME =
         new SharedAll.Type("FILETIME",
                  ctypes.StructType("FILETIME", [
                  { lo: Type.DWORD.implementation },
                  { hi: Type.DWORD.implementation }]));

       Type.FindData =
         new SharedAll.Type("FIND_DATA",
                  ctypes.StructType("FIND_DATA", [
                    { dwFileAttributes: ctypes.uint32_t },
                    { ftCreationTime:   Type.FILETIME.implementation },
                    { ftLastAccessTime: Type.FILETIME.implementation },
                    { ftLastWriteTime:  Type.FILETIME.implementation },
                    { nFileSizeHigh:    Type.DWORD.implementation },
                    { nFileSizeLow:     Type.DWORD.implementation },
                    { dwReserved0:      Type.DWORD.implementation },
                    { dwReserved1:      Type.DWORD.implementation },
                    { cFileName:        ctypes.ArrayType(ctypes.char16_t, Const.MAX_PATH) },
                    { cAlternateFileName: ctypes.ArrayType(ctypes.char16_t, 14) }
                      ]));

       Type.FILE_INFORMATION =
         new SharedAll.Type("FILE_INFORMATION",
                  ctypes.StructType("FILE_INFORMATION", [
                    { dwFileAttributes: ctypes.uint32_t },
                    { ftCreationTime:   Type.FILETIME.implementation },
                    { ftLastAccessTime: Type.FILETIME.implementation },
                    { ftLastWriteTime:  Type.FILETIME.implementation },
                    { dwVolumeSerialNumber: ctypes.uint32_t },
                    { nFileSizeHigh:    Type.DWORD.implementation },
                    { nFileSizeLow:     Type.DWORD.implementation },
                    { nNumberOfLinks:   ctypes.uint32_t },
                    { nFileIndex: ctypes.uint64_t }
                   ]));

       Type.SystemTime =
         new SharedAll.Type("SystemTime",
                  ctypes.StructType("SystemTime", [
                  { wYear:      ctypes.int16_t },
                  { wMonth:     ctypes.int16_t },
                  { wDayOfWeek: ctypes.int16_t },
                  { wDay:       ctypes.int16_t },
                  { wHour:      ctypes.int16_t },
                  { wMinute:    ctypes.int16_t },
                  { wSecond:    ctypes.int16_t },
                  { wMilliSeconds: ctypes.int16_t }
                  ]));

       // Special case: these functions are used by the
       // finalizer
       libc.declareLazy(SysFile, "_CloseHandle",
                        "CloseHandle", ctypes.winapi_abi,
                        /*return */ctypes.bool,
                        /*handle*/ ctypes.voidptr_t);

       SysFile.CloseHandle = function(fd) {
         if (fd == INVALID_HANDLE) {
           return true;
         } else {
           return fd.dispose(); // Returns the value of |CloseHandle|.
         }
       };

       libc.declareLazy(SysFile, "_FindClose",
                        "FindClose", ctypes.winapi_abi,
                        /*return */ctypes.bool,
                        /*handle*/ ctypes.voidptr_t);

       SysFile.FindClose = function(handle) {
         if (handle == INVALID_HANDLE) {
           return true;
         } else {
           return handle.dispose(); // Returns the value of |FindClose|.
         }
       };

       // Declare libc functions as functions of |OS.Win.File|

       libc.declareLazyFFI(SysFile, "CopyFile",
         "CopyFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*sourcePath*/ Type.path,
                    /*destPath*/   Type.path,
                    /*bailIfExist*/Type.bool);

       libc.declareLazyFFI(SysFile, "CreateDirectory",
         "CreateDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*name*/   Type.char16_t.in_ptr,
                    /*security*/Type.SECURITY_ATTRIBUTES.in_ptr);

       libc.declareLazyFFI(SysFile, "CreateFile",
         "CreateFileW", ctypes.winapi_abi,
                    /*return*/  Type.file_HANDLE,
                    /*name*/    Type.path,
                    /*access*/  Type.DWORD_FLAGS,
                    /*share*/   Type.DWORD_FLAGS,
                    /*security*/Type.SECURITY_ATTRIBUTES.in_ptr,
                    /*creation*/Type.DWORD_FLAGS,
                    /*flags*/   Type.DWORD_FLAGS,
                    /*template*/Type.HANDLE);

       libc.declareLazyFFI(SysFile, "DeleteFile",
         "DeleteFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path);

       libc.declareLazyFFI(SysFile, "FileTimeToSystemTime",
         "FileTimeToSystemTime", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*filetime*/Type.FILETIME.in_ptr,
                    /*systime*/ Type.SystemTime.out_ptr);

       libc.declareLazyFFI(SysFile, "SystemTimeToFileTime",
         "SystemTimeToFileTime", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*systime*/  Type.SystemTime.in_ptr,
                    /*filetime*/ Type.FILETIME.out_ptr);

       libc.declareLazyFFI(SysFile, "FindFirstFile",
         "FindFirstFileW", ctypes.winapi_abi,
                    /*return*/ Type.find_HANDLE,
                    /*pattern*/Type.path,
                    /*data*/   Type.FindData.out_ptr);

       libc.declareLazyFFI(SysFile, "FindNextFile",
         "FindNextFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*prev*/   Type.find_HANDLE,
                    /*data*/   Type.FindData.out_ptr);

       libc.declareLazyFFI(SysFile, "FormatMessage",
         "FormatMessageW", ctypes.winapi_abi,
                    /*return*/ Type.DWORD,
                    /*flags*/  Type.DWORD_FLAGS,
                    /*source*/ Type.void_t.in_ptr,
                    /*msgid*/  Type.DWORD_FLAGS,
                    /*langid*/ Type.DWORD_FLAGS,
                    /*buf*/    Type.out_wstring,
                    /*size*/   Type.DWORD,
                    /*Arguments*/Type.void_t.in_ptr
                   );

       libc.declareLazyFFI(SysFile, "GetCurrentDirectory",
         "GetCurrentDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_DWORD,
                    /*length*/ Type.DWORD,
                    /*buf*/    Type.out_path
                   );

       libc.declareLazyFFI(SysFile, "GetFullPathName",
         "GetFullPathNameW", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_DWORD,
                    /*fileName*/ Type.path,
                    /*length*/   Type.DWORD,
                    /*buf*/      Type.out_path,
                    /*filePart*/ Type.DWORD
                   );

       libc.declareLazyFFI(SysFile, "GetDiskFreeSpaceEx",
         "GetDiskFreeSpaceExW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*directoryName*/ Type.path,
                    /*freeBytesForUser*/ Type.uint64_t.out_ptr,
                    /*totalBytesForUser*/ Type.uint64_t.out_ptr,
                    /*freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr);

       libc.declareLazyFFI(SysFile, "GetFileInformationByHandle",
         "GetFileInformationByHandle", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*handle*/ Type.HANDLE,
                    /*info*/   Type.FILE_INFORMATION.out_ptr);

       libc.declareLazyFFI(SysFile, "MoveFileEx",
         "MoveFileExW", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*sourcePath*/ Type.path,
                    /*destPath*/ Type.path,
                    /*flags*/    Type.DWORD
                   );

       libc.declareLazyFFI(SysFile, "ReadFile",
         "ReadFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE,
                    /*buffer*/ Type.voidptr_t,
                    /*nbytes*/ Type.DWORD,
                    /*nbytes_read*/Type.DWORD.out_ptr,
                    /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
         );

       libc.declareLazyFFI(SysFile, "RemoveDirectory",
         "RemoveDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path);

       libc.declareLazyFFI(SysFile, "SetCurrentDirectory",
         "SetCurrentDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path
                   );

       libc.declareLazyFFI(SysFile, "SetEndOfFile",
         "SetEndOfFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE);

       libc.declareLazyFFI(SysFile, "SetFilePointer",
         "SetFilePointer", ctypes.winapi_abi,
                    /*return*/ Type.DWORD,
                    /*file*/   Type.HANDLE,
                    /*distlow*/Type.long,
                    /*disthi*/ Type.long.in_ptr,
                    /*method*/ Type.DWORD);

       libc.declareLazyFFI(SysFile, "SetFileTime",
         "SetFileTime",  ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*file*/     Type.HANDLE,
                    /*creation*/ Type.FILETIME.in_ptr,
                    /*access*/   Type.FILETIME.in_ptr,
                    /*write*/    Type.FILETIME.in_ptr);


       libc.declareLazyFFI(SysFile, "WriteFile",
         "WriteFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE,
                    /*buffer*/ Type.voidptr_t,
                    /*nbytes*/ Type.DWORD,
                    /*nbytes_wr*/Type.DWORD.out_ptr,
                    /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
         );

        libc.declareLazyFFI(SysFile, "FlushFileBuffers",
          "FlushFileBuffers", ctypes.winapi_abi,
                     /*return*/ Type.zero_or_nothing,
                     /*file*/   Type.HANDLE);

        libc.declareLazyFFI(SysFile, "GetFileAttributes",
          "GetFileAttributesW", ctypes.winapi_abi,
                     /*return*/   Type.DWORD_FLAGS,
                     /*fileName*/ Type.path);

        libc.declareLazyFFI(SysFile, "SetFileAttributes",
          "SetFileAttributesW", ctypes.winapi_abi,
                     /*return*/         Type.zero_or_nothing,
                     /*fileName*/       Type.path,
                     /*fileAttributes*/ Type.DWORD_FLAGS);

        advapi32.declareLazyFFI(SysFile, "GetNamedSecurityInfo",
          "GetNamedSecurityInfoW", ctypes.winapi_abi,
                     /*return*/       Type.DWORD,
                     /*objectName*/   Type.path,
                     /*objectType*/   Type.DWORD,
                     /*securityInfo*/ Type.DWORD,
                     /*sidOwner*/     Type.PSID.out_ptr,
                     /*sidGroup*/     Type.PSID.out_ptr,
                     /*dacl*/         Type.PACL.out_ptr,
                     /*sacl*/         Type.PACL.out_ptr,
                     /*securityDesc*/ Type.PSECURITY_DESCRIPTOR.out_ptr);

        advapi32.declareLazyFFI(SysFile, "SetNamedSecurityInfo",
          "SetNamedSecurityInfoW", ctypes.winapi_abi,
                     /*return*/       Type.DWORD,
                     /*objectName*/   Type.path,
                     /*objectType*/   Type.DWORD,
                     /*securityInfo*/ Type.DWORD,
                     /*sidOwner*/     Type.PSID,
                     /*sidGroup*/     Type.PSID,
                     /*dacl*/         Type.PACL,
                     /*sacl*/         Type.PACL);

        libc.declareLazyFFI(SysFile, "LocalFree",
          "LocalFree", ctypes.winapi_abi,
                     /*return*/       Type.HLOCAL,
                     /*mem*/          Type.HLOCAL);
     };

     exports.OS.Win = {
       File: {
           _init:  init
       }
     };
   })(this);
}
PK
!<.n;#modules/osfile/osfile_win_front.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Synchronous front-end for the JavaScript OS.File library.
 * Windows implementation.
 *
 * This front-end is meant to be imported by a worker thread.
 */

{
  if (typeof Components != "undefined") {
    // We do not wish osfile_win_front.jsm to be used directly as a main thread
    // module yet.
    throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
  }

  (function(exports) {
     "use strict";


      // exports.OS.Win is created by osfile_win_back.jsm
     if (exports.OS && exports.OS.File) {
        return; // Avoid double-initialization
     }

     let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
     let Path = require("resource://gre/modules/osfile/ospath.jsm");
     let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
     exports.OS.Win.File._init();
     let Const = exports.OS.Constants.Win;
     let WinFile = exports.OS.Win.File;
     let Type = WinFile.Type;

     // Mutable thread-global data
     // In the Windows implementation, methods |read| and |write|
     // require passing a pointer to an uint32 to determine how many
     // bytes have been read/written. In C, this is a benigne operation,
     // but in js-ctypes, this has a cost. Rather than re-allocating a
     // C uint32 and a C uint32* for each |read|/|write|, we take advantage
     // of the fact that the state is thread-private -- hence that two
     // |read|/|write| operations cannot take place at the same time --
     // and we use the following global mutable values:
     let gBytesRead = new ctypes.uint32_t(0);
     let gBytesReadPtr = gBytesRead.address();
     let gBytesWritten = new ctypes.uint32_t(0);
     let gBytesWrittenPtr = gBytesWritten.address();

     // Same story for GetFileInformationByHandle
     let gFileInfo = new Type.FILE_INFORMATION.implementation();
     let gFileInfoPtr = gFileInfo.address();

     /**
      * Representation of a file.
      *
      * You generally do not need to call this constructor yourself. Rather,
      * to open a file, use function |OS.File.open|.
      *
      * @param fd A OS-specific file descriptor.
      * @param {string} path File path of the file handle, used for error-reporting.
      * @constructor
      */
     let File = function File(fd, path) {
       exports.OS.Shared.AbstractFile.call(this, fd, path);
       this._closeResult = null;
     };
     File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);

     /**
      * Close the file.
      *
      * This method has no effect if the file is already closed. However,
      * if the first call to |close| has thrown an error, further calls
      * will throw the same error.
      *
      * @throws File.Error If closing the file revealed an error that could
      * not be reported earlier.
      */
     File.prototype.close = function close() {
       if (this._fd) {
         let fd = this._fd;
         this._fd = null;
         // Call |close(fd)|, detach finalizer if any
         // (|fd| may not be a CDataFinalizer if it has been
         // instantiated from a controller thread).
         let result = WinFile._CloseHandle(fd);
         if (typeof fd == "object" && "forget" in fd) {
           fd.forget();
         }
         if (result == -1) {
           this._closeResult = new File.Error("close", ctypes.winLastError, this._path);
         }
       }
       if (this._closeResult) {
         throw this._closeResult;
       }
       return;
     };

     /**
      * Read some bytes from a file.
      *
      * @param {C pointer} buffer A buffer for holding the data
      * once it is read.
      * @param {number} nbytes The number of bytes to read. It must not
      * exceed the size of |buffer| in bytes but it may exceed the number
      * of bytes unread in the file.
      * @param {*=} options Additional options for reading. Ignored in
      * this implementation.
      *
      * @return {number} The number of bytes effectively read. If zero,
      * the end of the file has been reached.
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.prototype._read = function _read(buffer, nbytes, options) {
       // |gBytesReadPtr| is a pointer to |gBytesRead|.
       throw_on_zero("read",
         WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null),
         this._path
       );
       return gBytesRead.value;
     };

     /**
      * Write some bytes to a file.
      *
      * @param {Typed array} buffer A buffer holding the data that must be
      * written.
      * @param {number} nbytes The number of bytes to write. It must not
      * exceed the size of |buffer| in bytes.
      * @param {*=} options Additional options for writing. Ignored in
      * this implementation.
      *
      * @return {number} The number of bytes effectively written.
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.prototype._write = function _write(buffer, nbytes, options) {
       if (this._appendMode) {
         // Need to manually seek on Windows, as O_APPEND is not supported.
         // This is, of course, a race, but there is no real way around this.
         this.setPosition(0, File.POS_END);
       }
       // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
       throw_on_zero("write",
         WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null),
         this._path
       );
       return gBytesWritten.value;
     };

     /**
      * Return the current position in the file.
      */
     File.prototype.getPosition = function getPosition(pos) {
       return this.setPosition(0, File.POS_CURRENT);
     };

     /**
      * Change the current position in the file.
      *
      * @param {number} pos The new position. Whether this position
      * is considered from the current position, from the start of
      * the file or from the end of the file is determined by
      * argument |whence|.  Note that |pos| may exceed the length of
      * the file.
      * @param {number=} whence The reference position. If omitted
      * or |OS.File.POS_START|, |pos| is relative to the start of the
      * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
      * current position in the file. If |OS.File.POS_END|, |pos| is
      * relative to the end of the file.
      *
      * @return The new position in the file.
      */
     File.prototype.setPosition = function setPosition(pos, whence) {
       if (whence === undefined) {
         whence = Const.FILE_BEGIN;
       }
       let pos64 = ctypes.Int64(pos);
       // Per MSDN, while |lDistanceToMove| (low) is declared as int32_t, when
       // providing |lDistanceToMoveHigh| as well, it should countain the
       // bottom 32 bits of the 64-bit integer. Hence the following |posLo|
       // cast is OK.
       let posLo = new ctypes.uint32_t(ctypes.Int64.lo(pos64));
       posLo = ctypes.cast(posLo, ctypes.int32_t);
       let posHi = new ctypes.int32_t(ctypes.Int64.hi(pos64));
       let result = WinFile.SetFilePointer(
         this.fd, posLo.value, posHi.address(), whence);
       // INVALID_SET_FILE_POINTER might be still a valid result, as it
       // represents the lower 32 bit of the int64 result. MSDN says to check
       // both, INVALID_SET_FILE_POINTER and a non-zero winLastError.
       if (result == Const.INVALID_SET_FILE_POINTER && ctypes.winLastError) {
         throw new File.Error("setPosition", ctypes.winLastError, this._path);
       }
       pos64 = ctypes.Int64.join(posHi.value, result);
       return Type.int64_t.project(pos64);
     };

     /**
      * Fetch the information on the file.
      *
      * @return File.Info The information on |this| file.
      */
     File.prototype.stat = function stat() {
       throw_on_zero("stat",
         WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr),
         this._path);
       return new File.Info(gFileInfo, this._path);
     };

     /**
      * Set the last access and modification date of the file.
      * The time stamp resolution is 1 second at best, but might be worse
      * depending on the platform.
      *
      * @param {Date,number=} accessDate The last access date. If numeric,
      * milliseconds since epoch. If omitted or null, then the current date
      * will be used.
      * @param {Date,number=} modificationDate The last modification date. If
      * numeric, milliseconds since epoch. If omitted or null, then the current
      * date will be used.
      *
      * @throws {TypeError} In case of invalid parameters.
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.prototype.setDates = function setDates(accessDate, modificationDate) {
       accessDate = Date_to_FILETIME("File.prototype.setDates", accessDate, this._path);
       modificationDate = Date_to_FILETIME("File.prototype.setDates",
                                           modificationDate,
                                           this._path);
       throw_on_zero("setDates",
                     WinFile.SetFileTime(this.fd, null, accessDate.address(),
                                         modificationDate.address()),
                     this._path);
     };

     /**
      * Set the file's access permission bits.
      */
     File.prototype.setPermissions = function setPermissions(options = {}) {
       if (!("winAttributes" in options)) {
         return;
       }
       let oldAttributes = WinFile.GetFileAttributes(this._path);
       if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
         throw new File.Error("setPermissions", ctypes.winLastError, this._path);
       }
       let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
       throw_on_zero("setPermissions",
                     WinFile.SetFileAttributes(this._path, newAttributes),
                     this._path);
     };

     /**
      * Flushes the file's buffers and causes all buffered data
      * to be written.
      * Disk flushes are very expensive and therefore should be used carefully,
      * sparingly and only in scenarios where it is vital that data survives
      * system crashes. Even though the function will be executed off the
      * main-thread, it might still affect the overall performance of any
      * running application.
      *
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.prototype.flush = function flush() {
       throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd), this._path);
     };

     // The default sharing mode for opening files: files are not
     // locked against being reopened for reading/writing or against
     // being deleted by the same process or another process.
     // This is consistent with the default Unix policy.
     const DEFAULT_SHARE = Const.FILE_SHARE_READ |
       Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;

     // The default flags for opening files.
     const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;

     /**
      * Open a file
      *
      * @param {string} path The path to the file.
      * @param {*=} mode The opening mode for the file, as
      * an object that may contain the following fields:
      *
      * - {bool} truncate If |true|, the file will be opened
      *  for writing. If the file does not exist, it will be
      *  created. If the file exists, its contents will be
      *  erased. Cannot be specified with |create|.
      * - {bool} create If |true|, the file will be opened
      *  for writing. If the file exists, this function fails.
      *  If the file does not exist, it will be created. Cannot
      *  be specified with |truncate| or |existing|.
      * - {bool} existing. If the file does not exist, this function
      *  fails. Cannot be specified with |create|.
      * - {bool} read If |true|, the file will be opened for
      *  reading. The file may also be opened for writing, depending
      *  on the other fields of |mode|.
      * - {bool} write If |true|, the file will be opened for
      *  writing. The file may also be opened for reading, depending
      *  on the other fields of |mode|.
      * - {bool} append If |true|, the file will be opened for appending,
      *  meaning the equivalent of |.setPosition(0, POS_END)| is executed
      *  before each write. The default is |true|, i.e. opening a file for
      *  appending. Specify |append: false| to open the file in regular mode.
      *
      * If neither |truncate|, |create| or |write| is specified, the file
      * is opened for reading.
      *
      * Note that |false|, |null| or |undefined| flags are simply ignored.
      *
      * @param {*=} options Additional options for file opening. This
      * implementation interprets the following fields:
      *
      * - {number} winShare If specified, a share mode, as per
      * Windows function |CreateFile|. You can build it from
      * constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
      * the file uses the default sharing policy: it can be opened
      * for reading and/or writing and it can be removed by other
      * processes and by the same process.
      * - {number} winSecurity If specified, Windows security
      * attributes, as per Windows function |CreateFile|. If unspecified,
      * no security attributes.
      * - {number} winAccess If specified, Windows access mode, as
      * per Windows function |CreateFile|. This also requires option
      * |winDisposition| and this replaces argument |mode|. If unspecified,
      * uses the string |mode|.
      * - {number} winDisposition If specified, Windows disposition mode,
      * as per Windows function |CreateFile|. This also requires option
      * |winAccess| and this replaces argument |mode|. If unspecified,
      * uses the string |mode|.
      *
      * @return {File} A file object.
      * @throws {OS.File.Error} If the file could not be opened.
      */
     File.open = function Win_open(path, mode = {}, options = {}) {
       let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
       let security = options.winSecurity || null;
       let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
       let template = options.winTemplate ? options.winTemplate._fd : null;
       let access;
       let disposition;

       mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);

       // The following option isn't a generic implementation of access to paths
       // of arbitrary lengths. It allows for the specific case of writing to an
       // Alternate Data Stream on a file whose path length is already close to
       // MAX_PATH. This implementation is safe with a full path as input, if
       // the first part of the path comes from local configuration and the
       // file without the ADS was successfully opened before, so we know the
       // path is valid.
       if (options.winAllowLengthBeyondMaxPathWithCaveats) {
         // Use the \\?\ syntax to allow lengths beyond MAX_PATH. This limited
         // implementation only supports a DOS local path or UNC path as input.
         let isUNC = path.length >= 2 && (path[0] == "\\" || path[0] == "/") &&
                                         (path[1] == "\\" || path[1] == "/");
         let pathToUse = "\\\\?\\" + (isUNC ? "UNC\\" + path.slice(2) : path);
         // Use GetFullPathName to normalize slashes into backslashes. This is
         // required because CreateFile won't do this for the \\?\ syntax.
         let buffer_size = 512;
         let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
         let expected_size = throw_on_zero("open",
           WinFile.GetFullPathName(pathToUse, buffer_size, array, 0)
         );
         if (expected_size > buffer_size) {
           // We don't need to allow an arbitrary path length for now.
           throw new File.Error("open", ctypes.winLastError, path);
         }
         path = array.readString();
       }

       if ("winAccess" in options && "winDisposition" in options) {
         access = options.winAccess;
         disposition = options.winDisposition;
       } else if (("winAccess" in options && !("winDisposition" in options))
                 ||(!("winAccess" in options) && "winDisposition" in options)) {
         throw new TypeError("OS.File.open requires either both options " +
           "winAccess and winDisposition or neither");
       } else {
         if (mode.read) {
           access |= Const.GENERIC_READ;
         }
         if (mode.write) {
           access |= Const.GENERIC_WRITE;
         }
         // Finally, handle create/existing/trunc
         if (mode.trunc) {
           if (mode.existing) {
             // It seems that Const.TRUNCATE_EXISTING is broken
             // in presence of links (source, anyone?). We need
             // to open normally, then perform truncation manually.
             disposition = Const.OPEN_EXISTING;
           } else {
             disposition = Const.CREATE_ALWAYS;
           }
         } else if (mode.create) {
           disposition = Const.CREATE_NEW;
         } else if (mode.read && !mode.write) {
           disposition = Const.OPEN_EXISTING;
         } else if (mode.existing) {
           disposition = Const.OPEN_EXISTING;
         } else {
           disposition = Const.OPEN_ALWAYS;
         }
       }

       let file = error_or_file(WinFile.CreateFile(path,
         access, share, security, disposition, flags, template), path);

       file._appendMode = !!mode.append;

       if (!(mode.trunc && mode.existing)) {
         return file;
       }
       // Now, perform manual truncation
       file.setPosition(0, File.POS_START);
       throw_on_zero("open",
         WinFile.SetEndOfFile(file.fd),
         path);
       return file;
     };

     /**
      * Checks if a file or directory exists
      *
      * @param {string} path The path to the file.
      *
      * @return {bool} true if the file exists, false otherwise.
      */
     File.exists = function Win_exists(path) {
       try {
         let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
         file.close();
         return true;
       } catch (x) {
         return false;
       }
     };

     /**
      * Remove an existing file.
      *
      * @param {string} path The name of the file.
      * @param {*=} options Additional options.
      *   - {bool} ignoreAbsent If |false|, throw an error if the file does
      *     not exist. |true| by default.
      *
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.remove = function remove(path, options = {}) {
       if (WinFile.DeleteFile(path)) {
         return;
       }

       if (ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND ||
           ctypes.winLastError == Const.ERROR_PATH_NOT_FOUND) {
         if ((!("ignoreAbsent" in options) || options.ignoreAbsent)) {
           return;
         }
       } else if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED) {
         // Save winLastError before another ctypes call.
         let lastError = ctypes.winLastError;
         let attributes = WinFile.GetFileAttributes(path);
         if (attributes != Const.INVALID_FILE_ATTRIBUTES) {
           if (!(attributes & Const.FILE_ATTRIBUTE_READONLY)) {
             throw new File.Error("remove", lastError, path);
           }
           let newAttributes = attributes & ~Const.FILE_ATTRIBUTE_READONLY;
           if (WinFile.SetFileAttributes(path, newAttributes) &&
               WinFile.DeleteFile(path)) {
             return;
           }
         }
       }

       throw new File.Error("remove", ctypes.winLastError, path);
     };

     /**
      * Remove an empty directory.
      *
      * @param {string} path The name of the directory to remove.
      * @param {*=} options Additional options.
      *   - {bool} ignoreAbsent If |false|, throw an error if the directory
      *     does not exist. |true| by default
      */
     File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
       let result = WinFile.RemoveDirectory(path);
       if (!result) {
         if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
             ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
           return;
         }
         throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
       }
     };

     /**
      * Create a directory and, optionally, its parent directories.
      *
      * @param {string} path The name of the directory.
      * @param {*=} options Additional options. This
      * implementation interprets the following fields:
      *
      * - {C pointer} winSecurity If specified, security attributes
      * as per winapi function |CreateDirectory|. If unspecified,
      * use the default security descriptor, inherited from the
      * parent directory.
      * - {bool} ignoreExisting If |false|, throw an error if the directory
      * already exists. |true| by default
      * - {string} from If specified, the call to |makeDir| creates all the
      * ancestors of |path| that are descendants of |from|. Note that |from|
      * and its existing descendants must be user-writeable and that |path|
      * must be a descendant of |from|.
      * Example:
      *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
      *  creates directories profileDir/foo, profileDir/foo/bar
       */
     File._makeDir = function makeDir(path, options = {}) {
       let security = options.winSecurity || null;
       let result = WinFile.CreateDirectory(path, security);

       if (result) {
         return;
       }

       if (("ignoreExisting" in options) && !options.ignoreExisting) {
         throw new File.Error("makeDir", ctypes.winLastError, path);
       }

       if (ctypes.winLastError == Const.ERROR_ALREADY_EXISTS) {
         return;
       }

       // If the user has no access, but it's a root directory, no error should be thrown
       let splitPath = OS.Path.split(path);
       // Removing last component if it's empty
       // An empty last component is caused by trailing slashes in path
       // This is always the case with root directories
       if( splitPath.components[splitPath.components.length - 1].length === 0 ) {
         splitPath.components.pop();
       }
       // One component consisting of a drive letter implies a directory root.
       if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED &&
           splitPath.winDrive &&
           splitPath.components.length === 1 ) {
         return;
       }

       throw new File.Error("makeDir", ctypes.winLastError, path);
     };

     /**
      * Copy a file to a destination.
      *
      * @param {string} sourcePath The platform-specific path at which
      * the file may currently be found.
      * @param {string} destPath The platform-specific path at which the
      * file should be copied.
      * @param {*=} options An object which may contain the following fields:
      *
      * @option {bool} noOverwrite - If true, this function will fail if
      * a file already exists at |destPath|. Otherwise, if this file exists,
      * it will be erased silently.
      *
      * @throws {OS.File.Error} In case of any error.
      *
      * General note: The behavior of this function is defined only when
      * it is called on a single file. If it is called on a directory, the
      * behavior is undefined and may not be the same across all platforms.
      *
      * General note: The behavior of this function with respect to metadata
      * is unspecified. Metadata may or may not be copied with the file. The
      * behavior may not be the same across all platforms.
     */
     File.copy = function copy(sourcePath, destPath, options = {}) {
       throw_on_zero("copy",
         WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false),
         sourcePath
       );
     };

     /**
      * Move a file to a destination.
      *
      * @param {string} sourcePath The platform-specific path at which
      * the file may currently be found.
      * @param {string} destPath The platform-specific path at which the
      * file should be moved.
      * @param {*=} options An object which may contain the following fields:
      *
      * @option {bool} noOverwrite - If set, this function will fail if
      * a file already exists at |destPath|. Otherwise, if this file exists,
      * it will be erased silently.
      * @option {bool} noCopy - If set, this function will fail if the
      * operation is more sophisticated than a simple renaming, i.e. if
      * |sourcePath| and |destPath| are not situated on the same drive.
      *
      * @throws {OS.File.Error} In case of any error.
      *
      * General note: The behavior of this function is defined only when
      * it is called on a single file. If it is called on a directory, the
      * behavior is undefined and may not be the same across all platforms.
      *
      * General note: The behavior of this function with respect to metadata
      * is unspecified. Metadata may or may not be moved with the file. The
      * behavior may not be the same across all platforms.
      */
     File.move = function move(sourcePath, destPath, options = {}) {
       let flags = 0;
       if (!options.noCopy) {
         flags = Const.MOVEFILE_COPY_ALLOWED;
       }
       if (!options.noOverwrite) {
         flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
       }
       throw_on_zero("move",
         WinFile.MoveFileEx(sourcePath, destPath, flags),
         sourcePath
       );

       // Inherit NTFS permissions from the destination directory
       // if possible.
       if (Path.dirname(sourcePath) === Path.dirname(destPath)) {
         // Skip if the move operation was the simple rename,
         return;
       }
       // The function may fail for various reasons (e.g. not all
       // filesystems support NTFS permissions or the user may not
       // have the enough rights to read/write permissions).
       // However we can safely ignore errors. The file was already
       // moved. Setting permissions is not mandatory.
       let dacl = new ctypes.voidptr_t();
       let sd = new ctypes.voidptr_t();
       WinFile.GetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
                                    Const.DACL_SECURITY_INFORMATION,
                                    null /*sidOwner*/, null /*sidGroup*/,
                                    dacl.address(), null /*sacl*/,
                                    sd.address());
       // dacl will be set only if the function succeeds.
       if (!dacl.isNull()) {
         WinFile.SetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
                                      Const.DACL_SECURITY_INFORMATION |
                                      Const.UNPROTECTED_DACL_SECURITY_INFORMATION,
                                      null /*sidOwner*/, null /*sidGroup*/,
                                      dacl, null /*sacl*/);
       }
       // sd will be set only if the function succeeds.
       if (!sd.isNull()) {
           WinFile.LocalFree(Type.HLOCAL.cast(sd));
       }
     };

     /**
      * Gets the number of bytes available on disk to the current user.
      *
      * @param {string} sourcePath Platform-specific path to a directory on 
      * the disk to query for free available bytes.
      *
      * @return {number} The number of bytes available for the current user.
      * @throws {OS.File.Error} In case of any error.
      */
     File.getAvailableFreeSpace = function Win_getAvailableFreeSpace(sourcePath) {
       let freeBytesAvailableToUser = new Type.uint64_t.implementation(0);
       let freeBytesAvailableToUserPtr = freeBytesAvailableToUser.address();

       throw_on_zero("getAvailableFreeSpace",
         WinFile.GetDiskFreeSpaceEx(sourcePath, freeBytesAvailableToUserPtr, null, null)
       );

       return freeBytesAvailableToUser.value;
     };

     /**
      * A global value used to receive data during time conversions.
      */
     let gSystemTime = new Type.SystemTime.implementation();
     let gSystemTimePtr = gSystemTime.address();

     /**
      * Utility function: convert a FILETIME to a JavaScript Date.
      */
     let FILETIME_to_Date = function FILETIME_to_Date(fileTime, path) {
       if (fileTime == null) {
         throw new TypeError("Expecting a non-null filetime");
       }
       throw_on_zero("FILETIME_to_Date",
                     WinFile.FileTimeToSystemTime(fileTime.address(),
                                                  gSystemTimePtr),
                     path);
       // Windows counts hours, minutes, seconds from UTC,
       // JS counts from local time, so we need to go through UTC.
       let utc = Date.UTC(gSystemTime.wYear,
                          gSystemTime.wMonth - 1
                          /*Windows counts months from 1, JS from 0*/,
                          gSystemTime.wDay, gSystemTime.wHour,
                          gSystemTime.wMinute, gSystemTime.wSecond,
                          gSystemTime.wMilliSeconds);
       return new Date(utc);
     };

     /**
      * Utility function: convert Javascript Date to FileTime.
      *
      * @param {string} fn Name of the calling function.
      * @param {Date,number} date The date to be converted. If omitted or null,
      * then the current date will be used. If numeric, assumed to be the date
      * in milliseconds since epoch.
      */
     let Date_to_FILETIME = function Date_to_FILETIME(fn, date, path) {
       if (typeof date === "number") {
         date = new Date(date);
       } else if (!date) {
         date = new Date();
       } else if (typeof date.getUTCFullYear !== "function") {
         throw new TypeError("|date| parameter of " + fn + " must be a " +
                             "|Date| instance or number");
       }
       gSystemTime.wYear = date.getUTCFullYear();
       // Windows counts months from 1, JS from 0.
       gSystemTime.wMonth = date.getUTCMonth() + 1;
       gSystemTime.wDay = date.getUTCDate();
       gSystemTime.wHour = date.getUTCHours();
       gSystemTime.wMinute = date.getUTCMinutes();
       gSystemTime.wSecond = date.getUTCSeconds();
       gSystemTime.wMilliseconds = date.getUTCMilliseconds();
       let result = new OS.Shared.Type.FILETIME.implementation();
       throw_on_zero("Date_to_FILETIME",
                     WinFile.SystemTimeToFileTime(gSystemTimePtr,
                                                  result.address()),
                     path);
       return result;
     };

     /**
      * Iterate on one directory.
      *
      * This iterator will not enter subdirectories.
      *
      * @param {string} path The directory upon which to iterate.
      * @param {*=} options An object that may contain the following field:
      * @option {string} winPattern Windows file name pattern; if set,
      * only files matching this pattern are returned.
      *
      * @throws {File.Error} If |path| does not represent a directory or
      * if the directory cannot be iterated.
      * @constructor
      */
     File.DirectoryIterator = function DirectoryIterator(path, options) {
       exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
       if (options && options.winPattern) {
         this._pattern = path + "\\" + options.winPattern;
       } else {
         this._pattern = path + "\\*";
       }
       this._path = path;

       // Pre-open the first item.
       this._first = true;
       this._findData = new Type.FindData.implementation();
       this._findDataPtr = this._findData.address();
       this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
       if (this._handle == Const.INVALID_HANDLE_VALUE) {
         let error = ctypes.winLastError;
         this._findData = null;
         this._findDataPtr = null;
         if (error == Const.ERROR_FILE_NOT_FOUND) {
           // Directory is empty, let's behave as if it were closed
           SharedAll.LOG("Directory is empty");
           this._closed = true;
           this._exists = true;
         } else if (error == Const.ERROR_PATH_NOT_FOUND) {
           // Directory does not exist, let's throw if we attempt to walk it
           SharedAll.LOG("Directory does not exist");
           this._closed = true;
           this._exists = false;
         } else {
           throw new File.Error("DirectoryIterator", error, this._path);
         }
       } else {
         this._closed = false;
         this._exists = true;
       }
     };

     File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);


     /**
      * Fetch the next entry in the directory.
      *
      * @return null If we have reached the end of the directory.
      */
     File.DirectoryIterator.prototype._next = function _next() {
       // Bailout if the directory does not exist
       if (!this._exists) {
         throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
       }
       // Bailout if the iterator is closed.
       if (this._closed) {
         return null;
       }
       // If this is the first entry, we have obtained it already
       // during construction.
       if (this._first) {
         this._first = false;
         return this._findData;
       }

       if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
         return this._findData;
       } else {
         let error = ctypes.winLastError;
         this.close();
         if (error == Const.ERROR_NO_MORE_FILES) {
            return null;
         } else {
            throw new File.Error("iter (FindNextFile)", error, this._path);
         }
       }
     },

     /**
      * Return the next entry in the directory, if any such entry is
      * available.
      *
      * Skip special directories "." and "..".
      *
      * @return {File.Entry} The next entry in the directory.
      * @throws {StopIteration} Once all files in the directory have been
      * encountered.
      */
     File.DirectoryIterator.prototype.next = function next() {
         // FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
         // that "." and ".." are absolutely normal file names if _path starts
         // with such prefix
         for (let entry = this._next(); entry != null; entry = this._next()) {
           let name = entry.cFileName.readString();
           if (name == "." || name == "..") {
             continue;
           }
           return new File.DirectoryIterator.Entry(entry, this._path);
         }
         throw StopIteration;
     };

     File.DirectoryIterator.prototype.close = function close() {
       if (this._closed) {
         return;
       }
       this._closed = true;
       if (this._handle) {
         // We might not have a handle if the iterator is closed
         // before being used.
         throw_on_zero("FindClose",
           WinFile.FindClose(this._handle),
           this._path);
         this._handle = null;
       }
     };

    /**
     * Determine whether the directory exists.
     *
     * @return {boolean}
     */
     File.DirectoryIterator.prototype.exists = function exists() {
       return this._exists;
     };

     File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
       if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
           !win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
        throw new TypeError();

       // Copy the relevant part of |win_entry| to ensure that
       // our data is not overwritten prematurely.
       let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
       let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);

       let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime, this._path);
       let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime, this._path);
       let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime, this._path);

       let name = win_entry.cFileName.readString();
       if (!name) {
         throw new TypeError("Empty name");
       }

       if (!parent) {
         throw new TypeError("Empty parent");
       }
       this._parent = parent;

       let path = Path.join(this._parent, name);

       SysAll.AbstractEntry.call(this, isDir, isSymLink, name,
         winCreationDate, winLastWriteDate,
         winLastAccessDate, path);
     };
     File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);

     /**
      * Return a version of an instance of
      * File.DirectoryIterator.Entry that can be sent from a worker
      * thread to the main thread. Note that deserialization is
      * asymmetric and returns an object with a different
      * implementation.
      */
     File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
       if (!(value instanceof File.DirectoryIterator.Entry)) {
         throw new TypeError("parameter of " +
           "File.DirectoryIterator.Entry.toMsg must be a " +
           "File.DirectoryIterator.Entry");
       }
       let serialized = {};
       for (let key in File.DirectoryIterator.Entry.prototype) {
         serialized[key] = value[key];
       }
       return serialized;
     };


     /**
      * Information on a file.
      *
      * To obtain the latest information on a file, use |File.stat|
      * (for an unopened file) or |File.prototype.stat| (for an
      * already opened file).
      *
      * @constructor
      */
     File.Info = function Info(stat, path) {
       let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
       let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);

       let winBirthDate = FILETIME_to_Date(stat.ftCreationTime, this._path);
       let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime, this._path);
       let lastWriteDate = FILETIME_to_Date(stat.ftLastWriteTime, this._path);

       let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
       let size = Type.uint64_t.importFromC(value);
       let winAttributes = {
         readOnly: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_READONLY),
         system: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_SYSTEM),
         hidden: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_HIDDEN),
       };

       SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
         winBirthDate, lastAccessDate, lastWriteDate, winAttributes);
     };
     File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);

     /**
      * Return a version of an instance of File.Info that can be sent
      * from a worker thread to the main thread. Note that deserialization
      * is asymmetric and returns an object with a different implementation.
      */
     File.Info.toMsg = function toMsg(stat) {
       if (!(stat instanceof File.Info)) {
         throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
       }
       let serialized = {};
       for (let key in File.Info.prototype) {
         serialized[key] = stat[key];
       }
       return serialized;
     };


     /**
      * Fetch the information on a file.
      *
      * Performance note: if you have opened the file already,
      * method |File.prototype.stat| is generally much faster
      * than method |File.stat|.
      *
      * Platform-specific note: under Windows, if the file is
      * already opened without sharing of the read capability,
      * this function will fail.
      *
      * @return {File.Information}
      */
     File.stat = function stat(path) {
       let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
       try {
         return file.stat();
       } finally {
         file.close();
       }
     };
     // All of the following is required to ensure that File.stat
     // also works on directories.
     const FILE_STAT_MODE = {
       read: true
     };
     const FILE_STAT_OPTIONS = {
       // Directories can be opened neither for reading(!) nor for writing
       winAccess: 0,
       // Directories can only be opened with backup semantics(!)
       winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
       winDisposition: Const.OPEN_EXISTING
     };

     /**
      * Set the file's access permission bits.
      */
     File.setPermissions = function setPermissions(path, options = {}) {
       if (!("winAttributes" in options)) {
         return;
       }
       let oldAttributes = WinFile.GetFileAttributes(path);
       if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
         throw new File.Error("setPermissions", ctypes.winLastError, path);
       }
       let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
       throw_on_zero("setPermissions",
                     WinFile.SetFileAttributes(path, newAttributes),
                     path);
     };

     /**
      * Set the last access and modification date of the file.
      * The time stamp resolution is 1 second at best, but might be worse
      * depending on the platform.
      *
      * Performance note: if you have opened the file already in write mode,
      * method |File.prototype.stat| is generally much faster
      * than method |File.stat|.
      *
      * Platform-specific note: under Windows, if the file is
      * already opened without sharing of the write capability,
      * this function will fail.
      *
      * @param {string} path The full name of the file to set the dates for.
      * @param {Date,number=} accessDate The last access date. If numeric,
      * milliseconds since epoch. If omitted or null, then the current date
      * will be used.
      * @param {Date,number=} modificationDate The last modification date. If
      * numeric, milliseconds since epoch. If omitted or null, then the current
      * date will be used.
      *
      * @throws {TypeError} In case of invalid paramters.
      * @throws {OS.File.Error} In case of I/O error.
      */
     File.setDates = function setDates(path, accessDate, modificationDate) {
       let file = File.open(path, FILE_SETDATES_MODE, FILE_SETDATES_OPTIONS);
       try {
         return file.setDates(accessDate, modificationDate);
       } finally {
         file.close();
       }
     };
     // All of the following is required to ensure that File.setDates
     // also works on directories.
     const FILE_SETDATES_MODE = {
       write: true
     };
     const FILE_SETDATES_OPTIONS = {
       winAccess: Const.GENERIC_WRITE,
       // Directories can only be opened with backup semantics(!)
       winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
       winDisposition: Const.OPEN_EXISTING
     };

     File.read = exports.OS.Shared.AbstractFile.read;
     File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
     File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
     File.makeDir = exports.OS.Shared.AbstractFile.makeDir;

     /**
      * Remove an existing directory and its contents.
      *
      * @param {string} path The name of the directory.
      * @param {*=} options Additional options.
      *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
      *     exist. |true| by default.
      *   - {boolean} ignorePermissions If |true|, remove the file even when lacking write
      *     permission.
      *
      * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
      *         not a directory.
      */
     File.removeDir = function(path, options = {}) {
       // We can't use File.stat here because it will follow the symlink.
       let attributes = WinFile.GetFileAttributes(path);
       if (attributes == Const.INVALID_FILE_ATTRIBUTES) {
         if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
             ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
           return;
         }
         throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
       }
       if (attributes & Const.FILE_ATTRIBUTE_REPARSE_POINT) {
         // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
         // directories are directories themselves. OS.File.remove()
         // will not work for them.
         OS.File.removeEmptyDir(path, options);
         return;
       }
       exports.OS.Shared.AbstractFile.removeRecursive(path, options);
     };

     /**
      * Get the current directory by getCurrentDirectory.
      */
     File.getCurrentDirectory = function getCurrentDirectory() {
       // This function is more complicated than one could hope.
       //
       // This is due to two facts:
       // - the maximal length of a path under Windows is not completely
       //  specified (there is a constant MAX_PATH, but it is quite possible
       //  to create paths that are much larger, see bug 744413);
       // - if we attempt to call |GetCurrentDirectory| with a buffer that
       //  is too short, it returns the length of the current directory, but
       //  this length might be insufficient by the time we can call again
       //  the function with a larger buffer, in the (unlikely but possible)
       //  case in which the process changes directory to a directory with
       //  a longer name between both calls.
       //
       let buffer_size = 4096;
       while (true) {
         let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
         let expected_size = throw_on_zero("getCurrentDirectory",
           WinFile.GetCurrentDirectory(buffer_size, array)
         );
         if (expected_size <= buffer_size) {
           return array.readString();
         }
         // At this point, we are in a case in which our buffer was not
         // large enough to hold the name of the current directory.
         // Consequently, we need to increase the size of the buffer.
         // Note that, even in crazy scenarios, the loop will eventually
         // converge, as the length of the paths cannot increase infinitely.
         buffer_size = expected_size + 1 /* to store \0 */;
       }
     };

     /**
      * Set the current directory by setCurrentDirectory.
      */
     File.setCurrentDirectory = function setCurrentDirectory(path) {
       throw_on_zero("setCurrentDirectory",
         WinFile.SetCurrentDirectory(path),
         path);
     };

     /**
      * Get/set the current directory by |curDir|.
      */
     Object.defineProperty(File, "curDir", {
         set: function(path) {
           this.setCurrentDirectory(path);
         },
         get: function() {
           return this.getCurrentDirectory();
         }
       }
     );

     // Utility functions, used for error-handling

     /**
      * Turn the result of |open| into an Error or a File
      * @param {number} maybe The result of the |open| operation that may
      * represent either an error or a success. If -1, this function raises
      * an error holding ctypes.winLastError, otherwise it returns the opened file.
      * @param {string=} path The path of the file.
      */
     function error_or_file(maybe, path) {
       if (maybe == Const.INVALID_HANDLE_VALUE) {
         throw new File.Error("open", ctypes.winLastError, path);
       }
       return new File(maybe, path);
     }

     /**
      * Utility function to sort errors represented as "0" from successes.
      *
      * @param {string=} operation The name of the operation. If unspecified,
      * the name of the caller function.
      * @param {number} result The result of the operation that may
      * represent either an error or a success. If 0, this function raises
      * an error holding ctypes.winLastError, otherwise it returns |result|.
      * @param {string=} path The path of the file.
      */
     function throw_on_zero(operation, result, path) {
       if (result == 0) {
         throw new File.Error(operation, ctypes.winLastError, path);
       }
       return result;
     }

     /**
      * Utility function to sort errors represented as "-1" from successes.
      *
      * @param {string=} operation The name of the operation. If unspecified,
      * the name of the caller function.
      * @param {number} result The result of the operation that may
      * represent either an error or a success. If -1, this function raises
      * an error holding ctypes.winLastError, otherwise it returns |result|.
      * @param {string=} path The path of the file.
      */
     function throw_on_negative(operation, result, path) {
       if (result < 0) {
         throw new File.Error(operation, ctypes.winLastError, path);
       }
       return result;
     }

     /**
      * Utility function to sort errors represented as |null| from successes.
      *
      * @param {string=} operation The name of the operation. If unspecified,
      * the name of the caller function.
      * @param {pointer} result The result of the operation that may
      * represent either an error or a success. If |null|, this function raises
      * an error holding ctypes.winLastError, otherwise it returns |result|.
      * @param {string=} path The path of the file.
      */
     function throw_on_null(operation, result, path) {
       if (result == null || (result.isNull && result.isNull())) {
         throw new File.Error(operation, ctypes.winLastError, path);
       }
       return result;
     }

     /**
      * Helper used by both versions of setPermissions
      */
     function toFileAttributes(winAttributes, oldDwAttrs) {
       if ("readOnly" in winAttributes) {
         if (winAttributes.readOnly) {
           oldDwAttrs |= Const.FILE_ATTRIBUTE_READONLY;
         } else {
           oldDwAttrs &= ~Const.FILE_ATTRIBUTE_READONLY;
         }
       }
       if ("system" in winAttributes) {
         if (winAttributes.system) {
           oldDwAttrs |= Const.FILE_ATTRIBUTE_SYSTEM;
         } else {
           oldDwAttrs &= ~Const.FILE_ATTRIBUTE_SYSTEM;
         }
       }
       if ("hidden" in winAttributes) {
         if (winAttributes.hidden) {
           oldDwAttrs |= Const.FILE_ATTRIBUTE_HIDDEN;
         } else {
           oldDwAttrs &= ~Const.FILE_ATTRIBUTE_HIDDEN;
         }
       }
       return oldDwAttrs;
     }

     File.Win = exports.OS.Win.File;
     File.Error = SysAll.Error;
     exports.OS.File = File;
     exports.OS.Shared.Type = Type;

     Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
     Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
     Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
   })(this);
}
PK
!<1	GGmodules/osfile/ospath.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handling native paths.
 *
 * This module contains a number of functions destined to simplify
 * working with native paths through a cross-platform API. Functions
 * of this module will only work with the following assumptions:
 *
 * - paths are valid;
 * - paths are defined with one of the grammars that this module can
 *   parse (see later);
 * - all path concatenations go through function |join|.
 */

"use strict";

if (typeof Components == "undefined") {
  let Path;
  if (OS.Constants.Win) {
    Path = require("resource://gre/modules/osfile/ospath_win.jsm");
  } else {
    Path = require("resource://gre/modules/osfile/ospath_unix.jsm");
  }
  module.exports = Path;
} else {
  let Cu = Components.utils;
  let Scope = {};
  Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", Scope);

  let Path = {};
  if (Scope.OS.Constants.Win) {
    Cu.import("resource://gre/modules/osfile/ospath_win.jsm", Path);
  } else {
    Cu.import("resource://gre/modules/osfile/ospath_unix.jsm", Path);
  }

  this.EXPORTED_SYMBOLS = [];
  for (let k in Path) {
    this.EXPORTED_SYMBOLS.push(k);
    this[k] = Path[k];
  }
}
PK
!<˟modules/osfile/ospath_unix.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handling native paths.
 *
 * This module contains a number of functions destined to simplify
 * working with native paths through a cross-platform API. Functions
 * of this module will only work with the following assumptions:
 *
 * - paths are valid;
 * - paths are defined with one of the grammars that this module can
 *   parse (see later);
 * - all path concatenations go through function |join|.
 */

"use strict";

// Boilerplate used to be able to import this module both from the main
// thread and from worker threads.
if (typeof Components != "undefined") {
  Components.utils.importGlobalProperties(["URL"]);
  // Global definition of |exports|, to keep everybody happy.
  // In non-main thread, |exports| is provided by the module
  // loader.
  this.exports = {};
} else if (typeof module == "undefined" || typeof exports == "undefined") {
  throw new Error("Please load this module using require()");
}

var EXPORTED_SYMBOLS = [
  "basename",
  "dirname",
  "join",
  "normalize",
  "split",
  "toFileURI",
  "fromFileURI",
];

/**
 * Return the final part of the path.
 * The final part of the path is everything after the last "/".
 */
var basename = function(path) {
  return path.slice(path.lastIndexOf("/") + 1);
};
exports.basename = basename;

/**
 * Return the directory part of the path.
 * The directory part of the path is everything before the last
 * "/". If the last few characters of this part are also "/",
 * they are ignored.
 *
 * If the path contains no directory, return ".".
 */
var dirname = function(path) {
  let index = path.lastIndexOf("/");
  if (index == -1) {
    return ".";
  }
  while (index >= 0 && path[index] == "/") {
    --index;
  }
  return path.slice(0, index + 1);
};
exports.dirname = dirname;

/**
 * Join path components.
 * This is the recommended manner of getting the path of a file/subdirectory
 * in a directory.
 *
 * Example: Obtaining $TMP/foo/bar in an OS-independent manner
 *  var tmpDir = OS.Constants.Path.tmpDir;
 *  var path = OS.Path.join(tmpDir, "foo", "bar");
 *
 * Under Unix, this will return "/tmp/foo/bar".
 *
 * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
 * same as `OS.Path.join("foo", "bar")`.
 */
var join = function(...path) {
  // If there is a path that starts with a "/", eliminate everything before
  let paths = [];
  for (let subpath of path) {
    if (subpath == null) {
      throw new TypeError("invalid path component");
    }
    if (subpath.length == 0) {
      continue;
    } else if (subpath[0] == "/") {
      paths = [subpath];
    } else {
      paths.push(subpath);
    }
  }
  return paths.join("/");
};
exports.join = join;

/**
 * Normalize a path by removing any unneeded ".", "..", "//".
 */
var normalize = function(path) {
  let stack = [];
  let absolute;
  if (path.length >= 0 && path[0] == "/") {
    absolute = true;
  } else {
    absolute = false;
  }
  path.split("/").forEach(function(v) {
    switch (v) {
    case "":  case ".":// fallthrough
      break;
    case "..":
      if (stack.length == 0) {
        if (absolute) {
          throw new Error("Path is ill-formed: attempting to go past root");
        } else {
          stack.push("..");
        }
      } else {
        if (stack[stack.length - 1] == "..") {
          stack.push("..");
        } else {
          stack.pop();
        }
      }
      break;
    default:
      stack.push(v);
    }
  });
  let string = stack.join("/");
  return absolute ? "/" + string : string;
};
exports.normalize = normalize;

/**
 * Return the components of a path.
 * You should generally apply this function to a normalized path.
 *
 * @return {{
 *   {bool} absolute |true| if the path is absolute, |false| otherwise
 *   {array} components the string components of the path
 * }}
 *
 * Other implementations may add additional OS-specific informations.
 */
var split = function(path) {
  return {
    absolute: path.length && path[0] == "/",
    components: path.split("/")
  };
};
exports.split = split;

/**
 * Returns the file:// URI file path of the given local file path.
 */
// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
var toFileURI = function toFileURI(path) {
  // Per https://url.spec.whatwg.org we should not encode [] in the path
  let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
  let uri = encodeURI(this.normalize(path)).replace(/%(5B|5D)/gi,
    match => dontNeedEscaping[match]);

  // add a prefix, and encodeURI doesn't escape a few characters that we do
  // want to escape, so fix that up
  let prefix = "file://";
  uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);

  return uri;
};
exports.toFileURI = toFileURI;

/**
 * Returns the local file path from a given file URI.
 */
var fromFileURI = function fromFileURI(uri) {
  let url = new URL(uri);
  if (url.protocol != 'file:') {
    throw new Error("fromFileURI expects a file URI");
  }
  let path = this.normalize(decodeURIComponent(url.pathname));
  return path;
};
exports.fromFileURI = fromFileURI;


//////////// Boilerplate
if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
  for (let symbol of EXPORTED_SYMBOLS) {
    this[symbol] = exports[symbol];
  }
}
PK
!<x[{'{'modules/osfile/ospath_win.jsm/**
 * Handling native paths.
 *
 * This module contains a number of functions destined to simplify
 * working with native paths through a cross-platform API. Functions
 * of this module will only work with the following assumptions:
 *
 * - paths are valid;
 * - paths are defined with one of the grammars that this module can
 *   parse (see later);
 * - all path concatenations go through function |join|.
 *
 * Limitations of this implementation.
 *
 * Windows supports 6 distinct grammars for paths. For the moment, this
 * implementation supports the following subset:
 *
 * - drivename:backslash-separated components
 * - backslash-separated components
 * - \\drivename\ followed by backslash-separated components
 *
 * Additionally, |normalize| can convert a path containing slash-
 * separated components to a path containing backslash-separated
 * components.
 */

"use strict";

// Boilerplate used to be able to import this module both from the main
// thread and from worker threads.
if (typeof Components != "undefined") {
  Components.utils.importGlobalProperties(["URL"]);
  // Global definition of |exports|, to keep everybody happy.
  // In non-main thread, |exports| is provided by the module
  // loader.
  this.exports = {};
} else if (typeof module == "undefined" || typeof exports == "undefined") {
  throw new Error("Please load this module using require()");
}

var EXPORTED_SYMBOLS = [
  "basename",
  "dirname",
  "join",
  "normalize",
  "split",
  "winGetDrive",
  "winIsAbsolute",
  "toFileURI",
  "fromFileURI",
];

/**
 * Return the final part of the path.
 * The final part of the path is everything after the last "\\".
 */
var basename = function(path) {
  if (path.startsWith("\\\\")) {
    // UNC-style path
    let index = path.lastIndexOf("\\");
    if (index != 1) {
      return path.slice(index + 1);
    }
    return ""; // Degenerate case
  }
  return path.slice(Math.max(path.lastIndexOf("\\"),
                             path.lastIndexOf(":")) + 1);
};
exports.basename = basename;

/**
 * Return the directory part of the path.
 *
 * If the path contains no directory, return the drive letter,
 * or "." if the path contains no drive letter or if option
 * |winNoDrive| is set.
 *
 * Otherwise, return everything before the last backslash,
 * including the drive/server name.
 *
 *
 * @param {string} path The path.
 * @param {*=} options Platform-specific options controlling the behavior
 * of this function. This implementation supports the following options:
 *  - |winNoDrive| If |true|, also remove the letter from the path name.
 */
var dirname = function(path, options) {
  let noDrive = (options && options.winNoDrive);

  // Find the last occurrence of "\\"
  let index = path.lastIndexOf("\\");
  if (index == -1) {
    // If there is no directory component...
    if (!noDrive) {
      // Return the drive path if possible, falling back to "."
      return this.winGetDrive(path) || ".";
    } else {
      // Or just "."
      return ".";
    }
  }

  if (index == 1 && path.charAt(0) == "\\") {
    // The path is reduced to a UNC drive
    if (noDrive) {
      return ".";
    } else {
      return path;
    }
  }

  // Ignore any occurrence of "\\: immediately before that one
  while (index >= 0 && path[index] == "\\") {
    --index;
  }

  // Compute what is left, removing the drive name if necessary
  let start;
  if (noDrive) {
    start = (this.winGetDrive(path) || "").length;
  } else {
    start = 0;
  }
  return path.slice(start, index + 1);
};
exports.dirname = dirname;

/**
 * Join path components.
 * This is the recommended manner of getting the path of a file/subdirectory
 * in a directory.
 *
 * Example: Obtaining $TMP/foo/bar in an OS-independent manner
 *  var tmpDir = OS.Constants.Path.tmpDir;
 *  var path = OS.Path.join(tmpDir, "foo", "bar");
 *
 * Under Windows, this will return "$TMP\foo\bar".
 *
 * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
 * same as `OS.Path.join("foo", "bar")`.
 */
var join = function(...path) {
  let paths = [];
  let root;
  let absolute = false;
  for (let subpath of path) {
    if (subpath == null) {
      throw new TypeError("invalid path component");
    }
    if (subpath == "") {
      continue;
    }
    let drive = this.winGetDrive(subpath);
    if (drive) {
      root = drive;
      let component = trimBackslashes(subpath.slice(drive.length));
      if (component) {
        paths = [component];
      } else {
        paths = [];
      }
      absolute = true;
    } else if (this.winIsAbsolute(subpath)) {
      paths = [trimBackslashes(subpath)];
      absolute = true;
    } else {
      paths.push(trimBackslashes(subpath));
    }
  }
  let result = "";
  if (root) {
    result += root;
  }
  if (absolute) {
    result += "\\";
  }
  result += paths.join("\\");
  return result;
};
exports.join = join;

/**
 * Return the drive name of a path, or |null| if the path does
 * not contain a drive name.
 *
 * Drive name appear either as "DriveName:..." (the return drive
 * name includes the ":") or "\\\\DriveName..." (the returned drive name
 * includes "\\\\").
 */
var winGetDrive = function(path) {
  if (path == null) {
    throw new TypeError("path is invalid");
  }

  if (path.startsWith("\\\\")) {
    // UNC path
    if (path.length == 2) {
      return null;
    }
    let index = path.indexOf("\\", 2);
    if (index == -1) {
      return path;
    }
    return path.slice(0, index);
  }
  // Non-UNC path
  let index = path.indexOf(":");
  if (index <= 0) return null;
  return path.slice(0, index + 1);
};
exports.winGetDrive = winGetDrive;

/**
 * Return |true| if the path is absolute, |false| otherwise.
 *
 * We consider that a path is absolute if it starts with "\\"
 * or "driveletter:\\".
 */
var winIsAbsolute = function(path) {
  let index = path.indexOf(":");
  return path.length > index + 1 && path[index + 1] == "\\";
};
exports.winIsAbsolute = winIsAbsolute;

/**
 * Normalize a path by removing any unneeded ".", "..", "\\".
 * Also convert any "/" to a "\\".
 */
var normalize = function(path) {
  let stack = [];

  if (!path.startsWith("\\\\")) {
    // Normalize "/" to "\\"
    path = path.replace(/\//g, "\\");
  }

  // Remove the drive (we will put it back at the end)
  let root = this.winGetDrive(path);
  if (root) {
    path = path.slice(root.length);
  }

  // Remember whether we need to restore a leading "\\" or drive name.
  let absolute = this.winIsAbsolute(path);

  // And now, fill |stack| from the components,
  // popping whenever there is a ".."
  path.split("\\").forEach(function loop(v) {
    switch (v) {
    case "":  case ".": // Ignore
      break;
    case "..":
      if (stack.length == 0) {
        if (absolute) {
          throw new Error("Path is ill-formed: attempting to go past root");
        } else {
         stack.push("..");
        }
      } else {
        if (stack[stack.length - 1] == "..") {
          stack.push("..");
        } else {
          stack.pop();
        }
      }
      break;
    default:
      stack.push(v);
    }
  });

  // Put everything back together
  let result = stack.join("\\");
  if (absolute || root) {
    result = "\\" + result;
  }
  if (root) {
    result = root + result;
  }
  return result;
};
exports.normalize = normalize;

/**
 * Return the components of a path.
 * You should generally apply this function to a normalized path.
 *
 * @return {{
 *   {bool} absolute |true| if the path is absolute, |false| otherwise
 *   {array} components the string components of the path
 *   {string?} winDrive the drive or server for this path
 * }}
 *
 * Other implementations may add additional OS-specific informations.
 */
var split = function(path) {
  return {
    absolute: this.winIsAbsolute(path),
    winDrive: this.winGetDrive(path),
    components: path.split("\\")
  };
};
exports.split = split;

/**
 * Return the file:// URI file path of the given local file path.
 */
// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
var toFileURI = function toFileURI(path) {
  // URI-escape forward slashes and convert backward slashes to forward
  path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F');
  // Per https://url.spec.whatwg.org we should not encode [] in the path
  let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
  let uri = encodeURI(path).replace(/%(5B|5D)/gi,
    match => dontNeedEscaping[match]);

  // add a prefix, and encodeURI doesn't escape a few characters that we do
  // want to escape, so fix that up
  let prefix = "file:///";
  uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);

  // turn e.g., file:///C: into file:///C:/
  if (uri.charAt(uri.length - 1) === ':') {
    uri += "/"
  }

  return uri;
};
exports.toFileURI = toFileURI;

/**
 * Returns the local file path from a given file URI.
 */
var fromFileURI = function fromFileURI(uri) {
  let url = new URL(uri);
  if (url.protocol != 'file:') {
    throw new Error("fromFileURI expects a file URI");
  }

  // strip leading slash, since Windows paths don't start with one
  uri = url.pathname.substr(1);

  let path = decodeURI(uri);
  // decode a few characters where URL's parsing is overzealous
  path = path.replace(/%(3b|3f|23)/gi,
        match => decodeURIComponent(match));
  path = this.normalize(path);

  // this.normalize() does not remove the trailing slash if the path
  // component is a drive letter. eg. 'C:\'' will not get normalized.
  if (path.endsWith(":\\")) {
    path = path.substr(0, path.length - 1);
  }
  return this.normalize(path);
};
exports.fromFileURI = fromFileURI;

/**
* Utility function: Remove any leading/trailing backslashes
* from a string.
*/
var trimBackslashes = function trimBackslashes(string) {
  return string.replace(/^\\+|\\+$/g,'');
};

//////////// Boilerplate
if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
  for (let symbol of EXPORTED_SYMBOLS) {
    this[symbol] = exports[symbol];
  }
}
PK
!<y&&modules/osfile.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Common front for various implementations of OS.File
 */

if (typeof Components != "undefined") {
  this.EXPORTED_SYMBOLS = ["OS"];
  Components.utils.import("resource://gre/modules/osfile/osfile_async_front.jsm", this);
} else {
  importScripts("resource://gre/modules/workers/require.js");

  var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");

  // At this stage, we need to import all sources at once to avoid
  // a unique failure on tbpl + talos that seems caused by a
  // what looks like a nested event loop bug (see bug 794091).
  if (SharedAll.Constants.Win) {
    importScripts(
      "resource://gre/modules/osfile/osfile_win_back.jsm",
      "resource://gre/modules/osfile/osfile_shared_front.jsm",
      "resource://gre/modules/osfile/osfile_win_front.jsm"
    );
  } else {
    importScripts(
      "resource://gre/modules/osfile/osfile_unix_back.jsm",
      "resource://gre/modules/osfile/osfile_shared_front.jsm",
      "resource://gre/modules/osfile/osfile_unix_front.jsm"
    );
  }

  OS.Path = require("resource://gre/modules/osfile/ospath.jsm");
}
PK
!<(G^^/modules/presentation/ControllerStateMachine.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
/* globals Components, dump */

"use strict";

this.EXPORTED_SYMBOLS = ["ControllerStateMachine"]; // jshint ignore:line

const { utils: Cu } = Components;

/* globals State, CommandType */
Cu.import("resource://gre/modules/presentation/StateMachineHelper.jsm");

const DEBUG = false;
function debug(str) {
  dump("-*- ControllerStateMachine: " + str + "\n");
}

var handlers = [
  function _initHandler(stateMachine, command) {
    // shouldn't receive any command at init state.
    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
  },
  function _connectingHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.CONNECT_ACK:
        stateMachine.state = State.CONNECTED;
        stateMachine._notifyDeviceConnected();
        break;
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command.
        break;
    }
  },
  function _connectedHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      case CommandType.LAUNCH_ACK:
        stateMachine._notifyLaunch(command.presentationId);
        break;
      case CommandType.TERMINATE:
        stateMachine._notifyTerminate(command.presentationId);
        break;
      case CommandType.TERMINATE_ACK:
        stateMachine._notifyTerminate(command.presentationId);
        break;
      case CommandType.ANSWER:
      case CommandType.ICE_CANDIDATE:
        stateMachine._notifyChannelDescriptor(command);
        break;
      case CommandType.RECONNECT_ACK:
        stateMachine._notifyReconnect(command.presentationId);
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command.
        break;
    }
  },
  function _closingHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command.
        break;
    }
  },
  function _closedHandler(stateMachine, command) {
    // ignore every command in closed state.
    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
  },
];

function ControllerStateMachine(channel, deviceId) {
  this.state = State.INIT;
  this._channel = channel;
  this._deviceId = deviceId;
}

ControllerStateMachine.prototype = {
  launch: function _launch(presentationId, url) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.LAUNCH,
        presentationId: presentationId,
        url: url,
      });
    }
  },

  terminate: function _terminate(presentationId) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.TERMINATE,
        presentationId: presentationId,
      });
    }
  },

  terminateAck: function _terminateAck(presentationId) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.TERMINATE_ACK,
        presentationId: presentationId,
      });
    }
  },

  reconnect: function _reconnect(presentationId, url) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.RECONNECT,
        presentationId: presentationId,
        url: url,
      });
    }
  },

  sendOffer: function _sendOffer(offer) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.OFFER,
        offer: offer,
      });
    }
  },

  sendAnswer: function _sendAnswer() {
    // answer can only be sent by presenting UA.
    debug("controller shouldn't generate answer");
  },

  updateIceCandidate: function _updateIceCandidate(candidate) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.ICE_CANDIDATE,
        candidate: candidate,
      });
    }
  },

  onCommand: function _onCommand(command) {
    handlers[this.state](this, command);
  },

  onChannelReady: function _onChannelReady() {
    if (this.state === State.INIT) {
      this._sendCommand({
        type: CommandType.CONNECT,
        deviceId: this._deviceId
      });
      this.state = State.CONNECTING;
    }
  },

  onChannelClosed: function _onChannelClose(reason, isByRemote) {
    switch (this.state) {
      case State.CONNECTED:
        if (isByRemote) {
          this.state = State.CLOSED;
          this._notifyDisconnected(reason);
        } else {
          this._sendCommand({
            type: CommandType.DISCONNECT,
            reason: reason
          });
          this.state = State.CLOSING;
          this._closeReason = reason;
        }
        break;
      case State.CLOSING:
        if (isByRemote) {
          this.state = State.CLOSED;
          if (this._closeReason) {
            reason = this._closeReason;
            delete this._closeReason;
          }
          this._notifyDisconnected(reason);
        }
        break;
      default:
        DEBUG && debug("unexpected channel close: " + reason + ", " + isByRemote); // jshint ignore:line
        break;
    }
  },

  _sendCommand: function _sendCommand(command) {
    this._channel.sendCommand(command);
  },

  _notifyDeviceConnected: function _notifyDeviceConnected() {
    //XXX trigger following command
    this._channel.notifyDeviceConnected();
  },

  _notifyDisconnected: function _notifyDisconnected(reason) {
    this._channel.notifyDisconnected(reason);
  },

  _notifyLaunch: function _notifyLaunch(presentationId) {
    this._channel.notifyLaunch(presentationId);
  },

  _notifyTerminate: function _notifyTerminate(presentationId) {
    this._channel.notifyTerminate(presentationId);
  },

  _notifyReconnect: function _notifyReconnect(presentationId) {
    this._channel.notifyReconnect(presentationId);
  },

  _notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
    switch (command.type) {
      case CommandType.ANSWER:
        this._channel.notifyAnswer(command.answer);
        break;
      case CommandType.ICE_CANDIDATE:
        this._channel.notifyIceCandidate(command.candidate);
        break;
    }
  },
};

this.ControllerStateMachine = ControllerStateMachine; // jshint ignore:line
PK
!<-modules/presentation/ReceiverStateMachine.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */
/* globals Components, dump */

"use strict";

this.EXPORTED_SYMBOLS = ["ReceiverStateMachine"]; // jshint ignore:line

const { utils: Cu } = Components;

/* globals State, CommandType */
Cu.import("resource://gre/modules/presentation/StateMachineHelper.jsm");

const DEBUG = false;
function debug(str) {
  dump("-*- ReceiverStateMachine: " + str + "\n");
}

var handlers = [
  function _initHandler(stateMachine, command) {
    // shouldn't receive any command at init state
    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
  },
  function _connectingHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.CONNECT:
        stateMachine._sendCommand({
          type: CommandType.CONNECT_ACK
        });
        stateMachine.state = State.CONNECTED;
        stateMachine._notifyDeviceConnected(command.deviceId);
        break;
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command
        break;
    }
  },
  function _connectedHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      case CommandType.LAUNCH:
        stateMachine._notifyLaunch(command.presentationId,
                                   command.url);
        stateMachine._sendCommand({
          type: CommandType.LAUNCH_ACK,
          presentationId: command.presentationId
        });
        break;
      case CommandType.TERMINATE:
        stateMachine._notifyTerminate(command.presentationId);
        break;
      case CommandType.TERMINATE_ACK:
        stateMachine._notifyTerminate(command.presentationId);
        break;
      case CommandType.OFFER:
      case CommandType.ICE_CANDIDATE:
        stateMachine._notifyChannelDescriptor(command);
        break;
      case CommandType.RECONNECT:
        stateMachine._notifyReconnect(command.presentationId,
                                      command.url);
        stateMachine._sendCommand({
          type: CommandType.RECONNECT_ACK,
          presentationId: command.presentationId
        });
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command
        break;
    }
  },
  function _closingHandler(stateMachine, command) {
    switch (command.type) {
      case CommandType.DISCONNECT:
        stateMachine.state = State.CLOSED;
        stateMachine._notifyDisconnected(command.reason);
        break;
      default:
        debug("unexpected command: " + JSON.stringify(command));
        // ignore unexpected command
        break;
    }
  },
  function _closedHandler(stateMachine, command) {
    // ignore every command in closed state.
    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
  },
];

function ReceiverStateMachine(channel) {
  this.state = State.INIT;
  this._channel = channel;
}

ReceiverStateMachine.prototype = {
  launch: function _launch() {
    // presentation session can only be launched by controlling UA.
    debug("receiver shouldn't trigger launch");
  },

  terminate: function _terminate(presentationId) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.TERMINATE,
        presentationId: presentationId,
      });
    }
  },

  terminateAck: function _terminateAck(presentationId) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.TERMINATE_ACK,
        presentationId: presentationId,
      });
    }
  },

  reconnect: function _reconnect() {
    debug("receiver shouldn't trigger reconnect");
  },

  sendOffer: function _sendOffer() {
    // offer can only be sent by controlling UA.
    debug("receiver shouldn't generate offer");
  },

  sendAnswer: function _sendAnswer(answer) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.ANSWER,
        answer: answer,
      });
    }
  },

  updateIceCandidate: function _updateIceCandidate(candidate) {
    if (this.state === State.CONNECTED) {
      this._sendCommand({
        type: CommandType.ICE_CANDIDATE,
        candidate: candidate,
      });
    }
  },

  onCommand: function _onCommand(command) {
    handlers[this.state](this, command);
  },

  onChannelReady: function _onChannelReady() {
    if (this.state === State.INIT) {
      this.state = State.CONNECTING;
    }
  },

  onChannelClosed: function _onChannelClose(reason, isByRemote) {
    switch (this.state) {
      case State.CONNECTED:
        if (isByRemote) {
          this.state = State.CLOSED;
          this._notifyDisconnected(reason);
        } else {
          this._sendCommand({
            type: CommandType.DISCONNECT,
            reason: reason
          });
          this.state = State.CLOSING;
          this._closeReason = reason;
        }
        break;
      case State.CLOSING:
        if (isByRemote) {
          this.state = State.CLOSED;
          if (this._closeReason) {
            reason = this._closeReason;
            delete this._closeReason;
          }
          this._notifyDisconnected(reason);
        } else {
          // do nothing and wait for remote channel closed.
        }
        break;
      default:
        DEBUG && debug("unexpected channel close: " + reason + ", " + isByRemote); // jshint ignore:line
        break;
    }
  },

  _sendCommand: function _sendCommand(command) {
    this._channel.sendCommand(command);
  },

  _notifyDeviceConnected: function _notifyDeviceConnected(deviceName) {
    this._channel.notifyDeviceConnected(deviceName);
  },

  _notifyDisconnected: function _notifyDisconnected(reason) {
    this._channel.notifyDisconnected(reason);
  },

  _notifyLaunch: function _notifyLaunch(presentationId, url) {
    this._channel.notifyLaunch(presentationId, url);
  },

  _notifyTerminate: function _notifyTerminate(presentationId) {
    this._channel.notifyTerminate(presentationId);
  },

  _notifyReconnect: function _notifyReconnect(presentationId, url) {
    this._channel.notifyReconnect(presentationId, url);
  },

  _notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
    switch (command.type) {
      case CommandType.OFFER:
        this._channel.notifyOffer(command.offer);
        break;
      case CommandType.ICE_CANDIDATE:
        this._channel.notifyIceCandidate(command.candidate);
        break;
    }
  },
};

this.ReceiverStateMachine = ReceiverStateMachine; // jshint ignore:line
PK
!<)@+modules/presentation/StateMachineHelper.jsm/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */

"use strict";

this.EXPORTED_SYMBOLS = ["State", "CommandType"]; // jshint ignore:line

const State = Object.freeze({
  INIT: 0,
  CONNECTING: 1,
  CONNECTED: 2,
  CLOSING: 3,
  CLOSED: 4,
});

const CommandType = Object.freeze({
  // control channel life cycle
  CONNECT: "connect", // { deviceId: <string> }
  CONNECT_ACK: "connect-ack", // { presentationId: <string> }
  DISCONNECT: "disconnect", // { reason: <int> }
  // presentation session life cycle
  LAUNCH: "launch", // { presentationId: <string>, url: <string> }
  LAUNCH_ACK: "launch-ack", // { presentationId: <string> }
  TERMINATE: "terminate", // { presentationId: <string> }
  TERMINATE_ACK: "terminate-ack", // { presentationId: <string> }
  RECONNECT: "reconnect", // { presentationId: <string> }
  RECONNECT_ACK: "reconnect-ack", // { presentationId: <string> }
  // session transport establishment
  OFFER: "offer", // { offer: <json> }
  ANSWER: "answer", // { answer: <json> }
  ICE_CANDIDATE: "ice-candidate", // { candidate: <string> }
});

this.State = State; // jshint ignore:line
this.CommandType = CommandType; // jshint ignore:line
PK
!<4tVQLjLjmodules/reader/JSDOMParser.js/*eslint-env es6:false*/
/*
 * DO NOT MODIFY THIS FILE DIRECTLY!
 *
 * This is a shared library that is maintained in an external repo:
 * https://github.com/mozilla/readability
 */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This is a relatively lightweight DOMParser that is safe to use in a web
 * worker. This is far from a complete DOM implementation; however, it should
 * contain the minimal set of functionality necessary for Readability.js.
 *
 * Aside from not implementing the full DOM API, there are other quirks to be
 * aware of when using the JSDOMParser:
 *
 *   1) Properly formed HTML/XML must be used. This means you should be extra
 *      careful when using this parser on anything received directly from an
 *      XMLHttpRequest. Providing a serialized string from an XMLSerializer,
 *      however, should be safe (since the browser's XMLSerializer should
 *      generate valid HTML/XML). Therefore, if parsing a document from an XHR,
 *      the recommended approach is to do the XHR in the main thread, use
 *      XMLSerializer.serializeToString() on the responseXML, and pass the
 *      resulting string to the worker.
 *
 *   2) Live NodeLists are not supported. DOM methods and properties such as
 *      getElementsByTagName() and childNodes return standard arrays. If you
 *      want these lists to be updated when nodes are removed or added to the
 *      document, you must take care to manually update them yourself.
 */
(function (global) {

  // XML only defines these and the numeric ones:

  var entityTable = {
    "lt": "<",
    "gt": ">",
    "amp": "&",
    "quot": '"',
    "apos": "'",
  };

  var reverseEntityTable = {
    "<": "&lt;",
    ">": "&gt;",
    "&": "&amp;",
    '"': "&quot;",
    "'": "&apos;",
  };

  function encodeTextContentHTML(s) {
    return s.replace(/[&<>]/g, function(x) {
      return reverseEntityTable[x];
    });
  }

  function encodeHTML(s) {
    return s.replace(/[&<>'"]/g, function(x) {
      return reverseEntityTable[x];
    });
  }

  function decodeHTML(str) {
    return str.replace(/&(quot|amp|apos|lt|gt);/g, function(match, tag) {
      return entityTable[tag];
    }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(match, hex, numStr) {
      var num = parseInt(hex || numStr, hex ? 16 : 10); // read num
      return String.fromCharCode(num);
    });
  }

  // When a style is set in JS, map it to the corresponding CSS attribute
  var styleMap = {
    "alignmentBaseline": "alignment-baseline",
    "background": "background",
    "backgroundAttachment": "background-attachment",
    "backgroundClip": "background-clip",
    "backgroundColor": "background-color",
    "backgroundImage": "background-image",
    "backgroundOrigin": "background-origin",
    "backgroundPosition": "background-position",
    "backgroundPositionX": "background-position-x",
    "backgroundPositionY": "background-position-y",
    "backgroundRepeat": "background-repeat",
    "backgroundRepeatX": "background-repeat-x",
    "backgroundRepeatY": "background-repeat-y",
    "backgroundSize": "background-size",
    "baselineShift": "baseline-shift",
    "border": "border",
    "borderBottom": "border-bottom",
    "borderBottomColor": "border-bottom-color",
    "borderBottomLeftRadius": "border-bottom-left-radius",
    "borderBottomRightRadius": "border-bottom-right-radius",
    "borderBottomStyle": "border-bottom-style",
    "borderBottomWidth": "border-bottom-width",
    "borderCollapse": "border-collapse",
    "borderColor": "border-color",
    "borderImage": "border-image",
    "borderImageOutset": "border-image-outset",
    "borderImageRepeat": "border-image-repeat",
    "borderImageSlice": "border-image-slice",
    "borderImageSource": "border-image-source",
    "borderImageWidth": "border-image-width",
    "borderLeft": "border-left",
    "borderLeftColor": "border-left-color",
    "borderLeftStyle": "border-left-style",
    "borderLeftWidth": "border-left-width",
    "borderRadius": "border-radius",
    "borderRight": "border-right",
    "borderRightColor": "border-right-color",
    "borderRightStyle": "border-right-style",
    "borderRightWidth": "border-right-width",
    "borderSpacing": "border-spacing",
    "borderStyle": "border-style",
    "borderTop": "border-top",
    "borderTopColor": "border-top-color",
    "borderTopLeftRadius": "border-top-left-radius",
    "borderTopRightRadius": "border-top-right-radius",
    "borderTopStyle": "border-top-style",
    "borderTopWidth": "border-top-width",
    "borderWidth": "border-width",
    "bottom": "bottom",
    "boxShadow": "box-shadow",
    "boxSizing": "box-sizing",
    "captionSide": "caption-side",
    "clear": "clear",
    "clip": "clip",
    "clipPath": "clip-path",
    "clipRule": "clip-rule",
    "color": "color",
    "colorInterpolation": "color-interpolation",
    "colorInterpolationFilters": "color-interpolation-filters",
    "colorProfile": "color-profile",
    "colorRendering": "color-rendering",
    "content": "content",
    "counterIncrement": "counter-increment",
    "counterReset": "counter-reset",
    "cursor": "cursor",
    "direction": "direction",
    "display": "display",
    "dominantBaseline": "dominant-baseline",
    "emptyCells": "empty-cells",
    "enableBackground": "enable-background",
    "fill": "fill",
    "fillOpacity": "fill-opacity",
    "fillRule": "fill-rule",
    "filter": "filter",
    "cssFloat": "float",
    "floodColor": "flood-color",
    "floodOpacity": "flood-opacity",
    "font": "font",
    "fontFamily": "font-family",
    "fontSize": "font-size",
    "fontStretch": "font-stretch",
    "fontStyle": "font-style",
    "fontVariant": "font-variant",
    "fontWeight": "font-weight",
    "glyphOrientationHorizontal": "glyph-orientation-horizontal",
    "glyphOrientationVertical": "glyph-orientation-vertical",
    "height": "height",
    "imageRendering": "image-rendering",
    "kerning": "kerning",
    "left": "left",
    "letterSpacing": "letter-spacing",
    "lightingColor": "lighting-color",
    "lineHeight": "line-height",
    "listStyle": "list-style",
    "listStyleImage": "list-style-image",
    "listStylePosition": "list-style-position",
    "listStyleType": "list-style-type",
    "margin": "margin",
    "marginBottom": "margin-bottom",
    "marginLeft": "margin-left",
    "marginRight": "margin-right",
    "marginTop": "margin-top",
    "marker": "marker",
    "markerEnd": "marker-end",
    "markerMid": "marker-mid",
    "markerStart": "marker-start",
    "mask": "mask",
    "maxHeight": "max-height",
    "maxWidth": "max-width",
    "minHeight": "min-height",
    "minWidth": "min-width",
    "opacity": "opacity",
    "orphans": "orphans",
    "outline": "outline",
    "outlineColor": "outline-color",
    "outlineOffset": "outline-offset",
    "outlineStyle": "outline-style",
    "outlineWidth": "outline-width",
    "overflow": "overflow",
    "overflowX": "overflow-x",
    "overflowY": "overflow-y",
    "padding": "padding",
    "paddingBottom": "padding-bottom",
    "paddingLeft": "padding-left",
    "paddingRight": "padding-right",
    "paddingTop": "padding-top",
    "page": "page",
    "pageBreakAfter": "page-break-after",
    "pageBreakBefore": "page-break-before",
    "pageBreakInside": "page-break-inside",
    "pointerEvents": "pointer-events",
    "position": "position",
    "quotes": "quotes",
    "resize": "resize",
    "right": "right",
    "shapeRendering": "shape-rendering",
    "size": "size",
    "speak": "speak",
    "src": "src",
    "stopColor": "stop-color",
    "stopOpacity": "stop-opacity",
    "stroke": "stroke",
    "strokeDasharray": "stroke-dasharray",
    "strokeDashoffset": "stroke-dashoffset",
    "strokeLinecap": "stroke-linecap",
    "strokeLinejoin": "stroke-linejoin",
    "strokeMiterlimit": "stroke-miterlimit",
    "strokeOpacity": "stroke-opacity",
    "strokeWidth": "stroke-width",
    "tableLayout": "table-layout",
    "textAlign": "text-align",
    "textAnchor": "text-anchor",
    "textDecoration": "text-decoration",
    "textIndent": "text-indent",
    "textLineThrough": "text-line-through",
    "textLineThroughColor": "text-line-through-color",
    "textLineThroughMode": "text-line-through-mode",
    "textLineThroughStyle": "text-line-through-style",
    "textLineThroughWidth": "text-line-through-width",
    "textOverflow": "text-overflow",
    "textOverline": "text-overline",
    "textOverlineColor": "text-overline-color",
    "textOverlineMode": "text-overline-mode",
    "textOverlineStyle": "text-overline-style",
    "textOverlineWidth": "text-overline-width",
    "textRendering": "text-rendering",
    "textShadow": "text-shadow",
    "textTransform": "text-transform",
    "textUnderline": "text-underline",
    "textUnderlineColor": "text-underline-color",
    "textUnderlineMode": "text-underline-mode",
    "textUnderlineStyle": "text-underline-style",
    "textUnderlineWidth": "text-underline-width",
    "top": "top",
    "unicodeBidi": "unicode-bidi",
    "unicodeRange": "unicode-range",
    "vectorEffect": "vector-effect",
    "verticalAlign": "vertical-align",
    "visibility": "visibility",
    "whiteSpace": "white-space",
    "widows": "widows",
    "width": "width",
    "wordBreak": "word-break",
    "wordSpacing": "word-spacing",
    "wordWrap": "word-wrap",
    "writingMode": "writing-mode",
    "zIndex": "z-index",
    "zoom": "zoom",
  };

  // Elements that can be self-closing
  var voidElems = {
    "area": true,
    "base": true,
    "br": true,
    "col": true,
    "command": true,
    "embed": true,
    "hr": true,
    "img": true,
    "input": true,
    "link": true,
    "meta": true,
    "param": true,
    "source": true,
    "wbr": true
  };

  var whitespace = [" ", "\t", "\n", "\r"];

  // See http://www.w3schools.com/dom/dom_nodetype.asp
  var nodeTypes = {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  };

  function getElementsByTagName(tag) {
    tag = tag.toUpperCase();
    var elems = [];
    var allTags = (tag === "*");
    function getElems(node) {
      var length = node.children.length;
      for (var i = 0; i < length; i++) {
        var child = node.children[i];
        if (allTags || (child.tagName === tag))
          elems.push(child);
        getElems(child);
      }
    }
    getElems(this);
    return elems;
  }

  var Node = function () {};

  Node.prototype = {
    attributes: null,
    childNodes: null,
    localName: null,
    nodeName: null,
    parentNode: null,
    textContent: null,
    nextSibling: null,
    previousSibling: null,

    get firstChild() {
      return this.childNodes[0] || null;
    },

    get firstElementChild() {
      return this.children[0] || null;
    },

    get lastChild() {
      return this.childNodes[this.childNodes.length - 1] || null;
    },

    get lastElementChild() {
      return this.children[this.children.length - 1] || null;
    },

    appendChild: function (child) {
      if (child.parentNode) {
        child.parentNode.removeChild(child);
      }

      var last = this.lastChild;
      if (last)
        last.nextSibling = child;
      child.previousSibling = last;

      if (child.nodeType === Node.ELEMENT_NODE) {
        child.previousElementSibling = this.children[this.children.length - 1] || null;
        this.children.push(child);
        child.previousElementSibling && (child.previousElementSibling.nextElementSibling = child);
      }
      this.childNodes.push(child);
      child.parentNode = this;
    },

    removeChild: function (child) {
      var childNodes = this.childNodes;
      var childIndex = childNodes.indexOf(child);
      if (childIndex === -1) {
        throw "removeChild: node not found";
      } else {
        child.parentNode = null;
        var prev = child.previousSibling;
        var next = child.nextSibling;
        if (prev)
          prev.nextSibling = next;
        if (next)
          next.previousSibling = prev;

        if (child.nodeType === Node.ELEMENT_NODE) {
          prev = child.previousElementSibling;
          next = child.nextElementSibling;
          if (prev)
            prev.nextElementSibling = next;
          if (next)
            next.previousElementSibling = prev;
          this.children.splice(this.children.indexOf(child), 1);
        }

        child.previousSibling = child.nextSibling = null;
        child.previousElementSibling = child.nextElementSibling = null;

        return childNodes.splice(childIndex, 1)[0];
      }
    },

    replaceChild: function (newNode, oldNode) {
      var childNodes = this.childNodes;
      var childIndex = childNodes.indexOf(oldNode);
      if (childIndex === -1) {
        throw "replaceChild: node not found";
      } else {
        // This will take care of updating the new node if it was somewhere else before:
        if (newNode.parentNode)
          newNode.parentNode.removeChild(newNode);

        childNodes[childIndex] = newNode;

        // update the new node's sibling properties, and its new siblings' sibling properties
        newNode.nextSibling = oldNode.nextSibling;
        newNode.previousSibling = oldNode.previousSibling;
        if (newNode.nextSibling)
          newNode.nextSibling.previousSibling = newNode;
        if (newNode.previousSibling)
          newNode.previousSibling.nextSibling = newNode;

        newNode.parentNode = this;

        // Now deal with elements before we clear out those values for the old node,
        // because it can help us take shortcuts here:
        if (newNode.nodeType === Node.ELEMENT_NODE) {
          if (oldNode.nodeType === Node.ELEMENT_NODE) {
            // Both were elements, which makes this easier, we just swap things out:
            newNode.previousElementSibling = oldNode.previousElementSibling;
            newNode.nextElementSibling = oldNode.nextElementSibling;
            if (newNode.previousElementSibling)
              newNode.previousElementSibling.nextElementSibling = newNode;
            if (newNode.nextElementSibling)
              newNode.nextElementSibling.previousElementSibling = newNode;
            this.children[this.children.indexOf(oldNode)] = newNode;
          } else {
            // Hard way:
            newNode.previousElementSibling = (function() {
              for (var i = childIndex - 1; i >= 0; i--) {
                if (childNodes[i].nodeType === Node.ELEMENT_NODE)
                  return childNodes[i];
              }
              return null;
            })();
            if (newNode.previousElementSibling) {
              newNode.nextElementSibling = newNode.previousElementSibling.nextElementSibling;
            } else {
              newNode.nextElementSibling = (function() {
                for (var i = childIndex + 1; i < childNodes.length; i++) {
                  if (childNodes[i].nodeType === Node.ELEMENT_NODE)
                    return childNodes[i];
                }
                return null;
              })();
            }
            if (newNode.previousElementSibling)
              newNode.previousElementSibling.nextElementSibling = newNode;
            if (newNode.nextElementSibling)
              newNode.nextElementSibling.previousElementSibling = newNode;

            if (newNode.nextElementSibling)
              this.children.splice(this.children.indexOf(newNode.nextElementSibling), 0, newNode);
            else
              this.children.push(newNode);
          }
        } else if (oldNode.nodeType === Node.ELEMENT_NODE) {
          // new node is not an element node.
          // if the old one was, update its element siblings:
          if (oldNode.previousElementSibling)
            oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling;
          if (oldNode.nextElementSibling)
            oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling;
          this.children.splice(this.children.indexOf(oldNode), 1);

          // If the old node wasn't an element, neither the new nor the old node was an element,
          // and the children array and its members shouldn't need any updating.
        }


        oldNode.parentNode = null;
        oldNode.previousSibling = null;
        oldNode.nextSibling = null;
        if (oldNode.nodeType === Node.ELEMENT_NODE) {
          oldNode.previousElementSibling = null;
          oldNode.nextElementSibling = null;
        }
        return oldNode;
      }
    },

    __JSDOMParser__: true,
  };

  for (var nodeType in nodeTypes) {
    Node[nodeType] = Node.prototype[nodeType] = nodeTypes[nodeType];
  }

  var Attribute = function (name, value) {
    this.name = name;
    this._value = value;
  };

  Attribute.prototype = {
    get value() {
      return this._value;
    },
    setValue: function(newValue) {
      this._value = newValue;
      delete this._decodedValue;
    },
    setDecodedValue: function(newValue) {
      this._value = encodeHTML(newValue);
      this._decodedValue = newValue;
    },
    getDecodedValue: function() {
      if (typeof this._decodedValue === "undefined") {
        this._decodedValue = (this._value && decodeHTML(this._value)) || "";
      }
      return this._decodedValue;
    },
  };

  var Comment = function () {
    this.childNodes = [];
  };

  Comment.prototype = {
    __proto__: Node.prototype,

    nodeName: "#comment",
    nodeType: Node.COMMENT_NODE
  };

  var Text = function () {
    this.childNodes = [];
  };

  Text.prototype = {
    __proto__: Node.prototype,

    nodeName: "#text",
    nodeType: Node.TEXT_NODE,
    get textContent() {
      if (typeof this._textContent === "undefined") {
        this._textContent = decodeHTML(this._innerHTML || "");
      }
      return this._textContent;
    },
    get innerHTML() {
      if (typeof this._innerHTML === "undefined") {
        this._innerHTML = encodeTextContentHTML(this._textContent || "");
      }
      return this._innerHTML;
    },

    set innerHTML(newHTML) {
      this._innerHTML = newHTML;
      delete this._textContent;
    },
    set textContent(newText) {
      this._textContent = newText;
      delete this._innerHTML;
    },
  };

  var Document = function () {
    this.styleSheets = [];
    this.childNodes = [];
    this.children = [];
  };

  Document.prototype = {
    __proto__: Node.prototype,

    nodeName: "#document",
    nodeType: Node.DOCUMENT_NODE,
    title: "",

    getElementsByTagName: getElementsByTagName,

    getElementById: function (id) {
      function getElem(node) {
        var length = node.children.length;
        if (node.id === id)
          return node;
        for (var i = 0; i < length; i++) {
          var el = getElem(node.children[i]);
          if (el)
            return el;
        }
        return null;
      }
      return getElem(this);
    },

    createElement: function (tag) {
      var node = new Element(tag);
      return node;
    },

    createTextNode: function (text) {
      var node = new Text();
      node.textContent = text;
      return node;
    },
  };

  var Element = function (tag) {
    this.attributes = [];
    this.childNodes = [];
    this.children = [];
    this.nextElementSibling = this.previousElementSibling = null;
    this.localName = tag.toLowerCase();
    this.tagName = tag.toUpperCase();
    this.style = new Style(this);
  };

  Element.prototype = {
    __proto__: Node.prototype,

    nodeType: Node.ELEMENT_NODE,

    getElementsByTagName: getElementsByTagName,

    get className() {
      return this.getAttribute("class") || "";
    },

    set className(str) {
      this.setAttribute("class", str);
    },

    get id() {
      return this.getAttribute("id") || "";
    },

    set id(str) {
      this.setAttribute("id", str);
    },

    get href() {
      return this.getAttribute("href") || "";
    },

    set href(str) {
      this.setAttribute("href", str);
    },

    get src() {
      return this.getAttribute("src") || "";
    },

    set src(str) {
      this.setAttribute("src", str);
    },

    get nodeName() {
      return this.tagName;
    },

    get innerHTML() {
      function getHTML(node) {
        var i = 0;
        for (i = 0; i < node.childNodes.length; i++) {
          var child = node.childNodes[i];
          if (child.localName) {
            arr.push("<" + child.localName);

            // serialize attribute list
            for (var j = 0; j < child.attributes.length; j++) {
              var attr = child.attributes[j];
              // the attribute value will be HTML escaped.
              var val = attr.value;
              var quote = (val.indexOf('"') === -1 ? '"' : "'");
              arr.push(" " + attr.name + '=' + quote + val + quote);
            }

            if (child.localName in voidElems && !child.childNodes.length) {
              // if this is a self-closing element, end it here
              arr.push("/>");
            } else {
              // otherwise, add its children
              arr.push(">");
              getHTML(child);
              arr.push("</" + child.localName + ">");
            }
          } else {
            // This is a text node, so asking for innerHTML won't recurse.
            arr.push(child.innerHTML);
          }
        }
      }

      // Using Array.join() avoids the overhead from lazy string concatenation.
      // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
      var arr = [];
      getHTML(this);
      return arr.join("");
    },

    set innerHTML(html) {
      var parser = new JSDOMParser();
      var node = parser.parse(html);
      var i;
      for (i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = null;
      }
      this.childNodes = node.childNodes;
      this.children = node.children;
      for (i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = this;
      }
    },

    set textContent(text) {
      // clear parentNodes for existing children
      for (var i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = null;
      }

      var node = new Text();
      this.childNodes = [ node ];
      this.children = [];
      node.textContent = text;
      node.parentNode = this;
    },

    get textContent() {
      function getText(node) {
        var nodes = node.childNodes;
        for (var i = 0; i < nodes.length; i++) {
          var child = nodes[i];
          if (child.nodeType === 3) {
            text.push(child.textContent);
          } else {
            getText(child);
          }
        }
      }

      // Using Array.join() avoids the overhead from lazy string concatenation.
      // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
      var text = [];
      getText(this);
      return text.join("");
    },

    getAttribute: function (name) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name)
          return attr.getDecodedValue();
      }
      return undefined;
    },

    setAttribute: function (name, value) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name) {
          attr.setDecodedValue(value);
          return;
        }
      }
      this.attributes.push(new Attribute(name, encodeHTML(value)));
    },

    removeAttribute: function (name) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name) {
          this.attributes.splice(i, 1);
          break;
        }
      }
    }
  };

  var Style = function (node) {
    this.node = node;
  };

  // getStyle() and setStyle() use the style attribute string directly. This
  // won't be very efficient if there are a lot of style manipulations, but
  // it's the easiest way to make sure the style attribute string and the JS
  // style property stay in sync. Readability.js doesn't do many style
  // manipulations, so this should be okay.
  Style.prototype = {
    getStyle: function (styleName) {
      var attr = this.node.getAttribute("style");
      if (!attr)
        return undefined;

      var styles = attr.split(";");
      for (var i = 0; i < styles.length; i++) {
        var style = styles[i].split(":");
        var name = style[0].trim();
        if (name === styleName)
          return style[1].trim();
      }

      return undefined;
    },

    setStyle: function (styleName, styleValue) {
      var value = this.node.getAttribute("style") || "";
      var index = 0;
      do {
        var next = value.indexOf(";", index) + 1;
        var length = next - index - 1;
        var style = (length > 0 ? value.substr(index, length) : value.substr(index));
        if (style.substr(0, style.indexOf(":")).trim() === styleName) {
          value = value.substr(0, index).trim() + (next ? " " + value.substr(next).trim() : "");
          break;
        }
        index = next;
      } while (index);

      value += " " + styleName + ": " + styleValue + ";";
      this.node.setAttribute("style", value.trim());
    }
  };

  // For each item in styleMap, define a getter and setter on the style
  // property.
  for (var jsName in styleMap) {
    (function (cssName) {
      Style.prototype.__defineGetter__(jsName, function () {
        return this.getStyle(cssName);
      });
      Style.prototype.__defineSetter__(jsName, function (value) {
        this.setStyle(cssName, value);
      });
    })(styleMap[jsName]);
  }

  var JSDOMParser = function () {
    this.currentChar = 0;

    // In makeElementNode() we build up many strings one char at a time. Using
    // += for this results in lots of short-lived intermediate strings. It's
    // better to build an array of single-char strings and then join() them
    // together at the end. And reusing a single array (i.e. |this.strBuf|)
    // over and over for this purpose uses less memory than using a new array
    // for each string.
    this.strBuf = [];

    // Similarly, we reuse this array to return the two arguments from
    // makeElementNode(), which saves us from having to allocate a new array
    // every time.
    this.retPair = [];

    this.errorState = "";
  };

  JSDOMParser.prototype = {
    error: function(m) {
      dump("JSDOMParser error: " + m + "\n");
      this.errorState += m + "\n";
    },

    /**
     * Look at the next character without advancing the index.
     */
    peekNext: function () {
      return this.html[this.currentChar];
    },

    /**
     * Get the next character and advance the index.
     */
    nextChar: function () {
      return this.html[this.currentChar++];
    },

    /**
     * Called after a quote character is read. This finds the next quote
     * character and returns the text string in between.
     */
    readString: function (quote) {
      var str;
      var n = this.html.indexOf(quote, this.currentChar);
      if (n === -1) {
        this.currentChar = this.html.length;
        str = null;
      } else {
        str = this.html.substring(this.currentChar, n);
        this.currentChar = n + 1;
      }

      return str;
    },

    /**
     * Called when parsing a node. This finds the next name/value attribute
     * pair and adds the result to the attributes list.
     */
    readAttribute: function (node) {
      var name = "";

      var n = this.html.indexOf("=", this.currentChar);
      if (n === -1) {
        this.currentChar = this.html.length;
      } else {
        // Read until a '=' character is hit; this will be the attribute key
        name = this.html.substring(this.currentChar, n);
        this.currentChar = n + 1;
      }

      if (!name)
        return;

      // After a '=', we should see a '"' for the attribute value
      var c = this.nextChar();
      if (c !== '"' && c !== "'") {
        this.error("Error reading attribute " + name + ", expecting '\"'");
        return;
      }

      // Read the attribute value (and consume the matching quote)
      var value = this.readString(c);

      node.attributes.push(new Attribute(name, value));

      return;
    },

    /**
     * Parses and returns an Element node. This is called after a '<' has been
     * read.
     *
     * @returns an array; the first index of the array is the parsed node;
     *          the second index is a boolean indicating whether this is a void
     *          Element
     */
    makeElementNode: function (retPair) {
      var c = this.nextChar();

      // Read the Element tag name
      var strBuf = this.strBuf;
      strBuf.length = 0;
      while (whitespace.indexOf(c) == -1 && c !== ">" && c !== "/") {
        if (c === undefined)
          return false;
        strBuf.push(c);
        c = this.nextChar();
      }
      var tag = strBuf.join('');

      if (!tag)
        return false;

      var node = new Element(tag);

      // Read Element attributes
      while (c !== "/" && c !== ">") {
        if (c === undefined)
          return false;
        while (whitespace.indexOf(this.html[this.currentChar++]) != -1);
        this.currentChar--;
        c = this.nextChar();
        if (c !== "/" && c !== ">") {
          --this.currentChar;
          this.readAttribute(node);
        }
      }

      // If this is a self-closing tag, read '/>'
      var closed = false;
      if (c === "/") {
        closed = true;
        c = this.nextChar();
        if (c !== ">") {
          this.error("expected '>' to close " + tag);
          return false;
        }
      }

      retPair[0] = node;
      retPair[1] = closed;
      return true;
    },

    /**
     * If the current input matches this string, advance the input index;
     * otherwise, do nothing.
     *
     * @returns whether input matched string
     */
    match: function (str) {
      var strlen = str.length;
      if (this.html.substr(this.currentChar, strlen).toLowerCase() === str.toLowerCase()) {
        this.currentChar += strlen;
        return true;
      }
      return false;
    },

    /**
     * Searches the input until a string is found and discards all input up to
     * and including the matched string.
     */
    discardTo: function (str) {
      var index = this.html.indexOf(str, this.currentChar) + str.length;
      if (index === -1)
        this.currentChar = this.html.length;
      this.currentChar = index;
    },

    /**
     * Reads child nodes for the given node.
     */
    readChildren: function (node) {
      var child;
      while ((child = this.readNode())) {
        // Don't keep Comment nodes
        if (child.nodeType !== 8) {
          node.appendChild(child);
        }
      }
    },

    discardNextComment: function() {
      if (this.match("--")) {
        this.discardTo("-->");
      } else {
        var c = this.nextChar();
        while (c !== ">") {
          if (c === undefined)
            return null;
          if (c === '"' || c === "'")
            this.readString(c);
          c = this.nextChar();
        }
      }
      return new Comment();
    },


    /**
     * Reads the next child node from the input. If we're reading a closing
     * tag, or if we've reached the end of input, return null.
     *
     * @returns the node
     */
    readNode: function () {
      var c = this.nextChar();

      if (c === undefined)
        return null;

      // Read any text as Text node
      if (c !== "<") {
        --this.currentChar;
        var textNode = new Text();
        var n = this.html.indexOf("<", this.currentChar);
        if (n === -1) {
          textNode.innerHTML = this.html.substring(this.currentChar, this.html.length);
          this.currentChar = this.html.length;
        } else {
          textNode.innerHTML = this.html.substring(this.currentChar, n);
          this.currentChar = n;
        }
        return textNode;
      }

      c = this.peekNext();

      // Read Comment node. Normally, Comment nodes know their inner
      // textContent, but we don't really care about Comment nodes (we throw
      // them away in readChildren()). So just returning an empty Comment node
      // here is sufficient.
      if (c === "!" || c === "?") {
        // We're still before the ! or ? that is starting this comment:
        this.currentChar++;
        return this.discardNextComment();
      }

      // If we're reading a closing tag, return null. This means we've reached
      // the end of this set of child nodes.
      if (c === "/") {
        --this.currentChar;
        return null;
      }

      // Otherwise, we're looking at an Element node
      var result = this.makeElementNode(this.retPair);
      if (!result)
        return null;

      var node = this.retPair[0];
      var closed = this.retPair[1];
      var localName = node.localName;

      // If this isn't a void Element, read its child nodes
      if (!closed) {
        this.readChildren(node);
        var closingTag = "</" + localName + ">";
        if (!this.match(closingTag)) {
          this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length));
          return null;
        }
      }

      // Only use the first title, because SVG might have other
      // title elements which we don't care about (medium.com
      // does this, at least).
      if (localName === "title" && !this.doc.title) {
        this.doc.title = node.textContent.trim();
      } else if (localName === "head") {
        this.doc.head = node;
      } else if (localName === "body") {
        this.doc.body = node;
      } else if (localName === "html") {
        this.doc.documentElement = node;
      }

      return node;
    },

    /**
     * Parses an HTML string and returns a JS implementation of the Document.
     */
    parse: function (html) {
      this.html = html;
      var doc = this.doc = new Document();
      this.readChildren(doc);

      // If this is an HTML document, remove root-level children except for the
      // <html> node
      if (doc.documentElement) {
        for (var i = doc.childNodes.length; --i >= 0;) {
          var child = doc.childNodes[i];
          if (child !== doc.documentElement) {
            doc.removeChild(child);
          }
        }
      }

      return doc;
    }
  };

  // Attach the standard DOM types to the global scope
  global.Node = Node;
  global.Comment = Comment;
  global.Document = Document;
  global.Element = Element;
  global.Text = Text;

  // Attach JSDOMParser to the global scope
  global.JSDOMParser = JSDOMParser;

})(this);
PK
!<modules/reader/Readability.js/*eslint-env es6:false*/
/*
 * DO NOT MODIFY THIS FILE DIRECTLY!
 *
 * This is a shared library that is maintained in an external repo:
 * https://github.com/mozilla/readability
 */

/*
 * Copyright (c) 2010 Arc90 Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This code is heavily based on Arc90's readability.js (1.7.1) script
 * available at: http://code.google.com/p/arc90labs-readability
 */

/**
 * Public constructor.
 * @param {Object}       uri     The URI descriptor object.
 * @param {HTMLDocument} doc     The document to parse.
 * @param {Object}       options The options object.
 */
function Readability(uri, doc, options) {
  options = options || {};

  this._uri = uri;
  this._doc = doc;
  this._biggestFrame = false;
  this._articleTitle = null;
  this._articleByline = null;
  this._articleDir = null;

  // Configureable options
  this._debug = !!options.debug;
  this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE;
  this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES;
  this._maxPages = options.maxPages || this.DEFAULT_MAX_PAGES;

  // Start with all flags set
  this._flags = this.FLAG_STRIP_UNLIKELYS |
                this.FLAG_WEIGHT_CLASSES |
                this.FLAG_CLEAN_CONDITIONALLY;

  // The list of pages we've parsed in this call of readability,
  // for autopaging. As a key store for easier searching.
  this._parsedPages = {};

  // A list of the ETag headers of pages we've parsed, in case they happen to match,
  // we'll know it's a duplicate.
  this._pageETags = {};

  // Make an AJAX request for each page and append it to the document.
  this._curPageNum = 1;

  var logEl;

  // Control whether log messages are sent to the console
  if (this._debug) {
    logEl = function(e) {
      var rv = e.nodeName + " ";
      if (e.nodeType == e.TEXT_NODE) {
        return rv + '("' + e.textContent + '")';
      }
      var classDesc = e.className && ("." + e.className.replace(/ /g, "."));
      var elDesc = "";
      if (e.id)
        elDesc = "(#" + e.id + classDesc + ")";
      else if (classDesc)
        elDesc = "(" + classDesc + ")";
      return rv + elDesc;
    };
    this.log = function () {
      if (typeof dump !== "undefined") {
        var msg = Array.prototype.map.call(arguments, function(x) {
          return (x && x.nodeName) ? logEl(x) : x;
        }).join(" ");
        dump("Reader: (Readability) " + msg + "\n");
      } else if (typeof console !== "undefined") {
        var args = ["Reader: (Readability) "].concat(arguments);
        console.log.apply(console, args);
      }
    };
  } else {
    this.log = function () {};
  }
}

Readability.prototype = {
  FLAG_STRIP_UNLIKELYS: 0x1,
  FLAG_WEIGHT_CLASSES: 0x2,
  FLAG_CLEAN_CONDITIONALLY: 0x4,

  // Max number of nodes supported by this parser. Default: 0 (no limit)
  DEFAULT_MAX_ELEMS_TO_PARSE: 0,

  // The number of top candidates to consider when analysing how
  // tight the competition is among candidates.
  DEFAULT_N_TOP_CANDIDATES: 5,

  // The maximum number of pages to loop through before we call
  // it quits and just show a link.
  DEFAULT_MAX_PAGES: 5,

  // Element tags to score by default.
  DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),

  // All of the regular expressions in use within readability.
  // Defined up here so we don't instantiate them repeatedly in loops.
  REGEXPS: {
    unlikelyCandidates: /banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|modal|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
    okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
    positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,
    negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|modal|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
    extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,
    byline: /byline|author|dateline|writtenby|p-author/i,
    replaceFonts: /<(\/?)font[^>]*>/gi,
    normalize: /\s{2,}/g,
    videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
    nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
    prevLink: /(prev|earl|old|new|<|«)/i,
    whitespace: /^\s*$/,
    hasContent: /\S$/,
  },

  DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ],

  ALTER_TO_DIV_EXCEPTIONS: ["DIV", "ARTICLE", "SECTION", "P"],

  /**
   * Run any post-process modifications to article content as necessary.
   *
   * @param Element
   * @return void
  **/
  _postProcessContent: function(articleContent) {
    // Readability cannot open relative uris so we convert them to absolute uris.
    this._fixRelativeUris(articleContent);
  },

  /**
   * Iterates over a NodeList, calls `filterFn` for each node and removes node
   * if function returned `true`.
   *
   * If function is not passed, removes all the nodes in node list.
   *
   * @param NodeList nodeList The nodes to operate on
   * @param Function filterFn the function to use as a filter
   * @return void
   */
  _removeNodes: function(nodeList, filterFn) {
    for (var i = nodeList.length - 1; i >= 0; i--) {
      var node = nodeList[i];
      var parentNode = node.parentNode;
      if (parentNode) {
        if (!filterFn || filterFn.call(this, node, i, nodeList)) {
          parentNode.removeChild(node);
        }
      }
    }
  },

  /**
   * Iterates over a NodeList, and calls _setNodeTag for each node.
   *
   * @param NodeList nodeList The nodes to operate on
   * @param String newTagName the new tag name to use
   * @return void
   */
  _replaceNodeTags: function(nodeList, newTagName) {
    for (var i = nodeList.length - 1; i >= 0; i--) {
      var node = nodeList[i];
      this._setNodeTag(node, newTagName);
    }
  },

  /**
   * Iterate over a NodeList, which doesn't natively fully implement the Array
   * interface.
   *
   * For convenience, the current object context is applied to the provided
   * iterate function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The iterate function.
   * @return void
   */
  _forEachNode: function(nodeList, fn) {
    Array.prototype.forEach.call(nodeList, fn, this);
  },

  /**
   * Iterate over a NodeList, return true if any of the provided iterate
   * function calls returns true, false otherwise.
   *
   * For convenience, the current object context is applied to the
   * provided iterate function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The iterate function.
   * @return Boolean
   */
  _someNode: function(nodeList, fn) {
    return Array.prototype.some.call(nodeList, fn, this);
  },

  /**
   * Concat all nodelists passed as arguments.
   *
   * @return ...NodeList
   * @return Array
   */
  _concatNodeLists: function() {
    var slice = Array.prototype.slice;
    var args = slice.call(arguments);
    var nodeLists = args.map(function(list) {
      return slice.call(list);
    });
    return Array.prototype.concat.apply([], nodeLists);
  },

  _getAllNodesWithTag: function(node, tagNames) {
    if (node.querySelectorAll) {
      return node.querySelectorAll(tagNames.join(','));
    }
    return [].concat.apply([], tagNames.map(function(tag) {
      var collection = node.getElementsByTagName(tag);
      return Array.isArray(collection) ? collection : Array.from(collection);
    }));
  },

  /**
   * Converts each <a> and <img> uri in the given element to an absolute URI,
   * ignoring #ref URIs.
   *
   * @param Element
   * @return void
   */
  _fixRelativeUris: function(articleContent) {
    var scheme = this._uri.scheme;
    var prePath = this._uri.prePath;
    var pathBase = this._uri.pathBase;

    function toAbsoluteURI(uri) {
      // If this is already an absolute URI, return it.
      if (/^[a-zA-Z][a-zA-Z0-9\+\-\.]*:/.test(uri))
        return uri;

      // Scheme-rooted relative URI.
      if (uri.substr(0, 2) == "//")
        return scheme + "://" + uri.substr(2);

      // Prepath-rooted relative URI.
      if (uri[0] == "/")
        return prePath + uri;

      // Dotslash relative URI.
      if (uri.indexOf("./") === 0)
        return pathBase + uri.slice(2);

      // Ignore hash URIs:
      if (uri[0] == "#")
        return uri;

      // Standard relative URI; add entire path. pathBase already includes a
      // trailing "/".
      return pathBase + uri;
    }

    var links = articleContent.getElementsByTagName("a");
    this._forEachNode(links, function(link) {
      var href = link.getAttribute("href");
      if (href) {
        // Replace links with javascript: URIs with text content, since
        // they won't work after scripts have been removed from the page.
        if (href.indexOf("javascript:") === 0) {
          var text = this._doc.createTextNode(link.textContent);
          link.parentNode.replaceChild(text, link);
        } else {
          link.setAttribute("href", toAbsoluteURI(href));
        }
      }
    });

    var imgs = articleContent.getElementsByTagName("img");
    this._forEachNode(imgs, function(img) {
      var src = img.getAttribute("src");
      if (src) {
        img.setAttribute("src", toAbsoluteURI(src));
      }
    });
  },

  /**
   * Get the article title as an H1.
   *
   * @return void
   **/
  _getArticleTitle: function() {
    var doc = this._doc;
    var curTitle = "";
    var origTitle = "";

    try {
      curTitle = origTitle = doc.title;

      // If they had an element with id "title" in their HTML
      if (typeof curTitle !== "string")
        curTitle = origTitle = this._getInnerText(doc.getElementsByTagName('title')[0]);
    } catch (e) {/* ignore exceptions setting the title. */}

    if (curTitle.match(/ [\|\-] /)) {
      curTitle = origTitle.replace(/(.*)[\|\-] .*/gi, '$1');

      if (curTitle.split(' ').length < 3)
        curTitle = origTitle.replace(/[^\|\-]*[\|\-](.*)/gi, '$1');
    } else if (curTitle.indexOf(': ') !== -1) {
      // Check if we have an heading containing this exact string, so we
      // could assume it's the full title.
      var headings = this._concatNodeLists(
        doc.getElementsByTagName('h1'),
        doc.getElementsByTagName('h2')
      );
      var match = this._someNode(headings, function(heading) {
        return heading.textContent === curTitle;
      });

      // If we don't, let's extract the title out of the original title string.
      if (!match) {
        curTitle = origTitle.substring(origTitle.lastIndexOf(':') + 1);

        // If the title is now too short, try the first colon instead:
        if (curTitle.split(' ').length < 3)
          curTitle = origTitle.substring(origTitle.indexOf(':') + 1);
      }
    } else if (curTitle.length > 150 || curTitle.length < 15) {
      var hOnes = doc.getElementsByTagName('h1');

      if (hOnes.length === 1)
        curTitle = this._getInnerText(hOnes[0]);
    }

    curTitle = curTitle.trim();

    if (curTitle.split(' ').length <= 4)
      curTitle = origTitle;

    return curTitle;
  },

  /**
   * Prepare the HTML document for readability to scrape it.
   * This includes things like stripping javascript, CSS, and handling terrible markup.
   *
   * @return void
   **/
  _prepDocument: function() {
    var doc = this._doc;

    // Remove all style tags in head
    this._removeNodes(doc.getElementsByTagName("style"));

    if (doc.body) {
      this._replaceBrs(doc.body);
    }

    this._replaceNodeTags(doc.getElementsByTagName("font"), "SPAN");
  },

  /**
   * Finds the next element, starting from the given node, and ignoring
   * whitespace in between. If the given node is an element, the same node is
   * returned.
   */
  _nextElement: function (node) {
    var next = node;
    while (next
        && (next.nodeType != Node.ELEMENT_NODE)
        && this.REGEXPS.whitespace.test(next.textContent)) {
      next = next.nextSibling;
    }
    return next;
  },

  /**
   * Replaces 2 or more successive <br> elements with a single <p>.
   * Whitespace between <br> elements are ignored. For example:
   *   <div>foo<br>bar<br> <br><br>abc</div>
   * will become:
   *   <div>foo<br>bar<p>abc</p></div>
   */
  _replaceBrs: function (elem) {
    this._forEachNode(this._getAllNodesWithTag(elem, ["br"]), function(br) {
      var next = br.nextSibling;

      // Whether 2 or more <br> elements have been found and replaced with a
      // <p> block.
      var replaced = false;

      // If we find a <br> chain, remove the <br>s until we hit another element
      // or non-whitespace. This leaves behind the first <br> in the chain
      // (which will be replaced with a <p> later).
      while ((next = this._nextElement(next)) && (next.tagName == "BR")) {
        replaced = true;
        var brSibling = next.nextSibling;
        next.parentNode.removeChild(next);
        next = brSibling;
      }

      // If we removed a <br> chain, replace the remaining <br> with a <p>. Add
      // all sibling nodes as children of the <p> until we hit another <br>
      // chain.
      if (replaced) {
        var p = this._doc.createElement("p");
        br.parentNode.replaceChild(p, br);

        next = p.nextSibling;
        while (next) {
          // If we've hit another <br><br>, we're done adding children to this <p>.
          if (next.tagName == "BR") {
            var nextElem = this._nextElement(next);
            if (nextElem && nextElem.tagName == "BR")
              break;
          }

          // Otherwise, make this node a child of the new <p>.
          var sibling = next.nextSibling;
          p.appendChild(next);
          next = sibling;
        }
      }
    });
  },

  _setNodeTag: function (node, tag) {
    this.log("_setNodeTag", node, tag);
    if (node.__JSDOMParser__) {
      node.localName = tag.toLowerCase();
      node.tagName = tag.toUpperCase();
      return node;
    }

    var replacement = node.ownerDocument.createElement(tag);
    while (node.firstChild) {
      replacement.appendChild(node.firstChild);
    }
    node.parentNode.replaceChild(replacement, node);
    if (node.readability)
      replacement.readability = node.readability;

    for (var i = 0; i < node.attributes.length; i++) {
      replacement.setAttribute(node.attributes[i].name, node.attributes[i].value);
    }
    return replacement;
  },

  /**
   * Prepare the article node for display. Clean out any inline styles,
   * iframes, forms, strip extraneous <p> tags, etc.
   *
   * @param Element
   * @return void
   **/
  _prepArticle: function(articleContent) {
    this._cleanStyles(articleContent);

    // Check for data tables before we continue, to avoid removing items in
    // those tables, which will often be isolated even though they're
    // visually linked to other content-ful elements (text, images, etc.).
    this._markDataTables(articleContent);

    // Clean out junk from the article content
    this._cleanConditionally(articleContent, "form");
    this._cleanConditionally(articleContent, "fieldset");
    this._clean(articleContent, "object");
    this._clean(articleContent, "embed");
    this._clean(articleContent, "h1");
    this._clean(articleContent, "footer");

    // Clean out elements have "share" in their id/class combinations from final top candidates,
    // which means we don't remove the top candidates even they have "share".
    this._forEachNode(articleContent.children, function(topCandidate) {
      this._cleanMatchedNodes(topCandidate, /share/);
    });

    // If there is only one h2 and its text content substantially equals article title,
    // they are probably using it as a header and not a subheader,
    // so remove it since we already extract the title separately.
    var h2 = articleContent.getElementsByTagName('h2');
    if (h2.length === 1) {
      var lengthSimilarRate = (h2[0].textContent.length - this._articleTitle.length) / this._articleTitle.length;
      if (Math.abs(lengthSimilarRate) < 0.5 &&
          (lengthSimilarRate > 0 ? h2[0].textContent.includes(this._articleTitle) :
                                   this._articleTitle.includes(h2[0].textContent))) {
        this._clean(articleContent, "h2");
      }
    }

    this._clean(articleContent, "iframe");
    this._clean(articleContent, "input");
    this._clean(articleContent, "textarea");
    this._clean(articleContent, "select");
    this._clean(articleContent, "button");
    this._cleanHeaders(articleContent);

    // Do these last as the previous stuff may have removed junk
    // that will affect these
    this._cleanConditionally(articleContent, "table");
    this._cleanConditionally(articleContent, "ul");
    this._cleanConditionally(articleContent, "div");

    // Remove extra paragraphs
    this._removeNodes(articleContent.getElementsByTagName('p'), function (paragraph) {
      var imgCount = paragraph.getElementsByTagName('img').length;
      var embedCount = paragraph.getElementsByTagName('embed').length;
      var objectCount = paragraph.getElementsByTagName('object').length;
      // At this point, nasty iframes have been removed, only remain embedded video ones.
      var iframeCount = paragraph.getElementsByTagName('iframe').length;
      var totalCount = imgCount + embedCount + objectCount + iframeCount;

      return totalCount === 0 && !this._getInnerText(paragraph, false);
    });

    this._forEachNode(this._getAllNodesWithTag(articleContent, ["br"]), function(br) {
      var next = this._nextElement(br.nextSibling);
      if (next && next.tagName == "P")
        br.parentNode.removeChild(br);
    });
  },

  /**
   * Initialize a node with the readability object. Also checks the
   * className/id for special names to add to its score.
   *
   * @param Element
   * @return void
  **/
  _initializeNode: function(node) {
    node.readability = {"contentScore": 0};

    switch (node.tagName) {
      case 'DIV':
        node.readability.contentScore += 5;
        break;

      case 'PRE':
      case 'TD':
      case 'BLOCKQUOTE':
        node.readability.contentScore += 3;
        break;

      case 'ADDRESS':
      case 'OL':
      case 'UL':
      case 'DL':
      case 'DD':
      case 'DT':
      case 'LI':
      case 'FORM':
        node.readability.contentScore -= 3;
        break;

      case 'H1':
      case 'H2':
      case 'H3':
      case 'H4':
      case 'H5':
      case 'H6':
      case 'TH':
        node.readability.contentScore -= 5;
        break;
    }

    node.readability.contentScore += this._getClassWeight(node);
  },

  _removeAndGetNext: function(node) {
    var nextNode = this._getNextNode(node, true);
    node.parentNode.removeChild(node);
    return nextNode;
  },

  /**
   * Traverse the DOM from node to node, starting at the node passed in.
   * Pass true for the second parameter to indicate this node itself
   * (and its kids) are going away, and we want the next node over.
   *
   * Calling this in a loop will traverse the DOM depth-first.
   */
  _getNextNode: function(node, ignoreSelfAndKids) {
    // First check for kids if those aren't being ignored
    if (!ignoreSelfAndKids && node.firstElementChild) {
      return node.firstElementChild;
    }
    // Then for siblings...
    if (node.nextElementSibling) {
      return node.nextElementSibling;
    }
    // And finally, move up the parent chain *and* find a sibling
    // (because this is depth-first traversal, we will have already
    // seen the parent nodes themselves).
    do {
      node = node.parentNode;
    } while (node && !node.nextElementSibling);
    return node && node.nextElementSibling;
  },

  /**
   * Like _getNextNode, but for DOM implementations with no
   * firstElementChild/nextElementSibling functionality...
   */
  _getNextNodeNoElementProperties: function(node, ignoreSelfAndKids) {
    function nextSiblingEl(n) {
      do {
        n = n.nextSibling;
      } while (n && n.nodeType !== n.ELEMENT_NODE);
      return n;
    }
    // First check for kids if those aren't being ignored
    if (!ignoreSelfAndKids && node.children[0]) {
      return node.children[0];
    }
    // Then for siblings...
    var next = nextSiblingEl(node);
    if (next) {
      return next;
    }
    // And finally, move up the parent chain *and* find a sibling
    // (because this is depth-first traversal, we will have already
    // seen the parent nodes themselves).
    do {
      node = node.parentNode;
      if (node)
        next = nextSiblingEl(node);
    } while (node && !next);
    return node && next;
  },

  _checkByline: function(node, matchString) {
    if (this._articleByline) {
      return false;
    }

    if (node.getAttribute !== undefined) {
      var rel = node.getAttribute("rel");
    }

    if ((rel === "author" || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) {
      this._articleByline = node.textContent.trim();
      return true;
    }

    return false;
  },

  _getNodeAncestors: function(node, maxDepth) {
    maxDepth = maxDepth || 0;
    var i = 0, ancestors = [];
    while (node.parentNode) {
      ancestors.push(node.parentNode);
      if (maxDepth && ++i === maxDepth)
        break;
      node = node.parentNode;
    }
    return ancestors;
  },

  /***
   * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is
   *         most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
   *
   * @param page a document to run upon. Needs to be a full document, complete with body.
   * @return Element
  **/
  _grabArticle: function (page) {
    this.log("**** grabArticle ****");
    var doc = this._doc;
    var isPaging = (page !== null ? true: false);
    page = page ? page : this._doc.body;

    // We can't grab an article if we don't have a page!
    if (!page) {
      this.log("No body found in document. Abort.");
      return null;
    }

    var pageCacheHtml = page.innerHTML;

    while (true) {
      var stripUnlikelyCandidates = this._flagIsActive(this.FLAG_STRIP_UNLIKELYS);

      // First, node prepping. Trash nodes that look cruddy (like ones with the
      // class name "comment", etc), and turn divs into P tags where they have been
      // used inappropriately (as in, where they contain no other block level elements.)
      var elementsToScore = [];
      var node = this._doc.documentElement;

      while (node) {
        var matchString = node.className + " " + node.id;

        // Check to see if this node is a byline, and remove it if it is.
        if (this._checkByline(node, matchString)) {
          node = this._removeAndGetNext(node);
          continue;
        }

        // Remove unlikely candidates
        if (stripUnlikelyCandidates) {
          if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
              !this.REGEXPS.okMaybeItsACandidate.test(matchString) &&
              node.tagName !== "BODY" &&
              node.tagName !== "A") {
            this.log("Removing unlikely candidate - " + matchString);
            node = this._removeAndGetNext(node);
            continue;
          }
        }

        // Remove DIV, SECTION, and HEADER nodes without any content(e.g. text, image, video, or iframe).
        if ((node.tagName === "DIV" || node.tagName === "SECTION" || node.tagName === "HEADER" ||
             node.tagName === "H1" || node.tagName === "H2" || node.tagName === "H3" ||
             node.tagName === "H4" || node.tagName === "H5" || node.tagName === "H6") &&
            this._isElementWithoutContent(node)) {
          node = this._removeAndGetNext(node);
          continue;
        }

        if (this.DEFAULT_TAGS_TO_SCORE.indexOf(node.tagName) !== -1) {
          elementsToScore.push(node);
        }

        // Turn all divs that don't have children block level elements into p's
        if (node.tagName === "DIV") {
          // Sites like http://mobile.slate.com encloses each paragraph with a DIV
          // element. DIVs with only a P element inside and no text content can be
          // safely converted into plain P elements to avoid confusing the scoring
          // algorithm with DIVs with are, in practice, paragraphs.
          if (this._hasSinglePInsideElement(node)) {
            var newNode = node.children[0];
            node.parentNode.replaceChild(newNode, node);
            node = newNode;
          } else if (!this._hasChildBlockElement(node)) {
            node = this._setNodeTag(node, "P");
            elementsToScore.push(node);
          } else {
            // EXPERIMENTAL
            this._forEachNode(node.childNodes, function(childNode) {
              if (childNode.nodeType === Node.TEXT_NODE && childNode.textContent.trim().length > 0) {
                var p = doc.createElement('p');
                p.textContent = childNode.textContent;
                p.style.display = 'inline';
                p.className = 'readability-styled';
                node.replaceChild(p, childNode);
              }
            });
          }
        }
        node = this._getNextNode(node);
      }

      /**
       * Loop through all paragraphs, and assign a score to them based on how content-y they look.
       * Then add their score to their parent node.
       *
       * A score is determined by things like number of commas, class names, etc. Maybe eventually link density.
      **/
      var candidates = [];
      this._forEachNode(elementsToScore, function(elementToScore) {
        if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === 'undefined')
          return;

        // If this paragraph is less than 25 characters, don't even count it.
        var innerText = this._getInnerText(elementToScore);
        if (innerText.length < 25)
          return;

        // Exclude nodes with no ancestor.
        var ancestors = this._getNodeAncestors(elementToScore, 3);
        if (ancestors.length === 0)
          return;

        var contentScore = 0;

        // Add a point for the paragraph itself as a base.
        contentScore += 1;

        // Add points for any commas within this paragraph.
        contentScore += innerText.split(',').length;

        // For every 100 characters in this paragraph, add another point. Up to 3 points.
        contentScore += Math.min(Math.floor(innerText.length / 100), 3);

        // Initialize and score ancestors.
        this._forEachNode(ancestors, function(ancestor, level) {
          if (!ancestor.tagName)
            return;

          if (typeof(ancestor.readability) === 'undefined') {
            this._initializeNode(ancestor);
            candidates.push(ancestor);
          }

          // Node score divider:
          // - parent:             1 (no division)
          // - grandparent:        2
          // - great grandparent+: ancestor level * 3
          if (level === 0)
            var scoreDivider = 1;
          else if (level === 1)
            scoreDivider = 2;
          else
            scoreDivider = level * 3;
          ancestor.readability.contentScore += contentScore / scoreDivider;
        });
      });

      // After we've calculated scores, loop through all of the possible
      // candidate nodes we found and find the one with the highest score.
      var topCandidates = [];
      for (var c = 0, cl = candidates.length; c < cl; c += 1) {
        var candidate = candidates[c];

        // Scale the final candidates score based on link density. Good content
        // should have a relatively small link density (5% or less) and be mostly
        // unaffected by this operation.
        var candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate));
        candidate.readability.contentScore = candidateScore;

        this.log('Candidate:', candidate, "with score " + candidateScore);

        for (var t = 0; t < this._nbTopCandidates; t++) {
          var aTopCandidate = topCandidates[t];

          if (!aTopCandidate || candidateScore > aTopCandidate.readability.contentScore) {
            topCandidates.splice(t, 0, candidate);
            if (topCandidates.length > this._nbTopCandidates)
              topCandidates.pop();
            break;
          }
        }
      }

      var topCandidate = topCandidates[0] || null;
      var neededToCreateTopCandidate = false;
      var parentOfTopCandidate;

      // If we still have no top candidate, just use the body as a last resort.
      // We also have to copy the body node so it is something we can modify.
      if (topCandidate === null || topCandidate.tagName === "BODY") {
        // Move all of the page's children into topCandidate
        topCandidate = doc.createElement("DIV");
        neededToCreateTopCandidate = true;
        // Move everything (not just elements, also text nodes etc.) into the container
        // so we even include text directly in the body:
        var kids = page.childNodes;
        while (kids.length) {
          this.log("Moving child out:", kids[0]);
          topCandidate.appendChild(kids[0]);
        }

        page.appendChild(topCandidate);

        this._initializeNode(topCandidate);
      } else if (topCandidate) {
        // Find a better top candidate node if it contains (at least three) nodes which belong to `topCandidates` array
        // and whose scores are quite closed with current `topCandidate` node.
        var alternativeCandidateAncestors = [];
        for (var i = 1; i < topCandidates.length; i++) {
          if (topCandidates[i].readability.contentScore / topCandidate.readability.contentScore >= 0.75) {
            alternativeCandidateAncestors.push(this._getNodeAncestors(topCandidates[i]));
          }
        }
        var MINIMUM_TOPCANDIDATES = 3;
        if (alternativeCandidateAncestors.length >= MINIMUM_TOPCANDIDATES) {
          parentOfTopCandidate = topCandidate.parentNode;
          while (parentOfTopCandidate.tagName !== "BODY") {
            var listsContainingThisAncestor = 0;
            for (var ancestorIndex = 0; ancestorIndex < alternativeCandidateAncestors.length && listsContainingThisAncestor < MINIMUM_TOPCANDIDATES; ancestorIndex++) {
              listsContainingThisAncestor += Number(alternativeCandidateAncestors[ancestorIndex].includes(parentOfTopCandidate));
            }
            if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) {
              topCandidate = parentOfTopCandidate;
              break;
            }
            parentOfTopCandidate = parentOfTopCandidate.parentNode;
          }
        }
        if (!topCandidate.readability) {
          this._initializeNode(topCandidate);
        }

        // Because of our bonus system, parents of candidates might have scores
        // themselves. They get half of the node. There won't be nodes with higher
        // scores than our topCandidate, but if we see the score going *up* in the first
        // few steps up the tree, that's a decent sign that there might be more content
        // lurking in other places that we want to unify in. The sibling stuff
        // below does some of that - but only if we've looked high enough up the DOM
        // tree.
        parentOfTopCandidate = topCandidate.parentNode;
        var lastScore = topCandidate.readability.contentScore;
        // The scores shouldn't get too low.
        var scoreThreshold = lastScore / 3;
        while (parentOfTopCandidate.tagName !== "BODY") {
          if (!parentOfTopCandidate.readability) {
            parentOfTopCandidate = parentOfTopCandidate.parentNode;
            continue;
          }
          var parentScore = parentOfTopCandidate.readability.contentScore;
          if (parentScore < scoreThreshold)
            break;
          if (parentScore > lastScore) {
            // Alright! We found a better parent to use.
            topCandidate = parentOfTopCandidate;
            break;
          }
          lastScore = parentOfTopCandidate.readability.contentScore;
          parentOfTopCandidate = parentOfTopCandidate.parentNode;
        }

        // If the top candidate is the only child, use parent instead. This will help sibling
        // joining logic when adjacent content is actually located in parent's sibling node.
        parentOfTopCandidate = topCandidate.parentNode;
        while (parentOfTopCandidate.tagName != "BODY" && parentOfTopCandidate.children.length == 1) {
          topCandidate = parentOfTopCandidate;
          parentOfTopCandidate = topCandidate.parentNode;
        }
        if (!topCandidate.readability) {
          this._initializeNode(topCandidate);
        }
      }

      // Now that we have the top candidate, look through its siblings for content
      // that might also be related. Things like preambles, content split by ads
      // that we removed, etc.
      var articleContent = doc.createElement("DIV");
      if (isPaging)
        articleContent.id = "readability-content";

      var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2);
      // Keep potential top candidate's parent node to try to get text direction of it later.
      parentOfTopCandidate = topCandidate.parentNode;
      var siblings = parentOfTopCandidate.children;

      for (var s = 0, sl = siblings.length; s < sl; s++) {
        var sibling = siblings[s];
        var append = false;

        this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : '');
        this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : 'Unknown');

        if (sibling === topCandidate) {
          append = true;
        } else {
          var contentBonus = 0;

          // Give a bonus if sibling nodes and top candidates have the example same classname
          if (sibling.className === topCandidate.className && topCandidate.className !== "")
            contentBonus += topCandidate.readability.contentScore * 0.2;

          if (sibling.readability &&
              ((sibling.readability.contentScore + contentBonus) >= siblingScoreThreshold)) {
            append = true;
          } else if (sibling.nodeName === "P") {
            var linkDensity = this._getLinkDensity(sibling);
            var nodeContent = this._getInnerText(sibling);
            var nodeLength = nodeContent.length;

            if (nodeLength > 80 && linkDensity < 0.25) {
              append = true;
            } else if (nodeLength < 80 && nodeLength > 0 && linkDensity === 0 &&
                       nodeContent.search(/\.( |$)/) !== -1) {
              append = true;
            }
          }
        }

        if (append) {
          this.log("Appending node:", sibling);

          if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) {
            // We have a node that isn't a common block level element, like a form or td tag.
            // Turn it into a div so it doesn't get filtered out later by accident.
            this.log("Altering sibling:", sibling, 'to div.');

            sibling = this._setNodeTag(sibling, "DIV");
          }

          articleContent.appendChild(sibling);
          // siblings is a reference to the children array, and
          // sibling is removed from the array when we call appendChild().
          // As a result, we must revisit this index since the nodes
          // have been shifted.
          s -= 1;
          sl -= 1;
        }
      }

      if (this._debug)
        this.log("Article content pre-prep: " + articleContent.innerHTML);
      // So we have all of the content that we need. Now we clean it up for presentation.
      this._prepArticle(articleContent);
      if (this._debug)
        this.log("Article content post-prep: " + articleContent.innerHTML);

      if (this._curPageNum === 1) {
        if (neededToCreateTopCandidate) {
          // We already created a fake div thing, and there wouldn't have been any siblings left
          // for the previous loop, so there's no point trying to create a new div, and then
          // move all the children over. Just assign IDs and class names here. No need to append
          // because that already happened anyway.
          topCandidate.id = "readability-page-1";
          topCandidate.className = "page";
        } else {
          var div = doc.createElement("DIV");
          div.id = "readability-page-1";
          div.className = "page";
          var children = articleContent.childNodes;
          while (children.length) {
            div.appendChild(children[0]);
          }
          articleContent.appendChild(div);
        }
      }

      if (this._debug)
        this.log("Article content after paging: " + articleContent.innerHTML);

      // Now that we've gone through the full algorithm, check to see if
      // we got any meaningful content. If we didn't, we may need to re-run
      // grabArticle with different flags set. This gives us a higher likelihood of
      // finding the content, and the sieve approach gives us a higher likelihood of
      // finding the -right- content.
      if (this._getInnerText(articleContent, true).length < 500) {
        page.innerHTML = pageCacheHtml;

        if (this._flagIsActive(this.FLAG_STRIP_UNLIKELYS)) {
          this._removeFlag(this.FLAG_STRIP_UNLIKELYS);
        } else if (this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) {
          this._removeFlag(this.FLAG_WEIGHT_CLASSES);
        } else if (this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) {
          this._removeFlag(this.FLAG_CLEAN_CONDITIONALLY);
        } else {
          return null;
        }
      } else {
        // Find out text direction from ancestors of final top candidate.
        var ancestors = [parentOfTopCandidate, topCandidate].concat(this._getNodeAncestors(parentOfTopCandidate));
        this._someNode(ancestors, function(ancestor) {
          if (!ancestor.tagName)
            return false;
          var articleDir = ancestor.getAttribute("dir");
          if (articleDir) {
            this._articleDir = articleDir;
            return true;
          }
          return false;
        });
        return articleContent;
      }
    }
  },

  /**
   * Check whether the input string could be a byline.
   * This verifies that the input is a string, and that the length
   * is less than 100 chars.
   *
   * @param possibleByline {string} - a string to check whether its a byline.
   * @return Boolean - whether the input string is a byline.
   */
  _isValidByline: function(byline) {
    if (typeof byline == 'string' || byline instanceof String) {
      byline = byline.trim();
      return (byline.length > 0) && (byline.length < 100);
    }
    return false;
  },

  /**
   * Attempts to get excerpt and byline metadata for the article.
   *
   * @return Object with optional "excerpt" and "byline" properties
   */
  _getArticleMetadata: function() {
    var metadata = {};
    var values = {};
    var metaElements = this._doc.getElementsByTagName("meta");

    // Match "description", or Twitter's "twitter:description" (Cards)
    // in name attribute.
    var namePattern = /^\s*((twitter)\s*:\s*)?(description|title)\s*$/gi;

    // Match Facebook's Open Graph title & description properties.
    var propertyPattern = /^\s*og\s*:\s*(description|title)\s*$/gi;

    // Find description tags.
    this._forEachNode(metaElements, function(element) {
      var elementName = element.getAttribute("name");
      var elementProperty = element.getAttribute("property");

      if ([elementName, elementProperty].indexOf("author") !== -1) {
        metadata.byline = element.getAttribute("content");
        return;
      }

      var name = null;
      if (namePattern.test(elementName)) {
        name = elementName;
      } else if (propertyPattern.test(elementProperty)) {
        name = elementProperty;
      }

      if (name) {
        var content = element.getAttribute("content");
        if (content) {
          // Convert to lowercase and remove any whitespace
          // so we can match below.
          name = name.toLowerCase().replace(/\s/g, '');
          values[name] = content.trim();
        }
      }
    });

    if ("description" in values) {
      metadata.excerpt = values["description"];
    } else if ("og:description" in values) {
      // Use facebook open graph description.
      metadata.excerpt = values["og:description"];
    } else if ("twitter:description" in values) {
      // Use twitter cards description.
      metadata.excerpt = values["twitter:description"];
    }

    metadata.title = this._getArticleTitle();
    if (!metadata.title) {
      if ("og:title" in values) {
        // Use facebook open graph title.
        metadata.title = values["og:title"];
      } else if ("twitter:title" in values) {
        // Use twitter cards title.
        metadata.title = values["twitter:title"];
      }
    }

    return metadata;
  },

  /**
   * Removes script tags from the document.
   *
   * @param Element
  **/
  _removeScripts: function(doc) {
    this._removeNodes(doc.getElementsByTagName('script'), function(scriptNode) {
      scriptNode.nodeValue = "";
      scriptNode.removeAttribute('src');
      return true;
    });
    this._removeNodes(doc.getElementsByTagName('noscript'));
  },

  /**
   * Check if this node has only whitespace and a single P element
   * Returns false if the DIV node contains non-empty text nodes
   * or if it contains no P or more than 1 element.
   *
   * @param Element
  **/
  _hasSinglePInsideElement: function(element) {
    // There should be exactly 1 element child which is a P:
    if (element.children.length != 1 || element.children[0].tagName !== "P") {
      return false;
    }

    // And there should be no text nodes with real content
    return !this._someNode(element.childNodes, function(node) {
      return node.nodeType === Node.TEXT_NODE &&
             this.REGEXPS.hasContent.test(node.textContent);
    });
  },

  _isElementWithoutContent: function(node) {
    return node.nodeType === Node.ELEMENT_NODE &&
      node.textContent.trim().length == 0 &&
      (node.children.length == 0 ||
       node.children.length == node.getElementsByTagName("br").length + node.getElementsByTagName("hr").length);
  },

  /**
   * Determine whether element has any children block level elements.
   *
   * @param Element
   */
  _hasChildBlockElement: function (element) {
    return this._someNode(element.childNodes, function(node) {
      return this.DIV_TO_P_ELEMS.indexOf(node.tagName) !== -1 ||
             this._hasChildBlockElement(node);
    });
  },

  /**
   * Get the inner text of a node - cross browser compatibly.
   * This also strips out any excess whitespace to be found.
   *
   * @param Element
   * @param Boolean normalizeSpaces (default: true)
   * @return string
  **/
  _getInnerText: function(e, normalizeSpaces) {
    normalizeSpaces = (typeof normalizeSpaces === 'undefined') ? true : normalizeSpaces;
    var textContent = e.textContent.trim();

    if (normalizeSpaces) {
      return textContent.replace(this.REGEXPS.normalize, " ");
    }
    return textContent;
  },

  /**
   * Get the number of times a string s appears in the node e.
   *
   * @param Element
   * @param string - what to split on. Default is ","
   * @return number (integer)
  **/
  _getCharCount: function(e, s) {
    s = s || ",";
    return this._getInnerText(e).split(s).length - 1;
  },

  /**
   * Remove the style attribute on every e and under.
   * TODO: Test if getElementsByTagName(*) is faster.
   *
   * @param Element
   * @return void
  **/
  _cleanStyles: function(e) {
    e = e || this._doc;
    if (!e)
      return;
    var cur = e.firstChild;

    // Remove any root styles, if we're able.
    if (typeof e.removeAttribute === 'function' && e.className !== 'readability-styled')
      e.removeAttribute('style');

    // Go until there are no more child nodes
    while (cur !== null) {
      if (cur.nodeType === cur.ELEMENT_NODE) {
        // Remove style attribute(s) :
        if (cur.className !== "readability-styled")
          cur.removeAttribute("style");

        this._cleanStyles(cur);
      }

      cur = cur.nextSibling;
    }
  },

  /**
   * Get the density of links as a percentage of the content
   * This is the amount of text that is inside a link divided by the total text in the node.
   *
   * @param Element
   * @return number (float)
  **/
  _getLinkDensity: function(element) {
    var textLength = this._getInnerText(element).length;
    if (textLength === 0)
      return 0;

    var linkLength = 0;

    // XXX implement _reduceNodeList?
    this._forEachNode(element.getElementsByTagName("a"), function(linkNode) {
      linkLength += this._getInnerText(linkNode).length;
    });

    return linkLength / textLength;
  },

  /**
   * Find a cleaned up version of the current URL, to use for comparing links for possible next-pageyness.
   *
   * @author Dan Lacy
   * @return string the base url
  **/
  _findBaseUrl: function() {
    var uri = this._uri;
    var noUrlParams = uri.path.split("?")[0];
    var urlSlashes = noUrlParams.split("/").reverse();
    var cleanedSegments = [];
    var possibleType = "";

    for (var i = 0, slashLen = urlSlashes.length; i < slashLen; i += 1) {
      var segment = urlSlashes[i];

      // Split off and save anything that looks like a file type.
      if (segment.indexOf(".") !== -1) {
        possibleType = segment.split(".")[1];

        // If the type isn't alpha-only, it's probably not actually a file extension.
        if (!possibleType.match(/[^a-zA-Z]/))
          segment = segment.split(".")[0];
      }

      // If our first or second segment has anything looking like a page number, remove it.
      if (segment.match(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i) && ((i === 1) || (i === 0)))
        segment = segment.replace(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i, "");

      var del = false;

      // If this is purely a number, and it's the first or second segment,
      // it's probably a page number. Remove it.
      if (i < 2 && segment.match(/^\d{1,2}$/))
        del = true;

      // If this is the first segment and it's just "index", remove it.
      if (i === 0 && segment.toLowerCase() === "index")
        del = true;

      // If our first or second segment is smaller than 3 characters,
      // and the first segment was purely alphas, remove it.
      if (i < 2 && segment.length < 3 && !urlSlashes[0].match(/[a-z]/i))
        del = true;

      // If it's not marked for deletion, push it to cleanedSegments.
      if (!del)
        cleanedSegments.push(segment);
    }

    // This is our final, cleaned, base article URL.
    return uri.scheme + "://" + uri.host + cleanedSegments.reverse().join("/");
  },

  /**
   * Look for any paging links that may occur within the document.
   *
   * @param body
   * @return object (array)
  **/
  _findNextPageLink: function(elem) {
    var uri = this._uri;
    var possiblePages = {};
    var allLinks = elem.getElementsByTagName('a');
    var articleBaseUrl = this._findBaseUrl();

    // Loop through all links, looking for hints that they may be next-page links.
    // Things like having "page" in their textContent, className or id, or being a child
    // of a node with a page-y className or id.
    //
    // Also possible: levenshtein distance? longest common subsequence?
    //
    // After we do that, assign each page a score, and
    for (var i = 0, il = allLinks.length; i < il; i += 1) {
      var link = allLinks[i];
      var linkHref = allLinks[i].href.replace(/#.*$/, '').replace(/\/$/, '');

      // If we've already seen this page, ignore it.
      if (linkHref === "" ||
        linkHref === articleBaseUrl ||
        linkHref === uri.spec ||
        linkHref in this._parsedPages) {
        continue;
      }

      // If it's on a different domain, skip it.
      if (uri.host !== linkHref.split(/\/+/g)[1])
        continue;

      var linkText = this._getInnerText(link);

      // If the linkText looks like it's not the next page, skip it.
      if (linkText.match(this.REGEXPS.extraneous) || linkText.length > 25)
        continue;

      // If the leftovers of the URL after removing the base URL don't contain
      // any digits, it's certainly not a next page link.
      var linkHrefLeftover = linkHref.replace(articleBaseUrl, '');
      if (!linkHrefLeftover.match(/\d/))
        continue;

      if (!(linkHref in possiblePages)) {
        possiblePages[linkHref] = {"score": 0, "linkText": linkText, "href": linkHref};
      } else {
        possiblePages[linkHref].linkText += ' | ' + linkText;
      }

      var linkObj = possiblePages[linkHref];

      // If the articleBaseUrl isn't part of this URL, penalize this link. It could
      // still be the link, but the odds are lower.
      // Example: http://www.actionscript.org/resources/articles/745/1/JavaScript-and-VBScript-Injection-in-ActionScript-3/Page1.html
      if (linkHref.indexOf(articleBaseUrl) !== 0)
        linkObj.score -= 25;

      var linkData = linkText + ' ' + link.className + ' ' + link.id;
      if (linkData.match(this.REGEXPS.nextLink))
        linkObj.score += 50;

      if (linkData.match(/pag(e|ing|inat)/i))
        linkObj.score += 25;

      if (linkData.match(/(first|last)/i)) {
        // -65 is enough to negate any bonuses gotten from a > or » in the text,
        // If we already matched on "next", last is probably fine.
        // If we didn't, then it's bad. Penalize.
        if (!linkObj.linkText.match(this.REGEXPS.nextLink))
          linkObj.score -= 65;
      }

      if (linkData.match(this.REGEXPS.negative) || linkData.match(this.REGEXPS.extraneous))
        linkObj.score -= 50;

      if (linkData.match(this.REGEXPS.prevLink))
        linkObj.score -= 200;

      // If a parentNode contains page or paging or paginat
      var parentNode = link.parentNode;
      var positiveNodeMatch = false;
      var negativeNodeMatch = false;

      while (parentNode) {
        var parentNodeClassAndId = parentNode.className + ' ' + parentNode.id;

        if (!positiveNodeMatch && parentNodeClassAndId && parentNodeClassAndId.match(/pag(e|ing|inat)/i)) {
          positiveNodeMatch = true;
          linkObj.score += 25;
        }

        if (!negativeNodeMatch && parentNodeClassAndId && parentNodeClassAndId.match(this.REGEXPS.negative)) {
          // If this is just something like "footer", give it a negative.
          // If it's something like "body-and-footer", leave it be.
          if (!parentNodeClassAndId.match(this.REGEXPS.positive)) {
            linkObj.score -= 25;
            negativeNodeMatch = true;
          }
        }

        parentNode = parentNode.parentNode;
      }

      // If the URL looks like it has paging in it, add to the score.
      // Things like /page/2/, /pagenum/2, ?p=3, ?page=11, ?pagination=34
      if (linkHref.match(/p(a|g|ag)?(e|ing|ination)?(=|\/)[0-9]{1,2}/i) || linkHref.match(/(page|paging)/i))
        linkObj.score += 25;

      // If the URL contains negative values, give a slight decrease.
      if (linkHref.match(this.REGEXPS.extraneous))
        linkObj.score -= 15;

      /**
       * Minor punishment to anything that doesn't match our current URL.
       * NOTE: I'm finding this to cause more harm than good where something is exactly 50 points.
       *     Dan, can you show me a counterexample where this is necessary?
       * if (linkHref.indexOf(window.location.href) !== 0) {
       *  linkObj.score -= 1;
       * }
      **/

      // If the link text can be parsed as a number, give it a minor bonus, with a slight
      // bias towards lower numbered pages. This is so that pages that might not have 'next'
      // in their text can still get scored, and sorted properly by score.
      var linkTextAsNumber = parseInt(linkText, 10);
      if (linkTextAsNumber) {
        // Punish 1 since we're either already there, or it's probably
        // before what we want anyways.
        if (linkTextAsNumber === 1) {
          linkObj.score -= 10;
        } else {
          linkObj.score += Math.max(0, 10 - linkTextAsNumber);
        }
      }
    }

    // Loop thrugh all of our possible pages from above and find our top
    // candidate for the next page URL. Require at least a score of 50, which
    // is a relatively high confidence that this page is the next link.
    var topPage = null;
    for (var page in possiblePages) {
      if (possiblePages.hasOwnProperty(page)) {
        if (possiblePages[page].score >= 50 &&
          (!topPage || topPage.score < possiblePages[page].score))
          topPage = possiblePages[page];
      }
    }

    var nextHref = null;
    if (topPage) {
      nextHref = topPage.href.replace(/\/$/, '');

      this.log('NEXT PAGE IS ' + nextHref);
      this._parsedPages[nextHref] = true;
    }
    return nextHref;
  },

  _successfulRequest: function(request) {
    return (request.status >= 200 && request.status < 300) ||
        request.status === 304 ||
         (request.status === 0 && request.responseText);
  },

  _ajax: function(url, options) {
    var request = new XMLHttpRequest();

    function respondToReadyState(readyState) {
      if (request.readyState === 4) {
        if (this._successfulRequest(request)) {
          if (options.success)
            options.success(request);
        } else if (options.error) {
          options.error(request);
        }
      }
    }

    if (typeof options === 'undefined')
      options = {};

    request.onreadystatechange = respondToReadyState;

    request.open('get', url, true);
    request.setRequestHeader('Accept', 'text/html');

    try {
      request.send(options.postBody);
    } catch (e) {
      if (options.error)
        options.error();
    }

    return request;
  },

  _appendNextPage: function(nextPageLink) {
    var doc = this._doc;
    this._curPageNum += 1;

    var articlePage = doc.createElement("DIV");
    articlePage.id = 'readability-page-' + this._curPageNum;
    articlePage.className = 'page';
    articlePage.innerHTML = '<p class="page-separator" title="Page ' + this._curPageNum + '">&sect;</p>';

    doc.getElementById("readability-content").appendChild(articlePage);

    if (this._curPageNum > this._maxPages) {
      var nextPageMarkup = "<div style='text-align: center'><a href='" + nextPageLink + "'>View Next Page</a></div>";
      articlePage.innerHTML = articlePage.innerHTML + nextPageMarkup;
      return;
    }

    // Now that we've built the article page DOM element, get the page content
    // asynchronously and load the cleaned content into the div we created for it.
    ((pageUrl, thisPage) => {
      this._ajax(pageUrl, {
        success: function(r) {

          // First, check to see if we have a matching ETag in headers - if we do, this is a duplicate page.
          var eTag = r.getResponseHeader('ETag');
          if (eTag) {
            if (eTag in this._pageETags) {
              this.log("Exact duplicate page found via ETag. Aborting.");
              articlePage.style.display = 'none';
              return;
            }
            this._pageETags[eTag] = 1;
          }

          // TODO: this ends up doubling up page numbers on NYTimes articles. Need to generically parse those away.
          var page = doc.createElement("DIV");

          // Do some preprocessing to our HTML to make it ready for appending.
          // - Remove any script tags. Swap and reswap newlines with a unicode
          //   character because multiline regex doesn't work in javascript.
          // - Turn any noscript tags into divs so that we can parse them. This
          //   allows us to find any next page links hidden via javascript.
          // - Turn all double br's into p's - was handled by prepDocument in the original view.
          //   Maybe in the future abstract out prepDocument to work for both the original document
          //   and AJAX-added pages.
          var responseHtml = r.responseText.replace(/\n/g, '\uffff').replace(/<script.*?>.*?<\/script>/gi, '');
          responseHtml = responseHtml.replace(/\n/g, '\uffff').replace(/<script.*?>.*?<\/script>/gi, '');
          responseHtml = responseHtml.replace(/\uffff/g, '\n').replace(/<(\/?)noscript/gi, '<$1div');
          responseHtml = responseHtml.replace(this.REGEXPS.replaceFonts, '<$1span>');

          page.innerHTML = responseHtml;
          this._replaceBrs(page);

          // Reset all flags for the next page, as they will search through it and
          // disable as necessary at the end of grabArticle.
          this._flags = 0x1 | 0x2 | 0x4;

          var secondNextPageLink = this._findNextPageLink(page);

          // NOTE: if we end up supporting _appendNextPage(), we'll need to
          // change this call to be async
          var content = this._grabArticle(page);

          if (!content) {
            this.log("No content found in page to append. Aborting.");
            return;
          }

          // Anti-duplicate mechanism. Essentially, get the first paragraph of our new page.
          // Compare it against all of the the previous document's we've gotten. If the previous
          // document contains exactly the innerHTML of this first paragraph, it's probably a duplicate.
          var firstP = content.getElementsByTagName("P").length ? content.getElementsByTagName("P")[0] : null;
          if (firstP && firstP.innerHTML.length > 100) {
            for (var i = 1; i <= this._curPageNum; i += 1) {
              var rPage = doc.getElementById('readability-page-' + i);
              if (rPage && rPage.innerHTML.indexOf(firstP.innerHTML) !== -1) {
                this.log('Duplicate of page ' + i + ' - skipping.');
                articlePage.style.display = 'none';
                this._parsedPages[pageUrl] = true;
                return;
              }
            }
          }

          this._removeScripts(content);

          thisPage.innerHTML = thisPage.innerHTML + content.innerHTML;

          // After the page has rendered, post process the content. This delay is necessary because,
          // in webkit at least, offsetWidth is not set in time to determine image width. We have to
          // wait a little bit for reflow to finish before we can fix floating images.
          setTimeout(() => {
            this._postProcessContent(thisPage);
          }, 500);


          if (secondNextPageLink)
            this._appendNextPage(secondNextPageLink);
        }
      });
    })(nextPageLink, articlePage);
  },

  /**
   * Get an elements class/id weight. Uses regular expressions to tell if this
   * element looks good or bad.
   *
   * @param Element
   * @return number (Integer)
  **/
  _getClassWeight: function(e) {
    if (!this._flagIsActive(this.FLAG_WEIGHT_CLASSES))
      return 0;

    var weight = 0;

    // Look for a special classname
    if (typeof(e.className) === 'string' && e.className !== '') {
      if (this.REGEXPS.negative.test(e.className))
        weight -= 25;

      if (this.REGEXPS.positive.test(e.className))
        weight += 25;
    }

    // Look for a special ID
    if (typeof(e.id) === 'string' && e.id !== '') {
      if (this.REGEXPS.negative.test(e.id))
        weight -= 25;

      if (this.REGEXPS.positive.test(e.id))
        weight += 25;
    }

    return weight;
  },

  /**
   * Clean a node of all elements of type "tag".
   * (Unless it's a youtube/vimeo video. People love movies.)
   *
   * @param Element
   * @param string tag to clean
   * @return void
   **/
  _clean: function(e, tag) {
    var isEmbed = ["object", "embed", "iframe"].indexOf(tag) !== -1;

    this._removeNodes(e.getElementsByTagName(tag), function(element) {
      // Allow youtube and vimeo videos through as people usually want to see those.
      if (isEmbed) {
        var attributeValues = [].map.call(element.attributes, function(attr) {
          return attr.value;
        }).join("|");

        // First, check the elements attributes to see if any of them contain youtube or vimeo
        if (this.REGEXPS.videos.test(attributeValues))
          return false;

        // Then check the elements inside this element for the same.
        if (this.REGEXPS.videos.test(element.innerHTML))
          return false;
      }

      return true;
    });
  },

  /**
   * Check if a given node has one of its ancestor tag name matching the
   * provided one.
   * @param  HTMLElement node
   * @param  String      tagName
   * @param  Number      maxDepth
   * @param  Function    filterFn a filter to invoke to determine whether this node 'counts'
   * @return Boolean
   */
  _hasAncestorTag: function(node, tagName, maxDepth, filterFn) {
    maxDepth = maxDepth || 3;
    tagName = tagName.toUpperCase();
    var depth = 0;
    while (node.parentNode) {
      if (maxDepth > 0 && depth > maxDepth)
        return false;
      if (node.parentNode.tagName === tagName && (!filterFn || filterFn(node.parentNode)))
        return true;
      node = node.parentNode;
      depth++;
    }
    return false;
  },

  /**
   * Return an object indicating how many rows and columns this table has.
   */
  _getRowAndColumnCount: function(table) {
    var rows = 0;
    var columns = 0;
    var trs = table.getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
      var rowspan = trs[i].getAttribute("rowspan") || 0;
      if (rowspan) {
        rowspan = parseInt(rowspan, 10);
      }
      rows += (rowspan || 1);

      // Now look for column-related info
      var columnsInThisRow = 0;
      var cells = trs[i].getElementsByTagName("td");
      for (var j = 0; j < cells.length; j++) {
        var colspan = cells[j].getAttribute("colspan") || 0;
        if (colspan) {
          colspan = parseInt(colspan, 10);
        }
        columnsInThisRow += (colspan || 1);
      }
      columns = Math.max(columns, columnsInThisRow);
    }
    return {rows: rows, columns: columns};
  },

  /**
   * Look for 'data' (as opposed to 'layout') tables, for which we use
   * similar checks as
   * https://dxr.mozilla.org/mozilla-central/rev/71224049c0b52ab190564d3ea0eab089a159a4cf/accessible/html/HTMLTableAccessible.cpp#920
   */
  _markDataTables: function(root) {
    var tables = root.getElementsByTagName("table");
    for (var i = 0; i < tables.length; i++) {
      var table = tables[i];
      var role = table.getAttribute("role");
      if (role == "presentation") {
        table._readabilityDataTable = false;
        continue;
      }
      var datatable = table.getAttribute("datatable");
      if (datatable == "0") {
        table._readabilityDataTable = false;
        continue;
      }
      var summary = table.getAttribute("summary");
      if (summary) {
        table._readabilityDataTable = true;
        continue;
      }

      var caption = table.getElementsByTagName("caption")[0];
      if (caption && caption.childNodes.length > 0) {
        table._readabilityDataTable = true;
        continue;
      }

      // If the table has a descendant with any of these tags, consider a data table:
      var dataTableDescendants = ["col", "colgroup", "tfoot", "thead", "th"];
      var descendantExists = function(tag) {
        return !!table.getElementsByTagName(tag)[0];
      };
      if (dataTableDescendants.some(descendantExists)) {
        this.log("Data table because found data-y descendant");
        table._readabilityDataTable = true;
        continue;
      }

      // Nested tables indicate a layout table:
      if (table.getElementsByTagName("table")[0]) {
        table._readabilityDataTable = false;
        continue;
      }

      var sizeInfo = this._getRowAndColumnCount(table);
      if (sizeInfo.rows >= 10 || sizeInfo.columns > 4) {
        table._readabilityDataTable = true;
        continue;
      }
      // Now just go by size entirely:
      table._readabilityDataTable = sizeInfo.rows * sizeInfo.columns > 10;
    }
  },

  /**
   * Clean an element of all tags of type "tag" if they look fishy.
   * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc.
   *
   * @return void
   **/
  _cleanConditionally: function(e, tag) {
    if (!this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY))
      return;

    var isList = tag === "ul" || tag === "ol";

    // Gather counts for other typical elements embedded within.
    // Traverse backwards so we can remove nodes at the same time
    // without effecting the traversal.
    //
    // TODO: Consider taking into account original contentScore here.
    this._removeNodes(e.getElementsByTagName(tag), function(node) {
      // First check if we're in a data table, in which case don't remove us.
      var isDataTable = function(t) {
        return t._readabilityDataTable;
      };

      if (this._hasAncestorTag(node, "table", -1, isDataTable)) {
        return false;
      }

      var weight = this._getClassWeight(node);
      var contentScore = 0;

      this.log("Cleaning Conditionally", node);

      if (weight + contentScore < 0) {
        return true;
      }

      if (this._getCharCount(node, ',') < 10) {
        // If there are not very many commas, and the number of
        // non-paragraph elements is more than paragraphs or other
        // ominous signs, remove the element.
        var p = node.getElementsByTagName("p").length;
        var img = node.getElementsByTagName("img").length;
        var li = node.getElementsByTagName("li").length - 100;
        var input = node.getElementsByTagName("input").length;

        var embedCount = 0;
        var embeds = node.getElementsByTagName("embed");
        for (var ei = 0, il = embeds.length; ei < il; ei += 1) {
          if (!this.REGEXPS.videos.test(embeds[ei].src))
            embedCount += 1;
        }

        var linkDensity = this._getLinkDensity(node);
        var contentLength = this._getInnerText(node).length;

        var haveToRemove =
          (img > 1 && p / img < 0.5 && !this._hasAncestorTag(node, "figure")) ||
          (!isList && li > p) ||
          (input > Math.floor(p/3)) ||
          (!isList && contentLength < 25 && (img === 0 || img > 2) && !this._hasAncestorTag(node, "figure")) ||
          (!isList && weight < 25 && linkDensity > 0.2) ||
          (weight >= 25 && linkDensity > 0.5) ||
          ((embedCount === 1 && contentLength < 75) || embedCount > 1);
        return haveToRemove;
      }
      return false;
    });
  },

  /**
   * Clean out elements whose id/class combinations match specific string.
   *
   * @param Element
   * @param RegExp match id/class combination.
   * @return void
   **/
  _cleanMatchedNodes: function(e, regex) {
    var endOfSearchMarkerNode = this._getNextNode(e, true);
    var next = this._getNextNode(e);
    while (next && next != endOfSearchMarkerNode) {
      if (regex.test(next.className + " " + next.id)) {
        next = this._removeAndGetNext(next);
      } else {
        next = this._getNextNode(next);
      }
    }
  },

  /**
   * Clean out spurious headers from an Element. Checks things like classnames and link density.
   *
   * @param Element
   * @return void
  **/
  _cleanHeaders: function(e) {
    for (var headerIndex = 1; headerIndex < 3; headerIndex += 1) {
      this._removeNodes(e.getElementsByTagName('h' + headerIndex), function (header) {
        return this._getClassWeight(header) < 0;
      });
    }
  },

  _flagIsActive: function(flag) {
    return (this._flags & flag) > 0;
  },

  _addFlag: function(flag) {
    this._flags = this._flags | flag;
  },

  _removeFlag: function(flag) {
    this._flags = this._flags & ~flag;
  },

  /**
   * Decides whether or not the document is reader-able without parsing the whole thing.
   *
   * @return boolean Whether or not we suspect parse() will suceeed at returning an article object.
   */
  isProbablyReaderable: function(helperIsVisible) {
    var nodes = this._getAllNodesWithTag(this._doc, ["p", "pre"]);

    // Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
    // Some articles' DOM structures might look like
    // <div>
    //   Sentences<br>
    //   <br>
    //   Sentences<br>
    // </div>
    var brNodes = this._getAllNodesWithTag(this._doc, ["div > br"]);
    if (brNodes.length) {
      var set = new Set();
      [].forEach.call(brNodes, function(node) {
        set.add(node.parentNode);
      });
      nodes = [].concat.apply(Array.from(set), nodes);
    }

    // FIXME we should have a fallback for helperIsVisible, but this is
    // problematic because of jsdom's elem.style handling - see
    // https://github.com/mozilla/readability/pull/186 for context.

    var score = 0;
    // This is a little cheeky, we use the accumulator 'score' to decide what to return from
    // this callback:
    return this._someNode(nodes, function(node) {
      if (helperIsVisible && !helperIsVisible(node))
        return false;
      var matchString = node.className + " " + node.id;

      if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
          !this.REGEXPS.okMaybeItsACandidate.test(matchString)) {
        return false;
      }

      if (node.matches && node.matches("li p")) {
        return false;
      }

      var textContentLength = node.textContent.trim().length;
      if (textContentLength < 140) {
        return false;
      }

      score += Math.sqrt(textContentLength - 140);

      if (score > 20) {
        return true;
      }
      return false;
    });
  },

  /**
   * Runs readability.
   *
   * Workflow:
   *  1. Prep the document by removing script tags, css, etc.
   *  2. Build readability's DOM tree.
   *  3. Grab the article content from the current dom tree.
   *  4. Replace the current DOM tree with the new one.
   *  5. Read peacefully.
   *
   * @return void
   **/
  parse: function () {
    // Avoid parsing too large documents, as per configuration option
    if (this._maxElemsToParse > 0) {
      var numTags = this._doc.getElementsByTagName("*").length;
      if (numTags > this._maxElemsToParse) {
        throw new Error("Aborting parsing document; " + numTags + " elements found");
      }
    }

    if (typeof this._doc.documentElement.firstElementChild === "undefined") {
      this._getNextNode = this._getNextNodeNoElementProperties;
    }
    // Remove script tags from the document.
    this._removeScripts(this._doc);

    // FIXME: Disabled multi-page article support for now as it
    // needs more work on infrastructure.

    // Make sure this document is added to the list of parsed pages first,
    // so we don't double up on the first page.
    // this._parsedPages[uri.spec.replace(/\/$/, '')] = true;

    // Pull out any possible next page link first.
    // var nextPageLink = this._findNextPageLink(doc.body);

    this._prepDocument();

    var metadata = this._getArticleMetadata();
    this._articleTitle = metadata.title;

    var articleContent = this._grabArticle();
    if (!articleContent)
      return null;

    this.log("Grabbed: " + articleContent.innerHTML);

    this._postProcessContent(articleContent);

    // if (nextPageLink) {
    //   // Append any additional pages after a small timeout so that people
    //   // can start reading without having to wait for this to finish processing.
    //   setTimeout((function() {
    //     this._appendNextPage(nextPageLink);
    //   }).bind(this), 500);
    // }

    // If we haven't found an excerpt in the article's metadata, use the article's
    // first paragraph as the excerpt. This is used for displaying a preview of
    // the article's content.
    if (!metadata.excerpt) {
      var paragraphs = articleContent.getElementsByTagName("p");
      if (paragraphs.length > 0) {
        metadata.excerpt = paragraphs[0].textContent.trim();
      }
    }

    var textContent = articleContent.textContent;
    return {
      uri: this._uri,
      title: this._articleTitle,
      byline: metadata.byline || this._articleByline,
      dir: this._articleDir,
      content: articleContent.innerHTML,
      textContent: textContent,
      length: textContent.length,
      excerpt: metadata.excerpt,
    };
  }
};

if (typeof module === "object") {
  module.exports = Readability;
}
PK
!<$\modules/reader/ReaderWorker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/chrome-worker */

"use strict";

/**
 * A worker dedicated to handle parsing documents for reader view.
 */

importScripts("resource://gre/modules/workers/require.js",
              "resource://gre/modules/reader/JSDOMParser.js",
              "resource://gre/modules/reader/Readability.js");

var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");

const DEBUG = false;

var worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function(method, args = []) {
  return Agent[method](...args);
};
worker.postMessage = function(result, ...transfers) {
  self.postMessage(result, ...transfers);
};
worker.close = function() {
  self.close();
};
worker.log = function(...args) {
  if (DEBUG) {
    dump("ReaderWorker: " + args.join(" ") + "\n");
  }
};

self.addEventListener("message", msg => worker.handleMessage(msg));

var Agent = {
  /**
   * Parses structured article data from a document.
   *
   * @param {object} uri URI data for the document.
   * @param {string} serializedDoc The serialized document.
   *
   * @return {object} Article object returned from Readability.
   */
  parseDocument(uri, serializedDoc) {
    let doc = new JSDOMParser().parse(serializedDoc);
    return new Readability(uri, doc).parse();
  },
};
PK
!<

modules/reader/ReaderWorker.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Interface to a dedicated thread handling readability parsing.
 */

const Cu = Components.utils;

Cu.import("resource://gre/modules/PromiseWorker.jsm", this);

this.EXPORTED_SYMBOLS = ["ReaderWorker"];

this.ReaderWorker = new BasePromiseWorker("resource://gre/modules/reader/ReaderWorker.js");
PK
!<y";;modules/reflect.jsm/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [ "Reflect" ];

/*
 * This is the js module for Reflect. Import it like so:
 *   Components.utils.import("resource://gre/modules/reflect.jsm");
 *
 * This will create a 'Reflect' object, which provides an interface to the
 * SpiderMonkey parser API.
 *
 * For documentation on the API, see:
 * https://developer.mozilla.org/en/SpiderMonkey/Parser_API
 *
 */


// Initialize the ctypes object. You do not need to do this yourself.
const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
init();
this.Reflect = Reflect;
PK
!<^((3(3modules/sdk/bootstrap.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @see http://dxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp

'use strict';

// IMPORTANT: Avoid adding any initialization tasks here, if you need to do
// something before add-on is loaded consider addon/runner module instead!

const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
        results: Cr, manager: Cm } = Components;
const ioService = Cc['@mozilla.org/network/io-service;1'].
                  getService(Ci.nsIIOService);
const resourceHandler = ioService.getProtocolHandler('resource').
                        QueryInterface(Ci.nsIResProtocolHandler);
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                     getService(Ci.mozIJSSubScriptLoader);
const prefService = Cc['@mozilla.org/preferences-service;1'].
                    getService(Ci.nsIPrefService).
                    QueryInterface(Ci.nsIPrefBranch);
const appInfo = Cc["@mozilla.org/xre/app-info;1"].
                getService(Ci.nsIXULAppInfo);
const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
           getService(Ci.nsIVersionComparator);

const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});

const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;


const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
                 'install', 'uninstall', 'upgrade', 'downgrade' ];

const bind = Function.call.bind(Function.bind);

var loader = null;
var unload = null;
var cuddlefishSandbox = null;
var nukeTimer = null;

var resourceDomains = [];
function setResourceSubstitution(domain, uri) {
  resourceDomains.push(domain);
  resourceHandler.setSubstitution(domain, uri);
}

// Utility function that synchronously reads local resource from the given
// `uri` and returns content string.
function readURI(uri) {
  let channel = NetUtil.newChannel({
    uri: NetUtil.newURI(uri, 'UTF-8'),
    loadUsingSystemPrincipal: true
  });

  let stream = channel.open2();

  let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
    createInstance(Ci.nsIConverterInputStream);
  cstream.init(stream, 'UTF-8', 0, 0);

  let str = {};
  let data = '';
  let read = 0;
  do {
    read = cstream.readString(0xffffffff, str);
    data += str.value;
  } while (read != 0);

  cstream.close();

  return data;
}

// We don't do anything on install & uninstall yet, but in a future
// we should allow add-ons to cleanup after uninstall.
function install(data, reason) {}
function uninstall(data, reason) {}

function startup(data, reasonCode) {
  try {
    let reason = REASON[reasonCode];
    // URI for the root of the XPI file.
    // 'jar:' URI if the addon is packed, 'file:' URI otherwise.
    // (Used by l10n module in order to fetch `locale` folder)
    let rootURI = data.resourceURI.spec;

    // TODO: Maybe we should perform read harness-options.json asynchronously,
    // since we can't do anything until 'sessionstore-windows-restored' anyway.
    let options = JSON.parse(readURI(rootURI + './harness-options.json'));

    let id = options.jetpackID;
    let name = options.name;

    // Clean the metadata
    options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};

    // freeze the permissionss
    Object.freeze(options.metadata[name]['permissions']);
    // freeze the metadata
    Object.freeze(options.metadata[name]);

    // Register a new resource 'domain' for this addon which is mapping to
    // XPI's `resources` folder.
    // Generate the domain name by using jetpack ID, which is the extension ID
    // by stripping common characters that doesn't work as a domain name:
    let uuidRe =
      /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;

    let domain = id.
      toLowerCase().
      replace(/@/g, '-at-').
      replace(/\./g, '-dot-').
      replace(uuidRe, '$1');

    let prefixURI = 'resource://' + domain + '/';
    let resourcesURI = ioService.newURI(rootURI + '/resources/');
    setResourceSubstitution(domain, resourcesURI);

    // Create path to URLs mapping supported by loader.
    let paths = {
      // Relative modules resolve to add-on package lib
      './': prefixURI + name + '/lib/',
      './tests/': prefixURI + name + '/tests/',
      '': 'resource://gre/modules/commonjs/'
    };

    // Maps addon lib and tests ressource folders for each package
    paths = Object.keys(options.metadata).reduce(function(result, name) {
      result[name + '/'] = prefixURI + name + '/lib/'
      result[name + '/tests/'] = prefixURI + name + '/tests/'
      return result;
    }, paths);

    // We need to map tests folder when we run sdk tests whose package name
    // is stripped
    if (name == 'addon-sdk')
      paths['tests/'] = prefixURI + name + '/tests/';

    let useBundledSDK = options['force-use-bundled-sdk'];
    if (!useBundledSDK) {
      try {
        useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
      }
      catch (e) {
        // Pref doesn't exist, allow using Firefox shipped SDK
      }
    }

    // Starting with Firefox 21.0a1, we start using modules shipped into firefox
    // Still allow using modules from the xpi if the manifest tell us to do so.
    // And only try to look for sdk modules in xpi if the xpi actually ship them
    if (options['is-sdk-bundled'] &&
        (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
      // Maps sdk module folders to their resource folder
      paths[''] = prefixURI + 'addon-sdk/lib/';
      // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
      // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
      // until we no longer support SDK modules in XPI:
      paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
    }

    // Retrieve list of module folder overloads based on preferences in order to
    // eventually used a local modules instead of files shipped into Firefox.
    let branch = prefService.getBranch('extensions.modules.' + id + '.path');
    paths = branch.getChildList('', {}).reduce(function (result, name) {
      // Allows overloading of any sub folder by replacing . by / in pref name
      let path = name.substr(1).split('.').join('/');
      // Only accept overloading folder by ensuring always ending with `/`
      if (path) path += '/';
      let fileURI = branch.getCharPref(name);

      // On mobile, file URI has to end with a `/` otherwise, setSubstitution
      // takes the parent folder instead.
      if (fileURI[fileURI.length-1] !== '/')
        fileURI += '/';

      // Maps the given file:// URI to a resource:// in order to avoid various
      // failure that happens with file:// URI and be close to production env
      let resourcesURI = ioService.newURI(fileURI);
      let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
      setResourceSubstitution(resName, resourcesURI);

      result[path] = 'resource://' + resName + '/';
      return result;
    }, paths);

    // Make version 2 of the manifest
    let manifest = options.manifest;

    // Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
    let cuddlefishPath = 'loader/cuddlefish.js';
    let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
    if (paths['sdk/']) { // sdk folder has been overloaded
                         // (from pref, or cuddlefish is still in the xpi)
      cuddlefishURI = paths['sdk/'] + cuddlefishPath;
    }
    else if (paths['']) { // root modules folder has been overloaded
      cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
    }

    cuddlefishSandbox = loadSandbox(cuddlefishURI);
    let cuddlefish = cuddlefishSandbox.exports;

    // Normalize `options.mainPath` so that it looks like one that will come
    // in a new version of linker.
    let main = options.mainPath;

    unload = cuddlefish.unload;
    loader = cuddlefish.Loader({
      paths: paths,
      // modules manifest.
      manifest: manifest,

      // Add-on ID used by different APIs as a unique identifier.
      id: id,
      // Add-on name.
      name: name,
      // Add-on version.
      version: options.metadata[name].version,
      // Add-on package descriptor.
      metadata: options.metadata[name],
      // Add-on load reason.
      loadReason: reason,

      prefixURI: prefixURI,
      // Add-on URI.
      rootURI: rootURI,
      // options used by system module.
      // File to write 'OK' or 'FAIL' (exit code emulation).
      resultFile: options.resultFile,
      // Arguments passed as --static-args
      staticArgs: options.staticArgs,

      // Option to prevent automatic kill of firefox during tests
      noQuit: options.no_quit,

      // Add-on preferences branch name
      preferencesBranch: options.preferencesBranch,

      // Arguments related to test runner.
      modules: {
        '@test/options': {
          iterations: options.iterations,
          filter: options.filter,
          profileMemory: options.profileMemory,
          stopOnError: options.stopOnError,
          verbose: options.verbose,
          parseable: options.parseable,
          checkMemory: options.check_memory,
        }
      }
    });

    let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
    let require = cuddlefish.Require(loader, module);

    // Init the 'sdk/webextension' module from the bootstrap addon parameter.
    require("sdk/webextension").initFromBootstrapAddonParam(data);

    require('sdk/addon/runner').startup(reason, {
      loader: loader,
      main: main,
      prefsURI: rootURI + 'defaults/preferences/prefs.js'
    });
  } catch (error) {
    dump('Bootstrap error: ' +
         (error.message ? error.message : String(error)) + '\n' +
         (error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
    throw error;
  }
};

function loadSandbox(uri) {
  let proto = {
    sandboxPrototype: {
      loadSandbox: loadSandbox,
      ChromeWorker: ChromeWorker
    }
  };
  let sandbox = Cu.Sandbox(systemPrincipal, proto);
  // Create a fake commonjs environnement just to enable loading loader.js
  // correctly
  sandbox.exports = {};
  sandbox.module = { uri: uri, exports: sandbox.exports };
  sandbox.require = function (id) {
    if (id !== "chrome")
      throw new Error("Bootstrap sandbox `require` method isn't implemented.");

    return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
      CC: bind(CC, Components), components: Components,
      ChromeWorker: ChromeWorker });
  };
  scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
  return sandbox;
}

function unloadSandbox(sandbox) {
  if (Cu.getClassName(sandbox, true) == "Sandbox")
    Cu.nukeSandbox(sandbox);
}

function setTimeout(callback, delay) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback({ notify: callback }, delay,
                         Ci.nsITimer.TYPE_ONE_SHOT);
  return timer;
}

function shutdown(data, reasonCode) {
  let reason = REASON[reasonCode];
  if (loader) {
    unload(loader, reason);
    unload = null;

    // Don't waste time cleaning up if the application is shutting down
    if (reason != "shutdown") {
      // Avoid leaking all modules when something goes wrong with one particular
      // module. Do not clean it up immediatly in order to allow executing some
      // actions on addon disabling.
      // We need to keep a reference to the timer, otherwise it is collected
      // and won't ever fire.
      nukeTimer = setTimeout(nukeModules, 1000);

      // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload
      resourceDomains.forEach(domain => {
        resourceHandler.setSubstitution(domain, null);
      })
    }
  }
};

function nukeModules() {
  nukeTimer = null;
  // module objects store `exports` which comes from sandboxes
  // We should avoid keeping link to these object to avoid leaking sandboxes
  for (let key in loader.modules) {
    delete loader.modules[key];
  }
  // Direct links to sandboxes should be removed too
  for (let key in loader.sandboxes) {
    let sandbox = loader.sandboxes[key];
    delete loader.sandboxes[key];
    // Bug 775067: From FF17 we can kill all CCW from a given sandbox
    unloadSandbox(sandbox);
  }
  unloadSandbox(loader.sharedGlobalSandbox);
  loader = null;

  // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
  // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
  // the addon is unload.

  unloadSandbox(cuddlefishSandbox.loaderSandbox);

  // Bug 764840: We need to unload cuddlefish otherwise it will stay alive
  // and keep a reference to this compartment.
  unloadSandbox(cuddlefishSandbox);
  cuddlefishSandbox = null;
}
PK
!<q((modules/sdk/system/Startup.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

var EXPORTED_SYMBOLS = ["Startup"];

var { utils: Cu, interfaces: Ci, classes: Cc } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;

const { XulApp } = Cu.import("resource://gre/modules/commonjs/sdk/system/xul-app.jsm", {});

const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
                       .getService(Ci.nsIAppStartup);

const NAME2TOPIC = {
  'Firefox': 'sessionstore-windows-restored',
  'Fennec': 'sessionstore-windows-restored',
  'SeaMonkey': 'sessionstore-windows-restored',
  'Thunderbird': 'mail-startup-done',
  'Instantbird': 'xul-window-visible'
};

var Startup = {
  initialized: !appStartupSrv.startingUp
};
var exports = Startup;

var gOnceInitializedDeferred = defer();
exports.onceInitialized = gOnceInitializedDeferred.promise;

// Set 'final-ui-startup' as default topic for unknown applications
var appStartup = 'final-ui-startup';

if (Startup.initialized) {
  gOnceInitializedDeferred.resolve()
}
else {
  // Gets the topic that fit best as application startup event, in according with
  // the current application (e.g. Firefox, Fennec, Thunderbird...)
  for (let name of Object.keys(NAME2TOPIC)) {
    if (XulApp.is(name)) {
      appStartup = NAME2TOPIC[name];
      break;
    }
  }

  let listener = function (subject, topic) {
    Services.obs.removeObserver(this, topic);
    Startup.initialized = true;
    Services.tm.dispatchToMainThread(() => gOnceInitializedDeferred.resolve());
  }

  Services.obs.addObserver(listener, appStartup);
}
PK
!<aڳ   modules/services-common/async.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Async"];

var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

// Constants for makeSyncCallback, waitForSyncCallback.
const CB_READY = {};
const CB_COMPLETE = {};
const CB_FAIL = {};

const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR;

Cu.import("resource://gre/modules/Services.jsm");

/*
 * Helpers for various async operations.
 */
this.Async = {

  /**
   * Execute an arbitrary number of asynchronous functions one after the
   * other, passing the callback arguments on to the next one.  All functions
   * must take a callback function as their last argument.  The 'this' object
   * will be whatever chain()'s is.
   *
   * @usage this._chain = Async.chain;
   *        this._chain(this.foo, this.bar, this.baz)(args, for, foo)
   *
   * This is equivalent to:
   *
   *   let self = this;
   *   self.foo(args, for, foo, function (bars, args) {
   *     self.bar(bars, args, function (baz, params) {
   *       self.baz(baz, params);
   *     });
   *   });
   */
  chain: function chain() {
    let funcs = Array.slice(arguments);
    let thisObj = this;
    return function callback() {
      if (funcs.length) {
        let args = Array.slice(arguments).concat(callback);
        let f = funcs.shift();
        f.apply(thisObj, args);
      }
    };
  },

  /**
   * Helpers for making asynchronous calls within a synchronous API possible.
   *
   * If you value your sanity, do not look closely at the following functions.
   */

  /**
   * Create a sync callback that remembers state, in particular whether it has
   * been called.
   * The returned callback can be called directly passing an optional arg which
   * will be returned by waitForSyncCallback().  The callback also has a
   * .throw() method, which takes an error object and will cause
   * waitForSyncCallback to fail with the error object thrown as an exception
   * (but note that the .throw method *does not* itself throw - it just causes
   * the wait function to throw).
   */
  makeSyncCallback: function makeSyncCallback() {
    // The main callback remembers the value it was passed, and that it got data.
    let onComplete = function onComplete(data) {
      onComplete.state = CB_COMPLETE;
      onComplete.value = data;
    };

    // Initialize private callback data in preparation for being called.
    onComplete.state = CB_READY;
    onComplete.value = null;

    // Allow an alternate callback to trigger an exception to be thrown.
    onComplete.throw = function onComplete_throw(data) {
      onComplete.state = CB_FAIL;
      onComplete.value = data;
    };

    return onComplete;
  },

  /**
   * Wait for a sync callback to finish.
   */
  waitForSyncCallback: function waitForSyncCallback(callback) {
    // Grab the current thread so we can make it give up priority.
    let tm = Cc["@mozilla.org/thread-manager;1"].getService();

    // Keep waiting until our callback is triggered (unless the app is quitting).
    tm.spinEventLoopUntil(() => !Async.checkAppReady || callback.state != CB_READY);

    // Reset the state of the callback to prepare for another call.
    let state = callback.state;
    callback.state = CB_READY;

    // Throw the value the callback decided to fail with.
    if (state == CB_FAIL) {
      throw callback.value;
    }

    // Return the value passed to the callback.
    return callback.value;
  },

  /**
   * Check if the app is still ready (not quitting). Returns true, or throws an
   * exception if not ready.
   */
  checkAppReady: function checkAppReady() {
    // Watch for app-quit notification to stop any sync calls
    Services.obs.addObserver(function onQuitApplication() {
      Services.obs.removeObserver(onQuitApplication, "quit-application");
      Async.checkAppReady = Async.promiseYield = function() {
        let exception = Components.Exception("App. Quitting", Cr.NS_ERROR_ABORT);
        exception.appIsShuttingDown = true;
        throw exception;
      };
    }, "quit-application");
    // In the common case, checkAppReady just returns true
    return (Async.checkAppReady = function() { return true; })();
  },

  /**
   * Check if the app is still ready (not quitting). Returns true if the app
   * is ready, or false if it is being shut down.
   */
  isAppReady() {
    try {
      return Async.checkAppReady()
    } catch (ex) {
      if (!Async.isShutdownException(ex)) {
        throw ex;
      }
    }
    return false;
  },

  /**
   * Check if the passed exception is one raised by checkAppReady. Typically
   * this will be used in exception handlers to allow such exceptions to
   * make their way to the top frame and allow the app to actually terminate.
   */
  isShutdownException(exception) {
    return exception && exception.appIsShuttingDown === true;
  },

  /**
   * Return the two things you need to make an asynchronous call synchronous
   * by spinning the event loop.
   */
  makeSpinningCallback: function makeSpinningCallback() {
    let cb = Async.makeSyncCallback();
    function callback(error, ret) {
      if (error)
        cb.throw(error);
      else
        cb(ret);
    }
    callback.wait = () => Async.waitForSyncCallback(cb);
    return callback;
  },

  // Prototype for mozIStorageCallback, used in querySpinningly.
  // This allows us to define the handle* functions just once rather
  // than on every querySpinningly invocation.
  _storageCallbackPrototype: {
    results: null,

    // These are set by queryAsync.
    names: null,
    syncCb: null,

    handleResult: function handleResult(results) {
      if (!this.names) {
        return;
      }
      if (!this.results) {
        this.results = [];
      }
      let row;
      while ((row = results.getNextRow()) != null) {
        let item = {};
        for (let name of this.names) {
          item[name] = row.getResultByName(name);
        }
        this.results.push(item);
      }
    },
    handleError: function handleError(error) {
      this.syncCb.throw(error);
    },
    handleCompletion: function handleCompletion(reason) {

      // If we got an error, handleError will also have been called, so don't
      // call the callback! We never cancel statements, so we don't need to
      // address that quandary.
      if (reason == REASON_ERROR)
        return;

      // If we were called with column names but didn't find any results,
      // the calling code probably still expects an array as a return value.
      if (this.names && !this.results) {
        this.results = [];
      }
      this.syncCb(this.results);
    }
  },

  querySpinningly: function querySpinningly(query, names) {
    // 'Synchronously' asyncExecute, fetching all results by name.
    let storageCallback = Object.create(Async._storageCallbackPrototype);
    storageCallback.names = names;
    storageCallback.syncCb = Async.makeSyncCallback();
    query.executeAsync(storageCallback);
    return Async.waitForSyncCallback(storageCallback.syncCb);
  },

  promiseSpinningly(promise) {
    let cb = Async.makeSpinningCallback();
    promise.then(result => {
      cb(null, result);
    }, err => {
      cb(err || new Error("Promise rejected without explicit error"));
    });
    return cb.wait();
  },

  /**
   * A "tight loop" of promises can still lock up the browser for some time.
   * Periodically waiting for a promise returned by this function will solve
   * that.
   * You should probably not use this method directly and instead use jankYielder
   * below.
   * Some reference here:
   * - https://gist.github.com/jesstelford/bbb30b983bddaa6e5fef2eb867d37678
   * - https://bugzilla.mozilla.org/show_bug.cgi?id=1094248
   */
  promiseYield() {
    return new Promise(resolve => {
      Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
    });
  },

  // Returns a method that yields every X calls.
  // Common case is calling the returned method every iteration in a loop.
  jankYielder(yieldEvery = 50) {
    let iterations = 0;
    return async () => {
      Async.checkAppReady(); // Let it throw!
      if (++iterations % yieldEvery === 0) {
        await Async.promiseYield();
      }
    }
  }
};
PK
!<Y*DD,modules/services-common/blocklist-clients.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["AddonBlocklistClient",
                         "GfxBlocklistClient",
                         "OneCRLBlocklistClient",
                         "PinningBlocklistClient",
                         "PluginBlocklistClient"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
Cu.importGlobalProperties(["fetch"]);

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Kinto",
                                  "resource://services-common/kinto-offline-client.js");
XPCOMUtils.defineLazyModuleGetter(this, "KintoHttpClient",
                                  "resource://services-common/kinto-http-client.js");
XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAdapter",
                                  "resource://services-common/kinto-storage-adapter.js");
XPCOMUtils.defineLazyModuleGetter(this, "CanonicalJSON",
                                  "resource://gre/modules/CanonicalJSON.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UptakeTelemetry",
                                  "resource://services-common/uptake-telemetry.js");

const KEY_APPDIR                             = "XCurProcD";
const PREF_SETTINGS_SERVER                   = "services.settings.server";
const PREF_BLOCKLIST_BUCKET                  = "services.blocklist.bucket";
const PREF_BLOCKLIST_ONECRL_COLLECTION       = "services.blocklist.onecrl.collection";
const PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS  = "services.blocklist.onecrl.checked";
const PREF_BLOCKLIST_ADDONS_COLLECTION       = "services.blocklist.addons.collection";
const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS  = "services.blocklist.addons.checked";
const PREF_BLOCKLIST_PLUGINS_COLLECTION      = "services.blocklist.plugins.collection";
const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
const PREF_BLOCKLIST_PINNING_ENABLED         = "services.blocklist.pinning.enabled";
const PREF_BLOCKLIST_PINNING_BUCKET          = "services.blocklist.pinning.bucket";
const PREF_BLOCKLIST_PINNING_COLLECTION      = "services.blocklist.pinning.collection";
const PREF_BLOCKLIST_PINNING_CHECKED_SECONDS = "services.blocklist.pinning.checked";
const PREF_BLOCKLIST_GFX_COLLECTION          = "services.blocklist.gfx.collection";
const PREF_BLOCKLIST_GFX_CHECKED_SECONDS     = "services.blocklist.gfx.checked";
const PREF_BLOCKLIST_ENFORCE_SIGNING         = "services.blocklist.signing.enforced";

const INVALID_SIGNATURE = "Invalid content/signature";

// FIXME: this was the default path in earlier versions of
// FirefoxAdapter, so for backwards compatibility we maintain this
// filename, even though it isn't descriptive of who is using it.
this.KINTO_STORAGE_PATH    = "kinto.sqlite";



function mergeChanges(collection, localRecords, changes) {
  const records = {};
  // Local records by id.
  localRecords.forEach((record) => records[record.id] = collection.cleanLocalFields(record));
  // All existing records are replaced by the version from the server.
  changes.forEach((record) => records[record.id] = record);

  return Object.values(records)
    // Filter out deleted records.
    .filter((record) => record.deleted != true)
    // Sort list by record id.
    .sort((a, b) => {
      if (a.id < b.id) {
        return -1;
      }
      return a.id > b.id ? 1 : 0;
    });
}


function fetchCollectionMetadata(remote, collection) {
  const client = new KintoHttpClient(remote);
  return client.bucket(collection.bucket).collection(collection.name).getData()
    .then(result => {
      return result.signature;
    });
}

function fetchRemoteCollection(remote, collection) {
  const client = new KintoHttpClient(remote);
  return client.bucket(collection.bucket)
           .collection(collection.name)
           .listRecords({sort: "id"});
}


class BlocklistClient {

  constructor(collectionName, lastCheckTimePref, processCallback, bucketName, signerName) {
    this.collectionName = collectionName;
    this.lastCheckTimePref = lastCheckTimePref;
    this.processCallback = processCallback;
    this.bucketName = bucketName;
    this.signerName = signerName;

    this._kinto = null;
  }

  get identifier() {
    return `${this.bucketName}/${this.collectionName}`;
  }

  get filename() {
    // Replace slash by OS specific path separator (eg. Windows)
    const identifier = OS.Path.join(...this.identifier.split("/"));
    return `${identifier}.json`;
  }

  /**
   * Load the the JSON file distributed with the release for this blocklist.
   *
   * For Bug 1257565 this method will have to try to load the file from the profile,
   * in order to leverage the updateJSONBlocklist() below, which writes a new
   * dump each time the collection changes.
   */
  async loadDumpFile() {
    // Replace OS specific path separator by / for URI.
    const { components: folderFile } = OS.Path.split(this.filename);
    const fileURI = `resource://app/defaults/${folderFile.join("/")}`;
    const response = await fetch(fileURI);
    if (!response.ok) {
      throw new Error(`Could not read from '${fileURI}'`);
    }
    // Will be rejected if JSON is invalid.
    return response.json();
  }

  async validateCollectionSignature(remote, payload, collection, options = {}) {
    const {ignoreLocal} = options;

    // this is a content-signature field from an autograph response.
    const {x5u, signature} = await fetchCollectionMetadata(remote, collection);
    const certChainResponse = await fetch(x5u)
    const certChain = await certChainResponse.text();

    const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
                       .createInstance(Ci.nsIContentSignatureVerifier);

    let toSerialize;
    if (ignoreLocal) {
      toSerialize = {
        last_modified: `${payload.last_modified}`,
        data: payload.data
      };
    } else {
      const {data: localRecords} = await collection.list();
      const records = mergeChanges(collection, localRecords, payload.changes);
      toSerialize = {
        last_modified: `${payload.lastModified}`,
        data: records
      };
    }

    const serialized = CanonicalJSON.stringify(toSerialize);

    if (verifier.verifyContentSignature(serialized, "p384ecdsa=" + signature,
                                        certChain,
                                        this.signerName)) {
      // In case the hash is valid, apply the changes locally.
      return payload;
    }
    throw new Error(INVALID_SIGNATURE);
  }

  /**
   * Synchronize from Kinto server, if necessary.
   *
   * @param {int}  lastModified     the lastModified date (on the server) for
                                    the remote collection.
   * @param {Date} serverTime       the current date return by the server.
   * @param {Object} options        additional advanced options.
   * @param {bool} options.loadDump load initial dump from disk on first sync (default: true)
   * @return {Promise}              which rejects on sync or process failure.
   */
  async maybeSync(lastModified, serverTime, options = {loadDump: true}) {
    const {loadDump} = options;
    const remote = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
    const enforceCollectionSigning =
      Services.prefs.getBoolPref(PREF_BLOCKLIST_ENFORCE_SIGNING);

    if (!this._kinto) {
      this._kinto = new Kinto({
        bucket: this.bucketName,
        adapter: FirefoxAdapter,
      });
    }

    // if there is a signerName and collection signing is enforced, add a
    // hook for incoming changes that validates the signature
    let hooks;
    if (this.signerName && enforceCollectionSigning) {
      hooks = {
        "incoming-changes": [(payload, collection) => {
          return this.validateCollectionSignature(remote, payload, collection);
        }]
      }
    }

    let sqliteHandle;
    let reportStatus = null;
    try {
      // Synchronize remote data into a local Sqlite DB.
      sqliteHandle = await FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
      const options = {
        hooks,
        adapterOptions: {sqliteHandle},
      };
      const collection = this._kinto.collection(this.collectionName, options);

      let collectionLastModified = await collection.db.getLastModified();

      // If there is no data currently in the collection, attempt to import
      // initial data from the application defaults.
      // This allows to avoid synchronizing the whole collection content on
      // cold start.
      if (!collectionLastModified && loadDump) {
        try {
          const initialData = await this.loadDumpFile();
          await collection.loadDump(initialData.data);
          collectionLastModified = await collection.db.getLastModified();
        } catch (e) {
          // Report but go-on.
          Cu.reportError(e);
        }
      }

      // If the data is up to date, there's no need to sync. We still need
      // to record the fact that a check happened.
      if (lastModified <= collectionLastModified) {
        this.updateLastCheck(serverTime);
        reportStatus = UptakeTelemetry.STATUS.UP_TO_DATE;
        return;
      }

      // Fetch changes from server.
      try {
        // Server changes have priority during synchronization.
        const strategy = Kinto.syncStrategy.SERVER_WINS;
        const {ok} = await collection.sync({remote, strategy});
        if (!ok) {
          // Some synchronization conflicts occured.
          reportStatus = UptakeTelemetry.STATUS.CONFLICT_ERROR;
          throw new Error("Sync failed");
        }
      } catch (e) {
        if (e.message == INVALID_SIGNATURE) {
          // Signature verification failed during synchronzation.
          reportStatus = UptakeTelemetry.STATUS.SIGNATURE_ERROR;
          // if sync fails with a signature error, it's likely that our
          // local data has been modified in some way.
          // We will attempt to fix this by retrieving the whole
          // remote collection.
          const payload = await fetchRemoteCollection(remote, collection);
          try {
            await this.validateCollectionSignature(remote, payload, collection, {ignoreLocal: true});
          } catch (e) {
            reportStatus = UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR;
            throw e;
          }
          // if the signature is good (we haven't thrown), and the remote
          // last_modified is newer than the local last_modified, replace the
          // local data
          const localLastModified = await collection.db.getLastModified();
          if (payload.last_modified >= localLastModified) {
            await collection.clear();
            await collection.loadDump(payload.data);
          }
        } else {
          // The sync has thrown, it can be a network or a general error.
          if (/NetworkError/.test(e.message)) {
            reportStatus = UptakeTelemetry.STATUS.NETWORK_ERROR;
          } else if (/Backoff/.test(e.message)) {
            reportStatus = UptakeTelemetry.STATUS.BACKOFF;
          } else {
            reportStatus = UptakeTelemetry.STATUS.SYNC_ERROR;
          }
          throw e;
        }
      }
      // Read local collection of records.
      const {data} = await collection.list();

      // Handle the obtained records (ie. apply locally).
      try {
        await this.processCallback(data);
      } catch (e) {
        reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
        throw e;
      }

      // Track last update.
      this.updateLastCheck(serverTime);
    } catch (e) {
      // No specific error was tracked, mark it as unknown.
      if (reportStatus === null) {
        reportStatus = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
      }
      throw e;
    } finally {
      if (sqliteHandle) {
        await sqliteHandle.close();
      }
      // No error was reported, this is a success!
      if (reportStatus === null) {
        reportStatus = UptakeTelemetry.STATUS.SUCCESS;
      }
      // Report success/error status to Telemetry.
      UptakeTelemetry.report(this.identifier, reportStatus);
    }
  }

  /**
   * Save last time server was checked in users prefs.
   *
   * @param {Date} serverTime   the current date return by server.
   */
  updateLastCheck(serverTime) {
    const checkedServerTimeInSeconds = Math.round(serverTime / 1000);
    Services.prefs.setIntPref(this.lastCheckTimePref, checkedServerTimeInSeconds);
  }
}

/**
 * Revoke the appropriate certificates based on the records from the blocklist.
 *
 * @param {Object} records   current records in the local db.
 */
async function updateCertBlocklist(records) {
  const certList = Cc["@mozilla.org/security/certblocklist;1"]
                     .getService(Ci.nsICertBlocklist);
  for (let item of records) {
    try {
      if (item.issuerName && item.serialNumber) {
        certList.revokeCertByIssuerAndSerial(item.issuerName,
                                            item.serialNumber);
      } else if (item.subject && item.pubKeyHash) {
        certList.revokeCertBySubjectAndPubKey(item.subject,
                                              item.pubKeyHash);
      }
    } catch (e) {
      // prevent errors relating to individual blocklist entries from
      // causing sync to fail. We will accumulate telemetry on these failures in
      // bug 1254099.
      Cu.reportError(e);
    }
  }
  certList.saveEntries();
}

/**
 * Modify the appropriate security pins based on records from the remote
 * collection.
 *
 * @param {Object} records   current records in the local db.
 */
async function updatePinningList(records) {
  if (!Services.prefs.getBoolPref(PREF_BLOCKLIST_PINNING_ENABLED)) {
    return;
  }
  const appInfo = Cc["@mozilla.org/xre/app-info;1"]
      .getService(Ci.nsIXULAppInfo);

  const siteSecurityService = Cc["@mozilla.org/ssservice;1"]
      .getService(Ci.nsISiteSecurityService);

  // clear the current preload list
  siteSecurityService.clearPreloads();

  // write each KeyPin entry to the preload list
  for (let item of records) {
    try {
      const {pinType, pins = [], versions} = item;
      if (versions.indexOf(appInfo.version) != -1) {
        if (pinType == "KeyPin" && pins.length) {
          siteSecurityService.setKeyPins(item.hostName,
              item.includeSubdomains,
              item.expires,
              pins.length,
              pins, true);
        }
        if (pinType == "STSPin") {
          siteSecurityService.setHSTSPreload(item.hostName,
                                             item.includeSubdomains,
                                             item.expires);
        }
      }
    } catch (e) {
      // prevent errors relating to individual preload entries from causing
      // sync to fail. We will accumulate telemetry for such failures in bug
      // 1254099.
    }
  }
}

/**
 * Write list of records into JSON file, and notify nsBlocklistService.
 *
 * @param {String} filename  path relative to profile dir.
 * @param {Object} records   current records in the local db.
 */
async function updateJSONBlocklist(filename, records) {
  // Write JSON dump for synchronous load at startup.
  const path = OS.Path.join(OS.Constants.Path.profileDir, filename);
  const blocklistFolder = OS.Path.dirname(path);

  await OS.File.makeDir(blocklistFolder, {from: OS.Constants.Path.profileDir});

  const serialized = JSON.stringify({data: records}, null, 2);
  try {
    await OS.File.writeAtomic(path, serialized, {tmpPath: path + ".tmp"});
    // Notify change to `nsBlocklistService`
    const eventData = {filename};
    Services.cpmm.sendAsyncMessage("Blocklist:reload-from-disk", eventData);
  } catch (e) {
    Cu.reportError(e);
  }
}

this.OneCRLBlocklistClient = new BlocklistClient(
  Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION),
  PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS,
  updateCertBlocklist,
  Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
  "onecrl.content-signature.mozilla.org"
);

this.AddonBlocklistClient = new BlocklistClient(
  Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_COLLECTION),
  PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS,
  (records) => updateJSONBlocklist(this.AddonBlocklistClient.filename, records),
  Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);

this.GfxBlocklistClient = new BlocklistClient(
  Services.prefs.getCharPref(PREF_BLOCKLIST_GFX_COLLECTION),
  PREF_BLOCKLIST_GFX_CHECKED_SECONDS,
  (records) => updateJSONBlocklist(this.GfxBlocklistClient.filename, records),
  Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);

this.PluginBlocklistClient = new BlocklistClient(
  Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_COLLECTION),
  PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS,
  (records) => updateJSONBlocklist(this.PluginBlocklistClient.filename, records),
  Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET)
);

this.PinningPreloadClient = new BlocklistClient(
  Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_COLLECTION),
  PREF_BLOCKLIST_PINNING_CHECKED_SECONDS,
  updatePinningList,
  Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_BUCKET),
  "pinning-preload.content-signature.mozilla.org"
);
PK
!<S,modules/services-common/blocklist-updater.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["checkVersions", "addTestBlocklistClient"];

const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.importGlobalProperties(["fetch"]);
XPCOMUtils.defineLazyModuleGetter(this, "UptakeTelemetry",
                                  "resource://services-common/uptake-telemetry.js");

const PREF_SETTINGS_SERVER              = "services.settings.server";
const PREF_SETTINGS_SERVER_BACKOFF      = "services.settings.server.backoff";
const PREF_BLOCKLIST_CHANGES_PATH       = "services.blocklist.changes.path";
const PREF_BLOCKLIST_LAST_UPDATE        = "services.blocklist.last_update_seconds";
const PREF_BLOCKLIST_LAST_ETAG          = "services.blocklist.last_etag";
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";

// Telemetry update source identifier.
const TELEMETRY_HISTOGRAM_KEY = "settings-changes-monitoring";


XPCOMUtils.defineLazyGetter(this, "gBlocklistClients", function() {
  const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
  return {
    [BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
    [BlocklistClients.AddonBlocklistClient.collectionName]: BlocklistClients.AddonBlocklistClient,
    [BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
    [BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient,
    [BlocklistClients.PinningPreloadClient.collectionName]: BlocklistClients.PinningPreloadClient,
  };
});

// Add a blocklist client for testing purposes. Do not use for any other purpose
this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = client; }


async function pollChanges(url, lastEtag) {
  //
  // Fetch a versionInfo object from the server that looks like:
  // {"data":[
  //   {
  //     "host":"kinto-ota.dev.mozaws.net",
  //     "last_modified":1450717104423,
  //     "bucket":"blocklists",
  //     "collection":"certificates"
  //    }]}

  // Use ETag to obtain a `304 Not modified` when no change occurred.
  const headers = {};
  if (lastEtag) {
    headers["If-None-Match"] = lastEtag;
  }
  const response = await fetch(url, {headers});

  let versionInfo = [];
  // If no changes since last time, go on with empty list of changes.
  if (response.status != 304) {
    let payload;
    try {
      payload = await response.json();
    } catch (e) {}
    if (!payload.hasOwnProperty("data")) {
      // If the server is failing, the JSON response might not contain the
      // expected data (e.g. error response - Bug 1259145)
      throw new Error(`Server error response ${JSON.stringify(payload)}`);
    }
    versionInfo = payload.data;
  }
  // The server should always return ETag. But we've had situations where the CDN
  // was interfering.
  const currentEtag = response.headers.has("ETag") ? response.headers.get("ETag") : undefined;
  const serverTimeMillis = Date.parse(response.headers.get("Date"));

  // Check if the server asked the clients to back off.
  let backoffSeconds;
  if (response.headers.has("Backoff")) {
    const value = parseInt(response.headers.get("Backoff"), 10);
    if (!isNaN(value)) {
      backoffSeconds = value;
    }
  }

  return {versionInfo, currentEtag, serverTimeMillis, backoffSeconds};
}


// This is called by the ping mechanism.
// returns a promise that rejects if something goes wrong
this.checkVersions = async function() {
  // Check if the server backoff time is elapsed.
  if (Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
    const backoffReleaseTime = Services.prefs.getCharPref(PREF_SETTINGS_SERVER_BACKOFF);
    const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
    if (remainingMilliseconds > 0) {
      // Backoff time has not elapsed yet.
      UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY,
                             UptakeTelemetry.STATUS.BACKOFF);
      throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
    } else {
      Services.prefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
    }
  }

  // Right now, we only use the collection name and the last modified info
  const kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
  const changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);

  let lastEtag;
  if (Services.prefs.prefHasUserValue(PREF_BLOCKLIST_LAST_ETAG)) {
    lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
  }

  let pollResult;
  try {
    pollResult = await pollChanges(changesEndpoint, lastEtag);
  } catch (e) {
    // Report polling error to Uptake Telemetry.
    let report;
    if (/Server/.test(e.message)) {
      report = UptakeTelemetry.STATUS.SERVER_ERROR;
    } else if (/NetworkError/.test(e.message)) {
      report = UptakeTelemetry.STATUS.NETWORK_ERROR;
    } else {
      report = UptakeTelemetry.STATUS.UNKNOWN_ERROR;
    }
    UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);
    // No need to go further.
    throw new Error(`Polling for changes failed: ${e.message}.`);
  }

  const {serverTimeMillis, versionInfo, currentEtag, backoffSeconds} = pollResult;

  // Report polling success to Uptake Telemetry.
  const report = versionInfo.length == 0 ? UptakeTelemetry.STATUS.UP_TO_DATE
                                         : UptakeTelemetry.STATUS.SUCCESS;
  UptakeTelemetry.report(TELEMETRY_HISTOGRAM_KEY, report);

  // Check if the server asked the clients to back off (for next poll).
  if (backoffSeconds) {
    const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
    Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, backoffReleaseTime);
  }

  // Record new update time and the difference between local and server time.
  // Negative clockDifference means local time is behind server time
  // by the absolute of that value in seconds (positive means it's ahead)
  const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
  Services.prefs.setIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, clockDifference);
  Services.prefs.setIntPref(PREF_BLOCKLIST_LAST_UPDATE, serverTimeMillis / 1000);

  // Iterate through the collections version info and initiate a synchronization
  // on the related blocklist client.
  let firstError;
  for (const collectionInfo of versionInfo) {
    const {bucket, collection, last_modified: lastModified} = collectionInfo;
    const client = gBlocklistClients[collection];
    if (client && client.bucketName == bucket) {
      try {
        await client.maybeSync(lastModified, serverTimeMillis);
      } catch (e) {
        if (!firstError) {
          firstError = e;
        }
      }
    }
  }
  if (firstError) {
    // cause the promise to reject by throwing the first observed error
    throw firstError;
  }

  // Save current Etag for next poll.
  if (currentEtag) {
    Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_ETAG, currentEtag);
  }
};
PK
!<q00%modules/services-common/hawkclient.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/*
 * HAWK is an HTTP authentication scheme using a message authentication code
 * (MAC) algorithm to provide partial HTTP request cryptographic verification.
 *
 * For details, see: https://github.com/hueniverse/hawk
 *
 * With HAWK, it is essential that the clocks on clients and server not have an
 * absolute delta of greater than one minute, as the HAWK protocol uses
 * timestamps to reduce the possibility of replay attacks.  However, it is
 * likely that some clients' clocks will be more than a little off, especially
 * in mobile devices, which would break HAWK-based services (like sync and
 * firefox accounts) for those clients.
 *
 * This library provides a stateful HAWK client that calculates (roughly) the
 * clock delta on the client vs the server.  The library provides an interface
 * for deriving HAWK credentials and making HAWK-authenticated REST requests to
 * a single remote server.  Therefore, callers who want to interact with
 * multiple HAWK services should instantiate one HawkClient per service.
 */

this.EXPORTED_SYMBOLS = ["HawkClient"];

var {interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// log.appender.dump should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Error" will be used by
// default.
// Note however that Sync will also add this log to *its* DumpAppender, so
// in a Sync context it shouldn't be necessary to adjust this - however, that
// also means error logs are likely to be dump'd twice but that's OK.
const PREF_LOG_LEVEL = "services.common.hawk.log.appender.dump";

// A pref that can be set so "sensitive" information (eg, personally
// identifiable info, credentials, etc) will be logged.
const PREF_LOG_SENSITIVE_DETAILS = "services.common.hawk.log.sensitive";

XPCOMUtils.defineLazyGetter(this, "log", function() {
  let log = Log.repository.getLogger("Hawk");
  // We set the log itself to "debug" and set the level from the preference to
  // the appender.  This allows other things to send the logs to different
  // appenders, while still allowing the pref to control what is seen via dump()
  log.level = Log.Level.Debug;
  let appender = new Log.DumpAppender();
  log.addAppender(appender);
  appender.level = Log.Level.Error;
  try {
    let level =
      Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
      && Services.prefs.getCharPref(PREF_LOG_LEVEL);
    appender.level = Log.Level[level] || Log.Level.Error;
  } catch (e) {
    log.error(e);
  }

  return log;
});

// A boolean to indicate if personally identifiable information (or anything
// else sensitive, such as credentials) should be logged.
XPCOMUtils.defineLazyGetter(this, "logPII", function() {
  try {
    return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
  } catch (_) {
    return false;
  }
});

/*
 * A general purpose client for making HAWK authenticated requests to a single
 * host.  Keeps track of the clock offset between the client and the host for
 * computation of the timestamp in the HAWK Authorization header.
 *
 * Clients should create one HawkClient object per each server they wish to
 * interact with.
 *
 * @param host
 *        The url of the host
 */
this.HawkClient = function(host) {
  this.host = host;

  // Clock offset in milliseconds between our client's clock and the date
  // reported in responses from our host.
  this._localtimeOffsetMsec = 0;
}

this.HawkClient.prototype = {

  /*
   * A boolean for feature detection.
   */
  willUTF8EncodeRequests: HAWKAuthenticatedRESTRequest.prototype.willUTF8EncodeObjectRequests,

  /*
   * Construct an error message for a response.  Private.
   *
   * @param restResponse
   *        A RESTResponse object from a RESTRequest
   *
   * @param error
   *        A string or object describing the error
   */
  _constructError(restResponse, error) {
    let errorObj = {
      error,
      // This object is likely to be JSON.stringify'd, but neither Error()
      // objects nor Components.Exception objects do the right thing there,
      // so we add a new element which is simply the .toString() version of
      // the error object, so it does appear in JSON'd values.
      errorString: error.toString(),
      message: restResponse.statusText,
      code: restResponse.status,
      errno: restResponse.status,
      toString() {
        return this.code + ": " + this.message;
      },
    };
    let retryAfter = restResponse.headers && restResponse.headers["retry-after"];
    retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
    if (retryAfter) {
      errorObj.retryAfter = retryAfter;
      // and notify observers of the retry interval
      if (this.observerPrefix) {
        Observers.notify(this.observerPrefix + ":backoff:interval", retryAfter);
      }
    }
    return errorObj;
  },

  /*
   *
   * Update clock offset by determining difference from date gives in the (RFC
   * 1123) Date header of a server response.  Because HAWK tolerates a window
   * of one minute of clock skew (so two minutes total since the skew can be
   * positive or negative), the simple method of calculating offset here is
   * probably good enough.  We keep the value in milliseconds to make life
   * easier, even though the value will not have millisecond accuracy.
   *
   * @param dateString
   *        An RFC 1123 date string (e.g., "Mon, 13 Jan 2014 21:45:06 GMT")
   *
   * For HAWK clock skew and replay protection, see
   * https://github.com/hueniverse/hawk#replay-protection
   */
  _updateClockOffset(dateString) {
    try {
      let serverDateMsec = Date.parse(dateString);
      this._localtimeOffsetMsec = serverDateMsec - this.now();
      log.debug("Clock offset vs " + this.host + ": " + this._localtimeOffsetMsec);
    } catch (err) {
      log.warn("Bad date header in server response: " + dateString);
    }
  },

  /*
   * Get the current clock offset in milliseconds.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this._localtimeOffsetMsec;
  },

  /*
   * return current time in milliseconds
   */
  now() {
    return Date.now();
  },

  /* A general method for sending raw RESTRequest calls authorized using HAWK
   *
   * @param path
   *        API endpoint path
   * @param method
   *        The HTTP request method
   * @param credentials
   *        Hawk credentials
   * @param payloadObj
   *        An object that can be encodable as JSON as the payload of the
   *        request
   * @param extraHeaders
   *        An object with header/value pairs to send with the request.
   * @return Promise
   *        Returns a promise that resolves to the response of the API call,
   *        or is rejected with an error.  If the server response can be parsed
   *        as JSON and contains an 'error' property, the promise will be
   *        rejected with this JSON-parsed response.
   */
  request(path, method, credentials = null, payloadObj = {}, extraHeaders = {},
                    retryOK = true) {
    method = method.toLowerCase();

    let deferred = PromiseUtils.defer();
    let uri = this.host + path;
    let self = this;

    function _onComplete(error) {
      // |error| can be either a normal caught error or an explicitly created
      // Components.Exception() error. Log it now as it might not end up
      // correctly in the logs by the time it's passed through _constructError.
      if (error) {
        log.warn("hawk request error", error);
      }
      // If there's no response there's nothing else to do.
      if (!this.response) {
        deferred.reject(error);
        return;
      }
      let restResponse = this.response;
      let status = restResponse.status;

      log.debug("(Response) " + path + ": code: " + status +
                " - Status text: " + restResponse.statusText);
      if (logPII) {
        log.debug("Response text: " + restResponse.body);
      }

      // All responses may have backoff headers, which are a server-side safety
      // valve to allow slowing down clients without hurting performance.
      self._maybeNotifyBackoff(restResponse, "x-weave-backoff");
      self._maybeNotifyBackoff(restResponse, "x-backoff");

      if (error) {
        // When things really blow up, reconstruct an error object that follows
        // the general format of the server on error responses.
        deferred.reject(self._constructError(restResponse, error));
        return;
      }

      self._updateClockOffset(restResponse.headers["date"]);

      if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
        // Retry once if we were rejected due to a bad timestamp.
        // Clock offset is adjusted already in the top of this function.
        log.debug("Received 401 for " + path + ": retrying");
        deferred.resolve(self.request(path, method, credentials, payloadObj, extraHeaders, false));
        return;
      }

      // If the server returned a json error message, use it in the rejection
      // of the promise.
      //
      // In the case of a 401, in which we are probably being rejected for a
      // bad timestamp, retry exactly once, during which time clock offset will
      // be adjusted.

      let jsonResponse = {};
      try {
        jsonResponse = JSON.parse(restResponse.body);
      } catch (notJSON) {}

      let okResponse = (200 <= status && status < 300);
      if (!okResponse || jsonResponse.error) {
        if (jsonResponse.error) {
          deferred.reject(jsonResponse);
        } else {
          deferred.reject(self._constructError(restResponse, "Request failed"));
        }
        return;
      }
      // It's up to the caller to know how to decode the response.
      // We just return the whole response.
      deferred.resolve(this.response);
    }

    function onComplete(error) {
      try {
        // |this| is the RESTRequest object and we need to ensure _onComplete
        // gets the same one.
        _onComplete.call(this, error);
      } catch (ex) {
        log.error("Unhandled exception processing response", ex);
        deferred.reject(ex);
      }
    }

    let extra = {
      now: this.now(),
      localtimeOffsetMsec: this.localtimeOffsetMsec,
      headers: extraHeaders
    };

    let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
    try {
      if (method == "post" || method == "put" || method == "patch") {
        request[method](payloadObj, onComplete);
      } else {
        request[method](onComplete);
      }
    } catch (ex) {
      log.error("Failed to make hawk request", ex);
      deferred.reject(ex);
    }

    return deferred.promise;
  },

  /*
   * The prefix used for all notifications sent by this module.  This
   * allows the handler of notifications to be sure they are handling
   * notifications for the service they expect.
   *
   * If not set, no notifications will be sent.
   */
  observerPrefix: null,

  // Given an optional header value, notify that a backoff has been requested.
  _maybeNotifyBackoff(response, headerName) {
    if (!this.observerPrefix || !response.headers) {
      return;
    }
    let headerVal = response.headers[headerName];
    if (!headerVal) {
      return;
    }
    let backoffInterval;
    try {
      backoffInterval = parseInt(headerVal, 10);
    } catch (ex) {
      log.error("hawkclient response had invalid backoff value in '" +
                headerName + "' header: " + headerVal);
      return;
    }
    Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
  },

  // override points for testing.
  newHAWKAuthenticatedRESTRequest(uri, credentials, extra) {
    return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
  },

}
PK
!<#QL&modules/services-common/hawkrequest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

this.EXPORTED_SYMBOLS = [
  "HAWKAuthenticatedRESTRequest",
  "deriveHawkCredentials"
];

Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://gre/modules/Credentials.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                  "resource://services-crypto/utils.js");

const Prefs = new Preferences("services.common.rest.");

/**
 * Single-use HAWK-authenticated HTTP requests to RESTish resources.
 *
 * @param uri
 *        (String) URI for the RESTRequest constructor
 *
 * @param credentials
 *        (Object) Optional credentials for computing HAWK authentication
 *        header.
 *
 * @param payloadObj
 *        (Object) Optional object to be converted to JSON payload
 *
 * @param extra
 *        (Object) Optional extra params for HAWK header computation.
 *        Valid properties are:
 *
 *          now:                 <current time in milliseconds>,
 *          localtimeOffsetMsec: <local clock offset vs server>,
 *          headers:             <An object with header/value pairs to be sent
 *                                as headers on the request>
 *
 * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
 * the local clock to make it agree with the server's clock.  For instance, if
 * the local clock is two minutes ahead of the server, the time offset in
 * milliseconds will be -120000.
 */

this.HAWKAuthenticatedRESTRequest =
 function HawkAuthenticatedRESTRequest(uri, credentials, extra = {}) {
  RESTRequest.call(this, uri);

  this.credentials = credentials;
  this.now = extra.now || Date.now();
  this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
  this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
  this.extraHeaders = extra.headers || {};

  // Expose for testing
  this._intl = getIntl();
};
HAWKAuthenticatedRESTRequest.prototype = {
  __proto__: RESTRequest.prototype,

  dispatch: function dispatch(method, data, onComplete, onProgress) {
    let contentType = "text/plain";
    if (method == "POST" || method == "PUT" || method == "PATCH") {
      contentType = "application/json";
    }
    if (this.credentials) {
      let options = {
        now: this.now,
        localtimeOffsetMsec: this.localtimeOffsetMsec,
        credentials: this.credentials,
        payload: data && JSON.stringify(data) || "",
        contentType,
      };
      let header = CryptoUtils.computeHAWK(this.uri, method, options);
      this.setHeader("Authorization", header.field);
      this._log.trace("hawk auth header: " + header.field);
    }

    for (let header in this.extraHeaders) {
      this.setHeader(header, this.extraHeaders[header]);
    }

    this.setHeader("Content-Type", contentType);

    this.setHeader("Accept-Language", this._intl.accept_languages);

    return RESTRequest.prototype.dispatch.call(
      this, method, data, onComplete, onProgress
    );
  }
};


/**
  * Generic function to derive Hawk credentials.
  *
  * Hawk credentials are derived using shared secrets, which depend on the token
  * in use.
  *
  * @param tokenHex
  *        The current session token encoded in hex
  * @param context
  *        A context for the credentials. A protocol version will be prepended
  *        to the context, see Credentials.keyWord for more information.
  * @param size
  *        The size in bytes of the expected derived buffer,
  *        defaults to 3 * 32.
  * @return credentials
  *        Returns an object:
  *        {
  *          algorithm: sha256
  *          id: the Hawk id (from the first 32 bytes derived)
  *          key: the Hawk key (from bytes 32 to 64)
  *          extra: size - 64 extra bytes (if size > 64)
  *        }
  */
this.deriveHawkCredentials = function deriveHawkCredentials(tokenHex,
                                                            context,
                                                            size = 96,
                                                            hexKey = false) {
  let token = CommonUtils.hexToBytes(tokenHex);
  let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);

  let result = {
    algorithm: "sha256",
    key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
    id: CommonUtils.bytesAsHex(out.slice(0, 32))
  };
  if (size > 64) {
    result.extra = out.slice(64);
  }

  return result;
}

// With hawk request, we send the user's accepted-languages with each request.
// To keep the number of times we read this pref at a minimum, maintain the
// preference in a stateful object that notices and updates itself when the
// pref is changed.
this.Intl = function Intl() {
  // We won't actually query the pref until the first time we need it
  this._accepted = "";
  this._everRead = false;
  this._log = Log.repository.getLogger("Services.common.RESTRequest");
  this._log.level = Log.Level[Prefs.get("log.logger.rest.request")];
  this.init();
};

this.Intl.prototype = {
  init() {
    Services.prefs.addObserver("intl.accept_languages", this);
  },

  uninit() {
    Services.prefs.removeObserver("intl.accept_languages", this);
  },

  observe(subject, topic, data) {
    this.readPref();
  },

  readPref() {
    this._everRead = true;
    try {
      this._accepted = Services.prefs.getComplexValue(
        "intl.accept_languages", Ci.nsIPrefLocalizedString).data;
    } catch (err) {
      this._log.error("Error reading intl.accept_languages pref", err);
    }
  },

  get accept_languages() {
    if (!this._everRead) {
      this.readPref();
    }
    return this._accepted;
  },
};

// Singleton getter for Intl, creating an instance only when we first need it.
var intl = null;
function getIntl() {
  if (!intl) {
    intl = new Intl();
  }
  return intl;
}

PK
!<f<,modules/services-common/kinto-http-client.js/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This file is generated from kinto-http.js - do not modify directly.
 */

const global = this;

this.EXPORTED_SYMBOLS = ["KintoHttpClient"];

/*
 * Version 4.3.4 - 1294207
 */

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.KintoHttpClient = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
 *
 * 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";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _base = require("../src/base");

var _base2 = _interopRequireDefault(_base);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const Cu = Components.utils;

Cu.import("resource://gre/modules/Timer.jsm");
Cu.importGlobalProperties(['fetch']);
const { EventEmitter } = Cu.import("resource://gre/modules/EventEmitter.jsm", {});

let KintoHttpClient = class KintoHttpClient extends _base2.default {
  constructor(remote, options = {}) {
    const events = {};
    EventEmitter.decorate(events);
    super(remote, _extends({ events }, options));
  }
};

// This fixes compatibility with CommonJS required by browserify.
// See http://stackoverflow.com/questions/33505992/babel-6-changes-how-it-exports-default/33683495#33683495

exports.default = KintoHttpClient;
if (typeof module === "object") {
  module.exports = KintoHttpClient;
}

},{"../src/base":7}],2:[function(require,module,exports){
var v1 = require('./v1');
var v4 = require('./v4');

var uuid = v4;
uuid.v1 = v1;
uuid.v4 = v4;

module.exports = uuid;

},{"./v1":5,"./v4":6}],3:[function(require,module,exports){
/**
 * Convert array of 16 byte values to UUID string format of the form:
 * XXXXXXXX-XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 */
var byteToHex = [];
for (var i = 0; i < 256; ++i) {
  byteToHex[i] = (i + 0x100).toString(16).substr(1);
}

function bytesToUuid(buf, offset) {
  var i = offset || 0;
  var bth = byteToHex;
  return  bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]];
}

module.exports = bytesToUuid;

},{}],4:[function(require,module,exports){
// Unique ID creation requires a high quality random # generator.  In the
// browser this is a little complicated due to unknown quality of Math.random()
// and inconsistent support for the `crypto` API.  We do the best we can via
// feature-detection
var rng;

var crypto = global.crypto || global.msCrypto; // for IE 11
if (crypto && crypto.getRandomValues) {
  // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
  var rnds8 = new Uint8Array(16);
  rng = function whatwgRNG() {
    crypto.getRandomValues(rnds8);
    return rnds8;
  };
}

if (!rng) {
  // Math.random()-based (RNG)
  //
  // If all else fails, use Math.random().  It's fast, but is of unspecified
  // quality.
  var  rnds = new Array(16);
  rng = function() {
    for (var i = 0, r; i < 16; i++) {
      if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
      rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
    }

    return rnds;
  };
}

module.exports = rng;

},{}],5:[function(require,module,exports){
// Unique ID creation requires a high quality random # generator.  We feature
// detect to determine the best RNG source, normalizing to a function that
// returns 128-bits of randomness, since that's what's usually required
var rng = require('./lib/rng');
var bytesToUuid = require('./lib/bytesToUuid');

// **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html

// random #'s we need to init node and clockseq
var _seedBytes = rng();

// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
var _nodeId = [
  _seedBytes[0] | 0x01,
  _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
];

// Per 4.2.2, randomize (14 bit) clockseq
var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;

// Previous uuid creation time
var _lastMSecs = 0, _lastNSecs = 0;

// See https://github.com/broofa/node-uuid for API details
function v1(options, buf, offset) {
  var i = buf && offset || 0;
  var b = buf || [];

  options = options || {};

  var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;

  // UUID timestamps are 100 nano-second units since the Gregorian epoch,
  // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
  // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
  // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
  var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();

  // Per 4.2.1.2, use count of uuid's generated during the current clock
  // cycle to simulate higher resolution clock
  var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;

  // Time since last uuid creation (in msecs)
  var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;

  // Per 4.2.1.2, Bump clockseq on clock regression
  if (dt < 0 && options.clockseq === undefined) {
    clockseq = clockseq + 1 & 0x3fff;
  }

  // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
  // time interval
  if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
    nsecs = 0;
  }

  // Per 4.2.1.2 Throw error if too many uuids are requested
  if (nsecs >= 10000) {
    throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
  }

  _lastMSecs = msecs;
  _lastNSecs = nsecs;
  _clockseq = clockseq;

  // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
  msecs += 12219292800000;

  // `time_low`
  var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
  b[i++] = tl >>> 24 & 0xff;
  b[i++] = tl >>> 16 & 0xff;
  b[i++] = tl >>> 8 & 0xff;
  b[i++] = tl & 0xff;

  // `time_mid`
  var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
  b[i++] = tmh >>> 8 & 0xff;
  b[i++] = tmh & 0xff;

  // `time_high_and_version`
  b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
  b[i++] = tmh >>> 16 & 0xff;

  // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
  b[i++] = clockseq >>> 8 | 0x80;

  // `clock_seq_low`
  b[i++] = clockseq & 0xff;

  // `node`
  var node = options.node || _nodeId;
  for (var n = 0; n < 6; ++n) {
    b[i + n] = node[n];
  }

  return buf ? buf : bytesToUuid(b);
}

module.exports = v1;

},{"./lib/bytesToUuid":3,"./lib/rng":4}],6:[function(require,module,exports){
var rng = require('./lib/rng');
var bytesToUuid = require('./lib/bytesToUuid');

function v4(options, buf, offset) {
  var i = buf && offset || 0;

  if (typeof(options) == 'string') {
    buf = options == 'binary' ? new Array(16) : null;
    options = null;
  }
  options = options || {};

  var rnds = options.random || (options.rng || rng)();

  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
  rnds[6] = (rnds[6] & 0x0f) | 0x40;
  rnds[8] = (rnds[8] & 0x3f) | 0x80;

  // Copy bytes to buffer, if provided
  if (buf) {
    for (var ii = 0; ii < 16; ++ii) {
      buf[i + ii] = rnds[ii];
    }
  }

  return buf || bytesToUuid(rnds);
}

module.exports = v4;

},{"./lib/bytesToUuid":3,"./lib/rng":4}],7:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.SUPPORTED_PROTOCOL_VERSION = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _desc, _value, _class;

var _utils = require("./utils");

var _http = require("./http");

var _http2 = _interopRequireDefault(_http);

var _endpoint = require("./endpoint");

var _endpoint2 = _interopRequireDefault(_endpoint);

var _requests = require("./requests");

var requests = _interopRequireWildcard(_requests);

var _batch = require("./batch");

var _bucket = require("./bucket");

var _bucket2 = _interopRequireDefault(_bucket);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
  var desc = {};
  Object['ke' + 'ys'](descriptor).forEach(function (key) {
    desc[key] = descriptor[key];
  });
  desc.enumerable = !!desc.enumerable;
  desc.configurable = !!desc.configurable;

  if ('value' in desc || desc.initializer) {
    desc.writable = true;
  }

  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
    return decorator(target, property, desc) || desc;
  }, desc);

  if (context && desc.initializer !== void 0) {
    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
    desc.initializer = undefined;
  }

  if (desc.initializer === void 0) {
    Object['define' + 'Property'](target, property, desc);
    desc = null;
  }

  return desc;
}

/**
 * Currently supported protocol version.
 * @type {String}
 */
const SUPPORTED_PROTOCOL_VERSION = exports.SUPPORTED_PROTOCOL_VERSION = "v1";

/**
 * High level HTTP client for the Kinto API.
 *
 * @example
 * const client = new KintoClient("https://kinto.dev.mozaws.net/v1");
 * client.bucket("default")
*    .collection("my-blog")
*    .createRecord({title: "First article"})
 *   .then(console.log.bind(console))
 *   .catch(console.error.bind(console));
 */
let KintoClientBase = (_dec = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec2 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec3 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec4 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec5 = (0, _utils.nobatch)("Can't use batch within a batch!"), _dec6 = (0, _utils.capable)(["permissions_endpoint"]), _dec7 = (0, _utils.support)("1.4", "2.0"), (_class = class KintoClientBase {
  /**
   * Constructor.
   *
   * @param  {String}       remote  The remote URL.
   * @param  {Object}       [options={}]                  The options object.
   * @param  {Boolean}      [options.safe=true]           Adds concurrency headers to every requests.
   * @param  {EventEmitter} [options.events=EventEmitter] The events handler instance.
   * @param  {Object}       [options.headers={}]          The key-value headers to pass to each request.
   * @param  {Object}       [options.retry=0]             Number of retries when request fails (default: 0)
   * @param  {String}       [options.bucket="default"]    The default bucket to use.
   * @param  {String}       [options.requestMode="cors"]  The HTTP request mode (from ES6 fetch spec).
   * @param  {Number}       [options.timeout=null]        The request timeout in ms, if any.
   */
  constructor(remote, options = {}) {
    if (typeof remote !== "string" || !remote.length) {
      throw new Error("Invalid remote URL: " + remote);
    }
    if (remote[remote.length - 1] === "/") {
      remote = remote.slice(0, -1);
    }
    this._backoffReleaseTime = null;

    this._requests = [];
    this._isBatch = !!options.batch;
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
    this._headers = options.headers || {};

    // public properties
    /**
     * The remote server base URL.
     * @type {String}
     */
    this.remote = remote;
    /**
     * Current server information.
     * @ignore
     * @type {Object|null}
     */
    this.serverInfo = null;
    /**
     * The event emitter instance. Should comply with the `EventEmitter`
     * interface.
     * @ignore
     * @type {Class}
     */
    this.events = options.events;

    const { requestMode, timeout } = options;
    /**
     * The HTTP instance.
     * @ignore
     * @type {HTTP}
     */
    this.http = new _http2.default(this.events, { requestMode, timeout });
    this._registerHTTPEvents();
  }

  /**
   * The remote endpoint base URL. Setting the value will also extract and
   * validate the version.
   * @type {String}
   */
  get remote() {
    return this._remote;
  }

  /**
   * @ignore
   */
  set remote(url) {
    let version;
    try {
      version = url.match(/\/(v\d+)\/?$/)[1];
    } catch (err) {
      throw new Error("The remote URL must contain the version: " + url);
    }
    if (version !== SUPPORTED_PROTOCOL_VERSION) {
      throw new Error(`Unsupported protocol version: ${version}`);
    }
    this._remote = url;
    this._version = version;
  }

  /**
   * The current server protocol version, eg. `v1`.
   * @type {String}
   */
  get version() {
    return this._version;
  }

  /**
   * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is
   * ongoing.
   *
   * @type {Number}
   */
  get backoff() {
    const currentTime = new Date().getTime();
    if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) {
      return this._backoffReleaseTime - currentTime;
    }
    return 0;
  }

  /**
   * Registers HTTP events.
   * @private
   */
  _registerHTTPEvents() {
    // Prevent registering event from a batch client instance
    if (!this._isBatch) {
      this.events.on("backoff", backoffMs => {
        this._backoffReleaseTime = backoffMs;
      });
    }
  }

  /**
   * Retrieve a bucket object to perform operations on it.
   *
   * @param  {String}  name              The bucket name.
   * @param  {Object}  [options={}]      The request options.
   * @param  {Boolean} [options.safe]    The resulting safe option.
   * @param  {Number}  [options.retry]   The resulting retry option.
   * @param  {Object}  [options.headers] The extended headers object option.
   * @return {Bucket}
   */
  bucket(name, options = {}) {
    return new _bucket2.default(this, name, {
      batch: this._isBatch,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * Note that unlike other options, headers aren't overridden, but
   * merged instead.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Object}
   */
  _getHeaders(options) {
    return _extends({}, this._headers, options.headers);
  }

  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return _extends({ safe: this._safe }, options).safe;
  }

  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return _extends({ retry: this._retry }, options).retry;
  }

  /**
   * Retrieves the server's "hello" endpoint. This endpoint reveals
   * server capabilities and settings as well as telling the client
   * "who they are" according to their given authorization headers.
   *
   * @private
   * @param  {Object}  [options={}] The request options.
   * @param  {Object}  [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async _getHello(options = {}) {
    const path = this.remote + (0, _endpoint2.default)("root");
    const { json } = await this.http.request(path, { headers: this._getHeaders(options) }, { retry: this._getRetry(options) });
    return json;
  }

  /**
   * Retrieves server information and persist them locally. This operation is
   * usually performed a single time during the instance lifecycle.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchServerInfo(options = {}) {
    if (this.serverInfo) {
      return this.serverInfo;
    }
    this.serverInfo = await this._getHello({ retry: this._getRetry(options) });
    return this.serverInfo;
  }

  /**
   * Retrieves Kinto server settings.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */

  async fetchServerSettings(options) {
    const { settings } = await this.fetchServerInfo(options);
    return settings;
  }

  /**
   * Retrieve server capabilities information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */

  async fetchServerCapabilities(options = {}) {
    const { capabilities } = await this.fetchServerInfo(options);
    return capabilities;
  }

  /**
   * Retrieve authenticated user information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Object}  [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */

  async fetchUser(options = {}) {
    const { user } = await this._getHello(options);
    return user;
  }

  /**
   * Retrieve authenticated user information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */

  async fetchHTTPApiVersion(options = {}) {
    const { http_api_version } = await this.fetchServerInfo(options);
    return http_api_version;
  }

  /**
   * Process batch requests, chunking them according to the batch_max_requests
   * server setting when needed.
   *
   * @param  {Array}  requests     The list of batch subrequests to perform.
   * @param  {Object} [options={}] The options object.
   * @return {Promise<Object, Error>}
   */
  async _batchRequests(requests, options = {}) {
    const headers = this._getHeaders(options);
    if (!requests.length) {
      return [];
    }
    const serverSettings = await this.fetchServerSettings({
      retry: this._getRetry(options)
    });
    const maxRequests = serverSettings["batch_max_requests"];
    if (maxRequests && requests.length > maxRequests) {
      const chunks = (0, _utils.partition)(requests, maxRequests);
      return (0, _utils.pMap)(chunks, chunk => this._batchRequests(chunk, options));
    }
    const { responses } = await this.execute({
      // FIXME: is this really necessary, since it's also present in
      // the "defaults"?
      headers,
      path: (0, _endpoint2.default)("batch"),
      method: "POST",
      body: {
        defaults: { headers },
        requests
      }
    }, { retry: this._getRetry(options) });
    return responses;
  }

  /**
   * Sends batch requests to the remote server.
   *
   * Note: Reserved for internal use only.
   *
   * @ignore
   * @param  {Function} fn                        The function to use for describing batch ops.
   * @param  {Object}   [options={}]              The options object.
   * @param  {Boolean}  [options.safe]            The safe option.
   * @param  {Number}   [options.retry]           The retry option.
   * @param  {String}   [options.bucket]          The bucket name option.
   * @param  {String}   [options.collection]      The collection name option.
   * @param  {Object}   [options.headers]         The headers object option.
   * @param  {Boolean}  [options.aggregate=false] Produces an aggregated result object.
   * @return {Promise<Object, Error>}
   */

  async batch(fn, options = {}) {
    const rootBatch = new KintoClientBase(this.remote, {
      events: this.events,
      batch: true,
      safe: this._getSafe(options),
      retry: this._getRetry(options)
    });
    let bucketBatch, collBatch;
    if (options.bucket) {
      bucketBatch = rootBatch.bucket(options.bucket);
      if (options.collection) {
        collBatch = bucketBatch.collection(options.collection);
      }
    }
    const batchClient = collBatch || bucketBatch || rootBatch;
    fn(batchClient);
    const responses = await this._batchRequests(rootBatch._requests, options);
    if (options.aggregate) {
      return (0, _batch.aggregate)(responses, rootBatch._requests);
    } else {
      return responses;
    }
  }

  /**
   * Executes an atomic HTTP request.
   *
   * @private
   * @param  {Object}  request             The request object.
   * @param  {String}  request.path        The path to fetch, relative
   *     to the Kinto server root.
   * @param  {String}  [request.method="GET"] The method to use in the
   *     request.
   * @param  {Body}    [request.body]      The request body.
   * @param  {Object}  [request.headers={}] The request headers.
   * @param  {Object}  [options={}]        The options object.
   * @param  {Boolean} [options.raw=false] If true, resolve with full response
   * @param  {Boolean} [options.stringify=true] If true, serialize body data to
   * @param  {Number}  [options.retry=0]   The number of times to
   *     retry a request if the server responds with Retry-After.
   * JSON.
   * @return {Promise<Object, Error>}
   */
  async execute(request, options = {}) {
    const { raw = false, stringify = true } = options;
    // If we're within a batch, add the request to the stack to send at once.
    if (this._isBatch) {
      this._requests.push(request);
      // Resolve with a message in case people attempt at consuming the result
      // from within a batch operation.
      const msg = "This result is generated from within a batch " + "operation and should not be consumed.";
      return raw ? { json: msg, headers: { get() {} } } : msg;
    }
    const result = await this.http.request(this.remote + request.path, (0, _utils.cleanUndefinedProperties)({
      // Limit requests to only those parts that would be allowed in
      // a batch request -- don't pass through other fancy fetch()
      // options like integrity, redirect, mode because they will
      // break on a batch request.  A batch request only allows
      // headers, method, path (above), and body.
      method: request.method,
      headers: request.headers,
      body: stringify ? JSON.stringify(request.body) : request.body
    }), { retry: this._getRetry(options) });
    return raw ? result : result.json;
  }

  /**
   * Fetch some pages from a paginated list, following the `next-page`
   * header automatically until we have fetched the requested number
   * of pages. Return a response with a `.next()` method that can be
   * called to fetch more results.
   *
   * @private
   * @param  {String}  path
   *     The path to make the request to.
   * @param  {Object}  params
   *     The parameters to use when making the request.
   * @param  {String}  [params.sort="-last_modified"]
   *     The sorting order to use when fetching.
   * @param  {Object}  [params.filters={}]
   *     The filters to send in the request.
   * @param  {Number}  [params.limit=undefined]
   *     The limit to send in the request. Undefined means no limit.
   * @param  {Number}  [params.pages=undefined]
   *     The number of pages to fetch. Undefined means one page. Pass
   *     Infinity to fetch everything.
   * @param  {String}  [params.since=undefined]
   *     The ETag from which to start fetching.
   * @param  {Object}  [options={}]
   *     Additional request-level parameters to use in all requests.
   * @param  {Object}  [options.headers={}]
   *     Headers to use during all requests.
   * @param  {Number}  [options.retry=0]
   *     Number of times to retry each request if the server responds
   *     with Retry-After.
   */
  async paginatedList(path, params, options = {}) {
    // FIXME: this is called even in batch requests, which doesn't
    // make any sense (since all batch requests get a "dummy"
    // response; see execute() above).
    const { sort, filters, limit, pages, since } = _extends({
      sort: "-last_modified"
    }, params);
    // Safety/Consistency check on ETag value.
    if (since && typeof since !== "string") {
      throw new Error(`Invalid value for since (${since}), should be ETag value.`);
    }

    const querystring = (0, _utils.qsify)(_extends({}, filters, {
      _sort: sort,
      _limit: limit,
      _since: since
    }));
    let results = [],
        current = 0;

    const next = async function (nextPage) {
      if (!nextPage) {
        throw new Error("Pagination exhausted.");
      }
      return processNextPage(nextPage);
    };

    const processNextPage = async nextPage => {
      const { headers } = options;
      return handleResponse((await this.http.request(nextPage, { headers })));
    };

    const pageResults = (results, nextPage, etag, totalRecords) => {
      // ETag string is supposed to be opaque and stored «as-is».
      // ETag header values are quoted (because of * and W/"foo").
      return {
        last_modified: etag ? etag.replace(/"/g, "") : etag,
        data: results,
        next: next.bind(null, nextPage),
        hasNextPage: !!nextPage,
        totalRecords
      };
    };

    const handleResponse = async function ({ headers, json }) {
      const nextPage = headers.get("Next-Page");
      const etag = headers.get("ETag");
      const totalRecords = parseInt(headers.get("Total-Records"), 10);

      if (!pages) {
        return pageResults(json.data, nextPage, etag, totalRecords);
      }
      // Aggregate new results with previous ones
      results = results.concat(json.data);
      current += 1;
      if (current >= pages || !nextPage) {
        // Pagination exhausted
        return pageResults(results, nextPage, etag, totalRecords);
      }
      // Follow next page
      return processNextPage(nextPage);
    };

    return handleResponse((await this.execute(
    // N.B.: This doesn't use _getHeaders, because all calls to
    // `paginatedList` are assumed to come from calls that already
    // have headers merged at e.g. the bucket or collection level.
    { headers: options.headers, path: path + "?" + querystring },
    // N.B. This doesn't use _getRetry, because all calls to
    // `paginatedList` are assumed to come from calls that already
    // used `_getRetry` at e.g. the bucket or collection level.
    { raw: true, retry: options.retry || 0 })));
  }

  /**
   * Lists all permissions.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number} [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object[], Error>}
   */

  async listPermissions(options = {}) {
    const path = (0, _endpoint2.default)("permissions");
    // Ensure the default sort parameter is something that exists in permissions
    // entries, as `last_modified` doesn't; here, we pick "id".
    const paginationOptions = _extends({ sort: "id" }, options);
    return this.paginatedList(path, paginationOptions, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Retrieves the list of buckets.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number} [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object[], Error>}
   */
  async listBuckets(options = {}) {
    const path = (0, _endpoint2.default)("bucket");
    return this.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Creates a new bucket on the server.
   *
   * @param  {String|null}  id                The bucket name (optional).
   * @param  {Object}       [options={}]      The options object.
   * @param  {Boolean}      [options.data]    The bucket data option.
   * @param  {Boolean}      [options.safe]    The safe option.
   * @param  {Object}       [options.headers] The headers object option.
   * @param  {Number}       [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async createBucket(id, options = {}) {
    const { data = {}, permissions } = options;
    if (id != null) {
      data.id = id;
    }
    const path = data.id ? (0, _endpoint2.default)("bucket", data.id) : (0, _endpoint2.default)("bucket");
    return this.execute(requests.createRequest(path, { data, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    }), { retry: this._getRetry(options) });
  }

  /**
   * Deletes a bucket from the server.
   *
   * @ignore
   * @param  {Object|String} bucket                  The bucket to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteBucket(bucket, options = {}) {
    const bucketObj = (0, _utils.toDataBody)(bucket);
    if (!bucketObj.id) {
      throw new Error("A bucket id is required.");
    }
    const path = (0, _endpoint2.default)("bucket", bucketObj.id);
    const { last_modified } = _extends({}, bucketObj, options);
    return this.execute(requests.deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    }), { retry: this._getRetry(options) });
  }

  /**
   * Deletes all buckets on the server.
   *
   * @ignore
   * @param  {Object}  [options={}]            The options object.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */

  async deleteBuckets(options = {}) {
    const path = (0, _endpoint2.default)("bucket");
    return this.execute(requests.deleteRequest(path, {
      last_modified: options.last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    }), { retry: this._getRetry(options) });
  }
}, (_applyDecoratedDescriptor(_class.prototype, "fetchServerSettings", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "fetchServerSettings"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchServerCapabilities", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "fetchServerCapabilities"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchUser", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "fetchUser"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchHTTPApiVersion", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, "fetchHTTPApiVersion"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "batch", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, "batch"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "listPermissions", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, "listPermissions"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "deleteBuckets", [_dec7], Object.getOwnPropertyDescriptor(_class.prototype, "deleteBuckets"), _class.prototype)), _class));
exports.default = KintoClientBase;

},{"./batch":8,"./bucket":9,"./endpoint":11,"./http":13,"./requests":14,"./utils":15}],8:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.aggregate = aggregate;
/**
 * Exports batch responses as a result object.
 *
 * @private
 * @param  {Array} responses The batch subrequest responses.
 * @param  {Array} requests  The initial issued requests.
 * @return {Object}
 */
function aggregate(responses = [], requests = []) {
  if (responses.length !== requests.length) {
    throw new Error("Responses length should match requests one.");
  }
  const results = {
    errors: [],
    published: [],
    conflicts: [],
    skipped: []
  };
  return responses.reduce((acc, response, index) => {
    const { status } = response;
    const request = requests[index];
    if (status >= 200 && status < 400) {
      acc.published.push(response.body);
    } else if (status === 404) {
      // Extract the id manually from request path while waiting for Kinto/kinto#818
      const regex = /(buckets|groups|collections|records)\/([^\/]+)$/;
      const extracts = request.path.match(regex);
      const id = extracts.length === 3 ? extracts[2] : undefined;
      acc.skipped.push({
        id,
        path: request.path,
        error: response.body
      });
    } else if (status === 412) {
      acc.conflicts.push({
        // XXX: specifying the type is probably superfluous
        type: "outgoing",
        local: request.body,
        remote: response.body.details && response.body.details.existing || null
      });
    } else {
      acc.errors.push({
        path: request.path,
        sent: request,
        error: response.body
      });
    }
    return acc;
  }, results);
}

},{}],9:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _dec, _desc, _value, _class;

var _utils = require("./utils");

var _collection = require("./collection");

var _collection2 = _interopRequireDefault(_collection);

var _requests = require("./requests");

var requests = _interopRequireWildcard(_requests);

var _endpoint = require("./endpoint");

var _endpoint2 = _interopRequireDefault(_endpoint);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
  var desc = {};
  Object['ke' + 'ys'](descriptor).forEach(function (key) {
    desc[key] = descriptor[key];
  });
  desc.enumerable = !!desc.enumerable;
  desc.configurable = !!desc.configurable;

  if ('value' in desc || desc.initializer) {
    desc.writable = true;
  }

  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
    return decorator(target, property, desc) || desc;
  }, desc);

  if (context && desc.initializer !== void 0) {
    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
    desc.initializer = undefined;
  }

  if (desc.initializer === void 0) {
    Object['define' + 'Property'](target, property, desc);
    desc = null;
  }

  return desc;
}

/**
 * Abstract representation of a selected bucket.
 *
 */
let Bucket = (_dec = (0, _utils.capable)(["history"]), (_class = class Bucket {
  /**
   * Constructor.
   *
   * @param  {KintoClient} client            The client instance.
   * @param  {String}      name              The bucket name.
   * @param  {Object}      [options={}]      The headers object option.
   * @param  {Object}      [options.headers] The headers object option.
   * @param  {Boolean}     [options.safe]    The safe option.
   * @param  {Number}      [options.retry]   The retry option.
   */
  constructor(client, name, options = {}) {
    /**
     * @ignore
     */
    this.client = client;
    /**
     * The bucket name.
     * @type {String}
     */
    this.name = name;
    /**
     * @ignore
     */
    this._isBatch = !!options.batch;
    /**
     * @ignore
     */
    this._headers = options.headers || {};
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
  }

  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * @private
   */
  _getHeaders(options) {
    return _extends({}, this._headers, options.headers);
  }

  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return _extends({ safe: this._safe }, options).safe;
  }

  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return _extends({ retry: this._retry }, options).retry;
  }

  /**
   * Selects a collection.
   *
   * @param  {String}  name              The collection name.
   * @param  {Object}  [options={}]      The options object.
   * @param  {Object}  [options.headers] The headers object option.
   * @param  {Boolean} [options.safe]    The safe option.
   * @return {Collection}
   */
  collection(name, options = {}) {
    return new _collection2.default(this.client, this, name, {
      batch: this._isBatch,
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options)
    });
  }

  /**
   * Retrieves bucket data.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getData(options = {}) {
    const request = {
      headers: this._getHeaders(options),
      path: (0, _endpoint2.default)("bucket", this.name)
    };
    const { data } = await this.client.execute(request, {
      retry: this._getRetry(options)
    });
    return data;
  }

  /**
   * Set bucket data.
   * @param  {Object}  data                    The bucket data object.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers={}]    The headers object option.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.patch]         The patch option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setData(data, options = {}) {
    if (!(0, _utils.isObject)(data)) {
      throw new Error("A bucket object is required.");
    }

    const bucket = _extends({}, data, { id: this.name });

    // For default bucket, we need to drop the id from the data object.
    // Bug in Kinto < 3.1.1
    const bucketId = bucket.id;
    if (bucket.id === "default") {
      delete bucket.id;
    }

    const path = (0, _endpoint2.default)("bucket", bucketId);
    const { patch, permissions } = options;
    const { last_modified } = _extends({}, data, options);
    const request = requests.updateRequest(path, { data: bucket, permissions }, {
      last_modified,
      patch,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Retrieves the list of history entries in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Array<Object>, Error>}
   */

  async listHistory(options = {}) {
    const path = (0, _endpoint2.default)("history", this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Retrieves the list of collections in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Array<Object>, Error>}
   */
  async listCollections(options = {}) {
    const path = (0, _endpoint2.default)("collection", this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Creates a new collection in current bucket.
   *
   * @param  {String|undefined}  id          The collection id.
   * @param  {Object}  [options={}]          The options object.
   * @param  {Boolean} [options.safe]        The safe option.
   * @param  {Object}  [options.headers]     The headers object option.
   * @param  {Number}  [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.permissions] The permissions object.
   * @param  {Object}  [options.data]        The data object.
   * @return {Promise<Object, Error>}
   */
  async createCollection(id, options = {}) {
    const { permissions, data = {} } = options;
    data.id = id;
    const path = (0, _endpoint2.default)("collection", this.name, id);
    const request = requests.createRequest(path, { data, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Deletes a collection from the current bucket.
   *
   * @param  {Object|String} collection              The collection to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteCollection(collection, options = {}) {
    const collectionObj = (0, _utils.toDataBody)(collection);
    if (!collectionObj.id) {
      throw new Error("A collection id is required.");
    }
    const { id } = collectionObj;
    const { last_modified } = _extends({}, collectionObj, options);
    const path = (0, _endpoint2.default)("collection", this.name, id);
    const request = requests.deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Retrieves the list of groups in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Array<Object>, Error>}
   */
  async listGroups(options = {}) {
    const path = (0, _endpoint2.default)("group", this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options)
    });
  }

  /**
   * Creates a new group in current bucket.
   *
   * @param  {String} id                The group id.
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getGroup(id, options = {}) {
    const request = {
      headers: this._getHeaders(options),
      path: (0, _endpoint2.default)("group", this.name, id)
    };
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Creates a new group in current bucket.
   *
   * @param  {String|undefined}  id                    The group id.
   * @param  {Array<String>}     [members=[]]          The list of principals.
   * @param  {Object}            [options={}]          The options object.
   * @param  {Object}            [options.data]        The data object.
   * @param  {Object}            [options.permissions] The permissions object.
   * @param  {Boolean}           [options.safe]        The safe option.
   * @param  {Object}            [options.headers]     The headers object option.
   * @param  {Number}            [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async createGroup(id, members = [], options = {}) {
    const data = _extends({}, options.data, {
      id,
      members
    });
    const path = (0, _endpoint2.default)("group", this.name, id);
    const { permissions } = options;
    const request = requests.createRequest(path, { data, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Updates an existing group in current bucket.
   *
   * @param  {Object}  group                   The group object.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.data]          The data object.
   * @param  {Object}  [options.permissions]   The permissions object.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async updateGroup(group, options = {}) {
    if (!(0, _utils.isObject)(group)) {
      throw new Error("A group object is required.");
    }
    if (!group.id) {
      throw new Error("A group id is required.");
    }
    const data = _extends({}, options.data, group);
    const path = (0, _endpoint2.default)("group", this.name, group.id);
    const { patch, permissions } = options;
    const { last_modified } = _extends({}, data, options);
    const request = requests.updateRequest(path, { data, permissions }, {
      last_modified,
      patch,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Deletes a group from the current bucket.
   *
   * @param  {Object|String} group                   The group to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteGroup(group, options = {}) {
    const groupObj = (0, _utils.toDataBody)(group);
    const { id } = groupObj;
    const { last_modified } = _extends({}, groupObj, options);
    const path = (0, _endpoint2.default)("group", this.name, id);
    const request = requests.deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Retrieves the list of permissions for this bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getPermissions(options = {}) {
    const request = {
      headers: this._getHeaders(options),
      path: (0, _endpoint2.default)("bucket", this.name)
    };
    const { permissions } = await this.client.execute(request, {
      retry: this._getRetry(options)
    });
    return permissions;
  }

  /**
   * Replaces all existing bucket permissions with the ones provided.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers={}]    The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setPermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("bucket", this.name);
    const { last_modified } = options;
    const data = { last_modified };
    const request = requests.updateRequest(path, { data, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Append principals to the bucket permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async addPermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("bucket", this.name);
    const { last_modified } = options;
    const request = requests.jsonPatchPermissionsRequest(path, permissions, "add", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Remove principals from the bucket permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async removePermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("bucket", this.name);
    const { last_modified } = options;
    const request = requests.jsonPatchPermissionsRequest(path, permissions, "remove", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Performs batch operations at the current bucket level.
   *
   * @param  {Function} fn                   The batch operation function.
   * @param  {Object}   [options={}]         The options object.
   * @param  {Object}   [options.headers]    The headers object option.
   * @param  {Boolean}  [options.safe]       The safe option.
   * @param  {Number}   [options.retry=0]    The retry option.
   * @param  {Boolean}  [options.aggregate]  Produces a grouped result object.
   * @return {Promise<Object, Error>}
   */
  async batch(fn, options = {}) {
    return this.client.batch(fn, {
      bucket: this.name,
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options),
      aggregate: !!options.aggregate
    });
  }
}, (_applyDecoratedDescriptor(_class.prototype, "listHistory", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "listHistory"), _class.prototype)), _class));
exports.default = Bucket;

},{"./collection":10,"./endpoint":11,"./requests":14,"./utils":15}],10:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _dec, _dec2, _dec3, _desc, _value, _class;

var _uuid = require("uuid");

var _utils = require("./utils");

var _requests = require("./requests");

var requests = _interopRequireWildcard(_requests);

var _endpoint = require("./endpoint");

var _endpoint2 = _interopRequireDefault(_endpoint);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
  var desc = {};
  Object['ke' + 'ys'](descriptor).forEach(function (key) {
    desc[key] = descriptor[key];
  });
  desc.enumerable = !!desc.enumerable;
  desc.configurable = !!desc.configurable;

  if ('value' in desc || desc.initializer) {
    desc.writable = true;
  }

  desc = decorators.slice().reverse().reduce(function (desc, decorator) {
    return decorator(target, property, desc) || desc;
  }, desc);

  if (context && desc.initializer !== void 0) {
    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
    desc.initializer = undefined;
  }

  if (desc.initializer === void 0) {
    Object['define' + 'Property'](target, property, desc);
    desc = null;
  }

  return desc;
}

/**
 * Abstract representation of a selected collection.
 *
 */
let Collection = (_dec = (0, _utils.capable)(["attachments"]), _dec2 = (0, _utils.capable)(["attachments"]), _dec3 = (0, _utils.capable)(["history"]), (_class = class Collection {
  /**
   * Constructor.
   *
   * @param  {KintoClient}  client            The client instance.
   * @param  {Bucket}       bucket            The bucket instance.
   * @param  {String}       name              The collection name.
   * @param  {Object}       [options={}]      The options object.
   * @param  {Object}       [options.headers] The headers object option.
   * @param  {Boolean}      [options.safe]    The safe option.
   * @param  {Number}       [options.retry]   The retry option.
   * @param  {Boolean}      [options.batch]   (Private) Whether this
   *     Collection is operating as part of a batch.
   */
  constructor(client, bucket, name, options = {}) {
    /**
     * @ignore
     */
    this.client = client;
    /**
     * @ignore
     */
    this.bucket = bucket;
    /**
     * The collection name.
     * @type {String}
     */
    this.name = name;

    /**
     * @ignore
     */
    this._isBatch = !!options.batch;

    /**
     * @ignore
     */
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
    // FIXME: This is kind of ugly; shouldn't the bucket be responsible
    // for doing the merge?
    this._headers = _extends({}, this.bucket._headers, options.headers);
  }

  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * @private
   */
  _getHeaders(options) {
    return _extends({}, this._headers, options.headers);
  }

  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return _extends({ safe: this._safe }, options).safe;
  }

  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return _extends({ retry: this._retry }, options).retry;
  }

  /**
   * Retrieves the total number of records in this collection.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Number, Error>}
   */
  async getTotalRecords(options = {}) {
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
      method: "HEAD"
    };
    const { headers } = await this.client.execute(request, {
      raw: true,
      retry: this._getRetry(options)
    });
    return parseInt(headers.get("Total-Records"), 10);
  }

  /**
   * Retrieves collection data.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getData(options = {}) {
    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const request = { headers: this._getHeaders(options), path };
    const { data } = await this.client.execute(request, {
      retry: this._getRetry(options)
    });
    return data;
  }

  /**
   * Set collection data.
   * @param  {Object}   data                    The collection data object.
   * @param  {Object}   [options={}]            The options object.
   * @param  {Object}   [options.headers]       The headers object option.
   * @param  {Number}   [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}  [options.safe]          The safe option.
   * @param  {Boolean}  [options.patch]         The patch option.
   * @param  {Number}   [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setData(data, options = {}) {
    if (!(0, _utils.isObject)(data)) {
      throw new Error("A collection object is required.");
    }
    const { patch, permissions } = options;
    const { last_modified } = _extends({}, data, options);

    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const request = requests.updateRequest(path, { data, permissions }, {
      last_modified,
      patch,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Retrieves the list of permissions for this collection.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getPermissions(options = {}) {
    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const request = { headers: this._getHeaders(options), path };
    const { permissions } = await this.client.execute(request, {
      retry: this._getRetry(options)
    });
    return permissions;
  }

  /**
   * Replaces all existing collection permissions with the ones provided.
   *
   * @param  {Object}   permissions             The permissions object.
   * @param  {Object}   [options={}]            The options object
   * @param  {Object}   [options.headers]       The headers object option.
   * @param  {Number}   [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}  [options.safe]          The safe option.
   * @param  {Number}   [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setPermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const data = { last_modified: options.last_modified };
    const request = requests.updateRequest(path, { data, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Append principals to the collection permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async addPermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const { last_modified } = options;
    const request = requests.jsonPatchPermissionsRequest(path, permissions, "add", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Remove principals from the collection permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async removePermissions(permissions, options = {}) {
    if (!(0, _utils.isObject)(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
    const { last_modified } = options;
    const request = requests.jsonPatchPermissionsRequest(path, permissions, "remove", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Creates a record in current collection.
   *
   * @param  {Object}  record                The record to create.
   * @param  {Object}  [options={}]          The options object.
   * @param  {Object}  [options.headers]     The headers object option.
   * @param  {Number}  [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]        The safe option.
   * @param  {Object}  [options.permissions] The permissions option.
   * @return {Promise<Object, Error>}
   */
  async createRecord(record, options = {}) {
    const { permissions } = options;
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, record.id);
    const request = requests.createRequest(path, { data: record, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Adds an attachment to a record, creating the record when it doesn't exist.
   *
   * @param  {String}  dataURL                 The data url.
   * @param  {Object}  [record={}]             The record data.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @param  {Object}  [options.permissions]   The permissions option.
   * @param  {String}  [options.filename]      Force the attachment filename.
   * @param  {String}  [options.gzipped]       Force the attachment to be gzipped or not.
   * @return {Promise<Object, Error>}
   */

  async addAttachment(dataURI, record = {}, options = {}) {
    const { permissions } = options;
    const id = record.id || _uuid.v4.v4();
    const path = (0, _endpoint2.default)("attachment", this.bucket.name, this.name, id);
    const { last_modified } = _extends({}, record, options);
    const addAttachmentRequest = requests.addAttachmentRequest(path, dataURI, { data: record, permissions }, {
      last_modified,
      filename: options.filename,
      gzipped: options.gzipped,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    await this.client.execute(addAttachmentRequest, {
      stringify: false,
      retry: this._getRetry(options)
    });
    return this.getRecord(id);
  }

  /**
   * Removes an attachment from a given record.
   *
   * @param  {Object}  recordId                The record id.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   */

  async removeAttachment(recordId, options = {}) {
    const { last_modified } = options;
    const path = (0, _endpoint2.default)("attachment", this.bucket.name, this.name, recordId);
    const request = requests.deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Updates a record in current collection.
   *
   * @param  {Object}  record                  The record to update.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @param  {Object}  [options.permissions]   The permissions option.
   * @return {Promise<Object, Error>}
   */
  async updateRecord(record, options = {}) {
    if (!(0, _utils.isObject)(record)) {
      throw new Error("A record object is required.");
    }
    if (!record.id) {
      throw new Error("A record id is required.");
    }
    const { permissions } = options;
    const { last_modified } = _extends({}, record, options);
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, record.id);
    const request = requests.updateRequest(path, { data: record, permissions }, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
      last_modified,
      patch: !!options.patch
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Deletes a record from the current collection.
   *
   * @param  {Object|String} record                  The record to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteRecord(record, options = {}) {
    const recordObj = (0, _utils.toDataBody)(record);
    if (!recordObj.id) {
      throw new Error("A record id is required.");
    }
    const { id } = recordObj;
    const { last_modified } = _extends({}, recordObj, options);
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, id);
    const request = requests.deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options)
    });
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Retrieves a record from the current collection.
   *
   * @param  {String} id                The record id to retrieve.
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getRecord(id, options = {}) {
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, id);
    const request = { headers: this._getHeaders(options), path };
    return this.client.execute(request, { retry: this._getRetry(options) });
  }

  /**
   * Lists records from the current collection.
   *
   * Sorting is done by passing a `sort` string option:
   *
   * - The field to order the results by, prefixed with `-` for descending.
   * Default: `-last_modified`.
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
   *
   * Filtering is done by passing a `filters` option object:
   *
   * - `{fieldname: "value"}`
   * - `{min_fieldname: 4000}`
   * - `{in_fieldname: "1,2,3"}`
   * - `{not_fieldname: 0}`
   * - `{exclude_fieldname: "0,1"}`
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
   *
   * Paginating is done by passing a `limit` option, then calling the `next()`
   * method from the resolved result object to fetch the next page, if any.
   *
   * @param  {Object}   [options={}]                    The options object.
   * @param  {Object}   [options.headers]               The headers object option.
   * @param  {Number}   [options.retry=0]               Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}   [options.filters=[]]            The filters object.
   * @param  {String}   [options.sort="-last_modified"] The sort field.
   * @param  {String}   [options.at]                    The timestamp to get a snapshot at.
   * @param  {String}   [options.limit=null]            The limit field.
   * @param  {String}   [options.pages=1]               The number of result pages to aggregate.
   * @param  {Number}   [options.since=null]            Only retrieve records modified since the provided timestamp.
   * @return {Promise<Object, Error>}
   */
  async listRecords(options = {}) {
    const path = (0, _endpoint2.default)("record", this.bucket.name, this.name);
    if (options.hasOwnProperty("at")) {
      return this.getSnapshot(options.at);
    } else {
      return this.client.paginatedList(path, options, {
        headers: this._getHeaders(options),
        retry: this._getRetry(options)
      });
    }
  }

  /**
   * @private
   */
  async isHistoryComplete() {
    // We consider that if we have the collection creation event part of the
    // history, then all records change events have been tracked.
    const { data: [oldestHistoryEntry] } = await this.bucket.listHistory({
      limit: 1,
      filters: {
        action: "create",
        resource_name: "collection",
        collection_id: this.name
      }
    });
    return !!oldestHistoryEntry;
  }

  /**
   * @private
   */
  async listChangesBackTo(at) {
    // Ensure we have enough history data to retrieve the complete list of
    // changes.
    if (!(await this.isHistoryComplete())) {
      throw new Error("Computing a snapshot is only possible when the full history for a " + "collection is available. Here, the history plugin seems to have " + "been enabled after the creation of the collection.");
    }
    const { data: changes } = await this.bucket.listHistory({
      pages: Infinity, // all pages up to target timestamp are required
      sort: "-target.data.last_modified",
      filters: {
        resource_name: "record",
        collection_id: this.name,
        "max_target.data.last_modified": String(at) }
    });
    return changes;
  }

  /**
   * @private
   */

  async getSnapshot(at) {
    if (!Number.isInteger(at) || at <= 0) {
      throw new Error("Invalid argument, expected a positive integer.");
    }
    // Retrieve history and check it covers the required time range.
    const changes = await this.listChangesBackTo(at);
    // Replay changes to compute the requested snapshot.
    const seenIds = new Set();
    let snapshot = [];
    for (const _ref of changes) {
      const { action, target: { data: record } } = _ref;

      if (action == "delete") {
        seenIds.add(record.id); // ensure not reprocessing deleted entries
        snapshot = snapshot.filter(r => r.id !== record.id);
      } else if (!seenIds.has(record.id)) {
        seenIds.add(record.id);
        snapshot.push(record);
      }
    }
    return {
      last_modified: String(at),
      data: snapshot.sort((a, b) => b.last_modified - a.last_modified),
      next: () => {
        throw new Error("Snapshots don't support pagination");
      },
      hasNextPage: false,
      totalRecords: snapshot.length
    };
  }

  /**
   * Performs batch operations at the current collection level.
   *
   * @param  {Function} fn                   The batch operation function.
   * @param  {Object}   [options={}]         The options object.
   * @param  {Object}   [options.headers]    The headers object option.
   * @param  {Boolean}  [options.safe]       The safe option.
   * @param  {Number}   [options.retry]      The retry option.
   * @param  {Boolean}  [options.aggregate]  Produces a grouped result object.
   * @return {Promise<Object, Error>}
   */
  async batch(fn, options = {}) {
    return this.client.batch(fn, {
      bucket: this.bucket.name,
      collection: this.name,
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options),
      aggregate: !!options.aggregate
    });
  }
}, (_applyDecoratedDescriptor(_class.prototype, "addAttachment", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "addAttachment"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "removeAttachment", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "removeAttachment"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "getSnapshot", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "getSnapshot"), _class.prototype)), _class));
exports.default = Collection;

},{"./endpoint":11,"./requests":14,"./utils":15,"uuid":2}],11:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = endpoint;
/**
 * Endpoints templates.
 * @type {Object}
 */
const ENDPOINTS = {
  root: () => "/",
  batch: () => "/batch",
  permissions: () => "/permissions",
  bucket: bucket => "/buckets" + (bucket ? `/${bucket}` : ""),
  history: bucket => `${ENDPOINTS.bucket(bucket)}/history`,
  collection: (bucket, coll) => `${ENDPOINTS.bucket(bucket)}/collections` + (coll ? `/${coll}` : ""),
  group: (bucket, group) => `${ENDPOINTS.bucket(bucket)}/groups` + (group ? `/${group}` : ""),
  record: (bucket, coll, id) => `${ENDPOINTS.collection(bucket, coll)}/records` + (id ? `/${id}` : ""),
  attachment: (bucket, coll, id) => `${ENDPOINTS.record(bucket, coll, id)}/attachment`
};

/**
 * Retrieves a server enpoint by its name.
 *
 * @private
 * @param  {String}    name The endpoint name.
 * @param  {...string} args The endpoint parameters.
 * @return {String}
 */
function endpoint(name, ...args) {
  return ENDPOINTS[name](...args);
}

},{}],12:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
/**
 * Kinto server error code descriptors.
 * @type {Object}
 */
exports.default = {
  104: "Missing Authorization Token",
  105: "Invalid Authorization Token",
  106: "Request body was not valid JSON",
  107: "Invalid request parameter",
  108: "Missing request parameter",
  109: "Invalid posted data",
  110: "Invalid Token / id",
  111: "Missing Token / id",
  112: "Content-Length header was not provided",
  113: "Request body too large",
  114: "Resource was created, updated or deleted meanwhile",
  115: "Method not allowed on this end point (hint: server may be readonly)",
  116: "Requested version not available on this server",
  117: "Client has sent too many requests",
  121: "Resource access is forbidden for this user",
  122: "Another resource violates constraint",
  201: "Service Temporary unavailable due to high load",
  202: "Service deprecated",
  999: "Internal Server Error"
};

},{}],13:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _utils = require("./utils");

var _errors = require("./errors");

var _errors2 = _interopRequireDefault(_errors);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Enhanced HTTP client for the Kinto protocol.
 * @private
 */
let HTTP = class HTTP {
  /**
   * Default HTTP request headers applied to each outgoing request.
   *
   * @type {Object}
   */
  static get DEFAULT_REQUEST_HEADERS() {
    return {
      Accept: "application/json",
      "Content-Type": "application/json"
    };
  }

  /**
   * Default options.
   *
   * @type {Object}
   */
  static get defaultOptions() {
    return { timeout: null, requestMode: "cors" };
  }

  /**
   * Constructor.
   *
   * @param {EventEmitter} events                       The event handler.
   * @param {Object}       [options={}}                 The options object.
   * @param {Number}       [options.timeout=null]       The request timeout in ms, if any (default: `null`).
   * @param {String}       [options.requestMode="cors"] The HTTP request mode (default: `"cors"`).
   */
  constructor(events, options = {}) {
    // public properties
    /**
     * The event emitter instance.
     * @type {EventEmitter}
     */
    if (!events) {
      throw new Error("No events handler provided");
    }
    this.events = events;

    /**
     * The request mode.
     * @see  https://fetch.spec.whatwg.org/#requestmode
     * @type {String}
     */
    this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode;

    /**
     * The request timeout.
     * @type {Number}
     */
    this.timeout = options.timeout || HTTP.defaultOptions.timeout;
  }

  /**
   * @private
   */
  timedFetch(url, options) {
    let hasTimedout = false;
    return new Promise((resolve, reject) => {
      // Detect if a request has timed out.
      let _timeoutId;
      if (this.timeout) {
        _timeoutId = setTimeout(() => {
          hasTimedout = true;
          reject(new Error("Request timeout."));
        }, this.timeout);
      }
      function proceedWithHandler(fn) {
        return arg => {
          if (!hasTimedout) {
            if (_timeoutId) {
              clearTimeout(_timeoutId);
            }
            fn(arg);
          }
        };
      }
      fetch(url, options).then(proceedWithHandler(resolve)).catch(proceedWithHandler(reject));
    });
  }

  /**
   * @private
   */
  async processResponse(response) {
    const { status } = response;
    const text = await response.text();
    // Check if we have a body; if so parse it as JSON.
    if (text.length === 0) {
      return this.formatResponse(response, null);
    }
    try {
      return this.formatResponse(response, JSON.parse(text));
    } catch (err) {
      const error = new Error(`HTTP ${status || 0}; ${err}`);
      error.response = response;
      error.stack = err.stack;
      throw error;
    }
  }

  /**
   * @private
   */
  formatResponse(response, json) {
    const { status, statusText, headers } = response;
    if (json && status >= 400) {
      let message = `HTTP ${status} ${json.error || ""}: `;
      if (json.errno && json.errno in _errors2.default) {
        const errnoMsg = _errors2.default[json.errno];
        message += errnoMsg;
        if (json.message && json.message !== errnoMsg) {
          message += ` (${json.message})`;
        }
      } else {
        message += statusText || "";
      }
      const error = new Error(message.trim());
      error.response = response;
      error.data = json;
      throw error;
    }
    return { status, json, headers };
  }

  /**
   * @private
   */
  async retry(url, retryAfter, request, options) {
    await (0, _utils.delay)(retryAfter);
    return this.request(url, request, _extends({}, options, { retry: options.retry - 1 }));
  }

  /**
   * Performs an HTTP request to the Kinto server.
   *
   * Resolves with an objet containing the following HTTP response properties:
   * - `{Number}  status`  The HTTP status code.
   * - `{Object}  json`    The JSON response body.
   * - `{Headers} headers` The response headers object; see the ES6 fetch() spec.
   *
   * @param  {String} url               The URL.
   * @param  {Object} [request={}]      The request object, passed to
   *     fetch() as its options object.
   * @param  {Object} [request.headers] The request headers object (default: {})
   * @param  {Object} [options={}]      Options for making the
   *     request
   * @param  {Number} [options.retry]   Number of retries (default: 0)
   * @return {Promise}
   */
  async request(url, request = { headers: {} }, options = { retry: 0 }) {
    // Ensure default request headers are always set
    request.headers = _extends({}, HTTP.DEFAULT_REQUEST_HEADERS, request.headers);
    // If a multipart body is provided, remove any custom Content-Type header as
    // the fetch() implementation will add the correct one for us.
    if (request.body && typeof request.body.append === "function") {
      delete request.headers["Content-Type"];
    }
    request.mode = this.requestMode;

    const response = await this.timedFetch(url, request);
    const { status, headers } = response;

    this._checkForDeprecationHeader(headers);
    this._checkForBackoffHeader(status, headers);

    // Check if the server summons the client to retry after a while.
    const retryAfter = this._checkForRetryAfterHeader(status, headers);
    // If number of allowed of retries is not exhausted, retry the same request.
    if (retryAfter && options.retry > 0) {
      return this.retry(url, retryAfter, request, options);
    } else {
      return this.processResponse(response);
    }
  }

  _checkForDeprecationHeader(headers) {
    const alertHeader = headers.get("Alert");
    if (!alertHeader) {
      return;
    }
    let alert;
    try {
      alert = JSON.parse(alertHeader);
    } catch (err) {
      console.warn("Unable to parse Alert header message", alertHeader);
      return;
    }
    console.warn(alert.message, alert.url);
    this.events.emit("deprecated", alert);
  }

  _checkForBackoffHeader(status, headers) {
    let backoffMs;
    const backoffSeconds = parseInt(headers.get("Backoff"), 10);
    if (backoffSeconds > 0) {
      backoffMs = new Date().getTime() + backoffSeconds * 1000;
    } else {
      backoffMs = 0;
    }
    this.events.emit("backoff", backoffMs);
  }

  _checkForRetryAfterHeader(status, headers) {
    let retryAfter = headers.get("Retry-After");
    if (!retryAfter) {
      return;
    }
    const delay = parseInt(retryAfter, 10) * 1000;
    retryAfter = new Date().getTime() + delay;
    this.events.emit("retry-after", retryAfter);
    return delay;
  }
};
exports.default = HTTP;

},{"./errors":12,"./utils":15}],14:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports.createRequest = createRequest;
exports.updateRequest = updateRequest;
exports.jsonPatchPermissionsRequest = jsonPatchPermissionsRequest;
exports.deleteRequest = deleteRequest;
exports.addAttachmentRequest = addAttachmentRequest;

var _utils = require("./utils");

const requestDefaults = {
  safe: false,
  // check if we should set default content type here
  headers: {},
  permissions: undefined,
  data: undefined,
  patch: false
};

/**
 * @private
 */
function safeHeader(safe, last_modified) {
  if (!safe) {
    return {};
  }
  if (last_modified) {
    return { "If-Match": `"${last_modified}"` };
  }
  return { "If-None-Match": "*" };
}

/**
 * @private
 */
function createRequest(path, { data, permissions }, options = {}) {
  const { headers, safe } = _extends({}, requestDefaults, options);
  return {
    method: data && data.id ? "PUT" : "POST",
    path,
    headers: _extends({}, headers, safeHeader(safe)),
    body: { data, permissions }
  };
}

/**
 * @private
 */
function updateRequest(path, { data, permissions }, options = {}) {
  const { headers, safe, patch } = _extends({}, requestDefaults, options);
  const { last_modified } = _extends({}, data, options);

  if (Object.keys((0, _utils.omit)(data, "id", "last_modified")).length === 0) {
    data = undefined;
  }

  return {
    method: patch ? "PATCH" : "PUT",
    path,
    headers: _extends({}, headers, safeHeader(safe, last_modified)),
    body: { data, permissions }
  };
}

/**
 * @private
 */
function jsonPatchPermissionsRequest(path, permissions, opType, options = {}) {
  const { headers, safe, last_modified } = _extends({}, requestDefaults, options);

  const ops = [];

  for (const [type, principals] of Object.entries(permissions)) {
    for (const principal of principals) {
      ops.push({
        op: opType,
        path: `/permissions/${type}/${principal}`
      });
    }
  }

  return {
    method: "PATCH",
    path,
    headers: _extends({}, headers, safeHeader(safe, last_modified), {
      "Content-Type": "application/json-patch+json"
    }),
    body: ops
  };
}

/**
 * @private
 */
function deleteRequest(path, options = {}) {
  const { headers, safe, last_modified } = _extends({}, requestDefaults, options);
  if (safe && !last_modified) {
    throw new Error("Safe concurrency check requires a last_modified value.");
  }
  return {
    method: "DELETE",
    path,
    headers: _extends({}, headers, safeHeader(safe, last_modified))
  };
}

/**
 * @private
 */
function addAttachmentRequest(path, dataURI, { data, permissions } = {}, options = {}) {
  const { headers, safe, gzipped } = _extends({}, requestDefaults, options);
  const { last_modified } = _extends({}, data, options);

  const body = { data, permissions };
  const formData = (0, _utils.createFormData)(dataURI, body, options);

  let customPath = gzipped != null ? customPath = path + "?gzipped=" + (gzipped ? "true" : "false") : path;

  return {
    method: "POST",
    path: customPath,
    headers: _extends({}, headers, safeHeader(safe, last_modified)),
    body: formData
  };
}

},{"./utils":15}],15:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports.partition = partition;
exports.delay = delay;
exports.pMap = pMap;
exports.omit = omit;
exports.toDataBody = toDataBody;
exports.qsify = qsify;
exports.checkVersion = checkVersion;
exports.support = support;
exports.capable = capable;
exports.nobatch = nobatch;
exports.isObject = isObject;
exports.parseDataURL = parseDataURL;
exports.extractFileInfo = extractFileInfo;
exports.createFormData = createFormData;
exports.cleanUndefinedProperties = cleanUndefinedProperties;
/**
 * Chunks an array into n pieces.
 *
 * @private
 * @param  {Array}  array
 * @param  {Number} n
 * @return {Array}
 */
function partition(array, n) {
  if (n <= 0) {
    return array;
  }
  return array.reduce((acc, x, i) => {
    if (i === 0 || i % n === 0) {
      acc.push([x]);
    } else {
      acc[acc.length - 1].push(x);
    }
    return acc;
  }, []);
}

/**
 * Returns a Promise always resolving after the specified amount in milliseconds.
 *
 * @return Promise<void>
 */
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * Maps a list to promises using the provided mapping function, executes them
 * sequentially then returns a Promise resolving with ordered results obtained.
 * Think of this as a sequential Promise.all.
 *
 * @private
 * @param  {Array}    list The list to map.
 * @param  {Function} fn   The mapping function.
 * @return {Promise}
 */
async function pMap(list, fn) {
  let results = [];
  await list.reduce(async function (promise, entry) {
    await promise;
    results = results.concat((await fn(entry)));
  }, Promise.resolve());
  return results;
}

/**
 * Takes an object and returns a copy of it with the provided keys omitted.
 *
 * @private
 * @param  {Object}    obj  The source object.
 * @param  {...String} keys The keys to omit.
 * @return {Object}
 */
function omit(obj, ...keys) {
  return Object.keys(obj).reduce((acc, key) => {
    if (keys.indexOf(key) === -1) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
}

/**
 * Always returns a resource data object from the provided argument.
 *
 * @private
 * @param  {Object|String} resource
 * @return {Object}
 */
function toDataBody(resource) {
  if (isObject(resource)) {
    return resource;
  }
  if (typeof resource === "string") {
    return { id: resource };
  }
  throw new Error("Invalid argument.");
}

/**
 * Transforms an object into an URL query string, stripping out any undefined
 * values.
 *
 * @param  {Object} obj
 * @return {String}
 */
function qsify(obj) {
  const encode = v => encodeURIComponent(typeof v === "boolean" ? String(v) : v);
  const stripUndefined = o => JSON.parse(JSON.stringify(o));
  const stripped = stripUndefined(obj);
  return Object.keys(stripped).map(k => {
    const ks = encode(k) + "=";
    if (Array.isArray(stripped[k])) {
      return ks + stripped[k].map(v => encode(v)).join(",");
    } else {
      return ks + encode(stripped[k]);
    }
  }).join("&");
}

/**
 * Checks if a version is within the provided range.
 *
 * @param  {String} version    The version to check.
 * @param  {String} minVersion The minimum supported version (inclusive).
 * @param  {String} maxVersion The minimum supported version (exclusive).
 * @throws {Error} If the version is outside of the provided range.
 */
function checkVersion(version, minVersion, maxVersion) {
  const extract = str => str.split(".").map(x => parseInt(x, 10));
  const [verMajor, verMinor] = extract(version);
  const [minMajor, minMinor] = extract(minVersion);
  const [maxMajor, maxMinor] = extract(maxVersion);
  const checks = [verMajor < minMajor, verMajor === minMajor && verMinor < minMinor, verMajor > maxMajor, verMajor === maxMajor && verMinor >= maxMinor];
  if (checks.some(x => x)) {
    throw new Error(`Version ${version} doesn't satisfy ${minVersion} <= x < ${maxVersion}`);
  }
}

/**
 * Generates a decorator function ensuring a version check is performed against
 * the provided requirements before executing it.
 *
 * @param  {String} min The required min version (inclusive).
 * @param  {String} max The required max version (inclusive).
 * @return {Function}
 */
function support(min, max) {
  return function (target, key, descriptor) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          const client = "client" in this ? this.client : this;
          return client.fetchHTTPApiVersion().then(version => checkVersion(version, min, max)).then(() => fn.apply(this, args));
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true
        });
        return wrappedMethod;
      }
    };
  };
}

/**
 * Generates a decorator function ensuring that the specified capabilities are
 * available on the server before executing it.
 *
 * @param  {Array<String>} capabilities The required capabilities.
 * @return {Function}
 */
function capable(capabilities) {
  return function (target, key, descriptor) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          const client = "client" in this ? this.client : this;
          return client.fetchServerCapabilities().then(available => {
            const missing = capabilities.filter(c => !(c in available));
            if (missing.length > 0) {
              const missingStr = missing.join(", ");
              throw new Error(`Required capabilities ${missingStr} not present on server`);
            }
          }).then(() => fn.apply(this, args));
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true
        });
        return wrappedMethod;
      }
    };
  };
}

/**
 * Generates a decorator function ensuring an operation is not performed from
 * within a batch request.
 *
 * @param  {String} message The error message to throw.
 * @return {Function}
 */
function nobatch(message) {
  return function (target, key, descriptor) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          if (this._isBatch) {
            throw new Error(message);
          }
          return fn.apply(this, args);
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true
        });
        return wrappedMethod;
      }
    };
  };
}

/**
 * Returns true if the specified value is an object (i.e. not an array nor null).
 * @param  {Object} thing The value to inspect.
 * @return {bool}
 */
function isObject(thing) {
  return typeof thing === "object" && thing !== null && !Array.isArray(thing);
}

/**
 * Parses a data url.
 * @param  {String} dataURL The data url.
 * @return {Object}
 */
function parseDataURL(dataURL) {
  const regex = /^data:(.*);base64,(.*)/;
  const match = dataURL.match(regex);
  if (!match) {
    throw new Error(`Invalid data-url: ${String(dataURL).substr(0, 32)}...`);
  }
  const props = match[1];
  const base64 = match[2];
  const [type, ...rawParams] = props.split(";");
  const params = rawParams.reduce((acc, param) => {
    const [key, value] = param.split("=");
    return _extends({}, acc, { [key]: value });
  }, {});
  return _extends({}, params, { type, base64 });
}

/**
 * Extracts file information from a data url.
 * @param  {String} dataURL The data url.
 * @return {Object}
 */
function extractFileInfo(dataURL) {
  const { name, type, base64 } = parseDataURL(dataURL);
  const binary = atob(base64);
  const array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  const blob = new Blob([new Uint8Array(array)], { type });
  return { blob, name };
}

/**
 * Creates a FormData instance from a data url and an existing JSON response
 * body.
 * @param  {String} dataURL            The data url.
 * @param  {Object} body               The response body.
 * @param  {Object} [options={}]       The options object.
 * @param  {Object} [options.filename] Force attachment file name.
 * @return {FormData}
 */
function createFormData(dataURL, body, options = {}) {
  const { filename = "untitled" } = options;
  const { blob, name } = extractFileInfo(dataURL);
  const formData = new FormData();
  formData.append("attachment", blob, name || filename);
  for (const property in body) {
    if (typeof body[property] !== "undefined") {
      formData.append(property, JSON.stringify(body[property]));
    }
  }
  return formData;
}

/**
 * Clones an object with all its undefined keys removed.
 * @private
 */
function cleanUndefinedProperties(obj) {
  const result = {};
  for (const key in obj) {
    if (typeof obj[key] !== "undefined") {
      result[key] = obj[key];
    }
  }
  return result;
}

},{}]},{},[1])(1)
});PK
!<vo`>`>/modules/services-common/kinto-offline-client.js/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This file is generated from kinto.js - do not modify directly.
 */

this.EXPORTED_SYMBOLS = ["Kinto"];

/*
 * Version 9.0.2 - b025c7b
 */

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Kinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
 *
 * 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";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _KintoBase = require("../src/KintoBase");

var _KintoBase2 = _interopRequireDefault(_KintoBase);

var _utils = require("../src/utils");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/Timer.jsm");
Cu.importGlobalProperties(["fetch"]);
const { EventEmitter } = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);

// Use standalone kinto-http module landed in FFx.
const { KintoHttpClient } = Cu.import("resource://services-common/kinto-http-client.js");

class Kinto extends _KintoBase2.default {
  constructor(options = {}) {
    const events = {};
    EventEmitter.decorate(events);

    const defaults = {
      events,
      ApiClass: KintoHttpClient
    };
    super(_extends({}, defaults, options));
  }

  collection(collName, options = {}) {
    const idSchema = {
      validate: _utils.RE_UUID.test.bind(_utils.RE_UUID),
      generate: function () {
        return generateUUID().toString().replace(/[{}]/g, "");
      }
    };
    return super.collection(collName, _extends({ idSchema }, options));
  }
}

exports.default = Kinto; // This fixes compatibility with CommonJS required by browserify.
// See http://stackoverflow.com/questions/33505992/babel-6-changes-how-it-exports-default/33683495#33683495

if (typeof module === "object") {
  module.exports = Kinto;
}

},{"../src/KintoBase":3,"../src/utils":7}],2:[function(require,module,exports){

},{}],3:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _collection = require("./collection");

var _collection2 = _interopRequireDefault(_collection);

var _base = require("./adapters/base");

var _base2 = _interopRequireDefault(_base);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const DEFAULT_BUCKET_NAME = "default";
const DEFAULT_REMOTE = "http://localhost:8888/v1";
const DEFAULT_RETRY = 1;

/**
 * KintoBase class.
 */
class KintoBase {
  /**
   * Provides a public access to the base adapter class. Users can create a
   * custom DB adapter by extending {@link BaseAdapter}.
   *
   * @type {Object}
   */
  static get adapters() {
    return {
      BaseAdapter: _base2.default
    };
  }

  /**
   * Synchronization strategies. Available strategies are:
   *
   * - `MANUAL`: Conflicts will be reported in a dedicated array.
   * - `SERVER_WINS`: Conflicts are resolved using remote data.
   * - `CLIENT_WINS`: Conflicts are resolved using local data.
   *
   * @type {Object}
   */
  static get syncStrategy() {
    return _collection2.default.strategy;
  }

  /**
   * Constructor.
   *
   * Options:
   * - `{String}`       `remote`         The server URL to use.
   * - `{String}`       `bucket`         The collection bucket name.
   * - `{EventEmitter}` `events`         Events handler.
   * - `{BaseAdapter}`  `adapter`        The base DB adapter class.
   * - `{Object}`       `adapterOptions` Options given to the adapter.
   * - `{String}`       `dbPrefix`       The DB name prefix.
   * - `{Object}`       `headers`        The HTTP headers to use.
   * - `{Object}`       `retry`          Number of retries when the server fails to process the request (default: `1`)
   * - `{String}`       `requestMode`    The HTTP CORS mode to use.
   * - `{Number}`       `timeout`        The requests timeout in ms (default: `5000`).
   *
   * @param  {Object} options The options object.
   */
  constructor(options = {}) {
    const defaults = {
      bucket: DEFAULT_BUCKET_NAME,
      remote: DEFAULT_REMOTE,
      retry: DEFAULT_RETRY
    };
    this._options = _extends({}, defaults, options);
    if (!this._options.adapter) {
      throw new Error("No adapter provided");
    }

    const {
      remote,
      events,
      headers,
      retry,
      requestMode,
      timeout,
      ApiClass
    } = this._options;

    // public properties

    /**
     * The kinto HTTP client instance.
     * @type {KintoClient}
     */
    this.api = new ApiClass(remote, {
      events,
      headers,
      retry,
      requestMode,
      timeout
    });
    /**
     * The event emitter instance.
     * @type {EventEmitter}
     */
    this.events = this._options.events;
  }

  /**
   * Creates a {@link Collection} instance. The second (optional) parameter
   * will set collection-level options like e.g. `remoteTransformers`.
   *
   * @param  {String} collName The collection name.
   * @param  {Object} [options={}]                 Extra options or override client's options.
   * @param  {Object} [options.idSchema]           IdSchema instance (default: UUID)
   * @param  {Object} [options.remoteTransformers] Array<RemoteTransformer> (default: `[]`])
   * @param  {Object} [options.hooks]              Array<Hook> (default: `[]`])
   * @return {Collection}
   */
  collection(collName, options = {}) {
    if (!collName) {
      throw new Error("missing collection name");
    }
    const { bucket, events, adapter, adapterOptions, dbPrefix } = _extends({}, this._options, options);
    const { idSchema, remoteTransformers, hooks } = options;

    return new _collection2.default(bucket, collName, this.api, {
      events,
      adapter,
      adapterOptions,
      dbPrefix,
      idSchema,
      remoteTransformers,
      hooks
    });
  }
}
exports.default = KintoBase;

},{"./adapters/base":5,"./collection":6}],4:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _base = require("./base.js");

var _base2 = _interopRequireDefault(_base);

var _utils = require("../utils");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const INDEXED_FIELDS = ["id", "_status", "last_modified"];

/**
 * IDB cursor handlers.
 * @type {Object}
 */
const cursorHandlers = {
  all(filters, done) {
    const results = [];
    return function (event) {
      const cursor = event.target.result;
      if (cursor) {
        if ((0, _utils.filterObject)(filters, cursor.value)) {
          results.push(cursor.value);
        }
        cursor.continue();
      } else {
        done(results);
      }
    };
  },

  in(values, done) {
    if (values.length === 0) {
      return done([]);
    }
    const sortedValues = [].slice.call(values).sort();
    const results = [];
    return function (event) {
      const cursor = event.target.result;
      if (!cursor) {
        done(results);
        return;
      }
      const { key, value } = cursor;
      let i = 0;
      while (key > sortedValues[i]) {
        // The cursor has passed beyond this key. Check next.
        ++i;
        if (i === sortedValues.length) {
          done(results); // There is no next. Stop searching.
          return;
        }
      }
      if (key === sortedValues[i]) {
        results.push(value);
        cursor.continue();
      } else {
        cursor.continue(sortedValues[i]);
      }
    };
  }
};

/**
 * Extract from filters definition the first indexed field. Since indexes were
 * created on single-columns, extracting a single one makes sense.
 *
 * @param  {Object} filters The filters object.
 * @return {String|undefined}
 */
function findIndexedField(filters) {
  const filteredFields = Object.keys(filters);
  const indexedFields = filteredFields.filter(field => {
    return INDEXED_FIELDS.indexOf(field) !== -1;
  });
  return indexedFields[0];
}

/**
 * Creates an IDB request and attach it the appropriate cursor event handler to
 * perform a list query.
 *
 * Multiple matching values are handled by passing an array.
 *
 * @param  {IDBStore}         store      The IDB store.
 * @param  {String|undefined} indexField The indexed field to query, if any.
 * @param  {Any}              value      The value to filter, if any.
 * @param  {Object}           filters    More filters.
 * @param  {Function}         done       The operation completion handler.
 * @return {IDBRequest}
 */
function createListRequest(store, indexField, value, filters, done) {
  if (!indexField) {
    // Get all records.
    const request = store.openCursor();
    request.onsuccess = cursorHandlers.all(filters, done);
    return request;
  }

  // WHERE IN equivalent clause
  if (Array.isArray(value)) {
    const request = store.index(indexField).openCursor();
    request.onsuccess = cursorHandlers.in(value, done);
    return request;
  }

  // WHERE field = value clause
  const request = store.index(indexField).openCursor(IDBKeyRange.only(value));
  request.onsuccess = cursorHandlers.all(filters, done);
  return request;
}

/**
 * IndexedDB adapter.
 *
 * This adapter doesn't support any options.
 */
class IDB extends _base2.default {
  /**
   * Constructor.
   *
   * @param  {String} dbname The database nale.
   */
  constructor(dbname) {
    super();
    this._db = null;
    // public properties
    /**
     * The database name.
     * @type {String}
     */
    this.dbname = dbname;
  }

  _handleError(method, err) {
    const error = new Error(method + "() " + err.message);
    error.stack = err.stack;
    throw error;
  }

  /**
   * Ensures a connection to the IndexedDB database has been opened.
   *
   * @override
   * @return {Promise}
   */
  open() {
    if (this._db) {
      return Promise.resolve(this);
    }
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbname, 1);
      request.onupgradeneeded = event => {
        // DB object
        const db = event.target.result;
        // Main collection store
        const collStore = db.createObjectStore(this.dbname, {
          keyPath: "id"
        });
        // Primary key (generated by IdSchema, UUID by default)
        collStore.createIndex("id", "id", { unique: true });
        // Local record status ("synced", "created", "updated", "deleted")
        collStore.createIndex("_status", "_status");
        // Last modified field
        collStore.createIndex("last_modified", "last_modified");

        // Metadata store
        const metaStore = db.createObjectStore("__meta__", {
          keyPath: "name"
        });
        metaStore.createIndex("name", "name", { unique: true });
      };
      request.onerror = event => reject(event.target.error);
      request.onsuccess = event => {
        this._db = event.target.result;
        resolve(this);
      };
    });
  }

  /**
   * Closes current connection to the database.
   *
   * @override
   * @return {Promise}
   */
  close() {
    if (this._db) {
      this._db.close(); // indexedDB.close is synchronous
      this._db = null;
    }
    return Promise.resolve();
  }

  /**
   * Returns a transaction and a store objects for this collection.
   *
   * To determine if a transaction has completed successfully, we should rather
   * listen to the transaction’s complete event rather than the IDBObjectStore
   * request’s success event, because the transaction may still fail after the
   * success event fires.
   *
   * @param  {String}      mode  Transaction mode ("readwrite" or undefined)
   * @param  {String|null} name  Store name (defaults to coll name)
   * @return {Object}
   */
  prepare(mode = undefined, name = null) {
    const storeName = name || this.dbname;
    // On Safari, calling IDBDatabase.transaction with mode == undefined raises
    // a TypeError.
    const transaction = mode ? this._db.transaction([storeName], mode) : this._db.transaction([storeName]);
    const store = transaction.objectStore(storeName);
    return { transaction, store };
  }

  /**
   * Deletes every records in the current collection.
   *
   * @override
   * @return {Promise}
   */
  clear() {
    var _this = this;

    return _asyncToGenerator(function* () {
      try {
        yield _this.open();
        return new Promise(function (resolve, reject) {
          const { transaction, store } = _this.prepare("readwrite");
          store.clear();
          transaction.onerror = function (event) {
            return reject(new Error(event.target.error));
          };
          transaction.oncomplete = function () {
            return resolve();
          };
        });
      } catch (e) {
        _this._handleError("clear", e);
      }
    })();
  }

  /**
   * Executes the set of synchronous CRUD operations described in the provided
   * callback within an IndexedDB transaction, for current db store.
   *
   * The callback will be provided an object exposing the following synchronous
   * CRUD operation methods: get, create, update, delete.
   *
   * Important note: because limitations in IndexedDB implementations, no
   * asynchronous code should be performed within the provided callback; the
   * promise will therefore be rejected if the callback returns a Promise.
   *
   * Options:
   * - {Array} preload: The list of record IDs to fetch and make available to
   *   the transaction object get() method (default: [])
   *
   * @example
   * const db = new IDB("example");
   * db.execute(transaction => {
   *   transaction.create({id: 1, title: "foo"});
   *   transaction.update({id: 2, title: "bar"});
   *   transaction.delete(3);
   *   return "foo";
   * })
   *   .catch(console.error.bind(console));
   *   .then(console.log.bind(console)); // => "foo"
   *
   * @param  {Function} callback The operation description callback.
   * @param  {Object}   options  The options object.
   * @return {Promise}
   */
  execute(callback, options = { preload: [] }) {
    var _this2 = this;

    return _asyncToGenerator(function* () {
      // Transactions in IndexedDB are autocommited when a callback does not
      // perform any additional operation.
      // The way Promises are implemented in Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1193394)
      // prevents using within an opened transaction.
      // To avoid managing asynchronocity in the specified `callback`, we preload
      // a list of record in order to execute the `callback` synchronously.
      // See also:
      // - http://stackoverflow.com/a/28388805/330911
      // - http://stackoverflow.com/a/10405196
      // - https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
      yield _this2.open();
      return new Promise(function (resolve, reject) {
        // Start transaction.
        const { transaction, store } = _this2.prepare("readwrite");
        // Preload specified records using index.
        const ids = options.preload;
        store.index("id").openCursor().onsuccess = cursorHandlers.in(ids, function (records) {
          // Store obtained records by id.
          const preloaded = records.reduce(function (acc, record) {
            acc[record.id] = record;
            return acc;
          }, {});
          // Expose a consistent API for every adapter instead of raw store methods.
          const proxy = transactionProxy(store, preloaded);
          // The callback is executed synchronously within the same transaction.
          let result;
          try {
            result = callback(proxy);
          } catch (e) {
            transaction.abort();
            reject(e);
          }
          if (result instanceof Promise) {
            // XXX: investigate how to provide documentation details in error.
            reject(new Error("execute() callback should not return a Promise."));
          }
          // XXX unsure if we should manually abort the transaction on error
          transaction.onerror = function (event) {
            return reject(new Error(event.target.error));
          };
          transaction.oncomplete = function (event) {
            return resolve(result);
          };
        });
      });
    })();
  }

  /**
   * Retrieve a record by its primary key from the IndexedDB database.
   *
   * @override
   * @param  {String} id The record id.
   * @return {Promise}
   */
  get(id) {
    var _this3 = this;

    return _asyncToGenerator(function* () {
      try {
        yield _this3.open();
        return new Promise(function (resolve, reject) {
          const { transaction, store } = _this3.prepare();
          const request = store.get(id);
          transaction.onerror = function (event) {
            return reject(new Error(event.target.error));
          };
          transaction.oncomplete = function () {
            return resolve(request.result);
          };
        });
      } catch (e) {
        _this3._handleError("get", e);
      }
    })();
  }

  /**
   * Lists all records from the IndexedDB database.
   *
   * @override
   * @return {Promise}
   */
  list(params = { filters: {} }) {
    var _this4 = this;

    return _asyncToGenerator(function* () {
      const { filters } = params;
      const indexField = findIndexedField(filters);
      const value = filters[indexField];
      try {
        yield _this4.open();
        const results = yield new Promise(function (resolve, reject) {
          let results = [];
          // If `indexField` was used already, don't filter again.
          const remainingFilters = (0, _utils.omitKeys)(filters, indexField);

          const { transaction, store } = _this4.prepare();
          createListRequest(store, indexField, value, remainingFilters, function (_results) {
            // we have received all requested records, parking them within
            // current scope
            results = _results;
          });
          transaction.onerror = function (event) {
            return reject(new Error(event.target.error));
          };
          transaction.oncomplete = function (event) {
            return resolve(results);
          };
        });

        // The resulting list of records is sorted.
        // XXX: with some efforts, this could be fully implemented using IDB API.
        return params.order ? (0, _utils.sortObjects)(params.order, results) : results;
      } catch (e) {
        _this4._handleError("list", e);
      }
    })();
  }

  /**
   * Store the lastModified value into metadata store.
   *
   * @override
   * @param  {Number}  lastModified
   * @return {Promise}
   */
  saveLastModified(lastModified) {
    var _this5 = this;

    return _asyncToGenerator(function* () {
      const value = parseInt(lastModified, 10) || null;
      yield _this5.open();
      return new Promise(function (resolve, reject) {
        const { transaction, store } = _this5.prepare("readwrite", "__meta__");
        store.put({ name: "lastModified", value: value });
        transaction.onerror = function (event) {
          return reject(event.target.error);
        };
        transaction.oncomplete = function (event) {
          return resolve(value);
        };
      });
    })();
  }

  /**
   * Retrieve saved lastModified value.
   *
   * @override
   * @return {Promise}
   */
  getLastModified() {
    var _this6 = this;

    return _asyncToGenerator(function* () {
      yield _this6.open();
      return new Promise(function (resolve, reject) {
        const { transaction, store } = _this6.prepare(undefined, "__meta__");
        const request = store.get("lastModified");
        transaction.onerror = function (event) {
          return reject(event.target.error);
        };
        transaction.oncomplete = function (event) {
          resolve(request.result && request.result.value || null);
        };
      });
    })();
  }

  /**
   * Load a dump of records exported from a server.
   *
   * @abstract
   * @return {Promise}
   */
  loadDump(records) {
    var _this7 = this;

    return _asyncToGenerator(function* () {
      try {
        yield _this7.execute(function (transaction) {
          records.forEach(function (record) {
            return transaction.update(record);
          });
        });
        const previousLastModified = yield _this7.getLastModified();
        const lastModified = Math.max(...records.map(function (record) {
          return record.last_modified;
        }));
        if (lastModified > previousLastModified) {
          yield _this7.saveLastModified(lastModified);
        }
        return records;
      } catch (e) {
        _this7._handleError("loadDump", e);
      }
    })();
  }
}

exports.default = IDB; /**
                        * IDB transaction proxy.
                        *
                        * @param  {IDBStore} store     The IndexedDB database store.
                        * @param  {Array}    preloaded The list of records to make available to
                        *                              get() (default: []).
                        * @return {Object}
                        */

function transactionProxy(store, preloaded = []) {
  return {
    create(record) {
      store.add(record);
    },

    update(record) {
      store.put(record);
    },

    delete(id) {
      store.delete(id);
    },

    get(id) {
      return preloaded[id];
    }
  };
}

},{"../utils":7,"./base.js":5}],5:[function(require,module,exports){
"use strict";

/**
 * Base db adapter.
 *
 * @abstract
 */

Object.defineProperty(exports, "__esModule", {
  value: true
});
class BaseAdapter {
  /**
   * Deletes every records present in the database.
   *
   * @abstract
   * @return {Promise}
   */
  clear() {
    throw new Error("Not Implemented.");
  }

  /**
   * Executes a batch of operations within a single transaction.
   *
   * @abstract
   * @param  {Function} callback The operation callback.
   * @param  {Object}   options  The options object.
   * @return {Promise}
   */
  execute(callback, options = { preload: [] }) {
    throw new Error("Not Implemented.");
  }

  /**
   * Retrieve a record by its primary key from the database.
   *
   * @abstract
   * @param  {String} id The record id.
   * @return {Promise}
   */
  get(id) {
    throw new Error("Not Implemented.");
  }

  /**
   * Lists all records from the database.
   *
   * @abstract
   * @param  {Object} params  The filters and order to apply to the results.
   * @return {Promise}
   */
  list(params = { filters: {}, order: "" }) {
    throw new Error("Not Implemented.");
  }

  /**
   * Store the lastModified value.
   *
   * @abstract
   * @param  {Number}  lastModified
   * @return {Promise}
   */
  saveLastModified(lastModified) {
    throw new Error("Not Implemented.");
  }

  /**
   * Retrieve saved lastModified value.
   *
   * @abstract
   * @return {Promise}
   */
  getLastModified() {
    throw new Error("Not Implemented.");
  }

  /**
   * Load a dump of records exported from a server.
   *
   * @abstract
   * @return {Promise}
   */
  loadDump(records) {
    throw new Error("Not Implemented.");
  }
}
exports.default = BaseAdapter;

},{}],6:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.CollectionTransaction = exports.SyncResultObject = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports.recordsEqual = recordsEqual;

var _base = require("./adapters/base");

var _base2 = _interopRequireDefault(_base);

var _IDB = require("./adapters/IDB");

var _IDB2 = _interopRequireDefault(_IDB);

var _utils = require("./utils");

var _uuid = require("uuid");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const RECORD_FIELDS_TO_CLEAN = ["_status"];
const AVAILABLE_HOOKS = ["incoming-changes"];

/**
 * Compare two records omitting local fields and synchronization
 * attributes (like _status and last_modified)
 * @param {Object} a    A record to compare.
 * @param {Object} b    A record to compare.
 * @return {boolean}
 */
function recordsEqual(a, b, localFields = []) {
  const fieldsToClean = RECORD_FIELDS_TO_CLEAN.concat(["last_modified"]).concat(localFields);
  const cleanLocal = r => (0, _utils.omitKeys)(r, fieldsToClean);
  return (0, _utils.deepEqual)(cleanLocal(a), cleanLocal(b));
}

/**
 * Synchronization result object.
 */
class SyncResultObject {
  /**
   * Object default values.
   * @type {Object}
   */
  static get defaults() {
    return {
      ok: true,
      lastModified: null,
      errors: [],
      created: [],
      updated: [],
      deleted: [],
      published: [],
      conflicts: [],
      skipped: [],
      resolved: []
    };
  }

  /**
   * Public constructor.
   */
  constructor() {
    /**
     * Current synchronization result status; becomes `false` when conflicts or
     * errors are registered.
     * @type {Boolean}
     */
    this.ok = true;
    Object.assign(this, SyncResultObject.defaults);
  }

  /**
   * Adds entries for a given result type.
   *
   * @param {String} type    The result type.
   * @param {Array}  entries The result entries.
   * @return {SyncResultObject}
   */
  add(type, entries) {
    if (!Array.isArray(this[type])) {
      return;
    }
    // Deduplicate entries by id. If the values don't have `id` attribute, just
    // keep all.
    const deduplicated = this[type].concat(entries).reduce((acc, cur) => {
      const existing = acc.filter(r => cur.id && r.id ? cur.id != r.id : true);
      return existing.concat(cur);
    }, []);
    this[type] = deduplicated;
    this.ok = this.errors.length + this.conflicts.length === 0;
    return this;
  }

  /**
   * Reinitializes result entries for a given result type.
   *
   * @param  {String} type The result type.
   * @return {SyncResultObject}
   */
  reset(type) {
    this[type] = SyncResultObject.defaults[type];
    this.ok = this.errors.length + this.conflicts.length === 0;
    return this;
  }
}

exports.SyncResultObject = SyncResultObject;
function createUUIDSchema() {
  return {
    generate() {
      return (0, _uuid.v4)();
    },

    validate(id) {
      return (0, _utils.isUUID)(id);
    }
  };
}

function markStatus(record, status) {
  return _extends({}, record, { _status: status });
}

function markDeleted(record) {
  return markStatus(record, "deleted");
}

function markSynced(record) {
  return markStatus(record, "synced");
}

/**
 * Import a remote change into the local database.
 *
 * @param  {IDBTransactionProxy} transaction The transaction handler.
 * @param  {Object}              remote      The remote change object to import.
 * @param  {Array<String>}       localFields The list of fields that remain local.
 * @return {Object}
 */
function importChange(transaction, remote, localFields) {
  const local = transaction.get(remote.id);
  if (!local) {
    // Not found locally but remote change is marked as deleted; skip to
    // avoid recreation.
    if (remote.deleted) {
      return { type: "skipped", data: remote };
    }
    const synced = markSynced(remote);
    transaction.create(synced);
    return { type: "created", data: synced };
  }
  // Compare local and remote, ignoring local fields.
  const isIdentical = recordsEqual(local, remote, localFields);
  // Apply remote changes on local record.
  const synced = _extends({}, local, markSynced(remote));
  // Detect or ignore conflicts if record has also been modified locally.
  if (local._status !== "synced") {
    // Locally deleted, unsynced: scheduled for remote deletion.
    if (local._status === "deleted") {
      return { type: "skipped", data: local };
    }
    if (isIdentical) {
      // If records are identical, import anyway, so we bump the
      // local last_modified value from the server and set record
      // status to "synced".
      transaction.update(synced);
      return { type: "updated", data: { old: local, new: synced } };
    }
    if (local.last_modified !== undefined && local.last_modified === remote.last_modified) {
      // If our local version has the same last_modified as the remote
      // one, this represents an object that corresponds to a resolved
      // conflict. Our local version represents the final output, so
      // we keep that one. (No transaction operation to do.)
      // But if our last_modified is undefined,
      // that means we've created the same object locally as one on
      // the server, which *must* be a conflict.
      return { type: "void" };
    }
    return {
      type: "conflicts",
      data: { type: "incoming", local: local, remote: remote }
    };
  }
  // Local record was synced.
  if (remote.deleted) {
    transaction.delete(remote.id);
    return { type: "deleted", data: local };
  }
  // Import locally.
  transaction.update(synced);
  // if identical, simply exclude it from all SyncResultObject lists
  const type = isIdentical ? "void" : "updated";
  return { type, data: { old: local, new: synced } };
}

/**
 * Abstracts a collection of records stored in the local database, providing
 * CRUD operations and synchronization helpers.
 */
class Collection {
  /**
   * Constructor.
   *
   * Options:
   * - `{BaseAdapter} adapter` The DB adapter (default: `IDB`)
   * - `{String} dbPrefix`     The DB name prefix (default: `""`)
   *
   * @param  {String} bucket  The bucket identifier.
   * @param  {String} name    The collection name.
   * @param  {Api}    api     The Api instance.
   * @param  {Object} options The options object.
   */
  constructor(bucket, name, api, options = {}) {
    this._bucket = bucket;
    this._name = name;
    this._lastModified = null;

    const DBAdapter = options.adapter || _IDB2.default;
    if (!DBAdapter) {
      throw new Error("No adapter provided");
    }
    const dbPrefix = options.dbPrefix || "";
    const db = new DBAdapter(`${dbPrefix}${bucket}/${name}`, options.adapterOptions);
    if (!(db instanceof _base2.default)) {
      throw new Error("Unsupported adapter.");
    }
    // public properties
    /**
     * The db adapter instance
     * @type {BaseAdapter}
     */
    this.db = db;
    /**
     * The Api instance.
     * @type {KintoClient}
     */
    this.api = api;
    /**
     * The event emitter instance.
     * @type {EventEmitter}
     */
    this.events = options.events;
    /**
     * The IdSchema instance.
     * @type {Object}
     */
    this.idSchema = this._validateIdSchema(options.idSchema);
    /**
     * The list of remote transformers.
     * @type {Array}
     */
    this.remoteTransformers = this._validateRemoteTransformers(options.remoteTransformers);
    /**
     * The list of hooks.
     * @type {Object}
     */
    this.hooks = this._validateHooks(options.hooks);
    /**
     * The list of fields names that will remain local.
     * @type {Array}
     */
    this.localFields = options.localFields || [];
  }

  /**
   * The collection name.
   * @type {String}
   */
  get name() {
    return this._name;
  }

  /**
   * The bucket name.
   * @type {String}
   */
  get bucket() {
    return this._bucket;
  }

  /**
   * The last modified timestamp.
   * @type {Number}
   */
  get lastModified() {
    return this._lastModified;
  }

  /**
   * Synchronization strategies. Available strategies are:
   *
   * - `MANUAL`: Conflicts will be reported in a dedicated array.
   * - `SERVER_WINS`: Conflicts are resolved using remote data.
   * - `CLIENT_WINS`: Conflicts are resolved using local data.
   *
   * @type {Object}
   */
  static get strategy() {
    return {
      CLIENT_WINS: "client_wins",
      SERVER_WINS: "server_wins",
      MANUAL: "manual"
    };
  }

  /**
   * Validates an idSchema.
   *
   * @param  {Object|undefined} idSchema
   * @return {Object}
   */
  _validateIdSchema(idSchema) {
    if (typeof idSchema === "undefined") {
      return createUUIDSchema();
    }
    if (typeof idSchema !== "object") {
      throw new Error("idSchema must be an object.");
    } else if (typeof idSchema.generate !== "function") {
      throw new Error("idSchema must provide a generate function.");
    } else if (typeof idSchema.validate !== "function") {
      throw new Error("idSchema must provide a validate function.");
    }
    return idSchema;
  }

  /**
   * Validates a list of remote transformers.
   *
   * @param  {Array|undefined} remoteTransformers
   * @return {Array}
   */
  _validateRemoteTransformers(remoteTransformers) {
    if (typeof remoteTransformers === "undefined") {
      return [];
    }
    if (!Array.isArray(remoteTransformers)) {
      throw new Error("remoteTransformers should be an array.");
    }
    return remoteTransformers.map(transformer => {
      if (typeof transformer !== "object") {
        throw new Error("A transformer must be an object.");
      } else if (typeof transformer.encode !== "function") {
        throw new Error("A transformer must provide an encode function.");
      } else if (typeof transformer.decode !== "function") {
        throw new Error("A transformer must provide a decode function.");
      }
      return transformer;
    });
  }

  /**
   * Validate the passed hook is correct.
   *
   * @param {Array|undefined} hook.
   * @return {Array}
   **/
  _validateHook(hook) {
    if (!Array.isArray(hook)) {
      throw new Error("A hook definition should be an array of functions.");
    }
    return hook.map(fn => {
      if (typeof fn !== "function") {
        throw new Error("A hook definition should be an array of functions.");
      }
      return fn;
    });
  }

  /**
   * Validates a list of hooks.
   *
   * @param  {Object|undefined} hooks
   * @return {Object}
   */
  _validateHooks(hooks) {
    if (typeof hooks === "undefined") {
      return {};
    }
    if (Array.isArray(hooks)) {
      throw new Error("hooks should be an object, not an array.");
    }
    if (typeof hooks !== "object") {
      throw new Error("hooks should be an object.");
    }

    const validatedHooks = {};

    for (const hook in hooks) {
      if (AVAILABLE_HOOKS.indexOf(hook) === -1) {
        throw new Error("The hook should be one of " + AVAILABLE_HOOKS.join(", "));
      }
      validatedHooks[hook] = this._validateHook(hooks[hook]);
    }
    return validatedHooks;
  }

  /**
   * Deletes every records in the current collection and marks the collection as
   * never synced.
   *
   * @return {Promise}
   */
  clear() {
    var _this = this;

    return _asyncToGenerator(function* () {
      yield _this.db.clear();
      yield _this.db.saveLastModified(null);
      return { data: [], permissions: {} };
    })();
  }

  /**
   * Encodes a record.
   *
   * @param  {String} type   Either "remote" or "local".
   * @param  {Object} record The record object to encode.
   * @return {Promise}
   */
  _encodeRecord(type, record) {
    if (!this[`${type}Transformers`].length) {
      return Promise.resolve(record);
    }
    return (0, _utils.waterfall)(this[`${type}Transformers`].map(transformer => {
      return record => transformer.encode(record);
    }), record);
  }

  /**
   * Decodes a record.
   *
   * @param  {String} type   Either "remote" or "local".
   * @param  {Object} record The record object to decode.
   * @return {Promise}
   */
  _decodeRecord(type, record) {
    if (!this[`${type}Transformers`].length) {
      return Promise.resolve(record);
    }
    return (0, _utils.waterfall)(this[`${type}Transformers`].reverse().map(transformer => {
      return record => transformer.decode(record);
    }), record);
  }

  /**
   * Adds a record to the local database, asserting that none
   * already exist with this ID.
   *
   * Note: If either the `useRecordId` or `synced` options are true, then the
   * record object must contain the id field to be validated. If none of these
   * options are true, an id is generated using the current IdSchema; in this
   * case, the record passed must not have an id.
   *
   * Options:
   * - {Boolean} synced       Sets record status to "synced" (default: `false`).
   * - {Boolean} useRecordId  Forces the `id` field from the record to be used,
   *                          instead of one that is generated automatically
   *                          (default: `false`).
   *
   * @param  {Object} record
   * @param  {Object} options
   * @return {Promise}
   */
  create(record, options = { useRecordId: false, synced: false }) {
    // Validate the record and its ID (if any), even though this
    // validation is also done in the CollectionTransaction method,
    // because we need to pass the ID to preloadIds.
    const reject = msg => Promise.reject(new Error(msg));
    if (typeof record !== "object") {
      return reject("Record is not an object.");
    }
    if ((options.synced || options.useRecordId) && !record.hasOwnProperty("id")) {
      return reject("Missing required Id; synced and useRecordId options require one");
    }
    if (!options.synced && !options.useRecordId && record.hasOwnProperty("id")) {
      return reject("Extraneous Id; can't create a record having one set.");
    }
    const newRecord = _extends({}, record, {
      id: options.synced || options.useRecordId ? record.id : this.idSchema.generate(),
      _status: options.synced ? "synced" : "created"
    });
    if (!this.idSchema.validate(newRecord.id)) {
      return reject(`Invalid Id: ${newRecord.id}`);
    }
    return this.execute(txn => txn.create(newRecord), {
      preloadIds: [newRecord.id]
    }).catch(err => {
      if (options.useRecordId) {
        throw new Error("Couldn't create record. It may have been virtually deleted.");
      }
      throw err;
    });
  }

  /**
   * Like {@link CollectionTransaction#update}, but wrapped in its own transaction.
   *
   * Options:
   * - {Boolean} synced: Sets record status to "synced" (default: false)
   * - {Boolean} patch:  Extends the existing record instead of overwriting it
   *   (default: false)
   *
   * @param  {Object} record
   * @param  {Object} options
   * @return {Promise}
   */
  update(record, options = { synced: false, patch: false }) {
    // Validate the record and its ID, even though this validation is
    // also done in the CollectionTransaction method, because we need
    // to pass the ID to preloadIds.
    if (typeof record !== "object") {
      return Promise.reject(new Error("Record is not an object."));
    }
    if (!record.hasOwnProperty("id")) {
      return Promise.reject(new Error("Cannot update a record missing id."));
    }
    if (!this.idSchema.validate(record.id)) {
      return Promise.reject(new Error(`Invalid Id: ${record.id}`));
    }

    return this.execute(txn => txn.update(record, options), {
      preloadIds: [record.id]
    });
  }

  /**
   * Like {@link CollectionTransaction#upsert}, but wrapped in its own transaction.
   *
   * @param  {Object} record
   * @return {Promise}
   */
  upsert(record) {
    // Validate the record and its ID, even though this validation is
    // also done in the CollectionTransaction method, because we need
    // to pass the ID to preloadIds.
    if (typeof record !== "object") {
      return Promise.reject(new Error("Record is not an object."));
    }
    if (!record.hasOwnProperty("id")) {
      return Promise.reject(new Error("Cannot update a record missing id."));
    }
    if (!this.idSchema.validate(record.id)) {
      return Promise.reject(new Error(`Invalid Id: ${record.id}`));
    }

    return this.execute(txn => txn.upsert(record), { preloadIds: [record.id] });
  }

  /**
   * Like {@link CollectionTransaction#get}, but wrapped in its own transaction.
   *
   * Options:
   * - {Boolean} includeDeleted: Include virtually deleted records.
   *
   * @param  {String} id
   * @param  {Object} options
   * @return {Promise}
   */
  get(id, options = { includeDeleted: false }) {
    return this.execute(txn => txn.get(id, options), { preloadIds: [id] });
  }

  /**
   * Like {@link CollectionTransaction#getAny}, but wrapped in its own transaction.
   *
   * @param  {String} id
   * @return {Promise}
   */
  getAny(id) {
    return this.execute(txn => txn.getAny(id), { preloadIds: [id] });
  }

  /**
   * Same as {@link Collection#delete}, but wrapped in its own transaction.
   *
   * Options:
   * - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
   *   update its `_status` attribute to `deleted` instead (default: true)
   *
   * @param  {String} id       The record's Id.
   * @param  {Object} options  The options object.
   * @return {Promise}
   */
  delete(id, options = { virtual: true }) {
    return this.execute(transaction => {
      return transaction.delete(id, options);
    }, { preloadIds: [id] });
  }

  /**
   * The same as {@link CollectionTransaction#deleteAny}, but wrapped
   * in its own transaction.
   *
   * @param  {String} id       The record's Id.
   * @return {Promise}
   */
  deleteAny(id) {
    return this.execute(txn => txn.deleteAny(id), { preloadIds: [id] });
  }

  /**
   * Lists records from the local database.
   *
   * Params:
   * - {Object} filters Filter the results (default: `{}`).
   * - {String} order   The order to apply   (default: `-last_modified`).
   *
   * Options:
   * - {Boolean} includeDeleted: Include virtually deleted records.
   *
   * @param  {Object} params  The filters and order to apply to the results.
   * @param  {Object} options The options object.
   * @return {Promise}
   */
  list(params = {}, options = { includeDeleted: false }) {
    var _this2 = this;

    return _asyncToGenerator(function* () {
      params = _extends({ order: "-last_modified", filters: {} }, params);
      const results = yield _this2.db.list(params);
      let data = results;
      if (!options.includeDeleted) {
        data = results.filter(function (record) {
          return record._status !== "deleted";
        });
      }
      return { data, permissions: {} };
    })();
  }

  /**
   * Imports remote changes into the local database.
   * This method is in charge of detecting the conflicts, and resolve them
   * according to the specified strategy.
   * @param  {SyncResultObject} syncResultObject The sync result object.
   * @param  {Array}            decodedChanges   The list of changes to import in the local database.
   * @param  {String}           strategy         The {@link Collection.strategy} (default: MANUAL)
   * @return {Promise}
   */
  importChanges(syncResultObject, decodedChanges, strategy = Collection.strategy.MANUAL) {
    var _this3 = this;

    return _asyncToGenerator(function* () {
      // Retrieve records matching change ids.
      try {
        const { imports, resolved } = yield _this3.db.execute(function (transaction) {
          const imports = decodedChanges.map(function (remote) {
            // Store remote change into local database.
            return importChange(transaction, remote, _this3.localFields);
          });
          const conflicts = imports.filter(function (i) {
            return i.type === "conflicts";
          }).map(function (i) {
            return i.data;
          });
          const resolved = _this3._handleConflicts(transaction, conflicts, strategy);
          return { imports, resolved };
        }, { preload: decodedChanges.map(function (record) {
            return record.id;
          }) });

        // Lists of created/updated/deleted records
        imports.forEach(function ({ type, data }) {
          return syncResultObject.add(type, data);
        });

        // Automatically resolved conflicts (if not manual)
        if (resolved.length > 0) {
          syncResultObject.reset("conflicts").add("resolved", resolved);
        }
      } catch (err) {
        const data = {
          type: "incoming",
          message: err.message,
          stack: err.stack
        };
        // XXX one error of the whole transaction instead of per atomic op
        syncResultObject.add("errors", data);
      }

      return syncResultObject;
    })();
  }

  /**
   * Imports the responses of pushed changes into the local database.
   * Basically it stores the timestamp assigned by the server into the local
   * database.
   * @param  {SyncResultObject} syncResultObject The sync result object.
   * @param  {Array}            toApplyLocally   The list of changes to import in the local database.
   * @param  {Array}            conflicts        The list of conflicts that have to be resolved.
   * @param  {String}           strategy         The {@link Collection.strategy}.
   * @return {Promise}
   */
  _applyPushedResults(syncResultObject, toApplyLocally, conflicts, strategy = Collection.strategy.MANUAL) {
    var _this4 = this;

    return _asyncToGenerator(function* () {
      const toDeleteLocally = toApplyLocally.filter(function (r) {
        return r.deleted;
      });
      const toUpdateLocally = toApplyLocally.filter(function (r) {
        return !r.deleted;
      });

      const { published, resolved } = yield _this4.db.execute(function (transaction) {
        const updated = toUpdateLocally.map(function (record) {
          const synced = markSynced(record);
          transaction.update(synced);
          return synced;
        });
        const deleted = toDeleteLocally.map(function (record) {
          transaction.delete(record.id);
          // Amend result data with the deleted attribute set
          return { id: record.id, deleted: true };
        });
        const published = updated.concat(deleted);
        // Handle conflicts, if any
        const resolved = _this4._handleConflicts(transaction, conflicts, strategy);
        return { published, resolved };
      });

      syncResultObject.add("published", published);

      if (resolved.length > 0) {
        syncResultObject.reset("conflicts").reset("resolved").add("resolved", resolved);
      }
      return syncResultObject;
    })();
  }

  /**
   * Handles synchronization conflicts according to specified strategy.
   *
   * @param  {SyncResultObject} result    The sync result object.
   * @param  {String}           strategy  The {@link Collection.strategy}.
   * @return {Promise<Array<Object>>} The resolved conflicts, as an
   *    array of {accepted, rejected} objects
   */
  _handleConflicts(transaction, conflicts, strategy) {
    if (strategy === Collection.strategy.MANUAL) {
      return [];
    }
    return conflicts.map(conflict => {
      const resolution = strategy === Collection.strategy.CLIENT_WINS ? conflict.local : conflict.remote;
      const rejected = strategy === Collection.strategy.CLIENT_WINS ? conflict.remote : conflict.local;
      let accepted, status, id;
      if (resolution === null) {
        // We "resolved" with the server-side deletion. Delete locally.
        // This only happens during SERVER_WINS because the local
        // version of a record can never be null.
        // We can get "null" from the remote side if we got a conflict
        // and there is no remote version available; see kinto-http.js
        // batch.js:aggregate.
        transaction.delete(conflict.local.id);
        accepted = null;
        // The record was deleted, but that status is "synced" with
        // the server, so we don't need to push the change.
        status = "synced";
        id = conflict.local.id;
      } else {
        const updated = this._resolveRaw(conflict, resolution);
        transaction.update(updated);
        accepted = updated;
        status = updated._status;
        id = updated.id;
      }
      return { rejected, accepted, id, _status: status };
    });
  }

  /**
   * Execute a bunch of operations in a transaction.
   *
   * This transaction should be atomic -- either all of its operations
   * will succeed, or none will.
   *
   * The argument to this function is itself a function which will be
   * called with a {@link CollectionTransaction}. Collection methods
   * are available on this transaction, but instead of returning
   * promises, they are synchronous. execute() returns a Promise whose
   * value will be the return value of the provided function.
   *
   * Most operations will require access to the record itself, which
   * must be preloaded by passing its ID in the preloadIds option.
   *
   * Options:
   * - {Array} preloadIds: list of IDs to fetch at the beginning of
   *   the transaction
   *
   * @return {Promise} Resolves with the result of the given function
   *    when the transaction commits.
   */
  execute(doOperations, { preloadIds = [] } = {}) {
    for (const id of preloadIds) {
      if (!this.idSchema.validate(id)) {
        return Promise.reject(Error(`Invalid Id: ${id}`));
      }
    }

    return this.db.execute(transaction => {
      const txn = new CollectionTransaction(this, transaction);
      const result = doOperations(txn);
      txn.emitEvents();
      return result;
    }, { preload: preloadIds });
  }

  /**
   * Resets the local records as if they were never synced; existing records are
   * marked as newly created, deleted records are dropped.
   *
   * A next call to {@link Collection.sync} will thus republish the whole
   * content of the local collection to the server.
   *
   * @return {Promise} Resolves with the number of processed records.
   */
  resetSyncStatus() {
    var _this5 = this;

    return _asyncToGenerator(function* () {
      const unsynced = yield _this5.list({ filters: { _status: ["deleted", "synced"] }, order: "" }, { includeDeleted: true });
      yield _this5.db.execute(function (transaction) {
        unsynced.data.forEach(function (record) {
          if (record._status === "deleted") {
            // Garbage collect deleted records.
            transaction.delete(record.id);
          } else {
            // Records that were synced become «created».
            transaction.update(_extends({}, record, {
              last_modified: undefined,
              _status: "created"
            }));
          }
        });
      });
      _this5._lastModified = null;
      yield _this5.db.saveLastModified(null);
      return unsynced.data.length;
    })();
  }

  /**
   * Returns an object containing two lists:
   *
   * - `toDelete`: unsynced deleted records we can safely delete;
   * - `toSync`: local updates to send to the server.
   *
   * @return {Promise}
   */
  gatherLocalChanges() {
    var _this6 = this;

    return _asyncToGenerator(function* () {
      const unsynced = yield _this6.list({
        filters: { _status: ["created", "updated"] },
        order: ""
      });
      const deleted = yield _this6.list({ filters: { _status: "deleted" }, order: "" }, { includeDeleted: true });

      return yield Promise.all(unsynced.data.concat(deleted.data).map(_this6._encodeRecord.bind(_this6, "remote")));
    })();
  }

  /**
   * Fetch remote changes, import them to the local database, and handle
   * conflicts according to `options.strategy`. Then, updates the passed
   * {@link SyncResultObject} with import results.
   *
   * Options:
   * - {String} strategy: The selected sync strategy.
   *
   * @param  {KintoClient.Collection} client           Kinto client Collection instance.
   * @param  {SyncResultObject}       syncResultObject The sync result object.
   * @param  {Object}                 options
   * @return {Promise}
   */
  pullChanges(client, syncResultObject, options = {}) {
    var _this7 = this;

    return _asyncToGenerator(function* () {
      if (!syncResultObject.ok) {
        return syncResultObject;
      }

      const since = _this7.lastModified ? _this7.lastModified : yield _this7.db.getLastModified();

      options = _extends({
        strategy: Collection.strategy.MANUAL,
        lastModified: since,
        headers: {}
      }, options);

      // Optionally ignore some records when pulling for changes.
      // (avoid redownloading our own changes on last step of #sync())
      let filters;
      if (options.exclude) {
        // Limit the list of excluded records to the first 50 records in order
        // to remain under de-facto URL size limit (~2000 chars).
        // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184#417184
        const exclude_id = options.exclude.slice(0, 50).map(function (r) {
          return r.id;
        }).join(",");
        filters = { exclude_id };
      }
      // First fetch remote changes from the server
      const { data, last_modified } = yield client.listRecords({
        // Since should be ETag (see https://github.com/Kinto/kinto.js/issues/356)
        since: options.lastModified ? `${options.lastModified}` : undefined,
        headers: options.headers,
        retry: options.retry,
        filters
      });
      // last_modified is the ETag header value (string).
      // For retro-compatibility with first kinto.js versions
      // parse it to integer.
      const unquoted = last_modified ? parseInt(last_modified, 10) : undefined;

      // Check if server was flushed.
      // This is relevant for the Kinto demo server
      // (and thus for many new comers).
      const localSynced = options.lastModified;
      const serverChanged = unquoted > options.lastModified;
      const emptyCollection = data.length === 0;
      if (!options.exclude && localSynced && serverChanged && emptyCollection) {
        throw Error("Server has been flushed.");
      }

      syncResultObject.lastModified = unquoted;

      // Decode incoming changes.
      const decodedChanges = yield Promise.all(data.map(function (change) {
        return _this7._decodeRecord("remote", change);
      }));
      // Hook receives decoded records.
      const payload = { lastModified: unquoted, changes: decodedChanges };
      const afterHooks = yield _this7.applyHook("incoming-changes", payload);

      // No change, nothing to import.
      if (afterHooks.changes.length > 0) {
        // Reflect these changes locally
        yield _this7.importChanges(syncResultObject, afterHooks.changes, options.strategy);
      }
      return syncResultObject;
    })();
  }

  applyHook(hookName, payload) {
    if (typeof this.hooks[hookName] == "undefined") {
      return Promise.resolve(payload);
    }
    return (0, _utils.waterfall)(this.hooks[hookName].map(hook => {
      return record => {
        const result = hook(payload, this);
        const resultThenable = result && typeof result.then === "function";
        const resultChanges = result && result.hasOwnProperty("changes");
        if (!(resultThenable || resultChanges)) {
          throw new Error(`Invalid return value for hook: ${JSON.stringify(result)} has no 'then()' or 'changes' properties`);
        }
        return result;
      };
    }), payload);
  }

  /**
   * Publish local changes to the remote server and updates the passed
   * {@link SyncResultObject} with publication results.
   *
   * @param  {KintoClient.Collection} client           Kinto client Collection instance.
   * @param  {SyncResultObject}       syncResultObject The sync result object.
   * @param  {Object}                 changes          The change object.
   * @param  {Array}                  changes.toDelete The list of records to delete.
   * @param  {Array}                  changes.toSync   The list of records to create/update.
   * @param  {Object}                 options          The options object.
   * @return {Promise}
   */
  pushChanges(client, changes, syncResultObject, options = {}) {
    var _this8 = this;

    return _asyncToGenerator(function* () {
      if (!syncResultObject.ok) {
        return syncResultObject;
      }
      const safe = !options.strategy || options.strategy !== Collection.CLIENT_WINS;
      const toDelete = changes.filter(function (r) {
        return r._status == "deleted";
      });
      const toSync = changes.filter(function (r) {
        return r._status != "deleted";
      });

      // Perform a batch request with every changes.
      const synced = yield client.batch(function (batch) {
        toDelete.forEach(function (r) {
          // never published locally deleted records should not be pusblished
          if (r.last_modified) {
            batch.deleteRecord(r);
          }
        });
        toSync.forEach(function (r) {
          // Clean local fields (like _status) before sending to server.
          const published = _this8.cleanLocalFields(r);
          if (r._status === "created") {
            batch.createRecord(published);
          } else {
            batch.updateRecord(published);
          }
        });
      }, {
        headers: options.headers,
        retry: options.retry,
        safe,
        aggregate: true
      });

      // Store outgoing errors into sync result object
      syncResultObject.add("errors", synced.errors.map(function (e) {
        return _extends({}, e, { type: "outgoing" });
      }));

      // Store outgoing conflicts into sync result object
      const conflicts = [];
      for (const _ref of synced.conflicts) {
        const { type, local, remote } = _ref;

        // Note: we ensure that local data are actually available, as they may
        // be missing in the case of a published deletion.
        const safeLocal = local && local.data || { id: remote.id };
        const realLocal = yield _this8._decodeRecord("remote", safeLocal);
        // We can get "null" from the remote side if we got a conflict
        // and there is no remote version available; see kinto-http.js
        // batch.js:aggregate.
        const realRemote = remote && (yield _this8._decodeRecord("remote", remote));
        const conflict = { type, local: realLocal, remote: realRemote };
        conflicts.push(conflict);
      }
      syncResultObject.add("conflicts", conflicts);

      // Records that must be deleted are either deletions that were pushed
      // to server (published) or deleted records that were never pushed (skipped).
      const missingRemotely = synced.skipped.map(function (r) {
        return _extends({}, r, { deleted: true });
      });

      // For created and updated records, the last_modified coming from server
      // will be stored locally.
      // Reflect publication results locally using the response from
      // the batch request.
      const published = synced.published.map(function (c) {
        return c.data;
      });
      const toApplyLocally = published.concat(missingRemotely);

      // Apply the decode transformers, if any
      const decoded = yield Promise.all(toApplyLocally.map(function (record) {
        return _this8._decodeRecord("remote", record);
      }));

      // We have to update the local records with the responses of the server
      // (eg. last_modified values etc.).
      if (decoded.length > 0 || conflicts.length > 0) {
        yield _this8._applyPushedResults(syncResultObject, decoded, conflicts, options.strategy);
      }

      return syncResultObject;
    })();
  }

  /**
   * Return a copy of the specified record without the local fields.
   *
   * @param  {Object} record  A record with potential local fields.
   * @return {Object}
   */
  cleanLocalFields(record) {
    const localKeys = RECORD_FIELDS_TO_CLEAN.concat(this.localFields);
    return (0, _utils.omitKeys)(record, localKeys);
  }

  /**
   * Resolves a conflict, updating local record according to proposed
   * resolution — keeping remote record `last_modified` value as a reference for
   * further batch sending.
   *
   * @param  {Object} conflict   The conflict object.
   * @param  {Object} resolution The proposed record.
   * @return {Promise}
   */
  resolve(conflict, resolution) {
    return this.db.execute(transaction => {
      const updated = this._resolveRaw(conflict, resolution);
      transaction.update(updated);
      return { data: updated, permissions: {} };
    });
  }

  /**
   * @private
   */
  _resolveRaw(conflict, resolution) {
    const resolved = _extends({}, resolution, {
      // Ensure local record has the latest authoritative timestamp
      last_modified: conflict.remote && conflict.remote.last_modified
    });
    // If the resolution object is strictly equal to the
    // remote record, then we can mark it as synced locally.
    // Otherwise, mark it as updated (so that the resolution is pushed).
    const synced = (0, _utils.deepEqual)(resolved, conflict.remote);
    return markStatus(resolved, synced ? "synced" : "updated");
  }

  /**
   * Synchronize remote and local data. The promise will resolve with a
   * {@link SyncResultObject}, though will reject:
   *
   * - if the server is currently backed off;
   * - if the server has been detected flushed.
   *
   * Options:
   * - {Object} headers: HTTP headers to attach to outgoing requests.
   * - {Number} retry: Number of retries when server fails to process the request (default: 1).
   * - {Collection.strategy} strategy: See {@link Collection.strategy}.
   * - {Boolean} ignoreBackoff: Force synchronization even if server is currently
   *   backed off.
   * - {String} bucket: The remove bucket id to use (default: null)
   * - {String} collection: The remove collection id to use (default: null)
   * - {String} remote The remote Kinto server endpoint to use (default: null).
   *
   * @param  {Object} options Options.
   * @return {Promise}
   * @throws {Error} If an invalid remote option is passed.
   */
  sync(options = {
    strategy: Collection.strategy.MANUAL,
    headers: {},
    retry: 1,
    ignoreBackoff: false,
    bucket: null,
    collection: null,
    remote: null
  }) {
    var _this9 = this;

    return _asyncToGenerator(function* () {
      options = _extends({}, options, {
        bucket: options.bucket || _this9.bucket,
        collection: options.collection || _this9.name
      });

      const previousRemote = _this9.api.remote;
      if (options.remote) {
        // Note: setting the remote ensures it's valid, throws when invalid.
        _this9.api.remote = options.remote;
      }
      if (!options.ignoreBackoff && _this9.api.backoff > 0) {
        const seconds = Math.ceil(_this9.api.backoff / 1000);
        return Promise.reject(new Error(`Server is asking clients to back off; retry in ${seconds}s or use the ignoreBackoff option.`));
      }

      const client = _this9.api.bucket(options.bucket).collection(options.collection);

      const result = new SyncResultObject();
      try {
        // Fetch last changes from the server.
        yield _this9.pullChanges(client, result, options);
        const { lastModified } = result;

        // Fetch local changes
        const toSync = yield _this9.gatherLocalChanges();

        // Publish local changes and pull local resolutions
        yield _this9.pushChanges(client, toSync, result, options);

        // Publish local resolution of push conflicts to server (on CLIENT_WINS)
        const resolvedUnsynced = result.resolved.filter(function (r) {
          return r._status !== "synced";
        });
        if (resolvedUnsynced.length > 0) {
          const resolvedEncoded = yield Promise.all(resolvedUnsynced.map(function (resolution) {
            let record = resolution.accepted;
            if (record === null) {
              record = { id: resolution.id, _status: resolution._status };
            }
            return _this9._encodeRecord("remote", record);
          }));
          yield _this9.pushChanges(client, resolvedEncoded, result, options);
        }
        // Perform a last pull to catch changes that occured after the last pull,
        // while local changes were pushed. Do not do it nothing was pushed.
        if (result.published.length > 0) {
          // Avoid redownloading our own changes during the last pull.
          const pullOpts = _extends({}, options, {
            lastModified,
            exclude: result.published
          });
          yield _this9.pullChanges(client, result, pullOpts);
        }

        // Don't persist lastModified value if any conflict or error occured
        if (result.ok) {
          // No conflict occured, persist collection's lastModified value
          _this9._lastModified = yield _this9.db.saveLastModified(result.lastModified);
        }
      } catch (e) {
        _this9.events.emit("sync:error", _extends({}, options, { error: e }));
        throw e;
      } finally {
        // Ensure API default remote is reverted if a custom one's been used
        _this9.api.remote = previousRemote;
      }
      _this9.events.emit("sync:success", _extends({}, options, { result }));
      return result;
    })();
  }

  /**
   * Load a list of records already synced with the remote server.
   *
   * The local records which are unsynced or whose timestamp is either missing
   * or superior to those being loaded will be ignored.
   *
   * @param  {Array} records The previously exported list of records to load.
   * @return {Promise} with the effectively imported records.
   */
  loadDump(records) {
    var _this10 = this;

    return _asyncToGenerator(function* () {
      if (!Array.isArray(records)) {
        throw new Error("Records is not an array.");
      }

      for (const record of records) {
        if (!record.hasOwnProperty("id") || !_this10.idSchema.validate(record.id)) {
          throw new Error("Record has invalid ID: " + JSON.stringify(record));
        }

        if (!record.last_modified) {
          throw new Error("Record has no last_modified value: " + JSON.stringify(record));
        }
      }

      // Fetch all existing records from local database,
      // and skip those who are newer or not marked as synced.

      // XXX filter by status / ids in records

      const { data } = yield _this10.list({}, { includeDeleted: true });
      const existingById = data.reduce(function (acc, record) {
        acc[record.id] = record;
        return acc;
      }, {});

      const newRecords = records.filter(function (record) {
        const localRecord = existingById[record.id];
        const shouldKeep =
        // No local record with this id.
        localRecord === undefined ||
        // Or local record is synced
        localRecord._status === "synced" &&
        // And was synced from server
        localRecord.last_modified !== undefined &&
        // And is older than imported one.
        record.last_modified > localRecord.last_modified;
        return shouldKeep;
      });

      return yield _this10.db.loadDump(newRecords.map(markSynced));
    })();
  }
}

exports.default = Collection; /**
                               * A Collection-oriented wrapper for an adapter's transaction.
                               *
                               * This defines the high-level functions available on a collection.
                               * The collection itself offers functions of the same name. These will
                               * perform just one operation in its own transaction.
                               */

class CollectionTransaction {
  constructor(collection, adapterTransaction) {
    this.collection = collection;
    this.adapterTransaction = adapterTransaction;

    this._events = [];
  }

  _queueEvent(action, payload) {
    this._events.push({ action, payload });
  }

  /**
   * Emit queued events, to be called once every transaction operations have
   * been executed successfully.
   */
  emitEvents() {
    for (const _ref2 of this._events) {
      const { action, payload } = _ref2;

      this.collection.events.emit(action, payload);
    }
    if (this._events.length > 0) {
      const targets = this._events.map(({ action, payload }) => _extends({
        action
      }, payload));
      this.collection.events.emit("change", { targets });
    }
    this._events = [];
  }

  /**
   * Retrieve a record by its id from the local database, or
   * undefined if none exists.
   *
   * This will also return virtually deleted records.
   *
   * @param  {String} id
   * @return {Object}
   */
  getAny(id) {
    const record = this.adapterTransaction.get(id);
    return { data: record, permissions: {} };
  }

  /**
   * Retrieve a record by its id from the local database.
   *
   * Options:
   * - {Boolean} includeDeleted: Include virtually deleted records.
   *
   * @param  {String} id
   * @param  {Object} options
   * @return {Object}
   */
  get(id, options = { includeDeleted: false }) {
    const res = this.getAny(id);
    if (!res.data || !options.includeDeleted && res.data._status === "deleted") {
      throw new Error(`Record with id=${id} not found.`);
    }

    return res;
  }

  /**
   * Deletes a record from the local database.
   *
   * Options:
   * - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
   *   update its `_status` attribute to `deleted` instead (default: true)
   *
   * @param  {String} id       The record's Id.
   * @param  {Object} options  The options object.
   * @return {Object}
   */
  delete(id, options = { virtual: true }) {
    // Ensure the record actually exists.
    const existing = this.adapterTransaction.get(id);
    const alreadyDeleted = existing && existing._status == "deleted";
    if (!existing || alreadyDeleted && options.virtual) {
      throw new Error(`Record with id=${id} not found.`);
    }
    // Virtual updates status.
    if (options.virtual) {
      this.adapterTransaction.update(markDeleted(existing));
    } else {
      // Delete for real.
      this.adapterTransaction.delete(id);
    }
    this._queueEvent("delete", { data: existing });
    return { data: existing, permissions: {} };
  }

  /**
   * Deletes a record from the local database, if any exists.
   * Otherwise, do nothing.
   *
   * @param  {String} id       The record's Id.
   * @return {Object}
   */
  deleteAny(id) {
    const existing = this.adapterTransaction.get(id);
    if (existing) {
      this.adapterTransaction.update(markDeleted(existing));
      this._queueEvent("delete", { data: existing });
    }
    return { data: _extends({ id }, existing), deleted: !!existing, permissions: {} };
  }

  /**
   * Adds a record to the local database, asserting that none
   * already exist with this ID.
   *
   * @param  {Object} record, which must contain an ID
   * @return {Object}
   */
  create(record) {
    if (typeof record !== "object") {
      throw new Error("Record is not an object.");
    }
    if (!record.hasOwnProperty("id")) {
      throw new Error("Cannot create a record missing id");
    }
    if (!this.collection.idSchema.validate(record.id)) {
      throw new Error(`Invalid Id: ${record.id}`);
    }

    this.adapterTransaction.create(record);
    this._queueEvent("create", { data: record });
    return { data: record, permissions: {} };
  }

  /**
   * Updates a record from the local database.
   *
   * Options:
   * - {Boolean} synced: Sets record status to "synced" (default: false)
   * - {Boolean} patch:  Extends the existing record instead of overwriting it
   *   (default: false)
   *
   * @param  {Object} record
   * @param  {Object} options
   * @return {Object}
   */
  update(record, options = { synced: false, patch: false }) {
    if (typeof record !== "object") {
      throw new Error("Record is not an object.");
    }
    if (!record.hasOwnProperty("id")) {
      throw new Error("Cannot update a record missing id.");
    }
    if (!this.collection.idSchema.validate(record.id)) {
      throw new Error(`Invalid Id: ${record.id}`);
    }

    const oldRecord = this.adapterTransaction.get(record.id);
    if (!oldRecord) {
      throw new Error(`Record with id=${record.id} not found.`);
    }
    const newRecord = options.patch ? _extends({}, oldRecord, record) : record;
    const updated = this._updateRaw(oldRecord, newRecord, options);
    this.adapterTransaction.update(updated);
    this._queueEvent("update", { data: updated, oldRecord });
    return { data: updated, oldRecord, permissions: {} };
  }

  /**
   * Lower-level primitive for updating a record while respecting
   * _status and last_modified.
   *
   * @param  {Object} oldRecord: the record retrieved from the DB
   * @param  {Object} newRecord: the record to replace it with
   * @return {Object}
   */
  _updateRaw(oldRecord, newRecord, { synced = false } = {}) {
    const updated = _extends({}, newRecord);
    // Make sure to never loose the existing timestamp.
    if (oldRecord && oldRecord.last_modified && !updated.last_modified) {
      updated.last_modified = oldRecord.last_modified;
    }
    // If only local fields have changed, then keep record as synced.
    // If status is created, keep record as created.
    // If status is deleted, mark as updated.
    const isIdentical = oldRecord && recordsEqual(oldRecord, updated, this.localFields);
    const keepSynced = isIdentical && oldRecord._status == "synced";
    const neverSynced = !oldRecord || oldRecord && oldRecord._status == "created";
    const newStatus = keepSynced || synced ? "synced" : neverSynced ? "created" : "updated";
    return markStatus(updated, newStatus);
  }

  /**
   * Upsert a record into the local database.
   *
   * This record must have an ID.
   *
   * If a record with this ID already exists, it will be replaced.
   * Otherwise, this record will be inserted.
   *
   * @param  {Object} record
   * @return {Object}
   */
  upsert(record) {
    if (typeof record !== "object") {
      throw new Error("Record is not an object.");
    }
    if (!record.hasOwnProperty("id")) {
      throw new Error("Cannot update a record missing id.");
    }
    if (!this.collection.idSchema.validate(record.id)) {
      throw new Error(`Invalid Id: ${record.id}`);
    }
    let oldRecord = this.adapterTransaction.get(record.id);
    const updated = this._updateRaw(oldRecord, record);
    this.adapterTransaction.update(updated);
    // Don't return deleted records -- pretend they are gone
    if (oldRecord && oldRecord._status == "deleted") {
      oldRecord = undefined;
    }
    if (oldRecord) {
      this._queueEvent("update", { data: updated, oldRecord });
    } else {
      this._queueEvent("create", { data: updated });
    }
    return { data: updated, oldRecord, permissions: {} };
  }
}
exports.CollectionTransaction = CollectionTransaction;

},{"./adapters/IDB":4,"./adapters/base":5,"./utils":7,"uuid":2}],7:[function(require,module,exports){
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.sortObjects = sortObjects;
exports.filterObject = filterObject;
exports.filterObjects = filterObjects;
exports.isUUID = isUUID;
exports.waterfall = waterfall;
exports.deepEqual = deepEqual;
exports.omitKeys = omitKeys;
const RE_UUID = exports.RE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

/**
 * Checks if a value is undefined.
 * @param  {Any}  value
 * @return {Boolean}
 */
function _isUndefined(value) {
  return typeof value === "undefined";
}

/**
 * Sorts records in a list according to a given ordering.
 *
 * @param  {String} order The ordering, eg. `-last_modified`.
 * @param  {Array}  list  The collection to order.
 * @return {Array}
 */
function sortObjects(order, list) {
  const hasDash = order[0] === "-";
  const field = hasDash ? order.slice(1) : order;
  const direction = hasDash ? -1 : 1;
  return list.slice().sort((a, b) => {
    if (a[field] && _isUndefined(b[field])) {
      return direction;
    }
    if (b[field] && _isUndefined(a[field])) {
      return -direction;
    }
    if (_isUndefined(a[field]) && _isUndefined(b[field])) {
      return 0;
    }
    return a[field] > b[field] ? direction : -direction;
  });
}

/**
 * Test if a single object matches all given filters.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Object} entry    The object to filter.
 * @return {Function}
 */
function filterObject(filters, entry) {
  return Object.keys(filters).every(filter => {
    const value = filters[filter];
    if (Array.isArray(value)) {
      return value.some(candidate => candidate === entry[filter]);
    }
    return entry[filter] === value;
  });
}

/**
 * Filters records in a list matching all given filters.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Array}  list     The collection to filter.
 * @return {Array}
 */
function filterObjects(filters, list) {
  return list.filter(entry => {
    return filterObject(filters, entry);
  });
}

/**
 * Checks if a string is an UUID.
 *
 * @param  {String} uuid The uuid to validate.
 * @return {Boolean}
 */
function isUUID(uuid) {
  return RE_UUID.test(uuid);
}

/**
 * Resolves a list of functions sequentially, which can be sync or async; in
 * case of async, functions must return a promise.
 *
 * @param  {Array} fns  The list of functions.
 * @param  {Any}   init The initial value.
 * @return {Promise}
 */
function waterfall(fns, init) {
  if (!fns.length) {
    return Promise.resolve(init);
  }
  return fns.reduce((promise, nextFn) => {
    return promise.then(nextFn);
  }, Promise.resolve(init));
}

/**
 * Simple deep object comparison function. This only supports comparison of
 * serializable JavaScript objects.
 *
 * @param  {Object} a The source object.
 * @param  {Object} b The compared object.
 * @return {Boolean}
 */
function deepEqual(a, b) {
  if (a === b) {
    return true;
  }
  if (typeof a !== typeof b) {
    return false;
  }
  if (!(a && typeof a == "object") || !(b && typeof b == "object")) {
    return false;
  }
  if (Object.keys(a).length !== Object.keys(b).length) {
    return false;
  }
  for (let k in a) {
    if (!deepEqual(a[k], b[k])) {
      return false;
    }
  }
  return true;
}

/**
 * Return an object without the specified keys.
 *
 * @param  {Object} obj        The original object.
 * @param  {Array}  keys       The list of keys to exclude.
 * @return {Object}            A copy without the specified keys.
 */
function omitKeys(obj, keys = []) {
  return Object.keys(obj).reduce((acc, key) => {
    if (keys.indexOf(key) === -1) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
}

},{}]},{},[1])(1)
});PK
!<:S880modules/services-common/kinto-storage-adapter.js/*
 * 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.
 */
const { utils: Cu } = Components;
const { Sqlite } = Cu.import("resource://gre/modules/Sqlite.jsm", {});
const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});

const SQLITE_PATH = "kinto.sqlite";

/**
 * Filter and sort list against provided filters and order.
 *
 * @param  {Object} filters  The filters to apply.
 * @param  {String} order    The order to apply.
 * @param  {Array}  list     The list to reduce.
 * @return {Array}
 */
function reduceRecords(filters, order, list) {
  const filtered = filters ? filterObjects(filters, list) : list;
  return order ? sortObjects(order, filtered) : filtered;
}

/**
 * Checks if a value is undefined.
 *
 * This is a copy of `_isUndefined` from kinto.js/src/utils.js.
 * @param  {Any}  value
 * @return {Boolean}
 */
function _isUndefined(value) {
  return typeof value === "undefined";
}

/**
 * Sorts records in a list according to a given ordering.
 *
 * This is a copy of `sortObjects` from kinto.js/src/utils.js.
 *
 * @param  {String} order The ordering, eg. `-last_modified`.
 * @param  {Array}  list  The collection to order.
 * @return {Array}
 */
function sortObjects(order, list) {
  const hasDash = order[0] === "-";
  const field = hasDash ? order.slice(1) : order;
  const direction = hasDash ? -1 : 1;
  return list.slice().sort((a, b) => {
    if (a[field] && _isUndefined(b[field])) {
      return direction;
    }
    if (b[field] && _isUndefined(a[field])) {
      return -direction;
    }
    if (_isUndefined(a[field]) && _isUndefined(b[field])) {
      return 0;
    }
    return a[field] > b[field] ? direction : -direction;
  });
}

/**
 * Test if a single object matches all given filters.
 *
 * This is a copy of `filterObject` from kinto.js/src/utils.js.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Object} entry    The object to filter.
 * @return {Function}
 */
function filterObject(filters, entry) {
  return Object.keys(filters).every(filter => {
    const value = filters[filter];
    if (Array.isArray(value)) {
      return value.some(candidate => candidate === entry[filter]);
    }
    return entry[filter] === value;
  });
}

/**
 * Filters records in a list matching all given filters.
 *
 * This is a copy of `filterObjects` from kinto.js/src/utils.js.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Array}  list     The collection to filter.
 * @return {Array}
 */
function filterObjects(filters, list) {
  return list.filter((entry) => {
    return filterObject(filters, entry);
  });
}

const statements = {
  "createCollectionData": `
    CREATE TABLE collection_data (
      collection_name TEXT,
      record_id TEXT,
      record TEXT
    );`,

  "createCollectionMetadata": `
    CREATE TABLE collection_metadata (
      collection_name TEXT PRIMARY KEY,
      last_modified INTEGER
    ) WITHOUT ROWID;`,

  "createCollectionDataRecordIdIndex": `
    CREATE UNIQUE INDEX unique_collection_record
      ON collection_data(collection_name, record_id);`,

  "clearData": `
    DELETE FROM collection_data
      WHERE collection_name = :collection_name;`,

  "createData": `
    INSERT INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  "updateData": `
    INSERT OR REPLACE INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  "deleteData": `
    DELETE FROM collection_data
      WHERE collection_name = :collection_name
      AND record_id = :record_id;`,

  "saveLastModified": `
    REPLACE INTO collection_metadata (collection_name, last_modified)
      VALUES (:collection_name, :last_modified);`,

  "getLastModified": `
    SELECT last_modified
      FROM collection_metadata
        WHERE collection_name = :collection_name;`,

  "getRecord": `
    SELECT record
      FROM collection_data
        WHERE collection_name = :collection_name
        AND record_id = :record_id;`,

  "listRecords": `
    SELECT record
      FROM collection_data
        WHERE collection_name = :collection_name;`,

  // N.B. we have to have a dynamic number of placeholders, which you
  // can't do without building your own statement. See `execute` for details
  "listRecordsById": `
    SELECT record_id, record
      FROM collection_data
        WHERE collection_name = ?
          AND record_id IN `,

  "importData": `
    REPLACE INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  "scanAllRecords": `SELECT * FROM collection_data;`,

  "clearCollectionMetadata": `DELETE FROM collection_metadata;`,

  "calculateStorage": `
    SELECT collection_name, SUM(LENGTH(record)) as size, COUNT(record) as num_records
      FROM collection_data
        GROUP BY collection_name;`,
};

const createStatements = [
  "createCollectionData",
  "createCollectionMetadata",
  "createCollectionDataRecordIdIndex",
];

const currentSchemaVersion = 1;

/**
 * Firefox adapter.
 *
 * Uses Sqlite as a backing store.
 *
 * Options:
 *  - sqliteHandle: a handle to the Sqlite database this adapter will
 *    use as its backing store. To open such a handle, use the
 *    static openConnection() method.
 */
class FirefoxAdapter extends Kinto.adapters.BaseAdapter {
  constructor(collection, options = {}) {
    super();
    const { sqliteHandle = null } = options;
    this.collection = collection;
    this._connection = sqliteHandle;
    this._options = options;
  }

  /**
   * Initialize a Sqlite connection to be suitable for use with Kinto.
   *
   * This will be called automatically by open().
   */
  static async _init(connection) {
    await connection.executeTransaction(async function doSetup() {
      const schema = await connection.getSchemaVersion();

      if (schema == 0) {

        for (let statementName of createStatements) {
          await connection.execute(statements[statementName]);
        }

        await connection.setSchemaVersion(currentSchemaVersion);
      } else if (schema != 1) {
        throw new Error("Unknown database schema: " + schema);
      }
    });
    return connection;
  }

  _executeStatement(statement, params) {
    return this._connection.executeCached(statement, params);
  }

  /**
   * Open and initialize a Sqlite connection to a database that Kinto
   * can use. When you are done with this connection, close it by
   * calling close().
   *
   * Options:
   *   - path: The path for the Sqlite database
   *
   * @returns SqliteConnection
   */
  static async openConnection(options) {
    const opts = Object.assign({}, { sharedMemoryCache: false }, options);
    const conn = await Sqlite.openConnection(opts).then(this._init);
    try {
      Sqlite.shutdown.addBlocker("Kinto storage adapter connection closing",
                                 () => conn.close());
    } catch (e) {
      // It's too late to block shutdown, just close the connection.
      await conn.close();
      throw e;
    }
    return conn;
  }

  clear() {
    const params = { collection_name: this.collection };
    return this._executeStatement(statements.clearData, params);
  }

  execute(callback, options = { preload: [] }) {
    let result;
    const conn = this._connection;
    const collection = this.collection;

    return conn.executeTransaction(async function doExecuteTransaction() {
      // Preload specified records from DB, within transaction.
      const parameters = [
        collection,
        ...options.preload,
      ];
      const placeholders = options.preload.map(_ => "?");
      const stmt = statements.listRecordsById + "(" + placeholders.join(",") + ");";
      const rows = await conn.execute(stmt, parameters);

      const preloaded = rows.reduce((acc, row) => {
        const record = JSON.parse(row.getResultByName("record"));
        acc[row.getResultByName("record_id")] = record;
        return acc;
      }, {});

      const proxy = transactionProxy(collection, preloaded);
      result = callback(proxy);

      for (let { statement, params } of proxy.operations) {
        await conn.executeCached(statement, params);
      }
    }, conn.TRANSACTION_EXCLUSIVE).then(_ => result);
  }

  get(id) {
    const params = {
      collection_name: this.collection,
      record_id: id,
    };
    return this._executeStatement(statements.getRecord, params).then(result => {
      if (result.length == 0) {
        return null;
      }
      return JSON.parse(result[0].getResultByName("record"));
    });
  }

  list(params = { filters: {}, order: "" }) {
    const parameters = {
      collection_name: this.collection,
    };
    return this._executeStatement(statements.listRecords, parameters).then(result => {
      const records = [];
      for (let k = 0; k < result.length; k++) {
        const row = result[k];
        records.push(JSON.parse(row.getResultByName("record")));
      }
      return records;
    }).then(results => {
      // The resulting list of records is filtered and sorted.
      // XXX: with some efforts, this could be implemented using SQL.
      return reduceRecords(params.filters, params.order, results);
    });
  }

  /**
   * Load a list of records into the local database.
   *
   * Note: The adapter is not in charge of filtering the already imported
   * records. This is done in `Collection#loadDump()`, as a common behaviour
   * between every adapters.
   *
   * @param  {Array} records.
   * @return {Array} imported records.
   */
  async loadDump(records) {
    const connection = this._connection;
    const collection_name = this.collection;
    await connection.executeTransaction(async function doImport() {
      for (let record of records) {
        const params = {
          collection_name,
          record_id: record.id,
          record: JSON.stringify(record),
        };
        await connection.execute(statements.importData, params);
      }
      const lastModified = Math.max(...records.map(record => record.last_modified));
      const params = {
        collection_name,
      };
      const previousLastModified = await connection.execute(
        statements.getLastModified, params).then(result => {
          return result.length > 0
            ? result[0].getResultByName("last_modified")
            : -1;
        });
      if (lastModified > previousLastModified) {
        const params = {
          collection_name,
          last_modified: lastModified,
        };
        await connection.execute(statements.saveLastModified, params);
      }
    });
    return records;
  }

  saveLastModified(lastModified) {
    const parsedLastModified = parseInt(lastModified, 10) || null;
    const params = {
      collection_name: this.collection,
      last_modified: parsedLastModified,
    };
    return this._executeStatement(statements.saveLastModified, params)
      .then(() => parsedLastModified);
  }

  getLastModified() {
    const params = {
      collection_name: this.collection,
    };
    return this._executeStatement(statements.getLastModified, params)
      .then(result => {
        if (result.length == 0) {
          return 0;
        }
        return result[0].getResultByName("last_modified");
      });
  }

  calculateStorage() {
    return this._executeStatement(statements.calculateStorage, {})
      .then(result => {
        return Array.from(result, row => ({
          collectionName: row.getResultByName("collection_name"),
          size: row.getResultByName("size"),
          numRecords: row.getResultByName("num_records"),
        }));
      });
  }

  /**
   * Reset the sync status of every record and collection we have
   * access to.
   */
  resetSyncStatus() {
    // We're going to use execute instead of executeCached, so build
    // in our own sanity check
    if (!this._connection) {
      throw new Error("The storage adapter is not open");
    }

    return this._connection.executeTransaction(async function(conn) {
      const promises = [];
      await conn.execute(statements.scanAllRecords, null, function(row) {
        const record = JSON.parse(row.getResultByName("record"));
        const record_id = row.getResultByName("record_id");
        const collection_name = row.getResultByName("collection_name");
        if (record._status === "deleted") {
          // Garbage collect deleted records.
          promises.push(conn.execute(statements.deleteData, { collection_name, record_id }));
        } else {
          const newRecord = Object.assign({}, record, {
            _status: "created",
            last_modified: undefined,
          });
          promises.push(conn.execute(statements.updateData, {
            record: JSON.stringify(newRecord),
            record_id,
            collection_name,
          }));
        }
      });
      await Promise.all(promises);
      await conn.execute(statements.clearCollectionMetadata);
    });
  }
}


function transactionProxy(collection, preloaded) {
  const _operations = [];

  return {
    get operations() {
      return _operations;
    },

    create(record) {
      _operations.push({
        statement: statements.createData,
        params: {
          collection_name: collection,
          record_id: record.id,
          record: JSON.stringify(record)
        }
      });
    },

    update(record) {
      _operations.push({
        statement: statements.updateData,
        params: {
          collection_name: collection,
          record_id: record.id,
          record: JSON.stringify(record)
        }
      });
    },

    delete(id) {
      _operations.push({
        statement: statements.deleteData,
        params: {
          collection_name: collection,
          record_id: id
        }
      });
    },

    get(id) {
      // Gecko JS engine outputs undesired warnings if id is not in preloaded.
      return id in preloaded ? preloaded[id] : undefined;
    }
  };
}
this.FirefoxAdapter = FirefoxAdapter;

this.EXPORTED_SYMBOLS = ["FirefoxAdapter"];
PK
!<11%modules/services-common/logmanager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict;"

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
  "resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
  "resource://services-common/utils.js");

Cu.import("resource://gre/modules/Preferences.jsm");

this.EXPORTED_SYMBOLS = [
  "LogManager",
];

const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days

// "shared" logs (ie, where the same log name is used by multiple LogManager
// instances) are a fact of life here - eg, FirefoxAccounts logs are used by
// both Sync and Reading List.
// However, different instances have different pref branches, so we need to
// handle when one pref branch says "Debug" and the other says "Error"
// So we (a) keep singleton console and dump appenders and (b) keep track
// of the minimum (ie, most verbose) level and use that.
// This avoids (a) the most recent setter winning (as that is indeterminate)
// and (b) multiple dump/console appenders being added to the same log multiple
// times, which would cause messages to appear twice.

// Singletons used by each instance.
var formatter;
var dumpAppender;
var consoleAppender;

// A set of all preference roots used by all instances.
var allBranches = new Set();

// A storage appender that is flushable to a file on disk.  Policies for
// when to flush, to what file, log rotation etc are up to the consumer
// (although it does maintain a .sawError property to help the consumer decide
// based on its policies)
function FlushableStorageAppender(formatter) {
  Log.StorageStreamAppender.call(this, formatter);
  this.sawError = false;
}

FlushableStorageAppender.prototype = {
  __proto__: Log.StorageStreamAppender.prototype,

  append(message) {
    if (message.level >= Log.Level.Error) {
      this.sawError = true;
    }
    Log.StorageStreamAppender.prototype.append.call(this, message);
  },

  reset() {
    Log.StorageStreamAppender.prototype.reset.call(this);
    this.sawError = false;
  },

  // Flush the current stream to a file. Somewhat counter-intuitively, you
  // must pass a log which will be written to with details of the operation.
  async flushToFile(subdirArray, filename, log) {
    let inStream = this.getInputStream();
    this.reset();
    if (!inStream) {
      log.debug("Failed to flush log to a file - no input stream");
      return;
    }
    log.debug("Flushing file log");
    log.trace("Beginning stream copy to " + filename + ": " + Date.now());
    try {
      await this._copyStreamToFile(inStream, subdirArray, filename, log);
      log.trace("onCopyComplete", Date.now());
    } catch (ex) {
      log.error("Failed to copy log stream to file", ex);
    }
  },

  /**
   * Copy an input stream to the named file, doing everything off the main
   * thread.
   * subDirArray is an array of path components, relative to the profile
   * directory, where the file will be created.
   * outputFileName is the filename to create.
   * Returns a promise that is resolved on completion or rejected with an error.
   */
  async _copyStreamToFile(inputStream, subdirArray, outputFileName, log) {
    // The log data could be large, so we don't want to pass it all in a single
    // message, so use BUFFER_SIZE chunks.
    const BUFFER_SIZE = 8192;

    // get a binary stream
    let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
    binaryStream.setInputStream(inputStream);

    let outputDirectory = OS.Path.join(OS.Constants.Path.profileDir, ...subdirArray);
    await OS.File.makeDir(outputDirectory, { ignoreExisting: true, from: OS.Constants.Path.profileDir });
    let fullOutputFileName = OS.Path.join(outputDirectory, outputFileName);
    let output = await OS.File.open(fullOutputFileName, { write: true} );
    try {
      while (true) {
        let available = binaryStream.available();
        if (!available) {
          break;
        }
        let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE));
        await output.write(new Uint8Array(chunk));
      }
    } finally {
      try {
        binaryStream.close(); // inputStream is closed by the binaryStream
        await output.close();
      } catch (ex) {
        log.error("Failed to close the input stream", ex);
      }
    }
    log.trace("finished copy to", fullOutputFileName);
  },
}

// The public LogManager object.
function LogManager(prefRoot, logNames, logFilePrefix) {
  this._prefObservers = [];
  this.init(prefRoot, logNames, logFilePrefix);
}

LogManager.prototype = {
  _cleaningUpFileLogs: false,

  init(prefRoot, logNames, logFilePrefix) {
    if (prefRoot instanceof Preferences) {
      this._prefs = prefRoot;
    } else {
      this._prefs = new Preferences(prefRoot);
    }

    this.logFilePrefix = logFilePrefix;
    if (!formatter) {
      // Create a formatter and various appenders to attach to the logs.
      formatter = new Log.BasicFormatter();
      consoleAppender = new Log.ConsoleAppender(formatter);
      dumpAppender = new Log.DumpAppender(formatter);
    }

    allBranches.add(this._prefs._branchStr);
    // We create a preference observer for all our prefs so they are magically
    // reflected if the pref changes after creation.
    let setupAppender = (appender, prefName, defaultLevel, findSmallest = false) => {
      let observer = newVal => {
        let level = Log.Level[newVal] || defaultLevel;
        if (findSmallest) {
          // As some of our appenders have global impact (ie, there is only one
          // place 'dump' goes to), we need to find the smallest value from all
          // prefs controlling this appender.
          // For example, if consumerA has dump=Debug then consumerB sets
          // dump=Error, we need to keep dump=Debug so consumerA is respected.
          for (let branch of allBranches) {
            let lookPrefBranch = new Preferences(branch);
            let lookVal = Log.Level[lookPrefBranch.get(prefName)];
            if (lookVal && lookVal < level) {
              level = lookVal;
            }
          }
        }
        appender.level = level;
      }
      this._prefs.observe(prefName, observer, this);
      this._prefObservers.push([prefName, observer]);
      // and call the observer now with the current pref value.
      observer(this._prefs.get(prefName));
      return observer;
    }

    this._observeConsolePref = setupAppender(consoleAppender, "log.appender.console", Log.Level.Fatal, true);
    this._observeDumpPref = setupAppender(dumpAppender, "log.appender.dump", Log.Level.Error, true);

    // The file appender doesn't get the special singleton behaviour.
    let fapp = this._fileAppender = new FlushableStorageAppender(formatter);
    // the stream gets a default of Debug as the user must go out of their way
    // to see the stuff spewed to it.
    this._observeStreamPref = setupAppender(fapp, "log.appender.file.level", Log.Level.Debug);

    // now attach the appenders to all our logs.
    for (let logName of logNames) {
      let log = Log.repository.getLogger(logName);
      for (let appender of [fapp, dumpAppender, consoleAppender]) {
        log.addAppender(appender);
      }
    }
    // and use the first specified log as a "root" for our log.
    this._log = Log.repository.getLogger(logNames[0] + ".LogManager");
  },

  /**
   * Cleanup this instance
   */
  finalize() {
    for (let [name, pref] of this._prefObservers) {
      this._prefs.ignore(name, pref, this);
    }
    this._prefObservers = [];
    try {
      allBranches.delete(this._prefs._branchStr);
    } catch (e) {}
    this._prefs = null;
  },

  get _logFileSubDirectoryEntries() {
    // At this point we don't allow a custom directory for the logs, nor allow
    // it to be outside the profile directory.
    // This returns an array of the the relative directory entries below the
    // profile dir, and is the directory about:sync-log uses.
    return ["weave", "logs"];
  },

  get sawError() {
    return this._fileAppender.sawError;
  },

  // Result values for resetFileLog.
  SUCCESS_LOG_WRITTEN: "success-log-written",
  ERROR_LOG_WRITTEN: "error-log-written",

  /**
   * Possibly generate a log file for all accumulated log messages and refresh
   * the input & output streams.
   * Whether a "success" or "error" log is written is determined based on
   * whether an "Error" log entry was written to any of the logs.
   * Returns a promise that resolves on completion with either null (for no
   * file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was
   * written, or ERROR_LOG_WRITTEN if an "error" log was written.
   */
  async resetFileLog() {
    try {
      let flushToFile;
      let reasonPrefix;
      let reason;
      if (this._fileAppender.sawError) {
        reason = this.ERROR_LOG_WRITTEN;
        flushToFile = this._prefs.get("log.appender.file.logOnError", true);
        reasonPrefix = "error";
      } else {
        reason = this.SUCCESS_LOG_WRITTEN;
        flushToFile = this._prefs.get("log.appender.file.logOnSuccess", false);
        reasonPrefix = "success";
      }

      // might as well avoid creating an input stream if we aren't going to use it.
      if (!flushToFile) {
        this._fileAppender.reset();
        return null;
      }

      // We have reasonPrefix at the start of the filename so all "error"
      // logs are grouped in about:sync-log.
      let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
      await this._fileAppender.flushToFile(this._logFileSubDirectoryEntries, filename, this._log);

      // It's not completely clear to markh why we only do log cleanups
      // for errors, but for now the Sync semantics have been copied...
      // (one theory is that only cleaning up on error makes it less
      // likely old error logs would be removed, but that's not true if
      // there are occasional errors - let's address this later!)
      if (reason == this.ERROR_LOG_WRITTEN && !this._cleaningUpFileLogs) {
        this._log.trace("Scheduling cleanup.");
        // Note we don't return/await or otherwise wait on this promise - it
        // continues in the background
        this.cleanupLogs().catch(err => {
          this._log.error("Failed to cleanup logs", err);
        });
      }
      return reason;
    } catch (ex) {
      this._log.error("Failed to resetFileLog", ex);
      return null;
    }
  },

  /**
   * Finds all logs older than maxErrorAge and deletes them using async I/O.
   */
  async cleanupLogs() {
    this._cleaningUpFileLogs = true;
    let logDir = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries);
    let iterator = new OS.File.DirectoryIterator(logDir.path);
    let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE);
    let threshold = Date.now() - 1000 * maxAge;

    this._log.debug("Log cleanup threshold time: " + threshold);
    await iterator.forEach(async (entry) => {
      // Note that we don't check this.logFilePrefix is in the name - we cleanup
      // all files in this directory regardless of that prefix so old logfiles
      // for prefixes no longer in use are still cleaned up. See bug 1279145.
      if (!entry.name.startsWith("error-") &&
          !entry.name.startsWith("success-")) {
        return;
      }
      try {
        // need to call .stat() as the enumerator doesn't give that to us on *nix.
        let info = await OS.File.stat(entry.path);
        if (info.lastModificationDate.getTime() >= threshold) {
          return;
        }
        this._log.trace(" > Cleanup removing " + entry.name +
                        " (" + info.lastModificationDate.getTime() + ")");
        await OS.File.remove(entry.path);
        this._log.trace("Deleted " + entry.name);
      } catch (ex) {
        this._log.debug("Encountered error trying to clean up old log file "
                        + entry.name, ex);
      }
    });
    iterator.close();
    this._cleaningUpFileLogs = false;
    this._log.debug("Done deleting files.");
    // This notification is used only for tests.
    Services.obs.notifyObservers(null, "services-tests:common:log-manager:cleanup-logs");
  },
}
PK
!<BPZ--$modules/services-common/observers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["Observers"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

/**
 * A service for adding, removing and notifying observers of notifications.
 * Wraps the nsIObserverService interface.
 *
 * @version 0.2
 */
this.Observers = {
  /**
   * Register the given callback as an observer of the given topic.
   *
   * @param   topic       {String}
   *          the topic to observe
   *
   * @param   callback    {Object}
   *          the callback; an Object that implements nsIObserver or a Function
   *          that gets called when the notification occurs
   *
   * @param   thisObject  {Object}  [optional]
   *          the object to use as |this| when calling a Function callback
   *
   * @returns the observer
   */
  add(topic, callback, thisObject) {
    let observer = new Observer(topic, callback, thisObject);
    this._cache.push(observer);
    this._service.addObserver(observer, topic, true);

    return observer;
  },

  /**
   * Unregister the given callback as an observer of the given topic.
   *
   * @param topic       {String}
   *        the topic being observed
   *
   * @param callback    {Object}
   *        the callback doing the observing
   *
   * @param thisObject  {Object}  [optional]
   *        the object being used as |this| when calling a Function callback
   */
  remove(topic, callback, thisObject) {
    // This seems fairly inefficient, but I'm not sure how much better
    // we can make it.  We could index by topic, but we can't index by callback
    // or thisObject, as far as I know, since the keys to JavaScript hashes
    // (a.k.a. objects) can apparently only be primitive values.
    let [observer] = this._cache.filter(v => v.topic == topic &&
                                             v.callback == callback &&
                                             v.thisObject == thisObject);
    if (observer) {
      this._service.removeObserver(observer, topic);
      this._cache.splice(this._cache.indexOf(observer), 1);
    } else {
      throw new Error("Attempt to remove non-existing observer");
    }
  },

  /**
   * Notify observers about something.
   *
   * @param topic   {String}
   *        the topic to notify observers about
   *
   * @param subject {Object}  [optional]
   *        some information about the topic; can be any JS object or primitive
   *
   * @param data    {String}  [optional] [deprecated]
   *        some more information about the topic; deprecated as the subject
   *        is sufficient to pass all needed information to the JS observers
   *        that this module targets; if you have multiple values to pass to
   *        the observer, wrap them in an object and pass them via the subject
   *        parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
   */
  notify(topic, subject, data) {
    subject = (typeof subject == "undefined") ? null : new Subject(subject);
       data = (typeof data == "undefined") ? null : data;
    this._service.notifyObservers(subject, topic, data);
  },

  _service: Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService),

  /**
   * A cache of observers that have been added.
   *
   * We use this to remove observers when a caller calls |remove|.
   *
   * XXX This might result in reference cycles, causing memory leaks,
   * if we hold a reference to an observer that holds a reference to us.
   * Could we fix that by making this an independent top-level object
   * rather than a property of this object?
   */
  _cache: []
};


function Observer(topic, callback, thisObject) {
  this.topic = topic;
  this.callback = callback;
  this.thisObject = thisObject;
}

Observer.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  observe(subject, topic, data) {
    // Extract the wrapped object for subjects that are one of our wrappers
    // around a JS object.  This way we support both wrapped subjects created
    // using this module and those that are real XPCOM components.
    if (subject && typeof subject == "object" &&
        ("wrappedJSObject" in subject) &&
        ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
      subject = subject.wrappedJSObject.object;

    if (typeof this.callback == "function") {
      if (this.thisObject)
        this.callback.call(this.thisObject, subject, data);
      else
        this.callback(subject, data);
    } else // typeof this.callback == "object" (nsIObserver)
      this.callback.observe(subject, topic, data);
  }
}


function Subject(object) {
  // Double-wrap the object and set a property identifying the wrappedJSObject
  // as one of our wrappers to distinguish between subjects that are one of our
  // wrappers (which we should unwrap when notifying our observers) and those
  // that are real JS XPCOM components (which we should pass through unaltered).
  this.wrappedJSObject = { observersModuleSubjectWrapper: true, object };
}

Subject.prototype = {
  QueryInterface: XPCOMUtils.generateQI([]),
  getScriptableHelper() {},
  getInterfaces() {}
};
PK
!<#
Z
Zmodules/services-common/rest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

this.EXPORTED_SYMBOLS = [
  "RESTRequest",
  "RESTResponse",
  "TokenAuthenticatedRESTRequest",
];

Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");

XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                  "resource://services-crypto/utils.js");

const Prefs = new Preferences("services.common.");

/**
 * Single use HTTP requests to RESTish resources.
 *
 * @param uri
 *        URI for the request. This can be an nsIURI object or a string
 *        that can be used to create one. An exception will be thrown if
 *        the string is not a valid URI.
 *
 * Examples:
 *
 * (1) Quick GET request:
 *
 *   new RESTRequest("http://server/rest/resource").get(function (error) {
 *     if (error) {
 *       // Deal with a network error.
 *       processNetworkErrorCode(error.result);
 *       return;
 *     }
 *     if (!this.response.success) {
 *       // Bail out if we're not getting an HTTP 2xx code.
 *       processHTTPError(this.response.status);
 *       return;
 *     }
 *     processData(this.response.body);
 *   });
 *
 * (2) Quick PUT request (non-string data is automatically JSONified)
 *
 *   new RESTRequest("http://server/rest/resource").put(data, function (error) {
 *     ...
 *   });
 *
 * (3) Streaming GET
 *
 *   let request = new RESTRequest("http://server/rest/resource");
 *   request.setHeader("Accept", "application/newlines");
 *   request.onComplete = function (error) {
 *     if (error) {
 *       // Deal with a network error.
 *       processNetworkErrorCode(error.result);
 *       return;
 *     }
 *     callbackAfterRequestHasCompleted()
 *   });
 *   request.onProgress = function () {
 *     if (!this.response.success) {
 *       // Bail out if we're not getting an HTTP 2xx code.
 *       return;
 *     }
 *     // Process body data and reset it so we don't process the same data twice.
 *     processIncrementalData(this.response.body);
 *     this.response.body = "";
 *   });
 *   request.get();
 */
this.RESTRequest = function RESTRequest(uri) {
  this.status = this.NOT_SENT;

  // If we don't have an nsIURI object yet, make one. This will throw if
  // 'uri' isn't a valid URI string.
  if (!(uri instanceof Ci.nsIURI)) {
    uri = Services.io.newURI(uri);
  }
  this.uri = uri;

  this._headers = {};
  this._log = Log.repository.getLogger(this._logName);
  this._log.level =
    Log.Level[Prefs.get("log.logger.rest.request")];
}
RESTRequest.prototype = {

  _logName: "Services.Common.RESTRequest",

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIBadCertListener2,
    Ci.nsIInterfaceRequestor,
    Ci.nsIChannelEventSink
  ]),

  /** Public API: **/

  /**
   * A constant boolean that indicates whether this object will automatically
   * utf-8 encode request bodies passed as an object. Used for feature detection
   * so, eg, loop can use the same source code for old and new Firefox versions.
   */
  willUTF8EncodeObjectRequests: true,

  /**
   * URI for the request (an nsIURI object).
   */
  uri: null,

  /**
   * HTTP method (e.g. "GET")
   */
  method: null,

  /**
   * RESTResponse object
   */
  response: null,

  /**
   * nsIRequest load flags. Don't do any caching by default. Don't send user
   * cookies and such over the wire (Bug 644734).
   */
  loadFlags: Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING | Ci.nsIRequest.LOAD_ANONYMOUS,

  /**
   * nsIHttpChannel
   */
  channel: null,

  /**
   * Flag to indicate the status of the request.
   *
   * One of NOT_SENT, SENT, IN_PROGRESS, COMPLETED, ABORTED.
   */
  status: null,

  NOT_SENT:    0,
  SENT:        1,
  IN_PROGRESS: 2,
  COMPLETED:   4,
  ABORTED:     8,

  /**
   * HTTP status text of response
   */
  statusText: null,

  /**
   * Request timeout (in seconds, though decimal values can be used for
   * up to millisecond granularity.)
   *
   * 0 for no timeout.
   */
  timeout: null,

  /**
   * The encoding with which the response to this request must be treated.
   * If a charset parameter is available in the HTTP Content-Type header for
   * this response, that will always be used, and this value is ignored. We
   * default to UTF-8 because that is a reasonable default.
   */
  charset: "utf-8",

  /**
   * Called when the request has been completed, including failures and
   * timeouts.
   *
   * @param error
   *        Error that occurred while making the request, null if there
   *        was no error.
   */
  onComplete: function onComplete(error) {
  },

  /**
   * Called whenever data is being received on the channel. If this throws an
   * exception, the request is aborted and the exception is passed as the
   * error to onComplete().
   */
  onProgress: function onProgress() {
  },

  /**
   * Set a request header.
   */
  setHeader: function setHeader(name, value) {
    this._headers[name.toLowerCase()] = value;
  },

  /**
   * Perform an HTTP GET.
   *
   * @param onComplete
   *        Short-circuit way to set the 'onComplete' method. Optional.
   * @param onProgress
   *        Short-circuit way to set the 'onProgress' method. Optional.
   *
   * @return the request object.
   */
  get: function get(onComplete, onProgress) {
    return this.dispatch("GET", null, onComplete, onProgress);
  },

  /**
   * Perform an HTTP PATCH.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   * @param onComplete
   *        Short-circuit way to set the 'onComplete' method. Optional.
   * @param onProgress
   *        Short-circuit way to set the 'onProgress' method. Optional.
   *
   * @return the request object.
   */
  patch: function patch(data, onComplete, onProgress) {
    return this.dispatch("PATCH", data, onComplete, onProgress);
  },

  /**
   * Perform an HTTP PUT.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   * @param onComplete
   *        Short-circuit way to set the 'onComplete' method. Optional.
   * @param onProgress
   *        Short-circuit way to set the 'onProgress' method. Optional.
   *
   * @return the request object.
   */
  put: function put(data, onComplete, onProgress) {
    return this.dispatch("PUT", data, onComplete, onProgress);
  },

  /**
   * Perform an HTTP POST.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   * @param onComplete
   *        Short-circuit way to set the 'onComplete' method. Optional.
   * @param onProgress
   *        Short-circuit way to set the 'onProgress' method. Optional.
   *
   * @return the request object.
   */
  post: function post(data, onComplete, onProgress) {
    return this.dispatch("POST", data, onComplete, onProgress);
  },

  /**
   * Perform an HTTP DELETE.
   *
   * @param onComplete
   *        Short-circuit way to set the 'onComplete' method. Optional.
   * @param onProgress
   *        Short-circuit way to set the 'onProgress' method. Optional.
   *
   * @return the request object.
   */
  delete: function delete_(onComplete, onProgress) {
    return this.dispatch("DELETE", null, onComplete, onProgress);
  },

  /**
   * Abort an active request.
   */
  abort: function abort() {
    if (this.status != this.SENT && this.status != this.IN_PROGRESS) {
      throw new Error("Can only abort a request that has been sent.");
    }

    this.status = this.ABORTED;
    this.channel.cancel(Cr.NS_BINDING_ABORTED);

    if (this.timeoutTimer) {
      // Clear the abort timer now that the channel is done.
      this.timeoutTimer.clear();
    }
  },

  /** Implementation stuff **/

  dispatch: function dispatch(method, data, onComplete, onProgress) {
    if (this.status != this.NOT_SENT) {
      throw new Error("Request has already been sent!");
    }

    this.method = method;
    if (onComplete) {
      this.onComplete = onComplete;
    }
    if (onProgress) {
      this.onProgress = onProgress;
    }

    // Create and initialize HTTP channel.
    let channel = NetUtil.newChannel({uri: this.uri, loadUsingSystemPrincipal: true})
                         .QueryInterface(Ci.nsIRequest)
                         .QueryInterface(Ci.nsIHttpChannel);
    this.channel = channel;
    channel.loadFlags |= this.loadFlags;
    channel.notificationCallbacks = this;

    this._log.debug(`${method} request to ${this.uri.spec}`);
    // Set request headers.
    let headers = this._headers;
    for (let key in headers) {
      if (key == "authorization") {
        this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
      } else {
        this._log.trace("HTTP Header " + key + ": " + headers[key]);
      }
      channel.setRequestHeader(key, headers[key], false);
    }

    // Set HTTP request body.
    if (method == "PUT" || method == "POST" || method == "PATCH") {
      // Convert non-string bodies into JSON with utf-8 encoding. If a string
      // is passed we assume they've already encoded it.
      let contentType = headers["content-type"];
      if (typeof data != "string") {
        data = JSON.stringify(data);
        if (!contentType) {
          contentType = "application/json";
        }
        if (!contentType.includes("charset")) {
          data = CommonUtils.encodeUTF8(data);
          contentType += "; charset=utf-8";
        } else {
          // If someone handed us an object but also a custom content-type
          // it's probably confused. We could go to even further lengths to
          // respect it, but this shouldn't happen in practice.
          Cu.reportError("rest.js found an object to JSON.stringify but also a " +
                         "content-type header with a charset specification. " +
                         "This probably isn't going to do what you expect");
        }
      }
      if (!contentType) {
        contentType = "text/plain";
      }

      this._log.debug(method + " Length: " + data.length);
      if (this._log.level <= Log.Level.Trace) {
        this._log.trace(method + " Body: " + data);
      }

      let stream = Cc["@mozilla.org/io/string-input-stream;1"]
                     .createInstance(Ci.nsIStringInputStream);
      stream.setData(data, data.length);

      channel.QueryInterface(Ci.nsIUploadChannel);
      channel.setUploadStream(stream, contentType, data.length);
    }
    // We must set this after setting the upload stream, otherwise it
    // will always be 'PUT'. Yeah, I know.
    channel.requestMethod = method;

    // Before opening the channel, set the charset that serves as a hint
    // as to what the response might be encoded as.
    channel.contentCharset = this.charset;

    // Blast off!
    try {
      channel.asyncOpen2(this);
    } catch (ex) {
      // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port.
      this._log.warn("Caught an error in asyncOpen", ex);
      CommonUtils.nextTick(onComplete.bind(this, ex));
    }
    this.status = this.SENT;
    this.delayTimeout();
    return this;
  },

  /**
   * Create or push back the abort timer that kills this request.
   */
  delayTimeout: function delayTimeout() {
    if (this.timeout) {
      CommonUtils.namedTimer(this.abortTimeout, this.timeout * 1000, this,
                             "timeoutTimer");
    }
  },

  /**
   * Abort the request based on a timeout.
   */
  abortTimeout: function abortTimeout() {
    this.abort();
    let error = Components.Exception("Aborting due to channel inactivity.",
                                     Cr.NS_ERROR_NET_TIMEOUT);
    if (!this.onComplete) {
      this._log.error("Unexpected error: onComplete not defined in " +
                      "abortTimeout.");
      return;
    }
    this.onComplete(error);
  },

  /** nsIStreamListener **/

  onStartRequest: function onStartRequest(channel) {
    if (this.status == this.ABORTED) {
      this._log.trace("Not proceeding with onStartRequest, request was aborted.");
      return;
    }

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
      this.status = this.ABORTED;
      channel.cancel(Cr.NS_BINDING_ABORTED);
      return;
    }

    this.status = this.IN_PROGRESS;

    this._log.trace("onStartRequest: " + channel.requestMethod + " " +
                    channel.URI.spec);

    // Create a response object and fill it with some data.
    let response = this.response = new RESTResponse();
    response.request = this;
    response.body = "";

    this.delayTimeout();
  },

  onStopRequest: function onStopRequest(channel, context, statusCode) {
    if (this.timeoutTimer) {
      // Clear the abort timer now that the channel is done.
      this.timeoutTimer.clear();
    }

    // We don't want to do anything for a request that's already been aborted.
    if (this.status == this.ABORTED) {
      this._log.trace("Not proceeding with onStopRequest, request was aborted.");
      return;
    }

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      this.status = this.ABORTED;
      return;
    }
    this.status = this.COMPLETED;

    let statusSuccess = Components.isSuccessCode(statusCode);
    let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
    this._log.trace("Channel for " + channel.requestMethod + " " + uri +
                    " returned status code " + statusCode);

    if (!this.onComplete) {
      this._log.error("Unexpected error: onComplete not defined in " +
                      "abortRequest.");
      this.onProgress = null;
      return;
    }

    // Throw the failure code and stop execution.  Use Components.Exception()
    // instead of Error() so the exception is QI-able and can be passed across
    // XPCOM borders while preserving the status code.
    if (!statusSuccess) {
      let message = Components.Exception("", statusCode).name;
      let error = Components.Exception(message, statusCode);
      this._log.debug(this.method + " " + uri + " failed: " + statusCode + " - " + message);
      this.onComplete(error);
      this.onComplete = this.onProgress = null;
      return;
    }

    this._log.debug(this.method + " " + uri + " " + this.response.status);

    // Additionally give the full response body when Trace logging.
    if (this._log.level <= Log.Level.Trace) {
      this._log.trace(this.method + " body: " + this.response.body);
    }

    delete this._inputStream;

    this.onComplete(null);
    this.onComplete = this.onProgress = null;
  },

  onDataAvailable: function onDataAvailable(channel, cb, stream, off, count) {
    // We get an nsIRequest, which doesn't have contentCharset.
    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      this.abort();

      if (this.onComplete) {
        this.onComplete(ex);
      }

      this.onComplete = this.onProgress = null;
      return;
    }

    if (channel.contentCharset) {
      this.response.charset = channel.contentCharset;

      if (!this._converterStream) {
        this._converterStream = Cc["@mozilla.org/intl/converter-input-stream;1"]
                                   .createInstance(Ci.nsIConverterInputStream);
      }
      this._converterStream.init(stream, channel.contentCharset, 0,
                                 this._converterStream.DEFAULT_REPLACEMENT_CHARACTER);

      try {
        let remaining = count;
        while (remaining > 0) {
          let str = {};
          let num = this._converterStream.readString(remaining, str);
          if (!num) {
            break;
          }
          remaining -= num;
          this.response.body += str.value;
        }
      } catch (ex) {
        this._log.warn("Exception thrown reading " + count + " bytes from " +
                       "the channel", ex);
        throw ex;
      }
    } else {
      this.response.charset = null;

      if (!this._inputStream) {
        this._inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                              .createInstance(Ci.nsIScriptableInputStream);
      }

      this._inputStream.init(stream);

      this.response.body += this._inputStream.read(count);
    }

    try {
      this.onProgress();
    } catch (ex) {
      this._log.warn("Got exception calling onProgress handler, aborting " +
                     this.method + " " + channel.URI.spec, ex);
      this.abort();

      if (!this.onComplete) {
        this._log.error("Unexpected error: onComplete not defined in " +
                        "onDataAvailable.");
        this.onProgress = null;
        return;
      }

      this.onComplete(ex);
      this.onComplete = this.onProgress = null;
      return;
    }

    this.delayTimeout();
  },

  /** nsIInterfaceRequestor **/

  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },

  /** nsIBadCertListener2 **/

  notifyCertProblem: function notifyCertProblem(socketInfo, sslStatus, targetHost) {
    this._log.warn("Invalid HTTPS certificate encountered!");
    // Suppress invalid HTTPS certificate warnings in the UI.
    // (The request will still fail.)
    return true;
  },

  /**
   * Returns true if headers from the old channel should be
   * copied to the new channel. Invoked when a channel redirect
   * is in progress.
   */
  shouldCopyOnRedirect: function shouldCopyOnRedirect(oldChannel, newChannel, flags) {
    let isInternal = !!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL);
    let isSameURI  = newChannel.URI.equals(oldChannel.URI);
    this._log.debug("Channel redirect: " + oldChannel.URI.spec + ", " +
                    newChannel.URI.spec + ", internal = " + isInternal);
    return isInternal && isSameURI;
  },

  /** nsIChannelEventSink **/
  asyncOnChannelRedirect:
    function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {

    let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
    let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
    this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);

    try {
      newChannel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      callback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
      return;
    }

    // For internal redirects, copy the headers that our caller set.
    try {
      if (this.shouldCopyOnRedirect(oldChannel, newChannel, flags)) {
        this._log.trace("Copying headers for safe internal redirect.");
        for (let key in this._headers) {
          newChannel.setRequestHeader(key, this._headers[key], false);
        }
      }
    } catch (ex) {
      this._log.error("Error copying headers", ex);
    }

    this.channel = newChannel;

    // We let all redirects proceed.
    callback.onRedirectVerifyCallback(Cr.NS_OK);
  }
};

/**
 * Response object for a RESTRequest. This will be created automatically by
 * the RESTRequest.
 */
this.RESTResponse = function RESTResponse() {
  this._log = Log.repository.getLogger(this._logName);
  this._log.level =
    Log.Level[Prefs.get("log.logger.rest.response")];
}
RESTResponse.prototype = {

  _logName: "Services.Common.RESTResponse",

  /**
   * Corresponding REST request
   */
  request: null,

  /**
   * HTTP status code
   */
  get status() {
    let status;
    try {
      status = this.request.channel.responseStatus;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP status code", ex);
      return null;
    }
    Object.defineProperty(this, "status", {value: status});
    return status;
  },

  /**
   * HTTP status text
   */
  get statusText() {
    let statusText;
    try {
      statusText = this.request.channel.responseStatusText;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP status text", ex);
      return null;
    }
    Object.defineProperty(this, "statusText", {value: statusText});
    return statusText;
  },

  /**
   * Boolean flag that indicates whether the HTTP status code is 2xx or not.
   */
  get success() {
    let success;
    try {
      success = this.request.channel.requestSucceeded;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP success flag", ex);
      return null;
    }
    Object.defineProperty(this, "success", {value: success});
    return success;
  },

  /**
   * Object containing HTTP headers (keyed as lower case)
   */
  get headers() {
    let headers = {};
    try {
      this._log.trace("Processing response headers.");
      let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
      channel.visitResponseHeaders(function(header, value) {
        headers[header.toLowerCase()] = value;
      });
    } catch (ex) {
      this._log.debug("Caught exception processing response headers", ex);
      return null;
    }

    Object.defineProperty(this, "headers", {value: headers});
    return headers;
  },

  /**
   * HTTP body (string)
   */
  body: null

};

/**
 * Single use MAC authenticated HTTP requests to RESTish resources.
 *
 * @param uri
 *        URI going to the RESTRequest constructor.
 * @param authToken
 *        (Object) An auth token of the form {id: (string), key: (string)}
 *        from which the MAC Authentication header for this request will be
 *        derived. A token as obtained from
 *        TokenServerClient.getTokenFromBrowserIDAssertion is accepted.
 * @param extra
 *        (Object) Optional extra parameters. Valid keys are: nonce_bytes, ts,
 *        nonce, and ext. See CrytoUtils.computeHTTPMACSHA1 for information on
 *        the purpose of these values.
 */
this.TokenAuthenticatedRESTRequest =
 function TokenAuthenticatedRESTRequest(uri, authToken, extra) {
  RESTRequest.call(this, uri);
  this.authToken = authToken;
  this.extra = extra || {};
}
TokenAuthenticatedRESTRequest.prototype = {
  __proto__: RESTRequest.prototype,

  dispatch: function dispatch(method, data, onComplete, onProgress) {
    let sig = CryptoUtils.computeHTTPMACSHA1(
      this.authToken.id, this.authToken.key, method, this.uri, this.extra
    );

    this.setHeader("Authorization", sig.getHeader());

    return RESTRequest.prototype.dispatch.call(
      this, method, data, onComplete, onProgress
    );
  },
};
PK
!<donA
A
A,modules/services-common/tokenserverclient.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [
  "TokenServerClient",
  "TokenServerClientError",
  "TokenServerClientNetworkError",
  "TokenServerClientServerError",
];

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-common/observers.js");

const PREF_LOG_LEVEL = "services.common.log.logger.tokenserverclient";

/**
 * Represents a TokenServerClient error that occurred on the client.
 *
 * This is the base type for all errors raised by client operations.
 *
 * @param message
 *        (string) Error message.
 */
this.TokenServerClientError = function TokenServerClientError(message) {
  this.name = "TokenServerClientError";
  this.message = message || "Client error.";
  // Without explicitly setting .stack, all stacks from these errors will point
  // to the "new Error()" call a few lines down, which isn't helpful.
  this.stack = Error().stack;
}
TokenServerClientError.prototype = new Error();
TokenServerClientError.prototype.constructor = TokenServerClientError;
TokenServerClientError.prototype._toStringFields = function() {
  return {message: this.message};
}
TokenServerClientError.prototype.toString = function() {
  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
}
TokenServerClientError.prototype.toJSON = function() {
  let result = this._toStringFields();
  result["name"] = this.name;
  return result;
}

/**
 * Represents a TokenServerClient error that occurred in the network layer.
 *
 * @param error
 *        The underlying error thrown by the network layer.
 */
this.TokenServerClientNetworkError =
 function TokenServerClientNetworkError(error) {
  this.name = "TokenServerClientNetworkError";
  this.error = error;
  this.stack = Error().stack;
}
TokenServerClientNetworkError.prototype = new TokenServerClientError();
TokenServerClientNetworkError.prototype.constructor =
  TokenServerClientNetworkError;
TokenServerClientNetworkError.prototype._toStringFields = function() {
  return {error: this.error};
}

/**
 * Represents a TokenServerClient error that occurred on the server.
 *
 * This type will be encountered for all non-200 response codes from the
 * server. The type of error is strongly enumerated and is stored in the
 * `cause` property. This property can have the following string values:
 *
 *   conditions-required -- The server is requesting that the client
 *     agree to service conditions before it can obtain a token. The
 *     conditions that must be presented to the user and agreed to are in
 *     the `urls` mapping on the instance. Keys of this mapping are
 *     identifiers. Values are string URLs.
 *
 *   invalid-credentials -- A token could not be obtained because
 *     the credentials presented by the client were invalid.
 *
 *   unknown-service -- The requested service was not found.
 *
 *   malformed-request -- The server rejected the request because it
 *     was invalid. If you see this, code in this file is likely wrong.
 *
 *   malformed-response -- The response from the server was not what was
 *     expected.
 *
 *   general -- A general server error has occurred. Clients should
 *     interpret this as an opaque failure.
 *
 * @param message
 *        (string) Error message.
 */
this.TokenServerClientServerError =
 function TokenServerClientServerError(message, cause = "general") {
  this.now = new Date().toISOString(); // may be useful to diagnose time-skew issues.
  this.name = "TokenServerClientServerError";
  this.message = message || "Server error.";
  this.cause = cause;
  this.stack = Error().stack;
}
TokenServerClientServerError.prototype = new TokenServerClientError();
TokenServerClientServerError.prototype.constructor =
  TokenServerClientServerError;

TokenServerClientServerError.prototype._toStringFields = function() {
  let fields = {
    now: this.now,
    message: this.message,
    cause: this.cause,
  };
  if (this.response) {
    fields.response_body = this.response.body;
    fields.response_headers = this.response.headers;
    fields.response_status = this.response.status;
  }
  return fields;
};

/**
 * Represents a client to the Token Server.
 *
 * http://docs.services.mozilla.com/token/index.html
 *
 * The Token Server supports obtaining tokens for arbitrary apps by
 * constructing URI paths of the form <app>/<app_version>. However, the service
 * discovery mechanism emphasizes the use of full URIs and tries to not force
 * the client to manipulate URIs. This client currently enforces this practice
 * by not implementing an API which would perform URI manipulation.
 *
 * If you are tempted to implement this API in the future, consider this your
 * warning that you may be doing it wrong and that you should store full URIs
 * instead.
 *
 * Areas to Improve:
 *
 *  - The server sends a JSON response on error. The client does not currently
 *    parse this. It might be convenient if it did.
 *  - Currently most non-200 status codes are rolled into one error type. It
 *    might be helpful if callers had a richer API that communicated who was
 *    at fault (e.g. differentiating a 503 from a 401).
 */
this.TokenServerClient = function TokenServerClient() {
  this._log = Log.repository.getLogger("Common.TokenServerClient");
  let level = Services.prefs.getCharPref(PREF_LOG_LEVEL, "Debug");
  this._log.level = Log.Level[level];
}
TokenServerClient.prototype = {
  /**
   * Logger instance.
   */
  _log: null,

  /**
   * Obtain a token from a BrowserID assertion against a specific URL.
   *
   * This asynchronously obtains the token. The callback receives 2 arguments:
   *
   *   (TokenServerClientError | null) If no token could be obtained, this
   *     will be a TokenServerClientError instance describing why. The
   *     type seen defines the type of error encountered. If an HTTP response
   *     was seen, a RESTResponse instance will be stored in the `response`
   *     property of this object. If there was no error and a token is
   *     available, this will be null.
   *
   *   (map | null) On success, this will be a map containing the results from
   *     the server. If there was an error, this will be null. The map has the
   *     following properties:
   *
   *       id       (string) HTTP MAC public key identifier.
   *       key      (string) HTTP MAC shared symmetric key.
   *       endpoint (string) URL where service can be connected to.
   *       uid      (string) user ID for requested service.
   *       duration (string) the validity duration of the issued token.
   *
   * Terms of Service Acceptance
   * ---------------------------
   *
   * Some services require users to accept terms of service before they can
   * obtain a token. If a service requires ToS acceptance, the error passed
   * to the callback will be a `TokenServerClientServerError` with the
   * `cause` property set to "conditions-required". The `urls` property of that
   * instance will be a map of string keys to string URL values. The user-agent
   * should prompt the user to accept the content at these URLs.
   *
   * Clients signify acceptance of the terms of service by sending a token
   * request with additional metadata. This is controlled by the
   * `conditionsAccepted` argument to this function. Clients only need to set
   * this flag once per service and the server remembers acceptance. If
   * the conditions for the service change, the server may request
   * clients agree to terms again. Therefore, clients should always be
   * prepared to handle a conditions required response.
   *
   * Clients should not blindly send acceptance to conditions. Instead, clients
   * should set `conditionsAccepted` if and only if the server asks for
   * acceptance, the conditions are displayed to the user, and the user agrees
   * to them.
   *
   * Example Usage
   * -------------
   *
   *   let client = new TokenServerClient();
   *   let assertion = getBrowserIDAssertionFromSomewhere();
   *   let url = "https://token.services.mozilla.com/1.0/sync/2.0";
   *
   *   client.getTokenFromBrowserIDAssertion(url, assertion,
   *                                         function onResponse(error, result) {
   *     if (error) {
   *       if (error.cause == "conditions-required") {
   *         promptConditionsAcceptance(error.urls, function onAccept() {
   *           client.getTokenFromBrowserIDAssertion(url, assertion,
   *           onResponse, true);
   *         }
   *         return;
   *       }
   *
   *       // Do other error handling.
   *       return;
   *     }
   *
   *     let {
   *       id: id, key: key, uid: uid, endpoint: endpoint, duration: duration
   *     } = result;
   *     // Do stuff with data and carry on.
   *   });
   *
   * @param  url
   *         (string) URL to fetch token from.
   * @param  assertion
   *         (string) BrowserID assertion to exchange token for.
   * @param  cb
   *         (function) Callback to be invoked with result of operation.
   * @param  conditionsAccepted
   *         (bool) Whether to send acceptance to service conditions.
   */
  getTokenFromBrowserIDAssertion:
    function getTokenFromBrowserIDAssertion(url, assertion, cb, addHeaders = {}) {
    if (!url) {
      throw new TokenServerClientError("url argument is not valid.");
    }

    if (!assertion) {
      throw new TokenServerClientError("assertion argument is not valid.");
    }

    if (!cb) {
      throw new TokenServerClientError("cb argument is not valid.");
    }

    this._log.debug("Beginning BID assertion exchange: " + url);

    let req = this.newRESTRequest(url);
    req.setHeader("Accept", "application/json");
    req.setHeader("Authorization", "BrowserID " + assertion);

    for (let header in addHeaders) {
      req.setHeader(header, addHeaders[header]);
    }

    let client = this;
    req.get(function onResponse(error) {
      if (error) {
        cb(new TokenServerClientNetworkError(error), null);
        return;
      }

      let self = this;
      function callCallback(error, result) {
        if (!cb) {
          self._log.warn("Callback already called! Did it throw?");
          return;
        }

        try {
          cb(error, result);
        } catch (ex) {
          self._log.warn("Exception when calling user-supplied callback", ex);
        }

        cb = null;
      }

      try {
        client._processTokenResponse(this.response, callCallback);
      } catch (ex) {
        this._log.warn("Error processing token server response", ex);

        let error = new TokenServerClientError(ex);
        error.response = this.response;
        callCallback(error, null);
      }
    });
  },

  /**
   * Handler to process token request responses.
   *
   * @param response
   *        RESTResponse from token HTTP request.
   * @param cb
   *        The original callback passed to the public API.
   */
  _processTokenResponse: function processTokenResponse(response, cb) {
    this._log.debug("Got token response: " + response.status);

    // Responses should *always* be JSON, even in the case of 4xx and 5xx
    // errors. If we don't see JSON, the server is likely very unhappy.
    let ct = response.headers["content-type"] || "";
    if (ct != "application/json" && !ct.startsWith("application/json;")) {
      this._log.warn("Did not receive JSON response. Misconfigured server?");
      this._log.debug("Content-Type: " + ct);
      this._log.debug("Body: " + response.body);

      let error = new TokenServerClientServerError("Non-JSON response.",
                                                   "malformed-response");
      error.response = response;
      cb(error, null);
      return;
    }

    let result;
    try {
      result = JSON.parse(response.body);
    } catch (ex) {
      this._log.warn("Invalid JSON returned by server: " + response.body);
      let error = new TokenServerClientServerError("Malformed JSON.",
                                                   "malformed-response");
      error.response = response;
      cb(error, null);
      return;
    }

    // Any response status can have X-Backoff or X-Weave-Backoff headers.
    this._maybeNotifyBackoff(response, "x-weave-backoff");
    this._maybeNotifyBackoff(response, "x-backoff");

    // The service shouldn't have any 3xx, so we don't need to handle those.
    if (response.status != 200) {
      // We /should/ have a Cornice error report in the JSON. We log that to
      // help with debugging.
      if ("errors" in result) {
        // This could throw, but this entire function is wrapped in a try. If
        // the server is sending something not an array of objects, it has
        // failed to keep its contract with us and there is little we can do.
        for (let error of result.errors) {
          this._log.info("Server-reported error: " + JSON.stringify(error));
        }
      }

      let error = new TokenServerClientServerError();
      error.response = response;

      if (response.status == 400) {
        error.message = "Malformed request.";
        error.cause = "malformed-request";
      } else if (response.status == 401) {
        // Cause can be invalid-credentials, invalid-timestamp, or
        // invalid-generation.
        error.message = "Authentication failed.";
        error.cause = result.status;
      } else if (response.status == 403) {
        // 403 should represent a "condition acceptance needed" response.
        //
        // The extra validation of "urls" is important. We don't want to signal
        // conditions required unless we are absolutely sure that is what the
        // server is asking for.
        if (!("urls" in result)) {
          this._log.warn("403 response without proper fields!");
          this._log.warn("Response body: " + response.body);

          error.message = "Missing JSON fields.";
          error.cause = "malformed-response";
        } else if (typeof(result.urls) != "object") {
          error.message = "urls field is not a map.";
          error.cause = "malformed-response";
        } else {
          error.message = "Conditions must be accepted.";
          error.cause = "conditions-required";
          error.urls = result.urls;
        }
      } else if (response.status == 404) {
        error.message = "Unknown service.";
        error.cause = "unknown-service";
      }

      // A Retry-After header should theoretically only appear on a 503, but
      // we'll look for it on any error response.
      this._maybeNotifyBackoff(response, "retry-after");

      cb(error, null);
      return;
    }

    for (let k of ["id", "key", "api_endpoint", "uid", "duration"]) {
      if (!(k in result)) {
        let error = new TokenServerClientServerError("Expected key not " +
                                                     " present in result: " +
                                                     k);
        error.cause = "malformed-response";
        error.response = response;
        cb(error, null);
        return;
      }
    }

    this._log.debug("Successful token response");
    cb(null, {
      id:             result.id,
      key:            result.key,
      endpoint:       result.api_endpoint,
      uid:            result.uid,
      duration:       result.duration,
      hashed_fxa_uid: result.hashed_fxa_uid,
    });
  },

  /*
   * The prefix used for all notifications sent by this module.  This
   * allows the handler of notifications to be sure they are handling
   * notifications for the service they expect.
   *
   * If not set, no notifications will be sent.
   */
  observerPrefix: null,

  // Given an optional header value, notify that a backoff has been requested.
  _maybeNotifyBackoff(response, headerName) {
    if (!this.observerPrefix) {
      return;
    }
    let headerVal = response.headers[headerName];
    if (!headerVal) {
      return;
    }
    let backoffInterval;
    try {
      backoffInterval = parseInt(headerVal, 10);
    } catch (ex) {
      this._log.error("TokenServer response had invalid backoff value in '" +
                      headerName + "' header: " + headerVal);
      return;
    }
    Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
  },

  // override points for testing.
  newRESTRequest(url) {
    return new RESTRequest(url);
  }
};
PK
!<n9RR+modules/services-common/uptake-telemetry.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["UptakeTelemetry"];

const { utils: Cu } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});


// Telemetry report results.
const TELEMETRY_HISTOGRAM_ID = "UPTAKE_REMOTE_CONTENT_RESULT_1";

/**
 * A Telemetry helper to report uptake of remote content.
 */
class UptakeTelemetry {

  /**
   * Supported uptake statuses:
   *
   * - `UP_TO_DATE`: Local content was already up-to-date with remote content.
   * - `SUCCESS`: Local content was updated successfully.
   * - `BACKOFF`: Remote server asked clients to backoff.
   * - `PARSE_ERROR`: Parsing server response has failed.
   * - `CONTENT_ERROR`: Server response has unexpected content.
   * - `PREF_DISABLED`: Update is disabled in user preferences.
   * - `SIGNATURE_ERROR`: Signature verification after diff-based sync has failed.
   * - `SIGNATURE_RETRY_ERROR`: Signature verification after full fetch has failed.
   * - `CONFLICT_ERROR`: Some remote changes are in conflict with local changes.
   * - `SYNC_ERROR`: Synchronization of remote changes has failed.
   * - `APPLY_ERROR`: Application of changes locally has failed.
   * - `SERVER_ERROR`: Server failed to respond.
   * - `CERTIFICATE_ERROR`: Server certificate verification has failed.
   * - `DOWNLOAD_ERROR`: Data could not be fully retrieved.
   * - `TIMEOUT_ERROR`: Server response has timed out.
   * - `NETWORK_ERROR`: Communication with server has failed.
   * - `NETWORK_OFFLINE_ERROR`: Network not available.
   * - `UNKNOWN_ERROR`: Uncategorized error.
   * - `CLEANUP_ERROR`: Clean-up of temporary files has failed.
   * - `CUSTOM_1_ERROR`: Update source specific error #1.
   * - `CUSTOM_2_ERROR`: Update source specific error #2.
   * - `CUSTOM_3_ERROR`: Update source specific error #3.
   * - `CUSTOM_4_ERROR`: Update source specific error #4.
   * - `CUSTOM_5_ERROR`: Update source specific error #5.
   *
   * @type {Object}
   */
  static get STATUS() {
    return {
      UP_TO_DATE:            "up_to_date",
      SUCCESS:               "success",
      BACKOFF:               "backoff",
      PREF_DISABLED:         "pref_disabled",
      PARSE_ERROR:           "parse_error",
      CONTENT_ERROR:         "content_error",
      SIGNATURE_ERROR:       "sign_error",
      SIGNATURE_RETRY_ERROR: "sign_retry_error",
      CONFLICT_ERROR:        "conflict_error",
      SYNC_ERROR:            "sync_error",
      APPLY_ERROR:           "apply_error",
      SERVER_ERROR:          "server_error",
      CERTIFICATE_ERROR:     "certificate_error",
      DOWNLOAD_ERROR:        "download_error",
      TIMEOUT_ERROR:         "timeout_error",
      NETWORK_ERROR:         "network_error",
      NETWORK_OFFLINE_ERROR: "offline_error",
      CLEANUP_ERROR:         "cleanup_error",
      UNKNOWN_ERROR:         "unknown_error",
      CUSTOM_1_ERROR:        "custom_1_error",
      CUSTOM_2_ERROR:        "custom_2_error",
      CUSTOM_3_ERROR:        "custom_3_error",
      CUSTOM_4_ERROR:        "custom_4_error",
      CUSTOM_5_ERROR:        "custom_5_error",
    };
  }

  /**
   * Reports the uptake status for the specified source.
   *
   * @param {string} source  the identifier of the update source.
   * @param {string} status  the uptake status.
   */
  static report(source, status) {
    Services.telemetry
      .getKeyedHistogramById(TELEMETRY_HISTOGRAM_ID)
      .add(source, status);
  }
}

this.UptakeTelemetry = UptakeTelemetry;
PK
!<C4GG modules/services-common/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

this.EXPORTED_SYMBOLS = ["CommonUtils"];

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

this.CommonUtils = {
  /*
   * Set manipulation methods. These should be lifted into toolkit, or added to
   * `Set` itself.
   */

  /**
   * Return elements of `a` or `b`.
   */
  union(a, b) {
    let out = new Set(a);
    for (let x of b) {
      out.add(x);
    }
    return out;
  },

  /**
   * Return elements of `a` that are not present in `b`.
   */
  difference(a, b) {
    let out = new Set(a);
    for (let x of b) {
      out.delete(x);
    }
    return out;
  },

  /**
   * Return elements of `a` that are also in `b`.
   */
  intersection(a, b) {
    let out = new Set();
    for (let x of a) {
      if (b.has(x)) {
        out.add(x);
      }
    }
    return out;
  },

  /**
   * Return true if `a` and `b` are the same size, and
   * every element of `a` is in `b`.
   */
  setEqual(a, b) {
    if (a.size != b.size) {
      return false;
    }
    for (let x of a) {
      if (!b.has(x)) {
        return false;
      }
    }
    return true;
  },

  /**
   * Checks elements in two arrays for equality, as determined by the `===`
   * operator. This function does not perform a deep comparison; see Sync's
   * `Util.deepEquals` for that.
   */
  arrayEqual(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;
  },

  /**
   * Encode byte string as base64URL (RFC 4648).
   *
   * @param bytes
   *        (string) Raw byte string to encode.
   * @param pad
   *        (bool) Whether to include padding characters (=). Defaults
   *        to true for historical reasons.
   */
  encodeBase64URL: function encodeBase64URL(bytes, pad = true) {
    let s = btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_");

    if (!pad) {
      return s.replace(/=+$/, "");
    }

    return s;
  },

  /**
   * Create a nsIURI instance from a string.
   */
  makeURI: function makeURI(URIString) {
    if (!URIString)
      return null;
    try {
      return Services.io.newURI(URIString);
    } catch (e) {
      let log = Log.repository.getLogger("Common.Utils");
      log.debug("Could not create URI", e);
      return null;
    }
  },

  /**
   * Execute a function on the next event loop tick.
   *
   * @param callback
   *        Function to invoke.
   * @param thisObj [optional]
   *        Object to bind the callback to.
   */
  nextTick: function nextTick(callback, thisObj) {
    if (thisObj) {
      callback = callback.bind(thisObj);
    }
    Services.tm.dispatchToMainThread(callback);
  },

  /**
   * Return a promise resolving on some later tick.
   *
   * This a wrapper around Promise.resolve() that prevents stack
   * accumulation and prevents callers from accidentally relying on
   * same-tick promise resolution.
   */
  laterTickResolvingPromise(value) {
    return new Promise(resolve => {
      this.nextTick(() => resolve(value));
    });
  },

  /**
   * Return a timer that is scheduled to call the callback after waiting the
   * provided time or as soon as possible. The timer will be set as a property
   * of the provided object with the given timer name.
   */
  namedTimer: function namedTimer(callback, wait, thisObj, name) {
    if (!thisObj || !name) {
      throw new Error(
          "You must provide both an object and a property name for the timer!");
    }

    // Delay an existing timer if it exists
    if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) {
      thisObj[name].delay = wait;
      return thisObj[name];
    }

    // Create a special timer that we can add extra properties
    let timer = Object.create(Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer));

    // Provide an easy way to clear out the timer
    timer.clear = function() {
      thisObj[name] = null;
      timer.cancel();
    };

    // Initialize the timer with a smart callback
    timer.initWithCallback({
      notify: function notify() {
        // Clear out the timer once it's been triggered
        timer.clear();
        callback.call(thisObj, timer);
      }
    }, wait, timer.TYPE_ONE_SHOT);

    return thisObj[name] = timer;
  },

  encodeUTF8: function encodeUTF8(str) {
    try {
      str = this._utf8Converter.ConvertFromUnicode(str);
      return str + this._utf8Converter.Finish();
    } catch (ex) {
      return null;
    }
  },

  decodeUTF8: function decodeUTF8(str) {
    try {
      str = this._utf8Converter.ConvertToUnicode(str);
      return str + this._utf8Converter.Finish();
    } catch (ex) {
      return null;
    }
  },

  byteArrayToString: function byteArrayToString(bytes) {
    return bytes.map(byte => String.fromCharCode(byte)).join("");
  },

  stringToByteArray: function stringToByteArray(bytesString) {
    return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
  },

  bytesAsHex: function bytesAsHex(bytes) {
    return Array.prototype.slice.call(bytes).map(c => ("0" + c.charCodeAt(0).toString(16)).slice(-2)).join("");
  },

  stringAsHex: function stringAsHex(str) {
    return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str));
  },

  stringToBytes: function stringToBytes(str) {
    return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str));
  },

  hexToBytes: function hexToBytes(str) {
    let bytes = [];
    for (let i = 0; i < str.length - 1; i += 2) {
      bytes.push(parseInt(str.substr(i, 2), 16));
    }
    return String.fromCharCode.apply(String, bytes);
  },

  hexAsString: function hexAsString(hex) {
    return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
  },

  /**
   * Base32 encode (RFC 4648) a string
   */
  encodeBase32: function encodeBase32(bytes) {
    const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let leftover = bytes.length % 5;

    // Pad the last quantum with zeros so the length is a multiple of 5.
    if (leftover) {
      for (let i = leftover; i < 5; i++)
        bytes += "\0";
    }

    // Chop the string into quanta of 5 bytes (40 bits). Each quantum
    // is turned into 8 characters from the 32 character base.
    let ret = "";
    for (let i = 0; i < bytes.length; i += 5) {
      let c = Array.prototype.slice.call(bytes.slice(i, i + 5)).map(byte => byte.charCodeAt(0));
      ret += key[c[0] >> 3]
           + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)]
           + key[(c[1] >> 1) & 0x1f]
           + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)]
           + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)]
           + key[(c[3] >> 2) & 0x1f]
           + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)]
           + key[c[4] & 0x1f];
    }

    switch (leftover) {
      case 1:
        return ret.slice(0, -6) + "======";
      case 2:
        return ret.slice(0, -4) + "====";
      case 3:
        return ret.slice(0, -3) + "===";
      case 4:
        return ret.slice(0, -1) + "=";
      default:
        return ret;
    }
  },

  /**
   * Base32 decode (RFC 4648) a string.
   */
  decodeBase32: function decodeBase32(str) {
    const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    let padChar = str.indexOf("=");
    let chars = (padChar == -1) ? str.length : padChar;
    let bytes = Math.floor(chars * 5 / 8);
    let blocks = Math.ceil(chars / 8);

    // Process a chunk of 5 bytes / 8 characters.
    // The processing of this is known in advance,
    // so avoid arithmetic!
    function processBlock(ret, cOffset, rOffset) {
      let c, val;

      // N.B., this relies on
      //   undefined | foo == foo.
      function accumulate(val) {
        ret[rOffset] |= val;
      }

      function advance() {
        c  = str[cOffset++];
        if (!c || c == "" || c == "=") // Easier than range checking.
          throw new Error("Done");     // Will be caught far away.
        val = key.indexOf(c);
        if (val == -1)
          throw new Error(`Unknown character in base32: ${c}`);
      }

      // Handle a left shift, restricted to bytes.
      function left(octet, shift) {
        return (octet << shift) & 0xff;
      }

      advance();
      accumulate(left(val, 3));
      advance();
      accumulate(val >> 2);
      ++rOffset;
      accumulate(left(val, 6));
      advance();
      accumulate(left(val, 1));
      advance();
      accumulate(val >> 4);
      ++rOffset;
      accumulate(left(val, 4));
      advance();
      accumulate(val >> 1);
      ++rOffset;
      accumulate(left(val, 7));
      advance();
      accumulate(left(val, 2));
      advance();
      accumulate(val >> 3);
      ++rOffset;
      accumulate(left(val, 5));
      advance();
      accumulate(val);
      ++rOffset;
    }

    // Our output. Define to be explicit (and maybe the compiler will be smart).
    let ret  = new Array(bytes);
    let i    = 0;
    let cOff = 0;
    let rOff = 0;

    for (; i < blocks; ++i) {
      try {
        processBlock(ret, cOff, rOff);
      } catch (ex) {
        // Handle the detection of padding.
        if (ex.message == "Done")
          break;
        throw ex;
      }
      cOff += 8;
      rOff += 5;
    }

    // Slice in case our shift overflowed to the right.
    return CommonUtils.byteArrayToString(ret.slice(0, bytes));
  },

  /**
   * Trim excess padding from a Base64 string and atob().
   *
   * See bug 562431 comment 4.
   */
  safeAtoB: function safeAtoB(b64) {
    let len = b64.length;
    let over = len % 4;
    return over ? atob(b64.substr(0, len - over)) : atob(b64);
  },

  /**
   * Parses a JSON file from disk using OS.File and promises.
   *
   * @param path the file to read. Will be passed to `OS.File.read()`.
   * @return a promise that resolves to the JSON contents of the named file.
   */
  readJSON(path) {
    return OS.File.read(path, { encoding: "utf-8" }).then((data) => {
      return JSON.parse(data);
    });
  },

  /**
   * Write a JSON object to the named file using OS.File and promises.
   *
   * @param contents a JS object. Will be serialized.
   * @param path the path of the file to write.
   * @return a promise, as produced by OS.File.writeAtomic.
   */
  writeJSON(contents, path) {
    let data = JSON.stringify(contents);
    return OS.File.writeAtomic(path, data, {encoding: "utf-8", tmpPath: path + ".tmp"});
  },


  /**
   * Ensure that the specified value is defined in integer milliseconds since
   * UNIX epoch.
   *
   * This throws an error if the value is not an integer, is negative, or looks
   * like seconds, not milliseconds.
   *
   * If the value is null or 0, no exception is raised.
   *
   * @param value
   *        Value to validate.
   */
  ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) {
    if (!value) {
      return;
    }

    if (!/^[0-9]+$/.test(value)) {
      throw new Error("Timestamp value is not a positive integer: " + value);
    }

    let intValue = parseInt(value, 10);

    if (!intValue) {
       return;
    }

    // Catch what looks like seconds, not milliseconds.
    if (intValue < 10000000000) {
      throw new Error("Timestamp appears to be in seconds: " + intValue);
    }
  },

  /**
   * Read bytes from an nsIInputStream into a string.
   *
   * @param stream
   *        (nsIInputStream) Stream to read from.
   * @param count
   *        (number) Integer number of bytes to read. If not defined, or
   *        0, all available input is read.
   */
  readBytesFromInputStream: function readBytesFromInputStream(stream, count) {
    let BinaryInputStream = Components.Constructor(
        "@mozilla.org/binaryinputstream;1",
        "nsIBinaryInputStream",
        "setInputStream");
    if (!count) {
      count = stream.available();
    }

    return new BinaryInputStream(stream).readBytes(count);
  },

  /**
   * Generate a new UUID using nsIUUIDGenerator.
   *
   * Example value: "1e00a2e2-1570-443e-bf5e-000354124234"
   *
   * @return string A hex-formatted UUID string.
   */
  generateUUID: function generateUUID() {
    let uuid = Cc["@mozilla.org/uuid-generator;1"]
                 .getService(Ci.nsIUUIDGenerator)
                 .generateUUID()
                 .toString();

    return uuid.substring(1, uuid.length - 1);
  },

  /**
   * Obtain an epoch value from a preference.
   *
   * This reads a string preference and returns an integer. The string
   * preference is expected to contain the integer milliseconds since epoch.
   * For best results, only read preferences that have been saved with
   * setDatePref().
   *
   * We need to store times as strings because integer preferences are only
   * 32 bits and likely overflow most dates.
   *
   * If the pref contains a non-integer value, the specified default value will
   * be returned.
   *
   * @param branch
   *        (Preferences) Branch from which to retrieve preference.
   * @param pref
   *        (string) The preference to read from.
   * @param def
   *        (Number) The default value to use if the preference is not defined.
   * @param log
   *        (Log.Logger) Logger to write warnings to.
   */
  getEpochPref: function getEpochPref(branch, pref, def = 0, log = null) {
    if (!Number.isInteger(def)) {
      throw new Error("Default value is not a number: " + def);
    }

    let valueStr = branch.get(pref, null);

    if (valueStr !== null) {
      let valueInt = parseInt(valueStr, 10);
      if (Number.isNaN(valueInt)) {
        if (log) {
          log.warn("Preference value is not an integer. Using default. " +
                   pref + "=" + valueStr + " -> " + def);
        }

        return def;
      }

      return valueInt;
    }

    return def;
  },

  /**
   * Obtain a Date from a preference.
   *
   * This is a wrapper around getEpochPref. It converts the value to a Date
   * instance and performs simple range checking.
   *
   * The range checking ensures the date is newer than the oldestYear
   * parameter.
   *
   * @param branch
   *        (Preferences) Branch from which to read preference.
   * @param pref
   *        (string) The preference from which to read.
   * @param def
   *        (Number) The default value (in milliseconds) if the preference is
   *        not defined or invalid.
   * @param log
   *        (Log.Logger) Logger to write warnings to.
   * @param oldestYear
   *        (Number) Oldest year to accept in read values.
   */
  getDatePref: function getDatePref(branch, pref, def = 0, log = null,
                                    oldestYear = 2010) {

    let valueInt = this.getEpochPref(branch, pref, def, log);
    let date = new Date(valueInt);

    if (valueInt == def || date.getFullYear() >= oldestYear) {
      return date;
    }

    if (log) {
      log.warn("Unexpected old date seen in pref. Returning default: " +
               pref + "=" + date + " -> " + def);
    }

    return new Date(def);
  },

  /**
   * Store a Date in a preference.
   *
   * This is the opposite of getDatePref(). The same notes apply.
   *
   * If the range check fails, an Error will be thrown instead of a default
   * value silently being used.
   *
   * @param branch
   *        (Preference) Branch from which to read preference.
   * @param pref
   *        (string) Name of preference to write to.
   * @param date
   *        (Date) The value to save.
   * @param oldestYear
   *        (Number) The oldest year to accept for values.
   */
  setDatePref: function setDatePref(branch, pref, date, oldestYear = 2010) {
    if (date.getFullYear() < oldestYear) {
      throw new Error("Trying to set " + pref + " to a very old time: " +
                      date + ". The current time is " + new Date() +
                      ". Is the system clock wrong?");
    }

    branch.set(pref, "" + date.getTime());
  },

  /**
   * Convert a string between two encodings.
   *
   * Output is only guaranteed if the input stream is composed of octets. If
   * the input string has characters with values larger than 255, data loss
   * will occur.
   *
   * The returned string is guaranteed to consist of character codes no greater
   * than 255.
   *
   * @param s
   *        (string) The source string to convert.
   * @param source
   *        (string) The current encoding of the string.
   * @param dest
   *        (string) The target encoding of the string.
   *
   * @return string
   */
  convertString: function convertString(s, source, dest) {
    if (!s) {
      throw new Error("Input string must be defined.");
    }

    let is = Cc["@mozilla.org/io/string-input-stream;1"]
               .createInstance(Ci.nsIStringInputStream);
    is.setData(s, s.length);

    let listener = Cc["@mozilla.org/network/stream-loader;1"]
                     .createInstance(Ci.nsIStreamLoader);

    let result;

    listener.init({
      onStreamComplete: function onStreamComplete(loader, context, status,
                                                  length, data) {
        result = String.fromCharCode.apply(this, data);
      },
    });

    let converter = this._converterService.asyncConvertData(source, dest,
                                                            listener, null);
    converter.onStartRequest(null, null);
    converter.onDataAvailable(null, null, is, 0, s.length);
    converter.onStopRequest(null, null, null);

    return result;
  },
};

XPCOMUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function() {
  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  return converter;
});

XPCOMUtils.defineLazyGetter(CommonUtils, "_converterService", function() {
  return Cc["@mozilla.org/streamConverters;1"]
           .getService(Ci.nsIStreamConverterService);
});
PK
!<xcc&modules/services-crypto/WeaveCrypto.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["WeaveCrypto"];

var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/async.js");

Cu.importGlobalProperties(["crypto"]);

const CRYPT_ALGO        = "AES-CBC";
const CRYPT_ALGO_LENGTH = 256;
const AES_CBC_IV_SIZE   = 16;
const OPERATIONS        = { ENCRYPT: 0, DECRYPT: 1 };
const UTF_LABEL          = "utf-8";

const KEY_DERIVATION_ALGO         = "PBKDF2";
const KEY_DERIVATION_HASHING_ALGO = "SHA-1";
const KEY_DERIVATION_ITERATIONS   = 4096; // PKCS#5 recommends at least 1000.
const DERIVED_KEY_ALGO            = CRYPT_ALGO;

this.WeaveCrypto = function WeaveCrypto() {
    this.init();
};

WeaveCrypto.prototype = {
    prefBranch: null,
    debug: true,  // services.sync.log.cryptoDebug

    observer: {
        _self: null,

        QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                               Ci.nsISupportsWeakReference]),

        observe(subject, topic, data) {
            let self = this._self;
            self.log("Observed " + topic + " topic.");
            if (topic == "nsPref:changed") {
                self.debug = self.prefBranch.getBoolPref("cryptoDebug");
            }
        }
    },

    init() {
        // Preferences. Add observer so we get notified of changes.
        this.prefBranch = Services.prefs.getBranch("services.sync.log.");
        this.prefBranch.addObserver("cryptoDebug", this.observer);
        this.observer._self = this;
        this.debug = this.prefBranch.getBoolPref("cryptoDebug", false);
        XPCOMUtils.defineLazyGetter(this, "encoder", () => new TextEncoder(UTF_LABEL));
        XPCOMUtils.defineLazyGetter(this, "decoder", () => new TextDecoder(UTF_LABEL, { fatal: true }));
    },

    log(message) {
        if (!this.debug) {
            return;
        }
        dump("WeaveCrypto: " + message + "\n");
        Services.console.logStringMessage("WeaveCrypto: " + message);
    },

    // /!\ Only use this for tests! /!\
    _getCrypto() {
        return crypto;
    },

    encrypt(clearTextUCS2, symmetricKey, iv) {
        this.log("encrypt() called");
        let clearTextBuffer = this.encoder.encode(clearTextUCS2).buffer;
        let encrypted = this._commonCrypt(clearTextBuffer, symmetricKey, iv, OPERATIONS.ENCRYPT);
        return this.encodeBase64(encrypted);
    },

    decrypt(cipherText, symmetricKey, iv) {
        this.log("decrypt() called");
        if (cipherText.length) {
            cipherText = atob(cipherText);
        }
        let cipherTextBuffer = this.byteCompressInts(cipherText);
        let decrypted = this._commonCrypt(cipherTextBuffer, symmetricKey, iv, OPERATIONS.DECRYPT);
        return this.decoder.decode(decrypted);
    },

    /**
     * _commonCrypt
     *
     * @args
     * data: data to encrypt/decrypt (ArrayBuffer)
     * symKeyStr: symmetric key (Base64 String)
     * ivStr: initialization vector (Base64 String)
     * operation: operation to apply (either OPERATIONS.ENCRYPT or OPERATIONS.DECRYPT)
     * @returns
     * the encrypted/decrypted data (ArrayBuffer)
    */
    _commonCrypt(data, symKeyStr, ivStr, operation) {
        this.log("_commonCrypt() called");
        ivStr = atob(ivStr);

        if (operation !== OPERATIONS.ENCRYPT && operation !== OPERATIONS.DECRYPT) {
            throw new Error("Unsupported operation in _commonCrypt.");
        }
        // We never want an IV longer than the block size, which is 16 bytes
        // for AES, neither do we want one smaller; throw in both cases.
        if (ivStr.length !== AES_CBC_IV_SIZE) {
            throw new Error(
                `Invalid IV size; must be ${AES_CBC_IV_SIZE} bytes.`);
        }

        let iv = this.byteCompressInts(ivStr);
        let symKey = this.importSymKey(symKeyStr, operation);
        let cryptMethod = (operation === OPERATIONS.ENCRYPT
                           ? crypto.subtle.encrypt
                           : crypto.subtle.decrypt)
                          .bind(crypto.subtle);
        let algo = { name: CRYPT_ALGO, iv };


        return Async.promiseSpinningly(
            cryptMethod(algo, symKey, data)
            .then(keyBytes => new Uint8Array(keyBytes))
        );
    },


    generateRandomKey() {
        this.log("generateRandomKey() called");
        let algo = {
            name: CRYPT_ALGO,
            length: CRYPT_ALGO_LENGTH
        };
        return Async.promiseSpinningly(
            crypto.subtle.generateKey(algo, true, [])
            .then(key => crypto.subtle.exportKey("raw", key))
            .then(keyBytes => {
                keyBytes = new Uint8Array(keyBytes);
                return this.encodeBase64(keyBytes);
            })
        );
    },

    generateRandomIV() {
      return this.generateRandomBytes(AES_CBC_IV_SIZE);
    },

    generateRandomBytes(byteCount) {
        this.log("generateRandomBytes() called");

        let randBytes = new Uint8Array(byteCount);
        crypto.getRandomValues(randBytes);

        return this.encodeBase64(randBytes);
    },

    //
    // SymKey CryptoKey memoization.
    //

    // Memoize the import of symmetric keys. We do this by using the base64
    // string itself as a key.
    _encryptionSymKeyMemo: {},
    _decryptionSymKeyMemo: {},
    importSymKey(encodedKeyString, operation) {
        let memo;

        // We use two separate memos for thoroughness: operation is an input to
        // key import.
        switch (operation) {
            case OPERATIONS.ENCRYPT:
                memo = this._encryptionSymKeyMemo;
                break;
            case OPERATIONS.DECRYPT:
                memo = this._decryptionSymKeyMemo;
                break;
            default:
                throw new Error("Unsupported operation in importSymKey.");
        }

        if (encodedKeyString in memo)
            return memo[encodedKeyString];

        let symmetricKeyBuffer = this.makeUint8Array(encodedKeyString, true);
        let algo = { name: CRYPT_ALGO };
        let usages = [operation === OPERATIONS.ENCRYPT ? "encrypt" : "decrypt"];

        return Async.promiseSpinningly(
            crypto.subtle.importKey("raw", symmetricKeyBuffer, algo, false, usages)
            .then(symKey => {
                memo[encodedKeyString] = symKey;
                return symKey;
            })
        );
    },


    //
    // Utility functions
    //

    /**
     * Returns an Uint8Array filled with a JS string,
     * which means we only keep utf-16 characters from 0x00 to 0xFF.
     */
    byteCompressInts(str) {
        let arrayBuffer = new Uint8Array(str.length);
        for (let i = 0; i < str.length; i++) {
            arrayBuffer[i] = str.charCodeAt(i) & 0xFF;
        }
        return arrayBuffer;
    },

    expandData(data) {
        let expanded = "";
        for (let i = 0; i < data.length; i++) {
            expanded += String.fromCharCode(data[i]);
        }
        return expanded;
    },

    encodeBase64(data) {
        return btoa(this.expandData(data));
    },

    makeUint8Array(input, isEncoded) {
        if (isEncoded) {
            input = atob(input);
        }
        return this.byteCompressInts(input);
    },
};
PK
!<bl``$modules/services-crypto/jwcrypto.jsm/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";


const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");

XPCOMUtils.defineLazyServiceGetter(this,
                                   "IdentityCryptoService",
                                   "@mozilla.org/identity/crypto-service;1",
                                   "nsIIdentityCryptoService");

this.EXPORTED_SYMBOLS = ["jwcrypto"];

const PREF_LOG_LEVEL = "services.crypto.jwcrypto.log.level";

XPCOMUtils.defineLazyGetter(this, "log", function() {
  let log = Log.repository.getLogger("Services.Crypto.jwcrypto");
  // Default log level is "Error", but consumers can change this with the pref
  // "services.crypto.jwcrypto.log.level".
  log.level = Log.Level.Error;
  let appender = new Log.DumpAppender();
  log.addAppender(appender);
  try {
    let level =
      Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
      && Services.prefs.getCharPref(PREF_LOG_LEVEL);
    log.level = Log.Level[level] || Log.Level.Error;
  } catch (e) {
    log.error(e);
  }

  return log;
});

const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime

function generateKeyPair(aAlgorithmName, aCallback) {
  log.debug("Generate key pair; alg = " + aAlgorithmName);

  IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) {
    if (!Components.isSuccessCode(rv)) {
      return aCallback("key generation failed");
    }

    var publicKey;

    switch (aKeyPair.keyType) {
     case ALGORITHMS.RS256:
      publicKey = {
        algorithm: "RS",
        exponent:  aKeyPair.hexRSAPublicKeyExponent,
        modulus:   aKeyPair.hexRSAPublicKeyModulus
      };
      break;

     case ALGORITHMS.DS160:
      publicKey = {
        algorithm: "DS",
        y: aKeyPair.hexDSAPublicValue,
        p: aKeyPair.hexDSAPrime,
        q: aKeyPair.hexDSASubPrime,
        g: aKeyPair.hexDSAGenerator
      };
      break;

    default:
      return aCallback("unknown key type");
    }

    let keyWrapper = {
      serializedPublicKey: JSON.stringify(publicKey),
      _kp: aKeyPair
    };

    return aCallback(null, keyWrapper);
  });
}

function sign(aPayload, aKeypair, aCallback) {
  aKeypair._kp.sign(aPayload, function(rv, signature) {
    if (!Components.isSuccessCode(rv)) {
      log.error("signer.sign failed");
      return aCallback("Sign failed");
    }
    log.debug("signer.sign: success");
    return aCallback(null, signature);
  });
}

function jwcryptoClass() {
}

jwcryptoClass.prototype = {
  /*
   * Determine the expiration of the assertion.  Returns expiry date
   * in milliseconds as integer.
   *
   * @param localtimeOffsetMsec (optional)
   *        The number of milliseconds that must be added to the local clock
   *        for it to agree with the server.  For example, if the local clock
   *        if two minutes fast, localtimeOffsetMsec would be -120000
   *
   * @param now (options)
   *        Current date in milliseconds.  Useful for mocking clock
   *        skew in testing.
   */
  getExpiration(duration = DURATION_MS, localtimeOffsetMsec = 0, now = Date.now()) {
    return now + localtimeOffsetMsec + duration;
  },

  isCertValid(aCert, aCallback) {
    // XXX check expiration, bug 769850
    aCallback(true);
  },

  generateKeyPair(aAlgorithmName, aCallback) {
    log.debug("generating");
    generateKeyPair(aAlgorithmName, aCallback);
  },

  /*
   * Generate an assertion and return it through the provided callback.
   *
   * @param aCert
   *        Identity certificate
   *
   * @param aKeyPair
   *        KeyPair object
   *
   * @param aAudience
   *        Audience of the assertion
   *
   * @param aOptions (optional)
   *        Can include:
   *        {
   *          localtimeOffsetMsec: <clock offset in milliseconds>,
   *          now: <current date in milliseconds>
   *          duration: <validity duration for this assertion in milliseconds>
   *        }
   *
   *        localtimeOffsetMsec is the number of milliseconds that need to be
   *        added to the local clock time to make it concur with the server.
   *        For example, if the local clock is two minutes fast, the offset in
   *        milliseconds would be -120000.
   *
   * @param aCallback
   *        Function to invoke with resulting assertion.  Assertion
   *        will be string or null on failure.
   */
  generateAssertion(aCert, aKeyPair, aAudience, aOptions, aCallback) {
    if (typeof aOptions == "function") {
      aCallback = aOptions;
      aOptions = { };
    }

    // for now, we hack the algorithm name
    // XXX bug 769851
    var header = {"alg": "DS128"};
    var headerBytes = IdentityCryptoService.base64UrlEncode(
                          JSON.stringify(header));

    var payload = {
      exp: this.getExpiration(
               aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
      aud: aAudience
    };
    var payloadBytes = IdentityCryptoService.base64UrlEncode(
                          JSON.stringify(payload));

    log.debug("payload", { payload, payloadBytes });
    sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) {
      if (err)
        return aCallback(err);

      var signedAssertion = headerBytes + "." + payloadBytes + "." + signature;
      return aCallback(null, aCert + "~" + signedAssertion);
    });
  }

};

this.jwcrypto = new jwcryptoClass();
this.jwcrypto.ALGORITHMS = ALGORITHMS;
PK
!<"*MM modules/services-crypto/utils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

this.EXPORTED_SYMBOLS = ["CryptoUtils"];

Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.CryptoUtils = {
  xor: function xor(a, b) {
    let bytes = [];

    if (a.length != b.length) {
      throw new Error("can't xor unequal length strings: " + a.length + " vs " + b.length);
    }

    for (let i = 0; i < a.length; i++) {
      bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
    }

    return String.fromCharCode.apply(String, bytes);
  },

  /**
   * Generate a string of random bytes.
   */
  generateRandomBytes: function generateRandomBytes(length) {
    let rng = Cc["@mozilla.org/security/random-generator;1"]
                .createInstance(Ci.nsIRandomGenerator);
    let bytes = rng.generateRandomBytes(length);
    return CommonUtils.byteArrayToString(bytes);
  },

  /**
   * UTF8-encode a message and hash it with the given hasher. Returns a
   * string containing bytes. The hasher is reset if it's an HMAC hasher.
   */
  digestUTF8: function digestUTF8(message, hasher) {
    let data = this._utf8Converter.convertToByteArray(message, {});
    hasher.update(data, data.length);
    let result = hasher.finish(false);
    if (hasher instanceof Ci.nsICryptoHMAC) {
      hasher.reset();
    }
    return result;
  },

  /**
   * Treat the given message as a bytes string and hash it with the given
   * hasher. Returns a string containing bytes. The hasher is reset if it's
   * an HMAC hasher.
   */
  digestBytes: function digestBytes(message, hasher) {
    // No UTF-8 encoding for you, sunshine.
    let bytes = Array.prototype.slice.call(message).map(b => b.charCodeAt(0));
    hasher.update(bytes, bytes.length);
    let result = hasher.finish(false);
    if (hasher instanceof Ci.nsICryptoHMAC) {
      hasher.reset();
    }
    return result;
  },

  /**
   * Encode the message into UTF-8 and feed the resulting bytes into the
   * given hasher. Does not return a hash. This can be called multiple times
   * with a single hasher, but eventually you must extract the result
   * yourself.
   */
  updateUTF8(message, hasher) {
    let bytes = this._utf8Converter.convertToByteArray(message, {});
    hasher.update(bytes, bytes.length);
  },

  /**
   * UTF-8 encode a message and perform a SHA-1 over it.
   *
   * @param message
   *        (string) Buffer to perform operation on. Should be a JS string.
   *                 It is possible to pass in a string representing an array
   *                 of bytes. But, you probably don't want to UTF-8 encode
   *                 such data and thus should not be using this function.
   *
   * @return string
   *         Raw bytes constituting SHA-1 hash. Value is a JS string. Each
   *         character is the byte value for that offset. Returned string
   *         always has .length == 20.
   */
  UTF8AndSHA1: function UTF8AndSHA1(message) {
    let hasher = Cc["@mozilla.org/security/hash;1"]
                 .createInstance(Ci.nsICryptoHash);
    hasher.init(hasher.SHA1);

    return CryptoUtils.digestUTF8(message, hasher);
  },

  sha1: function sha1(message) {
    return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
  },

  sha1Base32: function sha1Base32(message) {
    return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
  },

  sha256(message) {
    let hasher = Cc["@mozilla.org/security/hash;1"]
                 .createInstance(Ci.nsICryptoHash);
    hasher.init(hasher.SHA256);
    return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher));
  },

  /**
   * Produce an HMAC key object from a key string.
   */
  makeHMACKey: function makeHMACKey(str) {
    return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
  },

  /**
   * Produce an HMAC hasher and initialize it with the given HMAC key.
   */
  makeHMACHasher: function makeHMACHasher(type, key) {
    let hasher = Cc["@mozilla.org/security/hmac;1"]
                   .createInstance(Ci.nsICryptoHMAC);
    hasher.init(type, key);
    return hasher;
  },

  /**
   * HMAC-based Key Derivation (RFC 5869).
   */
  hkdf: function hkdf(ikm, xts, info, len) {
    if (typeof xts === undefined)
      xts = String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0, 0, 0, 0);
    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
                                       CryptoUtils.makeHMACKey(xts));
    let prk = CryptoUtils.digestBytes(ikm, h);
    return CryptoUtils.hkdfExpand(prk, info, len);
  },

  /**
   * HMAC-based Key Derivation Step 2 according to RFC 5869.
   */
  hkdfExpand: function hkdfExpand(prk, info, len) {
    const BLOCKSIZE = 256 / 8;
    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
                                       CryptoUtils.makeHMACKey(prk));
    let T = "";
    let Tn = "";
    let iterations = Math.ceil(len / BLOCKSIZE);
    for (let i = 0; i < iterations; i++) {
      Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
      T += Tn;
    }
    return T.slice(0, len);
  },

  /**
   * PBKDF2 implementation in Javascript.
   *
   * The arguments to this function correspond to items in
   * PKCS #5, v2.0 pp. 9-10
   *
   * P: the passphrase, an octet string:              e.g., "secret phrase"
   * S: the salt, an octet string:                    e.g., "DNXPzPpiwn"
   * c: the number of iterations, a positive integer: e.g., 4096
   * dkLen: the length in octets of the destination
   *        key, a positive integer:                  e.g., 16
   * hmacAlg: The algorithm to use for hmac
   * hmacLen: The hmac length
   *
   * The default value of 20 for hmacLen is appropriate for SHA1.  For SHA256,
   * hmacLen should be 32.
   *
   * The output is an octet string of length dkLen, which you
   * can encode as you wish.
   */
  pbkdf2Generate: function pbkdf2Generate(P, S, c, dkLen,
                       hmacAlg = Ci.nsICryptoHMAC.SHA1, hmacLen = 20) {

    // We don't have a default in the algo itself, as NSS does.
    if (!dkLen) {
      throw new Error("dkLen should be defined");
    }

    function F(S, c, i, h) {

      function XOR(a, b, isA) {
        if (a.length != b.length) {
          return false;
        }

        let val = [];
        for (let i = 0; i < a.length; i++) {
          if (isA) {
            val[i] = a[i] ^ b[i];
          } else {
            val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
          }
        }

        return val;
      }

      let ret;
      let U = [];

      /* Encode i into 4 octets: _INT */
      let I = [];
      I[0] = String.fromCharCode((i >> 24) & 0xff);
      I[1] = String.fromCharCode((i >> 16) & 0xff);
      I[2] = String.fromCharCode((i >> 8) & 0xff);
      I[3] = String.fromCharCode(i & 0xff);

      U[0] = CryptoUtils.digestBytes(S + I.join(""), h);
      for (let j = 1; j < c; j++) {
        U[j] = CryptoUtils.digestBytes(U[j - 1], h);
      }

      ret = U[0];
      for (let j = 1; j < c; j++) {
        ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
      }

      return ret;
    }

    let l = Math.ceil(dkLen / hmacLen);
    let r = dkLen - ((l - 1) * hmacLen);

    // Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
    let h = CryptoUtils.makeHMACHasher(hmacAlg,
                                       CryptoUtils.makeHMACKey(P));

    let T = [];
    for (let i = 0; i < l;) {
      T[i] = F(S, c, ++i, h);
    }

    let ret = "";
    for (let i = 0; i < l - 1;) {
      ret += T[i++];
    }
    ret += T[l - 1].substr(0, r);

    return ret;
  },

  /**
   * Compute the HTTP MAC SHA-1 for an HTTP request.
   *
   * @param  identifier
   *         (string) MAC Key Identifier.
   * @param  key
   *         (string) MAC Key.
   * @param  method
   *         (string) HTTP request method.
   * @param  URI
   *         (nsIURI) HTTP request URI.
   * @param  extra
   *         (object) Optional extra parameters. Valid keys are:
   *           nonce_bytes - How many bytes the nonce should be. This defaults
   *             to 8. Note that this many bytes are Base64 encoded, so the
   *             string length of the nonce will be longer than this value.
   *           ts - Timestamp to use. Should only be defined for testing.
   *           nonce - String nonce. Should only be defined for testing as this
   *             function will generate a cryptographically secure random one
   *             if not defined.
   *           ext - Extra string to be included in MAC. Per the HTTP MAC spec,
   *             the format is undefined and thus application specific.
   * @returns
   *         (object) Contains results of operation and input arguments (for
   *           symmetry). The object has the following keys:
   *
   *           identifier - (string) MAC Key Identifier (from arguments).
   *           key - (string) MAC Key (from arguments).
   *           method - (string) HTTP request method (from arguments).
   *           hostname - (string) HTTP hostname used (derived from arguments).
   *           port - (string) HTTP port number used (derived from arguments).
   *           mac - (string) Raw HMAC digest bytes.
   *           getHeader - (function) Call to obtain the string Authorization
   *             header value for this invocation.
   *           nonce - (string) Nonce value used.
   *           ts - (number) Integer seconds since Unix epoch that was used.
   */
  computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
                                                  uri, extra) {
    let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
    let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;

    // We are allowed to use more than the Base64 alphabet if we want.
    let nonce = (extra && extra.nonce)
                ? extra.nonce
                : btoa(CryptoUtils.generateRandomBytes(nonce_bytes));

    let host = uri.asciiHost;
    let port;
    let usedMethod = method.toUpperCase();

    if (uri.port != -1) {
      port = uri.port;
    } else if (uri.scheme == "http") {
      port = "80";
    } else if (uri.scheme == "https") {
      port = "443";
    } else {
      throw new Error("Unsupported URI scheme: " + uri.scheme);
    }

    let ext = (extra && extra.ext) ? extra.ext : "";

    let requestString = ts.toString(10) + "\n" +
                        nonce + "\n" +
                        usedMethod + "\n" +
                        uri.path + "\n" +
                        host + "\n" +
                        port + "\n" +
                        ext + "\n";

    let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
                                            CryptoUtils.makeHMACKey(key));
    let mac = CryptoUtils.digestBytes(requestString, hasher);

    function getHeader() {
      return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
                                              this.nonce, this.mac, this.ext);
    }

    return {
      identifier,
      key,
      method:     usedMethod,
      hostname:   host,
      port,
      mac,
      nonce,
      ts,
      ext,
      getHeader
    };
  },


  /**
   * Obtain the HTTP MAC Authorization header value from fields.
   *
   * @param  identifier
   *         (string) MAC key identifier.
   * @param  ts
   *         (number) Integer seconds since Unix epoch.
   * @param  nonce
   *         (string) Nonce value.
   * @param  mac
   *         (string) Computed HMAC digest (raw bytes).
   * @param  ext
   *         (optional) (string) Extra string content.
   * @returns
   *         (string) Value to put in Authorization header.
   */
  getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
                                                      mac, ext) {
    let header = 'MAC id="' + identifier + '", ' +
                'ts="' + ts + '", ' +
                'nonce="' + nonce + '", ' +
                'mac="' + btoa(mac) + '"';

    if (!ext) {
      return header;
    }

    return header += ', ext="' + ext + '"';
  },

  /**
   * Given an HTTP header value, strip out any attributes.
   */

  stripHeaderAttributes(value) {
    value = value || "";
    let i = value.indexOf(";");
    return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
  },

  /**
   * Compute the HAWK client values (mostly the header) for an HTTP request.
   *
   * @param  URI
   *         (nsIURI) HTTP request URI.
   * @param  method
   *         (string) HTTP request method.
   * @param  options
   *         (object) extra parameters (all but "credentials" are optional):
   *           credentials - (object, mandatory) HAWK credentials object.
   *             All three keys are required:
   *             id - (string) key identifier
   *             key - (string) raw key bytes
   *             algorithm - (string) which hash to use: "sha1" or "sha256"
   *           ext - (string) application-specific data, included in MAC
   *           localtimeOffsetMsec - (number) local clock offset (vs server)
   *           payload - (string) payload to include in hash, containing the
   *                     HTTP request body. If not provided, the HAWK hash
   *                     will not cover the request body, and the server
   *                     should not check it either. This will be UTF-8
   *                     encoded into bytes before hashing. This function
   *                     cannot handle arbitrary binary data, sorry (the
   *                     UTF-8 encoding process will corrupt any codepoints
   *                     between U+0080 and U+00FF). Callers must be careful
   *                     to use an HTTP client function which encodes the
   *                     payload exactly the same way, otherwise the hash
   *                     will not match.
   *           contentType - (string) payload Content-Type. This is included
   *                         (without any attributes like "charset=") in the
   *                         HAWK hash. It does *not* affect interpretation
   *                         of the "payload" property.
   *           hash - (base64 string) pre-calculated payload hash. If
   *                  provided, "payload" is ignored.
   *           ts - (number) pre-calculated timestamp, secs since epoch
   *           now - (number) current time, ms-since-epoch, for tests
   *           nonce - (string) pre-calculated nonce. Should only be defined
   *                   for testing as this function will generate a
   *                   cryptographically secure random one if not defined.
   * @returns
   *         (object) Contains results of operation. The object has the
   *         following keys:
   *           field - (string) HAWK header, to use in Authorization: header
   *           artifacts - (object) other generated values:
   *             ts - (number) timestamp, in seconds since epoch
   *             nonce - (string)
   *             method - (string)
   *             resource - (string) path plus querystring
   *             host - (string)
   *             port - (number)
   *             hash - (string) payload hash (base64)
   *             ext - (string) app-specific data
   *             MAC - (string) request MAC (base64)
   */
  computeHAWK(uri, method, options) {
    let credentials = options.credentials;
    let ts = options.ts || Math.floor(((options.now || Date.now()) +
                                       (options.localtimeOffsetMsec || 0))
                                      / 1000);

    let hash_algo, hmac_algo;
    if (credentials.algorithm == "sha1") {
      hash_algo = Ci.nsICryptoHash.SHA1;
      hmac_algo = Ci.nsICryptoHMAC.SHA1;
    } else if (credentials.algorithm == "sha256") {
      hash_algo = Ci.nsICryptoHash.SHA256;
      hmac_algo = Ci.nsICryptoHMAC.SHA256;
    } else {
      throw new Error("Unsupported algorithm: " + credentials.algorithm);
    }

    let port;
    if (uri.port != -1) {
      port = uri.port;
    } else if (uri.scheme == "http") {
      port = 80;
    } else if (uri.scheme == "https") {
      port = 443;
    } else {
      throw new Error("Unsupported URI scheme: " + uri.scheme);
    }

    let artifacts = {
      ts,
      nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
      method: method.toUpperCase(),
      resource: uri.path, // This includes both path and search/queryarg.
      host: uri.asciiHost.toLowerCase(), // This includes punycoding.
      port: port.toString(10),
      hash: options.hash,
      ext: options.ext,
    };

    let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);

    if (!artifacts.hash && options.hasOwnProperty("payload")
        && options.payload) {
      let hasher = Cc["@mozilla.org/security/hash;1"]
                     .createInstance(Ci.nsICryptoHash);
      hasher.init(hash_algo);
      CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
      CryptoUtils.updateUTF8(contentType + "\n", hasher);
      CryptoUtils.updateUTF8(options.payload, hasher);
      CryptoUtils.updateUTF8("\n", hasher);
      let hash = hasher.finish(false);
      // HAWK specifies this .hash to use +/ (not _-) and include the
      // trailing "==" padding.
      let hash_b64 = btoa(hash);
      artifacts.hash = hash_b64;
    }

    let requestString = ("hawk.1.header\n" +
                         artifacts.ts.toString(10) + "\n" +
                         artifacts.nonce + "\n" +
                         artifacts.method + "\n" +
                         artifacts.resource + "\n" +
                         artifacts.host + "\n" +
                         artifacts.port + "\n" +
                         (artifacts.hash || "") + "\n");
    if (artifacts.ext) {
      requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
    }
    requestString += "\n";

    let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
                                            CryptoUtils.makeHMACKey(credentials.key));
    artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
    // The output MAC uses "+" and "/", and padded== .

    function escape(attribute) {
      // This is used for "x=y" attributes inside HTTP headers.
      return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
    }
    let header = ('Hawk id="' + credentials.id + '", ' +
                  'ts="' + artifacts.ts + '", ' +
                  'nonce="' + artifacts.nonce + '", ' +
                  (artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
                  (artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
                  'mac="' + artifacts.mac + '"');
    return {
      artifacts,
      field: header,
    };
  },

};

XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";

  return converter;
});

var Svc = {};

XPCOMUtils.defineLazyServiceGetter(Svc,
                                   "KeyFactory",
                                   "@mozilla.org/security/keyobjectfactory;1",
                                   "nsIKeyObjectFactory");

Observers.add("xpcom-shutdown", function unloadServices() {
  Observers.remove("xpcom-shutdown", unloadServices);

  for (let k in Svc) {
    delete Svc[k];
  }
});
PK
!<go%%$modules/services-sync/SyncedTabs.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["SyncedTabs"];


const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://gre/modules/Preferences.jsm");

// The Sync XPCOM service
XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
  return Cc["@mozilla.org/weave/service;1"]
           .getService(Ci.nsISupports)
           .wrappedJSObject;
});

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

// from MDN...
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// A topic we fire whenever we have new tabs available. This might be due
// to a request made by this module to refresh the tab list, or as the result
// of a regularly scheduled sync. The intent is that consumers just listen
// for this notification and update their UI in response.
const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";

// The interval, in seconds, before which we consider the existing list
// of tabs "fresh enough" and don't force a new sync.
const TABS_FRESH_ENOUGH_INTERVAL = 30;

let log = Log.repository.getLogger("Sync.RemoteTabs");
// A new scope to do the logging thang...
(function() {
  let level = Preferences.get("services.sync.log.logger.tabs");
  if (level) {
    let appender = new Log.DumpAppender();
    log.level = appender.level = Log.Level[level] || Log.Level.Debug;
    log.addAppender(appender);
  }
})();


// A private singleton that does the work.
let SyncedTabsInternal = {
  /* Make a "tab" record. Returns a promise */
  async _makeTab(client, tab, url, showRemoteIcons) {
    let icon;
    if (showRemoteIcons) {
      icon = tab.icon;
    }
    if (!icon) {
      try {
        icon = (await PlacesUtils.promiseFaviconLinkUrl(url)).spec;
      } catch (ex) { /* no favicon avaiable */ }
    }
    if (!icon) {
      icon = "";
    }
    return {
      type:  "tab",
      title: tab.title || url,
      url,
      icon,
      client: client.id,
      lastUsed: tab.lastUsed,
    };
  },

  /* Make a "client" record. Returns a promise for consistency with _makeTab */
  async _makeClient(client) {
    return {
      id: client.id,
      type: "client",
      name: Weave.Service.clientsEngine.getClientName(client.id),
      isMobile: Weave.Service.clientsEngine.isMobile(client.id),
      lastModified: client.lastModified * 1000, // sec to ms
      tabs: []
    };
  },

  _tabMatchesFilter(tab, filter) {
    let reFilter = new RegExp(escapeRegExp(filter), "i");
    return tab.url.match(reFilter) || tab.title.match(reFilter);
  },

  async getTabClients(filter) {
    log.info("Generating tab list with filter", filter);
    let result = [];

    // If Sync isn't ready, don't try and get anything.
    if (!weaveXPCService.ready) {
      log.debug("Sync isn't yet ready, so returning an empty tab list");
      return result;
    }

    // A boolean that controls whether we should show the icon from the remote tab.
    const showRemoteIcons = Preferences.get("services.sync.syncedTabs.showRemoteIcons", true);

    let engine = Weave.Service.engineManager.get("tabs");

    let ntabs = 0;

    for (let client of Object.values(engine.getAllClients())) {
      if (!Weave.Service.clientsEngine.remoteClientExists(client.id)) {
        continue;
      }
      let clientRepr = await this._makeClient(client);
      log.debug("Processing client", clientRepr);

      for (let tab of client.tabs) {
        let url = tab.urlHistory[0];
        log.debug("remote tab", url);

        if (!url) {
          continue;
        }
        let tabRepr = await this._makeTab(client, tab, url, showRemoteIcons);
        if (filter && !this._tabMatchesFilter(tabRepr, filter)) {
          continue;
        }
        clientRepr.tabs.push(tabRepr);
      }
      // We return all clients, even those without tabs - the consumer should
      // filter it if they care.
      ntabs += clientRepr.tabs.length;
      result.push(clientRepr);
    }
    log.info(`Final tab list has ${result.length} clients with ${ntabs} tabs.`);
    return result;
  },

  async syncTabs(force) {
    if (!force) {
      // Don't bother refetching tabs if we already did so recently
      let lastFetch = Preferences.get("services.sync.lastTabFetch", 0);
      let now = Math.floor(Date.now() / 1000);
      if (now - lastFetch < TABS_FRESH_ENOUGH_INTERVAL) {
        log.info("_refetchTabs was done recently, do not doing it again");
        return false;
      }
    }

    // If Sync isn't configured don't try and sync, else we will get reports
    // of a login failure.
    if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
      log.info("Sync client is not configured, so not attempting a tab sync");
      return false;
    }
    // Ask Sync to just do the tabs engine if it can.
    try {
      log.info("Doing a tab sync.");
      await Weave.Service.sync(["tabs"]);
      return true;
    } catch (ex) {
      log.error("Sync failed", ex);
      throw ex;
    }
  },

  observe(subject, topic, data) {
    log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
    switch (topic) {
      case "weave:engine:sync:finish":
        if (data != "tabs") {
          return;
        }
        // The tabs engine just finished syncing
        // Set our lastTabFetch pref here so it tracks both explicit sync calls
        // and normally scheduled ones.
        Preferences.set("services.sync.lastTabFetch", Math.floor(Date.now() / 1000));
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      case "weave:service:start-over":
        // start-over needs to notify so consumers find no tabs.
        Preferences.reset("services.sync.lastTabFetch");
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      case "nsPref:changed":
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      default:
        break;
    }
  },

  get loginFailed() {
    if (!weaveXPCService.ready) {
      log.debug("Sync isn't yet ready; assuming the login didn't fail");
      return false;
    }
    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
  },

  // Returns true if Sync is configured to Sync tabs, false otherwise
  get isConfiguredToSyncTabs() {
    if (!weaveXPCService.ready) {
      log.debug("Sync isn't yet ready; assuming tab engine is enabled");
      return true;
    }

    let engine = Weave.Service.engineManager.get("tabs");
    return engine && engine.enabled;
  },

  get hasSyncedThisSession() {
    let engine = Weave.Service.engineManager.get("tabs");
    return engine && engine.hasSyncedThisSession;
  },
};

Services.obs.addObserver(SyncedTabsInternal, "weave:engine:sync:finish");
Services.obs.addObserver(SyncedTabsInternal, "weave:service:start-over");
// Observe the pref the indicates the state of the tabs engine has changed.
// This will force consumers to re-evaluate the state of sync and update
// accordingly.
Services.prefs.addObserver("services.sync.engine.tabs", SyncedTabsInternal);

// The public interface.
this.SyncedTabs = {
  // A mock-point for tests.
  _internal: SyncedTabsInternal,

  // We make the topic for the observer notification public.
  TOPIC_TABS_CHANGED,

  // Returns true if Sync is configured to Sync tabs, false otherwise
  get isConfiguredToSyncTabs() {
    return this._internal.isConfiguredToSyncTabs;
  },

  // Returns true if a tab sync has completed once this session. If this
  // returns false, then getting back no clients/tabs possibly just means we
  // are waiting for that first sync to complete.
  get hasSyncedThisSession() {
    return this._internal.hasSyncedThisSession;
  },

  // Return a promise that resolves with an array of client records, each with
  // a .tabs array. Note that part of the contract for this module is that the
  // returned objects are not shared between invocations, so callers are free
  // to mutate the returned objects (eg, sort, truncate) however they see fit.
  getTabClients(query) {
    return this._internal.getTabClients(query);
  },

  // Starts a background request to start syncing tabs. Returns a promise that
  // resolves when the sync is complete, but there's no resolved value -
  // callers should be listening for TOPIC_TABS_CHANGED.
  // If |force| is true we always sync. If false, we only sync if the most
  // recent sync wasn't "recently".
  syncTabs(force) {
    return this._internal.syncTabs(force);
  },

  sortTabClientsByLastUsed(clients) {
    // First sort the list of tabs for each client. Note that
    // this module promises that the objects it returns are never
    // shared, so we are free to mutate those objects directly.
    for (let client of clients) {
      let tabs = client.tabs;
      tabs.sort((a, b) => b.lastUsed - a.lastUsed);
    }
    // Now sort the clients - the clients are sorted in the order of the
    // most recent tab for that client (ie, it is important the tabs for
    // each client are already sorted.)
    clients.sort((a, b) => {
      if (a.tabs.length == 0) {
        return 1; // b comes first.
      }
      if (b.tabs.length == 0) {
        return -1; // a comes first.
      }
      return b.tabs[0].lastUsed - a.tabs[0].lastUsed;
    });
  },
};
PK
!<ɕ!modules/services-sync/UIState.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

 /**
 * @typedef {Object} UIState
 * @property {string} status The Sync/FxA status, see STATUS_* constants.
 * @property {string} [email] The FxA email configured to log-in with Sync.
 * @property {string} [displayName] The user's FxA display name.
 * @property {string} [avatarURL] The user's FxA avatar URL.
 * @property {Date} [lastSync] The last sync time.
 * @property {boolean} [syncing] Whether or not we are currently syncing.
 */

this.EXPORTED_SYMBOLS = ["UIState"];

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, "Weave",
                                  "resource://services-sync/main.js");

const TOPICS = [
  "weave:service:login:change",
  "weave:service:login:error",
  "weave:service:ready",
  "weave:service:sync:start",
  "weave:service:sync:finish",
  "weave:service:sync:error",
  "fxaccounts:onverified",
  "fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
  "fxaccounts:onlogout",
  "fxaccounts:profilechange",
];

const ON_UPDATE = "sync-ui-state:update"

const STATUS_NOT_CONFIGURED = "not_configured";
const STATUS_LOGIN_FAILED = "login_failed";
const STATUS_NOT_VERIFIED = "not_verified";
const STATUS_SIGNED_IN = "signed_in";

const DEFAULT_STATE = {
  status: STATUS_NOT_CONFIGURED
};

const UIStateInternal = {
  _initialized: false,
  _state: null,

  // We keep _syncing out of the state object because we can only track it
  // using sync events and we can't determine it at any point in time.
  _syncing: false,

  get state() {
    if (!this._state) {
      return DEFAULT_STATE;
    }
    return Object.assign({}, this._state, { syncing: this._syncing });
  },

  isReady() {
    if (!this._initialized) {
      this.init();
      return false;
    }
    return true;
  },

  init() {
    this._initialized = true;
    if (!Services.prefs.prefHasUserValue("services.sync.username")) {
      return;
    }
    // Refresh the state in the background.
    this.refreshState().catch(e => {
      Cu.reportError(e);
    });
  },

  // Used for testing.
  reset() {
    this._state = null;
    this._syncing = false;
    this._initialized = false;
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "weave:service:sync:start":
        this.toggleSyncActivity(true);
        break;
      case "weave:service:sync:finish":
      case "weave:service:sync:error":
        this.toggleSyncActivity(false);
        break;
      default:
        this.refreshState().catch(e => {
          Cu.reportError(e);
        });
        break;
    }
  },

  // Builds a new state from scratch.
  async refreshState() {
    const newState = {};
    await this._refreshFxAState(newState);
    this._setLastSyncTime(newState); // We want this in case we change accounts.
    this._state = newState;

    this.notifyStateUpdated();
    return this.state;
  },

  // Update the current state with the last sync time/currently syncing status.
  toggleSyncActivity(syncing) {
    this._syncing = syncing;
    this._setLastSyncTime(this._state);

    this.notifyStateUpdated();
  },

  notifyStateUpdated() {
    Services.obs.notifyObservers(null, ON_UPDATE);
  },

  async _refreshFxAState(newState) {
    let userData = await this._getUserData();
    this._populateWithUserData(newState, userData);
    if (newState.status != STATUS_SIGNED_IN) {
      return;
    }
    let profile = await this._getProfile();
    if (!profile) {
      return;
    }
    this._populateWithProfile(newState, profile);
  },

  _populateWithUserData(state, userData) {
    let status;
    if (!userData) {
      status = STATUS_NOT_CONFIGURED;
    } else {
      if (this._loginFailed()) {
        status = STATUS_LOGIN_FAILED;
      } else if (!userData.verified) {
        status = STATUS_NOT_VERIFIED;
      } else {
        status = STATUS_SIGNED_IN;
      }
      state.email = userData.email;
    }
    state.status = status;
  },

  _populateWithProfile(state, profile) {
    state.displayName = profile.displayName;
    state.avatarURL = profile.avatar;
    // A hack to handle that the user's email address may have changed.
    // This can probably be removed as part of bug 1383663.
    state.email = profile.email;
  },

  async _getUserData() {
    try {
      return await this.fxAccounts.getSignedInUser();
    } catch (e) {
      // This is most likely in tests, where 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.
      Cu.reportError("Error updating FxA account info: " + e);
      return null;
    }
  },

  async _getProfile() {
    try {
      return await this.fxAccounts.getSignedInUserProfile();
    } catch (e) {
      // Not fetching the profile is sad but the FxA logs will already have noise.
      return null;
    }
  },

  _setLastSyncTime(state) {
    if (state.status == UIState.STATUS_SIGNED_IN) {
      try {
        state.lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync", null));
      } catch (_) {
        state.lastSync = null;
      }
    }
  },

  _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(Ci.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;
  },

  set fxAccounts(mockFxAccounts) {
    delete this.fxAccounts;
    this.fxAccounts = mockFxAccounts;
  }
};

XPCOMUtils.defineLazyModuleGetter(UIStateInternal, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");

for (let topic of TOPICS) {
  Services.obs.addObserver(UIStateInternal, topic);
}

this.UIState = {
  _internal: UIStateInternal,

  ON_UPDATE,

  STATUS_NOT_CONFIGURED,
  STATUS_LOGIN_FAILED,
  STATUS_NOT_VERIFIED,
  STATUS_SIGNED_IN,

  /**
   * Returns true if the module has been initialized and the state set.
   * If not, return false and trigger an init in the background.
   */
  isReady() {
    return this._internal.isReady();
  },

  /**
   * @returns {UIState} The current Sync/FxA UI State.
   */
  get() {
    return this._internal.state;
  },

  /**
   * Refresh the state. Used for testing, don't call this directly since
   * UIState already listens to Sync/FxA notifications to determine if the state
   * needs to be refreshed. ON_UPDATE will be fired once the state is refreshed.
   *
   * @returns {Promise<UIState>} Resolved once the state is refreshed.
   */
  refresh() {
    return this._internal.refreshState();
  },

  /**
   * Reset the state of the whole module. Used for testing.
   */
  reset() {
    this._internal.reset();
  }
};
PK
!<HmMmM)modules/services-sync/addonsreconciler.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains middleware to reconcile state of AddonManager for
 * purposes of tracking events for Sync. The content in this file exists
 * because AddonManager does not have a getChangesSinceX() API and adding
 * that functionality properly was deemed too time-consuming at the time
 * add-on sync was originally written. If/when AddonManager adds this API,
 * this file can go away and the add-ons engine can be rewritten to use it.
 *
 * It was decided to have this tracking functionality exist in a separate
 * standalone file so it could be more easily understood, tested, and
 * hopefully ported.
 */

"use strict";

var Cu = Components.utils;

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/AddonManager.jsm");

const DEFAULT_STATE_FILE = "addonsreconciler";

this.CHANGE_INSTALLED   = 1;
this.CHANGE_UNINSTALLED = 2;
this.CHANGE_ENABLED     = 3;
this.CHANGE_DISABLED    = 4;

this.EXPORTED_SYMBOLS = ["AddonsReconciler", "CHANGE_INSTALLED",
                         "CHANGE_UNINSTALLED", "CHANGE_ENABLED",
                         "CHANGE_DISABLED"];
/**
 * Maintains state of add-ons.
 *
 * State is maintained in 2 data structures, an object mapping add-on IDs
 * to metadata and an array of changes over time. The object mapping can be
 * thought of as a minimal copy of data from AddonManager which is needed for
 * Sync. The array is effectively a log of changes over time.
 *
 * The data structures are persisted to disk by serializing to a JSON file in
 * the current profile. The data structures are updated by 2 mechanisms. First,
 * they can be refreshed from the global state of the AddonManager. This is a
 * sure-fire way of ensuring the reconciler is up to date. Second, the
 * reconciler adds itself as an AddonManager listener. When it receives change
 * notifications, it updates its internal state incrementally.
 *
 * The internal state is persisted to a JSON file in the profile directory.
 *
 * An instance of this is bound to an AddonsEngine instance. In reality, it
 * likely exists as a singleton. To AddonsEngine, it functions as a store and
 * an entity which emits events for tracking.
 *
 * The usage pattern for instances of this class is:
 *
 *   let reconciler = new AddonsReconciler();
 *   await reconciler.ensureStateLoaded();
 *
 *   // At this point, your instance should be ready to use.
 *
 * When you are finished with the instance, please call:
 *
 *   reconciler.stopListening();
 *   await reconciler.saveState(...);
 *
 * There are 2 classes of listeners in the AddonManager: AddonListener and
 * InstallListener. This class is a listener for both (member functions just
 * get called directly).
 *
 * When an add-on is installed, listeners are called in the following order:
 *
 *  IL.onInstallStarted, AL.onInstalling, IL.onInstallEnded, AL.onInstalled
 *
 * For non-restartless add-ons, an application restart may occur between
 * IL.onInstallEnded and AL.onInstalled. Unfortunately, Sync likely will
 * not be loaded when AL.onInstalled is fired shortly after application
 * start, so it won't see this event. Therefore, for add-ons requiring a
 * restart, Sync treats the IL.onInstallEnded event as good enough to
 * indicate an install. For restartless add-ons, Sync assumes AL.onInstalled
 * will follow shortly after IL.onInstallEnded and thus it ignores
 * IL.onInstallEnded.
 *
 * The listeners can also see events related to the download of the add-on.
 * This class isn't interested in those. However, there are failure events,
 * IL.onDownloadFailed and IL.onDownloadCanceled which get called if a
 * download doesn't complete successfully.
 *
 * For uninstalls, we see AL.onUninstalling then AL.onUninstalled. Like
 * installs, the events could be separated by an application restart and Sync
 * may not see the onUninstalled event. Again, if we require a restart, we
 * react to onUninstalling. If not, we assume we'll get onUninstalled.
 *
 * Enabling and disabling work by sending:
 *
 *   AL.onEnabling, AL.onEnabled
 *   AL.onDisabling, AL.onDisabled
 *
 * Again, they may be separated by a restart, so we heed the requiresRestart
 * flag.
 *
 * Actions can be undone. All undoable actions notify the same
 * AL.onOperationCancelled event. We treat this event like any other.
 *
 * Restartless add-ons have interesting behavior during uninstall. These
 * add-ons are first disabled then they are actually uninstalled. So, we will
 * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled
 * events only come after the Addon Manager is closed or another view is
 * switched to. In the case of Sync performing the uninstall, the uninstall
 * events will occur immediately. However, we still see disabling events and
 * heed them like they were normal. In the end, the state is proper.
 */
this.AddonsReconciler = function AddonsReconciler() {
  this._log = Log.repository.getLogger("Sync.AddonsReconciler");
  let level = Svc.Prefs.get("log.logger.addonsreconciler", "Debug");
  this._log.level = Log.Level[level];

  Svc.Obs.add("xpcom-shutdown", this.stopListening, this);
};
AddonsReconciler.prototype = {
  /** Flag indicating whether we are listening to AddonManager events. */
  _listening: false,

  /**
   * Define this as false if the reconciler should not persist state
   * to disk when handling events.
   *
   * This allows test code to avoid spinning to write during observer
   * notifications and xpcom shutdown, which appears to cause hangs on WinXP
   * (Bug 873861).
   */
  _shouldPersist: true,

  /** Log logger instance */
  _log: null,

  /**
   * Container for add-on metadata.
   *
   * Keys are add-on IDs. Values are objects which describe the state of the
   * add-on. This is a minimal mirror of data that can be queried from
   * AddonManager. In some cases, we retain data longer than AddonManager.
   */
  _addons: {},

  /**
   * List of add-on changes over time.
   *
   * Each element is an array of [time, change, id].
   */
  _changes: [],

  /**
   * Objects subscribed to changes made to this instance.
   */
  _listeners: [],

  /**
   * Accessor for add-ons in this object.
   *
   * Returns an object mapping add-on IDs to objects containing metadata.
   */
  get addons() {
    return this._addons;
  },

  async ensureStateLoaded() {
    if (!this._promiseStateLoaded) {
      this._promiseStateLoaded = this.loadState();
    }
    return this._promiseStateLoaded;
  },

  /**
   * Load reconciler state from a file.
   *
   * The path is relative to the weave directory in the profile. If no
   * path is given, the default one is used.
   *
   * If the file does not exist or there was an error parsing the file, the
   * state will be transparently defined as empty.
   *
   * @param file
   *        Path to load. ".json" is appended automatically. If not defined,
   *        a default path will be consulted.
   */
  async loadState(file = DEFAULT_STATE_FILE) {
    let json = await Utils.jsonLoad(file, this);
    this._addons = {};
    this._changes = [];

    if (!json) {
      this._log.debug("No data seen in loaded file: " + file);
      return false;
    }

    let version = json.version;
    if (!version || version != 1) {
      this._log.error("Could not load JSON file because version not " +
                      "supported: " + version);
      return false;
    }

    this._addons = json.addons;
    for (let id in this._addons) {
      let record = this._addons[id];
      record.modified = new Date(record.modified);
    }

    for (let [time, change, id] of json.changes) {
      this._changes.push([new Date(time), change, id]);
    }

    return true;
  },

  /**
   * Saves the current state to a file in the local profile.
   *
   * @param  file
   *         String path in profile to save to. If not defined, the default
   *         will be used.
   */
  async saveState(file = DEFAULT_STATE_FILE) {
    let state = {version: 1, addons: {}, changes: []};

    for (let [id, record] of Object.entries(this._addons)) {
      state.addons[id] = {};
      for (let [k, v] of Object.entries(record)) {
        if (k == "modified") {
          state.addons[id][k] = v.getTime();
        } else {
          state.addons[id][k] = v;
        }
      }
    }

    for (let [time, change, id] of this._changes) {
      state.changes.push([time.getTime(), change, id]);
    }

    this._log.info("Saving reconciler state to file: " + file);
    await Utils.jsonSave(file, this, state);
  },

  /**
   * Registers a change listener with this instance.
   *
   * Change listeners are called every time a change is recorded. The listener
   * is an object with the function "changeListener" that takes 3 arguments,
   * the Date at which the change happened, the type of change (a CHANGE_*
   * constant), and the add-on state object reflecting the current state of
   * the add-on at the time of the change.
   *
   * @param listener
   *        Object containing changeListener function.
   */
  addChangeListener: function addChangeListener(listener) {
    if (this._listeners.indexOf(listener) == -1) {
      this._log.debug("Adding change listener.");
      this._listeners.push(listener);
    }
  },

  /**
   * Removes a previously-installed change listener from the instance.
   *
   * @param listener
   *        Listener instance to remove.
   */
  removeChangeListener: function removeChangeListener(listener) {
    this._listeners = this._listeners.filter(element => {
      if (element == listener) {
        this._log.debug("Removing change listener.");
        return false;
      }
      return true;
    });
  },

  /**
   * Tells the instance to start listening for AddonManager changes.
   *
   * This is typically called automatically when Sync is loaded.
   */
  startListening: function startListening() {
    if (this._listening) {
      return;
    }

    this._log.info("Registering as Add-on Manager listener.");
    AddonManager.addAddonListener(this);
    AddonManager.addInstallListener(this);
    this._listening = true;
  },

  /**
   * Tells the instance to stop listening for AddonManager changes.
   *
   * The reconciler should always be listening. This should only be called when
   * the instance is being destroyed.
   *
   * This function will get called automatically on XPCOM shutdown. However, it
   * is a best practice to call it yourself.
   */
  stopListening: function stopListening() {
    if (!this._listening) {
      return;
    }

    this._log.debug("Stopping listening and removing AddonManager listeners.");
    AddonManager.removeInstallListener(this);
    AddonManager.removeAddonListener(this);
    this._listening = false;
  },

  /**
   * Refreshes the global state of add-ons by querying the AddonManager.
   */
  async refreshGlobalState() {
    this._log.info("Refreshing global state from AddonManager.");

    let installs;
    let addons = await AddonManager.getAllAddons();

    let ids = {};

    for (let addon of addons) {
      ids[addon.id] = true;
      this.rectifyStateFromAddon(addon);
    }

    // Look for locally-defined add-ons that no longer exist and update their
    // record.
    for (let [id, addon] of Object.entries(this._addons)) {
      if (id in ids) {
        continue;
      }

      // If the id isn't in ids, it means that the add-on has been deleted or
      // the add-on is in the process of being installed. We detect the
      // latter by seeing if an AddonInstall is found for this add-on.

      if (!installs) {
        installs = await AddonManager.getAllInstalls();
      }

      let installFound = false;
      for (let install of installs) {
        if (install.addon && install.addon.id == id &&
            install.state == AddonManager.STATE_INSTALLED) {

          installFound = true;
          break;
        }
      }

      if (installFound) {
        continue;
      }

      if (addon.installed) {
        addon.installed = false;
        this._log.debug("Adding change because add-on not present in " +
                        "Add-on Manager: " + id);
        this._addChange(new Date(), CHANGE_UNINSTALLED, addon);
      }
    }

    // See note for _shouldPersist.
    if (this._shouldPersist) {
      await this.saveState();
    }
  },

  /**
   * Rectifies the state of an add-on from an Addon instance.
   *
   * This basically says "given an Addon instance, assume it is truth and
   * apply changes to the local state to reflect it."
   *
   * This function could result in change listeners being called if the local
   * state differs from the passed add-on's state.
   *
   * @param addon
   *        Addon instance being updated.
   */
  rectifyStateFromAddon(addon) {
    this._log.debug(`Rectifying state for addon ${addon.name} (version=${addon.version}, id=${addon.id})`);

    let id = addon.id;
    let enabled = !addon.userDisabled;
    let guid = addon.syncGUID;
    let now = new Date();

    if (!(id in this._addons)) {
      let record = {
        id,
        guid,
        enabled,
        installed: true,
        modified: now,
        type: addon.type,
        scope: addon.scope,
        foreignInstall: addon.foreignInstall,
        isSyncable: addon.isSyncable,
      };
      this._addons[id] = record;
      this._log.debug("Adding change because add-on not present locally: " +
                      id);
      this._addChange(now, CHANGE_INSTALLED, record);
      return;
    }

    let record = this._addons[id];
    record.isSyncable = addon.isSyncable;

    if (!record.installed) {
      // It is possible the record is marked as uninstalled because an
      // uninstall is pending.
      if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) {
        record.installed = true;
        record.modified = now;
      }
    }

    if (record.enabled != enabled) {
      record.enabled = enabled;
      record.modified = now;
      let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED;
      this._log.debug("Adding change because enabled state changed: " + id);
      this._addChange(new Date(), change, record);
    }

    if (record.guid != guid) {
      record.guid = guid;
      // We don't record a change because the Sync engine rectifies this on its
      // own. This is tightly coupled with Sync. If this code is ever lifted
      // outside of Sync, this exception should likely be removed.
    }
  },

  /**
   * Record a change in add-on state.
   *
   * @param date
   *        Date at which the change occurred.
   * @param change
   *        The type of the change. A CHANGE_* constant.
   * @param state
   *        The new state of the add-on. From this.addons.
   */
  _addChange: function _addChange(date, change, state) {
    this._log.info("Change recorded for " + state.id);
    this._changes.push([date, change, state.id]);

    for (let listener of this._listeners) {
      try {
        listener.changeListener(date, change, state);
      } catch (ex) {
        this._log.warn("Exception calling change listener", ex);
      }
    }
  },

  /**
   * Obtain the set of changes to add-ons since the date passed.
   *
   * This will return an array of arrays. Each entry in the array has the
   * elements [date, change_type, id], where
   *
   *   date - Date instance representing when the change occurred.
   *   change_type - One of CHANGE_* constants.
   *   id - ID of add-on that changed.
   */
  getChangesSinceDate(date) {
    let length = this._changes.length;
    for (let i = 0; i < length; i++) {
      if (this._changes[i][0] >= date) {
        return this._changes.slice(i);
      }
    }

    return [];
  },

  /**
   * Prunes all recorded changes from before the specified Date.
   *
   * @param date
   *        Entries older than this Date will be removed.
   */
  pruneChangesBeforeDate(date) {
    this._changes = this._changes.filter(function test_age(change) {
      return change[0] >= date;
    });
  },

  /**
   * Obtains the set of all known Sync GUIDs for add-ons.
   */
  getAllSyncGUIDs() {
    let result = {};
    for (let id in this.addons) {
      result[id] = true;
    }

    return result;
  },

  /**
   * Obtain the add-on state record for an add-on by Sync GUID.
   *
   * If the add-on could not be found, returns null.
   *
   * @param  guid
   *         Sync GUID of add-on to retrieve.
   */
  getAddonStateFromSyncGUID(guid) {
    for (let id in this.addons) {
      let addon = this.addons[id];
      if (addon.guid == guid) {
        return addon;
      }
    }

    return null;
  },

  /**
   * Handler that is invoked as part of the AddonManager listeners.
   */
  _handleListener(action, addon, requiresRestart) {
    // Since this is called as an observer, we explicitly trap errors and
    // log them to ourselves so we don't see errors reported elsewhere.
    try {
      let id = addon.id;
      this._log.debug("Add-on change: " + action + " to " + id);

      // We assume that every event for non-restartless add-ons is
      // followed by another event and that this follow-up event is the most
      // appropriate to react to. Currently we ignore onEnabling, onDisabling,
      // and onUninstalling for non-restartless add-ons.
      if (requiresRestart === false) {
        this._log.debug("Ignoring " + action + " for restartless add-on.");
        return;
      }

      switch (action) {
        case "onEnabling":
        case "onEnabled":
        case "onDisabling":
        case "onDisabled":
        case "onInstalled":
        case "onInstallEnded":
        case "onOperationCancelled":
          this.rectifyStateFromAddon(addon);
          break;

        case "onUninstalling":
        case "onUninstalled":
          let id = addon.id;
          let addons = this.addons;
          if (id in addons) {
            let now = new Date();
            let record = addons[id];
            record.installed = false;
            record.modified = now;
            this._log.debug("Adding change because of uninstall listener: " +
                            id);
            this._addChange(now, CHANGE_UNINSTALLED, record);
          }
      }

      // See note for _shouldPersist.
      if (this._shouldPersist) {
        Async.promiseSpinningly(this.saveState());
      }
    } catch (ex) {
      this._log.warn("Exception", ex);
    }
  },

  // AddonListeners
  onEnabling: function onEnabling(addon, requiresRestart) {
    this._handleListener("onEnabling", addon, requiresRestart);
  },
  onEnabled: function onEnabled(addon) {
    this._handleListener("onEnabled", addon);
  },
  onDisabling: function onDisabling(addon, requiresRestart) {
    this._handleListener("onDisabling", addon, requiresRestart);
  },
  onDisabled: function onDisabled(addon) {
    this._handleListener("onDisabled", addon);
  },
  onInstalling: function onInstalling(addon, requiresRestart) {
    this._handleListener("onInstalling", addon, requiresRestart);
  },
  onInstalled: function onInstalled(addon) {
    this._handleListener("onInstalled", addon);
  },
  onUninstalling: function onUninstalling(addon, requiresRestart) {
    this._handleListener("onUninstalling", addon, requiresRestart);
  },
  onUninstalled: function onUninstalled(addon) {
    this._handleListener("onUninstalled", addon);
  },
  onOperationCancelled: function onOperationCancelled(addon) {
    this._handleListener("onOperationCancelled", addon);
  },

  // InstallListeners
  onInstallEnded: function onInstallEnded(install, addon) {
    this._handleListener("onInstallEnded", addon);
  }
};
PK
!<;
44#modules/services-sync/addonutils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["AddonUtils"];

var {interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/util.js");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
  "resource://gre/modules/addons/AddonRepository.jsm");

function AddonUtilsInternal() {
  this._log = Log.repository.getLogger("Sync.AddonUtils");
  this._log.Level = Log.Level[Svc.Prefs.get("log.logger.addonutils")];
}
AddonUtilsInternal.prototype = {
  /**
   * Obtain an AddonInstall object from an AddonSearchResult instance.
   *
   * The callback will be invoked with the result of the operation. The
   * callback receives 2 arguments, error and result. Error will be falsy
   * on success or some kind of error value otherwise. The result argument
   * will be an AddonInstall on success or null on failure. It is possible
   * for the error to be falsy but result to be null. This could happen if
   * an install was not found.
   *
   * @param addon
   *        AddonSearchResult to obtain install from.
   * @param cb
   *        Function to be called with result of operation.
   */
  getInstallFromSearchResult:
    function getInstallFromSearchResult(addon, cb) {

    this._log.debug("Obtaining install for " + addon.id);

    // We should theoretically be able to obtain (and use) addon.install if
    // it is available. However, the addon.sourceURI rewriting won't be
    // reflected in the AddonInstall, so we can't use it. If we ever get rid
    // of sourceURI rewriting, we can avoid having to reconstruct the
    // AddonInstall.
    AddonManager.getInstallForURL(
      addon.sourceURI.spec,
      function handleInstall(install) {
        cb(null, install);
      },
      "application/x-xpinstall",
      undefined,
      addon.name,
      addon.iconURL,
      addon.version
    );
  },

  /**
   * Installs an add-on from an AddonSearchResult instance.
   *
   * The options argument defines extra options to control the install.
   * Recognized keys in this map are:
   *
   *   syncGUID - Sync GUID to use for the new add-on.
   *   enabled - Boolean indicating whether the add-on should be enabled upon
   *             install.
   *
   * When complete it calls a callback with 2 arguments, error and result.
   *
   * If error is falsy, result is an object. If error is truthy, result is
   * null.
   *
   * The result object has the following keys:
   *
   *   id      ID of add-on that was installed.
   *   install AddonInstall that was installed.
   *   addon   Addon that was installed.
   *
   * @param addon
   *        AddonSearchResult to install add-on from.
   * @param options
   *        Object with additional metadata describing how to install add-on.
   * @param cb
   *        Function to be invoked with result of operation.
   */
  installAddonFromSearchResult:
    function installAddonFromSearchResult(addon, options, cb) {
    this._log.info("Trying to install add-on from search result: " + addon.id);

    this.getInstallFromSearchResult(addon, (error, install) => {
      if (error) {
        cb(error, null);
        return;
      }

      if (!install) {
        cb(new Error("AddonInstall not available: " + addon.id), null);
        return;
      }

      try {
        this._log.info("Installing " + addon.id);
        let log = this._log;

        let listener = {
          onInstallStarted: function onInstallStarted(install) {
            if (!options) {
              return;
            }

            if (options.syncGUID) {
              log.info("Setting syncGUID of " + install.name + ": " +
                       options.syncGUID);
              install.addon.syncGUID = options.syncGUID;
            }

            // We only need to change userDisabled if it is disabled because
            // enabled is the default.
            if ("enabled" in options && !options.enabled) {
              log.info("Marking add-on as disabled for install: " +
                       install.name);
              install.addon.userDisabled = true;
            }
          },
          onInstallEnded(install, addon) {
            install.removeListener(listener);

            cb(null, {id: addon.id, install, addon});
          },
          onInstallFailed(install) {
            install.removeListener(listener);

            cb(new Error("Install failed: " + install.error), null);
          },
          onDownloadFailed(install) {
            install.removeListener(listener);

            cb(new Error("Download failed: " + install.error), null);
          }
        };
        install.addListener(listener);
        install.install();
      } catch (ex) {
        this._log.error("Error installing add-on", ex);
        cb(ex, null);
      }
    });
  },

  /**
   * Uninstalls the addon instance.
   *
   * @param addon
   *        Addon instance to uninstall.
   */
  async uninstallAddon(addon) {
    return new Promise(res => {
      let listener = {
        onUninstalling(uninstalling, needsRestart) {
          if (addon.id != uninstalling.id) {
            return;
          }

          // We assume restartless add-ons will send the onUninstalled event
          // soon.
          if (!needsRestart) {
            return;
          }

          // For non-restartless add-ons, we issue the callback on uninstalling
          // because we will likely never see the uninstalled event.
          AddonManager.removeAddonListener(listener);
          res(addon);
        },
        onUninstalled(uninstalled) {
          if (addon.id != uninstalled.id) {
            return;
          }

          AddonManager.removeAddonListener(listener);
          res(addon);
        }
      };
      AddonManager.addAddonListener(listener);
      addon.uninstall();
    });
  },

  /**
   * Installs multiple add-ons specified by metadata.
   *
   * The first argument is an array of objects. Each object must have the
   * following keys:
   *
   *   id - public ID of the add-on to install.
   *   syncGUID - syncGUID for new add-on.
   *   enabled - boolean indicating whether the add-on should be enabled.
   *   requireSecureURI - Boolean indicating whether to require a secure
   *     URI when installing from a remote location. This defaults to
   *     true.
   *
   * The callback will be called when activity on all add-ons is complete. The
   * callback receives 2 arguments, error and result.
   *
   * If error is truthy, it contains a string describing the overall error.
   *
   * The 2nd argument to the callback is always an object with details on the
   * overall execution state. It contains the following keys:
   *
   *   installedIDs  Array of add-on IDs that were installed.
   *   installs      Array of AddonInstall instances that were installed.
   *   addons        Array of Addon instances that were installed.
   *   errors        Array of errors encountered. Only has elements if error is
   *                 truthy.
   *
   * @param installs
   *        Array of objects describing add-ons to install.
   * @param cb
   *        Function to be called when all actions are complete.
   */
  installAddons: function installAddons(installs, cb) {
    if (!cb) {
      throw new Error("Invalid argument: cb is not defined.");
    }

    let ids = [];
    for (let addon of installs) {
      ids.push(addon.id);
    }

    AddonRepository.getAddonsByIDs(ids, {
      searchSucceeded: (addons, addonsLength, total) => {
        this._log.info("Found " + addonsLength + "/" + ids.length +
                       " add-ons during repository search.");

        let ourResult = {
          installedIDs: [],
          installs:     [],
          addons:       [],
          skipped:      [],
          errors:       []
        };

        if (!addonsLength) {
          cb(null, ourResult);
          return;
        }

        let expectedInstallCount = 0;
        let finishedCount = 0;
        let installCallback = function installCallback(error, result) {
          finishedCount++;

          if (error) {
            ourResult.errors.push(error);
          } else {
            ourResult.installedIDs.push(result.id);
            ourResult.installs.push(result.install);
            ourResult.addons.push(result.addon);
          }

          if (finishedCount >= expectedInstallCount) {
            if (ourResult.errors.length > 0) {
              cb(new Error("1 or more add-ons failed to install"), ourResult);
            } else {
              cb(null, ourResult);
            }
          }
        };

        let toInstall = [];

        // Rewrite the "src" query string parameter of the source URI to note
        // that the add-on was installed by Sync and not something else so
        // server-side metrics aren't skewed (bug 708134). The server should
        // ideally send proper URLs, but this solution was deemed too
        // complicated at the time the functionality was implemented.
        for (let addon of addons) {
          // Find the specified options for this addon.
          let options;
          for (let install of installs) {
            if (install.id == addon.id) {
              options = install;
              break;
            }
          }
          if (!this.canInstallAddon(addon, options)) {
            ourResult.skipped.push(addon.id);
            continue;
          }

          // We can go ahead and attempt to install it.
          toInstall.push(addon);

          // We should always be able to QI the nsIURI to nsIURL. If not, we
          // still try to install the add-on, but we don't rewrite the URL,
          // potentially skewing metrics.
          try {
            addon.sourceURI.QueryInterface(Ci.nsIURL);
          } catch (ex) {
            this._log.warn("Unable to QI sourceURI to nsIURL: " +
                           addon.sourceURI.spec);
            continue;
          }

          let params = addon.sourceURI.query.split("&").map(
            function rewrite(param) {

            if (param.indexOf("src=") == 0) {
              return "src=sync";
            }
            return param;
          });

          addon.sourceURI.query = params.join("&");
        }

        expectedInstallCount = toInstall.length;

        if (!expectedInstallCount) {
          cb(null, ourResult);
          return;
        }

        // Start all the installs asynchronously. They will report back to us
        // as they finish, eventually triggering the global callback.
        for (let addon of toInstall) {
          let options = {};
          for (let install of installs) {
            if (install.id == addon.id) {
              options = install;
              break;
            }
          }

          this.installAddonFromSearchResult(addon, options, installCallback);
        }

      },

      searchFailed: function searchFailed() {
        cb(new Error("AddonRepository search failed"), null);
      },
    });
  },

  /**
   * Returns true if we are able to install the specified addon, false
   * otherwise. It is expected that this will log the reason if it returns
   * false.
   *
   * @param addon
   *        (Addon) Add-on instance to check.
   * @param options
   *        (object) The options specified for this addon. See installAddons()
   *        for the valid elements.
   */
  canInstallAddon(addon, options) {
    // sourceURI presence isn't enforced by AddonRepository. So, we skip
    // add-ons without a sourceURI.
    if (!addon.sourceURI) {
      this._log.info("Skipping install of add-on because missing " +
                     "sourceURI: " + addon.id);
      return false;
    }
    // Verify that the source URI uses TLS. We don't allow installs from
    // insecure sources for security reasons. The Addon Manager ensures
    // that cert validation etc is performed.
    // (We should also consider just dropping this entirely and calling
    // XPIProvider.isInstallAllowed, but that has additional semantics we might
    // need to think through...)
    let requireSecureURI = true;
    if (options && options.requireSecureURI !== undefined) {
      requireSecureURI = options.requireSecureURI;
    }

    if (requireSecureURI) {
      let scheme = addon.sourceURI.scheme;
      if (scheme != "https") {
        this._log.info(`Skipping install of add-on "${addon.id}" because sourceURI's scheme of "${scheme}" is not trusted`);
        return false;
      }
    }
    this._log.info(`Add-on "${addon.id}" is able to be installed`);
    return true;
  },


  /**
   * Update the user disabled flag for an add-on.
   *
   * If the new flag matches the existing or if the add-on
   * isn't currently active, the function will return immediately.
   *
   * @param addon
   *        (Addon) Add-on instance to operate on.
   * @param value
   *        (bool) New value for add-on's userDisabled property.
   */
  updateUserDisabled(addon, value) {
    if (addon.userDisabled == value) {
      return;
    }

    this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
    addon.userDisabled = !!value;
  },

};

XPCOMUtils.defineLazyGetter(this, "AddonUtils", function() {
  return new AddonUtilsInternal();
});
PK
!<*arar(modules/services-sync/bookmark_repair.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;

this.EXPORTED_SYMBOLS = ["BookmarkRepairRequestor", "BookmarkRepairResponder"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/collection_repair.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/doctor.js");
Cu.import("resource://services-sync/telemetry.js");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/utils.js");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");

const log = Log.repository.getLogger("Sync.Engine.Bookmarks.Repair");

const PREF_BRANCH = "services.sync.repairs.bookmarks.";

// How long should we wait after sending a repair request before we give up?
const RESPONSE_INTERVAL_TIMEOUT = 60 * 60 * 24 * 3; // 3 days

// The maximum number of IDs we will request to be repaired. Beyond this
// number we assume that trying to repair may do more harm than good and may
// ask another client to wipe the server and reupload everything. Bug 1341972
// is tracking that work.
const MAX_REQUESTED_IDS = 1000;

class AbortRepairError extends Error {
  constructor(reason) {
    super();
    this.reason = reason;
  }
}

// The states we can be in.
const STATE = Object.freeze({
  NOT_REPAIRING: "",

  // We need to try to find another client to use.
  NEED_NEW_CLIENT: "repair.need-new-client",

  // We've sent the first request to a client.
  SENT_REQUEST: "repair.sent",

  // We've retried a request to a client.
  SENT_SECOND_REQUEST: "repair.sent-again",

  // There were no problems, but we've gone as far as we can.
  FINISHED: "repair.finished",

  // We've found an error that forces us to abort this entire repair cycle.
  ABORTED: "repair.aborted",
});

// The preferences we use to hold our state.
const PREF = Object.freeze({
  // If a repair is in progress, this is the generated GUID for the "flow ID".
  REPAIR_ID: "flowID",

  // The IDs we are currently trying to obtain via the repair process, space sep'd.
  REPAIR_MISSING_IDS: "ids",

  // The ID of the client we're currently trying to get the missing items from.
  REPAIR_CURRENT_CLIENT: "currentClient",

  // The IDs of the clients we've previously tried to get the missing items
  // from, space sep'd.
  REPAIR_PREVIOUS_CLIENTS: "previousClients",

  // The time, in seconds, when we initiated the most recent client request.
  REPAIR_WHEN: "when",

  // Our current state.
  CURRENT_STATE: "state",
});

class BookmarkRepairRequestor extends CollectionRepairRequestor {
  constructor(service = null) {
    super(service);
    this.prefs = new Preferences(PREF_BRANCH);
  }

  /* Check if any other clients connected to our account are current performing
     a repair. A thin wrapper which exists mainly for mocking during tests.
  */
  anyClientsRepairing(flowID) {
    return Doctor.anyClientsRepairing(this.service, "bookmarks", flowID);
  }

  /* Return a set of IDs we should request.
  */
  getProblemIDs(validationInfo) {
    // Set of ids of "known bad records". Many of the validation issues will
    // report duplicates -- if the server is missing a record, it is unlikely
    // to cause only a single problem.
    let ids = new Set();

    // Note that we allow any of the validation problem fields to be missing so
    // that tests have a slightly easier time, hence the `|| []` in each loop.

    // Missing children records when the parent exists but a child doesn't.
    for (let { parent, child } of validationInfo.problems.missingChildren || []) {
      // We can't be sure if the child is missing or our copy of the parent is
      // wrong, so request both
      ids.add(parent);
      ids.add(child);
    }
    if (ids.size > MAX_REQUESTED_IDS) {
      return ids; // might as well give up here - we aren't going to repair.
    }

    // Orphans are when the child exists but the parent doesn't.
    // This could either be a problem in the child (it's wrong about the node
    // that should be its parent), or the parent could simply be missing.
    for (let { parent, id } of validationInfo.problems.orphans || []) {
      // Request both, to handle both cases
      ids.add(id);
      ids.add(parent);
    }
    if (ids.size > MAX_REQUESTED_IDS) {
      return ids; // might as well give up here - we aren't going to repair.
    }

    // Entries where we have the parent but we have a record from the server that
    // claims the child was deleted.
    for (let { parent, child } of validationInfo.problems.deletedChildren || []) {
      // Request both, since we don't know if it's a botched deletion or revival
      ids.add(parent);
      ids.add(child);
    }
    if (ids.size > MAX_REQUESTED_IDS) {
      return ids; // might as well give up here - we aren't going to repair.
    }

    // Entries where the child references a parent that we don't have, but we
    // have a record from the server that claims the parent was deleted.
    for (let { parent, child } of validationInfo.problems.deletedParents || []) {
      // Request both, since we don't know if it's a botched deletion or revival
      ids.add(parent);
      ids.add(child);
    }
    if (ids.size > MAX_REQUESTED_IDS) {
      return ids; // might as well give up here - we aren't going to repair.
    }

    // Cases where the parent and child disagree about who the parent is.
    for (let { parent, child } of validationInfo.problems.parentChildMismatches || []) {
      // Request both, since we don't know which is right.
      ids.add(parent);
      ids.add(child);
    }
    if (ids.size > MAX_REQUESTED_IDS) {
      return ids; // might as well give up here - we aren't going to repair.
    }

    // Cases where multiple parents reference a child. We re-request both the
    // child, and all the parents that think they own it. This may be more than
    // we need, but I don't think we can safely make the assumption that the
    // child is right.
    for (let { parents, child } of validationInfo.problems.multipleParents || []) {
      for (let parent of parents) {
        ids.add(parent);
      }
      ids.add(child);
    }

    return ids;
  }

  _countServerOnlyFixableProblems(validationInfo) {
    const fixableProblems = ["clientMissing", "serverMissing", "serverDeleted"];
    return fixableProblems.reduce((numProblems, problemLabel) => {
      return numProblems + validationInfo.problems[problemLabel].length;
    }, 0);
  }

  tryServerOnlyRepairs(validationInfo) {
    if (this._countServerOnlyFixableProblems(validationInfo) == 0) {
      return false;
    }
    let engine = this.service.engineManager.get("bookmarks");
    for (let id of validationInfo.problems.serverMissing) {
      engine.addForWeakUpload(id);
    }
    let toFetch = engine.toFetch.concat(validationInfo.problems.clientMissing,
                                        validationInfo.problems.serverDeleted);
    engine.toFetch = Array.from(new Set(toFetch));
    return true;
  }

  /* See if the repairer is willing and able to begin a repair process given
     the specified validation information.
     Returns true if a repair was started and false otherwise.
  */
  async startRepairs(validationInfo, flowID) {
    if (this._currentState != STATE.NOT_REPAIRING) {
      log.info(`Can't start a repair - repair with ID ${this._flowID} is already in progress`);
      return false;
    }

    let ids = this.getProblemIDs(validationInfo);
    if (ids.size > MAX_REQUESTED_IDS) {
      log.info("Not starting a repair as there are over " + MAX_REQUESTED_IDS + " problems");
      let extra = { flowID, reason: `too many problems: ${ids.size}` };
      this.service.recordTelemetryEvent("repair", "aborted", undefined, extra)
      return false;
    }

    if (ids.size == 0) {
      log.info("Not starting a repair as there are no problems");
      return false;
    }

    if (this.anyClientsRepairing()) {
      log.info("Can't start repair, since other clients are already repairing bookmarks");
      let extra = { flowID, reason: "other clients repairing" };
      this.service.recordTelemetryEvent("repair", "aborted", undefined, extra)
      return false;
    }

    log.info(`Starting a repair, looking for ${ids.size} missing item(s)`);
    // setup our prefs to indicate we are on our way.
    this._flowID = flowID;
    this._currentIDs = Array.from(ids);
    this._currentState = STATE.NEED_NEW_CLIENT;
    this.service.recordTelemetryEvent("repair", "started", undefined, { flowID, numIDs: ids.size.toString() });
    return this.continueRepairs();
  }

  /* Work out what state our current repair request is in, and whether it can
     proceed to a new state.
     Returns true if we could continue the repair - even if the state didn't
     actually move. Returns false if we aren't actually repairing.
  */
  async continueRepairs(response = null) {
    // Note that "ABORTED" and "FINISHED" should never be current when this
    // function returns - this function resets to NOT_REPAIRING in those cases.
    if (this._currentState == STATE.NOT_REPAIRING) {
      return false;
    }

    let state, newState;
    let abortReason;
    // we loop until the state doesn't change - but enforce a max of 10 times
    // to prevent errors causing infinite loops.
    for (let i = 0; i < 10; i++) {
      state = this._currentState;
      log.info("continueRepairs starting with state", state);
      try {
        newState = await this._continueRepairs(state, response);
        log.info("continueRepairs has next state", newState);
      } catch (ex) {
        if (!(ex instanceof AbortRepairError)) {
          throw ex;
        }
        log.info(`Repair has been aborted: ${ex.reason}`);
        newState = STATE.ABORTED;
        abortReason = ex.reason;
      }

      if (newState == STATE.ABORTED) {
        break;
      }

      this._currentState = newState;
      Services.prefs.savePrefFile(null); // flush prefs.
      if (state == newState) {
        break;
      }
    }
    if (state != newState) {
      log.error("continueRepairs spun without getting a new state");
    }
    if (newState == STATE.FINISHED || newState == STATE.ABORTED) {
      let object = newState == STATE.FINISHED ? "finished" : "aborted";
      let extra = {
        flowID: this._flowID,
        numIDs: this._currentIDs.length.toString(),
      };
      if (abortReason) {
        extra.reason = abortReason;
      }
      this.service.recordTelemetryEvent("repair", object, undefined, extra);
      // reset our state and flush our prefs.
      this.prefs.resetBranch();
      Services.prefs.savePrefFile(null); // flush prefs.
    }
    return true;
  }

  async _continueRepairs(state, response = null) {
    if (this.anyClientsRepairing(this._flowID)) {
      throw new AbortRepairError("other clients repairing");
    }
    switch (state) {
      case STATE.SENT_REQUEST:
      case STATE.SENT_SECOND_REQUEST:
        let flowID = this._flowID;
        let clientID = this._currentRemoteClient;
        if (!clientID) {
          throw new AbortRepairError(`In state ${state} but have no client IDs listed`);
        }
        if (response) {
          // We got an explicit response - let's see how we went.
          state = this._handleResponse(state, response);
          break;
        }
        // So we've sent a request - and don't yet have a response. See if the
        // client we sent it to has removed it from its list (ie, whether it
        // has synced since we wrote the request.)
        let client = this.service.clientsEngine.remoteClient(clientID);
        if (!client) {
          // hrmph - the client has disappeared.
          log.info(`previously requested client "${clientID}" has vanished - moving to next step`);
          state = STATE.NEED_NEW_CLIENT;
          let extra = {
            deviceID: this.service.identity.hashedDeviceID(clientID),
            flowID,
          }
          this.service.recordTelemetryEvent("repair", "abandon", "missing", extra);
          break;
        }
        if ((await this._isCommandPending(clientID, flowID))) {
          // So the command we previously sent is still queued for the client
          // (ie, that client is yet to have synced). Let's see if we should
          // give up on that client.
          let lastRequestSent = this.prefs.get(PREF.REPAIR_WHEN);
          let timeLeft = lastRequestSent + RESPONSE_INTERVAL_TIMEOUT - this._now();
          if (timeLeft <= 0) {
            log.info(`previous request to client ${clientID} is pending, but has taken too long`);
            state = STATE.NEED_NEW_CLIENT;
            // XXX - should we remove the command?
            let extra = {
              deviceID: this.service.identity.hashedDeviceID(clientID),
              flowID,
            }
            this.service.recordTelemetryEvent("repair", "abandon", "silent", extra);
            break;
          }
          // Let's continue to wait for that client to respond.
          log.trace(`previous request to client ${clientID} has ${timeLeft} seconds before we give up on it`);
          break;
        }
        // The command isn't pending - if this was the first request, we give
        // it another go (as that client may have cleared the command but is yet
        // to complete the sync)
        // XXX - note that this is no longer true - the responders don't remove
        // their command until they have written a response. This might mean
        // we could drop the entire STATE.SENT_SECOND_REQUEST concept???
        if (state == STATE.SENT_REQUEST) {
          log.info(`previous request to client ${clientID} was removed - trying a second time`);
          state = STATE.SENT_SECOND_REQUEST;
          await this._writeRequest(clientID);
        } else {
          // this was the second time around, so give up on this client
          log.info(`previous 2 requests to client ${clientID} were removed - need a new client`);
          state = STATE.NEED_NEW_CLIENT;
        }
        break;

      case STATE.NEED_NEW_CLIENT:
        // We need to find a new client to request.
        let newClientID = this._findNextClient();
        if (!newClientID) {
          state = STATE.FINISHED;
          break;
        }
        this._addToPreviousRemoteClients(this._currentRemoteClient);
        this._currentRemoteClient = newClientID;
        await this._writeRequest(newClientID);
        state = STATE.SENT_REQUEST;
        break;

      case STATE.ABORTED:
        break; // our caller will take the abort action.

      case STATE.FINISHED:
        break;

      case STATE.NOT_REPAIRING:
        // No repair is in progress. This is a common case, so only log trace.
        log.trace("continue repairs called but no repair in progress.");
        break;

      default:
        log.error(`continue repairs finds itself in an unknown state ${state}`);
        state = STATE.ABORTED;
        break;

    }
    return state;
  }

  /* Handle being in the SENT_REQUEST or SENT_SECOND_REQUEST state with an
     explicit response.
  */
  _handleResponse(state, response) {
    let clientID = this._currentRemoteClient;
    let flowID = this._flowID;

    if (response.flowID != flowID || response.clientID != clientID ||
        response.request != "upload") {
      log.info("got a response to a different repair request", response);
      // hopefully just a stale request that finally came in (either from
      // an entirely different repair flow, or from a client we've since
      // given up on.) It doesn't mean we need to abort though...
      return state;
    }
    // Pull apart the response and see if it provided everything we asked for.
    let remainingIDs = Array.from(CommonUtils.difference(this._currentIDs, response.ids));
    log.info(`repair response from ${clientID} provided "${response.ids}", remaining now "${remainingIDs}"`);
    this._currentIDs = remainingIDs;
    if (remainingIDs.length) {
      // try a new client for the remaining ones.
      state = STATE.NEED_NEW_CLIENT;
    } else {
      state = STATE.FINISHED;
    }
    // record telemetry about this
    let extra = {
      deviceID: this.service.identity.hashedDeviceID(clientID),
      flowID,
      numIDs: response.ids.length.toString(),
    }
    this.service.recordTelemetryEvent("repair", "response", "upload", extra);
    return state;
  }

  /* Issue a repair request to a specific client.
  */
  async _writeRequest(clientID) {
    log.trace("writing repair request to client", clientID);
    let ids = this._currentIDs;
    if (!ids) {
      throw new AbortRepairError("Attempting to write a request, but there are no IDs");
    }
    let flowID = this._flowID;
    // Post a command to that client.
    let request = {
      collection: "bookmarks",
      request: "upload",
      requestor: this.service.clientsEngine.localID,
      ids,
      flowID,
    }
    await this.service.clientsEngine.sendCommand("repairRequest", [request], clientID, { flowID });
    this.prefs.set(PREF.REPAIR_WHEN, Math.floor(this._now()));
    // record telemetry about this
    let extra = {
      deviceID: this.service.identity.hashedDeviceID(clientID),
      flowID,
      numIDs: ids.length.toString(),
    }
    this.service.recordTelemetryEvent("repair", "request", "upload", extra);
  }

  _findNextClient() {
    let alreadyDone = this._getPreviousRemoteClients();
    alreadyDone.push(this._currentRemoteClient);
    let remoteClients = this.service.clientsEngine.remoteClients;
    // we want to consider the most-recently synced clients first.
    remoteClients.sort((a, b) => b.serverLastModified - a.serverLastModified);
    for (let client of remoteClients) {
      log.trace("findNextClient considering", client);
      if (alreadyDone.indexOf(client.id) == -1 && this._isSuitableClient(client)) {
        return client.id;
      }
    }
    log.trace("findNextClient found no client");
    return null;
  }

  /* Is the passed client record suitable as a repair responder?
  */
  _isSuitableClient(client) {
    // filter only desktop firefox running > 53 (ie, any 54)
    return (client.type == DEVICE_TYPE_DESKTOP &&
            Services.vc.compare(client.version, 53) > 0);
  }

  /* Is our command still in the "commands" queue for the specific client?
  */
  async _isCommandPending(clientID, flowID) {
    // getClientCommands() is poorly named - it's only outgoing commands
    // from us we have yet to write. For our purposes, we want to check
    // them and commands previously written (which is in .commands)
    let clientCommands = await this.service.clientsEngine.getClientCommands(clientID);
    let commands = [...clientCommands,
                    ...this.service.clientsEngine.remoteClient(clientID).commands || []];
    for (let command of commands) {
      if (command.command != "repairRequest" || command.args.length != 1) {
        continue;
      }
      let arg = command.args[0];
      if (arg.collection == "bookmarks" && arg.request == "upload" &&
          arg.flowID == flowID) {
        return true;
      }
    }
    return false;
  }

  // accessors for our prefs.
  get _currentState() {
    return this.prefs.get(PREF.CURRENT_STATE, STATE.NOT_REPAIRING);
  }
  set _currentState(newState) {
    this.prefs.set(PREF.CURRENT_STATE, newState);
  }

  get _currentIDs() {
    let ids = this.prefs.get(PREF.REPAIR_MISSING_IDS, "");
    return ids.length ? ids.split(" ") : [];
  }
  set _currentIDs(arrayOfIDs) {
    this.prefs.set(PREF.REPAIR_MISSING_IDS, arrayOfIDs.join(" "));
  }

  get _currentRemoteClient() {
    return this.prefs.get(PREF.REPAIR_CURRENT_CLIENT);
  }
  set _currentRemoteClient(clientID) {
    this.prefs.set(PREF.REPAIR_CURRENT_CLIENT, clientID);
  }

  get _flowID() {
    return this.prefs.get(PREF.REPAIR_ID);
  }
  set _flowID(val) {
    this.prefs.set(PREF.REPAIR_ID, val);
  }

  // use a function for this pref to offer somewhat sane semantics.
  _getPreviousRemoteClients() {
    let alreadyDone = this.prefs.get(PREF.REPAIR_PREVIOUS_CLIENTS, "");
    return alreadyDone.length ? alreadyDone.split(" ") : [];
  }
  _addToPreviousRemoteClients(clientID) {
    let arrayOfClientIDs = this._getPreviousRemoteClients();
    arrayOfClientIDs.push(clientID);
    this.prefs.set(PREF.REPAIR_PREVIOUS_CLIENTS, arrayOfClientIDs.join(" "));
  }

  /* Used for test mocks.
  */
  _now() {
    // We use the server time, which is SECONDS
    return AsyncResource.serverTime;
  }
}

/* An object that responds to repair requests initiated by some other device.
*/
class BookmarkRepairResponder extends CollectionRepairResponder {
  async repair(request, rawCommand) {
    if (request.request != "upload") {
      await this._abortRepair(request, rawCommand,
                              `Don't understand request type '${request.request}'`);
      return;
    }

    // Note that we don't try and guard against multiple repairs being in
    // progress as we don't do anything too smart that could cause problems,
    // but just upload items. If we get any smarter we should re-think this
    // (but when we do, note that checking this._currentState isn't enough as
    // this responder is not a singleton)

    this._currentState = {
      request,
      rawCommand,
      processedCommand: false,
      ids: [],
    }

    try {
      let engine = this.service.engineManager.get("bookmarks");
      let { toUpload, toDelete } = await this._fetchItemsToUpload(request);

      if (toUpload.size || toDelete.size) {
        log.debug(`repair request will upload ${toUpload.size} items and delete ${toDelete.size} items`);
        // whew - now add these items to the tracker "weakly" (ie, they will not
        // persist in the case of a restart, but that's OK - we'll then end up here
        // again) and also record them in the response we send back.
        for (let id of toUpload) {
          engine.addForWeakUpload(id);
          this._currentState.ids.push(id);
        }
        for (let id of toDelete) {
          engine.addForWeakUpload(id, { forceTombstone: true });
          this._currentState.ids.push(id);
        }

        // We have arranged for stuff to be uploaded, so wait until that's done.
        Svc.Obs.add("weave:engine:sync:uploaded", this.onUploaded, this);
        // and record in telemetry that we got this far - just incase we never
        // end up doing the upload for some obscure reason.
        let eventExtra = {
          flowID: request.flowID,
          numIDs: this._currentState.ids.length.toString(),
        };
        this.service.recordTelemetryEvent("repairResponse", "uploading", undefined, eventExtra);
      } else {
        // We were unable to help with the repair, so report that we are done.
        await this._finishRepair();
      }
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        // this repair request will be tried next time.
        throw ex;
      }
      // On failure, we still write a response so the requestor knows to move
      // on, but we record the failure reason in telemetry.
      log.error("Failed to respond to the repair request", ex);
      this._currentState.failureReason = SyncTelemetry.transformError(ex);
      await this._finishRepair();
    }
  }

  async _fetchItemsToUpload(request) {
    let toUpload = new Set(); // items we will upload.
    let toDelete = new Set(); // items we will delete.

    let requested = new Set(request.ids);

    let engine = this.service.engineManager.get("bookmarks");
    // Determine every item that may be impacted by the requested IDs - eg,
    // this may include children if a requested ID is a folder.
    // Turn an array of { syncId, syncable } into a map of syncId -> syncable.
    let repairable = await PlacesSyncUtils.bookmarks.fetchSyncIdsForRepair(request.ids);
    if (repairable.length == 0) {
      // server will get upset if we request an empty set, and we can't do
      // anything in that case, so bail now.
      return { toUpload, toDelete };
    }

    // which of these items exist on the server?
    let itemSource = engine.itemSource();
    itemSource.ids = repairable.map(item => item.syncId);
    log.trace(`checking the server for items`, itemSource.ids);
    let itemsResponse = await itemSource.get();
    // If the response failed, don't bother trying to parse the output.
    // Throwing here means we abort the repair, which isn't ideal for transient
    // errors (eg, no network, 500 service outage etc), but we don't currently
    // have a sane/safe way to try again later (we'd need to implement a kind
    // of timeout, otherwise we might end up retrying forever and never remove
    // our request command.) Bug 1347805.
    if (!itemsResponse.success) {
      throw new Error(`request for server IDs failed: ${itemsResponse.status}`);
    }
    let existRemotely = new Set(JSON.parse(itemsResponse));
    // We need to be careful about handing the requested items:
    // * If the item exists locally but isn't in the tree of items we sync
    //   (eg, it might be a left-pane item or similar, we write a tombstone.
    // * If the item exists locally as a folder, we upload the folder and any
    //   children which don't exist on the server. (Note that we assume the
    //   parents *do* exist)
    // Bug 1343101 covers additional issues we might repair in the future.
    for (let { syncId: id, syncable } of repairable) {
      if (requested.has(id)) {
        if (syncable) {
          log.debug(`repair request to upload item '${id}' which exists locally; uploading`);
          toUpload.add(id);
        } else {
          // explicitly requested and not syncable, so tombstone.
          log.debug(`repair request to upload item '${id}' but it isn't under a syncable root; writing a tombstone`);
          toDelete.add(id);
        }
      // The item wasn't explicitly requested - only upload if it is syncable
      // and doesn't exist on the server.
      } else if (syncable && !existRemotely.has(id)) {
        log.debug(`repair request found related item '${id}' which isn't on the server; uploading`);
        toUpload.add(id);
      } else if (!syncable && existRemotely.has(id)) {
        log.debug(`repair request found non-syncable related item '${id}' on the server; writing a tombstone`);
        toDelete.add(id);
      } else {
        log.debug(`repair request found related item '${id}' which we will not upload; ignoring`);
      }
    }
    return { toUpload, toDelete };
  }

  onUploaded(subject, data) {
    if (data != "bookmarks") {
      return;
    }
    Svc.Obs.remove("weave:engine:sync:uploaded", this.onUploaded, this);
    if (subject.failed) {
      return;
    }
    log.debug(`bookmarks engine has uploaded stuff - creating a repair response`, subject);
    Async.promiseSpinningly(this._finishRepair());
  }

  async _finishRepair() {
    let clientsEngine = this.service.clientsEngine;
    let flowID = this._currentState.request.flowID;
    let response = {
      request: this._currentState.request.request,
      collection: "bookmarks",
      clientID: clientsEngine.localID,
      flowID,
      ids: this._currentState.ids,
    };
    let clientID = this._currentState.request.requestor;
    await clientsEngine.sendCommand("repairResponse", [response], clientID, { flowID });
    // and nuke the request from our client.
    await clientsEngine.removeLocalCommand(this._currentState.rawCommand);
    let eventExtra = {
      flowID,
      numIDs: response.ids.length.toString(),
    }
    if (this._currentState.failureReason) {
      // *sob* - recording this in "extra" means the value must be a string of
      // max 85 chars.
      eventExtra.failureReason = JSON.stringify(this._currentState.failureReason).substring(0, 85)
      this.service.recordTelemetryEvent("repairResponse", "failed", undefined, eventExtra);
    } else {
      this.service.recordTelemetryEvent("repairResponse", "finished", undefined, eventExtra);
    }
    this._currentState = null;
  }

  async _abortRepair(request, rawCommand, why) {
    log.warn(`aborting repair request: ${why}`);
    await this.service.clientsEngine.removeLocalCommand(rawCommand);
    // record telemetry for this.
    let eventExtra = {
      flowID: request.flowID,
      reason: why,
    };
    this.service.recordTelemetryEvent("repairResponse", "aborted", undefined, eventExtra);
    // We could also consider writing a response here so the requestor can take
    // some immediate action rather than timing out, but we abort only in cases
    // that should be rare, so let's wait and see what telemetry tells us.
  }
}

/* Exposed in case another module needs to understand our state.
*/
BookmarkRepairRequestor.STATE = STATE;
BookmarkRepairRequestor.PREF = PREF;
PK
!<eHzz+modules/services-sync/bookmark_validator.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-common/utils.js");

XPCOMUtils.defineLazyModuleGetter(this, "Async",
                                  "resource://services-common/async.js");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");

Cu.importGlobalProperties(["URLSearchParams"]);

this.EXPORTED_SYMBOLS = ["BookmarkValidator", "BookmarkProblemData"];

const LEFT_PANE_ROOT_ANNO = "PlacesOrganizer/OrganizerFolder";
const LEFT_PANE_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
const QUERY_PROTOCOL = "place:";

// Indicates if a local bookmark tree node should be excluded from syncing.
function isNodeIgnored(treeNode) {
  return treeNode.annos && treeNode.annos.some(anno => anno.name == LEFT_PANE_ROOT_ANNO ||
                                                       anno.name == LEFT_PANE_QUERY_ANNO);
}

function areURLsEqual(a, b) {
  if (a === b) {
    return true;
  }
  if (a.startsWith(QUERY_PROTOCOL) != b.startsWith(QUERY_PROTOCOL)) {
    return false;
  }
  // Tag queries are special because we rewrite them to point to the
  // local tag folder ID. It's expected that the folders won't match,
  // but all other params should.
  let aParams = new URLSearchParams(a.slice(QUERY_PROTOCOL.length));
  let aType = +aParams.get("type");
  if (aType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
    return false;
  }
  let bParams = new URLSearchParams(b.slice(QUERY_PROTOCOL.length));
  let bType = +bParams.get("type");
  if (bType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
    return false;
  }
  let aKeys = new Set(aParams.keys());
  let bKeys = new Set(bParams.keys());
  if (aKeys.size != bKeys.size) {
    return false;
  }
  // Tag queries shouldn't reference multiple folders, or named folders like
  // "TOOLBAR" or "BOOKMARKS_MENU". Just in case, we make sure all folder IDs
  // are numeric. If they are, we ignore them when comparing the query params.
  if (aKeys.has("folder") && aParams.getAll("folder").every(isFinite)) {
    aKeys.delete("folder");
  }
  if (bKeys.has("folder") && bParams.getAll("folder").every(isFinite)) {
    bKeys.delete("folder");
  }
  for (let key of aKeys) {
    if (!bKeys.has(key)) {
      return false;
    }
    if (!CommonUtils.arrayEqual(aParams.getAll(key).sort(),
                                bParams.getAll(key).sort())) {
      return false;
    }
  }
  for (let key of bKeys) {
    if (!aKeys.has(key)) {
      return false;
    }
  }
  return true;
}

const BOOKMARK_VALIDATOR_VERSION = 1;

/**
 * Result of bookmark validation. Contains the following fields which describe
 * server-side problems unless otherwise specified.
 *
 * - missingIDs (number): # of objects with missing ids
 * - duplicates (array of ids): ids seen more than once
 * - parentChildMismatches (array of {parent: parentid, child: childid}):
 *   instances where the child's parentid and the parent's children array
 *   do not match
 * - cycles (array of array of ids). List of cycles found in the server-side tree.
 * - clientCycles (array of array of ids). List of cycles found in the client-side tree.
 * - orphans (array of {id: string, parent: string}): List of nodes with
 *   either no parentid, or where the parent could not be found.
 * - missingChildren (array of {parent: id, child: id}):
 *   List of parent/children where the child id couldn't be found
 * - deletedChildren (array of { parent: id, child: id }):
 *   List of parent/children where child id was a deleted item (but still showed up
 *   in the children array)
 * - multipleParents (array of {child: id, parents: array of ids}):
 *   List of children that were part of multiple parent arrays
 * - deletedParents (array of ids) : List of records that aren't deleted but
 *   had deleted parents
 * - childrenOnNonFolder (array of ids): list of non-folders that still have
 *   children arrays
 * - duplicateChildren (array of ids): list of records who have the same
 *   child listed multiple times in their children array
 * - parentNotFolder (array of ids): list of records that have parents that
 *   aren't folders
 * - rootOnServer (boolean): true if the root came from the server
 * - badClientRoots (array of ids): Contains any client-side root ids where
 *   the root is missing or isn't a (direct) child of the places root.
 *
 * - clientMissing: Array of ids on the server missing from the client
 * - serverMissing: Array of ids on the client missing from the server
 * - serverDeleted: Array of ids on the client that the server had marked as deleted.
 * - serverUnexpected: Array of ids that appear on the server but shouldn't
 *   because the client attempts to never upload them.
 * - differences: Array of {id: string, differences: string array} recording
 *   the non-structural properties that are differente between the client and server
 * - structuralDifferences: As above, but contains the items where the differences were
 *   structural, that is, they contained childGUIDs or parentid
 */
class BookmarkProblemData {
  constructor() {
    this.rootOnServer = false;
    this.missingIDs = 0;

    this.duplicates = [];
    this.parentChildMismatches = [];
    this.cycles = [];
    this.clientCycles = [];
    this.orphans = [];
    this.missingChildren = [];
    this.deletedChildren = [];
    this.multipleParents = [];
    this.deletedParents = [];
    this.childrenOnNonFolder = [];
    this.duplicateChildren = [];
    this.parentNotFolder = [];

    this.badClientRoots = [];
    this.clientMissing = [];
    this.serverMissing = [];
    this.serverDeleted = [];
    this.serverUnexpected = [];
    this.differences = [];
    this.structuralDifferences = [];
  }

  /**
   * Convert ("difference", [{ differences: ["tags", "name"] }, { differences: ["name"] }]) into
   * [{ name: "difference:tags", count: 1}, { name: "difference:name", count: 2 }], etc.
   */
  _summarizeDifferences(prefix, diffs) {
    let diffCounts = new Map();
    for (let { differences } of diffs) {
      for (let type of differences) {
        let name = prefix + ":" + type;
        let count = diffCounts.get(name) || 0;
        diffCounts.set(name, count + 1);
      }
    }
    return [...diffCounts].map(([name, count]) => ({ name, count }));
  }

  /**
   * Produce a list summarizing problems found. Each entry contains {name, count},
   * where name is the field name for the problem, and count is the number of times
   * the problem was encountered.
   *
   * Validation has failed if all counts are not 0.
   *
   * If the `full` argument is truthy, we also include information about which
   * properties we saw structural differences in. Currently, this means either
   * "sdiff:parentid" and "sdiff:childGUIDS" may be present.
   */
  getSummary(full) {
    let result = [
      { name: "clientMissing", count: this.clientMissing.length },
      { name: "serverMissing", count: this.serverMissing.length },
      { name: "serverDeleted", count: this.serverDeleted.length },
      { name: "serverUnexpected", count: this.serverUnexpected.length },

      { name: "structuralDifferences", count: this.structuralDifferences.length },
      { name: "differences", count: this.differences.length },

      { name: "missingIDs", count: this.missingIDs },
      { name: "rootOnServer", count: this.rootOnServer ? 1 : 0 },

      { name: "duplicates", count: this.duplicates.length },
      { name: "parentChildMismatches", count: this.parentChildMismatches.length },
      { name: "cycles", count: this.cycles.length },
      { name: "clientCycles", count: this.clientCycles.length },
      { name: "badClientRoots", count: this.badClientRoots.length },
      { name: "orphans", count: this.orphans.length },
      { name: "missingChildren", count: this.missingChildren.length },
      { name: "deletedChildren", count: this.deletedChildren.length },
      { name: "multipleParents", count: this.multipleParents.length },
      { name: "deletedParents", count: this.deletedParents.length },
      { name: "childrenOnNonFolder", count: this.childrenOnNonFolder.length },
      { name: "duplicateChildren", count: this.duplicateChildren.length },
      { name: "parentNotFolder", count: this.parentNotFolder.length },
    ];
    if (full) {
      let structural = this._summarizeDifferences("sdiff", this.structuralDifferences);
      result.push.apply(result, structural);
    }
    return result;
  }
}

// Defined lazily to avoid initializing PlacesUtils.bookmarks too soon.
XPCOMUtils.defineLazyGetter(this, "SYNCED_ROOTS", () => [
  PlacesUtils.bookmarks.menuGuid,
  PlacesUtils.bookmarks.toolbarGuid,
  PlacesUtils.bookmarks.unfiledGuid,
  PlacesUtils.bookmarks.mobileGuid,
]);

// Maps root GUIDs to their query folder names from
// toolkit/components/places/nsNavHistoryQuery.cpp. We follow queries that
// reference existing folders in the client tree, and detect cycles where a
// query references its containing folder.
XPCOMUtils.defineLazyGetter(this, "ROOT_GUID_TO_QUERY_FOLDER_NAME", () => ({
  [PlacesUtils.bookmarks.rootGuid]: "PLACES_ROOT",
  [PlacesUtils.bookmarks.menuGuid]: "BOOKMARKS_MENU",

  // Tags should never show up in our client tree, and never form cycles, but we
  // report them just in case.
  [PlacesUtils.bookmarks.tagsGuid]: "TAGS",

  [PlacesUtils.bookmarks.unfiledGuid]: "UNFILED_BOOKMARKS",
  [PlacesUtils.bookmarks.toolbarGuid]: "TOOLBAR",
  [PlacesUtils.bookmarks.mobileGuid]: "MOBILE_BOOKMARKS",
}));

class BookmarkValidator {

  async canValidate() {
    return !await PlacesSyncUtils.bookmarks.havePendingChanges();
  }

  _followQueries(recordsByQueryId) {
    for (let entry of recordsByQueryId.values()) {
      if (entry.type !== "query" && (!entry.bmkUri || !entry.bmkUri.startsWith(QUERY_PROTOCOL))) {
        continue;
      }
      let params = new URLSearchParams(entry.bmkUri.slice(QUERY_PROTOCOL.length));
      // Queries with `excludeQueries` won't form cycles because they'll
      // exclude all queries, including themselves, from the result set.
      let excludeQueries = params.get("excludeQueries");
      if (excludeQueries === "1" || excludeQueries === "true") {
        // `nsNavHistoryQuery::ParseQueryBooleanString` allows `1` and `true`.
        continue;
      }
      entry.concreteItems = [];
      let queryIds = params.getAll("folder");
      for (let queryId of queryIds) {
        let concreteItem = recordsByQueryId.get(queryId);
        if (concreteItem) {
          entry.concreteItems.push(concreteItem);
        }
      }
    }
  }

  async createClientRecordsFromTree(clientTree) {
    // Iterate over the treeNode, converting it to something more similar to what
    // the server stores.
    let records = [];
    // A map of local IDs and well-known query folder names to records. Unlike
    // GUIDs, local IDs aren't synced, since they're not stable across devices.
    // New Places APIs use GUIDs to refer to bookmarks, but the legacy APIs
    // still use local IDs. We use this mapping to parse `place:` queries that
    // refer to folders via their local IDs.
    let recordsByQueryId = new Map();
    let syncedRoots = SYNCED_ROOTS;
    let maybeYield = Async.jankYielder();
    async function traverse(treeNode, synced) {
      await maybeYield();
      if (!synced) {
        synced = syncedRoots.includes(treeNode.guid);
      } else if (isNodeIgnored(treeNode)) {
        synced = false;
      }
      let localId = treeNode.id;
      let guid = PlacesSyncUtils.bookmarks.guidToSyncId(treeNode.guid);
      let itemType = "item";
      treeNode.ignored = !synced;
      treeNode.id = guid;
      switch (treeNode.type) {
        case PlacesUtils.TYPE_X_MOZ_PLACE:
          let query = null;
          if (treeNode.annos && treeNode.uri.startsWith(QUERY_PROTOCOL)) {
            query = treeNode.annos.find(({name}) =>
              name === PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
          }
          if (query && query.value) {
            itemType = "query";
          } else {
            itemType = "bookmark";
          }
          break;
        case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
          let isLivemark = false;
          if (treeNode.annos) {
            for (let anno of treeNode.annos) {
              if (anno.name === PlacesUtils.LMANNO_FEEDURI) {
                isLivemark = true;
                treeNode.feedUri = anno.value;
              } else if (anno.name === PlacesUtils.LMANNO_SITEURI) {
                isLivemark = true;
                treeNode.siteUri = anno.value;
              }
            }
          }
          itemType = isLivemark ? "livemark" : "folder";
          break;
        case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
          itemType = "separator";
          break;
      }

      if (treeNode.tags) {
        treeNode.tags = treeNode.tags.split(",");
      } else {
        treeNode.tags = [];
      }
      treeNode.type = itemType;
      treeNode.pos = treeNode.index;
      treeNode.bmkUri = treeNode.uri;
      records.push(treeNode);
      if (treeNode.guid in ROOT_GUID_TO_QUERY_FOLDER_NAME) {
        let queryId = ROOT_GUID_TO_QUERY_FOLDER_NAME[treeNode.guid];
        recordsByQueryId.set(queryId, treeNode);
      }
      if (localId) {
        // Always add the ID, since it's still possible for a query to
        // reference a root without using the well-known name. For example,
        // `place:folder=${PlacesUtils.mobileFolderId}` and
        // `place:folder=MOBILE_BOOKMARKS` are equivalent.
        recordsByQueryId.set(localId.toString(10), treeNode);
      }
      if (treeNode.type === "folder") {
        treeNode.childGUIDs = [];
        if (!treeNode.children) {
          treeNode.children = [];
        }
        for (let child of treeNode.children) {
          await traverse(child, synced);
          child.parent = treeNode;
          child.parentid = guid;
          treeNode.childGUIDs.push(child.guid);
        }
      }
    }
    await traverse(clientTree, false);
    clientTree.id = "places";
    this._followQueries(recordsByQueryId);
    return records;
  }

  /**
   * Process the server-side list. Mainly this builds the records into a tree,
   * but it also records information about problems, and produces arrays of the
   * deleted and non-deleted nodes.
   *
   * Returns an object containing:
   * - records:Array of non-deleted records. Each record contains the following
   *    properties
   *     - childGUIDs (array of strings, only present if type is 'folder'): the
   *       list of child GUIDs stored on the server.
   *     - children (array of records, only present if type is 'folder'):
   *       each record has these same properties. This may differ in content
   *       from what you may expect from the childGUIDs list, as it won't
   *       contain any records that could not be found.
   *     - parent (record): The parent to this record.
   *     - Unchanged properties send down from the server: id, title, type,
   *       parentName, parentid, bmkURI, keyword, tags, pos, queryId, loadInSidebar
   * - root: Root of the server-side bookmark tree. Has the same properties as
   *   above.
   * - deletedRecords: As above, but only contains items that the server sent
   *   where it also sent indication that the item should be deleted.
   * - problemData: a BookmarkProblemData object, with the caveat that
   *   the fields describing client/server relationship will not have been filled
   *   out yet.
   */
  // XXX This should be split up and the complexity reduced.
  // eslint-disable-next-line complexity
  async inspectServerRecords(serverRecords) {
    let deletedItemIds = new Set();
    let idToRecord = new Map();
    let deletedRecords = [];

    let folders = [];

    let problemData = new BookmarkProblemData();

    let resultRecords = [];

    let maybeYield = Async.jankYielder();
    for (let record of serverRecords) {
      await maybeYield();
      if (!record.id) {
        ++problemData.missingIDs;
        continue;
      }
      if (record.deleted) {
        deletedItemIds.add(record.id);
      } else if (idToRecord.has(record.id)) {
          problemData.duplicates.push(record.id);
          continue;
        }
      idToRecord.set(record.id, record);

      if (record.children) {
        if (record.type !== "folder") {
          // Due to implementation details in engines/bookmarks.js, (Livemark
          // subclassing BookmarkFolder) Livemarks will have a children array,
          // but it should still be empty.
          if (!record.children.length) {
            continue;
          }
          // Otherwise we mark it as an error and still try to resolve the children
          problemData.childrenOnNonFolder.push(record.id);
        }
        folders.push(record);

        if (new Set(record.children).size !== record.children.length) {
          problemData.duplicateChildren.push(record.id)
        }

        // The children array stores special guids as their local guid values,
        // e.g. 'menu________' instead of 'menu', but all other parts of the
        // serverside bookmark info stores it as the special value ('menu').
        record.childGUIDs = record.children;
        record.children = record.children.map(childID => {
          return PlacesSyncUtils.bookmarks.guidToSyncId(childID);
        });
      }
    }

    for (let deletedId of deletedItemIds) {
      let record = idToRecord.get(deletedId);
      if (record && !record.isDeleted) {
        deletedRecords.push(record);
        record.isDeleted = true;
      }
    }

    let root = idToRecord.get("places");

    if (!root) {
      // Fabricate a root. We want to remember that it's fake so that we can
      // avoid complaining about stuff like it missing it's childGUIDs later.
      root = { id: "places", children: [], type: "folder", title: "", fake: true };
      resultRecords.push(root);
      idToRecord.set("places", root);
    } else {
      problemData.rootOnServer = true;
    }

    // Build the tree, find orphans, and record most problems having to do with
    // the tree structure.
    for (let [id, record] of idToRecord) {
      if (record === root) {
        continue;
      }

      if (record.isDeleted) {
        continue;
      }

      let parentID = record.parentid;
      if (!parentID) {
        problemData.orphans.push({id: record.id, parent: parentID});
        continue;
      }

      let parent = idToRecord.get(parentID);
      if (!parent) {
        problemData.orphans.push({id: record.id, parent: parentID});
        continue;
      }

      if (parent.type !== "folder") {
        problemData.parentNotFolder.push(record.id);
        if (!parent.children) {
          parent.children = [];
        }
        if (!parent.childGUIDs) {
          parent.childGUIDs = [];
        }
      }

      if (!record.isDeleted) {
        resultRecords.push(record);
      }

      record.parent = parent;
      if (parent !== root || problemData.rootOnServer) {
        let childIndex = parent.children.indexOf(id);
        if (childIndex < 0) {
          problemData.parentChildMismatches.push({parent: parent.id, child: record.id});
        } else {
          parent.children[childIndex] = record;
        }
      } else {
        parent.children.push(record);
      }

      if (parent.isDeleted && !record.isDeleted) {
        problemData.deletedParents.push(record.id);
      }

      // We used to check if the parentName on the server matches the actual
      // local parent name, but given this is used only for de-duping a record
      // the first time it is seen and expensive to keep up-to-date, we decided
      // to just stop recording it. See bug 1276969 for more.
    }

    // Check that we aren't missing any children.
    for (let folder of folders) {
      folder.unfilteredChildren = folder.children;
      folder.children = [];
      for (let ci = 0; ci < folder.unfilteredChildren.length; ++ci) {
        let child = folder.unfilteredChildren[ci];
        let childObject;
        if (typeof child == "string") {
          // This can happen the parent refers to a child that has a different
          // parentid, or if it refers to a missing or deleted child. It shouldn't
          // be possible with totally valid bookmarks.
          childObject = idToRecord.get(child);
          if (!childObject) {
            problemData.missingChildren.push({parent: folder.id, child});
          } else {
            folder.unfilteredChildren[ci] = childObject;
            if (childObject.isDeleted) {
              problemData.deletedChildren.push({ parent: folder.id, child });
            }
          }
        } else {
          childObject = child;
        }

        if (!childObject) {
          continue;
        }

        if (childObject.parentid === folder.id) {
          folder.children.push(childObject);
          continue;
        }

        // The child is very probably in multiple `children` arrays --
        // see if we already have a problem record about it.
        let currentProblemRecord = problemData.multipleParents.find(pr =>
          pr.child === child);

        if (currentProblemRecord) {
          currentProblemRecord.parents.push(folder.id);
          continue;
        }

        let otherParent = idToRecord.get(childObject.parentid);
        // it's really an ... orphan ... sort of.
        if (!otherParent) {
          // if we never end up adding to this parent's list, we filter it out after this loop.
          problemData.multipleParents.push({
            child,
            parents: [folder.id]
          });
          if (!problemData.orphans.some(r => r.id === child)) {
            problemData.orphans.push({
              id: child,
              parent: childObject.parentid
            });
          }
          continue;
        }

        if (otherParent.isDeleted) {
          if (!problemData.deletedParents.includes(child)) {
            problemData.deletedParents.push(child);
          }
          continue;
        }

        if (otherParent.childGUIDs && !otherParent.childGUIDs.includes(child)) {
          if (!problemData.parentChildMismatches.some(r => r.child === child)) {
            // Might not be possible to get here.
            problemData.parentChildMismatches.push({ child, parent: folder.id });
          }
        }

        problemData.multipleParents.push({
          child,
          parents: [childObject.parentid, folder.id]
        });
      }
    }
    problemData.multipleParents = problemData.multipleParents.filter(record =>
      record.parents.length >= 2);

    problemData.cycles = this._detectCycles(resultRecords);

    return {
      deletedRecords,
      records: resultRecords,
      problemData,
      root,
    };
  }

  // helper for inspectServerRecords
  _detectCycles(records) {
    // currentPath and pathLookup contain the same data. pathLookup is faster to
    // query, but currentPath gives is the order of traversal that we need in
    // order to report the members of the cycles.
    let pathLookup = new Set();
    let currentPath = [];
    let cycles = [];
    let seenEver = new Set();
    const traverse = node => {
      if (pathLookup.has(node)) {
        let cycleStart = currentPath.lastIndexOf(node);
        let cyclePath = currentPath.slice(cycleStart).map(n => n.id);
        cycles.push(cyclePath);
        return;
      } else if (seenEver.has(node)) {
        // If we're checking the server, this is a problem, but it should already be reported.
        // On the client, this could happen due to including `node.concrete` in the child list.
        return;
      }
      seenEver.add(node);
      let children = node.children || [];
      if (node.concreteItems) {
        children.push(...node.concreteItems);
      }
      if (children.length) {
        pathLookup.add(node);
        currentPath.push(node);
        for (let child of children) {
          traverse(child);
        }
        currentPath.pop();
        pathLookup.delete(node);
      }
    };
    for (let record of records) {
      if (!seenEver.has(record)) {
        traverse(record);
      }
    }

    return cycles;
  }

  // Perform client-side sanity checking that doesn't involve server data
  _validateClient(problemData, clientRecords) {
    problemData.clientCycles = this._detectCycles(clientRecords);
    for (let rootGUID of SYNCED_ROOTS) {
      let record = clientRecords.find(record =>
        record.guid === rootGUID);
      if (!record || record.parentid !== "places") {
        problemData.badClientRoots.push(rootGUID);
      }
    }
  }

  /**
   * Compare the list of server records with the client tree.
   *
   * Returns the same data as described in the inspectServerRecords comment,
   * with the following additional fields.
   * - clientRecords: an array of client records in a similar format to
   *   the .records (ie, server records) entry.
   * - problemData is the same as for inspectServerRecords, except all properties
   *   will be filled out.
   */
  // XXX This should be split up and the complexity reduced.
  // eslint-disable-next-line complexity
  async compareServerWithClient(serverRecords, clientTree) {

    let clientRecords = await this.createClientRecordsFromTree(clientTree);
    let inspectionInfo = await this.inspectServerRecords(serverRecords);
    inspectionInfo.clientRecords = clientRecords;

    // Mainly do this to remove deleted items and normalize child guids.
    serverRecords = inspectionInfo.records;
    let problemData = inspectionInfo.problemData;

    this._validateClient(problemData, clientRecords);

    let allRecords = new Map();
    let serverDeletedLookup = new Set(inspectionInfo.deletedRecords.map(r => r.id));

    for (let sr of serverRecords) {
      if (sr.fake) {
        continue;
      }
      allRecords.set(sr.id, {client: null, server: sr});
    }

    for (let cr of clientRecords) {
      let unified = allRecords.get(cr.id);
      if (!unified) {
        allRecords.set(cr.id, {client: cr, server: null});
      } else {
        unified.client = cr;
      }
    }


    for (let [id, {client, server}] of allRecords) {
      if (!client && server) {
        problemData.clientMissing.push(id);
        continue;
      }
      if (!server && client) {
        if (serverDeletedLookup.has(id)) {
          problemData.serverDeleted.push(id);
        } else if (!client.ignored && client.id != "places") {
          problemData.serverMissing.push(id);
        }
        continue;
      }
      if (server && client && client.ignored) {
        problemData.serverUnexpected.push(id);
      }
      let differences = [];
      let structuralDifferences = [];

      // Don't bother comparing titles of roots. It's okay if locally it's
      // "Mobile Bookmarks", but the server thinks it's "mobile".
      // TODO: We probably should be handing other localized bookmarks (e.g.
      // default bookmarks) here as well, see bug 1316041.
      if (!SYNCED_ROOTS.includes(client.guid)) {
        // We want to treat undefined, null and an empty string as identical
        if ((client.title || "") !== (server.title || "")) {
          differences.push("title");
        }
      }

      if (client.parentid || server.parentid) {
        if (client.parentid !== server.parentid) {
          structuralDifferences.push("parentid");
        }
      }

      if (client.tags || server.tags) {
        let cl = client.tags ? [...client.tags].sort() : [];
        let sl = server.tags ? [...server.tags].sort() : [];
        if (!CommonUtils.arrayEqual(cl, sl)) {
          differences.push("tags");
        }
      }

      let sameType = client.type === server.type;
      if (!sameType) {
        if (server.type === "query" && client.type === "bookmark" && client.bmkUri.startsWith(QUERY_PROTOCOL)) {
          sameType = true;
        }
      }


      if (!sameType) {
        differences.push("type");
      } else {
        switch (server.type) {
          case "bookmark":
          case "query":
            if (!areURLsEqual(server.bmkUri, client.bmkUri)) {
              differences.push("bmkUri");
            }
            break;
          case "separator":
            if (server.pos != client.pos) {
              differences.push("pos");
            }
            break;
          case "livemark":
            if (server.feedUri != client.feedUri) {
              differences.push("feedUri");
            }
            if (server.siteUri != client.siteUri) {
              differences.push("siteUri");
            }
            break;
          case "folder":
            if (server.id === "places" && !problemData.rootOnServer) {
              // It's the fabricated places root. It won't have the GUIDs, but
              // it doesn't matter.
              break;
            }
            if (client.childGUIDs || server.childGUIDs) {
              let cl = client.childGUIDs || [];
              let sl = server.childGUIDs || [];
              if (!CommonUtils.arrayEqual(cl, sl)) {
                structuralDifferences.push("childGUIDs");
              }
            }
            break;
        }
      }

      if (differences.length) {
        problemData.differences.push({id, differences});
      }
      if (structuralDifferences.length) {
        problemData.structuralDifferences.push({ id, differences: structuralDifferences });
      }
    }
    return inspectionInfo;
  }

  async _getServerState(engine) {
// XXXXX - todo - we need to capture last-modified of the server here and
// ensure the repairer only applys with if-unmodified-since that date.
    let collection = engine.itemSource();
    let collectionKey = engine.service.collectionKeys.keyForCollection(engine.name);
    collection.full = true;
    let result = await collection.getBatched();
    if (!result.response.success) {
      throw result.response;
    }
    return result.records.map(record => {
      record.decrypt(collectionKey);
      return record.cleartext;
    });
  }

  async validate(engine) {
    let start = Date.now();
    let clientTree = await PlacesUtils.promiseBookmarksTree("", {
      includeItemIds: true
    });
    let serverState = await this._getServerState(engine);
    let serverRecordCount = serverState.length;
    let result = await this.compareServerWithClient(serverState, clientTree);
    let end = Date.now();
    let duration = end - start;

    engine._log.debug(`Validated bookmarks in ${duration}ms`);
    engine._log.debug(`Problem summary`);
    for (let { name, count } of result.problemData.getSummary()) {
      engine._log.debug(`  ${name}: ${count}`);
    }

    return {
      duration,
      version: this.version,
      problems: result.problemData,
      recordCount: serverRecordCount
    };
  }

}

BookmarkValidator.prototype.version = BOOKMARK_VALIDATOR_VERSION;
PK
!< q+modules/services-sync/browserid_identity.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["BrowserIDManager", "AuthenticationError"];

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/Log.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/tokenserverclient.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");

// Lazy imports to prevent unnecessary load on startup.
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
                                  "resource://services-sync/main.js");

XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
                                  "resource://services-sync/keys.js");

XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                  "resource://gre/modules/FxAccounts.jsm");

XPCOMUtils.defineLazyGetter(this, "log", function() {
  let log = Log.repository.getLogger("Sync.BrowserIDManager");
  log.level = Log.Level[Svc.Prefs.get("log.logger.identity")] || Log.Level.Error;
  return log;
});

// FxAccountsCommon.js doesn't use a "namespace", so create one here.
var fxAccountsCommon = {};
Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);

const OBSERVER_TOPICS = [
  fxAccountsCommon.ONLOGIN_NOTIFICATION,
  fxAccountsCommon.ONLOGOUT_NOTIFICATION,
  fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
];

function deriveKeyBundle(kB) {
  let out = CryptoUtils.hkdf(kB, undefined,
                             "identity.mozilla.com/picl/v1/oldsync", 2 * 32);
  let bundle = new BulkKeyBundle();
  // [encryptionKey, hmacKey]
  bundle.keyPair = [out.slice(0, 32), out.slice(32, 64)];
  return bundle;
}

/*
  General authentication error for abstracting authentication
  errors from multiple sources (e.g., from FxAccounts, TokenServer).
  details is additional details about the error - it might be a string, or
  some other error object (which should do the right thing when toString() is
  called on it)
*/
function AuthenticationError(details, source) {
  this.details = details;
  this.source = source;
}

AuthenticationError.prototype = {
  toString() {
    return "AuthenticationError(" + this.details + ")";
  }
}

this.BrowserIDManager = function BrowserIDManager() {
  // NOTE: _fxaService and _tokenServerClient are replaced with mocks by
  // the test suite.
  this._fxaService = fxAccounts;
  this._tokenServerClient = new TokenServerClient();
  this._tokenServerClient.observerPrefix = "weave:service";
  // will be a promise that resolves when we are ready to authenticate
  this.whenReadyToAuthenticate = null;
  this._log = log;
};

this.BrowserIDManager.prototype = {
  _fxaService: null,
  _tokenServerClient: null,
  // https://docs.services.mozilla.com/token/apis.html
  _token: null,
  _signedInUser: null, // the signedinuser we got from FxAccounts.

  // null if no error, otherwise a LOGIN_FAILED_* value that indicates why
  // we failed to authenticate (but note it might not be an actual
  // authentication problem, just a transient network error or similar)
  _authFailureReason: null,

  // it takes some time to fetch a sync key bundle, so until this flag is set,
  // we don't consider the lack of a keybundle as a failure state.
  _shouldHaveSyncKeyBundle: false,

  hashedUID() {
    if (!this._hashedUID) {
      throw new Error("hashedUID: Don't seem to have previously seen a token");
    }
    return this._hashedUID;
  },

  // Return a hashed version of a deviceID, suitable for telemetry.
  hashedDeviceID(deviceID) {
    let uid = this.hashedUID();
    // Combine the raw device id with the metrics uid to create a stable
    // unique identifier that can't be mapped back to the user's FxA
    // identity without knowing the metrics HMAC key.
    return Utils.sha256(deviceID + uid);
  },

  deviceID() {
    return this._signedInUser && this._signedInUser.deviceId;
  },

  initialize() {
    for (let topic of OBSERVER_TOPICS) {
      Services.obs.addObserver(this, topic);
    }
  },

  /**
   * Ensure the user is logged in.  Returns a promise that resolves when
   * the user is logged in, or is rejected if the login attempt has failed.
   */
  ensureLoggedIn() {
    if (!this._shouldHaveSyncKeyBundle && this.whenReadyToAuthenticate) {
      // We are already in the process of logging in.
      return this.whenReadyToAuthenticate.promise;
    }

    // If we are already happy then there is nothing more to do.
    if (this._syncKeyBundle) {
      return Promise.resolve();
    }

    // Similarly, if we have a previous failure that implies an explicit
    // re-entering of credentials by the user is necessary we don't take any
    // further action - an observer will fire when the user does that.
    if (Weave.Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
      return Promise.reject(new Error("User needs to re-authenticate"));
    }

    // So - we've a previous auth problem and aren't currently attempting to
    // log in - so fire that off.
    this.initializeWithCurrentIdentity();
    return this.whenReadyToAuthenticate.promise;
  },

  finalize() {
    // After this is called, we can expect Service.identity != this.
    for (let topic of OBSERVER_TOPICS) {
      Services.obs.removeObserver(this, topic);
    }
    this.resetCredentials();
    this._signedInUser = null;
  },

  initializeWithCurrentIdentity(isInitialSync = false) {
    // While this function returns a promise that resolves once we've started
    // the auth process, that process is complete when
    // this.whenReadyToAuthenticate.promise resolves.
    this._log.trace("initializeWithCurrentIdentity");

    // Reset the world before we do anything async.
    this.whenReadyToAuthenticate = PromiseUtils.defer();
    this.whenReadyToAuthenticate.promise.catch(err => {
      this._log.error("Could not authenticate", err);
    });

    // initializeWithCurrentIdentity() can be called after the
    // identity module was first initialized, e.g., after the
    // user completes a force authentication, so we should make
    // sure all credentials are reset before proceeding.
    this.resetCredentials();
    this._authFailureReason = null;

    return this._fxaService.getSignedInUser().then(accountData => {
      if (!accountData) {
        this._log.info("initializeWithCurrentIdentity has no user logged in");
        // and we are as ready as we can ever be for auth.
        this._shouldHaveSyncKeyBundle = true;
        this.whenReadyToAuthenticate.reject("no user is logged in");
        return;
      }

      this.username = accountData.email;
      this._updateSignedInUser(accountData);
      // The user must be verified before we can do anything at all; we kick
      // this and the rest of initialization off in the background (ie, we
      // don't return the promise)
      this._log.info("Waiting for user to be verified.");
      this._fxaService.whenVerified(accountData).then(accountData => {
        this._updateSignedInUser(accountData);

        this._log.info("Starting fetch for key bundle.");
        return this._fetchTokenForUser();
      }).then(token => {
        this._token = token;
        if (token) {
          // We may not have a token if the master-password is locked - but we
          // still treat this as "success" so we don't prompt for re-authentication.
          this._hashedUID = token.hashed_fxa_uid; // see _ensureValidToken for why we do this...
        }
        this._shouldHaveSyncKeyBundle = true; // and we should actually have one...
        this.whenReadyToAuthenticate.resolve();
        this._log.info("Background fetch for key bundle done");
        Weave.Status.login = LOGIN_SUCCEEDED;
        if (isInitialSync) {
          this._log.info("Doing initial sync actions");
          Svc.Prefs.set("firstSync", "resetClient");
          Services.obs.notifyObservers(null, "weave:service:setup-complete");
          Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
        }
      }).catch(authErr => {
        // report what failed...
        this._log.error("Background fetch for key bundle failed", authErr);
        this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
        this.whenReadyToAuthenticate.reject(authErr);
      });
      // and we are done - the fetch continues on in the background...
    }).catch(err => {
      this._log.error("Processing logged in account", err);
    });
  },

  _updateSignedInUser(userData) {
    // This object should only ever be used for a single user.  It is an
    // error to update the data if the user changes (but updates are still
    // necessary, as each call may add more attributes to the user).
    // We start with no user, so an initial update is always ok.
    if (this._signedInUser && this._signedInUser.email != userData.email) {
      throw new Error("Attempting to update to a different user.")
    }
    this._signedInUser = userData;
  },

  logout() {
    // This will be called when sync fails (or when the account is being
    // unlinked etc).  It may have failed because we got a 401 from a sync
    // server, so we nuke the token.  Next time sync runs and wants an
    // authentication header, we will notice the lack of the token and fetch a
    // new one.
    this._token = null;
  },

  observe(subject, topic, data) {
    this._log.debug("observed " + topic);
    switch (topic) {
    case fxAccountsCommon.ONLOGIN_NOTIFICATION: {
      // If our existing Sync state is that we needed to reauth, clear that
      // state now - it will get reset back if a problem persists.
      if (Weave.Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
        Weave.Status.login = LOGIN_SUCCEEDED;
      }
      // This should only happen if we've been initialized without a current
      // user - otherwise we'd have seen the LOGOUT notification and been
      // thrown away.
      // The exception is when we've initialized with a user that needs to
      // reauth with the server - in that case we will also get here, but
      // should have the same identity, and so we pass `false` into
      // initializeWithCurrentIdentity so that we won't do a full sync for our
      // first sync if we can avoid it.
      // initializeWithCurrentIdentity will throw and log if these constraints
      // aren't met (indirectly, via _updateSignedInUser()), so just go ahead
      // and do the init.
      let firstLogin = !this.username;
      this.initializeWithCurrentIdentity(firstLogin);

      if (!firstLogin) {
        // We still want to trigger these even if it isn't our first login.
        // Note that the promise returned by `initializeWithCurrentIdentity`
        // is resolved at the start of authentication, but we don't want to fire
        // this event or start the next sync until after authentication is done
        // (which is signaled by `this.whenReadyToAuthenticate.promise` resolving).
        this.whenReadyToAuthenticate.promise.then(() => {
          Services.obs.notifyObservers(null, "weave:service:setup-complete");
          return Async.promiseYield();
        }).then(() => {
          return Weave.Service.sync();
        }).catch(e => {
          this._log.warn("Failed to trigger setup complete notification", e);
        });
      }
    } break;

    case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
      Async.promiseSpinningly(Weave.Service.startOver());
      // startOver will cause this instance to be thrown away, so there's
      // nothing else to do.
      break;

    case fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION:
      // throw away token and fetch a new one
      this.resetCredentials();
      this._ensureValidToken().catch(err =>
        this._log.error("Error while fetching a new token", err));
      break;
    }
  },

  /**
   * Compute the sha256 of the message bytes.  Return bytes.
   */
  _sha256(message) {
    let hasher = Cc["@mozilla.org/security/hash;1"]
                    .createInstance(Ci.nsICryptoHash);
    hasher.init(hasher.SHA256);
    return CryptoUtils.digestBytes(message, hasher);
  },

  /**
   * Compute the X-Client-State header given the byte string kB.
   *
   * Return string: hex(first16Bytes(sha256(kBbytes)))
   */
  _computeXClientState(kBbytes) {
    return CommonUtils.bytesAsHex(this._sha256(kBbytes).slice(0, 16), false);
  },

  /**
   * Provide override point for testing token expiration.
   */
  _now() {
    return this._fxaService.now()
  },

  get _localtimeOffsetMsec() {
    return this._fxaService.localtimeOffsetMsec;
  },

  get syncKeyBundle() {
    return this._syncKeyBundle;
  },

  get username() {
    return Svc.Prefs.get("username", null);
  },

  /**
   * Set the username value.
   *
   * Changing the username has the side-effect of wiping credentials.
   */
  set username(value) {
    if (value) {
      value = value.toLowerCase();

      if (value == this.username) {
        return;
      }

      Svc.Prefs.set("username", value);
    } else {
      Svc.Prefs.reset("username");
    }

    // If we change the username, we interpret this as a major change event
    // and wipe out the credentials.
    this._log.info("Username changed. Removing stored credentials.");
    this.resetCredentials();
  },

  /**
   * Resets/Drops all credentials we hold for the current user.
   */
  resetCredentials() {
    this.resetSyncKeyBundle();
    this._token = null;
    this._hashedUID = null;
    // The cluster URL comes from the token, so resetting it to empty will
    // force Sync to not accidentally use a value from an earlier token.
    Weave.Service.clusterURL = null;
  },

  /**
   * Resets/Drops the sync key bundle we hold for the current user.
   */
  resetSyncKeyBundle() {
    this._syncKeyBundle = null;
    this._shouldHaveSyncKeyBundle = false;
  },

  /**
   * Pre-fetches any information that might help with migration away from this
   * identity.  Called after every sync and is really just an optimization that
   * allows us to avoid a network request for when we actually need the
   * migration info.
   */
  prefetchMigrationSentinel(service) {
    // nothing to do here until we decide to migrate away from FxA.
  },

  /**
    * Return credentials hosts for this identity only.
    */
  _getSyncCredentialsHosts() {
    return Utils.getSyncCredentialsHostsFxA();
  },

  /**
   * Deletes Sync credentials from the password manager.
   */
  deleteSyncCredentials() {
    for (let host of this._getSyncCredentialsHosts()) {
      let logins = Services.logins.findLogins({}, host, "", "");
      for (let login of logins) {
        Services.logins.removeLogin(login);
      }
    }
  },

  /**
   * The current state of the auth credentials.
   *
   * This essentially validates that enough credentials are available to use
   * Sync. It doesn't check we have all the keys we need as the master-password
   * may have been locked when we tried to get them - we rely on
   * unlockAndVerifyAuthState to check that for us.
   */
  get currentAuthState() {
    if (this._authFailureReason) {
      this._log.info("currentAuthState returning " + this._authFailureReason +
                     " due to previous failure");
      return this._authFailureReason;
    }

    // TODO: need to revisit this. Currently this isn't ready to go until
    // both the username and syncKeyBundle are both configured and having no
    // username seems to make things fail fast so that's good.
    if (!this.username) {
      return LOGIN_FAILED_NO_USERNAME;
    }

    return STATUS_OK;
  },

  // Do we currently have keys, or do we have enough that we should be able
  // to successfully fetch them?
  _canFetchKeys() {
    let userData = this._signedInUser;
    // a keyFetchToken means we can almost certainly grab them.
    // kA and kB means we already have them.
    return userData && (userData.keyFetchToken || (userData.kA && userData.kB));
  },

  /**
   * Verify the current auth state, unlocking the master-password if necessary.
   *
   * Returns a promise that resolves with the current auth state after
   * attempting to unlock.
   */
  unlockAndVerifyAuthState() {
    if (this._canFetchKeys()) {
      log.debug("unlockAndVerifyAuthState already has (or can fetch) sync keys");
      return Promise.resolve(STATUS_OK);
    }
    // so no keys - ensure MP unlocked.
    if (!Utils.ensureMPUnlocked()) {
      // user declined to unlock, so we don't know if they are stored there.
      log.debug("unlockAndVerifyAuthState: user declined to unlock master-password");
      return Promise.resolve(MASTER_PASSWORD_LOCKED);
    }
    // now we are unlocked we must re-fetch the user data as we may now have
    // the details that were previously locked away.
    return this._fxaService.getSignedInUser().then(
      accountData => {
        this._updateSignedInUser(accountData);
        // If we still can't get keys it probably means the user authenticated
        // without unlocking the MP or cleared the saved logins, so we've now
        // lost them - the user will need to reauth before continuing.
        let result;
        if (this._canFetchKeys()) {
          result = STATUS_OK;
        } else {
          result = LOGIN_FAILED_LOGIN_REJECTED;
        }
        log.debug("unlockAndVerifyAuthState re-fetched credentials and is returning", result);
        return result;
      }
    );
  },

  /**
   * Do we have a non-null, not yet expired token for the user currently
   * signed in?
   */
  hasValidToken() {
    // If pref is set to ignore cached authentication credentials for debugging,
    // then return false to force the fetching of a new token.
    let ignoreCachedAuthCredentials = false;
    try {
      ignoreCachedAuthCredentials = Svc.Prefs.get("debug.ignoreCachedAuthCredentials");
    } catch (e) {
      // Pref doesn't exist
    }
    if (ignoreCachedAuthCredentials) {
      return false;
    }
    if (!this._token) {
      return false;
    }
    if (this._token.expiration < this._now()) {
      return false;
    }
    return true;
  },

  // Get our tokenServerURL - a private helper. Returns a string.
  get _tokenServerUrl() {
    // We used to support services.sync.tokenServerURI but this was a
    // pain-point for people using non-default servers as Sync may auto-reset
    // all services.sync prefs. So if that still exists, it wins.
    let url = Svc.Prefs.get("tokenServerURI"); // Svc.Prefs "root" is services.sync
    if (!url) {
      url = Services.prefs.getCharPref("identity.sync.tokenserver.uri");
    }
    while (url.endsWith("/")) { // trailing slashes cause problems...
      url = url.slice(0, -1);
    }
    return url;
  },

  // Refresh the sync token for our user. Returns a promise that resolves
  // with a token (which may be null in one sad edge-case), or rejects with an
  // error.
  _fetchTokenForUser() {
    // tokenServerURI is mis-named - convention is uri means nsISomething...
    let tokenServerURI = this._tokenServerUrl;
    let log = this._log;
    let client = this._tokenServerClient;
    let fxa = this._fxaService;
    let userData = this._signedInUser;

    // We need kA and kB for things to work.  If we don't have them, just
    // return null for the token - sync calling unlockAndVerifyAuthState()
    // before actually syncing will setup the error states if necessary.
    if (!this._canFetchKeys()) {
      log.info("Unable to fetch keys (master-password locked?), so aborting token fetch");
      return Promise.resolve(null);
    }

    let maybeFetchKeys = () => {
      // This is called at login time and every time we need a new token - in
      // the latter case we already have kA and kB, so optimise that case.
      if (userData.kA && userData.kB) {
        return null;
      }
      log.info("Fetching new keys");
      return this._fxaService.getKeys().then(
        newUserData => {
          userData = newUserData;
          this._updateSignedInUser(userData); // throws if the user changed.
        }
      );
    }

    let getToken = assertion => {
      log.debug("Getting a token");
      let deferred = PromiseUtils.defer();
      let cb = function(err, token) {
        if (err) {
          return deferred.reject(err);
        }
        log.debug("Successfully got a sync token");
        return deferred.resolve(token);
      };

      let kBbytes = CommonUtils.hexToBytes(userData.kB);
      let headers = {"X-Client-State": this._computeXClientState(kBbytes)};
      client.getTokenFromBrowserIDAssertion(tokenServerURI, assertion, cb, headers);
      return deferred.promise;
    }

    let getAssertion = () => {
      log.info("Getting an assertion from", tokenServerURI);
      let audience = Services.io.newURI(tokenServerURI).prePath;
      return fxa.getAssertion(audience);
    };

    // wait until the account email is verified and we know that
    // getAssertion() will return a real assertion (not null).
    return fxa.whenVerified(this._signedInUser)
      .then(() => maybeFetchKeys())
      .then(() => getAssertion())
      .then(assertion => getToken(assertion))
      .catch(err => {
        // If we get a 401 fetching the token it may be that our certificate
        // needs to be regenerated.
        if (!err.response || err.response.status !== 401) {
          return Promise.reject(err);
        }
        log.warn("Token server returned 401, refreshing certificate and retrying token fetch");
        return fxa.invalidateCertificate()
          .then(() => getAssertion())
          .then(assertion => getToken(assertion))
      })
      .then(token => {
        // TODO: Make it be only 80% of the duration, so refresh the token
        // before it actually expires. This is to avoid sync storage errors
        // otherwise, we get a nasty notification bar briefly. Bug 966568.
        token.expiration = this._now() + (token.duration * 1000) * 0.80;
        if (!this._syncKeyBundle) {
          // We are given kA/kB as hex.
          this._syncKeyBundle = deriveKeyBundle(Utils.hexToBytes(userData.kB));
        }
        return token;
      })
      .catch(err => {
        // TODO: unify these errors - we need to handle errors thrown by
        // both tokenserverclient and hawkclient.
        // A tokenserver error thrown based on a bad response.
        if (err.response && err.response.status === 401) {
          err = new AuthenticationError(err, "tokenserver");
        // A hawkclient error.
        } else if (err.code && err.code === 401) {
          err = new AuthenticationError(err, "hawkclient");
        // An FxAccounts.jsm error.
        } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
          err = new AuthenticationError(err, "fxaccounts");
        }

        // TODO: write tests to make sure that different auth error cases are handled here
        // properly: auth error getting assertion, auth error getting token (invalid generation
        // and client-state error)
        if (err instanceof AuthenticationError) {
          this._log.error("Authentication error in _fetchTokenForUser", err);
          // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
          this._authFailureReason = LOGIN_FAILED_LOGIN_REJECTED;
        } else {
          this._log.error("Non-authentication error in _fetchTokenForUser", err);
          // for now assume it is just a transient network related problem
          // (although sadly, it might also be a regular unhandled exception)
          this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
        }
        // this._authFailureReason being set to be non-null in the above if clause
        // ensures we are in the correct currentAuthState, and
        // this._shouldHaveSyncKeyBundle being true ensures everything that cares knows
        // that there is no authentication dance still under way.
        this._shouldHaveSyncKeyBundle = true;
        Weave.Status.login = this._authFailureReason;
        throw err;
      });
  },

  // Returns a promise that is resolved when we have a valid token for the
  // current user stored in this._token.  When resolved, this._token is valid.
  _ensureValidToken() {
    if (this.hasValidToken()) {
      this._log.debug("_ensureValidToken already has one");
      return Promise.resolve();
    }
    const notifyStateChanged =
      () => Services.obs.notifyObservers(null, "weave:service:login:change");
    // reset this._token as a safety net to reduce the possibility of us
    // repeatedly attempting to use an invalid token if _fetchTokenForUser throws.
    this._token = null;
    return this._fetchTokenForUser().then(
      token => {
        this._token = token;
        // we store the hashed UID from the token so that if we see a transient
        // error fetching a new token we still know the "most recent" hashed
        // UID for telemetry.
        if (token) {
          // We may not have a token if the master-password is locked.
          this._hashedUID = token.hashed_fxa_uid;
        }
        notifyStateChanged();
      },
      error => {
        notifyStateChanged();
        throw error
      }
    );
  },

  getResourceAuthenticator() {
    return this._getAuthenticationHeader.bind(this);
  },

  /**
   * Obtain a function to be used for adding auth to RESTRequest instances.
   */
  getRESTRequestAuthenticator() {
    return this._addAuthenticationHeader.bind(this);
  },

  /**
   * @return a Hawk HTTP Authorization Header, lightly wrapped, for the .uri
   * of a RESTRequest or AsyncResponse object.
   */
  _getAuthenticationHeader(httpObject, method) {
    let cb = Async.makeSpinningCallback();
    this._ensureValidToken().then(cb, cb);
    // Note that in failure states we return null, causing the request to be
    // made without authorization headers, thereby presumably causing a 401,
    // which causes Sync to log out. If we throw, this may not happen as
    // expected.
    try {
      cb.wait();
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.error("Failed to fetch a token for authentication", ex);
      return null;
    }
    if (!this._token) {
      return null;
    }
    let credentials = {algorithm: "sha256",
                       id: this._token.id,
                       key: this._token.key,
                      };
    method = method || httpObject.method;

    // Get the local clock offset from the Firefox Accounts server.  This should
    // be close to the offset from the storage server.
    let options = {
      now: this._now(),
      localtimeOffsetMsec: this._localtimeOffsetMsec,
      credentials,
    };

    let headerValue = CryptoUtils.computeHAWK(httpObject.uri, method, options);
    return {headers: {authorization: headerValue.field}};
  },

  _addAuthenticationHeader(request, method) {
    let header = this._getAuthenticationHeader(request, method);
    if (!header) {
      return null;
    }
    request.setHeader("authorization", header.headers.authorization);
    return request;
  },

  createClusterManager(service) {
    return new BrowserIDClusterManager(service);
  },

  // Tell Sync what the login status should be if it saw a 401 fetching
  // info/collections as part of login verification (typically immediately
  // after login.)
  // In our case, it almost certainly means a transient error fetching a token
  // (and hitting this will cause us to logout, which will correctly handle an
  // authoritative login issue.)
  loginStatusFromVerification404() {
    return LOGIN_FAILED_NETWORK_ERROR;
  },
};

/* An implementation of the ClusterManager for this identity
 */

function BrowserIDClusterManager(service) {
  this._log = log;
  this.service = service;
}

BrowserIDClusterManager.prototype = {
  get identity() {
    return this.service.identity;
  },

  /**
   * Determine the cluster for the current user and update state.
   */
  setCluster() {
    // Make sure we didn't get some unexpected response for the cluster.
    let cluster = this._findCluster();
    this._log.debug("Cluster value = " + cluster);
    if (cluster == null) {
      return false;
    }

    // Convert from the funky "String object with additional properties" that
    // resource.js returns to a plain-old string.
    cluster = cluster.toString();
    // Don't update stuff if we already have the right cluster
    if (cluster == this.service.clusterURL) {
      return false;
    }

    this._log.debug("Setting cluster to " + cluster);
    this.service.clusterURL = cluster;

    return true;
  },

  _findCluster() {
    let endPointFromIdentityToken = () => {
      // The only reason (in theory ;) that we can end up with a null token
      // is when this.identity._canFetchKeys() returned false.  In turn, this
      // should only happen if the master-password is locked or the credentials
      // storage is screwed, and in those cases we shouldn't have started
      // syncing so shouldn't get here anyway.
      // But better safe than sorry! To keep things clearer, throw an explicit
      // exception - the message will appear in the logs and the error will be
      // treated as transient.
      if (!this.identity._token) {
        throw new Error("Can't get a cluster URL as we can't fetch keys.");
      }
      let endpoint = this.identity._token.endpoint;
      // For Sync 1.5 storage endpoints, we use the base endpoint verbatim.
      // However, it should end in "/" because we will extend it with
      // well known path components. So we add a "/" if it's missing.
      if (!endpoint.endsWith("/")) {
        endpoint += "/";
      }
      log.debug("_findCluster returning " + endpoint);
      return endpoint;
    };

    // Spinningly ensure we are ready to authenticate and have a valid token.
    let promiseClusterURL = () => {
      return this.identity.whenReadyToAuthenticate.promise.then(
        () => {
          // We need to handle node reassignment here.  If we are being asked
          // for a clusterURL while the service already has a clusterURL, then
          // it's likely a 401 was received using the existing token - in which
          // case we just discard the existing token and fetch a new one.
          if (this.service.clusterURL) {
            log.debug("_findCluster has a pre-existing clusterURL, so discarding the current token");
            this.identity._token = null;
          }
          return this.identity._ensureValidToken();
        }
      ).then(endPointFromIdentityToken
      );
    };

    let cb = Async.makeSpinningCallback();
    promiseClusterURL().then(function(clusterURL) {
      cb(null, clusterURL);
    }).catch(err => {
      log.info("Failed to fetch the cluster URL", err);
      // service.js's verifyLogin() method will attempt to fetch a cluster
      // URL when it sees a 401.  If it gets null, it treats it as a "real"
      // auth error and sets Status.login to LOGIN_FAILED_LOGIN_REJECTED, which
      // in turn causes a notification bar to appear informing the user they
      // need to re-authenticate.
      // On the other hand, if fetching the cluster URL fails with an exception,
      // verifyLogin() assumes it is a transient error, and thus doesn't show
      // the notification bar under the assumption the issue will resolve
      // itself.
      // Thus:
      // * On a real 401, we must return null.
      // * On any other problem we must let an exception bubble up.
      if (err instanceof AuthenticationError) {
        // callback with no error and a null result - cb.wait() returns null.
        cb(null, null);
      } else {
        // callback with an error - cb.wait() completes by raising an exception.
        cb(err);
      }
    });
    return cb.wait();
  },

  getUserBaseURL() {
    // Legacy Sync and FxA Sync construct the userBaseURL differently. Legacy
    // Sync appends path components onto an empty path, and in FxA Sync the
    // token server constructs this for us in an opaque manner. Since the
    // cluster manager already sets the clusterURL on Service and also has
    // access to the current identity, we added this functionality here.
    return this.service.clusterURL;
  }
}
PK
!<>s99*modules/services-sync/collection_repair.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/main.js");

XPCOMUtils.defineLazyModuleGetter(this, "BookmarkRepairRequestor",
  "resource://services-sync/bookmark_repair.js");

this.EXPORTED_SYMBOLS = ["getRepairRequestor", "getAllRepairRequestors",
                         "CollectionRepairRequestor",
                         "getRepairResponder",
                         "CollectionRepairResponder"];

// The individual requestors/responders, lazily loaded.
const REQUESTORS = {
  bookmarks: ["bookmark_repair.js", "BookmarkRepairRequestor"],
}

const RESPONDERS = {
  bookmarks: ["bookmark_repair.js", "BookmarkRepairResponder"],
}

// Should we maybe enforce the requestors being a singleton?
function _getRepairConstructor(which, collection) {
  if (!(collection in which)) {
    return null;
  }
  let [modname, symbolname] = which[collection];
  let ns = {};
  Cu.import("resource://services-sync/" + modname, ns);
  return ns[symbolname];
}

function getRepairRequestor(collection) {
  let ctor = _getRepairConstructor(REQUESTORS, collection);
  if (!ctor) {
    return null;
  }
  return new ctor();
}

function getAllRepairRequestors() {
  let result = {};
  for (let collection of Object.keys(REQUESTORS)) {
    let ctor = _getRepairConstructor(REQUESTORS, collection);
    result[collection] = new ctor();
  }
  return result;
}

function getRepairResponder(collection) {
  let ctor = _getRepairConstructor(RESPONDERS, collection);
  if (!ctor) {
    return null;
  }
  return new ctor();
}

// The abstract classes.
class CollectionRepairRequestor {
  constructor(service = null) {
    // allow service to be mocked in tests.
    this.service = service || Weave.Service;
  }

  /* Try to resolve some issues with the server without involving other clients.
     Returns true if we repaired some items.

     @param   validationInfo       {Object}
              The validation info as returned by the collection's validator.

  */
  tryServerOnlyRepairs(validationInfo) {
    return false;
  }

  /* See if the repairer is willing and able to begin a repair process given
     the specified validation information.
     Returns true if a repair was started and false otherwise.

     @param   validationInfo       {Object}
              The validation info as returned by the collection's validator.

     @param   flowID               {String}
              A guid that uniquely identifies this repair process for this
              collection, and which should be sent to any requestors and
              reported in telemetry.

  */
  async startRepairs(validationInfo, flowID) {
    throw new Error("not implemented");
  }

  /* Work out what state our current repair request is in, and whether it can
     proceed to a new state.
     Returns true if we could continue the repair - even if the state didn't
     actually move. Returns false if we aren't actually repairing.

     @param   responseInfo       {Object}
              An optional response to a previous repair request, as returned
              by a remote repair responder.

  */
  async continueRepairs(responseInfo = null) {
    throw new Error("not implemented");
  }
}

class CollectionRepairResponder {
  constructor(service = null) {
    // allow service to be mocked in tests.
    this.service = service || Weave.Service;
  }

  /* Take some action in response to a repair request. Returns a promise that
     resolves once the repair process has started, or rejects if there
     was an error starting the repair.

     Note that when the promise resolves the repair is not yet complete - at
     some point in the future the repair will auto-complete, at which time
     |rawCommand| will be removed from the list of client commands for this
     client.

     @param   request       {Object}
              The repair request as sent by another client.

     @param   rawCommand    {Object}
              The command object as stored in the clients engine, and which
              will be automatically removed once a repair completes.
  */
  async repair(request, rawCommand) {
    throw new Error("not implemented");
  }
}
PK
!<k<ff-modules/services-sync/collection_validator.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Async",
                                  "resource://services-common/async.js");


this.EXPORTED_SYMBOLS = ["CollectionValidator", "CollectionProblemData"];

class CollectionProblemData {
  constructor() {
    this.missingIDs = 0;
    this.duplicates = [];
    this.clientMissing = [];
    this.serverMissing = [];
    this.serverDeleted = [];
    this.serverUnexpected = [];
    this.differences = [];
  }

  /**
   * Produce a list summarizing problems found. Each entry contains {name, count},
   * where name is the field name for the problem, and count is the number of times
   * the problem was encountered.
   *
   * Validation has failed if all counts are not 0.
   */
  getSummary() {
    return [
      { name: "clientMissing", count: this.clientMissing.length },
      { name: "serverMissing", count: this.serverMissing.length },
      { name: "serverDeleted", count: this.serverDeleted.length },
      { name: "serverUnexpected", count: this.serverUnexpected.length },
      { name: "differences", count: this.differences.length },
      { name: "missingIDs", count: this.missingIDs },
      { name: "duplicates", count: this.duplicates.length }
    ];
  }
}

class CollectionValidator {
  // Construct a generic collection validator. This is intended to be called by
  // subclasses.
  // - name: Name of the engine
  // - idProp: Property that identifies a record. That is, if a client and server
  //   record have the same value for the idProp property, they should be
  //   compared against eachother.
  // - props: Array of properties that should be compared
  constructor(name, idProp, props) {
    this.name = name;
    this.props = props;
    this.idProp = idProp;

    // This property deals with the fact that form history records are never
    // deleted from the server. The FormValidator subclass needs to ignore the
    // client missing records, and it uses this property to achieve it -
    // (Bug 1354016).
    this.ignoresMissingClients = false;
  }

  // Should a custom ProblemData type be needed, return it here.
  emptyProblemData() {
    return new CollectionProblemData();
  }

  async getServerItems(engine) {
    let collection = engine.itemSource();
    let collectionKey = engine.service.collectionKeys.keyForCollection(engine.name);
    collection.full = true;
    let result = await collection.getBatched();
    if (!result.response.success) {
      throw result.response;
    }
    return result.records.map(record => {
      record.decrypt(collectionKey);
      return record.cleartext;
    });
  }

  // Should return a promise that resolves to an array of client items.
  getClientItems() {
    return Promise.reject("Must implement");
  }

  /**
   * Can we guarantee validation will fail with a reason that isn't actually a
   * problem? For example, if we know there are pending changes left over from
   * the last sync, this should resolve to false. By default resolves to true.
   */
  async canValidate() {
    return true;
  }

  // Turn the client item into something that can be compared with the server item,
  // and is also safe to mutate.
  normalizeClientItem(item) {
    return Cu.cloneInto(item, {});
  }

  // Turn the server item into something that can be easily compared with the client
  // items.
  async normalizeServerItem(item) {
    return item;
  }

  // Return whether or not a server item should be present on the client. Expected
  // to be overridden.
  clientUnderstands(item) {
    return true;
  }

  // Return whether or not a client item should be present on the server. Expected
  // to be overridden
  syncedByClient(item) {
    return true;
  }

  // Compare the server item and the client item, and return a list of property
  // names that are different. Can be overridden if needed.
  getDifferences(client, server) {
    let differences = [];
    for (let prop of this.props) {
      let clientProp = client[prop];
      let serverProp = server[prop];
      if ((clientProp || "") !== (serverProp || "")) {
        differences.push(prop);
      }
    }
    return differences;
  }

  // Returns an object containing
  //   problemData: an instance of the class returned by emptyProblemData(),
  //   clientRecords: Normalized client records
  //   records: Normalized server records,
  //   deletedRecords: Array of ids that were marked as deleted by the server.
  async compareClientWithServer(clientItems, serverItems) {
    let maybeYield = Async.jankYielder();
    const clientRecords = [];
    for (let item of clientItems) {
      await maybeYield();
      clientRecords.push(this.normalizeClientItem(item));
    }
    const serverRecords = [];
    for (let item of serverItems) {
      await maybeYield();
      serverRecords.push((await this.normalizeServerItem(item)));
    }
    let problems = this.emptyProblemData();
    let seenServer = new Map();
    let serverDeleted = new Set();
    let allRecords = new Map();

    for (let record of serverRecords) {
      let id = record[this.idProp];
      if (!id) {
        ++problems.missingIDs;
        continue;
      }
      if (record.deleted) {
        serverDeleted.add(record);
      } else {
        let possibleDupe = seenServer.get(id);
        if (possibleDupe) {
          problems.duplicates.push(id);
        } else {
          seenServer.set(id, record);
          allRecords.set(id, { server: record, client: null, });
        }
        record.understood = this.clientUnderstands(record);
      }
    }

    let seenClient = new Map();
    for (let record of clientRecords) {
      let id = record[this.idProp];
      record.shouldSync = this.syncedByClient(record);
      seenClient.set(id, record);
      let combined = allRecords.get(id);
      if (combined) {
        combined.client = record;
      } else {
        allRecords.set(id, { client: record, server: null });
      }
    }

    for (let [id, { server, client }] of allRecords) {
      if (!client && !server) {
        throw new Error("Impossible: no client or server record for " + id);
      } else if (server && !client) {
        if (!this.ignoresMissingClients && server.understood) {
          problems.clientMissing.push(id);
        }
      } else if (client && !server) {
        if (client.shouldSync) {
          problems.serverMissing.push(id);
        }
      } else {
        if (!client.shouldSync) {
          if (!problems.serverUnexpected.includes(id)) {
            problems.serverUnexpected.push(id);
          }
          continue;
        }
        let differences = this.getDifferences(client, server);
        if (differences && differences.length) {
          problems.differences.push({ id, differences });
        }
      }
    }
    return {
      problemData: problems,
      clientRecords,
      records: serverRecords,
      deletedRecords: [...serverDeleted]
    };
  }
}

// Default to 0, some engines may override.
CollectionValidator.prototype.version = 0;
PK
!<}
H!H!"modules/services-sync/constants.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Process each item in the "constants hash" to add to "global" and give a name
this.EXPORTED_SYMBOLS = [];
for (let [key, val] of Object.entries({

// Don't manually modify this line, as it is automatically replaced on merge day
// by the gecko_migration.py script.
WEAVE_VERSION: "1.58.0",

// Sync Server API version that the client supports.
SYNC_API_VERSION:                      "1.5",

// Version of the data format this client supports. The data format describes
// how records are packaged; this is separate from the Server API version and
// the per-engine cleartext formats.
STORAGE_VERSION:                       5,
PREFS_BRANCH:                          "services.sync.",

// Host "key" to access Weave Identity in the password manager
PWDMGR_HOST:                           "chrome://weave",
PWDMGR_PASSWORD_REALM:                 "Mozilla Services Password",
PWDMGR_PASSPHRASE_REALM:               "Mozilla Services Encryption Passphrase",
PWDMGR_KEYBUNDLE_REALM:                "Mozilla Services Key Bundles",

// Put in [] because those aren't allowed in a collection name.
DEFAULT_KEYBUNDLE_NAME:                "[default]",

// Our extra input to SHA256-HMAC in generateEntry.
// This includes the full crypto spec; change this when our algo changes.
HMAC_INPUT:                            "Sync-AES_256_CBC-HMAC256",

// Key dimensions.
SYNC_KEY_ENCODED_LENGTH:               26,
SYNC_KEY_DECODED_LENGTH:               16,
SYNC_KEY_HYPHENATED_LENGTH:            31,    // 26 chars, 5 hyphens.

NO_SYNC_NODE_INTERVAL:                 10 * 60 * 1000, // 10 minutes

MAX_ERROR_COUNT_BEFORE_BACKOFF:        3,
MAX_IGNORE_ERROR_COUNT:                5,

// Backoff intervals
MINIMUM_BACKOFF_INTERVAL:              15 * 60 * 1000,      // 15 minutes
MAXIMUM_BACKOFF_INTERVAL:              8 * 60 * 60 * 1000,  // 8 hours

// HMAC event handling timeout.
// 10 minutes: a compromise between the multi-desktop sync interval
// and the mobile sync interval.
HMAC_EVENT_INTERVAL:                   600000,

// How long to wait between sync attempts if the Master Password is locked.
MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000,   // 15 minutes

// The default for how long we "block" sync from running when doing a migration.
DEFAULT_BLOCK_PERIOD:                  2 * 24 * 60 * 60 * 1000, // 2 days

// 50 is hardcoded here because of URL length restrictions.
// (GUIDs can be up to 64 chars long.)
// Individual engines can set different values for their limit if their
// identifiers are shorter.
DEFAULT_GUID_FETCH_BATCH_SIZE:         50,

// Default batch size for applying incoming records.
DEFAULT_STORE_BATCH_SIZE:              1,
HISTORY_STORE_BATCH_SIZE:              50,
FORMS_STORE_BATCH_SIZE:                50,
PASSWORDS_STORE_BATCH_SIZE:            50,
ADDONS_STORE_BATCH_SIZE:               1000000, // process all addons at once
APPS_STORE_BATCH_SIZE:                 50,

// Default batch size for download batching
// (how many records are fetched at a time from the server when batching is used).
DEFAULT_DOWNLOAD_BATCH_SIZE:           1000,


// Default maximum size for a record payload
DEFAULT_MAX_RECORD_PAYLOAD_BYTES:      262144,  // 256KB

// score thresholds for early syncs
SINGLE_USER_THRESHOLD:                 1000,
MULTI_DEVICE_THRESHOLD:                300,

// Other score increment constants
SCORE_INCREMENT_SMALL:                 1,
SCORE_INCREMENT_MEDIUM:                10,

// Instant sync score increment
SCORE_INCREMENT_XLARGE:                300 + 1, //MULTI_DEVICE_THRESHOLD + 1

// Delay before incrementing global score
SCORE_UPDATE_DELAY:                    100,

// Delay for the back observer debouncer. This is chosen to be longer than any
// observed spurious idle/back events and short enough to pre-empt user activity.
IDLE_OBSERVER_BACK_DELAY:              100,

// Max number of records or bytes to upload in a single POST - we'll do multiple POSTS if either
// MAX_UPLOAD_RECORDS or MAX_UPLOAD_BYTES is hit)
MAX_UPLOAD_RECORDS:                    100,
MAX_UPLOAD_BYTES:                      1024 * 1023, // just under 1MB
MAX_HISTORY_UPLOAD:                    5000,
MAX_HISTORY_DOWNLOAD:                  5000,

// TTL of the message sent to another device when sending a tab
NOTIFY_TAB_SENT_TTL_SECS:              1 * 3600, // 1 hour

// Top-level statuses:
STATUS_OK:                             "success.status_ok",
SYNC_FAILED:                           "error.sync.failed",
LOGIN_FAILED:                          "error.login.failed",
SYNC_FAILED_PARTIAL:                   "error.sync.failed_partial",
CLIENT_NOT_CONFIGURED:                 "service.client_not_configured",
STATUS_DISABLED:                       "service.disabled",
MASTER_PASSWORD_LOCKED:                "service.master_password_locked",

// success states
LOGIN_SUCCEEDED:                       "success.login",
SYNC_SUCCEEDED:                        "success.sync",
ENGINE_SUCCEEDED:                      "success.engine",

// login failure status codes:
LOGIN_FAILED_NO_USERNAME:              "error.login.reason.no_username",
LOGIN_FAILED_NO_PASSPHRASE:            "error.login.reason.no_recoverykey",
LOGIN_FAILED_NETWORK_ERROR:            "error.login.reason.network",
LOGIN_FAILED_SERVER_ERROR:             "error.login.reason.server",
LOGIN_FAILED_INVALID_PASSPHRASE:       "error.login.reason.recoverykey",
LOGIN_FAILED_LOGIN_REJECTED:           "error.login.reason.account",

// sync failure status codes
METARECORD_DOWNLOAD_FAIL:              "error.sync.reason.metarecord_download_fail",
VERSION_OUT_OF_DATE:                   "error.sync.reason.version_out_of_date",
DESKTOP_VERSION_OUT_OF_DATE:           "error.sync.reason.desktop_version_out_of_date",
SETUP_FAILED_NO_PASSPHRASE:            "error.sync.reason.setup_failed_no_passphrase",
CREDENTIALS_CHANGED:                   "error.sync.reason.credentials_changed",
ABORT_SYNC_COMMAND:                    "aborting sync, process commands said so",
NO_SYNC_NODE_FOUND:                    "error.sync.reason.no_node_found",
OVER_QUOTA:                            "error.sync.reason.over_quota",
PROLONGED_SYNC_FAILURE:                "error.sync.prolonged_failure",
SERVER_MAINTENANCE:                    "error.sync.reason.serverMaintenance",

RESPONSE_OVER_QUOTA:                   "14",

// engine failure status codes
ENGINE_UPLOAD_FAIL:                    "error.engine.reason.record_upload_fail",
ENGINE_DOWNLOAD_FAIL:                  "error.engine.reason.record_download_fail",
ENGINE_UNKNOWN_FAIL:                   "error.engine.reason.unknown_fail",
ENGINE_APPLY_FAIL:                     "error.engine.reason.apply_fail",
ENGINE_METARECORD_DOWNLOAD_FAIL:       "error.engine.reason.metarecord_download_fail",
ENGINE_METARECORD_UPLOAD_FAIL:         "error.engine.reason.metarecord_upload_fail",
// an upload failure where the batch was interrupted with a 412
ENGINE_BATCH_INTERRUPTED:              "error.engine.reason.batch_interrupted",

// info types for Service.getStorageInfo
INFO_COLLECTIONS:                      "collections",
INFO_COLLECTION_USAGE:                 "collection_usage",
INFO_COLLECTION_COUNTS:                "collection_counts",
INFO_QUOTA:                            "quota",

// Ways that a sync can be disabled (messages only to be printed in debug log)
kSyncMasterPasswordLocked:             "User elected to leave Master Password locked",
kSyncWeaveDisabled:                    "Weave is disabled",
kSyncNetworkOffline:                   "Network is offline",
kSyncBackoffNotMet:                    "Trying to sync before the server said it's okay",
kFirstSyncChoiceNotMade:               "User has not selected an action for first sync",

// Application IDs
FIREFOX_ID:                            "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
FENNEC_ID:                             "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
SEAMONKEY_ID:                          "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
TEST_HARNESS_ID:                       "xuth@mozilla.org",

MIN_PP_LENGTH:                         12,
MIN_PASS_LENGTH:                       8,

DEVICE_TYPE_DESKTOP:                   "desktop",
DEVICE_TYPE_MOBILE:                    "mobile",

SQLITE_MAX_VARIABLE_NUMBER:            999,

})) {
  this[key] = val;
  this.EXPORTED_SYMBOLS.push(key);
}
PK
!<t%%modules/services-sync/doctor.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 doctor for our collections. She can be asked to make a consultation, and
// may just diagnose an issue without attempting to cure it, may diagnose and
// attempt to cure, or may decide she is overworked and underpaid.
// Or something - naming is hard :)

"use strict";

this.EXPORTED_SYMBOLS = ["Doctor"];

const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/resource.js");

Cu.import("resource://services-sync/util.js");
XPCOMUtils.defineLazyModuleGetter(this, "getRepairRequestor",
  "resource://services-sync/collection_repair.js");
XPCOMUtils.defineLazyModuleGetter(this, "getAllRepairRequestors",
  "resource://services-sync/collection_repair.js");

const log = Log.repository.getLogger("Sync.Doctor");

this.REPAIR_ADVANCE_PERIOD = 86400; // 1 day

this.Doctor = {
  anyClientsRepairing(service, collection, ignoreFlowID = null) {
    if (!service || !service.clientsEngine) {
      log.info("Missing clients engine, assuming we're in test code");
      return false;
    }
    return service.clientsEngine.remoteClients.some(client =>
      client.commands && client.commands.some(command => {
        if (command.command != "repairResponse" && command.command != "repairRequest") {
          return false;
        }
        if (!command.args || command.args.length != 1) {
          return false;
        }
        if (command.args[0].collection != collection) {
          return false;
        }
        if (ignoreFlowID != null && command.args[0].flowID == ignoreFlowID) {
          return false;
        }
        return true;
      })
    );
  },

  async consult(recentlySyncedEngines) {
    if (!Services.telemetry.canRecordBase) {
      log.info("Skipping consultation: telemetry reporting is disabled");
      return;
    }

    let engineInfos = this._getEnginesToValidate(recentlySyncedEngines);

    await this._runValidators(engineInfos);

    // We are called at the end of a sync, which is a good time to periodically
    // check each repairer to see if it can advance.
    if (this._now() - this.lastRepairAdvance > REPAIR_ADVANCE_PERIOD) {
      try {
        for (let [collection, requestor] of Object.entries(this._getAllRepairRequestors())) {
          try {
            let advanced = await requestor.continueRepairs();
            log.info(`${collection} reparier ${advanced ? "advanced" : "did not advance"}.`);
          } catch (ex) {
            if (Async.isShutdownException(ex)) {
              throw ex;
            }
            log.error(`${collection} repairer failed`, ex);
          }
        }
      } finally {
        this.lastRepairAdvance = this._now();
      }
    }
  },

  _getEnginesToValidate(recentlySyncedEngines) {
    let result = {};
    for (let e of recentlySyncedEngines) {
      let prefPrefix = `engine.${e.name}.`;
      if (!Svc.Prefs.get(prefPrefix + "validation.enabled", false)) {
        log.info(`Skipping check of ${e.name} - disabled via preferences`);
        continue;
      }
      // Check the last validation time for the engine.
      let lastValidation = Svc.Prefs.get(prefPrefix + "validation.lastTime", 0);
      let validationInterval = Svc.Prefs.get(prefPrefix + "validation.interval");
      let nowSeconds = this._now();

      if (nowSeconds - lastValidation < validationInterval) {
        log.info(`Skipping validation of ${e.name}: too recent since last validation attempt`);
        continue;
      }
      // Update the time now, even if we decline to actually perform a
      // validation. We don't want to check the rest of these more frequently
      // than once a day.
      Svc.Prefs.set(prefPrefix + "validation.lastTime", Math.floor(nowSeconds));

      // Validation only occurs a certain percentage of the time.
      let validationProbability = Svc.Prefs.get(prefPrefix + "validation.percentageChance", 0) / 100.0;
      if (validationProbability < Math.random()) {
        log.info(`Skipping validation of ${e.name}: Probability threshold not met`);
        continue;
      }

      let maxRecords = Svc.Prefs.get(prefPrefix + "validation.maxRecords");
      if (!maxRecords) {
        log.info(`Skipping validation of ${e.name}: No maxRecords specified`);
        continue;
      }
      // OK, so this is a candidate - the final decision will be based on the
      // number of records actually found.
      result[e.name] = { engine: e, maxRecords };
    }
    return result;
  },

  async _runValidators(engineInfos) {
    if (Object.keys(engineInfos).length == 0) {
      log.info("Skipping validation: no engines qualify");
      return;
    }

    if (Object.values(engineInfos).filter(i => i.maxRecords != -1).length != 0) {
      // at least some of the engines have maxRecord restrictions which require
      // us to ask the server for the counts.
      let countInfo = await this._fetchCollectionCounts();
      for (let [engineName, recordCount] of Object.entries(countInfo)) {
        if (engineName in engineInfos) {
          engineInfos[engineName].recordCount = recordCount;
        }
      }
    }

    for (let [engineName, { engine, maxRecords, recordCount }] of Object.entries(engineInfos)) {
      // maxRecords of -1 means "any number", so we can skip asking the server.
      // Used for tests.
      if (maxRecords >= 0 && recordCount > maxRecords) {
        log.debug(`Skipping validation for ${engineName} because ` +
                        `the number of records (${recordCount}) is greater ` +
                        `than the maximum allowed (${maxRecords}).`);
        continue;
      }
      let validator = engine.getValidator();
      if (!validator) {
        continue;
      }

      if (!await validator.canValidate()) {
        log.debug(`Skipping validation for ${engineName} because validator.canValidate() is false`);
        continue;
      }

      // Let's do it!
      Services.console.logStringMessage(
        `Sync is about to run a consistency check of ${engine.name}. This may be slow, and ` +
        `can be controlled using the pref "services.sync.${engine.name}.validation.enabled".\n` +
        `If you encounter any problems because of this, please file a bug.`);

      // Make a new flowID just incase we end up starting a repair.
      let flowID = Utils.makeGUID();
      try {
        // XXX - must get the flow ID to either the validator, or directly to
        // telemetry. I guess it's probably OK to always record a flowID even
        // if we don't end up repairing?
        log.info(`Running validator for ${engine.name}`);
        let result = await validator.validate(engine);
        Observers.notify("weave:engine:validate:finish", result, engine.name);
        // And see if we should repair.
        await this._maybeCure(engine, result, flowID);
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        log.error(`Failed to run validation on ${engine.name}!`, ex);
        Observers.notify("weave:engine:validate:error", ex, engine.name)
        // Keep validating -- there's no reason to think that a failure for one
        // validator would mean the others will fail.
      }
    }
  },

  async _maybeCure(engine, validationResults, flowID) {
    if (!this._shouldRepair(engine)) {
      log.info(`Skipping repair of ${engine.name} - disabled via preferences`);
      return;
    }

    let requestor = this._getRepairRequestor(engine.name);
    let didStart = false;
    if (requestor) {
      if (requestor.tryServerOnlyRepairs(validationResults)) {
        return; // TODO: It would be nice if we could request a validation to be
                // done on next sync.
      }
      didStart = await requestor.startRepairs(validationResults, flowID);
    }
    log.info(`${didStart ? "did" : "didn't"} start a repair of ${engine.name} with flowID ${flowID}`);
  },

  _shouldRepair(engine) {
    return Svc.Prefs.get(`engine.${engine.name}.repair.enabled`, false);
  },

  // mainly for mocking.
  async _fetchCollectionCounts() {
    let collectionCountsURL = Service.userBaseURL + "info/collection_counts";
    try {
      let infoResp = await Service._fetchInfo(collectionCountsURL);
      if (!infoResp.success) {
        log.error("Can't fetch collection counts: request to info/collection_counts responded with "
                        + infoResp.status);
        return {};
      }
      return infoResp.obj; // might throw because obj is a getter which parses json.
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      // Not running validation is totally fine, so we just write an error log and return.
      log.error("Caught error when fetching counts", ex);
      return {};
    }
  },

  get lastRepairAdvance() {
    return Svc.Prefs.get("doctor.lastRepairAdvance", 0);
  },

  set lastRepairAdvance(value) {
    Svc.Prefs.set("doctor.lastRepairAdvance", value);
  },

  // functions used so tests can mock them
  _now() {
    // We use the server time, which is SECONDS
    return AsyncResource.serverTime;
  },

  _getRepairRequestor(name) {
    return getRepairRequestor(name);
  },

  _getAllRepairRequestors() {
    return getAllRepairRequestors();
  }
}
PK
!<1dd'modules/services-sync/engines/addons.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file defines the add-on sync functionality.
 *
 * There are currently a number of known limitations:
 *  - We only sync XPI extensions and themes available from addons.mozilla.org.
 *    We hope to expand support for other add-ons eventually.
 *  - We only attempt syncing of add-ons between applications of the same type.
 *    This means add-ons will not synchronize between Firefox desktop and
 *    Firefox mobile, for example. This is because of significant add-on
 *    incompatibility between application types.
 *
 * Add-on records exist for each known {add-on, app-id} pair in the Sync client
 * set. Each record has a randomly chosen GUID. The records then contain
 * basic metadata about the add-on.
 *
 * We currently synchronize:
 *
 *  - Installations
 *  - Uninstallations
 *  - User enabling and disabling
 *
 * Synchronization is influenced by the following preferences:
 *
 *  - services.sync.addons.ignoreUserEnabledChanges
 *  - services.sync.addons.trustedSourceHostnames
 *
 *  and also influenced by whether addons have repository caching enabled and
 *  whether they allow installation of addons from insecure options (both of
 *  which are themselves influenced by the "extensions." pref branch)
 *
 * See the documentation in services-sync.js for the behavior of these prefs.
 */
"use strict";

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://services-sync/addonutils.js");
Cu.import("resource://services-sync/addonsreconciler.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/collection_validator.js");
Cu.import("resource://services-common/async.js");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                  "resource://gre/modules/addons/AddonRepository.jsm");

this.EXPORTED_SYMBOLS = ["AddonsEngine", "AddonValidator"];

// 7 days in milliseconds.
const PRUNE_ADDON_CHANGES_THRESHOLD = 60 * 60 * 24 * 7 * 1000;

/**
 * AddonRecord represents the state of an add-on in an application.
 *
 * Each add-on has its own record for each application ID it is installed
 * on.
 *
 * The ID of add-on records is a randomly-generated GUID. It is random instead
 * of deterministic so the URIs of the records cannot be guessed and so
 * compromised server credentials won't result in disclosure of the specific
 * add-ons present in a Sync account.
 *
 * The record contains the following fields:
 *
 *  addonID
 *    ID of the add-on. This correlates to the "id" property on an Addon type.
 *
 *  applicationID
 *    The application ID this record is associated with.
 *
 *  enabled
 *    Boolean stating whether add-on is enabled or disabled by the user.
 *
 *  source
 *    String indicating where an add-on is from. Currently, we only support
 *    the value "amo" which indicates that the add-on came from the official
 *    add-ons repository, addons.mozilla.org. In the future, we may support
 *    installing add-ons from other sources. This provides a future-compatible
 *    mechanism for clients to only apply records they know how to handle.
 */
function AddonRecord(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
AddonRecord.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Record.Addon"
};

Utils.deferGetSet(AddonRecord, "cleartext", ["addonID",
                                             "applicationID",
                                             "enabled",
                                             "source"]);

/**
 * The AddonsEngine handles synchronization of add-ons between clients.
 *
 * The engine maintains an instance of an AddonsReconciler, which is the entity
 * maintaining state for add-ons. It provides the history and tracking APIs
 * that AddonManager doesn't.
 *
 * The engine instance overrides a handful of functions on the base class. The
 * rationale for each is documented by that function.
 */
this.AddonsEngine = function AddonsEngine(service) {
  SyncEngine.call(this, "Addons", service);

  this._reconciler = new AddonsReconciler();
}
AddonsEngine.prototype = {
  __proto__:              SyncEngine.prototype,
  _storeObj:              AddonsStore,
  _trackerObj:            AddonsTracker,
  _recordObj:             AddonRecord,
  version:                1,

  syncPriority:           5,

  _reconciler:            null,

  async initialize() {
    await SyncEngine.prototype.initialize.call(this);
    await this._reconciler.ensureStateLoaded();
  },

  /**
   * Override parent method to find add-ons by their public ID, not Sync GUID.
   */
  async _findDupe(item) {
    let id = item.addonID;

    // The reconciler should have been updated at the top of the sync, so we
    // can assume it is up to date when this function is called.
    let addons = this._reconciler.addons;
    if (!(id in addons)) {
      return null;
    }

    let addon = addons[id];
    if (addon.guid != item.id) {
      return addon.guid;
    }

    return null;
  },

  /**
   * Override getChangedIDs to pull in tracker changes plus changes from the
   * reconciler log.
   */
  async getChangedIDs() {
    let changes = {};
    for (let [id, modified] of Object.entries(this._tracker.changedIDs)) {
      changes[id] = modified;
    }

    let lastSyncDate = new Date(this.lastSync * 1000);

    // The reconciler should have been refreshed at the beginning of a sync and
    // we assume this function is only called from within a sync.
    let reconcilerChanges = this._reconciler.getChangesSinceDate(lastSyncDate);
    let addons = this._reconciler.addons;
    for (let change of reconcilerChanges) {
      let changeTime = change[0];
      let id = change[2];

      if (!(id in addons)) {
        continue;
      }

      // Keep newest modified time.
      if (id in changes && changeTime < changes[id]) {
          continue;
      }

      if (!this.isAddonSyncable(addons[id])) {
        continue;
      }

      this._log.debug("Adding changed add-on from changes log: " + id);
      let addon = addons[id];
      changes[addon.guid] = changeTime.getTime() / 1000;
    }

    return changes;
  },

  /**
   * Override start of sync function to refresh reconciler.
   *
   * Many functions in this class assume the reconciler is refreshed at the
   * top of a sync. If this ever changes, those functions should be revisited.
   *
   * Technically speaking, we don't need to refresh the reconciler on every
   * sync since it is installed as an AddonManager listener. However, add-ons
   * are complicated and we force a full refresh, just in case the listeners
   * missed something.
   */
  async _syncStartup() {
    // We refresh state before calling parent because syncStartup in the parent
    // looks for changed IDs, which is dependent on add-on state being up to
    // date.
    await this._refreshReconcilerState();
    return SyncEngine.prototype._syncStartup.call(this);
  },

  /**
   * Override end of sync to perform a little housekeeping on the reconciler.
   *
   * We prune changes to prevent the reconciler state from growing without
   * bound. Even if it grows unbounded, there would have to be many add-on
   * changes (thousands) for it to slow things down significantly. This is
   * highly unlikely to occur. Still, we exercise defense just in case.
   */
  async _syncCleanup() {
    let ms = 1000 * this.lastSync - PRUNE_ADDON_CHANGES_THRESHOLD;
    this._reconciler.pruneChangesBeforeDate(new Date(ms));
    return SyncEngine.prototype._syncCleanup.call(this);
  },

  /**
   * Helper function to ensure reconciler is up to date.
   *
   * This will load the reconciler's state from the file
   * system (if needed) and refresh the state of the reconciler.
   */
  async _refreshReconcilerState() {
    this._log.debug("Refreshing reconciler state");
    return this._reconciler.refreshGlobalState();
  },

  isAddonSyncable(addon, ignoreRepoCheck) {
    return this._store.isAddonSyncable(addon, ignoreRepoCheck);
  }
};

/**
 * This is the primary interface between Sync and the Addons Manager.
 *
 * In addition to the core store APIs, we provide convenience functions to wrap
 * Add-on Manager APIs with Sync-specific semantics.
 */
function AddonsStore(name, engine) {
  Store.call(this, name, engine);
}
AddonsStore.prototype = {
  __proto__: Store.prototype,

  // Define the add-on types (.type) that we support.
  _syncableTypes: ["extension", "theme"],

  _extensionsPrefs: new Preferences("extensions."),

  get reconciler() {
    return this.engine._reconciler;
  },

  /**
   * Override applyIncoming to filter out records we can't handle.
   */
  async applyIncoming(record) {
    // The fields we look at aren't present when the record is deleted.
    if (!record.deleted) {
      // Ignore records not belonging to our application ID because that is the
      // current policy.
      if (record.applicationID != Services.appinfo.ID) {
        this._log.info("Ignoring incoming record from other App ID: " +
                        record.id);
        return;
      }

      // Ignore records that aren't from the official add-on repository, as that
      // is our current policy.
      if (record.source != "amo") {
        this._log.info("Ignoring unknown add-on source (" + record.source + ")" +
                       " for " + record.id);
        return;
      }
    }

    // Ignore incoming records for which an existing non-syncable addon
    // exists.
    let existingMeta = this.reconciler.addons[record.addonID];
    if (existingMeta && !this.isAddonSyncable(existingMeta)) {
      this._log.info("Ignoring incoming record for an existing but non-syncable addon", record.addonID);
      return;
    }

    await Store.prototype.applyIncoming.call(this, record);
  },


  /**
   * Provides core Store API to create/install an add-on from a record.
   */
  async create(record) {
    let cb = Async.makeSpinningCallback();
    AddonUtils.installAddons([{
      id:               record.addonID,
      syncGUID:         record.id,
      enabled:          record.enabled,
      requireSecureURI: this._extensionsPrefs.get("install.requireSecureOrigin", true),
    }], cb);

    // This will throw if there was an error. This will get caught by the sync
    // engine and the record will try to be applied later.
    let results = cb.wait();

    if (results.skipped.includes(record.addonID)) {
      this._log.info("Add-on skipped: " + record.addonID);
      // Just early-return for skipped addons - we don't want to arrange to
      // try again next time because the condition that caused up to skip
      // will remain true for this addon forever.
      return;
    }

    let addon;
    for (let a of results.addons) {
      if (a.id == record.addonID) {
        addon = a;
        break;
      }
    }

    // This should never happen, but is present as a fail-safe.
    if (!addon) {
      throw new Error("Add-on not found after install: " + record.addonID);
    }

    this._log.info("Add-on installed: " + record.addonID);
  },

  /**
   * Provides core Store API to remove/uninstall an add-on from a record.
   */
  async remove(record) {
    // If this is called, the payload is empty, so we have to find by GUID.
    let addon = await this.getAddonByGUID(record.id);
    if (!addon) {
      // We don't throw because if the add-on could not be found then we assume
      // it has already been uninstalled and there is nothing for this function
      // to do.
      return;
    }

    this._log.info("Uninstalling add-on: " + addon.id);
    await AddonUtils.uninstallAddon(addon);
  },

  /**
   * Provides core Store API to update an add-on from a record.
   */
  async update(record) {
    let addon = await this.getAddonByID(record.addonID);

    // update() is called if !this.itemExists. And, since itemExists consults
    // the reconciler only, we need to take care of some corner cases.
    //
    // First, the reconciler could know about an add-on that was uninstalled
    // and no longer present in the add-ons manager.
    if (!addon) {
      this.create(record);
      return;
    }

    // It's also possible that the add-on is non-restartless and has pending
    // install/uninstall activity.
    //
    // We wouldn't get here if the incoming record was for a deletion. So,
    // check for pending uninstall and cancel if necessary.
    if (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
      addon.cancelUninstall();

      // We continue with processing because there could be state or ID change.
    }

    this.updateUserDisabled(addon, !record.enabled);
  },

  /**
   * Provide core Store API to determine if a record exists.
   */
  async itemExists(guid) {
    let addon = this.reconciler.getAddonStateFromSyncGUID(guid);

    return !!addon;
  },

  /**
   * Create an add-on record from its GUID.
   *
   * @param guid
   *        Add-on GUID (from extensions DB)
   * @param collection
   *        Collection to add record to.
   *
   * @return AddonRecord instance
   */
  async createRecord(guid, collection) {
    let record = new AddonRecord(collection, guid);
    record.applicationID = Services.appinfo.ID;

    let addon = this.reconciler.getAddonStateFromSyncGUID(guid);

    // If we don't know about this GUID or if it has been uninstalled, we mark
    // the record as deleted.
    if (!addon || !addon.installed) {
      record.deleted = true;
      return record;
    }

    record.modified = addon.modified.getTime() / 1000;

    record.addonID = addon.id;
    record.enabled = addon.enabled;

    // This needs to be dynamic when add-ons don't come from AddonRepository.
    record.source = "amo";

    return record;
  },

  /**
   * Changes the id of an add-on.
   *
   * This implements a core API of the store.
   */
  async changeItemID(oldID, newID) {
    // We always update the GUID in the reconciler because it will be
    // referenced later in the sync process.
    let state = this.reconciler.getAddonStateFromSyncGUID(oldID);
    if (state) {
      state.guid = newID;
      await this.reconciler.saveState();
    }

    let addon = await this.getAddonByGUID(oldID);
    if (!addon) {
      this._log.debug("Cannot change item ID (" + oldID + ") in Add-on " +
                      "Manager because old add-on not present: " + oldID);
      return;
    }

    addon.syncGUID = newID;
  },

  /**
   * Obtain the set of all syncable add-on Sync GUIDs.
   *
   * This implements a core Store API.
   */
  async getAllIDs() {
    let ids = {};

    let addons = this.reconciler.addons;
    for (let id in addons) {
      let addon = addons[id];
      if (this.isAddonSyncable(addon)) {
        ids[addon.guid] = true;
      }
    }

    return ids;
  },

  /**
   * Wipe engine data.
   *
   * This uninstalls all syncable addons from the application. In case of
   * error, it logs the error and keeps trying with other add-ons.
   */
  async wipe() {
    this._log.info("Processing wipe.");

    await this.engine._refreshReconcilerState();

    // We only wipe syncable add-ons. Wipe is a Sync feature not a security
    // feature.
    let ids = await this.getAllIDs();
    for (let guid in ids) {
      let addon = await this.getAddonByGUID(guid);
      if (!addon) {
        this._log.debug("Ignoring add-on because it couldn't be obtained: " +
                        guid);
        continue;
      }

      this._log.info("Uninstalling add-on as part of wipe: " + addon.id);
      await Utils.catch.call(this, () => addon.uninstall())();
    }
  },

  /** *************************************************************************
   * Functions below are unique to this store and not part of the Store API  *
   ***************************************************************************/

  /**
   * Obtain an add-on from its public ID.
   *
   * @param id
   *        Add-on ID
   * @return Addon or undefined if not found
   */
  async getAddonByID(id) {
    return AddonManager.getAddonByID(id);
  },

  /**
   * Obtain an add-on from its Sync GUID.
   *
   * @param  guid
   *         Add-on Sync GUID
   * @return DBAddonInternal or null
   */
  async getAddonByGUID(guid) {
    return AddonManager.getAddonBySyncGUID(guid);
  },

  /**
   * Determines whether an add-on is suitable for Sync.
   *
   * @param  addon
   *         Addon instance
   * @param ignoreRepoCheck
   *         Should we skip checking the Addons repository (primarially useful
   *         for testing and validation).
   * @return Boolean indicating whether it is appropriate for Sync
   */
  isAddonSyncable: function isAddonSyncable(addon, ignoreRepoCheck = false) {
    // Currently, we limit syncable add-ons to those that are:
    //   1) In a well-defined set of types
    //   2) Installed in the current profile
    //   3) Not installed by a foreign entity (i.e. installed by the app)
    //      since they act like global extensions.
    //   4) Is not a hotfix.
    //   5) The addons XPIProvider doesn't veto it (i.e not being installed in
    //      the profile directory, or any other reasons it says the addon can't
    //      be synced)
    //   6) Are installed from AMO

    // We could represent the test as a complex boolean expression. We go the
    // verbose route so the failure reason is logged.
    if (!addon) {
      this._log.debug("Null object passed to isAddonSyncable.");
      return false;
    }

    if (this._syncableTypes.indexOf(addon.type) == -1) {
      this._log.debug(addon.id + " not syncable: type not in whitelist: " +
                      addon.type);
      return false;
    }

    if (!(addon.scope & AddonManager.SCOPE_PROFILE)) {
      this._log.debug(addon.id + " not syncable: not installed in profile.");
      return false;
    }

    // If the addon manager says it's not syncable, we skip it.
    if (!addon.isSyncable) {
      this._log.debug(addon.id + " not syncable: vetoed by the addon manager.");
      return false;
    }

    // This may be too aggressive. If an add-on is downloaded from AMO and
    // manually placed in the profile directory, foreignInstall will be set.
    // Arguably, that add-on should be syncable.
    // TODO Address the edge case and come up with more robust heuristics.
    if (addon.foreignInstall) {
      this._log.debug(addon.id + " not syncable: is foreign install.");
      return false;
    }

    // Ignore hotfix extensions (bug 741670). The pref may not be defined.
    // XXX - note that addon.isSyncable will be false for hotfix addons, so
    // this check isn't strictly necessary - except for Sync tests which aren't
    // setup to create a "real" hotfix addon. This can be removed once those
    // tests are fixed (but keeping it doesn't hurt either)
    if (this._extensionsPrefs.get("hotfix.id", null) == addon.id) {
      this._log.debug(addon.id + " not syncable: is a hotfix.");
      return false;
    }

    // If the AddonRepository's cache isn't enabled (which it typically isn't
    // in tests), getCachedAddonByID always returns null - so skip the check
    // in that case. We also provide a way to specifically opt-out of the check
    // even if the cache is enabled, which is used by the validators.
    if (ignoreRepoCheck || !AddonRepository.cacheEnabled) {
      return true;
    }

    let cb = Async.makeSyncCallback();
    AddonRepository.getCachedAddonByID(addon.id, cb);
    let result = Async.waitForSyncCallback(cb);

    if (!result) {
      this._log.debug(addon.id + " not syncable: add-on not found in add-on " +
                      "repository.");
      return false;
    }

    return this.isSourceURITrusted(result.sourceURI);
  },

  /**
   * Determine whether an add-on's sourceURI field is trusted and the add-on
   * can be installed.
   *
   * This function should only ever be called from isAddonSyncable(). It is
   * exposed as a separate function to make testing easier.
   *
   * @param  uri
   *         nsIURI instance to validate
   * @return bool
   */
  isSourceURITrusted: function isSourceURITrusted(uri) {
    // For security reasons, we currently limit synced add-ons to those
    // installed from trusted hostname(s). We additionally require TLS with
    // the add-ons site to help prevent forgeries.
    let trustedHostnames = Svc.Prefs.get("addons.trustedSourceHostnames", "")
                           .split(",");

    if (!uri) {
      this._log.debug("Undefined argument to isSourceURITrusted().");
      return false;
    }

    // Scheme is validated before the hostname because uri.host may not be
    // populated for certain schemes. It appears to always be populated for
    // https, so we avoid the potential NS_ERROR_FAILURE on field access.
    if (uri.scheme != "https") {
      this._log.debug("Source URI not HTTPS: " + uri.spec);
      return false;
    }

    if (trustedHostnames.indexOf(uri.host) == -1) {
      this._log.debug("Source hostname not trusted: " + uri.host);
      return false;
    }

    return true;
  },

  /**
   * Update the userDisabled flag on an add-on.
   *
   * This will enable or disable an add-on. It has no return value and does
   * not catch or handle exceptions thrown by the addon manager. If no action
   * is needed it will return immediately.
   *
   * @param addon
   *        Addon instance to manipulate.
   * @param value
   *        Boolean to which to set userDisabled on the passed Addon.
   */
  updateUserDisabled(addon, value) {
    if (addon.userDisabled == value) {
      return;
    }

    // A pref allows changes to the enabled flag to be ignored.
    if (Svc.Prefs.get("addons.ignoreUserEnabledChanges", false)) {
      this._log.info("Ignoring enabled state change due to preference: " +
                     addon.id);
      return;
    }

    AddonUtils.updateUserDisabled(addon, value);
    // updating this flag doesn't send a notification for appDisabled addons,
    // meaning the reconciler will not update its state and may resync the
    // addon - so explicitly rectify the state (bug 1366994)
    if (addon.appDisabled) {
      this.reconciler.rectifyStateFromAddon(addon);
    }
  },
};

/**
 * The add-ons tracker keeps track of real-time changes to add-ons.
 *
 * It hooks up to the reconciler and receives notifications directly from it.
 */
function AddonsTracker(name, engine) {
  Tracker.call(this, name, engine);
}
AddonsTracker.prototype = {
  __proto__: Tracker.prototype,

  get reconciler() {
    return this.engine._reconciler;
  },

  get store() {
    return this.engine._store;
  },

  /**
   * This callback is executed whenever the AddonsReconciler sends out a change
   * notification. See AddonsReconciler.addChangeListener().
   */
  changeListener: function changeHandler(date, change, addon) {
    this._log.debug("changeListener invoked: " + change + " " + addon.id);
    // Ignore changes that occur during sync.
    if (this.ignoreAll) {
      return;
    }

    if (!this.store.isAddonSyncable(addon)) {
      this._log.debug("Ignoring change because add-on isn't syncable: " +
                      addon.id);
      return;
    }

    if (this.addChangedID(addon.guid, date.getTime() / 1000)) {
      this.score += SCORE_INCREMENT_XLARGE;
    }
  },

  startTracking() {
    if (this.engine.enabled) {
      this.reconciler.startListening();
    }

    this.reconciler.addChangeListener(this);
  },

  stopTracking() {
    this.reconciler.removeChangeListener(this);
    this.reconciler.stopListening();
  },
};

class AddonValidator extends CollectionValidator {
  constructor(engine = null) {
    super("addons", "id", [
      "addonID",
      "enabled",
      "applicationID",
      "source"
    ]);
    this.engine = engine;
  }

  async getClientItems() {
    const installed = await AddonManager.getAllAddons();
    const addonsWithPendingOperation = await AddonManager.getAddonsWithOperationsByTypes(["extension", "theme"]);
    // Addons pending install won't be in the first list, but addons pending
    // uninstall/enable/disable will be in both lists.
    let all = new Map(installed.map(addon => [addon.id, addon]));
    for (let addon of addonsWithPendingOperation) {
      all.set(addon.id, addon);
    }
    // Convert to an array since Map.prototype.values returns an iterable
    return [...all.values()];
  }

  normalizeClientItem(item) {
    let enabled = !item.userDisabled;
    if (item.pendingOperations & AddonManager.PENDING_ENABLE) {
      enabled = true;
    } else if (item.pendingOperations & AddonManager.PENDING_DISABLE) {
      enabled = false;
    }
    return {
      enabled,
      id: item.syncGUID,
      addonID: item.id,
      applicationID: Services.appinfo.ID,
      source: "amo", // check item.foreignInstall?
      original: item
    };
  }

  async normalizeServerItem(item) {
    let guid = await this.engine._findDupe(item);
    if (guid) {
      item.id = guid;
    }
    return item;
  }

  clientUnderstands(item) {
    return item.applicationID === Services.appinfo.ID;
  }

  syncedByClient(item) {
    return !item.original.hidden &&
           !item.original.isSystem &&
           !(item.original.pendingOperations & AddonManager.PENDING_UNINSTALL) &&
           this.engine.isAddonSyncable(item.original, true);
  }
}
PK
!<q*modules/services-sync/engines/bookmarks.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["BookmarksEngine", "PlacesItem", "Bookmark",
                         "BookmarkFolder", "BookmarkQuery",
                         "Livemark", "BookmarkSeparator"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");

XPCOMUtils.defineLazyModuleGetter(this, "BookmarkValidator",
                                  "resource://services-sync/bookmark_validator.js");
XPCOMUtils.defineLazyGetter(this, "PlacesBundle", () => {
  let bundleService = Cc["@mozilla.org/intl/stringbundle;1"]
                        .getService(Ci.nsIStringBundleService);
  return bundleService.createBundle("chrome://places/locale/places.properties");
});
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
                                  "resource://gre/modules/PlacesBackups.jsm");

const ANNOS_TO_TRACK = [PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO,
                        PlacesSyncUtils.bookmarks.SIDEBAR_ANNO,
                        PlacesUtils.LMANNO_FEEDURI, PlacesUtils.LMANNO_SITEURI];

const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
const FOLDER_SORTINDEX = 1000000;
const {
  SOURCE_SYNC,
  SOURCE_IMPORT,
  SOURCE_IMPORT_REPLACE,
  SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
} = Ci.nsINavBookmarksService;

const ORGANIZERQUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
const ALLBOOKMARKS_ANNO = "AllBookmarks";
const MOBILE_ANNO = "MobileBookmarks";

// Roots that should be deleted from the server, instead of applied locally.
// This matches `AndroidBrowserBookmarksRepositorySession::forbiddenGUID`,
// but allows tags because we don't want to reparent tag folders or tag items
// to "unfiled".
const FORBIDDEN_INCOMING_IDS = ["pinned", "places", "readinglist"];

// Items with these parents should be deleted from the server. We allow
// children of the Places root, to avoid orphaning left pane queries and other
// descendants of custom roots.
const FORBIDDEN_INCOMING_PARENT_IDS = ["pinned", "readinglist"];

// The tracker ignores changes made by bookmark import and restore, and
// changes made by Sync. We don't need to exclude `SOURCE_IMPORT`, but both
// import and restore fire `bookmarks-restore-*` observer notifications, and
// the tracker doesn't currently distinguish between the two.
const IGNORED_SOURCES = [SOURCE_SYNC, SOURCE_IMPORT, SOURCE_IMPORT_REPLACE,
                         SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN];

function isSyncedRootNode(node) {
  return node.root == "bookmarksMenuFolder" ||
         node.root == "unfiledBookmarksFolder" ||
         node.root == "toolbarFolder" ||
         node.root == "mobileFolder";
}

// Returns the constructor for a bookmark record type.
function getTypeObject(type) {
  switch (type) {
    case "bookmark":
      return Bookmark;
    case "query":
      return BookmarkQuery;
    case "folder":
      return BookmarkFolder;
    case "livemark":
      return Livemark;
    case "separator":
      return BookmarkSeparator;
    case "item":
      return PlacesItem;
  }
  return null;
}

this.PlacesItem = function PlacesItem(collection, id, type) {
  CryptoWrapper.call(this, collection, id);
  this.type = type || "item";
}
PlacesItem.prototype = {
  decrypt: function PlacesItem_decrypt(keyBundle) {
    // Do the normal CryptoWrapper decrypt, but change types before returning
    let clear = CryptoWrapper.prototype.decrypt.call(this, keyBundle);

    // Convert the abstract places item to the actual object type
    if (!this.deleted)
      this.__proto__ = this.getTypeObject(this.type).prototype;

    return clear;
  },

  getTypeObject: function PlacesItem_getTypeObject(type) {
    let recordObj = getTypeObject(type);
    if (!recordObj) {
      throw new Error("Unknown places item object type: " + type);
    }
    return recordObj;
  },

  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.PlacesItem",

  // Converts the record to a Sync bookmark object that can be passed to
  // `PlacesSyncUtils.bookmarks.{insert, update}`.
  toSyncBookmark() {
    let result = {
      kind: this.type,
      syncId: this.id,
      parentSyncId: this.parentid,
    };
    let dateAdded = PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
      this.dateAdded, +this.modified * 1000);
    if (dateAdded !== undefined) {
      result.dateAdded = dateAdded;
    }
    return result;
  },

  // Populates the record from a Sync bookmark object returned from
  // `PlacesSyncUtils.bookmarks.fetch`.
  fromSyncBookmark(item) {
    this.parentid = item.parentSyncId;
    this.parentName = item.parentTitle;
    if (item.dateAdded) {
      this.dateAdded = item.dateAdded;
    }
  },
};

Utils.deferGetSet(PlacesItem,
                  "cleartext",
                  ["hasDupe", "parentid", "parentName", "type", "dateAdded"]);

this.Bookmark = function Bookmark(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "bookmark");
}
Bookmark.prototype = {
  __proto__: PlacesItem.prototype,
  _logName: "Sync.Record.Bookmark",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.title = this.title;
    info.url = this.bmkUri;
    info.description = this.description;
    info.loadInSidebar = this.loadInSidebar;
    info.tags = this.tags;
    info.keyword = this.keyword;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.bmkUri = item.url.href;
    this.description = item.description;
    this.loadInSidebar = item.loadInSidebar;
    this.tags = item.tags;
    this.keyword = item.keyword;
  },
};

Utils.deferGetSet(Bookmark,
                  "cleartext",
                  ["title", "bmkUri", "description",
                   "loadInSidebar", "tags", "keyword"]);

this.BookmarkQuery = function BookmarkQuery(collection, id) {
  Bookmark.call(this, collection, id, "query");
}
BookmarkQuery.prototype = {
  __proto__: Bookmark.prototype,
  _logName: "Sync.Record.BookmarkQuery",

  toSyncBookmark() {
    let info = Bookmark.prototype.toSyncBookmark.call(this);
    info.folder = this.folderName;
    info.query = this.queryId;
    return info;
  },

  fromSyncBookmark(item) {
    Bookmark.prototype.fromSyncBookmark.call(this, item);
    this.folderName = item.folder;
    this.queryId = item.query;
  },
};

Utils.deferGetSet(BookmarkQuery,
                  "cleartext",
                  ["folderName", "queryId"]);

this.BookmarkFolder = function BookmarkFolder(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "folder");
}
BookmarkFolder.prototype = {
  __proto__: PlacesItem.prototype,
  _logName: "Sync.Record.Folder",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.description = this.description;
    info.title = this.title;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.description = item.description;
    this.children = item.childSyncIds;
  },
};

Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title",
                                                "children"]);

this.Livemark = function Livemark(collection, id) {
  BookmarkFolder.call(this, collection, id, "livemark");
}
Livemark.prototype = {
  __proto__: BookmarkFolder.prototype,
  _logName: "Sync.Record.Livemark",

  toSyncBookmark() {
    let info = BookmarkFolder.prototype.toSyncBookmark.call(this);
    info.feed = this.feedUri;
    info.site = this.siteUri;
    return info;
  },

  fromSyncBookmark(item) {
    BookmarkFolder.prototype.fromSyncBookmark.call(this, item);
    this.feedUri = item.feed.href;
    if (item.site) {
      this.siteUri = item.site.href;
    }
  },
};

Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);

this.BookmarkSeparator = function BookmarkSeparator(collection, id) {
  PlacesItem.call(this, collection, id, "separator");
}
BookmarkSeparator.prototype = {
  __proto__: PlacesItem.prototype,
  _logName: "Sync.Record.Separator",

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.pos = item.index;
  },
};

Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");

this.BookmarksEngine = function BookmarksEngine(service) {
  SyncEngine.call(this, "Bookmarks", service);
}
BookmarksEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _recordObj: PlacesItem,
  _storeObj: BookmarksStore,
  _trackerObj: BookmarksTracker,
  version: 2,
  _defaultSort: "index",

  syncPriority: 4,
  allowSkippedRecord: false,

  emptyChangeset() {
    return new BookmarksChangeset();
  },

  async _buildGUIDMap() {
    let guidMap = {};
    let tree = await PlacesUtils.promiseBookmarksTree("");

    function* walkBookmarksTree(tree, parent = null) {
      if (tree) {
        // Skip root node
        if (parent) {
          yield [tree, parent];
        }
        if (tree.children) {
          for (let child of tree.children) {
            yield* walkBookmarksTree(child, tree);
          }
        }
      }
    }

    function* walkBookmarksRoots(tree) {
      for (let child of tree.children) {
        if (isSyncedRootNode(child)) {
          yield* walkBookmarksTree(child, tree);
        }
      }
    }

    let maybeYield = Async.jankYielder();
    for (let [node, parent] of walkBookmarksRoots(tree)) {
      await maybeYield();
      let {guid, type: placeType} = node;
      guid = PlacesSyncUtils.bookmarks.guidToSyncId(guid);
      let key;
      switch (placeType) {
        case PlacesUtils.TYPE_X_MOZ_PLACE:
          // Bookmark
          let query = null;
          if (node.annos && node.uri.startsWith("place:")) {
            query = node.annos.find(({name}) =>
              name === PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
          }
          if (query && query.value) {
            key = "q" + query.value;
          } else {
            key = "b" + node.uri + ":" + (node.title || "");
          }
          break;
        case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
          // Folder
          key = "f" + (node.title || "");
          break;
        case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
          // Separator
          key = "s" + node.index;
          break;
        default:
          this._log.error("Unknown place type: '" + placeType + "'");
          continue;
      }

      let parentName = parent.title || "";
      if (guidMap[parentName] == null)
        guidMap[parentName] = {};

      // If the entry already exists, remember that there are explicit dupes.
      let entry = new String(guid);
      entry.hasDupe = guidMap[parentName][key] != null;

      // Remember this item's GUID for its parent-name/key pair.
      guidMap[parentName][key] = entry;
      this._log.trace("Mapped: " + [parentName, key, entry, entry.hasDupe]);
    }

    return guidMap;
  },

  // Helper function to get a dupe GUID for an item.
  async _mapDupe(item) {
    // Figure out if we have something to key with.
    let key;
    let altKey;
    switch (item.type) {
      case "query":
        // Prior to Bug 610501, records didn't carry their Smart Bookmark
        // anno, so we won't be able to dupe them correctly. This altKey
        // hack should get them to dupe correctly.
        if (item.queryId) {
          key = "q" + item.queryId;
          altKey = "b" + item.bmkUri + ":" + (item.title || "");
          break;
        }
        // No queryID? Fall through to the regular bookmark case.
      case "bookmark":
        key = "b" + item.bmkUri + ":" + (item.title || "");
        break;
      case "folder":
      case "livemark":
        key = "f" + (item.title || "");
        break;
      case "separator":
        key = "s" + item.pos;
        break;
      default:
        return undefined;
    }

    // Figure out if we have a map to use!
    // This will throw in some circumstances. That's fine.
    let guidMap = await this.getGuidMap();

    // Give the GUID if we have the matching pair.
    let parentName = item.parentName || "";
    this._log.trace("Finding mapping: " + parentName + ", " + key);
    let parent = guidMap[parentName];

    if (!parent) {
      this._log.trace("No parent => no dupe.");
      return undefined;
    }

    let dupe = parent[key];

    if (dupe) {
      this._log.trace("Mapped dupe: " + dupe);
      return dupe;
    }

    if (altKey) {
      dupe = parent[altKey];
      if (dupe) {
        this._log.trace("Mapped dupe using altKey " + altKey + ": " + dupe);
        return dupe;
      }
    }

    this._log.trace("No dupe found for key " + key + "/" + altKey + ".");
    return undefined;
  },

  async _syncStartup() {
    await SyncEngine.prototype._syncStartup.call(this);

    try {
      // For first-syncs, make a backup for the user to restore
      if (this.lastSync == 0) {
        this._log.debug("Bookmarks backup starting.");
        await PlacesBackups.create(null, true);
        this._log.debug("Bookmarks backup done.");
      }
    } catch (ex) {
      // Failure to create a backup is somewhat bad, but probably not bad
      // enough to prevent syncing of bookmarks - so just log the error and
      // continue.
      this._log.warn("Error while backing up bookmarks, but continuing with sync", ex);
    }

    this._store._childrenToOrder = {};
    this._store.clearPendingDeletions();
  },

  async getGuidMap() {
    if (this._guidMap) {
      return this._guidMap;
    }
    try {
      return this._guidMap = await this._buildGUIDMap();
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.warn("Error while building GUID map, skipping all other incoming items", ex);
      // eslint-disable-next-line no-throw-literal
      throw {code: Engine.prototype.eEngineAbortApplyIncoming,
             cause: ex};
    }
  },

  async _deletePending() {
    // Delete pending items -- See the comment above BookmarkStore's deletePending
    let newlyModified = await this._store.deletePending();
    if (newlyModified) {
      this._log.debug("Deleted pending items", newlyModified);
      this._modified.insert(newlyModified);
    }
  },

  async _shouldReviveRemotelyDeletedRecord(item) {
    let modifiedTimestamp = this._modified.getModifiedTimestamp(item.id);
    if (!modifiedTimestamp) {
      // We only expect this to be called with items locally modified, so
      // something strange is going on - play it safe and don't revive it.
      this._log.error("_shouldReviveRemotelyDeletedRecord called on unmodified item: " + item.id);
      return false;
    }

    // In addition to preventing the deletion of this record (handled by the caller),
    // we use `touch` to mark the parent of this record for uploading next sync, in order
    // to ensure its children array is accurate. If `touch` returns new change records,
    // we revive the item and insert the changes into the current changeset.
    let newChanges = await PlacesSyncUtils.bookmarks.touch(item.id);
    if (newChanges) {
      this._modified.insert(newChanges);
      return true;
    }
    return false;
  },

  async _processIncoming(newitems) {
    try {
      await SyncEngine.prototype._processIncoming.call(this, newitems);
    } finally {
      await this._postProcessIncoming();
    }
  },

  // Applies pending tombstones, sets folder child order, and updates the sync
  // status of all `NEW` bookmarks to `NORMAL`.
  async _postProcessIncoming() {
    await this._deletePending();
    await this._orderChildren();
    let changes = this._modified.changes;
    await PlacesSyncUtils.bookmarks.markChangesAsSyncing(changes);
  },

  async _orderChildren() {
    await this._store._orderChildren();
    this._store._childrenToOrder = {};
  },

  async _syncFinish() {
    await SyncEngine.prototype._syncFinish.call(this);
    await PlacesSyncUtils.bookmarks.ensureMobileQuery();
  },

  async _syncCleanup() {
    await SyncEngine.prototype._syncCleanup.call(this);
    delete this._guidMap;
  },

  async _createRecord(id) {
    if (this._modified.isTombstone(id)) {
      // If we already know a changed item is a tombstone, just create the
      // record without dipping into Places.
      return this._createTombstone(id);
    }
    // Create the record as usual, but mark it as having dupes if necessary.
    let record = await SyncEngine.prototype._createRecord.call(this, id);
    let entry = await this._mapDupe(record);
    if (entry != null && entry.hasDupe) {
      record.hasDupe = true;
    }
    if (record.deleted) {
      // Make sure deleted items are marked as tombstones. We do this here
      // in addition to the `isTombstone` call above because it's possible
      // a changed bookmark might be deleted during a sync (bug 1313967).
      this._modified.setTombstone(record.id);
    }
    return record;
  },

  async _findDupe(item) {
    this._log.trace("Finding dupe for " + item.id +
                    " (already duped: " + item.hasDupe + ").");

    // Don't bother finding a dupe if the incoming item has duplicates.
    if (item.hasDupe) {
      this._log.trace(item.id + " already a dupe: not finding one.");
      return null;
    }
    let mapped = await this._mapDupe(item);
    this._log.debug(item.id + " mapped to " + mapped);
    // We must return a string, not an object, and the entries in the GUIDMap
    // are created via "new String()" making them an object.
    return mapped ? mapped.toString() : mapped;
  },

  async pullAllChanges() {
    return this.pullNewChanges();
  },

  async pullNewChanges() {
    return this._tracker.promiseChangedIDs();
  },

  async trackRemainingChanges() {
    let changes = this._modified.changes;
    await PlacesSyncUtils.bookmarks.pushChanges(changes);
  },

  _deleteId(id) {
    this._noteDeletedId(id);
  },

  async _resetClient() {
    await SyncEngine.prototype._resetClient.call(this);
    await PlacesSyncUtils.bookmarks.reset();
  },

  // Called when _findDupe returns a dupe item and the engine has decided to
  // switch the existing item to the new incoming item.
  async _switchItemToDupe(localDupeGUID, incomingItem) {
    let newChanges = await PlacesSyncUtils.bookmarks.dedupe(localDupeGUID,
                                                            incomingItem.id,
                                                            incomingItem.parentid);
    this._modified.insert(newChanges);
  },

  // Cleans up the Places root, reading list items (ignored in bug 762118,
  // removed in bug 1155684), and pinned sites.
  _shouldDeleteRemotely(incomingItem) {
    return FORBIDDEN_INCOMING_IDS.includes(incomingItem.id) ||
           FORBIDDEN_INCOMING_PARENT_IDS.includes(incomingItem.parentid);
  },

  beforeRecordDiscard(localRecord, remoteRecord, remoteIsNewer) {
    if (localRecord.type != "folder" || remoteRecord.type != "folder") {
      return;
    }
    // Resolve child order conflicts by taking the chronologically newer list,
    // then appending any missing items from the older list. This preserves the
    // order of those missing items relative to each other, but not relative to
    // the items that appear in the newer list.
    let newRecord = remoteIsNewer ? remoteRecord : localRecord;
    let newChildren = new Set(newRecord.children);

    let oldChildren = (remoteIsNewer ? localRecord : remoteRecord).children;
    let missingChildren = oldChildren ? oldChildren.filter(
      child => !newChildren.has(child)) : [];

    // Some of the children in `order` might have been deleted, or moved to
    // other folders. `PlacesSyncUtils.bookmarks.order` ignores them.
    let order = newRecord.children ?
                [...newRecord.children, ...missingChildren] : missingChildren;
    this._log.debug("Recording children of " + localRecord.id, order);
    this._store._childrenToOrder[localRecord.id] = order;
  },

  getValidator() {
    return new BookmarkValidator();
  }
};

function BookmarksStore(name, engine) {
  Store.call(this, name, engine);
  this._itemsToDelete = new Set();
}
BookmarksStore.prototype = {
  __proto__: Store.prototype,

  async itemExists(id) {
    return (await this.idForGUID(id)) > 0;
  },

  async applyIncoming(record) {
    this._log.debug("Applying record " + record.id);
    let isSpecial = PlacesSyncUtils.bookmarks.ROOTS.includes(record.id);

    if (record.deleted) {
      if (isSpecial) {
        this._log.warn("Ignoring deletion for special record " + record.id);
        return;
      }

      // Don't bother with pre and post-processing for deletions.
      await Store.prototype.applyIncoming.call(this, record);
      return;
    }

    // For special folders we're only interested in child ordering.
    if (isSpecial && record.children) {
      this._log.debug("Processing special node: " + record.id);
      // Reorder children later
      this._childrenToOrder[record.id] = record.children;
      return;
    }

    // Skip malformed records. (Bug 806460.)
    if (record.type == "query" &&
        !record.bmkUri) {
      this._log.warn("Skipping malformed query bookmark: " + record.id);
      return;
    }

    // Figure out the local id of the parent GUID if available
    let parentGUID = record.parentid;
    if (!parentGUID) {
      throw new Error(
          `Record ${record.id} has invalid parentid: ${parentGUID}`);
    }
    this._log.debug("Remote parent is " + parentGUID);

    // Do the normal processing of incoming records
    await Store.prototype.applyIncoming.call(this, record);

    if (record.type == "folder" && record.children) {
      this._childrenToOrder[record.id] = record.children;
    }
  },

  async create(record) {
    let info = record.toSyncBookmark();
    // This can throw if we're inserting an invalid or incomplete bookmark.
    // That's fine; the exception will be caught by `applyIncomingBatch`
    // without aborting further processing.
    let item = await PlacesSyncUtils.bookmarks.insert(info);
    if (item) {
      this._log.trace(`Created ${item.kind} ${item.syncId} under ${
        item.parentSyncId}`, item);
      if (item.dateAdded != record.dateAdded) {
        this.engine.addForWeakUpload(item.syncId);
      }
    }
  },

  async remove(record) {
    this._log.trace(`Buffering removal of item "${record.id}".`);
    this._itemsToDelete.add(record.id);
  },

  async update(record) {
    let info = record.toSyncBookmark();
    let item = await PlacesSyncUtils.bookmarks.update(info);
    if (item) {
      this._log.trace(`Updated ${item.kind} ${item.syncId} under ${
        item.parentSyncId}`, item);
      if (item.dateAdded != record.dateAdded) {
        this.engine.addForWeakUpload(item.syncId);
      }
    }
  },

  async _orderChildren() {
    for (let syncID in this._childrenToOrder) {
      let children = this._childrenToOrder[syncID];
      try {
        await PlacesSyncUtils.bookmarks.order(syncID, children);
      } catch (ex) {
        this._log.debug(`Could not order children for ${syncID}`, ex);
      }
    }
  },

  // There's some complexity here around pending deletions. Our goals:
  //
  // - Don't delete any bookmarks a user has created but not explicitly deleted
  //   (This includes any bookmark that was not a child of the folder at the
  //   time the deletion was recorded, and also bookmarks restored from a backup).
  // - Don't undelete any bookmark without ensuring the server structure
  //   includes it (see `BookmarkEngine.prototype._shouldReviveRemotelyDeletedRecord`)
  //
  // This leads the following approach:
  //
  // - Additions, moves, and updates are processed before deletions.
  //     - To do this, all deletion operations are buffered in `this._itemsToDelete`
  //       during a sync.
  //     - The exception to this is the moves that occur to fix the order of bookmark
  //       children, which are performed after we process deletions.
  // - Non-folders are deleted before folder deletions, so that when we process
  //   folder deletions we know the correct state.
  // - Remote deletions always win for folders, but do not result in recursive
  //   deletion of children. This is a hack because we're not able to distinguish
  //   between value changes and structural changes to folders, and we don't even
  //   have the old server record to compare to. See `BookmarkEngine`'s
  //   `_shouldReviveRemotelyDeletedRecord` method.
  // - When a folder is deleted, its remaining children are moved in order to
  //   their closest living ancestor.  If this is interrupted (unlikely, but
  //   possible given that we don't perform this operation in a transaction),
  //   we revive the folder.
  // - Remote deletions can lose for non-folders, but only until we handle
  //   bookmark restores correctly (removing stale state from the server -- this
  //   is to say, if bug 1230011 is fixed, we should never revive bookmarks).
  //
  // See `PlacesSyncUtils.bookmarks.remove` for the implementation.

  async deletePending() {
    let guidsToUpdate = await PlacesSyncUtils.bookmarks.remove([...this._itemsToDelete]);
    this.clearPendingDeletions();
    return guidsToUpdate;
  },

  clearPendingDeletions() {
    this._itemsToDelete.clear();
  },

  // Create a record starting from the weave id (places guid)
  async createRecord(id, collection) {
    let item = await PlacesSyncUtils.bookmarks.fetch(id);
    if (!item) { // deleted item
      let record = new PlacesItem(collection, id);
      record.deleted = true;
      return record;
    }

    let recordObj = getTypeObject(item.kind);
    if (!recordObj) {
      this._log.warn("Unknown item type, cannot serialize: " + item.kind);
      recordObj = PlacesItem;
    }
    let record = new recordObj(collection, id);
    record.fromSyncBookmark(item);

    record.sortindex = await this._calculateIndex(record);

    return record;
  },


  async GUIDForId(id) {
    let guid = await PlacesUtils.promiseItemGuid(id);
    return PlacesSyncUtils.bookmarks.guidToSyncId(guid);
  },

  async idForGUID(guid) {
    // guid might be a String object rather than a string.
    guid = PlacesSyncUtils.bookmarks.syncIdToGuid(guid.toString());

    try {
      return await PlacesUtils.promiseItemId(guid);
    } catch (ex) {
      return -1;
    }
  },

  async _calculateIndex(record) {
    // Ensure folders have a very high sort index so they're not synced last.
    if (record.type == "folder")
      return FOLDER_SORTINDEX;

    // For anything directly under the toolbar, give it a boost of more than an
    // unvisited bookmark
    let index = 0;
    if (record.parentid == "toolbar")
      index += 150;

    // Add in the bookmark's frecency if we have something.
    if (record.bmkUri != null) {
      let frecency = await PlacesSyncUtils.history.fetchURLFrecency(record.bmkUri);
      if (frecency != -1)
        index += frecency;
    }

    return index;
  },

  async wipe() {
    this.clearPendingDeletions();
    // Save a backup before clearing out all bookmarks.
    await PlacesBackups.create(null, true);
    await PlacesSyncUtils.bookmarks.wipe();
  }
};

// The bookmarks tracker is a special flower. Instead of listening for changes
// via observer notifications, it queries Places for the set of items that have
// changed since the last sync. Because it's a "pull-based" tracker, it ignores
// all concepts of "add a changed ID." However, it still registers an observer
// to bump the score, so that changed bookmarks are synced immediately.
function BookmarksTracker(name, engine) {
  this._batchDepth = 0;
  this._batchSawScoreIncrement = false;
  this._migratedOldEntries = false;
  Tracker.call(this, name, engine);
}
BookmarksTracker.prototype = {
  __proto__: Tracker.prototype,

  // `_ignore` checks the change source for each observer notification, so we
  // don't want to let the engine ignore all changes during a sync.
  get ignoreAll() {
    return false;
  },

  // Define an empty setter so that the engine doesn't throw a `TypeError`
  // setting a read-only property.
  set ignoreAll(value) {},

  // We never want to persist changed IDs, as the changes are already stored
  // in Places.
  persistChangedIDs: false,

  startTracking() {
    PlacesUtils.bookmarks.addObserver(this, true);
    Svc.Obs.add("bookmarks-restore-begin", this);
    Svc.Obs.add("bookmarks-restore-success", this);
    Svc.Obs.add("bookmarks-restore-failed", this);
  },

  stopTracking() {
    PlacesUtils.bookmarks.removeObserver(this);
    Svc.Obs.remove("bookmarks-restore-begin", this);
    Svc.Obs.remove("bookmarks-restore-success", this);
    Svc.Obs.remove("bookmarks-restore-failed", this);
  },

  // Ensure we aren't accidentally using the base persistence.
  addChangedID(id, when) {
    throw new Error("Don't add IDs to the bookmarks tracker");
  },

  removeChangedID(id) {
    throw new Error("Don't remove IDs from the bookmarks tracker");
  },

  // This method is called at various times, so we override with a no-op
  // instead of throwing.
  clearChangedIDs() {},

  promiseChangedIDs() {
    return PlacesSyncUtils.bookmarks.pullChanges();
  },

  get changedIDs() {
    throw new Error("Use promiseChangedIDs");
  },

  set changedIDs(obj) {
    throw new Error("Don't set initial changed bookmark IDs");
  },

  // Migrates tracker entries from the old JSON-based tracker to Places. This
  // is called the first time we start tracking changes.
  async _migrateOldEntries() {
    let existingIDs = await Utils.jsonLoad("changes/" + this.file, this);
    if (existingIDs === null) {
      // If the tracker file doesn't exist, we don't need to migrate, even if
      // the engine is enabled. It's possible we're upgrading before the first
      // sync. In the worst case, getting this wrong has the same effect as a
      // restore: we'll reupload everything to the server.
      this._log.debug("migrateOldEntries: Missing bookmarks tracker file; " +
                      "skipping migration");
      return null;
    }

    if (!this._needsMigration()) {
      // We have a tracker file, but bookmark syncing is disabled, or this is
      // the first sync. It's likely the tracker file is stale. Remove it and
      // skip migration.
      this._log.debug("migrateOldEntries: Bookmarks engine disabled or " +
                      "first sync; skipping migration");
      return Utils.jsonRemove("changes/" + this.file, this);
    }

    // At this point, we know the engine is enabled, we have a tracker file
    // (though it may be empty), and we've synced before.
    this._log.debug("migrateOldEntries: Migrating old tracker entries");
    let entries = [];
    for (let syncID in existingIDs) {
      let change = existingIDs[syncID];
      // Allow raw timestamps for backward-compatibility with changed IDs
      // persisted before bug 1274496.
      let timestamp = typeof change == "number" ? change : change.modified;
      entries.push({
        syncId: syncID,
        modified: timestamp * 1000,
      });
    }
    await PlacesSyncUtils.bookmarks.migrateOldTrackerEntries(entries);
    return Utils.jsonRemove("changes/" + this.file, this);
  },

  _needsMigration() {
    return this.engine && this.engineIsEnabled() && this.engine.lastSync > 0;
  },

  observe: function observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);

    switch (topic) {
      case "weave:engine:start-tracking":
        if (!this._migratedOldEntries) {
          this._migratedOldEntries = true;
          Async.promiseSpinningly(this._migrateOldEntries());
        }
        break;
      case "bookmarks-restore-begin":
        this._log.debug("Ignoring changes from importing bookmarks.");
        break;
      case "bookmarks-restore-success":
        this._log.debug("Tracking all items on successful import.");

        this._log.debug("Restore succeeded: wiping server and other clients.");
        Async.promiseSpinningly((async () => {
          await this.engine.service.resetClient([this.name]);
          await this.engine.service.wipeServer([this.name]);
          await this.engine.service.clientsEngine.sendCommand("wipeEngine", [this.name],
                                                              null, { reason: "bookmark-restore" });
        })());
        break;
      case "bookmarks-restore-failed":
        this._log.debug("Tracking all items on failed import.");
        break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsINavBookmarkObserver,
    Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS,
    Ci.nsISupportsWeakReference
  ]),

  /* Every add/remove/change will trigger a sync for MULTI_DEVICE (except in
     a batch operation, where we do it at the end of the batch) */
  _upScore: function BMT__upScore() {
    if (this._batchDepth == 0) {
      this.score += SCORE_INCREMENT_XLARGE;
    } else {
      this._batchSawScoreIncrement = true;
    }
  },

  onItemAdded: function BMT_onItemAdded(itemId, folder, index,
                                        itemType, uri, title, dateAdded,
                                        guid, parentGuid, source) {
    if (IGNORED_SOURCES.includes(source)) {
      return;
    }

    this._log.trace("onItemAdded: " + itemId);
    this._upScore();
  },

  onItemRemoved(itemId, parentId, index, type, uri,
                           guid, parentGuid, source) {
    if (IGNORED_SOURCES.includes(source)) {
      return;
    }

    this._log.trace("onItemRemoved: " + itemId);
    this._upScore();
  },

  // This method is oddly structured, but the idea is to return as quickly as
  // possible -- this handler gets called *every time* a bookmark changes, for
  // *each change*.
  onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value,
                                            lastModified, itemType, parentId,
                                            guid, parentGuid, oldValue,
                                            source) {
    if (IGNORED_SOURCES.includes(source)) {
      return;
    }

    if (isAnno && (ANNOS_TO_TRACK.indexOf(property) == -1))
      // Ignore annotations except for the ones that we sync.
      return;

    // Ignore favicon changes to avoid unnecessary churn.
    if (property == "favicon")
      return;

    this._log.trace("onItemChanged: " + itemId +
                    (", " + property + (isAnno ? " (anno)" : "")) +
                    (value ? (" = \"" + value + "\"") : ""));
    this._upScore();
  },

  onItemMoved: function BMT_onItemMoved(itemId, oldParent, oldIndex,
                                        newParent, newIndex, itemType,
                                        guid, oldParentGuid, newParentGuid,
                                        source) {
    if (IGNORED_SOURCES.includes(source)) {
      return;
    }

    this._log.trace("onItemMoved: " + itemId);
    this._upScore();
  },

  onBeginUpdateBatch() {
    ++this._batchDepth;
  },
  onEndUpdateBatch() {
    if (--this._batchDepth === 0 && this._batchSawScoreIncrement) {
      this.score += SCORE_INCREMENT_XLARGE;
      this._batchSawScoreIncrement = false;
    }
  },
  onItemVisited() {}
};

class BookmarksChangeset extends Changeset {

  getStatus(id) {
    let change = this.changes[id];
    if (!change) {
      return PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN;
    }
    return change.status;
  }

  getModifiedTimestamp(id) {
    let change = this.changes[id];
    if (change) {
      // Pretend the change doesn't exist if we've already synced or
      // reconciled it.
      return change.synced ? Number.NaN : change.modified;
    }
    return Number.NaN;
  }

  has(id) {
    let change = this.changes[id];
    if (change) {
      return !change.synced;
    }
    return false;
  }

  setTombstone(id) {
    let change = this.changes[id];
    if (change) {
      change.tombstone = true;
    }
  }

  delete(id) {
    let change = this.changes[id];
    if (change) {
      // Mark the change as synced without removing it from the set. We do this
      // so that we can update Places in `trackRemainingChanges`.
      change.synced = true;
    }
  }

  ids() {
    let results = new Set();
    for (let id in this.changes) {
      if (!this.changes[id].synced) {
        results.add(id);
      }
    }
    return [...results];
  }

  isTombstone(id) {
    let change = this.changes[id];
    if (change) {
      return change.tombstone;
    }
    return false;
  }
}
PK
!<nv''(modules/services-sync/engines/clients.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * How does the clients engine work?
 *
 * - We use 2 files - commands.json and commands-syncing.json.
 *
 * - At sync upload time, we attempt a rename of commands.json to
 *   commands-syncing.json, and ignore errors (helps for crash during sync!).
 * - We load commands-syncing.json and stash the contents in
 *   _currentlySyncingCommands which lives for the duration of the upload process.
 * - We use _currentlySyncingCommands to build the outgoing records
 * - Immediately after successful upload, we delete commands-syncing.json from
 *   disk (and clear _currentlySyncingCommands). We reconcile our local records
 *   with what we just wrote in the server, and add failed IDs commands
 *   back in commands.json
 * - Any time we need to "save" a command for future syncs, we load
 *   commands.json, update it, and write it back out.
 */

this.EXPORTED_SYMBOLS = [
  "ClientEngine",
  "ClientsRec"
];

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");

XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
  "resource://gre/modules/FxAccounts.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "getRepairRequestor",
  "resource://services-sync/collection_repair.js");

XPCOMUtils.defineLazyModuleGetter(this, "getRepairResponder",
  "resource://services-sync/collection_repair.js");

const CLIENTS_TTL = 1814400; // 21 days
const CLIENTS_TTL_REFRESH = 604800; // 7 days
const STALE_CLIENT_REMOTE_AGE = 604800; // 7 days

const SUPPORTED_PROTOCOL_VERSIONS = [SYNC_API_VERSION];

function hasDupeCommand(commands, action) {
  if (!commands) {
    return false;
  }
  return commands.some(other => other.command == action.command &&
    Utils.deepEquals(other.args, action.args));
}

this.ClientsRec = function ClientsRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
ClientsRec.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.Clients",
  ttl: CLIENTS_TTL
};

Utils.deferGetSet(ClientsRec,
                  "cleartext",
                  ["name", "type", "commands",
                   "version", "protocols",
                   "formfactor", "os", "appPackage", "application", "device",
                   "fxaDeviceId"]);


this.ClientEngine = function ClientEngine(service) {
  SyncEngine.call(this, "Clients", service);

  // Reset the last sync timestamp on every startup so that we fetch all clients
  this.resetLastSync();
  this.fxAccounts = fxAccounts;
  this.addClientCommandQueue = Promise.resolve();
}
ClientEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _storeObj: ClientStore,
  _recordObj: ClientsRec,
  _trackerObj: ClientsTracker,
  allowSkippedRecord: false,
  _knownStaleFxADeviceIds: null,

  // Always sync client data as it controls other sync behavior
  get enabled() {
    return true;
  },

  get lastRecordUpload() {
    return Svc.Prefs.get(this.name + ".lastRecordUpload", 0);
  },
  set lastRecordUpload(value) {
    Svc.Prefs.set(this.name + ".lastRecordUpload", Math.floor(value));
  },

  get remoteClients() {
    // return all non-stale clients for external consumption.
    return Object.values(this._store._remoteClients).filter(v => !v.stale);
  },

  remoteClient(id) {
    let client = this._store._remoteClients[id];
    return client && !client.stale ? client : null;
  },

  remoteClientExists(id) {
    return !!this.remoteClient(id);
  },

  // Aggregate some stats on the composition of clients on this account
  get stats() {
    let stats = {
      hasMobile: this.localType == DEVICE_TYPE_MOBILE,
      names: [this.localName],
      numClients: 1,
    };

    for (let id in this._store._remoteClients) {
      let {name, type, stale} = this._store._remoteClients[id];
      if (!stale) {
        stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE;
        stats.names.push(name);
        stats.numClients++;
      }
    }

    return stats;
  },

  /**
   * Obtain information about device types.
   *
   * Returns a Map of device types to integer counts. Guaranteed to include
   * "desktop" (which will have at least 1 - this device) and "mobile" (which
   * may have zero) counts. It almost certainly will include only these 2.
   */
  get deviceTypes() {
    let counts = new Map();

    counts.set(this.localType, 1); // currently this must be DEVICE_TYPE_DESKTOP
    counts.set(DEVICE_TYPE_MOBILE, 0);

    for (let id in this._store._remoteClients) {
      let record = this._store._remoteClients[id];
      if (record.stale) {
        continue; // pretend "stale" records don't exist.
      }
      let type = record.type;
      if (!counts.has(type)) {
        counts.set(type, 0);
      }

      counts.set(type, counts.get(type) + 1);
    }

    return counts;
  },

  get localID() {
    // Generate a random GUID id we don't have one
    let localID = Svc.Prefs.get("client.GUID", "");
    return localID == "" ? this.localID = Utils.makeGUID() : localID;
  },
  set localID(value) {
    Svc.Prefs.set("client.GUID", value);
  },

  get brandName() {
    let brand = Services.strings.createBundle(
      "chrome://branding/locale/brand.properties");
    return brand.GetStringFromName("brandShortName");
  },

  get localName() {
    let name = Utils.getDeviceName();
    // If `getDeviceName` returns the default name, set the pref. FxA registers
    // the device before syncing, so we don't need to update the registration
    // in this case.
    Svc.Prefs.set("client.name", name);
    return name;
  },
  set localName(value) {
    Svc.Prefs.set("client.name", value);
    // Update the registration in the background.
    this.fxAccounts.updateDeviceRegistration().catch(error => {
      this._log.warn("failed to update fxa device registration", error);
    });
  },

  get localType() {
    return Utils.getDeviceType();
  },
  set localType(value) {
    Svc.Prefs.set("client.type", value);
  },

  getClientName(id) {
    if (id == this.localID) {
      return this.localName;
    }
    let client = this._store._remoteClients[id];
    return client ? client.name : "";
  },

  getClientFxaDeviceId(id) {
    if (this._store._remoteClients[id]) {
      return this._store._remoteClients[id].fxaDeviceId;
    }
    return null;
  },

  isMobile: function isMobile(id) {
    if (this._store._remoteClients[id])
      return this._store._remoteClients[id].type == DEVICE_TYPE_MOBILE;
    return false;
  },

  async _readCommands() {
    let commands = await Utils.jsonLoad("commands", this);
    return commands || {};
  },

  /**
   * Low level function, do not use directly (use _addClientCommand instead).
   */
  async _saveCommands(commands) {
    try {
      await Utils.jsonSave("commands", this, commands);
    } catch (error) {
      this._log.error("Failed to save JSON outgoing commands", error);
    }
  },

  async _prepareCommandsForUpload() {
    try {
      await Utils.jsonMove("commands", "commands-syncing", this)
    } catch (e) {
      // Ignore errors
    }
    let commands = await Utils.jsonLoad("commands-syncing", this);
    return commands || {};
  },

  async _deleteUploadedCommands() {
    delete this._currentlySyncingCommands;
    try {
      await Utils.jsonRemove("commands-syncing", this);
    } catch (err) {
      this._log.error("Failed to delete syncing-commands file", err);
    }
  },

  // Gets commands for a client we are yet to write to the server. Doesn't
  // include commands for that client which are already on the server.
  // We should rename this!
  async getClientCommands(clientId) {
    const allCommands = await this._readCommands();
    return allCommands[clientId] || [];
  },

  async removeLocalCommand(command) {
    // the implementation of this engine is such that adding a command to
    // the local client is how commands are deleted! ¯\_(ツ)_/¯
    await this._addClientCommand(this.localID, command);
  },

  async _addClientCommand(clientId, command) {
    return this.addClientCommandQueue = (async () => {
      await this.addClientCommandQueue;
      try {
        const localCommands = await this._readCommands();
        const localClientCommands = localCommands[clientId] || [];
        const remoteClient = this._store._remoteClients[clientId];
        let remoteClientCommands = []
        if (remoteClient && remoteClient.commands) {
          remoteClientCommands = remoteClient.commands;
        }
        const clientCommands = localClientCommands.concat(remoteClientCommands);
        if (hasDupeCommand(clientCommands, command)) {
          return false;
        }
        localCommands[clientId] = localClientCommands.concat(command);
        await this._saveCommands(localCommands);
        return true;
      } catch (e) {
        // Failing to save a command should not "break the queue" of pending operations.
        this._log.error(e);
        return false;
      }
    })();
  },

  async _removeClientCommands(clientId) {
    const allCommands = await this._readCommands();
    delete allCommands[clientId];
    await this._saveCommands(allCommands);
  },

  async updateKnownStaleClients() {
    this._log.debug("Updating the known stale clients");
    await this._refreshKnownStaleClients();
    for (let client of Object.values(this._store._remoteClients)) {
      if (client.fxaDeviceId && this._knownStaleFxADeviceIds.includes(client.fxaDeviceId)) {
        this._log.info(`Hiding stale client ${client.id} - in known stale clients list`);
        client.stale = true;
      }
    }
  },

  // We assume that clients not present in the FxA Device Manager list have been
  // disconnected and so are stale
  async _refreshKnownStaleClients() {
    this._log.debug("Refreshing the known stale clients list");
    let localClients = Object.values(this._store._remoteClients)
                             .filter(client => client.fxaDeviceId) // iOS client records don't have fxaDeviceId
                             .map(client => client.fxaDeviceId);
    let fxaClients;
    try {
      let deviceList = await this.fxAccounts.getDeviceList();
      fxaClients = deviceList.map(device => device.id);
    } catch (ex) {
      this._log.error("Could not retrieve the FxA device list", ex);
      this._knownStaleFxADeviceIds = [];
      return;
    }
    this._knownStaleFxADeviceIds = Utils.arraySub(localClients, fxaClients);
  },

  async _syncStartup() {
    this.isFirstSync = !this.lastRecordUpload;
    // Reupload new client record periodically.
    if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) {
      this._tracker.addChangedID(this.localID);
      this.lastRecordUpload = Date.now() / 1000;
    }
    return SyncEngine.prototype._syncStartup.call(this);
  },

  async _processIncoming() {
    // Fetch all records from the server.
    this.lastSync = 0;
    this._incomingClients = {};
    try {
      await SyncEngine.prototype._processIncoming.call(this);
      // Refresh the known stale clients list at startup and when we receive
      // "device connected/disconnected" push notifications.
      if (!this._knownStaleFxADeviceIds) {
        await this._refreshKnownStaleClients();
      }
      // Since clients are synced unconditionally, any records in the local store
      // that don't exist on the server must be for disconnected clients. Remove
      // them, so that we don't upload records with commands for clients that will
      // never see them. We also do this to filter out stale clients from the
      // tabs collection, since showing their list of tabs is confusing.
      for (let id in this._store._remoteClients) {
        if (!this._incomingClients[id]) {
          this._log.info(`Removing local state for deleted client ${id}`);
          await this._removeRemoteClient(id);
        }
      }
      // Bug 1264498: Mobile clients don't remove themselves from the clients
      // collection when the user disconnects Sync, so we mark as stale clients
      // with the same name that haven't synced in over a week.
      // (Note we can't simply delete them, or we re-apply them next sync - see
      // bug 1287687)
      delete this._incomingClients[this.localID];
      let names = new Set([this.localName]);
      for (let [id, serverLastModified] of Object.entries(this._incomingClients)) {
        let record = this._store._remoteClients[id];
        // stash the server last-modified time on the record.
        record.serverLastModified = serverLastModified;
        if (record.fxaDeviceId && this._knownStaleFxADeviceIds.includes(record.fxaDeviceId)) {
          this._log.info(`Hiding stale client ${id} - in known stale clients list`);
          record.stale = true;
        }
        if (!names.has(record.name)) {
          names.add(record.name);
          continue;
        }
        let remoteAge = AsyncResource.serverTime - this._incomingClients[id];
        if (remoteAge > STALE_CLIENT_REMOTE_AGE) {
          this._log.info(`Hiding stale client ${id} with age ${remoteAge}`);
          record.stale = true;
        }
      }
    } finally {
      this._incomingClients = null;
    }
  },

  async _uploadOutgoing() {
    this._currentlySyncingCommands = await this._prepareCommandsForUpload();
    const clientWithPendingCommands = Object.keys(this._currentlySyncingCommands);
    for (let clientId of clientWithPendingCommands) {
      if (this._store._remoteClients[clientId] || this.localID == clientId) {
        this._modified.set(clientId, 0);
      }
    }
    let updatedIDs = this._modified.ids();
    await SyncEngine.prototype._uploadOutgoing.call(this);
    // Record the response time as the server time for each item we uploaded.
    for (let id of updatedIDs) {
      if (id != this.localID) {
        this._store._remoteClients[id].serverLastModified = this.lastSync;
      }
    }
  },

  async _onRecordsWritten(succeeded, failed) {
    // Reconcile the status of the local records with what we just wrote on the
    // server
    for (let id of succeeded) {
      const commandChanges = this._currentlySyncingCommands[id];
      if (id == this.localID) {
        if (this.isFirstSync) {
          this._log.info("Uploaded our client record for the first time, notifying other clients.");
          this._notifyCollectionChanged();
        }
        if (this.localCommands) {
          this.localCommands = this.localCommands.filter(command => !hasDupeCommand(commandChanges, command));
        }
      } else {
        const clientRecord = this._store._remoteClients[id];
        if (!commandChanges || !clientRecord) {
          // should be impossible, else we wouldn't have been writing it.
          this._log.warn("No command/No record changes for a client we uploaded");
          continue;
        }
        // fixup the client record, so our copy of _remoteClients matches what we uploaded.
        this._store._remoteClients[id] = await this._store.createRecord(id);
        // we could do better and pass the reference to the record we just uploaded,
        // but this will do for now
      }
    }

    // Re-add failed commands
    for (let id of failed) {
      const commandChanges = this._currentlySyncingCommands[id];
      if (!commandChanges) {
        continue;
      }
      await this._addClientCommand(id, commandChanges);
    }

    await this._deleteUploadedCommands();

    // Notify other devices that their own client collection changed
    const idsToNotify = succeeded.reduce((acc, id) => {
      if (id == this.localID) {
        return acc;
      }
      const fxaDeviceId = this.getClientFxaDeviceId(id);
      return fxaDeviceId ? acc.concat(fxaDeviceId) : acc;
    }, []);
    if (idsToNotify.length > 0) {
      this._notifyCollectionChanged(idsToNotify, NOTIFY_TAB_SENT_TTL_SECS);
    }
  },

  async _notifyCollectionChanged(ids = null, ttl = 0) {
    const message = {
      version: 1,
      command: "sync:collection_changed",
      data: {
        collections: ["clients"]
      }
    };
    let excludedIds = null;
    if (!ids) {
      const localFxADeviceId = await fxAccounts.getDeviceId();
      excludedIds = [localFxADeviceId];
    }
    try {
      await this.fxAccounts.notifyDevices(ids, excludedIds, message, ttl);
    } catch (e) {
      this._log.error("Could not notify of changes in the collection", e);
    }
  },

  async _syncFinish() {
    // Record histograms for our device types, and also write them to a pref
    // so non-histogram telemetry (eg, UITelemetry) and the sync scheduler
    // has easy access to them, and so they are accurate even before we've
    // successfully synced the first time after startup.
    for (let [deviceType, count] of this.deviceTypes) {
      let hid;
      let prefName = this.name + ".devices.";
      switch (deviceType) {
        case DEVICE_TYPE_DESKTOP:
          hid = "WEAVE_DEVICE_COUNT_DESKTOP";
          prefName += "desktop";
          break;
        case DEVICE_TYPE_MOBILE:
          hid = "WEAVE_DEVICE_COUNT_MOBILE";
          prefName += "mobile";
          break;
        default:
          this._log.warn(`Unexpected deviceType "${deviceType}" recording device telemetry.`);
          continue;
      }
      Services.telemetry.getHistogramById(hid).add(count);
      Svc.Prefs.set(prefName, count);
    }
    return SyncEngine.prototype._syncFinish.call(this);
  },

  async _reconcile(item) {
    // Every incoming record is reconciled, so we use this to track the
    // contents of the collection on the server.
    this._incomingClients[item.id] = item.modified;

    if (!(await this._store.itemExists(item.id))) {
      return true;
    }
    // Clients are synced unconditionally, so we'll always have new records.
    // Unfortunately, this will cause the scheduler to use the immediate sync
    // interval for the multi-device case, instead of the active interval. We
    // work around this by updating the record during reconciliation, and
    // returning false to indicate that the record doesn't need to be applied
    // later.
    await this._store.update(item);
    return false;
  },

  // Treat reset the same as wiping for locally cached clients
  async _resetClient() {
    await this._wipeClient();
  },

  async _wipeClient() {
    await SyncEngine.prototype._resetClient.call(this);
    this._knownStaleFxADeviceIds = null;
    delete this.localCommands;
    await this._store.wipe();
    try {
      await Utils.jsonRemove("commands", this);
    } catch (err) {
      this._log.warn("Could not delete commands.json", err);
    }
    try {
      await Utils.jsonRemove("commands-syncing", this)
    } catch (err) {
      this._log.warn("Could not delete commands-syncing.json", err);
    }
  },

  async removeClientData() {
    let res = this.service.resource(this.engineURL + "/" + this.localID);
    await res.delete();
  },

  // Override the default behavior to delete bad records from the server.
  async handleHMACMismatch(item, mayRetry) {
    this._log.debug("Handling HMAC mismatch for " + item.id);

    let base = await SyncEngine.prototype.handleHMACMismatch.call(this, item, mayRetry);
    if (base != SyncEngine.kRecoveryStrategy.error)
      return base;

    // It's a bad client record. Save it to be deleted at the end of the sync.
    this._log.debug("Bad client record detected. Scheduling for deletion.");
    this._deleteId(item.id);

    // Neither try again nor error; we're going to delete it.
    return SyncEngine.kRecoveryStrategy.ignore;
  },

  /**
   * A hash of valid commands that the client knows about. The key is a command
   * and the value is a hash containing information about the command such as
   * number of arguments and description.
   */
  _commands: {
    resetAll:    { args: 0, desc: "Clear temporary local data for all engines" },
    resetEngine: { args: 1, desc: "Clear temporary local data for engine" },
    wipeAll:     { args: 0, desc: "Delete all client data for all engines" },
    wipeEngine:  { args: 1, desc: "Delete all client data for engine" },
    logout:      { args: 0, desc: "Log out client" },
    displayURI:  { args: 3, desc: "Instruct a client to display a URI" },
    repairRequest:  {args: 1, desc: "Instruct a client to initiate a repair"},
    repairResponse: {args: 1, desc: "Instruct a client a repair request is complete"},
  },

  /**
   * Sends a command+args pair to a specific client.
   *
   * @param command Command string
   * @param args Array of arguments/data for command
   * @param clientId Client to send command to
   */
  async _sendCommandToClient(command, args, clientId, telemetryExtra) {
    this._log.trace("Sending " + command + " to " + clientId);

    let client = this._store._remoteClients[clientId];
    if (!client) {
      throw new Error("Unknown remote client ID: '" + clientId + "'.");
    }
    if (client.stale) {
      throw new Error("Stale remote client ID: '" + clientId + "'.");
    }

    let action = {
      command,
      args,
      // We send the flowID to the other client so *it* can report it in its
      // telemetry - we record it in ours below.
      flowID: telemetryExtra.flowID,
    };

    if ((await this._addClientCommand(clientId, action))) {
      this._log.trace(`Client ${clientId} got a new action`, [command, args]);
      this._tracker.addChangedID(clientId);
      try {
        telemetryExtra.deviceID = this.service.identity.hashedDeviceID(clientId);
      } catch (_) {}

      this.service.recordTelemetryEvent("sendcommand", command, undefined, telemetryExtra);
    } else {
      this._log.trace(`Client ${clientId} got a duplicate action`, [command, args]);
    }
  },

  /**
   * Check if the local client has any remote commands and perform them.
   *
   * @return false to abort sync
   */
  async processIncomingCommands() {
    return this._notify("clients:process-commands", "", async function() {
      if (!this.localCommands) {
        return true;
      }

      const clearedCommands = await this._readCommands()[this.localID];
      const commands = this.localCommands.filter(command => !hasDupeCommand(clearedCommands, command));
      let didRemoveCommand = false;
      let URIsToDisplay = [];
      // Process each command in order.
      for (let rawCommand of commands) {
        let shouldRemoveCommand = true; // most commands are auto-removed.
        let {command, args, flowID} = rawCommand;
        this._log.debug("Processing command " + command, args);

        this.service.recordTelemetryEvent("processcommand", command, undefined,
                                          { flowID });

        let engines = [args[0]];
        switch (command) {
          case "resetAll":
            engines = null;
            // Fallthrough
          case "resetEngine":
            await this.service.resetClient(engines);
            break;
          case "wipeAll":
            engines = null;
            // Fallthrough
          case "wipeEngine":
            await this.service.wipeClient(engines);
            break;
          case "logout":
            this.service.logout();
            return false;
          case "displayURI":
            let [uri, clientId, title] = args;
            URIsToDisplay.push({ uri, clientId, title });
            break;
          case "repairResponse": {
            // When we send a repair request to another device that understands
            // it, that device will send a response indicating what it did.
            let response = args[0];
            let requestor = getRepairRequestor(response.collection);
            if (!requestor) {
              this._log.warn("repairResponse for unknown collection", response);
              break;
            }
            if (!(await requestor.continueRepairs(response))) {
              this._log.warn("repairResponse couldn't continue the repair", response);
            }
            break;
          }
          case "repairRequest": {
            // Another device has sent us a request to make some repair.
            let request = args[0];
            let responder = getRepairResponder(request.collection);
            if (!responder) {
              this._log.warn("repairRequest for unknown collection", request);
              break;
            }
            try {
              if ((await responder.repair(request, rawCommand))) {
                // We've started a repair - once that collection has synced it
                // will write a "response" command and arrange for this repair
                // request to be removed from the local command list - if we
                // removed it now we might fail to write a response in cases of
                // premature shutdown etc.
                shouldRemoveCommand = false;
              }
            } catch (ex) {
              if (Async.isShutdownException(ex)) {
                // Let's assume this error was caused by the shutdown, so let
                // it try again next time.
                throw ex;
              }
              // otherwise there are no second chances - the command is removed
              // and will not be tried again.
              // (Note that this shouldn't be hit in the normal case - it's
              // expected the responder will handle all reasonable failures and
              // write a response indicating that it couldn't do what was asked.)
              this._log.error("Failed to handle a repair request", ex);
            }
            break;
          }
          default:
            this._log.warn("Received an unknown command: " + command);
            break;
        }
        // Add the command to the "cleared" commands list
        if (shouldRemoveCommand) {
          await this.removeLocalCommand(rawCommand);
          didRemoveCommand = true;
        }
      }
      if (didRemoveCommand) {
        this._tracker.addChangedID(this.localID);
      }

      if (URIsToDisplay.length) {
        this._handleDisplayURIs(URIsToDisplay);
      }

      return true;
    })();
  },

  /**
   * Validates and sends a command to a client or all clients.
   *
   * Calling this does not actually sync the command data to the server. If the
   * client already has the command/args pair, it won't receive a duplicate
   * command.
   * This method is async since it writes the command to a file.
   *
   * @param command
   *        Command to invoke on remote clients
   * @param args
   *        Array of arguments to give to the command
   * @param clientId
   *        Client ID to send command to. If undefined, send to all remote
   *        clients.
   * @param flowID
   *        A unique identifier used to track success for this operation across
   *        devices.
   */
  async sendCommand(command, args, clientId = null, telemetryExtra = {}) {
    let commandData = this._commands[command];
    // Don't send commands that we don't know about.
    if (!commandData) {
      this._log.error("Unknown command to send: " + command);
      return;
    } else if (!args || args.length != commandData.args) {
      // Don't send a command with the wrong number of arguments.
      this._log.error("Expected " + commandData.args + " args for '" +
                      command + "', but got " + args);
      return;
    }

    // We allocate a "flowID" here, so it is used for each client.
    telemetryExtra = Object.assign({}, telemetryExtra); // don't clobber the caller's object
    if (!telemetryExtra.flowID) {
      telemetryExtra.flowID = Utils.makeGUID();
    }

    if (clientId) {
      await this._sendCommandToClient(command, args, clientId, telemetryExtra);
    } else {
      for (let [id, record] of Object.entries(this._store._remoteClients)) {
        if (!record.stale) {
          await this._sendCommandToClient(command, args, id, telemetryExtra);
        }
      }
    }
  },

  /**
   * Send a URI to another client for display.
   *
   * A side effect is the score is increased dramatically to incur an
   * immediate sync.
   *
   * If an unknown client ID is specified, sendCommand() will throw an
   * Error object.
   *
   * @param uri
   *        URI (as a string) to send and display on the remote client
   * @param clientId
   *        ID of client to send the command to. If not defined, will be sent
   *        to all remote clients.
   * @param title
   *        Title of the page being sent.
   */
  async sendURIToClientForDisplay(uri, clientId, title) {
    this._log.info("Sending URI to client: " + uri + " -> " +
                   clientId + " (" + title + ")");
    await this.sendCommand("displayURI", [uri, this.localID, title], clientId);

    this._tracker.score += SCORE_INCREMENT_XLARGE;
  },

  /**
   * Handle a bunch of received 'displayURI' commands.
   *
   * Interested parties should observe the "weave:engine:clients:display-uris"
   * topic. The callback will receive an array as the subject parameter
   * containing objects with the following keys:
   *
   *   uri       URI (string) that is requested for display.
   *   clientId  ID of client that sent the command.
   *   title     Title of page that loaded URI (likely) corresponds to.
   *
   * The 'data' parameter to the callback will not be defined.
   *
   * @param uris
   *        An array containing URI objects to display
   * @param uris[].uri
   *        String URI that was received
   * @param uris[].clientId
   *        ID of client that sent URI
   * @param uris[].title
   *        String title of page that URI corresponds to. Older clients may not
   *        send this.
   */
  _handleDisplayURIs: function _handleDisplayURIs(uris) {
    Svc.Obs.notify("weave:engine:clients:display-uris", uris);
  },

  async _removeRemoteClient(id) {
    delete this._store._remoteClients[id];
    this._tracker.removeChangedID(id);
    await this._removeClientCommands(id);
    this._modified.delete(id);
  },
};

function ClientStore(name, engine) {
  Store.call(this, name, engine);
}
ClientStore.prototype = {
  __proto__: Store.prototype,

  _remoteClients: {},

  async create(record) {
    await this.update(record);
  },

  async update(record) {
    if (record.id == this.engine.localID) {
      // Only grab commands from the server; local name/type always wins
      this.engine.localCommands = record.commands;
    } else {
      this._remoteClients[record.id] = record.cleartext;
    }
  },

  async createRecord(id, collection) {
    let record = new ClientsRec(collection, id);

    const commandsChanges = this.engine._currentlySyncingCommands ?
                            this.engine._currentlySyncingCommands[id] :
                            [];

    // Package the individual components into a record for the local client
    if (id == this.engine.localID) {
      try {
        record.fxaDeviceId = await this.engine.fxAccounts.getDeviceId();
      } catch (error) {
        this._log.warn("failed to get fxa device id", error);
      }
      record.name = this.engine.localName;
      record.type = this.engine.localType;
      record.version = Services.appinfo.version;
      record.protocols = SUPPORTED_PROTOCOL_VERSIONS;

      // Substract the commands we recorded that we've already executed
      if (commandsChanges && commandsChanges.length &&
          this.engine.localCommands && this.engine.localCommands.length) {
        record.commands = this.engine.localCommands.filter(command => !hasDupeCommand(commandsChanges, command));
      }

      // Optional fields.
      record.os = Services.appinfo.OS;             // "Darwin"
      record.appPackage = Services.appinfo.ID;
      record.application = this.engine.brandName   // "Nightly"

      // We can't compute these yet.
      // record.device = "";            // Bug 1100723
      // record.formfactor = "";        // Bug 1100722
    } else {
      record.cleartext = Object.assign({}, this._remoteClients[id]);
      delete record.cleartext.serverLastModified; // serverLastModified is a local only attribute.

      // Add the commands we have to send
      if (commandsChanges && commandsChanges.length) {
        const recordCommands = record.cleartext.commands || [];
        const newCommands = commandsChanges.filter(command => !hasDupeCommand(recordCommands, command));
        record.cleartext.commands = recordCommands.concat(newCommands);
      }

      if (record.cleartext.stale) {
        // It's almost certainly a logic error for us to upload a record we
        // consider stale, so make log noise, but still remove the flag.
        this._log.error(`Preparing to upload record ${id} that we consider stale`);
        delete record.cleartext.stale;
      }
    }

    return record;
  },

  async itemExists(id) {
    return id in (await this.getAllIDs());
  },

  async getAllIDs() {
    let ids = {};
    ids[this.engine.localID] = true;
    for (let id in this._remoteClients)
      ids[id] = true;
    return ids;
  },

  async wipe() {
    this._remoteClients = {};
  },
};

function ClientsTracker(name, engine) {
  Tracker.call(this, name, engine);
  Svc.Obs.add("weave:engine:start-tracking", this);
  Svc.Obs.add("weave:engine:stop-tracking", this);
}
ClientsTracker.prototype = {
  __proto__: Tracker.prototype,

  _enabled: false,

  observe: function observe(subject, topic, data) {
    switch (topic) {
      case "weave:engine:start-tracking":
        if (!this._enabled) {
          Svc.Prefs.observe("client.name", this);
          this._enabled = true;
        }
        break;
      case "weave:engine:stop-tracking":
        if (this._enabled) {
          Svc.Prefs.ignore("client.name", this);
          this._enabled = false;
        }
        break;
      case "nsPref:changed":
        this._log.debug("client.name preference changed");
        this.addChangedID(Svc.Prefs.get("client.GUID"));
        this.score += SCORE_INCREMENT_XLARGE;
        break;
    }
  }
};
PK
!<w2modules/services-sync/engines/extension-storage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["ExtensionStorageEngine"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/util.js");
XPCOMUtils.defineLazyModuleGetter(this, "extensionStorageSync",
                                  "resource://gre/modules/ExtensionStorageSync.jsm");

/**
 * The Engine that manages syncing for the web extension "storage"
 * API, and in particular ext.storage.sync.
 *
 * ext.storage.sync is implemented using Kinto, so it has mechanisms
 * for syncing that we do not need to integrate in the Firefox Sync
 * framework, so this is something of a stub.
 */
this.ExtensionStorageEngine = function ExtensionStorageEngine(service) {
  SyncEngine.call(this, "Extension-Storage", service);
};
ExtensionStorageEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _trackerObj: ExtensionStorageTracker,
  // we don't need these since we implement our own sync logic
  _storeObj: undefined,
  _recordObj: undefined,

  syncPriority: 10,
  allowSkippedRecord: false,

  async _sync() {
    return extensionStorageSync.syncAll();
  },

  get enabled() {
    // By default, we sync extension storage if we sync addons. This
    // lets us simplify the UX since users probably don't consider
    // "extension preferences" a separate category of syncing.
    // However, we also respect engine.extension-storage.force, which
    // can be set to true or false, if a power user wants to customize
    // the behavior despite the lack of UI.
    const forced = Svc.Prefs.get("engine." + this.prefName + ".force", undefined);
    if (forced !== undefined) {
      return forced;
    }
    return Svc.Prefs.get("engine.addons", false);
  },
};

function ExtensionStorageTracker(name, engine) {
  Tracker.call(this, name, engine);
}
ExtensionStorageTracker.prototype = {
  __proto__: Tracker.prototype,

  startTracking() {
    Svc.Obs.add("ext.storage.sync-changed", this);
  },

  stopTracking() {
    Svc.Obs.remove("ext.storage.sync-changed", this);
  },

  observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);

    if (this.ignoreAll) {
      return;
    }

    if (topic !== "ext.storage.sync-changed") {
      return;
    }

    // Single adds, removes and changes are not so important on their
    // own, so let's just increment score a bit.
    this.score += SCORE_INCREMENT_MEDIUM;
  },

  // Override a bunch of methods which don't do anything for us.
  // This is a performance hack.
  ignoreID() {
  },
  unignoreID() {
  },
  addChangedID() {
  },
  removeChangedID() {
  },
  clearChangedIDs() {
  },
};
PK
!<	"LL&modules/services-sync/engines/forms.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["FormEngine", "FormRec", "FormValidator"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/collection_validator.js");
Cu.import("resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                  "resource://gre/modules/FormHistory.jsm");

const FORMS_TTL = 3 * 365 * 24 * 60 * 60;   // Three years in seconds.

this.FormRec = function FormRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
FormRec.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.Form",
  ttl: FORMS_TTL
};

Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);


var FormWrapper = {
  _log: Log.repository.getLogger("Sync.Engine.Forms"),

  _getEntryCols: ["fieldname", "value"],
  _guidCols:     ["guid"],

  async _search(terms, searchData) {
    return new Promise(resolve => {
      let results = [];
      let callbacks = {
        handleResult(result) {
          results.push(result);
        },
        handleCompletion(reason) {
          resolve(results);
        }
      };
      FormHistory.search(terms, searchData, callbacks);
    })
  },

  async _update(changes) {
    if (!FormHistory.enabled) {
      return; // update isn't going to do anything.
    }
    await new Promise(resolve => {
      let callbacks = {
        handleCompletion(reason) {
          resolve();
        }
      };
      FormHistory.update(changes, callbacks);
    });
  },

  async getEntry(guid) {
    let results = await this._search(this._getEntryCols, {guid});
    if (!results.length) {
      return null;
    }
    return {name: results[0].fieldname, value: results[0].value};
  },

  async getGUID(name, value) {
    // Query for the provided entry.
    let query = { fieldname: name, value };
    let results = await this._search(this._guidCols, query);
    return results.length ? results[0].guid : null;
  },

  async hasGUID(guid) {
    // We could probably use a count function here, but search exists...
    let results = await this._search(this._guidCols, {guid});
    return results.length != 0;
  },

  async replaceGUID(oldGUID, newGUID) {
    let changes = {
      op: "update",
      guid: oldGUID,
      newGuid: newGUID,
    }
    await this._update(changes);
  }

};

this.FormEngine = function FormEngine(service) {
  SyncEngine.call(this, "Forms", service);
}
FormEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _storeObj: FormStore,
  _trackerObj: FormTracker,
  _recordObj: FormRec,
  applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE,

  syncPriority: 6,

  get prefName() {
    return "history";
  },

  async _findDupe(item) {
    return FormWrapper.getGUID(item.name, item.value);
  }
};

function FormStore(name, engine) {
  Store.call(this, name, engine);
}
FormStore.prototype = {
  __proto__: Store.prototype,

  async _processChange(change) {
    // If this._changes is defined, then we are applying a batch, so we
    // can defer it.
    if (this._changes) {
      this._changes.push(change);
      return;
    }

    // Otherwise we must handle the change right now.
    await FormWrapper._update(change);
  },

  async applyIncomingBatch(records) {
    // We collect all the changes to be made then apply them all at once.
    this._changes = [];
    let failures = await Store.prototype.applyIncomingBatch.call(this, records);
    if (this._changes.length) {
      await FormWrapper._update(this._changes);
    }
    delete this._changes;
    return failures;
  },

  async getAllIDs() {
    let results = await FormWrapper._search(["guid"], [])
    let guids = {};
    for (let result of results) {
      guids[result.guid] = true;
    }
    return guids;
  },

  async changeItemID(oldID, newID) {
    await FormWrapper.replaceGUID(oldID, newID);
  },

  async itemExists(id) {
    return FormWrapper.hasGUID(id);
  },

  async createRecord(id, collection) {
    let record = new FormRec(collection, id);
    let entry = await FormWrapper.getEntry(id);
    if (entry != null) {
      record.name = entry.name;
      record.value = entry.value;
    } else {
      record.deleted = true;
    }
    return record;
  },

  async create(record) {
    this._log.trace("Adding form record for " + record.name);
    let change = {
      op: "add",
      fieldname: record.name,
      value: record.value
    };
    await this._processChange(change);
  },

  async remove(record) {
    this._log.trace("Removing form record: " + record.id);
    let change = {
      op: "remove",
      guid: record.id
    };
    await this._processChange(change);
  },

  async update(record) {
    this._log.trace("Ignoring form record update request!");
  },

  async wipe() {
    let change = {
      op: "remove"
    };
    await FormWrapper._update(change);
  }
};

function FormTracker(name, engine) {
  Tracker.call(this, name, engine);
}
FormTracker.prototype = {
  __proto__: Tracker.prototype,

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference]),

  startTracking() {
    Svc.Obs.add("satchel-storage-changed", this);
  },

  stopTracking() {
    Svc.Obs.remove("satchel-storage-changed", this);
  },

  observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);
    if (this.ignoreAll) {
      return;
    }
    switch (topic) {
      case "satchel-storage-changed":
        if (data == "formhistory-add" || data == "formhistory-remove") {
          let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
          this.trackEntry(guid);
        }
        break;
    }
  },

  trackEntry(guid) {
    if (this.addChangedID(guid)) {
      this.score += SCORE_INCREMENT_MEDIUM;
    }
  },
};


class FormsProblemData extends CollectionProblemData {
  getSummary() {
    // We don't support syncing deleted form data, so "clientMissing" isn't a problem
    return super.getSummary().filter(entry =>
      entry.name !== "clientMissing");
  }
}

class FormValidator extends CollectionValidator {
  constructor() {
    super("forms", "id", ["name", "value"]);
    this.ignoresMissingClients = true;
  }

  emptyProblemData() {
    return new FormsProblemData();
  }

  async getClientItems() {
    return FormWrapper._search(["guid", "fieldname", "value"], {});
  }

  normalizeClientItem(item) {
    return {
      id: item.guid,
      guid: item.guid,
      name: item.fieldname,
      fieldname: item.fieldname,
      value: item.value,
      original: item,
    };
  }

  async normalizeServerItem(item) {
    let res = Object.assign({
      guid: item.id,
      fieldname: item.name,
      original: item,
    }, item);
    // Missing `name` or `value` causes the getGUID call to throw
    if (item.name !== undefined && item.value !== undefined) {
      let guid = await FormWrapper.getGUID(item.name, item.value);
      if (guid) {
        res.guid = guid;
        res.id = guid;
        res.duped = true;
      }
    }

    return res;
  }
}
PK
!<7X1X1(modules/services-sync/engines/history.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["HistoryEngine", "HistoryRec"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

const HISTORY_TTL = 5184000; // 60 days in milliseconds
const THIRTY_DAYS_IN_MS = 2592000000; // 30 days in milliseconds

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
                                  "resource://gre/modules/PlacesSyncUtils.jsm");

this.HistoryRec = function HistoryRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
HistoryRec.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.History",
  ttl: HISTORY_TTL
};

Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]);


this.HistoryEngine = function HistoryEngine(service) {
  SyncEngine.call(this, "History", service);
}
HistoryEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _recordObj: HistoryRec,
  _storeObj: HistoryStore,
  _trackerObj: HistoryTracker,
  downloadLimit: MAX_HISTORY_DOWNLOAD,
  applyIncomingBatchSize: HISTORY_STORE_BATCH_SIZE,

  syncPriority: 7,

  async _processIncoming(newitems) {
    // We want to notify history observers that a batch operation is underway
    // so they don't do lots of work for each incoming record.
    let observers = PlacesUtils.history.getObservers();
    function notifyHistoryObservers(notification) {
      for (let observer of observers) {
        try {
          observer[notification]();
        } catch (ex) { }
      }
    }
    notifyHistoryObservers("onBeginUpdateBatch");
    try {
      await SyncEngine.prototype._processIncoming.call(this, newitems);
    } finally {
      notifyHistoryObservers("onEndUpdateBatch");
    }
  },

  async pullNewChanges() {
    let modifiedGUIDs = Object.keys(this._tracker.changedIDs);
    if (!modifiedGUIDs.length) {
      return {};
    }

    let guidsToRemove = await PlacesSyncUtils.history.determineNonSyncableGuids(modifiedGUIDs);
    this._tracker.removeChangedID(...guidsToRemove);
    return this._tracker.changedIDs;
  },
};

function HistoryStore(name, engine) {
  Store.call(this, name, engine);

  // Explicitly nullify our references to our cached services so we don't leak
  Svc.Obs.add("places-shutdown", function() {
    for (let query in this._stmts) {
      let stmt = this._stmts[query];
      stmt.finalize();
    }
    this._stmts = {};
  }, this);
}
HistoryStore.prototype = {
  __proto__: Store.prototype,

  __asyncHistory: null,
  get _asyncHistory() {
    if (!this.__asyncHistory) {
      this.__asyncHistory = Cc["@mozilla.org/browser/history;1"]
                              .getService(Ci.mozIAsyncHistory);
    }
    return this.__asyncHistory;
  },

  _stmts: {},
  _getStmt(query) {
    if (query in this._stmts) {
      return this._stmts[query];
    }

    this._log.trace("Creating SQL statement: " + query);
    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                        .DBConnection;
    return this._stmts[query] = db.createAsyncStatement(query);
  },

  // Some helper functions to handle GUIDs
  async setGUID(uri, guid) {

    if (!guid) {
      guid = Utils.makeGUID();
    }

    try {
      await PlacesSyncUtils.history.changeGuid(uri, guid);
    } catch (e) {
      this._log.error("Error setting GUID ${guid} for URI ${uri}", guid, uri);
    }

    return guid;
  },

  async GUIDForUri(uri, create) {

    // Use the existing GUID if it exists
    let guid;
    try {
      guid = await PlacesSyncUtils.history.fetchGuidForURL(uri);
    } catch (e) {
      this._log.error("Error fetching GUID for URL ${uri}", uri);
    }

    // If the URI has an existing GUID, return it.
    if (guid) {
      return guid;
    }

    // If the URI doesn't have a GUID and we were indicated to create one.
    if (create) {
      return this.setGUID(uri);
    }

    // If the URI doesn't have a GUID and we didn't create one for it.
    return null;
  },

  async changeItemID(oldID, newID) {
    this.setGUID(await PlacesSyncUtils.history.fetchURLInfoForGuid(oldID).url, newID);
  },

  async getAllIDs() {
    let urls = await PlacesSyncUtils.history.getAllURLs({ since: new Date((Date.now() - THIRTY_DAYS_IN_MS)), limit: MAX_HISTORY_UPLOAD });

    let urlsByGUID = {};
    for (let url of urls) {
      let guid = await this.GUIDForUri(url, true);
      urlsByGUID[guid] = url;
    }
    return urlsByGUID;
  },

  async applyIncomingBatch(records) {
    let failed = [];
    let blockers = [];

    // Convert incoming records to mozIPlaceInfo objects. Some records can be
    // ignored or handled directly, so we're rewriting the array in-place.
    let i, k;
    for (i = 0, k = 0; i < records.length; i++) {
      let record = records[k] = records[i];
      let shouldApply;

      try {
        if (record.deleted) {
          let promise = this.remove(record);
          promise = promise.catch(ex => failed.push(record.id));
          blockers.push(promise);

          // No further processing needed. Remove it from the list.
          shouldApply = false;
        } else {
          shouldApply = await this._recordToPlaceInfo(record);
        }
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        failed.push(record.id);
        shouldApply = false;
      }

      if (shouldApply) {
        k += 1;
      }
    }
    records.length = k; // truncate array

    if (records.length) {
      blockers.push(new Promise(resolve => {
        let updatePlacesCallback = {
          handleResult: function handleResult() {},
          handleError: function handleError(resultCode, placeInfo) {
            failed.push(placeInfo.guid);
          },
          handleCompletion: resolve,
        };
        this._asyncHistory.updatePlaces(records, updatePlacesCallback);
      }));
    }

    // failed is updated asynchronously, hence the await on blockers.
    await Promise.all(blockers);
    return failed;
  },

  /**
   * Converts a Sync history record to a mozIPlaceInfo.
   *
   * Throws if an invalid record is encountered (invalid URI, etc.),
   * returns true if the record is to be applied, false otherwise
   * (no visits to add, etc.),
   */
  async _recordToPlaceInfo(record) {
    // Sort out invalid URIs and ones Places just simply doesn't want.
    record.uri = Utils.makeURI(record.histUri);
    if (!record.uri) {
      this._log.warn("Attempted to process invalid URI, skipping.");
      throw new Error("Invalid URI in record");
    }

    if (!Utils.checkGUID(record.id)) {
      this._log.warn("Encountered record with invalid GUID: " + record.id);
      return false;
    }
    record.guid = record.id;

    if (!PlacesUtils.history.canAddURI(record.uri)) {
      this._log.trace("Ignoring record " + record.id + " with URI "
                      + record.uri.spec + ": can't add this URI.");
      return false;
    }

    // We dupe visits by date and type. So an incoming visit that has
    // the same timestamp and type as a local one won't get applied.
    // To avoid creating new objects, we rewrite the query result so we
    // can simply check for containment below.
    let curVisits = [];
    try {
      curVisits = await PlacesSyncUtils.history.fetchVisitsForURL(record.histUri);
    } catch (e) {
      this._log.error("Error while fetching visits for URL ${record.histUri}", record.histUri);
    }

    let i, k;
    for (i = 0; i < curVisits.length; i++) {
      curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
    }

    // Walk through the visits, make sure we have sound data, and eliminate
    // dupes. The latter is done by rewriting the array in-place.
    for (i = 0, k = 0; i < record.visits.length; i++) {
      let visit = record.visits[k] = record.visits[i];

      if (!visit.date || typeof visit.date != "number" || !Number.isInteger(visit.date)) {
        this._log.warn("Encountered record with invalid visit date: "
                       + visit.date);
        continue;
      }

      if (!visit.type ||
          !Object.values(PlacesUtils.history.TRANSITIONS).includes(visit.type)) {
        this._log.warn("Encountered record with invalid visit type: " +
                       visit.type + "; ignoring.");
        continue;
      }

      // Dates need to be integers.
      visit.date = Math.round(visit.date);

      if (curVisits.indexOf(visit.date + "," + visit.type) != -1) {
        // Visit is a dupe, don't increment 'k' so the element will be
        // overwritten.
        continue;
      }

      visit.visitDate = visit.date;
      visit.transitionType = visit.type;
      k += 1;
    }
    record.visits.length = k; // truncate array

    // No update if there aren't any visits to apply.
    // mozIAsyncHistory::updatePlaces() wants at least one visit.
    // In any case, the only thing we could change would be the title
    // and that shouldn't change without a visit.
    if (!record.visits.length) {
      this._log.trace("Ignoring record " + record.id + " with URI "
                      + record.uri.spec + ": no visits to add.");
      return false;
    }

    return true;
  },

  async remove(record) {
    this._log.trace("Removing page: " + record.id);
    let removed = await PlacesUtils.history.remove(record.id);
    if (removed) {
      this._log.trace("Removed page: " + record.id);
    } else {
      this._log.debug("Page already removed: " + record.id);
    }
  },

  async itemExists(id) {
    return !!(await PlacesSyncUtils.history.fetchURLInfoForGuid(id));
  },

  async createRecord(id, collection) {
    let foo = await PlacesSyncUtils.history.fetchURLInfoForGuid(id);
    let record = new HistoryRec(collection, id);
    if (foo) {
      record.histUri = foo.url;
      record.title = foo.title;
      record.sortindex = foo.frecency;
      try {
        record.visits = await PlacesSyncUtils.history.fetchVisitsForURL(record.histUri);
      } catch (e) {
        this._log.error("Error while fetching visits for URL ${record.histUri}", record.histUri);
        record.visits = [];
      }
    } else {
      record.deleted = true;
    }

    return record;
  },

  async wipe() {
    return PlacesUtils.history.clear();
  }
};

function HistoryTracker(name, engine) {
  Tracker.call(this, name, engine);
}
HistoryTracker.prototype = {
  __proto__: Tracker.prototype,

  startTracking() {
    this._log.info("Adding Places observer.");
    PlacesUtils.history.addObserver(this, true);
  },

  stopTracking() {
    this._log.info("Removing Places observer.");
    PlacesUtils.history.removeObserver(this);
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsINavHistoryObserver,
    Ci.nsISupportsWeakReference
  ]),

  onDeleteAffectsGUID(uri, guid, reason, source, increment) {
    if (this.ignoreAll || reason == Ci.nsINavHistoryObserver.REASON_EXPIRED) {
      return;
    }
    this._log.trace(source + ": " + uri.spec + ", reason " + reason);
    if (this.addChangedID(guid)) {
      this.score += increment;
    }
  },

  onDeleteVisits(uri, visitTime, guid, reason) {
    this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteVisits", SCORE_INCREMENT_SMALL);
  },

  onDeleteURI(uri, guid, reason) {
    this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteURI", SCORE_INCREMENT_XLARGE);
  },

  onVisit(uri, vid, time, session, referrer, trans, guid) {
    if (this.ignoreAll) {
      this._log.trace("ignoreAll: ignoring visit for " + guid);
      return;
    }

    this._log.trace("onVisit: " + uri.spec);
    if (this.addChangedID(guid)) {
      this.score += SCORE_INCREMENT_SMALL;
    }
  },

  onClearHistory() {
    this._log.trace("onClearHistory");
    // Note that we're going to trigger a sync, but none of the cleared
    // pages are tracked, so the deletions will not be propagated.
    // See Bug 578694.
    this.score += SCORE_INCREMENT_XLARGE;
  },

  onBeginUpdateBatch() {},
  onEndUpdateBatch() {},
  onPageChanged() {},
  onTitleChanged() {},
  onBeforeDeleteURI() {},
};
PK
!<l:|11*modules/services-sync/engines/passwords.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["PasswordEngine", "LoginRec", "PasswordValidator"];

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/collection_validator.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/async.js");

const SYNCABLE_LOGIN_FIELDS = [
  // `nsILoginInfo` fields.
  "hostname",
  "formSubmitURL",
  "httpRealm",
  "username",
  "password",
  "usernameField",
  "passwordField",

  // `nsILoginMetaInfo` fields.
  "timeCreated",
  "timePasswordChanged",
];

// Compares two logins to determine if their syncable fields changed. The login
// manager fires `modifyLogin` for changes to all fields, including ones we
// don't sync. In particular, `timeLastUsed` changes shouldn't mark the login
// for upload; otherwise, we might overwrite changed passwords before they're
// downloaded (bug 973166).
function isSyncableChange(oldLogin, newLogin) {
  oldLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  newLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  for (let property of SYNCABLE_LOGIN_FIELDS) {
    if (oldLogin[property] != newLogin[property]) {
      return true;
    }
  }
  return false;
}

this.LoginRec = function LoginRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
LoginRec.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.Login",
};

Utils.deferGetSet(LoginRec, "cleartext", [
    "hostname", "formSubmitURL",
    "httpRealm", "username", "password", "usernameField", "passwordField",
    "timeCreated", "timePasswordChanged",
    ]);


this.PasswordEngine = function PasswordEngine(service) {
  SyncEngine.call(this, "Passwords", service);
}
PasswordEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _storeObj: PasswordStore,
  _trackerObj: PasswordTracker,
  _recordObj: LoginRec,

  applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE,

  syncPriority: 2,

  async _syncFinish() {
    await SyncEngine.prototype._syncFinish.call(this);

    // Delete the Weave credentials from the server once.
    if (!Svc.Prefs.get("deletePwdFxA", false)) {
      try {
        let ids = [];
        for (let host of Utils.getSyncCredentialsHosts()) {
          for (let info of Services.logins.findLogins({}, host, "", "")) {
            ids.push(info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid);
          }
        }
        if (ids.length) {
          let coll = new Collection(this.engineURL, null, this.service);
          coll.ids = ids;
          let ret = await coll.delete();
          this._log.debug("Delete result: " + ret);
          if (!ret.success && ret.status != 400) {
            // A non-400 failure means try again next time.
            return;
          }
        } else {
          this._log.debug("Didn't find any passwords to delete");
        }
        // If there were no ids to delete, or we succeeded, or got a 400,
        // record success.
        Svc.Prefs.set("deletePwdFxA", true);
        Svc.Prefs.reset("deletePwd"); // The old prefname we previously used.
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        this._log.debug("Password deletes failed", ex);
      }
    }
  },

  async _findDupe(item) {
    let login = this._store._nsLoginInfoFromRecord(item);
    if (!login) {
      return null;
    }

    let logins = Services.logins.findLogins({}, login.hostname, login.formSubmitURL, login.httpRealm);

    await Async.promiseYield(); // Yield back to main thread after synchronous operation.

    // Look for existing logins that match the hostname, but ignore the password.
    for (let local of logins) {
      if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo) {
        return local.guid;
      }
    }

    return null;
  },

  async pullAllChanges() {
    let changes = {};
    let ids = await this._store.getAllIDs();
    for (let [id, info] of Object.entries(ids)) {
      changes[id] = info.timePasswordChanged / 1000;
    }
    return changes;
  }
};

function PasswordStore(name, engine) {
  Store.call(this, name, engine);
  this._nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
}
PasswordStore.prototype = {
  __proto__: Store.prototype,

  _newPropertyBag() {
    return Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
  },

  /**
   * Return an instance of nsILoginInfo (and, implicitly, nsILoginMetaInfo).
   */
  _nsLoginInfoFromRecord(record) {
    function nullUndefined(x) {
      return (x == undefined) ? null : x;
    }

    if (record.formSubmitURL && record.httpRealm) {
      this._log.warn("Record " + record.id + " has both formSubmitURL and httpRealm. Skipping.");
      return null;
    }

    // Passing in "undefined" results in an empty string, which later
    // counts as a value. Explicitly `|| null` these fields according to JS
    // truthiness. Records with empty strings or null will be unmolested.
    let info = new this._nsLoginInfo(record.hostname,
                                     nullUndefined(record.formSubmitURL),
                                     nullUndefined(record.httpRealm),
                                     record.username,
                                     record.password,
                                     record.usernameField,
                                     record.passwordField);

    info.QueryInterface(Ci.nsILoginMetaInfo);
    info.guid = record.id;
    if (record.timeCreated) {
      info.timeCreated = record.timeCreated;
    }
    if (record.timePasswordChanged) {
      info.timePasswordChanged = record.timePasswordChanged;
    }

    return info;
  },

  async _getLoginFromGUID(id) {
    let prop = this._newPropertyBag();
    prop.setPropertyAsAUTF8String("guid", id);

    let logins = Services.logins.searchLogins({}, prop);
    await Async.promiseYield(); // Yield back to main thread after synchronous operation.

    if (logins.length > 0) {
      this._log.trace(logins.length + " items matching " + id + " found.");
      return logins[0];
    }

    this._log.trace("No items matching " + id + " found. Ignoring");
    return null;
  },

  async getAllIDs() {
    let items = {};
    let logins = Services.logins.getAllLogins({});

    for (let i = 0; i < logins.length; i++) {
      // Skip over Weave password/passphrase entries.
      let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
      if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
        continue;
      }

      items[metaInfo.guid] = metaInfo;
    }

    return items;
  },

  async changeItemID(oldID, newID) {
    this._log.trace("Changing item ID: " + oldID + " to " + newID);

    let oldLogin = await this._getLoginFromGUID(oldID);
    if (!oldLogin) {
      this._log.trace("Can't change item ID: item doesn't exist");
      return;
    }
    if ((await this._getLoginFromGUID(newID))) {
      this._log.trace("Can't change item ID: new ID already in use");
      return;
    }

    let prop = this._newPropertyBag();
    prop.setPropertyAsAUTF8String("guid", newID);

    Services.logins.modifyLogin(oldLogin, prop);
  },

  async itemExists(id) {
    return !!(await this._getLoginFromGUID(id));
  },

  async createRecord(id, collection) {
    let record = new LoginRec(collection, id);
    let login = await this._getLoginFromGUID(id);

    if (!login) {
      record.deleted = true;
      return record;
    }

    record.hostname = login.hostname;
    record.formSubmitURL = login.formSubmitURL;
    record.httpRealm = login.httpRealm;
    record.username = login.username;
    record.password = login.password;
    record.usernameField = login.usernameField;
    record.passwordField = login.passwordField;

    // Optional fields.
    login.QueryInterface(Ci.nsILoginMetaInfo);
    record.timeCreated = login.timeCreated;
    record.timePasswordChanged = login.timePasswordChanged;

    return record;
  },

  async create(record) {
    let login = this._nsLoginInfoFromRecord(record);
    if (!login) {
      return;
    }

    this._log.debug("Adding login for " + record.hostname);
    this._log.trace("httpRealm: " + JSON.stringify(login.httpRealm) + "; " +
                    "formSubmitURL: " + JSON.stringify(login.formSubmitURL));
    try {
      Services.logins.addLogin(login);
    } catch (ex) {
      this._log.debug(`Adding record ${record.id} resulted in exception`, ex);
    }
  },

  async remove(record) {
    this._log.trace("Removing login " + record.id);

    let loginItem = await this._getLoginFromGUID(record.id);
    if (!loginItem) {
      this._log.trace("Asked to remove record that doesn't exist, ignoring");
      return;
    }

    Services.logins.removeLogin(loginItem);
  },

  async update(record) {
    let loginItem = await this._getLoginFromGUID(record.id);
    if (!loginItem) {
      this._log.debug("Skipping update for unknown item: " + record.hostname);
      return;
    }

    this._log.debug("Updating " + record.hostname);
    let newinfo = this._nsLoginInfoFromRecord(record);
    if (!newinfo) {
      return;
    }

    try {
      Services.logins.modifyLogin(loginItem, newinfo);
    } catch (ex) {
      this._log.debug(`Modifying record ${record.id} resulted in exception; not modifying`, ex);
    }
  },

  async wipe() {
    Services.logins.removeAllLogins();
  },
};

function PasswordTracker(name, engine) {
  Tracker.call(this, name, engine);
  Svc.Obs.add("weave:engine:start-tracking", this);
  Svc.Obs.add("weave:engine:stop-tracking", this);
}
PasswordTracker.prototype = {
  __proto__: Tracker.prototype,

  startTracking() {
    Svc.Obs.add("passwordmgr-storage-changed", this);
  },

  stopTracking() {
    Svc.Obs.remove("passwordmgr-storage-changed", this);
  },

  observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);

    if (this.ignoreAll) {
      return;
    }

    // A single add, remove or change or removing all items
    // will trigger a sync for MULTI_DEVICE.
    switch (data) {
      case "modifyLogin": {
        subject.QueryInterface(Ci.nsIArrayExtensions);
        let oldLogin = subject.GetElementAt(0);
        let newLogin = subject.GetElementAt(1);
        if (!isSyncableChange(oldLogin, newLogin)) {
          this._log.trace(`${data}: Ignoring change for ${newLogin.guid}`);
          break;
        }
        if (this._trackLogin(newLogin)) {
          this._log.trace(`${data}: Tracking change for ${newLogin.guid}`);
        }
        break;
      }

      case "addLogin":
      case "removeLogin":
        subject.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
        if (this._trackLogin(subject)) {
          this._log.trace(data + ": " + subject.guid);
        }
        break;

      case "removeAllLogins":
        this._log.trace(data);
        this.score += SCORE_INCREMENT_XLARGE;
        break;
    }
  },

  _trackLogin(login) {
    if (Utils.getSyncCredentialsHosts().has(login.hostname)) {
      // Skip over Weave password/passphrase changes.
      return false;
    }
    if (!this.addChangedID(login.guid)) {
      return false;
    }
    this.score += SCORE_INCREMENT_XLARGE;
    return true;
  },
};

class PasswordValidator extends CollectionValidator {
  constructor() {
    super("passwords", "id", [
      "hostname",
      "formSubmitURL",
      "httpRealm",
      "password",
      "passwordField",
      "username",
      "usernameField",
    ]);
  }

  getClientItems() {
    let logins = Services.logins.getAllLogins({});
    let syncHosts = Utils.getSyncCredentialsHosts()
    let result = logins.map(l => l.QueryInterface(Ci.nsILoginMetaInfo))
                       .filter(l => !syncHosts.has(l.hostname));
    return Promise.resolve(result);
  }

  normalizeClientItem(item) {
    return {
      id: item.guid,
      guid: item.guid,
      hostname: item.hostname,
      formSubmitURL: item.formSubmitURL,
      httpRealm: item.httpRealm,
      password: item.password,
      passwordField: item.passwordField,
      username: item.username,
      usernameField: item.usernameField,
      original: item,
    }
  }

  async normalizeServerItem(item) {
    return Object.assign({ guid: item.id }, item);
  }
}


PK
!<&modules/services-sync/engines/prefs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["PrefsEngine", "PrefRec"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

const PREF_SYNC_PREFS_PREFIX = "services.sync.prefs.sync.";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-common/utils.js");

XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                          "resource://gre/modules/LightweightThemeManager.jsm");

const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);

this.PrefRec = function PrefRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
PrefRec.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.Pref",
};

Utils.deferGetSet(PrefRec, "cleartext", ["value"]);


this.PrefsEngine = function PrefsEngine(service) {
  SyncEngine.call(this, "Prefs", service);
}
PrefsEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _storeObj: PrefStore,
  _trackerObj: PrefTracker,
  _recordObj: PrefRec,
  version: 2,

  syncPriority: 1,
  allowSkippedRecord: false,

  async getChangedIDs() {
    // No need for a proper timestamp (no conflict resolution needed).
    let changedIDs = {};
    if (this._tracker.modified)
      changedIDs[PREFS_GUID] = 0;
    return changedIDs;
  },

  async _wipeClient() {
    await SyncEngine.prototype._wipeClient.call(this);
    this.justWiped = true;
  },

  async _reconcile(item) {
    // Apply the incoming item if we don't care about the local data
    if (this.justWiped) {
      this.justWiped = false;
      return true;
    }
    return SyncEngine.prototype._reconcile.call(this, item);
  }
};


function PrefStore(name, engine) {
  Store.call(this, name, engine);
  Svc.Obs.add("profile-before-change", function() {
    this.__prefs = null;
  }, this);
}
PrefStore.prototype = {
  __proto__: Store.prototype,

 __prefs: null,
  get _prefs() {
    if (!this.__prefs) {
      this.__prefs = new Preferences();
    }
    return this.__prefs;
  },

  _getSyncPrefs() {
    let syncPrefs = Cc["@mozilla.org/preferences-service;1"]
                      .getService(Ci.nsIPrefService)
                      .getBranch(PREF_SYNC_PREFS_PREFIX)
                      .getChildList("", {});
    // Also sync preferences that determine which prefs get synced.
    let controlPrefs = syncPrefs.map(pref => PREF_SYNC_PREFS_PREFIX + pref);
    return controlPrefs.concat(syncPrefs);
  },

  _isSynced(pref) {
    return pref.startsWith(PREF_SYNC_PREFS_PREFIX) ||
           this._prefs.get(PREF_SYNC_PREFS_PREFIX + pref, false);
  },

  _getAllPrefs() {
    let values = {};
    for (let pref of this._getSyncPrefs()) {
      if (this._isSynced(pref)) {
        // Missing and default prefs get the null value.
        values[pref] = this._prefs.isSet(pref) ? this._prefs.get(pref, null) : null;
      }
    }
    return values;
  },

  _updateLightWeightTheme(themeID) {
    let themeObject = null;
    if (themeID) {
      themeObject = LightweightThemeManager.getUsedTheme(themeID);
    }
    LightweightThemeManager.currentTheme = themeObject;
  },

  _setAllPrefs(values) {
    let selectedThemeIDPref = "lightweightThemes.selectedThemeID";
    let selectedThemeIDBefore = this._prefs.get(selectedThemeIDPref, null);
    let selectedThemeIDAfter = selectedThemeIDBefore;

    // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise
    // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device).
    let prefs = Object.keys(values).sort(a => -a.indexOf(PREF_SYNC_PREFS_PREFIX));
    for (let pref of prefs) {
      if (!this._isSynced(pref)) {
        continue;
      }

      let value = values[pref];

      switch (pref) {
        // Some special prefs we don't want to set directly.
        case selectedThemeIDPref:
          selectedThemeIDAfter = value;
          break;

        // default is to just set the pref
        default:
          if (value == null) {
            // Pref has gone missing. The best we can do is reset it.
            this._prefs.reset(pref);
          } else {
            try {
              this._prefs.set(pref, value);
            } catch (ex) {
              this._log.trace(`Failed to set pref: ${pref}`, ex);
            }
          }
      }
    }

    // Notify the lightweight theme manager if the selected theme has changed.
    if (selectedThemeIDBefore != selectedThemeIDAfter) {
      this._updateLightWeightTheme(selectedThemeIDAfter);
    }
  },

  async getAllIDs() {
    /* We store all prefs in just one WBO, with just one GUID */
    let allprefs = {};
    allprefs[PREFS_GUID] = true;
    return allprefs;
  },

  async changeItemID(oldID, newID) {
    this._log.trace("PrefStore GUID is constant!");
  },

  async itemExists(id) {
    return (id === PREFS_GUID);
  },

  async createRecord(id, collection) {
    let record = new PrefRec(collection, id);

    if (id == PREFS_GUID) {
      record.value = this._getAllPrefs();
    } else {
      record.deleted = true;
    }

    return record;
  },

  async create(record) {
    this._log.trace("Ignoring create request");
  },

  async remove(record) {
    this._log.trace("Ignoring remove request");
  },

  async update(record) {
    // Silently ignore pref updates that are for other apps.
    if (record.id != PREFS_GUID)
      return;

    this._log.trace("Received pref updates, applying...");
    this._setAllPrefs(record.value);
  },

  async wipe() {
    this._log.trace("Ignoring wipe request");
  }
};

function PrefTracker(name, engine) {
  Tracker.call(this, name, engine);
  Svc.Obs.add("profile-before-change", this);
  Svc.Obs.add("weave:engine:start-tracking", this);
  Svc.Obs.add("weave:engine:stop-tracking", this);
}
PrefTracker.prototype = {
  __proto__: Tracker.prototype,

  get modified() {
    return Svc.Prefs.get("engine.prefs.modified", false);
  },
  set modified(value) {
    Svc.Prefs.set("engine.prefs.modified", value);
  },

  clearChangedIDs: function clearChangedIDs() {
    this.modified = false;
  },

 __prefs: null,
  get _prefs() {
    if (!this.__prefs) {
      this.__prefs = new Preferences();
    }
    return this.__prefs;
  },

  startTracking() {
    Services.prefs.addObserver("", this);
  },

  stopTracking() {
    this.__prefs = null;
    Services.prefs.removeObserver("", this);
  },

  observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);

    switch (topic) {
      case "profile-before-change":
        this.stopTracking();
        break;
      case "nsPref:changed":
        if (this.ignoreAll) {
          break;
        }
        // Trigger a sync for MULTI-DEVICE for a change that determines
        // which prefs are synced or a regular pref change.
        if (data.indexOf(PREF_SYNC_PREFS_PREFIX) == 0 ||
            this._prefs.get(PREF_SYNC_PREFS_PREFIX + data, false)) {
          this.score += SCORE_INCREMENT_XLARGE;
          this.modified = true;
          this._log.trace("Preference " + data + " changed");
        }
        break;
    }
  }
};
PK
!<}Qݕ//%modules/services-sync/engines/tabs.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["TabEngine", "TabSetRecord"];

var {classes: Cc, interfaces: Ci, utils: Cu} = Components;

const TABS_TTL = 1814400;          // 21 days.
const TAB_ENTRIES_LIMIT = 5;      // How many URLs to include in tab history.

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");

XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
  "resource:///modules/sessionstore/SessionStore.jsm");

this.TabSetRecord = function TabSetRecord(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
TabSetRecord.prototype = {
  __proto__: CryptoWrapper.prototype,
  _logName: "Sync.Record.Tabs",
  ttl: TABS_TTL,
};

Utils.deferGetSet(TabSetRecord, "cleartext", ["clientName", "tabs"]);


this.TabEngine = function TabEngine(service) {
  SyncEngine.call(this, "Tabs", service);
}
TabEngine.prototype = {
  __proto__: SyncEngine.prototype,
  _storeObj: TabStore,
  _trackerObj: TabTracker,
  _recordObj: TabSetRecord,
  // A flag to indicate if we have synced in this session. This is to help
  // consumers of remote tabs that may want to differentiate between "I've an
  // empty tab list as I haven't yet synced" vs "I've an empty tab list
  // as there really are no tabs"
  hasSyncedThisSession: false,

  syncPriority: 3,

  async initialize() {
    await SyncEngine.prototype.initialize.call(this);

    // Reset the client on every startup so that we fetch recent tabs.
    await this._resetClient();
  },

  async getChangedIDs() {
    // No need for a proper timestamp (no conflict resolution needed).
    let changedIDs = {};
    if (this._tracker.modified)
      changedIDs[this.service.clientsEngine.localID] = 0;
    return changedIDs;
  },

  // API for use by Sync UI code to give user choices of tabs to open.
  getAllClients() {
    return this._store._remoteClients;
  },

  getClientById(id) {
    return this._store._remoteClients[id];
  },

  async _resetClient() {
    await SyncEngine.prototype._resetClient.call(this);
    await this._store.wipe();
    this._tracker.modified = true;
    this.hasSyncedThisSession = false;
  },

  async removeClientData() {
    let url = this.engineURL + "/" + this.service.clientsEngine.localID;
    await this.service.resource(url).delete();
  },

  /**
   * Return a Set of open URLs.
   */
  getOpenURLs() {
    let urls = new Set();
    for (let entry of this._store.getAllTabs()) {
      urls.add(entry.urlHistory[0]);
    }
    return urls;
  },

  async _reconcile(item) {
    // Skip our own record.
    // TabStore.itemExists tests only against our local client ID.
    if ((await this._store.itemExists(item.id))) {
      this._log.trace("Ignoring incoming tab item because of its id: " + item.id);
      return false;
    }

    return SyncEngine.prototype._reconcile.call(this, item);
  },

  async _syncFinish() {
    this.hasSyncedThisSession = true;
    return SyncEngine.prototype._syncFinish.call(this);
  },
};


function TabStore(name, engine) {
  Store.call(this, name, engine);
}
TabStore.prototype = {
  __proto__: Store.prototype,

  async itemExists(id) {
    return id == this.engine.service.clientsEngine.localID;
  },

  getWindowEnumerator() {
    return Services.wm.getEnumerator("navigator:browser");
  },

  shouldSkipWindow(win) {
    return win.closed ||
           PrivateBrowsingUtils.isWindowPrivate(win);
  },

  getTabState(tab) {
    return JSON.parse(SessionStore.getTabState(tab));
  },

  getAllTabs(filter) {
    let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");

    let allTabs = [];

    let winEnum = this.getWindowEnumerator();
    while (winEnum.hasMoreElements()) {
      let win = winEnum.getNext();
      if (this.shouldSkipWindow(win)) {
        continue;
      }

      for (let tab of win.gBrowser.tabs) {
        let tabState = this.getTabState(tab);

        // Make sure there are history entries to look at.
        if (!tabState || !tabState.entries.length) {
          continue;
        }

        let acceptable = !filter ? (url) => url :
                                   (url) => url && !filteredUrls.test(url);

        let entries = tabState.entries;
        let index = tabState.index;
        let current = entries[index - 1];

        // We ignore the tab completely if the current entry url is
        // not acceptable (we need something accurate to open).
        if (!acceptable(current.url)) {
          continue;
        }

        if (current.url.length >= (MAX_UPLOAD_BYTES - 1000)) {
          this._log.trace("Skipping over-long URL.");
          continue;
        }

        // The element at `index` is the current page. Previous URLs were
        // previously visited URLs; subsequent URLs are in the 'forward' stack,
        // which we can't represent in Sync, so we truncate here.
        let candidates = (entries.length == index) ?
                         entries :
                         entries.slice(0, index);

        let urls = candidates.map((entry) => entry.url)
                             .filter(acceptable)
                             .reverse();                       // Because Sync puts current at index 0, and history after.

        // Truncate if necessary.
        if (urls.length > TAB_ENTRIES_LIMIT) {
          urls.length = TAB_ENTRIES_LIMIT;
        }

        allTabs.push({
          title: current.title || "",
          urlHistory: urls,
          icon: tabState.image ||
                (tabState.attributes && tabState.attributes.image) ||
                "",
          lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000),
        });
      }
    }

    return allTabs;
  },

  async createRecord(id, collection) {
    let record = new TabSetRecord(collection, id);
    record.clientName = this.engine.service.clientsEngine.localName;

    // Sort tabs in descending-used order to grab the most recently used
    let tabs = this.getAllTabs(true).sort(function(a, b) {
      return b.lastUsed - a.lastUsed;
    });

    // Figure out how many tabs we can pack into a payload.
    // We use byteLength here because the data is not encrypted in ascii yet.
    let size = new TextEncoder("utf-8").encode(JSON.stringify(tabs)).byteLength;
    let origLength = tabs.length;
    // See bug 535326 comment 8 for an explanation of the estimation
    const MAX_TAB_SIZE = this.engine.maxRecordPayloadBytes / 4 * 3 - 1500;
    if (size > MAX_TAB_SIZE) {
      // Estimate a little more than the direct fraction to maximize packing
      let cutoff = Math.ceil(tabs.length * MAX_TAB_SIZE / size);
      tabs = tabs.slice(0, cutoff + 1);

      // Keep dropping off the last entry until the data fits
      while (JSON.stringify(tabs).length > MAX_TAB_SIZE)
        tabs.pop();
    }

    this._log.trace("Created tabs " + tabs.length + " of " + origLength);
    tabs.forEach(function(tab) {
      this._log.trace("Wrapping tab: " + JSON.stringify(tab));
    }, this);

    record.tabs = tabs;
    return record;
  },

  async getAllIDs() {
    // Don't report any tabs if all windows are in private browsing for
    // first syncs.
    let ids = {};
    let allWindowsArePrivate = false;
    let wins = Services.wm.getEnumerator("navigator:browser");
    while (wins.hasMoreElements()) {
      if (PrivateBrowsingUtils.isWindowPrivate(wins.getNext())) {
        // Ensure that at least there is a private window.
        allWindowsArePrivate = true;
      } else {
        // If there is a not private windown then finish and continue.
        allWindowsArePrivate = false;
        break;
      }
    }

    if (allWindowsArePrivate &&
        !PrivateBrowsingUtils.permanentPrivateBrowsing) {
      return ids;
    }

    ids[this.engine.service.clientsEngine.localID] = true;
    return ids;
  },

  async wipe() {
    this._remoteClients = {};
  },

  async create(record) {
    this._log.debug("Adding remote tabs from " + record.clientName);
    this._remoteClients[record.id] = Object.assign({}, record.cleartext, {
      lastModified: record.modified
    });
  },

  async update(record) {
    this._log.trace("Ignoring tab updates as local ones win");
  },
};


function TabTracker(name, engine) {
  Tracker.call(this, name, engine);
  Svc.Obs.add("weave:engine:start-tracking", this);
  Svc.Obs.add("weave:engine:stop-tracking", this);

  // Make sure "this" pointer is always set correctly for event listeners.
  this.onTab = Utils.bind2(this, this.onTab);
  this._unregisterListeners = Utils.bind2(this, this._unregisterListeners);
}
TabTracker.prototype = {
  __proto__: Tracker.prototype,

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),

  clearChangedIDs() {
    this.modified = false;
  },

  _topics: ["pageshow", "TabOpen", "TabClose", "TabSelect"],

  _registerListenersForWindow(window) {
    this._log.trace("Registering tab listeners in window");
    for (let topic of this._topics) {
      window.addEventListener(topic, this.onTab);
    }
    window.addEventListener("unload", this._unregisterListeners);
    // If it's got a tab browser we can listen for things like navigation.
    if (window.gBrowser) {
      window.gBrowser.addProgressListener(this);
    }
  },

  _unregisterListeners(event) {
    this._unregisterListenersForWindow(event.target);
  },

  _unregisterListenersForWindow(window) {
    this._log.trace("Removing tab listeners in window");
    window.removeEventListener("unload", this._unregisterListeners);
    for (let topic of this._topics) {
      window.removeEventListener(topic, this.onTab);
    }
    if (window.gBrowser) {
      window.gBrowser.removeProgressListener(this);
    }
  },

  startTracking() {
    Svc.Obs.add("domwindowopened", this);
    let wins = Services.wm.getEnumerator("navigator:browser");
    while (wins.hasMoreElements()) {
      this._registerListenersForWindow(wins.getNext());
    }
  },

  stopTracking() {
    Svc.Obs.remove("domwindowopened", this);
    let wins = Services.wm.getEnumerator("navigator:browser");
    while (wins.hasMoreElements()) {
      this._unregisterListenersForWindow(wins.getNext());
    }
  },

  observe(subject, topic, data) {
    Tracker.prototype.observe.call(this, subject, topic, data);

    switch (topic) {
      case "domwindowopened":
        let onLoad = () => {
          subject.removeEventListener("load", onLoad);
          // Only register after the window is done loading to avoid unloads.
          this._registerListenersForWindow(subject);
        };

        // Add tab listeners now that a window has opened.
        subject.addEventListener("load", onLoad);
        break;
    }
  },

  onTab(event) {
    if (event.originalTarget.linkedBrowser) {
      let browser = event.originalTarget.linkedBrowser;
      if (PrivateBrowsingUtils.isBrowserPrivate(browser) &&
          !PrivateBrowsingUtils.permanentPrivateBrowsing) {
        this._log.trace("Ignoring tab event from private browsing.");
        return;
      }
    }

    this._log.trace("onTab event: " + event.type);
    this.modified = true;

    // For page shows, bump the score 10% of the time, emulating a partial
    // score. We don't want to sync too frequently. For all other page
    // events, always bump the score.
    if (event.type != "pageshow" || Math.random() < .1) {
      this.score += SCORE_INCREMENT_SMALL;
    }
  },

  // web progress listeners.
  onLocationChange(webProgress, request, location, flags) {
    // We only care about top-level location changes which are not in the same
    // document.
    if (webProgress.isTopLevel &&
        ((flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) == 0)) {
      this.modified = true;
    }
  },
};
PK
!<m55 modules/services-sync/engines.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "EngineManager",
  "Engine",
  "SyncEngine",
  "Tracker",
  "Store",
  "Changeset"
];

var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/JSONFile.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");

XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
  "resource://gre/modules/FxAccounts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

/*
 * Trackers are associated with a single engine and deal with
 * listening for changes to their particular data type.
 *
 * There are two things they keep track of:
 * 1) A score, indicating how urgently the engine wants to sync
 * 2) A list of IDs for all the changed items that need to be synced
 * and updating their 'score', indicating how urgently they
 * want to sync.
 *
 */
this.Tracker = function Tracker(name, engine) {
  if (!engine) {
    throw new Error("Tracker must be associated with an Engine instance.");
  }

  name = name || "Unnamed";
  this.name = this.file = name.toLowerCase();
  this.engine = engine;

  this._log = Log.repository.getLogger("Sync.Tracker." + name);
  let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
  this._log.level = Log.Level[level];

  this._score = 0;
  this._ignored = [];
  this._storage = new JSONFile({
    path: Utils.jsonFilePath("changes/" + this.file),
    // We use arrow functions instead of `.bind(this)` so that tests can
    // easily override these hooks.
    dataPostProcessor: json => this._dataPostProcessor(json),
    beforeSave: () => this._beforeSave(),
  });
  this.ignoreAll = false;

  Svc.Obs.add("weave:engine:start-tracking", this);
  Svc.Obs.add("weave:engine:stop-tracking", this);

  Svc.Prefs.observe("engine." + this.engine.prefName, this);
};

Tracker.prototype = {
  /*
   * Score can be called as often as desired to decide which engines to sync
   *
   * Valid values for score:
   * -1: Do not sync unless the user specifically requests it (almost disabled)
   * 0: Nothing has changed
   * 100: Please sync me ASAP!
   *
   * Setting it to other values should (but doesn't currently) throw an exception
   */
  get score() {
    return this._score;
  },

  // Default to an empty object if the file doesn't exist.
  _dataPostProcessor(json) {
    return typeof json == "object" && json || {};
  },

  // Ensure the Weave storage directory exists before writing the file.
  _beforeSave() {
    let basename = OS.Path.dirname(this._storage.path);
    return OS.File.makeDir(basename, { from: OS.Constants.Path.profileDir });
  },

  get changedIDs() {
    Async.promiseSpinningly(this._storage.load());
    return this._storage.data;
  },

  set score(value) {
    this._score = value;
    Observers.notify("weave:engine:score:updated", this.name);
  },

  // Should be called by service everytime a sync has been done for an engine
  resetScore() {
    this._score = 0;
  },

  persistChangedIDs: true,

  _saveChangedIDs() {
    if (!this.persistChangedIDs) {
      this._log.debug("Not saving changedIDs.");
      return;
    }
    this._storage.saveSoon();
  },

  // ignore/unignore specific IDs.  Useful for ignoring items that are
  // being processed, or that shouldn't be synced.
  // But note: not persisted to disk

  ignoreID(id) {
    this.unignoreID(id);
    this._ignored.push(id);
  },

  unignoreID(id) {
    let index = this._ignored.indexOf(id);
    if (index != -1)
      this._ignored.splice(index, 1);
  },

  _saveChangedID(id, when) {
    this._log.trace(`Adding changed ID: ${id}, ${JSON.stringify(when)}`);
    this.changedIDs[id] = when;
    this._saveChangedIDs();
  },

  addChangedID(id, when) {
    if (!id) {
      this._log.warn("Attempted to add undefined ID to tracker");
      return false;
    }

    if (this.ignoreAll || this._ignored.includes(id)) {
      return false;
    }

    // Default to the current time in seconds if no time is provided.
    if (when == null) {
      when = this._now();
    }

    // Add/update the entry if we have a newer time.
    if ((this.changedIDs[id] || -Infinity) < when) {
      this._saveChangedID(id, when);
    }

    return true;
  },

  removeChangedID(...ids) {
    if (!ids.length || this.ignoreAll) {
      return false;
    }
    for (let id of ids) {
      if (!id) {
        this._log.warn("Attempted to remove undefined ID from tracker");
        continue;
      }
      if (this._ignored.includes(id)) {
        this._log.debug(`Not removing ignored ID ${id} from tracker`);
        continue;
      }
      if (this.changedIDs[id] != null) {
        this._log.trace("Removing changed ID " + id);
        delete this.changedIDs[id];
      }
    }
    this._saveChangedIDs();
    return true;
  },

  clearChangedIDs() {
    this._log.trace("Clearing changed ID list");
    this._storage.data = {};
    this._saveChangedIDs();
  },

  _now() {
    return Date.now() / 1000;
  },

  _isTracking: false,

  // Override these in your subclasses.
  startTracking() {
  },

  stopTracking() {
  },

  engineIsEnabled() {
    if (!this.engine) {
      // Can't tell -- we must be running in a test!
      return true;
    }
    return this.engine.enabled;
  },

  onEngineEnabledChanged(engineEnabled) {
    if (engineEnabled == this._isTracking) {
      return;
    }

    if (engineEnabled) {
      this.startTracking();
      this._isTracking = true;
    } else {
      this.stopTracking();
      this._isTracking = false;
      this.clearChangedIDs();
    }
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "weave:engine:start-tracking":
        if (!this.engineIsEnabled()) {
          return;
        }
        this._log.trace("Got start-tracking.");
        if (!this._isTracking) {
          this.startTracking();
          this._isTracking = true;
        }
        return;
      case "weave:engine:stop-tracking":
        this._log.trace("Got stop-tracking.");
        if (this._isTracking) {
          this.stopTracking();
          this._isTracking = false;
        }
        return;
      case "nsPref:changed":
        if (data == PREFS_BRANCH + "engine." + this.engine.prefName) {
          this.onEngineEnabledChanged(this.engine.enabled);
        }
    }
  },

  async finalize() {
    // Stop listening for tracking and engine enabled change notifications.
    // Important for tests where we unregister the engine during cleanup.
    Svc.Obs.remove("weave:engine:start-tracking", this);
    Svc.Obs.remove("weave:engine:stop-tracking", this);
    Svc.Prefs.ignore("engine." + this.engine.prefName, this);

    // Persist all pending tracked changes to disk, and wait for the final write
    // to finish.
    this._saveChangedIDs();
    await this._storage.finalize();
  },
};



/**
 * The Store serves as the interface between Sync and stored data.
 *
 * The name "store" is slightly a misnomer because it doesn't actually "store"
 * anything. Instead, it serves as a gateway to something that actually does
 * the "storing."
 *
 * The store is responsible for record management inside an engine. It tells
 * Sync what items are available for Sync, converts items to and from Sync's
 * record format, and applies records from Sync into changes on the underlying
 * store.
 *
 * Store implementations require a number of functions to be implemented. These
 * are all documented below.
 *
 * For stores that deal with many records or which have expensive store access
 * routines, it is highly recommended to implement a custom applyIncomingBatch
 * and/or applyIncoming function on top of the basic APIs.
 */

this.Store = function Store(name, engine) {
  if (!engine) {
    throw new Error("Store must be associated with an Engine instance.");
  }

  name = name || "Unnamed";
  this.name = name.toLowerCase();
  this.engine = engine;

  this._log = Log.repository.getLogger("Sync.Store." + name);
  let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
  this._log.level = Log.Level[level];

  XPCOMUtils.defineLazyGetter(this, "_timer", function() {
    return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  });
}
Store.prototype = {

  /**
   * Apply multiple incoming records against the store.
   *
   * This is called with a set of incoming records to process. The function
   * should look at each record, reconcile with the current local state, and
   * make the local changes required to bring its state in alignment with the
   * record.
   *
   * The default implementation simply iterates over all records and calls
   * applyIncoming(). Store implementations may overwrite this function
   * if desired.
   *
   * @param  records Array of records to apply
   * @return Array of record IDs which did not apply cleanly
   */
  async applyIncomingBatch(records) {
    let failed = [];
    let maybeYield = Async.jankYielder();
    for (let record of records) {
      await maybeYield();
      try {
        await this.applyIncoming(record);
      } catch (ex) {
        if (ex.code == Engine.prototype.eEngineAbortApplyIncoming) {
          // This kind of exception should have a 'cause' attribute, which is an
          // originating exception.
          // ex.cause will carry its stack with it when rethrown.
          throw ex.cause;
        }
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        this._log.warn("Failed to apply incoming record " + record.id, ex);
        failed.push(record.id);
      }
    }
    return failed;
  },

  /**
   * Apply a single record against the store.
   *
   * This takes a single record and makes the local changes required so the
   * local state matches what's in the record.
   *
   * The default implementation calls one of remove(), create(), or update()
   * depending on the state obtained from the store itself. Store
   * implementations may overwrite this function if desired.
   *
   * @param record
   *        Record to apply
   */
  async applyIncoming(record) {
    if (record.deleted)
      await this.remove(record);
    else if (!(await this.itemExists(record.id)))
      await this.create(record);
    else
      await this.update(record);
  },

  // override these in derived objects

  /**
   * Create an item in the store from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The store record to create an item from
   */
  async create(record) {
    throw new Error("override create in a subclass");
  },

  /**
   * Remove an item in the store from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The store record to delete an item from
   */
  async remove(record) {
    throw new Error("override remove in a subclass");
  },

  /**
   * Update an item from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The record to use to update an item from
   */
  async update(record) {
    throw new Error("override update in a subclass");
  },

  /**
   * Determine whether a record with the specified ID exists.
   *
   * Takes a string record ID and returns a booleans saying whether the record
   * exists.
   *
   * @param  id
   *         string record ID
   * @return boolean indicating whether record exists locally
   */
  async itemExists(id) {
    throw new Error("override itemExists in a subclass");
  },

  /**
   * Create a record from the specified ID.
   *
   * If the ID is known, the record should be populated with metadata from
   * the store. If the ID is not known, the record should be created with the
   * delete field set to true.
   *
   * @param  id
   *         string record ID
   * @param  collection
   *         Collection to add record to. This is typically passed into the
   *         constructor for the newly-created record.
   * @return record type for this engine
   */
  async createRecord(id, collection) {
    throw new Error("override createRecord in a subclass");
  },

  /**
   * Change the ID of a record.
   *
   * @param  oldID
   *         string old/current record ID
   * @param  newID
   *         string new record ID
   */
  async changeItemID(oldID, newID) {
    throw new Error("override changeItemID in a subclass");
  },

  /**
   * Obtain the set of all known record IDs.
   *
   * @return Object with ID strings as keys and values of true. The values
   *         are ignored.
   */
  async getAllIDs() {
    throw new Error("override getAllIDs in a subclass");
  },

  /**
   * Wipe all data in the store.
   *
   * This function is called during remote wipes or when replacing local data
   * with remote data.
   *
   * This function should delete all local data that the store is managing. It
   * can be thought of as clearing out all state and restoring the "new
   * browser" state.
   */
  async wipe() {
    throw new Error("override wipe in a subclass");
  }
};

this.EngineManager = function EngineManager(service) {
  this.service = service;

  this._engines = {};

  // This will be populated by Service on startup.
  this._declined = new Set();
  this._log = Log.repository.getLogger("Sync.EngineManager");
  this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.engines", "Debug")];
}
EngineManager.prototype = {
  get(name) {
    // Return an array of engines if we have an array of names
    if (Array.isArray(name)) {
      let engines = [];
      name.forEach(function(name) {
        let engine = this.get(name);
        if (engine) {
          engines.push(engine);
        }
      }, this);
      return engines;
    }

    let engine = this._engines[name];
    if (!engine) {
      this._log.debug("Could not get engine: " + name);
      if (Object.keys) {
        this._log.debug("Engines are: " + JSON.stringify(Object.keys(this._engines)));
      }
    }
    return engine;
  },

  getAll() {
    let engines = [];
    for (let [, engine] of Object.entries(this._engines)) {
      engines.push(engine);
    }
    return engines;
  },

  /**
   * N.B., does not pay attention to the declined list.
   */
  getEnabled() {
    return this.getAll()
               .filter((engine) => engine.enabled)
               .sort((a, b) => a.syncPriority - b.syncPriority);
  },

  get enabledEngineNames() {
    return this.getEnabled().map(e => e.name);
  },

  persistDeclined() {
    Svc.Prefs.set("declinedEngines", [...this._declined].join(","));
  },

  /**
   * Returns an array.
   */
  getDeclined() {
    return [...this._declined];
  },

  setDeclined(engines) {
    this._declined = new Set(engines);
    this.persistDeclined();
  },

  isDeclined(engineName) {
    return this._declined.has(engineName);
  },

  /**
   * Accepts a Set or an array.
   */
  decline(engines) {
    for (let e of engines) {
      this._declined.add(e);
    }
    this.persistDeclined();
  },

  undecline(engines) {
    for (let e of engines) {
      this._declined.delete(e);
    }
    this.persistDeclined();
  },

  /**
   * Mark any non-enabled engines as declined.
   *
   * This is useful after initial customization during setup.
   */
  declineDisabled() {
    for (let e of this.getAll()) {
      if (!e.enabled) {
        this._log.debug("Declining disabled engine " + e.name);
        this._declined.add(e.name);
      }
    }
    this.persistDeclined();
  },

  /**
   * Register an Engine to the service. Alternatively, give an array of engine
   * objects to register.
   *
   * @param engineObject
   *        Engine object used to get an instance of the engine
   * @return The engine object if anything failed
   */
  async register(engineObject) {
    if (Array.isArray(engineObject)) {
      for (const e of engineObject) {
        await this.register(e);
      }
      return;
    }

    try {
      let engine = new engineObject(this.service);
      let name = engine.name;
      if (name in this._engines) {
        this._log.error("Engine '" + name + "' is already registered!");
      } else {
        if (engine.initialize) {
          await engine.initialize();
        }
        this._engines[name] = engine;
      }
    } catch (ex) {
      let name = engineObject || "";
      name = name.prototype || "";
      name = name.name || "";

      this._log.error(`Could not initialize engine ${name}`, ex);
    }
  },

  unregister(val) {
    let name = val;
    if (val instanceof Engine) {
      name = val.name;
    }
    if (name in this._engines) {
      let engine = this._engines[name];
      delete this._engines[name];
      Async.promiseSpinningly(engine.finalize());
    }
  },

  clear() {
    for (let name in this._engines) {
      let engine = this._engines[name];
      delete this._engines[name];
      Async.promiseSpinningly(engine.finalize());
    }
  },
};

this.Engine = function Engine(name, service) {
  if (!service) {
    throw new Error("Engine must be associated with a Service instance.");
  }

  this.Name = name || "Unnamed";
  this.name = name.toLowerCase();
  this.service = service;

  this._notify = Utils.notify("weave:engine:");
  this._log = Log.repository.getLogger("Sync.Engine." + this.Name);
  let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
  this._log.level = Log.Level[level];

  this._modified = this.emptyChangeset();
  this._tracker; // initialize tracker to load previously changed IDs
  this._log.debug("Engine constructed");
}
Engine.prototype = {
  // _storeObj, and _trackerObj should to be overridden in subclasses
  _storeObj: Store,
  _trackerObj: Tracker,

  // Override this method to return a new changeset type.
  emptyChangeset() {
    return new Changeset();
  },

  // Local 'constant'.
  // Signal to the engine that processing further records is pointless.
  eEngineAbortApplyIncoming: "error.engine.abort.applyincoming",

  // Should we keep syncing if we find a record that cannot be uploaded (ever)?
  // If this is false, we'll throw, otherwise, we'll ignore the record and
  // continue. This currently can only happen due to the record being larger
  // than the record upload limit.
  allowSkippedRecord: true,

  get prefName() {
    return this.name;
  },

  get enabled() {
    return Svc.Prefs.get("engine." + this.prefName, false);
  },

  set enabled(val) {
    Svc.Prefs.set("engine." + this.prefName, !!val);
  },

  get score() {
    return this._tracker.score;
  },

  get _store() {
    let store = new this._storeObj(this.Name, this);
    this.__defineGetter__("_store", () => store);
    return store;
  },

  get _tracker() {
    let tracker = new this._trackerObj(this.Name, this);
    this.__defineGetter__("_tracker", () => tracker);
    return tracker;
  },

  async sync() {
    if (!this.enabled) {
      return false;
    }

    if (!this._sync) {
      throw new Error("engine does not implement _sync method");
    }

    return this._notify("sync", this.name, this._sync)();
  },

  /**
   * Get rid of any local meta-data.
   */
  async resetClient() {
    if (!this._resetClient) {
      throw new Error("engine does not implement _resetClient method");
    }

    return this._notify("reset-client", this.name, this._resetClient)();
  },

  async _wipeClient() {
    await this.resetClient();
    this._log.debug("Deleting all local data");
    this._tracker.ignoreAll = true;
    await this._store.wipe();
    this._tracker.ignoreAll = false;
    this._tracker.clearChangedIDs();
  },

  async wipeClient() {
    return this._notify("wipe-client", this.name, this._wipeClient)();
  },

  /**
   * If one exists, initialize and return a validator for this engine (which
   * must have a `validate(engine)` method that returns a promise to an object
   * with a getSummary method). Otherwise return null.
   */
  getValidator() {
    return null;
  },

  async finalize() {
    await this._tracker.finalize();
  },
};

this.SyncEngine = function SyncEngine(name, service) {
  Engine.call(this, name || "SyncEngine", service);

  // Async initializations can be made in the initialize() method.

  // The map of ids => metadata for records needing a weak upload.
  //
  // Currently the "metadata" fields are:
  //
  // - forceTombstone: whether or not we should ignore the local information
  //   about the record, and write a tombstone for it anyway -- e.g. in the case
  //   of records that should exist locally, but should never be uploaded to the
  //   server (note that not all sync engines support tombstones)
  //
  // The difference between this and a "normal" upload is that these records
  // are only tracked in memory, and if the upload attempt fails (shutdown,
  // 412, etc), we abort uploading the "weak" set (by clearing the map).
  //
  // The rationale here is for the cases where we receive a record from the
  // server that we know is wrong in some (small) way. For example, the
  // dateAdded field on bookmarks -- maybe we have a better date, or the server
  // record is entirely missing the date, etc.
  //
  // In these cases, we fix our local copy of the record, and mark it for
  // weak upload. A normal ("strong") upload is problematic here because
  // in the case of a conflict from the server, there's a window where our
  // record would be marked as modified more recently than a change that occurs
  // on another device change, and we lose data from the user.
  //
  // Additionally, we use this as the set of items to upload for bookmark
  // repair reponse, which has similar constraints.
  this._needWeakUpload = new Map();
}

// Enumeration to define approaches to handling bad records.
// Attached to the constructor to allow use as a kind of static enumeration.
SyncEngine.kRecoveryStrategy = {
  ignore: "ignore",
  retry:  "retry",
  error:  "error"
};

SyncEngine.prototype = {
  __proto__: Engine.prototype,
  _recordObj: CryptoWrapper,
  version: 1,

  // Which sortindex to use when retrieving records for this engine.
  _defaultSort: undefined,

  // A relative priority to use when computing an order
  // for engines to be synced. Higher-priority engines
  // (lower numbers) are synced first.
  // It is recommended that a unique value be used for each engine,
  // in order to guarantee a stable sequence.
  syncPriority: 0,

  // How many records to pull in a single sync. This is primarily to avoid very
  // long first syncs against profiles with many history records.
  downloadLimit: null,

  // How many records to pull at one time when specifying IDs. This is to avoid
  // URI length limitations.
  guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,

  // How many records to process in a single batch.
  applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE,

  async initialize() {
    await this.loadToFetch();
    await this.loadPreviousFailed();
    this._log.debug("SyncEngine initialized", this.name);
  },

  get storageURL() {
    return this.service.storageURL;
  },

  get engineURL() {
    return this.storageURL + this.name;
  },

  get cryptoKeysURL() {
    return this.storageURL + "crypto/keys";
  },

  get metaURL() {
    return this.storageURL + "meta/global";
  },

  get syncID() {
    // Generate a random syncID if we don't have one
    let syncID = Svc.Prefs.get(this.name + ".syncID", "");
    return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
  },
  set syncID(value) {
    Svc.Prefs.set(this.name + ".syncID", value);
  },

  /*
   * lastSync is a timestamp in server time.
   */
  get lastSync() {
    return parseFloat(Svc.Prefs.get(this.name + ".lastSync", "0"));
  },
  set lastSync(value) {
    // Reset the pref in-case it's a number instead of a string
    Svc.Prefs.reset(this.name + ".lastSync");
    // Store the value as a string to keep floating point precision
    Svc.Prefs.set(this.name + ".lastSync", value.toString());
  },
  resetLastSync() {
    this._log.debug("Resetting " + this.name + " last sync time");
    Svc.Prefs.reset(this.name + ".lastSync");
    Svc.Prefs.set(this.name + ".lastSync", "0");
    this.lastSyncLocal = 0;
  },

  get toFetch() {
    return this._toFetch;
  },
  set toFetch(val) {
    // Coerce the array to a string for more efficient comparison.
    if (val + "" == this._toFetch) {
      return;
    }
    this._toFetch = val;
    Utils.namedTimer(function() {
      try {
        Async.promiseSpinningly(Utils.jsonSave("toFetch/" + this.name, this, val));
      } catch (error) {
        this._log.error("Failed to read JSON records to fetch", error);
      }
    }, 0, this, "_toFetchDelay");
  },

  async loadToFetch() {
    // Initialize to empty if there's no file.
    this._toFetch = [];
    let toFetch = await Utils.jsonLoad("toFetch/" + this.name, this);
    if (toFetch) {
      this._toFetch = toFetch;
    }
  },

  get previousFailed() {
    return this._previousFailed;
  },
  set previousFailed(val) {
    // Coerce the array to a string for more efficient comparison.
    if (val + "" == this._previousFailed) {
      return;
    }
    this._previousFailed = val;
    Utils.namedTimer(function() {
      Utils.jsonSave("failed/" + this.name, this, val).then(() => {
        this._log.debug("Successfully wrote previousFailed.");
      })
      .catch((error) => {
        this._log.error("Failed to set previousFailed", error);
      });
    }, 0, this, "_previousFailedDelay");
  },

  async loadPreviousFailed() {
    // Initialize to empty if there's no file
    this._previousFailed = [];
    let previousFailed = await Utils.jsonLoad("failed/" + this.name, this);
    if (previousFailed) {
      this._previousFailed = previousFailed;
    }
  },

  /*
   * lastSyncLocal is a timestamp in local time.
   */
  get lastSyncLocal() {
    return parseInt(Svc.Prefs.get(this.name + ".lastSyncLocal", "0"), 10);
  },
  set lastSyncLocal(value) {
    // Store as a string because pref can only store C longs as numbers.
    Svc.Prefs.set(this.name + ".lastSyncLocal", value.toString());
  },

  get maxRecordPayloadBytes() {
    let serverConfiguration = this.service.serverConfiguration;
    if (serverConfiguration && serverConfiguration.max_record_payload_bytes) {
      return serverConfiguration.max_record_payload_bytes;
    }
    return DEFAULT_MAX_RECORD_PAYLOAD_BYTES;
  },

  /*
   * Returns a changeset for this sync. Engine implementations can override this
   * method to bypass the tracker for certain or all changed items.
   */
  async getChangedIDs() {
    return this._tracker.changedIDs;
  },

  // Create a new record using the store and add in metadata.
  async _createRecord(id) {
    let record = await this._store.createRecord(id, this.name);
    record.id = id;
    record.collection = this.name;
    return record;
  },

  // Creates a tombstone Sync record with additional metadata.
  _createTombstone(id) {
    let tombstone = new this._recordObj(this.name, id);
    tombstone.id = id;
    tombstone.collection = this.name;
    tombstone.deleted = true;
    return tombstone;
  },

  addForWeakUpload(id, { forceTombstone = false } = {}) {
    this._needWeakUpload.set(id, { forceTombstone });
  },

  // Any setup that needs to happen at the beginning of each sync.
  async _syncStartup() {

    // Determine if we need to wipe on outdated versions
    let metaGlobal = await this.service.recordManager.get(this.metaURL);
    let engines = metaGlobal.payload.engines || {};
    let engineData = engines[this.name] || {};

    let needsWipe = false;

    // Assume missing versions are 0 and wipe the server
    if ((engineData.version || 0) < this.version) {
      this._log.debug("Old engine data: " + [engineData.version, this.version]);

      // Prepare to clear the server and upload everything
      needsWipe = true;
      this.syncID = "";

      // Set the newer version and newly generated syncID
      engineData.version = this.version;
      engineData.syncID = this.syncID;

      // Put the new data back into meta/global and mark for upload
      engines[this.name] = engineData;
      metaGlobal.payload.engines = engines;
      metaGlobal.changed = true;
    } else if (engineData.version > this.version) {
      // Don't sync this engine if the server has newer data
      let error = new String("New data: " + [engineData.version, this.version]);
      error.failureCode = VERSION_OUT_OF_DATE;
      throw error;
    } else if (engineData.syncID != this.syncID) {
      // Changes to syncID mean we'll need to upload everything
      this._log.debug("Engine syncIDs: " + [engineData.syncID, this.syncID]);
      this.syncID = engineData.syncID;
      await this._resetClient();
    }

    // Delete any existing data and reupload on bad version or missing meta.
    // No crypto component here...? We could regenerate per-collection keys...
    if (needsWipe) {
      await this.wipeServer();
    }

    // Save objects that need to be uploaded in this._modified. We also save
    // the timestamp of this fetch in this.lastSyncLocal. As we successfully
    // upload objects we remove them from this._modified. If an error occurs
    // or any objects fail to upload, they will remain in this._modified. At
    // the end of a sync, or after an error, we add all objects remaining in
    // this._modified to the tracker.
    this.lastSyncLocal = Date.now();
    let initialChanges;
    if (this.lastSync) {
      initialChanges = await this.pullNewChanges();
    } else {
      this._log.debug("First sync, uploading all items");
      initialChanges = await this.pullAllChanges();
    }
    this._modified.replace(initialChanges);
    // Clear the tracker now. If the sync fails we'll add the ones we failed
    // to upload back.
    this._tracker.clearChangedIDs();

    this._log.info(this._modified.count() +
                   " outgoing items pre-reconciliation");

    // Keep track of what to delete at the end of sync
    this._delete = {};
  },

  /**
   * A tiny abstraction to make it easier to test incoming record
   * application.
   */
  itemSource() {
    return new Collection(this.engineURL, this._recordObj, this.service);
  },

  /**
   * Process incoming records.
   * In the most awful and untestable way possible.
   * This now accepts something that makes testing vaguely less impossible.
   */
  async _processIncoming(newitems) {
    this._log.trace("Downloading & applying server changes");

    // Figure out how many total items to fetch this sync; do less on mobile.
    let batchSize = this.downloadLimit || Infinity;

    if (!newitems) {
      newitems = this.itemSource();
    }

    if (this._defaultSort) {
      newitems.sort = this._defaultSort;
    }

    newitems.newer = this.lastSync;
    newitems.full  = true;
    newitems.limit = batchSize;

    // applied    => number of items that should be applied.
    // failed     => number of items that failed in this sync.
    // newFailed  => number of items that failed for the first time in this sync.
    // reconciled => number of items that were reconciled.
    let count = {applied: 0, failed: 0, newFailed: 0, reconciled: 0};
    let handled = [];
    let applyBatch = [];
    let failed = [];
    let failedInPreviousSync = this.previousFailed;
    let fetchBatch = Utils.arrayUnion(this.toFetch, failedInPreviousSync);
    // Reset previousFailed for each sync since previously failed items may not fail again.
    this.previousFailed = [];

    // Used (via exceptions) to allow the record handler/reconciliation/etc.
    // methods to signal that they would like processing of incoming records to
    // cease.
    let aborting = undefined;

    async function doApplyBatch() {
      this._tracker.ignoreAll = true;
      try {
        failed = failed.concat((await this._store.applyIncomingBatch(applyBatch)));
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        // Catch any error that escapes from applyIncomingBatch. At present
        // those will all be abort events.
        this._log.warn("Got exception, aborting processIncoming", ex);
        aborting = ex;
      }
      this._tracker.ignoreAll = false;
      applyBatch = [];
    }

    async function doApplyBatchAndPersistFailed() {
      // Apply remaining batch.
      if (applyBatch.length) {
        await doApplyBatch.call(this);
      }
      // Persist failed items so we refetch them.
      if (failed.length) {
        this.previousFailed = Utils.arrayUnion(failed, this.previousFailed);
        count.failed += failed.length;
        this._log.debug("Records that failed to apply: " + failed);
        failed = [];
      }
    }

    let key = this.service.collectionKeys.keyForCollection(this.name);

    // Not binding this method to 'this' for performance reasons. It gets
    // called for every incoming record.
    let self = this;

    let recordHandler = async function(item) {
      if (aborting) {
        return;
      }

      // Grab a later last modified if possible
      if (self.lastModified == null || item.modified > self.lastModified)
        self.lastModified = item.modified;

      // Track the collection for the WBO.
      item.collection = self.name;

      // Remember which records were processed
      handled.push(item.id);

      try {
        try {
          item.decrypt(key);
        } catch (ex) {
          if (!Utils.isHMACMismatch(ex)) {
            throw ex;
          }
          let strategy = await self.handleHMACMismatch(item, true);
          if (strategy == SyncEngine.kRecoveryStrategy.retry) {
            // You only get one retry.
            try {
              // Try decrypting again, typically because we've got new keys.
              self._log.info("Trying decrypt again...");
              key = self.service.collectionKeys.keyForCollection(self.name);
              item.decrypt(key);
              strategy = null;
            } catch (ex) {
              if (!Utils.isHMACMismatch(ex)) {
                throw ex;
              }
              strategy = await self.handleHMACMismatch(item, false);
            }
          }

          switch (strategy) {
            case null:
              // Retry succeeded! No further handling.
              break;
            case SyncEngine.kRecoveryStrategy.retry:
              self._log.debug("Ignoring second retry suggestion.");
              // Fall through to error case.
            case SyncEngine.kRecoveryStrategy.error:
              self._log.warn("Error decrypting record", ex);
              failed.push(item.id);
              return;
            case SyncEngine.kRecoveryStrategy.ignore:
              self._log.debug("Ignoring record " + item.id +
                              " with bad HMAC: already handled.");
              return;
          }
        }
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        self._log.warn("Error decrypting record", ex);
        failed.push(item.id);
        return;
      }

      if (self._shouldDeleteRemotely(item)) {
        self._log.trace("Deleting item from server without applying", item);
        self._deleteId(item.id);
        return;
      }

      let shouldApply;
      try {
        shouldApply = await self._reconcile(item);
      } catch (ex) {
        if (ex.code == Engine.prototype.eEngineAbortApplyIncoming) {
          self._log.warn("Reconciliation failed: aborting incoming processing.");
          failed.push(item.id);
          aborting = ex.cause;
        } else if (!Async.isShutdownException(ex)) {
          self._log.warn("Failed to reconcile incoming record " + item.id, ex);
          failed.push(item.id);
          return;
        } else {
          throw ex;
        }
      }

      if (shouldApply) {
        count.applied++;
        applyBatch.push(item);
      } else {
        count.reconciled++;
        self._log.trace("Skipping reconciled incoming item " + item.id);
      }

      if (applyBatch.length == self.applyIncomingBatchSize) {
        await doApplyBatch.call(self);
      }
    };

    // Only bother getting data from the server if there's new things
    if (this.lastModified == null || this.lastModified > this.lastSync) {
      let { response, records } = await newitems.getBatched();
      if (!response.success) {
        response.failureCode = ENGINE_DOWNLOAD_FAIL;
        throw response;
      }

      let maybeYield = Async.jankYielder();
      for (let record of records) {
        await maybeYield();
        await recordHandler(record);
      }
      await doApplyBatchAndPersistFailed.call(this);

      if (aborting) {
        throw aborting;
      }
    }

    // Mobile: check if we got the maximum that we requested; get the rest if so.
    if (handled.length == newitems.limit) {
      let guidColl = new Collection(this.engineURL, null, this.service);

      // Sort and limit so that on mobile we only get the last X records.
      guidColl.limit = this.downloadLimit;
      guidColl.newer = this.lastSync;

      // index: Orders by the sortindex descending (highest weight first).
      guidColl.sort  = "index";

      let guids = await guidColl.get();
      if (!guids.success)
        throw guids;

      // Figure out which guids weren't just fetched then remove any guids that
      // were already waiting and prepend the new ones
      let extra = Utils.arraySub(guids.obj, handled);
      if (extra.length > 0) {
        fetchBatch = Utils.arrayUnion(extra, fetchBatch);
        this.toFetch = Utils.arrayUnion(extra, this.toFetch);
      }
    }

    // Fast-foward the lastSync timestamp since we have stored the
    // remaining items in toFetch.
    if (this.lastSync < this.lastModified) {
      this.lastSync = this.lastModified;
    }

    // Process any backlog of GUIDs.
    // At this point we impose an upper limit on the number of items to fetch
    // in a single request, even for desktop, to avoid hitting URI limits.
    batchSize = this.guidFetchBatchSize;

    while (fetchBatch.length && !aborting) {
      // Reuse the original query, but get rid of the restricting params
      // and batch remaining records.
      newitems.limit = 0;
      newitems.newer = 0;
      newitems.ids = fetchBatch.slice(0, batchSize);

      let resp = await newitems.get();
      if (!resp.success) {
        resp.failureCode = ENGINE_DOWNLOAD_FAIL;
        throw resp;
      }

      let maybeYield = Async.jankYielder();
      for (let json of resp.obj) {
        await maybeYield();
        let record = new this._recordObj();
        record.deserialize(json);
        await recordHandler(record);
      }

      // This batch was successfully applied. Not using
      // doApplyBatchAndPersistFailed() here to avoid writing toFetch twice.
      fetchBatch = fetchBatch.slice(batchSize);
      this.toFetch = Utils.arraySub(this.toFetch, newitems.ids);
      this.previousFailed = Utils.arrayUnion(this.previousFailed, failed);
      if (failed.length) {
        count.failed += failed.length;
        this._log.debug("Records that failed to apply: " + failed);
      }
      failed = [];

      if (aborting) {
        throw aborting;
      }

      if (this.lastSync < this.lastModified) {
        this.lastSync = this.lastModified;
      }
    }

    // Apply remaining items.
    await doApplyBatchAndPersistFailed.call(this);

    count.newFailed = this.previousFailed.reduce((count, engine) => {
      if (failedInPreviousSync.indexOf(engine) == -1) {
        count++;
      }
      return count;
    }, 0);
    count.succeeded = Math.max(0, count.applied - count.failed);
    this._log.info(["Records:",
                    count.applied, "applied,",
                    count.succeeded, "successfully,",
                    count.failed, "failed to apply,",
                    count.newFailed, "newly failed to apply,",
                    count.reconciled, "reconciled."].join(" "));
    Observers.notify("weave:engine:sync:applied", count, this.name);
  },

  // Indicates whether an incoming item should be deleted from the server at
  // the end of the sync. Engines can override this method to clean up records
  // that shouldn't be on the server.
  _shouldDeleteRemotely(remoteItem) {
    return false;
  },

  /**
   * Find a GUID of an item that is a duplicate of the incoming item but happens
   * to have a different GUID
   *
   * @return GUID of the similar item; falsy otherwise
   */
  async _findDupe(item) {
    // By default, assume there's no dupe items for the engine
  },

  /**
   * Called before a remote record is discarded due to failed reconciliation.
   * Used by bookmark sync to merge folder child orders.
   */
  beforeRecordDiscard(localRecord, remoteRecord, remoteIsNewer) {
  },

  // Called when the server has a record marked as deleted, but locally we've
  // changed it more recently than the deletion. If we return false, the
  // record will be deleted locally. If we return true, we'll reupload the
  // record to the server -- any extra work that's needed as part of this
  // process should be done at this point (such as mark the record's parent
  // for reuploading in the case of bookmarks).
  async _shouldReviveRemotelyDeletedRecord(remoteItem) {
    return true;
  },

  _deleteId(id) {
    this._tracker.removeChangedID(id);
    this._noteDeletedId(id);
  },

  // Marks an ID for deletion at the end of the sync.
  _noteDeletedId(id) {
    if (this._delete.ids == null)
      this._delete.ids = [id];
    else
      this._delete.ids.push(id);
  },

  async _switchItemToDupe(localDupeGUID, incomingItem) {
    // The local, duplicate ID is always deleted on the server.
    this._deleteId(localDupeGUID);

    // We unconditionally change the item's ID in case the engine knows of
    // an item but doesn't expose it through itemExists. If the API
    // contract were stronger, this could be changed.
    this._log.debug("Switching local ID to incoming: " + localDupeGUID + " -> " +
                    incomingItem.id);
    return this._store.changeItemID(localDupeGUID, incomingItem.id);
  },

  /**
   * Reconcile incoming record with local state.
   *
   * This function essentially determines whether to apply an incoming record.
   *
   * @param  item
   *         Record from server to be tested for application.
   * @return boolean
   *         Truthy if incoming record should be applied. False if not.
   */
  async _reconcile(item) {
    if (this._log.level <= Log.Level.Trace) {
      this._log.trace("Incoming: " + item);
    }

    // We start reconciling by collecting a bunch of state. We do this here
    // because some state may change during the course of this function and we
    // need to operate on the original values.
    let existsLocally   = await this._store.itemExists(item.id);
    let locallyModified = this._modified.has(item.id);

    // TODO Handle clock drift better. Tracked in bug 721181.
    let remoteAge = AsyncResource.serverTime - item.modified;
    let localAge  = locallyModified ?
      (Date.now() / 1000 - this._modified.getModifiedTimestamp(item.id)) : null;
    let remoteIsNewer = remoteAge < localAge;

    this._log.trace("Reconciling " + item.id + ". exists=" +
                    existsLocally + "; modified=" + locallyModified +
                    "; local age=" + localAge + "; incoming age=" +
                    remoteAge);

    // We handle deletions first so subsequent logic doesn't have to check
    // deleted flags.
    if (item.deleted) {
      // If the item doesn't exist locally, there is nothing for us to do. We
      // can't check for duplicates because the incoming record has no data
      // which can be used for duplicate detection.
      if (!existsLocally) {
        this._log.trace("Ignoring incoming item because it was deleted and " +
                        "the item does not exist locally.");
        return false;
      }

      // We decide whether to process the deletion by comparing the record
      // ages. If the item is not modified locally, the remote side wins and
      // the deletion is processed. If it is modified locally, we take the
      // newer record.
      if (!locallyModified) {
        this._log.trace("Applying incoming delete because the local item " +
                        "exists and isn't modified.");
        return true;
      }
      this._log.trace("Incoming record is deleted but we had local changes.");

      if (remoteIsNewer) {
        this._log.trace("Remote record is newer -- deleting local record.");
        return true;
      }
      // If the local record is newer, we defer to individual engines for
      // how to handle this. By default, we revive the record.
      let willRevive = await this._shouldReviveRemotelyDeletedRecord(item);
      this._log.trace("Local record is newer -- reviving? " + willRevive);

      return !willRevive;
    }

    // At this point the incoming record is not for a deletion and must have
    // data. If the incoming record does not exist locally, we check for a local
    // duplicate existing under a different ID. The default implementation of
    // _findDupe() is empty, so engines have to opt in to this functionality.
    //
    // If we find a duplicate, we change the local ID to the incoming ID and we
    // refresh the metadata collected above. See bug 710448 for the history
    // of this logic.
    if (!existsLocally) {
      let localDupeGUID = await this._findDupe(item);
      if (localDupeGUID) {
        this._log.trace("Local item " + localDupeGUID + " is a duplicate for " +
                        "incoming item " + item.id);

        // The current API contract does not mandate that the ID returned by
        // _findDupe() actually exists. Therefore, we have to perform this
        // check.
        existsLocally = await this._store.itemExists(localDupeGUID);

        // If the local item was modified, we carry its metadata forward so
        // appropriate reconciling can be performed.
        if (this._modified.has(localDupeGUID)) {
          locallyModified = true;
          localAge = this._tracker._now() - this._modified.getModifiedTimestamp(localDupeGUID);
          remoteIsNewer = remoteAge < localAge;

          this._modified.changeID(localDupeGUID, item.id);
        } else {
          locallyModified = false;
          localAge = null;
        }

        // Tell the engine to do whatever it needs to switch the items.
        await this._switchItemToDupe(localDupeGUID, item);

        this._log.debug("Local item after duplication: age=" + localAge +
                        "; modified=" + locallyModified + "; exists=" +
                        existsLocally);
      } else {
        this._log.trace("No duplicate found for incoming item: " + item.id);
      }
    }

    // At this point we've performed duplicate detection. But, nothing here
    // should depend on duplicate detection as the above should have updated
    // state seamlessly.

    if (!existsLocally) {
      // If the item doesn't exist locally and we have no local modifications
      // to the item (implying that it was not deleted), always apply the remote
      // item.
      if (!locallyModified) {
        this._log.trace("Applying incoming because local item does not exist " +
                        "and was not deleted.");
        return true;
      }

      // If the item was modified locally but isn't present, it must have
      // been deleted. If the incoming record is younger, we restore from
      // that record.
      if (remoteIsNewer) {
        this._log.trace("Applying incoming because local item was deleted " +
                        "before the incoming item was changed.");
        this._modified.delete(item.id);
        return true;
      }

      this._log.trace("Ignoring incoming item because the local item's " +
                      "deletion is newer.");
      return false;
    }

    // If the remote and local records are the same, there is nothing to be
    // done, so we don't do anything. In the ideal world, this logic wouldn't
    // be here and the engine would take a record and apply it. The reason we
    // want to defer this logic is because it would avoid a redundant and
    // possibly expensive dip into the storage layer to query item state.
    // This should get addressed in the async rewrite, so we ignore it for now.
    let localRecord = await this._createRecord(item.id);
    let recordsEqual = Utils.deepEquals(item.cleartext,
                                        localRecord.cleartext);

    // If the records are the same, we don't need to do anything. This does
    // potentially throw away a local modification time. But, if the records
    // are the same, does it matter?
    if (recordsEqual) {
      this._log.trace("Ignoring incoming item because the local item is " +
                      "identical.");

      this._modified.delete(item.id);
      return false;
    }

    // At this point the records are different.

    // If we have no local modifications, always take the server record.
    if (!locallyModified) {
      this._log.trace("Applying incoming record because no local conflicts.");
      return true;
    }

    // At this point, records are different and the local record is modified.
    // We resolve conflicts by record age, where the newest one wins. This does
    // result in data loss and should be handled by giving the engine an
    // opportunity to merge the records. Bug 720592 tracks this feature.
    this._log.warn("DATA LOSS: Both local and remote changes to record: " +
                   item.id);
    if (!remoteIsNewer) {
      this.beforeRecordDiscard(localRecord, item, remoteIsNewer);
    }
    return remoteIsNewer;
  },

  // Upload outgoing records.
  async _uploadOutgoing() {
    this._log.trace("Uploading local changes to server.");

    // collection we'll upload
    let up = new Collection(this.engineURL, null, this.service);
    let modifiedIDs = new Set(this._modified.ids());
    for (let id of this._needWeakUpload.keys()) {
      modifiedIDs.add(id);
    }
    let counts = { failed: 0, sent: 0 };
    if (modifiedIDs.size) {
      this._log.trace("Preparing " + modifiedIDs.size +
                      " outgoing records");

      counts.sent = modifiedIDs.size;

      let failed = [];
      let successful = [];
      let handleResponse = async (resp, batchOngoing = false) => {
        // Note: We don't want to update this.lastSync, or this._modified until
        // the batch is complete, however we want to remember success/failure
        // indicators for when that happens.
        if (!resp.success) {
          this._log.debug("Uploading records failed: " + resp);
          resp.failureCode = resp.status == 412 ? ENGINE_BATCH_INTERRUPTED : ENGINE_UPLOAD_FAIL;
          throw resp;
        }

        // Update server timestamp from the upload.
        failed = failed.concat(Object.keys(resp.obj.failed));
        successful = successful.concat(resp.obj.success);

        if (batchOngoing) {
          // Nothing to do yet
          return;
        }
        // Advance lastSync since we've finished the batch.
        let modified = resp.headers["x-weave-timestamp"];
        if (modified > this.lastSync) {
          this.lastSync = modified;
        }
        if (failed.length && this._log.level <= Log.Level.Debug) {
          this._log.debug("Records that will be uploaded again because "
                          + "the server couldn't store them: "
                          + failed.join(", "));
        }

        counts.failed += failed.length;

        for (let id of successful) {
          this._modified.delete(id);
        }

        await this._onRecordsWritten(successful, failed);

        // clear for next batch
        failed.length = 0;
        successful.length = 0;
      };

      let postQueue = up.newPostQueue(this._log, this.lastSync, handleResponse);

      for (let id of modifiedIDs) {
        let out;
        let ok = false;
        try {
          let { forceTombstone = false } = this._needWeakUpload.get(id) || {};
          if (forceTombstone) {
            out = await this._createTombstone(id);
          } else {
            out = await this._createRecord(id);
          }
          if (this._log.level <= Log.Level.Trace)
            this._log.trace("Outgoing: " + out);

          out.encrypt(this.service.collectionKeys.keyForCollection(this.name));
          let payloadLength = JSON.stringify(out.payload).length;
          if (payloadLength > this.maxRecordPayloadBytes) {
            if (this.allowSkippedRecord) {
              this._modified.delete(id); // Do not attempt to sync that record again
            }
            throw new Error(`Payload too big: ${payloadLength} bytes`);
          }
          ok = true;
        } catch (ex) {
          this._log.warn("Error creating record", ex);
          ++counts.failed;
          if (Async.isShutdownException(ex) || !this.allowSkippedRecord) {
            if (!this.allowSkippedRecord) {
              // Don't bother for shutdown errors
              Observers.notify("weave:engine:sync:uploaded", counts, this.name);
            }
            throw ex;
          }
        }
        if (ok) {
          let { enqueued, error } = await postQueue.enqueue(out);
          if (!enqueued) {
            ++counts.failed;
            if (!this.allowSkippedRecord) {
              Observers.notify("weave:engine:sync:uploaded", counts, this.name);
              throw error;
            }
            this._modified.delete(id);
            this._log.warn(`Failed to enqueue record "${id}" (skipping)`, error);
          }
        }
        await Async.promiseYield();
      }
      await postQueue.flush(true);
    }
    this._needWeakUpload.clear();

    if (counts.sent || counts.failed) {
      Observers.notify("weave:engine:sync:uploaded", counts, this.name);
    }
  },

  async _onRecordsWritten(succeeded, failed) {
    // Implement this method to take specific actions against successfully
    // uploaded records and failed records.
  },

  // Any cleanup necessary.
  // Save the current snapshot so as to calculate changes at next sync
  async _syncFinish() {
    this._log.trace("Finishing up sync");
    this._tracker.resetScore();

    let doDelete = async (key, val) => {
      let coll = new Collection(this.engineURL, this._recordObj, this.service);
      coll[key] = val;
      await coll.delete();
    };

    for (let [key, val] of Object.entries(this._delete)) {
      // Remove the key for future uses
      delete this._delete[key];

      // Send a simple delete for the property
      if (key != "ids" || val.length <= 100)
        await doDelete(key, val);
      else {
        // For many ids, split into chunks of at most 100
        while (val.length > 0) {
          await doDelete(key, val.slice(0, 100));
          val = val.slice(100);
        }
      }
    }
  },

  async _syncCleanup() {
    this._needWeakUpload.clear();
    if (!this._modified) {
      return;
    }

    try {
      // Mark failed WBOs as changed again so they are reuploaded next time.
      await this.trackRemainingChanges();
    } finally {
      this._modified.clear();
    }
  },

  async _sync() {
    try {
      await this._syncStartup();
      Observers.notify("weave:engine:sync:status", "process-incoming");
      await this._processIncoming();
      Observers.notify("weave:engine:sync:status", "upload-outgoing");
      await this._uploadOutgoing();
      await this._syncFinish();
    } finally {
      await this._syncCleanup();
    }
  },

  async canDecrypt() {
    // Report failure even if there's nothing to decrypt
    let canDecrypt = false;

    // Fetch the most recently uploaded record and try to decrypt it
    let test = new Collection(this.engineURL, this._recordObj, this.service);
    test.limit = 1;
    test.sort = "newest";
    test.full = true;

    let key = this.service.collectionKeys.keyForCollection(this.name);

    // Any failure fetching/decrypting will just result in false
    try {
      this._log.trace("Trying to decrypt a record from the server..");
      let json = (await test.get()).obj[0];
      let record = new this._recordObj();
      record.deserialize(json);
      record.decrypt(key);
      canDecrypt = true;
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.debug("Failed test decrypt", ex);
    }

    return canDecrypt;
  },

  async _resetClient() {
    this.resetLastSync();
    this.previousFailed = [];
    this.toFetch = [];
    this._needWeakUpload.clear();
  },

  async wipeServer() {
    let response = await this.service.resource(this.engineURL).delete();
    if (response.status != 200 && response.status != 404) {
      throw response;
    }
    await this._resetClient();
  },

  async removeClientData() {
    // Implement this method in engines that store client specific data
    // on the server.
  },

  /*
   * Decide on (and partially effect) an error-handling strategy.
   *
   * Asks the Service to respond to an HMAC error, which might result in keys
   * being downloaded. That call returns true if an action which might allow a
   * retry to occur.
   *
   * If `mayRetry` is truthy, and the Service suggests a retry,
   * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns
   * kRecoveryStrategy.error.
   *
   * Subclasses of SyncEngine can override this method to allow for different
   * behavior -- e.g., to delete and ignore erroneous entries.
   *
   * All return values will be part of the kRecoveryStrategy enumeration.
   */
  async handleHMACMismatch(item, mayRetry) {
    // By default we either try again, or bail out noisily.
    return ((await this.service.handleHMACEvent()) && mayRetry) ?
           SyncEngine.kRecoveryStrategy.retry :
           SyncEngine.kRecoveryStrategy.error;
  },

  /**
   * Returns a changeset containing all items in the store. The default
   * implementation returns a changeset with timestamps from long ago, to
   * ensure we always use the remote version if one exists.
   *
   * This function is only called for the first sync. Subsequent syncs call
   * `pullNewChanges`.
   *
   * @return A `Changeset` object.
   */
  async pullAllChanges() {
    let changes = {};
    let ids = await this._store.getAllIDs();
    for (let id in ids) {
      changes[id] = 0;
    }
    return changes;
  },

  /*
   * Returns a changeset containing entries for all currently tracked items.
   * The default implementation returns a changeset with timestamps indicating
   * when the item was added to the tracker.
   *
   * @return A `Changeset` object.
   */
  async pullNewChanges() {
    return this.getChangedIDs();
  },

  /**
   * Adds all remaining changeset entries back to the tracker, typically for
   * items that failed to upload. This method is called at the end of each sync.
   *
   */
  async trackRemainingChanges() {
    for (let [id, change] of this._modified.entries()) {
      this._tracker.addChangedID(id, change);
    }
  },
};

/**
 * A changeset is created for each sync in `Engine::get{Changed, All}IDs`,
 * and stores opaque change data for tracked IDs. The default implementation
 * only records timestamps, though engines can extend this to store additional
 * data for each entry.
 */
class Changeset {
  // Creates an empty changeset.
  constructor() {
    this.changes = {};
  }

  // Returns the last modified time, in seconds, for an entry in the changeset.
  // `id` is guaranteed to be in the set.
  getModifiedTimestamp(id) {
    return this.changes[id];
  }

  // Adds a change for a tracked ID to the changeset.
  set(id, change) {
    this.changes[id] = change;
  }

  // Adds multiple entries to the changeset, preserving existing entries.
  insert(changes) {
    Object.assign(this.changes, changes);
  }

  // Overwrites the existing set of tracked changes with new entries.
  replace(changes) {
    this.changes = changes;
  }

  // Indicates whether an entry is in the changeset.
  has(id) {
    return id in this.changes;
  }

  // Deletes an entry from the changeset. Used to clean up entries for
  // reconciled and successfully uploaded records.
  delete(id) {
    delete this.changes[id];
  }

  // Changes the ID of an entry in the changeset. Used when reconciling
  // duplicates that have local changes.
  changeID(oldID, newID) {
    this.changes[newID] = this.changes[oldID];
    delete this.changes[oldID];
  }

  // Returns an array of all tracked IDs in this changeset.
  ids() {
    return Object.keys(this.changes);
  }

  // Returns an array of `[id, change]` tuples. Used to repopulate the tracker
  // with entries for failed uploads at the end of a sync.
  entries() {
    return Object.entries(this.changes);
  }

  // Returns the number of entries in this changeset.
  count() {
    return this.ids().length;
  }

  // Clears the changeset.
  clear() {
    this.changes = {};
  }
}
PK
!<Tjjmodules/services-sync/keys.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [
  "BulkKeyBundle",
];

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://services-sync/constants.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");

/**
 * Represents a pair of keys.
 *
 * Each key stored in a key bundle is 256 bits. One key is used for symmetric
 * encryption. The other is used for HMAC.
 *
 * A KeyBundle by itself is just an anonymous pair of keys. Other types
 * deriving from this one add semantics, such as associated collections or
 * generating a key bundle via HKDF from another key.
 */
function KeyBundle() {
  this._encrypt = null;
  this._encryptB64 = null;
  this._hmac = null;
  this._hmacB64 = null;
  this._hmacObj = null;
  this._sha256HMACHasher = null;
}
KeyBundle.prototype = {
  _encrypt: null,
  _encryptB64: null,
  _hmac: null,
  _hmacB64: null,
  _hmacObj: null,
  _sha256HMACHasher: null,

  equals: function equals(bundle) {
    return bundle &&
           (bundle.hmacKey == this.hmacKey) &&
           (bundle.encryptionKey == this.encryptionKey);
  },

  /*
   * Accessors for the two keys.
   */
  get encryptionKey() {
    return this._encrypt;
  },

  set encryptionKey(value) {
    if (!value || typeof value != "string") {
      throw new Error("Encryption key can only be set to string values.");
    }

    if (value.length < 16) {
      throw new Error("Encryption key must be at least 128 bits long.");
    }

    this._encrypt = value;
    this._encryptB64 = btoa(value);
  },

  get encryptionKeyB64() {
    return this._encryptB64;
  },

  get hmacKey() {
    return this._hmac;
  },

  set hmacKey(value) {
    if (!value || typeof value != "string") {
      throw new Error("HMAC key can only be set to string values.");
    }

    if (value.length < 16) {
      throw new Error("HMAC key must be at least 128 bits long.");
    }

    this._hmac = value;
    this._hmacB64 = btoa(value);
    this._hmacObj = value ? Utils.makeHMACKey(value) : null;
    this._sha256HMACHasher = value ? Utils.makeHMACHasher(
      Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null;
  },

  get hmacKeyB64() {
    return this._hmacB64;
  },

  get hmacKeyObject() {
    return this._hmacObj;
  },

  get sha256HMACHasher() {
    return this._sha256HMACHasher;
  },

  /**
   * Populate this key pair with 2 new, randomly generated keys.
   */
  generateRandom: function generateRandom() {
    let generatedHMAC = Weave.Crypto.generateRandomKey();
    let generatedEncr = Weave.Crypto.generateRandomKey();
    this.keyPairB64 = [generatedEncr, generatedHMAC];
  },

};

/**
 * Represents a KeyBundle associated with a collection.
 *
 * This is just a KeyBundle with a collection attached.
 */
this.BulkKeyBundle = function BulkKeyBundle(collection) {
  let log = Log.repository.getLogger("Sync.BulkKeyBundle");
  log.info("BulkKeyBundle being created for " + collection);
  KeyBundle.call(this);

  this._collection = collection;
}

BulkKeyBundle.prototype = {
  __proto__: KeyBundle.prototype,

  get collection() {
    return this._collection;
  },

  /**
   * Obtain the key pair in this key bundle.
   *
   * The returned keys are represented as raw byte strings.
   */
  get keyPair() {
    return [this.encryptionKey, this.hmacKey];
  },

  set keyPair(value) {
    if (!Array.isArray(value) || value.length != 2) {
      throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys.");
    }

    this.encryptionKey = value[0];
    this.hmacKey       = value[1];
  },

  get keyPairB64() {
    return [this.encryptionKeyB64, this.hmacKeyB64];
  },

  set keyPairB64(value) {
    if (!Array.isArray(value) || value.length != 2) {
      throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " +
                      "keys.");
    }

    this.encryptionKey  = Utils.safeAtoB(value[0]);
    this.hmacKey        = Utils.safeAtoB(value[1]);
  },
};
PK
!<+modules/services-sync/main.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["Weave"];

const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

this.Weave = {};
Cu.import("resource://services-sync/constants.js", Weave);
var lazies = {
  "service.js":           ["Service"],
  "status.js":            ["Status"],
  "util.js":              ["Utils", "Svc"]
};

function lazyImport(module, dest, props) {
  function getter(prop) {
    return function() {
      let ns = {};
      Cu.import(module, ns);
      delete dest[prop];
      return dest[prop] = ns[prop];
    };
  }
  props.forEach(function(prop) { dest.__defineGetter__(prop, getter(prop)); });
}

for (let mod in lazies) {
  lazyImport("resource://services-sync/" + mod, Weave, lazies[mod]);
}

XPCOMUtils.defineLazyGetter(Weave, "Crypto", function() {
  let { WeaveCrypto } = Cu.import("resource://services-crypto/WeaveCrypto.js", {});
  return new WeaveCrypto();
});
PK
!<pA!modules/services-sync/policies.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "ErrorHandler",
  "SyncScheduler",
];

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/logmanager.js");
Cu.import("resource://services-common/async.js");

XPCOMUtils.defineLazyModuleGetter(this, "Status",
                                  "resource://services-sync/status.js");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "IdleService",
                                   "@mozilla.org/widget/idleservice;1",
                                   "nsIIdleService");

// Get the value for an interval that's stored in preferences. To save users
// from themselves (and us from them!) the minimum time they can specify
// is 60s.
function getThrottledIntervalPreference(prefName) {
  return Math.max(Svc.Prefs.get(prefName), 60) * 1000;
}

this.SyncScheduler = function SyncScheduler(service) {
  this.service = service;
  this.init();
}
SyncScheduler.prototype = {
  _log: Log.repository.getLogger("Sync.SyncScheduler"),

  _fatalLoginStatus: [LOGIN_FAILED_NO_PASSPHRASE,
                      LOGIN_FAILED_INVALID_PASSPHRASE,
                      LOGIN_FAILED_LOGIN_REJECTED],

  /**
   * The nsITimer object that schedules the next sync. See scheduleNextSync().
   */
  syncTimer: null,

  setDefaults: function setDefaults() {
    this._log.trace("Setting SyncScheduler policy values to defaults.");

    this.singleDeviceInterval = getThrottledIntervalPreference("scheduler.fxa.singleDeviceInterval");
    this.idleInterval         = getThrottledIntervalPreference("scheduler.idleInterval");
    this.activeInterval       = getThrottledIntervalPreference("scheduler.activeInterval");
    this.immediateInterval    = getThrottledIntervalPreference("scheduler.immediateInterval");
    this.eolInterval          = getThrottledIntervalPreference("scheduler.eolInterval");

    // A user is non-idle on startup by default.
    this.idle = false;

    this.hasIncomingItems = false;
    // This is the last number of clients we saw when previously updating the
    // client mode. If this != currentNumClients (obtained from prefs written
    // by the clients engine) then we need to transition to and from
    // single and multi-device mode.
    this.numClientsLastSync = 0;

    this._resyncs = 0;

    this.clearSyncTriggers();
  },

  // nextSync is in milliseconds, but prefs can't hold that much
  get nextSync() {
    return Svc.Prefs.get("nextSync", 0) * 1000;
  },
  set nextSync(value) {
    Svc.Prefs.set("nextSync", Math.floor(value / 1000));
  },

  get syncInterval() {
    return Svc.Prefs.get("syncInterval", this.singleDeviceInterval);
  },
  set syncInterval(value) {
    Svc.Prefs.set("syncInterval", value);
  },

  get syncThreshold() {
    return Svc.Prefs.get("syncThreshold", SINGLE_USER_THRESHOLD);
  },
  set syncThreshold(value) {
    Svc.Prefs.set("syncThreshold", value);
  },

  get globalScore() {
    return Svc.Prefs.get("globalScore", 0);
  },
  set globalScore(value) {
    Svc.Prefs.set("globalScore", value);
  },

  // The number of clients we have is maintained in preferences via the
  // clients engine, and only updated after a successsful sync.
  get numClients() {
    return Svc.Prefs.get("clients.devices.desktop", 0) +
           Svc.Prefs.get("clients.devices.mobile", 0);

  },
  set numClients(value) {
    throw new Error("Don't set numClients - the clients engine manages it.")
  },

  init: function init() {
    this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
    this.setDefaults();
    Svc.Obs.add("weave:engine:score:updated", this);
    Svc.Obs.add("network:offline-status-changed", this);
    Svc.Obs.add("weave:service:sync:start", this);
    Svc.Obs.add("weave:service:sync:finish", this);
    Svc.Obs.add("weave:engine:sync:finish", this);
    Svc.Obs.add("weave:engine:sync:error", this);
    Svc.Obs.add("weave:service:login:error", this);
    Svc.Obs.add("weave:service:logout:finish", this);
    Svc.Obs.add("weave:service:sync:error", this);
    Svc.Obs.add("weave:service:backoff:interval", this);
    Svc.Obs.add("weave:service:ready", this);
    Svc.Obs.add("weave:engine:sync:applied", this);
    Svc.Obs.add("weave:service:setup-complete", this);
    Svc.Obs.add("weave:service:start-over", this);
    Svc.Obs.add("FxA:hawk:backoff:interval", this);

    if (Status.checkSetup() == STATUS_OK) {
      Svc.Obs.add("wake_notification", this);
      IdleService.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
    }
  },

  // eslint-disable-next-line complexity
  observe: function observe(subject, topic, data) {
    this._log.trace("Handling " + topic);
    switch (topic) {
      case "weave:engine:score:updated":
        if (Status.login == LOGIN_SUCCEEDED) {
          Utils.namedTimer(this.calculateScore, SCORE_UPDATE_DELAY, this,
                           "_scoreTimer");
        }
        break;
      case "network:offline-status-changed":
        // Whether online or offline, we'll reschedule syncs
        this._log.trace("Network offline status change: " + data);
        this.checkSyncStatus();
        break;
      case "weave:service:sync:start":
        // Clear out any potentially pending syncs now that we're syncing
        this.clearSyncTriggers();

        // reset backoff info, if the server tells us to continue backing off,
        // we'll handle that later
        Status.resetBackoff();

        this.globalScore = 0;
        break;
      case "weave:service:sync:finish":
        this.nextSync = 0;
        this.adjustSyncInterval();

        if (Status.service == SYNC_FAILED_PARTIAL && this.requiresBackoff) {
          this.requiresBackoff = false;
          this.handleSyncError();
          return;
        }

        let sync_interval;
        this.updateGlobalScore();
        if (this.globalScore > 0) {
          // The global score should be 0 after a sync. If it's not, items were
          // changed during the last sync, and we should schedule an immediate
          // follow-up sync.
          this._resyncs++;
          if (this._resyncs <= this.maxResyncs) {
            sync_interval = 0;
          } else {
            this._log.warn(`Resync attempt ${this._resyncs} exceeded ` +
                           `maximum ${this.maxResyncs}`);
            Svc.Obs.notify("weave:service:resyncs-finished");
          }
        } else {
          this._resyncs = 0;
          Svc.Obs.notify("weave:service:resyncs-finished");
        }

        this._syncErrors = 0;
        if (Status.sync == NO_SYNC_NODE_FOUND) {
          // If we don't have a Sync node, override the interval, even if we've
          // scheduled a follow-up sync.
          this._log.trace("Scheduling a sync at interval NO_SYNC_NODE_FOUND.");
          sync_interval = NO_SYNC_NODE_INTERVAL;
        }
        this.scheduleNextSync(sync_interval);
        break;
      case "weave:engine:sync:finish":
        if (data == "clients") {
          // Update the client mode because it might change what we sync.
          this.updateClientMode();
        }
        break;
      case "weave:engine:sync:error":
        // `subject` is the exception thrown by an engine's sync() method.
        let exception = subject;
        if (exception.status >= 500 && exception.status <= 504) {
          this.requiresBackoff = true;
        }
        break;
      case "weave:service:login:error":
        this.clearSyncTriggers();

        if (Status.login == MASTER_PASSWORD_LOCKED) {
          // Try again later, just as if we threw an error... only without the
          // error count.
          this._log.debug("Couldn't log in: master password is locked.");
          this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
          this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
        } else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
          // Not a fatal login error, just an intermittent network or server
          // issue. Keep on syncin'.
          this.checkSyncStatus();
        }
        break;
      case "weave:service:logout:finish":
        // Start or cancel the sync timer depending on if
        // logged in or logged out
        this.checkSyncStatus();
        break;
      case "weave:service:sync:error":
        // There may be multiple clients but if the sync fails, client mode
        // should still be updated so that the next sync has a correct interval.
        this.updateClientMode();
        this.adjustSyncInterval();
        this.nextSync = 0;
        this.handleSyncError();
        break;
      case "FxA:hawk:backoff:interval":
      case "weave:service:backoff:interval":
        let requested_interval = subject * 1000;
        this._log.debug("Got backoff notification: " + requested_interval + "ms");
        // Leave up to 25% more time for the back off.
        let interval = requested_interval * (1 + Math.random() * 0.25);
        Status.backoffInterval = interval;
        Status.minimumNextSync = Date.now() + requested_interval;
        this._log.debug("Fuzzed minimum next sync: " + Status.minimumNextSync);
        break;
      case "weave:service:ready":
        // Applications can specify this preference if they want autoconnect
        // to happen after a fixed delay.
        let delay = Svc.Prefs.get("autoconnectDelay");
        if (delay) {
          this.delayedAutoConnect(delay);
        }
        break;
      case "weave:engine:sync:applied":
        let numItems = subject.succeeded;
        this._log.trace("Engine " + data + " successfully applied " + numItems +
                        " items.");
        if (numItems) {
          this.hasIncomingItems = true;
        }
        if (subject.newFailed) {
          this._log.error(`Engine ${data} found ${subject.newFailed} new records that failed to apply`);
        }
        break;
      case "weave:service:setup-complete":
         Services.prefs.savePrefFile(null);
         IdleService.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
         Svc.Obs.add("wake_notification", this);
         break;
      case "weave:service:start-over":
         this.setDefaults();
         try {
           IdleService.removeIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
         } catch (ex) {
           if (ex.result != Cr.NS_ERROR_FAILURE) {
             throw ex;
           }
           // In all likelihood we didn't have an idle observer registered yet.
           // It's all good.
         }
         break;
      case "idle":
        this._log.trace("We're idle.");
        this.idle = true;
        // Adjust the interval for future syncs. This won't actually have any
        // effect until the next pending sync (which will happen soon since we
        // were just active.)
        this.adjustSyncInterval();
        break;
      case "active":
        this._log.trace("Received notification that we're back from idle.");
        this.idle = false;
        Utils.namedTimer(function onBack() {
          if (this.idle) {
            this._log.trace("... and we're idle again. " +
                            "Ignoring spurious back notification.");
            return;
          }

          this._log.trace("Genuine return from idle. Syncing.");
          // Trigger a sync if we have multiple clients.
          if (this.numClients > 1) {
            this.scheduleNextSync(0);
          }
        }, IDLE_OBSERVER_BACK_DELAY, this, "idleDebouncerTimer");
        break;
      case "wake_notification":
        this._log.debug("Woke from sleep.");
        Utils.nextTick(() => {
          // Trigger a sync if we have multiple clients. We give it 5 seconds
          // incase the network is still in the process of coming back up.
          if (this.numClients > 1) {
            this._log.debug("More than 1 client. Will sync in 5s.");
            this.scheduleNextSync(5000);
          }
        });
        break;
    }
  },

  adjustSyncInterval: function adjustSyncInterval() {
    if (Status.eol) {
      this._log.debug("Server status is EOL; using eolInterval.");
      this.syncInterval = this.eolInterval;
      return;
    }

    if (this.numClients <= 1) {
      this._log.trace("Adjusting syncInterval to singleDeviceInterval.");
      this.syncInterval = this.singleDeviceInterval;
      return;
    }

    // Only MULTI_DEVICE clients will enter this if statement
    // since SINGLE_USER clients will be handled above.
    if (this.idle) {
      this._log.trace("Adjusting syncInterval to idleInterval.");
      this.syncInterval = this.idleInterval;
      return;
    }

    if (this.hasIncomingItems) {
      this._log.trace("Adjusting syncInterval to immediateInterval.");
      this.hasIncomingItems = false;
      this.syncInterval = this.immediateInterval;
    } else {
      this._log.trace("Adjusting syncInterval to activeInterval.");
      this.syncInterval = this.activeInterval;
    }
  },

  updateGlobalScore() {
    let engines = [this.service.clientsEngine].concat(this.service.engineManager.getEnabled());
    for (let i = 0;i < engines.length;i++) {
      this._log.trace(engines[i].name + ": score: " + engines[i].score);
      this.globalScore += engines[i].score;
      engines[i]._tracker.resetScore();
    }

    this._log.trace("Global score updated: " + this.globalScore);
  },

  calculateScore() {
    this.updateGlobalScore();
    this.checkSyncStatus();
  },

  /**
   * Query the number of known clients to figure out what mode to be in
   */
  updateClientMode: function updateClientMode() {
    // Nothing to do if it's the same amount
    let numClients = this.numClients;
    if (numClients == this.numClientsLastSync)
      return;

    this._log.debug(`Client count: ${this.numClientsLastSync} -> ${numClients}`);
    this.numClientsLastSync = numClients;

    if (numClients <= 1) {
      this._log.trace("Adjusting syncThreshold to SINGLE_USER_THRESHOLD");
      this.syncThreshold = SINGLE_USER_THRESHOLD;
    } else {
      this._log.trace("Adjusting syncThreshold to MULTI_DEVICE_THRESHOLD");
      this.syncThreshold = MULTI_DEVICE_THRESHOLD;
    }
    this.adjustSyncInterval();
  },

  /**
   * Check if we should be syncing and schedule the next sync, if it's not scheduled
   */
  checkSyncStatus: function checkSyncStatus() {
    // Should we be syncing now, if not, cancel any sync timers and return
    // if we're in backoff, we'll schedule the next sync.
    let ignore = [kSyncBackoffNotMet, kSyncMasterPasswordLocked];
    let skip = this.service._checkSync(ignore);
    this._log.trace("_checkSync returned \"" + skip + "\".");
    if (skip) {
      this.clearSyncTriggers();
      return;
    }

    // Only set the wait time to 0 if we need to sync right away
    let wait;
    if (this.globalScore > this.syncThreshold) {
      this._log.debug("Global Score threshold hit, triggering sync.");
      wait = 0;
    }
    this.scheduleNextSync(wait);
  },

  /**
   * Call sync() if Master Password is not locked.
   *
   * Otherwise, reschedule a sync for later.
   */
  syncIfMPUnlocked: function syncIfMPUnlocked() {
    // No point if we got kicked out by the master password dialog.
    if (Status.login == MASTER_PASSWORD_LOCKED &&
        Utils.mpLocked()) {
      this._log.debug("Not initiating sync: Login status is " + Status.login);

      // If we're not syncing now, we need to schedule the next one.
      this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
      this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
      return;
    }

    if (!Async.isAppReady()) {
      this._log.debug("Not initiating sync: app is shutting down");
      return;
    }
    Utils.nextTick(this.service.sync, this.service);
  },

  /**
   * Set a timer for the next sync
   */
  scheduleNextSync: function scheduleNextSync(interval) {
    // If no interval was specified, use the current sync interval.
    if (interval == null) {
      interval = this.syncInterval;
    }

    // Ensure the interval is set to no less than the backoff.
    if (Status.backoffInterval && interval < Status.backoffInterval) {
      this._log.trace("Requested interval " + interval +
                      " ms is smaller than the backoff interval. " +
                      "Using backoff interval " +
                      Status.backoffInterval + " ms instead.");
      interval = Status.backoffInterval;
    }

    if (this.nextSync != 0) {
      // There's already a sync scheduled. Don't reschedule if there's already
      // a timer scheduled for sooner than requested.
      let currentInterval = this.nextSync - Date.now();
      this._log.trace("There's already a sync scheduled in " +
                      currentInterval + " ms.");
      if (currentInterval < interval && this.syncTimer) {
        this._log.trace("Ignoring scheduling request for next sync in " +
                        interval + " ms.");
        return;
      }
    }

    // Start the sync right away if we're already late.
    if (interval <= 0) {
      this._log.trace("Requested sync should happen right away.");
      this.syncIfMPUnlocked();
      return;
    }

    this._log.debug("Next sync in " + interval + " ms.");
    Utils.namedTimer(this.syncIfMPUnlocked, interval, this, "syncTimer");

    // Save the next sync time in-case sync is disabled (logout/offline/etc.)
    this.nextSync = Date.now() + interval;
  },


  /**
   * Incorporates the backoff/retry logic used in error handling and elective
   * non-syncing.
   */
  scheduleAtInterval: function scheduleAtInterval(minimumInterval) {
    let interval = Utils.calculateBackoff(this._syncErrors,
                                          MINIMUM_BACKOFF_INTERVAL,
                                          Status.backoffInterval);
    if (minimumInterval) {
      interval = Math.max(minimumInterval, interval);
    }

    this._log.debug("Starting client-initiated backoff. Next sync in " +
                    interval + " ms.");
    this.scheduleNextSync(interval);
  },

 /**
  * Automatically start syncing after the given delay (in seconds).
  *
  * Applications can define the `services.sync.autoconnectDelay` preference
  * to have this called automatically during start-up with the pref value as
  * the argument. Alternatively, they can call it themselves to control when
  * Sync should first start to sync.
  */
  delayedAutoConnect: function delayedAutoConnect(delay) {
    if (this.service._checkSetup() == STATUS_OK) {
      Utils.namedTimer(this.autoConnect, delay * 1000, this, "_autoTimer");
    }
  },

  autoConnect: function autoConnect() {
    if (this.service._checkSetup() == STATUS_OK && !this.service._checkSync()) {
      // Schedule a sync based on when a previous sync was scheduled.
      // scheduleNextSync() will do the right thing if that time lies in
      // the past.
      this.scheduleNextSync(this.nextSync - Date.now());
    }

    // Once autoConnect is called we no longer need _autoTimer.
    if (this._autoTimer) {
      this._autoTimer.clear();
    }
  },

  _syncErrors: 0,
  /**
   * Deal with sync errors appropriately
   */
  handleSyncError: function handleSyncError() {
    this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
    this._syncErrors++;

    // Do nothing on the first couple of failures, if we're not in
    // backoff due to 5xx errors.
    if (!Status.enforceBackoff) {
      if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) {
        this.scheduleNextSync();
        return;
      }
      this._log.debug("Sync error count has exceeded " +
                      MAX_ERROR_COUNT_BEFORE_BACKOFF + "; enforcing backoff.");
      Status.enforceBackoff = true;
    }

    this.scheduleAtInterval();
  },


  /**
   * Remove any timers/observers that might trigger a sync
   */
  clearSyncTriggers: function clearSyncTriggers() {
    this._log.debug("Clearing sync triggers and the global score.");
    this.globalScore = this.nextSync = 0;

    // Clear out any scheduled syncs
    if (this.syncTimer)
      this.syncTimer.clear();
  },

  get maxResyncs() {
    return Svc.Prefs.get("maxResyncs", 0);
  },
};

this.ErrorHandler = function ErrorHandler(service) {
  this.service = service;
  this.init();
}
ErrorHandler.prototype = {
  MINIMUM_ALERT_INTERVAL_MSEC: 604800000,   // One week.

  /**
   * Flag that turns on error reporting for all errors, incl. network errors.
   */
  dontIgnoreErrors: false,

  /**
   * Flag that indicates if we have already reported a prolonged failure.
   * Once set, we don't report it again, meaning this error is only reported
   * one per run.
   */
  didReportProlongedError: false,

  init: function init() {
    Svc.Obs.add("weave:engine:sync:applied", this);
    Svc.Obs.add("weave:engine:sync:error", this);
    Svc.Obs.add("weave:service:login:error", this);
    Svc.Obs.add("weave:service:sync:error", this);
    Svc.Obs.add("weave:service:sync:finish", this);
    Svc.Obs.add("weave:service:start-over:finish", this);

    this.initLogs();
  },

  initLogs: function initLogs() {
    this._log = Log.repository.getLogger("Sync.ErrorHandler");
    this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];

    let root = Log.repository.getLogger("Sync");
    root.level = Log.Level[Svc.Prefs.get("log.rootLogger")];

    let logs = ["Sync", "FirefoxAccounts", "Hawk", "Common.TokenServerClient",
                "Sync.SyncMigration", "browserwindow.syncui",
                "Services.Common.RESTRequest", "Services.Common.RESTRequest",
                "BookmarkSyncUtils",
                "addons.xpi",
               ];

    this._logManager = new LogManager(Svc.Prefs, logs, "sync");
  },

  observe: function observe(subject, topic, data) {
    this._log.trace("Handling " + topic);
    switch (topic) {
      case "weave:engine:sync:applied":
        if (subject.newFailed) {
          // An engine isn't able to apply one or more incoming records.
          // We don't fail hard on this, but it usually indicates a bug,
          // so for now treat it as sync error (c.f. Service._syncEngine())
          Status.engines = [data, ENGINE_APPLY_FAIL];
          this._log.debug(data + " failed to apply some records.");
        }
        break;
      case "weave:engine:sync:error": {
        let exception = subject;  // exception thrown by engine's sync() method
        let engine_name = data;   // engine name that threw the exception

        this.checkServerError(exception);

        Status.engines = [engine_name, exception.failureCode || ENGINE_UNKNOWN_FAIL];
        if (Async.isShutdownException(exception)) {
          this._log.debug(engine_name + " was interrupted due to the application shutting down");
        } else {
          this._log.debug(engine_name + " failed", exception);
          Services.telemetry.getKeyedHistogramById("WEAVE_ENGINE_SYNC_ERRORS")
                            .add(engine_name);
        }
        break;
      }
      case "weave:service:login:error":
        this._log.error("Sync encountered a login error");
        this.resetFileLog();

        if (this.shouldReportError()) {
          this.notifyOnNextTick("weave:ui:login:error");
        } else {
          this.notifyOnNextTick("weave:ui:clear-error");
        }

        this.dontIgnoreErrors = false;
        break;
      case "weave:service:sync:error": {
        if (Status.sync == CREDENTIALS_CHANGED) {
          this.service.logout();
        }

        let exception = subject;
        if (Async.isShutdownException(exception)) {
          // If we are shutting down we just log the fact, attempt to flush
          // the log file and get out of here!
          this._log.error("Sync was interrupted due to the application shutting down");
          this.resetFileLog();
          break;
        }

        // Not a shutdown related exception...
        this._log.error("Sync encountered an error", exception);
        this.resetFileLog();

        if (this.shouldReportError()) {
          this.notifyOnNextTick("weave:ui:sync:error");
        } else {
          this.notifyOnNextTick("weave:ui:sync:finish");
        }

        this.dontIgnoreErrors = false;
        break;
      }
      case "weave:service:sync:finish":
        this._log.trace("Status.service is " + Status.service);

        // Check both of these status codes: in the event of a failure in one
        // engine, Status.service will be SYNC_FAILED_PARTIAL despite
        // Status.sync being SYNC_SUCCEEDED.
        // *facepalm*
        if (Status.sync == SYNC_SUCCEEDED &&
            Status.service == STATUS_OK) {
          // Great. Let's clear our mid-sync 401 note.
          this._log.trace("Clearing lastSyncReassigned.");
          Svc.Prefs.reset("lastSyncReassigned");
        }

        if (Status.service == SYNC_FAILED_PARTIAL) {
          this._log.error("Some engines did not sync correctly.");
          this.resetFileLog();

          if (this.shouldReportError()) {
            this.dontIgnoreErrors = false;
            this.notifyOnNextTick("weave:ui:sync:error");
            break;
          }
        } else {
          this.resetFileLog();
        }
        this.dontIgnoreErrors = false;
        this.notifyOnNextTick("weave:ui:sync:finish");
        break;
      case "weave:service:start-over:finish":
        // ensure we capture any logs between the last sync and the reset completing.
        this.resetFileLog();
        break;
    }
  },

  notifyOnNextTick: function notifyOnNextTick(topic) {
    Utils.nextTick(function() {
      this._log.trace("Notifying " + topic +
                      ". Status.login is " + Status.login +
                      ". Status.sync is " + Status.sync);
      Svc.Obs.notify(topic);
    }, this);
  },

  /**
   * Trigger a sync and don't muffle any errors, particularly network errors.
   */
  syncAndReportErrors: function syncAndReportErrors() {
    this._log.debug("Beginning user-triggered sync.");

    this.dontIgnoreErrors = true;
    Utils.nextTick(this.service.sync, this.service);
  },

  async _dumpAddons() {
    // Just dump the items that sync may be concerned with. Specifically,
    // active extensions that are not hidden.
    let addons = [];
    try {
      addons = await AddonManager.getAddonsByTypes(["extension"]);
    } catch (e) {
      this._log.warn("Failed to dump addons", e)
    }

    let relevantAddons = addons.filter(x => x.isActive && !x.hidden);
    this._log.debug("Addons installed", relevantAddons.length);
    for (let addon of relevantAddons) {
      this._log.debug(" - ${name}, version ${version}, id ${id}", addon);
    }
  },

  /**
   * Generate a log file for the sync that just completed
   * and refresh the input & output streams.
   */
  resetFileLog: function resetFileLog() {
    let onComplete = logType => {
      Svc.Obs.notify("weave:service:reset-file-log");
      this._log.trace("Notified: " + Date.now());
      if (logType == this._logManager.ERROR_LOG_WRITTEN) {
        Cu.reportError("Sync encountered an error - see about:sync-log for the log file.");
      }
    };

    // If we're writing an error log, dump extensions that may be causing problems.
    let beforeResetLog;
    if (this._logManager.sawError) {
      beforeResetLog = this._dumpAddons();
    } else {
      beforeResetLog = Promise.resolve();
    }
    // Note we do not return the promise here - the caller doesn't need to wait
    // for this to complete.
    beforeResetLog
      .then(() => this._logManager.resetFileLog())
      .then(onComplete, onComplete);
  },

  /**
   * Translates server error codes to meaningful strings.
   *
   * @param code
   *        server error code as an integer
   */
  errorStr: function errorStr(code) {
    switch (code.toString()) {
    case "1":
      return "illegal-method";
    case "2":
      return "invalid-captcha";
    case "3":
      return "invalid-username";
    case "4":
      return "cannot-overwrite-resource";
    case "5":
      return "userid-mismatch";
    case "6":
      return "json-parse-failure";
    case "7":
      return "invalid-password";
    case "8":
      return "invalid-record";
    case "9":
      return "weak-password";
    default:
      return "generic-server-error";
    }
  },

  // A function to indicate if Sync errors should be "reported" - which in this
  // context really means "should be notify observers of an error" - but note
  // that since bug 1180587, no one is going to surface an error to the user.
  shouldReportError: function shouldReportError() {
    if (Status.login == MASTER_PASSWORD_LOCKED) {
      this._log.trace("shouldReportError: false (master password locked).");
      return false;
    }

    if (this.dontIgnoreErrors) {
      return true;
    }

    if (Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
      // An explicit LOGIN_REJECTED state is always reported (bug 1081158)
      this._log.trace("shouldReportError: true (login was rejected)");
      return true;
    }

    let lastSync = Svc.Prefs.get("lastSync");
    if (lastSync && ((Date.now() - Date.parse(lastSync)) >
        Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
      Status.sync = PROLONGED_SYNC_FAILURE;
      if (this.didReportProlongedError) {
        this._log.trace("shouldReportError: false (prolonged sync failure, but" +
                        " we've already reported it).");
        return false;
      }
      this._log.trace("shouldReportError: true (first prolonged sync failure).");
      this.didReportProlongedError = true;
      return true;
    }

    // We got a 401 mid-sync. Wait for the next sync before actually handling
    // an error. This assumes that we'll get a 401 again on a login fetch in
    // order to report the error.
    if (!this.service.clusterURL) {
      this._log.trace("shouldReportError: false (no cluster URL; " +
                      "possible node reassignment).");
      return false;
    }


    let result = ([Status.login, Status.sync].indexOf(SERVER_MAINTENANCE) == -1 &&
                  [Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
    this._log.trace("shouldReportError: ${result} due to login=${login}, sync=${sync}",
                    {result, login: Status.login, sync: Status.sync});
    return result;
  },

  get currentAlertMode() {
    return Svc.Prefs.get("errorhandler.alert.mode");
  },

  set currentAlertMode(str) {
    return Svc.Prefs.set("errorhandler.alert.mode", str);
  },

  get earliestNextAlert() {
    return Svc.Prefs.get("errorhandler.alert.earliestNext", 0) * 1000;
  },

  set earliestNextAlert(msec) {
    return Svc.Prefs.set("errorhandler.alert.earliestNext", msec / 1000);
  },

  clearServerAlerts() {
    // If we have any outstanding alerts, apparently they're no longer relevant.
    Svc.Prefs.resetBranch("errorhandler.alert");
  },

  /**
   * X-Weave-Alert headers can include a JSON object:
   *
   *   {
   *    "code":    // One of "hard-eol", "soft-eol".
   *    "url":     // For "Learn more" link.
   *    "message": // Logged in Sync logs.
   *   }
   */
  handleServerAlert(xwa) {
    if (!xwa.code) {
      this._log.warn("Got structured X-Weave-Alert, but no alert code.");
      return;
    }

    switch (xwa.code) {
      // Gently and occasionally notify the user that this service will be
      // shutting down.
      case "soft-eol":
        // Fall through.

      // Tell the user that this service has shut down, and drop our syncing
      // frequency dramatically.
      case "hard-eol":
        // Note that both of these alerts should be subservient to future "sign
        // in with your Firefox Account" storage alerts.
        if ((this.currentAlertMode != xwa.code) ||
            (this.earliestNextAlert < Date.now())) {
          Utils.nextTick(function() {
            Svc.Obs.notify("weave:eol", xwa);
          }, this);
          this._log.error("X-Weave-Alert: " + xwa.code + ": " + xwa.message);
          this.earliestNextAlert = Date.now() + this.MINIMUM_ALERT_INTERVAL_MSEC;
          this.currentAlertMode = xwa.code;
        }
        break;
      default:
        this._log.debug("Got unexpected X-Weave-Alert code: " + xwa.code);
    }
  },

  /**
   * Handle HTTP response results or exceptions and set the appropriate
   * Status.* bits.
   *
   * This method also looks for "side-channel" warnings.
   */
  checkServerError(resp) {
    switch (resp.status) {
      case 200:
      case 404:
      case 513:
        let xwa = resp.headers["x-weave-alert"];

        // Only process machine-readable alerts.
        if (!xwa || !xwa.startsWith("{")) {
          this.clearServerAlerts();
          return;
        }

        try {
          xwa = JSON.parse(xwa);
        } catch (ex) {
          this._log.warn("Malformed X-Weave-Alert from server: " + xwa);
          return;
        }

        this.handleServerAlert(xwa);
        break;

      case 400:
        if (resp == RESPONSE_OVER_QUOTA) {
          Status.sync = OVER_QUOTA;
        }
        break;

      case 401:
        this.service.logout();
        this._log.info("Got 401 response; resetting clusterURL.");
        this.service.clusterURL = null;

        let delay = 0;
        if (Svc.Prefs.get("lastSyncReassigned")) {
          // We got a 401 in the middle of the previous sync, and we just got
          // another. Login must have succeeded in order for us to get here, so
          // the password should be correct.
          // This is likely to be an intermittent server issue, so back off and
          // give it time to recover.
          this._log.warn("Last sync also failed for 401. Delaying next sync.");
          delay = MINIMUM_BACKOFF_INTERVAL;
        } else {
          this._log.debug("New mid-sync 401 failure. Making a note.");
          Svc.Prefs.set("lastSyncReassigned", true);
        }
        this._log.info("Attempting to schedule another sync.");
        this.service.scheduler.scheduleNextSync(delay);
        break;

      case 500:
      case 502:
      case 503:
      case 504:
        Status.enforceBackoff = true;
        if (resp.status == 503 && resp.headers["retry-after"]) {
          let retryAfter = resp.headers["retry-after"];
          this._log.debug("Got Retry-After: " + retryAfter);
          if (this.service.isLoggedIn) {
            Status.sync = SERVER_MAINTENANCE;
          } else {
            Status.login = SERVER_MAINTENANCE;
          }
          Svc.Obs.notify("weave:service:backoff:interval",
                         parseInt(retryAfter, 10));
        }
        break;
    }

    switch (resp.result) {
      case Cr.NS_ERROR_UNKNOWN_HOST:
      case Cr.NS_ERROR_CONNECTION_REFUSED:
      case Cr.NS_ERROR_NET_TIMEOUT:
      case Cr.NS_ERROR_NET_RESET:
      case Cr.NS_ERROR_NET_INTERRUPT:
      case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
        // The constant says it's about login, but in fact it just
        // indicates general network error.
        if (this.service.isLoggedIn) {
          Status.sync = LOGIN_FAILED_NETWORK_ERROR;
        } else {
          Status.login = LOGIN_FAILED_NETWORK_ERROR;
        }
        break;
    }
  },
};
PK
!<XJ<modules/services-sync/record.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "WBORecord",
  "RecordManager",
  "CryptoWrapper",
  "CollectionKeyManager",
  "Collection",
];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

const CRYPTO_COLLECTION = "crypto";
const KEYS_WBO = "keys";

Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/keys.js");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/async.js");

this.WBORecord = function WBORecord(collection, id) {
  this.data = {};
  this.payload = {};
  this.collection = collection;      // Optional.
  this.id = id;                      // Optional.
}
WBORecord.prototype = {
  _logName: "Sync.Record.WBO",

  get sortindex() {
    if (this.data.sortindex)
      return this.data.sortindex;
    return 0;
  },

  // Get thyself from your URI, then deserialize.
  // Set thine 'response' field.
  async fetch(resource) {
    if (!(resource instanceof Resource)) {
      throw new Error("First argument must be a Resource instance.");
    }

    let r = await resource.get();
    if (r.success) {
      this.deserialize(r);   // Warning! Muffles exceptions!
    }
    this.response = r;
    return this;
  },

  upload(resource) {
    if (!(resource instanceof Resource)) {
      throw new Error("First argument must be a Resource instance.");
    }

    return resource.put(this);
  },

  // Take a base URI string, with trailing slash, and return the URI of this
  // WBO based on collection and ID.
  uri(base) {
    if (this.collection && this.id) {
      let url = Utils.makeURI(base + this.collection + "/" + this.id);
      url.QueryInterface(Ci.nsIURL);
      return url;
    }
    return null;
  },

  deserialize: function deserialize(json) {
    this.data = json.constructor.toString() == String ? JSON.parse(json) : json;

    try {
      // The payload is likely to be JSON, but if not, keep it as a string
      this.payload = JSON.parse(this.payload);
    } catch (ex) {}
  },

  toJSON: function toJSON() {
    // Copy fields from data to be stringified, making sure payload is a string
    let obj = {};
    for (let [key, val] of Object.entries(this.data))
      obj[key] = key == "payload" ? JSON.stringify(val) : val;
    if (this.ttl)
      obj.ttl = this.ttl;
    return obj;
  },

  toString: function toString() {
    return "{ " +
      "id: " + this.id + "  " +
      "index: " + this.sortindex + "  " +
      "modified: " + this.modified + "  " +
      "ttl: " + this.ttl + "  " +
      "payload: " + JSON.stringify(this.payload) +
      " }";
  }
};

Utils.deferGetSet(WBORecord, "data", ["id", "modified", "sortindex", "payload"]);

this.CryptoWrapper = function CryptoWrapper(collection, id) {
  this.cleartext = {};
  WBORecord.call(this, collection, id);
  this.ciphertext = null;
  this.id = id;
}
CryptoWrapper.prototype = {
  __proto__: WBORecord.prototype,
  _logName: "Sync.Record.CryptoWrapper",

  ciphertextHMAC: function ciphertextHMAC(keyBundle) {
    let hasher = keyBundle.sha256HMACHasher;
    if (!hasher) {
      throw new Error("Cannot compute HMAC without an HMAC key.");
    }

    return Utils.bytesAsHex(Utils.digestUTF8(this.ciphertext, hasher));
  },

  /*
   * Don't directly use the sync key. Instead, grab a key for this
   * collection, which is decrypted with the sync key.
   *
   * Cache those keys; invalidate the cache if the time on the keys collection
   * changes, or other auth events occur.
   *
   * Optional key bundle overrides the collection key lookup.
   */
  encrypt: function encrypt(keyBundle) {
    if (!keyBundle) {
      throw new Error("A key bundle must be supplied to encrypt.");
    }

    this.IV = Weave.Crypto.generateRandomIV();
    this.ciphertext = Weave.Crypto.encrypt(JSON.stringify(this.cleartext),
                                           keyBundle.encryptionKeyB64, this.IV);
    this.hmac = this.ciphertextHMAC(keyBundle);
    this.cleartext = null;
  },

  // Optional key bundle.
  decrypt: function decrypt(keyBundle) {
    if (!this.ciphertext) {
      throw new Error("No ciphertext: nothing to decrypt?");
    }

    if (!keyBundle) {
      throw new Error("A key bundle must be supplied to decrypt.");
    }

    // Authenticate the encrypted blob with the expected HMAC
    let computedHMAC = this.ciphertextHMAC(keyBundle);

    if (computedHMAC != this.hmac) {
      Utils.throwHMACMismatch(this.hmac, computedHMAC);
    }

    // Handle invalid data here. Elsewhere we assume that cleartext is an object.
    let cleartext = Weave.Crypto.decrypt(this.ciphertext,
                                         keyBundle.encryptionKeyB64, this.IV);
    let json_result = JSON.parse(cleartext);

    if (json_result && (json_result instanceof Object)) {
      this.cleartext = json_result;
      this.ciphertext = null;
    } else {
      throw new Error(
          `Decryption failed: result is <${json_result}>, not an object.`);
    }

    // Verify that the encrypted id matches the requested record's id.
    if (this.cleartext.id != this.id)
      throw new Error(
          `Record id mismatch: ${this.cleartext.id} != ${this.id}`);

    return this.cleartext;
  },

  cleartextToString() {
    return JSON.stringify(this.cleartext);
  },

  toString: function toString() {
    let payload = this.deleted ? "DELETED" : this.cleartextToString();

    return "{ " +
      "id: " + this.id + "  " +
      "index: " + this.sortindex + "  " +
      "modified: " + this.modified + "  " +
      "ttl: " + this.ttl + "  " +
      "payload: " + payload + "  " +
      "collection: " + (this.collection || "undefined") +
      " }";
  },

  // The custom setter below masks the parent's getter, so explicitly call it :(
  get id() {
    return WBORecord.prototype.__lookupGetter__("id").call(this);
  },

  // Keep both plaintext and encrypted versions of the id to verify integrity
  set id(val) {
    WBORecord.prototype.__lookupSetter__("id").call(this, val);
    return this.cleartext.id = val;
  },
};

Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]);
Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");

/**
 * An interface and caching layer for records.
 */
this.RecordManager = function RecordManager(service) {
  this.service = service;

  this._log = Log.repository.getLogger(this._logName);
  this._records = {};
}
RecordManager.prototype = {
  _recordType: CryptoWrapper,
  _logName: "Sync.RecordManager",

  async import(url) {
    this._log.trace("Importing record: " + (url.spec ? url.spec : url));
    try {
      // Clear out the last response with empty object if GET fails
      this.response = {};
      this.response = await this.service.resource(url).get();

      // Don't parse and save the record on failure
      if (!this.response.success)
        return null;

      let record = new this._recordType(url);
      record.deserialize(this.response);

      return this.set(url, record);
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.debug("Failed to import record", ex);
      return null;
    }
  },

  get(url) {
    // Use a url string as the key to the hash
    let spec = url.spec ? url.spec : url;
    if (spec in this._records)
      return Promise.resolve(this._records[spec]);
    return this.import(url);
  },

  set: function RecordMgr_set(url, record) {
    let spec = url.spec ? url.spec : url;
    return this._records[spec] = record;
  },

  contains: function RecordMgr_contains(url) {
    if ((url.spec || url) in this._records)
      return true;
    return false;
  },

  clearCache: function recordMgr_clearCache() {
    this._records = {};
  },

  del: function RecordMgr_del(url) {
    delete this._records[url];
  }
};

/**
 * Keeps track of mappings between collection names ('tabs') and KeyBundles.
 *
 * You can update this thing simply by giving it /info/collections. It'll
 * use the last modified time to bring itself up to date.
 */
this.CollectionKeyManager = function CollectionKeyManager(lastModified, default_, collections) {
  this.lastModified = lastModified || 0;
  this._default = default_ || null;
  this._collections = collections || {};

  this._log = Log.repository.getLogger("Sync.CollectionKeyManager");
}

// TODO: persist this locally as an Identity. Bug 610913.
// Note that the last modified time needs to be preserved.
CollectionKeyManager.prototype = {

  /**
   * Generate a new CollectionKeyManager that has the same attributes
   * as this one.
   */
  clone() {
    const newCollections = {};
    for (let c in this._collections) {
      newCollections[c] = this._collections[c];
    }

    return new CollectionKeyManager(this.lastModified, this._default, newCollections);
  },

  // Return information about old vs new keys:
  // * same: true if two collections are equal
  // * changed: an array of collection names that changed.
  _compareKeyBundleCollections: function _compareKeyBundleCollections(m1, m2) {
    let changed = [];

    function process(m1, m2) {
      for (let k1 in m1) {
        let v1 = m1[k1];
        let v2 = m2[k1];
        if (!(v1 && v2 && v1.equals(v2)))
          changed.push(k1);
      }
    }

    // Diffs both ways.
    process(m1, m2);
    process(m2, m1);

    // Return a sorted, unique array.
    changed.sort();
    let last;
    changed = changed.filter(x => (x != last) && (last = x));
    return {same: changed.length == 0,
            changed};
  },

  get isClear() {
   return !this._default;
  },

  clear: function clear() {
    this._log.info("Clearing collection keys...");
    this.lastModified = 0;
    this._collections = {};
    this._default = null;
  },

  keyForCollection(collection) {
    if (collection && this._collections[collection])
      return this._collections[collection];

    return this._default;
  },

  /**
   * If `collections` (an array of strings) is provided, iterate
   * over it and generate random keys for each collection.
   * Create a WBO for the given data.
   */
  _makeWBO(collections, defaultBundle) {
    let wbo = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    let c = {};
    for (let k in collections) {
      c[k] = collections[k].keyPairB64;
    }
    wbo.cleartext = {
      "default":     defaultBundle ? defaultBundle.keyPairB64 : null,
      "collections": c,
      "collection":  CRYPTO_COLLECTION,
      "id":          KEYS_WBO
    };
    return wbo;
  },

  /**
   * Create a WBO for the current keys.
   */
  asWBO(collection, id) {
    return this._makeWBO(this._collections, this._default);
  },

  /**
   * Compute a new default key, and new keys for any specified collections.
   */
  newKeys(collections) {
    let newDefaultKeyBundle = this.newDefaultKeyBundle();

    let newColls = {};
    if (collections) {
      collections.forEach(function(c) {
        let b = new BulkKeyBundle(c);
        b.generateRandom();
        newColls[c] = b;
      });
    }
    return [newDefaultKeyBundle, newColls];
  },

  /**
   * Generates new keys, but does not replace our local copy. Use this to
   * verify an upload before storing.
   */
  generateNewKeysWBO(collections) {
    let newDefaultKey, newColls;
    [newDefaultKey, newColls] = this.newKeys(collections);

    return this._makeWBO(newColls, newDefaultKey);
  },

  /**
   * Create a new default key.
   *
   * @returns {BulkKeyBundle}
   */
  newDefaultKeyBundle() {
    const key = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
    key.generateRandom();
    return key;
  },

  /**
   * Create a new default key and store it as this._default, since without one you cannot use setContents.
   */
  generateDefaultKey() {
    this._default = this.newDefaultKeyBundle();
  },

  /**
   * Return true if keys are already present for each of the given
   * collections.
   */
  hasKeysFor(collections) {
    // We can't use filter() here because sometimes collections is an iterator.
    for (let collection of collections) {
      if (!this._collections[collection]) {
        return false;
      }
    }
    return true;
  },

  /**
   * Return a new CollectionKeyManager that has keys for each of the
   * given collections (creating new ones for collections where we
   * don't already have keys).
   */
  ensureKeysFor(collections) {
    const newKeys = Object.assign({}, this._collections);
    for (let c of collections) {
      if (newKeys[c]) {
        continue;  // don't replace existing keys
      }

      const b = new BulkKeyBundle(c);
      b.generateRandom();
      newKeys[c] = b;
    }
    return new CollectionKeyManager(this.lastModified, this._default, newKeys);
  },

  // Take the fetched info/collections WBO, checking the change
  // time of the crypto collection.
  updateNeeded(info_collections) {

    this._log.info("Testing for updateNeeded. Last modified: " + this.lastModified);

    // No local record of modification time? Need an update.
    if (!this.lastModified)
      return true;

    // No keys on the server? We need an update, though our
    // update handling will be a little more drastic...
    if (!(CRYPTO_COLLECTION in info_collections))
      return true;

    // Otherwise, we need an update if our modification time is stale.
    return (info_collections[CRYPTO_COLLECTION] > this.lastModified);
  },

  //
  // Set our keys and modified time to the values fetched from the server.
  // Returns one of three values:
  //
  // * If the default key was modified, return true.
  // * If the default key was not modified, but per-collection keys were,
  //   return an array of such.
  // * Otherwise, return false -- we were up-to-date.
  //
  setContents: function setContents(payload, modified) {

    let self = this;

    this._log.info("Setting collection keys contents. Our last modified: " +
                   this.lastModified + ", input modified: " + modified + ".");

    if (!payload)
      throw new Error("No payload in CollectionKeyManager.setContents().");

    if (!payload.default) {
      this._log.warn("No downloaded default key: this should not occur.");
      this._log.warn("Not clearing local keys.");
      throw new Error("No default key in CollectionKeyManager.setContents(). Cannot proceed.");
    }

    // Process the incoming default key.
    let b = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
    b.keyPairB64 = payload.default;
    let newDefault = b;

    // Process the incoming collections.
    let newCollections = {};
    if ("collections" in payload) {
      this._log.info("Processing downloaded per-collection keys.");
      let colls = payload.collections;
      for (let k in colls) {
        let v = colls[k];
        if (v) {
          let keyObj = new BulkKeyBundle(k);
          keyObj.keyPairB64 = v;
          newCollections[k] = keyObj;
        }
      }
    }

    // Check to see if these are already our keys.
    let sameDefault = (this._default && this._default.equals(newDefault));
    let collComparison = this._compareKeyBundleCollections(newCollections, this._collections);
    let sameColls = collComparison.same;

    if (sameDefault && sameColls) {
      self._log.info("New keys are the same as our old keys!");
      if (modified) {
        self._log.info("Bumped local modified time.");
        self.lastModified = modified;
      }
      return false;
    }

    // Make sure things are nice and tidy before we set.
    this.clear();

    this._log.info("Saving downloaded keys.");
    this._default     = newDefault;
    this._collections = newCollections;

    // Always trust the server.
    if (modified) {
      self._log.info("Bumping last modified to " + modified);
      self.lastModified = modified;
    }

    return sameDefault ? collComparison.changed : true;
  },

  updateContents: function updateContents(syncKeyBundle, storage_keys) {
    let log = this._log;
    log.info("Updating collection keys...");

    // storage_keys is a WBO, fetched from storage/crypto/keys.
    // Its payload is the default key, and a map of collections to keys.
    // We lazily compute the key objects from the strings we're given.

    let payload;
    try {
      payload = storage_keys.decrypt(syncKeyBundle);
    } catch (ex) {
      log.warn("Got exception decrypting storage keys with sync key.", ex);
      log.info("Aborting updateContents. Rethrowing.");
      throw ex;
    }

    let r = this.setContents(payload, storage_keys.modified);
    log.info("Collection keys updated.");
    return r;
  }
}

this.Collection = function Collection(uri, recordObj, service) {
  if (!service) {
    throw new Error("Collection constructor requires a service.");
  }

  Resource.call(this, uri);

  // This is a bit hacky, but gets the job done.
  let res = service.resource(uri);
  this.authenticator = res.authenticator;

  this._recordObj = recordObj;
  this._service = service;

  this._full = false;
  this._ids = null;
  this._limit = 0;
  this._older = 0;
  this._newer = 0;
  this._data = [];
  // optional members used by batch upload operations.
  this._batch = null;
  this._commit = false;
  // Used for batch download operations -- note that this is explicitly an
  // opaque value and not (necessarily) a number.
  this._offset = null;
}
Collection.prototype = {
  __proto__: Resource.prototype,
  _logName: "Sync.Collection",

  _rebuildURL: function Coll__rebuildURL() {
    // XXX should consider what happens if it's not a URL...
    this.uri.QueryInterface(Ci.nsIURL);

    let args = [];
    if (this.older)
      args.push("older=" + this.older);
    else if (this.newer) {
      args.push("newer=" + this.newer);
    }
    if (this.full)
      args.push("full=1");
    if (this.sort)
      args.push("sort=" + this.sort);
    if (this.ids != null)
      args.push("ids=" + this.ids);
    if (this.limit > 0 && this.limit != Infinity)
      args.push("limit=" + this.limit);
    if (this._batch)
      args.push("batch=" + encodeURIComponent(this._batch));
    if (this._commit)
      args.push("commit=true");
    if (this._offset)
      args.push("offset=" + encodeURIComponent(this._offset));

    this.uri.query = (args.length > 0) ? "?" + args.join("&") : "";
  },

  // get full items
  get full() { return this._full; },
  set full(value) {
    this._full = value;
    this._rebuildURL();
  },

  // Apply the action to a certain set of ids
  get ids() { return this._ids; },
  set ids(value) {
    this._ids = value;
    this._rebuildURL();
  },

  // Limit how many records to get
  get limit() { return this._limit; },
  set limit(value) {
    this._limit = value;
    this._rebuildURL();
  },

  // get only items modified before some date
  get older() { return this._older; },
  set older(value) {
    this._older = value;
    this._rebuildURL();
  },

  // get only items modified since some date
  get newer() { return this._newer; },
  set newer(value) {
    this._newer = value;
    this._rebuildURL();
  },

  // get items sorted by some criteria. valid values:
  // oldest (oldest first)
  // newest (newest first)
  // index
  get sort() { return this._sort; },
  set sort(value) {
    this._sort = value;
    this._rebuildURL();
  },

  get offset() { return this._offset; },
  set offset(value) {
    this._offset = value;
    this._rebuildURL();
  },

  // Set information about the batch for this request.
  get batch() { return this._batch; },
  set batch(value) {
    this._batch = value;
    this._rebuildURL();
  },

  get commit() { return this._commit; },
  set commit(value) {
    this._commit = value && true;
    this._rebuildURL();
  },

  // Similar to get(), but will page through the items `batchSize` at a time,
  // deferring calling the record handler until we've gotten them all.
  //
  // Returns the last response processed, and doesn't run the record handler
  // on any items if a non-success status is received while downloading the
  // records (or if a network error occurs).
  async getBatched(batchSize = DEFAULT_DOWNLOAD_BATCH_SIZE) {
    let totalLimit = Number(this.limit) || Infinity;
    if (batchSize <= 0 || batchSize >= totalLimit) {
      throw new Error("Invalid batch size");
    }

    if (!this.full) {
      throw new Error("getBatched is unimplemented for guid-only GETs");
    }

    // _onComplete and _onProgress are reset after each `get` by AsyncResource.
    let { _onComplete, _onProgress } = this;
    let recordBuffer = [];
    let resp;
    try {
      let lastModifiedTime;
      this.limit = batchSize;

      do {
        this._onProgress = _onProgress;
        this._onComplete = _onComplete;
        if (batchSize + recordBuffer.length > totalLimit) {
          this.limit = totalLimit - recordBuffer.length;
        }
        this._log.trace("Performing batched GET", { limit: this.limit, offset: this.offset });
        // Actually perform the request
        resp = await this.get();
        if (!resp.success) {
          recordBuffer = [];
          break;
        }
        for (let json of resp.obj) {
          let record = new this._recordObj();
          record.deserialize(json);
          recordBuffer.push(record);
        }

        // Initialize last modified, or check that something broken isn't happening.
        let lastModified = resp.headers["x-last-modified"];
        if (!lastModifiedTime) {
          lastModifiedTime = lastModified;
          this.setHeader("X-If-Unmodified-Since", lastModified);
        } else if (lastModified != lastModifiedTime) {
          // Should be impossible -- We'd get a 412 in this case.
          throw new Error("X-Last-Modified changed in the middle of a download batch! " +
                          `${lastModified} => ${lastModifiedTime}`)
        }

        // If this is missing, we're finished.
        this.offset = resp.headers["x-weave-next-offset"];
      } while (this.offset && totalLimit > recordBuffer.length);
    } finally {
      // Ensure we undo any temporary state so that subsequent calls to get()
      // or getBatched() work properly. We do this before calling the record
      // handler so that we can more convincingly pretend to be a normal get()
      // call. Note: we're resetting these to the values they had before this
      // function was called.
      this._limit = totalLimit;
      this._offset = null;
      delete this._headers["x-if-unmodified-since"];
      this._rebuildURL();
    }
    return { response: resp, records: recordBuffer };
  },

  // This object only supports posting via the postQueue object.
  post() {
    throw new Error("Don't directly post to a collection - use newPostQueue instead");
  },

  newPostQueue(log, timestamp, postCallback) {
    let poster = (data, headers, batch, commit) => {
      this.batch = batch;
      this.commit = commit;
      for (let [header, value] of headers) {
        this.setHeader(header, value);
      }
      return Resource.prototype.post.call(this, data);
    }
    let getConfig = (name, defaultVal) => {
      if (this._service.serverConfiguration && this._service.serverConfiguration.hasOwnProperty(name)) {
        return this._service.serverConfiguration[name];
      }
      return defaultVal;
    }

    let config = {
      max_post_bytes: getConfig("max_post_bytes", MAX_UPLOAD_BYTES),
      max_post_records: getConfig("max_post_records", MAX_UPLOAD_RECORDS),

      max_batch_bytes: getConfig("max_total_bytes", Infinity),
      max_batch_records: getConfig("max_total_records", Infinity),
    }

    // Handle config edge cases
    if (config.max_post_records <= 0) { config.max_post_records = MAX_UPLOAD_RECORDS; }
    if (config.max_batch_records <= 0) { config.max_batch_records = Infinity; }
    if (config.max_post_bytes <= 0) { config.max_post_bytes = MAX_UPLOAD_BYTES; }
    if (config.max_batch_bytes <= 0) { config.max_batch_bytes = Infinity; }

    // Max size of BSO payload is 256k. This assumes at most 4k of overhead,
    // which sounds like plenty. If the server says it can't handle this, we
    // might have valid records we can't sync, so we give up on syncing.
    let requiredMax = 260 * 1024;
    if (config.max_post_bytes < requiredMax) {
      this._log.error("Server configuration max_post_bytes is too low", config);
      throw new Error("Server configuration max_post_bytes is too low");
    }

    return new PostQueue(poster, timestamp, config, log, postCallback);
  },
};

/* A helper to manage the posting of records while respecting the various
   size limits.

   This supports the concept of a server-side "batch". The general idea is:
   * We queue as many records as allowed in memory, then make a single POST.
   * This first POST (optionally) gives us a batch ID, which we use for
     all subsequent posts, until...
   * At some point we hit a batch-maximum, and jump through a few hoops to
     commit the current batch (ie, all previous POSTs) and start a new one.
   * Eventually commit the final batch.

  In most cases we expect there to be exactly 1 batch consisting of possibly
  multiple POSTs.
*/
function PostQueue(poster, timestamp, config, log, postCallback) {
  // The "post" function we should use when it comes time to do the post.
  this.poster = poster;
  this.log = log;

  // The config we use. We expect it to have fields "max_post_records",
  // "max_batch_records", "max_post_bytes", and "max_batch_bytes"
  this.config = config;

  // The callback we make with the response when we do get around to making the
  // post (which could be during any of the enqueue() calls or the final flush())
  // This callback may be called multiple times and must not add new items to
  // the queue.
  // The second argument passed to this callback is a boolean value that is true
  // if we're in the middle of a batch, and false if either the batch is
  // complete, or it's a post to a server that does not understand batching.
  this.postCallback = postCallback;

  // The string where we are capturing the stringified version of the records
  // queued so far. It will always be invalid JSON as it is always missing the
  // closing bracket.
  this.queued = "";

  // The number of records we've queued so far but are yet to POST.
  this.numQueued = 0;

  // The number of records/bytes we've processed in previous POSTs for our
  // current batch. Does *not* include records currently queued for the next POST.
  this.numAlreadyBatched = 0;
  this.bytesAlreadyBatched = 0;

  // The ID of our current batch. Can be undefined (meaning we are yet to make
  // the first post of a patch, so don't know if we have a batch), null (meaning
  // we've made the first post but the server response indicated no batching
  // semantics), otherwise we have made the first post and it holds the batch ID
  // returned from the server.
  this.batchID = undefined;

  // Time used for X-If-Unmodified-Since -- should be the timestamp from the last GET.
  this.lastModified = timestamp;
}

PostQueue.prototype = {
  async enqueue(record) {
    // We want to ensure the record has a .toJSON() method defined - even
    // though JSON.stringify() would implicitly call it, the stringify might
    // still work even if it isn't defined, which isn't what we want.
    let jsonRepr = record.toJSON();
    if (!jsonRepr) {
      throw new Error("You must only call this with objects that explicitly support JSON");
    }
    let bytes = JSON.stringify(jsonRepr);

    // Do a flush if we can't add this record without exceeding our single-request
    // limits, or without exceeding the total limit for a single batch.
    let newLength = this.queued.length + bytes.length + 2; // extras for leading "[" / "," and trailing "]"

    let maxAllowedBytes = Math.min(256 * 1024, this.config.max_post_bytes);

    let postSizeExceeded = this.numQueued >= this.config.max_post_records ||
                           newLength >= maxAllowedBytes;

    let batchSizeExceeded = (this.numQueued + this.numAlreadyBatched) >= this.config.max_batch_records ||
                            (newLength + this.bytesAlreadyBatched) >= this.config.max_batch_bytes;

    let singleRecordTooBig = bytes.length + 2 > maxAllowedBytes;

    if (postSizeExceeded || batchSizeExceeded) {
      this.log.trace(`PostQueue flushing due to postSizeExceeded=${postSizeExceeded}, batchSizeExceeded=${batchSizeExceeded}` +
                     `, max_batch_bytes: ${this.config.max_batch_bytes}, max_post_bytes: ${this.config.max_post_bytes}`);

      if (singleRecordTooBig) {
        return { enqueued: false, error: new Error("Single record too large to submit to server") };
      }

      // We need to write the queue out before handling this one, but we only
      // commit the batch (and thus start a new one) if the batch is full.
      // Note that if a single record is too big for the batch or post, then
      // the batch may be empty, and so we don't flush in that case.
      if (this.numQueued) {
        await this.flush(batchSizeExceeded || singleRecordTooBig);
      }
    }
    // Either a ',' or a '[' depending on whether this is the first record.
    this.queued += this.numQueued ? "," : "[";
    this.queued += bytes;
    this.numQueued++;
    return { enqueued: true };
  },

  async flush(finalBatchPost) {
    if (!this.queued) {
      // nothing queued - we can't be in a batch, and something has gone very
      // bad if we think we are.
      if (this.batchID) {
        throw new Error(`Flush called when no queued records but we are in a batch ${this.batchID}`);
      }
      return;
    }
    // the batch query-param and headers we'll send.
    let batch;
    let headers = [];
    if (this.batchID === undefined) {
      // First commit in a (possible) batch.
      batch = "true";
    } else if (this.batchID) {
      // We have an existing batch.
      batch = this.batchID;
    } else {
      // Not the first post and we know we have no batch semantics.
      batch = null;
    }

    headers.push(["x-if-unmodified-since", this.lastModified]);

    this.log.info(`Posting ${this.numQueued} records of ${this.queued.length + 1} bytes with batch=${batch}`);
    let queued = this.queued + "]";
    if (finalBatchPost) {
      this.bytesAlreadyBatched = 0;
      this.numAlreadyBatched = 0;
    } else {
      this.bytesAlreadyBatched += queued.length;
      this.numAlreadyBatched += this.numQueued;
    }
    this.queued = "";
    this.numQueued = 0;
    let response = await this.poster(queued, headers, batch, !!(finalBatchPost && this.batchID !== null));

    if (!response.success) {
      this.log.trace("Server error response during a batch", response);
      // not clear what we should do here - we expect the consumer of this to
      // abort by throwing in the postCallback below.
      await this.postCallback(response, !finalBatchPost);
      return;
    }

    if (finalBatchPost) {
      this.log.trace("Committed batch", this.batchID);
      this.batchID = undefined; // we are now in "first post for the batch" state.
      this.lastModified = response.headers["x-last-modified"];
      await this.postCallback(response, false);
      return;
    }

    if (response.status != 202) {
      if (this.batchID) {
        throw new Error("Server responded non-202 success code while a batch was in progress");
      }
      this.batchID = null; // no batch semantics are in place.
      this.lastModified = response.headers["x-last-modified"];
      await this.postCallback(response, false);
      return;
    }

    // this response is saying the server has batch semantics - we should
    // always have a batch ID in the response.
    let responseBatchID = response.obj.batch;
    this.log.trace("Server responsed 202 with batch", responseBatchID);
    if (!responseBatchID) {
      this.log.error("Invalid server response: 202 without a batch ID", response);
      throw new Error("Invalid server response: 202 without a batch ID");
    }

    if (this.batchID === undefined) {
      this.batchID = responseBatchID;
      if (!this.lastModified) {
        this.lastModified = response.headers["x-last-modified"];
        if (!this.lastModified) {
          throw new Error("Batch response without x-last-modified");
        }
      }
    }

    if (this.batchID != responseBatchID) {
      throw new Error(`Invalid client/server batch state - client has ${this.batchID}, server has ${responseBatchID}`);
    }

    await this.postCallback(response, true);
  },
}
PK
!<jII!modules/services-sync/resource.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = [
  "AsyncResource",
  "Resource"
];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/util.js");

const DEFAULT_LOAD_FLAGS =
  // Always validate the cache:
  Ci.nsIRequest.LOAD_BYPASS_CACHE |
  Ci.nsIRequest.INHIBIT_CACHING |
  // Don't send user cookies over the wire (Bug 644734).
  Ci.nsIRequest.LOAD_ANONYMOUS;

/*
 * AsyncResource represents a remote network resource, identified by a URI.
 * Create an instance like so:
 *
 *   let resource = new AsyncResource("http://foobar.com/path/to/resource");
 *
 * The 'resource' object has the following methods to issue HTTP requests
 * of the corresponding HTTP methods:
 *
 *   get(callback)
 *   put(data, callback)
 *   post(data, callback)
 *   delete(callback)
 *
 * 'callback' is a function with the following signature:
 *
 *   function callback(error, result) {...}
 *
 * 'error' will be null on successful requests. Likewise, result will not be
 * passed (=undefined) when an error occurs. Note that this is independent of
 * the status of the HTTP response.
 */
this.AsyncResource = function AsyncResource(uri) {
  this._log = Log.repository.getLogger(this._logName);
  this._log.level =
    Log.Level[Svc.Prefs.get("log.logger.network.resources")];
  this.uri = uri;
  this._headers = {};
  this._onComplete = Utils.bind2(this, this._onComplete);
}
AsyncResource.prototype = {
  _logName: "Sync.AsyncResource",

  // ** {{{ AsyncResource.serverTime }}} **
  //
  // Caches the latest server timestamp (X-Weave-Timestamp header).
  serverTime: null,

  /**
   * Callback to be invoked at request time to add authentication details.
   *
   * By default, a global authenticator is provided. If this is set, it will
   * be used instead of the global one.
   */
  authenticator: null,

  // Wait 5 minutes before killing a request.
  ABORT_TIMEOUT: 300000,

  // ** {{{ AsyncResource.headers }}} **
  //
  // Headers to be included when making a request for the resource.
  // Note: Header names should be all lower case, there's no explicit
  // check for duplicates due to case!
  get headers() {
    return this._headers;
  },
  set headers(value) {
    this._headers = value;
  },
  setHeader: function Res_setHeader(header, value) {
    this._headers[header.toLowerCase()] = value;
  },
  get headerNames() {
    return Object.keys(this.headers);
  },

  // ** {{{ AsyncResource.uri }}} **
  //
  // URI representing this resource.
  get uri() {
    return this._uri;
  },
  set uri(value) {
    if (typeof value == "string")
      this._uri = CommonUtils.makeURI(value);
    else
      this._uri = value;
  },

  // ** {{{ AsyncResource.spec }}} **
  //
  // Get the string representation of the URI.
  get spec() {
    if (this._uri)
      return this._uri.spec;
    return null;
  },

  // ** {{{ AsyncResource.data }}} **
  //
  // Get and set the data encapulated in the resource.
  _data: null,
  get data() {
    return this._data;
  },
  set data(value) {
    this._data = value;
  },

  // ** {{{ AsyncResource._createRequest }}} **
  //
  // This method returns a new IO Channel for requests to be made
  // through. It is never called directly, only {{{_doRequest}}} uses it
  // to obtain a request channel.
  //
  _createRequest(method) {
    this.method = method;
    let channel = NetUtil.newChannel({uri: this.spec, loadUsingSystemPrincipal: true})
                         .QueryInterface(Ci.nsIRequest)
                         .QueryInterface(Ci.nsIHttpChannel);

    channel.loadFlags |= DEFAULT_LOAD_FLAGS;

    // Setup a callback to handle channel notifications.
    let listener = new ChannelNotificationListener(this.headerNames);
    channel.notificationCallbacks = listener;

    // Compose a UA string fragment from the various available identifiers.
    if (Svc.Prefs.get("sendVersionInfo", true)) {
      channel.setRequestHeader("user-agent", Utils.userAgent, false);
    }

    let headers = this.headers;

    if (this.authenticator) {
      let result = this.authenticator(this, method);
      if (result && result.headers) {
        for (let [k, v] of Object.entries(result.headers)) {
          headers[k.toLowerCase()] = v;
        }
      }
    } else {
      this._log.debug("No authenticator found.");
    }

    for (let key of Object.keys(headers)) {
      if (key == "authorization")
        this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
      else
        this._log.trace("HTTP Header " + key + ": " + headers[key]);
      channel.setRequestHeader(key, headers[key], false);
    }
    return channel;
  },

  _onProgress(channel) {},

  _doRequest(action, data) {
    this._log.trace("In _doRequest.");
    return new Promise((resolve, reject) => {
      this._deferred = { resolve, reject };
      let channel = this._createRequest(action);

      if ("undefined" != typeof(data))
        this._data = data;

      // PUT and POST are treated differently because they have payload data.
      if ("PUT" == action || "POST" == action) {
        // Convert non-string bodies into JSON
        if (this._data.constructor.toString() != String)
          this._data = JSON.stringify(this._data);

        this._log.debug(action + " Length: " + this._data.length);
        this._log.trace(action + " Body: " + this._data);

        let type = ("content-type" in this._headers) ?
          this._headers["content-type"] : "text/plain";

        let stream = Cc["@mozilla.org/io/string-input-stream;1"].
          createInstance(Ci.nsIStringInputStream);
        stream.setData(this._data, this._data.length);

        channel.QueryInterface(Ci.nsIUploadChannel);
        channel.setUploadStream(stream, type, this._data.length);
      }

      // Setup a channel listener so that the actual network operation
      // is performed asynchronously.
      let listener = new ChannelListener(this._onComplete, this._onProgress,
                                         this._log, this.ABORT_TIMEOUT);
      channel.requestMethod = action;
      channel.asyncOpen2(listener);
    });
  },

  _onComplete(ex, data, channel) {
    this._log.trace("In _onComplete. An error occurred.", ex);

    if (ex) {
      if (!Async.isShutdownException(ex)) {
        this._log.warn("${action} request to ${url} failed: ${ex}",
                       { action: this.method, url: this.uri.spec, ex});
      }
      this._deferred.reject(ex);
      return;
    }

    this._data = data;
    let action = channel.requestMethod;

    this._log.trace("Channel: " + channel);
    this._log.trace("Action: " + action);

    // Process status and success first. This way a problem with headers
    // doesn't fail to include accurate status information.
    let status = 0;
    let success = false;

    try {
      status  = channel.responseStatus;
      success = channel.requestSucceeded;    // HTTP status.

      this._log.trace("Status: " + status);
      this._log.trace("Success: " + success);

      // Log the status of the request.
      let mesg = [action, success ? "success" : "fail", status,
                  channel.URI.spec].join(" ");
      this._log.debug("mesg: " + mesg);

      if (mesg.length > 200)
        mesg = mesg.substr(0, 200) + "…";
      this._log.debug(mesg);

      // Additionally give the full response body when Trace logging.
      if (this._log.level <= Log.Level.Trace)
        this._log.trace(action + " body: " + data);

    } catch (ex) {
      // Got a response, but an exception occurred during processing.
      // This shouldn't occur.
      this._log.warn("Caught unexpected exception in _oncomplete", ex);
    }

    // Process headers. They can be empty, or the call can otherwise fail, so
    // put this in its own try block.
    let headers = {};
    try {
      this._log.trace("Processing response headers.");

      // Read out the response headers if available.
      channel.visitResponseHeaders({
        visitHeader: function visitHeader(header, value) {
          headers[header.toLowerCase()] = value;
        }
      });

      // This is a server-side safety valve to allow slowing down
      // clients without hurting performance.
      if (headers["x-weave-backoff"]) {
        let backoff = headers["x-weave-backoff"];
        this._log.debug("Got X-Weave-Backoff: " + backoff);
        Observers.notify("weave:service:backoff:interval",
                         parseInt(backoff, 10));
      }

      if (success && headers["x-weave-quota-remaining"]) {
        Observers.notify("weave:service:quota:remaining",
                         parseInt(headers["x-weave-quota-remaining"], 10));
      }

      let contentLength = headers["content-length"];
      if (success && contentLength && data &&
          contentLength != data.length) {
        this._log.warn("The response body's length of: " + data.length +
                       " doesn't match the header's content-length of: " +
                       contentLength + ".");
      }
    } catch (ex) {
      this._log.debug("Caught exception visiting headers in _onComplete", ex);
    }

    let ret     = new String(data);
    ret.url     = channel.URI.spec;
    ret.status  = status;
    ret.success = success;
    ret.headers = headers;

    if (!success) {
      this._log.warn(`${action} request to ${ret.url} failed with status ${status}`);
    }
    // Make a lazy getter to convert the json response into an object.
    // Note that this can cause a parse error to be thrown far away from the
    // actual fetch, so be warned!
    XPCOMUtils.defineLazyGetter(ret, "obj", () => {
      try {
        return JSON.parse(ret);
      } catch (ex) {
        this._log.warn("Got exception parsing response body", ex);
        // Stringify to avoid possibly printing non-printable characters.
        this._log.debug("Parse fail: Response body starts: \"" +
                        JSON.stringify((ret + "").slice(0, 100)) +
                        "\".");
        throw ex;
      }
    });

    this._deferred.resolve(ret);
  },

  get() {
    return this._doRequest("GET", undefined);
  },

  put(data) {
    return this._doRequest("PUT", data);
  },

  post(data) {
    return this._doRequest("POST", data);
  },

  delete() {
    return this._doRequest("DELETE", undefined);
  }
};

// TODO: We still export both "Resource" and "AsyncRecourse" as the same
// object, but we should decide on one and unify all references.
this.Resource = AsyncResource;

// = ChannelListener =
//
// This object implements the {{{nsIStreamListener}}} interface
// and is called as the network operation proceeds.
function ChannelListener(onComplete, onProgress, logger, timeout) {
  this._onComplete = onComplete;
  this._onProgress = onProgress;
  this._log = logger;
  this._timeout = timeout;
  this.delayAbort();
}
ChannelListener.prototype = {

  onStartRequest: function Channel_onStartRequest(channel) {
    this._log.trace("onStartRequest called for channel " + channel + ".");

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
      channel.cancel(Cr.NS_BINDING_ABORTED);
      return;
    }

    // Save the latest server timestamp when possible.
    try {
      AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0;
    } catch (ex) {}

    this._log.trace("onStartRequest: " + channel.requestMethod + " " +
                    channel.URI.spec);
    this._data = "";
    this.delayAbort();
  },

  onStopRequest: function Channel_onStopRequest(channel, context, status) {
    // Clear the abort timer now that the channel is done.
    this.abortTimer.clear();

    if (!this._onComplete) {
      this._log.error("Unexpected error: _onComplete not defined in onStopRequest.");
      this._onProgress = null;
      return;
    }

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");

      this._onComplete(ex, this._data, channel);
      this._onComplete = this._onProgress = null;
      return;
    }

    let statusSuccess = Components.isSuccessCode(status);
    let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
    this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " +
                    "isSuccessCode(" + status + ")? " + statusSuccess);

    if (this._data == "") {
      this._data = null;
    }

    // Pass back the failure code and stop execution. Use Components.Exception()
    // instead of Error() so the exception is QI-able and can be passed across
    // XPCOM borders while preserving the status code.
    if (!statusSuccess) {
      let message = Components.Exception("", status).name;
      let error   = Components.Exception(message, status);

      this._onComplete(error, undefined, channel);
      this._onComplete = this._onProgress = null;
      return;
    }

    this._log.trace("Channel: flags = " + channel.loadFlags +
                    ", URI = " + uri +
                    ", HTTP success? " + channel.requestSucceeded);
    this._onComplete(null, this._data, channel);
    this._onComplete = this._onProgress = null;
  },

  onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
    let siStream;
    try {
      siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
      siStream.init(stream);
    } catch (ex) {
      this._log.warn("Exception creating nsIScriptableInputStream", ex);
      this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count);
      // Cannot proceed, so rethrow and allow the channel to cancel itself.
      throw ex;
    }

    try {
      this._data += siStream.read(count);
    } catch (ex) {
      this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + ".");
      throw ex;
    }

    try {
      let httpChannel = req.QueryInterface(Ci.nsIHttpChannel);
      this._onProgress(httpChannel);
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.warn("Got exception calling onProgress handler during fetch of "
                     + req.URI.spec, ex);
      this._log.trace("Rethrowing; expect a failure code from the HTTP channel.");
      throw ex;
    }

    this.delayAbort();
  },

  /**
   * Create or push back the abort timer that kills this request.
   */
  delayAbort: function delayAbort() {
    try {
      CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer");
    } catch (ex) {
      this._log.warn("Got exception extending abort timer", ex);
    }
  },

  abortRequest: function abortRequest() {
    // Ignore any callbacks if we happen to get any now
    this.onStopRequest = function() {};
    let error = Components.Exception("Aborting due to channel inactivity.",
                                     Cr.NS_ERROR_NET_TIMEOUT);
    if (!this._onComplete) {
      this._log.error("Unexpected error: _onComplete not defined in " +
                      "abortRequest.");
      return;
    }
    this._onComplete(error);
  }
};

/**
 * This class handles channel notification events.
 *
 * An instance of this class is bound to each created channel.
 *
 * Optionally pass an array of header names. Each header named
 * in this array will be copied between the channels in the
 * event of a redirect.
 */
function ChannelNotificationListener(headersToCopy) {
  this._headersToCopy = headersToCopy;

  this._log = Log.repository.getLogger(this._logName);
  this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")];
}
ChannelNotificationListener.prototype = {
  _logName: "Sync.Resource",

  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },

  QueryInterface(aIID) {
    if (aIID.equals(Ci.nsIBadCertListener2) ||
        aIID.equals(Ci.nsIInterfaceRequestor) ||
        aIID.equals(Ci.nsISupports) ||
        aIID.equals(Ci.nsIChannelEventSink))
      return this;

    throw Cr.NS_ERROR_NO_INTERFACE;
  },

  notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) {
    let log = Log.repository.getLogger("Sync.CertListener");
    log.warn("Invalid HTTPS certificate encountered!");

    // This suppresses the UI warning only. The request is still cancelled.
    return true;
  },

  asyncOnChannelRedirect:
    function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {

    let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
    let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
    this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);

    this._log.debug("Ensuring load flags are set.");
    newChannel.loadFlags |= DEFAULT_LOAD_FLAGS;

    // For internal redirects, copy the headers that our caller set.
    try {
      if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) &&
          newChannel.URI.equals(oldChannel.URI)) {
        this._log.debug("Copying headers for safe internal redirect.");

        // QI the channel so we can set headers on it.
        try {
          newChannel.QueryInterface(Ci.nsIHttpChannel);
        } catch (ex) {
          this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
          throw ex;
        }

        for (let header of this._headersToCopy) {
          let value = oldChannel.getRequestHeader(header);
          if (value) {
            let printed = (header == "authorization") ? "****" : value;
            this._log.debug("Header: " + header + " = " + printed);
            newChannel.setRequestHeader(header, value, false);
          } else {
            this._log.warn("No value for header " + header);
          }
        }
      }
    } catch (ex) {
      this._log.error("Error copying headers", ex);
    }

    // We let all redirects proceed.
    try {
      callback.onRedirectVerifyCallback(Cr.NS_OK);
    } catch (ex) {
      this._log.error("onRedirectVerifyCallback threw!", ex);
    }
  }
};
PK
!<]%יmodules/services-sync/rest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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://services-common/rest.js");
Cu.import("resource://services-sync/util.js");

this.EXPORTED_SYMBOLS = ["SyncStorageRequest"];

const STORAGE_REQUEST_TIMEOUT = 5 * 60; // 5 minutes

/**
 * RESTRequest variant for use against a Sync storage server.
 */
this.SyncStorageRequest = function SyncStorageRequest(uri) {
  RESTRequest.call(this, uri);

  this.authenticator = null;
}
SyncStorageRequest.prototype = {

  __proto__: RESTRequest.prototype,

  _logName: "Sync.StorageRequest",

  /**
   * Wait 5 minutes before killing a request.
   */
  timeout: STORAGE_REQUEST_TIMEOUT,

  dispatch: function dispatch(method, data, onComplete, onProgress) {
    // Compose a UA string fragment from the various available identifiers.
    if (Svc.Prefs.get("sendVersionInfo", true)) {
      this.setHeader("user-agent", Utils.userAgent);
    }

    if (this.authenticator) {
      let result = this.authenticator(this, method);
      if (result && result.headers) {
        for (let [k, v] of Object.entries(result.headers)) {
          this.setHeader(k, v);
        }
      }
    } else {
      this._log.debug("No authenticator found.");
    }

    return RESTRequest.prototype.dispatch.apply(this, arguments);
  },

  onStartRequest: function onStartRequest(channel) {
    RESTRequest.prototype.onStartRequest.call(this, channel);
    if (this.status == this.ABORTED) {
      return;
    }

    let headers = this.response.headers;
    // Save the latest server timestamp when possible.
    if (headers["x-weave-timestamp"]) {
      SyncStorageRequest.serverTime = parseFloat(headers["x-weave-timestamp"]);
    }

    // This is a server-side safety valve to allow slowing down
    // clients without hurting performance.
    if (headers["x-weave-backoff"]) {
      Svc.Obs.notify("weave:service:backoff:interval",
                     parseInt(headers["x-weave-backoff"], 10));
    }

    if (this.response.success && headers["x-weave-quota-remaining"]) {
      Svc.Obs.notify("weave:service:quota:remaining",
                     parseInt(headers["x-weave-quota-remaining"], 10));
    }
  },

  onStopRequest: function onStopRequest(channel, context, statusCode) {
    if (this.status != this.ABORTED) {
      let resp = this.response;
      let contentLength = resp.headers ? resp.headers["content-length"] : "";

      if (resp.success && contentLength &&
          contentLength != resp.body.length) {
        this._log.warn("The response body's length of: " + resp.body.length +
                       " doesn't match the header's content-length of: " +
                       contentLength + ".");
      }
    }

    RESTRequest.prototype.onStopRequest.apply(this, arguments);
  }
};
PK
!<cI modules/services-sync/service.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["Service"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

// How long before refreshing the cluster
const CLUSTER_BACKOFF = 5 * 60 * 1000; // 5 minutes

// How long a key to generate from an old passphrase.
const PBKDF2_KEY_BYTES = 16;

const CRYPTO_COLLECTION = "crypto";
const KEYS_WBO = "keys";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/rest.js");
Cu.import("resource://services-sync/stages/enginesync.js");
Cu.import("resource://services-sync/stages/declined.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/telemetry.js");
Cu.import("resource://services-sync/util.js");

function getEngineModules() {
  let result = {
    Addons: {module: "addons.js", symbol: "AddonsEngine"},
    Bookmarks: {module: "bookmarks.js", symbol: "BookmarksEngine"},
    Form: {module: "forms.js", symbol: "FormEngine"},
    History: {module: "history.js", symbol: "HistoryEngine"},
    Password: {module: "passwords.js", symbol: "PasswordEngine"},
    Prefs: {module: "prefs.js", symbol: "PrefsEngine"},
    Tab: {module: "tabs.js", symbol: "TabEngine"},
    ExtensionStorage: {module: "extension-storage.js", symbol: "ExtensionStorageEngine"},
  }
  if (Svc.Prefs.get("engine.addresses.available", false)) {
    result["Addresses"] = {
      module: "resource://formautofill/FormAutofillSync.jsm",
      symbol: "AddressesEngine",
    };
  }
  if (Svc.Prefs.get("engine.creditcards.available", false)) {
    result["CreditCards"] = {
      module: "resource://formautofill/FormAutofillSync.jsm",
      symbol: "CreditCardsEngine",
    };
  }
  return result;
}

const STORAGE_INFO_TYPES = [INFO_COLLECTIONS,
                            INFO_COLLECTION_USAGE,
                            INFO_COLLECTION_COUNTS,
                            INFO_QUOTA];

// A unique identifier for this browser session. Used for logging so
// we can easily see whether 2 logs are in the same browser session or
// after the browser restarted.
XPCOMUtils.defineLazyGetter(this, "browserSessionID", Utils.makeGUID);

function Sync11Service() {
  this._notify = Utils.notify("weave:service:");
}
Sync11Service.prototype = {

  _lock: Utils.lock,
  _locked: false,
  _loggedIn: false,

  infoURL: null,
  storageURL: null,
  metaURL: null,
  cryptoKeyURL: null,
  // The cluster URL comes via the ClusterManager object, which in the FxA
  // world is ebbedded in the token returned from the token server.
  _clusterURL: null,

  get clusterURL() {
    return this._clusterURL || "";
  },
  set clusterURL(value) {
    if (value != null && typeof value != "string") {
      throw new Error("cluster must be a string, got " + (typeof value));
    }
    this._clusterURL = value;
    this._updateCachedURLs();
  },

  get syncID() {
    // Generate a random syncID id we don't have one
    let syncID = Svc.Prefs.get("client.syncID", "");
    return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
  },
  set syncID(value) {
    Svc.Prefs.set("client.syncID", value);
  },

  get isLoggedIn() { return this._loggedIn; },

  get locked() { return this._locked; },
  lock: function lock() {
    if (this._locked)
      return false;
    this._locked = true;
    return true;
  },
  unlock: function unlock() {
    this._locked = false;
  },

  // A specialized variant of Utils.catch.
  // This provides a more informative error message when we're already syncing:
  // see Bug 616568.
  _catch(func) {
    function lockExceptions(ex) {
      if (Utils.isLockException(ex)) {
        // This only happens if we're syncing already.
        this._log.info("Cannot start sync: already syncing?");
      }
    }

    return Utils.catch.call(this, func, lockExceptions);
  },

  get userBaseURL() {
    if (!this._clusterManager) {
      return null;
    }
    return this._clusterManager.getUserBaseURL();
  },

  _updateCachedURLs: function _updateCachedURLs() {
    // Nothing to cache yet if we don't have the building blocks
    if (!this.clusterURL) {
      // Also reset all other URLs used by Sync to ensure we aren't accidentally
      // using one cached earlier - if there's no cluster URL any cached ones
      // are invalid.
      this.infoURL = undefined;
      this.storageURL = undefined;
      this.metaURL = undefined;
      this.cryptoKeysURL = undefined;
      return;
    }

    this._log.debug("Caching URLs under storage user base: " + this.userBaseURL);

    // Generate and cache various URLs under the storage API for this user
    this.infoURL = this.userBaseURL + "info/collections";
    this.storageURL = this.userBaseURL + "storage/";
    this.metaURL = this.storageURL + "meta/global";
    this.cryptoKeysURL = this.storageURL + CRYPTO_COLLECTION + "/" + KEYS_WBO;
  },

  _checkCrypto: function _checkCrypto() {
    let ok = false;

    try {
      let iv = Weave.Crypto.generateRandomIV();
      if (iv.length == 24)
        ok = true;

    } catch (e) {
      this._log.debug("Crypto check failed: " + e);
    }

    return ok;
  },

  /**
   * Here is a disgusting yet reasonable way of handling HMAC errors deep in
   * the guts of Sync. The astute reader will note that this is a hacky way of
   * implementing something like continuable conditions.
   *
   * A handler function is glued to each engine. If the engine discovers an
   * HMAC failure, we fetch keys from the server and update our keys, just as
   * we would on startup.
   *
   * If our key collection changed, we signal to the engine (via our return
   * value) that it should retry decryption.
   *
   * If our key collection did not change, it means that we already had the
   * correct keys... and thus a different client has the wrong ones. Reupload
   * the bundle that we fetched, which will bump the modified time on the
   * server and (we hope) prompt a broken client to fix itself.
   *
   * We keep track of the time at which we last applied this reasoning, because
   * thrashing doesn't solve anything. We keep a reasonable interval between
   * these remedial actions.
   */
  lastHMACEvent: 0,

  /*
   * Returns whether to try again.
   */
  async handleHMACEvent() {
    let now = Date.now();

    // Leave a sizable delay between HMAC recovery attempts. This gives us
    // time for another client to fix themselves if we touch the record.
    if ((now - this.lastHMACEvent) < HMAC_EVENT_INTERVAL)
      return false;

    this._log.info("Bad HMAC event detected. Attempting recovery " +
                   "or signaling to other clients.");

    // Set the last handled time so that we don't act again.
    this.lastHMACEvent = now;

    // Fetch keys.
    let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    try {
      let cryptoResp = (await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))).response;

      // Save out the ciphertext for when we reupload. If there's a bug in
      // CollectionKeyManager, this will prevent us from uploading junk.
      let cipherText = cryptoKeys.ciphertext;

      if (!cryptoResp.success) {
        this._log.warn("Failed to download keys.");
        return false;
      }

      let keysChanged = await this.handleFetchedKeys(this.identity.syncKeyBundle,
                                                     cryptoKeys, true);
      if (keysChanged) {
        // Did they change? If so, carry on.
        this._log.info("Suggesting retry.");
        return true;              // Try again.
      }

      // If not, reupload them and continue the current sync.
      cryptoKeys.ciphertext = cipherText;
      cryptoKeys.cleartext  = null;

      let uploadResp = await this._uploadCryptoKeys(cryptoKeys, cryptoResp.obj.modified);
      if (uploadResp.success) {
        this._log.info("Successfully re-uploaded keys. Continuing sync.");
      } else {
        this._log.warn("Got error response re-uploading keys. " +
                       "Continuing sync; let's try again later.");
      }

      return false;            // Don't try again: same keys.

    } catch (ex) {
      this._log.warn("Got exception fetching and handling crypto keys. " +
                     "Will try again later.", ex);
      return false;
    }
  },

  async handleFetchedKeys(syncKey, cryptoKeys, skipReset) {
    // Don't want to wipe if we're just starting up!
    let wasBlank = this.collectionKeys.isClear;
    let keysChanged = this.collectionKeys.updateContents(syncKey, cryptoKeys);

    if (keysChanged && !wasBlank) {
      this._log.debug("Keys changed: " + JSON.stringify(keysChanged));

      if (!skipReset) {
        this._log.info("Resetting client to reflect key change.");

        if (keysChanged.length) {
          // Collection keys only. Reset individual engines.
          await this.resetClient(keysChanged);
        } else {
          // Default key changed: wipe it all.
          await this.resetClient();
        }

        this._log.info("Downloaded new keys, client reset. Proceeding.");
      }
      return true;
    }
    return false;
  },

  /**
   * Prepare to initialize the rest of Weave after waiting a little bit
   */
  async onStartup() {
    this.status = Status;
    this.identity = Status._authManager;
    this.collectionKeys = new CollectionKeyManager();

    this.errorHandler = new ErrorHandler(this);

    this._log = Log.repository.getLogger("Sync.Service");
    this._log.level =
      Log.Level[Svc.Prefs.get("log.logger.service.main")];

    this._log.info("Loading Weave " + WEAVE_VERSION);

    this._clusterManager = this.identity.createClusterManager(this);
    this.recordManager = new RecordManager(this);

    this.enabled = true;

    await this._registerEngines();

    let ua = Cc["@mozilla.org/network/protocol;1?name=http"].
      getService(Ci.nsIHttpProtocolHandler).userAgent;
    this._log.info(ua);

    if (!this._checkCrypto()) {
      this.enabled = false;
      this._log.info("Could not load the Weave crypto component. Disabling " +
                      "Weave, since it will not work correctly.");
    }

    Svc.Obs.add("weave:service:setup-complete", this);
    Svc.Obs.add("sync:collection_changed", this); // Pulled from FxAccountsCommon
    Svc.Obs.add("fxaccounts:device_disconnected", this);
    Services.prefs.addObserver(PREFS_BRANCH + "engine.", this);

    this.scheduler = new SyncScheduler(this);

    if (!this.enabled) {
      this._log.info("Firefox Sync disabled.");
    }

    this._updateCachedURLs();

    let status = this._checkSetup();
    if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) {
      Svc.Obs.notify("weave:engine:start-tracking");
    }

    // Send an event now that Weave service is ready.  We don't do this
    // synchronously so that observers can import this module before
    // registering an observer.
    Utils.nextTick(() => {
      this.status.ready = true;

      // UI code uses the flag on the XPCOM service so it doesn't have
      // to load a bunch of modules.
      let xps = Cc["@mozilla.org/weave/service;1"]
                  .getService(Ci.nsISupports)
                  .wrappedJSObject;
      xps.ready = true;

      Svc.Obs.notify("weave:service:ready");
    });
  },

  _checkSetup: function _checkSetup() {
    if (!this.enabled) {
      return this.status.service = STATUS_DISABLED;
    }
    return this.status.checkSetup();
  },

  /**
   * Register the built-in engines for certain applications
   */
  async _registerEngines() {
    this.engineManager = new EngineManager(this);

    let engineModules = getEngineModules();

    let engines = [];
    // We allow a pref, which has no default value, to limit the engines
    // which are registered. We expect only tests will use this.
    if (Svc.Prefs.has("registerEngines")) {
      engines = Svc.Prefs.get("registerEngines").split(",");
      this._log.info("Registering custom set of engines", engines);
    } else {
      // default is all engines.
      engines = Object.keys(engineModules);
    }

    let declined = [];
    let pref = Svc.Prefs.get("declinedEngines");
    if (pref) {
      declined = pref.split(",");
    }

    let clientsEngine = new ClientEngine(this);
    // Ideally clientsEngine should not exist
    // (or be a promise that calls initialize() before returning the engine)
    await clientsEngine.initialize();
    this.clientsEngine = clientsEngine;

    for (let name of engines) {
      if (!(name in engineModules)) {
        this._log.info("Do not know about engine: " + name);
        continue;
      }
      let {module, symbol} = engineModules[name];
      if (!module.includes(":")) {
        module = "resource://services-sync/engines/" + module;
      }
      let ns = {};
      try {
        Cu.import(module, ns);
        if (!(symbol in ns)) {
          this._log.warn("Could not find exported engine instance: " + symbol);
          continue;
        }

        await this.engineManager.register(ns[symbol]);
      } catch (ex) {
        this._log.warn("Could not register engine " + name, ex);
      }
    }

    this.engineManager.setDeclined(declined);
  },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  // nsIObserver

  observe: function observe(subject, topic, data) {
    switch (topic) {
      // Ideally this observer should be in the SyncScheduler, but it would require
      // some work to know about the sync specific engines. We should move this there once it does.
      case "sync:collection_changed":
        // We check if we're running TPS here to avoid TPS failing because it
        // couldn't get to get the sync lock, due to us currently syncing the
        // clients engine.
        if (data.includes("clients") && !Svc.Prefs.get("testing.tps", false)) {
          // Sync in the background (it's fine not to wait on the returned promise
          // because sync() has a lock).
          // [] = clients collection only
          this.sync([]).catch(e => {
            this._log.error(e);
          });
        }
        break;
      case "fxaccounts:device_disconnected":
        data = JSON.parse(data);
        if (!data.isLocalDevice) {
          Async.promiseSpinningly(this.clientsEngine.updateKnownStaleClients());
        }
        break;
      case "weave:service:setup-complete":
        let status = this._checkSetup();
        if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED)
            Svc.Obs.notify("weave:engine:start-tracking");
        break;
      case "nsPref:changed":
        if (this._ignorePrefObserver)
          return;
        let engine = data.slice((PREFS_BRANCH + "engine.").length);
        this._handleEngineStatusChanged(engine);
        break;
    }
  },

  _handleEngineStatusChanged: function handleEngineDisabled(engine) {
    this._log.trace("Status for " + engine + " engine changed.");
    if (Svc.Prefs.get("engineStatusChanged." + engine, false)) {
      // The enabled status being changed back to what it was before.
      Svc.Prefs.reset("engineStatusChanged." + engine);
    } else {
      // Remember that the engine status changed locally until the next sync.
      Svc.Prefs.set("engineStatusChanged." + engine, true);
    }
  },

  /**
   * Obtain a Resource instance with authentication credentials.
   */
  resource: function resource(url) {
    let res = new Resource(url);
    res.authenticator = this.identity.getResourceAuthenticator();

    return res;
  },

  /**
   * Obtain a SyncStorageRequest instance with authentication credentials.
   */
  getStorageRequest: function getStorageRequest(url) {
    let request = new SyncStorageRequest(url);
    request.authenticator = this.identity.getRESTRequestAuthenticator();

    return request;
  },

  /**
   * Perform the info fetch as part of a login or key fetch, or
   * inside engine sync.
   */
  async _fetchInfo(url) {
    let infoURL = url || this.infoURL;

    this._log.trace("In _fetchInfo: " + infoURL);
    let info;
    try {
      info = await this.resource(infoURL).get();
    } catch (ex) {
      this.errorHandler.checkServerError(ex);
      throw ex;
    }

    // Always check for errors; this is also where we look for X-Weave-Alert.
    this.errorHandler.checkServerError(info);
    if (!info.success) {
      this._log.error("Aborting sync: failed to get collections.")
      throw info;
    }
    return info;
  },

  async verifyAndFetchSymmetricKeys(infoResponse) {

    this._log.debug("Fetching and verifying -- or generating -- symmetric keys.");

    let syncKeyBundle = this.identity.syncKeyBundle;
    if (!syncKeyBundle) {
      this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
      this.status.sync = CREDENTIALS_CHANGED;
      return false;
    }

    try {
      if (!infoResponse)
        infoResponse = await this._fetchInfo(); // Will throw an exception on failure.

      // This only applies when the server is already at version 4.
      if (infoResponse.status != 200) {
        this._log.warn("info/collections returned non-200 response. Failing key fetch.");
        this.status.login = LOGIN_FAILED_SERVER_ERROR;
        this.errorHandler.checkServerError(infoResponse);
        return false;
      }

      let infoCollections = infoResponse.obj;

      this._log.info("Testing info/collections: " + JSON.stringify(infoCollections));

      if (this.collectionKeys.updateNeeded(infoCollections)) {
        this._log.info("collection keys reports that a key update is needed.");

        // Don't always set to CREDENTIALS_CHANGED -- we will probably take care of this.

        // Fetch storage/crypto/keys.
        let cryptoKeys;

        if (infoCollections && (CRYPTO_COLLECTION in infoCollections)) {
          try {
            cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
            let cryptoResp = (await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))).response;

            if (cryptoResp.success) {
              await this.handleFetchedKeys(syncKeyBundle, cryptoKeys);
              return true;
            } else if (cryptoResp.status == 404) {
              // On failure, ask to generate new keys and upload them.
              // Fall through to the behavior below.
              this._log.warn("Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating.");
              cryptoKeys = null;
            } else {
              // Some other problem.
              this.status.login = LOGIN_FAILED_SERVER_ERROR;
              this.errorHandler.checkServerError(cryptoResp);
              this._log.warn("Got status " + cryptoResp.status + " fetching crypto keys.");
              return false;
            }
          } catch (ex) {
            this._log.warn("Got exception fetching cryptoKeys.", ex);
            // TODO: Um, what exceptions might we get here? Should we re-throw any?

            // One kind of exception: HMAC failure.
            if (Utils.isHMACMismatch(ex)) {
              this.status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
              this.status.sync = CREDENTIALS_CHANGED;
            } else {
              // In the absence of further disambiguation or more precise
              // failure constants, just report failure.
              this.status.login = LOGIN_FAILED;
            }
            return false;
          }
        } else {
          this._log.info("... 'crypto' is not a reported collection. Generating new keys.");
        }

        if (!cryptoKeys) {
          this._log.info("No keys! Generating new ones.");

          // Better make some and upload them, and wipe the server to ensure
          // consistency. This is all achieved via _freshStart.
          // If _freshStart fails to clear the server or upload keys, it will
          // throw.
          await this._freshStart();
          return true;
        }

        // Last-ditch case.
        return false;
      }
      // No update needed: we're good!
      return true;
    } catch (ex) {
      // This means no keys are present, or there's a network error.
      this._log.debug("Failed to fetch and verify keys", ex);
      this.errorHandler.checkServerError(ex);
      return false;
    }
  },

  async verifyLogin(allow40XRecovery = true) {
    if (!this.identity.username) {
      this._log.warn("No username in verifyLogin.");
      this.status.login = LOGIN_FAILED_NO_USERNAME;
      return false;
    }

    // Attaching auth credentials to a request requires access to
    // passwords, which means that Resource.get can throw MP-related
    // exceptions!
    // So we ask the identity to verify the login state after unlocking the
    // master password (ie, this call is expected to prompt for MP unlock
    // if necessary) while we still have control.
    let unlockedState = await this.identity.unlockAndVerifyAuthState();
    this._log.debug("Fetching unlocked auth state returned " + unlockedState);
    if (unlockedState != STATUS_OK) {
      this.status.login = unlockedState;
      return false;
    }

    try {
      // Make sure we have a cluster to verify against.
      // This is a little weird, if we don't get a node we pretend
      // to succeed, since that probably means we just don't have storage.
      if (this.clusterURL == "" && !this._clusterManager.setCluster()) {
        this.status.sync = NO_SYNC_NODE_FOUND;
        return true;
      }

      // Fetch collection info on every startup.
      let test = await this.resource(this.infoURL).get();

      switch (test.status) {
        case 200:
          // The user is authenticated.

          // We have no way of verifying the passphrase right now,
          // so wait until remoteSetup to do so.
          // Just make the most trivial checks.
          if (!this.identity.syncKeyBundle) {
            this._log.warn("No passphrase in verifyLogin.");
            this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
            return false;
          }

          // Go ahead and do remote setup, so that we can determine
          // conclusively that our passphrase is correct.
          if ((await this._remoteSetup(test))) {
            // Username/password verified.
            this.status.login = LOGIN_SUCCEEDED;
            return true;
          }

          this._log.warn("Remote setup failed.");
          // Remote setup must have failed.
          return false;

        case 401:
          this._log.warn("401: login failed.");
          // Fall through to the 404 case.

        case 404:
          // Check that we're verifying with the correct cluster
          if (allow40XRecovery && this._clusterManager.setCluster()) {
            return await this.verifyLogin(false);
          }

          // We must have the right cluster, but the server doesn't expect us.
          // The implications of this depend on the identity being used - for
          // the legacy identity, it's an authoritatively "incorrect password",
          // (ie, LOGIN_FAILED_LOGIN_REJECTED) but for FxA it probably means
          // "transient error fetching auth token".
          this.status.login = this.identity.loginStatusFromVerification404();
          return false;

        default:
          // Server didn't respond with something that we expected
          this.status.login = LOGIN_FAILED_SERVER_ERROR;
          this.errorHandler.checkServerError(test);
          return false;
      }
    } catch (ex) {
      // Must have failed on some network issue
      this._log.debug("verifyLogin failed", ex);
      this.status.login = LOGIN_FAILED_NETWORK_ERROR;
      this.errorHandler.checkServerError(ex);
      return false;
    }
  },

  async generateNewSymmetricKeys() {
    this._log.info("Generating new keys WBO...");
    let wbo = this.collectionKeys.generateNewKeysWBO();
    this._log.info("Encrypting new key bundle.");
    wbo.encrypt(this.identity.syncKeyBundle);

    let uploadRes = await this._uploadCryptoKeys(wbo, 0);
    if (uploadRes.status != 200) {
      this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!");
      this.errorHandler.checkServerError(uploadRes);
      throw new Error("Unable to upload symmetric keys.");
    }
    this._log.info("Got status " + uploadRes.status + " uploading keys.");
    let serverModified = uploadRes.obj;   // Modified timestamp according to server.
    this._log.debug("Server reports crypto modified: " + serverModified);

    // Now verify that info/collections shows them!
    this._log.debug("Verifying server collection records.");
    let info = await this._fetchInfo();
    this._log.debug("info/collections is: " + info);

    if (info.status != 200) {
      this._log.warn("Non-200 info/collections response. Aborting.");
      throw new Error("Unable to upload symmetric keys.");
    }

    info = info.obj;
    if (!(CRYPTO_COLLECTION in info)) {
      this._log.error("Consistency failure: info/collections excludes " +
                      "crypto after successful upload.");
      throw new Error("Symmetric key upload failed.");
    }

    // Can't check against local modified: clock drift.
    if (info[CRYPTO_COLLECTION] < serverModified) {
      this._log.error("Consistency failure: info/collections crypto entry " +
                      "is stale after successful upload.");
      throw new Error("Symmetric key upload failed.");
    }

    // Doesn't matter if the timestamp is ahead.

    // Download and install them.
    let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    let cryptoResp = (await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))).response;
    if (cryptoResp.status != 200) {
      this._log.warn("Failed to download keys.");
      throw new Error("Symmetric key download failed.");
    }
    let keysChanged = await this.handleFetchedKeys(this.identity.syncKeyBundle,
                                                   cryptoKeys, true);
    if (keysChanged) {
      this._log.info("Downloaded keys differed, as expected.");
    }
  },

  async startOver() {
    this._log.trace("Invoking Service.startOver.");
    Svc.Obs.notify("weave:engine:stop-tracking");
    this.status.resetSync();

    // Deletion doesn't make sense if we aren't set up yet!
    if (this.clusterURL != "") {
      // Clear client-specific data from the server, including disabled engines.
      for (let engine of [this.clientsEngine].concat(this.engineManager.getAll())) {
        try {
          await engine.removeClientData();
        } catch (ex) {
          this._log.warn(`Deleting client data for ${engine.name} failed`, ex);
        }
      }
      this._log.debug("Finished deleting client data.");
    } else {
      this._log.debug("Skipping client data removal: no cluster URL.");
    }

    // We want let UI consumers of the following notification know as soon as
    // possible, so let's fake for the CLIENT_NOT_CONFIGURED status for now
    // by emptying the passphrase (we still need the password).
    this._log.info("Service.startOver dropping sync key and logging out.");
    this.identity.resetSyncKeyBundle();
    this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
    this.logout();
    Svc.Obs.notify("weave:service:start-over");

    // Reset all engines and clear keys.
    await this.resetClient();
    this.collectionKeys.clear();
    this.status.resetBackoff();

    // Reset Weave prefs.
    this._ignorePrefObserver = true;
    Svc.Prefs.resetBranch("");
    this._ignorePrefObserver = false;
    this.clusterURL = null;

    Svc.Prefs.set("lastversion", WEAVE_VERSION);

    this.identity.deleteSyncCredentials();

    try {
      this.identity.finalize();
      this.status.__authManager = null;
      this.identity = Status._authManager;
      this._clusterManager = this.identity.createClusterManager(this);
      Svc.Obs.notify("weave:service:start-over:finish");
    } catch (err) {
      this._log.error("startOver failed to re-initialize the identity manager", err);
      // Still send the observer notification so the current state is
      // reflected in the UI.
      Svc.Obs.notify("weave:service:start-over:finish");
    }
  },

  async login() {
    async function onNotify() {
      this._loggedIn = false;
      if (Services.io.offline) {
        this.status.login = LOGIN_FAILED_NETWORK_ERROR;
        throw new Error("Application is offline, login should not be called");
      }

      this._log.info("Logging in the user.");
      // Just let any errors bubble up - they've more context than we do!
      try {
        await this.identity.ensureLoggedIn();
      } finally {
        this._checkSetup(); // _checkSetup has a side effect of setting the right state.
      }

      this._updateCachedURLs();

      this._log.info("User logged in successfully - verifying login.");
      if (!(await this.verifyLogin())) {
        // verifyLogin sets the failure states here.
        throw new Error(`Login failed: ${this.status.login}`);
      }

      this._loggedIn = true;

      return true;
    }

    let notifier = this._notify("login", "", onNotify.bind(this));
    return this._catch(this._lock("service.js: login", notifier))();
  },

  logout: function logout() {
    // If we failed during login, we aren't going to have this._loggedIn set,
    // but we still want to ask the identity to logout, so it doesn't try and
    // reuse any old credentials next time we sync.
    this._log.info("Logging out");
    this.identity.logout();
    this._loggedIn = false;

    Svc.Obs.notify("weave:service:logout:finish");
  },

  // Note: returns false if we failed for a reason other than the server not yet
  // supporting the api.
  async _fetchServerConfiguration() {
    // This is similar to _fetchInfo, but with different error handling.

    let infoURL = this.userBaseURL + "info/configuration";
    this._log.debug("Fetching server configuration", infoURL);
    let configResponse;
    try {
      configResponse = await this.resource(infoURL).get();
    } catch (ex) {
      // This is probably a network or similar error.
      this._log.warn("Failed to fetch info/configuration", ex);
      this.errorHandler.checkServerError(ex);
      return false;
    }

    if (configResponse.status == 404) {
      // This server doesn't support the URL yet - that's OK.
      this._log.debug("info/configuration returned 404 - using default upload semantics");
    } else if (configResponse.status != 200) {
      this._log.warn(`info/configuration returned ${configResponse.status} - using default configuration`);
      this.errorHandler.checkServerError(configResponse);
      return false;
    } else {
      this.serverConfiguration = configResponse.obj;
    }
    this._log.trace("info/configuration for this server", this.serverConfiguration);
    return true;
  },

  // Stuff we need to do after login, before we can really do
  // anything (e.g. key setup).
  async _remoteSetup(infoResponse) {
    if (!(await this._fetchServerConfiguration())) {
      return false;
    }

    this._log.debug("Fetching global metadata record");
    let meta = await this.recordManager.get(this.metaURL);

    // Checking modified time of the meta record.
    if (infoResponse &&
        (infoResponse.obj.meta != this.metaModified) &&
        (!meta || !meta.isNew)) {

      // Delete the cached meta record...
      this._log.debug("Clearing cached meta record. metaModified is " +
          JSON.stringify(this.metaModified) + ", setting to " +
          JSON.stringify(infoResponse.obj.meta));

      this.recordManager.del(this.metaURL);

      // ... fetch the current record from the server, and COPY THE FLAGS.
      let newMeta = await this.recordManager.get(this.metaURL);

      // If we got a 401, we do not want to create a new meta/global - we
      // should be able to get the existing meta after we get a new node.
      if (this.recordManager.response.status == 401) {
        this._log.debug("Fetching meta/global record on the server returned 401.");
        this.errorHandler.checkServerError(this.recordManager.response);
        return false;
      }

      if (this.recordManager.response.status == 404) {
        this._log.debug("No meta/global record on the server. Creating one.");
        try {
          await this._uploadNewMetaGlobal();
        } catch (uploadRes) {
          this._log.warn("Unable to upload new meta/global. Failing remote setup.");
          this.errorHandler.checkServerError(uploadRes);
          return false;
        }
      } else if (!newMeta) {
        this._log.warn("Unable to get meta/global. Failing remote setup.");
        this.errorHandler.checkServerError(this.recordManager.response);
        return false;
      } else {
        // If newMeta, then it stands to reason that meta != null.
        newMeta.isNew   = meta.isNew;
        newMeta.changed = meta.changed;
      }

      // Switch in the new meta object and record the new time.
      meta              = newMeta;
      this.metaModified = infoResponse.obj.meta;
    }

    let remoteVersion = (meta && meta.payload.storageVersion) ?
      meta.payload.storageVersion : "";

    this._log.debug(["Weave Version:", WEAVE_VERSION, "Local Storage:",
      STORAGE_VERSION, "Remote Storage:", remoteVersion].join(" "));

    // Check for cases that require a fresh start. When comparing remoteVersion,
    // we need to convert it to a number as older clients used it as a string.
    if (!meta || !meta.payload.storageVersion || !meta.payload.syncID ||
        STORAGE_VERSION > parseFloat(remoteVersion)) {

      this._log.info("One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed.");

      // abort the server wipe if the GET status was anything other than 404 or 200
      let status = this.recordManager.response.status;
      if (status != 200 && status != 404) {
        this.status.sync = METARECORD_DOWNLOAD_FAIL;
        this.errorHandler.checkServerError(this.recordManager.response);
        this._log.warn("Unknown error while downloading metadata record. " +
                       "Aborting sync.");
        return false;
      }

      if (!meta)
        this._log.info("No metadata record, server wipe needed");
      if (meta && !meta.payload.syncID)
        this._log.warn("No sync id, server wipe needed");

      this._log.info("Wiping server data");
      await this._freshStart();

      if (status == 404)
        this._log.info("Metadata record not found, server was wiped to ensure " +
                       "consistency.");
      else // 200
        this._log.info("Wiped server; incompatible metadata: " + remoteVersion);

      return true;
    } else if (remoteVersion > STORAGE_VERSION) {
      this.status.sync = VERSION_OUT_OF_DATE;
      this._log.warn("Upgrade required to access newer storage version.");
      return false;
    } else if (meta.payload.syncID != this.syncID) {

      this._log.info("Sync IDs differ. Local is " + this.syncID + ", remote is " + meta.payload.syncID);
      await this.resetClient();
      this.collectionKeys.clear();
      this.syncID = meta.payload.syncID;
      this._log.debug("Clear cached values and take syncId: " + this.syncID);

      if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) {
        this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
        return false;
      }

      // bug 545725 - re-verify creds and fail sanely
      if (!(await this.verifyLogin())) {
        this.status.sync = CREDENTIALS_CHANGED;
        this._log.info("Credentials have changed, aborting sync and forcing re-login.");
        return false;
      }

      return true;
    }
    if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) {
      this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
      return false;
    }

    return true;
  },

  /**
   * Return whether we should attempt login at the start of a sync.
   *
   * Note that this function has strong ties to _checkSync: callers
   * of this function should typically use _checkSync to verify that
   * any necessary login took place.
   */
  _shouldLogin: function _shouldLogin() {
    return this.enabled &&
           !Services.io.offline &&
           !this.isLoggedIn;
  },

  /**
   * Determine if a sync should run.
   *
   * @param ignore [optional]
   *        array of reasons to ignore when checking
   *
   * @return Reason for not syncing; not-truthy if sync should run
   */
  _checkSync: function _checkSync(ignore) {
    let reason = "";
    if (!this.enabled)
      reason = kSyncWeaveDisabled;
    else if (Services.io.offline)
      reason = kSyncNetworkOffline;
    else if (this.status.minimumNextSync > Date.now())
      reason = kSyncBackoffNotMet;
    else if ((this.status.login == MASTER_PASSWORD_LOCKED) &&
             Utils.mpLocked())
      reason = kSyncMasterPasswordLocked;
    else if (Svc.Prefs.get("firstSync") == "notReady")
      reason = kFirstSyncChoiceNotMade;

    if (ignore && ignore.indexOf(reason) != -1)
      return "";

    return reason;
  },

  async sync(engineNamesToSync) {
    let dateStr = Utils.formatTimestamp(new Date());
    this._log.debug("User-Agent: " + Utils.userAgent);
    this._log.info(`Starting sync at ${dateStr} in browser session ${browserSessionID}`);
    return this._catch(async function() {
      // Make sure we're logged in.
      if (this._shouldLogin()) {
        this._log.debug("In sync: should login.");
        if (!(await this.login())) {
          this._log.debug("Not syncing: login returned false.");
          return;
        }
      } else {
        this._log.trace("In sync: no need to login.");
      }
      await this._lockedSync(engineNamesToSync);
    })();
  },

  /**
   * Sync up engines with the server.
   */
  async _lockedSync(engineNamesToSync) {
    return this._lock("service.js: sync",
                      this._notify("sync", "", async function onNotify() {

      let histogram = Services.telemetry.getHistogramById("WEAVE_START_COUNT");
      histogram.add(1);

      let synchronizer = new EngineSynchronizer(this);
      await synchronizer.sync(engineNamesToSync); // Might throw!

      histogram = Services.telemetry.getHistogramById("WEAVE_COMPLETE_SUCCESS_COUNT");
      histogram.add(1);

      // We successfully synchronized.
      // Check if the identity wants to pre-fetch a migration sentinel from
      // the server.
      // If we have no clusterURL, we are probably doing a node reassignment
      // so don't attempt to get it in that case.
      if (this.clusterURL) {
        this.identity.prefetchMigrationSentinel(this);
      }

      // Now let's update our declined engines (but only if we have a metaURL;
      // if Sync failed due to no node we will not have one)
      if (this.metaURL) {
        let meta = await this.recordManager.get(this.metaURL);
        if (!meta) {
          this._log.warn("No meta/global; can't update declined state.");
          return;
        }

        let declinedEngines = new DeclinedEngines(this);
        let didChange = declinedEngines.updateDeclined(meta, this.engineManager);
        if (!didChange) {
          this._log.info("No change to declined engines. Not reuploading meta/global.");
          return;
        }

        await this.uploadMetaGlobal(meta);
      }
    }))();
  },

  /**
   * Upload a fresh meta/global record
   * @throws the response object if the upload request was not a success
   */
  async _uploadNewMetaGlobal() {
    let meta = new WBORecord("meta", "global");
    meta.payload.syncID = this.syncID;
    meta.payload.storageVersion = STORAGE_VERSION;
    meta.payload.declined = this.engineManager.getDeclined();
    meta.modified = 0;
    meta.isNew = true;

    await this.uploadMetaGlobal(meta);
  },

  /**
   * Upload meta/global, throwing the response on failure
   * @param {WBORecord} meta meta/global record
   * @throws the response object if the request was not a success
   */
  async uploadMetaGlobal(meta) {
    this._log.debug("Uploading meta/global", meta);
    let res = this.resource(this.metaURL);
    res.setHeader("X-If-Unmodified-Since", meta.modified);
    let response = await res.put(meta);
    if (!response.success) {
      throw response;
    }
    // From https://docs.services.mozilla.com/storage/apis-1.5.html:
    // "Successful responses will return the new last-modified time for the collection."
    meta.modified = response.obj;
    this.recordManager.set(this.metaURL, meta);
  },

  /**
   * Upload crypto/keys
   * @param {WBORecord} cryptoKeys crypto/keys record
   * @param {Number} lastModified known last modified timestamp (in decimal seconds),
   *                 will be used to set the X-If-Unmodified-Since header
   */
  async _uploadCryptoKeys(cryptoKeys, lastModified) {
    this._log.debug(`Uploading crypto/keys (lastModified: ${lastModified})`);
    let res = this.resource(this.cryptoKeysURL);
    res.setHeader("X-If-Unmodified-Since", lastModified);
    return res.put(cryptoKeys);
  },

  async _freshStart() {
    this._log.info("Fresh start. Resetting client.");
    await this.resetClient();
    this.collectionKeys.clear();

    // Wipe the server.
    await this.wipeServer();

    // Upload a new meta/global record.
    // _uploadNewMetaGlobal throws on failure -- including race conditions.
    // If we got into a race condition, we'll abort the sync this way, too.
    // That's fine. We'll just wait till the next sync. The client that we're
    // racing is probably busy uploading stuff right now anyway.
    await this._uploadNewMetaGlobal();

    // Wipe everything we know about except meta because we just uploaded it
    // TODO: there's a bug here. We should be calling resetClient, no?

    // Generate, upload, and download new keys. Do this last so we don't wipe
    // them...
    await this.generateNewSymmetricKeys();
  },

  /**
   * Wipe user data from the server.
   *
   * @param collections [optional]
   *        Array of collections to wipe. If not given, all collections are
   *        wiped by issuing a DELETE request for `storageURL`.
   *
   * @return the server's timestamp of the (last) DELETE.
   */
  async wipeServer(collections) {
    let response;
    let histogram = Services.telemetry.getHistogramById("WEAVE_WIPE_SERVER_SUCCEEDED");
    if (!collections) {
      // Strip the trailing slash.
      let res = this.resource(this.storageURL.slice(0, -1));
      res.setHeader("X-Confirm-Delete", "1");
      try {
        response = await res.delete();
      } catch (ex) {
        this._log.debug("Failed to wipe server", ex);
        histogram.add(false);
        throw ex;
      }
      if (response.status != 200 && response.status != 404) {
        this._log.debug("Aborting wipeServer. Server responded with " +
                        response.status + " response for " + this.storageURL);
        histogram.add(false);
        throw response;
      }
      histogram.add(true);
      return response.headers["x-weave-timestamp"];
    }

    let timestamp;
    for (let name of collections) {
      let url = this.storageURL + name;
      try {
        response = await this.resource(url).delete();
      } catch (ex) {
        this._log.debug("Failed to wipe '" + name + "' collection", ex);
        histogram.add(false);
        throw ex;
      }

      if (response.status != 200 && response.status != 404) {
        this._log.debug("Aborting wipeServer. Server responded with " +
                        response.status + " response for " + url);
        histogram.add(false);
        throw response;
      }

      if ("x-weave-timestamp" in response.headers) {
        timestamp = response.headers["x-weave-timestamp"];
      }
    }
    histogram.add(true);
    return timestamp;
  },

  /**
   * Wipe all local user data.
   *
   * @param engines [optional]
   *        Array of engine names to wipe. If not given, all engines are used.
   */
  async wipeClient(engines) {
    // If we don't have any engines, reset the service and wipe all engines
    if (!engines) {
      // Clear out any service data
      await this.resetService();

      engines = [this.clientsEngine].concat(this.engineManager.getAll());
    } else {
      // Convert the array of names into engines
      engines = this.engineManager.get(engines);
    }

    // Fully wipe each engine if it's able to decrypt data
    for (let engine of engines) {
      if ((await engine.canDecrypt())) {
        await engine.wipeClient();
      }
    }
  },

  /**
   * Wipe all remote user data by wiping the server then telling each remote
   * client to wipe itself.
   *
   * @param engines [optional]
   *        Array of engine names to wipe. If not given, all engines are used.
   */
  async wipeRemote(engines) {
    try {
      // Make sure stuff gets uploaded.
      await this.resetClient(engines);

      // Clear out any server data.
      await this.wipeServer(engines);

      // Only wipe the engines provided.
      let extra = { reason: "wipe-remote" };
      if (engines) {
        for (const e of engines) {
          await this.clientsEngine.sendCommand("wipeEngine", [e], null, extra);
        }
      } else {
        // Tell the remote machines to wipe themselves.
        await this.clientsEngine.sendCommand("wipeAll", [], null, extra);
      }

      // Make sure the changed clients get updated.
      await this.clientsEngine.sync();
    } catch (ex) {
      this.errorHandler.checkServerError(ex);
      throw ex;
    }
  },

  /**
   * Reset local service information like logs, sync times, caches.
   */
  async resetService() {
    return this._catch(async function reset() {
      this._log.info("Service reset.");

      // Pretend we've never synced to the server and drop cached data
      this.syncID = "";
      this.recordManager.clearCache();
    })();
  },

  /**
   * Reset the client by getting rid of any local server data and client data.
   *
   * @param engines [optional]
   *        Array of engine names to reset. If not given, all engines are used.
   */
  async resetClient(engines) {
    return this._catch(async function doResetClient() {
      // If we don't have any engines, reset everything including the service
      if (!engines) {
        // Clear out any service data
        await this.resetService();

        engines = [this.clientsEngine].concat(this.engineManager.getAll());
      } else {
        // Convert the array of names into engines
        engines = this.engineManager.get(engines);
      }

      // Have each engine drop any temporary meta data
      for (let engine of engines) {
        await engine.resetClient();
      }
    })();
  },

  /**
   * Fetch storage info from the server.
   *
   * @param type
   *        String specifying what info to fetch from the server. Must be one
   *        of the INFO_* values. See Sync Storage Server API spec for details.
   * @param callback
   *        Callback function with signature (error, data) where `data' is
   *        the return value from the server already parsed as JSON.
   *
   * @return RESTRequest instance representing the request, allowing callers
   *         to cancel the request.
   */
  getStorageInfo: function getStorageInfo(type, callback) {
    if (STORAGE_INFO_TYPES.indexOf(type) == -1) {
      throw new Error(`Invalid value for 'type': ${type}`);
    }

    let info_type = "info/" + type;
    this._log.trace("Retrieving '" + info_type + "'...");
    let url = this.userBaseURL + info_type;
    return this.getStorageRequest(url).get(function onComplete(error) {
      // Note: 'this' is the request.
      if (error) {
        this._log.debug("Failed to retrieve '" + info_type + "'", error);
        return callback(error);
      }
      if (this.response.status != 200) {
        this._log.debug("Failed to retrieve '" + info_type +
                        "': server responded with HTTP" +
                        this.response.status);
        return callback(this.response);
      }

      let result;
      try {
        result = JSON.parse(this.response.body);
      } catch (ex) {
        this._log.debug("Server returned invalid JSON for '" + info_type +
                        "': " + this.response.body);
        return callback(ex);
      }
      this._log.trace("Successfully retrieved '" + info_type + "'.");
      return callback(null, result);
    });
  },

  recordTelemetryEvent(object, method, value, extra = undefined) {
    Svc.Obs.notify("weave:telemetry:event", { object, method, value, extra });
  },
};

this.Service = new Sync11Service();
this.Service.promiseInitialized = new Promise(resolve => {
  this.Service.onStartup().then(resolve);
});
PK
!<S

(modules/services-sync/stages/declined.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains code for maintaining the set of declined engines,
 * in conjunction with EngineManager.
 */

"use strict";

this.EXPORTED_SYMBOLS = ["DeclinedEngines"];

var {utils: Cu} = Components;

Cu.import("resource://services-sync/constants.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Preferences.jsm");



this.DeclinedEngines = function(service) {
  this._log = Log.repository.getLogger("Sync.Declined");
  this._log.level = Log.Level[new Preferences(PREFS_BRANCH).get("log.logger.declined")];

  this.service = service;
}
this.DeclinedEngines.prototype = {
  updateDeclined(meta, engineManager = this.service.engineManager) {
    let enabled = new Set(engineManager.getEnabled().map(e => e.name));
    let known = new Set(engineManager.getAll().map(e => e.name));
    let remoteDeclined = new Set(meta.payload.declined || []);
    let localDeclined = new Set(engineManager.getDeclined());

    this._log.debug("Handling remote declined: " + JSON.stringify([...remoteDeclined]));
    this._log.debug("Handling local declined: " + JSON.stringify([...localDeclined]));

    // Any engines that are locally enabled should be removed from the remote
    // declined list.
    //
    // Any engines that are locally declined should be added to the remote
    // declined list.
    let newDeclined = CommonUtils.union(localDeclined, CommonUtils.difference(remoteDeclined, enabled));

    // If our declined set has changed, put it into the meta object and mark
    // it as changed.
    let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined);
    this._log.debug("Declined changed? " + declinedChanged);
    if (declinedChanged) {
      meta.changed = true;
      meta.payload.declined = [...newDeclined];
    }

    // Update the engine manager regardless.
    engineManager.setDeclined(newDeclined);

    // Any engines that are locally known, locally disabled, and not remotely
    // or locally declined, are candidates for enablement.
    let undecided = CommonUtils.difference(CommonUtils.difference(known, enabled), newDeclined);
    if (undecided.size) {
      let subject = {
        declined: newDeclined,
        enabled,
        known,
        undecided,
      };
      CommonUtils.nextTick(() => {
        Observers.notify("weave:engines:notdeclined", subject);
      });
    }

    return declinedChanged;
  },
};
PK
!<*00*modules/services-sync/stages/enginesync.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains code for synchronizing engines.
 */

this.EXPORTED_SYMBOLS = ["EngineSynchronizer"];

var {utils: Cu} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/async.js");
XPCOMUtils.defineLazyModuleGetter(this, "Doctor",
                                  "resource://services-sync/doctor.js");

/**
 * Perform synchronization of engines.
 *
 * This was originally split out of service.js. The API needs lots of love.
 */
this.EngineSynchronizer = function EngineSynchronizer(service) {
  this._log = Log.repository.getLogger("Sync.Synchronizer");
  this._log.level = Log.Level[Svc.Prefs.get("log.logger.synchronizer")];

  this.service = service;
}

EngineSynchronizer.prototype = {
  async sync(engineNamesToSync) {
    let startTime = Date.now();

    this.service.status.resetSync();

    // Make sure we should sync or record why we shouldn't.
    let reason = this.service._checkSync();
    if (reason) {
      if (reason == kSyncNetworkOffline) {
        this.service.status.sync = LOGIN_FAILED_NETWORK_ERROR;
      }

      // this is a purposeful abort rather than a failure, so don't set
      // any status bits
      reason = "Can't sync: " + reason;
      throw new Error(reason);
    }

    // If we don't have a node, get one. If that fails, retry in 10 minutes.
    if (!this.service.clusterURL && !this.service._clusterManager.setCluster()) {
      this.service.status.sync = NO_SYNC_NODE_FOUND;
      this._log.info("No cluster URL found. Cannot sync.");
      return;
    }

    // Ping the server with a special info request once a day.
    let infoURL = this.service.infoURL;
    let now = Math.floor(Date.now() / 1000);
    let lastPing = Svc.Prefs.get("lastPing", 0);
    if (now - lastPing > 86400) { // 60 * 60 * 24
      infoURL += "?v=" + WEAVE_VERSION;
      Svc.Prefs.set("lastPing", now);
    }

    let engineManager = this.service.engineManager;

    // Figure out what the last modified time is for each collection
    let info = await this.service._fetchInfo(infoURL);

    // Convert the response to an object and read out the modified times
    for (let engine of [this.service.clientsEngine].concat(engineManager.getAll())) {
      engine.lastModified = info.obj[engine.name] || 0;
    }

    if (!(await this.service._remoteSetup(info))) {
      throw new Error("Aborting sync, remote setup failed");
    }

    // Make sure we have an up-to-date list of clients before sending commands
    this._log.debug("Refreshing client list.");
    if (!(await this._syncEngine(this.service.clientsEngine))) {
      // Clients is an engine like any other; it can fail with a 401,
      // and we can elect to abort the sync.
      this._log.warn("Client engine sync failed. Aborting.");
      return;
    }

    // We only honor the "hint" of what engines to Sync if this isn't
    // a first sync.
    let allowEnginesHint = false;
    // Wipe data in the desired direction if necessary
    switch (Svc.Prefs.get("firstSync")) {
      case "resetClient":
        await this.service.resetClient(engineManager.enabledEngineNames);
        break;
      case "wipeClient":
        await this.service.wipeClient(engineManager.enabledEngineNames);
        break;
      case "wipeRemote":
        await this.service.wipeRemote(engineManager.enabledEngineNames);
        break;
      default:
        allowEnginesHint = true;
        break;
    }

    if (this.service.clientsEngine.localCommands) {
      try {
        if (!(await this.service.clientsEngine.processIncomingCommands())) {
          this.service.status.sync = ABORT_SYNC_COMMAND;
          throw new Error("Processed command aborted sync.");
        }

        // Repeat remoteSetup in-case the commands forced us to reset
        if (!(await this.service._remoteSetup(info))) {
          throw new Error("Remote setup failed after processing commands.");
        }
      } finally {
        // Always immediately attempt to push back the local client (now
        // without commands).
        // Note that we don't abort here; if there's a 401 because we've
        // been reassigned, we'll handle it around another engine.
        await this._syncEngine(this.service.clientsEngine);
      }
    }

    // Update engines because it might change what we sync.
    try {
      await this._updateEnabledEngines();
    } catch (ex) {
      this._log.debug("Updating enabled engines failed", ex);
      this.service.errorHandler.checkServerError(ex);
      throw ex;
    }

    // If the engines to sync has been specified, we sync in the order specified.
    let enginesToSync;
    if (allowEnginesHint && engineNamesToSync) {
      this._log.info("Syncing specified engines", engineNamesToSync);
      enginesToSync = engineManager.get(engineNamesToSync).filter(e => e.enabled);
    } else {
      this._log.info("Syncing all enabled engines.");
      enginesToSync = engineManager.getEnabled();
    }
    try {
      // We don't bother validating engines that failed to sync.
      let enginesToValidate = [];
      for (let engine of enginesToSync) {
        // If there's any problems with syncing the engine, report the failure
        if (!(await this._syncEngine(engine)) || this.service.status.enforceBackoff) {
          this._log.info("Aborting sync for failure in " + engine.name);
          break;
        }
        enginesToValidate.push(engine);
      }

      // If _syncEngine fails for a 401, we might not have a cluster URL here.
      // If that's the case, break out of this immediately, rather than
      // throwing an exception when trying to fetch metaURL.
      if (!this.service.clusterURL) {
        this._log.debug("Aborting sync, no cluster URL: " +
                        "not uploading new meta/global.");
        return;
      }

      // Upload meta/global if any engines changed anything.
      let meta = await this.service.recordManager.get(this.service.metaURL);
      if (meta.isNew || meta.changed) {
        this._log.info("meta/global changed locally: reuploading.");
        try {
          await this.service.uploadMetaGlobal(meta);
          delete meta.isNew;
          delete meta.changed;
        } catch (error) {
          this._log.error("Unable to upload meta/global. Leaving marked as new.");
        }
      }

      await Doctor.consult(enginesToValidate);

      // If there were no sync engine failures
      if (this.service.status.service != SYNC_FAILED_PARTIAL) {
        Svc.Prefs.set("lastSync", new Date().toString());
        this.service.status.sync = SYNC_SUCCEEDED;
      }
    } finally {
      Svc.Prefs.reset("firstSync");

      let syncTime = ((Date.now() - startTime) / 1000).toFixed(2);
      let dateStr = Utils.formatTimestamp(new Date());
      this._log.info("Sync completed at " + dateStr
                     + " after " + syncTime + " secs.");
    }
  },

  // Returns true if sync should proceed.
  // false / no return value means sync should be aborted.
  async _syncEngine(engine) {
    try {
      await engine.sync();
    } catch (e) {
      if (e.status == 401) {
        // Maybe a 401, cluster update perhaps needed?
        // We rely on ErrorHandler observing the sync failure notification to
        // schedule another sync and clear node assignment values.
        // Here we simply want to muffle the exception and return an
        // appropriate value.
        return false;
      }
      // Note that policies.js has already logged info about the exception...
      if (Async.isShutdownException(e)) {
        // Failure due to a shutdown exception should prevent other engines
        // trying to start and immediately failing.
        this._log.info(`${engine.name} was interrupted by shutdown; no other engines will sync`);
        return false;
      }
    }

    return true;
  },

  async _updateEnabledFromMeta(meta, numClients, engineManager = this.service.engineManager) {
    this._log.info("Updating enabled engines: " +
                    numClients + " clients.");

    if (meta.isNew || !meta.payload.engines) {
      this._log.debug("meta/global isn't new, or is missing engines. Not updating enabled state.");
      return;
    }

    // If we're the only client, and no engines are marked as enabled,
    // thumb our noses at the server data: it can't be right.
    // Belt-and-suspenders approach to Bug 615926.
    let hasEnabledEngines = false;
    for (let e in meta.payload.engines) {
      if (e != "clients") {
        hasEnabledEngines = true;
        break;
      }
    }

    if ((numClients <= 1) && !hasEnabledEngines) {
      this._log.info("One client and no enabled engines: not touching local engine status.");
      return;
    }

    this.service._ignorePrefObserver = true;

    let enabled = engineManager.enabledEngineNames;

    let toDecline = new Set();
    let toUndecline = new Set();

    for (let engineName in meta.payload.engines) {
      if (engineName == "clients") {
        // Clients is special.
        continue;
      }
      let index = enabled.indexOf(engineName);
      if (index != -1) {
        // The engine is enabled locally. Nothing to do.
        enabled.splice(index, 1);
        continue;
      }
      let engine = engineManager.get(engineName);
      if (!engine) {
        // The engine doesn't exist locally. Nothing to do.
        continue;
      }

      let attemptedEnable = false;
      // If the engine was enabled remotely, enable it locally.
      if (!Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
        this._log.trace("Engine " + engineName + " was enabled. Marking as non-declined.");
        toUndecline.add(engineName);
        this._log.trace(engineName + " engine was enabled remotely.");
        engine.enabled = true;
        // Note that setting engine.enabled to true might not have worked for
        // the password engine if a master-password is enabled.  However, it's
        // still OK that we added it to undeclined - the user *tried* to enable
        // it remotely - so it still winds up as not being flagged as declined
        // even though it's disabled remotely.
        attemptedEnable = true;
      }

      // If either the engine was disabled locally or enabling the engine
      // failed (see above re master-password) then wipe server data and
      // disable it everywhere.
      if (!engine.enabled) {
        this._log.trace("Wiping data for " + engineName + " engine.");
        await engine.wipeServer();
        delete meta.payload.engines[engineName];
        meta.changed = true; // the new enabled state must propagate
        // We also here mark the engine as declined, because the pref
        // was explicitly changed to false - unless we tried, and failed,
        // to enable it - in which case we leave the declined state alone.
        if (!attemptedEnable) {
          // This will be reflected in meta/global in the next stage.
          this._log.trace("Engine " + engineName + " was disabled locally. Marking as declined.");
          toDecline.add(engineName);
        }
      }
    }

    // Any remaining engines were either enabled locally or disabled remotely.
    for (let engineName of enabled) {
      let engine = engineManager.get(engineName);
      if (Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
        this._log.trace("The " + engineName + " engine was enabled locally.");
        toUndecline.add(engineName);
      } else {
        this._log.trace("The " + engineName + " engine was disabled remotely.");

        // Don't automatically mark it as declined!
        engine.enabled = false;
      }
    }

    engineManager.decline(toDecline);
    engineManager.undecline(toUndecline);

    Svc.Prefs.resetBranch("engineStatusChanged.");
    this.service._ignorePrefObserver = false;
  },

  async _updateEnabledEngines() {
    let meta = await this.service.recordManager.get(this.service.metaURL);
    let numClients = this.service.scheduler.numClients;
    let engineManager = this.service.engineManager;

    await this._updateEnabledFromMeta(meta, numClients, engineManager);
  },
};
Object.freeze(EngineSynchronizer.prototype);
PK
!<;::kR
R
modules/services-sync/status.js/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

this.EXPORTED_SYMBOLS = ["Status"];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://services-sync/constants.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://gre/modules/Services.jsm");

this.Status = {
  _log: Log.repository.getLogger("Sync.Status"),
  __authManager: null,
  ready: false,

  get _authManager() {
    if (this.__authManager) {
      return this.__authManager;
    }
    this.__authManager = new BrowserIDManager();
    this.__authManager.initialize();
    return this.__authManager;
  },

  get service() {
    return this._service;
  },

  set service(code) {
    this._log.debug("Status.service: " + (this._service || undefined) + " => " + code);
    this._service = code;
  },

  get login() {
    return this._login;
  },

  set login(code) {
    this._log.debug("Status.login: " + this._login + " => " + code);
    this._login = code;

    if (code == LOGIN_FAILED_NO_USERNAME ||
        code == LOGIN_FAILED_NO_PASSPHRASE) {
      this.service = CLIENT_NOT_CONFIGURED;
    } else if (code != LOGIN_SUCCEEDED) {
      this.service = LOGIN_FAILED;
    } else {
      this.service = STATUS_OK;
    }
  },

  get sync() {
    return this._sync;
  },

  set sync(code) {
    this._log.debug("Status.sync: " + this._sync + " => " + code);
    this._sync = code;
    this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED;
  },

  get eol() {
    let modePref = PREFS_BRANCH + "errorhandler.alert.mode";
    try {
      return Services.prefs.getCharPref(modePref) == "hard-eol";
    } catch (ex) {
      return false;
    }
  },

  get engines() {
    return this._engines;
  },

  set engines([name, code]) {
    this._log.debug("Status for engine " + name + ": " + code);
    this._engines[name] = code;

    if (code != ENGINE_SUCCEEDED) {
      this.service = SYNC_FAILED_PARTIAL;
    }
  },

  // Implement toString because adding a logger introduces a cyclic object
  // value, so we can't trivially debug-print Status as JSON.
  toString: function toString() {
    return "<Status" +
           ": login: " + Status.login +
           ", service: " + Status.service +
           ", sync: " + Status.sync + ">";
  },

  checkSetup: function checkSetup() {
    let result = this._authManager.currentAuthState;
    if (result == STATUS_OK) {
      Status.service = result;
      return result;
    }

    Status.login = result;
    return Status.service;
  },

  resetBackoff: function resetBackoff() {
    this.enforceBackoff = false;
    this.backoffInterval = 0;
    this.minimumNextSync = 0;
  },

  resetSync: function resetSync() {
    // Logger setup.
    let logPref = PREFS_BRANCH + "log.logger.status";
    let logLevel = Services.prefs.getCharPref(logPref, "Trace");
    this._log.level = Log.Level[logLevel];

    this._log.info("Resetting Status.");
    this.service = STATUS_OK;
    this._login = LOGIN_SUCCEEDED;
    this._sync = SYNC_SUCCEEDED;
    this._engines = {};
    this.partial = false;
  }
};

// Initialize various status values.
Status.resetBackoff();
Status.resetSync();
PK
!<F0vZZ"modules/services-sync/telemetry.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["SyncTelemetry"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/async.js");

let constants = {};
Cu.import("resource://services-sync/constants.js", constants);

XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
                              "resource://gre/modules/TelemetryController.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils",
                                  "resource://gre/modules/TelemetryUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                  "resource://gre/modules/TelemetryEnvironment.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
                                   "@mozilla.org/base/telemetry;1",
                                   "nsITelemetry");

const log = Log.repository.getLogger("Sync.Telemetry");

const TOPICS = [
  "profile-before-change",
  "weave:service:sync:start",
  "weave:service:sync:finish",
  "weave:service:sync:error",

  "weave:engine:sync:start",
  "weave:engine:sync:finish",
  "weave:engine:sync:error",
  "weave:engine:sync:applied",
  "weave:engine:sync:uploaded",
  "weave:engine:validate:finish",
  "weave:engine:validate:error",

  "weave:telemetry:event",
];

const PING_FORMAT_VERSION = 1;

const EMPTY_UID = "0".repeat(32);

// The set of engines we record telemetry for - any other engines are ignored.
const ENGINES = new Set(["addons", "bookmarks", "clients", "forms", "history",
                         "passwords", "prefs", "tabs", "extension-storage",
                         "addresses", "creditcards"]);

// A regex we can use to replace the profile dir in error messages. We use a
// regexp so we can simply replace all case-insensitive occurences.
// This escaping function is from:
// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
const reProfileDir = new RegExp(
        OS.Constants.Path.profileDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
        "gi");

function tryGetMonotonicTimestamp() {
  try {
    return Telemetry.msSinceProcessStart();
  } catch (e) {
    log.warn("Unable to get a monotonic timestamp!");
    return -1;
  }
}

function timeDeltaFrom(monotonicStartTime) {
  let now = tryGetMonotonicTimestamp();
  if (monotonicStartTime !== -1 && now !== -1) {
    return Math.round(now - monotonicStartTime);
  }
  return -1;
}

// This function validates the payload of a telemetry "event" - this can be
// removed once there are APIs available for the telemetry modules to collect
// these events (bug 1329530) - but for now we simulate that planned API as
// best we can.
function validateTelemetryEvent(eventDetails) {
  let { object, method, value, extra } = eventDetails;
  // Do do basic validation of the params - everything except "extra" must
  // be a string. method and object are required.
  if (typeof method != "string" || typeof object != "string" ||
      (value && typeof value != "string") ||
      (extra && typeof extra != "object")) {
    log.warn("Invalid event parameters - wrong types", eventDetails);
    return false;
  }
  // length checks.
  if (method.length > 20 || object.length > 20 ||
      (value && value.length > 80)) {
    log.warn("Invalid event parameters - wrong lengths", eventDetails);
    return false;
  }

  // extra can be falsey, or an object with string names and values.
  if (extra) {
    if (Object.keys(extra).length > 10) {
      log.warn("Invalid event parameters - too many extra keys", eventDetails);
      return false;
    }
    for (let [ename, evalue] of Object.entries(extra)) {
      if (typeof ename != "string" || ename.length > 15 ||
          typeof evalue != "string" || evalue.length > 85) {
        log.warn(`Invalid event parameters: extra item "${ename} is invalid`, eventDetails);
        return false;
      }
    }
  }
  return true;
}

class EngineRecord {
  constructor(name) {
    // startTime is in ms from process start, but is monotonic (unlike Date.now())
    // so we need to keep both it and when.
    this.startTime = tryGetMonotonicTimestamp();
    this.name = name;
  }

  toJSON() {
    let result = Object.assign({}, this);
    delete result.startTime;
    return result;
  }

  finished(error) {
    let took = timeDeltaFrom(this.startTime);
    if (took > 0) {
      this.took = took;
    }
    if (error) {
      this.failureReason = SyncTelemetry.transformError(error);
    }
  }

  recordApplied(counts) {
    if (this.incoming) {
      log.error(`Incoming records applied multiple times for engine ${this.name}!`);
      return;
    }
    if (this.name === "clients" && !counts.failed) {
      // ignore successful application of client records
      // since otherwise they show up every time and are meaningless.
      return;
    }

    let incomingData = {};
    let properties = ["applied", "failed", "newFailed", "reconciled"];
    // Only record non-zero properties and only record incoming at all if
    // there's at least one property we care about.
    for (let property of properties) {
      if (counts[property]) {
        incomingData[property] = counts[property];
        this.incoming = incomingData;
      }
    }
  }

  recordValidation(validationResult) {
    if (this.validation) {
      log.error(`Multiple validations occurred for engine ${this.name}!`);
      return;
    }
    let { problems, version, duration, recordCount } = validationResult;
    let validation = {
      version: version || 0,
      checked: recordCount || 0,
    };
    if (duration > 0) {
      validation.took = Math.round(duration);
    }
    let summarized = problems.getSummary(true).filter(({count}) => count > 0);
    if (summarized.length) {
      validation.problems = summarized;
    }
    this.validation = validation;
  }

  recordValidationError(e) {
    if (this.validation) {
      log.error(`Multiple validations occurred for engine ${this.name}!`);
      return;
    }

    this.validation = {
      failureReason: SyncTelemetry.transformError(e)
    };
  }

  recordUploaded(counts) {
    if (counts.sent || counts.failed) {
      if (!this.outgoing) {
        this.outgoing = [];
      }
      this.outgoing.push({
        sent: counts.sent || undefined,
        failed: counts.failed || undefined,
      });
    }
  }
}

class TelemetryRecord {
  constructor(allowedEngines) {
    this.allowedEngines = allowedEngines;
    // Our failure reason. This property only exists in the generated ping if an
    // error actually occurred.
    this.failureReason = undefined;
    this.uid = "";
    this.when = Date.now();
    this.startTime = tryGetMonotonicTimestamp();
    this.took = 0; // will be set later.

    // All engines that have finished (ie, does not include the "current" one)
    // We omit this from the ping if it's empty.
    this.engines = [];
    // The engine that has started but not yet stopped.
    this.currentEngine = null;
  }

  toJSON() {
    let result = {
      when: this.when,
      took: this.took,
      failureReason: this.failureReason,
      status: this.status,
      devices: this.devices,
    };
    let engines = [];
    for (let engine of this.engines) {
      engines.push(engine.toJSON());
    }
    if (engines.length > 0) {
      result.engines = engines;
    }
    return result;
  }

  finished(error) {
    this.took = timeDeltaFrom(this.startTime);
    if (this.currentEngine != null) {
      log.error("Finished called for the sync before the current engine finished");
      this.currentEngine.finished(null);
      this.onEngineStop(this.currentEngine.name);
    }
    if (error) {
      this.failureReason = SyncTelemetry.transformError(error);
    }

    // We don't bother including the "devices" field if we can't come up with a
    // UID or device ID for *this* device -- If that's the case, any data we'd
    // put there would be likely to be full of garbage anyway.
    // Note that we currently use the "sync device GUID" rather than the "FxA
    // device ID" as the latter isn't stable enough for our purposes - see bug
    // 1316535.
    let includeDeviceInfo = false;
    try {
      this.uid = Weave.Service.identity.hashedUID();
      this.deviceID = Weave.Service.identity.hashedDeviceID(Weave.Service.clientsEngine.localID);
      includeDeviceInfo = true;
    } catch (e) {
      this.uid = EMPTY_UID;
      this.deviceID = undefined;
    }

    if (includeDeviceInfo) {
      let remoteDevices = Weave.Service.clientsEngine.remoteClients;
      this.devices = remoteDevices.map(device => {
        return {
          os: device.os,
          version: device.version,
          id: Weave.Service.identity.hashedDeviceID(device.id),
        };
      });
    }

    // Check for engine statuses. -- We do this now, and not in engine.finished
    // to make sure any statuses that get set "late" are recorded
    for (let engine of this.engines) {
      let status = Status.engines[engine.name];
      if (status && status !== constants.ENGINE_SUCCEEDED) {
        engine.status = status;
      }
    }

    let statusObject = {};

    let serviceStatus = Status.service;
    if (serviceStatus && serviceStatus !== constants.STATUS_OK) {
      statusObject.service = serviceStatus;
      this.status = statusObject;
    }
    let syncStatus = Status.sync;
    if (syncStatus && syncStatus !== constants.SYNC_SUCCEEDED) {
      statusObject.sync = syncStatus;
      this.status = statusObject;
    }
  }

  onEngineStart(engineName) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }

    if (this.currentEngine) {
      log.error(`Being told that engine ${engineName} has started, but current engine ${
        this.currentEngine.name} hasn't stopped`);
      // Just discard the current engine rather than making up data for it.
    }
    this.currentEngine = new EngineRecord(engineName);
  }

  onEngineStop(engineName, error) {
    // We only care if it's the current engine if we have a current engine.
    if (this._shouldIgnoreEngine(engineName, !!this.currentEngine)) {
      return;
    }
    if (!this.currentEngine) {
      // It's possible for us to get an error before the start message of an engine
      // (somehow), in which case we still want to record that error.
      if (!error) {
        return;
      }
      log.error(`Error triggered on ${engineName} when no current engine exists: ${error}`);
      this.currentEngine = new EngineRecord(engineName);
    }
    this.currentEngine.finished(error);
    this.engines.push(this.currentEngine);
    this.currentEngine = null;
  }

  onEngineApplied(engineName, counts) {
    if (this._shouldIgnoreEngine(engineName)) {
      return;
    }
    this.currentEngine.recordApplied(counts);
  }

  onEngineValidated(engineName, validationData) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }
    let engine = this.engines.find(e => e.name === engineName);
    if (!engine && this.currentEngine && engineName === this.currentEngine.name) {
      engine = this.currentEngine;
    }
    if (engine) {
      engine.recordValidation(validationData);
    } else {
      log.warn(`Validation event triggered for engine ${engineName}, which hasn't been synced!`);
    }
  }

  onEngineValidateError(engineName, error) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }
    let engine = this.engines.find(e => e.name === engineName);
    if (!engine && this.currentEngine && engineName === this.currentEngine.name) {
      engine = this.currentEngine;
    }
    if (engine) {
      engine.recordValidationError(error);
    } else {
      log.warn(`Validation failure event triggered for engine ${engineName}, which hasn't been synced!`);
    }
  }

  onEngineUploaded(engineName, counts) {
    if (this._shouldIgnoreEngine(engineName)) {
      return;
    }
    this.currentEngine.recordUploaded(counts);
  }

  _shouldIgnoreEngine(engineName, shouldBeCurrent = true) {
    if (!this.allowedEngines.has(engineName)) {
      log.info(`Notification for engine ${engineName}, but we aren't recording telemetry for it`);
      return true;
    }
    if (shouldBeCurrent) {
      if (!this.currentEngine || engineName != this.currentEngine.name) {
        log.error(`Notification for engine ${engineName} but it isn't current`);
        return true;
      }
    }
    return false;
  }
}

function cleanErrorMessage(error) {
  // There's a chance the profiledir is in the error string which is PII we
  // want to avoid including in the ping.
  error = error.replace(reProfileDir, "[profileDir]");
  // MSG_INVALID_URL from /dom/bindings/Errors.msg -- no way to access this
  // directly from JS.
  if (error.endsWith("is not a valid URL.")) {
    error = "<URL> is not a valid URL.";
  }
  // Try to filter things that look somewhat like a URL (in that they contain a
  // colon in the middle of non-whitespace), in case anything else is including
  // these in error messages.
  error = error.replace(/\S+:\S+/g, "<URL>");
  return error;
}

class SyncTelemetryImpl {
  constructor(allowedEngines) {
    log.level = Log.Level[Svc.Prefs.get("log.logger.telemetry", "Trace")];
    // This is accessible so we can enable custom engines during tests.
    this.allowedEngines = allowedEngines;
    this.current = null;
    this.setupObservers();

    this.payloads = [];
    this.discarded = 0;
    this.events = [];
    this.maxEventsCount = Svc.Prefs.get("telemetry.maxEventsCount", 1000);
    this.maxPayloadCount = Svc.Prefs.get("telemetry.maxPayloadCount");
    this.submissionInterval = Svc.Prefs.get("telemetry.submissionInterval") * 1000;
    this.lastSubmissionTime = Telemetry.msSinceProcessStart();
    this.lastUID = EMPTY_UID;
    this.lastDeviceID = undefined;
    let sessionStartDate = Services.startup.getStartupInfo().main;
    this.sessionStartDate = TelemetryUtils.toLocalTimeISOString(
      TelemetryUtils.truncateToHours(sessionStartDate));
  }

  getPingJSON(reason) {
    return {
      os: TelemetryEnvironment.currentEnvironment["system"]["os"],
      why: reason,
      discarded: this.discarded || undefined,
      version: PING_FORMAT_VERSION,
      syncs: this.payloads.slice(),
      uid: this.lastUID,
      deviceID: this.lastDeviceID,
      sessionStartDate: this.sessionStartDate,
      events: this.events.length == 0 ? undefined : this.events,
    };
  }

  finish(reason) {
    // Note that we might be in the middle of a sync right now, and so we don't
    // want to touch this.current.
    let result = this.getPingJSON(reason);
    this.payloads = [];
    this.discarded = 0;
    this.events = [];
    this.submit(result);
  }

  setupObservers() {
    for (let topic of TOPICS) {
      Observers.add(topic, this, this);
    }
  }

  shutdown() {
    this.finish("shutdown");
    for (let topic of TOPICS) {
      Observers.remove(topic, this, this);
    }
  }

  submit(record) {
    if (Services.prefs.prefHasUserValue("identity.sync.tokenserver.uri")) {
      log.trace(`Not sending telemetry ping for self-hosted Sync user`);
      return false;
    }
    // We still call submit() with possibly illegal payloads so that tests can
    // know that the ping was built. We don't end up submitting them, however.
    if (record.syncs.length) {
      log.trace(`submitting ${record.syncs.length} sync record(s) to telemetry`);
      TelemetryController.submitExternalPing("sync", record);
      return true;
    }
    return false;
  }


  onSyncStarted() {
    if (this.current) {
      log.warn("Observed weave:service:sync:start, but we're already recording a sync!");
      // Just discard the old record, consistent with our handling of engines, above.
      this.current = null;
    }
    this.current = new TelemetryRecord(this.allowedEngines);
  }

  _checkCurrent(topic) {
    if (!this.current) {
      log.warn(`Observed notification ${topic} but no current sync is being recorded.`);
      return false;
    }
    return true;
  }

  shouldSubmitForIDChange(newUID, newDeviceID) {
    if (newUID != EMPTY_UID && this.lastUID != EMPTY_UID) {
      // Both are "real" uids, so we care if they've changed.
      return newUID != this.lastUID;
    }
    if (newDeviceID && this.lastDeviceID) {
      // Both are "real" device IDs, so we care if they've changed.
      return newDeviceID != this.lastDeviceID;
    }
    // We've gone from knowing one of the ids to not knowing it (which we
    // ignore) or we've gone from not knowing it to knowing it (which is fine),
    // so we shouldn't submit.
    return false;
  }

  onSyncFinished(error) {
    if (!this.current) {
      log.warn("onSyncFinished but we aren't recording");
      return;
    }
    this.current.finished(error);
    if (this.payloads.length) {
      if (this.shouldSubmitForIDChange(this.current.uid, this.current.deviceID)) {
        log.info("Early submission of sync telemetry due to changed IDs");
        this.finish("idchange");
        this.lastSubmissionTime = Telemetry.msSinceProcessStart();
      }
    }
    // Only update the last UIDs or device IDs if we actually know them.
    if (this.current.uid !== EMPTY_UID) {
      this.lastUID = this.current.uid;
    }
    if (this.current.deviceID) {
      this.lastDeviceID = this.current.deviceID;
    }
    if (this.payloads.length < this.maxPayloadCount) {
      this.payloads.push(this.current.toJSON());
    } else {
      ++this.discarded;
    }
    this.current = null;
    if ((Telemetry.msSinceProcessStart() - this.lastSubmissionTime) > this.submissionInterval) {
      this.finish("schedule");
      this.lastSubmissionTime = Telemetry.msSinceProcessStart();
    }
  }

  _recordEvent(eventDetails) {
    if (this.events.length >= this.maxEventsCount) {
      log.warn("discarding event - already queued our maximum", eventDetails);
      return;
    }

    if (!validateTelemetryEvent(eventDetails)) {
      // we've already logged what the problem is...
      return;
    }
    log.debug("recording event", eventDetails);

    let { object, method, value, extra } = eventDetails;
    if (extra && AsyncResource.serverTime && !extra.serverTime) {
      extra.serverTime = String(AsyncResource.serverTime);
    }
    let category = "sync";
    let ts = Math.floor(tryGetMonotonicTimestamp());

    // An event record is a simple array with at least 4 items.
    let event = [ts, category, method, object];
    // It may have up to 6 elements if |extra| is defined
    if (value) {
      event.push(value);
      if (extra) {
        event.push(extra);
      }
    } else if (extra) {
        event.push(null); // a null for the empty value.
        event.push(extra);
      }
    this.events.push(event);
  }

  observe(subject, topic, data) {
    log.trace(`observed ${topic} ${data}`);

    switch (topic) {
      case "profile-before-change":
        this.shutdown();
        break;

      /* sync itself state changes */
      case "weave:service:sync:start":
        this.onSyncStarted();
        break;

      case "weave:service:sync:finish":
        if (this._checkCurrent(topic)) {
          this.onSyncFinished(null);
        }
        break;

      case "weave:service:sync:error":
        // argument needs to be truthy (this should always be the case)
        this.onSyncFinished(subject || "Unknown");
        break;

      /* engine sync state changes */
      case "weave:engine:sync:start":
        if (this._checkCurrent(topic)) {
          this.current.onEngineStart(data);
        }
        break;
      case "weave:engine:sync:finish":
        if (this._checkCurrent(topic)) {
          this.current.onEngineStop(data, null);
        }
        break;

      case "weave:engine:sync:error":
        if (this._checkCurrent(topic)) {
          // argument needs to be truthy (this should always be the case)
          this.current.onEngineStop(data, subject || "Unknown");
        }
        break;

      /* engine counts */
      case "weave:engine:sync:applied":
        if (this._checkCurrent(topic)) {
          this.current.onEngineApplied(data, subject);
        }
        break;

      case "weave:engine:sync:uploaded":
        if (this._checkCurrent(topic)) {
          this.current.onEngineUploaded(data, subject);
        }
        break;

      case "weave:engine:validate:finish":
        if (this._checkCurrent(topic)) {
          this.current.onEngineValidated(data, subject);
        }
        break;

      case "weave:engine:validate:error":
        if (this._checkCurrent(topic)) {
          this.current.onEngineValidateError(data, subject || "Unknown");
        }
        break;

      case "weave:telemetry:event":
        this._recordEvent(subject);
        break;

      default:
        log.warn(`unexpected observer topic ${topic}`);
        break;
    }
  }

  // Transform an exception into a standard description. Exposed here for when
  // this module isn't directly responsible for knowing the transform should
  // happen (for example, when including an error in the |extra| field of
  // event telemetry)
  transformError(error) {
    if (Async.isShutdownException(error)) {
      return { name: "shutdownerror" };
    }

    if (typeof error === "string") {
      if (error.startsWith("error.")) {
        // This is hacky, but I can't imagine that it's not also accurate.
        return { name: "othererror", error };
      }
      error = cleanErrorMessage(error);
      return { name: "unexpectederror", error };
    }

    if (error.failureCode) {
      return { name: "othererror", error: error.failureCode };
    }

    if (error instanceof AuthenticationError) {
      return { name: "autherror", from: error.source };
    }

    if (error instanceof Ci.mozIStorageError) {
      return { name: "sqlerror", code: error.result };
    }

    let httpCode = error.status ||
      (error.response && error.response.status) ||
      error.code;

    if (httpCode) {
      return { name: "httperror", code: httpCode };
    }

    if (error.result) {
      return { name: "nserror", code: error.result };
    }

    return {
      name: "unexpectederror",
      error: cleanErrorMessage(String(error))
    };
  }

}

/* global SyncTelemetry */
this.SyncTelemetry = new SyncTelemetryImpl(ENGINES);
PK
!<QQmodules/services-sync/util.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["Utils", "Svc"];

var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;

Cu.import("resource://services-common/observers.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

// FxAccountsCommon.js doesn't use a "namespace", so create one here.
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
  let FxAccountsCommon = {};
  Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
  return FxAccountsCommon;
});

/*
 * Custom exception types.
 */
class LockException extends Error {
  constructor(message) {
    super(message);
    this.name = "LockException";
  }
}

class HMACMismatch extends Error {
  constructor(message) {
    super(message);
    this.name = "HMACMismatch";
  }
}

/*
 * Utility functions
 */
this.Utils = {
  // Alias in functions from CommonUtils. These previously were defined here.
  // In the ideal world, references to these would be removed.
  nextTick: CommonUtils.nextTick,
  namedTimer: CommonUtils.namedTimer,
  makeURI: CommonUtils.makeURI,
  encodeUTF8: CommonUtils.encodeUTF8,
  decodeUTF8: CommonUtils.decodeUTF8,
  safeAtoB: CommonUtils.safeAtoB,
  byteArrayToString: CommonUtils.byteArrayToString,
  bytesAsHex: CommonUtils.bytesAsHex,
  hexToBytes: CommonUtils.hexToBytes,
  encodeBase32: CommonUtils.encodeBase32,
  decodeBase32: CommonUtils.decodeBase32,

  // Aliases from CryptoUtils.
  generateRandomBytes: CryptoUtils.generateRandomBytes,
  computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
  digestUTF8: CryptoUtils.digestUTF8,
  digestBytes: CryptoUtils.digestBytes,
  sha1: CryptoUtils.sha1,
  sha1Base32: CryptoUtils.sha1Base32,
  sha256: CryptoUtils.sha256,
  makeHMACKey: CryptoUtils.makeHMACKey,
  makeHMACHasher: CryptoUtils.makeHMACHasher,
  hkdfExpand: CryptoUtils.hkdfExpand,
  pbkdf2Generate: CryptoUtils.pbkdf2Generate,
  getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,

  /**
   * The string to use as the base User-Agent in Sync requests.
   * This string will look something like
   *
   *   Firefox/49.0a1 (Windows NT 6.1; WOW64; rv:46.0) FxSync/1.51.0.20160516142357.desktop
   */
  _userAgent: null,
  get userAgent() {
    if (!this._userAgent) {
      let hph = Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler);
      this._userAgent =
        Services.appinfo.name + "/" + Services.appinfo.version +  // Product.
        " (" + hph.oscpu + ")" +                                  // (oscpu)
        " FxSync/" + WEAVE_VERSION + "." +                        // Sync.
        Services.appinfo.appBuildID + ".";                        // Build.
    }
    return this._userAgent + Svc.Prefs.get("client.type", "desktop");
  },

  /**
   * Wrap a [promise-returning] function to catch all exceptions and log them.
   *
   * @usage MyObj._catch = Utils.catch;
   *        MyObj.foo = function() { this._catch(func)(); }
   *
   * Optionally pass a function which will be called if an
   * exception occurs.
   */
  catch(func, exceptionCallback) {
    let thisArg = this;
    return async function WrappedCatch() {
      try {
        return await func.call(thisArg);
      } catch (ex) {
        thisArg._log.debug("Exception calling " + (func.name || "anonymous function"), ex);
        if (exceptionCallback) {
          return exceptionCallback.call(thisArg, ex);
        }
        return null;
      }
    };
  },

  throwLockException(label) {
    throw new LockException(`Could not acquire lock. Label: "${label}".`);
  },

  /**
   * Wrap a [promise-returning] function to call lock before calling the function
   * then unlock when it finishes executing or if it threw an error.
   *
   * @usage MyObj._lock = Utils.lock;
   *        MyObj.foo = async function() { await this._lock(func)(); }
   */
  lock(label, func) {
    let thisArg = this;
    return async function WrappedLock() {
      if (!thisArg.lock()) {
        Utils.throwLockException(label);
      }

      try {
        return await func.call(thisArg);
      } finally {
        thisArg.unlock();
      }
    };
  },

  isLockException: function isLockException(ex) {
    return ex instanceof LockException;
  },

  /**
   * Wrap [promise-returning] functions to notify when it starts and
   * finishes executing or if it threw an error.
   *
   * The message is a combination of a provided prefix, the local name, and
   * the event. Possible events are: "start", "finish", "error". The subject
   * is the function's return value on "finish" or the caught exception on
   * "error". The data argument is the predefined data value.
   *
   * Example:
   *
   * @usage function MyObj(name) {
   *          this.name = name;
   *          this._notify = Utils.notify("obj:");
   *        }
   *        MyObj.prototype = {
   *          foo: function() this._notify("func", "data-arg", async function () {
   *            //...
   *          }(),
   *        };
   */
  notify(prefix) {
    return function NotifyMaker(name, data, func) {
      let thisArg = this;
      let notify = function(state, subject) {
        let mesg = prefix + name + ":" + state;
        thisArg._log.trace("Event: " + mesg);
        Observers.notify(mesg, subject, data);
      };

      return async function WrappedNotify() {
        notify("start", null);
        try {
          let ret = await func.call(thisArg);
          notify("finish", ret);
          return ret;
        } catch (ex) {
          notify("error", ex);
          throw ex;
        }
      };
    };
  },

  /**
   * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
   * That makes them 12 characters long with 72 bits of entropy.
   */
  makeGUID: function makeGUID() {
    return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
  },

  _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
  checkGUID: function checkGUID(guid) {
    return !!guid && this._base64url_regex.test(guid);
  },

  /**
   * Add a simple getter/setter to an object that defers access of a property
   * to an inner property.
   *
   * @param obj
   *        Object to add properties to defer in its prototype
   * @param defer
   *        Property of obj to defer to
   * @param prop
   *        Property name to defer (or an array of property names)
   */
  deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
    if (Array.isArray(prop))
      return prop.map(prop => Utils.deferGetSet(obj, defer, prop));

    let prot = obj.prototype;

    // Create a getter if it doesn't exist yet
    if (!prot.__lookupGetter__(prop)) {
      prot.__defineGetter__(prop, function() {
        return this[defer][prop];
      });
    }

    // Create a setter if it doesn't exist yet
    if (!prot.__lookupSetter__(prop)) {
      prot.__defineSetter__(prop, function(val) {
        this[defer][prop] = val;
      });
    }
  },

  deepEquals: function eq(a, b) {
    // If they're triple equals, then it must be equals!
    if (a === b)
      return true;

    // If they weren't equal, they must be objects to be different
    if (typeof a != "object" || typeof b != "object")
      return false;

    // But null objects won't have properties to compare
    if (a === null || b === null)
      return false;

    // Make sure all of a's keys have a matching value in b
    for (let k in a)
      if (!eq(a[k], b[k]))
        return false;

    // Do the same for b's keys but skip those that we already checked
    for (let k in b)
      if (!(k in a) && !eq(a[k], b[k]))
        return false;

    return true;
  },

  // Generator and discriminator for HMAC exceptions.
  // Split these out in case we want to make them richer in future, and to
  // avoid inevitable confusion if the message changes.
  throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
    throw new HMACMismatch(
        `Record SHA256 HMAC mismatch: should be ${shouldBe}, is ${is}`);
  },

  isHMACMismatch: function isHMACMismatch(ex) {
    return ex instanceof HMACMismatch;
  },

  /**
   * Turn RFC 4648 base32 into our own user-friendly version.
   *   ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
   * becomes
   *   abcdefghijk8mn9pqrstuvwxyz234567
   */
  base32ToFriendly: function base32ToFriendly(input) {
    return input.toLowerCase()
                .replace(/l/g, "8")
                .replace(/o/g, "9");
  },

  base32FromFriendly: function base32FromFriendly(input) {
    return input.toUpperCase()
                .replace(/8/g, "L")
                .replace(/9/g, "O");
  },

  /**
   * Key manipulation.
   */

  // Return an octet string in friendly base32 *with no trailing =*.
  encodeKeyBase32: function encodeKeyBase32(keyData) {
    return Utils.base32ToFriendly(
             Utils.encodeBase32(keyData))
           .slice(0, SYNC_KEY_ENCODED_LENGTH);
  },

  decodeKeyBase32: function decodeKeyBase32(encoded) {
    return Utils.decodeBase32(
             Utils.base32FromFriendly(
               Utils.normalizePassphrase(encoded)))
           .slice(0, SYNC_KEY_DECODED_LENGTH);
  },

  jsonFilePath(filePath) {
    return OS.Path.normalize(OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json"));
  },

  /**
   * Load a JSON file from disk in the profile directory.
   *
   * @param filePath
   *        JSON file path load from profile. Loaded file will be
   *        <profile>/<filePath>.json. i.e. Do not specify the ".json"
   *        extension.
   * @param that
   *        Object to use for logging.
   *
   * @return Promise<>
   *        Promise resolved when the write has been performed.
   */
  async jsonLoad(filePath, that) {
    let path = Utils.jsonFilePath(filePath);

    if (that._log && that._log.trace) {
      that._log.trace("Loading json from disk: " + filePath);
    }

    try {
      return await CommonUtils.readJSON(path);
    } catch (e) {
      if (!(e instanceof OS.File.Error && e.becauseNoSuchFile)) {
        if (that._log) {
          that._log.debug("Failed to load json", e);
        }
      }
      return null;
    }
  },

  /**
   * Save a json-able object to disk in the profile directory.
   *
   * @param filePath
   *        JSON file path save to <filePath>.json
   * @param that
   *        Object to use for logging.
   * @param obj
   *        Function to provide json-able object to save. If this isn't a
   *        function, it'll be used as the object to make a json string.*
   *        Function called when the write has been performed. Optional.
   *
   * @return Promise<>
   *        Promise resolved when the write has been performed.
   */
  async jsonSave(filePath, that, obj) {
    let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
                            ...(filePath + ".json").split("/"));
    let dir = OS.Path.dirname(path);

    await OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir });

    if (that._log) {
      that._log.trace("Saving json to disk: " + path);
    }

    let json = typeof obj == "function" ? obj.call(that) : obj;

    return CommonUtils.writeJSON(json, path);
  },

  /**
   * Move a json file in the profile directory. Will fail if a file exists at the
   * destination.
   *
   * @returns a promise that resolves to undefined on success, or rejects on failure
   *
   * @param aFrom
   *        Current path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param aTo
   *        New path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param that
   *        Object to use for logging
   */
  jsonMove(aFrom, aTo, that) {
    let pathFrom = OS.Path.join(OS.Constants.Path.profileDir, "weave",
                                ...(aFrom + ".json").split("/"));
    let pathTo = OS.Path.join(OS.Constants.Path.profileDir, "weave",
                              ...(aTo + ".json").split("/"));
    if (that._log) {
      that._log.trace("Moving " + pathFrom + " to " + pathTo);
    }
    return OS.File.move(pathFrom, pathTo, { noOverwrite: true });
  },

  /**
   * Removes a json file in the profile directory.
   *
   * @returns a promise that resolves to undefined on success, or rejects on failure
   *
   * @param filePath
   *        Current path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param that
   *        Object to use for logging
   */
  jsonRemove(filePath, that) {
    let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
                            ...(filePath + ".json").split("/"));
    if (that._log) {
      that._log.trace("Deleting " + path);
    }
    return OS.File.remove(path, { ignoreAbsent: true });
  },

  /**
   * The following are the methods supported for UI use:
   *
   * * isPassphrase:
   *     determines whether a string is either a normalized or presentable
   *     passphrase.
   * * normalizePassphrase:
   *     take a presentable passphrase and reduce it to a normalized
   *     representation for storage. normalizePassphrase can safely be called
   *     on normalized input.
   */

  isPassphrase(s) {
    if (s) {
      return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
    }
    return false;
  },

  normalizePassphrase: function normalizePassphrase(pp) {
    // Short var name... have you seen the lines below?!
    // Allow leading and trailing whitespace.
    pp = pp.trim().toLowerCase();

    // 20-char sync key.
    if (pp.length == 23 &&
        [5, 11, 17].every(i => pp[i] == "-")) {

      return pp.slice(0, 5) + pp.slice(6, 11)
             + pp.slice(12, 17) + pp.slice(18, 23);
    }

    // "Modern" 26-char key.
    if (pp.length == 31 &&
        [1, 7, 13, 19, 25].every(i => pp[i] == "-")) {

      return pp.slice(0, 1) + pp.slice(2, 7)
             + pp.slice(8, 13) + pp.slice(14, 19)
             + pp.slice(20, 25) + pp.slice(26, 31);
    }

    // Something else -- just return.
    return pp;
  },

  /**
   * Create an array like the first but without elements of the second. Reuse
   * arrays if possible.
   */
  arraySub: function arraySub(minuend, subtrahend) {
    if (!minuend.length || !subtrahend.length)
      return minuend;
    let setSubtrahend = new Set(subtrahend);
    return minuend.filter(i => !setSubtrahend.has(i));
  },

  /**
   * Build the union of two arrays. Reuse arrays if possible.
   */
  arrayUnion: function arrayUnion(foo, bar) {
    if (!foo.length)
      return bar;
    if (!bar.length)
      return foo;
    return foo.concat(Utils.arraySub(bar, foo));
  },

  bind2: function Async_bind2(object, method) {
    return function innerBind() { return method.apply(object, arguments); };
  },

  /**
   * Is there a master password configured, regardless of current lock state?
   */
  mpEnabled: function mpEnabled() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                    .getService(Ci.nsIPK11TokenDB);
    let token = tokenDB.getInternalKeyToken();
    return token.hasPassword;
  },

  /**
   * Is there a master password configured and currently locked?
   */
  mpLocked: function mpLocked() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                    .getService(Ci.nsIPK11TokenDB);
    let token = tokenDB.getInternalKeyToken();
    return token.hasPassword && !token.isLoggedIn();
  },

  // If Master Password is enabled and locked, present a dialog to unlock it.
  // Return whether the system is unlocked.
  ensureMPUnlocked: function ensureMPUnlocked() {
    if (!Utils.mpLocked()) {
      return true;
    }
    let sdr = Cc["@mozilla.org/security/sdr;1"]
                .getService(Ci.nsISecretDecoderRing);
    try {
      sdr.encryptString("bacon");
      return true;
    } catch (e) {}
    return false;
  },

  /**
   * Return a value for a backoff interval.  Maximum is eight hours, unless
   * Status.backoffInterval is higher.
   *
   */
  calculateBackoff: function calculateBackoff(attempts, baseInterval,
                                              statusInterval) {
    let backoffInterval = attempts *
                          (Math.floor(Math.random() * baseInterval) +
                           baseInterval);
    return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
                    statusInterval);
  },

  /**
   * Return a set of hostnames (including the protocol) which may have
   * credentials for sync itself stored in the login manager.
   *
   * In general, these hosts will not have their passwords synced, will be
   * reset when we drop sync credentials, etc.
   */
  getSyncCredentialsHosts() {
    let result = new Set(this.getSyncCredentialsHostsLegacy());
    for (let host of this.getSyncCredentialsHostsFxA()) {
      result.add(host);
    }
    return result;
  },

  /*
   * Get the "legacy" identity hosts.
   */
  getSyncCredentialsHostsLegacy() {
    // the legacy sync host
    return new Set([PWDMGR_HOST]);
  },

  /*
   * Get the FxA identity hosts.
   */
  getSyncCredentialsHostsFxA() {
    let result = new Set();
    // the FxA host
    result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
    // We used to include the FxA hosts (hence the Set() result) but we now
    // don't give them special treatment (hence the Set() with exactly 1 item)
    return result;
  },

  getDefaultDeviceName() {
    // Generate a client name if we don't have a useful one yet
    let env = Cc["@mozilla.org/process/environment;1"]
                .getService(Ci.nsIEnvironment);
    let user = env.get("USER") || env.get("USERNAME") ||
               Svc.Prefs.get("account") || Svc.Prefs.get("username");
    // A little hack for people using the the moz-build environment on Windows
    // which sets USER to the literal "%USERNAME%" (yes, really)
    if (user == "%USERNAME%" && env.get("USERNAME")) {
      user = env.get("USERNAME");
    }

    let brand = Services.strings.createBundle(
      "chrome://branding/locale/brand.properties");
    let brandName = brand.GetStringFromName("brandShortName");

    // The DNS service may fail to provide a hostname in edge-cases we don't
    // fully understand - bug 1391488.
    let hostname;
    try {
      // hostname of the system, usually assigned by the user or admin
      hostname = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
    } catch (ex) {
      Cu.reportError(ex);
    }
    let system =
      // 'device' is defined on unix systems
      Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
      hostname ||
      // fall back on ua info string
      Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;

    let syncStrings = Services.strings.createBundle("chrome://weave/locale/sync.properties");
    return syncStrings.formatStringFromName("client.name2", [user, brandName, system], 3);
  },

  getDeviceName() {
    const deviceName = Svc.Prefs.get("client.name", "");

    if (deviceName === "") {
      return this.getDefaultDeviceName();
    }

    return deviceName;
  },

  getDeviceType() {
    return Svc.Prefs.get("client.type", DEVICE_TYPE_DESKTOP);
  },

  formatTimestamp(date) {
    // Format timestamp as: "%Y-%m-%d %H:%M:%S"
    let year = String(date.getFullYear());
    let month = String(date.getMonth() + 1).padStart(2, "0");
    let day = String(date.getDate()).padStart(2, "0");
    let hours = String(date.getHours()).padStart(2, "0");
    let minutes = String(date.getMinutes()).padStart(2, "0");
    let seconds = String(date.getSeconds()).padStart(2, "0");

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  }
};

XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  return converter;
});

/*
 * Commonly-used services
 */
this.Svc = {};
Svc.Prefs = new Preferences(PREFS_BRANCH);
Svc.Obs = Observers;

Svc.Obs.add("xpcom-shutdown", function() {
  for (let name in Svc)
    delete Svc[name];
});
PK
!<<u1'%modules/sessionstore/PrivacyLevel.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If 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(url) {
    return PrivacyLevel.canSave(url.startsWith("https:"));
  },

  /**
   * Checks whether we're allowed to save data for a specific site.
   *
   * @param isHttps A boolean that tells whether the site uses TLS.
   * @return {bool} Whether we can save data for the specified site.
   */
  canSave(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;
  }
});
PK
!<6DdLL'modules/sessionstore/SessionHistory.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If 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");
XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
  "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");

function debug(msg) {
  Services.console.logStringMessage("SessionHistory: " + msg);
}

/**
 * The external API exported by this module.
 */
this.SessionHistory = Object.freeze({
  isEmpty(docShell) {
    return SessionHistoryInternal.isEmpty(docShell);
  },

  collect(docShell, aFromIdx = -1) {
    return SessionHistoryInternal.collect(docShell, aFromIdx);
  },

  restore(docShell, tabData) {
    return SessionHistoryInternal.restore(docShell, tabData);
  }
});

/**
 * The internal API for the SessionHistory module.
 */
var SessionHistoryInternal = {
  /**
   * Mapping from legacy docshellIDs to docshellUUIDs.
   */
  _docshellUUIDMap: new Map(),

  /**
   * Returns whether the given docShell's session history is empty.
   *
   * @param docShell
   *        The docShell that owns the session history.
   */
  isEmpty(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.
   * @param aFromIdx
   *        The starting local index to collect the history from.
   * @return An object reprereseting a partial global history update.
   */
  collect(docShell, aFromIdx = -1) {
    let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
    let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
    let ihistory = history.QueryInterface(Ci.nsISHistory);

    let data = {entries: [], userContextId: loadContext.originAttributes.userContextId };
    // We want to keep track how many entries we *could* have collected and
    // how many we skipped, so we can sanitiy-check the current history index
    // and also determine whether we need to get any fallback data or not.
    let skippedCount = 0, entryCount = 0;

    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; entryCount++, txn = txn.next) {
        if (entryCount <= aFromIdx) {
          skippedCount++;
          continue;
        }
        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, entryCount);
    }

    // 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,
    // unless of course we just skipped all entries because aFromIdx was big enough.
    if (data.entries.length == 0 && (skippedCount != entryCount || aFromIdx < 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,
          triggeringPrincipal_base64: Utils.SERIALIZED_SYSTEMPRINCIPAL
        });
        data.index = 1;
      }
    }

    // Transform the entries from local to global index space.
    data.index += ihistory.globalIndexOffset;
    data.fromIdx = aFromIdx + ihistory.globalIndexOffset;

    // If we are not the most recent partialSHistory in our groupedSHistory, we
    // need to make certain that we don't replace the entries from the following
    // SHistories - so we replace only the number of entries which our SHistory
    // takes up.
    if (ihistory.globalIndexOffset + ihistory.count < ihistory.globalCount) {
      data.toIdx = data.fromIdx + ihistory.count;
    }

    return data;
  },

  /**
   * Get an object that is a serialized representation of a History entry.
   *
   * @param shEntry
   *        nsISHEntry instance
   * @return object
   */
  serializeEntry(shEntry) {
    let entry = { url: shEntry.URI.spec, 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.docshellUUID = shEntry.docshellID.toString();

    // 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.resultPrincipalURI) {
      entry.resultPrincipalURI = shEntry.resultPrincipalURI.spec;

      // For downgrade compatibility we store the loadReplace property as it
      // would be stored before result principal URI introduction so that
      // the old code can still create URL based principals for channels
      // correctly.  When resultPrincipalURI is non-null and not equal to
      // channel's orignalURI in the new code, it's equal to setting
      // LOAD_REPLACE in the old code.  Note that we only do 'the best we can'
      // here to derivate the 'old' loadReplace flag value.
      entry.loadReplace = entry.resultPrincipalURI != entry.originalURI;
    } else {
      // We want to store the property to let the backward compatibility code,
      // when reading the stored session, work. When this property is undefined
      // that code will derive the result principal URI from the load replace
      // flag.
      entry.resultPrincipalURI = null;
    }

    if (shEntry.loadReplace) {
      // Storing under a new property name, since it has changed its meaning
      // with the result principal URI introduction.
      entry.loadReplace2 = 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;
      }

      let layoutHistoryState = shEntry.layoutHistoryState;
      if (layoutHistoryState && layoutHistoryState.hasStates) {
        let presStates = layoutHistoryState.getKeys().map(key =>
          this._getSerializablePresState(layoutHistoryState, key)).filter(presState =>
            // Only keep presState entries that contain more than the key itself.
            Object.getOwnPropertyNames(presState).length > 1);

        if (presStates.length > 0) {
          entry.presState = presStates;
        }
      }
    }

    // Collect triggeringPrincipal data for the current history entry.
    if (shEntry.principalToInherit) {
      try {
        let principalToInherit = Utils.serializePrincipal(shEntry.principalToInherit);
        if (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;
  },

  /**
   * Get an object that is a serializable representation of a PresState.
   *
   * @param layoutHistoryState
   *        nsILayoutHistoryState instance
   * @param stateKey
   *        The state key of the presState to be retrieved.
   * @return object
   */
  _getSerializablePresState(layoutHistoryState, stateKey) {
    let presState = { stateKey };
    let x = {}, y = {}, scrollOriginDowngrade = {}, res = {}, scaleToRes = {};

    layoutHistoryState.getPresState(stateKey, x, y, scrollOriginDowngrade, res, scaleToRes);
    if (x.value !== 0 || y.value !== 0) {
      presState.scroll = x.value + "," + y.value;
    }
    if (scrollOriginDowngrade.value === false) {
      presState.scrollOriginDowngrade = scrollOriginDowngrade.value;
    }
    if (res.value != 1.0) {
      presState.res = res.value;
    }
    if (scaleToRes.value === true) {
      presState.scaleToRes = scaleToRes.value;
    }

    return presState;
  },

  /**
   * 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.
   * @return A reference to the docShell's nsISHistoryInternal interface.
   */
  restore(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);
    }
    return history;
  },

  /**
   * 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(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 (typeof entry.resultPrincipalURI === "undefined" && entry.loadReplace) {
      // This is backward compatibility code for stored sessions saved prior to
      // introduction of the resultPrincipalURI property.  The equivalent of this
      // property non-null value used to be the URL while the LOAD_REPLACE flag
      // was set.
      shEntry.resultPrincipalURI = shEntry.URI;
    } else if (entry.resultPrincipalURI) {
      shEntry.resultPrincipalURI = Utils.makeURI(entry.resultPrincipalURI);
    }
    if (entry.loadReplace2) {
      shEntry.loadReplace = entry.loadReplace2;
    }
    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 we have the legacy docshellID on our entry, upgrade it to a
    // docshellUUID by going through the mapping.
    if (entry.docshellID) {
      if (!this._docshellUUIDMap.has(entry.docshellID)) {
        // Convert the nsID to a string so that the docshellUUID property
        // is correctly stored as a string.
        this._docshellUUIDMap.set(entry.docshellID,
                                  uuidGenerator.generateUUID().toString());
      }
      entry.docshellUUID = this._docshellUUIDMap.get(entry.docshellID);
      delete entry.docshellID;
    }

    if (entry.docshellUUID) {
      shEntry.docshellID = Components.ID(entry.docshellUUID);
    }

    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) {
        shEntry.setScrollPosition(...this._deserializeScrollPosition(entry.scroll));
      }

      if (entry.presState) {
        let layoutHistoryState = shEntry.initLayoutHistoryState();

        for (let presState of entry.presState) {
          this._deserializePresState(layoutHistoryState, presState);
        }
      }
    }

    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, childDocIdents};
        docIdentMap[entry.docIdentifier] = matchingEntry;
      } else {
        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
        childDocIdents = matchingEntry.childDocIdents;
      }
    }

    if (entry.triggeringPrincipal_base64) {
      shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_base64);
    }
    if (entry.principalToInherit_base64) {
      shEntry.principalToInherit = Utils.deserializePrincipal(entry.principalToInherit_base64);
    }

    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;
  },

  /**
   * Expands serialized PresState data and adds it to the given nsILayoutHistoryState.
   *
   * @param layoutHistoryState
   *        nsILayoutHistoryState instance
   * @param presState
   *        Object containing serialized PresState data.
   */
  _deserializePresState(layoutHistoryState, presState) {
    let stateKey = presState.stateKey;
    let scrollOriginDowngrade =
      typeof presState.scrollOriginDowngrade == "boolean" ? presState.scrollOriginDowngrade : true;
    let res = presState.res || 1.0;
    let scaleToRes = presState.scaleToRes || false;

    layoutHistoryState.addNewPresState(stateKey, ...this._deserializeScrollPosition(presState.scroll),
                                       scrollOriginDowngrade, res, scaleToRes);
  },

  /**
   * Expands serialized scroll position data into an array containing the x and y coordinates,
   * defaulting to 0,0 if no scroll position was found.
   *
   * @param scroll
   *        Object containing serialized scroll position data.
   * @return An array containing the scroll position's x and y coordinates.
   */
  _deserializeScrollPosition(scroll = "0,0") {
    return scroll.split(",").map(pos => parseInt(pos, 10) || 0);
  },

};
PK
!<oFFmodules/sessionstore/Utils.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 = ["Utils"];

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "serializationHelper",
                                   "@mozilla.org/network/serialization-helper;1",
                                   "nsISerializationHelper");
XPCOMUtils.defineLazyGetter(this, "SERIALIZED_SYSTEMPRINCIPAL", function() {
  return Utils.serializePrincipal(Services.scriptSecurityManager.getSystemPrincipal());
});

function debug(msg) {
  Services.console.logStringMessage("Utils: " + msg);
}

this.Utils = Object.freeze({
  get SERIALIZED_SYSTEMPRINCIPAL() { return SERIALIZED_SYSTEMPRINCIPAL; },

  makeURI(url) {
    return Services.io.newURI(url);
  },

  makeInputStream(data) {
    if (typeof data == "string") {
      let stream = Cc["@mozilla.org/io/string-input-stream;1"].
                   createInstance(Ci.nsISupportsCString);
      stream.data = data;
      return stream; // XPConnect will QI this to nsIInputStream for us.
    }

    let stream = Cc["@mozilla.org/io/string-input-stream;1"].
                 createInstance(Ci.nsISupportsCString);
    stream.data = data.content;

    if (data.headers) {
      let mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]
          .createInstance(Ci.nsIMIMEInputStream);

      mimeStream.setData(stream);
      for (let [name, value] of data.headers) {
        mimeStream.addHeader(name, value);
      }
      return mimeStream;
    }

    return stream; // XPConnect will QI this to nsIInputStream for us.
  },

  serializeInputStream(aStream) {
    let data = {
      content: NetUtil.readInputStreamToString(aStream, aStream.available()),
    };

    if (aStream instanceof Ci.nsIMIMEInputStream) {
      data.headers = new Map();
      aStream.visitHeaders((name, value) => {
        data.headers.set(name, value);
      });
    }

    return data;
  },

  /**
   * Returns true if the |url| passed in is part of the given root |domain|.
   * For example, if |url| is "www.mozilla.org", and we pass in |domain| as
   * "mozilla.org", this will return true. It would return false the other way
   * around.
   */
  hasRootDomain(url, domain) {
    let host;

    try {
      host = this.makeURI(url).host;
    } catch (e) {
      // The given URL probably doesn't have a host.
      return false;
    }

    let index = host.indexOf(domain);
    if (index == -1)
      return false;

    if (host == domain)
      return true;

    let prevChar = host[index - 1];
    return (index == (host.length - domain.length)) &&
           (prevChar == "." || prevChar == "/");
  },

  shallowCopy(obj) {
    let retval = {};

    for (let key of Object.keys(obj)) {
      retval[key] = obj[key];
    }

    return retval;
  },

  /**
   * Serialize principal data.
   *
   * @param {nsIPrincipal} principal The principal to serialize.
   * @return {String} The base64 encoded principal data.
   */
  serializePrincipal(principal) {
    if (!principal)
      return null;

    return serializationHelper.serializeToString(principal);
  },

  /**
   * Deserialize a base64 encoded principal (serialized with
   * Utils::serializePrincipal).
   *
   * @param {String} principal_b64 A base64 encoded serialized principal.
   * @return {nsIPrincipal} A deserialized principal.
   */
  deserializePrincipal(principal_b64) {
    if (!principal_b64)
      return null;

    try {
      let principal = serializationHelper.deserializeObject(principal_b64);
      principal.QueryInterface(Ci.nsIPrincipal);
      return principal;
    } catch (e) {
      debug(`Failed to deserialize principal_b64 '${principal_b64}' ${e}`);
    }
    return null;
  }
});
PK
!<(EۦQQ(modules/subprocess/subprocess_common.jsm/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/* eslint-disable mozilla/balanced-listeners */

/* exported BaseProcess, PromiseWorker */

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.importGlobalProperties(["TextDecoder"]);

XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                  "resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                  "resource://gre/modules/Timer.jsm");

Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);

/* global SubprocessConstants */
var EXPORTED_SYMBOLS = ["BaseProcess", "PromiseWorker", "SubprocessConstants"];

const BUFFER_SIZE = 32768;

let nextResponseId = 0;

/**
 * Wraps a ChromeWorker so that messages sent to it return a promise which
 * resolves when the message has been received and the operation it triggers is
 * complete.
 */
class PromiseWorker extends ChromeWorker {
  constructor(url) {
    super(url);

    this.listeners = new Map();
    this.pendingResponses = new Map();

    this.addListener("close", this.onClose.bind(this));
    this.addListener("failure", this.onFailure.bind(this));
    this.addListener("success", this.onSuccess.bind(this));
    this.addListener("debug", this.onDebug.bind(this));

    this.addEventListener("message", this.onmessage);

    this.shutdown = this.shutdown.bind(this);
    AsyncShutdown.webWorkersShutdown.addBlocker(
      "Subprocess.jsm: Shut down IO worker",
      this.shutdown);
  }

  onClose() {
    AsyncShutdown.webWorkersShutdown.removeBlocker(this.shutdown);
  }

  shutdown() {
    return this.call("shutdown", []);
  }

  /**
   * Adds a listener for the given message from the worker. Any message received
   * from the worker with a `data.msg` property matching the given `msg`
   * parameter are passed to the given listener.
   *
   * @param {string} msg
   *        The message to listen for.
   * @param {function(Event)} listener
   *        The listener to call when matching messages are received.
   */
  addListener(msg, listener) {
    if (!this.listeners.has(msg)) {
      this.listeners.set(msg, new Set());
    }
    this.listeners.get(msg).add(listener);
  }

  /**
   * Removes the given message listener.
   *
   * @param {string} msg
   *        The message to stop listening for.
   * @param {function(Event)} listener
   *        The listener to remove.
   */
  removeListener(msg, listener) {
    let listeners = this.listeners.get(msg);
    if (listeners) {
      listeners.delete(listener);

      if (!listeners.size) {
        this.listeners.delete(msg);
      }
    }
  }

  onmessage(event) {
    let {msg} = event.data;
    let listeners = this.listeners.get(msg) || new Set();

    for (let listener of listeners) {
      try {
        listener(event.data);
      } catch (e) {
        Cu.reportError(e);
      }
    }
  }

  /**
   * Called when a message sent to the worker has failed, and rejects its
   * corresponding promise.
   *
   * @private
   */
  onFailure({msgId, error}) {
    this.pendingResponses.get(msgId).reject(error);
    this.pendingResponses.delete(msgId);
  }

  /**
   * Called when a message sent to the worker has succeeded, and resolves its
   * corresponding promise.
   *
   * @private
   */
  onSuccess({msgId, data}) {
    this.pendingResponses.get(msgId).resolve(data);
    this.pendingResponses.delete(msgId);
  }

  onDebug({message}) {
    dump(`Worker debug: ${message}\n`);
  }

  /**
   * Calls the given method in the worker, and returns a promise which resolves
   * or rejects when the method has completed.
   *
   * @param {string} method
   *        The name of the method to call.
   * @param {Array} args
   *        The arguments to pass to the method.
   * @param {Array} [transferList]
   *        A list of objects to transfer to the worker, rather than cloning.
   * @returns {Promise}
   */
  call(method, args, transferList = []) {
    let msgId = nextResponseId++;

    return new Promise((resolve, reject) => {
      this.pendingResponses.set(msgId, {resolve, reject});

      let message = {
        msg: method,
        msgId,
        args,
      };

      this.postMessage(message, transferList);
    });
  }
}

/**
 * Represents an input or output pipe connected to a subprocess.
 *
 * @property {integer} fd
 *           The file descriptor number of the pipe on the child process's side.
 *           @readonly
 */
class Pipe {
  /**
   * @param {Process} process
   *        The child process that this pipe is connected to.
   * @param {integer} fd
   *        The file descriptor number of the pipe on the child process's side.
   * @param {integer} id
   *        The internal ID of the pipe, which ties it to the corresponding Pipe
   *        object on the Worker side.
   */
  constructor(process, fd, id) {
    this.id = id;
    this.fd = fd;
    this.processId = process.id;
    this.worker = process.worker;

    /**
     * @property {boolean} closed
     *           True if the file descriptor has been closed, and can no longer
     *           be read from or written to. Pending IO operations may still
     *           complete, but new operations may not be initiated.
     *           @readonly
     */
    this.closed = false;
  }

  /**
   * Closes the end of the pipe which belongs to this process.
   *
   * @param {boolean} force
   *        If true, the pipe is closed immediately, regardless of any pending
   *        IO operations. If false, the pipe is closed after any existing
   *        pending IO operations have completed.
   * @returns {Promise<object>}
   *          Resolves to an object with no properties once the pipe has been
   *          closed.
   */
  close(force = false) {
    this.closed = true;
    return this.worker.call("close", [this.id, force]);
  }
}

/**
 * Represents an output-only pipe, to which data may be written.
 */
class OutputPipe extends Pipe {
  constructor(...args) {
    super(...args);

    this.encoder = new TextEncoder();
  }

  /**
   * Writes the given data to the stream.
   *
   * When given an array buffer or typed array, ownership of the buffer is
   * transferred to the IO worker, and it may no longer be used from this
   * thread.
   *
   * @param {ArrayBuffer|TypedArray|string} buffer
   *        Data to write to the stream.
   * @returns {Promise<object>}
   *          Resolves to an object with a `bytesWritten` property, containing
   *          the number of bytes successfully written, once the operation has
   *          completed.
   *
   * @rejects {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              all of the data in `buffer` could be written to it.
   */
  write(buffer) {
    if (typeof buffer === "string") {
      buffer = this.encoder.encode(buffer);
    }

    if (Cu.getClassName(buffer, true) !== "ArrayBuffer") {
      if (buffer.byteLength === buffer.buffer.byteLength) {
        buffer = buffer.buffer;
      } else {
        buffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
      }
    }

    let args = [this.id, buffer];

    return this.worker.call("write", args, [buffer]);
  }
}

/**
 * Represents an input-only pipe, from which data may be read.
 */
class InputPipe extends Pipe {
  constructor(...args) {
    super(...args);

    this.buffers = [];

    /**
     * @property {integer} dataAvailable
     *           The number of readable bytes currently stored in the input
     *           buffer.
     *           @readonly
     */
    this.dataAvailable = 0;

    this.decoder = new TextDecoder();

    this.pendingReads = [];

    this._pendingBufferRead = null;

    this.fillBuffer();
  }

  /**
   * @property {integer} bufferSize
   *           The current size of the input buffer. This varies depending on
   *           the size of pending read operations.
   *           @readonly
   */
  get bufferSize() {
    if (this.pendingReads.length) {
      return Math.max(this.pendingReads[0].length, BUFFER_SIZE);
    }
    return BUFFER_SIZE;
  }

  /**
   * Attempts to fill the input buffer.
   *
   * @private
   */
  fillBuffer() {
    let dataWanted = this.bufferSize - this.dataAvailable;

    if (!this._pendingBufferRead && dataWanted > 0) {
      this._pendingBufferRead = this._read(dataWanted);

      this._pendingBufferRead.then((result) => {
        this._pendingBufferRead = null;

        if (result) {
          this.onInput(result.buffer);

          this.fillBuffer();
        }
      });
    }
  }

  _read(size) {
    let args = [this.id, size];

    return this.worker.call("read", args).catch(e => {
      this.closed = true;

      for (let {length, resolve, reject} of this.pendingReads.splice(0)) {
        if (length === null && e.errorCode === SubprocessConstants.ERROR_END_OF_FILE) {
          resolve(new ArrayBuffer(0));
        } else {
          reject(e);
        }
      }
    });
  }

  /**
   * Adds the given data to the end of the input buffer.
   *
   * @param {ArrayBuffer} buffer
   *        An input buffer to append to the current buffered input.
   * @private
   */
  onInput(buffer) {
    this.buffers.push(buffer);
    this.dataAvailable += buffer.byteLength;
    this.checkPendingReads();
  }

  /**
   * Checks the topmost pending read operations and fulfills as many as can be
   * filled from the current input buffer.
   *
   * @private
   */
  checkPendingReads() {
    this.fillBuffer();

    let reads = this.pendingReads;
    while (reads.length && this.dataAvailable &&
           reads[0].length <= this.dataAvailable) {
      let pending = this.pendingReads.shift();

      let length = pending.length || this.dataAvailable;

      let result;
      let byteLength = this.buffers[0].byteLength;
      if (byteLength == length) {
        result = this.buffers.shift();
      } else if (byteLength > length) {
        let buffer = this.buffers[0];

        this.buffers[0] = buffer.slice(length);
        result = ArrayBuffer.transfer(buffer, length);
      } else {
        result = ArrayBuffer.transfer(this.buffers.shift(), length);
        let u8result = new Uint8Array(result);

        while (byteLength < length) {
          let buffer = this.buffers[0];
          let u8buffer = new Uint8Array(buffer);

          let remaining = length - byteLength;

          if (buffer.byteLength <= remaining) {
            this.buffers.shift();

            u8result.set(u8buffer, byteLength);
          } else {
            this.buffers[0] = buffer.slice(remaining);

            u8result.set(u8buffer.subarray(0, remaining), byteLength);
          }

          byteLength += Math.min(buffer.byteLength, remaining);
        }
      }

      this.dataAvailable -= result.byteLength;
      pending.resolve(result);
    }
  }

  /**
   * Reads exactly `length` bytes of binary data from the input stream, or, if
   * length is not provided, reads the first chunk of data to become available.
   * In the latter case, returns an empty array buffer on end of file.
   *
   * The read operation will not complete until enough data is available to
   * fulfill the request. If the pipe closes without enough available data to
   * fulfill the read, the operation fails, and any remaining buffered data is
   * lost.
   *
   * @param {integer} [length]
   *        The number of bytes to read.
   * @returns {Promise<ArrayBuffer>}
   *
   * @rejects {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  read(length = null) {
    if (length !== null && !(Number.isInteger(length) && length >= 0)) {
      throw new RangeError("Length must be a non-negative integer");
    }

    if (length == 0) {
      return Promise.resolve(new ArrayBuffer(0));
    }

    return new Promise((resolve, reject) => {
      this.pendingReads.push({length, resolve, reject});
      this.checkPendingReads();
    });
  }

  /**
   * Reads exactly `length` bytes from the input stream, and parses them as
   * UTF-8 JSON data.
   *
   * @param {integer} length
   *        The number of bytes to read.
   * @returns {Promise<object>}
   *
   * @rejects {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   *            - Subprocess.ERROR_INVALID_JSON: The data read from the pipe
   *              could not be parsed as a valid JSON string.
   */
  readJSON(length) {
    if (!Number.isInteger(length) || length <= 0) {
      throw new RangeError("Length must be a positive integer");
    }

    return this.readString(length).then(string => {
      try {
        return JSON.parse(string);
      } catch (e) {
        e.errorCode = SubprocessConstants.ERROR_INVALID_JSON;
        throw e;
      }
    });
  }

  /**
   * Reads a chunk of UTF-8 data from the input stream, and converts it to a
   * JavaScript string.
   *
   * If `length` is provided, reads exactly `length` bytes. Otherwise, reads the
   * first chunk of data to become available, and returns an empty string on end
   * of file. In the latter case, the chunk is decoded in streaming mode, and
   * any incomplete UTF-8 sequences at the end of a chunk are returned at the
   * start of a subsequent read operation.
   *
   * @param {integer} [length]
   *        The number of bytes to read.
   * @param {object} [options]
   *        An options object as expected by TextDecoder.decode.
   * @returns {Promise<string>}
   *
   * @rejects {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  readString(length = null, options = {stream: length === null}) {
    if (length !== null && !(Number.isInteger(length) && length >= 0)) {
      throw new RangeError("Length must be a non-negative integer");
    }

    return this.read(length).then(buffer => {
      return this.decoder.decode(buffer, options);
    });
  }

  /**
   * Reads 4 bytes from the input stream, and parses them as an unsigned
   * integer, in native byte order.
   *
   * @returns {Promise<integer>}
   *
   * @rejects {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  readUint32() {
    return this.read(4).then(buffer => {
      return new Uint32Array(buffer)[0];
    });
  }
}

/**
 * @class Process
 * @extends BaseProcess
 */

/**
 * Represents a currently-running process, and allows interaction with it.
 */
class BaseProcess {
  /**
   * @param {PromiseWorker} worker
   *        The worker instance which owns the process.
   * @param {integer} processId
   *        The internal ID of the Process object, which ties it to the
   *        corresponding process on the Worker side.
   * @param {integer[]} fds
   *        An array of internal Pipe IDs, one for each standard file descriptor
   *        in the child process.
   * @param {integer} pid
   *        The operating system process ID of the process.
   */
  constructor(worker, processId, fds, pid) {
    this.id = processId;
    this.worker = worker;

    /**
     * @property {integer} pid
     *           The process ID of the process, assigned by the operating system.
     *           @readonly
     */
    this.pid = pid;

    this.exitCode = null;

    this.exitPromise = new Promise(resolve => {
      this.worker.call("wait", [this.id]).then(({exitCode}) => {
        resolve(Object.freeze({exitCode}));
        this.exitCode = exitCode;
      });
    });

    if (fds[0] !== undefined) {
      /**
       * @property {OutputPipe} stdin
       *           A Pipe object which allows writing to the process's standard
       *           input.
       *           @readonly
       */
      this.stdin = new OutputPipe(this, 0, fds[0]);
    }
    if (fds[1] !== undefined) {
      /**
       * @property {InputPipe} stdout
       *           A Pipe object which allows reading from the process's standard
       *           output.
       *           @readonly
       */
      this.stdout = new InputPipe(this, 1, fds[1]);
    }
    if (fds[2] !== undefined) {
      /**
       * @property {InputPipe} [stderr]
       *           An optional Pipe object which allows reading from the
       *           process's standard error output.
       *           @readonly
       */
      this.stderr = new InputPipe(this, 2, fds[2]);
    }
  }

  /**
   * Spawns a process, and resolves to a BaseProcess instance on success.
   *
   * @param {object} options
   *        An options object as passed to `Subprocess.call`.
   *
   * @returns {Promise<BaseProcess>}
   */
  static create(options) {
    let worker = this.getWorker();

    return worker.call("spawn", [options]).then(({processId, fds, pid}) => {
      return new this(worker, processId, fds, pid);
    });
  }

  static get WORKER_URL() {
    throw new Error("Not implemented");
  }

  static get WorkerClass() {
    return PromiseWorker;
  }

  /**
   * Gets the current subprocess worker, or spawns a new one if it does not
   * currently exist.
   *
   * @returns {PromiseWorker}
   */
  static getWorker() {
    if (!this._worker) {
      this._worker = new this.WorkerClass(this.WORKER_URL);
    }
    return this._worker;
  }

  /**
   * Kills the process.
   *
   * @param {integer} [timeout=300]
   *        A timeout, in milliseconds, after which the process will be forcibly
   *        killed. On platforms which support it, the process will be sent
   *        a `SIGTERM` signal immediately, so that it has a chance to terminate
   *        gracefully, and a `SIGKILL` signal if it hasn't exited within
   *        `timeout` milliseconds. On other platforms (namely Windows), the
   *        process will be forcibly terminated immediately.
   *
   * @returns {Promise<object>}
   *          Resolves to an object with an `exitCode` property when the process
   *          has exited.
   */
  kill(timeout = 300) {
    // If the process has already exited, don't bother sending a signal.
    if (this.exitCode != null) {
      return this.wait();
    }

    let force = timeout <= 0;
    this.worker.call("kill", [this.id, force]);

    if (!force) {
      setTimeout(() => {
        if (this.exitCode == null) {
          this.worker.call("kill", [this.id, true]);
        }
      }, timeout);
    }

    return this.wait();
  }

  /**
   * Returns a promise which resolves to the process's exit code, once it has
   * exited.
   *
   * @returns {Promise<object>}
   * Resolves to an object with an `exitCode` property, containing the
   * process's exit code, once the process has exited.
   *
   * On Unix-like systems, a negative exit code indicates that the
   * process was killed by a signal whose signal number is the absolute
   * value of the error code. On Windows, an exit code of -9 indicates
   * that the process was killed via the {@linkcode BaseProcess#kill kill()}
   * method.
   */
  wait() {
    return this.exitPromise;
  }
}
PK
!<'8		'modules/subprocess/subprocess_shared.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/* exported Library, SubprocessConstants */

if (!ArrayBuffer.transfer) {
  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer
   *
   * @param {ArrayBuffer} buffer
   * @param {integer} [size = buffer.byteLength]
   * @returns {ArrayBuffer}
   */
  ArrayBuffer.transfer = function(buffer, size = buffer.byteLength) {
    let u8out = new Uint8Array(size);
    let u8buffer = new Uint8Array(buffer, 0, Math.min(size, buffer.byteLength));

    u8out.set(u8buffer);

    return u8out.buffer;
  };
}

var libraries = {};

class Library {
  constructor(name, names, definitions) {
    if (name in libraries) {
      return libraries[name];
    }

    for (let name of names) {
      try {
        if (!this.library) {
          this.library = ctypes.open(name);
        }
      } catch (e) {
        // Ignore errors until we've tried all the options.
      }
    }
    if (!this.library) {
      throw new Error("Could not load libc");
    }

    libraries[name] = this;

    for (let symbol of Object.keys(definitions)) {
      this.declare(symbol, ...definitions[symbol]);
    }
  }

  declare(name, ...args) {
    Object.defineProperty(this, name, {
      configurable: true,
      get() {
        Object.defineProperty(this, name, {
          configurable: true,
          value: this.library.declare(name, ...args),
        });

        return this[name];
      },
    });
  }
}

/**
 * Holds constants which apply to various Subprocess operations.
 * @namespace
 * @lends Subprocess
 */
const SubprocessConstants = {
  /**
   * @property {integer} ERROR_END_OF_FILE
   *           The operation failed because the end of the file was reached.
   *           @constant
   */
  ERROR_END_OF_FILE: 0xff7a0001,
  /**
   * @property {integer} ERROR_INVALID_JSON
   *           The operation failed because an invalid JSON was encountered.
   *           @constant
   */
  ERROR_INVALID_JSON: 0xff7a0002,
  /**
   * @property {integer} ERROR_BAD_EXECUTABLE
   *           The operation failed because the given file did not exist, or
   *           could not be executed.
   *           @constant
   */
  ERROR_BAD_EXECUTABLE: 0xff7a0003,
};

Object.freeze(SubprocessConstants);
PK
!</z(5(5+modules/subprocess/subprocess_shared_win.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/* exported LIBC, Win, createPipe, libc */

// This file is loaded into the same scope as subprocess_win.jsm
/* import-globals-from subprocess_win.jsm */

const LIBC = OS.Constants.libc;

const Win = OS.Constants.Win;

const LIBC_CHOICES = ["kernel32.dll"];

var win32 = {
  // On Windows 64, winapi_abi is an alias for default_abi.
  WINAPI: ctypes.winapi_abi,

  VOID: ctypes.void_t,

  BYTE: ctypes.uint8_t,
  WORD: ctypes.uint16_t,
  DWORD: ctypes.uint32_t,
  LONG: ctypes.long,
  LARGE_INTEGER: ctypes.int64_t,
  ULONGLONG: ctypes.uint64_t,

  UINT: ctypes.unsigned_int,
  UCHAR: ctypes.unsigned_char,

  BOOL: ctypes.bool,

  HANDLE: ctypes.voidptr_t,
  PVOID: ctypes.voidptr_t,
  LPVOID: ctypes.voidptr_t,

  CHAR: ctypes.char,
  WCHAR: ctypes.jschar,

  ULONG_PTR: ctypes.uintptr_t,

  SIZE_T: ctypes.size_t,
  PSIZE_T: ctypes.size_t.ptr,
};

Object.assign(win32, {
  DWORD_PTR: win32.ULONG_PTR,

  LPSTR: win32.CHAR.ptr,
  LPWSTR: win32.WCHAR.ptr,

  LPBYTE: win32.BYTE.ptr,
  LPDWORD: win32.DWORD.ptr,
  LPHANDLE: win32.HANDLE.ptr,

  // This is an opaque type.
  PROC_THREAD_ATTRIBUTE_LIST: ctypes.char.array(),
  LPPROC_THREAD_ATTRIBUTE_LIST: ctypes.char.ptr,
});

Object.assign(win32, {
  LPCSTR: win32.LPSTR,
  LPCWSTR: win32.LPWSTR,
  LPCVOID: win32.LPVOID,
});

Object.assign(win32, {
  INVALID_HANDLE_VALUE: ctypes.cast(ctypes.int64_t(-1), win32.HANDLE),
  NULL_HANDLE_VALUE: ctypes.cast(ctypes.uintptr_t(0), win32.HANDLE),

  CREATE_SUSPENDED: 0x00000004,
  CREATE_NEW_CONSOLE: 0x00000010,
  CREATE_UNICODE_ENVIRONMENT: 0x00000400,
  CREATE_NO_WINDOW: 0x08000000,
  CREATE_BREAKAWAY_FROM_JOB: 0x01000000,
  EXTENDED_STARTUPINFO_PRESENT: 0x00080000,

  STARTF_USESTDHANDLES: 0x0100,

  DUPLICATE_CLOSE_SOURCE: 0x01,
  DUPLICATE_SAME_ACCESS: 0x02,

  ERROR_HANDLE_EOF: 38,
  ERROR_BROKEN_PIPE: 109,
  ERROR_INSUFFICIENT_BUFFER: 122,

  FILE_FLAG_OVERLAPPED: 0x40000000,

  PIPE_TYPE_BYTE: 0x00,

  PIPE_ACCESS_INBOUND: 0x01,
  PIPE_ACCESS_OUTBOUND: 0x02,
  PIPE_ACCESS_DUPLEX: 0x03,

  PIPE_WAIT: 0x00,
  PIPE_NOWAIT: 0x01,

  STILL_ACTIVE: 259,

  PROC_THREAD_ATTRIBUTE_HANDLE_LIST: 0x00020002,

  JobObjectBasicLimitInformation: 2,
  JobObjectExtendedLimitInformation: 9,

  JOB_OBJECT_LIMIT_BREAKAWAY_OK: 0x00000800,

  // These constants are 32-bit unsigned integers, but Windows defines
  // them as negative integers cast to an unsigned type.
  STD_INPUT_HANDLE: -10 + 0x100000000,
  STD_OUTPUT_HANDLE: -11 + 0x100000000,
  STD_ERROR_HANDLE: -12 + 0x100000000,

  WAIT_TIMEOUT: 0x00000102,
  WAIT_FAILED: 0xffffffff,
});

Object.assign(win32, {
  JOBOBJECT_BASIC_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_BASIC_LIMIT_INFORMATION", [
    {"PerProcessUserTimeLimit": win32.LARGE_INTEGER},
    {"PerJobUserTimeLimit": win32.LARGE_INTEGER},
    {"LimitFlags": win32.DWORD},
    {"MinimumWorkingSetSize": win32.SIZE_T},
    {"MaximumWorkingSetSize": win32.SIZE_T},
    {"ActiveProcessLimit": win32.DWORD},
    {"Affinity": win32.ULONG_PTR},
    {"PriorityClass": win32.DWORD},
    {"SchedulingClass": win32.DWORD},
  ]),

  IO_COUNTERS: new ctypes.StructType("IO_COUNTERS", [
    {"ReadOperationCount": win32.ULONGLONG},
    {"WriteOperationCount": win32.ULONGLONG},
    {"OtherOperationCount": win32.ULONGLONG},
    {"ReadTransferCount": win32.ULONGLONG},
    {"WriteTransferCount": win32.ULONGLONG},
    {"OtherTransferCount": win32.ULONGLONG},
  ]),
});

Object.assign(win32, {
  JOBOBJECT_EXTENDED_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_EXTENDED_LIMIT_INFORMATION", [
    {"BasicLimitInformation": win32.JOBOBJECT_BASIC_LIMIT_INFORMATION},
    {"IoInfo": win32.IO_COUNTERS},
    {"ProcessMemoryLimit": win32.SIZE_T},
    {"JobMemoryLimit": win32.SIZE_T},
    {"PeakProcessMemoryUsed": win32.SIZE_T},
    {"PeakJobMemoryUsed": win32.SIZE_T},
  ]),

  OVERLAPPED: new ctypes.StructType("OVERLAPPED", [
     {"Internal": win32.ULONG_PTR},
     {"InternalHigh": win32.ULONG_PTR},
     {"Offset": win32.DWORD},
     {"OffsetHigh": win32.DWORD},
     {"hEvent": win32.HANDLE},
  ]),

  PROCESS_INFORMATION: new ctypes.StructType("PROCESS_INFORMATION", [
    {"hProcess": win32.HANDLE},
    {"hThread": win32.HANDLE},
    {"dwProcessId": win32.DWORD},
    {"dwThreadId": win32.DWORD},
  ]),

  SECURITY_ATTRIBUTES: new ctypes.StructType("SECURITY_ATTRIBUTES", [
    {"nLength": win32.DWORD},
    {"lpSecurityDescriptor": win32.LPVOID},
    {"bInheritHandle": win32.BOOL},
  ]),

  STARTUPINFOW: new ctypes.StructType("STARTUPINFOW", [
    {"cb": win32.DWORD},
    {"lpReserved": win32.LPWSTR},
    {"lpDesktop": win32.LPWSTR},
    {"lpTitle": win32.LPWSTR},
    {"dwX": win32.DWORD},
    {"dwY": win32.DWORD},
    {"dwXSize": win32.DWORD},
    {"dwYSize": win32.DWORD},
    {"dwXCountChars": win32.DWORD},
    {"dwYCountChars": win32.DWORD},
    {"dwFillAttribute": win32.DWORD},
    {"dwFlags": win32.DWORD},
    {"wShowWindow": win32.WORD},
    {"cbReserved2": win32.WORD},
    {"lpReserved2": win32.LPBYTE},
    {"hStdInput": win32.HANDLE},
    {"hStdOutput": win32.HANDLE},
    {"hStdError": win32.HANDLE},
  ]),
});

Object.assign(win32, {
  STARTUPINFOEXW: new ctypes.StructType("STARTUPINFOEXW", [
    {"StartupInfo": win32.STARTUPINFOW},
    {"lpAttributeList": win32.LPPROC_THREAD_ATTRIBUTE_LIST},
  ]),
});


var libc = new Library("libc", LIBC_CHOICES, {
  AssignProcessToJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hJob */
    win32.HANDLE, /* hProcess */
  ],

  CloseHandle: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hObject */
  ],

  CreateEventW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpEventAttributes */
    win32.BOOL, /* bManualReset */
    win32.BOOL, /* bInitialState */
    win32.LPWSTR, /* lpName */
  ],

  CreateFileW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.LPWSTR, /* lpFileName */
    win32.DWORD, /* dwDesiredAccess */
    win32.DWORD, /* dwShareMode */
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSecurityAttributes */
    win32.DWORD, /* dwCreationDisposition */
    win32.DWORD, /* dwFlagsAndAttributes */
    win32.HANDLE, /* opt hTemplateFile */
  ],

  CreateJobObjectW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpJobAttributes */
    win32.LPWSTR, /* lpName */
  ],

  CreateNamedPipeW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.LPWSTR, /* lpName */
    win32.DWORD, /* dwOpenMode */
    win32.DWORD, /* dwPipeMode */
    win32.DWORD, /* nMaxInstances */
    win32.DWORD, /* nOutBufferSize */
    win32.DWORD, /* nInBufferSize */
    win32.DWORD, /* nDefaultTimeOut */
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSecurityAttributes */
  ],

  CreatePipe: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPHANDLE, /* out hReadPipe */
    win32.LPHANDLE, /* out hWritePipe */
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpPipeAttributes */
    win32.DWORD, /* nSize */
  ],

  CreateProcessW: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPCWSTR, /* lpApplicationName */
    win32.LPWSTR, /* lpCommandLine */
    win32.SECURITY_ATTRIBUTES.ptr, /* lpProcessAttributes */
    win32.SECURITY_ATTRIBUTES.ptr, /* lpThreadAttributes */
    win32.BOOL, /* bInheritHandle */
    win32.DWORD, /* dwCreationFlags */
    win32.LPVOID, /* opt lpEnvironment */
    win32.LPCWSTR, /* opt lpCurrentDirectory */
    win32.STARTUPINFOW.ptr, /* lpStartupInfo */
    win32.PROCESS_INFORMATION.ptr, /* out lpProcessInformation */
  ],

  CreateSemaphoreW: [
    win32.WINAPI,
    win32.HANDLE,
    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSemaphoreAttributes */
    win32.LONG, /* lInitialCount */
    win32.LONG, /* lMaximumCount */
    win32.LPCWSTR, /* opt lpName */
  ],

  DeleteProcThreadAttributeList: [
    win32.WINAPI,
    win32.VOID,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* in/out lpAttributeList */
  ],

  DuplicateHandle: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hSourceProcessHandle */
    win32.HANDLE, /* hSourceHandle */
    win32.HANDLE, /* hTargetProcessHandle */
    win32.LPHANDLE, /* out lpTargetHandle */
    win32.DWORD, /* dwDesiredAccess */
    win32.BOOL, /* bInheritHandle */
    win32.DWORD, /* dwOptions */
  ],

  FreeEnvironmentStringsW: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPCWSTR, /* lpszEnvironmentBlock */
  ],

  GetCurrentProcess: [
    win32.WINAPI,
    win32.HANDLE,
  ],

  GetCurrentProcessId: [
    win32.WINAPI,
    win32.DWORD,
  ],

  GetEnvironmentStringsW: [
    win32.WINAPI,
    win32.LPCWSTR,
  ],

  GetExitCodeProcess: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hProcess */
    win32.LPDWORD, /* lpExitCode */
  ],

  GetOverlappedResult: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hFile */
    win32.OVERLAPPED.ptr, /* lpOverlapped */
    win32.LPDWORD, /* lpNumberOfBytesTransferred */
    win32.BOOL, /* bWait */
  ],

  GetStdHandle: [
    win32.WINAPI,
    win32.HANDLE,
    win32.DWORD, /* nStdHandle */
  ],

  InitializeProcThreadAttributeList: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* out opt lpAttributeList */
    win32.DWORD, /* dwAttributeCount */
    win32.DWORD, /* dwFlags */
    win32.PSIZE_T, /* in/out lpSize */
  ],

  ReadFile: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hFile */
    win32.LPVOID, /* out lpBuffer */
    win32.DWORD, /* nNumberOfBytesToRead */
    win32.LPDWORD, /* opt out lpNumberOfBytesRead */
    win32.OVERLAPPED.ptr, /* opt in/out lpOverlapped */
  ],

  ReleaseSemaphore: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hSemaphore */
    win32.LONG, /* lReleaseCount */
    win32.LONG.ptr, /* opt out lpPreviousCount */
  ],

  ResumeThread: [
    win32.WINAPI,
    win32.DWORD,
    win32.HANDLE, /* hThread */
  ],

  SetInformationJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hJob */
    ctypes.int, /* JobObjectInfoClass */
    win32.LPVOID, /* lpJobObjectInfo */
    win32.DWORD, /* cbJobObjectInfoLengt */
  ],

  TerminateJobObject: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hJob */
    win32.UINT, /* uExitCode */
  ],

  TerminateProcess: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hProcess */
    win32.UINT, /* uExitCode */
  ],

  UpdateProcThreadAttribute: [
    win32.WINAPI,
    win32.BOOL,
    win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* in/out lpAttributeList */
    win32.DWORD, /* dwFlags */
    win32.DWORD_PTR, /* Attribute */
    win32.PVOID, /* lpValue */
    win32.SIZE_T, /* cbSize */
    win32.PVOID, /* out opt lpPreviousValue */
    win32.PSIZE_T, /* opt lpReturnSize */
  ],

  WaitForMultipleObjects: [
    win32.WINAPI,
    win32.DWORD,
    win32.DWORD, /* nCount */
    win32.HANDLE.ptr, /* hHandles */
    win32.BOOL, /* bWaitAll */
    win32.DWORD, /* dwMilliseconds */
  ],

  WaitForSingleObject: [
    win32.WINAPI,
    win32.DWORD,
    win32.HANDLE, /* hHandle */
    win32.BOOL, /* bWaitAll */
    win32.DWORD, /* dwMilliseconds */
  ],

  WriteFile: [
    win32.WINAPI,
    win32.BOOL,
    win32.HANDLE, /* hFile */
    win32.LPCVOID, /* lpBuffer */
    win32.DWORD, /* nNumberOfBytesToRead */
    win32.LPDWORD, /* opt out lpNumberOfBytesWritten */
    win32.OVERLAPPED.ptr, /* opt in/out lpOverlapped */
  ],
});


let nextNamedPipeId = 0;

win32.Handle = function(handle) {
  return ctypes.CDataFinalizer(win32.HANDLE(handle), libc.CloseHandle);
};

win32.createPipe = function(secAttr, readFlags = 0, writeFlags = 0, size = 0) {
  readFlags |= win32.PIPE_ACCESS_INBOUND;
  writeFlags |= Win.FILE_ATTRIBUTE_NORMAL;

  if (size == 0) {
    size = 4096;
  }

  let pid = libc.GetCurrentProcessId();
  let pipeName = String.raw`\\.\Pipe\SubProcessPipe.${pid}.${nextNamedPipeId++}`;

  let readHandle = libc.CreateNamedPipeW(
    pipeName, readFlags,
    win32.PIPE_TYPE_BYTE | win32.PIPE_WAIT,
    1, /* number of connections */
    size, /* output buffer size */
    size,  /* input buffer size */
    0, /* timeout */
    secAttr.address());

  let isInvalid = handle => String(handle) == String(win32.HANDLE(Win.INVALID_HANDLE_VALUE));

  if (isInvalid(readHandle)) {
    return [];
  }

  let writeHandle = libc.CreateFileW(
    pipeName, Win.GENERIC_WRITE, 0, secAttr.address(),
    Win.OPEN_EXISTING, writeFlags, null);

  if (isInvalid(writeHandle)) {
    libc.CloseHandle(readHandle);
    return [];
  }

  return [win32.Handle(readHandle),
          win32.Handle(writeHandle)];
};

win32.createThreadAttributeList = function(handles) {
  try {
    void libc.InitializeProcThreadAttributeList;
    void libc.DeleteProcThreadAttributeList;
    void libc.UpdateProcThreadAttribute;
  } catch (e) {
    // This is only supported in Windows Vista and later.
    return null;
  }

  let size = win32.SIZE_T();
  if (!libc.InitializeProcThreadAttributeList(null, 1, 0, size.address()) &&
      ctypes.winLastError != win32.ERROR_INSUFFICIENT_BUFFER) {
    return null;
  }

  let attrList = win32.PROC_THREAD_ATTRIBUTE_LIST(size.value);

  if (!libc.InitializeProcThreadAttributeList(attrList, 1, 0, size.address())) {
    return null;
  }

  let ok = libc.UpdateProcThreadAttribute(
    attrList, 0, win32.PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
    handles, handles.constructor.size, null, null);

  if (!ok) {
    libc.DeleteProcThreadAttributeList(attrList);
    return null;
  }

  return attrList;
};
PK
!<@or..%modules/subprocess/subprocess_win.jsm/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/* eslint-disable mozilla/balanced-listeners */

/* exported SubprocessImpl */

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

var EXPORTED_SYMBOLS = ["SubprocessImpl"];

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/subprocess/subprocess_common.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");

/* import-globals-from subprocess_shared.js */
/* import-globals-from subprocess_shared_win.js */
Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);
Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared_win.js", this);

class WinPromiseWorker extends PromiseWorker {
  constructor(...args) {
    super(...args);

    this.signalEvent = libc.CreateSemaphoreW(null, 0, 32, null);

    this.call("init", [{
      breakAwayFromJob: !AppConstants.isPlatformAndVersionAtLeast("win", "6.2"),
      comspec: env.get("COMSPEC"),
      signalEvent: String(ctypes.cast(this.signalEvent, ctypes.uintptr_t).value),
    }]);
  }

  signalWorker() {
    libc.ReleaseSemaphore(this.signalEvent, 1, null);
  }

  postMessage(...args) {
    this.signalWorker();
    return super.postMessage(...args);
  }
}

class Process extends BaseProcess {
  static get WORKER_URL() {
    return "resource://gre/modules/subprocess/subprocess_worker_win.js";
  }

  static get WorkerClass() {
    return WinPromiseWorker;
  }
}

var SubprocessWin = {
  Process,

  call(options) {
    return Process.create(options);
  },

  * getEnvironment() {
    let env = libc.GetEnvironmentStringsW();
    try {
      for (let p = env, q = env; ; p = p.increment()) {
        if (p.contents == "\0") {
          if (String(p) == String(q)) {
            break;
          }

          let str = q.readString();
          q = p.increment();

          let idx = str.indexOf("=");
          if (idx == 0) {
            idx = str.indexOf("=", 1);
          }

          if (idx >= 0) {
            yield [str.slice(0, idx), str.slice(idx + 1)];
          }
        }
      }
    } finally {
      libc.FreeEnvironmentStringsW(env);
    }
  },

  async isExecutableFile(path) {
    if (!OS.Path.split(path).absolute) {
      return false;
    }

    try {
      let info = await OS.File.stat(path);
      return !(info.isDir || info.isSymlink);
    } catch (e) {
      return false;
    }
  },

  /**
   * Searches for the given executable file in the system executable
   * file paths as specified by the PATH environment variable.
   *
   * On Windows, if the unadorned filename cannot be found, the
   * extensions in the semicolon-separated list in the PATHEXT
   * environment variable are successively appended to the original
   * name and searched for in turn.
   *
   * @param {string} bin
   *        The name of the executable to find.
   * @param {object} environment
   *        An object containing a key for each environment variable to be used
   *        in the search.
   * @returns {Promise<string>}
   */
  async pathSearch(bin, environment) {
    let split = OS.Path.split(bin);
    if (split.absolute) {
      if (await this.isExecutableFile(bin)) {
        return bin;
      }
      let error = new Error(`File at path "${bin}" does not exist, or is not a normal file`);
      error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
      throw error;
    }

    let dirs = [];
    let exts = [];
    if (environment.PATH) {
      dirs = environment.PATH.split(";");
    }
    if (environment.PATHEXT) {
      exts = environment.PATHEXT.split(";");
    }

    for (let dir of dirs) {
      let path = OS.Path.join(dir, bin);

      if (await this.isExecutableFile(path)) {
        return path;
      }

      for (let ext of exts) {
        let file = path + ext;

        if (await this.isExecutableFile(file)) {
          return file;
        }
      }
    }
    let error = new Error(`Executable not found: ${bin}`);
    error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
    throw error;
  },
};

var SubprocessImpl = SubprocessWin;
PK
!<y)".modules/subprocess/subprocess_worker_common.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

// This file is loaded into the same context as subprocess_worker_unix.js
// and subprocess_worker_win.js
/* import-globals-from subprocess_worker_unix.js */

/* exported BasePipe, BaseProcess, debug */

function debug(message) {
  self.postMessage({msg: "debug", message});
}

class BasePipe {
  constructor() {
    this.closing = false;
    this.closed = false;

    this.closedPromise = new Promise(resolve => {
      this.resolveClosed = resolve;
    });

    this.pending = [];
  }

  shiftPending() {
    let result = this.pending.shift();

    if (this.closing && this.pending.length == 0) {
      this.close();
    }

    return result;
  }
}

let nextProcessId = 0;

class BaseProcess {
  constructor(options) {
    this.id = nextProcessId++;

    this.exitCode = null;

    this.exitPromise = new Promise(resolve => {
      this.resolveExit = resolve;
    });
    this.exitPromise.then(() => {
      // The input file descriptors will be closed after poll
      // reports that their input buffers are empty. If we close
      // them now, we may lose output.
      this.pipes[0].close(true);
    });

    this.pid = null;
    this.pipes = [];

    this.stringArrays = [];

    this.spawn(options);
  }

  /**
   * Waits for the process to exit and all of its pending IO operations to
   * complete.
   *
   * @returns {Promise<void>}
   */
  awaitFinished() {
    return Promise.all([
      this.exitPromise,
      ...this.pipes.map(pipe => pipe.closedPromise),
    ]);
  }

  /**
   * Creates a null-terminated array of pointers to null-terminated C-strings,
   * and returns it.
   *
   * @param {string[]} strings
   *        The strings to convert into a C string array.
   *
   * @returns {ctypes.char.ptr.array}
   */
  stringArray(strings) {
    let result = ctypes.char.ptr.array(strings.length + 1)();

    let cstrings = strings.map(str => ctypes.char.array()(str));
    for (let [i, cstring] of cstrings.entries()) {
      result[i] = cstring;
    }

    // Char arrays used in char arg and environment vectors must be
    // explicitly kept alive in a JS object, or they will be reaped
    // by the GC if it runs before our process is started.
    this.stringArrays.push(cstrings);

    return result;
  }
}

let requests = {
  init(details) {
    io.init(details);

    return {data: {}};
  },

  shutdown() {
    io.shutdown();

    return {data: {}};
  },

  close(pipeId, force = false) {
    let pipe = io.getPipe(pipeId);

    return pipe.close(force).then(() => ({data: {}}));
  },

  spawn(options) {
    let process = new Process(options);
    let processId = process.id;

    io.addProcess(process);

    let fds = process.pipes.map(pipe => pipe.id);

    return {data: {processId, fds, pid: process.pid}};
  },

  kill(processId, force = false) {
    let process = io.getProcess(processId);

    process.kill(force ? 9 : 15);

    return {data: {}};
  },

  wait(processId) {
    let process = io.getProcess(processId);

    process.wait();

    process.awaitFinished().then(() => {
      io.cleanupProcess(process);
    });

    return process.exitPromise.then(exitCode => {
      return {data: {exitCode}};
    });
  },

  read(pipeId, count) {
    let pipe = io.getPipe(pipeId);

    return pipe.read(count).then(buffer => {
      return {data: {buffer}};
    });
  },

  write(pipeId, buffer) {
    let pipe = io.getPipe(pipeId);

    return pipe.write(buffer).then(bytesWritten => {
      return {data: {bytesWritten}};
    });
  },

  getOpenFiles() {
    return {data: new Set(io.pipes.keys())};
  },

  getProcesses() {
    let data = new Map(Array.from(io.processes.values())
                            .filter(proc => proc.exitCode == null)
                            .map(proc => [proc.id, proc.pid]));
    return {data};
  },

  waitForNoProcesses() {
    return Promise.all(Array.from(io.processes.values(),
                                  proc => proc.awaitFinished()));
  },
};

onmessage = event => {
  io.messageCount--;

  let {msg, msgId, args} = event.data;

  new Promise(resolve => {
    resolve(requests[msg](...args));
  }).then(result => {
    let response = {
      msg: "success",
      msgId,
      data: result.data,
    };

    self.postMessage(response, result.transfer || []);
  }).catch(error => {
    if (error instanceof Error) {
      error = {
        message: error.message,
        fileName: error.fileName,
        lineNumber: error.lineNumber,
        column: error.column,
        stack: error.stack,
        errorCode: error.errorCode,
      };
    }

    self.postMessage({
      msg: "failure",
      msgId,
      error,
    });
  }).catch(error => {
    console.error(error);

    self.postMessage({
      msg: "failure",
      msgId,
      error: {},
    });
  });
};
PK
!<`-FF+modules/subprocess/subprocess_worker_win.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=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";

/* exported Process */

/* import-globals-from subprocess_shared.js */
/* import-globals-from subprocess_shared_win.js */
/* import-globals-from subprocess_worker_common.js */
importScripts("resource://gre/modules/subprocess/subprocess_shared.js",
              "resource://gre/modules/subprocess/subprocess_shared_win.js",
              "resource://gre/modules/subprocess/subprocess_worker_common.js");

const POLL_TIMEOUT = 5000;

// The exit code that we send when we forcibly terminate a process.
const TERMINATE_EXIT_CODE = 0x7f;

let io;

let nextPipeId = 0;

class Pipe extends BasePipe {
  constructor(process, origHandle) {
    super();

    let handle = win32.HANDLE();

    let curProc = libc.GetCurrentProcess();
    libc.DuplicateHandle(curProc, origHandle, curProc, handle.address(),
                         0, false /* inheritable */, win32.DUPLICATE_SAME_ACCESS);

    origHandle.dispose();

    this.id = nextPipeId++;
    this.process = process;

    this.handle = win32.Handle(handle);

    let event = libc.CreateEventW(null, false, false, null);

    this.overlapped = win32.OVERLAPPED();
    this.overlapped.hEvent = event;

    this._event = win32.Handle(event);

    this.buffer = null;
  }

  get event() {
    if (this.pending.length) {
      return this._event;
    }
    return null;
  }

  maybeClose() {}

  /**
   * Closes the file handle.
   *
   * @param {boolean} [force=false]
   *        If true, the file handle is closed immediately. If false, the
   *        file handle is closed after all current pending IO operations
   *        have completed.
   *
   * @returns {Promise<void>}
   *          Resolves when the file handle has been closed.
   */
  close(force = false) {
    if (!force && this.pending.length) {
      this.closing = true;
      return this.closedPromise;
    }

    for (let {reject} of this.pending) {
      let error = new Error("File closed");
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
      reject(error);
    }
    this.pending.length = 0;

    this.buffer = null;

    if (!this.closed) {
      this.handle.dispose();
      this._event.dispose();

      io.pipes.delete(this.id);

      this.handle = null;
      this.closed = true;
      this.resolveClosed();

      io.updatePollEvents();
    }
    return this.closedPromise;
  }

  /**
   * Called when an error occurred while attempting an IO operation on our file
   * handle.
   */
  onError() {
    this.close(true);
  }
}

class InputPipe extends Pipe {
  /**
   * Queues the next chunk of data to be read from the pipe if, and only if,
   * there is no IO operation currently pending.
   */
  readNext() {
    if (this.buffer === null) {
      this.readBuffer(this.pending[0].length);
    }
  }

  /**
   * Closes the pipe if there is a pending read operation with no more
   * buffered data to be read.
   */
  maybeClose() {
    if (this.buffer) {
      let read = win32.DWORD();

      let ok = libc.GetOverlappedResult(
        this.handle, this.overlapped.address(),
        read.address(), false);

      if (!ok) {
        this.onError();
      }
    }
  }

  /**
   * Asynchronously reads at most `length` bytes of binary data from the file
   * descriptor into an ArrayBuffer of the same size. Returns a promise which
   * resolves when the operation is complete.
   *
   * @param {integer} length
   *        The number of bytes to read.
   *
   * @returns {Promise<ArrayBuffer>}
   */
  read(length) {
    if (this.closing || this.closed) {
      throw new Error("Attempt to read from closed pipe");
    }

    return new Promise((resolve, reject) => {
      this.pending.push({resolve, reject, length});
      this.readNext();
    });
  }

  /**
   * Initializes an overlapped IO read operation to read exactly `count` bytes
   * into a new ArrayBuffer, which is stored in the `buffer` property until the
   * operation completes.
   *
   * @param {integer} count
   *        The number of bytes to read.
   */
  readBuffer(count) {
    this.buffer = new ArrayBuffer(count);

    let ok = libc.ReadFile(this.handle, this.buffer, count,
                           null, this.overlapped.address());

    if (!ok && (!this.process.handle || libc.winLastError)) {
      this.onError();
    } else {
      io.updatePollEvents();
    }
  }

  /**
   * Called when our pending overlapped IO operation has completed, whether
   * successfully or in failure.
   */
  onReady() {
    let read = win32.DWORD();

    let ok = libc.GetOverlappedResult(
      this.handle, this.overlapped.address(),
      read.address(), false);

    read = read.value;

    if (!ok) {
      this.onError();
    } else if (read > 0) {
      let buffer = this.buffer;
      this.buffer = null;

      let {resolve} = this.shiftPending();

      if (read == buffer.byteLength) {
        resolve(buffer);
      } else {
        resolve(ArrayBuffer.transfer(buffer, read));
      }

      if (this.pending.length) {
        this.readNext();
      } else {
        io.updatePollEvents();
      }
    }
  }
}

class OutputPipe extends Pipe {
  /**
   * Queues the next chunk of data to be written to the pipe if, and only if,
   * there is no IO operation currently pending.
   */
  writeNext() {
    if (this.buffer === null) {
      this.writeBuffer(this.pending[0].buffer);
    }
  }

  /**
   * Asynchronously writes the given buffer to our file descriptor, and returns
   * a promise which resolves when the operation is complete.
   *
   * @param {ArrayBuffer} buffer
   *        The buffer to write.
   *
   * @returns {Promise<integer>}
   *          Resolves to the number of bytes written when the operation is
   *          complete.
   */
  write(buffer) {
    if (this.closing || this.closed) {
      throw new Error("Attempt to write to closed pipe");
    }

    return new Promise((resolve, reject) => {
      this.pending.push({resolve, reject, buffer});
      this.writeNext();
    });
  }

  /**
   * Initializes an overapped IO read operation to write the data in `buffer` to
   * our file descriptor.
   *
   * @param {ArrayBuffer} buffer
   *        The buffer to write.
   */
  writeBuffer(buffer) {
    this.buffer = buffer;

    let ok = libc.WriteFile(this.handle, buffer, buffer.byteLength,
                            null, this.overlapped.address());

    if (!ok && libc.winLastError) {
      this.onError();
    } else {
      io.updatePollEvents();
    }
  }

  /**
   * Called when our pending overlapped IO operation has completed, whether
   * successfully or in failure.
   */
  onReady() {
    let written = win32.DWORD();

    let ok = libc.GetOverlappedResult(
      this.handle, this.overlapped.address(),
      written.address(), false);

    written = written.value;

    if (!ok || written != this.buffer.byteLength) {
      this.onError();
    } else if (written > 0) {
      let {resolve} = this.shiftPending();

      this.buffer = null;
      resolve(written);

      if (this.pending.length) {
        this.writeNext();
      } else {
        io.updatePollEvents();
      }
    }
  }
}

class Signal {
  constructor(event) {
    this.event = event;
  }

  cleanup() {
    libc.CloseHandle(this.event);
    this.event = null;
  }

  onError() {
    io.shutdown();
  }

  onReady() {
    io.messageCount += 1;
  }
}

class Process extends BaseProcess {
  constructor(...args) {
    super(...args);

    this.killed = false;
  }

  /**
   * Returns our process handle for use as an event in a WaitForMultipleObjects
   * call.
   */
  get event() {
    return this.handle;
  }

  /**
   * Forcibly terminates the process.
   */
  kill() {
    this.killed = true;
    libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
  }

  /**
   * Initializes the IO pipes for use as standard input, output, and error
   * descriptors in the spawned process.
   *
   * @returns {win32.Handle[]}
   *          The array of file handles belonging to the spawned process.
   */
  initPipes({stderr}) {
    let our_pipes = [];
    let their_pipes = [];

    let secAttr = new win32.SECURITY_ATTRIBUTES();
    secAttr.nLength = win32.SECURITY_ATTRIBUTES.size;
    secAttr.bInheritHandle = true;

    let pipe = input => {
      if (input) {
        let handles = win32.createPipe(secAttr, win32.FILE_FLAG_OVERLAPPED);
        our_pipes.push(new InputPipe(this, handles[0]));
        return handles[1];
      }
      let handles = win32.createPipe(secAttr, 0, win32.FILE_FLAG_OVERLAPPED);
      our_pipes.push(new OutputPipe(this, handles[1]));
      return handles[0];
    };

    their_pipes[0] = pipe(false);
    their_pipes[1] = pipe(true);

    if (stderr == "pipe") {
      their_pipes[2] = pipe(true);
    } else {
      let srcHandle;
      if (stderr == "stdout") {
        srcHandle = their_pipes[1];
      } else {
        srcHandle = libc.GetStdHandle(win32.STD_ERROR_HANDLE);
      }

      // If we don't have a valid stderr handle, just pass it along without duplicating.
      if (String(srcHandle) == win32.INVALID_HANDLE_VALUE ||
          String(srcHandle) == win32.NULL_HANDLE_VALUE) {
        their_pipes[2] = srcHandle;
      } else {
        let handle = win32.HANDLE();

        let curProc = libc.GetCurrentProcess();
        let ok = libc.DuplicateHandle(curProc, srcHandle, curProc, handle.address(),
                                      0, true /* inheritable */,
                                      win32.DUPLICATE_SAME_ACCESS);

        their_pipes[2] = ok && win32.Handle(handle);
      }
    }

    if (!their_pipes.every(handle => handle)) {
      throw new Error("Failed to create pipe");
    }

    this.pipes = our_pipes;

    return their_pipes;
  }

  /**
   * Creates a null-separated, null-terminated string list.
   *
   * @param {Array<string>} strings
   * @returns {win32.WCHAR.array}
   */
  stringList(strings) {
    // Remove empty strings, which would terminate the list early.
    strings = strings.filter(string => string);

    let string = strings.join("\0") + "\0\0";

    return win32.WCHAR.array()(string);
  }

  /**
   * Quotes a string for use as a single command argument, using Windows quoting
   * conventions.
   *
   * @see https://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx
   *
   * @param {string} str
   *        The argument string to quote.
   * @returns {string}
   */
  quoteString(str) {
    if (!/[\s"]/.test(str)) {
      return str;
    }

    let escaped = str.replace(/(\\*)("|$)/g, (m0, m1, m2) => {
      if (m2) {
        m2 = `\\${m2}`;
      }
      return `${m1}${m1}${m2}`;
    });

    return `"${escaped}"`;
  }

  spawn(options) {
    let {command, arguments: args} = options;

    if (/\\cmd\.exe$/i.test(command) && args.length == 3 && /^(\/S)?\/C$/i.test(args[1])) {
      // cmd.exe is insane and requires special treatment.
      args = [this.quoteString(args[0]), "/S/C", `"${args[2]}"`];
    } else {
      args = args.map(arg => this.quoteString(arg));
    }

    if (/\.(bat|cmd)$/i.test(command)) {
      command = io.comspec;
      args = ["cmd.exe", "/s/c", `"${args.join(" ")}"`];
    }

    let envp = this.stringList(options.environment);

    let handles = this.initPipes(options);

    let processFlags = win32.CREATE_NO_WINDOW
                     | win32.CREATE_SUSPENDED
                     | win32.CREATE_UNICODE_ENVIRONMENT;

    if (io.breakAwayFromJob) {
      processFlags |= win32.CREATE_BREAKAWAY_FROM_JOB;
    }

    let startupInfoEx = new win32.STARTUPINFOEXW();
    let startupInfo = startupInfoEx.StartupInfo;

    startupInfo.cb = win32.STARTUPINFOW.size;
    startupInfo.dwFlags = win32.STARTF_USESTDHANDLES;

    startupInfo.hStdInput = handles[0];
    startupInfo.hStdOutput = handles[1];
    startupInfo.hStdError = handles[2];

    // Note: This needs to be kept alive until we destroy the attribute list.
    let handleArray = win32.HANDLE.array()(handles);

    let threadAttrs = win32.createThreadAttributeList(handleArray);
    if (threadAttrs) {
      // If have thread attributes to pass, pass the size of the full extended
      // startup info struct.
      processFlags |= win32.EXTENDED_STARTUPINFO_PRESENT;
      startupInfo.cb = win32.STARTUPINFOEXW.size;

      startupInfoEx.lpAttributeList = threadAttrs;
    }

    let procInfo = new win32.PROCESS_INFORMATION();

    let errorMessage = "Failed to create process";
    let ok = libc.CreateProcessW(
      command, args.join(" "),
      null, /* Security attributes */
      null, /* Thread security attributes */
      true, /* Inherits handles */
      processFlags, envp, options.workdir,
      startupInfo.address(),
      procInfo.address());

    for (let handle of new Set(handles)) {
      // If any of our handles are invalid, they don't have finalizers.
      if (handle && handle.dispose) {
        handle.dispose();
      }
    }

    if (threadAttrs) {
      libc.DeleteProcThreadAttributeList(threadAttrs);
    }

    if (ok) {
      this.jobHandle = win32.Handle(libc.CreateJobObjectW(null, null));

      let info = win32.JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
      info.BasicLimitInformation.LimitFlags = win32.JOB_OBJECT_LIMIT_BREAKAWAY_OK;

      ok = libc.SetInformationJobObject(this.jobHandle, win32.JobObjectExtendedLimitInformation,
                                        ctypes.cast(info.address(), ctypes.voidptr_t),
                                        info.constructor.size);
      errorMessage = `Failed to set job limits: 0x${(ctypes.winLastError || 0).toString(16)}`;
    }

    if (ok) {
      ok = libc.AssignProcessToJobObject(this.jobHandle, procInfo.hProcess);
      if (!ok) {
        errorMessage = `Failed to attach process to job object: 0x${(ctypes.winLastError || 0).toString(16)}`;
        libc.TerminateProcess(procInfo.hProcess, TERMINATE_EXIT_CODE);
      }
    }

    if (!ok) {
      for (let pipe of this.pipes) {
        pipe.close();
      }
      throw new Error(errorMessage);
    }

    this.handle = win32.Handle(procInfo.hProcess);
    this.pid = procInfo.dwProcessId;

    libc.ResumeThread(procInfo.hThread);
    libc.CloseHandle(procInfo.hThread);
  }

  /**
   * Called when our process handle is signaled as active, meaning the process
   * has exited.
   */
  onReady() {
    this.wait();
  }

  /**
   * Attempts to wait for the process's exit status, without blocking. If
   * successful, resolves the `exitPromise` to the process's exit value.
   *
   * @returns {integer|null}
   *          The process's exit status, if it has already exited.
   */
  wait() {
    if (this.exitCode !== null) {
      return this.exitCode;
    }

    let status = win32.DWORD();

    let ok = libc.GetExitCodeProcess(this.handle, status.address());
    if (ok && status.value != win32.STILL_ACTIVE) {
      let exitCode = status.value;
      if (this.killed && exitCode == TERMINATE_EXIT_CODE) {
        // If we forcibly terminated the process, return the force kill exit
        // code that we return on other platforms.
        exitCode = -9;
      }

      this.resolveExit(exitCode);
      this.exitCode = exitCode;

      this.handle.dispose();
      this.handle = null;

      libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
      this.jobHandle.dispose();
      this.jobHandle = null;

      for (let pipe of this.pipes) {
        pipe.maybeClose();
      }

      io.updatePollEvents();

      return exitCode;
    }
  }
}

io = {
  events: null,
  eventHandlers: null,

  pipes: new Map(),

  processes: new Map(),

  messageCount: 0,

  running: true,

  init(details) {
    this.comspec = details.comspec;

    let signalEvent = ctypes.cast(ctypes.uintptr_t(details.signalEvent),
                                  win32.HANDLE);
    this.signal = new Signal(signalEvent);
    this.updatePollEvents();

    this.breakAwayFromJob = details.breakAwayFromJob;

    setTimeout(this.loop.bind(this), 0);
  },

  shutdown() {
    if (this.running) {
      this.running = false;

      this.signal.cleanup();
      this.signal = null;

      self.postMessage({msg: "close"});
      self.close();
    }
  },

  getPipe(pipeId) {
    let pipe = this.pipes.get(pipeId);

    if (!pipe) {
      let error = new Error("File closed");
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
      throw error;
    }
    return pipe;
  },

  getProcess(processId) {
    let process = this.processes.get(processId);

    if (!process) {
      throw new Error(`Invalid process ID: ${processId}`);
    }
    return process;
  },

  updatePollEvents() {
    let handlers = [this.signal,
                    ...this.pipes.values(),
                    ...this.processes.values()];

    handlers = handlers.filter(handler => handler.event);

    this.eventHandlers = handlers;

    let handles = handlers.map(handler => handler.event);
    this.events = win32.HANDLE.array()(handles);
  },

  loop() {
    this.poll();
    if (this.running) {
      setTimeout(this.loop.bind(this), 0);
    }
  },


  poll() {
    let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
    for (;; timeout = 0) {
      let events = this.events;
      let handlers = this.eventHandlers;

      let result = libc.WaitForMultipleObjects(events.length, events,
                                               false, timeout);

      if (result < handlers.length) {
        try {
          handlers[result].onReady();
        } catch (e) {
          console.error(e);
          debug(`Worker error: ${e} :: ${e.stack}`);
          handlers[result].onError();
        }
      } else {
        break;
      }
    }
  },

  addProcess(process) {
    this.processes.set(process.id, process);

    for (let pipe of process.pipes) {
      this.pipes.set(pipe.id, pipe);
    }
  },

  cleanupProcess(process) {
    this.processes.delete(process.id);
  },
};
PK
!<YW!!"modules/third_party/jsesc/jsesc.js/*
DO NOT TOUCH THIS FILE DIRECTLY. See the README for instructions.

Copyright Mathias Bynens <https://mathiasbynens.be/>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

this.EXPORTED_SYMBOLS = ["jsesc"];
/*! https://mths.be/jsesc v1.0.0 by @mathias */
;(function(root) {

	// Detect free variables `exports`
	var freeExports = typeof exports == 'object' && exports;

	// Detect free variable `module`
	var freeModule = typeof module == 'object' && module &&
		module.exports == freeExports && module;

	// Detect free variable `global`, from Node.js or Browserified code,
	// and use it as `root`
	var freeGlobal = typeof global == 'object' && global;
	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
		root = freeGlobal;
	}

	/*--------------------------------------------------------------------------*/

	var object = {};
	var hasOwnProperty = object.hasOwnProperty;
	var forOwn = function(object, callback) {
		var key;
		for (key in object) {
			if (hasOwnProperty.call(object, key)) {
				callback(key, object[key]);
			}
		}
	};

	var extend = function(destination, source) {
		if (!source) {
			return destination;
		}
		forOwn(source, function(key, value) {
			destination[key] = value;
		});
		return destination;
	};

	var forEach = function(array, callback) {
		var length = array.length;
		var index = -1;
		while (++index < length) {
			callback(array[index]);
		}
	};

	var toString = object.toString;
	var isArray = function(value) {
		return toString.call(value) == '[object Array]';
	};
	var isObject = function(value) {
		// This is a very simple check, but it’s good enough for what we need.
		return toString.call(value) == '[object Object]';
	};
	var isString = function(value) {
		return typeof value == 'string' ||
			toString.call(value) == '[object String]';
	};
	var isFunction = function(value) {
		// In a perfect world, the `typeof` check would be sufficient. However,
		// in Chrome 1–12, `typeof /x/ == 'object'`, and in IE 6–8
		// `typeof alert == 'object'` and similar for other host objects.
		return typeof value == 'function' ||
			toString.call(value) == '[object Function]';
	};

	/*--------------------------------------------------------------------------*/

	// https://mathiasbynens.be/notes/javascript-escapes#single
	var singleEscapes = {
		'"': '\\"',
		'\'': '\\\'',
		'\\': '\\\\',
		'\b': '\\b',
		'\f': '\\f',
		'\n': '\\n',
		'\r': '\\r',
		'\t': '\\t'
		// `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
		// '\v': '\\x0B'
	};
	var regexSingleEscape = /["'\\\b\f\n\r\t]/;

	var regexDigit = /[0-9]/;
	var regexWhitelist = /[ !#-&\(-\[\]-~]/;

	var jsesc = function(argument, options) {
		// Handle options
		var defaults = {
			'escapeEverything': false,
			'quotes': 'single',
			'wrap': false,
			'es6': false,
			'json': false,
			'compact': true,
			'lowercaseHex': false,
			'indent': '\t',
			'__indent__': ''
		};
		var json = options && options.json;
		if (json) {
			defaults.quotes = 'double';
			defaults.wrap = true;
		}
		options = extend(defaults, options);
		if (options.quotes != 'single' && options.quotes != 'double') {
			options.quotes = 'single';
		}
		var quote = options.quotes == 'double' ? '"' : '\'';
		var compact = options.compact;
		var indent = options.indent;
		var oldIndent;
		var newLine = compact ? '' : '\n';
		var result;
		var isEmpty = true;

		if (json && argument && isFunction(argument.toJSON)) {
			argument = argument.toJSON();
		}

		if (!isString(argument)) {
			if (isArray(argument)) {
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forEach(argument, function(value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '[]';
				}
				return '[' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + ']';
			} else if (!isObject(argument)) {
				if (json) {
					// For some values (e.g. `undefined`, `function` objects),
					// `JSON.stringify(value)` returns `undefined` (which isn’t valid
					// JSON) instead of `'null'`.
					return JSON.stringify(argument) || 'null';
				}
				return String(argument);
			} else { // it’s an object
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forOwn(argument, function(key, value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(key, options) + ':' +
						(compact ? '' : ' ') +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '{}';
				}
				return '{' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + '}';
			}
		}

		var string = argument;
		// Loop over each code unit in the string and escape it
		var index = -1;
		var length = string.length;
		var first;
		var second;
		var codePoint;
		result = '';
		while (++index < length) {
			var character = string.charAt(index);
			if (options.es6) {
				first = string.charCodeAt(index);
				if ( // check if it’s the start of a surrogate pair
					first >= 0xD800 && first <= 0xDBFF && // high surrogate
					length > index + 1 // there is a next code unit
				) {
					second = string.charCodeAt(index + 1);
					if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
						// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
						codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
						var hexadecimal = codePoint.toString(16);
						if (!options.lowercaseHex) {
							hexadecimal = hexadecimal.toUpperCase();
						}
						result += '\\u{' + hexadecimal + '}';
						index++;
						continue;
					}
				}
			}
			if (!options.escapeEverything) {
				if (regexWhitelist.test(character)) {
					// It’s a printable ASCII character that is not `"`, `'` or `\`,
					// so don’t escape it.
					result += character;
					continue;
				}
				if (character == '"') {
					result += quote == character ? '\\"' : character;
					continue;
				}
				if (character == '\'') {
					result += quote == character ? '\\\'' : character;
					continue;
				}
			}
			if (
				character == '\0' &&
				!json &&
				!regexDigit.test(string.charAt(index + 1))
			) {
				result += '\\0';
				continue;
			}
			if (regexSingleEscape.test(character)) {
				// no need for a `hasOwnProperty` check here
				result += singleEscapes[character];
				continue;
			}
			var charCode = character.charCodeAt(0);
			var hexadecimal = charCode.toString(16);
			if (!options.lowercaseHex) {
				hexadecimal = hexadecimal.toUpperCase();
			}
			var longhand = hexadecimal.length > 2 || json;
			var escaped = '\\' + (longhand ? 'u' : 'x') +
				('0000' + hexadecimal).slice(longhand ? -4 : -2);
			result += escaped;
			continue;
		}
		if (options.wrap) {
			result = quote + result + quote;
		}
		return result;
	};

	jsesc.version = '1.0.0';

	/*--------------------------------------------------------------------------*/

	// Some AMD build optimizers, like r.js, check for specific condition patterns
	// like the following:
	if (
		typeof define == 'function' &&
		typeof define.amd == 'object' &&
		define.amd
	) {
		define(function() {
			return jsesc;
		});
	}	else if (freeExports && !freeExports.nodeType) {
		if (freeModule) { // in Node.js or RingoJS v0.8.0+
			freeModule.exports = jsesc;
		} else { // in Narwhal or RingoJS v0.7.0-
			freeExports.jsesc = jsesc;
		}
	} else { // in Rhino or a web browser
		root.jsesc = jsesc;
	}

}(this));
PK
!<modules/vtt.jsm/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 = ["WebVTT"];

/**
 * Code below is vtt.js the JS WebVTT implementation.
 * Current source code can be found at http://github.com/mozilla/vtt.js
 *
 * Code taken from commit b89bfd06cd788a68c67e03f44561afe833db0849
 */
/**
 * Copyright 2013 vtt.js Contributors
 *
 * 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.
 */

var Cu = Components.utils;
Cu.import('resource://gre/modules/Services.jsm');
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");

(function(global) {

  var _objCreate = Object.create || (function() {
    function F() {}
    return function(o) {
      if (arguments.length !== 1) {
        throw new Error('Object.create shim only accepts one parameter.');
      }
      F.prototype = o;
      return new F();
    };
  })();

  // Creates a new ParserError object from an errorData object. The errorData
  // object should have default code and message properties. The default message
  // property can be overriden by passing in a message parameter.
  // See ParsingError.Errors below for acceptable errors.
  function ParsingError(errorData, message) {
    this.name = "ParsingError";
    this.code = errorData.code;
    this.message = message || errorData.message;
  }
  ParsingError.prototype = _objCreate(Error.prototype);
  ParsingError.prototype.constructor = ParsingError;

  // ParsingError metadata for acceptable ParsingErrors.
  ParsingError.Errors = {
    BadSignature: {
      code: 0,
      message: "Malformed WebVTT signature."
    },
    BadTimeStamp: {
      code: 1,
      message: "Malformed time stamp."
    }
  };

  // See spec, https://w3c.github.io/webvtt/#collect-a-webvtt-timestamp.
  function collectTimeStamp(input) {
    function computeSeconds(h, m, s, f) {
      if (m > 59 || s > 59) {
        return null;
      }
      // The attribute of the milli-seconds can only be three digits.
      if (f.length !== 3) {
        return null;
      }
      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
    }

    var timestamp = input.match(/^(\d+:)?(\d{2}):(\d{2})\.(\d+)/);
    if (!timestamp || timestamp.length !== 5) {
      return null;
    }

    let hours = timestamp[1]? timestamp[1].replace(":", "") : 0;
    let minutes = timestamp[2];
    let seconds = timestamp[3];
    let milliSeconds = timestamp[4];

    return computeSeconds(hours, minutes, seconds, milliSeconds);
  }

  // A settings object holds key/value pairs and will ignore anything but the first
  // assignment to a specific key.
  function Settings() {
    this.values = _objCreate(null);
  }

  Settings.prototype = {
    set: function(k, v) {
      if (v !== "") {
        this.values[k] = v;
      }
    },
    // Return the value for a key, or a default value.
    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
    // a number of possible default values as properties where 'defaultKey' is
    // the key of the property that will be chosen; otherwise it's assumed to be
    // a single value.
    get: function(k, dflt, defaultKey) {
      if (defaultKey) {
        return this.has(k) ? this.values[k] : dflt[defaultKey];
      }
      return this.has(k) ? this.values[k] : dflt;
    },
    // Check whether we have a value for a key.
    has: function(k) {
      return k in this.values;
    },
    // Accept a setting if its one of the given alternatives.
    alt: function(k, v, a) {
      for (var n = 0; n < a.length; ++n) {
        if (v === a[n]) {
          this.set(k, v);
          break;
        }
      }
    },
    // Accept a setting if its a valid digits value (int or float)
    digitsValue: function(k, v) {
      if (/^-0+(\.[0]*)?$/.test(v)) { // special case for -0.0
        this.set(k, 0.0);
      } else if (/^-?\d+(\.[\d]*)?$/.test(v)) {
        this.set(k, parseFloat(v));
      }
    },
    // Accept a setting if its a valid percentage.
    percent: function(k, v) {
      var m;
      if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
        v = parseFloat(v);
        if (v >= 0 && v <= 100) {
          this.set(k, v);
          return true;
        }
      }
      return false;
    }
  };

  // Helper function to parse input into groups separated by 'groupDelim', and
  // interprete each group as a key/value pair separated by 'keyValueDelim'.
  function parseOptions(input, callback, keyValueDelim, groupDelim) {
    var groups = groupDelim ? input.split(groupDelim) : [input];
    for (var i in groups) {
      if (typeof groups[i] !== "string") {
        continue;
      }
      var kv = groups[i].split(keyValueDelim);
      if (kv.length !== 2) {
        continue;
      }
      var k = kv[0];
      var v = kv[1];
      callback(k, v);
    }
  }

  function parseCue(input, cue, regionList) {
    // Remember the original input if we need to throw an error.
    var oInput = input;
    // 4.1 WebVTT timestamp
    function consumeTimeStamp() {
      var ts = collectTimeStamp(input);
      if (ts === null) {
        throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                              "Malformed timestamp: " + oInput);
      }
      // Remove time stamp from input.
      input = input.replace(/^[^\s\uFFFDa-zA-Z-]+/, "");
      return ts;
    }

    // 4.4.2 WebVTT cue settings
    function consumeCueSettings(input, cue) {
      var settings = new Settings();

      parseOptions(input, function (k, v) {
        switch (k) {
        case "region":
          // Find the last region we parsed with the same region id.
          for (var i = regionList.length - 1; i >= 0; i--) {
            if (regionList[i].id === v) {
              settings.set(k, regionList[i].region);
              break;
            }
          }
          break;
        case "vertical":
          settings.alt(k, v, ["rl", "lr"]);
          break;
        case "line":
          var vals = v.split(","),
              vals0 = vals[0];
          settings.digitsValue(k, vals0);
          settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
          settings.alt(k, vals0, ["auto"]);
          if (vals.length === 2) {
            settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
          }
          break;
        case "position":
          vals = v.split(",");
          settings.percent(k, vals[0]);
          if (vals.length === 2) {
            settings.alt("positionAlign", vals[1], ["line-left", "center", "line-right", "auto"]);
          }
          break;
        case "size":
          settings.percent(k, v);
          break;
        case "align":
          settings.alt(k, v, ["start", "center", "end", "left", "right"]);
          break;
        }
      }, /:/, /\s/);

      // Apply default values for any missing fields.
      cue.region = settings.get("region", null);
      cue.vertical = settings.get("vertical", "");
      cue.line = settings.get("line", "auto");
      cue.lineAlign = settings.get("lineAlign", "start");
      cue.snapToLines = settings.get("snapToLines", true);
      cue.size = settings.get("size", 100);
      cue.align = settings.get("align", "center");
      cue.position = settings.get("position", "auto");
      cue.positionAlign = settings.get("positionAlign", "center");
    }

    function skipWhitespace() {
      input = input.replace(/^[ \f\n\r\t]+/, "");
    }

    // 4.1 WebVTT cue timings.
    skipWhitespace();
    cue.startTime = consumeTimeStamp();   // (1) collect cue start time
    skipWhitespace();
    if (input.substr(0, 3) !== "-->") {     // (3) next characters must match "-->"
      throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                             "Malformed time stamp (time stamps must be separated by '-->'): " +
                             oInput);
    }
    input = input.substr(3);
    skipWhitespace();
    cue.endTime = consumeTimeStamp();     // (5) collect cue end time

    // 4.1 WebVTT cue settings list.
    skipWhitespace();
    consumeCueSettings(input, cue);
  }

  function onlyContainsWhiteSpaces(input) {
    return /^[ \f\n\r\t]+$/.test(input);
  }

  function containsTimeDirectionSymbol(input) {
    return input.indexOf("-->") !== -1;
  }

  function maybeIsTimeStampFormat(input) {
    return /^\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*-->\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*/.test(input);
  }

  var ESCAPE = {
    "&amp;": "&",
    "&lt;": "<",
    "&gt;": ">",
    "&lrm;": "\u200e",
    "&rlm;": "\u200f",
    "&nbsp;": "\u00a0"
  };

  var TAG_NAME = {
    c: "span",
    i: "i",
    b: "b",
    u: "u",
    ruby: "ruby",
    rt: "rt",
    v: "span",
    lang: "span"
  };

  var TAG_ANNOTATION = {
    v: "title",
    lang: "lang"
  };

  var NEEDS_PARENT = {
    rt: "ruby"
  };

  // Parse content into a document fragment.
  function parseContent(window, input, bReturnFrag) {
    function nextToken() {
      // Check for end-of-string.
      if (!input) {
        return null;
      }

      // Consume 'n' characters from the input.
      function consume(result) {
        input = input.substr(result.length);
        return result;
      }

      var m = input.match(/^([^<]*)(<[^>]+>?)?/);
      // The input doesn't contain a complete tag.
      if (!m[0]) {
        return null;
      }
      // If there is some text before the next tag, return it, otherwise return
      // the tag.
      return consume(m[1] ? m[1] : m[2]);
    }

    // Unescape a string 's'.
    function unescape1(e) {
      return ESCAPE[e];
    }
    function unescape(s) {
      while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
        s = s.replace(m[0], unescape1);
      }
      return s;
    }

    function shouldAdd(current, element) {
      return !NEEDS_PARENT[element.localName] ||
             NEEDS_PARENT[element.localName] === current.localName;
    }

    // Create an element for this tag.
    function createElement(type, annotation) {
      var tagName = TAG_NAME[type];
      if (!tagName) {
        return null;
      }
      var element = window.document.createElement(tagName);
      element.localName = tagName;
      var name = TAG_ANNOTATION[type];
      if (name) {
        element[name] = annotation ? annotation.trim() : "";
      }
      return element;
    }

    // https://w3c.github.io/webvtt/#webvtt-timestamp-object
    // Return hhhhh:mm:ss.fff
    function normalizedTimeStamp(secondsWithFrag) {
      var totalsec = parseInt(secondsWithFrag, 10);
      var hours = Math.floor(totalsec / 3600);
      var minutes = Math.floor(totalsec % 3600 / 60);
      var seconds = Math.floor(totalsec % 60);
      if (hours < 10) {
        hours = "0" + hours;
      }
      if (minutes < 10) {
        minutes = "0" + minutes;
      }
      if (seconds < 10) {
        seconds = "0" + seconds;
      }
      var f = secondsWithFrag.toString().split(".");
      if (f[1]) {
        f = f[1].slice(0, 3).padEnd(3, "0");
      } else {
        f = "000";
      }
      return hours + ':' + minutes + ':' + seconds + '.' + f;
    }

    var isFirefoxSupportPseudo = (/firefox/i.test(window.navigator.userAgent))
          && Services.prefs.getBoolPref("media.webvtt.pseudo.enabled");
    var root;
    if (bReturnFrag) {
      root = window.document.createDocumentFragment();
    } else if (isFirefoxSupportPseudo) {
      root = window.document.createElement("div", {pseudo: "::cue"});
    } else {
      root = window.document.createElement("div");
    }
    var current = root,
        t,
        tagStack = [];

    while ((t = nextToken()) !== null) {
      if (t[0] === '<') {
        if (t[1] === "/") {
          // If the closing tag matches, move back up to the parent node.
          if (tagStack.length &&
              tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
            tagStack.pop();
            current = current.parentNode;
          }
          // Otherwise just ignore the end tag.
          continue;
        }
        var ts = collectTimeStamp(t.substr(1, t.length - 1));
        var node;
        if (ts) {
          // Timestamps are lead nodes as well.
          node = window.document.createProcessingInstruction("timestamp", normalizedTimeStamp(ts));
          current.appendChild(node);
          continue;
        }
        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
        // If we can't parse the tag, skip to the next tag.
        if (!m) {
          continue;
        }
        // Try to construct an element, and ignore the tag if we couldn't.
        node = createElement(m[1], m[3]);
        if (!node) {
          continue;
        }
        // Determine if the tag should be added based on the context of where it
        // is placed in the cuetext.
        if (!shouldAdd(current, node)) {
          continue;
        }
        // Set the class list (as a list of classes, separated by space).
        if (m[2]) {
          node.className = m[2].substr(1).replace('.', ' ');
        }
        // Append the node to the current node, and enter the scope of the new
        // node.
        tagStack.push(m[1]);
        current.appendChild(node);
        current = node;
        continue;
      }

      // Text nodes are leaf nodes.
      current.appendChild(window.document.createTextNode(unescape(t)));
    }

    return root;
  }

  function StyleBox() {
  }

  // Apply styles to a div. If there is no div passed then it defaults to the
  // div on 'this'.
  StyleBox.prototype.applyStyles = function(styles, div) {
    div = div || this.div;
    for (var prop in styles) {
      if (styles.hasOwnProperty(prop)) {
        div.style[prop] = styles[prop];
      }
    }
  };

  StyleBox.prototype.formatStyle = function(val, unit) {
    return val === 0 ? 0 : val + unit;
  };

  XPCOMUtils.defineLazyPreferenceGetter(StyleBox.prototype, "supportPseudo",
                                        "media.webvtt.pseudo.enabled", false);

  // Constructs the computed display state of the cue (a div). Places the div
  // into the overlay which should be a block level element (usually a div).
  function CueStyleBox(window, cue, styleOptions) {
    var isIE8 = (typeof navigator !== "undefined") &&
      (/MSIE\s8\.0/).test(navigator.userAgent);

    var isFirefoxSupportPseudo = (/firefox/i.test(window.navigator.userAgent))
          && this.supportPseudo;
    var color = "rgba(255, 255, 255, 1)";
    var backgroundColor = "rgba(0, 0, 0, 0.8)";

    if (isIE8) {
      color = "rgb(255, 255, 255)";
      backgroundColor = "rgb(0, 0, 0)";
    }

    StyleBox.call(this);
    this.cue = cue;

    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
    // have inline positioning and will function as the cue background box.
    this.cueDiv = parseContent(window, cue.text, false);
    var styles = {
      color: color,
      backgroundColor: backgroundColor,
      display: "inline",
      font: styleOptions.font,
      whiteSpace: "pre-line",
    };
    if (isFirefoxSupportPseudo) {
      delete styles.color;
      delete styles.backgroundColor;
      delete styles.font;
      delete styles.whiteSpace;
    }

    if (!isIE8) {
      styles.writingMode = cue.vertical === "" ? "horizontal-tb"
                                               : cue.vertical === "lr" ? "vertical-lr"
                                                                       : "vertical-rl";
      styles.unicodeBidi = "plaintext";
    }
    this.applyStyles(styles, this.cueDiv);

    // Create an absolutely positioned div that will be used to position the cue
    // div.
    styles = {
      position: "absolute",
      textAlign: cue.align,
      font: styleOptions.font,
    };

    this.div = window.document.createElement("div");
    this.applyStyles(styles);

    this.div.appendChild(this.cueDiv);

    // Calculate the distance from the reference edge of the viewport to the text
    // position of the cue box. The reference edge will be resolved later when
    // the box orientation styles are applied.
    function convertCuePostionToPercentage(cuePosition) {
      if (cuePosition === "auto") {
        return 50;
      }
      return cuePosition;
    }
    var textPos = 0;
    let postionPercentage = convertCuePostionToPercentage(cue.position);
    switch (cue.computedPositionAlign) {
      // TODO : modify these fomula to follow the spec, see bug 1277437.
      case "line-left":
        textPos = postionPercentage;
        break;
      case "center":
        textPos = postionPercentage - (cue.size / 2);
        break;
      case "line-right":
        textPos = postionPercentage - cue.size;
        break;
    }

    // Horizontal box orientation; textPos is the distance from the left edge of the
    // area to the left edge of the box and cue.size is the distance extending to
    // the right from there.
    if (cue.vertical === "") {
      this.applyStyles({
        left:  this.formatStyle(textPos, "%"),
        width: this.formatStyle(cue.size, "%")
      });
    // Vertical box orientation; textPos is the distance from the top edge of the
    // area to the top edge of the box and cue.size is the height extending
    // downwards from there.
    } else {
      this.applyStyles({
        top: this.formatStyle(textPos, "%"),
        height: this.formatStyle(cue.size, "%")
      });
    }

    this.move = function(box) {
      this.applyStyles({
        top: this.formatStyle(box.top, "px"),
        bottom: this.formatStyle(box.bottom, "px"),
        left: this.formatStyle(box.left, "px"),
        right: this.formatStyle(box.right, "px"),
        height: this.formatStyle(box.height, "px"),
        width: this.formatStyle(box.width, "px")
      });
    };
  }
  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
  CueStyleBox.prototype.constructor = CueStyleBox;

  // Represents the co-ordinates of an Element in a way that we can easily
  // compute things with such as if it overlaps or intersects with another Element.
  // Can initialize it with either a StyleBox or another BoxPosition.
  function BoxPosition(obj) {
    var isIE8 = (typeof navigator !== "undefined") &&
      (/MSIE\s8\.0/).test(navigator.userAgent);

    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
    // was passed in and we need to copy the results of 'getBoundingClientRect'
    // as the object returned is readonly. All co-ordinate values are in reference
    // to the viewport origin (top left).
    var lh, height, width, top;
    if (obj.div) {
      height = obj.div.offsetHeight;
      width = obj.div.offsetWidth;
      top = obj.div.offsetTop;

      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
                  rects.getClientRects && rects.getClientRects();
      obj = obj.div.getBoundingClientRect();
      // In certain cases the outter div will be slightly larger then the sum of
      // the inner div's lines. This could be due to bold text, etc, on some platforms.
      // In this case we should get the average line height and use that. This will
      // result in the desired behaviour.
      lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
                 : 0;

    }
    this.left = obj.left;
    this.right = obj.right;
    this.top = obj.top || top;
    this.height = obj.height || height;
    this.bottom = obj.bottom || (top + (obj.height || height));
    this.width = obj.width || width;
    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;

    if (isIE8 && !this.lineHeight) {
      this.lineHeight = 13;
    }
  }

  // Move the box along a particular axis. Optionally pass in an amount to move
  // the box. If no amount is passed then the default is the line height of the
  // box.
  BoxPosition.prototype.move = function(axis, toMove) {
    toMove = toMove !== undefined ? toMove : this.lineHeight;
    switch (axis) {
    case "+x":
      this.left += toMove;
      this.right += toMove;
      break;
    case "-x":
      this.left -= toMove;
      this.right -= toMove;
      break;
    case "+y":
      this.top += toMove;
      this.bottom += toMove;
      break;
    case "-y":
      this.top -= toMove;
      this.bottom -= toMove;
      break;
    }
  };

  // Check if this box overlaps another box, b2.
  BoxPosition.prototype.overlaps = function(b2) {
    return this.left < b2.right &&
           this.right > b2.left &&
           this.top < b2.bottom &&
           this.bottom > b2.top;
  };

  // Check if this box overlaps any other boxes in boxes.
  BoxPosition.prototype.overlapsAny = function(boxes) {
    for (var i = 0; i < boxes.length; i++) {
      if (this.overlaps(boxes[i])) {
        return true;
      }
    }
    return false;
  };

  // Check if this box is within another box.
  BoxPosition.prototype.within = function(container) {
    return this.top >= container.top &&
           this.bottom <= container.bottom &&
           this.left >= container.left &&
           this.right <= container.right;
  };

  // Check if this box is entirely within the container or it is overlapping
  // on the edge opposite of the axis direction passed. For example, if "+x" is
  // passed and the box is overlapping on the left edge of the container, then
  // return true.
  BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
    switch (axis) {
    case "+x":
      return this.left < container.left;
    case "-x":
      return this.right > container.right;
    case "+y":
      return this.top < container.top;
    case "-y":
      return this.bottom > container.bottom;
    }
  };

  // Find the percentage of the area that this box is overlapping with another
  // box.
  BoxPosition.prototype.intersectPercentage = function(b2) {
    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
        intersectArea = x * y;
    return intersectArea / (this.height * this.width);
  };

  // Convert the positions from this box to CSS compatible positions using
  // the reference container's positions. This has to be done because this
  // box's positions are in reference to the viewport origin, whereas, CSS
  // values are in referecne to their respective edges.
  BoxPosition.prototype.toCSSCompatValues = function(reference) {
    return {
      top: this.top - reference.top,
      bottom: reference.bottom - this.bottom,
      left: this.left - reference.left,
      right: reference.right - this.right,
      height: this.height,
      width: this.width
    };
  };

  // Get an object that represents the box's position without anything extra.
  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
  BoxPosition.getSimpleBoxPosition = function(obj) {
    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;

    obj = obj.div ? obj.div.getBoundingClientRect() :
                  obj.tagName ? obj.getBoundingClientRect() : obj;
    var ret = {
      left: obj.left,
      right: obj.right,
      top: obj.top || top,
      height: obj.height || height,
      bottom: obj.bottom || (top + (obj.height || height)),
      width: obj.width || width
    };
    return ret;
  };

  // Move a StyleBox to its specified, or next best, position. The containerBox
  // is the box that contains the StyleBox, such as a div. boxPositions are
  // a list of other boxes that the styleBox can't overlap with.
  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {

    // Find the best position for a cue box, b, on the video. The axis parameter
    // is a list of axis, the order of which, it will move the box along. For example:
    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
    // direction. If it doesn't find a good position for it there it will then move
    // it along the x axis in the negative direction.
    function findBestPosition(b, axis) {
      var bestPosition,
          specifiedPosition = new BoxPosition(b),
          percentage = 1; // Highest possible so the first thing we get is better.

      for (var i = 0; i < axis.length; i++) {
        while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
               (b.within(containerBox) && b.overlapsAny(boxPositions))) {
          b.move(axis[i]);
        }
        // We found a spot where we aren't overlapping anything. This is our
        // best position.
        if (b.within(containerBox)) {
          return b;
        }
        var p = b.intersectPercentage(containerBox);
        // If we're outside the container box less then we were on our last try
        // then remember this position as the best position.
        if (percentage > p) {
          bestPosition = new BoxPosition(b);
          percentage = p;
        }
        // Reset the box position to the specified position.
        b = new BoxPosition(specifiedPosition);
      }
      return bestPosition || specifiedPosition;
    }

    var boxPosition = new BoxPosition(styleBox),
        cue = styleBox.cue,
        linePos = cue.computedLine,
        axis = [];

    // If we have a line number to align the cue to.
    if (cue.snapToLines) {
      var size;
      switch (cue.vertical) {
      case "":
        axis = [ "+y", "-y" ];
        size = "height";
        break;
      case "rl":
        axis = [ "+x", "-x" ];
        size = "width";
        break;
      case "lr":
        axis = [ "-x", "+x" ];
        size = "width";
        break;
      }

      var step = boxPosition.lineHeight,
          position = step * Math.round(linePos),
          maxPosition = containerBox[size] + step,
          initialAxis = axis[0];

      if (step == 0) {
        return;
      }

      // If the specified intial position is greater then the max position then
      // clamp the box to the amount of steps it would take for the box to
      // reach the max position.
      if (Math.abs(position) > maxPosition) {
        position = position < 0 ? -1 : 1;
        position *= Math.ceil(maxPosition / step) * step;
      }

      // If computed line position returns negative then line numbers are
      // relative to the bottom of the video instead of the top. Therefore, we
      // need to increase our initial position by the length or width of the
      // video, depending on the writing direction, and reverse our axis directions.
      if (linePos < 0) {
        position += cue.vertical === "" ? containerBox.height : containerBox.width;
        axis = axis.reverse();
      }

      // Move the box to the specified position. This may not be its best
      // position.
      boxPosition.move(initialAxis, position);

    } else {
      // If we have a percentage line value for the cue.
      var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;

      switch (cue.lineAlign) {
      case "center":
        linePos -= (calculatedPercentage / 2);
        break;
      case "end":
        linePos -= calculatedPercentage;
        break;
      }

      // Apply initial line position to the cue box.
      switch (cue.vertical) {
      case "":
        styleBox.applyStyles({
          top: styleBox.formatStyle(linePos, "%")
        });
        break;
      case "rl":
        styleBox.applyStyles({
          left: styleBox.formatStyle(linePos, "%")
        });
        break;
      case "lr":
        styleBox.applyStyles({
          right: styleBox.formatStyle(linePos, "%")
        });
        break;
      }

      axis = [ "+y", "-x", "+x", "-y" ];

      // Get the box position again after we've applied the specified positioning
      // to it.
      boxPosition = new BoxPosition(styleBox);
    }

    var bestPosition = findBestPosition(boxPosition, axis);
    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
  }

  function WebVTT() {
    // Nothing
  }

  // Helper to allow strings to be decoded instead of the default binary utf8 data.
  WebVTT.StringDecoder = function() {
    return {
      decode: function(data) {
        if (!data) {
          return "";
        }
        if (typeof data !== "string") {
          throw new Error("Error - expected string data.");
        }
        return decodeURIComponent(encodeURIComponent(data));
      }
    };
  };

  WebVTT.convertCueToDOMTree = function(window, cuetext) {
    if (!window) {
      return null;
    }
    return parseContent(window, cuetext, true);
  };

  var FONT_SIZE_PERCENT = 0.05;
  var FONT_STYLE = "sans-serif";

  // Runs the processing model over the cues and regions passed to it.
  // @param overlay A block level element (usually a div) that the computed cues
  //                and regions will be placed into.
  // @param controls  A Control bar element. Cues' position will be
  //                 affected and repositioned according to it.
  WebVTT.processCues = function(window, cues, overlay, controls) {
    if (!window || !cues || !overlay) {
      return null;
    }

    // Remove all previous children.
    while (overlay.firstChild) {
      overlay.firstChild.remove();
    }

    var controlBar;
    var controlBarShown;

    if (controls) {
      controlBar = controls.ownerDocument.getAnonymousElementByAttribute(
        controls, "anonid", "controlBar");
      controlBarShown = controlBar ? !!controlBar.clientHeight : false;
    }

    var rootOfCues = window.document.createElement("div");
    rootOfCues.style.position = "absolute";
    rootOfCues.style.left = "0";
    rootOfCues.style.right = "0";
    rootOfCues.style.top = "0";
    rootOfCues.style.bottom = "0";
    overlay.appendChild(rootOfCues);

    // Determine if we need to compute the display states of the cues. This could
    // be the case if a cue's state has been changed since the last computation or
    // if it has not been computed yet.
    function shouldCompute(cues) {
      if (overlay.lastControlBarShownStatus != controlBarShown) {
        return true;
      }

      for (var i = 0; i < cues.length; i++) {
        if (cues[i].hasBeenReset || !cues[i].displayState) {
          return true;
        }
      }
      return false;
    }

    // We don't need to recompute the cues' display states. Just reuse them.
    if (!shouldCompute(cues)) {
      for (var i = 0; i < cues.length; i++) {
        rootOfCues.appendChild(cues[i].displayState);
      }
      overlay.lastControlBarShownStatus = controlBarShown;
      return;
    }
    overlay.lastControlBarShownStatus = controlBarShown;

    var boxPositions = [],
        containerBox = BoxPosition.getSimpleBoxPosition(rootOfCues),
        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
    var styleOptions = {
      font: fontSize + "px " + FONT_STYLE
    };

    (function() {
      var styleBox, cue;

      if (controlBarShown) {
        // Add an empty output box that cover the same region as video control bar.
        boxPositions.push(BoxPosition.getSimpleBoxPosition(controlBar));
      }

      for (var i = 0; i < cues.length; i++) {
        cue = cues[i];

        // Compute the intial position and styles of the cue div.
        styleBox = new CueStyleBox(window, cue, styleOptions);
        styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
        rootOfCues.appendChild(styleBox.div);

        // Move the cue div to it's correct line position.
        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);

        // Remember the computed div so that we don't have to recompute it later
        // if we don't have too.
        cue.displayState = styleBox.div;

        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
      }
    })();
  };

  WebVTT.Parser = function(window, decoder) {
    this.window = window;
    this.state = "INITIAL";
    this.buffer = "";
    this.decoder = decoder || new TextDecoder("utf8");
    this.regionList = [];
  };

  WebVTT.Parser.prototype = {
    // If the error is a ParsingError then report it to the consumer if
    // possible. If it's not a ParsingError then throw it like normal.
    reportOrThrowError: function(e) {
      if (e instanceof ParsingError) {
        this.onparsingerror && this.onparsingerror(e);
      } else {
        throw e;
      }
    },
    parse: function (data) {
      var self = this;

      // If there is no data then we won't decode it, but will just try to parse
      // whatever is in buffer already. This may occur in circumstances, for
      // example when flush() is called.
      if (data) {
        // Try to decode the data that we received.
        self.buffer += self.decoder.decode(data, {stream: true});
      }

      function collectNextLine() {
        var buffer = self.buffer;
        var pos = 0;
        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
          ++pos;
        }
        var line = buffer.substr(0, pos);
        // Advance the buffer early in case we fail below.
        if (buffer[pos] === '\r') {
          ++pos;
        }
        if (buffer[pos] === '\n') {
          ++pos;
        }
        self.buffer = buffer.substr(pos);
        // Spec defined replacement.
        return line.replace(/[\u0000]/g, "\uFFFD");
      }

      function createCueIfNeeded() {
        if (!self.cue) {
          self.cue = new self.window.VTTCue(0, 0, "");
        }
      }

      // Parsing cue identifier and the identifier should be unique.
      // Return true if the input is a cue identifier.
      function parseCueIdentifier(input) {
        if (maybeIsTimeStampFormat(line)) {
          self.state = "CUE";
          return false;
        }

        createCueIfNeeded();
        // TODO : ensure the cue identifier is unique among all cue identifiers.
        self.cue.id = containsTimeDirectionSymbol(input) ? "" : input;
        self.state = "CUE";
        return true;
      }

      // Parsing the timestamp and cue settings.
      // See spec, https://w3c.github.io/webvtt/#collect-webvtt-cue-timings-and-settings
      function parseCueMayThrow(input) {
        try {
          createCueIfNeeded();
          parseCue(input, self.cue, self.regionList);
          self.state = "CUETEXT";
        } catch (e) {
          self.reportOrThrowError(e);
          // In case of an error ignore rest of the cue.
          self.cue = null;
          self.state = "BADCUE";
        }
      }

      // 3.4 WebVTT region and WebVTT region settings syntax
      function parseRegion(input) {
        var settings = new Settings();

        parseOptions(input, function (k, v) {
          switch (k) {
          case "id":
            settings.set(k, v);
            break;
          case "width":
            settings.percent(k, v);
            break;
          case "lines":
            settings.digitsValue(k, v);
            break;
          case "regionanchor":
          case "viewportanchor":
            var xy = v.split(',');
            if (xy.length !== 2) {
              break;
            }
            // We have to make sure both x and y parse, so use a temporary
            // settings object here.
            var anchor = new Settings();
            anchor.percent("x", xy[0]);
            anchor.percent("y", xy[1]);
            if (!anchor.has("x") || !anchor.has("y")) {
              break;
            }
            settings.set(k + "X", anchor.get("x"));
            settings.set(k + "Y", anchor.get("y"));
            break;
          case "scroll":
            settings.alt(k, v, ["up"]);
            break;
          }
        }, /=/, /\s/);

        // Create the region, using default values for any values that were not
        // specified.
        if (settings.has("id")) {
          try {
            var region = new self.window.VTTRegion();
            region.width = settings.get("width", 100);
            region.lines = settings.get("lines", 3);
            region.regionAnchorX = settings.get("regionanchorX", 0);
            region.regionAnchorY = settings.get("regionanchorY", 100);
            region.viewportAnchorX = settings.get("viewportanchorX", 0);
            region.viewportAnchorY = settings.get("viewportanchorY", 100);
            region.scroll = settings.get("scroll", "");
            // Register the region.
            self.onregion && self.onregion(region);
            // Remember the VTTRegion for later in case we parse any VTTCues that
            // reference it.
            self.regionList.push({
              id: settings.get("id"),
              region: region
            });
          } catch(e) {
            dump("VTTRegion Error " + e + "\n");
            var regionPref = Services.prefs.getBoolPref("media.webvtt.regions.enabled");
            dump("regionPref " + regionPref + "\n");
          }
        }
      }

      // Parsing the WebVTT signature, it contains parsing algo step1 to step9.
      // See spec, https://w3c.github.io/webvtt/#file-parsing
      function parseSignatureMayThrow(input) {
        let signature = collectNextLine();
        if (!/^WEBVTT([ \t].*)?$/.test(signature)) {
          throw new ParsingError(ParsingError.Errors.BadSignature);
        } else {
          self.state = "HEADER";
        }
      }

      // Parsing the region and style information.
      // See spec, https://w3c.github.io/webvtt/#collect-a-webvtt-block
      //
      // There are sereval things would appear in header,
      //   1. Region or Style setting
      //   2. Garbage (meaningless string)
      //   3. Empty line
      //   4. Cue's timestamp
      // The case 4 happens when there is no line interval between the header
      // and the cue blocks. In this case, we should preserve the line and
      // return it for the next phase parsing.
      function parseHeader() {
        let line = null;
        while (self.buffer && self.state === "HEADER") {
          line = collectNextLine();

          if (/^REGION|^STYLE/i.test(line)) {
            parseOptions(line, function (k, v) {
              switch (k.toUpperCase()) {
              case "REGION":
                parseRegion(v);
                break;
              case "STYLE":
                // TODO : not supported yet.
                break;
              }
            }, ":");
          } else if (maybeIsTimeStampFormat(line)) {
            self.state = "CUE";
            break;
          } else if (!line ||
                     onlyContainsWhiteSpaces(line) ||
                     containsTimeDirectionSymbol(line)) {
            // empty line, whitespaces or string contains "-->"
            break;
          }
        }

        // End parsing header part and doesn't see the timestamp.
        if (self.state === "HEADER") {
          self.state = "ID";
          line = null
        }
        return line;
      }

      // 5.1 WebVTT file parsing.
      try {
        if (self.state === "INITIAL") {
          parseSignatureMayThrow();
        }

        var line;
        if (self.state === "HEADER") {
          line = parseHeader();
        }

        while (self.buffer) {
          if (!line) {
            // Since the data receiving is async, we need to wait until the
            // buffer gets the full line.
            if (!/\r\n|\n|\r/.test(self.buffer)) {
              return this;
            }
            line = collectNextLine();
          }

          switch (self.state) {
          case "ID":
            // Ignore NOTE and line terminator
            if (/^NOTE($|[ \t])/.test(line) || !line) {
              break;
            }
            // If there is no cue identifier, keep the line and reuse this line
            // in next iteration.
            if (!parseCueIdentifier(line)) {
              continue;
            }
            break;
          case "CUE":
            parseCueMayThrow(line);
            break;
          case "CUETEXT":
            // Report the cue when (1) get an empty line (2) get the "-->""
            if (!line || containsTimeDirectionSymbol(line)) {
              // We are done parsing self cue.
              self.oncue && self.oncue(self.cue);
              self.cue = null;
              self.state = "ID";
              // Keep the line and reuse this line in next iteration.
              continue;
            }
            if (self.cue.text) {
              self.cue.text += "\n";
            }
            self.cue.text += line;
            break;
          case "BADCUE": // BADCUE
            // 54-62 - Collect and discard the remaining cue.
            if (!line) {
              self.state = "ID";
            }
            break;
          }
          // The line was already parsed, empty it to ensure we can get the
          // new line in next iteration.
          line = null;
        }
      } catch (e) {
        self.reportOrThrowError(e);

        // If we are currently parsing a cue, report what we have.
        if (self.state === "CUETEXT" && self.cue && self.oncue) {
          self.oncue(self.cue);
        }
        self.cue = null;
        // Enter BADWEBVTT state if header was not parsed correctly otherwise
        // another exception occurred so enter BADCUE state.
        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
      }
      return this;
    },
    flush: function () {
      var self = this;
      try {
        // Finish decoding the stream.
        self.buffer += self.decoder.decode();
        // Synthesize the end of the current cue or region.
        if (self.cue || self.state === "HEADER") {
          self.buffer += "\n\n";
          self.parse();
        }
        // If we've flushed, parsed, and we're still on the INITIAL state then
        // that means we don't have enough of the stream to parse the first
        // line.
        if (self.state === "INITIAL") {
          throw new ParsingError(ParsingError.Errors.BadSignature);
        }
      } catch(e) {
        self.reportOrThrowError(e);
      }
      self.onflush && self.onflush();
      return this;
    }
  };

  global.WebVTT = WebVTT;

}(this));
PK
!<V\\ modules/workers/PromiseWorker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env commonjs, mozilla/chrome-worker */

/**
 * A wrapper around `self` with extended capabilities designed
 * to simplify main thread-to-worker thread asynchronous function calls.
 *
 * This wrapper:
 * - groups requests and responses as a method `post` that returns a `Promise`;
 * - ensures that exceptions thrown on the worker thread are correctly serialized;
 * - provides some utilities for benchmarking various operations.
 *
 * Generally, you should use PromiseWorker.js along with its main thread-side
 * counterpart PromiseWorker.jsm.
 */

"use strict";

if (typeof Components != "undefined") {
  throw new Error("This module is meant to be used from the worker thread");
}
if (typeof require == "undefined" || typeof module == "undefined") {
  throw new Error("this module is meant to be imported using the implementation of require() at resource://gre/modules/workers/require.js");
}

importScripts("resource://gre/modules/workers/require.js");

/**
 * Built-in JavaScript exceptions that may be serialized without
 * loss of information.
 */
const EXCEPTION_NAMES = {
  EvalError: "EvalError",
  InternalError: "InternalError",
  RangeError: "RangeError",
  ReferenceError: "ReferenceError",
  SyntaxError: "SyntaxError",
  TypeError: "TypeError",
  URIError: "URIError",
};

/**
 * A constructor used to return data to the caller thread while
 * also executing some specific treatment (e.g. shutting down
 * the current thread, transmitting data instead of copying it).
 *
 * @param {object=} data The data to return to the caller thread.
 * @param {object=} meta Additional instructions, as an object
 * that may contain the following fields:
 * - {bool} shutdown If |true|, shut down the current thread after
 *   having sent the result.
 * - {Array} transfers An array of objects that should be transferred
 *   instead of being copied.
 *
 * @constructor
 */
function Meta(data, meta) {
  this.data = data;
  this.meta = meta;
}
exports.Meta = Meta;

/**
 * Base class for a worker.
 *
 * Derived classes are expected to provide the following methods:
 * {
 *   dispatch: function(method, args) {
 *     // Dispatch a call to method `method` with args `args`
 *   },
 *   log: function(...msg) {
 *     // Log (or discard) messages (optional)
 *   },
 *   postMessage: function(message, ...transfers) {
 *     // Post a message to the main thread
 *   },
 *   close: function() {
 *     // Close the worker
 *   }
 * }
 *
 * By default, the AbstractWorker is not connected to a message port,
 * hence will not receive anything.
 *
 * To connect it, use `onmessage`, as follows:
 *   self.addEventListener("message", msg => myWorkerInstance.handleMessage(msg));
 */
function AbstractWorker(agent) {
  this._agent = agent;
}
AbstractWorker.prototype = {
  // Default logger: discard all messages
  log() {
  },

  /**
   * Handle a message.
   */
  handleMessage(msg) {
    let data = msg.data;
    this.log("Received message", data);
    let id = data.id;

    let start;
    let options;
    if (data.args) {
      options = data.args[data.args.length - 1];
    }
    // If |outExecutionDuration| option was supplied, start measuring the
    // duration of the operation.
    if (options && typeof options === "object" && "outExecutionDuration" in options) {
       start = Date.now();
    }

    let result;
    let exn;
    let durationMs;
    let method = data.fun;
    try {
      this.log("Calling method", method);
      result = this.dispatch(method, data.args);
      this.log("Method", method, "succeeded");
    } catch (ex) {
      exn = ex;
      this.log("Error while calling agent method", method, exn, exn.moduleStack || exn.stack || "");
    }

    if (start) {
      // Record duration
      durationMs = Date.now() - start;
      this.log("Method took", durationMs, "ms");
    }

    // Now, post a reply, possibly as an uncaught error.
    // We post this message from outside the |try ... catch| block
    // to avoid capturing errors that take place during |postMessage| and
    // built-in serialization.
    if (!exn) {
      this.log("Sending positive reply", result, "id is", id);
      if (result instanceof Meta) {
        if ("transfers" in result.meta) {
          // Take advantage of zero-copy transfers
          this.postMessage({ok: result.data, id, durationMs},
            result.meta.transfers);
        } else {
          this.postMessage({ok: result.data, id, durationMs});
        }
        if (result.meta.shutdown || false) {
          // Time to close the worker
          this.close();
        }
      } else {
        this.postMessage({ok: result, id, durationMs});
      }
    } else if (exn.constructor.name in EXCEPTION_NAMES) {
      // Rather than letting the DOM mechanism [de]serialize built-in
      // JS errors, which loses lots of information (in particular,
      // the constructor name, the moduleName and the moduleStack),
      // we [de]serialize them manually with a little more care.
      this.log("Sending back exception", exn.constructor.name, "id is", id);
      let error = {
        exn: exn.constructor.name,
        message: exn.message,
        fileName: exn.moduleName || exn.fileName,
        lineNumber: exn.lineNumber,
        stack: exn.moduleStack
      };
      this.postMessage({fail: error, id, durationMs});
    } else if (exn == StopIteration) {
      // StopIteration is a well-known singleton, and requires a
      // slightly different treatment.
      this.log("Sending back StopIteration, id is", id);
      let error = {
        exn: "StopIteration"
      };
      this.postMessage({fail: error, id, durationMs});
    } else if ("toMsg" in exn) {
      // Extension mechanism for exception [de]serialization. We
      // assume that any exception with a method `toMsg()` knows how
      // to serialize itself. The other side is expected to have
      // registered a deserializer using the `ExceptionHandlers`
      // object.
      this.log("Sending back an error that knows how to serialize itself", exn, "id is", id);
      let msg = exn.toMsg();
      this.postMessage({fail: msg, id, durationMs});
    } else {
      // If we encounter an exception for which we have no
      // serialization mechanism in place, we have no choice but to
      // let the DOM handle said [de]serialization. We can just
      // attempt to mitigate the data loss by injecting `moduleName` and
      // `moduleStack`.
      this.log("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);

      try {
        // Attempt to introduce human-readable filename and stack
        exn.filename = exn.moduleName;
        exn.stack = exn.moduleStack;
      } catch (_) {
        // Nothing we can do
      }
      throw exn;
    }
  }
};
exports.AbstractWorker = AbstractWorker;
PK
!<Ixxmodules/workers/require.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/**
 * Implementation of a CommonJS module loader for workers.
 *
 * Use:
 * // in the .js file loaded by the constructor of the worker
 * importScripts("resource://gre/modules/workers/require.js");
 * let module = require("resource://gre/modules/worker/myModule.js");
 *
 * // in myModule.js
 * // Load dependencies
 * let SimpleTest = require("resource://gre/modules/workers/SimpleTest.js");
 * let Logger = require("resource://gre/modules/workers/Logger.js");
 *
 * // Define things that will not be exported
 * let someValue = // ...
 *
 * // Export symbols
 * exports.foo = // ...
 * exports.bar = // ...
 *
 *
 * Note #1:
 * Properties |fileName| and |stack| of errors triggered from a module
 * contain file names that do not correspond to human-readable module paths.
 * Human readers should rather use properties |moduleName| and |moduleStack|.
 *
 * Note #2:
 * The current version of |require()| only accepts absolute URIs.
 *
 * Note #3:
 * By opposition to some other module loader implementations, this module
 * loader does not enforce separation of global objects. Consequently, if
 * a module modifies a global object (e.g. |String.prototype|), all other
 * modules in the same worker may be affected.
 */


(function(exports) {
  "use strict";

  if (exports.require) {
    // Avoid double-imports
    return;
  }

  // Simple implementation of |require|
  let require = (function() {

    /**
     * Mapping from module paths to module exports.
     *
     * @keys {string} The absolute path to a module.
     * @values {object} The |exports| objects for that module.
     */
    let modules = new Map();

    /**
     * A human-readable version of |stack|.
     *
     * @type {string}
     */
    Object.defineProperty(Error.prototype, "moduleStack",
    {
      get() {
        return this.stack;
      }
    });
    /**
     * A human-readable version of |fileName|.
     *
     * @type {string}
     */
    Object.defineProperty(Error.prototype, "moduleName",
    {
      get() {
        let match = this.stack.match(/\@(.*):.*:/);
        if (match) {
          return match[1];
        }
        return "(unknown module)";
      }
    });

    /**
     * Import a module
     *
     * @param {string} path The path to the module.
     * @return {*} An object containing the properties exported by the module.
     */
    return function require(path) {
      if (typeof path != "string" || path.indexOf("://") == -1) {
        throw new TypeError("The argument to require() must be a string uri, got " + path);
      }
      // Automatically add ".js" if there is no extension
      let uri;
      if (path.lastIndexOf(".") <= path.lastIndexOf("/")) {
        uri = path + ".js";
      } else {
        uri = path;
      }

      // Exports provided by the module
      let exports = Object.create(null);

      // Identification of the module
      let module = {
        id: path,
        uri,
        exports
      };

      // Make module available immediately
      // (necessary in case of circular dependencies)
      if (modules.has(path)) {
        return modules.get(path).exports;
      }
      modules.set(path, module);

      try {
        // Load source of module, synchronously
        let xhr = new XMLHttpRequest();
        xhr.open("GET", uri, false);
        xhr.responseType = "text";
        xhr.send();


        let source = xhr.responseText;
        if (source == "") {
          // There doesn't seem to be a better way to detect that the file couldn't be found
          throw new Error("Could not find module " + path);
        }
        // Use `Function` to leave this scope, use `eval` to start the line
        // number from 1 that is observed by `source` and the error message
        // thrown from the module, and also use `arguments` for accessing
        // `source` and `uri` to avoid polluting the module's environment.
        let code = new Function("exports", "require", "module",
          `eval(arguments[3] + "\\n//# sourceURL=" + arguments[4] + "\\n")`);
        code(exports, require, module, source, uri);
      } catch (ex) {
        // Module loading has failed, exports should not be made available
        // after all.
        modules.delete(path);
        throw ex;
      }

      Object.freeze(module.exports);
      Object.freeze(module);
      return module.exports;
    };
  })();

  Object.freeze(require);

  Object.defineProperty(exports, "require", {
    value: require,
    enumerable: true,
    configurable: false
  });
})(this);
PK
!<Ү-k-k*components/nsUrlClassifierHashCompleter.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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;

// COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h,
// they correspond to the length, in bytes, of a hash prefix and the total
// hash.
const COMPLETE_LENGTH = 32;
const PARTIAL_LENGTH = 4;

// Upper limit on the server response minimumWaitDuration
const MIN_WAIT_DURATION_MAX_VALUE = 24 * 60 * 60 * 1000;
const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");

XPCOMUtils.defineLazyServiceGetter(this, 'gDbService',
                                   '@mozilla.org/url-classifier/dbservice;1',
                                   'nsIUrlClassifierDBService');

XPCOMUtils.defineLazyServiceGetter(this, 'gUrlUtil',
                                   '@mozilla.org/url-classifier/utils;1',
                                   'nsIUrlClassifierUtils');

let loggingEnabled = false;

// Log only if browser.safebrowsing.debug is true
function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "hashcompleter: " + d.toTimeString() + ": " + stuff.join(" ");
  dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n");
}

// Map the HTTP response code to a Telemetry bucket
// https://developers.google.com/safe-browsing/developers_guide_v2?hl=en
function httpStatusToBucket(httpStatus) {
  var statusBucket;
  switch (httpStatus) {
  case 100:
  case 101:
    // Unexpected 1xx return code
    statusBucket = 0;
    break;
  case 200:
    // OK - Data is available in the HTTP response body.
    statusBucket = 1;
    break;
  case 201:
  case 202:
  case 203:
  case 205:
  case 206:
    // Unexpected 2xx return code
    statusBucket = 2;
    break;
  case 204:
    // No Content - There are no full-length hashes with the requested prefix.
    statusBucket = 3;
    break;
  case 300:
  case 301:
  case 302:
  case 303:
  case 304:
  case 305:
  case 307:
  case 308:
    // Unexpected 3xx return code
    statusBucket = 4;
    break;
  case 400:
    // Bad Request - The HTTP request was not correctly formed.
    // The client did not provide all required CGI parameters.
    statusBucket = 5;
    break;
  case 401:
  case 402:
  case 405:
  case 406:
  case 407:
  case 409:
  case 410:
  case 411:
  case 412:
  case 414:
  case 415:
  case 416:
  case 417:
  case 421:
  case 426:
  case 428:
  case 429:
  case 431:
  case 451:
    // Unexpected 4xx return code
    statusBucket = 6;
    break;
  case 403:
    // Forbidden - The client id is invalid.
    statusBucket = 7;
    break;
  case 404:
    // Not Found
    statusBucket = 8;
    break;
  case 408:
    // Request Timeout
    statusBucket = 9;
    break;
  case 413:
    // Request Entity Too Large - Bug 1150334
    statusBucket = 10;
    break;
  case 500:
  case 501:
  case 510:
    // Unexpected 5xx return code
    statusBucket = 11;
    break;
  case 502:
  case 504:
  case 511:
    // Local network errors, we'll ignore these.
    statusBucket = 12;
    break;
  case 503:
    // Service Unavailable - The server cannot handle the request.
    // Clients MUST follow the backoff behavior specified in the
    // Request Frequency section.
    statusBucket = 13;
    break;
  case 505:
    // HTTP Version Not Supported - The server CANNOT handle the requested
    // protocol major version.
    statusBucket = 14;
    break;
  default:
    statusBucket = 15;
  };
  return statusBucket;
}

function FullHashMatch(table, hash, duration) {
  this.tableName = table;
  this.fullHash = hash;
  this.cacheDuration = duration;
}

FullHashMatch.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFullHashMatch]),

  tableName : null,
  fullHash : null,
  cacheDuration : null,
};

function HashCompleter() {
  // The current HashCompleterRequest in flight. Once it is started, it is set
  // to null. It may be used by multiple calls to |complete| in succession to
  // avoid creating multiple requests to the same gethash URL.
  this._currentRequest = null;
  // A map of gethashUrls to HashCompleterRequests that haven't yet begun.
  this._pendingRequests = {};

  // A map of gethash URLs to RequestBackoff objects.
  this._backoffs = {};

  // Whether we have been informed of a shutdown by the shutdown event.
  this._shuttingDown = false;

  // A map of gethash URLs to next gethash time in miliseconds
  this._nextGethashTimeMs = {};

  Services.obs.addObserver(this, "quit-application");
  Services.prefs.addObserver(PREF_DEBUG_ENABLED, this);
}

HashCompleter.prototype = {
  classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUrlClassifierHashCompleter,
                                         Ci.nsIRunnable,
                                         Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsITimerCallback,
                                         Ci.nsISupports]),

  // This is mainly how the HashCompleter interacts with other components.
  // Even though it only takes one partial hash and callback, subsequent
  // calls are made into the same HTTP request by using a thread dispatch.
  complete: function HC_complete(aPartialHash, aGethashUrl, aTableName, aCallback) {
    if (!aGethashUrl) {
      throw Cr.NS_ERROR_NOT_INITIALIZED;
    }

    if (!this._currentRequest) {
      this._currentRequest = new HashCompleterRequest(this, aGethashUrl);
    }
    if (this._currentRequest.gethashUrl == aGethashUrl) {
      this._currentRequest.add(aPartialHash, aCallback, aTableName);
    } else {
      if (!this._pendingRequests[aGethashUrl]) {
        this._pendingRequests[aGethashUrl] =
          new HashCompleterRequest(this, aGethashUrl);
      }
      this._pendingRequests[aGethashUrl].add(aPartialHash, aCallback, aTableName);
    }

    if (!this._backoffs[aGethashUrl]) {
      // Initialize request backoffs separately, since requests are deleted
      // after they are dispatched.
      var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
                  .getService().wrappedJSObject;

      // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398.
      this._backoffs[aGethashUrl] = new jslib.RequestBackoffV4(
        10 /* keep track of max requests */,
        0  /* don't throttle on successful requests per time period */);
    }

    if (!this._nextGethashTimeMs[aGethashUrl]) {
      this._nextGethashTimeMs[aGethashUrl] = 0;
    }

    // Start off this request. Without dispatching to a thread, every call to
    // complete makes an individual HTTP request.
    Services.tm.dispatchToMainThread(this);
  },

  // This is called after several calls to |complete|, or after the
  // currentRequest has finished.  It starts off the HTTP request by making a
  // |begin| call to the HashCompleterRequest.
  run: function() {
    // Clear everything on shutdown
    if (this._shuttingDown) {
      this._currentRequest = null;
      this._pendingRequests = null;
      this._nextGethashTimeMs = null;

      for (var url in this._backoffs) {
        this._backoffs[url] = null;
      }
      throw Cr.NS_ERROR_NOT_INITIALIZED;
    }

    // If we don't have an in-flight request, make one
    let pendingUrls = Object.keys(this._pendingRequests);
    if (!this._currentRequest && (pendingUrls.length > 0)) {
      let nextUrl = pendingUrls[0];
      this._currentRequest = this._pendingRequests[nextUrl];
      delete this._pendingRequests[nextUrl];
    }

    if (this._currentRequest) {
      try {
        this._currentRequest.begin();
      } finally {
        // If |begin| fails, we should get rid of our request.
        this._currentRequest = null;
      }
    }
  },

  // Pass the server response status to the RequestBackoff for the given
  // gethashUrl and fetch the next pending request, if there is one.
  finishRequest: function(url, aStatus) {
    this._backoffs[url].noteServerResponse(aStatus);
    Services.tm.dispatchToMainThread(this);
  },

  // Returns true if we can make a request from the given url, false otherwise.
  canMakeRequest: function(aGethashUrl) {
    return this._backoffs[aGethashUrl].canMakeRequest() &&
           Date.now() >= this._nextGethashTimeMs[aGethashUrl];
  },

  // Notifies the RequestBackoff of a new request so we can throttle based on
  // max requests/time period. This must be called before a channel is opened,
  // and finishRequest must be called once the response is received.
  noteRequest: function(aGethashUrl) {
    return this._backoffs[aGethashUrl].noteRequest();
  },

  observe: function HC_observe(aSubject, aTopic, aData) {
    switch (aTopic) {
    case "quit-application":
      this._shuttingDown = true;
      Services.obs.removeObserver(this, "quit-application");
      break;
    case "nsPref:changed":
      if (aData == PREF_DEBUG_ENABLED) {
        loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
      }
      break;
    }
  },
};

function HashCompleterRequest(aCompleter, aGethashUrl) {
  // HashCompleter object that created this HashCompleterRequest.
  this._completer = aCompleter;
  // The internal set of hashes and callbacks that this request corresponds to.
  this._requests = [];
  // nsIChannel that the hash completion query is transmitted over.
  this._channel = null;
  // Response body of hash completion. Created in onDataAvailable.
  this._response = "";
  // Whether we have been informed of a shutdown by the quit-application event.
  this._shuttingDown = false;
  this.gethashUrl = aGethashUrl;

  // Multiple partial hashes can be associated with the same tables
  // so we use a map here.
  this.tableNames = new Map();

  this.telemetryProvider = "";
  this.telemetryClockStart = 0;
}
HashCompleterRequest.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
                                         Ci.nsIStreamListener,
                                         Ci.nsIObserver,
                                         Ci.nsISupports]),

  // This is called by the HashCompleter to add a hash and callback to the
  // HashCompleterRequest. It must be called before calling |begin|.
  add: function HCR_add(aPartialHash, aCallback, aTableName) {
    this._requests.push({
      partialHash: aPartialHash,
      callback: aCallback,
      tableName: aTableName,
      response: { matches:[] },
    });

    if (aTableName) {
      let isTableNameV4 = aTableName.endsWith('-proto');
      if (0 === this.tableNames.size) {
        // Decide if this request is v4 by the first added partial hash.
        this.isV4 = isTableNameV4;
      } else if (this.isV4 !== isTableNameV4) {
        log('ERROR: Cannot mix "proto" tables with other types within ' +
            'the same gethash URL.');
      }
      this.tableNames.set(aTableName);

      // Assuming all tables with the same gethash URL have the same provider
      if (this.telemetryProvider == "") {
        this.telemetryProvider = gUrlUtil.getTelemetryProvider(aTableName);
      }
    }
  },

  fillTableStatesBase64: function HCR_fillTableStatesBase64(aCallback) {
    gDbService.getTables(aTableData => {
      aTableData.split("\n").forEach(line => {
        let p = line.indexOf(";");
        if (-1 === p) {
          return;
        }
        // [tableName];[stateBase64]:[checksumBase64]
        let tableName = line.substring(0, p);
        if (this.tableNames.has(tableName)) {
          let metadata = line.substring(p + 1).split(":");
          let stateBase64 = metadata[0];
          this.tableNames.set(tableName, stateBase64);
        }
      });

      aCallback();
    });

  },

  // This initiates the HTTP request. It can fail due to backoff timings and
  // will notify all callbacks as necessary. We notify the backoff object on
  // begin.
  begin: function HCR_begin() {
    if (!this._completer.canMakeRequest(this.gethashUrl)) {
      log("Can't make request to " + this.gethashUrl + "\n");
      this.notifyFailure(Cr.NS_ERROR_ABORT);
      return;
    }

    Services.obs.addObserver(this, "quit-application");

    // V4 requires table states to build the request so we need
    // a async call to retrieve the table states from disk.
    // Note that |HCR_begin| is fine to be sync because
    // it doesn't appear in a sync call chain.
    this.fillTableStatesBase64(() => {
      try {
        this.openChannel();
        // Notify the RequestBackoff if opening the channel succeeded. At this
        // point, finishRequest must be called.
        this._completer.noteRequest(this.gethashUrl);
      }
      catch (err) {
        this.notifyFailure(err);
        throw err;
      }
    });
  },

  notify: function HCR_notify() {
    // If we haven't gotten onStopRequest, just cancel. This will call us
    // with onStopRequest since we implement nsIStreamListener on the
    // channel.
    if (this._channel && this._channel.isPending()) {
      log("cancelling request to " + this.gethashUrl + " (timeout)\n");
      Services.telemetry.getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2").
        add(this.telemetryProvider, 1);
      this._channel.cancel(Cr.NS_BINDING_ABORTED);
    }
  },

  // Creates an nsIChannel for the request and fills the body.
  openChannel: function HCR_openChannel() {
    let loadFlags = Ci.nsIChannel.INHIBIT_CACHING |
                    Ci.nsIChannel.LOAD_BYPASS_CACHE;

    this.actualGethashUrl = this.gethashUrl;
    if (this.isV4) {
      // As per spec, we add the request payload to the gethash url.
      this.actualGethashUrl += "&$req=" + this.buildRequestV4();
    }

    log("actualGethashUrl: " + this.actualGethashUrl);

    let channel = NetUtil.newChannel({
      uri: this.actualGethashUrl,
      loadUsingSystemPrincipal: true
    });
    channel.loadFlags = loadFlags;
    channel.loadInfo.originAttributes = {
      // The firstPartyDomain value should sync with NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN
      // defined in nsNetUtil.h.
      firstPartyDomain: "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla"
    };

    // Disable keepalive.
    let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    httpChannel.setRequestHeader("Connection", "close", false);

    this._channel = channel;

    if (this.isV4) {
      httpChannel.setRequestHeader("X-HTTP-Method-Override", "POST", false);
    } else {
      let body = this.buildRequest();
      this.addRequestBody(body);
    }

    // Set a timer that cancels the channel after timeout_ms in case we
    // don't get a gethash response.
    this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    // Ask the timer to use nsITimerCallback (.notify()) when ready
    let timeout = Services.prefs.getIntPref(
      "urlclassifier.gethash.timeout_ms");
    this.timer_.initWithCallback(this, timeout, this.timer_.TYPE_ONE_SHOT);
    channel.asyncOpen2(this);
    this.telemetryClockStart = Date.now();
  },

  buildRequestV4: function HCR_buildRequestV4() {
    // Convert the "name to state" mapping to two equal-length arrays.
    let tableNameArray = [];
    let stateArray = [];
    this.tableNames.forEach((state, name) => {
      // We skip the table which is not associated with a state.
      if (state) {
        tableNameArray.push(name);
        stateArray.push(state);
      }
    });

    // Build the "distinct" prefix array.
    // The array is sorted to make sure the entries are arbitrary mixed in a
    // deterministic way
    let prefixSet = new Set();
    this._requests.forEach(r => prefixSet.add(btoa(r.partialHash)));
    let prefixArray = Array.from(prefixSet).sort();

    log("Build v4 gethash request with " + JSON.stringify(tableNameArray) + ', '
                                         + JSON.stringify(stateArray) + ', '
                                         + JSON.stringify(prefixArray));

    return gUrlUtil.makeFindFullHashRequestV4(tableNameArray,
                                              stateArray,
                                              prefixArray,
                                              tableNameArray.length,
                                              prefixArray.length);
  },

  // Returns a string for the request body based on the contents of
  // this._requests.
  buildRequest: function HCR_buildRequest() {
    // Sometimes duplicate entries are sent to HashCompleter but we do not need
    // to propagate these to the server. (bug 633644)
    let prefixes = [];

    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      if (prefixes.indexOf(request.partialHash) == -1) {
        prefixes.push(request.partialHash);
      }
    }

    // Sort to make sure the entries are arbitrary mixed in a deterministic way
    prefixes.sort();

    let body;
    body = PARTIAL_LENGTH + ":" + (PARTIAL_LENGTH * prefixes.length) +
           "\n" + prefixes.join("");

    log('Requesting completions for ' + prefixes.length + ' ' + PARTIAL_LENGTH + '-byte prefixes: ' + body);
    return body;
  },

  // Sets the request body of this._channel.
  addRequestBody: function HCR_addRequestBody(aBody) {
    let inputStream = Cc["@mozilla.org/io/string-input-stream;1"].
                      createInstance(Ci.nsIStringInputStream);

    inputStream.setData(aBody, aBody.length);

    let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel);
    uploadChannel.setUploadStream(inputStream, "text/plain", -1);

    let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
    httpChannel.requestMethod = "POST";
  },

  // Parses the response body and eventually adds items to the |response.matches| array
  // for elements of |this._requests|.
  handleResponse: function HCR_handleResponse() {
    if (this._response == "") {
      return;
    }

    if (this.isV4) {
      return this.handleResponseV4();
    }

    let start = 0;

    let length = this._response.length;
    while (start != length) {
      start = this.handleTable(start);
    }
  },

  handleResponseV4: function HCR_handleResponseV4() {
    let callback = {
      // onCompleteHashFound will be called for each fullhash found in
      // FullHashResponse.
      onCompleteHashFound : (aCompleteHash,
                             aTableNames,
                             aPerHashCacheDuration) => {
        log("V4 fullhash response complete hash found callback: " +
            JSON.stringify(aCompleteHash) + ", " +
            aTableNames + ", CacheDuration(" + aPerHashCacheDuration + ")");

        // Filter table names which we didn't requested.
        let filteredTables = aTableNames.split(",").filter(name => {
          return this.tableNames.get(name);
        });
        if (0 === filteredTables.length) {
          log("ERROR: Got complete hash which is from unknown table.");
          return;
        }
        if (filteredTables.length > 1) {
          log("WARNING: Got complete hash which has ambigious threat type.");
        }

        this.handleItem({
          completeHash: aCompleteHash,
          tableName: filteredTables[0],
          cacheDuration: aPerHashCacheDuration
        });
      },

      // onResponseParsed will be called no matter if there is match in
      // FullHashResponse, the callback is mainly used to pass negative cache
      // duration and minimum wait duration.
      onResponseParsed : (aMinWaitDuration,
                          aNegCacheDuration) => {
        log("V4 fullhash response parsed callback: " +
            "MinWaitDuration(" + aMinWaitDuration + "), " +
            "NegativeCacheDuration(" + aNegCacheDuration + ")");

        let minWaitDuration = aMinWaitDuration;

        if (aMinWaitDuration > MIN_WAIT_DURATION_MAX_VALUE) {
          log("WARNING: Minimum wait duration too large, clamping it down " +
              "to a reasonable value.");
          minWaitDuration = MIN_WAIT_DURATION_MAX_VALUE;
        } else if (aMinWaitDuration < 0) {
          log("WARNING: Minimum wait duration is negative, reset it to 0");
          minWaitDuration = 0;
        }

        this._completer._nextGethashTimeMs[this.gethashUrl] =
          Date.now() + minWaitDuration;

        // A fullhash request may contain more than one prefix, so the negative
        // cache duration should be set for all the prefixes in the request.
        this._requests.forEach(request => {
          request.response.negCacheDuration = aNegCacheDuration;
        });
      },
    };

    gUrlUtil.parseFindFullHashResponseV4(this._response, callback);
  },

  // This parses a table entry in the response body and calls |handleItem|
  // for complete hash in the table entry.
  handleTable: function HCR_handleTable(aStart) {
    let body = this._response.substring(aStart);

    // deal with new line indexes as there could be
    // new line characters in the data parts.
    let newlineIndex = body.indexOf("\n");
    if (newlineIndex == -1) {
      throw errorWithStack();
    }
    let header = body.substring(0, newlineIndex);
    let entries = header.split(":");
    if (entries.length != 3) {
      throw errorWithStack();
    }

    let list = entries[0];
    let addChunk = parseInt(entries[1]);
    let dataLength = parseInt(entries[2]);

    log('Response includes add chunks for ' + list + ': ' + addChunk);
    if (dataLength % COMPLETE_LENGTH != 0 ||
        dataLength == 0 ||
        dataLength > body.length - (newlineIndex + 1)) {
      throw errorWithStack();
    }

    let data = body.substr(newlineIndex + 1, dataLength);
    for (let i = 0; i < (dataLength / COMPLETE_LENGTH); i++) {
      this.handleItem({
        completeHash: data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH),
        tableName: list,
        chunkId: addChunk
      });
    }

    return aStart + newlineIndex + 1 + dataLength;
  },

  // This adds a complete hash to any entry in |this._requests| that matches
  // the hash.
  handleItem: function HCR_handleItem(aData) {
    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      if (aData.completeHash.startsWith(request.partialHash)) {
        request.response.matches.push(aData);
      }
    }
  },

  // notifySuccess and notifyFailure are used to alert the callbacks with
  // results. notifySuccess makes |completion| and |completionFinished| calls
  // while notifyFailure only makes a |completionFinished| call with the error
  // code.
  notifySuccess: function HCR_notifySuccess() {
    // V2 completion handler
    let completionV2 = (req) => {
      req.response.matches.forEach((m) => {
        req.callback.completionV2(m.completeHash, m.tableName, m.chunkId);
      });

      req.callback.completionFinished(Cr.NS_OK);
    };

    // V4 completion handler
    let completionV4 = (req) => {
      let matches = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);

      req.response.matches.forEach(m => {
        matches.appendElement(
          new FullHashMatch(m.tableName, m.completeHash, m.cacheDuration));
      });

      req.callback.completionV4(req.partialHash, req.tableName,
                                req.response.negCacheDuration, matches);

      req.callback.completionFinished(Cr.NS_OK);
    };

    let completion = this.isV4 ? completionV4 : completionV2;
    this._requests.forEach((req) => { completion(req); });
  },

  notifyFailure: function HCR_notifyFailure(aStatus) {
    log("notifying failure\n");
    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      request.callback.completionFinished(aStatus);
    }
  },

  onDataAvailable: function HCR_onDataAvailable(aRequest, aContext,
                                                aInputStream, aOffset, aCount) {
    let sis = Cc["@mozilla.org/scriptableinputstream;1"].
              createInstance(Ci.nsIScriptableInputStream);
    sis.init(aInputStream);
    this._response += sis.readBytes(aCount);
  },

  onStartRequest: function HCR_onStartRequest(aRequest, aContext) {
    // At this point no data is available for us and we have no reason to
    // terminate the connection, so we do nothing until |onStopRequest|.
    this._completer._nextGethashTimeMs[this.gethashUrl] = 0;

    if (this.telemetryClockStart > 0) {
      let msecs = Date.now() - this.telemetryClockStart;
      Services.telemetry.getKeyedHistogramById("URLCLASSIFIER_COMPLETE_SERVER_RESPONSE_TIME").
        add(this.telemetryProvider, msecs);
    }
  },

  onStopRequest: function HCR_onStopRequest(aRequest, aContext, aStatusCode) {
    Services.obs.removeObserver(this, "quit-application");

    if (this.timer_) {
      this.timer_.cancel();
      this.timer_ = null;
    }

    this.telemetryClockStart = 0;

    if (this._shuttingDown) {
      throw Cr.NS_ERROR_ABORT;
    }

    // Default HTTP status to service unavailable, in case we can't retrieve
    // the true status from the channel.
    let httpStatus = 503;
    if (Components.isSuccessCode(aStatusCode)) {
      let channel = aRequest.QueryInterface(Ci.nsIHttpChannel);
      let success = channel.requestSucceeded;
      httpStatus = channel.responseStatus;
      if (!success) {
        aStatusCode = Cr.NS_ERROR_ABORT;
      }
    }
    let success = Components.isSuccessCode(aStatusCode);
    log('Received a ' + httpStatus + ' status code from the gethash server (success=' + success + ').');

    Services.telemetry.getKeyedHistogramById("URLCLASSIFIER_COMPLETE_REMOTE_STATUS2").
      add(this.telemetryProvider, httpStatusToBucket(httpStatus));
    if (httpStatus == 400) {
      dump("Safe Browsing server returned a 400 during completion: request= " +
           this.actualGethashUrl + "\n");
    }

    Services.telemetry.getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2").
      add(this.telemetryProvider, 0);

    // Notify the RequestBackoff once a response is received.
    this._completer.finishRequest(this.gethashUrl, httpStatus);

    if (success) {
      try {
        this.handleResponse();
      }
      catch (err) {
        log(err.stack);
        aStatusCode = err.value;
        success = false;
      }
    }

    if (success) {
      this.notifySuccess();
    } else {
      this.notifyFailure(aStatusCode);
    }
  },

  observe: function HCR_observe(aSubject, aTopic, aData) {
    if (aTopic == "quit-application") {
      this._shuttingDown = true;
      if (this._channel) {
        this._channel.cancel(Cr.NS_ERROR_ABORT);
        telemetryClockStart = 0;
      }

      Services.obs.removeObserver(this, "quit-application");
    }
  },
};

// Converts a URL safe base64 string to a normal base64 string. Will not change
// normal base64 strings. This is modelled after the same function in
// nsUrlClassifierUtils.h.
function unUrlsafeBase64(aStr) {
  return !aStr ? "" : aStr.replace(/-/g, "+")
                          .replace(/_/g, "/");
}

function errorWithStack() {
  let err = new Error();
  err.value = Cr.NS_ERROR_FAILURE;
  return err;
}

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HashCompleter]);
PK
!<ι]](components/nsUrlClassifierListManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// This is the only implementation of nsIUrlListManager.
// A class that manages lists, namely white and black lists for
// phishing or malware protection. The ListManager knows how to fetch,
// update, and store lists.
//
// There is a single listmanager for the whole application.
//
// TODO more comprehensive update tests, for example add unittest check
//      that the listmanagers tables are properly written on updates

// Lower and upper limits on the server-provided polling frequency
const minDelayMs = 5 * 60 * 1000;
const maxDelayMs = 24 * 60 * 60 * 1000;
const defaultUpdateIntervalMs = 30 * 60 * 1000;
const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";

let loggingEnabled = false;

// Log only if browser.safebrowsing.debug is true
this.log = function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "listmanager: " + d.toTimeString() + ": " + stuff.join(" ");
  msg = Services.urlFormatter.trimSensitiveURLs(msg);
  Services.console.logStringMessage(msg);
  dump(msg + "\n");
}

/**
 * A ListManager keeps track of black and white lists and knows
 * how to update them.
 *
 * @constructor
 */
this.PROT_ListManager = function PROT_ListManager() {
  loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);

  log("Initializing list manager");
  this.updateInterval = defaultUpdateIntervalMs;

  // A map of tableNames to objects of type
  // { updateUrl: <updateUrl>, gethashUrl: <gethashUrl> }
  this.tablesData = {};
  // A map of updateUrls to maps of tables requiring updates, e.g.
  // { safebrowsing-update-url: { goog-phish-shavar: true,
  //                              goog-malware-shavar: true }
  this.needsUpdate_ = {};

  // A map of updateUrls to single-use nsITimer. An entry exists if and only if
  // there is at least one table with updates enabled for that url. nsITimers
  // are reset when enabling/disabling updates or on update callbacks (update
  // success, update failure, download error).
  this.updateCheckers_ = {};
  this.requestBackoffs_ = {};
  this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"]
                   .getService(Ci.nsIUrlClassifierDBService);

  Services.obs.addObserver(this, "quit-application");
  Services.prefs.addObserver(PREF_DEBUG_ENABLED, this);
}

/**
 * Register a new table table
 * @param tableName - the name of the table
 * @param updateUrl - the url for updating the table
 * @param gethashUrl - the url for fetching hash completions
 * @returns true if the table could be created; false otherwise
 */
PROT_ListManager.prototype.registerTable = function(tableName,
                                                    providerName,
                                                    updateUrl,
                                                    gethashUrl) {
  this.tablesData[tableName] = {};
  if (!updateUrl) {
    log("Can't register table " + tableName + " without updateUrl");
    return false;
  }
  log("registering " + tableName + " with " + updateUrl);
  this.tablesData[tableName].updateUrl = updateUrl;
  this.tablesData[tableName].gethashUrl = gethashUrl;
  this.tablesData[tableName].provider = providerName;

  // Keep track of all of our update URLs.
  if (!this.needsUpdate_[updateUrl]) {
    this.needsUpdate_[updateUrl] = {};

    // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398.
    this.requestBackoffs_[updateUrl] = new RequestBackoffV4(
                                            4 /* num requests */,
                                   60*60*1000 /* request time, 60 min */);
  }
  this.needsUpdate_[updateUrl][tableName] = false;

  return true;
}

/**
 * Unregister a table table from list
 */
PROT_ListManager.prototype.unregisterTable = function(tableName) {
  log("unregistering " + tableName);
  var table = this.tablesData[tableName];
  if (table) {
    if (!this.updatesNeeded_(table.updateUrl) &&
        this.updateCheckers_[table.updateUrl]) {
      this.updateCheckers_[table.updateUrl].cancel();
      this.updateCheckers_[table.updateUrl] = null;
    }
    delete this.needsUpdate_[table.updateUrl][tableName];
  }
  delete this.tablesData[tableName];

}

/**
 * Delete all of our data tables which seem to leak otherwise.
 * Remove observers
 */
PROT_ListManager.prototype.shutdown_ = function() {
  this.stopUpdateCheckers();
  for (var name in this.tablesData) {
    delete this.tablesData[name];
  }
  Services.obs.removeObserver(this, "quit-application");
  Services.prefs.removeObserver(PREF_DEBUG_ENABLED, this);
}

/**
 * xpcom-shutdown callback
 */
PROT_ListManager.prototype.observe = function(aSubject, aTopic, aData) {
  switch (aTopic) {
  case "quit-application":
    this.shutdown_();
    break;
  case "nsPref:changed":
    if (aData == PREF_DEBUG_ENABLED) {
      loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
    }
    break;
  }
}


PROT_ListManager.prototype.getGethashUrl = function(tableName) {
  if (this.tablesData[tableName] && this.tablesData[tableName].gethashUrl) {
    return this.tablesData[tableName].gethashUrl;
  }
  return "";
}

PROT_ListManager.prototype.getUpdateUrl = function(tableName) {
  if (this.tablesData[tableName] && this.tablesData[tableName].updateUrl) {
    return this.tablesData[tableName].updateUrl;
  }
  return "";
}

/**
 * Enable updates for some tables
 * @param tables - an array of table names that need updating
 */
PROT_ListManager.prototype.enableUpdate = function(tableName) {
  var table = this.tablesData[tableName];
  if (table) {
    log("Enabling table updates for " + tableName);
    this.needsUpdate_[table.updateUrl][tableName] = true;
  }
}

/**
 * Returns true if any table associated with the updateUrl requires updates.
 * @param updateUrl - the updateUrl
 */
PROT_ListManager.prototype.updatesNeeded_ = function(updateUrl) {
  let updatesNeeded = false;
  for (var tableName in this.needsUpdate_[updateUrl]) {
    if (this.needsUpdate_[updateUrl][tableName]) {
      updatesNeeded = true;
    }
  }
  return updatesNeeded;
}

/**
 * Disables updates for some tables
 * @param tables - an array of table names that no longer need updating
 */
PROT_ListManager.prototype.disableUpdate = function(tableName) {
  var table = this.tablesData[tableName];
  if (table) {
    log("Disabling table updates for " + tableName);
    this.needsUpdate_[table.updateUrl][tableName] = false;
    if (!this.updatesNeeded_(table.updateUrl) &&
        this.updateCheckers_[table.updateUrl]) {
      this.updateCheckers_[table.updateUrl].cancel();
      this.updateCheckers_[table.updateUrl] = null;
    }
  }
}

/**
 * Determine if we have some tables that need updating.
 */
PROT_ListManager.prototype.requireTableUpdates = function() {
  for (var name in this.tablesData) {
    // Tables that need updating even if other tables don't require it
    if (this.needsUpdate_[this.tablesData[name].updateUrl][name]) {
      return true;
    }
  }

  return false;
}

/**
 *  Set timer to check update after delay
 */
PROT_ListManager.prototype.setUpdateCheckTimer = function (updateUrl,
                                                           delay)
{
  this.updateCheckers_[updateUrl] = Cc["@mozilla.org/timer;1"]
                                    .createInstance(Ci.nsITimer);
  this.updateCheckers_[updateUrl].initWithCallback(() => {
    this.updateCheckers_[updateUrl] = null;
    this.checkForUpdates(updateUrl);
  }, delay, Ci.nsITimer.TYPE_ONE_SHOT);
}
/**
 * Acts as a nsIUrlClassifierCallback for getTables.
 */
PROT_ListManager.prototype.kickoffUpdate_ = function (onDiskTableData)
{
  this.startingUpdate_ = false;
  var initialUpdateDelay = 3000;
  // Add a fuzz of 0-1 minutes for both v2 and v4 according to Bug 1305478.
  initialUpdateDelay += Math.floor(Math.random() * (1 * 60 * 1000));

  // If the user has never downloaded tables, do the check now.
  log("needsUpdate: " + JSON.stringify(this.needsUpdate_, undefined, 2));
  for (var updateUrl in this.needsUpdate_) {
    // If we haven't already kicked off updates for this updateUrl, set a
    // non-repeating timer for it. The timer delay will be reset either on
    // updateSuccess to this.updateInterval, or backed off on downloadError.
    // Don't set the updateChecker unless at least one table has updates
    // enabled.
    if (this.updatesNeeded_(updateUrl) && !this.updateCheckers_[updateUrl]) {
      let provider = null;
      Object.keys(this.tablesData).forEach(function(table) {
        if (this.tablesData[table].updateUrl === updateUrl) {
          let newProvider = this.tablesData[table].provider;
          if (provider) {
            if (newProvider !== provider) {
              log("Multiple tables for the same updateURL have a different provider?!");
            }
          } else {
            provider = newProvider;
          }
        }
      }, this);
      log("Initializing update checker for " + updateUrl
          + " provided by " + provider);

      // Use the initialUpdateDelay + fuzz unless we had previous updates
      // and the server told us when to try again.
      let updateDelay = initialUpdateDelay;
      let nextUpdatePref = "browser.safebrowsing.provider." + provider +
                           ".nextupdatetime";
      let nextUpdate;
      try {
        nextUpdate = Services.prefs.getCharPref(nextUpdatePref);
      } catch (ex) {
      }

      if (nextUpdate) {
        updateDelay = Math.min(maxDelayMs, Math.max(0, nextUpdate - Date.now()));
        log("Next update at " + nextUpdate);
      }
      log("Next update " + updateDelay / 60000 + "min from now");

      this.setUpdateCheckTimer(updateUrl, updateDelay);
    } else {
      log("No updates needed or already initialized for " + updateUrl);
    }
  }
}

PROT_ListManager.prototype.stopUpdateCheckers = function() {
  log("Stopping updates");
  for (var updateUrl in this.updateCheckers_) {
    if (this.updateCheckers_[updateUrl]) {
      this.updateCheckers_[updateUrl].cancel();
      this.updateCheckers_[updateUrl] = null;
    }
  }
}

/**
 * Determine if we have any tables that require updating.  Different
 * Wardens may call us with new tables that need to be updated.
 */
PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
  // We update tables if we have some tables that want updates.  If there
  // are no tables that want to be updated - we dont need to check anything.
  if (this.requireTableUpdates()) {
    log("Starting managing lists");

    // Get the list of existing tables from the DBService before making any
    // update requests.
    if (!this.startingUpdate_) {
      this.startingUpdate_ = true;
      // check the current state of tables in the database
      this.dbService_.getTables(this.kickoffUpdate_.bind(this));
    }
  } else {
    log("Stopping managing lists (if currently active)");
    this.stopUpdateCheckers();                    // Cancel pending updates
  }
}

/**
 * Updates our internal tables from the update server
 *
 * @param updateUrl: request updates for tables associated with that url, or
 * for all tables if the url is empty.
 */
PROT_ListManager.prototype.checkForUpdates = function(updateUrl) {
  log("checkForUpdates with " + updateUrl);
  // See if we've triggered the request backoff logic.
  if (!updateUrl) {
    return false;
  }
  if (!this.requestBackoffs_[updateUrl] ||
      !this.requestBackoffs_[updateUrl].canMakeRequest()) {
    log("Can't make update request");
    return false;
  }
  // Grab the current state of the tables from the database
  this.dbService_.getTables(BindToObject(this.makeUpdateRequest_, this,
                            updateUrl));
  return true;
}

/**
 * Method that fires the actual HTTP update request.
 * First we reset any tables that have disappeared.
 * @param tableData List of table data already in the database, in the form
 *        tablename;<chunk ranges>\n
 */
PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
  log("this.tablesData: " + JSON.stringify(this.tablesData, undefined, 2));
  log("existing chunks: " + tableData + "\n");
  // Disallow blank updateUrls
  if (!updateUrl) {
    return;
  }
  // An object of the form
  // { tableList: comma-separated list of tables to request,
  //   tableNames: map of tables that need updating,
  //   request: list of tables and existing chunk ranges from tableData
  // }
  var streamerMap = { tableList: null,
                      tableNames: {},
                      requestPayload: "",
                      isPostRequest: true };

  let useProtobuf = false;
  let onceThru = false;
  for (var tableName in this.tablesData) {
    // Skip tables not matching this update url
    if (this.tablesData[tableName].updateUrl != updateUrl) {
      continue;
    }

    // Check if |updateURL| is for 'proto'. (only v4 uses protobuf for now.)
    // We use the table name 'goog-*-proto' and an additional provider "google4"
    // to describe the v4 settings.
    let isCurTableProto = tableName.endsWith('-proto');
    if (!onceThru) {
      useProtobuf = isCurTableProto;
      onceThru = true;
    } else if (useProtobuf !== isCurTableProto) {
      log('ERROR: Cannot mix "proto" tables with other types ' +
          'within the same provider.');
    }

    if (this.needsUpdate_[this.tablesData[tableName].updateUrl][tableName]) {
      streamerMap.tableNames[tableName] = true;
    }
    if (!streamerMap.tableList) {
      streamerMap.tableList = tableName;
    } else {
      streamerMap.tableList += "," + tableName;
    }
  }

  if (useProtobuf) {
    let tableArray = [];
    Object.keys(streamerMap.tableNames).forEach(aTableName => {
      if (streamerMap.tableNames[aTableName]) {
        tableArray.push(aTableName);
      }
    });

    // Build the <tablename, stateBase64> mapping.
    let tableState = {};
    tableData.split("\n").forEach(line => {
      let p = line.indexOf(";");
      if (-1 === p) {
        return;
      }
      let tableName = line.substring(0, p);
      if (tableName in streamerMap.tableNames) {
        let metadata = line.substring(p + 1).split(":");
        let stateBase64 = metadata[0];
        log(tableName + " ==> " + stateBase64);
        tableState[tableName] = stateBase64;
      }
    });

    // The state is a byte stream which server told us from the
    // last table update. The state would be used to do the partial
    // update and the empty string means the table has
    // never been downloaded. See Bug 1287058 for supporting
    // partial update.
    let stateArray = [];
    tableArray.forEach(listName => {
      stateArray.push(tableState[listName] || "");
    });

    log("stateArray: " + stateArray);

    let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
                     .getService(Ci.nsIUrlClassifierUtils);

    streamerMap.requestPayload = urlUtils.makeUpdateRequestV4(tableArray,
                                                              stateArray,
                                                              tableArray.length);
    streamerMap.isPostRequest = false;
  } else {
    // Build the request. For each table already in the database, include the
    // chunk data from the database
    var lines = tableData.split("\n");
    for (var i = 0; i < lines.length; i++) {
      var fields = lines[i].split(";");
      var name = fields[0];
      if (streamerMap.tableNames[name]) {
        streamerMap.requestPayload += lines[i] + "\n";
        delete streamerMap.tableNames[name];
      }
    }
    // For each requested table that didn't have chunk data in the database,
    // request it fresh
    for (let tableName in streamerMap.tableNames) {
      streamerMap.requestPayload += tableName + ";\n";
    }

    streamerMap.isPostRequest = true;
  }

  log("update request: " + JSON.stringify(streamerMap, undefined, 2) + "\n");

  // Don't send an empty request.
  if (streamerMap.requestPayload.length > 0) {
    this.makeUpdateRequestForEntry_(updateUrl, streamerMap.tableList,
                                    streamerMap.requestPayload,
                                    streamerMap.isPostRequest);
  } else {
    // We were disabled between kicking off getTables and now.
    log("Not sending empty request");
  }
}

PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function(updateUrl,
                                                                 tableList,
                                                                 requestPayload,
                                                                 isPostRequest) {
  log("makeUpdateRequestForEntry_: requestPayload " + requestPayload +
      " update: " + updateUrl + " tablelist: " + tableList + "\n");
  var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
                 .getService(Ci.nsIUrlClassifierStreamUpdater);

  this.requestBackoffs_[updateUrl].noteRequest();

  if (!streamer.downloadUpdates(
        tableList,
        requestPayload,
        isPostRequest,
        updateUrl,
        BindToObject(this.updateSuccess_, this, tableList, updateUrl),
        BindToObject(this.updateError_, this, tableList, updateUrl),
        BindToObject(this.downloadError_, this, tableList, updateUrl))) {
    // Our alarm gets reset in one of the 3 callbacks.
    log("pending update, queued request until later");
  } else {
    let table = Object.keys(this.tablesData).find(key => {
      return this.tablesData[key].updateUrl === updateUrl;
    });
    let provider = this.tablesData[table].provider;
    Services.obs.notifyObservers(null, "safebrowsing-update-begin", provider);
  }
}

/**
 * Callback function if the update request succeeded.
 * @param waitForUpdate String The number of seconds that the client should
 *        wait before requesting again.
 */
PROT_ListManager.prototype.updateSuccess_ = function(tableList, updateUrl,
                                                     waitForUpdateSec) {
  log("update success for " + tableList + " from " + updateUrl + ": " +
      waitForUpdateSec + "\n");

  // The time unit below are all milliseconds if not specified.

  var delay = 0;
  if (waitForUpdateSec) {
    delay = parseInt(waitForUpdateSec, 10) * 1000;
  }
  // As long as the delay is something sane (5 min to 1 day), update
  // our delay time for requesting updates. We always use a non-repeating
  // timer since the delay is set differently at every callback.
  if (delay > maxDelayMs) {
    log("Ignoring delay from server (too long), waiting " +
        maxDelayMs / 60000 + "min");
    delay = maxDelayMs;
  } else if (delay < minDelayMs) {
    log("Ignoring delay from server (too short), waiting " +
        this.updateInterval / 60000 + "min");
    delay = this.updateInterval;
  } else {
    log("Waiting " + delay / 60000 + "min");
  }

  this.setUpdateCheckTimer(updateUrl, delay);

  // Let the backoff object know that we completed successfully.
  this.requestBackoffs_[updateUrl].noteServerResponse(200);

  // Set last update time for provider
  // Get the provider for these tables, check for consistency
  let tables = tableList.split(",");
  let provider = null;
  for (let table of tables) {
    let newProvider = this.tablesData[table].provider;
    if (provider) {
      if (newProvider !== provider) {
        log("Multiple tables for the same updateURL have a different provider?!");
      }
    } else {
      provider = newProvider;
    }
  }

  // Store the last update time (needed to know if the table is "fresh")
  // and the next update time (to know when to update next).
  let lastUpdatePref = "browser.safebrowsing.provider." + provider + ".lastupdatetime";
  let now = Date.now();
  log("Setting last update of " + provider + " to " + now);
  Services.prefs.setCharPref(lastUpdatePref, now.toString());

  let nextUpdatePref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
  let targetTime = now + delay;
  log("Setting next update of " + provider + " to " + targetTime
      + " (" + delay / 60000 + "min from now)");
  Services.prefs.setCharPref(nextUpdatePref, targetTime.toString());

  Services.obs.notifyObservers(null, "safebrowsing-update-finished", "success");
}

/**
 * Callback function if the update request succeeded.
 * @param result String The error code of the failure
 */
PROT_ListManager.prototype.updateError_ = function(table, updateUrl, result) {
  log("update error for " + table + " from " + updateUrl + ": " + result + "\n");
  // There was some trouble applying the updates. Don't try again for at least
  // updateInterval milliseconds.
  this.setUpdateCheckTimer(updateUrl, this.updateInterval);

  Services.obs.notifyObservers(null, "safebrowsing-update-finished",
                               "update error: " + result);
}

/**
 * Callback function when the download failed
 * @param status String http status or an empty string if connection refused.
 */
PROT_ListManager.prototype.downloadError_ = function(table, updateUrl, status) {
  log("download error for " + table + ": " + status + "\n");
  // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
  // error.  In this case, we treat this is a http 500 error.
  if (!status) {
    status = 500;
  }
  status = parseInt(status, 10);
  this.requestBackoffs_[updateUrl].noteServerResponse(status);
  var delay = this.updateInterval;
  if (this.requestBackoffs_[updateUrl].isErrorStatus(status)) {
    // Schedule an update for when our backoff is complete
    delay = this.requestBackoffs_[updateUrl].nextRequestDelay();
  } else {
    log("Got non error status for error callback?!");
  }

  this.setUpdateCheckTimer(updateUrl, delay);

  Services.obs.notifyObservers(null, "safebrowsing-update-finished",
                               "download error: " + status);
}

/**
 * Get back-off time for the given provider.
 * Return 0 if we are not in back-off mode.
 */
PROT_ListManager.prototype.getBackOffTime = function(provider) {
  let updateUrl = "";
  for (var table in this.tablesData) {
    if (this.tablesData[table].provider == provider) {
      updateUrl = this.tablesData[table].updateUrl;
      break;
    }
  }

  if (!updateUrl || !this.requestBackoffs_[updateUrl]) {
    return 0;
  }

  let delay = this.requestBackoffs_[updateUrl].nextRequestDelay();
  return delay == 0 ? 0 : Date.now() + delay;
}

PROT_ListManager.prototype.QueryInterface = function(iid) {
  if (iid.equals(Ci.nsISupports) ||
      iid.equals(Ci.nsIUrlListManager) ||
      iid.equals(Ci.nsIObserver) ||
      iid.equals(Ci.nsITimerCallback))
    return this;

  throw Components.results.NS_ERROR_NO_INTERFACE;
}

var modScope = this;
function Init() {
  // Pull the library in.
  var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
              .getService().wrappedJSObject;
  modScope.BindToObject = jslib.BindToObject;
  modScope.RequestBackoffV4 = jslib.RequestBackoffV4;

  // We only need to call Init once.
  modScope.Init = function() {};
}

function RegistrationData()
{
}
RegistrationData.prototype = {
    classID: Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}"),
    _xpcom_factory: {
        createInstance: function(outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;
            Init();
            return (new PROT_ListManager()).QueryInterface(iid);
        }
    },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RegistrationData]);
PK
!<U>> components/nsUrlClassifierLib.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// We wastefully reload the same JS files across components.  This puts all
// the common JS files used by safebrowsing and url-classifier into a
// single component.

const Cc = Components.classes;
const Ci = Components.interfaces;
const G_GDEBUG = false;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

/**
 * Partially applies a function to a particular "this object" and zero or
 * more arguments. The result is a new function with some arguments of the first
 * function pre-filled and the value of |this| "pre-specified".
 *
 * Remaining arguments specified at call-time are appended to the pre-
 * specified ones.
 *
 * Usage:
 * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
 * barMethBound("arg3", "arg4");
 *
 * @param fn {string} Reference to the function to be bound
 *
 * @param self {object} Specifies the object which |this| should point to
 * when the function is run. If the value is null or undefined, it will default
 * to the global object.
 *
 * @returns {function} A partially-applied form of the speficied function.
 */
this.BindToObject = function BindToObject(fn, self, opt_args) {
  var boundargs = fn.boundArgs_ || [];
  boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length));

  if (fn.boundSelf_)
    self = fn.boundSelf_;
  if (fn.boundFn_)
    fn = fn.boundFn_;

  var newfn = function() {
    // Combine the static args and the new args into one big array
    var args = boundargs.concat(Array.slice(arguments));
    return fn.apply(self, args);
  }

  newfn.boundArgs_ = boundargs;
  newfn.boundSelf_ = self;
  newfn.boundFn_ = fn;

  return newfn;
}

// This implements logic for stopping requests if the server starts to return
// too many errors.  If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we
// back off for TIMEOUT_INCREMENT minutes.  If we get another error
// immediately after we restart, we double the timeout and add
// TIMEOUT_INCREMENT minutes, etc.
// 
// This is similar to the logic used by the search suggestion service.

// HTTP responses that count as an error.  We also include any 5xx response
// as an error.
this.HTTP_FOUND                 = 302;
this.HTTP_SEE_OTHER             = 303;
this.HTTP_TEMPORARY_REDIRECT    = 307;

/**
 * @param maxErrors Number of times to request before backing off.
 * @param retryIncrement Time (ms) for each retry before backing off.
 * @param maxRequests Number the number of requests needed to trigger backoff
 * @param requestPeriod Number time (ms) in which maxRequests have to occur to
 *     trigger the backoff behavior (0 to disable maxRequests)
 * @param timeoutIncrement Number time (ms) the starting timeout period
 *     we double this time for consecutive errors
 * @param maxTimeout Number time (ms) maximum timeout period
 */
this.RequestBackoff =
function RequestBackoff(maxErrors, retryIncrement,
                        maxRequests, requestPeriod,
                        timeoutIncrement, maxTimeout) {
  this.MAX_ERRORS_ = maxErrors;
  this.RETRY_INCREMENT_ = retryIncrement;
  this.MAX_REQUESTS_ = maxRequests;
  this.REQUEST_PERIOD_ = requestPeriod;
  this.TIMEOUT_INCREMENT_ = timeoutIncrement;
  this.MAX_TIMEOUT_ = maxTimeout;

  // Queue of ints keeping the time of all requests
  this.requestTimes_ = [];

  this.numErrors_ = 0;
  this.errorTimeout_ = 0;
  this.nextRequestTime_ = 0;
}

/**
 * Reset the object for reuse. This deliberately doesn't clear requestTimes_.
 */
RequestBackoff.prototype.reset = function() {
  this.numErrors_ = 0;
  this.errorTimeout_ = 0;
  this.nextRequestTime_ = 0;
}

/**
 * Check to see if we can make a request.
 */
RequestBackoff.prototype.canMakeRequest = function() {
  var now = Date.now();
  if (now < this.nextRequestTime_) {
    return false;
  }

  return (this.requestTimes_.length < this.MAX_REQUESTS_ ||
          (now - this.requestTimes_[0]) > this.REQUEST_PERIOD_);
}

RequestBackoff.prototype.noteRequest = function() {
  var now = Date.now();
  this.requestTimes_.push(now);

  // We only care about keeping track of MAX_REQUESTS
  if (this.requestTimes_.length > this.MAX_REQUESTS_)
    this.requestTimes_.shift();
}

RequestBackoff.prototype.nextRequestDelay = function() {
  return Math.max(0, this.nextRequestTime_ - Date.now());
}

/**
 * Notify this object of the last server response.  If it's an error,
 */
RequestBackoff.prototype.noteServerResponse = function(status) {
  if (this.isErrorStatus(status)) {
    this.numErrors_++;

    if (this.numErrors_ < this.MAX_ERRORS_)
      this.errorTimeout_ = this.RETRY_INCREMENT_;
    else if (this.numErrors_ == this.MAX_ERRORS_)
      this.errorTimeout_ = this.TIMEOUT_INCREMENT_;
    else
      this.errorTimeout_ *= 2;

    this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_);
    this.nextRequestTime_ = Date.now() + this.errorTimeout_;
  } else {
    // Reset error timeout, allow requests to go through.
    this.reset();
  }
}

/**
 * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors.
 * @param status Number http status
 * @return Boolean true if we consider this http status an error
 */
RequestBackoff.prototype.isErrorStatus = function(status) {
  return ((400 <= status && status <= 599) ||
          HTTP_FOUND == status ||
          HTTP_SEE_OTHER == status ||
          HTTP_TEMPORARY_REDIRECT == status);
}

// Wrap a general-purpose |RequestBackoff| to a v4-specific one
// since both listmanager and hashcompleter would use it.
// Note that |maxRequests| and |requestPeriod| is still configurable
// to throttle pending requests.
function RequestBackoffV4(maxRequests, requestPeriod) {
  let rand = Math.random();
  let retryInterval = Math.floor(15 * 60 * 1000 * (rand + 1));   // 15 ~ 30 min.
  let backoffInterval = Math.floor(30 * 60 * 1000 * (rand + 1)); // 30 ~ 60 min.

  return new RequestBackoff(2 /* max errors */,
                retryInterval /* retry interval, 15~30 min */,
                  maxRequests /* num requests */,
                requestPeriod /* request time, 60 min */,
              backoffInterval /* backoff interval, 60 min */,
          24 * 60 * 60 * 1000 /* max backoff, 24hr */);
}

// Expose this whole component.
var lib = this;

function UrlClassifierLib() {
  this.wrappedJSObject = lib;
}
UrlClassifierLib.prototype.classID = Components.ID("{26a4a019-2827-4a89-a85c-5931a678823a}");
UrlClassifierLib.prototype.QueryInterface = XPCOMUtils.generateQI([]);

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UrlClassifierLib]);
PK
!<;UU8components/PrivateBrowsingTrackingProtectionWhitelist.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

function PrivateBrowsingTrackingProtectionWhitelist() {
  // The list of URIs explicitly excluded from tracking protection.
  this._allowlist = [];

  Services.obs.addObserver(this, "last-pb-context-exited", true);
}

PrivateBrowsingTrackingProtectionWhitelist.prototype = {
  classID: Components.ID("{a319b616-c45d-4037-8d86-01c592b5a9af}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivateBrowsingTrackingProtectionWhitelist,
                                         Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsISupports]),
  _xpcom_factory: XPCOMUtils.generateSingletonFactory(PrivateBrowsingTrackingProtectionWhitelist),

  /**
   * Add the provided URI to the list of allowed tracking sites.
   *
   * @param uri nsIURI
   *        The URI to add to the list.
   */
  addToAllowList(uri) {
    if (this._allowlist.indexOf(uri.spec) === -1) {
      this._allowlist.push(uri.spec);
    }
  },

  /**
   * Remove the provided URI from the list of allowed tracking sites.
   *
   * @param uri nsIURI
   *        The URI to add to the list.
   */
  removeFromAllowList(uri) {
    let index = this._allowlist.indexOf(uri.spec);
    if (index !== -1) {
      this._allowlist.splice(index, 1);
    }
  },

  /**
   * Check if the provided URI exists in the list of allowed tracking sites.
   *
   * @param uri nsIURI
   *        The URI to add to the list.
   */
  existsInAllowList(uri) {
    return this._allowlist.indexOf(uri.spec) !== -1;
  },

  observe(subject, topic, data) {
    if (topic == "last-pb-context-exited") {
      this._allowlist = [];
    }
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PrivateBrowsingTrackingProtectionWhitelist]);
PK
!<8ůcomponents/SecurityReporter.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/. */

const { classes: Cc, interfaces: Ci, utils: Cu} = Components;

Cu.importGlobalProperties(["fetch"]);

const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const protocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"]
                          .getService(Ci.nsIHttpProtocolHandler);
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
const HISTOGRAM_ID = "TLS_ERROR_REPORT_UI";


XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                  "resource://gre/modules/UpdateUtils.jsm");

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 SecurityReporter() { }

SecurityReporter.prototype = {
  classDescription: "Security reporter component",
  classID:          Components.ID("{8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b}"),
  contractID:       "@mozilla.org/securityreporter;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISecurityReporter]),
  reportTLSError(transportSecurityInfo, hostname, port) {
    // don't send if there's no transportSecurityInfo (since the report cannot
    // contain anything of interest)
    if (!transportSecurityInfo) {
      return;
    }

    // don't send a report if the pref is not enabled
    if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
      return;
    }

    // Don't send a report if the host we're connecting to is the report
    // server (otherwise we'll get loops when this fails)
    let endpoint =
      Services.prefs.getCharPref("security.ssl.errorReporting.url");
    let reportURI = Services.io.newURI(endpoint);

    if (reportURI.host == hostname) {
      return;
    }

    // Convert the nsIX509CertList into a format that can be parsed into
    // JSON
    let asciiCertChain = [];

    if (transportSecurityInfo.failedCertChain) {
      let certs = transportSecurityInfo.failedCertChain.getEnumerator();
      while (certs.hasMoreElements()) {
        let cert = certs.getNext();
        cert.QueryInterface(Ci.nsIX509Cert);
        asciiCertChain.push(btoa(getDERString(cert)));
      }
    }

    let report = {
      hostname,
      port,
      timestamp: Math.round(Date.now() / 1000),
      errorCode: transportSecurityInfo.errorCode,
      failedCertChain: asciiCertChain,
      userAgent: protocolHandler.userAgent,
      version: 1,
      build: Services.appinfo.appBuildID,
      product: Services.appinfo.name,
      channel: UpdateUtils.UpdateChannel
    }

    fetch(endpoint, {
      method: "POST",
      body: JSON.stringify(report),
      headers: {
        "Content-Type": "application/json"
      }
    }).then(function(aResponse) {
      if (!aResponse.ok) {
        // request returned non-success status
        Services.telemetry.getHistogramById(HISTOGRAM_ID)
          .add(TLS_ERROR_REPORT_TELEMETRY_FAILURE);
      } else {
        Services.telemetry.getHistogramById(HISTOGRAM_ID)
          .add(TLS_ERROR_REPORT_TELEMETRY_SUCCESS);
      }
    }).catch(function(e) {
      // error making request to reportURL
      Services.telemetry.getHistogramById(HISTOGRAM_ID)
          .add(TLS_ERROR_REPORT_TELEMETRY_FAILURE);
    });
  }
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SecurityReporter]);
PK
!<+55+chrome/toolkit/content/extensions/dummy.xul<?xml version="1.0"?>
<window id="documentElement"/>
PK
!<?g1	>>/chrome/toolkit/content/extensions/ext-alarms.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

// WeakMap[Extension -> Map[name -> Alarm]]
let alarmsMap = new WeakMap();

// WeakMap[Extension -> Set[callback]]
let alarmCallbacksMap = new WeakMap();

// Manages an alarm created by the extension (alarms API).
function Alarm(extension, name, alarmInfo) {
  this.extension = extension;
  this.name = name;
  this.when = alarmInfo.when;
  this.delayInMinutes = alarmInfo.delayInMinutes;
  this.periodInMinutes = alarmInfo.periodInMinutes;
  this.canceled = false;

  let delay, scheduledTime;
  if (this.when) {
    scheduledTime = this.when;
    delay = this.when - Date.now();
  } else {
    if (!this.delayInMinutes) {
      this.delayInMinutes = this.periodInMinutes;
    }
    delay = this.delayInMinutes * 60 * 1000;
    scheduledTime = Date.now() + delay;
  }

  this.scheduledTime = scheduledTime;

  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
  this.timer = timer;
}

Alarm.prototype = {
  clear() {
    this.timer.cancel();
    alarmsMap.get(this.extension).delete(this.name);
    this.canceled = true;
  },

  observe(subject, topic, data) {
    if (this.canceled) {
      return;
    }

    for (let callback of alarmCallbacksMap.get(this.extension)) {
      callback(this);
    }

    if (!this.periodInMinutes) {
      this.clear();
      return;
    }

    let delay = this.periodInMinutes * 60 * 1000;
    this.scheduledTime = Date.now() + delay;
    this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  get data() {
    return {
      name: this.name,
      scheduledTime: this.scheduledTime,
      periodInMinutes: this.periodInMinutes,
    };
  },
};

this.alarms = class extends ExtensionAPI {
  onShutdown() {
    let {extension} = this;

    if (alarmsMap.has(extension)) {
      for (let alarm of alarmsMap.get(extension).values()) {
        alarm.clear();
      }
      alarmsMap.delete(extension);
      alarmCallbacksMap.delete(extension);
    }
  }

  getAPI(context) {
    let {extension} = context;

    alarmsMap.set(extension, new Map());
    alarmCallbacksMap.set(extension, new Set());

    return {
      alarms: {
        create: function(name, alarmInfo) {
          name = name || "";
          let alarms = alarmsMap.get(extension);
          if (alarms.has(name)) {
            alarms.get(name).clear();
          }
          let alarm = new Alarm(extension, name, alarmInfo);
          alarms.set(alarm.name, alarm);
        },

        get: function(name) {
          name = name || "";
          let alarms = alarmsMap.get(extension);
          if (alarms.has(name)) {
            return Promise.resolve(alarms.get(name).data);
          }
          return Promise.resolve();
        },

        getAll: function() {
          let result = Array.from(alarmsMap.get(extension).values(), alarm => alarm.data);
          return Promise.resolve(result);
        },

        clear: function(name) {
          name = name || "";
          let alarms = alarmsMap.get(extension);
          if (alarms.has(name)) {
            alarms.get(name).clear();
            return Promise.resolve(true);
          }
          return Promise.resolve(false);
        },

        clearAll: function() {
          let cleared = false;
          for (let alarm of alarmsMap.get(extension).values()) {
            alarm.clear();
            cleared = true;
          }
          return Promise.resolve(cleared);
        },

        onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
          let callback = alarm => {
            fire.sync(alarm.data);
          };

          alarmCallbacksMap.get(extension).add(callback);
          return () => {
            alarmCallbacksMap.get(extension).delete(callback);
          };
        }).api(),
      },
    };
  }
};
PK
!<!e17chrome/toolkit/content/extensions/ext-backgroundPage.js"use strict";

Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                  "resource://gre/modules/TelemetryStopwatch.jsm");

Cu.import("resource://gre/modules/ExtensionParent.jsm");
var {
  HiddenExtensionPage,
  promiseExtensionViewLoaded,
} = ExtensionParent;

// Responsible for the background_page section of the manifest.
class BackgroundPage extends HiddenExtensionPage {
  constructor(extension, options) {
    super(extension, "background");

    this.page = options.page || null;
    this.isGenerated = !!options.scripts;

    if (this.page) {
      this.url = this.extension.baseURI.resolve(this.page);
    } else if (this.isGenerated) {
      this.url = this.extension.baseURI.resolve("_generated_background_page.html");
    }

    if (!this.extension.isExtensionURL(this.url)) {
      this.extension.manifestError("Background page must be a file within the extension");
      this.url = this.extension.baseURI.resolve("_blank.html");
    }
  }

  async build() {
    TelemetryStopwatch.start("WEBEXT_BACKGROUND_PAGE_LOAD_MS", this);
    await this.createBrowserElement();
    this.extension._backgroundPageFrameLoader = this.browser.frameLoader;

    extensions.emit("extension-browser-inserted", this.browser);

    this.browser.loadURI(this.url);

    let context = await promiseExtensionViewLoaded(this.browser);
    TelemetryStopwatch.finish("WEBEXT_BACKGROUND_PAGE_LOAD_MS", this);

    if (context) {
      // Wait until all event listeners registered by the script so far
      // to be handled.
      await Promise.all(context.listenerPromises);
      context.listenerPromises = null;
    }

    this.extension.emit("startup");
  }

  shutdown() {
    this.extension._backgroundPageFrameLoader = null;
    super.shutdown();
  }
}

this.backgroundPage = class extends ExtensionAPI {
  onManifestEntry(entryName) {
    let {manifest} = this.extension;

    this.bgPage = new BackgroundPage(this.extension, manifest.background);

    return this.bgPage.build();
  }

  onShutdown() {
    this.bgPage.shutdown();
  }
};
PK
!<Y+Y+8chrome/toolkit/content/extensions/ext-browser-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
                                  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                  "resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionCommon",
                                  "resource://gre/modules/ExtensionCommon.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "require",
                                  "resource://devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                  "resource://gre/modules/Timer.jsm");

Cu.import("resource://gre/modules/ExtensionUtils.jsm");
const {
  getWinUtils,
} = ExtensionUtils;

/* eslint-env mozilla/frame-script */

// Minimum time between two resizes.
const RESIZE_TIMEOUT = 100;

/**
 * Check if the provided color is fully opaque.
 *
 * @param   {string} color
 *          Any valid CSS color.
 * @returns {boolean} true if the color is opaque.
 */
const isOpaque = function(color) {
  try {
    if (/(rgba|hsla)/i.test(color)) {
      // Match .123456, 123.456, 123456 with an optional % sign.
      let numberRe = /(\.\d+|\d+\.?\d*)%?/g;
      // hsla/rgba, opacity is the last number in the color string (can be a percentage).
      let opacity = color.match(numberRe)[3];

      // Convert to [0, 1] space if the opacity was expressed as a percentage.
      if (opacity.includes("%")) {
        opacity = opacity.slice(0, -1);
        opacity = opacity / 100;
      }

      return opacity * 1 >= 1;
    } else if (/^#[a-f0-9]{4}$/i.test(color)) {
      // Hex color with 4 characters, opacity is one if last character is F
      return color.toUpperCase().endsWith("F");
    } else if (/^#[a-f0-9]{8}$/i.test(color)) {
      // Hex color with 8 characters, opacity is one if last 2 characters are FF
      return color.toUpperCase().endsWith("FF");
    }
  } catch (e) {
    // Invalid color.
  }
  return true;
};

const BrowserListener = {
  init({allowScriptsToClose, blockParser, fixedWidth, maxHeight, maxWidth, stylesheets, isInline}) {
    this.fixedWidth = fixedWidth;
    this.stylesheets = stylesheets || [];

    this.isInline = isInline;
    this.maxWidth = maxWidth;
    this.maxHeight = maxHeight;

    this.blockParser = blockParser;
    this.needsResize = fixedWidth || maxHeight || maxWidth;

    this.oldBackground = null;

    if (allowScriptsToClose) {
      getWinUtils(content).allowScriptsToClose();
    }

    // Force external links to open in tabs.
    docShell.isAppTab = true;

    if (this.blockParser) {
      this.blockingPromise = new Promise(resolve => {
        this.unblockParser = resolve;
      });
      addEventListener("DOMDocElementInserted", this, true);
    }

    addEventListener("load", this, true);
    addEventListener("DOMWindowCreated", this, true);
    addEventListener("DOMContentLoaded", this, true);
    addEventListener("DOMWindowClose", this, true);
    addEventListener("MozScrolledAreaChanged", this, true);
  },

  destroy() {
    if (this.blockParser) {
      removeEventListener("DOMDocElementInserted", this, true);
    }

    removeEventListener("load", this, true);
    removeEventListener("DOMWindowCreated", this, true);
    removeEventListener("DOMContentLoaded", this, true);
    removeEventListener("DOMWindowClose", this, true);
    removeEventListener("MozScrolledAreaChanged", this, true);
  },

  receiveMessage({name, data}) {
    if (name === "Extension:InitBrowser") {
      this.init(data);
    } else if (name === "Extension:UnblockParser") {
      if (this.unblockParser) {
        this.unblockParser();
        this.blockingPromise = null;
      }
    }
  },

  loadStylesheets() {
    let winUtils = getWinUtils(content);

    for (let url of this.stylesheets) {
      winUtils.addSheet(ExtensionCommon.stylesheetMap.get(url), winUtils.AGENT_SHEET);
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "DOMDocElementInserted":
        if (this.blockingPromise) {
          event.target.blockParsing(this.blockingPromise);
        }
        break;

      case "DOMWindowCreated":
        if (event.target === content.document) {
          this.loadStylesheets();
        }
        break;

      case "DOMWindowClose":
        if (event.target === content) {
          event.preventDefault();

          sendAsyncMessage("Extension:DOMWindowClose");
        }
        break;

      case "DOMContentLoaded":
        if (event.target === content.document) {
          sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});

          if (this.needsResize) {
            this.handleDOMChange(true);
          }
        }
        break;

      case "load":
        if (event.target.contentWindow === content) {
          // For about:addons inline <browser>s, we currently receive a load
          // event on the <browser> element, but no load or DOMContentLoaded
          // events from the content window.

          // Inline browsers don't receive the "DOMWindowCreated" event, so this
          // is a workaround to load the stylesheets.
          if (this.isInline) {
            this.loadStylesheets();
          }
          sendAsyncMessage("Extension:BrowserContentLoaded", {url: content.location.href});
        } else if (event.target !== content.document) {
          break;
        }

        if (!this.needsResize) {
          break;
        }

        // We use a capturing listener, so we get this event earlier than any
        // load listeners in the content page. Resizing after a timeout ensures
        // that we calculate the size after the entire event cycle has completed
        // (unless someone spins the event loop, anyway), and hopefully after
        // the content has made any modifications.
        Promise.resolve().then(() => {
          this.handleDOMChange(true);
        });

        // Mutation observer to make sure the panel shrinks when the content does.
        new content.MutationObserver(this.handleDOMChange.bind(this)).observe(
          content.document.documentElement, {
            attributes: true,
            characterData: true,
            childList: true,
            subtree: true,
          });
        break;

      case "MozScrolledAreaChanged":
        if (this.needsResize) {
          this.handleDOMChange();
        }
        break;
    }
  },

  // Resizes the browser to match the preferred size of the content (debounced).
  handleDOMChange(ignoreThrottling = false) {
    if (ignoreThrottling && this.resizeTimeout) {
      clearTimeout(this.resizeTimeout);
      this.resizeTimeout = null;
    }

    if (this.resizeTimeout == null) {
      this.resizeTimeout = setTimeout(() => {
        try {
          if (content) {
            this._handleDOMChange("delayed");
          }
        } finally {
          this.resizeTimeout = null;
        }
      }, RESIZE_TIMEOUT);

      this._handleDOMChange();
    }
  },

  _handleDOMChange(detail) {
    let doc = content.document;

    let body = doc.body;
    if (!body || doc.compatMode === "BackCompat") {
      // In quirks mode, the root element is used as the scroll frame, and the
      // body lies about its scroll geometry, and returns the values for the
      // root instead.
      body = doc.documentElement;
    }


    let result;
    if (this.fixedWidth) {
      // If we're in a fixed-width area (namely a slide-in subview of the main
      // menu panel), we need to calculate the view height based on the
      // preferred height of the content document's root scrollable element at the
      // current width, rather than the complete preferred dimensions of the
      // content window.

      // Compensate for any offsets (margin, padding, ...) between the scroll
      // area of the body and the outer height of the document.
      let getHeight = elem => elem.getBoundingClientRect(elem).height;
      let bodyPadding = getHeight(doc.documentElement) - getHeight(body);

      let height = Math.ceil(body.scrollHeight + bodyPadding);

      result = {height, detail};
    } else {
      let background = doc.defaultView.getComputedStyle(body).backgroundColor;
      if (!isOpaque(background)) {
        // Ignore non-opaque backgrounds.
        background = null;
      }

      if (background !== this.oldBackground) {
        sendAsyncMessage("Extension:BrowserBackgroundChanged", {background});
      }
      this.oldBackground = background;


      // Adjust the size of the browser based on its content's preferred size.
      let {contentViewer} = docShell;
      let ratio = content.devicePixelRatio;

      let w = {}, h = {};
      contentViewer.getContentSizeConstrained(this.maxWidth * ratio,
                                              this.maxHeight * ratio,
                                              w, h);

      let width = Math.ceil(w.value / ratio);
      let height = Math.ceil(h.value / ratio);

      result = {width, height, detail};
    }

    sendAsyncMessage("Extension:BrowserResized", result);
  },
};

addMessageListener("Extension:InitBrowser", BrowserListener);
addMessageListener("Extension:UnblockParser", BrowserListener);

var WebBrowserChrome = {
  onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
    // isAppTab is the value for the docShell that received the click.  We're
    // handling this in the top-level frame and want traversal behavior to
    // match the value for this frame rather than any subframe, so we pass
    // through the docShell.isAppTab value rather than what we were handed.
    return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, docShell.isAppTab);
  },

  shouldLoadURI(docShell, URI, referrer, hasPostData, triggeringPrincipal) {
    return true;
  },

  shouldLoadURIInThisProcess(URI) {
    return E10SUtils.shouldLoadURIInThisProcess(URI);
  },

  reloadInFreshProcess(docShell, URI, referrer, triggeringPrincipal, loadFlags) {
    return false;
  },

  startPrerenderingDocument(href, referrer, triggeringPrincipal) {
  },

  shouldSwitchToPrerenderedDocument(href, referrer, success, failure) {
    return false;
  },
};

if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
  let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsITabChild);
  tabchild.webBrowserChrome = WebBrowserChrome;
}
PK
!<#}tii8chrome/toolkit/content/extensions/ext-browserSettings.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");

Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");

const getSettingsAPI = (extension, name, callback) => {
  return {
    async get(details) {
      return {
        levelOfControl: details.incognito ?
          "not_controllable" :
          await ExtensionPreferencesManager.getLevelOfControl(
            extension, name),
        value: await callback(),
      };
    },
    set(details) {
      return ExtensionPreferencesManager.setSetting(
        extension, name, details.value);
    },
    clear(details) {
      return ExtensionPreferencesManager.removeSetting(extension, name);
    },
  };
};

// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("cacheEnabled", {
  prefNames: [
    "browser.cache.disk.enable",
    "browser.cache.memory.enable",
  ],

  setCallback(value) {
    let returnObj = {};
    for (let pref of this.prefNames) {
      returnObj[pref] = value;
    }
    return returnObj;
  },
});

this.browserSettings = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      browserSettings: {
        cacheEnabled: getSettingsAPI(extension,
          "cacheEnabled",
          () => {
            return Preferences.get("browser.cache.disk.enable") &&
              Preferences.get("browser.cache.memory.enable");
          }),
      },
    };
  }
};
PK
!<669chrome/toolkit/content/extensions/ext-c-backgroundPage.js"use strict";

this.backgroundPage = class extends ExtensionAPI {
  getAPI(context) {
    function getBackgroundPage() {
      for (let view of context.extension.views) {
        if (view.viewType == "background" && context.principal.subsumes(view.principal)) {
          return view.contentWindow;
        }
      }
      return null;
    }
    return {
      extension: {
        getBackgroundPage,
      },

      runtime: {
        getBackgroundPage() {
          return context.cloneScope.Promise.resolve(getBackgroundPage());
        },
      },
    };
  }
};
PK
!<g4chrome/toolkit/content/extensions/ext-c-extension.js"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");

this.extension = class extends ExtensionAPI {
  getAPI(context) {
    let api = {
      getURL(url) {
        return context.extension.baseURI.resolve(url);
      },

      get lastError() {
        return context.lastError;
      },

      get inIncognitoContext() {
        return context.incognito;
      },
    };

    if (context.envType === "addon_child") {
      api.getViews = function(fetchProperties) {
        let result = Cu.cloneInto([], context.cloneScope);

        for (let view of context.extension.views) {
          if (!view.active) {
            continue;
          }
          if (!context.principal.subsumes(view.principal)) {
            continue;
          }

          if (fetchProperties !== null) {
            if (fetchProperties.type !== null && view.viewType != fetchProperties.type) {
              continue;
            }

            if (fetchProperties.windowId !== null && view.windowId != fetchProperties.windowId) {
              continue;
            }

            if (fetchProperties.tabId !== null && view.tabId != fetchProperties.tabId) {
              continue;
            }
          }

          result.push(view.contentWindow);
        }

        return result;
      };
    }

    return {extension: api};
  }
};
PK
!<i3chrome/toolkit/content/extensions/ext-c-identity.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

var {Constructor: CC} = Components;

XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                  "resource://services-common/utils.js");
XPCOMUtils.defineLazyPreferenceGetter(this, "redirectDomain",
                                      "extensions.webextensions.identity.redirectDomain");

let CryptoHash = CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString");

Cu.importGlobalProperties(["URL", "TextEncoder"]);

const computeHash = str => {
  let byteArr = new TextEncoder().encode(str);
  let hash = new CryptoHash("sha1");
  hash.update(byteArr, byteArr.length);
  return CommonUtils.bytesAsHex(hash.finish(false));
};

this.identity = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      identity: {
        getRedirectURL: function(path = "") {
          let hash = computeHash(extension.id);
          let url = new URL(`https://${hash}.${redirectDomain}/`);
          url.pathname = path;
          return url.href;
        },
      },
    };
  }
};
PK
!<R2chrome/toolkit/content/extensions/ext-c-runtime.js"use strict";

this.runtime = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;

    return {
      runtime: {
        onConnect: context.messenger.onConnect("runtime.onConnect"),

        onMessage: context.messenger.onMessage("runtime.onMessage"),

        onConnectExternal: context.messenger.onConnectExternal("runtime.onConnectExternal"),

        onMessageExternal: context.messenger.onMessageExternal("runtime.onMessageExternal"),

        connect: function(extensionId, connectInfo) {
          let name = (connectInfo !== null && connectInfo.name) || "";
          extensionId = extensionId || extension.id;
          let recipient = {extensionId};

          return context.messenger.connect(context.messageManager, name, recipient);
        },

        sendMessage(...args) {
          let extensionId, message, options, responseCallback;

          if (typeof args[args.length - 1] === "function") {
            responseCallback = args.pop();
          }

          function checkOptions(options) {
            let toProxyScript = false;
            if (typeof options !== "object") {
              return [false, "runtime.sendMessage's options argument is invalid"];
            }

            for (let key of Object.keys(options)) {
              if (key === "toProxyScript") {
                let value = options[key];
                if (typeof value !== "boolean") {
                  return [false, "runtime.sendMessage's options.toProxyScript argument is invalid"];
                }
                toProxyScript = value;
              } else {
                return [false, `Unexpected property ${key}`];
              }
            }

            return [true, {toProxyScript}];
          }

          if (!args.length) {
            return Promise.reject({message: "runtime.sendMessage's message argument is missing"});
          } else if (args.length === 1) {
            message = args[0];
          } else if (args.length === 2) {
            // With two optional arguments, this is the ambiguous case,
            // particularly sendMessage("string", {} or null)
            // Given that sending a message within the extension is generally
            // more common than sending the empty object to another extension,
            // we prefer that conclusion, as long as the second argument looks
            // like valid options object, or is null/undefined.
            let [validOpts] = checkOptions(args[1]);
            if (validOpts || args[1] == null) {
              [message, options] = args;
            } else {
              [extensionId, message] = args;
            }
          } else if (args.length === 3 || (args.length === 4 && args[3] == null)) {
            [extensionId, message, options] = args;
          } else if (args.length === 4 && !responseCallback) {
            return Promise.reject({message: "runtime.sendMessage's last argument is not a function"});
          } else {
            return Promise.reject({message: "runtime.sendMessage received too many arguments"});
          }

          if (extensionId != null && typeof extensionId !== "string") {
            return Promise.reject({message: "runtime.sendMessage's extensionId argument is invalid"});
          }

          extensionId = extensionId || extension.id;
          let recipient = {extensionId};

          if (options != null) {
            let [valid, arg] = checkOptions(options);
            if (!valid) {
              return Promise.reject({message: arg});
            }
            Object.assign(recipient, arg);
          }

          return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
        },

        connectNative(application) {
          let recipient = {
            childId: context.childManager.id,
            toNativeApp: application,
          };

          return context.messenger.connectNative(context.messageManager, "", recipient);
        },

        sendNativeMessage(application, message) {
          let recipient = {
            childId: context.childManager.id,
            toNativeApp: application,
          };
          return context.messenger.sendNativeMessage(context.messageManager, message, recipient);
        },

        get lastError() {
          return context.lastError;
        },

        getManifest() {
          return Cu.cloneInto(extension.manifest, context.cloneScope);
        },

        id: extension.id,

        getURL: function(url) {
          return extension.baseURI.resolve(url);
        },
      },
    };
  }
};
PK
!<"2chrome/toolkit/content/extensions/ext-c-storage.js"use strict";

/* import-globals-from ext-c-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                  "resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                  "resource://gre/modules/TelemetryStopwatch.jsm");
Cu.import("resource://gre/modules/Services.jsm");

var {
  ExtensionError,
} = ExtensionUtils;

const storageGetHistogram = "WEBEXT_STORAGE_LOCAL_GET_MS";
const storageSetHistogram = "WEBEXT_STORAGE_LOCAL_SET_MS";

this.storage = class extends ExtensionAPI {
  getAPI(context) {
    /**
     * Serializes the given storage items for transporting to the parent
     * process.
     *
     * @param {Array<string>|object} items
     *        The items to serialize. If an object is provided, its
     *        values are serialized to StructuredCloneHolder objects.
     *        Otherwise, it is returned as-is.
     * @returns {Array<string>|object}
     */
    function serialize(items) {
      if (items && typeof items === "object" && !Array.isArray(items)) {
        let result = {};
        for (let [key, value] of Object.entries(items)) {
          try {
            result[key] = new StructuredCloneHolder(value, context.cloneScope);
          } catch (e) {
            throw new ExtensionError(String(e));
          }
        }
        return result;
      }
      return items;
    }

    /**
     * Deserializes the given storage items from the parent process into
     * the extension context.
     *
     * @param {object} items
     *        The items to deserialize. Any property of the object which
     *        is a StructuredCloneHolder instance is deserialized into
     *        the extension scope. Any other object is cloned into the
     *        extension scope directly.
     * @returns {object}
     */
    function deserialize(items) {
      let result = new context.cloneScope.Object();
      for (let [key, value] of Object.entries(items)) {
        if (value && typeof value === "object" && Cu.getClassName(value, true) === "StructuredCloneHolder") {
          value = value.deserialize(context.cloneScope);
        } else {
          value = Cu.cloneInto(value, context.cloneScope);
        }
        result[key] = value;
      }
      return result;
    }

    function sanitize(items) {
      // The schema validator already takes care of arrays (which are only allowed
      // to contain strings). Strings and null are safe values.
      if (typeof items != "object" || items === null || Array.isArray(items)) {
        return items;
      }
      // If we got here, then `items` is an object generated by `ObjectType`'s
      // `normalize` method from Schemas.jsm. The object returned by `normalize`
      // lives in this compartment, while the values live in compartment of
      // `context.contentWindow`. The `sanitize` method runs with the principal
      // of `context`, so we cannot just use `ExtensionStorage.sanitize` because
      // it is not allowed to access properties of `items`.
      // So we enumerate all properties and sanitize each value individually.
      let sanitized = {};
      for (let [key, value] of Object.entries(items)) {
        sanitized[key] = ExtensionStorage.sanitize(value, context);
      }
      return sanitized;
    }

    return {
      storage: {
        local: {
          get: async function(keys) {
            const stopwatchKey = {};
            TelemetryStopwatch.start(storageGetHistogram, stopwatchKey);
            try {
              let result = await context.childManager.callParentAsyncFunction("storage.local.get", [
                serialize(keys),
              ], null, {noClone: true}).then(deserialize);
              TelemetryStopwatch.finish(storageGetHistogram, stopwatchKey);
              return result;
            } catch (e) {
              TelemetryStopwatch.cancel(storageGetHistogram, stopwatchKey);
              throw e;
            }
          },
          set: async function(items) {
            const stopwatchKey = {};
            TelemetryStopwatch.start(storageSetHistogram, stopwatchKey);
            try {
              let result = await context.childManager.callParentAsyncFunction("storage.local.set", [
                serialize(items),
              ], null, {noClone: true});
              TelemetryStopwatch.finish(storageSetHistogram, stopwatchKey);
              return result;
            } catch (e) {
              TelemetryStopwatch.cancel(storageSetHistogram, stopwatchKey);
              throw e;
            }
          },
        },

        sync: {
          get: function(keys) {
            keys = sanitize(keys);
            return context.childManager.callParentAsyncFunction("storage.sync.get", [
              keys,
            ]);
          },
          set: function(items) {
            items = sanitize(items);
            return context.childManager.callParentAsyncFunction("storage.sync.set", [
              items,
            ]);
          },
        },

        onChanged: new EventManager(context, "storage.onChanged", fire => {
          let onChanged = (data, area) => {
            let changes = new context.cloneScope.Object();
            for (let [key, value] of Object.entries(data)) {
              changes[key] = deserialize(value);
            }
            fire.raw(changes, area);
          };

          let parent = context.childManager.getParentEvent("storage.onChanged");
          parent.addListener(onChanged);
          return () => {
            parent.removeListener(onChanged);
          };
        }).api(),
      },
    };
  }
};
PK
!<2άڒ/chrome/toolkit/content/extensions/ext-c-test.js"use strict";

// The ext-c-* files are imported into the same scopes.
/* import-globals-from ext-c-toolkit.js */

/**
 * Checks whether the given error matches the given expectations.
 *
 * @param {*} error
 *        The error to check.
 * @param {string|RegExp|function|null} expectedError
 *        The expectation to check against. If this parameter is:
 *
 *        - a string, the error message must exactly equal the string.
 *        - a regular expression, it must match the error message.
 *        - a function, it is called with the error object and its
 *          return value is returned.
 *        - null, the function always returns true.
 * @param {BaseContext} context
 *
 * @returns {boolean}
 *        True if the error matches the expected error.
 */
const errorMatches = (error, expectedError, context) => {
  if (expectedError === null) {
    return true;
  }

  if (typeof expectedError === "function") {
    return context.runSafeWithoutClone(expectedError, error);
  }

  if (typeof error !== "object" || error == null ||
      typeof error.message !== "string") {
    return false;
  }

  if (typeof expectedError === "string") {
    return error.message === expectedError;
  }

  try {
    return expectedError.test(error.message);
  } catch (e) {
    Cu.reportError(e);
  }

  return false;
};

/**
 * Calls .toSource() on the given value, but handles null, undefined,
 * and errors.
 *
 * @param {*} value
 * @returns {string}
 */
const toSource = value => {
  if (value === null) {
    return "null";
  }
  if (value === undefined) {
    return "undefined";
  }
  if (typeof value === "string") {
    return JSON.stringify(value);
  }

  try {
    return String(value.toSource());
  } catch (e) {
    return "<unknown>";
  }
};

this.test = class extends ExtensionAPI {
  getAPI(context) {
    const {extension} = context;

    function getStack() {
      return new context.cloneScope.Error().stack.replace(/^/gm, "    ");
    }

    function assertTrue(value, msg) {
      extension.emit("test-result", Boolean(value), String(msg), getStack());
    }

    return {
      test: {
        sendMessage(...args) {
          extension.emit("test-message", ...args);
        },

        notifyPass(msg) {
          extension.emit("test-done", true, msg, getStack());
        },

        notifyFail(msg) {
          extension.emit("test-done", false, msg, getStack());
        },

        log(msg) {
          extension.emit("test-log", true, msg, getStack());
        },

        fail(msg) {
          assertTrue(false, msg);
        },

        succeed(msg) {
          assertTrue(true, msg);
        },

        assertTrue(value, msg) {
          assertTrue(value, msg);
        },

        assertFalse(value, msg) {
          assertTrue(!value, msg);
        },

        assertEq(expected, actual, msg) {
          let equal = expected === actual;

          expected = String(expected);
          actual = String(actual);

          if (!equal && expected === actual) {
            actual += " (different)";
          }
          extension.emit("test-eq", equal, String(msg), expected, actual, getStack());
        },

        assertRejects(promise, expectedError, msg) {
          // Wrap in a native promise for consistency.
          promise = Promise.resolve(promise);

          if (msg) {
            msg = `: ${msg}`;
          }

          return promise.then(result => {
            assertTrue(false, `Promise resolved, expected rejection${msg}`);
          }, error => {
            let errorMessage = toSource(error && error.message);

            assertTrue(errorMatches(error, expectedError, context),
                       `Promise rejected, expecting rejection to match ${toSource(expectedError)}, ` +
                       `got ${errorMessage}${msg}`);
          });
        },

        assertThrows(func, expectedError, msg) {
          if (msg) {
            msg = `: ${msg}`;
          }

          try {
            func();

            assertTrue(false, `Function did not throw, expected error${msg}`);
          } catch (error) {
            let errorMessage = toSource(error && error.message);

            assertTrue(errorMatches(error, expectedError, context),
                       `Function threw, expecting error to match ${toSource(expectedError)}` +
                       `got ${errorMessage}${msg}`);
          }
        },

        onMessage: new EventManager(context, "test.onMessage", fire => {
          let handler = (event, ...args) => {
            fire.async(...args);
          };

          extension.on("test-harness-message", handler);
          return () => {
            extension.off("test-harness-message", handler);
          };
        }).api(),
      },
    };
  }
};
PK
!<Q		2chrome/toolkit/content/extensions/ext-c-toolkit.js"use strict";

Cu.import("resource://gre/modules/ExtensionCommon.jsm");

// These are defined on "global" which is used for the same scopes as the other
// ext-c-*.js files.
/* exported EventManager */
/* global EventManager: false */

global.EventManager = ExtensionCommon.EventManager;

global.initializeBackgroundPage = (contentWindow) => {
  // Override the `alert()` method inside background windows;
  // we alias it to console.log().
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
  let alertDisplayedWarning = false;
  let alertOverwrite = text => {
    if (!alertDisplayedWarning) {
      require("devtools/client/framework/devtools-browser");

      let hudservice = require("devtools/client/webconsole/hudservice");
      hudservice.openBrowserConsoleOrFocus();

      contentWindow.console.warn("alert() is not supported in background windows; please use console.log instead.");

      alertDisplayedWarning = true;
    }

    contentWindow.console.log(text);
  };
  Cu.exportFunction(alertOverwrite, contentWindow, {defineAs: "alert"});
};

extensions.registerModules({
  backgroundPage: {
    url: "chrome://extensions/content/ext-c-backgroundPage.js",
    scopes: ["addon_child"],
    manifest: ["background"],
    paths: [
      ["extension", "getBackgroundPage"],
      ["runtime", "getBackgroundPage"],
    ],
  },
  extension: {
    url: "chrome://extensions/content/ext-c-extension.js",
    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
    paths: [
      ["extension"],
    ],
  },
  i18n: {
    url: "chrome://extensions/content/ext-i18n.js",
    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
    paths: [
      ["i18n"],
    ],
  },
  runtime: {
    url: "chrome://extensions/content/ext-c-runtime.js",
    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
    paths: [
      ["runtime"],
    ],
  },
  storage: {
    url: "chrome://extensions/content/ext-c-storage.js",
    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
    paths: [
      ["storage"],
    ],
  },
  test: {
    url: "chrome://extensions/content/ext-c-test.js",
    scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"],
    paths: [
      ["test"],
    ],
  },
});

if (AppConstants.MOZ_BUILD_APP === "browser") {
  extensions.registerModules({
    identity: {
      url: "chrome://extensions/content/ext-c-identity.js",
      scopes: ["addon_child"],
      paths: [
        ["identity"],
      ],
    },
  });
}
PK
!<=chrome/toolkit/content/extensions/ext-contextualIdentities.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                  "resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "containersEnabled",
                                      "privacy.userContext.enabled");

const convertIdentity = identity => {
  let result = {
    name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
    icon: identity.icon,
    color: identity.color,
    cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
  };

  return result;
};

this.contextualIdentities = class extends ExtensionAPI {
  getAPI(context) {
    let self = {
      contextualIdentities: {
        get(cookieStoreId) {
          if (!containersEnabled) {
            return Promise.resolve(false);
          }

          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            return Promise.resolve(null);
          }

          let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
          return Promise.resolve(convertIdentity(identity));
        },

        query(details) {
          if (!containersEnabled) {
            return Promise.resolve(false);
          }

          let identities = [];
          ContextualIdentityService.getPublicIdentities().forEach(identity => {
            if (details.name &&
                ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
              return;
            }

            identities.push(convertIdentity(identity));
          });

          return Promise.resolve(identities);
        },

        create(details) {
          if (!containersEnabled) {
            return Promise.resolve(false);
          }

          let identity = ContextualIdentityService.create(details.name,
                                                          details.icon,
                                                          details.color);
          return Promise.resolve(convertIdentity(identity));
        },

        update(cookieStoreId, details) {
          if (!containersEnabled) {
            return Promise.resolve(false);
          }

          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            return Promise.resolve(null);
          }

          let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
          if (!identity) {
            return Promise.resolve(null);
          }

          if (details.name !== null) {
            identity.name = details.name;
          }

          if (details.color !== null) {
            identity.color = details.color;
          }

          if (details.icon !== null) {
            identity.icon = details.icon;
          }

          if (!ContextualIdentityService.update(identity.userContextId,
                                                identity.name, identity.icon,
                                                identity.color)) {
            return Promise.resolve(null);
          }

          return Promise.resolve(convertIdentity(identity));
        },

        remove(cookieStoreId) {
          if (!containersEnabled) {
            return Promise.resolve(false);
          }

          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            return Promise.resolve(null);
          }

          let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
          if (!identity) {
            return Promise.resolve(null);
          }

          // We have to create the identity object before removing it.
          let convertedIdentity = convertIdentity(identity);

          if (!ContextualIdentityService.remove(identity.userContextId)) {
            return Promise.resolve(null);
          }

          return Promise.resolve(convertedIdentity);
        },
      },
    };

    return self;
  }
};
PK
!<880chrome/toolkit/content/extensions/ext-cookies.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                  "resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

/* globals DEFAULT_STORE, PRIVATE_STORE */

const convertCookie = ({cookie, isPrivate}) => {
  let result = {
    name: cookie.name,
    value: cookie.value,
    domain: cookie.host,
    hostOnly: !cookie.isDomain,
    path: cookie.path,
    secure: cookie.isSecure,
    httpOnly: cookie.isHttpOnly,
    session: cookie.isSession,
  };

  if (!cookie.isSession) {
    result.expirationDate = cookie.expiry;
  }

  if (cookie.originAttributes.userContextId) {
    result.storeId = getCookieStoreIdForContainer(cookie.originAttributes.userContextId);
  } else if (cookie.originAttributes.privateBrowsingId || isPrivate) {
    result.storeId = PRIVATE_STORE;
  } else {
    result.storeId = DEFAULT_STORE;
  }

  return result;
};

const isSubdomain = (otherDomain, baseDomain) => {
  return otherDomain == baseDomain || otherDomain.endsWith("." + baseDomain);
};

// Checks that the given extension has permission to set the given cookie for
// the given URI.
const checkSetCookiePermissions = (extension, uri, cookie) => {
  // Permission checks:
  //
  //  - If the extension does not have permissions for the specified
  //    URL, it cannot set cookies for it.
  //
  //  - If the specified URL could not set the given cookie, neither can
  //    the extension.
  //
  // Ideally, we would just have the cookie service make the latter
  // determination, but that turns out to be quite complicated. At the
  // moment, it requires constructing a cookie string and creating a
  // dummy channel, both of which can be problematic. It also triggers
  // a whole set of additional permission and preference checks, which
  // may or may not be desirable.
  //
  // So instead, we do a similar set of checks here. Exactly what
  // cookies a given URL should be able to set is not well-documented,
  // and is not standardized in any standard that anyone actually
  // follows. So instead, we follow the rules used by the cookie
  // service.
  //
  // See source/netwerk/cookie/nsCookieService.cpp, in particular
  // CheckDomain() and SetCookieInternal().

  if (uri.scheme != "http" && uri.scheme != "https") {
    return false;
  }

  if (!extension.whiteListedHosts.matches(uri)) {
    return false;
  }

  if (!cookie.host) {
    // If no explicit host is specified, this becomes a host-only cookie.
    cookie.host = uri.host;
    return true;
  }

  // A leading "." is not expected, but is tolerated if it's not the only
  // character in the host. If there is one, start by stripping it off. We'll
  // add a new one on success.
  if (cookie.host.length > 1) {
    cookie.host = cookie.host.replace(/^\./, "");
  }
  cookie.host = cookie.host.toLowerCase();

  if (cookie.host != uri.host) {
    // Not an exact match, so check for a valid subdomain.
    let baseDomain;
    try {
      baseDomain = Services.eTLD.getBaseDomain(uri);
    } catch (e) {
      if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
          e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
        // The cookie service uses these to determine whether the domain
        // requires an exact match. We already know we don't have an exact
        // match, so return false. In all other cases, re-raise the error.
        return false;
      }
      throw e;
    }

    // The cookie domain must be a subdomain of the base domain. This prevents
    // us from setting cookies for domains like ".co.uk".
    // The domain of the requesting URL must likewise be a subdomain of the
    // cookie domain. This prevents us from setting cookies for entirely
    // unrelated domains.
    if (!isSubdomain(cookie.host, baseDomain) ||
        !isSubdomain(uri.host, cookie.host)) {
      return false;
    }

    // RFC2109 suggests that we may only add cookies for sub-domains 1-level
    // below us, but enforcing that would break the web, so we don't.
  }

  // An explicit domain was passed, so add a leading "." to make this a
  // domain cookie.
  cookie.host = "." + cookie.host;

  // We don't do any significant checking of path permissions. RFC2109
  // suggests we only allow sites to add cookies for sub-paths, similar to
  // same origin policy enforcement, but no-one implements this.

  return true;
};

const query = function* (detailsIn, props, context) {
  // Different callers want to filter on different properties. |props|
  // tells us which ones they're interested in.
  let details = {};
  props.forEach(property => {
    if (detailsIn[property] !== null) {
      details[property] = detailsIn[property];
    }
  });

  if ("domain" in details) {
    details.domain = details.domain.toLowerCase().replace(/^\./, "");
  }

  let userContextId = 0;
  let isPrivate = context.incognito;
  if (details.storeId) {
    if (!isValidCookieStoreId(details.storeId)) {
      return;
    }

    if (isDefaultCookieStoreId(details.storeId)) {
      isPrivate = false;
    } else if (isPrivateCookieStoreId(details.storeId)) {
      isPrivate = true;
    } else if (isContainerCookieStoreId(details.storeId)) {
      isPrivate = false;
      userContextId = getContainerForCookieStoreId(details.storeId);
      if (!userContextId) {
        return;
      }
    }
  }

  let storeId = DEFAULT_STORE;
  if (isPrivate) {
    storeId = PRIVATE_STORE;
  } else if ("storeId" in details) {
    storeId = details.storeId;
  }

  // We can use getCookiesFromHost for faster searching.
  let enumerator;
  let uri;
  let originAttributes = {
    userContextId,
    privateBrowsingId: isPrivate ? 1 : 0,
  };
  if ("url" in details) {
    try {
      uri = Services.io.newURI(details.url).QueryInterface(Ci.nsIURL);
      enumerator = Services.cookies.getCookiesFromHost(uri.host, originAttributes);
    } catch (ex) {
      // This often happens for about: URLs
      return;
    }
  } else if ("domain" in details) {
    enumerator = Services.cookies.getCookiesFromHost(details.domain, originAttributes);
  } else {
    enumerator = Services.cookies.getCookiesWithOriginAttributes(JSON.stringify(originAttributes));
  }

  // Based on nsCookieService::GetCookieStringInternal
  function matches(cookie) {
    function domainMatches(host) {
      return cookie.rawHost == host || (cookie.isDomain && host.endsWith(cookie.host));
    }

    function pathMatches(path) {
      let cookiePath = cookie.path.replace(/\/$/, "");

      if (!path.startsWith(cookiePath)) {
        return false;
      }

      // path == cookiePath, but without the redundant string compare.
      if (path.length == cookiePath.length) {
        return true;
      }

      // URL path is a substring of the cookie path, so it matches if, and
      // only if, the next character is a path delimiter.
      let pathDelimiters = ["/", "?", "#", ";"];
      return pathDelimiters.includes(path[cookiePath.length]);
    }

    // "Restricts the retrieved cookies to those that would match the given URL."
    if (uri) {
      if (!domainMatches(uri.host)) {
        return false;
      }

      if (cookie.isSecure && uri.scheme != "https") {
        return false;
      }

      if (!pathMatches(uri.path)) {
        return false;
      }
    }

    if ("name" in details && details.name != cookie.name) {
      return false;
    }

    // "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."
    if ("domain" in details && !isSubdomain(cookie.rawHost, details.domain)) {
      return false;
    }

    // "Restricts the retrieved cookies to those whose path exactly matches this string.""
    if ("path" in details && details.path != cookie.path) {
      return false;
    }

    if ("secure" in details && details.secure != cookie.isSecure) {
      return false;
    }

    if ("session" in details && details.session != cookie.isSession) {
      return false;
    }

    // Check that the extension has permissions for this host.
    if (!context.extension.whiteListedHosts.matchesCookie(cookie)) {
      return false;
    }

    return true;
  }

  while (enumerator.hasMoreElements()) {
    let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
    if (matches(cookie)) {
      yield {cookie, isPrivate, storeId};
    }
  }
};

this.cookies = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    let self = {
      cookies: {
        get: function(details) {
          // FIXME: We don't sort by length of path and creation time.
          for (let cookie of query(details, ["url", "name", "storeId"], context)) {
            return Promise.resolve(convertCookie(cookie));
          }

          // Found no match.
          return Promise.resolve(null);
        },

        getAll: function(details) {
          let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"];
          let result = Array.from(query(details, allowed, context), convertCookie);

          return Promise.resolve(result);
        },

        set: function(details) {
          let uri = Services.io.newURI(details.url).QueryInterface(Ci.nsIURL);

          let path;
          if (details.path !== null) {
            path = details.path;
          } else {
            // This interface essentially emulates the behavior of the
            // Set-Cookie header. In the case of an omitted path, the cookie
            // service uses the directory path of the requesting URL, ignoring
            // any filename or query parameters.
            path = uri.directory;
          }

          let name = details.name !== null ? details.name : "";
          let value = details.value !== null ? details.value : "";
          let secure = details.secure !== null ? details.secure : false;
          let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
          let isSession = details.expirationDate === null;
          let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
          let isPrivate = context.incognito;
          let userContextId = 0;
          if (isDefaultCookieStoreId(details.storeId)) {
            isPrivate = false;
          } else if (isPrivateCookieStoreId(details.storeId)) {
            isPrivate = true;
          } else if (isContainerCookieStoreId(details.storeId)) {
            let containerId = getContainerForCookieStoreId(details.storeId);
            if (containerId === null) {
              return Promise.reject({message: `Illegal storeId: ${details.storeId}`});
            }
            isPrivate = false;
            userContextId = containerId;
          } else if (details.storeId !== null) {
            return Promise.reject({message: "Unknown storeId"});
          }

          let cookieAttrs = {host: details.domain, path: path, isSecure: secure};
          if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
            return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`});
          }

          let originAttributes = {
            userContextId,
            privateBrowsingId: isPrivate ? 1 : 0,
          };

          // The permission check may have modified the domain, so use
          // the new value instead.
          Services.cookies.add(cookieAttrs.host, path, name, value,
                               secure, httpOnly, isSession, expiry, originAttributes);

          return self.cookies.get(details);
        },

        remove: function(details) {
          for (let {cookie, storeId} of query(details, ["url", "name", "storeId"], context)) {
            Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);

            // Todo: could there be multiple per subdomain?
            return Promise.resolve({
              url: details.url,
              name: details.name,
              storeId,
            });
          }

          return Promise.resolve(null);
        },

        getAllCookieStores: function() {
          let data = {};
          for (let tab of extension.tabManager.query()) {
            if (!(tab.cookieStoreId in data)) {
              data[tab.cookieStoreId] = [];
            }
            data[tab.cookieStoreId].push(tab.id);
          }

          let result = [];
          for (let key in data) {
            result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE});
          }
          return Promise.resolve(result);
        },

        onChanged: new EventManager(context, "cookies.onChanged", fire => {
          let observer = (subject, topic, data) => {
            let notify = (removed, cookie, cause) => {
              cookie.QueryInterface(Ci.nsICookie2);

              if (extension.whiteListedHosts.matchesCookie(cookie)) {
                fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
              }
            };

            // We do our best effort here to map the incompatible states.
            switch (data) {
              case "deleted":
                notify(true, subject, "explicit");
                break;
              case "added":
                notify(false, subject, "explicit");
                break;
              case "changed":
                notify(true, subject, "overwrite");
                notify(false, subject, "explicit");
                break;
              case "batch-deleted":
                subject.QueryInterface(Ci.nsIArray);
                for (let i = 0; i < subject.length; i++) {
                  let cookie = subject.queryElementAt(i, Ci.nsICookie2);
                  if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
                    notify(true, cookie, "expired");
                  } else {
                    notify(true, cookie, "evicted");
                  }
                }
                break;
            }
          };

          Services.obs.addObserver(observer, "cookie-changed");
          Services.obs.addObserver(observer, "private-cookie-changed");
          return () => {
            Services.obs.removeObserver(observer, "cookie-changed");
            Services.obs.removeObserver(observer, "private-cookie-changed");
          };
        }).api(),
      },
    };

    return self;
  }
};
PK
!<&ii2chrome/toolkit/content/extensions/ext-downloads.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                  "resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                  "resource://gre/modules/DownloadPaths.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://gre/modules/EventEmitter.jsm");

var {
  normalizeTime,
} = ExtensionUtils;

var {
  ignoreEvent,
} = ExtensionCommon;

const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito",
                              "danger", "mime", "startTime", "endTime",
                              "estimatedEndTime", "state",
                              "paused", "canResume", "error",
                              "bytesReceived", "totalBytes",
                              "fileSize", "exists",
                              "byExtensionId", "byExtensionName"];

// Fields that we generate onChanged events for.
const DOWNLOAD_ITEM_CHANGE_FIELDS = ["endTime", "state", "paused", "canResume",
                                     "error", "exists"];

// From https://fetch.spec.whatwg.org/#forbidden-header-name
const FORBIDDEN_HEADERS = ["ACCEPT-CHARSET", "ACCEPT-ENCODING",
                           "ACCESS-CONTROL-REQUEST-HEADERS", "ACCESS-CONTROL-REQUEST-METHOD",
                           "CONNECTION", "CONTENT-LENGTH", "COOKIE", "COOKIE2", "DATE", "DNT",
                           "EXPECT", "HOST", "KEEP-ALIVE", "ORIGIN", "REFERER", "TE", "TRAILER",
                           "TRANSFER-ENCODING", "UPGRADE", "VIA"];

const FORBIDDEN_PREFIXES = /^PROXY-|^SEC-/i;

class DownloadItem {
  constructor(id, download, extension) {
    this.id = id;
    this.download = download;
    this.extension = extension;
    this.prechange = {};
  }

  get url() { return this.download.source.url; }
  get referrer() { return this.download.source.referrer; }
  get filename() { return this.download.target.path; }
  get incognito() { return this.download.source.isPrivate; }
  get danger() { return "safe"; } // TODO
  get mime() { return this.download.contentType; }
  get startTime() { return this.download.startTime; }
  get endTime() { return null; } // TODO
  get estimatedEndTime() { return null; } // TODO
  get state() {
    if (this.download.succeeded) {
      return "complete";
    }
    if (this.download.canceled) {
      return "interrupted";
    }
    return "in_progress";
  }
  get paused() {
    return this.download.canceled && this.download.hasPartialData && !this.download.error;
  }
  get canResume() {
    return (this.download.stopped || this.download.canceled) &&
      this.download.hasPartialData && !this.download.error;
  }
  get error() {
    if (!this.download.startTime || !this.download.stopped || this.download.succeeded) {
      return null;
    }
    // TODO store this instead of calculating it

    if (this.download.error) {
      if (this.download.error.becauseSourceFailed) {
        return "NETWORK_FAILED"; // TODO
      }
      if (this.download.error.becauseTargetFailed) {
        return "FILE_FAILED"; // TODO
      }
      return "CRASH";
    }
    return "USER_CANCELED";
  }
  get bytesReceived() {
    return this.download.currentBytes;
  }
  get totalBytes() {
    return this.download.hasProgress ? this.download.totalBytes : -1;
  }
  get fileSize() {
    // todo: this is supposed to be post-compression
    return this.download.succeeded ? this.download.target.size : -1;
  }
  get exists() { return this.download.target.exists; }
  get byExtensionId() { return this.extension ? this.extension.id : undefined; }
  get byExtensionName() { return this.extension ? this.extension.name : undefined; }

  /**
   * Create a cloneable version of this object by pulling all the
   * fields into simple properties (instead of getters).
   *
   * @returns {object} A DownloadItem with flat properties,
   *                   suitable for cloning.
   */
  serialize() {
    let obj = {};
    for (let field of DOWNLOAD_ITEM_FIELDS) {
      obj[field] = this[field];
    }
    if (obj.startTime) {
      obj.startTime = obj.startTime.toISOString();
    }
    return obj;
  }

  // When a change event fires, handlers can look at how an individual
  // field changed by comparing item.fieldname with item.prechange.fieldname.
  // After all handlers have been invoked, this gets called to store the
  // current values of all fields ahead of the next event.
  _storePrechange() {
    for (let field of DOWNLOAD_ITEM_CHANGE_FIELDS) {
      this.prechange[field] = this[field];
    }
  }
}


// DownloadMap maps back and forth betwen the numeric identifiers used in
// the downloads WebExtension API and a Download object from the Downloads jsm.
// todo: make id and extension info persistent (bug 1247794)
const DownloadMap = {
  currentId: 0,
  loadPromise: null,

  // Maps numeric id -> DownloadItem
  byId: new Map(),

  // Maps Download object -> DownloadItem
  byDownload: new WeakMap(),

  lazyInit() {
    if (this.loadPromise == null) {
      EventEmitter.decorate(this);
      this.loadPromise = Downloads.getList(Downloads.ALL).then(list => {
        let self = this;
        return list.addView({
          onDownloadAdded(download) {
            const item = self.newFromDownload(download, null);
            self.emit("create", item);
            item._storePrechange();
          },

          onDownloadRemoved(download) {
            const item = self.byDownload.get(download);
            if (item != null) {
              self.emit("erase", item);
              self.byDownload.delete(download);
              self.byId.delete(item.id);
            }
          },

          onDownloadChanged(download) {
            const item = self.byDownload.get(download);
            if (item == null) {
              Cu.reportError("Got onDownloadChanged for unknown download object");
            } else {
              self.emit("change", item);
              item._storePrechange();
            }
          },
        }).then(() => list.getAll())
          .then(downloads => {
            downloads.forEach(download => {
              this.newFromDownload(download, null);
            });
          })
          .then(() => list);
      });
    }
    return this.loadPromise;
  },

  getDownloadList() {
    return this.lazyInit();
  },

  getAll() {
    return this.lazyInit().then(() => this.byId.values());
  },

  fromId(id) {
    const download = this.byId.get(id);
    if (!download) {
      throw new Error(`Invalid download id ${id}`);
    }
    return download;
  },

  newFromDownload(download, extension) {
    if (this.byDownload.has(download)) {
      return this.byDownload.get(download);
    }

    const id = ++this.currentId;
    let item = new DownloadItem(id, download, extension);
    this.byId.set(id, item);
    this.byDownload.set(download, item);
    return item;
  },

  erase(item) {
    // This will need to get more complicated for bug 1255507 but for now we
    // only work with downloads in the DownloadList from getAll()
    return this.getDownloadList().then(list => {
      list.remove(item.download);
    });
  },
};

// Create a callable function that filters a DownloadItem based on a
// query object of the type passed to search() or erase().
const downloadQuery = query => {
  let queryTerms = [];
  let queryNegativeTerms = [];
  if (query.query != null) {
    for (let term of query.query) {
      if (term[0] == "-") {
        queryNegativeTerms.push(term.slice(1).toLowerCase());
      } else {
        queryTerms.push(term.toLowerCase());
      }
    }
  }

  function normalizeDownloadTime(arg, before) {
    if (arg == null) {
      return before ? Number.MAX_VALUE : 0;
    }
    return normalizeTime(arg).getTime();
  }

  const startedBefore = normalizeDownloadTime(query.startedBefore, true);
  const startedAfter = normalizeDownloadTime(query.startedAfter, false);
  // const endedBefore = normalizeDownloadTime(query.endedBefore, true);
  // const endedAfter = normalizeDownloadTime(query.endedAfter, false);

  const totalBytesGreater = query.totalBytesGreater || 0;
  const totalBytesLess = (query.totalBytesLess != null)
        ? query.totalBytesLess : Number.MAX_VALUE;

  // Handle options for which we can have a regular expression and/or
  // an explicit value to match.
  function makeMatch(regex, value, field) {
    if (value == null && regex == null) {
      return input => true;
    }

    let re;
    try {
      re = new RegExp(regex || "", "i");
    } catch (err) {
      throw new Error(`Invalid ${field}Regex: ${err.message}`);
    }
    if (value == null) {
      return input => re.test(input);
    }

    value = value.toLowerCase();
    if (re.test(value)) {
      return input => (value == input);
    }
    return input => false;
  }

  const matchFilename = makeMatch(query.filenameRegex, query.filename, "filename");
  const matchUrl = makeMatch(query.urlRegex, query.url, "url");

  return function(item) {
    const url = item.url.toLowerCase();
    const filename = item.filename.toLowerCase();

    if (!queryTerms.every(term => url.includes(term) || filename.includes(term))) {
      return false;
    }

    if (queryNegativeTerms.some(term => url.includes(term) || filename.includes(term))) {
      return false;
    }

    if (!matchFilename(filename) || !matchUrl(url)) {
      return false;
    }

    if (!item.startTime) {
      if (query.startedBefore != null || query.startedAfter != null) {
        return false;
      }
    } else if (item.startTime > startedBefore || item.startTime < startedAfter) {
      return false;
    }

    // todo endedBefore, endedAfter

    if (item.totalBytes == -1) {
      if (query.totalBytesGreater != null || query.totalBytesLess != null) {
        return false;
      }
    } else if (item.totalBytes <= totalBytesGreater || item.totalBytes >= totalBytesLess) {
      return false;
    }

    // todo: include danger
    const SIMPLE_ITEMS = ["id", "mime", "startTime", "endTime", "state",
                          "paused", "error",
                          "bytesReceived", "totalBytes", "fileSize", "exists"];
    for (let field of SIMPLE_ITEMS) {
      if (query[field] != null && item[field] != query[field]) {
        return false;
      }
    }

    return true;
  };
};

const queryHelper = query => {
  let matchFn;
  try {
    matchFn = downloadQuery(query);
  } catch (err) {
    return Promise.reject({message: err.message});
  }

  let compareFn;
  if (query.orderBy != null) {
    const fields = query.orderBy.map(field => field[0] == "-"
                                     ? {reverse: true, name: field.slice(1)}
                                     : {reverse: false, name: field});

    for (let field of fields) {
      if (!DOWNLOAD_ITEM_FIELDS.includes(field.name)) {
        return Promise.reject({message: `Invalid orderBy field ${field.name}`});
      }
    }

    compareFn = (dl1, dl2) => {
      for (let field of fields) {
        const val1 = dl1[field.name];
        const val2 = dl2[field.name];

        if (val1 < val2) {
          return field.reverse ? 1 : -1;
        } else if (val1 > val2) {
          return field.reverse ? -1 : 1;
        }
      }
      return 0;
    };
  }

  return DownloadMap.getAll().then(downloads => {
    if (compareFn) {
      downloads = Array.from(downloads);
      downloads.sort(compareFn);
    }
    let results = [];
    for (let download of downloads) {
      if (query.limit && results.length >= query.limit) {
        break;
      }
      if (matchFn(download)) {
        results.push(download);
      }
    }
    return results;
  });
};

this.downloads = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      downloads: {
        download(options) {
          let {filename} = options;
          if (filename && AppConstants.platform === "win") {
            // cross platform javascript code uses "/"
            filename = filename.replace(/\//g, "\\");
          }

          if (filename != null) {
            if (filename.length == 0) {
              return Promise.reject({message: "filename must not be empty"});
            }

            let path = OS.Path.split(filename);
            if (path.absolute) {
              return Promise.reject({message: "filename must not be an absolute path"});
            }

            if (path.components.some(component => component == "..")) {
              return Promise.reject({message: "filename must not contain back-references (..)"});
            }
          }

          if (options.conflictAction == "prompt") {
            // TODO
            return Promise.reject({message: "conflictAction prompt not yet implemented"});
          }

          if (options.headers) {
            for (let {name} of options.headers) {
              if (FORBIDDEN_HEADERS.includes(name.toUpperCase()) || name.match(FORBIDDEN_PREFIXES)) {
                return Promise.reject({message: "Forbidden request header name"});
              }
            }
          }

          // Handle method, headers and body options.
          function adjustChannel(channel) {
            if (channel instanceof Ci.nsIHttpChannel) {
              const method = options.method || "GET";
              channel.requestMethod = method;

              if (options.headers) {
                for (let {name, value} of options.headers) {
                  channel.setRequestHeader(name, value, false);
                }
              }

              if (options.body != null) {
                const stream = Cc["@mozilla.org/io/string-input-stream;1"]
                               .createInstance(Ci.nsIStringInputStream);
                stream.setData(options.body, options.body.length);

                channel.QueryInterface(Ci.nsIUploadChannel2);
                channel.explicitSetUploadStream(stream, null, -1, method, false);
              }
            }
            return Promise.resolve();
          }

          async function createTarget(downloadsDir) {
            let target;
            if (filename) {
              target = OS.Path.join(downloadsDir, filename);
            } else {
              let uri = NetUtil.newURI(options.url);

              let remote = "download";
              if (uri instanceof Ci.nsIURL) {
                remote = uri.fileName;
              }
              target = OS.Path.join(downloadsDir, remote);
            }

            // Create any needed subdirectories if required by filename.
            const dir = OS.Path.dirname(target);
            await OS.File.makeDir(dir, {from: downloadsDir});

            if (await OS.File.exists(target)) {
              // This has a race, something else could come along and create
              // the file between this test and them time the download code
              // creates the target file.  But we can't easily fix it without
              // modifying DownloadCore so we live with it for now.
              switch (options.conflictAction) {
                case "uniquify":
                default:
                  target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path;
                  if (options.saveAs) {
                    // createNiceUniqueFile actually creates the file, which
                    // is premature if we need to show a SaveAs dialog.
                    await OS.File.remove(target);
                  }
                  break;

                case "overwrite":
                  break;
              }
            }

            if (!options.saveAs) {
              return target;
            }

            // Setup the file picker Save As dialog.
            const picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
            const window = Services.wm.getMostRecentWindow("navigator:browser");
            picker.init(window, null, Ci.nsIFilePicker.modeSave);
            picker.displayDirectory = new FileUtils.File(dir);
            picker.appendFilters(Ci.nsIFilePicker.filterAll);
            picker.defaultString = OS.Path.basename(target);

            // Open the dialog and resolve/reject with the result.
            return new Promise((resolve, reject) => {
              picker.open(result => {
                if (result === Ci.nsIFilePicker.returnCancel) {
                  reject({message: "Download canceled by the user"});
                } else {
                  resolve(picker.file.path);
                }
              });
            });
          }

          let download;
          return Downloads.getPreferredDownloadsDirectory()
            .then(downloadsDir => createTarget(downloadsDir))
            .then(target => {
              const source = {
                url: options.url,
              };

              if (options.method || options.headers || options.body) {
                source.adjustChannel = adjustChannel;
              }

              return Downloads.createDownload({
                source,
                target: {
                  path: target,
                  partFilePath: target + ".part",
                },
              });
            }).then(dl => {
              download = dl;
              return DownloadMap.getDownloadList();
            }).then(list => {
              list.add(download);

              // This is necessary to make pause/resume work.
              download.tryToKeepPartialData = true;
              download.start();

              const item = DownloadMap.newFromDownload(download, extension);
              return item.id;
            });
        },

        removeFile(id) {
          return DownloadMap.lazyInit().then(() => {
            let item;
            try {
              item = DownloadMap.fromId(id);
            } catch (err) {
              return Promise.reject({message: `Invalid download id ${id}`});
            }
            if (item.state !== "complete") {
              return Promise.reject({message: `Cannot remove incomplete download id ${id}`});
            }
            return OS.File.remove(item.filename, {ignoreAbsent: false}).catch((err) => {
              return Promise.reject({message: `Could not remove download id ${item.id} because the file doesn't exist`});
            });
          });
        },

        search(query) {
          return queryHelper(query)
            .then(items => items.map(item => item.serialize()));
        },

        pause(id) {
          return DownloadMap.lazyInit().then(() => {
            let item;
            try {
              item = DownloadMap.fromId(id);
            } catch (err) {
              return Promise.reject({message: `Invalid download id ${id}`});
            }
            if (item.state != "in_progress") {
              return Promise.reject({message: `Download ${id} cannot be paused since it is in state ${item.state}`});
            }

            return item.download.cancel();
          });
        },

        resume(id) {
          return DownloadMap.lazyInit().then(() => {
            let item;
            try {
              item = DownloadMap.fromId(id);
            } catch (err) {
              return Promise.reject({message: `Invalid download id ${id}`});
            }
            if (!item.canResume) {
              return Promise.reject({message: `Download ${id} cannot be resumed`});
            }

            return item.download.start();
          });
        },

        cancel(id) {
          return DownloadMap.lazyInit().then(() => {
            let item;
            try {
              item = DownloadMap.fromId(id);
            } catch (err) {
              return Promise.reject({message: `Invalid download id ${id}`});
            }
            if (item.download.succeeded) {
              return Promise.reject({message: `Download ${id} is already complete`});
            }
            return item.download.finalize(true);
          });
        },

        showDefaultFolder() {
          Downloads.getPreferredDownloadsDirectory().then(dir => {
            let dirobj = new FileUtils.File(dir);
            if (dirobj.isDirectory()) {
              dirobj.launch();
            } else {
              throw new Error(`Download directory ${dirobj.path} is not actually a directory`);
            }
          }).catch(Cu.reportError);
        },

        erase(query) {
          return queryHelper(query).then(items => {
            let results = [];
            let promises = [];
            for (let item of items) {
              promises.push(DownloadMap.erase(item));
              results.push(item.id);
            }
            return Promise.all(promises).then(() => results);
          });
        },

        open(downloadId) {
          return DownloadMap.lazyInit().then(() => {
            let download = DownloadMap.fromId(downloadId).download;
            if (download.succeeded) {
              return download.launch();
            }
            return Promise.reject({message: "Download has not completed."});
          }).catch((error) => {
            return Promise.reject({message: error.message});
          });
        },

        show(downloadId) {
          return DownloadMap.lazyInit().then(() => {
            let download = DownloadMap.fromId(downloadId);
            return download.download.showContainingDirectory();
          }).then(() => {
            return true;
          }).catch(error => {
            return Promise.reject({message: error.message});
          });
        },

        getFileIcon(downloadId, options) {
          return DownloadMap.lazyInit().then(() => {
            let size = options && options.size ? options.size : 32;
            let download = DownloadMap.fromId(downloadId).download;
            let pathPrefix = "";
            let path;

            if (download.succeeded) {
              let file = FileUtils.File(download.target.path);
              path = Services.io.newFileURI(file).spec;
            } else {
              path = OS.Path.basename(download.target.path);
              pathPrefix = "//";
            }

            return new Promise((resolve, reject) => {
              let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
              chromeWebNav
                .QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDocShell)
                .createAboutBlankContentViewer(Services.scriptSecurityManager.getSystemPrincipal());

              let img = chromeWebNav.document.createElement("img");
              img.width = size;
              img.height = size;

              let handleLoad;
              let handleError;
              const cleanup = () => {
                img.removeEventListener("load", handleLoad);
                img.removeEventListener("error", handleError);
                chromeWebNav.close();
                chromeWebNav = null;
              };

              handleLoad = () => {
                let canvas = chromeWebNav.document.createElement("canvas");
                canvas.width = size;
                canvas.height = size;
                let context = canvas.getContext("2d");
                context.drawImage(img, 0, 0, size, size);
                let dataURL = canvas.toDataURL("image/png");
                cleanup();
                resolve(dataURL);
              };

              handleError = (error) => {
                Cu.reportError(error);
                cleanup();
                reject(new Error("An unexpected error occurred"));
              };

              img.addEventListener("load", handleLoad);
              img.addEventListener("error", handleError);
              img.src = `moz-icon:${pathPrefix}${path}?size=${size}`;
            });
          }).catch((error) => {
            return Promise.reject({message: error.message});
          });
        },

        // When we do setShelfEnabled(), check for additional "downloads.shelf" permission.
        // i.e.:
        // setShelfEnabled(enabled) {
        //   if (!extension.hasPermission("downloads.shelf")) {
        //     throw new context.cloneScope.Error("Permission denied because 'downloads.shelf' permission is missing.");
        //   }
        //   ...
        // }

        onChanged: new EventManager(context, "downloads.onChanged", fire => {
          const handler = (what, item) => {
            let changes = {};
            const noundef = val => (val === undefined) ? null : val;
            DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
              if (item[fld] != item.prechange[fld]) {
                changes[fld] = {
                  previous: noundef(item.prechange[fld]),
                  current: noundef(item[fld]),
                };
              }
            });
            if (Object.keys(changes).length > 0) {
              changes.id = item.id;
              fire.async(changes);
            }
          };

          let registerPromise = DownloadMap.getDownloadList().then(() => {
            DownloadMap.on("change", handler);
          });
          return () => {
            registerPromise.then(() => {
              DownloadMap.off("change", handler);
            });
          };
        }).api(),

        onCreated: new EventManager(context, "downloads.onCreated", fire => {
          const handler = (what, item) => {
            fire.async(item.serialize());
          };
          let registerPromise = DownloadMap.getDownloadList().then(() => {
            DownloadMap.on("create", handler);
          });
          return () => {
            registerPromise.then(() => {
              DownloadMap.off("create", handler);
            });
          };
        }).api(),

        onErased: new EventManager(context, "downloads.onErased", fire => {
          const handler = (what, item) => {
            fire.async(item.id);
          };
          let registerPromise = DownloadMap.getDownloadList().then(() => {
            DownloadMap.on("erase", handler);
          });
          return () => {
            registerPromise.then(() => {
              DownloadMap.off("erase", handler);
            });
          };
        }).api(),

        onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
      },
    };
  }
};
PK
!<)92chrome/toolkit/content/extensions/ext-extension.js"use strict";

this.extension = class extends ExtensionAPI {
  getAPI(context) {
    return {
      extension: {
        get lastError() {
          return context.lastError;
        },

        isAllowedIncognitoAccess() {
          return Promise.resolve(true);
        },

        isAllowedFileSchemeAccess() {
          return Promise.resolve(false);
        },
      },
    };
  }
};

PK
!<1]]4chrome/toolkit/content/extensions/ext-geolocation.js"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

// If the user has changed the permission on the addon to something other than
// always allow, then we want to preserve that choice.  We only set the
// permission if it is not set (unknown_action), and we only remove the
// permission on shutdown if it is always allow.

this.geolocation = class extends ExtensionAPI {
  onStartup() {
    let {extension} = this;

    if (extension.hasPermission("geolocation") &&
        Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.UNKNOWN_ACTION) {
      Services.perms.add(extension.principal.URI, "geo",
                         Services.perms.ALLOW_ACTION,
                         Services.perms.EXPIRE_SESSION);
    }
  }

  onShutdown() {
    let {extension} = this;

    if (extension.hasPermission("geolocation") &&
        Services.perms.testPermission(extension.principal.URI, "geo") == Services.perms.ALLOW_ACTION) {
      Services.perms.remove(extension.principal.URI, "geo");
    }
  }
};
PK
!<sT^^-chrome/toolkit/content/extensions/ext-i18n.js"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
                                  "resource:///modules/translation/LanguageDetector.jsm");


this.i18n = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      i18n: {
        getMessage: function(messageName, substitutions) {
          return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
        },

        getAcceptLanguages: function() {
          let result = extension.localeData.acceptLanguages;
          return Promise.resolve(result);
        },

        getUILanguage: function() {
          return extension.localeData.uiLocale;
        },

        detectLanguage: function(text) {
          return LanguageDetector.detectLanguage(text).then(result => ({
            isReliable: result.confident,
            languages: result.languages.map(lang => {
              return {
                language: lang.languageCode,
                percentage: lang.percent,
              };
            }),
          }));
        },
      },
    };
  }
};
PK
!<j8L1chrome/toolkit/content/extensions/ext-identity.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

Cu.importGlobalProperties(["URL", "XMLHttpRequest"]);

var {
  promiseDocumentLoaded,
} = ExtensionUtils;

const checkRedirected = (url, redirectURI) => {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("HEAD", url);
    // We expect this if the user has not authenticated.
    xhr.onload = () => {
      reject(0);
    };
    // An unexpected error happened, log for extension authors.
    xhr.onerror = () => {
      reject(xhr.status);
    };
    // Catch redirect to our redirect_uri before a new request is made.
    xhr.channel.notificationCallbacks = {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSync]),

      getInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink]),

      asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
        let responseURL = newChannel.URI.spec;
        if (responseURL.startsWith(redirectURI)) {
          resolve(responseURL);
          // Cancel the redirect.
          callback.onRedirectVerifyCallback(Components.results.NS_BINDING_ABORTED);
          return;
        }
        callback.onRedirectVerifyCallback(Components.results.NS_OK);
      },
    };
    xhr.send();
  });
};

const openOAuthWindow = (details, redirectURI) => {
  let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  let supportsStringPrefURL = Cc["@mozilla.org/supports-string;1"]
                                .createInstance(Ci.nsISupportsString);
  supportsStringPrefURL.data = details.url;
  args.appendElement(supportsStringPrefURL);

  let window = Services.ww.openWindow(null,
                                      Services.prefs.getCharPref("browser.chromeURL"),
                                      "launchWebAuthFlow_dialog",
                                      "chrome,location=yes,centerscreen,dialog=no,resizable=yes",
                                      args);

  return new Promise((resolve, reject) => {
    let wpl;

    // If the user just closes the window we need to reject
    function unloadlistener() {
      window.removeEventListener("unload", unloadlistener);
      window.gBrowser.removeTabsProgressListener(wpl);
      reject({message: "User cancelled or denied access."});
    }

    wpl = {
      onLocationChange(browser, webProgress, request, locationURI) {
        if (locationURI.spec.startsWith(redirectURI)) {
          resolve(locationURI.spec);
          window.removeEventListener("unload", unloadlistener);
          window.gBrowser.removeTabsProgressListener(wpl);
          window.close();
        }
      },
      onProgressChange() {},
      onStatusChange() {},
      onSecurityChange() {},
    };

    promiseDocumentLoaded(window.document).then(() => {
      window.gBrowser.addTabsProgressListener(wpl);
      window.addEventListener("unload", unloadlistener);
    });
  });
};

this.identity = class extends ExtensionAPI {
  getAPI(context) {
    return {
      identity: {
        launchWebAuthFlow: function(details) {
          // In OAuth2 the url should have a redirect_uri param, parse the url and grab it
          let url, redirectURI;
          try {
            url = new URL(details.url);
          } catch (e) {
            return Promise.reject({message: "details.url is invalid"});
          }
          try {
            redirectURI = new URL(url.searchParams.get("redirect_uri"));
            if (!redirectURI) {
              return Promise.reject({message: "redirect_uri is missing"});
            }
          } catch (e) {
            return Promise.reject({message: "redirect_uri is invalid"});
          }

          // If the request is automatically redirected the user has already
          // authorized and we do not want to show the window.
          return checkRedirected(details.url, redirectURI).catch((requestError) => {
            // requestError is zero or xhr.status
            if (requestError !== 0) {
              Cu.reportError(`browser.identity auth check failed with ${requestError}`);
              return Promise.reject({message: "Invalid request"});
            }
            if (!details.interactive) {
              return Promise.reject({message: `Requires user interaction`});
            }

            return openOAuthWindow(details, redirectURI);
          });
        },
      },
    };
  }
};
PK
!<-chrome/toolkit/content/extensions/ext-idle.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://gre/modules/EventEmitter.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "idleService",
                                   "@mozilla.org/widget/idleservice;1",
                                   "nsIIdleService");

// WeakMap[Extension -> Object]
let observersMap = new WeakMap();

const getIdleObserverInfo = (extension, context) => {
  let observerInfo = observersMap.get(extension);
  if (!observerInfo) {
    observerInfo = {
      observer: null,
      detectionInterval: 60,
    };
    observersMap.set(extension, observerInfo);
    context.callOnClose({
      close: () => {
        let {observer, detectionInterval} = observersMap.get(extension);
        if (observer) {
          idleService.removeIdleObserver(observer, detectionInterval);
        }
        observersMap.delete(extension);
      },
    });
  }
  return observerInfo;
};

const getIdleObserver = (extension, context) => {
  let observerInfo = getIdleObserverInfo(extension, context);
  let {observer, detectionInterval} = observerInfo;
  if (!observer) {
    observer = {
      observe: function(subject, topic, data) {
        if (topic == "idle" || topic == "active") {
          this.emit("stateChanged", topic);
        }
      },
    };
    EventEmitter.decorate(observer);
    idleService.addIdleObserver(observer, detectionInterval);
    observerInfo.observer = observer;
    observerInfo.detectionInterval = detectionInterval;
  }
  return observer;
};

const setDetectionInterval = (extension, context, newInterval) => {
  let observerInfo = getIdleObserverInfo(extension, context);
  let {observer, detectionInterval} = observerInfo;
  if (observer) {
    idleService.removeIdleObserver(observer, detectionInterval);
    idleService.addIdleObserver(observer, newInterval);
  }
  observerInfo.detectionInterval = newInterval;
};

this.idle = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      idle: {
        queryState: function(detectionIntervalInSeconds) {
          if (idleService.idleTime < detectionIntervalInSeconds * 1000) {
            return Promise.resolve("active");
          }
          return Promise.resolve("idle");
        },
        setDetectionInterval: function(detectionIntervalInSeconds) {
          setDetectionInterval(extension, context, detectionIntervalInSeconds);
        },
        onStateChanged: new EventManager(context, "idle.onStateChanged", fire => {
          let listener = (event, data) => {
            fire.sync(data);
          };

          getIdleObserver(extension, context).on("stateChanged", listener);
          return () => {
            getIdleObserver(extension, context).off("stateChanged", listener);
          };
        }).api(),
      },
    };
  }
};
PK
!<
I!!3chrome/toolkit/content/extensions/ext-management.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
  const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  return stringSvc.createBundle("chrome://global/locale/extensions.properties");
});
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "promptService",
                                   "@mozilla.org/embedcomp/prompt-service;1",
                                   "nsIPromptService");
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://devtools/shared/event-emitter.js");

XPCOMUtils.defineLazyGetter(this, "GlobalManager", () => {
  const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
  return GlobalManager;
});

var {
  ExtensionError,
} = ExtensionUtils;

const _ = (key, ...args) => {
  if (args.length) {
    return strBundle.formatStringFromName(key, args, args.length);
  }
  return strBundle.GetStringFromName(key);
};

const installType = addon => {
  if (addon.temporarilyInstalled) {
    return "development";
  } else if (addon.foreignInstall) {
    return "sideload";
  } else if (addon.isSystem) {
    return "other";
  }
  return "normal";
};

const getExtensionInfoForAddon = (extension, addon) => {
  let extInfo = {
    id: addon.id,
    name: addon.name,
    description: addon.description || "",
    version: addon.version,
    mayDisable: !!(addon.permissions & AddonManager.PERM_CAN_DISABLE),
    enabled: addon.isActive,
    optionsUrl: addon.optionsURL || "",
    installType: installType(addon),
    type: addon.type,
  };

  if (extension) {
    let m = extension.manifest;

    let hostPerms = extension.whiteListedHosts.patterns.map(matcher => matcher.pattern);

    extInfo.permissions = Array.from(extension.permissions).filter(perm => {
      return !hostPerms.includes(perm);
    });
    extInfo.hostPermissions = hostPerms;

    extInfo.shortName = m.short_name || "";
    if (m.icons) {
      extInfo.icons = Object.keys(m.icons).map(key => {
        return {size: Number(key), url: m.icons[key]};
      });
    }
  }

  if (!addon.isActive) {
    extInfo.disabledReason = "unknown";
  }
  if (addon.homepageURL) {
    extInfo.homepageUrl = addon.homepageURL;
  }
  if (addon.updateURL) {
    extInfo.updateUrl = addon.updateURL;
  }
  return extInfo;
};

const listenerMap = new WeakMap();
// Some management APIs are intentionally limited.
const allowedTypes = ["theme", "extension"];

class AddonListener {
  constructor() {
    AddonManager.addAddonListener(this);
    EventEmitter.decorate(this);
  }

  release() {
    AddonManager.removeAddonListener(this);
  }

  getExtensionInfo(addon) {
    let ext = addon.isWebExtension && GlobalManager.extensionMap.get(addon.id);
    return getExtensionInfoForAddon(ext, addon);
  }

  checkAllowed(addon) {
    return !addon.isSystem && allowedTypes.includes(addon.type);
  }

  onEnabled(addon) {
    if (!this.checkAllowed(addon)) {
      return;
    }
    this.emit("onEnabled", this.getExtensionInfo(addon));
  }

  onDisabled(addon) {
    if (!this.checkAllowed(addon)) {
      return;
    }
    this.emit("onDisabled", this.getExtensionInfo(addon));
  }

  onInstalled(addon) {
    if (!this.checkAllowed(addon)) {
      return;
    }
    this.emit("onInstalled", this.getExtensionInfo(addon));
  }

  onUninstalled(addon) {
    if (!this.checkAllowed(addon)) {
      return;
    }
    this.emit("onUninstalled", this.getExtensionInfo(addon));
  }
}

let addonListener;

const getManagementListener = (extension, context) => {
  if (!listenerMap.has(extension)) {
    if (!addonListener) {
      addonListener = new AddonListener();
    }
    listenerMap.set(extension, {});
    context.callOnClose({
      close: () => {
        listenerMap.delete(extension);
        if (listenerMap.length === 0) {
          addonListener.release();
          addonListener = null;
        }
      },
    });
  }
  return addonListener;
};

this.management = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      management: {
        async get(id) {
          let addon = await AddonManager.getAddonByID(id);
          if (!addon.isSystem) {
            return getExtensionInfoForAddon(extension, addon);
          }
        },

        async getAll() {
          let addons = await AddonManager.getAddonsByTypes(allowedTypes);
          return addons.filter(addon => !addon.isSystem).map(addon => {
            // If the extension is enabled get it and use it for more data.
            let ext = addon.isWebExtension && GlobalManager.extensionMap.get(addon.id);
            return getExtensionInfoForAddon(ext, addon);
          });
        },

        async getSelf() {
          let addon = await AddonManager.getAddonByID(extension.id);
          return getExtensionInfoForAddon(extension, addon);
        },

        async uninstallSelf(options) {
          if (options && options.showConfirmDialog) {
            let message = _("uninstall.confirmation.message", extension.name);
            if (options.dialogMessage) {
              message = `${options.dialogMessage}\n${message}`;
            }
            let title = _("uninstall.confirmation.title", extension.name);
            let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING +
                              promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING;
            let button0Title = _("uninstall.confirmation.button-0.label");
            let button1Title = _("uninstall.confirmation.button-1.label");
            let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0});
            if (response == 1) {
              throw new ExtensionError("User cancelled uninstall of extension");
            }
          }
          let addon = await AddonManager.getAddonByID(extension.id);
          let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
          if (!canUninstall) {
            throw new ExtensionError("The add-on cannot be uninstalled");
          }
          addon.uninstall();
        },

        async setEnabled(id, enabled) {
          let addon = await AddonManager.getAddonByID(id);
          if (!addon) {
            throw new ExtensionError(`No such addon ${id}`);
          }
          if (addon.type !== "theme") {
            throw new ExtensionError("setEnabled applies only to theme addons");
          }
          if (addon.isSystem) {
            throw new ExtensionError("setEnabled cannot be used with a system addon");
          }
          addon.userDisabled = !enabled;
        },

        onDisabled: new EventManager(context, "management.onDisabled", fire => {
          let listener = (event, data) => {
            fire.async(data);
          };

          getManagementListener(extension, context).on("onDisabled", listener);
          return () => {
            getManagementListener(extension, context).off("onDisabled", listener);
          };
        }).api(),

        onEnabled: new EventManager(context, "management.onEnabled", fire => {
          let listener = (event, data) => {
            fire.async(data);
          };

          getManagementListener(extension, context).on("onEnabled", listener);
          return () => {
            getManagementListener(extension, context).off("onEnabled", listener);
          };
        }).api(),

        onInstalled: new EventManager(context, "management.onInstalled", fire => {
          let listener = (event, data) => {
            fire.async(data);
          };

          getManagementListener(extension, context).on("onInstalled", listener);
          return () => {
            getManagementListener(extension, context).off("onInstalled", listener);
          };
        }).api(),

        onUninstalled: new EventManager(context, "management.onUninstalled", fire => {
          let listener = (event, data) => {
            fire.async(data);
          };

          getManagementListener(extension, context).on("onUninstalled", listener);
          return () => {
            getManagementListener(extension, context).off("onUninstalled", listener);
          };
        }).api(),

      },
    };
  }
};
PK
!<hRx6chrome/toolkit/content/extensions/ext-notifications.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                  "resource://gre/modules/EventEmitter.jsm");

var {
  ignoreEvent,
} = ExtensionCommon;

// WeakMap[Extension -> Map[id -> Notification]]
let notificationsMap = new WeakMap();

// Manages a notification popup (notifications API) created by the extension.
function Notification(extension, id, options) {
  this.extension = extension;
  this.id = id;
  this.options = options;

  let imageURL;
  if (options.iconUrl) {
    imageURL = this.extension.baseURI.resolve(options.iconUrl);
  }

  try {
    let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
    svc.showAlertNotification(imageURL,
                              options.title,
                              options.message,
                              true, // textClickable
                              this.id,
                              this,
                              this.id);
  } catch (e) {
    // This will fail if alerts aren't available on the system.
  }
}

Notification.prototype = {
  clear() {
    try {
      let svc = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
      svc.closeAlert(this.id);
    } catch (e) {
      // This will fail if the OS doesn't support this function.
    }
    notificationsMap.get(this.extension).delete(this.id);
  },

  observe(subject, topic, data) {
    let notifications = notificationsMap.get(this.extension);

    let emitAndDelete = event => {
      notifications.emit(event, data);
      notifications.delete(this.id);
    };

    // Don't try to emit events if the extension has been unloaded
    if (!notifications) {
      return;
    }

    switch (topic) {
      case "alertclickcallback":
        emitAndDelete("clicked");
        break;
      case "alertfinished":
        emitAndDelete("closed");
        break;
      case "alertshow":
        notifications.emit("shown", data);
        break;
    }
  },
};

this.notifications = class extends ExtensionAPI {
  constructor(extension) {
    super(extension);

    this.nextId = 0;
  }

  onShutdown() {
    let {extension} = this;

    if (notificationsMap.has(extension)) {
      for (let notification of notificationsMap.get(extension).values()) {
        notification.clear();
      }
      notificationsMap.delete(extension);
    }
  }

  getAPI(context) {
    let {extension} = context;

    let map = new Map();
    EventEmitter.decorate(map);
    notificationsMap.set(extension, map);

    return {
      notifications: {
        create: (notificationId, options) => {
          if (!notificationId) {
            notificationId = String(this.nextId++);
          }

          let notifications = notificationsMap.get(extension);
          if (notifications.has(notificationId)) {
            notifications.get(notificationId).clear();
          }

          // FIXME: Lots of options still aren't supported, especially
          // buttons.
          let notification = new Notification(extension, notificationId, options);
          notificationsMap.get(extension).set(notificationId, notification);

          return Promise.resolve(notificationId);
        },

        clear: function(notificationId) {
          let notifications = notificationsMap.get(extension);
          if (notifications.has(notificationId)) {
            notifications.get(notificationId).clear();
            return Promise.resolve(true);
          }
          return Promise.resolve(false);
        },

        getAll: function() {
          let result = {};
          notificationsMap.get(extension).forEach((value, key) => {
            result[key] = value.options;
          });
          return Promise.resolve(result);
        },

        onClosed: new EventManager(context, "notifications.onClosed", fire => {
          let listener = (event, notificationId) => {
            // FIXME: Support the byUser argument.
            fire.async(notificationId, true);
          };

          notificationsMap.get(extension).on("closed", listener);
          return () => {
            notificationsMap.get(extension).off("closed", listener);
          };
        }).api(),

        onClicked: new EventManager(context, "notifications.onClicked", fire => {
          let listener = (event, notificationId) => {
            fire.async(notificationId, true);
          };

          notificationsMap.get(extension).on("clicked", listener);
          return () => {
            notificationsMap.get(extension).off("clicked", listener);
          };
        }).api(),

        onShown: new EventManager(context, "notifications.onShown", fire => {
          let listener = (event, notificationId) => {
            fire.async(notificationId, true);
          };

          notificationsMap.get(extension).on("shown", listener);
          return () => {
            notificationsMap.get(extension).off("shown", listener);
          };
        }).api(),

        // Intend to implement this later: https://bugzilla.mozilla.org/show_bug.cgi?id=1190681
        onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"),
      },
    };
  }
};
PK
!<0

4chrome/toolkit/content/extensions/ext-permissions.js"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
                                  "resource://gre/modules/ExtensionPermissions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");

var {
  ExtensionError,
} = ExtensionUtils;

XPCOMUtils.defineLazyPreferenceGetter(this, "promptsEnabled",
                                      "extensions.webextOptionalPermissionPrompts");

this.permissions = class extends ExtensionAPI {
  getAPI(context) {
    return {
      permissions: {
        async request(perms) {
          let {permissions, origins} = perms;

          let manifestPermissions = context.extension.manifest.optional_permissions;
          for (let perm of permissions) {
            if (!manifestPermissions.includes(perm)) {
              throw new ExtensionError(`Cannot request permission ${perm} since it was not declared in optional_permissions`);
            }
          }

          let optionalOrigins = context.extension.optionalOrigins;
          for (let origin of origins) {
            if (!optionalOrigins.subsumes(new MatchPattern(origin))) {
              throw new ExtensionError(`Cannot request origin permission for ${origin} since it was not declared in optional_permissions`);
            }
          }

          if (promptsEnabled) {
            permissions = permissions.filter(perm => !context.extension.hasPermission(perm));
            origins = origins.filter(origin => !context.extension.whiteListedHosts.subsumes(new MatchPattern(origin)));

            if (permissions.length == 0 && origins.length == 0) {
              return true;
            }

            let browser = context.pendingEventBrowser || context.xulBrowser;
            let allow = await new Promise(resolve => {
              let subject = {
                wrappedJSObject: {
                  browser,
                  name: context.extension.name,
                  icon: context.extension.iconURL,
                  permissions: {permissions, origins},
                  resolve,
                },
              };
              Services.obs.notifyObservers(subject, "webextension-optional-permission-prompt");
            });
            if (!allow) {
              return false;
            }
          }

          await ExtensionPermissions.add(context.extension, perms);
          return true;
        },

        async getAll() {
          let perms = context.extension.userPermissions;
          delete perms.apis;
          return perms;
        },

        async contains(permissions) {
          for (let perm of permissions.permissions) {
            if (!context.extension.hasPermission(perm)) {
              return false;
            }
          }

          for (let origin of permissions.origins) {
            if (!context.extension.whiteListedHosts.subsumes(new MatchPattern(origin))) {
              return false;
            }
          }

          return true;
        },

        async remove(permissions) {
          await ExtensionPermissions.remove(context.extension, permissions);
          return true;
        },
      },
    };
  }
};

/* eslint-disable mozilla/balanced-listeners */
extensions.on("uninstall", extension => {
  ExtensionPermissions.removeAll(extension);
});
PK
!<YKK0chrome/toolkit/content/extensions/ext-privacy.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");

Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
var {
  ExtensionError,
} = ExtensionUtils;

const checkScope = scope => {
  if (scope && scope !== "regular") {
    throw new ExtensionError(
      `Firefox does not support the ${scope} settings scope.`);
  }
};

const getPrivacyAPI = (extension, name, callback) => {
  return {
    async get(details) {
      return {
        levelOfControl: details.incognito ?
          "not_controllable" :
          await ExtensionPreferencesManager.getLevelOfControl(
            extension, name),
        value: await callback(),
      };
    },
    set(details) {
      checkScope(details.scope);
      return ExtensionPreferencesManager.setSetting(
        extension, name, details.value);
    },
    clear(details) {
      checkScope(details.scope);
      return ExtensionPreferencesManager.removeSetting(
        extension, name);
    },
  };
};

// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
  prefNames: [
    "network.predictor.enabled",
    "network.prefetch-next",
    "network.http.speculative-parallel-limit",
    "network.dns.disablePrefetch",
  ],

  setCallback(value) {
    return {
      "network.http.speculative-parallel-limit": value ? undefined : 0,
      "network.dns.disablePrefetch": !value,
      "network.predictor.enabled": value,
      "network.prefetch-next": value,
    };
  },
});

ExtensionPreferencesManager.addSetting("network.peerConnectionEnabled", {
  prefNames: [
    "media.peerconnection.enabled",
  ],

  setCallback(value) {
    return {[this.prefNames[0]]: value};
  },
});

ExtensionPreferencesManager.addSetting("network.webRTCIPHandlingPolicy", {
  prefNames: [
    "media.peerconnection.ice.default_address_only",
    "media.peerconnection.ice.no_host",
    "media.peerconnection.ice.proxy_only",
  ],

  setCallback(value) {
    let prefs = {};
    // Start with all prefs being reset.
    for (let pref of this.prefNames) {
      prefs[pref] = undefined;
    }
    switch (value) {
      case "default":
        // All prefs are already set to be reset.
        break;

      case "default_public_and_private_interfaces":
        prefs["media.peerconnection.ice.default_address_only"] = true;
        break;

      case "default_public_interface_only":
        prefs["media.peerconnection.ice.default_address_only"] = true;
        prefs["media.peerconnection.ice.no_host"] = true;
        break;

      case "disable_non_proxied_udp":
        prefs["media.peerconnection.ice.proxy_only"] = true;
        break;
    }
    return prefs;
  },
});

ExtensionPreferencesManager.addSetting("services.passwordSavingEnabled", {
  prefNames: [
    "signon.rememberSignons",
  ],

  setCallback(value) {
    return {[this.prefNames[0]]: value};
  },
});

ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", {
  prefNames: [
    "browser.send_pings",
  ],

  setCallback(value) {
    return {[this.prefNames[0]]: value};
  },
});

ExtensionPreferencesManager.addSetting("websites.referrersEnabled", {
  prefNames: [
    "network.http.sendRefererHeader",
  ],

  // Values for network.http.sendRefererHeader:
  // 0=don't send any, 1=send only on clicks, 2=send on image requests as well
  // http://searchfox.org/mozilla-central/rev/61054508641ee76f9c49bcf7303ef3cfb6b410d2/modules/libpref/init/all.js#1585
  setCallback(value) {
    return {[this.prefNames[0]]: value ? 2 : 0};
  },
});

this.privacy = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      privacy: {
        network: {
          networkPredictionEnabled: getPrivacyAPI(extension,
            "network.networkPredictionEnabled",
            () => {
              return Preferences.get("network.predictor.enabled") &&
                Preferences.get("network.prefetch-next") &&
                Preferences.get("network.http.speculative-parallel-limit") > 0 &&
                !Preferences.get("network.dns.disablePrefetch");
            }),
          peerConnectionEnabled: getPrivacyAPI(extension,
            "network.peerConnectionEnabled",
            () => {
              return Preferences.get("media.peerconnection.enabled");
            }),
          webRTCIPHandlingPolicy: getPrivacyAPI(extension,
            "network.webRTCIPHandlingPolicy",
            () => {
              if (Preferences.get("media.peerconnection.ice.proxy_only")) {
                return "disable_non_proxied_udp";
              }

              let default_address_only =
                Preferences.get("media.peerconnection.ice.default_address_only");
              if (default_address_only) {
                if (Preferences.get("media.peerconnection.ice.no_host")) {
                  return "default_public_interface_only";
                }
                return "default_public_and_private_interfaces";
              }

              return "default";
            }),
        },

        services: {
          passwordSavingEnabled: getPrivacyAPI(extension,
            "services.passwordSavingEnabled",
            () => {
              return Preferences.get("signon.rememberSignons");
            }),
        },

        websites: {
          hyperlinkAuditingEnabled: getPrivacyAPI(extension,
            "websites.hyperlinkAuditingEnabled",
            () => {
              return Preferences.get("browser.send_pings");
            }),
          referrersEnabled: getPrivacyAPI(extension,
            "websites.referrersEnabled",
            () => {
              return Preferences.get("network.http.sendRefererHeader") !== 0;
            }),
        },
      },
    };
  }
};
PK
!<LZ
Z
9chrome/toolkit/content/extensions/ext-protocolHandlers.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

XPCOMUtils.defineLazyServiceGetter(this, "handlerService",
                                   "@mozilla.org/uriloader/handler-service;1",
                                   "nsIHandlerService");
XPCOMUtils.defineLazyServiceGetter(this, "protocolService",
                                   "@mozilla.org/uriloader/external-protocol-service;1",
                                   "nsIExternalProtocolService");
Cu.importGlobalProperties(["URL"]);

const hasHandlerApp = handlerConfig => {
  let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
  let appHandlers = protoInfo.possibleApplicationHandlers;
  for (let i = 0; i < appHandlers.length; i++) {
    let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
    if (handler instanceof Ci.nsIWebHandlerApp &&
        handler.uriTemplate === handlerConfig.uriTemplate) {
      return true;
    }
  }
  return false;
};

this.protocolHandlers = class extends ExtensionAPI {
  onManifestEntry(entryName) {
    let {extension} = this;
    let {manifest} = extension;

    for (let handlerConfig of manifest.protocol_handlers) {
      if (hasHandlerApp(handlerConfig)) {
        continue;
      }

      let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]
                      .createInstance(Ci.nsIWebHandlerApp);
      handler.name = handlerConfig.name;
      handler.uriTemplate = handlerConfig.uriTemplate;

      let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
      protoInfo.possibleApplicationHandlers.appendElement(handler);
      handlerService.store(protoInfo);
    }
  }

  onShutdown(shutdownReason) {
    let {extension} = this;
    let {manifest} = extension;

    if (shutdownReason === "APP_SHUTDOWN") {
      return;
    }

    for (let handlerConfig of manifest.protocol_handlers) {
      let protoInfo = protocolService.getProtocolHandlerInfo(handlerConfig.protocol);
      let appHandlers = protoInfo.possibleApplicationHandlers;
      for (let i = 0; i < appHandlers.length; i++) {
        let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
        if (handler instanceof Ci.nsIWebHandlerApp &&
            handler.uriTemplate === handlerConfig.uriTemplate) {
          appHandlers.removeElementAt(i);
          if (protoInfo.preferredApplicationHandler === handler) {
            protoInfo.preferredApplicationHandler = null;
            protoInfo.alwaysAskBeforeHandling = true;
          }
          handlerService.store(protoInfo);
          break;
        }
      }
    }
  }
};
PK
!<u.chrome/toolkit/content/extensions/ext-proxy.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "ProxyScriptContext",
                                  "resource://gre/modules/ProxyScriptContext.jsm");

// WeakMap[Extension -> ProxyScriptContext]
let proxyScriptContextMap = new WeakMap();

this.proxy = class extends ExtensionAPI {
  onShutdown() {
    let {extension} = this;

    let proxyScriptContext = proxyScriptContextMap.get(extension);
    if (proxyScriptContext) {
      proxyScriptContext.unload();
      proxyScriptContextMap.delete(extension);
    }
  }

  getAPI(context) {
    let {extension} = context;
    return {
      proxy: {
        register(url) {
          this.unregister();

          let proxyScriptContext = new ProxyScriptContext(extension, url);
          if (proxyScriptContext.load()) {
            proxyScriptContextMap.set(extension, proxyScriptContext);
          }
        },

        unregister() {
          // Unload the current proxy script if one is loaded.
          if (proxyScriptContextMap.has(extension)) {
            proxyScriptContextMap.get(extension).unload();
            proxyScriptContextMap.delete(extension);
          }
        },

        registerProxyScript(url) {
          this.register(url);
        },

        onProxyError: new EventManager(context, "proxy.onProxyError", fire => {
          let listener = (name, error) => {
            fire.async(error);
          };
          extension.on("proxy-error", listener);
          return () => {
            extension.off("proxy-error", listener);
          };
        }).api(),
      },
    };
  }
};
PK
!<찟0chrome/toolkit/content/extensions/ext-runtime.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                  "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                  "resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                  "resource://gre/modules/ExtensionParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");

this.runtime = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      runtime: {
        onStartup: new EventManager(context, "runtime.onStartup", fire => {
          if (context.incognito) {
            // This event should not fire if we are operating in a private profile.
            return () => {};
          }
          let listener = () => {
            if (extension.startupReason === "APP_STARTUP") {
              fire.sync();
            }
          };
          extension.on("startup", listener);
          return () => {
            extension.off("startup", listener);
          };
        }).api(),

        onInstalled: new EventManager(context, "runtime.onInstalled", fire => {
          let temporary = !!extension.addonData.temporarilyInstalled;

          let listener = () => {
            switch (extension.startupReason) {
              case "APP_STARTUP":
                if (AddonManagerPrivate.browserUpdated) {
                  fire.sync({reason: "browser_update", temporary});
                }
                break;
              case "ADDON_INSTALL":
                fire.sync({reason: "install", temporary});
                break;
              case "ADDON_UPGRADE":
                fire.sync({
                  reason: "update",
                  previousVersion: extension.addonData.oldVersion,
                  temporary,
                });
                break;
            }
          };
          extension.on("startup", listener);
          return () => {
            extension.off("startup", listener);
          };
        }).api(),

        onUpdateAvailable: new EventManager(context, "runtime.onUpdateAvailable", fire => {
          let instanceID = extension.addonData.instanceID;
          AddonManager.addUpgradeListener(instanceID, upgrade => {
            extension.upgrade = upgrade;
            let details = {
              version: upgrade.version,
            };
            fire.sync(details);
          });
          return () => {
            AddonManager.removeUpgradeListener(instanceID).catch(e => {
              // This can happen if we try this after shutdown is complete.
            });
          };
        }).api(),

        reload: () => {
          if (extension.upgrade) {
            // If there is a pending update, install it now.
            extension.upgrade.install();
          } else {
            // Otherwise, reload the current extension.
            AddonManager.getAddonByID(extension.id, addon => {
              addon.reload();
            });
          }
        },

        get lastError() {
          // TODO(robwu): Figure out how to make sure that errors in the parent
          // process are propagated to the child process.
          // lastError should not be accessed from the parent.
          return context.lastError;
        },

        getBrowserInfo: function() {
          const {name, vendor, version, appBuildID} = Services.appinfo;
          const info = {name, vendor, version, buildID: appBuildID};
          return Promise.resolve(info);
        },

        getPlatformInfo: function() {
          return Promise.resolve(ExtensionParent.PlatformInfo);
        },

        openOptionsPage: function() {
          if (!extension.manifest.options_ui) {
            return Promise.reject({message: "No `options_ui` declared"});
          }

          // This expects openOptionsPage to be defined in the file using this,
          // e.g. the browser/ version of ext-runtime.js
          /* global openOptionsPage:false */
          return openOptionsPage(extension).then(() => {});
        },

        setUninstallURL: function(url) {
          if (url.length == 0) {
            return Promise.resolve();
          }

          let uri;
          try {
            uri = NetUtil.newURI(url);
          } catch (e) {
            return Promise.reject({message: `Invalid URL: ${JSON.stringify(url)}`});
          }

          if (uri.scheme != "http" && uri.scheme != "https") {
            return Promise.reject({message: "url must have the scheme http or https"});
          }

          extension.uninstallURL = url;
          return Promise.resolve();
        },
      },
    };
  }
};
PK
!<˱||0chrome/toolkit/content/extensions/ext-storage.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                  "resource://gre/modules/ExtensionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "extensionStorageSync",
                                  "resource://gre/modules/ExtensionStorageSync.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                  "resource://gre/modules/AddonManager.jsm");

var {
  ExtensionError,
} = ExtensionUtils;

const enforceNoTemporaryAddon = extensionId => {
  const EXCEPTION_MESSAGE =
        "The storage API will not work with a temporary addon ID. " +
        "Please add an explicit addon ID to your manifest. " +
        "For more information see https://bugzil.la/1323228.";
  if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
    throw new ExtensionError(EXCEPTION_MESSAGE);
  }
};

this.storage = class extends ExtensionAPI {
  getAPI(context) {
    let {extension} = context;
    return {
      storage: {
        local: {
          get: function(spec) {
            return ExtensionStorage.get(extension.id, spec);
          },
          set: function(items) {
            return ExtensionStorage.set(extension.id, items);
          },
          remove: function(keys) {
            return ExtensionStorage.remove(extension.id, keys);
          },
          clear: function() {
            return ExtensionStorage.clear(extension.id);
          },
        },

        sync: {
          get: function(spec) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.get(extension, spec, context);
          },
          set: function(items) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.set(extension, items, context);
          },
          remove: function(keys) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.remove(extension, keys, context);
          },
          clear: function() {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.clear(extension, context);
          },
        },

        onChanged: new EventManager(context, "storage.onChanged", fire => {
          let listenerLocal = changes => {
            fire.raw(changes, "local");
          };
          let listenerSync = changes => {
            fire.async(changes, "sync");
          };

          ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
          extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
          return () => {
            ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
            extensionStorageSync.removeOnChangedListener(extension, listenerSync);
          };
        }).api(),
      },
    };
  }
};
PK
!<f2!!.chrome/toolkit/content/extensions/ext-theme.js"use strict";

Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                  "resource://gre/modules/LightweightThemeManager.jsm");

XPCOMUtils.defineLazyGetter(this, "gThemesEnabled", () => {
  return Preferences.get("extensions.webextensions.themes.enabled");
});

const ICONS = Preferences.get("extensions.webextensions.themes.icons.buttons", "").split(",");

/** Class representing a theme. */
class Theme {
  /**
   * Creates a theme instance.
   *
   * @param {string} baseURI The base URI of the extension, used to
   *   resolve relative filepaths.
   * @param {Object} logger  Reference to the (console) logger that will be used
   *   to show manifest warnings to the theme author.
   */
  constructor(baseURI, logger) {
    // A dictionary of light weight theme styles.
    this.lwtStyles = {
      icons: {},
    };
    this.baseURI = baseURI;
    this.logger = logger;
  }

  /**
   * Loads a theme by reading the properties from the extension's manifest.
   * This method will override any currently applied theme.
   *
   * @param {Object} details Theme part of the manifest. Supported
   *   properties can be found in the schema under ThemeType.
   */
  load(details) {
    if (details.colors) {
      this.loadColors(details.colors);
    }

    if (details.images) {
      this.loadImages(details.images);
    }

    if (details.icons) {
      this.loadIcons(details.icons);
    }

    if (details.properties) {
      this.loadProperties(details.properties);
    }

    // Lightweight themes require all properties to be defined.
    if (this.lwtStyles.headerURL &&
        this.lwtStyles.accentcolor &&
        this.lwtStyles.textcolor) {
      LightweightThemeManager.fallbackThemeData = this.lwtStyles;
      Services.obs.notifyObservers(null,
        "lightweight-theme-styling-update",
        JSON.stringify(this.lwtStyles));
    }
  }

  /**
   * Helper method for loading colors found in the extension's manifest.
   *
   * @param {Object} colors Dictionary mapping color properties to values.
   */
  loadColors(colors) {
    for (let color of Object.keys(colors)) {
      let val = colors[color];

      if (!val) {
        continue;
      }

      let cssColor = val;
      if (Array.isArray(val)) {
        cssColor = "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")";
      }

      switch (color) {
        case "accentcolor":
        case "frame":
          this.lwtStyles.accentcolor = cssColor;
          break;
        case "textcolor":
        case "tab_text":
          this.lwtStyles.textcolor = cssColor;
          break;
      }
    }
  }

  /**
   * Helper method for loading images found in the extension's manifest.
   *
   * @param {Object} images Dictionary mapping image properties to values.
   */
  loadImages(images) {
    for (let image of Object.keys(images)) {
      let val = images[image];

      if (!val) {
        continue;
      }

      switch (image) {
        case "additional_backgrounds": {
          let backgroundImages = val.map(img => this.baseURI.resolve(img));
          this.lwtStyles.additionalBackgrounds = backgroundImages;
          break;
        }
        case "headerURL":
        case "theme_frame": {
          let resolvedURL = this.baseURI.resolve(val);
          this.lwtStyles.headerURL = resolvedURL;
          break;
        }
      }
    }
  }

  /**
   * Helper method for loading icons found in the extension's manifest.
   *
   * @param {Object} icons Dictionary mapping icon properties to extension URLs.
   */
  loadIcons(icons) {
    if (!Preferences.get("extensions.webextensions.themes.icons.enabled")) {
      // Return early if icons are disabled.
      return;
    }

    for (let icon of Object.getOwnPropertyNames(icons)) {
      let val = icons[icon];
      // We also have to compare against the baseURI spec because
      // `val` might have been resolved already. Resolving "" against
      // the baseURI just produces that URI, so check for equality.
      if (!val || val == this.baseURI.spec || !ICONS.includes(icon)) {
        continue;
      }
      let variableName = `--${icon}-icon`;
      let resolvedURL = this.baseURI.resolve(val);
      this.lwtStyles.icons[variableName] = resolvedURL;
    }
  }

  /**
   * Helper method for preparing properties found in the extension's manifest.
   * Properties are commonly used to specify more advanced behavior of colors,
   * images or icons.
   *
   * @param {Object} properties Dictionary mapping properties to values.
   */
  loadProperties(properties) {
    let additionalBackgroundsCount = (this.lwtStyles.additionalBackgrounds &&
      this.lwtStyles.additionalBackgrounds.length) || 0;
    const assertValidAdditionalBackgrounds = (property, valueCount) => {
      if (!additionalBackgroundsCount) {
        this.logger.warn(`The '${property}' property takes effect only when one ` +
          `or more additional background images are specified using the 'additional_backgrounds' property.`);
        return false;
      }
      if (additionalBackgroundsCount !== valueCount) {
        this.logger.warn(`The amount of values specified for '${property}' ` +
          `(${valueCount}) is not equal to the amount of additional background ` +
          `images (${additionalBackgroundsCount}), which may lead to unexpected results.`);
      }
      return true;
    };

    for (let property of Object.getOwnPropertyNames(properties)) {
      let val = properties[property];

      if (!val) {
        continue;
      }

      switch (property) {
        case "additional_backgrounds_alignment": {
          if (!assertValidAdditionalBackgrounds(property, val.length)) {
            break;
          }

          let alignment = [];
          if (this.lwtStyles.headerURL) {
            alignment.push("right top");
          }
          this.lwtStyles.backgroundsAlignment = alignment.concat(val).join(",");
          break;
        }
        case "additional_backgrounds_tiling": {
          if (!assertValidAdditionalBackgrounds(property, val.length)) {
            break;
          }

          let tiling = [];
          if (this.lwtStyles.headerURL) {
            tiling.push("no-repeat");
          }
          for (let i = 0, l = this.lwtStyles.additionalBackgrounds.length; i < l; ++i) {
            tiling.push(val[i] || "no-repeat");
          }
          this.lwtStyles.backgroundsTiling = tiling.join(",");
          break;
        }
      }
    }
  }

  /**
   * Unloads the currently applied theme.
   */
  unload() {
    let lwtStyles = {
      headerURL: "",
      accentcolor: "",
      additionalBackgrounds: "",
      backgroundsAlignment: "",
      backgroundsTiling: "",
      textcolor: "",
      icons: {},
    };

    for (let icon of ICONS) {
      lwtStyles.icons[`--${icon}--icon`] = "";
    }
    LightweightThemeManager.fallbackThemeData = null;
    Services.obs.notifyObservers(null,
      "lightweight-theme-styling-update",
      JSON.stringify(lwtStyles));
  }
}

this.theme = class extends ExtensionAPI {
  onManifestEntry(entryName) {
    if (!gThemesEnabled) {
      // Return early if themes are disabled.
      return;
    }

    let {extension} = this;
    let {manifest} = extension;

    if (!gThemesEnabled) {
      // Return early if themes are disabled.
      return;
    }

    this.theme = new Theme(extension.baseURI, extension.logger);
    this.theme.load(manifest.theme);
  }

  onShutdown() {
    if (this.theme) {
      this.theme.unload();
    }
  }

  getAPI(context) {
    let {extension} = context;

    return {
      theme: {
        update: (details) => {
          if (!gThemesEnabled) {
            // Return early if themes are disabled.
            return;
          }

          if (!this.theme) {
            // WebExtensions using the Theme API will not have a theme defined
            // in the manifest. Therefore, we need to initialize the theme the
            // first time browser.theme.update is called.
            this.theme = new Theme(extension.baseURI, extension.logger);
          }

          this.theme.load(details);
        },
        reset: () => {
          if (!gThemesEnabled) {
            // Return early if themes are disabled.
            return;
          }

          if (!this.theme) {
            // If no theme has been initialized, nothing to do.
            return;
          }

          this.theme.unload();
        },
      },
    };
  }
};
PK
!<0,0chrome/toolkit/content/extensions/ext-toolkit.js"use strict";

// These are defined on "global" which is used for the same scopes as the other
// ext-*.js files.
/* exported getCookieStoreIdForTab, getCookieStoreIdForContainer,
            getContainerForCookieStoreId,
            isValidCookieStoreId, isContainerCookieStoreId,
            EventManager, InputEventManager */
/* global getCookieStoreIdForTab:false, getCookieStoreIdForContainer:false,
          getContainerForCookieStoreId: false,
          isValidCookieStoreId:false, isContainerCookieStoreId:false,
          isDefaultCookieStoreId: false, isPrivateCookieStoreId:false,
          EventManager: false, InputEventManager: false */

XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                  "resource://gre/modules/ContextualIdentityService.jsm");

Cu.import("resource://gre/modules/ExtensionCommon.jsm");

global.EventManager = ExtensionCommon.EventManager;
global.InputEventManager = class extends EventManager {
  constructor(...args) {
    super(...args);
    this.inputHandling = true;
  }
};

/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */

global.DEFAULT_STORE = "firefox-default";
global.PRIVATE_STORE = "firefox-private";
global.CONTAINER_STORE = "firefox-container-";

global.getCookieStoreIdForTab = function(data, tab) {
  if (data.incognito) {
    return PRIVATE_STORE;
  }

  if (tab.userContextId) {
    return getCookieStoreIdForContainer(tab.userContextId);
  }

  return DEFAULT_STORE;
};

global.isPrivateCookieStoreId = function(storeId) {
  return storeId == PRIVATE_STORE;
};

global.isDefaultCookieStoreId = function(storeId) {
  return storeId == DEFAULT_STORE;
};

global.isContainerCookieStoreId = function(storeId) {
  return storeId !== null && storeId.startsWith(CONTAINER_STORE);
};

global.getCookieStoreIdForContainer = function(containerId) {
  return CONTAINER_STORE + containerId;
};

global.getContainerForCookieStoreId = function(storeId) {
  if (!isContainerCookieStoreId(storeId)) {
    return null;
  }

  let containerId = storeId.substring(CONTAINER_STORE.length);
  if (ContextualIdentityService.getPublicIdentityFromId(containerId)) {
    return parseInt(containerId, 10);
  }

  return null;
};

global.isValidCookieStoreId = function(storeId) {
  return isDefaultCookieStoreId(storeId) ||
         isPrivateCookieStoreId(storeId) ||
         isContainerCookieStoreId(storeId);
};

extensions.registerModules({
  manifest: {
    schema: "chrome://extensions/content/schemas/extension_types.json",
    scopes: [],
  },
  alarms: {
    url: "chrome://extensions/content/ext-alarms.js",
    schema: "chrome://extensions/content/schemas/alarms.json",
    scopes: ["addon_parent"],
    paths: [
      ["alarms"],
    ],
  },
  backgroundPage: {
    url: "chrome://extensions/content/ext-backgroundPage.js",
    scopes: ["addon_parent"],
    manifest: ["background"],
  },
  browserSettings: {
    url: "chrome://extensions/content/ext-browserSettings.js",
    schema: "chrome://extensions/content/schemas/browser_settings.json",
    scopes: ["addon_parent"],
    paths: [
      ["browserSettings"],
    ],
  },
  contextualIdentities: {
    url: "chrome://extensions/content/ext-contextualIdentities.js",
    schema: "chrome://extensions/content/schemas/contextual_identities.json",
    scopes: ["addon_parent"],
    paths: [
      ["contextualIdentities"],
    ],
  },
  cookies: {
    url: "chrome://extensions/content/ext-cookies.js",
    schema: "chrome://extensions/content/schemas/cookies.json",
    scopes: ["addon_parent"],
    paths: [
      ["cookies"],
    ],
  },
  downloads: {
    url: "chrome://extensions/content/ext-downloads.js",
    schema: "chrome://extensions/content/schemas/downloads.json",
    scopes: ["addon_parent"],
    paths: [
      ["downloads"],
    ],
  },
  extension: {
    url: "chrome://extensions/content/ext-extension.js",
    schema: "chrome://extensions/content/schemas/extension.json",
    scopes: ["addon_parent"],
    paths: [
      ["extension"],
    ],
  },
  geolocation: {
    url: "chrome://extensions/content/ext-geolocation.js",
    events: ["startup"],
  },
  i18n: {
    url: "chrome://extensions/content/ext-i18n.js",
    schema: "chrome://extensions/content/schemas/i18n.json",
    scopes: ["addon_parent", "content_child", "devtools_child"],
    paths: [
      ["i18n"],
    ],
  },
  idle: {
    url: "chrome://extensions/content/ext-idle.js",
    schema: "chrome://extensions/content/schemas/idle.json",
    scopes: ["addon_parent"],
    paths: [
      ["idle"],
    ],
  },
  management: {
    url: "chrome://extensions/content/ext-management.js",
    schema: "chrome://extensions/content/schemas/management.json",
    scopes: ["addon_parent"],
    paths: [
      ["management"],
    ],
  },
  notifications: {
    url: "chrome://extensions/content/ext-notifications.js",
    schema: "chrome://extensions/content/schemas/notifications.json",
    scopes: ["addon_parent"],
    paths: [
      ["notifications"],
    ],
  },
  permissions: {
    url: "chrome://extensions/content/ext-permissions.js",
    schema: "chrome://extensions/content/schemas/permissions.json",
    scopes: ["addon_parent"],
    paths: [
      ["permissions"],
    ],
  },
  privacy: {
    url: "chrome://extensions/content/ext-privacy.js",
    schema: "chrome://extensions/content/schemas/privacy.json",
    scopes: ["addon_parent"],
    paths: [
      ["privacy"],
    ],
  },
  protocolHandlers: {
    url: "chrome://extensions/content/ext-protocolHandlers.js",
    schema: "chrome://extensions/content/schemas/extension_protocol_handlers.json",
    scopes: ["addon_parent"],
    manifest: ["protocol_handlers"],
  },
  proxy: {
    url: "chrome://extensions/content/ext-proxy.js",
    schema: "chrome://extensions/content/schemas/proxy.json",
    scopes: ["addon_parent"],
    paths: [
      ["proxy"],
    ],
  },
  runtime: {
    url: "chrome://extensions/content/ext-runtime.js",
    schema: "chrome://extensions/content/schemas/runtime.json",
    scopes: ["addon_parent", "content_parent", "devtools_parent"],
    paths: [
      ["runtime"],
    ],
  },
  storage: {
    url: "chrome://extensions/content/ext-storage.js",
    schema: "chrome://extensions/content/schemas/storage.json",
    scopes: ["addon_parent", "content_parent", "devtools_parent"],
    paths: [
      ["storage"],
    ],
  },
  test: {
    schema: "chrome://extensions/content/schemas/test.json",
    scopes: [],
  },
  theme: {
    url: "chrome://extensions/content/ext-theme.js",
    schema: "chrome://extensions/content/schemas/theme.json",
    scopes: ["addon_parent"],
    manifest: ["theme"],
    paths: [
      ["theme"],
    ],
  },
  topSites: {
    url: "chrome://extensions/content/ext-topSites.js",
    schema: "chrome://extensions/content/schemas/top_sites.json",
    scopes: ["addon_parent"],
    paths: [
      ["topSites"],
    ],
  },
  webNavigation: {
    url: "chrome://extensions/content/ext-webNavigation.js",
    schema: "chrome://extensions/content/schemas/web_navigation.json",
    scopes: ["addon_parent"],
    paths: [
      ["webNavigation"],
    ],
  },
  webRequest: {
    url: "chrome://extensions/content/ext-webRequest.js",
    schema: "chrome://extensions/content/schemas/web_request.json",
    scopes: ["addon_parent"],
    paths: [
      ["webRequest"],
    ],
  },
});

if (AppConstants.MOZ_BUILD_APP === "browser") {
  extensions.registerModules({
    identity: {
      url: "chrome://extensions/content/ext-identity.js",
      schema: "chrome://extensions/content/schemas/identity.json",
      scopes: ["addon_parent"],
      paths: [
        ["identity"],
      ],
    },
  });
}
PK
!<v(==1chrome/toolkit/content/extensions/ext-topSites.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                  "resource://gre/modules/NewTabUtils.jsm");

this.topSites = class extends ExtensionAPI {
  getAPI(context) {
    return {
      topSites: {
        get: function(options) {
          return new Promise(function(resolve) {
            NewTabUtils.links.populateCache(function() {
              let urls;

              if (options && options.providers && options.providers.length > 0) {
                let urlLists = options.providers.map(function(p) {
                  let provider = NewTabUtils[`${p}Provider`];
                  return provider ? NewTabUtils.getProviderLinks(provider).slice() : [];
                });
                urls = NewTabUtils.links.mergeLinkLists(urlLists);
              } else {
                urls = NewTabUtils.links.getLinks();
              }

              resolve(urls.filter(link => !!link)
                          .map(link => {
                            return {
                              url: link.url,
                              title: link.title,
                            };
                          }));
            }, false);
          });
        },
      },
    };
  }
};
PK
!<96chrome/toolkit/content/extensions/ext-webNavigation.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

// This file expectes tabTracker to be defined in the global scope (e.g.
// by ext-utils.js).
/* global tabTracker */

XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
                                  "resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
                                  "resource://gre/modules/WebNavigation.jsm");

const defaultTransitionTypes = {
  topFrame: "link",
  subFrame: "auto_subframe",
};

const frameTransitions = {
  anyFrame: {
    qualifiers: ["server_redirect", "client_redirect", "forward_back"],
  },
  topFrame: {
    types: ["reload", "form_submit"],
  },
};

const tabTransitions = {
  topFrame: {
    qualifiers: ["from_address_bar"],
    types: ["auto_bookmark", "typed", "keyword", "generated", "link"],
  },
  subFrame: {
    types: ["manual_subframe"],
  },
};

const isTopLevelFrame = ({frameId, parentFrameId}) => {
  return frameId == 0 && parentFrameId == -1;
};

const fillTransitionProperties = (eventName, src, dst) => {
  if (eventName == "onCommitted" ||
      eventName == "onHistoryStateUpdated" ||
      eventName == "onReferenceFragmentUpdated") {
    let frameTransitionData = src.frameTransitionData || {};
    let tabTransitionData = src.tabTransitionData || {};

    let transitionType, transitionQualifiers = [];

    // Fill transition properties for any frame.
    for (let qualifier of frameTransitions.anyFrame.qualifiers) {
      if (frameTransitionData[qualifier]) {
        transitionQualifiers.push(qualifier);
      }
    }

    if (isTopLevelFrame(dst)) {
      for (let type of frameTransitions.topFrame.types) {
        if (frameTransitionData[type]) {
          transitionType = type;
        }
      }

      for (let qualifier of tabTransitions.topFrame.qualifiers) {
        if (tabTransitionData[qualifier]) {
          transitionQualifiers.push(qualifier);
        }
      }

      for (let type of tabTransitions.topFrame.types) {
        if (tabTransitionData[type]) {
          transitionType = type;
        }
      }

      // If transitionType is not defined, defaults it to "link".
      if (!transitionType) {
        transitionType = defaultTransitionTypes.topFrame;
      }
    } else {
      // If it is sub-frame, transitionType defaults it to "auto_subframe",
      // "manual_subframe" is set only in case of a recent user interaction.
      transitionType = tabTransitionData.link ?
        "manual_subframe" : defaultTransitionTypes.subFrame;
    }

    // Fill the transition properties in the webNavigation event object.
    dst.transitionType = transitionType;
    dst.transitionQualifiers = transitionQualifiers;
  }
};

// Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) {
  let name = `webNavigation.${eventName}`;
  let register = (fire, urlFilters) => {
    // Don't create a MatchURLFilters instance if the listener does not include any filter.
    let filters = urlFilters ?
          new MatchURLFilters(urlFilters.url) : null;

    let listener = data => {
      if (!data.browser) {
        return;
      }

      let data2 = {
        url: data.url,
        timeStamp: Date.now(),
      };

      if (eventName == "onErrorOccurred") {
        data2.error = data.error;
      }

      if (data.frameId != undefined) {
        data2.frameId = data.frameId;
        data2.parentFrameId = data.parentFrameId;
      }

      if (data.sourceFrameId != undefined) {
        data2.sourceFrameId = data.sourceFrameId;
      }

      // Fills in tabId typically.
      Object.assign(data2, tabTracker.getBrowserData(data.browser));
      if (data2.tabId < 0) {
        return;
      }

      if (data.sourceTabBrowser) {
        data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
      }

      fillTransitionProperties(eventName, data, data2);

      fire.async(data2);
    };

    WebNavigation[eventName].addListener(listener, filters);
    return () => {
      WebNavigation[eventName].removeListener(listener);
    };
  };

  return EventManager.call(this, context, name, register);
}

WebNavigationEventManager.prototype = Object.create(EventManager.prototype);

const convertGetFrameResult = (tabId, data) => {
  return {
    errorOccurred: data.errorOccurred,
    url: data.url,
    tabId,
    frameId: data.frameId,
    parentFrameId: data.parentFrameId,
  };
};

this.webNavigation = class extends ExtensionAPI {
  getAPI(context) {
    let {tabManager} = context.extension;

    return {
      webNavigation: {
        onTabReplaced: new EventManager(context, "webNavigation.onTabReplaced", fire => {
          return () => {};
        }).api(),
        onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
        onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
        onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(),
        onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
        onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
        onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
        onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
        onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
        getAllFrames(details) {
          let tab = tabManager.get(details.tabId);

          let {innerWindowID, messageManager} = tab.browser;
          let recipient = {innerWindowID};

          return context.sendMessage(messageManager, "WebNavigation:GetAllFrames", {}, {recipient})
                        .then((results) => results.map(convertGetFrameResult.bind(null, details.tabId)));
        },
        getFrame(details) {
          let tab = tabManager.get(details.tabId);

          let recipient = {
            innerWindowID: tab.browser.innerWindowID,
          };

          let mm = tab.browser.messageManager;
          return context.sendMessage(mm, "WebNavigation:GetFrame", {options: details}, {recipient})
                        .then((result) => {
                          return result ?
                            convertGetFrameResult(details.tabId, result) :
                            Promise.reject({message: `No frame found with frameId: ${details.frameId}`});
                        });
        },
      },
    };
  }
};
PK
!<J3chrome/toolkit/content/extensions/ext-webRequest.js"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

// This file expectes tabTracker to be defined in the global scope (e.g.
// by ext-utils.js).
/* global tabTracker */

XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                  "resource://gre/modules/WebRequest.jsm");

// EventManager-like class specifically for WebRequest. Inherits from
// EventManager. Takes care of converting |details| parameter
// when invoking listeners.
function WebRequestEventManager(context, eventName) {
  let name = `webRequest.${eventName}`;
  let register = (fire, filter, info) => {
    let listener = data => {
      // Prevent listening in on requests originating from system principal to
      // prevent tinkering with OCSP, app and addon updates, etc.
      if (data.isSystemPrincipal) {
        return;
      }

      // Check hosts permissions for both the resource being requested,
      const hosts = context.extension.whiteListedHosts;
      if (!hosts.matches(Services.io.newURI(data.url))) {
        return;
      }
      // and the origin that is loading the resource.
      const origin = data.documentUrl;
      const own = origin && origin.startsWith(context.extension.getURL());
      if (origin && !own && !hosts.matches(Services.io.newURI(origin))) {
        return;
      }

      let browserData = {tabId: -1, windowId: -1};
      if (data.browser) {
        browserData = tabTracker.getBrowserData(data.browser);
      }
      if (filter.tabId != null && browserData.tabId != filter.tabId) {
        return;
      }
      if (filter.windowId != null && browserData.windowId != filter.windowId) {
        return;
      }

      let data2 = {
        requestId: data.requestId,
        url: data.url,
        originUrl: data.originUrl,
        documentUrl: data.documentUrl,
        method: data.method,
        tabId: browserData.tabId,
        type: data.type,
        timeStamp: Date.now(),
        frameId: data.windowId,
        parentFrameId: data.parentWindowId,
      };

      const maybeCached = ["onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred"];
      if (maybeCached.includes(eventName)) {
        data2.fromCache = !!data.fromCache;
      }

      if ("ip" in data) {
        data2.ip = data.ip;
      }

      let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
                      "requestBody", "scheme", "realm", "isProxy", "challenger"];
      for (let opt of optional) {
        if (opt in data) {
          data2[opt] = data[opt];
        }
      }

      return fire.sync(data2);
    };

    let filter2 = {};
    if (filter.urls) {
      let perms = new MatchPatternSet([...context.extension.whiteListedHosts.patterns,
                                       ...context.extension.optionalOrigins.patterns]);

      filter2.urls = new MatchPatternSet(filter.urls);

      if (!perms.overlapsAll(filter2.urls)) {
        Cu.reportError("The webRequest.addListener filter doesn't overlap with host permissions.");
      }
    }
    if (filter.types) {
      filter2.types = filter.types;
    }
    if (filter.tabId) {
      filter2.tabId = filter.tabId;
    }
    if (filter.windowId) {
      filter2.windowId = filter.windowId;
    }

    let info2 = [];
    if (info) {
      for (let desc of info) {
        if (desc == "blocking" && !context.extension.hasPermission("webRequestBlocking")) {
          Cu.reportError("Using webRequest.addListener with the blocking option " +
                         "requires the 'webRequestBlocking' permission.");
        } else {
          info2.push(desc);
        }
      }
    }

    WebRequest[eventName].addListener(listener, filter2, info2);
    return () => {
      WebRequest[eventName].removeListener(listener);
    };
  };

  return EventManager.call(this, context, name, register);
}

WebRequestEventManager.prototype = Object.create(EventManager.prototype);

this.webRequest = class extends ExtensionAPI {
  getAPI(context) {
    return {
      webRequest: {
        onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
        onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
        onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
        onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
        onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
        onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
        onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
        onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
        onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
        handlerBehaviorChanged: function() {
          // TODO: Flush all caches.
        },
      },
    };
  }
};
PK
!<{5chrome/toolkit/content/extensions/schemas/alarms.json// 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": "alarms",
    "permissions": ["alarms"],
    "types": [
      {
        "id": "Alarm",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name of this alarm."
          },
          "scheduledTime": {
            "type": "number",
            "description": "Time when the alarm is scheduled to fire, in milliseconds past the epoch."
          },
          "periodInMinutes": {
            "type": "number",
            "optional": true,
            "description": "When present, signals that the alarm triggers periodically after so many minutes."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "create",
        "type": "function",
        "description": "Creates an alarm. After the delay is expired, the onAlarm event is fired. If there is another alarm with the same name (or no name if none is specified), it will be cancelled and replaced by this alarm.",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "Optional name to identify this alarm. Defaults to the empty string."
          },
          {
            "type": "object",
            "name": "alarmInfo",
            "description": "Details about the alarm. The alarm first fires either at 'when' milliseconds past the epoch (if 'when' is provided), after 'delayInMinutes' minutes from the current time (if 'delayInMinutes' is provided instead), or after 'periodInMinutes' minutes from the current time (if only 'periodInMinutes' is provided). Users should never provide both 'when' and 'delayInMinutes'. If 'periodInMinutes' is provided, then the alarm recurs repeatedly after that many minutes.",
            "properties": {
              "when": {"type": "number", "optional": true,
                "description": "Time when the alarm is scheduled to first fire, in milliseconds past the epoch."},
              "delayInMinutes": {"type": "number", "optional": true,
                "description": "Number of minutes from the current time after which the alarm should first fire."},
              "periodInMinutes": {"type": "number", "optional": true,
                "description": "Number of minutes after which the alarm should recur repeatedly."}
            }
          }
        ]
      },
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves details about the specified alarm.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "The name of the alarm to get. Defaults to the empty string."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "alarm",
                "$ref": "Alarm",
                "optional": true
               }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Gets an array of all the alarms.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              { "name": "alarms", "type": "array", "items": { "$ref": "Alarm" } }
            ]
          }
        ]
      },
      {
        "name": "clear",
        "type": "function",
        "description": "Clears the alarm with the given name.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "The name of the alarm to clear. Defaults to the empty string."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              { "name": "wasCleared", "type": "boolean", "description": "Whether an alarm of the given name was found to clear." }
            ]
          }
        ]
      },
      {
        "name": "clearAll",
        "type": "function",
        "description": "Clears all alarms.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              { "name": "wasCleared", "type": "boolean", "description": "Whether any alarm was found to clear." }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onAlarm",
        "type": "function",
        "description": "Fired when an alarm has expired. Useful for transient background pages.",
        "parameters": [
          {
            "name": "name",
            "$ref": "Alarm",
            "description": "The alarm that has expired."
          }
        ]
      }
    ]
  }
]
PK
!<Y?chrome/toolkit/content/extensions/schemas/browser_settings.json// 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": [
            "browserSettings"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "browserSettings",
    "description": "Use the <code>browser.browserSettings</code> API to control global settings of the browser.",
    "permissions": ["browserSettings"],
    "properties": {
      "cacheEnabled": {
        "$ref": "types.Setting",
        "description": "Enables or disables the browser cache."
      }
    }
  }
]
PK
!<Dchrome/toolkit/content/extensions/schemas/contextual_identities.json/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "contextualIdentities"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "contextualIdentities",
    "description": "Use the <code>browser.contextualIdentities</code> API to query and modify contextual identity, also called as containers.",
    "permissions": ["contextualIdentities"],
    "types": [
      {
        "id": "ContextualIdentity",
        "type": "object",
        "description": "Represents information about a contextual identity.",
        "properties": {
          "name": {"type": "string", "description": "The name of the contextual identity."},
          "icon": {"type": "string", "description": "The icon of the contextual identity."},
          "color": {"type": "string", "description": "The color of the contextual identity."},
          "cookieStoreId": {"type": "string", "description": "The cookie store ID of the contextual identity."}
        }
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves information about a single contextual identity.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          }
        ]
      },
      {
        "name": "query",
        "type": "function",
        "description": "Retrieves all contextual identities",
        "async": true,
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to filter the contextual identities being retrieved.",
            "properties": {
              "name": {"type": "string", "optional": true, "description": "Filters the contextual identity by name."}
            }
          }
        ]
      },
      {
        "name": "create",
        "type": "function",
        "description": "Creates a contextual identity with the given data.",
        "async": true,
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details about the contextual identity being created.",
            "properties": {
              "name": {"type": "string", "optional": false, "description": "The name of the contextual identity." },
              "color": {"type": "string", "optional": false, "description": "The color of the contextual identity." },
              "icon": {"type": "string", "optional": false, "description": "The icon of the contextual identity." }
            }
          }
        ]
      },
      {
        "name": "update",
        "type": "function",
        "description": "Updates a contextual identity with the given data.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          },
          {
            "type": "object",
            "name": "details",
            "description": "Details about the contextual identity being created.",
            "properties": {
              "name": {"type": "string", "optional": true, "description": "The name of the contextual identity." },
              "color": {"type": "string", "optional": true, "description": "The color of the contextual identity." },
              "icon": {"type": "string", "optional": true, "description": "The icon of the contextual identity." }
            }
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "description": "Deletes a contetual identity by its cookie Store ID.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          }
        ]
      }
    ]
  }
]
PK
!<lx226chrome/toolkit/content/extensions/schemas/cookies.json// 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": "OptionalPermission",
        "choices": [{
          "type": "string",
          "enum": [
            "cookies"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "cookies",
    "description": "Use the <code>browser.cookies</code> API to query and modify cookies, and to be notified when they change.",
    "permissions": ["cookies"],
    "types": [
      {
        "id": "Cookie",
        "type": "object",
        "description": "Represents information about an HTTP cookie.",
        "properties": {
          "name": {"type": "string", "description": "The name of the cookie."},
          "value": {"type": "string", "description": "The value of the cookie."},
          "domain": {"type": "string", "description": "The domain of the cookie (e.g. \"www.google.com\", \"example.com\")."},
          "hostOnly": {"type": "boolean", "description": "True if the cookie is a host-only cookie (i.e. a request's host must exactly match the domain of the cookie)."},
          "path": {"type": "string", "description": "The path of the cookie."},
          "secure": {"type": "boolean", "description": "True if the cookie is marked as Secure (i.e. its scope is limited to secure channels, typically HTTPS)."},
          "httpOnly": {"type": "boolean", "description": "True if the cookie is marked as HttpOnly (i.e. the cookie is inaccessible to client-side scripts)."},
          "session": {"type": "boolean", "description": "True if the cookie is a session cookie, as opposed to a persistent cookie with an expiration date."},
          "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies."},
          "storeId": {"type": "string", "description": "The ID of the cookie store containing this cookie, as provided in getAllCookieStores()."}
        }
      },
      {
        "id": "CookieStore",
        "type": "object",
        "description": "Represents a cookie store in the browser. An incognito mode window, for instance, uses a separate cookie store from a non-incognito window.",
        "properties": {
          "id": {"type": "string", "description": "The unique identifier for the cookie store."},
          "tabIds": {"type": "array", "items": {"type": "integer"}, "description": "Identifiers of all the browser tabs that share this cookie store."},
          "incognito": {"type": "boolean", "description": "Indicates if this is an incognito cookie store"}
        }
      },
      {
        "id": "OnChangedCause",
        "type": "string",
        "enum": ["evicted", "expired", "explicit", "expired_overwrite", "overwrite"],
        "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to $(ref:cookies.remove), \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\".  If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\".  If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly."
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details to identify the cookie being retrieved.",
            "properties": {
              "url": {"type": "string", "description": "The URL with which the cookie to retrieve is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
              "name": {"type": "string", "description": "The name of the cookie to retrieve."},
              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to look for the cookie. By default, the current execution context's cookie store will be used."}
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie. This parameter is null if no such cookie was found."
              }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to filter the cookies being retrieved.",
            "properties": {
              "url": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those that would match the given URL."},
              "name": {"type": "string", "optional": true, "description": "Filters the cookies by name."},
              "domain": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."},
              "path": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose path exactly matches this string."},
              "secure": {"type": "boolean", "optional": true, "description": "Filters the cookies by their Secure property."},
              "session": {"type": "boolean", "optional": true, "description": "Filters out session vs. persistent cookies."},
              "storeId": {"type": "string", "optional": true, "description": "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used."}
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookies", "type": "array", "items": {"$ref": "Cookie"}, "description": "All the existing, unexpired cookies that match the given cookie info."
              }
            ]
          }
        ]
      },
      {
        "name": "set",
        "type": "function",
        "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details about the cookie being set.",
            "properties": {
              "url": {"type": "string", "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
              "name": {"type": "string", "optional": true, "description": "The name of the cookie. Empty by default if omitted."},
              "value": {"type": "string", "optional": true, "description": "The value of the cookie. Empty by default if omitted."},
              "domain": {"type": "string", "optional": true, "description": "The domain of the cookie. If omitted, the cookie becomes a host-only cookie."},
              "path": {"type": "string", "optional": true, "description": "The path of the cookie. Defaults to the path portion of the url parameter."},
              "secure": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as Secure. Defaults to false."},
              "httpOnly": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as HttpOnly. Defaults to false."},
              "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie."},
              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to set the cookie. By default, the cookie is set in the current execution context's cookie store."}
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie that's been set.  If setting failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set."
              }
            ]
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "description": "Deletes a cookie by name.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to identify the cookie to remove.",
            "properties": {
              "url": {"type": "string", "description": "The URL associated with the cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
              "name": {"type": "string", "description": "The name of the cookie to remove."},
              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store to look in for the cookie. If unspecified, the cookie is looked for by default in the current execution context's cookie store."}
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Contains details about the cookie that's been removed.  If removal failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set.",
                "optional": true,
                "properties": {
                  "url": {"type": "string", "description": "The URL associated with the cookie that's been removed."},
                  "name": {"type": "string", "description": "The name of the cookie that's been removed."},
                  "storeId": {"type": "string", "description": "The ID of the cookie store from which the cookie was removed."}
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAllCookieStores",
        "type": "function",
        "description": "Lists all existing cookie stores.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookieStores", "type": "array", "items": {"$ref": "CookieStore"}, "description": "All the existing cookie stores."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onChanged",
        "type": "function",
        "description": "Fired when a cookie is set or removed. As a special case, note that updating a cookie's properties is implemented as a two step process: the cookie to be updated is first removed entirely, generating a notification with \"cause\" of \"overwrite\" .  Afterwards, a new cookie is written with the updated values, generating a second notification with \"cause\" \"explicit\".",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "removed": {"type": "boolean", "description": "True if a cookie was removed."},
              "cookie": {"$ref": "Cookie", "description": "Information about the cookie that was set or removed."},
              "cause": {"$ref": "OnChangedCause", "description": "The underlying reason behind the cookie's change."}
            }
          }
        ]
      }
    ]
  }
]
PK
!<LD~mm8chrome/toolkit/content/extensions/schemas/downloads.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "downloads",
            "downloads.open",
            "downloads.shelf"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "downloads",
    "permissions": ["downloads"],
    "types": [
      {
        "id": "FilenameConflictAction",
        "type": "string",
        "enum": [
          "uniquify",
          "overwrite",
          "prompt"
        ]
      },
      {
        "id": "InterruptReason",
        "type": "string",
        "enum": [
          "FILE_FAILED",
          "FILE_ACCESS_DENIED",
          "FILE_NO_SPACE",
          "FILE_NAME_TOO_LONG",
          "FILE_TOO_LARGE",
          "FILE_VIRUS_INFECTED",
          "FILE_TRANSIENT_ERROR",
          "FILE_BLOCKED",
          "FILE_SECURITY_CHECK_FAILED",
          "FILE_TOO_SHORT",
          "NETWORK_FAILED",
          "NETWORK_TIMEOUT",
          "NETWORK_DISCONNECTED",
          "NETWORK_SERVER_DOWN",
          "NETWORK_INVALID_REQUEST",
          "SERVER_FAILED",
          "SERVER_NO_RANGE",
          "SERVER_BAD_CONTENT",
          "SERVER_UNAUTHORIZED",
          "SERVER_CERT_PROBLEM",
          "SERVER_FORBIDDEN",
          "USER_CANCELED",
          "USER_SHUTDOWN",
          "CRASH"
        ]
      },
      {
        "id": "DangerType",
        "type": "string",
        "enum": [
          "file",
          "url",
          "content",
          "uncommon",
          "host",
          "unwanted",
          "safe",
          "accepted"
        ],
        "description": "<dl><dt>file</dt><dd>The download's filename is suspicious.</dd><dt>url</dt><dd>The download's URL is known to be malicious.</dd><dt>content</dt><dd>The downloaded file is known to be malicious.</dd><dt>uncommon</dt><dd>The download's URL is not commonly downloaded and could be dangerous.</dd><dt>safe</dt><dd>The download presents no known danger to the user's computer.</dd></dl>These string constants will never change, however the set of DangerTypes may change."
      },
      {
        "id": "State",
        "type": "string",
        "enum": [
          "in_progress",
          "interrupted",
          "complete"
        ],
        "description": "<dl><dt>in_progress</dt><dd>The download is currently receiving data from the server.</dd><dt>interrupted</dt><dd>An error broke the connection with the file host.</dd><dt>complete</dt><dd>The download completed successfully.</dd></dl>These string constants will never change, however the set of States may change."
      },
      {
        "id": "DownloadItem",
        "type": "object",
        "properties": {
          "id": {
            "description": "An identifier that is persistent across browser sessions.",
            "type": "integer"
          },
          "url": {
            "description": "Absolute URL.",
            "type": "string"
          },
          "referrer": {
            "type": "string",
            "optional": true
          },
          "filename": {
            "description": "Absolute local path.",
            "type": "string"
          },
          "incognito": {
            "description": "False if this download is recorded in the history, true if it is not recorded.",
            "type": "boolean"
          },
          "danger": {
            "$ref": "DangerType",
            "description": "Indication of whether this download is thought to be safe or known to be suspicious."
          },
          "mime": {
            "description": "The file's MIME type.",
            "type": "string"
          },
          "startTime": {
            "description": "Number of milliseconds between the unix epoch and when this download began.",
            "type": "string"
          },
          "endTime": {
            "description": "Number of milliseconds between the unix epoch and when this download ended.",
            "optional": true,
            "type": "string"
          },
          "estimatedEndTime": {
            "type": "string",
            "optional": true
          },
          "state": {
            "$ref": "State",
            "description": "Indicates whether the download is progressing, interrupted, or complete."
          },
          "paused": {
            "description": "True if the download has stopped reading data from the host, but kept the connection open.",
            "type": "boolean"
          },
          "canResume": {
            "type": "boolean"
          },
          "error": {
            "description": "Number indicating why a download was interrupted.",
            "optional": true,
            "$ref": "InterruptReason"
          },
          "bytesReceived": {
            "description": "Number of bytes received so far from the host, without considering file compression.",
            "type": "number"
          },
          "totalBytes": {
            "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
            "type": "number"
          },
          "fileSize": {
            "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
            "type": "number"
          },
          "exists": {
            "type": "boolean"
          },
          "byExtensionId": {
            "type": "string",
            "optional": true
          },
          "byExtensionName": {
            "type": "string",
            "optional": true
          }
        }
      },
      {
        "id": "StringDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "string"
          },
          "previous": {
            "optional": true,
            "type": "string"
          }
        }
      },
      {
        "id": "DoubleDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "number"
          },
          "previous": {
            "optional": true,
            "type": "number"
          }
        }
      },
      {
        "id": "BooleanDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "boolean"
          },
          "previous": {
            "optional": true,
            "type": "boolean"
          }
        }
      },
      {
        "id": "DownloadTime",
        "description": "A time specified as a Date object, a number or string representing milliseconds since the epoch, or an ISO 8601 string",
        "choices": [
          {
            "type": "string",
            "pattern": "^[1-9]\\d*$"
          },
          {
            "$ref": "extensionTypes.Date"
          }
        ]
      },
      {
        "id": "DownloadQuery",
        "description": "Parameters that combine to specify a predicate that can be used to select a set of downloads.  Used for example in search() and erase()",
        "type": "object",
        "properties": {
          "query": {
            "description": "This array of search terms limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> or <code>url</code> contain all of the search terms that do not begin with a dash '-' and none of the search terms that do begin with a dash.",
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "startedBefore": {
            "description": "Limits results to downloads that started before the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "startedAfter": {
            "description": "Limits results to downloads that started after the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "endedBefore": {
            "description": "Limits results to downloads that ended before the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "endedAfter": {
            "description": "Limits results to downloads that ended after the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "totalBytesGreater": {
            "description": "Limits results to downloads whose totalBytes is greater than the given integer.",
            "optional": true,
            "type": "number"
          },
          "totalBytesLess": {
            "description": "Limits results to downloads whose totalBytes is less than the given integer.",
            "optional": true,
            "type": "number"
          },
          "filenameRegex": {
            "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> matches the given regular expression.",
            "optional": true,
            "type": "string"
          },
          "urlRegex": {
            "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>url</code> matches the given regular expression.",
            "optional": true,
            "type": "string"
          },
          "limit": {
            "description": "Setting this integer limits the number of results. Otherwise, all matching <a href='#type-DownloadItem'>DownloadItems</a> will be returned.",
            "optional": true,
            "type": "integer"
          },
          "orderBy": {
            "description": "Setting elements of this array to <a href='#type-DownloadItem'>DownloadItem</a> properties in order to sort the search results. For example, setting <code>orderBy='startTime'</code> sorts the <a href='#type-DownloadItem'>DownloadItems</a> by their start time in ascending order. To specify descending order, prefix <code>orderBy</code> with a hyphen: '-startTime'.",
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "id": {
            "type": "integer",
            "optional": true
          },
          "url": {
            "description": "Absolute URL.",
            "optional": true,
            "type": "string"
          },
          "filename": {
            "description": "Absolute local path.",
            "optional": true,
            "type": "string"
          },
          "danger": {
            "$ref": "DangerType",
            "description": "Indication of whether this download is thought to be safe or known to be suspicious.",
            "optional": true
          },
          "mime": {
            "description": "The file's MIME type.",
            "optional": true,
            "type": "string"
          },
          "startTime": {
            "optional": true,
            "type": "string"
          },
          "endTime": {
            "optional": true,
            "type": "string"
          },
          "state": {
            "$ref": "State",
            "description": "Indicates whether the download is progressing, interrupted, or complete.",
            "optional": true
          },
          "paused": {
            "description": "True if the download has stopped reading data from the host, but kept the connection open.",
            "optional": true,
            "type": "boolean"
          },
          "error": {
            "description": "Why a download was interrupted.",
            "optional": true,
            "$ref": "InterruptReason"
          },
          "bytesReceived": {
            "description": "Number of bytes received so far from the host, without considering file compression.",
            "optional": true,
            "type": "number"
          },
          "totalBytes": {
            "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
            "optional": true,
            "type": "number"
          },
          "fileSize": {
            "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
            "optional": true,
            "type": "number"
          },
          "exists": {
            "type": "boolean",
            "optional": true
          }
        }
      }
    ],
    "functions": [
      {
        "name": "download",
        "type": "function",
        "async": "callback",
        "description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both <code>filename</code> and <code>saveAs</code> are specified, then the Save As dialog will be displayed, pre-populated with the specified <code>filename</code>. If the download started successfully, <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s <code>downloadId</code>. If there was an error starting the download, then <code>callback</code> will be called with <code>downloadId=undefined</code> and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.",
        "parameters": [
          {
            "description": "What to download and how.",
            "name": "options",
            "type": "object",
            "properties": {
              "url": {
                "description": "The URL to download.",
                "type": "string",
                "format": "url"
              },
              "filename": {
                "description": "A file path relative to the Downloads directory to contain the downloaded file.",
                "optional": true,
                "type": "string"
              },
              "conflictAction": {
                "$ref": "FilenameConflictAction",
                "optional": true
              },
              "saveAs": {
                "description": "Use a file-chooser to allow the user to select a filename.",
                "optional": true,
                "type": "boolean"
              },
              "method": {
                "description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
                "enum": [
                  "GET",
                  "POST"
                ],
                "optional": true,
                "type": "string"
              },
              "headers": {
                "optional": true,
                "type": "array",
                "description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "description": "Name of the HTTP header.",
                      "type": "string"
                    },
                    "value": {
                      "description": "Value of the HTTP header.",
                      "type": "string"
                    }
                  }
                }
              },
              "body": {
                "description": "Post body.",
                "optional": true,
                "type": "string"
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "downloadId",
                "type": "integer"
              }
            ]
          }
        ]
      },
      {
        "name": "search",
        "type": "function",
        "async": "callback",
        "description": "Find <a href='#type-DownloadItem'>DownloadItems</a>. Set <code>query</code> to the empty object to get all <a href='#type-DownloadItem'>DownloadItems</a>. To get a specific <a href='#type-DownloadItem'>DownloadItem</a>, set only the <code>id</code> field.",
        "parameters": [
          {
            "name": "query",
            "$ref": "DownloadQuery"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "items": {
                  "$ref": "DownloadItem"
                },
                "name": "results",
                "type": "array"
              }
            ]
          }
        ]
      },
      {
        "name": "pause",
        "type": "function",
        "async": "callback",
        "description": "Pause the download. If the request was successful the download is in a paused state. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
        "parameters": [
          {
            "description": "The id of the download to pause.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "resume",
        "type": "function",
        "async": "callback",
        "description": "Resume a paused download. If the request was successful the download is in progress and unpaused. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
        "parameters": [
          {
            "description": "The id of the download to resume.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "cancel",
        "type": "function",
        "async": "callback",
        "description": "Cancel a download. When <code>callback</code> is run, the download is cancelled, completed, interrupted or doesn't exist anymore.",
        "parameters": [
          {
            "description": "The id of the download to cancel.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "getFileIcon",
        "type": "function",
        "async": "callback",
        "description": "Retrieve an icon for the specified download. For new downloads, file icons are available after the <a href='#event-onCreated'>onCreated</a> event has been received. The image returned by this function while a download is in progress may be different from the image returned after the download is complete. Icon retrieval is done by querying the underlying operating system or toolkit depending on the platform. The icon that is returned will therefore depend on a number of factors including state of the download, platform, registered file types and visual theme. If a file icon cannot be determined, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain an error message.",
        "parameters": [
          {
            "description": "The identifier for the download.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "options",
            "optional": true,
            "properties": {
              "size": {
                "description": "The size of the icon.  The returned icon will be square with dimensions size * size pixels.  The default size for the icon is 32x32 pixels.",
                "optional": true,
                "minimum": 1,
                "maximum": 127,
                "type": "integer"
              }
            },
            "type": "object"
          },
          {
            "name": "callback",
            "parameters": [
              {
                "name": "iconURL",
                "optional": true,
                "type": "string"
              }
            ],
            "type": "function"
          }
        ]
      },
      {
        "name": "open",
        "type": "function",
        "async": "callback",
        "requireUserInput": true,
        "description": "Open the downloaded file.",
        "permissions": ["downloads.open"],
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "show",
        "type": "function",
        "description": "Show the downloaded file in its folder in a file manager.",
        "async": "callback",
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "success",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "showDefaultFolder",
        "type": "function",
        "parameters": []
      },
      {
        "name": "erase",
        "type": "function",
        "async": "callback",
        "description": "Erase matching <a href='#type-DownloadItem'>DownloadItems</a> from history",
        "parameters": [
          {
            "name": "query",
            "$ref": "DownloadQuery"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "items": {
                  "type": "integer"
                },
                "name": "erasedIds",
                "type": "array"
              }
            ]
          }
        ]
      },
      {
        "name": "removeFile",
        "async": "callback",
        "type": "function",
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [ ]
          }
        ]
      },
      {
        "description": "Prompt the user to either accept or cancel a dangerous download. <code>acceptDanger()</code> does not automatically accept dangerous downloads.",
        "name": "acceptDanger",
        "unsupported": true,
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [ ]
          }
        ],
        "type": "function"
      },
      {
        "description": "Initiate dragging the file to another application.",
        "name": "drag",
        "unsupported": true,
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          }
        ],
        "type": "function"
      },
      {
        "name": "setShelfEnabled",
        "type": "function",
        "unsupported": true,
        "parameters": [
          {
            "name": "enabled",
            "type": "boolean"
          }
        ]
      }
    ],
    "events": [
      {
        "description": "This event fires with the <a href='#type-DownloadItem'>DownloadItem</a> object when a download begins.",
        "name": "onCreated",
        "parameters": [
          {
            "$ref": "DownloadItem",
            "name": "downloadItem"
          }
        ],
        "type": "function"
      },
      {
        "description": "Fires with the <code>downloadId</code> when a download is erased from history.",
        "name": "onErased",
        "parameters": [
          {
            "name": "downloadId",
            "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that was erased.",
            "type": "integer"
          }
        ],
        "type": "function"
      },
      {
        "name": "onChanged",
        "description": "When any of a <a href='#type-DownloadItem'>DownloadItem</a>'s properties except <code>bytesReceived</code> changes, this event fires with the <code>downloadId</code> and an object containing the properties that changed.",
        "parameters": [
          {
            "name": "downloadDelta",
            "type": "object",
            "properties": {
              "id": {
                "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that changed.",
                "type": "integer"
              },
              "url": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>url</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "filename": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>filename</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "danger": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>danger</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "mime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>mime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "startTime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>startTime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "endTime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>endTime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "state": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>state</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "canResume": {
                "optional": true,
                "$ref": "BooleanDelta"
              },
              "paused": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>paused</code>.",
                "optional": true,
                "$ref": "BooleanDelta"
              },
              "error": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>error</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "totalBytes": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>totalBytes</code>.",
                "optional": true,
                "$ref": "DoubleDelta"
              },
              "fileSize": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>fileSize</code>.",
                "optional": true,
                "$ref": "DoubleDelta"
              },
              "exists": {
                "optional": true,
                "$ref": "BooleanDelta"
              }
            }
          }
        ],
        "type": "function"
      }
    ]
  }
]
PK
!<az445chrome/toolkit/content/extensions/schemas/events.json// 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": "events",
    "description": "The <code>chrome.events</code> namespace contains common types used by APIs dispatching events to notify you when something interesting happens.",
    "types": [
      {
        "id": "Rule",
        "type": "object",
        "description": "Description of a declarative rule for handling events.",
        "properties": {
          "id": {
            "type": "string",
            "optional": true,
            "description": "Optional identifier that allows referencing this rule."
          },
          "tags": {
            "type": "array",
            "items": {"type": "string"},
            "optional": true,
            "description":  "Tags can be used to annotate rules and perform operations on sets of rules."
          },
          "conditions": {
            "type": "array",
            "items": {"type": "any"},
            "description": "List of conditions that can trigger the actions."
          },
          "actions": {
            "type": "array",
            "items": {"type": "any"},
            "description": "List of actions that are triggered if one of the condtions is fulfilled."
          },
          "priority": {
            "type": "integer",
            "optional": true,
            "description": "Optional priority of this rule. Defaults to 100."
          }
        }
      },
      {
        "id": "Event",
        "type": "object",
        "description": "An object which allows the addition and removal of listeners for a Chrome event.",
        "functions": [
          {
            "name": "addListener",
            "type": "function",
            "description": "Registers an event listener <em>callback</em> to an event.",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Called when an event occurs. The parameters of this function depend on the type of event."
              }
            ]
          },
          {
            "name": "removeListener",
            "type": "function",
            "description": "Deregisters an event listener <em>callback</em> from an event.",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Listener that shall be unregistered."
              }
            ]
          },
          {
            "name": "hasListener",
            "type": "function",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Listener whose registration status shall be tested."
              }
            ],
            "returns": {
              "type": "boolean",
              "description": "True if <em>callback</em> is registered to the event."
            }
          },
          {
            "name": "hasListeners",
            "type": "function",
            "parameters": [],
            "returns": {
              "type": "boolean",
              "description": "True if any event listeners are registered to the event."
            }
          },
          {
            "name": "addRules",
            "unsupported": true,
            "type": "function",
            "description": "Registers rules to handle events.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "rules",
                "type": "array",
                "items": {"$ref": "Rule"},
                "description": "Rules to be registered. These do not replace previously registered rules."
              },
              {
                "name": "callback",
                "optional": true,
                "type": "function",
                "parameters": [
                  {
                    "name": "rules",
                    "type": "array",
                    "items": {"$ref": "Rule"},
                    "description": "Rules that were registered, the optional parameters are filled with values."
                  }
                ],
                "description": "Called with registered rules."
              }
            ]
          },
          {
            "name": "getRules",
            "unsupported": true,
            "type": "function",
            "description": "Returns currently registered rules.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "ruleIdentifiers",
                "optional": true,
                "type": "array",
                "items": {"type": "string"},
                "description": "If an array is passed, only rules with identifiers contained in this array are returned."
              },
              {
                "name": "callback",
                "type": "function",
                "parameters": [
                  {
                    "name": "rules",
                    "type": "array",
                    "items": {"$ref": "Rule"},
                    "description": "Rules that were registered, the optional parameters are filled with values."
                  }
                ],
                "description": "Called with registered rules."
              }
            ]
          },
          {
            "name": "removeRules",
            "unsupported": true,
            "type": "function",
            "description": "Unregisters currently registered rules.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "ruleIdentifiers",
                "optional": true,
                "type": "array",
                "items": {"type": "string"},
                "description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
              },
              {
                "name": "callback",
                "optional": true,
                "type": "function",
                "parameters": [],
                "description": "Called when rules were unregistered."
              }
            ]
          }
        ]
      },
      {
        "id": "UrlFilter",
        "type": "object",
        "description": "Filters URLs for various criteria. See <a href='events#filtered'>event filtering</a>. All criteria are case sensitive.",
        "properties": {
          "hostContains": {
            "type": "string",
            "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.",
            "optional": true
          },
          "hostEquals": {
            "type": "string",
            "description": "Matches if the host name of the URL is equal to a specified string.",
            "optional": true
          },
          "hostPrefix": {
            "type": "string",
            "description": "Matches if the host name of the URL starts with a specified string.",
            "optional": true
          },
          "hostSuffix": {
            "type": "string",
            "description": "Matches if the host name of the URL ends with a specified string.",
            "optional": true
          },
          "pathContains": {
            "type": "string",
            "description": "Matches if the path segment of the URL contains a specified string.",
            "optional": true
          },
          "pathEquals": {
            "type": "string",
            "description": "Matches if the path segment of the URL is equal to a specified string.",
            "optional": true
          },
          "pathPrefix": {
            "type": "string",
            "description": "Matches if the path segment of the URL starts with a specified string.",
            "optional": true
          },
          "pathSuffix": {
            "type": "string",
            "description": "Matches if the path segment of the URL ends with a specified string.",
            "optional": true
          },
          "queryContains": {
            "type": "string",
            "description": "Matches if the query segment of the URL contains a specified string.",
            "optional": true
          },
          "queryEquals": {
            "type": "string",
            "description": "Matches if the query segment of the URL is equal to a specified string.",
            "optional": true
          },
          "queryPrefix": {
            "type": "string",
            "description": "Matches if the query segment of the URL starts with a specified string.",
            "optional": true
          },
          "querySuffix": {
            "type": "string",
            "description": "Matches if the query segment of the URL ends with a specified string.",
            "optional": true
          },
          "urlContains": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlEquals": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlMatches": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
            "optional": true
          },
          "originAndPathMatches": {
            "type": "string",
            "description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
            "optional": true
          },
          "urlPrefix": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlSuffix": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "schemes": {
            "type": "array",
            "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.",
            "optional": true,
            "items": { "type": "string" }
          },
          "ports": {
            "type": "array",
            "description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.",
            "optional": true,
            "items": {
              "choices": [
                {"type": "integer", "description": "A specific port."},
                {"type": "array", "minItems": 2, "maxItems": 2, "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."}
              ]
            }
          }
        }
      }
    ]
  }
]
PK
!<2\:chrome/toolkit/content/extensions/schemas/experiments.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [
          {
            "type": "string",
            "pattern": "^experiments(\\.\\w+)+$"
          }
        ]
      }
    ]
  }
]
PK
!<Uy8chrome/toolkit/content/extensions/schemas/extension.json// 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": "extension",
    "allowedContexts": ["content", "devtools"],
    "description": "The <code>browser.extension</code> API has utilities that can be used by any extension page. It includes support for exchanging messages between an extension and its content scripts or between extensions, as described in detail in $(topic:messaging)[Message Passing].",
    "properties": {
      "lastError": {
        "type": "object",
        "optional": true,
        "allowedContexts": ["content", "devtools"],
        "description": "Set for the lifetime of a callback if an ansychronous extension api has resulted in an error. If no error has occured lastError will be <var>undefined</var>.",
        "properties": {
          "message": { "type": "string", "description": "Description of the error that has taken place." }
        },
        "additionalProperties": {
          "type": "any"
        }
      },
      "inIncognitoContext": {
        "type": "boolean",
        "optional": true,
        "allowedContexts": ["content", "devtools"],
        "description": "True for content scripts running inside incognito tabs, and for extension pages running inside an incognito process. The latter only applies to extensions with 'split' incognito_behavior."
      }
    },
    "types": [
      {
        "id": "ViewType",
        "type": "string",
        "enum": ["tab", "popup", "sidebar"],
        "description": "The type of extension view."
      }
    ],
    "functions": [
      {
        "name": "getURL",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Converts a relative path within an extension install directory to a fully-qualified URL.",
        "parameters": [
          {
            "type": "string",
            "name": "path",
            "description": "A path to a resource within an extension expressed relative to its install directory."
          }
        ],
        "returns": {
          "type": "string",
          "description": "The fully-qualified URL to the resource."
        }
      },
      {
        "name": "getViews",
        "type": "function",
        "description": "Returns an array of the JavaScript 'window' objects for each of the pages running inside the current extension.",
        "parameters": [
          {
            "type": "object",
            "name": "fetchProperties",
            "optional": true,
            "properties": {
              "type": {
                "$ref": "ViewType",
                "optional": true,
                "description": "The type of view to get. If omitted, returns all views (including background pages and tabs). Valid values: 'tab', 'popup', 'sidebar'."
              },
              "windowId": {
                "type": "integer",
                "optional": true,
                "description": "The window to restrict the search to. If omitted, returns all views."
              },
              "tabId": {
                "type": "integer",
                "optional":true,
                "description": "Find a view according to a tab id. If this field is omitted, returns all views."
              }
            }
          }
        ],
        "returns": {
          "type": "array",
          "description": "Array of global objects",
          "items": {
            "type": "object",
            "isInstanceOf": "Window",
            "additionalProperties": { "type": "any" }
          }
        }
      },
      {
        "name": "getBackgroundPage",
        "type": "function",
        "description": "Returns the JavaScript 'window' object for the background page running inside the current extension. Returns null if the extension has no background page.",
        "parameters": [],
        "returns": {
            "type": "object",
            "optional": true,
            "isInstanceOf": "Window",
            "additionalProperties": { "type": "any" }
         }
      },
      {
        "name": "isAllowedIncognitoAccess",
        "type": "function",
        "description": "Retrieves the state of the extension's access to Incognito-mode (as determined by the user-controlled 'Allowed in Incognito' checkbox.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "isAllowedAccess",
                "type": "boolean",
                "description": "True if the extension has access to Incognito mode, false otherwise."
              }
            ]
          }
        ]
      },
      {
        "name": "isAllowedFileSchemeAccess",
        "type": "function",
        "description": "Retrieves the state of the extension's access to the 'file://' scheme (as determined by the user-controlled 'Allow access to File URLs' checkbox.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "isAllowedAccess",
                "type": "boolean",
                "description": "True if the extension can access the 'file://' scheme, false otherwise."
              }
            ]
          }
        ]
      },
      {
        "name": "setUpdateUrlData",
        "unsupported": true,
        "type": "function",
        "description": "Sets the value of the ap CGI parameter used in the extension's update URL.  This value is ignored for extensions that are hosted in the browser vendor's store.",
        "parameters": [
          {"type": "string", "name": "data", "maxLength": 1024}
        ]
      }
    ],
    "events": [
      {
        "name": "onRequest",
        "unsupported": true,
        "deprecated": "Please use $(ref:runtime.onMessage).",
        "type": "function",
        "description": "Fired when a request is sent from either an extension process or a content script.",
        "parameters": [
          {"name": "request", "type": "any", "optional": true, "description": "The request sent by the calling script."},
          {"name": "sender", "$ref": "runtime.MessageSender" },
          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response. If you have more than one <code>onRequest</code> listener in the same document, then only one may send a response." }
        ]
      },
      {
        "name": "onRequestExternal",
        "unsupported": true,
        "deprecated": "Please use $(ref:runtime.onMessageExternal).",
        "type": "function",
        "description": "Fired when a request is sent from another extension.",
        "parameters": [
          {"name": "request", "type": "any", "optional": true, "description": "The request sent by the calling script."},
          {"name": "sender", "$ref": "runtime.MessageSender" },
          {"name": "sendResponse", "type": "function", "description": "Function to call when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response." }
        ]
      }
    ]
  }
]
PK
!<qqJchrome/toolkit/content/extensions/schemas/extension_protocol_handlers.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "id": "ProtocolHandler",
        "type": "object",
        "description": "Represents a protocol handler definition.",
        "properties": {
          "name": {
            "description": "A user-readable title string for the protocol handler. This will be displayed to the user in interface objects as needed.",
            "type": "string"
          },
          "protocol": {
            "description": "The protocol the site wishes to handle, specified as a string. For example, you can register to handle SMS text message links by registering to handle the \"sms\" scheme.",
            "choices": [{
              "type": "string",
              "enum": [
                "bitcoin", "geo", "gopher", "im", "irc", "ircs", "magnet",
                "mailto", "mms", "news", "nntp", "sip", "sms", "smsto", "ssh",
                "tel", "urn", "webcal", "wtai", "xmpp"
              ]
            }, {
              "type": "string",
              "pattern": "^(ext|web)\\+[a-z0-9.+-]+$"
            }]
          },
          "uriTemplate": {
            "description": "The URL of the handler, as a string. This string should include \"%s\" as a placeholder which will be replaced with the escaped URL of the document to be handled. This URL might be a true URL, or it could be a phone number, email address, or so forth.",
            "preprocess": "localize",
            "choices": [
              {"$ref": "ExtensionURL"},
              {"$ref": "HttpURL"}
            ]
          }
        }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "protocol_handlers": {
            "description": "A list of protocol handler definitions.",
            "optional": true,
            "type": "array",
            "items": {"$ref": "ProtocolHandler"}
          }
        }
      }
    ]
  }
]
PK
!<&ZZ>chrome/toolkit/content/extensions/schemas/extension_types.json// 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": "extensionTypes",
    "description": "The <code>browser.extensionTypes</code> API contains type declarations for WebExtensions.",
    "types": [
      {
        "id": "ImageFormat",
        "type": "string",
        "enum": ["jpeg", "png"],
        "description": "The format of an image."
      },
      {
        "id": "ImageDetails",
        "type": "object",
        "description": "Details about the format and quality of an image.",
        "properties": {
          "format": {
            "$ref": "ImageFormat",
            "optional": true,
            "description": "The format of the resulting image.  Default is <code>\"jpeg\"</code>."
          },
          "quality": {
            "type": "integer",
            "optional": true,
            "minimum": 0,
            "maximum": 100,
            "description": "When format is <code>\"jpeg\"</code>, controls the quality of the resulting image.  This value is ignored for PNG images.  As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
          }
        }
      },
      {
        "id": "RunAt",
        "type": "string",
        "enum": ["document_start", "document_end", "document_idle"],
        "description": "The soonest that the JavaScript or CSS will be injected into the tab."
      },
      {
        "id": "CSSOrigin",
        "type": "string",
        "enum": ["user", "author"],
        "description": "The origin of the CSS to inject, this affects the cascading order (priority) of the stylesheet."
      },
      {
        "id": "InjectDetails",
        "type": "object",
        "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.",
        "properties": {
          "code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject.<br><br><b>Warning:</b><br>Be careful using the <code>code</code> parameter. Incorrect use of it may open your extension to <a href=\"https://en.wikipedia.org/wiki/Cross-site_scripting\">cross site scripting</a> attacks."},
          "file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."},
          "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."},
          "matchAboutBlank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."},
          "frameId": {
            "type": "integer",
            "minimum": 0,
            "optional": true,
            "description": "The ID of the frame to inject the script into. This may not be used in combination with <code>allFrames</code>."
          },
          "runAt": {
            "$ref": "RunAt",
            "optional": true,
            "default": "document_idle",
            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
          },
          "cssOrigin": {
            "$ref": "CSSOrigin",
            "optional": true,
            "description": "The css origin of the stylesheet to inject. Defaults to \"author\"."
          }
        }
      },
      {
        "id": "Date",
        "choices": [
          {
            "type": "string",
            "format": "date"
          },
          {
            "type": "integer",
            "minimum": 0
          },
          {
            "type": "object",
            "isInstanceOf": "Date",
            "additionalProperties": { "type": "any" }
          }
        ]
      }
    ]
  }
]
PK
!<B)3chrome/toolkit/content/extensions/schemas/i18n.json// 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": {
          "default_locale": {
            "type": "string",
            "optional": "true"
          }
        }
      }
    ]
  },
  {
    "namespace": "i18n",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "Use the <code>browser.i18n</code> infrastructure to implement internationalization across your whole app or extension.",
    "types": [
      {
        "id": "LanguageCode",
        "type": "string",
        "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>. For an unknown language, <code>und</code> will be returned, which means that [percentage] of the text is unknown to CLD"
      }
    ],
    "functions": [
      {
        "name": "getAcceptLanguages",
        "type": "function",
        "description": "Gets the accept-languages of the browser. This is different from the locale used by the browser; to get the locale, use $(ref:i18n.getUILanguage).",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {"name": "languages", "type": "array", "items": {"$ref": "LanguageCode"}, "description": "Array of LanguageCode"}
            ]
          }
        ]
      },
      {
        "name": "getMessage",
        "type": "function",
        "description": "Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the <code>getMessage()</code> call is wrong &mdash; for example, <em>messageName</em> is not a string or the <em>substitutions</em> array has more than 9 elements &mdash; this method returns <code>undefined</code>.",
        "parameters": [
          {
            "type": "string",
            "name": "messageName",
            "description": "The name of the message, as specified in the <code>$(topic:i18n-messages)[messages.json]</code> file."
          },
          {
            "type": "any",
            "name": "substitutions",
            "optional": true,
            "description": "Substitution strings, if the message requires any."
          }
        ],
        "returns": {
          "type": "string",
          "description": "Message localized for current locale."
        }
      },
      {
        "name": "getUILanguage",
        "type": "function",
        "description": "Gets the browser UI language of the browser. This is different from $(ref:i18n.getAcceptLanguages) which returns the preferred user languages.",
        "parameters": [],
        "returns": {
          "type": "string",
          "description": "The browser UI language code such as en-US or fr-FR."
        }
      },
      {
        "name": "detectLanguage",
        "type": "function",
        "description": "Detects the language of the provided text using CLD.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "text",
            "description": "User input string to be translated."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "type": "object",
                "name": "result",
                "description": "LanguageDetectionResult object that holds detected langugae reliability and array of DetectedLanguage",
                "properties": {
                  "isReliable": { "type": "boolean", "description": "CLD detected language reliability" },
                  "languages":
                    {
                      "type": "array",
                      "description": "array of detectedLanguage",
                      "items":
                        {
                          "type": "object",
                          "description": "DetectedLanguage object that holds detected ISO language code and its percentage in the input string",
                          "properties":
                            {
                              "language":
                                {
                                  "$ref": "LanguageCode"
                                },
                              "percentage":
                                {
                                  "type": "integer",
                                  "description": "The percentage of the detected language"
                                }
                            }
                        }
                    }
                }
              }
            ]
          }
        ]
      }
    ],
    "events": []
  }
]
PK
!<?sVrr7chrome/toolkit/content/extensions/schemas/identity.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "identity"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "identity",
    "description": "Use the chrome.identity API to get OAuth2 access tokens. ",
    "permissions": ["identity"],
    "types": [
      {
        "id": "AccountInfo",
        "type": "object",
        "description": "An object encapsulating an OAuth account id.",
        "properties": {
          "id": {
            "type": "string",
            "description": "A unique identifier for the account. This ID will not change for the lifetime of the account. "
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAccounts",
        "type": "function",
        "unsupported": true,
        "description": "Retrieves a list of AccountInfo objects describing the accounts present on the profile.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": {
                  "$ref": "AccountInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAuthToken",
        "type": "function",
        "unsupported": true,
        "description": "Gets an OAuth2 access token using the client ID and scopes specified in the oauth2 section of manifest.json.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "optional": true,
            "type": "object",
            "properties": {
              "interactive": {
                "optional": true,
                "type": "boolean"
              },
              "account": {
                "optional": true,
                "$ref": "AccountInfo"
              },
              "scopes": {
                "optional": true,
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          {
            "name": "callback",
            "optional": true,
            "type": "function",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": {
                  "$ref": "AccountInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getProfileUserInfo",
        "type": "function",
        "unsupported": true,
        "description": "Retrieves email address and obfuscated gaia id of the user signed into a profile.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "userinfo",
                "type": "object",
                "properties": {
                  "email": {"type": "string"},
                  "id": { "type": "string" }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "removeCachedAuthToken",
        "type": "function",
        "unsupported": true,
        "description": "Removes an OAuth2 access token from the Identity API's token cache.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "token": {"type": "string"}
            }
          },
          {
            "name": "callback",
            "optional": true,
            "type": "function",
            "parameters": [
              {
                "name": "userinfo",
                "type": "object",
                "properties": {
                  "email": {"type": "string"},
                  "id": { "type": "string" }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "launchWebAuthFlow",
        "type": "function",
        "description": "Starts an auth flow at the specified URL.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "url": {"type": "string"},
              "interactive": {"type": "boolean", "optional": true}
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": " responseUrl",
                "type": "string",
                "optional": true
              }
            ]
          }
        ]
      },
      {
        "name": "getRedirectURL",
        "type": "function",
        "description": "Generates a redirect URL to be used in |launchWebAuthFlow|.",
        "parameters": [
          {
            "name": " path",
            "type": "string",
            "default": "",
            "optional": true,
            "description": "The path appended to the end of the generated URL. "
          }
        ],
        "returns": {
          "type": "string"
        }
      }
    ],
    "events": [
      {
        "name": "onSignInChanged",
        "unsupported": true,
        "type": "function",
        "description": "Fired when signin state changes for an account on the user's profile.",
        "parameters": [
          {
            "name": "account",
            "$ref": "AccountInfo"
          },
          {
            "name": "signedIn",
            "type": "boolean"
          }
        ]
      }
    ]
  }
]
PK
!<(3chrome/toolkit/content/extensions/schemas/idle.json// 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": "idle",
    "description": "Use the <code>browser.idle</code> API to detect when the machine's idle state changes.",
    "permissions": ["idle"],
    "types": [
      {
        "id": "IdleState",
        "type": "string",
        "enum": ["active", "idle"]
      }
    ],
    "functions": [
      {
        "name": "queryState",
        "type": "function",
        "description": "Returns \"idle\" if the user has not generated any input for a specified number of seconds, or \"active\" otherwise.",
        "async": "callback",
        "parameters": [
          {
            "name": "detectionIntervalInSeconds",
            "type": "integer",
            "minimum": 15,
            "description": "The system is considered idle if detectionIntervalInSeconds seconds have elapsed since the last user input detected."
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "newState",
                "$ref": "IdleState"
              }
            ]
          }
        ]
      },
      {
        "name": "setDetectionInterval",
        "type": "function",
        "description": "Sets the interval, in seconds, used to determine when the system is in an idle state for onStateChanged events. The default interval is 60 seconds.",
        "parameters": [
          {
            "name": "intervalInSeconds",
            "type": "integer",
            "minimum": 15,
            "description": "Threshold, in seconds, used to determine when the system is in an idle state."
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onStateChanged",
        "type": "function",
        "description": "Fired when the system changes to an active or idle state. The event fires with \"idle\" if the the user has not generated any input for a specified number of seconds, and \"active\" when the user generates input on an idle system.",
        "parameters": [
          {
            "name": "newState",
            "$ref": "IdleState"
          }
        ]
      }
    ]
  }
]
PK
!<1!)!)9chrome/toolkit/content/extensions/schemas/management.json// 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": [
            "management"
          ]
        }]
      }
    ]
  },
  {
    "namespace":"management",
    "description": "The <code>browser.management</code> API provides ways to manage the list of extensions that are installed and running.",
    "types": [
      {
        "id": "IconInfo",
        "description": "Information about an icon belonging to an extension.",
        "type": "object",
        "properties": {
          "size": {
            "type": "integer",
            "description": "A number representing the width and height of the icon. Likely values include (but are not limited to) 128, 48, 24, and 16."
          },
          "url": {
            "type": "string",
            "description": "The URL for this icon image. To display a grayscale version of the icon (to indicate that an extension is disabled, for example), append <code>?grayscale=true</code> to the URL."
          }
        }
      },
      {
        "id": "ExtensionDisabledReason",
        "description": "A reason the item is disabled.",
        "type": "string",
        "enum": ["unknown", "permissions_increase"]
      },
      {
        "id": "ExtensionType",
        "description": "The type of this extension. Will always be 'extension'.",
        "type": "string",
        "enum": ["extension", "theme"]
      },
      {
        "id": "ExtensionInstallType",
        "description": "How the extension was installed. One of<br><var>development</var>: The extension was loaded unpacked in developer mode,<br><var>normal</var>: The extension was installed normally via an .xpi file,<br><var>sideload</var>: The extension was installed by other software on the machine,<br><var>other</var>: The extension was installed by other means.",
        "type": "string",
        "enum": ["development", "normal", "sideload", "other"]
      },
      {
        "id": "ExtensionInfo",
        "description": "Information about an installed extension.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The extension's unique identifier.",
            "type": "string"
          },
          "name": {
            "description": "The name of this extension.",
            "type": "string"
          },
          "shortName": {
            "description": "A short version of the name of this extension.",
            "type": "string",
            "optional": true
          },
          "description": {
            "description": "The description of this extension.",
            "type": "string"
          },
          "version": {
            "description": "The <a href='manifest/version'>version</a> of this extension.",
            "type": "string"
          },
          "versionName": {
            "description": "The <a href='manifest/version#version_name'>version name</a> of this extension if the manifest specified one.",
            "type": "string",
            "optional": true
          },
          "mayDisable": {
            "description": "Whether this extension can be disabled or uninstalled by the user.",
            "type": "boolean"
          },
          "enabled": {
            "description": "Whether it is currently enabled or disabled.",
            "type": "boolean"
          },
          "disabledReason": {
            "description": "A reason the item is disabled.",
            "$ref": "ExtensionDisabledReason",
            "optional": true
          },
          "type": {
            "description": "The type of this extension. Will always return 'extension'.",
            "$ref": "ExtensionType"
          },
          "homepageUrl": {
            "description": "The URL of the homepage of this extension.",
            "type": "string",
            "optional": true
          },
          "updateUrl": {
            "description": "The update URL of this extension.",
            "type": "string",
            "optional": true
          },
          "optionsUrl": {
            "description": "The url for the item's options page, if it has one.",
            "type": "string"
          },
          "icons": {
            "description": "A list of icon information. Note that this just reflects what was declared in the manifest, and the actual image at that url may be larger or smaller than what was declared, so you might consider using explicit width and height attributes on img tags referencing these images. See the <a href='manifest/icons'>manifest documentation on icons</a> for more details.",
            "type": "array",
            "optional": true,
            "items": {
              "$ref": "IconInfo"
            }
          },
          "permissions": {
            "description": "Returns a list of API based permissions.",
            "type": "array",
            "optional": true,
            "items" : {
              "type": "string"
            }
          },
          "hostPermissions": {
            "description": "Returns a list of host based permissions.",
            "type": "array",
            "optional": true,
            "items" : {
              "type": "string"
            }
          },
          "installType": {
            "description": "How the extension was installed.",
            "$ref": "ExtensionInstallType"
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAll",
        "type": "function",
        "permissions": ["management"],
        "description": "Returns a list of information about installed extensions.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "type": "array",
                "name": "result",
                "items": {
                  "$ref": "ExtensionInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "get",
        "type": "function",
        "permissions": ["management"],
        "description": "Returns information about the installed extension that has the given ID.",
        "async": "callback",
        "parameters": [
          {
            "name": "id",
            "$ref": "manifest.ExtensionID",
            "description": "The ID from an item of $(ref:management.ExtensionInfo)."
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "result",
                "$ref": "ExtensionInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "getSelf",
        "type": "function",
        "description": "Returns information about the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "result",
                "$ref": "ExtensionInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "uninstallSelf",
        "type": "function",
        "description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "options",
            "optional": true,
            "properties": {
              "showConfirmDialog": {
                "type": "boolean",
                "optional": true,
                "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
              },
              "dialogMessage": {
                "type": "string",
                "optional": true,
                "description": "The message to display to a user when being asked to confirm removal of the extension."
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "setEnabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Enables or disables the given add-on.",
        "async": "callback",
        "parameters": [
          {
            "name": "id",
            "type": "string",
            "description": "ID of the add-on to enable/disable."
          },
          {
            "name": "enabled",
            "type": "boolean",
            "description": "Whether to enable or disable the add-on."
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onDisabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been disabled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onEnabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been enabled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onInstalled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been installed.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onUninstalled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been uninstalled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      }
    ]
  }
]
PK
!<]Ѫ//7chrome/toolkit/content/extensions/schemas/manifest.json[
  {
    "namespace": "manifest",
    "permissions": [],
    "types": [
      {
        "id": "WebExtensionManifest",
        "type": "object",
        "description": "Represents a WebExtension manifest.json file",
        "properties": {
          "manifest_version": {
            "type": "integer",
            "minimum": 2,
            "maximum": 2
          },

          "minimum_chrome_version":{
            "type": "string",
            "optional": true
          },

          "applications": {
            "type": "object",
            "optional": true,
            "properties": {
              "gecko": {
                "$ref": "FirefoxSpecificProperties",
                "optional": true
              }
            }
          },

          "browser_specific_settings": {
            "type": "object",
            "optional": true,
            "properties": {
              "gecko": {
                "$ref": "FirefoxSpecificProperties",
                "optional": true
              }
            }
          },

          "name": {
            "type": "string",
            "optional": false,
            "preprocess": "localize"
          },

          "short_name": {
            "type": "string",
            "optional": true,
            "preprocess": "localize"
          },

          "description": {
            "type": "string",
            "optional": true,
            "preprocess": "localize"
          },

          "author": {
            "type": "string",
            "optional": true,
            "preprocess": "localize",
            "onError": "warn"
          },

          "version": {
            "type": "string",
            "optional": false
          },

          "homepage_url": {
            "type": "string",
            "format": "url",
            "optional": true,
            "preprocess": "localize"
          },

          "icons": {
            "type": "object",
            "optional": true,
            "patternProperties": {
              "^[1-9]\\d*$": { "type": "string" }
            }
          },

          "incognito": {
            "type": "string",
            "enum": ["spanning"],
            "optional": true,
            "onError": "warn"
          },

          "background": {
            "choices": [
              {
                "type": "object",
                "properties": {
                  "page": { "$ref": "ExtensionURL" },
                  "persistent": {
                    "optional": true,
                    "$ref": "PersistentBackgroundProperty"
                  }
                },
                "additionalProperties": { "$ref": "UnrecognizedProperty" }
              },
              {
                "type": "object",
                "properties": {
                  "scripts": {
                    "type": "array",
                    "items": { "$ref": "ExtensionURL" }
                  },
                  "persistent": {
                    "optional": true,
                    "$ref": "PersistentBackgroundProperty"
                  }
                },
                "additionalProperties": { "$ref": "UnrecognizedProperty" }
              }
            ],
            "optional": true
          },

          "options_ui": {
            "type": "object",

            "optional": true,

            "properties": {
              "page": { "$ref": "ExtensionURL" },
              "browser_style": {
                "type": "boolean",
                "optional": true
              },
              "chrome_style": {
                "type": "boolean",
                "optional": true
              },
              "open_in_tab": {
                "type": "boolean",
                "optional": true
              }
            },

            "additionalProperties": {
              "type": "any",
              "deprecated": "An unexpected property was found in the WebExtension manifest"
            }
          },

          "content_scripts": {
            "type": "array",
            "optional": true,
            "items": { "$ref": "ContentScript" }
          },

          "content_security_policy": {
            "type": "string",
            "optional": true,
            "format": "contentSecurityPolicy",
            "onError": "warn"
          },

          "permissions": {
            "type": "array",
            "default": [],
            "items": {
              "$ref": "Permission",
              "onError": "warn"
            },
            "optional": true
          },

          "optional_permissions": {
            "type": "array",
            "items": {
              "$ref": "OptionalPermission",
              "onError": "warn"
            },
            "optional": true,
            "default": []
          },

          "web_accessible_resources": {
            "type": "array",
            "items": { "type": "string" },
            "optional": true
          },

          "developer": {
            "type": "object",
            "optional": true,
            "properties": {
              "name": {
                "type": "string",
                "optional": true,
                "preprocess": "localize"
              },
              "url": {
                "type": "string",
                "optional": true,
                "preprocess": "localize"
              }
            }
          }

        },

        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "ThemeIcons",
        "type": "object",
        "properties": {
          "light": {
            "$ref": "ExtensionURL",
            "description": "A light icon to use for dark themes"
          },
          "dark": {
            "$ref": "ExtensionURL",
            "description": "The dark icon to use for light themes"
          },
          "size": {
            "type": "integer",
            "description": "The size of the icons"
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": [
              "clipboardRead",
              "clipboardWrite",
              "geolocation",
              "idle",
              "notifications"
            ]
          },
          { "$ref": "MatchPattern" }
        ]
      },
      {
        "id": "Permission",
        "choices": [
          { "$ref": "OptionalPermission" },
          {
            "type": "string",
            "enum": [
              "alarms",
              "storage",
              "unlimitedStorage"
            ]
          }
        ]
      },
      {
        "id": "HttpURL",
        "type": "string",
        "format": "url",
        "pattern": "^https?://.*$"
      },
      {
        "id": "ExtensionURL",
        "type": "string",
        "format": "strictRelativeUrl"
      },
      {
        "id": "ExtensionID",
        "choices": [
          {
            "type": "string",
            "pattern": "(?i)^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$"
          },
          {
            "type": "string",
            "pattern": "(?i)^[a-z0-9-._]*@[a-z0-9-._]+$"
          }
        ]
      },
      {
        "id": "FirefoxSpecificProperties",
        "type": "object",
        "properties": {
          "id": {
            "$ref": "ExtensionID",
            "optional": true
          },

          "update_url": {
            "type": "string",
            "format": "url",
            "optional": true
          },

          "strict_min_version": {
            "type": "string",
            "optional": true
          },

          "strict_max_version": {
            "type": "string",
            "optional": true
          }
        }
      },
      {
        "id": "MatchPattern",
        "choices": [
          {
            "type": "string",
            "enum": ["<all_urls>"]
          },
          {
            "type": "string",
            "pattern": "^(https?|wss?|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$"
          },
          {
            "type": "string",
            "pattern": "^file:///.*$"
          }
        ]
      },
      {
        "id": "MatchPatternInternal",
        "description": "Same as MatchPattern above, but includes moz-extension protocol",
        "choices": [
          {
            "type": "string",
            "enum": ["<all_urls>"]
          },
          {
            "type": "string",
            "pattern": "^(https?|wss?|file|ftp|moz-extension|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$"
          },
          {
            "type": "string",
            "pattern": "^file:///.*$"
          }
        ]
      },
      {
        "id": "ContentScript",
        "type": "object",
        "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time. Based on InjectDetails, but using underscore rather than camel case naming conventions.",
        "additionalProperties": { "$ref": "UnrecognizedProperty" },
        "properties": {
          "matches": {
            "type": "array",
            "optional": false,
            "minItems": 1,
            "items": { "$ref": "MatchPattern" }
          },
          "exclude_matches": {
            "type": "array",
            "optional": true,
            "minItems": 1,
            "items": { "$ref": "MatchPattern" }
          },
          "include_globs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "exclude_globs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "css": {
            "type": "array",
            "optional": true,
            "description": "The list of CSS files to inject",
            "items": { "$ref": "ExtensionURL" }
          },
          "js": {
            "type": "array",
            "optional": true,
            "description": "The list of CSS files to inject",
            "items": { "$ref": "ExtensionURL" }
          },
          "all_frames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."},
          "match_about_blank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."},
          "run_at": {
            "$ref": "extensionTypes.RunAt",
            "optional": true,
            "default": "document_idle",
            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
          }
        }
      },
      {
        "id": "IconPath",
        "choices": [
          {
            "type": "object",
            "patternProperties": {
              "^[1-9]\\d*$": { "$ref": "ExtensionURL" }
            },
            "additionalProperties": false
          },
          { "$ref": "ExtensionURL" }
        ]
      },
      {
        "id": "IconImageData",
        "choices": [
          {
            "type": "object",
            "patternProperties": {
              "^[1-9]\\d*$": { "$ref": "ImageData" }
            },
            "additionalProperties": false
          },
          { "$ref": "ImageData" }
        ]
      },
      {
        "id": "ImageData",
        "type": "object",
        "isInstanceOf": "ImageData",
        "postprocess": "convertImageDataToURL"
      },
      {
        "id": "UnrecognizedProperty",
        "type": "any",
        "deprecated": "An unexpected property was found in the WebExtension manifest."
      },
      {
        "id": "PersistentBackgroundProperty",
        "type": "boolean",
        "deprecated": "Event pages are not currently supported. This will run as a persistent background page."
      }
    ]
  }
]
PK
!<"pXCchrome/toolkit/content/extensions/schemas/native_host_manifest.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "id": "NativeHostManifest",
        "type": "object",
        "description": "Represents a native host manifest file",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^\\w+(\\.\\w+)*$"
          },
          "description": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "stdio"
            ]
          },
          "allowed_extensions": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "manifest.ExtensionID"
            }
          }
        }
      }
    ]
  }
]
PK
!<+33<chrome/toolkit/content/extensions/schemas/notifications.json// 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": "notifications",
    "permissions": ["notifications"],
    "types": [
      {
        "id": "TemplateType",
        "type": "string",
        "enum": [
          "basic",
          "image",
          "list",
          "progress"
        ]
      },
      {
        "id": "PermissionLevel",
        "type": "string",
        "enum": [
          "granted",
          "denied"
        ]
      },
      {
        "id": "NotificationItem",
        "type": "object",
        "properties": {
          "title": {
            "description": "Title of one item of a list notification.",
            "type": "string"
          },
          "message": {
            "description": "Additional details about this item.",
            "type": "string"
          }
        }
      },
      {
        "id": "CreateNotificationOptions",
        "type": "object",
        "properties": {
          "type": {
            "description": "Which type of notification to display.",
            "$ref": "TemplateType"
          },
          "iconUrl": {
            "optional": true,
            "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.",
            "type": "string"
          },
          "appIconMaskUrl": {
            "optional": true,
            "description": "A URL to the app icon mask.",
            "type": "string"
          },
          "title": {
            "description": "Title of the notification (e.g. sender name for email).",
            "type": "string"
          },
          "message": {
            "description": "Main notification content.",
            "type": "string"
          },
          "contextMessage": {
            "optional": true,
            "description": "Alternate notification content with a lower-weight font.",
            "type": "string"
          },
          "priority": {
            "optional": true,
            "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.",
            "type": "integer",
            "minimum": -2,
            "maximum": 2
          },
          "eventTime": {
            "optional": true,
            "description": "A timestamp associated with the notification, in milliseconds past the epoch.",
            "type": "number"
          },
          "buttons": {
            "unsupported": true,
            "optional": true,
            "description": "Text and icons for up to two notification action buttons.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "iconUrl": {
                  "optional": true,
                  "type": "string"
                }
              }
            }
          },
          "imageUrl": {
            "optional": true,
            "description": "A URL to the image thumbnail for image-type notifications.",
            "type": "string"
          },
          "items": {
            "optional": true,
            "description": "Items for multi-item notifications.",
            "type": "array",
            "items": { "$ref": "NotificationItem" }
          },
          "progress": {
            "optional": true,
            "description": "Current progress ranges from 0 to 100.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "isClickable": {
            "optional": true,
            "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.",
            "type": "boolean"
          }
        }
      },
      {
        "id": "UpdateNotificationOptions",
        "type": "object",
        "properties": {
          "type": {
            "optional": true,
            "description": "Which type of notification to display.",
            "$ref": "TemplateType"
          },
          "iconUrl": {
            "optional": true,
            "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.",
            "type": "string"
          },
          "appIconMaskUrl": {
            "optional": true,
            "description": "A URL to the app icon mask.",
            "type": "string"
          },
          "title": {
            "optional": true,
            "description": "Title of the notification (e.g. sender name for email).",
            "type": "string"
          },
          "message": {
            "optional": true,
            "description": "Main notification content.",
            "type": "string"
          },
          "contextMessage": {
            "optional": true,
            "description": "Alternate notification content with a lower-weight font.",
            "type": "string"
          },
          "priority": {
            "optional": true,
            "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.",
            "type": "integer",
            "minimum": -2,
            "maximum": 2
          },
          "eventTime": {
            "optional": true,
            "description": "A timestamp associated with the notification, in milliseconds past the epoch.",
            "type": "number"
          },
          "buttons": {
            "unsupported": true,
            "optional": true,
            "description": "Text and icons for up to two notification action buttons.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "iconUrl": {
                  "optional": true,
                  "type": "string"
                }
              }
            }
          },
          "imageUrl": {
            "optional": true,
            "description": "A URL to the image thumbnail for image-type notifications.",
            "type": "string"
          },
          "items": {
            "optional": true,
            "description": "Items for multi-item notifications.",
            "type": "array",
            "items": { "$ref": "NotificationItem" }
          },
          "progress": {
            "optional": true,
            "description": "Current progress ranges from 0 to 100.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "isClickable": {
            "optional": true,
            "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.",
            "type": "boolean"
          }
        }
      }
    ],
    "functions": [
      {
        "name": "create",
        "type": "function",
        "description": "Creates and displays a notification.",
        "async": "callback",
        "parameters": [
          {
            "optional": true,
            "type": "string",
            "name": "notificationId",
            "description": "Identifier of the notification. If it is empty, this method generates an id. If it matches an existing notification, this method first clears that notification before proceeding with the create operation."
          },
          {
            "$ref": "CreateNotificationOptions",
            "name": "options",
            "description": "Contents of the notification."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "notificationId",
                "type": "string",
                "description": "The notification id (either supplied or generated) that represents the created notification."
              }
            ]
          }
        ]
      },
      {
        "name": "update",
        "unsupported": true,
        "type": "function",
        "description": "Updates an existing notification.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The id of the notification to be updated."
          },
          {
            "$ref": "UpdateNotificationOptions",
            "name": "options",
            "description": "Contents of the notification to update to."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasUpdated",
                "type": "boolean",
                "description": "Indicates whether a matching notification existed."
              }
            ]
          }
        ]
      },
      {
        "name": "clear",
        "type": "function",
        "description": "Clears an existing notification.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The id of the notification to be updated."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasCleared",
                "type": "boolean",
                "description": "Indicates whether a matching notification existed."
              }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Retrieves all the notifications.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "notifications",
                "type": "object",
                "additionalProperties": {"$ref": "CreateNotificationOptions"},
                "description": "The set of notifications currently in the system."
              }
            ]
          }
        ]
      },
      {
        "name": "getPermissionLevel",
        "unsupported": true,
        "type": "function",
        "description": "Retrieves whether the user has enabled notifications from this app or extension.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "level",
                "$ref": "PermissionLevel",
                "description": "The current permission level."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onClosed",
        "type": "function",
        "description": "Fired when the notification closed, either by the system or by user action.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the closed notification."
          },
          {
            "type": "boolean",
            "name": "byUser",
            "description": "True if the notification was closed by the user."
          }
        ]
      },
      {
        "name": "onClicked",
        "type": "function",
        "description": "Fired when the user clicked in a non-button area of the notification.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the clicked notification."
          }
        ]
      },
      {
        "name": "onButtonClicked",
        "type": "function",
        "description": "Fired when the  user pressed a button in the notification.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the clicked notification."
          },
          {
            "type": "number",
            "name": "buttonIndex",
            "description": "The index of the button clicked by the user."
          }
        ]
      },
      {
        "name": "onPermissionLevelChanged",
        "unsupported": true,
        "type": "function",
        "description": "Fired when the user changes the permission level.",
        "parameters": [
          {
            "$ref": "PermissionLevel",
            "name": "level",
            "description": "The new permission level."
          }
        ]
      },
      {
        "name": "onShowSettings",
        "unsupported": true,
        "type": "function",
        "description": "Fired when the user clicked on a link for the app's notification settings.",
        "parameters": [
        ]
      },
      {
        "name": "onShown",
        "type": "function",
        "description": "Fired when the notification is shown.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the shown notification."
          }
        ]
      }
    ]
  }
]
PK
!<EQ:chrome/toolkit/content/extensions/schemas/permissions.json[
  {
    "namespace": "permissions",
    "permissions": ["manifest:optional_permissions"],
    "types": [
      {
        "id": "Permissions",
        "type": "object",
        "properties": {
          "permissions": {
            "type": "array",
            "items": { "$ref": "manifest.OptionalPermission" },
            "optional": true,
            "default": []
          },
          "origins": {
            "type": "array",
            "items": { "$ref": "manifest.MatchPattern" },
            "optional": true,
            "default": []
          }
        }
      },
      {
        "id": "AnyPermissions",
        "type": "object",
        "properties": {
          "permissions": {
            "type": "array",
            "items": { "$ref": "manifest.Permission" },
            "optional": true,
            "default": []
          },
          "origins": {
            "type": "array",
            "items": { "$ref": "manifest.MatchPatternInternal" },
            "optional": true,
            "default": []
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAll",
        "type": "function",
        "async": "callback",
        "description": "Get a list of all the extension's permissions.",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "permissions",
                "$ref": "AnyPermissions"
              }
            ]
          }
        ]
      },
      {
        "name": "contains",
        "type": "function",
        "async": "callback",
        "description": "Check if the extension has the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "AnyPermissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "result",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "request",
        "type": "function",
        "allowedContexts": ["content"],
        "async": "callback",
        "requireUserInput": true,
        "description": "Request the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "granted",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "async": "callback",
        "description": "Relinquish the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onAdded",
        "type": "function",
        "unsupported": true,
        "description": "Fired when the extension acquires new permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          }
        ]
      },
      {
        "name": "onRemoved",
        "type": "function",
        "unsupported": true,
        "description": "Fired when permissions are removed from the extension.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          }
        ]
      }
    ]
  }
]
PK
!<Z6chrome/toolkit/content/extensions/schemas/privacy.json// 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": [
            "privacy"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "privacy",
    "permissions": ["privacy"]
  },
  {
    "namespace": "privacy.network",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "types": [
      {
        "id": "IPHandlingPolicy",
        "type": "string",
        "enum": ["default", "default_public_and_private_interfaces", "default_public_interface_only", "disable_non_proxied_udp"],
        "description": "The IP handling policy of WebRTC."
      }
    ],
    "properties": {
      "networkPredictionEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser attempts to speed up your web browsing experience by pre-resolving DNS entries, prerendering sites (<code>&lt;link rel='prefetch' ...&gt;</code>), and preemptively opening TCP and SSL connections to servers.  This preference's value is a boolean, defaulting to <code>true</code>."
      },
      "peerConnectionEnabled": {
        "$ref": "types.Setting",
        "description": "Allow users to enable and disable RTCPeerConnections (aka WebRTC)."
      },
      "webRTCIPHandlingPolicy": {
        "$ref": "types.Setting",
        "description": "Allow users to specify the media performance/privacy tradeoffs which impacts how WebRTC traffic will be routed and how much local address information is exposed. This preference's value is of type IPHandlingPolicy, defaulting to <code>default</code>."
      }
    }
  },
  {
    "namespace": "privacy.services",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "properties": {
      "passwordSavingEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the password manager will ask if you want to save passwords. This preference's value is a boolean, defaulting to <code>true</code>."
      }
    }
  },
  {
    "namespace": "privacy.websites",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "properties": {
      "thirdPartyCookiesAllowed": {
        "$ref": "types.Setting",
        "description": "If disabled, the browser blocks third-party sites from setting cookies. The value of this preference is of type boolean, and the default value is <code>true</code>.",
        "unsupported": true
      },
      "hyperlinkAuditingEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser sends auditing pings when requested by a website (<code>&lt;a ping&gt;</code>). The value of this preference is of type boolean, and the default value is <code>true</code>."
      },
      "referrersEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser sends <code>referer</code> headers with your requests. Yes, the name of this preference doesn't match the misspelled header. No, we're not going to change it. The value of this preference is of type boolean, and the default value is <code>true</code>."
      },
      "protectedContentEnabled": {
        "$ref": "types.Setting",
        "description": "<strong>Available on Windows and ChromeOS only</strong>: If enabled, the browser provides a unique ID to plugins in order to run protected content. The value of this preference is of type boolean, and the default value is <code>true</code>.",
        "unsupported": true
      }
    }
  }
]
PK
!<T4chrome/toolkit/content/extensions/schemas/proxy.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "proxy"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "proxy",
    "description": "Use the browser.proxy API to register proxy scripts in Firefox. Proxy scripts in Firefox are proxy auto-config files with extra contextual information and support for additional return types.",
    "permissions": ["proxy"],
    "functions": [
      {
        "name": "register",
        "type": "function",
        "description": "Registers the proxy script for the extension.",
        "async": true,
        "parameters": [
          {
            "name": "url",
            "type": "string",
            "format": "strictRelativeUrl"
          }
        ]
      },
      {
        "name": "unregister",
        "type": "function",
        "description": "Unregisters the proxy script for the extension.",
        "async": true,
        "parameters": []
      },
      {
        "name": "registerProxyScript",
        "type": "function",
        "description": "Registers the proxy script for the extension.",
        "deprecated": "Please use $(ref:proxy.register)",
        "async": true,
        "parameters": [
          {
            "name": "url",
            "type": "string",
            "format": "strictRelativeUrl"
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onProxyError",
        "type": "function",
        "description": "Notifies about proxy script errors.",
        "parameters": [
          {
            "name": "error",
            "type": "object"
          }
        ]
      }
    ]
  }
]PK
!<#`}h}h6chrome/toolkit/content/extensions/schemas/runtime.json// 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": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "nativeMessaging"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "runtime",
    "allowedContexts": ["content", "devtools", "proxy"],
    "description": "Use the <code>browser.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.",
    "types": [
      {
        "id": "Port",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object which allows two way communication with other pages.",
        "properties": {
          "name": {"type": "string"},
          "disconnect": { "type": "function" },
          "onDisconnect": { "$ref": "events.Event" },
          "onMessage": { "$ref": "events.Event" },
          "postMessage": {"type": "function"},
          "sender": {
            "$ref": "MessageSender",
            "optional": true,
            "description": "This property will <b>only</b> be present on ports passed to onConnect/onConnectExternal listeners."
          }
        },
        "additionalProperties": { "type": "any"}
      },
      {
        "id": "MessageSender",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object containing information about the script context that sent a message or request.",
        "properties": {
          "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab (including content scripts), and <strong>only</strong> if the receiver is an extension, not an app."},
          "frameId": {"type": "integer", "optional": true, "description": "The $(topic:frame_ids)[frame] that opened the connection. 0 for top-level frames, positive for child frames. This will only be set when <code>tab</code> is set."},
          "id": {"type": "string", "optional": true, "description": "The ID of the extension or app that opened the connection, if any."},
          "url": {"type": "string", "optional": true, "description": "The URL of the page or frame that opened the connection. If the sender is in an iframe, it will be iframe's URL not the URL of the page which hosts it."},
          "tlsChannelId": {"unsupported": true, "type": "string", "optional": true, "description": "The TLS channel ID of the page or frame that opened the connection, if requested by the extension or app, and if available."}
        }
      },
      {
        "id": "PlatformOs",
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The operating system the browser is running on.",
        "enum": ["mac", "win", "android", "cros", "linux", "openbsd"]
      },
      {
        "id": "PlatformArch",
        "type": "string",
        "enum": ["arm", "x86-32", "x86-64"],
        "allowedContexts": ["content", "devtools"],
        "description": "The machine's processor architecture."
      },
      {
        "id": "PlatformInfo",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object containing information about the current platform.",
        "properties": {
          "os": {
            "$ref": "PlatformOs",
            "description": "The operating system the browser is running on."
          },
          "arch": {
            "$ref": "PlatformArch",
            "description": "The machine's processor architecture."
          },
          "nacl_arch" : {
            "unsupported": true,
            "description": "The native client architecture. This may be different from arch on some platforms.",
            "$ref": "PlatformNaclArch"
          }
        }
      },
      {
        "id": "BrowserInfo",
        "type": "object",
        "description": "An object containing information about the current browser.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the browser, for example 'Firefox'."
          },
          "vendor": {
            "type": "string",
            "description": "The name of the browser vendor, for example 'Mozilla'."
          },
          "version": {
            "type": "string",
            "description": "The browser's version, for example '42.0.0' or '0.8.1pre'."
          },
          "buildID": {
            "type": "string",
            "description": "The browser's build ID/date, for example '20160101'."
          }
        }
      },
      {
        "id": "RequestUpdateCheckStatus",
        "type": "string",
        "enum": ["throttled", "no_update", "update_available"],
        "allowedContexts": ["content", "devtools"],
        "description": "Result of the update check."
      },
      {
        "id": "OnInstalledReason",
        "type": "string",
        "enum": ["install", "update", "browser_update"],
        "allowedContexts": ["content", "devtools"],
        "description": "The reason that this event is being dispatched."
      },
      {
        "id": "OnRestartRequiredReason",
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.",
        "enum": ["app_update", "os_update", "periodic"]
      }
    ],
    "properties": {
      "lastError": {
        "type": "object",
        "optional": true,
        "allowedContexts": ["content", "devtools"],
        "description": "This will be defined during an API method callback if there was an error",
        "properties": {
          "message": {
            "optional": true,
            "type": "string",
            "description": "Details about the error which occurred."
          }
        },
        "additionalProperties": {
          "type": "any"
        }
      },
      "id": {
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The ID of the extension/app."
      }
    },
    "functions": [
      {
        "name": "getBackgroundPage",
        "type": "function",
        "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "backgroundPage",
                "optional": true,
                "type": "object",
                "isInstanceOf": "Window",
                "additionalProperties": { "type": "any" },
                "description": "The JavaScript 'window' object for the background page."
              }
            ]
          }
        ]
      },
      {
        "name": "openOptionsPage",
        "type": "function",
        "description": "<p>Open your Extension's options page, if possible.</p><p>The precise behavior may depend on your manifest's <code>$(topic:optionsV2)[options_ui]</code> or <code>$(topic:options)[options_page]</code> key, or what the browser happens to support at the time.</p><p>If your Extension does not declare an options page, or the browser failed to create one for some other reason, the callback will set $(ref:lastError).</p>",
        "async": "callback",
        "parameters": [{
          "type": "function",
          "name": "callback",
          "parameters": [],
          "optional": true
        }]
      },
      {
        "name": "getManifest",
        "allowedContexts": ["content", "devtools"],
        "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full $(topic:manifest)[manifest file].",
        "type": "function",
        "parameters": [],
        "returns": {
          "type": "object",
          "properties": {},
          "additionalProperties": { "type": "any" },
          "description": "The manifest details."
        }
      },
      {
        "name": "getURL",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.",
        "parameters": [
          {
            "type": "string",
            "name": "path",
            "description": "A path to a resource within an app/extension expressed relative to its install directory."
          }
        ],
        "returns": {
          "type": "string",
          "description": "The fully-qualified URL to the resource."
        }
      },
      {
        "name": "setUninstallURL",
        "type": "function",
        "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 255 characters.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "url",
            "maxLength": 255,
            "description": "URL to be opened after the extension is uninstalled. This URL must have an http: or https: scheme. Set an empty string to not open a new tab upon uninstallation."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "description": "Called when the uninstall URL is set. If the given URL is invalid, $(ref:runtime.lastError) will be set.",
            "parameters": []
          }
        ]
      },
      {
        "name": "reload",
        "description": "Reloads the app or extension.",
        "type": "function",
        "parameters": []
      },
      {
        "name": "requestUpdateCheck",
        "unsupported": true,
        "type": "function",
        "description": "Requests an update check for this app/extension.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "status",
                "$ref": "RequestUpdateCheckStatus",
                "description": "Result of the update check."
              },
              {
                "name": "details",
                "type": "object",
                "optional": true,
                "properties": {
                  "version": {
                    "type": "string",
                    "description": "The version of the available update."
                  }
                },
                "description": "If an update is available, this contains more information about the available update."
              }
            ]
          }
        ]
      },
      {
        "name": "restart",
        "unsupported": true,
        "description": "Restart the device when the app runs in kiosk mode. Otherwise, it's no-op.",
        "type": "function",
        "parameters": []
      },
      {
        "name": "connect",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and $(topic:manifest/externally_connectable)[web messaging]. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).",
        "parameters": [
          {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."},
          {
            "type": "object",
            "name": "connectInfo",
            "properties": {
              "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for processes that are listening for the connection event." },
              "includeTlsChannelId": { "type": "boolean", "optional": true, "description": "Whether the TLS channel ID will be passed into onConnectExternal for processes that are listening for the connection event." }
            },
            "optional": true
          }
        ],
        "returns": {
          "$ref": "Port",
          "description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. "
        }
      },
      {
        "name": "connectNative",
        "type": "function",
        "description": "Connects to a native application in the host machine.",
        "permissions": ["nativeMessaging"],
        "parameters": [
          {
            "type": "string",
            "name": "application",
            "description": "The name of the registered application to connect to."
          }
        ],
        "returns": {
          "$ref": "Port",
          "description": "Port through which messages can be sent and received with the application"
        }
      },
      {
        "name": "sendMessage",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "allowedContexts": ["content", "devtools", "proxy"],
        "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
        "async": "responseCallback",
        "parameters": [
          {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."},
          { "type": "any", "name": "message" },
          {
            "type": "object",
            "name": "options",
            "properties": {
              "includeTlsChannelId": { "type": "boolean", "optional": true, "unsupported": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." },
              "toProxyScript": { "type": "boolean", "optional": true, "description": "If true, the message will be directed to the extension's proxy sandbox."}
            },
            "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 extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
              }
            ]
          }
        ]
      },
      {
        "name": "sendNativeMessage",
        "type": "function",
        "description": "Send a single message to a native application.",
        "permissions": ["nativeMessaging"],
        "async": "responseCallback",
        "parameters": [
          {
            "name": "application",
            "description": "The name of the native messaging host.",
            "type": "string"
          },
          {
            "name": "message",
            "description": "The message that will be passed to the native messaging host.",
            "type": "any"
          },
          {
            "type": "function",
            "name": "responseCallback",
            "optional": true,
            "parameters": [
              {
                "name": "response",
                "type": "any",
                "description": "The response message sent by the native messaging host. If an error occurs while connecting to the native messaging host, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
              }
            ]
          }
        ]
      },
      {
        "name": "getBrowserInfo",
        "type": "function",
        "description": "Returns information about the current browser.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "description": "Called with results",
            "parameters": [
              {
                "name": "browserInfo",
                "$ref": "BrowserInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "getPlatformInfo",
        "type": "function",
        "description": "Returns information about the current platform.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "description": "Called with results",
            "parameters": [
              {
                "name": "platformInfo",
                "$ref": "PlatformInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "getPackageDirectoryEntry",
        "unsupported": true,
        "type": "function",
        "description": "Returns a DirectoryEntry for the package directory.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "directoryEntry",
                "type": "object",
                "additionalProperties": { "type": "any" },
                "isInstanceOf": "DirectoryEntry"
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onStartup",
        "type": "function",
        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired for incognito profiles."
      },
      {
        "name": "onInstalled",
        "type": "function",
        "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "reason": {
                "$ref": "OnInstalledReason",
                "description": "The reason that this event is being dispatched."
              },
              "previousVersion": {
                "type": "string",
                "optional": true,
                "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'."
              },
              "temporary": {
                "type": "boolean",
                "description": "Indicates whether the addon is installed as a temporary extension."
              },
              "id": {
                "type": "string",
                "optional": true,
                "unsupported": true,
                "description": "Indicates the ID of the imported shared module extension which updated. This is present only if 'reason' is 'shared_module_update'."
              }
            }
          }
        ]
      },
      {
        "name": "onSuspend",
        "unsupported": true,
        "type": "function",
        "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. "
      },
      {
        "name": "onSuspendCanceled",
        "unsupported": true,
        "type": "function",
        "description": "Sent after onSuspend to indicate that the app won't be unloaded after all."
      },
      {
        "name": "onUpdateAvailable",
        "type": "function",
        "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call $(ref:runtime.reload). If your extension is using a persistent background page, the background page of course never gets unloaded, so unless you call $(ref:runtime.reload) manually in response to this event the update will not get installed until the next time the browser itself restarts. If no handlers are listening for this event, and your extension has a persistent background page, it behaves as if $(ref:runtime.reload) is called in response to this event.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "version": {
                "type": "string",
                "description": "The version number of the available update."
              }
            },
            "additionalProperties": { "type": "any" },
            "description": "The manifest details of the available update."
          }
        ]
      },
      {
        "name": "onBrowserUpdateAvailable",
        "unsupported": true,
        "type": "function",
        "description": "Fired when an update for the browser is available, but isn't installed immediately because a browser restart is required.",
        "deprecated": "Please use $(ref:runtime.onRestartRequired).",
        "parameters": []
      },
      {
        "name": "onConnect",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Fired when a connection is made from either an extension process or a content script.",
        "parameters": [
          {"$ref": "Port", "name": "port"}
        ]
      },
      {
        "name": "onConnectExternal",
        "type": "function",
        "description": "Fired when a connection is made from another extension.",
        "parameters": [
          {"$ref": "Port", "name": "port"}
        ]
      },
      {
        "name": "onMessage",
        "type": "function",
        "allowedContexts": ["content", "devtools", "proxy"],
        "description": "Fired when a message is sent from either an extension process or a content script.",
        "parameters": [
          {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},
          {"name": "sender", "$ref": "MessageSender" },
          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
        ],
        "returns": {
          "type": "boolean",
          "optional": true,
          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
        }
      },
      {
        "name": "onMessageExternal",
        "type": "function",
        "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.",
        "parameters": [
          {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},
          {"name": "sender", "$ref": "MessageSender" },
          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
        ],
        "returns": {
          "type": "boolean",
          "optional": true,
          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
        }
      },
      {
        "name": "onRestartRequired",
        "unsupported": true,
        "type": "function",
        "description": "Fired when an app or the device that it runs on needs to be restarted. The app should close all its windows at its earliest convenient time to let the restart to happen. If the app does nothing, a restart will be enforced after a 24-hour grace period has passed. Currently, this event is only fired for Chrome OS kiosk apps.",
        "parameters": [
          {
            "$ref": "OnRestartRequiredReason",
            "name": "reason",
            "description": "The reason that the event is being dispatched."
          }
        ]
      }
    ]
  }
]
PK
!<ZFu((6chrome/toolkit/content/extensions/schemas/storage.json// 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": "storage",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "Use the <code>browser.storage</code> API to store, retrieve, and track changes to user data.",
    "permissions": ["storage"],
    "types": [
      {
        "id": "StorageChange",
        "type": "object",
        "properties": {
          "oldValue": {
            "type": "any",
            "description": "The old value of the item, if there was an old value.",
            "optional": true
          },
          "newValue": {
            "type": "any",
            "description": "The new value of the item, if there is a new value.",
            "optional": true
          }
        }
      },
      {
        "id": "StorageArea",
        "type": "object",
        "functions": [
          {
            "name": "get",
            "type": "function",
            "description": "Gets one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } },
                  {
                    "type": "object",
                    "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
                    "additionalProperties": { "type": "any" }
                  }
                ],
                "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object).  An empty list or object will return an empty result object.  Pass in <code>null</code> to get the entire contents of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "items",
                    "type": "object",
                    "additionalProperties": { "type": "any" },
                    "description": "Object with items in their key-value mappings."
                  }
                ]
              }
            ]
          },
          {
            "name": "getBytesInUse",
            "unsupported": true,
            "type": "function",
            "description": "Gets the amount of space (in bytes) being used by one or more items.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } }
                ],
                "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "bytesInUse",
                    "type": "integer",
                    "description": "Amount of space being used in storage, in bytes."
                  }
                ]
              }
            ]
          },
          {
            "name": "set",
            "type": "function",
            "description": "Sets multiple items.",
            "async": "callback",
            "parameters": [
              {
                "name": "items",
                "type": "object",
                "additionalProperties": { "type": "any" },
                "description": "<p>An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.</p><p>Primitive values such as numbers will serialize as expected. Values with a <code>typeof</code> <code>\"object\"</code> and <code>\"function\"</code> will typically serialize to <code>{}</code>, with the exception of <code>Array</code> (serializes as expected), <code>Date</code>, and <code>Regex</code> (serialize using their <code>String</code> representation).</p>"
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "remove",
            "type": "function",
            "description": "Removes one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  {"type": "string"},
                  {"type": "array", "items": {"type": "string"}}
                ],
                "description": "A single key or a list of keys for items to remove."
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "clear",
            "type": "function",
            "description": "Removes all items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onChanged",
        "type": "function",
        "description": "Fired when one or more items change.",
        "parameters": [
          {
            "name": "changes",
            "type": "object",
            "additionalProperties": { "$ref": "StorageChange" },
            "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
          },
          {
            "name": "areaName",
            "type": "string",
            "description": "The name of the storage area (<code>\"sync\"</code>, <code>\"local\"</code> or <code>\"managed\"</code>) the changes are for."
          }
        ]
      }
    ],
    "properties": {
      "sync": {
        "$ref": "StorageArea",
        "description": "Items in the <code>sync</code> storage area are synced by the browser.",
        "properties": {
          "QUOTA_BYTES": {
            "value": 102400,
            "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
          },
          "QUOTA_BYTES_PER_ITEM": {
            "value": 8192,
            "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $(ref:runtime.lastError)."
          },
          "MAX_ITEMS": {
            "value": 512,
            "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $(ref:runtime.lastError)."
          },
          "MAX_WRITE_OPERATIONS_PER_HOUR": {
            "value": 1800,
            "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each hour. This is 1 every 2 seconds, a lower ceiling than the short term higher writes-per-minute limit.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
          },
          "MAX_WRITE_OPERATIONS_PER_MINUTE": {
            "value": 120,
            "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each minute. This is 2 per second, providing higher throughput than writes-per-hour over a shorter period of time.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
          },
          "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
            "value": 1000000,
            "deprecated": "The storage.sync API no longer has a sustained write operation quota.",
            "description": ""
          }
        }
      },
      "local": {
        "$ref": "StorageArea",
        "description": "Items in the <code>local</code> storage area are local to each machine.",
        "properties": {
          "QUOTA_BYTES": {
            "value": 5242880,
            "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
          }
        }
      },
      "managed": {
        "unsupported": true,
        "$ref": "StorageArea",
        "description": "Items in the <code>managed</code> storage area are set by the domain administrator, and are read-only for the extension; trying to modify this namespace results in an error."
      }
    }
  }
]
PK
!<O63chrome/toolkit/content/extensions/schemas/test.json// 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": "test",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "none",
    "functions": [
      {
        "name": "notifyFail",
        "type": "function",
        "description": "Notifies the browser process that test code running in the extension failed.  This is only used for internal unit testing.",
        "parameters": [
          {"type": "string", "name": "message"}
        ]
      },
      {
        "name": "notifyPass",
        "type": "function",
        "description": "Notifies the browser process that test code running in the extension passed.  This is only used for internal unit testing.",
        "parameters": [
          {"type": "string", "name": "message", "optional": true}
        ]
      },
      {
        "name": "log",
        "type": "function",
        "description": "Logs a message during internal unit testing.",
        "parameters": [
          {"type": "string", "name": "message"}
        ]
      },
      {
        "name": "sendMessage",
        "type": "function",
        "description": "Sends a string message to the browser process, generating a Notification that C++ test code can wait for.",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          {"type": "any", "name": "arg1", "optional": true},
          {"type": "any", "name": "arg2", "optional": true}
        ]
      },
      {
        "name": "fail",
        "type": "function",
        "parameters": [
          {"type": "any", "name": "message", "optional": true}
        ]
      },
      {
        "name": "succeed",
        "type": "function",
        "parameters": [
          {"type": "any", "name": "message", "optional": true}
        ]
      },
      {
        "name": "assertTrue",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          {"name": "test", "type": "any", "optional": true},
          {"type": "string", "name": "message", "optional": true}
        ]
      },
      {
        "name": "assertFalse",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          {"name": "test", "type": "any", "optional": true},
          {"type": "string", "name": "message", "optional": true}
        ]
      },
      {
        "name": "assertBool",
        "type": "function",
        "unsupported": true,
        "parameters": [
          {
            "name": "test",
            "choices": [
              {"type": "string"},
              {"type": "boolean"}
            ]
          },
          {"type": "boolean", "name": "expected"},
          {"type": "string", "name": "message", "optional": true}
        ]
      },
      {
        "name": "checkDeepEq",
        "type": "function",
        "unsupported": true,
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          {"type": "any", "name": "expected"},
          {"type": "any", "name": "actual"}
        ]
      },
      {
        "name": "assertEq",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          {"type": "any", "name": "expected", "optional": true},
          {"type": "any", "name": "actual", "optional": true},
          {"type": "string", "name": "message", "optional": true}
        ]
      },
      {
        "name": "assertNoLastError",
        "type": "function",
        "unsupported": true,
        "parameters": []
      },
      {
        "name": "assertLastError",
        "type": "function",
        "unsupported": true,
        "parameters": [
          {"type": "string", "name": "expectedError"}
        ]
      },
      {
        "name": "assertRejects",
        "type": "function",
        "async": true,
        "parameters": [
          {
            "name": "promise",
            "$ref": "Promise"
          },
          {
            "name": "expectedError",
            "$ref": "ExpectedError",
            "optional": true
          },
          {
            "name": "message",
            "type": "string",
            "optional": true
          }
        ]
      },
      {
        "name": "assertThrows",
        "type": "function",
        "parameters": [
          {
            "name": "func",
            "type": "function"
          },
          {
            "name": "expectedError",
            "$ref": "ExpectedError",
            "optional": true
          },
          {
            "name": "message",
            "type": "string",
            "optional": true
          }
        ]
      }
    ],
    "types": [
      {
        "id": "ExpectedError",
        "choices": [
          {"type": "string"},
          {"type": "object", "isInstanceOf": "RegExp", "additionalProperties": true},
          {"type": "function"}
        ]
      },
      {
        "id": "Promise",
        "choices": [
          {
            "type": "object",
            "properties": {
              "then": {"type": "function"}
            },
            "additionalProperties": true
          },
          {
            "type": "object",
            "isInstanceOf": "Promise",
            "additionalProperties": true
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onMessage",
        "type": "function",
        "description": "Used to test sending messages to extensions.",
        "parameters": [
          {
            "type": "string",
            "name": "message"
          },
          {
            "type": "any",
            "name": "argument"
          }
        ]
      }
    ]
  }
]
PK
!<x554chrome/toolkit/content/extensions/schemas/theme.json// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.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": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [{
          "type": "string",
          "enum": [
            "theme"
          ]
        }]
      },
      {
        "id": "ThemeType",
        "type": "object",
        "properties": {
          "images": {
            "type": "object",
            "optional": true,
            "properties": {
              "additional_backgrounds": {
                "type": "array",
                "items": { "$ref": "ExtensionURL" },
                "optional": true
              },
              "headerURL": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "theme_frame": {
                "$ref": "ExtensionURL",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          },
          "colors": {
            "type": "object",
            "optional": true,
            "properties": {
              "accentcolor": {
                "type": "string",
                "optional": true
              },
              "frame": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "optional": true
              },
              "tab_text": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "optional": true
              },
              "textcolor": {
                "type": "string",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          },
          "icons": {
            "type": "object",
            "optional": true,
            "properties": {
              "back": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "forward": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "reload": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "stop": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "bookmark_star": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "bookmark_menu": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "downloads": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "home": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "app_menu": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "cut": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "copy": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "paste": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "new_window": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "new_private_window": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "save_page": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "print": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "history": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "full_screen": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "find": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "options": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "addons": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "developer": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "synced_tabs": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "open_file": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "sidebars": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "share_page": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "subscribe": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "text_encoding": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "email_link": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "forget": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "pocket": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "getmsg": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newmsg": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "address": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "reply": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "replyall": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "replylist": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "forwarding": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "delete": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "junk": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "file": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "nextUnread": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "prevUnread": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "mark": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "tag": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "compact": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "archive": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "chat": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "nextMsg": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "prevMsg": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "QFB": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "conversation": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newcard": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newlist": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "editcard": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newim": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "send": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "spelling": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "attach": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "security": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "save": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "quote": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "buddy": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "join_chat": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "chat_accounts": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "calendar": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "tasks": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "synchronize": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newevent": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "newtask": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "editevent": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "today": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "category": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "complete": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "priority": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "saveandclose": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "attendees": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "privacy": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "status": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "freebusy": {
                "$ref": "ExtensionURL",
                "optional": true
              },
              "timezones": {
                "$ref": "ExtensionURL",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          },
          "properties": {
            "type": "object",
            "optional": true,
            "properties": {
              "additional_backgrounds_alignment": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "bottom", "center", "left", "right", "top",
                    "center bottom", "center center", "center top",
                    "left bottom", "left center", "left top",
                    "right bottom", "right center", "right top"
                  ]
                },
                "optional": true
              },
              "additional_backgrounds_tiling": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": ["no-repeat", "repeat", "repeat-x", "repeat-y"]
                },
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "theme": {
            "optional": true,
            "$ref": "ThemeType"
          }
        }
      }
    ]
  },
  {
    "namespace": "theme",
    "description": "The theme API allows customizing of visual elements of the browser.",
    "permissions": ["theme"],
    "functions": [
      {
        "name": "update",
        "type": "function",
        "async": true,
        "description": "Make complete or partial updates to the theme. Resolves when the update has completed.",
        "parameters": [
          {
            "name": "details",
            "$ref": "manifest.ThemeType",
            "description": "The properties of the theme to update."
          }
        ]
      },
      {
        "name": "reset",
        "type": "function",
        "async": true,
        "description": "Removes the updates made to the theme.",
        "parameters": []
      }
    ]
  }
]
PK
!<D),,8chrome/toolkit/content/extensions/schemas/top_sites.json/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [{
          "type": "string",
          "enum": [
            "topSites"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "topSites",
    "description": "Use the chrome.topSites API to access the top sites that are displayed on the new tab page. ",
    "permissions": ["topSites"],
    "types": [
      {
        "id": "MostVisitedURL",
        "type": "object",
        "description": "An object encapsulating a most visited URL, such as the URLs on the new tab page.",
        "properties": {
          "url": {
            "type": "string",
            "description": "The most visited URL."
          },
          "title": {
            "type": "string",
            "optional": true,
            "description": "The title of the page."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Gets a list of top sites.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "options",
            "properties": {
              "providers": {
                "type": "array",
                "items": { "type": "string" },
                "description": "Which providers to get top sites from. Possible values are \"places\" and \"activityStream\".",
                "optional": true
              }
            },
            "optional": true
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": {
                  "$ref": "MostVisitedURL"
                }
              }
            ]
          }
        ]
      }
    ]
  }
]
PK
!<#PP4chrome/toolkit/content/extensions/schemas/types.json// 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": "types",
    "description": "Contains types used by other schemas.",
    "types": [
      {
        "id": "SettingScope",
        "type": "string",
        "enum": ["regular", "regular_only", "incognito_persistent", "incognito_session_only"],
        "description": "The scope of the Setting. One of<ul><li><var>regular</var>: setting for the regular profile (which is inherited by the incognito profile if not overridden elsewhere),</li><li><var>regular_only</var>: setting for the regular profile only (not inherited by the incognito profile),</li><li><var>incognito_persistent</var>: setting for the incognito profile that survives browser restarts (overrides regular preferences),</li><li><var>incognito_session_only</var>: setting for the incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular and incognito_persistent preferences).</li></ul> Only <var>regular</var> is supported by Firefox at this time."
      },
      {
        "id": "LevelOfControl",
        "type": "string",
        "enum": ["not_controllable", "controlled_by_other_extensions", "controllable_by_this_extension", "controlled_by_this_extension"],
        "description": "One of<ul><li><var>not_controllable</var>: cannot be controlled by any extension</li><li><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence</li><li><var>controllable_by_this_extension</var>: can be controlled by this extension</li><li><var>controlled_by_this_extension</var>: controlled by this extension</li></ul>"
      },
      {
        "id": "Setting",
        "type": "object",
        "functions": [
          {
            "name": "get",
            "type": "function",
            "description": "Gets the value of a setting.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to consider.",
                "properties": {
                  "incognito": {
                    "type": "boolean",
                    "optional": true,
                    "description": "Whether to return the value that applies to the incognito session (default false)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "parameters": [
                  {
                    "name": "details",
                    "type": "object",
                    "description": "Details of the currently effective value.",
                    "properties": {
                      "value": {
                        "description": "The value of the setting.",
                        "type": "any"
                      },
                      "levelOfControl": {
                        "$ref": "LevelOfControl",
                        "description": "The level of control of the setting."
                      },
                      "incognitoSpecific": {
                        "description": "Whether the effective value is specific to the incognito session.<br/>This property will <em>only</em> be present if the <var>incognito</var> property in the <var>details</var> parameter of <code>get()</code> was true.",
                        "type": "boolean",
                        "optional": true
                      }
                    }
                  }
                ]
              }
            ]
          },
          {
            "name": "set",
            "type": "function",
            "description": "Sets the value of a setting.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to change.",
                "properties": {
                  "value": {
                    "description": "The value of the setting. <br/>Note that every setting has a specific value type, which is described together with the setting. An extension should <em>not</em> set a value of a different type.",
                    "type": "any"
                  },
                  "scope": {
                    "$ref": "SettingScope",
                    "optional": true,
                    "description": "Where to set the setting (default: regular)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Called at the completion of the set operation.",
                "optional": true,
                "parameters": []
              }
            ]
          },
          {
            "name": "clear",
            "type": "function",
            "description": "Clears the setting, restoring any default value.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to clear.",
                "properties": {
                  "scope": {
                    "$ref": "SettingScope",
                    "optional": true,
                    "description": "Where to clear the setting (default: regular)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Called at the completion of the clear operation.",
                "optional": true,
                "parameters": []
              }
            ]
          }
        ],
        "events": [
          {
            "name": "onChange",
            "type": "function",
            "description": "Fired after the setting changes.",
            "unsupported": true,
            "parameters": [
              {
                "type": "object",
                "name": "details",
                "properties": {
                  "value": {
                    "description": "The value of the setting after the change.",
                    "type": "any"
                  },
                  "levelOfControl": {
                    "$ref": "LevelOfControl",
                    "description": "The level of control of the setting."
                  },
                  "incognitoSpecific": {
                    "description": "Whether the value that has changed is specific to the incognito session.<br/>This property will <em>only</em> be present if the user has enabled the extension in incognito mode.",
                    "type": "boolean",
                    "optional": true
                  }
                }
              }
            ]
          }
        ]
      }
    ]
  }
]
PK
!<8LL=chrome/toolkit/content/extensions/schemas/web_navigation.json// 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": "OptionalPermission",
        "choices": [{
          "type": "string",
          "enum": [
            "webNavigation"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "webNavigation",
    "description": "Use the <code>browser.webNavigation</code> API to receive notifications about the status of navigation requests in-flight.",
    "permissions": ["webNavigation"],
    "types": [
      {
        "id": "TransitionType",
        "type": "string",
        "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "start_page", "form_submit", "reload", "keyword", "keyword_generated"],
        "description": "Cause of the navigation. The same transition types as defined in the history API are used. These are the same transition types as defined in the $(topic:transition_types)[history API] except with <code>\"start_page\"</code> in place of <code>\"auto_toplevel\"</code> (for backwards compatibility)."
      },
      {
        "id": "TransitionQualifier",
        "type": "string",
        "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"]
      },
      {
        "id": "EventUrlFilters",
        "type": "object",
        "properties": {
          "url": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "events.UrlFilter" }
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getFrame",
        "type": "function",
        "description": "Retrieves information about the given frame. A frame refers to an &lt;iframe&gt; or a &lt;frame&gt; of a web page and is identified by a tab ID and a frame ID.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information about the frame to retrieve information about.",
            "properties": {
              "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab in which the frame is." },
              "processId": {"optional": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": { "type": "integer", "minimum": 0, "description": "The ID of the frame in the given tab." }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "type": "object",
                "name": "details",
                "optional": true,
                "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.",
                "properties": {
                  "errorOccurred": {
                    "optional": true,
                    "type": "boolean",
                    "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
                  },
                  "url": {
                    "type": "string",
                    "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
                  },
                  "tabId": {
                    "type": "integer",
                    "description": "The ID of the tab in which the frame is."
                  },
                  "frameId": {
                    "type": "integer",
                    "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
                  },
                  "parentFrameId": {
                    "type": "integer",
                    "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAllFrames",
        "type": "function",
        "description": "Retrieves information about all frames of a given tab.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information about the tab to retrieve all frames from.",
            "properties": {
              "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab." }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "array",
                "description": "A list of frames in the given tab, null if the specified tab ID is invalid.",
                "optional": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "errorOccurred": {
                      "optional": true,
                      "type": "boolean",
                      "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
                    },
                    "processId": {
                      "unsupported": true,
                      "type": "integer",
                      "description": "The ID of the process runs the renderer for this tab."
                    },
                    "tabId": {
                      "type": "integer",
                      "description": "The ID of the tab in which the frame is."
                    },
                    "frameId": {
                      "type": "integer",
                      "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
                    },
                    "parentFrameId": {
                      "type": "integer",
                      "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
                    },
                    "url": {
                      "type": "string",
                      "description": "The URL currently associated with this frame."
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onBeforeNavigate",
        "type": "function",
        "description": "Fired when a navigation is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation is about to occur."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique for a given tab and process."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."},
              "timeStamp": {"type": "number", "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCommitted",
        "type": "function",
        "description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."},
              "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}},
              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onDOMContentLoaded",
        "type": "function",
        "description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "timeStamp": {"type": "number", "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCompleted",
        "type": "function",
        "description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "timeStamp": {"type": "number", "description": "The time when the document finished loading, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onErrorOccurred",
        "type": "function",
        "description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "error": {"unsupported": true, "type": "string", "description": "The error description."},
              "timeStamp": {"type": "number", "description": "The time when the error occurred, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCreatedNavigationTarget",
        "type": "function",
        "description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "sourceTabId": {"type": "integer", "description": "The ID of the tab in which the navigation is triggered."},
              "sourceProcessId": {"type": "integer", "description": "The ID of the process runs the renderer for the source tab."},
              "sourceFrameId": {"type": "integer", "description": "The ID of the frame with sourceTabId in which the navigation is triggered. 0 indicates the main frame."},
              "url": {"type": "string", "description": "The URL to be opened in the new window."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the url is opened"},
              "timeStamp": {"type": "number", "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onReferenceFragmentUpdated",
        "type": "function",
        "description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."},
              "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}},
              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onTabReplaced",
        "type": "function",
        "description": "Fired when the contents of the tab is replaced by a different (usually previously pre-rendered) tab.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "replacedTabId": {"type": "integer", "description": "The ID of the tab that was replaced."},
              "tabId": {"type": "integer", "description": "The ID of the tab that replaced the old tab."},
              "timeStamp": {"type": "number", "description": "The time when the replacement happened, in milliseconds since the epoch."}
            }
          }
        ]
      },
      {
        "name": "onHistoryStateUpdated",
        "type": "function",
        "description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
              "url": {"type": "string"},
              "processId": {"unsupported": true, "type": "integer", "description": "The ID of the process runs the renderer for this tab."},
              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
              "transitionType": {"unsupported": true, "$ref": "TransitionType", "description": "Cause of the navigation."},
              "transitionQualifiers": {"unsupported": true, "type": "array", "description": "A list of transition qualifiers.", "items": {"$ref": "TransitionQualifier"}},
              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      }
    ]
  }
]
PK
!<2٦:chrome/toolkit/content/extensions/schemas/web_request.json// 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": "OptionalPermission",
        "choices": [{
          "type": "string",
          "enum": [
            "webRequest",
            "webRequestBlocking"
          ]
        }]
      }
    ]
  },
  {
    "namespace": "webRequest",
    "description": "Use the <code>browser.webRequest</code> API to observe and analyze traffic and to intercept, block, or modify requests in-flight.",
    "permissions": ["webRequest"],
    "properties": {
      "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES": {
        "value": 20,
        "description": "The maximum number of times that <code>handlerBehaviorChanged</code> can be called per 10 minute sustained interval. <code>handlerBehaviorChanged</code> is an expensive function call that shouldn't be called often."
      }
    },
    "types": [
      {
        "id": "ResourceType",
        "type": "string",
        "enum": [
          "main_frame",
          "sub_frame",
          "stylesheet",
          "script",
          "image",
          "object",
          "object_subrequest",
          "xmlhttprequest",
          "xbl",
          "xslt",
          "ping",
          "beacon",
          "xml_dtd",
          "font",
          "media",
          "websocket",
          "csp_report",
          "imageset",
          "web_manifest",
          "other"
        ]
      },
      {
        "id": "OnBeforeRequestOptions",
        "type": "string",
        "enum": ["blocking", "requestBody"]
      },
      {
        "id": "OnBeforeSendHeadersOptions",
        "type": "string",
        "enum": ["requestHeaders", "blocking"]
      },
      {
        "id": "OnSendHeadersOptions",
        "type": "string",
        "enum": ["requestHeaders"]
      },
      {
        "id": "OnHeadersReceivedOptions",
        "type": "string",
        "enum": ["blocking", "responseHeaders"]
      },
      {
        "id": "OnAuthRequiredOptions",
        "type": "string",
        "enum": ["responseHeaders", "blocking", "asyncBlocking"]
      },
      {
        "id": "OnResponseStartedOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "OnBeforeRedirectOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "OnCompletedOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "RequestFilter",
        "type": "object",
        "description": "An object describing filters to apply to webRequest events.",
        "properties": {
          "urls": {
            "type": "array",
            "description": "A list of URLs or URL patterns. Requests that cannot match any of the URLs will be filtered out.",
            "items": { "type": "string" },
            "minItems": 1
          },
          "types": {
            "type": "array",
            "optional": true,
            "description": "A list of request types. Requests that cannot match any of the types will be filtered out.",
            "items": { "$ref": "ResourceType" },
            "minItems": 1
          },
          "tabId": { "type": "integer", "optional": true },
          "windowId": { "type": "integer", "optional": true }
        }
      },
      {
        "id": "HttpHeaders",
        "type": "array",
        "description": "An array of HTTP headers. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>.",
        "items": {
          "type": "object",
          "properties": {
            "name": {"type": "string", "description": "Name of the HTTP header."},
            "value": {"type": "string", "optional": true, "description": "Value of the HTTP header if it can be represented by UTF-8."},
            "binaryValue": {
              "type": "array",
              "optional": true,
              "description": "Value of the HTTP header if it cannot be represented by UTF-8, stored as individual byte values (0..255).",
              "items": {"type": "integer"}
            }
          }
        }
      },
      {
        "id": "BlockingResponse",
        "type": "object",
        "description": "Returns value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.",
        "properties": {
          "cancel": {
            "type": "boolean",
            "optional": true,
            "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent."
          },
          "redirectUrl": {
            "type": "string",
            "optional": true,
            "description": "Only used as a response to the onBeforeRequest and onHeadersReceived events. If set, the original request is prevented from being sent/completed and is instead redirected to the given URL. Redirections to non-HTTP schemes such as data: are allowed. Redirects initiated by a redirect action use the original request method for the redirect, with one exception: If the redirect is initiated at the onHeadersReceived stage, then the redirect will be issued using the GET method."
          },
          "requestHeaders": {
            "$ref": "HttpHeaders",
            "optional": true,
            "description": "Only used as a response to the onBeforeSendHeaders event. If set, the request is made with these request headers instead."
          },
          "responseHeaders": {
            "$ref": "HttpHeaders",
            "optional": true,
            "description": "Only used as a response to the onHeadersReceived event. If set, the server is assumed to have responded with these response headers instead. Only return <code>responseHeaders</code> if you really want to modify the headers in order to limit the number of conflicts (only one extension may modify <code>responseHeaders</code> for each request)."
          },
          "authCredentials": {
            "type": "object",
            "description": "Only used as a response to the onAuthRequired event. If set, the request is made using the supplied credentials.",
            "optional": true,
            "properties": {
              "username": {"type": "string"},
              "password": {"type": "string"}
            }
          }
        }
      },
      {
        "id": "UploadData",
        "type": "object",
        "properties": {
          "bytes": {
            "type": "any",
            "optional": true,
            "description": "An ArrayBuffer with a copy of the data."
          },
          "file": {
            "type": "string",
            "optional": true,
            "description": "A string with the file's path and name."
          }
        },
        "description": "Contains data uploaded in a URL request."
      }
    ],
    "functions": [
      {
        "name": "handlerBehaviorChanged",
        "type": "function",
        "description": "Needs to be called when the behavior of the webRequest handlers has changed to prevent incorrect handling due to caching. This function call is expensive. Don't call it often.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onBeforeRequest",
        "type": "function",
        "description": "Fired when a request is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "requestBody": {
                "type": "object",
                "optional": true,
                "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.",
                "properties": {
                  "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."},
                  "formData": {
                    "type": "object",
                    "optional": true,
                    "description": "If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': ['value1', 'value2']}.",
                    "properties": {},
                    "additionalProperties": {
                      "type": "array",
                      "items": { "type": "string" }
                    }
                  },
                  "raw" : {
                    "type": "array",
                    "optional": true,
                    "items": {"$ref": "UploadData"},
                    "description": "If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array."
                  }
                }
              },
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeRequestOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onBeforeSendHeaders",
        "type": "function",
        "description": "Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any HTTP data is sent. ",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeSendHeadersOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onSendHeaders",
        "type": "function",
        "description": "Fired just before a request is going to be sent to the server (modifications of previous onBeforeSendHeaders callbacks are visible by the time onSendHeaders is fired).",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnSendHeadersOptions"
            }
          }
        ]
      },
      {
        "name": "onHeadersReceived",
        "type": "function",
        "description": "Fired when HTTP response headers of a request have been received.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."},
              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."},
              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
             }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnHeadersReceivedOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onAuthRequired",
        "type": "function",
        "description": "Fired when an authentication failure is received. The listener has three options: it can provide authentication credentials, it can cancel the request and display the error page, or it can take no action on the challenge. If bad user credentials are provided, this may be called multiple times for the same request.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."},
              "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true},
              "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}},
              "isProxy": {"type": "boolean", "description": "True for Proxy-Authenticate, false for WWW-Authenticate."},
              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
              "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."},
              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
            }
          },
          {
            "type": "function",
            "optional": true,
            "name": "callback",
            "parameters": [
              {"name": "response", "$ref": "BlockingResponse"}
            ]
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnAuthRequiredOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onResponseStarted",
        "type": "function",
        "description": "Fired when the first byte of the response body is received. For HTTP requests, this means that the status line and response headers are available.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
              "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnResponseStartedOptions"
            }
          }
        ]
      },
      {
        "name": "onBeforeRedirect",
        "type": "function",
        "description": "Fired when a server-initiated redirect is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
              "redirectUrl": {"type": "string", "description": "The new URL."},
              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this redirect."},
              "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeRedirectOptions"
            }
          }
        ]
      },
      {
        "name": "onCompleted",
        "type": "function",
        "description": "Fired when a request is completed.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
              "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnCompletedOptions"
            }
          }
        ]
      },
      {
        "name": "onErrorOccurred",
        "type": "function",
        "description": "Fired when an error occurs.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
              "url": {"type": "string"},
              "method": {"type": "string", "description": "Standard HTTP method."},
              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
              "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
              "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
              "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
              "error": {"type": "string", "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."}
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          }
        ]
      }
    ]
  }
]
PK
!<YOkz2chrome/toolkit/content/gfxsanity/gfxFrameScript.js/* eslint-env mozilla/frame-script */

var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const gfxFrameScript = {
  domUtils: null,

  init() {
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    let webProgress =  docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIWebProgress);
    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);

    this.domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils);

    webNav.loadURI("chrome://gfxsanity/content/sanitytest.html",
                   Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
                   null, null, null);

  },

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "MozAfterPaint":
        sendAsyncMessage("gfxSanity:ContentLoaded");
        removeEventListener("MozAfterPaint", this);
        break;
    }
  },

  isSanityTest(aUri) {
    if (!aUri) {
      return false;
    }

    return aUri.endsWith("/sanitytest.html");
  },

  onStateChange(webProgress, req, flags, status) {
    if (webProgress.isTopLevel &&
        (flags & Ci.nsIWebProgressListener.STATE_STOP) &&
        this.isSanityTest(req.name)) {

      webProgress.removeProgressListener(this);

      // If no paint is pending, then the test already painted
      if (this.domUtils.isMozAfterPaintPending) {
        addEventListener("MozAfterPaint", this);
      } else {
        sendAsyncMessage("gfxSanity:ContentLoaded");
      }
    }
  },

  // Needed to support web progress listener
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIWebProgressListener,
    Ci.nsISupportsWeakReference,
    Ci.nsIObserver,
  ]),
};

gfxFrameScript.init();
PK
!<#-3662chrome/toolkit/content/gfxsanity/sanityparent.html<html>
  <head>
  </head>
  <body>

  </body>
</html>
PK
!<-0chrome/toolkit/content/gfxsanity/sanitytest.html<html>
    <body>
        <div style="width:64px; height:64px; background-color:red;"></div>
        <video src="videotest.mp4"></video>
    </body>
</html>
PK
!<	.chrome/toolkit/content/gfxsanity/videotest.mp4 ftypisomisomiso2avc1mp41freemdatEH, #x264 - core 142 r2431 a5831aa - H.264/MPEG-4 AVC codec - Copyleft 2003-2014 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00Ve+s|
kG3'e)`|Ʊjޯ8M[ELjл܎뚂T4!I
Ӻ!ǻMUlTbmoovlmvhd(@iodsOtrak\tkhd(@@@mdia mdhdU-hdlrvideVideoHandler:minfvmhd$dinfdrefurl stblstsdavc1@@HH0avcCd
gd
D&<HXh"sttsstscstsz
stco0_udtaWmeta!hdlrmdirappl*ilst"toodataLavf56.1.0PK
!<4chrome/toolkit/content/global/BrowserElementChild.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
Cu.import("resource://gre/modules/Services.jsm");

function debug(msg) {
  //dump("BrowserElementChild - " + msg + "\n");
}

// NB: this must happen before we process any messages from
// mozbrowser API clients.
docShell.isActive = true;

function parentDocShell(docshell) {
  if (!docshell) {
    return null;
  }
  let treeitem = docshell.QueryInterface(Ci.nsIDocShellTreeItem);
  return treeitem.parent ? treeitem.parent.QueryInterface(Ci.nsIDocShell) : null;
}

function isTopBrowserElement(docShell) {
  while (docShell) {
    docShell = parentDocShell(docShell);
    if (docShell && docShell.isMozBrowser) {
      return false;
    }
  }
  return true;
}

var BrowserElementIsReady;

debug(`Might load BE scripts: BEIR: ${BrowserElementIsReady}`);
if (!BrowserElementIsReady) {
  debug("Loading BE scripts")
  if (!("BrowserElementIsPreloaded" in this)) {
    if(Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
      // general content apps
      if (isTopBrowserElement(docShell)) {
        Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
      }
    } else {
      // rocketbar in system app and other in-process case (ex. B2G desktop client)
      Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
    }

    Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
  }

  function onDestroy() {
    removeMessageListener("browser-element-api:destroy", onDestroy);

    if (api) {
      api.destroy();
    }
    if ("CopyPasteAssistent" in this) {
      CopyPasteAssistent.destroy();
    }

    BrowserElementIsReady = false;
  }
  addMessageListener("browser-element-api:destroy", onDestroy);

  BrowserElementIsReady = true;
} else {
  debug("BE already loaded, abort");
}

sendAsyncMessage('browser-element-api:call', { 'msg_name': 'hello' });
PK
!<z(;chrome/toolkit/content/global/BrowserElementChildPreload.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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) {
  // dump("BrowserElementChildPreload - " + msg + "\n");
}

debug("loaded");

var BrowserElementIsReady;

var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
                                  "resource://gre/modules/ManifestFinder.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
                                  "resource://gre/modules/ManifestObtainer.jsm");


var kLongestReturnedString = 128;

var Timer = Components.Constructor("@mozilla.org/timer;1",
                                   "nsITimer",
                                   "initWithCallback");

function sendAsyncMsg(msg, data) {
  // Ensure that we don't send any messages before BrowserElementChild.js
  // finishes loading.
  if (!BrowserElementIsReady) {
    return;
  }

  if (!data) {
    data = { };
  }

  data.msg_name = msg;
  sendAsyncMessage('browser-element-api:call', data);
}

function sendSyncMsg(msg, data) {
  // Ensure that we don't send any messages before BrowserElementChild.js
  // finishes loading.
  if (!BrowserElementIsReady) {
    return;
  }

  if (!data) {
    data = { };
  }

  data.msg_name = msg;
  return sendSyncMessage('browser-element-api:call', data);
}

var CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';

var OBSERVED_EVENTS = [
  'xpcom-shutdown',
  'audio-playback',
  'activity-done',
  'will-launch-app'
];

var LISTENED_EVENTS = [
  { type: "DOMTitleChanged", useCapture: true, wantsUntrusted: false },
  { type: "DOMLinkAdded", useCapture: true, wantsUntrusted: false },
  { type: "MozScrolledAreaChanged", useCapture: true, wantsUntrusted: false },
  { type: "MozDOMFullscreen:Request", useCapture: true, wantsUntrusted: false },
  { type: "MozDOMFullscreen:NewOrigin", useCapture: true, wantsUntrusted: false },
  { type: "MozDOMFullscreen:Exit", useCapture: true, wantsUntrusted: false },
  { type: "DOMMetaAdded", useCapture: true, wantsUntrusted: false },
  { type: "DOMMetaChanged", useCapture: true, wantsUntrusted: false },
  { type: "DOMMetaRemoved", useCapture: true, wantsUntrusted: false },
  { type: "scrollviewchange", useCapture: true, wantsUntrusted: false },
  { type: "click", useCapture: false, wantsUntrusted: false },
  // This listens to unload events from our message manager, but /not/ from
  // the |content| window.  That's because the window's unload event doesn't
  // bubble, and we're not using a capturing listener.  If we'd used
  // useCapture == true, we /would/ hear unload events from the window, which
  // is not what we want!
  { type: "unload", useCapture: false, wantsUntrusted: false },
];

// We are using the system group for those events so if something in the
// content called .stopPropagation() this will still be called.
var LISTENED_SYSTEM_EVENTS = [
  { type: "DOMWindowClose", useCapture: false },
  { type: "DOMWindowCreated", useCapture: false },
  { type: "DOMWindowResize", useCapture: false },
  { type: "contextmenu", useCapture: false },
  { type: "scroll", useCapture: false },
];

/**
 * The BrowserElementChild implements one half of <iframe mozbrowser>.
 * (The other half is, unsurprisingly, BrowserElementParent.)
 *
 * This script is injected into an <iframe mozbrowser> via
 * nsIMessageManager::LoadFrameScript().
 *
 * Our job here is to listen for events within this frame and bubble them up to
 * the parent process.
 */

var global = this;

function BrowserElementChild() {
  // Maps outer window id --> weak ref to window.  Used by modal dialog code.
  this._windowIDDict = {};

  this._nextPaintHandler = null;

  this._isContentWindowCreated = false;

  this._init();
};

BrowserElementChild.prototype = {

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  _init: function() {
    debug("Starting up.");

    BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);

    docShell.QueryInterface(Ci.nsIWebProgress)
            .addProgressListener(this._progressListener,
                                 Ci.nsIWebProgress.NOTIFY_LOCATION |
                                 Ci.nsIWebProgress.NOTIFY_SECURITY |
                                 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);

    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
    if (!webNavigation.sessionHistory) {
      webNavigation.sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
                                       .createInstance(Ci.nsISHistory);
    }

    // This is necessary to get security web progress notifications.
    var securityUI = Cc['@mozilla.org/secure_browser_ui;1']
                       .createInstance(Ci.nsISecureBrowserUI);
    securityUI.init(content);

    // A cache of the menuitem dom objects keyed by the id we generate
    // and pass to the embedder
    this._ctxHandlers = {};
    // Counter of contextmenu events fired
    this._ctxCounter = 0;

    this._shuttingDown = false;

    LISTENED_EVENTS.forEach(event => {
      addEventListener(event.type, this, event.useCapture, event.wantsUntrusted);
    });

    // Registers a MozAfterPaint handler for the very first paint.
    this._addMozAfterPaintHandler(function () {
      sendAsyncMsg('firstpaint');
    });

    addMessageListener("browser-element-api:call", this);

    let els = Cc["@mozilla.org/eventlistenerservice;1"]
                .getService(Ci.nsIEventListenerService);
    LISTENED_SYSTEM_EVENTS.forEach(event => {
      els.addSystemEventListener(global, event.type, this, event.useCapture);
    });

    OBSERVED_EVENTS.forEach((aTopic) => {
      Services.obs.addObserver(this, aTopic);
    });
  },

  /**
   * Shut down the frame's side of the browser API.  This is called when:
   *   - our TabChildGlobal starts to die
   *   - the content is moved to frame without the browser API
   * This is not called when the page inside |content| unloads.
   */
  destroy: function() {
    debug("Destroying");
    this._shuttingDown = true;

    BrowserElementPromptService.unmapWindowToBrowserElementChild(content);

    docShell.QueryInterface(Ci.nsIWebProgress)
            .removeProgressListener(this._progressListener);

    LISTENED_EVENTS.forEach(event => {
      removeEventListener(event.type, this, event.useCapture, event.wantsUntrusted);
    });

    this._deactivateNextPaintListener();

    removeMessageListener("browser-element-api:call", this);

    let els = Cc["@mozilla.org/eventlistenerservice;1"]
                .getService(Ci.nsIEventListenerService);
    LISTENED_SYSTEM_EVENTS.forEach(event => {
      els.removeSystemEventListener(global, event.type, this, event.useCapture);
    });

    OBSERVED_EVENTS.forEach((aTopic) => {
      Services.obs.removeObserver(this, aTopic);
    });
  },

  handleEvent: function(event) {
    switch (event.type) {
      case "DOMTitleChanged":
        this._titleChangedHandler(event);
        break;
      case "DOMLinkAdded":
        this._linkAddedHandler(event);
        break;
      case "MozScrolledAreaChanged":
        this._mozScrollAreaChanged(event);
        break;
      case "MozDOMFullscreen:Request":
        this._mozRequestedDOMFullscreen(event);
        break;
      case "MozDOMFullscreen:NewOrigin":
        this._mozFullscreenOriginChange(event);
        break;
      case "MozDOMFullscreen:Exit":
        this._mozExitDomFullscreen(event);
        break;
      case "DOMMetaAdded":
        this._metaChangedHandler(event);
        break;
      case "DOMMetaChanged":
        this._metaChangedHandler(event);
        break;
      case "DOMMetaRemoved":
        this._metaChangedHandler(event);
        break;
      case "scrollviewchange":
        this._ScrollViewChangeHandler(event);
        break;
      case "click":
        this._ClickHandler(event);
        break;
      case "unload":
        this.destroy(event);
        break;
      case "DOMWindowClose":
        this._windowCloseHandler(event);
        break;
      case "DOMWindowCreated":
        this._windowCreatedHandler(event);
        break;
      case "DOMWindowResize":
        this._windowResizeHandler(event);
        break;
      case "contextmenu":
        this._contextmenuHandler(event);
        break;
      case "scroll":
        this._scrollEventHandler(event);
        break;
    }
  },

  receiveMessage: function(message) {
    let self = this;

    let mmCalls = {
      "purge-history": this._recvPurgeHistory,
      "get-screenshot": this._recvGetScreenshot,
      "get-contentdimensions": this._recvGetContentDimensions,
      "send-mouse-event": this._recvSendMouseEvent,
      "send-touch-event": this._recvSendTouchEvent,
      "get-can-go-back": this._recvCanGoBack,
      "get-can-go-forward": this._recvCanGoForward,
      "go-back": this._recvGoBack,
      "go-forward": this._recvGoForward,
      "reload": this._recvReload,
      "stop": this._recvStop,
      "zoom": this._recvZoom,
      "unblock-modal-prompt": this._recvStopWaiting,
      "fire-ctx-callback": this._recvFireCtxCallback,
      "owner-visibility-change": this._recvOwnerVisibilityChange,
      "entered-fullscreen": this._recvEnteredFullscreen,
      "exit-fullscreen": this._recvExitFullscreen,
      "activate-next-paint-listener": this._activateNextPaintListener,
      "deactivate-next-paint-listener": this._deactivateNextPaintListener,
      "find-all": this._recvFindAll,
      "find-next": this._recvFindNext,
      "clear-match": this._recvClearMatch,
      "execute-script": this._recvExecuteScript,
      "get-web-manifest": this._recvGetWebManifest,
    }

    if (message.data.msg_name in mmCalls) {
      return mmCalls[message.data.msg_name].apply(self, arguments);
    }
  },

  _paintFrozenTimer: null,
  observe: function(subject, topic, data) {
    // Ignore notifications not about our document.  (Note that |content| /can/
    // be null; see bug 874900.)

    if (topic !== 'activity-done' &&
        topic !== 'audio-playback' &&
        topic !== 'will-launch-app' &&
        (!content || subject !== content.document)) {
      return;
    }
    if (topic == 'activity-done' && docShell !== subject)
      return;
    switch (topic) {
      case 'activity-done':
        sendAsyncMsg('activitydone', { success: (data == 'activity-success') });
        break;
      case 'audio-playback':
        if (subject === content) {
          sendAsyncMsg('audioplaybackchange', { _payload_: data });
        }
        break;
      case 'xpcom-shutdown':
        this._shuttingDown = true;
        break;
      case 'will-launch-app':
        // If the launcher is not visible, let's ignore the message.
        if (!docShell.isActive) {
          return;
        }

        // If this is not a content process, let's not freeze painting.
        if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_CONTENT) {
          return;
        }

        docShell.contentViewer.pausePainting();

        this._paintFrozenTimer && this._paintFrozenTimer.cancel();
        this._paintFrozenTimer = new Timer(this, 3000, Ci.nsITimer.TYPE_ONE_SHOT);
        break;
    }
  },

  notify: function(timer) {
    docShell.contentViewer.resumePainting();
    this._paintFrozenTimer.cancel();
    this._paintFrozenTimer = null;
  },

  get _windowUtils() {
    return content.document.defaultView
                  .QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIDOMWindowUtils);
  },

  _tryGetInnerWindowID: function(win) {
    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
    try {
      return utils.currentInnerWindowID;
    }
    catch(e) {
      return null;
    }
  },

  /**
   * Show a modal prompt.  Called by BrowserElementPromptService.
   */
  showModalPrompt: function(win, args) {
    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);

    args.windowID = { outer: utils.outerWindowID,
                      inner: this._tryGetInnerWindowID(win) };
    sendAsyncMsg('showmodalprompt', args);

    let returnValue = this._waitForResult(win);

    if (args.promptType == 'prompt' ||
        args.promptType == 'confirm' ||
        args.promptType == 'custom-prompt') {
      return returnValue;
    }
  },

  /**
   * Spin in a nested event loop until we receive a unblock-modal-prompt message for
   * this window.
   */
  _waitForResult: function(win) {
    debug("_waitForResult(" + win + ")");
    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);

    let outerWindowID = utils.outerWindowID;
    let innerWindowID = this._tryGetInnerWindowID(win);
    if (innerWindowID === null) {
      // I have no idea what waiting for a result means when there's no inner
      // window, so let's just bail.
      debug("_waitForResult: No inner window. Bailing.");
      return;
    }

    this._windowIDDict[outerWindowID] = Cu.getWeakReference(win);

    debug("Entering modal state (outerWindowID=" + outerWindowID + ", " +
                                "innerWindowID=" + innerWindowID + ")");

    utils.enterModalState();

    // We'll decrement win.modalDepth when we receive a unblock-modal-prompt message
    // for the window.
    if (!win.modalDepth) {
      win.modalDepth = 0;
    }
    win.modalDepth++;
    let origModalDepth = win.modalDepth;

    debug("Nested event loop - begin");
    Services.tm.spinEventLoopUntil(() => {
      // Bail out of the loop if the inner window changed; that means the
      // window navigated.  Bail out when we're shutting down because otherwise
      // we'll leak our window.
      if (this._tryGetInnerWindowID(win) !== innerWindowID) {
        debug("_waitForResult: Inner window ID changed " +
              "while in nested event loop.");
        return true;
      }

      return win.modalDepth !== origModalDepth || this._shuttingDown;
    });
    debug("Nested event loop - finish");

    if (win.modalDepth == 0) {
      delete this._windowIDDict[outerWindowID];
    }

    // If we exited the loop because the inner window changed, then bail on the
    // modal prompt.
    if (innerWindowID !== this._tryGetInnerWindowID(win)) {
      throw Components.Exception("Modal state aborted by navigation",
                                 Cr.NS_ERROR_NOT_AVAILABLE);
    }

    let returnValue = win.modalReturnValue;
    delete win.modalReturnValue;

    if (!this._shuttingDown) {
      utils.leaveModalState();
    }

    debug("Leaving modal state (outerID=" + outerWindowID + ", " +
                               "innerID=" + innerWindowID + ")");
    return returnValue;
  },

  _recvStopWaiting: function(msg) {
    let outerID = msg.json.windowID.outer;
    let innerID = msg.json.windowID.inner;
    let returnValue = msg.json.returnValue;
    debug("recvStopWaiting(outer=" + outerID + ", inner=" + innerID +
          ", returnValue=" + returnValue + ")");

    if (!this._windowIDDict[outerID]) {
      debug("recvStopWaiting: No record of outer window ID " + outerID);
      return;
    }

    let win = this._windowIDDict[outerID].get();

    if (!win) {
      debug("recvStopWaiting, but window is gone\n");
      return;
    }

    if (innerID !== this._tryGetInnerWindowID(win)) {
      debug("recvStopWaiting, but inner ID has changed\n");
      return;
    }

    debug("recvStopWaiting " + win);
    win.modalReturnValue = returnValue;
    win.modalDepth--;
  },

  _recvEnteredFullscreen: function() {
    if (!this._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.
      sendAsyncMsg("exit-dom-fullscreen");
    }
  },

  _recvExitFullscreen: function() {
    this._windowUtils.exitFullscreen();
  },

  _titleChangedHandler: function(e) {
    debug("Got titlechanged: (" + e.target.title + ")");
    var win = e.target.defaultView;

    // Ignore titlechanges which don't come from the top-level
    // <iframe mozbrowser> window.
    if (win == content) {
      sendAsyncMsg('titlechange', { _payload_: e.target.title });
    }
    else {
      debug("Not top level!");
    }
  },

  _maybeCopyAttribute: function(src, target, attribute) {
    if (src.getAttribute(attribute)) {
      target[attribute] = src.getAttribute(attribute);
    }
  },

  _iconChangedHandler: function(e) {
    debug('Got iconchanged: (' + e.target.href + ')');
    let icon = { href: e.target.href };
    this._maybeCopyAttribute(e.target, icon, 'sizes');
    this._maybeCopyAttribute(e.target, icon, 'rel');
    sendAsyncMsg('iconchange', icon);
  },

  _openSearchHandler: function(e) {
    debug('Got opensearch: (' + e.target.href + ')');

    if (e.target.type !== "application/opensearchdescription+xml") {
      return;
    }

    sendAsyncMsg('opensearch', { title: e.target.title,
                                 href: e.target.href });

  },

  _manifestChangedHandler: function(e) {
    debug('Got manifestchanged: (' + e.target.href + ')');
    let manifest = { href: e.target.href };
    sendAsyncMsg('manifestchange', manifest);

  },

  // Processes the "rel" field in <link> tags and forward to specific handlers.
  _linkAddedHandler: function(e) {
    let win = e.target.ownerGlobal;
    // Ignore links which don't come from the top-level
    // <iframe mozbrowser> window.
    if (win != content) {
      debug('Not top level!');
      return;
    }

    let handlers = {
      'icon': this._iconChangedHandler.bind(this),
      'apple-touch-icon': this._iconChangedHandler.bind(this),
      'apple-touch-icon-precomposed': this._iconChangedHandler.bind(this),
      'search': this._openSearchHandler,
      'manifest': this._manifestChangedHandler
    };

    debug('Got linkAdded: (' + e.target.href + ') ' + e.target.rel);
    e.target.rel.split(' ').forEach(function(x) {
      let token = x.toLowerCase();
      if (handlers[token]) {
        handlers[token](e);
      }
    }, this);
  },

  _metaChangedHandler: function(e) {
    let win = e.target.ownerGlobal;
    // Ignore metas which don't come from the top-level
    // <iframe mozbrowser> window.
    if (win != content) {
      debug('Not top level!');
      return;
    }

    var name = e.target.name;
    var property = e.target.getAttributeNS(null, "property");

    if (!name && !property) {
      return;
    }

    debug('Got metaChanged: (' + (name || property) + ') ' +
          e.target.content);

    let handlers = {
      'viewmode': this._genericMetaHandler,
      'theme-color': this._genericMetaHandler,
      'theme-group': this._genericMetaHandler,
      'application-name': this._applicationNameChangedHandler
    };
    let handler = handlers[name];

    if ((property || name).match(/^og:/)) {
      name = property || name;
      handler = this._genericMetaHandler;
    }

    if (handler) {
      handler(name, e.type, e.target);
    }
  },

  _applicationNameChangedHandler: function(name, eventType, target) {
    if (eventType !== 'DOMMetaAdded') {
      // Bug 1037448 - Decide what to do when <meta name="application-name">
      // changes
      return;
    }

    let meta = { name: name,
                 content: target.content };

    let lang;
    let elm;

    for (elm = target;
         !lang && elm && elm.nodeType == target.ELEMENT_NODE;
         elm = elm.parentNode) {
      if (elm.hasAttribute('lang')) {
        lang = elm.getAttribute('lang');
        continue;
      }

      if (elm.hasAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')) {
        lang = elm.getAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang');
        continue;
      }
    }

    // No lang has been detected.
    if (!lang && elm.nodeType == target.DOCUMENT_NODE) {
      lang = elm.contentLanguage;
    }

    if (lang) {
      meta.lang = lang;
    }

    sendAsyncMsg('metachange', meta);
  },

  _ScrollViewChangeHandler: function(e) {
    e.stopPropagation();
    let detail = {
      state: e.state,
    };
    sendAsyncMsg('scrollviewchange', detail);
  },

  _ClickHandler: function(e) {

    let isHTMLLink = node =>
      ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) ||
       (node instanceof Ci.nsIDOMHTMLAreaElement && node.href) ||
        node instanceof Ci.nsIDOMHTMLLinkElement);

    // Open in a new tab if middle click or ctrl/cmd-click,
    // and e.target is a link or inside a link.
    if ((Services.appinfo.OS == 'Darwin' && e.metaKey) ||
        (Services.appinfo.OS != 'Darwin' && e.ctrlKey) ||
         e.button == 1) {

      let node = e.target;
      while (node && !isHTMLLink(node)) {
        node = node.parentNode;
      }

      if (node) {
        sendAsyncMsg('opentab', {url: node.href});
      }
    }
  },

  _genericMetaHandler: function(name, eventType, target) {
    let meta = {
      name: name,
      content: target.content,
      type: eventType.replace('DOMMeta', '').toLowerCase()
    };
    sendAsyncMsg('metachange', meta);
  },

  _addMozAfterPaintHandler: function(callback) {
    function onMozAfterPaint() {
      let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
      if (uri.spec != "about:blank") {
        debug("Got afterpaint event: " + uri.spec);
        removeEventListener('MozAfterPaint', onMozAfterPaint,
                            /* useCapture = */ true);
        callback();
      }
    }

    addEventListener('MozAfterPaint', onMozAfterPaint, /* useCapture = */ true);
    return onMozAfterPaint;
  },

  _removeMozAfterPaintHandler: function(listener) {
    removeEventListener('MozAfterPaint', listener,
                        /* useCapture = */ true);
  },

  _activateNextPaintListener: function(e) {
    if (!this._nextPaintHandler) {
      this._nextPaintHandler = this._addMozAfterPaintHandler(() => {
        this._nextPaintHandler = null;
        sendAsyncMsg('nextpaint');
      });
    }
  },

  _deactivateNextPaintListener: function(e) {
    if (this._nextPaintHandler) {
      this._removeMozAfterPaintHandler(this._nextPaintHandler);
      this._nextPaintHandler = null;
    }
  },

  _windowCloseHandler: function(e) {
    let win = e.target;
    if (win != content || e.defaultPrevented) {
      return;
    }

    debug("Closing window " + win);
    sendAsyncMsg('close');

    // Inform the window implementation that we handled this close ourselves.
    e.preventDefault();
  },

  _windowCreatedHandler: function(e) {
    let targetDocShell = e.target.defaultView
          .QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIWebNavigation);
    if (targetDocShell != docShell) {
      return;
    }

    let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
    debug("Window created: " + uri.spec);
    if (uri.spec != "about:blank") {
      this._addMozAfterPaintHandler(function () {
        sendAsyncMsg('documentfirstpaint');
      });
      this._isContentWindowCreated = true;
    }
  },

  _windowResizeHandler: function(e) {
    let win = e.target;
    if (win != content || e.defaultPrevented) {
      return;
    }

    debug("resizing window " + win);
    sendAsyncMsg('resize', { width: e.detail.width, height: e.detail.height });

    // Inform the window implementation that we handled this resize ourselves.
    e.preventDefault();
  },

  _contextmenuHandler: function(e) {
    debug("Got contextmenu");

    if (e.defaultPrevented) {
      return;
    }

    this._ctxCounter++;
    this._ctxHandlers = {};

    var elem = e.target;
    var menuData = {systemTargets: [], contextmenu: null};
    var ctxMenuId = null;
    var clipboardPlainTextOnly = Services.prefs.getBoolPref('clipboard.plainTextOnly');
    var copyableElements = {
      image: false,
      link: false,
      hasElements: function() {
        return this.image || this.link;
      }
    };

    // Set the event target as the copy image command needs it to
    // determine what was context-clicked on.
    docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit).setCommandNode(elem);

    while (elem && elem.parentNode) {
      var ctxData = this._getSystemCtxMenuData(elem);
      if (ctxData) {
        menuData.systemTargets.push({
          nodeName: elem.nodeName,
          data: ctxData
        });
      }

      if (!ctxMenuId && 'hasAttribute' in elem && elem.hasAttribute('contextmenu')) {
        ctxMenuId = elem.getAttribute('contextmenu');
      }

      // Enable copy image/link option
      if (elem.nodeName == 'IMG') {
        copyableElements.image = !clipboardPlainTextOnly;
      } else if (elem.nodeName == 'A') {
        copyableElements.link = true;
      }

      elem = elem.parentNode;
    }

    if (ctxMenuId || copyableElements.hasElements()) {
      var menu = null;
      if (ctxMenuId) {
        menu = e.target.ownerDocument.getElementById(ctxMenuId);
      }
      menuData.contextmenu = this._buildMenuObj(menu, '', copyableElements);
    }

    // Pass along the position where the context menu should be located
    menuData.clientX = e.clientX;
    menuData.clientY = e.clientY;
    menuData.screenX = e.screenX;
    menuData.screenY = e.screenY;

    // The value returned by the contextmenu sync call is true if the embedder
    // called preventDefault() on its contextmenu event.
    //
    // We call preventDefault() on our contextmenu event if the embedder called
    // preventDefault() on /its/ contextmenu event.  This way, if the embedder
    // ignored the contextmenu event, TabChild will fire a click.
    if (sendSyncMsg('contextmenu', menuData)[0]) {
      e.preventDefault();
    } else {
      this._ctxHandlers = {};
    }
  },

  _getSystemCtxMenuData: function(elem) {
    let documentURI =
      docShell.QueryInterface(Ci.nsIWebNavigation).currentURI.spec;
    if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
        (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {
      return {uri: elem.href,
              documentURI: documentURI,
              text: elem.textContent.substring(0, kLongestReturnedString)};
    }
    if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) {
      return {uri: elem.currentURI.spec, documentURI: documentURI};
    }
    if (elem instanceof Ci.nsIDOMHTMLImageElement) {
      return {uri: elem.src, documentURI: documentURI};
    }
    if (elem instanceof Ci.nsIDOMHTMLMediaElement) {
      let hasVideo = !(elem.readyState >= elem.HAVE_METADATA &&
                       (elem.videoWidth == 0 || elem.videoHeight == 0));
      return {uri: elem.currentSrc || elem.src,
              hasVideo: hasVideo,
              documentURI: documentURI};
    }
    if (elem instanceof Ci.nsIDOMHTMLInputElement &&
        elem.hasAttribute("name")) {
      // For input elements, we look for a parent <form> and if there is
      // one we return the form's method and action uri.
      let parent = elem.parentNode;
      while (parent) {
        if (parent instanceof Ci.nsIDOMHTMLFormElement &&
            parent.hasAttribute("action")) {
          let actionHref = docShell.QueryInterface(Ci.nsIWebNavigation)
                                   .currentURI
                                   .resolve(parent.getAttribute("action"));
          let method = parent.hasAttribute("method")
            ? parent.getAttribute("method").toLowerCase()
            : "get";
          return {
            documentURI: documentURI,
            action: actionHref,
            method: method,
            name: elem.getAttribute("name"),
          }
        }
        parent = parent.parentNode;
      }
    }
    return false;
  },

  _scrollEventHandler: function(e) {
    let win = e.target.defaultView;
    if (win != content) {
      return;
    }

    debug("scroll event " + win);
    sendAsyncMsg("scroll", { top: win.scrollY, left: win.scrollX });
  },

  _recvPurgeHistory: function(data) {
    debug("Received purgeHistory message: (" + data.json.id + ")");

    let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;

    try {
      if (history && history.count) {
        history.PurgeHistory(history.count);
      }
    } catch(e) {}

    sendAsyncMsg('got-purge-history', { id: data.json.id, successRv: true });
  },

  _recvGetScreenshot: function(data) {
    debug("Received getScreenshot message: (" + data.json.id + ")");

    let self = this;
    let maxWidth = data.json.args.width;
    let maxHeight = data.json.args.height;
    let mimeType = data.json.args.mimeType;
    let domRequestID = data.json.id;

    let takeScreenshotClosure = function() {
      self._takeScreenshot(maxWidth, maxHeight, mimeType, domRequestID);
    };

    let maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS', 2000);

    // Try to wait for the event loop to go idle before we take the screenshot,
    // but once we've waited maxDelayMS milliseconds, go ahead and take it
    // anyway.
    Cc['@mozilla.org/message-loop;1'].getService(Ci.nsIMessageLoop).postIdleTask(
      takeScreenshotClosure, maxDelayMS);
  },

  _recvExecuteScript: function(data) {
    debug("Received executeScript message: (" + data.json.id + ")");

    let domRequestID = data.json.id;

    let sendError = errorMsg => sendAsyncMsg("execute-script-done", {
      errorMsg,
      id: domRequestID
    });

    let sendSuccess = successRv => sendAsyncMsg("execute-script-done", {
      successRv,
      id: domRequestID
    });

    let isJSON = obj => {
      try {
        JSON.stringify(obj);
      } catch(e) {
        return false;
      }
      return true;
    }

    let expectedOrigin = data.json.args.options.origin;
    let expectedUrl = data.json.args.options.url;

    if (expectedOrigin) {
      if (expectedOrigin != content.location.origin) {
        sendError("Origin mismatches");
        return;
      }
    }

    if (expectedUrl) {
      let expectedURI
      try {
       expectedURI = Services.io.newURI(expectedUrl);
      } catch(e) {
        sendError("Malformed URL");
        return;
      }
      let currentURI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
      if (!currentURI.equalsExceptRef(expectedURI)) {
        sendError("URL mismatches");
        return;
      }
    }

    let sandbox = new Cu.Sandbox([content], {
      sandboxPrototype: content,
      sandboxName: "browser-api-execute-script",
      allowWaivers: false,
      sameZoneAs: content
    });

    try {
      let sandboxRv = Cu.evalInSandbox(data.json.args.script, sandbox, "1.8");
      if (sandboxRv instanceof sandbox.Promise) {
        sandboxRv.then(rv => {
          if (isJSON(rv)) {
            sendSuccess(rv);
          } else {
            sendError("Value returned (resolve) by promise is not a valid JSON object");
          }
        }, error => {
          if (isJSON(error)) {
            sendError(error);
          } else {
            sendError("Value returned (reject) by promise is not a valid JSON object");
          }
        });
      } else {
        if (isJSON(sandboxRv)) {
          sendSuccess(sandboxRv);
        } else {
          sendError("Script last expression must be a promise or a JSON object");
        }
      }
    } catch(e) {
      sendError(e.toString());
    }
  },

  _recvGetContentDimensions: function(data) {
    debug("Received getContentDimensions message: (" + data.json.id + ")");
    sendAsyncMsg('got-contentdimensions', {
      id: data.json.id,
      successRv: this._getContentDimensions()
    });
  },

  _mozScrollAreaChanged: function(e) {
    sendAsyncMsg('scrollareachanged', {
      width: e.width,
      height: e.height
    });
  },

  _mozRequestedDOMFullscreen: function(e) {
    sendAsyncMsg("requested-dom-fullscreen");
  },

  _mozFullscreenOriginChange: function(e) {
    sendAsyncMsg("fullscreen-origin-change", {
      originNoSuffix: e.target.nodePrincipal.originNoSuffix
    });
  },

  _mozExitDomFullscreen: function(e) {
    sendAsyncMsg("exit-dom-fullscreen");
  },

  _getContentDimensions: function() {
    return {
      width: content.document.body.scrollWidth,
      height: content.document.body.scrollHeight
    }
  },

  /**
   * Actually take a screenshot and foward the result up to our parent, given
   * the desired maxWidth and maxHeight (in CSS pixels), and given the
   * DOMRequest ID associated with the request from the parent.
   */
  _takeScreenshot: function(maxWidth, maxHeight, mimeType, domRequestID) {
    // You can think of the screenshotting algorithm as carrying out the
    // following steps:
    //
    // - Calculate maxWidth, maxHeight, and viewport's width and height in the
    //   dimension of device pixels by multiply the numbers with
    //   window.devicePixelRatio.
    //
    // - Let scaleWidth be the factor by which we'd need to downscale the
    //   viewport pixel width so it would fit within maxPixelWidth.
    //   (If the viewport's pixel width is less than maxPixelWidth, let
    //   scaleWidth be 1.) Compute scaleHeight the same way.
    //
    // - Scale the viewport by max(scaleWidth, scaleHeight).  Now either the
    //   viewport's width is no larger than maxWidth, the viewport's height is
    //   no larger than maxHeight, or both.
    //
    // - Crop the viewport so its width is no larger than maxWidth and its
    //   height is no larger than maxHeight.
    //
    // - Set mozOpaque to true and background color to solid white
    //   if we are taking a JPEG screenshot, keep transparent if otherwise.
    //
    // - Return a screenshot of the page's viewport scaled and cropped per
    //   above.
    debug("Taking a screenshot: maxWidth=" + maxWidth +
          ", maxHeight=" + maxHeight +
          ", mimeType=" + mimeType +
          ", domRequestID=" + domRequestID + ".");

    if (!content) {
      // If content is not loaded yet, bail out since even sendAsyncMessage
      // fails...
      debug("No content yet!");
      return;
    }

    let devicePixelRatio = content.devicePixelRatio;

    let maxPixelWidth = Math.round(maxWidth * devicePixelRatio);
    let maxPixelHeight = Math.round(maxHeight * devicePixelRatio);

    let contentPixelWidth = content.innerWidth * devicePixelRatio;
    let contentPixelHeight = content.innerHeight * devicePixelRatio;

    let scaleWidth = Math.min(1, maxPixelWidth / contentPixelWidth);
    let scaleHeight = Math.min(1, maxPixelHeight / contentPixelHeight);

    let scale = Math.max(scaleWidth, scaleHeight);

    let canvasWidth =
      Math.min(maxPixelWidth, Math.round(contentPixelWidth * scale));
    let canvasHeight =
      Math.min(maxPixelHeight, Math.round(contentPixelHeight * scale));

    let transparent = (mimeType !== 'image/jpeg');

    var canvas = content.document
      .createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    if (!transparent)
      canvas.mozOpaque = true;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    let ctx = canvas.getContext("2d", { willReadFrequently: true });
    ctx.scale(scale * devicePixelRatio, scale * devicePixelRatio);

    let flags = ctx.DRAWWINDOW_DRAW_VIEW |
                ctx.DRAWWINDOW_USE_WIDGET_LAYERS |
                ctx.DRAWWINDOW_DO_NOT_FLUSH |
                ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES;
    ctx.drawWindow(content, 0, 0, content.innerWidth, content.innerHeight,
                   transparent ? "rgba(255,255,255,0)" : "rgb(255,255,255)",
                   flags);

    // Take a JPEG screenshot by default instead of PNG with alpha channel.
    // This requires us to unpremultiply the alpha channel, which
    // is expensive on ARM processors because they lack a hardware integer
    // division instruction.
    canvas.toBlob(function(blob) {
      sendAsyncMsg('got-screenshot', {
        id: domRequestID,
        successRv: blob
      });
    }, mimeType);
  },

  _recvFireCtxCallback: function(data) {
    debug("Received fireCtxCallback message: (" + data.json.menuitem + ")");

    let doCommandIfEnabled = (command) => {
      if (docShell.isCommandEnabled(command)) {
        docShell.doCommand(command);
      }
    };

    if (data.json.menuitem == 'copy-image') {
      doCommandIfEnabled('cmd_copyImage');
    } else if (data.json.menuitem == 'copy-link') {
      doCommandIfEnabled('cmd_copyLink');
    } else if (data.json.menuitem in this._ctxHandlers) {
      this._ctxHandlers[data.json.menuitem].click();
      this._ctxHandlers = {};
    } else {
      // We silently ignore if the embedder uses an incorrect id in the callback
      debug("Ignored invalid contextmenu invocation");
    }
  },

  _buildMenuObj: function(menu, idPrefix, copyableElements) {
    var menuObj = {type: 'menu', customized: false, items: []};
    // Customized context menu
    if (menu) {
      this._maybeCopyAttribute(menu, menuObj, 'label');

      for (var i = 0, child; child = menu.children[i++];) {
        if (child.nodeName === 'MENU') {
          menuObj.items.push(this._buildMenuObj(child, idPrefix + i + '_', false));
        } else if (child.nodeName === 'MENUITEM') {
          var id = this._ctxCounter + '_' + idPrefix + i;
          var menuitem = {id: id, type: 'menuitem'};
          this._maybeCopyAttribute(child, menuitem, 'label');
          this._maybeCopyAttribute(child, menuitem, 'icon');
          this._ctxHandlers[id] = child;
          menuObj.items.push(menuitem);
        }
      }

      if (menuObj.items.length > 0) {
        menuObj.customized = true;
      }
    }
    // Note: Display "Copy Link" first in order to make sure "Copy Image" is
    //       put together with other image options if elem is an image link.
    // "Copy Link" menu item
    if (copyableElements.link) {
      menuObj.items.push({id: 'copy-link'});
    }
    // "Copy Image" menu item
    if (copyableElements.image) {
      menuObj.items.push({id: 'copy-image'});
    }

    return menuObj;
  },

  /**
   * Called when the window which contains this iframe becomes hidden or
   * visible.
   */
  _recvOwnerVisibilityChange: function(data) {
    debug("Received ownerVisibilityChange: (" + data.json.visible + ")");
    var visible = data.json.visible;
    if (docShell && docShell.isActive !== visible) {
      docShell.isActive = visible;

      // Ensure painting is not frozen if the app goes visible.
      if (visible && this._paintFrozenTimer) {
        this.notify();
      }
    }
  },

  _recvSendMouseEvent: function(data) {
    let json = data.json;
    let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
    utils.sendMouseEventToWindow(json.type, json.x, json.y, json.button,
                                 json.clickCount, json.modifiers);
  },

  _recvSendTouchEvent: function(data) {
    let json = data.json;
    let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
    utils.sendTouchEventToWindow(json.type, json.identifiers, json.touchesX,
                                 json.touchesY, json.radiisX, json.radiisY,
                                 json.rotationAngles, json.forces, json.count,
                                 json.modifiers);
  },

  _recvCanGoBack: function(data) {
    var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    sendAsyncMsg('got-can-go-back', {
      id: data.json.id,
      successRv: webNav.canGoBack
    });
  },

  _recvCanGoForward: function(data) {
    var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    sendAsyncMsg('got-can-go-forward', {
      id: data.json.id,
      successRv: webNav.canGoForward
    });
  },

  _recvGoBack: function(data) {
    try {
      docShell.QueryInterface(Ci.nsIWebNavigation).goBack();
    } catch(e) {
      // Silently swallow errors; these happen when we can't go back.
    }
  },

  _recvGoForward: function(data) {
    try {
      docShell.QueryInterface(Ci.nsIWebNavigation).goForward();
    } catch(e) {
      // Silently swallow errors; these happen when we can't go forward.
    }
  },

  _recvReload: function(data) {
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    let reloadFlags = data.json.hardReload ?
      webNav.LOAD_FLAGS_BYPASS_PROXY | webNav.LOAD_FLAGS_BYPASS_CACHE :
      webNav.LOAD_FLAGS_NONE;
    try {
      webNav.reload(reloadFlags);
    } catch(e) {
      // Silently swallow errors; these can happen if a used cancels reload
    }
  },

  _recvStop: function(data) {
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    webNav.stop(webNav.STOP_NETWORK);
  },

  _recvZoom: function(data) {
    docShell.contentViewer.fullZoom = data.json.zoom;
  },

  async _recvGetWebManifest(data) {
    debug(`Received GetWebManifest message: (${data.json.id})`);
    let manifest = null;
    let hasManifest = ManifestFinder.contentHasManifestLink(content);
    if (hasManifest) {
      try {
        manifest = await ManifestObtainer.contentObtainManifest(content);
      } catch (e) {
        sendAsyncMsg('got-web-manifest', {
          id: data.json.id,
          errorMsg: `Error fetching web manifest: ${e}.`,
        });
        return;
      }
    }
    sendAsyncMsg('got-web-manifest', {
      id: data.json.id,
      successRv: manifest
    });
  },

  _initFinder: function() {
    if (!this._finder) {
      let {Finder} = Components.utils.import("resource://gre/modules/Finder.jsm", {});
      this._finder = new Finder(docShell);
    }
    let listener = {
      onMatchesCountResult: (data) => {
        sendAsyncMsg("findchange", {
          active: true,
          searchString: this._finder.searchString,
          searchLimit: this._finder.matchesCountLimit,
          activeMatchOrdinal: data.current,
          numberOfMatches: data.total
        });
        this._finder.removeResultListener(listener);
      }
    };
    this._finder.addResultListener(listener);
  },

  _recvFindAll: function(data) {
    this._initFinder();
    let searchString = data.json.searchString;
    this._finder.caseSensitive = data.json.caseSensitive;
    this._finder.fastFind(searchString, false, false);
    this._finder.requestMatchesCount(searchString, this._finder.matchesCountLimit, false);
  },

  _recvFindNext: function(data) {
    if (!this._finder) {
      debug("findNext() called before findAll()");
      return;
    }
    this._initFinder();
    this._finder.findAgain(data.json.backward, false, false);
    this._finder.requestMatchesCount(this._finder.searchString, this._finder.matchesCountLimit, false);
  },

  _recvClearMatch: function(data) {
    if (!this._finder) {
      debug("clearMach() called before findAll()");
      return;
    }
    this._finder.removeSelection();
    sendAsyncMsg("findchange", {active: false});
  },

  // The docShell keeps a weak reference to the progress listener, so we need
  // to keep a strong ref to it ourselves.
  _progressListener: {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                           Ci.nsISupportsWeakReference]),
    _seenLoadStart: false,

    onLocationChange: function(webProgress, request, location, flags) {
      // We get progress events from subshells here, which is kind of weird.
      if (webProgress != docShell) {
        return;
      }

      // Ignore locationchange events which occur before the first loadstart.
      // These are usually about:blank loads we don't care about.
      if (!this._seenLoadStart) {
        return;
      }

      // Remove password and wyciwyg from uri.
      location = Cc["@mozilla.org/docshell/urifixup;1"]
        .getService(Ci.nsIURIFixup).createExposableURI(location);

      var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);

      sendAsyncMsg('locationchange', { url: location.spec,
                                       canGoBack: webNav.canGoBack,
                                       canGoForward: webNav.canGoForward });
    },

    onStateChange: function(webProgress, request, stateFlags, status) {
      if (webProgress != docShell) {
        return;
      }

      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
        this._seenLoadStart = true;
        sendAsyncMsg('loadstart');
      }

      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        let bgColor = 'transparent';
        try {
          bgColor = content.getComputedStyle(content.document.body)
                           .getPropertyValue('background-color');
        } catch (e) {}
        sendAsyncMsg('loadend', {backgroundColor: bgColor});

        switch (status) {
          case Cr.NS_OK :
          case Cr.NS_BINDING_ABORTED :
            // Ignoring NS_BINDING_ABORTED, which is set when loading page is
            // stopped.
          case Cr.NS_ERROR_PARSED_DATA_CACHED:
            return;

          // TODO See nsDocShell::DisplayLoadError to see what extra
          // information we should be annotating this first block of errors
          // with. Bug 1107091.
          case Cr.NS_ERROR_UNKNOWN_PROTOCOL :
            sendAsyncMsg('error', { type: 'unknownProtocolFound' });
            return;
          case Cr.NS_ERROR_FILE_NOT_FOUND :
            sendAsyncMsg('error', { type: 'fileNotFound' });
            return;
          case Cr.NS_ERROR_UNKNOWN_HOST :
            sendAsyncMsg('error', { type: 'dnsNotFound' });
            return;
          case Cr.NS_ERROR_CONNECTION_REFUSED :
            sendAsyncMsg('error', { type: 'connectionFailure' });
            return;
          case Cr.NS_ERROR_NET_INTERRUPT :
            sendAsyncMsg('error', { type: 'netInterrupt' });
            return;
          case Cr.NS_ERROR_NET_TIMEOUT :
            sendAsyncMsg('error', { type: 'netTimeout' });
            return;
          case Cr.NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION :
            sendAsyncMsg('error', { type: 'cspBlocked' });
            return;
          case Cr.NS_ERROR_PHISHING_URI :
            sendAsyncMsg('error', { type: 'deceptiveBlocked' });
            return;
          case Cr.NS_ERROR_MALWARE_URI :
            sendAsyncMsg('error', { type: 'malwareBlocked' });
            return;
          case Cr.NS_ERROR_UNWANTED_URI :
            sendAsyncMsg('error', { type: 'unwantedBlocked' });
            return;
          case Cr.NS_ERROR_FORBIDDEN_URI :
            sendAsyncMsg('error', { type: 'forbiddenBlocked' });
            return;

          case Cr.NS_ERROR_OFFLINE :
            sendAsyncMsg('error', { type: 'offline' });
            return;
          case Cr.NS_ERROR_MALFORMED_URI :
            sendAsyncMsg('error', { type: 'malformedURI' });
            return;
          case Cr.NS_ERROR_REDIRECT_LOOP :
            sendAsyncMsg('error', { type: 'redirectLoop' });
            return;
          case Cr.NS_ERROR_UNKNOWN_SOCKET_TYPE :
            sendAsyncMsg('error', { type: 'unknownSocketType' });
            return;
          case Cr.NS_ERROR_NET_RESET :
            sendAsyncMsg('error', { type: 'netReset' });
            return;
          case Cr.NS_ERROR_DOCUMENT_NOT_CACHED :
            sendAsyncMsg('error', { type: 'notCached' });
            return;
          case Cr.NS_ERROR_DOCUMENT_IS_PRINTMODE :
            sendAsyncMsg('error', { type: 'isprinting' });
            return;
          case Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED :
            sendAsyncMsg('error', { type: 'deniedPortAccess' });
            return;
          case Cr.NS_ERROR_UNKNOWN_PROXY_HOST :
            sendAsyncMsg('error', { type: 'proxyResolveFailure' });
            return;
          case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED :
            sendAsyncMsg('error', { type: 'proxyConnectFailure' });
            return;
          case Cr.NS_ERROR_INVALID_CONTENT_ENCODING :
            sendAsyncMsg('error', { type: 'contentEncodingFailure' });
            return;
          case Cr.NS_ERROR_REMOTE_XUL :
            sendAsyncMsg('error', { type: 'remoteXUL' });
            return;
          case Cr.NS_ERROR_UNSAFE_CONTENT_TYPE :
            sendAsyncMsg('error', { type: 'unsafeContentType' });
            return;
          case Cr.NS_ERROR_CORRUPTED_CONTENT :
            sendAsyncMsg('error', { type: 'corruptedContentErrorv2' });
            return;

          default:
            // getErrorClass() will throw if the error code passed in is not a NSS
            // error code.
            try {
              let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
                                       .getService(Ci.nsINSSErrorsService);
              if (nssErrorsService.getErrorClass(status)
                    == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
                // XXX Is there a point firing the event if the error page is not
                // certerror? If yes, maybe we should add a property to the
                // event to to indicate whether there is a custom page. That would
                // let the embedder have more control over the desired behavior.
                let errorPage = Services.prefs.getCharPref(CERTIFICATE_ERROR_PAGE_PREF, "");

                if (errorPage == 'certerror') {
                  sendAsyncMsg('error', { type: 'certerror' });
                  return;
                }
              }
            } catch (e) {}

            sendAsyncMsg('error', { type: 'other' });
            return;
        }
      }
    },

    onSecurityChange: function(webProgress, request, state) {
      if (webProgress != docShell) {
        return;
      }

      var securityStateDesc;
      if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
        securityStateDesc = 'secure';
      }
      else if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
        securityStateDesc = 'broken';
      }
      else if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
        securityStateDesc = 'insecure';
      }
      else {
        debug("Unexpected securitychange state!");
        securityStateDesc = '???';
      }

      var trackingStateDesc;
      if (state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) {
        trackingStateDesc = 'loaded_tracking_content';
      }
      else if (state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) {
        trackingStateDesc = 'blocked_tracking_content';
      }

      var mixedStateDesc;
      if (state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
        mixedStateDesc = 'blocked_mixed_active_content';
      }
      else if (state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
        // Note that STATE_LOADED_MIXED_ACTIVE_CONTENT implies STATE_IS_BROKEN
        mixedStateDesc = 'loaded_mixed_active_content';
      }

      var isEV = !!(state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
      var isTrackingContent = !!(state &
        (Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT |
        Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT));
      var isMixedContent = !!(state &
        (Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT |
        Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT));

      sendAsyncMsg('securitychange', {
        state: securityStateDesc,
        trackingState: trackingStateDesc,
        mixedState: mixedStateDesc,
        extendedValidation: isEV,
        trackingContent: isTrackingContent,
        mixedContent: isMixedContent,
      });
    },
  },

  // Expose the message manager for WebApps and others.
  _messageManagerPublic: {
    sendAsyncMessage: global.sendAsyncMessage.bind(global),
    sendSyncMessage: global.sendSyncMessage.bind(global),
    addMessageListener: global.addMessageListener.bind(global),
    removeMessageListener: global.removeMessageListener.bind(global)
  },

  get messageManager() {
    return this._messageManagerPublic;
  }
};

var api = null;
if ('DoPreloadPostfork' in this && typeof this.DoPreloadPostfork === 'function') {
  // If we are preloaded, instantiate BrowserElementChild after a content
  // process is forked.
  this.DoPreloadPostfork(function() {
    api = new BrowserElementChild();
  });
} else {
  api = new BrowserElementChild();
}
PK
!<ZK&&8chrome/toolkit/content/global/BrowserElementCopyPaste.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
/* 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/. */

"use strict";

function debug(msg) {
  // dump("BrowserElementCopyPaste - " + msg + "\n");
}

debug("loaded");

var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;

var CopyPasteAssistent = {
  COMMAND_MAP: {
    'cut': 'cmd_cut',
    'copy': 'cmd_copyAndCollapseToEnd',
    'paste': 'cmd_paste',
    'selectall': 'cmd_selectAll'
  },

  init: function() {
    addEventListener("mozcaretstatechanged", this,
                     /* useCapture = */ true, /* wantsUntrusted = */ false);
    addMessageListener("browser-element-api:call", this);
  },

  destroy: function() {
    removeEventListener("mozcaretstatechanged", this,
                        /* useCapture = */ true, /* wantsUntrusted = */ false);
    removeMessageListener("browser-element-api:call", this);
  },

  handleEvent: function(event) {
    switch (event.type) {
      case "mozcaretstatechanged":
        this._caretStateChangedHandler(event);
        break;
    }
  },

  receiveMessage: function(message) {
    switch (message.name) {
      case "browser-element-api:call":
        this._browserAPIHandler(message);
        break;
    }
  },

  _browserAPIHandler: function(e) {
    switch (e.data.msg_name) {
      case 'copypaste-do-command':
        if (this._isCommandEnabled(e.data.command)) {
          docShell.doCommand(this.COMMAND_MAP[e.data.command]);
        }
        break;
    }
  },

  _isCommandEnabled: function(cmd) {
    let command = this.COMMAND_MAP[cmd];
    if (!command) {
      return false;
    }

    return docShell.isCommandEnabled(command);
  },

  _caretStateChangedHandler: function(e) {
    e.stopPropagation();

    let boundingClientRect = e.boundingClientRect;
    let canPaste = this._isCommandEnabled("paste");
    let zoomFactor = content.innerWidth == 0 ? 1 : content.screen.width / content.innerWidth;

    let detail = {
      rect: {
        width: boundingClientRect ? boundingClientRect.width : 0,
        height: boundingClientRect ? boundingClientRect.height : 0,
        top: boundingClientRect ? boundingClientRect.top : 0,
        bottom: boundingClientRect ? boundingClientRect.bottom : 0,
        left: boundingClientRect ? boundingClientRect.left : 0,
        right: boundingClientRect ? boundingClientRect.right : 0,
      },
      commands: {
        canSelectAll: this._isCommandEnabled("selectall"),
        canCut: this._isCommandEnabled("cut"),
        canCopy: this._isCommandEnabled("copy"),
        canPaste: this._isCommandEnabled("paste"),
      },
      zoomFactor: zoomFactor,
      reason: e.reason,
      collapsed: e.collapsed,
      caretVisible: e.caretVisible,
      selectionVisible: e.selectionVisible,
      selectionEditable: e.selectionEditable,
      selectedTextContent: e.selectedTextContent
    };

    // Get correct geometry information if we have nested iframe.
    let currentWindow = e.target.defaultView;
    while (currentWindow.realFrameElement) {
      let currentRect = currentWindow.realFrameElement.getBoundingClientRect();
      detail.rect.top += currentRect.top;
      detail.rect.bottom += currentRect.top;
      detail.rect.left += currentRect.left;
      detail.rect.right += currentRect.left;
      currentWindow = currentWindow.realFrameElement.ownerGlobal;

      let targetDocShell = currentWindow
          .QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIWebNavigation);
      if(targetDocShell.isMozBrowser) {
        break;
      }
    }

    sendAsyncMsg('caretstatechanged', detail);
  },
};

CopyPasteAssistent.init();
PK
!<*6chrome/toolkit/content/global/TopLevelVideoDocument.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// <video> is used for top-level audio documents as well
let videoElement = document.getElementsByTagName("video")[0];

// 1. Handle fullscreen mode;
// 2. Send keystrokes to the video element if the body element is focused,
//    to be received by the event listener in videocontrols.xml.
document.addEventListener("keypress", ev => {
  if (ev.synthetic) // prevent recursion
    return;

  // Maximize the standalone video when pressing F11,
  // but ignore audio elements
  if (ev.key == "F11" && videoElement.videoWidth != 0 && videoElement.videoHeight != 0) {
    // If we're in browser fullscreen mode, it means the user pressed F11
    // while browser chrome or another tab had focus.
    // Don't break leaving that mode, so do nothing here.
    if (window.fullScreen) {
      return;
    }

    // If we're not in browser fullscreen mode, prevent entering into that,
    // so we don't end up there after pressing Esc.
    ev.preventDefault();
    ev.stopPropagation();

    if (!document.mozFullScreenElement) {
      videoElement.mozRequestFullScreen();
    } else {
      document.mozCancelFullScreen();
    }
    return;
  }

  // Check if the video element is focused, so it already receives
  // keystrokes, and don't send it another one from here.
  if (document.activeElement == videoElement)
    return;

  let newEvent = new KeyboardEvent("keypress", ev);
  newEvent.synthetic = true;
  videoElement.dispatchEvent(newEvent);
});
PK
!<⭝1chrome/toolkit/content/global/XPCNativeWrapper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Moved to C++ implementation in XPConnect.  See bug 281988.
 */
PK
!<qyEE&chrome/toolkit/content/global/about.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// get release notes and vendor URL from prefs
var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                          .getService(Components.interfaces.nsIURLFormatter);
var releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL");
if (releaseNotesURL != "about:blank") {
  var relnotes = document.getElementById("releaseNotesURL");
  relnotes.setAttribute("href", releaseNotesURL);
  relnotes.parentNode.removeAttribute("hidden");
}

var vendorURL = formatter.formatURLPref("app.vendorURL");
if (vendorURL != "about:blank") {
  var vendor = document.getElementById("vendorURL");
  vendor.setAttribute("href", vendorURL);
}

// insert the version of the XUL application (!= XULRunner platform version)
var versionNum = Components.classes["@mozilla.org/xre/app-info;1"]
                           .getService(Components.interfaces.nsIXULAppInfo)
                           .version;
var version = document.getElementById("version");
version.textContent += " " + versionNum;

// append user agent
var ua = navigator.userAgent;
if (ua) {
  document.getElementById("buildID").textContent += " " + ua;
}
PK
!< lvv)chrome/toolkit/content/global/about.xhtml<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % aboutDTD SYSTEM "chrome://global/locale/about.dtd" >
%aboutDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>About:</title>
  <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
</head>

<body dir="&locale.dir;">
  <div id="aboutLogoContainer">
    <a id="vendorURL">
      <img src="about:logo" alt="&brandShortName;"/>
      <p id="version">&about.version;</p>
    </a>
  </div>

  <ul id="aboutPageList">
    <li>&about.credits.beforeLink;<a href="about:credits">&about.credits.linkTitle;</a>&about.credits.afterLink;</li>
    <li>&about.license.beforeTheLink;<a href="about:license">&about.license.linkTitle;</a>&about.license.afterTheLink;</li>
    <li hidden="true">&about.relnotes.beforeTheLink;<a id="releaseNotesURL">&about.relnotes.linkTitle;</a>&about.relnotes.afterTheLink;</li>
    <li>&about.buildconfig.beforeTheLink;<a href="about:buildconfig">&about.buildconfig.linkTitle;</a>&about.buildconfig.afterTheLink;</li>
    <li id="buildID">&about.buildIdentifier;</li>
    <script type="application/javascript" src="chrome://global/content/about.js"/>
  </ul>

</body>
</html>
PK
!<Od+chrome/toolkit/content/global/aboutAbout.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var Cc = Components.classes;
var Ci = Components.interfaces;
var gProtocols = [];
var gContainer;
window.onload = function() {
  gContainer = document.getElementById("abouts");
  findAbouts();
}

function findAbouts() {
  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  for (var cid in Cc) {
    var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
    if (result) {
      var aboutType = result[1];
      var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
      try {
        var am = Cc[contract].getService(Ci.nsIAboutModule);
        var uri = ios.newURI("about:" + aboutType);
        var flags = am.getURIFlags(uri);
        if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
          gProtocols.push(aboutType);
        }
      } catch (e) {
        // getService might have thrown if the component doesn't actually
        // implement nsIAboutModule
      }
    }
  }
  gProtocols.sort().forEach(createProtocolListing);
}

function createProtocolListing(aProtocol) {
  var uri = "about:" + aProtocol;
  var li = document.createElement("li");
  var link = document.createElement("a");
  var text = document.createTextNode(uri);

  link.href = uri;
  link.appendChild(text);
  li.appendChild(link);
  gContainer.appendChild(li);
}
PK
!<A؍.chrome/toolkit/content/global/aboutAbout.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ENTITY % aboutAboutDTD SYSTEM "chrome://global/locale/aboutAbout.dtd" >
%aboutAboutDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>&aboutAbout.title;</title>
  <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css"/>
  <script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script>
</head>

<body dir="&locale.dir;">
  <div class="container">
    <h1>&aboutAbout.title;</h1>
    <p><em>&aboutAbout.note;</em></p>
    <ul id="abouts" class="columns"></ul>
  </div>
</body>
</html>
PK
!<Mhm+chrome/toolkit/content/global/aboutCache.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// First, parse and save the incoming arguments ("?storage=name&context=key")
// Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
var search = window.location.href.match(/^.*\?(.*)$/);
var searchParams = new URLSearchParams(search ? search[1] : "");
var storage = searchParams.get("storage") || "";
var cacheContext = searchParams.get("context");

// The context is in a format as used by the HTTP cache v2 back end
if (cacheContext)
  var [context, isAnon, isInBrowser, appId, isPrivate] = cacheContext.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
if (appId)
  appId = appId.match(/i(\d+),/)[1];


function $(id) { return document.getElementById(id) || {}; }

// Initialize the context UI controls at the start according what we got in the "context=" argument
addEventListener("DOMContentLoaded", function() {
  $("anon").checked = !!isAnon;
  $("inbrowser").checked = !!isInBrowser;
  $("appid").value = appId || "";
  $("priv").checked = !!isPrivate;
}, false);

// When user presses the [Update] button, we build a new context key according the UI control
// values and navigate to a new about:cache?storage=<name>&context=<key> URL.
function navigate() {
  context = "";
  if ($("anon").checked)
    context += "a,";
  if ($("inbrowser").checked)
    context += "b,";
  if ($("appid").value)
    context += "i" + $("appid").value + ",";
  if ($("priv").checked)
    context += "p,";

  window.location.href = "about:cache?storage=" + storage + "&context=" + context;
}
PK
!<hSa3chrome/toolkit/content/global/aboutCheckerboard.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

table.listing {
    width: 100%;
}

table th, table td {
    padding: 5px;
    border: inset 2px black;
    margin: 0px;
    width: 50%;
    vertical-align: top;
}

hr {
    clear: both;
    margin: 10px;
}

iframe {
    width: 100%;
    height: 900px;
}

#player, #raw {
    width: 800px;
    margin-left: auto;
    margin-right: auto;
}

#controls {
    text-align: center;
}

#canvas {
    border: solid 1px black;
}

#active {
    width: 100%;
    border: solid 1px black;
    margin-top: 0;
}

#trace {
    width: 100%;
}
PK
!<y}2chrome/toolkit/content/global/aboutCheckerboard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var trace;
var service;
var reports;

function onLoad() {
  trace = document.getElementById("trace");
  service = new CheckerboardReportService();
  updateEnabled();
  reports = service.getReports();
  for (var i = 0; i < reports.length; i++) {
    let text = "Severity " + reports[i].severity + " at " + new Date(reports[i].timestamp).toString();
    let link = document.createElement("a");
    link.href = "javascript:showReport(" + i + ")";
    link.textContent = text;
    let bullet = document.createElement("li");
    bullet.appendChild(link);
    document.getElementById(reports[i].reason).appendChild(bullet);
  }
}

function updateEnabled() {
  let enabled = document.getElementById("enabled");
  if (service.isRecordingEnabled()) {
    enabled.textContent = "enabled";
    enabled.style.color = "green";
  } else {
    enabled.textContent = "disabled";
    enabled.style.color = "red";
  }
}

function toggleEnabled() {
  service.setRecordingEnabled(!service.isRecordingEnabled());
  updateEnabled();
}

function flushReports() {
  service.flushActiveReports();
}

function showReport(index) {
  trace.value = reports[index].log;
  loadData();
}

// -- Code to load and render the trace --

const CANVAS_USE_RATIO = 0.75;
const FRAME_INTERVAL_MS = 50;
const VECTOR_NORMALIZED_MAGNITUDE = 30.0;

var renderData = [];
var currentFrame = 0;
var playing = false;
var timerId = 0;

var minX = undefined;
var minY = undefined;
var maxX = undefined;
var maxY = undefined;

function log(x) {
    if (console) {
        console.log(x);
    }
}

function getFlag(flag) {
    return document.getElementById(flag).checked;
}

// parses the lines in the textarea, ignoring anything that doesn't have RENDERTRACE.
// for each matching line, tokenizes on whitespace and ignores all tokens prior to
// RENDERTRACE. Additional info can be included at the end of the line, and will be
// displayed but not parsed. Allowed syntaxes:
//   <junk> RENDERTRACE <timestamp> rect <color> <x> <y> <width> <height> [extraInfo]
function loadData() {
    stopPlay();
    renderData = [];
    currentFrame = 0;
    minX = undefined;
    minY = undefined;
    maxX = undefined;
    maxY = undefined;

    var charPos = 0;
    var lastLineLength = 0;
    var lines = trace.value.split(/\r|\n/);
    for (var i = 0; i < lines.length; i++) {
        charPos += lastLineLength;
        lastLineLength = lines[i].length + 1;
        // skip lines without RENDERTRACE
        if (!/RENDERTRACE/.test(lines[i])) {
            continue;
        }

        var tokens = lines[i].split(/\s+/);
        var j = 0;
        // skip tokens until RENDERTRACE
        while (j < tokens.length && tokens[j++] != "RENDERTRACE"); // empty loop body
        if (j >= tokens.length - 2) {
            log("Error parsing line: " + lines[i]);
            continue;
        }

        var timestamp = tokens[j++];
        var destIndex = renderData.length;
        if (destIndex == 0) {
            // create the initial frame
            renderData.push({
                timestamp,
                rects: {},
            });
        } else if (renderData[destIndex - 1].timestamp == timestamp) {
            // timestamp hasn't changed use, so update the previous object
            destIndex--;
        } else {
            // clone a new copy of the last frame and update timestamp
            renderData.push(JSON.parse(JSON.stringify(renderData[destIndex - 1])));
            renderData[destIndex].timestamp = timestamp;
        }

        switch (tokens[j++]) {
            case "rect":
                if (j > tokens.length - 5) {
                    log("Error parsing line: " + lines[i]);
                    continue;
                }

                var rect = {};
                var color = tokens[j++];
                renderData[destIndex].rects[color] = rect;
                rect.x = parseFloat(tokens[j++]);
                rect.y = parseFloat(tokens[j++]);
                rect.width = parseFloat(tokens[j++]);
                rect.height = parseFloat(tokens[j++]);
                rect.dataText = trace.value.substring(charPos, charPos + lines[i].length);

                if (!getFlag("excludePageFromZoom") || color != "brown") {
                    if (typeof minX == "undefined") {
                        minX = rect.x;
                        minY = rect.y;
                        maxX = rect.x + rect.width;
                        maxY = rect.y + rect.height;
                    } else {
                        minX = Math.min(minX, rect.x);
                        minY = Math.min(minY, rect.y);
                        maxX = Math.max(maxX, rect.x + rect.width);
                        maxY = Math.max(maxY, rect.y + rect.height);
                    }
                }
                break;

            default:
                log("Error parsing line " + lines[i]);
                break;
        }
    }

    if (!renderFrame()) {
        alert("No data found; nothing to render!");
    }
}

// render the current frame (i.e. renderData[currentFrame])
// returns false if currentFrame is out of bounds, true otherwise
function renderFrame() {
    var frame = currentFrame;
    if (frame < 0 || frame >= renderData.length) {
        log("Invalid frame index");
        return false;
    }

    var canvas = document.getElementById("canvas");
    if (!canvas.getContext) {
        log("No canvas context");
    }

    var context = canvas.getContext("2d");

    // midpoint of the bounding box
    var midX = (minX + maxX) / 2.0;
    var midY = (minY + maxY) / 2.0;

    // midpoint of the canvas
    var cmx = canvas.width / 2.0;
    var cmy = canvas.height / 2.0;

    // scale factor
    var scale = CANVAS_USE_RATIO * Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY));

    function projectX(value) {
        return cmx + ((value - midX) * scale);
    }

    function projectY(value) {
        return cmy + ((value - midY) * scale);
    }

    function drawRect(color, rect) {
        context.strokeStyle = color;
        context.strokeRect(
            projectX(rect.x),
            projectY(rect.y),
            rect.width * scale,
            rect.height * scale);
    }

    // clear canvas
    context.fillStyle = "white";
    context.fillRect(0, 0, canvas.width, canvas.height);
    var activeData = "";
    // draw rects
    for (var i in renderData[frame].rects) {
        drawRect(i, renderData[frame].rects[i]);
        activeData += "\n" + renderData[frame].rects[i].dataText;
    }
    // draw timestamp and frame counter
    context.fillStyle = "black";
    context.fillText((frame + 1) + "/" + renderData.length + ": " + renderData[frame].timestamp, 5, 15);

    document.getElementById("active").textContent = activeData;

    return true;
}

// -- Player controls --

function reset(beginning) {
    currentFrame = (beginning ? 0 : renderData.length - 1);
    renderFrame();
}

function step(backwards) {
    if (playing) {
        togglePlay();
    }
    currentFrame += (backwards ? -1 : 1);
    if (!renderFrame()) {
        currentFrame -= (backwards ? -1 : 1);
    }
}

function pause() {
    clearInterval(timerId);
    playing = false;
}

function togglePlay() {
    if (playing) {
        pause();
    } else {
        timerId = setInterval(function() {
            currentFrame++;
            if (!renderFrame()) {
                currentFrame--;
                togglePlay();
            }
        }, FRAME_INTERVAL_MS);
        playing = true;
    }
}

function stopPlay() {
    if (playing) {
        togglePlay();
    }
    currentFrame = 0;
    renderFrame();
}
PK
!<;;5chrome/toolkit/content/global/aboutCheckerboard.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta name="viewport" content="width=device-width"/>
  <link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/>
  <script type="text/javascript" src="chrome://global/content/aboutCheckerboard.js"></script>
 </head>

 <body onload="onLoad()">
  <p>Checkerboard recording is <span id="enabled" style="color: red">undetermined</span>.
     <button onclick="toggleEnabled()">Toggle it!</button>.</p>
  <p>If there are active reports in progress, you can stop and flush them by clicking here:
     <button onclick="flushReports()">Flush active reports</button></p>
  <table class="listing" cellspacing="0">
   <tr>
    <th>Most severe checkerboarding reports</th>
    <th>Most recent checkerboarding reports</th>
   </tr>
   <tr>
    <td><ul id="severe"></ul></td>
    <td><ul id="recent"></ul></td>
   </tr>
  </table>

  <hr/>

  <div id="player">
   <div id="controls">
    <button onclick="reset(true)">&#171;</button><!-- rewind button -->
    <button onclick="step(true)">&lt;</button><!-- step back button -->
    <button onclick="togglePlay()">|| &#9654;</button><!-- pause button -->
    <button onclick="stopPlay()">&#9744;</button><!-- stop button -->
    <button onclick="step(false)">&gt;</button><!-- step forward button -->
    <button onclick="reset(false)">&#187;</button><!-- forward button -->
   </div>
   <canvas id="canvas" width="800" height="600">Canvas not supported!</canvas>
   <pre id="active">(Details for currently visible replay frame)</pre>
  </div>

  <hr/>

  <div id="raw">
   Raw log:<br/>
   <textarea id="trace" rows="10"></textarea>
   <div>
    <input type="checkbox" id="excludePageFromZoom" onclick="loadData()"/><label for="excludePageFromZoom">Exclude page coordinates from zoom calculations</label><br/>
   </div>
  </div>
 </body>
</html>
PK
!<T		-chrome/toolkit/content/global/aboutMemory.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * The version used for mobile is located at
 * mobile/android/themes/core/aboutMemory.css.
 * Desktop-specific stuff is at the bottom of this file.
 */

html {
  background: -moz-Dialog;
  font: message-box;
}

body {
  padding: 0 2em;
  margin: 0;
  min-width: 45em;
  margin: auto;
}

div.ancillary {
  margin: 0.5em 0;
  -moz-user-select: none;
}

div.section {
  padding: 2em;
  margin: 1em 0em;
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  background: -moz-Field;
}

div.opsRow {
  padding: 0.5em;
  margin-right: 0.5em;
  margin-top: 0.5em;
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  background: -moz-Field;
  display: inline-block;
}

div.opsRowLabel {
  display: block;
  margin-bottom: 0.2em;
  font-weight: bold;
}

.opsRowLabel label {
  margin-left: 1em;
  font-weight: normal;
}

div.non-verbose pre.entries {
  overflow-x: hidden;
  text-overflow: ellipsis;
}

h1 {
  padding: 0;
  margin: 0;
  display: inline;  /* allow subsequent text to the right of the heading */
}

h2 {
  background: #ddd;
  padding-left: .1em;
}

h3 {
  display: inline;  /* allow subsequent text to the right of the heading */
}

a.upDownArrow {
  font-size: 130%;
  text-decoration: none;
  -moz-user-select: none;  /* no need to include this when cutting+pasting */
}

.accuracyWarning {
  color: #d22;
}

.badInputWarning {
  color: #f00;
}

.treeline {
  color: #888;
}

.mrValue {
  font-weight: bold;
  color: #400;
}

.mrPerc {
}

.mrSep {
}

.mrName {
  color: #004;
}

.mrNote {
  color: #604;
}

.hasKids {
  cursor: pointer;
}

.hasKids:hover {
  text-decoration: underline;
}

.noselect {
  -moz-user-select: none;  /* no need to include this when cutting+pasting */
}

.option {
  font-size: 80%;
  -moz-user-select: none;  /* no need to include this when cutting+pasting */
}

.legend {
  font-size: 80%;
  -moz-user-select: none;  /* no need to include this when cutting+pasting */
}

.debug {
  font-size: 80%;
}

.hidden {
  display: none;
}

.invalid {
  color: #fff;
  background-color: #f00;
}

/* Desktop-specific parts go here. */

.hasKids:hover {
  text-decoration: underline;
}

PK
!<

,chrome/toolkit/content/global/aboutMemory.js/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */

// You can direct about:memory to immediately load memory reports from a file
// by providing a file= query string.  For example,
//
//     about:memory?file=/home/username/reports.json.gz
//
// "file=" is not case-sensitive.  We'll URI-unescape the contents of the
// "file=" argument, and obviously the filename is case-sensitive iff you're on
// a case-sensitive filesystem.  If you specify more than one "file=" argument,
// only the first one is used.

"use strict";

// ---------------------------------------------------------------------------

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var CC = Components.Constructor;

const KIND_NONHEAP           = Ci.nsIMemoryReporter.KIND_NONHEAP;
const KIND_HEAP              = Ci.nsIMemoryReporter.KIND_HEAP;
const KIND_OTHER             = Ci.nsIMemoryReporter.KIND_OTHER;

const UNITS_BYTES            = Ci.nsIMemoryReporter.UNITS_BYTES;
const UNITS_COUNT            = Ci.nsIMemoryReporter.UNITS_COUNT;
const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
const UNITS_PERCENTAGE       = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
 "resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
 "resource://gre/modules/FileUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "nsBinaryStream",
                            () => CC("@mozilla.org/binaryinputstream;1",
                                     "nsIBinaryInputStream",
                                     "setInputStream"));
XPCOMUtils.defineLazyGetter(this, "nsFile",
                            () => CC("@mozilla.org/file/local;1",
                                     "nsIFile", "initWithPath"));
XPCOMUtils.defineLazyGetter(this, "nsGzipConverter",
                            () => CC("@mozilla.org/streamconv;1?from=gzip&to=uncompressed",
                                     "nsIStreamConverter"));

var gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
             .getService(Ci.nsIMemoryReporterManager);

const gPageName = "about:memory";
document.title = gPageName;

const gUnnamedProcessStr = "Main Process";

var gIsDiff = false;

// ---------------------------------------------------------------------------

// Forward slashes in URLs in paths are represented with backslashes to avoid
// being mistaken for path separators.  Paths/names where this hasn't been
// undone are prefixed with "unsafe"; the rest are prefixed with "safe".
function flipBackslashes(aUnsafeStr) {
  // Save memory by only doing the replacement if it's necessary.
  return (aUnsafeStr.indexOf("\\") === -1)
         ? aUnsafeStr
         : aUnsafeStr.replace(/\\/g, "/");
}

const gAssertionFailureMsgPrefix = "aboutMemory.js assertion failed: ";

// This is used for things that should never fail, and indicate a defect in
// this file if they do.
function assert(aCond, aMsg) {
  if (!aCond) {
    reportAssertionFailure(aMsg)
    throw new Error(gAssertionFailureMsgPrefix + aMsg);
  }
}

// This is used for malformed input from memory reporters.
function assertInput(aCond, aMsg) {
  if (!aCond) {
    throw new Error("Invalid memory report(s): " + aMsg);
  }
}

function handleException(ex) {
  let str = "" + ex;
  if (str.startsWith(gAssertionFailureMsgPrefix)) {
    // Argh, assertion failure within this file!  Give up.
    throw ex;
  } else {
    // File or memory reporter problem.  Print a message.
    updateMainAndFooter(str, NO_TIMESTAMP, HIDE_FOOTER, "badInputWarning");
  }
}

function reportAssertionFailure(aMsg) {
  let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
  if (debug.isDebugBuild) {
    debug.assertion(aMsg, "false", "aboutMemory.js", 0);
  }
}

function debug(x) {
  let section = appendElement(document.body, "div", "section");
  appendElementWithText(section, "div", "debug", JSON.stringify(x));
}

// ---------------------------------------------------------------------------

function onUnload() {
}

// ---------------------------------------------------------------------------

// The <div> holding everything but the header and footer (if they're present).
// It's what is updated each time the page changes.
var gMain;

// The <div> holding the footer.
var gFooter;

// The "verbose" checkbox.
var gVerbose;

// The "anonymize" checkbox.
var gAnonymize;

// Values for the |aFooterAction| argument to updateTitleMainAndFooter.
const HIDE_FOOTER = 0;
const SHOW_FOOTER = 1;

// Values for the |aShowTimestamp| argument to updateTitleMainAndFooter.
const NO_TIMESTAMP = 0;
const SHOW_TIMESTAMP = 1;

function updateTitleMainAndFooter(aTitleNote, aMsg, aShowTimestamp,
                                  aFooterAction, aClassName) {
  document.title = gPageName;
  if (aTitleNote) {
    document.title += " (" + aTitleNote + ")";
  }

  // Clear gMain by replacing it with an empty node.
  let tmp = gMain.cloneNode(false);
  gMain.parentNode.replaceChild(tmp, gMain);
  gMain = tmp;

  gMain.classList.remove("hidden");
  gMain.classList.remove("verbose");
  gMain.classList.remove("non-verbose");
  if (gVerbose) {
    gMain.classList.add(gVerbose.checked ? "verbose" : "non-verbose");
  }

  let msgElement;
  if (aMsg) {
    let className = "section"
    if (aClassName) {
      className = className + " " + aClassName;
    }
    if (aShowTimestamp == SHOW_TIMESTAMP) {
      // JS has many options for pretty-printing timestamps. We use
      // toISOString() because it has sub-second granularity, which is useful
      // if you quickly and repeatedly click one of the buttons.
      aMsg += " (" + (new Date()).toISOString() + ")";
    }
    msgElement = appendElementWithText(gMain, "div", className, aMsg);
  }

  switch (aFooterAction) {
   case HIDE_FOOTER: gFooter.classList.add("hidden"); break;
   case SHOW_FOOTER: gFooter.classList.remove("hidden"); break;
   default: assert(false, "bad footer action in updateTitleMainAndFooter");
  }
  return msgElement;
}

function updateMainAndFooter(aMsg, aShowTimestamp, aFooterAction, aClassName) {
  return updateTitleMainAndFooter("", aMsg, aShowTimestamp, aFooterAction,
                                  aClassName);
}

function appendTextNode(aP, aText) {
  let e = document.createTextNode(aText);
  aP.appendChild(e);
  return e;
}

function appendElement(aP, aTagName, aClassName) {
  let e = document.createElement(aTagName);
  if (aClassName) {
    e.className = aClassName;
  }
  aP.appendChild(e);
  return e;
}

function appendElementWithText(aP, aTagName, aClassName, aText) {
  let e = appendElement(aP, aTagName, aClassName);
  // Setting textContent clobbers existing children, but there are none.  More
  // importantly, it avoids creating a JS-land object for the node, saving
  // memory.
  e.textContent = aText;
  return e;
}

// ---------------------------------------------------------------------------

const explicitTreeDescription =
"This tree covers explicit memory allocations by the application.  It includes \
\n\n\
* allocations made at the operating system level (via calls to functions such as \
VirtualAlloc, vm_allocate, and mmap), \
\n\n\
* allocations made at the heap allocation level (via functions such as malloc, \
calloc, realloc, memalign, operator new, and operator new[]) that have not been \
explicitly decommitted (i.e. evicted from memory and swap), and \
\n\n\
* where possible, the overhead of the heap allocator itself.\
\n\n\
It excludes memory that is mapped implicitly such as code and data segments, \
and thread stacks. \
\n\n\
'explicit' is not guaranteed to cover every explicit allocation, but it does cover \
most (including the entire heap), and therefore it is the single best number to \
focus on when trying to reduce memory usage.";

// ---------------------------------------------------------------------------

function appendButton(aP, aTitle, aOnClick, aText, aId) {
  let b = appendElementWithText(aP, "button", "", aText);
  b.title = aTitle;
  b.onclick = aOnClick;
  if (aId) {
    b.id = aId;
  }
  return b;
}

function appendHiddenFileInput(aP, aId, aChangeListener) {
  let input = appendElementWithText(aP, "input", "hidden", "");
  input.type = "file";
  input.id = aId;      // used in testing
  input.addEventListener("change", aChangeListener);
  return input;
}

function onLoad() {
  // Generate the header.

  let header = appendElement(document.body, "div", "ancillary");

  // A hidden file input element that can be invoked when necessary.
  let fileInput1 = appendHiddenFileInput(header, "fileInput1", function() {
    let file = this.files[0];
    let filename = file.mozFullPath;
    updateAboutMemoryFromFile(filename);
  });

  // Ditto.
  let fileInput2 =
      appendHiddenFileInput(header, "fileInput2", function(e) {
    let file = this.files[0];
    // First time around, we stash a copy of the filename and reinvoke.  Second
    // time around we do the diff and display.
    if (!this.filename1) {
      this.filename1 = file.mozFullPath;

      // e.skipClick is only true when testing -- it allows fileInput2's
      // onchange handler to be re-called without having to go via the file
      // picker.
      if (!e.skipClick) {
        this.click();
      }
    } else {
      let filename1 = this.filename1;
      delete this.filename1;
      updateAboutMemoryFromTwoFiles(filename1, file.mozFullPath);
    }
  });

  const CuDesc = "Measure current memory reports and show.";
  const LdDesc = "Load memory reports from file and show.";
  const DfDesc = "Load memory report data from two files and show the " +
                 "difference.";

  const SvDesc = "Save memory reports to file.";

  const GCDesc = "Do a global garbage collection.";
  const CCDesc = "Do a cycle collection.";
  const MMDesc = "Send three \"heap-minimize\" notifications in a " +
                 "row.  Each notification triggers a global garbage " +
                 "collection followed by a cycle collection, and causes the " +
                 "process to reduce memory usage in other ways, e.g. by " +
                 "flushing various caches.";

  const GCAndCCLogDesc = "Save garbage collection log and concise cycle " +
                         "collection log.\n" +
                         "WARNING: These logs may be large (>1GB).";
  const GCAndCCAllLogDesc = "Save garbage collection log and verbose cycle " +
                            "collection log.\n" +
                            "WARNING: These logs may be large (>1GB).";

  const DMDEnabledDesc = "Analyze memory reports coverage and save the " +
                         "output to the temp directory.\n";
  const DMDDisabledDesc = "DMD is not running. Please re-start with $DMD and " +
                          "the other relevant environment variables set " +
                          "appropriately.";

  let ops = appendElement(header, "div", "");

  let row1 = appendElement(ops, "div", "opsRow");

  let labelDiv1 =
   appendElementWithText(row1, "div", "opsRowLabel", "Show memory reports");
  let label1 = appendElementWithText(labelDiv1, "label", "");
  gVerbose = appendElement(label1, "input", "");
  gVerbose.type = "checkbox";
  gVerbose.id = "verbose";   // used for testing
  appendTextNode(label1, "verbose");

  const kEllipsis = "\u2026";

  // The "measureButton" id is used for testing.
  appendButton(row1, CuDesc, doMeasure, "Measure", "measureButton");
  appendButton(row1, LdDesc, () => fileInput1.click(), "Load" + kEllipsis);
  appendButton(row1, DfDesc, () => fileInput2.click(),
               "Load and diff" + kEllipsis);

  let row2 = appendElement(ops, "div", "opsRow");

  let labelDiv2 =
    appendElementWithText(row2, "div", "opsRowLabel", "Save memory reports");
  appendButton(row2, SvDesc, saveReportsToFile, "Measure and save" + kEllipsis);

  // XXX: this isn't a great place for this checkbox, but I can't think of
  // anywhere better.
  let label2 = appendElementWithText(labelDiv2, "label", "");
  gAnonymize = appendElement(label2, "input", "");
  gAnonymize.type = "checkbox";
  appendTextNode(label2, "anonymize");

  let row3 = appendElement(ops, "div", "opsRow");

  appendElementWithText(row3, "div", "opsRowLabel", "Free memory");
  appendButton(row3, GCDesc, doGC, "GC");
  appendButton(row3, CCDesc, doCC, "CC");
  appendButton(row3, MMDesc, doMMU, "Minimize memory usage");

  let row4 = appendElement(ops, "div", "opsRow");

  appendElementWithText(row4, "div", "opsRowLabel", "Save GC & CC logs");
  appendButton(row4, GCAndCCLogDesc,
               saveGCLogAndConciseCCLog, "Save concise", "saveLogsConcise");
  appendButton(row4, GCAndCCAllLogDesc,
               saveGCLogAndVerboseCCLog, "Save verbose", "saveLogsVerbose");

  // Three cases here:
  // - DMD is disabled (i.e. not built): don't show the button.
  // - DMD is enabled but is not running: show the button, but disable it.
  // - DMD is enabled and is running: show the button and enable it.
  if (gMgr.isDMDEnabled) {
    let row5 = appendElement(ops, "div", "opsRow");

    appendElementWithText(row5, "div", "opsRowLabel", "Save DMD output");
    let enableButtons = gMgr.isDMDRunning;

    let dmdButton =
      appendButton(row5, enableButtons ? DMDEnabledDesc : DMDDisabledDesc,
                   doDMD, "Save");
    dmdButton.disabled = !enableButtons;
  }

  // Generate the main div, where content ("section" divs) will go.  It's
  // hidden at first.

  gMain = appendElement(document.body, "div", "");
  gMain.id = "mainDiv";

  // Generate the footer.  It's hidden at first.

  gFooter = appendElement(document.body, "div", "ancillary hidden");

  let a = appendElementWithText(gFooter, "a", "option",
                                "Troubleshooting information");
  a.href = "about:support";

  let legendText1 = "Click on a non-leaf node in a tree to expand ('++') " +
                    "or collapse ('--') its children.";
  let legendText2 = "Hover the pointer over the name of a memory report " +
                    "to see a description of what it measures.";

  appendElementWithText(gFooter, "div", "legend", legendText1);
  appendElementWithText(gFooter, "div", "legend hiddenOnMobile", legendText2);

  // See if we're loading from a file.  (Because about:memory is a non-standard
  // URL, location.search is undefined, so we have to use location.href
  // instead.)
  let search = location.href.split("?")[1];
  if (search) {
    let searchSplit = search.split("&");
    for (let i = 0; i < searchSplit.length; i++) {
      if (searchSplit[i].toLowerCase().startsWith("file=")) {
        let filename = searchSplit[i].substring("file=".length);
        updateAboutMemoryFromFile(decodeURIComponent(filename));
        return;
      }
    }
  }
}

// ---------------------------------------------------------------------------

function doGC() {
  Services.obs.notifyObservers(null, "child-gc-request");
  Cu.forceGC();
  updateMainAndFooter("Garbage collection completed", SHOW_TIMESTAMP,
                      HIDE_FOOTER);
}

function doCC() {
  Services.obs.notifyObservers(null, "child-cc-request");
  window.QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindowUtils)
        .cycleCollect();
  updateMainAndFooter("Cycle collection completed", SHOW_TIMESTAMP,
                      HIDE_FOOTER);
}

function doMMU() {
  Services.obs.notifyObservers(null, "child-mmu-request");
  gMgr.minimizeMemoryUsage(
    () => updateMainAndFooter("Memory minimization completed",
                              SHOW_TIMESTAMP, HIDE_FOOTER));
}

function doMeasure() {
  updateAboutMemoryFromReporters();
}

function saveGCLogAndConciseCCLog() {
  dumpGCLogAndCCLog(false);
}

function saveGCLogAndVerboseCCLog() {
  dumpGCLogAndCCLog(true);
}

function doDMD() {
  updateMainAndFooter("Saving memory reports and DMD output...",
                      NO_TIMESTAMP, HIDE_FOOTER);
  try {
    let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
                   .getService(Ci.nsIMemoryInfoDumper);

    dumper.dumpMemoryInfoToTempDir(/* identifier = */ "",
                                   gAnonymize.checked,
                                   /* minimize = */ false);
    updateMainAndFooter("Saved memory reports and DMD reports analysis " +
                        "to the temp directory",
                        SHOW_TIMESTAMP, HIDE_FOOTER);
  } catch (ex) {
    updateMainAndFooter(ex.toString(), NO_TIMESTAMP, HIDE_FOOTER);
  }
}

function dumpGCLogAndCCLog(aVerbose) {
  let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
                .getService(Ci.nsIMemoryInfoDumper);

  let inProgress = updateMainAndFooter("Saving logs...",
                                       NO_TIMESTAMP, HIDE_FOOTER);
  let section = appendElement(gMain, "div", "section");

  function displayInfo(gcLog, ccLog, isParent) {
    appendElementWithText(section, "div", "",
                          "Saved GC log to " + gcLog.path);

    let ccLogType = aVerbose ? "verbose" : "concise";
    appendElementWithText(section, "div", "",
                          "Saved " + ccLogType + " CC log to " + ccLog.path);
  }

  dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ true,
                               { onDump: displayInfo,
                                 onFinish() {
                                   inProgress.remove();
                                 }
                               });
}

/**
 * Top-level function that does the work of generating the page from the memory
 * reporters.
 */
function updateAboutMemoryFromReporters() {
  updateMainAndFooter("Measuring...", NO_TIMESTAMP, HIDE_FOOTER);

  try {
    let processLiveMemoryReports =
        function(aHandleReport, aDisplayReports) {
      let handleReport = function(aProcess, aUnsafePath, aKind, aUnits,
                                  aAmount, aDescription) {
        aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                      aDescription, /* presence = */ undefined);
      }

      let displayReportsAndFooter = function() {
        updateTitleMainAndFooter("live measurement", "", NO_TIMESTAMP,
                                 SHOW_FOOTER);
        aDisplayReports();
      }

      gMgr.getReports(handleReport, null, displayReportsAndFooter, null,
                      gAnonymize.checked);
    }

    // Process the reports from the live memory reporters.
    appendAboutMemoryMain(processLiveMemoryReports,
                          gMgr.hasMozMallocUsableSize);

  } catch (ex) {
    handleException(ex);
  }
}

// Increment this if the JSON format changes.
//
var gCurrentFileFormatVersion = 1;


/**
 * Parse a string as JSON and extract the |memory_report| property if it has
 * one, which indicates the string is from a crash dump.
 *
 * @param aStr
 *        The string.
 * @return The extracted object.
 */
function parseAndUnwrapIfCrashDump(aStr) {
  let obj = JSON.parse(aStr);
  if (obj.memory_report !== undefined) {
    // It looks like a crash dump. The memory reports should be in the
    // |memory_report| property.
    obj = obj.memory_report;
  }
  return obj;
}

/**
 * Populate about:memory using the data in the given JSON object.
 *
 * @param aObj
 *        An object that (hopefully!) conforms to the JSON schema used by
 *        nsIMemoryInfoDumper.
 */
function updateAboutMemoryFromJSONObject(aObj) {
  try {
    assertInput(aObj.version === gCurrentFileFormatVersion,
                "data version number missing or doesn't match");
    assertInput(aObj.hasMozMallocUsableSize !== undefined,
                "missing 'hasMozMallocUsableSize' property");
    assertInput(aObj.reports && aObj.reports instanceof Array,
                "missing or non-array 'reports' property");

    let processMemoryReportsFromFile =
        function(aHandleReport, aDisplayReports) {
      for (let i = 0; i < aObj.reports.length; i++) {
        let r = aObj.reports[i];
        aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
                      r.description, r._presence);
      }
      aDisplayReports();
    }
    appendAboutMemoryMain(processMemoryReportsFromFile,
                          aObj.hasMozMallocUsableSize);
  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Populate about:memory using the data in the given JSON string.
 *
 * @param aStr
 *        A string containing JSON data conforming to the schema used by
 *        nsIMemoryReporterManager::dumpReports.
 */
function updateAboutMemoryFromJSONString(aStr) {
  try {
    let obj = parseAndUnwrapIfCrashDump(aStr);
    updateAboutMemoryFromJSONObject(obj);
  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Loads the contents of a file into a string and passes that to a callback.
 *
 * @param aFilename
 *        The name of the file being read from.
 * @param aTitleNote
 *        A description to put in the page title upon completion.
 * @param aFn
 *        The function to call and pass the read string to upon completion.
 */
function loadMemoryReportsFromFile(aFilename, aTitleNote, aFn) {
  updateMainAndFooter("Loading...", NO_TIMESTAMP, HIDE_FOOTER);

  try {
    let reader = new FileReader();
    reader.onerror = () => { throw new Error("FileReader.onerror"); };
    reader.onabort = () => { throw new Error("FileReader.onabort"); };
    reader.onload = (aEvent) => {
      // Clear "Loading..." from above.
      updateTitleMainAndFooter(aTitleNote, "", NO_TIMESTAMP, SHOW_FOOTER);
      aFn(aEvent.target.result);
    };

    // If it doesn't have a .gz suffix, read it as a (legacy) ungzipped file.
    if (!aFilename.endsWith(".gz")) {
      File.createFromFileName(aFilename).then(file => {
        reader.readAsText(file);
      });
      return;
    }

    // Read compressed gzip file.
    let converter = new nsGzipConverter();
    converter.asyncConvertData("gzip", "uncompressed", {
      data: [],
      onStartRequest(aR, aC) {},
      onDataAvailable(aR, aC, aStream, aO, aCount) {
        let bi = new nsBinaryStream(aStream);
        this.data.push(bi.readBytes(aCount));
      },
      onStopRequest(aR, aC, aStatusCode) {
        try {
          if (!Components.isSuccessCode(aStatusCode)) {
            throw new Components.Exception("Error while reading gzip file", aStatusCode);
          }
          reader.readAsText(new Blob(this.data));
        } catch (ex) {
          handleException(ex);
        }
      }
    }, null);

    let file = new nsFile(aFilename);
    let fileChan = NetUtil.newChannel({
                     uri: Services.io.newFileURI(file),
                     loadUsingSystemPrincipal: true
                   });
    fileChan.asyncOpen2(converter);

  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Like updateAboutMemoryFromReporters(), but gets its data from a file instead
 * of the memory reporters.
 *
 * @param aFilename
 *        The name of the file being read from.  The expected format of the
 *        file's contents is described in a comment in nsIMemoryInfoDumper.idl.
 */
function updateAboutMemoryFromFile(aFilename) {
  loadMemoryReportsFromFile(aFilename, /* title note */ aFilename,
                            updateAboutMemoryFromJSONString);
}

/**
 * Like updateAboutMemoryFromFile(), but gets its data from a two files and
 * diffs them.
 *
 * @param aFilename1
 *        The name of the first file being read from.
 * @param aFilename2
 *        The name of the first file being read from.
 */
function updateAboutMemoryFromTwoFiles(aFilename1, aFilename2) {
  let titleNote = "diff of " + aFilename1 + " and " + aFilename2;
  loadMemoryReportsFromFile(aFilename1, titleNote, function(aStr1) {
    loadMemoryReportsFromFile(aFilename2, titleNote, function(aStr2) {
      try {
        let obj1 = parseAndUnwrapIfCrashDump(aStr1);
        let obj2 = parseAndUnwrapIfCrashDump(aStr2);
        gIsDiff = true;
        updateAboutMemoryFromJSONObject(diffJSONObjects(obj1, obj2));
        gIsDiff = false;
      } catch (ex) {
        handleException(ex);
      }
    });
  });
}

// ---------------------------------------------------------------------------

// Something unlikely to appear in a process name.
var kProcessPathSep = "^:^:^";

// Short for "diff report".
function DReport(aKind, aUnits, aAmount, aDescription, aNMerged, aPresence) {
  this._kind = aKind;
  this._units = aUnits;
  this._amount = aAmount;
  this._description = aDescription;
  this._nMerged = aNMerged;
  if (aPresence !== undefined) {
    this._presence = aPresence;
  }
}

DReport.prototype = {
  assertCompatible(aKind, aUnits) {
    assert(this._kind == aKind, "Mismatched kinds");
    assert(this._units == aUnits, "Mismatched units");

    // We don't check that the "description" properties match.  This is because
    // on Linux we can get cases where the paths are the same but the
    // descriptions differ, like this:
    //
    //   "path": "size/other-files/icon-theme.cache/[r--p]",
    //   "description": "/usr/share/icons/gnome/icon-theme.cache (read-only, not executable, private)"
    //
    //   "path": "size/other-files/icon-theme.cache/[r--p]"
    //   "description": "/usr/share/icons/hicolor/icon-theme.cache (read-only, not executable, private)"
    //
    // In those cases, we just use the description from the first-encountered
    // one, which is what about:memory also does.
    // (Note: reports with those paths are no longer generated, but allowing
    // the descriptions to differ seems reasonable.)
  },

  merge(aJr) {
    this.assertCompatible(aJr.kind, aJr.units);
    this._amount += aJr.amount;
    this._nMerged++;
  },

  toJSON(aProcess, aPath, aAmount) {
    return {
      process:     aProcess,
      path:        aPath,
      kind:        this._kind,
      units:       this._units,
      amount:      aAmount,
      description: this._description,
      _presence:   this._presence
    };
  }
};

// Constants that indicate if a DReport was present only in one of the data
// sets, or had to be added for balance.
DReport.PRESENT_IN_FIRST_ONLY  = 1;
DReport.PRESENT_IN_SECOND_ONLY = 2;
DReport.ADDED_FOR_BALANCE = 3;

/**
 * Make a report map, which has combined path+process strings for keys, and
 * DReport objects for values.
 *
 * @param aJSONReports
 *        The |reports| field of a JSON object.
 * @return The constructed report map.
 */
function makeDReportMap(aJSONReports) {
  let dreportMap = {};
  for (let i = 0; i < aJSONReports.length; i++) {
    let jr = aJSONReports[i];

    assert(jr.process !== undefined, "Missing process");
    assert(jr.path !== undefined, "Missing path");
    assert(jr.kind !== undefined, "Missing kind");
    assert(jr.units !== undefined, "Missing units");
    assert(jr.amount !== undefined, "Missing amount");
    assert(jr.description !== undefined, "Missing description");

    // Strip out some non-deterministic stuff that prevents clean diffs.
    // Ideally the memory reports themselves would contain information about
    // which parts of the the process and path need to be stripped -- saving us
    // from hardwiring knowledge of specific reporters here -- but we have no
    // mechanism for that. (Any future redesign of how memory reporters work
    // should include such a mechanism.)

    // Strip PIDs:
    // - pid 123
    // - pid=123
    let pidRegex = /pid([ =])\d+/g;
    let pidSubst = "pid$1NNN";
    let process = jr.process.replace(pidRegex, pidSubst);
    let path = jr.path.replace(pidRegex, pidSubst);

    // Strip addresses:
    // - .../js-zone(0x12345678)/...
    // - .../zone(0x12345678)/...
    // - .../worker(<URL>, 0x12345678)/...
    path = path.replace(/zone\(0x[0-9A-Fa-f]+\)\//, "zone(0xNNN)/");
    path = path.replace(/\/worker\((.+), 0x[0-9A-Fa-f]+\)\//,
                        "/worker($1, 0xNNN)/");

    // Strip top window IDs:
    // - explicit/window-objects/top(<URL>, id=123)/...
    path = path.replace(/^(explicit\/window-objects\/top\(.*, id=)\d+\)/,
                        "$1NNN)");

    // Strip null principal UUIDs (but not other UUIDs, because they may be
    // deterministic, such as those used by add-ons).
    path = path.replace(
      /moz-nullprincipal:{........-....-....-....-............}/g,
      "moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}");

    // Normalize omni.ja! paths.
    path = path.replace(/jar:file:\\\\\\(.+)\\omni.ja!/,
                        "jar:file:\\\\\\...\\omni.ja!");

    // Normalize script source counts.
    path = path.replace(/source\(scripts=(\d+), /,
                        "source\(scripts=NNN, ");

    let processPath = process + kProcessPathSep + path;
    let rOld = dreportMap[processPath];
    if (rOld === undefined) {
      dreportMap[processPath] =
        new DReport(jr.kind, jr.units, jr.amount, jr.description, 1, undefined);
    } else {
      rOld.merge(jr);
    }
  }
  return dreportMap;
}

// Return a new dreportMap which is the diff of two dreportMaps.  Empties
// aDReportMap2 along the way.
function diffDReportMaps(aDReportMap1, aDReportMap2) {
  let result = {};

  for (let processPath in aDReportMap1) {
    let r1 = aDReportMap1[processPath];
    let r2 = aDReportMap2[processPath];
    let r2_amount, r2_nMerged;
    let presence;
    if (r2 !== undefined) {
      r1.assertCompatible(r2._kind, r2._units);
      r2_amount = r2._amount;
      r2_nMerged = r2._nMerged;
      delete aDReportMap2[processPath];
      presence = undefined;   // represents that it's present in both
    } else {
      r2_amount = 0;
      r2_nMerged = 0;
      presence = DReport.PRESENT_IN_FIRST_ONLY;
    }
    result[processPath] =
      new DReport(r1._kind, r1._units, r2_amount - r1._amount, r1._description,
                  Math.max(r1._nMerged, r2_nMerged), presence);
  }

  for (let processPath in aDReportMap2) {
    let r2 = aDReportMap2[processPath];
    result[processPath] = new DReport(r2._kind, r2._units, r2._amount,
                                      r2._description, r2._nMerged,
                                      DReport.PRESENT_IN_SECOND_ONLY);
  }

  return result;
}

function makeJSONReports(aDReportMap) {
  let reports = [];
  for (let processPath in aDReportMap) {
    let r = aDReportMap[processPath];
    if (r._amount !== 0) {
      // If _nMerged > 1, we give the full (aggregated) amount in the first
      // copy, and then use amount=0 in the remainder.  When viewed in
      // about:memory, this shows up as an entry with a "[2]"-style suffix
      // and the correct amount.
      let split = processPath.split(kProcessPathSep);
      assert(split.length >= 2);
      let process = split.shift();
      let path = split.join();
      reports.push(r.toJSON(process, path, r._amount));
      for (let i = 1; i < r._nMerged; i++) {
        reports.push(r.toJSON(process, path, 0));
      }
    }
  }

  return reports;
}

// Diff two JSON objects holding memory reports.
function diffJSONObjects(aJson1, aJson2) {
  function simpleProp(aProp) {
    assert(aJson1[aProp] !== undefined && aJson1[aProp] === aJson2[aProp],
           aProp + " properties don't match");
    return aJson1[aProp];
  }

  return {
    version: simpleProp("version"),

    hasMozMallocUsableSize: simpleProp("hasMozMallocUsableSize"),

    reports: makeJSONReports(diffDReportMaps(makeDReportMap(aJson1.reports),
                                             makeDReportMap(aJson2.reports)))
  };
}

// ---------------------------------------------------------------------------

// |PColl| is short for "process collection".
function PColl() {
  this._trees = {};
  this._degenerates = {};
  this._heapTotal = 0;
}

/**
 * Processes reports (whether from reporters or from a file) and append the
 * main part of the page.
 *
 * @param aProcessReports
 *        Function that extracts the memory reports from the reporters or from
 *        file.
 * @param aHasMozMallocUsableSize
 *        Boolean indicating if moz_malloc_usable_size works.
 */
function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize) {
  let pcollsByProcess = {};

  function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                        aDescription, aPresence) {
    if (aUnsafePath.startsWith("explicit/")) {
      assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
                  "bad explicit kind");
      assertInput(aUnits === UNITS_BYTES, "bad explicit units");
    }

    assert(aPresence === undefined ||
           aPresence == DReport.PRESENT_IN_FIRST_ONLY ||
           aPresence == DReport.PRESENT_IN_SECOND_ONLY,
           "bad presence");

    let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
    let unsafeNames = aUnsafePath.split("/");
    let unsafeName0 = unsafeNames[0];
    let isDegenerate = unsafeNames.length === 1;

    // Get the PColl table for the process, creating it if necessary.
    let pcoll = pcollsByProcess[process];
    if (!pcollsByProcess[process]) {
      pcoll = pcollsByProcess[process] = new PColl();
    }

    // Get the root node, creating it if necessary.
    let psubcoll = isDegenerate ? pcoll._degenerates : pcoll._trees;
    let t = psubcoll[unsafeName0];
    if (!t) {
      t = psubcoll[unsafeName0] =
        new TreeNode(unsafeName0, aUnits, isDegenerate);
    }

    if (!isDegenerate) {
      // Add any missing nodes in the tree implied by aUnsafePath, and fill in
      // the properties that we can with a top-down traversal.
      for (let i = 1; i < unsafeNames.length; i++) {
        let unsafeName = unsafeNames[i];
        let u = t.findKid(unsafeName);
        if (!u) {
          u = new TreeNode(unsafeName, aUnits, isDegenerate);
          if (!t._kids) {
            t._kids = [];
          }
          t._kids.push(u);
        }
        t = u;
      }

      // Update the heap total if necessary.
      if (unsafeName0 === "explicit" && aKind == KIND_HEAP) {
        pcollsByProcess[process]._heapTotal += aAmount;
      }
    }

    if (t._amount) {
      // Duplicate!  Sum the values and mark it as a dup.
      t._amount += aAmount;
      t._nMerged = t._nMerged ? t._nMerged + 1 : 2;
      assert(t._presence === aPresence, "presence mismatch");
    } else {
      // New leaf node.  Fill in extra node details from the report.
      t._amount = aAmount;
      t._description = aDescription;
      if (aPresence !== undefined) {
        t._presence = aPresence;
      }
    }
  }

  function displayReports() {
    // Sort the processes.
    let processes = Object.keys(pcollsByProcess);
    processes.sort(function(aProcessA, aProcessB) {
      assert(aProcessA != aProcessB,
             "Elements of Object.keys() should be unique, but " +
             "saw duplicate '" + aProcessA + "' elem.");

      // Always put the main process first.
      if (aProcessA == gUnnamedProcessStr) {
        return -1;
      }
      if (aProcessB == gUnnamedProcessStr) {
        return 1;
      }

      // Then sort by resident size.
      let nodeA = pcollsByProcess[aProcessA]._degenerates["resident"];
      let nodeB = pcollsByProcess[aProcessB]._degenerates["resident"];
      let residentA = nodeA ? nodeA._amount : -1;
      let residentB = nodeB ? nodeB._amount : -1;

      if (residentA > residentB) {
        return -1;
      }
      if (residentA < residentB) {
        return 1;
      }

      // Then sort by process name.
      if (aProcessA < aProcessB) {
        return -1;
      }
      if (aProcessA > aProcessB) {
        return 1;
      }

      return 0;
    });

    // Generate output for each process.
    for (let i = 0; i < processes.length; i++) {
      let process = processes[i];
      let section = appendElement(gMain, "div", "section");

      appendProcessAboutMemoryElements(section, i, process,
                                       pcollsByProcess[process]._trees,
                                       pcollsByProcess[process]._degenerates,
                                       pcollsByProcess[process]._heapTotal,
                                       aHasMozMallocUsableSize);
    }
  }

  aProcessReports(handleReport, displayReports);
}

// ---------------------------------------------------------------------------

// There are two kinds of TreeNode.
// - Leaf TreeNodes correspond to reports.
// - Non-leaf TreeNodes are just scaffolding nodes for the tree;  their values
//   are derived from their children.
// Some trees are "degenerate", i.e. they contain a single node, i.e. they
// correspond to a report whose path has no '/' separators.
function TreeNode(aUnsafeName, aUnits, aIsDegenerate) {
  this._units = aUnits;
  this._unsafeName = aUnsafeName;
  if (aIsDegenerate) {
    this._isDegenerate = true;
  }

  // Leaf TreeNodes have these properties added immediately after construction:
  // - _amount
  // - _description
  // - _nMerged (only defined if > 1)
  // - _presence (only defined if value is PRESENT_IN_{FIRST,SECOND}_ONLY)
  //
  // Non-leaf TreeNodes have these properties added later:
  // - _kids
  // - _amount
  // - _description
  // - _hideKids (only defined if true)
  // - _maxAbsDescendant (on-demand, only when gIsDiff is set)
}

TreeNode.prototype = {
  findKid(aUnsafeName) {
    if (this._kids) {
      for (let i = 0; i < this._kids.length; i++) {
        if (this._kids[i]._unsafeName === aUnsafeName) {
          return this._kids[i];
        }
      }
    }
    return undefined;
  },

  // When gIsDiff is false, tree operations -- sorting and determining if a
  // sub-tree is significant -- are straightforward. But when gIsDiff is true,
  // the combination of positive and negative values within a tree complicates
  // things. So for a non-leaf node, instead of just looking at _amount, we
  // instead look at the maximum absolute value of the node and all of its
  // descendants.
  maxAbsDescendant() {
    if (!this._kids) {
      // No kids? Just return the absolute value of the amount.
      return Math.abs(this._amount);
    }

    if ("_maxAbsDescendant" in this) {
      // We've computed this before? Return the saved value.
      return this._maxAbsDescendant;
    }

    // Compute the maximum absolute value of all descendants.
    let max = Math.abs(this._amount);
    for (let i = 0; i < this._kids.length; i++) {
      max = Math.max(max, this._kids[i].maxAbsDescendant());
    }
    this._maxAbsDescendant = max;
    return max;
  },

  toString() {
    switch (this._units) {
      case UNITS_BYTES: return formatBytes(this._amount);
      case UNITS_COUNT:
      case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
      case UNITS_PERCENTAGE: return formatPercentage(this._amount);
      default:
        throw "Invalid memory report(s): bad units in TreeNode.toString";
    }
  }
};

// Sort TreeNodes first by size, then by name.  The latter is important for the
// about:memory tests, which need a predictable ordering of reporters which
// have the same amount.
TreeNode.compareAmounts = function(aA, aB) {
  let a, b;
  if (gIsDiff) {
    a = aA.maxAbsDescendant();
    b = aB.maxAbsDescendant();
  } else {
    a = aA._amount;
    b = aB._amount;
  }
  if (a > b) {
    return -1;
  }
  if (a < b) {
    return 1;
  }
  return TreeNode.compareUnsafeNames(aA, aB);
};

TreeNode.compareUnsafeNames = function(aA, aB) {
  return aA._unsafeName.localeCompare(aB._unsafeName);
};


/**
 * Fill in the remaining properties for the specified tree in a bottom-up
 * fashion.
 *
 * @param aRoot
 *        The tree root.
 */
function fillInTree(aRoot) {
  // Fill in the remaining properties bottom-up.
  function fillInNonLeafNodes(aT) {
    if (!aT._kids) {
      // Leaf node.  Has already been filled in.

    } else if (aT._kids.length === 1 && aT != aRoot) {
      // Non-root, non-leaf node with one child.  Merge the child with the node
      // to avoid redundant entries.
      let kid = aT._kids[0];
      let kidBytes = fillInNonLeafNodes(kid);
      aT._unsafeName += "/" + kid._unsafeName;
      if (kid._kids) {
        aT._kids = kid._kids;
      } else {
        delete aT._kids;
      }
      aT._amount = kidBytes;
      aT._description = kid._description;
      if (kid._nMerged !== undefined) {
        aT._nMerged = kid._nMerged
      }
      assert(!aT._hideKids && !kid._hideKids, "_hideKids set when merging");

    } else {
      // Non-leaf node with multiple children.  Derive its _amount and
      // _description entirely from its children...
      let kidsBytes = 0;
      for (let i = 0; i < aT._kids.length; i++) {
        kidsBytes += fillInNonLeafNodes(aT._kids[i]);
      }

      // ... except in one special case. When diffing two memory report sets,
      // if one set has a node with children and the other has the same node
      // but without children -- e.g. the first has "a/b/c" and "a/b/d", but
      // the second only has "a/b" -- we need to add a fake node "a/b/(fake)"
      // to the second to make the trees comparable. It's ugly, but it works.
      if (aT._amount !== undefined &&
          (aT._presence === DReport.PRESENT_IN_FIRST_ONLY ||
           aT._presence === DReport.PRESENT_IN_SECOND_ONLY)) {
        aT._amount += kidsBytes;
        let fake = new TreeNode("(fake child)", aT._units);
        fake._presence = DReport.ADDED_FOR_BALANCE;
        fake._amount = aT._amount - kidsBytes;
        aT._kids.push(fake);
        delete aT._presence;
      } else {
        assert(aT._amount === undefined,
               "_amount already set for non-leaf node")
        aT._amount = kidsBytes;
      }
      aT._description = "The sum of all entries below this one.";
    }
    return aT._amount;
  }

  // cannotMerge is set because don't want to merge into a tree's root node.
  fillInNonLeafNodes(aRoot);
}

/**
 * Compute the "heap-unclassified" value and insert it into the "explicit"
 * tree.
 *
 * @param aT
 *        The "explicit" tree.
 * @param aHeapAllocatedNode
 *        The "heap-allocated" tree node.
 * @param aHeapTotal
 *        The sum of all explicit HEAP reports for this process.
 * @return A boolean indicating if "heap-allocated" is known for the process.
 */
function addHeapUnclassifiedNode(aT, aHeapAllocatedNode, aHeapTotal) {
  if (aHeapAllocatedNode === undefined)
    return false;

  if (aT.findKid("heap-unclassified")) {
    // heap-unclassified was already calculated, there's nothing left to do.
    // This can happen when memory reports are exported from areweslimyet.com.
    return true;
  }

  assert(aHeapAllocatedNode._isDegenerate, "heap-allocated is not degenerate");
  let heapAllocatedBytes = aHeapAllocatedNode._amount;
  let heapUnclassifiedT = new TreeNode("heap-unclassified", UNITS_BYTES);
  heapUnclassifiedT._amount = heapAllocatedBytes - aHeapTotal;
  heapUnclassifiedT._description =
      "Memory not classified by a more specific report. This includes " +
      "slop bytes due to internal fragmentation in the heap allocator " +
      "(caused when the allocator rounds up request sizes).";
  aT._kids.push(heapUnclassifiedT);
  aT._amount += heapUnclassifiedT._amount;
  return true;
}

/**
 * Sort all kid nodes from largest to smallest, and insert aggregate nodes
 * where appropriate.
 *
 * @param aTotalBytes
 *        The size of the tree's root node.
 * @param aT
 *        The tree.
 */
function sortTreeAndInsertAggregateNodes(aTotalBytes, aT) {
  const kSignificanceThresholdPerc = 1;

  function isInsignificant(aT) {
    if (gVerbose.checked)
      return false;

    let perc = gIsDiff
             ? 100 * aT.maxAbsDescendant() / Math.abs(aTotalBytes)
             : 100 * aT._amount / aTotalBytes;
    return perc < kSignificanceThresholdPerc;
  }

  if (!aT._kids) {
    return;
  }

  aT._kids.sort(TreeNode.compareAmounts);

  // If the first child is insignificant, they all are, and there's no point
  // creating an aggregate node that lacks siblings.  Just set the parent's
  // _hideKids property and process all children.
  if (isInsignificant(aT._kids[0])) {
    aT._hideKids = true;
    for (let i = 0; i < aT._kids.length; i++) {
      sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
    }
    return;
  }

  // Look at all children except the last one.
  let i;
  for (i = 0; i < aT._kids.length - 1; i++) {
    if (isInsignificant(aT._kids[i])) {
      // This child is below the significance threshold.  If there are other
      // (smaller) children remaining, move them under an aggregate node.
      let i0 = i;
      let nAgg = aT._kids.length - i0;
      // Create an aggregate node.  Inherit units from the parent;  everything
      // in the tree should have the same units anyway (we test this later).
      let aggT = new TreeNode("(" + nAgg + " tiny)", aT._units);
      aggT._kids = [];
      let aggBytes = 0;
      for ( ; i < aT._kids.length; i++) {
        aggBytes += aT._kids[i]._amount;
        aggT._kids.push(aT._kids[i]);
      }
      aggT._hideKids = true;
      aggT._amount = aggBytes;
      aggT._description =
        nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
        "% significance threshold.";
      aT._kids.splice(i0, nAgg, aggT);
      aT._kids.sort(TreeNode.compareAmounts);

      // Process the moved children.
      for (i = 0; i < aggT._kids.length; i++) {
        sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
      }
      return;
    }

    sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
  }

  // The first n-1 children were significant.  Don't consider if the last child
  // is significant;  there's no point creating an aggregate node that only has
  // one child.  Just process it.
  sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
}

// Global variable indicating if we've seen any invalid values for this
// process;  it holds the unsafePaths of any such reports.  It is reset for
// each new process.
var gUnsafePathsWithInvalidValuesForThisProcess = [];

function appendWarningElements(aP, aHasKnownHeapAllocated,
                               aHasMozMallocUsableSize) {
  if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
    appendElementWithText(aP, "p", "",
      "WARNING: the 'heap-allocated' memory reporter and the " +
      "moz_malloc_usable_size() function do not work for this platform " +
      "and/or configuration.  This means that 'heap-unclassified' is not " +
      "shown and the 'explicit' tree shows much less memory than it should.\n\n");

  } else if (!aHasKnownHeapAllocated) {
    appendElementWithText(aP, "p", "",
      "WARNING: the 'heap-allocated' memory reporter does not work for this " +
      "platform and/or configuration. This means that 'heap-unclassified' " +
      "is not shown and the 'explicit' tree shows less memory than it should.\n\n");

  } else if (!aHasMozMallocUsableSize) {
    appendElementWithText(aP, "p", "",
      "WARNING: the moz_malloc_usable_size() function does not work for " +
      "this platform and/or configuration.  This means that much of the " +
      "heap-allocated memory is not measured by individual memory reporters " +
      "and so will fall under 'heap-unclassified'.\n\n");
  }

  if (gUnsafePathsWithInvalidValuesForThisProcess.length > 0) {
    let div = appendElement(aP, "div");
    appendElementWithText(div, "p", "",
      "WARNING: the following values are negative or unreasonably large.\n");

    let ul = appendElement(div, "ul");
    for (let i = 0;
         i < gUnsafePathsWithInvalidValuesForThisProcess.length;
         i++) {
      appendTextNode(ul, " ");
      appendElementWithText(ul, "li", "",
        flipBackslashes(gUnsafePathsWithInvalidValuesForThisProcess[i]) + "\n");
    }

    appendElementWithText(div, "p", "",
      "This indicates a defect in one or more memory reporters.  The " +
      "invalid values are highlighted.\n\n");
    gUnsafePathsWithInvalidValuesForThisProcess = [];  // reset for the next process
  }
}

/**
 * Appends the about:memory elements for a single process.
 *
 * @param aP
 *        The parent DOM node.
 * @param aN
 *        The number of the process, starting at 0.
 * @param aProcess
 *        The name of the process.
 * @param aTrees
 *        The table of non-degenerate trees for this process.
 * @param aDegenerates
 *        The table of degenerate trees for this process.
 * @param aHasMozMallocUsableSize
 *        Boolean indicating if moz_malloc_usable_size works.
 * @return The generated text.
 */
function appendProcessAboutMemoryElements(aP, aN, aProcess, aTrees,
                                          aDegenerates, aHeapTotal,
                                          aHasMozMallocUsableSize) {
  const kUpwardsArrow   = "\u2191",
        kDownwardsArrow = "\u2193";

  let appendLink = function(aHere, aThere, aArrow) {
    let link = appendElementWithText(aP, "a", "upDownArrow", aArrow);
    link.href = "#" + aThere + aN;
    link.id = aHere + aN;
    link.title = "Go to the " + aThere + " of " + aProcess;
    link.style = "text-decoration: none";

    // This jumps to the anchor without the page location getting the anchor
    // name tacked onto its end, which is what happens with a vanilla link.
    link.addEventListener("click", function(event) {
      document.documentElement.scrollTop =
        document.querySelector(event.target.href).offsetTop;
      event.preventDefault();
    });

    // This gives nice spacing when we copy and paste.
    appendElementWithText(aP, "span", "", "\n");
  }

  appendElementWithText(aP, "h1", "", aProcess);
  appendLink("start", "end", kDownwardsArrow);

  // We'll fill this in later.
  let warningsDiv = appendElement(aP, "div", "accuracyWarning");

  // The explicit tree.
  let hasExplicitTree;
  let hasKnownHeapAllocated;
  {
    let treeName = "explicit";
    let t = aTrees[treeName];
    if (t) {
      let pre = appendSectionHeader(aP, "Explicit Allocations");
      hasExplicitTree = true;
      fillInTree(t);
      // Using the "heap-allocated" reporter here instead of
      // nsMemoryReporterManager.heapAllocated goes against the usual pattern.
      // But the "heap-allocated" node will go in the tree like the others, so
      // we have to deal with it, and once we're dealing with it, it's easier
      // to keep doing so rather than switching to the distinguished amount.
      hasKnownHeapAllocated =
        aDegenerates &&
        addHeapUnclassifiedNode(t, aDegenerates["heap-allocated"], aHeapTotal);
      sortTreeAndInsertAggregateNodes(t._amount, t);
      t._description = explicitTreeDescription;
      appendTreeElements(pre, t, aProcess, "");
      delete aTrees[treeName];
    }
    appendTextNode(aP, "\n");  // gives nice spacing when we copy and paste
  }

  // Fill in and sort all the non-degenerate other trees.
  let otherTrees = [];
  for (let unsafeName in aTrees) {
    let t = aTrees[unsafeName];
    assert(!t._isDegenerate, "tree is degenerate");
    fillInTree(t);
    sortTreeAndInsertAggregateNodes(t._amount, t);
    otherTrees.push(t);
  }
  otherTrees.sort(TreeNode.compareUnsafeNames);

  // Get the length of the longest root value among the degenerate other trees,
  // and sort them as well.
  let otherDegenerates = [];
  let maxStringLength = 0;
  for (let unsafeName in aDegenerates) {
    let t = aDegenerates[unsafeName];
    assert(t._isDegenerate, "tree is not degenerate");
    let length = t.toString().length;
    if (length > maxStringLength) {
      maxStringLength = length;
    }
    otherDegenerates.push(t);
  }
  otherDegenerates.sort(TreeNode.compareUnsafeNames);

  // Now generate the elements, putting non-degenerate trees first.
  let pre = appendSectionHeader(aP, "Other Measurements");
  for (let i = 0; i < otherTrees.length; i++) {
    let t = otherTrees[i];
    appendTreeElements(pre, t, aProcess, "");
    appendTextNode(pre, "\n");  // blank lines after non-degenerate trees
  }
  for (let i = 0; i < otherDegenerates.length; i++) {
    let t = otherDegenerates[i];
    let padText = pad("", maxStringLength - t.toString().length, " ");
    appendTreeElements(pre, t, aProcess, padText);
  }
  appendTextNode(aP, "\n");  // gives nice spacing when we copy and paste

  // Add any warnings about inaccuracies in the "explicit" tree due to platform
  // limitations.  These must be computed after generating all the text.  The
  // newlines give nice spacing if we copy+paste into a text buffer.
  if (hasExplicitTree) {
    appendWarningElements(warningsDiv, hasKnownHeapAllocated,
                          aHasMozMallocUsableSize);
  }

  appendElementWithText(aP, "h3", "", "End of " + aProcess);
  appendLink("end", "start", kUpwardsArrow);
}

/**
 * Determines if a number has a negative sign when converted to a string.
 * Works even for -0.
 *
 * @param aN
 *        The number.
 * @return A boolean.
 */
function hasNegativeSign(aN) {
  if (aN === 0) {                   // this succeeds for 0 and -0
    return 1 / aN === -Infinity;    // this succeeds for -0
  }
  return aN < 0;
}

/**
 * Formats an int as a human-readable string.
 *
 * @param aN
 *        The integer to format.
 * @param aExtra
 *        An extra string to tack onto the end.
 * @return A human-readable string representing the int.
 *
 * Note: building an array of chars and converting that to a string with
 * Array.join at the end is more memory efficient than using string
 * concatenation.  See bug 722972 for details.
 */
function formatInt(aN, aExtra) {
  let neg = false;
  if (hasNegativeSign(aN)) {
    neg = true;
    aN = -aN;
  }
  let s = [];
  while (true) {
    let k = aN % 1000;
    aN = Math.floor(aN / 1000);
    if (aN > 0) {
      if (k < 10) {
        s.unshift(",00", k);
      } else if (k < 100) {
        s.unshift(",0", k);
      } else {
        s.unshift(",", k);
      }
    } else {
      s.unshift(k);
      break;
    }
  }
  if (neg) {
    s.unshift("-");
  }
  if (aExtra) {
    s.push(aExtra);
  }
  return s.join("");
}

/**
 * Converts a byte count to an appropriate string representation.
 *
 * @param aBytes
 *        The byte count.
 * @return The string representation.
 */
function formatBytes(aBytes) {
  let unit = gVerbose.checked ? " B" : " MB";

  let s;
  if (gVerbose.checked) {
    s = formatInt(aBytes, unit);
  } else {
    let mbytes = (aBytes / (1024 * 1024)).toFixed(2);
    let a = String(mbytes).split(".");
    // If the argument to formatInt() is -0, it will print the negative sign.
    s = formatInt(Number(a[0])) + "." + a[1] + unit;
  }
  return s;
}

/**
 * Converts a percentage to an appropriate string representation.
 *
 * @param aPerc100x
 *        The percentage, multiplied by 100 (see nsIMemoryReporter).
 * @return The string representation
 */
function formatPercentage(aPerc100x) {
  return (aPerc100x / 100).toFixed(2) + "%";
}

/**
 * Right-justifies a string in a field of a given width, padding as necessary.
 *
 * @param aS
 *        The string.
 * @param aN
 *        The field width.
 * @param aC
 *        The char used to pad.
 * @return The string representation.
 */
function pad(aS, aN, aC) {
  let padding = "";
  let n2 = aN - aS.length;
  for (let i = 0; i < n2; i++) {
    padding += aC;
  }
  return padding + aS;
}

// There's a subset of the Unicode "light" box-drawing chars that is widely
// implemented in terminals, and this code sticks to that subset to maximize
// the chance that copying and pasting about:memory output to a terminal will
// work correctly.
const kHorizontal                   = "\u2500",
      kVertical                     = "\u2502",
      kUpAndRight                   = "\u2514",
      kUpAndRight_Right_Right       = "\u2514\u2500\u2500",
      kVerticalAndRight             = "\u251c",
      kVerticalAndRight_Right_Right = "\u251c\u2500\u2500",
      kVertical_Space_Space         = "\u2502  ";

const kNoKidsSep                    = " \u2500\u2500 ",
      kHideKidsSep                  = " ++ ",
      kShowKidsSep                  = " -- ";

function appendMrNameSpan(aP, aDescription, aUnsafeName, aIsInvalid, aNMerged,
                          aPresence) {
  let safeName = flipBackslashes(aUnsafeName);
  if (!aIsInvalid && !aNMerged && !aPresence) {
    safeName += "\n";
  }
  let nameSpan = appendElementWithText(aP, "span", "mrName", safeName);
  nameSpan.title = aDescription;

  if (aIsInvalid) {
    let noteText = " [?!]";
    if (!aNMerged) {
      noteText += "\n";
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
    noteSpan.title =
      "Warning: this value is invalid and indicates a bug in one or more " +
      "memory reporters. ";
  }

  if (aNMerged) {
    let noteText = " [" + aNMerged + "]";
    if (!aPresence) {
      noteText += "\n";
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
    noteSpan.title =
      "This value is the sum of " + aNMerged +
      " memory reports that all have the same path.";
  }

  if (aPresence) {
    let c, title;
    switch (aPresence) {
     case DReport.PRESENT_IN_FIRST_ONLY:
      c = "-";
      title = "This value was only present in the first set of memory reports.";
      break;
     case DReport.PRESENT_IN_SECOND_ONLY:
      c = "+";
      title = "This value was only present in the second set of memory reports.";
      break;
     case DReport.ADDED_FOR_BALANCE:
      c = "!";
      title = "One of the sets of memory reports lacked children for this " +
              "node's parent. This is a fake child node added to make the " +
              "two memory sets comparable.";
      break;
     default: assert(false, "bad presence");
      break;
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote",
                                         " [" + c + "]\n");
    noteSpan.title = title;
  }
}

// This is used to record the (safe) IDs of which sub-trees have been manually
// expanded (marked as true) and collapsed (marked as false).  It's used to
// replicate the collapsed/expanded state when the page is updated.  It can end
// up holding IDs of nodes that no longer exist, e.g. for compartments that
// have been closed.  This doesn't seem like a big deal, because the number is
// limited by the number of entries the user has changed from their original
// state.
var gShowSubtreesBySafeTreeId = {};

function assertClassListContains(e, className) {
  assert(e, "undefined " + className);
  assert(e.classList.contains(className), "classname isn't " + className);
}

function toggle(aEvent) {
  // This relies on each line being a span that contains at least four spans:
  // mrValue, mrPerc, mrSep, mrName, and then zero or more mrNotes.  All
  // whitespace must be within one of these spans for this function to find the
  // right nodes.  And the span containing the children of this line must
  // immediately follow.  Assertions check this.

  // |aEvent.target| will be one of the spans.  Get the outer span.
  let outerSpan = aEvent.target.parentNode;
  assertClassListContains(outerSpan, "hasKids");

  // Toggle the '++'/'--' separator.
  let isExpansion;
  let sepSpan = outerSpan.childNodes[2];
  assertClassListContains(sepSpan, "mrSep");
  if (sepSpan.textContent === kHideKidsSep) {
    isExpansion = true;
    sepSpan.textContent = kShowKidsSep;
  } else if (sepSpan.textContent === kShowKidsSep) {
    isExpansion = false;
    sepSpan.textContent = kHideKidsSep;
  } else {
    assert(false, "bad sepSpan textContent");
  }

  // Toggle visibility of the span containing this node's children.
  let subTreeSpan = outerSpan.nextSibling;
  assertClassListContains(subTreeSpan, "kids");
  subTreeSpan.classList.toggle("hidden");

  // Record/unrecord that this sub-tree was toggled.
  let safeTreeId = outerSpan.id;
  if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
    delete gShowSubtreesBySafeTreeId[safeTreeId];
  } else {
    gShowSubtreesBySafeTreeId[safeTreeId] = isExpansion;
  }
}

function expandPathToThisElement(aElement) {
  if (aElement.classList.contains("kids")) {
    // Unhide the kids.
    aElement.classList.remove("hidden");
    expandPathToThisElement(aElement.previousSibling);  // hasKids

  } else if (aElement.classList.contains("hasKids")) {
    // Change the separator to '--'.
    let sepSpan = aElement.childNodes[2];
    assertClassListContains(sepSpan, "mrSep");
    sepSpan.textContent = kShowKidsSep;
    expandPathToThisElement(aElement.parentNode);       // kids or pre.entries

  } else {
    assertClassListContains(aElement, "entries");
  }
}

/**
 * Appends the elements for the tree, including its heading.
 *
 * @param aP
 *        The parent DOM node.
 * @param aRoot
 *        The tree root.
 * @param aProcess
 *        The process the tree corresponds to.
 * @param aPadText
 *        A string to pad the start of each entry.
 */
function appendTreeElements(aP, aRoot, aProcess, aPadText) {
  /**
   * Appends the elements for a particular tree, without a heading.
   *
   * @param aP
   *        The parent DOM node.
   * @param aProcess
   *        The process the tree corresponds to.
   * @param aUnsafeNames
   *        An array of the names forming the path to aT.
   * @param aRoot
   *        The root of the tree this sub-tree belongs to.
   * @param aT
   *        The tree.
   * @param aTreelineText1
   *        The first part of the treeline for this entry and this entry's
   *        children.
   * @param aTreelineText2a
   *        The second part of the treeline for this entry.
   * @param aTreelineText2b
   *        The second part of the treeline for this entry's children.
   * @param aParentStringLength
   *        The length of the formatted byte count of the top node in the tree.
   */
  function appendTreeElements2(aP, aProcess, aUnsafeNames, aRoot, aT,
                               aTreelineText1, aTreelineText2a,
                               aTreelineText2b, aParentStringLength) {
    function appendN(aS, aC, aN) {
      for (let i = 0; i < aN; i++) {
        aS += aC;
      }
      return aS;
    }

    // The tree line.  Indent more if this entry is narrower than its parent.
    let valueText = aT.toString();
    let extraTreelineLength =
      Math.max(aParentStringLength - valueText.length, 0);
    if (extraTreelineLength > 0) {
      aTreelineText2a =
        appendN(aTreelineText2a, kHorizontal, extraTreelineLength);
      aTreelineText2b =
        appendN(aTreelineText2b, " ", extraTreelineLength);
    }
    let treelineText = aTreelineText1 + aTreelineText2a;
    appendElementWithText(aP, "span", "treeline", treelineText);

    // Detect and record invalid values.  But not if gIsDiff is true, because
    // we expect negative values in that case.
    assertInput(aRoot._units === aT._units,
                "units within a tree are inconsistent");
    let tIsInvalid = false;
    if (!gIsDiff && !(0 <= aT._amount && aT._amount <= aRoot._amount)) {
      tIsInvalid = true;
      let unsafePath = aUnsafeNames.join("/");
      gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
      reportAssertionFailure("Invalid value (" + aT._amount + " / " +
                             aRoot._amount + ") for " +
                             flipBackslashes(unsafePath));
    }

    // For non-leaf nodes, the entire sub-tree is put within a span so it can
    // be collapsed if the node is clicked on.
    let d;
    let sep;
    let showSubtrees;
    if (aT._kids) {
      // Determine if we should show the sub-tree below this entry;  this
      // involves reinstating any previous toggling of the sub-tree.
      let unsafePath = aUnsafeNames.join("/");
      let safeTreeId = aProcess + ":" + flipBackslashes(unsafePath);
      showSubtrees = !aT._hideKids;
      if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
        showSubtrees = gShowSubtreesBySafeTreeId[safeTreeId];
      }
      d = appendElement(aP, "span", "hasKids");
      d.id = safeTreeId;
      d.onclick = toggle;
      sep = showSubtrees ? kShowKidsSep : kHideKidsSep;
    } else {
      assert(!aT._hideKids, "leaf node with _hideKids set")
      sep = kNoKidsSep;
      d = aP;
    }

    // The value.
    appendElementWithText(d, "span", "mrValue" + (tIsInvalid ? " invalid" : ""),
                          valueText);

    // The percentage (omitted for single entries).
    let percText;
    if (!aT._isDegenerate) {
      // Treat 0 / 0 as 100%.
      let num = aRoot._amount === 0 ? 100 : (100 * aT._amount / aRoot._amount);
      let numText = num.toFixed(2);
      percText = numText === "100.00"
               ? " (100.0%)"
               : (0 <= num && num < 10 ? " (0" : " (") + numText + "%)";
      appendElementWithText(d, "span", "mrPerc", percText);
    }

    // The separator.
    appendElementWithText(d, "span", "mrSep", sep);

    // The entry's name.
    appendMrNameSpan(d, aT._description, aT._unsafeName,
                     tIsInvalid, aT._nMerged, aT._presence);

    // In non-verbose mode, invalid nodes can be hidden in collapsed sub-trees.
    // But it's good to always see them, so force this.
    if (!gVerbose.checked && tIsInvalid) {
      expandPathToThisElement(d);
    }

    // Recurse over children.
    if (aT._kids) {
      // The 'kids' class is just used for sanity checking in toggle().
      d = appendElement(aP, "span", showSubtrees ? "kids" : "kids hidden");

      let kidTreelineText1 = aTreelineText1 + aTreelineText2b;
      for (let i = 0; i < aT._kids.length; i++) {
        let kidTreelineText2a, kidTreelineText2b;
        if (i < aT._kids.length - 1) {
          kidTreelineText2a = kVerticalAndRight_Right_Right;
          kidTreelineText2b = kVertical_Space_Space;
        } else {
          kidTreelineText2a = kUpAndRight_Right_Right;
          kidTreelineText2b = "   ";
        }
        aUnsafeNames.push(aT._kids[i]._unsafeName);
        appendTreeElements2(d, aProcess, aUnsafeNames, aRoot, aT._kids[i],
                            kidTreelineText1, kidTreelineText2a,
                            kidTreelineText2b, valueText.length);
        aUnsafeNames.pop();
      }
    }
  }

  let rootStringLength = aRoot.toString().length;
  appendTreeElements2(aP, aProcess, [aRoot._unsafeName], aRoot, aRoot,
                      aPadText, "", "", rootStringLength);
}

// ---------------------------------------------------------------------------

function appendSectionHeader(aP, aText) {
  appendElementWithText(aP, "h2", "", aText + "\n");
  return appendElement(aP, "pre", "entries");
}

// ---------------------------------------------------------------------------

function saveReportsToFile() {
  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  fp.appendFilter("Zipped JSON files", "*.json.gz");
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.filterIndex = 0;
  fp.addToRecentDocs = true;
  fp.defaultString = "memory-report.json.gz";

  let fpFinish = function(file) {
    let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
                   .getService(Ci.nsIMemoryInfoDumper);
    let finishDumping = () => {
      updateMainAndFooter("Saved memory reports to " + file.path,
                          SHOW_TIMESTAMP, HIDE_FOOTER);
    }
    dumper.dumpMemoryReportsToNamedFile(file.path, finishDumping, null,
                                        gAnonymize.checked);
  }

  let fpCallback = function(aResult) {
    if (aResult == Ci.nsIFilePicker.returnOK ||
        aResult == Ci.nsIFilePicker.returnReplace) {
      fpFinish(fp.file);
    }
  };

  try {
    fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
  } catch (ex) {
    // This will fail on Android, since there is no Save as file picker there.
    // Just save to the default downloads dir if it does.
    Downloads.getSystemDownloadsDirectory().then(function(dirPath) {
      let file = FileUtils.File(dirPath);
      file.append(fp.defaultString);
      fpFinish(file);
    });

    return;
  }
  fp.open(fpCallback);
}
PK
!<X0PP/chrome/toolkit/content/global/aboutMemory.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta name="viewport" content="width=device-width"/>
    <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
    <script type="text/javascript" src="chrome://global/content/aboutMemory.js"/>
  </head>

  <body onload="onLoad()" onunload="onUnload()"></body>
</html>
PK
!<da>;>;0chrome/toolkit/content/global/aboutNetworking.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
const FileUtils = Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils
const gEnv = Cc["@mozilla.org/process/environment;1"]
               .getService(Ci.nsIEnvironment);
const gDashboard = Cc["@mozilla.org/network/dashboard;1"]
                     .getService(Ci.nsIDashboard);
const gDirServ = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIDirectoryServiceProvider);

const gRequestNetworkingData = {
  "http": gDashboard.requestHttpConnections,
  "sockets": gDashboard.requestSockets,
  "dns": gDashboard.requestDNSInfo,
  "websockets": gDashboard.requestWebsocketConnections,
  "dnslookuptool": () => {},
  "logging": () => {},
  "rcwn": gDashboard.requestRcwnStats,
};
const gDashboardCallbacks = {
  "http": displayHttp,
  "sockets": displaySockets,
  "dns": displayDns,
  "websockets": displayWebsockets,
  "rcwn": displayRcwnStats,
};

const REFRESH_INTERVAL_MS = 3000;

function col(element) {
  let col = document.createElement("td");
  let content = document.createTextNode(element);
  col.appendChild(content);
  return col;
}

function displayHttp(data) {
  let cont = document.getElementById("http_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "http_content");

  for (let i = 0; i < data.connections.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.connections[i].host));
    row.appendChild(col(data.connections[i].port));
    row.appendChild(col(data.connections[i].spdy));
    row.appendChild(col(data.connections[i].ssl));
    row.appendChild(col(data.connections[i].active.length));
    row.appendChild(col(data.connections[i].idle.length));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displaySockets(data) {
  let cont = document.getElementById("sockets_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "sockets_content");

  for (let i = 0; i < data.sockets.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.sockets[i].host));
    row.appendChild(col(data.sockets[i].port));
    row.appendChild(col(data.sockets[i].tcp));
    row.appendChild(col(data.sockets[i].active));
    row.appendChild(col(data.sockets[i].sent));
    row.appendChild(col(data.sockets[i].received));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayDns(data) {
  let cont = document.getElementById("dns_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "dns_content");

  for (let i = 0; i < data.entries.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.entries[i].hostname));
    row.appendChild(col(data.entries[i].family));
    let column = document.createElement("td");

    for (let j = 0; j < data.entries[i].hostaddr.length; j++) {
      column.appendChild(document.createTextNode(data.entries[i].hostaddr[j]));
      column.appendChild(document.createElement("br"));
    }

    row.appendChild(column);
    row.appendChild(col(data.entries[i].expiration));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayWebsockets(data) {
  let cont = document.getElementById("websockets_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "websockets_content");

  for (let i = 0; i < data.websockets.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.websockets[i].hostport));
    row.appendChild(col(data.websockets[i].encrypted));
    row.appendChild(col(data.websockets[i].msgsent));
    row.appendChild(col(data.websockets[i].msgreceived));
    row.appendChild(col(data.websockets[i].sentsize));
    row.appendChild(col(data.websockets[i].receivedsize));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayRcwnStats(data) {
  let status = Services.prefs.getBoolPref("network.http.rcwn.enabled");
  let cacheWon = data.rcwnCacheWonCount;
  let netWon = data.rcwnNetWonCount;
  let total = data.totalNetworkRequests;
  let cacheSlow = data.cacheSlowCount;
  let cacheNotSlow = data.cacheNotSlowCount;

  document.getElementById("rcwn_status").innerText = status;
  document.getElementById("total_req_count").innerText = total;
  document.getElementById("rcwn_cache_won_count").innerText = cacheWon;
  document.getElementById("rcwn_cache_net_count").innerText = netWon;
  document.getElementById("rcwn_cache_slow").innerText = cacheSlow;
  document.getElementById("rcwn_cache_not_slow").innerText = cacheNotSlow;

  // Keep in sync with CachePerfStats::EDataType in CacheFileUtils.h
  const perfStatTypes = [
    "open",
    "read",
    "write",
    "entryopen",
  ];

  const perfStatFieldNames = [
    "avgShort",
    "avgLong",
    "stddevLong",
  ]

  for (let typeIndex in perfStatTypes) {
    for (let statFieldIndex in perfStatFieldNames) {
      document.getElementById("rcwn_perfstats_" + perfStatTypes[typeIndex] + "_"
                              + perfStatFieldNames[statFieldIndex]).innerText =
        data.perfStats[typeIndex][perfStatFieldNames[statFieldIndex]];
    }
  }
}

function requestAllNetworkingData() {
  for (let id in gRequestNetworkingData)
    requestNetworkingDataForTab(id);
}

function requestNetworkingDataForTab(id) {
  gRequestNetworkingData[id](gDashboardCallbacks[id]);
}

let gInited = false;
function init() {
  if (gInited) {
    return;
  }
  gInited = true;
  gDashboard.enableLogging = true;
  if (Services.prefs.getBoolPref("network.warnOnAboutNetworking")) {
    let div = document.getElementById("warning_message");
    div.classList.add("active");
    div.hidden = false;
    document.getElementById("confpref").addEventListener("click", confirm);
  }

  requestAllNetworkingData();

  let autoRefresh = document.getElementById("autorefcheck");
  if (autoRefresh.checked)
    setAutoRefreshInterval(autoRefresh);

  autoRefresh.addEventListener("click", function() {
    let refrButton = document.getElementById("refreshButton");
    if (this.checked) {
      setAutoRefreshInterval(this);
      refrButton.disabled = "disabled";
    } else {
      clearInterval(this.interval);
      refrButton.disabled = null;
    }
  });

  let refr = document.getElementById("refreshButton");
  refr.addEventListener("click", requestAllNetworkingData);
  if (document.getElementById("autorefcheck").checked)
    refr.disabled = "disabled";

  // Event delegation on #categories element
  let menu = document.getElementById("categories");
  menu.addEventListener("click", function click(e) {
    if (e.target && e.target.parentNode == menu)
      show(e.target);
  });

  let dnsLookupButton = document.getElementById("dnsLookupButton");
  dnsLookupButton.addEventListener("click", function() {
    doLookup();
  });

  let setLogButton = document.getElementById("set-log-file-button");
  setLogButton.addEventListener("click", setLogFile);

  let setModulesButton = document.getElementById("set-log-modules-button");
  setModulesButton.addEventListener("click", setLogModules);

  let startLoggingButton = document.getElementById("start-logging-button");
  startLoggingButton.addEventListener("click", startLogging);

  let stopLoggingButton = document.getElementById("stop-logging-button");
  stopLoggingButton.addEventListener("click", stopLogging);

  try {
    let file = gDirServ.getFile("TmpD", {});
    file.append("log.txt");
    document.getElementById("log-file").value = file.path;
  } catch (e) {
    console.error(e);
  }

  // Update the value of the log file.
  updateLogFile();

  // Update the active log modules
  updateLogModules();

  // If we can't set the file and the modules at runtime,
  // the start and stop buttons wouldn't really do anything.
  if (setLogButton.disabled && setModulesButton.disabled) {
    startLoggingButton.disabled = true;
    stopLoggingButton.disabled = true;
  }

  if (location.hash) {
    let sectionButton = document.getElementById("category-" + location.hash.substring(1));
    if (sectionButton) {
      sectionButton.click();
    }
  }
}

function updateLogFile() {
  let logPath = "";

  // Try to get the environment variable for the log file
  logPath = gEnv.get("MOZ_LOG_FILE") || gEnv.get("NSPR_LOG_FILE");
  let currentLogFile = document.getElementById("current-log-file");
  let setLogFileButton = document.getElementById("set-log-file-button");

  // If the log file was set from an env var, we disable the ability to set it
  // at runtime.
  if (logPath.length > 0) {
    currentLogFile.innerText = logPath;
    setLogFileButton.disabled = true;
  } else {
    // There may be a value set by a pref.
    currentLogFile.innerText = gDashboard.getLogPath();
  }
}

function updateLogModules() {
  // Try to get the environment variable for the log file
  let logModules = gEnv.get("MOZ_LOG") ||
                   gEnv.get("MOZ_LOG_MODULES") ||
                   gEnv.get("NSPR_LOG_MODULES");
  let currentLogModules = document.getElementById("current-log-modules");
  let setLogModulesButton = document.getElementById("set-log-modules-button");
  if (logModules.length > 0) {
    currentLogModules.innerText = logModules;
    // If the log modules are set by an environment variable at startup, do not
    // allow changing them throught a pref. It would be difficult to figure out
    // which ones are enabled and which ones are not. The user probably knows
    // what he they are doing.
    setLogModulesButton.disabled = true;
  } else {
    let activeLogModules = [];
    try {
      if (Services.prefs.getBoolPref("logging.config.add_timestamp")) {
        activeLogModules.push("timestamp");
      }
    } catch (e) {}
    try {
      if (Services.prefs.getBoolPref("logging.config.sync")) {
        activeLogModules.push("sync");
      }
    } catch (e) {}

    let children = Services.prefs.getBranch("logging.").getChildList("", {});

    for (let pref of children) {
      if (pref.startsWith("config.")) {
        continue;
      }

      try {
        let value = Services.prefs.getIntPref(`logging.${pref}`);
        activeLogModules.push(`${pref}:${value}`);
      } catch (e) {
        console.error(e);
      }
    }

    currentLogModules.innerText = activeLogModules.join(",");
  }
}

function setLogFile() {
  let setLogButton = document.getElementById("set-log-file-button");
  if (setLogButton.disabled) {
    // There's no point trying since it wouldn't work anyway.
    return;
  }
  let logFile = document.getElementById("log-file").value.trim();
  Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
  updateLogFile();
}

function clearLogModules() {
  // Turn off all the modules.
  let children = Services.prefs.getBranch("logging.").getChildList("", {});
  for (let pref of children) {
    if (!pref.startsWith("config.")) {
      Services.prefs.clearUserPref(`logging.${pref}`);
    }
  }
  Services.prefs.clearUserPref("logging.config.add_timestamp");
  Services.prefs.clearUserPref("logging.config.sync");
  updateLogModules();
}

function setLogModules() {
  let setLogModulesButton = document.getElementById("set-log-modules-button");
  if (setLogModulesButton.disabled) {
    // The modules were set via env var, so we shouldn't try to change them.
    return;
  }

  let modules = document.getElementById("log-modules").value.trim();

  // Clear previously set log modules.
  clearLogModules();

  let logModules = modules.split(",");
  for (let module of logModules) {
    if (module == "timestamp") {
      Services.prefs.setBoolPref("logging.config.add_timestamp", true);
    } else if (module == "rotate") {
      // XXX: rotate is not yet supported.
    } else if (module == "append") {
      // XXX: append is not yet supported.
    } else if (module == "sync") {
      Services.prefs.setBoolPref("logging.config.sync", true);
    } else {
      let [key, value] = module.split(":");
      Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
    }
  }

  updateLogModules();
}

function startLogging() {
  setLogFile();
  setLogModules();
}

function stopLogging() {
  clearLogModules();
  // clear the log file as well
  Services.prefs.clearUserPref("logging.config.LOG_FILE");
  updateLogFile();
}

function confirm() {
  let div = document.getElementById("warning_message");
  div.classList.remove("active");
  div.hidden = true;
  let warnBox = document.getElementById("warncheck");
  Services.prefs.setBoolPref("network.warnOnAboutNetworking", warnBox.checked);
}

function show(button) {
  let current_tab = document.querySelector(".active");
  let category = button.getAttribute("id").substring("category-".length);
  let content = document.getElementById(category);
  if (current_tab == content)
    return;
  current_tab.classList.remove("active");
  current_tab.hidden = true;
  content.classList.add("active");
  content.hidden = false;

  let current_button = document.querySelector("[selected=true]");
  current_button.removeAttribute("selected");
  button.setAttribute("selected", "true");

  let autoRefresh = document.getElementById("autorefcheck");
  if (autoRefresh.checked) {
    clearInterval(autoRefresh.interval);
    setAutoRefreshInterval(autoRefresh);
  }

  let title = document.getElementById("sectionTitle");
  title.textContent = button.children[0].textContent;
  location.hash = category;
}

function setAutoRefreshInterval(checkBox) {
  let active_tab = document.querySelector(".active");
  checkBox.interval = setInterval(function() {
    requestNetworkingDataForTab(active_tab.id);
  }, REFRESH_INTERVAL_MS);
}

// We use the pageshow event instead of onload. This is needed because sometimes
// the page is loaded via session-restore/bfcache. In such cases we need to call
// init() to keep the page behaviour consistent with the ticked checkboxes.
// Mostly the issue is with the autorefresh checkbox.
window.addEventListener("pageshow", function() {
  init();
});

function doLookup() {
  let host = document.getElementById("host").value;
  if (host) {
    gDashboard.requestDNSLookup(host, displayDNSLookup);
  }
}

function displayDNSLookup(data) {
  let cont = document.getElementById("dnslookuptool_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "dnslookuptool_content");

  if (data.answer) {
    for (let address of data.address) {
      let row = document.createElement("tr");
      row.appendChild(col(address));
      new_cont.appendChild(row);
    }
  } else {
    new_cont.appendChild(col(data.error));
  }

  parent.replaceChild(new_cont, cont);
}
PK
!<b]))3chrome/toolkit/content/global/aboutNetworking.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
<!ENTITY % networkingDTD SYSTEM "chrome://global/locale/aboutNetworking.dtd"> %networkingDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
    <head>
        <title>&aboutNetworking.title;</title>
        <link rel="stylesheet" href="chrome://mozapps/skin/aboutNetworking.css" type="text/css" />
        <script type="application/javascript" src="chrome://global/content/aboutNetworking.js" />
    </head>
    <body id="body">
        <div id="warning_message" class="warningBackground" hidden="true">
            <div class="container">
                <h1 class="title">&aboutNetworking.warning;</h1>
                <div class="toggle-container-with-text">
                    <input id="warncheck" type="checkbox" checked="yes" role="checkbox" />
                    <label for="warncheck">&aboutNetworking.showNextTime;</label>
                </div>
                <div>
                    <button id="confpref" class="primary">&aboutNetworking.ok;</button>
                </div>
            </div>
        </div>
        <div id="categories">
            <div class="category" selected="true" id="category-http">
                <span class="category-name">&aboutNetworking.HTTP;</span>
            </div>
            <div class="category" id="category-sockets">
                <span class="category-name">&aboutNetworking.sockets;</span>
            </div>
            <div class="category" id="category-dns">
                <span class="category-name">&aboutNetworking.dns;</span>
            </div>
            <div class="category" id="category-websockets">
                <span class="category-name">&aboutNetworking.websockets;</span>
            </div>
            <hr></hr>
            <div class="category" id="category-dnslookuptool">
                <span class="category-name">&aboutNetworking.dnsLookup;</span>
            </div>
            <div class="category" id="category-logging">
                <span class="category-name">&aboutNetworking.logging;</span>
            </div>
            <div class="category" id="category-rcwn">
                <span class="category-name">&aboutNetworking.rcwn;</span>
            </div>
        </div>
        <div class="main-content">
            <div class="header">
                <div id="sectionTitle" class="header-name">
                    &aboutNetworking.HTTP;
                </div>
                <div id="refreshDiv" class="toggle-container-with-text">
                    <button id="refreshButton">&aboutNetworking.refresh;</button>
                    <input id="autorefcheck" type="checkbox" name="Autorefresh" role="checkbox" />
                    <label for="autorefcheck">&aboutNetworking.autoRefresh;</label>
                </div>
            </div>

          <div id="http" class="tab active">
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.hostname;</th>
                          <th>&aboutNetworking.port;</th>
                          <th>&aboutNetworking.http2;</th>
                          <th>&aboutNetworking.ssl;</th>
                          <th>&aboutNetworking.active;</th>
                          <th>&aboutNetworking.idle;</th>
                      </tr>
                  </thead>
                  <tbody id="http_content" />
              </table>
          </div>

          <div id="sockets" class="tab" hidden="true">
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.host;</th>
                          <th>&aboutNetworking.port;</th>
                          <th>&aboutNetworking.tcp;</th>
                          <th>&aboutNetworking.active;</th>
                          <th>&aboutNetworking.sent;</th>
                          <th>&aboutNetworking.received;</th>
                      </tr>
                  </thead>
                  <tbody id="sockets_content" />
              </table>
          </div>

          <div id="dns" class="tab" hidden="true">
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.hostname;</th>
                          <th>&aboutNetworking.family;</th>
                          <th>&aboutNetworking.addresses;</th>
                          <th>&aboutNetworking.expires;</th>
                      </tr>
                  </thead>
                  <tbody id="dns_content" />
              </table>
          </div>

          <div id="websockets" class="tab" hidden="true">
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.hostname;</th>
                          <th>&aboutNetworking.ssl;</th>
                          <th>&aboutNetworking.messagesSent;</th>
                          <th>&aboutNetworking.messagesReceived;</th>
                          <th>&aboutNetworking.bytesSent;</th>
                          <th>&aboutNetworking.bytesReceived;</th>
                      </tr>
                  </thead>
                  <tbody id="websockets_content" />
              </table>
          </div>

          <div id="dnslookuptool" class="tab" hidden="true">
              &aboutNetworking.dnsDomain;: <input type="text" name="host" id="host"></input>
              <button id="dnsLookupButton">&aboutNetworking.dnsLookupButton;</button>
              <hr/>
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.dnsLookupTableColumn;</th>
                      </tr>
                  </thead>
                  <tbody id="dnslookuptool_content" />
              </table>
          </div>

          <div id="rcwn" class="tab" hidden="true">
              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.rcwnStatus;</th>
                          <th>&aboutNetworking.totalNetworkRequests;</th>
                          <th>&aboutNetworking.rcwnCacheWonCount;</th>
                          <th>&aboutNetworking.rcwnNetWonCount;</th>
                      </tr>
                  </thead>
                  <tbody id="rcwn_content">
                    <tr>
                      <td id="rcwn_status"> </td>
                      <td id="total_req_count"> </td>
                      <td id="rcwn_cache_won_count"> </td>
                      <td id="rcwn_cache_net_count"> </td>
                    </tr>
                  </tbody>
              </table>

              <br/><br/>

              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.rcwnOperation;</th>
                          <th>&aboutNetworking.rcwnAvgShort;</th>
                          <th>&aboutNetworking.rcwnAvgLong;</th>
                          <th>&aboutNetworking.rcwnStddevLong;</th>
                      </tr>
                  </thead>
                  <tbody id="cacheperf_content">
                      <tr>
                          <td>&aboutNetworking.rcwnPerfOpen;</td>
                          <td id="rcwn_perfstats_open_avgShort"> </td>
                          <td id="rcwn_perfstats_open_avgLong"> </td>
                          <td id="rcwn_perfstats_open_stddevLong"> </td>
                      </tr>
                      <tr>
                          <td>&aboutNetworking.rcwnPerfRead;</td>
                          <td id="rcwn_perfstats_read_avgShort"> </td>
                          <td id="rcwn_perfstats_read_avgLong"> </td>
                          <td id="rcwn_perfstats_read_stddevLong"> </td>
                      </tr>
                      <tr>
                          <td>&aboutNetworking.rcwnPerfWrite;</td>
                          <td id="rcwn_perfstats_write_avgShort"> </td>
                          <td id="rcwn_perfstats_write_avgLong"> </td>
                          <td id="rcwn_perfstats_write_stddevLong"> </td>
                      </tr>
                      <tr>
                          <td>&aboutNetworking.rcwnPerfEntryOpen;</td>
                          <td id="rcwn_perfstats_entryopen_avgShort"> </td>
                          <td id="rcwn_perfstats_entryopen_avgLong"> </td>
                          <td id="rcwn_perfstats_entryopen_stddevLong"> </td>
                      </tr>
                  </tbody>
              </table>

              <br/><br/>

              <table>
                  <thead>
                      <tr>
                          <th>&aboutNetworking.rcwnCacheSlow;</th>
                          <th>&aboutNetworking.rcwnCacheNotSlow;</th>
                      </tr>
                  </thead>
                  <tbody>
                      <tr>
                          <td id="rcwn_cache_slow"> </td>
                          <td id="rcwn_cache_not_slow"> </td>
                      </tr>
                  </tbody>
              </table>
          </div>

          <div id="logging" class="tab" hidden="true">
            <div>
              &aboutNetworking.logTutorial;
            </div>
            <br/>
            <div>
              <button id="start-logging-button"> &aboutNetworking.startLogging; </button>
              <button id="stop-logging-button"> &aboutNetworking.stopLogging; </button>
            </div>
            <br/>
            <br/>
            <div>
              &aboutNetworking.currentLogFile; <div id="current-log-file"></div><br/>
              <input type="text" name="log-file" id="log-file"></input>
              <button id="set-log-file-button"> &aboutNetworking.setLogFile; </button>
            </div>
            <div>
              &aboutNetworking.currentLogModules; <div id="current-log-modules"></div><br/>
              <input type="text" name="log-modules" id="log-modules" value="timestamp,sync,nsHttp:5,nsSocketTransport:5,nsStreamPump:5,nsHostResolver:5"></input>
              <button id="set-log-modules-button"> &aboutNetworking.setLogModules; </button>
            </div>
          </div>

        </div>
    </body>
</html>

PK
!<9	''1chrome/toolkit/content/global/aboutPerformance.js/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */

"use strict";

var { classes: Cc, interfaces: Ci, utils: Cu } = Components;

const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { ObjectUtils } = Cu.import("resource://gre/modules/ObjectUtils.jsm", {});
const { Memory } = Cu.import("resource://gre/modules/Memory.jsm", {});
const { DownloadUtils } = Cu.import("resource://gre/modules/DownloadUtils.jsm", {});

// about:performance observes notifications on this topic.
// if a notification is sent, this causes the page to be updated immediately,
// regardless of whether the page is on pause.
const TEST_DRIVER_TOPIC = "test-about:performance-test-driver";

// about:performance posts notifications on this topic whenever the page
// is updated.
const UPDATE_COMPLETE_TOPIC = "about:performance-update-complete";

// How often we should add a sample to our buffer.
const BUFFER_SAMPLING_RATE_MS = 1000;

// The age of the oldest sample to keep.
const BUFFER_DURATION_MS = 10000;

// How often we should update
const UPDATE_INTERVAL_MS = 5000;

// The name of the application
const BRAND_BUNDLE = Services.strings.createBundle(
  "chrome://branding/locale/brand.properties");
const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");

// The maximal number of items to display before showing a "Show All"
// button.
const MAX_NUMBER_OF_ITEMS_TO_DISPLAY = 3;

// If the frequency of alerts is below this value,
// we consider that the feature has no impact.
const MAX_FREQUENCY_FOR_NO_IMPACT = .05;
// If the frequency of alerts is above `MAX_FREQUENCY_FOR_NO_IMPACT`
// and below this value, we consider that the feature impacts the
// user rarely.
const MAX_FREQUENCY_FOR_RARE = .1;
// If the frequency of alerts is above `MAX_FREQUENCY_FOR_FREQUENT`
// and below this value, we consider that the feature impacts the
// user frequently. Anything above is consider permanent.
const MAX_FREQUENCY_FOR_FREQUENT = .5;

// If the number of high-impact alerts among all alerts is above
// this value, we consider that the feature has a major impact
// on user experience.
const MIN_PROPORTION_FOR_MAJOR_IMPACT = .05;
// Otherwise and if the number of medium-impact alerts among all
// alerts is above this value, we consider that the feature has
// a noticeable impact on user experience.
const MIN_PROPORTION_FOR_NOTICEABLE_IMPACT = .1;

// The current mode. Either `MODE_GLOBAL` to display a summary of results
// since we opened about:performance or `MODE_RECENT` to display the latest
// BUFFER_DURATION_MS ms.
const MODE_GLOBAL = "global";
const MODE_RECENT = "recent";

let tabFinder = {
  update() {
    this._map = new Map();
    let windows = Services.wm.getEnumerator("navigator:browser");
    while (windows.hasMoreElements()) {
      let win = windows.getNext();
      let tabbrowser = win.gBrowser;
      for (let browser of tabbrowser.browsers) {
        let id = browser.outerWindowID; // May be `null` if the browser isn't loaded yet
        if (id != null) {
          this._map.set(id, browser);
        }
      }
    }
  },

  /**
   * Find the <xul:tab> for a window id.
   *
   * This is useful e.g. for reloading or closing tabs.
   *
   * @return null If the xul:tab could not be found, e.g. if the
   * windowId is that of a chrome window.
   * @return {{tabbrowser: <xul:tabbrowser>, tab: <xul.tab>}} The
   * tabbrowser and tab if the latter could be found.
   */
  get(id) {
    let browser = this._map.get(id);
    if (!browser) {
      return null;
    }
    let tabbrowser = browser.getTabBrowser();
    return {tabbrowser, tab: tabbrowser.getTabForBrowser(browser)};
  },

  getAny(ids) {
    for (let id of ids) {
      let result = this.get(id);
      if (result) {
        return result;
      }
    }
    return null;
  }
};

/**
 * Returns a Promise that's resolved after the next turn of the event loop.
 *
 * Just returning a resolved Promise would mean that any `then` callbacks
 * would be called right after the end of the current turn, so `setTimeout`
 * is used to delay Promise resolution until the next turn.
 *
 * In mochi tests, it's possible for this to be called after the
 * about:performance window has been torn down, which causes `setTimeout` to
 * throw an NS_ERROR_NOT_INITIALIZED exception. In that case, returning
 * `undefined` is fine.
 */
function wait(ms = 0) {
  try {
    let resolve;
    let p = new Promise(resolve_ => { resolve = resolve_ });
    setTimeout(resolve, ms);
    return p;
  } catch (e) {
    dump("WARNING: wait aborted because of an invalid Window state in aboutPerformance.js.\n");
    return undefined;
  }
}

/**
 * The performance of a webpage between two instants.
 *
 * Clients should call `promiseInit()` before using the methods of this object.
 *
 * @param {PerformanceDiff} The underlying performance data.
 * @param {"webpages"} The kind of delta represented by this object.
 * @param {Map<groupId, timestamp>} ageMap A map containing the oldest known
 *  appearance of each groupId, used to determine how long we have been monitoring
 *  this item.
 * @param {Map<Delta key, Array>} alertMap A map containing the alerts that each
 *  item has already triggered in the past.
 */
function Delta(diff, kind, snapshotDate, ageMap, alertMap) {
  if (kind != "webpages") {
    throw new TypeError(`Unknown kind: ${kind}`);
  }

  /**
   * We only understand "webpages" right now.
   */
  this.kind = kind;

  /**
   * The underlying PerformanceDiff.
   * @type {PerformanceDiff}
   */
  this.diff = diff;

  /**
   * A key unique to the item (webpage), shared by successive
   * instances of `Delta`.
   * @type{string}
   */
  this.key = kind + diff.key;

  // Find the oldest occurrence of this item.
  let creationDate = snapshotDate;
  for (let groupId of diff.groupIds) {
    let date = ageMap.get(groupId);
    if (date && date <= creationDate) {
      creationDate = date;
    }
  }

  /**
   * The timestamp at which the data was measured.
   */
  this.creationDate = creationDate;

  /**
   * Number of milliseconds since the start of the measure.
   */
  this.age = snapshotDate - creationDate;

  /**
   * A UX-friendly, human-readable name for this item.
   */
  this.readableName = null;

  /**
   * A complete name, possibly useful for power users or debugging.
   */
  this.fullName = null;


  // `true` once initialization is complete.
  this._initialized = false;
  // `true` if this item should be displayed
  this._show = false;

  /**
   * All the alerts that this item has caused since about:performance
   * was opened.
   */
  this.alerts = (alertMap.get(this.key) || []).slice();
  switch (this.slowness) {
    case 0: break;
    case 1: this.alerts[0] = (this.alerts[0] || 0) + 1; break;
    case 2: this.alerts[1] = (this.alerts[1] || 0) + 1; break;
    default: throw new Error();
  }
}
Delta.prototype = {
  /**
   * `true` if this item should be displayed, `false` otherwise.
   */
  get show() {
    this._ensureInitialized();
    return this._show;
  },

  /**
   * Estimate the slowness of this item.
   *
   * @return 0 if the item has good performance.
   * @return 1 if the item has average performance.
   * @return 2 if the item has poor performance.
   */
  get slowness() {
    if (Delta.compare(this, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
      return 0;
    }
    if (Delta.compare(this, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE) <= 0) {
      return 1;
    }
    return 2;
  },
  _ensureInitialized() {
    if (!this._initialized) {
      throw new Error();
    }
  },

  /**
   * Initialize, asynchronously.
   */
  promiseInit() {
    if (this.kind == "webpages") {
      return this._initWebpage();
    }
    throw new TypeError();
  },
  _initWebpage() {
    this._initialized = true;
    let found = tabFinder.getAny(this.diff.windowIds);
    if (!found || found.tab.linkedBrowser.contentTitle == null) {
      // Either this is not a real page or the page isn't restored yet.
      return;
    }

    this.readableName = found.tab.linkedBrowser.contentTitle;
    this.fullName = this.diff.names.join(", ");
    this._show = true;
  },
  toString() {
    return `[Delta] ${this.diff.key} => ${this.readableName}, ${this.fullName}`;
  }
};

Delta.compare = function(a, b) {
  return (
    (a.diff.jank.longestDuration - b.diff.jank.longestDuration) ||
    (a.diff.jank.totalUserTime - b.diff.jank.totalUserTime) ||
    (a.diff.jank.totalSystemTime - b.diff.jank.totalSystemTime) ||
    (a.diff.cpow.totalCPOWTime - b.diff.cpow.totalCPOWTime) ||
    (a.diff.ticks.ticks - b.diff.ticks.ticks) ||
    0
  );
};

Delta.revCompare = function(a, b) {
  return -Delta.compare(a, b);
};

/**
 * The highest value considered "good performance".
 */
Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE = {
  diff: {
    cpow: {
      totalCPOWTime: 0,
    },
    jank: {
      longestDuration: 3,
      totalUserTime: Number.POSITIVE_INFINITY,
      totalSystemTime: Number.POSITIVE_INFINITY
    },
    ticks: {
      ticks: Number.POSITIVE_INFINITY,
    }
  }
};

/**
 * The highest value considered "average performance".
 */
Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE = {
  diff: {
    cpow: {
      totalCPOWTime: Number.POSITIVE_INFINITY,
    },
    jank: {
      longestDuration: 7,
      totalUserTime: Number.POSITIVE_INFINITY,
      totalSystemTime: Number.POSITIVE_INFINITY
    },
    ticks: {
      ticks: Number.POSITIVE_INFINITY,
    }
  }
};

/**
 * Utilities for dealing with state
 */
var State = {
  _monitor: PerformanceStats.getMonitor([
    "jank", "cpow", "ticks",
  ]),

  /**
   * Indexed by the number of minutes since the snapshot was taken.
   *
   * @type {Array<ApplicationSnapshot>}
   */
  _buffer: [],
  /**
   * The first snapshot since opening the page.
   *
   * @type ApplicationSnapshot
   */
  _oldest: null,

  /**
   * The latest snapshot.
   *
   * @type ApplicationSnapshot
   */
  _latest: null,

  /**
   * The performance alerts for each group.
   *
   * This map is cleaned up during each update to avoid leaking references
   * to groups that have been gc-ed.
   *
   * @type{Map<Delta key, Array<number>} A map in which the keys are provided
   * by property `key` of instances of `Delta` and the values are arrays
   * [number of moderate-impact alerts, number of high-impact alerts]
   */
  _alerts: new Map(),

  /**
   * The date at which each group was first seen.
   *
   * This map is cleaned up during each update to avoid leaking references
   * to groups that have been gc-ed.
   *
   * @type{Map<string, timestamp} A map in which keys are
   * values for `delta.groupId` and values are approximate
   * dates at which the group was first encountered, as provided
   * by `Cu.now()``.
   */
  _firstSeen: new Map(),

  /**
   * Update the internal state.
   *
   * @return {Promise}
   */
  async update() {
    // If the buffer is empty, add one value for bootstraping purposes.
    if (this._buffer.length == 0) {
      if (this._oldest) {
        throw new Error("Internal Error, we shouldn't have a `_oldest` value yet.");
      }
      this._latest = this._oldest = await this._monitor.promiseSnapshot();
      this._buffer.push(this._oldest);
      await wait(BUFFER_SAMPLING_RATE_MS * 1.1);
    }


    let now = Cu.now();

    // If we haven't sampled in a while, add a sample to the buffer.
    let latestInBuffer = this._buffer[this._buffer.length - 1];
    let deltaT = now - latestInBuffer.date;
    if (deltaT > BUFFER_SAMPLING_RATE_MS) {
      this._latest = await this._monitor.promiseSnapshot();
      this._buffer.push(this._latest);
    }

    // If we have too many samples, remove the oldest sample.
    let oldestInBuffer = this._buffer[0];
    if (oldestInBuffer.date + BUFFER_DURATION_MS < this._latest.date) {
      this._buffer.shift();
    }
  },

  /**
   * @return {Promise}
   */
  promiseDeltaSinceStartOfTime() {
    return this._promiseDeltaSince(this._oldest);
  },

  /**
   * @return {Promise}
   */
  promiseDeltaSinceStartOfBuffer() {
    return this._promiseDeltaSince(this._buffer[0]);
  },

  /**
   * @return {Promise}
   * @resolve {{
   *  webpages: Array<Delta>,
   *  deltas: Set<Delta key>,
   *  duration: number of milliseconds
   * }}
   */
  async _promiseDeltaSince(oldest) {
    let current = this._latest;
    if (!oldest) {
      throw new TypeError();
    }
    if (!current) {
      throw new TypeError();
    }

    tabFinder.update();
    // We rebuild the maps during each iteration to make sure that
    // we do not maintain references to groups that has been removed
    // (e.g. pages that have been closed).
    let oldFirstSeen = this._firstSeen;
    let cleanedUpFirstSeen = new Map();

    let oldAlerts = this._alerts;
    let cleanedUpAlerts = new Map();

    let result = {
      webpages: [],
      deltas: new Set(),
      duration: current.date - oldest.date
    };

    for (let kind of ["webpages"]) {
      for (let [key, value] of current[kind]) {
        let item = ObjectUtils.strict(new Delta(value.subtract(oldest[kind].get(key)), kind, current.date, oldFirstSeen, oldAlerts));
        await item.promiseInit();

        if (!item.show) {
          continue;
        }
        result[kind].push(item);
        result.deltas.add(item.key);

        for (let groupId of item.diff.groupIds) {
          cleanedUpFirstSeen.set(groupId, item.creationDate);
        }
        cleanedUpAlerts.set(item.key, item.alerts);
      }
    }

    this._firstSeen = cleanedUpFirstSeen;
    this._alerts = cleanedUpAlerts;
    return result;
  },
};

var View = {
  /**
   * A cache for all the per-item DOM elements that are reused across refreshes.
   *
   * Reusing the same elements means that elements that were hidden (respectively
   * visible) in an iteration remain hidden (resp visible) in the next iteration.
   */
  DOMCache: {
    _map: new Map(),
    /**
     * @param {string} deltaKey The key for the item that we are displaying.
     * @return {null} If the `deltaKey` doesn't have a component cached yet.
     * Otherwise, the value stored with `set`.
     */
    get(deltaKey) {
      return this._map.get(deltaKey);
    },
    set(deltaKey, value) {
      this._map.set(deltaKey, value);
    },
    /**
     * Remove all the elements whose key does not appear in `set`.
     *
     * @param {Set} set a set of deltaKey.
     */
    trimTo(set) {
      let remove = [];
      for (let key of this._map.keys()) {
        if (!set.has(key)) {
          remove.push(key);
        }
      }
      for (let key of remove) {
        this._map.delete(key);
      }
    }
  },
  /**
   * Display the items in a category.
   *
   * @param {Array<PerformanceDiff>} subset The items to display. They will
   * be displayed in the order of `subset`.
   * @param {string} id The id of the DOM element that will contain the items.
   * @param {string} nature The nature of the subset. One of "webpages" or "system".
   * @param {string} currentMode The current display mode. One of MODE_GLOBAL or MODE_RECENT.
   */
  updateCategory(subset, id, nature, currentMode) {
    subset = subset.slice().sort(Delta.revCompare);


    // Grab everything from the DOM before cleaning up
    this._setupStructure(id);

    // An array of `cachedElements` that need to be added
    let toAdd = [];
    for (let delta of subset) {
      if (!(delta instanceof Delta)) {
        throw new TypeError();
      }
      let cachedElements = this._grabOrCreateElements(delta, nature);
      toAdd.push(cachedElements);
      cachedElements.eltTitle.textContent = delta.readableName;
      cachedElements.eltName.textContent = `Full name: ${delta.fullName}.`;
      cachedElements.eltLoaded.textContent = `Measure start: ${Math.round(delta.age / 1000)} seconds ago.`

      let processes = delta.diff.processes.map(proc => `${proc.processId} (${proc.isChildProcess ? "child" : "parent"})`);
      cachedElements.eltProcess.textContent = `Processes: ${processes.join(", ")}`;

      let eltImpact = cachedElements.eltImpact;
      if (currentMode == MODE_RECENT) {
        cachedElements.eltRoot.setAttribute("impact", delta.diff.jank.longestDuration + 1);
        if (Delta.compare(delta, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
          eltImpact.textContent = ` currently performs well.`;
        } else if (Delta.compare(delta, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE)) {
          eltImpact.textContent = ` may currently be slowing down ${BRAND_NAME}.`;
        } else {
          eltImpact.textContent = ` is currently considerably slowing down ${BRAND_NAME}.`;
        }

        cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.diff.jank.longestDuration + 1}/${delta.diff.jank.durations.length}`;
        cachedElements.eltCPU.textContent = `CPU usage: ${Math.ceil(delta.diff.jank.totalCPUTime / delta.diff.deltaT / 10)}%.`;
        cachedElements.eltSystem.textContent = `System usage: ${Math.ceil(delta.diff.jank.totalSystemTime / delta.diff.deltaT / 10)}%.`;
        cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.diff.cpow.totalCPOWTime / delta.diff.deltaT / 10)}%.`;
      } else {
        if (delta.alerts.length == 0) {
          eltImpact.textContent = " has performed well so far.";
          cachedElements.eltFPS.textContent = `Impact on framerate: no impact.`;
          cachedElements.eltRoot.setAttribute("impact", 0);
        } else {
          let impact = 0;
          let sum = /* medium impact */ delta.alerts[0] + /* high impact */ delta.alerts[1];
          let frequency = sum * 1000 / delta.diff.deltaT;

          let describeFrequency;
          if (frequency <= MAX_FREQUENCY_FOR_NO_IMPACT) {
            describeFrequency = `has no impact on the performance of ${BRAND_NAME}.`
          } else {
            let describeImpact;
            if (frequency <= MAX_FREQUENCY_FOR_RARE) {
              describeFrequency = `rarely slows down ${BRAND_NAME}.`;
              impact += 1;
            } else if (frequency <= MAX_FREQUENCY_FOR_FREQUENT) {
              describeFrequency = `has slown down ${BRAND_NAME} frequently.`;
              impact += 2.5;
            } else {
              describeFrequency = `seems to have slown down ${BRAND_NAME} very often.`;
              impact += 5;
            }
            // At this stage, `sum != 0`
            if (delta.alerts[1] / sum > MIN_PROPORTION_FOR_MAJOR_IMPACT) {
              describeImpact = "When this happens, the slowdown is generally important."
              impact *= 2;
            } else {
              describeImpact = "When this happens, the slowdown is generally noticeable."
            }

            eltImpact.textContent = ` ${describeFrequency} ${describeImpact}`;
            cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.alerts[1] || 0} high-impacts, ${delta.alerts[0] || 0} medium-impact.`;
          }
          cachedElements.eltRoot.setAttribute("impact", Math.round(impact));
        }

        cachedElements.eltCPU.textContent = `CPU usage: ${Math.ceil(delta.diff.jank.totalCPUTime / delta.diff.deltaT / 10)}% (total ${delta.diff.jank.totalUserTime}ms).`;
        cachedElements.eltSystem.textContent = `System usage: ${Math.ceil(delta.diff.jank.totalSystemTime / delta.diff.deltaT / 10)}% (total ${delta.diff.jank.totalSystemTime}ms).`;
        cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.diff.cpow.totalCPOWTime / delta.diff.deltaT / 10)}% (total ${delta.diff.cpow.totalCPOWTime}ms).`;
      }
    }
    this._insertElements(toAdd, id);
  },

  _insertElements(elements, id) {
    let eltContainer = document.getElementById(id);
    eltContainer.classList.remove("measuring");
    eltContainer.eltVisibleContent.innerHTML = "";
    eltContainer.eltHiddenContent.innerHTML = "";
    eltContainer.appendChild(eltContainer.eltShowMore);

    for (let i = 0; i < elements.length && i < MAX_NUMBER_OF_ITEMS_TO_DISPLAY; ++i) {
      let cachedElements = elements[i];
      eltContainer.eltVisibleContent.appendChild(cachedElements.eltRoot);
    }
    for (let i = MAX_NUMBER_OF_ITEMS_TO_DISPLAY; i < elements.length; ++i) {
      let cachedElements = elements[i];
      eltContainer.eltHiddenContent.appendChild(cachedElements.eltRoot);
    }
    if (elements.length <= MAX_NUMBER_OF_ITEMS_TO_DISPLAY) {
      eltContainer.eltShowMore.classList.add("hidden");
    } else {
      eltContainer.eltShowMore.classList.remove("hidden");
    }
    if (elements.length == 0) {
      eltContainer.textContent = "Nothing";
    }
  },
  _setupStructure(id) {
    let eltContainer = document.getElementById(id);
    if (!eltContainer.eltVisibleContent) {
      eltContainer.eltVisibleContent = document.createElement("ul");
      eltContainer.eltVisibleContent.classList.add("visible_items");
      eltContainer.appendChild(eltContainer.eltVisibleContent);
    }
    if (!eltContainer.eltHiddenContent) {
      eltContainer.eltHiddenContent = document.createElement("ul");
      eltContainer.eltHiddenContent.classList.add("hidden");
      eltContainer.eltHiddenContent.classList.add("hidden_additional_items");
      eltContainer.appendChild(eltContainer.eltHiddenContent);
    }
    if (!eltContainer.eltShowMore) {
      eltContainer.eltShowMore = document.createElement("button");
      eltContainer.eltShowMore.textContent = "Show all";
      eltContainer.eltShowMore.classList.add("show_all_items");
      eltContainer.appendChild(eltContainer.eltShowMore);
      eltContainer.eltShowMore.addEventListener("click", function() {
        if (eltContainer.eltHiddenContent.classList.contains("hidden")) {
          eltContainer.eltHiddenContent.classList.remove("hidden");
          eltContainer.eltShowMore.textContent = "Hide";
        } else {
          eltContainer.eltHiddenContent.classList.add("hidden");
          eltContainer.eltShowMore.textContent = "Show all";
        }
      });
    }
    return eltContainer;
  },

  _grabOrCreateElements(delta, nature) {
    let cachedElements = this.DOMCache.get(delta.key);
    if (cachedElements) {
      if (cachedElements.eltRoot.parentElement) {
        cachedElements.eltRoot.parentElement.removeChild(cachedElements.eltRoot);
      }
    } else {
      this.DOMCache.set(delta.key, cachedElements = {});

      let eltDelta = document.createElement("li");
      eltDelta.classList.add("delta");
      cachedElements.eltRoot = eltDelta;

      let eltSpan = document.createElement("span");
      eltDelta.appendChild(eltSpan);

      let eltSummary = document.createElement("span");
      eltSummary.classList.add("summary");
      eltSpan.appendChild(eltSummary);

      let eltTitle = document.createElement("span");
      eltTitle.classList.add("title");
      eltSummary.appendChild(eltTitle);
      cachedElements.eltTitle = eltTitle;

      let eltImpact = document.createElement("span");
      eltImpact.classList.add("impact");
      eltSummary.appendChild(eltImpact);
      cachedElements.eltImpact = eltImpact;

      let eltShowMore = document.createElement("a");
      eltShowMore.classList.add("more");
      eltSpan.appendChild(eltShowMore);
      eltShowMore.textContent = "more";
      eltShowMore.href = "";
      eltShowMore.addEventListener("click", () => {
        if (eltDetails.classList.contains("hidden")) {
          eltDetails.classList.remove("hidden");
          eltShowMore.textContent = "less";
        } else {
          eltDetails.classList.add("hidden");
          eltShowMore.textContent = "more";
        }
      });

      // Add buttons
      if (nature == "webpages") {
        eltSpan.appendChild(document.createElement("br"));

        let eltCloseTab = document.createElement("button");
        eltCloseTab.textContent = "Close tab";
        eltSpan.appendChild(eltCloseTab);
        let windowIds = delta.diff.windowIds;
        eltCloseTab.addEventListener("click", () => {
          let found = tabFinder.getAny(windowIds);
          if (!found) {
            // Cannot find the tab. Maybe it is closed already?
            return;
          }
          let {tabbrowser, tab} = found;
          tabbrowser.removeTab(tab);
        });

        let eltReloadTab = document.createElement("button");
        eltReloadTab.textContent = "Reload tab";
        eltSpan.appendChild(eltReloadTab);
        eltReloadTab.addEventListener("click", () => {
          let found = tabFinder.getAny(windowIds);
          if (!found) {
            // Cannot find the tab. Maybe it is closed already?
            return;
          }
          let {tabbrowser, tab} = found;
          tabbrowser.reloadTab(tab);
        });
      }

      // Prepare details
      let eltDetails = document.createElement("ul");
      eltDetails.classList.add("details");
      eltDetails.classList.add("hidden");
      eltSpan.appendChild(eltDetails);

      for (let [name, className] of [
        ["eltName", "name"],
        ["eltFPS", "fps"],
        ["eltCPU", "cpu"],
        ["eltSystem", "system"],
        ["eltCPOW", "cpow"],
        ["eltLoaded", "loaded"],
        ["eltProcess", "process"],
      ]) {
        let elt = document.createElement("li");
        elt.classList.add(className);
        eltDetails.appendChild(elt);
        cachedElements[name] = elt;
      }
    }

    return cachedElements;
  },
};

var Control = {
  init() {
    this._initAutorefresh();
    this._initDisplayMode();
  },
  async update() {
    let mode = this._displayMode;
    if (this._autoRefreshInterval || !State._buffer[0]) {
      // Update the state only if we are not on pause.
      await State.update();
    }
    await wait(0);
    let state = await (mode == MODE_GLOBAL ?
      State.promiseDeltaSinceStartOfTime() :
      State.promiseDeltaSinceStartOfBuffer());

    for (let category of ["webpages"]) {
      await wait(0);
      await View.updateCategory(state[category], category, category, mode);
    }
    await wait(0);

    // Make sure that we do not keep obsolete stuff around.
    View.DOMCache.trimTo(state.deltas);

    await wait(0);

    // Inform watchers
    Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, mode);
  },
  _setOptions(options) {
    dump(`about:performance _setOptions ${JSON.stringify(options)}\n`);
    let eltRefresh = document.getElementById("check-autorefresh");
    if ((options.autoRefresh > 0) != eltRefresh.checked) {
      eltRefresh.click();
    }
    let eltCheckRecent = document.getElementById("check-display-recent");
    if (!!options.displayRecent != eltCheckRecent.checked) {
      eltCheckRecent.click();
    }
  },
  _initAutorefresh() {
    let onRefreshChange = (shouldUpdateNow = false) => {
      if (eltRefresh.checked == !!this._autoRefreshInterval) {
        // Nothing to change.
        return;
      }
      if (eltRefresh.checked) {
        this._autoRefreshInterval = window.setInterval(() => Control.update(), UPDATE_INTERVAL_MS);
        if (shouldUpdateNow) {
          Control.update();
        }
      } else {
        window.clearInterval(this._autoRefreshInterval);
        this._autoRefreshInterval = null;
      }
    }

    let eltRefresh = document.getElementById("check-autorefresh");
    eltRefresh.addEventListener("change", () => onRefreshChange(true));

    onRefreshChange(false);
  },
  _autoRefreshInterval: null,
  _initDisplayMode() {
    let onModeChange = (shouldUpdateNow) => {
      if (eltCheckRecent.checked) {
        this._displayMode = MODE_RECENT;
      } else {
        this._displayMode = MODE_GLOBAL;
      }
      if (shouldUpdateNow) {
        Control.update();
      }
    };

    let eltCheckRecent = document.getElementById("check-display-recent");
    let eltLabelRecent = document.getElementById("label-display-recent");
    eltCheckRecent.addEventListener("click", () => onModeChange(true));
    eltLabelRecent.textContent = `Display only the latest ${Math.round(BUFFER_DURATION_MS / 1000)}s`;

    onModeChange(false);
  },
  // The display mode. One of `MODE_GLOBAL` or `MODE_RECENT`.
  _displayMode: MODE_GLOBAL,
};

/**
 * This functionality gets memory related information of sub-processes and
 * updates the performance table regularly.
 * If the page goes hidden, it also handles visibility change by not
 * querying the content processes unnecessarily.
 */
var SubprocessMonitor = {
  _timeout: null,

  /**
   * Init will start the process of updating the table if the page is not hidden,
   * and set up an event listener for handling visibility changes.
   */
  init() {
    if (!document.hidden) {
      SubprocessMonitor.updateTable();
    }
    document.addEventListener("visibilitychange", SubprocessMonitor.handleVisibilityChange);
  },

  /**
   * This function updates the table after an interval if the page is visible
   * and clears the interval otherwise.
   */
  handleVisibilityChange() {
    if (!document.hidden) {
      SubprocessMonitor.queueUpdate();
    } else {
      clearTimeout(this._timeout);
      this._timeout = null;
    }
  },

  /**
   * This function queues a timer to request the next summary using updateTable
   * after some delay.
   */
  queueUpdate() {
    this._timeout = setTimeout(() => this.updateTable(), UPDATE_INTERVAL_MS);
  },

  /**
   * This is a helper function for updateTable, which updates a particular row.
   * @param {<tr> node} row The row to be updated.
   * @param {object} summaries The object with the updated RSS and USS values.
   * @param {string} pid The pid represented by the row for which we update.
   */
  updateRow(row, summaries, pid) {
    row.cells[0].textContent = pid;
    let RSSval = DownloadUtils.convertByteUnits(summaries[pid].rss);
    row.cells[1].textContent = RSSval.join(" ");
    let USSval = DownloadUtils.convertByteUnits(summaries[pid].uss);
    row.cells[2].textContent = USSval.join(" ");
  },

  /**
   * This function adds a row to the subprocess-performance table for every new pid
   * and populates and regularly updates it with RSS/USS measurements.
   */
  updateTable() {
    if (!document.hidden) {
      Memory.summary().then((summaries) => {
        if (!(Object.keys(summaries).length)) {
          // The summaries list was empty, which means we timed out getting
          // the memory reports. We'll try again later.
          SubprocessMonitor.queueUpdate();
          return;
        }
        let resultTable = document.getElementById("subprocess-reports");
        let recycle = [];
        // We first iterate the table to check if summaries exist for rowPids,
        // if yes, update them and delete the pid's summary or else hide the row
        // for recycling it. Start at row 1 instead of 0 (to skip the header row).
        for (let i = 1, row; (row = resultTable.rows[i]); i++) {
          let rowPid = row.dataset.pid;
          let summary = summaries[rowPid];
          if (summary) {
            // Now we update the values in the row, which is hardcoded for now,
            // but we might want to make this more adaptable in the future.
            SubprocessMonitor.updateRow(row, summaries, rowPid);
            delete summaries[rowPid];
          } else {
            // Take this unnecessary row, hide it and stash it for potential re-use.
            row.hidden = true;
            recycle.push(row);
          }
        }
        // For the remaining pids in summaries, we choose from the recyclable
        // (hidden) nodes, and if they get exhausted, append a row to the table.
        for (let pid in summaries) {
          let row = recycle.pop();
          if (row) {
            row.hidden = false;
          } else {
            // We create a new row here, and set it to row
            row = document.createElement("tr");
            // Insert cell for pid
            row.insertCell();
            // Insert a cell for USS.
            row.insertCell();
            // Insert another cell for RSS.
            row.insertCell();
          }
          row.dataset.pid = pid;
          // Update the row and put it at the bottom
          SubprocessMonitor.updateRow(row, summaries, pid);
          resultTable.appendChild(row);
        }
      });
      SubprocessMonitor.queueUpdate();
    }
  },
};

var go = async function() {

  SubprocessMonitor.init();
  Control.init();

  // Setup a hook to allow tests to configure and control this page
  let testUpdate = function(subject, topic, value) {
    let options = JSON.parse(value);
    Control._setOptions(options);
    Control.update();
  };
  Services.obs.addObserver(testUpdate, TEST_DRIVER_TOPIC);
  window.addEventListener("unload", () => Services.obs.removeObserver(testUpdate, TEST_DRIVER_TOPIC));

  await Control.update();
  await wait(BUFFER_SAMPLING_RATE_MS * 1.1);
  await Control.update();
};
PK
!<4chrome/toolkit/content/global/aboutPerformance.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>about:performance</title>
    <link rel="icon" type="image/png" id="favicon"
          href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
          type="text/css"/>
    <script type="text/javascript" src="chrome://global/content/aboutPerformance.js"></script>
    <style>
      @import url("chrome://global/skin/in-content/common.css");

      html {
        --aboutSupport-table-background: #ebebeb;
        background-color: var(--in-content-page-background);
      }
      body {
        margin: 40px 48px;
      }
      .hidden {
        display: none;
      }
      .summary .title {
        font-weight: bold;
      }
      a {
        text-decoration: none;
      }
      a.more {
        margin-left: 2ch;
      }
      ul.hidden_additional_items {
        padding-top: 0;
        margin-top: 0;
      }
      ul.visible_items {
        padding-bottom: 0;
        margin-bottom: 0;
      }
      li.delta {
        margin-top: .5em;
      }
      h2 {
        margin-top: 1cm;
      }
      button.show_all_items {
        margin-top: .5cm;
        margin-left: 1cm;
      }
      body {
        margin-left: 1cm;
      }
      div.measuring {
         background: url(chrome://global/skin/media/throbber.png) no-repeat center;
         min-width: 36px;
         min-height: 36px;
      }
      li.delta {
        border-left-width: 5px;
        border-left-style: solid;
        padding-left: 1em;
        list-style: none;
      }
      li.delta[impact="0"] {
        border-left-color: rgb(0, 255, 0);
      }
      li.delta[impact="1"] {
        border-left-color: rgb(24, 231, 0);
      }
      li.delta[impact="2"] {
        border-left-color: rgb(48, 207, 0);
      }
      li.delta[impact="3"] {
        border-left-color: rgb(72, 183, 0);
      }
      li.delta[impact="4"] {
        border-left-color: rgb(96, 159, 0);
      }
      li.delta[impact="5"] {
        border-left-color: rgb(120, 135, 0);
      }
      li.delta[impact="6"] {
        border-left-color: rgb(144, 111, 0);
      }
      li.delta[impact="7"] {
        border-left-color: rgb(168, 87, 0);
      }
      li.delta[impact="8"] {
        border-left-color: rgb(192, 63, 0);
      }
      li.delta[impact="9"] {
        border-left-color: rgb(216, 39, 0);
      }
      li.delta[impact="10"] {
        border-left-color: rgb(240, 15, 0);
      }
      li.delta[impact="11"] {
        border-left-color: rgb(255, 0, 0);
      }

      #subprocess-reports {
        background-color: var(--aboutSupport-table-background);
        color: var(--in-content-text-color);
        font: message-box;
        text-align: start;
        border: 1px solid var(--in-content-border-color);
        border-spacing: 0px;
        float: right;
        margin-bottom: 20px;
        -moz-margin-start: 20px;
        -moz-margin-end: 0;
        width: 100%;
      }
      #subprocess-reports:dir(rtl) {
        float: left;
      }
      #subprocess-reports th,
      #subprocess-reports td {
        border: 1px solid var(--in-content-border-color);
        padding: 4px;
      }
      #subprocess-reports thead th {
        text-align: center;
      }
      #subprocess-reports th {
        text-align: start;
        background-color: var(--in-content-table-header-background);
        color: var(--in-content-selected-text);
      }
      #subprocess-reports th.column {
        white-space: nowrap;
        width: 0px;
      }
      #subprocess-reports td {
        background-color: #ebebeb;
        text-align: start;
        border-color: var(--in-content-table-border-dark-color);
        border-spacing: 40px;
      }
      .options {
        width: 100%;
      }
      .options > .toggle-container-with-text {
        display: inline-flex;
      }
      .options > .toggle-container-with-text:not(:first-child) {
        margin-inline-start: 2ch;
      }
    </style>
  </head>
  <body onload="go()">
    <div>
      <h2>Memory usage of Subprocesses</h2>
      <table id="subprocess-reports">
        <tr>
          <th>Process ID</th>
          <th title="RSS measures the pages resident in the main memory for the process">Resident Set Size</th>
          <th title="USS gives a count of unshared pages, unique to the process">Unique Set Size</th>
        </tr>
      </table>
    </div>
    <div class="options">
      <div class="toggle-container-with-text">
        <input type="checkbox" checked="false" id="check-display-recent" role="checkbox"></input>
        <label for="check-display-recent" id="label-display-recent">Display only the last few seconds.</label>
      </div>
      <div class="toggle-container-with-text">
        <input type="checkbox" checked="true" id="check-autorefresh" role="checkbox"></input>
        <label for="check-autorefresh">Refresh automatically</label>
      </div>
    </div>
    <div>
      <h2>Performance of Web pages</h2>
      <div id="webpages" class="measuring">
      </div>
    </div>
  </body>
</html>
PK
!<@(@(.chrome/toolkit/content/global/aboutProfiles.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyServiceGetter(
  this,
  "ProfileService",
  "@mozilla.org/toolkit/profile-service;1",
  "nsIToolkitProfileService"
);

const bundle = Services.strings.createBundle(
  "chrome://global/locale/aboutProfiles.properties");

// nsIToolkitProfileService.selectProfile can be used only during the selection
// of the profile in the ProfileManager. If we are showing about:profiles in a
// tab, the selectedProfile returns the default profile.
// In this function we use the ProfD to find the current profile.
function findCurrentProfile() {
  let cpd;
  try {
    cpd = Cc["@mozilla.org/file/directory_service;1"]
            .getService(Ci.nsIProperties)
            .get("ProfD", Ci.nsIFile);
  } catch (e) {}

  if (cpd) {
    let itr = ProfileService.profiles;
    while (itr.hasMoreElements()) {
      let profile = itr.getNext().QueryInterface(Ci.nsIToolkitProfile);
      if (profile.rootDir.path == cpd.path) {
        return profile;
      }
    }
  }

  // selectedProfile can trow if nothing is selected or if the selected profile
  // has been deleted.
  try {
    return ProfileService.selectedProfile;
  } catch (e) {
    return null;
  }
}

function refreshUI() {
  let parent = document.getElementById("profiles");
  while (parent.firstChild) {
    parent.firstChild.remove();
  }

  let defaultProfile;
  try {
    defaultProfile = ProfileService.defaultProfile;
  } catch (e) {}

  let currentProfile = findCurrentProfile() || defaultProfile;

  let iter = ProfileService.profiles;
  while (iter.hasMoreElements()) {
    let profile = iter.getNext().QueryInterface(Ci.nsIToolkitProfile);
    display({ profile,
              isDefault: profile == defaultProfile,
              isCurrentProfile: profile == currentProfile });
  }

  let createButton = document.getElementById("create-button");
  createButton.onclick = createProfileWizard;

  let restartSafeModeButton = document.getElementById("restart-in-safe-mode-button");
  restartSafeModeButton.onclick = function() { restart(true); }

  let restartNormalModeButton = document.getElementById("restart-button");
  restartNormalModeButton.onclick = function() { restart(false); }
}

function openDirectory(dir) {
  let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
                                           "nsILocalFile", "initWithPath");
  new nsLocalFile(dir).reveal();
}

function display(profileData) {
  let parent = document.getElementById("profiles");

  let div = document.createElement("div");
  parent.appendChild(div);

  let nameStr = bundle.formatStringFromName("name", [profileData.profile.name], 1);

  let name = document.createElement("h2");
  name.appendChild(document.createTextNode(nameStr));

  div.appendChild(name);

  if (profileData.isCurrentProfile) {
    let currentProfile = document.createElement("h3");
    let currentProfileStr = bundle.GetStringFromName("currentProfile");
    currentProfile.appendChild(document.createTextNode(currentProfileStr));
    div.appendChild(currentProfile);
  }

  let table = document.createElement("table");
  div.appendChild(table);

  let tbody = document.createElement("tbody");
  table.appendChild(tbody);

  function createItem(title, value, dir = false) {
    let tr = document.createElement("tr");
    tbody.appendChild(tr);

    let th = document.createElement("th");
    th.setAttribute("class", "column");
    th.appendChild(document.createTextNode(title));
    tr.appendChild(th);

    let td = document.createElement("td");
    td.appendChild(document.createTextNode(value));
    tr.appendChild(td);

    if (dir) {
      td.appendChild(document.createTextNode(" "));
      let button = document.createElement("button");
      let string = "openDir";
      if (AppConstants.platform == "win") {
        string = "winOpenDir2";
      } else if (AppConstants.platform == "macosx") {
        string = "macOpenDir";
      }
      let buttonText = document.createTextNode(bundle.GetStringFromName(string));
      button.appendChild(buttonText);
      td.appendChild(button);

      button.addEventListener("click", function(e) {
        openDirectory(value);
      });
    }
  }

  createItem(bundle.GetStringFromName("isDefault"),
             profileData.isDefault ? bundle.GetStringFromName("yes") : bundle.GetStringFromName("no"));

  createItem(bundle.GetStringFromName("rootDir"), profileData.profile.rootDir.path, true);

  if (profileData.profile.localDir.path != profileData.profile.rootDir.path) {
    createItem(bundle.GetStringFromName("localDir"), profileData.profile.localDir.path, true);
  }

  let renameButton = document.createElement("button");
  renameButton.appendChild(document.createTextNode(bundle.GetStringFromName("rename")));
  renameButton.onclick = function() {
    renameProfile(profileData.profile);
  };
  div.appendChild(renameButton);

  if (!profileData.isCurrentProfile) {
    let removeButton = document.createElement("button");
    removeButton.appendChild(document.createTextNode(bundle.GetStringFromName("remove")));
    removeButton.onclick = function() {
      removeProfile(profileData.profile);
    };

    div.appendChild(removeButton);
  }

  if (!profileData.isDefault) {
    let defaultButton = document.createElement("button");
    defaultButton.appendChild(document.createTextNode(bundle.GetStringFromName("setAsDefault")));
    defaultButton.onclick = function() {
      defaultProfile(profileData.profile);
    };
    div.appendChild(defaultButton);
  }

  if (!profileData.isCurrentProfile) {
    let runButton = document.createElement("button");
    runButton.appendChild(document.createTextNode(bundle.GetStringFromName("launchProfile")));
    runButton.onclick = function() {
      openProfile(profileData.profile);
    };
    div.appendChild(runButton);
  }

  let sep = document.createElement("hr");
  div.appendChild(sep);
}

function CreateProfile(profile) {
  ProfileService.selectedProfile = profile;
  ProfileService.flush();
  refreshUI();
}

function createProfileWizard() {
  // This should be rewritten in HTML eventually.
  window.openDialog("chrome://mozapps/content/profile/createProfileWizard.xul",
                    "", "centerscreen,chrome,modal,titlebar",
                    ProfileService);
}

function renameProfile(profile) {
  let title = bundle.GetStringFromName("renameProfileTitle");
  let msg = bundle.formatStringFromName("renameProfile", [profile.name], 1);
  let newName = { value: profile.name };

  if (Services.prompt.prompt(window, title, msg, newName, null,
                             { value: 0 })) {
    newName = newName.value;

    if (newName == profile.name) {
      return;
    }

    try {
      profile.name = newName;
    } catch (e) {
      let title = bundle.GetStringFromName("invalidProfileNameTitle");
      let msg = bundle.formatStringFromName("invalidProfileName", [newName], 1);
      Services.prompt.alert(window, title, msg);
      return;
    }

    ProfileService.flush();
    refreshUI();
  }
}

function removeProfile(profile) {
  let deleteFiles = false;

  if (profile.rootDir.exists()) {
    let title = bundle.GetStringFromName("deleteProfileTitle");
    let msg = bundle.formatStringFromName("deleteProfileConfirm",
                                          [profile.rootDir.path], 1);

    let buttonPressed = Services.prompt.confirmEx(window, title, msg,
                          (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
                          (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
                          (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
                          bundle.GetStringFromName("dontDeleteFiles"),
                          null,
                          bundle.GetStringFromName("deleteFiles"),
                          null, {value: 0});
    if (buttonPressed == 1) {
      return;
    }

    if (buttonPressed == 2) {
      deleteFiles = true;
    }
  }

  // If we are deleting the selected or the default profile we must choose a
  // different one.
  let isSelected = false;
  try {
    isSelected = ProfileService.selectedProfile == profile;
  } catch (e) {}

  let isDefault = false;
  try {
    isDefault = ProfileService.defaultProfile == profile;
  } catch (e) {}

  if (isSelected || isDefault) {
    let itr = ProfileService.profiles;
    while (itr.hasMoreElements()) {
      let p = itr.getNext().QueryInterface(Ci.nsIToolkitProfile);
      if (profile == p) {
        continue;
      }

      if (isSelected) {
        ProfileService.selectedProfile = p;
      }

      if (isDefault) {
        ProfileService.defaultProfile = p;
      }

      break;
    }
  }

  profile.remove(deleteFiles);
  ProfileService.flush();
  refreshUI();
}

function defaultProfile(profile) {
  ProfileService.defaultProfile = profile;
  ProfileService.selectedProfile = profile;
  ProfileService.flush();
  refreshUI();
}

function openProfile(profile) {
  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                     .createInstance(Ci.nsISupportsPRBool);
  Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");

  if (cancelQuit.data) {
    return;
  }

  Services.startup.createInstanceWithProfile(profile);
}

function restart(safeMode) {
  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                     .createInstance(Ci.nsISupportsPRBool);
  Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");

  if (cancelQuit.data) {
    return;
  }

  let flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile;

  if (safeMode) {
    Services.startup.restartInSafeMode(flags);
  } else {
    Services.startup.quit(flags);
  }
}

window.addEventListener("DOMContentLoaded", function() {
  refreshUI();
}, {once: true});
PK
!<ف2WW1chrome/toolkit/content/global/aboutProfiles.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
<!ENTITY % profilesDTD SYSTEM "chrome://global/locale/aboutProfiles.dtd"> %profilesDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>&aboutProfiles.title;</title>
    <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
    <link rel="stylesheet" href="chrome://mozapps/skin/aboutProfiles.css" type="text/css" />
    <script type="application/javascript" src="chrome://global/content/aboutProfiles.js" />
  </head>
  <body id="body" dir="&locale.dir;">
    <div id="action-box">
      <h3>&aboutProfiles.restart.title;</h3>
      <button id="restart-in-safe-mode-button">&aboutProfiles.restart.inSafeMode;</button>
      <button id="restart-button">&aboutProfiles.restart.normal;</button>
    </div>

    <h1>&aboutProfiles.title;</h1>
    <div class="page-subtitle">&aboutProfiles.subtitle;</div>

    <div>
      <button id="create-button">&aboutProfiles.create;</button>
    </div>

    <div id="profiles" class="tab"></div>
  </body>
</html>
PK
!<4m``/chrome/toolkit/content/global/aboutRights.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html [
  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
  %htmlDTD;
  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
  %brandDTD;
  <!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
  %securityPrefsDTD;
  <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
  %aboutRightsDTD;
]>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">

<head>
  <title>&rights.title;</title>
  <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css"/>
</head>

<body id="your-rights" dir="&rights.locale-direction;">
<div class="container">
  <h1>&rights.title;</h1>

  <p>&rights.intro;</p>

  <ul>
    <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li>
  <!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
    - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
    - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
    <li>&rights.intro-point2-a;<a href="http://www.mozilla.org/foundation/trademarks/policy.html">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li>
    <li>&rights.intro-point2.5;</li>
    <li>&rights2.intro-point3a;<a href="https://www.mozilla.org/legal/privacy/firefox.html">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
    <li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li>
    <li>&rights.intro-point5;</li>
  </ul>

  <div id="webservices-container">
    <a name="webservices"/>
    <h3>&rights2.webservices-header;</h3>

    <p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights3.webservices-c;</p>

    <div id="disabling-webservices-container" style="margin-left:40px;">
      <a name="disabling-webservices"/>
      <p><strong>&rights.safebrowsing-a;</strong>&rights.safebrowsing-b;</p>
      <ul>
        <li>&rights.safebrowsing-term1;</li>
        <li>&rights.safebrowsing-term2;</li>
        <li>&rights2.safebrowsing-term3;</li>
        <li>&rights.safebrowsing-term4;</li>
      </ul>

      <p><strong>&rights.locationawarebrowsing-a;</strong>&rights.locationawarebrowsing-b;</p>
      <ul>
        <li>&rights.locationawarebrowsing-term1a;<code>&rights.locationawarebrowsing-term1b;</code></li>
        <li>&rights.locationawarebrowsing-term2;</li>
        <li>&rights.locationawarebrowsing-term3;</li>
        <li>&rights.locationawarebrowsing-term4;</li>
      </ul>
    </div>

    <ol>
  <!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
      <li>&rights2.webservices-term1;</li>
      <li>&rights.webservices-term2;</li>
      <li>&rights2.webservices-term3;</li>
      <li><strong>&rights.webservices-term4;</strong></li>
      <li><strong>&rights.webservices-term5;</strong></li>
      <li>&rights.webservices-term6;</li>
      <li>&rights.webservices-term7;</li>
    </ol>
  </div>
</div>

<script type="application/javascript"><![CDATA[
  var servicesDiv = document.getElementById("webservices-container");
  servicesDiv.style.display = "none";

  function showServices() {
    servicesDiv.style.display = "";
  }

  var disablingServicesDiv = document.getElementById("disabling-webservices-container");
  disablingServicesDiv.style.display = "none";

  function showDisablingServices() {
    disablingServicesDiv.style.display = "";
  }
]]></script>

</body>
</html>
PK
!<"^A4chrome/toolkit/content/global/aboutServiceWorkers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const bundle = Services.strings.createBundle(
  "chrome://global/locale/aboutServiceWorkers.properties");

const brandBundle = Services.strings.createBundle(
  "chrome://branding/locale/brand.properties");

var gSWM;
var gSWCount = 0;

function init() {
  let enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
  if (!enabled) {
    let div = document.getElementById("warning_not_enabled");
    div.classList.add("active");
    return;
  }

  gSWM = Cc["@mozilla.org/serviceworkers/manager;1"]
           .getService(Ci.nsIServiceWorkerManager);
  if (!gSWM) {
    dump("AboutServiceWorkers: Failed to get the ServiceWorkerManager service!\n");
    return;
  }

  let data = gSWM.getAllRegistrations();
  if (!data) {
    dump("AboutServiceWorkers: Failed to retrieve the registrations.\n");
    return;
  }

  let length = data.length;
  if (!length) {
    let div = document.getElementById("warning_no_serviceworkers");
    div.classList.add("active");
    return;
  }

  let ps = undefined;
  try {
    ps = Cc["@mozilla.org/push/Service;1"]
           .getService(Ci.nsIPushService);
  } catch (e) {
    dump("Could not acquire PushService\n");
  }

  for (let i = 0; i < length; ++i) {
    let info = data.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
    if (!info) {
      dump("AboutServiceWorkers: Invalid nsIServiceWorkerRegistrationInfo interface.\n");
      continue;
    }

    display(info, ps);
  }
}

function display(info, pushService) {
  let parent = document.getElementById("serviceworkers");

  let div = document.createElement("div");
  parent.appendChild(div);

  let title = document.createElement("h2");
  let titleStr = bundle.formatStringFromName("title", [info.principal.origin], 1);
  title.appendChild(document.createTextNode(titleStr));
  div.appendChild(title);

  if (info.principal.appId) {
    let b2gtitle = document.createElement("h3");
    let trueFalse = bundle.GetStringFromName(info.principal.isInIsolatedMozBrowserElement ? "true" : "false");

    let b2gtitleStr =
      bundle.formatStringFromName("b2gtitle", [ brandBundle.getString("brandShortName"),
                                                info.principal.appId,
                                                trueFalse], 2);
    b2gtitle.appendChild(document.createTextNode(b2gtitleStr));
    div.appendChild(b2gtitle);
  }

  let list = document.createElement("ul");
  div.appendChild(list);

  function createItem(title, value, makeLink) {
    let item = document.createElement("li");
    list.appendChild(item);

    let bold = document.createElement("strong");
    bold.appendChild(document.createTextNode(title + " "));
    item.appendChild(bold);

    let textNode = document.createTextNode(value);

    if (makeLink) {
      let link = document.createElement("a");
      link.href = value;
      link.target = "_blank";
      link.appendChild(textNode);
      item.appendChild(link);
    } else {
      item.appendChild(textNode);
    }

    return textNode;
  }

  createItem(bundle.GetStringFromName("scope"), info.scope);
  createItem(bundle.GetStringFromName("scriptSpec"), info.scriptSpec, true);
  let currentWorkerURL = info.activeWorker ? info.activeWorker.scriptSpec : "";
  createItem(bundle.GetStringFromName("currentWorkerURL"), currentWorkerURL, true);
  let activeCacheName = info.activeWorker ? info.activeWorker.cacheName : "";
  createItem(bundle.GetStringFromName("activeCacheName"), activeCacheName);
  let waitingCacheName = info.waitingWorker ? info.waitingWorker.cacheName : "";
  createItem(bundle.GetStringFromName("waitingCacheName"), waitingCacheName);

  let pushItem = createItem(bundle.GetStringFromName("pushEndpoint"), bundle.GetStringFromName("waiting"));
  if (pushService) {
    pushService.getSubscription(info.scope, info.principal, (status, pushRecord) => {
      if (Components.isSuccessCode(status)) {
        pushItem.data = JSON.stringify(pushRecord);
      } else {
        dump("about:serviceworkers - retrieving push registration failed\n");
      }
    });
  }

  let updateButton = document.createElement("button");
  updateButton.appendChild(document.createTextNode(bundle.GetStringFromName("update")));
  updateButton.onclick = function() {
    gSWM.propagateSoftUpdate(info.principal.originAttributes, info.scope);
  };
  div.appendChild(updateButton);

  let unregisterButton = document.createElement("button");
  unregisterButton.appendChild(document.createTextNode(bundle.GetStringFromName("unregister")));
  div.appendChild(unregisterButton);

  let loadingMessage = document.createElement("span");
  loadingMessage.appendChild(document.createTextNode(bundle.GetStringFromName("waiting")));
  loadingMessage.classList.add("inactive");
  div.appendChild(loadingMessage);

  unregisterButton.onclick = function() {
    let cb = {
      unregisterSucceeded() {
        parent.removeChild(div);

        if (!--gSWCount) {
         let div = document.getElementById("warning_no_serviceworkers");
         div.classList.add("active");
        }
      },

      unregisterFailed() {
        alert(bundle.GetStringFromName("unregisterError"));
      },

      QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback])
    };

    loadingMessage.classList.remove("inactive");
    gSWM.propagateUnregister(info.principal, cb, info.scope);
  };

  let sep = document.createElement("hr");
  div.appendChild(sep);

  ++gSWCount;
}

window.addEventListener("DOMContentLoaded", function() {
  init();
}, {once: true});
PK
!<i!!!7chrome/toolkit/content/global/aboutServiceWorkers.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
<!ENTITY % serviceworkersDTD SYSTEM "chrome://global/locale/aboutServiceWorkers.dtd"> %serviceworkersDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>&aboutServiceWorkers.title;</title>
        <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css" />
        <link rel="stylesheet" href="chrome://mozapps/skin/aboutServiceWorkers.css" type="text/css" />
        <script type="application/javascript" src="chrome://global/content/aboutServiceWorkers.js" />
    </head>
    <body id="body" dir="&locale.dir;">
        <div id="warning_not_enabled" class="warningBackground">
            <div class="warningMessage">&aboutServiceWorkers.warning_not_enabled;</div>
        </div>

        <div id="warning_no_serviceworkers" class="warningBackground">
            <div class="warningMessage">&aboutServiceWorkers.warning_no_serviceworkers;</div>
        </div>

        <div id="serviceworkers" class="tab active">
          <h1>&aboutServiceWorkers.maintitle;</h1>
        </div>
    </body>
</html>
PK
!<Q͞͞-chrome/toolkit/content/global/aboutSupport.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Troubleshoot.jsm");
Cu.import("resource://gre/modules/ResetProfile.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                  "resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
                                  "resource://gre/modules/PlacesDBUtils.jsm");

window.addEventListener("load", function onload(event) {
  try {
  window.removeEventListener("load", onload);
  Troubleshoot.snapshot(function(snapshot) {
    for (let prop in snapshotFormatters)
      snapshotFormatters[prop](snapshot[prop]);
  });
  populateActionBox();
  setupEventListeners();
  } catch (e) {
    Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
  }
});

// Each property in this object corresponds to a property in Troubleshoot.jsm's
// snapshot data.  Each function is passed its property's corresponding data,
// and it's the function's job to update the page with it.
var snapshotFormatters = {

  application: function application(data) {
    let strings = stringBundle();
    $("application-box").textContent = data.name;
    $("useragent-box").textContent = data.userAgent;
    $("os-box").textContent = data.osVersion;
    $("supportLink").href = data.supportURL;
    let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
    if (data.vendor)
      version += " (" + data.vendor + ")";
    $("version-box").textContent = version;
    $("buildid-box").textContent = data.buildID;
    if (data.updateChannel)
      $("updatechannel-box").textContent = data.updateChannel;

    let statusText = strings.GetStringFromName("multiProcessStatus.unknown");

    // Whitelist of known values with string descriptions:
    switch (data.autoStartStatus) {
      case 0:
      case 1:
      case 2:
      case 4:
      case 6:
      case 7:
      case 8:
        statusText = strings.GetStringFromName("multiProcessStatus." + data.autoStartStatus);
        break;

      case 10:
        statusText = (Services.appinfo.OS == "Darwin" ? "OS X 10.6 - 10.8" : "Windows XP");
        break;
    }

    $("multiprocess-box").textContent = strings.formatStringFromName("multiProcessWindows",
      [data.numRemoteWindows, data.numTotalWindows, statusText], 3);

    if (data.remoteAutoStart) {
      $("contentprocesses-box").textContent = data.currentContentProcesses +
                                              "/" +
                                              data.maxContentProcesses;
    } else {
      $("contentprocesses-row").hidden = true;
    }

    let styloReason;
    if (!data.styloBuild) {
      styloReason = strings.GetStringFromName("disabledByBuild");
    } else if (data.styloResult != data.styloDefault) {
      if (data.styloResult) {
        styloReason = strings.GetStringFromName("enabledByUser");
      } else {
        styloReason = strings.GetStringFromName("disabledByUser");
      }
    } else if (data.styloDefault) {
      styloReason = strings.GetStringFromName("enabledByDefault");
    } else {
      styloReason = strings.GetStringFromName("disabledByDefault");
    }
    $("stylo-box").textContent = `${data.styloResult} (${styloReason})`;

    let keyGoogleFound = data.keyGoogleFound ? "found" : "missing";
    $("key-google-box").textContent = strings.GetStringFromName(keyGoogleFound);

    let keyMozillaFound = data.keyMozillaFound ? "found" : "missing";
    $("key-mozilla-box").textContent = strings.GetStringFromName(keyMozillaFound);

    $("safemode-box").textContent = data.safeMode;
  },

  crashes: function crashes(data) {
    if (!AppConstants.MOZ_CRASHREPORTER)
      return;

    let strings = stringBundle();
    let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
    $("crashes-title").textContent =
      PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
                .replace("#1", daysRange);
    let reportURL;
    try {
      reportURL = Services.prefs.getCharPref("breakpad.reportURL");
      // Ignore any non http/https urls
      if (!/^https?:/i.test(reportURL))
        reportURL = null;
    } catch (e) { }
    if (!reportURL) {
      $("crashes-noConfig").style.display = "block";
      $("crashes-noConfig").classList.remove("no-copy");
      return;
    }
    $("crashes-allReports").style.display = "block";
    $("crashes-allReports").classList.remove("no-copy");

    if (data.pending > 0) {
      $("crashes-allReportsWithPending").textContent =
        PluralForm.get(data.pending, strings.GetStringFromName("pendingReports"))
                  .replace("#1", data.pending);
    }

    let dateNow = new Date();
    $.append($("crashes-tbody"), data.submitted.map(function(crash) {
      let date = new Date(crash.date);
      let timePassed = dateNow - date;
      let formattedDate;
      if (timePassed >= 24 * 60 * 60 * 1000) {
        let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
        let daysPassedString = strings.GetStringFromName("crashesTimeDays");
        formattedDate = PluralForm.get(daysPassed, daysPassedString)
                                  .replace("#1", daysPassed);
      } else if (timePassed >= 60 * 60 * 1000) {
        let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
        let hoursPassedString = strings.GetStringFromName("crashesTimeHours");
        formattedDate = PluralForm.get(hoursPassed, hoursPassedString)
                                  .replace("#1", hoursPassed);
      } else {
        let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
        let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes");
        formattedDate = PluralForm.get(minutesPassed, minutesPassedString)
                                  .replace("#1", minutesPassed);
      }
      return $.new("tr", [
        $.new("td", [
          $.new("a", crash.id, null, {href: reportURL + crash.id})
        ]),
        $.new("td", formattedDate)
      ]);
    }));
  },

  extensions: function extensions(data) {
    $.append($("extensions-tbody"), data.map(function(extension) {
      return $.new("tr", [
        $.new("td", extension.name),
        $.new("td", extension.version),
        $.new("td", extension.isActive),
        $.new("td", extension.id),
      ]);
    }));
  },

  features: function features(data) {
    $.append($("features-tbody"), data.map(function(feature) {
      return $.new("tr", [
        $.new("td", feature.name),
        $.new("td", feature.version),
        $.new("td", feature.id),
      ]);
    }));
  },

  experiments: function experiments(data) {
    $.append($("experiments-tbody"), data.map(function(experiment) {
      return $.new("tr", [
        $.new("td", experiment.name),
        $.new("td", experiment.id),
        $.new("td", experiment.description),
        $.new("td", experiment.active),
        $.new("td", experiment.endDate),
        $.new("td", [
          $.new("a", experiment.detailURL, null, {href: experiment.detailURL, })
        ]),
        $.new("td", experiment.branch),
      ]);
    }));
  },

  modifiedPreferences: function modifiedPreferences(data) {
    $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
      function([name, value]) {
        return $.new("tr", [
          $.new("td", name, "pref-name"),
          // Very long preference values can cause users problems when they
          // copy and paste them into some text editors.  Long values generally
          // aren't useful anyway, so truncate them to a reasonable length.
          $.new("td", String(value).substr(0, 120), "pref-value"),
        ]);
      }
    ));
  },

  lockedPreferences: function lockedPreferences(data) {
    $.append($("locked-prefs-tbody"), sortedArrayFromObject(data).map(
      function([name, value]) {
        return $.new("tr", [
          $.new("td", name, "pref-name"),
          $.new("td", String(value).substr(0, 120), "pref-value"),
        ]);
      }
    ));
  },

  graphics: function graphics(data) {
    let strings = stringBundle();

    function localizedMsg(msgArray) {
      let nameOrMsg = msgArray.shift();
      if (msgArray.length) {
        // formatStringFromName logs an NS_ASSERTION failure otherwise that says
        // "use GetStringFromName".  Lame.
        try {
          return strings.formatStringFromName(nameOrMsg, msgArray,
                                              msgArray.length);
        } catch (err) {
          // Throws if nameOrMsg is not a name in the bundle.  This shouldn't
          // actually happen though, since msgArray.length > 1 => nameOrMsg is a
          // name in the bundle, not a message, and the remaining msgArray
          // elements are parameters.
          return nameOrMsg;
        }
      }
      try {
        return strings.GetStringFromName(nameOrMsg);
      } catch (err) {
        // Throws if nameOrMsg is not a name in the bundle.
      }
      return nameOrMsg;
    }

    // Read APZ info out of data.info, stripping it out in the process.
    let apzInfo = [];
    let formatApzInfo = function(info) {
      let out = [];
      for (let type of ["Wheel", "Touch", "Drag", "Keyboard"]) {
        let key = "Apz" + type + "Input";

        if (!(key in info))
          continue;

        delete info[key];

        let message = localizedMsg([type.toLowerCase() + "Enabled"]);
        out.push(message);
      }

      return out;
    };

    // Create a <tr> element with key and value columns.
    //
    // @key      Text in the key column. Localized automatically, unless starts with "#".
    // @value    Text in the value column. Not localized.
    function buildRow(key, value) {
      let title;
      if (key[0] == "#") {
        title = key.substr(1);
      } else {
        try {
          title = strings.GetStringFromName(key);
        } catch (e) {
          title = key;
        }
      }
      let td = $.new("td", value);
      td.style["white-space"] = "pre-wrap";

      return $.new("tr", [
        $.new("th", title, "column"),
        td,
      ]);
    }

    // @where    The name in "graphics-<name>-tbody", of the element to append to.
    // @trs      Array of row elements.
    function addRows(where, trs) {
      $.append($("graphics-" + where + "-tbody"), trs);
    }

    // Build and append a row.
    //
    // @where    The name in "graphics-<name>-tbody", of the element to append to.
    function addRow(where, key, value) {
      addRows(where, [buildRow(key, value)]);
    }
    if (data.clearTypeParameters !== undefined) {
      addRow("diagnostics", "clearTypeParameters", data.clearTypeParameters);
    }
    if ("info" in data) {
      apzInfo = formatApzInfo(data.info);

      let trs = sortedArrayFromObject(data.info).map(function([prop, val]) {
        return $.new("tr", [
          $.new("th", prop, "column"),
          $.new("td", String(val)),
        ]);
      });
      addRows("diagnostics", trs);

      delete data.info;
    }

    let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
    let gpuProcessPid = windowUtils.gpuProcessPid;

    if (gpuProcessPid != -1) {
      let gpuProcessKillButton = null;
      if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) {
        gpuProcessKillButton = $.new("button");

        gpuProcessKillButton.addEventListener("click", function() {
          windowUtils.terminateGPUProcess();
        });

        gpuProcessKillButton.textContent = strings.GetStringFromName("gpuProcessKillButton");
      }

      addRow("diagnostics", "GPUProcessPid", gpuProcessPid);
      if (gpuProcessKillButton) {
        addRow("diagnostics", "GPUProcess", [gpuProcessKillButton]);
      }
    }

    if ((AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) && AppConstants.platform != "macosx") {
      let gpuDeviceResetButton = $.new("button");

      gpuDeviceResetButton.addEventListener("click", function() {
        windowUtils.triggerDeviceReset();
      });

      gpuDeviceResetButton.textContent = strings.GetStringFromName("gpuDeviceResetButton");
      addRow("diagnostics", "Device Reset", [gpuDeviceResetButton]);
    }

    // graphics-failures-tbody tbody
    if ("failures" in data) {
      // If indices is there, it should be the same length as failures,
      // (see Troubleshoot.jsm) but we check anyway:
      if ("indices" in data && data.failures.length == data.indices.length) {
        let combined = [];
        for (let i = 0; i < data.failures.length; i++) {
          let assembled = assembleFromGraphicsFailure(i, data);
          combined.push(assembled);
        }
        combined.sort(function(a, b) {
            if (a.index < b.index) return -1;
            if (a.index > b.index) return 1;
            return 0;
        });
        $.append($("graphics-failures-tbody"),
                 combined.map(function(val) {
                   return $.new("tr", [$.new("th", val.header, "column"),
                                       $.new("td", val.message)]);
                 }));
        delete data.indices;
      } else {
        $.append($("graphics-failures-tbody"),
          [$.new("tr", [$.new("th", "LogFailure", "column"),
                        $.new("td", data.failures.map(function(val) {
                          return $.new("p", val);
                       }))])]);
      }
    } else {
      $("graphics-failures-tbody").style.display = "none";
    }

    // Add a new row to the table, and take the key (or keys) out of data.
    //
    // @where        Table section to add to.
    // @key          Data key to use.
    // @colKey       The localization key to use, if different from key.
    function addRowFromKey(where, key, colKey) {
      if (!(key in data))
        return;
      colKey = colKey || key;

      let value;
      let messageKey = key + "Message";
      if (messageKey in data) {
        value = localizedMsg(data[messageKey]);
        delete data[messageKey];
      } else {
        value = data[key];
      }
      delete data[key];

      if (value) {
        addRow(where, colKey, value);
      }
    }

    // graphics-features-tbody
    let compositor = "";
    if (data.windowLayerManagerRemote) {
      compositor = data.windowLayerManagerType;
      if (data.windowUsingAdvancedLayers) {
        compositor += " (Advanced Layers)";
      }
    } else {
      compositor = "BasicLayers (" + strings.GetStringFromName("mainThreadNoOMTC") + ")";
    }
    addRow("features", "compositing", compositor);
    delete data.windowLayerManagerRemote;
    delete data.windowLayerManagerType;
    delete data.numTotalWindows;
    delete data.numAcceleratedWindows;
    delete data.numAcceleratedWindowsMessage;
    delete data.windowUsingAdvancedLayers;

    addRow("features", "asyncPanZoom",
           apzInfo.length
           ? apzInfo.join("; ")
           : localizedMsg(["apzNone"]));
    addRowFromKey("features", "webgl1WSIInfo");
    addRowFromKey("features", "webgl1Renderer");
    addRowFromKey("features", "webgl1Version");
    addRowFromKey("features", "webgl1DriverExtensions");
    addRowFromKey("features", "webgl1Extensions");
    addRowFromKey("features", "webgl2WSIInfo");
    addRowFromKey("features", "webgl2Renderer");
    addRowFromKey("features", "webgl2Version");
    addRowFromKey("features", "webgl2DriverExtensions");
    addRowFromKey("features", "webgl2Extensions");
    addRowFromKey("features", "supportsHardwareH264", "hardwareH264");
    addRowFromKey("features", "direct2DEnabled", "#Direct2D");

    if ("directWriteEnabled" in data) {
      let message = data.directWriteEnabled;
      if ("directWriteVersion" in data)
        message += " (" + data.directWriteVersion + ")";
      addRow("features", "#DirectWrite", message);
      delete data.directWriteEnabled;
      delete data.directWriteVersion;
    }

    // Adapter tbodies.
    let adapterKeys = [
      ["adapterDescription", "gpuDescription"],
      ["adapterVendorID", "gpuVendorID"],
      ["adapterDeviceID", "gpuDeviceID"],
      ["driverVersion", "gpuDriverVersion"],
      ["driverDate", "gpuDriverDate"],
      ["adapterDrivers", "gpuDrivers"],
      ["adapterSubsysID", "gpuSubsysID"],
      ["adapterRAM", "gpuRAM"],
    ];

    function showGpu(id, suffix) {
      function get(prop) {
        return data[prop + suffix];
      }

      let trs = [];
      for (let [prop, key] of adapterKeys) {
        let value = get(prop);
        if (value === undefined || value === "")
          continue;
        trs.push(buildRow(key, value));
      }

      if (trs.length == 0) {
        $("graphics-" + id + "-tbody").style.display = "none";
        return;
      }

      let active = "yes";
      if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) {
        active = "no";
      }
      addRow(id, "gpuActive", strings.GetStringFromName(active));
      addRows(id, trs);
    }
    showGpu("gpu-1", "");
    showGpu("gpu-2", "2");

    // Remove adapter keys.
    for (let [prop, /* key */] of adapterKeys) {
      delete data[prop];
      delete data[prop + "2"];
    }
    delete data.isGPU2Active;

    let featureLog = data.featureLog;
    delete data.featureLog;

    let features = [];
    for (let feature of featureLog.features) {
      // Only add interesting decisions - ones that were not automatic based on
      // all.js/gfxPrefs defaults.
      if (feature.log.length > 1 || feature.log[0].status != "available") {
        features.push(feature);
      }
    }

    if (features.length) {
      for (let feature of features) {
        let trs = [];
        for (let entry of feature.log) {
          if (entry.type == "default" && entry.status == "available")
            continue;

          let contents;
          if (entry.message.length > 0 && entry.message[0] == "#") {
            // This is a failure ID. See nsIGfxInfo.idl.
            let m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message);
            if (m) {
              let bugSpan = $.new("span");
              bugSpan.textContent = strings.GetStringFromName("blocklistedBug") + "; ";

              let bugHref = $.new("a");
              bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
              bugHref.textContent = strings.formatStringFromName("bugLink", [m[1]], 1);

              contents = [bugSpan, bugHref];
            } else {
              contents = strings.formatStringFromName(
                "unknownFailure", [entry.message.substr(1)], 1);
            }
          } else {
            contents = entry.status + " by " + entry.type + ": " + entry.message;
          }

          trs.push($.new("tr", [
            $.new("td", contents),
          ]));
        }
        addRow("decisions", feature.name, [$.new("table", trs)]);
      }
    } else {
      $("graphics-decisions-tbody").style.display = "none";
    }

    if (featureLog.fallbacks.length) {
      for (let fallback of featureLog.fallbacks) {
        addRow("workarounds", fallback.name, fallback.message);
      }
    } else {
      $("graphics-workarounds-tbody").style.display = "none";
    }

    let crashGuards = data.crashGuards;
    delete data.crashGuards;

    if (crashGuards.length) {
      for (let guard of crashGuards) {
        let resetButton = $.new("button");
        let onClickReset = function() {
          Services.prefs.setIntPref(guard.prefName, 0);
          resetButton.removeEventListener("click", onClickReset);
          resetButton.disabled = true;
        };

        resetButton.textContent = strings.GetStringFromName("resetOnNextRestart");
        resetButton.addEventListener("click", onClickReset);

        addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
      }
    } else {
      $("graphics-crashguards-tbody").style.display = "none";
    }

    // Now that we're done, grab any remaining keys in data and drop them into
    // the diagnostics section.
    for (let key in data) {
      let value = data[key];
      if (Array.isArray(value)) {
        value = localizedMsg(value);
      }
      addRow("diagnostics", key, value);
    }
  },

  media: function media(data) {
    let strings = stringBundle();

    function insertBasicInfo(key, value) {
      function createRow(key, value) {
        let th = $.new("th", strings.GetStringFromName(key), "column");
        let td = $.new("td", value);
        td.style["white-space"] = "pre-wrap";
        return $.new("tr", [th, td]);
      }
      $.append($("media-info-tbody"), [createRow(key, value)]);
    }

    function createDeviceInfoRow(device) {
      let deviceInfo = Ci.nsIAudioDeviceInfo;

      let states = {};
      states[deviceInfo.STATE_DISABLED] = "Disabled";
      states[deviceInfo.STATE_UNPLUGGED] = "Unplugged";
      states[deviceInfo.STATE_ENABLED] = "Enabled";

      let preferreds = {};
      preferreds[deviceInfo.PREF_NONE] = "None";
      preferreds[deviceInfo.PREF_MULTIMEDIA] = "Multimedia";
      preferreds[deviceInfo.PREF_VOICE] = "Voice";
      preferreds[deviceInfo.PREF_NOTIFICATION] = "Notification";
      preferreds[deviceInfo.PREF_ALL] = "All";

      let formats = {};
      formats[deviceInfo.FMT_S16LE] = "S16LE";
      formats[deviceInfo.FMT_S16BE] = "S16BE";
      formats[deviceInfo.FMT_F32LE] = "F32LE";
      formats[deviceInfo.FMT_F32BE] = "F32BE";

      function toPreferredString(preferred) {
        if (preferred == deviceInfo.PREF_NONE) {
          return preferreds[deviceInfo.PREF_NONE];
        } else if (preferred & deviceInfo.PREF_ALL) {
          return preferreds[deviceInfo.PREF_ALL];
        }
        let str = "";
        for (let pref of [deviceInfo.PREF_MULTIMEDIA,
                          deviceInfo.PREF_VOICE,
                          deviceInfo.PREF_NOTIFICATION]) {
          if (preferred & pref) {
            str += " " + preferreds[pref];
          }
        }
        return str;
      }

      function toFromatString(dev) {
        let str = "default: " + formats[dev.defaultFormat] + ", support:";
        for (let fmt of [deviceInfo.FMT_S16LE,
                         deviceInfo.FMT_S16BE,
                         deviceInfo.FMT_F32LE,
                         deviceInfo.FMT_F32BE]) {
          if (dev.supportedFormat & fmt) {
            str += " " + formats[fmt];
          }
        }
        return str;
      }

      function toRateString(dev) {
        return "default: " + dev.defaultRate +
               ", support: " + dev.minRate + " - " + dev.maxRate;
      }

      function toLatencyString(dev) {
        return dev.minLatency + " - " + dev.maxLatency;
      }

      return $.new("tr", [$.new("td", device.name),
                          $.new("td", device.groupId),
                          $.new("td", device.vendor),
                          $.new("td", states[device.state]),
                          $.new("td", toPreferredString(device.preferred)),
                          $.new("td", toFromatString(device)),
                          $.new("td", device.maxChannels),
                          $.new("td", toRateString(device)),
                          $.new("td", toLatencyString(device))]);
    }

    function insertDeviceInfo(side, devices) {
      let rows = [];
      for (let dev of devices) {
        rows.push(createDeviceInfoRow(dev));
      }
      $.append($("media-" + side + "-devices-tbody"), rows);
    }

    // Basic information
    insertBasicInfo("audioBackend", data.currentAudioBackend);
    insertBasicInfo("maxAudioChannels", data.currentMaxAudioChannels);
    insertBasicInfo("channelLayout", data.currentPreferredChannelLayout);
    insertBasicInfo("sampleRate", data.currentPreferredSampleRate);

    // Output devices information
    insertDeviceInfo("output", data.audioOutputDevices);

    // Input devices information
    insertDeviceInfo("input", data.audioInputDevices);
  },

  javaScript: function javaScript(data) {
    $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
  },

  accessibility: function accessibility(data) {
    $("a11y-activated").textContent = data.isActive;
    $("a11y-force-disabled").textContent = data.forceDisabled || 0;
    let a11yHandlerUsed = $("a11y-handler-used");
    if (a11yHandlerUsed) {
      a11yHandlerUsed.textContent = data.handlerUsed;
    }
  },

  libraryVersions: function libraryVersions(data) {
    let strings = stringBundle();
    let trs = [
      $.new("tr", [
        $.new("th", ""),
        $.new("th", strings.GetStringFromName("minLibVersions")),
        $.new("th", strings.GetStringFromName("loadedLibVersions")),
      ])
    ];
    sortedArrayFromObject(data).forEach(
      function([name, val]) {
        trs.push($.new("tr", [
          $.new("td", name),
          $.new("td", val.minVersion),
          $.new("td", val.version),
        ]));
      }
    );
    $.append($("libversions-tbody"), trs);
  },

  userJS: function userJS(data) {
    if (!data.exists)
      return;
    let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
    userJSFile.append("user.js");
    $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
    $("prefs-user-js-section").style.display = "";
    // Clear the no-copy class
    $("prefs-user-js-section").className = "";
  },

  sandbox: function sandbox(data) {
    if (!AppConstants.MOZ_SANDBOX)
      return;

    let strings = stringBundle();
    let tbody = $("sandbox-tbody");
    for (let key in data) {
      // Simplify the display a little in the common case.
      if (key === "hasPrivilegedUserNamespaces" &&
          data[key] === data["hasUserNamespaces"]) {
        continue;
      }
      if (key === "syscallLog") {
        // Not in this table.
        continue;
      }
      tbody.appendChild($.new("tr", [
        $.new("th", strings.GetStringFromName(key), "column"),
        $.new("td", data[key]),
      ]));
    }

    if ("syscallLog" in data) {
      let syscallBody = $("sandbox-syscalls-tbody");
      let argsHead = $("sandbox-syscalls-argshead");
      for (let syscall of data.syscallLog) {
        if (argsHead.colSpan < syscall.args.length) {
          argsHead.colSpan = syscall.args.length;
        }
        let cells = [
          $.new("td", syscall.index, "integer"),
          $.new("td", syscall.msecAgo / 1000),
          $.new("td", syscall.pid, "integer"),
          $.new("td", syscall.tid, "integer"),
          $.new("td", strings.GetStringFromName("sandboxProcType." +
                                                syscall.procType)),
          $.new("td", syscall.syscall, "integer"),
        ];
        for (let arg of syscall.args) {
          cells.push($.new("td", arg, "integer"));
        }
        syscallBody.appendChild($.new("tr", cells));
      }
    }
  },
};

var $ = document.getElementById.bind(document);

$.new = function $_new(tag, textContentOrChildren, className, attributes) {
  let elt = document.createElement(tag);
  if (className)
    elt.className = className;
  if (attributes) {
    for (let attrName in attributes)
      elt.setAttribute(attrName, attributes[attrName]);
  }
  if (Array.isArray(textContentOrChildren))
    this.append(elt, textContentOrChildren);
  else
    elt.textContent = String(textContentOrChildren);
  return elt;
};

$.append = function $_append(parent, children) {
  children.forEach(c => parent.appendChild(c));
};

function stringBundle() {
  return Services.strings.createBundle(
           "chrome://global/locale/aboutSupport.properties");
}

function assembleFromGraphicsFailure(i, data) {
  // Only cover the cases we have today; for example, we do not have
  // log failures that assert and we assume the log level is 1/error.
  let message = data.failures[i];
  let index = data.indices[i];
  let what = "";
  if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
    // Non-asserting log failure - the message is substring(14)
    what = "LogFailure";
    message = message.substring(14);
  } else if (message.search(/\[GFX1-\]: /) == 0) {
    // Non-asserting - the message is substring(9)
    what = "Error";
    message = message.substring(9);
  } else if (message.search(/\[GFX1\]: /) == 0) {
    // Asserting - the message is substring(8)
    what = "Assert";
    message = message.substring(8);
  }
  let assembled = {"index": index,
                   "header": ("(#" + index + ") " + what),
                   "message": message};
  return assembled;
}

function sortedArrayFromObject(obj) {
  let tuples = [];
  for (let prop in obj)
    tuples.push([prop, obj[prop]]);
  tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2));
  return tuples;
}

function copyRawDataToClipboard(button) {
  if (button)
    button.disabled = true;
  try {
    Troubleshoot.snapshot(function(snapshot) {
      if (button)
        button.disabled = false;
      let str = Cc["@mozilla.org/supports-string;1"].
                createInstance(Ci.nsISupportsString);
      str.data = JSON.stringify(snapshot, undefined, 2);
      let transferable = Cc["@mozilla.org/widget/transferable;1"].
                         createInstance(Ci.nsITransferable);
      transferable.init(getLoadContext());
      transferable.addDataFlavor("text/unicode");
      transferable.setTransferData("text/unicode", str, str.data.length * 2);
      Cc["@mozilla.org/widget/clipboard;1"].
        getService(Ci.nsIClipboard).
        setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
      if (AppConstants.platform == "android") {
        // Present a toast notification.
        let message = {
          type: "Toast:Show",
          message: stringBundle().GetStringFromName("rawDataCopied"),
          duration: "short"
        };
        Services.androidBridge.handleGeckoMessage(message);
      }
    });
  } catch (err) {
    if (button)
      button.disabled = false;
    throw err;
  }
}

function getLoadContext() {
  return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsILoadContext);
}

function copyContentsToClipboard() {
  // Get the HTML and text representations for the important part of the page.
  let contentsDiv = $("contents");
  let dataHtml = contentsDiv.innerHTML;
  let dataText = createTextForElement(contentsDiv);

  // We can't use plain strings, we have to use nsSupportsString.
  let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
  let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
  let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);

  let transferable = Cc["@mozilla.org/widget/transferable;1"]
                       .createInstance(Ci.nsITransferable);
  transferable.init(getLoadContext());

  // Add the HTML flavor.
  transferable.addDataFlavor("text/html");
  ssHtml.data = dataHtml;
  transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);

  // Add the plain text flavor.
  transferable.addDataFlavor("text/unicode");
  ssText.data = dataText;
  transferable.setTransferData("text/unicode", ssText, dataText.length * 2);

  // Store the data into the clipboard.
  let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
                    .getService(Ci.nsIClipboard);
  clipboard.setData(transferable, null, clipboard.kGlobalClipboard);

  if (AppConstants.platform == "android") {
    // Present a toast notification.
    let message = {
      type: "Toast:Show",
      message: stringBundle().GetStringFromName("textCopied"),
      duration: "short"
    };
    Services.androidBridge.handleGeckoMessage(message);
  }
}

// Return the plain text representation of an element.  Do a little bit
// of pretty-printing to make it human-readable.
function createTextForElement(elem) {
  let serializer = new Serializer();
  let text = serializer.serialize(elem);

  // Actual CR/LF pairs are needed for some Windows text editors.
  if (AppConstants.platform == "win") {
    text = text.replace(/\n/g, "\r\n");
  }

  return text;
}

function Serializer() {
}

Serializer.prototype = {

  serialize(rootElem) {
    this._lines = [];
    this._startNewLine();
    this._serializeElement(rootElem);
    this._startNewLine();
    return this._lines.join("\n").trim() + "\n";
  },

  // The current line is always the line that writing will start at next.  When
  // an element is serialized, the current line is updated to be the line at
  // which the next element should be written.
  get _currentLine() {
    return this._lines.length ? this._lines[this._lines.length - 1] : null;
  },

  set _currentLine(val) {
    return this._lines[this._lines.length - 1] = val;
  },

  _serializeElement(elem) {
    if (this._ignoreElement(elem))
      return;

    // table
    if (elem.localName == "table") {
      this._serializeTable(elem);
      return;
    }

    // all other elements

    let hasText = false;
    for (let child of elem.childNodes) {
      if (child.nodeType == Node.TEXT_NODE) {
        let text = this._nodeText(child);
        this._appendText(text);
        hasText = hasText || !!text.trim();
      } else if (child.nodeType == Node.ELEMENT_NODE)
        this._serializeElement(child);
    }

    // For headings, draw a "line" underneath them so they stand out.
    if (/^h[0-9]+$/.test(elem.localName)) {
      let headerText = (this._currentLine || "").trim();
      if (headerText) {
        this._startNewLine();
        this._appendText("-".repeat(headerText.length));
      }
    }

    // Add a blank line underneath block elements but only if they contain text.
    if (hasText) {
      let display = window.getComputedStyle(elem).getPropertyValue("display");
      if (display == "block") {
        this._startNewLine();
        this._startNewLine();
      }
    }
  },

  _startNewLine(lines) {
    let currLine = this._currentLine;
    if (currLine) {
      // The current line is not empty.  Trim it.
      this._currentLine = currLine.trim();
      if (!this._currentLine)
        // The current line became empty.  Discard it.
        this._lines.pop();
    }
    this._lines.push("");
  },

  _appendText(text, lines) {
    this._currentLine += text;
  },

  _isHiddenSubHeading(th) {
    return th.parentNode.parentNode.style.display == "none";
  },

  _serializeTable(table) {
    // Collect the table's column headings if in fact there are any.  First
    // check thead.  If there's no thead, check the first tr.
    let colHeadings = {};
    let tableHeadingElem = table.querySelector("thead");
    if (!tableHeadingElem)
      tableHeadingElem = table.querySelector("tr");
    if (tableHeadingElem) {
      let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
      // If there's a contiguous run of th's in the children starting from the
      // rightmost child, then consider them to be column headings.
      for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
        let col = tableHeadingCols[i];
        if (col.localName != "th" || col.classList.contains("title-column"))
          break;
        colHeadings[i] = this._nodeText(col).trim();
      }
    }
    let hasColHeadings = Object.keys(colHeadings).length > 0;
    if (!hasColHeadings)
      tableHeadingElem = null;

    let trs = table.querySelectorAll("table > tr, tbody > tr");
    let startRow =
      tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;

    if (startRow >= trs.length)
      // The table's empty.
      return;

    if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
      // Use column headings.  Print each tr as a multi-line chunk like:
      //   Heading 1: Column 1 value
      //   Heading 2: Column 2 value
      for (let i = startRow; i < trs.length; i++) {
        if (this._ignoreElement(trs[i]))
          continue;
        let children = trs[i].querySelectorAll("td");
        for (let j = 0; j < children.length; j++) {
          let text = "";
          if (colHeadings[j])
            text += colHeadings[j] + ": ";
          text += this._nodeText(children[j]).trim();
          this._appendText(text);
          this._startNewLine();
        }
        this._startNewLine();
      }
      return;
    }

    // Don't use column headings.  Assume the table has only two columns and
    // print each tr in a single line like:
    //   Column 1 value: Column 2 value
    for (let i = startRow; i < trs.length; i++) {
      if (this._ignoreElement(trs[i]))
        continue;
      let children = trs[i].querySelectorAll("th,td");
      let rowHeading = this._nodeText(children[0]).trim();
      if (children[0].classList.contains("title-column")) {
        if (!this._isHiddenSubHeading(children[0]))
          this._appendText(rowHeading);
      } else if (children.length == 1) {
        // This is a single-cell row.
        this._appendText(rowHeading);
      } else {
        let childTables = trs[i].querySelectorAll("table");
        if (childTables.length) {
          // If we have child tables, don't use nodeText - its trs are already
          // queued up from querySelectorAll earlier.
          this._appendText(rowHeading + ": ");
        } else {
          this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim());
        }
      }
      this._startNewLine();
    }
    this._startNewLine();
  },

  _ignoreElement(elem) {
    return elem.classList.contains("no-copy");
  },

  _nodeText(node) {
    return node.textContent.replace(/\s+/g, " ");
  },
};

function openProfileDirectory() {
  // Get the profile directory.
  let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
  let profileDir = currProfD.path;

  // Show the profile directory.
  let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
                                           "nsILocalFile", "initWithPath");
  new nsLocalFile(profileDir).reveal();
}

/**
 * Profile reset is only supported for the default profile if the appropriate migrator exists.
 */
function populateActionBox() {
  if (ResetProfile.resetSupported()) {
    $("reset-box").style.display = "block";
    $("action-box").style.display = "block";
  }
  if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
    $("safe-mode-box").style.display = "block";
    $("action-box").style.display = "block";
  }
}

// Prompt user to restart the browser in safe mode
function safeModeRestart() {
  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);
  }
}
/**
 * Set up event listeners for buttons.
 */
function setupEventListeners() {
  if (AppConstants.platform !== "android") {
    $("show-update-history-button").addEventListener("click", function(event) {
      var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
      prompter.showUpdateHistory(window);
    });
    $("reset-box-button").addEventListener("click", function(event) {
      ResetProfile.openConfirmationDialog(window);
    });
    $("restart-in-safe-mode-button").addEventListener("click", function(event) {
      if (Services.obs.enumerateObservers("restart-in-safe-mode").hasMoreElements()) {
        Services.obs.notifyObservers(null, "restart-in-safe-mode");
      } else {
        safeModeRestart();
      }
    });
    $("verify-place-integrity-button").addEventListener("click", function(event) {
      PlacesDBUtils.checkAndFixDatabase().then((tasksStatusMap) => {
        let msg = PlacesDBUtils.getLegacyLog(tasksStatusMap).join("\n");
        $("verify-place-result").style.display = "block";
        $("verify-place-result").classList.remove("no-copy");
        $("verify-place-result").textContent = msg;
      });
    });
  }

  $("copy-raw-data-to-clipboard").addEventListener("click", function(event) {
    copyRawDataToClipboard(this);
  });
  $("copy-to-clipboard").addEventListener("click", function(event) {
    copyContentsToClipboard();
  });
  $("profile-dir-button").addEventListener("click", function(event) {
    openProfileDirectory();
  });
}
PK
!<5V@@0chrome/toolkit/content/global/aboutSupport.xhtml<?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 % aboutSupportDTD SYSTEM "chrome://global/locale/aboutSupport.dtd"> %aboutSupportDTD;
  <!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd"> %resetProfileDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>&aboutSupport.pageTitle;</title>

    <link rel="icon" type="image/png" id="favicon"
          href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
          type="text/css"/>
    <link rel="stylesheet" href="chrome://global/skin/aboutSupport.css"
          type="text/css"/>

    <script type="application/javascript"
            src="chrome://global/content/aboutSupport.js"/>
    <script type="application/javascript"
            src="chrome://global/content/resetProfile.js"/>
  </head>

  <body dir="&locale.dir;">

    <div id="action-box">
      <div id="reset-box">
        <h3>&refreshProfile.title;</h3>
        <button id="reset-box-button">
          &refreshProfile.button.label;
        </button>
      </div>
      <div id="safe-mode-box">
        <h3>&aboutSupport.safeModeTitle;</h3>
        <button id="restart-in-safe-mode-button">
          &aboutSupport.restartInSafeMode.label;
        </button>
      </div>
    </div>

    <h1>
      &aboutSupport.pageTitle;
    </h1>

    <div class="page-subtitle">
        &aboutSupport.pageSubtitle;
    </div>

    <div>
      <button id="copy-raw-data-to-clipboard">
        &aboutSupport.copyRawDataToClipboard.label;
      </button>
      <button id="copy-to-clipboard">
        &aboutSupport.copyTextToClipboard.label;
      </button>
    </div>

    <div id="contents">

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.appBasicsTitle;
      </h2>

      <table>
        <tbody>
          <tr>
            <th class="column">
              &aboutSupport.appBasicsName;
            </th>

            <td id="application-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsVersion;
            </th>

            <td id="version-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsBuildID;
              </th>
            <td id="buildid-box"></td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsUpdateHistory;
            </th>

            <td>
              <button id="show-update-history-button">
                &aboutSupport.appBasicsShowUpdateHistory;
              </button>
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsUpdateChannel;
            </th>
            <td id="updatechannel-box"></td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsUserAgent;
            </th>

            <td id="useragent-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsOS;
            </th>

            <td id="os-box">
            </td>
          </tr>

          <tr id="profile-row" class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsProfileDirWinMac;
            </th>

            <td>
              <button id="profile-dir-button">
                &aboutSupport.showWin2.label;
               </button>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsEnabledPlugins;
            </th>

            <td>
              <a href="about:plugins">about:plugins</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsBuildConfig;
            </th>

            <td>
              <a href="about:buildconfig">about:buildconfig</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsMemoryUse;
            </th>

            <td>
              <a href="about:memory">about:memory</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsPerformance;
            </th>

            <td>
              <a href="about:performance">about:performance</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsServiceWorkers;
            </th>

            <td>
              <a href="about:serviceworkers">about:serviceworkers</a>
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsMultiProcessSupport;
            </th>

            <td id="multiprocess-box">
            </td>
          </tr>

          <tr id="contentprocesses-row">
            <th class="column">
              &aboutSupport.appBasicsProcessCount;
            </th>

            <td id="contentprocesses-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              Stylo
            </th>

            <td id="stylo-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsKeyGoogle;
            </th>

            <td id="key-google-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsKeyMozilla;
            </th>

            <td id="key-mozilla-box">
            </td>
          </tr>

          <tr>
            <th class="column">
              &aboutSupport.appBasicsSafeMode;
            </th>

            <td id="safemode-box">
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column">
              &aboutSupport.appBasicsProfiles;
            </th>

            <td>
              <a href="about:profiles">about:profiles</a>
            </td>
          </tr>

        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="crashes-title">
        &aboutSupport.crashes.title;
      </h2>

      <table id="crashes-table">
        <thead>
          <tr>
            <th>
              &aboutSupport.crashes.id;
            </th>
            <th>
              &aboutSupport.crashes.sendDate;
            </th>
          </tr>
        </thead>
        <tbody id="crashes-tbody">
        </tbody>
      </table>
      <p id="crashes-allReports" class="hidden no-copy">
        <a href="about:crashes" id="crashes-allReportsWithPending" class="block">&aboutSupport.crashes.allReports;</a>
      </p>
      <p id="crashes-noConfig" class="hidden no-copy">&aboutSupport.crashes.noConfig;</p>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.featuresTitle;
      </h2>

      <table>
        <thead>
          <tr>
            <th>
              &aboutSupport.featureName;
            </th>
            <th>
              &aboutSupport.featureVersion;
            </th>
            <th>
              &aboutSupport.featureId;
            </th>
          </tr>
        </thead>
        <tbody id="features-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.extensionsTitle;
      </h2>

      <table>
        <thead>
          <tr>
            <th>
              &aboutSupport.extensionName;
            </th>
            <th>
              &aboutSupport.extensionVersion;
            </th>
            <th>
              &aboutSupport.extensionEnabled;
            </th>
            <th>
              &aboutSupport.extensionId;
            </th>
          </tr>
        </thead>
        <tbody id="extensions-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.graphicsTitle;
      </h2>

      <table>
        <tbody id="graphics-features-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsFeaturesTitle;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-tbody">
        </tbody>

        <tbody id="graphics-gpu-1-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsGPU1Title;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-gpu-2-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsGPU2Title;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-diagnostics-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsDiagnosticsTitle;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-decisions-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsDecisionLogTitle;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-crashguards-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsCrashGuardsTitle;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-workarounds-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsWorkaroundsTitle;
            </th>
          </tr>
        </tbody>

        <tbody id="graphics-failures-tbody">
          <tr>
            <th colspan="2" class="title-column">
              &aboutSupport.graphicsFailureLogTitle;
            </th>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.mediaTitle;
      </h2>
      <table>
        <tbody id="media-info-tbody">
        </tbody>

        <tbody id="media-output-devices-tbody">
          <tr>
            <th colspan="10" class="title-column">
              &aboutSupport.mediaOutputDevicesTitle;
            </th>
          </tr>
          <tr>
            <th>
              &aboutSupport.mediaDeviceName;
            </th>
            <th>
              &aboutSupport.mediaDeviceGroup;
            </th>
            <th>
              &aboutSupport.mediaDeviceVendor;
            </th>
            <th>
              &aboutSupport.mediaDeviceState;
            </th>
            <th>
              &aboutSupport.mediaDevicePreferred;
            </th>
            <th>
              &aboutSupport.mediaDeviceFormat;
            </th>
            <th>
              &aboutSupport.mediaDeviceChannels;
            </th>
            <th>
              &aboutSupport.mediaDeviceRate;
            </th>
            <th>
              &aboutSupport.mediaDeviceLatency;
            </th>
          </tr>
        </tbody>

        <tbody id="media-input-devices-tbody">
          <tr>
            <th colspan="10" class="title-column">
              &aboutSupport.mediaInputDevicesTitle;
            </th>
          </tr>
          <tr>
            <th>
              &aboutSupport.mediaDeviceName;
            </th>
            <th>
              &aboutSupport.mediaDeviceGroup;
            </th>
            <th>
              &aboutSupport.mediaDeviceVendor;
            </th>
            <th>
              &aboutSupport.mediaDeviceState;
            </th>
            <th>
              &aboutSupport.mediaDevicePreferred;
            </th>
            <th>
              &aboutSupport.mediaDeviceFormat;
            </th>
            <th>
              &aboutSupport.mediaDeviceChannels;
            </th>
            <th>
              &aboutSupport.mediaDeviceRate;
            </th>
            <th>
              &aboutSupport.mediaDeviceLatency;
            </th>
          </tr>
        </tbody>

      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.modifiedKeyPrefsTitle;
      </h2>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name">
            &aboutSupport.modifiedPrefsName;
          </th>

          <th class="value">
            &aboutSupport.modifiedPrefsValue;
          </th>
        </thead>

        <tbody id="prefs-tbody">
        </tbody>
      </table>

      <section id="prefs-user-js-section" class="hidden no-copy">
        <h3>&aboutSupport.userJSTitle;</h3>
        <p>&aboutSupport.userJSDescription;</p>
      </section>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.lockedKeyPrefsTitle;
      </h2>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name">
            &aboutSupport.lockedPrefsName;
          </th>

          <th class="value">
            &aboutSupport.lockedPrefsValue;
          </th>
        </thead>

        <tbody id="locked-prefs-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section">
        &aboutSupport.placeDatabaseTitle;
      </h2>

      <table>
        <tr class="no-copy">
          <th class="column">
            &aboutSupport.placeDatabaseIntegrity;
          </th>

          <td>
            <button id="verify-place-integrity-button">
              &aboutSupport.placeDatabaseVerifyIntegrity;
            </button>
            <pre id="verify-place-result" class="hidden no-copy"></pre>
          </td>
        </tr>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section">
        &aboutSupport.jsTitle;
      </h2>

      <table>
        <tbody>
          <tr>
            <th class="column">
              &aboutSupport.jsIncrementalGC;
            </th>

            <td id="javascript-incremental-gc">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section">
        &aboutSupport.a11yTitle;
      </h2>

      <table>
        <tbody>
          <tr>
            <th class="column">
              &aboutSupport.a11yActivated;
            </th>

            <td id="a11y-activated">
            </td>
          </tr>
          <tr>
            <th class="column">
              &aboutSupport.a11yForceDisabled;
            </th>

            <td id="a11y-force-disabled">
            </td>
          </tr>
          <tr>
            <th class="column">
              &aboutSupport.a11yHandlerUsed;
            </th>

            <td id="a11y-handler-used">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section">
        &aboutSupport.libraryVersionsTitle;
      </h2>

      <table>
        <tbody id="libversions-tbody">
        </tbody>
      </table>


      <h2 class="major-section">
        &aboutSupport.experimentsTitle;
      </h2>

      <table>
        <thead>
          <tr>
            <th>
              &aboutSupport.experimentName;
            </th>
            <th>
              &aboutSupport.experimentId;
            </th>
            <th>
              &aboutSupport.experimentDescription;
            </th>
            <th>
              &aboutSupport.experimentActive;
            </th>
            <th>
              &aboutSupport.experimentEndDate;
            </th>
            <th>
              &aboutSupport.experimentHomepage;
            </th>
            <th>
              &aboutSupport.experimentBranch;
            </th>
          </tr>
        </thead>
        <tbody id="experiments-tbody">
        </tbody>
      </table>
      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="sandbox">
	&aboutSupport.sandboxTitle;
      </h2>

      <table>
	<tbody id="sandbox-tbody">
	</tbody>
      </table>


    </div>

  </body>

</html>
PK
!<z0chrome/toolkit/content/global/aboutTelemetry.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

html {
  height: 100%;
}

body {
  display: flex;
  align-items: stretch;
  height: 100%;
}

#categories {
  min-width: 250px;
  padding-top: 0px;
  overflow-y: auto;
}

#category-raw {
  border-top: 1px solid var(--in-content-header-border-color);
  box-sizing: border-box;
  background-color: inherit;
  min-width: inherit;
  position: absolute;
  bottom: 0;
  left: 0;
}

#category-raw.selected {
   background-color: var(--in-content-category-background-active);
}

.heading {
  display: flex;
  flex-direction: column;
  font-size: 18px;
  color: var(--in-content-category-text);
  pointer-events: none;
  padding: 12px 21px 15px 21px;
  border-bottom: 1px solid var(--in-content-header-border-color);
}

.header {
  display: flex;
}

.header select {
  margin-left: 4px;
}

#sectionTitle {
  flex-grow: 1;
}

.heading > h3 {
  margin: 0;
  padding-bottom: 12px;
}

#ping-type {
  align-self: center;
  pointer-events: all;
  cursor: pointer;
}

#older-ping, #newer-ping, #ping-date {
  pointer-events: all;
  -moz-user-select: none;
  cursor: pointer;
  text-align: center;
}

.controls {
  display: flex;
  justify-content: space-between;
}

.category:not(.has-data) {
  display: none;
}

.category {
  cursor: pointer;
  display: flex;
  flex-direction: column;
  min-height: 42px;
}

.category-name {
  padding: 9px 0px;
  vertical-align: middle;
}

.category.has-subsection {
  padding-inline-start: 0px;
}

.category.has-subsection > span {
  padding-inline-start: 11px;
}

.category.has-subsection.selected {
  border-inline-start: none;
}

.category-subsection {
  padding: 9px 0px;
  padding-inline-start: 30px;
  display: none;
  -moz-user-select: none;
}

.category-subsection::first-letter {
  text-transform: uppercase;
}

.category.selected > .category-subsection {
  display: block;
}

.category-subsection.selected {
  padding-inline-start: 26px;
  border-inline-start: solid 4px var(--in-content-border-highlight);
}

.category-name {
  pointer-events: none;
}

.main-content {
  width: 100%;
  font-size: 18px;
  line-height:1.6;
  z-index: 1;
  position: relative;
}

section:not(.active) {
  display: none;
}

#page-description {
  border: 1px solid threedshadow;
  margin: 0px;
  padding: 10px;
  line-height: 1.2;
}

#ping-explanation > span {
  cursor: pointer;
  border-bottom-width: 2px;
  border-bottom-style: solid;
}

#ping-explanation > span:hover {
  color: var(--in-content-page-color);
  border-bottom-width: 2px;
  border-bottom-style: solid;
}

#ping-picker.hidden {
  display: none;
}

#ping-picker {
  position: absolute;
  z-index: 2;
  top: 40px;
  right: 40px;
  border-radius: 2px;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  padding: 24px;
  flex-direction: column;
  background-color: white;
  border: 1px solid var(--in-content-header-border-color);
  margin: 12px 0px;
}

#ping-picker .title {
  margin: 4px 0px;
}

#ping-source-picker {
  margin-left: 5px;
  margin-bottom: 10px;
}

.stack-title {
  font-size: medium;
  font-weight: bold;
  text-decoration: underline;
}

#histograms, #thread-hang-stats>div {
  overflow: hidden;
}

.histogram {
  float: left;
  white-space: nowrap;
  padding: 10px;
  position: relative; /* required for position:absolute of the contained .copy-node */
  padding: 12px 20px 12px 20px;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 2px;
  margin-bottom: 24px;
  margin-right: 24px;
  min-height: 284px;
}


body[dir="rtl"] .histogram {
  float: right;
}

.histogram-title {
  text-overflow: ellipsis;
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
}

.keyed-histogram {
  white-space: nowrap;
  padding: 15px;
  position: relative; /* required for position:absolute of the contained .copy-node */
  overflow: hidden;
}

.keyed-histogram-title {
  text-overflow: ellipsis;
  width: 100%;
  margin: 10px;
  font-weight: bold;
  font-size: 120%;
  white-space: nowrap;
}

.bar {
  width: 2em;
  margin: 2px;
  text-align: center;
  float: left;
  font-family: monospace;
}

body[dir="rtl"] .bar {
  float: right;
}

.bar-inner {
  background-color: #0095DD;
  border: 1px solid #00539F;
  border-radius: 2px;
}

.bar:nth-child(even) .long-label  {
  margin-bottom: 1em;
}

th, td, table {
  text-align: start;
  border-collapse: collapse;
}

table {
  width: 100%;
  font-size: 15px;
}

td {
  padding-bottom: 0.25em;
  border-bottom: 1px solid var(--in-content-box-border-color);
}

tr:not(:first-child):hover {
  background-color: rgba(0, 0, 0, 0.05);
}

th {
  font-size: larger;
  font-weight: bold;
  white-space: nowrap;
  padding-bottom: 0.5em;
}

body[dir="rtl"] th {
  text-align: right;
}

caption {
  font-weight: bold;
  white-space: nowrap;
  text-align: left;
  font-size: large;
}

body[dir="rtl"] caption {
  text-align: right;
}

.copy-node {
  visibility: hidden;
  position: absolute;
  bottom: 1px;
  right: 1px;
}

body[dir="rtl"] .copy-node {
  left: 1px;
}

.histogram:hover .copy-node {
  visibility: visible;
}

#raw-ping-data {
  font-size: 15px;
}

caption {
  font-size: larger;
  margin: 5px 0;
}PK
!<@-11/chrome/toolkit/content/global/aboutTelemetry.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm");
Cu.import("resource://gre/modules/TelemetryController.jsm");
Cu.import("resource://gre/modules/TelemetryArchive.jsm");
Cu.import("resource://gre/modules/TelemetryUtils.jsm");
Cu.import("resource://gre/modules/TelemetryLog.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");

const Telemetry = Services.telemetry;
const bundle = Services.strings.createBundle(
  "chrome://global/locale/aboutTelemetry.properties");
const brandBundle = Services.strings.createBundle(
  "chrome://branding/locale/brand.properties");

// Maximum height of a histogram bar (in em for html, in chars for text)
const MAX_BAR_HEIGHT = 8;
const MAX_BAR_CHARS = 25;
const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_DEBUG_SLOW_SQL = "toolkit.telemetry.debugSlowSql";
const PREF_SYMBOL_SERVER_URI = "profiler.symbolicationUrl";
const DEFAULT_SYMBOL_SERVER_URI = "http://symbolapi.mozilla.org";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";

// ms idle before applying the filter (allow uninterrupted typing)
const FILTER_IDLE_TIMEOUT = 500;

const isWindows = (Services.appinfo.OS == "WINNT");
const EOL = isWindows ? "\r\n" : "\n";

// This is the ping object currently displayed in the page.
var gPingData = null;

// Cached value of document's RTL mode
var documentRTLMode = "";

/**
 * Helper function for determining whether the document direction is RTL.
 * Caches result of check on first invocation.
 */
function isRTL() {
  if (!documentRTLMode)
    documentRTLMode = window.getComputedStyle(document.body).direction;
  return (documentRTLMode == "rtl");
}

function isFlatArray(obj) {
  if (!Array.isArray(obj)) {
    return false;
  }
  return !obj.some(e => typeof(e) == "object");
}

/**
 * This is a helper function for explodeObject.
 */
function flattenObject(obj, map, path, array) {
  for (let k of Object.keys(obj)) {
    let newPath = [...path, array ? "[" + k + "]" : k];
    let v = obj[k];
    if (!v || (typeof(v) != "object")) {
      map.set(newPath.join("."), v);
    } else if (isFlatArray(v)) {
      map.set(newPath.join("."), "[" + v.join(", ") + "]");
    } else {
      flattenObject(v, map, newPath, Array.isArray(v));
    }
  }
}

/**
 * This turns a JSON object into a "flat" stringified form.
 *
 * For an object like {a: "1", b: {c: "2", d: "3"}} it returns a Map of the
 * form Map(["a","1"], ["b.c", "2"], ["b.d", "3"]).
 */
function explodeObject(obj) {
  let map = new Map();
  flattenObject(obj, map, []);
  return map;
}

function filterObject(obj, filterOut) {
  let ret = {};
  for (let k of Object.keys(obj)) {
    if (filterOut.indexOf(k) == -1) {
      ret[k] = obj[k];
    }
  }
  return ret;
}

/**
 * This turns a JSON object into a "flat" stringified form, separated into top-level sections.
 *
 * For an object like:
 *   {
 *     a: {b: "1"},
 *     c: {d: "2", e: {f: "3"}}
 *   }
 * it returns a Map of the form:
 *   Map([
 *     ["a", Map(["b","1"])],
 *     ["c", Map([["d", "2"], ["e.f", "3"]])]
 *   ])
 */
function sectionalizeObject(obj) {
  let map = new Map();
  for (let k of Object.keys(obj)) {
    map.set(k, explodeObject(obj[k]));
  }
  return map;
}

/**
 * Obtain the main DOMWindow for the current context.
 */
function getMainWindow() {
  return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsIDocShellTreeItem)
               .rootTreeItem
               .QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindow);
}

/**
 * Obtain the DOMWindow that can open a preferences pane.
 *
 * This is essentially "get the browser chrome window" with the added check
 * that the supposed browser chrome window is capable of opening a preferences
 * pane.
 *
 * This may return null if we can't find the browser chrome window.
 */
function getMainWindowWithPreferencesPane() {
  let mainWindow = getMainWindow();
  if (mainWindow && "openAdvancedPreferences" in mainWindow) {
    return mainWindow;
  }
  return null;
}

/**
 * Remove all child nodes of a document node.
 */
function removeAllChildNodes(node) {
  while (node.hasChildNodes()) {
    node.removeChild(node.lastChild);
  }
}

/**
 * Pad a number to two digits with leading "0".
 */
function padToTwoDigits(n) {
  return new String(n).padStart(2, "0");
}

/**
 * Return yesterdays date with the same time.
 */
function yesterday(date) {
  let d = new Date(date);
  d.setDate(d.getDate() - 1);
  return d;
}

/**
 * Return tomorrow's date with the same time.
 */
function tomorrow(date) {
  let d = new Date(date);
  d.setDate(d.getDate() + 1);
  return d;
}

/**
 * This returns a short date string of the form YYYY/MM/DD.
 */
function shortDateString(date) {
  return date.getFullYear()
         + "/" + padToTwoDigits(date.getMonth() + 1)
         + "/" + padToTwoDigits(date.getDate());
}

/**
 * This returns a short time string of the form hh:mm:ss.
 */
function shortTimeString(date) {
  return padToTwoDigits(date.getHours())
         + ":" + padToTwoDigits(date.getMinutes())
         + ":" + padToTwoDigits(date.getSeconds());
}

var Settings = {
  SETTINGS: [
    // data upload
    {
      pref: PREF_FHR_UPLOAD_ENABLED,
      defaultPrefValue: false,
    },
    // extended "Telemetry" recording
    {
      pref: PREF_TELEMETRY_ENABLED,
      defaultPrefValue: false,
    },
  ],

  attachObservers() {
    for (let s of this.SETTINGS) {
      let setting = s;
      Preferences.observe(setting.pref, this.render, this);
    }

    let elements = document.getElementsByClassName("change-data-choices-link");
    for (let el of elements) {
      el.addEventListener("click", function() {
        if (AppConstants.platform == "android") {
          Cu.import("resource://gre/modules/Messaging.jsm");
          EventDispatcher.instance.sendRequest({
            type: "Settings:Show",
            resource: "preferences_privacy",
          });
        } else {
          // Show the data choices preferences on desktop.
          let mainWindow = getMainWindowWithPreferencesPane();
          // The advanced subpanes are only supported in the old organization,
          // which will be removed by bug 1349689.
          if (Preferences.get("browser.preferences.useOldOrganization")) {
            mainWindow.openAdvancedPreferences("dataChoicesTab", {origin: "aboutTelemetry"});
          } else {
            mainWindow.openPreferences("privacy-reports", {origin: "aboutTelemetry"});
          }
        }
      });
    }
  },

  detachObservers() {
    for (let setting of this.SETTINGS) {
      Preferences.ignore(setting.pref, this.render, this);
    }
  },

  getStatusStringForSetting(setting) {
    let enabled = Preferences.get(setting.pref, setting.defaultPrefValue);
    let status = bundle.GetStringFromName(enabled ? "telemetryEnabled" : "telemetryDisabled");
    return status;
  },

  /**
   * Updates the button & text at the top of the page to reflect Telemetry state.
   */
  render() {
    let homeExplanation = document.getElementById("home-explanation");
    let fhrEnabled = Preferences.get(this.SETTINGS[0].pref, this.SETTINGS[0].defaultPrefValue);
    fhrEnabled = bundle.GetStringFromName(fhrEnabled ? "telemetryEnabled" : "telemetryDisabled");
    let extendedEnabled = Preferences.get(this.SETTINGS[1].pref, this.SETTINGS[1].defaultPrefValue);
    extendedEnabled = bundle.GetStringFromName(extendedEnabled ? "extendedTelemetryEnabled" : "extendedTelemetryDisabled");
    let parameters = [fhrEnabled, extendedEnabled].map(this.convertStringToLink);

    let explanation = bundle.formatStringFromName("homeExplanation", parameters, 2);

    // eslint-disable-next-line no-unsanitized/property
    homeExplanation.innerHTML = explanation;
    this.attachObservers()
  },

  convertStringToLink(string) {
    return "<a href=\"\" class=\"change-data-choices-link\">" + string + "</a>";
  },
};

var PingPicker = {
  viewCurrentPingData: null,
  _archivedPings: null,
  TYPE_ALL: bundle.GetStringFromName("telemetryPingTypeAll"),

  attachObservers() {
    let pingSourceElements = document.getElementsByName("choose-ping-source");
    for (let el of pingSourceElements) {
      el.addEventListener("change", () => this.onPingSourceChanged());
    }

    let displays = document.getElementsByName("choose-ping-display");
    for (let el of displays) {
      el.addEventListener("change", () => this.onPingDisplayChanged());
    }

    document.getElementById("show-subsession-data").addEventListener("change", () => {
      this._updateCurrentPingData();
    });

    document.getElementById("choose-ping-id").addEventListener("change", () => {
      this._updateArchivedPingData();
    });
    document.getElementById("choose-ping-type").addEventListener("change", () => {
      this.filterDisplayedPings();
    });


    document.getElementById("newer-ping")
            .addEventListener("click", () => this._movePingIndex(-1));
    document.getElementById("older-ping")
            .addEventListener("click", () => this._movePingIndex(1));

    document.addEventListener("click", (ev) => {
      if (ev.target.querySelector("#ping-picker")) {
        document.getElementById("ping-picker").classList.add("hidden");
      }
    });
    document.getElementById("choose-payload")
            .addEventListener("change", () => displayPingData(gPingData));
    document.getElementById("processes")
            .addEventListener("change", () => displayPingData(gPingData));
    Array.from(document.querySelectorAll(".change-ping")).forEach(el =>
      el.addEventListener("click", () =>
        document.getElementById("ping-picker").classList.remove("hidden"))
    );
  },

  onPingSourceChanged() {
    this.update();
  },

  onPingDisplayChanged() {
    this.update();
  },

  render() {
    let pings = bundle.GetStringFromName("pingExplanationLink");
    let pingLink = "<a href=\"http://gecko.readthedocs.io/en/latest/toolkit/components/telemetry/telemetry/concepts/pings.html\">" + pings + "</a>";
    let pingName = this._getSelectedPingName();

    let pingDate = document.getElementById("ping-date");
    pingDate.textContent = pingName;
    pingDate.setAttribute("title", pingName);

    // Display the type and controls if the ping is not current
    let pingType = document.getElementById("ping-type");
    let older = document.getElementById("older-ping");
    let newer = document.getElementById("newer-ping");
    let explanation;
    if (!this.viewCurrentPingData) {
      let pingTypeText = this._getSelectedPingType();
      pingType.hidden = false;
      older.hidden = false;
      newer.hidden = false;
      pingType.textContent = pingTypeText;
      pingName = bundle.formatStringFromName("namedPing", [pingName, pingTypeText], 2);
      let pingNameHtml = "<span class=\"change-ping\">" + pingName + "</span>";
      let parameters = [pingLink, pingNameHtml, pingTypeText];
      explanation = bundle.formatStringFromName("pingDetails", parameters, 3);
    } else {
      pingType.hidden = true;
      older.hidden = true;
      newer.hidden = true;
      pingDate.textContent = bundle.GetStringFromName("currentPingSidebar");
      let pingNameHtml = "<span class=\"change-ping\">" + pingName + "</span>";
      explanation = bundle.formatStringFromName("pingDetailsCurrent", [pingLink, pingNameHtml], 2);
    }

    let pingExplanation = document.getElementById("ping-explanation");

    // eslint-disable-next-line no-unsanitized/property
    pingExplanation.innerHTML = explanation;
    pingExplanation.querySelector(".change-ping").addEventListener("click", () =>
      document.getElementById("ping-picker").classList.remove("hidden")
    );

    GenericSubsection.deleteAllSubSections();
  },

  async update() {
    let viewCurrent = document.getElementById("ping-source-current").checked;
    let currentChanged = viewCurrent !== this.viewCurrentPingData;
    this.viewCurrentPingData = viewCurrent;

    // If we have no archived pings, disable the ping archive selection.
    // This can happen on new profiles or if the ping archive is disabled.
    let archivedPingList = await TelemetryArchive.promiseArchivedPingList();
    let sourceArchived = document.getElementById("ping-source-archive");
    sourceArchived.disabled = (archivedPingList.length == 0);

    if (currentChanged) {
      if (this.viewCurrentPingData) {
        document.getElementById("current-ping-picker").hidden = false;
        document.getElementById("archived-ping-picker").hidden = true;
        this._updateCurrentPingData();
      } else {
        document.getElementById("current-ping-picker").hidden = true;
        await this._updateArchivedPingList(archivedPingList);
        document.getElementById("archived-ping-picker").hidden = false;
      }
    }
  },

  _updateCurrentPingData() {
    const subsession = document.getElementById("show-subsession-data").checked;
    const ping = TelemetryController.getCurrentPingData(subsession);
    if (!ping) {
      return;
    }
    displayPingData(ping, true);
  },

  _updateArchivedPingData() {
    let id = this._getSelectedPingId();
    let res = Promise.resolve();
    if (id) {
      res = TelemetryArchive.promiseArchivedPingById(id)
                            .then((ping) => displayPingData(ping, true));
    }
    return res;
  },

  async _updateArchivedPingList(pingList) {
    // The archived ping list is sorted in ascending timestamp order,
    // but descending is more practical for the operations we do here.
    pingList.reverse();
    this._archivedPings = pingList;
    // Render the archive data.
    this._renderPingList();
    // Update the displayed ping.
    await this._updateArchivedPingData();
  },

  _renderPingList() {
    let pingSelector = document.getElementById("choose-ping-id");
    Array.from(pingSelector.children).forEach((child) => removeAllChildNodes(child));

    let pingTypes = new Set();
    pingTypes.add(this.TYPE_ALL);
    let todayString =  (new Date()).toDateString();
    let yesterdayString = yesterday(new Date()).toDateString();
    for (let p of this._archivedPings) {
      pingTypes.add(p.type);
      let date = new Date(p.timestampCreated);
      let datetext = date.toLocaleDateString() + " " + shortTimeString(date);
      let text = datetext + ", " + p.type;

      let option = document.createElement("option");
      let content = document.createTextNode(text);
      option.appendChild(content);
      option.setAttribute("value", p.id);
      option.dataset.type = p.type;
      option.dataset.date = datetext;

      if (date.toDateString() == todayString) {
        pingSelector.children[0].appendChild(option);
      } else if (date.toDateString() == yesterdayString) {
        pingSelector.children[1].appendChild(option);
      } else {
        pingSelector.children[2].appendChild(option);
      }
    }
    this._renderPingTypes(pingTypes);
  },

  _renderPingTypes(pingTypes) {
    let pingTypeSelector = document.getElementById("choose-ping-type");
    removeAllChildNodes(pingTypeSelector);
    pingTypes.forEach((type) => {
      let option = document.createElement("option");
      option.appendChild(document.createTextNode(type));
      option.setAttribute("value", type);
      pingTypeSelector.appendChild(option);
    });
  },

  _movePingIndex(offset) {
    if (this.viewCurrentPingData) {
      return;
    }
    let typeSelector = document.getElementById("choose-ping-type");
    let type = typeSelector.selectedOptions.item(0).value;

    let id = this._getSelectedPingId();
    let index = this._archivedPings.findIndex((p) => p.id == id);
    let newIndex = Math.min(Math.max(0, index + offset), this._archivedPings.length - 1);

    let pingList;
    if (offset > 0) {
      pingList = this._archivedPings.slice(newIndex);
    } else {
      pingList = this._archivedPings.slice(0, newIndex);
      pingList.reverse();
    }

    let ping = pingList.find((p) => {
      return type == this.TYPE_ALL || p.type == type;
    });

    if (ping) {
      this.selectPing(ping);
      this._updateArchivedPingData();
    }
  },

  selectPing(ping) {
    let pingSelector = document.getElementById("choose-ping-id");
    // Use some() to break if we find the ping.
    Array.from(pingSelector.children).some((group) => {
      return Array.from(group.children).some((option) => {
        if (option.value == ping.id) {
          option.selected = true;
          return true;
        }
        return false;
      });
    });
  },

  filterDisplayedPings() {
    let pingSelector = document.getElementById("choose-ping-id");
    let typeSelector = document.getElementById("choose-ping-type");
    let type = typeSelector.selectedOptions.item(0).value;
    let first = true;
    Array.from(pingSelector.children).forEach((group) => {
      Array.from(group.children).forEach((option) => {
        if (first && option.dataset.type == type) {
          option.selected = true;
          first = false;
        }
        option.hidden = (type != this.TYPE_ALL) && (option.dataset.type != type);
      });
    });
    this._updateArchivedPingData();
  },

  _getSelectedPingName() {
    if (this.viewCurrentPingData) return bundle.GetStringFromName("currentPing");

    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.dataset.date;
  },

  _getSelectedPingType() {
    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.dataset.type;
  },

  _getSelectedPingId() {
    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.getAttribute("value");
  },

  _showRawPingData() {
    show(document.getElementById("category-raw"));
  },

  _showStructuredPingData() {
    show(document.getElementById("category-home"));
  },
};

var GeneralData = {
  /**
   * Renders the general data
   */
  render(aPing) {
    setHasData("general-data-section", true);
    let generalDataSection = document.getElementById("general-data");
    removeAllChildNodes(generalDataSection);

    const headings = [
      "namesHeader",
      "valuesHeader",
    ].map(h => bundle.GetStringFromName(h));

    // The payload & environment parts are handled by other renderers.
    let ignoreSections = ["payload", "environment"];
    let data = explodeObject(filterObject(aPing, ignoreSections));

    const table = GenericTable.render(data, headings);
    generalDataSection.appendChild(table);
  },
};

var EnvironmentData = {
  /**
   * Renders the environment data
   */
  render(ping) {
    let dataDiv = document.getElementById("environment-data");
    removeAllChildNodes(dataDiv);
    const hasData = !!ping.environment;
    setHasData("environment-data-section", hasData);
    if (!hasData) {
      return;
    }

    let ignore = ["addons"];
    let env = filterObject(ping.environment, ignore);
    let sections = sectionalizeObject(env);
    GenericSubsection.render(sections, dataDiv, "environment-data-section");

    // We use specialized rendering here to make the addon and plugin listings
    // more readable.
    this.createAddonSection(dataDiv, ping);
  },

  renderPersona(addonObj, addonSection, sectionTitle) {
    let table = document.createElement("table");
    table.setAttribute("id", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);
    this.appendRow(table, "persona", addonObj.persona);
    addonSection.appendChild(table);
  },

  renderActivePlugins(addonObj, addonSection, sectionTitle) {
    let table = document.createElement("table");
    table.setAttribute("id", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);

    for (let plugin of addonObj) {
      let data = explodeObject(plugin);
      this.appendHeadingName(table, data.get("name"));

      for (let [key, value] of data) {
        this.appendRow(table, key, value);
      }
    }

    addonSection.appendChild(table);
  },

  renderAddonsObject(addonObj, addonSection, sectionTitle) {
    let table = document.createElement("table");
    table.setAttribute("id", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);

    for (let id of Object.keys(addonObj)) {
      let addon = addonObj[id];
      this.appendHeadingName(table, addon.name || id);
      this.appendAddonID(table, id);
      let data = explodeObject(addon);

      for (let [key, value] of data) {
        this.appendRow(table, key, value);
      }
    }

    addonSection.appendChild(table);
  },

  renderKeyValueObject(addonObj, addonSection, sectionTitle) {
    let data = explodeObject(addonObj);
    let table = GenericTable.render(data);
    table.setAttribute("class", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);
    addonSection.appendChild(table);
  },

  appendAddonID(table, addonID) {
    this.appendRow(table, "id", addonID);
  },

  appendHeadingName(table, name) {
    let headings = document.createElement("tr");
    this.appendColumn(headings, "th", name);
    headings.cells[0].colSpan = 2;
    table.appendChild(headings);
  },

  appendAddonSubsectionTitle(section, table) {
    let caption = document.createElement("caption");
    caption.setAttribute("class", "addon-caption");
    caption.appendChild(document.createTextNode(section));
    table.appendChild(caption);
  },

  createAddonSection(dataDiv, ping) {
    let addonSection = document.createElement("div");
    addonSection.setAttribute("class", "subsection-data subdata");
    let addons = ping.environment.addons;
    this.renderAddonsObject(addons.activeAddons, addonSection, "activeAddons");
    this.renderActivePlugins(addons.activePlugins, addonSection, "activePlugins");
    this.renderKeyValueObject(addons.theme, addonSection, "theme");
    this.renderKeyValueObject(addons.activeExperiment, addonSection, "activeExperiment");
    this.renderAddonsObject(addons.activeGMPlugins, addonSection, "activeGMPlugins");
    this.renderPersona(addons, addonSection, "persona");

    let hasAddonData = Object.keys(ping.environment.addons).length > 0;
    let s = GenericSubsection.renderSubsectionHeader("addons", hasAddonData, "environment-data-section");
    s.appendChild(addonSection);
    dataDiv.appendChild(s);
  },

  appendRow(table, id, value) {
    let row = document.createElement("tr");
    row.id = id;
    this.appendColumn(row, "td", id);
    this.appendColumn(row, "td", value);
    table.appendChild(row);
  },
  /**
   * Helper function for appending a column to the data table.
   *
   * @param aRowElement Parent row element
   * @param aColType Column's tag name
   * @param aColText Column contents
   */
  appendColumn(aRowElement, aColType, aColText) {
    let colElement = document.createElement(aColType);
    let colTextElement = document.createTextNode(aColText);
    colElement.appendChild(colTextElement);
    aRowElement.appendChild(colElement);
  },
};

var TelLog = {
  /**
   * Renders the telemetry log
   */
  render(payload) {
    let entries = payload.log;
    const hasData = entries && entries.length > 0;
    setHasData("telemetry-log-section", hasData);
    if (!hasData) {
      return;
    }

    let table = document.createElement("table");

    let caption = document.createElement("caption");
    let captionString = bundle.GetStringFromName("telemetryLogTitle");
    caption.appendChild(document.createTextNode(captionString + "\n"));
    table.appendChild(caption);

    let headings = document.createElement("tr");
    this.appendColumn(headings, "th", bundle.GetStringFromName("telemetryLogHeadingId") + "\t");
    this.appendColumn(headings, "th", bundle.GetStringFromName("telemetryLogHeadingTimestamp") + "\t");
    this.appendColumn(headings, "th", bundle.GetStringFromName("telemetryLogHeadingData") + "\t");
    table.appendChild(headings);

    for (let entry of entries) {
        let row = document.createElement("tr");
        for (let elem of entry) {
            this.appendColumn(row, "td", elem + "\t");
        }
        table.appendChild(row);
    }

    let dataDiv = document.getElementById("telemetry-log");
    removeAllChildNodes(dataDiv);
    dataDiv.appendChild(table);
  },

  /**
   * Helper function for appending a column to the data table.
   *
   * @param aRowElement Parent row element
   * @param aColType Column's tag name
   * @param aColText Column contents
   */
  appendColumn(aRowElement, aColType, aColText) {
    let colElement = document.createElement(aColType);
    let colTextElement = document.createTextNode(aColText);
    colElement.appendChild(colTextElement);
    aRowElement.appendChild(colElement);
  },
};

var SlowSQL = {

  slowSqlHits: bundle.GetStringFromName("slowSqlHits"),

  slowSqlAverage: bundle.GetStringFromName("slowSqlAverage"),

  slowSqlStatement: bundle.GetStringFromName("slowSqlStatement"),

  mainThreadTitle: bundle.GetStringFromName("slowSqlMain"),

  otherThreadTitle: bundle.GetStringFromName("slowSqlOther"),

  /**
   * Render slow SQL statistics
   */
  render: function SlowSQL_render(aPing) {
    // We can add the debug SQL data to the current ping later.
    // However, we need to be careful to never send that debug data
    // out due to privacy concerns.
    // We want to show the actual ping data for archived pings,
    // so skip this there.
    let debugSlowSql = PingPicker.viewCurrentPingData && Preferences.get(PREF_DEBUG_SLOW_SQL, false);
    let slowSql = debugSlowSql ? Telemetry.debugSlowSQL : aPing.payload.slowSQL;
    if (!slowSql) {
      setHasData("slow-sql-section", false);
      return;
    }

    let {mainThread, otherThreads} =
      debugSlowSql ? Telemetry.debugSlowSQL : aPing.payload.slowSQL;

    let mainThreadCount = Object.keys(mainThread).length;
    let otherThreadCount = Object.keys(otherThreads).length;
    if (mainThreadCount == 0 && otherThreadCount == 0) {
      setHasData("slow-sql-section", false);
      return;
    }

    setHasData("slow-sql-section", true);
    if (debugSlowSql) {
      document.getElementById("sql-warning").hidden = false;
    }

    let slowSqlDiv = document.getElementById("slow-sql-tables");
    removeAllChildNodes(slowSqlDiv);

    // Main thread
    if (mainThreadCount > 0) {
      let table = document.createElement("table");
      this.renderTableHeader(table, this.mainThreadTitle);
      this.renderTable(table, mainThread);

      slowSqlDiv.appendChild(table);
      slowSqlDiv.appendChild(document.createElement("hr"));
    }

    // Other threads
    if (otherThreadCount > 0) {
      let table = document.createElement("table");
      this.renderTableHeader(table, this.otherThreadTitle);
      this.renderTable(table, otherThreads);

      slowSqlDiv.appendChild(table);
      slowSqlDiv.appendChild(document.createElement("hr"));
    }
  },

  /**
   * Creates a header row for a Slow SQL table
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param aTable Parent table element
   * @param aTitle Table's title
   */
  renderTableHeader: function SlowSQL_renderTableHeader(aTable, aTitle) {
    let caption = document.createElement("caption");
    caption.appendChild(document.createTextNode(aTitle + "\n"));
    aTable.appendChild(caption);

    let headings = document.createElement("tr");
    this.appendColumn(headings, "th", this.slowSqlHits + "\t");
    this.appendColumn(headings, "th", this.slowSqlAverage + "\t");
    this.appendColumn(headings, "th", this.slowSqlStatement + "\n");
    aTable.appendChild(headings);
  },

  /**
   * Fills out the table body
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param aTable Parent table element
   * @param aSql SQL stats object
   */
  renderTable: function SlowSQL_renderTable(aTable, aSql) {
    for (let [sql, [hitCount, totalTime]] of Object.entries(aSql)) {
      let averageTime = totalTime / hitCount;

      let sqlRow = document.createElement("tr");

      this.appendColumn(sqlRow, "td", hitCount + "\t");
      this.appendColumn(sqlRow, "td", averageTime.toFixed(0) + "\t");
      this.appendColumn(sqlRow, "td", sql + "\n");

      aTable.appendChild(sqlRow);
    }
  },

  /**
   * Helper function for appending a column to a Slow SQL table.
   *
   * @param aRowElement Parent row element
   * @param aColType Column's tag name
   * @param aColText Column contents
   */
  appendColumn: function SlowSQL_appendColumn(aRowElement, aColType, aColText) {
    let colElement = document.createElement(aColType);
    let colTextElement = document.createTextNode(aColText);
    colElement.appendChild(colTextElement);
    aRowElement.appendChild(colElement);
  }
};

var StackRenderer = {

  stackTitle: bundle.GetStringFromName("stackTitle"),

  memoryMapTitle: bundle.GetStringFromName("memoryMapTitle"),

  /**
   * Outputs the memory map associated with this hang report
   *
   * @param aDiv Output div
   */
  renderMemoryMap: function StackRenderer_renderMemoryMap(aDiv, memoryMap) {
    aDiv.appendChild(document.createTextNode(this.memoryMapTitle));
    aDiv.appendChild(document.createElement("br"));

    for (let currentModule of memoryMap) {
      aDiv.appendChild(document.createTextNode(currentModule.join(" ")));
      aDiv.appendChild(document.createElement("br"));
    }

    aDiv.appendChild(document.createElement("br"));
  },

  /**
   * Outputs the raw PCs from the hang's stack
   *
   * @param aDiv Output div
   * @param aStack Array of PCs from the hang stack
   */
  renderStack: function StackRenderer_renderStack(aDiv, aStack) {
    aDiv.appendChild(document.createTextNode(this.stackTitle));
    let stackText = " " + aStack.join(" ");
    aDiv.appendChild(document.createTextNode(stackText));

    aDiv.appendChild(document.createElement("br"));
    aDiv.appendChild(document.createElement("br"));
  },
  renderStacks: function StackRenderer_renderStacks(aPrefix, aStacks,
                                                    aMemoryMap, aRenderHeader) {
    let div = document.getElementById(aPrefix + "-data");
    removeAllChildNodes(div);

    let fetchE = document.getElementById(aPrefix + "-fetch-symbols");
    if (fetchE) {
      fetchE.hidden = false;
    }
    let hideE = document.getElementById(aPrefix + "-hide-symbols");
    if (hideE) {
      hideE.hidden = true;
    }

    if (aStacks.length == 0) {
      return;
    }

    setHasData(aPrefix + "-section", true);

    this.renderMemoryMap(div, aMemoryMap);

    for (let i = 0; i < aStacks.length; ++i) {
      let stack = aStacks[i];
      aRenderHeader(i);
      this.renderStack(div, stack)
    }
  },

  /**
   * Renders the title of the stack: e.g. "Late Write #1" or
   * "Hang Report #1 (6 seconds)".
   *
   * @param aFormatArgs formating args to be passed to formatStringFromName.
   */
  renderHeader: function StackRenderer_renderHeader(aPrefix, aFormatArgs) {
    let div = document.getElementById(aPrefix + "-data");

    let titleElement = document.createElement("span");
    titleElement.className = "stack-title";

    let titleText = bundle.formatStringFromName(
      aPrefix + "-title", aFormatArgs, aFormatArgs.length);
    titleElement.appendChild(document.createTextNode(titleText));

    div.appendChild(titleElement);
    div.appendChild(document.createElement("br"));
  }
};

var RawPayload = {
  /**
   * Renders the raw payload
   */
  render(aPing) {
    setHasData("raw-ping-data-section", true);
    let pre = document.getElementById("raw-ping-data");
    pre.textContent = JSON.stringify(aPing, null, 2);
  }
};

function SymbolicationRequest(aPrefix, aRenderHeader,
                              aMemoryMap, aStacks, aDurations = null) {
  this.prefix = aPrefix;
  this.renderHeader = aRenderHeader;
  this.memoryMap = aMemoryMap;
  this.stacks = aStacks;
  this.durations = aDurations;
}
/**
 * A callback for onreadystatechange. It replaces the numeric stack with
 * the symbolicated one returned by the symbolication server.
 */
SymbolicationRequest.prototype.handleSymbolResponse =
function SymbolicationRequest_handleSymbolResponse() {
  if (this.symbolRequest.readyState != 4)
    return;

  let fetchElement = document.getElementById(this.prefix + "-fetch-symbols");
  fetchElement.hidden = true;
  let hideElement = document.getElementById(this.prefix + "-hide-symbols");
  hideElement.hidden = false;
  let div = document.getElementById(this.prefix + "-data");
  removeAllChildNodes(div);
  let errorMessage = bundle.GetStringFromName("errorFetchingSymbols");

  if (this.symbolRequest.status != 200) {
    div.appendChild(document.createTextNode(errorMessage));
    return;
  }

  let jsonResponse = {};
  try {
    jsonResponse = JSON.parse(this.symbolRequest.responseText);
  } catch (e) {
    div.appendChild(document.createTextNode(errorMessage));
    return;
  }

  for (let i = 0; i < jsonResponse.length; ++i) {
    let stack = jsonResponse[i];
    this.renderHeader(i, this.durations);

    for (let symbol of stack) {
      div.appendChild(document.createTextNode(symbol));
      div.appendChild(document.createElement("br"));
    }
    div.appendChild(document.createElement("br"));
  }
};
/**
 * Send a request to the symbolication server to symbolicate this stack.
 */
SymbolicationRequest.prototype.fetchSymbols =
function SymbolicationRequest_fetchSymbols() {
  let symbolServerURI =
    Preferences.get(PREF_SYMBOL_SERVER_URI, DEFAULT_SYMBOL_SERVER_URI);
  let request = {"memoryMap": this.memoryMap, "stacks": this.stacks,
                 "version": 3};
  let requestJSON = JSON.stringify(request);

  this.symbolRequest = new XMLHttpRequest();
  this.symbolRequest.open("POST", symbolServerURI, true);
  this.symbolRequest.setRequestHeader("Content-type", "application/json");
  this.symbolRequest.setRequestHeader("Content-length",
                                      requestJSON.length);
  this.symbolRequest.setRequestHeader("Connection", "close");
  this.symbolRequest.onreadystatechange = this.handleSymbolResponse.bind(this);
  this.symbolRequest.send(requestJSON);
}

var ChromeHangs = {

  symbolRequest: null,

  /**
   * Renders raw chrome hang data
   */
  render: function ChromeHangs_render(payload) {
    let hangs = payload.chromeHangs;
    setHasData("chrome-hangs-section", !!hangs);
    if (!hangs) {
      return;
    }

    let stacks = hangs.stacks;
    let memoryMap = hangs.memoryMap;
    let durations = hangs.durations;

    StackRenderer.renderStacks("chrome-hangs", stacks, memoryMap,
                               (index) => this.renderHangHeader(index, durations));
  },

  renderHangHeader: function ChromeHangs_renderHangHeader(aIndex, aDurations) {
    StackRenderer.renderHeader("chrome-hangs", [aIndex + 1, aDurations[aIndex]]);
  }
};

var CapturedStacks = {
  symbolRequest: null,

  render: function CapturedStacks_render(payload) {
    // Retrieve captured stacks from telemetry payload.
    let capturedStacks = "processes" in payload && "parent" in payload.processes
      ? payload.processes.parent.capturedStacks
      : false;
    let hasData = capturedStacks && capturedStacks.stacks &&
                  capturedStacks.stacks.length > 0;
    setHasData("captured-stacks-section", hasData);
    if (!hasData) {
      return;
    }

    let stacks = capturedStacks.stacks;
    let memoryMap = capturedStacks.memoryMap;
    let captures = capturedStacks.captures;

    StackRenderer.renderStacks("captured-stacks", stacks, memoryMap,
                              (index) => this.renderCaptureHeader(index, captures));
  },

  renderCaptureHeader: function CaptureStacks_renderCaptureHeader(index, captures) {
    let key = captures[index][0];
    let cardinality = captures[index][2];
    StackRenderer.renderHeader("captured-stacks", [key, cardinality]);
  }
};

var ThreadHangStats = {

  /**
   * Renders raw thread hang stats data
   */
  render(aPayload) {
    let div = document.getElementById("thread-hang-stats");
    removeAllChildNodes(div);

    let stats = aPayload.threadHangStats;
    setHasData("thread-hang-stats-section", stats && (stats.length > 0));
    if (!stats) {
      return;
    }

    stats.forEach((thread) => {
      div.appendChild(this.renderThread(thread));
    });
  },

  /**
   * Creates and fills data corresponding to a thread
   */
  renderThread(aThread) {
    let div = document.createElement("div");

    let title = document.createElement("h2");
    title.textContent = aThread.name;
    div.appendChild(title);

    // Don't localize the histogram name, because the
    // name is also used as the div element's ID
    Histogram.render(div, aThread.name + "-Activity",
                     aThread.activity, {exponential: true}, true);
    aThread.hangs.forEach((hang, index) => {
      let hangName = aThread.name + "-Hang-" + (index + 1);
      let hangDiv = Histogram.render(
        div, hangName, hang.histogram, {exponential: true}, true);
      let stackDiv = document.createElement("div");
      hang.stack.forEach((frame) => {
        stackDiv.appendChild(document.createTextNode(frame));
        // Leave an extra <br> at the end of the stack listing
        stackDiv.appendChild(document.createElement("br"));
      });
      // Insert stack after the histogram title
      hangDiv.insertBefore(stackDiv, hangDiv.childNodes[1]);
    });
    return div;
  },
};

var Histogram = {

  hgramSamplesCaption: bundle.GetStringFromName("histogramSamples"),

  hgramAverageCaption: bundle.GetStringFromName("histogramAverage"),

  hgramSumCaption: bundle.GetStringFromName("histogramSum"),

  hgramCopyCaption: bundle.GetStringFromName("histogramCopy"),

  /**
   * Renders a single Telemetry histogram
   *
   * @param aParent Parent element
   * @param aName Histogram name
   * @param aHgram Histogram information
   * @param aOptions Object with render options
   *                 * exponential: bars follow logarithmic scale
   * @param aIsBHR whether or not requires fixing the labels for TimeHistogram
   */
  render: function Histogram_render(aParent, aName, aHgram, aOptions, aIsBHR) {
    let options = aOptions || {};
    let hgram = this.processHistogram(aHgram, aName, aIsBHR);

    let outerDiv = document.createElement("div");
    outerDiv.className = "histogram";
    outerDiv.id = aName;

    let divTitle = document.createElement("div");
    divTitle.className = "histogram-title";
    divTitle.appendChild(document.createTextNode(aName));
    outerDiv.appendChild(divTitle);

    let stats = hgram.sample_count + " " + this.hgramSamplesCaption + ", " +
                this.hgramAverageCaption + " = " + hgram.pretty_average + ", " +
                this.hgramSumCaption + " = " + hgram.sum;

    let divStats = document.createElement("div");
    divStats.appendChild(document.createTextNode(stats));
    outerDiv.appendChild(divStats);

    if (isRTL()) {
      hgram.buckets.reverse();
      hgram.values.reverse();
    }

    let textData = this.renderValues(outerDiv, hgram, options);

    // The 'Copy' button contains the textual data, copied to clipboard on click
    let copyButton = document.createElement("button");
    copyButton.className = "copy-node";
    copyButton.appendChild(document.createTextNode(this.hgramCopyCaption));
    copyButton.histogramText = aName + EOL + stats + EOL + EOL + textData;
    copyButton.addEventListener("click", function() {
      Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper)
                                                 .copyString(this.histogramText);
    });
    outerDiv.appendChild(copyButton);

    aParent.appendChild(outerDiv);
    return outerDiv;
  },

  processHistogram(aHgram, aName, aIsBHR) {
    const values = Object.keys(aHgram.values).map(k => aHgram.values[k]);
    if (!values.length) {
      // If we have no values collected for this histogram, just return
      // zero values so we still render it.
      return {
        values: [],
        pretty_average: 0,
        max: 0,
        sample_count: 0,
        sum: 0
      };
    }

    const sample_count = values.reduceRight((a, b) => a + b);
    const average = Math.round(aHgram.sum * 10 / sample_count) / 10;
    const max_value = Math.max(...values);

    function labelFunc(k) {
      // - BHR histograms are TimeHistograms: Exactly power-of-two buckets (from 0)
      //   (buckets: [0..1], [2..3], [4..7], [8..15], ... note the 0..1 anomaly - same bucket)
      // - TimeHistogram's JS representation adds a dummy (empty) "0" bucket, and
      //   the rest of the buckets have the label as the upper value of the
      //   bucket (non TimeHistograms have the lower value of the bucket as label).
      //   So JS TimeHistograms bucket labels are: 0 (dummy), 1, 3, 7, 15, ...
      // - see toolkit/components/telemetry/Telemetry.cpp
      //   (CreateJSTimeHistogram, CreateJSThreadHangStats, CreateJSHangHistogram)
      // - see toolkit/components/telemetry/ThreadHangStats.h
      // Fix BHR labels to the "standard" format for about:telemetry as follows:
      //   - The dummy 0 label+bucket will be filtered before arriving here
      //   - If it's 1 -> manually correct it to 0 (the 0..1 anomaly)
      //   - For the rest, set the label as the bottom value instead of the upper.
      //   --> so we'll end with the following (non dummy) labels: 0, 2, 4, 8, 16, ...
      if (!aIsBHR) {
        return k;
      }
      return k == 1 ? 0 : (k + 1) / 2;
    }

    const labelledValues = Object.keys(aHgram.values)
                           .filter(label => !aIsBHR || Number(label) != 0) // remove dummy 0 label for BHR
                           .map(k => [labelFunc(Number(k)), aHgram.values[k]]);

    let result = {
      values: labelledValues,
      pretty_average: average,
      max: max_value,
      sample_count,
      sum: aHgram.sum
    };

    return result;
  },

  /**
   * Return a non-negative, logarithmic representation of a non-negative number.
   * e.g. 0 => 0, 1 => 1, 10 => 2, 100 => 3
   *
   * @param aNumber Non-negative number
   */
  getLogValue(aNumber) {
    return Math.max(0, Math.log10(aNumber) + 1);
  },

  /**
   * Create histogram HTML bars, also returns a textual representation
   * Both aMaxValue and aSumValues must be positive.
   * Values are assumed to use 0 as baseline.
   *
   * @param aDiv Outer parent div
   * @param aHgram The histogram data
   * @param aOptions Object with render options (@see #render)
   */
  renderValues: function Histogram_renderValues(aDiv, aHgram, aOptions) {
    let text = "";
    // If the last label is not the longest string, alignment will break a little
    let labelPadTo = 0;
    if (aHgram.values.length) {
      labelPadTo = String(aHgram.values[aHgram.values.length - 1][0]).length;
    }
    let maxBarValue = aOptions.exponential ? this.getLogValue(aHgram.max) : aHgram.max;

    for (let [label, value] of aHgram.values) {
      label = String(label);
      let barValue = aOptions.exponential ? this.getLogValue(value) : value;

      // Create a text representation: <right-aligned-label> |<bar-of-#><value>  <percentage>
      text += EOL
              + " ".repeat(Math.max(0, labelPadTo - label.length)) + label // Right-aligned label
              + " |" + "#".repeat(Math.round(MAX_BAR_CHARS * barValue / maxBarValue)) // Bar
              + "  " + value // Value
              + "  " + Math.round(100 * value / aHgram.sample_count) + "%"; // Percentage

      // Construct the HTML labels + bars
      let belowEm = Math.round(MAX_BAR_HEIGHT * (barValue / maxBarValue) * 10) / 10;
      let aboveEm = MAX_BAR_HEIGHT - belowEm;

      let barDiv = document.createElement("div");
      barDiv.className = "bar";
      barDiv.style.paddingTop = aboveEm + "em";

      // Add value label or an nbsp if no value
      barDiv.appendChild(document.createTextNode(value ? value : "\u00A0"));

      // Create the blue bar
      let bar = document.createElement("div");
      bar.className = "bar-inner";
      bar.style.height = belowEm + "em";
      barDiv.appendChild(bar);

      // Add a special class to move the text down to prevent text overlap
      if (label.length > 3) {
          bar.classList.add("long-label");
      }
      // Add bucket label
      barDiv.appendChild(document.createTextNode(label));

      aDiv.appendChild(barDiv);
    }

    return text.substr(EOL.length); // Trim the EOL before the first line
  },
};


var Search = {

  // Pass if: all non-empty array items match (case-sensitive)
  isPassText(subject, filter) {
    for (let item of filter) {
      if (item.length && subject.indexOf(item) < 0) {
        return false; // mismatch and not a spurious space
      }
    }
    return true;
  },

  isPassRegex(subject, filter) {
    return filter.test(subject);
  },

  chooseFilter(filterText) {
    let filter = filterText.toString();
    // Setup normalized filter string (trimmed, lower cased and split on spaces if not RegEx)
    let isPassFunc; // filter function, set once, then applied to all elements
    filter = filter.trim();
    if (filter[0] != "/") { // Plain text: case insensitive, AND if multi-string
      isPassFunc = this.isPassText;
      filter = filter.toLowerCase().split(" ");
    } else {
      isPassFunc = this.isPassRegex;
      var r = filter.match(/^\/(.*)\/(i?)$/);
      try {
        filter = RegExp(r[1], r[2]);
      } catch (e) { // Incomplete or bad RegExp - always no match
        isPassFunc = function() {
          return false;
        };
      }
    }
    return [isPassFunc, filter]
  },

  filterElements(elements, filterText) {
    let [isPassFunc, filter] = this.chooseFilter(filterText);

    let needLowerCase = (isPassFunc === this.isPassText);
    for (let element of elements) {
      let subject = needLowerCase ? element.id.toLowerCase() : element.id;
      element.hidden = !isPassFunc(subject, filter);
    }
  },

  filterKeyedElements(keyedElements, filterText) {
    let [isPassFunc, filter] = this.chooseFilter(filterText);

    let needLowerCase = (isPassFunc === this.isPassText);
    keyedElements.forEach((keyedElement) => {
      let subject = needLowerCase ? keyedElement.key.id.toLowerCase() : keyedElement.key.id;
      if (!isPassFunc(subject, filter)) { // If the keyedHistogram's name is not matched
        let allElementHidden = true;
        for (let element of keyedElement.datas) {
          let subject = needLowerCase ? element.id.toLowerCase() : element.id;
          let match = isPassFunc(subject, filter);
          element.hidden = !match;
          if (match) {
            allElementHidden = false;
          }
        }
        keyedElement.key.hidden = allElementHidden;
      } else { // If the keyedHistogram's name is matched
        keyedElement.key.hidden = false;
        for (let element of keyedElement.datas) {
          element.hidden = false;
        }
      }
    });
  },

  searchHandler(e) {
    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
    }
    this.idleTimeout = setTimeout(() => Search.search(e.target.value), FILTER_IDLE_TIMEOUT);
  },

  search(text) {
    let selectedSection = document.querySelector("section.active");
    if (selectedSection.id === "histograms-section") {
      let histograms = selectedSection.getElementsByClassName("histogram");
      this.filterElements(histograms, text);
    } else if (selectedSection.id === "keyed-histograms-section") {
      let keyedElements = [];
      let keyedHistograms = selectedSection.getElementsByClassName("keyed-histogram");
      for (let key of keyedHistograms) {
        let datas = key.getElementsByClassName("histogram");
        keyedElements.push({key, datas});
      }
      this.filterKeyedElements(keyedElements, text);
    } else if (selectedSection.id === "keyed-scalars-section") {
      let keyedElements = [];
      let keyedScalars = selectedSection.getElementsByClassName("keyed-scalar");
      for (let key of keyedScalars) {
        let datas = key.querySelector("table").rows;
        keyedElements.push({key, datas});
      }
      this.filterKeyedElements(keyedElements, text);
    } else {
      let tables = selectedSection.querySelectorAll("table");
      for (let table of tables) {
        this.filterElements(table.rows, text);
      }
    }
  },
}

/*
 * Helper function to render JS objects with white space between top level elements
 * so that they look better in the browser
 * @param   aObject JavaScript object or array to render
 * @return  String
 */
function RenderObject(aObject) {
  let output = "";
  if (Array.isArray(aObject)) {
    if (aObject.length == 0) {
      return "[]";
    }
    output = "[" + JSON.stringify(aObject[0]);
    for (let i = 1; i < aObject.length; i++) {
      output += ", " + JSON.stringify(aObject[i]);
    }
    return output + "]";
  }
  let keys = Object.keys(aObject);
  if (keys.length == 0) {
    return "{}";
  }
  output = "{\"" + keys[0] + "\":\u00A0" + JSON.stringify(aObject[keys[0]]);
  for (let i = 1; i < keys.length; i++) {
    output += ", \"" + keys[i] + "\":\u00A0" + JSON.stringify(aObject[keys[i]]);
  }
  return output + "}";
}

var GenericSubsection = {

  addSubSectionToSidebar(id, title) {
    let category = document.querySelector("#categories > [value=" + id + "]");
    category.classList.add("has-subsection");
    let subCategory = document.createElement("div");
    subCategory.classList.add("category-subsection");
    subCategory.setAttribute("value", id + "-" + title);
    subCategory.addEventListener("click", (ev) => {
      let section = ev.target;
      showSubSection(section);
    });
    subCategory.appendChild(document.createTextNode(title))
    category.appendChild(subCategory);
  },

  render(data, dataDiv, sectionID) {
    for (let [title, sectionData] of data) {
      let hasData = sectionData.size > 0;
      let s = this.renderSubsectionHeader(title, hasData, sectionID);
      s.appendChild(this.renderSubsectionData(title, sectionData));
      dataDiv.appendChild(s);
    }
  },

  renderSubsectionHeader(title, hasData, sectionID) {
    this.addSubSectionToSidebar(sectionID, title);
    let section = document.createElement("div");
    section.setAttribute("id", sectionID + "-" + title);
    section.classList.add("sub-section");
    if (hasData) {
      section.classList.add("has-subdata");
    }
    return section;
  },

  renderSubsectionData(title, data) {
    // Create data container
    let dataDiv = document.createElement("div");
    dataDiv.setAttribute("class", "subsection-data subdata");
    // Instanciate the data
    let table = GenericTable.render(data);
    let caption = document.createElement("caption");
    caption.textContent = title;
    table.appendChild(caption);
    dataDiv.appendChild(table);

    return dataDiv;
  },

  deleteAllSubSections() {
    let subsections = document.querySelectorAll(".category-subsection");
    subsections.forEach((el) => {
      el.parentElement.removeChild(el);
    })
  },

}

var GenericTable = {

  defaultHeadings: [
    bundle.GetStringFromName("keysHeader"),
    bundle.GetStringFromName("valuesHeader")
  ],

  /**
   * Returns a n-column table.
   * @param rows An array of arrays, each containing data to render
   *             for one row.
   * @param headings The column header strings.
   */
  render(rows, headings = this.defaultHeadings) {
    let table = document.createElement("table");
    this.renderHeader(table, headings);
    this.renderBody(table, rows);
    return table;
  },

  /**
   * Create the table header.
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param table Table element
   * @param headings Array of column header strings.
   */
  renderHeader(table, headings) {
    let headerRow = document.createElement("tr");
    table.appendChild(headerRow);

    for (let i = 0; i < headings.length; ++i) {
      let suffix = (i == (headings.length - 1)) ? "\n" : "\t";
      let column = document.createElement("th");
      column.appendChild(document.createTextNode(headings[i] + suffix));
      headerRow.appendChild(column);
    }
  },

  /**
   * Create the table body
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param table Table element
   * @param rows An array of arrays, each containing data to render
   *             for one row.
   */
  renderBody(table, rows) {
    for (let row of rows) {
      row = row.map(value => {
        // use .valueOf() to unbox Number, String, etc. objects
        if (value &&
           (typeof value == "object") &&
           (typeof value.valueOf() == "object")) {
          return RenderObject(value);
        }
        return value;
      });

      let newRow = document.createElement("tr");
      newRow.id = row[0];
      table.appendChild(newRow);

      for (let i = 0; i < row.length; ++i) {
        let suffix = (i == (row.length - 1)) ? "\n" : "\t";
        let field = document.createElement("td");
        field.appendChild(document.createTextNode(row[i] + suffix));
        newRow.appendChild(field);
      }
    }
  },
};

var KeyedHistogram = {
  render(parent, id, keyedHistogram) {
    let outerDiv = document.createElement("div");
    outerDiv.className = "keyed-histogram";
    outerDiv.id = id;

    let divTitle = document.createElement("div");
    divTitle.className = "keyed-histogram-title";
    divTitle.appendChild(document.createTextNode(id));
    outerDiv.appendChild(divTitle);

    for (let [name, hgram] of Object.entries(keyedHistogram)) {
      Histogram.render(outerDiv, name, hgram);
    }

    parent.appendChild(outerDiv);
    return outerDiv;
  },
};

var AddonDetails = {
  tableIDTitle: bundle.GetStringFromName("addonTableID"),
  tableDetailsTitle: bundle.GetStringFromName("addonTableDetails"),

  /**
   * Render the addon details section as a series of headers followed by key/value tables
   * @param aPing A ping object to render the data from.
   */
  render: function AddonDetails_render(aPing) {
    let addonSection = document.getElementById("addon-details");
    removeAllChildNodes(addonSection);
    let addonDetails = aPing.payload.addonDetails;
    const hasData = addonDetails && Object.keys(addonDetails).length > 0;
    setHasData("addon-details-section", hasData);
    if (!hasData) {
      return;
    }

    for (let provider in addonDetails) {
      let providerSection = document.createElement("h2");
      let titleText = bundle.formatStringFromName("addonProvider", [provider], 1);
      providerSection.appendChild(document.createTextNode(titleText));
      addonSection.appendChild(providerSection);

      let headingStrings = [this.tableIDTitle, this.tableDetailsTitle ]
      let table = GenericTable.render(explodeObject(addonDetails[provider]),
                                      headingStrings);
      addonSection.appendChild(table);
    }
  },
};

var Scalars = {
  /**
   * Render the scalar data - if present - from the payload in a simple key-value table.
   * @param aPayload A payload object to render the data from.
   */
  render(aPayload) {
    let scalarsSection = document.getElementById("scalars");
    removeAllChildNodes(scalarsSection);

    let processesSelect = document.getElementById("processes");
    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");

    if (!aPayload.processes ||
        !selectedProcess ||
        !(selectedProcess in aPayload.processes)) {
      return;
    }

    let scalars = aPayload.processes[selectedProcess].scalars || {};
    let hasData = Array.from(processesSelect.options).some((option) => {
      let value = option.getAttribute("value");
      let sclrs = aPayload.processes[value].scalars;
      return sclrs && Object.keys(sclrs).length > 0;
    });
    setHasData("scalars-section", hasData);
    if (Object.keys(scalars).length > 0) {
      const headings = [
        "namesHeader",
        "valuesHeader",
      ].map(h => bundle.GetStringFromName(h));
      const table = GenericTable.render(explodeObject(scalars), headings);
      scalarsSection.appendChild(table);
    }
  },
};

var KeyedScalars = {
  /**
   * Render the keyed scalar data - if present - from the payload in a simple key-value table.
   * @param aPayload A payload object to render the data from.
   */
  render(aPayload) {
    let scalarsSection = document.getElementById("keyed-scalars");
    removeAllChildNodes(scalarsSection);

    let processesSelect = document.getElementById("processes");
    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");

    if (!aPayload.processes ||
        !selectedProcess ||
        !(selectedProcess in aPayload.processes)) {
      return;
    }

    let keyedScalars = aPayload.processes[selectedProcess].keyedScalars || {};
    let hasData = Array.from(processesSelect.options).some((option) => {
      let value = option.getAttribute("value");
      let keyedS = aPayload.processes[value].keyedScalars;
      return keyedS && Object.keys(keyedS).length > 0;
    });
    setHasData("keyed-scalars-section", hasData);
    if (!Object.keys(keyedScalars).length > 0) {
      return;
    }

    const headings = [
      "namesHeader",
      "valuesHeader",
    ].map(h => bundle.GetStringFromName(h));
    for (let scalar in keyedScalars) {
      // Add the name of the scalar.
      let container = document.createElement("div");
      container.classList.add("keyed-scalar");
      container.id = scalar;
      let scalarNameSection = document.createElement("h2");
      scalarNameSection.appendChild(document.createTextNode(scalar));
      container.appendChild(scalarNameSection);
      // Populate the section with the key-value pairs from the scalar.
      const table = GenericTable.render(explodeObject(keyedScalars[scalar]), headings);
      container.appendChild(table);
      scalarsSection.appendChild(container);
    }
  },
};

var Events = {
  /**
   * Render the event data - if present - from the payload in a simple table.
   * @param aPayload A payload object to render the data from.
   */
  render(aPayload) {
    let eventsSection = document.getElementById("events");
    removeAllChildNodes(eventsSection);

    let processesSelect = document.getElementById("processes");
    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");

    if (!aPayload.processes ||
        !selectedProcess ||
        !(selectedProcess in aPayload.processes)) {
      return;
    }

    let events = aPayload.processes[selectedProcess].events || {};
    let hasData = Array.from(processesSelect.options).some((option) => {
      let value = option.getAttribute("value");
      let evts = aPayload.processes[value].events;
      return evts && Object.keys(evts).length > 0;
    });
    setHasData("events-section", hasData);
    if (Object.keys(events).length > 0) {
      const headings = [
        "timestampHeader",
        "categoryHeader",
        "methodHeader",
        "objectHeader",
        "valuesHeader",
        "extraHeader",
      ].map(h => bundle.GetStringFromName(h));

      const table = GenericTable.render(events, headings);
      eventsSection.appendChild(table);
    }
  },
};

/**
 * Helper function for showing either the toggle element or "No data collected" message for a section
 *
 * @param aSectionID ID of the section element that needs to be changed
 * @param aHasData true (default) indicates that toggle should be displayed
 */
function setHasData(aSectionID, aHasData) {
  let sectionElement = document.getElementById(aSectionID);
  sectionElement.classList[aHasData ? "add" : "remove"]("has-data");

  // Display or Hide the section in the sidebar
  let sectionCategory = document.querySelector(".category[value=" + aSectionID + "]");
  sectionCategory.classList[aHasData ? "add" : "remove"]("has-data");
}

/**
 * Sets the text of the page header based on a config pref + bundle strings
 */
function setupPageHeader() {
  let serverOwner = Preferences.get(PREF_TELEMETRY_SERVER_OWNER, "Mozilla");
  let brandName = brandBundle.GetStringFromName("brandFullName");
  let subtitleText = bundle.formatStringFromName(
    "pageSubtitle", [serverOwner, brandName], 2);

  let subtitleElement = document.getElementById("page-subtitle");
  subtitleElement.appendChild(document.createTextNode(subtitleText));
}

function displayProcessesSelector(selectedSection) {
  let whitelist = [
    "scalars-section",
    "keyed-scalars-section",
    "histograms-section",
    "keyed-histograms-section",
    "events-section"
  ];
  let processes = document.getElementById("processes");
  processes.hidden = !whitelist.includes(selectedSection);
}

function adjustSearchState() {
  let selectedSection = document.querySelector("section.active").id;
  let blacklist = [
    "home-section",
    "raw-ping-data-section"
  ];
  // TODO: Implement global search for the Home section
  let search = document.getElementById("search");
  search.hidden = blacklist.includes(selectedSection);
  // Filter element on section change.
  if (!blacklist.includes(selectedSection)) {
    Search.search(search.value);
  }
}

/**
 * Change the url according to the current section displayed
 * e.g about:telemetry#general-data
 */
function changeUrlPath(selectedSection, subSection) {
  if (subSection) {
    let hash = window.location.hash.split("_")[0] + "_" + selectedSection;
    window.location.hash = hash;
  } else {
    window.location.hash = selectedSection.replace("-section", "-tab");
  }
}

/**
 * Change the section displayed
 */
function show(selected) {
  let current_button = document.querySelector(".category.selected");
  current_button.classList.remove("selected");
  selected.classList.add("selected");
  // Hack because subsection text appear selected. See Bug 1375114.
  document.getSelection().empty();

  let selectedValue = selected.getAttribute("value");
  let current_section = document.querySelector("section.active");
  let selected_section = document.getElementById(selectedValue);
  if (current_section == selected_section)
    return;
  current_section.classList.remove("active");
  selected_section.classList.add("active");

  let title = selected.querySelector(".category-name").textContent.trim();
  document.getElementById("sectionTitle").textContent = title;

  let search = document.getElementById("search");
  let placeholder = bundle.formatStringFromName("filterPlaceholder", [ title ], 1);
  search.setAttribute("placeholder", placeholder);
  displayProcessesSelector(selectedValue);
  adjustSearchState();
  changeUrlPath(selectedValue);
}

function showSubSection(selected) {
  let current_selection = document.querySelector(".category-subsection.selected");
  if (current_selection)
    current_selection.classList.remove("selected");
  selected.classList.add("selected");

  let section = document.getElementById(selected.getAttribute("value"));
  section.parentElement.childNodes.forEach((element) => {
    element.hidden = true;
  });
  section.hidden = false;

  let title = selected.parentElement.querySelector(".category-name").textContent;
  let subsection = selected.textContent;
  document.getElementById("sectionTitle").textContent = title + " - " + subsection;
  document.getSelection().empty(); // prevent subsection text selection
  changeUrlPath(subsection, true);
}

/**
 * Initializes load/unload, pref change and mouse-click listeners
 */
function setupListeners() {
  Settings.attachObservers();
  PingPicker.attachObservers();

  let menu = document.getElementById("categories");
  menu.addEventListener("click", (e) => {
    if (e.target && e.target.parentNode == menu) {
      show(e.target)
    }
  });

  let search = document.getElementById("search");
  search.addEventListener("input", Search.searchHandler);

  // Clean up observers when page is closed
  window.addEventListener("unload",
    function(aEvent) {
      Settings.detachObservers();
  }, {once: true});

  document.getElementById("chrome-hangs-fetch-symbols").addEventListener("click",
    function() {
      if (!gPingData) {
        return;
      }

      let hangs = gPingData.payload.chromeHangs;
      let req = new SymbolicationRequest("chrome-hangs",
                                         ChromeHangs.renderHangHeader,
                                         hangs.memoryMap,
                                         hangs.stacks,
                                         hangs.durations);
      req.fetchSymbols();
  });

  document.getElementById("chrome-hangs-hide-symbols").addEventListener("click",
    function() {
      if (!gPingData) {
        return;
      }

      ChromeHangs.render(gPingData);
  });

  document.getElementById("captured-stacks-fetch-symbols").addEventListener("click",
    function() {
      if (!gPingData) {
        return;
      }
      let capturedStacks = gPingData.payload.processes.parent.capturedStacks;
      let req = new SymbolicationRequest("captured-stacks",
                                         CapturedStacks.renderCaptureHeader,
                                         capturedStacks.memoryMap,
                                         capturedStacks.stacks,
                                         capturedStacks.captures);
      req.fetchSymbols();
  });

  document.getElementById("captured-stacks-hide-symbols").addEventListener("click",
    function() {
      if (gPingData) {
        CapturedStacks.render(gPingData.payload);
      }
  });

  document.getElementById("late-writes-fetch-symbols").addEventListener("click",
    function() {
      if (!gPingData) {
        return;
      }

      let lateWrites = gPingData.payload.lateWrites;
      let req = new SymbolicationRequest("late-writes",
                                         LateWritesSingleton.renderHeader,
                                         lateWrites.memoryMap,
                                         lateWrites.stacks);
      req.fetchSymbols();
  });

  document.getElementById("late-writes-hide-symbols").addEventListener("click",
    function() {
      if (!gPingData) {
        return;
      }

      LateWritesSingleton.renderLateWrites(gPingData.payload.lateWrites);
  });
}

// Restore sections states
function urlStateRestore() {
  if (window.location.hash) {
    let section = window.location.hash.slice(1).replace("-tab", "-section");
    let subsection = section.split("_")[1];
    section = section.split("_")[0];
    let category = document.querySelector(".category[value=" + section + "]");
    if (category) {
      show(category);
      if (subsection) {
        let selector = ".category-subsection[value=" + section + "-" + subsection + "]";
        let subcategory = document.querySelector(selector);
        showSubSection(subcategory);
      }
    }
  }
}

function onLoad() {
  window.removeEventListener("load", onLoad);

  // Set the text in the page header
  setupPageHeader();

  // Set up event listeners
  setupListeners();

  // Render settings.
  Settings.render();

  // Update ping data when async Telemetry init is finished.
  Telemetry.asyncFetchTelemetryData(async () => {
    await PingPicker.update();
    urlStateRestore();
  });
}

var LateWritesSingleton = {
  renderHeader: function LateWritesSingleton_renderHeader(aIndex) {
    StackRenderer.renderHeader("late-writes", [aIndex + 1]);
  },

  renderLateWrites: function LateWritesSingleton_renderLateWrites(lateWrites) {
    setHasData("late-writes-section", !!lateWrites);
    if (!lateWrites) {
      return;
    }

    let stacks = lateWrites.stacks;
    let memoryMap = lateWrites.memoryMap;
    StackRenderer.renderStacks("late-writes", stacks, memoryMap,
                               LateWritesSingleton.renderHeader);
  },
};

var HistogramSection = {
  render(aPayload) {
    let hgramDiv = document.getElementById("histograms");
    removeAllChildNodes(hgramDiv);

    let histograms = {};
    let hgramsSelect = document.getElementById("processes");
    let hgramsOption = hgramsSelect.selectedOptions.item(0);
    let hgramsProcess = hgramsOption.getAttribute("value");

    if (hgramsProcess === "parent") {
      histograms = aPayload.histograms;
    } else if ("processes" in aPayload && hgramsProcess in aPayload.processes &&
               "histograms" in aPayload.processes[hgramsProcess]) {
      histograms = aPayload.processes[hgramsProcess].histograms;
    }

    let hasData = Array.from(hgramsSelect.options).some((option) => {
      if (option == "parent") {
        return Object.keys(aPayload.histograms).length > 0;
      }
      let value = option.getAttribute("value");
      let histos = aPayload.processes[value].histograms;
      return histos && Object.keys(histos).length > 0;
    });
    setHasData("histograms-section", hasData);

    if (Object.keys(histograms).length > 0) {
      for (let [name, hgram] of Object.entries(histograms)) {
        Histogram.render(hgramDiv, name, hgram, {unpacked: true});
      }
    }
  },
}

var KeyedHistogramSection = {
  render(aPayload) {
    let keyedDiv = document.getElementById("keyed-histograms");
    removeAllChildNodes(keyedDiv);

    let keyedHistograms = {};
    let keyedHgramsSelect = document.getElementById("processes");
    let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
    let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
    if (keyedHgramsProcess === "parent") {
      keyedHistograms = aPayload.keyedHistograms;
    } else if ("processes" in aPayload && keyedHgramsProcess in aPayload.processes &&
               "keyedHistograms" in aPayload.processes[keyedHgramsProcess]) {
      keyedHistograms = aPayload.processes[keyedHgramsProcess].keyedHistograms;
    }

    let hasData = Array.from(keyedHgramsSelect.options).some((option) => {
      if (option == "parent") {
        return Object.keys(aPayload.keyedHistograms).length > 0;
      }
      let value = option.getAttribute("value");
      let keyedHistos = aPayload.processes[value].keyedHistograms;
      return keyedHistos && Object.keys(keyedHistos).length > 0;
    });
    setHasData("keyed-histograms-section", hasData);
    if (Object.keys(keyedHistograms).length > 0) {
      for (let [id, keyed] of Object.entries(keyedHistograms)) {
        if (Object.keys(keyed).length > 0) {
          KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
        }
      }
    }
  },
}

var SessionInformation = {
  render(aPayload) {
    let infoSection = document.getElementById("session-info");
    removeAllChildNodes(infoSection);

    let hasData = Object.keys(aPayload.info).length > 0;
    setHasData("session-info-section", hasData);

    if (hasData) {
      const table = GenericTable.render(explodeObject(aPayload.info));
      infoSection.appendChild(table);
    }
  },
}

var SimpleMeasurements = {
  render(aPayload) {
    let simpleSection = document.getElementById("simple-measurements");
    removeAllChildNodes(simpleSection);

    let simpleMeasurements = this.sortStartupMilestones(aPayload.simpleMeasurements);
    let hasData = Object.keys(simpleMeasurements).length > 0;
    setHasData("simple-measurements-section", hasData);

    if (hasData) {
      const table = GenericTable.render(explodeObject(simpleMeasurements));
      simpleSection.appendChild(table);
    }
  },

  /**
   * Helper function for sorting the startup milestones in the Simple Measurements
   * section into temporal order.
   *
   * @param aSimpleMeasurements Telemetry ping's "Simple Measurements" data
   * @return Sorted measurements
   */
  sortStartupMilestones(aSimpleMeasurements) {
    const telemetryTimestamps = TelemetryTimestamps.get();
    let startupEvents = Services.startup.getStartupInfo();
    delete startupEvents["process"];

    function keyIsMilestone(k) {
      return (k in startupEvents) || (k in telemetryTimestamps);
    }

    let sortedKeys = Object.keys(aSimpleMeasurements);

    // Sort the measurements, with startup milestones at the front + ordered by time
    sortedKeys.sort(function keyCompare(keyA, keyB) {
      let isKeyAMilestone = keyIsMilestone(keyA);
      let isKeyBMilestone = keyIsMilestone(keyB);

      // First order by startup vs non-startup measurement
      if (isKeyAMilestone && !isKeyBMilestone)
        return -1;
      if (!isKeyAMilestone && isKeyBMilestone)
        return 1;
      // Don't change order of non-startup measurements
      if (!isKeyAMilestone && !isKeyBMilestone)
        return 0;

      // If both keys are startup measurements, order them by value
      return aSimpleMeasurements[keyA] - aSimpleMeasurements[keyB];
    });

    // Insert measurements into a result object in sort-order
    let result = {};
    for (let key of sortedKeys) {
      result[key] = aSimpleMeasurements[key];
    }

    return result;
  },
}

function renderProcessList(ping, selectEl) {
  removeAllChildNodes(selectEl);
  let option = document.createElement("option");
  option.appendChild(document.createTextNode("parent"));
  option.setAttribute("value", "parent");
  option.selected = true;
  selectEl.appendChild(option);

  if (!("processes" in ping.payload)) {
    selectEl.disabled = true;
    return;
  }
  selectEl.disabled = false;

  for (let process of Object.keys(ping.payload.processes)) {
    // TODO: parent hgrams are on root payload, not in payload.processes.parent
    // When/If that gets moved, you'll need to remove this
    if (process === "parent") {
      continue;
    }
    option = document.createElement("option");
    option.appendChild(document.createTextNode(process));
    option.setAttribute("value", process);
    selectEl.appendChild(option);
  }
}

function renderPayloadList(ping) {
  // Rebuild the payload select with options:
  //   Parent Payload (selected)
  //   Child Payload 1..ping.payload.childPayloads.length
  let listEl = document.getElementById("choose-payload");
  removeAllChildNodes(listEl);

  let option = document.createElement("option");
  let text = bundle.GetStringFromName("parentPayload");
  let content = document.createTextNode(text);
  let payloadIndex = 0;
  option.appendChild(content);
  option.setAttribute("value", payloadIndex++);
  option.selected = true;
  listEl.appendChild(option);

  if (!ping.payload.childPayloads) {
    listEl.disabled = true;
    return
  }
  listEl.disabled = false;

  for (; payloadIndex <= ping.payload.childPayloads.length; ++payloadIndex) {
    option = document.createElement("option");
    text = bundle.formatStringFromName("childPayloadN", [payloadIndex], 1);
    content = document.createTextNode(text);
    option.appendChild(content);
    option.setAttribute("value", payloadIndex);
    listEl.appendChild(option);
  }
}

function togglePingSections(isMainPing) {
  // We always show the sections that are "common" to all pings.
  let commonSections = new Set(["heading",
                                "home",
                                "general-data-section",
                                "environment-data-section",
                                "raw-ping-data-section"]);

  let elements = document.querySelectorAll(".category");
  for (let section of elements) {
    if (commonSections.has(section.getAttribute("value"))) {
      continue;
    }
    section.classList.toggle("has-data", isMainPing);
  }
}

function displayPingData(ping, updatePayloadList = false) {
  gPingData = ping;
  // Render raw ping data.
  RawPayload.render(ping);

  try {
    PingPicker.render();
    displayRichPingData(ping, updatePayloadList);
  } catch (err) {
    console.log(err);
    PingPicker._showRawPingData();
  }
  adjustSearchState();
}

function displayRichPingData(ping, updatePayloadList) {
  // Update the payload list and process lists
  if (updatePayloadList) {
    renderPayloadList(ping);
    renderProcessList(ping, document.getElementById("processes"));
  }

  // Show general data.
  GeneralData.render(ping);

  // Show environment data.
  EnvironmentData.render(ping);

  // We only have special rendering code for the payloads from "main" pings.
  // For any other pings we just render the raw JSON payload.
  let isMainPing = (ping.type == "main" || ping.type == "saved-session");
  togglePingSections(isMainPing);

  if (!isMainPing) {
    return;
  }

  // Show slow SQL stats
  SlowSQL.render(ping);

  // Render Addon details.
  AddonDetails.render(ping);

  let payload = ping.payload;
  // Show basic session info gathered
  SessionInformation.render(payload);

  // Show scalar data.
  Scalars.render(payload);
  KeyedScalars.render(payload);

  // Show histogram data
  HistogramSection.render(payload);

  // Show keyed histogram data
  KeyedHistogramSection.render(payload);

  // Show event data.
  Events.render(payload);

  // Show captured stacks.
  CapturedStacks.render(payload);

  LateWritesSingleton.renderLateWrites(payload.lateWrites);

  // Select payload to render
  let payloadSelect = document.getElementById("choose-payload");
  let payloadOption = payloadSelect.selectedOptions.item(0);
  let payloadIndex = payloadOption.getAttribute("value");

  if (payloadIndex > 0) {
    payload = ping.payload.childPayloads[payloadIndex - 1];
  }

  // Show chrome hang stacks
  ChromeHangs.render(payload);

  // Show telemetry log.
  TelLog.render(payload);

  // Show thread hang stats
  ThreadHangStats.render(payload);

  // Show simple measurements
  SimpleMeasurements.render(payload);

}

window.addEventListener("load", onLoad);
PK
!<̨}##2chrome/toolkit/content/global/aboutTelemetry.xhtml<?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 % aboutTelemetryDTD SYSTEM "chrome://global/locale/aboutTelemetry.dtd"> %aboutTelemetryDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>&aboutTelemetry.pageTitle;</title>

    <link rel="stylesheet" href="chrome://global/content/aboutTelemetry.css"
          type="text/css"/>

    <script type="application/javascript"
            src="chrome://global/content/aboutTelemetry.js"/>
  </head>

  <body id="body" dir="&locale.dir;">

    <div id="categories">
      <div class="heading">
        <span id="ping-type" hidden="true" class="change-ping"></span>
        <div class="controls">
          <span id="older-ping" hidden="true">&lt;&lt;</span>
          <span id="ping-date" class="change-ping"></span>
          <span id="newer-ping" hidden="true">&gt;&gt;</span>
        </div>
      </div>
      <div id="category-home" class="category has-data selected" value="home-section">
        <span class="category-name">Home</span>
      </div>
      <div class="category" value="general-data-section">
        <span class="category-name">&aboutTelemetry.generalDataSection;</span>
      </div>
      <div class="category" value="environment-data-section">
        <span class="category-name">&aboutTelemetry.environmentDataSection;</span>
      </div>
      <div class="category" value="session-info-section">
        <span class="category-name">&aboutTelemetry.sessionInfoSection;</span>
      </div>
      <div class="category" value="scalars-section">
        <span class="category-name">&aboutTelemetry.scalarsSection;</span>
      </div>
      <div class="category" value="keyed-scalars-section">
        <span class="category-name">&aboutTelemetry.keyedScalarsSection;</span>
      </div>
      <div class="category" value="histograms-section">
        <span class="category-name">&aboutTelemetry.histogramsSection;</span>
      </div>
      <div class="category" value="keyed-histograms-section">
        <span class="category-name">&aboutTelemetry.keyedHistogramsSection;</span>
      </div>
      <div class="category" value="events-section">
        <span class="category-name">&aboutTelemetry.eventsSection;</span>
      </div>
      <div class="category" value="simple-measurements-section">
        <span class="category-name">&aboutTelemetry.simpleMeasurementsSection;</span>
      </div>
      <div class="category" value="telemetry-log-section">
        <span class="category-name">&aboutTelemetry.telemetryLogSection;</span>
      </div>
      <div class="category" value="slow-sql-section">
        <span class="category-name">&aboutTelemetry.slowSqlSection;</span>
      </div>
      <div class="category" value="chrome-hangs-section">
        <span class="category-name">&aboutTelemetry.chromeHangsSection;</span>
      </div>
      <div class="category" value="thread-hang-stats-section">
        <span class="category-name">&aboutTelemetry.threadHangStatsSection;</span>
      </div>
      <div class="category" value="late-writes-section">
        <span class="category-name">&aboutTelemetry.lateWritesSection;</span>
      </div>
      <div class="category" value="addon-details-section">
        <span class="category-name">&aboutTelemetry.addonDetailsSection;</span>
      </div>
      <div class="category" value="captured-stacks-section">
        <span class="category-name">&aboutTelemetry.capturedStacksSection;</span>
      </div>
      <div class="category" value="late-writes-section">
        <span class="category-name">&aboutTelemetry.lateWritesSection;</span>
      </div>
      <div id="category-raw" class="category has-data" value="raw-ping-data-section">
          <span class="category-name">&aboutTelemetry.raw;</span>
      </div>
    </div>

    <div class="main-content">

      <div id="ping-picker" class="hidden">
        <div id="ping-source-picker">
          <h4 class="title">&aboutTelemetry.pingDataSource;</h4>
          <input type="radio" id="ping-source-current" name="choose-ping-source" value="current" checked="checked" />
          &aboutTelemetry.showCurrentPingData;
          <input type="radio" id="ping-source-archive" name="choose-ping-source" value="archive" />
          &aboutTelemetry.showArchivedPingData;
        </div>
        <div id="current-ping-picker">
          <input id="show-subsession-data" type="checkbox" checked="checked" />&aboutTelemetry.showSubsessionData;
        </div>
        <div id="archived-ping-picker" class="hidden">
          <h4 class="title">&aboutTelemetry.choosePing;</h4>
          <div>
            <h4 class="title">&aboutTelemetry.archivePingType;</h4>
            <select id="choose-ping-type"></select>
          </div>
          <div>
            <h4 class="title">&aboutTelemetry.archivePingHeader;</h4>
            <select id="choose-ping-id">
              <optgroup label="&aboutTelemetry.optionGroupToday;">
              </optgroup>
              <optgroup label="&aboutTelemetry.optionGroupYesterday;">
              </optgroup>
              <optgroup label="&aboutTelemetry.optionGroupOlder;">
              </optgroup>
            </select>
          </div>
        </div>
        <div>
          <h4 class="title">&aboutTelemetry.payloadChoiceHeader;</h4>
          <select id="choose-payload"></select>
        </div>
      </div>

      <div class="header">
          <div id="sectionTitle" class="header-name">
              &aboutTelemetry.pageTitle;
          </div>
          <input type="text" id="search" placeholder="" hidden="true"/>
          <select id="processes" hidden="true"></select>
      </div>

      <section id="home-section" class="active">
        <h3 id="page-subtitle"></h3>
        <p id="home-explanation"></p>
        <p id="ping-explanation"></p>
      </section>

      <section id="raw-ping-data-section">
        <pre id="raw-ping-data"></pre>
      </section>

      <section id="general-data-section">
        <div id="general-data" class="data"></div>
      </section>

      <section id="environment-data-section">
        <div id="environment-data" class="data"></div>
      </section>

      <section id="session-info-section">
        <div id="session-info" class="data"></div>
      </section>

      <section id="scalars-section">
        <div id="scalars" class="data"></div>
      </section>

      <section id="keyed-scalars-section">
        <div id="keyed-scalars" class="data"></div>
      </section>

      <section id="histograms-section">
        <div id="histograms" class="data"></div>
      </section>

      <section id="keyed-histograms-section">
        <div id="keyed-histograms" class="data"></div>
      </section>

      <section id="events-section">
        <div id="events" class="data"></div>
      </section>

      <section id="simple-measurements-section">
        <div id="simple-measurements" class="data"></div>
      </section>

      <section id="telemetry-log-section">
        <div id="telemetry-log" class="data"></div>
      </section>

      <section id="slow-sql-section">
        <div id="slow-sql-tables" class="data">
          <p id="sql-warning" class="hidden">&aboutTelemetry.fullSqlWarning;</p>
        </div>
      </section>

      <section id="chrome-hangs-section">
        <div id="chrome-hangs" class="data">
          <a id="chrome-hangs-fetch-symbols" href="">&aboutTelemetry.fetchStackSymbols;</a>
          <a id="chrome-hangs-hide-symbols" class="hidden" href="">&aboutTelemetry.hideStackSymbols;</a>
          <br/>
          <br/>
          <div id="chrome-hangs-data">
          </div>
        </div>
      </section>

      <section id="thread-hang-stats-section">
        <div id="thread-hang-stats" class="data"></div>
      </section>

      <section id="late-writes-section">
        <div id="late-writes" class="data">
          <a id="late-writes-fetch-symbols" href="">&aboutTelemetry.fetchStackSymbols;</a>
          <a id="late-writes-hide-symbols" class="hidden" href="">&aboutTelemetry.hideStackSymbols;</a>
          <br/>
          <br/>
          <div id="late-writes-data">
          </div>
        </div>
      </section>

      <section id="addon-details-section">
        <div id="addon-details" class="data"></div>
      </section>

      <section id="captured-stacks-section">
        <div id="captured-stacks" class="data">
          <a id="captured-stacks-fetch-symbols" href="">&aboutTelemetry.fetchStackSymbols;</a>
          <a id="captured-stacks-hide-symbols" class="hidden" href="">&aboutTelemetry.hideStackSymbols;</a>
          <br/>
          <br/>
          <div id="captured-stacks-data">
          </div>
        </div>
      </section>
    </div>

  </body>

</html>
PK
!< 4chrome/toolkit/content/global/aboutUrlClassifier.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  --aboutUrlClassifier-table-background: #ebebeb;
  background-color: var(--in-content-page-background);
}

body {
  margin: 40px 48px;
}

.major-section {
  margin-top: 2em;
  margin-bottom: 1em;
  font-size: large;
  text-align: start;
  font-weight: bold;
}

table {
  background-color: var(--aboutUrlClassifier-table-background);
  color: var(--in-content-text-color);
  font: message-box;
  text-align: start;
  width: 100%;
  border: 1px solid var(--in-content-border-color);
  border-spacing: 0px;
}

th, td {
  border: 1px solid var(--in-content-border-color);
  padding: 4px;
}

thead th {
  text-align: center;
}

th {
  text-align: start;
  background-color: var(--in-content-table-header-background);
  color: var(--in-content-selected-text);
}

th.column {
  white-space: nowrap;
  width: 0px;
}

td {
  text-align: start;
  border-color: var(--in-content-table-border-dark-color);
}

#provider-table > tbody > tr >  td:last-child {
  text-align: center;
}

#cache-table > tbody > tr >  td:last-child {
  text-align: center;
}

#debug-table, #cache-table {
  margin-top: 20px;
}

.options > .toggle-container-with-text {
  display: inline-flex;
}

button {
  margin-inline-start: 0;
  margin-inline-end: 8px;
}
PK
!<E<<3chrome/toolkit/content/global/aboutUrlClassifier.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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://gre/modules/Services.jsm");

const bundle = Services.strings.createBundle(
  "chrome://global/locale/aboutUrlClassifier.properties");

const UPDATE_BEGIN = "safebrowsing-update-begin";
const UPDATE_FINISH = "safebrowsing-update-finished";
const JSLOG_PREF = "browser.safebrowsing.debug";

const STR_NA = bundle.GetStringFromName("NotAvailable");

function unLoad() {
  window.removeEventListener("unload", unLoad);

  Provider.uninit();
  Cache.uninit();
  Debug.uninit();
}

function onLoad() {
  window.removeEventListener("load", onLoad);
  window.addEventListener("unload", unLoad);

  Provider.init();
  Cache.init();
  Debug.init();
}

/*
 * Provider
 */
var Provider = {
  providers: null,

  updatingProvider: "",

  init() {
    this.providers = new Set();
    let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
    let children = branch.getChildList("", {});
    for (let child of children) {
      this.providers.add(child.split(".")[0]);
    }

    this.register();
    this.render();
    this.refresh();
  },

  uninit() {
    Services.obs.removeObserver(this.onBeginUpdate, UPDATE_BEGIN);
    Services.obs.removeObserver(this.onFinishUpdate, UPDATE_FINISH);
  },

  onBeginUpdate(aSubject, aTopic, aData) {
    this.updatingProvider = aData;
    let p = this.updatingProvider;

    // Disable update button for the provider while we are doing update.
    document.getElementById("update-" + p).disabled = true;

    let elem = document.getElementById(p + "-col-lastupdateresult");
    elem.childNodes[0].nodeValue = bundle.GetStringFromName("Updating");
  },

  onFinishUpdate(aSubject, aTopic, aData) {
    let p = this.updatingProvider;
    this.updatingProvider = "";

    // It is possible that we get update-finished event only because
    // about::url-classifier is opened after update-begin event is fired.
    if (p === "") {
      this.refresh();
      return;
    }

    this.refresh([p]);

    document.getElementById("update-" + p).disabled = false;

    let elem = document.getElementById(p + "-col-lastupdateresult");
    if (aData.startsWith("success")) {
      elem.childNodes[0].nodeValue = bundle.GetStringFromName("success");
    } else if (aData.startsWith("update error")) {
      elem.childNodes[0].nodeValue =
        bundle.formatStringFromName("updateError", [aData.split(": ")[1]], 1);
    } else if (aData.startsWith("download error")) {
      elem.childNodes[0].nodeValue =
        bundle.formatStringFromName("downloadError", [aData.split(": ")[1]], 1);
    } else {
      elem.childNodes[0].nodeValue = aData;
    }
  },

  register() {
    // Handle begin update
    this.onBeginUpdate = this.onBeginUpdate.bind(this);
    Services.obs.addObserver(this.onBeginUpdate, UPDATE_BEGIN);

    // Handle finish update
    this.onFinishUpdate = this.onFinishUpdate.bind(this);
    Services.obs.addObserver(this.onFinishUpdate, UPDATE_FINISH);
  },

  // This should only be called once because we assume number of providers
  // won't change.
  render() {
    let tbody = document.getElementById("provider-table-body");

    for (let provider of this.providers) {
      let tr = document.createElement("tr");
      let cols = document.getElementById("provider-head-row").childNodes;
      for (let column of cols) {
        if (!column.id) {
          continue;
        }
        let td = document.createElement("td");
        td.id = provider + "-" + column.id;

        if (column.id === "col-update") {
          let btn = document.createElement("button");
          btn.id = "update-" + provider;
          btn.addEventListener("click", () => { this.update(provider); });

          let str = bundle.GetStringFromName("TriggerUpdate")
          btn.appendChild(document.createTextNode(str));
          td.appendChild(btn);
        } else {
          let str = column.id === "col-lastupdateresult" ? STR_NA : "";
          td.appendChild(document.createTextNode(str));
        }
        tr.appendChild(td);
      }
      tbody.appendChild(tr);
    }
  },

  refresh(listProviders = this.providers) {
    for (let provider of listProviders) {
      let values = {};
      values["col-provider"] = provider;

      let pref = "browser.safebrowsing.provider." + provider + ".lastupdatetime";
      let lut = Services.prefs.getCharPref(pref, "");
      values["col-lastupdatetime"] = lut ? new Date(lut * 1) : STR_NA;

      pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
      let nut = Services.prefs.getCharPref(pref, "");
      values["col-nextupdatetime"] = nut ? new Date(nut * 1) : STR_NA;

      let listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]
                        .getService(Ci.nsIUrlListManager);
      let bot = listmanager.getBackOffTime(provider);
      values["col-backofftime"] = bot ? new Date(bot * 1) : STR_NA;

      for (let key of Object.keys(values)) {
        let elem = document.getElementById(provider + "-" + key);
        elem.childNodes[0].nodeValue = values[key];
      }
    }
  },

  // Call update for the provider.
  update(provider) {
    let listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"]
                      .getService(Ci.nsIUrlListManager);

    let pref = "browser.safebrowsing.provider." + provider + ".lists";
    let tables = Services.prefs.getCharPref(pref, "").split(",");
    let table = tables.find(t => listmanager.getUpdateUrl(t) != "");

    let updateUrl = listmanager.getUpdateUrl(table);
    if (!listmanager.checkForUpdates(updateUrl)) {
      // This may because of back-off algorithm.
      let elem = document.getElementById(provider + "-col-lastupdateresult");
      elem.childNodes[0].nodeValue = bundle.GetStringFromName("CannotUpdate");
    }
  },

};

/*
 * Cache
 */
var Cache = {
  // Tables that show cahe entries.
  showCacheEnties: null,

  init() {
    this.showCacheEnties = new Set();

    this.register()
    this.render();
  },

  uninit() {
    Services.obs.removeObserver(this.refresh, UPDATE_FINISH);
  },

  register() {
    this.refresh = this.refresh.bind(this);
    Services.obs.addObserver(this.refresh, UPDATE_FINISH);
  },

  render() {
    this.createCacheEntries();

    let refreshBtn = document.getElementById("refresh-cache-btn");
    refreshBtn.addEventListener("click", () => { this.refresh(); });

    let clearBtn = document.getElementById("clear-cache-btn");
    clearBtn.addEventListener("click", () => {
      let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
                      .getService(Ci.nsIUrlClassifierDBService);
      dbservice.clearCache();
      // Since clearCache is async call, we just simply assume it will be
      // updated in 100 milli-seconds.
      setTimeout(() => { this.refresh(); }, 100);
    });
  },

  refresh() {
    this.clearCacheEntries();
    this.createCacheEntries();
  },

  clearCacheEntries() {
    let ctbody = document.getElementById("cache-table-body");
    while (ctbody.firstChild) {
      ctbody.firstChild.remove();
    }

    let cetbody = document.getElementById("cache-entries-table-body");
    while (cetbody.firstChild) {
      cetbody.firstChild.remove();
    }
  },

  createCacheEntries() {
    function createRow(tds, body, cols) {
      let tr = document.createElement("tr");
      tds.forEach(function(v, i, a) {
        let td = document.createElement("td");
        if (i == 0 && tds.length != cols) {
          td.setAttribute("colspan", cols - tds.length + 1);
        }
        let elem = typeof v === "object" ? v : document.createTextNode(v);
        td.appendChild(elem);
        tr.appendChild(td);
      })
      body.appendChild(tr);
    }

    let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
                    .getService(Ci.nsIUrlClassifierInfo);

    for (let provider of Provider.providers) {
      let pref = "browser.safebrowsing.provider." + provider + ".lists";
      let tables = Services.prefs.getCharPref(pref, "").split(",");

      for (let table of tables) {
        let cache = dbservice.getCacheInfo(table);
        let entries = cache.entries;
        if (entries.length === 0) {
          this.showCacheEnties.delete(table);
          continue;
        }

        let positiveCacheCount = 0;
        for (let i = 0; i < entries.length ; i++) {
          let entry = entries.queryElementAt(i, Ci.nsIUrlClassifierCacheEntry);
          let matches = entry.matches;
          positiveCacheCount += matches.length;

          // If we don't have to show cache entries for this table then just
          // skip the following code.
          if (!this.showCacheEnties.has(table)) {
            continue;
          }

          let tds = [table, entry.prefix, new Date(entry.expiry * 1000).toString()];
          let j = 0;
          do {
            if (matches.length >= 1) {
              let match =
                matches.queryElementAt(j, Ci.nsIUrlClassifierPositiveCacheEntry);
              let list = [match.fullhash, new Date(match.expiry * 1000).toString()];
              tds = tds.concat(list);
            } else {
              tds = tds.concat([STR_NA, STR_NA])
            }
            createRow(tds, document.getElementById("cache-entries-table-body"), 5);
            j++;
            tds = [""];
          } while (j < matches.length)
        }

        // Create cache information entries.
        let chk = document.createElement("input");
        chk.type = "checkbox";
        chk.checked = this.showCacheEnties.has(table);
        chk.addEventListener("click", () => {
          if (chk.checked) {
            this.showCacheEnties.add(table);
          } else {
            this.showCacheEnties.delete(table);
          }
          this.refresh();
        });

        let tds = [table, entries.length, positiveCacheCount, chk];
        createRow(tds, document.getElementById("cache-table-body"), tds.length);
      }
    }

    let entries_div = document.getElementById("cache-entries");
    entries_div.style.display = this.showCacheEnties.size == 0 ? "none" : "block";
  },
};

/*
 * Debug
 */
var Debug = {
  // url-classifier NSPR Log modules.
  modules: ["UrlClassifierDbService",
            "nsChannelClassifier",
            "UrlClassifierProtocolParser",
            "UrlClassifierStreamUpdater",
            "UrlClassifierPrefixSet",
            "ApplicationReputation"],

  init() {
    this.register();
    this.render();
    this.refresh();
  },

  uninit() {
    Services.prefs.removeObserver(JSLOG_PREF, this.refreshJSDebug);
  },

  register() {
    this.refreshJSDebug = this.refreshJSDebug.bind(this);
    Services.prefs.addObserver(JSLOG_PREF, this.refreshJSDebug);
  },

  render() {
    // This function update the log module text field if we click
    // safebrowsing log module check box.
    function logModuleUpdate(module) {
      let txt = document.getElementById("log-modules");
      let chk = document.getElementById("chk-" + module);

      let dst = chk.checked ? "," + module + ":5" : "";
      let re = new RegExp(",?" + module + ":[0-9]");

      let str = txt.value.replace(re, dst);
      if (chk.checked) {
        str = txt.value === str ? str + dst : str;
      }
      txt.value = str.replace(/^,/, "");
    }

    let setLog = document.getElementById("set-log-modules");
    setLog.addEventListener("click", this.nsprlog);

    let setLogFile = document.getElementById("set-log-file");
    setLogFile.addEventListener("click", this.logfile);

    let setJSLog = document.getElementById("js-log");
    setJSLog.addEventListener("click", this.jslog);

    let modules = document.getElementById("log-modules");
    let sbModules = document.getElementById("sb-log-modules");
    for (let module of this.modules) {
      let container = document.createElement("div");
      container.className = "toggle-container-with-text";
      sbModules.appendChild(container);

      let chk = document.createElement("input");
      chk.id = "chk-" + module;
      chk.type = "checkbox";
      chk.checked = true;
      chk.addEventListener("click", () => { logModuleUpdate(module) });
      container.appendChild(chk, modules);

      let label = document.createElement("label");
      label.for = chk.id;
      label.appendChild(document.createTextNode(module));
      container.appendChild(label, modules);
    }

    this.modules.map(logModuleUpdate);

    let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
    file.append("safebrowsing.log");

    let logFile = document.getElementById("log-file");
    logFile.value = file.path;

    let curLog = document.getElementById("cur-log-modules");
    curLog.childNodes[0].nodeValue = "";

    let curLogFile = document.getElementById("cur-log-file");
    curLogFile.childNodes[0].nodeValue = "";
  },

  refresh() {
    this.refreshJSDebug();

    // Disable configure log modules if log modules are already set
    // by environment variable.
    let env = Cc["@mozilla.org/process/environment;1"]
              .getService(Ci.nsIEnvironment);

    let logModules = env.get("MOZ_LOG") ||
                     env.get("MOZ_LOG_MODULES") ||
                     env.get("NSPR_LOG_MODULES");

    if (logModules.length > 0) {
      document.getElementById("set-log-modules").disabled = true;
      for (let module of this.modules) {
        document.getElementById("chk-" + module).disabled = true;
      }

      let curLogModules = document.getElementById("cur-log-modules");
      curLogModules.childNodes[0].nodeValue = logModules;
    }

    // Disable set log file if log file is already set
    // by environment variable.
    let logFile = env.get("MOZ_LOG_FILE") || env.get("NSPR_LOG_FILE");
    if (logFile.length > 0) {
      document.getElementById("set-log-file").disabled = true;
      document.getElementById("log-file").value = logFile;
    }
  },

  refreshJSDebug() {
    let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);

    let jsChk = document.getElementById("js-log");
    jsChk.checked = enabled;

    let curJSLog = document.getElementById("cur-js-log");
    curJSLog.childNodes[0].nodeValue = enabled ?
      bundle.GetStringFromName("Enabled") :
      bundle.GetStringFromName("Disabled");
  },

  jslog() {
    let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
    Services.prefs.setBoolPref(JSLOG_PREF, !enabled);
  },

  nsprlog() {
    // Turn off debugging for all the modules.
    let children = Services.prefs.getBranch("logging.").getChildList("", {});
    for (let pref of children) {
      if (!pref.startsWith("config.")) {
        Services.prefs.clearUserPref(`logging.${pref}`);
      }
    }

    let value = document.getElementById("log-modules").value;
    let logModules = value.split(",");
    for (let module of logModules) {
      let [key, value] = module.split(":");
      Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
    }

    let curLogModules = document.getElementById("cur-log-modules");
    curLogModules.childNodes[0].nodeValue = value;
  },

  logfile() {
    let logFile = document.getElementById("log-file").value.trim();
    Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);

    let curLogFile = document.getElementById("cur-log-file");
    curLogFile.childNodes[0].nodeValue = logFile;
  }
};
PK
!<??6chrome/toolkit/content/global/aboutUrlClassifier.xhtml<?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 % urlClassifierDTD SYSTEM "chrome://global/locale/aboutUrlClassifier.dtd"> %urlClassifierDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
  <link rel="stylesheet" href="chrome://global/content/aboutUrlClassifier.css" type="text/css"/>
  <script type="text/javascript" src="chrome://global/content/aboutUrlClassifier.js"></script>
</head>

<body onload="onLoad()" class="aboutPageWideContainer">
  <h1>&aboutUrlClassifier.pageTitle;</h1>
  <div id="provider">
    <h2 class="major-section">&aboutUrlClassifier.providerTitle;</h2>
    <table id="provider-table">
      <thead>
        <tr id="provider-head-row">
          <th id="col-provider">&aboutUrlClassifier.provider;</th>
          <th id="col-lastupdatetime">&aboutUrlClassifier.providerLastUpdateTime;</th>
          <th id="col-nextupdatetime">&aboutUrlClassifier.providerNextUpdateTime;</th>
          <th id="col-backofftime">&aboutUrlClassifier.providerBackOffTime;</th>
          <th id="col-lastupdateresult">&aboutUrlClassifier.providerLastUpdateStatus;</th>
          <th id="col-update">&aboutUrlClassifier.providerUpdateBtn;</th>
        </tr>
      </thead>
      <tbody id="provider-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
  </div>
  <div id="cache">
    <h2 class="major-section">&aboutUrlClassifier.cacheTitle;</h2>
    <div id="cache-modules" class="options">
      <button id="refresh-cache-btn">&aboutUrlClassifier.cacheRefreshBtn;</button>
      <button id="clear-cache-btn">&aboutUrlClassifier.cacheClearBtn;</button>
      <br></br>
    </div>
    <table id="cache-table">
      <thead>
        <tr id="cache-head-row">
          <th id="col-tablename">&aboutUrlClassifier.cacheTableName;</th>
          <th id="col-negativeentries">&aboutUrlClassifier.cacheNCacheEntries;</th>
          <th id="col-positiveentries">&aboutUrlClassifier.cachePCacheEntries;</th>
          <th id="col-showentries">&aboutUrlClassifier.cacheShowEntries;</th>
        </tr>
      </thead>
      <tbody id="cache-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
    <br></br>
  </div>
  <div id="cache-entries">
    <h2 class="major-section">&aboutUrlClassifier.cacheEntries;</h2>
    <table id="cache-entries-table">
      <thead>
        <tr id="cache-entries-row">
          <th id="col-table">&aboutUrlClassifier.cacheTableName;</th>
          <th id="col-prefix">&aboutUrlClassifier.cachePrefix;</th>
          <th id="col-n-expire">&aboutUrlClassifier.cacheNCacheExpiry;</th>
          <th id="col-fullhash">&aboutUrlClassifier.cacheFullhash;</th>
          <th id="col-p-expire">&aboutUrlClassifier.cachePCacheExpiry;</th>
        </tr>
      </thead>
      <tbody id="cache-entries-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
  </div>
  <div id="debug">
    <h2 class="major-section">&aboutUrlClassifier.debugTitle;</h2>
    <div id="debug-modules" class="options">
      <input id="log-modules" type="text" value=""/>
      <button id="set-log-modules">&aboutUrlClassifier.debugModuleBtn;</button>
      <br></br>
      <input id="log-file" type="text" value=""/>
      <button id="set-log-file">&aboutUrlClassifier.debugFileBtn;</button>
      <br></br>
      <div class="toggle-container-with-text">
        <input id="js-log" type="checkbox"/>
        <label for="js-log">&aboutUrlClassifier.debugJSLogChk;</label>
      </div>
    </div>
    <table id="debug-table">
    <tbody>
    <tr>
      <th class="column">&aboutUrlClassifier.debugSBModules;</th>
      <td id="sb-log-modules">
      </td>
    </tr>
    <tr>
      <th class="column">&aboutUrlClassifier.debugModules;</th>
      <td id="cur-log-modules">
      </td>
    </tr>
    <tr>
      <th class="column">&aboutUrlClassifier.debugSBJSModules;</th>
      <td id="cur-js-log">
      </td>
    </tr>
    <tr>
      <th class="column">&aboutUrlClassifier.debugFile;</th>
      <td id="cur-log-file">
      </td>
    </tr>
    </tbody>
    </table>
  </div>
</body>
</html>
PK
!<K
9chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  background-color: #edeceb;
  font: message-box;
}

.controls {
  font-size: 1.1em;
  display: inline-block;
  margin: 0 0.5em;
}

.control {
  margin: 0.5em 0;
}

.control > button {
  margin: 0 0.25em;
}

.message > p {
  margin: 0.5em 0.5em;
}

.log p {
  font-family: monospace;
  padding-left: 2em;
  text-indent: -2em;
  margin-top: 2px;
  margin-bottom: 2px;
}

#content > div {
  padding: 1em 2em;
  margin: 1em 0;
  border: 1px solid #afaca9;
  border-radius: 10px;
  background: none repeat scroll 0% 0% #fff;
}

.section-heading *
{
  display: inline-block;
}

.section-heading > button {
  margin-left: 1em;
  margin-right: 1em;
}

.peer-connection > h3
{
  background-color: #ddd;
}

.peer-connection table {
  width: 100%;
  text-align: center;
  border: none;
}

.peer-connection table th,
.peer-connection table td {
  padding: 0.4em;
}

.peer-connection table tr:nth-child(even) {
  background-color: #ddd;
}

.info-label {
  font-weight: bold;
}

.section-ctrl {
  margin: 1em 1.5em;
}

div.fold-trigger {
  color: blue;
  cursor: pointer;
}

@media screen {
  .fold-closed {
    display: none !important;
  }
}

@media print {
  .no-print {
    display: none !important;
  }
}
PK
!<FS~`:chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.html<!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>about:webrtc</title>
  <link rel="stylesheet" type="text/css" media="all"
    href="chrome://global/content/aboutwebrtc/aboutWebrtc.css"/>
  <script type="text/javascript"
          src="chrome://global/content/aboutwebrtc/aboutWebrtc.js"
          defer="defer"></script>
</head>
<body id="body" onload="onLoad()">
  <div id="controls" class="no-print"></div>
  <div id="content"></div>
</body>
</html>
PK
!<J(\\8chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "FilePicker",
                                   "@mozilla.org/filepicker;1", "nsIFilePicker");
XPCOMUtils.defineLazyGetter(this, "strings", () => {
  return Services.strings.createBundle("chrome://global/locale/aboutWebrtc.properties");
});

const getString = strings.GetStringFromName;
const formatString = strings.formatStringFromName;

const LOGFILE_NAME_DEFAULT = "aboutWebrtc.html";
const WEBRTC_TRACE_ALL = 65535;

function getStats() {
  return new Promise(resolve =>
    WebrtcGlobalInformation.getAllStats(stats => resolve(stats)));
}

function getLog() {
  return new Promise(resolve =>
    WebrtcGlobalInformation.getLogging("", log => resolve(log)));
}

// Begin initial data queries as page loads. Store returned Promises for
// later use.
var reportsRetrieved = getStats();
var logRetrieved = getLog();

function onLoad() {
  document.title = getString("document_title");
  let controls = document.querySelector("#controls");
  if (controls) {
    let set = ControlSet.render();
    ControlSet.add(new SavePage());
    ControlSet.add(new DebugMode());
    ControlSet.add(new AecLogging());
    controls.appendChild(set);
  }

  let contentElem = document.querySelector("#content");
  if (!contentElem) {
    return;
  }

  let contentInit = function(data) {
    AboutWebRTC.init(onClearStats, onClearLog);
    AboutWebRTC.render(contentElem, data);
  };

  Promise.all([reportsRetrieved, logRetrieved])
    .then(([stats, log]) => contentInit({reports: stats.reports, log}))
    .catch(error => contentInit({error}));
}

function onClearLog() {
  WebrtcGlobalInformation.clearLogging();
  getLog()
    .then(log => AboutWebRTC.refresh({log}))
    .catch(error => AboutWebRTC.refresh({logError: error}));
}

function onClearStats() {
  WebrtcGlobalInformation.clearAllStats();
  getStats()
    .then(stats => AboutWebRTC.refresh({reports: stats.reports}))
    .catch(error => AboutWebRTC.refresh({reportError: error}));
}

var ControlSet = {
  render() {
    let controls = document.createElement("div");
    let control = document.createElement("div");
    let message = document.createElement("div");

    controls.className = "controls";
    control.className = "control";
    message.className = "message";
    controls.appendChild(control);
    controls.appendChild(message);

    this.controlSection = control;
    this.messageSection = message;
    return controls;
  },

  add(controlObj) {
    let [controlElem, messageElem] = controlObj.render();
    this.controlSection.appendChild(controlElem);
    this.messageSection.appendChild(messageElem);
  }
};

function Control() {
  this._label = null;
  this._message = null;
  this._messageHeader = null;
}

Control.prototype = {
  render() {
    let controlElem = document.createElement("button");
    let messageElem = document.createElement("p");

    this.ctrl = controlElem;
    controlElem.onclick = this.onClick.bind(this);
    this.msg = messageElem;
    this.update();

    return [controlElem, messageElem];
  },

  set label(val) {
    return this._labelVal = val || "\xA0";
  },

  get label() {
    return this._labelVal;
  },

  set message(val) {
    return this._messageVal = val;
  },

  get message() {
    return this._messageVal;
  },

  update() {
    this.ctrl.textContent = this._label;

    this.msg.textContent = "";
    if (this._message) {
      this.msg.appendChild(Object.assign(document.createElement("span"), {
        className: "info-label",
        textContent: `${this._messageHeader}: `,
      }));
      this.msg.appendChild(document.createTextNode(this._message));
    }
  },

  onClick(event) {
    return true;
  }
};

function SavePage() {
  Control.call(this);
  this._messageHeader = getString("save_page_label");
  this._label = getString("save_page_label");
}

SavePage.prototype = Object.create(Control.prototype);
SavePage.prototype.constructor = SavePage;

SavePage.prototype.onClick = function() {
  let content = document.querySelector("#content");

  if (!content)
    return;

  FoldEffect.expandAll();
  FilePicker.init(window, getString("save_page_dialog_title"), FilePicker.modeSave);
  FilePicker.defaultString = LOGFILE_NAME_DEFAULT;
  let rv = FilePicker.show();

  if (rv == FilePicker.returnOK || rv == FilePicker.returnReplace) {
    let fout = FileUtils.openAtomicFileOutputStream(
      FilePicker.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);

    let nodes = content.querySelectorAll(".no-print");
    let noPrintList = [];
    for (let node of nodes) {
      noPrintList.push(node);
      node.style.setProperty("display", "none");
    }

    fout.write(content.outerHTML, content.outerHTML.length);
    FileUtils.closeAtomicFileOutputStream(fout);

    for (let node of noPrintList) {
      node.style.removeProperty("display");
    }

    this._message = formatString("save_page_msg", [FilePicker.file.path], 1);
    this.update();
  }
};

function DebugMode() {
  Control.call(this);
  this._messageHeader = getString("debug_mode_msg_label");

  if (WebrtcGlobalInformation.debugLevel > 0) {
    this.onState();
  } else {
    this._label = getString("debug_mode_off_state_label");
    this._message = null;
  }
}

DebugMode.prototype = Object.create(Control.prototype);
DebugMode.prototype.constructor = DebugMode;

DebugMode.prototype.onState = function() {
  this._label = getString("debug_mode_on_state_label");
  try {
    let file = Services.prefs.getCharPref("media.webrtc.debug.log_file");
    this._message = formatString("debug_mode_on_state_msg", [file], 1);
  } catch (e) {
    this._message = null;
  }
};

DebugMode.prototype.offState = function() {
  this._label = getString("debug_mode_off_state_label");
  try {
    let file = Services.prefs.getCharPref("media.webrtc.debug.log_file");
    this._message = formatString("debug_mode_off_state_msg", [file], 1);
  } catch (e) {
    this._message = null;
  }
};

DebugMode.prototype.onClick = function() {
  if (WebrtcGlobalInformation.debugLevel > 0) {
    WebrtcGlobalInformation.debugLevel = 0;
    this.offState();
  } else {
    WebrtcGlobalInformation.debugLevel = WEBRTC_TRACE_ALL;
    this.onState();
  }

  this.update();
};

function AecLogging() {
  Control.call(this);
  this._messageHeader = getString("aec_logging_msg_label");

  if (WebrtcGlobalInformation.aecDebug) {
    this.onState();
  } else {
    this._label = getString("aec_logging_off_state_label");
    this._message = null;
  }
}

AecLogging.prototype = Object.create(Control.prototype);
AecLogging.prototype.constructor = AecLogging;

AecLogging.prototype.offState = function() {
  this._label = getString("aec_logging_off_state_label");
  try {
    let file = Services.prefs.getCharPref("media.webrtc.debug.aec_log_dir");
    this._message = formatString("aec_logging_off_state_msg", [file], 1);
  } catch (e) {
    this._message = null;
  }
};

AecLogging.prototype.onState = function() {
  this._label = getString("aec_logging_on_state_label");
  try {
    this._message = getString("aec_logging_on_state_msg");
  } catch (e) {
    this._message = null;
  }
};

AecLogging.prototype.onClick = function() {
  if (WebrtcGlobalInformation.aecDebug) {
    WebrtcGlobalInformation.aecDebug = false;
    this.offState();
  } else {
    WebrtcGlobalInformation.aecDebug = true;
    this.onState();
  }
  this.update();
};

var AboutWebRTC = {
  _reports: [],
  _log: [],

  init(onClearStats, onClearLog) {
    this._onClearStats = onClearStats;
    this._onClearLog = onClearLog;
  },

  render(parent, data) {
    this._content = parent;
    this._setData(data);

    if (data.error) {
      let msg = document.createElement("h3");
      msg.textContent = getString("cannot_retrieve_log");
      parent.appendChild(msg);
      msg = document.createElement("p");
      msg.textContent = `${data.error.name}: ${data.error.message}`;
      parent.appendChild(msg);
      return;
    }

    this._peerConnections = this.renderPeerConnections();
    this._connectionLog = this.renderConnectionLog();
    this._content.appendChild(this._peerConnections);
    this._content.appendChild(this._connectionLog);
  },

  _setData(data) {
    if (data.reports) {
      this._reports = data.reports;
    }

    if (data.log) {
      this._log = data.log;
    }
  },

  refresh(data) {
    this._setData(data);
    let pc = this._peerConnections;
    this._peerConnections = this.renderPeerConnections();
    let log = this._connectionLog;
    this._connectionLog = this.renderConnectionLog();
    this._content.replaceChild(this._peerConnections, pc);
    this._content.replaceChild(this._connectionLog, log);
  },

  renderPeerConnections() {
    let connections = document.createElement("div");
    connections.className = "stats";

    let heading = document.createElement("span");
    heading.className = "section-heading";
    let elem = document.createElement("h3");
    elem.textContent = getString("stats_heading");
    heading.appendChild(elem);

    elem = document.createElement("button");
    elem.textContent = getString("stats_clear");
    elem.className = "no-print";
    elem.onclick = this._onClearStats;
    heading.appendChild(elem);
    connections.appendChild(heading);

    if (!this._reports || !this._reports.length) {
      return connections;
    }

    let reports = [...this._reports];
    reports.sort((a, b) => b.timestamp - a.timestamp);
    for (let report of reports) {
      let peerConnection = new PeerConnection(report);
      connections.appendChild(peerConnection.render());
    }

    return connections;
  },

  renderConnectionLog() {
    let content = document.createElement("div");
    content.className = "log";

    let heading = document.createElement("span");
    heading.className = "section-heading";
    let elem = document.createElement("h3");
    elem.textContent = getString("log_heading");
    heading.appendChild(elem);
    elem = document.createElement("button");
    elem.textContent = getString("log_clear");
    elem.className = "no-print";
    elem.onclick = this._onClearLog;
    heading.appendChild(elem);
    content.appendChild(heading);

    if (!this._log || !this._log.length) {
      return content;
    }

    let div = document.createElement("div");
    let sectionCtrl = document.createElement("div");
    sectionCtrl.className = "section-ctrl no-print";
    let foldEffect = new FoldEffect(div, {
      showMsg: getString("log_show_msg"),
      hideMsg: getString("log_hide_msg")
    });
    sectionCtrl.appendChild(foldEffect.render());
    content.appendChild(sectionCtrl);

    for (let line of this._log) {
      elem = document.createElement("p");
      elem.textContent = line;
      div.appendChild(elem);
    }

    content.appendChild(div);
    return content;
  }
};

function PeerConnection(report) {
  this._report = report;
}

PeerConnection.prototype = {
  render() {
    let pc = document.createElement("div");
    pc.className = "peer-connection";
    pc.appendChild(this.renderHeading());

    let div = document.createElement("div");
    let sectionCtrl = document.createElement("div");
    sectionCtrl.className = "section-ctrl no-print";
    let foldEffect = new FoldEffect(div);
    sectionCtrl.appendChild(foldEffect.render());
    pc.appendChild(sectionCtrl);

    div.appendChild(this.renderDesc());
    div.appendChild(new ICEStats(this._report).render());
    div.appendChild(new SDPStats(this._report).render());
    div.appendChild(new RTPStats(this._report).render());

    pc.appendChild(div);
    return pc;
  },

  renderHeading() {
    let pcInfo = this.getPCInfo(this._report);
    let heading = document.createElement("h3");
    let now = new Date(this._report.timestamp).toTimeString();
    heading.textContent =
      `[ ${pcInfo.id} ] ${pcInfo.url} ${pcInfo.closed ? `(${getString("connection_closed")})` : ""} ${now}`;
    return heading;
  },

  renderDesc() {
    let info = document.createElement("div");
    let label = document.createElement("span");
    let body = document.createElement("span");

    label.className = "info-label";
    label.textContent = `${getString("peer_connection_id_label")}: `;
    info.appendChild(label);

    body.className = "info-body";
    body.textContent = this._report.pcid;
    info.appendChild(body);

    return info;
  },

  getPCInfo(report) {
    return {
      id: report.pcid.match(/id=(\S+)/)[1],
      url: report.pcid.match(/url=([^)]+)/)[1],
      closed: report.closed
    };
  }
};

function SDPStats(report) {
  this._report = report;
}

SDPStats.prototype = {
  render() {
    let div = document.createElement("div");
    let elem = document.createElement("h4");

    elem.textContent = getString("sdp_heading");
    div.appendChild(elem);

    elem = document.createElement("h5");
    elem.textContent = getString("local_sdp_heading");
    div.appendChild(elem);

    elem = document.createElement("pre");
    elem.textContent = this._report.localSdp;
    div.appendChild(elem);

    elem = document.createElement("h5");
    elem.textContent = getString("remote_sdp_heading");
    div.appendChild(elem);

    elem = document.createElement("pre");
    elem.textContent = this._report.remoteSdp;
    div.appendChild(elem);

    return div;
  }
};

function RTPStats(report) {
  this._report = report;
  this._stats = [];
}

RTPStats.prototype = {
  render() {
    let div = document.createElement("div");
    let heading = document.createElement("h4");

    heading.textContent = getString("rtp_stats_heading");
    div.appendChild(heading);

    this.generateRTPStats();

    for (let statSet of this._stats) {
      div.appendChild(this.renderRTPStatSet(statSet));
    }

    return div;
  },

  generateRTPStats() {
    let remoteRtpStats = {};
    let rtpStats = [].concat((this._report.inboundRTPStreamStats || []),
                             (this._report.outboundRTPStreamStats || []));

    // Generate an id-to-streamStat index for each streamStat that is marked
    // as a remote. This will be used next to link the remote to its local side.
    for (let stats of rtpStats) {
      if (stats.isRemote) {
        remoteRtpStats[stats.id] = stats;
      }
    }

    // If a streamStat has a remoteId attribute, create a remoteRtpStats
    // attribute that references the remote streamStat entry directly.
    // That is, the index generated above is merged into the returned list.
    for (let stats of rtpStats) {
      if (stats.remoteId) {
        stats.remoteRtpStats = remoteRtpStats[stats.remoteId];
      }
    }

    this._stats = rtpStats;
  },

  renderAvStats(stats) {
    let statsString = "";

    if (stats.mozAvSyncDelay) {
      statsString += `${getString("av_sync_label")}: ${stats.mozAvSyncDelay} ms `;
    }
    if (stats.mozJitterBufferDelay) {
      statsString += `${getString("jitter_buffer_delay_label")}: ${stats.mozJitterBufferDelay} ms`;
    }

    let line = document.createElement("p");
    line.textContent = statsString;
    return line;
  },

  renderCoderStats(stats) {
    let statsString = "";
    let label;

    if (stats.bitrateMean) {
      statsString += ` ${getString("avg_bitrate_label")}: ${(stats.bitrateMean / 1000000).toFixed(2)} Mbps`;
      if (stats.bitrateStdDev) {
        statsString += ` (${(stats.bitrateStdDev / 1000000).toFixed(2)} SD)`;
      }
    }

    if (stats.framerateMean) {
      statsString += ` ${getString("avg_framerate_label")}: ${(stats.framerateMean).toFixed(2)} fps`;
      if (stats.framerateStdDev) {
        statsString += ` (${stats.framerateStdDev.toFixed(2)} SD)`;
      }
    }

    if (stats.droppedFrames) {
      statsString += ` ${getString("dropped_frames_label")}: ${stats.droppedFrames}`;
    }
    if (stats.discardedPackets) {
      statsString += ` ${getString("discarded_packets_label")}: ${stats.discardedPackets}`;
    }

    if (statsString) {
      label = (stats.packetsReceived ? ` ${getString("decoder_label")}:` : ` ${getString("encoder_label")}:`);
      statsString = label + statsString;
    }

    let line = document.createElement("p");
    line.textContent = statsString;
    return line;
  },

  renderTransportStats(stats, typeLabel) {
    let time  = new Date(stats.timestamp).toTimeString();
    let statsString = `${typeLabel}: ${time} ${stats.type} SSRC: ${stats.ssrc}`;

    if (stats.packetsReceived) {
      statsString += ` ${getString("received_label")}: ${stats.packetsReceived} ${getString("packets")}`;

      if (stats.bytesReceived) {
        statsString += ` (${(stats.bytesReceived / 1024).toFixed(2)} Kb)`;
      }

      statsString += ` ${getString("lost_label")}: ${stats.packetsLost} ${getString("jitter_label")}: ${stats.jitter}`;

      if (stats.roundTripTime) {
        statsString += ` RTT: ${stats.roundTripTime} ms`;
      }
    } else if (stats.packetsSent) {
      statsString += ` ${getString("sent_label")}: ${stats.packetsSent} ${getString("packets")}`;
      if (stats.bytesSent) {
        statsString += ` (${(stats.bytesSent / 1024).toFixed(2)} Kb)`;
      }
    }

    let line = document.createElement("p");
    line.textContent = statsString;
    return line;
  },

  renderRTPStatSet(stats) {
    let div = document.createElement("div");
    let heading = document.createElement("h5");

    heading.textContent = stats.id;
    div.appendChild(heading);

    if (stats.MozAvSyncDelay || stats.mozJitterBufferDelay) {
      div.appendChild(this.renderAvStats(stats));
    }

    div.appendChild(this.renderCoderStats(stats));
    div.appendChild(this.renderTransportStats(stats, getString("typeLocal")));

    if (stats.remoteId && stats.remoteRtpStats) {
      div.appendChild(this.renderTransportStats(stats.remoteRtpStats, getString("typeRemote")));
    }

    return div;
  },
};

function ICEStats(report) {
  this._report = report;
}

ICEStats.prototype = {
  render() {
    let tbody = [];
    for (let stat of this.generateICEStats()) {
      tbody.push([
        stat.localcandidate || "",
        stat.remotecandidate || "",
        stat.state || "",
        stat.priority || "",
        stat.nominated || "",
        stat.selected || "",
        stat.bytesSent || "",
        stat.bytesReceived || ""
      ]);
    }

    let statsTable = new SimpleTable(
      [getString("local_candidate"), getString("remote_candidate"), getString("ice_state"),
       getString("priority"), getString("nominated"), getString("selected"),
       getString("ice_pair_bytes_sent"), getString("ice_pair_bytes_received")],
      tbody);

    let div = document.createElement("div");
    let heading = document.createElement("h4");

    heading.textContent = getString("ice_stats_heading");
    div.appendChild(heading);

    div.appendChild(statsTable.render());
    div.appendChild(this.renderIceMetric("ice_restart_count_label",
                                         this._report.iceRestarts));
    div.appendChild(this.renderIceMetric("ice_rollback_count_label",
                                         this._report.iceRollbacks));

    return div;
  },

  renderIceMetric(labelName, value) {
    let info = document.createElement("div");
    let label = document.createElement("span");
    let body = document.createElement("span");

    label.className = "info-label";
    label.textContent = `${getString(labelName)}: `;
    info.appendChild(label);

    body.className = "info-body";
    body.textContent = value;
    info.appendChild(body);
    return info;
  },

  generateICEStats() {
    // Create an index based on candidate ID for each element in the
    // iceCandidateStats array.
    let candidates = new Map();

    for (let candidate of this._report.iceCandidateStats) {
      candidates.set(candidate.id, candidate);
    }

    // A component may have a remote or local candidate address or both.
    // Combine those with both; these will be the peer candidates.
    let matched = {};
    let stats = [];
    let stat;

    for (let pair of this._report.iceCandidatePairStats) {
      let local = candidates.get(pair.localCandidateId);
      let remote = candidates.get(pair.remoteCandidateId);

      if (local) {
        stat = {
          localcandidate: this.candidateToString(local),
          state: pair.state,
          priority: pair.priority,
          nominated: pair.nominated,
          selected: pair.selected,
          bytesSent: pair.bytesSent,
          bytesReceived: pair.bytesReceived
        };
        matched[local.id] = true;

        if (remote) {
          stat.remotecandidate = this.candidateToString(remote);
          matched[remote.id] = true;
        }
        stats.push(stat);
      }
    }

    for (let c of candidates.values()) {
      if (matched[c.id])
        continue;

      stat = {};
      stat[c.type] = this.candidateToString(c);
      stats.push(stat);
    }

    return stats.sort((a, b) => (b.priority || 0) - (a.priority || 0));
  },

  candidateToString(c) {
    if (!c) {
      return "*";
    }

    var type = c.candidateType;

    if (c.type == "local-candidate" && c.candidateType == "relayed") {
      type = `${c.candidateType}-${c.mozLocalTransport}`;
    }

    return `${c.ipAddress}:${c.portNumber}/${c.transport}(${type})`;
  }
};

function SimpleTable(heading, data) {
  this._heading = heading || [];
  this._data = data;
}

SimpleTable.prototype = {
  renderRow(list) {
    let row = document.createElement("tr");

    for (let elem of list) {
      let cell = document.createElement("td");
      cell.textContent = elem;
      row.appendChild(cell);
    }

    return row;
  },

  render() {
    let table = document.createElement("table");

    if (this._heading) {
      table.appendChild(this.renderRow(this._heading));
    }

    for (let row of this._data) {
      table.appendChild(this.renderRow(row));
    }

    return table;
  }
};

function FoldEffect(targetElem, options = {}) {
  if (targetElem) {
    this._showMsg = "\u25BC " + (options.showMsg || getString("fold_show_msg"));
    this._showHint = options.showHint || getString("fold_show_hint");
    this._hideMsg = "\u25B2 " + (options.hideMsg || getString("fold_hide_msg"));
    this._hideHint = options.hideHint || getString("fold_hide_hint");
    this._target = targetElem;
  }
}

FoldEffect.prototype = {
  render() {
    this._target.classList.add("fold-target");

    let ctrl = document.createElement("div");
    this._trigger = ctrl;
    ctrl.className = "fold-trigger";
    ctrl.addEventListener("click", this.onClick.bind(this));
    this.close();

    FoldEffect._sections.push(this);
    return ctrl;
  },

  onClick() {
    if (this._target.classList.contains("fold-closed")) {
      this.open();
    } else {
      this.close();
    }
    return true;
  },

  open() {
    this._target.classList.remove("fold-closed");
    this._trigger.setAttribute("title", this._hideHint);
    this._trigger.textContent = this._hideMsg;
  },

  close() {
    this._target.classList.add("fold-closed");
    this._trigger.setAttribute("title", this._showHint);
    this._trigger.textContent = this._showMsg;
  }
};

FoldEffect._sections = [];

FoldEffect.expandAll = function() {
  for (let section of this._sections) {
    section.open();
  }
};

FoldEffect.collapseAll = function() {
  for (let section of this._sections) {
    section.close();
  }
};
PK
!<	J8chrome/toolkit/content/global/accessibility/AccessFu.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#virtual-cursor-box {
  position: fixed;
  border: 1px solid orange;
  pointer-events: none;
  display: none;
  border-radius: 2px;
  box-shadow: 1px 1px 1px #444;
  display: none;
  z-index: 10;
}

#virtual-cursor-box.show {
  display: block;
}

#virtual-cursor-box > div {
  border-radius: 1px;
  box-shadow: inset 1px 1px 1px #444;
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

#announce-box {
  position: fixed;
  width: 7.5em;
  height: 5em;
  top: calc(100% - 50% - 2.5em);
  left: calc(100% - 50% - 3.75em);
  pointer-events: none;
  display: table;
  font-size: 28pt;
  font-weight: 700;
  color: orange;
  background-color: black;
  border-radius: 0.25em;
}

#announce-box:not(.showing) {
  opacity: 0.0;
  -moz-transition: opacity 0.4s linear;
}

#announce-box.showing {
  opacity: 1.0;
  -moz-transition: opacity 0.2s linear;
}

#announce-box * {
  text-align: center;
  display: table-cell;
  vertical-align: middle;
}
PK
!<7chrome/toolkit/content/global/accessibility/clicked.oggOggS_<N^~vorbisqOggS_<Nnovorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)Software=Sony Sound Forge 8.0DATE=2013-10-05vorbis)BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d(	Ij9g'r9iN8 Q9	&cnkn)%
Y@H!RH!b!b!r!r
*
2 L2餓N:騣:(B-JL1Vc]|s9s9s	BCV BdB!R)r
2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d 	")Ifjihm˲,˲iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r,
Y@R,r4Gs4s<s<GtDɔLL
Y@1q$OR-r5Ws=sMu]WUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUАU!fj3a 4dC
Y К9堩T's9ls)ʙŠКsIКsyҚsasiҚXsYК樹sΉ'Ts9s9sΩ^9sΉڛk	]sd	s9s9s	BCV@aƝ }b!!t1)FJPR'tАU RH!RH!RH!b!r
**(2,2ˬ:C1J,5Vcs9HkZ+RJ) 4d@ dARH!r)

Y$Q%Q%2-S3=UTUWvmYu۷]u}׍_eYeYeYeYeYe	BCV B!RH!b1ǜNB	АU GqǑɑ$K$M,4O4EQ4MS]ueS6]5eUeveٶe[}Y}}}}}u 4d #9")"9Hd(8#I$Y&yg驢
h爎(ilʮ뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮@h*@@Gr$Gr$ER$Er$
Y1CR$Dz,M4O4=3=UtE
YK$QR-R5R-UT=UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU4M4А䤦zb9AhI\:霣\#FI!SIZjsTdHA-R!BCVMKIM4@=@EM4Q4QMT4@E3E@4UM4Q4QMQ5O4@E@4M@TMB!+8@ I4cY<xLX<@4x<	4σi y<Hσ4L	фj<ӄi4a@!+8H8dYHeeYey@!+(XpcY@,`YMx@4%(4d%p4Q8iq,KDei"4KDy	<ӄ(iQ4M6hJ,PhJ $8y(iq,DQMSU]X牢(.4EQ4MUu]h牢(.4MM4UUU]扦iE4MUu]h@M4Uu]h4MUU]וei,TUU]וe꺮+u]ٕeYຮ+˲, :ɨM"aJ1cB
aLBH!dRR*)
B*%RAHR2J-R!J RR)8Xcb9$BJ1s!s9c9眔1sNJɘs9'd9眓R:sJ)t9礔RBsRJ)s9@6lN0ThJ 8i'iIy'iji'i<DQMSUy牢(r]QM4MU%ˢ(
4MTUUii¶UUU]ua۪u]ue뺮,Op*auBCV1)R!BH)@!+p1c16a1c1q
c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1ZkV΅@Y3$	A1$JJB9(%ZB1Zl19Z)9礤Z1Z\!Z-l!Rk1Zc3JZ1k,JRkkE([k1Zk5)sKZc&1Zk"R2S֚0c1ZS-ZkRJ)#dZsNJ	e-Քs@=8@%A'Ua	BCVBJ1Ƙs9sRs9 B!1ƘsA!BHc9 BRʘsA!RJ)%9 B(RJJsA!RJ)R!BRJ))B!J)RRJ)BRJ)R
!J)RJI)B	RJ)RJ)J)RJ)%RJRJ)RJJ)J)RJ)RJ)RJ)RJ))RJRJ)RRJ)R)RJ)RJ)RJ)RJI)RJRJ)RJ)R*RJ)#*-N3<G2L@ZrIICF栤I!XKe AIJ)*B˘Rb+1t1G9TB2@8@H
E@@.!pL8'6A 1(*|H.\]BBX@	88'ST@ ""	!)19A6HRh8:<>@BDFHJLNP@OggSS_<N`YbF&B9=HLH\e.Ӓ8i-%U)x(vFQe)kXBrKVuk)?'E	Mqh)A^8GTW&xEu% ߑ@;Nj	\v""Ԍڤ#
R
 mG-δfœZd6%d{d.5"@LwTn~m,^Zn%^E~k7=
ns6gqTA6zVNopW=T%mdgPl{I|FO}=ߘiI_=XbfՊZdj6L,Y2(\@YQ⺔]ҿ~D2G%oc\nQB/ydY"a3eV'+=]Wbb"Sy_^E
nَLއǯM=2p11~0<'O}?=뉕Oigi@֗tPp<ijq
@%`
0Ȃw1@	O[z|8JY8pࣁ(!_ee@⃟<8T@I'Tvi)]o!-$
ƛ)aݒFPLfefDK<~vDw9g;LfA<UO%lEؚհ,=39k|i{^
DeQ
!\hEl`:@(
vʊ1b
(푄,Dq?DJ)	PhH,$"l"`Tcס[(ZCzRG.{B4iAݾx!94-$EDeҿmXC,m\݁
j$I ~]J$^*.;gB8 4E
:aSI=3_$<?ߡ{0*H)3()UE(DPB!$m]đm 2isP& R`VU-AXH
)	I8
6F[b#"rRJg9>d(׃_A>
M	׀CZ$94nn_~>sJϣH
P.\b"0Z  xpm7Tz^$<%ܸ2 af
jf1/3z(jRI*PK3eCu9;^ПSij#AsӳZUUdeE4bU+Š]OZEQժS
!"HjHkŀAxv-Y-"`[Xy2Fn&(m	l^&<S [@+D˂ZbӃl!M^#N8e<cw@pJ}eYF`$%+,`HؒW֢.ZcO>.W	?˨V-5Czt=I)wH#N,H7@r8~3n"^іc4!%KNw6kӻe޵Ym#Bˤ+q.EV!woGj,KEs+F85(g/ C`d;|YYD(ċ
%HebЬWkjϤZ,6NM V
XCuUx[VV(`E`eeŊVAUԨL\Jd,FVmZ⺅|n;/e!SSB/3A
aQ߰Q$OV(KEJNsXqhk4jE%N$ecgz?mLOOr
d9}=}ŁɒUC[V(}2B땒{`=9=rdO;z*y6oټ6fjcVۓgzI3]j{h,ċU7/|
774h01@f)7@O=L]7>*(Mv9lcD.֦$M
zeU 8	PK
!<PUU=chrome/toolkit/content/global/accessibility/content-script.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
  'resource://gre/modules/accessibility/Presentation.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
  'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'EventManager',
  'resource://gre/modules/accessibility/EventManager.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ContentControl',
  'resource://gre/modules/accessibility/ContentControl.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
  'resource://gre/modules/accessibility/Constants.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'States',
  'resource://gre/modules/accessibility/Constants.jsm');

Logger.info('content-script.js', content.document.location);

var eventManager = null;
var contentControl = null;

function forwardToParent(aMessage) {
  // XXX: This is a silly way to make a deep copy
  let newJSON = JSON.parse(JSON.stringify(aMessage.json));
  newJSON.origin = 'child';
  sendAsyncMessage(aMessage.name, newJSON);
}

function forwardToChild(aMessage, aListener, aVCPosition) {
  let acc = aVCPosition || Utils.getVirtualCursor(content.document).position;

  if (!Utils.isAliveAndVisible(acc) || acc.role != Roles.INTERNAL_FRAME) {
    return false;
  }

  Logger.debug(() => {
    return ['forwardToChild', Logger.accessibleToString(acc),
            aMessage.name, JSON.stringify(aMessage.json, null, '  ')];
  });

  let mm = Utils.getMessageManager(acc.DOMNode);

  if (aListener) {
    mm.addMessageListener(aMessage.name, aListener);
  }

  // XXX: This is a silly way to make a deep copy
  let newJSON = JSON.parse(JSON.stringify(aMessage.json));
  newJSON.origin = 'parent';
  if (Utils.isContentProcess) {
    // XXX: OOP content's screen offset is 0,
    // so we remove the real screen offset here.
    newJSON.x -= content.mozInnerScreenX;
    newJSON.y -= content.mozInnerScreenY;
  }
  mm.sendAsyncMessage(aMessage.name, newJSON);
  return true;
}

function activateContextMenu(aMessage) {
  let position = Utils.getVirtualCursor(content.document).position;
  if (!forwardToChild(aMessage, activateContextMenu, position)) {
    let center = Utils.getBounds(position, true).center();

    let evt = content.document.createEvent('HTMLEvents');
    evt.initEvent('contextmenu', true, true);
    evt.clientX = center.x;
    evt.clientY = center.y;
    position.DOMNode.dispatchEvent(evt);
  }
}

function presentCaretChange(aText, aOldOffset, aNewOffset) {
  if (aOldOffset !== aNewOffset) {
    let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
                                                aOldOffset, aOldOffset, true);
    sendAsyncMessage('AccessFu:Present', msg);
  }
}

function scroll(aMessage) {
  let position = Utils.getVirtualCursor(content.document).position;
  if (!forwardToChild(aMessage, scroll, position)) {
    sendAsyncMessage('AccessFu:DoScroll',
                     { bounds: Utils.getBounds(position, true),
                       page: aMessage.json.page,
                       horizontal: aMessage.json.horizontal });
  }
}

addMessageListener(
  'AccessFu:Start',
  function(m) {
    if (m.json.logLevel) {
      Logger.logLevel = Logger[m.json.logLevel];
    }

    Logger.debug('AccessFu:Start');
    if (m.json.buildApp)
      Utils.MozBuildApp = m.json.buildApp;

    addMessageListener('AccessFu:ContextMenu', activateContextMenu);
    addMessageListener('AccessFu:Scroll', scroll);

    if (!contentControl) {
      contentControl = new ContentControl(this);
    }
    contentControl.start();

    if (!eventManager) {
      eventManager = new EventManager(this, contentControl);
    }
    eventManager.inTest = m.json.inTest;
    eventManager.start();

    function contentStarted() {
      let accDoc = Utils.AccService.getAccessibleFor(content.document);
      if (accDoc && !Utils.getState(accDoc).contains(States.BUSY)) {
        sendAsyncMessage('AccessFu:ContentStarted');
      } else {
        content.setTimeout(contentStarted, 0);
      }
    }

    if (m.json.inTest) {
      // During a test we want to wait for the document to finish loading for
      // consistency.
      contentStarted();
    }
  });

addMessageListener(
  'AccessFu:Stop',
  function(m) {
    Logger.debug('AccessFu:Stop');

    removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
    removeMessageListener('AccessFu:Scroll', scroll);

    eventManager.stop();
    contentControl.stop();
  });

sendAsyncMessage('AccessFu:Ready');
PK
!<D1Bchrome/toolkit/content/global/accessibility/virtual_cursor_key.oggOggS#H^d_vorbiswOggS#H>aovorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)Software=Sony Sound Forge 8.0DATE=2013-10-05vorbis)BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d(	Ij9g'r9iN8 Q9	&cnkn)%
Y@H!RH!b!b!r!r
*
2 L2餓N:騣:(B-JL1Vc]|s9s9s	BCV BdB!R)r
2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d 	")Ifjihm˲,˲iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r,
Y@R,r4Gs4s<s<GtDɔLL
Y@1q$OR-r5Ws=sMu]WUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUАU!fj3a 4dC
Y К9堩T's9ls)ʙŠКsIКsyҚsasiҚXsYК樹sΉ'Ts9s9sΩ^9sΉڛk	]sd	s9s9s	BCV@aƝ }b!!t1)FJPR'tАU RH!RH!RH!b!r
**(2,2ˬ:C1J,5Vcs9HkZ+RJ) 4d@ dARH!r)

Y$Q%Q%2-S3=UTUWvmYu۷]u}׍_eYeYeYeYeYe	BCV B!RH!b1ǜNB	АU GqǑɑ$K$M,4O4EQ4MS]ueS6]5eUeveٶe[}Y}}}}}u 4d #9")"9Hd(8#I$Y&yg驢
h爎(ilʮ뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮@h*@@Gr$Gr$ER$Er$
Y1CR$Dz,M4O4=3=UtE
YK$QR-R5R-UT=UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU4M4А#ArBn=X1$939
"tA'=9R(DL%7 
¦\I8BCVQ11rIɠD1	9'I)-3)%c㜣IɤKcА@bR
)R)R1RJ9S91)S)A圃A(  B(4dE'p$ϓ4K%KEeMו445QTUUTUMU%MMMTUEUUӖMUm4eTUUնe~Wu3MYUMUuז}_m]445QTUMUTU6U׶5QtUQUeYTUYveYUW}KUSMUUUmU}tU]WeUY~[ׅ}UMuU}aeau(iij(m[誢ʲgʲl(,,,붨ʲ,p,뺭q0|)˦꺩nq̶m*°ʲ/u!QUuݔ]WeYm_w[m;q9omn+?a8gmn+ìBQU}]e7]Yn7[׍*˾ʲ1o0]6[ם}c	kq:u	Ǐ@!+8!S* tR T1!sNJPJj!* TIȜJh)RPJkRkb Ji-ZjZc2dI	Ji-sNJ砤BJKJ-VIɠAHSIPJkKJ1[n1Ji-[I)Sm-ƚ# dIɜJi-Z嘔BJJJR̜AHJI)JLJJRZl1֜Rl5ZI)ƒJl-Z[LuZJiVkjPJk%KJkn1Ji[I[-ƚSk5jn1[m=֚sJR-ƚcm՚{ Ji-bj-bJ*Zl1Z9bIŒR-ƚ[ljl1Rsl5Z-ƚSKZsV@	eА@ARIir9*	B9'rLB))UA%9))9%K*-Vk))k- M

Y	D  c"sNJcIsB*cA()PJ*)JI%
lДXА@`b1 tT2*LJ'ZuRkZj@2K%ZfĘZ+B(4d%@c9gb941*ƜBcA!9!BBBA!RJ B)tBR
*pQdsBCVy1J9F) RcJIrB))V9Z Zl5vJi-ZCJXk!b5ZkJ-Zk͹E6'	*4d% c1b1C)Řs)s9b9s1s9Ƙs9s9s9s9s9s9s	*pQdsBCVVb11c1Fb1cl1c1Ƙb1c1c1c1c1c1c1c1c1c1c1[kZkZkZk@
VG8),4d%Øs9)褄BCJ9(%PJ))sNJJZJsRR*%R ZJ-Z%Rj:ZkAH)Z-PJJb5RRjb1Rl-cZk1k-)bZkIb5ZnpHqBCV!B9sB!R1砃B!DJ1tB!1砃B!1tB!:BRJtB!PB BRJ)B(RJ)%B	RJ)BJ)BRJ)B!RJ)RBPJ)RJ!RJ)RJ	!PJ)RJ)!J)RJ)#$"l4PhJqj) ŜKrb.R9GeHՔ1SRkbQOcJ1ìVJ(rv 0!3@)1\2

DŽsiXb:X\`2]u  !nx
O	:E
 ""9$f$Dd%OggSs#H9621,M1ۆgסe6ZnFa(ĠPp)sj7 yM7TY[X,Xk:7լAquz%uTTR<BDdss))_"TWV}T_2s5Bb,9=d ">WV4wZkx"	6g4I29qx:ׄxBLr]7NPK
!<Cchrome/toolkit/content/global/accessibility/virtual_cursor_move.oggOggSzK!vorbisqOggSzKovorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)Software=Sony Sound Forge 8.0DATE=2013-10-05vorbis)BCV1L ŀАU`$)fI)(yHI)0c1c1c 4d(	Ij9g'r9iN8 Q9	&cnkn)%
Y@H!RH!b!b!r!r
*
2 L2餓N:騣:(B-JL1Vc]|s9s9s	BCV BdB!R)r
2ȀАU GI˱$O,Q53ESTMUUUUu]Wvevuv}Y[}Y[؅]aaaa}}} 4d #9)"9d 	")Ifjihm˲,˲iiiiiiifYeYeYeYeYeYeYeYeYeYeYeYeY@h*@@qq$ER$r,
Y@R,r4Gs4s<s<GtDɔLL
Y@1q$OR-r5Ws=sMu]WUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUАU!fj3a 4dC
Y К9堩T's9ls)ʙŠКsIКsyҚsasiҚXsYК樹sΉ'Ts9s9sΩ^9sΉڛk	]sd	s9s9s	BCV@aƝ }b!!t1)FJPR'tАU RH!RH!RH!b!r
**(2,2ˬ:C1J,5Vcs9HkZ+RJ) 4d@ dARH!r)

Y$Q%Q%2-S3=UTUWvmYu۷]u}׍_eYeYeYeYeYe	BCV B!RH!b1ǜNB	АU GqǑɑ$K$M,4O4EQ4MS]ueS6]5eUeveٶe[}Y}}}}}u 4d #9")"9Hd(8#I$Y&yg驢
h爎(ilʮ뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮뺮@h*@@Gr$Gr$ER$Er$
Y1CR$Dz,M4O4=3=UtE
YK$QR-R5R-UT=UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU4M4А䤦zb9AhI\:霣\#FI!SIZjsTdHA-R!BCVMKIM4@=@EM4Q4QMT4@E3E@4UM4Q4QMQ5O4@E@4M@TMB!+8@ I4cY<xLX<@4x<	4σi y<Hσ4L	фj<ӄi4a@!+8H8dYHeeYey@!+(XpcY@,`YMx@4%(4d%p4Q8iq,KDei"4KDy	<ӄ(iQ4M6hJ,PhJ $8y(iq,DQMSU]X牢(.4EQ4MUu]h牢(.4MM4UUU]扦iE4MUu]h@M4Uu]h4MUU]וei,TUU]וe꺮+u]ٕeYຮ+˲, :ɨM"aJ1cB
aLBH!dRR*)
B*%RAHR2J-R!J RR)8Xcb9$BJ1s!s9c9眔1sNJɘs9'd9眓R:sJ)t9礔RBsRJ)s9@6lN0ThJ 8i'iIy'iji'i<DQMSUy牢(r]QM4MU%ˢ(
4MTUUii¶UUU]ua۪u]ue뺮,Op*auBCV1)R!BH)@!+p1c16a1c1q
c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1ZkV΅@Y3$	A1$JJB9(%ZB1Zl19Z)9礤Z1Z\!Z-l!Rk1Zc3JZ1k,JRkkE([k1Zk5)sKZc&1Zk"R2S֚0c1ZS-ZkRJ)#dZsNJ	e-Քs@=8@%A'Ua	BCVBJ1Ƙs9sRs9 B!1ƘsA!BHc9 BRʘsA!RJ)%9 B(RJJsA!RJ)R!BRJ))B!J)RRJ)BRJ)R
!J)RJI)B	RJ)RJ)J)RJ)%RJRJ)RJJ)J)RJ)RJ)RJ)RJ))RJRJ)RRJ)R)RJ)RJ)RJ)RJI)RJRJ)RJ)R*RJ)#*-N3<G2L@ZrIICF栤I!XKe AIJ)*B˘Rb+1t1G9TB2@8@H
E@@.!pL8'6A 1(*|H.\]BBX@	88'ST@ ""	!)19A6HRh8:<>@BDFHJLNP@OggSzK{?
JNH[HHCD@DS]5˗jƍ<~*YD{6nX(TrZT"`gaOjZثP5JZFHEJ{jXtE_$u<tE_$u<ܢN
bjUbTTEVXP5h5U@*Uke
:TQ+h |Q_0Υ@5\
$Oܨ`0ObbVVTF  ֺozEmAES1D1RhlӕEcZ=$&{j,Zg˯޽k?˳=9xfwkFTě(KjC*i̒R('cwu~DŽ8fmY+y!iH3`ia!&fFV2Bcʨ ȴ(TnH ˓>
(@`m.0%/
mUСE"
K	aC!0TDjJ)XQEDcJaE 0h,.˲z
JZa5(XV KKEQkZ0@(Om33Kk=r)gouۨ\KQ Ur%]պ
Xi*J00>_\5h%u+tɍV(.8l\-$׋aܑ3j?⽔svVju.*'>$5LbyŮ ߑ]>NCz&xbDл^;i5]k]س[0q{~(y\9]/4]sg׆~'wL~vtUEꧏM0[Ǚ8&|9_Y'k^+bJ\,gYYa%Sph	MWB	BD`l5Mp5=pn;Zg*ylݭO J-ᓫfϢ5p,t5wvlW,aj8ywi$A@X*<vǓ鸎Qԃx|D5wL}:\jc1<+{nq6J,c`ϖz+'$y1^g~Se+;~4\n:Kf*KLA2p/)WXNRNnԯ?sԫEZY]?Ǔe>M>kPK
!<	.chrome/toolkit/content/global/alerts/alert.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#alertBox[animate] {
  animation-duration: 20s;
  animation-fill-mode: both;
  animation-name: alert-animation;
}

#alertBox[animate]:not([clicked]):not([closing]):hover {
  animation-play-state: paused;
}

#alertBox:not([hasOrigin]) > box > #alertTextBox > #alertFooter,
#alertBox:not([hasIcon]) > box > #alertIcon,
#alertImage:not([src]) {
  display: none;
}

#alertTitleBox {
  -moz-box-pack: center;
  -moz-box-align: center;
}

.alertText {
  white-space: pre-wrap;
}

@keyframes alert-animation {
  to {
    visibility: hidden;
  }
}
PK
!<	l0l0-chrome/toolkit/content/global/alerts/alert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");

// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
const NS_ALERT_HORIZONTAL = 1;
const NS_ALERT_LEFT = 2;
const NS_ALERT_TOP = 4;

const WINDOW_MARGIN = AppConstants.platform == "win" ? 0 : 10;
const BODY_TEXT_LIMIT = 200;
const WINDOW_SHADOW_SPREAD = AppConstants.platform == "win" ? 10 : 0;


var gOrigin = 0; // Default value: alert from bottom right.
var gReplacedWindow = null;
var gAlertListener = null;
var gAlertTextClickable = false;
var gAlertCookie = "";
var gIsReplaced = false;
var gRequireInteraction = false;

function prefillAlertInfo() {
  // unwrap all the args....
  // arguments[0] --> the image src url
  // arguments[1] --> the alert title
  // arguments[2] --> the alert text
  // arguments[3] --> is the text clickable?
  // arguments[4] --> the alert cookie to be passed back to the listener
  // arguments[5] --> the alert origin reported by the look and feel
  // arguments[6] --> bidi
  // arguments[7] --> lang
  // arguments[8] --> requires interaction
  // arguments[9] --> replaced alert window (nsIDOMWindow)
  // arguments[10] --> an optional callback listener (nsIObserver)
  // arguments[11] -> the nsIURI.hostPort of the origin, optional
  // arguments[12] -> the alert icon URL, optional

  switch (window.arguments.length) {
    default:
    case 13: {
      if (window.arguments[12]) {
        let alertBox = document.getElementById("alertBox");
        alertBox.setAttribute("hasIcon", true);

        let icon = document.getElementById("alertIcon");
        icon.src = window.arguments[12];
      }
    }
    case 12: {
      if (window.arguments[11]) {
        let alertBox = document.getElementById("alertBox");
        alertBox.setAttribute("hasOrigin", true);

        let hostPort = window.arguments[11];
        const ALERT_BUNDLE = Services.strings.createBundle(
          "chrome://alerts/locale/alert.properties");
        const BRAND_BUNDLE = Services.strings.createBundle(
          "chrome://branding/locale/brand.properties");
        const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
        let label = document.getElementById("alertSourceLabel");
        label.setAttribute("value",
          ALERT_BUNDLE.formatStringFromName("source.label",
                                            [hostPort],
                                            1));
        let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
        doNotDisturbMenuItem.setAttribute("label",
          ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
                                            [BRAND_NAME],
                                            1));
        let disableForOrigin = document.getElementById("disableForOriginMenuItem");
        disableForOrigin.setAttribute("label",
          ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
                                            [hostPort],
                                            1));
        let openSettings = document.getElementById("openSettingsMenuItem");
        openSettings.setAttribute("label",
          ALERT_BUNDLE.GetStringFromName("webActions.settings.label"));
      }
    }
    case 11:
      gAlertListener = window.arguments[10];
    case 10:
      gReplacedWindow = window.arguments[9];
    case 9:
      gRequireInteraction = window.arguments[8];
    case 8:
      if (window.arguments[7]) {
        document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
        document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
      }
    case 7:
      if (window.arguments[6]) {
        document.getElementById("alertNotification").style.direction = window.arguments[6];
      }
    case 6:
      gOrigin = window.arguments[5];
    case 5:
      gAlertCookie = window.arguments[4];
    case 4:
      gAlertTextClickable = window.arguments[3];
      if (gAlertTextClickable) {
        document.getElementById("alertNotification").setAttribute("clickable", true);
        document.getElementById("alertTextLabel").setAttribute("clickable", true);
      }
    case 3:
      if (window.arguments[2]) {
        document.getElementById("alertBox").setAttribute("hasBodyText", true);
        let bodyText = window.arguments[2];
        let bodyTextLabel = document.getElementById("alertTextLabel");

        if (bodyText.length > BODY_TEXT_LIMIT) {
          bodyTextLabel.setAttribute("tooltiptext", bodyText);

          let ellipsis = "\u2026";
          try {
            ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
                                                      Ci.nsIPrefLocalizedString).data;
          } catch (e) { }

          // Copied from nsContextMenu.js' formatSearchContextItem().
          // If the JS character after our truncation point is a trail surrogate,
          // include it in the truncated string to avoid splitting a surrogate pair.
          let truncLength = BODY_TEXT_LIMIT;
          let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0);
          if (truncChar >= 0xDC00 && truncChar <= 0xDFFF) {
            truncLength++;
          }

          bodyText = bodyText.substring(0, truncLength) +
                     ellipsis;
        }
        bodyTextLabel.textContent = bodyText;
      }
    case 2:
      document.getElementById("alertTitleLabel").setAttribute("value", window.arguments[1]);
    case 1:
      if (window.arguments[0]) {
        document.getElementById("alertBox").setAttribute("hasImage", true);
        document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
      }
    case 0:
      break;
  }
}

function onAlertLoad() {
  const ALERT_DURATION_IMMEDIATE = 20000;
  let alertTextBox = document.getElementById("alertTextBox");
  let alertImageBox = document.getElementById("alertImageBox");
  alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";

  sizeToContent();

  if (gReplacedWindow && !gReplacedWindow.closed) {
    moveWindowToReplace(gReplacedWindow);
    gReplacedWindow.gIsReplaced = true;
    gReplacedWindow.close();
  } else {
    moveWindowToEnd();
  }

  window.addEventListener("XULAlertClose", function() { window.close(); });

  // If the require interaction flag is set, prevent auto-closing the notification.
  if (!gRequireInteraction) {
    if (!Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
      setTimeout(function() { window.close(); }, ALERT_DURATION_IMMEDIATE);
    } else {
      let alertBox = document.getElementById("alertBox");
      alertBox.addEventListener("animationend", function hideAlert(event) {
        if (event.animationName == "alert-animation" ||
            event.animationName == "alert-clicked-animation" ||
            event.animationName == "alert-closing-animation") {
          alertBox.removeEventListener("animationend", hideAlert);
          window.close();
        }
      });
      alertBox.setAttribute("animate", true);
    }
  }

  let alertSettings = document.getElementById("alertSettings");
  alertSettings.addEventListener("focus", onAlertSettingsFocus);
  alertSettings.addEventListener("click", onAlertSettingsClick);

  let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
  document.documentElement.dispatchEvent(ev);

  if (gAlertListener) {
    gAlertListener.observe(null, "alertshow", gAlertCookie);
  }
}

function moveWindowToReplace(aReplacedAlert) {
  let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;

  // Move windows that come after the replaced alert if the height is different.
  if (heightDelta != 0) {
    let windows = Services.wm.getEnumerator("alert:alert");
    while (windows.hasMoreElements()) {
      let alertWindow = windows.getNext();
      // boolean to determine if the alert window is after the replaced alert.
      let alertIsAfter = gOrigin & NS_ALERT_TOP ?
                         alertWindow.screenY > aReplacedAlert.screenY :
                         aReplacedAlert.screenY > alertWindow.screenY;
      if (alertIsAfter) {
        // The new Y position of the window.
        let adjustedY = gOrigin & NS_ALERT_TOP ?
                        alertWindow.screenY + heightDelta :
                        alertWindow.screenY - heightDelta;
        alertWindow.moveTo(alertWindow.screenX, adjustedY);
      }
    }
  }

  let adjustedY = gOrigin & NS_ALERT_TOP ? aReplacedAlert.screenY :
                  aReplacedAlert.screenY - heightDelta;
  window.moveTo(aReplacedAlert.screenX, adjustedY);
}

function moveWindowToEnd() {
  // Determine position
  let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
          screen.availLeft + screen.availWidth - window.outerWidth;
  let y = gOrigin & NS_ALERT_TOP ? screen.availTop :
          screen.availTop + screen.availHeight - window.outerHeight;

  // Position the window at the end of all alerts.
  let windows = Services.wm.getEnumerator("alert:alert");
  while (windows.hasMoreElements()) {
    let alertWindow = windows.getNext();
    if (alertWindow != window) {
      if (gOrigin & NS_ALERT_TOP) {
        y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD);
      } else {
        y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD);
      }
    }
  }

  // Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
  y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN;
  x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN;

  window.moveTo(x, y);
}

function onAlertBeforeUnload() {
  if (!gIsReplaced) {
    // Move other alert windows to fill the gap left by closing alert.
    let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD;
    let windows = Services.wm.getEnumerator("alert:alert");
    while (windows.hasMoreElements()) {
      let alertWindow = windows.getNext();
      if (alertWindow != window) {
        if (gOrigin & NS_ALERT_TOP) {
          if (alertWindow.screenY > window.screenY) {
            alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta);
          }
        } else if (window.screenY > alertWindow.screenY) {
          alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY + heightDelta);
        }
      }
    }
  }

  if (gAlertListener) {
    gAlertListener.observe(null, "alertfinished", gAlertCookie);
  }
}

function onAlertClick() {
  if (gAlertListener && gAlertTextClickable) {
    gAlertListener.observe(null, "alertclickcallback", gAlertCookie);
  }

  let alertBox = document.getElementById("alertBox");
  if (alertBox.getAttribute("animate") == "true") {
    // Closed when the animation ends.
    alertBox.setAttribute("clicked", "true");
  } else {
    window.close();
  }
}

function doNotDisturb() {
  const alertService = Cc["@mozilla.org/alerts-service;1"]
                         .getService(Ci.nsIAlertsService)
                         .QueryInterface(Ci.nsIAlertsDoNotDisturb);
  alertService.manualDoNotDisturb = true;
  Services.telemetry.getHistogramById("WEB_NOTIFICATION_MENU")
                    .add(0);
  onAlertClose();
}

function disableForOrigin() {
  gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
  onAlertClose();
}

function onAlertSettingsFocus(event) {
  event.target.removeAttribute("focusedViaMouse");
}

function onAlertSettingsClick(event) {
  // XXXjaws Hack used to remove the focus-ring only
  // from mouse interaction, but focus-ring drawing
  // should only be enabled when interacting via keyboard.
  event.target.setAttribute("focusedViaMouse", true);
  event.stopPropagation();
}

function openSettings() {
  gAlertListener.observe(null, "alertsettingscallback", gAlertCookie);
  onAlertClose();
}

function onAlertClose() {
  let alertBox = document.getElementById("alertBox");
  if (alertBox.getAttribute("animate") == "true") {
    // Closed when the animation ends.
    alertBox.setAttribute("closing", "true");
  } else {
    window.close();
  }
}
PK
!<9n

.chrome/toolkit/content/global/alerts/alert.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window [
<!ENTITY % alertDTD SYSTEM "chrome://alerts/locale/alert.dtd">
%alertDTD;
]>

<?xml-stylesheet href="chrome://global/content/alerts/alert.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/alerts/alert.css" type="text/css"?>

<window id="alertNotification"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        windowtype="alert:alert"
        xmlns:xhtml="http://www.w3.org/1999/xhtml"
        role="alert"
        pack="start"
        onload="onAlertLoad();"
        onclick="onAlertClick();"
        onbeforeunload="onAlertBeforeUnload();">

  <script type="application/javascript" src="chrome://global/content/alerts/alert.js"/>

  <vbox id="alertBox" class="alertBox">
    <box id="alertTitleBox">
      <image id="alertIcon"/>
      <label id="alertTitleLabel" class="alertTitle plain" crop="end"/>
      <vbox class="alertCloseBox">
        <toolbarbutton class="alertCloseButton close-icon"
                       tooltiptext="&closeAlert.tooltip;"
                       onclick="event.stopPropagation();"
                       oncommand="onAlertClose();"/>
      </vbox>
    </box>
    <box>
      <hbox id="alertImageBox" class="alertImageBox" align="center" pack="center">
        <image id="alertImage"/>
      </hbox>

      <vbox id="alertTextBox" class="alertTextBox">
        <label id="alertTextLabel" class="alertText plain"/>
        <spacer flex="1"/>
        <box id="alertFooter">
          <label id="alertSourceLabel" class="alertSource plain"/>
          <button type="menu" id="alertSettings" tooltiptext="&settings.label;">
            <menupopup position="after_end">
              <menuitem id="doNotDisturbMenuItem"
                        oncommand="doNotDisturb();"/>
              <menuseparator/>
              <menuitem id="disableForOriginMenuItem"
                        oncommand="disableForOrigin();"/>
              <menuitem id="openSettingsMenuItem"
                        oncommand="openSettings();"/>
            </menupopup>
          </button>
        </box>
      </vbox>
    </box>
  </vbox>

  <!-- This method is called inline because we want to make sure we establish the width
       and height of the alert before we fire the onload handler. -->
  <script type="application/javascript">prefillAlertInfo();</script>
</window>

PK
!<vC*chrome/toolkit/content/global/appPicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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");

function AppPicker() {}

AppPicker.prototype =
{
    // Class members
    _incomingParams: null,

    /**
    * Init the dialog and populate the application list
    */
    appPickerLoad: function appPickerLoad() {
        const nsILocalHandlerApp = Components.interfaces.nsILocalHandlerApp;

        this._incomingParams = window.arguments[0];
        this._incomingParams.handlerApp = null;

        document.title = this._incomingParams.title;

        // Header creation - at the very least, we must have
        // a mime type:
        //
        // (icon) Zip File
        // (icon) filename
        //
        // (icon) Web Feed
        // (icon) mime/type
        //
        // (icon) mime/type
        // (icon)

        var mimeInfo = this._incomingParams.mimeInfo;
        var filename = this._incomingParams.filename;
        if (!filename) {
          filename = mimeInfo.MIMEType;
        }
        var description = this._incomingParams.description;
        if (!description) {
          description = filename;
          filename = "";
        }

        // Setup the dialog header information
        document.getElementById("content-description").setAttribute("value",
          description);
        document.getElementById("suggested-filename").setAttribute("value",
          filename);
        document.getElementById("content-icon").setAttribute("src",
          "moz-icon://" + filename + "?size=32&contentType=" +
          mimeInfo.MIMEType);

        // Grab a list of nsILocalHandlerApp application helpers to list
        var fileList = mimeInfo.possibleLocalHandlers;

        var list = document.getElementById("app-picker-listbox");

        var primaryCount = 0;

        if (!fileList || fileList.length == 0) {
          // display a message saying nothing is configured
          document.getElementById("app-picker-notfound").removeAttribute("hidden");
          return;
        }

        for (var idx = 0; idx < fileList.length; idx++) {
          var file = fileList.queryElementAt(idx, nsILocalHandlerApp);
          try {
              if (!file.executable || !file.executable.isFile())
                continue;
          } catch (err) {
            continue;
          }

          var item = document.createElement("listitem");
          item.className = "listitem-iconic";
          item.handlerApp = file;
          item.setAttribute("label", this.getFileDisplayName(file.executable));
          item.setAttribute("image", this.getFileIconURL(file.executable));
          list.appendChild(item);

          primaryCount++;
        }

        if ( primaryCount == 0 ) {
          // display a message saying nothing is configured
          document.getElementById("app-picker-notfound").removeAttribute("hidden");
        }
    },

    /**
    * Retrieve the moz-icon for the app
    */
    getFileIconURL: function getFileIconURL(file) {
      var ios = Components.classes["@mozilla.org/network/io-service;1"].
                getService(Components.interfaces.nsIIOService);

      if (!ios) return "";
      const nsIFileProtocolHandler =
        Components.interfaces.nsIFileProtocolHandler;

      var fph = ios.getProtocolHandler("file")
                .QueryInterface(nsIFileProtocolHandler);
      if (!fph) return "";

      var urlSpec = fph.getURLSpecFromFile(file);
      return "moz-icon://" + urlSpec + "?size=32";
    },

    /**
    * Retrieve the pretty description from the file
    */
    getFileDisplayName: function getFileDisplayName(file) {
      if (AppConstants.platform == "win") {
        if (file instanceof Components.interfaces.nsILocalFileWin) {
          try {
            return file.getVersionInfoField("FileDescription");
          } catch (e) {}
        }
      } else if (AppConstants.platform == "macosx") {
        if (file instanceof Components.interfaces.nsILocalFileMac) {
          try {
            return file.bundleDisplayName;
          } catch (e) {}
        }
      }
      return file.leafName;
    },

    /**
    * Double click accepts an app
    */
    appDoubleClick: function appDoubleClick() {
      var list = document.getElementById("app-picker-listbox");
      var selItem = list.selectedItem;

      if (!selItem) {
          this._incomingParams.handlerApp = null;
          return true;
      }

      this._incomingParams.handlerApp = selItem.handlerApp;
      window.close();

      return true;
    },

    appPickerOK: function appPickerOK() {
      if (this._incomingParams.handlerApp) return true;

      var list = document.getElementById("app-picker-listbox");
      var selItem = list.selectedItem;

      if (!selItem) {
        this._incomingParams.handlerApp = null;
        return true;
      }
      this._incomingParams.handlerApp = selItem.handlerApp;

      return true;
    },

    appPickerCancel: function appPickerCancel() {
      this._incomingParams.handlerApp = null;
      return true;
    },

    /**
    * User browse for an app.
    */
    appPickerBrowse: function appPickerBrowse() {
      var nsIFilePicker = Components.interfaces.nsIFilePicker;
      var fp = Components.classes["@mozilla.org/filepicker;1"].
               createInstance(nsIFilePicker);

      fp.init(window, this._incomingParams.title, nsIFilePicker.modeOpen);
      fp.appendFilters(nsIFilePicker.filterApps);

      var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
                            .getService(Components.interfaces.nsIProperties);
      var startLocation;
      if (AppConstants.platform == "win") {
        startLocation = "ProgF"; // Program Files
      } else if (AppConstants.platform == "macosx") {
        startLocation = "LocApp"; // Local Applications
      } else {
        startLocation = "Home";
      }
      fp.displayDirectory =
        fileLoc.get(startLocation, Components.interfaces.nsILocalFile);

      fp.open(rv => {
          if (rv == nsIFilePicker.returnOK && fp.file) {
              var localHandlerApp =
                Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
                createInstance(Components.interfaces.nsILocalHandlerApp);
              localHandlerApp.executable = fp.file;

              this._incomingParams.handlerApp = localHandlerApp;
              window.close();
          }
      });
      return true;
    }
}

// Global object
var g_dialog = new AppPicker();
PK
!<	11+chrome/toolkit/content/global/appPicker.xul<?xml version="1.0"?> 

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

  <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
  <?xml-stylesheet href="chrome://global/skin/appPicker.css" type="text/css"?>

  <!DOCTYPE dialog SYSTEM "chrome://global/locale/appPicker.dtd" >

  <dialog id="app-picker"
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    onload="g_dialog.appPickerLoad();"
    buttons="accept,cancel,extra2"
    buttonlabelextra2="&BrowseButton.label;"
    ondialogextra2="g_dialog.appPickerBrowse();"
    defaultButton="cancel"
    ondialogaccept="return g_dialog.appPickerOK();"
    ondialogcancel="return g_dialog.appPickerCancel();"
    aria-describedby="content-description suggested-filename"
    persist="screenX screenY">

    <script type="application/javascript" src="chrome://global/content/appPicker.js"/>

    <hbox id="file-info" align="center">
      <image id="content-icon" src=""/>
      <vbox flex="1">
        <label id="content-description" crop="center" value=""/>
        <label id="suggested-filename" crop="center" value=""/>
      </vbox>
    </hbox>

    <label id="sendto-message" value="&SendMsg.label;" control="app-picker-listbox"/>

    <listbox id="app-picker-listbox" rows="5"
             ondblclick="g_dialog.appDoubleClick();"/>

    <label id="app-picker-notfound" value="&NoAppFound.label;" hidden="true"/>
  </dialog>
PK
!<	].chrome/toolkit/content/global/autocomplete.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* Apply crisp rendering for favicons at exactly 2dppx resolution */
@media (resolution: 2dppx) {
  .ac-site-icon {
    image-rendering: -moz-crisp-edges;
  }
}

richlistitem {
  -moz-box-orient: horizontal;
  overflow: hidden;
}

.ac-title-text,
.ac-tags-text,
.ac-url-text,
.ac-action-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ac-tags[empty] {
  display: none;
}

.autocomplete-richlistitem:not([selected]):not(:hover) > .ac-action[actiontype=searchengine],
.autocomplete-richlistitem:not([selected]):not(:hover) > .ac-separator[actiontype=searchengine] {
  display: none;
}

.ac-separator[type=keyword] {
  display: none;
}
PK
!<;+8chrome/toolkit/content/global/backgroundPageThumbs.xhtml<!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/. -->

<!-- This page is used to host a (remote) browser for background page
     thumbnailing purposes. It's always loaded as chrome:// . -->
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta charset='utf-8' />
    <title>backgroundPageThumbs.html</title>
  </head>
  <body></body>
</html>

PK
!<	7>{{<chrome/toolkit/content/global/backgroundPageThumbsContent.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

var { classes: Cc, interfaces: Ci, utils: Cu } = Components;

Cu.importGlobalProperties(["Blob", "FileReader"]);

Cu.import("resource://gre/modules/PageThumbUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const STATE_LOADING = 1;
const STATE_CAPTURING = 2;
const STATE_CANCELED = 3;

// NOTE: Copied from nsSandboxFlags.h
/**
 * This flag prevents content from creating new auxiliary browsing contexts,
 * e.g. using the target attribute, the window.open() method, or the
 * showModalDialog() method.
 */
const SANDBOXED_AUXILIARY_NAVIGATION = 0x2;

const backgroundPageThumbsContent = {

  init() {
    Services.obs.addObserver(this, "document-element-inserted", true);

    // We want a low network priority for this service - lower than b/g tabs
    // etc - so set it to the lowest priority available.
    this._webNav.QueryInterface(Ci.nsIDocumentLoader).
      loadGroup.QueryInterface(Ci.nsISupportsPriority).
      priority = Ci.nsISupportsPriority.PRIORITY_LOWEST;

    docShell.allowMedia = false;
    docShell.allowPlugins = false;
    docShell.allowContentRetargeting = false;
    let defaultFlags = Ci.nsIRequest.LOAD_ANONYMOUS |
                       Ci.nsIRequest.LOAD_BYPASS_CACHE |
                       Ci.nsIRequest.INHIBIT_CACHING |
                       Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
    docShell.defaultLoadFlags = defaultFlags;
    docShell.sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;

    addMessageListener("BackgroundPageThumbs:capture",
                       this._onCapture.bind(this));
    docShell.
      QueryInterface(Ci.nsIInterfaceRequestor).
      getInterface(Ci.nsIWebProgress).
      addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
  },

  observe(subj, topic, data) {
    // Arrange to prevent (most) popup dialogs for this window - popups done
    // in the parent (eg, auth) aren't prevented, but alert() etc are.
    // disableDialogs only works on the current inner window, so it has
    // to be called every page load, but before scripts run.
    if (content && subj == content.document) {
      content.
        QueryInterface(Ci.nsIInterfaceRequestor).
        getInterface(Ci.nsIDOMWindowUtils).
        disableDialogs();
    }
  },

  get _webNav() {
    return docShell.QueryInterface(Ci.nsIWebNavigation);
  },

  _onCapture(msg) {
    this._nextCapture = {
      id: msg.data.id,
      url: msg.data.url,
    };
    if (this._currentCapture) {
      if (this._state == STATE_LOADING) {
        // Cancel the current capture.
        this._state = STATE_CANCELED;
        this._loadAboutBlank();
      }
      // Let the current capture finish capturing, or if it was just canceled,
      // wait for onStateChange due to the about:blank load.
      return;
    }
    this._startNextCapture();
  },

  _startNextCapture() {
    if (!this._nextCapture)
      return;
    this._currentCapture = this._nextCapture;
    delete this._nextCapture;
    this._state = STATE_LOADING;
    this._currentCapture.pageLoadStartDate = new Date();

    try {
      this._webNav.loadURI(this._currentCapture.url,
                           Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
                           null, null, null);
    } catch (e) {
      this._failCurrentCapture("BAD_URI");
      delete this._currentCapture;
      this._startNextCapture();
    }
  },

  onStateChange(webProgress, req, flags, status) {
    if (webProgress.isTopLevel &&
        (flags & Ci.nsIWebProgressListener.STATE_STOP) &&
        this._currentCapture) {
      if (req.name == "about:blank") {
        if (this._state == STATE_CAPTURING) {
          // about:blank has loaded, ending the current capture.
          this._finishCurrentCapture();
          delete this._currentCapture;
          this._startNextCapture();
        } else if (this._state == STATE_CANCELED) {
          delete this._currentCapture;
          this._startNextCapture();
        }
      } else if (this._state == STATE_LOADING &&
               Components.isSuccessCode(status)) {
        // The requested page has loaded.  Capture it.
        this._state = STATE_CAPTURING;
        this._captureCurrentPage();
      } else if (this._state != STATE_CANCELED) {
        // Something went wrong.  Cancel the capture.  Loading about:blank
        // while onStateChange is still on the stack does not actually stop
        // the request if it redirects, so do it asyncly.
        this._state = STATE_CANCELED;
        if (!this._cancelTimer) {
          this._cancelTimer =
            Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
          this._cancelTimer.init(() => {
            this._loadAboutBlank();
            delete this._cancelTimer;
          }, 0, Ci.nsITimer.TYPE_ONE_SHOT);
        }
      }
    }
  },

  _captureCurrentPage() {
    let capture = this._currentCapture;
    capture.finalURL = this._webNav.currentURI.spec;
    capture.pageLoadTime = new Date() - capture.pageLoadStartDate;

    let canvasDrawDate = new Date();

    let finalCanvas = PageThumbUtils.createSnapshotThumbnail(content, null);
    capture.canvasDrawTime = new Date() - canvasDrawDate;

    finalCanvas.toBlob(blob => {
      capture.imageBlob = new Blob([blob]);
      // Load about:blank to finish the capture and wait for onStateChange.
      this._loadAboutBlank();
    });
  },

  _finishCurrentCapture() {
    let capture = this._currentCapture;
    let fileReader = new FileReader();
    fileReader.onloadend = () => {
      sendAsyncMessage("BackgroundPageThumbs:didCapture", {
        id: capture.id,
        imageData: fileReader.result,
        finalURL: capture.finalURL,
        telemetry: {
          CAPTURE_PAGE_LOAD_TIME_MS: capture.pageLoadTime,
          CAPTURE_CANVAS_DRAW_TIME_MS: capture.canvasDrawTime,
        },
      });
    };
    fileReader.readAsArrayBuffer(capture.imageBlob);
  },

  _failCurrentCapture(reason) {
    let capture = this._currentCapture;
    sendAsyncMessage("BackgroundPageThumbs:didCapture", {
      id: capture.id,
      failReason: reason,
    });
  },

  // We load about:blank to finish all captures, even canceled captures.  Two
  // reasons: GC the captured page, and ensure it can't possibly load any more
  // resources.
  _loadAboutBlank: function _loadAboutBlank() {
    // It's possible we've been destroyed by now, if so don't do anything:
    if (!docShell) {
      return;
    }
    this._webNav.loadURI("about:blank",
                         Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
                         null, null, null);
  },

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIWebProgressListener,
    Ci.nsISupportsWeakReference,
    Ci.nsIObserver,
  ]),
};

backgroundPageThumbsContent.init();
PK
!<n{YY7chrome/toolkit/content/global/bindings/autocomplete.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="autocompleteBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="autocomplete" role="xul:combobox"
           extends="chrome://global/content/bindings/textbox.xml#textbox">
    <resources>
      <stylesheet src="chrome://global/content/autocomplete.css"/>
      <stylesheet src="chrome://global/skin/autocomplete.css"/>
    </resources>

    <content sizetopopup="pref">
      <xul:hbox class="autocomplete-textbox-container" flex="1" xbl:inherits="focused">
        <children includes="image|deck|stack|box">
          <xul:image class="autocomplete-icon" allowevents="true"/>
        </children>

        <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
          <children/>
          <html:input anonid="input" class="autocomplete-textbox textbox-input"
                      allowevents="true"
                      xbl:inherits="tooltiptext=inputtooltiptext,value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
        </xul:hbox>
        <children includes="hbox"/>
      </xul:hbox>

      <xul:dropmarker anonid="historydropmarker" class="autocomplete-history-dropmarker"
                      allowevents="true"
                      xbl:inherits="open,enablehistory,parentfocused=focused"/>

      <xul:popupset anonid="popupset" class="autocomplete-result-popupset"/>

      <children includes="toolbarbutton"/>
    </content>

    <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
      <field name="mController">null</field>
      <field name="mSearchNames">null</field>
      <field name="mIgnoreInput">false</field>

      <field name="_searchBeginHandler">null</field>
      <field name="_searchCompleteHandler">null</field>
      <field name="_textEnteredHandler">null</field>
      <field name="_textRevertedHandler">null</field>

      <constructor><![CDATA[
        this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
          getService(Components.interfaces.nsIAutoCompleteController);

        this._searchBeginHandler = this.initEventHandler("searchbegin");
        this._searchCompleteHandler = this.initEventHandler("searchcomplete");
        this._textEnteredHandler = this.initEventHandler("textentered");
        this._textRevertedHandler = this.initEventHandler("textreverted");

        // For security reasons delay searches on pasted values.
        this.inputField.controllers.insertControllerAt(0, this._pasteController);
      ]]></constructor>

      <destructor><![CDATA[
        this.inputField.controllers.removeController(this._pasteController);
      ]]></destructor>

      <!-- =================== nsIAutoCompleteInput =================== -->

      <field name="_popup">null</field>
      <property name="popup" readonly="true">
        <getter><![CDATA[
          // Memoize the result in a field rather than replacing this property,
          // so that it can be reset along with the binding.
          if (this._popup) {
            return this._popup;
          }

          let popup = null;
          let popupId = this.getAttribute("autocompletepopup");
          if (popupId) {
            popup = document.getElementById(popupId);
          }
          if (!popup) {
            popup = document.createElement("panel");
            popup.setAttribute("type", "autocomplete");
            popup.setAttribute("noautofocus", "true");

            let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
            popupset.appendChild(popup);
          }
          popup.mInput = this;

          return this._popup = popup;
        ]]></getter>
      </property>

      <property name="controller" onget="return this.mController;" readonly="true"/>

      <property name="popupOpen"
                onget="return this.popup.popupOpen;"
                onset="if (val) this.openPopup(); else this.closePopup();"/>

      <property name="disableAutoComplete"
                onset="this.setAttribute('disableautocomplete', val); return val;"
                onget="return this.getAttribute('disableautocomplete') == 'true';"/>

      <property name="completeDefaultIndex"
                onset="this.setAttribute('completedefaultindex', val); return val;"
                onget="return this.getAttribute('completedefaultindex') == 'true';"/>

      <property name="completeSelectedIndex"
                onset="this.setAttribute('completeselectedindex', val); return val;"
                onget="return this.getAttribute('completeselectedindex') == 'true';"/>

      <property name="forceComplete"
                onset="this.setAttribute('forcecomplete', val); return val;"
                onget="return this.getAttribute('forcecomplete') == 'true';"/>

      <property name="minResultsForPopup"
                onset="this.setAttribute('minresultsforpopup', val); return val;"
                onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>

      <property name="showCommentColumn"
                onset="this.setAttribute('showcommentcolumn', val); return val;"
                onget="return this.getAttribute('showcommentcolumn') == 'true';"/>

      <property name="showImageColumn"
                onset="this.setAttribute('showimagecolumn', val); return val;"
                onget="return this.getAttribute('showimagecolumn') == 'true';"/>

      <property name="timeout"
                onset="this.setAttribute('timeout', val); return val;">
        <getter><![CDATA[
          // For security reasons delay searches on pasted values.
          if (this._valueIsPasted) {
            let t = parseInt(this.getAttribute("pastetimeout"));
            return isNaN(t) ? 1000 : t;
          }

          let t = parseInt(this.getAttribute("timeout"));
          return isNaN(t) ? 50 : t;
        ]]></getter>
      </property>

      <property name="searchParam"
                onget="return this.getAttribute('autocompletesearchparam') || '';"
                onset="this.setAttribute('autocompletesearchparam', val); return val;"/>

      <property name="searchCount" readonly="true"
                onget="this.initSearchNames(); return this.mSearchNames.length;"/>

      <field name="shrinkDelay" readonly="true">
        parseInt(this.getAttribute("shrinkdelay")) || 0
      </field>

      <property name="PrivateBrowsingUtils" readonly="true">
        <getter><![CDATA[
          let module = {};
          Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", module);
          Object.defineProperty(this, "PrivateBrowsingUtils", {
            configurable: true,
            enumerable: true,
            writable: true,
            value: module.PrivateBrowsingUtils
          });
          return module.PrivateBrowsingUtils;
        ]]></getter>
      </property>

      <property name="inPrivateContext" readonly="true"
                onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>

      <property name="noRollupOnCaretMove" readonly="true"
                onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/>

      <!-- This is the maximum number of drop-down rows we get when we
            hit the drop marker beside fields that have it (like the URLbar).-->
      <field name="maxDropMarkerRows" readonly="true">14</field>

      <method name="getSearchAt">
        <parameter name="aIndex"/>
        <body><![CDATA[
          this.initSearchNames();
          return this.mSearchNames[aIndex];
        ]]></body>
      </method>

      <method name="setTextValueWithReason">
        <parameter name="aValue"/>
        <parameter name="aReason"/>
        <body><![CDATA[
          if (aReason == Components.interfaces.nsIAutoCompleteInput
                                   .TEXTVALUE_REASON_COMPLETEDEFAULT) {
            this._disableTrim = true;
          }
          this.textValue = aValue;
          this._disableTrim = false;
        ]]></body>
      </method>

      <property name="textValue">
        <getter><![CDATA[
          if (typeof this.onBeforeTextValueGet == "function") {
            let result = this.onBeforeTextValueGet();
            if (result) {
              return result.value;
            }
          }
          return this.value;
        ]]></getter>
        <setter><![CDATA[
          if (typeof this.onBeforeTextValueSet == "function")
            val = this.onBeforeTextValueSet(val);

          this.value = val;

          // Completing a result should simulate the user typing the result, so
          // fire an input event.
          let evt = document.createEvent("UIEvents");
          evt.initUIEvent("input", true, false, window, 0);
          this.mIgnoreInput = true;
          this.dispatchEvent(evt);
          this.mIgnoreInput = false;

          return this.value;
        ]]></setter>
      </property>

      <method name="selectTextRange">
        <parameter name="aStartIndex"/>
        <parameter name="aEndIndex"/>
        <body><![CDATA[
          this.inputField.setSelectionRange(aStartIndex, aEndIndex);
        ]]></body>
      </method>

      <method name="onSearchBegin">
        <body><![CDATA[
          if (this.popup && typeof this.popup.onSearchBegin == "function")
            this.popup.onSearchBegin();
          if (this._searchBeginHandler)
            this._searchBeginHandler();
        ]]></body>
      </method>

      <method name="onSearchComplete">
        <body><![CDATA[
          if (this.mController.matchCount == 0)
            this.setAttribute("nomatch", "true");
          else
            this.removeAttribute("nomatch");

          if (this.ignoreBlurWhileSearching && !this.focused) {
            this.handleEnter();
            this.detachController();
          }

          if (this._searchCompleteHandler)
            this._searchCompleteHandler();
        ]]></body>
      </method>

      <method name="onTextEntered">
        <parameter name="event"/>
        <body><![CDATA[
          let rv = false;
          if (this._textEnteredHandler) {
            rv = this._textEnteredHandler(event);
          }
          return rv;
        ]]></body>
      </method>

      <method name="onTextReverted">
        <body><![CDATA[
          if (this._textRevertedHandler)
            return this._textRevertedHandler();
          return false;
        ]]></body>
      </method>

      <!-- =================== nsIDOMXULMenuListElement =================== -->

      <property name="editable" readonly="true"
                onget="return true;" />

      <property name="crop"
                onset="this.setAttribute('crop',val); return val;"
                onget="return this.getAttribute('crop');"/>

      <property name="open"
                onget="return this.getAttribute('open') == 'true';">
        <setter><![CDATA[
          if (val)
            this.showHistoryPopup();
          else
            this.closePopup();
        ]]></setter>
      </property>

      <!-- =================== PUBLIC MEMBERS =================== -->

      <field name="valueIsTyped">false</field>
      <field name="_disableTrim">false</field>
      <property name="value">
        <getter><![CDATA[
          if (typeof this.onBeforeValueGet == "function") {
            var result = this.onBeforeValueGet();
            if (result)
              return result.value;
          }
          return this.inputField.value;
        ]]></getter>
        <setter><![CDATA[
          this.mIgnoreInput = true;

          if (typeof this.onBeforeValueSet == "function")
            val = this.onBeforeValueSet(val);

          if (typeof this.trimValue == "function" && !this._disableTrim)
            val = this.trimValue(val);

          this.valueIsTyped = false;
          this.inputField.value = val;

          if (typeof this.formatValue == "function")
            this.formatValue();

          this.mIgnoreInput = false;
          var event = document.createEvent("Events");
          event.initEvent("ValueChange", true, true);
          this.inputField.dispatchEvent(event);
          return val;
        ]]></setter>
      </property>

      <property name="focused" readonly="true"
                onget="return this.getAttribute('focused') == 'true';"/>

      <!-- maximum number of rows to display at a time -->
      <property name="maxRows"
                onset="this.setAttribute('maxrows', val); return val;"
                onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>

      <!-- option to allow scrolling through the list via the tab key, rather than
           tab moving focus out of the textbox -->
      <property name="tabScrolling"
                onset="this.setAttribute('tabscrolling', val); return val;"
                onget="return this.getAttribute('tabscrolling') == 'true';"/>

      <!-- option to completely ignore any blur events while searches are
           still going on. -->
      <property name="ignoreBlurWhileSearching"
                onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
                onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>

      <!-- disable key navigation handling in the popup results -->
      <property name="disableKeyNavigation"
                onset="this.setAttribute('disablekeynavigation', val); return val;"
                onget="return this.getAttribute('disablekeynavigation') == 'true';"/>

      <!-- option to highlight entries that don't have any matches -->
      <property name="highlightNonMatches"
                onset="this.setAttribute('highlightnonmatches', val); return val;"
                onget="return this.getAttribute('highlightnonmatches') == 'true';"/>

      <!-- =================== PRIVATE MEMBERS =================== -->

      <!-- ::::::::::::: autocomplete controller ::::::::::::: -->

      <method name="attachController">
        <body><![CDATA[
          this.mController.input = this;
        ]]></body>
      </method>

      <method name="detachController">
        <body><![CDATA[
          if (this.mController.input == this)
            this.mController.input = null;
        ]]></body>
      </method>

      <!-- ::::::::::::: popup opening ::::::::::::: -->

      <method name="openPopup">
        <body><![CDATA[
          if (this.focused)
            this.popup.openAutocompletePopup(this, this);
        ]]></body>
      </method>

      <method name="closePopup">
        <body><![CDATA[
          this.popup.closePopup();
        ]]></body>
      </method>

      <method name="showHistoryPopup">
        <body><![CDATA[
          // history dropmarker pushed state
          function cleanup(popup) {
            popup.removeEventListener("popupshowing", onShow);
          }
          function onShow(event) {
            var popup = event.target, input = popup.input;
            cleanup(popup);
            input.setAttribute("open", "true");
            function onHide() {
              input.removeAttribute("open");
              popup.removeEventListener("popuphiding", onHide);
            }
            popup.addEventListener("popuphiding", onHide);
          }
          this.popup.addEventListener("popupshowing", onShow);
          setTimeout(cleanup, 1000, this.popup);

          // Store our "normal" maxRows on the popup, so that it can reset the
          // value when the popup is hidden.
          this.popup._normalMaxRows = this.maxRows;

          // Increase our maxRows temporarily, since we want the dropdown to
          // be bigger in this case. The popup's popupshowing/popuphiding
          // handlers will take care of resetting this.
          this.maxRows = this.maxDropMarkerRows;

          // Ensure that we have focus.
          if (!this.focused)
            this.focus();
          this.attachController();
          this.mController.startSearch("");
        ]]></body>
      </method>

      <method name="toggleHistoryPopup">
        <body><![CDATA[
          // If this method is called on the same event tick as the popup gets
          // hidden, do nothing to avoid re-opening the popup when the drop
          // marker is clicked while the popup is still open.
          if (!this.popup.isPopupHidingTick && !this.popup.popupOpen)
            this.showHistoryPopup();
          else
            this.closePopup();
        ]]></body>
      </method>

      <!-- ::::::::::::: event dispatching ::::::::::::: -->

      <method name="initEventHandler">
        <parameter name="aEventType"/>
        <body><![CDATA[
          let handlerString = this.getAttribute("on" + aEventType);
          if (handlerString) {
            return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
          }
          return null;
        ]]></body>
      </method>

      <!-- ::::::::::::: key handling ::::::::::::: -->

      <field name="_selectionDetails">null</field>
      <method name="onKeyPress">
        <parameter name="aEvent"/>
        <body><![CDATA[
          return this.handleKeyPress(aEvent);
        ]]></body>
      </method>

      <method name="handleKeyPress">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (aEvent.target.localName != "textbox")
            return true; // Let child buttons of autocomplete take input

          // Re: urlbarDeferred, see the comment in urlbarBindings.xml.
          if (aEvent.defaultPrevented && !aEvent.urlbarDeferred) {
            return false;
          }

          var cancel = false;

          // Catch any keys that could potentially move the caret. Ctrl can be
          // used in combination with these keys on Windows and Linux; and Alt
          // can be used on OS X, so make sure the unused one isn't used.
          let metaKey = /Mac/.test(navigator.platform) ? aEvent.ctrlKey : aEvent.altKey;
          if (!this.disableKeyNavigation && !metaKey) {
            switch (aEvent.keyCode) {
              case KeyEvent.DOM_VK_LEFT:
              case KeyEvent.DOM_VK_RIGHT:
              case KeyEvent.DOM_VK_HOME:
                cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
                break;
            }
          }

          // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
          if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
            switch (aEvent.keyCode) {
              case KeyEvent.DOM_VK_TAB:
                if (this.tabScrolling && this.popup.popupOpen)
                  cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
                                                                KeyEvent.DOM_VK_UP :
                                                                KeyEvent.DOM_VK_DOWN);
                else if (this.forceComplete && this.mController.matchCount >= 1)
                  this.mController.handleTab();
                break;
              case KeyEvent.DOM_VK_UP:
              case KeyEvent.DOM_VK_DOWN:
              case KeyEvent.DOM_VK_PAGE_UP:
              case KeyEvent.DOM_VK_PAGE_DOWN:
                cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
                break;
            }
          }

          // Handle keys we know aren't part of a shortcut, even with Alt or
          // Ctrl.
          switch (aEvent.keyCode) {
            case KeyEvent.DOM_VK_ESCAPE:
              cancel = this.mController.handleEscape();
              break;
            case KeyEvent.DOM_VK_RETURN:
              if (/Mac/.test(navigator.platform)) {
                // Prevent the default action, since it will beep on Mac
                if (aEvent.metaKey)
                  aEvent.preventDefault();
              }
              if (this.mController.selection) {
                this._selectionDetails = {
                  index: this.mController.selection.currentIndex,
                  kind: "key"
                };
              }
              cancel = this.handleEnter(aEvent);
              break;
            case KeyEvent.DOM_VK_DELETE:
              if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) {
                break;
              }
              cancel = this.handleDelete();
              break;
            case KeyEvent.DOM_VK_BACK_SPACE:
              if (/Mac/.test(navigator.platform) && aEvent.shiftKey) {
                cancel = this.handleDelete();
              }
              break;
            case KeyEvent.DOM_VK_DOWN:
            case KeyEvent.DOM_VK_UP:
              if (aEvent.altKey)
                this.toggleHistoryPopup();
              break;
            case KeyEvent.DOM_VK_F4:
              if (!/Mac/.test(navigator.platform)) {
                this.toggleHistoryPopup();
              }
              break;
          }

          if (cancel) {
            aEvent.stopPropagation();
            aEvent.preventDefault();
          }

          return true;
        ]]></body>
      </method>

      <method name="handleEnter">
        <parameter name="event"/>
        <body><![CDATA[
          return this.mController.handleEnter(false, event || null);
        ]]></body>
      </method>

      <method name="handleDelete">
        <body><![CDATA[
          return this.mController.handleDelete();
        ]]></body>
      </method>

      <!-- ::::::::::::: miscellaneous ::::::::::::: -->

      <method name="initSearchNames">
        <body><![CDATA[
          if (!this.mSearchNames) {
            var names = this.getAttribute("autocompletesearch");
            if (!names)
              this.mSearchNames = [];
            else
              this.mSearchNames = names.split(" ");
          }
        ]]></body>
      </method>

      <method name="_focus">
        <!-- doesn't reset this.mController -->
        <body><![CDATA[
          this._dontBlur = true;
          this.focus();
          this._dontBlur = false;
        ]]></body>
      </method>

      <method name="resetActionType">
        <body><![CDATA[
          if (this.mIgnoreInput)
            return;
          this.removeAttribute("actiontype");
        ]]></body>
      </method>

      <field name="_valueIsPasted">false</field>
      <field name="_pasteController"><![CDATA[
        ({
          _autocomplete: this,
          _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
          supportsCommand: aCommand => aCommand == "cmd_paste",
          doCommand(aCommand) {
            this._autocomplete._valueIsPasted = true;
            this._autocomplete.editor.paste(this._kGlobalClipboard);
            this._autocomplete._valueIsPasted = false;
          },
          isCommandEnabled(aCommand) {
            return this._autocomplete.editor.isSelectionEditable &&
                   this._autocomplete.editor.canPaste(this._kGlobalClipboard);
          },
          onEvent() {}
        })
      ]]></field>

      <method name="onInput">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!this.mIgnoreInput && this.mController.input == this) {
            this.valueIsTyped = true;
            this.mController.handleText();
          }
          this.resetActionType();
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="input"><![CDATA[
        this.onInput(event);
      ]]></handler>

      <handler event="keypress" phase="capturing"
               action="return this.onKeyPress(event);"/>

      <handler event="compositionstart" phase="capturing"
               action="if (this.mController.input == this) this.mController.handleStartComposition();"/>

      <handler event="compositionend" phase="capturing"
               action="if (this.mController.input == this) this.mController.handleEndComposition();"/>

      <handler event="focus" phase="capturing"><![CDATA[
        this.attachController();
        if (window.gBrowser && window.gBrowser.selectedBrowser.hasAttribute("usercontextid")) {
          this.userContextId = parseInt(window.gBrowser.selectedBrowser.getAttribute("usercontextid"));
        } else {
          this.userContextId = 0;
        }
      ]]></handler>

      <handler event="blur" phase="capturing"><![CDATA[
        if (!this._dontBlur) {
          if (this.forceComplete && this.mController.matchCount >= 1) {
            // mousemove sets selected index. Don't blindly use that selected
            // index in this blur handler since if the popup is open you can
            // easily "select" another match just by moving the mouse over it.
            let filledVal = this.value.replace(/.+ >> /, "").toLowerCase();
            let selectedVal = null;
            if (this.popup.selectedIndex >= 0) {
              selectedVal = this.mController.getFinalCompleteValueAt(
                this.popup.selectedIndex);
            }
            if (selectedVal && filledVal != selectedVal.toLowerCase()) {
              for (let i = 0; i < this.mController.matchCount; i++) {
                let matchVal = this.mController.getFinalCompleteValueAt(i);
                if (matchVal.toLowerCase() == filledVal) {
                  this.popup.selectedIndex = i;
                  break;
                }
              }
            }
            this.mController.handleEnter(false);
          }
          if (!this.ignoreBlurWhileSearching)
            this.detachController();
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
    <resources>
      <stylesheet src="chrome://global/content/autocomplete.css"/>
      <stylesheet src="chrome://global/skin/tree.css"/>
      <stylesheet src="chrome://global/skin/autocomplete.css"/>
    </resources>

    <content ignorekeys="true" level="top" consumeoutsideclicks="never">
      <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
        <xul:treecols anonid="treecols">
          <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
        </xul:treecols>
        <xul:treechildren class="autocomplete-treebody"/>
      </xul:tree>
    </content>

    <implementation>
      <field name="mShowCommentColumn">false</field>
      <field name="mShowImageColumn">false</field>

      <property name="showCommentColumn"
                   onget="return this.mShowCommentColumn;">
        <setter>
          <![CDATA[
          if (!val && this.mShowCommentColumn) {
            // reset the flex on the value column and remove the comment column
            document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1);
            this.removeColumn("treecolAutoCompleteComment");
          } else if (val && !this.mShowCommentColumn) {
            // reset the flex on the value column and add the comment column
            document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2);
            this.addColumn({id: "treecolAutoCompleteComment", flex: 1});
          }
          this.mShowCommentColumn = val;
          return val;
        ]]>
        </setter>
      </property>

      <property name="showImageColumn"
                onget="return this.mShowImageColumn;">
        <setter>
          <![CDATA[
          if (!val && this.mShowImageColumn) {
            // remove the image column
            this.removeColumn("treecolAutoCompleteImage");
          } else if (val && !this.mShowImageColumn) {
            // add the image column
            this.addColumn({id: "treecolAutoCompleteImage", flex: 1});
          }
          this.mShowImageColumn = val;
          return val;
        ]]>
        </setter>
      </property>


      <method name="addColumn">
        <parameter name="aAttrs"/>
        <body>
          <![CDATA[
          var col = document.createElement("treecol");
          col.setAttribute("class", "autocomplete-treecol");
          for (var name in aAttrs)
            col.setAttribute(name, aAttrs[name]);
          this.treecols.appendChild(col);
          return col;
        ]]>
        </body>
      </method>

      <method name="removeColumn">
        <parameter name="aColId"/>
        <body>
          <![CDATA[
          return this.treecols.removeChild(document.getElementById(aColId));
        ]]>
        </body>
      </method>

      <property name="selectedIndex"
                onget="return this.tree.currentIndex;">
        <setter>
          <![CDATA[
          this.tree.view.selection.select(val);
          if (this.tree.treeBoxObject.height > 0)
            this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val);
          // Fire select event on xul:tree so that accessibility API
          // support layer can fire appropriate accessibility events.
          var event = document.createEvent("Events");
          event.initEvent("select", true, true);
          this.tree.dispatchEvent(event);
          return val;
        ]]></setter>
      </property>

      <method name="adjustHeight">
        <body>
          <![CDATA[
          // detect the desired height of the tree
          var bx = this.tree.treeBoxObject;
          var view = this.tree.view;
          if (!view)
            return;
          var rows = this.maxRows;
          if (!view.rowCount || (rows && view.rowCount < rows))
            rows = view.rowCount;

          var height = rows * bx.rowHeight;

          if (height == 0) {
            this.tree.setAttribute("collapsed", "true");
          } else {
            if (this.tree.hasAttribute("collapsed"))
              this.tree.removeAttribute("collapsed");

            this.tree.setAttribute("height", height);
          }
          this.tree.setAttribute("hidescrollbar", view.rowCount <= rows);
        ]]>
        </body>
      </method>

      <method name="openAutocompletePopup">
        <parameter name="aInput"/>
        <parameter name="aElement"/>
        <body><![CDATA[
          // until we have "baseBinding", (see bug #373652) this allows
          // us to override openAutocompletePopup(), but still call
          // the method on the base class
          this._openAutocompletePopup(aInput, aElement);
        ]]></body>
      </method>

      <method name="_openAutocompletePopup">
        <parameter name="aInput"/>
        <parameter name="aElement"/>
        <body><![CDATA[
          if (!this.mPopupOpen) {
            this.mInput = aInput;
            this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
            this.invalidate();

            this.showCommentColumn = this.mInput.showCommentColumn;
            this.showImageColumn = this.mInput.showImageColumn;

            var rect = aElement.getBoundingClientRect();
            var nav = aElement.ownerGlobal.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIWebNavigation);
            var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell);
            var docViewer = docShell.contentViewer;
            var width = (rect.right - rect.left) * docViewer.fullZoom;
            this.setAttribute("width", width > 100 ? width : 100);

            // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840
            var popupDirection = aElement.ownerGlobal.getComputedStyle(aElement).direction;
            this.style.direction = popupDirection;

            this.openPopup(aElement, "after_start", 0, 0, false, false);
          }
        ]]></body>
      </method>

      <method name="invalidate">
        <body><![CDATA[
          this.adjustHeight();
          this.tree.treeBoxObject.invalidate();
        ]]></body>
      </method>

      <method name="selectBy">
        <parameter name="aReverse"/>
        <parameter name="aPage"/>
        <body><![CDATA[
          try {
            var amount = aPage ? 5 : 1;
            this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount - 1);
            if (this.selectedIndex == -1) {
              this.input._focus();
            }
          } catch (ex) {
            // do nothing - occasionally timer-related js errors happen here
            // e.g. "this.selectedIndex has no properties", when you type fast and hit a
            // navigation key before this popup has opened
          }
        ]]></body>
      </method>

      <!-- =================== PUBLIC MEMBERS =================== -->

      <field name="tree">
        document.getAnonymousElementByAttribute(this, "anonid", "tree");
      </field>

      <field name="treecols">
        document.getAnonymousElementByAttribute(this, "anonid", "treecols");
      </field>

      <property name="view"
                onget="return this.mView;">
        <setter><![CDATA[
          // We must do this by hand because the tree binding may not be ready yet
          this.mView = val;
          this.tree.boxObject.view = val;
        ]]></setter>
      </property>

    </implementation>
  </binding>

  <binding id="autocomplete-base-popup" role="none"
extends="chrome://global/content/bindings/popup.xml#popup">
    <implementation implements="nsIAutoCompletePopup">
      <field name="mInput">null</field>
      <field name="mPopupOpen">false</field>
      <field name="mIsPopupHidingTick">false</field>

      <!-- =================== nsIAutoCompletePopup =================== -->

      <property name="input" readonly="true"
                onget="return this.mInput"/>

      <property name="overrideValue" readonly="true"
                onget="return null;"/>

      <property name="popupOpen" readonly="true"
                onget="return this.mPopupOpen;"/>

      <property name="isPopupHidingTick" readonly="true"
                onget="return this.mIsPopupHidingTick;"/>

      <method name="closePopup">
        <body>
          <![CDATA[
          if (this.mPopupOpen) {
            this.hidePopup();
            this.removeAttribute("width");
          }
        ]]>
        </body>
      </method>

      <!-- This is the default number of rows that we give the autocomplete
           popup when the textbox doesn't have a "maxrows" attribute
           for us to use. -->
      <field name="defaultMaxRows" readonly="true">6</field>

      <!-- In some cases (e.g. when the input's dropmarker button is clicked),
           the input wants to display a popup with more rows. In that case, it
           should increase its maxRows property and store the "normal" maxRows
           in this field. When the popup is hidden, we restore the input's
           maxRows to the value stored in this field.

           This field is set to -1 between uses so that we can tell when it's
           been set by the input and when we need to set it in the popupshowing
           handler. -->
      <field name="_normalMaxRows">-1</field>

      <property name="maxRows" readonly="true">
        <getter>
          <![CDATA[
          return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
        ]]>
        </getter>
      </property>

      <method name="getNextIndex">
        <parameter name="aReverse"/>
        <parameter name="aAmount"/>
        <parameter name="aIndex"/>
        <parameter name="aMaxRow"/>
        <body><![CDATA[
          if (aMaxRow < 0)
            return -1;

          var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount;
          if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow)
            newIdx = aMaxRow;
          else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0)
            newIdx = 0;

          if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
            aIndex = -1;
          else
            aIndex = newIdx;

          return aIndex;
        ]]></body>
      </method>

      <method name="onPopupClick">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
          controller.handleEnter(true, aEvent);
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="popupshowing"><![CDATA[
        // If normalMaxRows wasn't already set by the input, then set it here
        // so that we restore the correct number when the popup is hidden.

        // Null-check this.mInput; see bug 1017914
        if (this._normalMaxRows < 0 && this.mInput) {
          this._normalMaxRows = this.mInput.maxRows;
        }

        // Set an attribute for styling the popup based on the input.
        let inputID = "";
        if (this.mInput && this.mInput.ownerDocument &&
            this.mInput.ownerDocument.documentURIObject.schemeIs("chrome")) {
          inputID = this.mInput.id;
          // Take care of elements with no id that are inside xbl bindings
          if (!inputID) {
            let bindingParent = this.mInput.ownerDocument.getBindingParent(this.mInput);
            if (bindingParent) {
              inputID = bindingParent.id;
            }
          }
        }
        this.setAttribute("autocompleteinput", inputID);

        this.mPopupOpen = true;
      ]]></handler>

      <handler event="popuphiding"><![CDATA[
        var isListActive = true;
        if (this.selectedIndex == -1)
          isListActive = false;
        var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
        controller.stopSearch();

        this.removeAttribute("autocompleteinput");
        this.mPopupOpen = false;

        // Prevent opening popup from historydropmarker mousedown handler
        // on the same event tick the popup is hidden by the same mousedown
        // event.
        this.mIsPopupHidingTick = true;
        setTimeout(() => {
          this.mIsPopupHidingTick = false;
        }, 0);

        // Reset the maxRows property to the cached "normal" value (if there's
        // any), and reset normalMaxRows so that we can detect whether it was set
        // by the input when the popupshowing handler runs.

        // Null-check this.mInput; see bug 1017914
        if (this.mInput && this._normalMaxRows > 0) {
          this.mInput.maxRows = this._normalMaxRows;
        }
        this._normalMaxRows = -1;
        // If the list was being navigated and then closed, make sure
        // we fire accessible focus event back to textbox

        // Null-check this.mInput; see bug 1017914
        if (isListActive && this.mInput) {
          this.mInput.mIgnoreFocus = true;
          this.mInput._focus();
          this.mInput.mIgnoreFocus = false;
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="autocomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
    <resources>
      <stylesheet src="chrome://global/content/autocomplete.css"/>
      <stylesheet src="chrome://global/skin/autocomplete.css"/>
    </resources>

    <content ignorekeys="true" level="top" consumeoutsideclicks="never">
      <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/>
      <xul:hbox>
        <children/>
      </xul:hbox>
    </content>

    <implementation implements="nsIAutoCompletePopup">
      <field name="_currentIndex">0</field>
      <field name="_rlbAnimated">false</field>

      <!-- =================== nsIAutoCompletePopup =================== -->

      <property name="selectedIndex"
                onget="return this.richlistbox.selectedIndex;">
        <setter>
          <![CDATA[
          this.richlistbox.selectedIndex = val;
          // Since ensureElementIsVisible may cause an expensive Layout flush,
          // invoke it only if there may be a scrollbar, so if we could fetch
          // more results than we can show at once.
          // maxResults is the maximum number of fetched results, maxRows is the
          // maximum number of rows we show at once, without a scrollbar.
          if (this.mPopupOpen && this.maxResults > this.maxRows) {
            // when clearing the selection (val == -1, so selectedItem will be
            // null), we want to scroll back to the top.  see bug #406194
            this.richlistbox.ensureElementIsVisible(
              this.richlistbox.selectedItem || this.richlistbox.firstChild);
          }
          return val;
        ]]>
        </setter>
      </property>

      <method name="onSearchBegin">
        <body><![CDATA[
          this.richlistbox.mousedOverIndex = -1;

          if (typeof this._onSearchBegin == "function") {
            this._onSearchBegin();
          }
        ]]></body>
      </method>

      <method name="openAutocompletePopup">
        <parameter name="aInput"/>
        <parameter name="aElement"/>
        <body>
          <![CDATA[
          // until we have "baseBinding", (see bug #373652) this allows
          // us to override openAutocompletePopup(), but still call
          // the method on the base class
          this._openAutocompletePopup(aInput, aElement);
        ]]>
        </body>
      </method>

      <method name="_openAutocompletePopup">
        <parameter name="aInput"/>
        <parameter name="aElement"/>
        <body>
          <![CDATA[
          if (!this.mPopupOpen) {
            // It's possible that the panel is hidden initially
            // to avoid impacting startup / new window performance
            aInput.popup.hidden = false;

            this.mInput = aInput;
            // clear any previous selection, see bugs 400671 and 488357
            this.selectedIndex = -1;

            var width = aElement.getBoundingClientRect().width;
            this.setAttribute("width", width > 100 ? width : 100);
            // invalidate() depends on the width attribute
            this._invalidate();

            this.openPopup(aElement, "after_start", 0, 0, false, false);
          }
        ]]>
        </body>
      </method>

      <method name="invalidate">
        <parameter name="reason"/>
        <body>
          <![CDATA[
          // Don't bother doing work if we're not even showing
          if (!this.mPopupOpen)
            return;

          this._invalidate(reason);
          ]]>
        </body>
      </method>

      <method name="_invalidate">
        <parameter name="reason"/>
        <body>
          <![CDATA[
          // collapsed if no matches
          this.richlistbox.collapsed = (this._matchCount == 0);

          // Update the richlistbox height.
          if (this._adjustHeightTimeout) {
            clearTimeout(this._adjustHeightTimeout);
          }
          if (this._shrinkTimeout) {
            clearTimeout(this._shrinkTimeout);
          }

          if (this.mPopupOpen) {
            delete this._adjustHeightOnPopupShown;
            this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
          } else {
            this._adjustHeightOnPopupShown = true;
          }

          this._currentIndex = 0;
          if (this._appendResultTimeout) {
            clearTimeout(this._appendResultTimeout);
          }
          this._appendCurrentResult(reason);
        ]]>
        </body>
      </method>

      <property name="maxResults" readonly="true">
        <getter>
          <![CDATA[
            // This is how many richlistitems will be kept around.
            // Note, this getter may be overridden, or instances
            // can have the nomaxresults attribute set to have no
            // limit.
            if (this.getAttribute("nomaxresults") == "true") {
              return Infinity;
            }

            return 20;
          ]]>
        </getter>
      </property>

      <property name="_matchCount" readonly="true">
        <getter>
          <![CDATA[
          return Math.min(this.mInput.controller.matchCount, this.maxResults);
          ]]>
        </getter>
      </property>

      <method name="_collapseUnusedItems">
        <body>
          <![CDATA[
            let existingItemsCount = this.richlistbox.childNodes.length;
            for (let i = this._matchCount; i < existingItemsCount; ++i) {
              let item = this.richlistbox.childNodes[i];

              item.collapsed = true;
              if (typeof item._onCollapse == "function") {
                item._onCollapse();
              }
            }
          ]]>
        </body>
      </method>

      <method name="adjustHeight">
        <body>
          <![CDATA[
          // Figure out how many rows to show
          let rows = this.richlistbox.childNodes;
          let numRows = Math.min(this._matchCount, this.maxRows, rows.length);

          this.removeAttribute("height");

          // Default the height to 0 if we have no rows to show
          let height = 0;
          if (numRows) {
            let firstRowRect = rows[0].getBoundingClientRect();
            if (this._rlbPadding == undefined) {
              let style = window.getComputedStyle(this.richlistbox);

              let transition = style.transitionProperty;
              this._rlbAnimated = transition && transition != "none";

              let paddingTop = parseInt(style.paddingTop) || 0;
              let paddingBottom = parseInt(style.paddingBottom) || 0;
              this._rlbPadding = paddingTop + paddingBottom;
            }

            if (numRows > this.maxRows) {
              // Set a fixed max-height to avoid flicker when growing the panel.
              let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect();
              let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top;
              this.richlistbox.style.maxHeight =
                visibleHeight + this._rlbPadding + "px";
            }

            // The class `forceHandleUnderflow` is for the item might need to
            // handle OverUnderflow or Overflow when the height of an item will
            // be changed dynamically.
            for (let i = 0; i < numRows; i++) {
              if (rows[i].classList.contains("forceHandleUnderflow")) {
                rows[i].handleOverUnderflow();
              }
            }

            let lastRowRect = rows[numRows - 1].getBoundingClientRect();
            // Calculate the height to have the first row to last row shown
            height = lastRowRect.bottom - firstRowRect.top +
                     this._rlbPadding;
          }

          let animate = this._rlbAnimated &&
                        this.getAttribute("dontanimate") != "true";
          let currentHeight = this.richlistbox.getBoundingClientRect().height;
          if (height > currentHeight) {
            // Grow immediately.
            if (animate) {
              this.richlistbox.removeAttribute("height");
              this.richlistbox.style.height = height + "px";
            } else {
              this.richlistbox.style.removeProperty("height");
              this.richlistbox.height = height;
            }
          } else {
            // Delay shrinking to avoid flicker.
            this._shrinkTimeout = setTimeout(() => {
              this._collapseUnusedItems();
              if (animate) {
                this.richlistbox.removeAttribute("height");
                this.richlistbox.style.height = height + "px";
              } else {
                this.richlistbox.style.removeProperty("height");
                this.richlistbox.height = height;
              }
            }, this.mInput.shrinkDelay);
          }
          ]]>
        </body>
      </method>

      <method name="_appendCurrentResult">
        <parameter name="invalidateReason"/>
        <body>
          <![CDATA[
          var controller = this.mInput.controller;
          var matchCount = this._matchCount;
          var existingItemsCount = this.richlistbox.childNodes.length;

          // Process maxRows per chunk to improve performance and user experience
          for (let i = 0; i < this.maxRows; i++) {
            if (this._currentIndex >= matchCount) {
              break;
            }
            let item;
            let reusable = false;
            let itemExists = this._currentIndex < existingItemsCount;

            let originalValue, originalText, originalType;
            let value = controller.getValueAt(this._currentIndex);
            let label = controller.getLabelAt(this._currentIndex);
            let comment = controller.getCommentAt(this._currentIndex);
            let style = controller.getStyleAt(this._currentIndex);
            let image = controller.getImageAt(this._currentIndex);
            // trim the leading/trailing whitespace
            let trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");

            if (itemExists) {
              item = this.richlistbox.childNodes[this._currentIndex];

              originalValue = item.getAttribute("ac-value");
              originalText = item.getAttribute("ac-text");
              originalType = item.getAttribute("originaltype");

              // All of types are reusable except for autofill-profile,
              // which has different structure of <content> and overrides
              // _adjustAcItem().
              reusable = originalType === style ||
                         (style !== "autofill-profile" && originalType !== "autofill-profile" &&
                         style !== "autofill-footer" && originalType !== "autofill-footer");
            } else {
              // need to create a new item
              item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
            }

            item.setAttribute("dir", this.style.direction);
            item.setAttribute("ac-image", image);
            item.setAttribute("ac-value", value);
            item.setAttribute("ac-label", label);
            item.setAttribute("ac-comment", comment);
            item.setAttribute("ac-text", trimmedSearchString);

            // Completely reuse the existing richlistitem for invalidation
            // due to new results, but only when: the item is the same, *OR*
            // we are about to replace the currently moused-over item, to
            // avoid surprising the user.
            let iface = Components.interfaces.nsIAutoCompletePopup;
            if (reusable &&
                originalText == trimmedSearchString &&
                invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
                (originalValue == value ||
                 this.richlistbox.mousedOverIndex === this._currentIndex)) {

              // try to re-use the existing item
              let reused = item._reuseAcItem();
              if (reused) {
                this._currentIndex++;
                continue;
              }
            } else {
              if (typeof item._cleanup == "function") {
                item._cleanup();
              }

              item.setAttribute("originaltype", style);
            }

            if (itemExists) {
              // Adjust only when the result's type is reusable for existing
              // item's. Otherwise, we might insensibly call old _adjustAcItem()
              // as new binding has not been attached yet.
              // We don't need to worry about switching to new binding, since
              // _adjustAcItem() will fired by its own constructor accordingly.
              if (reusable) {
                item._adjustAcItem();
              }
              item.collapsed = false;
            } else {
              // set the class at the end so we can use the attributes
              // in the xbl constructor
              item.className = "autocomplete-richlistitem";
              this.richlistbox.appendChild(item);
            }

            if (typeof item._onChanged == "function") {
              // The binding may have not been applied yet.
              setTimeout(() => {
                item._onChanged();
              }, 0);
            }

            this._currentIndex++;
          }

          if (typeof this.onResultsAdded == "function")
            this.onResultsAdded();

          if (this._currentIndex < matchCount) {
            // yield after each batch of items so that typing the url bar is
            // responsive
            this._appendResultTimeout = setTimeout(() => this._appendCurrentResult(), 0);
          }
        ]]>
        </body>
      </method>

      <!-- The x-coordinate relative to the leading edge of the window of the
           items' site icons (favicons). -->
      <property name="siteIconStart"
                onget="return this._siteIconStart;">
        <setter>
          <![CDATA[
          if (val != this._siteIconStart) {
            this._siteIconStart = val;
            for (let item of this.richlistbox.childNodes) {
              let changed = item.adjustSiteIconStart(val);
              if (changed) {
                item.handleOverUnderflow();
              }
            }
          }
          return val;
          ]]>
        </setter>
      </property>

      <property name="overflowPadding"
                onget="return Number(this.getAttribute('overflowpadding'))"
                readonly="true" />

      <method name="selectBy">
        <parameter name="aReverse"/>
        <parameter name="aPage"/>
        <body>
          <![CDATA[
          try {
            var amount = aPage ? 5 : 1;

            // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
            this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1);
            if (this.selectedIndex == -1) {
              this.input._focus();
            }
          } catch (ex) {
            // do nothing - occasionally timer-related js errors happen here
            // e.g. "this.selectedIndex has no properties", when you type fast and hit a
            // navigation key before this popup has opened
          }
            ]]>
        </body>
      </method>

      <field name="richlistbox">
        document.getAnonymousElementByAttribute(this, "anonid", "richlistbox");
      </field>

      <property name="view"
                onget="return this.mInput.controller;"
                onset="return val;"/>

    </implementation>
    <handlers>
      <handler event="popupshown">
        <![CDATA[
          if (this._adjustHeightOnPopupShown) {
            delete this._adjustHeightOnPopupShown;
            this.adjustHeight();
          }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="autocomplete-richlistitem-insecure-field" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem">
    <content align="center"
             onoverflow="this._onOverflow();"
             onunderflow="this._onUnderflow();">
      <xul:image anonid="type-icon"
                 class="ac-type-icon"
                 xbl:inherits="selected,current,type"/>
      <xul:image anonid="site-icon"
                 class="ac-site-icon"
                 xbl:inherits="src=image,selected,type"/>
      <xul:vbox class="ac-title"
                align="left"
                xbl:inherits="">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="title-text"
                           class="ac-title-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:vbox>
      <xul:hbox anonid="tags"
                class="ac-tags"
                align="center"
                xbl:inherits="selected">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="tags-text"
                           class="ac-tags-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
      <xul:hbox anonid="separator"
                class="ac-separator"
                align="center"
                xbl:inherits="selected,actiontype,type">
        <xul:description class="ac-separator-text">—</xul:description>
      </xul:hbox>
      <xul:hbox class="ac-url"
                align="center"
                xbl:inherits="selected,actiontype">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="url-text"
                           class="ac-url-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
      <xul:hbox class="ac-action"
                align="center"
                xbl:inherits="selected,actiontype">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="action-text"
                           class="ac-action-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
    </content>

    <handlers>
      <handler event="click" button="0"><![CDATA[
        let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
        window.openUILinkIn(baseURL + "insecure-password", "tab", {
          relatedToCurrent: true,
        });
      ]]></handler>
    </handlers>

    <implementation>
      <constructor><![CDATA[
        // Unlike other autocomplete items, the height of the insecure warning
        // increases by wrapping. So "forceHandleUnderflow" is for container to
        // recalculate an item's height and width.
        this.classList.add("forceHandleUnderflow");
      ]]></constructor>

      <property name="_learnMoreString">
        <getter><![CDATA[
          if (!this.__learnMoreString) {
            this.__learnMoreString =
              Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties").
              GetStringFromName("insecureFieldWarningLearnMore");
          }
          return this.__learnMoreString;
        ]]></getter>
      </property>

      <!-- Override _getSearchTokens to have the Learn More text emphasized -->
      <method name="_getSearchTokens">
        <parameter name="aSearch"/>
        <body>
          <![CDATA[
            return [this._learnMoreString.toLowerCase()];
          ]]>
        </body>
      </method>

    </implementation>
  </binding>

  <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">

    <content align="center"
             onoverflow="this._onOverflow();"
             onunderflow="this._onUnderflow();">
      <xul:image anonid="type-icon"
                 class="ac-type-icon"
                 xbl:inherits="selected,current,type"/>
      <xul:image anonid="site-icon"
                 class="ac-site-icon"
                 xbl:inherits="src=image,selected,type"/>
      <xul:hbox class="ac-title"
                align="center"
                xbl:inherits="selected">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="title-text"
                           class="ac-title-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
      <xul:hbox anonid="tags"
                class="ac-tags"
                align="center"
                xbl:inherits="selected">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="tags-text"
                           class="ac-tags-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
      <xul:hbox anonid="separator"
                class="ac-separator"
                align="center"
                xbl:inherits="selected,actiontype,type">
        <xul:description class="ac-separator-text">—</xul:description>
      </xul:hbox>
      <xul:hbox class="ac-url"
                align="center"
                xbl:inherits="selected,actiontype">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="url-text"
                           class="ac-url-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
      <xul:hbox class="ac-action"
                align="center"
                xbl:inherits="selected,actiontype">
        <xul:description class="ac-text-overflow-container">
          <xul:description anonid="action-text"
                           class="ac-action-text"
                           xbl:inherits="selected"/>
        </xul:description>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULSelectControlItemElement">
      <constructor>
        <![CDATA[
          this._typeIcon = document.getAnonymousElementByAttribute(
            this, "anonid", "type-icon"
          );
          this._siteIcon = document.getAnonymousElementByAttribute(
            this, "anonid", "site-icon"
          );
          this._titleText = document.getAnonymousElementByAttribute(
            this, "anonid", "title-text"
          );
          this._tags = document.getAnonymousElementByAttribute(
            this, "anonid", "tags"
          );
          this._tagsText = document.getAnonymousElementByAttribute(
            this, "anonid", "tags-text"
          );
          this._separator = document.getAnonymousElementByAttribute(
            this, "anonid", "separator"
          );
          this._urlText = document.getAnonymousElementByAttribute(
            this, "anonid", "url-text"
          );
          this._actionText = document.getAnonymousElementByAttribute(
            this, "anonid", "action-text"
          );
          this._adjustAcItem();
        ]]>
      </constructor>

      <method name="_cleanup">
        <body>
        <![CDATA[
          this.removeAttribute("url");
          this.removeAttribute("image");
          this.removeAttribute("title");
          this.removeAttribute("text");
          this.removeAttribute("displayurl");
        ]]>
        </body>
      </method>

      <property name="label" readonly="true">
        <getter>
          <![CDATA[
            // This property is a string that is read aloud by screen readers,
            // so it must not contain anything that should not be user-facing.

            let parts = [
              this.getAttribute("title"),
              this.getAttribute("displayurl"),
            ];
            let label = parts.filter(str => str).join(" ")

            // allow consumers that have extended popups to override
            // the label values for the richlistitems
            let panel = this.parentNode.parentNode;
            if (panel.createResultLabel) {
              return panel.createResultLabel(this, label);
            }

            return label;
          ]]>
        </getter>
      </property>

      <property name="_stringBundle">
        <getter><![CDATA[
          if (!this.__stringBundle) {
            this.__stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
          }
          return this.__stringBundle;
        ]]></getter>
      </property>

      <field name="_boundaryCutoff">null</field>

      <property name="boundaryCutoff" readonly="true">
        <getter>
          <![CDATA[
          if (!this._boundaryCutoff) {
            this._boundaryCutoff =
              Components.classes["@mozilla.org/preferences-service;1"].
              getService(Components.interfaces.nsIPrefBranch).
              getIntPref("toolkit.autocomplete.richBoundaryCutoff");
          }
          return this._boundaryCutoff;
          ]]>
        </getter>
      </property>

      <field name="_inOverflow">false</field>

      <method name="_onOverflow">
        <body>
          <![CDATA[
          this._inOverflow = true;
          this._handleOverflow();
          ]]>
        </body>
      </method>

      <method name="_onUnderflow">
        <body>
          <![CDATA[
          this._inOverflow = false;
          this._handleOverflow();
          ]]>
        </body>
      </method>

      <method name="_getBoundaryIndices">
        <parameter name="aText"/>
        <parameter name="aSearchTokens"/>
        <body>
          <![CDATA[
          // Short circuit for empty search ([""] == "")
          if (aSearchTokens == "")
            return [0, aText.length];

          // Find which regions of text match the search terms
          let regions = [];
          for (let search of Array.prototype.slice.call(aSearchTokens)) {
            let matchIndex = -1;
            let searchLen = search.length;

            // Find all matches of the search terms, but stop early for perf
            let lowerText = aText.substr(0, this.boundaryCutoff).toLowerCase();
            while ((matchIndex = lowerText.indexOf(search, matchIndex + 1)) >= 0) {
              regions.push([matchIndex, matchIndex + searchLen]);
            }
          }

          // Sort the regions by start position then end position
          regions = regions.sort((a, b) => {
            let start = a[0] - b[0];
            return (start == 0) ? a[1] - b[1] : start;
          });

          // Generate the boundary indices from each region
          let start = 0;
          let end = 0;
          let boundaries = [];
          let len = regions.length;
          for (let i = 0; i < len; i++) {
            // We have a new boundary if the start of the next is past the end
            let region = regions[i];
            if (region[0] > end) {
              // First index is the beginning of match
              boundaries.push(start);
              // Second index is the beginning of non-match
              boundaries.push(end);

              // Track the new region now that we've stored the previous one
              start = region[0];
            }

            // Push back the end index for the current or new region
            end = Math.max(end, region[1]);
          }

          // Add the last region
          boundaries.push(start);
          boundaries.push(end);

          // Put on the end boundary if necessary
          if (end < aText.length)
            boundaries.push(aText.length);

          // Skip the first item because it's always 0
          return boundaries.slice(1);
          ]]>
        </body>
      </method>

      <method name="_getSearchTokens">
        <parameter name="aSearch"/>
        <body>
          <![CDATA[
          let search = aSearch.toLowerCase();
          return search.split(/\s+/);
          ]]>
        </body>
      </method>

      <method name="_setUpDescription">
        <parameter name="aDescriptionElement"/>
        <parameter name="aText"/>
        <parameter name="aNoEmphasis"/>
        <body>
          <![CDATA[
          // Get rid of all previous text
          if (!aDescriptionElement) {
            return;
          }
          while (aDescriptionElement.hasChildNodes())
            aDescriptionElement.firstChild.remove();

          // If aNoEmphasis is specified, don't add any emphasis
          if (aNoEmphasis) {
            aDescriptionElement.appendChild(document.createTextNode(aText));
            return;
          }

          // Get the indices that separate match and non-match text
          let search = this.getAttribute("text");
          let tokens = this._getSearchTokens(search);
          let indices = this._getBoundaryIndices(aText, tokens);

          this._appendDescriptionSpans(indices, aText, aDescriptionElement,
                                       aDescriptionElement);
          ]]>
        </body>
      </method>

      <method name="_appendDescriptionSpans">
        <parameter name="indices"/>
        <parameter name="text"/>
        <parameter name="spansParentElement"/>
        <parameter name="descriptionElement"/>
        <body>
          <![CDATA[
          let next;
          let start = 0;
          let len = indices.length;
          // Even indexed boundaries are matches, so skip the 0th if it's empty
          for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
            next = indices[i];
            let spanText = text.substr(start, next - start);
            start = next;

            if (i % 2 == 0) {
              // Emphasize the text for even indices
              let span = spansParentElement.appendChild(
                document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
              this._setUpEmphasisSpan(span, descriptionElement);
              span.textContent = spanText;
            } else {
              // Otherwise, it's plain text
              spansParentElement.appendChild(document.createTextNode(spanText));
            }
          }
          ]]>
        </body>
      </method>

      <method name="_setUpTags">
        <parameter name="tags"/>
        <body>
          <![CDATA[
          while (this._tagsText.hasChildNodes()) {
            this._tagsText.firstChild.remove();
          }

          let anyTagsMatch = false;

          // Include only tags that match the search string.
          for (let tag of tags) {
            // Check if the tag matches the search string.
            let search = this.getAttribute("text");
            let tokens = this._getSearchTokens(search);
            let indices = this._getBoundaryIndices(tag, tokens);

            if (indices.length == 2 &&
                indices[0] == 0 &&
                indices[1] == tag.length) {
              // The tag doesn't match the search string, so don't include it.
              continue;
            }

            anyTagsMatch = true;

            let tagSpan =
              document.createElementNS("http://www.w3.org/1999/xhtml", "span");
            tagSpan.classList.add("ac-tag");
            this._tagsText.appendChild(tagSpan);

            this._appendDescriptionSpans(indices, tag, tagSpan, this._tagsText);
          }

          return anyTagsMatch;
          ]]>
        </body>
      </method>

      <method name="_setUpEmphasisSpan">
        <parameter name="aSpan"/>
        <parameter name="aDescriptionElement"/>
        <body>
          <![CDATA[
          aSpan.classList.add("ac-emphasize-text");
          switch (aDescriptionElement) {
            case this._titleText:
              aSpan.classList.add("ac-emphasize-text-title");
              break;
            case this._tagsText:
              aSpan.classList.add("ac-emphasize-text-tag");
              break;
            case this._urlText:
              aSpan.classList.add("ac-emphasize-text-url");
              break;
            case this._actionText:
              aSpan.classList.add("ac-emphasize-text-action");
              break;
          }
          ]]>
        </body>
      </method>

      <!--
        This will generate an array of emphasis pairs for use with
        _setUpEmphasisedSections(). Each pair is a tuple (array) that
        represents a block of text - containing the text of that block, and a
        boolean for whether that block should have an emphasis styling applied
        to it.

        These pairs are generated by parsing a localised string (aSourceString)
        with parameters, in the format that is used by
        nsIStringBundle.formatStringFromName():

          "textA %1$S textB textC %2$S"

        Or:

          "textA %S"

        Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided
        replacement strings. These are specified an array of tuples
        (aReplacements), each containing the replacement text and a boolean for
        whether that text should have an emphasis styling applied. This is used
        as a 1-based array - ie, "%1$S" is replaced by the item in the first
        index of aReplacements, "%2$S" by the second, etc. "%S" will always
        match the first index.
      -->
      <method name="_generateEmphasisPairs">
        <parameter name="aSourceString"/>
        <parameter name="aReplacements"/>
        <body>
          <![CDATA[
            let pairs = [];

            // Split on %S, %1$S, %2$S, etc. ie:
            //   "textA %S"
            //     becomes ["textA ", "%S"]
            //   "textA %1$S textB textC %2$S"
            //     becomes ["textA ", "%1$S", " textB textC ", "%2$S"]
            let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/);

            for (let part of parts) {
              // The above regex will actually give us an empty string at the
              // end - we don't want that, as we don't want to later generate an
              // empty text node for it.
              if (part.length === 0)
                continue;

              // Determine if this token is a replacement token or a normal text
              // token. If it is a replacement token, we want to extract the
              // numerical number. However, we still want to match on "$S".
              let match = part.match(/^%(?:([0-9]+)\$)?S$/);

              if (match) {
                // "%S" doesn't have a numerical number in it, but will always
                // be assumed to be 1. Furthermore, the input string specifies
                // these with a 1-based index, but we want a 0-based index.
                let index = (match[1] || 1) - 1;

                if (index >= 0 && index < aReplacements.length) {
                  pairs.push([...aReplacements[index]]);
                }
              } else {
                pairs.push([part]);
              }
            }

            return pairs;
          ]]>
        </body>
      </method>

      <!--
        _setUpEmphasisedSections() has the same use as _setUpDescription,
        except instead of taking a string and highlighting given tokens, it takes
        an array of pairs generated by _generateEmphasisPairs(). This allows
        control over emphasising based on specific blocks of text, rather than
        search for substrings.
      -->
      <method name="_setUpEmphasisedSections">
        <parameter name="aDescriptionElement"/>
        <parameter name="aTextPairs"/>
        <body>
          <![CDATA[
          // Get rid of all previous text
          while (aDescriptionElement.hasChildNodes())
            aDescriptionElement.firstChild.remove();

          for (let [text, emphasise] of aTextPairs) {
            if (emphasise) {
              let span = aDescriptionElement.appendChild(
                document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
              span.textContent = text;
              switch (emphasise) {
                case "match":
                  this._setUpEmphasisSpan(span, aDescriptionElement);
                  break;
              }
            } else {
              aDescriptionElement.appendChild(document.createTextNode(text));
            }
          }
          ]]>
        </body>
      </method>

      <field name="_textToSubURI">null</field>
      <method name="_unescapeUrl">
        <parameter name="url"/>
        <body>
          <![CDATA[
          if (!this._textToSubURI) {
            this._textToSubURI =
              Components.classes["@mozilla.org/intl/texttosuburi;1"]
                        .getService(Components.interfaces.nsITextToSubURI);
          }
          return this._textToSubURI.unEscapeURIForUI("UTF-8", url);
          ]]>
        </body>
      </method>

      <method name="_onChanged">
        <body>
          <![CDATA[
            let popup = this.parentNode.parentNode;
            let iconChanged = this.adjustSiteIconStart(popup._siteIconStart);

            if (iconChanged) {
              this.handleOverUnderflow();
            }
          ]]>
        </body>
      </method>


      <method name="_reuseAcItem">
        <body>
          <![CDATA[
            let action = this._parseActionUrl(this.getAttribute("url"));
            let popup = this.parentNode.parentNode;

            // If the item is a searchengine action, then it should
            // only be reused if the engine name is the same as the
            // popup's override engine name, if any.
            if (!action ||
                action.type != "searchengine" ||
                !popup.overrideSearchEngineName ||
                action.params.engineName == popup.overrideSearchEngineName) {

              this.collapsed = false;
              // Call adjustSiteIconStart only after setting collapsed=
              // false.  The calculations it does may be wrong otherwise.
              this.adjustSiteIconStart(popup._siteIconStart);
              // The popup may have changed size between now and the last
              // time the item was shown, so always handle over/underflow.
              this.handleOverUnderflow();

              return true;
            }

            return false;
          ]]>
        </body>
      </method>


      <method name="_adjustAcItem">
        <body>
          <![CDATA[
          this.setAttribute("url", this.getAttribute("ac-value"));
          this.setAttribute("image", this.getAttribute("ac-image"));
          this.setAttribute("title", this.getAttribute("ac-comment"));
          this.setAttribute("text", this.getAttribute("ac-text"));

          let popup = this.parentNode.parentNode;
          if (!popup.popupOpen) {
            // Removing the max-width and resetting it later when overflow is
            // handled is jarring when the item is visible, so skip this when
            // the popup is open.
            this._removeMaxWidths();
          }

          let title = this.getAttribute("title");
          let titleLooksLikeUrl = false;

          let displayUrl;
          let originalUrl = this.getAttribute("url");
          let emphasiseUrl = true;

          let type = this.getAttribute("originaltype");
          let types = new Set(type.split(/\s+/));
          let initialTypes = new Set(types);
          // Remove types that should ultimately not be in the `type` string.
          types.delete("action");
          types.delete("autofill");
          types.delete("heuristic");
          type = [...types][0] || "";

          let action;

          if (initialTypes.has("autofill")) {
            // Treat autofills as visiturl actions.
            action = {
              type: "visiturl",
              params: {
                url: this.getAttribute("title"),
              },
            };
          }

          this.removeAttribute("actiontype");
          this.classList.remove("overridable-action");

          // If the type includes an action, set up the item appropriately.
          if (initialTypes.has("action") || action) {
            action = action || this._parseActionUrl(originalUrl);
            this.setAttribute("actiontype", action.type);

            if (action.type == "switchtab") {
              this.classList.add("overridable-action");
              displayUrl = this._unescapeUrl(action.params.url);
              let desc = this._stringBundle.GetStringFromName("switchToTab2");
              this._setUpDescription(this._actionText, desc, true);
            } else if (action.type == "remotetab") {
              displayUrl = this._unescapeUrl(action.params.url);
              let desc = action.params.deviceName;
              this._setUpDescription(this._actionText, desc, true);
            } else if (action.type == "searchengine") {
              emphasiseUrl = false;

              // The order here is not localizable, we default to appending
              // "- Search with Engine" to the search string, to be able to
              // properly generate emphasis pairs. That said, no localization
              // changed the order while it was possible, so doesn't look like
              // there's a strong need for that.
              let {engineName, searchSuggestion, searchQuery} = action.params;

              // Override the engine name if the popup defines an override.
              let override = popup.overrideSearchEngineName;
              if (override && override != engineName) {
                engineName = override;
                action.params.engineName = override;
                let newURL =
                  PlacesUtils.mozActionURI(action.type, action.params);
                this.setAttribute("url", newURL);
              }

              let engineStr =
                this._stringBundle.formatStringFromName("searchWithEngine",
                                                        [engineName], 1);
              this._setUpDescription(this._actionText, engineStr, true);

              // Make the title by generating an array of pairs and its
              // corresponding interpolation string (e.g., "%1$S") to pass to
              // _generateEmphasisPairs.
              let pairs;
              if (searchSuggestion) {
                // Check if the search query appears in the suggestion.  It may
                // not.  If it does, then emphasize the query in the suggestion
                // and otherwise just include the suggestion without emphasis.
                let idx = searchSuggestion.indexOf(searchQuery);
                if (idx >= 0) {
                  pairs = [
                    [searchSuggestion.substring(0, idx), ""],
                    [searchQuery, "match"],
                    [searchSuggestion.substring(idx + searchQuery.length), ""],
                  ];
                } else {
                  pairs = [
                    [searchSuggestion, ""],
                  ];
                }
              } else {
                pairs = [
                  [searchQuery, ""],
                ];
              }
              let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join("");
              title = this._generateEmphasisPairs(interpStr, pairs);

              // If this is a default search match, we remove the image so we
              // can style it ourselves with a generic search icon.
              // We don't do this when matching an aliased search engine,
              // because the icon helps with recognising which engine will be
              // used (when using the default engine, we don't need that
              // recognition).
              if (!action.params.alias && !initialTypes.has("favicon")) {
                this.removeAttribute("image");
              }
            } else if (action.type == "visiturl") {
              emphasiseUrl = false;
              displayUrl = this._unescapeUrl(action.params.url);
              title = displayUrl;
              titleLooksLikeUrl = true;
              let visitStr = this._stringBundle.GetStringFromName("visit");
              this._setUpDescription(this._actionText, visitStr, true);
            } else if (action.type == "extension") {
              let content = action.params.content;
              displayUrl = content;
              this._setUpDescription(this._actionText, content, true);
            }
          }

          if (!displayUrl) {
            let input = popup.input;
            let url = typeof(input.trimValue) == "function" ?
                      input.trimValue(originalUrl) :
                      originalUrl;
            displayUrl = this._unescapeUrl(url);
          }
          // For performance reasons we may want to limit the displayUrl size.
          if (popup.textRunsMaxLen) {
            displayUrl = displayUrl.substr(0, popup.textRunsMaxLen);
          }
          this.setAttribute("displayurl", displayUrl);

          // Show the domain as the title if we don't have a title.
          if (!title) {
            title = displayUrl;
            titleLooksLikeUrl = true;
            try {
              let uri = Services.io.newURI(originalUrl);
              // Not all valid URLs have a domain.
              if (uri.host)
                title = uri.host;
            } catch (e) {}
          }

          this._tags.setAttribute("empty", "true");

          if (type == "tag" || type == "bookmark-tag") {
            // The title is separated from the tags by an endash
            let tags;
            [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);

            // Each tag is split by a comma in an undefined order, so sort it
            let sortedTags = tags.split(/\s*,\s*/).sort((a, b) => {
              return a.localeCompare(a);
            });

            let anyTagsMatch = this._setUpTags(sortedTags);
            if (anyTagsMatch) {
              this._tags.removeAttribute("empty");
            }
            if (type == "bookmark-tag") {
              type = "bookmark";
            }
          } else if (type == "keyword") {
            // Note that this is a moz-action with action.type == keyword.
            emphasiseUrl = false;
            let keywordArg = this.getAttribute("text").replace(/^[^\s]+\s*/, "");
            if (!keywordArg) {
              // Treat keyword searches without arguments as visiturl actions.
              type = "visiturl";
              this.setAttribute("actiontype", "visiturl");
              let visitStr = this._stringBundle.GetStringFromName("visit");
              this._setUpDescription(this._actionText, visitStr, true);
            } else {
              let pairs = [[title, ""], [keywordArg, "match"]];
              let interpStr =
                this._stringBundle.GetStringFromName("bookmarkKeywordSearch");
              title = this._generateEmphasisPairs(interpStr, pairs);
              // The action box will be visible since this is a moz-action, but
              // we want it to appear as if it were not visible, so set its text
              // to the empty string.
              this._setUpDescription(this._actionText, "", false);
            }
          }

          this.setAttribute("type", type);

          if (titleLooksLikeUrl) {
            this._titleText.setAttribute("lookslikeurl", "true");
          } else {
            this._titleText.removeAttribute("lookslikeurl");
          }

          if (Array.isArray(title)) {
            // For performance reasons we may want to limit the title size.
            if (popup.textRunsMaxLen) {
              title = title.map(t => t.substr(0, popup.textRunsMaxLen));
            }
            this._setUpEmphasisedSections(this._titleText, title);
          } else {
            // For performance reasons we may want to limit the title size.
            if (popup.textRunsMaxLen) {
              title = title.substr(0, popup.textRunsMaxLen);
            }
            this._setUpDescription(this._titleText, title, false);
          }
          this._setUpDescription(this._urlText, displayUrl, !emphasiseUrl);

          if (this._inOverflow) {
            this._handleOverflow();
          }
          ]]>
        </body>
      </method>

      <method name="_removeMaxWidths">
        <body>
          <![CDATA[
          this._titleText.style.removeProperty("max-width");
          this._tagsText.style.removeProperty("max-width");
          this._urlText.style.removeProperty("max-width");
          this._actionText.style.removeProperty("max-width");
          ]]>
        </body>
      </method>

      <!-- Sets the x-coordinate of the leading edge of the site icon (favicon)
           relative the the leading edge of the window.
           @param newStart The new x-coordinate, relative to the leading edge of
                  the window.  Pass undefined to reset the icon's position to
                  whatever is specified in CSS.
           @return True if the icon's position changed, false if not. -->
      <method name="adjustSiteIconStart">
        <parameter name="newStart"/>
        <body>
          <![CDATA[
          if (typeof(newStart) != "number") {
            this._typeIcon.style.removeProperty("margin-inline-start");
            return true;
          }
          let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
          let rect = utils.getBoundsWithoutFlushing(this._siteIcon);

          let dir = this.getAttribute("dir");
          let delta = dir == "rtl" ? rect.right - newStart
                                   : newStart - rect.left;
          let px = this._typeIcon.style.marginInlineStart;
          if (!px) {
            // Allow margin-inline-start not to be specified in CSS initially.
            let style = window.getComputedStyle(this._typeIcon);
            px = dir == "rtl" ? style.marginRight : style.marginLeft;
          }
          let typeIconStart = Number(px.substr(0, px.length - 2));
          this._typeIcon.style.marginInlineStart = (typeIconStart + delta) + "px";
          return delta > 0;
          ]]>
        </body>
      </method>

      <!-- This method truncates the displayed strings as necessary. -->
      <method name="_handleOverflow">
        <body><![CDATA[
          let itemRect = this.parentNode.getBoundingClientRect();
          let titleRect = this._titleText.getBoundingClientRect();
          let tagsRect = this._tagsText.getBoundingClientRect();
          let separatorRect = this._separator.getBoundingClientRect();
          let urlRect = this._urlText.getBoundingClientRect();
          let actionRect = this._actionText.getBoundingClientRect();
          let separatorURLActionWidth =
            separatorRect.width + Math.max(urlRect.width, actionRect.width);

          // Total width for the title and URL/action is the width of the item
          // minus the start of the title text minus a little optional extra padding.
          // This extra padding amount is basically arbitrary but keeps the text
          // from getting too close to the popup's edge.
          let dir = this.getAttribute("dir");
          let titleStart = dir == "rtl" ? itemRect.right - titleRect.right
                                        : titleRect.left - itemRect.left;

          let popup = this.parentNode.parentNode;
          let itemWidth = itemRect.width - titleStart - popup.overflowPadding;

          if (this._tags.hasAttribute("empty")) {
            // The tags box is not displayed in this case.
            tagsRect.width = 0;
          }

          let titleTagsWidth = titleRect.width + tagsRect.width;
          if (titleTagsWidth + separatorURLActionWidth > itemWidth) {
            // Title + tags + URL/action overflows the item width.

            // The percentage of the item width allocated to the title and tags.
            let titleTagsPct = 0.66;

            let titleTagsAvailable = itemWidth - separatorURLActionWidth;
            let titleTagsMaxWidth = Math.max(
              titleTagsAvailable,
              itemWidth * titleTagsPct
            );
            if (titleTagsWidth > titleTagsMaxWidth) {
              // Title + tags overflows the max title + tags width.

              // The percentage of the title + tags width allocated to the
              // title.
              let titlePct = 0.33;

              let titleAvailable = titleTagsMaxWidth - tagsRect.width;
              let titleMaxWidth = Math.max(
                titleAvailable,
                titleTagsMaxWidth * titlePct
              );
              let tagsAvailable = titleTagsMaxWidth - titleRect.width;
              let tagsMaxWidth = Math.max(
                tagsAvailable,
                titleTagsMaxWidth * (1 - titlePct)
              );
              this._titleText.style.maxWidth = titleMaxWidth + "px";
              this._tagsText.style.maxWidth = tagsMaxWidth + "px";
            }
            let urlActionMaxWidth = Math.max(
              itemWidth - titleTagsWidth,
              itemWidth * (1 - titleTagsPct)
            );
            urlActionMaxWidth -= separatorRect.width;
            this._urlText.style.maxWidth = urlActionMaxWidth + "px";
            this._actionText.style.maxWidth = urlActionMaxWidth + "px";
          }
        ]]></body>
      </method>

      <method name="handleOverUnderflow">
        <body>
          <![CDATA[
          this._removeMaxWidths();
          this._handleOverflow();
          ]]>
        </body>
      </method>

      <method name="_parseActionUrl">
        <parameter name="aUrl"/>
        <body><![CDATA[
          if (!aUrl.startsWith("moz-action:"))
            return null;

          // URL is in the format moz-action:ACTION,PARAMS
          // Where PARAMS is a JSON encoded object.
          let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);

          let action = {
            type,
          };

          try {
            action.params = JSON.parse(params);
            for (let key in action.params) {
              action.params[key] = decodeURIComponent(action.params[key]);
            }
          } catch (e) {
            // If this failed, we assume that params is not a JSON object, and
            // is instead just a flat string. This may happen for legacy
            // search components.
            action.params = {
              url: params,
            }
          }

          return action;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <!--
        This overrides listitem's mousedown handler because we want to set the
        selected item even when the shift or accel keys are pressed.
      -->
      <handler event="mousedown"><![CDATA[
        // Call this.control only once since it's not a simple getter.
        let control = this.control;
        if (!control || control.disabled) {
          return;
        }
        if (!this.selected) {
          control.selectItem(this);
        }
        control.currentItem = this;
      ]]></handler>

      <handler event="mouseover"><![CDATA[
        // The point of implementing this handler is to allow drags to change
        // the selected item.  If the user mouses down on an item, it becomes
        // selected.  If they then drag the mouse to another item, select it.
        // Handle all three primary mouse buttons: right, left, and wheel, since
        // all three change the selection on mousedown.
        let mouseDown = event.buttons & 0b111;
        if (!mouseDown) {
          return;
        }
        // Call this.control only once since it's not a simple getter.
        let control = this.control;
        if (!control || control.disabled) {
          return;
        }
        if (!this.selected) {
          control.selectItem(this);
        }
        control.currentItem = this;
      ]]></handler>
    </handlers>
  </binding>

  <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
    <content>
      <children includes="treecols"/>
      <xul:treerows class="autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
        <children/>
      </xul:treerows>
    </content>
  </binding>

  <binding id="autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
    <implementation>
      <field name="mLastMoveTime">Date.now()</field>
      <field name="mousedOverIndex">-1</field>
    </implementation>
    <handlers>
      <handler event="mouseup">
        <![CDATA[
        // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
        let item = event.originalTarget;
        while (item && item.localName != "richlistitem") {
          item = item.parentNode;
        }

        if (!item)
          return;

        this.parentNode.onPopupClick(event);
      ]]>
      </handler>

      <handler event="mousemove">
        <![CDATA[
        if (Date.now() - this.mLastMoveTime <= 30) {
          return;
        }

        let item = event.target;
        while (item && item.localName != "richlistitem") {
          item = item.parentNode;
        }

        if (!item) {
          return;
        }

        let index = this.getIndexOfItem(item);

        this.mousedOverIndex = index;

        if (item.selectedByMouseOver) {
          this.selectedIndex = index;
        }

        this.mLastMoveTime = Date.now();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="autocomplete-treebody">
    <implementation>
      <field name="mLastMoveTime">Date.now()</field>
    </implementation>

    <handlers>
      <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/>

      <handler event="mousedown"><![CDATA[
         var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
         if (rc != this.parentNode.currentIndex)
            this.parentNode.view.selection.select(rc);
      ]]></handler>

      <handler event="mousemove"><![CDATA[
        if (event.defaultPrevented) {
          // Allow bindings that extend this one to cancel the event so that
          // nothing is selected.
          return;
        }
        if (Date.now() - this.mLastMoveTime > 30) {
         var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
         if (rc != this.parentNode.currentIndex)
            this.parentNode.view.selection.select(rc);
         this.mLastMoveTime = Date.now();
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="autocomplete-treerows">
    <content>
      <xul:hbox flex="1" class="tree-bodybox">
        <children/>
      </xul:hbox>
      <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/>
    </content>
  </binding>

  <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
    <handlers>
      <handler event="mousedown" button="0"><![CDATA[
        document.getBindingParent(this).toggleHistoryPopup();
      ]]></handler>
    </handlers>
  </binding>

</bindings>
PK
!<2chrome/toolkit/content/global/bindings/browser.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="browserBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="browser" extends="xul:browser" role="outerdoc">
    <content clickthrough="never">
      <children/>
    </content>
    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIBrowser">
      <property name="autoscrollEnabled">
        <getter>
          <![CDATA[
            if (this.getAttribute("autoscroll") == "false")
              return false;

            return this.mPrefs.getBoolPref("general.autoScroll", true);
          ]]>
        </getter>
      </property>

      <property name="canGoBack"
                onget="return this.webNavigation.canGoBack;"
                readonly="true"/>

      <property name="canGoForward"
                onget="return this.webNavigation.canGoForward;"
                readonly="true"/>

      <method name="_wrapURIChangeCall">
        <parameter name="fn"/>
        <body>
          <![CDATA[
            if (!this.isRemoteBrowser) {
              this.inLoadURI = true;
              try {
                fn();
              } finally {
                this.inLoadURI = false;
              }
            } else {
              fn();
            }
          ]]>
        </body>
      </method>


      <method name="goBack">
        <body>
          <![CDATA[
            var webNavigation = this.webNavigation;
            if (webNavigation.canGoBack)
              this._wrapURIChangeCall(() => webNavigation.goBack());
          ]]>
        </body>
      </method>

      <method name="goForward">
        <body>
          <![CDATA[
            var webNavigation = this.webNavigation;
            if (webNavigation.canGoForward)
              this._wrapURIChangeCall(() => webNavigation.goForward());
          ]]>
        </body>
      </method>

      <method name="reload">
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
            this.reloadWithFlags(flags);
          ]]>
        </body>
      </method>

      <method name="reloadWithFlags">
        <parameter name="aFlags"/>
        <body>
          <![CDATA[
            this.webNavigation.reload(aFlags);
          ]]>
        </body>
      </method>

      <method name="stop">
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.STOP_ALL;
            this.webNavigation.stop(flags);
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURI">
        <parameter name="aURI"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
            this._wrapURIChangeCall(() =>
              this.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset));
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURIWithFlags">
        <parameter name="aURI"/>
        <parameter name="aFlags"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <parameter name="aPostData"/>
        <body>
          <![CDATA[
            if (!aURI)
              aURI = "about:blank";

            var aReferrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_UNSET;
            var aTriggeringPrincipal;

            // Check for loadURIWithFlags(uri, { ... });
            var params = arguments[1];
            if (params && typeof(params) == "object") {
              aFlags = params.flags;
              aReferrerURI = params.referrerURI;
              if ("referrerPolicy" in params) {
                aReferrerPolicy = params.referrerPolicy;
              }
              if ("triggeringPrincipal" in params) {
                aTriggeringPrincipal = params.triggeringPrincipal;
              }
              aCharset = params.charset;
              aPostData = params.postData;
            }

            this._wrapURIChangeCall(() =>
              this.webNavigation.loadURIWithOptions(
                  aURI, aFlags, aReferrerURI, aReferrerPolicy,
                  aPostData, null, null, aTriggeringPrincipal));
          ]]>
        </body>
      </method>

      <method name="goHome">
        <body>
          <![CDATA[
            try {
              this.loadURI(this.homePage);
            } catch (e) {
            }
          ]]>
        </body>
      </method>

      <property name="homePage">
        <getter>
          <![CDATA[
            var uri;

            if (this.hasAttribute("homepage"))
              uri = this.getAttribute("homepage");
            else
              uri = "http://www.mozilla.org/"; // widget pride

            return uri;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.setAttribute("homepage", val);
            return val;
          ]]>
        </setter>
      </property>

      <method name="gotoIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
          ]]>
        </body>
      </method>

      <property name="currentURI" readonly="true">
       <getter><![CDATA[
          if (this.webNavigation) {
            return this.webNavigation.currentURI;
          }
          return null;
       ]]>
       </getter>
      </property>

      <!--
        Used by session restore to ensure that currentURI is set so
        that switch-to-tab works before the tab is fully
        restored. This function also invokes onLocationChanged
        listeners in tabbrowser.xml.
      -->
      <method name="_setCurrentURI">
        <parameter name="aURI"/>
        <body><![CDATA[
          this.docShell.setCurrentURI(aURI);
        ]]></body>
      </method>

      <property name="documentURI"
                onget="return this.contentDocument.documentURIObject;"
                readonly="true"/>

      <property name="documentContentType"
                onget="return this.contentDocument ? this.contentDocument.contentType : null;"
                readonly="true"/>

      <property name="preferences"
                onget="return this.mPrefs.QueryInterface(Components.interfaces.nsIPrefService);"
                readonly="true"/>

      <!--
        Weak reference to an optional frame loader that can be used to influence
        process selection for this browser.
        See nsIBrowser.sameProcessAsFrameLoader.
      -->
      <field name="_sameProcessAsFrameLoader">null</field>
      <property name="sameProcessAsFrameLoader">
        <getter><![CDATA[
          return this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get();
        ]]></getter>
        <setter><![CDATA[
          this._sameProcessAsFrameLoader = Components.utils.getWeakReference(val);
        ]]></setter>
      </property>

      <field name="_docShell">null</field>

      <property name="docShell" readonly="true">
        <getter><![CDATA[
          if (this._docShell)
            return this._docShell;

          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          if (!frameLoader)
            return null;
          this._docShell = frameLoader.docShell;
          return this._docShell;
        ]]></getter>
      </property>

      <field name="_loadContext">null</field>

      <property name="loadContext" readonly="true">
        <getter><![CDATA[
          if (this._loadContext)
            return this._loadContext;

          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          if (!frameLoader)
            return null;
          this._loadContext = frameLoader.loadContext;
          return this._loadContext;
        ]]></getter>
      </property>

      <property name="autoCompletePopup"
                onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
                readonly="true"/>

      <property name="dateTimePicker"
                onget="return document.getElementById(this.getAttribute('datetimepicker'))"
                readonly="true"/>

      <property name="docShellIsActive">
        <getter>
          <![CDATA[
            return this.docShell && this.docShell.isActive;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (this.docShell)
              return this.docShell.isActive = val;
            return false;
          ]]>
        </setter>
      </property>

      <method name="preserveLayers">
        <parameter name="preserve"/>
        <body>
          // Only useful for remote browsers.
        </body>
      </method>

      <property name="imageDocument"
                readonly="true">
        <getter>
          <![CDATA[
            var document = this.contentDocument;
            if (!document || !(document instanceof Components.interfaces.nsIImageDocument))
              return null;

            try {
                return {width: document.imageRequest.image.width, height: document.imageRequest.image.height };
            } catch (e) {}
            return null;
          ]]>
        </getter>
      </property>

      <property name="isRemoteBrowser"
                onget="return (this.getAttribute('remote') == 'true');"
                readonly="true"/>

      <property name="remoteType"
                readonly="true">
        <getter>
          <![CDATA[
            if (!this.isRemoteBrowser) {
              return null;
            }

            let remoteType = this.getAttribute("remoteType");
            if (remoteType) {
              return remoteType;
            }

            let E10SUtils = Components.utils.import("resource://gre/modules/E10SUtils.jsm", {}).E10SUtils;
            return E10SUtils.DEFAULT_REMOTE_TYPE;
          ]]>
        </getter>
      </property>

      <property name="messageManager"
                readonly="true">
        <getter>
          <![CDATA[
            var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            if (!owner.frameLoader) {
              return null;
            }
            return owner.frameLoader.messageManager;
          ]]>
        </getter>

      </property>

      <field name="_webNavigation">null</field>

      <property name="webNavigation"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._webNavigation) {
            if (!this.docShell) {
              return null;
            }
            this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
          }
          return this._webNavigation;
        ]]>
        </getter>
      </property>

      <field name="_webBrowserFind">null</field>

      <property name="webBrowserFind"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._webBrowserFind)
            this._webBrowserFind = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);
          return this._webBrowserFind;
        ]]>
        </getter>
      </property>

      <method name="getTabBrowser">
        <body>
          <![CDATA[
            for (let node = this.parentNode; node instanceof Element; node = node.parentNode) {
              if (node.localName == "tabbrowser")
                return node;
            }
            return null;
          ]]>
        </body>
      </method>

      <field name="_finder">null</field>

      <property name="finder" readonly="true">
        <getter><![CDATA[
          if (!this._finder) {
            if (!this.docShell)
              return null;

            let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
            this._finder = new Finder(this.docShell);
          }
          return this._finder;
        ]]></getter>
      </property>

      <field name="_fastFind">null</field>
      <property name="fastFind" readonly="true">
        <getter><![CDATA[
          if (!this._fastFind) {
            if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
              return null;

            var tabBrowser = this.getTabBrowser();
            if (tabBrowser && "fastFind" in tabBrowser)
              return this._fastFind = tabBrowser.fastFind;

            if (!this.docShell)
              return null;

            this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
                                       .createInstance(Components.interfaces.nsITypeAheadFind);
            this._fastFind.init(this.docShell);
          }
          return this._fastFind;
        ]]></getter>
      </property>

      <property name="outerWindowID" readonly="true">
        <getter><![CDATA[
          return this.contentWindow
                     .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                     .getInterface(Components.interfaces.nsIDOMWindowUtils)
                     .outerWindowID;
        ]]></getter>
      </property>

      <property name="innerWindowID" readonly="true">
        <getter><![CDATA[
          try {
            return this.contentWindow
                       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                       .getInterface(Components.interfaces.nsIDOMWindowUtils)
                       .currentInnerWindowID;
          } catch (e) {
            if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
              throw e;
            }
            return null;
          }
        ]]></getter>
      </property>

      <field name="_lastSearchString">null</field>

      <property name="webProgress"
                readonly="true"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>

      <field name="_contentWindow">null</field>

      <property name="contentWindow"
                readonly="true"
                onget="return this._contentWindow || (this._contentWindow = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow));"/>

      <property name="contentWindowAsCPOW"
                readonly="true"
                onget="return this.contentWindow;"/>

      <property name="sessionHistory"
                onget="return this.webNavigation.sessionHistory;"
                readonly="true"/>

      <property name="markupDocumentViewer"
                onget="return this.docShell.contentViewer;"
                readonly="true"/>

      <property name="contentViewerEdit"
                onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerEdit);"
                readonly="true"/>

      <property name="contentViewerFile"
                onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerFile);"
                readonly="true"/>

      <property name="contentDocument"
                onget="return this.webNavigation.document;"
                readonly="true"/>

      <property name="contentDocumentAsCPOW"
                onget="return this.contentDocument;"
                readonly="true"/>

      <property name="contentTitle"
                onget="return this.contentDocument.title;"
                readonly="true"/>

      <property name="characterSet"
                onget="return this.docShell.charset;">
        <setter><![CDATA[
          this.docShell.charset = val;
          this.docShell.gatherCharsetMenuTelemetry();
        ]]></setter>
      </property>

      <property name="mayEnableCharacterEncodingMenu"
                onget="return this.docShell.mayEnableCharacterEncodingMenu;"
                readonly="true"/>

      <property name="contentPrincipal"
                onget="return this.contentDocument.nodePrincipal;"
                readonly="true"/>

      <property name="showWindowResizer"
                onset="if (val) this.setAttribute('showresizer', 'true');
                       else this.removeAttribute('showresizer');
                       return val;"
                onget="return this.getAttribute('showresizer') == 'true';"/>

      <property name="manifestURI"
                readonly="true">
        <getter><![CDATA[
          return this.contentDocument.documentElement &&
                 this.contentDocument.documentElement.getAttribute("manifest");
        ]]></getter>
      </property>

      <property name="fullZoom">
        <getter><![CDATA[
          return this.markupDocumentViewer.fullZoom;
        ]]></getter>
        <setter><![CDATA[
          this.markupDocumentViewer.fullZoom = val;
        ]]></setter>
      </property>

      <property name="textZoom">
        <getter><![CDATA[
          return this.markupDocumentViewer.textZoom;
        ]]></getter>
        <setter><![CDATA[
          this.markupDocumentViewer.textZoom = val;
        ]]></setter>
      </property>

      <property name="effectiveTextZoom"
                readonly="true">
        <getter><![CDATA[
          return this.markupDocumentViewer.effectiveTextZoom;
        ]]></getter>
      </property>

      <property name="isSyntheticDocument">
        <getter><![CDATA[
          return this.contentDocument.mozSyntheticDocument;
        ]]></getter>
      </property>

      <property name="hasContentOpener">
        <getter><![CDATA[
          return !!this.contentWindow.opener;
        ]]></getter>
      </property>

      <field name="mPrefs" readonly="true">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranch);
      </field>

      <field name="_mStrBundle">null</field>

      <property name="mStrBundle">
        <getter>
        <![CDATA[
          if (!this._mStrBundle) {
            // need to create string bundle manually instead of using <xul:stringbundle/>
            // see bug 63370 for details
            this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.nsIStringBundleService)
                                         .createBundle("chrome://global/locale/browser.properties");
          }
          return this._mStrBundle;
        ]]></getter>
      </property>

      <method name="addProgressListener">
        <parameter name="aListener"/>
        <parameter name="aNotifyMask"/>
        <body>
          <![CDATA[
            if (!aNotifyMask) {
              aNotifyMask = Components.interfaces.nsIWebProgress.NOTIFY_ALL;
            }
            this.webProgress.addProgressListener(aListener, aNotifyMask);
          ]]>
        </body>
      </method>

      <method name="removeProgressListener">
        <parameter name="aListener"/>
        <body>
          <![CDATA[
            this.webProgress.removeProgressListener(aListener);
         ]]>
        </body>
      </method>

      <method name="findChildShell">
        <parameter name="aDocShell"/>
        <parameter name="aSoughtURI"/>
        <body>
          <![CDATA[
            if (aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation)
                         .currentURI.spec == aSoughtURI.spec)
              return aDocShell;
            var node = aDocShell.QueryInterface(
                                   Components.interfaces.nsIDocShellTreeItem);
            for (var i = 0; i < node.childCount; ++i) {
              var docShell = node.getChildAt(i);
              docShell = this.findChildShell(docShell, aSoughtURI);
              if (docShell)
                return docShell;
            }
            return null;
          ]]>
        </body>
      </method>

      <method name="onPageHide">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            // Delete the feeds cache if we're hiding the topmost page
            // (as opposed to one of its iframes).
            if (this.feeds && aEvent.target == this.contentDocument)
              this.feeds = null;
            if (!this.docShell || !this.fastFind)
              return;
            var tabBrowser = this.getTabBrowser();
            if (!tabBrowser || !("fastFind" in tabBrowser) ||
                tabBrowser.selectedBrowser == this)
              this.fastFind.setDocShell(this.docShell);
         ]]>
        </body>
      </method>

      <method name="updateBlockedPopups">
        <body>
          <![CDATA[
            let event = document.createEvent("Events");
            event.initEvent("DOMUpdatePageReport", true, true);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="retrieveListOfBlockedPopups">
        <body>
          <![CDATA[
          this.messageManager.sendAsyncMessage("PopupBlocking:GetBlockedPopupList", null);
          return new Promise(resolve => {
            let self = this;
            this.messageManager.addMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
              function replyReceived(msg) {
                self.messageManager.removeMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
                                                          replyReceived);
                resolve(msg.data.popupData);
              }
            );
          });
          ]]>
        </body>
      </method>

      <method name="unblockPopup">
        <parameter name="aPopupIndex"/>
        <body><![CDATA[
          this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup",
                                               {index: aPopupIndex});
        ]]></body>
      </method>

      <field name="blockedPopups">null</field>

      <!-- Obsolete name for blockedPopups. Used by android. -->
      <property name="pageReport"
         onget="return this.blockedPopups;"
         readonly="true"/>

      <method name="audioPlaybackStarted">
        <body>
          <![CDATA[
            if (this._audioMuted) {
              return;
            }
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackStarted", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="audioPlaybackStopped">
        <body>
          <![CDATA[
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackStopped", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <!--
        When the pref "media.block-autoplay-until-in-foreground" is on, all
        windows would be blocked by default in gecko. The "block" means the
        autoplay media can't be started in that tab unless the tab has been
        visited or resumed by tab's play tab icon. Since the window is blocked
        by default, there's no method to signal entering that state.
        (1) If the window is resumed, no matter it has autoplay media or not
            - will call mediaBlockStopped()
        (2) If the window has blocked any autoplay media
            - will call activeMediaBlockStarted()
        (3) If the window has resumed any autoplay media
            - will call activeMediaBlockStopped()
       -->
      <method name="activeMediaBlockStarted">
        <body>
          <![CDATA[
            this._hasAnyPlayingMediaBeenBlocked = true;
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="activeMediaBlockStopped">
        <body>
          <![CDATA[
            if (!this._hasAnyPlayingMediaBeenBlocked) {
              return;
            }
            this._hasAnyPlayingMediaBeenBlocked = false;
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="mediaBlockStopped">
        <body>
          <![CDATA[
            this._mediaBlocked = false;
          ]]>
        </body>
      </method>

      <field name="_audioMuted">false</field>
      <property name="audioMuted"
                onget="return this._audioMuted;"
                readonly="true"/>

      <field name="_mediaBlocked">true</field>
      <property name="mediaBlocked" readonly="true">
        <getter>
          <![CDATA[
            if (this.mPrefs.getBoolPref("media.block-autoplay-until-in-foreground", true)) {
              return this._mediaBlocked;
            }
            return false;
          ]]>
        </getter>
      </property>

      <field name="_hasAnyPlayingMediaBeenBlocked">false</field>

      <method name="mute">
        <parameter name="transientState"/>
        <body>
          <![CDATA[
            if (!transientState) {
              this._audioMuted = true;
            }
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "mute"});
          ]]>
        </body>
      </method>

      <method name="unmute">
        <body>
          <![CDATA[
            this._audioMuted = false;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "unmute"});
          ]]>
        </body>
      </method>

      <method name="pauseMedia">
        <parameter name="disposable"/>
        <body>
          <![CDATA[
            let suspendedReason;
            if (disposable) {
              suspendedReason = "mediaControlPaused";
            } else {
              suspendedReason = "lostAudioFocusTransiently";
            }

            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: suspendedReason});
          ]]>
        </body>
      </method>

      <method name="stopMedia">
        <body>
          <![CDATA[
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "mediaControlStopped"});
          ]]>
        </body>
      </method>

      <method name="resumeMedia">
        <body>
          <![CDATA[
            this._mediaBlocked = false;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "resumeMedia"});
            if (this._hasAnyPlayingMediaBeenBlocked) {
              this._hasAnyPlayingMediaBeenBlocked = false;
              let event = document.createEvent("Events");
              event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
              this.dispatchEvent(event);
            }
          ]]>
        </body>
      </method>

      <!--
        Only send the message "Browser:UnselectedTabHover" when someone requests
        for the message, which can reduce non-necessary communication.
      -->
      <field name="_shouldSendUnselectedTabHover">false</field>
      <field name="_unselectedTabHoverMessageListenerCount">0</field>
      <property name="shouldHandleUnselectedTabHover"
                onget="return this._shouldSendUnselectedTabHover;"
                readonly="true"/>

      <method name="unselectedTabHover">
        <parameter name="hovered"/>
        <body>
          <![CDATA[
            if (!this._shouldSendUnselectedTabHover) {
              return;
            }
            this.messageManager.sendAsyncMessage("Browser:UnselectedTabHover",
              { hovered });
          ]]>
        </body>
      </method>

      <property name="securityUI">
        <getter>
          <![CDATA[
            if (!this.docShell.securityUI) {
              const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
              if (!this.hasAttribute("disablesecurity") &&
                  SECUREBROWSERUI_CONTRACTID in Components.classes) {
                var securityUI = Components.classes[SECUREBROWSERUI_CONTRACTID]
                                           .createInstance(Components.interfaces.nsISecureBrowserUI);
                securityUI.init(this.contentWindow);
              }
            }

            return this.docShell.securityUI;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.docShell.securityUI = val;
          ]]>
        </setter>
      </property>

      <field name="urlbarChangeTracker">
        ({
          _startedLoadSinceLastUserTyping: false,

          startedLoad() {
            this._startedLoadSinceLastUserTyping = true;
          },
          finishedLoad() {
            this._startedLoadSinceLastUserTyping = false;
          },
          userTyped() {
            this._startedLoadSinceLastUserTyping = false;
          },
        })
      </field>

      <method name="didStartLoadSinceLastUserTyping">
        <body><![CDATA[
          return !this.inLoadURI &&
                 this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
        ]]></body>
      </method>

      <field name="_userTypedValue">
        null
      </field>

      <property name="userTypedValue"
                onget="return this._userTypedValue;">
        <setter><![CDATA[
          this.urlbarChangeTracker.userTyped();
          this._userTypedValue = val;
          return val;
        ]]></setter>
      </property>

      <field name="mFormFillAttached">
        false
      </field>

      <field name="isShowingMessage">
        false
      </field>

      <field name="droppedLinkHandler">
        null
      </field>

      <field name="mIconURL">null</field>

      <!-- This is managed by the tabbrowser -->
      <field name="lastURI">null</field>

      <field name="mDestroyed">false</field>

      <constructor>
        <![CDATA[
          try {
            // |webNavigation.sessionHistory| will have been set by the frame
            // loader when creating the docShell as long as this xul:browser
            // doesn't have the 'disablehistory' attribute set.
            if (this.docShell && this.webNavigation.sessionHistory) {
              var os = Components.classes["@mozilla.org/observer-service;1"]
                                 .getService(Components.interfaces.nsIObserverService);
              os.addObserver(this, "browser:purge-session-history", true);

              // enable global history if we weren't told otherwise
              if (!this.hasAttribute("disableglobalhistory") && !this.isRemoteBrowser) {
                try {
                  this.docShell.useGlobalHistory = true;
                } catch (ex) {
                  // This can occur if the Places database is locked
                  Components.utils.reportError("Error enabling browser global history: " + ex);
                }
              }
            }
          } catch (e) {
            Components.utils.reportError(e);
          }
          try {
            // Ensures the securityUI is initialized.
            var securityUI = this.securityUI; // eslint-disable-line no-unused-vars
          } catch (e) {
          }

          // tabbrowser.xml sets "sameProcessAsFrameLoader" as a direct property
          // on some browsers before they are put into a DOM (and get a
          // binding).  This hack makes sure that we hold a weak reference to
          // the other browser (and go through the proper getter and setter).
          if (this.hasOwnProperty("sameProcessAsFrameLoader")) {
            var sameProcessAsFrameLoader = this.sameProcessAsFrameLoader;
            delete this.sameProcessAsFrameLoader;
            this.sameProcessAsFrameLoader = sameProcessAsFrameLoader;
          }

          if (!this.isRemoteBrowser) {
            this.addEventListener("pagehide", this.onPageHide, true);
          }

          if (this.messageManager) {
            this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
            this.messageManager.addMessageListener("Autoscroll:Start", this);
            this.messageManager.addMessageListener("Autoscroll:Cancel", this);
            this.messageManager.addMessageListener("AudioPlayback:Start", this);
            this.messageManager.addMessageListener("AudioPlayback:Stop", this);
            this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStart", this);
            this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStop", this);
            this.messageManager.addMessageListener("AudioPlayback:MediaBlockStop", this);
            this.messageManager.addMessageListener("UnselectedTabHover:Toggle", this);

            if (this.hasAttribute("selectmenulist")) {
              this.messageManager.addMessageListener("Forms:ShowDropDown", this);
              this.messageManager.addMessageListener("Forms:HideDropDown", this);
            }

          }
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.destroy();
        ]]>
      </destructor>

      <!-- This is necessary because the destructor doesn't always get called when
           we are removed from a tabbrowser. This will be explicitly called by tabbrowser.

           Note: this function is overriden in remote-browser.xml, so any clean-up that
           also applies to browser.isRemoteBrowser = true must be duplicated there. -->
      <method name="destroy">
        <body>
          <![CDATA[
          // Make sure that any open select is closed.
          if (this._selectParentHelper) {
            let menulist = document.getElementById(this.getAttribute("selectmenulist"));
            this._selectParentHelper.hide(menulist, this);
          }
          if (this.mDestroyed)
            return;
          this.mDestroyed = true;

          if (this.docShell && this.webNavigation.sessionHistory) {
            var os = Components.classes["@mozilla.org/observer-service;1"]
                               .getService(Components.interfaces.nsIObserverService);
            try {
              os.removeObserver(this, "browser:purge-session-history");
            } catch (ex) {
              // It's not clear why this sometimes throws an exception.
            }
          }

          this._fastFind = null;
          this._webBrowserFind = null;

          // The feeds cache can keep the document inside this browser alive.
          this.feeds = null;

          this.lastURI = null;

          if (!this.isRemoteBrowser) {
            this.removeEventListener("pagehide", this.onPageHide, true);
          }

          if (this._autoScrollNeedsCleanup) {
            // we polluted the global scope, so clean it up
            this._autoScrollPopup.remove();
          }
          ]]>
        </body>
      </method>

      <!--
        We call this _receiveMessage (and alias receiveMessage to it) so that
        bindings that inherit from this one can delegate to it.
      -->
      <method name="_receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          let data = aMessage.data;
          switch (aMessage.name) {
            case "PopupBlocking:UpdateBlockedPopups": {
              this.blockedPopups = {
                length: data.count,
                reported: !data.freshPopup,
              };

              this.updateBlockedPopups();
              break;
            }
            case "Autoscroll:Start": {
              if (!this.autoscrollEnabled) {
                return false;
              }
              this.startScroll(data.scrolldir, data.screenX, data.screenY);
              if (this.isRemoteBrowser && data.scrollId != null &&
                  this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)) {
                let { tabParent } = this.frameLoader;
                if (tabParent) {
                  tabParent.startApzAutoscroll(data.screenX, data.screenY,
                                               data.scrollId, data.presShellId);
                }
                // Save the IDs for later
                this._autoScrollScrollId = data.scrollId;
                this._autoScrollPresShellId = data.presShellId;
              }
              return true;
            }
            case "Autoscroll:Cancel":
              this._autoScrollPopup.hidePopup();
              break;
            case "AudioPlayback:Start":
              this.audioPlaybackStarted();
              break;
            case "AudioPlayback:Stop":
              this.audioPlaybackStopped();
              break;
            case "AudioPlayback:ActiveMediaBlockStart":
              this.activeMediaBlockStarted();
              break;
            case "AudioPlayback:ActiveMediaBlockStop":
              this.activeMediaBlockStopped();
              break;
            case "AudioPlayback:MediaBlockStop":
              this.mediaBlockStopped();
              break;
            case "UnselectedTabHover:Toggle":
              this._shouldSendUnselectedTabHover = data.enable ?
                ++this._unselectedTabHoverMessageListenerCount > 0 :
                --this._unselectedTabHoverMessageListenerCount == 0;
              break;
            case "Forms:ShowDropDown": {
              if (!this._selectParentHelper) {
                this._selectParentHelper =
                  Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
              }

              let menulist = document.getElementById(this.getAttribute("selectmenulist"));
              menulist.menupopup.style.direction = data.direction;
              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom,
                                                data.uaBackgroundColor, data.uaColor,
                                                data.uaSelectBackgroundColor, data.uaSelectColor,
                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
              this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
              break;
            }

            case "Forms:HideDropDown": {
              if (this._selectParentHelper) {
                let menulist = document.getElementById(this.getAttribute("selectmenulist"));
                this._selectParentHelper.hide(menulist, this);
              }
              break;
            }

          }
          return undefined;
        ]]></body>
      </method>

      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          return this._receiveMessage(aMessage);
        ]]></body>
      </method>

      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aState"/>
        <body>
          <![CDATA[
            if (aTopic != "browser:purge-session-history")
              return;

            this.purgeSessionHistory();
          ]]>
        </body>
      </method>

      <method name="purgeSessionHistory">
        <body>
          <![CDATA[
            this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
          ]]>
        </body>
      </method>

      <method name="createAboutBlankContentViewer">
        <parameter name="aPrincipal"/>
        <body>
          <![CDATA[
            let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
            this.docShell.createAboutBlankContentViewer(principal);
          ]]>
        </body>
      </method>

      <field name="_AUTOSCROLL_SNAP">10</field>
      <field name="_scrolling">false</field>
      <field name="_startX">null</field>
      <field name="_startY">null</field>
      <field name="_autoScrollPopup">null</field>
      <field name="_autoScrollNeedsCleanup">false</field>
      <!-- These IDs identify the scroll frame being autoscrolled. -->
      <field name="_autoScrollScrollId">null</field>
      <field name="_autoScrollPresShellId">null</field>

      <method name="stopScroll">
        <body>
          <![CDATA[
            if (this._scrolling) {
              this._scrolling = false;
              window.removeEventListener("mousemove", this, true);
              window.removeEventListener("mousedown", this, true);
              window.removeEventListener("mouseup", this, true);
              window.removeEventListener("DOMMouseScroll", this, true);
              window.removeEventListener("contextmenu", this, true);
              window.removeEventListener("keydown", this, true);
              window.removeEventListener("keypress", this, true);
              window.removeEventListener("keyup", this, true);
              this.messageManager.sendAsyncMessage("Autoscroll:Stop");

              if (this.isRemoteBrowser && this._autoScrollScrollId != null) {
                let { tabParent } = this.frameLoader;
                if (tabParent) {
                  tabParent.stopApzAutoscroll(this._autoScrollScrollId,
                                              this._autoScrollPresShellId);
                }
                this._autoScrollScrollId = null;
                this._autoScrollPresShellId = null;
              }
            }
         ]]>
       </body>
     </method>

      <method name="_createAutoScrollPopup">
        <body>
          <![CDATA[
            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var popup = document.createElementNS(XUL_NS, "panel");
            popup.className = "autoscroller";
            // We set this attribute on the element so that mousemove
            // events can be handled by browser-content.js.
            popup.setAttribute("mousethrough", "always");
            popup.setAttribute("rolluponmousewheel", "true");
            return popup;
          ]]>
        </body>
      </method>

      <method name="startScroll">
        <parameter name="scrolldir"/>
        <parameter name="screenX"/>
        <parameter name="screenY"/>
        <body><![CDATA[
            if (!this._autoScrollPopup) {
              if (this.hasAttribute("autoscrollpopup")) {
                // our creator provided a popup to share
                this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
              } else {
                // we weren't provided a popup; we have to use the global scope
                this._autoScrollPopup = this._createAutoScrollPopup();
                document.documentElement.appendChild(this._autoScrollPopup);
                this._autoScrollNeedsCleanup = true;
              }
            }

            // we need these attributes so themers don't need to create per-platform packages
            if (screen.colorDepth > 8) { // need high color for transparency
              // Exclude second-rate platforms
              this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
              // Enable translucency on Windows and Mac
              this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
            }

            this._autoScrollPopup.setAttribute("noautofocus", "true");
            this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
            this._autoScrollPopup.addEventListener("popuphidden", this, true);
            this._autoScrollPopup.showPopup(document.documentElement,
                                            screenX,
                                            screenY,
                                            "popup", null, null);
            this._ignoreMouseEvents = true;
            this._scrolling = true;
            this._startX = screenX;
            this._startY = screenY;

            window.addEventListener("mousemove", this, true);
            window.addEventListener("mousedown", this, true);
            window.addEventListener("mouseup", this, true);
            window.addEventListener("DOMMouseScroll", this, true);
            window.addEventListener("contextmenu", this, true);
            window.addEventListener("keydown", this, true);
            window.addEventListener("keypress", this, true);
            window.addEventListener("keyup", this, true);
         ]]>
        </body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this._scrolling) {
            switch (aEvent.type) {
              case "mousemove": {
                var x = aEvent.screenX - this._startX;
                var y = aEvent.screenY - this._startY;

                if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
                    (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
                  this._ignoreMouseEvents = false;
                break;
              }
              case "mouseup":
              case "mousedown":
              case "contextmenu": {
                if (!this._ignoreMouseEvents) {
                  // Use a timeout to prevent the mousedown from opening the popup again.
                  // Ideally, we could use preventDefault here, but contenteditable
                  // and middlemouse paste don't interact well. See bug 1188536.
                  setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
                }
                this._ignoreMouseEvents = false;
                break;
              }
              case "DOMMouseScroll": {
                this._autoScrollPopup.hidePopup();
                aEvent.preventDefault();
                break;
              }
              case "popuphidden": {
                this._autoScrollPopup.removeEventListener("popuphidden", this, true);
                this.stopScroll();
                break;
              }
              case "keydown": {
                if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
                  // the escape key will be processed by
                  // nsXULPopupManager::KeyDown and the panel will be closed.
                  // So, don't consume the key event here.
                  break;
                }
                // don't break here. we need to eat keydown events.
              }
              case "keypress":
              case "keyup": {
                // All keyevents should be eaten here during autoscrolling.
                aEvent.stopPropagation();
                aEvent.preventDefault();
                break;
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="closeBrowser">
        <body>
        <![CDATA[
          // The request comes from a XPCOM component, we'd want to redirect
          // the request to tabbrowser.
          let tabbrowser = this.getTabBrowser();
          if (tabbrowser) {
            let tab = tabbrowser.getTabForBrowser(this);
            if (tab) {
              tabbrowser.removeTab(tab);
              return;
            }
          }

          throw new Error("Closing a browser which was not attached to a tabbrowser is unsupported.");
        ]]>
        </body>
      </method>

      <method name="swapBrowsers">
        <parameter name="aOtherBrowser"/>
        <parameter name="aFlags"/>
        <body>
        <![CDATA[
          // The request comes from a XPCOM component, we'd want to redirect
          // the request to tabbrowser so tabbrowser will be setup correctly,
          // and it will eventually call swapDocShells.
          let ourTabBrowser = this.getTabBrowser();
          let otherTabBrowser = aOtherBrowser.getTabBrowser();
          if (ourTabBrowser && otherTabBrowser) {
            let ourTab = ourTabBrowser.getTabForBrowser(this);
            let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser);
            ourTabBrowser.swapBrowsers(ourTab, otherTab, aFlags);
            return;
          }

          // One of us is not connected to a tabbrowser, so just swap.
          this.swapDocShells(aOtherBrowser);
        ]]>
        </body>
      </method>

      <method name="swapDocShells">
        <parameter name="aOtherBrowser"/>
        <body>
        <![CDATA[
          if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser)
            throw new Error("Can only swap docshells between browsers in the same process.");

          // Give others a chance to swap state.
          // IMPORTANT: Since a swapDocShells call does not swap the messageManager
          //            instances attached to a browser to aOtherBrowser, others
          //            will need to add the message listeners to the new
          //            messageManager.
          //            This is not a bug in swapDocShells or the FrameLoader,
          //            merely a design decision: If message managers were swapped,
          //            so that no new listeners were needed, the new
          //            aOtherBrowser.messageManager would have listeners pointing
          //            to the JS global of the current browser, which would rather
          //            easily create leaks while swapping.
          // IMPORTANT2: When the current browser element is removed from DOM,
          //             which is quite common after a swpDocShells call, its
          //             frame loader is destroyed, and that destroys the relevant
          //             message manager, which will remove the listeners.
          let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
          this.dispatchEvent(event);
          event = new CustomEvent("SwapDocShells", {"detail": this});
          aOtherBrowser.dispatchEvent(event);

          // We need to swap fields that are tied to our docshell or related to
          // the loaded page
          // Fields which are built as a result of notifactions (pageshow/hide,
          // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
          // because these notifications are dispatched again once the docshells
          // are swapped.
          var fieldsToSwap = [
            "_docShell",
            "_webBrowserFind",
            "_contentWindow",
            "_webNavigation"
          ];

          if (this.isRemoteBrowser) {
            fieldsToSwap.push(...[
              "_remoteWebNavigation",
              "_remoteWebNavigationImpl",
              "_remoteWebProgressManager",
              "_remoteWebProgress",
              "_remoteFinder",
              "_securityUI",
              "_documentURI",
              "_documentContentType",
              "_contentTitle",
              "_characterSet",
              "_mayEnableCharacterEncodingMenu",
              "_contentPrincipal",
              "_imageDocument",
              "_fullZoom",
              "_textZoom",
              "_isSyntheticDocument",
              "_innerWindowID",
              "_manifestURI",
            ]);
          }

          var ourFieldValues = {};
          var otherFieldValues = {};
          for (let field of fieldsToSwap) {
            ourFieldValues[field] = this[field];
            otherFieldValues[field] = aOtherBrowser[field];
          }

          if (window.PopupNotifications)
            PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);

          try {
            this.swapFrameLoaders(aOtherBrowser);
          } catch (ex) {
            // This may not be implemented for browser elements that are not
            // attached to a BrowserDOMWindow.
          }

          for (let field of fieldsToSwap) {
            this[field] = otherFieldValues[field];
            aOtherBrowser[field] = ourFieldValues[field];
          }

          if (!this.isRemoteBrowser) {
            // Null the current nsITypeAheadFind instances so that they're
            // lazily re-created on access. We need to do this because they
            // might have attached the wrong docShell.
            this._fastFind = aOtherBrowser._fastFind = null;
          } else {
            // Rewire the remote listeners
            this._remoteWebNavigationImpl.swapBrowser(this);
            aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser);

            if (this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager) {
              this._remoteWebProgressManager.swapBrowser(this);
              aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
            }

            if (this._remoteFinder)
              this._remoteFinder.swapBrowser(this);
            if (aOtherBrowser._remoteFinder)
              aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
          }

          event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
          this.dispatchEvent(event);
          event = new CustomEvent("EndSwapDocShells", {"detail": this});
          aOtherBrowser.dispatchEvent(event);
        ]]>
        </body>
      </method>

      <method name="getInPermitUnload">
        <parameter name="aCallback"/>
        <body>
        <![CDATA[
          if (!this.docShell || !this.docShell.contentViewer) {
            aCallback(false);
            return;
          }
          aCallback(this.docShell.contentViewer.inPermitUnload);
        ]]>
        </body>
      </method>

      <method name="permitUnload">
        <body>
        <![CDATA[
          if (!this.docShell || !this.docShell.contentViewer) {
            return {permitUnload: true, timedOut: false};
          }
          return {permitUnload: this.docShell.contentViewer.permitUnload(), timedOut: false};
        ]]>
        </body>
      </method>

      <method name="print">
        <parameter name="aOuterWindowID"/>
        <parameter name="aPrintSettings"/>
        <parameter name="aPrintProgressListener"/>
        <body>
          <![CDATA[
            var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            if (!owner.frameLoader) {
              throw Components.Exception("No frame loader.",
                                         Components.results.NS_ERROR_FAILURE);
            }

            owner.frameLoader.print(aOuterWindowID, aPrintSettings,
                                    aPrintProgressListener);
          ]]>
        </body>
      </method>

      <method name="dropLinks">
        <parameter name="aLinksCount"/>
        <parameter name="aLinks"/>
        <body><![CDATA[
          if (!this.droppedLinkHandler) {
            return false;
          }
          let links = [];
          for (let i = 0; i < aLinksCount; i += 3) {
            links.push({
              url: aLinks[i],
              name: aLinks[i + 1],
              type: aLinks[i + 2],
            });
          }
          this.droppedLinkHandler(null, links);
          return true;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_F7" group="system">
        <![CDATA[
          if (event.defaultPrevented || !event.isTrusted)
            return;

          const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled";
          const kPrefWarnOnEnable    = "accessibility.warn_on_browsewithcaret";
          const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";

          var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
          if (!isEnabled)
            return;

          // Toggle browse with caret mode
          var browseWithCaretOn = this.mPrefs.getBoolPref(kPrefCaretBrowsingOn, false);
          var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
          if (warn && !browseWithCaretOn) {
            var checkValue = {value: false};
            var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                          .getService(Components.interfaces.nsIPromptService);

            var buttonPressed = promptService.confirmEx(window,
              this.mStrBundle.GetStringFromName("browsewithcaret.checkWindowTitle"),
              this.mStrBundle.GetStringFromName("browsewithcaret.checkLabel"),
              // Make "No" the default:
              promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT,
              null, null, null, this.mStrBundle.GetStringFromName("browsewithcaret.checkMsg"),
              checkValue);
            if (buttonPressed != 0) {
              if (checkValue.value) {
                try {
                  this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
                } catch (ex) {
                }
              }
              return;
            }
            if (checkValue.value) {
              try {
                this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
              } catch (ex) {
              }
            }
          }

          // Toggle the pref
          try {
            this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
          } catch (ex) {
          }
        ]]>
      </handler>
      <handler event="dragover" group="system">
      <![CDATA[
        if (!this.droppedLinkHandler || event.defaultPrevented)
          return;

        // For drags that appear to be internal text (for example, tab drags),
        // set the dropEffect to 'none'. This prevents the drop even if some
        // other listener cancelled the event.
        var types = event.dataTransfer.types;
        if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
          event.dataTransfer.dropEffect = "none";
          event.stopPropagation();
          event.preventDefault();
        }

        // No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process
        // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
        if (this.isRemoteBrowser)
          return;

        let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
                            getService(Components.interfaces.nsIDroppedLinkHandler);
        if (linkHandler.canDropLink(event, false))
          event.preventDefault();
      ]]>
      </handler>
      <handler event="drop" group="system">
      <![CDATA[
        // No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process
        // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
        if (!this.droppedLinkHandler || event.defaultPrevented || this.isRemoteBrowser)
          return;

        let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
                            getService(Components.interfaces.nsIDroppedLinkHandler);
        try {
          // Pass true to prevent the dropping of javascript:/data: URIs
          var links = linkHandler.dropLinks(event, true);
        } catch (ex) {
          return;
        }

        if (links.length) {
          let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event);
          this.droppedLinkHandler(event, links, triggeringPrincipal);
        }
      ]]>
      </handler>
    </handlers>

  </binding>

</bindings>
PK
!<F661chrome/toolkit/content/global/bindings/button.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="buttonBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="button-base" extends="chrome://global/content/bindings/general.xml#basetext" role="xul:button">
    <implementation implements="nsIDOMXULButtonElement">
      <property name="type"
                onget="return this.getAttribute('type');"
                onset="this.setAttribute('type', val); return val;"/>

      <property name="dlgType"
                onget="return this.getAttribute('dlgtype');"
                onset="this.setAttribute('dlgtype', val); return val;"/>

      <property name="group"
                onget="return this.getAttribute('group');"
                onset="this.setAttribute('group', val); return val;"/>

      <property name="open" onget="return this.hasAttribute('open');">
        <setter><![CDATA[
          if (this.boxObject instanceof MenuBoxObject) {
            this.boxObject.openMenu(val);
          } else if (val) {
            // Fall back to just setting the attribute
            this.setAttribute("open", "true");
          } else {
            this.removeAttribute("open");
          }
          return val;
        ]]></setter>
      </property>

      <property name="checked" onget="return this.hasAttribute('checked');">
        <setter><![CDATA[
          if (this.type == "checkbox") {
            this.checkState = val ? 1 : 0;
          } else if (this.type == "radio" && val) {
            var sibs = this.parentNode.getElementsByAttribute("group", this.group);
            for (var i = 0; i < sibs.length; ++i)
              sibs[i].removeAttribute("checked");
          }

          if (val)
            this.setAttribute("checked", "true");
          else
            this.removeAttribute("checked");

          return val;
        ]]></setter>
      </property>

      <property name="checkState">
        <getter><![CDATA[
          var state = this.getAttribute("checkState");
          if (state == "")
            return this.checked ? 1 : 0;
          if (state == "0")
            return 0;
          if (state == "2")
            return 2;
          return 1;
        ]]></getter>
        <setter><![CDATA[
          this.setAttribute("checkState", val);
          return val;
        ]]></setter>
      </property>

      <property name="autoCheck"
                onget="return this.getAttribute('autoCheck') == 'true';"
                onset="this.setAttribute('autoCheck', val); return val;"/>

      <method name ="filterButtons">
        <parameter name="node"/>
        <body>
        <![CDATA[
          // if the node isn't visible, don't descend into it.
          var cs = node.ownerGlobal.getComputedStyle(node);
          if (cs.visibility != "visible" || cs.display == "none") {
            return NodeFilter.FILTER_REJECT;
          }
          // but it may be a popup element, in which case we look at "state"...
          if (cs.display == "-moz-popup" && node.state != "open") {
            return NodeFilter.FILTER_REJECT;
          }
          // OK - the node seems visible, so it is a candidate.
          if (node.localName == "button" && node.accessKey && !node.disabled)
            return NodeFilter.FILTER_ACCEPT;
          return NodeFilter.FILTER_SKIP;
        ]]>
        </body>
      </method>

      <method name="fireAccessKeyButton">
        <parameter name="aSubtree"/>
        <parameter name="aAccessKeyLower"/>
        <body>
        <![CDATA[
          var iterator = aSubtree.ownerDocument.createTreeWalker(aSubtree,
                                                                 NodeFilter.SHOW_ELEMENT,
                                                                 this.filterButtons);
          while (iterator.nextNode()) {
            var test = iterator.currentNode;
            if (test.accessKey.toLowerCase() == aAccessKeyLower &&
                !test.disabled && !test.collapsed && !test.hidden) {
              test.focus();
              test.click();
              return true;
            }
          }
          return false;
        ]]>
        </body>
      </method>

      <method name="_handleClick">
        <body>
        <![CDATA[
          if (!this.disabled &&
              (this.autoCheck || !this.hasAttribute("autoCheck"))) {

            if (this.type == "checkbox") {
              this.checked = !this.checked;
            } else if (this.type == "radio") {
              this.checked = true;
            }
          }
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <!-- While it would seem we could do this by handling oncommand, we can't
           because any external oncommand handlers might get called before ours,
           and then they would see the incorrect value of checked. Additionally
           a command attribute would redirect the command events anyway.-->
      <handler event="click" button="0" action="this._handleClick();"/>
      <handler event="keypress" key=" ">
      <![CDATA[
        this._handleClick();
        // Prevent page from scrolling on the space key.
        event.preventDefault();
      ]]>
      </handler>

      <handler event="keypress">
      <![CDATA[
        if (this.boxObject instanceof MenuBoxObject) {
          if (this.open)
            return;
        } else {
          if (event.keyCode == KeyEvent.DOM_VK_UP ||
              (event.keyCode == KeyEvent.DOM_VK_LEFT &&
                document.defaultView.getComputedStyle(this.parentNode)
                        .direction == "ltr") ||
              (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
                document.defaultView.getComputedStyle(this.parentNode)
                        .direction == "rtl")) {
            event.preventDefault();
            window.document.commandDispatcher.rewindFocus();
            return;
          }

          if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
              (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
                document.defaultView.getComputedStyle(this.parentNode)
                        .direction == "ltr") ||
              (event.keyCode == KeyEvent.DOM_VK_LEFT &&
                document.defaultView.getComputedStyle(this.parentNode)
                        .direction == "rtl")) {
            event.preventDefault();
            window.document.commandDispatcher.advanceFocus();
            return;
          }
        }

        if (event.keyCode || event.charCode <= 32 || event.altKey ||
            event.ctrlKey || event.metaKey)
          return;  // No printable char pressed, not a potential accesskey

        // Possible accesskey pressed
        var charPressedLower = String.fromCharCode(event.charCode).toLowerCase();

        // If the accesskey of the current button is pressed, just activate it
        if (this.accessKey.toLowerCase() == charPressedLower) {
          this.click();
          return;
        }

        // Search for accesskey in the list of buttons for this doc and each subdoc
        // Get the buttons for the main document and all sub-frames
        for (var frameCount = -1; frameCount < window.top.frames.length; frameCount++) {
          var doc = (frameCount == -1) ? window.top.document :
            window.top.frames[frameCount].document
          if (this.fireAccessKeyButton(doc.documentElement, charPressedLower))
            return;
        }

        // Test anonymous buttons
        var dlg = window.top.document;
        var buttonBox = dlg.getAnonymousElementByAttribute(dlg.documentElement,
                                                         "anonid", "buttons");
        if (buttonBox)
          this.fireAccessKeyButton(buttonBox, charPressedLower);
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="button" display="xul:button"
           extends="chrome://global/content/bindings/button.xml#button-base">
    <resources>
      <stylesheet src="chrome://global/skin/button.css"/>
    </resources>

    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
                align="center" pack="center" flex="1" anonid="button-box">
        <children>
          <xul:image class="button-icon" xbl:inherits="src=image"/>
          <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop,highlightable"/>
          <xul:label class="button-highlightable-text" xbl:inherits="xbl:text=label,accesskey,crop,highlightable"/>
        </children>
      </xul:hbox>
    </content>
  </binding>

  <binding id="menu" display="xul:menu"
           extends="chrome://global/content/bindings/button.xml#button">
    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
                align="center" pack="center" flex="1">
        <children>
          <xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
                    align="center" pack="center" flex="1">
            <xul:image class="button-icon" xbl:inherits="src=image"/>
            <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
          </xul:hbox>
          <xul:dropmarker class="button-menu-dropmarker" xbl:inherits="open,disabled,label"/>
        </children>
      </xul:hbox>
    </content>

    <handlers>
      <handler event="keypress" keycode="VK_RETURN" action="this.open = true;"/>
      <handler event="keypress" key=" ">
      <![CDATA[
        this.open = true;
        // Prevent page from scrolling on the space key.
        event.preventDefault();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="menu-button-base"
           extends="chrome://global/content/bindings/button.xml#button-base">
    <implementation implements="nsIDOMEventListener">
      <constructor>
        this.init();
      </constructor>

      <method name="init">
        <body>
        <![CDATA[
          var btn = document.getAnonymousElementByAttribute(this, "anonid", "button");
          if (!btn)
            throw "XBL binding for <button type=\"menu-button\"/> binding must contain an element with anonid=\"button\"";

          var menubuttonParent = this;
          btn.addEventListener("mouseover", function() {
            if (!this.disabled)
              menubuttonParent.buttonover = true;
          }, true);
          btn.addEventListener("mouseout", function() {
            menubuttonParent.buttonover = false;
          }, true);
          btn.addEventListener("mousedown", function() {
            if (!this.disabled) {
              menubuttonParent.buttondown = true;
              document.addEventListener("mouseup", menubuttonParent, true);
            }
          }, true);
        ]]>
        </body>
      </method>

      <property name="buttonover" onget="return this.getAttribute('buttonover');">
        <setter>
        <![CDATA[
          var v = val || val == "true";
          if (!v && this.buttondown) {
            this.buttondown = false;
            this._pendingActive = true;
          } else if (this._pendingActive) {
            this.buttondown = true;
            this._pendingActive = false;
          }

          if (v)
            this.setAttribute("buttonover", "true");
          else
            this.removeAttribute("buttonover");
          return val;
        ]]>
        </setter>
      </property>

      <property name="buttondown" onget="return this.getAttribute('buttondown') == 'true';">
        <setter>
        <![CDATA[
          if (val || val == "true")
            this.setAttribute("buttondown", "true");
          else
            this.removeAttribute("buttondown");
          return val;
        ]]>
        </setter>
      </property>

      <field name="_pendingActive">false</field>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this._pendingActive = false;
          this.buttondown = false;
          document.removeEventListener("mouseup", this, true);
        ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_RETURN">
        if (event.originalTarget == this)
          this.open = true;
      </handler>
      <handler event="keypress" key=" ">
        if (event.originalTarget == this) {
          this.open = true;
          // Prevent page from scrolling on the space key.
          event.preventDefault();
        }
      </handler>
    </handlers>
  </binding>

  <binding id="menu-button" display="xul:menu"
           extends="chrome://global/content/bindings/button.xml#menu-button-base">
    <resources>
      <stylesheet src="chrome://global/skin/button.css"/>
    </resources>

    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:button class="box-inherit button-menubutton-button"
                  anonid="button" flex="1" allowevents="true"
                  xbl:inherits="disabled,crop,image,label,accesskey,command,
                                buttonover,buttondown,align,dir,pack,orient">
        <children/>
      </xul:button>
      <xul:dropmarker class="button-menubutton-dropmarker" xbl:inherits="open,disabled,label"/>
    </content>
  </binding>

  <binding id="button-image" display="xul:button"
           extends="chrome://global/content/bindings/button.xml#button">
    <content>
      <xul:image class="button-image-icon" xbl:inherits="src=image"/>
    </content>
  </binding>

  <binding id="button-repeat" display="xul:autorepeatbutton"
           extends="chrome://global/content/bindings/button.xml#button"/>

</bindings>
PK
!≮=2chrome/toolkit/content/global/bindings/calendar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Initialize the Calendar and generate nodes for week headers and days, and
 * attach event listeners.
 *
 * @param {Object} options
 *        {
 *          {Number} calViewSize: Number of days to appear on a calendar view
 *        }
 * @param {Object} context
 *        {
 *          {DOMElement} weekHeader
 *          {DOMElement} daysView
 *        }
 */
function Calendar(options, context) {
  const DAYS_IN_A_WEEK = 7;

  this.context = context;
  this.state = {
    days: [],
    weekHeaders: []
  };
  this.props = {};
  this.elements = {
    weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
    daysView: this._generateNodes(options.calViewSize, context.daysView)
  };

  this._attachEventListeners();
}

Calendar.prototype = {

  /**
   * Set new properties and render them.
   *
   * @param {Object} props
   *        {
   *          {Boolean} isVisible: Whether or not the calendar is in view
   *          {Array<Object>} days: Data for days
   *          {
   *            {Number} dateValue: Date in milliseconds
   *            {Number} textContent
   *            {Array<String>} classNames
   *          }
   *          {Array<Object>} weekHeaders: Data for weekHeaders
   *          {
   *            {Number} textContent
   *            {Array<String>} classNames
   *            {Boolean} enabled
   *          }
   *          {Function} getDayString: Transform day number to string
   *          {Function} getWeekHeaderString: Transform day of week number to string
   *          {Function} setSelection: Set selection for dateKeeper
   *        }
   */
  setProps(props) {
    if (props.isVisible) {
      // Transform the days and weekHeaders array for rendering
      const days = props.days.map(({ dateObj, classNames, enabled }) => {
        return {
          textContent: props.getDayString(dateObj.getUTCDate()),
          className: classNames.join(" "),
          enabled
        };
      });
      const weekHeaders = props.weekHeaders.map(({ textContent, classNames }) => {
        return {
          textContent: props.getWeekHeaderString(textContent),
          className: classNames.join(" ")
        };
      });
      // Update the DOM nodes states
      this._render({
        elements: this.elements.daysView,
        items: days,
        prevState: this.state.days
      });
      this._render({
        elements: this.elements.weekHeaders,
        items: weekHeaders,
        prevState: this.state.weekHeaders,
      });
      // Update the state to current
      this.state.days = days;
      this.state.weekHeaders = weekHeaders;
    }

    this.props = Object.assign(this.props, props);
  },

  /**
   * Render the items onto the DOM nodes
   * @param  {Object}
   *         {
   *           {Array<DOMElement>} elements
   *           {Array<Object>} items
   *           {Array<Object>} prevState: state of items from last render
   *         }
   */
  _render({ elements, items, prevState }) {
    for (let i = 0, l = items.length; i < l; i++) {
      let el = elements[i];

      // Check if state from last render has changed, if so, update the elements
      if (!prevState[i] || prevState[i].textContent != items[i].textContent) {
        el.textContent = items[i].textContent;
      }
      if (!prevState[i] || prevState[i].className != items[i].className) {
        el.className = items[i].className;
      }
    }
  },

  /**
   * Generate DOM nodes
   *
   * @param  {Number} size: Number of nodes to generate
   * @param  {DOMElement} context: Element to append the nodes to
   * @return {Array<DOMElement>}
   */
  _generateNodes(size, context) {
    let frag = document.createDocumentFragment();
    let refs = [];

    for (let i = 0; i < size; i++) {
      let el = document.createElement("div");
      el.dataset.id = i;
      refs.push(el);
      frag.appendChild(el);
    }
    context.appendChild(frag);

    return refs;
  },

  /**
   * Handle events
   * @param  {DOMEvent} event
   */
  handleEvent(event) {
    switch (event.type) {
      case "click": {
        if (event.target.parentNode == this.context.daysView) {
          let targetId = event.target.dataset.id;
          let targetObj = this.props.days[targetId];
          if (targetObj.enabled) {
            this.props.setSelection(targetObj.dateObj);
          }
        }
        break;
      }
    }
  },

  /**
   * Attach event listener to daysView
   */
  _attachEventListeners() {
    this.context.daysView.addEventListener("click", this);
  }
};
PK
!<Kg

3chrome/toolkit/content/global/bindings/checkbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="checkboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="checkbox" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
    <resources>
      <stylesheet src="chrome://global/skin/checkbox.css"/>
    </resources>
  </binding>

  <binding id="checkbox-baseline" role="xul:checkbox"
    extends="chrome://global/content/bindings/general.xml#basetext">
    <content>
      <xul:image class="checkbox-check" xbl:inherits="checked,disabled"/>
      <xul:hbox class="checkbox-label-box" flex="1">
        <xul:image class="checkbox-icon" xbl:inherits="src"/>
        <xul:label class="checkbox-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULCheckboxElement">
      <method name="setChecked">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var change = (aValue != (this.getAttribute("checked") == "true"));
          if (aValue)
            this.setAttribute("checked", "true");
          else
            this.removeAttribute("checked");
          if (change) {
            var event = document.createEvent("Events");
            event.initEvent("CheckboxStateChange", true, true);
            this.dispatchEvent(event);
          }
          return aValue;
        ]]>
        </body>
      </method>

      <!-- public implementation -->
      <property name="checked"    onset="return this.setChecked(val);"
                                  onget="return this.getAttribute('checked') == 'true';"/>
    </implementation>

    <handlers>
      <!-- While it would seem we could do this by handling oncommand, we need can't
           because any external oncommand handlers might get called before ours, and
           then they would see the incorrect value of checked. -->
      <handler event="click" button="0" action="if (!this.disabled) this.checked = !this.checked;"/>
      <handler event="keypress" key=" ">
        <![CDATA[
          this.checked = !this.checked;
          // Prevent page from scrolling on the space key.
          event.preventDefault();
        ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
PK
!<CȀLL6chrome/toolkit/content/global/bindings/colorpicker.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="colorpickerBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="colorpicker" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/colorpicker.css"/>
    </resources>

    <content>
      <xul:vbox flex="1">

        <xul:hbox>
          <xul:image class="colorpickertile cp-light" color="#FFFFFF"/>
          <xul:image class="colorpickertile cp-light" color="#FFCCCC"/>
          <xul:image class="colorpickertile cp-light" color="#FFCC99"/>
          <xul:image class="colorpickertile cp-light" color="#FFFF99"/>
          <xul:image class="colorpickertile cp-light" color="#FFFFCC"/>
          <xul:image class="colorpickertile cp-light" color="#99FF99"/>
          <xul:image class="colorpickertile cp-light" color="#99FFFF"/>
          <xul:image class="colorpickertile cp-light" color="#CCFFFF"/>
          <xul:image class="colorpickertile cp-light" color="#CCCCFF"/>
          <xul:image class="colorpickertile cp-light" color="#FFCCFF"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#CCCCCC"/>
          <xul:image class="colorpickertile" color="#FF6666"/>
          <xul:image class="colorpickertile" color="#FF9966"/>
          <xul:image class="colorpickertile cp-light" color="#FFFF66"/>
          <xul:image class="colorpickertile cp-light" color="#FFFF33"/>
          <xul:image class="colorpickertile cp-light" color="#66FF99"/>
          <xul:image class="colorpickertile cp-light" color="#33FFFF"/>
          <xul:image class="colorpickertile cp-light" color="#66FFFF"/>
          <xul:image class="colorpickertile" color="#9999FF"/>
          <xul:image class="colorpickertile" color="#FF99FF"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#C0C0C0"/>
          <xul:image class="colorpickertile" color="#FF0000"/>
          <xul:image class="colorpickertile" color="#FF9900"/>
          <xul:image class="colorpickertile" color="#FFCC66"/>
          <xul:image class="colorpickertile cp-light" color="#FFFF00"/>
          <xul:image class="colorpickertile cp-light" color="#33FF33"/>
          <xul:image class="colorpickertile" color="#66CCCC"/>
          <xul:image class="colorpickertile" color="#33CCFF"/>
          <xul:image class="colorpickertile" color="#6666CC"/>
          <xul:image class="colorpickertile" color="#CC66CC"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#999999"/>
          <xul:image class="colorpickertile" color="#CC0000"/>
          <xul:image class="colorpickertile" color="#FF6600"/>
          <xul:image class="colorpickertile" color="#FFCC33"/>
          <xul:image class="colorpickertile" color="#FFCC00"/>
          <xul:image class="colorpickertile" color="#33CC00"/>
          <xul:image class="colorpickertile" color="#00CCCC"/>
          <xul:image class="colorpickertile" color="#3366FF"/>
          <xul:image class="colorpickertile" color="#6633FF"/>
          <xul:image class="colorpickertile" color="#CC33CC"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#666666"/>
          <xul:image class="colorpickertile" color="#990000"/>
          <xul:image class="colorpickertile" color="#CC6600"/>
          <xul:image class="colorpickertile" color="#CC9933"/>
          <xul:image class="colorpickertile" color="#999900"/>
          <xul:image class="colorpickertile" color="#009900"/>
          <xul:image class="colorpickertile" color="#339999"/>
          <xul:image class="colorpickertile" color="#3333FF"/>
          <xul:image class="colorpickertile" color="#6600CC"/>
          <xul:image class="colorpickertile" color="#993399"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#333333"/>
          <xul:image class="colorpickertile" color="#660000"/>
          <xul:image class="colorpickertile" color="#993300"/>
          <xul:image class="colorpickertile" color="#996633"/>
          <xul:image class="colorpickertile" color="#666600"/>
          <xul:image class="colorpickertile" color="#006600"/>
          <xul:image class="colorpickertile" color="#336666"/>
          <xul:image class="colorpickertile" color="#000099"/>
          <xul:image class="colorpickertile" color="#333399"/>
          <xul:image class="colorpickertile" color="#663366"/>
        </xul:hbox>
        <xul:hbox>
          <xul:image class="colorpickertile" color="#000000"/>
          <xul:image class="colorpickertile" color="#330000"/>
          <xul:image class="colorpickertile" color="#663300"/>
          <xul:image class="colorpickertile" color="#663333"/>
          <xul:image class="colorpickertile" color="#333300"/>
          <xul:image class="colorpickertile" color="#003300"/>
          <xul:image class="colorpickertile" color="#003333"/>
          <xul:image class="colorpickertile" color="#000066"/>
          <xul:image class="colorpickertile" color="#330099"/>
          <xul:image class="colorpickertile" color="#330033"/>
        </xul:hbox>
      </xul:vbox>
      <!-- Something to take tab focus
      <button style="border : 0px; width: 0px; height: 0px;"/>
      -->
    </content>

    <implementation implements="nsIDOMEventListener">
      <property name="color">
        <getter><![CDATA[
          return this.mSelectedCell ? this.mSelectedCell.getAttribute("color") : null;
        ]]></getter>
        <setter><![CDATA[
          if (!val)
            return val;
          var uppercaseVal = val.toUpperCase();
          // Translate standard HTML color strings:
          if (uppercaseVal[0] != "#") {
            switch (uppercaseVal) {
              case "GREEN":
                uppercaseVal = "#008000";
                break;
              case "LIME":
                uppercaseVal = "#00FF00";
                break;
              case "OLIVE":
                uppercaseVal = "#808000";
                break;
              case "TEAL":
                uppercaseVal = "#008080";
                break;
              case "YELLOW":
                uppercaseVal = "#FFFF00";
                break;
              case "RED":
                uppercaseVal = "#FF0000";
                break;
              case "MAROON":
                uppercaseVal = "#800000";
                break;
              case "PURPLE":
                uppercaseVal = "#800080";
                break;
              case "FUCHSIA":
                uppercaseVal = "#FF00FF";
                break;
              case "NAVY":
                uppercaseVal = "#000080";
                break;
              case "BLUE":
                uppercaseVal = "#0000FF";
                break;
              case "AQUA":
                uppercaseVal = "#00FFFF";
                break;
              case "WHITE":
                uppercaseVal = "#FFFFFF";
                break;
              case "SILVER":
                uppercaseVal = "#C0C0C0";
                break;
              case "GRAY":
                uppercaseVal = "#808080";
                break;
              default: // BLACK
                uppercaseVal = "#000000";
                break;
            }
          }
          var cells = this.mBox.getElementsByAttribute("color", uppercaseVal);
          if (cells.item(0)) {
            this.selectCell(cells[0]);
            this.hoverCell(this.mSelectedCell);
          }
          return val;
        ]]></setter>
      </property>

      <method name="initColor">
        <parameter name="aColor"/>
        <body><![CDATA[
          // Use this to initialize color without
          //  triggering the "onselect" handler,
          //  which closes window when it's a popup
          this.mDoOnSelect = false;
          this.color = aColor;
          this.mDoOnSelect = true;
        ]]></body>
      </method>

      <method name="initialize">
        <body><![CDATA[
          this.mSelectedCell = null;
          this.mHoverCell = null;
          this.mBox = document.getAnonymousNodes(this)[0];
          this.mIsPopup = false;
          this.mDoOnSelect = true;

          let imageEls = this.mBox.querySelectorAll("image");
          // We set the background of the picker tiles here using images in
          // order for the color to show up even when author colors are
          // disabled or the user is using high contrast mode.
          for (let el of imageEls) {
            let dataURI = "data:image/svg+xml,<svg style='background-color: " +
                          encodeURIComponent(el.getAttribute("color")) +
                          "' xmlns='http://www.w3.org/2000/svg' />";
            el.setAttribute("src", dataURI);
          }

          this.hoverCell(this.mBox.childNodes[0].childNodes[0]);

          // used to capture keydown at the document level
          this.mPickerKeyDown = function(aEvent) {
            document._focusedPicker.pickerKeyDown(aEvent);
          }

        ]]></body>
      </method>

      <method name="_fireEvent">
        <parameter name="aTarget"/>
        <parameter name="aEventName"/>
        <body>
        <![CDATA[
          try {
            var event = document.createEvent("Events");
            event.initEvent(aEventName, true, true);
            var cancel = !aTarget.dispatchEvent(event);
            if (aTarget.hasAttribute("on" + aEventName)) {
              var fn = new Function("event", aTarget.getAttribute("on" + aEventName));
              var rv = fn.call(aTarget, event);
              if (rv == false)
                cancel = true;
            }
            return !cancel;
          } catch (e) {
            Components.utils.reportError(e);
          }
          return false;
        ]]>
        </body>
      </method>

      <method name="resetHover">
        <body><![CDATA[
          if (this.mHoverCell)
            this.mHoverCell.removeAttribute("hover");
        ]]></body>
      </method>

      <method name="getColIndex">
        <parameter name="aCell"/>
        <body><![CDATA[
          var cell = aCell;
          var idx;
          for (idx = -1; cell; idx++)
            cell = cell.previousSibling;

          return idx;
        ]]></body>
      </method>

      <method name="isColorCell">
        <parameter name="aCell"/>
        <body><![CDATA[
          return aCell && aCell.hasAttribute("color");
        ]]></body>
      </method>

      <method name="hoverLeft">
        <body><![CDATA[
          var cell = this.mHoverCell.previousSibling;
          this.hoverCell(cell);
        ]]></body>
      </method>

      <method name="hoverRight">
        <body><![CDATA[
          var cell = this.mHoverCell.nextSibling;
          this.hoverCell(cell);
        ]]></body>
      </method>

      <method name="hoverUp">
        <body><![CDATA[
          var row = this.mHoverCell.parentNode.previousSibling;
          if (row) {
            var colIdx = this.getColIndex(this.mHoverCell);
            var cell = row.childNodes[colIdx];
            this.hoverCell(cell);
          }
        ]]></body>
      </method>

      <method name="hoverDown">
        <body><![CDATA[
          var row = this.mHoverCell.parentNode.nextSibling;
          if (row) {
            var colIdx = this.getColIndex(this.mHoverCell);
            var cell = row.childNodes[colIdx];
            this.hoverCell(cell);
          }
        ]]></body>
      </method>

      <method name="hoverTo">
        <parameter name="aRow"/>
        <parameter name="aCol"/>

        <body><![CDATA[
          var row = this.mBox.childNodes[aRow];
          if (!row) return;
          var cell = row.childNodes[aCol];
          if (!cell) return;
          this.hoverCell(cell);
        ]]></body>
      </method>

      <method name="hoverCell">
        <parameter name="aCell"/>

        <body><![CDATA[
          if (this.isColorCell(aCell)) {
            this.resetHover();
            aCell.setAttribute("hover", "true");
            this.mHoverCell = aCell;
            var event = document.createEvent("Events");
            event.initEvent("DOMMenuItemActive", true, true);
            aCell.dispatchEvent(event);
          }
        ]]></body>
      </method>

      <method name="selectHoverCell">
        <body><![CDATA[
          this.selectCell(this.mHoverCell);
        ]]></body>
      </method>

      <method name="selectCell">
        <parameter name="aCell"/>

        <body><![CDATA[
          if (this.isColorCell(aCell)) {
            if (this.mSelectedCell)
              this.mSelectedCell.removeAttribute("selected");

            this.mSelectedCell = aCell;
            aCell.setAttribute("selected", "true");

            if (this.mDoOnSelect)
              this._fireEvent(this, "select");
          }
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.keyCode) {
            case 37: // left
              this.hoverLeft();
              break;
            case 38: // up
              this.hoverUp();
              break;
            case 39: // right
              this.hoverRight();
              break;
            case 40: // down
              this.hoverDown();
              break;
            case 13: // enter
            case 32: // space
              this.selectHoverCell();
              break;
          }
        ]]></body>
      </method>

	  <constructor><![CDATA[
        this.initialize();
      ]]></constructor>

    </implementation>

    <handlers>
      <handler event="mouseover"><![CDATA[
        this.hoverCell(event.originalTarget);
      ]]></handler>

      <handler event="click"><![CDATA[
        if (event.originalTarget.hasAttribute("color")) {
          this.selectCell(event.originalTarget);
          this.hoverCell(this.mSelectedCell);
        }
      ]]></handler>


      <handler event="focus" phase="capturing">
      <![CDATA[
        if (!this.mIsPopup && this.getAttribute("focused") != "true") {
          this.setAttribute("focused", "true");
          document.addEventListener("keydown", this, true);
          if (this.mSelectedCell)
            this.hoverCell(this.mSelectedCell);
        }
      ]]>
      </handler>

      <handler event="blur" phase="capturing">
      <![CDATA[
        if (!this.mIsPopup && this.getAttribute("focused") == "true") {
          document.removeEventListener("keydown", this, true);
          this.removeAttribute("focused");
          this.resetHover();
        }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="colorpicker-button" display="xul:menu" role="xul:colorpicker"
           extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/colorpicker.css"/>
    </resources>

    <content>
      <xul:image class="colorpicker-button-colorbox" anonid="colorbox" flex="1" xbl:inherits="disabled"/>

      <xul:panel class="colorpicker-button-menupopup"
                 anonid="colorpopup" noautofocus="true" level="top"
                 onmousedown="event.stopPropagation()"
                 onpopupshowing="this._colorPicker.onPopupShowing()"
                 onpopuphiding="this._colorPicker.onPopupHiding()"
                 onselect="this._colorPicker.pickerChange()">
        <xul:colorpicker xbl:inherits="palettename,disabled" allowevents="true" anonid="colorpicker"/>
      </xul:panel>
    </content>

    <implementation>
      <property name="open"
                onget="return this.getAttribute('open') == 'true'"
                onset="this.showPopup();"/>
      <property name="color">
        <getter><![CDATA[
          return this.getAttribute("color");
        ]]></getter>
        <setter><![CDATA[
          this.mColorBox.setAttribute("src",
            "data:image/svg+xml,<svg style='background-color: " +
            encodeURIComponent(val) +
            "' xmlns='http://www.w3.org/2000/svg' />");
          this.setAttribute("color", val);
          return val;
        ]]></setter>
      </property>

      <method name="initialize">
        <body><![CDATA[
          this.mColorBox = document.getAnonymousElementByAttribute(this, "anonid", "colorbox");
          this.mColorBox.setAttribute("src",
            "data:image/svg+xml,<svg style='background-color: " +
            encodeURIComponent(this.color) +
            "' xmlns='http://www.w3.org/2000/svg' />");

          var popup = document.getAnonymousElementByAttribute(this, "anonid", "colorpopup")
          popup._colorPicker = this;

          this.mPicker = document.getAnonymousElementByAttribute(this, "anonid", "colorpicker")
        ]]></body>
      </method>

      <method name="_fireEvent">
        <parameter name="aTarget"/>
        <parameter name="aEventName"/>
        <body>
        <![CDATA[
          try {
            var event = document.createEvent("Events");
            event.initEvent(aEventName, true, true);
            var cancel = !aTarget.dispatchEvent(event);
            if (aTarget.hasAttribute("on" + aEventName)) {
              var fn = new Function("event", aTarget.getAttribute("on" + aEventName));
              var rv = fn.call(aTarget, event);
              if (rv == false)
                cancel = true;
            }
            return !cancel;
          } catch (e) {
            dump(e);
          }
          return false;
        ]]>
        </body>
      </method>

      <method name="showPopup">
        <body><![CDATA[
          this.mPicker.parentNode.openPopup(this, "after_start", 0, 0, false, false);
        ]]></body>
      </method>

      <method name="hidePopup">
        <body><![CDATA[
          this.mPicker.parentNode.hidePopup();
        ]]></body>
      </method>

      <method name="onPopupShowing">
        <body><![CDATA[
          if ("resetHover" in this.mPicker)
            this.mPicker.resetHover();
          document.addEventListener("keydown", this.mPicker, true);
          this.mPicker.mIsPopup = true;
          // Initialize to current button's color
          this.mPicker.initColor(this.color);
        ]]></body>
      </method>

      <method name="onPopupHiding">
        <body><![CDATA[
          // Removes the key listener
          document.removeEventListener("keydown", this.mPicker, true);
          this.mPicker.mIsPopup = false;
        ]]></body>
      </method>

      <method name="pickerChange">
        <body><![CDATA[
          this.color = this.mPicker.color;
          setTimeout(function(aPopup) { aPopup.hidePopup() }, 1, this.mPicker.parentNode);

          this._fireEvent(this, "change");
        ]]></body>
      </method>

      <constructor><![CDATA[
        this.initialize();
      ]]></constructor>

    </implementation>

    <handlers>
      <handler event="keydown"><![CDATA[
        // open popup if key is space/up/left/right/down and popup is closed
        if ( (event.keyCode == 32 || (event.keyCode > 36 && event.keyCode < 41)) && !this.open)
          this.showPopup();
        else if ( (event.keyCode == 27) && this.open)
          this.hidePopup();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="colorpickertile" role="xul:colorpickertile">
  </binding>

</bindings>
PK
!<Ӷ˱((4chrome/toolkit/content/global/bindings/datekeeper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * DateKeeper keeps track of the date states.
 */
function DateKeeper(props) {
  this.init(props);
}

{
  const DAYS_IN_A_WEEK = 7,
        MONTHS_IN_A_YEAR = 12,
        YEAR_VIEW_SIZE = 200,
        YEAR_BUFFER_SIZE = 10,
        // The min value is 0001-01-01 based on HTML spec:
        // https://html.spec.whatwg.org/#valid-date-string
        MIN_DATE = -62135596800000,
        // The max value is derived from the ECMAScript spec:
        // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
        MAX_DATE = 8640000000000000;

  DateKeeper.prototype = {
    get year() {
      return this.state.dateObj.getUTCFullYear();
    },

    get month() {
      return this.state.dateObj.getUTCMonth();
    },

    get day() {
      return this.state.dateObj.getUTCDate();
    },

    get selection() {
      return this.state.selection;
    },

    /**
     * Initialize DateKeeper
     * @param  {Number} year
     * @param  {Number} month
     * @param  {Number} day
     * @param  {Number} min
     * @param  {Number} max
     * @param  {Number} step
     * @param  {Number} stepBase
     * @param  {Number} firstDayOfWeek
     * @param  {Array<Number>} weekends
     * @param  {Number} calViewSize
     */
    init({ year, month, day, min, max, step, stepBase, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
      const today = new Date();
      const isDateSet = year != undefined && month != undefined && day != undefined;

      this.state = {
        step, firstDayOfWeek, weekends, calViewSize,
        // min & max are NaN if empty or invalid
        min: new Date(Number.isNaN(min) ? MIN_DATE : min),
        max: new Date(Number.isNaN(max) ? MAX_DATE : max),
        stepBase: new Date(stepBase),
        today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()),
        weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
        years: [],
        months: [],
        days: [],
        selection: { year, month, day },
      };

      this.state.dateObj = isDateSet ?
                           this._newUTCDate(year, month, day) :
                           new Date(this.state.today);
    },
    /**
     * Set new date. The year is always treated as full year, so the short-form
     * is not supported.
     * @param {Object} date parts
     *        {
     *          {Number} year [optional]
     *          {Number} month [optional]
     *          {Number} date [optional]
     *        }
     */
    set({ year = this.year, month = this.month, day = this.day }) {
      // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
      this.state.dateObj.setUTCFullYear(year, month, day);
    },

    /**
     * Set selection date
     * @param {Number} year
     * @param {Number} month
     * @param {Number} day
     */
    setSelection({ year, month, day }) {
      this.state.selection.year = year;
      this.state.selection.month = month;
      this.state.selection.day = day;
    },

    /**
     * Set month. Makes sure the day is <= the last day of the month
     * @param {Number} month
     */
    setMonth(month) {
      const lastDayOfMonth = this._newUTCDate(this.year, month + 1, 0).getUTCDate();
      this.set({ year: this.year,
                 month,
                 day: Math.min(this.day, lastDayOfMonth) });
    },

    /**
     * Set year. Makes sure the day is <= the last day of the month
     * @param {Number} year
     */
    setYear(year) {
      const lastDayOfMonth = this._newUTCDate(year, this.month + 1, 0).getUTCDate();
      this.set({ year,
                 month: this.month,
                 day: Math.min(this.day, lastDayOfMonth) });
    },

    /**
     * Set month by offset. Makes sure the day is <= the last day of the month
     * @param {Number} offset
     */
    setMonthByOffset(offset) {
      const lastDayOfMonth = this._newUTCDate(this.year, this.month + offset + 1, 0).getUTCDate();
      this.set({ year: this.year,
                 month: this.month + offset,
                 day: Math.min(this.day, lastDayOfMonth) });
    },

    /**
     * Generate the array of months
     * @return {Array<Object>}
     *         {
     *           {Number} value: Month in int
     *           {Boolean} enabled
     *         }
     */
    getMonths() {
      let months = [];

      for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
        months.push({
          value: i,
          enabled: true
        });
      }

      return months;
    },

    /**
     * Generate the array of years
     * @return {Array<Object>}
     *         {
     *           {Number} value: Year in int
     *           {Boolean} enabled
     *         }
     */
    getYears() {
      let years = [];

      const firstItem = this.state.years[0];
      const lastItem = this.state.years[this.state.years.length - 1];
      const currentYear = this.year;

      // Generate new years array when the year is outside of the first &
      // last item range. If not, return the cached result.
      if (!firstItem || !lastItem ||
          currentYear <= firstItem.value + YEAR_BUFFER_SIZE ||
          currentYear >= lastItem.value - YEAR_BUFFER_SIZE) {
        // The year is set in the middle with items on both directions
        for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) {
          years.push({
            value: currentYear + i,
            enabled: true
          });
        }
        this.state.years = years;
      }
      return this.state.years;
    },

    /**
     * Get days for calendar
     * @return {Array<Object>}
     *         {
     *           {Date} dateObj
     *           {Array<String>} classNames
     *           {Boolean} enabled
     *         }
     */
    getDays() {
      const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
      const month = this.month;
      let days = [];

      for (let i = 0; i < this.state.calViewSize; i++) {
        const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(),
                                         firstDayOfMonth.getUTCMonth(),
                                         firstDayOfMonth.getUTCDate() + i);
        let classNames = [];
        let enabled = true;

        const isWeekend = this.state.weekends.includes(dateObj.getUTCDay());
        const isCurrentMonth = month == dateObj.getUTCMonth();
        const isSelection = this.state.selection.year == dateObj.getUTCFullYear() &&
                            this.state.selection.month == dateObj.getUTCMonth() &&
                            this.state.selection.day == dateObj.getUTCDate();
        const isOutOfRange = dateObj.getTime() < this.state.min.getTime() ||
                             dateObj.getTime() > this.state.max.getTime();
        const isToday = this.state.today.getTime() == dateObj.getTime();
        const isOffStep = this._checkIsOffStep(dateObj,
                                               this._newUTCDate(dateObj.getUTCFullYear(),
                                                                dateObj.getUTCMonth(),
                                                                dateObj.getUTCDate() + 1));

        if (isWeekend) {
          classNames.push("weekend");
        }
        if (!isCurrentMonth) {
          classNames.push("outside");
        }
        if (isSelection && !isOutOfRange && !isOffStep) {
          classNames.push("selection");
        }
        if (isOutOfRange) {
          classNames.push("out-of-range");
          enabled = false;
        }
        if (isToday) {
          classNames.push("today");
        }
        if (isOffStep) {
          classNames.push("off-step");
          enabled = false;
        }
        days.push({
          dateObj,
          classNames,
          enabled,
        });
      }
      return days;
    },

    /**
     * Check if a date is off step given a starting point and the next increment
     * @param  {Date} start
     * @param  {Date} next
     * @return {Boolean}
     */
    _checkIsOffStep(start, next) {
      // If the increment is larger or equal to the step, it must not be off-step.
      if (next - start >= this.state.step) {
        return false;
      }
      // Calculate the last valid date
      const lastValidStep = Math.floor((next - 1 - this.state.stepBase) / this.state.step);
      const lastValidTimeInMs = lastValidStep * this.state.step + this.state.stepBase.getTime();
      // The date is off-step if the last valid date is smaller than the start date
      return lastValidTimeInMs < start.getTime();
    },

    /**
     * Get week headers for calendar
     * @param  {Number} firstDayOfWeek
     * @param  {Array<Number>} weekends
     * @return {Array<Object>}
     *         {
     *           {Number} textContent
     *           {Array<String>} classNames
     *         }
     */
    _getWeekHeaders(firstDayOfWeek, weekends) {
      let headers = [];
      let dayOfWeek = firstDayOfWeek;

      for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
        headers.push({
          textContent: dayOfWeek % DAYS_IN_A_WEEK,
          classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
        });
        dayOfWeek++;
      }
      return headers;
    },

    /**
     * Get the first day on a calendar month
     * @param  {Date} dateObj
     * @param  {Number} firstDayOfWeek
     * @return {Date}
     */
    _getFirstCalendarDate(dateObj, firstDayOfWeek) {
      const daysOffset = 1 - DAYS_IN_A_WEEK;
      let firstDayOfMonth = this._newUTCDate(dateObj.getUTCFullYear(), dateObj.getUTCMonth());
      let dayOfWeek = firstDayOfMonth.getUTCDay();

      return this._newUTCDate(
        firstDayOfMonth.getUTCFullYear(),
        firstDayOfMonth.getUTCMonth(),
        // When first calendar date is the same as first day of the week, add
        // another row on top of it.
        firstDayOfWeek == dayOfWeek ? daysOffset : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK);
    },

    /**
     * Helper function for creating UTC dates
     * @param  {...[Number]} parts
     * @return {Date}
     */
    _newUTCDate(...parts) {
      return new Date(Date.UTC(...parts));
    }
  };
}
PK
!<e,e,4chrome/toolkit/content/global/bindings/datepicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 datekeeper.js */
/* import-globals-from calendar.js */
/* import-globals-from spinner.js */

"use strict";

function DatePicker(context) {
  this.context = context;
  this._attachEventListeners();
}

{
  const CAL_VIEW_SIZE = 42;

  DatePicker.prototype = {
    /**
     * Initializes the date picker. Set the default states and properties.
     * @param  {Object} props
     *         {
     *           {Number} year [optional]
     *           {Number} month [optional]
     *           {Number} date [optional]
     *           {Number} min
     *           {Number} max
     *           {Number} step
     *           {Number} stepBase
     *           {Number} firstDayOfWeek
     *           {Array<Number>} weekends
     *           {Array<String>} monthStrings
     *           {Array<String>} weekdayStrings
     *           {String} locale [optional]: User preferred locale
     *         }
     */
    init(props = {}) {
      this.props = props;
      this._setDefaultState();
      this._createComponents();
      this._update();
    },

    /*
     * Set initial date picker states.
     */
    _setDefaultState() {
      const { year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
              monthStrings, weekdayStrings, locale, dir } = this.props;
      const dateKeeper = new DateKeeper({
        year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
        calViewSize: CAL_VIEW_SIZE
      });

      document.dir = dir;

      this.state = {
        dateKeeper,
        locale,
        isMonthPickerVisible: false,
        datetimeOrders: new Intl.DateTimeFormat(locale)
                          .formatToParts(new Date(0)).map(part => part.type),
        getDayString: new Intl.NumberFormat(locale).format,
        getWeekHeaderString: weekday => weekdayStrings[weekday],
        getMonthString: month => monthStrings[month],
        setSelection: date => {
          dateKeeper.setSelection({
            year: date.getUTCFullYear(),
            month: date.getUTCMonth(),
            day: date.getUTCDate(),
          });
          this._update();
          this._dispatchState();
          this._closePopup();
        },
        setYear: year => {
          dateKeeper.setYear(year);
          dateKeeper.setSelection({
            year,
            month: dateKeeper.selection.month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        setMonth: month => {
          dateKeeper.setMonth(month);
          dateKeeper.setSelection({
            year: dateKeeper.selection.year,
            month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        toggleMonthPicker: () => {
          this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
          this._update();
        }
      };
    },

    /**
     * Initalize the date picker components.
     */
    _createComponents() {
      this.components = {
        calendar: new Calendar({
          calViewSize: CAL_VIEW_SIZE,
          locale: this.state.locale
        }, {
          weekHeader: this.context.weekHeader,
          daysView: this.context.daysView
        }),
        monthYear: new MonthYear({
          setYear: this.state.setYear,
          setMonth: this.state.setMonth,
          getMonthString: this.state.getMonthString,
          datetimeOrders: this.state.datetimeOrders,
          locale: this.state.locale
        }, {
          monthYear: this.context.monthYear,
          monthYearView: this.context.monthYearView
        })
      };
    },

    /**
     * Update date picker and its components.
     */
    _update() {
      const { dateKeeper, isMonthPickerVisible } = this.state;

      if (isMonthPickerVisible) {
        this.state.months = dateKeeper.getMonths();
        this.state.years = dateKeeper.getYears();
      } else {
        this.state.days = dateKeeper.getDays();
      }

      this.components.monthYear.setProps({
        isVisible: isMonthPickerVisible,
        dateObj: dateKeeper.state.dateObj,
        months: this.state.months,
        years: this.state.years,
        toggleMonthPicker: this.state.toggleMonthPicker
      });
      this.components.calendar.setProps({
        isVisible: !isMonthPickerVisible,
        days: this.state.days,
        weekHeaders: dateKeeper.state.weekHeaders,
        setSelection: this.state.setSelection,
        getDayString: this.state.getDayString,
        getWeekHeaderString: this.state.getWeekHeaderString
      });

      isMonthPickerVisible ?
        this.context.monthYearView.classList.remove("hidden") :
        this.context.monthYearView.classList.add("hidden");
    },

    /**
     * Use postMessage to close the picker.
     */
    _closePopup() {
      window.postMessage({
        name: "ClosePopup"
      }, "*");
    },

    /**
     * Use postMessage to pass the state of picker to the panel.
     */
    _dispatchState() {
      const { year, month, day } = this.state.dateKeeper.selection;
      // The panel is listening to window for postMessage event, so we
      // do postMessage to itself to send data to input boxes.
      window.postMessage({
        name: "PickerPopupChanged",
        detail: {
          year,
          month,
          day,
        }
      }, "*");
    },

    /**
     * Attach event listeners
     */
    _attachEventListeners() {
      window.addEventListener("message", this);
      document.addEventListener("mouseup", this, { passive: true });
      document.addEventListener("mousedown", this);
    },

    /**
     * Handle events.
     *
     * @param  {Event} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "message": {
          this.handleMessage(event);
          break;
        }
        case "mousedown": {
          // Use preventDefault to keep focus on input boxes
          event.preventDefault();
          event.target.setCapture();

          if (event.target == this.context.buttonPrev) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(-1);
            this._update();
          } else if (event.target == this.context.buttonNext) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(1);
            this._update();
          }
          break;
        }
        case "mouseup": {
          if (event.target == this.context.buttonPrev || event.target == this.context.buttonNext) {
            event.target.classList.remove("active");
          }

        }
      }
    },

    /**
     * Handle postMessage events.
     *
     * @param {Event} event
     */
    handleMessage(event) {
      switch (event.data.name) {
        case "PickerSetValue": {
          this.set(event.data.detail);
          break;
        }
        case "PickerInit": {
          this.init(event.data.detail);
          break;
        }
      }
    },

    /**
     * Set the date state and update the components with the new state.
     *
     * @param {Object} dateState
     *        {
     *          {Number} year [optional]
     *          {Number} month [optional]
     *          {Number} date [optional]
     *        }
     */
    set({ year, month, day }) {
      const { dateKeeper } = this.state;

      dateKeeper.set({
        year, month, day
      });
      dateKeeper.setSelection({
        year, month, day
      });
      this._update();
    }
  };

  /**
   * MonthYear is a component that handles the month & year spinners
   *
   * @param {Object} options
   *        {
   *          {String} locale
   *          {Function} setYear
   *          {Function} setMonth
   *          {Function} getMonthString
   *          {Array<String>} datetimeOrders
   *        }
   * @param {DOMElement} context
   */
  function MonthYear(options, context) {
    const spinnerSize = 5;
    const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
                                                                 timeZone: "UTC" }).format;
    const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
                                                                 month: "long",
                                                                 timeZone: "UTC" }).format;
    const spinnerOrder =
      options.datetimeOrders.indexOf("month") < options.datetimeOrders.indexOf("year") ?
      "order-month-year" : "order-year-month";

    context.monthYearView.classList.add(spinnerOrder);

    this.context = context;
    this.state = { dateFormat };
    this.props = {};
    this.components = {
      month: new Spinner({
        id: "spinner-month",
        setValue: month => {
          this.state.isMonthSet = true;
          options.setMonth(month);
        },
        getDisplayString: options.getMonthString,
        viewportSize: spinnerSize
      }, context.monthYearView),
      year: new Spinner({
        id: "spinner-year",
        setValue: year => {
          this.state.isYearSet = true;
          options.setYear(year);
        },
        getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))),
        viewportSize: spinnerSize
      }, context.monthYearView)
    };

    this._attachEventListeners();
  }

  MonthYear.prototype = {

    /**
     * Set new properties and pass them to components
     *
     * @param {Object} props
     *        {
     *          {Boolean} isVisible
     *          {Date} dateObj
     *          {Array<Object>} months
     *          {Array<Object>} years
     *          {Function} toggleMonthPicker
     *         }
     */
    setProps(props) {
      this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);

      if (props.isVisible) {
        this.context.monthYear.classList.add("active");
        this.components.month.setState({
          value: props.dateObj.getUTCMonth(),
          items: props.months,
          isInfiniteScroll: true,
          isValueSet: this.state.isMonthSet,
          smoothScroll: !this.state.firstOpened
        });
        this.components.year.setState({
          value: props.dateObj.getUTCFullYear(),
          items: props.years,
          isInfiniteScroll: false,
          isValueSet: this.state.isYearSet,
          smoothScroll: !this.state.firstOpened
        });
        this.state.firstOpened = false;
      } else {
        this.context.monthYear.classList.remove("active");
        this.state.isMonthSet = false;
        this.state.isYearSet = false;
        this.state.firstOpened = true;
      }

      this.props = Object.assign(this.props, props);
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "click": {
          this.props.toggleMonthPicker();
          break;
        }
      }
    },

    /**
     * Attach event listener to monthYear button
     */
    _attachEventListeners() {
      this.context.monthYear.addEventListener("click", this);
    }
  };
}
PK
!<m)tt6chrome/toolkit/content/global/bindings/datetimebox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.w3.org/1999/xhtml");
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

.datetime-input-box-wrapper {
  -moz-appearance: none;
  display: inline-flex;
  flex: 1;
  cursor: default;
  background-color: inherit;
  color: inherit;
  font-family: monospace;
  min-width: 0;
  justify-content: space-between;
}

.datetime-input-edit-wrapper {
  overflow: hidden;
  white-space: nowrap;
}

.datetime-edit-field {
  display: inline;
  cursor: default;
  -moz-user-select: none;
  text-align: center;
  padding: 1px 3px;
  border: 0;
  margin: 0;
  ime-mode: disabled;
  font: inherit;
  outline: none;
}

.datetime-edit-field:not([disabled="true"]):focus {
  background-color: Highlight;
  color: HighlightText;
  outline: none;
}

.datetime-edit-field[disabled="true"],
.datetime-edit-field[readonly="true"]  {
  color: GrayText;
  -moz-user-select: none;
}

.datetime-reset-button {
  background-image: url(chrome://global/skin/icons/input-clear.svg);
  background-color: transparent;
  background-repeat: no-repeat;
  background-size: 12px, 12px;
  border: none;
  height: 12px;
  width: 12px;
  align-self: center;
  flex: none;
}
PK
!<r	R776chrome/toolkit/content/global/bindings/datetimebox.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
<!ENTITY % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd">
%datetimeboxDTD;
]>

<bindings id="datetimeboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="date-input"
           extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
    </resources>

    <implementation>
      <constructor>
      <![CDATA[
        /* eslint-disable no-multi-spaces */
        this.mYearPlaceHolder = ]]>"&date.year.placeholder;"<![CDATA[;
        this.mMonthPlaceHolder = ]]>"&date.month.placeholder;"<![CDATA[;
        this.mDayPlaceHolder = ]]>"&date.day.placeholder;"<![CDATA[;
        /* eslint-enable no-multi-spaces */

        this.mMinMonth = 1;
        this.mMaxMonth = 12;
        this.mMinDay = 1;
        this.mMaxDay = 31;
        this.mMinYear = 1;
        // Maximum year limited by ECMAScript date object range, year <= 275760.
        this.mMaxYear = 275760;
        this.mMonthDayLength = 2;
        this.mYearLength = 4;
        this.mMonthPageUpDownInterval = 3;
        this.mDayPageUpDownInterval = 7;
        this.mYearPageUpDownInterval = 10;

        this.buildEditFields();

        if (this.mInputElement.value) {
          this.setFieldsFromInputValue();
        }
      ]]>
      </constructor>

      <method name="buildEditFields">
        <body>
        <![CDATA[
          const HTML_NS = "http://www.w3.org/1999/xhtml";
          let root =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");

          let yearMaxLength = this.mMaxYear.toString().length
          this.mYearField = this.createEditField(this.mYearPlaceHolder,
            true, this.mYearLength, yearMaxLength, this.mMinYear, this.mMaxYear,
            this.mYearPageUpDownInterval);
          this.mMonthField = this.createEditField(this.mMonthPlaceHolder,
            true, this.mMonthDayLength, this.mMonthDayLength, this.mMinMonth,
            this.mMaxMonth, this.mMonthPageUpDownInterval);
          this.mDayField = this.createEditField(this.mDayPlaceHolder,
            true, this.mMonthDayLength, this.mMonthDayLength, this.mMinDay,
            this.mMaxDay, this.mDayPageUpDownInterval);

          let fragment = document.createDocumentFragment();
          let formatter = Intl.DateTimeFormat(this.mLocales, {
            year: "numeric",
            month: "numeric",
            day: "numeric"
          });
          formatter.formatToParts(Date.now()).map(part => {
            switch (part.type) {
              case "year":
                fragment.appendChild(this.mYearField);
                break;
              case "month":
                fragment.appendChild(this.mMonthField);
                break;
              case "day":
                fragment.appendChild(this.mDayField);
                break;
              default:
                let span = document.createElementNS(HTML_NS, "span");
                span.textContent = part.value;
                fragment.appendChild(span);
                break;
            }
          });

          root.appendChild(fragment);
        ]]>
        </body>
      </method>

      <method name="clearInputFields">
        <parameter name="aFromInputElement"/>
        <body>
        <![CDATA[
          this.log("clearInputFields");

          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          if (this.mMonthField && !this.mMonthField.disabled &&
              !this.mMonthField.readOnly) {
            this.clearFieldValue(this.mMonthField);
          }

          if (this.mDayField && !this.mDayField.disabled &&
              !this.mDayField.readOnly) {
            this.clearFieldValue(this.mDayField);
          }

          if (this.mYearField && !this.mYearField.disabled &&
              !this.mYearField.readOnly) {
            this.clearFieldValue(this.mYearField);
          }

          if (!aFromInputElement) {
            if (this.mInputElement.value) {
              this.mInputElement.setUserInput("");
            } else {
              this.mInputElement.updateValidityState();
            }
          }
        ]]>
        </body>
      </method>

      <method name="setFieldsFromInputValue">
        <body>
        <![CDATA[
          let value = this.mInputElement.value;
          if (!value) {
            this.clearInputFields(true);
            return;
          }

          this.log("setFieldsFromInputValue: " + value);
          let [year, month, day] = value.split("-");

          this.setFieldValue(this.mYearField, year);
          this.setFieldValue(this.mMonthField, month);
          this.setFieldValue(this.mDayField, day);

          this.notifyPicker();
        ]]>
        </body>
      </method>

      <method name="setInputValueFromFields">
        <body>
        <![CDATA[
          if (this.isAnyFieldEmpty()) {
            // Clear input element's value if any of the field has been cleared,
            // otherwise update the validity state, since it may become "not"
            // invalid if fields are not complete.
            if (this.mInputElement.value) {
              this.mInputElement.setUserInput("");
            } else {
              this.mInputElement.updateValidityState();
            }
            // We still need to notify picker in case any of the field has
            // changed.
            this.notifyPicker();
            return;
          }

          let { year, month, day } = this.getCurrentValue();

          // Convert to a valid date string according to:
          // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-date-string
          year = year.toString().padStart(this.mYearLength, "0");
          month = (month < 10) ? ("0" + month) : month;
          day = (day < 10) ? ("0" + day) : day;

          let date = [year, month, day].join("-");

          if (date == this.mInputElement.value) {
            return;
          }

          this.log("setInputValueFromFields: " + date);
          this.notifyPicker();
          this.mInputElement.setUserInput(date);
        ]]>
        </body>
      </method>

      <method name="setFieldsFromPicker">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          let year = aValue.year;
          let month = aValue.month;
          let day = aValue.day;

          if (!this.isEmpty(year)) {
            this.setFieldValue(this.mYearField, year);
          }

          if (!this.isEmpty(month)) {
            this.setFieldValue(this.mMonthField, month);
          }

          if (!this.isEmpty(day)) {
            this.setFieldValue(this.mDayField, day);
          }

          // Update input element's .value if needed.
          this.setInputValueFromFields();
        ]]>
        </body>
      </method>

      <method name="handleKeypress">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          let targetField = aEvent.originalTarget;
          let key = aEvent.key;

          if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
            let buffer = targetField.getAttribute("typeBuffer") || "";

            buffer = buffer.concat(key);
            this.setFieldValue(targetField, buffer);

            let n = Number(buffer);
            let max = targetField.getAttribute("max");
            let maxLength = targetField.getAttribute("maxlength");
            if (buffer.length >= maxLength || n * 10 > max) {
              buffer = "";
              this.advanceToNextField();
            }
            targetField.setAttribute("typeBuffer", buffer);
          }
        ]]>
        </body>
      </method>

      <method name="incrementFieldValue">
        <parameter name="aTargetField"/>
        <parameter name="aTimes"/>
        <body>
        <![CDATA[
          let value = this.getFieldValue(aTargetField);

          // Use current date if field is empty.
          if (this.isEmpty(value)) {
            let now = new Date();

            if (aTargetField == this.mYearField) {
              value = now.getFullYear();
            } else if (aTargetField == this.mMonthField) {
              value = now.getMonth() + 1;
            } else if (aTargetField == this.mDayField) {
              value = now.getDate();
            } else {
              this.log("Field not supported in incrementFieldValue.");
              return;
            }
          }

          let min = Number(aTargetField.getAttribute("min"));
          let max = Number(aTargetField.getAttribute("max"));

          value += Number(aTimes);
          if (value > max) {
            value -= (max - min + 1);
          } else if (value < min) {
            value += (max - min + 1);
          }

          this.setFieldValue(aTargetField, value);
        ]]>
        </body>
      </method>

      <method name="handleKeyboardNav">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          let targetField = aEvent.originalTarget;
          let key = aEvent.key;

          // Home/End key does nothing on year field.
          if (targetField == this.mYearField && (key == "Home" ||
                                                 key == "End")) {
            return;
          }

          switch (key) {
            case "ArrowUp":
              this.incrementFieldValue(targetField, 1);
              break;
            case "ArrowDown":
              this.incrementFieldValue(targetField, -1);
              break;
            case "PageUp": {
              let interval = targetField.getAttribute("pginterval");
              this.incrementFieldValue(targetField, interval);
              break;
            }
            case "PageDown": {
              let interval = targetField.getAttribute("pginterval");
              this.incrementFieldValue(targetField, 0 - interval);
              break;
            }
            case "Home":
              let min = targetField.getAttribute("min");
              this.setFieldValue(targetField, min);
              break;
            case "End":
              let max = targetField.getAttribute("max");
              this.setFieldValue(targetField, max);
              break;
          }
          this.setInputValueFromFields();
        ]]>
        </body>
      </method>

      <method name="getCurrentValue">
        <body>
        <![CDATA[
          let year = this.getFieldValue(this.mYearField);
          let month = this.getFieldValue(this.mMonthField);
          let day = this.getFieldValue(this.mDayField);

          let date = { year, month, day };

          this.log("getCurrentValue: " + JSON.stringify(date));
          return date;
        ]]>
        </body>
      </method>

      <method name="setFieldValue">
       <parameter name="aField"/>
       <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (!aField || !aField.classList.contains("numeric")) {
            return;
          }

          let value = Number(aValue);
          if (isNaN(value)) {
            this.log("NaN on setFieldValue!");
            return;
          }

          let maxLength = aField.getAttribute("maxlength");
          if (aValue.length == maxLength) {
            let min = Number(aField.getAttribute("min"));
            let max = Number(aField.getAttribute("max"));

            if (value < min) {
              value = min;
            } else if (value > max) {
              value = max;
            }
          }

          aField.setAttribute("rawValue", value);

          // Display formatted value based on locale.
          let minDigits = aField.getAttribute("mindigits");
          let formatted = value.toLocaleString(this.mLocales, {
            minimumIntegerDigits: minDigits,
            useGrouping: false
          });

          aField.textContent = formatted;
          this.updateResetButtonVisibility();
        ]]>
        </body>
      </method>

      <method name="isAnyFieldAvailable">
        <parameter name="aForPicker"/>
        <body>
        <![CDATA[
          let { year, month, day } = this.getCurrentValue();

          return !this.isEmpty(year) || !this.isEmpty(month) ||
                 !this.isEmpty(day);
        ]]>
        </body>
      </method>

      <method name="isAnyFieldEmpty">
        <body>
        <![CDATA[
          let { year, month, day } = this.getCurrentValue();

          return (this.isEmpty(year) || this.isEmpty(month) ||
                  this.isEmpty(day));
        ]]>
        </body>
      </method>

    </implementation>
  </binding>

  <binding id="time-input"
           extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
    </resources>

    <implementation>
      <property name="kMsPerSecond" readonly="true" onget="return 1000;" />
      <property name="kMsPerMinute" readonly="true" onget="return (60 * 1000);" />

      <constructor>
      <![CDATA[
        const kDefaultAMString = "AM";
        const kDefaultPMString = "PM";

        let { amString, pmString } =
          this.getStringsForLocale(this.mLocales);

        this.mAMIndicator = amString || kDefaultAMString;
        this.mPMIndicator = pmString || kDefaultPMString;

        /* eslint-disable no-multi-spaces */
        this.mHourPlaceHolder = ]]>"&time.hour.placeholder;"<![CDATA[;
        this.mMinutePlaceHolder = ]]>"&time.minute.placeholder;"<![CDATA[;
        this.mSecondPlaceHolder = ]]>"&time.second.placeholder;"<![CDATA[;
        this.mMillisecPlaceHolder = ]]>"&time.millisecond.placeholder;"<![CDATA[;
        this.mDayPeriodPlaceHolder = ]]>"&time.dayperiod.placeholder;"<![CDATA[;
        /* eslint-enable no-multi-spaces */

        this.mHour12 = this.is12HourTime(this.mLocales);
        this.mMillisecSeparatorText = ".";
        this.mMaxLength = 2;
        this.mMillisecMaxLength = 3;
        this.mDefaultStep = 60 * 1000; // in milliseconds

        this.mMinHour = this.mHour12 ? 1 : 0;
        this.mMaxHour = this.mHour12 ? 12 : 23;
        this.mMinMinute = 0;
        this.mMaxMinute = 59;
        this.mMinSecond = 0;
        this.mMaxSecond = 59;
        this.mMinMillisecond = 0;
        this.mMaxMillisecond = 999;

        this.mHourPageUpDownInterval = 3;
        this.mMinSecPageUpDownInterval = 10;

        this.buildEditFields();

        if (this.mInputElement.value) {
          this.setFieldsFromInputValue();
        }
        ]]>
      </constructor>

      <method name="getInputElementValues">
        <body>
        <![CDATA[
          let value = this.mInputElement.value;
          if (value.length === 0) {
            return {};
          }

          let hour, minute, second, millisecond;
          [hour, minute, second] = value.split(":");
          if (second) {
            [second, millisecond] = second.split(".");

            // Convert fraction of second to milliseconds.
            if (millisecond && millisecond.length === 1) {
              millisecond *= 100;
            } else if (millisecond && millisecond.length === 2) {
              millisecond *= 10;
            }
          }

          return { hour, minute, second, millisecond };
        ]]>
        </body>
      </method>

      <method name="hasSecondField">
        <body>
        <![CDATA[
          return !!this.mSecondField;
        ]]>
        </body>
      </method>

      <method name="hasMillisecField">
        <body>
        <![CDATA[
          return !!this.mMillisecField;
        ]]>
        </body>
      </method>

      <method name="hasDayPeriodField">
        <body>
        <![CDATA[
          return !!this.mDayPeriodField;
        ]]>
        </body>
      </method>

      <method name="shouldShowSecondField">
        <body>
        <![CDATA[
          let { second } = this.getInputElementValues();
          if (second != undefined) {
            return true;
          }

          let stepBase = this.mInputElement.getStepBase();
          if ((stepBase % this.kMsPerMinute) != 0) {
            return true;
          }

          let step = this.mInputElement.getStep();
          if ((step % this.kMsPerMinute) != 0) {
            return true;
          }

          return false;
        ]]>
        </body>
      </method>

      <method name="shouldShowMillisecField">
        <body>
        <![CDATA[
          let { millisecond } = this.getInputElementValues();
          if (millisecond != undefined) {
            return true;
          }

          let stepBase = this.mInputElement.getStepBase();
          if ((stepBase % this.kMsPerSecond) != 0) {
            return true;
          }

          let step = this.mInputElement.getStep();
          if ((step % this.kMsPerSecond) != 0) {
            return true;
          }

          return false;
        ]]>
        </body>
      </method>

      <method name="rebuildEditFieldsIfNeeded">
        <body>
        <![CDATA[
          if ((this.shouldShowSecondField() == this.hasSecondField()) &&
              (this.shouldShowMillisecField() == this.hasMillisecField())) {
            return;
          }

          let root =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
          while (root.firstChild) {
            root.firstChild.remove();
          }

          this.mHourField = null;
          this.mMinuteField = null;
          this.mSecondField = null;
          this.mMillisecField = null;

          this.buildEditFields();
        ]]>
        </body>
      </method>

      <method name="buildEditFields">
        <body>
        <![CDATA[
          const HTML_NS = "http://www.w3.org/1999/xhtml";
          let root =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");

          let options = {
            hour: "numeric",
            minute: "numeric",
            hour12: this.mHour12
          };

          this.mHourField = this.createEditField(this.mHourPlaceHolder,
            true, this.mMaxLength, this.mMaxLength, this.mMinHour,
            this.mMaxHour, this.mHourPageUpDownInterval);
          this.mMinuteField = this.createEditField(this.mMinutePlaceHolder,
            true, this.mMaxLength, this.mMaxLength, this.mMinMinute,
            this.mMaxMinute, this.mMinSecPageUpDownInterval);

          if (this.mHour12) {
            this.mDayPeriodField = this.createEditField(
              this.mDayPeriodPlaceHolder, false);
          }

          if (this.shouldShowSecondField()) {
            options["second"] = "numeric";
            this.mSecondField = this.createEditField(this.mSecondPlaceHolder,
              true, this.mMaxLength, this.mMaxLength, this.mMinSecond,
              this.mMaxSecond, this.mMinSecPageUpDownInterval);

            if (this.shouldShowMillisecField()) {
              this.mMillisecField = this.createEditField(
                this.mMillisecPlaceHolder, true, this.mMillisecMaxLength,
                this.mMillisecMaxLength, this.mMinMillisecond,
                this.mMaxMillisecond, this.mMinSecPageUpDownInterval);
            }
          }

          let fragment = document.createDocumentFragment();
          let formatter = Intl.DateTimeFormat(this.mLocales, options);
          formatter.formatToParts(Date.now()).map(part => {
            switch (part.type) {
              case "hour":
                fragment.appendChild(this.mHourField);
                break;
              case "minute":
                fragment.appendChild(this.mMinuteField);
                break;
              case "second":
                fragment.appendChild(this.mSecondField);
                if (this.shouldShowMillisecField()) {
                  // Intl.DateTimeFormat does not support millisecond, so we
                  // need to handle this on our own.
                  let span = document.createElementNS(HTML_NS, "span");
                  span.textContent = this.mMillisecSeparatorText;
                  fragment.appendChild(span);
                  fragment.appendChild(this.mMillisecField);
                }
                break;
              case "dayPeriod":
                fragment.appendChild(this.mDayPeriodField);
                break;
              default:
                let span = document.createElementNS(HTML_NS, "span");
                span.textContent = part.value;
                fragment.appendChild(span);
                break;
            }
          });

          root.appendChild(fragment);
        ]]>
        </body>
      </method>

      <method name="getStringsForLocale">
        <parameter name="aLocales"/>
        <body>
        <![CDATA[
          this.log("getStringsForLocale: " + aLocales);

          let intlUtils = window.intlUtils;
          if (!intlUtils) {
            return {};
          }

          let amString, pmString;
          let keys = [ "dates/gregorian/dayperiods/am",
                       "dates/gregorian/dayperiods/pm" ];

          let result = intlUtils.getDisplayNames(this.mLocales, {
            style: "short",
            keys
          });

          [ amString, pmString ] = keys.map(key => result.values[key]);

          return { amString, pmString };
        ]]>
        </body>
      </method>

      <method name="is12HourTime">
        <parameter name="aLocales"/>
          <body>
          <![CDATA[
            let options = (new Intl.DateTimeFormat(aLocales, {
              hour: "numeric"
            })).resolvedOptions();

            return options.hour12;
          ]]>
          </body>
      </method>

      <method name="setFieldsFromInputValue">
        <body>
        <![CDATA[
          let { hour, minute, second, millisecond } =
            this.getInputElementValues();

          if (this.isEmpty(hour) && this.isEmpty(minute)) {
            this.clearInputFields(true);
            return;
          }

          // Second and millisecond part are optional, rebuild edit fields if
          // needed.
          this.rebuildEditFieldsIfNeeded();

          this.setFieldValue(this.mHourField, hour);
          this.setFieldValue(this.mMinuteField, minute);
          if (this.mHour12) {
            this.setDayPeriodValue(hour >= this.mMaxHour ? this.mPMIndicator
                                                         : this.mAMIndicator);
          }

          if (this.hasSecondField()) {
            this.setFieldValue(this.mSecondField,
              (second != undefined) ? second : 0);
          }

          if (this.hasMillisecField()) {
            this.setFieldValue(this.mMillisecField,
              (millisecond != undefined) ? millisecond : 0);
          }

          this.notifyPicker();
        ]]>
        </body>
      </method>

      <method name="setInputValueFromFields">
        <body>
        <![CDATA[
          if (this.isAnyFieldEmpty()) {
            // Clear input element's value if any of the field has been cleared,
            // otherwise update the validity state, since it may become "not"
            // invalid if fields are not complete.
            if (this.mInputElement.value) {
              this.mInputElement.setUserInput("");
            } else {
              this.mInputElement.updateValidityState();
            }
            // We still need to notify picker in case any of the field has
            // changed.
            this.notifyPicker();
            return;
          }

          let { hour, minute, second, millisecond } = this.getCurrentValue();
          let dayPeriod = this.getDayPeriodValue();

          // Convert to a valid time string according to:
          // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-time-string
          if (this.mHour12) {
            if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
              hour += this.mMaxHour;
            } else if (dayPeriod == this.mAMIndicator &&
                       hour == this.mMaxHour) {
              hour = 0;
            }
          }

          hour = (hour < 10) ? ("0" + hour) : hour;
          minute = (minute < 10) ? ("0" + minute) : minute;

          let time = hour + ":" + minute;
          if (second != undefined) {
            second = (second < 10) ? ("0" + second) : second;
            time += ":" + second;
          }

          if (millisecond != undefined) {
            // Convert milliseconds to fraction of second.
            millisecond = millisecond.toString().padStart(
              this.mMillisecMaxLength, "0");
            time += "." + millisecond;
          }

          if (time == this.mInputElement.value) {
            return;
          }

          this.log("setInputValueFromFields: " + time);
          this.notifyPicker();
          this.mInputElement.setUserInput(time);
        ]]>
        </body>
      </method>

      <method name="setFieldsFromPicker">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          let hour = aValue.hour;
          let minute = aValue.minute;
          this.log("setFieldsFromPicker: " + hour + ":" + minute);

          if (!this.isEmpty(hour)) {
            this.setFieldValue(this.mHourField, hour);
            if (this.mHour12) {
              this.setDayPeriodValue(hour >= this.mMaxHour ? this.mPMIndicator
                                                           : this.mAMIndicator);
            }
          }

          if (!this.isEmpty(minute)) {
            this.setFieldValue(this.mMinuteField, minute);
          }

          // Update input element's .value if needed.
          this.setInputValueFromFields();
        ]]>
        </body>
       </method>

      <method name="clearInputFields">
        <parameter name="aFromInputElement"/>
        <body>
        <![CDATA[
          this.log("clearInputFields");

          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          if (this.mHourField && !this.mHourField.disabled &&
              !this.mHourField.readOnly) {
            this.clearFieldValue(this.mHourField);
          }

          if (this.mMinuteField && !this.mMinuteField.disabled &&
              !this.mMinuteField.readOnly) {
            this.clearFieldValue(this.mMinuteField);
          }

          if (this.hasSecondField() && !this.mSecondField.disabled &&
              !this.mSecondField.readOnly) {
            this.clearFieldValue(this.mSecondField);
          }

          if (this.hasMillisecField() && !this.mMillisecField.disabled &&
              !this.mMillisecField.readOnly) {
            this.clearFieldValue(this.mMillisecField);
          }

          if (this.hasDayPeriodField() && !this.mDayPeriodField.disabled &&
              !this.mDayPeriodField.readOnly) {
            this.clearFieldValue(this.mDayPeriodField);
          }

          if (!aFromInputElement) {
            if (this.mInputElement.value) {
              this.mInputElement.setUserInput("");
            } else {
              this.mInputElement.updateValidityState();
            }
          }
        ]]>
        </body>
      </method>

      <method name="notifyMinMaxStepAttrChanged">
        <body>
        <![CDATA[
          // Second and millisecond part are optional, rebuild edit fields if
          // needed.
          this.rebuildEditFieldsIfNeeded();
          // Fill in values again.
          this.setFieldsFromInputValue();
        ]]>
        </body>
      </method>

      <method name="incrementFieldValue">
        <parameter name="aTargetField"/>
        <parameter name="aTimes"/>
        <body>
        <![CDATA[
          let value = this.getFieldValue(aTargetField);

          // Use current time if field is empty.
          if (this.isEmpty(value)) {
            let now = new Date();

            if (aTargetField == this.mHourField) {
              value = now.getHours();
              if (this.mHour12) {
                value = (value % this.mMaxHour) || this.mMaxHour;
              }
            } else if (aTargetField == this.mMinuteField) {
              value = now.getMinutes();
            } else if (aTargetField == this.mSecondField) {
              value = now.getSeconds();
            } else if (aTargetField == this.mMillisecField) {
              value = now.getMilliseconds();
            } else {
              this.log("Field not supported in incrementFieldValue.");
              return;
            }
          }

          let min = aTargetField.getAttribute("min");
          let max = aTargetField.getAttribute("max");

          value += Number(aTimes);
          if (value > max) {
            value -= (max - min + 1);
          } else if (value < min) {
            value += (max - min + 1);
          }

          this.setFieldValue(aTargetField, value);
        ]]>
        </body>
      </method>

      <method name="handleKeyboardNav">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          let targetField = aEvent.originalTarget;
          let key = aEvent.key;

          if (this.hasDayPeriodField() &&
              targetField == this.mDayPeriodField) {
            // Home/End key does nothing on AM/PM field.
            if (key == "Home" || key == "End") {
              return;
            }

            this.setDayPeriodValue(
              this.getDayPeriodValue() == this.mAMIndicator ? this.mPMIndicator
                                                            : this.mAMIndicator);
            this.setInputValueFromFields();
            return;
          }

          switch (key) {
            case "ArrowUp":
              this.incrementFieldValue(targetField, 1);
              break;
            case "ArrowDown":
              this.incrementFieldValue(targetField, -1);
              break;
            case "PageUp": {
              let interval = targetField.getAttribute("pginterval");
              this.incrementFieldValue(targetField, interval);
              break;
            }
            case "PageDown": {
              let interval = targetField.getAttribute("pginterval");
              this.incrementFieldValue(targetField, 0 - interval);
              break;
            }
            case "Home":
              let min = targetField.getAttribute("min");
              this.setFieldValue(targetField, min);
              break;
            case "End":
              let max = targetField.getAttribute("max");
              this.setFieldValue(targetField, max);
              break;
          }
          this.setInputValueFromFields();
        ]]>
        </body>
      </method>

      <method name="handleKeypress">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this.isDisabled() || this.isReadonly()) {
            return;
          }

          let targetField = aEvent.originalTarget;
          let key = aEvent.key;

          if (this.hasDayPeriodField() &&
              targetField == this.mDayPeriodField) {
            if (key == "a" || key == "A") {
              this.setDayPeriodValue(this.mAMIndicator);
            } else if (key == "p" || key == "P") {
              this.setDayPeriodValue(this.mPMIndicator);
            }
            return;
          }

          if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
            let buffer = targetField.getAttribute("typeBuffer") || "";

            buffer = buffer.concat(key);
            this.setFieldValue(targetField, buffer);

            let n = Number(buffer);
            let max = targetField.getAttribute("max");
            let maxLength = targetField.getAttribute("maxLength");
            if (buffer.length >= maxLength || n * 10 > max) {
              buffer = "";
              this.advanceToNextField();
            }
            targetField.setAttribute("typeBuffer", buffer);
          }
        ]]>
        </body>
      </method>

      <method name="setFieldValue">
       <parameter name="aField"/>
       <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (!aField || !aField.classList.contains("numeric")) {
            return;
          }

          let value = Number(aValue);
          if (isNaN(value)) {
            this.log("NaN on setFieldValue!");
            return;
          }

          if (aField == this.mHourField) {
            if (this.mHour12) {
              // Try to change to 12hr format if user input is 0 or greater
              // than 12.
              let maxLength = aField.getAttribute("maxlength");
              if (value == 0 && aValue.length == maxLength) {
                value = this.mMaxHour;
              } else {
                value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
              }
            } else if (value > this.mMaxHour) {
              value = this.mMaxHour;
            }
          }

          aField.setAttribute("rawValue", value);

          let minDigits = aField.getAttribute("mindigits");
          let formatted = value.toLocaleString(this.mLocales, {
            minimumIntegerDigits: minDigits,
            useGrouping: false
          });

          aField.textContent = formatted;
          this.updateResetButtonVisibility();
        ]]>
        </body>
      </method>

      <method name="getDayPeriodValue">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (!this.hasDayPeriodField()) {
            return "";
          }

          let placeholder = this.mDayPeriodField.placeholder;
          let value = this.mDayPeriodField.textContent;

          return (value == placeholder ? "" : value);
        ]]>
        </body>
      </method>

      <method name="setDayPeriodValue">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (!this.hasDayPeriodField()) {
            return;
          }

          this.mDayPeriodField.textContent = aValue;
          this.updateResetButtonVisibility();
        ]]>
        </body>
      </method>

      <method name="isAnyFieldAvailable">
        <parameter name="aForPicker"/>
        <body>
        <![CDATA[
          let { hour, minute, second, millisecond } = this.getCurrentValue();
          let dayPeriod = this.getDayPeriodValue();

          let available = !this.isEmpty(hour) || !this.isEmpty(minute);
          if (available) {
            return true;
          }

          // Picker only cares about hour:minute.
          if (aForPicker) {
            return false;
          }

          return (this.hasDayPeriodField() && !this.isEmpty(dayPeriod)) ||
                 (this.hasSecondField() && !this.isEmpty(second)) ||
                 (this.hasMillisecField() && !this.isEmpty(millisecond));
        ]]>
        </body>
      </method>

      <method name="isAnyFieldEmpty">
        <body>
        <![CDATA[
          let { hour, minute, second, millisecond } = this.getCurrentValue();
          let dayPeriod = this.getDayPeriodValue();

          return (this.isEmpty(hour) || this.isEmpty(minute) ||
                  (this.hasDayPeriodField() && this.isEmpty(dayPeriod)) ||
                  (this.hasSecondField() && this.isEmpty(second)) ||
                  (this.hasMillisecField() && this.isEmpty(millisecond)));
        ]]>
        </body>
      </method>

      <method name="getCurrentValue">
        <body>
        <![CDATA[
          let hour = this.getFieldValue(this.mHourField);
          if (!this.isEmpty(hour)) {
            if (this.mHour12) {
              let dayPeriod = this.getDayPeriodValue();
              if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
                hour += this.mMaxHour;
              } else if (dayPeriod == this.mAMIndicator &&
                         hour == this.mMaxHour) {
                hour = 0;
              }
            }
          }

          let minute = this.getFieldValue(this.mMinuteField);
          let second = this.getFieldValue(this.mSecondField);
          let millisecond = this.getFieldValue(this.mMillisecField);

          let time = { hour, minute, second, millisecond };

          this.log("getCurrentValue: " + JSON.stringify(time));
          return time;
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="datetime-input-base">
    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
    </resources>

    <content>
      <html:div class="datetime-input-box-wrapper" anonid="input-box-wrapper"
                xbl:inherits="context,disabled,readonly">
        <html:span class="datetime-input-edit-wrapper"
                   anonid="edit-wrapper">
          <!-- Each of the date/time input types will append their input child
             - elements here -->
        </html:span>

        <html:button class="datetime-reset-button" anonid="reset-button"
                     tabindex="-1" xbl:inherits="disabled"/>
      </html:div>
    </content>

    <implementation implements="nsIDateTimeInputArea">
      <constructor>
      <![CDATA[
        this.DEBUG = false;
        this.mInputElement = this.parentNode;
        this.mLocales = window.getRegionalPrefsLocales();

        this.mIsRTL = false;
        let intlUtils = window.intlUtils;
        if (intlUtils) {
          this.mIsRTL =
            intlUtils.getLocaleInfo(this.mLocales).direction === "rtl";
        }

        if (this.mIsRTL) {
          let inputBoxWrapper =
            document.getAnonymousElementByAttribute(this, "anonid",
                                                    "input-box-wrapper");
          inputBoxWrapper.dir = "rtl";
        }

        this.mMin = this.mInputElement.min;
        this.mMax = this.mInputElement.max;
        this.mStep = this.mInputElement.step;
        this.mIsPickerOpen = false;

        this.mResetButton =
          document.getAnonymousElementByAttribute(this, "anonid", "reset-button");
        this.mResetButton.style.visibility = "hidden";

        this.EVENTS.forEach((eventName) => {
          this.addEventListener(eventName, this, { mozSystemGroup: true });
        });
        // Handle keypress separately since we need to catch it on capturing.
        this.addEventListener("keypress", this, {
          capture: true,
          mozSystemGroup: true
        });
        // This is to close the picker when input element blurs.
        this.mInputElement.addEventListener("blur", this,
                                            { mozSystemGroup: true });
      ]]>
      </constructor>

      <destructor>
      <![CDATA[
        this.mInputElement = null;

        this.EVENTS.forEach((eventName) => {
          this.removeEventListener(eventName, this, { mozSystemGroup: true });
        });
        this.removeEventListener("keypress", this, {
          capture: true,
          mozSystemGroup: true
        });
      ]]>
      </destructor>

      <property name="EVENTS" readonly="true">
        <getter>
        <![CDATA[
          return ["click", "focus", "blur", "copy", "cut", "paste", "mousedown"];
        ]]>
        </getter>
      </property>

      <method name="log">
        <parameter name="aMsg"/>
        <body>
        <![CDATA[
          if (this.DEBUG) {
            dump("[DateTimeBox] " + aMsg + "\n");
          }
        ]]>
        </body>
      </method>

      <method name="createEditField">
        <parameter name="aPlaceHolder"/>
        <parameter name="aIsNumeric"/>
        <parameter name="aMinDigits"/>
        <parameter name="aMaxLength"/>
        <parameter name="aMinValue"/>
        <parameter name="aMaxValue"/>
        <parameter name="aPageUpDownInterval"/>
        <body>
        <![CDATA[
          const HTML_NS = "http://www.w3.org/1999/xhtml";

          let field = document.createElementNS(HTML_NS, "span");
          field.classList.add("datetime-edit-field");
          field.textContent = aPlaceHolder;
          field.placeholder = aPlaceHolder;
          field.tabIndex = this.mInputElement.tabIndex;

          field.setAttribute("readonly", this.mInputElement.readOnly);
          field.setAttribute("disabled", this.mInputElement.disabled);
          // Set property as well for convenience.
          field.disabled = this.mInputElement.disabled;
          field.readOnly = this.mInputElement.readOnly;

          if (aIsNumeric) {
            field.classList.add("numeric");
            // Maximum value allowed.
            field.setAttribute("min", aMinValue);
            // Minumim value allowed.
            field.setAttribute("max", aMaxValue);
            // Interval when pressing pageUp/pageDown key.
            field.setAttribute("pginterval", aPageUpDownInterval);
            // Used to store what the user has already typed in the field,
            // cleared when value is cleared and when field is blurred.
            field.setAttribute("typeBuffer", "");
            // Used to store the non-formatted number, clered when value is
            // cleared.
            field.setAttribute("rawValue", "");
            // Minimum digits to display, padded with leading 0s.
            field.setAttribute("mindigits", aMinDigits);
            // Maximum length for the field, will be advance to the next field
            // automatically if exceeded.
            field.setAttribute("maxlength", aMaxLength);

            if (this.mIsRTL) {
              // Force the direction to be "ltr", so that the field stays in the
              // same order even when it's empty (with placeholder). By using
              // "embed", the text inside the element is still displayed based
              // on its directionality.
              field.style.unicodeBidi = "embed";
              field.style.direction = "ltr";
            }
          }

          return field;
        ]]>
        </body>
      </method>

      <method name="updateResetButtonVisibility">
        <body>
          <![CDATA[
            if (this.isAnyFieldAvailable(false)) {
              this.mResetButton.style.visibility = "visible";
            } else {
              this.mResetButton.style.visibility = "hidden";
            }
          ]]>
        </body>
      </method>

      <method name="focusInnerTextBox">
        <body>
        <![CDATA[
          this.log("Focus inner editable field.");

          let editRoot =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");

          for (let child = editRoot.firstChild; child; child = child.nextSibling) {
            if ((child instanceof HTMLSpanElement) &&
                child.classList.contains("datetime-edit-field")) {
              this.mLastFocusedField = child;
              child.focus();
              break;
            }
          }
        ]]>
        </body>
      </method>

      <method name="blurInnerTextBox">
        <body>
        <![CDATA[
          this.log("Blur inner editable field.");

          if (this.mLastFocusedField) {
            this.mLastFocusedField.blur();
          } else {
            // If .mLastFocusedField hasn't been set, blur all editable fields,
            // so that the bound element will actually be blurred. Note that
            // blurring on a element that has no focus won't have any effect.
            let editRoot =
              document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
            for (let child = editRoot.firstChild; child; child = child.nextSibling) {
              if ((child instanceof HTMLSpanElement) &&
                  child.classList.contains("datetime-edit-field")) {
                child.blur();
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="notifyInputElementValueChanged">
        <body>
        <![CDATA[
          this.log("inputElementValueChanged");
          this.setFieldsFromInputValue();
        ]]>
        </body>
      </method>

      <method name="notifyMinMaxStepAttrChanged">
        <body>
        <!-- No operation by default -->
        </body>
      </method>

      <method name="setValueFromPicker">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          this.setFieldsFromPicker(aValue);
        ]]>
        </body>
      </method>

      <method name="hasBadInput">
        <body>
        <![CDATA[
          // Incomplete field does not imply bad input.
          if (this.isAnyFieldEmpty()) {
            return false;
          }

          // All fields are available but input element's value is empty implies
          // it has been sanitized.
          if (!this.mInputElement.value) {
            return true;
          }

          return false;
        ]]>
        </body>
      </method>

      <method name="advanceToNextField">
        <parameter name="aReverse"/>
        <body>
        <![CDATA[
          this.log("advanceToNextField");

          let focusedInput = this.mLastFocusedField;
          let next = aReverse ? focusedInput.previousElementSibling
                              : focusedInput.nextElementSibling;
          if (!next && !aReverse) {
            this.setInputValueFromFields();
            return;
          }

          while (next) {
            if ((next instanceof HTMLSpanElement) &&
                next.classList.contains("datetime-edit-field")) {
              next.focus();
              break;
            }
            next = aReverse ? next.previousElementSibling
                            : next.nextElementSibling;
          }
        ]]>
        </body>
      </method>

      <method name="setPickerState">
        <parameter name="aIsOpen"/>
        <body>
        <![CDATA[
          this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
          this.mIsPickerOpen = aIsOpen;
        ]]>
        </body>
      </method>

      <method name="setEditAttribute">
        <parameter name="aName"/>
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          this.log("setAttribute: " + aName + "=" + aValue);

          if (aName != "tabindex" && aName != "disabled" &&
              aName != "readonly") {
            return;
          }

          let editRoot =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");

          for (let child = editRoot.firstChild; child; child = child.nextSibling) {
            if ((child instanceof HTMLSpanElement) &&
                child.classList.contains("datetime-edit-field")) {

              switch (aName) {
                case "tabindex":
                  child.setAttribute(aName, aValue);
                  break;
                case "disabled": {
                  let value = this.mInputElement.disabled;
                  child.setAttribute("disabled", value);
                  child.disabled = value;
                  break;
                }
                case "readonly": {
                  let value = this.mInputElement.readOnly;
                  child.setAttribute("readonly", value);
                  child.readOnly = value;
                  break;
                }
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="removeEditAttribute">
        <parameter name="aName"/>
        <body>
        <![CDATA[
          this.log("removeAttribute: " + aName);

          if (aName != "tabindex" && aName != "disabled" &&
              aName != "readonly") {
            return;
          }

          let editRoot =
            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");

          for (let child = editRoot.firstChild; child; child = child.nextSibling) {
            if ((child instanceof HTMLSpanElement) &&
                child.classList.contains("datetime-edit-field")) {
              child.removeAttribute(aName);
              // Update property as well.
              if (aName == "readonly") {
                child.readOnly = false;
              } else if (aName == "disabled") {
                child.disabled = false;
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="isEmpty">
        <parameter name="aValue"/>
        <body>
          return (aValue == undefined || 0 === aValue.length);
        </body>
      </method>

      <method name="getFieldValue">
        <parameter name="aField"/>
        <body>
        <![CDATA[
          if (!aField || !aField.classList.contains("numeric")) {
            return undefined;
          }

          let value = aField.getAttribute("rawValue");
          // Avoid returning 0 when field is empty.
          return (this.isEmpty(value) ? undefined : Number(value));
        ]]>
        </body>
      </method>

      <method name="clearFieldValue">
        <parameter name="aField"/>
        <body>
        <![CDATA[
          aField.textContent = aField.placeholder;
          if (aField.classList.contains("numeric")) {
            aField.setAttribute("typeBuffer", "");
            aField.setAttribute("rawValue", "");
          }
          this.updateResetButtonVisibility();
        ]]>
        </body>
      </method>

      <method name="setFieldValue">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="clearInputFields">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="setFieldsFromInputValue">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="setInputValueFromFields">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="setFieldsFromPicker">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="handleKeypress">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="handleKeyboardNav">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="getCurrentValue">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="isAnyFieldAvailable">
        <body>
          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        </body>
      </method>

      <method name="notifyPicker">
        <body>
        <![CDATA[
          if (this.mIsPickerOpen && this.isAnyFieldAvailable(true)) {
            this.mInputElement.updateDateTimePicker(this.getCurrentValue());
          }
        ]]>
        </body>
      </method>

      <method name="isDisabled">
        <body>
        <![CDATA[
          return this.mInputElement.hasAttribute("disabled");
        ]]>
        </body>
      </method>

      <method name="isReadonly">
        <body>
        <![CDATA[
          return this.mInputElement.hasAttribute("readonly");
        ]]>
        </body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this.log("handleEvent: " + aEvent.type);

          switch (aEvent.type) {
            case "keypress": {
              this.onKeyPress(aEvent);
              break;
            }
            case "click": {
              this.onClick(aEvent);
              break;
            }
            case "focus": {
              this.onFocus(aEvent);
              break;
            }
            case "blur": {
              this.onBlur(aEvent);
              break;
            }
            case "mousedown": {
              if (aEvent.originalTarget == this.mResetButton) {
                aEvent.preventDefault();
              }
              break;
            }
            case "copy":
            case "cut":
            case "paste": {
              aEvent.preventDefault();
              break;
            }
            default:
              break;
          }
        ]]>
        </body>
      </method>

      <method name="onFocus">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this.log("onFocus originalTarget: " + aEvent.originalTarget);

          if (document.activeElement != this.mInputElement) {
            return;
          }

          let target = aEvent.originalTarget;
          if ((target instanceof HTMLSpanElement) &&
              target.classList.contains("datetime-edit-field")) {
            if (target.disabled) {
              return;
            }
            this.mLastFocusedField = target;
            this.mInputElement.setFocusState(true);
          }
        ]]>
        </body>
      </method>

      <method name="onBlur">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this.log("onBlur originalTarget: " + aEvent.originalTarget +
            " target: " + aEvent.target);

          if (aEvent.target == this.mInputElement && this.mIsPickerOpen) {
            this.mInputElement.closeDateTimePicker();
          }

          let target = aEvent.originalTarget;
          target.setAttribute("typeBuffer", "");
          this.setInputValueFromFields();
          this.mInputElement.setFocusState(false);
        ]]>
        </body>
      </method>

      <method name="onKeyPress">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this.log("onKeyPress key: " + aEvent.key);

          switch (aEvent.key) {
            // Close picker on Enter, Escape or Space key.
            case "Enter":
            case "Escape":
            case " ": {
              if (this.mIsPickerOpen) {
                this.mInputElement.closeDateTimePicker();
                aEvent.preventDefault();
              }
              break;
            }
            case "Backspace": {
              let targetField = aEvent.originalTarget;
              this.clearFieldValue(targetField);
              this.setInputValueFromFields();
              aEvent.preventDefault();
              break;
            }
            case "ArrowRight":
            case "ArrowLeft": {
              this.advanceToNextField(!(aEvent.key == "ArrowRight"));
              aEvent.preventDefault();
              break;
            }
            case "ArrowUp":
            case "ArrowDown":
            case "PageUp":
            case "PageDown":
            case "Home":
            case "End": {
              this.handleKeyboardNav(aEvent);
              aEvent.preventDefault();
              break;
            }
            default: {
              // printable characters
              if (aEvent.keyCode == 0 &&
                  !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)) {
                this.handleKeypress(aEvent);
                aEvent.preventDefault();
              }
              break;
            }
          }
        ]]>
        </body>
      </method>

      <method name="onClick">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          this.log("onClick originalTarget: " + aEvent.originalTarget);

          // XXX: .originalTarget is not expected.
          // When clicking on one of the inner text boxes, the .originalTarget is
          // a HTMLDivElement and when clicking on the reset button, it's a
          // HTMLButtonElement.
          if (aEvent.defaultPrevented || this.isDisabled() || this.isReadonly()) {
            return;
          }

          if (aEvent.originalTarget == this.mResetButton) {
            this.clearInputFields(false);
          } else if (!this.mIsPickerOpen) {
            this.mInputElement.openDateTimePicker(this.getCurrentValue());
          }
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

</bindings>
PK
!<LE9chrome/toolkit/content/global/bindings/datetimepicker.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
  <!ENTITY % datetimepickerDTD SYSTEM "chrome://global/locale/datetimepicker.dtd">
  %datetimepickerDTD;
]>

<bindings id="timepickerBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="datetimepicker-base"
           extends="chrome://global/content/bindings/general.xml#basecontrol">

    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
      <stylesheet src="chrome://global/skin/dropmarker.css"/>
      <stylesheet src="chrome://global/skin/datetimepicker.css"/>
    </resources>

    <content align="center">
      <xul:hbox class="datetimepicker-input-box" align="center"
                xbl:inherits="context,disabled,readonly">
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-one"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-two"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-three"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled"
                       onup="this.parentNode._increaseOrDecrease(1);"
                       ondown="this.parentNode._increaseOrDecrease(-1);"/>
    </content>

    <implementation>
      <field name="_dateValue">null</field>
      <field name="_fieldOne">
        document.getAnonymousElementByAttribute(this, "anonid", "input-one");
      </field>
      <field name="_fieldTwo">
        document.getAnonymousElementByAttribute(this, "anonid", "input-two");
      </field>
      <field name="_fieldThree">
        document.getAnonymousElementByAttribute(this, "anonid", "input-three");
      </field>
      <field name="_fieldAMPM">
        document.getAnonymousElementByAttribute(this, "anonid", "input-ampm");
      </field>
      <field name="_separatorFirst">
        document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
      </field>
      <field name="_separatorSecond">
        document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
      </field>
      <field name="_lastFocusedField">null</field>
      <field name="_hasEntry">true</field>
      <field name="_valueEntered">false</field>
      <field name="attachedControl">null</field>

      <property name="_currentField" readonly="true">
        <getter>
          var focusedInput = document.activeElement;
          if (focusedInput == this._fieldOne ||
              focusedInput == this._fieldTwo ||
              focusedInput == this._fieldThree ||
              focusedInput == this._fieldAMPM)
            return focusedInput;
          return this._lastFocusedField || this._fieldOne;
        </getter>
      </property>

      <property name="dateValue" onget="return new Date(this._dateValue);">
        <setter>
          <![CDATA[
            if (!(val instanceof Date))
              throw "Invalid Date";

            this._setValueNoSync(val);
            if (this.attachedControl)
              this.attachedControl._setValueNoSync(val);
            return val;
          ]]>
        </setter>
      </property>

      <property name="readOnly" onset="if (val) this.setAttribute('readonly', 'true');
                                       else this.removeAttribute('readonly'); return val;"
                                onget="return this.getAttribute('readonly') == 'true';"/>

      <method name="_fireEvent">
        <parameter name="aEventName"/>
        <parameter name="aTarget"/>
        <body>
          var event = document.createEvent("Events");
          event.initEvent(aEventName, true, true);
          return !aTarget.dispatchEvent(event);
        </body>
      </method>

      <method name="_setValueOnChange">
        <parameter name="aField"/>
        <body>
          <![CDATA[
            if (!this._hasEntry)
              return;

            if (aField == this._fieldOne ||
                aField == this._fieldTwo ||
                aField == this._fieldThree) {
              var value = Number(aField.value);
              if (isNaN(value))
                value = 0;

              value = this._constrainValue(aField, value, true);
              this._setFieldValue(aField, value);
            }
          ]]>
        </body>
      </method>

      <method name="_init">
        <body/>
      </method>

      <constructor>
        this._init();

        var cval = this.getAttribute("value");
        if (cval) {
          try {
            this.value = cval;
            return;
          } catch (ex) { }
        }
        this.dateValue = new Date();
      </constructor>

      <destructor>
        if (this.attachedControl) {
          this.attachedControl.attachedControl = null;
          this.attachedControl = null;
        }
      </destructor>

    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        <![CDATA[
          var target = event.originalTarget;
          if (target == this._fieldOne ||
              target == this._fieldTwo ||
              target == this._fieldThree ||
              target == this._fieldAMPM)
            this._lastFocusedField = target;
        ]]>
      </handler>

      <handler event="keypress">
        <![CDATA[
          if (this._hasEntry && event.charCode &&
              this._currentField != this._fieldAMPM &&
	            !(event.altKey || event.ctrlKey || event.metaKey) &&
              (event.charCode < 48 || event.charCode > 57))
            event.preventDefault();
        ]]>
      </handler>

      <handler event="keypress" keycode="VK_UP">
        if (this._hasEntry)
          this._increaseOrDecrease(1);
      </handler>
      <handler event="keypress" keycode="VK_DOWN">
        if (this._hasEntry)
          this._increaseOrDecrease(-1);
      </handler>

      <handler event="input">
        this._valueEntered = true;
      </handler>

      <handler event="change">
        this._setValueOnChange(event.originalTarget);
      </handler>
    </handlers>

  </binding>

  <binding id="timepicker"
           extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">

    <implementation>
      <field name="is24HourClock">false</field>
      <field name="hourLeadingZero">false</field>
      <field name="minuteLeadingZero">true</field>
      <field name="secondLeadingZero">true</field>
      <field name="amIndicator">"AM"</field>
      <field name="pmIndicator">"PM"</field>

      <field name="hourField">null</field>
      <field name="minuteField">null</field>
      <field name="secondField">null</field>

      <property name="value">
        <getter>
          <![CDATA[
            var minute = this._dateValue.getMinutes();
            if (minute < 10)
              minute = "0" + minute;

            var second = this._dateValue.getSeconds();
            if (second < 10)
              second = "0" + second;
            return this._dateValue.getHours() + ":" + minute + ":" + second;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var items = val.match(/^([0-9]{1,2})\:([0-9]{1,2})\:?([0-9]{1,2})?$/);
            if (!items)
              throw "Invalid Time";

            var dt = this.dateValue;
            dt.setHours(items[1]);
            dt.setMinutes(items[2]);
            dt.setSeconds(items[3] ? items[3] : 0);
            this.dateValue = dt;
            return val;
          ]]>
        </setter>
      </property>
      <property name="hour" onget="return this._dateValue.getHours();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 23)
              throw "Invalid Hour";
            this._setFieldValue(this.hourField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="minute" onget="return this._dateValue.getMinutes();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 59)
              throw "Invalid Minute";
            this._setFieldValue(this.minuteField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="second" onget="return this._dateValue.getSeconds();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 59)
              throw "Invalid Second";
            this._setFieldValue(this.secondField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="isPM">
        <getter>
          <![CDATA[
            return (this.hour >= 12);
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (val) {
              if (this.hour < 12)
                this.hour += 12;
            } else if (this.hour >= 12)
              this.hour -= 12;
            return val;
          ]]>
        </setter>
      </property>
      <property name="hideSeconds">
        <getter>
          return (this.getAttribute("hideseconds") == "true");
        </getter>
        <setter>
          if (val)
            this.setAttribute("hideseconds", "true");
          else
            this.removeAttribute("hideseconds");
          if (this.secondField)
            this.secondField.parentNode.collapsed = val;
          this._separatorSecond.collapsed = val;
          return val;
        </setter>
      </property>
      <property name="increment">
        <getter>
          <![CDATA[
            var increment = this.getAttribute("increment");
            increment = Number(increment);
            if (isNaN(increment) || increment <= 0 || increment >= 60)
              return 1;
            return increment;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (typeof val == "number")
              this.setAttribute("increment", val);
            return val;
          ]]>
        </setter>
      </property>

      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI(this.hourField, this.hour);
              this._updateUI(this.minuteField, this.minute);
              this._updateUI(this.secondField, this.second);
            }
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecrease">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;

            var field = this._currentField;
            if (this._valueEntered)
              this._setValueOnChange(field);

            if (field == this._fieldAMPM) {
              this.isPM = !this.isPM;
              this._fireEvent("change", this);
            } else {
              var oldval;
              var change = aDir;
              if (field == this.hourField) {
                oldval = this.hour;
              } else if (field == this.minuteField) {
                oldval = this.minute;
                change *= this.increment;
              } else if (field == this.secondField) {
                oldval = this.second;
              }

              var newval = this._constrainValue(field, oldval + change, false);

              if (field == this.hourField)
                this.hour = newval;
              else if (field == this.minuteField)
                this.minute = newval;
              else if (field == this.secondField)
                this.second = newval;

              if (oldval != newval)
                this._fireEvent("change", this);
            }
            field.select();
          ]]>
        </body>
      </method>
      <method name="_setFieldValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            if (aField == this.hourField)
              this._dateValue.setHours(aValue);
            else if (aField == this.minuteField)
              this._dateValue.setMinutes(aValue);
            else if (aField == this.secondField)
              this._dateValue.setSeconds(aValue);

            this.setAttribute("value", this.value);
            this._updateUI(aField, aValue);

            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            this._valueEntered = false;

            var prependZero = false;
            if (aField == this.hourField) {
              prependZero = this.hourLeadingZero;
              if (!this.is24HourClock) {
                if (aValue >= 12) {
                  if (aValue > 12)
                    aValue -= 12;
                  this._fieldAMPM.value = this.pmIndicator;
                } else {
                  if (aValue == 0)
                    aValue = 12;
                  this._fieldAMPM.value = this.amIndicator;
                }
              }
            } else if (aField == this.minuteField) {
              prependZero = this.minuteLeadingZero;
            } else if (aField == this.secondField) {
              prependZero = this.secondLeadingZero;
            }

            if (prependZero && aValue < 10)
              aField.value = "0" + aValue;
            else
              aField.value = aValue;
          ]]>
        </body>
      </method>
      <method name="_constrainValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aNoWrap"/>
        <body>
          <![CDATA[
            // aNoWrap is true when the user entered a value, so just
            // constrain within limits. If false, the value is being
            // incremented or decremented, so wrap around values
            var max = (aField == this.hourField) ? 24 : 60;
            if (aValue < 0)
              return aNoWrap ? 0 : max + aValue;
            if (aValue >= max)
              return aNoWrap ? max - 1 : aValue - max;
            return aValue;
          ]]>
        </body>
      </method>
      <method name="_init">
        <body>
          <![CDATA[
            this.hourField = this._fieldOne;
            this.minuteField = this._fieldTwo;
            this.secondField = this._fieldThree;

            var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;

            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";

            var pmTime = new Date(2000, 0, 1, 16, 7, 9).toLocaleTimeString(locale);
            var numberFields = pmTime.match(numberOrder);
            if (numberFields) {
              this._separatorFirst.value = numberFields[3];
              this._separatorSecond.value = numberFields[5];
              if (Number(numberFields[2]) > 12)
                this.is24HourClock = true;
              else
                this.pmIndicator = numberFields[1] || numberFields[7];
            }

            var amTime = new Date(2000, 0, 1, 1, 7, 9).toLocaleTimeString(locale);
            numberFields = amTime.match(numberOrder);
            if (numberFields) {
              this.hourLeadingZero = (numberFields[2].length > 1);
              this.minuteLeadingZero = (numberFields[4].length > 1);
              this.secondLeadingZero = (numberFields[6].length > 1);

              if (!this.is24HourClock) {
                this.amIndicator = numberFields[1] || numberFields[7];
                if (numberFields[1]) {
                  var mfield = this._fieldAMPM.parentNode;
                  var mcontainer = mfield.parentNode;
                  mcontainer.insertBefore(mfield, mcontainer.firstChild);
                }
                var size = (numberFields[1] || numberFields[7]).length;
                if (this.pmIndicator.length > size)
                  size = this.pmIndicator.length;
                this._fieldAMPM.size = size;
                this._fieldAMPM.maxLength = size;
              } else {
                this._fieldAMPM.parentNode.collapsed = true;
              }
            }

            this.hideSeconds = this.hideSeconds;
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress">
        <![CDATA[
          // just allow any printable character to switch the AM/PM state
          if (event.charCode && !this.disabled && !this.readOnly &&
              this._currentField == this._fieldAMPM) {
            this.isPM = !this.isPM;
            this._fieldAMPM.select();
            this._fireEvent("change", this);
            event.preventDefault();
          }
        ]]>
      </handler>
    </handlers>

  </binding>

  <binding id="datepicker"
           extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">

    <implementation>
      <field name="yearLeadingZero">false</field>
      <field name="monthLeadingZero">true</field>
      <field name="dateLeadingZero">true</field>

      <field name="yearField"/>
      <field name="monthField"/>
      <field name="dateField"/>

      <property name="value">
        <getter>
          <![CDATA[
            var month = this._dateValue.getMonth();
            month = (month < 9) ? month = "0" + ++month : month + 1;

            var date = this._dateValue.getDate();
            if (date < 10)
              date = "0" + date;
            return this._dateValue.getFullYear() + "-" + month + "-" + date;
          ]]>

        </getter>
        <setter>
          <![CDATA[
            var results = val.match(/^([0-9]{1,4})\-([0-9]{1,2})\-([0-9]{1,2})$/);
            if (!results)
              throw "Invalid Date";

            this.dateValue = new Date(results[1] + "/" + results[2] + "/" + results[3]);
            this.setAttribute("value", this.value);
            return val;
          ]]>
        </setter>
      </property>
      <property name="year" onget="return this._dateValue.getFullYear();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 1 || valnum > 9999)
              throw "Invalid Year";
            this._setFieldValue(this.yearField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="month" onget="return this._dateValue.getMonth();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 0 || valnum > 11)
              throw "Invalid Month";
            this._setFieldValue(this.monthField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="date" onget="return this._dateValue.getDate();">
        <setter>
          <![CDATA[
            var valnum = Number(val);
            if (isNaN(valnum) || valnum < 1 || valnum > 31)
              throw "Invalid Date";
            this._setFieldValue(this.dateField, valnum);
            return val;
          ]]>
        </setter>
      </property>
      <property name="open" onget="return false;" onset="return val;"/>

      <property name="displayedMonth" onget="return this.month;"
                onset="this.month = val; return val;"/>
      <property name="displayedYear" onget="return this.year;"
                onset="this.year = val; return val;"/>

      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI(this.yearField, this.year);
              this._updateUI(this.monthField, this.month);
              this._updateUI(this.dateField, this.date);
            }
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecrease">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;

            var field = this._currentField;
            if (this._valueEntered)
              this._setValueOnChange(field);

            var oldval;
            if (field == this.yearField)
              oldval = this.year;
            else if (field == this.monthField)
              oldval = this.month;
            else if (field == this.dateField)
              oldval = this.date;

            var newval = this._constrainValue(field, oldval + aDir, false);

            if (field == this.yearField)
              this.year = newval;
            else if (field == this.monthField)
              this.month = newval;
            else if (field == this.dateField)
              this.date = newval;

            if (oldval != newval)
              this._fireEvent("change", this);
            field.select();
          ]]>
        </body>
      </method>
      <method name="_setFieldValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            if (aField == this.yearField) {
              let oldDate = this.date;
              this._dateValue.setFullYear(aValue);
              if (oldDate != this.date) {
                this._dateValue.setDate(0);
                this._updateUI(this.dateField, this.date);
              }
            } else if (aField == this.monthField) {
              let oldDate = this.date;
              this._dateValue.setMonth(aValue);
              if (oldDate != this.date) {
                this._dateValue.setDate(0);
                this._updateUI(this.dateField, this.date);
              }
            } else if (aField == this.dateField) {
              this._dateValue.setDate(aValue);
            }

            this.setAttribute("value", this.value);
            this._updateUI(aField, aValue);

            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            this._valueEntered = false;

            var prependZero = false;
            if (aField == this.yearField) {
              if (this.yearLeadingZero) {
                aField.value = ("000" + aValue).slice(-4);
                return;
              }
            } else if (aField == this.monthField) {
              aValue++;
              prependZero = this.monthLeadingZero;
            } else if (aField == this.dateField) {
              prependZero = this.dateLeadingZero;
            }
            if (prependZero && aValue < 10)
              aField.value = "0" + aValue;
            else
              aField.value = aValue;
          ]]>
        </body>
      </method>
      <method name="_constrainValue">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aNoWrap"/>
        <body>
          <![CDATA[
            // the month will be 1 to 12 if entered by the user, so subtract 1
            if (aNoWrap && aField == this.monthField)
              aValue--;

            if (aField == this.dateField) {
              if (aValue < 1)
                return new Date(this.year, this.month + 1, 0).getDate();

              var currentMonth = this.month;
              var dt = new Date(this.year, currentMonth, aValue);
              return (dt.getMonth() != currentMonth ? 1 : aValue);
            }
            var min = (aField == this.monthField) ? 0 : 1;
            var max = (aField == this.monthField) ? 11 : 9999;
            if (aValue < min)
              return aNoWrap ? min : max;
            if (aValue > max)
              return aNoWrap ? max : min;
            return aValue;
          ]]>
        </body>
      </method>
      <method name="_init">
        <body>
          <![CDATA[
            // We'll default to YYYY/MM/DD to start.
            var yfield = "input-one";
            var mfield = "input-two";
            var dfield = "input-three";
            var twoDigitYear = false;
            this.yearLeadingZero = true;
            this.monthLeadingZero = true;
            this.dateLeadingZero = true;

            var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;

            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";

            var dt = new Date(2002, 9, 4).toLocaleDateString(locale);
            var numberFields = dt.match(numberOrder);
            if (numberFields) {
              this._separatorFirst.value = numberFields[3];
              this._separatorSecond.value = numberFields[5];

              var yi = 2, mi = 4, di = 6;

              function fieldForNumber(i) {
                if (i == 2)
                  return "input-one";
                if (i == 4)
                  return "input-two";
                return "input-three";
              }

              for (var i = 1; i < numberFields.length; i++) {
                switch (Number(numberFields[i])) {
                  case 2:
                    twoDigitYear = true; // fall through
                  case 2002:
                    yi = i;
                    yfield = fieldForNumber(i);
                    break;
                  case 9, 10:
                    mi = i;
                    mfield = fieldForNumber(i);
                    break;
                  case 4:
                    di = i;
                    dfield = fieldForNumber(i);
                    break;
                }
              }

              this.yearLeadingZero = (numberFields[yi].length > 1);
              this.monthLeadingZero = (numberFields[mi].length > 1);
              this.dateLeadingZero = (numberFields[di].length > 1);
            }

            this.yearField = document.getAnonymousElementByAttribute(this, "anonid", yfield);
            if (!twoDigitYear)
              this.yearField.parentNode.classList.add("datetimepicker-input-subbox", "datetimepicker-year");
            this.monthField = document.getAnonymousElementByAttribute(this, "anonid", mfield);
            this.dateField = document.getAnonymousElementByAttribute(this, "anonid", dfield);

            this._fieldAMPM.parentNode.collapsed = true;
            this.yearField.size = twoDigitYear ? 2 : 4;
            this.yearField.maxLength = twoDigitYear ? 2 : 4;
          ]]>
        </body>
      </method>
    </implementation>

  </binding>

  <binding id="datepicker-grid"
           extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">

    <content>
      <vbox class="datepicker-mainbox"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
        <hbox class="datepicker-monthbox" align="center">
          <button class="datepicker-previous datepicker-button" type="repeat"
                  xbl:inherits="disabled"
                  oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(-1);"/>
          <spacer flex="1"/>
          <deck anonid="monthlabeldeck">
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
            <label class="datepicker-gridlabel" value=""/>
          </deck>
          <label anonid="yearlabel" class="datepicker-gridlabel"/>
          <spacer flex="1"/>
          <button class="datepicker-next datepicker-button" type="repeat"
                  xbl:inherits="disabled"
                  oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(1);"/>
        </hbox>
        <grid class="datepicker-grid" role="grid">
          <columns>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
            <column class="datepicker-gridrow" flex="1"/>
          </columns>
          <rows anonid="datebox">
            <row anonid="dayofweekbox">
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
              <label class="datepicker-weeklabel" role="columnheader"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
            <row>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
              <label class="datepicker-gridlabel" role="gridcell"/>
            </row>
          </rows>
        </grid>
      </vbox>
    </content>

    <implementation>
      <field name="_hasEntry">false</field>
      <field name="_weekStart">&firstdayofweek.default;</field>
      <field name="_displayedDate">null</field>
      <field name="_todayItem">null</field>

      <field name="yearField">
        document.getAnonymousElementByAttribute(this, "anonid", "yearlabel");
      </field>
      <field name="monthField">
        document.getAnonymousElementByAttribute(this, "anonid", "monthlabeldeck");
      </field>
      <field name="dateField">
        document.getAnonymousElementByAttribute(this, "anonid", "datebox");
      </field>

      <field name="_selectedItem">null</field>

      <property name="selectedItem" onget="return this._selectedItem">
        <setter>
          <![CDATA[
            if (!val.value)
              return val;
            if (val.parentNode.parentNode != this.dateField)
              return val;

            if (this._selectedItem)
              this._selectedItem.removeAttribute("selected");
            this._selectedItem = val;
            val.setAttribute("selected", "true");
            this._displayedDate.setDate(val.value);
            return val;
          ]]>
        </setter>
      </property>

      <property name="displayedMonth">
        <getter>
          return this._displayedDate.getMonth();
        </getter>
        <setter>
          this._updateUI(this.monthField, val, true);
          return val;
        </setter>
      </property>
      <property name="displayedYear">
        <getter>
          return this._displayedDate.getFullYear();
        </getter>
        <setter>
          this._updateUI(this.yearField, val, true);
          return val;
        </setter>
      </property>

      <method name="_init">
        <body>
          <![CDATA[
            var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
            var dtfMonth = Intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
            var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});

            var monthLabel = this.monthField.firstChild;
            var tempDate = new Date(Date.UTC(2005, 0, 1));
            for (var month = 0; month < 12; month++) {
              tempDate.setUTCMonth(month);
              monthLabel.setAttribute("value", dtfMonth.format(tempDate));
              monthLabel = monthLabel.nextSibling;
            }

            var fdow = Number(this.getAttribute("firstdayofweek"));
            if (!isNaN(fdow) && fdow >= 0 && fdow <= 6)
              this._weekStart = fdow;

            var weekbox = document.getAnonymousElementByAttribute(this, "anonid", "dayofweekbox").childNodes;
            var date = new Date();
            date.setDate(date.getDate() - (date.getDay() - this._weekStart));
            for (var i = 0; i < weekbox.length; i++) {
              weekbox[i].value = dtfWeekday.format(date);
              date.setDate(date.getDate() + 1);
            }
          ]]>
        </body>
      </method>
      <method name="_setValueNoSync">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var dt = new Date(aValue);
            if (!isNaN(dt)) {
              this._dateValue = dt;
              this.setAttribute("value", this.value);
              this._updateUI();
            }
          ]]>
        </body>
      </method>
      <method name="_updateUI">
        <parameter name="aField"/>
        <parameter name="aValue"/>
        <parameter name="aCheckMonth"/>
        <body>
          <![CDATA[
            var date;
            var currentMonth;
            if (aCheckMonth) {
              if (!this._displayedDate)
                this._displayedDate = this.dateValue;

              var expectedMonth = aValue;
              if (aField == this.monthField) {
                this._displayedDate.setMonth(aValue);
              } else {
                expectedMonth = this._displayedDate.getMonth();
                this._displayedDate.setFullYear(aValue);
              }

              if (expectedMonth != -1 && expectedMonth != 12 &&
                  expectedMonth != this._displayedDate.getMonth()) {
                // If the month isn't what was expected, then the month overflowed.
                // Setting the date to 0 will go back to the last day of the right month.
                this._displayedDate.setDate(0);
              }

              date = new Date(this._displayedDate);
              currentMonth = this._displayedDate.getMonth();
            } else {
              var samemonth = (this._displayedDate &&
                               this._displayedDate.getMonth() == this.month &&
                               this._displayedDate.getFullYear() == this.year);
              if (samemonth) {
                var items = this.dateField.getElementsByAttribute("value", this.date);
                if (items.length)
                  this.selectedItem = items[0];
                return;
              }

              date = this.dateValue;
              this._displayedDate = new Date(date);
              currentMonth = this.month;
            }

            if (this._todayItem) {
              this._todayItem.removeAttribute("today");
              this._todayItem = null;
            }

            if (this._selectedItem) {
              this._selectedItem.removeAttribute("selected");
              this._selectedItem = null;
            }

            // Update the month and year title
            this.monthField.selectedIndex = currentMonth;
            this.yearField.setAttribute("value", date.getFullYear());

            date.setDate(1);
            var firstWeekday = (7 + date.getDay() - this._weekStart) % 7;
            date.setDate(date.getDate() - firstWeekday);

            var today = new Date();
            var datebox = this.dateField;
            for (var k = 1; k < datebox.childNodes.length; k++) {
              var row = datebox.childNodes[k];
              for (var i = 0; i < 7; i++) {
                var item = row.childNodes[i];

                if (currentMonth == date.getMonth()) {
                  item.value = date.getDate();

                  // highlight today
                  if (this._isSameDay(today, date)) {
                    this._todayItem = item;
                    item.setAttribute("today", "true");
                  }

                  // highlight the selected date
                  if (this._isSameDay(this._dateValue, date)) {
                    this._selectedItem = item;
                    item.setAttribute("selected", "true");
                  }
                } else {
                  item.value = "";
                }

                date.setDate(date.getDate() + 1);
              }
            }

            this._fireEvent("monthchange", this);
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecreaseDateFromEvent">
        <parameter name="aEvent"/>
        <parameter name="aDiff"/>
        <body>
          <![CDATA[
            if (aEvent.originalTarget == this && !this.disabled && !this.readOnly) {
              var newdate = this.dateValue;
              newdate.setDate(newdate.getDate() + aDiff);
              this.dateValue = newdate;
              this._fireEvent("change", this);
            }
            aEvent.stopPropagation();
            aEvent.preventDefault();
          ]]>
        </body>
      </method>
      <method name="_increaseOrDecreaseMonth">
        <parameter name="aDir"/>
        <body>
          <![CDATA[
            if (!this.disabled) {
              var month = this._displayedDate ? this._displayedDate.getMonth() :
                                                this.month;
              this._updateUI(this.monthField, month + aDir, true);
            }
          ]]>
        </body>
      </method>
      <method name="_isSameDay">
        <parameter name="aDate1"/>
        <parameter name="aDate2"/>
        <body>
          <![CDATA[
            return (aDate1 && aDate2 &&
                    aDate1.getDate() == aDate2.getDate() &&
                    aDate1.getMonth() == aDate2.getMonth() &&
                    aDate1.getFullYear() == aDate2.getFullYear());
          ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="click">
        <![CDATA[
          if (event.button != 0 || this.disabled || this.readOnly)
            return;

          var target = event.originalTarget;
          if (target.classList.contains("datepicker-gridlabel") &&
              target != this.selectedItem) {
            this.selectedItem = target;
            this._dateValue = new Date(this._displayedDate);
            if (this.attachedControl)
              this.attachedControl._setValueNoSync(this._dateValue);
            this._fireEvent("change", this);

            if (this.attachedControl && "open" in this.attachedControl)
              this.attachedControl.open = false; // close the popup
          }
        ]]>
      </handler>
      <handler event="MozMousePixelScroll" preventdefault="true"/>
      <handler event="DOMMouseScroll" preventdefault="true">
        <![CDATA[
          this._increaseOrDecreaseMonth(event.detail < 0 ? -1 : 1);
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_LEFT"
               action="this._increaseOrDecreaseDateFromEvent(event, -1);"/>
      <handler event="keypress" keycode="VK_RIGHT"
               action="this._increaseOrDecreaseDateFromEvent(event, 1);"/>
      <handler event="keypress" keycode="VK_UP"
               action="this._increaseOrDecreaseDateFromEvent(event, -7);"/>
      <handler event="keypress" keycode="VK_DOWN"
               action="this._increaseOrDecreaseDateFromEvent(event, 7);"/>
      <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true"
               action="this._increaseOrDecreaseMonth(-1);"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true"
               action="this._increaseOrDecreaseMonth(1);"/>
    </handlers>
  </binding>

  <binding id="datepicker-popup" display="xul:menu"
           extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">
    <content align="center">
      <xul:hbox class="textbox-input-box datetimepicker-input-box" align="center"
                allowevents="true" xbl:inherits="context,disabled,readonly">
        <xul:hbox class="datetimepicker-input-subbox" align="baseline">
          <html:input class="datetimepicker-input textbox-input" anonid="input-one"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="datetimepicker-input-subbox" align="baseline">
          <html:input class="datetimepicker-input textbox-input" anonid="input-two"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
        <xul:hbox class="datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-three"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
        <xul:hbox class="datetimepicker-input-subbox" align="center">
          <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
                      size="2" maxlength="2"
                      xbl:inherits="disabled,readonly"/>
        </xul:hbox>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled" allowevents="true"
                       onup="this.parentNode._increaseOrDecrease(1);"
                       ondown="this.parentNode._increaseOrDecrease(-1);"/>
      <xul:dropmarker class="datepicker-dropmarker" xbl:inherits="disabled"/>
      <xul:panel onpopupshown="this.firstChild.focus();" level="top">
        <xul:datepicker anonid="grid" type="grid" class="datepicker-popupgrid"
                        xbl:inherits="disabled,readonly,firstdayofweek"/>
      </xul:panel>
    </content>
    <implementation>
      <constructor>
        var grid = document.getAnonymousElementByAttribute(this, "anonid", "grid");
        this.attachedControl = grid;
        grid.attachedControl = this;
        grid._setValueNoSync(this._dateValue);
      </constructor>
      <property name="open" onget="return this.hasAttribute('open');">
        <setter>
          <![CDATA[
            if (this.boxObject instanceof MenuBoxObject)
              this.boxObject.openMenu(val);
            return val;
          ]]>
        </setter>
      </property>
      <property name="displayedMonth">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth;
        </getter>
        <setter>
          document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth = val;
          return val;
        </setter>
      </property>
      <property name="displayedYear">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear;
        </getter>
        <setter>
          document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear = val;
          return val;
        </setter>
      </property>
    </implementation>
  </binding>

</bindings>
PK
!<ttg008chrome/toolkit/content/global/bindings/datetimepopup.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="dateTimePopupBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
  <binding id="datetime-popup"
           extends="chrome://global/content/bindings/popup.xml#arrowpanel">
    <resources>
      <stylesheet src="chrome://global/skin/datetimepopup.css"/>
    </resources>
    <implementation>
      <property name="dateTimePopupFrame">
        <getter>
          let frame = this.querySelector("#dateTimePopupFrame");
          if (!frame) {
            frame = this.ownerDocument.createElement("iframe");
            frame.id = "dateTimePopupFrame";
            this.appendChild(frame);
          }
          return frame;
        </getter>
      </property>
      <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
      <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
      <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
      <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
      <constructor><![CDATA[
        this.mozIntl = Components.classes["@mozilla.org/mozintl;1"]
                                 .getService(Components.interfaces.mozIMozIntl);
        // Notify DateTimePickerHelper.jsm that binding is ready.
        this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
      ]]></constructor>
      <method name="openPicker">
        <parameter name="type"/>
        <parameter name="anchor"/>
        <parameter name="detail"/>
        <body><![CDATA[
          this.type = type;
          this.pickerState = {};
          // TODO: Resize picker according to content zoom level
          this.style.fontSize = "10px";
          switch (type) {
            case "time": {
              this.detail = detail;
              this.dateTimePopupFrame.addEventListener("load", this, true);
              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
              this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
              this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
              break;
            }
            case "date": {
              this.detail = detail;
              this.dateTimePopupFrame.addEventListener("load", this, true);
              this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
              this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
              this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
              break;
            }
          }
          this.hidden = false;
          this.openPopup(anchor, "after_start", 0, 0);
        ]]></body>
      </method>
      <method name="closePicker">
        <body><![CDATA[
          this.setInputBoxValue(true);
          this.pickerState = {};
          this.type = undefined;
          this.dateTimePopupFrame.removeEventListener("load", this, true);
          this.dateTimePopupFrame.contentDocument.removeEventListener("message", this);
          this.dateTimePopupFrame.setAttribute("src", "");
          this.hidePopup();
          this.hidden = true;
        ]]></body>
      </method>
      <method name="setPopupValue">
        <parameter name="data"/>
        <body><![CDATA[
          switch (this.type) {
            case "time": {
              this.postMessageToPicker({
                name: "PickerSetValue",
                detail: data.value
              });
              break;
            }
            case "date": {
              const { year, month, day } = data.value;
              this.postMessageToPicker({
                name: "PickerSetValue",
                detail: {
                  year,
                  // Month value from input box starts from 1 instead of 0
                  month: month == undefined ? undefined : month - 1,
                  day
                }
              });
              break;
            }
          }
        ]]></body>
      </method>
      <method name="initPicker">
        <parameter name="detail"/>
        <body><![CDATA[
          // TODO: When bug 1376616 lands, replace this.setGregorian with
          //       mozIntl.Locale for setting calendar to Gregorian
          const locale = this.setGregorian(Services.locale.getAppLocaleAsBCP47());
          const dir = this.mozIntl.getLocaleInfo(locale).direction;

          switch (this.type) {
            case "time": {
              const { hour, minute } = detail.value;
              const format = detail.format || "12";

              this.postMessageToPicker({
                name: "PickerInit",
                detail: {
                  hour,
                  minute,
                  format,
                  locale,
                  min: detail.min,
                  max: detail.max,
                  step: detail.step,
                }
              });
              break;
            }
            case "date": {
              const { year, month, day } = detail.value;
              const { firstDayOfWeek, weekends } =
                this.getCalendarInfo(locale);
              const monthStrings = this.getDisplayNames(
                locale, [
                  "dates/gregorian/months/january",
                  "dates/gregorian/months/february",
                  "dates/gregorian/months/march",
                  "dates/gregorian/months/april",
                  "dates/gregorian/months/may",
                  "dates/gregorian/months/june",
                  "dates/gregorian/months/july",
                  "dates/gregorian/months/august",
                  "dates/gregorian/months/september",
                  "dates/gregorian/months/october",
                  "dates/gregorian/months/november",
                  "dates/gregorian/months/december",
                ], "short");
              const weekdayStrings = this.getDisplayNames(
                locale, [
                  "dates/gregorian/weekdays/sunday",
                  "dates/gregorian/weekdays/monday",
                  "dates/gregorian/weekdays/tuesday",
                  "dates/gregorian/weekdays/wednesday",
                  "dates/gregorian/weekdays/thursday",
                  "dates/gregorian/weekdays/friday",
                  "dates/gregorian/weekdays/saturday",
                ], "short");

              this.postMessageToPicker({
                name: "PickerInit",
                detail: {
                  year,
                  // Month value from input box starts from 1 instead of 0
                  month: month == undefined ? undefined : month - 1,
                  day,
                  firstDayOfWeek,
                  weekends,
                  monthStrings,
                  weekdayStrings,
                  locale,
                  dir,
                  min: detail.min,
                  max: detail.max,
                  step: detail.step,
                  stepBase: detail.stepBase,
                }
              });
              break;
            }
          }
        ]]></body>
      </method>
      <method name="setInputBoxValue">
        <parameter name="passAllValues"/>
        <body><![CDATA[
          /**
           * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
           */
          switch (this.type) {
            case "time": {
              const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
              const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
              if (passAllValues && isAnyValueSet) {
                this.sendPickerValueChanged({ hour, minute });
              } else {
                this.sendPickerValueChanged({
                  hour: isHourSet || isDayPeriodSet ? hour : undefined,
                  minute: isMinuteSet ? minute : undefined
                });
              }
              break;
            }
            case "date": {
              this.sendPickerValueChanged(this.pickerState);
              break;
            }
          }
        ]]></body>
      </method>
      <method name="sendPickerValueChanged">
        <parameter name="value"/>
        <body><![CDATA[
          switch (this.type) {
            case "time": {
              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
                detail: {
                  hour: value.hour,
                  minute: value.minute
                }
              }));
              break;
            }
            case "date": {
              this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
                detail: {
                  year: value.year,
                  // Month value from input box starts from 1 instead of 0
                  month: value.month == undefined ? undefined : value.month + 1,
                  day: value.day
                }
              }));
              break;
            }
          }
        ]]></body>
      </method>
      <method name="getCalendarInfo">
        <parameter name="locale"/>
        <body><![CDATA[
          const calendarInfo = this.mozIntl.getCalendarInfo(locale);

          // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
          // so they need to be mapped to JavaScript convention with 0 as Sunday
          // and 6 as Saturday
          let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
              weekendStart = calendarInfo.weekendStart - 1,
              weekendEnd = calendarInfo.weekendEnd - 1;

          let weekends = [];

          // Make sure weekendEnd is greater than weekendStart
          if (weekendEnd < weekendStart) {
            weekendEnd += 7;
          }

          // We get the weekends by incrementing weekendStart up to weekendEnd.
          // If the start and end is the same day, then weekends only has one day.
          for (let day = weekendStart; day <= weekendEnd; day++) {
            weekends.push(day % 7);
          }

          return {
            firstDayOfWeek,
            weekends
          }
        ]]></body>
      </method>
      <method name="getDisplayNames">
        <parameter name="locale"/>
        <parameter name="keys"/>
        <parameter name="style"/>
        <body><![CDATA[
          const displayNames = this.mozIntl.getDisplayNames(locale, {keys, style});
          return keys.map(key => displayNames.values[key]);
        ]]></body>
      </method>
      <method name="setGregorian">
        <parameter name="locale"/>
        <body><![CDATA[
          if (locale.match(/u-ca-/)) {
            return locale.replace(/u-ca-[^-]+/, "u-ca-gregory");
          }
          return locale + "-u-ca-gregory";
        ]]></body>
      </method>
      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "load": {
              this.initPicker(this.detail);
              this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
              break;
            }
            case "message": {
              this.handleMessage(aEvent);
              break;
            }
          }
        ]]></body>
      </method>
      <method name="handleMessage">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
            return;
          }

          switch (aEvent.data.name) {
            case "PickerPopupChanged": {
              this.pickerState = aEvent.data.detail;
              this.setInputBoxValue();
              break;
            }
            case "ClosePopup": {
              this.closePicker();
              break;
            }
          }
        ]]></body>
      </method>
      <method name="postMessageToPicker">
        <parameter name="data"/>
        <body><![CDATA[
          if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
            this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
          }
        ]]></body>
      </method>

    </implementation>
  </binding>
</bindings>
PK
!<gVk>>1chrome/toolkit/content/global/bindings/dialog.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="dialogBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="dialog" extends="chrome://global/content/bindings/general.xml#root-element">
    <resources>
      <stylesheet src="chrome://global/skin/dialog.css"/>
    </resources>
    <content>
      <xul:vbox class="box-inherit dialog-content-box" flex="1">
        <children/>
      </xul:vbox>

      <xul:hbox class="dialog-button-box" anonid="buttons"
                xbl:inherits="pack=buttonpack,align=buttonalign,dir=buttondir,orient=buttonorient"
                pack="end">
        <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
        <xul:spacer anonid="spacer" flex="1" hidden="true"/>
        <xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
        <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="cancel" class="dialog-button"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
      </xul:hbox>
    </content>

    <implementation>
      <field name="_mStrBundle">null</field>
      <field name="_closeHandler">(function(event) {
        if (!document.documentElement.cancelDialog())
          event.preventDefault();
      })</field>

      <property name="buttons"
                onget="return this.getAttribute('buttons');"
                onset="this._configureButtons(val); return val;"/>

      <property name="defaultButton">
        <getter>
        <![CDATA[
          if (this.hasAttribute("defaultButton"))
            return this.getAttribute("defaultButton");
          return "accept";  // default to the accept button
        ]]>
        </getter>
        <setter>
        <![CDATA[
          this._setDefaultButton(val);
          return val;
        ]]>
        </setter>
      </property>

      <method name="acceptDialog">
        <body>
        <![CDATA[
          return this._doButtonCommand("accept");
        ]]>
        </body>
      </method>

      <method name="cancelDialog">
        <body>
        <![CDATA[
          return this._doButtonCommand("cancel");
        ]]>
        </body>
      </method>

      <method name="getButton">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          return this._buttons[aDlgType];
        ]]>
        </body>
      </method>

      <method name="moveToAlertPosition">
        <body>
        <![CDATA[
          // hack. we need this so the window has something like its final size
          if (window.outerWidth == 1) {
            dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n");
            sizeToContent();
          }

          if (opener) {
            var xOffset = (opener.outerWidth - window.outerWidth) / 2;
            var yOffset = opener.outerHeight / 5;

            var newX = opener.screenX + xOffset;
            var newY = opener.screenY + yOffset;
          } else {
            newX = (screen.availWidth - window.outerWidth) / 2;
            newY = (screen.availHeight - window.outerHeight) / 2;
          }

          // ensure the window is fully onscreen (if smaller than the screen)
          if (newX < screen.availLeft)
            newX = screen.availLeft + 20;
          if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth))
            newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20;

          if (newY < screen.availTop)
            newY = screen.availTop + 20;
          if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight))
            newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60;

          window.moveTo( newX, newY );
        ]]>
        </body>
      </method>

      <method name="centerWindowOnScreen">
        <body>
        <![CDATA[
          var xOffset = screen.availWidth / 2 - window.outerWidth / 2;
          var yOffset = screen.availHeight / 2 - window.outerHeight / 2;

          xOffset = xOffset > 0 ? xOffset : 0;
          yOffset = yOffset > 0 ? yOffset : 0;
          window.moveTo(xOffset, yOffset);
        ]]>
        </body>
      </method>

      <constructor>
      <![CDATA[
        this._configureButtons(this.buttons);

        // listen for when window is closed via native close buttons
        window.addEventListener("close", this._closeHandler);

        // for things that we need to initialize after onload fires
        window.addEventListener("load", this.postLoadInit);

        window.moveToAlertPosition = this.moveToAlertPosition;
        window.centerWindowOnScreen = this.centerWindowOnScreen;
      ]]>
      </constructor>

      <method name="postLoadInit">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          function focusInit() {
            const dialog = document.documentElement;
            const defaultButton = dialog.getButton(dialog.defaultButton);
            // give focus to the first focusable element in the dialog
            if (!document.commandDispatcher.focusedElement) {
              document.commandDispatcher.advanceFocusIntoSubtree(dialog);

              var focusedElt = document.commandDispatcher.focusedElement;
              if (focusedElt) {
                var initialFocusedElt = focusedElt;
                while (focusedElt.localName == "tab" ||
                       focusedElt.getAttribute("noinitialfocus") == "true") {
                  document.commandDispatcher.advanceFocusIntoSubtree(focusedElt);
                  focusedElt = document.commandDispatcher.focusedElement;
                  if (focusedElt == initialFocusedElt)
                    break;
                }

                if (initialFocusedElt.localName == "tab") {
                  if (focusedElt.hasAttribute("dlgtype")) {
                    // We don't want to focus on anonymous OK, Cancel, etc. buttons,
                    // so return focus to the tab itself
                    initialFocusedElt.focus();
                  }
                } else if (!/Mac/.test(navigator.platform) &&
                           focusedElt.hasAttribute("dlgtype") && focusedElt != defaultButton) {
                  defaultButton.focus();
                }
              }
            }

            try {
              if (defaultButton)
                window.notifyDefaultButtonLoaded(defaultButton);
            } catch (e) { }
          }

          // Give focus after onload completes, see bug 103197.
          setTimeout(focusInit, 0);
        ]]>
        </body>
      </method>

      <property name="mStrBundle">
        <getter>
        <![CDATA[
          if (!this._mStrBundle) {
            // need to create string bundle manually instead of using <xul:stringbundle/>
            // see bug 63370 for details
            this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.nsIStringBundleService)
                                         .createBundle("chrome://global/locale/dialog.properties");
          }
          return this._mStrBundle;
        ]]></getter>
      </property>

      <method name="_configureButtons">
        <parameter name="aButtons"/>
        <body>
        <![CDATA[
          // by default, get all the anonymous button elements
          var buttons = {};
          this._buttons = buttons;
          buttons.accept = document.getAnonymousElementByAttribute(this, "dlgtype", "accept");
          buttons.cancel = document.getAnonymousElementByAttribute(this, "dlgtype", "cancel");
          buttons.extra1 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra1");
          buttons.extra2 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra2");
          buttons.help = document.getAnonymousElementByAttribute(this, "dlgtype", "help");
          buttons.disclosure = document.getAnonymousElementByAttribute(this, "dlgtype", "disclosure");

          // look for any overriding explicit button elements
          var exBtns = this.getElementsByAttribute("dlgtype", "*");
          var dlgtype;
          var i;
          for (i = 0; i < exBtns.length; ++i) {
            dlgtype = exBtns[i].getAttribute("dlgtype");
            buttons[dlgtype].hidden = true; // hide the anonymous button
            buttons[dlgtype] = exBtns[i];
          }

          // add the label and oncommand handler to each button
          for (dlgtype in buttons) {
            var button = buttons[dlgtype];
            button.addEventListener("command", this._handleButtonCommand, true);

            // don't override custom labels with pre-defined labels on explicit buttons
            if (!button.hasAttribute("label")) {
              // dialog attributes override the default labels in dialog.properties
              if (this.hasAttribute("buttonlabel" + dlgtype)) {
                button.setAttribute("label", this.getAttribute("buttonlabel" + dlgtype));
                if (this.hasAttribute("buttonaccesskey" + dlgtype))
                  button.setAttribute("accesskey", this.getAttribute("buttonaccesskey" + dlgtype));
              } else if (dlgtype != "extra1" && dlgtype != "extra2") {
                button.setAttribute("label", this.mStrBundle.GetStringFromName("button-" + dlgtype));
                var accessKey = this.mStrBundle.GetStringFromName("accesskey-" + dlgtype);
                if (accessKey)
                  button.setAttribute("accesskey", accessKey);
              }
            }
            // allow specifying alternate icons in the dialog header
            if (!button.hasAttribute("icon")) {
              // if there's an icon specified, use that
              if (this.hasAttribute("buttonicon" + dlgtype))
                button.setAttribute("icon", this.getAttribute("buttonicon" + dlgtype));
              // otherwise set defaults
              else
                switch (dlgtype) {
                  case "accept":
                    button.setAttribute("icon", "accept");
                    break;
                  case "cancel":
                    button.setAttribute("icon", "cancel");
                    break;
                  case "disclosure":
                    button.setAttribute("icon", "properties");
                    break;
                  case "help":
                    button.setAttribute("icon", "help");
                    break;
                  default:
                    break;
                }
            }
          }

          // ensure that hitting enter triggers the default button command
          this.defaultButton = this.defaultButton;

          // if there is a special button configuration, use it
          if (aButtons) {
            // expect a comma delimited list of dlgtype values
            var list = aButtons.split(",");

            // mark shown dlgtypes as true
            var shown = { accept: false, cancel: false, help: false,
                          disclosure: false, extra1: false, extra2: false };
            for (i = 0; i < list.length; ++i)
              shown[list[i].replace(/ /g, "")] = true;

            // hide/show the buttons we want
            for (dlgtype in buttons)
              buttons[dlgtype].hidden = !shown[dlgtype];

            // show the spacer on Windows only when the extra2 button is present
            if (/Win/.test(navigator.platform)) {
              var spacer = document.getAnonymousElementByAttribute(this, "anonid", "spacer");
              spacer.removeAttribute("hidden");
              spacer.setAttribute("flex", shown["extra2"] ? "1" : "0");
            }
          }
        ]]>
        </body>
      </method>

      <method name="_setDefaultButton">
        <parameter name="aNewDefault"/>
        <body>
        <![CDATA[
          // remove the default attribute from the previous default button, if any
          var oldDefaultButton = this.getButton(this.defaultButton);
          if (oldDefaultButton)
            oldDefaultButton.removeAttribute("default");

          var newDefaultButton = this.getButton(aNewDefault);
          if (newDefaultButton) {
            this.setAttribute("defaultButton", aNewDefault);
            newDefaultButton.setAttribute("default", "true");
          } else {
            this.setAttribute("defaultButton", "none");
            if (aNewDefault != "none")
              dump("invalid new default button: " + aNewDefault + ", assuming: none\n");
          }
        ]]>
        </body>
      </method>

      <method name="_handleButtonCommand">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          return document.documentElement._doButtonCommand(
                                        aEvent.target.getAttribute("dlgtype"));
        ]]>
        </body>
      </method>

      <method name="_doButtonCommand">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var button = this.getButton(aDlgType);
          if (!button.disabled) {
            var noCancel = this._fireButtonEvent(aDlgType);
            if (noCancel) {
              if (aDlgType == "accept" || aDlgType == "cancel") {
                var closingEvent = new CustomEvent("dialogclosing", {
                  bubbles: true,
                  detail: { button: aDlgType },
                });
                this.dispatchEvent(closingEvent);
                window.close();
              }
            }
            return noCancel;
          }
          return true;
        ]]>
        </body>
      </method>

      <method name="_fireButtonEvent">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent("dialog" + aDlgType, true, true);

          // handle dom event handlers
          var noCancel = this.dispatchEvent(event);

          // handle any xml attribute event handlers
          var handler = this.getAttribute("ondialog" + aDlgType);
          if (handler != "") {
            var fn = new Function("event", handler);
            var returned = fn(event);
            if (returned == false)
              noCancel = false;
          }

          return noCancel;
        ]]>
        </body>
      </method>

      <method name="_hitEnter">
        <parameter name="evt"/>
        <body>
        <![CDATA[
          if (evt.defaultPrevented)
            return;

          var btn = this.getButton(this.defaultButton);
          if (btn)
            this._doButtonCommand(this.defaultButton);
        ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_RETURN"
               group="system" action="this._hitEnter(event);"/>
      <handler event="keypress" keycode="VK_ESCAPE" group="system">
        if (!event.defaultPrevented)
          this.cancelDialog();
      </handler>
      <handler event="focus" phase="capturing">
        var btn = this.getButton(this.defaultButton);
        if (btn)
          btn.setAttribute("default", event.originalTarget == btn || !(event.originalTarget instanceof Components.interfaces.nsIDOMXULButtonElement));
      </handler>
    </handlers>

  </binding>

  <binding id="dialogheader">
    <resources>
      <stylesheet src="chrome://global/skin/dialog.css"/>
    </resources>
    <content>
      <xul:label class="dialogheader-title" xbl:inherits="value=title,crop" crop="right" flex="1"/>
      <xul:label class="dialogheader-description" xbl:inherits="value=description"/>
    </content>
  </binding>

</bindings>
PK
!<5_||1chrome/toolkit/content/global/bindings/editor.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="editorBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="editor" role="outerdoc">
    <implementation type="application/javascript">
      <constructor>
        <![CDATA[
          // Make window editable immediately only
          //   if the "editortype" attribute is supplied
          // This allows using same contentWindow for different editortypes,
          //   where the type is determined during the apps's window.onload handler.
          if (this.editortype)
            this.makeEditable(this.editortype, true);
        ]]>
      </constructor>
      <destructor/>

      <field name="_editorContentListener">
        <![CDATA[
          ({
            QueryInterface(iid) {
              if (iid.equals(Components.interfaces.nsIURIContentListener) ||
                  iid.equals(Components.interfaces.nsISupportsWeakReference) ||
                  iid.equals(Components.interfaces.nsISupports))
              return this;

              throw Components.results.NS_ERROR_NO_INTERFACE;
            },
            onStartURIOpen(uri) {
              return false;
            },
            doContent(contentType, isContentPreferred, request, contentHandler) {
              return false;
            },
            isPreferred(contentType, desiredContentType) {
              return false;
            },
            canHandleContent(contentType, isContentPreferred, desiredContentType) {
              return false;
            },
            loadCookie: null,
            parentContentListener: null
          })
        ]]>
      </field>
      <method name="makeEditable">
        <parameter name="editortype"/>
        <parameter name="waitForUrlLoad"/>
        <body>
        <![CDATA[
          this.editingSession.makeWindowEditable(this.contentWindow, editortype, waitForUrlLoad, true, false);
          this.setAttribute("editortype", editortype);

          this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
              .getInterface(Components.interfaces.nsIURIContentListener)
              .parentContentListener = this._editorContentListener;
        ]]>
        </body>
      </method>
      <method name="getEditor">
        <parameter name="containingWindow"/>
        <body>
        <![CDATA[
          return this.editingSession.getEditorForWindow(containingWindow);
        ]]>
        </body>
      </method>
      <method name="getHTMLEditor">
        <parameter name="containingWindow"/>
        <body>
        <![CDATA[
          var editor = this.editingSession.getEditorForWindow(containingWindow);
          return editor.QueryInterface(Components.interfaces.nsIHTMLEditor);
        ]]>
        </body>
      </method>

      <field name="_finder">null</field>
      <property name="finder" readonly="true">
        <getter><![CDATA[
          if (!this._finder) {
            if (!this.docShell)
              return null;

            let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
            this._finder = new Finder(this.docShell);
          }
          return this._finder;
        ]]></getter>
      </property>

      <field name="_fastFind">null</field>
      <property name="fastFind"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._fastFind) {
            if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
              return null;

            if (!this.docShell)
              return null;

            this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
                                       .createInstance(Components.interfaces.nsITypeAheadFind);
            this._fastFind.init(this.docShell);
          }
          return this._fastFind;
        ]]>
        </getter>
      </property>

      <field name="_lastSearchString">null</field>

      <property name="editortype"
                onget="return this.getAttribute('editortype');"
                onset="this.setAttribute('editortype', val); return val;"/>
      <property name="webNavigation"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
                readonly="true"/>
      <property name="contentDocument" readonly="true"
                onget="return this.webNavigation.document;"/>
      <property name="docShell" readonly="true">
        <getter><![CDATA[
          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          return frameLoader ? frameLoader.docShell : null;
        ]]></getter>
      </property>
      <property name="currentURI"
                readonly="true"
                onget="return this.webNavigation.currentURI;"/>
      <property name="contentWindow"
                readonly="true"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
      <property name="contentWindowAsCPOW"
                readonly="true"
                onget="return this.contentWindow;"/>
      <property name="webBrowserFind"
                readonly="true"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);"/>
      <property name="markupDocumentViewer"
                readonly="true"
                onget="return this.docShell.contentViewer;"/>
      <property name="editingSession"
                readonly="true"
                onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIEditingSession);"/>
      <property name="commandManager"
                readonly="true"
                onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsICommandManager);"/>
      <property name="fullZoom"
                onget="return this.markupDocumentViewer.fullZoom;"
                onset="this.markupDocumentViewer.fullZoom = val;"/>
      <property name="textZoom"
                onget="return this.markupDocumentViewer.textZoom;"
                onset="this.markupDocumentViewer.textZoom = val;"/>
      <property name="isSyntheticDocument"
                onget="return this.contentDocument.isSyntheticDocument;"
                readonly="true"/>
      <property name="messageManager"
                readonly="true">
        <getter>
          <![CDATA[
            var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            if (!owner.frameLoader) {
              return null;
            }
            return owner.frameLoader.messageManager;
          ]]>
        </getter>
      </property>
      <property name="outerWindowID" readonly="true">
        <getter><![CDATA[
          return this.contentWindow
                     .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                     .getInterface(Components.interfaces.nsIDOMWindowUtils)
                     .outerWindowID;
        ]]></getter>
      </property>
    </implementation>
  </binding>

</bindings>
PK
!<_8bA3chrome/toolkit/content/global/bindings/expander.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="expanderBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="expander" display="xul:vbox">
    <resources>
      <stylesheet src="chrome://global/skin/expander.css"/>
    </resources>
    <content>
      <xul:hbox align="center">
        <xul:button type="disclosure" class="expanderButton" anonid="disclosure" xbl:inherits="disabled" mousethrough="always"/>
        <xul:label class="header expanderButton" anonid="label" xbl:inherits="value=label,disabled" mousethrough="always" flex="1"/>
        <xul:button anonid="clear-button" xbl:inherits="label=clearlabel,disabled=cleardisabled,hidden=clearhidden" mousethrough="always" icon="clear"/>
      </xul:hbox>
      <xul:vbox flex="1" anonid="settings" class="settingsContainer" collapsed="true" xbl:inherits="align">
        <children/>
      </xul:vbox>
    </content>
    <implementation>
      <constructor><![CDATA[
        var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
        var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
        var open = this.getAttribute("open") == "true";
        settings.collapsed = !open;
        expander.open = open;
      ]]></constructor>
      <property name="open">
        <setter>
          <![CDATA[
            var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
            var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
            settings.collapsed = !val;
            expander.open = val;
            if (val)
              this.setAttribute("open", "true");
            else
              this.setAttribute("open", "false");
            return val;
          ]]>
        </setter>
        <getter>
          return this.getAttribute("open");
        </getter>
      </property>        
      <method name="onCommand">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var element = aEvent.originalTarget;
          var button = element.getAttribute("anonid");
          switch (button) {
          case "disclosure":
          case "label":
            if (this.open == "true")
              this.open = false;
            else
              this.open = true;
            break;
          case "clear-button":
            var event = document.createEvent("Events");
            event.initEvent("clear", true, true);
            this.dispatchEvent(event);
            break;
          }
        ]]></body>
      </method>
    </implementation>
    <handlers>
      <handler event="command"><![CDATA[
        this.onCommand(event);
      ]]></handler>
      <handler event="click"><![CDATA[
        if (event.originalTarget.localName == "label")
          this.onCommand(event);
      ]]></handler>
    </handlers>
  </binding>
          
</bindings>

          
PK
!<Mː4chrome/toolkit/content/global/bindings/filefield.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="filefieldBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="filefield" extends="chrome://global/content/bindings/general.xml#basetext">
    <resources>
      <stylesheet src="chrome://global/skin/filefield.css"/>
    </resources>
    <content>
      <xul:stringbundle anonid="bundle" src="chrome://global/locale/filefield.properties"/>
      <xul:hbox class="fileFieldContentBox" align="center" flex="1" xbl:inherits="disabled">
        <xul:image class="fileFieldIcon" xbl:inherits="src=image,disabled"/>
        <xul:textbox class="fileFieldLabel" xbl:inherits="value=label,disabled,accesskey,tabindex,aria-labelledby" flex="1" readonly="true"/>
      </xul:hbox>
    </content>
    <implementation implements="nsIDOMXULLabeledControlElement">
      <property name="label" onget="return this.getAttribute('label');">
        <setter>
          this.setAttribute("label", val);
          var elt = document.getAnonymousElementByAttribute(this, "class", "fileFieldLabel");
          return (elt.value = val);
        </setter>
      </property>

      <field name="_file">null</field>
      <property name="file"  onget="return this._file">
        <setter>
        <![CDATA[
          this._file = val;
          if (val) {
            this.image = this._getIconURLForFile(val);
            this.label = this._getDisplayNameForFile(val);
          } else {
            this.removeAttribute("image");
            var bundle = document.getAnonymousElementByAttribute(this, "anonid", "bundle");
            this.label = bundle.getString("downloadHelperNoneSelected");
          }
          return val;
        ]]>
        </setter>
      </property>
      <method name="_getDisplayNameForFile">
        <parameter name="aFile"/>
        <body>
        <![CDATA[
          if (/Win/.test(navigator.platform)) {
            var lfw = aFile.QueryInterface(Components.interfaces.nsILocalFileWin);
            try {
              return lfw.getVersionInfoField("FileDescription");
            } catch (e) {
              // fall through to the filename
            }
          } else if (/Mac/.test(navigator.platform)) {
            var lfm = aFile.QueryInterface(Components.interfaces.nsILocalFileMac);
            try {
              return lfm.bundleDisplayName;
            } catch (e) {
              // fall through to the file name
            }
          }
          var ios = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);
          var url = ios.newFileURI(aFile).QueryInterface(Components.interfaces.nsIURL);
          return url.fileName;
        ]]>
        </body>
      </method>

      <method name="_getIconURLForFile">
        <parameter name="aFile"/>
        <body>
        <![CDATA[
          if (!aFile)
            return "";
          var ios = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);
          var fph = ios.getProtocolHandler("file")
                       .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
          var urlspec = fph.getURLSpecFromFile(aFile);
          return "moz-icon://" + urlspec + "?size=16";
        ]]>
        </body>
      </method>
    </implementation>
  </binding>
</bindings>
PK
!<F%2chrome/toolkit/content/global/bindings/findbar.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
<!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
%findBarDTD;
]>

<bindings id="findbarBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <!-- Private binding -->
  <binding id="findbar-textbox"
           extends="chrome://global/content/bindings/textbox.xml#textbox">
    <implementation>

      <field name="_findbar">null</field>
      <property name="findbar" readonly="true">
        <getter>
          return this._findbar ?
                 this._findbar : this._findbar = document.getBindingParent(this);
        </getter>
      </property>

      <method name="_handleEnter">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (this.findbar._findMode == this.findbar.FIND_NORMAL) {
            let findString = this.findbar._findField;
            if (!findString.value)
              return;
            if (aEvent.getModifierState("Accel")) {
              this.findbar.getElement("highlight").click();
              return;
            }

            this.findbar.onFindAgainCommand(aEvent.shiftKey);
          } else {
            this.findbar._finishFAYT(aEvent);
          }
        ]]></body>
      </method>

      <method name="_handleTab">
        <parameter name="aEvent"/>
        <body><![CDATA[
          let shouldHandle = !aEvent.altKey && !aEvent.ctrlKey &&
                             !aEvent.metaKey;
          if (shouldHandle &&
              this.findbar._findMode != this.findbar.FIND_NORMAL) {

            this.findbar._finishFAYT(aEvent);
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="input"><![CDATA[
        // We should do nothing during composition.  E.g., composing string
        // before converting may matches a forward word of expected word.
        // After that, even if user converts the composition string to the
        // expected word, it may find second or later searching word in the
        // document.
        if (this.findbar._isIMEComposing) {
          return;
        }

        if (this._hadValue && !this.value) {
          this._willfullyDeleted = true;
          this._hadValue = false;
        } else if (this.value.trim()) {
          this._hadValue = true;
          this._willfullyDeleted = false;
        }
        this.findbar._find(this.value);
      ]]></handler>

      <handler event="keypress"><![CDATA[
        let shouldHandle = !event.altKey && !event.ctrlKey &&
                           !event.metaKey && !event.shiftKey;

        switch (event.keyCode) {
          case KeyEvent.DOM_VK_RETURN:
            this._handleEnter(event);
            break;
          case KeyEvent.DOM_VK_TAB:
            this._handleTab(event);
            break;
          case KeyEvent.DOM_VK_PAGE_UP:
          case KeyEvent.DOM_VK_PAGE_DOWN:
            if (shouldHandle) {
              this.findbar.browser.finder.keyPress(event);
              event.preventDefault();
            }
            break;
          case KeyEvent.DOM_VK_UP:
          case KeyEvent.DOM_VK_DOWN:
            this.findbar.browser.finder.keyPress(event);
            event.preventDefault();
            break;
        }
      ]]></handler>

      <handler event="blur"><![CDATA[
        let findbar = this.findbar;
        // Note: This code used to remove the selection
        // if it matched an editable.
        findbar.browser.finder.enableSelection();
      ]]></handler>

      <handler event="focus"><![CDATA[
        if (/Mac/.test(navigator.platform)) {
          let findbar = this.findbar;
          findbar._onFindFieldFocus();
        }
      ]]></handler>

      <handler event="compositionstart"><![CDATA[
        // Don't close the find toolbar while IME is composing.
        let findbar = this.findbar;
        findbar._isIMEComposing = true;
        if (findbar._quickFindTimeout) {
          clearTimeout(findbar._quickFindTimeout);
          findbar._quickFindTimeout = null;
        }
      ]]></handler>

      <handler event="compositionend"><![CDATA[
        let findbar = this.findbar;
        findbar._isIMEComposing = false;
        if (findbar._findMode != findbar.FIND_NORMAL)
          findbar._setFindCloseTimeout();
      ]]></handler>

      <handler event="dragover"><![CDATA[
        if (event.dataTransfer.types.includes("text/plain"))
          event.preventDefault();
      ]]></handler>

      <handler event="drop"><![CDATA[
        let value = event.dataTransfer.getData("text/plain");
        this.value = value;
        this.findbar._find(value);
        event.stopPropagation();
        event.preventDefault();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="findbar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <resources>
      <stylesheet src="chrome://global/skin/findBar.css"/>
    </resources>

    <content hidden="true">
    <xul:hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
      <xul:hbox anonid="findbar-textbox-wrapper" align="stretch">
        <xul:textbox anonid="findbar-textbox"
                     class="findbar-textbox findbar-find-fast"
                     xbl:inherits="flash"/>
        <xul:toolbarbutton anonid="find-previous"
                           class="findbar-find-previous tabbable"
                           tooltiptext="&previous.tooltip;"
                           oncommand="onFindAgainCommand(true);"
                           disabled="true"
                           xbl:inherits="accesskey=findpreviousaccesskey"/>
        <xul:toolbarbutton anonid="find-next"
                           class="findbar-find-next tabbable"
                           tooltiptext="&next.tooltip;"
                           oncommand="onFindAgainCommand(false);"
                           disabled="true"
                           xbl:inherits="accesskey=findnextaccesskey"/>
      </xul:hbox>
      <xul:toolbarbutton anonid="highlight"
                         class="findbar-highlight findbar-button tabbable"
                         label="&highlightAll.label;"
                         accesskey="&highlightAll.accesskey;"
                         tooltiptext="&highlightAll.tooltiptext;"
                         oncommand="toggleHighlight(this.checked);"
                         type="checkbox"
                         xbl:inherits="accesskey=highlightaccesskey"/>
      <xul:toolbarbutton anonid="find-case-sensitive"
                         class="findbar-case-sensitive findbar-button tabbable"
                         label="&caseSensitive.label;"
                         accesskey="&caseSensitive.accesskey;"
                         tooltiptext="&caseSensitive.tooltiptext;"
                         oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"
                         type="checkbox"
                         xbl:inherits="accesskey=matchcaseaccesskey"/>
      <xul:toolbarbutton anonid="find-entire-word"
                         class="findbar-entire-word findbar-button tabbable"
                         label="&entireWord.label;"
                         accesskey="&entireWord.accesskey;"
                         tooltiptext="&entireWord.tooltiptext;"
                         oncommand="toggleEntireWord(this.checked);"
                         type="checkbox"
                         xbl:inherits="accesskey=entirewordaccesskey"/>
      <xul:label anonid="match-case-status" class="findbar-find-fast"/>
      <xul:label anonid="entire-word-status" class="findbar-find-fast"/>
      <xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
      <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
      <xul:description anonid="find-status"
                       control="findbar-textbox"
                       class="findbar-find-fast findbar-find-status">
      <!-- Do not use value, first child is used because it provides a11y with text change events -->
      </xul:description>
    </xul:hbox>
    <xul:toolbarbutton anonid="find-closebutton"
                       class="findbar-closebutton close-icon"
                       tooltiptext="&findCloseButton.tooltip;"
                       oncommand="close();"/>
    </content>

    <implementation implements="nsIMessageListener, nsIEditActionListener">
      <!-- Please keep in sync with toolkit/content/browser-content.js -->
      <field name="FIND_NORMAL">0</field>
      <field name="FIND_TYPEAHEAD">1</field>
      <field name="FIND_LINKS">2</field>

      <field name="__findMode">0</field>
      <property name="_findMode" onget="return this.__findMode;"
                onset="this.__findMode = val; this._updateBrowserWithState(); return val;"/>

      <field name="_flashFindBar">0</field>
      <field name="_initialFlashFindBarCount">6</field>

      <!--
        - For tests that need to know when the find bar is finished
        - initializing, we store a promise to notify on.
        -->
      <field name="_startFindDeferred">null</field>

      <property name="prefillWithSelection"
                onget="return this.getAttribute('prefillwithselection') != 'false'"
                onset="this.setAttribute('prefillwithselection', val); return val;"/>

      <method name="getElement">
        <parameter name="aAnonymousID"/>
        <body><![CDATA[
          return document.getAnonymousElementByAttribute(this,
                                                         "anonid",
                                                         aAnonymousID)
        ]]></body>
      </method>

      <property name="findMode"
                readonly="true"
                onget="return this._findMode;"/>

      <property name="hasTransactions" readonly="true">
        <getter><![CDATA[
          if (this._findField.value)
            return true;

          // Watch out for lazy editor init
          if (this._findField.editor) {
            let tm = this._findField.editor.transactionManager;
            return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
          }
          return false;
        ]]></getter>
      </property>

      <field name="_browser">null</field>
      <property name="browser">
        <getter><![CDATA[
          if (!this._browser) {
            this._browser =
              document.getElementById(this.getAttribute("browserid"));
          }
          return this._browser;
        ]]></getter>
        <setter><![CDATA[
          if (this._browser) {
            if (this._browser.messageManager) {
              this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
              this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
            }
            let finder = this._browser.finder;
            if (finder)
              finder.removeResultListener(this);
          }

          this._browser = val;
          if (this._browser) {
            // Need to do this to ensure the correct initial state.
            this._updateBrowserWithState();
            this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
            this._browser.messageManager.addMessageListener("Findbar:Mouseup", this);
            this._browser.finder.addResultListener(this);

            this._findField.value = this._browser._lastSearchString;
          }
          return val;
        ]]></setter>
      </property>

      <field name="__prefsvc">null</field>
      <property name="_prefsvc">
        <getter><![CDATA[
          if (!this.__prefsvc) {
            this.__prefsvc = Components.classes["@mozilla.org/preferences-service;1"]
              .getService(Components.interfaces.nsIPrefBranch);
          }
          return this.__prefsvc;
        ]]></getter>
      </property>

      <field name="_observer"><![CDATA[({
        _self: this,

        QueryInterface(aIID) {
          if (aIID.equals(Components.interfaces.nsIObserver) ||
              aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
              aIID.equals(Components.interfaces.nsISupports))
            return this;

          throw Components.results.NS_ERROR_NO_INTERFACE;
        },

        observe(aSubject, aTopic, aPrefName) {
          if (aTopic != "nsPref:changed")
            return;

          let prefsvc = this._self._prefsvc;

          switch (aPrefName) {
            case "accessibility.typeaheadfind":
              this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
              break;
            case "accessibility.typeaheadfind.linksonly":
              this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
              break;
            case "accessibility.typeaheadfind.casesensitive":
              this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
              break;
            case "findbar.entireword":
              this._self._entireWord = prefsvc.getBoolPref(aPrefName);
              this._self.toggleEntireWord(this._self._entireWord, true);
              break;
            case "findbar.highlightAll":
              this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
              break;
            case "findbar.modalHighlight":
              this._self._useModalHighlight = prefsvc.getBoolPref(aPrefName);
              if (this._self.browser.finder)
                this._self.browser.finder.onModalHighlightChange(this._self._useModalHighlight);
              break;
          }
        }
      })]]></field>

      <field name="_destroyed">false</field>

      <constructor><![CDATA[
        // These elements are accessed frequently and are therefore cached
        this._findField = this.getElement("findbar-textbox");
        this._foundMatches = this.getElement("found-matches");
        this._findStatusIcon = this.getElement("find-status-icon");
        this._findStatusDesc = this.getElement("find-status");

        this._foundURL = null;

        let prefsvc = this._prefsvc;

        this._quickFindTimeoutLength =
          prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
        this._flashFindBar =
          prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
        this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");

        prefsvc.addObserver("accessibility.typeaheadfind",
                            this._observer);
        prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
                            this._observer);
        prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
                            this._observer);
        prefsvc.addObserver("findbar.entireword", this._observer);
        prefsvc.addObserver("findbar.highlightAll", this._observer);
        prefsvc.addObserver("findbar.modalHighlight", this._observer);

        this._findAsYouType =
          prefsvc.getBoolPref("accessibility.typeaheadfind");
        this._typeAheadLinksOnly =
          prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
        this._typeAheadCaseSensitive =
          prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
        this._entireWord = prefsvc.getBoolPref("findbar.entireword");
        this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");

        // Convenience
        this.nsITypeAheadFind = Components.interfaces.nsITypeAheadFind;
        this.nsISelectionController = Components.interfaces.nsISelectionController;
        this._findSelection = this.nsISelectionController.SELECTION_FIND;

        this._findResetTimeout = -1;

        // Make sure the FAYT keypress listener is attached by initializing the
        // browser property
        if (this.getAttribute("browserid"))
          setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
      ]]></constructor>

      <destructor><![CDATA[
        this.destroy();
      ]]></destructor>

      <!-- This is necessary because the destructor isn't called when
           we are removed from a document that is not destroyed. This
           needs to be explicitly called in this case -->
      <method name="destroy">
        <body><![CDATA[
          if (this._destroyed)
            return;
          this._destroyed = true;

          if (this.browser.finder)
            this.browser.finder.destroy();

          this.browser = null;

          let prefsvc = this._prefsvc;
          prefsvc.removeObserver("accessibility.typeaheadfind",
                                 this._observer);
          prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
                                 this._observer);
          prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
                                 this._observer);
          prefsvc.removeObserver("findbar.entireword", this._observer);
          prefsvc.removeObserver("findbar.highlightAll", this._observer);
          prefsvc.removeObserver("findbar.modalHighlight", this._observer);

          // Clear all timers that might still be running.
          this._cancelTimers();
        ]]></body>
      </method>

      <method name="_cancelTimers">
        <body><![CDATA[
          if (this._flashFindBarTimeout) {
            clearInterval(this._flashFindBarTimeout);
            this._flashFindBarTimeout = null;
          }
          if (this._quickFindTimeout) {
            clearTimeout(this._quickFindTimeout);
            this._quickFindTimeout = null;
          }
          if (this._findResetTimeout) {
            clearTimeout(this._findResetTimeout);
            this._findResetTimeout = null;
          }
        ]]></body>
      </method>

      <method name="_setFindCloseTimeout">
        <body><![CDATA[
          if (this._quickFindTimeout)
            clearTimeout(this._quickFindTimeout);

          // Don't close the find toolbar while IME is composing OR when the
          // findbar is already hidden.
          if (this._isIMEComposing || this.hidden) {
            this._quickFindTimeout = null;
            return;
          }

          this._quickFindTimeout = setTimeout(() => {
             if (this._findMode != this.FIND_NORMAL)
               this.close();
             this._quickFindTimeout = null;
           }, this._quickFindTimeoutLength);
        ]]></body>
      </method>

      <field name="_pluralForm">null</field>
      <property name="pluralForm">
        <getter><![CDATA[
          if (!this._pluralForm) {
            this._pluralForm = Components.utils.import(
                               "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
          }
          return this._pluralForm;
        ]]></getter>
      </property>

      <!--
        - Updates the search match count after each find operation on a new string.
        - @param aRes
        -        the result of the find operation
        -->
      <method name="_updateMatchesCount">
        <body><![CDATA[
          if (!this._dispatchFindEvent("matchescount"))
            return;

          this.browser.finder.requestMatchesCount(this._findField.value,
            this._findMode == this.FIND_LINKS);
        ]]></body>
      </method>

      <!--
        - Turns highlight on or off.
        - @param aHighlight (boolean)
        -        Whether to turn the highlight on or off
        - @param aFromPrefObserver (boolean)
        -        Whether the callee is the pref observer, which means we should
        -        not set the same pref again.
        -->
      <method name="toggleHighlight">
        <parameter name="aHighlight"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (aHighlight === this._highlightAll) {
            return;
          }

          this.browser.finder.onHighlightAllChange(aHighlight);

          this._setHighlightAll(aHighlight, aFromPrefObserver);

          if (!this._dispatchFindEvent("highlightallchange")) {
            return;
          }

          let word = this._findField.value;
          // Bug 429723. Don't attempt to highlight ""
          if (aHighlight && !word)
            return;

          this.browser.finder.highlight(aHighlight, word,
            this._findMode == this.FIND_LINKS);

          // Update the matches count
          this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
        ]]></body>
      </method>

      <!--
        - Updates the highlight-all mode of the findbar and its UI.
        - @param aHighlight (boolean)
        -        Whether to turn the highlight on or off.
        - @param aFromPrefObserver (boolean)
        -        Whether the callee is the pref observer, which means we should
        -        not set the same pref again.
        -->
      <method name="_setHighlightAll">
        <parameter name="aHighlight"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (typeof aHighlight != "boolean") {
            aHighlight = this._highlightAll;
          }
          if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
            this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
          }
          this._highlightAll = aHighlight;
          let checkbox = this.getElement("highlight");
          checkbox.checked = this._highlightAll;
        ]]></body>
      </method>

      <!--
        - Updates the case-sensitivity mode of the findbar and its UI.
        - @param [optional] aString
        -        The string for which case sensitivity might be turned on.
        -        This only used when case-sensitivity is in auto mode,
        -        @see _shouldBeCaseSensitive. The default value for this
        -        parameter is the find-field value.
        -->
      <method name="_updateCaseSensitivity">
        <parameter name="aString"/>
        <body><![CDATA[
          let val = aString || this._findField.value;

          let caseSensitive = this._shouldBeCaseSensitive(val);
          let checkbox = this.getElement("find-case-sensitive");
          let statusLabel = this.getElement("match-case-status");
          checkbox.checked = caseSensitive;

          statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";

          // Show the checkbox on the full Find bar in non-auto mode.
          // Show the label in all other cases.
          let hideCheckbox = this._findMode != this.FIND_NORMAL ||
            (this._typeAheadCaseSensitive != 0 &&
             this._typeAheadCaseSensitive != 1);
          checkbox.hidden = hideCheckbox;
          statusLabel.hidden = !hideCheckbox;

          this.browser.finder.caseSensitive = caseSensitive;
        ]]></body>
      </method>

      <!--
        - Sets the findbar case-sensitivity mode
        - @param aCaseSensitivity (int)
        -   0 - case insensitive
        -   1 - case sensitive
        -   2 - auto = case sensitive iff match string contains upper case letters
        -   @see _shouldBeCaseSensitive
        -->
      <method name="_setCaseSensitivity">
        <parameter name="aCaseSensitivity"/>
        <body><![CDATA[
          this._typeAheadCaseSensitive = aCaseSensitivity;
          this._updateCaseSensitivity();
          this._findFailedString = null;
          this._find();

          this._dispatchFindEvent("casesensitivitychange");
        ]]></body>
      </method>

      <!--
        - Updates the entire-word mode of the findbar and its UI.
        -->
      <method name="_setEntireWord">
        <body><![CDATA[
          let entireWord = this._entireWord;
          let checkbox = this.getElement("find-entire-word");
          let statusLabel = this.getElement("entire-word-status");
          checkbox.checked = entireWord;

          statusLabel.value = entireWord ? this._entireWordStr : "";

          // Show the checkbox on the full Find bar in non-auto mode.
          // Show the label in all other cases.
          let hideCheckbox = this._findMode != this.FIND_NORMAL;
          checkbox.hidden = hideCheckbox;
          statusLabel.hidden = !hideCheckbox;

          this.browser.finder.entireWord = entireWord;
        ]]></body>
      </method>

      <!--
        - Sets the findbar entire-word mode
        - @param aEntireWord (boolean)
        - Whether or not entire-word mode should be turned on.
        -->
      <method name="toggleEntireWord">
        <parameter name="aEntireWord"/>
        <parameter name="aFromPrefObserver"/>
        <body><![CDATA[
          if (!aFromPrefObserver) {
            // Just set the pref; our observer will change the find bar behavior.
            this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
            return;
          }

          this._findFailedString = null;
          this._find();
        ]]></body>
      </method>

      <field name="_strBundle">null</field>
      <property name="strBundle">
        <getter><![CDATA[
          if (!this._strBundle) {
            this._strBundle =
              Components.classes["@mozilla.org/intl/stringbundle;1"]
                        .getService(Components.interfaces.nsIStringBundleService)
                        .createBundle("chrome://global/locale/findbar.properties");
          }
          return this._strBundle;
        ]]></getter>
      </property>

      <!--
        - Opens and displays the find bar.
        -
        - @param aMode
        -        the find mode to be used, which is either FIND_NORMAL,
        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
        -        find mode if any or FIND_NORMAL.
        - @returns true if the find bar wasn't previously open, false otherwise.
        -->
      <method name="open">
        <parameter name="aMode"/>
        <body><![CDATA[
          if (aMode != undefined)
            this._findMode = aMode;

          if (!this._notFoundStr) {
            var stringsBundle = this.strBundle;
            this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
            this._wrappedToTopStr =
              stringsBundle.GetStringFromName("WrappedToTop");
            this._wrappedToBottomStr =
              stringsBundle.GetStringFromName("WrappedToBottom");
            this._normalFindStr =
              stringsBundle.GetStringFromName("NormalFind");
            this._fastFindStr =
              stringsBundle.GetStringFromName("FastFind");
            this._fastFindLinksStr =
              stringsBundle.GetStringFromName("FastFindLinks");
            this._caseSensitiveStr =
              stringsBundle.GetStringFromName("CaseSensitive");
            this._entireWordStr =
              stringsBundle.GetStringFromName("EntireWord");
          }

          this._findFailedString = null;

          this._updateFindUI();
          if (this.hidden) {
            this.removeAttribute("noanim");
            this.hidden = false;

            this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);

            let event = document.createEvent("Events");
            event.initEvent("findbaropen", true, false);
            this.dispatchEvent(event);

            this.browser.finder.onFindbarOpen();

            return true;
          }
          return false;
        ]]></body>
      </method>

      <!--
        - Closes the findbar.
        -->
      <method name="close">
        <parameter name="aNoAnim"/>
        <body><![CDATA[
          if (this.hidden)
            return;

          if (aNoAnim)
            this.setAttribute("noanim", true);
          this.hidden = true;

          // 'focusContent()' iterates over all listeners in the chrome
          // process, so we need to call it from here.
          this.browser.finder.focusContent();
          this.browser.finder.onFindbarClose();

          this._cancelTimers();

          this._findFailedString = null;
        ]]></body>
      </method>

      <method name="clear">
        <body><![CDATA[
          this.browser.finder.removeSelection();
          this._findField.reset();
          this.toggleHighlight(false);
          this._updateStatusUI();
          this._enableFindButtons(false);
        ]]></body>
      </method>

      <method name="_dispatchKeypressEvent">
        <parameter name="aTarget"/>
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!aTarget)
            return;

          let event = document.createEvent("KeyboardEvent");
          event.initKeyEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
                             aEvent.view, aEvent.ctrlKey, aEvent.altKey,
                             aEvent.shiftKey, aEvent.metaKey, aEvent.keyCode,
                             aEvent.charCode);
          aTarget.dispatchEvent(event);
        ]]></body>
      </method>

      <field name="_xulBrowserWindow">null</field>
      <method name="_updateStatusUIBar">
        <parameter name="aFoundURL"/>
        <body><![CDATA[
          if (!this._xulBrowserWindow) {
            try {
              this._xulBrowserWindow =
                window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIWebNavigation)
                      .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                      .treeOwner
                      .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIXULWindow)
                      .XULBrowserWindow;
            } catch (ex) { }
            if (!this._xulBrowserWindow)
              return false;
          }

          // Call this has the same effect like hovering over link,
          // the browser shows the URL as a tooltip.
          this._xulBrowserWindow.setOverLink(aFoundURL || "", null);
          return true;
        ]]></body>
      </method>

      <method name="_finishFAYT">
        <parameter name="aKeypressEvent"/>
        <body><![CDATA[
          this.browser.finder.focusContent();

          if (aKeypressEvent)
            aKeypressEvent.preventDefault();

          this.browser.finder.keyPress(aKeypressEvent);

          this.close();
          return true;
        ]]></body>
      </method>

      <method name="_shouldBeCaseSensitive">
        <parameter name="aString"/>
        <body><![CDATA[
          if (this._typeAheadCaseSensitive == 0)
            return false;
          if (this._typeAheadCaseSensitive == 1)
            return true;

          return aString != aString.toLowerCase();
        ]]></body>
      </method>

      <!-- We get a fake event object through an IPC message which contains the
           data we need to make a decision. We then return |true| if and only if
           the page gets to deal with the event itself. Everywhere we return
           false, the message sender will take care of calling event.preventDefault
           on the real event. -->
      <method name="_onBrowserKeypress">
        <parameter name="aFakeEvent"/>
        <parameter name="aShouldFastFind"/>
        <body><![CDATA[
          const FAYT_LINKS_KEY = "'";
          const FAYT_TEXT_KEY = "/";

          // Fast keypresses can stack up when the content process is slow or
          // hangs when in e10s mode. We make sure the findbar isn't 'opened'
          // several times in a row, because then the find query is selected
          // each time, losing characters typed initially.
          let inputField = this._findField.inputField;
          if (!this.hidden && document.activeElement == inputField) {
            this._dispatchKeypressEvent(inputField, aFakeEvent);
            return false;
          }

          if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
            if (!aFakeEvent.charCode)
              return true;

            this._findField.select();
            this._findField.focus();
            this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
            return false;
          }

          if (!aShouldFastFind)
            return true;

          let key = aFakeEvent.charCode ? String.fromCharCode(aFakeEvent.charCode) : null;
          let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY);
          let autostartFAYT = !manualstartFAYT && this._findAsYouType &&
                              key && key != " ";
          if (manualstartFAYT || autostartFAYT) {
            let mode = (key == FAYT_LINKS_KEY ||
                        (autostartFAYT && this._typeAheadLinksOnly)) ?
              this.FIND_LINKS : this.FIND_TYPEAHEAD;

            // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
            // it doesn't get confused by a lingering value
            this._findField.value = "";

            this.open(mode);
            this._setFindCloseTimeout();
            this._findField.select();
            this._findField.focus();

            if (autostartFAYT)
              this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
            else
              this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);

            return false;
          }
          return undefined;
        ]]></body>
      </method>

      <!-- See nsIMessageListener -->
      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          if (aMessage.target != this._browser) {
            return undefined;
          }
          switch (aMessage.name) {
            case "Findbar:Mouseup":
              if (!this.hidden && this._findMode != this.FIND_NORMAL)
                this.close();
              break;

            case "Findbar:Keypress":
              return this._onBrowserKeypress(aMessage.data.fakeEvent,
                                             aMessage.data.shouldFastFind);
          }
          return undefined;
        ]]></body>
      </method>

      <method name="_updateBrowserWithState">
        <body><![CDATA[
          if (this._browser && this._browser.messageManager) {
            this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
              findMode: this._findMode
            });
          }
        ]]></body>
      </method>

      <method name="_enableFindButtons">
        <parameter name="aEnable"/>
        <body><![CDATA[
          this.getElement("find-next").disabled =
            this.getElement("find-previous").disabled = !aEnable;
        ]]></body>
      </method>

      <!--
        - Determines whether minimalist or general-purpose search UI is to be
        - displayed when the find bar is activated.
        -->
      <method name="_updateFindUI">
        <body><![CDATA[
          let showMinimalUI = this._findMode != this.FIND_NORMAL;

          let nodes = this.getElement("findbar-container").childNodes;
          let wrapper = this.getElement("findbar-textbox-wrapper");
          let foundMatches = this._foundMatches;
          for (let node of nodes) {
            if (node == wrapper || node == foundMatches)
               continue;
            node.hidden = showMinimalUI;
          }
          this.getElement("find-next").hidden =
            this.getElement("find-previous").hidden = showMinimalUI;
          foundMatches.hidden = showMinimalUI || !foundMatches.value;
          this._updateCaseSensitivity();
          this._setEntireWord();
          this._setHighlightAll();

          if (showMinimalUI)
            this._findField.classList.add("minimal");
          else
            this._findField.classList.remove("minimal");

          if (this._findMode == this.FIND_TYPEAHEAD)
            this._findField.placeholder = this._fastFindStr;
          else if (this._findMode == this.FIND_LINKS)
            this._findField.placeholder = this._fastFindLinksStr;
          else
            this._findField.placeholder = this._normalFindStr;
        ]]></body>
      </method>

      <method name="_find">
        <parameter name="aValue"/>
        <body><![CDATA[
          if (!this._dispatchFindEvent(""))
            return;

          let val = aValue || this._findField.value;

          // We have to carry around an explicit version of this,
          // because finder.searchString doesn't update on failed
          // searches.
          this.browser._lastSearchString = val;

          // Only search on input if we don't have a last-failed string,
          // or if the current search string doesn't start with it.
          // In entire-word mode we always attemp a find; since sequential matching
          // is not guaranteed, the first character typed may not be a word (no
          // match), but the with the second character it may well be a word,
          // thus a match.
          if (!this._findFailedString ||
              !val.startsWith(this._findFailedString) ||
              this._entireWord) {
            // Getting here means the user commanded a find op. Make sure any
            // initial prefilling is ignored if it hasn't happened yet.
            if (this._startFindDeferred) {
              this._startFindDeferred.resolve();
              this._startFindDeferred = null;
            }

            this._enableFindButtons(val);
            this._updateCaseSensitivity(val);
            this._setEntireWord();

            this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
                                         this._findMode != this.FIND_NORMAL);
          }

          if (this._findMode != this.FIND_NORMAL)
            this._setFindCloseTimeout();

          if (this._findResetTimeout != -1)
            clearTimeout(this._findResetTimeout);

          // allow a search to happen on input again after a second has
          // expired since the previous input, to allow for dynamic
          // content and/or page loading
          this._findResetTimeout = setTimeout(() => {
            this._findFailedString = null;
            this._findResetTimeout = -1;
          }, 1000);
        ]]></body>
      </method>

      <method name="_flash">
        <body><![CDATA[
          if (this._flashFindBarCount === undefined)
            this._flashFindBarCount = this._initialFlashFindBarCount;

          if (this._flashFindBarCount-- == 0) {
            clearInterval(this._flashFindBarTimeout);
            this.removeAttribute("flash");
            this._flashFindBarCount = 6;
            return;
          }

          this.setAttribute("flash",
                            (this._flashFindBarCount % 2 == 0) ?
                            "false" : "true");
        ]]></body>
      </method>

      <method name="_findAgain">
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          this.browser.finder.findAgain(aFindPrevious,
                                        this._findMode == this.FIND_LINKS,
                                        this._findMode != this.FIND_NORMAL);
        ]]></body>
      </method>

      <method name="_updateStatusUI">
        <parameter name="res"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          switch (res) {
            case this.nsITypeAheadFind.FIND_WRAPPED:
              this._findStatusIcon.setAttribute("status", "wrapped");
              this._findStatusDesc.textContent =
                aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
              this._findField.removeAttribute("status");
              break;
            case this.nsITypeAheadFind.FIND_NOTFOUND:
              this._findStatusIcon.setAttribute("status", "notfound");
              this._findStatusDesc.textContent = this._notFoundStr;
              this._findField.setAttribute("status", "notfound");
              break;
            case this.nsITypeAheadFind.FIND_PENDING:
              this._findStatusIcon.setAttribute("status", "pending");
              this._findStatusDesc.textContent = "";
              this._findField.removeAttribute("status");
              break;
            case this.nsITypeAheadFind.FIND_FOUND:
            default:
              this._findStatusIcon.removeAttribute("status");
              this._findStatusDesc.textContent = "";
              this._findField.removeAttribute("status");
              break;
          }
        ]]></body>
      </method>

      <method name="updateControlState">
        <parameter name="aResult"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          this._updateStatusUI(aResult, aFindPrevious);
          this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND &&
            !!this._findField.value);
        ]]></body>
      </method>

      <method name="_dispatchFindEvent">
        <parameter name="aType"/>
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          let event = document.createEvent("CustomEvent");
          event.initCustomEvent("find" + aType, true, true, {
            query: this._findField.value,
            caseSensitive: !!this._typeAheadCaseSensitive,
            entireWord: this._entireWord,
            highlightAll: this._highlightAll,
            findPrevious: aFindPrevious
          });
          return this.dispatchEvent(event);
        ]]></body>
      </method>


      <!--
        - Opens the findbar, focuses the findfield and selects its contents.
        - Also flashes the findbar the first time it's used.
        - @param aMode
        -        the find mode to be used, which is either FIND_NORMAL,
        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
        -        find mode if any or FIND_NORMAL.
        -->
      <method name="startFind">
        <parameter name="aMode"/>
        <body><![CDATA[
          let prefsvc = this._prefsvc;
          let userWantsPrefill = true;
          this.open(aMode);

          if (this._flashFindBar) {
            this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
            prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
                               --this._flashFindBar);
          }

          let {PromiseUtils} =
            Components.utils.import("resource://gre/modules/PromiseUtils.jsm", {});
          this._startFindDeferred = PromiseUtils.defer();
          let startFindPromise = this._startFindDeferred.promise;

          if (this.prefillWithSelection)
            userWantsPrefill =
              prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");

          if (this.prefillWithSelection && userWantsPrefill) {
            // NB: We have to focus this._findField here so tests that send
            // key events can open and close the find bar synchronously.
            this._findField.focus();

            // (e10s) since we focus lets also select it, otherwise that would
            // only happen in this.onCurrentSelection and, because it is async,
            // there's a chance keypresses could come inbetween, leading to
            // jumbled up queries.
            this._findField.select();

            this.browser.finder.getInitialSelection();
            return startFindPromise;
          }

          // If userWantsPrefill is false but prefillWithSelection is true,
          // then we might need to check the selection clipboard. Call
          // onCurrentSelection to do so.
          // Note: this.onCurrentSelection clears this._startFindDeferred.
          this.onCurrentSelection("", true);
          return startFindPromise;
        ]]></body>
      </method>

      <!--
        - Convenient alias to startFind(gFindBar.FIND_NORMAL);
        -
        - You should generally map the window's find command to this method.
        -   e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
        -->
      <method name="onFindCommand">
        <body><![CDATA[
          return this.startFind(this.FIND_NORMAL);
        ]]></body>
      </method>

      <!--
        - Stub for find-next and find-previous commands
        - @param aFindPrevious
        -        true for find-previous, false otherwise.
        -->
      <method name="onFindAgainCommand">
        <parameter name="aFindPrevious"/>
        <body><![CDATA[
          let findString = this._browser.finder.searchString || this._findField.value;
          if (!findString)
            return this.startFind();

          // We dispatch the findAgain event here instead of in _findAgain since
          // if there is a find event handler that prevents the default then
          // finder.searchString will never get updated which in turn means
          // there would never be findAgain events because of the logic below.
          if (!this._dispatchFindEvent("again", aFindPrevious))
            return undefined;

          // user explicitly requested another search, so do it even if we think it'll fail
          this._findFailedString = null;

          // Ensure the stored SearchString is in sync with what we want to find
          if (this._findField.value != this._browser.finder.searchString) {
            this._find(this._findField.value);
          } else {
            this._findAgain(aFindPrevious);
            if (this._useModalHighlight) {
              this.open();
              this._findField.focus();
            }
          }

          return undefined;
        ]]></body>
      </method>


      <!--
        - This handles all the result changes for both
        - type-ahead-find and highlighting.
        - @param aResult
        -   One of the nsITypeAheadFind.FIND_* constants
        -   indicating the result of a search operation.
        - @param aFindBackwards
        -   If the search was done from the bottom to
        -   the top. This is used for right error messages
        -   when reaching "the end of the page".
        - @param aLinkURL
        -   When a link matched then its URK. Always null
        -   when not in FIND_LINKS mode.
        -->
      <method name="onFindResult">
        <parameter name="aData"/>
        <body><![CDATA[
          if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
            // If an explicit Find Again command fails, re-open the toolbar.
            if (aData.storeResult && this.open()) {
              this._findField.select();
              this._findField.focus();
            }
            this._findFailedString = aData.searchString;
          } else {
            this._findFailedString = null;
          }

          this._updateStatusUI(aData.result, aData.findBackwards);
          this._updateStatusUIBar(aData.linkURL);

          if (this._findMode != this.FIND_NORMAL)
            this._setFindCloseTimeout();
        ]]></body>
      </method>

      <!--
        - This handles all the result changes for matches counts.
        - @param aResult
        -   Result Object, containing the total amount of matches and a vector
        -   of the current result.
        -->
      <method name="onMatchesCountResult">
        <parameter name="aResult"/>
        <body><![CDATA[
          if (aResult.total !== 0) {
            if (aResult.total == -1) {
              this._foundMatches.value = this.pluralForm.get(
                aResult.limit,
                this.strBundle.GetStringFromName("FoundMatchesCountLimit")
              ).replace("#1", aResult.limit);
            } else {
              this._foundMatches.value = this.pluralForm.get(
                aResult.total,
                this.strBundle.GetStringFromName("FoundMatches")
              ).replace("#1", aResult.current)
               .replace("#2", aResult.total);
            }
            this._foundMatches.hidden = false;
          } else {
            this._foundMatches.hidden = true;
            this._foundMatches.value = "";
          }
        ]]></body>
      </method>

      <method name="onHighlightFinished">
        <parameter name="result"/>
        <body><![CDATA[
          // Noop.
        ]]></body>
      </method>

      <method name="onCurrentSelection">
        <parameter name="aSelectionString" />
        <parameter name="aIsInitialSelection" />
        <body><![CDATA[
          // Ignore the prefill if the user has already typed in the findbar,
          // it would have been overwritten anyway. See bug 1198465.
          if (aIsInitialSelection && !this._startFindDeferred)
            return;

          if (/Mac/.test(navigator.platform) && aIsInitialSelection && !aSelectionString) {
            let clipboardSearchString = this.browser.finder.clipboardSearchString;
            if (clipboardSearchString)
              aSelectionString = clipboardSearchString;
          }

          if (aSelectionString)
            this._findField.value = aSelectionString;

          if (aIsInitialSelection) {
            this._enableFindButtons(!!this._findField.value);
            this._findField.select();
            this._findField.focus();

            this._startFindDeferred.resolve();
            this._startFindDeferred = null;
          }
        ]]></body>
      </method>

      <!--
        - This handler may cancel a request to focus content by returning |false|
        - explicitly.
        -->
      <method name="shouldFocusContent">
        <body><![CDATA[
          const fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
          if (fm.focusedWindow != window)
            return false;

          let focusedElement = fm.focusedElement;
          if (!focusedElement)
            return false;

          let bindingParent = document.getBindingParent(focusedElement);
          if (bindingParent != this && bindingParent != this._findField)
            return false;

          return true;
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <!--
        - We have to guard against `this.close` being |null| due to an unknown
        - issue, which is tracked in bug 957999.
        -->
      <handler event="keypress" keycode="VK_ESCAPE" phase="capturing"
               action="if (this.close) this.close();" preventdefault="true"/>
    </handlers>
  </binding>
</bindings>
PK
!<dm!!2chrome/toolkit/content/global/bindings/general.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="generalBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="basecontrol">
    <implementation implements="nsIDOMXULControlElement">
      <!-- public implementation -->
      <property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
                                       else this.removeAttribute('disabled');
                                       return val;"
                                onget="return this.getAttribute('disabled') == 'true';"/>
      <property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex')) || 0"
                                onset="if (val) this.setAttribute('tabindex', val);
                                       else this.removeAttribute('tabindex'); return val;"/>
    </implementation>
  </binding>

  <binding id="basetext" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <implementation implements="nsIDOMXULLabeledControlElement">
      <!-- public implementation -->
      <property name="label"      onset="this.setAttribute('label',val); return val;"
                                  onget="return this.getAttribute('label');"/>
      <property name="crop"       onset="this.setAttribute('crop',val); return val;"
                                  onget="return this.getAttribute('crop');"/>
      <property name="image"      onset="this.setAttribute('image',val); return val;"
                                  onget="return this.getAttribute('image');"/>
      <property name="command"    onset="this.setAttribute('command',val); return val;"
                                  onget="return this.getAttribute('command');"/>
      <property name="accessKey">
        <getter>
          <![CDATA[
            return this.labelElement ? this.labelElement.accessKey : this.getAttribute("accesskey");
          ]]>
        </getter>
        <setter>
          <![CDATA[
            // Always store on the control
            this.setAttribute("accesskey", val);
            // If there is a label, change the accesskey on the labelElement
            // if it's also set there
            if (this.labelElement) {
              this.labelElement.accessKey = val;
            }
            return val;
          ]]>
        </setter>
      </property>

      <field name="labelElement"/>
    </implementation>      
  </binding>

  <binding id="control-item" extends="chrome://global/content/bindings/general.xml#basetext">
    <implementation>
      <property name="value"      onset="this.setAttribute('value', val); return val;"
                                  onget="return this.getAttribute('value');"/>
    </implementation>
  </binding>

  <binding id="root-element">
    <implementation>
      <field name="_lightweightTheme">null</field>
      <constructor><![CDATA[
        if (this.hasAttribute("lightweightthemes")) {
          let temp = {};
          Components.utils.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp);
          this._lightweightTheme = new temp.LightweightThemeConsumer(this.ownerDocument);
        }
      ]]></constructor>
      <destructor><![CDATA[
        if (this._lightweightTheme) {
          this._lightweightTheme.destroy();
          this._lightweightTheme = null;
        }
      ]]></destructor>
    </implementation>
  </binding>

  <binding id="iframe" role="outerdoc">
    <implementation>
      <property name="docShell" readonly="true">
        <getter><![CDATA[
          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          return frameLoader ? frameLoader.docShell : null;
        ]]></getter>
      </property>
      <property name="contentWindow"
                readonly="true"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
      <property name="webNavigation"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
                readonly="true"/>
      <property name="contentDocument" readonly="true"
                onget="return this.webNavigation.document;"/>
    </implementation>
  </binding>

  <binding id="statusbarpanel" display="xul:button">
    <content>
      <children>
        <xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop" crop="right" flex="1"/>
      </children>
    </content>

    <implementation>
      <property name="label"
                onget="return this.getAttribute('label');"
                onset="this.setAttribute('label',val); return val;"/>
      <property name="image"
                onget="return this.getAttribute('image');"
                onset="this.setAttribute('image',val); return val;"/>
      <property name="src"
                onget="return this.getAttribute('src');"
                onset="this.setAttribute('src',val); return val;"/>
    </implementation>
  </binding>

  <binding id="statusbarpanel-menu-iconic" display="xul:menu"
           extends="chrome://global/content/bindings/general.xml#statusbarpanel">
    <content>
      <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
      <children/>
    </content>
  </binding>

  <binding id="statusbar" role="xul:statusbar">
    <content>
      <children/>
      <xul:statusbarpanel class="statusbar-resizerpanel">
        <xul:resizer dir="bottomend"/>
      </xul:statusbarpanel>
    </content>
  </binding>

  <binding id="statusbarpanel-iconic" display="xul:button" role="xul:button"
           extends="chrome://global/content/bindings/general.xml#statusbarpanel">
    <content>
      <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
    </content>
  </binding>

  <binding id="statusbarpanel-iconic-text" display="xul:button" role="xul:button"
           extends="chrome://global/content/bindings/general.xml#statusbarpanel">
    <content>
      <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
      <xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop"/>
    </content>
  </binding>

  <binding id="image" role="xul:image">
    <implementation implements="nsIDOMXULImageElement">
      <property name="src"
                onget="return this.getAttribute('src');"
                onset="this.setAttribute('src',val); return val;"/>
    </implementation>
  </binding>

  <binding id="deck">
    <implementation>
      <property name="selectedIndex"
                onget="return this.getAttribute('selectedIndex') || '0'">
        <setter>
        <![CDATA[
          if (this.selectedIndex == val)
            return val;
          this.setAttribute("selectedIndex", val);
          var event = document.createEvent("Events");
          event.initEvent("select", true, true);
          this.dispatchEvent(event);
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedPanel">
        <getter>
          <![CDATA[
            return this.childNodes[this.selectedIndex];
          ]]>
        </getter>

        <setter>
          <![CDATA[
            var selectedIndex = -1;
            for (var panel = val; panel != null; panel = panel.previousSibling)
              ++selectedIndex;
            this.selectedIndex = selectedIndex;
            return val;
          ]]>
        </setter>
      </property>
    </implementation>
  </binding>

  <binding id="dropmarker" extends="xul:button" role="xul:dropmarker">
    <resources>
      <stylesheet src="chrome://global/skin/dropmarker.css"/>
    </resources>

    <content>
      <xul:image class="dropmarker-icon"/>
    </content>
  </binding>

  <binding id="windowdragbox">
    <implementation>
      <field name="_dragBindingAlive">true</field>
      <constructor>
        if (!this._draggableStarted) {
          this._draggableStarted = true;
          try {
            let tmp = {};
            Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
            let draghandle = new tmp.WindowDraggingElement(this);
            draghandle.mouseDownCheck = function() {
              return this._dragBindingAlive;
            };
          } catch (e) {}
        }
      </constructor>
    </implementation>
  </binding>

</bindings>
PK
!<#3chrome/toolkit/content/global/bindings/groupbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="groupboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="groupbox-base">
    <resources>
      <stylesheet src="chrome://global/skin/groupbox.css"/>
    </resources>
  </binding>

  <binding id="groupbox" role="xul:groupbox"
    extends="chrome://global/content/bindings/groupbox.xml#groupbox-base">
    <content>
      <xul:hbox class="groupbox-title" align="center" pack="start">
        <children includes="caption"/>
      </xul:hbox>
      <xul:box flex="1" class="groupbox-body" xbl:inherits="orient,align,pack">
        <children/>
      </xul:box>
    </content>
  </binding>

  <binding id="caption" extends="chrome://global/content/bindings/general.xml#basetext">
    <resources>
      <stylesheet src="chrome://global/skin/groupbox.css"/>
    </resources>
    
    <content>
      <children>
        <xul:image class="caption-icon" xbl:inherits="src=image"/>
        <xul:label class="caption-text" flex="1"
                   xbl:inherits="default,value=label,crop,accesskey"/>
      </children>
    </content>
  </binding>

</bindings>
PK
!<2chrome/toolkit/content/global/bindings/listbox.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This files relies on these specific Chrome/XBL globals -->
<!-- globals ChromeNodeList -->

<bindings id="listboxBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <!--
    Interface binding that is base for bindings of xul:listbox and
    xul:richlistbox elements. This binding assumes that successors bindings
    will implement the following properties and methods:

    /** Return the number of items */
    readonly itemCount

    /** Return index of given item
    * @param aItem - given item element */
    getIndexOfItem(aItem)

    /** Return item at given index
    * @param aIndex - index of item element */
    getItemAtIndex(aIndex)

    /** Return count of item elements */
    getRowCount()

    /** Return count of visible item elements */
    getNumberOfVisibleRows()

    /** Return index of first visible item element */
    getIndexOfFirstVisibleRow()

    /** Return true if item of given index is visible
     * @param aIndex - index of item element
     *
     * @note XXX: this method should be removed after bug 364612 is fixed
     */
    ensureIndexIsVisible(aIndex)

    /** Return true if item element is visible
     * @param aElement - given item element */
    ensureElementIsVisible(aElement)

    /** Scroll list control to make visible item of given index
     * @param aIndex - index of item element
     *
     * @note XXX: this method should be removed after bug 364612 is fixed
     */
    scrollToIndex(aIndex)

    /** Create item element and append it to the end of listbox
     * @param aLabel - label of new item element
     * @param aValue - value of new item element */
    appendItem(aLabel, aValue)

    /** Create item element and insert it to given position
     * @param aIndex - insertion position
     * @param aLabel - label of new item element
     * @param aValue - value of new item element */
    insertItemAt(aIndex, aLabel, aValue)

    /** Scroll up/down one page
     * @param aDirection - specifies scrolling direction, should be either -1 or 1
     * @return the number of elements the selection scrolled
     */
    scrollOnePage(aDirection)

    /** Fire "select" event */
    _fireOnSelect()
   -->
   <binding id="listbox-base" role="xul:listbox"
            extends="chrome://global/content/bindings/general.xml#basecontrol">

    <implementation implements="nsIDOMXULMultiSelectControlElement">
      <field name="_lastKeyTime">0</field>
      <field name="_incrementalString">""</field>

    <!-- nsIDOMXULSelectControlElement -->
      <property name="selectedItem"
                onset="this.selectItem(val);">
        <getter>
        <![CDATA[
          return this.selectedItems.length > 0 ? this.selectedItems[0] : null;
        ]]>
        </getter>
      </property>

      <property name="selectedIndex">
        <getter>
        <![CDATA[
          if (this.selectedItems.length > 0)
            return this.getIndexOfItem(this.selectedItems[0]);
          return -1;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          if (val >= 0) {
            // This is a micro-optimization so that a call to getIndexOfItem or
            // getItemAtIndex caused by _fireOnSelect (especially for derived
            // widgets) won't loop the children.
            this._selecting = {
              item: this.getItemAtIndex(val),
              index: val
            };
            this.selectItem(this._selecting.item);
            delete this._selecting;
          } else {
            this.clearSelection();
            this.currentItem = null;
          }
        ]]>
        </setter>
      </property>

      <property name="value">
        <getter>
        <![CDATA[
          if (this.selectedItems.length > 0)
            return this.selectedItem.value;
          return null;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          var kids = this.getElementsByAttribute("value", val);
          if (kids && kids.item(0))
            this.selectItem(kids[0]);
          return val;
        ]]>
        </setter>
      </property>

      <method name="removeItemAt">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var remove = this.getItemAtIndex(index);
          if (remove)
            this.removeChild(remove);
          return remove;
        ]]>
        </body>
      </method>

    <!-- nsIDOMXULMultiSelectControlElement -->
      <property name="selType"
                onget="return this.getAttribute('seltype');"
                onset="this.setAttribute('seltype', val); return val;"/>

      <property name="currentItem" onget="return this._currentItem;">
        <setter>
          if (this._currentItem == val)
            return val;

          if (this._currentItem)
            this._currentItem.current = false;
          this._currentItem = val;

          if (val)
            val.current = true;

          return val;
        </setter>
      </property>

      <property name="currentIndex">
        <getter>
          return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
        </getter>
        <setter>
        <![CDATA[
          if (val >= 0)
            this.currentItem = this.getItemAtIndex(val);
          else
            this.currentItem = null;
        ]]>
        </setter>
      </property>

      <field name="selectedItems">new ChromeNodeList()</field>

      <method name="addItemToSelection">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (this.selType != "multiple" && this.selectedCount)
            return;

          if (aItem.selected)
            return;

          this.selectedItems.append(aItem);
          aItem.selected = true;

          this._fireOnSelect();
        ]]>
        </body>
      </method>

      <method name="removeItemFromSelection">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (!aItem.selected)
            return;

          this.selectedItems.remove(aItem);
          aItem.selected = false;
          this._fireOnSelect();
        ]]>
        </body>
      </method>

      <method name="toggleItemSelection">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (aItem.selected)
            this.removeItemFromSelection(aItem);
          else
            this.addItemToSelection(aItem);
        ]]>
        </body>
      </method>

      <method name="selectItem">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (!aItem)
            return;

          if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem)
            return;

          this._selectionStart = null;

          var suppress = this._suppressOnSelect;
          this._suppressOnSelect = true;

          this.clearSelection();
          this.addItemToSelection(aItem);
          this.currentItem = aItem;

          this._suppressOnSelect = suppress;
          this._fireOnSelect();
        ]]>
        </body>
      </method>

      <method name="selectItemRange">
        <parameter name="aStartItem"/>
        <parameter name="aEndItem"/>
        <body>
        <![CDATA[
          if (this.selType != "multiple")
            return;

          if (!aStartItem)
            aStartItem = this._selectionStart ?
              this._selectionStart : this.currentItem;

          if (!aStartItem)
            aStartItem = aEndItem;

          var suppressSelect = this._suppressOnSelect;
          this._suppressOnSelect = true;

          this._selectionStart = aStartItem;

          var currentItem;
          var startIndex = this.getIndexOfItem(aStartItem);
          var endIndex = this.getIndexOfItem(aEndItem);
          if (endIndex < startIndex) {
            currentItem = aEndItem;
            aEndItem = aStartItem;
            aStartItem = currentItem;
          } else {
            currentItem = aStartItem;
          }

          while (currentItem) {
            this.addItemToSelection(currentItem);
            if (currentItem == aEndItem) {
              currentItem = this.getNextItem(currentItem, 1);
              break;
            }
            currentItem = this.getNextItem(currentItem, 1);
          }

          // Clear around new selection
          // Don't use clearSelection() because it causes a lot of noise
          // with respect to selection removed notifications used by the
          // accessibility API support.
          var userSelecting = this._userSelecting;
          this._userSelecting = false; // that's US automatically unselecting
          for (; currentItem; currentItem = this.getNextItem(currentItem, 1))
            this.removeItemFromSelection(currentItem);

          for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem;
               currentItem = this.getNextItem(currentItem, 1))
            this.removeItemFromSelection(currentItem);
          this._userSelecting = userSelecting;

          this._suppressOnSelect = suppressSelect;

          this._fireOnSelect();
        ]]>
        </body>
      </method>

      <method name="selectAll">
        <body>
          this._selectionStart = null;

          var suppress = this._suppressOnSelect;
          this._suppressOnSelect = true;

          var item = this.getItemAtIndex(0);
          while (item) {
            this.addItemToSelection(item);
            item = this.getNextItem(item, 1);
          }

          this._suppressOnSelect = suppress;
          this._fireOnSelect();
        </body>
      </method>

      <method name="invertSelection">
        <body>
          this._selectionStart = null;

          var suppress = this._suppressOnSelect;
          this._suppressOnSelect = true;

          var item = this.getItemAtIndex(0);
          while (item) {
            if (item.selected)
              this.removeItemFromSelection(item);
            else
              this.addItemToSelection(item);
            item = this.getNextItem(item, 1);
          }

          this._suppressOnSelect = suppress;
          this._fireOnSelect();
        </body>
      </method>

      <method name="clearSelection">
        <body>
        <![CDATA[
          if (this.selectedItems) {
            while (this.selectedItems.length > 0) {
              let item = this.selectedItems[0];
              item.selected = false;
              this.selectedItems.remove(item);
            }
          }

          this._selectionStart = null;
          this._fireOnSelect();
        ]]>
        </body>
      </method>

      <property name="selectedCount" readonly="true"
                onget="return this.selectedItems.length;"/>

      <method name="getSelectedItem">
        <parameter name="aIndex"/>
        <body>
        <![CDATA[
          return aIndex < this.selectedItems.length ?
            this.selectedItems[aIndex] : null;
        ]]>
        </body>
      </method>

    <!-- Other public members -->
      <property name="disableKeyNavigation"
                onget="return this.hasAttribute('disableKeyNavigation');">
        <setter>
          if (val)
            this.setAttribute("disableKeyNavigation", "true");
          else
            this.removeAttribute("disableKeyNavigation");
          return val;
        </setter>
      </property>

      <property name="suppressOnSelect"
                onget="return this.getAttribute('suppressonselect') == 'true';"
                onset="this.setAttribute('suppressonselect', val);"/>

      <property name="_selectDelay"
                onset="this.setAttribute('_selectDelay', val);"
                onget="return this.getAttribute('_selectDelay') || 50;"/>

      <method name="timedSelect">
        <parameter name="aItem"/>
        <parameter name="aTimeout"/>
        <body>
        <![CDATA[
          var suppress = this._suppressOnSelect;
          if (aTimeout != -1)
            this._suppressOnSelect = true;

          this.selectItem(aItem);

          this._suppressOnSelect = suppress;

          if (aTimeout != -1) {
            if (this._selectTimeout)
              window.clearTimeout(this._selectTimeout);
            this._selectTimeout =
              window.setTimeout(this._selectTimeoutHandler, aTimeout, this);
          }
        ]]>
        </body>
      </method>

      <method name="moveByOffset">
        <parameter name="aOffset"/>
        <parameter name="aIsSelecting"/>
        <parameter name="aIsSelectingRange"/>
        <body>
        <![CDATA[
          if ((aIsSelectingRange || !aIsSelecting) &&
              this.selType != "multiple")
            return;

          var newIndex = this.currentIndex + aOffset;
          if (newIndex < 0)
            newIndex = 0;

          var numItems = this.getRowCount();
          if (newIndex > numItems - 1)
            newIndex = numItems - 1;

          var newItem = this.getItemAtIndex(newIndex);
          // make sure that the item is actually visible/selectable
          if (this._userSelecting && newItem && !this._canUserSelect(newItem))
            newItem =
              aOffset > 0 ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1) :
                            this.getPreviousItem(newItem, 1) || this.getNextItem(newItem, 1);
          if (newItem) {
            this.ensureIndexIsVisible(this.getIndexOfItem(newItem));
            if (aIsSelectingRange)
              this.selectItemRange(null, newItem);
            else if (aIsSelecting)
              this.selectItem(newItem);

            this.currentItem = newItem;
          }
        ]]>
        </body>
      </method>

    <!-- Private -->
      <method name="getNextItem">
        <parameter name="aStartItem"/>
        <parameter name="aDelta"/>
        <body>
        <![CDATA[
          while (aStartItem) {
            aStartItem = aStartItem.nextSibling;
            if (aStartItem && aStartItem instanceof
                Components.interfaces.nsIDOMXULSelectControlItemElement &&
                (!this._userSelecting || this._canUserSelect(aStartItem))) {
              --aDelta;
              if (aDelta == 0)
                return aStartItem;
            }
          }
          return null;
        ]]></body>
      </method>

      <method name="getPreviousItem">
        <parameter name="aStartItem"/>
        <parameter name="aDelta"/>
        <body>
        <![CDATA[
          while (aStartItem) {
            aStartItem = aStartItem.previousSibling;
            if (aStartItem && aStartItem instanceof
                Components.interfaces.nsIDOMXULSelectControlItemElement &&
                (!this._userSelecting || this._canUserSelect(aStartItem))) {
              --aDelta;
              if (aDelta == 0)
                return aStartItem;
            }
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="_moveByOffsetFromUserEvent">
        <parameter name="aOffset"/>
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (!aEvent.defaultPrevented) {
            this._userSelecting = true;
            this._mayReverse = true;
            this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey);
            this._userSelecting = false;
            this._mayReverse = false;
            aEvent.preventDefault();
          }
        ]]>
        </body>
      </method>

      <method name="_canUserSelect">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          var style = document.defaultView.getComputedStyle(aItem);
          return style.display != "none" && style.visibility == "visible";
        ]]>
        </body>
      </method>

      <method name="_selectTimeoutHandler">
        <parameter name="aMe"/>
        <body>
          aMe._fireOnSelect();
          aMe._selectTimeout = null;
        </body>
      </method>

      <field name="_suppressOnSelect">false</field>
      <field name="_userSelecting">false</field>
      <field name="_mayReverse">false</field>
      <field name="_selectTimeout">null</field>
      <field name="_currentItem">null</field>
      <field name="_selectionStart">null</field>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
               action="this._moveByOffsetFromUserEvent(-1, event);"
               group="system"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
               action="this._moveByOffsetFromUserEvent(1, event);"
               group="system"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="control shift any"
               group="system">
        <![CDATA[
          this._mayReverse = true;
          this._moveByOffsetFromUserEvent(-this.currentIndex, event);
          this._mayReverse = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_END" modifiers="control shift any"
               group="system">
        <![CDATA[
          this._mayReverse = true;
          this._moveByOffsetFromUserEvent(this.getRowCount() - this.currentIndex - 1, event);
          this._mayReverse = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="control shift any"
               group="system">
        <![CDATA[
          this._mayReverse = true;
          this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
          this._mayReverse = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="control shift any"
               group="system">
        <![CDATA[
          this._mayReverse = true;
          this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
          this._mayReverse = false;
        ]]>
      </handler>
      <handler event="keypress" key=" " modifiers="control" phase="target">
      <![CDATA[
        if (this.currentItem && this.selType == "multiple")
          this.toggleItemSelection(this.currentItem);
      ]]>
      </handler>
      <handler event="focus">
      <![CDATA[
        if (this.getRowCount() > 0) {
          if (this.currentIndex == -1) {
            this.currentIndex = this.getIndexOfFirstVisibleRow();
          } else {
            this.currentItem._fireEvent("DOMMenuItemActive");
          }
        }
        this._lastKeyTime = 0;
      ]]>
      </handler>
      <handler event="keypress" phase="target">
      <![CDATA[
        if (this.disableKeyNavigation || !event.charCode ||
            event.altKey || event.ctrlKey || event.metaKey)
          return;

        if (event.timeStamp - this._lastKeyTime > 1000)
          this._incrementalString = "";

        var key = String.fromCharCode(event.charCode).toLowerCase();
        this._incrementalString += key;
        this._lastKeyTime = event.timeStamp;

        // If all letters in the incremental string are the same, just
        // try to match the first one
        var incrementalString = /^(.)\1+$/.test(this._incrementalString) ?
                                RegExp.$1 : this._incrementalString;
        var length = incrementalString.length;

        var rowCount = this.getRowCount();
        var l = this.selectedItems.length;
        var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
        // start from the first element if none was selected or from the one
        // following the selected one if it's a new or a repeated-letter search
        if (start == -1 || length == 1)
          start++;

        for (var i = 0; i < rowCount; i++) {
          var k = (start + i) % rowCount;
          var listitem = this.getItemAtIndex(k);
          if (!this._canUserSelect(listitem))
            continue;
          // allow richlistitems to specify the string being searched for
          var searchText = "searchLabel" in listitem ? listitem.searchLabel :
                           listitem.getAttribute("label"); // (see also bug 250123)
          searchText = searchText.substring(0, length).toLowerCase();
          if (searchText == incrementalString) {
            this.ensureIndexIsVisible(k);
            this.timedSelect(listitem, this._selectDelay);
            break;
          }
        }
      ]]>
      </handler>
    </handlers>
  </binding>


  <!-- Binding for xul:listbox element.
  -->
  <binding id="listbox"
           extends="#listbox-base">

    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <content>
      <children includes="listcols">
        <xul:listcols>
          <xul:listcol flex="1"/>
        </xul:listcols>
      </children>
      <xul:listrows>
        <children includes="listhead"/>
        <xul:listboxbody xbl:inherits="rows,size,minheight">
          <children includes="listitem"/>
        </xul:listboxbody>
      </xul:listrows>
    </content>

    <implementation>

      <!-- ///////////////// public listbox members ///////////////// -->

      <property name="listBoxObject"
                onget="return this.boxObject;"
                readonly="true"/>

      <!-- ///////////////// private listbox members ///////////////// -->

      <field name="_touchY">-1</field>

      <method name="_fireOnSelect">
        <body>
        <![CDATA[
          if (!this._suppressOnSelect && !this.suppressOnSelect) {
            var event = document.createEvent("Events");
            event.initEvent("select", true, true);
            this.dispatchEvent(event);
          }
        ]]>
        </body>
      </method>

      <constructor>
      <![CDATA[
        var count = this.itemCount;
        for (var index = 0; index < count; index++) {
          var item = this.getItemAtIndex(index);
          if (item.getAttribute("selected") == "true")
            this.selectedItems.append(item);
        }
      ]]>
      </constructor>

      <!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->

      <method name="appendItem">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          const XULNS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

          var item = this.ownerDocument.createElementNS(XULNS, "listitem");
          item.setAttribute("label", aLabel);
          item.setAttribute("value", aValue);
          this.appendChild(item);
          return item;
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="aIndex"/>
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          const XULNS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

          var item = this.ownerDocument.createElementNS(XULNS, "listitem");
          item.setAttribute("label", aLabel);
          item.setAttribute("value", aValue);
          var before = this.getItemAtIndex(aIndex);
          if (before)
            this.insertBefore(item, before);
          else
            this.appendChild(item);
          return item;
        </body>
      </method>

      <property name="itemCount" readonly="true"
                onget="return this.listBoxObject.getRowCount()"/>

      <!-- ///////////////// nsIListBoxObject ///////////////// -->
      <method name="getIndexOfItem">
        <parameter name="item"/>
        <body>
          <![CDATA[
            if (this._selecting && this._selecting.item == item)
              return this._selecting.index;
            return this.listBoxObject.getIndexOfItem(item);
          ]]>
        </body>
      </method>
      <method name="getItemAtIndex">
        <parameter name="index"/>
        <body>
          <![CDATA[
            if (this._selecting && this._selecting.index == index)
              return this._selecting.item;
            return this.listBoxObject.getItemAtIndex(index);
          ]]>
        </body>
      </method>
      <method name="ensureIndexIsVisible">
        <parameter name="index"/>
        <body>
          return this.listBoxObject.ensureIndexIsVisible(index);
        </body>
      </method>
      <method name="ensureElementIsVisible">
        <parameter name="element"/>
        <body>
          return this.ensureIndexIsVisible(this.listBoxObject.getIndexOfItem(element));
        </body>
      </method>
      <method name="scrollToIndex">
        <parameter name="index"/>
        <body>
          return this.listBoxObject.scrollToIndex(index);
        </body>
      </method>
      <method name="getNumberOfVisibleRows">
        <body>
          return this.listBoxObject.getNumberOfVisibleRows();
        </body>
      </method>
      <method name="getIndexOfFirstVisibleRow">
        <body>
          return this.listBoxObject.getIndexOfFirstVisibleRow();
        </body>
      </method>
      <method name="getRowCount">
        <body>
          return this.listBoxObject.getRowCount();
        </body>
      </method>

      <method name="scrollOnePage">
        <parameter name="direction"/>  <!-- Must be -1 or 1 -->
        <body>
          <![CDATA[
            var pageOffset = this.getNumberOfVisibleRows() * direction;
            // skip over invisible elements - the user won't care about them
            for (var i = 0; i != pageOffset; i += direction) {
              var item = this.getItemAtIndex(this.currentIndex + i);
              if (item && !this._canUserSelect(item))
                pageOffset += direction;
            }
            var newTop = this.getIndexOfFirstVisibleRow() + pageOffset;
            if (direction == 1) {
              var maxTop = this.getRowCount() - this.getNumberOfVisibleRows();
              for (i = this.getRowCount(); i >= 0 && i > maxTop; i--) {
                item = this.getItemAtIndex(i);
                if (item && !this._canUserSelect(item))
                  maxTop--;
              }
              if (newTop >= maxTop)
                newTop = maxTop;
            }
            if (newTop < 0)
              newTop = 0;
            this.scrollToIndex(newTop);
            return pageOffset;
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress" key=" " phase="target">
        <![CDATA[
          if (this.currentItem) {
            if (this.currentItem.getAttribute("type") != "checkbox") {
              this.addItemToSelection(this.currentItem);
              // Prevent page from scrolling on the space key.
              event.preventDefault();
            } else if (!this.currentItem.disabled) {
              this.currentItem.checked = !this.currentItem.checked;
              this.currentItem.doCommand();
              // Prevent page from scrolling on the space key.
              event.preventDefault();
            }
          }
        ]]>
      </handler>

      <handler event="MozSwipeGesture">
        <![CDATA[
          // Figure out which index to show
          let targetIndex = 0;

          // Only handle swipe gestures up and down
          switch (event.direction) {
            case event.DIRECTION_DOWN:
              targetIndex = this.itemCount - 1;
              // Fall through for actual action
            case event.DIRECTION_UP:
              this.ensureIndexIsVisible(targetIndex);
              break;
          }
        ]]>
      </handler>

      <handler event="touchstart">
        <![CDATA[
          if (event.touches.length > 1) {
            // Multiple touch points detected, abort. In particular this aborts
            // the panning gesture when the user puts a second finger down after
            // already panning with one finger. Aborting at this point prevents
            // the pan gesture from being resumed until all fingers are lifted
            // (as opposed to when the user is back down to one finger).
            this._touchY = -1;
          } else {
            this._touchY = event.touches[0].screenY;
          }
        ]]>
      </handler>
      <handler event="touchmove">
        <![CDATA[
          if (event.touches.length == 1 &&
              this._touchY >= 0) {
            let deltaY = this._touchY - event.touches[0].screenY;
            let lines = Math.trunc(deltaY / this.listBoxObject.getRowHeight());
            if (Math.abs(lines) > 0) {
              this.listBoxObject.scrollByLines(lines);
              deltaY -= lines * this.listBoxObject.getRowHeight();
              this._touchY = event.touches[0].screenY + deltaY;
            }
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="touchend">
        <![CDATA[
          this._touchY = -1;
        ]]>
      </handler>

    </handlers>
  </binding>

  <binding id="listrows">

    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <handlers>
      <handler event="DOMMouseScroll" phase="capturing">
      <![CDATA[
        if (event.axis == event.HORIZONTAL_AXIS)
          return;

        var listBox = this.parentNode.listBoxObject;
        var rows = event.detail;
        if (rows == UIEvent.SCROLL_PAGE_UP)
          rows = -listBox.getNumberOfVisibleRows();
        else if (rows == UIEvent.SCROLL_PAGE_DOWN)
          rows = listBox.getNumberOfVisibleRows();

        listBox.scrollByLines(rows);
        event.preventDefault();
      ]]>
      </handler>

      <handler event="MozMousePixelScroll" phase="capturing">
      <![CDATA[
        if (event.axis == event.HORIZONTAL_AXIS)
          return;

        // shouldn't be scrolled by pixel scrolling events before a line/page
        // scrolling event.
        event.preventDefault();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="listitem" role="xul:listitem"
           extends="chrome://global/content/bindings/general.xml#basetext">
    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <content>
      <children>
        <xul:listcell xbl:inherits="label,crop,disabled,flexlabel"/>
      </children>
    </content>

    <implementation implements="nsIDOMXULSelectControlItemElement">
      <property name="current" onget="return this.getAttribute('current') == 'true';">
        <setter><![CDATA[
          if (val)
            this.setAttribute("current", "true");
          else
            this.removeAttribute("current");

          let control = this.control;
          if (!control || !control.suppressMenuItemEvent) {
            this._fireEvent(val ? "DOMMenuItemActive" : "DOMMenuItemInactive");
          }

          return val;
        ]]></setter>
      </property>

      <!-- ///////////////// nsIDOMXULSelectControlItemElement ///////////////// -->

      <property name="value" onget="return this.getAttribute('value');"
                             onset="this.setAttribute('value', val); return val;"/>
      <property name="label" onget="return this.getAttribute('label');"
                             onset="this.setAttribute('label', val); return val;"/>

      <property name="selected" onget="return this.getAttribute('selected') == 'true';">
        <setter><![CDATA[
          if (val)
            this.setAttribute("selected", "true");
          else
            this.removeAttribute("selected");

          return val;
        ]]></setter>
      </property>

      <property name="control">
        <getter><![CDATA[
          var parent = this.parentNode;
          while (parent) {
            if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
              return parent;
            parent = parent.parentNode;
          }
          return null;
        ]]></getter>
      </property>

      <method name="_fireEvent">
        <parameter name="name"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent(name, true, true);
          this.dispatchEvent(event);
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <!-- If there is no modifier key, we select on mousedown, not
           click, so that drags work correctly. -->
      <handler event="mousedown">
      <![CDATA[
        var control = this.control;
        if (!control || control.disabled)
          return;
        if ((!event.ctrlKey || (/Mac/.test(navigator.platform) && event.button == 2)) &&
            !event.shiftKey && !event.metaKey) {
          if (!this.selected) {
            control.selectItem(this);
          }
          control.currentItem = this;
        }
      ]]>
      </handler>

      <!-- On a click (up+down on the same item), deselect everything
           except this item. -->
      <handler event="click" button="0">
      <![CDATA[
        var control = this.control;
        if (!control || control.disabled)
          return;
        control._userSelecting = true;
        if (control.selType != "multiple") {
          control.selectItem(this);
        } else if (event.ctrlKey || event.metaKey) {
          control.toggleItemSelection(this);
          control.currentItem = this;
        } else if (event.shiftKey) {
          control.selectItemRange(null, this);
          control.currentItem = this;
        } else {
          /* We want to deselect all the selected items except what was
            clicked, UNLESS it was a right-click.  We have to do this
            in click rather than mousedown so that you can drag a
            selected group of items */

          // use selectItemRange instead of selectItem, because this
          // doesn't de- and reselect this item if it is selected
          control.selectItemRange(this, this);
        }
        control._userSelecting = false;
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="listitem-iconic"
           extends="chrome://global/content/bindings/listbox.xml#listitem">
    <content>
      <children>
        <xul:listcell class="listcell-iconic" xbl:inherits="label,image,crop,disabled,flexlabel"/>
      </children>
    </content>
  </binding>

  <binding id="listitem-checkbox"
           extends="chrome://global/content/bindings/listbox.xml#listitem">
    <content>
      <children>
        <xul:listcell type="checkbox" xbl:inherits="label,crop,checked,disabled,flexlabel"/>
      </children>
    </content>

    <implementation>
      <property name="checked"
                onget="return this.getAttribute('checked') == 'true';">
        <setter><![CDATA[
          if (val)
            this.setAttribute("checked", "true");
          else
            this.removeAttribute("checked");
          var event = document.createEvent("Events");
          event.initEvent("CheckboxStateChange", true, true);
          this.dispatchEvent(event);
          return val;
        ]]></setter>
      </property>
    </implementation>

    <handlers>
      <handler event="mousedown" button="0">
      <![CDATA[
        if (!this.disabled && !this.control.disabled) {
          this.checked = !this.checked;
          this.doCommand();
        }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="listitem-checkbox-iconic"
           extends="chrome://global/content/bindings/listbox.xml#listitem-checkbox">
    <content>
      <children>
        <xul:listcell type="checkbox" class="listcell-iconic" xbl:inherits="label,image,crop,checked,disabled,flexlabel"/>
      </children>
    </content>
  </binding>

  <binding id="listcell" role="xul:listcell"
           extends="chrome://global/content/bindings/general.xml#basecontrol">

    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <content>
      <children>
        <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
      </children>
    </content>
  </binding>

  <binding id="listcell-iconic"
           extends="chrome://global/content/bindings/listbox.xml#listcell">
    <content>
      <children>
        <xul:image class="listcell-icon" xbl:inherits="src=image"/>
        <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
      </children>
    </content>
  </binding>

  <binding id="listcell-checkbox"
           extends="chrome://global/content/bindings/listbox.xml#listcell">
    <content>
      <children>
        <xul:image class="listcell-check" xbl:inherits="checked,disabled"/>
        <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
      </children>
    </content>
  </binding>

  <binding id="listcell-checkbox-iconic"
           extends="chrome://global/content/bindings/listbox.xml#listcell-checkbox">
    <content>
      <children>
        <xul:image class="listcell-check" xbl:inherits="checked,disabled"/>
        <xul:image class="listcell-icon" xbl:inherits="src=image"/>
        <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
      </children>
    </content>
  </binding>

  <binding id="listhead" role="xul:listhead">

    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <content>
      <xul:listheaditem>
        <children includes="listheader"/>
      </xul:listheaditem>
    </content>
  </binding>

  <binding id="listheader" display="xul:button" role="xul:listheader">

    <resources>
      <stylesheet src="chrome://global/skin/listbox.css"/>
    </resources>

    <content>
      <xul:image class="listheader-icon"/>
      <xul:label class="listheader-label" xbl:inherits="value=label,crop" flex="1" crop="right"/>
      <xul:image class="listheader-sortdirection" xbl:inherits="sortDirection"/>
    </content>
  </binding>

</bindings>
PK
!<Qqz!+!+/chrome/toolkit/content/global/bindings/menu.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="menuitemBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="menuitem-base" role="xul:menuitem"
           extends="chrome://global/content/bindings/general.xml#control-item">
    <resources>
      <stylesheet src="chrome://global/skin/menu.css"/>
    </resources>
    <implementation implements="nsIDOMXULSelectControlItemElement, nsIDOMXULContainerItemElement">
      <!-- nsIDOMXULSelectControlItemElement -->
      <property name="selected" readonly="true"
                onget="return this.getAttribute('selected') == 'true';"/>
      <property name="control" readonly="true">
        <getter>
          <![CDATA[
            var parent = this.parentNode;
            if (parent &&
                parent.parentNode instanceof Components.interfaces.nsIDOMXULSelectControlElement)
              return parent.parentNode;
            return null;
          ]]>
        </getter>
      </property>

      <!-- nsIDOMXULContainerItemElement -->
      <property name="parentContainer" readonly="true">
        <getter>
          for (var parent = this.parentNode; parent; parent = parent.parentNode) {
            if (parent instanceof Components.interfaces.nsIDOMXULContainerElement)
              return parent;
          }
          return null;
        </getter>
      </property>
    </implementation>
  </binding>

  <binding id="menu-base"
           extends="chrome://global/content/bindings/menu.xml#menuitem-base">

    <implementation implements="nsIDOMXULContainerElement">
      <property name="open" onget="return this.hasAttribute('open');">
        <setter><![CDATA[
          this.boxObject.openMenu(val);
          return val;
        ]]></setter>
      </property>

      <property name="openedWithKey" readonly="true">
        <getter><![CDATA[
          return this.boxObject.openedWithKey;
        ]]></getter>
      </property>

      <!-- nsIDOMXULContainerElement interface -->
      <method name="appendItem">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          return this.insertItemAt(-1, aLabel, aValue);
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="aIndex"/>
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          const XUL_NS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

          var menupopup = this.menupopup;
          if (!menupopup) {
            menupopup = this.ownerDocument.createElementNS(XUL_NS, "menupopup");
            this.appendChild(menupopup);
          }

          var menuitem = this.ownerDocument.createElementNS(XUL_NS, "menuitem");
          menuitem.setAttribute("label", aLabel);
          menuitem.setAttribute("value", aValue);

          var before = this.getItemAtIndex(aIndex);
          if (before)
            return menupopup.insertBefore(menuitem, before);
          return menupopup.appendChild(menuitem);
        </body>
      </method>

      <method name="removeItemAt">
        <parameter name="aIndex"/>
        <body>
        <![CDATA[
          var menupopup = this.menupopup;
          if (menupopup) {
            var item = this.getItemAtIndex(aIndex);
            if (item)
              return menupopup.removeChild(item);
          }
          return null;
        ]]>
        </body>
      </method>

      <property name="itemCount" readonly="true">
        <getter>
          var menupopup = this.menupopup;
          return menupopup ? menupopup.childNodes.length : 0;
        </getter>
      </property>

      <method name="getIndexOfItem">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          var menupopup = this.menupopup;
          if (menupopup) {
            var items = menupopup.childNodes;
            var length = items.length;
            for (var index = 0; index < length; ++index) {
              if (items[index] == aItem)
                return index;
            }
          }
          return -1;
        ]]>
        </body>
      </method>

      <method name="getItemAtIndex">
        <parameter name="aIndex"/>
        <body>
        <![CDATA[
          var menupopup = this.menupopup;
          if (!menupopup || aIndex < 0 || aIndex >= menupopup.childNodes.length)
            return null;

          return menupopup.childNodes[aIndex];
        ]]>
        </body>
      </method>

      <property name="menupopup" readonly="true">
        <getter>
        <![CDATA[
          const XUL_NS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

          for (var child = this.firstChild; child; child = child.nextSibling) {
            if (child.namespaceURI == XUL_NS && child.localName == "menupopup")
              return child;
          }
          return null;
        ]]>
        </getter>
      </property>
    </implementation>
  </binding>

  <binding id="menu"
           extends="chrome://global/content/bindings/menu.xml#menu-base">
    <content>
      <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
      <xul:hbox class="menu-accel-container" anonid="accel">
        <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
      </xul:hbox>
      <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
        <xul:image/>
      </xul:hbox>
      <children includes="menupopup"/>
    </content>
  </binding>

  <binding id="menuitem" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
    <content>
      <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
      <xul:hbox class="menu-accel-container" anonid="accel">
        <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
    <content>
      <xul:label class="menu-text" xbl:inherits="value=label,crop" crop="right"/>
    </content>
  </binding>

  <binding id="menu-menubar"
           extends="chrome://global/content/bindings/menu.xml#menu-base">
    <content>
      <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
      <children includes="menupopup"/>
    </content>
  </binding>

  <binding id="menu-menubar-iconic"
           extends="chrome://global/content/bindings/menu.xml#menu-base">
    <content>
      <xul:image class="menubar-left" xbl:inherits="src=image"/>
      <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
      <children includes="menupopup"/>
    </content>
  </binding>

  <binding id="menuitem-iconic" extends="chrome://global/content/bindings/menu.xml#menuitem">
    <content>
      <xul:hbox class="menu-iconic-left" align="center" pack="center"
                xbl:inherits="selected,_moz-menuactive,disabled,checked">
        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
      </xul:hbox>
      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
      <children/>
      <xul:hbox class="menu-accel-container" anonid="accel">
        <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="menuitem-iconic-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
    <content>
      <xul:hbox class="menu-iconic-left" align="center" pack="center"
                xbl:inherits="selected,disabled,checked">
        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
      </xul:hbox>
      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
    </content>
  </binding>

  <binding id="menucaption-inmenulist" extends="chrome://global/content/bindings/menu.xml#menucaption">
    <content>
      <xul:hbox class="menu-iconic-left" align="center" pack="center"
                xbl:inherits="selected,disabled,checked">
        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
      </xul:hbox>
      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop,highlightable" crop="right"/>
      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,highlightable" crop="right"/>
    </content>
  </binding>

  <binding id="menuitem-iconic-desc-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
    <content>
      <xul:hbox class="menu-iconic-left" align="center" pack="center"
                xbl:inherits="selected,disabled,checked">
        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
      </xul:hbox>
      <xul:label class="menu-iconic-text" xbl:inherits="value=label,accesskey,crop" crop="right" flex="1"/>
      <xul:label class="menu-iconic-text menu-description" xbl:inherits="value=description" crop="right" flex="10000"/>
    </content>
  </binding>

  <binding id="menu-iconic"
           extends="chrome://global/content/bindings/menu.xml#menu-base">
    <content>
      <xul:hbox class="menu-iconic-left" align="center" pack="center">
        <xul:image class="menu-iconic-icon" xbl:inherits="src=image"/>
      </xul:hbox>
      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop,highlightable" crop="right"/>
      <xul:label class="menu-iconic-highlightable-text" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right"/>
      <xul:hbox class="menu-accel-container" anonid="accel">
        <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
      </xul:hbox>
      <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
        <xul:image/>
      </xul:hbox>
      <children includes="menupopup|template"/>
    </content>
  </binding>

  <binding id="menubutton-item" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
    <content>
      <xul:label class="menubutton-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
      <children includes="menupopup"/>
    </content>
  </binding>

  <binding id="menuseparator" role="xul:menuseparator"
           extends="chrome://global/content/bindings/menu.xml#menuitem-base">
  </binding>

</bindings>
PK
!<>wSwS3chrome/toolkit/content/global/bindings/menulist.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="menulistBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="menulist-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/content/menulist.css"/>
      <stylesheet src="chrome://global/skin/menulist.css"/>
    </resources>
  </binding>

  <binding id="menulist" display="xul:menu" role="xul:menulist"
           extends="chrome://global/content/bindings/menulist.xml#menulist-base">
    <content sizetopopup="pref">
      <xul:hbox class="menulist-label-box" flex="1">
        <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
        <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey,highlightable" crop="right" flex="1"/>
        <xul:label class="menulist-highlightable-label" xbl:inherits="xbl:text=label,crop,accesskey,highlightable" crop="right" flex="1"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
      <children includes="menupopup"/>
    </content>

    <handlers>
      <handler event="command" phase="capturing"
        action="if (event.target.parentNode.parentNode == this) this.selectedItem = event.target;"/>

      <handler event="popupshowing">
        <![CDATA[
          if (event.target.parentNode == this) {
            this.menuBoxObject.activeChild = null;
            if (this.selectedItem)
              // Not ready for auto-setting the active child in hierarchies yet.
              // For now, only do this when the outermost menupopup opens.
              this.menuBoxObject.activeChild = this.mSelectedInternal;
          }
        ]]>
      </handler>

      <handler event="keypress" modifiers="shift any" group="system">
        <![CDATA[
          if (!event.defaultPrevented &&
              (event.keyCode == KeyEvent.DOM_VK_UP ||
               event.keyCode == KeyEvent.DOM_VK_DOWN ||
               event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
               event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
               event.keyCode == KeyEvent.DOM_VK_HOME ||
               event.keyCode == KeyEvent.DOM_VK_END ||
               event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
               event.charCode > 0)) {
            // Moving relative to an item: start from the currently selected item
            this.menuBoxObject.activeChild = this.mSelectedInternal;
            if (this.menuBoxObject.handleKeyPress(event)) {
              this.menuBoxObject.activeChild.doCommand();
              event.preventDefault();
            }
          }
        ]]>
      </handler>
    </handlers>

    <implementation implements="nsIDOMXULMenuListElement">
      <constructor>
        this.mInputField = null;
        this.mSelectedInternal = null;
        this.mAttributeObserver = null;
        this.menuBoxObject = this.boxObject;
        this.setInitialSelection();
      </constructor>

      <method name="setInitialSelection">
        <body>
          <![CDATA[
            var popup = this.menupopup;
            if (popup) {
              var arr = popup.getElementsByAttribute("selected", "true");

              var editable = this.editable;
              var value = this.value;
              if (!arr.item(0) && value)
                arr = popup.getElementsByAttribute(editable ? "label" : "value", value);

              if (arr.item(0))
                this.selectedItem = arr[0];
              else if (!editable)
                this.selectedIndex = 0;
            }
          ]]>
        </body>
      </method>

      <property name="value" onget="return this.getAttribute('value');">
        <setter>
          <![CDATA[
            // if the new value is null, we still need to remove the old value
            if (val == null)
              return this.selectedItem = val;

            var arr = null;
            var popup = this.menupopup;
            if (popup)
              arr = popup.getElementsByAttribute("value", val);

            if (arr && arr.item(0))
              this.selectedItem = arr[0];
            else {
              this.selectedItem = null;
              this.setAttribute("value", val);
            }

            return val;
          ]]>
        </setter>
      </property>

      <property name="inputField" readonly="true" onget="return null;"/>

      <property name="crop" onset="this.setAttribute('crop',val); return val;"
                            onget="return this.getAttribute('crop');"/>
      <property name="image"  onset="this.setAttribute('image',val); return val;"
                              onget="return this.getAttribute('image');"/>
      <property name="label" readonly="true" onget="return this.getAttribute('label');"/>
      <property name="description" onset="this.setAttribute('description',val); return val;"
                                   onget="return this.getAttribute('description');"/>

      <property name="editable" onget="return this.getAttribute('editable') == 'true';">
        <setter>
          <![CDATA[
            if (!val && this.editable) {
              // If we were focused and transition from editable to not editable,
              // focus the parent menulist so that the focus does not get stuck.
              if (this.inputField == document.activeElement)
                window.setTimeout(() => this.focus(), 0);
            }

            this.setAttribute("editable", val);
            return val;
          ]]>
        </setter>
      </property>

      <property name="open" onset="this.menuBoxObject.openMenu(val);
                                   return val;"
                            onget="return this.hasAttribute('open');"/>

      <property name="itemCount" readonly="true"
                onget="return this.menupopup ? this.menupopup.childNodes.length : 0"/>

      <property name="menupopup" readonly="true">
        <getter>
          <![CDATA[
            var popup = this.firstChild;
            while (popup && popup.localName != "menupopup")
              popup = popup.nextSibling;
            return popup;
          ]]>
        </getter>
      </property>

      <method name="contains">
        <parameter name="item"/>
        <body>
          <![CDATA[
            if (!item)
              return false;

            var parent = item.parentNode;
            return (parent && parent.parentNode == this);
          ]]>
        </body>
      </method>

      <property name="selectedIndex">
        <getter>
          <![CDATA[
            // Quick and dirty. We won't deal with hierarchical menulists yet.
            if (!this.selectedItem ||
                !this.mSelectedInternal.parentNode ||
                this.mSelectedInternal.parentNode.parentNode != this)
              return -1;

            var children = this.mSelectedInternal.parentNode.childNodes;
            var i = children.length;
            while (i--)
              if (children[i] == this.mSelectedInternal)
                break;

            return i;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var popup = this.menupopup;
            if (popup && 0 <= val) {
              if (val < popup.childNodes.length)
                this.selectedItem = popup.childNodes[val];
            } else
              this.selectedItem = null;
            return val;
          ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
          <![CDATA[
            return this.mSelectedInternal;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var oldval = this.mSelectedInternal;
            if (oldval == val)
              return val;

            if (val && !this.contains(val))
              return val;

            if (oldval) {
              oldval.removeAttribute("selected");
              this.mAttributeObserver.disconnect();
            }

            this.mSelectedInternal = val;
            let attributeFilter = ["value", "label", "image", "description"];
            if (val) {
              val.setAttribute("selected", "true");
              for (let attr of attributeFilter) {
                if (val.hasAttribute(attr)) {
                  this.setAttribute(attr, val.getAttribute(attr));
                } else {
                  this.removeAttribute(attr);
                }
              }

              this.mAttributeObserver = new MutationObserver(this.handleMutation.bind(this));
              this.mAttributeObserver.observe(val, { attributeFilter });
            } else {
              for (let attr of attributeFilter) {
                this.removeAttribute(attr);
              }
            }

            var event = document.createEvent("Events");
            event.initEvent("select", true, true);
            this.dispatchEvent(event);

            event = document.createEvent("Events");
            event.initEvent("ValueChange", true, true);
            this.dispatchEvent(event);

            return val;
          ]]>
        </setter>
      </property>

      <method name="handleMutation">
        <parameter name="aRecords"/>
        <body>
          <![CDATA[
            for (let record of aRecords) {
              let t = record.target;
              if (t == this.mSelectedInternal) {
                let attrName = record.attributeName;
                switch (attrName) {
                  case "value":
                  case "label":
                  case "image":
                  case "description":
                    if (t.hasAttribute(attrName)) {
                      this.setAttribute(attrName, t.getAttribute(attrName));
                    } else {
                      this.removeAttribute(attrName);
                    }
                }
              }
            }
          ]]>
        </body>
      </method>

      <method name="getIndexOfItem">
        <parameter name="item"/>
        <body>
        <![CDATA[
          var popup = this.menupopup;
          if (popup) {
            var children = popup.childNodes;
            var i = children.length;
            while (i--)
              if (children[i] == item)
                return i;
          }
          return -1;
        ]]>
        </body>
      </method>

      <method name="getItemAtIndex">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var popup = this.menupopup;
          if (popup) {
            var children = popup.childNodes;
            if (index >= 0 && index < children.length)
              return children[index];
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="appendItem">
        <parameter name="label"/>
        <parameter name="value"/>
        <parameter name="description"/>
        <body>
        <![CDATA[
          const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var popup = this.menupopup ||
                      this.appendChild(document.createElementNS(XULNS, "menupopup"));
          var item = document.createElementNS(XULNS, "menuitem");
          item.setAttribute("label", label);
          item.setAttribute("value", value);
          if (description)
            item.setAttribute("description", description);

          popup.appendChild(item);
          return item;
        ]]>
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="index"/>
        <parameter name="label"/>
        <parameter name="value"/>
        <parameter name="description"/>
        <body>
        <![CDATA[
          const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var popup = this.menupopup ||
                      this.appendChild(document.createElementNS(XULNS, "menupopup"));
          var item = document.createElementNS(XULNS, "menuitem");
          item.setAttribute("label", label);
          item.setAttribute("value", value);
          if (description)
            item.setAttribute("description", description);

          if (index >= 0 && index < popup.childNodes.length)
            popup.insertBefore(item, popup.childNodes[index]);
          else
            popup.appendChild(item);
          return item;
        ]]>
        </body>
      </method>

      <method name="removeItemAt">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var popup = this.menupopup;
          if (popup && 0 <= index && index < popup.childNodes.length) {
            var remove = popup.childNodes[index];
            popup.removeChild(remove);
            return remove;
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="removeAllItems">
        <body>
        <![CDATA[
          this.selectedItem = null;
          var popup = this.menupopup;
          if (popup)
            this.removeChild(popup);
        ]]>
        </body>
      </method>

      <destructor>
        <![CDATA[
          if (this.mAttributeObserver) {
            this.mAttributeObserver.disconnect();
          }
        ]]>
      </destructor>
    </implementation>
  </binding>

  <binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content sizetopopup="pref">
      <xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context,disabled,readonly,focused" flex="1">
        <html:input class="menulist-editable-input" anonid="input" allowevents="true"
                    xbl:inherits="value=label,value,disabled,tabindex,readonly,placeholder"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu"
                      xbl:inherits="open,disabled,parentfocused=focused"/>
      <children includes="menupopup"/>
    </content>

    <implementation>
      <method name="_selectInputFieldValueInList">
        <body>
        <![CDATA[
          if (this.hasAttribute("disableautoselect"))
            return;

          // Find and select the menuitem that matches inputField's "value"
          var arr = null;
          var popup = this.menupopup;

          if (popup)
            arr = popup.getElementsByAttribute("label", this.inputField.value);

          this.setSelectionInternal(arr ? arr.item(0) : null);
        ]]>
        </body>
      </method>

      <method name="setSelectionInternal">
        <parameter name="val"/>
        <body>
          <![CDATA[
            // This is called internally to set selected item
            //  without triggering infinite loop
            //  when using selectedItem's setter
            if (this.mSelectedInternal == val)
              return val;

            if (this.mSelectedInternal)
              this.mSelectedInternal.removeAttribute("selected");

            this.mSelectedInternal = val;

            if (val)
              val.setAttribute("selected", "true");

            // Do NOT change the "value", which is owned by inputField
            return val;
          ]]>
        </body>
      </method>

      <property name="inputField" readonly="true">
        <getter><![CDATA[
          if (!this.mInputField)
            this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
          return this.mInputField;
        ]]></getter>
      </property>

      <property name="label"      onset="this.inputField.value = val; return val;"
                                  onget="return this.inputField.value;"/>

      <property name="value"      onget="return this.inputField.value;">
        <setter>
        <![CDATA[
          // Override menulist's value setter to refer to the inputField's value
          // (Allows using "menulist.value" instead of "menulist.inputField.value")
          this.inputField.value = val;
          this.setAttribute("value", val);
          this.setAttribute("label", val);
          this._selectInputFieldValueInList();
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
          <![CDATA[
            // Make sure internally-selected item
            //  is in sync with inputField.value
            this._selectInputFieldValueInList();
            return this.mSelectedInternal;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            var oldval = this.mSelectedInternal;
            if (oldval == val)
              return val;

            if (val && !this.contains(val))
              return val;

            // This doesn't touch inputField.value or "value" and "label" attributes
            this.setSelectionInternal(val);
            if (val) {
              // Editable menulist uses "label" as its "value"
              var label = val.getAttribute("label");
              this.inputField.value = label;
              this.setAttribute("value", label);
              this.setAttribute("label", label);
            } else {
              this.inputField.value = "";
              this.removeAttribute("value");
              this.removeAttribute("label");
            }

            var event = document.createEvent("Events");
            event.initEvent("select", true, true);
            this.dispatchEvent(event);

            event = document.createEvent("Events");
            event.initEvent("ValueChange", true, true);
            this.dispatchEvent(event);

            return val;
          ]]>
        </setter>
      </property>
      <property name="disableautoselect"
                onset="if (val) this.setAttribute('disableautoselect','true');
                       else this.removeAttribute('disableautoselect'); return val;"
                onget="return this.hasAttribute('disableautoselect');"/>

      <property name="editor" readonly="true">
        <getter><![CDATA[
          const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
          return this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
        ]]></getter>
      </property>

      <property name="readOnly"   onset="this.inputField.readOnly = val;
                                         if (val) this.setAttribute('readonly', 'true');
                                         else this.removeAttribute('readonly'); return val;"
                                  onget="return this.inputField.readOnly;"/>

      <method name="select">
        <body>
          this.inputField.select();
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        <![CDATA[
          this.setAttribute("focused", "true");
        ]]>
      </handler>

      <handler event="blur" phase="capturing">
        <![CDATA[
          this.removeAttribute("focused");
        ]]>
      </handler>

      <handler event="popupshowing">
        <![CDATA[
          // editable menulists elements aren't in the focus order,
          // so when the popup opens we need to force the focus to the inputField
          if (event.target.parentNode == this) {
            if (document.commandDispatcher.focusedElement != this.inputField)
              this.inputField.focus();

            this.menuBoxObject.activeChild = null;
            if (this.selectedItem)
              // Not ready for auto-setting the active child in hierarchies yet.
              // For now, only do this when the outermost menupopup opens.
              this.menuBoxObject.activeChild = this.mSelectedInternal;
          }
        ]]>
      </handler>

      <handler event="keypress">
        <![CDATA[
          // open popup if key is up arrow, down arrow, or F4
          if (!event.ctrlKey && !event.shiftKey) {
            if (event.keyCode == KeyEvent.DOM_VK_UP ||
                event.keyCode == KeyEvent.DOM_VK_DOWN ||
                (event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
              event.preventDefault();
              this.open = true;
            }
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="menulist-description" display="xul:menu"
           extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content sizetopopup="pref">
      <xul:hbox class="menulist-label-box" flex="1">
        <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
        <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
        <xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
      <children includes="menupopup"/>
    </content>
  </binding>

  <binding id="menulist-popuponly" display="xul:menu"
           extends="chrome://global/content/bindings/menulist.xml#menulist">
    <content sizetopopup="pref">
      <children includes="menupopup"/>
    </content>
  </binding>
</bindings>
PK
!<u[u[7chrome/toolkit/content/global/bindings/notification.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings [
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>

<bindings id="notificationBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="notificationbox">
    <content>
      <xul:stack xbl:inherits="hidden=notificationshidden"
                 class="notificationbox-stack">
        <xul:spacer/>
        <children includes="notification"/>
      </xul:stack>
      <children/>
    </content>

    <implementation>
      <field name="PRIORITY_INFO_LOW" readonly="true">1</field>
      <field name="PRIORITY_INFO_MEDIUM" readonly="true">2</field>
      <field name="PRIORITY_INFO_HIGH" readonly="true">3</field>
      <field name="PRIORITY_WARNING_LOW" readonly="true">4</field>
      <field name="PRIORITY_WARNING_MEDIUM" readonly="true">5</field>
      <field name="PRIORITY_WARNING_HIGH" readonly="true">6</field>
      <field name="PRIORITY_CRITICAL_LOW" readonly="true">7</field>
      <field name="PRIORITY_CRITICAL_MEDIUM" readonly="true">8</field>
      <field name="PRIORITY_CRITICAL_HIGH" readonly="true">9</field>
      <field name="PRIORITY_CRITICAL_BLOCK" readonly="true">10</field>

      <field name="currentNotification">null</field>

      <field name="_closedNotification">null</field>
      <field name="_blockingCanvas">null</field>
      <field name="_animating">false</field>

      <property name="_allowAnimation">
        <getter>
          <![CDATA[
            var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                                  .getService(Components.interfaces.nsIPrefBranch);
            return prefs.getBoolPref("toolkit.cosmeticAnimations.enabled");
          ]]>
        </getter>
      </property>

      <property name="notificationsHidden"
                onget="return this.getAttribute('notificationshidden') == 'true';">
        <setter>
          <![CDATA[
            if (val)
              this.setAttribute("notificationshidden", true);
            else this.removeAttribute("notificationshidden");
            return val;
          ]]>
        </setter>
      </property>

      <property name="allNotifications" readonly="true">
        <getter>
          <![CDATA[
            var closedNotification = this._closedNotification;
            var notifications = this.getElementsByTagName("notification");
            return Array.filter(notifications, n => n != closedNotification);
          ]]>
        </getter>
      </property>

      <method name="getNotificationWithValue">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (aValue == notifications[n].getAttribute("value"))
                return notifications[n];
            }
            return null;
          ]]>
        </body>
      </method>

      <method name="appendNotification">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <parameter name="aImage"/>
        <parameter name="aPriority"/>
        <parameter name="aButtons"/>
        <parameter name="aEventCallback"/>
        <body>
          <![CDATA[
            if (aPriority < this.PRIORITY_INFO_LOW ||
                aPriority > this.PRIORITY_CRITICAL_BLOCK)
              throw "Invalid notification priority " + aPriority;

            // check for where the notification should be inserted according to
            // priority. If two are equal, the existing one appears on top.
            var notifications = this.allNotifications;
            var insertPos = null;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (notifications[n].priority < aPriority)
                break;
              insertPos = notifications[n];
            }

            const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var newitem = document.createElementNS(XULNS, "notification");
            // Can't use instanceof in case this was created from a different document:
            let labelIsDocFragment = aLabel && typeof aLabel == "object" && aLabel.nodeType &&
                                     aLabel.nodeType == aLabel.DOCUMENT_FRAGMENT_NODE;
            if (!labelIsDocFragment)
              newitem.setAttribute("label", aLabel);
            newitem.setAttribute("value", aValue);
            if (aImage)
              newitem.setAttribute("image", aImage);
            newitem.eventCallback = aEventCallback;

            if (aButtons) {
              // The notification-button-default class is added to the button
              // with isDefault set to true. If there is no such button, it is
              // added to the first button (unless that button has isDefault
              // set to false). There cannot be multiple default buttons.
              var defaultElem;

              for (var b = 0; b < aButtons.length; b++) {
                var button = aButtons[b];
                var buttonElem = document.createElementNS(XULNS, "button");
                buttonElem.setAttribute("label", button.label);
                if (typeof button.accessKey == "string")
                  buttonElem.setAttribute("accesskey", button.accessKey);
                if (typeof button.type == "string") {
                  buttonElem.setAttribute("type", button.type);
                  if ((button.type == "menu-button" || button.type == "menu") &&
                      "popup" in button) {
                    buttonElem.appendChild(button.popup);
                    delete button.popup;
                  }
                  if (typeof button.anchor == "string")
                    buttonElem.setAttribute("anchor", button.anchor);
                }
                buttonElem.classList.add("notification-button");

                if (button.isDefault ||
                    b == 0 && !("isDefault" in button))
                  defaultElem = buttonElem;

                newitem.appendChild(buttonElem);
                buttonElem.buttonInfo = button;
              }

              if (defaultElem)
                defaultElem.classList.add("notification-button-default");
            }

            newitem.setAttribute("priority", aPriority);
            if (aPriority >= this.PRIORITY_CRITICAL_LOW)
              newitem.setAttribute("type", "critical");
            else if (aPriority <= this.PRIORITY_INFO_HIGH)
              newitem.setAttribute("type", "info");
            else
              newitem.setAttribute("type", "warning");

            if (!insertPos) {
              newitem.style.position = "fixed";
              newitem.style.top = "100%";
              newitem.style.marginTop = "-15px";
              newitem.style.opacity = "0";
            }
            this.insertBefore(newitem, insertPos);
            // Can only insert the document fragment after the item has been created because
            // otherwise the XBL structure isn't there yet:
            if (labelIsDocFragment) {
              document.getAnonymousElementByAttribute(newitem, "anonid", "messageText")
                .appendChild(aLabel);
            }

            if (!insertPos)
              this._showNotification(newitem, true);

            // Fire event for accessibility APIs
            var event = document.createEvent("Events");
            event.initEvent("AlertActive", true, true);
            newitem.dispatchEvent(event);

            return newitem;
          ]]>
        </body>
      </method>

      <method name="removeNotification">
        <parameter name="aItem"/>
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            if (aItem == this.currentNotification)
              this.removeCurrentNotification(aSkipAnimation);
            else if (aItem != this._closedNotification)
              this._removeNotificationElement(aItem);
            return aItem;
          ]]>
        </body>
      </method>

      <method name="_removeNotificationElement">
        <parameter name="aChild"/>
        <body>
          <![CDATA[
            if (aChild.eventCallback)
              aChild.eventCallback("removed");
            this.removeChild(aChild);

            // make sure focus doesn't get lost (workaround for bug 570835)
            let fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
            if (!fm.getFocusedElementForWindow(window, false, {}))
              fm.moveFocus(window, this, fm.MOVEFOCUS_FORWARD, 0);
          ]]>
        </body>
      </method>

      <method name="removeCurrentNotification">
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            this._showNotification(this.currentNotification, false, aSkipAnimation);
          ]]>
        </body>
      </method>

      <method name="removeAllNotifications">
        <parameter name="aImmediate"/>
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              if (aImmediate)
                this._removeNotificationElement(notifications[n]);
              else
                this.removeNotification(notifications[n]);
            }
            this.currentNotification = null;

            // Clean up any currently-animating notification; this is necessary
            // if a notification was just opened and is still animating, but we
            // want to close it *without* animating.  This can even happen if
            // the user toggled `toolkit.cosmeticAnimations.enabled` to false
            // and called this method immediately after an animated notification
            // displayed (although this case isn't very likely).
            if (aImmediate || !this._allowAnimation)
              this._finishAnimation();
          ]]>
        </body>
      </method>

      <method name="removeTransientNotifications">
        <body>
          <![CDATA[
            var notifications = this.allNotifications;
            for (var n = notifications.length - 1; n >= 0; n--) {
              var notification = notifications[n];
              if (notification.persistence)
                notification.persistence--;
              else if (Date.now() > notification.timeout)
                this.removeNotification(notification);
            }
          ]]>
        </body>
      </method>

      <method name="_showNotification">
        <parameter name="aNotification"/>
        <parameter name="aSlideIn"/>
        <parameter name="aSkipAnimation"/>
        <body>
          <![CDATA[
            this._finishAnimation();

            var height = aNotification.boxObject.height;
            var skipAnimation = aSkipAnimation || height == 0 ||
                                !this._allowAnimation;
            aNotification.classList.toggle("animated", !skipAnimation);

            if (aSlideIn) {
              this.currentNotification = aNotification;
              aNotification.style.removeProperty("position");
              aNotification.style.removeProperty("top");
              aNotification.style.removeProperty("margin-top");
              aNotification.style.removeProperty("opacity");

              if (skipAnimation) {
                this._setBlockingState(this.currentNotification);
                return;
              }
            } else {
              this._closedNotification = aNotification;
              var notifications = this.allNotifications;
              var idx = notifications.length - 1;
              this.currentNotification = (idx >= 0) ? notifications[idx] : null;

              if (skipAnimation) {
                this._removeNotificationElement(this._closedNotification);
                this._closedNotification = null;
                this._setBlockingState(this.currentNotification);
                return;
              }

              aNotification.style.marginTop = -height + "px";
              aNotification.style.opacity = 0;
            }

            this._animating = true;
          ]]>
        </body>
      </method>

      <method name="_finishAnimation">
        <body><![CDATA[
          if (this._animating) {
            this._animating = false;
            if (this._closedNotification) {
              this._removeNotificationElement(this._closedNotification);
              this._closedNotification = null;
            }
            this._setBlockingState(this.currentNotification);
          }
        ]]></body>
      </method>

      <method name="_setBlockingState">
        <parameter name="aNotification"/>
        <body>
          <![CDATA[
            var isblock = aNotification &&
                          aNotification.priority == this.PRIORITY_CRITICAL_BLOCK;
            var canvas = this._blockingCanvas;
            if (isblock) {
              if (!canvas)
                canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
              const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
              let content = this.firstChild;
              if (!content ||
                   content.namespaceURI != XULNS ||
                   content.localName != "browser")
                return;

              var width = content.boxObject.width;
              var height = content.boxObject.height;
              content.collapsed = true;

              canvas.setAttribute("width", width);
              canvas.setAttribute("height", height);
              canvas.setAttribute("flex", "1");

              this.appendChild(canvas);
              this._blockingCanvas = canvas;

              var bgcolor = "white";
              try {
                var prefService = Components.classes["@mozilla.org/preferences-service;1"].
                                    getService(Components.interfaces.nsIPrefBranch);
                bgcolor = prefService.getCharPref("browser.display.background_color");

                var win = content.contentWindow;
                var context = canvas.getContext("2d");
                context.globalAlpha = 0.5;
                context.drawWindow(win, win.scrollX, win.scrollY,
                                   width, height, bgcolor);
              } catch (ex) { }
            } else if (canvas) {
              canvas.remove();
              this._blockingCanvas = null;
              let content = this.firstChild;
              if (content)
                content.collapsed = false;
            }
          ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="transitionend"><![CDATA[
        if (event.target.localName == "notification" &&
            event.propertyName == "margin-top")
          this._finishAnimation();
      ]]></handler>
    </handlers>

  </binding>

  <binding id="notification" role="xul:alert">
    <content>
      <xul:hbox class="notification-inner" flex="1" xbl:inherits="type">
        <xul:hbox anonid="details" align="center" flex="1"
                  oncommand="this.parentNode.parentNode._doButtonCommand(event);">
          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
          <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
          <xul:spacer flex="1"/>
          <children/>
        </xul:hbox>
        <xul:toolbarbutton ondblclick="event.stopPropagation();"
                           class="messageCloseButton close-icon tabbable"
                           xbl:inherits="hidden=hideclose"
                           tooltiptext="&closeNotification.tooltip;"
                           oncommand="document.getBindingParent(this).dismiss();"/>
      </xul:hbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <property name="label" onset="this.setAttribute('label', val); return val;"
                             onget="return this.getAttribute('label');"/>
      <property name="value" onset="this.setAttribute('value', val); return val;"
                             onget="return this.getAttribute('value');"/>
      <property name="image" onset="this.setAttribute('image', val); return val;"
                             onget="return this.getAttribute('image');"/>
      <property name="type" onset="this.setAttribute('type', val); return val;"
                            onget="return this.getAttribute('type');"/>
      <property name="priority" onget="return parseInt(this.getAttribute('priority')) || 0;"
                                onset="this.setAttribute('priority', val); return val;"/>
      <property name="persistence" onget="return parseInt(this.getAttribute('persistence')) || 0;"
                                   onset="this.setAttribute('persistence', val); return val;"/>
      <field name="timeout">0</field>

      <property name="control" readonly="true">
        <getter>
          <![CDATA[
            var parent = this.parentNode;
            while (parent) {
              if (parent.localName == "notificationbox")
                return parent;
              parent = parent.parentNode;
            }
            return null;
          ]]>
        </getter>
      </property>

      <!-- This method should only be called when the user has
           manually closed the notification. If you want to
           programmatically close the notification, you should
           call close() instead. -->
      <method name="dismiss">
        <body>
          <![CDATA[
            if (this.eventCallback) {
              this.eventCallback("dismissed");
            }
            this.close();
          ]]>
        </body>
      </method>

      <method name="close">
        <body>
          <![CDATA[
            var control = this.control;
            if (control)
              control.removeNotification(this);
            else
              this.hidden = true;
          ]]>
        </body>
      </method>

      <method name="_doButtonCommand">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            if (!("buttonInfo" in aEvent.target))
              return;

            var button = aEvent.target.buttonInfo;
            if (button.popup) {
              document.getElementById(button.popup).
                openPopup(aEvent.originalTarget, "after_start", 0, 0, false, false, aEvent);
              aEvent.stopPropagation();
            } else {
              var callback = button.callback;
              if (callback) {
                var result = callback(this, button, aEvent.target);
                if (!result)
                  this.close();
                aEvent.stopPropagation();
              }
            }
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="popup-notification">
    <content orient="vertical">
      <xul:hbox align="start" class="popup-notification-body-container">
        <xul:image class="popup-notification-icon"
                   xbl:inherits="popupid,src=icon,class=iconclass"/>
        <xul:vbox flex="1" pack="start"
                  class="popup-notification-body" xbl:inherits="popupid">
          <xul:hbox align="start">
            <xul:vbox flex="1">
              <xul:label class="popup-notification-origin header"
                         xbl:inherits="value=origin,tooltiptext=origin"
                         crop="center"/>
              <xul:description class="popup-notification-description"
                               xbl:inherits="xbl:text=label,popupid"/>
            </xul:vbox>
            <xul:toolbarbutton anonid="closebutton"
                               class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                               xbl:inherits="oncommand=closebuttoncommand,hidden=closebuttonhidden"
                               tooltiptext="&closeNotification.tooltip;"/>
          </xul:hbox>
          <children includes="popupnotificationcontent"/>
          <xul:label class="text-link popup-notification-learnmore-link"
                     xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
          <xul:checkbox anonid="checkbox"
                        xbl:inherits="hidden=checkboxhidden,checked=checkboxchecked,label=checkboxlabel,oncommand=checkboxcommand" />
          <xul:description class="popup-notification-warning" xbl:inherits="hidden=warninghidden,xbl:text=warninglabel"/>
        </xul:vbox>
      </xul:hbox>
      <xul:hbox class="popup-notification-button-container">
        <children includes="button"/>
        <xul:button anonid="secondarybutton"
                    class="popup-notification-button"
                    xbl:inherits="oncommand=secondarybuttoncommand,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,hidden=secondarybuttonhidden"/>
        <xul:toolbarseparator xbl:inherits="hidden=dropmarkerhidden"/>
        <xul:button anonid="menubutton"
                    type="menu"
                    class="popup-notification-button popup-notification-dropmarker"
                    xbl:inherits="onpopupshown=dropmarkerpopupshown,hidden=dropmarkerhidden">
          <xul:menupopup anonid="menupopup"
                         position="after_end"
                         xbl:inherits="oncommand=menucommand">
            <children/>
          </xul:menupopup>
        </xul:button>
        <xul:button anonid="button"
                    class="popup-notification-button"
                    default="true"
                    label="&defaultButton.label;"
                    accesskey="&defaultButton.accesskey;"
                    xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,highlight=buttonhighlight,disabled=mainactiondisabled"/>
      </xul:hbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <field name="checkbox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "checkbox");
      </field>
      <field name="closebutton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "closebutton");
      </field>
      <field name="button" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "button");
      </field>
      <field name="secondaryButton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton");
      </field>
      <field name="menubutton" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "menubutton");
      </field>
      <field name="menupopup" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
      </field>
    </implementation>
  </binding>
</bindings>
PK
!<0v##4chrome/toolkit/content/global/bindings/numberbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="numberboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="numberbox"
           extends="chrome://global/content/bindings/textbox.xml#textbox">

    <resources>
      <stylesheet src="chrome://global/skin/numberbox.css"/>
    </resources>

    <content>
      <xul:hbox class="textbox-input-box numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
        <html:input class="numberbox-input textbox-input" anonid="input"
                    xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled,hidden=hidespinbuttons"/>
    </content>

    <implementation>
      <field name="_valueEntered">false</field>
      <field name="_spinButtons">null</field>
      <field name="_value">0</field>
      <field name="decimalSymbol">"."</field>

      <property name="spinButtons" readonly="true">
        <getter>
          <![CDATA[
            if (!this._spinButtons)
              this._spinButtons = document.getAnonymousElementByAttribute(this, "anonid", "buttons");
            return this._spinButtons;
          ]]>
        </getter>
      </property>

      <property name="value" onget="return '' + this.valueNumber"
                             onset="return this.valueNumber = val;"/>

      <property name="valueNumber">
        <getter>
          if (this._valueEntered) {
            var newval = this.inputField.value;
            newval = newval.replace(this.decimalSymbol, ".");
            this._validateValue(newval, false);
          }
          return this._value;
        </getter>
        <setter>
          this._validateValue(val, false);
          return val;
        </setter>
      </property>

      <property name="wrapAround">
        <getter>
        <![CDATA[
          return (this.getAttribute("wraparound") == "true")
        ]]>
        </getter>
        <setter>
        <![CDATA[
          if (val)
            this.setAttribute("wraparound", "true");
          else
            this.removeAttribute("wraparound");
          this._enableDisableButtons();
          return val;
        ]]>
        </setter>
      </property>

      <property name="min">
        <getter>
          var min = this.getAttribute("min");
          return min ? Number(min) : 0;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val == "number") {
            this.setAttribute("min", val);
            if (this.valueNumber < val)
              this._validateValue(val, false);
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="max">
        <getter>
          var max = this.getAttribute("max");
          return max ? Number(max) : Infinity;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val != "number")
            return val;
          var min = this.min;
          if (val < min)
            val = min;
          this.setAttribute("max", val);
          if (this.valueNumber > val)
            this._validateValue(val, false);
          return val;
        ]]>
        </setter>
      </property>

      <property name="decimalPlaces">
        <getter>
          var places = this.getAttribute("decimalplaces");
          return places ? Number(places) : 0;
        </getter>
        <setter>
          if (typeof val == "number") {
            this.setAttribute("decimalplaces", val);
            this._validateValue(this.valueNumber, false);
          }
          return val;
        </setter>
      </property>

      <property name="increment">
        <getter>
          var increment = this.getAttribute("increment");
          return increment ? Number(increment) : 1;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val == "number")
            this.setAttribute("increment", val);
          return val;
        ]]>
        </setter>
      </property>

      <method name="decrease">
        <body>
          return this._validateValue(this.valueNumber - this.increment, true);
        </body>
      </method>

      <method name="increase">
        <body>
          return this._validateValue(this.valueNumber + this.increment, true);
        </body>
      </method>

      <method name="_modifyUp">
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;
            var oldval = this.valueNumber;
            var newval = this.increase();
            this.inputField.select();
            if (oldval != newval)
              this._fireChange();
          ]]>
        </body>
      </method>
      <method name="_modifyDown">
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;
            var oldval = this.valueNumber;
            var newval = this.decrease();
            this.inputField.select();
            if (oldval != newval)
              this._fireChange();
          ]]>
        </body>
      </method>

      <method name="_enableDisableButtons">
        <body>
          <![CDATA[
            var buttons = this.spinButtons;
            if (this.wrapAround) {
              buttons.decreaseDisabled = buttons.increaseDisabled = false;
            } else if (this.disabled || this.readOnly) {
              buttons.decreaseDisabled = buttons.increaseDisabled = true;
            } else {
              buttons.decreaseDisabled = (this.valueNumber <= this.min);
              buttons.increaseDisabled = (this.valueNumber >= this.max);
            }
          ]]>
        </body>
      </method>

      <method name="_validateValue">
        <parameter name="aValue"/>
        <parameter name="aIsIncDec"/>
        <body>
          <![CDATA[
            aValue = Number(aValue) || 0;

            var min = this.min;
            var max = this.max;
            var wrapAround = this.wrapAround &&
                             min != -Infinity && max != Infinity;
            if (aValue < min)
              aValue = (aIsIncDec && wrapAround ? max : min);
            else if (aValue > max)
              aValue = (aIsIncDec && wrapAround ? min : max);

            var places = this.decimalPlaces;
            aValue = (places == Infinity) ? "" + aValue : aValue.toFixed(places);

            this._valueEntered = false;
            this._value = Number(aValue);
            this.inputField.value = aValue.replace(/\./, this.decimalSymbol);

            if (!wrapAround)
              this._enableDisableButtons();

            return aValue;
          ]]>
        </body>
      </method>

      <method name="_fireChange">
        <body>
          var evt = document.createEvent("Events");
          evt.initEvent("change", true, true);
          this.dispatchEvent(evt);
        </body>
      </method>

      <constructor><![CDATA[
        if (this.max < this.min)
          this.max = this.min;

        var dsymbol = (Number(5.4)).toLocaleString().match(/\D/);
        if (dsymbol != null)
          this.decimalSymbol = dsymbol[0];

        var value = this.inputField.value || 0;
        this._validateValue(value, false);
      ]]></constructor>

    </implementation>

    <handlers>
      <handler event="input" phase="capturing">
        this._valueEntered = true;
      </handler>

      <handler event="keypress">
        <![CDATA[
          if (!event.ctrlKey && !event.metaKey && !event.altKey && event.charCode) {
            if (event.charCode == this.decimalSymbol.charCodeAt(0) &&
                this.decimalPlaces &&
                String(this.inputField.value).indexOf(this.decimalSymbol) == -1)
              return;

            if (event.charCode == 45 && this.min < 0)
              return;

            if (event.charCode < 48 || event.charCode > 57)
              event.preventDefault();
          }
        ]]>
      </handler>

      <handler event="keypress" keycode="VK_UP">
        this._modifyUp();
      </handler>

      <handler event="keypress" keycode="VK_DOWN">
        this._modifyDown();
      </handler>

      <handler event="up" preventdefault="true">
        this._modifyUp();
      </handler>

      <handler event="down" preventdefault="true">
        this._modifyDown();
      </handler>

      <handler event="change">
        if (event.originalTarget == this.inputField) {
          var newval = this.inputField.value;
          newval = newval.replace(this.decimalSymbol, ".");
          this._validateValue(newval, false);
        }
      </handler>
    </handlers>

  </binding>

</bindings>
PK
!<5odod0chrome/toolkit/content/global/bindings/popup.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This files relies on these specific Chrome/XBL globals -->
<!-- globals PopupBoxObject -->

<bindings id="popupBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="popup-base">
    <resources>
      <stylesheet src="chrome://global/skin/popup.css"/>
    </resources>

    <implementation implements="nsIDOMXULPopupElement">
      <property name="label" onget="return this.getAttribute('label');"
                             onset="this.setAttribute('label', val); return val;"/>
      <property name="position" onget="return this.getAttribute('position');"
                                onset="this.setAttribute('position', val); return val;"/>
      <property name="popupBoxObject">
        <getter>
          return this.boxObject;
        </getter>
      </property>

      <property name="state" readonly="true"
                onget="return this.popupBoxObject.popupState"/>

      <property name="triggerNode" readonly="true"
                onget="return this.popupBoxObject.triggerNode"/>

      <property name="anchorNode" readonly="true"
                onget="return this.popupBoxObject.anchorNode"/>

      <method name="openPopup">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aAttributesOverride"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          // Allow for passing an options object as the second argument.
          if (arguments.length == 2 &&
              arguments[1] != null &&
              typeof arguments[1] == "object") {
            let params = arguments[1];
            aPosition = params.position;
            aX = params.x;
            aY = params.y;
            aIsContextMenu = params.isContextMenu;
            aAttributesOverride = params.attributesOverride;
            aTriggerEvent = params.triggerEvent;
          }

          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
                                 aIsContextMenu, aAttributesOverride, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="openPopupAtScreen">
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="openPopupAtScreenRect">
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <parameter name="aIsContextMenu"/>
        <parameter name="aAttributesOverride"/>
        <parameter name="aTriggerEvent"/>
        <body>
        <![CDATA[
          try {
            var popupBox = this.popupBoxObject;
            if (popupBox)
              popupBox.openPopupAtScreenRect(aPosition, aX, aY, aWidth, aHeight,
                                             aIsContextMenu, aAttributesOverride, aTriggerEvent);
          } catch (e) {}
        ]]>
        </body>
      </method>

      <method name="showPopup">
        <parameter name="element"/>
        <parameter name="xpos"/>
        <parameter name="ypos"/>
        <parameter name="popuptype"/>
        <parameter name="anchoralignment"/>
        <parameter name="popupalignment"/>
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.popupBoxObject;
          } catch (e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch (e) {}
          if (menuBox instanceof MenuBoxObject)
            menuBox.openMenu(true);
          else if (popupBox)
            popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
        ]]>
        </body>
      </method>

      <method name="hidePopup">
        <parameter name="cancel"/>
        <body>
        <![CDATA[
          var popupBox = null;
          var menuBox = null;
          try {
            popupBox = this.popupBoxObject;
          } catch (e) {}
          try {
            menuBox = this.parentNode.boxObject;
          } catch (e) {}
          if (menuBox instanceof MenuBoxObject)
            menuBox.openMenu(false);
          else if (popupBox instanceof PopupBoxObject)
            popupBox.hidePopup(cancel);
        ]]>
        </body>
      </method>

      <property name="autoPosition">
        <getter>
        <![CDATA[
          return this.popupBoxObject.autoPosition;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          return this.popupBoxObject.autoPosition = val;
        ]]>
        </setter>
      </property>

      <property name="alignmentPosition" readonly="true">
        <getter>
        <![CDATA[
          return this.popupBoxObject.alignmentPosition;
        ]]>
        </getter>
      </property>

      <property name="alignmentOffset" readonly="true">
        <getter>
        <![CDATA[
          return this.popupBoxObject.alignmentOffset;
        ]]>
        </getter>
      </property>

      <method name="enableKeyboardNavigator">
        <parameter name="aEnableKeyboardNavigator"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
        ]]>
        </body>
      </method>

      <method name="enableRollup">
        <parameter name="aEnableRollup"/>
        <body>
        <![CDATA[
          this.popupBoxObject.enableRollup(aEnableRollup);
        ]]>
        </body>
      </method>

      <method name="sizeTo">
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <body>
        <![CDATA[
          this.popupBoxObject.sizeTo(aWidth, aHeight);
        ]]>
        </body>
      </method>

      <method name="moveTo">
        <parameter name="aLeft"/>
        <parameter name="aTop"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveTo(aLeft, aTop);
        ]]>
        </body>
      </method>

      <method name="moveToAnchor">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aAttributesOverride"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
        ]]>
        </body>
      </method>

      <method name="getOuterScreenRect">
        <body>
        <![CDATA[
          return this.popupBoxObject.getOuterScreenRect();
        ]]>
        </body>
      </method>

      <method name="setConstraintRect">
        <parameter name="aRect"/>
        <body>
        <![CDATA[
          this.popupBoxObject.setConstraintRect(aRect);
        ]]>
        </body>
      </method>
    </implementation>

  </binding>

  <binding id="popup" role="xul:menupopup"
           extends="chrome://global/content/bindings/popup.xml#popup-base">

    <content>
      <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
                          smoothscroll="false">
        <children/>
      </xul:arrowscrollbox>
    </content>

    <implementation>
      <field name="scrollBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "class", "popup-internal-box");
      </field>
    </implementation>

    <handlers>
      <handler event="popupshowing" phase="target">
        <![CDATA[
          var array = [];
          var width = 0;
          for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
            if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
              var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
              if (accel && accel.boxObject) {
                array.push(accel);
                if (accel.boxObject.width > width)
                  width = accel.boxObject.width;
              }
            }
          }
          for (var i = 0; i < array.length; i++)
            array[i].width = width;
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="panel" role="xul:panel"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    <implementation implements="nsIDOMXULPopupElement">
      <field name="_prevFocus">0</field>
      <field name="_dragBindingAlive">true</field>
      <constructor>
      <![CDATA[
        if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) {
          this._draggableStarted = true;
          try {
            let tmp = {};
            Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
            let draghandle = new tmp.WindowDraggingElement(this);
            draghandle.mouseDownCheck = function() {
              return this._dragBindingAlive;
            }
          } catch (e) {}
        }
      ]]>
      </constructor>
    </implementation>

    <handlers>
      <handler event="popupshowing"><![CDATA[
        // Capture the previous focus before has a chance to get set inside the panel
        try {
          this._prevFocus = Components.utils
                            .getWeakReference(document.commandDispatcher.focusedElement);
          if (this._prevFocus.get())
            return;
        } catch (ex) { }

        this._prevFocus = Components.utils.getWeakReference(document.activeElement);
      ]]></handler>
      <handler event="popupshown"><![CDATA[
        // Fire event for accessibility APIs
        var alertEvent = document.createEvent("Events");
        alertEvent.initEvent("AlertActive", true, true);
        this.dispatchEvent(alertEvent);
       ]]></handler>
      <handler event="popuphiding"><![CDATA[
        try {
          this._currentFocus = document.commandDispatcher.focusedElement;
        } catch (e) {
          this._currentFocus = document.activeElement;
        }
      ]]></handler>
      <handler event="popuphidden"><![CDATA[
        function doFocus() {
          // Focus was set on an element inside this panel,
          // so we need to move it back to where it was previously
          try {
            let fm = Components.classes["@mozilla.org/focus-manager;1"]
                               .getService(Components.interfaces.nsIFocusManager);
            fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
          } catch (e) {
            prevFocus.focus();
          }
        }
        var currentFocus = this._currentFocus;
        var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
        this._currentFocus = null;
        this._prevFocus = null;

        // Avoid changing focus if focus changed while we hide the popup
        // (This can happen e.g. if the popup is hiding as a result of a
        // click/keypress that focused something)
        let nowFocus;
        try {
          nowFocus = document.commandDispatcher.focusedElement;
        } catch (e) {
          nowFocus = document.activeElement;
        }
        if (nowFocus && nowFocus != currentFocus)
          return;

        if (prevFocus && this.getAttribute("norestorefocus") != "true") {
          // Try to restore focus
          try {
            if (document.commandDispatcher.focusedWindow != window)
              return; // Focus has already been set to a window outside of this panel
          } catch (ex) {}

          if (!currentFocus) {
            doFocus();
            return;
          }
          while (currentFocus) {
            if (currentFocus == this) {
              doFocus();
              return;
            }
            currentFocus = currentFocus.parentNode;
          }
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
    <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
               xbl:inherits="side,panelopen">
        <xul:box anonid="arrowbox" class="panel-arrowbox">
          <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
        </xul:box>
        <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
          <children/>
        </xul:box>
      </xul:vbox>
    </content>
    <implementation>
      <field name="_fadeTimer">null</field>
      <method name="sizeTo">
        <parameter name="aWidth"/>
        <parameter name="aHeight"/>
        <body>
        <![CDATA[
          this.popupBoxObject.sizeTo(aWidth, aHeight);
          if (this.state == "open") {
            this.adjustArrowPosition();
          }
        ]]>
        </body>
      </method>
      <method name="moveToAnchor">
        <parameter name="aAnchorElement"/>
        <parameter name="aPosition"/>
        <parameter name="aX"/>
        <parameter name="aY"/>
        <parameter name="aAttributesOverride"/>
        <body>
        <![CDATA[
          this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
        ]]>
        </body>
      </method>
      <method name="adjustArrowPosition">
        <body>
        <![CDATA[
        var anchor = this.anchorNode;
        if (!anchor) {
          return;
        }

        var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
        var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");

        var position = this.alignmentPosition;
        var offset = this.alignmentOffset;

        this.setAttribute("arrowposition", position);

        if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
          container.orient = "horizontal";
          arrowbox.orient = "vertical";
          if (position.indexOf("_after") > 0) {
            arrowbox.pack = "end";
          } else {
            arrowbox.pack = "start";
          }
          arrowbox.style.transform = "translate(0, " + -offset + "px)";

          // The assigned side stays the same regardless of direction.
          var isRTL = (window.getComputedStyle(this).direction == "rtl");

          if (position.indexOf("start_") == 0) {
            container.dir = "reverse";
            this.setAttribute("side", isRTL ? "left" : "right");
          } else {
            container.dir = "";
            this.setAttribute("side", isRTL ? "right" : "left");
          }
        } else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
          container.orient = "";
          arrowbox.orient = "";
          if (position.indexOf("_end") > 0) {
            arrowbox.pack = "end";
          } else {
            arrowbox.pack = "start";
          }
          arrowbox.style.transform = "translate(" + -offset + "px, 0)";

          if (position.indexOf("before_") == 0) {
            container.dir = "reverse";
            this.setAttribute("side", "bottom");
          } else {
            container.dir = "";
            this.setAttribute("side", "top");
          }
        }
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="popupshowing" phase="target">
      <![CDATA[
        var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
        arrow.hidden = this.anchorNode == null;
        document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
                .style.removeProperty("transform");

        this.adjustArrowPosition();

        if (this.getAttribute("animate") != "false") {
          this.setAttribute("animate", "open");
        }

        // set fading
        var fade = this.getAttribute("fade");
        var fadeDelay = 0;
        if (fade == "fast") {
          fadeDelay = 1;
        } else if (fade == "slow") {
          fadeDelay = 4000;
        } else {
          return;
        }

        this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
      ]]>
      </handler>
      <handler event="popuphiding" phase="target">
        let animate = (this.getAttribute("animate") != "false");

        if (this._fadeTimer) {
          clearTimeout(this._fadeTimer);
          if (animate) {
            this.setAttribute("animate", "fade");
          }
        } else if (animate) {
          this.setAttribute("animate", "cancel");
        }
      </handler>
      <handler event="popupshown" phase="target">
        this.setAttribute("panelopen", "true");
      </handler>
      <handler event="popuphidden" phase="target">
        this.removeAttribute("panelopen");
        if (this.getAttribute("animate") != "false") {
          this.removeAttribute("animate");
        }
      </handler>
      <handler event="popuppositioned" phase="target">
        this.adjustArrowPosition();
      </handler>
    </handlers>
  </binding>

  <binding id="tooltip" role="xul:tooltip"
           extends="chrome://global/content/bindings/popup.xml#popup-base">
    <content>
      <children>
        <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
      </children>
    </content>

    <implementation>
      <field name="_mouseOutCount">0</field>
      <field name="_isMouseOver">false</field>

      <property name="label"
                onget="return this.getAttribute('label');"
                onset="this.setAttribute('label', val); return val;"/>

      <property name="page" onset="if (val) this.setAttribute('page', 'true');
                                   else this.removeAttribute('page');
                                   return val;"
                            onget="return this.getAttribute('page') == 'true';"/>
      <property name="textProvider"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._textProvider) {
            this._textProvider = Components.classes["@mozilla.org/embedcomp/default-tooltiptextprovider;1"]
                                 .getService(Components.interfaces.nsITooltipTextProvider);
          }
          return this._textProvider;
        ]]>
        </getter>
      </property>

      <!-- Given the supplied element within a page, set the tooltip's text to the text
           for that element. Returns true if text was assigned, and false if the no text
           is set, which normally would be used to cancel tooltip display.
        -->
      <method name="fillInPageTooltip">
        <parameter name="tipElement"/>
        <body>
        <![CDATA[
          let tttp = this.textProvider;
          let textObj = {}, dirObj = {};
          let shouldChangeText = tttp.getNodeText(tipElement, textObj, dirObj);
          if (shouldChangeText) {
            this.style.direction = dirObj.value;
            this.label = textObj.value;
          }
          return shouldChangeText;
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="mouseover"><![CDATA[
        var rel = event.relatedTarget;
        if (!rel)
          return;

        // find out if the node we entered from is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }

        // if the exited node is not a descendant of ours, we are entering for the first time
        if (rel != this)
          this._isMouseOver = true;
      ]]></handler>

      <handler event="mouseout"><![CDATA[
        var rel = event.relatedTarget;

        // relatedTarget is null when the titletip is first shown: a mouseout event fires
        // because the mouse is exiting the main window and entering the titletip "window".
        // relatedTarget is also null when the mouse exits the main window completely,
        // so count how many times relatedTarget was null after titletip is first shown
        // and hide popup the 2nd time
        if (!rel) {
          ++this._mouseOutCount;
          if (this._mouseOutCount > 1)
            this.hidePopup();
          return;
        }

        // find out if the node we are entering is one of our anonymous children
        while (rel) {
          if (rel == this)
            break;
          rel = rel.parentNode;
        }

        // if the entered node is not a descendant of ours, hide the tooltip
        if (rel != this && this._isMouseOver) {
          this.hidePopup();
        }
      ]]></handler>

      <handler event="popupshowing"><![CDATA[
        if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
          event.preventDefault();
        }
      ]]></handler>

      <handler event="popuphiding"><![CDATA[
        this._isMouseOver = false;
        this._mouseOutCount = 0;
      ]]></handler>
    </handlers>
  </binding>

  <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
    <content>
      <xul:scrollbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
        <children/>
      </xul:scrollbox>
    </content>
    <implementation>
      <field name="AUTOSCROLL_INTERVAL">25</field>
      <field name="NOT_DRAGGING">0</field>
      <field name="DRAG_OVER_BUTTON">-1</field>
      <field name="DRAG_OVER_POPUP">1</field>

      <field name="_draggingState">this.NOT_DRAGGING</field>
      <field name="_scrollTimer">0</field>

      <method name="enableDragScrolling">
        <!-- when overItem is true, drag started over menuitem; when false, drag
             started while the popup was opening.
          -->
        <parameter name="overItem"/>
        <body>
        <![CDATA[
          if (!this._draggingState) {
            this.setCaptureAlways();
            this._draggingState = overItem ? this.DRAG_OVER_POPUP : this.DRAG_OVER_BUTTON;
          }
        ]]>
        </body>
      </method>
      <method name="_clearScrollTimer">
        <body>
        <![CDATA[
          if (this._scrollTimer) {
            this.ownerGlobal.clearInterval(this._scrollTimer);
            this._scrollTimer = 0;
          }
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="popupshown">
        // Enable drag scrolling even when the mouse wasn't used. The mousemove
        // handler will remove it if the mouse isn't down.
        this.enableDragScrolling(false);
      </handler>

      <handler event="popuphidden">
      <![CDATA[
        this._draggingState = this.NOT_DRAGGING;
        this._clearScrollTimer();
        this.releaseCapture();
      ]]>
      </handler>

      <handler event="mousedown" button="0">
      <![CDATA[
        if (this.state == "open" &&
            (event.target.localName == "menuitem" ||
             event.target.localName == "menu" ||
             event.target.localName == "menucaption")) {
          this.enableDragScrolling(true);
        }
      ]]>
      </handler>
      <handler event="mouseup" button="0">
      <![CDATA[
        this._draggingState = this.NOT_DRAGGING;
        this._clearScrollTimer();
      ]]>
      </handler>
      <handler event="mousemove">
      <![CDATA[
        if (!this._draggingState) {
          return;
        }

        this._clearScrollTimer();

        // If the user released the mouse before the popup opens, we will
        // still be capturing, so check that the button is still pressed. If
        // not, release the capture and do nothing else. This also handles if
        // the dropdown was opened via the keyboard.
        if (!(event.buttons & 1)) {
          this._draggingState = this.NOT_DRAGGING;
          this.releaseCapture();
          return;
        }

        // If dragging outside the top or bottom edge of the popup, but within
        // the popup area horizontally, scroll the list in that direction. The
        // _draggingState flag is used to ensure that scrolling does not start
        // until the mouse has moved over the popup first, preventing scrolling
        // while over the dropdown button.
        let popupRect = this.getOuterScreenRect();
        if (event.screenX >= popupRect.left && event.screenX <= popupRect.right) {
          if (this._draggingState == this.DRAG_OVER_BUTTON) {
            if (event.screenY > popupRect.top && event.screenY < popupRect.bottom) {
              this._draggingState = this.DRAG_OVER_POPUP;
            }
          }

          if (this._draggingState == this.DRAG_OVER_POPUP &&
              (event.screenY <= popupRect.top || event.screenY >= popupRect.bottom)) {
            let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
            this.scrollBox.scrollByIndex(scrollAmount);

            let win = this.ownerGlobal;
            this._scrollTimer = win.setInterval(() => {
              this.scrollBox.scrollByIndex(scrollAmount);
            }, this.AUTOSCROLL_INTERVAL);
          }
        }
      ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
PK
!<:6chrome/toolkit/content/global/bindings/preferences.xml<?xml version="1.0"?>

<!DOCTYPE bindings [
  <!ENTITY % preferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
  %preferencesDTD;
  <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
  %globalKeysDTD;
]>

<bindings id="preferencesBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">


  <binding id="preferences">
    <implementation implements="nsIObserver">
      <method name="_constructAfterChildren">
      <body>
      <![CDATA[
      // This method will be called after the last of the child
      // <preference> elements is constructed. Its purpose is to propagate
      // the values to the associated form elements. Sometimes the code for
      // some <preference> initializers depend on other <preference> elements
      // being initialized so we wait and call updateElements on all of them
      // once the last one has been constructed. See bugs 997570 and 992185.

      var elements = this.getElementsByTagName("preference");
      for (let element of elements) {
        element.updateElements();
      }

      this._constructAfterChildrenCalled = true;
      ]]>
      </body>
      </method>
      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
        <![CDATA[
          for (var i = 0; i < this.childNodes.length; ++i) {
            var preference = this.childNodes[i];
            if (preference.name == aData) {
              preference.value = preference.valueFromPreferences;
            }
          }
        ]]>
        </body>
      </method>

      <method name="fireChangedEvent">
        <parameter name="aPreference"/>
        <body>
        <![CDATA[
          // Value changed, synthesize an event
          try {
            var event = document.createEvent("Events");
            event.initEvent("change", true, true);
            aPreference.dispatchEvent(event);
          } catch (e) {
            Components.utils.reportError(e);
          }
        ]]>
        </body>
      </method>

      <field name="service">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefService);
      </field>
      <field name="rootBranch">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranch);
      </field>
      <field name="defaultBranch">
        this.service.getDefaultBranch("");
      </field>
      <field name="rootBranchInternal">
        Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranchInternal);
      </field>
      <property name="type" readonly="true">
        <getter>
          <![CDATA[
            return document.documentElement.type || "";
          ]]>
        </getter>
      </property>
      <property name="instantApply" readonly="true">
        <getter>
          <![CDATA[
            var doc = document.documentElement;
            return this.type == "child" ? doc.instantApply
                                        : doc.instantApply || this.rootBranch.getBoolPref("browser.preferences.instantApply");
          ]]>
        </getter>
      </property>

      <!-- We want to call _constructAfterChildren after all child
           <preference> elements have been constructed. To do this, we get
           and store the node list of all child <preference> elements in the
           constructor, and maintain a count which is incremented in the
           constructor of <preference>. _constructAfterChildren is called
           when the count matches the length of the list. -->
      <field name="_constructedChildrenCount">0</field>
      <field name="_preferenceChildren">null</field>
      <!-- Some <preference> elements are added dynamically after
           _constructAfterChildren has already been called - we want to
           avoid looping over all of them again in this case so we remember
           if we already called it. -->
      <field name="_constructAfterChildrenCalled">false</field>
      <constructor>
      <![CDATA[
        this._preferenceChildren = this.getElementsByTagName("preference");
      ]]>
      </constructor>
    </implementation>
  </binding>

  <binding id="preference">
    <implementation>
      <constructor>
      <![CDATA[
        // if the element has been inserted without the name attribute set,
        // we have nothing to do here
        if (!this.name)
          return;

        this.preferences.rootBranchInternal
            .addObserver(this.name, this.preferences);
        // In non-instant apply mode, we must try and use the last saved state
        // from any previous opens of a child dialog instead of the value from
        // preferences, to pick up any edits a user may have made.

        var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                    .getService(Components.interfaces.nsIScriptSecurityManager);
        if (this.preferences.type == "child" &&
            !this.instantApply && window.opener &&
            secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
          var pdoc = window.opener.document;

          // Try to find a preference element for the same preference.
          var preference = null;
          var parentPreferences = pdoc.getElementsByTagName("preferences");
          for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
            var parentPrefs = parentPreferences[k]
                                    .getElementsByAttribute("name", this.name);
            for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
              if (parentPrefs[l].localName == "preference")
                preference = parentPrefs[l];
            }
          }

          // Don't use the value setter here, we don't want updateElements to be prematurely fired.
          this._value = preference ? preference.value : this.valueFromPreferences;
        } else {
          this._value = this.valueFromPreferences;
        }
        if (this.preferences._constructAfterChildrenCalled) {
          // This <preference> was added after _constructAfterChildren() was already called.
          // We can directly call updateElements().
          this.updateElements();
          return;
        }
        this.preferences._constructedChildrenCount++;
        if (this.preferences._constructedChildrenCount ==
            this.preferences._preferenceChildren.length) {
          // This is the last <preference>, time to updateElements() on all of them.
          this.preferences._constructAfterChildren();
        }
      ]]>
      </constructor>
      <destructor>
        this.preferences.rootBranchInternal
            .removeObserver(this.name, this.preferences);
      </destructor>
      <field name="_constructed">false</field>
      <property name="instantApply">
        <getter>
          if (this.getAttribute("instantApply") == "false")
            return false;
          return this.getAttribute("instantApply") == "true" || this.preferences.instantApply;
        </getter>
      </property>

      <property name="preferences" onget="return this.parentNode"/>
      <property name="name" onget="return this.getAttribute('name');">
        <setter>
          if (val == this.name)
            return val;

          this.preferences.rootBranchInternal
              .removeObserver(this.name, this.preferences);
          this.setAttribute("name", val);
          this.preferences.rootBranchInternal
              .addObserver(val, this.preferences);

          return val;
        </setter>
      </property>
      <property name="type" onget="return this.getAttribute('type');"
                            onset="this.setAttribute('type', val); return val;"/>
      <property name="inverted" onget="return this.getAttribute('inverted') == 'true';"
                                onset="this.setAttribute('inverted', val); return val;"/>
      <property name="readonly" onget="return this.getAttribute('readonly') == 'true';"
                                onset="this.setAttribute('readonly', val); return val;"/>

      <field name="_value">null</field>
      <method name="_setValue">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (this.value !== aValue) {
            this._value = aValue;
            if (this.instantApply)
              this.valueFromPreferences = aValue;
            this.preferences.fireChangedEvent(this);
          }
          return aValue;
        ]]>
        </body>
      </method>
      <property name="value" onget="return this._value" onset="return this._setValue(val);"/>

      <property name="locked">
        <getter>
          return this.preferences.rootBranch.prefIsLocked(this.name);
        </getter>
      </property>

      <property name="disabled">
        <getter>
          return this.getAttribute("disabled") == "true";
        </getter>
        <setter>
        <![CDATA[
          if (val)
            this.setAttribute("disabled", "true");
          else
            this.removeAttribute("disabled");

          if (!this.id)
            return val;

          var elements = document.getElementsByAttribute("preference", this.id);
          for (var i = 0; i < elements.length; ++i) {
            elements[i].disabled = val;

            var labels = document.getElementsByAttribute("control", elements[i].id);
            for (var j = 0; j < labels.length; ++j)
              labels[j].disabled = val;
          }

          return val;
        ]]>
        </setter>
      </property>

      <property name="tabIndex">
        <getter>
          return parseInt(this.getAttribute("tabindex"));
        </getter>
        <setter>
        <![CDATA[
          if (val)
            this.setAttribute("tabindex", val);
          else
            this.removeAttribute("tabindex");

          if (!this.id)
            return val;

          var elements = document.getElementsByAttribute("preference", this.id);
          for (var i = 0; i < elements.length; ++i) {
            elements[i].tabIndex = val;

            var labels = document.getElementsByAttribute("control", elements[i].id);
            for (var j = 0; j < labels.length; ++j)
              labels[j].tabIndex = val;
          }

          return val;
        ]]>
        </setter>
      </property>

      <property name="hasUserValue">
        <getter>
        <![CDATA[
          return this.preferences.rootBranch.prefHasUserValue(this.name) &&
                 this.value !== undefined;
        ]]>
        </getter>
      </property>

      <method name="reset">
        <body>
          // defer reset until preference update
          this.value = undefined;
        </body>
      </method>

      <field name="_useDefault">false</field>
      <property name="defaultValue">
        <getter>
        <![CDATA[
          this._useDefault = true;
          var val = this.valueFromPreferences;
          this._useDefault = false;
          return val;
        ]]>
        </getter>
      </property>

      <property name="_branch">
        <getter>
          return this._useDefault ? this.preferences.defaultBranch : this.preferences.rootBranch;
        </getter>
      </property>

      <field name="batching">false</field>

      <method name="_reportUnknownType">
        <body>
        <![CDATA[
          var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                                         .getService(Components.interfaces.nsIConsoleService);
          var msg = "<preference> with id='" + this.id + "' and name='" +
                    this.name + "' has unknown type '" + this.type + "'.";
          consoleService.logStringMessage(msg);
        ]]>
        </body>
      </method>

      <property name="valueFromPreferences">
        <getter>
        <![CDATA[
          try {
            // Force a resync of value with preferences.
            switch (this.type) {
            case "int":
              return this._branch.getIntPref(this.name);
            case "bool":
              var val = this._branch.getBoolPref(this.name);
              return this.inverted ? !val : val;
            case "wstring":
              return this._branch
                         .getComplexValue(this.name, Components.interfaces.nsIPrefLocalizedString)
                         .data;
            case "string":
            case "unichar":
              return this._branch.getStringPref(this.name);
            case "fontname":
              var family = this._branch.getStringPref(this.name);
              var fontEnumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
                                             .createInstance(Components.interfaces.nsIFontEnumerator);
              return fontEnumerator.getStandardFamilyName(family);
            case "file":
              var f = this._branch
                          .getComplexValue(this.name, Components.interfaces.nsILocalFile);
              return f;
            default:
              this._reportUnknownType();
            }
          } catch (e) { }
          return null;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          // Exit early if nothing to do.
          if (this.readonly || this.valueFromPreferences == val)
            return val;

          // The special value undefined means 'reset preference to default'.
          if (val === undefined) {
            this.preferences.rootBranch.clearUserPref(this.name);
            return val;
          }

          // Force a resync of preferences with value.
          switch (this.type) {
          case "int":
            this.preferences.rootBranch.setIntPref(this.name, val);
            break;
          case "bool":
            this.preferences.rootBranch.setBoolPref(this.name, this.inverted ? !val : val);
            break;
          case "wstring":
            var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
                                .createInstance(Components.interfaces.nsIPrefLocalizedString);
            pls.data = val;
            this.preferences.rootBranch
                .setComplexValue(this.name, Components.interfaces.nsIPrefLocalizedString, pls);
            break;
          case "string":
          case "unichar":
          case "fontname":
            this.preferences.rootBranch.setStringPref(this.name, val);
            break;
          case "file":
            var lf;
            if (typeof(val) == "string") {
              lf = Components.classes["@mozilla.org/file/local;1"]
                             .createInstance(Components.interfaces.nsILocalFile);
              lf.persistentDescriptor = val;
              if (!lf.exists())
                lf.initWithPath(val);
            } else
              lf = val.QueryInterface(Components.interfaces.nsILocalFile);
            this.preferences.rootBranch
                .setComplexValue(this.name, Components.interfaces.nsILocalFile, lf);
            break;
          default:
            this._reportUnknownType();
          }
          if (!this.batching)
            this.preferences.service.savePrefFile(null);
          return val;
        ]]>
        </setter>
      </property>

      <method name="setElementValue">
        <parameter name="aElement"/>
        <body>
        <![CDATA[
          if (this.locked)
            aElement.disabled = true;

          if (!this.isElementEditable(aElement))
            return;

          var rv = undefined;
          if (aElement.hasAttribute("onsyncfrompreference")) {
            // Value changed, synthesize an event
            try {
              var event = document.createEvent("Events");
              event.initEvent("syncfrompreference", true, true);
              var f = new Function("event",
                                   aElement.getAttribute("onsyncfrompreference"));
              rv = f.call(aElement, event);
            } catch (e) {
              Components.utils.reportError(e);
            }
          }
          var val = rv;
          if (val === undefined)
            val = this.instantApply ? this.valueFromPreferences : this.value;
          // if the preference is marked for reset, show default value in UI
          if (val === undefined)
            val = this.defaultValue;

          /**
           * Initialize a UI element property with a value. Handles the case
           * where an element has not yet had a XBL binding attached for it and
           * the property setter does not yet exist by setting the same attribute
           * on the XUL element using DOM apis and assuming the element's
           * constructor or property getters appropriately handle this state.
           */
          function setValue(element, attribute, value) {
            if (attribute in element)
              element[attribute] = value;
            else
              element.setAttribute(attribute, value);
          }
          if (aElement.localName == "checkbox" ||
              aElement.localName == "listitem")
            setValue(aElement, "checked", val);
          else if (aElement.localName == "colorpicker")
            setValue(aElement, "color", val);
          else if (aElement.localName == "textbox") {
            // XXXmano Bug 303998: Avoid a caret placement issue if either the
            // preference observer or its setter calls updateElements as a result
            // of the input event handler.
            if (aElement.value !== val)
              setValue(aElement, "value", val);
          } else
            setValue(aElement, "value", val);
        ]]>
        </body>
      </method>

      <method name="getElementValue">
        <parameter name="aElement"/>
        <body>
        <![CDATA[
          if (aElement.hasAttribute("onsynctopreference")) {
            // Value changed, synthesize an event
            try {
              var event = document.createEvent("Events");
              event.initEvent("synctopreference", true, true);
              var f = new Function("event",
                                   aElement.getAttribute("onsynctopreference"));
              var rv = f.call(aElement, event);
              if (rv !== undefined)
                return rv;
            } catch (e) {
              Components.utils.reportError(e);
            }
          }

          /**
           * Read the value of an attribute from an element, assuming the
           * attribute is a property on the element's node API. If the property
           * is not present in the API, then assume its value is contained in
           * an attribute, as is the case before a binding has been attached.
           */
          function getValue(element, attribute) {
            if (attribute in element)
              return element[attribute];
            return element.getAttribute(attribute);
          }
          if (aElement.localName == "checkbox" ||
              aElement.localName == "listitem")
            var value = getValue(aElement, "checked");
          else if (aElement.localName == "colorpicker")
            value = getValue(aElement, "color");
          else
            value = getValue(aElement, "value");

          switch (this.type) {
          case "int":
            return parseInt(value, 10) || 0;
          case "bool":
            return typeof(value) == "boolean" ? value : value == "true";
          }
          return value;
        ]]>
        </body>
      </method>

      <method name="isElementEditable">
        <parameter name="aElement"/>
        <body>
        <![CDATA[
          switch (aElement.localName) {
          case "checkbox":
          case "colorpicker":
          case "radiogroup":
          case "textbox":
          case "listitem":
          case "listbox":
          case "menulist":
            return true;
          }
          return aElement.getAttribute("preference-editable") == "true";
        ]]>
        </body>
      </method>

      <method name="updateElements">
        <body>
        <![CDATA[
          if (!this.id)
            return;

          // This "change" event handler tracks changes made to preferences by
          // sources other than the user in this window.
          var elements = document.getElementsByAttribute("preference", this.id);
          for (var i = 0; i < elements.length; ++i)
            this.setElementValue(elements[i]);
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="change">
        this.updateElements();
      </handler>
    </handlers>
  </binding>

  <binding id="prefwindow"
           extends="chrome://global/content/bindings/dialog.xml#dialog">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
             closebuttonlabel="&preferencesCloseButton.label;"
             closebuttonaccesskey="&preferencesCloseButton.accesskey;"
             role="dialog"
             title="&preferencesDefaultTitleWin.title;">
      <xul:windowdragbox orient="vertical">
        <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
                        role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
      </xul:windowdragbox>
      <xul:hbox flex="1" class="paneDeckContainer">
        <xul:deck anonid="paneDeck" flex="1">
          <children includes="prefpane"/>
        </xul:deck>
      </xul:hbox>
      <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
        <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
        <xul:spacer anonid="spacer" flex="1"/>
        <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
        <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
      </xul:hbox>
      <xul:hbox>
        <children/>
      </xul:hbox>
    </content>
    <implementation implements="nsITimerCallback">
      <constructor>
      <![CDATA[
        if (this.type != "child") {
          if (!this._instantApplyInitialized) {
            let psvc = Components.classes["@mozilla.org/preferences-service;1"]
                                 .getService(Components.interfaces.nsIPrefBranch);
            this.instantApply = psvc.getBoolPref("browser.preferences.instantApply");
          }
          if (this.instantApply) {
            var docElt = document.documentElement;
            var acceptButton = docElt.getButton("accept");
            acceptButton.hidden = true;
            var cancelButton  = docElt.getButton("cancel");
            if (/Mac/.test(navigator.platform)) {
              // no buttons on Mac except Help
              cancelButton.hidden = true;
              // Move Help button to the end
              document.getAnonymousElementByAttribute(this, "anonid", "spacer").hidden = true;
              // Also, don't fire onDialogAccept on enter
              acceptButton.disabled = true;
            } else {
              // morph the Cancel button into the Close button
              cancelButton.setAttribute("icon", "close");
              cancelButton.label = docElt.getAttribute("closebuttonlabel");
              cancelButton.accesskey = docElt.getAttribute("closebuttonaccesskey");
            }
          }
        }
        this.setAttribute("animated", this._shouldAnimate ? "true" : "false");
        var panes = this.preferencePanes;

        var lastPane = null;
        if (this.lastSelected) {
          lastPane = document.getElementById(this.lastSelected);
          if (!lastPane) {
            this.lastSelected = "";
          }
        }

        var paneToLoad;
        if ("arguments" in window && window.arguments[0] && document.getElementById(window.arguments[0]) && document.getElementById(window.arguments[0]).nodeName == "prefpane") {
          paneToLoad = document.getElementById(window.arguments[0]);
          this.lastSelected = paneToLoad.id;
        } else if (lastPane)
          paneToLoad = lastPane;
        else
          paneToLoad = panes[0];

        for (var i = 0; i < panes.length; ++i) {
          this._makePaneButton(panes[i]);
          if (panes[i].loaded) {
            // Inline pane content, fire load event to force initialization.
            this._fireEvent("paneload", panes[i]);
          }
        }
        this.showPane(paneToLoad);

        if (panes.length == 1)
          this._selector.setAttribute("collapsed", "true");
      ]]>
      </constructor>

      <destructor>
      <![CDATA[
        // Release timers to avoid reference cycles.
        if (this._animateTimer) {
          this._animateTimer.cancel();
          this._animateTimer = null;
        }
        if (this._fadeTimer) {
          this._fadeTimer.cancel();
          this._fadeTimer = null;
        }
      ]]>
      </destructor>

      <!-- Derived bindings can set this to true to cause us to skip
           reading the browser.preferences.instantApply pref in the constructor.
           Then they can set instantApply to their wished value. -->
      <field name="_instantApplyInitialized">false</field>
      <!-- Controls whether changed pref values take effect immediately. -->
      <field name="instantApply">false</field>

      <property name="preferencePanes"
                onget="return this.getElementsByTagName('prefpane');"/>

      <property name="type" onget="return this.getAttribute('type');"/>
      <property name="_paneDeck"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'paneDeck');"/>
      <property name="_paneDeckContainer"
                onget="return document.getAnonymousElementByAttribute(this, 'class', 'paneDeckContainer');"/>
      <property name="_selector"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'selector');"/>
      <property name="lastSelected"
                onget="return this.getAttribute('lastSelected');">
        <setter>
          this.setAttribute("lastSelected", val);
          document.persist(this.id, "lastSelected");
          return val;
        </setter>
      </property>
      <property name="currentPane"
                onset="return this._currentPane = val;">
        <getter>
          if (!this._currentPane)
            this._currentPane = this.preferencePanes[0];

          return this._currentPane;
        </getter>
      </property>
      <field name="_currentPane">null</field>


      <method name="_makePaneButton">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          var radio = document.createElement("radio");
          radio.setAttribute("pane", aPaneElement.id);
          radio.setAttribute("label", aPaneElement.label);
          // Expose preference group choice to accessibility APIs as an unchecked list item
          // The parent group is exposed to accessibility APIs as a list
          if (aPaneElement.image)
            radio.setAttribute("src", aPaneElement.image);
          radio.style.listStyleImage = aPaneElement.style.listStyleImage;
          this._selector.appendChild(radio);
          return radio;
        ]]>
        </body>
      </method>

      <method name="showPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          if (!aPaneElement)
            return;

          this._selector.selectedItem = document.getAnonymousElementByAttribute(this, "pane", aPaneElement.id);
          if (!aPaneElement.loaded) {
            let OverlayLoadObserver = function(aPane) {
              this._pane = aPane;
            }
            OverlayLoadObserver.prototype = {
              _outer: this,
              observe(aSubject, aTopic, aData) {
                this._pane.loaded = true;
                this._outer._fireEvent("paneload", this._pane);
                this._outer._selectPane(this._pane);
              }
            };

            var obs = new OverlayLoadObserver(aPaneElement);
            document.loadOverlay(aPaneElement.src, obs);
          } else
            this._selectPane(aPaneElement);
        ]]>
        </body>
      </method>

      <method name="_fireEvent">
        <parameter name="aEventName"/>
        <parameter name="aTarget"/>
        <body>
        <![CDATA[
          // Panel loaded, synthesize a load event.
          try {
            var event = document.createEvent("Events");
            event.initEvent(aEventName, true, true);
            var cancel = !aTarget.dispatchEvent(event);
            if (aTarget.hasAttribute("on" + aEventName)) {
              var fn = new Function("event", aTarget.getAttribute("on" + aEventName));
              var rv = fn.call(aTarget, event);
              if (rv == false)
                cancel = true;
            }
            return !cancel;
          } catch (e) {
            Components.utils.reportError(e);
          }
          return false;
        ]]>
        </body>
      </method>

      <field name="_initialized">false</field>
      <method name="_selectPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          if (/Mac/.test(navigator.platform)) {
            var paneTitle = aPaneElement.label;
            if (paneTitle != "")
              document.title = paneTitle;
          }
          var helpButton = document.documentElement.getButton("help");
          if (aPaneElement.helpTopic)
            helpButton.hidden = false;
          else
            helpButton.hidden = true;

          // Find this pane's index in the deck and set the deck's
          // selectedIndex to that value to switch to it.
          var prefpanes = this.preferencePanes;
          for (var i = 0; i < prefpanes.length; ++i) {
            if (prefpanes[i] == aPaneElement) {
              this._paneDeck.selectedIndex = i;

              if (this.type != "child") {
                if (aPaneElement.hasAttribute("flex") && this._shouldAnimate &&
                    prefpanes.length > 1)
                  aPaneElement.removeAttribute("flex");
                // Calling sizeToContent after the first prefpane is loaded
                // will size the windows contents so style information is
                // available to calculate correct sizing.
                if (!this._initialized && prefpanes.length > 1) {
                  if (this._shouldAnimate)
                    this.style.minHeight = 0;
                  window.sizeToContent();
                }

                var oldPane = this.lastSelected ? document.getElementById(this.lastSelected) : this.preferencePanes[0];
                oldPane.selected = !(aPaneElement.selected = true);
                this.lastSelected = aPaneElement.id;
                this.currentPane = aPaneElement;
                this._initialized = true;

                // Only animate if we've switched between prefpanes
                if (this._shouldAnimate && oldPane.id != aPaneElement.id) {
                  aPaneElement.style.opacity = 0.0;
                  this.animate(oldPane, aPaneElement);
                } else if (!this._shouldAnimate && prefpanes.length > 1) {
                  var targetHeight = parseInt(window.getComputedStyle(this._paneDeckContainer).height);
                  var verticalPadding = parseInt(window.getComputedStyle(aPaneElement).paddingTop);
                  verticalPadding += parseInt(window.getComputedStyle(aPaneElement).paddingBottom);
                  if (aPaneElement.contentHeight > targetHeight - verticalPadding) {
                    // To workaround the bottom border of a groupbox from being
                    // cutoff an hbox with a class of bottomBox may enclose it.
                    // This needs to include its padding to resize properly.
                    // See bug 394433
                    var bottomPadding = 0;
                    var bottomBox = aPaneElement.getElementsByAttribute("class", "bottomBox")[0];
                    if (bottomBox)
                      bottomPadding = parseInt(window.getComputedStyle(bottomBox).paddingBottom);
                    window.innerHeight += bottomPadding + verticalPadding + aPaneElement.contentHeight - targetHeight;
                  }

                  // XXX rstrong - extend the contents of the prefpane to
                  // prevent elements from being cutoff (see bug 349098).
                  if (aPaneElement.contentHeight + verticalPadding < targetHeight)
                    aPaneElement._content.style.height = targetHeight - verticalPadding + "px";
                }
              }
              break;
            }
          }
        ]]>
        </body>
      </method>

      <property name="_shouldAnimate">
        <getter>
        <![CDATA[
          var psvc = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
          return psvc.getBoolPref("browser.preferences.animateFadeIn",
                                  /Mac/.test(navigator.platform));
        ]]>
        </getter>
      </property>

      <method name="animate">
        <parameter name="aOldPane"/>
        <parameter name="aNewPane"/>
        <body>
        <![CDATA[
          // if we are already resizing, use currentHeight
          var oldHeight = this._currentHeight ? this._currentHeight : aOldPane.contentHeight;

          this._multiplier = aNewPane.contentHeight > oldHeight ? 1 : -1;
          var sizeDelta = Math.abs(oldHeight - aNewPane.contentHeight);
          this._animateRemainder = sizeDelta % this._animateIncrement;

          this._setUpAnimationTimer(oldHeight);
        ]]>
        </body>
      </method>

      <property name="_sizeIncrement">
        <getter>
        <![CDATA[
          var lastSelectedPane = document.getElementById(this.lastSelected);
          var increment = this._animateIncrement * this._multiplier;
          var newHeight = this._currentHeight + increment;
          if ((this._multiplier > 0 && this._currentHeight >= lastSelectedPane.contentHeight) ||
              (this._multiplier < 0 && this._currentHeight <= lastSelectedPane.contentHeight))
            return 0;

          if ((this._multiplier > 0 && newHeight > lastSelectedPane.contentHeight) ||
              (this._multiplier < 0 && newHeight < lastSelectedPane.contentHeight))
            increment = this._animateRemainder * this._multiplier;
          return increment;
        ]]>
        </getter>
      </property>

      <method name="notify">
        <parameter name="aTimer"/>
        <body>
        <![CDATA[
          if (!document)
            aTimer.cancel();

          if (aTimer == this._animateTimer) {
            var increment = this._sizeIncrement;
            if (increment != 0) {
              window.innerHeight += increment;
              this._currentHeight += increment;
            } else {
              aTimer.cancel();
              this._setUpFadeTimer();
            }
          } else if (aTimer == this._fadeTimer) {
            var elt = document.getElementById(this.lastSelected);
            var newOpacity = parseFloat(window.getComputedStyle(elt).opacity) + this._fadeIncrement;
            if (newOpacity < 1.0)
              elt.style.opacity = newOpacity;
            else {
              aTimer.cancel();
              elt.style.opacity = 1.0;
            }
          }
        ]]>
        </body>
      </method>

      <method name="_setUpAnimationTimer">
        <parameter name="aStartHeight"/>
        <body>
        <![CDATA[
          if (!this._animateTimer)
            this._animateTimer = Components.classes["@mozilla.org/timer;1"]
                                           .createInstance(Components.interfaces.nsITimer);
          else
            this._animateTimer.cancel();
          this._currentHeight = aStartHeight;

          this._animateTimer.initWithCallback(this, this._animateDelay,
                                              Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
        ]]>
        </body>
      </method>

      <method name="_setUpFadeTimer">
        <body>
        <![CDATA[
          if (!this._fadeTimer)
            this._fadeTimer = Components.classes["@mozilla.org/timer;1"]
                                        .createInstance(Components.interfaces.nsITimer);
          else
            this._fadeTimer.cancel();

          this._fadeTimer.initWithCallback(this, this._fadeDelay,
                                           Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
        ]]>
        </body>
      </method>

      <field name="_animateTimer">null</field>
      <field name="_fadeTimer">null</field>
      <field name="_animateDelay">15</field>
      <field name="_animateIncrement">40</field>
      <field name="_fadeDelay">5</field>
      <field name="_fadeIncrement">0.40</field>
      <field name="_animateRemainder">0</field>
      <field name="_currentHeight">0</field>
      <field name="_multiplier">0</field>

      <method name="addPane">
        <parameter name="aPaneElement"/>
        <body>
        <![CDATA[
          this.appendChild(aPaneElement);

          // Set up pane button
          this._makePaneButton(aPaneElement);
        ]]>
        </body>
      </method>

      <method name="openSubDialog">
        <parameter name="aURL"/>
        <parameter name="aFeatures"/>
        <parameter name="aParams"/>
        <body>
          return openDialog(aURL, "", "modal,centerscreen,resizable=no" + (aFeatures != "" ? ("," + aFeatures) : ""), aParams);
        </body>
      </method>

      <method name="openWindow">
        <parameter name="aWindowType"/>
        <parameter name="aURL"/>
        <parameter name="aFeatures"/>
        <parameter name="aParams"/>
        <body>
        <![CDATA[
          var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                             .getService(Components.interfaces.nsIWindowMediator);
          var win = aWindowType ? wm.getMostRecentWindow(aWindowType) : null;
          if (win) {
            if ("initWithParams" in win)
              win.initWithParams(aParams);
            win.focus();
          } else {
            var features = "resizable,dialog=no,centerscreen" + (aFeatures != "" ? ("," + aFeatures) : "");
            var parentWindow = (this.instantApply || !window.opener || window.opener.closed) ? window : window.opener;
            win = parentWindow.openDialog(aURL, "_blank", features, aParams);
          }
          return win;
        ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="dialogaccept">
      <![CDATA[
        if (!this._fireEvent("beforeaccept", this)) {
          return false;
        }

        var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                    .getService(Components.interfaces.nsIScriptSecurityManager);
        if (this.type == "child" && window.opener &&
            secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
          var pdocEl = window.opener.document.documentElement;
          if (pdocEl.instantApply) {
            let panes = this.preferencePanes;
            for (let i = 0; i < panes.length; ++i)
              panes[i].writePreferences(true);
          } else {
            // Clone all the preferences elements from the child document and
            // insert them into the pane collection of the parent.
            var pdoc = window.opener.document;
            if (pdoc.documentElement.localName == "prefwindow") {
              var currentPane = pdoc.documentElement.currentPane;
              var id = window.location.href + "#childprefs";
              var childPrefs = pdoc.getElementById(id);
              if (!childPrefs) {
                childPrefs = pdoc.createElement("preferences");
                currentPane.appendChild(childPrefs);
                childPrefs.id = id;
              }
              let panes = this.preferencePanes;
              for (let i = 0; i < panes.length; ++i) {
                var preferences = panes[i].preferences;
                for (var j = 0; j < preferences.length; ++j) {
                  // Try to find a preference element for the same preference.
                  var preference = null;
                  var parentPreferences = pdoc.getElementsByTagName("preferences");
                  for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
                    var parentPrefs = parentPreferences[k]
                                         .getElementsByAttribute("name", preferences[j].name);
                    for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
                      if (parentPrefs[l].localName == "preference")
                        preference = parentPrefs[l];
                    }
                  }
                  if (!preference) {
                    // No matching preference in the parent window.
                    preference = pdoc.createElement("preference");
                    childPrefs.appendChild(preference);
                    preference.name     = preferences[j].name;
                    preference.type     = preferences[j].type;
                    preference.inverted = preferences[j].inverted;
                    preference.readonly = preferences[j].readonly;
                    preference.disabled = preferences[j].disabled;
                  }
                  preference.value = preferences[j].value;
                }
              }
            }
          }
        } else {
          let panes = this.preferencePanes;
          for (var i = 0; i < panes.length; ++i)
            panes[i].writePreferences(false);

          let psvc = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefService);
          psvc.savePrefFile(null);
        }

        return true;
      ]]>
      </handler>
      <handler event="command">
        if (event.originalTarget.hasAttribute("pane")) {
          var pane = document.getElementById(event.originalTarget.getAttribute("pane"));
          this.showPane(pane);
        }
      </handler>

      <handler event="keypress" key="&windowClose.key;" modifiers="accel" phase="capturing">
      <![CDATA[
        if (this.instantApply)
          window.close();
        event.stopPropagation();
        event.preventDefault();
      ]]>
      </handler>

      <handler event="keypress"
               keycode="&openHelp.commandkey;"
               phase="capturing">
      <![CDATA[
        var helpButton = this.getButton("help");
        if (helpButton.disabled || helpButton.hidden)
          return;
        this._fireEvent("dialoghelp", this);
        event.stopPropagation();
        event.preventDefault();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="prefpane">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content>
      <xul:vbox class="content-box" xbl:inherits="flex">
        <children/>
      </xul:vbox>
    </content>
    <implementation>
      <method name="writePreferences">
        <parameter name="aFlushToDisk"/>
        <body>
        <![CDATA[
          // Write all values to preferences.
          if (this._deferredValueUpdateElements.size) {
            this._finalizeDeferredElements();
          }

          var preferences = this.preferences;
          for (var i = 0; i < preferences.length; ++i) {
            var preference = preferences[i];
            preference.batching = true;
            preference.valueFromPreferences = preference.value;
            preference.batching = false;
          }
          if (aFlushToDisk) {
            var psvc = Components.classes["@mozilla.org/preferences-service;1"]
                                 .getService(Components.interfaces.nsIPrefService);
            psvc.savePrefFile(null);
          }
        ]]>
        </body>
      </method>

      <property name="src"
                onget="return this.getAttribute('src');"
                onset="this.setAttribute('src', val); return val;"/>
      <property name="selected"
                onget="return this.getAttribute('selected') == 'true';"
                onset="this.setAttribute('selected', val); return val;"/>
      <property name="image"
                onget="return this.getAttribute('image');"
                onset="this.setAttribute('image', val); return val;"/>
      <property name="label"
                onget="return this.getAttribute('label');"
                onset="this.setAttribute('label', val); return val;"/>

      <property name="preferenceElements"
                onget="return this.getElementsByAttribute('preference', '*');"/>
      <property name="preferences"
                onget="return this.getElementsByTagName('preference');"/>

      <property name="helpTopic">
        <getter>
        <![CDATA[
          // if there are tabs, and the selected tab provides a helpTopic, return that
          var box = this.getElementsByTagName("tabbox");
          if (box[0]) {
            var tab = box[0].selectedTab;
            if (tab && tab.hasAttribute("helpTopic"))
              return tab.getAttribute("helpTopic");
          }

          // otherwise, return the helpTopic of the current panel
          return this.getAttribute("helpTopic");
        ]]>
        </getter>
      </property>

      <field name="_loaded">false</field>
      <property name="loaded"
                onget="return !this.src ? true : this._loaded;"
                onset="this._loaded = val; return val;"/>

      <method name="preferenceForElement">
        <parameter name="aElement"/>
        <body>
          return document.getElementById(aElement.getAttribute("preference"));
        </body>
      </method>

      <method name="getPreferenceElement">
        <parameter name="aStartElement"/>
        <body>
        <![CDATA[
          var temp = aStartElement;
          while (temp && temp.nodeType == Node.ELEMENT_NODE &&
                 !temp.hasAttribute("preference"))
            temp = temp.parentNode;
          return temp && temp.nodeType == Node.ELEMENT_NODE ?
                 temp : aStartElement;
        ]]>
        </body>
      </method>

      <property name="DeferredTask" readonly="true">
        <getter><![CDATA[
          let module = {};
          Components.utils.import("resource://gre/modules/DeferredTask.jsm", module);
          Object.defineProperty(this, "DeferredTask", {
            configurable: true,
            enumerable: true,
            writable: true,
            value: module.DeferredTask
          });
          return module.DeferredTask;
        ]]></getter>
      </property>
      <method name="_deferredValueUpdate">
        <parameter name="aElement"/>
        <body>
        <![CDATA[
          delete aElement._deferredValueUpdateTask;
          let preference = document.getElementById(aElement.getAttribute("preference"));
          let prefVal = preference.getElementValue(aElement);
          preference.value = prefVal;
          this._deferredValueUpdateElements.delete(aElement);
        ]]>
        </body>
      </method>
      <field name="_deferredValueUpdateElements">
        new Set();
      </field>
      <method name="_finalizeDeferredElements">
        <body>
        <![CDATA[
          for (let el of this._deferredValueUpdateElements) {
            if (el._deferredValueUpdateTask) {
              el._deferredValueUpdateTask.finalize();
            }
          }
        ]]>
        </body>
      </method>
      <method name="userChangedValue">
        <parameter name="aElement"/>
        <body>
        <![CDATA[
          let element = this.getPreferenceElement(aElement);
          if (element.hasAttribute("preference")) {
            if (element.getAttribute("delayprefsave") != "true") {
              var preference = document.getElementById(element.getAttribute("preference"));
              var prefVal = preference.getElementValue(element);
              preference.value = prefVal;
            } else {
              if (!element._deferredValueUpdateTask) {
                element._deferredValueUpdateTask = new this.DeferredTask(this._deferredValueUpdate.bind(this, element), 1000);
                this._deferredValueUpdateElements.add(element);
              } else {
                // Each time the preference is changed, restart the delay.
                element._deferredValueUpdateTask.disarm();
              }
              element._deferredValueUpdateTask.arm();
            }
          }
        ]]>
        </body>
      </method>

      <property name="contentHeight">
        <getter>
          var targetHeight = parseInt(window.getComputedStyle(this._content).height);
          targetHeight += parseInt(window.getComputedStyle(this._content).marginTop);
          targetHeight += parseInt(window.getComputedStyle(this._content).marginBottom);
          return targetHeight;
        </getter>
      </property>
      <field name="_content">
        document.getAnonymousElementByAttribute(this, "class", "content-box");
      </field>
    </implementation>
    <handlers>
      <handler event="command">
        // This "command" event handler tracks changes made to preferences by
        // the user in this window.
        if (event.sourceEvent)
          event = event.sourceEvent;
        this.userChangedValue(event.target);
      </handler>
      <handler event="select">
        // This "select" event handler tracks changes made to colorpicker
        // preferences by the user in this window.
        if (event.target.localName == "colorpicker")
          this.userChangedValue(event.target);
      </handler>
      <handler event="change">
        // This "change" event handler tracks changes made to preferences by
        // the user in this window.
        this.userChangedValue(event.target);
      </handler>
      <handler event="input">
        // This "input" event handler tracks changes made to preferences by
        // the user in this window.
        this.userChangedValue(event.target);
      </handler>
      <handler event="paneload">
      <![CDATA[
        // Initialize all values from preferences.
        var elements = this.preferenceElements;
        for (var i = 0; i < elements.length; ++i) {
          try {
            var preference = this.preferenceForElement(elements[i]);
            preference.setElementValue(elements[i]);
          } catch (e) {
            dump("*** No preference found for " + elements[i].getAttribute("preference") + "\n");
          }
        }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="panebutton" role="xul:listitem"
           extends="chrome://global/content/bindings/radio.xml#radio">
    <resources>
      <stylesheet src="chrome://global/skin/preferences.css"/>
    </resources>
    <content>
      <xul:image class="paneButtonIcon" xbl:inherits="src"/>
      <xul:label class="paneButtonLabel" xbl:inherits="value=label"/>
    </content>
  </binding>

</bindings>


PK
!<MM8chrome/toolkit/content/global/bindings/progressmeter.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="progressmeterBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="progressmeter" role="xul:progressmeter">
    <resources>
      <stylesheet src="chrome://global/skin/progressmeter.css"/>
    </resources>

    <content>
      <xul:spacer class="progress-bar" xbl:inherits="mode"/>
      <xul:spacer class="progress-remainder" xbl:inherits="mode"/>
    </content>

    <implementation>
      <property name="mode" onset="if (this.mode != val) this.setAttribute('mode', val); return val;"
                            onget="return this.getAttribute('mode');"/>

      <property name="value" onget="return this.getAttribute('value') || '0';">
        <setter><![CDATA[
          var p = Math.round(val);
          var max = Math.round(this.max);
          if (p < 0)
            p = 0;
          else if (p > max)
            p = max;
          var c = this.value;
          if (p != c) {
            var delta = p - c;
            if (delta < 0)
              delta = -delta;
            if (delta > 3 || p == 0 || p == max) {
              this.setAttribute("value", p);
              // Fire DOM event so that accessible value change events occur
              var event = document.createEvent("Events");
              event.initEvent("ValueChange", true, true);
              this.dispatchEvent(event);
            }
          }

          return val;
        ]]></setter>
      </property>
      <property name="max"
                onget="return this.getAttribute('max') || '100';"
                onset="this.setAttribute('max', isNaN(val) ? 100 : Math.max(val, 1));
                       this.value = this.value;
                       return val;" />
    </implementation>
  </binding>

  <binding id="progressmeter-undetermined"
           extends="chrome://global/content/bindings/progressmeter.xml#progressmeter">
    <content>
      <xul:stack class="progress-remainder" flex="1" anonid="stack" style="overflow: -moz-hidden-unscrollable;">
        <xul:spacer class="progress-bar" anonid="spacer" top="0" style="margin-right: -1000px;"/>
      </xul:stack>
    </content>

    <implementation>
      <field name="_alive">true</field>
      <method name="_init">
        <body><![CDATA[
          var stack =
            document.getAnonymousElementByAttribute(this, "anonid", "stack");
          var spacer =
            document.getAnonymousElementByAttribute(this, "anonid", "spacer");
          var isLTR =
           document.defaultView.getComputedStyle(this).direction == "ltr";
          var startTime = performance.now();
          var self = this;

          function nextStep(t) {
            try {
              var width = stack.boxObject.width;
              if (!width) {
                // Maybe we've been removed from the document.
                if (self._alive)
                  requestAnimationFrame(nextStep);
                return;
              }

              var elapsedTime = t - startTime;

              // Width of chunk is 1/5 (determined by the ratio 2000:400) of the
              // total width of the progress bar. The left edge of the chunk
              // starts at -1 and moves all the way to 4. It covers the distance
              // in 2 seconds.
              var position = isLTR ? ((elapsedTime % 2000) / 400) - 1 :
                                     ((elapsedTime % 2000) / -400) + 4;

              width = width >> 2;
              spacer.height = stack.boxObject.height;
              spacer.width = width;
              spacer.left = width * position;

              requestAnimationFrame(nextStep);
            } catch (e) {
            }
          }
          requestAnimationFrame(nextStep);
        ]]></body>
      </method>

      <constructor>this._init();</constructor>
    </implementation>
  </binding>

</bindings>
PK
!<9!/B/B0chrome/toolkit/content/global/bindings/radio.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="radioBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="radiogroup" role="xul:radiogroup"
           extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/radio.css"/>
    </resources>

    <implementation implements="nsIDOMXULSelectControlElement">
      <constructor>
        <![CDATA[
          if (this.getAttribute("disabled") == "true")
            this.disabled = true;

          var children = this._getRadioChildren();
          var length = children.length;
          for (var i = 0; i < length; i++) {
            if (children[i].getAttribute("selected") == "true") {
              this.selectedIndex = i;
              return;
            }
          }

          var value = this.value;
          if (value)
            this.value = value;
          else
            this.selectedIndex = 0;
        ]]>
      </constructor>

      <property name="value" onget="return this.getAttribute('value');">
        <setter>
          <![CDATA[
            this.setAttribute("value", val);
            var children = this._getRadioChildren();
            for (var i = 0; i < children.length; i++) {
              if (String(children[i].value) == String(val)) {
                this.selectedItem = children[i];
                break;
              }
            }
            return val;
          ]]>
        </setter>
      </property>
      <property name="disabled">
        <getter>
        <![CDATA[
          if (this.getAttribute("disabled") == "true")
            return true;
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (!children[i].hidden && !children[i].collapsed && !children[i].disabled)
              return false;
          }
          return true;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          if (val)
            this.setAttribute("disabled", "true");
          else
            this.removeAttribute("disabled");
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            children[i].disabled = val;
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="itemCount" readonly="true"
                onget="return this._getRadioChildren().length"/>

      <property name="selectedIndex">
        <getter>
        <![CDATA[
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (children[i].selected)
              return i;
          }
          return -1;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          this.selectedItem = this._getRadioChildren()[val];
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
        <![CDATA[
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (children[i].selected)
              return children[i];
          }
          return null;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          var focused = this.getAttribute("focused") == "true";
          var alreadySelected = false;

          if (val) {
            alreadySelected = val.getAttribute("selected") == "true";
            val.setAttribute("focused", focused);
            val.setAttribute("selected", "true");
            this.setAttribute("value", val.value);
          } else {
            this.removeAttribute("value");
          }

          // uncheck all other group nodes
          var children = this._getRadioChildren();
          var previousItem = null;
          for (var i = 0; i < children.length; ++i) {
            if (children[i] != val) {
              if (children[i].getAttribute("selected") == "true")
                previousItem = children[i];

              children[i].removeAttribute("selected");
              children[i].removeAttribute("focused");
            }
          }

          var event = document.createEvent("Events");
          event.initEvent("select", false, true);
          this.dispatchEvent(event);

          if (!alreadySelected && focused) {
            // Only report if actual change
            var myEvent;
            if (val) {
              myEvent = document.createEvent("Events");
              myEvent.initEvent("RadioStateChange", true, true);
              val.dispatchEvent(myEvent);
            }

            if (previousItem) {
              myEvent = document.createEvent("Events");
              myEvent.initEvent("RadioStateChange", true, true);
              previousItem.dispatchEvent(myEvent);
            }
          }

          return val;
        ]]>
        </setter>
      </property>

      <property name="focusedItem">
        <getter>
        <![CDATA[
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (children[i].getAttribute("focused") == "true")
              return children[i];
          }
          return null;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          if (val) val.setAttribute("focused", "true");

          // unfocus all other group nodes
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (children[i] != val)
              children[i].removeAttribute("focused");
          }
          return val;
        ]]>
        </setter>
      </property>

      <method name="checkAdjacentElement">
        <parameter name="aNextFlag"/>
        <body>
        <![CDATA[
          var currentElement = this.focusedItem || this.selectedItem;
          var i;
          var children = this._getRadioChildren();
          for (i = 0; i < children.length; ++i ) {
            if (children[i] == currentElement)
              break;
          }
          var index = i;

          if (aNextFlag) {
            do {
              if (++i == children.length)
                i = 0;
              if (i == index)
                break;
            }
            while (children[i].hidden || children[i].collapsed || children[i].disabled);
            // XXX check for display/visibility props too

            this.selectedItem = children[i];
            children[i].doCommand();
          } else {
            do {
              if (i == 0)
                i = children.length;
              if (--i == index)
                break;
            }
            while (children[i].hidden || children[i].collapsed || children[i].disabled);
            // XXX check for display/visibility props too

            this.selectedItem = children[i];
            children[i].doCommand();
          }
        ]]>
        </body>
      </method>
      <field name="_radioChildren">null</field>
      <method name="_getRadioChildren">
        <body>
        <![CDATA[
          if (this._radioChildren)
            return this._radioChildren;

          var radioChildren = [];
          var doc = this.ownerDocument;

          if (this.hasChildNodes()) {
            // Don't store the collected child nodes immediately,
            // collecting the child nodes could trigger constructors
            // which would blow away our list.

            const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
            var iterator = doc.createTreeWalker(this,
                                                nsIDOMNodeFilter.SHOW_ELEMENT,
                                                this._filterRadioGroup);
            while (iterator.nextNode())
              radioChildren.push(iterator.currentNode);
            return this._radioChildren = radioChildren;
          }

          // We don't have child nodes.
          const XUL_NS = "http://www.mozilla.org/keymaster/"
                       + "gatekeeper/there.is.only.xul";
          var elems = doc.getElementsByAttribute("group", this.id);
          for (var i = 0; i < elems.length; i++) {
            if ((elems[i].namespaceURI == XUL_NS) &&
                (elems[i].localName == "radio")) {
              radioChildren.push(elems[i]);
            }
          }
          return this._radioChildren = radioChildren;
        ]]>
        </body>
      </method>
      <method name="_filterRadioGroup">
        <parameter name="node"/>
        <body>
        <![CDATA[
          switch (node.localName) {
            case "radio": return NodeFilter.FILTER_ACCEPT;
            case "template":
            case "radiogroup": return NodeFilter.FILTER_REJECT;
            default: return NodeFilter.FILTER_SKIP;
          }
        ]]>
        </body>
      </method>

      <method name="getIndexOfItem">
        <parameter name="item"/>
        <body>
          return this._getRadioChildren().indexOf(item);
        </body>
      </method>

      <method name="getItemAtIndex">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var children = this._getRadioChildren();
          return (index >= 0 && index < children.length) ? children[index] : null;
        ]]>
        </body>
      </method>

      <method name="appendItem">
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var radio = document.createElementNS(XULNS, "radio");
          radio.setAttribute("label", label);
          radio.setAttribute("value", value);
          this.appendChild(radio);
          this._radioChildren = null;
          return radio;
        ]]>
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="index"/>
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var radio = document.createElementNS(XULNS, "radio");
          radio.setAttribute("label", label);
          radio.setAttribute("value", value);
          var before = this.getItemAtIndex(index);
          if (before)
            before.parentNode.insertBefore(radio, before);
          else
            this.appendChild(radio);
          this._radioChildren = null;
          return radio;
        ]]>
        </body>
      </method>

      <method name="removeItemAt">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var remove = this.getItemAtIndex(index);
          if (remove) {
            remove.remove();
            this._radioChildren = null;
          }
          return remove;
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="mousedown">
        if (this.disabled)
          event.preventDefault();
       </handler>

      <!-- keyboard navigation -->
      <!-- Here's how keyboard navigation works in radio groups on Windows:
           The group takes 'focus'
           The user is then free to navigate around inside the group
           using the arrow keys. Accessing previous or following radio buttons
           is done solely through the arrow keys and not the tab button. Tab
           takes you to the next widget in the tab order -->
      <handler event="keypress" key=" " phase="target">
        this.selectedItem = this.focusedItem;
        this.selectedItem.doCommand();
        // Prevent page from scrolling on the space key.
        event.preventDefault();
      </handler>
      <handler event="keypress" keycode="VK_UP" phase="target">
        this.checkAdjacentElement(false);
        event.stopPropagation();
        event.preventDefault();
      </handler>
      <handler event="keypress" keycode="VK_LEFT" phase="target">
        // left arrow goes back when we are ltr, forward when we are rtl
        this.checkAdjacentElement(document.defaultView.getComputedStyle(
                                    this).direction == "rtl");
        event.stopPropagation();
        event.preventDefault();
      </handler>
      <handler event="keypress" keycode="VK_DOWN" phase="target">
        this.checkAdjacentElement(true);
        event.stopPropagation();
        event.preventDefault();
      </handler>
      <handler event="keypress" keycode="VK_RIGHT" phase="target">
        // right arrow goes forward when we are ltr, back when we are rtl
        this.checkAdjacentElement(document.defaultView.getComputedStyle(
                                    this).direction == "ltr");
        event.stopPropagation();
        event.preventDefault();
      </handler>

      <!-- set a focused attribute on the selected item when the group
           receives focus so that we can style it as if it were focused even though
           it is not (Windows platform behaviour is for the group to receive focus,
           not the item -->
      <handler event="focus" phase="target">
        <![CDATA[
          this.setAttribute("focused", "true");
          if (this.focusedItem)
            return;

          var val = this.selectedItem;
          if (!val || val.disabled || val.hidden || val.collapsed) {
            var children = this._getRadioChildren();
            for (var i = 0; i < children.length; ++i) {
              if (!children[i].hidden && !children[i].collapsed && !children[i].disabled) {
                val = children[i];
                break;
              }
            }
          }
          this.focusedItem = val;
        ]]>
      </handler>
      <handler event="blur" phase="target">
        this.removeAttribute("focused");
        this.focusedItem = null;
      </handler>
    </handlers>
  </binding>

  <binding id="radio" role="xul:radiobutton"
    extends="chrome://global/content/bindings/general.xml#control-item">
    <resources>
      <stylesheet src="chrome://global/skin/radio.css"/>
    </resources>

    <content>
      <xul:image class="radio-check" xbl:inherits="disabled,selected"/>
      <xul:hbox class="radio-label-box" align="center" flex="1">
        <xul:image class="radio-icon" xbl:inherits="src"/>
        <xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULSelectControlItemElement">
      <constructor>
        <![CDATA[
          // Just clear out the parent's cached list of radio children
          var control = this.control;
          if (control)
            control._radioChildren = null;
        ]]>
      </constructor>
      <destructor>
        <![CDATA[
          if (!this.control)
            return;

          var radioList = this.control._radioChildren;
          if (!radioList)
            return;
          for (var i = 0; i < radioList.length; ++i) {
            if (radioList[i] == this) {
              radioList.splice(i, 1);
              return;
            }
          }
        ]]>
      </destructor>
      <property name="selected" readonly="true">
        <getter>
          <![CDATA[
            return this.hasAttribute("selected");
          ]]>
        </getter>
      </property>
      <property name="radioGroup" readonly="true" onget="return this.control"/>
      <property name="control" readonly="true">
        <getter>
        <![CDATA[
          const XUL_NS = "http://www.mozilla.org/keymaster/"
                       + "gatekeeper/there.is.only.xul";
          var parent = this.parentNode;
          while (parent) {
            if ((parent.namespaceURI == XUL_NS) &&
                (parent.localName == "radiogroup")) {
              return parent;
            }
            parent = parent.parentNode;
          }

          var group = this.getAttribute("group");
          if (!group) {
            return null;
          }

          parent = this.ownerDocument.getElementById(group);
          if (!parent ||
              (parent.namespaceURI != XUL_NS) ||
              (parent.localName != "radiogroup")) {
            parent = null;
          }
          return parent;
        ]]>
        </getter>
      </property>
    </implementation>
    <handlers>
      <handler event="click" button="0">
        <![CDATA[
          if (!this.disabled)
            this.control.selectedItem = this;
         ]]>
      </handler>

      <handler event="mousedown" button="0">
        <![CDATA[
          if (!this.disabled)
            this.control.focusedItem = this;
         ]]>
      </handler>
    </handlers>
  </binding>
</bindings>
PK
!<&SS9chrome/toolkit/content/global/bindings/remote-browser.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="firefoxBrowserBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">

    <implementation type="application/javascript"
                    implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIRemoteBrowser">

      <field name="_securityUI">null</field>

      <property name="securityUI"
                readonly="true">
        <getter><![CDATA[
          if (!this._securityUI) {
            // Don't attempt to create the remote web progress if the
            // messageManager has already gone away
            if (!this.messageManager)
              return null;

            let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
            let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
            this._securityUI = new RemoteSecurityUI();
          }

          // We want to double-wrap the JS implemented interface, so that QI and instanceof works.
          var ptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
                              .createInstance(Components.interfaces.nsISupportsInterfacePointer);
          ptr.data = this._securityUI;
          return ptr.data.QueryInterface(Components.interfaces.nsISecureBrowserUI);
        ]]></getter>
      </property>

      <field name="_controller">null</field>

      <field name="_selectParentHelper">null</field>

      <field name="_remoteWebNavigation">null</field>

      <property name="webNavigation"
                onget="return this._remoteWebNavigation;"
                readonly="true"/>

      <field name="_remoteWebProgress">null</field>

      <property name="webProgress" readonly="true">
      	<getter>
      	  <![CDATA[
            if (!this._remoteWebProgress) {
              // Don't attempt to create the remote web progress if the
              // messageManager has already gone away
              if (!this.messageManager)
                return null;

              let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
              let { RemoteWebProgressManager } = Components.utils.import(jsm, {});
              this._remoteWebProgressManager = new RemoteWebProgressManager(this);
              this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
            }
            return this._remoteWebProgress;
      	  ]]>
      	</getter>
      </property>

      <field name="_remoteFinder">null</field>

      <property name="finder" readonly="true">
        <getter><![CDATA[
          if (!this._remoteFinder) {
            // Don't attempt to create the remote finder if the
            // messageManager has already gone away
            if (!this.messageManager)
              return null;

            let jsm = "resource://gre/modules/RemoteFinder.jsm";
            let { RemoteFinder } = Components.utils.import(jsm, {});
            this._remoteFinder = new RemoteFinder(this);
          }
          return this._remoteFinder;
        ]]></getter>
      </property>

      <field name="_documentURI">null</field>

      <field name="_documentContentType">null</field>

      <!--
        Used by session restore to ensure that currentURI is set so
        that switch-to-tab works before the tab is fully
        restored. This function also invokes onLocationChanged
        listeners in tabbrowser.xml.
      -->
      <method name="_setCurrentURI">
        <parameter name="aURI"/>
        <body><![CDATA[
          this._remoteWebProgressManager.setCurrentURI(aURI);
        ]]></body>
      </method>

      <property name="documentURI"
                onget="return this._documentURI;"
                readonly="true"/>

      <property name="documentContentType"
                onget="return this._documentContentType;"
                readonly="true"/>

      <field name="_contentTitle">""</field>

      <property name="contentTitle"
                onget="return this._contentTitle"
                readonly="true"/>

      <field name="_characterSet">""</field>

      <property name="characterSet"
                onget="return this._characterSet">
        <setter><![CDATA[
          this.messageManager.sendAsyncMessage("UpdateCharacterSet", {value: val});
          this._characterSet = val;
        ]]></setter>
      </property>

      <field name="_mayEnableCharacterEncodingMenu">null</field>

      <property name="mayEnableCharacterEncodingMenu"
                onget="return this._mayEnableCharacterEncodingMenu;"
                readonly="true"/>

      <field name="_contentWindow">null</field>

      <property name="contentWindow"
                onget="return null"
                readonly="true"/>

      <property name="contentWindowAsCPOW"
                onget="return this._contentWindow"
                readonly="true"/>

      <property name="contentDocument"
                onget="return null"
                readonly="true"/>

      <field name="_contentPrincipal">null</field>

      <property name="contentPrincipal"
                onget="return this._contentPrincipal"
                readonly="true"/>

      <property name="contentDocumentAsCPOW"
                onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
                readonly="true"/>

      <field name="_imageDocument">null</field>

      <property name="imageDocument"
                onget="return this._imageDocument"
                readonly="true"/>

      <field name="_fullZoom">1</field>
      <property name="fullZoom">
        <getter><![CDATA[
          return this._fullZoom;
        ]]></getter>
        <setter><![CDATA[
          let changed = val.toFixed(2) != this._fullZoom.toFixed(2);

          this._fullZoom = val;
          try {
            this.messageManager.sendAsyncMessage("FullZoom", {value: val});
          } catch (ex) {}

          if (changed) {
            let event = new Event("FullZoomChange", {bubbles: true});
            this.dispatchEvent(event);
          }
        ]]></setter>
      </property>

      <field name="_textZoom">1</field>
      <property name="textZoom">
        <getter><![CDATA[
          return this._textZoom;
        ]]></getter>
        <setter><![CDATA[
          let changed = val.toFixed(2) != this._textZoom.toFixed(2);

          this._textZoom = val;
          try {
            this.messageManager.sendAsyncMessage("TextZoom", {value: val});
          } catch (ex) {}

          if (changed) {
            let event = new Event("TextZoomChange", {bubbles: true});
            this.dispatchEvent(event);
          }
        ]]></setter>
      </property>

      <field name="_isSyntheticDocument">false</field>
      <property name="isSyntheticDocument">
        <getter><![CDATA[
          return this._isSyntheticDocument;
        ]]></getter>
      </property>

      <property name="hasContentOpener">
        <getter><![CDATA[
          let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
          return frameLoader.tabParent.hasContentOpener;
        ]]></getter>
      </property>

      <field name="_outerWindowID">null</field>
      <property name="outerWindowID"
                onget="return this._outerWindowID"
                readonly="true"/>

      <field name="_innerWindowID">null</field>
      <property name="innerWindowID">
        <getter><![CDATA[
          return this._innerWindowID;
        ]]></getter>
      </property>

      <property name="docShellIsActive">
        <getter>
          <![CDATA[
            let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            return frameLoader.tabParent.docShellIsActive;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            frameLoader.tabParent.docShellIsActive = val;
            return val;
          ]]>
        </setter>
      </property>

      <method name="preserveLayers">
        <parameter name="preserve"/>
        <body><![CDATA[
          let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
          if (frameLoader.tabParent) {
            frameLoader.tabParent.preserveLayers(preserve);
          }
        ]]></body>
      </method>

      <field name="_manifestURI"/>
      <property name="manifestURI"
                onget="return this._manifestURI"
                readonly="true"/>

      <field name="mDestroyed">false</field>

      <field name="_permitUnloadId">0</field>

      <method name="getInPermitUnload">
        <parameter name="aCallback"/>
        <body>
        <![CDATA[
          let id = this._permitUnloadId++;
          let mm = this.messageManager;
          mm.sendAsyncMessage("InPermitUnload", {id});
          mm.addMessageListener("InPermitUnload", function listener(msg) {
            if (msg.data.id != id) {
              return;
            }
	    aCallback(msg.data.inPermitUnload);
          });
        ]]>
        </body>
      </method>

      <method name="permitUnload">
        <body>
        <![CDATA[
          let { frameLoader } = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
          let tabParent = frameLoader.tabParent;

          if (!tabParent.hasBeforeUnload) {
            return { permitUnload: true, timedOut: false };
          }

          const kTimeout = 1000;

          let finished = false;
          let responded = false;
          let permitUnload;
          let id = this._permitUnloadId++;
          let mm = this.messageManager;
          let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;

          let msgListener = msg => {
            if (msg.data.id != id) {
              return;
            }
            if (msg.data.kind == "start") {
              responded = true;
              return;
            }
            done(msg.data.permitUnload);
          };

          let observer = subject => {
            if (subject == mm) {
              done(true);
            }
          };

          function done(result) {
            finished = true;
            permitUnload = result;
            mm.removeMessageListener("PermitUnload", msgListener);
            Services.obs.removeObserver(observer, "message-manager-close");
          }

          mm.sendAsyncMessage("PermitUnload", {id});
          mm.addMessageListener("PermitUnload", msgListener);
          Services.obs.addObserver(observer, "message-manager-close");

          let timedOut = false;
          function timeout() {
            if (!responded) {
              timedOut = true;
            }

            // Dispatch something to ensure that the main thread wakes up.
            Services.tm.dispatchToMainThread(function() {});
          }

          let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
          timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);

          while (!finished && !timedOut) {
            Services.tm.currentThread.processNextEvent(true);
          }

          return {permitUnload, timedOut};
        ]]>
        </body>
      </method>

      <constructor>
        <![CDATA[
          /*
           * Don't try to send messages from this function. The message manager for
           * the <browser> element may not be initialized yet.
           */

          this._remoteWebNavigation = Components.classes["@mozilla.org/remote-web-navigation;1"]
                                                .createInstance(Components.interfaces.nsIWebNavigation);
          this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
          this._remoteWebNavigationImpl.swapBrowser(this);

          // Initialize contentPrincipal to the about:blank principal for this loadcontext
          let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
          let aboutBlank = Services.io.newURI("about:blank");
          let ssm = Services.scriptSecurityManager;
          this._contentPrincipal = ssm.getLoadContextCodebasePrincipal(aboutBlank, this.loadContext);

          this.messageManager.addMessageListener("Browser:Init", this);
          this.messageManager.addMessageListener("DOMTitleChanged", this);
          this.messageManager.addMessageListener("ImageDocumentLoaded", this);
          this.messageManager.addMessageListener("FullZoomChange", this);
          this.messageManager.addMessageListener("TextZoomChange", this);
          this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
          this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
          this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
          this.messageManager.addMessageListener("MozApplicationManifest", this);
          this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);

          if (this.hasAttribute("selectmenulist")) {
            this.messageManager.addMessageListener("Forms:ShowDropDown", this);
            this.messageManager.addMessageListener("Forms:HideDropDown", this);
          }

          if (!this.hasAttribute("disablehistory")) {
            Services.obs.addObserver(this, "browser:purge-session-history", true);
          }

          let jsm = "resource://gre/modules/RemoteController.jsm";
          let RemoteController = Components.utils.import(jsm, {}).RemoteController;
          this._controller = new RemoteController(this);
          this.controllers.appendController(this._controller);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.destroy();
        ]]>
      </destructor>

      <!-- This is necessary because the destructor doesn't always get called when
           we are removed from a tabbrowser. This will be explicitly called by tabbrowser.

           Note: This overrides the destroy() method from browser.xml. -->
      <method name="destroy">
        <body><![CDATA[
          // Make sure that any open select is closed.
          if (this._selectParentHelper) {
            let menulist = document.getElementById(this.getAttribute("selectmenulist"));
            this._selectParentHelper.hide(menulist, this);
          }

          if (this.mDestroyed)
            return;
          this.mDestroyed = true;

          try {
            this.controllers.removeController(this._controller);
          } catch (ex) {
            // This can fail when this browser element is not attached to a
            // BrowserDOMWindow.
          }

          if (!this.hasAttribute("disablehistory")) {
            let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
            try {
              Services.obs.removeObserver(this, "browser:purge-session-history");
            } catch (ex) {
              // It's not clear why this sometimes throws an exception.
            }
          }
        ]]></body>
      </method>

      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          let data = aMessage.data;
          switch (aMessage.name) {
            case "Browser:Init":
              this._outerWindowID = data.outerWindowID;
              break;
            case "DOMTitleChanged":
              this._contentTitle = data.title;
              break;
            case "ImageDocumentLoaded":
              this._imageDocument = {
                width: data.width,
                height: data.height
              };
              break;

            case "Forms:ShowDropDown": {
              if (!this._selectParentHelper) {
                this._selectParentHelper =
                  Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
              }

              let menulist = document.getElementById(this.getAttribute("selectmenulist"));
              menulist.menupopup.style.direction = data.direction;

              let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
                         this.isSyntheticDocument ? this._fullZoom : this._textZoom;
              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
                                                zoom, data.uaBackgroundColor, data.uaColor,
                                                data.uaSelectBackgroundColor, data.uaSelectColor,
                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
              this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
              break;
            }

            case "FullZoomChange": {
              this._fullZoom = data.value;
              let event = document.createEvent("Events");
              event.initEvent("FullZoomChange", true, false);
              this.dispatchEvent(event);
              break;
            }

            case "TextZoomChange": {
              this._textZoom = data.value;
              let event = document.createEvent("Events");
              event.initEvent("TextZoomChange", true, false);
              this.dispatchEvent(event);
              break;
            }

            case "ZoomChangeUsingMouseWheel": {
              let event = document.createEvent("Events");
              event.initEvent("ZoomChangeUsingMouseWheel", true, false);
              this.dispatchEvent(event);
              break;
            }

            case "DOMFullscreen:RequestExit": {
              let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
              windowUtils.exitFullscreen();
              break;
            }

            case "DOMFullscreen:RequestRollback": {
              let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
              windowUtils.remoteFrameFullscreenReverted();
              break;
            }

            case "MozApplicationManifest":
              this._manifestURI = aMessage.data.manifest;
              break;

            default:
              // Delegate to browser.xml.
              return this._receiveMessage(aMessage);
          }
          return undefined;
        ]]></body>
      </method>

      <method name="enableDisableCommands">
        <parameter name="aAction"/>
        <parameter name="aEnabledLength"/>
        <parameter name="aEnabledCommands"/>
        <parameter name="aDisabledLength"/>
        <parameter name="aDisabledCommands"/>
        <body>
          if (this._controller) {
            this._controller.enableDisableCommands(aAction,
                                                   aEnabledLength, aEnabledCommands,
                                                   aDisabledLength, aDisabledCommands);
          }
        </body>
      </method>

      <method name="purgeSessionHistory">
        <body>
          <![CDATA[
            try {
              this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
            } catch (ex) {
              // This can throw if the browser has started to go away.
              if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
                throw ex;
              }
            }
            this._remoteWebNavigationImpl.canGoBack = false;
            this._remoteWebNavigationImpl.canGoForward = false;
          ]]>
        </body>
      </method>

      <method name="createAboutBlankContentViewer">
        <parameter name="aPrincipal"/>
        <body>
          <![CDATA[
            // Ensure that the content process has the permissions which are
            // needed to create a document with the given principal.
            let permissionPrincipal =
              BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
            let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            frameLoader.tabParent.transmitPermissionsForPrincipal(permissionPrincipal);

            // Create the about blank content viewer in the content process
            this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
          ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="dragstart">
      <![CDATA[
        // If we're a remote browser dealing with a dragstart, stop it
        // from propagating up, since our content process should be dealing
        // with the mouse movement.
        event.stopPropagation();
      ]]>
      </handler>
    </handlers>

  </binding>

</bindings>
PK
!<z:ff2chrome/toolkit/content/global/bindings/resizer.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="resizerBindings"
   xmlns="http://www.mozilla.org/xbl">

  <binding id="resizer">
    <resources>
      <stylesheet src="chrome://global/skin/resizer.css"/>
    </resources>
    <implementation>
      <constructor>
      <![CDATA[
        // don't do this for viewport resizers; causes a crash related to
        // bugs 563665 and 581536 otherwise
        if (this.parentNode == this.ownerDocument.documentElement)
          return;

        // if the direction is rtl, set the rtl attribute so that the
        // stylesheet can use this to make the cursor appear properly
        var cs = window.getComputedStyle(this);
        if (cs.writingMode === undefined || cs.writingMode == "horizontal-tb") {
          if (cs.direction == "rtl") {
            this.setAttribute("rtl", "true");
          }
        } else if (cs.writingMode.endsWith("-rl")) {
          // writing-modes 'vertical-rl' and 'sideways-rl' want rtl resizers,
          // as they will appear at the bottom left of the element
          this.setAttribute("rtl", "true");
        }
      ]]>
      </constructor>
    </implementation>
  </binding>

</bindings>
PK
!<H\UPUP6chrome/toolkit/content/global/bindings/richlistbox.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="richlistboxBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="richlistbox"
           extends="chrome://global/content/bindings/listbox.xml#listbox-base">
    <resources>
      <stylesheet src="chrome://global/skin/richlistbox.css"/>
    </resources>

    <content>
      <children includes="listheader"/>
      <xul:scrollbox allowevents="true" orient="vertical" anonid="main-box"
                     flex="1" style="overflow: auto;" xbl:inherits="dir,pack">
        <children/>
      </xul:scrollbox>
    </content>

    <implementation>
      <field name="_scrollbox">
        document.getAnonymousElementByAttribute(this, "anonid", "main-box");
      </field>
      <field name="scrollBoxObject">
        this._scrollbox.boxObject;
      </field>
      <constructor>
        <![CDATA[
          // add a template build listener
          if (this.builder)
            this.builder.addListener(this._builderListener);
          else
            this._refreshSelection();
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          // remove the template build listener
          if (this.builder)
            this.builder.removeListener(this._builderListener);
        ]]>
      </destructor>

    <!-- Overriding baselistbox -->
      <method name="_fireOnSelect">
        <body>
          <![CDATA[
            // make sure not to modify last-selected when suppressing select events
            // (otherwise we'll lose the selection when a template gets rebuilt)
            if (this._suppressOnSelect || this.suppressOnSelect)
              return;

            // remember the current item and all selected items with IDs
            var state = this.currentItem ? this.currentItem.id : "";
            if (this.selType == "multiple" && this.selectedCount) {
              let getId = function getId(aItem) { return aItem.id; }
              state += " " + [...this.selectedItems].filter(getId).map(getId).join(" ");
            }
            if (state)
              this.setAttribute("last-selected", state);
            else
              this.removeAttribute("last-selected");

            // preserve the index just in case no IDs are available
            if (this.currentIndex > -1)
              this._currentIndex = this.currentIndex + 1;

            var event = document.createEvent("Events");
            event.initEvent("select", true, true);
            this.dispatchEvent(event);

            // always call this (allows a commandupdater without controller)
            document.commandDispatcher.updateCommands("richlistbox-select");
          ]]>
        </body>
      </method>

      <!-- We override base-listbox here because those methods don't take dir
           into account on listbox (which doesn't support dir yet) -->
      <method name="getNextItem">
        <parameter name="aStartItem"/>
        <parameter name="aDelta"/>
        <body>
        <![CDATA[
          var prop = this.dir == "reverse" && this._mayReverse ?
                                                "previousSibling" :
                                                "nextSibling";
          while (aStartItem) {
            aStartItem = aStartItem[prop];
            if (aStartItem && aStartItem instanceof
                Components.interfaces.nsIDOMXULSelectControlItemElement &&
                (!this._userSelecting || this._canUserSelect(aStartItem))) {
              --aDelta;
              if (aDelta == 0)
                return aStartItem;
            }
          }
          return null;
        ]]></body>
      </method>

      <method name="getPreviousItem">
        <parameter name="aStartItem"/>
        <parameter name="aDelta"/>
        <body>
        <![CDATA[
          var prop = this.dir == "reverse" && this._mayReverse ?
                                                "nextSibling" :
                                                "previousSibling";
          while (aStartItem) {
            aStartItem = aStartItem[prop];
            if (aStartItem && aStartItem instanceof
                Components.interfaces.nsIDOMXULSelectControlItemElement &&
                (!this._userSelecting || this._canUserSelect(aStartItem))) {
              --aDelta;
              if (aDelta == 0)
                return aStartItem;
            }
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="appendItem">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          return this.insertItemAt(-1, aLabel, aValue);
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="aIndex"/>
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
          const XULNS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

          var item =
            this.ownerDocument.createElementNS(XULNS, "richlistitem");
          item.setAttribute("value", aValue);

          var label = this.ownerDocument.createElementNS(XULNS, "label");
          label.setAttribute("value", aLabel);
          label.setAttribute("flex", "1");
          label.setAttribute("crop", "end");
          item.appendChild(label);

          var before = this.getItemAtIndex(aIndex);
          if (!before)
            this.appendChild(item);
          else
            this.insertBefore(item, before);

          return item;
        </body>
      </method>

      <property name="itemCount" readonly="true"
                onget="return this.children.length"/>

      <method name="getIndexOfItem">
        <parameter name="aItem"/>
        <body>
          <![CDATA[
            // don't search the children, if we're looking for none of them
            if (aItem == null)
              return -1;
            if (this._selecting && this._selecting.item == aItem)
              return this._selecting.index;
            return this.children.indexOf(aItem);
          ]]>
        </body>
      </method>

      <method name="getItemAtIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            if (this._selecting && this._selecting.index == aIndex)
              return this._selecting.item;
            return this.children[aIndex] || null;
          ]]>
        </body>
      </method>

      <method name="ensureIndexIsVisible">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            // work around missing implementation in scrollBoxObject
            return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
          ]]>
        </body>
      </method>

      <method name="ensureElementIsVisible">
        <parameter name="aElement"/>
        <body>
          <![CDATA[
            if (!aElement)
              return;
            var targetRect = aElement.getBoundingClientRect();
            var scrollRect = this._scrollbox.getBoundingClientRect();
            var offset = targetRect.top - scrollRect.top;
            if (offset >= 0) {
              // scrollRect.bottom wouldn't take a horizontal scroll bar into account
              let scrollRectBottom = scrollRect.top + this._scrollbox.clientHeight;
              offset = targetRect.bottom - scrollRectBottom;
              if (offset <= 0)
                return;
            }
            this._scrollbox.scrollTop += offset;
          ]]>
        </body>
      </method>

      <method name="scrollToIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            var item = this.getItemAtIndex(aIndex);
            if (item)
              this.scrollBoxObject.scrollToElement(item);
          ]]>
        </body>
      </method>

      <method name="getNumberOfVisibleRows">
        <!-- returns the number of currently visible rows                -->
        <!-- don't rely on this function, if the items' height can vary! -->
        <body>
          <![CDATA[
            var children = this.children;

            for (var top = 0; top < children.length && !this._isItemVisible(children[top]); top++);
            for (var ix = top; ix < children.length && this._isItemVisible(children[ix]); ix++);

            return ix - top;
          ]]>
        </body>
      </method>

      <method name="getIndexOfFirstVisibleRow">
        <body>
          <![CDATA[
            var children = this.children;

            for (var ix = 0; ix < children.length; ix++)
              if (this._isItemVisible(children[ix]))
                return ix;

            return -1;
          ]]>
        </body>
      </method>

      <method name="getRowCount">
        <body>
          <![CDATA[
            return this.children.length;
          ]]>
        </body>
      </method>

      <method name="scrollOnePage">
        <parameter name="aDirection"/> <!-- Must be -1 or 1 -->
        <body>
          <![CDATA[
            var children = this.children;

            if (children.length == 0)
              return 0;

            // If nothing is selected, we just select the first element
            // at the extreme we're moving away from
            if (!this.currentItem)
              return aDirection == -1 ? children.length : 0;

            // If the current item is visible, scroll by one page so that
            // the new current item is at approximately the same position as
            // the existing current item.
            if (this._isItemVisible(this.currentItem))
              this.scrollBoxObject.scrollBy(0, this.scrollBoxObject.height * aDirection);

            // Figure out, how many items fully fit into the view port
            // (including the currently selected one), and determine
            // the index of the first one lying (partially) outside
            var height = this.scrollBoxObject.height;
            var startBorder = this.currentItem.boxObject.y;
            if (aDirection == -1)
              startBorder += this.currentItem.boxObject.height;

            var index = this.currentIndex;
            for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
              var boxObject = children[ix].boxObject;
              if (boxObject.height == 0)
                continue; // hidden children have a y of 0
              var endBorder = boxObject.y + (aDirection == -1 ? boxObject.height : 0);
              if ((endBorder - startBorder) * aDirection > height)
                break; // we've reached the desired distance
              index = ix;
            }

            return index != this.currentIndex ? index - this.currentIndex : aDirection;
          ]]>
        </body>
      </method>

    <!-- richlistbox specific -->
      <property name="children" readonly="true">
        <getter>
          <![CDATA[
            let iface = Components.interfaces.nsIDOMXULSelectControlItemElement;
            let children = Array.from(this.childNodes)
                                .filter(node => node instanceof iface);
            if (this.dir == "reverse" && this._mayReverse) {
              children.reverse();
            }
            return children;
          ]]>
        </getter>
      </property>

      <field name="_builderListener" readonly="true">
        <![CDATA[
          ({
            mOuter: this,
            item: null,
            willRebuild(builder) { },
            didRebuild(builder) {
              this.mOuter._refreshSelection();
            }
          });
        ]]>
      </field>

      <method name="_refreshSelection">
        <body>
          <![CDATA[
            // when this method is called, we know that either the currentItem
            // and selectedItems we have are null (ctor) or a reference to an
            // element no longer in the DOM (template).

            // first look for the last-selected attribute
            var state = this.getAttribute("last-selected");
            if (state) {
              var ids = state.split(" ");

              var suppressSelect = this._suppressOnSelect;
              this._suppressOnSelect = true;
              this.clearSelection();
              for (let i = 1; i < ids.length; i++) {
                var selectedItem = document.getElementById(ids[i]);
                if (selectedItem)
                  this.addItemToSelection(selectedItem);
              }

              var currentItem = document.getElementById(ids[0]);
              if (!currentItem && this._currentIndex)
                currentItem = this.getItemAtIndex(Math.min(
                  this._currentIndex - 1, this.getRowCount()));
              if (currentItem) {
                this.currentItem = currentItem;
                if (this.selType != "multiple" && this.selectedCount == 0)
                  this.selectedItem = currentItem;

                if (this.scrollBoxObject.height) {
                  this.ensureElementIsVisible(currentItem);
                } else {
                  // XXX hack around a bug in ensureElementIsVisible as it will
                  // scroll beyond the last element, bug 493645.
                  var previousElement = this.dir == "reverse" ? currentItem.nextSibling :
                                                                currentItem.previousSibling;
                  this.ensureElementIsVisible(previousElement);
                }
              }
              this._suppressOnSelect = suppressSelect;
              // XXX actually it's just a refresh, but at least
              // the Extensions manager expects this:
              this._fireOnSelect();
              return;
            }

            // try to restore the selected items according to their IDs
            // (applies after a template rebuild, if last-selected was not set)
            if (this.selectedItems) {
              let itemIds = [];
              for (let i = this.selectedCount - 1; i >= 0; i--) {
                let selectedItem = this.selectedItems[i];
                itemIds.push(selectedItem.id);
                this.selectedItems.remove(selectedItem);
              }
              for (let i = 0; i < itemIds.length; i++) {
                let selectedItem = document.getElementById(itemIds[i]);
                if (selectedItem) {
                  this.selectedItems.append(selectedItem);
                }
              }
            }
            if (this.currentItem && this.currentItem.id)
              this.currentItem = document.getElementById(this.currentItem.id);
            else
              this.currentItem = null;

            // if we have no previously current item or if the above check fails to
            // find the previous nodes (which causes it to clear selection)
            if (!this.currentItem && this.selectedCount == 0) {
              this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;

              // cf. listbox constructor:
              // select items according to their attributes
              var children = this.children;
              for (let i = 0; i < children.length; ++i) {
                if (children[i].getAttribute("selected") == "true")
                  this.selectedItems.append(children[i]);
              }
            }

            if (this.selType != "multiple" && this.selectedCount == 0)
              this.selectedItem = this.currentItem;
          ]]>
        </body>
      </method>

      <method name="_isItemVisible">
        <parameter name="aItem"/>
        <body>
          <![CDATA[
            if (!aItem)
              return false;

            var y = this.scrollBoxObject.positionY + this.scrollBoxObject.y;

            // Partially visible items are also considered visible
            return (aItem.boxObject.y + aItem.boxObject.height > y) &&
                   (aItem.boxObject.y < y + this.scrollBoxObject.height);
          ]]>
        </body>
      </method>

      <field name="_currentIndex">null</field>

      <!-- For backwards-compatibility and for convenience.
        Use getIndexOfItem instead. -->
      <method name="getIndexOf">
        <parameter name="aElement"/>
        <body>
          <![CDATA[
            return this.getIndexOfItem(aElement);
          ]]>
        </body>
      </method>

      <!-- For backwards-compatibility and for convenience.
        Use ensureElementIsVisible instead -->
      <method name="ensureSelectedElementIsVisible">
        <body>
          <![CDATA[
            return this.ensureElementIsVisible(this.selectedItem);
          ]]>
        </body>
      </method>

      <!-- For backwards-compatibility and for convenience.
        Use moveByOffset instead. -->
      <method name="goUp">
        <body>
          <![CDATA[
            var index = this.currentIndex;
            this.moveByOffset(-1, true, false);
            return index != this.currentIndex;
          ]]>
        </body>
      </method>
      <method name="goDown">
        <body>
          <![CDATA[
            var index = this.currentIndex;
            this.moveByOffset(1, true, false);
            return index != this.currentIndex;
          ]]>
        </body>
      </method>

      <!-- deprecated (is implied by currentItem and selectItem) -->
      <method name="fireActiveItemEvent"><body/></method>
    </implementation>

    <handlers>
      <handler event="click">
        <![CDATA[
          // clicking into nothing should unselect
          if (event.originalTarget == this._scrollbox) {
            this.clearSelection();
            this.currentItem = null;
          }
        ]]>
      </handler>

      <handler event="MozSwipeGesture">
        <![CDATA[
          // Only handle swipe gestures up and down
          switch (event.direction) {
            case event.DIRECTION_DOWN:
              this._scrollbox.scrollTop = this._scrollbox.scrollHeight;
              break;
            case event.DIRECTION_UP:
              this._scrollbox.scrollTop = 0;
              break;
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="richlistitem"
           extends="chrome://global/content/bindings/listbox.xml#listitem">
    <content>
      <children/>
    </content>

    <resources>
      <stylesheet src="chrome://global/skin/richlistbox.css"/>
    </resources>

    <implementation>
      <field name="selectedByMouseOver">false</field>

      <destructor>
        <![CDATA[
          var control = this.control;
          if (!control)
            return;
          // When we are destructed and we are current or selected, unselect ourselves
          // so that richlistbox's selection doesn't point to something not in the DOM.
          // We don't want to reset last-selected, so we set _suppressOnSelect.
          if (this.selected) {
            var suppressSelect = control._suppressOnSelect;
            control._suppressOnSelect = true;
            control.removeItemFromSelection(this);
            control._suppressOnSelect = suppressSelect;
          }
          if (this.current)
            control.currentItem = null;
        ]]>
      </destructor>

      <property name="label" readonly="true">
        <!-- Setter purposely not implemented; the getter returns a
             concatentation of label text to expose via accessibility APIs -->
        <getter>
          <![CDATA[
            const XULNS =
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            return Array.map(this.getElementsByTagNameNS(XULNS, "label"),
                             label => label.value)
                        .join(" ");
          ]]>
        </getter>
      </property>

      <property name="searchLabel">
        <getter>
          <![CDATA[
            return this.hasAttribute("searchlabel") ?
                   this.getAttribute("searchlabel") : this.label;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (val !== null)
              this.setAttribute("searchlabel", val);
            else
              // fall back to the label property (default value)
              this.removeAttribute("searchlabel");
            return val;
          ]]>
        </setter>
      </property>
    </implementation>
  </binding>
</bindings>
PK
!<\1 1 0chrome/toolkit/content/global/bindings/scale.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="scaleBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
  
  <binding id="scalethumb" extends="xul:button" role="xul:thumb">
    <resources>
      <stylesheet src="chrome://global/skin/scale.css"/>
    </resources>
  </binding>

  <binding id="scaleslider" display="xul:slider"
           extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/scale.css"/>
    </resources>
  </binding>

  <binding id="scale" role="xul:scale"
           extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/scale.css"/>
    </resources>

    <content align="center" pack="center">
      <xul:slider anonid="slider" class="scale-slider" snap="true" flex="1"
                  xbl:inherits="disabled,orient,dir,curpos=value,minpos=min,maxpos=max,increment,pageincrement,movetoclick">
        <xul:thumb class="scale-thumb" xbl:inherits="disabled,orient"/>
      </xul:slider>
    </content>
    
    <implementation implements="nsISliderListener">
      <property name="value" onget="return this._getIntegerAttribute('curpos', 0);"
                             onset="return this._setIntegerAttribute('curpos', val);"/>
      <property name="min" onget="return this._getIntegerAttribute('minpos', 0);"
                           onset="return this._setIntegerAttribute('minpos', val);"/>
      <property name="max" onget="return this._getIntegerAttribute('maxpos', 100);"
                           onset="return this._setIntegerAttribute('maxpos', val);"/>
      <property name="increment" onget="return this._getIntegerAttribute('increment', 1);"
                                 onset="return this._setIntegerAttribute('increment', val);"/>
      <property name="pageIncrement" onget="return this._getIntegerAttribute('pageincrement', 10);"
                                     onset="return this._setIntegerAttribute('pageincrement', val);"/>

      <property name="_slider" readonly="true">
        <getter>
          if (!this._sliderElement)
            this._sliderElement = document.getAnonymousElementByAttribute(this, "anonid", "slider");
          return this._sliderElement;
        </getter>
      </property>

      <constructor>
        <![CDATA[
          this._userChanged = false;
          var value = parseInt(this.getAttribute("value"), 10);
          if (!isNaN(value))
            this.value = value;
          else if (this.min > 0)
            this.value = this.min;
          else if (this.max < 0)
            this.value = this.max;
        ]]>
      </constructor>

      <method name="_getIntegerAttribute">
        <parameter name="aAttr"/>
        <parameter name="aDefaultValue"/>
        <body>
          var value = this._slider.getAttribute(aAttr);
          var intvalue = parseInt(value, 10);
          if (!isNaN(intvalue))
            return intvalue;
          return aDefaultValue;
        </body>
      </method>

      <method name="_setIntegerAttribute">
        <parameter name="aAttr"/>
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var intvalue = parseInt(aValue, 10);
          if (!isNaN(intvalue)) {
            if (aAttr == "curpos") {
              if (intvalue < this.min)
                intvalue = this.min;
              else if (intvalue > this.max)
                intvalue = this.max;
            }
            this._slider.setAttribute(aAttr, intvalue);
          }
          return aValue;
        ]]>
        </body>
      </method>

      <method name="decrease">
        <body>
        <![CDATA[
          var newpos = this.value - this.increment;
          var startpos = this.min;
          this.value = (newpos > startpos) ? newpos : startpos;
        ]]>
        </body>
      </method>
      <method name="increase">
        <body>
        <![CDATA[
          var newpos = this.value + this.increment;
          var endpos = this.max;
          this.value = (newpos < endpos) ? newpos : endpos;
        ]]>
        </body>
      </method>

      <method name="decreasePage">
        <body>
        <![CDATA[
          var newpos = this.value - this.pageIncrement;
          var startpos = this.min;
          this.value = (newpos > startpos) ? newpos : startpos;
        ]]>
        </body>
      </method>
      <method name="increasePage">
        <body>
        <![CDATA[
          var newpos = this.value + this.pageIncrement;
          var endpos = this.max;
          this.value = (newpos < endpos) ? newpos : endpos;
        ]]>
        </body>
      </method>

      <method name="valueChanged">
        <parameter name="which"/>
        <parameter name="newValue"/>
        <parameter name="userChanged"/>
        <body>
        <![CDATA[
          switch (which) {
            case "curpos":
              this.setAttribute("value", newValue);

              // in the future, only fire the change event when userChanged
              // or _userChanged is true
              var changeEvent = document.createEvent("Events");
              changeEvent.initEvent("change", true, true);
              this.dispatchEvent(changeEvent);
              break;

            case "minpos":
              this.setAttribute("min", newValue);
              break;

            case "maxpos":
              this.setAttribute("max", newValue);
              break;
          }
        ]]>
        </body>
      </method>

      <method name="dragStateChanged">
        <parameter name="isDragging"/>
        <body/>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_LEFT" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient != "vertical" && this.dir == "reverse") ? this.increase() : this.decrease();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_RIGHT" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient != "vertical" && this.dir == "reverse") ? this.decrease() : this.increase();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_UP" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient == "vertical" && this.dir != "reverse") ? this.decrease() : this.increase();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_DOWN" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient == "vertical" && this.dir != "reverse") ? this.increase() : this.decrease();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient == "vertical" && this.dir != "reverse") ? this.decreasePage() : this.increasePage();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true">
        <![CDATA[
          this._userChanged = true;
          (this.orient == "vertical" && this.dir != "reverse") ? this.increasePage() : this.decreasePage();
          this._userChanged = false;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_HOME" preventdefault="true">
        this._userChanged = true;
        this.value = (this.dir == "reverse") ? this.max : this.min;
        this._userChanged = false;
      </handler>
      <handler event="keypress" keycode="VK_END" preventdefault="true">
        this._userChanged = true;
        this.value = (this.dir == "reverse") ? this.min : this.max;
        this._userChanged = false;
      </handler>
    </handlers>

  </binding>
</bindings>
PK
!<oo4chrome/toolkit/content/global/bindings/scrollbar.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="scrollbarBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
  
  <binding id="thumb" extends="xul:button" />

  <binding id="scrollbar-base" bindToUntrustedContent="true">
    <handlers>
      <handler event="contextmenu" preventdefault="true" action="event.stopPropagation();"/>
      <handler event="click" preventdefault="true" action="event.stopPropagation();"/>
      <handler event="dblclick" action="event.stopPropagation();"/>
      <handler event="command" action="event.stopPropagation();"/>
    </handlers>
  </binding>

  <binding id="scrollbar" bindToUntrustedContent="true" extends="chrome://global/content/bindings/scrollbar.xml#scrollbar-base">
    <content clickthrough="always">
      <xul:scrollbarbutton sbattr="scrollbar-up-top" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
      <xul:scrollbarbutton sbattr="scrollbar-down-top" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
      <xul:slider flex="1" xbl:inherits="disabled,curpos,maxpos,pageincrement,increment,orient,sborient=orient">
        <xul:thumb sbattr="scrollbar-thumb" xbl:inherits="orient,sborient=orient,collapsed=disabled" 
                   align="center" pack="center"/>
      </xul:slider>
      <xul:scrollbarbutton sbattr="scrollbar-up-bottom" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
      <xul:scrollbarbutton sbattr="scrollbar-down-bottom" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
    </content>
  </binding>
</bindings>
PK
!<ݠݠ4chrome/toolkit/content/global/bindings/scrollbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="arrowscrollboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/scrollbox.css"/>
    </resources>
  </binding>

  <binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
    <content>
      <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
        <children/>
      </xul:box>
    </content>

    <implementation>
      <method name="scrollByIndex">
        <parameter name="index"/>
        <body>
          this.boxObject.scrollByIndex(index);
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
    <content>
      <xul:autorepeatbutton class="autorepeatbutton-up"
                            anonid="scrollbutton-up"
                            xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
                            oncommand="_autorepeatbuttonScroll(event);"/>
      <xul:spacer class="arrowscrollbox-overflow-start-indicator"
                  xbl:inherits="collapsed=scrolledtostart"/>
      <xul:scrollbox class="arrowscrollbox-scrollbox"
                     anonid="scrollbox"
                     flex="1"
                     xbl:inherits="orient,align,pack,dir">
        <children/>
      </xul:scrollbox>
      <xul:spacer class="arrowscrollbox-overflow-end-indicator"
                  xbl:inherits="collapsed=scrolledtoend"/>
      <xul:autorepeatbutton class="autorepeatbutton-down"
                            anonid="scrollbutton-down"
                            xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
                            oncommand="_autorepeatbuttonScroll(event);"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        this.setAttribute("notoverflowing", "true");
        this._updateScrollButtonsDisabledState();
      ]]></constructor>

      <destructor><![CDATA[
        this._stopSmoothScroll();
      ]]></destructor>

      <field name="_scrollbox">
        document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
      </field>
      <field name="_scrollButtonUp">
        document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
      </field>
      <field name="_scrollButtonDown">
        document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
      </field>

      <field name="__prefBranch">null</field>
      <property name="_prefBranch" readonly="true">
        <getter><![CDATA[
          if (this.__prefBranch === null) {
            this.__prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
                                          .getService(Components.interfaces.nsIPrefBranch);
          }
          return this.__prefBranch;
        ]]></getter>
      </property>

      <field name="_scrollIncrement">null</field>
      <property name="scrollIncrement" readonly="true">
        <getter><![CDATA[
          if (this._scrollIncrement === null) {
            this._scrollIncrement = this._prefBranch
                                        .getIntPref("toolkit.scrollbox.scrollIncrement", 20);
          }
          return this._scrollIncrement;
        ]]></getter>
      </property>

      <field name="_smoothScroll">null</field>
      <property name="smoothScroll">
        <getter><![CDATA[
          if (this._smoothScroll === null) {
            if (this.hasAttribute("smoothscroll")) {
              this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
            } else {
              this._smoothScroll = this._prefBranch
                                       .getBoolPref("toolkit.scrollbox.smoothScroll", true);
            }
          }
          return this._smoothScroll;
        ]]></getter>
        <setter><![CDATA[
          this._smoothScroll = val;
          return val;
        ]]></setter>
      </property>

      <field name="_scrollBoxObject">null</field>
      <property name="scrollBoxObject" readonly="true">
        <getter><![CDATA[
          if (!this._scrollBoxObject) {
            this._scrollBoxObject = this._scrollbox.boxObject;
          }
          return this._scrollBoxObject;
        ]]></getter>
      </property>

      <property name="scrollClientRect" readonly="true">
        <getter><![CDATA[
          return this._scrollbox.getBoundingClientRect();
        ]]></getter>
      </property>

      <property name="scrollClientSize" readonly="true">
        <getter><![CDATA[
          return this.orient == "vertical" ?
                 this._scrollbox.clientHeight :
                 this._scrollbox.clientWidth;
        ]]></getter>
      </property>

      <property name="scrollSize" readonly="true">
        <getter><![CDATA[
          return this.orient == "vertical" ?
                 this._scrollbox.scrollHeight :
                 this._scrollbox.scrollWidth;
        ]]></getter>
      </property>

      <property name="lineScrollAmount" readonly="true">
        <getter><![CDATA[
          // line scroll amout should be the width (at horizontal scrollbox) or
          // the height (at vertical scrollbox) of the scrolled elements.
          // However, the elements may have different width or height.  So,
          // for consistent speed, let's use avalage with of the elements.
          var elements = this._getScrollableElements();
          if (!elements.length) {
            // Returning 0 shouldn't be problem because if there is no
            // scrollable elements, it's impossible to scroll anyway.
            return 0;
          }

          if (this._isRTLScrollbox)
            elements.reverse();

          var [start, end] = this._startEndProps;
          var low = 0;
          var high = elements.length - 1;
          // XXX If the total width is 0, do we need something more?
          var totalWidth =
            elements[high].getBoundingClientRect()[end] - elements[low].getBoundingClientRect()[start];
          return totalWidth / elements.length;
        ]]></getter>
      </property>

      <property name="scrollPaddingRect" readonly="true">
        <getter><![CDATA[
          // This assumes that this._scrollbox doesn't have any border.
          var outerRect = this.scrollClientRect;
          var innerRect = {};
          innerRect.left = outerRect.left - this._scrollbox.scrollLeft;
          innerRect.top = outerRect.top - this._scrollbox.scrollTop;
          innerRect.right = innerRect.left + this._scrollbox.scrollWidth;
          innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight;
          return innerRect;
        ]]></getter>
      </property>
      <field name="scrollboxPaddingStart"><![CDATA[
        this._isRTLScrollbox ? this.scrollboxPaddingRight : this.scrollboxPaddingLeft;
      ]]></field>
      <field name="scrollboxPaddingLeft"><![CDATA[
        parseFloat(window.getComputedStyle(this._scrollbox).paddingLeft);
      ]]></field>
      <field name="scrollboxPaddingRight"><![CDATA[
        parseFloat(window.getComputedStyle(this._scrollbox).paddingRight);
      ]]></field>
      <property name="scrollPosition">
        <getter><![CDATA[
          return this.orient == "vertical" ?
                 this._scrollbox.scrollTop :
                 this._scrollbox.scrollLeft;
        ]]></getter>
        <setter><![CDATA[
          if (this.orient == "vertical")
            this._scrollbox.scrollTop = val;
          else
            this._scrollbox.scrollLeft = val;
          return val;
        ]]></setter>
      </property>

      <field name="_startEndProps"><![CDATA[
        this.orient == "vertical" ? ["top", "bottom"] : ["left", "right"];
      ]]></field>

      <field name="_isRTLScrollbox"><![CDATA[
        this.orient != "vertical" &&
        document.defaultView.getComputedStyle(this._scrollbox).direction == "rtl";
      ]]></field>

      <field name="_scrollTarget">null</field>

      <method name="_boundsWithoutFlushing">
        <parameter name="element"/>
        <body><![CDATA[
          if (!("_DOMWindowUtils" in this)) {
            try {
              this._DOMWindowUtils =
                window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
            } catch (e) {
              // Can't access nsIDOMWindowUtils if we're unprivileged.
              this._DOMWindowUtils = null;
            }
          }

          return this._DOMWindowUtils ?
                 this._DOMWindowUtils.getBoundsWithoutFlushing(element) :
                 element.getBoundingClientRect();
        ]]></body>
      </method>

      <method name="_canScrollToElement">
        <parameter name="element"/>
        <body><![CDATA[
          if (element.hidden) {
            return false;
          }

          // See if the element is hidden via CSS without the hidden attribute.
          // If we get only zeros for the client rect, this means the element
          // is hidden. As a performance optimization, we don't flush layout
          // here which means that on the fly changes aren't fully supported.
          let rect = this._boundsWithoutFlushing(element);
          return !!(rect.top || rect.left || rect.width || rect.height);
        ]]></body>
      </method>

      <method name="ensureElementIsVisible">
        <parameter name="element"/>
        <parameter name="aSmoothScroll"/>
        <body><![CDATA[
          if (!this._canScrollToElement(element))
            return;

          var vertical = this.orient == "vertical";
          var rect = this.scrollClientRect;
          var containerStart = vertical ? rect.top : rect.left;
          var containerEnd = vertical ? rect.bottom : rect.right;
          rect = element.getBoundingClientRect();
          var elementStart = vertical ? rect.top : rect.left;
          var elementEnd = vertical ? rect.bottom : rect.right;

          var scrollPaddingRect = this.scrollPaddingRect;
          let style = window.getComputedStyle(this._scrollbox);
          var scrollContentRect = {
            left: scrollPaddingRect.left + parseFloat(style.paddingLeft),
            top: scrollPaddingRect.top + parseFloat(style.paddingTop),
            right: scrollPaddingRect.right - parseFloat(style.paddingRight),
            bottom: scrollPaddingRect.bottom - parseFloat(style.paddingBottom)
          };

          // Provide an entry point for derived bindings to adjust these values.
          if (this._adjustElementStartAndEnd) {
            [elementStart, elementEnd] =
              this._adjustElementStartAndEnd(element, elementStart, elementEnd);
          }

          if (elementStart <= (vertical ? scrollContentRect.top : scrollContentRect.left)) {
            elementStart = vertical ? scrollPaddingRect.top : scrollPaddingRect.left;
          }
          if (elementEnd >= (vertical ? scrollContentRect.bottom : scrollContentRect.right)) {
            elementEnd = vertical ? scrollPaddingRect.bottom : scrollPaddingRect.right;
          }

          var amountToScroll;

          if (elementStart < containerStart) {
            amountToScroll = elementStart - containerStart;
          } else if (containerEnd < elementEnd) {
            amountToScroll = elementEnd - containerEnd;
          } else if (this._isScrolling) {
            // decelerate if a currently-visible element is selected during the scroll
            const STOP_DISTANCE = 15;
            if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
              amountToScroll = elementStart - containerStart;
            else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
              amountToScroll = elementEnd - containerEnd;
            else
              amountToScroll = this._isScrolling * STOP_DISTANCE;
          } else {
            return;
          }

          this._stopSmoothScroll();

          if (aSmoothScroll != false && this.smoothScroll) {
            this._smoothScrollByPixels(amountToScroll, element);
          } else {
            this.scrollByPixels(amountToScroll);
          }
        ]]></body>
      </method>

      <method name="_smoothScrollByPixels">
        <parameter name="amountToScroll"/>
        <parameter name="element"/><!-- optional -->
        <body><![CDATA[
          if (amountToScroll == 0)
            return;

          // Shouldn't forget pending scroll amount if the scroll direction
          // isn't changed because this may be called high frequency with very
          // small pixel values.
          var scrollDirection = 0;
          if (amountToScroll) {
            // Positive amountToScroll makes us scroll right (elements fly left),
            // negative scrolls left.
            scrollDirection = amountToScroll < 0 ? -1 : 1;
          }

          // However, if the scroll direction is changed, let's cancel the
          // pending scroll because user must want to scroll from current
          // position.
          if (this._isScrolling && this._isScrolling != scrollDirection)
            this._stopSmoothScroll();

          this._scrollTarget = element;
          this._isScrolling = scrollDirection;

          this._scrollAnim.start(amountToScroll, !this._scrollTarget);
        ]]></body>
      </method>

      <field name="_scrollAnim"><![CDATA[({
        scrollbox: this,
        distance: 0.0,
        requestHandle: 0, /* 0 indicates there is no pending request */

        // Be aware, |distance| may be dounble. I.e., the absolute value of it can
        // be less than 1.  Set |isContinuousScroll| to true when the scroll may be
        // a part of continous scroll, for example, it's caused by turning mosue wheel.
        start: function scrollAnim_start(distance, isContinuousScroll) {
          // When it's a continous scroll and the scroll was started, this needs to
          // respect preceding scroll requests.  For example, 1.5px scroll occurs 2 times,
          // 3px should be scrolled.  So, fractional values shouldn't be discarded.
          if (isContinuousScroll && this.distance) {
            // |this.startPos| is integer due to cache of |.scrollPosition|.  Therefore,
            // we need to manage actual destination with |this.destination|.
            var oldDestination = this.destination;
            this.destination = this._clampPosition(this.destination + distance);

            // If scroll position has already reached the ends, we need to do nothing.
            if (oldDestination == this.destination)
              return;

            // If the integer part of the destination isn't changed, we need to do
            // nothing now, wait next event.
            if (Math.trunc(this.destination) == Math.trunc(this.destination - distance))
              return;

            // Let's restart animation from current position to the new destination.
            if (this.requestHandle) {
              this.stop();
              this.startPos = this.scrollbox.scrollPosition;
              // The call of |.stop()| causes clearing |this.distance| but let's recover it
              // for keeping continuous scroll.
              this.distance = this.destination - this.startPos;
            }
          } else {
            this.startPos = this.scrollbox.scrollPosition;
            this.destination = this._clampPosition(this.startPos + distance);
            this.distance = this.destination - this.startPos;

            // If absolute value of |this.distance| is less than 1px and this call is
            // start of a continous scroll, should wait to scroll until accumulated
            // scroll amount becomes 1px or greater.
            if (isContinuousScroll && Math.abs(this.distance) < 1)
              return;
          }
          this.duration = Math.min(1000, Math.round(50 * Math.sqrt(Math.abs(distance))));
          this.startTime = window.performance.now();

          if (!this.requestHandle)
            this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
        },

        stop: function scrollAnim_stop() {
          window.cancelAnimationFrame(this.requestHandle);
          this.requestHandle = 0;
          // Reset continouos scroll transaction at stopping the scroll animation.
          this.distance = 0;
        },

        sample: function scrollAnim_handleEvent(timeStamp) {
          // Note that timeStamp sometimes older than start time.  If we use
          // native value below, it causes scrolling revese direction.
          // So, if the timeStamp is older, let's treat it as same as the start time.
          const timePassed = Math.max(0, timeStamp - this.startTime);
          const pos = timePassed >= this.duration ? 1 :
                      1 - Math.pow(1 - timePassed / this.duration, 4);

          this.scrollbox.scrollPosition = this.startPos + (this.distance * pos);

          if (pos == 1)
            this.scrollbox._stopSmoothScroll();
          else
            this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
        },

        _clampPosition: function scrollAnim_clampPosition(aScrollPosition) {
          if (aScrollPosition < 0) {
            return 0;
          }
          var maxPos = this.scrollbox.scrollSize - this.scrollbox.scrollClientSize;
          if (aScrollPosition > maxPos) {
            return maxPos;
          }
          return aScrollPosition;
        }
      })]]></field>

      <method name="scrollByIndex">
        <parameter name="index"/>
        <parameter name="aSmoothScroll"/>
        <body><![CDATA[
          if (index == 0)
            return;

          // Each scrollByIndex call is expected to scroll the given number of
          // items. If a previous call is still in progress because of smooth
          // scrolling, we need to complete it before starting a new one.
          if (this._scrollTarget) {
            let elements = this._getScrollableElements();
            if (this._scrollTarget != elements[0] &&
                this._scrollTarget != elements[elements.length - 1])
              this.ensureElementIsVisible(this._scrollTarget, false);
          }

          var rect = this.scrollClientRect;
          var [start, end] = this._startEndProps;
          var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
          var nextElement = this._elementFromPoint(x, index);
          if (!nextElement)
            return;

          var targetElement;
          if (this._isRTLScrollbox)
            index *= -1;
          while (index < 0 && nextElement) {
            if (this._canScrollToElement(nextElement))
              targetElement = nextElement;
            nextElement = nextElement.previousSibling;
            index++;
          }
          while (index > 0 && nextElement) {
            if (this._canScrollToElement(nextElement))
              targetElement = nextElement;
            nextElement = nextElement.nextSibling;
            index--;
          }
          if (!targetElement)
            return;

          this.ensureElementIsVisible(targetElement, aSmoothScroll);
        ]]></body>
      </method>

      <method name="scrollByPage">
        <parameter name="pageDelta"/>
        <parameter name="aSmoothScroll"/>
        <body><![CDATA[
          if (pageDelta == 0)
            return;

          // If a previous call is still in progress because of smooth
          // scrolling, we need to complete it before starting a new one.
          if (this._scrollTarget) {
            let elements = this._getScrollableElements();
            if (this._scrollTarget != elements[0] &&
                this._scrollTarget != elements[elements.length - 1])
              this.ensureElementIsVisible(this._scrollTarget, false);
          }

          var [start, end] = this._startEndProps;
          var rect = this.scrollClientRect;
          var containerEdge = pageDelta > 0 ? rect[end] + 1 : rect[start] - 1;
          var pixelDelta = pageDelta * (rect[end] - rect[start]);
          var destinationPosition = containerEdge + pixelDelta;
          var nextElement = this._elementFromPoint(containerEdge, pageDelta);
          if (!nextElement)
            return;

          // We need to iterate over our elements in the direction of pageDelta.
          // pageDelta is the physical direction, so in a horizontal scroll box,
          // positive values scroll to the right no matter if the scrollbox is
          // LTR or RTL. But RTL changes how we need to advance the iteration
          // (whether to get the next or the previous sibling of the current
          // element).
          var logicalAdvanceDir = pageDelta * (this._isRTLScrollbox ? -1 : 1);
          var advance = logicalAdvanceDir > 0 ? (e => e.nextSibling) : (e => e.previousSibling);

          var extendsPastTarget = (pageDelta > 0)
            ? (e => e.getBoundingClientRect()[end] > destinationPosition)
            : (e => e.getBoundingClientRect()[start] < destinationPosition);

          // We want to scroll to the last element we encounter before we find
          // an element which extends past destinationPosition.
          var targetElement;
          do {
            if (this._canScrollToElement(nextElement))
              targetElement = nextElement;
            nextElement = advance(nextElement);
          } while (nextElement && !extendsPastTarget(nextElement));

          if (!targetElement)
            return;

          this.ensureElementIsVisible(targetElement, aSmoothScroll);
        ]]></body>
      </method>

      <method name="_getScrollableElements">
        <body><![CDATA[
          var nodes = this.childNodes;
          if (nodes.length == 1 &&
              nodes[0].localName == "children" &&
              nodes[0].namespaceURI == "http://www.mozilla.org/xbl") {
            nodes = document.getBindingParent(this).childNodes;
          }

          return Array.filter(nodes, this._canScrollToElement, this);
        ]]></body>
      </method>

      <method name="_elementFromPoint">
        <parameter name="aX"/>
        <parameter name="aPhysicalScrollDir"/>
        <body><![CDATA[
          var elements = this._getScrollableElements();
          if (!elements.length)
            return null;

          if (this._isRTLScrollbox)
            elements.reverse();

          var [start, end] = this._startEndProps;
          var low = 0;
          var high = elements.length - 1;

          if (aX < elements[low].getBoundingClientRect()[start] ||
              aX > elements[high].getBoundingClientRect()[end])
            return null;

          var mid, rect;
          while (low <= high) {
            mid = Math.floor((low + high) / 2);
            rect = elements[mid].getBoundingClientRect();
            if (rect[start] > aX)
              high = mid - 1;
            else if (rect[end] < aX)
              low = mid + 1;
            else
              return elements[mid];
          }

          // There's no element at the requested coordinate, but the algorithm
          // from above yields an element next to it, in a random direction.
          // The desired scrolling direction leads to the correct element.

          if (!aPhysicalScrollDir)
            return null;

          if (aPhysicalScrollDir < 0 && rect[start] > aX)
            mid = Math.max(mid - 1, 0);
          else if (aPhysicalScrollDir > 0 && rect[end] < aX)
            mid = Math.min(mid + 1, elements.length - 1);

          return elements[mid];
        ]]></body>
      </method>

      <method name="_autorepeatbuttonScroll">
        <parameter name="event"/>
        <body><![CDATA[
          var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
          if (this._isRTLScrollbox)
            dir *= -1;

          this.scrollByPixels(this.scrollIncrement * dir);

          event.stopPropagation();
        ]]></body>
      </method>

      <method name="scrollByPixels">
        <parameter name="px"/>
        <body><![CDATA[
          this.scrollPosition += px;
        ]]></body>
      </method>

      <!-- 0: idle
           1: scrolling right
          -1: scrolling left -->
      <field name="_isScrolling">0</field>
      <field name="_prevMouseScrolls">[null, null]</field>

      <field name="_touchStart">-1</field>

      <method name="_stopSmoothScroll">
        <body><![CDATA[
          if (this._isScrolling) {
            this._scrollAnim.stop();
            this._isScrolling = 0;
            this._scrollTarget = null;
          }
        ]]></body>
      </method>

      <field name="_scrollButtonUpdatePending">false</field>
      <method name="_updateScrollButtonsDisabledState">
        <body><![CDATA[
          if (this.hasAttribute("notoverflowing")) {
            this.setAttribute("scrolledtoend", "true");
            this.setAttribute("scrolledtostart", "true");
            return;
          }

          if (this._scrollButtonUpdatePending) {
            return;
          }
          this._scrollButtonUpdatePending = true;

          // Wait until after the next paint to get current layout data from
          // getBoundsWithoutFlushing.
          window.requestAnimationFrame(() => {
            setTimeout(() => {
              this._scrollButtonUpdatePending = false;

              let scrolledToStart = false;
              let scrolledToEnd = false;

              if (this.hasAttribute("notoverflowing")) {
                scrolledToStart = true;
                scrolledToEnd = true;
              } else {
                let scrollboxPaddingLeft = Math.round(this.scrollboxPaddingLeft);
                let scrollboxPaddingRight = Math.round(this.scrollboxPaddingRight);

                let [leftOrTop, rightOrBottom] = this._startEndProps;
                let leftOrTopEdge = ele => Math.round(this._boundsWithoutFlushing(ele)[leftOrTop]);
                let rightOrBottomEdge = ele => Math.round(this._boundsWithoutFlushing(ele)[rightOrBottom]);

                let elements = this._getScrollableElements();
                let [leftOrTopElement, rightOrBottomElement] = [elements[0], elements[elements.length - 1]];
                if (this._isRTLScrollbox) {
                  [leftOrTopElement, rightOrBottomElement] = [rightOrBottomElement, leftOrTopElement];
                }

                if (leftOrTopElement &&
                    leftOrTopEdge(leftOrTopElement) >= leftOrTopEdge(this._scrollbox) + scrollboxPaddingLeft) {
                  scrolledToStart = !this._isRTLScrollbox;
                  scrolledToEnd = this._isRTLScrollbox;
                } else if (rightOrBottomElement &&
                           rightOrBottomEdge(rightOrBottomElement) <= rightOrBottomEdge(this._scrollbox) - scrollboxPaddingRight) {
                  scrolledToStart = this._isRTLScrollbox;
                  scrolledToEnd = !this._isRTLScrollbox;
                }
              }

              if (scrolledToEnd) {
                this.setAttribute("scrolledtoend", "true");
              } else {
                this.removeAttribute("scrolledtoend");
              }

              if (scrolledToStart) {
                this.setAttribute("scrolledtostart", "true");
              } else {
                this.removeAttribute("scrolledtostart");
              }
            }, 0);
          });
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="wheel"><![CDATA[
        let doScroll = false;
        let useSmoothScroll = event.deltaMode != event.DOM_DELTA_PIXEL && this.smoothScroll;
        let scrollAmount = 0;
        if (this.orient == "vertical") {
          doScroll = true;
          if (event.deltaMode == event.DOM_DELTA_PIXEL)
            scrollAmount = event.deltaY;
          else if (event.deltaMode == event.DOM_DELTA_PAGE)
            scrollAmount = event.deltaY * this.scrollClientSize;
          else
            scrollAmount = event.deltaY * this.lineScrollAmount;
        } else {
          // We allow vertical scrolling to scroll a horizontal scrollbox
          // because many users have a vertical scroll wheel but no
          // horizontal support.
          // Because of this, we need to avoid scrolling chaos on trackpads
          // and mouse wheels that support simultaneous scrolling in both axes.
          // We do this by scrolling only when the last two scroll events were
          // on the same axis as the current scroll event.
          // For diagonal scroll events we only respect the dominant axis.
          let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
          let delta = isVertical ? event.deltaY : event.deltaX;
          let scrollByDelta = isVertical && this._isRTLScrollbox ? -delta : delta;

          if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
            doScroll = true;
            if (event.deltaMode == event.DOM_DELTA_PIXEL)
              scrollAmount = scrollByDelta;
            else if (event.deltaMode == event.DOM_DELTA_PAGE)
              scrollAmount = scrollByDelta * this.scrollClientSize;
            else
              scrollAmount = scrollByDelta * this.lineScrollAmount;
          }

          if (this._prevMouseScrolls.length > 1)
            this._prevMouseScrolls.shift();
          this._prevMouseScrolls.push(isVertical);
        }

        if (doScroll) {
          if (useSmoothScroll)
            this._smoothScrollByPixels(scrollAmount);
          else
            this.scrollByPixels(scrollAmount);
        }

        event.stopPropagation();
        event.preventDefault();
      ]]></handler>

      <handler event="touchstart"><![CDATA[
        if (event.touches.length > 1) {
          // Multiple touch points detected, abort. In particular this aborts
          // the panning gesture when the user puts a second finger down after
          // already panning with one finger. Aborting at this point prevents
          // the pan gesture from being resumed until all fingers are lifted
          // (as opposed to when the user is back down to one finger).
          this._touchStart = -1;
        } else {
          this._touchStart = (this.orient == "vertical"
                ? event.touches[0].screenY
                : event.touches[0].screenX);
        }
      ]]></handler>

      <handler event="touchmove"><![CDATA[
        if (event.touches.length == 1 &&
            this._touchStart >= 0) {
          var touchPoint = (this.orient == "vertical"
                ? event.touches[0].screenY
                : event.touches[0].screenX);
          var delta = this._touchStart - touchPoint;
          if (Math.abs(delta) > 0) {
            this.scrollByPixels(delta);
            this._touchStart = touchPoint;
          }
          event.preventDefault();
        }
      ]]></handler>

      <handler event="touchend"><![CDATA[
        this._touchStart = -1;
      ]]></handler>

      <handler event="underflow" phase="capturing"><![CDATA[
        // filter underflow events which were dispatched on nested scrollboxes
        if (event.target != this)
          return;

        // Ignore events that doesn't match our orientation.
        // Scrollport event orientation:
        //   0: vertical
        //   1: horizontal
        //   2: both
        if (this.orient == "vertical") {
          if (event.detail == 1)
            return;
        } else if (event.detail == 0) {
          // horizontal scrollbox
          return;
        }

        this.setAttribute("notoverflowing", "true");

        try {
          // See bug 341047 and comments in overflow handler as to why
          // try..catch is needed here
          this._updateScrollButtonsDisabledState();
        } catch (e) {
          this.removeAttribute("notoverflowing");
        }
      ]]></handler>

      <handler event="overflow" phase="capturing"><![CDATA[
        // filter underflow events which were dispatched on nested scrollboxes
        if (event.target != this)
          return;

        // Ignore events that doesn't match our orientation.
        // Scrollport event orientation:
        //   0: vertical
        //   1: horizontal
        //   2: both
        if (this.orient == "vertical") {
          if (event.detail == 1)
            return;
        } else if (event.detail == 0) {
          // horizontal scrollbox
          return;
        }

        this.removeAttribute("notoverflowing");

        try {
          // See bug 341047, the overflow event is dispatched when the
          // scrollbox already is mostly destroyed. This causes some code in
          // _updateScrollButtonsDisabledState() to throw an error. It also
          // means that the notoverflowing attribute was removed erroneously,
          // as the whole overflow event should not be happening in that case.
          this._updateScrollButtonsDisabledState();
        } catch (e) {
          this.setAttribute("notoverflowing", "true");
        }
      ]]></handler>

      <handler event="scroll"><![CDATA[
        this._updateScrollButtonsDisabledState();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
    <content repeat="hover">
      <xul:image class="autorepeatbutton-icon"/>
    </content>
  </binding>

  <binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
    <content>
      <xul:toolbarbutton class="scrollbutton-up"
                         xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
                         anonid="scrollbutton-up"
                         onclick="_distanceScroll(event);"
                         onmousedown="if (event.button == 0) _startScroll(-1);"
                         onmouseup="if (event.button == 0) _stopScroll();"
                         onmouseover="_continueScroll(-1);"
                         onmouseout="_pauseScroll();"/>
      <xul:spacer class="arrowscrollbox-overflow-start-indicator"
                  xbl:inherits="collapsed=scrolledtostart"/>
      <xul:scrollbox class="arrowscrollbox-scrollbox"
                     anonid="scrollbox"
                     flex="1"
                     xbl:inherits="orient,align,pack,dir">
        <children/>
      </xul:scrollbox>
      <xul:spacer class="arrowscrollbox-overflow-end-indicator"
                  xbl:inherits="collapsed=scrolledtoend"/>
      <xul:toolbarbutton class="scrollbutton-down"
                         xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
                         anonid="scrollbutton-down"
                         onclick="_distanceScroll(event);"
                         onmousedown="if (event.button == 0) _startScroll(1);"
                         onmouseup="if (event.button == 0) _stopScroll();"
                         onmouseover="_continueScroll(1);"
                         onmouseout="_pauseScroll();"/>
    </content>
    <implementation implements="nsITimerCallback, nsIDOMEventListener">
      <constructor><![CDATA[
        this._scrollDelay =
          this._prefBranch.getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay",
                                      this._scrollDelay);
      ]]></constructor>

      <destructor><![CDATA[
        // Release timer to avoid reference cycles.
        if (this._scrollTimer) {
          this._scrollTimer.cancel();
          this._scrollTimer = null;
        }
      ]]></destructor>

      <field name="_scrollIndex">0</field>
      <field name="_scrollDelay">150</field>

      <method name="notify">
        <parameter name="aTimer"/>
        <body>
        <![CDATA[
          if (!document)
            aTimer.cancel();

          this.scrollByIndex(this._scrollIndex);
        ]]>
        </body>
      </method>

      <field name="_arrowScrollAnim"><![CDATA[({
        scrollbox: this,
        requestHandle: 0, /* 0 indicates there is no pending request */
        start: function arrowSmoothScroll_start() {
          this.lastFrameTime = window.performance.now();
          if (!this.requestHandle)
            this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
        },
        stop: function arrowSmoothScroll_stop() {
          window.cancelAnimationFrame(this.requestHandle);
          this.requestHandle = 0;
        },
        sample: function arrowSmoothScroll_handleEvent(timeStamp) {
          const scrollIndex = this.scrollbox._scrollIndex;
          const timePassed = timeStamp - this.lastFrameTime;
          this.lastFrameTime = timeStamp;

          const scrollDelta = 0.5 * timePassed * scrollIndex;
          this.scrollbox.scrollPosition += scrollDelta;

          this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
        }
      })]]></field>

      <method name="_startScroll">
        <parameter name="index"/>
        <body><![CDATA[
          if (this._isRTLScrollbox)
            index *= -1;
          this._scrollIndex = index;
          this._mousedown = true;
          if (this.smoothScroll) {
            this._arrowScrollAnim.start();
            return;
          }

          if (!this._scrollTimer)
            this._scrollTimer =
              Components.classes["@mozilla.org/timer;1"]
                        .createInstance(Components.interfaces.nsITimer);
          else
            this._scrollTimer.cancel();

          this._scrollTimer.initWithCallback(this, this._scrollDelay,
                                             this._scrollTimer.TYPE_REPEATING_SLACK);
          this.notify(this._scrollTimer);
        ]]>
        </body>
      </method>

      <method name="_stopScroll">
        <body><![CDATA[
          if (this._scrollTimer)
            this._scrollTimer.cancel();
          this._mousedown = false;
          if (!this._scrollIndex || !this.smoothScroll)
            return;

          this.scrollByIndex(this._scrollIndex);
          this._scrollIndex = 0;
          this._arrowScrollAnim.stop();
        ]]></body>
      </method>

      <method name="_pauseScroll">
        <body><![CDATA[
          if (this._mousedown) {
            this._stopScroll();
            this._mousedown = true;
            document.addEventListener("mouseup", this);
            document.addEventListener("blur", this, true);
          }
        ]]></body>
      </method>

      <method name="_continueScroll">
        <parameter name="index"/>
        <body><![CDATA[
          if (this._mousedown)
            this._startScroll(index);
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (aEvent.type == "mouseup" ||
              aEvent.type == "blur" && aEvent.target == document) {
            this._mousedown = false;
            document.removeEventListener("mouseup", this);
            document.removeEventListener("blur", this, true);
          }
        ]]></body>
      </method>

      <method name="_distanceScroll">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (aEvent.detail < 2 || aEvent.detail > 3)
            return;

          var scrollBack = (aEvent.originalTarget == this._scrollButtonUp);
          var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack;
          var targetElement;

          if (aEvent.detail == 2) {
            // scroll by the size of the scrollbox
            let [start, end] = this._startEndProps;
            let x;
            if (scrollLeftOrUp)
              x = this.scrollClientRect[start] - this.scrollClientSize;
            else
              x = this.scrollClientRect[end] + this.scrollClientSize;
            targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);

            // the next partly-hidden element will become fully visible,
            // so don't scroll too far
            if (targetElement)
              targetElement = scrollBack ?
                              targetElement.nextSibling :
                              targetElement.previousSibling;
          }

          if (!targetElement) {
            // scroll to the first resp. last element
            let elements = this._getScrollableElements();
            targetElement = scrollBack ?
                            elements[0] :
                            elements[elements.length - 1];
          }

          this.ensureElementIsVisible(targetElement);
        ]]></body>
      </method>

    </implementation>
  </binding>
</bindings>
PK
!<6chrome/toolkit/content/global/bindings/spinbuttons.xml<?xml version="1.0" encoding="UTF-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="spinbuttonsBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="spinbuttons"
           extends="chrome://global/content/bindings/general.xml#basecontrol">

    <resources>
      <stylesheet src="chrome://global/skin/spinbuttons.css"/>
    </resources>

    <content>
      <xul:vbox class="spinbuttons-box" flex="1">
        <xul:button anonid="increaseButton" type="repeat" flex="1"
                    class="spinbuttons-button spinbuttons-up"
                    xbl:inherits="disabled,disabled=increasedisabled"/>
        <xul:button anonid="decreaseButton" type="repeat" flex="1"
                    class="spinbuttons-button spinbuttons-down"
                    xbl:inherits="disabled,disabled=decreasedisabled"/>
      </xul:vbox>
    </content>

    <implementation>
      <property name="_increaseButton" readonly="true">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "increaseButton");
        </getter>
      </property>
      <property name="_decreaseButton" readonly="true">
        <getter>
          return document.getAnonymousElementByAttribute(this, "anonid", "decreaseButton");
        </getter>
      </property>

      <property name="increaseDisabled"
                onget="return this._increaseButton.getAttribute('disabled') == 'true';"
                onset="if (val) this._increaseButton.setAttribute('disabled', 'true');
                       else this._increaseButton.removeAttribute('disabled'); return val;"/>
      <property name="decreaseDisabled"
                onget="return this._decreaseButton.getAttribute('disabled') == 'true';"
                onset="if (val) this._decreaseButton.setAttribute('disabled', 'true');
                       else this._decreaseButton.removeAttribute('disabled'); return val;"/>
    </implementation>

    <handlers>
      <handler event="mousedown">
        <![CDATA[
          // on the Mac, the native theme draws the spinbutton as a single widget
          // so a state attribute is set based on where the mouse button was pressed
          if (event.originalTarget == this._increaseButton)
            this.setAttribute("state", "up");
          else if (event.originalTarget == this._decreaseButton)
            this.setAttribute("state", "down");
        ]]>
      </handler>

      <handler event="mouseup">
        this.removeAttribute("state");
      </handler>
      <handler event="mouseout">
        this.removeAttribute("state");
      </handler>

      <handler event="command">
        <![CDATA[
          var eventname;
          if (event.originalTarget == this._increaseButton)
            eventname = "up";
          else if (event.originalTarget == this._decreaseButton)
            eventname = "down";

          var evt = document.createEvent("Events");
          evt.initEvent(eventname, true, true);
          var cancel = this.dispatchEvent(evt);

          if (this.hasAttribute("on" + eventname)) {
            var fn = new Function("event", this.getAttribute("on" + eventname));
            if (fn.call(this, event) == false)
              cancel = true;
          }

          return !cancel;
        ]]>
      </handler>

    </handlers>
  </binding>

</bindings>
PK
!<B	GG1chrome/toolkit/content/global/bindings/spinner.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/*
 * The spinner is responsible for displaying the items, and does
 * not care what the values represent. The setValue function is called
 * when it detects a change in value triggered by scroll event.
 * Supports scrolling, clicking on up or down, clicking on item, and
 * dragging.
 */

function Spinner(props, context) {
  this.context = context;
  this._init(props);
}

{

  const ITEM_HEIGHT = 2.5,
        VIEWPORT_SIZE = 7,
        VIEWPORT_COUNT = 5,
        SCROLL_TIMEOUT = 100;

  Spinner.prototype = {
    /**
     * Initializes a spinner. Set the default states and properties, cache
     * element references, create the HTML markup, and add event listeners.
     *
     * @param  {Object} props [Properties passed in from parent]
     *         {
     *           {Function} setValue: Takes a value and set the state to
     *             the parent component.
     *           {Function} getDisplayString: Takes a value, and output it
     *             as localized strings.
     *           {Number} viewportSize [optional]: Number of items in a
     *             viewport.
     *           {Boolean} hideButtons [optional]: Hide up & down buttons
     *           {Number} rootFontSize [optional]: Used to support zoom in/out
     *         }
     */
    _init(props) {
      const { id, setValue, getDisplayString, hideButtons, rootFontSize = 10 } = props;

      const spinnerTemplate = document.getElementById("spinner-template");
      const spinnerElement = document.importNode(spinnerTemplate.content, true);

      // Make sure viewportSize is an odd number because we want to have the selected
      // item in the center. If it's an even number, use the default size instead.
      const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;

      this.state = {
        items: [],
        isScrolling: false
      };
      this.props = {
        setValue, getDisplayString, viewportSize, rootFontSize,
        // We can assume that the viewportSize is an odd number. Calculate how many
        // items we need to insert on top of the spinner so that the selected is at
        // the center. Ex: if viewportSize is 5, we need 2 items on top.
        viewportTopOffset: (viewportSize - 1) / 2
      };
      this.elements = {
        container: spinnerElement.querySelector(".spinner-container"),
        spinner: spinnerElement.querySelector(".spinner"),
        up: spinnerElement.querySelector(".up"),
        down: spinnerElement.querySelector(".down"),
        itemsViewElements: []
      };

      this.elements.spinner.style.height = (ITEM_HEIGHT * viewportSize) + "rem";

      if (id) {
        this.elements.container.id = id;
      }
      if (hideButtons) {
        this.elements.container.classList.add("hide-buttons");
      }

      this.context.appendChild(spinnerElement);
      this._attachEventListeners();
    },

    /**
     * Only the parent component calls setState on the spinner.
     * It checks if the items have changed and updates the spinner.
     * If only the value has changed, smooth scrolls to the new value.
     *
     * @param {Object} newState [The new spinner state]
     *        {
     *          {Number/String} value: The centered value
     *          {Array} items: The list of items for display
     *          {Boolean} isInfiniteScroll: Whether or not the spinner should
     *            have infinite scroll capability
     *          {Boolean} isValueSet: true if user has selected a value
     *        }
     */
    setState(newState) {
      const { value, items } = this.state;
      const { value: newValue, items: newItems, isValueSet, isInvalid, smoothScroll = true } = newState;

      if (this._isArrayDiff(newItems, items)) {
        this.state = Object.assign(this.state, newState);
        this._updateItems();
        this._scrollTo(newValue, true);
      } else if (newValue != value) {
        this.state = Object.assign(this.state, newState);
        if (smoothScroll) {
          this._smoothScrollTo(newValue, true);
        } else {
          this._scrollTo(newValue, true);
        }
      }

      if (isValueSet && !isInvalid) {
        this._updateSelection();
      } else {
        this._removeSelection();
      }
    },

    /**
     * Whenever scroll event is detected:
     * - Update the index state
     * - If a smooth scroll has reached its destination, set [isScrolling] state
     *   to false
     * - If the value has changed, update the [value] state and call [setValue]
     * - If infinite scrolling is on, reset the scrolling position if necessary
     */
    _onScroll() {
      const { items, itemsView, isInfiniteScroll } = this.state;
      const { viewportSize, viewportTopOffset } = this.props;
      const { spinner } = this.elements;

      this.state.index = this._getIndexByOffset(spinner.scrollTop);

      const value = itemsView[this.state.index + viewportTopOffset].value;

      // Check if smooth scrolling has reached its destination.
      // This prevents input box jump when input box changes values.
      if (this.state.value == value && this.state.isScrolling) {
        this.state.isScrolling = false;
      }

      // Call setValue if value has changed, and is not smooth scrolling
      if (this.state.value != value && !this.state.isScrolling) {
        this.state.value = value;
        this.props.setValue(value);
      }

      // Do infinite scroll when items length is bigger or equal to viewport
      // and isInfiniteScroll is not false.
      if (items.length >= viewportSize && isInfiniteScroll) {
        // If the scroll position is near the top or bottom, jump back to the middle
        // so user can keep scrolling up or down.
        if (this.state.index < viewportSize ||
            this.state.index > itemsView.length - viewportSize) {
          this._scrollTo(this.state.value, true);
        }
      }

      // Use a timer to detect if a scroll event has not fired within some time
      // (defined in SCROLL_TIMEOUT). This is required because we need to hide
      // highlight and hover state when user is scrolling.
      clearTimeout(this.state.scrollTimer);
      this.elements.spinner.classList.add("scrolling");
      this.state.scrollTimer = setTimeout(() => {
        this.elements.spinner.classList.remove("scrolling");
        this.elements.spinner.dispatchEvent(new CustomEvent("ScrollStop"));
      }, SCROLL_TIMEOUT);
    },

    /**
     * Updates the spinner items to the current states.
     */
    _updateItems() {
      const { viewportSize, viewportTopOffset } = this.props;
      const { items, isInfiniteScroll } = this.state;

      // Prepends null elements so the selected value is centered in spinner
      let itemsView = new Array(viewportTopOffset).fill({}).concat(items);

      if (items.length >= viewportSize && isInfiniteScroll) {
        // To achieve infinite scroll, we move the scroll position back to the
        // center when it is near the top or bottom. The scroll momentum could
        // be lost in the process, so to minimize that, we need at least 2 sets
        // of items to act as buffer: one for the top and one for the bottom.
        // But if the number of items is small ( < viewportSize * viewport count)
        // we should add more sets.
        let count = Math.ceil(viewportSize * VIEWPORT_COUNT / items.length) * 2;
        for (let i = 0; i < count; i += 1) {
          itemsView.push(...items);
        }
      }

      // Reuse existing DOM nodes when possible. Create or remove
      // nodes based on how big itemsView is.
      this._prepareNodes(itemsView.length, this.elements.spinner);
      // Once DOM nodes are ready, set display strings using textContent
      this._setDisplayStringAndClass(itemsView, this.elements.itemsViewElements);

      this.state.itemsView = itemsView;
    },

    /**
     * Make sure the number or child elements is the same as length
     * and keep the elements' references for updating textContent
     *
     * @param {Number} length [The number of child elements]
     * @param {DOMElement} parent [The parent element reference]
     */
    _prepareNodes(length, parent) {
      const diff = length - parent.childElementCount;

      if (!diff) {
        return;
      }

      if (diff > 0) {
        // Add more elements if length is greater than current
        let frag = document.createDocumentFragment();

        // Remove margin bottom on the last element before appending
        if (parent.lastChild) {
          parent.lastChild.style.marginBottom = "";
        }

        for (let i = 0; i < diff; i++) {
          let el = document.createElement("div");
          frag.appendChild(el);
          this.elements.itemsViewElements.push(el);
        }
        parent.appendChild(frag);
      } else if (diff < 0) {
        // Remove elements if length is less than current
        for (let i = 0; i < Math.abs(diff); i++) {
          parent.removeChild(parent.lastChild);
        }
        this.elements.itemsViewElements.splice(diff);
      }

      parent.lastChild.style.marginBottom =
        (ITEM_HEIGHT * this.props.viewportTopOffset) + "rem";
    },

    /**
     * Set the display string and class name to the elements.
     *
     * @param {Array<Object>} items
     *        [{
     *          {Number/String} value: The value in its original form
     *          {Boolean} enabled: Whether or not the item is enabled
     *        }]
     * @param {Array<DOMElement>} elements
     */
    _setDisplayStringAndClass(items, elements) {
      const { getDisplayString } = this.props;

      items.forEach((item, index) => {
        elements[index].textContent =
          item.value != undefined ? getDisplayString(item.value) : "";
        elements[index].className = item.enabled ? "" : "disabled";
      });
    },

    /**
     * Attach event listeners to the spinner and buttons.
     */
    _attachEventListeners() {
      const { spinner, container } = this.elements;

      spinner.addEventListener("scroll", this, { passive: true });
      container.addEventListener("mouseup", this, { passive: true });
      container.addEventListener("mousedown", this, { passive: true });
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      const { mouseState = {}, index, itemsView } = this.state;
      const { viewportTopOffset, setValue } = this.props;
      const { spinner, up, down } = this.elements;

      switch (event.type) {
        case "scroll": {
          this._onScroll();
          break;
        }
        case "mousedown": {
          this.state.mouseState = {
            down: true,
            layerX: event.layerX,
            layerY: event.layerY
          };
          if (event.target == up) {
            // An "active" class is needed to simulate :active pseudo-class
            // because element is not focused.
            event.target.classList.add("active");
            this._smoothScrollToIndex(index - 1);
          }
          if (event.target == down) {
            event.target.classList.add("active");
            this._smoothScrollToIndex(index + 1);
          }
          if (event.target.parentNode == spinner) {
            // Listen to dragging events
            spinner.addEventListener("mousemove", this, { passive: true });
            spinner.addEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mouseup": {
          this.state.mouseState.down = false;
          if (event.target == up || event.target == down) {
            event.target.classList.remove("active");
          }
          if (event.target.parentNode == spinner) {
            // Check if user clicks or drags, scroll to the item if clicked,
            // otherwise get the current index and smooth scroll there.
            if (event.layerX == mouseState.layerX && event.layerY == mouseState.layerY) {
              const newIndex = this._getIndexByOffset(event.target.offsetTop) - viewportTopOffset;
              if (index == newIndex) {
                // Set value manually if the clicked element is already centered.
                // This happens when the picker first opens, and user pick the
                // default value.
                setValue(itemsView[index + viewportTopOffset].value);
              } else {
                this._smoothScrollToIndex(newIndex);
              }
            } else {
              this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
            }
            // Stop listening to dragging
            spinner.removeEventListener("mousemove", this, { passive: true });
            spinner.removeEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mouseleave": {
          if (event.target == spinner) {
            // Stop listening to drag event if mouse is out of the spinner
            this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
            spinner.removeEventListener("mousemove", this, { passive: true });
            spinner.removeEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mousemove": {
          // Change spinner position on drag
          spinner.scrollTop -= event.movementY;
          break;
        }
      }
    },

    /**
     * Find the index by offset
     * @param {Number} offset: Offset value in pixel.
     * @return {Number}  Index number
     */
    _getIndexByOffset(offset) {
      return Math.round(offset / (ITEM_HEIGHT * this.props.rootFontSize));
    },

    /**
     * Find the index of a value that is the closest to the current position.
     * If centering is true, find the index closest to the center.
     *
     * @param {Number/String} value: The value to find
     * @param {Boolean} centering: Whether or not to find the value closest to center
     * @return {Number} index of the value, returns -1 if value is not found
     */
    _getScrollIndex(value, centering) {
      const { itemsView } = this.state;
      const { viewportTopOffset } = this.props;

      // If index doesn't exist, or centering is true, start from the middle point
      let currentIndex = centering || (this.state.index == undefined) ?
                         Math.round((itemsView.length - viewportTopOffset) / 2) :
                         this.state.index;
      let closestIndex = itemsView.length;
      let indexes = [];
      let diff = closestIndex;
      let isValueFound = false;

      // Find indexes of items match the value
      itemsView.forEach((item, index) => {
        if (item.value == value) {
          indexes.push(index);
        }
      });

      // Find the index closest to currentIndex
      indexes.forEach(index => {
        let d = Math.abs(index - currentIndex);
        if (d < diff) {
          diff = d;
          closestIndex = index;
          isValueFound = true;
        }
      });

      return isValueFound ? (closestIndex - viewportTopOffset) : -1;
    },

    /**
     * Scroll to a value.
     *
     * @param  {Number/String} value: Value to scroll to
     * @param  {Boolean} centering: Whether or not to scroll to center location
     */
    _scrollTo(value, centering) {
      const index = this._getScrollIndex(value, centering);
      // Do nothing if the value is not found
      if (index > -1) {
        this.state.index = index;
        this.elements.spinner.scrollTop = this.state.index * ITEM_HEIGHT * this.props.rootFontSize;
      }
    },

    /**
     * Smooth scroll to a value.
     *
     * @param  {Number/String} value: Value to scroll to
     */
    _smoothScrollTo(value) {
      const index = this._getScrollIndex(value);
      // Do nothing if the value is not found
      if (index > -1) {
        this.state.index = index;
        this._smoothScrollToIndex(this.state.index);
      }
    },

    /**
     * Smooth scroll to a value based on the index
     *
     * @param  {Number} index: Index number
     */
    _smoothScrollToIndex(index) {
      const element = this.elements.spinner.children[index];
      if (element) {
        // Set the isScrolling flag before smooth scrolling begins
        // and remove it when it has reached the destination.
        // This prevents input box jump when input box changes values
        this.state.isScrolling = true;
        element.scrollIntoView({
          behavior: "smooth", block: "start"
        });
      }
    },

    /**
     * Update the selection state.
     */
    _updateSelection() {
      const { itemsViewElements, selected } = this.elements;
      const { itemsView, index } = this.state;
      const { viewportTopOffset } = this.props;
      const currentItemIndex = index + viewportTopOffset;

      if (selected && selected != itemsViewElements[currentItemIndex]) {
        this._removeSelection();
      }

      this.elements.selected = itemsViewElements[currentItemIndex];
      if (itemsView[currentItemIndex] && itemsView[currentItemIndex].enabled) {
        this.elements.selected.classList.add("selection");
      }
    },

    /**
     * Remove selection if selected exists and different from current
     */
    _removeSelection() {
      const { selected } = this.elements;
      if (selected) {
        selected.classList.remove("selection");
      }
    },

    /**
     * Compares arrays of objects. It assumes the structure is an array of
     * objects, and objects in a and b have the same number of properties.
     *
     * @param  {Array<Object>} a
     * @param  {Array<Object>} b
     * @return {Boolean}  Returns true if a and b are different
     */
    _isArrayDiff(a, b) {
      // Check reference first, exit early if reference is the same.
      if (a == b) {
        return false;
      }

      if (a.length != b.length) {
        return true;
      }

      for (let i = 0; i < a.length; i++) {
        for (let prop in a[i]) {
          if (a[i][prop] != b[i][prop]) {
            return true;
          }
        }
      }
      return false;
    }
  };
}
PK
!<I__3chrome/toolkit/content/global/bindings/splitter.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="splitterBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="splitter" extends="xul:splitter">
    <resources>
      <stylesheet src="chrome://global/skin/splitter.css"/>
    </resources>
  </binding>

  <binding id="grippy" extends="xul:button">
    <resources>
      <stylesheet src="chrome://global/skin/splitter.css"/>
    </resources>
    <handlers>
      <handler event="command">
        <![CDATA[
          var splitter = this.parentNode;
          if (splitter) {
            var state = splitter.getAttribute("state");
            if (state == "collapsed")
              splitter.setAttribute("state", "open");
            else
              splitter.setAttribute("state", "collapsed");
          }
        ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
PK
!<$J

7chrome/toolkit/content/global/bindings/stringbundle.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="stringBundleBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="stringbundleset" extends="xul:box"/>

  <binding id="stringbundle" extends="xul:spacer">
    <implementation name="XStringBundle">

      <method name="getString">
        <parameter name="aStringKey"/>
        <body>
          <![CDATA[
            try {
              return this.stringBundle.GetStringFromName(aStringKey);
            } catch (e) {
              dump("*** Failed to get string " + aStringKey + " in bundle: " + this.src + "\n");
              throw e;
            }
          ]]>
        </body>
      </method>

      <method name="getFormattedString">
        <parameter name="aStringKey"/>
        <parameter name="aStringsArray"/>
        <body>
          <![CDATA[
            try {
              return this.stringBundle.formatStringFromName(aStringKey, aStringsArray, aStringsArray.length);
            } catch (e) {
              dump("*** Failed to format string " + aStringKey + " in bundle: " + this.src + "\n");
              throw e;
            }
          ]]>
        </body>
      </method>

      <property name="stringBundle" readonly="true">
        <getter>
          <![CDATA[
            if (!this._bundle) {
              try {
                this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.nsIStringBundleService)
                                         .createBundle(this.src);
              } catch (e) {
                dump("Failed to get stringbundle:\n");
                dump(e + "\n");
              }
            }
            return this._bundle;
          ]]>
        </getter>
      </property>

      <property name="src">
        <getter>
          <![CDATA[
            return this.getAttribute("src");
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this._bundle = null;
            this.setAttribute("src", val);
            return val;
          ]]>
        </setter>
      </property>

      <property name="strings">
        <getter>
          <![CDATA[
            // Note: this is a sucky method name! Should be:
            //       readonly attribute nsISimpleEnumerator strings;
            return this.stringBundle.getSimpleEnumeration();
          ]]>
        </getter>
      </property>

      <field name="_bundle">null</field>

    </implementation>
  </binding>

</bindings>
PK
!<wooo1chrome/toolkit/content/global/bindings/tabbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="tabBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tab-base">
    <resources>
      <stylesheet src="chrome://global/skin/tabbox.css"/>
    </resources>
  </binding>

  <binding id="tabbox"
           extends="chrome://global/content/bindings/tabbox.xml#tab-base">
    <implementation implements="nsIDOMEventListener">
      <property name="handleCtrlTab">
        <setter>
        <![CDATA[
          this.setAttribute("handleCtrlTab", val);
          return val;
        ]]>
        </setter>
        <getter>
        <![CDATA[
          return (this.getAttribute("handleCtrlTab") != "false");
        ]]>
        </getter>
      </property>

      <property name="handleCtrlPageUpDown">
        <setter>
        <![CDATA[
          this.setAttribute("handleCtrlPageUpDown", val);
          return val;
        ]]>
        </setter>
        <getter>
        <![CDATA[
          return (this.getAttribute("handleCtrlPageUpDown") != "false");
        ]]>
        </getter>
      </property>

      <field name="_handleMetaAltArrows" readonly="true">
        /Mac/.test(navigator.platform)
      </field>

      <!-- _tabs and _tabpanels are deprecated, they exist only for
           backwards compatibility. -->
      <property name="_tabs" readonly="true" onget="return this.tabs;"/>
      <property name="_tabpanels" readonly="true" onget="return this.tabpanels;"/>

      <property name="tabs" readonly="true">
        <getter>
        <![CDATA[
          return this.getElementsByTagNameNS(
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
              "tabs").item(0);
        ]]>
        </getter>
      </property>

      <property name="tabpanels" readonly="true">
        <getter>
        <![CDATA[
          return this.getElementsByTagNameNS(
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
              "tabpanels").item(0);
        ]]>
        </getter>
      </property>

      <property name="selectedIndex">
        <getter>
        <![CDATA[
          var tabs = this.tabs;
          return tabs ? tabs.selectedIndex : -1;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          var tabs = this.tabs;
          if (tabs)
            tabs.selectedIndex = val;
          this.setAttribute("selectedIndex", val);
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedTab">
        <getter>
        <![CDATA[
          var tabs = this.tabs;
          return tabs && tabs.selectedItem;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          if (val) {
            var tabs = this.tabs;
            if (tabs)
              tabs.selectedItem = val;
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedPanel">
        <getter>
        <![CDATA[
          var tabpanels = this.tabpanels;
          return tabpanels && tabpanels.selectedPanel;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          if (val) {
            var tabpanels = this.tabpanels;
            if (tabpanels)
              tabpanels.selectedPanel = val;
          }
          return val;
        ]]>
        </setter>
      </property>

      <method name="handleEvent">
        <parameter name="event"/>
        <body>
        <![CDATA[
          if (!event.isTrusted) {
            // Don't let untrusted events mess with tabs.
            return;
          }

          // Don't check if the event was already consumed because tab
          // navigation should always work for better user experience.

          switch (event.keyCode) {
            case event.DOM_VK_TAB:
              if (event.ctrlKey && !event.altKey && !event.metaKey)
                if (this.tabs && this.handleCtrlTab) {
                  this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
                  event.preventDefault();
                }
              break;
            case event.DOM_VK_PAGE_UP:
              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
                if (this.tabs && this.handleCtrlPageUpDown) {
                  this.tabs.advanceSelectedTab(-1, true);
                  event.preventDefault();
                }
              break;
            case event.DOM_VK_PAGE_DOWN:
              if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
                if (this.tabs && this.handleCtrlPageUpDown) {
                  this.tabs.advanceSelectedTab(1, true);
                  event.preventDefault();
                }
              break;
            case event.DOM_VK_LEFT:
              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
                if (this.tabs && this._handleMetaAltArrows) {
                  var offset = window.getComputedStyle(this)
                                     .direction == "ltr" ? -1 : 1;
                  this.tabs.advanceSelectedTab(offset, true);
                  event.preventDefault();
                }
              break;
            case event.DOM_VK_RIGHT:
              if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
                if (this.tabs && this._handleMetaAltArrows) {
                  offset = window.getComputedStyle(this)
                                     .direction == "ltr" ? 1 : -1;
                  this.tabs.advanceSelectedTab(offset, true);
                  event.preventDefault();
                }
              break;
          }
        ]]>
        </body>
      </method>

      <field name="_eventNode">this</field>

      <property name="eventNode" onget="return this._eventNode;">
        <setter>
          <![CDATA[
            if (val != this._eventNode) {
              const nsIEventListenerService =
                Components.interfaces.nsIEventListenerService;
              let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                                  .getService(nsIEventListenerService);
              els.addSystemEventListener(val, "keydown", this, false);
              els.removeSystemEventListener(this._eventNode, "keydown", this, false);
              this._eventNode = val;
            }
            return val;
          ]]>
        </setter>
      </property>

      <constructor>
        switch (this.getAttribute("eventnode")) {
          case "parent": this._eventNode = this.parentNode; break;
          case "window": this._eventNode = window; break;
          case "document": this._eventNode = document; break;
        }
        const nsIEventListenerService =
          Components.interfaces.nsIEventListenerService;
        let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                            .getService(nsIEventListenerService);
        els.addSystemEventListener(this._eventNode, "keydown", this, false);
      </constructor>

      <destructor>
        const nsIEventListenerService =
          Components.interfaces.nsIEventListenerService;
        let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                            .getService(nsIEventListenerService);
        els.removeSystemEventListener(this._eventNode, "keydown", this, false);
      </destructor>
    </implementation>
  </binding>

  <binding id="tabs" role="xul:tabs"
           extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/tabbox.css"/>
    </resources>

    <content>
      <xul:spacer class="tabs-left"/>
      <children/>
      <xul:spacer class="tabs-right" flex="1"/>
    </content>

    <implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement">
      <constructor>
      <![CDATA[
        // first and last tabs need to be able to have unique styles
        // and also need to select first tab on startup.
        if (this.firstChild)
          this.firstChild.setAttribute("first-tab", "true");
        if (this.lastChild)
          this.lastChild.setAttribute("last-tab", "true");

        if (!this.hasAttribute("orient"))
          this.setAttribute("orient", "horizontal");

        if (this.tabbox && this.tabbox.hasAttribute("selectedIndex")) {
          let selectedIndex = parseInt(this.tabbox.getAttribute("selectedIndex"));
          this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
          return;
        }

        var children = this.childNodes;
        var length = children.length;
        for (var i = 0; i < length; i++) {
          if (children[i].getAttribute("selected") == "true") {
            this.selectedIndex = i;
            return;
          }
        }

        var value = this.value;
        if (value)
          this.value = value;
        else
          this.selectedIndex = 0;
      ]]>
      </constructor>

      <!-- nsIDOMXULRelatedElement -->
      <method name="getRelatedElement">
        <parameter name="aTabElm"/>
        <body>
        <![CDATA[
          if (!aTabElm)
            return null;

          let tabboxElm = this.tabbox;
          if (!tabboxElm)
            return null;

          let tabpanelsElm = tabboxElm.tabpanels;
          if (!tabpanelsElm)
            return null;

          // Get linked tab panel by 'linkedpanel' attribute on the given tab
          // element.
          let linkedPanelId = aTabElm.linkedPanel;
          if (linkedPanelId) {
            let ownerDoc = this.ownerDocument;

            // XXX bug 565858: if XUL tab element is anonymous element then
            // suppose linked tab panel is hosted within the same XBL binding
            // and search it by ID attribute inside an anonymous content of
            // the binding. This is not robust assumption since tab elements may
            // live outside a tabbox element so that for example tab elements
            // can be explicit content but tab panels can be anonymous.

            let bindingParent = ownerDoc.getBindingParent(aTabElm);
            if (bindingParent)
              return ownerDoc.getAnonymousElementByAttribute(bindingParent,
                                                             "id",
                                                             linkedPanelId);

            return ownerDoc.getElementById(linkedPanelId);
          }

          // otherwise linked tabpanel element has the same index as the given
          // tab element.
          let tabElmIdx = this.getIndexOfItem(aTabElm);
          return tabpanelsElm.childNodes[tabElmIdx];
        ]]>
        </body>
      </method>

      <!-- nsIDOMXULSelectControlElement -->
      <property name="itemCount" readonly="true"
                onget="return this.childNodes.length"/>

      <property name="value" onget="return this.getAttribute('value');">
        <setter>
          <![CDATA[
            this.setAttribute("value", val);
            var children = this.childNodes;
            for (var c = children.length - 1; c >= 0; c--) {
              if (children[c].value == val) {
                this.selectedIndex = c;
                break;
              }
            }
            return val;
          ]]>
        </setter>
      </property>

      <field name="_tabbox">null</field>
      <property name="tabbox" readonly="true">
        <getter><![CDATA[
          // Memoize the result in a field rather than replacing this property,
          // so that it can be reset along with the binding.
          if (this._tabbox) {
            return this._tabbox;
          }

          let parent = this.parentNode;
          while (parent) {
            if (parent.localName == "tabbox") {
              break;
            }
            parent = parent.parentNode;
          }

          return this._tabbox = parent;
        ]]></getter>
      </property>

      <!-- _tabbox is deprecated, it exists only for backwards compatibility. -->
      <field name="_tabbox" readonly="true"><![CDATA[
        this.tabbox;
      ]]></field>

      <property name="selectedIndex">
        <getter>
        <![CDATA[
          const tabs = this.childNodes;
          for (var i = 0; i < tabs.length; i++) {
            if (tabs[i].selected)
              return i;
          }
          return -1;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          var tab = this.getItemAtIndex(val);
          if (tab) {
            var alreadySelected = tab.selected;

            Array.forEach(this.childNodes, function(aTab) {
              if (aTab.selected && aTab != tab)
                aTab._selected = false;
            });
            tab._selected = true;

            this.setAttribute("value", tab.value);

            let linkedPanel = this.getRelatedElement(tab);
            if (linkedPanel) {
              this.tabbox.setAttribute("selectedIndex", val);

              // This will cause an onselect event to fire for the tabpanel
              // element.
              this.tabbox.tabpanels.selectedPanel = linkedPanel;
            }

            if (!alreadySelected) {
              // Fire an onselect event for the tabs element.
              var event = document.createEvent("Events");
              event.initEvent("select", true, true);
              this.dispatchEvent(event);
            }
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedItem">
        <getter>
        <![CDATA[
          const tabs = this.childNodes;
          for (var i = 0; i < tabs.length; i++) {
            if (tabs[i].selected)
              return tabs[i];
          }
          return null;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          if (val && !val.selected)
            // The selectedIndex setter ignores invalid values
            // such as -1 if |val| isn't one of our child nodes.
            this.selectedIndex = this.getIndexOfItem(val);
          return val;
        ]]>
        </setter>
      </property>

      <method name="getIndexOfItem">
        <parameter name="item"/>
        <body>
        <![CDATA[
          return Array.indexOf(this.childNodes, item);
        ]]>
        </body>
      </method>

      <method name="getItemAtIndex">
        <parameter name="index"/>
        <body>
        <![CDATA[
          return this.childNodes.item(index);
        ]]>
        </body>
      </method>

      <method name="_selectNewTab">
        <parameter name="aNewTab"/>
        <parameter name="aFallbackDir"/>
        <parameter name="aWrap"/>
        <body>
        <![CDATA[
          var requestedTab = aNewTab;
          while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
            aNewTab = aFallbackDir == -1 ? aNewTab.previousSibling : aNewTab.nextSibling;
            if (!aNewTab && aWrap)
              aNewTab = aFallbackDir == -1 ? this.childNodes[this.childNodes.length - 1] :
                                             this.childNodes[0];
            if (!aNewTab || aNewTab == requestedTab)
              return;
          }

          var isTabFocused = false;
          try {
            isTabFocused =
              (document.commandDispatcher.focusedElement == this.selectedItem);
          } catch (e) {}
          this.selectedItem = aNewTab;
          if (isTabFocused) {
            aNewTab.focus();
          } else if (this.getAttribute("setfocus") != "false") {
            let selectedPanel = this.tabbox.selectedPanel;
            document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);

            // Make sure that the focus doesn't move outside the tabbox
            if (this.tabbox) {
              try {
                let el = document.commandDispatcher.focusedElement;
                while (el && el != this.tabbox.tabpanels) {
                  if (el == this.tabbox || el == selectedPanel)
                    return;
                  el = el.parentNode;
                }
                aNewTab.focus();
              } catch (e) {
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="_canAdvanceToTab">
        <parameter name="aTab"/>
        <body>
        <![CDATA[
          return true;
        ]]>
        </body>
      </method>

      <method name="advanceSelectedTab">
        <parameter name="aDir"/>
        <parameter name="aWrap"/>
        <body>
        <![CDATA[
          var startTab = this.selectedItem;
          var next = startTab[aDir == -1 ? "previousSibling" : "nextSibling"];
          if (!next && aWrap) {
            next = aDir == -1 ? this.childNodes[this.childNodes.length - 1] :
                                this.childNodes[0];
          }
          if (next && next != startTab) {
            this._selectNewTab(next, aDir, aWrap);
          }
        ]]>
        </body>
      </method>

      <method name="appendItem">
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var tab = document.createElementNS(XULNS, "tab");
          tab.setAttribute("label", label);
          tab.setAttribute("value", value);
          this.appendChild(tab);
          return tab;
        ]]>
        </body>
      </method>

      <method name="insertItemAt">
        <parameter name="index"/>
        <parameter name="label"/>
        <parameter name="value"/>
        <body>
        <![CDATA[
          var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var tab = document.createElementNS(XULNS, "tab");
          tab.setAttribute("label", label);
          tab.setAttribute("value", value);
          var before = this.getItemAtIndex(index);
          if (before)
            this.insertBefore(tab, before);
          else
            this.appendChild(tab);
          return tab;
        ]]>
        </body>
      </method>

      <method name="removeItemAt">
        <parameter name="index"/>
        <body>
        <![CDATA[
          var remove = this.getItemAtIndex(index);
          if (remove)
            this.removeChild(remove);
          return remove;
        ]]>
        </body>
      </method>
    </implementation>

  </binding>

  <binding id="tabpanels" role="xul:tabpanels"
           extends="chrome://global/content/bindings/tabbox.xml#tab-base">
    <implementation implements="nsIDOMXULRelatedElement">
      <!-- nsIDOMXULRelatedElement -->
      <method name="getRelatedElement">
        <parameter name="aTabPanelElm"/>
        <body>
        <![CDATA[
          if (!aTabPanelElm)
            return null;

          let tabboxElm = this.tabbox;
          if (!tabboxElm)
            return null;

          let tabsElm = tabboxElm.tabs;
          if (!tabsElm)
            return null;

          // Return tab element having 'linkedpanel' attribute equal to the id
          // of the tab panel or the same index as the tab panel element.
          let tabpanelIdx = Array.indexOf(this.childNodes, aTabPanelElm);
          if (tabpanelIdx == -1)
            return null;

          let tabElms = tabsElm.childNodes;
          let tabElmFromIndex = tabElms[tabpanelIdx];

          let tabpanelId = aTabPanelElm.id;
          if (tabpanelId) {
            for (let idx = 0; idx < tabElms.length; idx++) {
              var tabElm = tabElms[idx];
              if (tabElm.linkedPanel == tabpanelId)
                return tabElm;
            }
          }

          return tabElmFromIndex;
        ]]>
        </body>
      </method>

      <!-- public -->
      <field name="_tabbox">null</field>
      <property name="tabbox" readonly="true">
        <getter><![CDATA[
          // Memoize the result in a field rather than replacing this property,
          // so that it can be reset along with the binding.
          if (this._tabbox) {
            return this._tabbox;
          }

          let parent = this.parentNode;
          while (parent) {
            if (parent.localName == "tabbox") {
              break;
            }
            parent = parent.parentNode;
          }

          return this._tabbox = parent;
        ]]></getter>
      </property>

      <field name="_selectedPanel">this.childNodes.item(this.selectedIndex)</field>

      <property name="selectedIndex">
        <getter>
        <![CDATA[
          var indexStr = this.getAttribute("selectedIndex");
          return indexStr ? parseInt(indexStr) : -1;
        ]]>
        </getter>

        <setter>
        <![CDATA[
          if (val < 0 || val >= this.childNodes.length)
            return val;
          var panel = this._selectedPanel;
          this._selectedPanel = this.childNodes[val];
          this.setAttribute("selectedIndex", val);
          if (this._selectedPanel != panel) {
            var event = document.createEvent("Events");
            event.initEvent("select", true, true);
            this.dispatchEvent(event);
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="selectedPanel">
        <getter>
          <![CDATA[
            return this._selectedPanel;
          ]]>
        </getter>

        <setter>
          <![CDATA[
            var selectedIndex = -1;
            for (var panel = val; panel != null; panel = panel.previousSibling)
              ++selectedIndex;
            this.selectedIndex = selectedIndex;
            return val;
          ]]>
        </setter>
      </property>
    </implementation>
  </binding>

  <binding id="tab" display="xul:button" role="xul:tab"
           extends="chrome://global/content/bindings/general.xml#control-item">
    <resources>
      <stylesheet src="chrome://global/skin/tabbox.css"/>
    </resources>

    <content>
      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
        <xul:image class="tab-icon"
                   xbl:inherits="validate,src=image"
                   role="presentation"/>
        <xul:label class="tab-text"
                   xbl:inherits="value=label,accesskey,crop,disabled"
                   flex="1"
                   role="presentation"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULSelectControlItemElement">
      <property name="control" readonly="true">
        <getter>
          <![CDATA[
            var parent = this.parentNode;
            if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
              return parent;
            return null;
          ]]>
        </getter>
      </property>

      <property name="selected" readonly="true"
                onget="return this.getAttribute('selected') == 'true';"/>

      <property name="_selected">
        <setter><![CDATA[
          if (val) {
            this.setAttribute("selected", "true");
            this.setAttribute("visuallyselected", "true");
          } else {
            this.removeAttribute("selected");
            this.removeAttribute("visuallyselected");
          }

          this._setPositionAttributes(val);

          return val;
        ]]></setter>
      </property>

      <method name="_setPositionAttributes">
        <parameter name="aSelected"/>
        <body><![CDATA[
          if (this.previousSibling && this.previousSibling.localName == "tab") {
            if (aSelected)
              this.previousSibling.setAttribute("beforeselected", "true");
            else
              this.previousSibling.removeAttribute("beforeselected");
            this.removeAttribute("first-tab");
          } else {
            this.setAttribute("first-tab", "true");
          }

          if (this.nextSibling && this.nextSibling.localName == "tab") {
            if (aSelected)
              this.nextSibling.setAttribute("afterselected", "true");
            else
              this.nextSibling.removeAttribute("afterselected");
            this.removeAttribute("last-tab");
          } else {
            this.setAttribute("last-tab", "true");
          }
        ]]></body>
      </method>

      <property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
                                   onset="this.setAttribute('linkedpanel', val); return val;"/>

      <field name="arrowKeysShouldWrap" readonly="true">
        /Mac/.test(navigator.platform)
      </field>
      <property name="TelemetryStopwatch" readonly="true">
        <getter><![CDATA[
          let module = {};
          Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", module);
          Object.defineProperty(this, "TelemetryStopwatch", {
            configurable: true,
            enumerable: true,
            writable: true,
            value: module.TelemetryStopwatch
          });
          return module.TelemetryStopwatch;
        ]]></getter>
      </property>
    </implementation>

    <handlers>
      <handler event="mousedown" button="0">
      <![CDATA[
        if (this.disabled)
          return;

        if (this != this.parentNode.selectedItem) { // Not selected yet
          let stopwatchid = this.parentNode.getAttribute("stopwatchid");
          if (stopwatchid) {
            this.TelemetryStopwatch.start(stopwatchid);
          }

          // Call this before setting the 'ignorefocus' attribute because this
          // will pass on focus if the formerly selected tab was focused as well.
          this.parentNode._selectNewTab(this);

          var isTabFocused = false;
          try {
            isTabFocused = (document.commandDispatcher.focusedElement == this);
          } catch (e) {}

          // Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
          // focus the tab; we only want tabs to be focusable by the mouse if
          // they are already focused. After a short timeout we'll reset
          // '-moz-user-focus' so that tabs can be focused by keyboard again.
          if (!isTabFocused) {
            this.setAttribute("ignorefocus", "true");
            setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
          }

          if (stopwatchid) {
            this.TelemetryStopwatch.finish(stopwatchid);
          }
        }
        // Otherwise this tab is already selected and we will fall
        // through to mousedown behavior which sets focus on the current tab,
        // Only a click on an already selected tab should focus the tab itself.
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_LEFT" group="system" preventdefault="true">
      <![CDATA[
        var direction = window.getComputedStyle(this.parentNode).direction;
        this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1, this.arrowKeysShouldWrap);
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_RIGHT" group="system" preventdefault="true">
      <![CDATA[
        var direction = window.getComputedStyle(this.parentNode).direction;
        this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1, this.arrowKeysShouldWrap);
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_UP" group="system" preventdefault="true">
      <![CDATA[
        this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_DOWN" group="system" preventdefault="true">
      <![CDATA[
        this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_HOME" group="system" preventdefault="true">
      <![CDATA[
        this.parentNode._selectNewTab(this.parentNode.childNodes[0]);
      ]]>
      </handler>

      <handler event="keydown" keycode="VK_END" group="system" preventdefault="true">
      <![CDATA[
        var tabs = this.parentNode.childNodes;
        this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
      ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
PK
!<V!0909/chrome/toolkit/content/global/bindings/text.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="textBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:html="http://www.w3.org/1999/xhtml">

  <!-- bound to <description>s -->
  <binding id="text-base" role="xul:text">
    <implementation implements="nsIDOMXULDescriptionElement">
      <property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
                                       else this.removeAttribute('disabled');
                                       return val;"
                                onget="return this.getAttribute('disabled') == 'true';"/>
      <property name="value" onget="return this.getAttribute('value');"
                             onset="this.setAttribute('value', val); return val;"/>
      <property name="crop" onget="return this.getAttribute('crop');"
                            onset="this.setAttribute('crop', val); return val;"/>
    </implementation>
  </binding>

  <binding id="text-label" extends="chrome://global/content/bindings/text.xml#text-base">
    <implementation implements="nsIDOMXULLabelElement">
      <property name="accessKey">
        <getter>
          <![CDATA[
            var accessKey = this.getAttribute("accesskey");
            return accessKey ? accessKey[0] : null;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.setAttribute("accesskey", val);
            return val;
          ]]>
        </setter>
      </property>

      <property name="control" onget="return getAttribute('control');">
        <setter>
          <![CDATA[
            // After this gets set, the label will use the binding #label-control
            this.setAttribute("control", val);
            return val;
          ]]>
        </setter>
      </property>
    </implementation>
  </binding>

  <binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
    <content>
      <children/><html:span anonid="accessKeyParens"></html:span>
    </content>
    <implementation implements="nsIDOMXULLabelElement">
      <constructor>
        <![CDATA[
          this.formatAccessKey(true);
        ]]>
      </constructor>

      <method name="formatAccessKey">
        <parameter name="firstTime"/>
        <body>
          <![CDATA[
            var control = this.labeledControlElement;
            if (!control) {
              var bindingParent = document.getBindingParent(this);
              if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
                control = bindingParent; // For controls that make the <label> an anon child
              }
            }
            if (control) {
              control.labelElement = this;
            }

            var accessKey = this.accessKey;
            // No need to remove existing formatting the first time.
            if (firstTime && !accessKey)
              return;

            if (this.mInsertSeparator === undefined) {
              try {
                var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                                       getService(Components.interfaces.nsIPrefBranch);
                this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);

                const nsIPrefLocalizedString =
                  Components.interfaces.nsIPrefLocalizedString;

                const prefNameInsertSeparator =
                  "intl.menuitems.insertseparatorbeforeaccesskeys";
                const prefNameAlwaysAppendAccessKey =
                  "intl.menuitems.alwaysappendaccesskeys";

                var val = prefs.getComplexValue(prefNameInsertSeparator,
                                                nsIPrefLocalizedString).data;
                this.mInsertSeparator = (val == "true");

                val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
                                            nsIPrefLocalizedString).data;
                this.mAlwaysAppendAccessKey = (val == "true");
              } catch (e) {
                this.mInsertSeparator = true;
              }
            }

            if (!this.mUnderlineAccesskey)
              return;

            var afterLabel = document.getAnonymousElementByAttribute(this, "anonid", "accessKeyParens");
            afterLabel.textContent = "";

            var oldAccessKey = this.getElementsByAttribute("class", "accesskey").item(0);
            if (oldAccessKey) { // Clear old accesskey
              this.mergeElement(oldAccessKey);
            }

            var oldHiddenSpan =
              this.getElementsByAttribute("class", "hiddenColon").item(0);
            if (oldHiddenSpan) {
              this.mergeElement(oldHiddenSpan);
            }

            var labelText = this.textContent;
            if (!accessKey || !labelText || !control) {
              return;
            }
            var accessKeyIndex = -1;
            if (!this.mAlwaysAppendAccessKey) {
              accessKeyIndex = labelText.indexOf(accessKey);
              if (accessKeyIndex < 0) { // Try again in upper case
                accessKeyIndex =
                  labelText.toUpperCase().indexOf(accessKey.toUpperCase());
              }
            }

            const HTML_NS = "http://www.w3.org/1999/xhtml";
            var span = document.createElementNS(HTML_NS, "span");
            span.className = "accesskey";

            // Note that if you change the following code, see the comment of
            // nsTextBoxFrame::UpdateAccessTitle.

            // If accesskey is not in string, append in parentheses
            if (accessKeyIndex < 0) {
              // If end is colon, we should insert before colon.
              // i.e., "label:" -> "label(X):"
              var colonHidden = false;
              if (/:$/.test(labelText)) {
                labelText = labelText.slice(0, -1);
                var hiddenSpan = document.createElementNS(HTML_NS, "span");
                hiddenSpan.className = "hiddenColon";
                hiddenSpan.style.display = "none";
                // Hide the last colon by using span element.
                // I.e., label<span style="display:none;">:</span>
                this.wrapChar(hiddenSpan, labelText.length);
                colonHidden = true;
              }
              // If end is space(U+20),
              // we should not add space before parentheses.
              var endIsSpace = false;
              if (/ $/.test(labelText)) {
                endIsSpace = true;
              }
              if (this.mInsertSeparator && !endIsSpace)
                afterLabel.textContent = " (";
              else
                afterLabel.textContent = "(";
              span.textContent = accessKey.toUpperCase();
              afterLabel.appendChild(span);
              if (!colonHidden)
                afterLabel.appendChild(document.createTextNode(")"));
              else
                afterLabel.appendChild(document.createTextNode("):"));
              return;
            }
            this.wrapChar(span, accessKeyIndex);
          ]]>
        </body>
      </method>

      <method name="wrapChar">
        <parameter name="element"/>
        <parameter name="index"/>
        <body>
          <![CDATA[
             var treeWalker = document.createTreeWalker(this,
                                                        NodeFilter.SHOW_TEXT,
                                                        null);
             var node = treeWalker.nextNode();
             while (index >= node.length) {
               index -= node.length;
               node = treeWalker.nextNode();
             }
             if (index) {
               node = node.splitText(index);
             }
             node.parentNode.insertBefore(element, node);
             if (node.length > 1) {
               node.splitText(1);
             }
             element.appendChild(node);
          ]]>
        </body>
      </method>

      <method name="mergeElement">
        <parameter name="element"/>
        <body>
          <![CDATA[
            if (element.previousSibling instanceof Text) {
              element.previousSibling.appendData(element.textContent)
            } else {
              element.parentNode.insertBefore(element.firstChild, element);
            }
            element.remove();
          ]]>
        </body>
      </method>

      <field name="mUnderlineAccesskey">
        !/Mac/.test(navigator.platform)
      </field>
      <field name="mInsertSeparator"/>
      <field name="mAlwaysAppendAccessKey">false</field>

      <property name="accessKey">
        <getter>
          <![CDATA[
            var accessKey = null;
            var labeledEl = this.labeledControlElement;
            if (labeledEl) {
              accessKey = labeledEl.getAttribute("accesskey");
            }
            if (!accessKey) {
              accessKey = this.getAttribute("accesskey");
            }
            return accessKey ? accessKey[0] : null;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            // If this label already has an accesskey attribute store it here as well
            if (this.hasAttribute("accesskey")) {
              this.setAttribute("accesskey", val);
            }
            var control = this.labeledControlElement;
            if (control) {
              control.setAttribute("accesskey", val);
            }
            this.formatAccessKey(false);
            return val;
          ]]>
        </setter>
      </property>

      <property name="labeledControlElement" readonly="true"
                onget="var control = this.control; return control ? document.getElementById(control) : null;" />

      <property name="control" onget="return this.getAttribute('control');">
        <setter>
          <![CDATA[
            var control = this.labeledControlElement;
            if (control) {
              control.labelElement = null; // No longer pointed to be this label
            }
            this.setAttribute("control", val);
            this.formatAccessKey(false);
            return val;
          ]]>
        </setter>
      </property>

    </implementation>

    <handlers>
      <handler event="click" action="if (this.disabled) return;
                                     var controlElement = this.labeledControlElement;
                                     if(controlElement)
                                       controlElement.focus();
                                    "/>
    </handlers>
  </binding>

  <binding id="text-link" extends="chrome://global/content/bindings/text.xml#text-label" role="xul:link">
    <implementation>
      <property name="href" onget="return this.getAttribute('href');"
                            onset="this.setAttribute('href', val); return val;" />
      <method name="open">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          var href = this.href;
          if (!href || this.disabled || aEvent.defaultPrevented)
            return;

          var uri = null;
          try {
            const nsISSM = Components.interfaces.nsIScriptSecurityManager;
            const secMan =
                     Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                               .getService(nsISSM);

            const ioService =
                     Components.classes["@mozilla.org/network/io-service;1"]
                               .getService(Components.interfaces.nsIIOService);

            uri = ioService.newURI(href);

            let principal;
            if (this.getAttribute("useoriginprincipal") == "true") {
              principal = this.nodePrincipal;
            } else {
              principal = secMan.createNullPrincipal({});
            }
            try {
              secMan.checkLoadURIWithPrincipal(principal, uri,
                                               nsISSM.DISALLOW_INHERIT_PRINCIPAL);
            } catch (ex) {
              var msg = "Error: Cannot open a " + uri.scheme + ": link using \
                         the text-link binding.";
              Components.utils.reportError(msg);
              return;
            }

            const cID = "@mozilla.org/uriloader/external-protocol-service;1";
            const nsIEPS = Components.interfaces.nsIExternalProtocolService;
            var protocolSvc = Components.classes[cID].getService(nsIEPS);

            // if the scheme is not an exposed protocol, then opening this link
            // should be deferred to the system's external protocol handler
            if (!protocolSvc.isExposedProtocol(uri.scheme)) {
              protocolSvc.loadUrl(uri);
              aEvent.preventDefault()
              return;
            }

          } catch (ex) {
            Components.utils.reportError(ex);
          }

          aEvent.preventDefault();
          href = uri ? uri.spec : href;

          // Try handing off the link to the host application, e.g. for
          // opening it in a tabbed browser.
          var linkHandled = Components.classes["@mozilla.org/supports-PRBool;1"]
                                      .createInstance(Components.interfaces.nsISupportsPRBool);
          linkHandled.data = false;
          let {shiftKey, ctrlKey, metaKey, altKey, button} = aEvent;
          let data = {shiftKey, ctrlKey, metaKey, altKey, button, href};
          Components.classes["@mozilla.org/observer-service;1"]
                    .getService(Components.interfaces.nsIObserverService)
                    .notifyObservers(linkHandled, "handle-xul-text-link", JSON.stringify(data));
          if (linkHandled.data)
            return;

          // otherwise, fall back to opening the anchor directly
          var win = window;
          if (window instanceof Components.interfaces.nsIDOMChromeWindow) {
            while (win.opener && !win.opener.closed)
              win = win.opener;
          }
          win.open(href);
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="click" phase="capturing" button="0" action="this.open(event)"/>
      <handler event="click" phase="capturing" button="1" action="this.open(event)"/>
      <handler event="keypress" preventdefault="true" keycode="VK_RETURN" action="this.click()" />
    </handlers>
  </binding>

</bindings>
PK
!<Fbgbg2chrome/toolkit/content/global/bindings/textbox.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This files relies on these specific Chrome/XBL globals -->
<!-- globals ChromeWindow -->


<!DOCTYPE bindings [
  <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
  %textcontextDTD;
]>

<bindings id="textboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="textbox" extends="xul:box" role="xul:textbox">
    <resources>
      <stylesheet src="chrome://global/content/textbox.css"/>
      <stylesheet src="chrome://global/skin/textbox.css"/>
    </resources>

    <content>
      <children/>
      <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
        <html:input class="textbox-input" anonid="input"
                    xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,noinitialfocus,mozactionhint,spellcheck"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULTextBoxElement, nsIDOMXULLabeledControlElement">
      <!-- nsIDOMXULLabeledControlElement -->
      <field name="crop">""</field>
      <field name="image">""</field>
      <field name="command">""</field>
      <field name="accessKey">""</field>

      <field name="mInputField">null</field>
      <field name="mIgnoreClick">false</field>
      <field name="mIgnoreFocus">false</field>
      <field name="mEditor">null</field>

      <property name="inputField" readonly="true">
        <getter><![CDATA[
          if (!this.mInputField)
            this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
          return this.mInputField;
        ]]></getter>
      </property>

      <property name="value"      onset="this.inputField.value = val; return val;"
                                  onget="return this.inputField.value;"/>
      <property name="defaultValue" onset="this.inputField.defaultValue = val; return val;"
                                  onget="return this.inputField.defaultValue;"/>
      <property name="label"      onset="this.setAttribute('label', val); return val;"
                                  onget="return this.getAttribute('label') ||
                                                (this.labelElement ? this.labelElement.value :
                                                 this.placeholder);"/>
      <property name="placeholder" onset="this.inputField.placeholder = val; return val;"
                                  onget="return this.inputField.placeholder;"/>
      <property name="emptyText"  onset="this.placeholder = val; return val;"
                                  onget="return this.placeholder;"/>
      <property name="type"       onset="if (val) this.setAttribute('type', val);
                                         else this.removeAttribute('type'); return val;"
                                  onget="return this.getAttribute('type');"/>
      <property name="maxLength"  onset="this.inputField.maxLength = val; return val;"
                                  onget="return this.inputField.maxLength;"/>
      <property name="disabled"   onset="this.inputField.disabled = val;
                                         if (val) this.setAttribute('disabled', 'true');
                                         else this.removeAttribute('disabled'); return val;"
                                  onget="return this.inputField.disabled;"/>
      <property name="tabIndex"   onget="return parseInt(this.getAttribute('tabindex'));"
                                  onset="this.inputField.tabIndex = val;
                                         if (val) this.setAttribute('tabindex', val);
                                         else this.removeAttribute('tabindex'); return val;"/>
      <property name="size"       onset="this.inputField.size = val; return val;"
                                  onget="return this.inputField.size;"/>
      <property name="readOnly"   onset="this.inputField.readOnly = val;
                                         if (val) this.setAttribute('readonly', 'true');
                                         else this.removeAttribute('readonly'); return val;"
                                  onget="return this.inputField.readOnly;"/>
      <property name="clickSelectsAll"
                onget="return this.getAttribute('clickSelectsAll') == 'true';"
                onset="if (val) this.setAttribute('clickSelectsAll', 'true');
                       else this.removeAttribute('clickSelectsAll'); return val;" />

      <property name="editor" readonly="true">
        <getter><![CDATA[
          if (!this.mEditor) {
            const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
            this.mEditor = this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
          }
          return this.mEditor;
        ]]></getter>
      </property>

      <method name="reset">
        <body><![CDATA[
          this.value = this.defaultValue;
          try {
            this.editor.transactionManager.clear();
            return true;
          } catch (e) {}
          return false;
        ]]></body>
      </method>

      <method name="select">
        <body>
          this.inputField.select();
        </body>
      </method>

      <property name="controllers"    readonly="true" onget="return this.inputField.controllers"/>
      <property name="textLength"     readonly="true"
                                      onget="return this.inputField.textLength;"/>
      <property name="selectionStart" onset="this.inputField.selectionStart = val; return val;"
                                      onget="return this.inputField.selectionStart;"/>
      <property name="selectionEnd"   onset="this.inputField.selectionEnd = val; return val;"
                                      onget="return this.inputField.selectionEnd;"/>

      <method name="setSelectionRange">
        <parameter name="aSelectionStart"/>
        <parameter name="aSelectionEnd"/>
        <body>
          this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
        </body>
      </method>

      <method name="_setNewlineHandling">
        <body><![CDATA[
          var str = this.getAttribute("newlines");
          if (str && this.editor) {
            const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
            for (var x in nsIPlaintextEditor) {
              if (/^eNewlines/.test(x)) {
                if (str == RegExp.rightContext.toLowerCase()) {
                  this.editor.QueryInterface(nsIPlaintextEditor)
                      .newlineHandling = nsIPlaintextEditor[x];
                  break;
                }
              }
            }
          }
        ]]></body>
      </method>

      <method name="_maybeSelectAll">
        <body><![CDATA[
          if (!this.mIgnoreClick && this.clickSelectsAll &&
              document.activeElement == this.inputField &&
              this.inputField.selectionStart == this.inputField.selectionEnd)
            this.editor.selectAll();
        ]]></body>
      </method>

      <constructor><![CDATA[
        var str = this.boxObject.getProperty("value");
        if (str) {
          this.inputField.value = str;
          this.boxObject.removeProperty("value");
        }

        this._setNewlineHandling();

        if (this.hasAttribute("emptytext"))
          this.placeholder = this.getAttribute("emptytext");
      ]]></constructor>

      <destructor>
        <![CDATA[
          var field = this.inputField;
          if (field && field.value)
            this.boxObject.setProperty("value", field.value);
          this.mInputField = null;
        ]]>
      </destructor>

    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        <![CDATA[
          if (this.hasAttribute("focused"))
            return;

          switch (event.originalTarget) {
            case this:
              // Forward focus to actual HTML input
              this.inputField.focus();
              break;
            case this.inputField:
              if (this.mIgnoreFocus) {
                this.mIgnoreFocus = false;
              } else if (this.clickSelectsAll) {
                try {
                  if (!this.editor || !this.editor.composing)
                    this.editor.selectAll();
                } catch (e) {}
              }
              break;
            default:
              // Allow other children (e.g. URL bar buttons) to get focus
              return;
          }
          this.setAttribute("focused", "true");
        ]]>
      </handler>

      <handler event="blur" phase="capturing">
        <![CDATA[
          this.removeAttribute("focused");

          // don't trigger clickSelectsAll when switching application windows
          if (window == window.top &&
              window.constructor == ChromeWindow &&
              document.activeElement == this.inputField)
            this.mIgnoreFocus = true;
        ]]>
      </handler>

      <handler event="mousedown">
        <![CDATA[
          this.mIgnoreClick = this.hasAttribute("focused");

          if (!this.mIgnoreClick) {
            this.mIgnoreFocus = true;
            this.inputField.setSelectionRange(0, 0);
            if (event.originalTarget == this ||
                event.originalTarget == this.inputField.parentNode)
              this.inputField.focus();
          }
        ]]>
      </handler>

      <handler event="click" action="this._maybeSelectAll();"/>

    </handlers>
  </binding>

  <binding id="timed-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
    <implementation>
      <constructor><![CDATA[
        try {
          var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                                         .getService(Components.interfaces.nsIConsoleService);
          var scriptError = Components.classes["@mozilla.org/scripterror;1"]
                                      .createInstance(Components.interfaces.nsIScriptError);
          scriptError.init("Timed textboxes are deprecated. Consider using type=\"search\" instead.",
                           this.ownerDocument.location.href, null, null,
                           null, scriptError.warningFlag, "XUL Widgets");
          consoleService.logMessage(scriptError);
        } catch (e) {}
      ]]></constructor>
      <field name="_timer">null</field>
      <property name="timeout"
                onset="this.setAttribute('timeout', val); return val;"
                onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
      <property name="value"
                onget="return this.inputField.value;">
        <setter><![CDATA[
          this.inputField.value = val;
          if (this._timer)
            clearTimeout(this._timer);
          return val;
        ]]></setter>
      </property>
      <method name="_fireCommand">
        <parameter name="me"/>
        <body>
          <![CDATA[
            me._timer = null;
            me.doCommand();
          ]]>
        </body>
      </method>
    </implementation>
    <handlers>
      <handler event="input">
        <![CDATA[
          if (this._timer)
            clearTimeout(this._timer);
          this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_RETURN">
        <![CDATA[
          if (this._timer)
            clearTimeout(this._timer);
          this._fireCommand(this);
          event.preventDefault();
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
    <content>
      <children/>
      <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck" align="center">
        <xul:image class="textbox-search-sign"/>
        <html:input class="textbox-input" anonid="input" mozactionhint="search"
                    xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck"/>
        <xul:deck class="textbox-search-icons" anonid="search-icons">
          <xul:image class="textbox-search-icon" anonid="searchbutton-icon"
                     xbl:inherits="src=image,label=searchbuttonlabel,searchbutton,disabled"/>
          <xul:image class="textbox-search-clear"
                     onclick="document.getBindingParent(this)._clearSearch();"
                     label="&searchTextBox.clear.label;"
                     xbl:inherits="disabled"/>
        </xul:deck>
      </xul:hbox>
    </content>
    <implementation>
      <field name="_timer">null</field>
      <field name="_searchIcons">
        document.getAnonymousElementByAttribute(this, "anonid", "search-icons");
      </field>
      <field name="_searchButtonIcon">
        document.getAnonymousElementByAttribute(this, "anonid", "searchbutton-icon");
      </field>
      <property name="timeout"
                onset="this.setAttribute('timeout', val); return val;"
                onget="return parseInt(this.getAttribute('timeout')) || 500;"/>
      <property name="searchButton"
                onget="return this.getAttribute('searchbutton') == 'true';">
        <setter><![CDATA[
          if (val) {
            this.setAttribute("searchbutton", "true");
            this.removeAttribute("aria-autocomplete");
            // Hack for the button to get the right accessible:
            this._searchButtonIcon.setAttribute("onclick", "true");
          } else {
            this.removeAttribute("searchbutton");
            this._searchButtonIcon.removeAttribute("onclick");
            this.setAttribute("aria-autocomplete", "list");
          }
          return val;
        ]]></setter>
      </property>
      <property name="value"
                onget="return this.inputField.value;">
        <setter><![CDATA[
          this.inputField.value = val;

          if (val)
            this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
          else
            this._searchIcons.selectedIndex = 0;

          if (this._timer)
            clearTimeout(this._timer);

          return val;
        ]]></setter>
      </property>
      <constructor><![CDATA[
        // Ensure the button state is up to date:
        this.searchButton = this.searchButton;
        this._searchButtonIcon.addEventListener("click", (e) => this._iconClick(e));
      ]]></constructor>
      <method name="_fireCommand">
        <parameter name="me"/>
        <body><![CDATA[
          if (me._timer)
            clearTimeout(me._timer);
          me._timer = null;
          me.doCommand();
        ]]></body>
      </method>
      <method name="_iconClick">
        <body><![CDATA[
          if (this.searchButton)
            this._enterSearch();
          else
            this.focus();
        ]]></body>
      </method>
      <method name="_enterSearch">
        <body><![CDATA[
          if (this.disabled)
            return;
          if (this.searchButton && this.value && !this.readOnly)
            this._searchIcons.selectedIndex = 1;
          this._fireCommand(this);
        ]]></body>
      </method>
      <method name="_clearSearch">
        <body><![CDATA[
          if (!this.disabled && !this.readOnly && this.value) {
            this.value = "";
            this._fireCommand(this);
            this._searchIcons.selectedIndex = 0;
            return true;
          }
          return false;
        ]]></body>
      </method>
    </implementation>
    <handlers>
      <handler event="input">
        <![CDATA[
          if (this.searchButton) {
            this._searchIcons.selectedIndex = 0;
            return;
          }
          if (this._timer)
            clearTimeout(this._timer);
          this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
          this._searchIcons.selectedIndex = this.value ? 1 : 0;
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_ESCAPE">
        <![CDATA[
          if (this._clearSearch()) {
            event.preventDefault();
            event.stopPropagation();
          }
        ]]>
      </handler>
      <handler event="keypress" keycode="VK_RETURN">
        <![CDATA[
          this._enterSearch();
          event.preventDefault();
          event.stopPropagation();
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="textarea" extends="chrome://global/content/bindings/textbox.xml#textbox">
    <content>
      <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
        <html:textarea class="textbox-textarea" anonid="input"
                       xbl:inherits="xbl:text=value,disabled,tabindex,rows,cols,readonly,wrap,placeholder,mozactionhint,spellcheck"><children/></html:textarea>
      </xul:hbox>
    </content>
  </binding>

  <binding id="input-box">
    <content context="_child">
      <children/>
      <xul:menupopup anonid="input-box-contextmenu"
                     class="textbox-contextmenu"
                     onpopupshowing="var input =
                                       this.parentNode.getElementsByAttribute('anonid', 'input')[0];
                                     if (document.commandDispatcher.focusedElement != input)
                                       input.focus();
                                     this.parentNode._doPopupItemEnabling(this);"
                     oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
        <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
        <xul:menuseparator/>
        <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
        <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
        <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
        <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
        <xul:menuseparator/>
        <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
      </xul:menupopup>
    </content>

    <implementation>
      <method name="_doPopupItemEnabling">
        <parameter name="popupNode"/>
        <body>
          <![CDATA[
            var children = popupNode.childNodes;
            for (var i = 0; i < children.length; i++) {
              var command = children[i].getAttribute("cmd");
              if (command) {
                var controller = document.commandDispatcher.getControllerForCommand(command);
                var enabled = controller.isCommandEnabled(command);
                if (enabled)
                  children[i].removeAttribute("disabled");
                else
                  children[i].setAttribute("disabled", "true");
              }
            }
          ]]>
        </body>
      </method>

      <method name="_setMenuItemVisibility">
        <parameter name="anonid"/>
        <parameter name="visible"/>
        <body><![CDATA[
          document.getAnonymousElementByAttribute(this, "anonid", anonid).
            hidden = !visible;
        ]]></body>
      </method>

      <method name="doCommand">
        <parameter name="command"/>
        <body>
          <![CDATA[
            var controller = document.commandDispatcher.getControllerForCommand(command);
            controller.doCommand(command);
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="input-box-spell" extends="chrome://global/content/bindings/textbox.xml#input-box">
    <content context="_child">
      <children/>
      <xul:menupopup anonid="input-box-contextmenu"
                     class="textbox-contextmenu"
                     onpopupshowing="var input =
                                       this.parentNode.getElementsByAttribute('anonid', 'input')[0];
                                     if (document.commandDispatcher.focusedElement != input)
                                       input.focus();
                                     this.parentNode._doPopupItemEnablingSpell(this);"
                     onpopuphiding="this.parentNode._doPopupItemDisabling(this);"
                     oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
        <xul:menuitem label="&spellNoSuggestions.label;" anonid="spell-no-suggestions" disabled="true"/>
        <xul:menuitem label="&spellAddToDictionary.label;" accesskey="&spellAddToDictionary.accesskey;" anonid="spell-add-to-dictionary"
                      oncommand="this.parentNode.parentNode.spellCheckerUI.addToDictionary();"/>
        <xul:menuitem label="&spellUndoAddToDictionary.label;" accesskey="&spellUndoAddToDictionary.accesskey;" anonid="spell-undo-add-to-dictionary"
                      oncommand="this.parentNode.parentNode.spellCheckerUI.undoAddToDictionary();"/>
        <xul:menuseparator anonid="spell-suggestions-separator"/>
        <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
        <xul:menuseparator/>
        <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
        <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
        <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
        <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
        <xul:menuseparator/>
        <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
        <xul:menuseparator anonid="spell-check-separator"/>
        <xul:menuitem label="&spellCheckToggle.label;" type="checkbox" accesskey="&spellCheckToggle.accesskey;" anonid="spell-check-enabled"
                      oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"/>
        <xul:menu label="&spellDictionaries.label;" accesskey="&spellDictionaries.accesskey;" anonid="spell-dictionaries">
          <xul:menupopup anonid="spell-dictionaries-menu"
                         onpopupshowing="event.stopPropagation();"
                         onpopuphiding="event.stopPropagation();"/>
        </xul:menu>
      </xul:menupopup>
    </content>

    <implementation>
      <field name="_spellCheckInitialized">false</field>
      <field name="_enabledCheckbox">
        document.getAnonymousElementByAttribute(this, "anonid", "spell-check-enabled");
      </field>
      <field name="_suggestionsSeparator">
        document.getAnonymousElementByAttribute(this, "anonid", "spell-no-suggestions");
      </field>
      <field name="_dictionariesMenu">
        document.getAnonymousElementByAttribute(this, "anonid", "spell-dictionaries-menu");
      </field>

      <property name="spellCheckerUI" readonly="true">
        <getter><![CDATA[
          if (!this._spellCheckInitialized) {
            this._spellCheckInitialized = true;

            const CI = Components.interfaces;
            if (!(document instanceof CI.nsIDOMXULDocument))
              return null;

            var textbox = document.getBindingParent(this);
            if (!textbox || !(textbox instanceof CI.nsIDOMXULTextBoxElement))
              return null;

            try {
              Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm", this);
              this.InlineSpellCheckerUI = new this.InlineSpellChecker(textbox.editor);
            } catch (ex) { }
          }

          return this.InlineSpellCheckerUI;
        ]]></getter>
      </property>

      <method name="_doPopupItemEnablingSpell">
        <parameter name="popupNode"/>
        <body>
          <![CDATA[
            var spellui = this.spellCheckerUI;
            if (!spellui || !spellui.canSpellCheck) {
              this._setMenuItemVisibility("spell-no-suggestions", false);
              this._setMenuItemVisibility("spell-check-enabled", false);
              this._setMenuItemVisibility("spell-check-separator", false);
              this._setMenuItemVisibility("spell-add-to-dictionary", false);
              this._setMenuItemVisibility("spell-undo-add-to-dictionary", false);
              this._setMenuItemVisibility("spell-suggestions-separator", false);
              this._setMenuItemVisibility("spell-dictionaries", false);
              return;
            }

            spellui.initFromEvent(document.popupRangeParent,
                                  document.popupRangeOffset);

            var enabled = spellui.enabled;
            var showUndo = spellui.canSpellCheck && spellui.canUndo();
            this._enabledCheckbox.setAttribute("checked", enabled);

            var overMisspelling = spellui.overMisspelling;
            this._setMenuItemVisibility("spell-add-to-dictionary", overMisspelling);
            this._setMenuItemVisibility("spell-undo-add-to-dictionary", showUndo);
            this._setMenuItemVisibility("spell-suggestions-separator", overMisspelling || showUndo);

            // suggestion list
            var numsug = spellui.addSuggestionsToMenu(popupNode, this._suggestionsSeparator, 5);
            this._setMenuItemVisibility("spell-no-suggestions", overMisspelling && numsug == 0);

            // dictionary list
            var numdicts = spellui.addDictionaryListToMenu(this._dictionariesMenu, null);
            this._setMenuItemVisibility("spell-dictionaries", enabled && numdicts > 1);

            this._doPopupItemEnabling(popupNode);
          ]]>
        </body>
      </method>
      <method name="_doPopupItemDisabling">
        <body><![CDATA[
          if (this.spellCheckerUI) {
            this.spellCheckerUI.clearSuggestionsFromMenu();
            this.spellCheckerUI.clearDictionaryListFromMenu();
          }
        ]]></body>
      </method>
    </implementation>
  </binding>

</bindings>
PK
!<<k!..4chrome/toolkit/content/global/bindings/timekeeper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * TimeKeeper keeps track of the time states. Given min, max, step, and
 * format (12/24hr), TimeKeeper will determine the ranges of possible
 * selections, and whether or not the current time state is out of range
 * or off step.
 *
 * @param {Object} props
 *        {
 *          {Date} min
 *          {Date} max
 *          {Number} step
 *          {String} format: Either "12" or "24"
 *        }
 */
function TimeKeeper(props) {
  this.props = props;
  this.state = { time: new Date(0), ranges: {} };
}

{
  const DAY_PERIOD_IN_HOURS = 12,
        SECOND_IN_MS = 1000,
        MINUTE_IN_MS = 60000,
        HOUR_IN_MS = 3600000,
        DAY_PERIOD_IN_MS = 43200000,
        DAY_IN_MS = 86400000,
        TIME_FORMAT_24 = "24";

  TimeKeeper.prototype = {
    /**
     * Getters for different time units.
     * @return {Number}
     */
    get hour() {
      return this.state.time.getUTCHours();
    },
    get minute() {
      return this.state.time.getUTCMinutes();
    },
    get second() {
      return this.state.time.getUTCSeconds();
    },
    get millisecond() {
      return this.state.time.getUTCMilliseconds();
    },
    get dayPeriod() {
      // 0 stands for AM and 12 for PM
      return this.state.time.getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;
    },

    /**
     * Get the ranges of different time units.
     * @return {Object}
     *         {
     *           {Array<Number>} dayPeriod
     *           {Array<Number>} hours
     *           {Array<Number>} minutes
     *           {Array<Number>} seconds
     *           {Array<Number>} milliseconds
     *         }
     */
    get ranges() {
      return this.state.ranges;
    },

    /**
     * Set new time, check if the current state is valid, and set ranges.
     *
     * @param {Object} timeState: The new time
     *        {
     *          {Number} hour [optional]
     *          {Number} minute [optional]
     *          {Number} second [optional]
     *          {Number} millisecond [optional]
     *        }
     */
    setState(timeState) {
      const { min, max } = this.props;
      const { hour, minute, second, millisecond } = timeState;

      if (hour != undefined) {
        this.state.time.setUTCHours(hour);
      }
      if (minute != undefined) {
        this.state.time.setUTCMinutes(minute);
      }
      if (second != undefined) {
        this.state.time.setUTCSeconds(second);
      }
      if (millisecond != undefined) {
        this.state.time.setUTCMilliseconds(millisecond);
      }

      this.state.isOffStep = this._isOffStep(this.state.time);
      this.state.isOutOfRange = (this.state.time < min || this.state.time > max);
      this.state.isInvalid = this.state.isOutOfRange || this.state.isOffStep;

      this._setRanges(this.dayPeriod, this.hour, this.minute, this.second);
    },

    /**
     * Set day-period (AM/PM)
     * @param {Number} dayPeriod: 0 as AM, 12 as PM
     */
    setDayPeriod(dayPeriod) {
      if (dayPeriod == this.dayPeriod) {
        return;
      }

      if (dayPeriod == 0) {
        this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
      } else {
        this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS });
      }
    },

    /**
     * Set hour in 24hr format (0 ~ 23)
     * @param {Number} hour
     */
    setHour(hour) {
      this.setState({ hour });
    },

    /**
     * Set minute (0 ~ 59)
     * @param {Number} minute
     */
    setMinute(minute) {
      this.setState({ minute });
    },

    /**
     * Set second (0 ~ 59)
     * @param {Number} second
     */
    setSecond(second) {
      this.setState({ second });
    },

    /**
     * Set millisecond (0 ~ 999)
     * @param {Number} millisecond
     */
    setMillisecond(millisecond) {
      this.setState({ millisecond });
    },

    /**
     * Calculate the range of possible choices for each time unit.
     * Reuse the old result if the input has not changed.
     *
     * @param {Number} dayPeriod
     * @param {Number} hour
     * @param {Number} minute
     * @param {Number} second
     */
    _setRanges(dayPeriod, hour, minute, second) {
      this.state.ranges.dayPeriod =
        this.state.ranges.dayPeriod || this._getDayPeriodRange();

      if (this.state.dayPeriod != dayPeriod) {
        this.state.ranges.hours = this._getHoursRange(dayPeriod);
      }

      if (this.state.hour != hour) {
        this.state.ranges.minutes = this._getMinutesRange(hour);
      }

      if (this.state.hour != hour || this.state.minute != minute) {
        this.state.ranges.seconds = this._getSecondsRange(hour, minute);
      }

      if (this.state.hour != hour || this.state.minute != minute || this.state.second != second) {
        this.state.ranges.milliseconds = this._getMillisecondsRange(hour, minute, second);
      }

      // Save the time states for comparison.
      this.state.dayPeriod = dayPeriod;
      this.state.hour = hour;
      this.state.minute = minute;
      this.state.second = second;
    },

    /**
     * Get the AM/PM range. Return an empty array if in 24hr mode.
     *
     * @return {Array<Number>}
     */
    _getDayPeriodRange() {
      if (this.props.format == TIME_FORMAT_24) {
        return [];
      }

      const start = 0;
      const end = DAY_IN_MS - 1;
      const minStep = DAY_PERIOD_IN_MS;
      const formatter = (time) =>
        new Date(time).getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the hours range.
     *
     * @param  {Number} dayPeriod
     * @return {Array<Number>}
     */
    _getHoursRange(dayPeriod) {
      const { format } = this.props;
      const start = format == "24" ? 0 : dayPeriod * HOUR_IN_MS;
      const end = format == "24" ? DAY_IN_MS - 1 : start + DAY_PERIOD_IN_MS - 1;
      const minStep = HOUR_IN_MS;
      const formatter = (time) => new Date(time).getUTCHours();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the minutes range
     *
     * @param  {Number} hour
     * @return {Array<Number>}
     */
    _getMinutesRange(hour) {
      const start = hour * HOUR_IN_MS;
      const end = start + HOUR_IN_MS - 1;
      const minStep = MINUTE_IN_MS;
      const formatter = (time) => new Date(time).getUTCMinutes();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the seconds range
     *
     * @param  {Number} hour
     * @param  {Number} minute
     * @return {Array<Number>}
     */
    _getSecondsRange(hour, minute) {
      const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS;
      const end = start + MINUTE_IN_MS - 1;
      const minStep = SECOND_IN_MS;
      const formatter = (time) => new Date(time).getUTCSeconds();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the milliseconds range
     * @param  {Number} hour
     * @param  {Number} minute
     * @param  {Number} second
     * @return {Array<Number>}
     */
    _getMillisecondsRange(hour, minute, second) {
      const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS + second * SECOND_IN_MS;
      const end = start + SECOND_IN_MS - 1;
      const minStep = 1;
      const formatter = (time) => new Date(time).getUTCMilliseconds();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Calculate the range of possible steps.
     *
     * @param  {Number} startValue: Start time in ms
     * @param  {Number} endValue: End time in ms
     * @param  {Number} minStep: Smallest step in ms for the time unit
     * @param  {Function} formatter: Outputs time in a particular format
     * @return {Array<Object>}
     *         {
     *           {Number} value
     *           {Boolean} enabled
     *         }
     */
    _getSteps(startValue, endValue, minStep, formatter) {
      const { min, max, step } = this.props;
      // The timeStep should be big enough so that there won't be
      // duplications. Ex: minimum step for minute should be 60000ms,
      // if smaller than that, next step might return the same minute.
      const timeStep = Math.max(minStep, step);

      // Make sure the starting point and end point is not off step
      let time = min.valueOf() + Math.ceil((startValue - min.valueOf()) / timeStep) * timeStep;
      let maxValue = min.valueOf() + Math.floor((max.valueOf() - min.valueOf()) / step) * step;
      let steps = [];

      // Increment by timeStep until reaching the end of the range.
      while (time <= endValue) {
        steps.push({
          value: formatter(time),
          // Check if the value is within the min and max. If it's out of range,
          // also check for the case when minStep is too large, and has stepped out
          // of range when it should be enabled.
          enabled: (time >= min.valueOf() && time <= max.valueOf()) ||
            (time > maxValue && startValue <= maxValue &&
              endValue >= maxValue && formatter(time) == formatter(maxValue))
        });
        time += timeStep;
      }

      return steps;
    },

    /**
     * A generic function for stepping up or down from a value of a range.
     * It stops at the upper and lower limits.
     *
     * @param  {Number} current: The current value
     * @param  {Number} offset: The offset relative to current value
     * @param  {Array<Object>} range: List of possible steps
     * @return {Number} The new value
     */
    _step(current, offset, range) {
      const index = range.findIndex(step => step.value == current);
      const newIndex = offset > 0 ?
                     Math.min(index + offset, range.length - 1) :
                     Math.max(index + offset, 0);
      return range[newIndex].value;
    },

    /**
     * Step up or down AM/PM
     *
     * @param  {Number} offset
     */
    stepDayPeriodBy(offset) {
      const current = this.dayPeriod;
      const dayPeriod = this._step(current, offset, this.state.ranges.dayPeriod);

      if (current != dayPeriod) {
        this.hour < DAY_PERIOD_IN_HOURS ?
          this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS }) :
          this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
      }
    },

    /**
     * Step up or down hours
     *
     * @param  {Number} offset
     */
    stepHourBy(offset) {
      const current = this.hour;
      const hour = this._step(current, offset, this.state.ranges.hours);

      if (current != hour) {
        this.setState({ hour });
      }
    },

    /**
     * Step up or down minutes
     *
     * @param  {Number} offset
     */
    stepMinuteBy(offset) {
      const current = this.minute;
      const minute = this._step(current, offset, this.state.ranges.minutes);

      if (current != minute) {
        this.setState({ minute });
      }
    },

    /**
     * Step up or down seconds
     *
     * @param  {Number} offset
     */
    stepSecondBy(offset) {
      const current = this.second;
      const second = this._step(current, offset, this.state.ranges.seconds);

      if (current != second) {
        this.setState({ second });
      }
    },

    /**
     * Step up or down milliseconds
     *
     * @param  {Number} offset
     */
    stepMillisecondBy(offset) {
      const current = this.milliseconds;
      const millisecond = this._step(current, offset, this.state.ranges.millisecond);

      if (current != millisecond) {
        this.setState({ millisecond });
      }
    },

    /**
     * Checks if the time state is off step.
     *
     * @param  {Date} time
     * @return {Boolean}
     */
    _isOffStep(time) {
      const { min, step } = this.props;

      return (time.valueOf() - min.valueOf()) % step != 0;
    }
  };
}
PK
!<UWS S 4chrome/toolkit/content/global/bindings/timepicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 timekeeper.js */
 /* import-globals-from spinner.js */

"use strict";

function TimePicker(context) {
  this.context = context;
  this._attachEventListeners();
}

{
  const DAY_PERIOD_IN_HOURS = 12,
        DAY_IN_MS = 86400000;

  TimePicker.prototype = {
    /**
     * Initializes the time picker. Set the default states and properties.
     * @param  {Object} props
     *         {
     *           {Number} hour [optional]: Hour in 24 hours format (0~23), default is current hour
     *           {Number} minute [optional]: Minute (0~59), default is current minute
     *           {Number} min: Minimum time, in ms
     *           {Number} max: Maximum time, in ms
     *           {Number} step: Step size in ms
     *           {String} format [optional]: "12" for 12 hours, "24" for 24 hours format
     *           {String} locale [optional]: User preferred locale
     *         }
     */
    init(props) {
      this.props = props || {};
      this._setDefaultState();
      this._createComponents();
      this._setComponentStates();
    },

    /*
     * Set initial time states. If there's no hour & minute, it will
     * use the current time. The Time module keeps track of the time states,
     * and calculates the valid options given the time, min, max, step,
     * and format (12 or 24).
     */
    _setDefaultState() {
      const { hour, minute, min, max, step, format } = this.props;
      const now = new Date();

      let timerHour = hour == undefined ? now.getHours() : hour;
      let timerMinute = minute == undefined ? now.getMinutes() : minute;
      let timeKeeper = new TimeKeeper({
        min: new Date(Number.isNaN(min) ? 0 : min),
        max: new Date(Number.isNaN(max) ? DAY_IN_MS - 1 : max),
        step,
        format: format || "12"
      });
      timeKeeper.setState({ hour: timerHour, minute: timerMinute });

      this.state = { timeKeeper };
    },

    /**
     * Initalize the spinner components.
     */
    _createComponents() {
      const { locale, format } = this.props;
      const { timeKeeper } = this.state;

      const wrapSetValueFn = (setTimeFunction) => {
        return (value) => {
          setTimeFunction(value);
          this._setComponentStates();
          this._dispatchState();
        };
      };
      const numberFormat = new Intl.NumberFormat(locale).format;

      this.components = {
        hour: new Spinner({
          setValue: wrapSetValueFn(value => {
            timeKeeper.setHour(value);
            this.state.isHourSet = true;
          }),
          getDisplayString: hour => {
            if (format == "24") {
              return numberFormat(hour);
            }
            // Hour 0 in 12 hour format is displayed as 12.
            const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
            return hourIn12 == 0 ? numberFormat(12)
              : numberFormat(hourIn12);
          }
        }, this.context),
        minute: new Spinner({
          setValue: wrapSetValueFn(value => {
            timeKeeper.setMinute(value);
            this.state.isMinuteSet = true;
          }),
          getDisplayString: minute => numberFormat(minute)
        }, this.context)
      };

      this._insertLayoutElement({
        tag: "div",
        textContent: ":",
        className: "colon",
        insertBefore: this.components.minute.elements.container
      });

      // The AM/PM spinner is only available in 12hr mode
      // TODO: Replace AM & PM string with localized string
      if (format == "12") {
        this.components.dayPeriod = new Spinner({
          setValue: wrapSetValueFn(value => {
            timeKeeper.setDayPeriod(value);
            this.state.isDayPeriodSet = true;
          }),
          getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
          hideButtons: true
        }, this.context);

        this._insertLayoutElement({
          tag: "div",
          className: "spacer",
          insertBefore: this.components.dayPeriod.elements.container
        });
      }
    },

    /**
     * Insert element for layout purposes.
     *
     * @param {Object}
     *        {
     *          {String} tag: The tag to create
     *          {DOMElement} insertBefore: The DOM node to insert before
     *          {String} className [optional]: Class name
     *          {String} textContent [optional]: Text content
     *        }
     */
    _insertLayoutElement({ tag, insertBefore, className, textContent }) {
      let el = document.createElement(tag);
      el.textContent = textContent;
      el.className = className;
      this.context.insertBefore(el, insertBefore);
    },

    /**
     * Set component states.
     */
    _setComponentStates() {
      const { timeKeeper, isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
      const isInvalid = timeKeeper.state.isInvalid;
      // Value is set to min if it's first opened and time state is invalid
      const setToMinValue = !isHourSet && !isMinuteSet && !isDayPeriodSet && isInvalid;

      this.components.hour.setState({
        value: setToMinValue ? timeKeeper.ranges.hours[0].value : timeKeeper.hour,
        items: timeKeeper.ranges.hours,
        isInfiniteScroll: true,
        isValueSet: isHourSet,
        isInvalid
      });

      this.components.minute.setState({
        value: setToMinValue ? timeKeeper.ranges.minutes[0].value : timeKeeper.minute,
        items: timeKeeper.ranges.minutes,
        isInfiniteScroll: true,
        isValueSet: isMinuteSet,
        isInvalid
      });

      // The AM/PM spinner is only available in 12hr mode
      if (this.props.format == "12") {
        this.components.dayPeriod.setState({
          value: setToMinValue ? timeKeeper.ranges.dayPeriod[0].value : timeKeeper.dayPeriod,
          items: timeKeeper.ranges.dayPeriod,
          isInfiniteScroll: false,
          isValueSet: isDayPeriodSet,
          isInvalid
        });
      }
    },

    /**
     * Dispatch CustomEvent to pass the state of picker to the panel.
     */
    _dispatchState() {
      const { hour, minute } = this.state.timeKeeper;
      const { isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
      // The panel is listening to window for postMessage event, so we
      // do postMessage to itself to send data to input boxes.
      window.postMessage({
        name: "PickerPopupChanged",
        detail: {
          hour,
          minute,
          isHourSet,
          isMinuteSet,
          isDayPeriodSet
        }
      }, "*");
    },
    _attachEventListeners() {
      window.addEventListener("message", this);
      document.addEventListener("mousedown", this);
    },

    /**
     * Handle events.
     *
     * @param  {Event} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "message": {
          this.handleMessage(event);
          break;
        }
        case "mousedown": {
          // Use preventDefault to keep focus on input boxes
          event.preventDefault();
          event.target.setCapture();
          break;
        }
      }
    },

    /**
     * Handle postMessage events.
     *
     * @param {Event} event
     */
    handleMessage(event) {
      switch (event.data.name) {
        case "PickerSetValue": {
          this.set(event.data.detail);
          break;
        }
        case "PickerInit": {
          this.init(event.data.detail);
          break;
        }
      }
    },

    /**
     * Set the time state and update the components with the new state.
     *
     * @param {Object} timeState
     *        {
     *          {Number} hour [optional]
     *          {Number} minute [optional]
     *          {Number} second [optional]
     *          {Number} millisecond [optional]
     *        }
     */
    set(timeState) {
      if (timeState.hour != undefined) {
        this.state.isHourSet = true;
      }
      if (timeState.minute != undefined) {
        this.state.isMinuteSet = true;
      }
      this.state.timeKeeper.setState(timeState);
      this._setComponentStates();
    }
  };
}
PK
!<)u*O*O2chrome/toolkit/content/global/bindings/toolbar.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="toolbarBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="toolbar-base">
    <resources>
      <stylesheet src="chrome://global/skin/toolbar.css"/>
    </resources>
  </binding>

  <binding id="toolbox" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
    <implementation>
      <field name="palette">
        null
      </field>

      <field name="toolbarset">
        null
      </field>

      <field name="customToolbarCount">
        0
      </field>

      <field name="externalToolbars">
       []
      </field>

      <!-- Set by customizeToolbar.js -->
      <property name="customizing">
        <getter><![CDATA[
          return this.getAttribute("customizing") == "true";
        ]]></getter>
        <setter><![CDATA[
          if (val)
            this.setAttribute("customizing", "true");
          else
            this.removeAttribute("customizing");
          return val;
        ]]></setter>
      </property>

      <constructor>
        <![CDATA[
          // Look to see if there is a toolbarset.
          this.toolbarset = this.firstChild;
          while (this.toolbarset && this.toolbarset.localName != "toolbarset")
            this.toolbarset = this.toolbarset.nextSibling;

          if (this.toolbarset) {
            // Create each toolbar described by the toolbarset.
            var index = 0;
            while (this.toolbarset.hasAttribute("toolbar" + (++index))) {
              var toolbarInfo = this.toolbarset.getAttribute("toolbar" + index);
              var infoSplit = toolbarInfo.split(":");
              this.appendCustomToolbar(infoSplit[0], infoSplit[1]);
            }
          }
        ]]>
      </constructor>

      <method name="appendCustomToolbar">
        <parameter name="aName"/>
        <parameter name="aCurrentSet"/>
        <body>
          <![CDATA[
            if (!this.toolbarset)
              return null;
            var toolbar = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                                  "toolbar");
            toolbar.id = "__customToolbar_" + aName.replace(" ", "_");
            toolbar.setAttribute("customizable", "true");
            toolbar.setAttribute("customindex", ++this.customToolbarCount);
            toolbar.setAttribute("toolbarname", aName);
            toolbar.setAttribute("currentset", aCurrentSet);
            toolbar.setAttribute("mode", this.getAttribute("mode"));
            toolbar.setAttribute("iconsize", this.getAttribute("iconsize"));
            toolbar.setAttribute("context", this.toolbarset.getAttribute("context"));
            toolbar.setAttribute("class", "chromeclass-toolbar");

            this.insertBefore(toolbar, this.toolbarset);
            return toolbar;
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="toolbar" role="xul:toolbar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
    <implementation>
      <property name="toolbarName"
                onget="return this.getAttribute('toolbarname');"
                onset="this.setAttribute('toolbarname', val); return val;"/>

      <field name="_toolbox">null</field>
      <property name="toolbox" readonly="true">
        <getter><![CDATA[
          if (this._toolbox)
            return this._toolbox;

          let toolboxId = this.getAttribute("toolboxid");
          if (toolboxId) {
            let toolbox = document.getElementById(toolboxId);
            if (!toolbox) {
              let tbName = this.toolbarName;
              if (tbName)
                tbName = " (" + tbName + ")";
              else
                tbName = "";
              throw new Error(`toolbar ID ${this.id}${tbName}: toolboxid attribute '${toolboxId}' points to a toolbox that doesn't exist`);
            }

            if (toolbox.externalToolbars.indexOf(this) == -1)
              toolbox.externalToolbars.push(this);

            return this._toolbox = toolbox;
          }

          return this._toolbox = (this.parentNode &&
                                  this.parentNode.localName == "toolbox") ?
                                 this.parentNode : null;
        ]]></getter>
      </property>

      <constructor>
        <![CDATA[
          if (document.readyState == "complete") {
            this._init();
          } else {
            // Need to wait until XUL overlays are loaded. See bug 554279.
            let self = this;
            document.addEventListener("readystatechange", function(event) {
              if (document.readyState != "complete")
                return;
              document.removeEventListener("readystatechange", arguments.callee);
              self._init();
            });
          }
        ]]>
      </constructor>

      <method name="_init">
        <body>
        <![CDATA[
          // Searching for the toolbox palette in the toolbar binding because
          // toolbars are constructed first.
          var toolbox = this.toolbox;
          if (!toolbox)
            return;

          if (!toolbox.palette) {
            // Look to see if there is a toolbarpalette.
            var node = toolbox.firstChild;
            while (node) {
              if (node.localName == "toolbarpalette")
                break;
              node = node.nextSibling;
            }

            if (!node)
              return;

            // Hold on to the palette but remove it from the document.
            toolbox.palette = node;
            toolbox.removeChild(node);
          }

          // Build up our contents from the palette.
          var currentSet = this.getAttribute("currentset");
          if (!currentSet)
            currentSet = this.getAttribute("defaultset");
          if (currentSet)
            this.currentSet = currentSet;
        ]]>
        </body>
      </method>

      <method name="_idFromNode">
        <parameter name="aNode"/>
        <body>
        <![CDATA[
          if (aNode.getAttribute("skipintoolbarset") == "true")
            return "";

          switch (aNode.localName) {
            case "toolbarseparator":
              return "separator";
            case "toolbarspring":
              return "spring";
            case "toolbarspacer":
              return "spacer";
            default:
              return aNode.id;
          }
        ]]>
        </body>
      </method>

      <property name="currentSet">
        <getter>
          <![CDATA[
            var node = this.firstChild;
            var currentSet = [];
            while (node) {
              var id = this._idFromNode(node);
              if (id) {
                currentSet.push(id);
              }
              node = node.nextSibling;
            }

            return currentSet.join(",") || "__empty";
          ]]>
        </getter>

        <setter>
          <![CDATA[
            if (val == this.currentSet)
              return val;

            var ids = (val == "__empty") ? [] : val.split(",");

            var nodeidx = 0;
            var paletteItems = { }, added = { };

            var palette = this.toolbox ? this.toolbox.palette : null;

            // build a cache of items in the toolbarpalette
            var paletteChildren = palette ? palette.childNodes : [];
            for (let c = 0; c < paletteChildren.length; c++) {
              let curNode = paletteChildren[c];
              paletteItems[curNode.id] = curNode;
            }

            var children = this.childNodes;

            // iterate over the ids to use on the toolbar
            for (let i = 0; i < ids.length; i++) {
              let id = ids[i];
              // iterate over the existing nodes on the toolbar. nodeidx is the
              // spot where we want to insert items.
              let found = false;
              for (let c = nodeidx; c < children.length; c++) {
                let curNode = children[c];
                if (this._idFromNode(curNode) == id) {
                  // the node already exists. If c equals nodeidx, we haven't
                  // iterated yet, so the item is already in the right position.
                  // Otherwise, insert it here.
                  if (c != nodeidx) {
                    this.insertBefore(curNode, children[nodeidx]);
                  }

                  added[curNode.id] = true;
                  nodeidx++;
                  found = true;
                  break;
                }
              }
              if (found) {
                // move on to the next id
                continue;
              }

              // the node isn't already on the toolbar, so add a new one.
              var nodeToAdd = paletteItems[id] || this._getToolbarItem(id);
              if (nodeToAdd && !(nodeToAdd.id in added)) {
                added[nodeToAdd.id] = true;
                this.insertBefore(nodeToAdd, children[nodeidx] || null);
                nodeToAdd.setAttribute("removable", "true");
                nodeidx++;
              }
            }

            // remove any leftover removable nodes
            for (let i = children.length - 1; i >= nodeidx; i--) {
              let curNode = children[i];

              let curNodeId = this._idFromNode(curNode);
              // skip over fixed items
              if (curNodeId && curNode.getAttribute("removable") == "true") {
                if (palette)
                  palette.appendChild(curNode);
                else
                  this.removeChild(curNode);
              }
            }

            return val;
          ]]>
        </setter>
      </property>

      <field name="_newElementCount">0</field>
      <method name="_getToolbarItem">
        <parameter name="aId"/>
        <body>
          <![CDATA[
            const XUL_NS = "http://www.mozilla.org/keymaster/" +
                           "gatekeeper/there.is.only.xul";

            var newItem = null;
            switch (aId) {
              // Handle special cases
              case "separator":
              case "spring":
              case "spacer":
                newItem = document.createElementNS(XUL_NS, "toolbar" + aId);
                // Due to timers resolution Date.now() can be the same for
                // elements created in small timeframes.  So ids are
                // differentiated through a unique count suffix.
                newItem.id = aId + Date.now() + (++this._newElementCount);
                if (aId == "spring")
                  newItem.flex = 1;
                break;
              default:
                var toolbox = this.toolbox;
                if (!toolbox)
                  break;

                // look for an item with the same id, as the item may be
                // in a different toolbar.
                var item = document.getElementById(aId);
                if (item && item.parentNode &&
                    item.parentNode.localName == "toolbar" &&
                    item.parentNode.toolbox == toolbox) {
                  newItem = item;
                  break;
                }

                if (toolbox.palette) {
                  // Attempt to locate an item with a matching ID within
                  // the palette.
                  let paletteItem = this.toolbox.palette.firstChild;
                  while (paletteItem) {
                    if (paletteItem.id == aId) {
                      newItem = paletteItem;
                      break;
                    }
                    paletteItem = paletteItem.nextSibling;
                  }
                }
                break;
            }

            return newItem;
          ]]>
        </body>
      </method>

      <method name="insertItem">
        <parameter name="aId"/>
        <parameter name="aBeforeElt"/>
        <parameter name="aWrapper"/>
        <body>
          <![CDATA[
            var newItem = this._getToolbarItem(aId);
            if (!newItem)
              return null;

            var insertItem = newItem;
            // make sure added items are removable
            newItem.setAttribute("removable", "true");

            // Wrap the item in another node if so inclined.
            if (aWrapper) {
              aWrapper.appendChild(newItem);
              insertItem = aWrapper;
            }

            // Insert the palette item into the toolbar.
            if (aBeforeElt)
              this.insertBefore(insertItem, aBeforeElt);
            else
              this.appendChild(insertItem);

            return newItem;
          ]]>
        </body>
      </method>

      <method name="hasCustomInteractiveItems">
        <parameter name="aCurrentSet"/>
        <body><![CDATA[
          if (aCurrentSet == "__empty")
            return false;

          var defaultOrNoninteractive = (this.getAttribute("defaultset") || "")
                                          .split(",")
                                          .concat(["separator", "spacer", "spring"]);
          return aCurrentSet.split(",").some(function(item) {
            return defaultOrNoninteractive.indexOf(item) == -1;
          });
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="toolbar-menubar-autohide"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <implementation>
      <constructor>
        this._setInactive();
      </constructor>
      <destructor>
        this._setActive();
      </destructor>

      <field name="_inactiveTimeout">null</field>

      <field name="_contextMenuListener"><![CDATA[({
        toolbar: this,
        contextMenu: null,

        get active() {
          return !!this.contextMenu;
        },

        init(event) {
          var node = event.target;
          while (node != this.toolbar) {
            if (node.localName == "menupopup")
              return;
            node = node.parentNode;
          }

          var contextMenuId = this.toolbar.getAttribute("context");
          if (!contextMenuId)
            return;

          this.contextMenu = document.getElementById(contextMenuId);
          if (!this.contextMenu)
            return;

          this.contextMenu.addEventListener("popupshown", this);
          this.contextMenu.addEventListener("popuphiding", this);
          this.toolbar.addEventListener("mousemove", this);
        },
        handleEvent(event) {
          switch (event.type) {
            case "popupshown":
              this.toolbar.removeEventListener("mousemove", this);
              break;
            case "popuphiding":
            case "mousemove":
              this.toolbar._setInactiveAsync();
              this.toolbar.removeEventListener("mousemove", this);
              this.contextMenu.removeEventListener("popuphiding", this);
              this.contextMenu.removeEventListener("popupshown", this);
              this.contextMenu = null;
              break;
          }
        }
      })]]></field>

      <method name="_setInactive">
        <body><![CDATA[
          this.setAttribute("inactive", "true");
        ]]></body>
      </method>

      <method name="_setInactiveAsync">
        <body><![CDATA[
          this._inactiveTimeout = setTimeout(function(self) {
            if (self.getAttribute("autohide") == "true") {
              self._inactiveTimeout = null;
              self._setInactive();
            }
          }, 0, this);
        ]]></body>
      </method>

      <method name="_setActive">
        <body><![CDATA[
          if (this._inactiveTimeout) {
            clearTimeout(this._inactiveTimeout);
            this._inactiveTimeout = null;
          }
          this.removeAttribute("inactive");
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="DOMMenuBarActive"     action="this._setActive();"/>
      <handler event="popupshowing"         action="this._setActive();"/>
      <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
      <handler event="DOMMenuBarInactive"><![CDATA[
        if (!this._contextMenuListener.active)
          this._setInactiveAsync();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="toolbar-drag"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <implementation>
      <field name="_dragBindingAlive">true</field>
      <constructor><![CDATA[
        if (!this._draggableStarted) {
          this._draggableStarted = true;
          try {
            let tmp = {};
            Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
            let draggableThis = new tmp.WindowDraggingElement(this);
            draggableThis.mouseDownCheck = function(e) {
              // Don't move while customizing.
              return this._dragBindingAlive &&
                     this.getAttribute("customizing") != "true";
            };
          } catch (e) {}
        }
      ]]></constructor>
    </implementation>
  </binding>

  <binding id="menubar" role="xul:menubar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:menubar">
    <implementation>
       <field name="_active">false</field>
       <field name="_statusbar">null</field>
       <field name="_originalStatusText">null</field>
       <property name="statusbar" onget="return this.getAttribute('statusbar');"
                                  onset="this.setAttribute('statusbar', val); return val;"/>
       <method name="_updateStatusText">
          <parameter name="itemText"/>
          <body>
           <![CDATA[
            if (!this._active)
                return;
            var newText = itemText ? itemText : this._originalStatusText;
            if (newText != this._statusbar.label)
                this._statusbar.label = newText;
           ]]>
          </body>
        </method>
    </implementation>
    <handlers>
        <handler event="DOMMenuBarActive">
          <![CDATA[
            if (!this.statusbar) return;
            this._statusbar = document.getElementById(this.statusbar);
            if (!this._statusbar)
              return;
            this._active = true;
            this._originalStatusText = this._statusbar.label;
          ]]>
        </handler>
        <handler event="DOMMenuBarInactive">
          <![CDATA[
            if (!this._active)
              return;
            this._active = false;
            this._statusbar.label = this._originalStatusText;
          ]]>
        </handler>
        <handler event="DOMMenuItemActive">this._updateStatusText(event.target.statusText);</handler>
        <handler event="DOMMenuItemInactive">this._updateStatusText("");</handler>
    </handlers>
  </binding>

  <binding id="toolbardecoration" role="xul:toolbarseparator" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
  </binding>

  <binding id="toolbarpaletteitem" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:button">
    <content>
      <xul:hbox class="toolbarpaletteitem-box" flex="1" xbl:inherits="type,place">
        <children/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="toolbarpaletteitem-palette" extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
    <content>
      <xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
        <children/>
      </xul:hbox>
      <xul:label xbl:inherits="value=title"/>
    </content>
  </binding>

  <binding id="toolbarpaletteitem-palette-wrapping-label" extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
    <content>
      <xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
        <children/>
      </xul:hbox>
      <xul:label xbl:inherits="xbl:text=title"/>
    </content>
  </binding>

</bindings>
PK
!<^^8chrome/toolkit/content/global/bindings/toolbarbutton.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="toolbarbuttonBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="toolbarbutton" display="xul:button" role="xul:toolbarbutton"
           extends="chrome://global/content/bindings/button.xml#button-base">
    <resources>
      <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
    </resources>

    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,wrap"/>
      <xul:label class="toolbarbutton-multiline-text" flex="1"
                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
      <children includes="box"/>
    </content>
  </binding>

  <binding id="menu" display="xul:menu"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type,consumeanchor"/>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
      <xul:label class="toolbarbutton-multiline-text" flex="1"
                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
      <xul:dropmarker anonid="dropmarker" type="menu"
                      class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
    </content>
  </binding>

  <binding id="menu-vertical" display="xul:menu"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:hbox flex="1" align="center">
        <xul:vbox flex="1" align="center">
          <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
          <xul:label class="toolbarbutton-text" crop="right" flex="1"
                     xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
          <xul:label class="toolbarbutton-multiline-text" flex="1"
                     xbl:inherits="xbl:text=label,accesskey,wrap"/>
        </xul:vbox>
        <xul:dropmarker anonid="dropmarker" type="menu"
                        class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="menu-button" display="xul:menu"
           extends="chrome://global/content/bindings/button.xml#menu-button-base">
    <resources>
      <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
    </resources>

    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
                         anonid="button" flex="1" allowevents="true"
                         xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,badge,
                                       align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
      <xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
                      anonid="dropmarker" xbl:inherits="align,dir,pack,orient,disabled,label,open,consumeanchor"/>
    </content>
  </binding>

  <binding id="toolbarbutton-image"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
    </content>
  </binding>

  <binding id="toolbarbutton-badged"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:stack class="toolbarbutton-badge-stack">
        <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
        <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
      </xul:stack>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,wrap"/>
      <xul:label class="toolbarbutton-multiline-text" flex="1"
                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
    </content>
  </binding>

  <binding id="toolbarbutton-badged-menu" display="xul:menu"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <content>
      <children includes="observes|template|menupopup|panel|tooltip"/>
      <xul:stack class="toolbarbutton-badge-stack">
        <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
        <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
      </xul:stack>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
      <xul:label class="toolbarbutton-multiline-text" flex="1"
                 xbl:inherits="xbl:text=label,accesskey,wrap"/>
      <xul:dropmarker anonid="dropmarker" type="menu"
                      class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
    </content>
  </binding>
</bindings>
PK
!<%/chrome/toolkit/content/global/bindings/tree.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings [
<!ENTITY % treeDTD SYSTEM "chrome://global/locale/tree.dtd">
%treeDTD;
]>

<bindings id="treeBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tree-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
    <resources>
      <stylesheet src="chrome://global/skin/tree.css"/>
    </resources>
    <implementation>
      <method name="_isAccelPressed">
        <parameter name="aEvent"/>
        <body><![CDATA[
          return aEvent.getModifierState("Accel");
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="tree" extends="chrome://global/content/bindings/tree.xml#tree-base" role="xul:tree">
    <content hidevscroll="true" hidehscroll="true" clickthrough="never">
      <children includes="treecols"/>
      <xul:stack class="tree-stack" flex="1">
        <xul:treerows class="tree-rows" flex="1" xbl:inherits="hidevscroll">
          <children/>
        </xul:treerows>
        <xul:textbox anonid="input" class="tree-input" left="0" top="0" hidden="true"/>
      </xul:stack>
      <xul:hbox xbl:inherits="collapsed=hidehscroll">
        <xul:scrollbar orient="horizontal" flex="1" increment="16" style="position:relative; z-index:2147483647;"/>
        <xul:scrollcorner xbl:inherits="collapsed=hidevscroll"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMXULTreeElement, nsIDOMXULMultiSelectControlElement">

      <!-- ///////////////// nsIDOMXULTreeElement ///////////////// -->

      <property name="columns"
                onget="return this.treeBoxObject.columns;"/>

      <property name="view"
                onget="return this.treeBoxObject.view ?
                       this.treeBoxObject.view.QueryInterface(Components.interfaces.nsITreeView) :
                       null;"
                onset="return this.treeBoxObject.view = val;"/>

      <property name="body"
                onget="return this.treeBoxObject.treeBody;"/>

      <property name="editable"
                onget="return this.getAttribute('editable') == 'true';"
                onset="if (val) this.setAttribute('editable', 'true');
                       else this.removeAttribute('editable'); return val;"/>

      <!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->

      <!-- ///////////////// nsIDOMXULMultiSelectControlElement ///////////////// -->

      <property name="selType"
                onget="return this.getAttribute('seltype')"
                onset="this.setAttribute('seltype', val); return val;"/>

      <property name="currentIndex"
                onget="return this.view ? this.view.selection.currentIndex: - 1;"
                onset="if (this.view) return this.view.selection.currentIndex = val; return val;"/>

      <property name="treeBoxObject"
                onget="return this.boxObject;"
                readonly="true"/>
      <property name="contentView"
                onget="return this.view; /*.QueryInterface(Components.interfaces.nsITreeContentView)*/"
                readonly="true"/>
      <property name="builderView"
                onget="return this.view; /*.QueryInterface(Components.interfaces.nsIXULTreeBuilder)*/"
                readonly="true"/>
      <field name="pageUpOrDownMovesSelection">
        !/Mac/.test(navigator.platform)
      </field>
      <property name="keepCurrentInView"
                onget="return (this.getAttribute('keepcurrentinview') == 'true');"
                onset="if (val) this.setAttribute('keepcurrentinview', 'true');
                       else this.removeAttribute('keepcurrentinview'); return val;"/>

      <property name="enableColumnDrag"
                onget="return this.hasAttribute('enableColumnDrag');"
                onset="if (val) this.setAttribute('enableColumnDrag', 'true');
                       else this.removeAttribute('enableColumnDrag'); return val;"/>

      <field name="_inputField">null</field>

      <property name="inputField" readonly="true">
        <getter><![CDATA[
          if (!this._inputField)
            this._inputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
          return this._inputField;
        ]]></getter>
      </property>

      <property name="disableKeyNavigation"
                onget="return this.hasAttribute('disableKeyNavigation');"
                onset="if (val) this.setAttribute('disableKeyNavigation', 'true');
                       else this.removeAttribute('disableKeyNavigation'); return val;"/>

      <field name="_editingRow">-1</field>
      <field name="_editingColumn">null</field>

      <property name="editingRow" readonly="true"
                onget="return this._editingRow;"/>
      <property name="editingColumn" readonly="true"
                onget="return this._editingColumn;"/>

      <property name="_selectDelay"
                onset="this.setAttribute('_selectDelay', val);"
                onget="return this.getAttribute('_selectDelay') || 50;"/>
      <field name="_columnsDirty">true</field>
      <field name="_lastKeyTime">0</field>
      <field name="_incrementalString">""</field>

      <field name="_touchY">-1</field>

      <method name="_ensureColumnOrder">
        <body><![CDATA[
          if (!this._columnsDirty)
            return;

          if (this.columns) {
            // update the ordinal position of each column to assure that it is
            // an odd number and 2 positions above its next sibling
            var cols = [];
            var i;
            for (var col = this.columns.getFirstColumn(); col; col = col.getNext())
              cols.push(col.element);
            for (i = 0; i < cols.length; ++i)
              cols[i].setAttribute("ordinal", (i * 2) + 1);

            // update the ordinal positions of splitters to even numbers, so that
            // they are in between columns
            var splitters = this.getElementsByTagName("splitter");
            for (i = 0; i < splitters.length; ++i)
              splitters[i].setAttribute("ordinal", (i + 1) * 2);
          }
          this._columnsDirty = false;
        ]]></body>
      </method>

      <method name="_reorderColumn">
        <parameter name="aColMove"/>
        <parameter name="aColBefore"/>
        <parameter name="aBefore"/>
        <body><![CDATA[
          this._ensureColumnOrder();

          var i;
          var cols = [];
          var col = this.columns.getColumnFor(aColBefore);
          if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
            if (aBefore)
              cols.push(aColBefore);
            for (col = col.getNext(); col.element != aColMove;
                 col = col.getNext())
              cols.push(col.element);

            aColMove.ordinal = cols[0].ordinal;
            for (i = 0; i < cols.length; ++i)
              cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
          } else if (aColBefore.ordinal != aColMove.ordinal) {
            if (!aBefore)
              cols.push(aColBefore);
            for (col = col.getPrevious(); col.element != aColMove;
                 col = col.getPrevious())
              cols.push(col.element);

            aColMove.ordinal = cols[0].ordinal;
            for (i = 0; i < cols.length; ++i)
              cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
          }
        ]]></body>
      </method>

      <method name="_getColumnAtX">
        <parameter name="aX"/>
        <parameter name="aThresh"/>
        <parameter name="aPos"/>
        <body><![CDATA[
          var isRTL = document.defaultView.getComputedStyle(this)
                              .direction == "rtl";

          if (aPos)
            aPos.value = isRTL ? "after" : "before";

          var columns = [];
          var col = this.columns.getFirstColumn();
          while (col) {
            columns.push(col);
            col = col.getNext();
          }
          if (isRTL)
            columns.reverse();
          var currentX = this.boxObject.x;
          var adjustedX = aX + this.treeBoxObject.horizontalPosition;
          for (var i = 0; i < columns.length; ++i) {
            col = columns[i];
            var cw = col.element.boxObject.width;
            if (cw > 0) {
              currentX += cw;
              if (currentX - (cw * aThresh) > adjustedX)
                return col.element;
            }
          }

          if (aPos)
            aPos.value = isRTL ? "before" : "after";
          return columns.pop().element;
        ]]></body>
      </method>

      <method name="changeOpenState">
        <parameter name="row"/>
        <!-- Optional parameter openState == true or false to set.
             No openState param == toggle -->
        <parameter name="openState"/>
        <body><![CDATA[
          if (row < 0 || !this.view.isContainer(row)) {
            return false;
          }

          if (this.view.isContainerOpen(row) != openState) {
            this.view.toggleOpenState(row);
            if (row == this.currentIndex) {
              // Only fire event when current row is expanded or collapsed
              // because that's all the assistive technology really cares about.
              var event = document.createEvent("Events");
              event.initEvent("OpenStateChange", true, true);
              this.dispatchEvent(event);
            }
            return true;
          }
          return false;
        ]]></body>
      </method>

      <property name="_cellSelType">
        <getter>
          <![CDATA[
            var seltype = this.selType;
            if (seltype == "cell" || seltype == "text")
              return seltype;
            return null;
          ]]>
        </getter>
      </property>

      <method name="_getNextColumn">
        <parameter name="row"/>
        <parameter name="left"/>
        <body><![CDATA[
          var col = this.view.selection.currentColumn;
          if (col) {
            col = left ? col.getPrevious() : col.getNext();
          } else {
            col = this.columns.getKeyColumn();
          }
          while (col && (col.width == 0 || !col.selectable ||
                 !this.view.isSelectable(row, col)))
            col = left ? col.getPrevious() : col.getNext();
          return col;
        ]]></body>
      </method>

      <method name="_keyNavigate">
        <parameter name="event"/>
        <body><![CDATA[
          var key = String.fromCharCode(event.charCode).toLowerCase();
          if (event.timeStamp - this._lastKeyTime > 1000)
            this._incrementalString = key;
          else
            this._incrementalString += key;
          this._lastKeyTime = event.timeStamp;

          var length = this._incrementalString.length;
          var incrementalString = this._incrementalString;
          var charIndex = 1;
          while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1])
            charIndex++;
          // If all letters in incremental string are same, just try to match the first one
          if (charIndex == length) {
            length = 1;
            incrementalString = incrementalString.substring(0, length);
          }

          var keyCol = this.columns.getKeyColumn();
          var rowCount = this.view.rowCount;
          var start = 1;

          var c = this.currentIndex;
          if (length > 1) {
            start = 0;
            if (c < 0)
              c = 0;
          }

          for (var i = 0; i < rowCount; i++) {
            var l = (i + start + c) % rowCount;
            var cellText = this.view.getCellText(l, keyCol);
            cellText = cellText.substring(0, length).toLowerCase();
            if (cellText == incrementalString)
              return l;
          }
          return -1;
        ]]></body>
      </method>

      <method name="startEditing">
        <parameter name="row"/>
        <parameter name="column"/>
        <body>
          <![CDATA[
            if (!this.editable)
              return false;
            if (row < 0 || row >= this.view.rowCount || !column)
              return false;
            if (column.type != Components.interfaces.nsITreeColumn.TYPE_TEXT &&
                column.type != Components.interfaces.nsITreeColumn.TYPE_PASSWORD)
              return false;
            if (column.cycler || !this.view.isEditable(row, column))
              return false;

            // Beyond this point, we are going to edit the cell.
            if (this._editingColumn)
              this.stopEditing();

            var input = this.inputField;

            var box = this.treeBoxObject;
            box.ensureCellIsVisible(row, column);

            // Get the coordinates of the text inside the cell.
            var textRect = box.getCoordsForCellItem(row, column, "text");

            // Get the coordinates of the cell itself.
            var cellRect = box.getCoordsForCellItem(row, column, "cell");

            // Calculate the top offset of the textbox.
            var style = window.getComputedStyle(input);
            var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
            input.top = textRect.y - topadj;

            // The leftside of the textbox is aligned to the left side of the text
            // in LTR mode, and left side of the cell in RTL mode.
            var left, widthdiff;
            if (style.direction == "rtl") {
              left = cellRect.x;
              widthdiff = cellRect.x - textRect.x;
            } else {
              left = textRect.x;
              widthdiff = textRect.x - cellRect.x;
            }

            input.left = left;
            input.height = textRect.height + topadj +
                           parseInt(style.borderBottomWidth) +
                           parseInt(style.paddingBottom);
            input.width = cellRect.width - widthdiff;
            input.hidden = false;

            input.value = this.view.getCellText(row, column);
            var selectText = function selectText() {
              input.select();
              input.inputField.focus();
            }
            setTimeout(selectText, 0);

            this._editingRow = row;
            this._editingColumn = column;
            this.setAttribute("editing", "true");

            box.invalidateCell(row, column);
            return true;
          ]]>
        </body>
      </method>

      <method name="stopEditing">
        <parameter name="accept"/>
        <body>
          <![CDATA[
            if (!this._editingColumn)
              return;

            var input = this.inputField;
            var editingRow = this._editingRow;
            var editingColumn = this._editingColumn;
            this._editingRow = -1;
            this._editingColumn = null;

            if (accept) {
              var value = input.value;
              this.view.setCellText(editingRow, editingColumn, value);
            }
            input.hidden = true;
            input.value = "";
            this.removeAttribute("editing");
          ]]>
        </body>
      </method>

      <method name="_moveByOffset">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this._isAccelPressed(event) && this.view.selection.single) {
              this.treeBoxObject.scrollByLines(offset);
              return;
            }

            var c = this.currentIndex + offset;
            if (offset > 0 ? c > edge : c < edge) {
              if (this.view.selection.isSelected(edge) && this.view.selection.count <= 1)
                return;
              c = edge;
            }

            var cellSelType = this._cellSelType;
            if (cellSelType) {
              var column = this.view.selection.currentColumn;
              if (!column)
                return;

              while ((offset > 0 ? c <= edge : c >= edge) && !this.view.isSelectable(c, column))
                c += offset;
              if (offset > 0 ? c > edge : c < edge)
                return;
            }

            if (!this._isAccelPressed(event))
              this.view.selection.timedSelect(c, this._selectDelay);
            else // Ctrl+Up/Down moves the anchor without selecting
              this.currentIndex = c;
            this.treeBoxObject.ensureRowIsVisible(c);
          ]]>
        </body>
      </method>

      <method name="_moveByOffsetShift">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.selection.single) {
              this.treeBoxObject.scrollByLines(offset);
              return;
            }

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            var c = this.currentIndex;
            if (c == -1)
                c = 0;

            if (c == edge) {
              if (this.view.selection.isSelected(c))
                return;
            }

            // Extend the selection from the existing pivot, if any
            this.view.selection.rangedSelect(-1, c + offset,
                                             this._isAccelPressed(event));
            this.treeBoxObject.ensureRowIsVisible(c + offset);

          ]]>
        </body>
      </method>

      <method name="_moveByPage">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.pageUpOrDownMovesSelection == this._isAccelPressed(event)) {
               this.treeBoxObject.scrollByPages(offset);
               return;
            }

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            var c = this.currentIndex;
            if (c == -1)
              return;

            if (c == edge && this.view.selection.isSelected(c)) {
              this.treeBoxObject.ensureRowIsVisible(c);
              return;
            }
            var i = this.treeBoxObject.getFirstVisibleRow();
            var p = this.treeBoxObject.getPageLength();

            if (offset > 0) {
              i += p - 1;
              if (c >= i) {
                 i = c + p;
                 this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i);
              }
              i = i > edge ? edge : i;

            } else if (c <= i) {
               i = c <= p ? 0 : c - p;
               this.treeBoxObject.ensureRowIsVisible(i);
            }
            this.view.selection.timedSelect(i, this._selectDelay);
          ]]>
        </body>
      </method>

      <method name="_moveByPageShift">
        <parameter name="offset"/>
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0) &&
                !(this.pageUpOrDownMovesSelection == this._isAccelPressed(event))) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            if (this.view.selection.single)
              return;

            var c = this.currentIndex;
            if (c == -1)
              return;
            if (c == edge && this.view.selection.isSelected(c)) {
              this.treeBoxObject.ensureRowIsVisible(edge);
              return;
            }
            var i = this.treeBoxObject.getFirstVisibleRow();
            var p = this.treeBoxObject.getPageLength();

            if (offset > 0) {
              i += p - 1;
              if (c >= i) {
                 i = c + p;
                 this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i);
              }
              // Extend the selection from the existing pivot, if any
              this.view.selection.rangedSelect(-1, i > edge ? edge : i, this._isAccelPressed(event));

            } else {

              if (c <= i) {
                 i = c <= p ? 0 : c - p;
                 this.treeBoxObject.ensureRowIsVisible(i);
              }
              // Extend the selection from the existing pivot, if any
              this.view.selection.rangedSelect(-1, i, this._isAccelPressed(event));
            }

          ]]>
        </body>
      </method>

      <method name="_moveToEdge">
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.selection.isSelected(edge) && this.view.selection.count == 1) {
              this.currentIndex = edge;
              return;
            }

            // Normal behaviour is to select the first/last row
            if (!this._isAccelPressed(event))
              this.view.selection.timedSelect(edge, this._selectDelay);

            // In a multiselect tree Ctrl+Home/End moves the anchor
            else if (!this.view.selection.single)
              this.currentIndex = edge;

            this.treeBoxObject.ensureRowIsVisible(edge);
          ]]>
        </body>
      </method>

      <method name="_moveToEdgeShift">
        <parameter name="edge"/>
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.preventDefault();

            if (this.view.rowCount == 0)
              return;

            if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
              this.view.selection.timedSelect(0, this._selectDelay);
              return;
            }

            if (this.view.selection.single ||
                (this.view.selection.isSelected(edge)) && this.view.selection.isSelected(this.currentIndex))
              return;

            // Extend the selection from the existing pivot, if any.
            // -1 doesn't work here, so using currentIndex instead
            this.view.selection.rangedSelect(this.currentIndex, edge, this._isAccelPressed(event));

            this.treeBoxObject.ensureRowIsVisible(edge);
          ]]>
        </body>
      </method>
      <method name="_handleEnter">
        <parameter name="event"/>
        <body><![CDATA[
          if (this._editingColumn) {
            this.stopEditing(true);
            this.focus();
            return true;
          }

          if (/Mac/.test(navigator.platform)) {
            // See if we can edit the cell.
            var row = this.currentIndex;
            if (this._cellSelType) {
              var column = this.view.selection.currentColumn;
              var startedEditing = this.startEditing(row, column);
              if (startedEditing)
                return true;
            }
          }
          return this.changeOpenState(this.currentIndex);
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="touchstart">
        <![CDATA[
          function isScrollbarElement(target) {
            return (target.localName == "thumb" || target.localName == "slider")
                && target.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          }
          if (event.touches.length > 1 || isScrollbarElement(event.touches[0].target)) {
            // Multiple touch points detected, abort. In particular this aborts
            // the panning gesture when the user puts a second finger down after
            // already panning with one finger. Aborting at this point prevents
            // the pan gesture from being resumed until all fingers are lifted
            // (as opposed to when the user is back down to one finger).
            // Additionally, if the user lands on the scrollbar don't use this
            // code for scrolling, instead allow gecko to handle scrollbar
            // interaction normally.
            this._touchY = -1;
          } else {
            this._touchY = event.touches[0].screenY;
          }
        ]]>
      </handler>
      <handler event="touchmove">
        <![CDATA[
          if (event.touches.length == 1 &&
              this._touchY >= 0) {
            var deltaY = this._touchY - event.touches[0].screenY;
            var lines = Math.trunc(deltaY / this.treeBoxObject.rowHeight);
            if (Math.abs(lines) > 0) {
              this.treeBoxObject.scrollByLines(lines);
              deltaY -= lines * this.treeBoxObject.rowHeight;
              this._touchY = event.touches[0].screenY + deltaY;
            }
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="touchend">
        <![CDATA[
          this._touchY = -1;
        ]]>
      </handler>
      <handler event="MozMousePixelScroll">
        <![CDATA[
          if (!(this.getAttribute("allowunderflowscroll") == "true" &&
                this.getAttribute("hidevscroll") == "true"))
            event.preventDefault();
        ]]>
      </handler>
      <handler event="DOMMouseScroll">
        <![CDATA[
          if (!(this.getAttribute("allowunderflowscroll") == "true" &&
                this.getAttribute("hidevscroll") == "true"))
            event.preventDefault();

          if (this._editingColumn)
            return;
          if (event.axis == event.HORIZONTAL_AXIS)
            return;

          var rows = event.detail;
          if (rows == UIEvent.SCROLL_PAGE_UP)
            this.treeBoxObject.scrollByPages(-1);
          else if (rows == UIEvent.SCROLL_PAGE_DOWN)
            this.treeBoxObject.scrollByPages(1);
          else
            this.treeBoxObject.scrollByLines(rows);
        ]]>
      </handler>
      <handler event="MozSwipeGesture" preventdefault="true">
        <![CDATA[
          // Figure out which row to show
          let targetRow = 0;

          // Only handle swipe gestures up and down
          switch (event.direction) {
            case event.DIRECTION_DOWN:
              targetRow = this.view.rowCount - 1;
              // Fall through for actual action
            case event.DIRECTION_UP:
              this.treeBoxObject.ensureRowIsVisible(targetRow);
              break;
          }
        ]]>
      </handler>
      <handler event="select" phase="target"
               action="if (event.originalTarget == this) this.stopEditing(true);"/>
      <handler event="focus">
        <![CDATA[
          this.treeBoxObject.focused = true;
          if (this.currentIndex == -1 && this.view.rowCount > 0) {
            this.currentIndex = this.treeBoxObject.getFirstVisibleRow();
          }
          if (this._cellSelType && !this.view.selection.currentColumn) {
            var col = this._getNextColumn(this.currentIndex, false);
            this.view.selection.currentColumn = col;
          }
        ]]>
      </handler>
      <handler event="blur" action="this.treeBoxObject.focused = false;"/>
      <handler event="blur" phase="capturing"
               action="if (event.originalTarget == this.inputField.inputField) this.stopEditing(true);"/>
      <handler event="keydown" keycode="VK_RETURN">
        if (this._handleEnter(event)) {
          event.stopPropagation();
          event.preventDefault();
        }
      </handler>
      <!-- Use F2 key to enter text editing. -->
      <handler event="keydown" keycode="VK_F2">
        <![CDATA[
          if (!this._cellSelType)
            return;
          var row = this.currentIndex;
          var column = this.view.selection.currentColumn;
          if (this.startEditing(row, column))
            event.preventDefault();
        ]]>
      </handler>

      <handler event="keydown" keycode="VK_ESCAPE">
        <![CDATA[
          if (this._editingColumn) {
            this.stopEditing(false);
            this.focus();
            event.stopPropagation();
            event.preventDefault();
          }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_LEFT">
        <![CDATA[
         if (this._editingColumn)
           return;

         var row = this.currentIndex;
         if (row < 0)
           return;

         var cellSelType = this._cellSelType;
         var checkContainers = true;

         var currentColumn;
         if (cellSelType) {
           currentColumn = this.view.selection.currentColumn;
           if (currentColumn && !currentColumn.primary)
             checkContainers = false;
         }

         if (checkContainers) {
           if (this.changeOpenState(this.currentIndex, false)) {
             event.preventDefault();
             return;
           }
           var parentIndex = this.view.getParentIndex(this.currentIndex);
           if (parentIndex >= 0) {
             if (cellSelType && !this.view.isSelectable(parentIndex, currentColumn)) {
               return;
             }
             this.view.selection.select(parentIndex);
             this.treeBoxObject.ensureRowIsVisible(parentIndex);
             event.preventDefault();
             return;
           }
         }

         if (cellSelType) {
           var col = this._getNextColumn(row, true);
           if (col) {
             this.view.selection.currentColumn = col;
             this.treeBoxObject.ensureCellIsVisible(row, col);
             event.preventDefault();
           }
         }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_RIGHT">
        <![CDATA[
         if (this._editingColumn)
           return;

          var row = this.currentIndex;
          if (row < 0)
            return;

          var cellSelType = this._cellSelType;
          var checkContainers = true;

          var currentColumn;
          if (cellSelType) {
            currentColumn = this.view.selection.currentColumn;
            if (currentColumn && !currentColumn.primary)
              checkContainers = false;
          }

          if (checkContainers) {
            if (this.changeOpenState(row, true)) {
              event.preventDefault();
              return;
            }
            var c = row + 1;
            var view = this.view;
            if (c < view.rowCount &&
                view.getParentIndex(c) == row) {
              // If already opened, select the first child.
              // The getParentIndex test above ensures that the children
              // are already populated and ready.
              if (cellSelType && !this.view.isSelectable(c, currentColumn)) {
                let col = this._getNextColumn(c, false);
                if (col) {
                  this.view.selection.currentColumn = col;
                }
              }
              this.view.selection.timedSelect(c, this._selectDelay);
              this.treeBoxObject.ensureRowIsVisible(c);
              event.preventDefault();
              return;
            }
          }

          if (cellSelType) {
            let col = this._getNextColumn(row, false);
            if (col) {
              this.view.selection.currentColumn = col;
              this.treeBoxObject.ensureCellIsVisible(row, col);
              event.preventDefault();
            }
          }
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_UP" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffset(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_DOWN" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffset(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_UP" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffsetShift(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_DOWN" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByOffsetShift(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPage(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPage(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPageShift(-1, 0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveByPageShift(1, this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_HOME" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdge(0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_END" modifiers="accel any">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdge(this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_HOME" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdgeShift(0, event);
        ]]>
      </handler>
      <handler event="keydown" keycode="VK_END" modifiers="accel any, shift">
        <![CDATA[
          if (this._editingColumn)
            return;
          this._moveToEdgeShift(this.view.rowCount - 1, event);
        ]]>
      </handler>
      <handler event="keypress">
        <![CDATA[
         if (this._editingColumn)
           return;

         if (event.charCode == " ".charCodeAt(0)) {
           var c = this.currentIndex;
           if (!this.view.selection.isSelected(c) ||
               (!this.view.selection.single && this._isAccelPressed(event))) {
             this.view.selection.toggleSelect(c);
             event.preventDefault();
           }
         } else if (!this.disableKeyNavigation && event.charCode > 0 &&
                    !event.altKey && !this._isAccelPressed(event) &&
                    !event.metaKey && !event.ctrlKey) {
           var l = this._keyNavigate(event);
           if (l >= 0) {
             this.view.selection.timedSelect(l, this._selectDelay);
             this.treeBoxObject.ensureRowIsVisible(l);
           }
           event.preventDefault();
         }
         ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="treecols" role="xul:treecolumns">
    <resources>
      <stylesheet src="chrome://global/skin/tree.css"/>
    </resources>
    <content orient="horizontal">
      <xul:hbox class="tree-scrollable-columns" flex="1">
        <children includes="treecol|splitter"/>
      </xul:hbox>
      <xul:treecolpicker class="treecol-image" fixed="true" xbl:inherits="tooltiptext=pickertooltiptext"/>
    </content>
    <implementation>
      <constructor><![CDATA[
        // Set resizeafter="farthest" on the splitters if nothing else has been
        // specified.
        Array.forEach(this.getElementsByTagName("splitter"), function(splitter) {
          if (!splitter.hasAttribute("resizeafter"))
            splitter.setAttribute("resizeafter", "farthest");
        });
      ]]></constructor>
    </implementation>
  </binding>

  <binding id="treerows" extends="chrome://global/content/bindings/tree.xml#tree-base">
    <content>
      <xul:hbox flex="1" class="tree-bodybox">
        <children/>
      </xul:hbox>
      <xul:scrollbar height="0" minwidth="0" minheight="0" orient="vertical" xbl:inherits="collapsed=hidevscroll" style="position:relative; z-index:2147483647;"/>
    </content>
    <handlers>
      <handler event="underflow">
        <![CDATA[
          // Scrollport event orientation
          // 0: vertical
          // 1: horizontal
          // 2: both (not used)
          var tree = document.getBindingParent(this);
          if (event.detail == 1)
            tree.setAttribute("hidehscroll", "true");
          else if (event.detail == 0)
            tree.setAttribute("hidevscroll", "true");
          event.stopPropagation();
        ]]>
      </handler>
      <handler event="overflow">
        <![CDATA[
          var tree = document.getBindingParent(this);
          if (event.detail == 1)
            tree.removeAttribute("hidehscroll");
          else if (event.detail == 0)
            tree.removeAttribute("hidevscroll");
          event.stopPropagation();
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="treebody" extends="chrome://global/content/bindings/tree.xml#tree-base">
    <implementation>
      <constructor>
        if ("_ensureColumnOrder" in this.parentNode)
          this.parentNode._ensureColumnOrder();
      </constructor>

      <field name="_lastSelectedRow">
        -1
      </field>
    </implementation>
    <handlers>
      <!-- If there is no modifier key, we select on mousedown, not
           click, so that drags work correctly. -->
      <handler event="mousedown" clickcount="1">
      <![CDATA[
         if (this.parentNode.disabled)
           return;
         if (((!this._isAccelPressed(event) ||
             !this.parentNode.pageUpOrDownMovesSelection) &&
             !event.shiftKey && !event.metaKey) ||
             this.parentNode.view.selection.single) {
           var b = this.parentNode.treeBoxObject;
           var cell = b.getCellAt(event.clientX, event.clientY);
           var view = this.parentNode.view;

           // save off the last selected row
           this._lastSelectedRow = cell.row;

           if (cell.row == -1)
             return;

           if (cell.childElt == "twisty")
             return;

           if (cell.col && event.button == 0) {
             if (cell.col.cycler) {
               view.cycleCell(cell.row, cell.col);
               return;
             } else if (cell.col.type == Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) {
               if (this.parentNode.editable && cell.col.editable &&
                   view.isEditable(cell.row, cell.col)) {
                 var value = view.getCellValue(cell.row, cell.col);
                 value = value == "true" ? "false" : "true";
                 view.setCellValue(cell.row, cell.col, value);
                 return;
               }
             }
           }

           var cellSelType = this.parentNode._cellSelType;
           if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image")
             return;

           if (cellSelType) {
             if (!cell.col.selectable ||
                 !view.isSelectable(cell.row, cell.col)) {
               return;
             }
           }

           if (!view.selection.isSelected(cell.row)) {
             view.selection.select(cell.row);
             b.ensureRowIsVisible(cell.row);
           }

           if (cellSelType) {
             view.selection.currentColumn = cell.col;
           }
         }
      ]]>
      </handler>

      <!-- On a click (up+down on the same item), deselect everything
           except this item. -->
      <handler event="click" button="0" clickcount="1">
      <![CDATA[
        if (this.parentNode.disabled)
          return;
        var b = this.parentNode.treeBoxObject;
        var cell = b.getCellAt(event.clientX, event.clientY);
        var view = this.parentNode.view;

        if (cell.row == -1)
          return;

        if (cell.childElt == "twisty") {
          if (view.selection.currentIndex >= 0 &&
              view.isContainerOpen(cell.row)) {
            var parentIndex = view.getParentIndex(view.selection.currentIndex);
            while (parentIndex >= 0 && parentIndex != cell.row)
              parentIndex = view.getParentIndex(parentIndex);
            if (parentIndex == cell.row) {
              var parentSelectable = true;
              if (this.parentNode._cellSelType) {
                var currentColumn = view.selection.currentColumn;
                if (!view.isSelectable(parentIndex, currentColumn))
                  parentSelectable = false;
              }
              if (parentSelectable)
                view.selection.select(parentIndex);
            }
          }
          this.parentNode.changeOpenState(cell.row);
          return;
        }

        if (!view.selection.single) {
          var augment = this._isAccelPressed(event);
          if (event.shiftKey) {
            view.selection.rangedSelect(-1, cell.row, augment);
            b.ensureRowIsVisible(cell.row);
            return;
          }
          if (augment) {
            view.selection.toggleSelect(cell.row);
            b.ensureRowIsVisible(cell.row);
            view.selection.currentIndex = cell.row;
            return;
          }
        }

        /* We want to deselect all the selected items except what was
          clicked, UNLESS it was a right-click.  We have to do this
          in click rather than mousedown so that you can drag a
          selected group of items */

        if (!cell.col) return;

        // if the last row has changed in between the time we
        // mousedown and the time we click, don't fire the select handler.
        // see bug #92366
        if (!cell.col.cycler && this._lastSelectedRow == cell.row &&
            cell.col.type != Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) {

          var cellSelType = this.parentNode._cellSelType;
          if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image")
            return;

          if (cellSelType) {
            if (!cell.col.selectable ||
                !view.isSelectable(cell.row, cell.col)) {
              return;
            }
          }

          view.selection.select(cell.row);
          b.ensureRowIsVisible(cell.row);

          if (cellSelType) {
            view.selection.currentColumn = cell.col;
          }
        }
      ]]>
      </handler>

      <!-- double-click -->
      <handler event="click" clickcount="2">
      <![CDATA[
        if (this.parentNode.disabled)
          return;
        var tbo = this.parentNode.treeBoxObject;
        var view = this.parentNode.view;
        var row = view.selection.currentIndex;

        if (row == -1)
          return;

        var cell = tbo.getCellAt(event.clientX, event.clientY);

        if (cell.childElt != "twisty") {
          view.selection.currentColumn = cell.col;
          this.parentNode.startEditing(row, cell.col);
        }

        if (this.parentNode._editingColumn || !view.isContainer(row))
          return;

        // Cyclers and twisties respond to single clicks, not double clicks
        if (cell.col && !cell.col.cycler && cell.childElt != "twisty")
          this.parentNode.changeOpenState(row);
      ]]>
      </handler>

    </handlers>
  </binding>

  <binding id="treecol-base" role="xul:treecolumnitem"
           extends="chrome://global/content/bindings/tree.xml#tree-base">
    <implementation>
      <constructor>
        this.parentNode.parentNode._columnsDirty = true;
      </constructor>

      <property name="ordinal">
        <getter><![CDATA[
          var val = this.getAttribute("ordinal");
          if (val == "")
            return "1";

          return "" + (val == "0" ? 0 : parseInt(val));
        ]]></getter>
        <setter><![CDATA[
          this.setAttribute("ordinal", val);
          return val;
        ]]></setter>
      </property>

      <property name="_previousVisibleColumn">
        <getter><![CDATA[
          var sib = this.boxObject.previousSibling;
          while (sib) {
            if (sib.localName == "treecol" && sib.boxObject.width > 0 && sib.parentNode == this.parentNode)
              return sib;
            sib = sib.boxObject.previousSibling;
          }
          return null;
        ]]></getter>
      </property>

      <method name="_onDragMouseMove">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var col = document.treecolDragging;
          if (!col) return;

          // determine if we have moved the mouse far enough
          // to initiate a drag
          if (col.mDragGesturing) {
            if (Math.abs(aEvent.clientX - col.mStartDragX) < 5 &&
                Math.abs(aEvent.clientY - col.mStartDragY) < 5) {
              return;
            }
            col.mDragGesturing = false;
            col.setAttribute("dragging", "true");
            window.addEventListener("click", col._onDragMouseClick, true);
          }

          var pos = {};
          var targetCol = col.parentNode.parentNode._getColumnAtX(aEvent.clientX, 0.5, pos);

          // bail if we haven't mousemoved to a different column
          if (col.mTargetCol == targetCol && col.mTargetDir == pos.value)
            return;

          var tree = col.parentNode.parentNode;
          var sib;
          var column;
          if (col.mTargetCol) {
            // remove previous insertbefore/after attributes
            col.mTargetCol.removeAttribute("insertbefore");
            col.mTargetCol.removeAttribute("insertafter");
            column = tree.columns.getColumnFor(col.mTargetCol);
            tree.treeBoxObject.invalidateColumn(column);
            sib = col.mTargetCol._previousVisibleColumn;
            if (sib) {
              sib.removeAttribute("insertafter");
              column = tree.columns.getColumnFor(sib);
              tree.treeBoxObject.invalidateColumn(column);
            }
            col.mTargetCol = null;
            col.mTargetDir = null;
          }

          if (targetCol) {
            // set insertbefore/after attributes
            if (pos.value == "after") {
              targetCol.setAttribute("insertafter", "true");
            } else {
              targetCol.setAttribute("insertbefore", "true");
              sib = targetCol._previousVisibleColumn;
              if (sib) {
                sib.setAttribute("insertafter", "true");
                column = tree.columns.getColumnFor(sib);
                tree.treeBoxObject.invalidateColumn(column);
              }
            }
            column = tree.columns.getColumnFor(targetCol);
            tree.treeBoxObject.invalidateColumn(column);
            col.mTargetCol = targetCol;
            col.mTargetDir = pos.value;
          }
        ]]></body>
      </method>

      <method name="_onDragMouseUp">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var col = document.treecolDragging;
          if (!col) return;

          if (!col.mDragGesturing) {
            if (col.mTargetCol) {
              // remove insertbefore/after attributes
              var before = col.mTargetCol.hasAttribute("insertbefore");
              col.mTargetCol.removeAttribute(before ? "insertbefore" : "insertafter");

              var sib = col.mTargetCol._previousVisibleColumn;
              if (before && sib) {
                sib.removeAttribute("insertafter");
              }

              // Move the column only if it will result in a different column
              // ordering
              var move = true;

              // If this is a before move and the previous visible column is
              // the same as the column we're moving, don't move
              if (before && col == sib) {
                move = false;
              } else if (!before && col == col.mTargetCol) {
                // If this is an after move and the column we're moving is
                // the same as the target column, don't move.
                move = false;
              }

              if (move) {
                col.parentNode.parentNode._reorderColumn(col, col.mTargetCol, before);
              }

              // repaint to remove lines
              col.parentNode.parentNode.treeBoxObject.invalidate();

              col.mTargetCol = null;
            }
          } else
            col.mDragGesturing = false;

          document.treecolDragging = null;
          col.removeAttribute("dragging");

          window.removeEventListener("mousemove", col._onDragMouseMove, true);
          window.removeEventListener("mouseup", col._onDragMouseUp, true);
          // we have to wait for the click event to fire before removing
          // cancelling handler
          var clickHandler = function(handler) {
            window.removeEventListener("click", handler, true);
          };
          window.setTimeout(clickHandler, 0, col._onDragMouseClick);
        ]]></body>
      </method>

      <method name="_onDragMouseClick">
        <parameter name="aEvent"/>
        <body><![CDATA[
          // prevent click event from firing after column drag and drop
          aEvent.stopPropagation();
          aEvent.preventDefault();
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="mousedown" button="0"><![CDATA[
        if (this.parentNode.parentNode.enableColumnDrag) {
          var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var cols = this.parentNode.getElementsByTagNameNS(xulns, "treecol");

          // only start column drag operation if there are at least 2 visible columns
          var visible = 0;
          for (var i = 0; i < cols.length; ++i)
            if (cols[i].boxObject.width > 0) ++visible;

          if (visible > 1) {
            window.addEventListener("mousemove", this._onDragMouseMove, true);
            window.addEventListener("mouseup", this._onDragMouseUp, true);
            document.treecolDragging = this;
            this.mDragGesturing = true;
            this.mStartDragX = event.clientX;
            this.mStartDragY = event.clientY;
          }
        }
      ]]></handler>
      <handler event="click" button="0" phase="target">
        <![CDATA[
          if (event.target != event.originalTarget)
            return;

          // On Windows multiple clicking on tree columns only cycles one time
          // every 2 clicks.
          if (/Win/.test(navigator.platform) && event.detail % 2 == 0)
            return;

          var tree = this.parentNode.parentNode;
          if (tree.columns) {
            tree.view.cycleHeader(tree.columns.getColumnFor(this));
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="treecol" extends="chrome://global/content/bindings/tree.xml#treecol-base">
    <content>
      <xul:label class="treecol-text" xbl:inherits="crop,value=label" flex="1" crop="right"/>
      <xul:image class="treecol-sortdirection" xbl:inherits="sortDirection,hidden=hideheader"/>
    </content>
  </binding>

  <binding id="treecol-image" extends="chrome://global/content/bindings/tree.xml#treecol-base">
    <content>
      <xul:image class="treecol-icon" xbl:inherits="src"/>
    </content>
  </binding>

  <binding id="columnpicker" display="xul:button" role="xul:button"
           extends="chrome://global/content/bindings/tree.xml#tree-base">
    <content>
      <xul:image class="tree-columnpicker-icon"/>
      <xul:menupopup anonid="popup">
        <xul:menuseparator anonid="menuseparator"/>
        <xul:menuitem anonid="menuitem" label="&restoreColumnOrder.label;"/>
      </xul:menupopup>
    </content>

    <implementation>
      <method name="buildPopup">
        <parameter name="aPopup"/>
        <body>
          <![CDATA[
            // We no longer cache the picker content, remove the old content.
            while (aPopup.childNodes.length > 2)
              aPopup.firstChild.remove();

            var refChild = aPopup.firstChild;

            var tree = this.parentNode.parentNode;
            for (var currCol = tree.columns.getFirstColumn(); currCol;
                 currCol = currCol.getNext()) {
              // Construct an entry for each column in the row, unless
              // it is not being shown.
              var currElement = currCol.element;
              if (!currElement.hasAttribute("ignoreincolumnpicker")) {
                var popupChild = document.createElement("menuitem");
                popupChild.setAttribute("type", "checkbox");
                var columnName = currElement.getAttribute("display") ||
                                 currElement.getAttribute("label");
                popupChild.setAttribute("label", columnName);
                popupChild.setAttribute("colindex", currCol.index);
                if (currElement.getAttribute("hidden") != "true")
                  popupChild.setAttribute("checked", "true");
                if (currCol.primary)
                  popupChild.setAttribute("disabled", "true");
                aPopup.insertBefore(popupChild, refChild);
              }
            }

            var hidden = !tree.enableColumnDrag;
            const anonids = ["menuseparator", "menuitem"];
            for (var i = 0; i < anonids.length; i++) {
              var element = document.getAnonymousElementByAttribute(this, "anonid", anonids[i]);
              element.hidden = hidden;
            }
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="command">
        <![CDATA[
          if (event.originalTarget == this) {
            var popup = document.getAnonymousElementByAttribute(this, "anonid", "popup");
            this.buildPopup(popup);
            popup.showPopup(this, -1, -1, "popup", "bottomright", "topright");
          } else {
            var tree = this.parentNode.parentNode;
            tree.stopEditing(true);
            var menuitem = document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
            if (event.originalTarget == menuitem) {
              tree.columns.restoreNaturalOrder();
              tree._ensureColumnOrder();
            } else {
              var colindex = event.originalTarget.getAttribute("colindex");
              var column = tree.columns[colindex];
              if (column) {
                var element = column.element;
                if (element.getAttribute("hidden") == "true")
                  element.setAttribute("hidden", "false");
                else
                  element.setAttribute("hidden", "true");
              }
            }
          }
        ]]>
      </handler>
    </handlers>
  </binding>
</bindings>
PK
!<e#558chrome/toolkit/content/global/bindings/videocontrols.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

.scrubber,
.volumeControl {
  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
}

.playButton,
.muteButton,
.scrubber .scale-slider,
.volumeControl .scale-slider {
  -moz-user-focus: none;
}

.controlBar[fullscreen-unavailable] > .fullscreenButton {
  display: none;
}

.mediaControlsFrame {
  direction: ltr;
  /* Prevent unwanted style inheritance. See bug 554717. */
  text-align: left;
  list-style-image: none !important;
  font: normal normal normal 100%/normal sans-serif !important;
  text-decoration: none !important;
}

.controlsOverlay[scaled] {
  -moz-box-align: center;
}

/* CSS Transitions
 *
 * These are overriden by the default theme; the rules here just 
 * provide a fallback to drive the required transitionend event
 * (in case a 3rd party theme does not provide transitions).
 */
.controlBar:not([immediate]) {
  transition-property: opacity;
  transition-duration: 1ms;
}
.controlBar[fadeout] {
  opacity: 0;
}
.volumeStack:not([immediate]) {
  transition-property: opacity, margin-top;
  transition-duration: 1ms, 1ms;
}
.volumeStack[fadeout] {
  opacity: 0;
  margin-top: 0;
}
.statusOverlay:not([immediate]) {
  transition-property: opacity;
  transition-duration: 1ms;
  transition-delay: 750ms;
}
.statusOverlay[fadeout] {
  opacity: 0;
}

/* Error description formatting */
.errorLabel {
  display: none;
}

[error="errorAborted"]         > [anonid="errorAborted"],
[error="errorNetwork"]         > [anonid="errorNetwork"],
[error="errorDecode"]          > [anonid="errorDecode"],
[error="errorSrcNotSupported"] > [anonid="errorSrcNotSupported"],
[error="errorNoSource"]        > [anonid="errorNoSource"],
[error="errorGeneric"]         > [anonid="errorGeneric"] {
  display: inline;
}
PK
!<@D@D8chrome/toolkit/content/global/bindings/videocontrols.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
%videocontrolsDTD;
]>

<bindings id="videoControlBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:svg="http://www.w3.org/2000/svg"
          xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="suppressChangeEvent"
         extends="chrome://global/content/bindings/scale.xml#scale">
<implementation implements="nsIXBLAccessible">
  <!-- nsIXBLAccessible -->
  <property name="accessibleName" readonly="true">
    <getter>
      var currTime = this.positionValue;
      var totalTime = this.durationValue;

      return this.scrubberNameFormat
                 .replace(/#1/, currTime)
                 .replace(/#2/, totalTime);
    </getter>
  </property>

  <constructor>
    <![CDATA[
    /* eslint-disable no-multi-spaces */
    this.scrubberNameFormat = ]]>"&scrubberScale.nameFormat;"<![CDATA[;
    /* eslint-enable no-multi-spaces */
    this.positionValue = "";
    this.durationValue = "";
    this.valueBar = null;
    this.isDragging = false;
    this.isPausedByDragging = false;

    this.type = this.getAttribute("class");
    this.Utils = document.getBindingParent(this.parentNode).Utils;
    this.valueBar = this.Utils.progressBar;
    ]]>
  </constructor>

  <method name="valueChanged">
    <parameter name="which"/>
    <parameter name="newValue"/>
    <parameter name="userChanged"/>
    <body>
      <![CDATA[
      // This method is a copy of the base binding's valueChanged(), except that it does
      // not dispatch a |change| event (to avoid exposing the event to web content), and
      // just calls the videocontrol's seekToPosition() method directly.
      switch (which) {
        case "curpos":
          // Update the time shown in the thumb.
          this.positionValue = this.Utils.formatTime(newValue, this.Utils.showHours);
          this.Utils.positionLabel.setAttribute("value", this.positionValue);
          // Update the value bar to match the thumb position.
          let percent = newValue / this.max;
          if (!isNaN(percent) && percent != Infinity) {
            this.valueBar.value = Math.round(percent * 10000); // has max=10000
          } else {
            this.valueBar.removeAttribute("value");
          }

          // The value of userChanged is true when changing the position with the mouse,
          // but not when pressing an arrow key. However, the base binding sets
          // ._userChanged in its keypress handlers, so we just need to check both.
          if (!userChanged && !this._userChanged) {
            return;
          }
          this.setAttribute("value", newValue);
          this.Utils.seekToPosition(newValue);
          break;

        case "minpos":
          this.setAttribute("min", newValue);
          break;

        case "maxpos":
          // Update the value bar to match the thumb position.
          this.valueBar.value = Math.round(this.value / newValue * 10000); // has max=10000
          this.setAttribute("max", newValue);
          break;
      }
      ]]>
    </body>
  </method>

  <method name="dragStateChanged">
    <parameter name="isDragging"/>
    <body>
      <![CDATA[
      this.Utils.log("--- dragStateChanged: " + isDragging + " ---");
      this.isDragging = isDragging;
      if (this.isPausedByDragging && !isDragging) {
        // After the drag ends, resume playing.
        this.Utils.video.play();
        this.isPausedByDragging = false;
      }
      ]]>
    </body>
  </method>

  <method name="pauseVideoDuringDragging">
    <body>
      <![CDATA[
      if (this.isDragging &&
         !this.Utils.video.paused && !this.isPausedByDragging) {
        this.isPausedByDragging = true;
        this.Utils.video.pause();
      }
      ]]>
    </body>
  </method>

</implementation>
</binding>

<binding id="videoControls">
  <resources>
    <stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
    <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
  </resources>

  <xbl:content xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    xmlns="http://www.w3.org/1999/xhtml" class="mediaControlsFrame">
    <div anonid="controlsContainer" class="controlsContainer" role="none">
      <div anonid="statusOverlay" class="statusOverlay stackItem" hidden="true">
        <div anonid="statusIcon" class="statusIcon"></div>
        <span class="errorLabel" anonid="errorAborted">&error.aborted;</span>
        <span class="errorLabel" anonid="errorNetwork">&error.network;</span>
        <span class="errorLabel" anonid="errorDecode">&error.decode;</span>
        <span class="errorLabel" anonid="errorSrcNotSupported">&error.srcNotSupported;</span>
        <span class="errorLabel" anonid="errorNoSource">&error.noSource2;</span>
        <span class="errorLabel" anonid="errorGeneric">&error.generic;</span>
      </div>

      <div anonid="controlsOverlay" class="controlsOverlay stackItem">
        <div class="controlsSpacerStack" aria-hideen="true">
          <div anonid="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
          <div anonid="clickToPlay" class="clickToPlay" hidden="true"></div>
        </div>
        <div anonid="controlBar" class="controlBar" hidden="true">
          <button anonid="playButton"
                  class="playButton"
                  playlabel="&playButton.playLabel;"
                  pauselabel="&playButton.pauseLabel;"
                  tabindex="-1"/>
          <div anonid="scrubberStack" class="scrubberStack progressContainer" role="none">
            <div class="progressBackgroundBar stackItem" role="none">
              <div class="progressStack" role="none">
                <progress anonid="bufferBar" class="bufferBar" value="0" max="100" tabindex="-1"></progress>
                <progress anonid="progressBar" class="progressBar" value="0" max="100" tabindex="-1"></progress>
              </div>
            </div>
            <input type="range" anonid="scrubber" class="scrubber" tabindex="-1" mozinputrangeignorepreventdefault="true" />
          </div>
          <span anonid="positionLabel" class="positionLabel" role="presentation"></span>
          <span anonid="durationLabel" class="durationLabel" role="presentation"></span>
          <span anonid="positionDurationBox" class="positionDurationBox" aria-hidden="true">
            &positionAndDuration.nameFormat;
          </span>
          <div anonid="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
          <button anonid="muteButton"
                  class="muteButton"
                  mutelabel="&muteButton.muteLabel;"
                  unmutelabel="&muteButton.unmuteLabel;"
                  tabindex="-1"/>
          <div anonid="volumeStack" class="volumeStack progressContainer" role="none">
            <input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1" mozinputrangeignorepreventdefault="true" />
          </div>
          <button anonid="closedCaptionButton" class="closedCaptionButton"/>
          <button anonid="fullscreenButton"
                  class="fullscreenButton"
                  enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
                  exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
        </div>
        <div anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>
      </div>
    </div>
  </xbl:content>

  <implementation>

  <constructor>
    <![CDATA[
    this.isTouchControls = false;
    this.randomID = 0;

    this.Utils = {
      debug: false,
      video: null,
      videocontrols: null,
      controlBar: null,
      playButton: null,
      muteButton: null,
      volumeControl: null,
      durationLabel: null,
      positionLabel: null,
      scrubber: null,
      progressBar: null,
      bufferBar: null,
      statusOverlay: null,
      controlsSpacer: null,
      clickToPlay: null,
      controlsOverlay: null,
      fullscreenButton: null,
      layoutControls: null,
      currentTextTrackIndex: 0,

      textTracksCount: 0,
      randomID: 0,
      videoEvents: ["play", "pause", "ended", "volumechange", "loadeddata",
                    "loadstart", "timeupdate", "progress",
                    "playing", "waiting", "canplay", "canplaythrough",
                    "seeking", "seeked", "emptied", "loadedmetadata",
                    "error", "suspend", "stalled",
                    "mozvideoonlyseekbegin", "mozvideoonlyseekcompleted"],

      showHours: false,
      firstFrameShown: false,
      timeUpdateCount: 0,
      maxCurrentTimeSeen: 0,
      isPausedByDragging: false,
      _isAudioOnly: false,

      get isAudioOnly() { return this._isAudioOnly; },
      set isAudioOnly(val) {
        this._isAudioOnly = val;
        this.setFullscreenButtonState();

        if (!this.isTopLevelSyntheticDocument) {
          return;
        }
        if (this._isAudioOnly) {
          this.video.style.height = this.controlBarMinHeight + "px";
          this.video.style.width = "66%";
        } else {
          this.video.style.removeProperty("height");
          this.video.style.removeProperty("width");
        }
      },

      get isControlBarHidden() {
        return this.controlBar.hidden ||
               this.controlBar.hideByAdjustment ||
               this.controlBar.getAttribute("fadeout") === "true";
      },

      suppressError: false,

      setupStatusFader(immediate) {
        // Since the play button will be showing, we don't want to
        // show the throbber behind it. The throbber here will
        // only show if needed after the play button has been pressed.
        if (!this.clickToPlay.hidden) {
          this.startFadeOut(this.statusOverlay, true);
          return;
        }

        var show = false;
        if (this.video.seeking ||
            (this.video.error && !this.suppressError) ||
            this.video.networkState == this.video.NETWORK_NO_SOURCE ||
            (this.video.networkState == this.video.NETWORK_LOADING &&
              (this.video.paused || this.video.ended
                ? this.video.readyState < this.video.HAVE_CURRENT_DATA
                : this.video.readyState < this.video.HAVE_FUTURE_DATA)) ||
            (this.timeUpdateCount <= 1 && !this.video.ended &&
             this.video.readyState < this.video.HAVE_FUTURE_DATA &&
             this.video.networkState == this.video.NETWORK_LOADING)) {
          show = true;
        }

        // Explicitly hide the status fader if this
        // is audio only until bug 619421 is fixed.
        if (this.isAudioOnly) {
          show = false;
        }

        if (this._showThrobberTimer) {
          show = true;
        }

        this.log("Status overlay: seeking=" + this.video.seeking +
                 " error=" + this.video.error + " readyState=" + this.video.readyState +
                 " paused=" + this.video.paused + " ended=" + this.video.ended +
                 " networkState=" + this.video.networkState +
                 " timeUpdateCount=" + this.timeUpdateCount +
                 " _showThrobberTimer=" + this._showThrobberTimer +
                 " --> " + (show ? "SHOW" : "HIDE"));
        this.startFade(this.statusOverlay, show, immediate);
      },

      /*
      * Set the initial state of the controls. The binding is normally created along
      * with video element, but could be attached at any point (eg, if the video is
      * removed from the document and then reinserted). Thus, some one-time events may
      * have already fired, and so we'll need to explicitly check the initial state.
      */
      setupInitialState() {
        this.randomID = Math.random();
        this.videocontrols.randomID = this.randomID;

        this.setPlayButtonState(this.video.paused);

        this.setFullscreenButtonState();

        var duration = Math.round(this.video.duration * 1000); // in ms
        var currentTime = Math.round(this.video.currentTime * 1000); // in ms
        this.log("Initial playback position is at " + currentTime + " of " + duration);
        // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
        // to determine if the media source changed while we were detached.
        this.initPositionDurationBox();
        this.maxCurrentTimeSeen = currentTime;
        this.showPosition(currentTime, duration);

        // If we have metadata, check if this is a <video> without
        // video data, or a video with no audio track.
        if (this.video.readyState >= this.video.HAVE_METADATA) {
          if (this.video instanceof HTMLVideoElement &&
              (this.video.videoWidth == 0 || this.video.videoHeight == 0)) {
            this.isAudioOnly = true;
          }

          // We have to check again if the media has audio here,
          // because of bug 718107: switching to fullscreen may
          // cause the bindings to detach and reattach, hence
          // unsetting the attribute.
          if (!this.isAudioOnly && !this.video.mozHasAudio) {
            this.muteButton.setAttribute("noAudio", "true");
            this.muteButton.setAttribute("disabled", "true");
          }
        }

        if (this.isAudioOnly) {
          this.clickToPlay.hidden = true;
        }

        // If the first frame hasn't loaded, kick off a throbber fade-in.
        if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
          this.firstFrameShown = true;
        }

        // We can't determine the exact buffering status, but do know if it's
        // fully loaded. (If it's still loading, it will fire a progress event
        // and we'll figure out the exact state then.)
        this.bufferBar.max = 100;
        if (this.video.readyState >= this.video.HAVE_METADATA) {
          this.showBuffered();
        } else {
          this.bufferBar.value = 0;
        }

        // Set the current status icon.
        if (this.hasError()) {
          this.clickToPlay.hidden = true;
          this.statusIcon.setAttribute("type", "error");
          this.updateErrorText();
          this.setupStatusFader(true);
        }

        let adjustableControls = [
          ...this.prioritizedControls,
          this.controlBar,
          this.clickToPlay
        ];

        for (let control of adjustableControls) {
          if (!control) {
            break;
          }

          Object.defineProperties(control, {
            // We should directly access CSSOM to get pre-defined style instead of
            // retrieving computed dimensions from layout.
            minWidth: {
              get: () => {
                let controlAnonId = control.getAttribute("anonid");
                let propertyName = `--${controlAnonId}-width`;
                if (control.modifier) {
                  propertyName += "-" + control.modifier;
                }
                let preDefinedSize = this.controlBarComputedStyles.getPropertyValue(propertyName);

                return parseInt(preDefinedSize, 10);
              }
            },
            isAdjustableControl: {
              value: true
            },
            modifier: {
              value: "",
              writable: true
            },
            isWanted: {
              value: true,
              writable: true
            },
            hideByAdjustment: {
              set: (v) => {
                if (v) {
                  control.setAttribute("hidden", "true");
                } else {
                  control.removeAttribute("hidden");
                }

                control._isHiddenByAdjustment = v;
              },
              get: () => control._isHiddenByAdjustment
            },
            _isHiddenByAdjustment: {
              value: false,
              writable: true
            }
          });
        }
        this.adjustControlSize();

        // Can only update the volume controls once we've computed
        // _volumeControlWidth, since the volume slider implementation
        // depends on it.
        this.updateVolumeControls();
      },

      setupNewLoadState() {
        // videocontrols.css hides the control bar by default, because if script
        // is disabled our binding's script is disabled too (bug 449358). Thus,
        // the controls are broken and we don't want them shown. But if script is
        // enabled, the code here will run and can explicitly unhide the controls.
        //
        // For videos with |autoplay| set, we'll leave the controls initially hidden,
        // so that they don't get in the way of the playing video. Otherwise we'll
        // go ahead and reveal the controls now, so they're an obvious user cue.
        //
        // (Note: the |controls| attribute is already handled via layout/style/html.css)
        var shouldShow = !this.dynamicControls ||
                         (this.video.paused &&
                         !(this.video.autoplay && this.video.mozAutoplayEnabled));
        // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
        let shouldClickToPlayShow = shouldShow && !this.isAudioOnly &&
                                    this.video.currentTime == 0 && !this.hasError();
        this.startFade(this.clickToPlay, shouldClickToPlayShow, true);
        this.startFade(this.controlsSpacer, shouldClickToPlayShow, true);
        this.startFade(this.controlBar, shouldShow, true);
      },

      get dynamicControls() {
        // Don't fade controls for <audio> elements.
        var enabled = !this.isAudioOnly;

        // Allow tests to explicitly suppress the fading of controls.
        if (this.video.hasAttribute("mozNoDynamicControls")) {
          enabled = false;
        }

        // If the video hits an error, suppress controls if it
        // hasn't managed to do anything else yet.
        if (!this.firstFrameShown && this.hasError()) {
          enabled = false;
        }

        return enabled;
      },

      updateVolume() {
        const volume = this.volumeControl.value;
        this.setVolume(volume / 100);
      },

      updateVolumeControls() {
        var volume = this.video.muted ? 0 : this.video.volume;
        var volumePercentage = Math.round(volume * 100);
        this.updateMuteButtonState();
        this.volumeControl.value = volumePercentage;
      },

      /*
       * We suspend a video element's video decoder if the video
       * element is invisible. However, resuming the video decoder
       * takes time and we show the throbber UI if it takes more than
       * 250 ms.
       *
       * When an already-suspended video element becomes visible, we
       * resume its video decoder immediately and queue a video-only seek
       * task to seek the resumed video decoder to the current position;
       * meanwhile, we also file a "mozvideoonlyseekbegin" event which
       * we used to start the timer here.
       *
       * Once the queued seek operation is done, we dispatch a
       * "canplay" event which indicates that the resuming operation
       * is completed.
       */
      SHOW_THROBBER_TIMEOUT_MS: 250,
      _showThrobberTimer: null,
      _delayShowThrobberWhileResumingVideoDecoder() {
        this._showThrobberTimer = setTimeout(() => {
          this.statusIcon.setAttribute("type", "throbber");
          // Show the throbber immediately since we have waited for SHOW_THROBBER_TIMEOUT_MS.
          // We don't want to wait for another transition-delay(750ms) and the
          // transition-duration(300ms).
          this.setupStatusFader(true);
        }, this.SHOW_THROBBER_TIMEOUT_MS);
      },
      _cancelShowThrobberWhileResumingVideoDecoder() {
        if (this._showThrobberTimer) {
          clearTimeout(this._showThrobberTimer);
          this._showThrobberTimer = null;
        }
      },

      handleEvent(aEvent) {
        this.log("Got media event ----> " + aEvent.type);

        // If the binding is detached (or has been replaced by a
        // newer instance of the binding), nuke our event-listeners.
        if (this.videocontrols.randomID != this.randomID) {
          this.terminateEventListeners();
          return;
        }

        switch (aEvent.type) {
          case "play":
            this.setPlayButtonState(false);
            this.setupStatusFader();
            if (!this._triggeredByControls && this.dynamicControls && this.videocontrols.isTouchControls) {
              this.startFadeOut(this.controlBar);
            }
            if (!this._triggeredByControls) {
              this.clickToPlay.hidden = true;
              this.controlsSpacer.setAttribute("fadeout", "true");
            }
            this._triggeredByControls = false;
            break;
          case "pause":
            // Little white lie: if we've internally paused the video
            // while dragging the scrubber, don't change the button state.
            if (!this.scrubber.isDragging) {
              this.setPlayButtonState(true);
            }
            this.setupStatusFader();
            break;
          case "ended":
            this.setPlayButtonState(true);
            // We throttle timechange events, so the thumb might not be
            // exactly at the end when the video finishes.
            this.showPosition(Math.round(this.video.currentTime * 1000),
            Math.round(this.video.duration * 1000));
            this.startFadeIn(this.controlBar);
            this.setupStatusFader();
            break;
          case "volumechange":
            this.updateVolumeControls();
            // Show the controls to highlight the changing volume,
            // but only if the click-to-play overlay has already
            // been hidden (we don't hide controls when the overlay is visible).
            if (this.clickToPlay.hidden && !this.isAudioOnly) {
              this.startFadeIn(this.controlBar);
              clearTimeout(this._hideControlsTimeout);
              this._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
            }
            break;
          case "loadedmetadata":
            // If a <video> doesn't have any video data, treat it as <audio>
            // and show the controls (they won't fade back out)
            if (this.video instanceof HTMLVideoElement &&
                (this.video.videoWidth == 0 || this.video.videoHeight == 0)) {
              this.isAudioOnly = true;
              this.clickToPlay.hidden = true;
              this.startFadeIn(this.controlBar);
              this.setFullscreenButtonState();
            }
            this.showPosition(Math.round(this.video.currentTime * 1000), Math.round(this.video.duration * 1000));
            if (!this.isAudioOnly && !this.video.mozHasAudio) {
              this.muteButton.setAttribute("noAudio", "true");
              this.muteButton.setAttribute("disabled", "true");
            }
            this.adjustControlSize();
            break;
          case "loadeddata":
            this.firstFrameShown = true;
            this.setupStatusFader();
            break;
          case "loadstart":
            this.maxCurrentTimeSeen = 0;
            this.controlsSpacer.removeAttribute("aria-label");
            this.statusOverlay.removeAttribute("error");
            this.statusIcon.setAttribute("type", "throbber");
            this.isAudioOnly = (this.video instanceof HTMLAudioElement);
            this.setPlayButtonState(true);
            this.setupNewLoadState();
            this.setupStatusFader();
            break;
          case "progress":
            this.statusIcon.removeAttribute("stalled");
            this.showBuffered();
            this.setupStatusFader();
            break;
          case "stalled":
            this.statusIcon.setAttribute("stalled", "true");
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "suspend":
            this.setupStatusFader();
            break;
          case "timeupdate":
            var currentTime = Math.round(this.video.currentTime * 1000); // in ms
            var duration = Math.round(this.video.duration * 1000); // in ms

            // If playing/seeking after the video ended, we won't get a "play"
            // event, so update the button state here.
            if (!this.video.paused) {
              this.setPlayButtonState(false);
            }

            this.timeUpdateCount++;
            // Whether we show the statusOverlay sometimes depends
            // on whether we've seen more than one timeupdate
            // event (if we haven't, there hasn't been any
            // "playback activity" and we may wish to show the
            // statusOverlay while we wait for HAVE_ENOUGH_DATA).
            // If we've seen more than 2 timeupdate events,
            // the count is no longer relevant to setupStatusFader.
            if (this.timeUpdateCount <= 2) {
              this.setupStatusFader();
            }

            // If the user is dragging the scrubber ignore the delayed seek
            // responses (don't yank the thumb away from the user)
            if (this.scrubber.isDragging) {
              return;
            }
            this.showPosition(currentTime, duration);
            this.showBuffered();
            break;
          case "emptied":
            this.bufferBar.value = 0;
            this.showPosition(0, 0);
            break;
          case "seeking":
            this.showBuffered();
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "waiting":
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "seeked":
          case "playing":
          case "canplay":
          case "canplaythrough":
            this.setupStatusFader();
            break;
          case "error":
            // We'll show the error status icon when we receive an error event
            // under either of the following conditions:
            // 1. The video has its error attribute set; this means we're loading
            //    from our src attribute, and the load failed, or we we're loading
            //    from source children and the decode or playback failed after we
            //    determined our selected resource was playable.
            // 2. The video's networkState is NETWORK_NO_SOURCE. This means we we're
            //    loading from child source elements, but we were unable to select
            //    any of the child elements for playback during resource selection.
            if (this.hasError()) {
              this.suppressError = false;
              this.clickToPlay.hidden = true;
              this.statusIcon.setAttribute("type", "error");
              this.updateErrorText();
              this.setupStatusFader(true);
              // If video hasn't shown anything yet, disable the controls.
              if (!this.firstFrameShown && !this.isAudioOnly) {
                this.startFadeOut(this.controlBar);
              }
              this.controlsSpacer.removeAttribute("hideCursor");
            }
            break;
          case "mozvideoonlyseekbegin":
            this._delayShowThrobberWhileResumingVideoDecoder();
            break;
          case "mozvideoonlyseekcompleted":
            this._cancelShowThrobberWhileResumingVideoDecoder();
            this.setupStatusFader();
            break;
          default:
            this.log("!!! event " + aEvent.type + " not handled!");
        }
      },

      terminateEventListeners() {
        if (this.videoEvents) {
          for (let event of this.videoEvents) {
            try {
              this.video.removeEventListener(event, this, {
                capture: true,
                mozSystemGroup: true
              });
            } catch (ex) {}
          }
        }

        if (this.controlListeners) {
          for (let element of this.controlListeners) {
            try {
              element.item.removeEventListener(element.event, element.func,
                { mozSystemGroup: true, capture: element.capture });
            } catch (ex) {}
          }

          delete this.controlListeners;
        }

        this.log("--- videocontrols terminated ---");
      },

      hasError() {
        // We either have an explicit error, or the resource selection
        // algorithm is running and we've tried to load something and failed.
        // Note: we don't consider the case where we've tried to load but
        // there's no sources to load as an error condition, as sites may
        // do this intentionally to work around requires-user-interaction to
        // play restrictions, and we don't want to display a debug message
        // if that's the case.
        return this.video.error != null ||
               (this.video.networkState == this.video.NETWORK_NO_SOURCE &&
               this.hasSources());
      },

      hasSources() {
        if (this.video.hasAttribute("src") &&
            this.video.getAttribute("src") !== "") {
          return true;
        }
        for (var child = this.video.firstChild;
             child !== null;
             child = child.nextElementSibling) {
          if (child instanceof HTMLSourceElement) {
            return true;
          }
        }
        return false;
      },

      updateErrorText() {
        let error;
        let v = this.video;
        // It is possible to have both v.networkState == NETWORK_NO_SOURCE
        // as well as v.error being non-null. In this case, we will show
        // the v.error.code instead of the v.networkState error.
        if (v.error) {
          switch (v.error.code) {
            case v.error.MEDIA_ERR_ABORTED:
              error = "errorAborted";
              break;
            case v.error.MEDIA_ERR_NETWORK:
              error = "errorNetwork";
              break;
            case v.error.MEDIA_ERR_DECODE:
              error = "errorDecode";
              break;
            case v.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
              error = "errorSrcNotSupported";
              break;
            default:
              error = "errorGeneric";
              break;
          }
        } else if (v.networkState == v.NETWORK_NO_SOURCE) {
          error = "errorNoSource";
        } else {
          return; // No error found.
        }

        let label = document.getAnonymousElementByAttribute(this.videocontrols, "anonid", error);
        this.controlsSpacer.setAttribute("aria-label", label.textContent);
        this.statusOverlay.setAttribute("error", error);
      },

      formatTime(aTime, showHours = false) {
        // Format the duration as "h:mm:ss" or "m:ss"
        aTime = Math.round(aTime / 1000);
        let hours = Math.floor(aTime / 3600);
        let mins  = Math.floor((aTime % 3600) / 60);
        let secs  = Math.floor(aTime % 60);
        let timeString;
        if (secs < 10) {
          secs = "0" + secs;
        }
        if (hours || showHours) {
          if (mins < 10) {
            mins = "0" + mins;
          }
          timeString = hours + ":" + mins + ":" + secs;
        } else {
          timeString = mins + ":" + secs;
        }
        return timeString;
      },

      initPositionDurationBox() {
        if (this.videocontrols.isTouchControls) {
          return;
        }

        const positionTextNode = Array.prototype.find.call(
          this.positionDurationBox.childNodes, (n) => !!~n.textContent.search("#1"));
        const durationSpan = this.durationSpan;
        const durationFormat = durationSpan.textContent;
        const positionFormat = positionTextNode.textContent;

        durationSpan.classList.add("duration");
        durationSpan.setAttribute("role", "none");
        durationSpan.setAttribute("anonid", "durationSpan");

        Object.defineProperties(this.positionDurationBox, {
          durationSpan: {
            value: durationSpan
          },
          position: {
            set: (v) => {
              positionTextNode.textContent = positionFormat.replace("#1", v);
            }
          },
          duration: {
            set: (v) => {
              durationSpan.textContent = v ? durationFormat.replace("#2", v) : "";
            }
          }
        });
      },

      showDuration(duration) {
        let isInfinite = (duration == Infinity);
        this.log("Duration is " + duration + "ms.\n");

        if (isNaN(duration) || isInfinite) {
          duration = this.maxCurrentTimeSeen;
        }

        // If the duration is over an hour, thumb should show h:mm:ss instead of mm:ss
        this.showHours = (duration >= 3600000);

        // Format the duration as "h:mm:ss" or "m:ss"
        let timeString = isInfinite ? "" : this.formatTime(duration);
        if (this.videocontrols.isTouchControls) {
          this.durationLabel.setAttribute("value", timeString);
        } else {
          this.positionDurationBox.duration = timeString;

          if (this.showHours) {
            this.positionDurationBox.modifier = "long";
            this.durationSpan.modifier = "long";
          }
        }

        // "durationValue" property is used by scale binding to
        // generate accessible name.
        this.scrubber.durationValue = timeString;

        this.scrubber.max = duration;
        // XXX Can't set increment here, due to bug 473103. Also, doing so causes
        // snapping when dragging with the mouse, so we can't just set a value for
        // the arrow-keys.
        this.scrubber.pageIncrement = Math.round(duration / 10);
      },

      pauseVideoDuringDragging() {
        if (!this.video.paused &&
            !this.isPausedByDragging &&
            this.scrubber.isDragging) {
          this.isPausedByDragging = true;
          this.video.pause();
        }
      },

      onScrubberInput(e) {
        const duration = Math.round(this.video.duration * 1000); // in ms
        let time = this.scrubber.value;

        this.seekToPosition(time);
        this.showPosition(time, duration);

        this.scrubber.isDragging = true;
        this.pauseVideoDuringDragging();
      },

      onScrubberChange(e) {
        this.scrubber.isDragging = false;

        if (this.isPausedByDragging) {
          this.video.play();
          this.isPausedByDragging = false;
        }
      },

      updateScrubberProgress() {
        if (this.videocontrols.isTouchControls) {
          return;
        }

        const positionPercent = this.scrubber.value / this.scrubber.max * 100;

        if (!isNaN(positionPercent) && positionPercent != Infinity) {
          this.progressBar.value = positionPercent;
        } else {
          this.progressBar.value = 0;
        }
      },

      seekToPosition(newPosition) {
        newPosition /= 1000; // convert from ms
        this.log("+++ seeking to " + newPosition);
        this.video.currentTime = newPosition;
      },

      setVolume(newVolume) {
        this.log("*** setting volume to " + newVolume);
        this.video.volume = newVolume;
        this.video.muted = false;
      },

      showPosition(currentTime, duration) {
        // If the duration is unknown (because the server didn't provide
        // it, or the video is a stream), then we want to fudge the duration
        // by using the maximum playback position that's been seen.
        if (currentTime > this.maxCurrentTimeSeen) {
          this.maxCurrentTimeSeen = currentTime;
        }
        this.showDuration(duration);

        this.log("time update @ " + currentTime + "ms of " + duration + "ms");

        let positionTime = this.formatTime(currentTime, this.showHours);

        this.scrubber.value = currentTime;
        if (this.videocontrols.isTouchControls) {
          this.positionLabel.setAttribute("value", positionTime);
        } else {
          this.positionDurationBox.position = positionTime;
          this.updateScrubberProgress();
        }
      },

      showBuffered() {
        function bsearch(haystack, needle, cmp) {
          var length = haystack.length;
          var low = 0;
          var high = length;
          while (low < high) {
            var probe = low + ((high - low) >> 1);
            var r = cmp(haystack, probe, needle);
            if (r == 0) {
              return probe;
            } else if (r > 0) {
              low = probe + 1;
            } else {
              high = probe;
            }
          }
          return -1;
        }

        function bufferedCompare(buffered, i, time) {
          if (time > buffered.end(i)) {
            return 1;
          } else if (time >= buffered.start(i)) {
            return 0;
          }
          return -1;
        }

        var duration = Math.round(this.video.duration * 1000);
        if (isNaN(duration) || duration == Infinity) {
          duration = this.maxCurrentTimeSeen;
        }

        // Find the range that the current play position is in and use that
        // range for bufferBar.  At some point we may support multiple ranges
        // displayed in the bar.
        var currentTime = this.video.currentTime;
        var buffered = this.video.buffered;
        var index = bsearch(buffered, currentTime, bufferedCompare);
        var endTime = 0;
        if (index >= 0) {
          endTime = Math.round(buffered.end(index) * 1000);
        }
        this.bufferBar.max = duration;
        this.bufferBar.value = endTime;
      },

      _controlsHiddenByTimeout: false,
      _showControlsTimeout: 0,
      SHOW_CONTROLS_TIMEOUT_MS: 500,
      _showControlsFn() {
        if (Utils.video.matches("video:hover")) {
          Utils.startFadeIn(Utils.controlBar, false);
          Utils._showControlsTimeout = 0;
          Utils._controlsHiddenByTimeout = false;
        }
      },

      _hideControlsTimeout: 0,
      _hideControlsFn() {
        if (!Utils.scrubber.isDragging) {
          Utils.startFade(Utils.controlBar, false);
          Utils._hideControlsTimeout = 0;
          Utils._controlsHiddenByTimeout = true;
        }
      },
      HIDE_CONTROLS_TIMEOUT_MS: 2000,
      onMouseMove(event) {
        // If the controls are static, don't change anything.
        if (!this.dynamicControls) {
          return;
        }

        clearTimeout(this._hideControlsTimeout);

        // Suppress fading out the controls until the video has rendered
        // its first frame. But since autoplay videos start off with no
        // controls, let them fade-out so the controls don't get stuck on.
        if (!this.firstFrameShown &&
            !(this.video.autoplay && this.video.mozAutoplayEnabled)) {
          return;
        }

        if (this._controlsHiddenByTimeout) {
          this._showControlsTimeout = setTimeout(this._showControlsFn, this.SHOW_CONTROLS_TIMEOUT_MS);
        } else {
          this.startFade(this.controlBar, true);
        }

        // Hide the controls if the mouse cursor is left on top of the video
        // but above the control bar and if the click-to-play overlay is hidden.
        if ((this._controlsHiddenByTimeout ||
            event.clientY < this.controlBar.getBoundingClientRect().top) &&
            this.clickToPlay.hidden) {
          this._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
        }
      },

      onMouseInOut(event) {
        // If the controls are static, don't change anything.
        if (!this.dynamicControls) {
          return;
        }

        clearTimeout(this._hideControlsTimeout);

        // Ignore events caused by transitions between child nodes.
        // Note that the videocontrols element is the same
        // size as the *content area* of the video element,
        // but this is not the same as the video element's
        // border area if the video has border or padding.
        if (this.isEventWithin(event, this.videocontrols)) {
          return;
        }

        var isMouseOver = (event.type == "mouseover");

        var controlRect = this.controlBar.getBoundingClientRect();
        var isMouseInControls = event.clientY > controlRect.top &&
        event.clientY < controlRect.bottom &&
        event.clientX > controlRect.left &&
        event.clientX < controlRect.right;

        // Suppress fading out the controls until the video has rendered
        // its first frame. But since autoplay videos start off with no
        // controls, let them fade-out so the controls don't get stuck on.
        if (!this.firstFrameShown && !isMouseOver &&
            !(this.video.autoplay && this.video.mozAutoplayEnabled)) {
          return;
        }

        if (!isMouseOver && !isMouseInControls) {
          this.adjustControlSize();

          // Keep the controls visible if the click-to-play is visible.
          if (!this.clickToPlay.hidden) {
            return;
          }

          this.startFadeOut(this.controlBar, false);
          this.textTrackList.setAttribute("hidden", "true");
          clearTimeout(this._showControlsTimeout);
          Utils._controlsHiddenByTimeout = false;
        }
      },

      startFadeIn(element, immediate) {
        this.startFade(element, true, immediate);
      },

      startFadeOut(element, immediate) {
        this.startFade(element, false, immediate);
      },

      startFade(element, fadeIn, immediate) {
        if (element.classList.contains("controlBar") && fadeIn) {
          // Bug 493523, the scrubber doesn't call valueChanged while hidden,
          // so our dependent state (eg, timestamp in the thumb) will be stale.
          // As a workaround, update it manually when it first becomes unhidden.
          if (element.hidden) {
            if (this.videocontrols.isTouchControls) {
              this.scrubber.valueChanged("curpos", this.video.currentTime * 1000, false);
            } else {
              this.scrubber.value = this.video.currentTime * 1000;
            }
          }
        }

        if (immediate) {
          element.setAttribute("immediate", true);
        } else {
          element.removeAttribute("immediate");
        }

        if (fadeIn) {
          // hidden state should be controlled by adjustControlSize
          if (!(element.isAdjustableControl && element.hideByAdjustment)) {
            element.hidden = false;
          }
          // force style resolution, so that transition begins
          // when we remove the attribute.
          element.clientTop;
          element.removeAttribute("fadeout");
          if (element.classList.contains("controlBar")) {
            this.controlsSpacer.removeAttribute("hideCursor");
          }
        } else {
          element.setAttribute("fadeout", true);
          if (element.classList.contains("controlBar") && !this.hasError() &&
              document.mozFullScreenElement == this.video) {
            this.controlsSpacer.setAttribute("hideCursor", true);
          }
        }
      },

      onTransitionEnd(event) {
        // Ignore events for things other than opacity changes.
        if (event.propertyName != "opacity") {
          return;
        }

        var element = event.originalTarget;

        // Nothing to do when a fade *in* finishes.
        if (!element.hasAttribute("fadeout")) {
          return;
        }

        if (this.videocontrols.isTouchControls) {
          this.scrubber.dragStateChanged(false);
        }
        element.hidden = true;
      },

      _triggeredByControls: false,

      startPlay() {
        this._triggeredByControls = true;
        this.hideClickToPlay();
        this.video.play();
      },

      togglePause() {
        if (this.video.paused || this.video.ended) {
          this.startPlay();
        } else {
          this.video.pause();
        }

        // We'll handle style changes in the event listener for
        // the "play" and "pause" events, same as if content
        // script was controlling video playback.
      },

      isVideoWithoutAudioTrack() {
        return this.video.readyState >= this.video.HAVE_METADATA &&
               !this.isAudioOnly &&
               !this.video.mozHasAudio;
      },

      toggleMute() {
        if (this.isVideoWithoutAudioTrack()) {
          return;
        }
        this.video.muted = !this.isEffectivelyMuted();
        if (this.video.volume === 0) {
          this.video.volume = 0.5;
        }

        // We'll handle style changes in the event listener for
        // the "volumechange" event, same as if content script was
        // controlling volume.
      },

      isVideoInFullScreen() {
        return document.mozFullScreenElement == this.video;
      },

      toggleFullscreen() {
        this.isVideoInFullScreen() ?
          document.mozCancelFullScreen() :
          this.video.mozRequestFullScreen();
      },

      setFullscreenButtonState() {
        if (this.isAudioOnly || !document.mozFullScreenEnabled) {
          this.controlBar.setAttribute("fullscreen-unavailable", true);
          this.adjustControlSize();
          return;
        }
        this.controlBar.removeAttribute("fullscreen-unavailable");
        this.adjustControlSize();

        var attrName = this.isVideoInFullScreen() ? "exitfullscreenlabel" : "enterfullscreenlabel";
        var value = this.fullscreenButton.getAttribute(attrName);
        this.fullscreenButton.setAttribute("aria-label", value);

        if (this.isVideoInFullScreen()) {
          this.fullscreenButton.setAttribute("fullscreened", "true");
        } else {
          this.fullscreenButton.removeAttribute("fullscreened");
        }
      },

      onFullscreenChange() {
        if (this.isVideoInFullScreen()) {
          Utils._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
        }
        this.setFullscreenButtonState();
      },

      clickToPlayClickHandler(e) {
        if (e.button != 0) {
          return;
        }
        if (this.hasError() && !this.suppressError) {
          // Errors that can be dismissed should be placed here as we discover them.
          if (this.video.error.code != this.video.error.MEDIA_ERR_ABORTED) {
            return;
          }
          this.statusOverlay.hidden = true;
          this.suppressError = true;
          return;
        }
        if (e.defaultPrevented) {
          return;
        }
        if (this.playButton.hasAttribute("paused")) {
          this.startPlay();
        } else {
          this.video.pause();
        }
      },
      hideClickToPlay() {
        let videoHeight = this.video.clientHeight;
        let videoWidth = this.video.clientWidth;

        // The play button will animate to 3x its size. This
        // shows the animation unless the video is too small
        // to show 2/3 of the animation.
        let animationScale = 2;
        let animationMinSize = this.clickToPlay.minWidth * animationScale;

        if (animationMinSize > videoWidth ||
            animationMinSize > (videoHeight - this.controlBarMinHeight)) {
          this.clickToPlay.setAttribute("immediate", "true");
          this.clickToPlay.hidden = true;
        } else {
          this.clickToPlay.removeAttribute("immediate");
        }
        this.clickToPlay.setAttribute("fadeout", "true");
        this.controlsSpacer.setAttribute("fadeout", "true");
      },

      setPlayButtonState(aPaused) {
        if (aPaused) {
          this.playButton.setAttribute("paused", "true");
        } else {
          this.playButton.removeAttribute("paused");
        }

        var attrName = aPaused ? "playlabel" : "pauselabel";
        var value = this.playButton.getAttribute(attrName);
        this.playButton.setAttribute("aria-label", value);
      },

      isEffectivelyMuted() {
        return this.video.muted || !this.video.volume;
      },

      updateMuteButtonState() {
        var muted = this.isEffectivelyMuted();

        if (muted) {
          this.muteButton.setAttribute("muted", "true");
        } else {
          this.muteButton.removeAttribute("muted");
        }

        var attrName = muted ? "unmutelabel" : "mutelabel";
        var value = this.muteButton.getAttribute(attrName);
        this.muteButton.setAttribute("aria-label", value);
      },

      _getComputedPropertyValueAsInt(element, property) {
        let value = getComputedStyle(element, null).getPropertyValue(property);
        return parseInt(value, 10);
      },

      keyHandler(event) {
        // Ignore keys when content might be providing its own.
        if (!this.video.hasAttribute("controls")) {
          return;
        }

        var keystroke = "";
        if (event.altKey) {
          keystroke += "alt-";
        }
        if (event.shiftKey) {
          keystroke += "shift-";
        }
        if (navigator.platform.startsWith("Mac")) {
          if (event.metaKey) {
            keystroke += "accel-";
          }
          if (event.ctrlKey) {
            keystroke += "control-";
          }
        } else {
          if (event.metaKey) {
            keystroke += "meta-";
          }
          if (event.ctrlKey) {
            keystroke += "accel-";
          }
        }
        switch (event.keyCode) {
          case KeyEvent.DOM_VK_UP:
            keystroke += "upArrow";
            break;
          case KeyEvent.DOM_VK_DOWN:
            keystroke += "downArrow";
            break;
          case KeyEvent.DOM_VK_LEFT:
            keystroke += "leftArrow";
            break;
          case KeyEvent.DOM_VK_RIGHT:
            keystroke += "rightArrow";
            break;
          case KeyEvent.DOM_VK_HOME:
            keystroke += "home";
            break;
          case KeyEvent.DOM_VK_END:
            keystroke += "end";
            break;
        }

        if (String.fromCharCode(event.charCode) == " ") {
          keystroke += "space";
        }

        this.log("Got keystroke: " + keystroke);
        var oldval, newval;

        try {
          switch (keystroke) {
            case "space": /* Play */
              let target = event.originalTarget;
              if (target.localName === "button" && !target.disabled) {
                break;
              }

              this.togglePause();
              break;
            case "downArrow": /* Volume decrease */
              oldval = this.video.volume;
              this.video.volume = (oldval < 0.1 ? 0 : oldval - 0.1);
              this.video.muted = false;
              break;
            case "upArrow": /* Volume increase */
              oldval = this.video.volume;
              this.video.volume = (oldval > 0.9 ? 1 : oldval + 0.1);
              this.video.muted = false;
              break;
            case "accel-downArrow": /* Mute */
              this.video.muted = true;
              break;
            case "accel-upArrow": /* Unmute */
              this.video.muted = false;
              break;
            case "leftArrow": /* Seek back 15 seconds */
            case "accel-leftArrow": /* Seek back 10% */
              oldval = this.video.currentTime;
              if (keystroke == "leftArrow") {
                newval = oldval - 15;
              } else {
                newval = oldval - (this.video.duration || this.maxCurrentTimeSeen / 1000) / 10;
              }
              this.video.currentTime = (newval >= 0 ? newval : 0);
              break;
            case "rightArrow": /* Seek forward 15 seconds */
            case "accel-rightArrow": /* Seek forward 10% */
              oldval = this.video.currentTime;
              var maxtime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
              if (keystroke == "rightArrow") {
                newval = oldval + 15;
              } else {
                newval = oldval + maxtime / 10;
              }
              this.video.currentTime = (newval <= maxtime ? newval : maxtime);
              break;
            case "home": /* Seek to beginning */
              this.video.currentTime = 0;
              break;
            case "end": /* Seek to end */
              if (this.video.currentTime != this.video.duration) {
                this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
              }
              break;
            default:
              return;
          }
        } catch (e) { /* ignore any exception from setting .currentTime */ }

        event.preventDefault(); // Prevent page scrolling
      },

      isSupportedTextTrack(textTrack) {
        return textTrack.kind == "subtitles" ||
               textTrack.kind == "captions";
      },

      get isClosedCaptionAvailable() {
        return this.overlayableTextTracks.length && !this.videocontrols.isTouchControls;
      },

      get overlayableTextTracks() {
        return Array.prototype.filter.call(this.video.textTracks, this.isSupportedTextTrack);
      },

      isClosedCaptionOn() {
        for (let tt of this.overlayableTextTracks) {
          if (tt.mode === "showing") {
            return true;
          }
        }

        return false;
      },

      setClosedCaptionButtonState() {
        if (this.isClosedCaptionOn()) {
          this.closedCaptionButton.setAttribute("enabled", "true");
        } else {
          this.closedCaptionButton.removeAttribute("enabled");
        }

        let ttItems = this.textTrackList.childNodes;

        for (let tti of ttItems) {
          const idx = +tti.getAttribute("index");

          if (idx == this.currentTextTrackIndex) {
            tti.setAttribute("on", "true");
          } else {
            tti.removeAttribute("on");
          }
        }

        this.adjustControlSize();
      },

      addNewTextTrack(tt) {
        if (!this.isSupportedTextTrack(tt)) {
          return;
        }

        if (tt.index && tt.index < this.textTracksCount) {
          // Don't create items for initialized tracks. However, we
          // still need to care about mode since TextTrackManager would
          // turn on the first available track automatically.
          if (tt.mode === "showing") {
            this.changeTextTrack(tt.index);
          }
          return;
        }

        tt.index = this.textTracksCount++;

        const label = tt.label || "";
        const ttText = document.createTextNode(label);
        const ttBtn = document.createElement("button");

        ttBtn.classList.add("textTrackItem");
        ttBtn.setAttribute("index", tt.index);

        ttBtn.addEventListener("click", event => {
          event.stopPropagation();

          this.changeTextTrack(tt.index);
        });

        ttBtn.appendChild(ttText);

        this.textTrackList.appendChild(ttBtn);

        if (tt.mode === "showing" && tt.index) {
          this.changeTextTrack(tt.index);
        }
      },

      changeTextTrack(index) {
        for (let tt of this.overlayableTextTracks) {
          if (tt.index === index) {
            tt.mode = "showing";

            this.currentTextTrackIndex = tt.index;
          } else {
            tt.mode = "disabled";
          }
        }

        // should fallback to off
        if (this.currentTextTrackIndex !== index) {
          this.currentTextTrackIndex = 0;
        }

        this.textTrackList.setAttribute("hidden", "true");
        this.setClosedCaptionButtonState();
      },

      onControlBarTransitioned() {
        this.textTrackList.setAttribute("hidden", "true");
        this.video.dispatchEvent(new CustomEvent("controlbarchange"));
        this.adjustControlSize();
      },

      toggleClosedCaption() {
        if (this.textTrackList.hasAttribute("hidden")) {
          this.textTrackList.removeAttribute("hidden");
        } else {
          this.textTrackList.setAttribute("hidden", "true");
        }
      },

      onTextTrackAdd(trackEvent) {
        this.addNewTextTrack(trackEvent.track);
        this.setClosedCaptionButtonState();
      },

      onTextTrackRemove(trackEvent) {
        const toRemoveIndex = trackEvent.track.index;
        const ttItems = this.textTrackList.childNodes;

        if (!ttItems) {
          return;
        }

        for (let tti of ttItems) {
          const idx = +tti.getAttribute("index");

          if (idx === toRemoveIndex) {
            tti.remove();
            this.textTracksCount--;
          }

          if (idx === this.currentTextTrackIndex) {
            this.currentTextTrackIndex = 0;

            this.video.dispatchEvent(new CustomEvent("texttrackchange"));
          }
        }

        this.setClosedCaptionButtonState();
      },

      initTextTracks() {
        // add 'off' button anyway as new text track might be
        // dynamically added after initialization.
        const offLabel = this.textTrackList.getAttribute("offlabel");
        this.addNewTextTrack({
          label: offLabel,
          kind: "subtitles"
        });

        for (let tt of this.overlayableTextTracks) {
          this.addNewTextTrack(tt);
        }

        this.setClosedCaptionButtonState();
      },

      isEventWithin(event, parent1, parent2) {
        function isDescendant(node) {
          while (node) {
            if (node == parent1 || node == parent2) {
              return true;
            }
            node = node.parentNode;
          }
          return false;
        }
        return isDescendant(event.target) && isDescendant(event.relatedTarget);
      },

      log(msg) {
        if (this.debug) {
          console.log("videoctl: " + msg + "\n");
        }
      },

      get isTopLevelSyntheticDocument() {
        let doc = this.video.ownerDocument;
        let win = doc.defaultView;
        return doc.mozSyntheticDocument && win === win.top;
      },

      controlBarMinHeight: 40,
      controlBarMinVisibleHeight: 28,
      adjustControlSize() {
        if (this.videocontrols.isTouchControls) {
          return;
        }

        const minControlBarPaddingWidth = 18;

        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");

        let minRequiredWidth = this.prioritizedControls
          .filter(control => control && control.isWanted)
          .reduce((accWidth, cc) => accWidth + cc.minWidth, minControlBarPaddingWidth);
        // Skip the adjustment in case the stylesheets haven't been loaded yet.
        if (!minRequiredWidth) {
          return;
        }

        let givenHeight = this.video.clientHeight;
        let videoWidth = (this.isAudioOnly ?
                          this.controlBar.clientWidth :
                          this.video.clientWidth) || minRequiredWidth;
        let videoHeight = this.isAudioOnly ? this.controlBarMinHeight : givenHeight;

        let widthUsed = minControlBarPaddingWidth;
        let preventAppendControl = false;

        for (let control of this.prioritizedControls) {
          if (!control.isWanted) {
            control.hideByAdjustment = true;
            continue;
          }

          control.hideByAdjustment = preventAppendControl ||
          widthUsed + control.minWidth > videoWidth;

          if (control.hideByAdjustment) {
            preventAppendControl = true;
          } else {
            widthUsed += control.minWidth;
          }
        }

        // Use flexible spacer to separate controls when scrubber is hidden.
        // As long as muteButton hidden, which means only play button presents,
        // hide spacer and make playButton centered.
        this.controlBarSpacer.hidden = !this.scrubberStack.hidden || this.muteButton.hidden;

        // Since the size of videocontrols is expanded with controlBar in <audio>, we
        // should fix the dimensions in order not to recursively trigger reflow afterwards.
        if (this.video instanceof HTMLAudioElement) {
          if (givenHeight) {
            // The height of controlBar should be capped with the bounds between controlBarMinHeight
            // and controlBarMinVisibleHeight.
            let controlBarHeight = Math.max(Math.min(givenHeight, this.controlBarMinHeight), this.controlBarMinVisibleHeight);
            this.controlBar.style.height = `${controlBarHeight}px`;
          }
          this.controlBar.style.width = `${videoWidth}px`;
          return;
        }

        if (videoHeight < this.controlBarMinHeight ||
            widthUsed === minControlBarPaddingWidth) {
          this.controlBar.setAttribute("size", "hidden");
          this.controlBar.hideByAdjustment = true;
        } else {
          this.controlBar.removeAttribute("size");
          this.controlBar.hideByAdjustment = false;
        }

        // Adjust clickToPlayButton size.
        const minVideoSideLength = Math.min(videoWidth, videoHeight);
        const clickToPlayViewRatio = 0.15;
        const clickToPlayScaledSize = Math.max(
        this.clickToPlay.minWidth, minVideoSideLength * clickToPlayViewRatio);

        if (clickToPlayScaledSize >= videoWidth ||
           (clickToPlayScaledSize + this.controlBarMinHeight / 2 >= videoHeight / 2 )) {
          this.clickToPlay.hideByAdjustment = true;
        } else {
          if (this.clickToPlay.hidden && !this.video.played.length && this.video.paused) {
            this.clickToPlay.hideByAdjustment = false;
          }
          this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
          this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
        }
      },

      init(binding) {
        this.video = binding.parentNode;
        this.videocontrols = binding;

        this.controlsContainer    = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
        this.statusIcon    = document.getAnonymousElementByAttribute(binding, "anonid", "statusIcon");
        this.controlBar    = document.getAnonymousElementByAttribute(binding, "anonid", "controlBar");
        this.playButton    = document.getAnonymousElementByAttribute(binding, "anonid", "playButton");
        this.controlBarSpacer    = document.getAnonymousElementByAttribute(binding, "anonid", "controlBarSpacer");
        this.muteButton    = document.getAnonymousElementByAttribute(binding, "anonid", "muteButton");
        this.volumeStack   = document.getAnonymousElementByAttribute(binding, "anonid", "volumeStack");
        this.volumeControl = document.getAnonymousElementByAttribute(binding, "anonid", "volumeControl");
        this.progressBar   = document.getAnonymousElementByAttribute(binding, "anonid", "progressBar");
        this.bufferBar     = document.getAnonymousElementByAttribute(binding, "anonid", "bufferBar");
        this.scrubberStack = document.getAnonymousElementByAttribute(binding, "anonid", "scrubberStack");
        this.scrubber      = document.getAnonymousElementByAttribute(binding, "anonid", "scrubber");
        this.durationLabel = document.getAnonymousElementByAttribute(binding, "anonid", "durationLabel");
        this.positionLabel = document.getAnonymousElementByAttribute(binding, "anonid", "positionLabel");
        this.positionDurationBox   = document.getAnonymousElementByAttribute(binding, "anonid", "positionDurationBox");
        this.statusOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "statusOverlay");
        this.controlsOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "controlsOverlay");
        this.controlsSpacer     = document.getAnonymousElementByAttribute(binding, "anonid", "controlsSpacer");
        this.clickToPlay        = document.getAnonymousElementByAttribute(binding, "anonid", "clickToPlay");
        this.fullscreenButton   = document.getAnonymousElementByAttribute(binding, "anonid", "fullscreenButton");
        this.closedCaptionButton = document.getAnonymousElementByAttribute(binding, "anonid", "closedCaptionButton");
        this.textTrackList = document.getAnonymousElementByAttribute(binding, "anonid", "textTrackList");

        if (this.positionDurationBox) {
          this.durationSpan = this.positionDurationBox.getElementsByTagName("span")[0];
        }

        this.controlBarComputedStyles = getComputedStyle(this.controlBar);

        // Hide and show control in certain order.
        this.prioritizedControls = [
          this.playButton,
          this.muteButton,
          this.fullscreenButton,
          this.closedCaptionButton,
          this.positionDurationBox,
          this.scrubberStack,
          this.durationSpan,
          this.volumeStack
        ];

        // XXX controlsContainer is a desktop only element. To determine whether
        // isTouchControls or not during the whole initialization process, get
        // this state overridden here.
        this.videocontrols.isTouchControls = !this.controlsContainer;
        this.isAudioOnly = (this.video instanceof HTMLAudioElement);
        this.setupInitialState();
        this.setupNewLoadState();
        this.initTextTracks();

        // Use the handleEvent() callback for all media events.
        // Only the "error" event listener must capture, so that it can trap error
        // events from <source> children, which don't bubble. But we use capture
        // for all events in order to simplify the event listener add/remove.
        for (let event of this.videoEvents) {
          this.video.addEventListener(event, this, {
            capture: true,
            mozSystemGroup: true
          });
        }

        var self = this;
        this.controlListeners = [];

        // Helper function to add an event listener to the given element
        // Due to this helper function, "Utils" is made available to the event
        // listener functions. Hence declare it as a global for ESLint.
        /* global Utils */
        function addListener(elem, eventName, func, capture = false) {
          let boundFunc = func.bind(self);
          self.controlListeners.push({ item: elem, event: eventName, func: boundFunc, capture });
          elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true, capture });
        }

        addListener(this.muteButton, "click", this.toggleMute);
        addListener(this.closedCaptionButton, "click", this.toggleClosedCaption);
        addListener(this.fullscreenButton, "click", this.toggleFullscreen);
        addListener(this.playButton, "click", this.clickToPlayClickHandler);
        addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
        addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
        addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);

        addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
        addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
        addListener(this.video.ownerDocument, "mozfullscreenchange", this.onFullscreenChange);
        addListener(this.controlBar, "transitionend", this.onControlBarTransitioned);
        addListener(this.video.ownerDocument, "fullscreenchange", this.onFullscreenChange);
        addListener(this.video, "keypress", this.keyHandler, true);

        addListener(this.videocontrols, "dragstart", function(event) {
          event.preventDefault(); // prevent dragging of controls image (bug 517114)
        });

        if (!this.videocontrols.isTouchControls) {
          addListener(this.scrubber, "input", this.onScrubberInput);
          addListener(this.scrubber, "change", this.onScrubberChange);
          // add mouseup listener additionally to handle the case that `change` event
          // isn't fired when the input value before/after dragging are the same. (bug 1328061)
          addListener(this.scrubber, "mouseup", this.onScrubberChange);
          addListener(this.volumeControl, "input", this.updateVolume);
          addListener(this.video.textTracks, "addtrack", this.onTextTrackAdd);
          addListener(this.video.textTracks, "removetrack", this.onTextTrackRemove);
        }

        this.log("--- videocontrols initialized ---");
      }
    };

    this.Utils.init(this);
    ]]>
  </constructor>
  <destructor>
    <![CDATA[
    this.Utils.terminateEventListeners();
    // randomID used to be a <field>, which meant that the XBL machinery
    // undefined the property when the element was unbound. The code in
    // this file actually depends on this, so now that randomID is an
    // expando, we need to make sure to explicitly delete it.
    delete this.randomID;
    ]]>
  </destructor>

  </implementation>

  <handlers>
    <handler event="mouseover">
      if (!this.isTouchControls) {
        this.Utils.onMouseInOut(event);
      }
    </handler>
    <handler event="mouseout">
      if (!this.isTouchControls) {
        this.Utils.onMouseInOut(event);
      }
    </handler>
    <handler event="mousemove">
      if (!this.isTouchControls) {
        this.Utils.onMouseMove(event);
      }
    </handler>
  </handlers>
</binding>

<binding id="touchControls" extends="chrome://global/content/bindings/videocontrols.xml#videoControls">

  <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="mediaControlsFrame">
    <stack flex="1">
      <vbox anonid="statusOverlay" flex="1" class="statusOverlay" hidden="true">
        <box anonid="statusIcon" class="statusIcon"/>
        <label class="errorLabel" anonid="errorAborted">&error.aborted;</label>
        <label class="errorLabel" anonid="errorNetwork">&error.network;</label>
        <label class="errorLabel" anonid="errorDecode">&error.decode;</label>
        <label class="errorLabel" anonid="errorSrcNotSupported">&error.srcNotSupported;</label>
        <label class="errorLabel" anonid="errorNoSource">&error.noSource2;</label>
        <label class="errorLabel" anonid="errorGeneric">&error.generic;</label>
      </vbox>

      <vbox anonid="controlsOverlay" class="controlsOverlay">
        <spacer anonid="controlsSpacer" class="controlsSpacer" flex="1"/>
        <box flex="1" hidden="true">
          <box anonid="clickToPlay" class="clickToPlay" hidden="true" flex="1"/>
          <vbox anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></vbox>
        </box>
        <vbox anonid="controlBar" class="controlBar" hidden="true">
          <hbox class="buttonsBar">
            <button anonid="playButton"
                    class="playButton"
                    playlabel="&playButton.playLabel;"
                    pauselabel="&playButton.pauseLabel;"/>
            <label anonid="positionLabel" class="positionLabel" role="presentation"/>
            <stack anonid="scrubberStack" class="scrubberStack">
              <box class="backgroundBar"/>
              <progressmeter class="flexibleBar" value="100"/>
              <progressmeter anonid="bufferBar" class="bufferBar"/>
              <progressmeter anonid="progressBar" class="progressBar" max="10000"/>
              <scale anonid="scrubber" class="scrubber" movetoclick="true"/>
            </stack>
            <label anonid="durationLabel" class="durationLabel" role="presentation"/>
            <button anonid="muteButton"
                    class="muteButton"
                    mutelabel="&muteButton.muteLabel;"
                    unmutelabel="&muteButton.unmuteLabel;"/>
            <stack anonid="volumeStack" class="volumeStack">
              <box anonid="volumeBackground" class="volumeBackground"/>
              <box anonid="volumeForeground" class="volumeForeground"/>
              <scale anonid="volumeControl" class="volumeControl" movetoclick="true"/>
            </stack>
            <button anonid="castingButton" class="castingButton" hidden="true"
                    aria-label="&castingButton.castingLabel;"/>
            <button anonid="closedCaptionButton" class="closedCaptionButton" hidden="true"/>
            <button anonid="fullscreenButton"
                    class="fullscreenButton"
                    enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
                    exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
          </hbox>
        </vbox>
      </vbox>
    </stack>
  </xbl:content>

  <implementation>
  <constructor>
    <![CDATA[
    this.isTouchControls = true;
    this.TouchUtils = {
      videocontrols: null,
      video: null,
      controlsTimer: null,
      controlsTimeout: 5000,
      positionLabel: null,
      castingButton: null,

      get Utils() {
        return this.videocontrols.Utils;
      },

      get visible() {
        return !this.Utils.controlBar.hasAttribute("fadeout") &&
               !(this.Utils.controlBar.getAttribute("hidden") == "true");
      },

      _firstShow: false,
      get firstShow() { return this._firstShow; },
      set firstShow(val) {
        this._firstShow = val;
        this.Utils.controlBar.setAttribute("firstshow", val);
      },

      toggleControls() {
        if (!this.Utils.dynamicControls || !this.visible) {
          this.showControls();
        } else {
          this.delayHideControls(0);
        }
      },

      showControls() {
        if (this.Utils.dynamicControls) {
          this.Utils.startFadeIn(this.Utils.controlBar);
          this.delayHideControls(this.controlsTimeout);
        }
      },

      clearTimer() {
        if (this.controlsTimer) {
          clearTimeout(this.controlsTimer);
          this.controlsTimer = null;
        }
      },

      delayHideControls(aTimeout) {
        this.clearTimer();
        let self = this;
        this.controlsTimer = setTimeout(function() {
          self.hideControls();
        }, aTimeout);
      },

      hideControls() {
        if (!this.Utils.dynamicControls) {
          return;
        }
        this.Utils.startFadeOut(this.Utils.controlBar);
        if (this.firstShow) {
          this.videocontrols.addEventListener("transitionend", this);
        }
      },

      handleEvent(aEvent) {
        if (aEvent.type == "transitionend") {
          this.firstShow = false;
          try {
            this.videocontrols.removeEventListener("transitionend", this);
          } catch (ex) {}
          return;
        }

        if (this.videocontrols.randomID != this.Utils.randomID) {
          this.terminateEventListeners();
        }
      },

      terminateEventListeners() {
        for (var event of this.videoEvents) {
          try {
            this.Utils.video.removeEventListener(event, this);
          } catch (ex) {}
        }
      },

      isVideoCasting() {
        return this.video.mozIsCasting;
      },

      updateCasting(eventDetail) {
        let castingData = JSON.parse(eventDetail);
        if ("allow" in castingData) {
          this.video.mozAllowCasting = !!castingData.allow;
        }

        if ("active" in castingData) {
          this.video.mozIsCasting = !!castingData.active;
        }
        this.setCastButtonState();
      },

      startCasting() {
        this.videocontrols.dispatchEvent(new CustomEvent("VideoBindingCast"));
      },

      setCastButtonState() {
        if (this.isAudioOnly || !this.video.mozAllowCasting) {
          this.castingButton.hidden = true;
          return;
        }

        if (this.video.mozIsCasting) {
          this.castingButton.setAttribute("active", "true");
        } else {
          this.castingButton.removeAttribute("active");
        }

        this.castingButton.hidden = false;
      },

      init(binding) {
        this.videocontrols = binding;
        this.video = binding.parentNode;

        let self = this;
        this.Utils.playButton.addEventListener("command", function() {
          if (!self.video.paused) {
            self.delayHideControls(0);
          } else {
            self.showControls();
          }
        });
        this.Utils.scrubber.addEventListener("touchstart", function() {
          self.clearTimer();
        });
        this.Utils.scrubber.addEventListener("touchend", function() {
          self.delayHideControls(self.controlsTimeout);
        });
        this.Utils.muteButton.addEventListener("click", function() { self.delayHideControls(self.controlsTimeout); });

        this.castingButton = document.getAnonymousElementByAttribute(binding, "anonid", "castingButton");
        this.castingButton.addEventListener("command", function() {
          self.startCasting();
        });

        this.video.addEventListener("media-videoCasting", function(e) {
          if (!e.isTrusted) {
            return;
          }
          self.updateCasting(e.detail);
        }, false, true);

        // The first time the controls appear we want to just display
        // a play button that does not fade away. The firstShow property
        // makes that happen. But because of bug 718107 this init() method
        // may be called again when we switch in or out of fullscreen
        // mode. So we only set firstShow if we're not autoplaying and
        // if we are at the beginning of the video and not already playing
        if (!this.video.autoplay && this.Utils.dynamicControls && this.video.paused &&
            this.video.currentTime === 0) {
          this.firstShow = true;
        }

        // If the video is not at the start, then we probably just
        // transitioned into or out of fullscreen mode, and we don't want
        // the controls to remain visible. this.controlsTimeout is a full
        // 5s, which feels too long after the transition.
        if (this.video.currentTime !== 0) {
          this.delayHideControls(this.Utils.HIDE_CONTROLS_TIMEOUT_MS);
        }
      }
    };

    this.TouchUtils.init(this)
    this.dispatchEvent(new CustomEvent("VideoBindingAttached"));
    ]]>
  </constructor>
  <destructor>
    <![CDATA[
    // XBL destructors don't appear to be inherited properly, so we need
    // to do this here in addition to the videoControls destructor. :-(
    delete this.randomID;
    ]]>
  </destructor>

  </implementation>

  <handlers>
    <handler event="mouseup">
      if (event.originalTarget.nodeName == "vbox") {
        if (this.TouchUtils.firstShow) {
          this.Utils.video.play();
        }
        this.TouchUtils.toggleControls();
      }
    </handler>
  </handlers>

</binding>

<binding id="noControls">

  <resources>
    <stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
    <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
  </resources>

  <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="mediaControlsFrame">
    <vbox flex="1" class="statusOverlay" hidden="true">
      <box flex="1">
        <box class="clickToPlay" flex="1"/>
      </box>
    </vbox>
  </xbl:content>

  <implementation>
  <constructor>
    <![CDATA[
    this.randomID = 0;
    this.Utils = {
      randomID: 0,
      videoEvents: ["play",
                    "playing"],
      controlListeners: [],
      terminateEventListeners() {
        for (let event of this.videoEvents) {
          try {
            this.video.removeEventListener(event, this, { mozSystemGroup: true });
          } catch (ex) {}
        }

        for (let element of this.controlListeners) {
          try {
            element.item.removeEventListener(element.event, element.func,
              { mozSystemGroup: true });
          } catch (ex) {}
        }

        delete this.controlListeners;
      },

      hasError() {
        return (this.video.error != null || this.video.networkState == this.video.NETWORK_NO_SOURCE);
      },

      handleEvent(aEvent) {
        // If the binding is detached (or has been replaced by a
        // newer instance of the binding), nuke our event-listeners.
        if (this.binding.randomID != this.randomID) {
          this.terminateEventListeners();
          return;
        }

        switch (aEvent.type) {
          case "play":
            this.noControlsOverlay.hidden = true;
            break;
          case "playing":
            this.noControlsOverlay.hidden = true;
            break;
        }
      },

      blockedVideoHandler() {
        if (this.binding.randomID != this.randomID) {
          this.terminateEventListeners();
          return;
        } else if (this.hasError()) {
          this.noControlsOverlay.hidden = true;
          return;
        }
        this.noControlsOverlay.hidden = false;
      },

      clickToPlayClickHandler(e) {
        if (this.binding.randomID != this.randomID) {
          this.terminateEventListeners();
          return;
        } else if (e.button != 0) {
          return;
        }

        this.noControlsOverlay.hidden = true;
        this.video.play();
      },

      init(binding) {
        this.binding = binding;
        this.randomID = Math.random();
        this.binding.randomID = this.randomID;
        this.video = binding.parentNode;
        this.clickToPlay       = document.getAnonymousElementByAttribute(binding, "class", "clickToPlay");
        this.noControlsOverlay = document.getAnonymousElementByAttribute(binding, "class", "statusOverlay");

        let self = this;
        function addListener(elem, eventName, func) {
          let boundFunc = func.bind(self);
          self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
          elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
        }
        addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
        addListener(this.video, "MozNoControlsBlockedVideo", this.blockedVideoHandler);

        for (let event of this.videoEvents) {
          this.video.addEventListener(event, this, { mozSystemGroup: true });
        }

        if (this.video.autoplay && !this.video.mozAutoplayEnabled) {
          this.blockedVideoHandler();
        }
      }
    };
    this.Utils.init(this);
    this.Utils.video.dispatchEvent(new CustomEvent("MozNoControlsVideoBindingAttached"));
    ]]>
  </constructor>
  <destructor>
    <![CDATA[
    this.Utils.terminateEventListeners();
    // randomID used to be a <field>, which meant that the XBL machinery
    // undefined the property when the element was unbound. The code in
    // this file actually depends on this, so now that randomID is an
    // expando, we need to make sure to explicitly delete it.
    delete this.randomID;
    ]]>
  </destructor>
  </implementation>
</binding>

</bindings>
PK
!<V`I`I1chrome/toolkit/content/global/bindings/wizard.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings [
  <!ENTITY % wizardDTD SYSTEM "chrome://global/locale/wizard.dtd">
  %wizardDTD;
]>

<bindings id="wizardBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="wizard-base">
    <resources>
      <stylesheet src="chrome://global/skin/wizard.css"/>
    </resources>
  </binding>

  <binding id="wizard" extends="chrome://global/content/bindings/general.xml#root-element">
    <resources>
      <stylesheet src="chrome://global/skin/wizard.css"/>
    </resources>
    <content>
      <xul:hbox class="wizard-header" anonid="Header"/>

      <xul:deck class="wizard-page-box" flex="1" anonid="Deck">
        <children includes="wizardpage"/>
      </xul:deck>
      <children/>

      <xul:hbox class="wizard-buttons" anonid="Buttons" xbl:inherits="pagestep,firstpage,lastpage"/>
    </content>

    <implementation>
      <property name="title" onget="return document.title;"
                             onset="return document.title = val;"/>

      <property name="canAdvance" onget="return this._canAdvance;"
                                  onset="this._nextButton.disabled = !val; return this._canAdvance = val;"/>
      <property name="canRewind" onget="return this._canRewind;"
                                 onset="this._backButton.disabled = !val; return this._canRewind = val;"/>

      <property name="pageStep" readonly="true" onget="return this._pageStack.length"/>

      <field name="pageCount">0</field>

      <field name="_accessMethod">null</field>
      <field name="_pageStack">null</field>
      <field name="_currentPage">null</field>

      <property name="wizardPages">
        <getter>
        <![CDATA[
          var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          return this.getElementsByTagNameNS(xulns, "wizardpage");
        ]]>
        </getter>
      </property>

      <property name="currentPage" onget="return this._currentPage">
        <setter>
        <![CDATA[
          if (!val)
            return val;

          this._currentPage = val;

          // Setting this attribute allows wizard's clients to dynamically
          // change the styles of each page based on purpose of the page.
          this.setAttribute("currentpageid", val.pageid);
          if (this.onFirstPage) {
            this.canRewind = false;
            this.setAttribute("firstpage", "true");
            if (/Linux/.test(navigator.platform)) {
              this._backButton.setAttribute('hidden', 'true');
            }
          } else {
            this.canRewind = true;
            this.setAttribute("firstpage", "false");
            if (/Linux/.test(navigator.platform)) {
              this._backButton.setAttribute('hidden', 'false');
            }
          }

          if (this.onLastPage) {
            this.canAdvance = true;
            this.setAttribute("lastpage", "true");
          } else {
            this.setAttribute("lastpage", "false");
          }

          this._deck.setAttribute("selectedIndex", val.pageIndex);
          this._advanceFocusToPage(val);

          this._adjustWizardHeader();
          this._wizardButtons.onPageChange();

          this._fireEvent(val, "pageshow");

          return val;
        ]]>
        </setter>
      </property>

      <property name="pageIndex"
                onget="return this._currentPage ? this._currentPage.pageIndex : -1;">
        <setter>
        <![CDATA[
          if (val < 0 || val >= this.pageCount)
            return val;

          var page = this.wizardPages[val];
          this._pageStack[this._pageStack.length-1] = page;
          this.currentPage = page;

          return val;
        ]]>
        </setter>
      </property>

      <property name="onFirstPage" readonly="true"
                onget="return this._pageStack.length == 1;"/>

      <property name="onLastPage" readonly="true">
        <getter><![CDATA[
          var cp = this.currentPage;
          return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount-1) ||
                       (this._accessMethod == "random" && cp.next == ""));
         ]]></getter>
      </property>

      <method name="getButton">
        <parameter name="aDlgType"/>
        <body>
        <![CDATA[
          var btns = this.getElementsByAttribute("dlgtype", aDlgType);
          return btns.item(0) ? btns[0] : document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aDlgType);
        ]]>
        </body>
      </method>

      <field name="_canAdvance"/>
      <field name="_canRewind"/>
      <field name="_wizardHeader"/>
      <field name="_wizardButtons"/>
      <field name="_deck"/>
      <field name="_backButton"/>
      <field name="_nextButton"/>
      <field name="_cancelButton"/>

      <!-- functions to be added as oncommand listeners to the wizard buttons -->
      <field name="_backFunc">(function() { document.documentElement.rewind(); })</field>
      <field name="_nextFunc">(function() { document.documentElement.advance(); })</field>
      <field name="_finishFunc">(function() { document.documentElement.advance(); })</field>
      <field name="_cancelFunc">(function() { document.documentElement.cancel(); })</field>
      <field name="_extra1Func">(function() { document.documentElement.extra1(); })</field>
      <field name="_extra2Func">(function() { document.documentElement.extra2(); })</field>

      <field name="_closeHandler">(function(event) {
        if (document.documentElement.cancel())
          event.preventDefault();
      })</field>

      <constructor><![CDATA[
        this._canAdvance = true;
        this._canRewind = false;
        this._hasLoaded = false;

        this._pageStack = [];

        try {
          // need to create string bundle manually instead of using <xul:stringbundle/>
          // see bug 63370 for details
          this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                   .getService(Components.interfaces.nsIStringBundleService)
                                   .createBundle("chrome://global/locale/wizard.properties");
        } catch (e) {
          // This fails in remote XUL, which has to provide titles for all pages
          // see bug 142502
        }

        // get anonymous content references
        this._wizardHeader = document.getAnonymousElementByAttribute(this, "anonid", "Header");
        this._wizardButtons = document.getAnonymousElementByAttribute(this, "anonid", "Buttons");
        this._deck = document.getAnonymousElementByAttribute(this, "anonid", "Deck");

        this._initWizardButton("back");
        this._initWizardButton("next");
        this._initWizardButton("finish");
        this._initWizardButton("cancel");
        this._initWizardButton("extra1");
        this._initWizardButton("extra2");

        this._initPages();

        window.addEventListener("close", this._closeHandler);

        // start off on the first page
        this.pageCount = this.wizardPages.length;
        this.advance();

        // give focus to the first focusable element in the dialog
        window.addEventListener("load", this._setInitialFocus);
      ]]></constructor>

      <method name="getPageById">
        <parameter name="aPageId"/>
        <body><![CDATA[
          var els = this.getElementsByAttribute("pageid", aPageId);
          return els.item(0);
        ]]></body>
      </method>

      <method name="extra1">
        <body><![CDATA[
          if (this.currentPage)
            this._fireEvent(this.currentPage, "extra1");
        ]]></body>
      </method>

      <method name="extra2">
        <body><![CDATA[
          if (this.currentPage)
            this._fireEvent(this.currentPage, "extra2");
        ]]></body>
      </method>

      <method name="rewind">
        <body><![CDATA[
          if (!this.canRewind)
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pagerewound"))
            return;

          if (!this._fireEvent(this, "wizardback"))
            return;


          this._pageStack.pop();
          this.currentPage = this._pageStack[this._pageStack.length-1];
          this.setAttribute("pagestep", this._pageStack.length);
        ]]></body>
      </method>

      <method name="advance">
        <parameter name="aPageId"/>
        <body><![CDATA[
          if (!this.canAdvance)
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
            return;

          if (this.currentPage && !this._fireEvent(this.currentPage, "pageadvanced"))
            return;

          if (this.onLastPage && !aPageId) {
            if (this._fireEvent(this, "wizardfinish"))
              window.setTimeout(function() {window.close();}, 1);
          } else {
            if (!this._fireEvent(this, "wizardnext"))
              return;

            var page;
            if (aPageId)
              page = this.getPageById(aPageId);
            else {
              if (this.currentPage) {
                if (this._accessMethod == "random")
                  page = this.getPageById(this.currentPage.next);
                else
                  page = this.wizardPages[this.currentPage.pageIndex+1];
              } else
                page = this.wizardPages[0];
            }

            if (page) {
              this._pageStack.push(page);
              this.setAttribute("pagestep", this._pageStack.length);

              this.currentPage = page;
            }
          }
        ]]></body>
      </method>

      <method name="goTo">
        <parameter name="aPageId"/>
        <body><![CDATA[
          var page = this.getPageById(aPageId);
          if (page) {
            this._pageStack[this._pageStack.length-1] = page;
            this.currentPage = page;
          }
        ]]></body>
      </method>

      <method name="cancel">
        <body><![CDATA[
          if (!this._fireEvent(this, "wizardcancel"))
            return true;

          window.close();
          window.setTimeout(function() {window.close();}, 1);
          return false;
        ]]></body>
      </method>

      <method name="_setInitialFocus">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          document.documentElement._hasLoaded = true;
          var focusInit =
            function() {
              // give focus to the first focusable element in the dialog
              if (!document.commandDispatcher.focusedElement)
                document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);

              try {
                var button =
                      document.documentElement._wizardButtons.defaultButton;
                if (button)
                  window.notifyDefaultButtonLoaded(button);
              } catch (e) { }
            };

          // Give focus after onload completes, see bug 103197.
          setTimeout(focusInit, 0);
        ]]>
        </body>
      </method>

      <method name="_advanceFocusToPage">
        <parameter name="aPage"/>
        <body>
        <![CDATA[
          if (!this._hasLoaded)
            return;

          document.commandDispatcher.advanceFocusIntoSubtree(aPage);

          // if advanceFocusIntoSubtree tries to focus one of our
          // dialog buttons, then remove it and put it on the root
          var focused = document.commandDispatcher.focusedElement;
          if (focused && focused.hasAttribute("dlgtype"))
            this.focus();
        ]]>
        </body>
      </method>

      <method name="_initPages">
        <body><![CDATA[
          var meth = "sequential";
          var pages = this.wizardPages;
          for (var i = 0; i < pages.length; ++i) {
            var page = pages[i];
            page.pageIndex = i;
            if (page.next != "")
              meth = "random";
          }
          this._accessMethod = meth;
        ]]></body>
      </method>

      <method name="_initWizardButton">
        <parameter name="aName"/>
        <body><![CDATA[
         var btn = document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aName);
         if (btn) {
           btn.addEventListener("command", this["_"+aName+"Func"]);
           this["_"+aName+"Button"] = btn;
         }
         return btn;
        ]]></body>
      </method>

      <method name="_adjustWizardHeader">
        <body><![CDATA[
          var label = this.currentPage.getAttribute("label");
          if (!label && this.onFirstPage && this._bundle) {
            if (/Mac/.test(navigator.platform)) {
              label = this._bundle.GetStringFromName("default-first-title-mac");
            } else {
              label = this._bundle.formatStringFromName("default-first-title", [this.title], 1);
            }
          } else if (!label && this.onLastPage && this._bundle) {
            if (/Mac/.test(navigator.platform)) {
              label = this._bundle.GetStringFromName("default-last-title-mac");
            } else {
              label = this._bundle.formatStringFromName("default-last-title", [this.title], 1);
            }
          }
          this._wizardHeader.setAttribute("label", label);
          this._wizardHeader.setAttribute("description", this.currentPage.getAttribute("description"));
        ]]></body>
      </method>

      <method name="_hitEnter">
        <parameter name="evt"/>
        <body>
        <![CDATA[
          if (!evt.defaultPrevented)
            this.advance();
        ]]>
        </body>
      </method>

      <method name="_fireEvent">
        <parameter name="aTarget"/>
        <parameter name="aType"/>
        <body>
        <![CDATA[
          var event = document.createEvent("Events");
          event.initEvent(aType, true, true);

          // handle dom event handlers
          var noCancel = aTarget.dispatchEvent(event);

          // handle any xml attribute event handlers
          var handler = aTarget.getAttribute("on"+aType);
          if (handler != "") {
            var fn = new Function("event", handler);
            var returned = fn.apply(aTarget, [event]);
            if (returned == false)
              noCancel = false;
          }

          return noCancel;
        ]]>
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_RETURN"
               group="system" action="this._hitEnter(event)"/>
      <handler event="keypress" keycode="VK_ESCAPE" group="system">
        if (!event.defaultPrevented)
          this.cancel();
      </handler>
    </handlers>
  </binding>

  <binding id="wizardpage" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <implementation>
      <field name="pageIndex">-1</field>

      <property name="pageid" onget="return this.getAttribute('pageid');"
                              onset="this.setAttribute('pageid', val);"/>

      <property name="next"   onget="return this.getAttribute('next');"
                              onset="this.setAttribute('next', val);
                                     this.parentNode._accessMethod = 'random';
                                     return val;"/>
    </implementation>
  </binding>


  <binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <content>
      <xul:hbox class="wizard-header-box-1" flex="1">
        <xul:vbox class="wizard-header-box-text" flex="1">
          <xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
          <xul:label class="wizard-header-description" xbl:inherits="xbl:text=description"/>
        </xul:vbox>
        <xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
    <content>
      <xul:vbox class="wizard-buttons-box-1" flex="1">
        <xul:separator class="wizard-buttons-separator groove"/>
        <xul:hbox class="wizard-buttons-box-2">
          <xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
          <xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
          <xul:spacer flex="1" anonid="spacer"/>
          <xul:button label="&button-back-win.label;" accesskey="&button-back-win.accesskey;"
                      class="wizard-button" dlgtype="back" icon="go-back"/>
          <xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
            <xul:hbox>
              <xul:button label="&button-finish-win.label;" class="wizard-button"
                          dlgtype="finish" default="true" flex="1"/>
            </xul:hbox>
            <xul:hbox>
              <xul:button label="&button-next-win.label;" accesskey="&button-next-win.accesskey;"
                          class="wizard-button" dlgtype="next" icon="go-forward"
                          default="true" flex="1"/>
            </xul:hbox>
          </xul:deck>
          <xul:button label="&button-cancel-win.label;" class="wizard-button"
                      dlgtype="cancel" icon="cancel"/>
        </xul:hbox>
      </xul:vbox>
    </content>

    <implementation>
      <field name="_wizardButtonDeck" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "WizardButtonDeck");
      </field>

      <method name="onPageChange">
        <body><![CDATA[
          if (this.getAttribute("lastpage") == "true") {
            this._wizardButtonDeck.setAttribute("selectedIndex", 0);
          } else {
            this._wizardButtonDeck.setAttribute("selectedIndex", 1);
          }
        ]]></body>
      </method>

      <property name="defaultButton" readonly="true">
        <getter><![CDATA[
          const kXULNS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var buttons = this._wizardButtonDeck.selectedPanel
                            .getElementsByTagNameNS(kXULNS, "button");
          for (var i = 0; i < buttons.length; i++) {
            if (buttons[i].getAttribute("default") == "true" &&
                !buttons[i].hidden && !buttons[i].disabled)
              return buttons[i];
          }
          return null;
        ]]></getter>
      </property>
    </implementation>
  </binding>

</bindings>
PK
!<]jUCNCN.chrome/toolkit/content/global/browser-child.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/BrowserUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
Cu.import("resource://gre/modules/Timer.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
  "resource://gre/modules/PageThumbUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Utils",
  "resource://gre/modules/sessionstore/Utils.jsm");

if (AppConstants.MOZ_CRASHREPORTER) {
  XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
                                     "@mozilla.org/xre/app-info;1",
                                     "nsICrashReporter");
}

var WebProgressListener = {
  init() {
    this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                     .createInstance(Ci.nsIWebProgress);
    this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
    this._filter.target = tabEventTarget;

    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebProgress);
    webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
  },

  uninit() {
    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebProgress);
    webProgress.removeProgressListener(this._filter);

    this._filter.removeProgressListener(this);
    this._filter = null;
  },

  _requestSpec(aRequest, aPropertyName) {
    if (!aRequest || !(aRequest instanceof Ci.nsIChannel))
      return null;
    return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec;
  },

  _setupJSON: function setupJSON(aWebProgress, aRequest, aStateFlags) {
    // Avoid accessing content.document when being called from onStateChange
    // unless if we are in STATE_STOP, because otherwise the getter will
    // instantiate an about:blank document for us.
    let contentDocument = null;
    if (aStateFlags) {
      // We're being called from onStateChange
      if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        contentDocument = content.document;
      }
    } else {
      contentDocument = content.document;
    }

    let innerWindowID = null;
    if (aWebProgress) {
      let domWindowID = null;
      try {
        domWindowID = aWebProgress.DOMWindowID;
        innerWindowID = aWebProgress.innerDOMWindowID;
      } catch (e) {
        // The DOM Window ID getters above may throw if the inner or outer
        // windows aren't created yet or are destroyed at the time we're making
        // this call but that isn't fatal so ignore the exceptions here.
      }

      aWebProgress = {
        isTopLevel: aWebProgress.isTopLevel,
        isLoadingDocument: aWebProgress.isLoadingDocument,
        loadType: aWebProgress.loadType,
        DOMWindowID: domWindowID
      };
    }

    return {
      webProgress: aWebProgress || null,
      requestURI: this._requestSpec(aRequest, "URI"),
      originalRequestURI: this._requestSpec(aRequest, "originalURI"),
      documentContentType: contentDocument ? contentDocument.contentType : null,
      innerWindowID,
    };
  },

  _setupObjects: function setupObjects(aWebProgress, aRequest) {
    let domWindow;
    try {
      domWindow = aWebProgress && aWebProgress.DOMWindow;
    } catch (e) {
      // If nsDocShell::Destroy has already been called, then we'll
      // get NS_NOINTERFACE when trying to get the DOM window. Ignore
      // that here.
      domWindow = null;
    }

    return {
      contentWindow: content,
      // DOMWindow is not necessarily the content-window with subframes.
      DOMWindow: domWindow,
      webProgress: aWebProgress,
      request: aRequest,
    };
  },

  _send(name, data, objects) {
    if (RemoteAddonsChild.useSyncWebProgress) {
      sendRpcMessage(name, data, objects);
    } else {
      sendAsyncMessage(name, data, objects);
    }
  },

  onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    let json = this._setupJSON(aWebProgress, aRequest, aStateFlags);
    let objects = this._setupObjects(aWebProgress, aRequest);

    json.stateFlags = aStateFlags;
    json.status = aStatus;

    // It's possible that this state change was triggered by
    // loading an internal error page, for which the parent
    // will want to know some details, so we'll update it with
    // the documentURI.
    if (aWebProgress && aWebProgress.isTopLevel) {
      json.documentURI = content.document.documentURIObject.spec;
      json.charset = content.document.characterSet;
      json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
      json.inLoadURI = WebNavigation.inLoadURI;
    }

    this._send("Content:StateChange", json, objects);
  },

  // Note: Because the nsBrowserStatusFilter timeout runnable is
  // SystemGroup-labeled, this method should not modify content DOM or
  // run content JS.
  onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
    let json = this._setupJSON(aWebProgress, aRequest);
    let objects = this._setupObjects(aWebProgress, aRequest);

    json.curSelf = aCurSelf;
    json.maxSelf = aMaxSelf;
    json.curTotal = aCurTotal;
    json.maxTotal = aMaxTotal;

    this._send("Content:ProgressChange", json, objects);
  },

  onProgressChange64: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
    this.onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal);
  },

  onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
    let json = this._setupJSON(aWebProgress, aRequest);
    let objects = this._setupObjects(aWebProgress, aRequest);

    json.location = aLocationURI ? aLocationURI.spec : "";
    json.flags = aFlags;

    // These properties can change even for a sub-frame navigation.
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    json.canGoBack = webNav.canGoBack;
    json.canGoForward = webNav.canGoForward;

    if (aWebProgress && aWebProgress.isTopLevel) {
      json.documentURI = content.document.documentURIObject.spec;
      json.title = content.document.title;
      json.charset = content.document.characterSet;
      json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
      json.principal = content.document.nodePrincipal;
      json.synthetic = content.document.mozSyntheticDocument;
      json.inLoadURI = WebNavigation.inLoadURI;

      if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
        let uri = aLocationURI.clone();
        try {
          // If the current URI contains a username/password, remove it.
          uri.userPass = "";
        } catch (ex) { /* Ignore failures on about: URIs. */ }
        CrashReporter.annotateCrashReport("URL", uri.spec);
      }
    }

    this._send("Content:LocationChange", json, objects);
  },

  // Note: Because the nsBrowserStatusFilter timeout runnable is
  // SystemGroup-labeled, this method should not modify content DOM or
  // run content JS.
  onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
    let json = this._setupJSON(aWebProgress, aRequest);
    let objects = this._setupObjects(aWebProgress, aRequest);

    json.status = aStatus;
    json.message = aMessage;

    this._send("Content:StatusChange", json, objects);
  },

  onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
    let json = this._setupJSON(aWebProgress, aRequest);
    let objects = this._setupObjects(aWebProgress, aRequest);

    json.state = aState;
    json.status = SecurityUI.getSSLStatusAsString();

    this._send("Content:SecurityChange", json, objects);
  },

  onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
    return true;
  },

  sendLoadCallResult() {
    sendAsyncMessage("Content:LoadURIResult");
  },

  QueryInterface: function QueryInterface(aIID) {
    if (aIID.equals(Ci.nsIWebProgressListener) ||
        aIID.equals(Ci.nsIWebProgressListener2) ||
        aIID.equals(Ci.nsISupportsWeakReference) ||
        aIID.equals(Ci.nsISupports)) {
        return this;
    }

    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
};

WebProgressListener.init();
addEventListener("unload", () => {
  WebProgressListener.uninit();
});

var WebNavigation =  {
  init() {
    addMessageListener("WebNavigation:GoBack", this);
    addMessageListener("WebNavigation:GoForward", this);
    addMessageListener("WebNavigation:GotoIndex", this);
    addMessageListener("WebNavigation:LoadURI", this);
    addMessageListener("WebNavigation:SetOriginAttributes", this);
    addMessageListener("WebNavigation:Reload", this);
    addMessageListener("WebNavigation:Stop", this);
  },

  get webNavigation() {
    return docShell.QueryInterface(Ci.nsIWebNavigation);
  },

  _inLoadURI: false,

  get inLoadURI() {
    return this._inLoadURI;
  },

  receiveMessage(message) {
    switch (message.name) {
      case "WebNavigation:GoBack":
        this.goBack();
        break;
      case "WebNavigation:GoForward":
        this.goForward();
        break;
      case "WebNavigation:GotoIndex":
        this.gotoIndex(message.data.index);
        break;
      case "WebNavigation:LoadURI":
        let histogram = Services.telemetry.getKeyedHistogramById("FX_TAB_REMOTE_NAVIGATION_DELAY_MS");
        histogram.add("WebNavigation:LoadURI",
                      Services.telemetry.msSystemNow() - message.data.requestTime);

        this.loadURI(message.data.uri, message.data.flags,
                     message.data.referrer, message.data.referrerPolicy,
                     message.data.postData, message.data.headers,
                     message.data.baseURI, message.data.triggeringPrincipal);
        break;
      case "WebNavigation:SetOriginAttributes":
        this.setOriginAttributes(message.data.originAttributes);
        break;
      case "WebNavigation:Reload":
        this.reload(message.data.flags);
        break;
      case "WebNavigation:Stop":
        this.stop(message.data.flags);
        break;
    }
  },

  _wrapURIChangeCall(fn) {
    this._inLoadURI = true;
    try {
      fn();
    } finally {
      this._inLoadURI = false;
      WebProgressListener.sendLoadCallResult();
    }
  },

  goBack() {
    if (this.webNavigation.canGoBack) {
      this._wrapURIChangeCall(() => this.webNavigation.goBack());
    }
  },

  goForward() {
    if (this.webNavigation.canGoForward) {
      this._wrapURIChangeCall(() => this.webNavigation.goForward());
    }
  },

  gotoIndex(index) {
    this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index));
  },

  loadURI(uri, flags, referrer, referrerPolicy, postData, headers, baseURI, triggeringPrincipal) {
    if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
      let annotation = uri;
      try {
        let url = Services.io.newURI(uri);
        // If the current URI contains a username/password, remove it.
        url.userPass = "";
        annotation = url.spec;
      } catch (ex) { /* Ignore failures to parse and failures
                      on about: URIs. */ }
      CrashReporter.annotateCrashReport("URL", annotation);
    }
    if (referrer)
      referrer = Services.io.newURI(referrer);
    if (postData)
      postData = Utils.makeInputStream(postData);
    if (headers)
      headers = Utils.makeInputStream(headers);
    if (baseURI)
      baseURI = Services.io.newURI(baseURI);
    if (triggeringPrincipal)
      triggeringPrincipal = Utils.deserializePrincipal(triggeringPrincipal)
    this._wrapURIChangeCall(() => {
      return this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
                                                   postData, headers, baseURI, triggeringPrincipal);
    });
  },

  setOriginAttributes(originAttributes) {
    if (originAttributes) {
      this.webNavigation.setOriginAttributesBeforeLoading(originAttributes);
    }
  },

  reload(flags) {
    this.webNavigation.reload(flags);
  },

  stop(flags) {
    this.webNavigation.stop(flags);
  }
};

WebNavigation.init();

var SecurityUI = {
  getSSLStatusAsString() {
    let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;

    if (status) {
      let helper = Cc["@mozilla.org/network/serialization-helper;1"]
                      .getService(Ci.nsISerializationHelper);

      status.QueryInterface(Ci.nsISerializable);
      return helper.serializeToString(status);
    }

    return null;
  }
};

var ControllerCommands = {
  init() {
    addMessageListener("ControllerCommands:Do", this);
    addMessageListener("ControllerCommands:DoWithParams", this);
  },

  receiveMessage(message) {
    switch (message.name) {
      case "ControllerCommands:Do":
        if (docShell.isCommandEnabled(message.data))
          docShell.doCommand(message.data);
        break;

      case "ControllerCommands:DoWithParams":
        var data = message.data;
        if (docShell.isCommandEnabled(data.cmd)) {
          var params = Cc["@mozilla.org/embedcomp/command-params;1"].
                       createInstance(Ci.nsICommandParams);
          for (var name in data.params) {
            var value = data.params[name];
            if (value.type == "long") {
              params.setLongValue(name, parseInt(value.value));
            } else {
              throw Cr.NS_ERROR_NOT_IMPLEMENTED;
            }
          }
          docShell.doCommandWithParams(data.cmd, params);
        }
        break;
    }
  }
}

ControllerCommands.init()

addEventListener("DOMTitleChanged", function(aEvent) {
  if (!aEvent.isTrusted || aEvent.target.defaultView != content)
    return;
  sendAsyncMessage("DOMTitleChanged", { title: content.document.title });
}, false);

addEventListener("DOMWindowClose", function(aEvent) {
  if (!aEvent.isTrusted)
    return;
  sendAsyncMessage("DOMWindowClose");
}, false);

addEventListener("ImageContentLoaded", function(aEvent) {
  if (content.document instanceof Ci.nsIImageDocument) {
    let req = content.document.imageRequest;
    if (!req.image)
      return;
    sendAsyncMessage("ImageDocumentLoaded", { width: req.image.width,
                                              height: req.image.height });
  }
}, false);

const ZoomManager = {
  get fullZoom() {
    return this._cache.fullZoom;
  },

  get textZoom() {
    return this._cache.textZoom;
  },

  set fullZoom(value) {
    this._cache.fullZoom = value;
    this._markupViewer.fullZoom = value;
  },

  set textZoom(value) {
    this._cache.textZoom = value;
    this._markupViewer.textZoom = value;
  },

  refreshFullZoom() {
    return this._refreshZoomValue("fullZoom");
  },

  refreshTextZoom() {
    return this._refreshZoomValue("textZoom");
  },

  /**
   * Retrieves specified zoom property value from markupViewer and refreshes
   * cache if needed.
   * @param valueName Either 'fullZoom' or 'textZoom'.
   * @returns Returns true if cached value was actually refreshed.
   * @private
   */
  _refreshZoomValue(valueName) {
    let actualZoomValue = this._markupViewer[valueName];
    // Round to remove any floating-point error.
    actualZoomValue = Number(actualZoomValue.toFixed(2));
    if (actualZoomValue != this._cache[valueName]) {
      this._cache[valueName] = actualZoomValue;
      return true;
    }
    return false;
  },

  get _markupViewer() {
    return docShell.contentViewer;
  },

  _cache: {
    fullZoom: NaN,
    textZoom: NaN
  }
};

addMessageListener("FullZoom", function(aMessage) {
  ZoomManager.fullZoom = aMessage.data.value;
});

addMessageListener("TextZoom", function(aMessage) {
  ZoomManager.textZoom = aMessage.data.value;
});

addEventListener("FullZoomChange", function() {
  if (ZoomManager.refreshFullZoom()) {
    sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom });
  }
}, false);

addEventListener("TextZoomChange", function(aEvent) {
  if (ZoomManager.refreshTextZoom()) {
    sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom });
  }
}, false);

addEventListener("ZoomChangeUsingMouseWheel", function() {
  sendAsyncMessage("ZoomChangeUsingMouseWheel", {});
}, false);

addMessageListener("UpdateCharacterSet", function(aMessage) {
  docShell.charset = aMessage.data.value;
  docShell.gatherCharsetMenuTelemetry();
});

/**
 * Remote thumbnail request handler for PageThumbs thumbnails.
 */
addMessageListener("Browser:Thumbnail:Request", function(aMessage) {
  let snapshot;
  let args = aMessage.data.additionalArgs;
  let fullScale = args ? args.fullScale : false;
  if (fullScale) {
    snapshot = PageThumbUtils.createSnapshotThumbnail(content, null, args);
  } else {
    let snapshotWidth = aMessage.data.canvasWidth;
    let snapshotHeight = aMessage.data.canvasHeight;
    snapshot =
      PageThumbUtils.createCanvas(content, snapshotWidth, snapshotHeight);
    PageThumbUtils.createSnapshotThumbnail(content, snapshot, args);
  }

  snapshot.toBlob(function(aBlob) {
    sendAsyncMessage("Browser:Thumbnail:Response", {
      thumbnail: aBlob,
      id: aMessage.data.id
    });
  });
});

/**
 * Remote isSafeForCapture request handler for PageThumbs.
 */
addMessageListener("Browser:Thumbnail:CheckState", function(aMessage) {
  let result = PageThumbUtils.shouldStoreContentThumbnail(content, docShell);
  sendAsyncMessage("Browser:Thumbnail:CheckState:Response", {
    result
  });
});

/**
 * Remote GetOriginalURL request handler for PageThumbs.
 */
addMessageListener("Browser:Thumbnail:GetOriginalURL", function(aMessage) {
  let channel = docShell.currentDocumentChannel;
  let channelError = PageThumbUtils.isChannelErrorResponse(channel);
  let originalURL;
  try {
    originalURL = channel.originalURI.spec;
  } catch (ex) {}
  sendAsyncMessage("Browser:Thumbnail:GetOriginalURL:Response", {
    channelError,
    originalURL,
  });
});

/**
 * Remote createAboutBlankContentViewer request handler.
 */
addMessageListener("Browser:CreateAboutBlank", function(aMessage) {
  if (!content.document || content.document.documentURI != "about:blank") {
    throw new Error("Can't create a content viewer unless on about:blank");
  }
  let principal = aMessage.data;
  principal = BrowserUtils.principalWithMatchingOA(principal, content.document.nodePrincipal);
  docShell.createAboutBlankContentViewer(principal);
});

// The AddonsChild needs to be rooted so that it stays alive as long as
// the tab.
var AddonsChild = RemoteAddonsChild.init(this);
if (AddonsChild) {
  addEventListener("unload", () => {
    RemoteAddonsChild.uninit(AddonsChild);
  });
}

addMessageListener("InPermitUnload", msg => {
  let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
  sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload});
});

addMessageListener("PermitUnload", msg => {
  sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"});

  let permitUnload = true;
  if (docShell && docShell.contentViewer) {
    permitUnload = docShell.contentViewer.permitUnload();
  }

  sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload});
});

// We may not get any responses to Browser:Init if the browser element
// is torn down too quickly.
var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils)
                           .outerWindowID;
sendAsyncMessage("Browser:Init", {outerWindowID});
PK
!<;⻔0chrome/toolkit/content/global/browser-content.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/. */

/* eslint-env mozilla/frame-script */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
  "resource://gre/modules/ReaderMode.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper",
  "resource://gre/modules/SelectContentHelper.jsm");

var global = this;


// Lazily load the finder code
addMessageListener("Finder:Initialize", function() {
  let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
  new RemoteFinderListener(global);
});

var ClickEventHandler = {
  init: function init() {
    this._scrollable = null;
    this._scrolldir = "";
    this._startX = null;
    this._startY = null;
    this._screenX = null;
    this._screenY = null;
    this._lastFrame = null;
    this._autoscrollHandledByApz = false;
    this._scrollId = null;
    this.autoscrollLoop = this.autoscrollLoop.bind(this);

    Services.els.addSystemEventListener(global, "mousedown", this, true);

    addMessageListener("Autoscroll:Stop", this);
  },

  isAutoscrollBlocker(node) {
    let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
    let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");

    while (node) {
      if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
          node.hasAttribute("href")) {
        return true;
      }

      if (mmPaste && (node instanceof content.HTMLInputElement ||
                      node instanceof content.HTMLTextAreaElement)) {
        return true;
      }

      if (node instanceof content.XULElement && mmScrollbarPosition
          && (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
        return true;
      }

      node = node.parentNode;
    }
    return false;
  },

  findNearestScrollableElement(aNode) {
    // this is a list of overflow property values that allow scrolling
    const scrollingAllowed = ["scroll", "auto"];

    // go upward in the DOM and find any parent element that has a overflow
    // area and can therefore be scrolled
    for (this._scrollable = aNode; this._scrollable;
         this._scrollable = this._scrollable.parentNode) {
      // do not use overflow based autoscroll for <html> and <body>
      // Elements or non-html elements such as svg or Document nodes
      // also make sure to skip select elements that are not multiline
      if (!(this._scrollable instanceof content.HTMLElement) ||
          ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) {
        continue;
      }

      var overflowx = this._scrollable.ownerGlobal
                          .getComputedStyle(this._scrollable)
                          .getPropertyValue("overflow-x");
      var overflowy = this._scrollable.ownerGlobal
                          .getComputedStyle(this._scrollable)
                          .getPropertyValue("overflow-y");
      // we already discarded non-multiline selects so allow vertical
      // scroll for multiline ones directly without checking for a
      // overflow property
      var scrollVert = this._scrollable.scrollTopMax &&
        (this._scrollable instanceof content.HTMLSelectElement ||
         scrollingAllowed.indexOf(overflowy) >= 0);

      // do not allow horizontal scrolling for select elements, it leads
      // to visual artifacts and is not the expected behavior anyway
      if (!(this._scrollable instanceof content.HTMLSelectElement) &&
          this._scrollable.scrollLeftMin != this._scrollable.scrollLeftMax &&
          scrollingAllowed.indexOf(overflowx) >= 0) {
        this._scrolldir = scrollVert ? "NSEW" : "EW";
        break;
      } else if (scrollVert) {
        this._scrolldir = "NS";
        break;
      }
    }

    if (!this._scrollable) {
      this._scrollable = aNode.ownerGlobal;
      if (this._scrollable.scrollMaxX != this._scrollable.scrollMinX) {
        this._scrolldir = this._scrollable.scrollMaxY !=
                          this._scrollable.scrollMinY ? "NSEW" : "EW";
      } else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) {
        this._scrolldir = "NS";
      } else if (this._scrollable.frameElement) {
        this.findNearestScrollableElement(this._scrollable.frameElement);
      } else {
        this._scrollable = null; // abort scrolling
      }
    }
  },

  startScroll(event) {

    this.findNearestScrollableElement(event.originalTarget);

    if (!this._scrollable)
      return;

    let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils);
    let scrollable = this._scrollable;
    if (scrollable instanceof Ci.nsIDOMWindow) {
      // getViewId() needs an element to operate on.
      scrollable = scrollable.document.documentElement;
    }
    this._scrollId = null;
    try {
      this._scrollId = domUtils.getViewId(scrollable);
    } catch (e) {
      // No view ID - leave this._scrollId as null. Receiving side will check.
    }
    let presShellId = domUtils.getPresShellId();
    let [enabled] = sendSyncMessage("Autoscroll:Start",
                                    {scrolldir: this._scrolldir,
                                     screenX: event.screenX,
                                     screenY: event.screenY,
                                     scrollId: this._scrollId,
                                     presShellId});
    if (!enabled) {
      this._scrollable = null;
      return;
    }

    Services.els.addSystemEventListener(global, "mousemove", this, true);
    addEventListener("pagehide", this, true);
    Services.obs.addObserver(this, "autoscroll-handled-by-apz");

    this._ignoreMouseEvents = true;
    this._startX = event.screenX;
    this._startY = event.screenY;
    this._screenX = event.screenX;
    this._screenY = event.screenY;
    this._scrollErrorX = 0;
    this._scrollErrorY = 0;
    this._autoscrollHandledByApz = false;
    this._lastFrame = content.performance.now();

    content.requestAnimationFrame(this.autoscrollLoop);
  },

  stopScroll() {
    if (this._scrollable) {
      this._scrollable.mozScrollSnap();
      this._scrollable = null;

      Services.els.removeSystemEventListener(global, "mousemove", this, true);
      removeEventListener("pagehide", this, true);
      Services.obs.removeObserver(this, "autoscroll-handled-by-apz");
    }
  },

  accelerate(curr, start) {
    const speed = 12;
    var val = (curr - start) / speed;

    if (val > 1)
      return val * Math.sqrt(val) - 1;
    if (val < -1)
      return val * Math.sqrt(-val) + 1;
    return 0;
  },

  roundToZero(num) {
    if (num > 0)
      return Math.floor(num);
    return Math.ceil(num);
  },

  autoscrollLoop(timestamp) {
    if (!this._scrollable) {
      // Scrolling has been canceled
      return;
    }

    if (this._autoscrollHandledByApz) {
      // APZ is handling the autoscroll, so we don't need to keep running
      // this callback.
      return;
    }

    // avoid long jumps when the browser hangs for more than
    // |maxTimeDelta| ms
    const maxTimeDelta = 100;
    var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
    // we used to scroll |accelerate()| pixels every 20ms (50fps)
    var timeCompensation = timeDelta / 20;
    this._lastFrame = timestamp;

    var actualScrollX = 0;
    var actualScrollY = 0;
    // don't bother scrolling vertically when the scrolldir is only horizontal
    // and the other way around
    if (this._scrolldir != "EW") {
      var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
      var desiredScrollY = this._scrollErrorY + y;
      actualScrollY = this.roundToZero(desiredScrollY);
      this._scrollErrorY = (desiredScrollY - actualScrollY);
    }
    if (this._scrolldir != "NS") {
      var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
      var desiredScrollX = this._scrollErrorX + x;
      actualScrollX = this.roundToZero(desiredScrollX);
      this._scrollErrorX = (desiredScrollX - actualScrollX);
    }

    const kAutoscroll = 15;  // defined in mozilla/layers/ScrollInputMethods.h
    Services.telemetry.getHistogramById("SCROLL_INPUT_METHODS").add(kAutoscroll);

    this._scrollable.scrollBy({
      left: actualScrollX,
      top: actualScrollY,
      behavior: "instant"
    });

    content.requestAnimationFrame(this.autoscrollLoop);
  },

  handleEvent(event) {
    if (event.type == "mousemove") {
      this._screenX = event.screenX;
      this._screenY = event.screenY;
    } else if (event.type == "mousedown") {
      if (event.isTrusted &
          !event.defaultPrevented &&
          event.button == 1 &&
          !this._scrollable &&
          !this.isAutoscrollBlocker(event.originalTarget)) {
        this.startScroll(event);
      }
    } else if (event.type == "pagehide") {
      if (this._scrollable) {
        var doc =
          this._scrollable.ownerDocument || this._scrollable.document;
        if (doc == event.target) {
          sendAsyncMessage("Autoscroll:Cancel");
        }
      }
    }
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Autoscroll:Stop": {
        this.stopScroll();
        break;
      }
    }
  },

  observe(subject, topic, data) {
    if (topic === "autoscroll-handled-by-apz") {
      // The caller passes in the scroll id via 'data'.
      if (data == this._scrollId) {
        this._autoscrollHandledByApz = true;
      }
    }
  },
};
ClickEventHandler.init();

var PopupBlocking = {
  popupData: null,
  popupDataInternal: null,

  init() {
    addEventListener("DOMPopupBlocked", this, true);
    addEventListener("pageshow", this, true);
    addEventListener("pagehide", this, true);

    addMessageListener("PopupBlocking:UnblockPopup", this);
    addMessageListener("PopupBlocking:GetBlockedPopupList", this);
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "PopupBlocking:UnblockPopup": {
        let i = msg.data.index;
        if (this.popupData && this.popupData[i]) {
          let data = this.popupData[i];
          let internals = this.popupDataInternal[i];
          let dwi = internals.requestingWindow;

          // If we have a requesting window and the requesting document is
          // still the current document, open the popup.
          if (dwi && dwi.document == internals.requestingDocument) {
            dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures);
          }
        }
        break;
      }

      case "PopupBlocking:GetBlockedPopupList": {
        let popupData = [];
        let length = this.popupData ? this.popupData.length : 0;

        // Limit 15 popup URLs to be reported through the UI
        length = Math.min(length, 15);

        for (let i = 0; i < length; i++) {
          let popupWindowURIspec = this.popupData[i].popupWindowURIspec;

          if (popupWindowURIspec == global.content.location.href) {
            popupWindowURIspec = "<self>";
          } else {
            // Limit 500 chars to be sent because the URI will be cropped
            // by the UI anyway, and data: URIs can be significantly larger.
            popupWindowURIspec = popupWindowURIspec.substring(0, 500)
          }

          popupData.push({popupWindowURIspec});
        }

        sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {popupData});
        break;
      }
    }
  },

  handleEvent(ev) {
    switch (ev.type) {
      case "DOMPopupBlocked":
        return this.onPopupBlocked(ev);
      case "pageshow":
        return this._removeIrrelevantPopupData();
      case "pagehide":
        return this._removeIrrelevantPopupData(ev.target);
    }
    return undefined;
  },

  onPopupBlocked(ev) {
    if (!this.popupData) {
      this.popupData = [];
      this.popupDataInternal = [];
    }

    let obj = {
      popupWindowURIspec: ev.popupWindowURI ? ev.popupWindowURI.spec : "about:blank",
      popupWindowFeatures: ev.popupWindowFeatures,
      popupWindowName: ev.popupWindowName
    };

    let internals = {
      requestingWindow: ev.requestingWindow,
      requestingDocument: ev.requestingWindow.document,
    };

    this.popupData.push(obj);
    this.popupDataInternal.push(internals);
    this.updateBlockedPopups(true);
  },

  _removeIrrelevantPopupData(removedDoc = null) {
    if (this.popupData) {
      let i = 0;
      let oldLength = this.popupData.length;
      while (i < this.popupData.length) {
        let {requestingWindow, requestingDocument} = this.popupDataInternal[i];
        // Filter out irrelevant reports.
        if (requestingWindow && requestingWindow.document == requestingDocument &&
            requestingDocument != removedDoc) {
          i++;
        } else {
          this.popupData.splice(i, 1);
          this.popupDataInternal.splice(i, 1);
        }
      }
      if (this.popupData.length == 0) {
        this.popupData = null;
        this.popupDataInternal = null;
      }
      if (!this.popupData || oldLength > this.popupData.length) {
        this.updateBlockedPopups(false);
      }
    }
  },

  updateBlockedPopups(freshPopup) {
    sendAsyncMessage("PopupBlocking:UpdateBlockedPopups",
      {
        count: this.popupData ? this.popupData.length : 0,
        freshPopup
      });
  },
};
PopupBlocking.init();

XPCOMUtils.defineLazyGetter(this, "console", () => {
  // Set up console.* for frame scripts.
  let Console = Components.utils.import("resource://gre/modules/Console.jsm", {});
  return new Console.ConsoleAPI();
});

var Printing = {
  // Bug 1088061: nsPrintEngine's DoCommonPrint currently expects the
  // progress listener passed to it to QI to an nsIPrintingPromptService
  // in order to know that a printing progress dialog has been shown. That's
  // really all the interface is used for, hence the fact that I don't actually
  // implement the interface here. Bug 1088061 has been filed to remove
  // this hackery.
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsIPrintingPromptService]),

  MESSAGES: [
    "Printing:Preview:Enter",
    "Printing:Preview:Exit",
    "Printing:Preview:Navigate",
    "Printing:Preview:ParseDocument",
    "Printing:Print",
  ],

  init() {
    this.MESSAGES.forEach(msgName => addMessageListener(msgName, this));
    addEventListener("PrintingError", this, true);
    addEventListener("printPreviewUpdate", this, true);
  },

  get shouldSavePrintSettings() {
    return Services.prefs.getBoolPref("print.use_global_printsettings") &&
           Services.prefs.getBoolPref("print.save_print_settings");
  },

  printPreviewInitializingInfo: null,

  handleEvent(event) {
    switch (event.type) {
      case "PrintingError": {
        let win = event.target.defaultView;
        let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebBrowserPrint);
        let nsresult = event.detail;
        sendAsyncMessage("Printing:Error", {
          isPrinting: wbp.doingPrint,
          nsresult,
        });
        break;
      }

      case "printPreviewUpdate": {
        let info = this.printPreviewInitializingInfo;
        if (!info) {
          // If there is no printPreviewInitializingInfo then we did not
          // initiate the preview so ignore this event.
          return;
        }

        // Only send Printing:Preview:Entered message on first update, indicated
        // by printPreviewInitializingInfo.entered not being set.
        if (!info.entered) {
          info.entered = true;
          sendAsyncMessage("Printing:Preview:Entered", {
            failed: false,
            changingBrowsers: info.changingBrowsers
          });

          // If we have another request waiting, dispatch it now.
          if (info.nextRequest) {
            Services.tm.dispatchToMainThread(info.nextRequest);
          }
        }

        // Always send page count update.
        this.updatePageCount();
        break;
      }
    }
  },

  receiveMessage(message) {
    let data = message.data;
    switch (message.name) {
      case "Printing:Preview:Enter": {
        this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.changingBrowsers, data.defaultPrinterName);
        break;
      }

      case "Printing:Preview:Exit": {
        this.exitPrintPreview();
        break;
      }

      case "Printing:Preview:Navigate": {
        this.navigate(data.navType, data.pageNum);
        break;
      }

      case "Printing:Preview:ParseDocument": {
        this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID));
        break;
      }

      case "Printing:Print": {
        this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
        break;
      }
    }
  },

  getPrintSettings(defaultPrinterName) {
    try {
      let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
                    .getService(Ci.nsIPrintSettingsService);

      let printSettings = PSSVC.globalPrintSettings;
      if (!printSettings.printerName) {
        printSettings.printerName = defaultPrinterName;
      }
      // First get any defaults from the printer
      PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
                                         printSettings);
      // now augment them with any values from last time
      PSSVC.initPrintSettingsFromPrefs(printSettings, true,
                                       printSettings.kInitSaveAll);

      return printSettings;
    } catch (e) {
      Components.utils.reportError(e);
    }

    return null;
  },

  parseDocument(URL, contentWindow) {
    // By using ReaderMode primitives, we parse given document and place the
    // resulting JS object into the DOM of current browser.
    let articlePromise = ReaderMode.parseDocument(contentWindow.document).catch(Cu.reportError);
    articlePromise.then(function(article) {
      // We make use of a web progress listener in order to know when the content we inject
      // into the DOM has finished rendering. If our layout engine is still painting, we
      // will wait for MozAfterPaint event to be fired.
      let webProgressListener = {
        onStateChange(webProgress, req, flags, status) {
          if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
            webProgress.removeProgressListener(webProgressListener);
            let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDOMWindowUtils);
            // Here we tell the parent that we have parsed the document successfully
            // using ReaderMode primitives and we are able to enter on preview mode.
            if (domUtils.isMozAfterPaintPending) {
              addEventListener("MozAfterPaint", function onPaint() {
                removeEventListener("MozAfterPaint", onPaint);
                sendAsyncMessage("Printing:Preview:ReaderModeReady");
              });
            } else {
              sendAsyncMessage("Printing:Preview:ReaderModeReady");
            }
          }
        },

        QueryInterface: XPCOMUtils.generateQI([
          Ci.nsIWebProgressListener,
          Ci.nsISupportsWeakReference,
          Ci.nsIObserver,
        ]),
      };

      // Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
      let webProgress =  docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIWebProgress);
      webProgress.addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);

      content.document.head.innerHTML = "";

      // Set title of document
      content.document.title = article.title;

      // Set base URI of document. Print preview code will read this value to
      // populate the URL field in print settings so that it doesn't show
      // "about:blank" as its URI.
      let headBaseElement = content.document.createElement("base");
      headBaseElement.setAttribute("href", URL);
      content.document.head.appendChild(headBaseElement);

      // Create link element referencing aboutReader.css and append it to head
      let headStyleElement = content.document.createElement("link");
      headStyleElement.setAttribute("rel", "stylesheet");
      headStyleElement.setAttribute("href", "chrome://global/skin/aboutReader.css");
      headStyleElement.setAttribute("type", "text/css");
      content.document.head.appendChild(headStyleElement);

      // Create link element referencing simplifyMode.css and append it to head
      headStyleElement = content.document.createElement("link");
      headStyleElement.setAttribute("rel", "stylesheet");
      headStyleElement.setAttribute("href", "chrome://global/content/simplifyMode.css");
      headStyleElement.setAttribute("type", "text/css");
      content.document.head.appendChild(headStyleElement);

      content.document.body.innerHTML = "";

      // Create container div (main element) and append it to body
      let containerElement = content.document.createElement("div");
      containerElement.setAttribute("id", "container");
      content.document.body.appendChild(containerElement);

      // Create header div and append it to container
      let headerElement = content.document.createElement("div");
      headerElement.setAttribute("id", "reader-header");
      headerElement.setAttribute("class", "header");
      containerElement.appendChild(headerElement);

      // Jam the article's title and byline into header div
      let titleElement = content.document.createElement("h1");
      titleElement.setAttribute("id", "reader-title");
      titleElement.textContent = article.title;
      headerElement.appendChild(titleElement);

      let bylineElement = content.document.createElement("div");
      bylineElement.setAttribute("id", "reader-credits");
      bylineElement.setAttribute("class", "credits");
      bylineElement.textContent = article.byline;
      headerElement.appendChild(bylineElement);

      // Display header element
      headerElement.style.display = "block";

      // Create content div and append it to container
      let contentElement = content.document.createElement("div");
      contentElement.setAttribute("class", "content");
      containerElement.appendChild(contentElement);

      // Create style element for content div and import aboutReaderContent.css
      let controlContentStyle = content.document.createElement("style");
      controlContentStyle.setAttribute("scoped", "");
      controlContentStyle.textContent = "@import url(\"chrome://global/skin/aboutReaderContent.css\");";
      contentElement.appendChild(controlContentStyle);

      // Jam the article's content into content div
      let readerContent = content.document.createElement("div");
      readerContent.setAttribute("id", "moz-reader-content");
      contentElement.appendChild(readerContent);

      let articleUri = Services.io.newURI(article.url);
      let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
      let contentFragment = parserUtils.parseFragment(article.content,
        Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
        false, articleUri, readerContent);

      readerContent.appendChild(contentFragment);

      // Display reader content element
      readerContent.style.display = "block";
    });
  },

  enterPrintPreview(contentWindow, simplifiedMode, changingBrowsers, defaultPrinterName) {
    try {
      let printSettings = this.getPrintSettings(defaultPrinterName);

      // If we happen to be on simplified mode, we need to set docURL in order
      // to generate header/footer content correctly, since simplified tab has
      // "about:blank" as its URI.
      if (printSettings && simplifiedMode)
        printSettings.docURL = contentWindow.document.baseURI;

      // The print preview docshell will be in a different TabGroup, so
      // printPreviewInitialize must be run in a separate runnable to avoid
      // touching a different TabGroup in our own runnable.
      let printPreviewInitialize = () => {
        try {
          this.printPreviewInitializingInfo = { changingBrowsers };
          docShell.printPreview.printPreview(printSettings, contentWindow, this);
        } catch (error) {
          // This might fail if we, for example, attempt to print a XUL document.
          // In that case, we inform the parent to bail out of print preview.
          Components.utils.reportError(error);
          this.printPreviewInitializingInfo = null;
          sendAsyncMessage("Printing:Preview:Entered", { failed: true });
        }
      }

      // If printPreviewInitializingInfo.entered is not set we are still in the
      // initial setup of a previous preview request. We delay this one until
      // that has finished because running them at the same time will almost
      // certainly cause failures.
      if (this.printPreviewInitializingInfo &&
          !this.printPreviewInitializingInfo.entered) {
        this.printPreviewInitializingInfo.nextRequest = printPreviewInitialize;
      } else {
        Services.tm.dispatchToMainThread(printPreviewInitialize);
      }
    } catch (error) {
      // This might fail if we, for example, attempt to print a XUL document.
      // In that case, we inform the parent to bail out of print preview.
      Components.utils.reportError(error);
      sendAsyncMessage("Printing:Preview:Entered", { failed: true });
    }
  },

  exitPrintPreview() {
    this.printPreviewInitializingInfo = null;
    docShell.printPreview.exitPrintPreview();
  },

  print(contentWindow, simplifiedMode, defaultPrinterName) {
    let printSettings = this.getPrintSettings(defaultPrinterName);

    // If we happen to be on simplified mode, we need to set docURL in order
    // to generate header/footer content correctly, since simplified tab has
    // "about:blank" as its URI.
    if (printSettings && simplifiedMode) {
      printSettings.docURL = contentWindow.document.baseURI;
    }

    try {
      let print = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebBrowserPrint);

      if (print.doingPrintPreview) {
        this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW");
      } else {
        this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE");
      }

      print.print(printSettings, null);

      if (print.doingPrintPreview) {
        if (simplifiedMode) {
          this.logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED");
        } else {
          this.logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW");
        }
      } else {
        this.logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW");
      }
    } catch (e) {
      // Pressing cancel is expressed as an NS_ERROR_ABORT return value,
      // causing an exception to be thrown which we catch here.
      if (e.result != Cr.NS_ERROR_ABORT) {
        Cu.reportError(`In Printing:Print:Done handler, got unexpected rv
                        ${e.result}.`);
        sendAsyncMessage("Printing:Error", {
          isPrinting: true,
          nsresult: e.result,
        });
      }
    }

    if (this.shouldSavePrintSettings) {
      let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
                    .getService(Ci.nsIPrintSettingsService);

      PSSVC.savePrintSettingsToPrefs(printSettings, true,
                                     printSettings.kInitSaveAll);
      PSSVC.savePrintSettingsToPrefs(printSettings, false,
                                     printSettings.kInitSavePrinterName);
    }
  },

  logKeyedTelemetry(id, key) {
    let histogram = Services.telemetry.getKeyedHistogramById(id);
    histogram.add(key);
  },

  updatePageCount() {
    let numPages = docShell.printPreview.printPreviewNumPages;
    sendAsyncMessage("Printing:Preview:UpdatePageCount", {
      numPages,
    });
  },

  navigate(navType, pageNum) {
    docShell.printPreview.printPreviewNavigate(navType, pageNum);
  },

  /* nsIWebProgressListener for print preview */

  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    sendAsyncMessage("Printing:Preview:StateChange", {
      stateFlags: aStateFlags,
      status: aStatus,
    });
  },

  onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
                   aMaxSelfProgress, aCurTotalProgress,
                   aMaxTotalProgress) {
    sendAsyncMessage("Printing:Preview:ProgressChange", {
      curSelfProgress: aCurSelfProgress,
      maxSelfProgress: aMaxSelfProgress,
      curTotalProgress: aCurTotalProgress,
      maxTotalProgress: aMaxTotalProgress,
    });
  },

  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
  onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
  onSecurityChange(aWebProgress, aRequest, aState) {},
}
Printing.init();

function SwitchDocumentDirection(aWindow) {
 // document.dir can also be "auto", in which case it won't change
  if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
    aWindow.document.dir = "rtl";
  } else if (aWindow.document.dir == "rtl") {
    aWindow.document.dir = "ltr";
  }
  for (let run = 0; run < aWindow.frames.length; run++) {
    SwitchDocumentDirection(aWindow.frames[run]);
  }
}

addMessageListener("SwitchDocumentDirection", () => {
  SwitchDocumentDirection(content.window);
});

var FindBar = {
  /* Please keep in sync with toolkit/content/widgets/findbar.xml */
  FIND_NORMAL: 0,
  FIND_TYPEAHEAD: 1,
  FIND_LINKS: 2,

  _findMode: 0,

  init() {
    addMessageListener("Findbar:UpdateState", this);
    Services.els.addSystemEventListener(global, "keypress", this, false);
    Services.els.addSystemEventListener(global, "mouseup", this, false);
  },

  receiveMessage(msg) {
    switch (msg.name) {
      case "Findbar:UpdateState":
        this._findMode = msg.data.findMode;
        break;
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "keypress":
        this._onKeypress(event);
        break;
      case "mouseup":
        this._onMouseup(event);
        break;
    }
  },

  /**
   * Returns whether FAYT can be used for the given event in
   * the current content state.
   */
  _canAndShouldFastFind() {
    let should = false;
    let can = BrowserUtils.canFastFind(content);
    if (can) {
      // XXXgijs: why all these shenanigans? Why not use the event's target?
      let focusedWindow = {};
      let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow);
      let win = focusedWindow.value;
      should = BrowserUtils.shouldFastFind(elt, win);
    }
    return { can, should }
  },

  _onKeypress(event) {
    // Useless keys:
    if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) {
      return undefined;
    }

    // Check the focused element etc.
    let fastFind = this._canAndShouldFastFind();

    // Can we even use find in this page at all?
    if (!fastFind.can) {
      return undefined;
    }

    let fakeEvent = {};
    for (let k in event) {
      if (typeof event[k] != "object" && typeof event[k] != "function" &&
          !(k in content.KeyboardEvent)) {
        fakeEvent[k] = event[k];
      }
    }
    // sendSyncMessage returns an array of the responses from all listeners
    let rv = sendSyncMessage("Findbar:Keypress", {
      fakeEvent,
      shouldFastFind: fastFind.should
    });
    if (rv.indexOf(false) !== -1) {
      event.preventDefault();
      return false;
    }
    return undefined;
  },

  _onMouseup(event) {
    if (this._findMode != this.FIND_NORMAL)
      sendAsyncMessage("Findbar:Mouseup");
  },
};
FindBar.init();

let WebChannelMessageToChromeListener = {
  // Preference containing the list (space separated) of origins that are
  // allowed to send non-string values through a WebChannel, mainly for
  // backwards compatability. See bug 1238128 for more information.
  URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",

  // Cached list of whitelisted principals, we avoid constructing this if the
  // value in `_lastWhitelistValue` hasn't changed since we constructed it last.
  _cachedWhitelist: [],
  _lastWhitelistValue: "",

  init() {
    addEventListener("WebChannelMessageToChrome", e => {
      this._onMessageToChrome(e);
    }, true, true);
  },

  _getWhitelistedPrincipals() {
    let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
    if (whitelist != this._lastWhitelistValue) {
      let urls = whitelist.split(/\s+/);
      this._cachedWhitelist = urls.map(origin =>
        Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
    }
    return this._cachedWhitelist;
  },

  _onMessageToChrome(e) {
    // If target is window then we want the document principal, otherwise fallback to target itself.
    let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;

    if (e.detail) {
      if (typeof e.detail != "string") {
        // Check if the principal is one of the ones that's allowed to send
        // non-string values for e.detail.  They're whitelisted by site origin,
        // so we compare on originNoSuffix in order to avoid other origin attributes
        // that are not relevant here, such as containers or private browsing.
        let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
          principal.originNoSuffix == whitelisted.originNoSuffix);
        if (!objectsAllowed) {
          Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
          return;
        }
      }
      sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
    } else {
      Cu.reportError("WebChannel message failed. No message detail.");
    }
  }
};

WebChannelMessageToChromeListener.init();

// This should be kept in sync with /browser/base/content.js.
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
addMessageListener("WebChannelMessageToContent", function(e) {
  if (e.data) {
    // e.objects.eventTarget will be defined if sending a response to
    // a WebChannelMessageToChrome event. An unsolicited send
    // may not have an eventTarget defined, in this case send to the
    // main content window.
    let eventTarget = e.objects.eventTarget || content;

    // Use nodePrincipal if available, otherwise fallback to document principal.
    let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;

    if (e.principal.subsumes(targetPrincipal)) {
      // If eventTarget is a window, use it as the targetWindow, otherwise
      // find the window that owns the eventTarget.
      let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerGlobal;

      eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
        detail: Cu.cloneInto({
          id: e.data.id,
          message: e.data.message,
        }, targetWindow),
      }));
    } else {
      Cu.reportError("WebChannel message failed. Principal mismatch.");
    }
  } else {
    Cu.reportError("WebChannel message failed. No message data.");
  }
});

var AudioPlaybackListener = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),

  init() {
    Services.obs.addObserver(this, "audio-playback");

    addMessageListener("AudioPlayback", this);
    addEventListener("unload", () => {
      AudioPlaybackListener.uninit();
    });
  },

  uninit() {
    Services.obs.removeObserver(this, "audio-playback");

    removeMessageListener("AudioPlayback", this);
  },

  handleMediaControlMessage(msg) {
    let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
    let suspendTypes = Ci.nsISuspendedTypes;
    switch (msg) {
      case "mute":
        utils.audioMuted = true;
        break;
      case "unmute":
        utils.audioMuted = false;
        break;
      case "lostAudioFocus":
        utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
        break;
      case "lostAudioFocusTransiently":
        utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE;
        break;
      case "gainAudioFocus":
        utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
        break;
      case "mediaControlPaused":
        utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
        break;
      case "mediaControlStopped":
        utils.mediaSuspend = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
        break;
      case "resumeMedia":
        utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
        break;
      default:
        dump("Error : wrong media control msg!\n");
        break;
    }
  },

  observe(subject, topic, data) {
    if (topic === "audio-playback") {
      if (subject && subject.top == global.content) {
        let name = "AudioPlayback:";
        if (data === "activeMediaBlockStart") {
          name += "ActiveMediaBlockStart";
        } else if (data === "activeMediaBlockStop") {
          name += "ActiveMediaBlockStop";
        } else if (data == "mediaBlockStop") {
          name += "MediaBlockStop";
        } else {
          name += (data === "active") ? "Start" : "Stop";
        }
        sendAsyncMessage(name);
      }
    }
  },

  receiveMessage(msg) {
    if (msg.name == "AudioPlayback") {
      this.handleMediaControlMessage(msg.data.type);
    }
  },
};
AudioPlaybackListener.init();

var UnselectedTabHoverObserver = {
  init() {
    addMessageListener("Browser:UnselectedTabHover", this);
    addEventListener("UnselectedTabHover:Enable", this);
    addEventListener("UnselectedTabHover:Disable", this);
  },
  receiveMessage(message) {
    Services.obs.notifyObservers(content.window, "unselected-tab-hover",
                                 message.data.hovered);
  },
  handleEvent(event) {
    sendAsyncMessage("UnselectedTabHover:Toggle",
                     { enable: event.type == "UnselectedTabHover:Enable" });
  }
};
UnselectedTabHoverObserver.init();

addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() {
  let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
  if (!sessionHistory) {
    return;
  }

  // place the entry at current index at the end of the history list, so it won't get removed
  if (sessionHistory.index < sessionHistory.count - 1) {
    let indexEntry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
    sessionHistory.QueryInterface(Components.interfaces.nsISHistoryInternal);
    indexEntry.QueryInterface(Components.interfaces.nsISHEntry);
    sessionHistory.addEntry(indexEntry, true);
  }

  let purge = sessionHistory.count;
  if (global.content.location.href != "about:blank") {
    --purge; // Don't remove the page the user's staring at from shistory
  }

  if (purge > 0) {
    sessionHistory.PurgeHistory(purge);
  }
});

var ViewSelectionSource = {
  init() {
    addMessageListener("ViewSource:GetSelection", this);
  },

  receiveMessage(message) {
    if (message.name == "ViewSource:GetSelection") {
      let selectionDetails;
      try {
        selectionDetails = message.objects.target ? this.getMathMLSelection(message.objects.target)
                                                  : this.getSelection();
      } finally {
        sendAsyncMessage("ViewSource:GetSelectionDone", selectionDetails);
      }
    }
  },

  /**
   * A helper to get a path like FIXptr, but with an array instead of the
   * "tumbler" notation.
   * See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
   */
  getPath(ancestor, node) {
    var n = node;
    var p = n.parentNode;
    if (n == ancestor || !p)
      return null;
    var path = [];
    if (!path)
      return null;
    do {
      for (var i = 0; i < p.childNodes.length; i++) {
        if (p.childNodes.item(i) == n) {
          path.push(i);
          break;
        }
      }
      n = p;
      p = n.parentNode;
    } while (n != ancestor && p);
    return path;
  },

  getSelection() {
    // These are markers used to delimit the selection during processing. They
    // are removed from the final rendering.
    // We use noncharacter Unicode codepoints to minimize the risk of clashing
    // with anything that might legitimately be present in the document.
    // U+FDD0..FDEF <noncharacters>
    const MARK_SELECTION_START = "\uFDD0";
    const MARK_SELECTION_END = "\uFDEF";

    var focusedWindow = Services.focus.focusedWindow || content;
    var selection = focusedWindow.getSelection();

    var range = selection.getRangeAt(0);
    var ancestorContainer = range.commonAncestorContainer;
    var doc = ancestorContainer.ownerDocument;

    var startContainer = range.startContainer;
    var endContainer = range.endContainer;
    var startOffset = range.startOffset;
    var endOffset = range.endOffset;

    // let the ancestor be an element
    var Node = doc.defaultView.Node;
    if (ancestorContainer.nodeType == Node.TEXT_NODE ||
        ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
      ancestorContainer = ancestorContainer.parentNode;

    // for selectAll, let's use the entire document, including <html>...</html>
    // @see nsDocumentViewer::SelectAll() for how selectAll is implemented
    try {
      if (ancestorContainer == doc.body)
        ancestorContainer = doc.documentElement;
    } catch (e) { }

    // each path is a "child sequence" (a.k.a. "tumbler") that
    // descends from the ancestor down to the boundary point
    var startPath = this.getPath(ancestorContainer, startContainer);
    var endPath = this.getPath(ancestorContainer, endContainer);

    // clone the fragment of interest and reset everything to be relative to it
    // note: it is with the clone that we operate/munge from now on.  Also note
    // that we clone into a data document to prevent images in the fragment from
    // loading and the like.  The use of importNode here, as opposed to adoptNode,
    // is _very_ important.
    // XXXbz wish there were a less hacky way to create an untrusted document here
    var isHTML = (doc.createElement("div").tagName == "DIV");
    var dataDoc = isHTML ?
      ancestorContainer.ownerDocument.implementation.createHTMLDocument("") :
      ancestorContainer.ownerDocument.implementation.createDocument("", "", null);
    ancestorContainer = dataDoc.importNode(ancestorContainer, true);
    startContainer = ancestorContainer;
    endContainer = ancestorContainer;

    // Only bother with the selection if it can be remapped. Don't mess with
    // leaf elements (such as <isindex>) that secretly use anynomous content
    // for their display appearance.
    var canDrawSelection = ancestorContainer.hasChildNodes();
    var tmpNode;
    if (canDrawSelection) {
      var i;
      for (i = startPath ? startPath.length - 1 : -1; i >= 0; i--) {
        startContainer = startContainer.childNodes.item(startPath[i]);
      }
      for (i = endPath ? endPath.length - 1 : -1; i >= 0; i--) {
        endContainer = endContainer.childNodes.item(endPath[i]);
      }

      // add special markers to record the extent of the selection
      // note: |startOffset| and |endOffset| are interpreted either as
      // offsets in the text data or as child indices (see the Range spec)
      // (here, munging the end point first to keep the start point safe...)
      if (endContainer.nodeType == Node.TEXT_NODE ||
          endContainer.nodeType == Node.CDATA_SECTION_NODE) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
        // To get a neat output, the idea here is to remap the end point from:
        // 1. ...<tag>]...   to   ...]<tag>...
        // 2. ...]</tag>...  to   ...</tag>]...
        if ((endOffset > 0 && endOffset < endContainer.data.length) ||
            !endContainer.parentNode || !endContainer.parentNode.parentNode)
          endContainer.insertData(endOffset, MARK_SELECTION_END);
        else {
          tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
          endContainer = endContainer.parentNode;
          if (endOffset === 0)
            endContainer.parentNode.insertBefore(tmpNode, endContainer);
          else
            endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
        }
      } else {
        tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
        endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
      }

      if (startContainer.nodeType == Node.TEXT_NODE ||
          startContainer.nodeType == Node.CDATA_SECTION_NODE) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
        // To get a neat output, the idea here is to remap the start point from:
        // 1. ...<tag>[...   to   ...[<tag>...
        // 2. ...[</tag>...  to   ...</tag>[...
        if ((startOffset > 0 && startOffset < startContainer.data.length) ||
            !startContainer.parentNode || !startContainer.parentNode.parentNode ||
            startContainer != startContainer.parentNode.lastChild)
          startContainer.insertData(startOffset, MARK_SELECTION_START);
        else {
          tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
          startContainer = startContainer.parentNode;
          if (startOffset === 0)
            startContainer.parentNode.insertBefore(tmpNode, startContainer);
          else
            startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
        }
      } else {
        tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
        startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
      }
    }

    // now extract and display the syntax highlighted source
    tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
    tmpNode.appendChild(ancestorContainer);

    return { uri: (isHTML ? "view-source:data:text/html;charset=utf-8," :
                            "view-source:data:application/xml;charset=utf-8,")
                  + encodeURIComponent(tmpNode.innerHTML),
             drawSelection: canDrawSelection,
             baseURI: doc.baseURI };
  },

  /**
   * Reformat the source of a MathML node to highlight the node that was targetted.
   *
   * @param node
   *        Some element within the fragment of interest.
   */
  getMathMLSelection(node) {
    var Node = node.ownerGlobal.Node;
    this._lineCount = 0;
    this._startTargetLine = 0;
    this._endTargetLine = 0;
    this._targetNode = node;
    if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE)
      this._targetNode = this._targetNode.parentNode;

    // walk up the tree to the top-level element (e.g., <math>, <svg>)
    var topTag = "math";
    var topNode = this._targetNode;
    while (topNode && topNode.localName != topTag) {
      topNode = topNode.parentNode;
    }
    if (!topNode)
      return undefined;

    // serialize
    const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css";
    const BUNDLE_URL = "chrome://global/locale/viewSource.properties";

    let bundle = Services.strings.createBundle(BUNDLE_URL);
    var title = bundle.GetStringFromName("viewMathMLSourceTitle");
    var wrapClass = this.wrapLongLines ? ' class="wrap"' : "";
    var source =
      "<!DOCTYPE html>"
    + "<html>"
    + "<head><title>" + title + "</title>"
    + '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">'
    + '<style type="text/css">'
    + "#target { border: dashed 1px; background-color: lightyellow; }"
    + "</style>"
    + "</head>"
    + '<body id="viewsource"' + wrapClass
    + ' onload="document.title=\'' + title + '\'; document.getElementById(\'target\').scrollIntoView(true)">'
    + "<pre>"
    + this.getOuterMarkup(topNode, 0)
    + "</pre></body></html>"
    ; // end

    return { uri: "data:text/html;charset=utf-8," + encodeURIComponent(source),
             drawSelection: false, baseURI: node.ownerDocument.baseURI };
  },

  get wrapLongLines() {
    return Services.prefs.getBoolPref("view_source.wrap_long_lines");
  },

  getInnerMarkup(node, indent) {
    var str = "";
    for (var i = 0; i < node.childNodes.length; i++) {
      str += this.getOuterMarkup(node.childNodes.item(i), indent);
    }
    return str;
  },

  getOuterMarkup(node, indent) {
    var Node = node.ownerGlobal.Node;
    var newline = "";
    var padding = "";
    var str = "";
    if (node == this._targetNode) {
      this._startTargetLine = this._lineCount;
      str += '</pre><pre id="target">';
    }

    switch (node.nodeType) {
    case Node.ELEMENT_NODE: // Element
      // to avoid the wide gap problem, '\n' is not emitted on the first
      // line and the lines before & after the <pre id="target">...</pre>
      if (this._lineCount > 0 &&
          this._lineCount != this._startTargetLine &&
          this._lineCount != this._endTargetLine) {
        newline = "\n";
      }
      this._lineCount++;
      for (var k = 0; k < indent; k++) {
        padding += " ";
      }
      str += newline + padding
          + '&lt;<span class="start-tag">' + node.nodeName + "</span>";
      for (var i = 0; i < node.attributes.length; i++) {
        var attr = node.attributes.item(i);
        if (attr.nodeName.match(/^[-_]moz/)) {
          continue;
        }
        str += ' <span class="attribute-name">'
            + attr.nodeName
            + '</span>=<span class="attribute-value">"'
            + this.unicodeToEntity(attr.nodeValue)
            + '"</span>';
      }
      if (!node.hasChildNodes()) {
        str += "/&gt;";
      } else {
        str += "&gt;";
        var oldLine = this._lineCount;
        str += this.getInnerMarkup(node, indent + 2);
        if (oldLine == this._lineCount) {
          newline = "";
          padding = "";
        } else {
          newline = (this._lineCount == this._endTargetLine) ? "" : "\n";
          this._lineCount++;
        }
        str += newline + padding
            + '&lt;/<span class="end-tag">' + node.nodeName + "</span>&gt;";
      }
      break;
    case Node.TEXT_NODE: // Text
      var tmp = node.nodeValue;
      tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
      tmp = tmp.replace(/^ +/, "");
      tmp = tmp.replace(/ +$/, "");
      if (tmp.length != 0) {
        str += '<span class="text">' + this.unicodeToEntity(tmp) + "</span>";
      }
      break;
    default:
      break;
    }

    if (node == this._targetNode) {
      this._endTargetLine = this._lineCount;
      str += "</pre><pre>";
    }
    return str;
  },

  unicodeToEntity(text) {
    const charTable = {
      "&": '&amp;<span class="entity">amp;</span>',
      "<": '&amp;<span class="entity">lt;</span>',
      ">": '&amp;<span class="entity">gt;</span>',
      '"': '&amp;<span class="entity">quot;</span>'
    };

    function charTableLookup(letter) {
      return charTable[letter];
    }

    function convertEntity(letter) {
      try {
        var unichar = this._entityConverter
                          .ConvertToEntity(letter, entityVersion);
        var entity = unichar.substring(1); // extract '&'
        return '&amp;<span class="entity">' + entity + "</span>";
      } catch (ex) {
        return letter;
      }
    }

    if (!this._entityConverter) {
      try {
        this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"]
                                  .createInstance(Ci.nsIEntityConverter);
      } catch (e) { }
    }

    const entityVersion = Ci.nsIEntityConverter.entityW3C;

    var str = text;

    // replace chars in our charTable
    str = str.replace(/[<>&"]/g, charTableLookup);

    // replace chars > 0x7f via nsIEntityConverter
    str = str.replace(/[^\0-\u007f]/g, convertEntity);

    return str;
  }
};

ViewSelectionSource.init();

addEventListener("MozApplicationManifest", function(e) {
  let doc = e.target;
  let info = {
    uri: doc.documentURI,
    characterSet: doc.characterSet,
    manifest: doc.documentElement.getAttribute("manifest"),
    principal: doc.nodePrincipal,
  };
  sendAsyncMessage("MozApplicationManifest", info);
}, false);

let AutoCompletePopup = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),

  _connected: false,

  MESSAGES: [
    "FormAutoComplete:HandleEnter",
    "FormAutoComplete:PopupClosed",
    "FormAutoComplete:PopupOpened",
    "FormAutoComplete:RequestFocus",
  ],

  init() {
    addEventListener("unload", this);
    addEventListener("DOMContentLoaded", this);
    // WebExtension browserAction is preloaded and does not receive DCL, wait
    // on pageshow so we can hookup the formfill controller.
    addEventListener("pageshow", this, true);

    for (let messageName of this.MESSAGES) {
      addMessageListener(messageName, this);
    }

    this._input = null;
    this._popupOpen = false;
  },

  destroy() {
    if (this._connected) {
      let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                         .getService(Ci.nsIFormFillController);
      controller.detachFromBrowser(docShell);
      this._connected = false;
    }

    removeEventListener("pageshow", this);
    removeEventListener("unload", this);
    removeEventListener("DOMContentLoaded", this);

    for (let messageName of this.MESSAGES) {
      removeMessageListener(messageName, this);
    }
  },

  connect() {
    if (this._connected) {
      return;
    }
    // We need to wait for a content viewer to be available
    // before we can attach our AutoCompletePopup handler,
    // since nsFormFillController assumes one will exist
    // when we call attachToBrowser.

    // Hook up the form fill autocomplete controller.
    let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                       .getService(Ci.nsIFormFillController);
    controller.attachToBrowser(docShell,
                               this.QueryInterface(Ci.nsIAutoCompletePopup));
    this._connected = true;
  },

  handleEvent(event) {
    switch (event.type) {
      case "pageshow": {
        removeEventListener("pageshow", this);
        this.connect();
        break;
      }

      case "DOMContentLoaded": {
        removeEventListener("DOMContentLoaded", this);
        this.connect();
        break;
      }

      case "unload": {
        this.destroy();
        break;
      }
    }
  },

  receiveMessage(message) {
    switch (message.name) {
      case "FormAutoComplete:HandleEnter": {
        this.selectedIndex = message.data.selectedIndex;

        let controller = Cc["@mozilla.org/autocomplete/controller;1"]
                           .getService(Ci.nsIAutoCompleteController);
        controller.handleEnter(message.data.isPopupSelection);
        break;
      }

      case "FormAutoComplete:PopupClosed": {
        this._popupOpen = false;
        break;
      }

      case "FormAutoComplete:PopupOpened": {
        this._popupOpen = true;
        break;
      }

      case "FormAutoComplete:RequestFocus": {
        if (this._input) {
          this._input.focus();
        }
        break;
      }
    }
  },

  get input() { return this._input; },
  get overrideValue() { return null; },
  set selectedIndex(index) {
    sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
  },
  get selectedIndex() {
    // selectedIndex getter must be synchronous because we need the
    // correct value when the controller is in controller::HandleEnter.
    // We can't easily just let the parent inform us the new value every
    // time it changes because not every action that can change the
    // selectedIndex is trivial to catch (e.g. moving the mouse over the
    // list).
    return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
  },
  get popupOpen() {
    return this._popupOpen;
  },

  openAutocompletePopup(input, element) {
    if (this._popupOpen || !input) {
      return;
    }

    let rect = BrowserUtils.getElementBoundingScreenRect(element);
    let window = element.ownerGlobal;
    let dir = window.getComputedStyle(element).direction;
    let results = this.getResultsFromController(input);

    sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
                     { results, rect, dir });
    this._input = input;
  },

  closePopup() {
    // We set this here instead of just waiting for the
    // PopupClosed message to do it so that we don't end
    // up in a state where the content thinks that a popup
    // is open when it isn't (or soon won't be).
    this._popupOpen = false;
    sendAsyncMessage("FormAutoComplete:ClosePopup", {});
  },

  invalidate() {
    if (this._popupOpen) {
      let results = this.getResultsFromController(this._input);
      sendAsyncMessage("FormAutoComplete:Invalidate", { results });
    }
  },

  selectBy(reverse, page) {
    this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
      reverse,
      page
    });
  },

  getResultsFromController(inputField) {
    let results = [];

    if (!inputField) {
      return results;
    }

    let controller = inputField.controller;
    if (!(controller instanceof Ci.nsIAutoCompleteController)) {
      return results;
    }

    for (let i = 0; i < controller.matchCount; ++i) {
      let result = {};
      result.value = controller.getValueAt(i);
      result.label = controller.getLabelAt(i);
      result.comment = controller.getCommentAt(i);
      result.style = controller.getStyleAt(i);
      result.image = controller.getImageAt(i);
      results.push(result);
    }

    return results;
  },
}

AutoCompletePopup.init();

/**
 * DateTimePickerListener is the communication channel between the input box
 * (content) for date/time input types and its picker (chrome).
 */
let DateTimePickerListener = {
  /**
   * On init, just listen for the event to open the picker, once the picker is
   * opened, we'll listen for update and close events.
   */
  init() {
    addEventListener("MozOpenDateTimePicker", this);
    this._inputElement = null;

    addEventListener("unload", () => {
      this.uninit();
    });
  },

  uninit() {
    removeEventListener("MozOpenDateTimePicker", this);
    this._inputElement = null;
  },

  /**
   * Cleanup function called when picker is closed.
   */
  close() {
    this.removeListeners();
    this._inputElement.setDateTimePickerState(false);
    this._inputElement = null;
  },

  /**
   * Called after picker is opened to start listening for input box update
   * events.
   */
  addListeners() {
    addEventListener("MozUpdateDateTimePicker", this);
    addEventListener("MozCloseDateTimePicker", this);
    addEventListener("pagehide", this);

    addMessageListener("FormDateTime:PickerValueChanged", this);
    addMessageListener("FormDateTime:PickerClosed", this);
  },

  /**
   * Stop listeneing for events when picker is closed.
   */
  removeListeners() {
    removeEventListener("MozUpdateDateTimePicker", this);
    removeEventListener("MozCloseDateTimePicker", this);
    removeEventListener("pagehide", this);

    removeMessageListener("FormDateTime:PickerValueChanged", this);
    removeMessageListener("FormDateTime:PickerClosed", this);
  },

  /**
   * Helper function that returns the CSS direction property of the element.
   */
  getComputedDirection(aElement) {
    return aElement.ownerGlobal.getComputedStyle(aElement)
      .getPropertyValue("direction");
  },

  /**
   * Helper function that returns the rect of the element, which is the position
   * relative to the left/top of the content area.
   */
  getBoundingContentRect(aElement) {
    return BrowserUtils.getElementBoundingRect(aElement);
  },

  getTimePickerPref() {
    return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
  },

  /**
   * nsIMessageListener.
   */
  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "FormDateTime:PickerClosed": {
        this.close();
        break;
      }
      case "FormDateTime:PickerValueChanged": {
        this._inputElement.updateDateTimeInputBox(aMessage.data);
        break;
      }
      default:
        break;
    }
  },

  /**
   * nsIDOMEventListener, for chrome events sent by the input element and other
   * DOM events.
   */
  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "MozOpenDateTimePicker": {
        // Time picker is disabled when preffed off
        if (!(aEvent.originalTarget instanceof content.HTMLInputElement) ||
            (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())) {
          return;
        }
        this._inputElement = aEvent.originalTarget;
        this._inputElement.setDateTimePickerState(true);
        this.addListeners();

        let value = this._inputElement.getDateTimeInputBoxValue();
        sendAsyncMessage("FormDateTime:OpenPicker", {
          rect: this.getBoundingContentRect(this._inputElement),
          dir: this.getComputedDirection(this._inputElement),
          type: this._inputElement.type,
          detail: {
            // Pass partial value if it's available, otherwise pass input
            // element's value.
            value: Object.keys(value).length > 0 ? value
                                                 : this._inputElement.value,
            min: this._inputElement.getMinimum(),
            max: this._inputElement.getMaximum(),
            step: this._inputElement.getStep(),
            stepBase: this._inputElement.getStepBase(),
          },
        });
        break;
      }
      case "MozUpdateDateTimePicker": {
        let value = this._inputElement.getDateTimeInputBoxValue();
        value.type = this._inputElement.type;
        sendAsyncMessage("FormDateTime:UpdatePicker", { value });
        break;
      }
      case "MozCloseDateTimePicker": {
        sendAsyncMessage("FormDateTime:ClosePicker");
        this.close();
        break;
      }
      case "pagehide": {
        if (this._inputElement &&
            this._inputElement.ownerDocument == aEvent.target) {
          sendAsyncMessage("FormDateTime:ClosePicker");
          this.close();
        }
        break;
      }
      default:
        break;
    }
  },
}

DateTimePickerListener.init();

addEventListener("mozshowdropdown", event => {
  if (!event.isTrusted)
    return;

  if (!SelectContentHelper.open) {
    new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this);
  }
});

addEventListener("mozshowdropdown-sourcetouch", event => {
  if (!event.isTrusted)
    return;

  if (!SelectContentHelper.open) {
    new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this);
  }
});
PK
!<.r.chrome/toolkit/content/global/buildconfig.html<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width; user-scalable=false;">
  <title>about:buildconfig</title>
  <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css">
  <style type="text/css">
    th { text-align: start; }
    h2 { margin-top: 1.5em; }
    th, td { vertical-align: top; }
  </style>
</head>
<body class="aboutPageWideContainer">
<h1>about:buildconfig</h1>
<h2>Source</h2>
<p>Built from <a href="https://hg.mozilla.org/releases/mozilla-release/rev/8fbf05f4b92125e081984f5e39b559b83e5cc729">https://hg.mozilla.org/releases/mozilla-release/rev/8fbf05f4b92125e081984f5e39b559b83e5cc729</a></p>
<h2>Build platform</h2>
<table>
  <tbody>
    <tr>
      <th>target</th>
    </tr>
    <tr>
      <td>i686-pc-mingw32</td>
    </tr>
  </tbody>
</table>
<h2>Build tools</h2>
<table>
  <tbody>
    <tr>
      <th>Compiler</th>
      <th>Version</th>
      <th>Compiler flags</th>
    </tr>
    <tr>
      <td>z:/build/build/src/vs2015u3/VC/bin/amd64_x86/cl.exe</td>
      <td>19.00.24213</td>
      <td>-TC -nologo -wd4091 -D_HAS_EXCEPTIONS=0 -W3 -Gy -Zc:inline -arch:SSE2 -Gw -wd4244 -wd4267 -we4553</td>
    </tr>
    <tr>
      <td>z:/build/build/src/vs2015u3/VC/bin/amd64_x86/cl.exe</td>
      <td>19.00.24213</td>
      <td>-utf-8 -TP -nologo -wd5026 -wd5027 -Zc:sizedDealloc- -wd4091 -wd4577 -D_HAS_EXCEPTIONS=0 -W3 -Gy -Zc:inline -arch:SSE2 -Gw -wd4251 -wd4244 -wd4267 -wd4800 -wd4595 -we4553 -GR-  -Zi -GL -wd4624 -wd4952 -O1 -Oi -Oy- </td>
    </tr>
  </tbody>
</table>
<h2>Configure options</h2>
<p>MOZ_AUTOMATION=1 'MOZILLABUILD=C:\mozilla-build' --enable-update-channel=release MOZILLA_OFFICIAL=1 MOZ_PGO=1 WINDOWSSDKDIR=z:/build/build/src/vs2015u3/SDK MAKECAB=z:/build/build/src/makecab.exe --enable-jemalloc --enable-js-shell RUSTC=z:/build/build/src/rustc/bin/rustc CARGO=z:/build/build/src/rustc/bin/cargo --with-mozilla-api-keyfile=c:/builds/mozilla-desktop-geoloc-api.key --with-google-api-keyfile=c:/builds/gapi.data LLVM_CONFIG=z:/build/build/src/clang/bin/llvm-config --enable-rust-simd MAKE=z:/build/build/src/mozmake.EXE --enable-crashreporter --enable-official-branding --enable-verify-mar</p>
</body>
</html>
PK
!<Pu=66.chrome/toolkit/content/global/commonDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */

#infoContainer {
  max-width: 45em;
}

#info\.body {
  -moz-user-focus: normal;
  -moz-user-select: text;
  cursor: text !important;
  white-space: pre-wrap;
  unicode-bidi: plaintext;
}

#loginLabel, #password1Label {
  text-align: right;
}

PK
!<
}'		-chrome/toolkit/content/global/commonDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var Ci = Components.interfaces;
var Cr = Components.results;
var Cc = Components.classes;
var Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/CommonDialog.jsm");

var propBag, args, Dialog;

function commonDialogOnLoad() {
    propBag = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
                                 .QueryInterface(Ci.nsIWritablePropertyBag);
    // Convert to a JS object
    args = {};
    let propEnum = propBag.enumerator;
    while (propEnum.hasMoreElements()) {
        let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
        args[prop.name] = prop.value;
    }

    let dialog = document.documentElement;

    let ui = {
        prompt: window,
        loginContainer: document.getElementById("loginContainer"),
        loginTextbox: document.getElementById("loginTextbox"),
        loginLabel: document.getElementById("loginLabel"),
        password1Container: document.getElementById("password1Container"),
        password1Textbox: document.getElementById("password1Textbox"),
        password1Label: document.getElementById("password1Label"),
        infoBody: document.getElementById("info.body"),
        infoTitle: document.getElementById("info.title"),
        infoIcon: document.getElementById("info.icon"),
        checkbox: document.getElementById("checkbox"),
        checkboxContainer: document.getElementById("checkboxContainer"),
        button3: dialog.getButton("extra2"),
        button2: dialog.getButton("extra1"),
        button1: dialog.getButton("cancel"),
        button0: dialog.getButton("accept"),
        focusTarget: window,
    };

    // limit the dialog to the screen width
    document.getElementById("filler").maxWidth = screen.availWidth;

    Dialog = new CommonDialog(args, ui);
    Dialog.onLoad(dialog);
    // resize the window to the content
    window.sizeToContent();
    window.getAttention();
}

function commonDialogOnUnload() {
    // Convert args back into property bag
    for (let propName in args)
        propBag.setProperty(propName, args[propName]);
}
PK
!<.chrome/toolkit/content/global/commonDialog.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/commonDialog.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?>

<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">

<dialog id="commonDialog"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        aria-describedby="info.body"
        onunload="commonDialogOnUnload();"
        ondialogaccept="Dialog.onButton0(); return true;"
        ondialogcancel="Dialog.onButton1(); return true;"
        ondialogextra1="Dialog.onButton2(); window.close();"
        ondialogextra2="Dialog.onButton3(); window.close();"
        buttonpack="center">

  <script type="application/javascript" src="chrome://global/content/commonDialog.js"/>
  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
  <script type="application/javascript">
    document.addEventListener("DOMContentLoaded", function() {
      commonDialogOnLoad();
    });
  </script>

  <commandset id="selectEditMenuItems">
    <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/>
    <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
  </commandset>

  <popupset id="contentAreaContextSet">
    <menupopup id="contentAreaContextMenu"
               onpopupshowing="goUpdateCommand('cmd_copy')">
      <menuitem id="context-copy"
                label="&copyCmd.label;"
                accesskey="&copyCmd.accesskey;"
                command="cmd_copy"
                disabled="true"/>
      <menuitem id="context-selectall"
                label="&selectAllCmd.label;"
                accesskey="&selectAllCmd.accesskey;"
                command="cmd_selectAll"/>
    </menupopup>
  </popupset>

  <hbox id="filler" style="min-width: 0%;">
    <spacer style="width: 29em;"/>
  </hbox>

  <grid>
    <columns>
      <column/>
      <column flex="1"/>
    </columns>

    <rows>
      <row>
        <hbox id="iconContainer" align="start">
          <image id="info.icon" class="spaced"/>
        </hbox>
        <vbox id="infoContainer"
              pack="center"
        >
          <!-- Only shown on OS X, since it has no dialog title -->
          <description id="info.title"
            hidden="true"
          />
          <description id="info.body" context="contentAreaContextMenu" noinitialfocus="true"/>
        </vbox>
      </row>
      <row id="loginContainer" hidden="true" align="center">
        <label id="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
        <textbox id="loginTextbox"/>
      </row>
      <row id ="password1Container" hidden="true" align="center">
        <label id="password1Label" value="&editfield1.label;" control="password1Textbox"/>
        <textbox type="password" id="password1Textbox"/>
      </row>
      <row id="checkboxContainer" hidden="true">
        <spacer/>
        <checkbox id="checkbox" oncommand="Dialog.onCheckbox()"/>
      </row>
    </rows>
  </grid>

</dialog>
PK
!<:QII'chrome/toolkit/content/global/config.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/. */

Components.utils.import("resource://gre/modules/Services.jsm");

const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
const nsISupportsString = Components.interfaces.nsISupportsString;
const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
const nsIClipboardHelper = Components.interfaces.nsIClipboardHelper;

const nsSupportsString_CONTRACTID = "@mozilla.org/supports-string;1";
const nsPrompt_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
const nsPrefService_CONTRACTID = "@mozilla.org/preferences-service;1";
const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";

const gPrefBranch = Services.prefs;
const gClipboardHelper = Components.classes[nsClipboardHelper_CONTRACTID].getService(nsIClipboardHelper);

var gLockProps = ["default", "user", "locked"];
// we get these from a string bundle
var gLockStrs = [];
var gTypeStrs = [];

const PREF_IS_DEFAULT_VALUE = 0;
const PREF_IS_MODIFIED = 1;
const PREF_IS_LOCKED = 2;

var gPrefHash = {};
var gPrefArray = [];
var gPrefView = gPrefArray; // share the JS array
var gSortedColumn = "prefCol";
var gSortFunction = null;
var gSortDirection = 1; // 1 is ascending; -1 is descending
var gConfigBundle = null;
var gFilter = null;

var view = {
  get rowCount() { return gPrefView.length; },
  getCellText(index, col) {
    if (!(index in gPrefView))
      return "";

    var value = gPrefView[index][col.id];

    switch (col.id) {
      case "lockCol":
        return gLockStrs[value];
      case "typeCol":
        return gTypeStrs[value];
      default:
        return value;
    }
  },
  getRowProperties(index) { return ""; },
  getCellProperties(index, col) {
    if (index in gPrefView)
      return gLockProps[gPrefView[index].lockCol];

    return "";
  },
  getColumnProperties(col) { return ""; },
  treebox: null,
  selection: null,
  isContainer(index) { return false; },
  isContainerOpen(index) { return false; },
  isContainerEmpty(index) { return false; },
  isSorted() { return true; },
  canDrop(index, orientation) { return false; },
  drop(row, orientation) {},
  setTree(out) { this.treebox = out; },
  getParentIndex(rowIndex) { return -1; },
  hasNextSibling(rowIndex, afterIndex) { return false; },
  getLevel(index) { return 1; },
  getImageSrc(row, col) { return ""; },
  toggleOpenState(index) {},
  cycleHeader(col) {
    var index = this.selection.currentIndex;
    if (col.id == gSortedColumn) {
      gSortDirection = -gSortDirection;
      gPrefArray.reverse();
      if (gPrefView != gPrefArray)
        gPrefView.reverse();
      if (index >= 0)
        index = gPrefView.length - index - 1;
    } else {
      var pref = null;
      if (index >= 0)
        pref = gPrefView[index];

      var old = document.getElementById(gSortedColumn);
      old.removeAttribute("sortDirection");
      gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
      if (gPrefView != gPrefArray)
        gPrefView.sort(gSortFunction);
      gSortedColumn = col.id;
      if (pref)
        index = getViewIndexOfPref(pref);
    }
    col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
    this.treebox.invalidate();
    if (index >= 0) {
      this.selection.select(index);
      this.treebox.ensureRowIsVisible(index);
    }
  },
  selectionChanged() {},
  cycleCell(row, col) {},
  isEditable(row, col) { return false; },
  isSelectable(row, col) { return false; },
  setCellValue(row, col, value) {},
  setCellText(row, col, value) {},
  performAction(action) {},
  performActionOnRow(action, row) {},
  performActionOnCell(action, row, col) {},
  isSeparator(index) { return false; }
};

// find the index in gPrefView of a pref object
// or -1 if it does not exist in the filtered view
function getViewIndexOfPref(pref) {
  var low = -1, high = gPrefView.length;
  var index = (low + high) >> 1;
  while (index > low) {
    var mid = gPrefView[index];
    if (mid == pref)
      return index;
    if (gSortFunction(mid, pref) < 0)
      low = index;
    else
      high = index;
    index = (low + high) >> 1;
  }
  return -1;
}

// find the index in gPrefView where a pref object belongs
function getNearestViewIndexOfPref(pref) {
  var low = -1, high = gPrefView.length;
  var index = (low + high) >> 1;
  while (index > low) {
    if (gSortFunction(gPrefView[index], pref) < 0)
      low = index;
    else
      high = index;
    index = (low + high) >> 1;
  }
  return high;
}

// find the index in gPrefArray of a pref object
function getIndexOfPref(pref) {
  var low = -1, high = gPrefArray.length;
  var index = (low + high) >> 1;
  while (index > low) {
    var mid = gPrefArray[index];
    if (mid == pref)
      return index;
    if (gSortFunction(mid, pref) < 0)
      low = index;
    else
      high = index;
    index = (low + high) >> 1;
  }
  return index;
}

function getNearestIndexOfPref(pref) {
  var low = -1, high = gPrefArray.length;
  var index = (low + high) >> 1;
  while (index > low) {
    if (gSortFunction(gPrefArray[index], pref) < 0)
      low = index;
    else
      high = index;
    index = (low + high) >> 1;
  }
  return high;
}

var gPrefListener =
{
  observe(subject, topic, prefName) {
    if (topic != "nsPref:changed")
      return;

    var arrayIndex = gPrefArray.length;
    var viewIndex = arrayIndex;
    var selectedIndex = view.selection.currentIndex;
    var pref;
    var updateView = false;
    var updateArray = false;
    var addedRow = false;
    if (prefName in gPrefHash) {
      pref = gPrefHash[prefName];
      viewIndex = getViewIndexOfPref(pref);
      arrayIndex = getIndexOfPref(pref);
      fetchPref(prefName, arrayIndex);
      // fetchPref replaces the existing pref object
      pref = gPrefHash[prefName];
      if (viewIndex >= 0) {
        // Might need to update the filtered view
        gPrefView[viewIndex] = gPrefHash[prefName];
        view.treebox.invalidateRow(viewIndex);
      }
      if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
        updateArray = true;
        gPrefArray.splice(arrayIndex, 1);
        if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
          updateView = true;
          gPrefView.splice(viewIndex, 1);
        }
      }
    } else {
      fetchPref(prefName, arrayIndex);
      pref = gPrefArray.pop();
      updateArray = true;
      addedRow = true;
      if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
        updateView = true;
      }
    }
    if (updateArray) {
      // Reinsert in the data array
      var newIndex = getNearestIndexOfPref(pref);
      gPrefArray.splice(newIndex, 0, pref);

      if (updateView) {
        // View is filtered, reinsert in the view separately
        newIndex = getNearestViewIndexOfPref(pref);
        gPrefView.splice(newIndex, 0, pref);
      } else if (gFilter) {
        // View is filtered, but nothing to update
        return;
      }

      if (addedRow)
        view.treebox.rowCountChanged(newIndex, 1);

      // Invalidate the changed range in the view
      var low = Math.min(viewIndex, newIndex);
      var high = Math.max(viewIndex, newIndex);
      view.treebox.invalidateRange(low, high);

      if (selectedIndex == viewIndex) {
        selectedIndex = newIndex;
      } else if (selectedIndex >= low && selectedIndex <= high) {
        selectedIndex += (newIndex > viewIndex) ? -1 : 1;
      }
      if (selectedIndex >= 0) {
        view.selection.select(selectedIndex);
        if (selectedIndex == newIndex)
          view.treebox.ensureRowIsVisible(selectedIndex);
      }
    }
  }
};

function prefObject(prefName, prefIndex) {
  this.prefCol = prefName;
}

prefObject.prototype =
{
  lockCol: PREF_IS_DEFAULT_VALUE,
  typeCol: nsIPrefBranch.PREF_STRING,
  valueCol: ""
};

function fetchPref(prefName, prefIndex) {
  var pref = new prefObject(prefName);

  gPrefHash[prefName] = pref;
  gPrefArray[prefIndex] = pref;

  if (gPrefBranch.prefIsLocked(prefName))
    pref.lockCol = PREF_IS_LOCKED;
  else if (gPrefBranch.prefHasUserValue(prefName))
    pref.lockCol = PREF_IS_MODIFIED;

  try {
    switch (gPrefBranch.getPrefType(prefName)) {
      case gPrefBranch.PREF_BOOL:
        pref.typeCol = gPrefBranch.PREF_BOOL;
        // convert to a string
        pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
        break;
      case gPrefBranch.PREF_INT:
        pref.typeCol = gPrefBranch.PREF_INT;
        // convert to a string
        pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
        break;
      default:
      case gPrefBranch.PREF_STRING:
        pref.valueCol = gPrefBranch.getStringPref(prefName);
        // Try in case it's a localized string (will throw an exception if not)
        if (pref.lockCol == PREF_IS_DEFAULT_VALUE &&
            /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol))
          pref.valueCol = gPrefBranch.getComplexValue(prefName, nsIPrefLocalizedString).data;
        break;
    }
  } catch (e) {
    // Also catch obscure cases in which you can't tell in advance
    // that the pref exists but has no user or default value...
  }
}

function onConfigLoad() {
  // Load strings
  gConfigBundle = document.getElementById("configBundle");

  gLockStrs[PREF_IS_DEFAULT_VALUE] = gConfigBundle.getString("default");
  gLockStrs[PREF_IS_MODIFIED] = gConfigBundle.getString("modified");
  gLockStrs[PREF_IS_LOCKED] = gConfigBundle.getString("locked");

  gTypeStrs[nsIPrefBranch.PREF_STRING] = gConfigBundle.getString("string");
  gTypeStrs[nsIPrefBranch.PREF_INT] = gConfigBundle.getString("int");
  gTypeStrs[nsIPrefBranch.PREF_BOOL] = gConfigBundle.getString("bool");

  var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");

  if (showWarning)
    document.getElementById("warningButton").focus();
  else
    ShowPrefs();
}

// Unhide the warning message
function ShowPrefs() {
  gPrefBranch.getChildList("").forEach(fetchPref);

  var descending = document.getElementsByAttribute("sortDirection", "descending");
  if (descending.item(0)) {
    gSortedColumn = descending[0].id;
    gSortDirection = -1;
  } else {
    var ascending = document.getElementsByAttribute("sortDirection", "ascending");
    if (ascending.item(0))
      gSortedColumn = ascending[0].id;
    else
      document.getElementById(gSortedColumn).setAttribute("sortDirection", "ascending");
  }
  gSortFunction = gSortFunctions[gSortedColumn];
  gPrefArray.sort(gSortFunction);

  gPrefBranch.addObserver("", gPrefListener);

  var configTree = document.getElementById("configTree");
  configTree.view = view;
  configTree.controllers.insertControllerAt(0, configController);

  document.getElementById("configDeck").setAttribute("selectedIndex", 1);
  document.getElementById("configTreeKeyset").removeAttribute("disabled");
  if (!document.getElementById("showWarningNextTime").checked)
    gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);

  // Process about:config?filter=<string>
  var textbox = document.getElementById("textbox");
  // About URIs don't support query params, so do this manually
  var loc = document.location.href;
  var matches = /[?&]filter\=([^&]+)/i.exec(loc);
  if (matches)
    textbox.value = decodeURIComponent(matches[1]);

  // Even if we did not set the filter string via the URL query,
  // textbox might have been set via some other mechanism
  if (textbox.value)
    FilterPrefs();
  textbox.focus();
}

function onConfigUnload() {
  if (document.getElementById("configDeck").getAttribute("selectedIndex") == 1) {
    gPrefBranch.removeObserver("", gPrefListener);
    var configTree = document.getElementById("configTree");
    configTree.view = null;
    configTree.controllers.removeController(configController);
  }
}

function FilterPrefs() {
  if (document.getElementById("configDeck").getAttribute("selectedIndex") != 1) {
    return;
  }

  var substring = document.getElementById("textbox").value;
  // Check for "/regex/[i]"
  if (substring.charAt(0) == "/") {
    var r = substring.match(/^\/(.*)\/(i?)$/);
    try {
      gFilter = RegExp(r[1], r[2]);
    } catch (e) {
      return; // Do nothing on incomplete or bad RegExp
    }
  } else if (substring) {
    gFilter = RegExp(substring.replace(/([^* \w])/g, "\\$1")
                              .replace(/^\*+/, "").replace(/\*+/g, ".*"), "i");
  } else {
    gFilter = null;
  }

  var prefCol = (view.selection && view.selection.currentIndex < 0) ?
                null : gPrefView[view.selection.currentIndex].prefCol;
  var oldlen = gPrefView.length;
  gPrefView = gPrefArray;
  if (gFilter) {
    gPrefView = [];
    for (var i = 0; i < gPrefArray.length; ++i)
      if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol))
        gPrefView.push(gPrefArray[i]);
  }
  view.treebox.invalidate();
  view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
  gotoPref(prefCol);
}

function prefColSortFunction(x, y) {
  if (x.prefCol > y.prefCol)
    return gSortDirection;
  if (x.prefCol < y.prefCol)
    return -gSortDirection;
  return 0;
}

function lockColSortFunction(x, y) {
  if (x.lockCol != y.lockCol)
    return gSortDirection * (y.lockCol - x.lockCol);
  return prefColSortFunction(x, y);
}

function typeColSortFunction(x, y) {
  if (x.typeCol != y.typeCol)
    return gSortDirection * (y.typeCol - x.typeCol);
  return prefColSortFunction(x, y);
}

function valueColSortFunction(x, y) {
  if (x.valueCol > y.valueCol)
    return gSortDirection;
  if (x.valueCol < y.valueCol)
    return -gSortDirection;
  return prefColSortFunction(x, y);
}

const gSortFunctions =
{
  prefCol: prefColSortFunction,
  lockCol: lockColSortFunction,
  typeCol: typeColSortFunction,
  valueCol: valueColSortFunction
};

const configController = {
  supportsCommand: function supportsCommand(command) {
    return command == "cmd_copy";
  },
  isCommandEnabled: function isCommandEnabled(command) {
    return view.selection && view.selection.currentIndex >= 0;
  },
  doCommand: function doCommand(command) {
    copyPref();
  },
  onEvent: function onEvent(event) {
  }
}

function updateContextMenu() {
  var lockCol = PREF_IS_LOCKED;
  var typeCol = nsIPrefBranch.PREF_STRING;
  var valueCol = "";
  var copyDisabled = true;
  var prefSelected = view.selection.currentIndex >= 0;

  if (prefSelected) {
    var prefRow = gPrefView[view.selection.currentIndex];
    lockCol = prefRow.lockCol;
    typeCol = prefRow.typeCol;
    valueCol = prefRow.valueCol;
    copyDisabled = false;
  }

  var copyPref = document.getElementById("copyPref");
  copyPref.setAttribute("disabled", copyDisabled);

  var copyName = document.getElementById("copyName");
  copyName.setAttribute("disabled", copyDisabled);

  var copyValue = document.getElementById("copyValue");
  copyValue.setAttribute("disabled", copyDisabled);

  var resetSelected = document.getElementById("resetSelected");
  resetSelected.setAttribute("disabled", lockCol != PREF_IS_MODIFIED);

  var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
  // indicates that a pref is locked or no pref is selected at all
  var isLocked = lockCol == PREF_IS_LOCKED;

  var modifySelected = document.getElementById("modifySelected");
  modifySelected.setAttribute("disabled", isLocked);
  modifySelected.hidden = canToggle;

  var toggleSelected = document.getElementById("toggleSelected");
  toggleSelected.setAttribute("disabled", isLocked);
  toggleSelected.hidden = !canToggle;
}

function copyPref() {
  var pref = gPrefView[view.selection.currentIndex];
  gClipboardHelper.copyString(pref.prefCol + ";" + pref.valueCol);
}

function copyName() {
  gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol);
}

function copyValue() {
  gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol);
}

function ModifySelected() {
  if (view.selection.currentIndex >= 0)
    ModifyPref(gPrefView[view.selection.currentIndex]);
}

function ResetSelected() {
  var entry = gPrefView[view.selection.currentIndex];
  gPrefBranch.clearUserPref(entry.prefCol);
}

function NewPref(type) {
  var result = { value: "" };
  var dummy = { value: 0 };
  if (Services.prompt.prompt(window,
                             gConfigBundle.getFormattedString("new_title",
                                                              [gTypeStrs[type]]),
                             gConfigBundle.getString("new_prompt"),
                             result,
                             null,
                             dummy)) {
    result.value = result.value.trim();
    if (!result.value) {
      return;
    }

    var pref;
    if (result.value in gPrefHash)
      pref = gPrefHash[result.value];
    else
      pref = { prefCol: result.value, lockCol: PREF_IS_DEFAULT_VALUE, typeCol: type, valueCol: "" };
    if (ModifyPref(pref))
      setTimeout(gotoPref, 0, result.value);
  }
}

function gotoPref(pref) {
  // make sure the pref exists and is displayed in the current view
  var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
  if (index >= 0) {
    view.selection.select(index);
    view.treebox.ensureRowIsVisible(index);
  } else {
    view.selection.clearSelection();
    view.selection.currentIndex = -1;
  }
}

function ModifyPref(entry) {
  if (entry.lockCol == PREF_IS_LOCKED)
    return false;
  var title = gConfigBundle.getFormattedString("modify_title", [gTypeStrs[entry.typeCol]]);
  if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
    var check = { value: entry.valueCol == "false" };
    if (!entry.valueCol && !Services.prompt.select(window, title, entry.prefCol, 2, [false, true], check))
      return false;
    gPrefBranch.setBoolPref(entry.prefCol, check.value);
  } else {
    var result = { value: entry.valueCol };
    var dummy = { value: 0 };
    if (!Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy))
      return false;
    if (entry.typeCol == nsIPrefBranch.PREF_INT) {
      // | 0 converts to integer or 0; - 0 to float or NaN.
      // Thus, this check should catch all cases.
      var val = result.value | 0;
      if (val != result.value - 0) {
        var err_title = gConfigBundle.getString("nan_title");
        var err_text = gConfigBundle.getString("nan_text");
        Services.prompt.alert(window, err_title, err_text);
        return false;
      }
      gPrefBranch.setIntPref(entry.prefCol, val);
    } else {
      gPrefBranch.setStringPref(entry.prefCol, result.value);
    }
  }

  Services.prefs.savePrefFile(null);
  return true;
}
PK
!<xxq9(chrome/toolkit/content/global/config.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/in-content/info-pages.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/config.css" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://global/locale/config.dtd">

<window id="config"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&window.title;"
        windowtype="Preferences:ConfigManager"
        role="application"
        aria-describedby="warningTitle warningText"
        width="750"
        height="500"
        disablefastfind="true"
        onunload="onConfigUnload();"
        onload="onConfigLoad();">

<script src="chrome://global/content/config.js"/>

<stringbundle id="configBundle" src="chrome://global/locale/config.properties"/>

<menupopup id="configContext" onpopupshowing="if (event.target == this) updateContextMenu();">
  <menuitem id="toggleSelected" default="true"
            label="&toggle.label;" accesskey="&toggle.accesskey;"
            oncommand="ModifySelected();"/>
  <menuitem id="modifySelected" default="true"
            label="&modify.label;" accesskey="&modify.accesskey;"
            oncommand="ModifySelected();"/>
  <menuseparator/>
  <menuitem id="copyPref" label="&copyPref.label;" accesskey="&copyPref.accesskey;" oncommand="copyPref();"/>
  <menuitem id="copyName" label="&copyName.label;" accesskey="&copyName.accesskey;" oncommand="copyName();"/>
  <menuitem id="copyValue" label="&copyValue.label;" accesskey="&copyValue.accesskey;" oncommand="copyValue();"/>
  <menu label="&new.label;" accesskey="&new.accesskey;">
    <menupopup>
      <menuitem label="&string.label;" accesskey="&string.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_STRING);"/>
      <menuitem label="&integer.label;" accesskey="&integer.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_INT);"/>
      <menuitem label="&boolean.label;" accesskey="&boolean.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_BOOL);"/>
    </menupopup>
  </menu>
  <menuitem id="resetSelected" label="&reset.label;" accesskey="&reset.accesskey;" oncommand="ResetSelected();"/>
</menupopup>

<keyset id="configTreeKeyset" disabled="true">
  <key keycode="VK_RETURN" oncommand="ModifySelected();"/>
  <key key="&focusSearch.key;" modifiers="accel" oncommand="document.getElementById('textbox').focus();"/>
  <key key="&focusSearch2.key;" modifiers="accel" oncommand="document.getElementById('textbox').focus();"/>
</keyset>
<deck id="configDeck" flex="1">
  <vbox id="warningScreen" flex="1" align="center" style="overflow: auto;">
    <spacer flex="1"/>
    <vbox id="warningBox" class="container">
      <hbox class="title" flex="1">
        <label id="warningTitle" class="title-text" flex="1">&aboutWarningTitle.label;</label>
      </hbox>
      <vbox class="description" flex="1">
        <label id="warningText">&aboutWarningText.label;</label>
        <checkbox id="showWarningNextTime" label="&aboutWarningCheckbox.label;" checked="true"/>
        <hbox class="button-container">
          <button id="warningButton" class="primary" oncommand="ShowPrefs();" label="&aboutWarningButton2.label;"/>
        </hbox>
      </vbox>
    </vbox>
    <spacer flex="2"/>
  </vbox>
  <vbox flex="1">
    <hbox id="filterRow" align="center">
      <label value="&searchPrefs.label;" accesskey="&searchPrefs.accesskey;" control="textbox"/>
      <textbox id="textbox" flex="1" type="search" class="compact"
               aria-controls="configTree"
               oncommand="FilterPrefs();"/>
    </hbox>

    <tree id="configTree" flex="1" seltype="single"
          onselect="updateCommands('select');"
          enableColumnDrag="true" context="configContext">
      <treecols>
        <treecol id="prefCol" label="&prefColumn.label;" flex="7"
            ignoreincolumnpicker="true"
            persist="hidden width ordinal sortDirection"/>
        <splitter class="tree-splitter" />
        <treecol id="lockCol" label="&lockColumn.label;" flex="1"
            persist="hidden width ordinal sortDirection"/>
        <splitter class="tree-splitter" />
        <treecol id="typeCol" label="&typeColumn.label;" flex="1"
            persist="hidden width ordinal sortDirection"/>
        <splitter class="tree-splitter" />
        <treecol id="valueCol" label="&valueColumn.label;" flex="10"
            persist="hidden width ordinal sortDirection"/>
      </treecols>

      <treechildren id="configTreeBody" ondblclick="if (event.button == 0) ModifySelected();"/>
    </tree>
  </vbox>
</deck>
</window>
PK
!<3n1chrome/toolkit/content/global/contentAreaUtils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                  "resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                  "resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadLastDir",
                                  "resource://gre/modules/DownloadLastDir.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                  "resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");

var ContentAreaUtils = {

  // this is for backwards compatibility.
  get ioService() {
    return Services.io;
  },

  get stringBundle() {
    delete this.stringBundle;
    return this.stringBundle =
      Services.strings.createBundle("chrome://global/locale/contentAreaCommands.properties");
  }
}

function urlSecurityCheck(aURL, aPrincipal, aFlags) {
  return BrowserUtils.urlSecurityCheck(aURL, aPrincipal, aFlags);
}

/**
 * Determine whether or not a given focused DOMWindow is in the content area.
 **/
function isContentFrame(aFocusedWindow) {
  if (!aFocusedWindow)
    return false;

  return (aFocusedWindow.top == window.content);
}

function forbidCPOW(arg, func, argname) {
  if (arg && (typeof(arg) == "object" || typeof(arg) == "function") &&
      Components.utils.isCrossProcessWrapper(arg)) {
    throw new Error(`no CPOWs allowed for argument ${argname} to ${func}`);
  }
}

// Clientele: (Make sure you don't break any of these)
//  - File    ->  Save Page/Frame As...
//  - Context ->  Save Page/Frame As...
//  - Context ->  Save Link As...
//  - Alt-Click links in web pages
//  - Alt-Click links in the UI
//
// Try saving each of these types:
// - A complete webpage using File->Save Page As, and Context->Save Page As
// - A webpage as HTML only using the above methods
// - A webpage as Text only using the above methods
// - An image with an extension (e.g. .jpg) in its file name, using
//   Context->Save Image As...
// - An image without an extension (e.g. a banner ad on cnn.com) using
//   the above method.
// - A linked document using Save Link As...
// - A linked document using Alt-click Save Link As...
//
function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
                 aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate) {
  forbidCPOW(aURL, "saveURL", "aURL");
  forbidCPOW(aReferrer, "saveURL", "aReferrer");
  // Allow aSourceDocument to be a CPOW.

  internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
               aFilePickerTitleKey, null, aReferrer, aSourceDocument,
               aSkipPrompt, null, aIsContentWindowPrivate);
}

// Just like saveURL, but will get some info off the image before
// calling internalSave
// Clientele: (Make sure you don't break any of these)
//  - Context ->  Save Image As...
const imgICache = Components.interfaces.imgICache;
const nsISupportsCString = Components.interfaces.nsISupportsCString;

/**
 * Offers to save an image URL to the file system.
 *
 * @param aURL (string)
 *        The URL of the image to be saved.
 * @param aFileName (string)
 *        The suggested filename for the saved file.
 * @param aFilePickerTitleKey (string, optional)
 *        Localized string key for an alternate title for the file
 *        picker. If set to null, this will default to something sensible.
 * @param aShouldBypassCache (bool)
 *        If true, the image will always be retrieved from the server instead
 *        of the network or image caches.
 * @param aSkipPrompt (bool)
 *        If true, we will attempt to save the file with the suggested
 *        filename to the default downloads folder without showing the
 *        file picker.
 * @param aReferrer (nsIURI, optional)
 *        The referrer URI object (not a URL string) to use, or null
 *        if no referrer should be sent.
 * @param aDoc (nsIDocument, deprecated, optional)
 *        The content document that the save is being initiated from. If this
 *        is omitted, then aIsContentWindowPrivate must be provided.
 * @param aContentType (string, optional)
 *        The content type of the image.
 * @param aContentDisp (string, optional)
 *        The content disposition of the image.
 * @param aIsContentWindowPrivate (bool)
 *        Whether or not the containing window is in private browsing mode.
 *        Does not need to be provided is aDoc is passed.
 */
function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
                      aSkipPrompt, aReferrer, aDoc, aContentType, aContentDisp,
                      aIsContentWindowPrivate) {
  forbidCPOW(aURL, "saveImageURL", "aURL");
  forbidCPOW(aReferrer, "saveImageURL", "aReferrer");

  if (aDoc && aIsContentWindowPrivate == undefined) {
    if (Components.utils.isCrossProcessWrapper(aDoc)) {
      Deprecated.warning("saveImageURL should not be passed document CPOWs. " +
                         "The caller should pass in the content type and " +
                         "disposition themselves",
                         "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643");
    }
    // This will definitely not work for in-browser code or multi-process compatible
    // add-ons due to bug 1233497, which makes unsafe CPOW usage throw by default.
    Deprecated.warning("saveImageURL should be passed the private state of " +
                       "the containing window.",
                       "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643");
    aIsContentWindowPrivate =
      PrivateBrowsingUtils.isContentWindowPrivate(aDoc.defaultView);
  }

  // We'd better have the private state by now.
  if (aIsContentWindowPrivate == undefined) {
    throw new Error("saveImageURL couldn't compute private state of content window");
  }

  if (!aShouldBypassCache && (aDoc && !Components.utils.isCrossProcessWrapper(aDoc)) &&
      (!aContentType && !aContentDisp)) {
    try {
      var imageCache = Components.classes["@mozilla.org/image/tools;1"]
                                 .getService(Components.interfaces.imgITools)
                                 .getImgCacheForDocument(aDoc);
      var props =
        imageCache.findEntryProperties(makeURI(aURL, getCharsetforSave(null)), aDoc);
      if (props) {
        aContentType = props.get("type", nsISupportsCString);
        aContentDisp = props.get("content-disposition", nsISupportsCString);
      }
    } catch (e) {
      // Failure to get type and content-disposition off the image is non-fatal
    }
  }

  internalSave(aURL, null, aFileName, aContentDisp, aContentType,
               aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
               null, aSkipPrompt, null, aIsContentWindowPrivate);
}

// This is like saveDocument, but takes any browser/frame-like element
// (nsIFrameLoaderOwner) and saves the current document inside it,
// whether in-process or out-of-process.
function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID = 0) {
  if (!aBrowser) {
    throw "Must have a browser when calling saveBrowser";
  }
  let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
                    .frameLoader
                    .QueryInterface(Ci.nsIWebBrowserPersistable);
  let stack = Components.stack.caller;
  persistable.startPersistence(aOuterWindowID, {
    onDocumentReady(document) {
      saveDocument(document, aSkipPrompt);
    },
    onError(status) {
      throw new Components.Exception("saveBrowser failed asynchronously in startPersistence",
                                     status, stack);
    }
  });
}

// Saves a document; aDocument can be an nsIWebBrowserPersistDocument
// (see saveBrowser, above) or an nsIDOMDocument.
//
// aDocument can also be a CPOW for a remote nsIDOMDocument, in which
// case "save as" modes that serialize the document's DOM are
// unavailable.  This is a temporary measure for the "Save Frame As"
// command (bug 1141337) and pre-e10s add-ons.
function saveDocument(aDocument, aSkipPrompt) {
  const Ci = Components.interfaces;

  if (!aDocument)
    throw "Must have a document when calling saveDocument";

  let contentDisposition = null;
  let cacheKeyInt = null;

  if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
    // nsIWebBrowserPersistDocument exposes these directly.
    contentDisposition = aDocument.contentDisposition;
    cacheKeyInt = aDocument.cacheKey;
  } else if (aDocument instanceof Ci.nsIDOMDocument) {
    // Otherwise it's an actual nsDocument (and possibly a CPOW).
    // We want to use cached data because the document is currently visible.
    let ifreq =
      aDocument.defaultView
               .QueryInterface(Ci.nsIInterfaceRequestor);

    try {
      contentDisposition =
        ifreq.getInterface(Ci.nsIDOMWindowUtils)
             .getDocumentMetadata("content-disposition");
    } catch (ex) {
      // Failure to get a content-disposition is ok
    }

    try {
      let shEntry =
        ifreq.getInterface(Ci.nsIWebNavigation)
             .QueryInterface(Ci.nsIWebPageDescriptor)
             .currentDescriptor
             .QueryInterface(Ci.nsISHEntry);

      let cacheKey = shEntry.cacheKey
                            .QueryInterface(Ci.nsISupportsPRUint32)
                            .data;
      // cacheKey might be a CPOW, which can't be passed to native
      // code, but the data attribute is just a number.
      cacheKeyInt = cacheKey.data;
    } catch (ex) {
      // We might not find it in the cache.  Oh, well.
    }
  }

  // Convert the cacheKey back into an XPCOM object.
  let cacheKey = null;
  if (cacheKeyInt) {
    cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]
      .createInstance(Ci.nsISupportsPRUint32);
    cacheKey.data = cacheKeyInt;
  }

  internalSave(aDocument.documentURI, aDocument, null, contentDisposition,
               aDocument.contentType, false, null, null,
               aDocument.referrer ? makeURI(aDocument.referrer) : null,
               aDocument, aSkipPrompt, cacheKey);
}

function DownloadListener(win, transfer) {
  function makeClosure(name) {
    return function() {
      transfer[name].apply(transfer, arguments);
    }
  }

  this.window = win;

  // Now... we need to forward all calls to our transfer
  for (var i in transfer) {
    if (i != "QueryInterface")
      this[i] = makeClosure(i);
  }
}

DownloadListener.prototype = {
  QueryInterface: function dl_qi(aIID) {
    if (aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
        aIID.equals(Components.interfaces.nsIWebProgressListener) ||
        aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
        aIID.equals(Components.interfaces.nsISupports)) {
      return this;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  getInterface: function dl_gi(aIID) {
    if (aIID.equals(Components.interfaces.nsIAuthPrompt) ||
        aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
      var ww =
        Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                  .getService(Components.interfaces.nsIPromptFactory);
      return ww.getPrompt(this.window, aIID);
    }

    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
}

const kSaveAsType_Complete = 0; // Save document with attached objects.
XPCOMUtils.defineConstant(this, "kSaveAsType_Complete", 0);
// const kSaveAsType_URL      = 1; // Save document or URL by itself.
const kSaveAsType_Text     = 2; // Save document, converting to plain text.
XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text);

/**
 * internalSave: Used when saving a document or URL.
 *
 * If aChosenData is null, this method:
 *  - Determines a local target filename to use
 *  - Prompts the user to confirm the destination filename and save mode
 *    (aContentType affects this)
 *  - [Note] This process involves the parameters aURL, aReferrer (to determine
 *    how aURL was encoded), aDocument, aDefaultFileName, aFilePickerTitleKey,
 *    and aSkipPrompt.
 *
 * If aChosenData is non-null, this method:
 *  - Uses the provided source URI and save file name
 *  - Saves the document as complete DOM if possible (aDocument present and
 *    right aContentType)
 *  - [Note] The parameters aURL, aDefaultFileName, aFilePickerTitleKey, and
 *    aSkipPrompt are ignored.
 *
 * In any case, this method:
 *  - Creates a 'Persist' object (which will perform the saving in the
 *    background) and then starts it.
 *  - [Note] This part of the process only involves the parameters aDocument,
 *    aShouldBypassCache and aReferrer. The source, the save name and the save
 *    mode are the ones determined previously.
 *
 * @param aURL
 *        The String representation of the URL of the document being saved
 * @param aDocument
 *        The document to be saved
 * @param aDefaultFileName
 *        The caller-provided suggested filename if we don't
 *        find a better one
 * @param aContentDisposition
 *        The caller-provided content-disposition header to use.
 * @param aContentType
 *        The caller-provided content-type to use
 * @param aShouldBypassCache
 *        If true, the document will always be refetched from the server
 * @param aFilePickerTitleKey
 *        Alternate title for the file picker
 * @param aChosenData
 *        If non-null this contains an instance of object AutoChosen (see below)
 *        which holds pre-determined data so that the user does not need to be
 *        prompted for a target filename.
 * @param aReferrer
 *        the referrer URI object (not URL string) to use, or null
 *        if no referrer should be sent.
 * @param aInitiatingDocument [optional]
 *        The document from which the save was initiated.
 *        If this is omitted then aIsContentWindowPrivate has to be provided.
 * @param aSkipPrompt [optional]
 *        If set to true, we will attempt to save the file to the
 *        default downloads folder without prompting.
 * @param aCacheKey [optional]
 *        If set will be passed to saveURI.  See nsIWebBrowserPersist for
 *        allowed values.
 * @param aIsContentWindowPrivate [optional]
 *        This parameter is provided when the aInitiatingDocument is not a
 *        real document object. Stores whether aInitiatingDocument.defaultView
 *        was private or not.
 */
function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
                      aContentType, aShouldBypassCache, aFilePickerTitleKey,
                      aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt,
                      aCacheKey, aIsContentWindowPrivate) {
  forbidCPOW(aURL, "internalSave", "aURL");
  forbidCPOW(aReferrer, "internalSave", "aReferrer");
  forbidCPOW(aCacheKey, "internalSave", "aCacheKey");
  // Allow aInitiatingDocument to be a CPOW.

  if (aSkipPrompt == undefined)
    aSkipPrompt = false;

  if (aCacheKey == undefined)
    aCacheKey = null;

  // Note: aDocument == null when this code is used by save-link-as...
  var saveMode = GetSaveModeForContentType(aContentType, aDocument);

  var file, sourceURI, saveAsType;
  // Find the URI object for aURL and the FileName/Extension to use when saving.
  // FileName/Extension will be ignored if aChosenData supplied.
  if (aChosenData) {
    file = aChosenData.file;
    sourceURI = aChosenData.uri;
    saveAsType = kSaveAsType_Complete;

    continueSave();
  } else {
    var charset = null;
    if (aDocument)
      charset = aDocument.characterSet;
    else if (aReferrer)
      charset = aReferrer.originCharset;
    var fileInfo = new FileInfo(aDefaultFileName);
    initFileInfo(fileInfo, aURL, charset, aDocument,
                 aContentType, aContentDisposition);
    sourceURI = fileInfo.uri;

    var fpParams = {
      fpTitleKey: aFilePickerTitleKey,
      fileInfo,
      contentType: aContentType,
      saveMode,
      saveAsType: kSaveAsType_Complete,
      file
    };

    // Find a URI to use for determining last-downloaded-to directory
    let relatedURI = aReferrer || sourceURI;

    promiseTargetFile(fpParams, aSkipPrompt, relatedURI).then(aDialogAccepted => {
      if (!aDialogAccepted)
        return;

      saveAsType = fpParams.saveAsType;
      file = fpParams.file;

      continueSave();
    }).catch(Components.utils.reportError);
  }

  function continueSave() {
    // XXX We depend on the following holding true in appendFiltersForContentType():
    // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
    // If we should save as text, the saveAsType is kSaveAsType_Text.
    var useSaveDocument = aDocument &&
                          (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
                           ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text)));
    // If we're saving a document, and are saving either in complete mode or
    // as converted text, pass the document to the web browser persist component.
    // If we're just saving the HTML (second option in the list), send only the URI.
    let nonCPOWDocument =
      aDocument && !Components.utils.isCrossProcessWrapper(aDocument);

    let isPrivate = aIsContentWindowPrivate;
    if (isPrivate === undefined) {
      isPrivate = aInitiatingDocument instanceof Components.interfaces.nsIDOMDocument
        ? PrivateBrowsingUtils.isContentWindowPrivate(aInitiatingDocument.defaultView)
        : aInitiatingDocument.isPrivate;
    }

    var persistArgs = {
      sourceURI,
      sourceReferrer: aReferrer,
      sourceDocument: useSaveDocument ? aDocument : null,
      targetContentType: (saveAsType == kSaveAsType_Text) ? "text/plain" : null,
      targetFile: file,
      sourceCacheKey: aCacheKey,
      sourcePostData: nonCPOWDocument ? getPostData(aDocument) : null,
      bypassCache: aShouldBypassCache,
      isPrivate,
    };

    // Start the actual save process
    internalPersist(persistArgs);
  }
}

/**
 * internalPersist: Creates a 'Persist' object (which will perform the saving
 *  in the background) and then starts it.
 *
 * @param persistArgs.sourceURI
 *        The nsIURI of the document being saved
 * @param persistArgs.sourceCacheKey [optional]
 *        If set will be passed to saveURI
 * @param persistArgs.sourceDocument [optional]
 *        The document to be saved, or null if not saving a complete document
 * @param persistArgs.sourceReferrer
 *        Required and used only when persistArgs.sourceDocument is NOT present,
 *        the nsIURI of the referrer to use, or null if no referrer should be
 *        sent.
 * @param persistArgs.sourcePostData
 *        Required and used only when persistArgs.sourceDocument is NOT present,
 *        represents the POST data to be sent along with the HTTP request, and
 *        must be null if no POST data should be sent.
 * @param persistArgs.targetFile
 *        The nsIFile of the file to create
 * @param persistArgs.targetContentType
 *        Required and used only when persistArgs.sourceDocument is present,
 *        determines the final content type of the saved file, or null to use
 *        the same content type as the source document. Currently only
 *        "text/plain" is meaningful.
 * @param persistArgs.bypassCache
 *        If true, the document will always be refetched from the server
 * @param persistArgs.isPrivate
 *        Indicates whether this is taking place in a private browsing context.
 */
function internalPersist(persistArgs) {
  var persist = makeWebBrowserPersist();

  // Calculate persist flags.
  const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
  const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
                nsIWBP.PERSIST_FLAGS_FORCE_ALLOW_COOKIES;
  if (persistArgs.bypassCache)
    persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
  else
    persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;

  // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
  persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;

  // Find the URI associated with the target file
  var targetFileURL = makeFileURI(persistArgs.targetFile);

  // Create download and initiate it (below)
  var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
  tr.init(persistArgs.sourceURI,
          targetFileURL, "", null, null, null, persist, persistArgs.isPrivate);
  persist.progressListener = new DownloadListener(window, tr);

  if (persistArgs.sourceDocument) {
    // Saving a Document, not a URI:
    var filesFolder = null;
    if (persistArgs.targetContentType != "text/plain") {
      // Create the local directory into which to save associated files.
      filesFolder = persistArgs.targetFile.clone();

      var nameWithoutExtension = getFileBaseName(filesFolder.leafName);
      var filesFolderLeafName =
        ContentAreaUtils.stringBundle
                        .formatStringFromName("filesFolder", [nameWithoutExtension], 1);

      filesFolder.leafName = filesFolderLeafName;
    }

    var encodingFlags = 0;
    if (persistArgs.targetContentType == "text/plain") {
      encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
      encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
      encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;
    } else {
      encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
    }

    const kWrapColumn = 80;
    persist.saveDocument(persistArgs.sourceDocument, targetFileURL, filesFolder,
                         persistArgs.targetContentType, encodingFlags, kWrapColumn);
  } else {
    persist.savePrivacyAwareURI(persistArgs.sourceURI,
                                persistArgs.sourceCacheKey,
                                persistArgs.sourceReferrer,
                                Components.interfaces.nsIHttpChannel.REFERRER_POLICY_UNSET,
                                persistArgs.sourcePostData,
                                null,
                                targetFileURL,
                                persistArgs.isPrivate);
  }
}

/**
 * Structure for holding info about automatically supplied parameters for
 * internalSave(...). This allows parameters to be supplied so the user does not
 * need to be prompted for file info.
 * @param aFileAutoChosen This is an nsIFile object that has been
 *        pre-determined as the filename for the target to save to
 * @param aUriAutoChosen  This is the nsIURI object for the target
 */
function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
  this.file = aFileAutoChosen;
  this.uri  = aUriAutoChosen;
}

/**
 * Structure for holding info about a URL and the target filename it should be
 * saved to. This structure is populated by initFileInfo(...).
 * @param aSuggestedFileName This is used by initFileInfo(...) when it
 *        cannot 'discover' the filename from the url
 * @param aFileName The target filename
 * @param aFileBaseName The filename without the file extension
 * @param aFileExt The extension of the filename
 * @param aUri An nsIURI object for the url that is being saved
 */
function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) {
  this.suggestedFileName = aSuggestedFileName;
  this.fileName = aFileName;
  this.fileBaseName = aFileBaseName;
  this.fileExt = aFileExt;
  this.uri = aUri;
}

/**
 * Determine what the 'default' filename string is, its file extension and the
 * filename without the extension. This filename is used when prompting the user
 * for confirmation in the file picker dialog.
 * @param aFI A FileInfo structure into which we'll put the results of this method.
 * @param aURL The String representation of the URL of the document being saved
 * @param aURLCharset The charset of aURL.
 * @param aDocument The document to be saved
 * @param aContentType The content type we're saving, if it could be
 *        determined by the caller.
 * @param aContentDisposition The content-disposition header for the object
 *        we're saving, if it could be determined by the caller.
 */
function initFileInfo(aFI, aURL, aURLCharset, aDocument,
                      aContentType, aContentDisposition) {
  try {
    // Get an nsIURI object from aURL if possible:
    try {
      aFI.uri = makeURI(aURL, aURLCharset);
      // Assuming nsiUri is valid, calling QueryInterface(...) on it will
      // populate extra object fields (eg filename and file extension).
      var url = aFI.uri.QueryInterface(Components.interfaces.nsIURL);
      aFI.fileExt = url.fileExtension;
    } catch (e) {
    }

    // Get the default filename:
    aFI.fileName = getDefaultFileName((aFI.suggestedFileName || aFI.fileName),
                                      aFI.uri, aDocument, aContentDisposition);
    // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
    // if saveURL(...) was the original caller (hence both aContentType and
    // aDocument are blank). If they were saving a link to a website then make
    // the extension .htm .
    if (!aFI.fileExt && !aDocument && !aContentType && (/^http(s?):\/\//i.test(aURL))) {
      aFI.fileExt = "htm";
      aFI.fileBaseName = aFI.fileName;
    } else {
      aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
      aFI.fileBaseName = getFileBaseName(aFI.fileName);
    }
  } catch (e) {
  }
}

/**
 * Given the Filepicker Parameters (aFpP), show the file picker dialog,
 * prompting the user to confirm (or change) the fileName.
 * @param aFpP
 *        A structure (see definition in internalSave(...) method)
 *        containing all the data used within this method.
 * @param aSkipPrompt
 *        If true, attempt to save the file automatically to the user's default
 *        download directory, thus skipping the explicit prompt for a file name,
 *        but only if the associated preference is set.
 *        If false, don't save the file automatically to the user's
 *        default download directory, even if the associated preference
 *        is set, but ask for the target explicitly.
 * @param aRelatedURI
 *        An nsIURI associated with the download. The last used
 *        directory of the picker is retrieved from/stored in the
 *        Content Pref Service using this URI.
 * @return Promise
 * @resolve a boolean. When true, it indicates that the file picker dialog
 *          is accepted.
 */
function promiseTargetFile(aFpP, /* optional */ aSkipPrompt, /* optional */ aRelatedURI) {
  return (async function() {
    let downloadLastDir = new DownloadLastDir(window);
    let prefBranch = Services.prefs.getBranch("browser.download.");
    let useDownloadDir = prefBranch.getBoolPref("useDownloadDir");

    if (!aSkipPrompt)
      useDownloadDir = false;

    // Default to the user's default downloads directory configured
    // through download prefs.
    let dirPath = await Downloads.getPreferredDownloadsDirectory();
    let dirExists = await OS.File.exists(dirPath);
    let dir = new FileUtils.File(dirPath);

    if (useDownloadDir && dirExists) {
      dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
                                       aFpP.fileInfo.fileExt));
      aFpP.file = uniqueFile(dir);
      return true;
    }

    // We must prompt for the file name explicitly.
    // If we must prompt because we were asked to...
    let file = await new Promise(resolve => {
      if (useDownloadDir) {
        // Keep async behavior in both branches
        Services.tm.dispatchToMainThread(function() {
          resolve(null);
        });
      } else {
        downloadLastDir.getFileAsync(aRelatedURI, function getFileAsyncCB(aFile) {
          resolve(aFile);
        });
      }
    });
    if (file && (await OS.File.exists(file.path))) {
      dir = file;
      dirExists = true;
    }

    if (!dirExists) {
      // Default to desktop.
      dir = Services.dirsvc.get("Desk", Components.interfaces.nsIFile);
    }

    let fp = makeFilePicker();
    let titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
    fp.init(window, ContentAreaUtils.stringBundle.GetStringFromName(titleKey),
            Components.interfaces.nsIFilePicker.modeSave);

    fp.displayDirectory = dir;
    fp.defaultExtension = aFpP.fileInfo.fileExt;
    fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName,
                                             aFpP.fileInfo.fileExt);
    appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt,
                                aFpP.saveMode);

    // The index of the selected filter is only preserved and restored if there's
    // more than one filter in addition to "All Files".
    if (aFpP.saveMode != SAVEMODE_FILEONLY) {
      // eslint-disable-next-line mozilla/use-default-preference-values
      try {
        fp.filterIndex = prefBranch.getIntPref("save_converter_index");
      } catch (e) {
      }
    }

    let result = await new Promise(resolve => {
      fp.open(function(aResult) {
        resolve(aResult);
      });
    });
    if (result == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) {
      return false;
    }

    if (aFpP.saveMode != SAVEMODE_FILEONLY)
      prefBranch.setIntPref("save_converter_index", fp.filterIndex);

    // Do not store the last save directory as a pref inside the private browsing mode
    downloadLastDir.setFile(aRelatedURI, fp.file.parent);

    fp.file.leafName = validateFileName(fp.file.leafName);

    aFpP.saveAsType = fp.filterIndex;
    aFpP.file = fp.file;
    aFpP.fileURL = fp.fileURL;

    return true;
  })();
}

// Since we're automatically downloading, we don't get the file picker's
// logic to check for existing files, so we need to do that here.
//
// Note - this code is identical to that in
//   mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
// If you are updating this code, update that code too! We can't share code
// here since that code is called in a js component.
function uniqueFile(aLocalFile) {
  var collisionCount = 0;
  while (aLocalFile.exists()) {
    collisionCount++;
    if (collisionCount == 1) {
      // Append "(2)" before the last dot in (or at the end of) the filename
      // special case .ext.gz etc files so we don't wind up with .tar(2).gz
      if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
        aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
      else
        aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
    } else {
      // replace the last (n) in the filename with (n+1)
      aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount + 1) + ")");
    }
  }
  return aLocalFile;
}

/**
 * Download a URL using the new jsdownloads API.
 *
 * @param aURL
 *        the url to download
 * @param [optional] aFileName
 *        the destination file name, if omitted will be obtained from the url.
 * @param aInitiatingDocument
 *        The document from which the download was initiated.
 */
function DownloadURL(aURL, aFileName, aInitiatingDocument) {
  // For private browsing, try to get document out of the most recent browser
  // window, or provide our own if there's no browser window.
  let isPrivate = aInitiatingDocument.defaultView
                                     .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                                     .getInterface(Components.interfaces.nsIWebNavigation)
                                     .QueryInterface(Components.interfaces.nsILoadContext)
                                     .usePrivateBrowsing;

  let fileInfo = new FileInfo(aFileName);
  initFileInfo(fileInfo, aURL, null, null, null, null);

  let filepickerParams = {
    fileInfo,
    saveMode: SAVEMODE_FILEONLY
  };

  (async function() {
    let accepted = await promiseTargetFile(filepickerParams, true, fileInfo.uri);
    if (!accepted)
      return;

    let file = filepickerParams.file;
    let download = await Downloads.createDownload({
      source: { url: aURL, isPrivate },
      target: { path: file.path, partFilePath: file.path + ".part" }
    });
    download.tryToKeepPartialData = true;

    // Ignore errors because failures are reported through the download list.
    download.start().catch(() => {});

    // Add the download to the list, allowing it to be managed.
    let list = await Downloads.getList(Downloads.ALL);
    list.add(download);
  })().catch(Components.utils.reportError);
}

// We have no DOM, and can only save the URL as is.
const SAVEMODE_FILEONLY      = 0x00;
XPCOMUtils.defineConstant(this, "SAVEMODE_FILEONLY", SAVEMODE_FILEONLY);
// We have a DOM and can save as complete.
const SAVEMODE_COMPLETE_DOM  = 0x01;
XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_DOM", SAVEMODE_COMPLETE_DOM);
// We have a DOM which we can serialize as text.
const SAVEMODE_COMPLETE_TEXT = 0x02;
XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_TEXT", SAVEMODE_COMPLETE_TEXT);

// If we are able to save a complete DOM, the 'save as complete' filter
// must be the first filter appended.  The 'save page only' counterpart
// must be the second filter appended.  And the 'save as complete text'
// filter must be the third filter appended.
function appendFiltersForContentType(aFilePicker, aContentType, aFileExtension, aSaveMode) {
  // The bundle name for saving only a specific content type.
  var bundleName;
  // The corresponding filter string for a specific content type.
  var filterString;

  // Every case where GetSaveModeForContentType can return non-FILEONLY
  // modes must be handled here.
  if (aSaveMode != SAVEMODE_FILEONLY) {
    switch (aContentType) {
    case "text/html":
      bundleName   = "WebPageHTMLOnlyFilter";
      filterString = "*.htm; *.html";
      break;

    case "application/xhtml+xml":
      bundleName   = "WebPageXHTMLOnlyFilter";
      filterString = "*.xht; *.xhtml";
      break;

    case "image/svg+xml":
      bundleName   = "WebPageSVGOnlyFilter";
      filterString = "*.svg; *.svgz";
      break;

    case "text/xml":
    case "application/xml":
      bundleName   = "WebPageXMLOnlyFilter";
      filterString = "*.xml";
      break;
    }
  }

  if (!bundleName) {
    if (aSaveMode != SAVEMODE_FILEONLY)
      throw "Invalid save mode for type '" + aContentType + "'";

    var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension);
    if (mimeInfo) {

      var extEnumerator = mimeInfo.getFileExtensions();

      var extString = "";
      while (extEnumerator.hasMore()) {
        var extension = extEnumerator.getNext();
        if (extString)
          extString += "; ";    // If adding more than one extension,
                                // separate by semi-colon
        extString += "*." + extension;
      }

      if (extString)
        aFilePicker.appendFilter(mimeInfo.description, extString);
    }
  }

  if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
    aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName("WebPageCompleteFilter"),
                             filterString);
    // We should always offer a choice to save document only if
    // we allow saving as complete.
    aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName(bundleName),
                             filterString);
  }

  if (aSaveMode & SAVEMODE_COMPLETE_TEXT)
    aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);

  // Always append the all files (*) filter
  aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
}

function getPostData(aDocument) {
  const Ci = Components.interfaces;

  if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
    return aDocument.postData;
  }
  try {
    // Find the session history entry corresponding to the given document. In
    // the current implementation, nsIWebPageDescriptor.currentDescriptor always
    // returns a session history entry.
    let sessionHistoryEntry =
        aDocument.defaultView
                 .QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .QueryInterface(Ci.nsIWebPageDescriptor)
                 .currentDescriptor
                 .QueryInterface(Ci.nsISHEntry);
    return sessionHistoryEntry.postData;
  } catch (e) {
  }
  return null;
}

function makeWebBrowserPersist() {
  const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
  const persistIID = Components.interfaces.nsIWebBrowserPersist;
  return Components.classes[persistContractID].createInstance(persistIID);
}

function makeURI(aURL, aOriginCharset, aBaseURI) {
  return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
}

function makeFileURI(aFile) {
  return Services.io.newFileURI(aFile);
}

function makeFilePicker() {
  const fpContractID = "@mozilla.org/filepicker;1";
  const fpIID = Components.interfaces.nsIFilePicker;
  return Components.classes[fpContractID].createInstance(fpIID);
}

function getMIMEService() {
  const mimeSvcContractID = "@mozilla.org/mime;1";
  const mimeSvcIID = Components.interfaces.nsIMIMEService;
  const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
  return mimeSvc;
}

// Given aFileName, find the fileName without the extension on the end.
function getFileBaseName(aFileName) {
  // Remove the file extension from aFileName:
  return aFileName.replace(/\.[^.]*$/, "");
}

function getMIMETypeForURI(aURI) {
  try {
    return getMIMEService().getTypeFromURI(aURI);
  } catch (e) {
  }
  return null;
}

function getMIMEInfoForType(aMIMEType, aExtension) {
  if (aMIMEType || aExtension) {
    try {
      return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
    } catch (e) {
    }
  }
  return null;
}

function getDefaultFileName(aDefaultFileName, aURI, aDocument,
                            aContentDisposition) {
  // 1) look for a filename in the content-disposition header, if any
  if (aContentDisposition) {
    const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
    const mhpIID = Components.interfaces.nsIMIMEHeaderParam;
    const mhp = Components.classes[mhpContractID].getService(mhpIID);
    var dummy = { value: null };  // Need an out param...
    var charset = getCharsetforSave(aDocument);

    var fileName = null;
    try {
      fileName = mhp.getParameter(aContentDisposition, "filename", charset,
                                  true, dummy);
    } catch (e) {
      try {
        fileName = mhp.getParameter(aContentDisposition, "name", charset, true,
                                    dummy);
      } catch (e) {
      }
    }
    if (fileName)
      return fileName;
  }

  let docTitle;
  if (aDocument) {
    // If the document looks like HTML or XML, try to use its original title.
    docTitle = validateFileName(aDocument.title).trim();
    if (docTitle) {
      let contentType = aDocument.contentType;
      if (contentType == "application/xhtml+xml" ||
          contentType == "application/xml" ||
          contentType == "image/svg+xml" ||
          contentType == "text/html" ||
          contentType == "text/xml") {
        // 2) Use the document title
        return docTitle;
      }
    }
  }

  try {
    var url = aURI.QueryInterface(Components.interfaces.nsIURL);
    if (url.fileName != "") {
      // 3) Use the actual file name, if present
      var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                   .getService(Components.interfaces.nsITextToSubURI);
      return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName));
    }
  } catch (e) {
    // This is something like a data: and so forth URI... no filename here.
  }

  if (docTitle)
    // 4) Use the document title
    return docTitle;

  if (aDefaultFileName)
    // 5) Use the caller-provided name, if any
    return validateFileName(aDefaultFileName);

  // 6) If this is a directory, use the last directory name
  var path = aURI.path.match(/\/([^\/]+)\/$/);
  if (path && path.length > 1)
    return validateFileName(path[1]);

  try {
    if (aURI.host)
      // 7) Use the host.
      return aURI.host;
  } catch (e) {
    // Some files have no information at all, like Javascript generated pages
  }
  try {
    // 8) Use the default file name
    return ContentAreaUtils.stringBundle.GetStringFromName("DefaultSaveFileName");
  } catch (e) {
    // in case localized string cannot be found
  }
  // 9) If all else fails, use "index"
  return "index";
}

function validateFileName(aFileName) {
  var re = /[\/]+/g;
  if (navigator.appVersion.indexOf("Windows") != -1) {
    re = /[\\\/\|]+/g;
    aFileName = aFileName.replace(/[\"]+/g, "'");
    aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
    aFileName = aFileName.replace(/[\<]+/g, "(");
    aFileName = aFileName.replace(/[\>]+/g, ")");
  } else if (navigator.appVersion.indexOf("Macintosh") != -1)
    re = /[\:\/]+/g;
  else if (navigator.appVersion.indexOf("Android") != -1) {
    // On mobile devices, the filesystem may be very limited in what
    // it considers valid characters. To avoid errors, we sanitize
    // conservatively.
    const dangerousChars = "*?<>|\":/\\[];,+=";
    var processed = "";
    for (var i = 0; i < aFileName.length; i++)
      processed += aFileName.charCodeAt(i) >= 32 &&
                   !(dangerousChars.indexOf(aFileName[i]) >= 0) ? aFileName[i]
                                                                : "_";

    // Last character should not be a space
    processed = processed.trim();

    // If a large part of the filename has been sanitized, then we
    // will use a default filename instead
    if (processed.replace(/_/g, "").length <= processed.length / 2) {
      // We purposefully do not use a localized default filename,
      // which we could have done using
      // ContentAreaUtils.stringBundle.GetStringFromName("DefaultSaveFileName")
      // since it may contain invalid characters.
      var original = processed;
      processed = "download";

      // Preserve a suffix, if there is one
      if (original.indexOf(".") >= 0) {
        var suffix = original.split(".").slice(-1)[0];
        if (suffix && suffix.indexOf("_") < 0)
          processed += "." + suffix;
      }
    }
    return processed;
  }

  return aFileName.replace(re, "_");
}

function getNormalizedLeafName(aFile, aDefaultExtension) {
  if (!aDefaultExtension)
    return aFile;

  if (AppConstants.platform == "win") {
    // Remove trailing dots and spaces on windows
    aFile = aFile.replace(/[\s.]+$/, "");
  }

  // Remove leading dots
  aFile = aFile.replace(/^\.+/, "");

  // Fix up the file name we're saving to to include the default extension
  var i = aFile.lastIndexOf(".");
  if (aFile.substr(i + 1) != aDefaultExtension)
    return aFile + "." + aDefaultExtension;

  return aFile;
}

function getDefaultExtension(aFilename, aURI, aContentType) {
  if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
    return "";   // temporary fix for bug 120327

  // First try the extension from the filename
  const stdURLContractID = "@mozilla.org/network/standard-url;1";
  const stdURLIID = Components.interfaces.nsIURL;
  var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
  url.filePath = aFilename;

  var ext = url.fileExtension;

  // This mirrors some code in nsExternalHelperAppService::DoContent
  // Use the filename first and then the URI if that fails

  var mimeInfo = getMIMEInfoForType(aContentType, ext);

  if (ext && mimeInfo && mimeInfo.extensionExists(ext))
    return ext;

  // Well, that failed.  Now try the extension from the URI
  var urlext;
  try {
    url = aURI.QueryInterface(Components.interfaces.nsIURL);
    urlext = url.fileExtension;
  } catch (e) {
  }

  if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
    return urlext;
  }
  try {
    if (mimeInfo)
      return mimeInfo.primaryExtension;
  } catch (e) {
  }
  // Fall back on the extensions in the filename and URI for lack
  // of anything better.
  return ext || urlext;
}

function GetSaveModeForContentType(aContentType, aDocument) {
  // We can only save a complete page if we have a loaded document,
  // and it's not a CPOW -- nsWebBrowserPersist needs a real document.
  if (!aDocument || Components.utils.isCrossProcessWrapper(aDocument))
    return SAVEMODE_FILEONLY;

  // Find the possible save modes using the provided content type
  var saveMode = SAVEMODE_FILEONLY;
  switch (aContentType) {
  case "text/html":
  case "application/xhtml+xml":
  case "image/svg+xml":
    saveMode |= SAVEMODE_COMPLETE_TEXT;
    // Fall through
  case "text/xml":
  case "application/xml":
    saveMode |= SAVEMODE_COMPLETE_DOM;
    break;
  }

  return saveMode;
}

function getCharsetforSave(aDocument) {
  if (aDocument)
    return aDocument.characterSet;

  if (document.commandDispatcher.focusedWindow)
    return document.commandDispatcher.focusedWindow.document.characterSet;

  return window.content.document.characterSet;
}

/**
 * Open a URL from chrome, determining if we can handle it internally or need to
 *  launch an external application to handle it.
 * @param aURL The URL to be opened
 *
 * WARNING: Please note that openURL() does not perform any content security checks!!!
 */
function openURL(aURL) {
  var uri = makeURI(aURL);

  var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
                              .getService(Components.interfaces.nsIExternalProtocolService);

  if (!protocolSvc.isExposedProtocol(uri.scheme)) {
    // If we're not a browser, use the external protocol service to load the URI.
    protocolSvc.loadUrl(uri);
  } else {
    var recentWindow = Services.wm.getMostRecentWindow("navigator:browser");
    if (recentWindow) {
      recentWindow.openUILinkIn(uri.spec, "tab");
      return;
    }

    var loadgroup = Components.classes["@mozilla.org/network/load-group;1"]
                              .createInstance(Components.interfaces.nsILoadGroup);
    var appstartup = Services.startup;

    var loadListener = {
      onStartRequest: function ll_start(aRequest, aContext) {
        appstartup.enterLastWindowClosingSurvivalArea();
      },
      onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) {
        appstartup.exitLastWindowClosingSurvivalArea();
      },
      QueryInterface: function ll_QI(iid) {
        if (iid.equals(Components.interfaces.nsISupports) ||
            iid.equals(Components.interfaces.nsIRequestObserver) ||
            iid.equals(Components.interfaces.nsISupportsWeakReference))
          return this;
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
    }
    loadgroup.groupObserver = loadListener;

    var uriListener = {
      onStartURIOpen(uri) { return false; },
      doContent(ctype, preferred, request, handler) { return false; },
      isPreferred(ctype, desired) { return false; },
      canHandleContent(ctype, preferred, desired) { return false; },
      loadCookie: null,
      parentContentListener: null,
      getInterface(iid) {
        if (iid.equals(Components.interfaces.nsIURIContentListener))
          return this;
        if (iid.equals(Components.interfaces.nsILoadGroup))
          return loadgroup;
        throw Components.results.NS_ERROR_NO_INTERFACE;
      }
    }

    var channel = NetUtil.newChannel({
      uri,
      loadUsingSystemPrincipal: true
    });

    var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
                              .getService(Components.interfaces.nsIURILoader);
    uriLoader.openURI(channel,
                      Components.interfaces.nsIURILoader.IS_CONTENT_PREFERRED,
                      uriListener);
  }
}
PK
!<!5%%(chrome/toolkit/content/global/crashes.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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, utils: Cu, interfaces: Ci } = Components;

var reportURL;

Cu.import("resource://gre/modules/CrashReports.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
  "resource://gre/modules/CrashSubmit.jsm");

const buildID = Services.appinfo.appBuildID;

function submitPendingReport(event) {
  let link = event.target;
  let id = link.firstChild.textContent;
  link.className = "submitting";
  CrashSubmit.submit(id, { noThrottle: true }).then(
    (remoteCrashID) => {
      link.className = "";
      // Reset the link to point at our new crash report. This way, if the
      // user clicks "Back", the link will be correct.
      link.firstChild.textContent = remoteCrashID;
      link.setAttribute("id", remoteCrashID);
      link.removeEventListener("click", submitPendingReport, true);

      if (reportURL) {
        link.setAttribute("href", reportURL + remoteCrashID);
        // redirect the user to their brand new crash report
        window.location.href = reportURL + remoteCrashID;
      }
    },
    () => {
      // XXX: do something more useful here
      link.className = "";

      // Dispatch an event, useful for testing
      let event = document.createEvent("Events");
      event.initEvent("CrashSubmitFailed", true, false);
      document.dispatchEvent(event);
    });
  event.preventDefault();
  return false;
}

function populateReportList() {

  var prefService = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefBranch);

  try {
    reportURL = prefService.getCharPref("breakpad.reportURL");
    // Ignore any non http/https urls
    if (!/^https?:/i.test(reportURL))
      reportURL = null;
  } catch (e) { }
  if (!reportURL) {
    document.getElementById("clear-reports").style.display = "none";
    document.getElementById("reportList").style.display = "none";
    document.getElementById("noConfig").style.display = "block";
    return;
  }
  let reports = CrashReports.getReports();

  if (reports.length == 0) {
    document.getElementById("clear-reports").style.display = "none";
    document.getElementById("reportList").style.display = "none";
    document.getElementById("noReports").style.display = "block";
    return;
  }

  var dateFormatter;
  var timeFormatter;
  try {
    dateFormatter = Services.intl.createDateTimeFormat(undefined, { dateStyle: "short" });
    timeFormatter = Services.intl.createDateTimeFormat(undefined, { timeStyle: "short" });
  } catch (e) {
    // XXX Fallback to be removed once bug 1215247 is complete
    // and the Intl API is available on all platforms.
    dateFormatter = {
      format(date) {
        return date.toLocaleDateString();
      }
    }
    timeFormatter = {
      format(date) {
        return date.toLocaleTimeString();
      }
    }
  }
  var ios = Cc["@mozilla.org/network/io-service;1"].
            getService(Ci.nsIIOService);
  var reportURI = ios.newURI(reportURL);
  // resolving this URI relative to /report/index
  var aboutThrottling = ios.newURI("../../about/throttling", null, reportURI);

  for (var i = 0; i < reports.length; i++) {
    var row = document.createElement("tr");
    var cell = document.createElement("td");
    row.appendChild(cell);
    var link = document.createElement("a");
    if (reports[i].pending) {
      link.setAttribute("href", aboutThrottling.spec);
      link.addEventListener("click", submitPendingReport, true);
    } else {
      link.setAttribute("href", reportURL + reports[i].id);
    }
    link.setAttribute("id", reports[i].id);
    link.classList.add("crashReport");
    link.appendChild(document.createTextNode(reports[i].id));
    cell.appendChild(link);

    var date = new Date(reports[i].date);
    cell = document.createElement("td");
    cell.appendChild(document.createTextNode(dateFormatter.format(date)));
    row.appendChild(cell);
    cell = document.createElement("td");
    cell.appendChild(document.createTextNode(timeFormatter.format(date)));
    row.appendChild(cell);
    if (reports[i].pending) {
      document.getElementById("unsubmitted").appendChild(row);
    } else {
      document.getElementById("submitted").appendChild(row);
    }
  }
}

var clearReports = async function() {
  let bundle = Services.strings.createBundle("chrome://global/locale/crashes.properties");

  if (!Services.
         prompt.confirm(window,
                        bundle.GetStringFromName("deleteconfirm.title"),
                        bundle.GetStringFromName("deleteconfirm.description"))) {
    return;
  }

  let cleanupFolder = async function(path, filter) {
    let iterator = new OS.File.DirectoryIterator(path);
    try {
      await iterator.forEach(async function(aEntry) {
        if (!filter || (await filter(aEntry))) {
          await OS.File.remove(aEntry.path);
        }
      });
    } catch (e) {
      if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile) {
        throw e;
      }
    } finally {
      iterator.close();
    }
  };

  await cleanupFolder(CrashReports.submittedDir.path, function(aEntry) {
    return aEntry.name.startsWith("bp-") && aEntry.name.endsWith(".txt");
  });

  let oneYearAgo = Date.now() - 31586000000;
  await cleanupFolder(CrashReports.reportsDir.path, async function(aEntry) {
    if (!aEntry.name.startsWith("InstallTime") ||
        aEntry.name == "InstallTime" + buildID) {
      return false;
    }

    let date = aEntry.winLastWriteDate;
    if (!date) {
      let stat = await OS.File.stat(aEntry.path);
      date = stat.lastModificationDate;
    }

    return (date < oneYearAgo);
  });

  await cleanupFolder(CrashReports.pendingDir.path);

  document.getElementById("clear-reports").style.display = "none";
  document.getElementById("reportList").style.display = "none";
  document.getElementById("noReports").style.display = "block";
};
PK
!<+chrome/toolkit/content/global/crashes.xhtml<?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.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
[
  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
  <!ENTITY % crashesDTD SYSTEM "chrome://global/locale/crashes.dtd">
  %globalDTD;
  %crashesDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
:root {
  font-family: sans-serif;
  margin: 40px auto;
  min-width: 30em;
  max-width: 60em;
}
table {
  clear: both;
  width: 90%;
  margin: 0 auto;
  padding-bottom: 2em;
}
th {
  font-size: 130%;
  text-align: left;
  white-space: nowrap;
}
th[chromedir="rtl"] {
  text-align: right;
}
/* name */
th:first-child {
  padding-inline-end: 2em;
}
/* submitted */
th:last-child {
  text-align: center;
}
:link, :visited {
  display: block;
  min-height: 17px;
}
/* date */
td:first-child + td {
  width: 0;
  padding-inline-start: 1em;
  padding-inline-end: .5em;
  white-space: nowrap;
}
/* time */
td:last-child {
  width: 0;
  padding-inline-start: .5em;
  white-space: nowrap;
}

#clear-reports {
  float: right;
}
#clear-reports[chromedir="rtl"] {
  float: left;
}

.submitting {
  background-image: url(chrome://global/skin/icons/loading.png);
  background-repeat: no-repeat;
  background-position: right;
  background-size: 16px;
}

@media (min-resolution: 1.1dppx) {
  .submitting {
    background-image: url(chrome://global/skin/icons/loading@2x.png);
  }
}
</style>
<link rel="stylesheet" media="screen, projection" type="text/css"
      href="chrome://global/skin/in-content/common.css"/>
<script type="application/javascript" src="chrome://global/content/crashes.js"/>

<title>&crashReports.title;</title>
</head><body onload="populateReportList()" dir="&locale.dir;">
<button chromedir="&locale.dir;" id="clear-reports"
        onclick="clearReports().then(null, Cu.reportError)">&clearAllReports.label;</button>
<div id="reportList">
  <div id="reportListUnsubmitted">
    <h1>&crashesUnsubmitted.label;</h1>
    <table>
      <thead>
        <tr>
          <th chromedir="&locale.dir;">&id.heading;</th>
          <th chromedir="&locale.dir;" colspan="2">&dateCrashed.heading;</th>
        </tr>
      </thead>
      <tbody id="unsubmitted">
      </tbody>
    </table>
  </div>
  <div id="reportListSubmitted">
    <h1>&crashesSubmitted.label;</h1>
    <table>
      <thead>
        <tr>
          <th chromedir="&locale.dir;">&id.heading;</th>
          <th chromedir="&locale.dir;" colspan="2">&dateSubmitted.heading;</th>
        </tr>
      </thead>
      <tbody id="submitted">
      </tbody>
    </table>
  </div>
</div>
<p id="noReports" style="display: none">&noReports.label;</p>
<p id="noConfig" style="display: none">&noConfig.label;</p>
</body>
</html>
PK
!<-(2chrome/toolkit/content/global/customizeToolbar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

#palette-box {
  overflow: auto;
  display: block;
  min-height: 3em;
}

#palette-box > toolbarpaletteitem {
  width: 110px;
  height: 94px;
  overflow: hidden;
  display: inline-block;
}

.toolbarpaletteitem-box {
  -moz-box-pack: center;
  -moz-box-flex: 1;
  width: 110px;
  max-width: 110px;
}

toolbarpaletteitem > label {
  text-align: center;
}

#main-box > box {
  overflow: hidden; 
}

/* Hide the toolbarbutton label because we replicate it on the wrapper */
.toolbarbutton-text {
  display: none;
}
PK
!<rbt`t`1chrome/toolkit/content/global/customizeToolbar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 gToolboxDocument = null;
var gToolbox = null;
var gCurrentDragOverItem = null;
var gToolboxChanged = false;
var gToolboxSheet = false;
var gPaletteBox = null;

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AppConstants.jsm");

function onLoad() {
  if ("arguments" in window && window.arguments[0]) {
    InitWithToolbox(window.arguments[0]);
    repositionDialog(window);
  } else if (window.frameElement &&
           "toolbox" in window.frameElement) {
    gToolboxSheet = true;
    InitWithToolbox(window.frameElement.toolbox);
    repositionDialog(window.frameElement.panel);
  }
}

function InitWithToolbox(aToolbox) {
  gToolbox = aToolbox;
  dispatchCustomizationEvent("beforecustomization");
  gToolboxDocument = gToolbox.ownerDocument;
  gToolbox.customizing = true;
  forEachCustomizableToolbar(function(toolbar) {
    toolbar.setAttribute("customizing", "true");
  });
  gPaletteBox = document.getElementById("palette-box");

  var elts = getRootElements();
  for (let i = 0; i < elts.length; i++) {
    elts[i].addEventListener("dragstart", onToolbarDragStart, true);
    elts[i].addEventListener("dragover", onToolbarDragOver, true);
    elts[i].addEventListener("dragexit", onToolbarDragExit, true);
    elts[i].addEventListener("drop", onToolbarDrop, true);
  }

  initDialog();
}

function onClose() {
  if (!gToolboxSheet)
    window.close();
  else
    finishToolbarCustomization();
}

function onUnload() {
  if (!gToolboxSheet)
    finishToolbarCustomization();
}

function finishToolbarCustomization() {
  removeToolboxListeners();
  unwrapToolbarItems();
  persistCurrentSets();
  gToolbox.customizing = false;
  forEachCustomizableToolbar(function(toolbar) {
    toolbar.removeAttribute("customizing");
  });

  notifyParentComplete();
}

function initDialog() {
  if (!gToolbox.toolbarset) {
    document.getElementById("newtoolbar").hidden = true;
  }

  var mode = gToolbox.getAttribute("mode");
  document.getElementById("modelist").value = mode;
  var smallIconsCheckbox = document.getElementById("smallicons");
  smallIconsCheckbox.checked = gToolbox.getAttribute("iconsize") == "small";
  if (mode == "text")
    smallIconsCheckbox.disabled = true;

  // Build up the palette of other items.
  buildPalette();

  // Wrap all the items on the toolbar in toolbarpaletteitems.
  wrapToolbarItems();
}

function repositionDialog(aWindow) {
  // Position the dialog touching the bottom of the toolbox and centered with
  // it.
  if (!aWindow)
    return;

  var width;
  if (aWindow != window)
    width = aWindow.getBoundingClientRect().width;
  else if (document.documentElement.hasAttribute("width"))
    width = document.documentElement.getAttribute("width");
  else
    width = parseInt(document.documentElement.style.width);
  var screenX = gToolbox.boxObject.screenX
                + ((gToolbox.boxObject.width - width) / 2);
  var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height;

  aWindow.moveTo(screenX, screenY);
}

function removeToolboxListeners() {
  var elts = getRootElements();
  for (let i = 0; i < elts.length; i++) {
    elts[i].removeEventListener("dragstart", onToolbarDragStart, true);
    elts[i].removeEventListener("dragover", onToolbarDragOver, true);
    elts[i].removeEventListener("dragexit", onToolbarDragExit, true);
    elts[i].removeEventListener("drop", onToolbarDrop, true);
  }
}

/**
 * Invoke a callback on the toolbox to notify it that the dialog is done
 * and going away.
 */
function notifyParentComplete() {
  if ("customizeDone" in gToolbox)
    gToolbox.customizeDone(gToolboxChanged);
  dispatchCustomizationEvent("aftercustomization");
}

function toolboxChanged(aType) {
  gToolboxChanged = true;
  if ("customizeChange" in gToolbox)
    gToolbox.customizeChange(aType);
  dispatchCustomizationEvent("customizationchange");
}

function dispatchCustomizationEvent(aEventName) {
  var evt = document.createEvent("Events");
  evt.initEvent(aEventName, true, true);
  gToolbox.dispatchEvent(evt);
}

/**
 * Persist the current set of buttons in all customizable toolbars to
 * localstore.
 */
function persistCurrentSets() {
  if (!gToolboxChanged || gToolboxDocument.defaultView.closed)
    return;

  var customCount = 0;
  forEachCustomizableToolbar(function(toolbar) {
    // Calculate currentset and store it in the attribute.
    var currentSet = toolbar.currentSet;
    toolbar.setAttribute("currentset", currentSet);

    var customIndex = toolbar.hasAttribute("customindex");
    if (customIndex) {
      if (!toolbar.hasChildNodes()) {
        // Remove custom toolbars whose contents have been removed.
        gToolbox.removeChild(toolbar);
      } else if (gToolbox.toolbarset) {
        // Persist custom toolbar info on the <toolbarset/>
        gToolbox.toolbarset.setAttribute("toolbar" + (++customCount),
                                         toolbar.toolbarName + ":" + currentSet);
        gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar" + customCount);
      }
    }

    if (!customIndex) {
      // Persist the currentset attribute directly on hardcoded toolbars.
      gToolboxDocument.persist(toolbar.id, "currentset");
    }
  });

  // Remove toolbarX attributes for removed toolbars.
  while (gToolbox.toolbarset && gToolbox.toolbarset.hasAttribute("toolbar" + (++customCount))) {
    gToolbox.toolbarset.removeAttribute("toolbar" + customCount);
    gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar" + customCount);
  }
}

/**
 * Wraps all items in all customizable toolbars in a toolbox.
 */
function wrapToolbarItems() {
  forEachCustomizableToolbar(function(toolbar) {
    Array.forEach(toolbar.childNodes, function(item) {
      if (AppConstants.platform == "macosx") {
        if (item.firstChild && item.firstChild.localName == "menubar")
          return;
      }
      if (isToolbarItem(item)) {
        let wrapper = wrapToolbarItem(item);
        cleanupItemForToolbar(item, wrapper);
      }
    });
  });
}

function getRootElements() {
  return [gToolbox].concat(gToolbox.externalToolbars);
}

/**
 * Unwraps all items in all customizable toolbars in a toolbox.
 */
function unwrapToolbarItems() {
  let elts = getRootElements();
  for (let i = 0; i < elts.length; i++) {
    let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem");
    let paletteItem;
    while ((paletteItem = paletteItems.item(0)) != null) {
      let toolbarItem = paletteItem.firstChild;
      restoreItemForToolbar(toolbarItem, paletteItem);
      paletteItem.parentNode.replaceChild(toolbarItem, paletteItem);
    }
  }
}

/**
 * Creates a wrapper that can be used to contain a toolbaritem and prevent
 * it from receiving UI events.
 */
function createWrapper(aId, aDocument) {
  var wrapper = aDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                         "toolbarpaletteitem");

  wrapper.id = "wrapper-" + aId;
  return wrapper;
}

/**
 * Wraps an item that has been cloned from a template and adds
 * it to the end of the palette.
 */
function wrapPaletteItem(aPaletteItem) {
  var wrapper = createWrapper(aPaletteItem.id, document);

  wrapper.appendChild(aPaletteItem);

  // XXX We need to call this AFTER the palette item has been appended
  // to the wrapper or else we crash dropping certain buttons on the
  // palette due to removal of the command and disabled attributes - JRH
  cleanUpItemForPalette(aPaletteItem, wrapper);

  gPaletteBox.appendChild(wrapper);
}

/**
 * Wraps an item that is currently on a toolbar and replaces the item
 * with the wrapper. This is not used when dropping items from the palette,
 * only when first starting the dialog and wrapping everything on the toolbars.
 */
function wrapToolbarItem(aToolbarItem) {
  var wrapper = createWrapper(aToolbarItem.id, gToolboxDocument);

  wrapper.flex = aToolbarItem.flex;

  aToolbarItem.parentNode.replaceChild(wrapper, aToolbarItem);

  wrapper.appendChild(aToolbarItem);

  return wrapper;
}

/**
 * Get the list of ids for the current set of items on each toolbar.
 */
function getCurrentItemIds() {
  var currentItems = {};
  forEachCustomizableToolbar(function(toolbar) {
    var child = toolbar.firstChild;
    while (child) {
      if (isToolbarItem(child))
        currentItems[child.id] = 1;
      child = child.nextSibling;
    }
  });
  return currentItems;
}

/**
 * Builds the palette of draggable items that are not yet in a toolbar.
 */
function buildPalette() {
  // Empty the palette first.
  while (gPaletteBox.lastChild)
    gPaletteBox.removeChild(gPaletteBox.lastChild);

  // Add the toolbar separator item.
  var templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                              "toolbarseparator");
  templateNode.id = "separator";
  wrapPaletteItem(templateNode);

  // Add the toolbar spring item.
  templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                              "toolbarspring");
  templateNode.id = "spring";
  templateNode.flex = 1;
  wrapPaletteItem(templateNode);

  // Add the toolbar spacer item.
  templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                              "toolbarspacer");
  templateNode.id = "spacer";
  templateNode.flex = 1;
  wrapPaletteItem(templateNode);

  var currentItems = getCurrentItemIds();
  templateNode = gToolbox.palette.firstChild;
  while (templateNode) {
    // Check if the item is already in a toolbar before adding it to the palette.
    if (!(templateNode.id in currentItems)) {
      var paletteItem = document.importNode(templateNode, true);
      wrapPaletteItem(paletteItem);
    }

    templateNode = templateNode.nextSibling;
  }
}

/**
 * Makes sure that an item that has been cloned from a template
 * is stripped of any attributes that may adversely affect its
 * appearance in the palette.
 */
function cleanUpItemForPalette(aItem, aWrapper) {
  aWrapper.setAttribute("place", "palette");
  setWrapperType(aItem, aWrapper);

  if (aItem.hasAttribute("title"))
    aWrapper.setAttribute("title", aItem.getAttribute("title"));
  else if (aItem.hasAttribute("label"))
    aWrapper.setAttribute("title", aItem.getAttribute("label"));
  else if (isSpecialItem(aItem)) {
    var stringBundle = document.getElementById("stringBundle");
    // Remove the common "toolbar" prefix to generate the string name.
    var title = stringBundle.getString(aItem.localName.slice(7) + "Title");
    aWrapper.setAttribute("title", title);
  }
  aWrapper.setAttribute("tooltiptext", aWrapper.getAttribute("title"));

  // Remove attributes that screw up our appearance.
  aItem.removeAttribute("command");
  aItem.removeAttribute("observes");
  aItem.removeAttribute("type");
  aItem.removeAttribute("width");

  Array.forEach(aWrapper.querySelectorAll("[disabled]"), function(aNode) {
    aNode.removeAttribute("disabled");
  });
}

/**
 * Makes sure that an item that has been cloned from a template
 * is stripped of all properties that may adversely affect its
 * appearance in the toolbar.  Store critical properties on the
 * wrapper so they can be put back on the item when we're done.
 */
function cleanupItemForToolbar(aItem, aWrapper) {
  setWrapperType(aItem, aWrapper);
  aWrapper.setAttribute("place", "toolbar");

  if (aItem.hasAttribute("command")) {
    aWrapper.setAttribute("itemcommand", aItem.getAttribute("command"));
    aItem.removeAttribute("command");
  }

  if (aItem.checked) {
    aWrapper.setAttribute("itemchecked", "true");
    aItem.checked = false;
  }

  if (aItem.disabled) {
    aWrapper.setAttribute("itemdisabled", "true");
    aItem.disabled = false;
  }
}

/**
 * Restore all the properties that we stripped off above.
 */
function restoreItemForToolbar(aItem, aWrapper) {
  if (aWrapper.hasAttribute("itemdisabled"))
    aItem.disabled = true;

  if (aWrapper.hasAttribute("itemchecked"))
    aItem.checked = true;

  if (aWrapper.hasAttribute("itemcommand")) {
    let commandID = aWrapper.getAttribute("itemcommand");
    aItem.setAttribute("command", commandID);

    // XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
    let command = gToolboxDocument.getElementById(commandID);
    if (command && command.hasAttribute("disabled"))
      aItem.setAttribute("disabled", command.getAttribute("disabled"));
  }
}

function setWrapperType(aItem, aWrapper) {
  if (aItem.localName == "toolbarseparator") {
    aWrapper.setAttribute("type", "separator");
  } else if (aItem.localName == "toolbarspring") {
    aWrapper.setAttribute("type", "spring");
  } else if (aItem.localName == "toolbarspacer") {
    aWrapper.setAttribute("type", "spacer");
  } else if (aItem.localName == "toolbaritem" && aItem.firstChild) {
    aWrapper.setAttribute("type", aItem.firstChild.localName);
  }
}

function setDragActive(aItem, aValue) {
  var node = aItem;
  var direction = window.getComputedStyle(aItem).direction;
  var value = direction == "ltr" ? "left" : "right";
  if (aItem.localName == "toolbar") {
    node = aItem.lastChild;
    value = direction == "ltr" ? "right" : "left";
  }

  if (!node)
    return;

  if (aValue) {
    if (!node.hasAttribute("dragover"))
      node.setAttribute("dragover", value);
  } else {
    node.removeAttribute("dragover");
  }
}

function addNewToolbar() {
  var promptService = Services.prompt;
  var stringBundle = document.getElementById("stringBundle");
  var message = stringBundle.getString("enterToolbarName");
  var title = stringBundle.getString("enterToolbarTitle");

  var name = {};

  // Quitting from the toolbar dialog while the new toolbar prompt is up
  // can cause things to become unresponsive on the Mac. Until dialog modality
  // is fixed (395465), disable the "Done" button explicitly.
  var doneButton = document.getElementById("donebutton");
  doneButton.disabled = true;

  while (true) {

    if (!promptService.prompt(window, title, message, name, null, {})) {
      doneButton.disabled = false;
      return;
    }

    if (!name.value) {
      message = stringBundle.getFormattedString("enterToolbarBlank", [name.value]);
      continue;
    }

    var dupeFound = false;

     // Check for an existing toolbar with the same display name
    for (let i = 0; i < gToolbox.childNodes.length; ++i) {
      var toolbar = gToolbox.childNodes[i];
      var toolbarName = toolbar.getAttribute("toolbarname");

      if (toolbarName == name.value &&
          toolbar.getAttribute("type") != "menubar" &&
          toolbar.nodeName == "toolbar") {
        dupeFound = true;
        break;
      }
    }

    if (!dupeFound)
      break;

    message = stringBundle.getFormattedString("enterToolbarDup", [name.value]);
  }

  gToolbox.appendCustomToolbar(name.value, "");

  toolboxChanged();

  doneButton.disabled = false;
}

/**
 * Restore the default set of buttons to fixed toolbars,
 * remove all custom toolbars, and rebuild the palette.
 */
function restoreDefaultSet() {
  // Unwrap the items on the toolbar.
  unwrapToolbarItems();

  // Remove all of the customized toolbars.
  var child = gToolbox.lastChild;
  while (child) {
    if (child.hasAttribute("customindex")) {
      var thisChild = child;
      child = child.previousSibling;
      thisChild.currentSet = "__empty";
      gToolbox.removeChild(thisChild);
    } else {
      child = child.previousSibling;
    }
  }

  // Restore the defaultset for fixed toolbars.
  forEachCustomizableToolbar(function(toolbar) {
    var defaultSet = toolbar.getAttribute("defaultset");
    if (defaultSet)
      toolbar.currentSet = defaultSet;
  });

  // Restore the default icon size and mode.
  document.getElementById("smallicons").checked = (updateIconSize() == "small");
  document.getElementById("modelist").value = updateToolbarMode();

  // Now rebuild the palette.
  buildPalette();

  // Now re-wrap the items on the toolbar.
  wrapToolbarItems();

  toolboxChanged("reset");
}

function updateIconSize(aSize) {
  return updateToolboxProperty("iconsize", aSize, "large");
}

function updateToolbarMode(aModeValue) {
  var mode = updateToolboxProperty("mode", aModeValue, "icons");

  var iconSizeCheckbox = document.getElementById("smallicons");
  iconSizeCheckbox.disabled = mode == "text";

  return mode;
}

function updateToolboxProperty(aProp, aValue, aToolkitDefault) {
  var toolboxDefault = gToolbox.getAttribute("default" + aProp) ||
                       aToolkitDefault;

  gToolbox.setAttribute(aProp, aValue || toolboxDefault);
  gToolboxDocument.persist(gToolbox.id, aProp);

  forEachCustomizableToolbar(function(toolbar) {
    var toolbarDefault = toolbar.getAttribute("default" + aProp) ||
                         toolboxDefault;
    if (toolbar.getAttribute("lock" + aProp) == "true" &&
        toolbar.getAttribute(aProp) == toolbarDefault)
      return;

    toolbar.setAttribute(aProp, aValue || toolbarDefault);
    gToolboxDocument.persist(toolbar.id, aProp);
  });

  toolboxChanged(aProp);

  return aValue || toolboxDefault;
}

function forEachCustomizableToolbar(callback) {
  Array.filter(gToolbox.childNodes, isCustomizableToolbar).forEach(callback);
  Array.filter(gToolbox.externalToolbars, isCustomizableToolbar).forEach(callback);
}

function isCustomizableToolbar(aElt) {
  return aElt.localName == "toolbar" &&
         aElt.getAttribute("customizable") == "true";
}

function isSpecialItem(aElt) {
  return aElt.localName == "toolbarseparator" ||
         aElt.localName == "toolbarspring" ||
         aElt.localName == "toolbarspacer";
}

function isToolbarItem(aElt) {
  return aElt.localName == "toolbarbutton" ||
         aElt.localName == "toolbaritem" ||
         aElt.localName == "toolbarseparator" ||
         aElt.localName == "toolbarspring" ||
         aElt.localName == "toolbarspacer";
}

// Drag and Drop observers

function onToolbarDragExit(aEvent) {
  if (isUnwantedDragEvent(aEvent)) {
    return;
  }

  if (gCurrentDragOverItem)
    setDragActive(gCurrentDragOverItem, false);
}

function onToolbarDragStart(aEvent) {
  var item = aEvent.target;
  while (item && item.localName != "toolbarpaletteitem") {
    if (item.localName == "toolbar")
      return;
    item = item.parentNode;
  }

  item.setAttribute("dragactive", "true");

  var dt = aEvent.dataTransfer;
  var documentId = gToolboxDocument.documentElement.id;
  dt.setData("text/toolbarwrapper-id/" + documentId, item.firstChild.id);
  dt.effectAllowed = "move";
}

function onToolbarDragOver(aEvent) {
  if (isUnwantedDragEvent(aEvent)) {
    return;
  }

  var documentId = gToolboxDocument.documentElement.id;
  if (!aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase()))
    return;

  var toolbar = aEvent.target;
  var dropTarget = aEvent.target;
  while (toolbar && toolbar.localName != "toolbar") {
    dropTarget = toolbar;
    toolbar = toolbar.parentNode;
  }

  // Make sure we are dragging over a customizable toolbar.
  if (!toolbar || !isCustomizableToolbar(toolbar)) {
    gCurrentDragOverItem = null;
    return;
  }

  var previousDragItem = gCurrentDragOverItem;

  if (dropTarget.localName == "toolbar") {
    gCurrentDragOverItem = dropTarget;
  } else {
    gCurrentDragOverItem = null;

    var direction = window.getComputedStyle(dropTarget.parentNode).direction;
    var dropTargetCenter = dropTarget.boxObject.x + (dropTarget.boxObject.width / 2);
    var dragAfter;
    if (direction == "ltr")
      dragAfter = aEvent.clientX > dropTargetCenter;
    else
      dragAfter = aEvent.clientX < dropTargetCenter;

    if (dragAfter) {
      gCurrentDragOverItem = dropTarget.nextSibling;
      if (!gCurrentDragOverItem)
        gCurrentDragOverItem = toolbar;
    } else
      gCurrentDragOverItem = dropTarget;
  }

  if (previousDragItem && gCurrentDragOverItem != previousDragItem) {
    setDragActive(previousDragItem, false);
  }

  setDragActive(gCurrentDragOverItem, true);

  aEvent.preventDefault();
  aEvent.stopPropagation();
}

function onToolbarDrop(aEvent) {
  if (isUnwantedDragEvent(aEvent)) {
    return;
  }

  if (!gCurrentDragOverItem)
    return;

  setDragActive(gCurrentDragOverItem, false);

  var documentId = gToolboxDocument.documentElement.id;
  var draggedItemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId);
  if (gCurrentDragOverItem.id == draggedItemId)
    return;

  var toolbar = aEvent.target;
  while (toolbar.localName != "toolbar")
    toolbar = toolbar.parentNode;

  var draggedPaletteWrapper = document.getElementById("wrapper-" + draggedItemId);
  if (!draggedPaletteWrapper) {
    // The wrapper has been dragged from the toolbar.
    // Get the wrapper from the toolbar document and make sure that
    // it isn't being dropped on itself.
    let wrapper = gToolboxDocument.getElementById("wrapper-" + draggedItemId);
    if (wrapper == gCurrentDragOverItem)
       return;

    // Don't allow non-removable kids (e.g., the menubar) to move.
    if (wrapper.firstChild.getAttribute("removable") != "true")
      return;

    // Remove the item from its place in the toolbar.
    wrapper.remove();

    // Determine which toolbar we are dropping on.
    var dropToolbar = null;
    if (gCurrentDragOverItem.localName == "toolbar")
      dropToolbar = gCurrentDragOverItem;
    else
      dropToolbar = gCurrentDragOverItem.parentNode;

    // Insert the item into the toolbar.
    if (gCurrentDragOverItem != dropToolbar)
      dropToolbar.insertBefore(wrapper, gCurrentDragOverItem);
    else
      dropToolbar.appendChild(wrapper);
  } else {
    // The item has been dragged from the palette

    // Create a new wrapper for the item. We don't know the id yet.
    let wrapper = createWrapper("", gToolboxDocument);

    // Ask the toolbar to clone the item's template, place it inside the wrapper, and insert it in the toolbar.
    var newItem = toolbar.insertItem(draggedItemId, gCurrentDragOverItem == toolbar ? null : gCurrentDragOverItem, wrapper);

    // Prepare the item and wrapper to look good on the toolbar.
    cleanupItemForToolbar(newItem, wrapper);
    wrapper.id = "wrapper-" + newItem.id;
    wrapper.flex = newItem.flex;

    // Remove the wrapper from the palette.
    if (draggedItemId != "separator" &&
        draggedItemId != "spring" &&
        draggedItemId != "spacer")
      gPaletteBox.removeChild(draggedPaletteWrapper);
  }

  gCurrentDragOverItem = null;

  toolboxChanged();
}

function onPaletteDragOver(aEvent) {
  if (isUnwantedDragEvent(aEvent)) {
    return;
  }
  var documentId = gToolboxDocument.documentElement.id;
  if (aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase()))
    aEvent.preventDefault();
}

function onPaletteDrop(aEvent) {
  if (isUnwantedDragEvent(aEvent)) {
    return;
  }
  var documentId = gToolboxDocument.documentElement.id;
  var itemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId);

  var wrapper = gToolboxDocument.getElementById("wrapper-" + itemId);
  if (wrapper) {
    // Don't allow non-removable kids (e.g., the menubar) to move.
    if (wrapper.firstChild.getAttribute("removable") != "true")
      return;

    var wrapperType = wrapper.getAttribute("type");
    if (wrapperType != "separator" &&
        wrapperType != "spacer" &&
        wrapperType != "spring") {
      restoreItemForToolbar(wrapper.firstChild, wrapper);
      wrapPaletteItem(document.importNode(wrapper.firstChild, true));
      gToolbox.palette.appendChild(wrapper.firstChild);
    }

    // The item was dragged out of the toolbar.
    wrapper.remove();
  }

  toolboxChanged();
}


function isUnwantedDragEvent(aEvent) {
  try {
    if (Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")) {
      return false;
    }
  } catch (ex) {}

  /* Discard drag events that originated from a separate window to
     prevent content->chrome privilege escalations. */
  let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
  // mozSourceNode is null in the dragStart event handler or if
  // the drag event originated in an external application.
  if (!mozSourceNode) {
    return true;
  }
  let sourceWindow = mozSourceNode.ownerGlobal;
  return sourceWindow != window && sourceWindow != gToolboxDocument.defaultView;
}

PK
!<ڽW		2chrome/toolkit/content/global/customizeToolbar.xul<?xml version="1.0"?> 

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE dialog [
<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
  %customizeToolbarDTD;
]>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/customizeToolbar.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/customizeToolbar.css" type="text/css"?>

<window id="CustomizeToolbarWindow"
        title="&dialog.title;"
        onload="onLoad();"
        onunload="onUnload();"
        style="&dialog.dimensions;"
        persist="width height"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script type="application/javascript" src="chrome://global/content/customizeToolbar.js"/>

<stringbundle id="stringBundle" src="chrome://global/locale/customizeToolbar.properties"/>

<keyset id="CustomizeToolbarKeyset">
  <key id="cmd_close1" keycode="VK_ESCAPE" oncommand="onClose();"/>
  <key id="cmd_close2" keycode="VK_RETURN" oncommand="onClose();"/>
</keyset>

<vbox id="main-box" flex="1">
  <description id="instructions">
  &instructions.description;
  </description>

  <vbox flex="1" id="palette-box"
        ondragstart="onToolbarDragStart(event)"
        ondragover="onPaletteDragOver(event)"
        ondrop="onPaletteDrop(event)"/>

  <box align="center">
    <label value="&show.label;"/>
    <menulist id="modelist" value="icons" oncommand="updateToolbarMode(this.value);">
      <menupopup id="modelistpopup">
        <menuitem id="modefull" value="full" label="&iconsAndText.label;"/>
        <menuitem id="modeicons" value="icons" label="&icons.label;"/>
        <menuitem id="modetext" value="text" label="&text.label;"/>
      </menupopup>
    </menulist>

    <checkbox id="smallicons" oncommand="updateIconSize(this.checked ? 'small' : 'large');" label="&useSmallIcons.label;"/>

    <button id="newtoolbar" label="&addNewToolbar.label;" oncommand="addNewToolbar();" icon="add"/>
    <button id="restoreDefault" label="&restoreDefaultSet.label;" oncommand="restoreDefaultSet();" icon="revert"/>
  </box>

  <separator class="groove"/>

  <hbox align="center" pack="end">
    <button id="donebutton" label="&saveChanges.label;" oncommand="onClose();"
            default="true" icon="close"/>
  </hbox>
</vbox>

</window>
PK
!<0ܣY
Y
.chrome/toolkit/content/global/datepicker.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html [
  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
  %htmlDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
  <title>Date Picker</title>
  <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
  <script type="application/javascript" src="chrome://global/content/bindings/datekeeper.js"></script>
  <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
  <script type="application/javascript" src="chrome://global/content/bindings/calendar.js"></script>
  <script type="application/javascript" src="chrome://global/content/bindings/datepicker.js"></script>
</head>
<body>
  <div id="date-picker">
    <div class="calendar-container">
      <div class="nav">
        <button class="prev"/>
        <button class="next"/>
      </div>
      <div class="week-header"></div>
      <div class="days-viewport">
        <div class="days-view"></div>
      </div>
    </div>
    <div class="month-year-container">
      <button class="month-year"/>
    </div>
    <div class="month-year-view"></div>
  </div>
  <template id="spinner-template">
    <div class="spinner-container">
      <button class="up"/>
      <div class="spinner"></div>
      <button class="down"/>
    </div>
  </template>
  <script type="application/javascript">
  /* import-globals-from widgets/datepicker.js */
  // We need to hide the scroll bar but maintain its scrolling
  // capability, so using |overflow: hidden| is not an option.
  // Instead, we are inserting a user agent stylesheet that is
  // capable of selecting scrollbars, and do |display: none|.
  var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                  getInterface(Components.interfaces.nsIDOMWindowUtils);
  domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET);
  // Create a DatePicker instance and prepare to be
  // initialized by the "DatePickerInit" event from datetimepopup.xml
  const root = document.getElementById("date-picker");
  new DatePicker({
    monthYear: root.querySelector(".month-year"),
    monthYearView: root.querySelector(".month-year-view"),
    buttonPrev: root.querySelector(".prev"),
    buttonNext: root.querySelector(".next"),
    weekHeader: root.querySelector(".week-header"),
    daysView: root.querySelector(".days-view")
  });
  </script>
</body>
</html>
PK
!<ճ4chrome/toolkit/content/global/directionDetector.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 html>
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="chrome://global/locale/intl.css">
  </head>
  <body>
    <window id="target" style="display: none;"></window>
  </body>
</html>
PK
!<$Zdd0chrome/toolkit/content/global/editMenuOverlay.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/. */

// update menu items that rely on focus or on the current selection
function goUpdateGlobalEditMenuItems() {
  // Don't bother updating the edit commands if they aren't visible in any way
  // (i.e. the Edit menu isn't open, nor is the context menu open, nor have the
  // cut, copy, and paste buttons been added to the toolbars) for performance.
  // This only works in applications/on platforms that set the gEditUIVisible
  // flag, so we check to see if that flag is defined before using it.
  if (typeof gEditUIVisible != "undefined" && !gEditUIVisible)
    return;

  goUpdateCommand("cmd_undo");
  goUpdateCommand("cmd_redo");
  goUpdateCommand("cmd_cut");
  goUpdateCommand("cmd_copy");
  goUpdateCommand("cmd_paste");
  goUpdateCommand("cmd_selectAll");
  goUpdateCommand("cmd_delete");
  goUpdateCommand("cmd_switchTextDirection");
}

// update menu items that relate to undo/redo
function goUpdateUndoEditMenuItems() {
  goUpdateCommand("cmd_undo");
  goUpdateCommand("cmd_redo");
}

// update menu items that depend on clipboard contents
function goUpdatePasteMenuItems() {
  goUpdateCommand("cmd_paste");
}
PK
!<zz1chrome/toolkit/content/global/editMenuOverlay.xul<?xml version="1.0"?> <!-- -*- Mode: HTML -*- --> 
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE overlay SYSTEM "chrome://global/locale/editMenuOverlay.dtd">

<overlay id="editMenuOverlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <script type="application/javascript" src="chrome://global/content/editMenuOverlay.js"/>

  <commandset id="editMenuCommands">
    <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select"
                oncommandupdate="goUpdateGlobalEditMenuItems()"/>
    <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo"
                oncommandupdate="goUpdateUndoEditMenuItems()"/>
    <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard"
                oncommandupdate="goUpdatePasteMenuItems()"/>
    <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/>
    <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/>
    <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/>
    <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/>
    <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/>
    <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/>
    <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
    <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/>
  </commandset>

  <!-- These key nodes are here only for show. The real bindings come from
       XBL, in platformHTMLBindings.xml. See bugs 57078 and 71779. -->

  <keyset id="editMenuKeys">
    <key id="key_undo" key="&undoCmd.key;" modifiers="accel" command="cmd_undo"/>
    <key id="key_redo" key="&redoCmd.key;" modifiers="accel" command="cmd_redo"/>
    <key id="key_cut" key="&cutCmd.key;" modifiers="accel" command="cmd_cut"/>
    <key id="key_copy" key="&copyCmd.key;" modifiers="accel" command="cmd_copy"/>
    <key id="key_paste" key="&pasteCmd.key;" modifiers="accel" command="cmd_paste"/>
    <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
    <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel" command="cmd_selectAll"/>
    <key id="key_find" key="&findCmd.key;" modifiers="accel" command="cmd_find"/>
    <key id="key_findAgain" key="&findAgainCmd.key;" modifiers="accel" command="cmd_findAgain"/>
    <key id="key_findPrevious" key="&findAgainCmd.key;" modifiers="shift,accel" command="cmd_findPrevious"/>
    <key id="key_findAgain2" keycode="&findAgainCmd.key2;" command="cmd_findAgain"/>
    <key id="key_findPrevious2" keycode="&findAgainCmd.key2;" modifiers="shift" command="cmd_findPrevious"/>
  </keyset>

  <!-- Edit Menu -->
  <menu id="menu_edit" label="&editMenu.label;"
        accesskey="&editMenu.accesskey;"/>

  <menuitem id="menu_undo" label="&undoCmd.label;"
            key="key_undo" accesskey="&undoCmd.accesskey;"
            command="cmd_undo"/>
  <menuitem id="menu_redo" label="&redoCmd.label;"
            key="key_redo" accesskey="&redoCmd.accesskey;"
            command="cmd_redo"/>
  <menuitem id="menu_cut" label="&cutCmd.label;"
            key="key_cut" accesskey="&cutCmd.accesskey;"
            command="cmd_cut"/>
  <menuitem id="menu_copy" label="&copyCmd.label;"
            key="key_copy" accesskey="&copyCmd.accesskey;"
            command="cmd_copy"/>
  <menuitem id="menu_paste" label="&pasteCmd.label;"
            key="key_paste" accesskey="&pasteCmd.accesskey;"
            command="cmd_paste"/>
  <menuitem id="menu_delete" label="&deleteCmd.label;"
            key="key_delete" accesskey="&deleteCmd.accesskey;"
            command="cmd_delete"/>
  <menuitem id="menu_selectAll" label="&selectAllCmd.label;"
            key="key_selectAll" accesskey="&selectAllCmd.accesskey;"
            command="cmd_selectAll"/>
  <menuitem id="menu_find" label="&findCmd.label;"
            key="key_find" accesskey="&findCmd.accesskey;"
            command="cmd_find"/>
  <menuitem id="menu_findAgain" label="&findAgainCmd.label;"
            key="key_findAgain" accesskey="&findAgainCmd.accesskey;"
            command="cmd_findAgain"/>
  <menuitem id="menu_findPrevious" label="&findPreviousCmd.label;"
            key="key_findPrevious" accesskey="&findPreviousCmd.accesskey;"
            command="cmd_findPrevious"/>

  <menuitem id="cMenu_undo" label="&undoCmd.label;"
            accesskey="&undoCmd.accesskey;" command="cmd_undo"/>
  <menuitem id="cMenu_redo" label="&redoCmd.label;"
            accesskey="&redoCmd.accesskey;" command="cmd_redo"/>
  <menuitem id="cMenu_cut" label="&cutCmd.label;"
            accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
  <menuitem id="cMenu_copy" label="&copyCmd.label;"
            accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
  <menuitem id="cMenu_paste" label="&pasteCmd.label;"
            accesskey="&pasteCmd.accesskey;" command="cmd_paste"/>
  <menuitem id="cMenu_delete" label="&deleteCmd.label;"
            accesskey="&deleteCmd.accesskey;" command="cmd_delete"/>
  <menuitem id="cMenu_selectAll" label="&selectAllCmd.label;"
            accesskey="&selectAllCmd.accesskey;" command="cmd_selectAll"/>
  <menuitem id="cMenu_find" label="&findCmd.label;"
            accesskey="&findCmd.accesskey;" command="cmd_find"/>
  <menuitem id="cMenu_findAgain" label="&findAgainCmd.label;"
            accesskey="&findAgainCmd.accesskey;" command="cmd_findAgain"/>
  <menuitem id="cMenu_findPrevious" label="&findPreviousCmd.label;"
            accesskey="&findPreviousCmd.accesskey;" command="cmd_findPrevious"/>
</overlay>
PK
!<\83chrome/toolkit/content/global/filepicker.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

allFilter=*
htmlFilter=*.html; *.htm; *.shtml; *.xhtml
textFilter=*.txt; *.text
imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw
xmlFilter=*.xml
xulFilter=*.xul
audioFilter=*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.ra; *.ram; *.snd; *.wav; *.wma
videoFilter=*.avi; *.divx; *.flv; *.m4v; *.mkv; *.mov; *.mp4; *.mpeg; *.mpg; *.ogm; *.ogv; *.ogx; *.rm; *.rmvb; *.smil; *.webm; *.wmv; *.xvid
PK
!<&}*chrome/toolkit/content/global/findUtils.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/. */

Components.utils.import("resource://gre/modules/Services.jsm");

var gFindBundle;

function nsFindInstData() {}
nsFindInstData.prototype =
{
  // set the next three attributes on your object to override the defaults
  browser: null,

  get rootSearchWindow() { return this._root || this.window.content; },
  set rootSearchWindow(val) { this._root = val; },

  get currentSearchWindow() {
    if (this._current)
      return this._current;

    var focusedWindow = this.window.document.commandDispatcher.focusedWindow;
    if (!focusedWindow || focusedWindow == this.window)
      focusedWindow = this.window.content;

    return focusedWindow;
  },
  set currentSearchWindow(val) { this._current = val; },

  get webBrowserFind() { return this.browser.webBrowserFind; },

  init() {
    var findInst = this.webBrowserFind;
    // set up the find to search the focussedWindow, bounded by the content window.
    var findInFrames = findInst.QueryInterface(Components.interfaces.nsIWebBrowserFindInFrames);
    findInFrames.rootSearchFrame = this.rootSearchWindow;
    findInFrames.currentSearchFrame = this.currentSearchWindow;

    // always search in frames for now. We could add a checkbox to the dialog for this.
    findInst.searchFrames = true;
  },

  window,
  _root: null,
  _current: null
}

// browser is the <browser> element
// rootSearchWindow is the window to constrain the search to (normally window.content)
// currentSearchWindow is the frame to start searching (can be, and normally, rootSearchWindow)
function findInPage(findInstData) {
  // is the dialog up already?
  if ("findDialog" in window && window.findDialog)
    window.findDialog.focus();
  else {
    findInstData.init();
    window.findDialog = window.openDialog("chrome://global/content/finddialog.xul", "_blank", "chrome,resizable=no,dependent=yes", findInstData);
  }
}

function findAgainInPage(findInstData, reverse) {
  if ("findDialog" in window && window.findDialog)
    window.findDialog.focus();
  else {
    // get the find service, which stores global find state, and init the
    // nsIWebBrowser find with it. We don't assume that there was a previous
    // Find that set this up.
    var findService = Components.classes["@mozilla.org/find/find_service;1"]
                           .getService(Components.interfaces.nsIFindService);

    var searchString = findService.searchString;
    if (searchString.length == 0) {
      // no previous find text
      findInPage(findInstData);
      return;
    }

    findInstData.init();
    var findInst = findInstData.webBrowserFind;
    findInst.searchString  = searchString;
    findInst.matchCase     = findService.matchCase;
    findInst.wrapFind      = findService.wrapFind;
    findInst.entireWord    = findService.entireWord;
    findInst.findBackwards = findService.findBackwards ^ reverse;

    var found = findInst.findNext();
    if (!found) {
      if (!gFindBundle)
        gFindBundle = document.getElementById("findBundle");

      Services.prompt.alert(window, gFindBundle.getString("notFoundTitle"), gFindBundle.getString("notFoundWarning"));
    }

    // Reset to normal value, otherwise setting can get changed in find dialog
    findInst.findBackwards = findService.findBackwards;
  }
}

function canFindAgainInPage() {
    var findService = Components.classes["@mozilla.org/find/find_service;1"]
                           .getService(Components.interfaces.nsIFindService);
    return (findService.searchString.length > 0);
}

PK
!<>1+chrome/toolkit/content/global/finddialog.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/. */

// Defined in dialog.xml.
/* globals moveToAlertPosition */

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/FormHistory.jsm");

var dialog;     // Quick access to document/form elements.
var gFindInst;   // nsIWebBrowserFind that we're going to use
var gFindInstData; // use this to update the find inst data

function initDialogObject() {
  // Create dialog object and initialize.
  dialog = {};
  dialog.findKey         = document.getElementById("dialog.findKey");
  dialog.caseSensitive   = document.getElementById("dialog.caseSensitive");
  dialog.wrap            = document.getElementById("dialog.wrap");
  dialog.find            = document.getElementById("btnFind");
  dialog.up              = document.getElementById("radioUp");
  dialog.down            = document.getElementById("radioDown");
  dialog.rg              = dialog.up.radioGroup;
  dialog.bundle          = null;

  // Move dialog to center, if it not been shown before
  var windowElement = document.getElementById("findDialog");
  if (!windowElement.hasAttribute("screenX") || !windowElement.hasAttribute("screenY")) {
    sizeToContent();
    moveToAlertPosition();
  }
}

function fillDialog() {
  // get the find service, which stores global find state
  var findService = Components.classes["@mozilla.org/find/find_service;1"]
                              .getService(Components.interfaces.nsIFindService);

  // Set initial dialog field contents. Use the gFindInst attributes first,
  // this is necessary for window.find()
  dialog.findKey.value           = gFindInst.searchString ? gFindInst.searchString : findService.searchString;
  dialog.caseSensitive.checked   = gFindInst.matchCase ? gFindInst.matchCase : findService.matchCase;
  dialog.wrap.checked            = gFindInst.wrapFind ? gFindInst.wrapFind : findService.wrapFind;
  var findBackwards              = gFindInst.findBackwards ? gFindInst.findBackwards : findService.findBackwards;
  if (findBackwards)
    dialog.rg.selectedItem = dialog.up;
  else
    dialog.rg.selectedItem = dialog.down;
}

function saveFindData() {
  // get the find service, which stores global find state
  var findService = Components.classes["@mozilla.org/find/find_service;1"]
                         .getService(Components.interfaces.nsIFindService);

  // Set data attributes per user input.
  findService.searchString  = dialog.findKey.value;
  findService.matchCase     = dialog.caseSensitive.checked;
  findService.wrapFind      = dialog.wrap.checked;
  findService.findBackwards = dialog.up.selected;
}

function onLoad() {
  initDialogObject();

  // get the find instance
  var arg0 = window.arguments[0];
  // If the dialog was opened from window.find(),
  // arg0 will be an instance of nsIWebBrowserFind
  if (arg0 instanceof Components.interfaces.nsIWebBrowserFind) {
    gFindInst = arg0;
  } else {
    gFindInstData = arg0;
    gFindInst = gFindInstData.webBrowserFind;
  }

  fillDialog();
  doEnabling();

  if (dialog.findKey.value)
    dialog.findKey.select();
  dialog.findKey.focus();
}

function onUnload() {
  window.opener.findDialog = 0;
}

function onAccept() {
  if (gFindInstData && gFindInst != gFindInstData.webBrowserFind) {
    gFindInstData.init();
    gFindInst = gFindInstData.webBrowserFind;
  }

  // Transfer dialog contents to the find service.
  saveFindData();
  updateFormHistory();

  // set up the find instance
  gFindInst.searchString  = dialog.findKey.value;
  gFindInst.matchCase     = dialog.caseSensitive.checked;
  gFindInst.wrapFind      = dialog.wrap.checked;
  gFindInst.findBackwards = dialog.up.selected;

  // Search.
  var result = gFindInst.findNext();

  if (!result) {
    if (!dialog.bundle)
      dialog.bundle = document.getElementById("findBundle");
    Services.prompt.alert(window, dialog.bundle.getString("notFoundTitle"),
                                  dialog.bundle.getString("notFoundWarning"));
    dialog.findKey.select();
    dialog.findKey.focus();
  }
  return false;
}

function doEnabling() {
  dialog.find.disabled = !dialog.findKey.value;
}

function updateFormHistory() {
  if (window.opener.PrivateBrowsingUtils &&
      window.opener.PrivateBrowsingUtils.isWindowPrivate(window.opener) ||
      !dialog.findKey.value)
    return;

  if (FormHistory.enabled) {
    FormHistory.update({
      op: "bump",
      fieldname: "find-dialog",
      value: dialog.findKey.value
    }, {
      handleError(aError) {
        Components.utils.reportError("Saving find to form history failed: " +
                                     aError.message);
      }
    });
  }
}
PK
!<q,chrome/toolkit/content/global/finddialog.xul<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://global/locale/finddialog.dtd">

<dialog id="findDialog"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  orient="horizontal"
  windowtype="findInPage"
  onload="onLoad();"
  onunload="onUnload();"
  ondialogaccept="return onAccept();"
  buttons="accept,cancel"
  title="&findDialog.title;"
  persist="screenX screenY">

  <script type="application/javascript" src="chrome://global/content/finddialog.js"/>
  <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>

  <hbox>
    <vbox>
      <hbox align="center">
        <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findKey"/>
        <textbox id="dialog.findKey" flex="1"
                 type="autocomplete"
                 autocompletesearch="form-history"
                 autocompletesearchparam="find-dialog"
                 oninput="doEnabling();"/>
      </hbox>
      <hbox align="center">
        <vbox>
          <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;" accesskey="&caseSensitiveCheckbox.accesskey;"/>
          <checkbox id="dialog.wrap" label="&wrapCheckbox.label;" accesskey="&wrapCheckbox.accesskey;" checked="true"/>
        </vbox>
        <groupbox orient="horizontal">
          <caption label="&direction.label;"/>
          <radiogroup orient="horizontal">
            <radio id="radioUp" label="&up.label;" accesskey="&up.accesskey;"/>
            <radio id="radioDown" label="&down.label;" accesskey="&down.accesskey;" selected="true"/>
          </radiogroup>
        </groupbox>
      </hbox>
    </vbox>
    <vbox>
      <button id="btnFind" label="&findButton.label;" accesskey="&findButton.accesskey;"
              dlgtype="accept" icon="find"/>
      <button label="&cancelButton.label;" icon="cancel" dlgtype="cancel"/>
    </vbox>
  </hbox>
</dialog>
PK
!<9'Ax.chrome/toolkit/content/global/globalOverlay.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function closeWindow(aClose, aPromptFunction) {
  let { AppConstants } = Components.utils.import("resource://gre/modules/AppConstants.jsm", {});

  // Closing the last window doesn't quit the application on OS X.
  if (AppConstants.platform != "macosx") {
    var windowCount = 0;
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                       .getService(Components.interfaces.nsIWindowMediator);
    var e = wm.getEnumerator(null);

    while (e.hasMoreElements()) {
      var w = e.getNext();
      if (w.closed) {
        continue;
      }
      if (++windowCount == 2)
        break;
    }

    // If we're down to the last window and someone tries to shut down, check to make sure we can!
    if (windowCount == 1 && !canQuitApplication("lastwindow"))
      return false;
    if (windowCount != 1 && typeof(aPromptFunction) == "function" && !aPromptFunction())
      return false;
  } else if (typeof(aPromptFunction) == "function" && !aPromptFunction()) {
    return false;
  }

  if (aClose) {
    window.close();
    return window.closed;
  }

  return true;
}

function canQuitApplication(aData) {
  var os = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
  if (!os) return true;

  try {
    var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
                              .createInstance(Components.interfaces.nsISupportsPRBool);
    os.notifyObservers(cancelQuit, "quit-application-requested", aData || null);

    // Something aborted the quit process.
    if (cancelQuit.data)
      return false;
  } catch (ex) { }
  return true;
}

function goQuitApplication() {
  if (!canQuitApplication())
    return false;

  var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
                     getService(Components.interfaces.nsIAppStartup);

  appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
  return true;
}

//
// Command Updater functions
//
function goUpdateCommand(aCommand) {
  try {
    var controller = top.document.commandDispatcher
                        .getControllerForCommand(aCommand);

    var enabled = false;
    if (controller)
      enabled = controller.isCommandEnabled(aCommand);

    goSetCommandEnabled(aCommand, enabled);
  } catch (e) {
    Components.utils.reportError("An error occurred updating the " +
                                 aCommand + " command: " + e);
  }
}

function goDoCommand(aCommand) {
  try {
    var controller = top.document.commandDispatcher
                        .getControllerForCommand(aCommand);
    if (controller && controller.isCommandEnabled(aCommand))
      controller.doCommand(aCommand);
  } catch (e) {
    Components.utils.reportError("An error occurred executing the " +
                                 aCommand + " command: " + e);
  }
}


function goSetCommandEnabled(aID, aEnabled) {
  var node = document.getElementById(aID);

  if (node) {
    if (aEnabled)
      node.removeAttribute("disabled");
    else
      node.setAttribute("disabled", "true");
  }
}

function goSetMenuValue(aCommand, aLabelAttribute) {
  var commandNode = top.document.getElementById(aCommand);
  if (commandNode) {
    var label = commandNode.getAttribute(aLabelAttribute);
    if (label)
      commandNode.setAttribute("label", label);
  }
}

function goSetAccessKey(aCommand, aValueAttribute) {
  var commandNode = top.document.getElementById(aCommand);
  if (commandNode) {
    var value = commandNode.getAttribute(aValueAttribute);
    if (value)
      commandNode.setAttribute("accesskey", value);
  }
}

// this function is used to inform all the controllers attached to a node that an event has occurred
// (e.g. the tree controllers need to be informed of blur events so that they can change some of the
// menu items back to their default values)
function goOnEvent(aNode, aEvent) {
  var numControllers = aNode.controllers.getControllerCount();
  var controller;

  for (var controllerIndex = 0; controllerIndex < numControllers; controllerIndex++) {
    controller = aNode.controllers.getControllerAt(controllerIndex);
    if (controller)
      controller.onEvent(aEvent);
  }
}

function setTooltipText(aID, aTooltipText) {
  var element = document.getElementById(aID);
  if (element)
    element.setAttribute("tooltiptext", aTooltipText);
}

Object.defineProperty(this, "NS_ASSERT", {
  configurable: true,
  enumerable: true,
  get() {
    delete this.NS_ASSERT;
    var tmpScope = {};
    Components.utils.import("resource://gre/modules/debug.js", tmpScope);
    return this.NS_ASSERT = tmpScope.NS_ASSERT;
  },
});
PK
!<gOI
I
7chrome/toolkit/content/global/gmp-sources/openh264.json{
  "vendors": {
    "gmp-gmpopenh264": {
      "platforms": {
        "WINNT_x86-msvc-x64": {
          "alias": "WINNT_x86-msvc"
        },
        "Android_x86-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-android-x86-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "8ce4d4318aa6ae9ac1376500d5fceecb3df38727aa920efd9f7829c139face4a069cab683d3902e7cdb89daad2a7e928ffba120812ae343f052a833812dad387",
          "filesize": 1640053
        },
        "WINNT_x86-msvc": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "991e01c3b95fa13fac52e0512e1936f1edae42ecbbbcc55447a36915eb3ca8f836546cc780343751691e0188872e5bc56fe3ad5f23f3243e90b96a637561b89e",
          "filesize": 356940
        },
        "WINNT_x86-msvc-x86": {
          "alias": "WINNT_x86-msvc"
        },
        "Linux_x86_64-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "e1086ee6e4fb60a1aa11b5626594b97695533a8e269d776877cebd5cf29088619e2c164e7bd1eba5486f772c943f2efec723f69cc48478ec84a11d7b61ca1865",
          "filesize": 515722
        },
        "Darwin_x86-gcc3-u-i386-x86_64": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx32-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "64b0e13e6319b7a31ed35a46bea5abcfe6af04ba59a277db07677236cfb685813763731ff6b44b85e03e1489f3b15f8df0128a299a36720531b9f4ba6e1c1f58",
          "filesize": 382435
        },
        "Darwin_x86_64-gcc3": {
          "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
        },
        "Linux_x86-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "19084f0230218c584715861f4723e072b1af02e26995762f368105f670f60ecb4082531bc4e33065a4675dd1296f6872a6cb101547ef2d19ef3e25e2e16d4dc0",
          "filesize": 515857
        },
        "Darwin_x86_64-gcc3-u-i386-x86_64": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "3b52343070a2f75e91b7b0d3bb33935352237c7e1d2fdc6a467d039ffbbda6a72087f9e0a369fe95e6c4c789ff3052f0c134af721d7273db9ba66d077d85b327",
          "filesize": 390308
        },
        "Android_arm-eabi-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-android-arm-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "7a15245c781f32df310ebb88cb8a783512eab934b38ffd889d6420473d40eddbe8a89c17cc60d4e7647c156b04d20030e1ae0081e3f90a0d8f94626ec5f4d817",
          "filesize": 1515895
        },
        "Darwin_x86-gcc3": {
          "alias": "Darwin_x86-gcc3-u-i386-x86_64"
        },
        "WINNT_x86_64-msvc-x64": {
          "alias": "WINNT_x86_64-msvc"
        },
        "WINNT_x86_64-msvc": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-0410d336bb748149a4f560eb6108090f078254b1.zip",
          "hashValue": "5030b47065e817db5c40bca9c62ac27292bbf636e24698f45dc67f03fa6420b97bd2f792c1cb39df65776c1e7597c70122ac7abf36fb2ad0603734e9e8ec4ef3",
          "filesize": 404355
        }
      },
      "version": "1.6"
    }
  },
  "hashFunction": "sha512",
  "name": "OpenH264-1.6",
  "schema_version": 1000
}
PK
!<B$Luu:chrome/toolkit/content/global/gmp-sources/widevinecdm.json{
  "vendors": {
    "gmp-widevinecdm": {
      "platforms": {
        "WINNT_x86-msvc-x64": {
          "alias": "WINNT_x86-msvc"
        },
        "WINNT_x86-msvc": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/1.4.8.1008-win-ia32.zip",
          "hashValue": "4630796cf6e5cc832317bf07bcda70f46432111d0bb381da9e2ebf9350cbac7790d6db1c34c7e3bced774a9b7811310cf21011fccbff779ac76d237505b5f117",
          "filesize": 2544372
        },
        "WINNT_x86-msvc-x86": {
          "alias": "WINNT_x86-msvc"
        },
        "Linux_x86_64-gcc3": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/1.4.8.1008-linux-x64.zip",
          "hashValue": "37e037a5e0c320a6a577492050d86b2bbd00239610785b0f07326e6c47b6d1899ac4f6874ad1436982a95a13c11fd73e10e9287d88da0c1036dd6eb36fe91e65",
          "filesize": 2207964
        },
        "Darwin_x86_64-gcc3-u-i386-x86_64": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/1.4.8.1008-mac-x64.zip",
          "hashValue": "950b2e1e74dc685294540e2969050a5558e9314d17a122b38b61de35897e692da247bff820fa3df721c607b38aa3bd95f36f7d4da83fb11a67ec9b1198890be8",
          "filesize": 1755276
        },
        "Darwin_x86_64-gcc3": {
          "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
        },
        "Linux_x86-gcc3": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/1.4.8.1008-linux-ia32.zip",
          "hashValue": "5af0f41dddf3077c2977a775a3009c435569892f7efa2110932424fda3556d6ecee76fbb1e9633e72b72e09a92b0b7fa2ac159e1741b0d4cd94e095b6e121bce",
          "filesize": 2289324
        },
        "WINNT_x86_64-msvc-x64": {
          "alias": "WINNT_x86_64-msvc"
        },
        "WINNT_x86_64-msvc": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/1.4.8.1008-win-x64.zip",
          "hashValue": "2c3ebe3a5c3fa62e2fb2e41080d576c71fa060cb81ef415815652a020bb3da16de28d9a2d0d5af4f71df920abd159d6c3cf27ebdd1d65daf62cee6282f9def85",
          "filesize": 2537223
        }
      },
      "version": "1.4.8.1008"
    }
  },
  "hashFunction": "sha512",
  "name": "Widevine-1.4.8.1008",
  "schema_version": 1000
}
PK
!<5rr*chrome/toolkit/content/global/license.html<!DOCTYPE HTML>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/.  -->

<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
    <title>Licenses</title>

    <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css">
  </head>

  <body id="lic-info">
    <div class="container">
      <h1><a id="top"></a>Licenses</h1>

      <div>


    <p>All of the <b>source code</b> to this product is
       available under licenses which are both
       <a href="http://www.gnu.org/philosophy/free-sw.html">free</a> and
       <a href="http://www.opensource.org/docs/definition.php">open source</a>.
       A URL identifying the specific source code used to create this copy can be found
       on the <a href="about:buildconfig">build configuration page</a>, and you can read
       <a href="https://developer.mozilla.org/en/Mozilla_Source_Code_%28Mercurial%29">instructions
       on how to download and build the code for yourself</a>.
    </p>

    <p>More specifically, most of the source code is available under the
       <a href="about:license#mpl">Mozilla Public License 2.0</a> (MPL).
       The MPL has a
       <a href="http://www.mozilla.org/MPL/2.0/FAQ.html">FAQ</a> to help
       you understand it. The remainder of the software which is not
       under the MPL is available under one of a variety of other
       free and open source licenses. Those that require reproduction
       of the license text in the distribution are given below.
       (Note: your copy of this product may not contain code covered by one
       or more of the licenses listed here, depending on the exact product
       and version you choose.)
    </p>

    <ul>
      <li><a href="about:license#mpl">Mozilla Public License 2.0</a>
      <br><br>
      </li>
      <li><a href="about:license#lgpl">GNU Lesser General Public License 2.1</a>
      <br><br>
      </li>
      <li><a href="about:license#lgpl-3.0">GNU Lesser General Public License 3.0</a>
      <br><br>
      </li>
      <li><a href="about:license#gpl-3.0">GNU General Public License 3.0</a>
      <br><br>
      </li>
      <li><a href="about:license#ACE">ACE License</a></li>
      <li><a href="about:license#acorn">acorn License</a></li>
      <li><a href="about:license#adobecmap">Adobe CMap License</a></li>
      <li><a href="about:license#android">Android Open Source License</a></li>
      <li><a href="about:license#angle">ANGLE License</a></li>
      <li><a href="about:license#agg">Anti-Grain Geometry Public License</a></li>
      <li><a href="about:license#apache">Apache License 2.0</a></li>
      <li><a href="about:license#apple">Apple License</a></li>
      <li><a href="about:license#apple-mozilla">Apple/Mozilla NPRuntime License</a></li>
      <li><a href="about:license#arm">ARM License</a></li>
      <li><a href="about:license#babel">Babel License</a></li>
      <li><a href="about:license#babylon">Babylon License</a></li>
      <li><a href="about:license#bspatch">bspatch License</a></li>
      <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
      <li><a href="about:license#chromium">Chromium License</a></li>
      <li><a href="about:license#codemirror">CodeMirror License</a></li>
      <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
      <li><a href="about:license#d3">D3 License</a></li>
      <li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
      <li><a href="about:license#diff">diff License</a></li>
      <li><a href="about:license#dtoa">dtoa License</a></li>
      <li><a href="about:license#fuzz-aldrin">fuzz-aldrin License</a></li>
      <li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
      <li><a href="about:license#emojione">EmojiOne License</a></li>
      <li><a href="about:license#hunspell-ee">Estonian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#expat">Expat License</a></li>
      <li><a href="about:license#firebug">Firebug License</a></li>
      <li><a href="about:license#gfx-font-list">gfxFontList License</a></li>
      <li><a href="about:license#google-bsd">Google BSD License</a></li>
      <li><a href="about:license#gears">Google Gears License</a></li>
      <li><a href="about:license#gears-istumbler">Google Gears/iStumbler License</a></li>
      <li><a href="about:license#vp8">Google VP8 License</a></li>
      <li><a href="about:license#gsl">GSL License</a></li>
      <li><a href="about:license#gyp">gyp License</a></li>
      <li><a href="about:license#halloc">halloc License</a></li>
      <li><a href="about:license#harfbuzz">HarfBuzz License</a></li>
      <li><a href="about:license#icu">ICU License</a></li>
      <li><a href="about:license#immutable">Immutable.js License</a></li>
      <li><a href="about:license#jpnic">Japan Network Information Center License</a></li>
      <li><a href="about:license#jsmn">JSMN License</a></li>
      <li><a href="about:license#jszip">JSZip License</a></li>
      <li><a href="about:license#jemalloc">jemalloc License</a></li>
      <li><a href="about:license#jquery">jQuery License</a></li>
      <li><a href="about:license#k_exp">k_exp License</a></li>
      <li><a href="about:license#khronos">Khronos group License</a></li>
      <li><a href="about:license#kiss_fft">Kiss FFT License</a></li>
      <li><a href="about:license#lcms">lcms License</a></li>
      <li><a href="about:license#libc++">libc++ License</a></li>
      <li><a href="about:license#libcubeb">libcubeb License</a></li>
      <li><a href="about:license#libevent">libevent License</a></li>
      <li><a href="about:license#libffi">libffi License</a></li>
      <li><a href="about:license#libjingle">libjingle License</a></li>
      <li><a href="about:license#libnestegg">libnestegg License</a></li>
      <li><a href="about:license#libsoundtouch">libsoundtouch License</a></li>
      <li><a href="about:license#libyuv">libyuv License</a></li>
      <li><a href="about:license#hunspell-lt">Lithuanian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#lodash">lodash License</a></li>
      <li><a href="about:license#microformatsshiv">MIT license — microformat-shiv</a></li>
      <li><a href="about:license#myspell">MySpell License</a></li>
      <li><a href="about:license#naturalSort">naturalSort License</a></li>
      <li><a href="about:license#nicer">nICEr License</a></li>
      <li><a href="about:license#node-md5">node-md5 License</a></li>
      <li><a href="about:license#node-properties">node-properties License</a></li>
      <li><a href="about:license#nrappkit">nrappkit License</a></li>
      <li><a href="about:license#openaes">OpenAES License</a></li>
      <li><a href="about:license#openvision">OpenVision License</a></li>
      <li><a href="about:license#openvr">OpenVR License</a></li>
      <li><a href="about:license#pbkdf2-sha256">pbkdf2_sha256 License</a></li>
      <li><a href="about:license#pdfium">PDFium License</a></li>
      <li><a href="about:license#praton">praton License</a></li>
      <li><a href="about:license#praton1">praton and inet_ntop License</a></li>
      <li><a href="about:license#qcms">qcms License</a></li>
      <li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
      <li><a href="about:license#raven-js">Raven.js License</a></li>
      <li><a href="about:license#react">React License</a></li>
      <li><a href="about:license#react-intl">React Intl License</a></li>
      <li><a href="about:license#react-redux">React-Redux License</a></li>
      <li><a href="about:license#react-virtualized">React Virtualized License</a></li>
      <li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
      <li><a href="about:license#redux">Redux License</a></li>
      <li><a href="about:license#reselect">Reselect License</a></li>
      <li><a href="about:license#hunspell-ru">Russian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#sctp">SCTP Licenses</a></li>
      <li><a href="about:license#skia">Skia License</a></li>
      <li><a href="about:license#snappy">Snappy License</a></li>
      <li><a href="about:license#sprintf.js">sprintf.js License</a></li>
      <li><a href="about:license#sunsoft">SunSoft License</a></li>
      <li><a href="about:license#superfasthash">SuperFastHash License</a></li>
      <li><a href="about:license#unicode">Unicode License</a></li>
      <li><a href="about:license#ucal">University of California License</a></li>
      <li><a href="about:license#hunspell-en-US">US English Spellchecking Dictionary Licenses</a></li>
      <li><a href="about:license#v8">V8 License</a></li>
      <li><a href="about:license#validator">Validator License</a></li>
      <li><a href="about:license#vtune">VTune License</a></li>
      <li><a href="about:license#webrtc">WebRTC License</a></li>
      <li><a href="about:license#x264">x264 License</a></li>
      <li><a href="about:license#xiph">Xiph.org Foundation License</a></li>
    </ul>

<br>

    <ul>
      <li><a href="about:license#other-notices">Other Required Notices</a>
      <li><a href="about:license#optional-notices">Optional Notices</a>
      <li><a href="about:license#proprietary-notices">Proprietary Operating System Components</a>
    </ul>

    </div>

    <hr>

  <h1 id="mpl">Mozilla Public License 2.0</h1>

  <h2 id="definitions">1. Definitions</h2>

  <dl>
    <dt>1.1. "Contributor"</dt>

    <dd>
      <p>means each individual or legal entity that creates, contributes to
      the creation of, or owns Covered Software.</p>
    </dd>

    <dt>1.2. "Contributor Version"</dt>

    <dd>
      <p>means the combination of the Contributions of others (if any) used
      by a Contributor and that particular Contributor's Contribution.</p>
    </dd>

    <dt>1.3. "Contribution"</dt>

    <dd>
      <p>means Covered Software of a particular Contributor.</p>
    </dd>

    <dt>1.4. "Covered Software"</dt>

    <dd>
      <p>means Source Code Form to which the initial Contributor has attached
      the notice in Exhibit A, the Executable Form of such Source Code Form,
      and Modifications of such Source Code Form, in each case including
      portions thereof.</p>
    </dd>

    <dt>1.5. "Incompatible With Secondary Licenses"</dt>

    <dd>
      <p>means</p>

      <ol type="a">
        <li>
          <p>that the initial Contributor has attached the notice described
          in Exhibit B to the Covered Software; or</p>
        </li>

        <li>
          <p>that the Covered Software was made available under the terms of
          version 1.1 or earlier of the License, but not also under the terms
          of a Secondary License.</p>
        </li>
      </ol>
    </dd>

    <dt>1.6. "Executable Form"</dt>

    <dd>
      <p>means any form of the work other than Source Code Form.</p>
    </dd>

    <dt>1.7. "Larger Work"</dt>

    <dd>
      <p>means a work that combines Covered Software with other material, in
      a separate file or files, that is not Covered Software.</p>
    </dd>

    <dt>1.8. "License"</dt>

    <dd>
      <p>means this document.</p>
    </dd>

    <dt>1.9. "Licensable"</dt>

    <dd>
      <p>means having the right to grant, to the maximum extent possible,
      whether at the time of the initial grant or subsequently, any and all
      of the rights conveyed by this License.</p>
    </dd>

    <dt>1.10. "Modifications"</dt>

    <dd>
      <p>means any of the following:</p>

      <ol type="a">
        <li>
          <p>any file in Source Code Form that results from an addition to,
          deletion from, or modification of the contents of Covered Software;
          or</p>
        </li>

        <li>
          <p>any new file in Source Code Form that contains any Covered
          Software.</p>
        </li>
      </ol>
    </dd>

    <dt>1.11. "Patent Claims" of a Contributor</dt>

    <dd>
      <p>means any patent claim(s), including without limitation, method,
      process, and apparatus claims, in any patent Licensable by such
      Contributor that would be infringed, but for the grant of the License,
      by the making, using, selling, offering for sale, having made, import,
      or transfer of either its Contributions or its Contributor Version.</p>
    </dd>

    <dt>1.12. "Secondary License"</dt>

    <dd>
      <p>means either the GNU General Public License, Version 2.0, the GNU
      Lesser General Public License, Version 2.1, the GNU Affero General
      Public License, Version 3.0, or any later versions of those
      licenses.</p>
    </dd>

    <dt>1.13. "Source Code Form"</dt>

    <dd>
      <p>means the form of the work preferred for making modifications.</p>
    </dd>

    <dt>1.14. "You" (or "Your")</dt>

    <dd>
      <p>means an individual or a legal entity exercising rights under this
      License. For legal entities, "You" includes any entity that controls,
      is controlled by, or is under common control with You. For purposes of
      this definition, "control" means (a) the power, direct or indirect, to
      cause the direction or management of such entity, whether by contract
      or otherwise, or (b) ownership of more than fifty percent (50%) of the
      outstanding shares or beneficial ownership of such entity.</p>
    </dd>
  </dl>

  <h2 id="license-grants-and-conditions">2. License Grants and
  Conditions</h2>

  <h3 id="grants">2.1. Grants</h3>

  <p>Each Contributor hereby grants You a world-wide, royalty-free,
  non-exclusive license:</p>

  <ol type="a">
    <li>
      <p>under intellectual property rights (other than patent or trademark)
      Licensable by such Contributor to use, reproduce, make available,
      modify, display, perform, distribute, and otherwise exploit its
      Contributions, either on an unmodified basis, with Modifications, or as
      part of a Larger Work; and</p>
    </li>

    <li>
      <p>under Patent Claims of such Contributor to make, use, sell, offer
      for sale, have made, import, and otherwise transfer either its
      Contributions or its Contributor Version.</p>
    </li>
  </ol>

  <h3 id="effective-date">2.2. Effective Date</h3>

  <p>The licenses granted in Section 2.1 with respect to any Contribution
  become effective for each Contribution on the date the Contributor first
  distributes such Contribution.</p>

  <h3 id="limitations-on-grant-scope">2.3. Limitations on Grant Scope</h3>

  <p>The licenses granted in this Section 2 are the only rights granted under
  this License. No additional rights or licenses will be implied from the
  distribution or licensing of Covered Software under this License.
  Notwithstanding Section 2.1(b) above, no patent license is granted by a
  Contributor:</p>

  <ol type="a">
    <li>
      <p>for any code that a Contributor has removed from Covered Software;
      or</p>
    </li>

    <li>
      <p>for infringements caused by: (i) Your and any other third party's
      modifications of Covered Software, or (ii) the combination of its
      Contributions with other software (except as part of its Contributor
      Version); or</p>
    </li>

    <li>
      <p>under Patent Claims infringed by Covered Software in the absence of
      its Contributions.</p>
    </li>
  </ol>

  <p>This License does not grant any rights in the trademarks, service marks,
  or logos of any Contributor (except as may be necessary to comply with the
  notice requirements in Section 3.4).</p>

  <h3 id="subsequent-licenses">2.4. Subsequent Licenses</h3>

  <p>No Contributor makes additional grants as a result of Your choice to
  distribute the Covered Software under a subsequent version of this License
  (see Section 10.2) or under the terms of a Secondary License (if permitted
  under the terms of Section 3.3).</p>

  <h3 id="representation">2.5. Representation</h3>

  <p>Each Contributor represents that the Contributor believes its
  Contributions are its original creation(s) or it has sufficient rights to
  grant the rights to its Contributions conveyed by this License.</p>

  <h3 id="fair-use">2.6. Fair Use</h3>

  <p>This License is not intended to limit any rights You have under
  applicable copyright doctrines of fair use, fair dealing, or other
  equivalents.</p>

  <h3 id="conditions">2.7. Conditions</h3>

  <p>Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
  in Section 2.1.</p>

  <h2 id="responsibilities">3. Responsibilities</h2>

  <h3 id="distribution-of-source-form">3.1. Distribution of Source Form</h3>

  <p>All distribution of Covered Software in Source Code Form, including any
  Modifications that You create or to which You contribute, must be under the
  terms of this License. You must inform recipients that the Source Code Form
  of the Covered Software is governed by the terms of this License, and how
  they can obtain a copy of this License. You may not attempt to alter or
  restrict the recipients' rights in the Source Code Form.</p>

  <h3 id="distribution-of-executable-form">3.2. Distribution of Executable
  Form</h3>

  <p>If You distribute Covered Software in Executable Form then:</p>

  <ol type="a">
    <li>
      <p>such Covered Software must also be made available in Source Code
      Form, as described in Section 3.1, and You must inform recipients of
      the Executable Form how they can obtain a copy of such Source Code Form
      by reasonable means in a timely manner, at a charge no more than the
      cost of distribution to the recipient; and</p>
    </li>

    <li>
      <p>You may distribute such Executable Form under the terms of this
      License, or sublicense it under different terms, provided that the
      license for the Executable Form does not attempt to limit or alter the
      recipients' rights in the Source Code Form under this License.</p>
    </li>
  </ol>

  <h3 id="distribution-of-a-larger-work">3.3. Distribution of a Larger
  Work</h3>

  <p>You may create and distribute a Larger Work under terms of Your choice,
  provided that You also comply with the requirements of this License for the
  Covered Software. If the Larger Work is a combination of Covered Software
  with a work governed by one or more Secondary Licenses, and the Covered
  Software is not Incompatible With Secondary Licenses, this License permits
  You to additionally distribute such Covered Software under the terms of
  such Secondary License(s), so that the recipient of the Larger Work may, at
  their option, further distribute the Covered Software under the terms of
  either this License or such Secondary License(s).</p>

  <h3 id="notices">3.4. Notices</h3>

  <p>You may not remove or alter the substance of any license notices
  (including copyright notices, patent notices, disclaimers of warranty, or
  limitations of liability) contained within the Source Code Form of the
  Covered Software, except that You may alter any license notices to the
  extent required to remedy known factual inaccuracies.</p>

  <h3 id="application-of-additional-terms">3.5. Application of Additional
  Terms</h3>

  <p>You may choose to offer, and to charge a fee for, warranty, support,
  indemnity or liability obligations to one or more recipients of Covered
  Software. However, You may do so only on Your own behalf, and not on behalf
  of any Contributor. You must make it absolutely clear that any such
  warranty, support, indemnity, or liability obligation is offered by You
  alone, and You hereby agree to indemnify every Contributor for any
  liability incurred by such Contributor as a result of warranty, support,
  indemnity or liability terms You offer. You may include additional
  disclaimers of warranty and limitations of liability specific to any
  jurisdiction.</p>

  <h2 id="inability-to-comply-due-to-statute-or-regulation">4. Inability to
  Comply Due to Statute or Regulation</h2>

  <p>If it is impossible for You to comply with any of the terms of this
  License with respect to some or all of the Covered Software due to statute,
  judicial order, or regulation then You must: (a) comply with the terms of
  this License to the maximum extent possible; and (b) describe the
  limitations and the code they affect. Such description must be placed in a
  text file included with all distributions of the Covered Software under
  this License. Except to the extent prohibited by statute or regulation,
  such description must be sufficiently detailed for a recipient of ordinary
  skill to be able to understand it.</p>

  <h2 id="termination">5. Termination</h2>

  <h3>5.1.</h3>

  <p>The rights granted under this License will terminate automatically
  if You fail to comply with any of its terms. However, if You become
  compliant, then the rights granted under this License from a particular
  Contributor are reinstated (a) provisionally, unless and until such
  Contributor explicitly and finally terminates Your grants, and (b) on an
  ongoing basis, if such Contributor fails to notify You of the
  non-compliance by some reasonable means prior to 60 days after You have
  come back into compliance. Moreover, Your grants from a particular
  Contributor are reinstated on an ongoing basis if such Contributor notifies
  You of the non-compliance by some reasonable means, this is the first time
  You have received notice of non-compliance with this License from such
  Contributor, and You become compliant prior to 30 days after Your receipt
  of the notice.</p>

  <h3>5.2.</h3>

  <p>If You initiate litigation against any entity by asserting a patent
  infringement claim (excluding declaratory judgment actions, counter-claims,
  and cross-claims) alleging that a Contributor Version directly or
  indirectly infringes any patent, then the rights granted to You by any and
  all Contributors for the Covered Software under Section 2.1 of this License
  shall terminate.</p>

  <h3>5.3.</h3>

  <p>In the event of termination under Sections 5.1 or 5.2 above, all
  end user license agreements (excluding distributors and resellers) which
  have been validly granted by You or Your distributors under this License
  prior to termination shall survive termination.</p>

  <h2 id="disclaimer-of-warranty">6. Disclaimer of Warranty</h2>

  <p><em>Covered Software is provided under this License on an "as is" basis,
  without warranty of any kind, either expressed, implied, or statutory,
  including, without limitation, warranties that the Covered Software is free
  of defects, merchantable, fit for a particular purpose or non-infringing.
  The entire risk as to the quality and performance of the Covered Software
  is with You. Should any Covered Software prove defective in any respect,
  You (not any Contributor) assume the cost of any necessary servicing,
  repair, or correction. This disclaimer of warranty constitutes an essential
  part of this License. No use of any Covered Software is authorized under
  this License except under this disclaimer.</em></p>

  <h2 id="limitation-of-liability">7. Limitation of Liability</h2>

  <p><em>Under no circumstances and under no legal theory, whether tort
  (including negligence), contract, or otherwise, shall any Contributor, or
  anyone who distributes Covered Software as permitted above, be liable to
  You for any direct, indirect, special, incidental, or consequential damages
  of any character including, without limitation, damages for lost profits,
  loss of goodwill, work stoppage, computer failure or malfunction, or any
  and all other commercial damages or losses, even if such party shall have
  been informed of the possibility of such damages. This limitation of
  liability shall not apply to liability for death or personal injury
  resulting from such party's negligence to the extent applicable law
  prohibits such limitation. Some jurisdictions do not allow the exclusion or
  limitation of incidental or consequential damages, so this exclusion and
  limitation may not apply to You.</em></p>

  <h2 id="litigation">8. Litigation</h2>

  <p>Any litigation relating to this License may be brought only in the
  courts of a jurisdiction where the defendant maintains its principal place
  of business and such litigation shall be governed by laws of that
  jurisdiction, without reference to its conflict-of-law provisions. Nothing
  in this Section shall prevent a party's ability to bring cross-claims or
  counter-claims.</p>

  <h2 id="miscellaneous">9. Miscellaneous</h2>

  <p>This License represents the complete agreement concerning the subject
  matter hereof. If any provision of this License is held to be
  unenforceable, such provision shall be reformed only to the extent
  necessary to make it enforceable. Any law or regulation which provides that
  the language of a contract shall be construed against the drafter shall not
  be used to construe this License against a Contributor.</p>

  <h2 id="versions-of-the-license">10. Versions of the License</h2>

  <h3 id="new-versions">10.1. New Versions</h3>

  <p>Mozilla Foundation is the license steward. Except as provided in Section
  10.3, no one other than the license steward has the right to modify or
  publish new versions of this License. Each version will be given a
  distinguishing version number.</p>

  <h3 id="effect-of-new-versions">10.2. Effect of New Versions</h3>

  <p>You may distribute the Covered Software under the terms of the version
  of the License under which You originally received the Covered Software, or
  under the terms of any subsequent version published by the license
  steward.</p>

  <h3 id="modified-versions">10.3. Modified Versions</h3>

  <p>If you create software not governed by this License, and you want to
  create a new license for such software, you may create and use a modified
  version of this License if you rename the license and remove any references
  to the name of the license steward (except to note that such modified
  license differs from this License).</p>

  <h3 id=
  "distributing-source-code-form-that-is-incompatible-with-secondary-licenses">
  10.4. Distributing Source Code Form that is Incompatible With Secondary
  Licenses</h3>

  <p>If You choose to distribute Source Code Form that is Incompatible With
  Secondary Licenses under the terms of this version of the License, the
  notice described in Exhibit B of this License must be attached.</p>

  <h2 id="exhibit-a---source-code-form-license-notice">Exhibit A - Source
  Code Form License Notice</h2>

  <blockquote>
    <p>This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this file,
    You can obtain one at http://mozilla.org/MPL/2.0/.</p>
  </blockquote>

  <p>If it is not possible or desirable to put the notice in a particular
  file, then You may include the notice in a location (such as a LICENSE file
  in a relevant directory) where a recipient would be likely to look for such
  a notice.</p>

  <p>You may add additional accurate notices of copyright ownership.</p>

  <h2 id="exhibit-b---incompatible-with-secondary-licenses-notice">Exhibit B
  - "Incompatible With Secondary Licenses" Notice</h2>

  <blockquote>
    <p>This Source Code Form is "Incompatible With Secondary Licenses", as
    defined by the Mozilla Public License, v. 2.0.</p>
  </blockquote>


    <hr>

  <h1 id="lgpl">GNU Lesser General Public License 2.1</h1>

<p>This product contains code from the following LGPLed libraries:</p>

<ul>
<li><a href="http://www.surina.net/soundtouch/">libsoundtouch</a>
<li><a href="http://libav.org/">libav</a>
<li><a href="http://ffmpeg.org/">FFmpeg</a>
</ul>

<pre>
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]
</pre>

<h3><a id="SEC2">Preamble</a></h3>

<p>
  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
</p>
<p>
  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
</p>
<p>
  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
</p>
<p>
  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
</p>
<p>
  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.
</p>
<p>
  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
</p>
<p>
  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
</p>
<p>
  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
</p>
<p>
  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
</p>
<p>
  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.
</p>
<p>
  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.
</p>
<p>
  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
</p>
<p>
  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
</p>
<p>
  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
</p>
<p>
  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
</p>

<h3><a id="SEC3">TERMS AND CONDITIONS FOR COPYING,
DISTRIBUTION AND MODIFICATION</a></h3>


<p>
<strong>0.</strong>
This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
</p>
<p>
  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
</p>
<p>
  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)
</p>
<p>
  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
</p>
<p>
  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.
</p>
<p>
<strong>1.</strong>
You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
</p>
<p>
  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
</p>
<p>
<strong>2.</strong>
You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
</p>

<ul>
  <li><strong>a)</strong>
       The modified work must itself be a software library.</li>
  <li><strong>b)</strong>
       You must cause the files modified to carry prominent notices
       stating that you changed the files and the date of any change.</li>

  <li><strong>c)</strong>
       You must cause the whole of the work to be licensed at no
       charge to all third parties under the terms of this License.</li>

  <li><strong>d)</strong>
       If a facility in the modified Library refers to a function or a
       table of data to be supplied by an application program that uses
       the facility, other than as an argument passed when the facility
       is invoked, then you must make a good faith effort to ensure that,
       in the event an application does not supply such function or
       table, the facility still operates, and performs whatever part of
       its purpose remains meaningful.
       <p>
       (For example, a function in a library to compute square roots has
       a purpose that is entirely well-defined independent of the
       application.  Therefore, Subsection 2d requires that any
       application-supplied function or table used by this function must
       be optional: if the application does not supply it, the square
       root function must still compute square roots.)</p></li>
</ul>

<p>
These requirements apply to the modified work as a whole.  If identifiable
sections of that work are not derived from the Library, and can be
reasonably considered independent and separate works in themselves, then
this License, and its terms, do not apply to those sections when you
distribute them as separate works.  But when you distribute the same
sections as part of a whole which is a work based on the Library, the
distribution of the whole must be on the terms of this License, whose
permissions for other licensees extend to the entire whole, and thus to
each and every part regardless of who wrote it.
</p>
<p>
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works
based on the Library.
</p>
<p>
In addition, mere aggregation of another work not based on the Library with
the Library (or with a work based on the Library) on a volume of a storage
or distribution medium does not bring the other work under the scope of
this License.
</p>
<p>
<strong>3.</strong>
You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.
</p>
<p>
  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
</p>
<p>
  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
</p>
<p>
<strong>4.</strong>
You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
</p>
<p>
  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
</p>
<p>
<strong>5.</strong>
A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
</p>
<p>
  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
</p>
<p>
  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.
</p>
<p>
  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
</p>
<p>
  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
</p>
<p>
<strong>6.</strong>
As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
</p>
<p>
  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:
</p>

<ul>
    <li><strong>a)</strong> Accompany the work with the complete
    corresponding machine-readable source code for the Library
    including whatever changes were used in the work (which must be
    distributed under Sections 1 and 2 above); and, if the work is an
    executable linked with the Library, with the complete
    machine-readable "work that uses the Library", as object code
    and/or source code, so that the user can modify the Library and
    then relink to produce a modified executable containing the
    modified Library.  (It is understood that the user who changes the
    contents of definitions files in the Library will not necessarily
    be able to recompile the application to use the modified
    definitions.)</li>

    <li><strong>b)</strong> Use a suitable shared library mechanism
    for linking with the Library.  A suitable mechanism is one that
    (1) uses at run time a copy of the library already present on the
    user's computer system, rather than copying library functions into
    the executable, and (2) will operate properly with a modified
    version of the library, if the user installs one, as long as the
    modified version is interface-compatible with the version that the
    work was made with.</li>

    <li><strong>c)</strong> Accompany the work with a written offer,
    valid for at least three years, to give the same user the
    materials specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.</li>

    <li><strong>d)</strong> If distribution of the work is made by
    offering access to copy from a designated place, offer equivalent
    access to copy the above specified materials from the same
    place.</li>

    <li><strong>e)</strong> Verify that the user has already received
    a copy of these materials or that you have already sent this user
    a copy.</li>
</ul>

<p>
  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
</p>
<p>
  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
</p>
<p>
<strong>7.</strong> You may place library facilities that are a work
based on the Library side-by-side in a single library together with
other library facilities not covered by this License, and distribute
such a combined library, provided that the separate distribution of
the work based on the Library and of the other library facilities is
otherwise permitted, and provided that you do these two things:
</p>

<ul>
    <li><strong>a)</strong> Accompany the combined library with a copy
    of the same work based on the Library, uncombined with any other
    library facilities.  This must be distributed under the terms of
    the Sections above.</li>

    <li><strong>b)</strong> Give prominent notice with the combined
    library of the fact that part of it is a work based on the
    Library, and explaining where to find the accompanying uncombined
    form of the same work.</li>
</ul>

<p>
<strong>8.</strong> You may not copy, modify, sublicense, link with,
or distribute the Library except as expressly provided under this
License.  Any attempt otherwise to copy, modify, sublicense, link
with, or distribute the Library is void, and will automatically
terminate your rights under this License.  However, parties who have
received copies, or rights, from you under this License will not have
their licenses terminated so long as such parties remain in full
compliance.
</p>
<p>
<strong>9.</strong>
You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
</p>
<p>
<strong>10.</strong>
Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
</p>
<p>
<strong>11.</strong>
If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
</p>
<p>
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
</p>
<p>
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
</p>
<p>
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
</p>
<p>
<strong>12.</strong>
If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.
</p>
<p>
<strong>13.</strong>
The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
</p>
<p>
Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
</p>
<p>
<strong>14.</strong>
If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
</p>
<p>
<strong>NO WARRANTY</strong>
</p>
<p>
<strong>15.</strong>
BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
</p>
<p>
<strong>16.</strong>
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
</p>

    <hr>

  <h1 id="lgpl-3.0">GNU Lesser General Public License 3.0</h1>

<p>Some versions of this product contains code from the following LGPLed libraries:</p>

<ul>
<li><a
href="https://addons.mozilla.org/en-US/firefox/addon/görans-hemmasnickrade-ordli/">Swedish dictionary</a>
</ul>

<pre>Copyright &copy; 2007 Free Software Foundation, Inc.
 &lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.</pre>

<p>This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.</p>

<h3><a id="section0">0. Additional Definitions</a></h3>

<p>As used herein, &ldquo;this License&rdquo; refers to version 3 of the GNU Lesser
General Public License, and the &ldquo;GNU GPL&rdquo; refers to version 3 of the GNU
General Public License.</p>

<p>&ldquo;The Library&rdquo; refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.</p>

<p>An &ldquo;Application&rdquo; is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.</p>

<p>A &ldquo;Combined Work&rdquo; is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the &ldquo;Linked
Version&rdquo;.</p>

<p>The &ldquo;Minimal Corresponding Source&rdquo; for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.</p>

<p>The &ldquo;Corresponding Application Code&rdquo; for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.</p>

<h3><a id="section1">1. Exception to Section 3 of the GNU GPL.</a></h3>

<p>You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.</p>

<h3><a id="section2">2. Conveying Modified Versions.</a></h3>

<p>If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:</p>

<ul>
<li>a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or</li>

<li>b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.</li>
</ul>

<h3><a id="section3">3. Object Code Incorporating Material from Library Header Files.</a></h3>

<p>The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:</p>

<ul>
<li>a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.</li>

<li>b) Accompany the object code with a copy of the GNU GPL and this license
   document.</li>
</ul>

<h3><a id="section4">4. Combined Works.</a></h3>

<p>You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:</p>

<ul>
<li>a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.</li>

<li>b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.</li>

<li>c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.</li>

<li>d) Do one of the following:

<ul>
<li>0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.</li>

<li>1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.</li>
</ul></li>

<li>e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)</li>
</ul>

<h3><a id="section5">5. Combined Libraries.</a></h3>

<p>You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:</p>

<ul>
<li>a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.</li>

<li>b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.</li>
</ul>

<h3><a id="section6">6. Revised Versions of the GNU Lesser General Public License.</a></h3>

<p>The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.</p>

<p>Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License &ldquo;or any later version&rdquo;
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.</p>

<p>If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.</p>


    <hr>


    <h1 id="gpl-3.0">GNU General Public License 3.0</h1>

    <p>This license does not apply to any of the code shipped with
    Firefox, but may apply to blocklists downloaded after installation
    for use with the tracking protection feature. Firefox and such
    blocklists are separate and independent works as described in
    Sections 5 and 6 of this license. Our blocklist is based on one
    originally written by Disconnect.me.</p>

<pre>Version 3, 29 June 2007

Copyright &copy; 2007 Free Software Foundation, Inc.
&lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.</pre>

<h2><a name="preamble"></a>Preamble</h2>

<p>The GNU General Public License is a free, copyleft license for
software and other kinds of works.</p>

<p>The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.</p>

<p>When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.</p>

<p>To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.</p>

<p>For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.</p>

<p>Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.</p>

<p>For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.</p>

<p>Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.</p>

<p>Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.</p>

<p>The precise terms and conditions for copying, distribution and
modification follow.</p>

<h2><a name="terms"></a>TERMS AND CONDITIONS</h2>

<h3><a name="section0"></a>0. Definitions.</h3>

<p>&ldquo;This License&rdquo; refers to version 3 of the GNU General Public License.</p>

<p>&ldquo;Copyright&rdquo; also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.</p>

<p>&ldquo;The Program&rdquo; refers to any copyrightable work licensed under this
License.  Each licensee is addressed as &ldquo;you&rdquo;.  &ldquo;Licensees&rdquo; and
&ldquo;recipients&rdquo; may be individuals or organizations.</p>

<p>To &ldquo;modify&rdquo; a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a &ldquo;modified version&rdquo; of the
earlier work or a work &ldquo;based on&rdquo; the earlier work.</p>

<p>A &ldquo;covered work&rdquo; means either the unmodified Program or a work based
on the Program.</p>

<p>To &ldquo;propagate&rdquo; a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.</p>

<p>To &ldquo;convey&rdquo; a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.</p>

<p>An interactive user interface displays &ldquo;Appropriate Legal Notices&rdquo;
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.</p>

<h3><a name="section1"></a>1. Source Code.</h3>

<p>The &ldquo;source code&rdquo; for a work means the preferred form of the work
for making modifications to it.  &ldquo;Object code&rdquo; means any non-source
form of a work.</p>

<p>A &ldquo;Standard Interface&rdquo; means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.</p>

<p>The &ldquo;System Libraries&rdquo; of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
&ldquo;Major Component&rdquo;, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.</p>

<p>The &ldquo;Corresponding Source&rdquo; for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.</p>

<p>The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.</p>

<p>The Corresponding Source for a work in source code form is that
same work.</p>

<h3><a name="section2"></a>2. Basic Permissions.</h3>

<p>All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.</p>

<p>You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.</p>

<p>Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.</p>

<h3><a name="section3"></a>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h3>

<p>No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.</p>

<p>When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.</p>

<h3><a name="section4"></a>4. Conveying Verbatim Copies.</h3>

<p>You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.</p>

<p>You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.</p>

<h3><a name="section5"></a>5. Conveying Modified Source Versions.</h3>

<p>You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:</p>

<ul>
<li>a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.</li>

<li>b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    &ldquo;keep intact all notices&rdquo;.</li>

<li>c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.</li>

<li>d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.</li>
</ul>

<p>A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
&ldquo;aggregate&rdquo; if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.</p>

<h3><a name="section6"></a>6. Conveying Non-Source Forms.</h3>

<p>You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:</p>

<ul>
<li>a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.</li>

<li>b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.</li>

<li>c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.</li>

<li>d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.</li>

<li>e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.</li>
</ul>

<p>A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.</p>

<p>A &ldquo;User Product&rdquo; is either (1) a &ldquo;consumer product&rdquo;, which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, &ldquo;normally used&rdquo; refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.</p>

<p>&ldquo;Installation Information&rdquo; for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.</p>

<p>If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).</p>

<p>The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.</p>

<p>Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.</p>

<h3><a name="section7"></a>7. Additional Terms.</h3>

<p>&ldquo;Additional permissions&rdquo; are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.</p>

<p>When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.</p>

<p>Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:</p>

<ul>
<li>a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or</li>

<li>b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or</li>

<li>c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or</li>

<li>d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or</li>

<li>e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or</li>

<li>f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.</li>
</ul>

<p>All other non-permissive additional terms are considered &ldquo;further
restrictions&rdquo; within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.</p>

<p>If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.</p>

<p>Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.</p>

<h3><a name="section8"></a>8. Termination.</h3>

<p>You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).</p>

<p>However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.</p>

<p>Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.</p>

<p>Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.</p>

<h3><a name="section9"></a>9. Acceptance Not Required for Having Copies.</h3>

<p>You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.</p>

<h3><a name="section10"></a>10. Automatic Licensing of Downstream Recipients.</h3>

<p>Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.</p>

<p>An &ldquo;entity transaction&rdquo; is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.</p>

<p>You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.</p>

<h3><a name="section11"></a>11. Patents.</h3>

<p>A &ldquo;contributor&rdquo; is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's &ldquo;contributor version&rdquo;.</p>

<p>A contributor's &ldquo;essential patent claims&rdquo; are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, &ldquo;control&rdquo; includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.</p>

<p>Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.</p>

<p>In the following three paragraphs, a &ldquo;patent license&rdquo; is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To &ldquo;grant&rdquo; such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.</p>

<p>If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  &ldquo;Knowingly relying&rdquo; means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.</p>

<p>If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.</p>

<p>A patent license is &ldquo;discriminatory&rdquo; if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.</p>

<p>Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.</p>

<h3><a name="section12"></a>12. No Surrender of Others' Freedom.</h3>

<p>If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.</p>

<h3><a name="section13"></a>13. Use with the GNU Affero General Public License.</h3>

<p>Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.</p>

<h3><a name="section14"></a>14. Revised Versions of this License.</h3>

<p>The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.</p>

<p>Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License &ldquo;or any later version&rdquo; applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.</p>

<p>If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.</p>

<p>Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.</p>

<h3><a name="section15"></a>15. Disclaimer of Warranty.</h3>

<p>THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM &ldquo;AS IS&rdquo; WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p>

<h3><a name="section16"></a>16. Limitation of Liability.</h3>

<p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.</p>

<h3><a name="section17"></a>17. Interpretation of Sections 15 and 16.</h3>

<p>If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.</p>

<p>END OF TERMS AND CONDITIONS</p>

<h2><a name="howto"></a>How to Apply These Terms to Your New Programs</h2>

<p>If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.</p>

<p>To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the &ldquo;copyright&rdquo; line and a pointer to where the full notice is found.</p>

<pre>    &lt;one line to give the program's name and a brief idea of what it does.&gt;
    Copyright (C) &lt;year&gt;  &lt;name of author&gt;

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
</pre>

<p>Also add information on how to contact you by electronic and paper mail.</p>

<p>If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:</p>

<pre>    &lt;program&gt;  Copyright (C) &lt;year&gt;  &lt;name of author&gt;
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.
</pre>

<p>The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an &ldquo;about box&rdquo;.</p>

<p>You should also get your employer (if you work as a programmer) or school,
if any, to sign a &ldquo;copyright disclaimer&rdquo; for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
&lt;<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>&gt;.</p>

<p>The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
&lt;<a href="http://www.gnu.org/philosophy/why-not-lgpl.html">http://www.gnu.org/philosophy/why-not-lgpl.html</a>&gt;.</p>

<hr>

    <h1><a id="ACE"></a>ACE License</h1>

    <p>This license applies to the file
    <code>media/webrtc/trunk/webrtc/system_wrappers/source/condition_variable_event_win.cc</code>.</p>

<pre>
ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), and CoSMIC(TM)
(henceforth referred to as "DOC software") are copyrighted by
Douglas C. Schmidt and his research group at Washington University,
University of California, Irvine, and Vanderbilt University,
Copyright (c) 1993-2009, all rights reserved.
Since DOC software is open-source, freely available software,
you are free to use, modify, copy, and distribute--perpetually and
irrevocably--the DOC software source code and object code produced
from the source, as well as copy and distribute modified versions of
this software. You must, however, include this copyright statement
along with any code built using DOC software that you release. No
copyright statement needs to be provided if you just ship binary
executables of your software products.
</pre>


    <hr>

    <h1><a id="adobecmap"></a>Adobe CMap License</h1>

    <p>This license applies to files in the directory
    <code>browser/extensions/pdfjs/content/web/cmaps/</code>.</p>

<pre>
Copyright 1990-2009 Adobe Systems Incorporated.
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 Adobe Systems Incorporated nor the names
of its contributors may be used to endorse or promote
products derived from this software without specific prior
written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="android"></a>Android Open Source License</h1>

    <p>This license applies to various files in the Mozilla codebase,
    including those in the directory <code>gfx/skia/</code>.</p>
<!-- This is the wrong directory, what was intended? -->

<pre>
 Copyright 2009, The Android Open Source Project

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="angle"></a>ANGLE License</h1>

    <p>This license applies to files in the directory <code>gfx/angle/</code>.</p>

<pre>
Copyright (C) 2002-2010 The ANGLE Project Authors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

    Redistributions in binary form must reproduce the above
    copyright notice, this list of conditions and the following
    disclaimer in the documentation and/or other materials provided
    with the distribution.

    Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
    Ltd., nor the names of their contributors may be used to endorse
    or promote products derived from this software without specific
    prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="agg"></a>Anti-Grain Geometry Public License</h1>

    <p>This license applies to files in the directory
    <span class="path">modules/pdfium/pdfium/third_party/agg23/</span>.</p>

<pre>
Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)

Permission to copy, use, modify, sell and distribute this software
is granted provided this copyright notice appears in all copies.
This software is provided "as is" without express or implied
warranty, and with no claim as to its suitability for any purpose.
</pre>


    <hr>

    <h1><a id="acorn"></a>acorn License</h1>

    <p>This license applies to all files in
      <code>devtools/shared/acorn</code>.
    </p>
<pre>
Copyright (C) 2012 by Marijn Haverbeke &lt;marijnh@gmail.com&gt;

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Please note that some subdirectories of the CodeMirror distribution
include their own LICENSE files, and are released under different
licences.
</pre>


    <hr>

    <h1><a id="apache"></a>Apache License 2.0</h1>

    <p>This license applies to various files in the Mozilla codebase.</p>

<pre>
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS
</pre>



    <hr>

    <h1><a id="apple"></a>Apple License</h1>

    <p>This license applies to certain files in the directories <code>dom/media/webaudio/blink</code>, and <code>widget/cocoa</code>.</p>

<pre>
Copyright (C) 2008, 2009 Apple Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="apple-mozilla"></a>Apple/Mozilla NPRuntime License</h1>

    <p>This license applies to the file
    <code>dom/plugins/base/npruntime.h</code>.</p>

<pre>
Copyright &copy; 2004, Apple Computer, Inc. and The Mozilla Foundation.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the names of Apple Computer, Inc. ("Apple") or The Mozilla
Foundation ("Mozilla") nor the names of their contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY APPLE, MOZILLA AND THEIR CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE, MOZILLA OR
THEIR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="arm"></a>ARM License</h1>

    <p>This license applies to files in the directory <code>js/src/jit/arm64/vixl/</code>.</p>

<pre>
Copyright 2013, ARM Limited
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
  * Neither the name of ARM Limited nor the names of its contributors may be
    used to endorse or promote products derived from this software without
    specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="babel"></a>Babel License</h1>

    <p>This license applies to this file in the directory
      <code>devtools/client/debugger/new/debugger.js/</code>.
    </p>

<pre>
Copyright (c) 2014-2017 Sebastian McKenzie <sebmck@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>

    </hr>

    <h1><a id="babylon"></a>Babylon License</h1>

    <p>This license applies to this file in the directory
      <code>devtools/client/debugger/new/debugger.js/</code>.
    </p>

<pre>
Copyright (C) 2012-2014 by various contributors (see AUTHORS)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <h1><a id="bspatch"></a>bspatch License</h1>

    <p>This license applies to the files
    <code>toolkit/mozapps/update/updater/bspatch.cpp</code> and
    <code>toolkit/mozapps/update/updater/bspatch.h</code>.
    </p>

<pre>
Copyright 2003,2004 Colin Percival
All rights reserved

Redistribution and use in source and binary forms, with or without
modification, are permitted providing that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="cairo"></a>Cairo Component Licenses</h1>

    <p>This license, with different copyright holders, applies to certain files
     in the directory <code>gfx/cairo/</code>. The copyright
     holders and the applicable ranges of dates are as follows:

    <ul>
<li>2004 Richard D. Worth
<li>2004, 2005 Red Hat, Inc.
<li>2003 USC, Information Sciences Institute
<li>2004 David Reveman
<li>2005 Novell, Inc.
<li>2004 David Reveman, Peter Nilsson
<li>2000 Keith Packard, member of The XFree86 Project, Inc.
<li>2005 Lars Knoll &amp; Zack Rusin, Trolltech
<li>1998, 2000, 2002, 2004 Keith Packard
<li>2004 Nicholas Miell
<li>2005 Trolltech AS
<li>2000 SuSE, Inc.
<li>2003 Carl Worth
<li>1987, 1988, 1989, 1998 The Open Group
<li>1987, 1988, 1989 Digital Equipment Corporation, Maynard, Massachusetts.
<li>1998 Keith Packard
<li>2003 Richard Henderson
    </ul>

<pre>
Copyright &copy; &lt;date&gt; &lt;copyright holder&gt;

Permission to use, copy, modify, distribute, and sell this software
and its documentation for any purpose is hereby granted without
fee, provided that the above copyright notice appear in all copies
and that both that copyright notice and this permission notice
appear in supporting documentation, and that the name of
&lt;copyright holder&gt; not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.
&lt;copyright holder&gt; makes no representations about the suitability of this
software for any purpose. It is provided "as is" without express or
implied warranty.

&lt;COPYRIGHT HOLDER&gt; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt; BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>


    <hr>

    <h1><a id="chromium"></a>Chromium License</h1>

    <p>This license applies to parts of the code in:</p>
    <ul>
        <li><code>browser/extensions/mortar/host/common/opengles2-utils.jsm</code></li>
        <li><code>editor/libeditor/EditorEventListener.cpp</code></li>
        <li><code>security/sandbox/</code></li>
        <li><code>widget/cocoa/GfxInfo.mm</code></li>
    </ul>
    <p>and also some files in these directories:</p>
    <ul>
        <li><code>browser/extensions/mortar/ppapi/</code></li>
        <li><code>dom/media/webspeech/recognition/</code></li>
        <li><code>dom/plugins/</code></li>
        <li><code>gfx/ots/</code></li>
        <li><code>gfx/ycbcr/</code></li>
        <li><code>ipc/chromium/</code></li>
        <li><code>modules/pdfium/pdfium/third_party/base/</code></li>
        <li><code>media/openmax_dl/</code></li>
        <li><code>toolkit/components/downloads/chromium/</code></li>
        <li><code>toolkit/components/url-classifier/chromium/</code></li>
        <li><code>tools/profiler/</code></li>
    </ul>

<pre>
Copyright (c) 2006-2016 The Chromium Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="codemirror"></a>CodeMirror License</h1>

    <p>This license applies to all files in
      <code>devtools/client/sourceeditor/codemirror</code> and
      to specified files in the <code>devtools/client/sourceeditor/test/</code>:
    </p>
    <ul>
      <li><code>cm_comment_test.js</code></li>
      <li><code>cm_driver.js</code></li>
      <li><code>cm_mode_javascript_test.js</code></li>
      <li><code>cm_mode_test.css</code></li>
      <li><code>cm_mode_test.js</code></li>
      <li><code>cm_test.js</code></li>
    </ul>
<pre>
Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Please note that some subdirectories of the CodeMirror distribution
include their own LICENSE files, and are released under different
licences.
</pre>


    <hr>

    <h1><a id="cubic-bezier"></a>cubic-bezier License</h1>

    <p>This license applies to the file
    <code>devtools/client/shared/widgets/CubicBezierWidget.js
    </code>.</p>
<pre>
Copyright (c) 2013 Lea Verou. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</pre>


    <hr>

    <h1><a id="d3"></a>D3 License</h1>

    <p>This license applies to the file
      <code>devtools/client/shared/d3.js</code>.
    </p>
<pre>
Copyright (c) 2014, Michael Bostock
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* The name Michael Bostock may not be used to endorse or promote products
  derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="dagre-d3"></a>Dagre-D3 License</h1>

    <p>This license applies to the file
      <code>devtools/client/webaudioeditor/lib/dagre-d3.js</code>.
    </p>
<pre>
Copyright (c) 2013 Chris Pettitt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>


<hr>

<h1><a id="diff"></a>diff License</h1>

<p>This license applies to the file
<code>devtools/client/inspector/markup/test/helper_diff.js</code>.</p>

<pre>
Copyright (c) 2014 Slava

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>


<hr>

<h1><a id="dtoa"></a>dtoa License</h1>

<p>This license applies to the file
<code>nsprpub/pr/src/misc/dtoa.c</code>.</p>

<pre>
The author of this software is David M. Gay.

Copyright (c) 1991, 2000, 2001 by Lucent Technologies.

Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.

THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY.  IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
</pre>

    <hr>

    <h1><a id="fuzz-aldrin"></a>fuzz-aldrin License</h1>

    <p>This license applies to some of the code in
    <code>devtools/client/debugger/new/debugger.js</code>.</p>

<pre>
Copyright (c) 2015 Jean Christophe Roy

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

</pre>


    <hr>

    <h1><a id="hunspell-nl"></a>Dutch Spellchecking Dictionary License</h1>

    <p>This license applies to the Dutch Spellchecking Dictionary. (This
      code only ships in some localized versions of this product.)</p>

<pre>
Copyright (c) 2006, 2007 OpenTaal
Copyright (c) 2001, 2002, 2003, 2005 Simon Brouwer e.a.
Copyright (c) 1996 Nederlandstalige Tex Gebruikersgroep

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the OpenTaal, Simon Brouwer e.a., or Nederlandstalige Tex
Gebruikersgroep nor the names of its contributors may be used to endorse or
promote products derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="emojione"></a>EmojiOne License</h1>

    <p>This license applies to the emoji art contained within the bundled
emoji font file.</p>

<pre>
Copyright (c) 2016 Ranks.com Inc.
Copyright (c) 2014 Twitter, Inc and other contributors.

Creative Commons Attribution 4.0 International (CC BY 4.0)

See https://creativecommons.org/licenses/by/4.0/legalcode or
for the human readable summary: https://creativecommons.org/licenses/by/4.0/

You are free to:

Share — copy and redistribute the material in any medium or format

Adapt — remix, transform, and build upon the material for any purpose, even commercially.

The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:

Attribution — You must give appropriate credit, provide a link to the license,
and indicate if changes were made. You may do so in any reasonable manner,
but not in any way that suggests the licensor endorses you or your use.

No additional restrictions — You may not apply legal terms or technological
measures that legally restrict others from doing anything the license permits.

Notices:

You do not have to comply with the license for elements of the material in
the public domain or where your use is permitted by an applicable exception or
limitation. No warranties are given. The license may not give you all of the
permissions necessary for your intended use. For example, other rights such as
publicity, privacy, or moral rights may limit how you use the material.
</pre>


    <hr>

    <h1><a id="hunspell-ee"></a>Estonian Spellchecking Dictionary License</h1>

    <p>This license applies to precursor works to certain files which are
      part of the Estonian Spellchecking Dictionary. The
      shipped versions are under the GNU Lesser General Public License. (This
      code only ships in some localized versions of this product.)</p>

<pre>
Copyright © Institute of the Estonian Language

E-mail: litsents@eki.ee
URL: http://www.eki.ee/tarkvara/

The present Licence Agreement gives the user of this Software Product
(hereinafter: Product) the right to use the Product for whatever purpose
(incl. distribution, copying, altering, inclusion in other software, and
selling) on the following conditions:

1. The present Licence Agreement should belong unaltered to each copy ever
   made of this Product;
2. Neither the Institute of the Estonian Language (hereinafter: IEL) nor the
   author(s) of the Product will take responsibility for any detriment, direct
   or indirect, possibly ensuing from the application of the Product;
3. The IEL is ready to share the Product with other users as we wish to
   advance research on the Estonian language and to promote the use of
   Estonian in rapidly developing infotechnology, yet we refuse to bind
   ourselves to any further obligation, which means that the IEL is not
   obliged either to warrant the suitability of the Product for a specific
   purpose, to improve the software, or to provide a more detailed description
   of the underlying algorithms. (Which does not mean, though, that we may not
   do it.)

Notification Request:

As a courtesy, we would appreciate being informed whenever our linguistic
products are used to create derivative works. If you modify our software or
include it in other products, please inform us by sending e-mail to
litsents@eki.ee or by letter to

Institute of the Estonian Language
Roosikrantsi 6
10119 Tallinn
ESTONIA

Phone &amp; Fax: +372 6411443
</pre>



    <hr>

    <h1><a id="expat"></a>Expat License</h1>

    <p>This license applies to certain files in the directory
    <code>parser/expat/</code>.</p>

<pre>
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
                               and Clark Cooper
Copyright (c) 2001, 2002, 2003 Expat maintainers.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>



    <hr>


    <h1><a id="firebug"></a>Firebug License</h1>

    <p>This license applies to the code
    <code>devtools/shared/webconsole/network-helper.js</code>.</p>

<pre>
Copyright (c) 2007, Parakey Inc.
All rights reserved.

Redistribution and use of this software in source and binary forms, with or
without modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other
  materials provided with the distribution.

* Neither the name of Parakey Inc. nor the names of its
  contributors may be used to endorse or promote products
  derived from this software without specific prior
  written permission of Parakey Inc.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>



    <hr>

    <h1><a id="gfx-font-list"></a>gfxFontList License</h1>

    <p>This license applies to the files
    <code>gfx/thebes/gfxMacPlatformFontList.mm</code> and
    <code>gfx/thebes/gfxPlatformFontList.cpp</code>.
    </p>

<pre>
Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>



    <hr>

    <h1><a id="google-bsd"></a>Google BSD License</h1>

    <p>This license applies to files in the directories
    <code>toolkit/crashreporter/google-breakpad/</code>,
    <code>toolkit/components/protobuf/</code> and
    <code>devtools/client/netmonitor/src/utils/filter-text-utils.js.</code></p>

<pre>
Copyright (c) 2006, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
    * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="vp8"></a>Google VP8 License</h1>

    <p>This license applies to certain files in the directory
    <code>media/libvpx</code>.</p>
<pre>
Copyright (c) 2010, Google, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials provided
  with the distribution.

- Neither the name of Google nor the names of its contributors may
  be used to endorse or promote products derived from this software
  without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Subject to the terms and conditions of the above License, Google
hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this
section) patent license to make, have made, use, offer to sell, sell,
import, and otherwise transfer this implementation of VP8, where such
license applies only to those patent claims, both currently owned by
Google and acquired in the future, licensable by Google that are
necessarily infringed by this implementation of VP8. If You or your
agent or exclusive licensee institute or order or agree to the
institution of patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that this
implementation of VP8 or any code incorporated within this
implementation of VP8 constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any rights
granted to You under this License for this implementation of VP8
shall terminate as of the date such litigation is filed.
</pre>

    <hr>

    <h1><a id="gears-istumbler"></a>Google Gears/iStumbler License</h1>

    <p>This license applies to the file
    <code>netwerk/wifi/osx_wifi.h</code>.</p>

<pre>
Copyright 2008, Google Inc.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
 3. Neither the name of 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The contents of this file are taken from Apple80211.h from the iStumbler
project (http://www.istumbler.net). This project is released under the BSD
license with the following restrictions.

Copyright (c) 02006, Alf Watt (alf@istumbler.net). 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 iStumbler nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="gsl"></a>GSL License</h1>

    <p>This license applies to <code>mfbt/Span.h</code> and
    <code>mfbt/tests/gtest/TestSpan.cpp</code>.</p>
    <!-- https://github.com/Microsoft/GSL/blob/3819df6e378ffccf0e29465afe99c3b324c2aa70/LICENSE -->
<pre>
Copyright (c) 2015 Microsoft Corporation. All rights reserved.

This code is licensed under the MIT License (MIT).

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>


    <hr>

    <h1><a id="gyp"></a>gyp License</h1>

    <p>This license applies to certain files in the directory
    <code>media/webrtc/trunk/tools/gyp</code>.</p>
<pre>
Copyright (c) 2009 Google Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="halloc"></a>halloc License</h1>

    <p>This license applies to certain files in the directory
    <code>media/libnestegg/src</code>.</p>
<pre>
Copyright (c) 2004-2010 Alex Pankratov. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of the project nor the names of its contributors may be
      used to endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="harfbuzz"></a>HarfBuzz License</h1>

    <p>This license, with different copyright holders, applies to the files in
    the directory <code>gfx/harfbuzz/</code>.
    The copyright holders and the applicable ranges of dates are as follows:</p>

    <ul>
        <li>1998-2004  David Turner and Werner Lemberg</li>
        <li>2004, 2007, 2008, 2009, 2010  Red Hat, Inc.</li>
        <li>2006  Behdad Esfahbod</li>
        <li>2007  Chris Wilson</li>
        <li>2009  Keith Stribley &lt;devel@thanlwinsoft.org&gt;</li>
        <li>2010  Mozilla Foundation</li>
    </ul>

<pre>
Copyright (C) &lt;date&gt; &lt;copyright holder&gt;

 This is part of HarfBuzz, an OpenType Layout engine library.

Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the
above copyright notice and the following two paragraphs appear in
all copies of this software.

IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
</pre>


    <hr>

    <h1><a id="icu"></a>ICU License</h1>

    <p>This license applies to some code in the
    <code>gfx/thebes</code> directory.</p>

<pre>
ICU License - ICU 1.8.1 and later

COPYRIGHT AND PERMISSION NOTICE

Copyright (c) 1995-2012 International Business Machines Corporation and
others

All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, provided that the above copyright notice(s) and this
permission notice appear in all copies of the Software and that both the
above copyright notice(s) and this permission notice appear in supporting
documentation.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in this Software without prior written authorization of the
copyright holder.
All trademarks and registered trademarks mentioned herein are the property
of their respective owners.
</pre>
    <hr>
    <h1><a id="immutable"></a>Immutable.js License</h1>

<pre>
BSD License

For Immutable JS software

Copyright (c) 2014-2015, Facebook, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name Facebook nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="jpnic"></a>Japan Network Information Center License</h1>
    <p>This license applies to certain files in the
    directory <code>netwerk/dns/</code>.</p>
<pre>
Copyright (c) 2001,2002 Japan Network Information Center.
All rights reserved.

By using this file, you agree to the terms and conditions set forth below.

     LICENSE TERMS AND CONDITIONS

The following License Terms and Conditions apply, unless a different
license is obtained from Japan Network Information Center ("JPNIC"),
a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
Chiyoda-ku, Tokyo 101-0047, Japan.

1. Use, Modification and Redistribution (including distribution of any
   modified or derived work) in source and/or binary forms is permitted
   under this License Terms and Conditions.

2. Redistribution of source code must retain the copyright notices as they
   appear in each source code file, this License Terms and Conditions.

3. Redistribution in binary form must reproduce the Copyright Notice,
   this License Terms and Conditions, in the documentation and/or other
   materials provided with the distribution.  For the purposes of binary
   distribution the "Copyright Notice" refers to the following language:
   "Copyright (c) 2000-2002 Japan Network Information Center. All rights
   reserved."

4. The name of JPNIC may not be used to endorse or promote products
   derived from this Software without specific prior written approval of
   JPNIC.

5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL JPNIC BE LIABLE
   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
</pre>

    <hr>

    <h1><a id="jsmn"></a>JSMN License</h1>

    <p>This license applies to files in the directories
    <code>browser/extensions/mortar/json/</code>.
    </p>

<pre>
Copyright (c) 2010 Serge A. Zaitsev
Copyright (c) 2015 Andreas Gal

Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="jszip"></a>JSZip License</h1>

    <p>This license applies to the file
    <code>devtools/client/shared/vendor/jszip.js</code>.</p>

<pre>
Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, António Afonso

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="jemalloc"></a>jemalloc License</h1>

    <p>This license applies to files in the directories
    <code>memory/mozjemalloc/</code> and
    </p>

<pre>
Copyright (C) 2006-2008 Jason Evans &lt;jasone@canonware.com&gt;.
All rights reserved.
Copyright (C) 2007-2017 Mozilla Foundation.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice(s), this list of conditions and the following disclaimer as
   the first lines of this file unmodified other than the possible
   addition of one or more copyright notices.
2. Redistributions in binary form must reproduce the above copyright
   notice(s), this list of conditions and the following disclaimer in
   the documentation and/or other materials provided with the
   distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="jquery"></a>jQuery License</h1>

    <p>This license applies to all copies of jQuery in the code.</p>

<pre>
Copyright (c) 2010 John Resig, http://jquery.com/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="k_exp"></a>k_exp License</h1>

    <p>This license applies to the file
    <code>modules/fdlibm/src/k_exp.cpp</code>.
    </p>

<pre>
Copyright (c) 2011 David Schultz &lt;das@FreeBSD.ORG&gt;
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="khronos"></a>Khronos group License</h1>

    <p>This license applies to the following files:</p>

    <ul>
      <li><code>openmax_dl/dl/api/omxtypes.h</code></li>
      <li><code>openmax_dl/dl/sp/api/omxSP.h</code></li>
    </ul>

    <p>and also some files in these directories:</p>

    <ul>
      <li><code>browser/extensions/mortar/ppapi/</code></li>
    </ul>

<pre>
Copyright 2005-2008 The Khronos Group Inc. All Rights Reserved.

These materials are protected by copyright laws and contain material
proprietary to the Khronos Group, Inc.  You may use these materials
for implementing Khronos specifications, without altering or removing
any trademark, copyright or other notice from the specification.

Khronos Group makes no, and expressly disclaims any, representations
or warranties, express or implied, regarding these materials, including,
without limitation, any implied warranties of merchantability or fitness
for a particular purpose or non-infringement of any intellectual property.
Khronos Group makes no, and expressly disclaims any, warranties, express
or implied, regarding the correctness, accuracy, completeness, timeliness,
and reliability of these materials.

Under no circumstances will the Khronos Group, or any of its Promoters,
Contributors or Members or their respective partners, officers, directors,
employees, agents or representatives be liable for any damages, whether
direct, indirect, special or consequential damages for lost revenues,
lost profits, or otherwise, arising from or in connection with these
materials.

Khronos and OpenMAX are trademarks of the Khronos Group Inc.
</pre>

    <hr>

    <h1><a id="kiss_fft"></a>Kiss FFT License</h1>

    <p>This license applies to files in the directory
      <code>media/kiss_fft/</code>.</p>

<pre>
Copyright (c) 2003-2010 Mark Borgerding

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the author nor the names of any contributors may be used to
      endorse or promote products derived from this software without specific
      prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="lcms"></a>lcms License</h1>

    <p>This license applies to files in the directory
      <span class="path">modules/pdfium/pdfium/third_party/lcms2-2.6/</span>.</p>

<pre>
Copyright (c) 1998-2014 Marti Maria Saguer

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="libc++"></a>libc++ License</h1>

    <p class="correctme">This license applies to the copy of libc++ obtained
    from the Android NDK.</p>

<pre>
Copyright (c) 2009-2014 by the contributors listed in the libc++ CREDITS.TXT

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>


    <h1><a id="libcubeb"></a>libcubeb License</h1>

    <p class="correctme">This license applies to files in the directory
    <code>media/libcubeb</code>.
    </p>

<pre>
Copyright &copy; 2011 Mozilla Foundation

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>

    <hr>

    <h1><a id="libevent"></a>libevent License</h1>

    <p>This license applies to files in the directory
    <code>ipc/chromium/src/third_party/libevent/</code>.
    </p>

<pre>
Copyright 2000-2002 Niels Provos &lt;provos@citi.umich.edu&gt;
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="libffi"></a>libffi License</h1>

    <p>This license applies to files in the directory
    <code>js/src/ctypes/libffi/</code>.
    </p>

<pre>
libffi - Copyright (c) 1996-2008 Red Hat, Inc and others.
See source files for details.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
``Software''), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>


    <hr>

    <h1><a id="libjingle"></a>libjingle License</h1>

    <p>This license applies to the following files:</p>
    <ul>
      <li><code>media/mtransport/sigslot.h</code></li>
      <li><code>media/mtransport/test/gtest_utils.h</code></li>
    </ul>

<pre>
Copyright (c) 2004--2005, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * The name of the author may not be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="libnestegg"></a>libnestegg License</h1>

    <p>This license applies to certain files in the directory
    <code>media/libnestegg</code>.
    </p>

<pre>
Copyright &copy; 2010 Mozilla Foundation

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>

    <hr>

    <h1><a id="libsoundtouch"></a>libsoundtouch License</h1>

    <p>This license applies to certain files in the directory
    <code>media/libsoundtouch/src/</code>.
    </p>

<pre>
The SoundTouch Library Copyright &copy; Olli Parviainen 2001-2012

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
</pre>

    <hr>

    <h1><a id="libyuv"></a>libyuv License</h1>

    <p>This license applies to files in the directory
    <code>media/libyuv</code> except
    for the file <code>media/libyuv/source/x86inc.asm</code>.
    </p>

<pre>
Copyright (c) 2011, The LibYuv project authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.

  * Neither the name of Google nor the names of its contributors may
    be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="hunspell-lt"></a>Lithuanian Spellchecking Dictionary License</h1>

    <p>This license applies to the Lithuanian Spellchecking Dictionary. (This
      code only ships in some localized versions of this product.)</p>

<pre>
Copyright (c) 2000-2013, Albertas Agejevas and contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders 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 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 ALBERTAS AGEJEVAS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="lodash"></a>License - lodash</h1>

    <p>This license applies to some of the code in
    <code>devtools/client/debugger/new/debugger.js</code>.</p>

<pre>
Copyright JS Foundation and other contributors <https://js.foundation/>

Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>

This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash

The following license applies to all parts of this software except as
documented below:

====

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.

====

Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.

CC0: http://creativecommons.org/publicdomain/zero/1.0/

====

Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.

</pre>

    <hr>

    <h1><a id="microformatsshiv"></a>MIT license — microformat-shiv</h1>

    <p>This license applies to some files in the directory
    <code>toolkit/components/microformats</code>.</p>

<pre>
MIT license — microformat-shiv

Copyright (c) 2012-2013 Glenn Jones

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>


    <hr>

    <h1><a id="myspell"></a>MySpell License</h1>

    <p>This license applies to some files in the directory
    <code>extensions/spellcheck/hunspell</code>.</p>

<pre>
Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
And Contributors.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. All modifications to the source code must be clearly marked as
   such.  Binary redistributions based on modified source code
   must be clearly marked as modified versions in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>


<hr>

<h1><a id="naturalSort"></a>naturalSort License</h1>

<p>This license applies to <code>devtools/client/shared/natural-sort.js</code>.</p>

<pre>
The MIT License (MIT)

Copyright (c) 2014 Gabriel Llamas

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="nicer"></a>nICEr License</h1>

    <p>This license applies to certain files in the directory
    <code>media/mtransport/third_party/nICEr</code>.</p>

<pre>
   Copyright (C) 2007, Adobe Systems Inc.
   Copyright (C) 2007-2008, Network Resonance, Inc.

Each source file bears an individual copyright notice.

The following license applies to this distribution as a whole.


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 Adobe Systems, Network Resonance nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="openaes"></a>OpenAES License</h1>

    <p>This license applies to certain files in the directory
    <code>media/gmp-clearkey/0.1/openaes</code>.
    </p>

<pre>
Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  - Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  - Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="openvision"></a>OpenVision License</h1>

    <p>This license applies to the file
    <code>extensions/auth/gssapi.h</code>.</p>

<pre>
Copyright 1993 by OpenVision Technologies, Inc.

Permission to use, copy, modify, distribute, and sell this software
and its documentation for any purpose is hereby granted without fee,
provided that the above copyright notice appears in all copies and
that both that copyright notice and this permission notice appear in
supporting documentation, and that the name of OpenVision not be used
in advertising or publicity pertaining to distribution of the software
without specific, written prior permission. OpenVision makes no
representations about the suitability of this software for any
purpose.  It is provided "as is" without express or implied warranty.

OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
</pre>


    <hr>

    <h1><a id="openvr"></a>OpenVR License</h1>

    <p>This license applies to certain files in the directory
    <code>gfx/vr/openvr</code>.</p>
<pre>
Copyright (c) 2015, Valve Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


<hr>

    <h1><a id="node-md5"></a>node-md5 License</h1>

    <p>This license applies to some of the code in
    <code>devtools/client/debugger/new/debugger.js</code>.</p>

<pre>
Copyright © 2011-2012, Paul Vorbach.
Copyright © 2009, Jeff Mott.

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 Crypto-JS nor the names of its contributors may be used to
  endorse or promote products derived from this software without specific prior
  written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="node-properties"></a>node-properties License</h1>

    <p>This license applies to
    <code>devtools/shared/node-properties/node-properties.js</code>.</p>

<pre>
The MIT License (MIT)

Copyright (c) 2014 Gabriel Llamas

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="nrappkit"></a>nrappkit License</h1>

    <p>This license applies to certain files in the directory
    <code>media/mtransport/third_party/nrappkit</code>.</p>

<pre>
Copyright (C) 2001-2007, Network Resonance, Inc.
All Rights Reserved

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. Neither the name of Network Resonance, Inc. nor the name of any
   contributors to this software may be used to endorse or promote
   products derived from this software without specific prior written
   permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</pre>

    <p>This license applies to certain files in the directory
    <code>media/mtransport/third_party/nrappkit</code>.</p>

<pre>
Copyright (C) 1999-2003 RTFM, Inc.
All Rights Reserved

This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
<ekr@rtfm.com> and licensed by RTFM, Inc.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
   must display the following acknowledgement:

   This product includes software developed by Eric Rescorla for
   RTFM, Inc.

4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
   used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ERIC RESCORLA AND RTFM ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
oDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>

   <p>Note that RTFM, Inc. has waived clause (3) above as of June 20, 2012
   for files appearing in this distribution. This waiver applies only to
   files included in this distribution. it does not apply to any other
   part of ssldump not included in this distribution.</p>

    <p>This license applies to the file <code>media/mtransport/third_party/nrappkit/src/port/generic/include/sys/queue.h</code>.</p>

<pre>
Copyright (c) 1991, 1993
	The Regents of the University of California.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
4. Neither the name of the University nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>


  <p>This license applies to the file:
  <code>media/mtransport/third_party/nrappkit/src/util/util.c</code>.</p>

<pre>
Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="praton"></a>praton License</h1>

    <p>This license applies to the file
    <code>nsprpub/pr/src/misc/praton.c</code>.</p>

<pre>
Copyright (c) 1983, 1990, 1993
   The Regents of the University of California.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
4. Neither the name of the University nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.


Portions Copyright (c) 1993 by Digital Equipment Corporation.

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies, and that
the name of Digital Equipment Corporation not be used in advertising or
publicity pertaining to distribution of the document or software without
specific, written prior permission.

THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
</pre>


    <hr>

    <h1><a id="praton1"></a>praton and inet_ntop License</h1>

    <p>This license applies to the files
    <code>nsprpub/pr/src/misc/praton.c</code> and
    <code>media/mtransport/third_party/nrappkit/src/util/util.c</code>.</p>

<pre>
Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
Portions Copyright (c) 1996-1999 by Internet Software Consortium.

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>


    <hr>

    <h1><a id="pbkdf2-sha256"></a>pbkdf2_sha256 License</h1>

    <p>This license applies to the code
    <code>mozglue/android/pbkdf2_sha256.c</code> and
    <code>mozglue/android/pbkdf2_sha256.h</code>.
    </p>

<pre>
Copyright 2005,2007,2009 Colin Percival
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="pdfium"></a>PDFium License</h1>

    <p>This license applies to files in the directory
      <span class="path">modules/pdfium/pdfium/</span>.
    </p>

<pre>
Copyright 2014 PDFium Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="qcms"></a>qcms License</h1>

    <p>This license applies to certain files in the directory
      <code>gfx/qcms/</code>.</p>
<pre>
Copyright (C) 2009 Mozilla Corporation
Copyright (C) 1998-2007 Marti Maria

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject
to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>


    <hr>

    <h1><a id="qrcode-generator"></a>QR Code Generator License</h1>

    <p>This license applies to certain files in the directory
      <code>devtools/shared/qrcode/encoder/</code>.</p>
<pre>
Copyright (c) 2009 Kazuhiko Arase

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</pre>

    <hr>

    <h1><a id="raven-js"></a>Raven.js License</h1>

    <p>This license applies to the file
      <code>browser/extensions/screenshots/webextension/build/raven.js</code>.</p>
<pre>
Copyright (c) 2014 Matt Robenolt and other contributors
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="react"></a>React License</h1>

    <p>This license applies to various files in the Mozilla codebase.</p>

<pre>
Copyright (c) 2013-2015, Facebook, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name Facebook nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="react-intl"></a>React Intl License</h1>

    <p>This license applies to the files
    <code>browser/extensions/activity-stream/vendor/react-intl.js</code>.</p>
<pre>
Copyright 2014 Yahoo Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

    * Neither the name of the Yahoo 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 YAHOO! INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="react-redux"></a>React-Redux License</h1>

    <p>This license applies to the files
    <code>devtools/client/shared/vendor/react-redux.js</code> and
    <code>browser/extensions/activity-stream/vendor/react-redux.js</code>.</p>
<pre>
Copyright (c) 2015 Dan Abramov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

    <hr>

    <h1><a id="react-virtualized"></a>React Virtualized License</h1>

    <p>This license applies to the file
    <code>devtools/client/shared/vendor/react-virtualized.js</code>.</p>
<pre>
Copyright (c) 2015 Brian Vaughn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

    <hr>

    <h1><a id="xdg"></a>Red Hat xdg_user_dir_lookup License</h1>

    <p>This license applies to the
    <code>xdg_user_dir_lookup</code> function in
    <code>xpcom/io/SpecialSystemDirectory.cpp</code>.</p>

<pre>
Copyright (c) 2007 Red Hat, Inc.

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

    <hr>

    <h1><a id="redux"></a>Redux License</h1>

    <p>This license applies to the file
    <code>devtools/client/shared/vendor/redux.js</code> and
    <code>browser/extensions/activity-stream/vendor/Redux.jsm</code>.</p>
<pre>
Copyright (c) 2015 Dan Abramov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

<hr>

<h1><a id="reselect"></a>Reselect License</h1>

<p>This license applies to the file
<code>devtools/client/shared/vendor/reselect.js</code> and
<code>browser/extensions/activity-stream/vendor/reselect.js</code>.</p>
<pre>
The MIT License (MIT)

Copyright (c) 2015-2016 Reselect Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

    <hr>

    <h1><a id="hunspell-ru"></a>Russian Spellchecking Dictionary License</h1>

    <p>This license applies to the Russian Spellchecking Dictionary. (This
      code only ships in some localized versions of this product.)</p>

<pre>
* Copyright (c) 1997-2008, Alexander I. Lebedev

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.
* Modified versions must be clearly marked as such.
* The name of Alexander I. Lebedev may not be used to endorse or promote
  products derived from this software without specific prior written
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="sctp"></a>SCTP Licenses</h1>

    <p>These licenses apply to certain files in the directory
    <code>netwerk/sctp/src/</code>.</p>

<pre>
Copyright (c) 2009-2010 Brad Penoff
Copyright (c) 2009-2010 Humaira Kamal
Copyright (c) 2011-2012 Irene Ruengeler
Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved.
Copyright (c) 2010-2012, by Randall Stewart. All rights reserved.
Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
Copyright (c) 2008-2012, by Brad Penoff. All rights reserved.
Copyright (c) 1980, 1982, 1986, 1987, 1988, 1990, 1993
  The Regents of the University of California.
Copyright (c) 2005 Robert N. M. Watson All rights reserved.
Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
a) Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
b) Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in
   the documentation and/or other materials provided with the distribution.
c) Neither the name of Cisco Systems, Inc, the name of the university,
   the WIDE project, nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="skia"></a>Skia License</h1>

    <p>This license applies to certain files in the directory
    <code>gfx/skia/</code>.</p>

<pre>
Copyright (c) 2011 Google Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="snappy"></a>Snappy License</h1>

    <p>This license applies to certain files in the directory
    <code>other-licenses/snappy/</code>.</p>

<pre>
Copyright 2011, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
    * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="sprintf.js"></a>sprintf.js License</h1>

    <p>This license applies to
    <code>devtools/shared/sprintfjs/sprintf.js</code>.</p>

<pre>
Copyright (c) 2007-2016, Alexandru Marasteanu &lt;hello [at) alexei (dot] ro&gt;
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name of this software nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

    <hr>

    <h1><a id="sunsoft"></a>SunSoft License</h1>

    <p>This license applies to the
      <code>ICC_H</code> block in
      <code>gfx/qcms/qcms.h</code>.</p>

<pre>
Copyright (c) 1994-1996 SunSoft, Inc.

                    Rights Reserved

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restrict-
ion, including without limitation the rights to use, copy, modify,
merge, publish distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
INFRINGEMENT.  IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of SunSoft, Inc.
shall not be used in advertising or otherwise to promote the
sale, use or other dealings in this Software without written
authorization from SunSoft Inc.
</pre>


    <hr>

    <h1><a id="superfasthash"></a>SuperFastHash License</h1>

    <p>This license applies to files in the directory
      <code>security/sandbox/chromium/base/third_party/superfasthash/</code>.</p>

<pre>
Copyright (c) 2010, Paul Hsieh
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 my name, Paul Hsieh, nor the names of any other contributors to the
  code use may not be used to endorse or promote products derived from this
  software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="unicode"></a>Unicode License</h1>

    <p>This license applies to files in the <code>intl/icu</code>
    and <code>intl/tzdata</code> directories and certain files in
    the <code>js/src/vm</code> directory.</p>
    </p>

<pre>
COPYRIGHT AND PERMISSION NOTICE

Copyright © 1991-2016 Unicode, Inc. All rights reserved.
Distributed under the Terms of Use in http://www.unicode.org/copyright.html.

Permission is hereby granted, free of charge, to any person obtaining
a copy of the Unicode data files and any associated documentation
(the "Data Files") or Unicode software and any associated documentation
(the "Software") to deal in the Data Files or Software
without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, and/or sell copies of
the Data Files or Software, and to permit persons to whom the Data Files
or Software are furnished to do so, provided that either
(a) this copyright and permission notice appear with all copies
of the Data Files or Software, or
(b) this copyright and permission notice appear in associated
Documentation.

THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT OF THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THE DATA FILES OR SOFTWARE.

Except as contained in this notice, the name of a copyright holder
shall not be used in advertising or otherwise to promote the sale,
use or other dealings in these Data Files or Software without prior
written authorization of the copyright holder.

---------------------

Third-Party Software Licenses

This section contains third-party software notices and/or additional
terms for licensed third-party software components included within ICU
libraries.

1. ICU License - ICU 1.8.1 to ICU 57.1

COPYRIGHT AND PERMISSION NOTICE

Copyright (c) 1995-2016 International Business Machines Corporation and others
All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, provided that the above
copyright notice(s) and this permission notice appear in all copies of
the Software and that both the above copyright notice(s) and this
permission notice appear in supporting documentation.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Except as contained in this notice, the name of a copyright holder
shall not be used in advertising or otherwise to promote the sale, use
or other dealings in this Software without prior written authorization
of the copyright holder.

All trademarks and registered trademarks mentioned herein are the
property of their respective owners.

2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)

 #     The Google Chrome software developed by Google is licensed under
 # the BSD license. Other software included in this distribution is
 # provided under other licenses, as set forth below.
 #
 #  The BSD License
 #  http://opensource.org/licenses/bsd-license.php
 #  Copyright (C) 2006-2008, Google Inc.
 #
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
 #
 #  Redistributions of source code must retain the above copyright notice,
 # this list of conditions and the following disclaimer.
 #  Redistributions in binary form must reproduce the above
 # copyright notice, this list of conditions and the following
 # disclaimer in the documentation and/or other materials provided with
 # the distribution.
 #  Neither the name of  Google Inc. nor the names of its
 # contributors may be used to endorse or promote products derived from
 # this software without specific prior written permission.
 #
 #
 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
 #
 #  The word list in cjdict.txt are generated by combining three word lists
 # listed below with further processing for compound word breaking. The
 # frequency is generated with an iterative training against Google web
 # corpora.
 #
 #  * Libtabe (Chinese)
 #    - https://sourceforge.net/project/?group_id=1519
 #    - Its license terms and conditions are shown below.
 #
 #  * IPADIC (Japanese)
 #    - http://chasen.aist-nara.ac.jp/chasen/distribution.html
 #    - Its license terms and conditions are shown below.
 #
 #  ---------COPYING.libtabe ---- BEGIN--------------------
 #
 #  /*
 #   * Copyrighy (c) 1999 TaBE Project.
 #   * Copyright (c) 1999 Pai-Hsiang Hsiao.
 #   * All rights reserved.
 #   *
 #   * Redistribution and use in source and binary forms, with or without
 #   * modification, are permitted provided that the following conditions
 #   * are met:
 #   *
 #   * . Redistributions of source code must retain the above copyright
 #   *   notice, this list of conditions and the following disclaimer.
 #   * . Redistributions in binary form must reproduce the above copyright
 #   *   notice, this list of conditions and the following disclaimer in
 #   *   the documentation and/or other materials provided with the
 #   *   distribution.
 #   * . Neither the name of the TaBE Project nor the names of its
 #   *   contributors may be used to endorse or promote products derived
 #   *   from this software without specific prior written permission.
 #   *
 #   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 #   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 #   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 #   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 #   * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 #   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 #   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 #   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 #   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 #   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 #   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 #   * OF THE POSSIBILITY OF SUCH DAMAGE.
 #   */
 #
 #  /*
 #   * Copyright (c) 1999 Computer Systems and Communication Lab,
 #   *                    Institute of Information Science, Academia
 #       *                    Sinica. All rights reserved.
 #   *
 #   * Redistribution and use in source and binary forms, with or without
 #   * modification, are permitted provided that the following conditions
 #   * are met:
 #   *
 #   * . Redistributions of source code must retain the above copyright
 #   *   notice, this list of conditions and the following disclaimer.
 #   * . Redistributions in binary form must reproduce the above copyright
 #   *   notice, this list of conditions and the following disclaimer in
 #   *   the documentation and/or other materials provided with the
 #   *   distribution.
 #   * . Neither the name of the Computer Systems and Communication Lab
 #   *   nor the names of its contributors may be used to endorse or
 #   *   promote products derived from this software without specific
 #   *   prior written permission.
 #   *
 #   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 #   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 #   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 #   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 #   * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 #   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 #   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 #   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 #   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 #   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 #   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 #   * OF THE POSSIBILITY OF SUCH DAMAGE.
 #   */
 #
 #  Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
 #      University of Illinois
 #  c-tsai4@uiuc.edu  http://casper.beckman.uiuc.edu/~c-tsai4
 #
 #  ---------------COPYING.libtabe-----END--------------------------------
 #
 #
 #  ---------------COPYING.ipadic-----BEGIN-------------------------------
 #
 #  Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
 #  and Technology.  All Rights Reserved.
 #
 #  Use, reproduction, and distribution of this software is permitted.
 #  Any copy of this software, whether in its original form or modified,
 #  must include both the above copyright notice and the following
 #  paragraphs.
 #
 #  Nara Institute of Science and Technology (NAIST),
 #  the copyright holders, disclaims all warranties with regard to this
 #  software, including all implied warranties of merchantability and
 #  fitness, in no event shall NAIST be liable for
 #  any special, indirect or consequential damages or any damages
 #  whatsoever resulting from loss of use, data or profits, whether in an
 #  action of contract, negligence or other tortuous action, arising out
 #  of or in connection with the use or performance of this software.
 #
 #  A large portion of the dictionary entries
 #  originate from ICOT Free Software.  The following conditions for ICOT
 #  Free Software applies to the current dictionary as well.
 #
 #  Each User may also freely distribute the Program, whether in its
 #  original form or modified, to any third party or parties, PROVIDED
 #  that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
 #  on, or be attached to, the Program, which is distributed substantially
 #  in the same form as set out herein and that such intended
 #  distribution, if actually made, will neither violate or otherwise
 #  contravene any of the laws and regulations of the countries having
 #  jurisdiction over the User or the intended distribution itself.
 #
 #  NO WARRANTY
 #
 #  The program was produced on an experimental basis in the course of the
 #  research and development conducted during the project and is provided
 #  to users as so produced on an experimental basis.  Accordingly, the
 #  program is provided without any warranty whatsoever, whether express,
 #  implied, statutory or otherwise.  The term "warranty" used herein
 #  includes, but is not limited to, any warranty of the quality,
 #  performance, merchantability and fitness for a particular purpose of
 #  the program and the nonexistence of any infringement or violation of
 #  any right of any third party.
 #
 #  Each user of the program will agree and understand, and be deemed to
 #  have agreed and understood, that there is no warranty whatsoever for
 #  the program and, accordingly, the entire risk arising from or
 #  otherwise connected with the program is assumed by the user.
 #
 #  Therefore, neither ICOT, the copyright holder, or any other
 #  organization that participated in or was otherwise related to the
 #  development of the program and their respective officials, directors,
 #  officers and other employees shall be held liable for any and all
 #  damages, including, without limitation, general, special, incidental
 #  and consequential damages, arising out of or otherwise in connection
 #  with the use or inability to use the program or any product, material
 #  or result produced or otherwise obtained by using the program,
 #  regardless of whether they have been advised of, or otherwise had
 #  knowledge of, the possibility of such damages at any time during the
 #  project or thereafter.  Each user will be deemed to have agreed to the
 #  foregoing by his or her commencement of use of the program.  The term
 #  "use" as used herein includes, but is not limited to, the use,
 #  modification, copying and distribution of the program and the
 #  production of secondary products from the program.
 #
 #  In the case where the program, whether in its original form or
 #  modified, was distributed or delivered to or received by a user from
 #  any person, organization or entity other than ICOT, unless it makes or
 #  grants independently of ICOT any specific warranty to the user in
 #  writing, such person, organization or entity, will also be exempted
 #  from and not be held liable to the user for any such damages as noted
 #  above as far as the program is concerned.
 #
 #  ---------------COPYING.ipadic-----END----------------------------------

3. Lao Word Break Dictionary Data (laodict.txt)

 #  Copyright (c) 2013 International Business Machines Corporation
 #  and others. All Rights Reserved.
 #
 # Project: http://code.google.com/p/lao-dictionary/
 # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
 # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
 #              (copied below)
 #
 #  This file is derived from the above dictionary, with slight
 #  modifications.
 #  ----------------------------------------------------------------------
 #  Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
 #  modification,
 #  are permitted provided that the following conditions are met:
 #
 #
 # Redistributions of source code must retain the above copyright notice, this
 #  list of conditions and the following disclaimer. Redistributions in
 #  binary form must reproduce the above copyright notice, this list of
 #  conditions and the following disclaimer in the documentation and/or
 #  other materials provided with the distribution.
 #
 #
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 # OF THE POSSIBILITY OF SUCH DAMAGE.
 #  --------------------------------------------------------------------------

4. Burmese Word Break Dictionary Data (burmesedict.txt)

 #  Copyright (c) 2014 International Business Machines Corporation
 #  and others. All Rights Reserved.
 #
 #  This list is part of a project hosted at:
 #    github.com/kanyawtech/myanmar-karen-word-lists
 #
 #  --------------------------------------------------------------------------
 #  Copyright (c) 2013, LeRoy Benjamin Sharon
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
 #  modification, are permitted provided that the following conditions
 #  are met: Redistributions of source code must retain the above
 #  copyright notice, this list of conditions and the following
 #  disclaimer.  Redistributions in binary form must reproduce the
 #  above copyright notice, this list of conditions and the following
 #  disclaimer in the documentation and/or other materials provided
 #  with the distribution.
 #
 #    Neither the name Myanmar Karen Word Lists, nor the names of its
 #    contributors may be used to endorse or promote products derived
 #    from this software without specific prior written permission.
 #
 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 #  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 #  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 #  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
 #  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 #  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 #  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 #  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 #  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 #  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 #  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 #  SUCH DAMAGE.
 #  --------------------------------------------------------------------------

5. Time Zone Database

  ICU uses the public domain data and code derived from Time Zone
Database for its time zone support. The ownership of the TZ database
is explained in BCP 175: Procedure for Maintaining the Time Zone
Database section 7.

 # 7.  Database Ownership
 #
 #    The TZ database itself is not an IETF Contribution or an IETF
 #    document.  Rather it is a pre-existing and regularly updated work
 #    that is in the public domain, and is intended to remain in the
 #    public domain.  Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
 #    not apply to the TZ Database or contributions that individuals make
 #    to it.  Should any claims be made and substantiated against the TZ
 #    Database, the organization that is providing the IANA
 #    Considerations defined in this RFC, under the memorandum of
 #    understanding with the IETF, currently ICANN, may act in accordance
 #    with all competent court orders.  No ownership claims will be made
 #    by ICANN or the IETF Trust on the database or the code.  Any person
 #    making a contribution to the database or code waives all rights to
 #    future claims in that contribution or in the TZ Database.</pre>


    <hr>

    <h1><a id="ucal"></a>University of California License</h1>

    <p>This license applies to the following files or, in the case of
    directories, certain files in those directories:</p>

    <ul>
      <li><code>dbm/</code></li>
      <li><code>db/mork/src/morkQuickSort.cpp</code></li>
      <li><code>xpcom/ds/nsQuickSort.cpp</code></li>
      <li><code>nsprpub/pr/src/misc/praton.c</code></li>
      <li><code>media/mtransport/third_party/nICEr/src/stun/addrs.c</code></li>
    </ul>

<pre>
Copyright (c) 1990, 1993
 The Regents of the University of California.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
[3 Deleted as of 22nd July 1999; see
    <a href="ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change">ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change</a>
    for details]
4. Neither the name of the University nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="hunspell-en-US"></a>US English Spellchecking Dictionary Licenses</h1>

    <p>These licenses apply to certain files in the directory
      <code>extensions/spellcheck/locales/en-US/hunspell/</code>. (This
      code only ships in some localized versions of this product.)</p>

<pre>
Different parts of the US English dictionary (SCOWL) are subject to the
following licenses as  shown below. For additional details, sources, credits,
and public domain references, see <a href="https://dxr.mozilla.org/mozilla-central/source/extensions/spellcheck/locales/en-US/hunspell/README_en_US.txt">README.txt</a>.

The collective work of the Spell Checking Oriented Word Lists (SCOWL) is under
the  following copyright:

Copyright 2000-2007 by Kevin Atkinson
Permission to use, copy, modify, distribute and sell these word lists, the
associated scripts, the output created from the scripts, and its documentation
for any purpose is hereby granted without fee, provided that the above
copyright notice appears in all copies and that both that copyright notice and
this permission notice appear in supporting documentation. Kevin Atkinson makes
no representations about the suitability of this array for any purpose. It is
provided  "as is" without express or implied warranty.

The WordNet database is under the following copyright:

This software and database is being provided to you, the LICENSEE, by Princeton
University under the following license.  By obtaining, using and/or copying
this software and database, you agree that you have read, understood, and will
comply with these terms and conditions:
Permission to use, copy, modify and distribute this software and database and
its documentation for any purpose and without fee or royalty is hereby granted,
provided that you agree to comply with the following copyright notice and
statements, including the disclaimer, and that the same appear on ALL copies of
the software, database and documentation, including modifications that you make
for internal use or for distribution.
WordNet 1.6 Copyright 1997 by Princeton University.  All rights reserved.
THIS SOFTWARE AND DATABASE IS PROVIDED "AS IS" AND PRINCETON UNIVERSITY
MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF
EXAMPLE, BUT NOT LIMITATION, PRINCETON UNIVERSITY MAKES NO
REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY
PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE, DATABASE OR
DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS,
TRADEMARKS OR OTHER RIGHTS.
The name of Princeton University or Princeton may not be used in advertising or
publicity pertaining to distribution of the software and/or database.  Title to
copyright in this software, database and any associated documentation shall at
all times remain with Princeton University and LICENSEE agrees to preserve same.

The "UK Advanced Cryptics Dictionary" is under the following copyright:

Copyright (c) J Ross Beresford 1993-1999. All Rights Reserved.
The following restriction is placed on the use of this publication: if The UK
Advanced Cryptics Dictionary is used in a software package or redistributed in
any form, the copyright notice must be prominently displayed and the text of
this document must be included verbatim.   There are no other restrictions: I
would like to see the list distributed as widely as possible.

Various parts are under the Ispell copyright:

Copyright 1993, Geoff Kuenning, Granada Hills, CA
All rights reserved.   Redistribution and use in source and binary forms, with
or without modification, are permitted provided that the following conditions
are met:
  1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
  3. All modifications to the source code must be clearly marked as such.
Binary redistributions based on modified source code must be clearly marked as
modified versions in the documentation and/or other materials provided with
the distribution.
  (clause 4 removed with permission from Geoff Kuenning)
  5. The name of Geoff Kuenning may not be used to endorse or promote products
derived from this software without specific prior written permission.
 THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS  IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

Additional Contributors:

 Alan Beale &lt;biljir@pobox.com&gt;
 M Cooper &lt;thegrendel@theriver.com&gt;
</pre>



    <hr>

    <h1><a id="v8"></a>V8 License</h1>

    <p>This license applies to certain files in the directories
      <code>js/src/irregexp</code>,
      <code>js/src/builtin</code>,
      <code>js/src/jit/arm</code> and
      <code>js/src/jit/mips</code>.
    </p>
<pre>
Copyright 2006-2012 the V8 project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
    * Neither the name of Google Inc. nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>

<hr>

<h1><a id="validator"></a>Validator License</h1>

<p>This license applies to certain files in the directory
  <code>devtools/shared/stringvalidator/</code>,
</p>
<pre>

Copyright (c) 2016 Chris O"Hara <cohara87@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</pre>

    <hr>


    <h1><a id="vtune"></a>VTune License</h1>

    <p>This license applies to certain files in the directory
    <code>js/src/vtune</code>.</p>
<pre>
Copyright (c) 2011 Intel Corporation.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.
  * Neither the name of Intel Corporation nor the names of its
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="webrtc"></a>WebRTC License</h1>

    <p>This license applies to certain files in the directory
    <code>media/webrtc/trunk</code>.</p>
<pre>
Copyright (c) 2011, The WebRTC project authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.

  * Neither the name of Google nor the names of its contributors may
    be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="x264"></a>x264 License</h1>

    <p>This license applies to the file <code>
    media/webrtc/trunk/third_party/libyuv/source/x86inc.asm</code>.
    </p>

<pre>
Copyright (C) 2005-2012 x264 project

Authors: Loren Merritt <lorenm@u.washington.edu>
         Anton Mitrofanov <BugMaster@narod.ru>
         Jason Garrett-Glaser <darkshikari@gmail.com>
         Henrik Gramner <hengar-6@student.ltu.se>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>

    <hr>

    <h1><a id="xiph"></a>Xiph.org Foundation License</h1>

    <p>This license applies to files in the following directories
      with the specified copyright year ranges:</p>
    <ul>
      <li><code>media/libogg/</code>, 2002</li>
      <li><code>media/libtheora/</code>, 2002-2007</li>
      <li><code>media/libvorbis/</code>, 2002-2004</li>
      <li><code>media/libtremor/</code>, 2002-2010</li>
      <li><code>media/libspeex_resampler/</code>, 2002-2008</li>
    </ul>

<pre>
Copyright (c) &lt;year&gt;, Xiph.org Foundation

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

- Neither the name of the Xiph.org Foundation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>


    <hr>

    <h1><a id="other-notices"></a>Other Required Notices</h1>

    <ul>
      <li>This software is based in part on the work of the Independent
          JPEG Group.</li>
      <li>Portions of the OS/2 and Android versions
          of this software are copyright &copy;1996-2012
          <a href="http://www.freetype.org/">The FreeType Project</a>.
          All rights reserved.</li>
    </ul>


    <hr>

    <h1><a id="optional-notices"></a>Optional Notices</h1>

    <p>Some permissive software licenses request but do not require an
    acknowledgement of the use of their software. We are very grateful
    to the following people and projects for their contributions to
    this product:</p>

    <ul>
      <li>The <a href="http://www.zlib.net/">zlib</a> compression library
          (Jean-loup Gailly, Mark Adler and team)</li>
      <li>The <a href="http://www.bzip.org/">bzip2</a> compression library
          (Julian Seward)</li>
      <li>The <a href="http://www.libpng.org/pub/png/">libpng</a> graphics library
          (Glenn Randers-Pehrson and team)</li>
      <li>The <a href="http://www.sqlite.org/">sqlite</a> database engine
          (D. Richard Hipp and team)</li>
      <li>The <a href="http://nsis.sourceforge.net/">Nullsoft Scriptable Install System</a>
          (Amir Szekely and team)</li>
      <li>The <a href="http://mattmccutchen.net/bigint/">C++ Big Integer Library</a>
          (Matt McCutchen)</li>
    </ul>




    <hr>

    <h1><a id="proprietary-notices"></a>Proprietary Operating System Components</h1>

    <p>Under some circumstances, under our
    <a href="http://www.mozilla.org/foundation/licensing/binary-components/">binary components policy</a>,
    Mozilla may decide to include additional
    operating system vendor code with the installer of our products designed
    for that vendor's proprietary platform, to make our products work well on
    that specific operating system. The following license statements
    apply to such inclusions.</p>

    <h2><a id="directx"></a>Microsoft Windows: Terms for 'Microsoft Distributable Code'</h2>

    <p>These terms apply to the following files;
    they are referred to below as "Distributable Code":
      <ul>
        <li><code>d3d*.dll</code> (Direct3D libraries)</li>
        <li><code>msvc*.dll</code> (C and C++ runtime libraries)</li>
      </ul>
    </p>

<pre>
Copyright (c) Microsoft Corporation.

The Distributable Code may be used and distributed only if you comply with the
following terms:

(i)   You may use, copy, and distribute the Distributable Code only as part of
      this product;
(ii)  You may not use the Distributable Code on a platform other than Windows;
(iii) You may not alter any copyright, trademark or patent notice in the
      Distributable Code;
(iv)  You may not modify or distribute the source code of any Distributable
      Code so that any part of the source code becomes subject to the MPL or
      any other copyleft license;
(v)   You must comply with any technical limitations in the Distributable Code
      that only allow you to use it in certain ways; and
(vi)  You must comply with all domestic and international export laws and
      regulations that apply to the Distributable Code.
</pre>



      <hr>

      <p><a href="about:license#top">Return to top</a>.</p>
    </div>
  </body>
</html>
PK
!<	HD1chrome/toolkit/content/global/manifestMessages.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*/
/*
 * Manifest obtainer frame script implementation of:
 * http://www.w3.org/TR/appmanifest/#obtaining
 *
 * It searches a top-level browsing context for
 * a <link rel=manifest> element. Then fetches
 * and processes the linked manifest.
 *
 * BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410
 */
/*globals Task, ManifestObtainer, ManifestFinder, content, sendAsyncMessage, addMessageListener, Components*/
"use strict";
const {
  utils: Cu,
} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
				  "resource://gre/modules/ManifestObtainer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
				  "resource://gre/modules/ManifestFinder.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ManifestIcons",
				  "resource://gre/modules/ManifestIcons.jsm");

const MessageHandler = {
  registerListeners() {
    addMessageListener(
      "DOM:WebManifest:hasManifestLink",
      this.hasManifestLink.bind(this)
    );
    addMessageListener(
      "DOM:ManifestObtainer:Obtain",
      this.obtainManifest.bind(this)
    );
    addMessageListener(
      "DOM:Manifest:FireAppInstalledEvent",
      this.fireAppInstalledEvent.bind(this)
    );
    addMessageListener(
      "DOM:WebManifest:fetchIcon",
      this.fetchIcon.bind(this)
    );
  },

  /**
   * Check if the content document includes a link to a web manifest.
   * @param {Object} aMsg The IPC message, which is destructured to just
   *                      get the id.
   */
  hasManifestLink({data: {id}}) {
    const response = makeMsgResponse(id);
    response.result = ManifestFinder.contentHasManifestLink(content);
    response.success = true;
    sendAsyncMessage("DOM:WebManifest:hasManifestLink", response);
  },

  /**
   * Asynchronously obtains a web manifest from content by using the
   * ManifestObtainer and messages back the result.
   * @param {Object} aMsg The IPC message, which is destructured to just
   *                      get the id.
   */
  async obtainManifest({data: {id}}) {
    const response = makeMsgResponse(id);
    try {
      response.result = await ManifestObtainer.contentObtainManifest(content);
      response.success = true;
    } catch (err) {
      response.result = serializeError(err);
    }
    sendAsyncMessage("DOM:ManifestObtainer:Obtain", response);
  },

  fireAppInstalledEvent({data: {id}}){
    const ev = new Event("appinstalled");
    const response = makeMsgResponse(id);
    if (!content || content.top !== content) {
      const msg = "Can only dispatch install event on top-level browsing contexts.";
      response.result = serializeError(new Error(msg));
    } else {
      response.success = true;
      content.dispatchEvent(ev);
    }
    sendAsyncMessage("DOM:Manifest:FireAppInstalledEvent", response);
  },

  /**
   * Given a manifest and an expected icon size, ask ManifestIcons
   * to fetch the appropriate icon and send along result
   */
  async fetchIcon({data: {id, manifest, iconSize}}) {
    const response = makeMsgResponse(id);
    try {
      response.result =
        await ManifestIcons.contentFetchIcon(content, manifest, iconSize);
      response.success = true;
    } catch (err) {
      response.result = serializeError(err);
    }
    sendAsyncMessage("DOM:WebManifest:fetchIcon", response);
  },

};
/**
 * Utility function to Serializes an JS Error, so it can be transferred over
 * the message channel.
 * FIX ME: https://bugzilla.mozilla.org/show_bug.cgi?id=1172586
 * @param  {Error} aError The error to serialize.
 * @return {Object} The serialized object.
 */
function serializeError(aError) {
  const clone = {
    "fileName": aError.fileName,
    "lineNumber": aError.lineNumber,
    "columnNumber": aError.columnNumber,
    "stack": aError.stack,
    "message": aError.message,
    "name": aError.name
  };
  return clone;
}

function makeMsgResponse(aId) {
    return {
      id: aId,
      success: false,
      result: undefined
    };
  }

MessageHandler.registerListeners();
PK
!<KZ*chrome/toolkit/content/global/menulist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

html|*.menulist-editable-input {
  -moz-appearance: none !important;
  background: transparent ! important;
  -moz-box-flex: 1;
}
PK
!<KDž-chrome/toolkit/content/global/minimal-xul.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file should only contain a minimal set of rules for the XUL elements
 * that may be implicitly created as part of HTML/SVG documents (e.g.
 * scrollbars).  Rules for everything else related to XUL can be found in
 * xul.css.  (This split of the XUL rules is to minimize memory use and improve
 * performance in HTML/SVG documents.)
 *
 * This file should also not contain any app specific styling.  Defaults for
 * widgets of a particular application should be in that application's style
 * sheet.  For example style definitions for navigator can be found in
 * navigator.css.
 *
 * THIS FILE IS LOCKED DOWN.  YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST
 * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com
 */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

* {
  -moz-user-focus: ignore;
  -moz-user-select: none;
  display: -moz-box;
  box-sizing: border-box;
}

:root {
  text-rendering: optimizeLegibility;
  -moz-binding: url("chrome://global/content/bindings/general.xml#root-element");
  -moz-control-character-visibility: visible;
}

:root:-moz-locale-dir(rtl) {
  direction: rtl;
}

/* hide the content and destroy the frame */
[hidden="true"] {
  display: none;
}

/* hide the content, but don't destroy the frames */
[collapsed="true"],
[moz-collapsed="true"] {
  visibility: collapse;
}

/********** label **********/

description {
  -moz-binding: url("chrome://global/content/bindings/text.xml#text-base");
}

label {
  -moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
}

label.text-link, label[onclick] {
  -moz-binding: url("chrome://global/content/bindings/text.xml#text-link");
  -moz-user-focus: normal;
}

label[control], label.radio-label, label.checkbox-label, label.toolbarbutton-multiline-text {
  -moz-binding: url("chrome://global/content/bindings/text.xml#label-control");
}

html|span.accesskey {
  text-decoration: underline;
}

/********** resizer **********/

resizer {
  -moz-binding: url("chrome://global/content/bindings/resizer.xml#resizer");
  position: relative;
  z-index: 2147483647;
}

/********** scrollbar **********/

/* Scrollbars are never flipped even if BiDI kicks in. */
scrollbar[orient="horizontal"] {
  direction: ltr;
}

thumb {
  -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#thumb);
  display: -moz-box !important;
}

.scale-thumb {
  -moz-binding: url(chrome://global/content/bindings/scale.xml#scalethumb);
}

scrollbar, scrollbarbutton, scrollcorner, slider, thumb, scale {
  -moz-user-select: none;
}

scrollcorner {
  display: -moz-box !important;
}

scrollcorner[hidden="true"] {
  display: none !important;
}

scrollbar[value="hidden"] {
  visibility: hidden;
}

scale {
  -moz-binding: url(chrome://global/content/bindings/scale.xml#scale);
}

.scale-slider {
  -moz-binding: url(chrome://global/content/bindings/scale.xml#scaleslider);
  -moz-user-focus: normal;
}

scrollbarbutton[sbattr="scrollbar-up-top"]:not(:-moz-system-metric(scrollbar-start-backward)),
scrollbarbutton[sbattr="scrollbar-down-top"]:not(:-moz-system-metric(scrollbar-start-forward)),
scrollbarbutton[sbattr="scrollbar-up-bottom"]:not(:-moz-system-metric(scrollbar-end-backward)),
scrollbarbutton[sbattr="scrollbar-down-bottom"]:not(:-moz-system-metric(scrollbar-end-forward)) {
  display: none;
}

thumb[sbattr="scrollbar-thumb"]:-moz-system-metric(scrollbar-thumb-proportional) {
  -moz-box-flex: 1;
}
PK
!<q+chrome/toolkit/content/global/mozilla.xhtml<!DOCTYPE html
[
  <!ENTITY % mozillaDTD SYSTEM "chrome://global/locale/mozilla.dtd" >
  %mozillaDTD;
  <!ENTITY % directionDTD SYSTEM "chrome://global/locale/global.dtd" >
  %directionDTD;
]>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta charset='utf-8' />
    <title>&mozilla.title.15.1;</title>

<style>
html {
  background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat;
  color: white;
  font-style: italic;
  text-rendering: optimizeLegibility;
  min-height: 100%;
}

#moztext {
  margin-top: 15%;
  font-size: 1.1em;
  font-family: serif;
  text-align: center;
  line-height: 1.5;
}

#from {
  font-size: 1.95em;
  font-family: serif;
  text-align: right;
}

em {
  font-size: 1.3em;
  line-height: 0;
}

a {
  text-decoration: none;
  color: white;
}
</style>
</head>

<body dir="&locale.dir;">

<section>
  <p id="moztext">
  &mozilla.quote.15.1;
  </p>

  <p id="from">
  &mozilla.from.15.1;
  </p>
</section>

</body>
</html>
PK
!<qv?v?,chrome/toolkit/content/global/netError.xhtml<?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 % netErrorAppDTD
    SYSTEM "chrome://global/locale/netErrorApp.dtd">
  %netErrorAppDTD;
  <!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://global/skin/netError.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[
      // Error url MUST be formatted like this:
      //   moz-neterror:page?e=error&u=url&d=desc
      //
      // or optionally, to specify an alternate CSS class to allow for
      // custom styling and favicon:
      //
      //   moz-neterror:page?e=error&u=url&s=classname&d=desc

      // Note that this file uses document.documentURI to get
      // the URL (with the format from above). This is because
      // document.location.href gets the current URI off the docshell,
      // which is the URL displayed in the location bar, i.e.
      // the URI that the user attempted to load.

      function getErrorCode()
      {
        var url = document.documentURI;
        var error = url.search(/e\=/);
        var duffUrl = url.search(/\&u\=/);
        return decodeURIComponent(url.slice(error + 2, duffUrl));
      }

      function getCSSClass()
      {
        var url = document.documentURI;
        var matches = url.match(/s\=([^&]+)\&/);
        // s is optional, if no match just return nothing
        if (!matches || matches.length < 2)
          return "";

        // parenthetical match is the second entry
        return decodeURIComponent(matches[1]);
      }

      function getDescription()
      {
        var url = document.documentURI;
        var desc = url.search(/d\=/);

        // desc == -1 if not found; if so, return an empty string
        // instead of what would turn out to be portions of the URI
        if (desc == -1)
          return "";

        return decodeURIComponent(url.slice(desc + 2));
      }

      function 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 initPage()
      {
        var err = getErrorCode();

        // 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");
        }

        var title = document.getElementById("errorTitleText");
        if (title)
        {
          title.parentNode.replaceChild(errTitle, title);
          // change id to the replaced child's id so styling works
          errTitle.id = "errorTitleText";
        }

        var sd = document.getElementById("errorShortDescText");
        if (sd)
          sd.textContent = getDescription();

        var ld = document.getElementById("errorLongDesc");
        if (ld)
        {
          ld.parentNode.replaceChild(errDesc, ld);
          // change id to the replaced child's id so styling works
          errDesc.id = "errorLongDesc";
        }

        // remove undisplayed errors to avoid bug 39098
        var errContainer = document.getElementById("errorContainer");
        errContainer.remove();

        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 (className == "expertBadCert") {
          showSecuritySection();
        }

        if (err == "remoteXUL") {
          // Remove the "Try again" button for remote XUL errors given that
          // it is useless.
          document.getElementById("errorTryAgain").style.display = "none";
        }

        if (err == "cspBlocked") {
          // Remove the "Try again" button for CSP violations, since it's
          // almost certainly useless. (Bug 553180)
          document.getElementById("errorTryAgain").style.display = "none";
        }

        if (err == "nssBadCert") {
          // Remove the "Try again" button for security exceptions, since it's
          // almost certainly useless.
          document.getElementById("errorTryAgain").style.display = "none";
          document.getElementById("errorPageContainer").setAttribute("class", "certerror");
          addDomainErrorLink();
        }
        else {
          // Remove the override block for non-certificate errors.  CSS-hiding
          // isn't good enough here, because of bug 39098
          var secOverride = document.getElementById("securityOverrideDiv");
          secOverride.remove();
        }

        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;
          }
        }
      }

      function showSecuritySection() {
        // Swap link out, content in
        document.getElementById('securityOverrideContent').style.display = '';
        document.getElementById('securityOverrideLink').style.display = 'none';
      }

      /* In the case of SSL error pages about domain mismatch, see if
         we can hyperlink the user to the correct site.  We don't want
         to do this generically since it allows MitM attacks to redirect
         users to a site under attacker control, but in certain cases
         it is safe (and helpful!) to do so.  Bug 402210
      */
      function addDomainErrorLink() {
        // Rather than textContent, we need to treat description as HTML
        var sd = document.getElementById("errorShortDescText");
        if (sd) {
          var desc = getDescription();

          // sanitize description text - see bug 441169

          // First, find the index of the <a> tag we care about, being careful not to
          // use an over-greedy regex
          var re = /<a id="cert_domain_link" title="([^"]+)">/;
          var result = re.exec(desc);
          if(!result)
            return;

          // Remove sd's existing children
          sd.textContent = "";

          // Everything up to the link should be text content
          sd.appendChild(document.createTextNode(desc.slice(0, result.index)));

          // Now create the link itself
          var anchorEl = document.createElement("a");
          anchorEl.setAttribute("id", "cert_domain_link");
          anchorEl.setAttribute("title", result[1]);
          anchorEl.appendChild(document.createTextNode(result[1]));
          sd.appendChild(anchorEl);

          // Finally, append text for anything after the closing </a>
          sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
        }

        var link = document.getElementById('cert_domain_link');
        if (!link)
          return;

        var okHost = link.getAttribute("title");
        var thisHost = document.location.hostname;
        var proto = document.location.protocol;

        // If okHost is a wildcard domain ("*.example.com") let's
        // use "www" instead.  "*.example.com" isn't going to
        // get anyone anywhere useful. bug 432491
        okHost = okHost.replace(/^\*\./, "www.");

        /* case #1:
         * example.com uses an invalid security certificate.
         *
         * The certificate is only valid for www.example.com
         *
         * Make sure to include the "." ahead of thisHost so that
         * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
         *
         * We'd normally just use a RegExp here except that we lack a
         * library function to escape them properly (bug 248062), and
         * domain names are famous for having '.' characters in them,
         * which would allow spurious and possibly hostile matches.
         */
        if (endsWith(okHost, "." + thisHost))
          link.href = proto + okHost;

        /* case #2:
         * browser.garage.maemo.org uses an invalid security certificate.
         *
         * The certificate is only valid for garage.maemo.org
         */
        if (endsWith(thisHost, "." + okHost))
          link.href = proto + okHost;
      }

      function endsWith(haystack, needle) {
        return haystack.slice(-needle.length) == needle;
      }

    ]]></script>
  </head>

  <body dir="&locale.dir;">

    <!-- 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_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">&nssBadCert.title;</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_inadequateSecurityError">&inadequateSecurityError.title;</h1>
      </div>
      <div id="errorDescriptionsContainer">
        <div id="ed_generic">&generic.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">&nssBadCert.longDesc2;</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_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
      </div>
    </div>

    <!-- PAGE CONTAINER (for styling purposes only) -->
    <div id="errorPageContainer">

      <!-- Error Title -->
      <div id="errorTitle">
        <h1 id="errorTitleText" />
      </div>

      <!-- LONG CONTENT (the section most likely to require scrolling) -->
      <div id="errorLongContent">

        <!-- Short Description -->
        <div id="errorShortDesc">
          <p id="errorShortDescText" />
        </div>

        <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
        <div id="errorLongDesc" />

        <!-- Override section - For ssl errors only.  Removed on init for other
             error types.  -->
        <div id="securityOverrideDiv">
          <a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
          <div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div>
        </div>
      </div>

      <!-- Retry Button -->
      <button id="errorTryAgain" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
      <script>
        // 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.
        if (window.top == window) {
            var button = document.getElementById("errorTryAgain");
            var nextSibling = button.nextSibling;
            var parent = button.parentNode;
            parent.removeChild(button);
            button.setAttribute("autofocus", "true");
            parent.insertBefore(button, nextSibling);
        }
      </script>

    </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>
PK
!<1*chrome/toolkit/content/global/notfound.wavRIFFWAVEfmt DDdatab~~}}||{{zyyxxwvuttsrqqpponnnnmmmmmmmnnoppqqrstvwxxy{|}}~~~}}||{{zzzyyyyyyyzzzzzzzzzzzyyxxwwvutssrqponmkjiihhggggghhijkmnprsuwy{}~|zywutsrqponnmllkkkkjiiiihhgggffeddccbbbaaaaaaabccdefghijlmnprsuwyz|~}zxurpmkhfdb`^\[ZXWVVUUTTTTTUUVVWXYZ[[\]_`abccdefhijjklmmmnoppqqqqrrssssstttuuvvvwwxxyyyyzz{{{|||}}}~~~ytokfa]XTOKGC@=:7410.-+**)))))**+-.02357:<?ADGIMPSVY\`cfhlorvy||yuqlhd_[VRMHC?:61-)%"	

#',05:@FLRY_elry~~~~~~~}|zyxvtrpnligda_\YWTRPNLKIHGFEEEEEEFFGGHIIJKLNOPQRSTTUVWWXXYYZZ[[[\\\\]]^^__`abcdefghjlnprtvy{~yslf_YSMGA<72.*&#  #&),.259=@DHKORVZ]adhknruy||yvrokgd`]ZVSOLIEC@=:87520.,)(&%$"!"#$%&(+-/258;?BGKNRW]bglqw}~~PK
!<w=w=6chrome/toolkit/content/global/platformHTMLBindings.xml<?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="htmlBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
  <binding id="inputFields" bindToUntrustedContent="true">
    <handlers>
    <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
    <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
    <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>

    <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
    <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
    <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
    <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>

    <!-- Cut/copy/paste/undo -->
    <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
    <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
    <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />

      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>

      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>

      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
      <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>

      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>

      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>

      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
    </handlers>
  </binding>

  <binding id="textAreas" bindToUntrustedContent="true">
    <handlers>
    <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
    <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
    <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
    <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>

    <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
    <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
    <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
    <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>

    <!-- Cut/copy/paste/undo -->
    <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
    <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
    <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
    <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
    <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />
      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>

      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>

      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
      <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>

      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>

      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>

      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>

      <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
     </handlers>
  </binding>

  <binding id="browser">
    <handlers>
      <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
      <handler event="keypress" key=" " command="cmd_scrollPageDown" />

      <handler event="keypress" keycode="VK_UP" command="cmd_moveUp" />
      <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown" />
      <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft" />
      <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight" />

      <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
      <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
      <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
      <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
      <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
      <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>

      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut"/>
      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />

      <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2" />
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2" />
      <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectLeft2" />
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectRight2" />
      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />

      <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2" />
      <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2" />
      <handler event="keypress" keycode="VK_UP" modifiers="control,shift" command="cmd_selectUp2" />
      <handler event="keypress" keycode="VK_DOWN" modifiers="control,shift" command="cmd_selectDown2" />
      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />

      <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
      <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
    </handlers>
  </binding>

  <binding id="editor">
    <handlers>
      <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
      <handler event="keypress" key=" " command="cmd_scrollPageDown" />

      <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
      <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
      <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>

      <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
      <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
      <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>

      <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
      <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
      <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
      <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
      <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
      <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
      <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
      <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
      <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
      <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>

      <handler event="keypress" keycode="VK_LEFT" modifiers="accel" command="cmd_moveLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="accel" command="cmd_moveRight2"/>
      <handler event="keypress" keycode="VK_LEFT" modifiers="shift,accel" command="cmd_selectLeft2"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,accel" command="cmd_selectRight2"/>

      <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
      <handler event="keypress" keycode="VK_UP" modifiers="shift,accel" command="cmd_selectUp2"/>
      <handler event="keypress" keycode="VK_DOWN" modifiers="shift,accel" command="cmd_selectDown2"/>

      <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
      <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
      <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
      <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>

      <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
      <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
      <handler event="keypress" keycode="VK_HOME" command="cmd_selectBeginLine" modifiers="shift"/>
      <handler event="keypress" keycode="VK_END" command="cmd_selectEndLine" modifiers="shift"/>
      <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
      <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
    </handlers>
  </binding>
</bindings>
PK
!<S)chrome/toolkit/content/global/plugins.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== plugins.css =====================================================
   == Styles used by the about:plugins page.
   ======================================================================= */

body {
  background-color: -moz-Field;
  color: -moz-FieldText;
  font: message-box;
}

div#outside {
 text-align: justify;
 width: 90%;
 margin-left: 5%;
 margin-right: 5%;
}

#plugs {
  text-align: center;
  font-size: xx-large;
  font-weight: bold;
}

#noplugs {
  font-size: x-large;
  font-weight: bold;
}

.plugname {
  margin-top: 2em;
  margin-bottom: 1em;
  font-size: large;
  text-align: start;
  font-weight: bold;
}

dl {
  margin: 0px 0px 3px 0px;
}

table {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
  font: message-box;
  text-align: start;
  width: 100%;
  border: 1px solid ThreeDShadow;
  border-spacing: 0px;
}

th, td {
  border: none;
  padding: 3px;
}

th {
  text-align: center;
  background-color: Highlight;
  color: HighlightText;
}

th + th,
td + td {
  border-inline-start: 1px dotted ThreeDShadow; 
}

td {
  text-align: start;
  border-top: 1px dotted ThreeDShadow;
}

th.type, th.suff {
  width: 25%;
}

th.desc {
  width: 50%;
}

.notice {
  background: -moz-cellhighlight;
  border: 1px solid ThreeDShadow;
  padding: 10px;
}
PK
!<#닐""*chrome/toolkit/content/global/plugins.html<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html>
<head>
<script type="application/javascript">
  "use strict";

  Components.utils.import("resource://gre/modules/Services.jsm");

  var Ci = Components.interfaces;
  var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  var pluginsbundle = strBundleService.createBundle("chrome://global/locale/plugins.properties");

  // eslint-disable-next-line no-unsanitized/method
  document.writeln("<title>" + pluginsbundle.GetStringFromName("title_label") + "<\/title>");
</script>
<link rel="stylesheet" type="text/css" href="chrome://global/content/plugins.css">
<link rel="stylesheet" type="text/css" href="chrome://global/skin/plugins.css">
</head>
<body>
<div id="outside">
<script type="application/javascript">
  /* eslint-env mozilla/frame-script */

  "use strict";

  function setDirection() {
    var frame = document.getElementById("directionDetector");
    var direction = frame.contentDocument
                         .defaultView
                         .window
                         .getComputedStyle(frame.contentDocument.getElementById("target"))
                         .getPropertyValue("direction");
    document.body.removeChild(frame);
    document.dir = direction;
  }

  function setupDirection() {
    var frame = document.createElement("iframe");
    frame.setAttribute("id", "directionDetector");
    frame.setAttribute("src", "chrome://global/content/directionDetector.html");
    frame.setAttribute("width", "0");
    frame.setAttribute("height", "0");
    frame.setAttribute("style", "visibility: hidden;");
    frame.setAttribute("onload", "setDirection();");
    document.body.appendChild(frame);
  }
  setupDirection();

  /* JavaScript to enumerate and display all installed plug-ins

   * First, refresh plugins in case anything has been changed recently in
   * prefs: (The "false" argument tells refresh not to reload or activate
   * any plug-ins that would be active otherwise.  In contrast, one would
   * use "true" in the case of ASD instead of restarting)
   */
  navigator.plugins.refresh(false);

  addMessageListener("PluginList", function({ data: aPlugins }) {
    var fragment = document.createDocumentFragment();

    // "Installed plugins"
    var id, label;
    if (aPlugins.length > 0) {
      id = "plugs";
      label = "installedplugins_label";
    } else {
      id = "noplugs";
      label = "nopluginsareinstalled_label";
    }
    var enabledplugins = document.createElement("h1");
    enabledplugins.setAttribute("id", id);
    enabledplugins.appendChild(document.createTextNode(pluginsbundle.GetStringFromName(label)));
    fragment.appendChild(enabledplugins);

    var deprecation = document.createElement("p");
    deprecation.setAttribute("class", "notice");
    deprecation.textContent = pluginsbundle.GetStringFromName("deprecation_description") + " \u00A0 ";
    var deprecationLink = document.createElement("a");
    deprecationLink.textContent = pluginsbundle.GetStringFromName("deprecation_learn_more");
    deprecationLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "npapi";
    deprecation.appendChild(deprecationLink);
    fragment.appendChild(deprecation);

    var stateNames = {};
    ["STATE_SOFTBLOCKED",
     "STATE_BLOCKED",
     "STATE_OUTDATED",
     "STATE_VULNERABLE_UPDATE_AVAILABLE",
     "STATE_VULNERABLE_NO_UPDATE"].forEach(function(label) {
      stateNames[Ci.nsIBlocklistService[label]] = label;
    });

    for (var i = 0; i < aPlugins.length; i++) {
      var plugin = aPlugins[i];
      if (plugin) {
        // "Shockwave Flash"
        var plugname = document.createElement("h2");
        plugname.setAttribute("class", "plugname");
        plugname.appendChild(document.createTextNode(plugin.name));
        fragment.appendChild(plugname);

        var dl = document.createElement("dl");
        fragment.appendChild(dl);

        // "File: Flash Player.plugin"
        var fileDd = document.createElement("dd");
        var file = document.createElement("span");
        file.setAttribute("class", "label");
        file.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("file_label") + " "));
        fileDd.appendChild(file);
        fileDd.appendChild(document.createTextNode(plugin.pluginLibraries));
        dl.appendChild(fileDd);

        // "Path: /usr/lib/mozilla/plugins/libtotem-cone-plugin.so"
        var pathDd = document.createElement("dd");
        var path = document.createElement("span");
        path.setAttribute("class", "label");
        path.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("path_label") + " "));
        pathDd.appendChild(path);
        pathDd.appendChild(document.createTextNode(plugin.pluginFullpath));
        dl.appendChild(pathDd);

        // "Version: "
        var versionDd = document.createElement("dd");
        var version = document.createElement("span");
        version.setAttribute("class", "label");
        version.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("version_label") + " "));
        versionDd.appendChild(version);
        versionDd.appendChild(document.createTextNode(plugin.version));
        dl.appendChild(versionDd);

        // "State: "
        var stateDd = document.createElement("dd");
        var state = document.createElement("span");
        state.setAttribute("label", "state");
        state.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("state_label") + " "));
        stateDd.appendChild(state);
        var status = plugin.isActive ? pluginsbundle.GetStringFromName("state_enabled") : pluginsbundle.GetStringFromName("state_disabled");
        if (plugin.blocklistState in stateNames) {
          status += " (" + stateNames[plugin.blocklistState] + ")";
        }
        stateDd.appendChild(document.createTextNode(status));
        dl.appendChild(stateDd);

        // Plugin Description
        var descDd = document.createElement("dd");
        descDd.appendChild(document.createTextNode(plugin.description));
        dl.appendChild(descDd);

        // MIME Type table
        var mimetypeTable = document.createElement("table");
        mimetypeTable.setAttribute("border", "1");
        mimetypeTable.setAttribute("class", "contenttable");
        fragment.appendChild(mimetypeTable);

        var thead = document.createElement("thead");
        mimetypeTable.appendChild(thead);
        var tr = document.createElement("tr");
        thead.appendChild(tr);

        // "MIME Type" column header
        var typeTh = document.createElement("th");
        typeTh.setAttribute("class", "type");
        typeTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("mimetype_label")));
        tr.appendChild(typeTh);

        // "Description" column header
        var descTh = document.createElement("th");
        descTh.setAttribute("class", "desc");
        descTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("description_label")));
        tr.appendChild(descTh);

        // "Suffixes" column header
        var suffixesTh = document.createElement("th");
        suffixesTh.setAttribute("class", "suff");
        suffixesTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("suffixes_label")));
        tr.appendChild(suffixesTh);

        var tbody = document.createElement("tbody");
        mimetypeTable.appendChild(tbody);

        var mimeTypes = plugin.pluginMimeTypes;
        for (var j = 0; j < mimeTypes.length; j++) {
          var mimetype = mimeTypes[j];
          if (mimetype) {
            var mimetypeRow = document.createElement("tr");
            tbody.appendChild(mimetypeRow);

            // "application/x-shockwave-flash"
            var typename = document.createElement("td");
            typename.appendChild(document.createTextNode(mimetype.type));
            mimetypeRow.appendChild(typename);

            // "Shockwave Flash"
            var description = document.createElement("td");
            description.appendChild(document.createTextNode(mimetype.description));
            mimetypeRow.appendChild(description);

            // "swf"
            var suffixes = document.createElement("td");
            suffixes.appendChild(document.createTextNode(mimetype.suffixes));
            mimetypeRow.appendChild(suffixes);
          }
        }
      }
    }

    document.getElementById("outside").appendChild(fragment);
  });

  sendAsyncMessage("RequestPlugins");
</script>
</div>
</body>
</html>
PK
!<I
V?V?/chrome/toolkit/content/global/printPageSetup.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/. */

var gDialog;
var paramBlock;
var gPrefs         = null;
var gPrintService  = null;
var gPrintSettings = null;
var gStringBundle  = null;
var gDoingMetric   = false;

var gPrintSettingsInterface = Components.interfaces.nsIPrintSettings;
var gDoDebug = false;

// ---------------------------------------------------
function initDialog() {
  gDialog = {};

  gDialog.orientation     = document.getElementById("orientation");
  gDialog.portrait        = document.getElementById("portrait");
  gDialog.landscape       = document.getElementById("landscape");

  gDialog.printBG         = document.getElementById("printBG");

  gDialog.shrinkToFit     = document.getElementById("shrinkToFit");

  gDialog.marginGroup     = document.getElementById("marginGroup");

  gDialog.marginPage      = document.getElementById("marginPage");
  gDialog.marginTop       = document.getElementById("marginTop");
  gDialog.marginBottom    = document.getElementById("marginBottom");
  gDialog.marginLeft      = document.getElementById("marginLeft");
  gDialog.marginRight     = document.getElementById("marginRight");

  gDialog.topInput        = document.getElementById("topInput");
  gDialog.bottomInput     = document.getElementById("bottomInput");
  gDialog.leftInput       = document.getElementById("leftInput");
  gDialog.rightInput      = document.getElementById("rightInput");

  gDialog.hLeftOption     = document.getElementById("hLeftOption");
  gDialog.hCenterOption   = document.getElementById("hCenterOption");
  gDialog.hRightOption    = document.getElementById("hRightOption");

  gDialog.fLeftOption     = document.getElementById("fLeftOption");
  gDialog.fCenterOption   = document.getElementById("fCenterOption");
  gDialog.fRightOption    = document.getElementById("fRightOption");

  gDialog.scalingLabel    = document.getElementById("scalingInput");
  gDialog.scalingInput    = document.getElementById("scalingInput");

  gDialog.enabled         = false;

  gDialog.strings                        = [];
  gDialog.strings["marginUnits.inches"]  = document.getElementById("marginUnits.inches").childNodes[0].nodeValue;
  gDialog.strings["marginUnits.metric"]  = document.getElementById("marginUnits.metric").childNodes[0].nodeValue;
  gDialog.strings["customPrompt.title"]  = document.getElementById("customPrompt.title").childNodes[0].nodeValue;
  gDialog.strings["customPrompt.prompt"] = document.getElementById("customPrompt.prompt").childNodes[0].nodeValue;

}

// ---------------------------------------------------
function isListOfPrinterFeaturesAvailable() {
  return gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".has_special_printerfeatures", false);
}

// ---------------------------------------------------
function checkDouble(element) {
  element.value = element.value.replace(/[^.0-9]/g, "");
}

// Theoretical paper width/height.
var gPageWidth  = 8.5;
var gPageHeight = 11.0;

// ---------------------------------------------------
function setOrientation() {
  var selection = gDialog.orientation.selectedItem;

  var style = "background-color:white;";
  if ((selection == gDialog.portrait && gPageWidth > gPageHeight) ||
      (selection == gDialog.landscape && gPageWidth < gPageHeight)) {
    // Swap width/height.
    var temp = gPageHeight;
    gPageHeight = gPageWidth;
    gPageWidth = temp;
  }
  var div = gDoingMetric ? 100 : 10;
  style += "width:" + gPageWidth / div + unitString() + ";height:" + gPageHeight / div + unitString() + ";";
  gDialog.marginPage.setAttribute( "style", style );
}

// ---------------------------------------------------
function unitString() {
  return (gPrintSettings.paperSizeUnit == gPrintSettingsInterface.kPaperSizeInches) ? "in" : "mm";
}

// ---------------------------------------------------
function checkMargin( value, max, other ) {
  // Don't draw this margin bigger than permitted.
  return Math.min(value, max - other.value);
}

// ---------------------------------------------------
function changeMargin( node ) {
  // Correct invalid input.
  checkDouble(node);

  // Reset the margin height/width for this node.
  var val = node.value;
  var nodeToStyle;
  var attr = "width";
  if ( node == gDialog.topInput ) {
    nodeToStyle = gDialog.marginTop;
    val = checkMargin( val, gPageHeight, gDialog.bottomInput );
    attr = "height";
  } else if ( node == gDialog.bottomInput ) {
    nodeToStyle = gDialog.marginBottom;
    val = checkMargin( val, gPageHeight, gDialog.topInput );
    attr = "height";
  } else if ( node == gDialog.leftInput ) {
    nodeToStyle = gDialog.marginLeft;
    val = checkMargin( val, gPageWidth, gDialog.rightInput );
  } else {
    nodeToStyle = gDialog.marginRight;
    val = checkMargin( val, gPageWidth, gDialog.leftInput );
  }
  var style = attr + ":" + (val / 10) + unitString() + ";";
  nodeToStyle.setAttribute( "style", style );
}

// ---------------------------------------------------
function changeMargins() {
  changeMargin( gDialog.topInput );
  changeMargin( gDialog.bottomInput );
  changeMargin( gDialog.leftInput );
  changeMargin( gDialog.rightInput );
}

// ---------------------------------------------------
function customize( node ) {
  // If selection is now "Custom..." then prompt user for custom setting.
  if ( node.value == 6 ) {
    var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                     .getService( Components.interfaces.nsIPromptService );
    var title      = gDialog.strings["customPrompt.title"];
    var promptText = gDialog.strings["customPrompt.prompt"];
    var result = { value: node.custom };
    var ok = prompter.prompt(window, title, promptText, result, null, { value: false } );
    if ( ok ) {
        node.custom = result.value;
    }
  }
}

// ---------------------------------------------------
function setHeaderFooter( node, value ) {
  node.value = hfValueToId(value);
  if (node.value == 6) {
    // Remember current Custom... value.
    node.custom = value;
  } else {
    // Start with empty Custom... value.
    node.custom = "";
  }
}

var gHFValues = [];
gHFValues["&T"] = 1;
gHFValues["&U"] = 2;
gHFValues["&D"] = 3;
gHFValues["&P"] = 4;
gHFValues["&PT"] = 5;

function hfValueToId(val) {
  if ( val in gHFValues ) {
      return gHFValues[val];
  }
  if ( val.length ) {
      return 6; // Custom...
  }
  return 0; // --blank--
}

function hfIdToValue(node) {
  var result = "";
  switch ( parseInt( node.value ) ) {
  case 0:
    break;
  case 1:
    result = "&T";
    break;
  case 2:
    result = "&U";
    break;
  case 3:
    result = "&D";
    break;
  case 4:
    result = "&P";
    break;
  case 5:
    result = "&PT";
    break;
  case 6:
    result = node.custom;
    break;
  }
  return result;
}

function setPrinterDefaultsForSelectedPrinter() {
  if (gPrintSettings.printerName == "") {
    gPrintSettings.printerName = gPrintService.defaultPrinterName;
  }

  // First get any defaults from the printer
  gPrintService.initPrintSettingsFromPrinter(gPrintSettings.printerName, gPrintSettings);

  // now augment them with any values from last time
  gPrintService.initPrintSettingsFromPrefs(gPrintSettings, true, gPrintSettingsInterface.kInitSaveAll);

  if (gDoDebug) {
    dump("pagesetup/setPrinterDefaultsForSelectedPrinter: printerName='" + gPrintSettings.printerName + "', orientation='" + gPrintSettings.orientation + "'\n");
  }
}

// ---------------------------------------------------
function loadDialog() {
  var print_orientation   = 0;
  var print_margin_top    = 0.5;
  var print_margin_left   = 0.5;
  var print_margin_bottom = 0.5;
  var print_margin_right  = 0.5;

  try {
    gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);

    gPrintService = Components.classes["@mozilla.org/gfx/printsettings-service;1"];
    if (gPrintService) {
      gPrintService = gPrintService.getService();
      if (gPrintService) {
        gPrintService = gPrintService.QueryInterface(Components.interfaces.nsIPrintSettingsService);
      }
    }
  } catch (ex) {
    dump("loadDialog: ex=" + ex + "\n");
  }

  setPrinterDefaultsForSelectedPrinter();

  gDialog.printBG.checked = gPrintSettings.printBGColors || gPrintSettings.printBGImages;

  gDialog.shrinkToFit.checked   = gPrintSettings.shrinkToFit;

  gDialog.scalingLabel.disabled = gDialog.scalingInput.disabled = gDialog.shrinkToFit.checked;

  var marginGroupLabel = gDialog.marginGroup.label;
  if (gPrintSettings.paperSizeUnit == gPrintSettingsInterface.kPaperSizeInches) {
    marginGroupLabel = marginGroupLabel.replace(/#1/, gDialog.strings["marginUnits.inches"]);
    gDoingMetric = false;
  } else {
    marginGroupLabel = marginGroupLabel.replace(/#1/, gDialog.strings["marginUnits.metric"]);
    // Also, set global page dimensions for A4 paper, in millimeters (assumes portrait at this point).
    gPageWidth = 2100;
    gPageHeight = 2970;
    gDoingMetric = true;
  }
  gDialog.marginGroup.label = marginGroupLabel;

  print_orientation   = gPrintSettings.orientation;
  print_margin_top    = convertMarginInchesToUnits(gPrintSettings.marginTop, gDoingMetric);
  print_margin_left   = convertMarginInchesToUnits(gPrintSettings.marginLeft, gDoingMetric);
  print_margin_right  = convertMarginInchesToUnits(gPrintSettings.marginRight, gDoingMetric);
  print_margin_bottom = convertMarginInchesToUnits(gPrintSettings.marginBottom, gDoingMetric);

  if (gDoDebug) {
    dump("print_orientation   " + print_orientation + "\n");

    dump("print_margin_top    " + print_margin_top + "\n");
    dump("print_margin_left   " + print_margin_left + "\n");
    dump("print_margin_right  " + print_margin_right + "\n");
    dump("print_margin_bottom " + print_margin_bottom + "\n");
  }

  if (print_orientation == gPrintSettingsInterface.kPortraitOrientation) {
    gDialog.orientation.selectedItem = gDialog.portrait;
  } else if (print_orientation == gPrintSettingsInterface.kLandscapeOrientation) {
    gDialog.orientation.selectedItem = gDialog.landscape;
  }

  // Set orientation the first time on a timeout so the dialog sizes to the
  // maximum height specified in the .xul file.  Otherwise, if the user switches
  // from landscape to portrait, the content grows and the buttons are clipped.
  setTimeout( setOrientation, 0 );

  gDialog.topInput.value    = print_margin_top.toFixed(1);
  gDialog.bottomInput.value = print_margin_bottom.toFixed(1);
  gDialog.leftInput.value   = print_margin_left.toFixed(1);
  gDialog.rightInput.value  = print_margin_right.toFixed(1);
  changeMargins();

  setHeaderFooter( gDialog.hLeftOption, gPrintSettings.headerStrLeft );
  setHeaderFooter( gDialog.hCenterOption, gPrintSettings.headerStrCenter );
  setHeaderFooter( gDialog.hRightOption, gPrintSettings.headerStrRight );

  setHeaderFooter( gDialog.fLeftOption, gPrintSettings.footerStrLeft );
  setHeaderFooter( gDialog.fCenterOption, gPrintSettings.footerStrCenter );
  setHeaderFooter( gDialog.fRightOption, gPrintSettings.footerStrRight );

  gDialog.scalingInput.value = (gPrintSettings.scaling * 100).toFixed(0);

  // Enable/disable widgets based in the information whether the selected
  // printer supports the matching feature or not
  if (isListOfPrinterFeaturesAvailable()) {
    if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".can_change_orientation"))
      gDialog.orientation.removeAttribute("disabled");
    else
      gDialog.orientation.setAttribute("disabled", "true");
  }

  // Give initial focus to the orientation radio group.
  // Done on a timeout due to to bug 103197.
  setTimeout( function() { gDialog.orientation.focus(); }, 0 );
}

// ---------------------------------------------------
function onLoad() {
  // Init gDialog.
  initDialog();

  if (window.arguments[0] != null) {
    gPrintSettings = window.arguments[0].QueryInterface(Components.interfaces.nsIPrintSettings);
    paramBlock     = window.arguments[1].QueryInterface(Components.interfaces.nsIDialogParamBlock);
  } else if (gDoDebug) {
    alert("window.arguments[0] == null!");
  }

  // default return value is "cancel"
  paramBlock.SetInt(0, 0);

  if (gPrintSettings) {
    loadDialog();
  } else if (gDoDebug) {
    alert("Could initialize gDialog, PrintSettings is null!");
  }
}

function convertUnitsMarginToInches(aVal, aIsMetric) {
  if (aIsMetric) {
    return aVal / 25.4;
  }
  return aVal;
}

function convertMarginInchesToUnits(aVal, aIsMetric) {
  if (aIsMetric) {
    return aVal * 25.4;
  }
  return aVal;
}

// ---------------------------------------------------
function onAccept() {

  if (gPrintSettings) {
    if ( gDialog.orientation.selectedItem == gDialog.portrait ) {
      gPrintSettings.orientation = gPrintSettingsInterface.kPortraitOrientation;
    } else {
      gPrintSettings.orientation = gPrintSettingsInterface.kLandscapeOrientation;
    }

    // save these out so they can be picked up by the device spec
    gPrintSettings.marginTop    = convertUnitsMarginToInches(gDialog.topInput.value, gDoingMetric);
    gPrintSettings.marginLeft   = convertUnitsMarginToInches(gDialog.leftInput.value, gDoingMetric);
    gPrintSettings.marginBottom = convertUnitsMarginToInches(gDialog.bottomInput.value, gDoingMetric);
    gPrintSettings.marginRight  = convertUnitsMarginToInches(gDialog.rightInput.value, gDoingMetric);

    gPrintSettings.headerStrLeft   = hfIdToValue(gDialog.hLeftOption);
    gPrintSettings.headerStrCenter = hfIdToValue(gDialog.hCenterOption);
    gPrintSettings.headerStrRight  = hfIdToValue(gDialog.hRightOption);

    gPrintSettings.footerStrLeft   = hfIdToValue(gDialog.fLeftOption);
    gPrintSettings.footerStrCenter = hfIdToValue(gDialog.fCenterOption);
    gPrintSettings.footerStrRight  = hfIdToValue(gDialog.fRightOption);

    gPrintSettings.printBGColors = gDialog.printBG.checked;
    gPrintSettings.printBGImages = gDialog.printBG.checked;

    gPrintSettings.shrinkToFit   = gDialog.shrinkToFit.checked;

    var scaling = document.getElementById("scalingInput").value;
    if (scaling < 10.0) {
      scaling = 10.0;
    }
    if (scaling > 500.0) {
      scaling = 500.0;
    }
    scaling /= 100.0;
    gPrintSettings.scaling = scaling;

    if (gDoDebug) {
      dump("******* Page Setup Accepting ******\n");
      dump("print_margin_top    " + gDialog.topInput.value + "\n");
      dump("print_margin_left   " + gDialog.leftInput.value + "\n");
      dump("print_margin_right  " + gDialog.bottomInput.value + "\n");
      dump("print_margin_bottom " + gDialog.rightInput.value + "\n");
    }
  }

  // set return value to "ok"
  if (paramBlock) {
    paramBlock.SetInt(0, 1);
  } else {
    dump("*** FATAL ERROR: No paramBlock\n");
  }

  var flags = gPrintSettingsInterface.kInitSaveMargins |
              gPrintSettingsInterface.kInitSaveHeaderLeft |
              gPrintSettingsInterface.kInitSaveHeaderCenter |
              gPrintSettingsInterface.kInitSaveHeaderRight |
              gPrintSettingsInterface.kInitSaveFooterLeft |
              gPrintSettingsInterface.kInitSaveFooterCenter |
              gPrintSettingsInterface.kInitSaveFooterRight |
              gPrintSettingsInterface.kInitSaveBGColors |
              gPrintSettingsInterface.kInitSaveBGImages |
              gPrintSettingsInterface.kInitSaveInColor |
              gPrintSettingsInterface.kInitSaveReversed |
              gPrintSettingsInterface.kInitSaveOrientation |
              gPrintSettingsInterface.kInitSaveOddEvenPages |
              gPrintSettingsInterface.kInitSaveShrinkToFit |
              gPrintSettingsInterface.kInitSaveScaling;

  gPrintService.savePrintSettingsToPrefs(gPrintSettings, true, flags);

  return true;
}

// ---------------------------------------------------
function onCancel() {
  // set return value to "cancel"
  if (paramBlock) {
    paramBlock.SetInt(0, 0);
  } else {
    dump("*** FATAL ERROR: No paramBlock\n");
  }

  return true;
}
PK
!<F&))0chrome/toolkit/content/global/printPageSetup.xul<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/printPageSetup.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://global/locale/printPageSetup.dtd">

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  id="printPageSetupDialog"
  onload="onLoad();"
  ondialogaccept="return onAccept();"
  oncancel="return onCancel();"
  title="&printSetup.title;"
  persist="screenX screenY"
  screenX="24" screenY="24">

  <script type="application/javascript" src="chrome://global/content/printPageSetup.js"/>

  <!-- Localizable strings manipulated at run-time. -->
  <data id="marginUnits.inches">&marginUnits.inches;</data>
  <data id="marginUnits.metric">&marginUnits.metric;</data>
  <data id="customPrompt.title">&customPrompt.title;</data>
  <data id="customPrompt.prompt">&customPrompt.prompt;</data>

  <tabbox flex="1">
    <tabs>
      <tab label="&basic.tab;"/>
      <tab label="&advanced.tab;"/>
    </tabs>
    <tabpanels flex="1">
      <vbox>
        <groupbox>
          <caption label="&formatGroup.label;"/>
          <vbox>
            <hbox align="center">
              <label control="orientation" value="&orientation.label;"/>
              <radiogroup id="orientation" oncommand="setOrientation()">
                <hbox align="center">
                  <radio id="portrait"
                         class="portrait-page"
                         label="&portrait.label;"
                         accesskey="&portrait.accesskey;"/>
                  <radio id="landscape"
                         class="landscape-page"
                         label="&landscape.label;"
                         accesskey="&landscape.accesskey;"/>
                </hbox>
              </radiogroup>
            </hbox>
            <separator/>
            <hbox align="center">
              <label control="scalingInput"
                     value="&scale.label;"
                     accesskey="&scale.accesskey;"/>
              <textbox id="scalingInput" size="4" oninput="checkDouble(this)"/>
              <label value="&scalePercent;"/>
              <separator/>
              <checkbox id="shrinkToFit"
                        label="&shrinkToFit.label;"
                        accesskey="&shrinkToFit.accesskey;"
                        oncommand="gDialog.scalingInput.disabled=gDialog.scalingLabel.disabled=this.checked"/>
            </hbox>
          </vbox>
        </groupbox>
        <groupbox>
          <caption label="&optionsGroup.label;"/>
          <checkbox id="printBG"
                    label="&printBG.label;"
                    accesskey="&printBG.accesskey;"/>
        </groupbox>
      </vbox>
      <vbox>
        <groupbox>
          <caption id="marginGroup" label="&marginGroup.label;"/>
          <vbox>
            <hbox align="center">
              <spacer flex="1"/>
              <label control="topInput"
                     value="&marginTop.label;"
                     accesskey="&marginTop.accesskey;"/>
              <textbox id="topInput" size="5" oninput="changeMargin(this)"/>
              <!-- This invisible label (with same content as the visible one!) is used
                   to ensure that the <textbox> is centered above the page.  The same
                   technique is deployed for the bottom/left/right input fields, below. -->
              <label value="&marginTop.label;" style="visibility: hidden;"/>
              <spacer flex="1"/>
            </hbox>
            <hbox dir="ltr">
              <spacer flex="1"/>
              <vbox>
                <spacer flex="1"/>
                <label control="leftInput"
                       value="&marginLeft.label;"
                       accesskey="&marginLeft.accesskey;"/>
                <textbox id="leftInput" size="5" oninput="changeMargin(this)"/>
                <label value="&marginLeft.label;" style="visibility: hidden;"/>
                <spacer flex="1"/>
              </vbox>
              <!-- The "margin page" draws a simulated printout page with dashed lines
                   for the margins.  The height/width style attributes of the marginTop,
                   marginBottom, marginLeft, and marginRight elements are set by
                   the JS code dynamically based on the user input. -->
              <vbox id="marginPage" style="height:29.7mm;">
                <box id="marginTop" style="height:0.05in;"/>
                <hbox flex="1" dir="ltr">
                  <box id="marginLeft" style="width:0.025in;"/>
                  <box style="border: 1px; border-style: dashed; border-color: gray;" flex="1"/>
                  <box id="marginRight" style="width:0.025in;"/>
                </hbox>
                <box id="marginBottom" style="height:0.05in;"/>
              </vbox>
              <vbox>
                <spacer flex="1"/>
                <label control="rightInput"
                       value="&marginRight.label;"
                       accesskey="&marginRight.accesskey;"/>
                <textbox id="rightInput" size="5" oninput="changeMargin(this)"/>
                <label value="&marginRight.label;" style="visibility: hidden;"/>
                <spacer flex="1"/>
              </vbox>
              <spacer flex="1"/>
            </hbox>
            <hbox align="center">
              <spacer flex="1"/>
              <label control="bottomInput"
                     value="&marginBottom.label;"
                     accesskey="&marginBottom.accesskey;"/>
              <textbox id="bottomInput" size="5" oninput="changeMargin(this)"/>
              <label value="&marginBottom.label;" style="visibility: hidden;"/>
              <spacer flex="1"/>
            </hbox>
          </vbox>
        </groupbox>
        <groupbox>
          <caption id="headersAndFooters" label="&headerFooter.label;"/>
          <grid>
            <columns>
              <column/>
              <column/>
              <column/>
            </columns>
            <rows>
              <row dir="ltr">
                <menulist id="hLeftOption" oncommand="customize(this)" tooltiptext="&headerLeft.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
                <menulist id="hCenterOption" oncommand="customize(this)" tooltiptext="&headerCenter.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
                <menulist id="hRightOption" oncommand="customize(this)" tooltiptext="&headerRight.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
              </row>
              <row dir="ltr">
                <vbox align="center">
                  <label value="&hfLeft.label;"/>
                </vbox>
                <vbox align="center">
                  <label value="&hfCenter.label;"/>
                </vbox>
                <vbox align="center">
                  <label value="&hfRight.label;"/>
                </vbox>
              </row>
              <row dir="ltr">
                <menulist id="fLeftOption" oncommand="customize(this)" tooltiptext="&footerLeft.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
                <menulist id="fCenterOption" oncommand="customize(this)" tooltiptext="&footerCenter.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
                <menulist id="fRightOption" oncommand="customize(this)" tooltiptext="&footerRight.tip;">
                  <menupopup>
                    <menuitem value="0" label="&hfBlank;"/>
                    <menuitem value="1" label="&hfTitle;"/>
                    <menuitem value="2" label="&hfURL;"/>
                    <menuitem value="3" label="&hfDateAndTime;"/>
                    <menuitem value="4" label="&hfPage;"/>
                    <menuitem value="5" label="&hfPageAndTotal;"/>
                    <menuitem value="6" label="&hfCustom;"/>
                  </menupopup>
                </menulist>
              </row>  
            </rows>
          </grid>
        </groupbox>
      </vbox>
    </tabpanels>
  </tabbox>
</dialog>

PK
!<V"DD6chrome/toolkit/content/global/printPreviewBindings.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- this file depends on printUtils.js -->

<!DOCTYPE bindings [
<!ENTITY % printPreviewDTD SYSTEM "chrome://global/locale/printPreview.dtd" >
%printPreviewDTD;
]>

<bindings id="printPreviewBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="printpreviewtoolbar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <resources>
      <stylesheet src="chrome://global/skin/printPreview.css"/>
    </resources>

    <content>
      <xul:button label="&print.label;" accesskey="&print.accesskey;"
        oncommand="this.parentNode.print();" icon="print"/>

      <xul:button anonid="pageSetup" label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
        oncommand="this.parentNode.doPageSetup();"/>

      <xul:vbox align="center" pack="center">
        <xul:label value="&page.label;" accesskey="&page.accesskey;" control="pageNumber"/>
      </xul:vbox>
      <xul:toolbarbutton anonid="navigateHome" class="navigate-button tabbable"
        oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"/>
      <xul:toolbarbutton anonid="navigatePrevious" class="navigate-button tabbable"
        oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"/>
      <xul:hbox align="center" pack="center">
        <xul:textbox id="pageNumber" size="3" value="1" min="1" type="number"
          hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"/>
        <xul:label value="&of.label;"/>
        <xul:label value="1"/>
      </xul:hbox>
      <xul:toolbarbutton anonid="navigateNext" class="navigate-button tabbable"
        oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"/>
      <xul:toolbarbutton anonid="navigateEnd" class="navigate-button tabbable"
        oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"/>

      <xul:toolbarseparator class="toolbarseparator-primary"/>
      <xul:vbox align="center" pack="center">
        <xul:label value="&scale.label;" accesskey="&scale.accesskey;" control="scale"/>
      </xul:vbox>

      <xul:hbox align="center" pack="center">
        <xul:menulist id="scale" crop="none"
          oncommand="parentNode.parentNode.scale(this.selectedItem.value);">
          <xul:menupopup>
            <xul:menuitem value="0.3" label="&p30.label;"/>
            <xul:menuitem value="0.4" label="&p40.label;"/>
            <xul:menuitem value="0.5" label="&p50.label;"/>
            <xul:menuitem value="0.6" label="&p60.label;"/>
            <xul:menuitem value="0.7" label="&p70.label;"/>
            <xul:menuitem value="0.8" label="&p80.label;"/>
            <xul:menuitem value="0.9" label="&p90.label;"/>
            <xul:menuitem value="1" label="&p100.label;"/>
            <xul:menuitem value="1.25" label="&p125.label;"/>
            <xul:menuitem value="1.5" label="&p150.label;"/>
            <xul:menuitem value="1.75" label="&p175.label;"/>
            <xul:menuitem value="2" label="&p200.label;"/>
            <xul:menuseparator/>
            <xul:menuitem flex="1" value="ShrinkToFit"
              label="&ShrinkToFit.label;"/>
            <xul:menuitem value="Custom" label="&Custom.label;"/>
          </xul:menupopup>
        </xul:menulist>
      </xul:hbox>

      <xul:toolbarseparator class="toolbarseparator-primary"/>
      <xul:hbox align="center" pack="center">
        <xul:toolbarbutton label="&portrait.label;" checked="true"
          accesskey="&portrait.accesskey;"
          type="radio" group="orient" class="toolbar-portrait-page tabbable"
          oncommand="parentNode.parentNode.orient('portrait');"/>
        <xul:toolbarbutton label="&landscape.label;"
          accesskey="&landscape.accesskey;"
          type="radio" group="orient" class="toolbar-landscape-page tabbable"
          oncommand="parentNode.parentNode.orient('landscape');"/>
      </xul:hbox>

      <xul:toolbarseparator class="toolbarseparator-primary"/>
      <xul:checkbox label="&simplifyPage.label;" checked="false" disabled="true"
        accesskey="&simplifyPage.accesskey;"
        tooltiptext-disabled="&simplifyPage.disabled.tooltip;"
        tooltiptext-enabled="&simplifyPage.enabled.tooltip;"
        oncommand="this.parentNode.simplify();"/>

      <xul:toolbarseparator class="toolbarseparator-primary"/>
      <xul:button label="&close.label;" accesskey="&close.accesskey;"
        oncommand="PrintUtils.exitPrintPreview();" icon="close"/>
      <xul:data value="&customPrompt.title;"/>
    </content>

    <implementation implements="nsIMessageListener">
      <field name="mPrintButton">
        document.getAnonymousNodes(this)[0]
      </field>
      <field name="mPageSetupButton">
        document.getAnonymousElementByAttribute(this, "anonid", "pageSetup");
      </field>
      <field name="mNavigateHomeButton">
        document.getAnonymousElementByAttribute(this, "anonid", "navigateHome");
      </field>
      <field name="mNavigatePreviousButton">
        document.getAnonymousElementByAttribute(this, "anonid", "navigatePrevious");
      </field>
      <field name="mPageTextBox">
        document.getAnonymousNodes(this)[5].childNodes[0]
      </field>
      <field name="mNavigateNextButton">
        document.getAnonymousElementByAttribute(this, "anonid", "navigateNext");
      </field>
      <field name="mNavigateEndButton">
        document.getAnonymousElementByAttribute(this, "anonid", "navigateEnd");
      </field>
      <field name="mTotalPages">
        document.getAnonymousNodes(this)[5].childNodes[2]
      </field>
      <field name="mScaleLabel">
        document.getAnonymousNodes(this)[9].firstChild
      </field>
      <field name="mScaleCombobox">
        document.getAnonymousNodes(this)[10].firstChild
      </field>
      <field name="mOrientButtonsBox">
        document.getAnonymousNodes(this)[12]
      </field>
      <field name="mPortaitButton">
        this.mOrientButtonsBox.childNodes[0]
      </field>
      <field name="mLandscapeButton">
        this.mOrientButtonsBox.childNodes[1]
      </field>
      <field name="mSimplifyPageCheckbox">
        document.getAnonymousNodes(this)[14]
      </field>
      <field name="mSimplifyPageNotAllowed">
        this.mSimplifyPageCheckbox.disabled
      </field>
      <field name="mSimplifyPageToolbarSeparator">
        document.getAnonymousNodes(this)[15]
      </field>
      <field name="mCustomTitle">
        document.getAnonymousNodes(this)[17].firstChild
      </field>
      <field name="mPrintPreviewObs">
      </field>
      <field name="mWebProgress">
      </field>
      <field name="mPPBrowser">
        null
      </field>
      <field name="mMessageManager">
        null
      </field>

      <method name="initialize">
        <parameter name="aPPBrowser"/>
        <body>
        <![CDATA[
          let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
          if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
            this.mSimplifyPageCheckbox.hidden = true;
            this.mSimplifyPageToolbarSeparator.hidden = true;
          }
          this.mPPBrowser = aPPBrowser;
          this.mMessageManager = aPPBrowser.messageManager;
          this.mMessageManager.addMessageListener("Printing:Preview:UpdatePageCount", this);
          this.updateToolbar();

          let $ = id => document.getAnonymousElementByAttribute(this, "anonid", id);
          let ltr = document.documentElement.matches(":root:-moz-locale-dir(ltr)");
          // Windows 7 doesn't support ⏮ and ⏭ by default, and fallback doesn't
          // always work (bug 1343330).
          let {AppConstants} = Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
          let useCompatCharacters = AppConstants.isPlatformAndVersionAtMost("win", "6.1");
          let leftEnd = useCompatCharacters ? "⏪" : "⏮";
          let rightEnd = useCompatCharacters ? "⏩" : "⏭";
          $("navigateHome").label = ltr ? leftEnd : rightEnd;
          $("navigatePrevious").label = ltr ? "◂" : "▸";
          $("navigateNext").label = ltr ? "▸" : "◂";
          $("navigateEnd").label = ltr ? rightEnd : leftEnd;
        ]]>
        </body>
      </method>

      <method name="destroy">
        <body>
        <![CDATA[
          this.mMessageManager.removeMessageListener("Printing:Preview:UpdatePageCount", this);
          delete this.mMessageManager;
          delete this.mPPBrowser;
        ]]>
        </body>
      </method>

      <method name="disableUpdateTriggers">
        <parameter name="aDisabled"/>
        <body>
        <![CDATA[
          this.mPrintButton.disabled = aDisabled;
          this.mPageSetupButton.disabled = aDisabled;
          this.mNavigateHomeButton.disabled = aDisabled;
          this.mNavigatePreviousButton.disabled = aDisabled;
          this.mPageTextBox.disabled = aDisabled;
          this.mNavigateNextButton.disabled = aDisabled;
          this.mNavigateEndButton.disabled = aDisabled;
          this.mScaleCombobox.disabled = aDisabled;
          this.mPortaitButton.disabled = aDisabled;
          this.mLandscapeButton.disabled = aDisabled;
          this.mSimplifyPageCheckbox.disabled = this.mSimplifyPageNotAllowed || aDisabled;
        ]]>
        </body>
      </method>

      <method name="doPageSetup">
        <body>
        <![CDATA[
          /* import-globals-from printUtils.js */
          var didOK = PrintUtils.showPageSetup();
          if (didOK) {
            // the changes that effect the UI
            this.updateToolbar();

            // Now do PrintPreview
            PrintUtils.printPreview();
          }
        ]]>
        </body>
      </method>

      <method name="navigate">
        <parameter name="aDirection"/>
        <parameter name="aPageNum"/>
        <parameter name="aHomeOrEnd"/>
        <body>
        <![CDATA[
          const nsIWebBrowserPrint = Components.interfaces.nsIWebBrowserPrint;
          let navType, pageNum;

          // we use only one of aHomeOrEnd, aDirection, or aPageNum
          if (aHomeOrEnd) {
            // We're going to either the very first page ("home"), or the
            // very last page ("end").
            if (aHomeOrEnd == "home") {
              navType = nsIWebBrowserPrint.PRINTPREVIEW_HOME;
              this.mPageTextBox.value = 1;
            } else {
              navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
              this.mPageTextBox.value = this.mPageTextBox.max;
            }
            pageNum = 0;
          } else if (aDirection) {
            // aDirection is either +1 or -1, and allows us to increment
            // or decrement our currently viewed page.
            this.mPageTextBox.valueNumber += aDirection;
            navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
            pageNum = this.mPageTextBox.value;  // TODO: back to valueNumber?
          } else {
            // We're going to a specific page (aPageNum)
            navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
            pageNum = aPageNum;
          }

          this.mMessageManager.sendAsyncMessage("Printing:Preview:Navigate", {
            navType,
            pageNum,
          });
        ]]>
        </body>
      </method>

      <method name="print">
        <body>
        <![CDATA[
          PrintUtils.printWindow(this.mPPBrowser.outerWindowID, this.mPPBrowser);
        ]]>
        </body>
      </method>

      <method name="promptForScaleValue">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var value = Math.round(aValue);
          var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
          var promptStr = this.mScaleLabel.value;
          var renameTitle = this.mCustomTitle;
          var result = {value};
          var confirmed = promptService.prompt(window, renameTitle, promptStr, result, null, {value});
          if (!confirmed || (!result.value) || (result.value == "") || result.value == value) {
            return -1;
          }
          return result.value;
        ]]>
        </body>
      </method>

      <method name="setScaleCombobox">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var scaleValues = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.25, 1.5, 1.75, 2];

          aValue = new Number(aValue);

          for (var i = 0; i < scaleValues.length; i++) {
            if (aValue == scaleValues[i]) {
              this.mScaleCombobox.selectedIndex = i;
              return;
            }
          }
          this.mScaleCombobox.value = "Custom";
        ]]>
        </body>
      </method>

      <method name="scale">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          var settings = PrintUtils.getPrintSettings();
          if (aValue == "ShrinkToFit") {
            if (!settings.shrinkToFit) {
              settings.shrinkToFit = true;
              this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
              PrintUtils.printPreview();
            }
            return;
          }

          if (aValue == "Custom") {
            aValue = this.promptForScaleValue(settings.scaling * 100.0);
            if (aValue >= 10) {
              aValue /= 100.0;
            } else {
              if (this.mScaleCombobox.hasAttribute("lastValidInx")) {
                this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute("lastValidInx");
              }
              return;
            }
          }

          this.setScaleCombobox(aValue);
          this.mScaleCombobox.setAttribute("lastValidInx", this.mScaleCombobox.selectedIndex);

          if (settings.scaling != aValue || settings.shrinkToFit) {
            settings.shrinkToFit = false;
            settings.scaling = aValue;
            this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
            PrintUtils.printPreview();
          }
        ]]>
        </body>
      </method>

      <method name="orient">
        <parameter name="aOrientation"/>
        <body>
        <![CDATA[
          const kIPrintSettings = Components.interfaces.nsIPrintSettings;
          var orientValue = (aOrientation == "portrait") ? kIPrintSettings.kPortraitOrientation :
                                                           kIPrintSettings.kLandscapeOrientation;
          var settings = PrintUtils.getPrintSettings();
          if (settings.orientation != orientValue) {
            settings.orientation = orientValue;
            this.savePrintSettings(settings, settings.kInitSaveOrientation);
            PrintUtils.printPreview();
          }
        ]]>
        </body>
      </method>

      <method name="simplify">
        <body>
        <![CDATA[
          PrintUtils.setSimplifiedMode(this.mSimplifyPageCheckbox.checked);
          PrintUtils.printPreview();
        ]]>
        </body>
      </method>

      <method name="enableSimplifyPage">
        <body>
        <![CDATA[
          this.mSimplifyPageNotAllowed = false;
          this.mSimplifyPageCheckbox.disabled = false;
          this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
               this.mSimplifyPageCheckbox.getAttribute("tooltiptext-enabled"));
        ]]>
        </body>
      </method>

      <method name="disableSimplifyPage">
        <body>
        <![CDATA[
          this.mSimplifyPageNotAllowed = true;
          this.mSimplifyPageCheckbox.disabled = true;
          this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
               this.mSimplifyPageCheckbox.getAttribute("tooltiptext-disabled"));
        ]]>
        </body>
      </method>

      <method name="updateToolbar">
        <body>
        <![CDATA[
          var settings = PrintUtils.getPrintSettings();

          var isPortrait = settings.orientation == Components.interfaces.nsIPrintSettings.kPortraitOrientation;

          this.mPortaitButton.checked = isPortrait;
          this.mLandscapeButton.checked = !isPortrait;

          if (settings.shrinkToFit) {
            this.mScaleCombobox.value = "ShrinkToFit";
          } else {
            this.setScaleCombobox(settings.scaling);
          }

          this.mPageTextBox.value = 1;
        ]]>
        </body>
      </method>

      <method name="savePrintSettings">
        <parameter name="settings"/>
        <parameter name="flags"/>
        <body><![CDATA[
          var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
                                .getService(Components.interfaces.nsIPrintSettingsService);
          PSSVC.savePrintSettingsToPrefs(settings, true, flags);
        ]]></body>
      </method>

      <!-- nsIMessageListener -->
      <method name="receiveMessage">
        <parameter name="message"/>
        <body>
        <![CDATA[
          if (message.name == "Printing:Preview:UpdatePageCount") {
            let numPages = message.data.numPages;
            this.mTotalPages.value = numPages;
            this.mPageTextBox.max = numPages;
          }
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

</bindings>
PK
!<5chrome/toolkit/content/global/printPreviewProgress.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/. */

// dialog is just an array we'll use to store various properties from the dialog document...
var dialog;

// the printProgress is a nsIPrintProgress object
var printProgress = null;

// random global variables...
var targetFile;

var docTitle = "";
var docURL   = "";
var progressParams = null;

function ellipseString(aStr, doFront) {
  if (aStr.length > 3 && (aStr.substr(0, 3) == "..." || aStr.substr(aStr.length - 4, 3) == "..."))
    return aStr;

  var fixedLen = 64;
  if (aStr.length <= fixedLen)
    return aStr;

  if (doFront)
    return "..." + aStr.substr(aStr.length - fixedLen, fixedLen);

  return aStr.substr(0, fixedLen) + "...";
}

// all progress notifications are done through the nsIWebProgressListener implementation...
var progressListener = {

  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
      window.close();
  },

  onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
    if (!progressParams)
      return;
    var docTitleStr = ellipseString(progressParams.docTitle, false);
    if (docTitleStr != docTitle) {
      docTitle = docTitleStr;
      dialog.title.value = docTitle;
    }
    var docURLStr = ellipseString(progressParams.docURL, true);
    if (docURLStr != docURL && dialog.title != null) {
      docURL = docURLStr;
      if (docTitle == "")
        dialog.title.value = docURLStr;
    }
  },

  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
  onSecurityChange(aWebProgress, aRequest, state) {},

  onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
    if (aMessage)
      dialog.title.setAttribute("value", aMessage);
  },

  QueryInterface(iid) {
    if (iid.equals(Components.interfaces.nsIWebProgressListener) || iid.equals(Components.interfaces.nsISupportsWeakReference))
      return this;
    throw Components.results.NS_NOINTERFACE;
  }
}

function onLoad() {
  // Set global variables.
  printProgress = window.arguments[0];
  if (window.arguments[1]) {
    progressParams = window.arguments[1].QueryInterface(Components.interfaces.nsIPrintProgressParams)
    if (progressParams) {
      docTitle = ellipseString(progressParams.docTitle, false);
      docURL   = ellipseString(progressParams.docURL, true);
    }
  }

  if (!printProgress) {
    dump( "Invalid argument to printPreviewProgress.xul\n" );
    window.close()
    return;
  }

  dialog         = {};
  dialog.strings = [];
  dialog.title   = document.getElementById("dialog.title");
  dialog.titleLabel = document.getElementById("dialog.titleLabel");

  dialog.title.value = docTitle;

  // set our web progress listener on the helper app launcher
  printProgress.registerListener(progressListener);

  // We need to delay the set title else dom will overwrite it
  window.setTimeout(doneIniting, 100);
}

function onUnload() {
  if (!printProgress)
    return;
  try {
    printProgress.unregisterListener(progressListener);
    printProgress = null;
  } catch (e) {}
}

function getString(stringId) {
  // Check if we've fetched this string already.
  if (!(stringId in dialog.strings)) {
    // Try to get it.
    var elem = document.getElementById( "dialog.strings." + stringId);
    try {
      if (elem && elem.childNodes && elem.childNodes[0] &&
          elem.childNodes[0].nodeValue)
        dialog.strings[stringId] = elem.childNodes[0].nodeValue;
      // If unable to fetch string, use an empty string.
      else
        dialog.strings[stringId] = "";
    } catch (e) { dialog.strings[stringId] = ""; }
  }
  return dialog.strings[stringId];
}

// If the user presses cancel, tell the app launcher and close the dialog...
function onCancel() {
  // Cancel app launcher.
  try {
    printProgress.processCanceledByUser = true;
  } catch (e) { return true; }

  // don't Close up dialog by returning false, the backend will close the dialog when everything will be aborted.
  return false;
}

function doneIniting() {
  // called by function timeout in onLoad
  printProgress.doneIniting();
}
PK
!<8.

6chrome/toolkit/content/global/printPreviewProgress.xul<?xml version="1.0"?> 

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://global/locale/printPreviewProgress.dtd">

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&printWindow.title;"
        style="width: 36em;"
        buttons="cancel"
        oncancel="onCancel()"
        onload="onLoad()"
        onunload="onUnload()">

  <script type="application/javascript" src="chrome://global/content/printPreviewProgress.js"/>

    <grid flex="1">
      <columns>
        <column/>
        <column/>
      </columns>
      
      <rows>
        <row>
          <hbox pack="end">
            <label id="dialog.titleLabel" value="&title;"/>
          </hbox>
          <label id="dialog.title"/>
        </row>
        <row class="thin-separator">             
          <hbox pack="end">
            <label id="dialog.progressSpaces" value="&progress;"/>
          </hbox>
            <label id="dialog.progressLabel" value="&preparing;"/>
        </row>
      </rows>
    </grid>
</dialog>
PK
!<!!.chrome/toolkit/content/global/printProgress.js// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// dialog is just an array we'll use to store various properties from the dialog document...
var dialog;

// the printProgress is a nsIPrintProgress object
var printProgress = null;

// random global variables...
var targetFile;

var docTitle = "";
var docURL   = "";
var progressParams = null;
var switchUI = true;

function ellipseString(aStr, doFront) {
  if (aStr.length > 3 && (aStr.substr(0, 3) == "..." || aStr.substr(aStr.length - 4, 3) == "...")) {
    return aStr;
  }

  var fixedLen = 64;
  if (aStr.length > fixedLen) {
    if (doFront) {
      var endStr = aStr.substr(aStr.length - fixedLen, fixedLen);
      return "..." + endStr;
    }
    var frontStr = aStr.substr(0, fixedLen);
    return frontStr + "...";
  }
  return aStr;
}

// all progress notifications are done through the nsIWebProgressListener implementation...
var progressListener = {
    onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
      if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START) {
        // Put progress meter in undetermined mode.
        // dialog.progress.setAttribute( "value", 0 );
        dialog.progress.setAttribute( "mode", "undetermined" );
      }

      if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
        // we are done printing
        // Indicate completion in title area.
        var msg = getString( "printComplete" );
        dialog.title.setAttribute("value", msg);

        // Put progress meter at 100%.
        dialog.progress.setAttribute( "value", 100 );
        dialog.progress.setAttribute( "mode", "normal" );
        var percentPrint = getString( "progressText" );
        percentPrint = replaceInsert( percentPrint, 1, 100 );
        dialog.progressText.setAttribute("value", percentPrint);

        var fm = Components.classes["@mozilla.org/focus-manager;1"]
                     .getService(Components.interfaces.nsIFocusManager);
        if (fm && fm.activeWindow == window) {
          // This progress dialog is the currently active window. In
          // this case we need to make sure that some other window
          // gets focus before we close this dialog to work around the
          // buggy Windows XP Fax dialog, which ends up parenting
          // itself to the currently focused window and is unable to
          // survive that window going away. What happens without this
          // opener.focus() call on Windows XP is that the fax dialog
          // is opened only to go away when this dialog actually
          // closes (which can happen asynchronously, so the fax
          // dialog just flashes once and then goes away), so w/o this
          // fix, it's impossible to fax on Windows XP w/o manually
          // switching focus to another window (or holding on to the
          // progress dialog with the mouse long enough).
          opener.focus();
        }

        window.close();
      }
    },

    onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
      if (switchUI) {
        dialog.tempLabel.setAttribute("hidden", "true");
        dialog.progress.setAttribute("hidden", "false");

        var progressLabel = getString("progress");
        if (progressLabel == "") {
          progressLabel = "Progress:"; // better than nothing
        }
        switchUI = false;
      }

      if (progressParams) {
        var docTitleStr = ellipseString(progressParams.docTitle, false);
        if (docTitleStr != docTitle) {
          docTitle = docTitleStr;
          dialog.title.value = docTitle;
        }
        var docURLStr = progressParams.docURL;
        if (docURLStr != docURL && dialog.title != null) {
          docURL = docURLStr;
          if (docTitle == "") {
            dialog.title.value = ellipseString(docURLStr, true);
          }
        }
      }

      // Calculate percentage.
      var percent;
      if ( aMaxTotalProgress > 0 ) {
        percent = Math.round( (aCurTotalProgress * 100) / aMaxTotalProgress );
        if ( percent > 100 )
          percent = 100;

        dialog.progress.removeAttribute( "mode");

        // Advance progress meter.
        dialog.progress.setAttribute( "value", percent );

        // Update percentage label on progress meter.
        var percentPrint = getString( "progressText" );
        percentPrint = replaceInsert( percentPrint, 1, percent );
        dialog.progressText.setAttribute("value", percentPrint);
      } else {
        // Progress meter should be barber-pole in this case.
        dialog.progress.setAttribute( "mode", "undetermined" );
        // Update percentage label on progress meter.
        dialog.progressText.setAttribute("value", "");
      }
    },

    onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
      // we can ignore this notification
    },

    onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
      if (aMessage != "")
        dialog.title.setAttribute("value", aMessage);
    },

    onSecurityChange(aWebProgress, aRequest, state) {
      // we can ignore this notification
    },

    QueryInterface(iid) {
     if (iid.equals(Components.interfaces.nsIWebProgressListener) || iid.equals(Components.interfaces.nsISupportsWeakReference))
      return this;

     throw Components.results.NS_NOINTERFACE;
    }
};

function getString( stringId ) {
   // Check if we've fetched this string already.
   if (!(stringId in dialog.strings)) {
      // Try to get it.
      var elem = document.getElementById( "dialog.strings." + stringId );
      try {
        if ( elem
           &&
           elem.childNodes
           &&
           elem.childNodes[0]
           &&
           elem.childNodes[0].nodeValue ) {
         dialog.strings[stringId] = elem.childNodes[0].nodeValue;
        } else {
          // If unable to fetch string, use an empty string.
          dialog.strings[stringId] = "";
        }
      } catch (e) { dialog.strings[stringId] = ""; }
   }
   return dialog.strings[stringId];
}

function loadDialog() {
}

function replaceInsert( text, index, value ) {
   var result = text;
   var regExp = new RegExp( "#" + index );
   result = result.replace( regExp, value );
   return result;
}

function onLoad() {

    // Set global variables.
    printProgress = window.arguments[0];
    if (window.arguments[1]) {
      progressParams = window.arguments[1].QueryInterface(Components.interfaces.nsIPrintProgressParams)
      if (progressParams) {
        docTitle = ellipseString(progressParams.docTitle, false);
        docURL   = ellipseString(progressParams.docURL, true);
      }
    }

    if ( !printProgress ) {
        dump( "Invalid argument to printProgress.xul\n" );
        window.close()
        return;
    }

    dialog = {};
    dialog.strings = [];
    dialog.title        = document.getElementById("dialog.title");
    dialog.titleLabel   = document.getElementById("dialog.titleLabel");
    dialog.progress     = document.getElementById("dialog.progress");
    dialog.progressText = document.getElementById("dialog.progressText");
    dialog.progressLabel = document.getElementById("dialog.progressLabel");
    dialog.tempLabel    = document.getElementById("dialog.tempLabel");

    dialog.progress.setAttribute("hidden", "true");

    var progressLabel = getString("preparing");
    if (progressLabel == "") {
      progressLabel = "Preparing..."; // better than nothing
    }
    dialog.tempLabel.value = progressLabel;

    dialog.title.value = docTitle;

    // Fill dialog.
    loadDialog();

    // set our web progress listener on the helper app launcher
    printProgress.registerListener(progressListener);
    // We need to delay the set title else dom will overwrite it
    window.setTimeout(doneIniting, 500);
}

function onUnload() {
  if (printProgress) {
   try {
     printProgress.unregisterListener(progressListener);
     printProgress = null;
   } catch ( exception ) {}
  }
}

// If the user presses cancel, tell the app launcher and close the dialog...
function onCancel() {
  // Cancel app launcher.
   try {
     printProgress.processCanceledByUser = true;
   } catch ( exception ) { return true; }

  // don't Close up dialog by returning false, the backend will close the dialog when everything will be aborted.
  return false;
}

function doneIniting() {
  printProgress.doneIniting();
}
PK
!<w/chrome/toolkit/content/global/printProgress.xul<?xml version="1.0"?> 

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://global/locale/printProgress.dtd">

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        buttons="cancel"
        title="&printWindow.title;"
        style="width: 36em;"
        ondialogcancel="onCancel()"
        onload="onLoad()"
        onunload="onUnload()">

  <script type="application/javascript" src="chrome://global/content/printProgress.js"/>

    <!-- This is non-visible content that simply adds translatable string
         into the document so that it is accessible to JS code.
         
         XXX-TODO: 
          convert to use string bundles. 
    -->
  
    <data id="dialog.strings.dialogCloseLabel">&dialogClose.label;</data>
    <data id="dialog.strings.printComplete">&printComplete;</data>
    <data id="dialog.strings.progressText">&percentPrint;</data>
    <data id="dialog.strings.progressLabel">&progress;</data>
    <data id="dialog.strings.preparing">&preparing;</data>

    <grid flex="1">
      <columns>
        <column/>
        <column/>
        <column/>
      </columns>
      
      <rows>
        <row>
          <hbox pack="end">
            <label id="dialog.titleLabel" value="&title;"/>
          </hbox>
            <label id="dialog.title"/>
        </row>
        <row class="thin-separator">             
          <hbox pack="end">
            <label id="dialog.progressLabel" control="dialog.progress" value="&progress;"/>
          </hbox>
            <label id="dialog.tempLabel" value="&preparing;"/>
            <progressmeter id="dialog.progress" mode="normal" value="0"/>
          <hbox pack="end" style="min-width: 2.5em;">
            <label id="dialog.progressText"/>
          </hbox>
        </row>
      </rows>
    </grid>
</dialog>
PK
!<gugu+chrome/toolkit/content/global/printUtils.js// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */

// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * PrintUtils is a utility for front-end code to trigger common print
 * operations (printing, show print preview, show page settings).
 *
 * Unfortunately, likely due to inconsistencies in how different operating
 * systems do printing natively, our XPCOM-level printing interfaces
 * are a bit confusing and the method by which we do something basic
 * like printing a page is quite circuitous.
 *
 * To compound that, we need to support remote browsers, and that means
 * kicking off the print jobs in the content process. This means we send
 * messages back and forth to that process. browser-content.js contains
 * the object that listens and responds to the messages that PrintUtils
 * sends.
 *
 * This also means that <xul:browser>'s that hope to use PrintUtils must have
 * their type attribute set to "content".
 *
 * PrintUtils sends messages at different points in its implementation, but
 * their documentation is consolidated here for ease-of-access.
 *
 *
 * Messages sent:
 *
 *   Printing:Print
 *     Kick off a print job for a nsIDOMWindow, passing the outer window ID as
 *     windowID.
 *
 *   Printing:Preview:Enter
 *     This message is sent to put content into print preview mode. We pass
 *     the content window of the browser we're showing the preview of, and
 *     the target of the message is the browser that we'll be showing the
 *     preview in.
 *
 *   Printing:Preview:Exit
 *     This message is sent to take content out of print preview mode.
 *
 *
 * Messages Received
 *
 *   Printing:Preview:Entered
 *     This message is sent by the content process once it has completed
 *     putting the content into print preview mode. We must wait for that to
 *     to complete before switching the chrome UI to print preview mode,
 *     otherwise we have layout issues.
 *
 *   Printing:Preview:StateChange, Printing:Preview:ProgressChange
 *     Due to a timing issue resulting in a main-process crash, we have to
 *     manually open the progress dialog for print preview. The progress
 *     dialog is opened here in PrintUtils, and then we listen for update
 *     messages from the child. Bug 1088061 has been filed to investigate
 *     other solutions.
 *
 */

var gPrintSettingsAreGlobal = false;
var gSavePrintSettings = false;
var gFocusedElement = null;

var PrintUtils = {
  init() {
    window.messageManager.addMessageListener("Printing:Error", this);
  },

  get bundle() {
    let stringService = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                  .getService(Components.interfaces.nsIStringBundleService);
    delete this.bundle;
    return this.bundle = stringService.createBundle("chrome://global/locale/printing.properties");
  },

  /**
   * Shows the page setup dialog, and saves any settings changed in
   * that dialog if print.save_print_settings is set to true.
   *
   * @return true on success, false on failure
   */
  showPageSetup() {
    try {
      var printSettings = this.getPrintSettings();
      var PRINTPROMPTSVC = Components.classes["@mozilla.org/embedcomp/printingprompt-service;1"]
                                     .getService(Components.interfaces.nsIPrintingPromptService);
      PRINTPROMPTSVC.showPageSetup(window, printSettings, null);
      if (gSavePrintSettings) {
        // Page Setup data is a "native" setting on the Mac
        var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
                              .getService(Components.interfaces.nsIPrintSettingsService);
        PSSVC.savePrintSettingsToPrefs(printSettings, true, printSettings.kInitSaveNativeData);
      }
    } catch (e) {
      dump("showPageSetup " + e + "\n");
      return false;
    }
    return true;
  },

  getDefaultPrinterName() {
    try {
      let PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
                            .getService(Components.interfaces.nsIPrintSettingsService);

      return PSSVC.defaultPrinterName;
    } catch (e) {
      Components.utils.reportError(e);
    }

    return null;
  },

  /**
   * Starts the process of printing the contents of a window.
   *
   * @param aWindowID
   *        The outer window ID of the nsIDOMWindow to print.
   * @param aBrowser
   *        The <xul:browser> that the nsIDOMWindow for aWindowID belongs to.
   */
  printWindow(aWindowID, aBrowser) {
    let mm = aBrowser.messageManager;
    let defaultPrinterName = this.getDefaultPrinterName();
    mm.sendAsyncMessage("Printing:Print", {
      windowID: aWindowID,
      simplifiedMode: this._shouldSimplify,
      defaultPrinterName,
    });
  },

  /**
   * Deprecated.
   *
   * Starts the process of printing the contents of window.content.
   *
   */
  print() {
    if (gBrowser) {
      return this.printWindow(gBrowser.selectedBrowser.outerWindowID,
                              gBrowser.selectedBrowser);
    }

    if (this.usingRemoteTabs) {
      throw new Error("PrintUtils.print cannot be run in windows running with " +
                      "remote tabs. Use PrintUtils.printWindow instead.");
    }

    let domWindow = window.content;
    let ifReq = domWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
    let browser = ifReq.getInterface(Components.interfaces.nsIWebNavigation)
                       .QueryInterface(Components.interfaces.nsIDocShell)
                       .chromeEventHandler;
    if (!browser) {
      throw new Error("PrintUtils.print could not resolve content window " +
                      "to a browser.");
    }

    let windowID = ifReq.getInterface(Components.interfaces.nsIDOMWindowUtils)
                        .outerWindowID;

    let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
    let msg = "PrintUtils.print is now deprecated. Please use PrintUtils.printWindow.";
    let url = "https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Printing";
    Deprecated.warning(msg, url);

    this.printWindow(windowID, browser);
    return undefined;
  },

  /**
   * Initializes print preview.
   *
   * @param aListenerObj
   *        An object that defines the following functions:
   *
   *        getPrintPreviewBrowser:
   *          Returns the <xul:browser> to display the print preview in. This
   *          <xul:browser> must have its type attribute set to "content".
   *
   *        getSimplifiedPrintPreviewBrowser:
   *          Returns the <xul:browser> to display the simplified print preview
   *          in. This <xul:browser> must have its type attribute set to
   *          "content".
   *
   *        getSourceBrowser:
   *          Returns the <xul:browser> that contains the document being
   *          printed. This <xul:browser> must have its type attribute set to
   *          "content".
   *
   *        getSimplifiedSourceBrowser:
   *          Returns the <xul:browser> that contains the simplified version
   *          of the document being printed. This <xul:browser> must have its
   *          type attribute set to "content".
   *
   *        getNavToolbox:
   *          Returns the primary toolbox for this window.
   *
   *        onEnter:
   *          Called upon entering print preview.
   *
   *        onExit:
   *          Called upon exiting print preview.
   *
   *        These methods must be defined. printPreview can be called
   *        with aListenerObj as null iff this window is already displaying
   *        print preview (in which case, the previous aListenerObj passed
   *        to it will be used).
   */
  printPreview(aListenerObj) {
    // If we already have a toolbar someone is calling printPreview() to get us
    // to refresh the display and aListenerObj won't be passed.
    let printPreviewTB = document.getElementById("print-preview-toolbar");
    if (!printPreviewTB) {
      this._listener = aListenerObj;
      this._sourceBrowser = aListenerObj.getSourceBrowser();
      this._originalTitle = this._sourceBrowser.contentTitle;
      this._originalURL = this._sourceBrowser.currentURI.spec;

      // Here we log telemetry data for when the user enters print preview.
      this.logTelemetry("PRINT_PREVIEW_OPENED_COUNT");
    } else {
      // Disable toolbar elements that can cause another update to be triggered
      // during this update.
      printPreviewTB.disableUpdateTriggers(true);

      // collapse the browser here -- it will be shown in
      // enterPrintPreview; this forces a reflow which fixes display
      // issues in bug 267422.
      // We use the print preview browser as the source browser to avoid
      // re-initializing print preview with a document that might now have changed.
      this._sourceBrowser = this._shouldSimplify ?
        this._listener.getSimplifiedPrintPreviewBrowser() :
        this._listener.getPrintPreviewBrowser();
      this._sourceBrowser.collapsed = true;

      // If the user transits too quickly within preview and we have a pending
      // progress dialog, we will close it before opening a new one.
      this.ensureProgressDialogClosed();
    }

    this._webProgressPP = {};
    let ppParams        = {};
    let notifyOnOpen    = {};
    let printSettings   = this.getPrintSettings();
    // Here we get the PrintingPromptService so we can display the PP Progress from script
    // For the browser implemented via XUL with the PP toolbar we cannot let it be
    // automatically opened from the print engine because the XUL scrollbars in the PP window
    // will layout before the content window and a crash will occur.
    // Doing it all from script, means it lays out before hand and we can let printing do its own thing
    let PPROMPTSVC = Components.classes["@mozilla.org/embedcomp/printingprompt-service;1"]
                               .getService(Components.interfaces.nsIPrintingPromptService);
    // just in case we are already printing,
    // an error code could be returned if the Progress Dialog is already displayed
    try {
      PPROMPTSVC.showProgress(window, null, printSettings, this._obsPP, false,
                              this._webProgressPP, ppParams, notifyOnOpen);
      if (ppParams.value) {
        ppParams.value.docTitle = this._originalTitle;
        ppParams.value.docURL   = this._originalURL;
      }

      // this tells us whether we should continue on with PP or
      // wait for the callback via the observer
      if (!notifyOnOpen.value.valueOf() || this._webProgressPP.value == null) {
        this.enterPrintPreview();
      }
    } catch (e) {
      this.enterPrintPreview();
    }
  },

  /**
   * Returns the nsIWebBrowserPrint associated with some content window.
   * This method is being kept here for compatibility reasons, but should not
   * be called by code hoping to support e10s / remote browsers.
   *
   * @param aWindow
   *        The window from which to get the nsIWebBrowserPrint from.
   * @return nsIWebBrowserPrint
   */
  getWebBrowserPrint(aWindow) {
    let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
    let text = "getWebBrowserPrint is now deprecated, and fully unsupported for " +
               "multi-process browsers. Please use a frame script to get " +
               "access to nsIWebBrowserPrint from content.";
    let url = "https://developer.mozilla.org/en-US/docs/Printing_from_a_XUL_App";
    Deprecated.warning(text, url);

    if (this.usingRemoteTabs) {
      return {};
    }

    var contentWindow = aWindow || window.content;
    return contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                        .getInterface(Components.interfaces.nsIWebBrowserPrint);
  },

  /**
   * Returns the nsIWebBrowserPrint from the print preview browser's docShell.
   * This method is being kept here for compatibility reasons, but should not
   * be called by code hoping to support e10s / remote browsers.
   *
   * @return nsIWebBrowserPrint
   */
  getPrintPreview() {
    let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
    let text = "getPrintPreview is now deprecated, and fully unsupported for " +
               "multi-process browsers. Please use a frame script to get " +
               "access to nsIWebBrowserPrint from content.";
    let url = "https://developer.mozilla.org/en-US/docs/Printing_from_a_XUL_App";
    Deprecated.warning(text, url);

    if (this.usingRemoteTabs) {
      return {};
    }

    return this._currentPPBrowser.docShell.printPreview;
  },

  // "private" methods and members. Don't use them.

  _listener: null,
  _closeHandlerPP: null,
  _webProgressPP: null,
  _sourceBrowser: null,
  _originalTitle: "",
  _originalURL: "",
  _shouldSimplify: false,

  get usingRemoteTabs() {
    // We memoize this, since it's highly unlikely to change over the lifetime
    // of the window.
    let usingRemoteTabs =
      window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
            .getInterface(Components.interfaces.nsIWebNavigation)
            .QueryInterface(Components.interfaces.nsILoadContext)
            .useRemoteTabs;
    delete this.usingRemoteTabs;
    return this.usingRemoteTabs = usingRemoteTabs;
  },

  displayPrintingError(nsresult, isPrinting) {
    // The nsresults from a printing error are mapped to strings that have
    // similar names to the errors themselves. For example, for error
    // NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, the name of the string
    // for the error message is: PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE. What's
    // more, if we're in the process of doing a print preview, it's possible
    // that there are strings specific for print preview for these errors -
    // if so, the names of those strings have _PP as a suffix. It's possible
    // that no print preview specific strings exist, in which case it is fine
    // to fall back to the original string name.
    const MSG_CODES = [
      "GFX_PRINTER_NO_PRINTER_AVAILABLE",
      "GFX_PRINTER_NAME_NOT_FOUND",
      "GFX_PRINTER_COULD_NOT_OPEN_FILE",
      "GFX_PRINTER_STARTDOC",
      "GFX_PRINTER_ENDDOC",
      "GFX_PRINTER_STARTPAGE",
      "GFX_PRINTER_DOC_IS_BUSY",
      "ABORT",
      "NOT_AVAILABLE",
      "NOT_IMPLEMENTED",
      "OUT_OF_MEMORY",
      "UNEXPECTED",
    ];

    // PERR_FAILURE is the catch-all error message if we've gotten one that
    // we don't recognize.
    let msgName = "PERR_FAILURE";

    for (let code of MSG_CODES) {
      let nsErrorResult = "NS_ERROR_" + code;
      if (Components.results[nsErrorResult] == nsresult) {
        msgName = "PERR_" + code;
        break;
      }
    }

    let msg, title;

    if (!isPrinting) {
      // Try first with _PP suffix.
      let ppMsgName = msgName + "_PP";
      try {
        msg = this.bundle.GetStringFromName(ppMsgName);
      } catch (e) {
        // We allow localizers to not have the print preview error string,
        // and just fall back to the printing error string.
      }
    }

    if (!msg) {
      msg = this.bundle.GetStringFromName(msgName);
    }

    title = this.bundle.GetStringFromName(isPrinting ? "print_error_dialog_title"
                                                     : "printpreview_error_dialog_title");

    let promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                              .getService(Components.interfaces.nsIPromptService);
    promptSvc.alert(window, title, msg);
  },

  receiveMessage(aMessage) {
    if (aMessage.name == "Printing:Error") {
      this.displayPrintingError(aMessage.data.nsresult,
                                aMessage.data.isPrinting);
      return undefined;
    }

    // If we got here, then the message we've received must involve
    // updating the print progress UI.
    if (!this._webProgressPP.value) {
      // We somehow didn't get a nsIWebProgressListener to be updated...
      // I guess there's nothing to do.
      return undefined;
    }

    let listener = this._webProgressPP.value;
    let mm = aMessage.target.messageManager;
    let data = aMessage.data;

    switch (aMessage.name) {
      case "Printing:Preview:ProgressChange": {
        return listener.onProgressChange(null, null,
                                         data.curSelfProgress,
                                         data.maxSelfProgress,
                                         data.curTotalProgress,
                                         data.maxTotalProgress);
      }

      case "Printing:Preview:StateChange": {
        if (data.stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
          // Strangely, the printing engine sends 2 STATE_STOP messages when
          // print preview is finishing. One has the STATE_IS_DOCUMENT flag,
          // the other has the STATE_IS_NETWORK flag. However, the webProgressPP
          // listener stops listening once the first STATE_STOP is sent.
          // Any subsequent messages result in NS_ERROR_FAILURE errors getting
          // thrown. This should all get torn out once bug 1088061 is fixed.
          mm.removeMessageListener("Printing:Preview:StateChange", this);
          mm.removeMessageListener("Printing:Preview:ProgressChange", this);

          // Enable toobar elements that we disabled during update.
          let printPreviewTB = document.getElementById("print-preview-toolbar");
          printPreviewTB.disableUpdateTriggers(false);
        }

        return listener.onStateChange(null, null,
                                      data.stateFlags,
                                      data.status);
      }
    }
    return undefined;
  },

  setPrinterDefaultsForSelectedPrinter(aPSSVC, aPrintSettings) {
    if (!aPrintSettings.printerName)
      aPrintSettings.printerName = aPSSVC.defaultPrinterName;

    // First get any defaults from the printer
    aPSSVC.initPrintSettingsFromPrinter(aPrintSettings.printerName, aPrintSettings);
    // now augment them with any values from last time
    aPSSVC.initPrintSettingsFromPrefs(aPrintSettings, true, aPrintSettings.kInitSaveAll);
  },

  getPrintSettings() {
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
    if (pref) {
      gPrintSettingsAreGlobal = pref.getBoolPref("print.use_global_printsettings");
      gSavePrintSettings = pref.getBoolPref("print.save_print_settings");
    }

    var printSettings;
    try {
      var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
                            .getService(Components.interfaces.nsIPrintSettingsService);
      if (gPrintSettingsAreGlobal) {
        printSettings = PSSVC.globalPrintSettings;
        this.setPrinterDefaultsForSelectedPrinter(PSSVC, printSettings);
      } else {
        printSettings = PSSVC.newPrintSettings;
      }
    } catch (e) {
      dump("getPrintSettings: " + e + "\n");
    }
    return printSettings;
  },

  // This observer is called once the progress dialog has been "opened"
  _obsPP:
  {
    observe(aSubject, aTopic, aData) {
      // delay the print preview to show the content of the progress dialog
      setTimeout(function() { PrintUtils.enterPrintPreview(); }, 0);
    },

    QueryInterface(iid) {
      if (iid.equals(Components.interfaces.nsIObserver) ||
          iid.equals(Components.interfaces.nsISupportsWeakReference) ||
          iid.equals(Components.interfaces.nsISupports))
        return this;
      throw Components.results.NS_NOINTERFACE;
    }
  },

  setSimplifiedMode(shouldSimplify) {
    this._shouldSimplify = shouldSimplify;
  },

  /**
  * Currently, we create a new print preview browser to host the simplified
  * cloned-document when Simplify Page option is used on preview. To accomplish
  * this, we need to keep track of what browser should be presented, based on
  * whether the 'Simplify page' checkbox is checked.
  *
  * _ppBrowsers
  *        Set of print preview browsers.
  * _currentPPBrowser
  *        References the current print preview browser that is being presented.
  */
  _ppBrowsers: new Set(),
  _currentPPBrowser: null,

  enterPrintPreview() {
    // Send a message to the print preview browser to initialize
    // print preview. If we happen to have gotten a print preview
    // progress listener from nsIPrintingPromptService.showProgress
    // in printPreview, we add listeners to feed that progress
    // listener.
    let ppBrowser = this._shouldSimplify ?
      this._listener.getSimplifiedPrintPreviewBrowser() :
      this._listener.getPrintPreviewBrowser();
    this._ppBrowsers.add(ppBrowser);

    // If we're switching from 'normal' print preview to 'simplified' print
    // preview, we will want to run reader mode against the 'normal' print
    // preview browser's content:
    let oldPPBrowser = null;
    let changingPrintPreviewBrowsers = false;
    if (this._currentPPBrowser && ppBrowser != this._currentPPBrowser) {
      changingPrintPreviewBrowsers = true;
      oldPPBrowser = this._currentPPBrowser;
    }
    this._currentPPBrowser = ppBrowser;
    let mm = ppBrowser.messageManager;
    let defaultPrinterName = this.getDefaultPrinterName();

    let sendEnterPreviewMessage = function(browser, simplified) {
      mm.sendAsyncMessage("Printing:Preview:Enter", {
        windowID: browser.outerWindowID,
        simplifiedMode: simplified,
        changingBrowsers: changingPrintPreviewBrowsers,
        defaultPrinterName,
      });
    };

    // If we happen to have gotten simplify page checked, we will lazily
    // instantiate a new tab that parses the original page using ReaderMode
    // primitives. When it's ready, and in order to enter on preview, we send
    // over a message to print preview browser passing up the simplified tab as
    // reference. If not, we pass the original tab instead as content source.
    if (this._shouldSimplify) {
      let simplifiedBrowser = this._listener.getSimplifiedSourceBrowser();
      if (simplifiedBrowser) {
        sendEnterPreviewMessage(simplifiedBrowser, true);
      } else {
        simplifiedBrowser = this._listener.createSimplifiedBrowser();

        // After instantiating the simplified tab, we attach a listener as
        // callback. Once we discover reader mode has been loaded, we fire
        // up a message to enter on print preview.
        let spMM = simplifiedBrowser.messageManager;
        spMM.addMessageListener("Printing:Preview:ReaderModeReady", function onReaderReady() {
          spMM.removeMessageListener("Printing:Preview:ReaderModeReady", onReaderReady);
          sendEnterPreviewMessage(simplifiedBrowser, true);
        });

        // Here, we send down a message to simplified browser in order to parse
        // the original page. After we have parsed it, content will tell parent
        // that the document is ready for print previewing.
        spMM.sendAsyncMessage("Printing:Preview:ParseDocument", {
          URL: this._originalURL,
          windowID: oldPPBrowser.outerWindowID,
        });

        // Here we log telemetry data for when the user enters simplify mode.
        this.logTelemetry("PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT");
      }
    } else {
      sendEnterPreviewMessage(this._sourceBrowser, false);
    }

    let waitForPrintProgressToEnableToolbar = false;
    if (this._webProgressPP.value) {
      mm.addMessageListener("Printing:Preview:StateChange", this);
      mm.addMessageListener("Printing:Preview:ProgressChange", this);
      waitForPrintProgressToEnableToolbar = true;
    }

    let onEntered = (message) => {
      mm.removeMessageListener("Printing:Preview:Entered", onEntered);

      if (message.data.failed) {
        // Something went wrong while putting the document into print preview
        // mode. Bail out.
        this._listener.onEnter();
        this._listener.onExit();
        return;
      }

      // Stash the focused element so that we can return to it after exiting
      // print preview.
      gFocusedElement = document.commandDispatcher.focusedElement;

      let printPreviewTB = document.getElementById("print-preview-toolbar");
      if (printPreviewTB) {
        if (message.data.changingBrowsers) {
          printPreviewTB.destroy();
          printPreviewTB.initialize(ppBrowser);
        } else {
          // printPreviewTB.initialize above already calls updateToolbar.
          printPreviewTB.updateToolbar();
        }

        // If we don't have a progress listener to enable the toolbar do it now.
        if (!waitForPrintProgressToEnableToolbar) {
          printPreviewTB.disableUpdateTriggers(false);
        }

        ppBrowser.collapsed = false;
        ppBrowser.focus();
        return;
      }

      // Set the original window as an active window so any mozPrintCallbacks can
      // run without delayed setTimeouts.
      if (this._listener.activateBrowser) {
        this._listener.activateBrowser(this._sourceBrowser);
      } else {
        this._sourceBrowser.docShellIsActive = true;
      }

      // show the toolbar after we go into print preview mode so
      // that we can initialize the toolbar with total num pages
      const XUL_NS =
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      printPreviewTB = document.createElementNS(XUL_NS, "toolbar");
      printPreviewTB.setAttribute("printpreview", true);
      printPreviewTB.setAttribute("fullscreentoolbar", true);
      printPreviewTB.id = "print-preview-toolbar";

      let navToolbox = this._listener.getNavToolbox();
      navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox);
      printPreviewTB.initialize(ppBrowser);

      // The print preview processing may not have fully completed, so if we
      // have a progress listener, disable the toolbar elements that can trigger
      // updates and it will enable them when completed.
      if (waitForPrintProgressToEnableToolbar) {
        printPreviewTB.disableUpdateTriggers(true);
      }

      // Enable simplify page checkbox when the page is an article
      if (this._sourceBrowser.isArticle) {
        printPreviewTB.enableSimplifyPage();
      } else {
        this.logTelemetry("PRINT_PREVIEW_SIMPLIFY_PAGE_UNAVAILABLE_COUNT");
        printPreviewTB.disableSimplifyPage();
      }

      // copy the window close handler
      if (document.documentElement.hasAttribute("onclose"))
        this._closeHandlerPP = document.documentElement.getAttribute("onclose");
      else
        this._closeHandlerPP = null;
      document.documentElement.setAttribute("onclose", "PrintUtils.exitPrintPreview(); return false;");

      // disable chrome shortcuts...
      window.addEventListener("keydown", this.onKeyDownPP, true);
      window.addEventListener("keypress", this.onKeyPressPP, true);

      ppBrowser.collapsed = false;
      ppBrowser.focus();
      // on Enter PP Call back
      this._listener.onEnter();
    };

    mm.addMessageListener("Printing:Preview:Entered", onEntered);
  },

  exitPrintPreview() {
    for (let browser of this._ppBrowsers) {
      let browserMM = browser.messageManager;
      browserMM.sendAsyncMessage("Printing:Preview:Exit");
    }
    this._ppBrowsers.clear();
    this._currentPPBrowser = null;
    window.removeEventListener("keydown", this.onKeyDownPP, true);
    window.removeEventListener("keypress", this.onKeyPressPP, true);

    // restore the old close handler
    document.documentElement.setAttribute("onclose", this._closeHandlerPP);
    this._closeHandlerPP = null;

    // remove the print preview toolbar
    let printPreviewTB = document.getElementById("print-preview-toolbar");
    printPreviewTB.destroy();
    printPreviewTB.remove();

    let fm = Components.classes["@mozilla.org/focus-manager;1"]
                       .getService(Components.interfaces.nsIFocusManager);
    if (gFocusedElement)
      fm.setFocus(gFocusedElement, fm.FLAG_NOSCROLL);
    else
      this._sourceBrowser.focus();
    gFocusedElement = null;

    this.setSimplifiedMode(false);

    this.ensureProgressDialogClosed();

    this._listener.onExit();
  },

  logTelemetry(ID) {
    let histogram = Services.telemetry.getHistogramById(ID);
    histogram.add(true);
  },

  onKeyDownPP(aEvent) {
    // Esc exits the PP
    if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
      PrintUtils.exitPrintPreview();
    }
  },

  onKeyPressPP(aEvent) {
    var closeKey;
    try {
      closeKey = document.getElementById("key_close")
                         .getAttribute("key");
      closeKey = aEvent["DOM_VK_" + closeKey];
    } catch (e) {}
    var isModif = aEvent.ctrlKey || aEvent.metaKey;
    // Ctrl-W exits the PP
    if (isModif &&
        (aEvent.charCode == closeKey || aEvent.charCode == closeKey + 32)) {
      PrintUtils.exitPrintPreview();
    } else if (isModif) {
      var printPreviewTB = document.getElementById("print-preview-toolbar");
      var printKey = document.getElementById("printKb").getAttribute("key").toUpperCase();
      var pressedKey = String.fromCharCode(aEvent.charCode).toUpperCase();
      if (printKey == pressedKey) {
        printPreviewTB.print();
      }
    }
    // cancel shortkeys
    if (isModif) {
      aEvent.preventDefault();
      aEvent.stopPropagation();
    }
  },

  /**
   * If there's a printing or print preview progress dialog displayed, force
   * it to close now.
   */
  ensureProgressDialogClosed() {
    if (this._webProgressPP && this._webProgressPP.value) {
      this._webProgressPP.value.onStateChange(null, null,
        Components.interfaces.nsIWebProgressListener.STATE_STOP, 0);
    }
  },
}

PrintUtils.init();
PK
!<lh
h
0chrome/toolkit/content/global/process-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { classes: Cc, interfaces: Ci, utils: Cu } = Components;

// Creates a new PageListener for this process. This will listen for page loads
// and for those that match URLs provided by the parent process will set up
// a dedicated message port and notify the parent process.
Cu.import("resource://gre/modules/RemotePageManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");

const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;

Services.cpmm.addMessageListener("gmp-plugin-crash", msg => {
  let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"]
                     .getService(Ci.mozIGeckoMediaPluginService);

  gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName);
});

if (gInContentProcess) {
  let ProcessObserver = {
    TOPICS: [
      "inner-window-destroyed",
      "xpcom-shutdown",
    ],

    init() {
      for (let topic of this.TOPICS) {
        Services.obs.addObserver(this, topic);
        Services.cpmm.addMessageListener("Memory:GetSummary", this);
      }
    },

    uninit() {
      for (let topic of this.TOPICS) {
        Services.obs.removeObserver(this, topic);
        Services.cpmm.removeMessageListener("Memory:GetSummary", this);
      }
    },

    receiveMessage(msg) {
      if (msg.name != "Memory:GetSummary") {
        return;
      }
      let pid = Services.appinfo.processID;
      let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                     .getService(Ci.nsIMemoryReporterManager);
      let rss = memMgr.resident;
      let uss = memMgr.residentUnique;
      Services.cpmm.sendAsyncMessage("Memory:Summary", {
        pid,
        summary: {
          uss,
          rss,
        }
      });
    },

    observe(subject, topic, data) {
      switch (topic) {
        case "inner-window-destroyed": {
          // Forward inner-window-destroyed notifications with the
          // inner window ID, so that code in the parent that should
          // do something when content windows go away can do it
          let innerWindowID =
            subject.QueryInterface(Ci.nsISupportsPRUint64).data;
          Services.cpmm.sendAsyncMessage("Toolkit:inner-window-destroyed",
                                         innerWindowID);
          break;
        }
        case "xpcom-shutdown": {
          this.uninit();
          break;
        }
      }
    },
  };

  ProcessObserver.init();
}
PK
!<I		5chrome/toolkit/content/global/reader/aboutReader.html<!DOCTYPE html>
<html>

<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <meta name="viewport" content="width=device-width; user-scalable=0" />

  <link rel="stylesheet" href="chrome://global/skin/aboutReader.css" type="text/css"/>

  <script type="text/javascript" src="chrome://global/content/reader/aboutReader.js"></script>
</head>

<body>
  <div id="container" class="container">
    <div id="reader-header" class="header">
      <style scoped>
        @import url("chrome://global/skin/aboutReaderControls.css");
      </style>
      <a id="reader-domain" class="domain"></a>
      <div class="domain-border"></div>
      <h1 id="reader-title"></h1>
      <div id="reader-credits" class="credits"></div>
      <div id="meta-data" class="meta-data">
        <div id="reader-estimated-time"></div>
      </div>
    </div>

    <hr>

    <div class="content">
      <style scoped>
        @import url("chrome://global/skin/aboutReaderContent.css");
      </style>
      <div id="moz-reader-content"></div>
    </div>

    <div>
      <style scoped>
        @import url("chrome://global/skin/aboutReaderControls.css");
      </style>
      <div id="reader-message"></div>
    </div>
  </div>

  <ul id="reader-toolbar" class="toolbar">
    <style scoped>
      @import url("chrome://global/skin/aboutReaderControls.css");
    </style>
    <li><button id="close-button" class="button close-button"/></li>
    <ul id="style-dropdown" class="dropdown">
      <li><button class="dropdown-toggle button style-button"/></li>
      <li id="reader-popup" class="dropdown-popup">
        <div id="font-type-buttons"></div>
        <hr>
        <div id="font-size-buttons">
          <button id="font-size-minus" class="minus-button"/>
          <button id="font-size-sample"/>
          <button id="font-size-plus" class="plus-button"/>
        </div>
        <hr>
        <div id="content-width-buttons">
          <button id="content-width-minus" class="content-width-minus-button"/>
          <button id="content-width-plus" class="content-width-plus-button"/>
        </div>
        <hr>
        <div id="line-height-buttons">
          <button id="line-height-minus" class="line-height-minus-button"/>
          <button id="line-height-plus" class="line-height-plus-button"/>
        </div>
        <hr>
        <div id="color-scheme-buttons"></div>
        <div class="dropdown-arrow"/>
      </li>
    </ul>
  </ul>

</body>

</html>
PK
!<Ztt3chrome/toolkit/content/global/reader/aboutReader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

window.addEventListener("DOMContentLoaded", function() {
  document.dispatchEvent(new CustomEvent("AboutReaderContentLoaded", { bubbles: true }));
});
PK
!<B90chrome/toolkit/content/global/remote-test-ipc.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

dump("Loading remote script!\n");
dump(content + "\n");

var cpm = Components.classes["@mozilla.org/childprocessmessagemanager;1"]
                            .getService(Components.interfaces.nsISyncMessageSender);
cpm.addMessageListener("cpm-async",
  function(m) {
    cpm.sendSyncMessage("ppm-sync");
    dump(content.document.documentElement);
    cpm.sendAsyncMessage("ppm-async");
  });

var Cc = Components.classes;
var Ci = Components.interfaces;
var dshell = content.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShellTreeItem)
                    .rootTreeItem
                    .QueryInterface(Ci.nsIDocShell);


addEventListener("click",
  function(e) {
    dump(e.target + "\n");
    if (e.target instanceof Components.interfaces.nsIDOMHTMLAnchorElement &&
        dshell == docShell) {
      var retval = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                            getInterface(Components.interfaces.nsIContentFrameMessageManager).
                            sendSyncMessage("linkclick", { href: e.target.href });
      dump(uneval(retval[0]) + "\n");
      // Test here also that both retvals are the same
      sendAsyncMessage("linkclick-reply-object", uneval(retval[0]) == uneval(retval[1]) ? retval[0] : "");
    }
  },
  true);

addMessageListener("chrome-message",
  function(m) {
    dump(uneval(m.json) + "\n");
    sendAsyncMessage("chrome-message-reply", m.json);
  });

addMessageListener("speed-test-start",
  function(m) {
    while (sendSyncMessage("speed-test")[0].message != "done");
  });

addMessageListener("async-echo", function(m) {
  sendAsyncMessage(m.name);
});
PK
!<{Vޡ__.chrome/toolkit/content/global/resetProfile.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#migratedItems {
  margin-inline-start: 1.5em;
}

#resetProfileFooter {
  font-weight: bold;
}

#resetProfileProgressDialog {
  padding: 10px;
}
PK
!<
-chrome/toolkit/content/global/resetProfile.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// NB: this file can be loaded from aboutSupport.xhtml or from the
// resetProfile.xul dialog, and so Cu may or may not exist already.
// Proceed with caution:
if (!("Cu" in window)) {
  window.Cu = Components.utils;
}

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ResetProfile.jsm");

function onResetProfileAccepted() {
  let retVals = window.arguments[0];
  retVals.reset = true;
}
PK
!<=zrr.chrome/toolkit/content/global/resetProfile.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE prefwindow [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" >
%resetProfileDTD;
]>

<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://global/content/resetProfile.css"?>

<dialog id="resetProfileDialog"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
            title="&refreshProfile.dialog.title;"
            buttons="accept,cancel"
            defaultButton="cancel"
            buttonlabelaccept="&refreshProfile.dialog.button.label;"
            ondialogaccept="return onResetProfileAccepted();"
            ondialogcancel="window.close();">

  <script type="application/javascript" src="chrome://global/content/resetProfile.js"/>

  <description value="&refreshProfile.dialog.description1;"></description>
  <label value="&refreshProfile.dialog.description2;"/>

  <vbox id="migratedItems">
    <label class="migratedLabel" value="&refreshProfile.dialog.items.label1;"/>
    <label class="migratedLabel" value="&refreshProfile.dialog.items.label2;"/>
  </vbox>
</dialog>
PK
!<yvv6chrome/toolkit/content/global/resetProfileProgress.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" >
%resetProfileDTD;
]>

<?xml-stylesheet href="chrome://global/content/resetProfile.css"?>
<?xml-stylesheet href="chrome://global/skin/"?>

<window id="resetProfileProgressDialog"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&refreshProfile.dialog.title;"
        style="min-width: 300px;">
  <vbox>
    <description>&refreshProfile.cleaning.description;</description>
    <progressmeter mode="undetermined"/>
  </vbox>
</window>
PK
!<}*-chrome/toolkit/content/global/selectDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

 // Defined in dialog.xml.
 /* globals centerWindowOnScreen:false, moveToAlertPosition:false */

var Ci = Components.interfaces;
var Cr = Components.results;
var Cc = Components.classes;
var Cu = Components.utils;

var gArgs, listBox;

function dialogOnLoad() {
    gArgs = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
                               .QueryInterface(Ci.nsIWritablePropertyBag);

    let promptType = gArgs.getProperty("promptType");
    if (promptType != "select") {
        Cu.reportError("selectDialog opened for unknown type: " + promptType);
        window.close();
    }

    // Default to canceled.
    gArgs.setProperty("ok", false);

    document.title = gArgs.getProperty("title");

    let text = gArgs.getProperty("text");
    document.getElementById("info.txt").setAttribute("value", text);

    let items = gArgs.getProperty("list");
    listBox = document.getElementById("list");

    for (let i = 0; i < items.length; i++) {
        let str = items[i];
        if (str == "")
            str = "<>";
        listBox.appendItem(str);
        listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick);
    }
    listBox.selectedIndex = 0;
    listBox.focus();

    // resize the window to the content
    window.sizeToContent();

    // Move to the right location
    moveToAlertPosition();
    centerWindowOnScreen();

    // play sound
    try {
        Cc["@mozilla.org/sound;1"].
        createInstance(Ci.nsISound).
        playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN);
    } catch (e) { }
}

function dialogOK() {
    gArgs.setProperty("selected", listBox.selectedIndex);
    gArgs.setProperty("ok", true);
    return true;
}

function dialogDoubleClick() {
    dialogOK();
    window.close();
}
PK
!<$$.chrome/toolkit/content/global/selectDialog.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      onload="dialogOnLoad()"
      ondialogaccept="return dialogOK();">

  <script type="application/javascript" src="chrome://global/content/selectDialog.js" />
  <keyset id="dialogKeys"/>
  <vbox style="width: 24em;margin: 5px;">
    <label id="info.txt"/>
    <vbox>
      <listbox id="list" rows="4" flex="1"/>
    </vbox>
  </vbox>
</dialog>
PK
!<|.chrome/toolkit/content/global/simplifyMode.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This file defines specific rules for print preview when using simplify mode.
 * Some of these rules (styling for title and author on the header element)
 * already exist on aboutReaderControls.css, however, we decoupled it from the
 * original file so we don't need to load a bunch of extra queries that will not
 * take effect when using the simplify page checkbox. */

body {
  padding-top: 0px;
  padding-bottom: 0px;
}

#container {
  max-width: 100%;
  font-family: Georgia, "Times New Roman", serif;
}

.header > h1 {
  font-size: 1.6em;
  line-height: 1.25em;
  margin: 30px 0;
}

.header > .credits {
  font-size: 0.9em;
  line-height: 1.48em;
  margin: 0 0 30px 0;
  font-style: italic;
}PK
!<V,chrome/toolkit/content/global/tabprompts.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Tab Modal Prompt boxes */
tabmodalprompt {
  width: 100%;
  height: 100%;
  -moz-box-pack: center;
  -moz-box-orient: vertical;
}

.mainContainer {
  min-width: 20em;
  min-height: 12em;
  -moz-user-focus: normal;
}

.info\.title {
  margin-bottom: 1em !important;
  font-weight: bold;
}

.info\.body {
  margin: 0 !important;
  -moz-user-focus: normal;
  -moz-user-select: text;
  cursor: text !important;
  white-space: pre-wrap;
  unicode-bidi: plaintext;
}

label[value=""] {
  visibility: collapse;
}
PK
!<W_P88,chrome/toolkit/content/global/tabprompts.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This file is imported into the browser window, and expects various variables,
     e.g. Ci, Services, to be available. -->
<!-- eslint-env mozilla/browser-window -->

<!DOCTYPE bindings [
<!ENTITY % commonDialogDTD  SYSTEM "chrome://global/locale/commonDialog.dtd">
<!ENTITY % dialogOverlayDTD SYSTEM "chrome://global/locale/dialogOverlay.dtd">
%commonDialogDTD;
%dialogOverlayDTD;
]>

<bindings id="tabPrompts"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tabmodalprompt">

    <resources>
        <stylesheet src="chrome://global/content/tabprompts.css"/>
        <stylesheet src="chrome://global/skin/tabprompts.css"/>
    </resources>

    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
                 role="dialog"
                 aria-describedby="info.body">

        <!-- This is based on the guts of commonDialog.xul -->
        <spacer flex="1"/>
        <hbox pack="center">
            <vbox anonid="mainContainer" class="mainContainer">
                <grid class="topContainer" flex="1">
                    <columns>
                        <column/>
                        <column flex="1"/>
                    </columns>

                    <rows>
                        <vbox anonid="infoContainer" align="center" pack="center" flex="1">
                            <description anonid="info.title" class="info.title" hidden="true" />
                            <description anonid="info.body" class="info.body"/>
                        </vbox>

                        <row anonid="loginContainer" hidden="true" align="center">
                            <label anonid="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
                            <textbox anonid="loginTextbox"/>
                        </row>

                        <row anonid="password1Container" hidden="true" align="center">
                            <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/>
                            <textbox anonid="password1Textbox" type="password"/>
                        </row>

                        <row anonid="checkboxContainer" hidden="true">
                            <spacer/>
                            <checkbox anonid="checkbox"/>
                        </row>

                        <xbl:children includes="row"/>
                    </rows>
                </grid>
                <xbl:children/>
                <hbox class="buttonContainer">
                    <button anonid="button3" hidden="true"/>
                    <spacer anonid="buttonSpacer" flex="1"/>
                    <button anonid="button0" label="&okButton.label;"/>
                    <button anonid="button2" hidden="true"/>
                    <button anonid="button1" label="&cancelButton.label;"/>
                </hbox>
            </vbox>
        </hbox>
        <spacer flex="2"/>
    </xbl:content>

    <implementation implements="nsIDOMEventListener">
        <constructor>
        <![CDATA[
            let self = this;
            function getElement(anonid) {
                return document.getAnonymousElementByAttribute(self, "anonid", anonid);
            }

            this.ui = {
                prompt: this,
                loginContainer: getElement("loginContainer"),
                loginTextbox: getElement("loginTextbox"),
                loginLabel: getElement("loginLabel"),
                password1Container: getElement("password1Container"),
                password1Textbox: getElement("password1Textbox"),
                password1Label: getElement("password1Label"),
                infoBody: getElement("info.body"),
                infoTitle: getElement("info.title"),
                infoIcon: null,
                checkbox: getElement("checkbox"),
                checkboxContainer: getElement("checkboxContainer"),
                button3: getElement("button3"),
                button2: getElement("button2"),
                button1: getElement("button1"),
                button0: getElement("button0"),
                // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported
            };

            this.ui.button0.addEventListener("command", this.onButtonClick.bind(this, 0));
            this.ui.button1.addEventListener("command", this.onButtonClick.bind(this, 1));
            this.ui.button2.addEventListener("command", this.onButtonClick.bind(this, 2));
            this.ui.button3.addEventListener("command", this.onButtonClick.bind(this, 3));
            // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called!
            this.ui.checkbox.addEventListener("command", function() { self.Dialog.onCheckbox(); });
            this.isLive = false;
        ]]>
        </constructor>
        <destructor>
        <![CDATA[
            if (this.isLive) {
                this.abortPrompt();
            }
        ]]>
        </destructor>

        <field name="ui"/>
        <field name="args"/>
        <field name="linkedTab"/>
        <field name="onCloseCallback"/>
        <field name="Dialog"/>
        <field name="isLive"/>
        <field name="availWidth"/>
        <field name="availHeight"/>
        <field name="minWidth"/>
        <field name="minHeight"/>

        <method name="init">
            <parameter name="args"/>
            <parameter name="linkedTab"/>
            <parameter name="onCloseCallback"/>
            <body>
            <![CDATA[
                this.args = args;
                this.linkedTab = linkedTab;
                this.onCloseCallback = onCloseCallback;

                if (args.enableDelay)
                    throw "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts";

                // We need to remove the prompt when the tab or browser window is closed or
                // the page navigates, else we never unwind the event loop and that's sad times.
                // Remember to cleanup in shutdownPrompt()!
                this.isLive = true;
                window.addEventListener("resize", this);
                window.addEventListener("unload", this);
                if (linkedTab) {
                  linkedTab.addEventListener("TabClose", this);
                }
                // Note:
                // nsPrompter.js or in e10s mode browser-parent.js call abortPrompt,
                // when the domWindow, for which the prompt was created, generates
                // a "pagehide" event.

                let tmp = {};
                Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp);
                this.Dialog = new tmp.CommonDialog(args, this.ui);
                this.Dialog.onLoad(null);

                // Display the tabprompt title that shows the prompt origin when
                // the prompt origin is not the same as that of the top window.
                if (!args.showAlertOrigin)
                    this.ui.infoTitle.removeAttribute("hidden");

                // TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
                //       Better yet, just drop support for 4-button dialogs. (bug 609510)

                this.onResize();
            ]]>
            </body>
        </method>

        <method name="shutdownPrompt">
            <body>
            <![CDATA[
                // remove our event listeners
                try {
                    window.removeEventListener("resize", this);
                    window.removeEventListener("unload", this);
                    if (this.linkedTab) {
                      this.linkedTab.removeEventListener("TabClose", this);
                    }
                } catch (e) { }
                this.isLive = false;
                // invoke callback
                this.onCloseCallback();
            ]]>
            </body>
        </method>

        <method name="abortPrompt">
            <body>
            <![CDATA[
                // Called from other code when the page changes.
                this.Dialog.abortPrompt();
                this.shutdownPrompt();
            ]]>
            </body>
        </method>

        <method name="handleEvent">
            <parameter name="aEvent"/>
            <body>
            <![CDATA[
                switch (aEvent.type) {
                  case "resize":
                    this.onResize();
                    break;
                  case "unload":
                  case "TabClose":
                    this.abortPrompt();
                    break;
                }
            ]]>
            </body>
        </method>

        <method name="onResize">
            <body>
            <![CDATA[
                let availWidth = this.clientWidth;
                let availHeight = this.clientHeight;
                if (availWidth == this.availWidth && availHeight == this.availHeight)
                    return;
                this.availWidth = availWidth;
                this.availHeight = availHeight;

                let self = this;
                function getElement(anonid) {
                    return document.getAnonymousElementByAttribute(self, "anonid", anonid);
                }
                let main = getElement("mainContainer");
                let info = getElement("infoContainer");
                let body = this.ui.infoBody;

                // cap prompt dimensions at 60% width and 60% height of content area
                if (!this.minWidth)
                  this.minWidth = parseInt(window.getComputedStyle(main).minWidth);
                if (!this.minHeight)
                  this.minHeight = parseInt(window.getComputedStyle(main).minHeight);
                let maxWidth = Math.max(Math.floor(availWidth * 0.6), this.minWidth) +
                               info.clientWidth - main.clientWidth;
                let maxHeight = Math.max(Math.floor(availHeight * 0.6), this.minHeight) +
                                info.clientHeight - main.clientHeight;
                body.style.maxWidth = maxWidth + "px";
                info.style.overflow = info.style.width = info.style.height = "";

                // when prompt text is too long, use scrollbars
                if (info.clientWidth > maxWidth) {
                    info.style.overflow = "auto";
                    info.style.width = maxWidth + "px";
                }
                if (info.clientHeight > maxHeight) {
                    info.style.overflow = "auto";
                    info.style.height = maxHeight + "px";
                }
            ]]>
            </body>
        </method>

        <method name="onButtonClick">
            <parameter name="buttonNum"/>
            <body>
            <![CDATA[
                // We want to do all the work her asynchronously off a Gecko
                // runnable, because of situations like the one described in
                // https://bugzilla.mozilla.org/show_bug.cgi?id=1167575#c35 : we
                // get here off processing of an OS event and will also process
                // one more Gecko runnable before we break out of the event loop
                // spin whoever posted the prompt is doing.  If we do all our
                // work sync, we will exit modal state _before_ processing that
                // runnable, and if exiting moral state posts a runnable we will
                // incorrectly process that runnable before leaving our event
                // loop spin.
                Services.tm.dispatchToMainThread(() => {
                    this.Dialog["onButton" + buttonNum]();
                    this.shutdownPrompt();
                  });
            ]]>
            </body>
        </method>

        <method name="onKeyAction">
            <parameter name="action"/>
            <parameter name="event"/>
            <body>
            <![CDATA[
                if (event.defaultPrevented)
                    return;

                event.stopPropagation();
                if (action == "default") {
                    let bnum = this.args.defaultButtonNum || 0;
                    this.onButtonClick(bnum);
                } else { // action == "cancel"
                    this.onButtonClick(1); // Cancel button
                }
            ]]>
            </body>
        </method>
    </implementation>

    <handlers>
        <!-- Based on dialog.xml handlers -->
        <handler event="keypress" keycode="VK_RETURN"
                 group="system" action="this.onKeyAction('default', event);"/>
        <handler event="keypress" keycode="VK_ESCAPE"
                 group="system" action="this.onKeyAction('cancel', event);"/>
        <handler event="focus" phase="capturing">
            let bnum = this.args.defaultButtonNum || 0;
            let defaultButton = this.ui["button" + bnum];

            let { AppConstants } =
                Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
            if (AppConstants.platform == "macosx") {
              // On OS X, the default button always stays marked as such (until
              // the entire prompt blurs).
              defaultButton.setAttribute("default", true);
            } else {
              // On other platforms, the default button is only marked as such
              // when no other button has focus. XUL buttons on not-OSX will
              // react to pressing enter as a command, so you can't trigger the
              // default without tabbing to it or something that isn't a button.
              let focusedDefault = (event.originalTarget == defaultButton);
              let someButtonFocused = event.originalTarget instanceof Ci.nsIDOMXULButtonElement;
              defaultButton.setAttribute("default", focusedDefault || !someButtonFocused);
            }
        </handler>
        <handler event="blur">
            // If focus shifted to somewhere else in the browser, don't make
            // the default button look active.
            let bnum = this.args.defaultButtonNum || 0;
            let button = this.ui["button" + bnum];
            button.setAttribute("default", false);
        </handler>
    </handlers>

  </binding>
</bindings>
PK
!<e***chrome/toolkit/content/global/test-ipc.xul<?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"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        width="800" height="800" orient="vertical" onload="initRemoteFrameScript();">
  <script>

    function dumpClientRect(r) {
      dump(r.left + "," + r.top + "," + r.right + "," +
           r.bottom + "," + r.width + "," + r.height + "\n");
    }

    function handleMozAfterPaint(e) {
      return;
      dump(e.type + "\n")
      var rects = e.clientRects;
      var i;
      dump("\tclientRects:\n");
      for (i = 0; i &lt; rects.length; ++i) {
        var r = rects.item(i);
        dump("\t\t");
        dumpClientRect(rects.item(i));
      }

      dump("\tboundingClientRect\n\t\t");
      dumpClientRect(e.boundingClientRect);

      var paintRequests = e.paintRequests;
      dump("\tpaintRequests\n");
      for (i = 0; i &lt; paintRequests.length; ++i) {
        var pr = paintRequests.item(i);
        dump("\t\t");
        dumpClientRect(pr.clientRect);
        if (pr.reason)
          dump("\t\t" + pr.reason + "\n");
      }
    }

    function handleMozScrolledAreaChanged(e) {
      return;
      dump(e.type + "\n");
      dump("\t" + e.x + "," + e.y + "," + e.width + "," + e.height + "\n");
    }

    function restart() {
      var y = document.getElementById('page');
      var p = y.parentNode;
      p.removeChild(y);
      p.appendChild(y);

      var fl = y.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
      fl.activateFrameEvent("MozAfterPaint", true);
      fl.activateFrameEvent("MozScrolledAreaChanged", true);
      y.addEventListener("MozAfterPaint", handleMozAfterPaint, true);
      y.addEventListener("MozScrolledAreaChanged", handleMozScrolledAreaChanged, true);
    }
    
    function loadURL(url) {
      document.getElementById('page').setAttribute('src', url);
    }

    function randomClick() {
       // First focus the remote frame, then dispatch click. This way remote frame gets focus before
       // mouse event.
       document.getElementById('page').focus();
       var frameLoader = document.getElementById('page').QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
       var x = parseInt(Math.random() * 100);
       var y = parseInt(Math.random() * 100);
       frameLoader.sendCrossProcessMouseEvent("mousedown", x, y, 0, 1, 0, false);
       frameLoader.sendCrossProcessMouseEvent("mouseup", x, y, 0, 1, 0, false);
     }

    function keyPress() {
       // First focus the remote frame, then dispatch click. This way remote frame gets focus before
       // mouse event.
       document.getElementById('page').focus();
       var frameLoader = document.getElementById('page').QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;

       var keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_A;
       frameLoader.sendCrossProcessKeyEvent("keydown", keyCode, 0, 0);
       frameLoader.sendCrossProcessKeyEvent("keypress", keyCode, 0, 0);
       frameLoader.sendCrossProcessKeyEvent("keyup", keyCode, 0, 0);
     }

    function openWindow() {
      window.open('chrome://global/content/test-ipc.xul', '_blank', 'chrome,resizable,width=800,height=800');
    }
    
    function closeWindow() {
      window.close();
    }

    function initRemoteFrameScript() {
      // 1. Test that loading a script works, and that accessing process level mm and
      //     global mm works.
      var ppm = Components.classes["@mozilla.org/parentprocessmessagemanager;1"]
                          .getService(Components.interfaces.nsIMessageBroadcaster);
      var gm = Components.classes["@mozilla.org/globalmessagemanager;1"]
                         .getService(Components.interfaces.nsIMessageBroadcaster);
      var cpm = Components.classes["@mozilla.org/childprocessmessagemanager;1"]
                            .getService(Components.interfaces.nsISyncMessageSender);

      if (ppm.childCount != 2) {
        alert("Should have two child processes!");
      }
      var childprocessmm = ppm.getChildAt(1); // 0 is the in-process child process mm
      
      childprocessmm.addMessageListener("ppm-sync",
        function(m) {
          if (m.target != childprocessmm) alert("Wrong target!");
          document.getElementById("messageLog").value += "[SYNC1 PPM]"; 
        }
      );
      
      ppm.addMessageListener("ppm-sync",
        function(m) {
          // Check that global process message manager gets the per-process mm as target.
          if (m.target != childprocessmm) alert("Wrong target!");
          document.getElementById("messageLog").value += "[SYNC2 PPM]"; 
        }
      );
      childprocessmm.addMessageListener("ppm-async",
        function(m) {
          if (m.target != childprocessmm) alert("Wrong target!");
          document.getElementById("messageLog").value += "[ASYNC1 PPM]"; 
        }
      );
      ppm.addMessageListener("ppm-async",
        function(m) {
          // Check that global process message manager gets the per-process mm as target.
          if (m.target != childprocessmm) alert("Wrong target!");
          document.getElementById("messageLog").value += "[ASYNC2 PPM]"; 
        }
      );
      messageManager.loadFrameScript("chrome://global/content/remote-test-ipc.js", true);
      ppm.sendAsyncMessage("cpm-async");

      // 2. Test that adding message listener works, and that receiving a sync message works.
      messageManager.addMessageListener("linkclick",
        function(m) {
          // This checks that json sending works in sync messages. 
          document.getElementById("messageLog").value = m.name + ": " + m.json.href;
          return { message: "linkclick-received" };
        });

      // 3. Test that returning multiple json results works.
      messageManager.addMessageListener("linkclick",
        function(m) {
          return { message: "linkclick-received" };
        });

      // 4. Test that receiving an async message works.
      //    Test also that sending async message to content works.
      //    Test also that { receiveMessage: function(m) {} } works.
      messageManager.addMessageListener("linkclick-reply-object",
        { foobarObjectVar: true,
          receiveMessage: function(m) {
            var s = (m.json.message == "linkclick-received") &amp;&amp;
                    (this.foobarObjectVar) ? "PASS" : "FAIL";
            messageManager.broadcastAsyncMessage("chrome-message", { ok : s } );
          }
        }
        );

      // 5. Final test to check that everything went ok.
      messageManager.addMessageListener("chrome-message-reply",
        function(m) {
          // Check that 'this' and .target values are handled correctly
          if (m.target == document.getElementById("page") &amp;&amp;
              this == messageManager) {
            // Check that the message properties are enumerable.
            var hasName = false;
            var hasSync = false;
            var hasJSON = false;
            for (i in m) {
              if (i == "name") {
                hasName = true;
              } else if (i == "sync") {
                hasSync = true;
              } else if (i == "json") {
                hasJSON = true;
              }
            }
            if (hasName &amp;&amp; hasSync &amp;&amp; hasJSON) {
              document.getElementById("messageLog").value += ", " + m.json.ok;
            }
          }
        });
    }

    var speedTestStartTime = 0;
    var speedTestCount = 0;
    function messageSpeed() {
      speedTestCount = 0;
      messageManager.addMessageListener("speed-test", speedHandler);
      messageManager.broadcastAsyncMessage("speed-test-start");
    }

    function speedHandler() {
      if (!speedTestCount) {
        speedTestStartTime = new Date().getTime();
      }
      if (++speedTestCount == 1000) {
        setTimeout("alert('" + speedTestCount + " in "  + (new Date().getTime() - speedTestStartTime) +  "ms')", 0);
        return { message: "done" };
      }
      return { message: "continue" };
    }

    var addRemoveTestCount = 0;

    function echoListener() {
      if (++addRemoveTestCount == 1) {
        alert("Expected echo message");
        messageManager.removeMessageListener("async-echo", echoListener);
        messageManager.broadcastAsyncMessage("async-echo");
        return;
      }
      alert("Unexpected echo message");
    }

    function listenerAddRemove() {
      addRemoveTestCount = 0;
      messageManager.addMessageListener("async-echo", echoListener);
      messageManager.broadcastAsyncMessage("async-echo");
    }

    var MozAfterPaintCount = 0;
    function enableMozAfterPaint() {
      messageManager.addMessageListener("MozAfterPaint",
        function(m) {
          document.getElementById("messageLog").value = m.name + "[" + (++MozAfterPaintCount) + "]";
        });
      messageManager.loadFrameScript("data:,addEventListener('MozAfterPaint', function(e) { sendAsyncMessage('MozAfterPaint'); },true);", false);
    }

   var Ci = Components.interfaces;
   var Cu = Components.utils;
   Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  </script>

  <toolbar id="controls">
    <toolbarbutton label="Back"/>
    <toolbarbutton label="Forward"/>
    <textbox onchange="loadURL(this.value)" flex="1" id="URL"/>
  </toolbar>
  <toolbar>
    <toolbarbutton onclick="restart()" label="Recover"/>
    <toolbarbutton onclick="randomClick()" label="random click"/>
    <toolbarbutton onclick="keyPress()" label="key press"/>
    <toolbarbutton onclick="messageSpeed()" label="test message handling speed"/>
    <toolbarbutton onclick="listenerAddRemove()" label="test listener add/remove"/>
    <toolbarbutton onclick="enableMozAfterPaint()" label="MozAfterPaint"/>
    <toolbarbutton onclick="openWindow()" label="open new window"/>
    <toolbarbutton onclick="closeWindow()" label="close this window"/>
  </toolbar>
  <toolbar><label value="Load a script (URL) to content process:"/>
    <textbox flex="1" id="script"/><button
      label="send" oncommand="document.getElementById('page')
                                      .QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
                                      .frameLoader.messageManager
                                      .loadFrameScript(this.previousSibling.value, false);"/>
  </toolbar>
  <toolbar>
    <label value="Eval script in chrome context"/>
    <textbox flex="1"/><button label="run" oncommand="eval(this.previousSibling.value);"/>
  </toolbar>

  <browser type="content" src="http://www.google.com/" flex="1" id="page" remote="true"/>
  <label id="messageLog" value="" crop="center"/>
</window>
PK
!<c)chrome/toolkit/content/global/textbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

html|*.textbox-input {
  -moz-appearance: none !important;
  text-align: inherit;
  text-shadow: inherit;
  box-sizing: border-box;
  -moz-box-flex: 1;
}

html|*.textbox-textarea {
  -moz-appearance: none !important;
  text-shadow: inherit;
  box-sizing: border-box;
  -moz-box-flex: 1;
}

/*
html|*.textbox-input::placeholder,
html|*.textbox-textarea::placeholder {
  text-align: left;
  direction: ltr;
}

html|*.textbox-input::placeholder:-moz-locale-dir(rtl),
html|*.textbox-textarea::placeholder:-moz-locale-dir(rtl) {
  text-align: right;
  direction: rtl;
}
*/
PK
!<Y##.chrome/toolkit/content/global/timepicker.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html [
  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
  %htmlDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<head>
  <title>Time Picker</title>
  <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
  <script type="application/javascript" src="chrome://global/content/bindings/timekeeper.js"></script>
  <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
  <script type="application/javascript" src="chrome://global/content/bindings/timepicker.js"></script>
</head>
<body>
  <div id="time-picker"></div>
  <template id="spinner-template">
    <div class="spinner-container">
      <button class="up"/>
      <div class="spinner"></div>
      <button class="down"/>
    </div>
  </template>
  <script type="application/javascript">
  /* import-globals-from widgets/timepicker.js */
  // We need to hide the scroll bar but maintain its scrolling
  // capability, so using |overflow: hidden| is not an option.
  // Instead, we are inserting a user agent stylesheet that is
  // capable of selecting scrollbars, and do |display: none|.
  var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                  getInterface(Components.interfaces.nsIDOMWindowUtils);
  domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET);
  // Create a TimePicker instance and prepare to be
  // initialized by the "TimePickerInit" event from timepicker.xml
  new TimePicker(document.getElementById("time-picker"));
  </script>
</body>
</html>
PK
!<,b*chrome/toolkit/content/global/treeUtils.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/. */

var gTreeUtils = {
  deleteAll(aTree, aView, aItems, aDeletedItems) {
    for (var i = 0; i < aItems.length; ++i)
      aDeletedItems.push(aItems[i]);
    aItems.splice(0, aItems.length);
    var oldCount = aView.rowCount;
    aView._rowCount = 0;
    aTree.treeBoxObject.rowCountChanged(0, -oldCount);
  },

  deleteSelectedItems(aTree, aView, aItems, aDeletedItems) {
    var selection = aTree.view.selection;
    selection.selectEventsSuppressed = true;

    var rc = selection.getRangeCount();
    for (var i = 0; i < rc; ++i) {
      var min = { }; var max = { };
      selection.getRangeAt(i, min, max);
      for (let j = min.value; j <= max.value; ++j) {
        aDeletedItems.push(aItems[j]);
        aItems[j] = null;
      }
    }

    var nextSelection = 0;
    for (i = 0; i < aItems.length; ++i) {
      if (!aItems[i]) {
        let j = i;
        while (j < aItems.length && !aItems[j])
          ++j;
        aItems.splice(i, j - i);
        nextSelection = j < aView.rowCount ? j - 1 : j - 2;
        aView._rowCount -= j - i;
        aTree.treeBoxObject.rowCountChanged(i, i - j);
      }
    }

    if (aItems.length) {
      selection.select(nextSelection);
      aTree.treeBoxObject.ensureRowIsVisible(nextSelection);
      aTree.focus();
    }
    selection.selectEventsSuppressed = false;
  },

  sort(aTree, aView, aDataSet, aColumn, aComparator,
                 aLastSortColumn, aLastSortAscending) {
    var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true;
    if (aDataSet.length == 0)
      return ascending;

    var sortFunction = null;
    if (aComparator) {
      sortFunction = function(a, b) { return aComparator(a[aColumn], b[aColumn]); };
    }
    aDataSet.sort(sortFunction);
    if (!ascending)
      aDataSet.reverse();

    aTree.view.selection.clearSelection();
    aTree.view.selection.select(0);
    aTree.treeBoxObject.invalidate();
    aTree.treeBoxObject.ensureRowIsVisible(0);

    return ascending;
  }
};
PK
!<^△bb;chrome/toolkit/content/global/unifiedcomplete-top-urls.json[
  ["https://www.google.com/", "Google"],
  ["https://www.youtube.com/", "YouTube"],
  ["https://www.facebook.com/", "Facebook"],
  ["https://www.baidu.com/", "百度一下"],
  ["https://www.yahoo.com/", "Yahoo"],
  ["https://www.wikipedia.org/", "Wikipedia"],
  ["http://www.qq.com/", "腾讯首页"],
  ["http://www.sohu.com/", "搜狐"],
  ["https://world.taobao.com/", "淘宝海外全球站"],
  ["https://www.tmall.com/", "天猫tmall.com"],
  ["https://www.live.com/", "Live"],
  ["https://www.amazon.com/", "Amazon.com"],
  ["https://vk.com/", "VK"],
  ["https://twitter.com/", "Twitter"],
  ["https://www.instagram.com/", "Instagram"],
  ["http://360.cn/", "360公司官网"],
  ["http://www.sina.com.cn/", "新浪首页"],
  ["https://www.linkedin.com/", "LinkedIn"],
  ["http://www.jd.com/", "京东(JD.COM)"],
  ["https://www.reddit.com/", "reddit"]
]
PK
!<Q:chrome/toolkit/content/global/url-classifier/unittests.xul<?xml version="1.0"?>
<window id="PROT_unittest"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
        onload="onProtUnittestLoad();"
        title="prot unittests">

<script><![CDATA[
  const Cc = Components.classes;
  const Ci = Components.interfaces;

  function G_Debug(zone, s) {
    var label = document.createElement('label');
    var txt = "[" + zone + "] " + s;
    label.appendChild(document.createTextNode(txt));

    document.documentElement.appendChild(label);
  }
  
  function G_Assert(zone, cond, msg) {
    if (!cond) {
      G_Debug(zone, msg);
      throw msg;
    }
  }

  function ProtectionTableTests() {
    var z = "trtable UNITTEST";

    G_Debug(z, "Starting");  

    var url = "http://www.yahoo.com?foo=bar";
    var url2 = "http://168.188.99.26/.secure/www.ebay.com/";
    var urlTable = Cc['@mozilla.org/url-classifier/table;1?type=url']
                     .createInstance(Ci.nsIUrlClassifierTable);
    urlTable.insert(url, "1");
    urlTable.insert(url2, "1");
    G_Assert(z, urlTable.exists(url), "URL lookups broken");
    G_Assert(z, !urlTable.exists("about:config"), "about:config breaks domlook");
    G_Assert(z, urlTable.exists(url2), "URL lookups broken");
    G_Assert(z, urlTable.exists("http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") == true,
             "URL Canonicalization broken");
    G_Assert(z, urlTable.count == 2, 'urlTable: wrong size');

    var dom1 = "bar.com";
    var dom2 = "amazon.co.uk";
    var dom3 = "127.0.0.1";
    var domainTable = Cc['@mozilla.org/url-classifier/table;1?type=domain']
                        .createInstance(Ci.nsIUrlClassifierTable);
    domainTable.insert(dom1, "1");
    domainTable.insert(dom2, "1");
    domainTable.insert(dom3, "1");
    G_Assert(z, domainTable.exists("http://www.bar.com/?zaz=asdf#url"),
             "Domain lookups broken (single dot)");
    G_Assert(z, domainTable.exists("http://www.amazon.co.uk/?z=af#url"),
             "Domain lookups broken (two dots)");
    G_Assert(z, domainTable.exists("http://127.0.0.1/?z=af#url"),
             "Domain lookups broken (IP)");
    G_Assert(z, domainTable.count == 3, 'domainTable: wrong size');

    var site1 = "google.com/safebrowsing/";
    var site2 = "www.foo.bar/";
    var site3 = "127.0.0.1/";
    var siteTable = Cc['@mozilla.org/url-classifier/table;1?type=site']
                      .createInstance(Ci.nsIUrlClassifierTable);
    siteTable.insert(site1, "1");
    siteTable.insert(site2, "1");
    siteTable.insert(site3, "1");
    G_Assert(z, siteTable.exists("http://www.google.com/safebrowsing/1.php"),
             "Site lookups broken - reducing");
    G_Assert(z, siteTable.exists("http://www.foo.bar/some/random/path"),
             "Site lookups broken - fqdn");
    G_Assert(z, siteTable.exists("http://127.0.0.1/something?hello=1"),
             "Site lookups broken - IP");
    G_Assert(z, !siteTable.exists("http://www.google.com/search/"),
             "Site lookups broken - overreaching");
    G_Assert(z, siteTable.count == 3, 'siteTable: wrong size');

    var url1 = "http://poseidon.marinet.gr/~eleni/eBay/index.php";
    var domainHash = "01844755C8143C4579BB28DD59C23747";
    var enchashTable = Cc['@mozilla.org/url-classifier/table;1?type=enchash']
                         .createInstance(Ci.nsIUrlClassifierTable);
    enchashTable.insert(domainHash, "bGtEQWJuMl9FA3Kl5RiXMpgFU8nDJl9J0hXjUck9+"
                                    + "mMUQwAN6llf0gJeY5DIPPc2f+a8MSBFJN17ANGJ"
                                    + "Zl5oZVsQfSW4i12rlScsx4tweZAE");
    G_Assert(z, enchashTable.exists(url1), 'enchash lookup failed');
    G_Assert(z, !enchashTable.exists(url1 + '/foo'),
             "enchash lookup broken - overreaching");
    G_Assert(z, enchashTable.count == 1, 'enchashTable: wrong size');

    // TODO: test replace
    G_Debug(z, "PASSED");
  }
  
  function ProtectionListManagerTests() {
    var z = "listmanager UNITTEST";
    G_Debug(z, "Starting");

    // test lookup and register
    var listManagerInst = Cc["@mozilla.org/url-classifier/listmanager;1"]
                            .createInstance(Ci.nsIUrlListManager);
    var listName = 'foo-bar-url';
    listManagerInst.registerTable(listName, false);
    listManagerInst.safeInsert(listName, 'test', '1');
    G_Assert(z, listManagerInst.safeExists(listName, 'test'),
                'insert/exist failed');

    // test serialization
    var baseName = (new Date().getTime()) + ".tmp";
    var tempDir = Cc["@mozilla.org/file/directory_service;1"]
                  .getService(Ci.nsIProperties)
                  .get("TmpD", Ci.nsILocalFile);
    tempDir.append(baseName);
    tempDir.createUnique(tempDir.DIRECTORY_TYPE, 0744);

    var listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
                        .getService(Ci.nsIUrlListManager);
    listManager.setAppDir(tempDir);
    
    var data = "";

    var set1Name = "test1-foo-domain";
    data += "[" + set1Name + " 1.2]\n";
    var set1 = {};
    for (var i = 0; i < 10; i++) {
      set1["http://" + i + ".com"] = 1;
      data += "+" + i + ".com\t1\n";
    }

    data += "\n";
    var set2Name = "test2-foo-domain";
    // TODO must have blank line
    data += "\n[" + set2Name + " 1.7]\n";
    var set2 = {};
    for (var i = 0; i < 5; i++) {
      set2["http://" + i + ".com"] = 1;
      data += "+" + i + ".com\t1\n";
    }

    function deserialized(tablesKnown, tablesData) {
      listManager.wrappedJSObject.dataReady(tablesKnown, tablesData);

      var file = tempDir.clone();
      file.append(set1Name + ".sst");
      G_Assert(z, file.exists() && file.isFile() && file.isReadable(), 
               "Failed to write out: " + file.path);
      
      file = tempDir.clone();
      file.append(set2Name + ".sst");
      G_Assert(z, file.exists() && file.isFile() && file.isReadable(), 
               "Failed to write out: " + file.path);
      
      // now try to read them back from disk
      listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
                       .createInstance(Ci.nsIUrlListManager);
      listManager.setAppDir(tempDir);
      var tables = [ set1Name, set2Name ];
      listManager.enableUpdate(set1Name);
      listManager.enableUpdate(set2Name);
      listManager.wrappedJSObject.readDataFiles();
      
      // assert that the values match
      for (var prop in set1) {
        G_Assert(z, 
                 listManager.wrappedJSObject.tablesData[set1Name].exists(prop), 
                 "Couldn't find member " + prop + "of set1 from disk.");
      }
      
      for (var prop in set2) {
        G_Assert(z,
                 listManager.wrappedJSObject.tablesData[set2Name].exists(prop), 
                 "Couldn't find member " + prop + "of set2 from disk.");
      }

      tempDir.remove(true);
      
      G_Debug(z, "PASSED");
    };

    // Use the unwrapped object for the unittest
    listManager.wrappedJSObject.deserialize_(data, deserialized);
  }

  function onProtUnittestLoad() {
    ProtectionTableTests();
    ProtectionListManagerTests();
  }
]]></script>
</window>
PK
!<	N2chrome/toolkit/content/global/viewPartialSource.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/. */

/* import-globals-from viewSource.js */

Components.utils.import("resource://gre/modules/Services.jsm");

function onLoadViewPartialSource() {
  // check the view_source.wrap_long_lines pref
  // and set the menuitem's checked attribute accordingly
  let wrapLongLines = Services.prefs.getBoolPref("view_source.wrap_long_lines");
  document.getElementById("menu_wrapLongLines")
          .setAttribute("checked", wrapLongLines);
  document.getElementById("menu_highlightSyntax")
          .setAttribute("checked",
                        Services.prefs.getBoolPref("view_source.syntax_highlight"));

  let args = window.arguments[0];
  viewSourceChrome.loadViewSourceFromSelection(args.URI, args.drawSelection, args.baseURI);
  window.content.focus();
}
PK
!<3chrome/toolkit/content/global/viewPartialSource.xul<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/viewSource.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>

<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % sourceDTD SYSTEM "chrome://global/locale/viewSource.dtd" >
%sourceDTD;
]>

<window id="viewSource"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="onLoadViewPartialSource();"
        contenttitlesetting="true"
        title="&mainWindow.title;"
        titlemodifier="&mainWindow.titlemodifier;"
        titlepreface=""
        titlemenuseparator ="&mainWindow.titlemodifierseparator;"
        windowtype="navigator:view-source"
        width="500" height="300"
        screenX="10" screenY="10"
        persist="screenX screenY width height sizemode">

  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
  <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
  <script type="application/javascript" src="chrome://global/content/viewSource.js"/>
  <script type="application/javascript" src="chrome://global/content/viewPartialSource.js"/>
  <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
  <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>

  <stringbundle id="viewSourceBundle" src="chrome://global/locale/viewSource.properties"/>

  <command id="cmd_savePage" oncommand="ViewSourceSavePage();"/>
  <command id="cmd_print" oncommand="PrintUtils.printWindow(gBrowser.outerWindowID, gBrowser);"/>
  <command id="cmd_printpreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
  <command id="cmd_pagesetup" oncommand="PrintUtils.showPageSetup();"/>
  <command id="cmd_close" oncommand="window.close();"/>
  <commandset id="editMenuCommands"/>
  <command id="cmd_find"
           oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
  <command id="cmd_findAgain"
           oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
  <command id="cmd_findPrevious"
           oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
  <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
  <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
  <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
  <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
  <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
  <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>

  <keyset id="editMenuKeys"/>
  <keyset id="viewSourceKeys">
    <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
    <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
    <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel" command="cmd_close"/>
    <key keycode="VK_ESCAPE" command="cmd_close"/>

    <key id="key_textZoomEnlarge" key="&textEnlarge.commandkey;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomEnlarge2" key="&textEnlarge.commandkey2;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomEnlarge3" key="&textEnlarge.commandkey3;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomReduce"  key="&textReduce.commandkey;" command="cmd_textZoomReduce" modifiers="accel"/>
    <key id="key_textZoomReduce2"  key="&textReduce.commandkey2;" command="cmd_textZoomReduce" modifiers="accel"/>
    <key id="key_textZoomReset" key="&textReset.commandkey;" command="cmd_textZoomReset" modifiers="accel"/>
    <key id="key_textZoomReset2" key="&textReset.commandkey2;" command="cmd_textZoomReset" modifiers="accel"/>
  </keyset>

  <menupopup id="viewSourceContextMenu">
    <menuitem id="cMenu_findAgain"/>
    <menuseparator/>
    <menuitem id="cMenu_copy"/>
    <menuitem id="context-copyLink"
              label="&copyLinkCmd.label;"
              accesskey="&copyLinkCmd.accesskey;"
              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
    <menuitem id="context-copyEmail"
              label="&copyEmailCmd.label;"
              accesskey="&copyEmailCmd.accesskey;"
              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
    <menuseparator/>
    <menuitem id="cMenu_selectAll"/>
  </menupopup>

  <!-- Menu -->
  <toolbox id="viewSource-toolbox">
    <menubar id="viewSource-main-menubar">

      <menu id="menu_file" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
        <menupopup id="menu_FilePopup">
          <menuitem key="key_savePage" command="cmd_savePage" id="menu_savePage"
                    label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
          <menuitem command="cmd_pagesetup" id="menu_pageSetup"
                    label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
          <menuitem command="cmd_printpreview" id="menu_printPreview"
                    label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
          <menuitem key="key_print" command="cmd_print" id="menu_print"
                    label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
          <menuseparator/>
          <menuitem key="key_close" command="cmd_close" id="menu_close"
                    label="&closeCmd.label;" accesskey="&closeCmd.accesskey;"/>
        </menupopup>
      </menu>

      <menu id="menu_edit">
        <menupopup id="editmenu-popup">
          <menuitem id="menu_undo"/>
          <menuitem id="menu_redo"/>
          <menuseparator/>
          <menuitem id="menu_cut"/>
          <menuitem id="menu_copy"/>
          <menuitem id="menu_paste"/>
          <menuitem id="menu_delete"/>
          <menuseparator/>
          <menuitem id="menu_selectAll"/>
          <menuseparator/>
          <menuitem id="menu_find"/>
          <menuitem id="menu_findAgain"/>
        </menupopup>
      </menu>

      <menu id="menu_view" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
        <menupopup id="viewmenu-popup">
          <menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
            <menupopup>
              <menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
                        label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
                        key="key_textZoomEnlarge"/>
              <menuitem id="menu_textReduce" command="cmd_textZoomReduce"
                        label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
                        key="key_textZoomReduce"/>
              <menuseparator/>
              <menuitem id="menu_textReset" command="cmd_textZoomReset"
                        label="&menu_textReset.label;" accesskey="&menu_textReset.accesskey;"
                        key="key_textZoomReset"/>
            </menupopup>
          </menu>
          <menuseparator/>
          <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
                    label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
          <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
                    label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
        </menupopup>
      </menu>
    </menubar>
  </toolbox>

  <vbox id="appcontent" flex="1">
    <browser id="content" type="content" primary="true" name="content" src="about:blank" flex="1"
             disablehistory="true" context="viewSourceContextMenu" />
    <findbar id="FindToolbar" browserid="content"/>
  </vbox>

</window>
PK
!<%~%~3chrome/toolkit/content/global/viewSource-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

var { utils: Cu, interfaces: Ci, classes: Cc } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
  "resource://gre/modules/DeferredTask.jsm");

const NS_XHTML = "http://www.w3.org/1999/xhtml";
const BUNDLE_URL = "chrome://global/locale/viewSource.properties";

// These are markers used to delimit the selection during processing. They
// are removed from the final rendering.
// We use noncharacter Unicode codepoints to minimize the risk of clashing
// with anything that might legitimately be present in the document.
// U+FDD0..FDEF <noncharacters>
const MARK_SELECTION_START = "\uFDD0";
const MARK_SELECTION_END = "\uFDEF";

var global = this;

/**
 * ViewSourceContent should be loaded in the <xul:browser> of the
 * view source window, and initialized as soon as it has loaded.
 */
var ViewSourceContent = {
  /**
   * We'll act as an nsISelectionListener as well so that we can send
   * updates to the view source window's status bar.
   */
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISelectionListener]),

  /**
   * These are the messages that ViewSourceContent is prepared to listen
   * for. If you need ViewSourceContent to handle more messages, add them
   * here.
   */
  messages: [
    "ViewSource:LoadSource",
    "ViewSource:LoadSourceDeprecated",
    "ViewSource:LoadSourceWithSelection",
    "ViewSource:GoToLine",
    "ViewSource:ToggleWrapping",
    "ViewSource:ToggleSyntaxHighlighting",
    "ViewSource:SetCharacterSet",
  ],

  /**
   * When showing selection source, chrome will construct a page fragment to
   * show, and then instruct content to draw a selection after load.  This is
   * set true when there is a pending request to draw selection.
   */
  needsDrawSelection: false,

  /**
   * ViewSourceContent is attached as an nsISelectionListener on pageshow,
   * and removed on pagehide. When the initial about:blank is transitioned
   * away from, a pagehide is fired without us having attached ourselves
   * first. We use this boolean to keep track of whether or not we're
   * attached, so we don't attempt to remove our listener when it's not
   * yet there (which throws).
   */
  selectionListenerAttached: false,

  get isViewSource() {
    let uri = content.document.documentURI;
    return uri.startsWith("view-source:") ||
           (uri.startsWith("data:") && uri.includes("MathML"));
  },

  get isAboutBlank() {
    let uri = content.document.documentURI;
    return uri == "about:blank";
  },

  /**
   * This should be called as soon as this frame script has loaded.
   */
  init() {
    this.messages.forEach((msgName) => {
      addMessageListener(msgName, this);
    });

    addEventListener("pagehide", this, true);
    addEventListener("pageshow", this, true);
    addEventListener("click", this);
    addEventListener("unload", this);
    Services.els.addSystemEventListener(global, "contextmenu", this, false);
  },

  /**
   * This should be called when the frame script is being unloaded,
   * and the browser is tearing down.
   */
  uninit() {
    this.messages.forEach((msgName) => {
      removeMessageListener(msgName, this);
    });

    removeEventListener("pagehide", this, true);
    removeEventListener("pageshow", this, true);
    removeEventListener("click", this);
    removeEventListener("unload", this);

    Services.els.removeSystemEventListener(global, "contextmenu", this, false);

    // Cancel any pending toolbar updates.
    if (this.updateStatusTask) {
      this.updateStatusTask.disarm();
    }
  },

  /**
   * Anything added to the messages array will get handled here, and should
   * get dispatched to a specific function for the message name.
   */
  receiveMessage(msg) {
    if (!this.isViewSource && !this.isAboutBlank) {
      return;
    }
    let data = msg.data;
    let objects = msg.objects;
    switch (msg.name) {
      case "ViewSource:LoadSource":
        this.viewSource(data.URL, data.outerWindowID, data.lineNumber,
                        data.shouldWrap);
        break;
      case "ViewSource:LoadSourceDeprecated":
        this.viewSourceDeprecated(data.URL, objects.pageDescriptor, data.lineNumber,
                                  data.forcedCharSet);
        break;
      case "ViewSource:LoadSourceWithSelection":
        this.viewSourceWithSelection(data.URL, data.drawSelection, data.baseURI);
        break;
      case "ViewSource:GoToLine":
        this.goToLine(data.lineNumber);
        break;
      case "ViewSource:ToggleWrapping":
        this.toggleWrapping();
        break;
      case "ViewSource:ToggleSyntaxHighlighting":
        this.toggleSyntaxHighlighting();
        break;
      case "ViewSource:SetCharacterSet":
        this.setCharacterSet(data.charset, data.doPageLoad);
        break;
    }
  },

  /**
   * Any events should get handled here, and should get dispatched to
   * a specific function for the event type.
   */
  handleEvent(event) {
    if (!this.isViewSource) {
      return;
    }
    switch (event.type) {
      case "pagehide":
        this.onPageHide(event);
        break;
      case "pageshow":
        this.onPageShow(event);
        break;
      case "click":
        this.onClick(event);
        break;
      case "unload":
        this.uninit();
        break;
      case "contextmenu":
        this.onContextMenu(event);
        break;
    }
  },

  /**
   * A getter for the view source string bundle.
   */
  get bundle() {
    delete this.bundle;
    this.bundle = Services.strings.createBundle(BUNDLE_URL);
    return this.bundle;
  },

  /**
   * A shortcut to the nsISelectionController for the content.
   */
  get selectionController() {
    return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsISelectionDisplay)
                   .QueryInterface(Ci.nsISelectionController);
  },

  /**
   * A shortcut to the nsIWebBrowserFind for the content.
   */
  get webBrowserFind() {
    return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIWebBrowserFind);
  },

  /**
   * Called when the parent sends a message to view some source code.
   *
   * @param URL (required)
   *        The URL string of the source to be shown.
   * @param outerWindowID (optional)
   *        The outerWindowID of the content window that has hosted
   *        the document, in case we want to retrieve it from the network
   *        cache.
   * @param lineNumber (optional)
   *        The line number to focus as soon as the source has finished
   *        loading.
   */
  viewSource(URL, outerWindowID, lineNumber) {
    let pageDescriptor, forcedCharSet;

    if (outerWindowID) {
      let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
      let requestor = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);

      try {
        let otherWebNav = requestor.getInterface(Ci.nsIWebNavigation);
        pageDescriptor = otherWebNav.QueryInterface(Ci.nsIWebPageDescriptor)
                                    .currentDescriptor;
      } catch (e) {
        // We couldn't get the page descriptor, so we'll probably end up re-retrieving
        // this document off of the network.
      }

      let utils = requestor.getInterface(Ci.nsIDOMWindowUtils);
      let doc = contentWindow.document;
      forcedCharSet = utils.docCharsetIsForced ? doc.characterSet
                                               : null;
    }

    this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
  },

  /**
   * Called when the parent is using the deprecated API for viewSource.xul.
   * This function will throw if it's called on a remote browser.
   *
   * @param URL (required)
   *        The URL string of the source to be shown.
   * @param pageDescriptor (optional)
   *        The currentDescriptor off of an nsIWebPageDescriptor, in the
   *        event that the caller wants to try to load the source out of
   *        the network cache.
   * @param lineNumber (optional)
   *        The line number to focus as soon as the source has finished
   *        loading.
   * @param forcedCharSet (optional)
   *        The document character set to use instead of the default one.
   */
  viewSourceDeprecated(URL, pageDescriptor, lineNumber, forcedCharSet) {
    // This should not be called if this frame script is running
    // in a content process!
    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
      throw new Error("ViewSource deprecated API should not be used with " +
                      "remote browsers.");
    }

    this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
  },

  /**
   * Common utility function used by both the current and deprecated APIs
   * for loading source.
   *
   * @param URL (required)
   *        The URL string of the source to be shown.
   * @param pageDescriptor (optional)
   *        The currentDescriptor off of an nsIWebPageDescriptor, in the
   *        event that the caller wants to try to load the source out of
   *        the network cache.
   * @param lineNumber (optional)
   *        The line number to focus as soon as the source has finished
   *        loading.
   * @param forcedCharSet (optional)
   *        The document character set to use instead of the default one.
   */
  loadSource(URL, pageDescriptor, lineNumber, forcedCharSet) {
    const viewSrcURL = "view-source:" + URL;

    if (forcedCharSet) {
      try {
        docShell.charset = forcedCharSet;
      } catch (e) { /* invalid charset */ }
    }

    if (lineNumber && lineNumber > 0) {
      let doneLoading = (event) => {
        // Ignore possible initial load of about:blank
        if (this.isAboutBlank ||
            !content.document.body) {
          return;
        }
        this.goToLine(lineNumber);
        removeEventListener("pageshow", doneLoading);
      };

      addEventListener("pageshow", doneLoading);
    }

    if (!pageDescriptor) {
      this.loadSourceFromURL(viewSrcURL);
      return;
    }

    try {
      let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor);
      pageLoader.loadPage(pageDescriptor,
                          Ci.nsIWebPageDescriptor.DISPLAY_AS_SOURCE);
    } catch (e) {
      // We were not able to load the source from the network cache.
      this.loadSourceFromURL(viewSrcURL);
      return;
    }

    let shEntrySource = pageDescriptor.QueryInterface(Ci.nsISHEntry);
    let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"]
                    .createInstance(Ci.nsISHEntry);
    shEntry.setURI(Services.io.newURI(viewSrcURL));
    shEntry.setTitle(viewSrcURL);
    let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
    shEntry.triggeringPrincipal = systemPrincipal;
    shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
    shEntry.cacheKey = shEntrySource.cacheKey;
    docShell.QueryInterface(Ci.nsIWebNavigation)
            .sessionHistory
            .QueryInterface(Ci.nsISHistoryInternal)
            .addEntry(shEntry, true);
  },

  /**
   * Load some URL in the browser.
   *
   * @param URL
   *        The URL string to load.
   */
  loadSourceFromURL(URL) {
    let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    webNav.loadURI(URL, loadFlags, null, null, null);
  },

  /**
   * This handler is for click events from:
   *   * error page content, which can show up if the user attempts to view the
   *     source of an attack page.
   *   * in-page context menu actions
   */
  onClick(event) {
    let target = event.originalTarget;
    // Check for content menu actions
    if (target.id) {
      this.contextMenuItems.forEach(itemSpec => {
        if (itemSpec.id !== target.id) {
          return;
        }
        itemSpec.handler.call(this, event);
        event.stopPropagation();
      });
    }

    // Don't trust synthetic events
    if (!event.isTrusted || event.target.localName != "button")
      return;

    let errorDoc = target.ownerDocument;

    if (/^about:blocked/.test(errorDoc.documentURI)) {
      // The event came from a button on a malware/phishing block page

      if (target == errorDoc.getElementById("getMeOutButton")) {
        // Instead of loading some safe page, just close the window
        sendAsyncMessage("ViewSource:Close");
      } else if (target == errorDoc.getElementById("reportButton")) {
        // This is the "Why is this site blocked" button. We redirect
        // to the generic page describing phishing/malware protection.
        let URL = Services.urlFormatter.formatURLPref("app.support.baseURL");
        sendAsyncMessage("ViewSource:OpenURL", { URL })
      } else if (target == errorDoc.getElementById("ignoreWarningButton")) {
        // Allow users to override and continue through to the site
        docShell.QueryInterface(Ci.nsIWebNavigation)
                .loadURIWithOptions(content.location.href,
                                    Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                                    null, Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
                                    null, null, null);
      }
    }
  },

  /**
   * Handler for the pageshow event.
   *
   * @param event
   *        The pageshow event being handled.
   */
  onPageShow(event) {
    let selection = content.getSelection();
    if (selection) {
      selection.QueryInterface(Ci.nsISelectionPrivate)
               .addSelectionListener(this);
      this.selectionListenerAttached = true;
    }
    content.focus();

    // If we need to draw the selection, wait until an actual view source page
    // has loaded, instead of about:blank.
    if (this.needsDrawSelection &&
        content.document.documentURI.startsWith("view-source:")) {
      this.needsDrawSelection = false;
      this.drawSelection();
    }

    if (content.document.body) {
      this.injectContextMenu();
    }

    sendAsyncMessage("ViewSource:SourceLoaded");
  },

  /**
   * Handler for the pagehide event.
   *
   * @param event
   *        The pagehide event being handled.
   */
  onPageHide(event) {
    // The initial about:blank will fire pagehide before we
    // ever set a selectionListener, so we have a boolean around
    // to keep track of when the listener is attached.
    if (this.selectionListenerAttached) {
      content.getSelection()
             .QueryInterface(Ci.nsISelectionPrivate)
             .removeSelectionListener(this);
      this.selectionListenerAttached = false;
    }
    sendAsyncMessage("ViewSource:SourceUnloaded");
  },

  onContextMenu(event) {
    let addonInfo = {};
    let subject = {
      event,
      addonInfo,
    };

    subject.wrappedJSObject = subject;
    Services.obs.notifyObservers(subject, "content-contextmenu");

    let node = event.target;

    let result = {
      isEmail: false,
      isLink: false,
      href: "",
      // We have to pass these in the event that we're running in
      // a remote browser, so that ViewSourceChrome knows where to
      // open the context menu.
      screenX: event.screenX,
      screenY: event.screenY,
    };

    if (node && node.localName == "a") {
      result.isLink = node.href.startsWith("view-source:");
      result.isEmail = node.href.startsWith("mailto:");
      result.href = node.href.substring(node.href.indexOf(":") + 1);
    }

    sendSyncMessage("ViewSource:ContextMenuOpening", result);
  },

  /**
   * Attempts to go to a particular line in the source code being
   * shown. If it succeeds in finding the line, it will fire a
   * "ViewSource:GoToLine:Success" message, passing up an object
   * with the lineNumber we just went to. If it cannot find the line,
   * it will fire a "ViewSource:GoToLine:Failed" message.
   *
   * @param lineNumber
   *        The line number to attempt to go to.
   */
  goToLine(lineNumber) {
    let body = content.document.body;

    // The source document is made up of a number of pre elements with
    // id attributes in the format <pre id="line123">, meaning that
    // the first line in the pre element is number 123.
    // Do binary search to find the pre element containing the line.
    // However, in the plain text case, we have only one pre without an
    // attribute, so assume it begins on line 1.
    let pre;
    for (let lbound = 0, ubound = body.childNodes.length; ; ) {
      let middle = (lbound + ubound) >> 1;
      pre = body.childNodes[middle];

      let firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1;

      if (lbound == ubound - 1) {
        break;
      }

      if (lineNumber >= firstLine) {
        lbound = middle;
      } else {
        ubound = middle;
      }
    }

    let result = {};
    let found = this.findLocation(pre, lineNumber, null, -1, false, result);

    if (!found) {
      sendAsyncMessage("ViewSource:GoToLine:Failed");
      return;
    }

    let selection = content.getSelection();
    selection.removeAllRanges();

    // In our case, the range's startOffset is after "\n" on the previous line.
    // Tune the selection at the beginning of the next line and do some tweaking
    // to position the focusNode and the caret at the beginning of the line.
    selection.QueryInterface(Ci.nsISelectionPrivate)
      .interlinePosition = true;

    selection.addRange(result.range);

    if (!selection.isCollapsed) {
      selection.collapseToEnd();

      let offset = result.range.startOffset;
      let node = result.range.startContainer;
      if (offset < node.data.length) {
        // The same text node spans across the "\n", just focus where we were.
        selection.extend(node, offset);
      } else {
        // There is another tag just after the "\n", hook there. We need
        // to focus a safe point because there are edgy cases such as
        // <span>...\n</span><span>...</span> vs.
        // <span>...\n<span>...</span></span><span>...</span>
        node = node.nextSibling ? node.nextSibling : node.parentNode.nextSibling;
        selection.extend(node, 0);
      }
    }

    let selCon = this.selectionController;
    selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
    selCon.setCaretVisibilityDuringSelection(true);

    // Scroll the beginning of the line into view.
    selCon.scrollSelectionIntoView(
      Ci.nsISelectionController.SELECTION_NORMAL,
      Ci.nsISelectionController.SELECTION_FOCUS_REGION,
      true);

    sendAsyncMessage("ViewSource:GoToLine:Success", { lineNumber });
  },


  /**
   * Some old code from the original view source implementation. Original
   * documentation follows:
   *
   * "Loops through the text lines in the pre element. The arguments are either
   *  (pre, line) or (node, offset, interlinePosition). result is an out
   *  argument. If (pre, line) are specified (and node == null), result.range is
   *  a range spanning the specified line. If the (node, offset,
   *  interlinePosition) are specified, result.line and result.col are the line
   *  and column number of the specified offset in the specified node relative to
   *  the whole file."
   */
  findLocation(pre, lineNumber, node, offset, interlinePosition, result) {
    if (node && !pre) {
      // Look upwards to find the current pre element.
      for (pre = node;
           pre.nodeName != "PRE";
           pre = pre.parentNode);
    }

    // The source document is made up of a number of pre elements with
    // id attributes in the format <pre id="line123">, meaning that
    // the first line in the pre element is number 123.
    // However, in the plain text case, there is only one <pre> without an id,
    // so assume line 1.
    let curLine = pre.id ? parseInt(pre.id.substring(4)) : 1;

    // Walk through each of the text nodes and count newlines.
    let treewalker = content.document
        .createTreeWalker(pre, Ci.nsIDOMNodeFilter.SHOW_TEXT, null);

    // The column number of the first character in the current text node.
    let firstCol = 1;

    let found = false;
    for (let textNode = treewalker.firstChild();
         textNode && !found;
         textNode = treewalker.nextNode()) {

      // \r is not a valid character in the DOM, so we only check for \n.
      let lineArray = textNode.data.split(/\n/);
      let lastLineInNode = curLine + lineArray.length - 1;

      // Check if we can skip the text node without further inspection.
      if (node ? (textNode != node) : (lastLineInNode < lineNumber)) {
        if (lineArray.length > 1) {
          firstCol = 1;
        }
        firstCol += lineArray[lineArray.length - 1].length;
        curLine = lastLineInNode;
        continue;
      }

      // curPos is the offset within the current text node of the first
      // character in the current line.
      for (var i = 0, curPos = 0;
           i < lineArray.length;
           curPos += lineArray[i++].length + 1) {

        if (i > 0) {
          curLine++;
        }

        if (node) {
          if (offset >= curPos && offset <= curPos + lineArray[i].length) {
            // If we are right after the \n of a line and interlinePosition is
            // false, the caret looks as if it were at the end of the previous
            // line, so we display that line and column instead.

            if (i > 0 && offset == curPos && !interlinePosition) {
              result.line = curLine - 1;
              var prevPos = curPos - lineArray[i - 1].length;
              result.col = (i == 1 ? firstCol : 1) + offset - prevPos;
            } else {
              result.line = curLine;
              result.col = (i == 0 ? firstCol : 1) + offset - curPos;
            }
            found = true;

            break;
          }

        } else if (curLine == lineNumber && !("range" in result)) {
          result.range = content.document.createRange();
          result.range.setStart(textNode, curPos);

          // This will always be overridden later, except when we look for
          // the very last line in the file (this is the only line that does
          // not end with \n).
          result.range.setEndAfter(pre.lastChild);

        } else if (curLine == lineNumber + 1) {
          result.range.setEnd(textNode, curPos - 1);
          found = true;
          break;
        }
      }
    }

    return found || ("range" in result);
  },

  /**
   * Toggles the "wrap" class on the document body, which sets whether
   * or not long lines are wrapped.  Notifies parent to update the pref.
   */
  toggleWrapping() {
    let body = content.document.body;
    let state = body.classList.toggle("wrap");
    sendAsyncMessage("ViewSource:StoreWrapping", { state });
  },

  /**
   * Toggles the "highlight" class on the document body, which sets whether
   * or not syntax highlighting is displayed.  Notifies parent to update the
   * pref.
   */
  toggleSyntaxHighlighting() {
    let body = content.document.body;
    let state = body.classList.toggle("highlight");
    sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
  },

  /**
   * Called when the parent has changed the character set to view the
   * source with.
   *
   * @param charset
   *        The character set to use.
   * @param doPageLoad
   *        Whether or not we should reload the page ourselves with the
   *        nsIWebPageDescriptor. Part of a workaround for bug 136322.
   */
  setCharacterSet(charset, doPageLoad) {
    docShell.charset = charset;
    if (doPageLoad) {
      this.reload();
    }
  },

  /**
   * Reloads the content.
   */
  reload() {
    let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor);
    try {
      pageLoader.loadPage(pageLoader.currentDescriptor,
                          Ci.nsIWebPageDescriptor.DISPLAY_NORMAL);
    } catch (e) {
      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
      webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
    }
  },

  /**
   * A reference to a DeferredTask that is armed every time the
   * selection changes.
   */
  updateStatusTask: null,

  /**
   * Called once the DeferredTask fires. Sends a message up to the
   * parent to update the status bar text.
   */
  updateStatus() {
    let selection = content.getSelection();

    if (!selection.focusNode) {
      sendAsyncMessage("ViewSource:UpdateStatus", { label: "" });
      return;
    }
    if (selection.focusNode.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
      return;
    }

    let selCon = this.selectionController;
    selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
    selCon.setCaretVisibilityDuringSelection(true);

    let interlinePosition = selection.QueryInterface(Ci.nsISelectionPrivate)
                                     .interlinePosition;

    let result = {};
    this.findLocation(null, -1,
        selection.focusNode, selection.focusOffset, interlinePosition, result);

    let label = this.bundle.formatStringFromName("statusBarLineCol",
                                                 [result.line, result.col], 2);
    sendAsyncMessage("ViewSource:UpdateStatus", { label });
  },

  /**
   * Loads a view source selection showing the given view-source url and
   * highlight the selection.
   *
   * @param uri view-source uri to show
   * @param drawSelection true to highlight the selection
   * @param baseURI base URI of the original document
   */
  viewSourceWithSelection(uri, drawSelection, baseURI) {
    this.needsDrawSelection = drawSelection;

    // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
    let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
    let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET;
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    webNav.loadURIWithOptions(uri, loadFlags,
                              null, referrerPolicy,  // referrer
                              null, null,  // postData, headers
                              Services.io.newURI(baseURI));
  },

  /**
   * nsISelectionListener
   */

  /**
   * Gets called every time the selection is changed. Coalesces frequent
   * changes, and calls updateStatus after 100ms of no selection change
   * activity.
   */
  notifySelectionChanged(doc, sel, reason) {
    if (!this.updateStatusTask) {
      this.updateStatusTask = new DeferredTask(() => {
        this.updateStatus();
      }, 100);
    }

    this.updateStatusTask.arm();
  },

  /**
   * Using special markers left in the serialized source, this helper makes the
   * underlying markup of the selected fragment to automatically appear as
   * selected on the inflated view-source DOM.
   */
  drawSelection() {
    content.document.title =
      this.bundle.GetStringFromName("viewSelectionSourceTitle");

    // find the special selection markers that we added earlier, and
    // draw the selection between the two...
    var findService = null;
    try {
      // get the find service which stores the global find state
      findService = Cc["@mozilla.org/find/find_service;1"]
                    .getService(Ci.nsIFindService);
    } catch (e) { }
    if (!findService)
      return;

    // cache the current global find state
    var matchCase     = findService.matchCase;
    var entireWord    = findService.entireWord;
    var wrapFind      = findService.wrapFind;
    var findBackwards = findService.findBackwards;
    var searchString  = findService.searchString;
    var replaceString = findService.replaceString;

    // setup our find instance
    var findInst = this.webBrowserFind;
    findInst.matchCase = true;
    findInst.entireWord = false;
    findInst.wrapFind = true;
    findInst.findBackwards = false;

    // ...lookup the start mark
    findInst.searchString = MARK_SELECTION_START;
    var startLength = MARK_SELECTION_START.length;
    findInst.findNext();

    var selection = content.getSelection();
    if (!selection.rangeCount)
      return;

    var range = selection.getRangeAt(0);

    var startContainer = range.startContainer;
    var startOffset = range.startOffset;

    // ...lookup the end mark
    findInst.searchString = MARK_SELECTION_END;
    var endLength = MARK_SELECTION_END.length;
    findInst.findNext();

    var endContainer = selection.anchorNode;
    var endOffset = selection.anchorOffset;

    // reset the selection that find has left
    selection.removeAllRanges();

    // delete the special markers now...
    endContainer.deleteData(endOffset, endLength);
    startContainer.deleteData(startOffset, startLength);
    if (startContainer == endContainer)
      endOffset -= startLength; // has shrunk if on same text node...
    range.setEnd(endContainer, endOffset);

    // show the selection and scroll it into view
    selection.addRange(range);
    // the default behavior of the selection is to scroll at the end of
    // the selection, whereas in this situation, it is more user-friendly
    // to scroll at the beginning. So we override the default behavior here
    try {
      this.selectionController.scrollSelectionIntoView(
                                 Ci.nsISelectionController.SELECTION_NORMAL,
                                 Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
                                 true);
    } catch (e) { }

    // restore the current find state
    findService.matchCase     = matchCase;
    findService.entireWord    = entireWord;
    findService.wrapFind      = wrapFind;
    findService.findBackwards = findBackwards;
    findService.searchString  = searchString;
    findService.replaceString = replaceString;

    findInst.matchCase     = matchCase;
    findInst.entireWord    = entireWord;
    findInst.wrapFind      = wrapFind;
    findInst.findBackwards = findBackwards;
    findInst.searchString  = searchString;
  },

  /**
   * In-page context menu items that are injected after page load.
   */
  contextMenuItems: [
    {
      id: "goToLine",
      accesskey: true,
      handler() {
        sendAsyncMessage("ViewSource:PromptAndGoToLine");
      }
    },
    {
      id: "wrapLongLines",
      get checked() {
        return Services.prefs.getBoolPref("view_source.wrap_long_lines");
      },
      handler() {
        this.toggleWrapping();
      }
    },
    {
      id: "highlightSyntax",
      get checked() {
        return Services.prefs.getBoolPref("view_source.syntax_highlight");
      },
      handler() {
        this.toggleSyntaxHighlighting();
      }
    },
  ],

  /**
   * Add context menu items for view source specific actions.
   */
  injectContextMenu() {
    let doc = content.document;

    let menu = doc.createElementNS(NS_XHTML, "menu");
    menu.setAttribute("type", "context");
    menu.setAttribute("id", "actions");
    doc.body.appendChild(menu);
    doc.body.setAttribute("contextmenu", "actions");

    this.contextMenuItems.forEach(itemSpec => {
      let item = doc.createElementNS(NS_XHTML, "menuitem");
      item.setAttribute("id", itemSpec.id);
      let labelName = `context_${itemSpec.id}_label`;
      let label = this.bundle.GetStringFromName(labelName);
      item.setAttribute("label", label);
      if ("checked" in itemSpec) {
        item.setAttribute("type", "checkbox");
      }
      if (itemSpec.accesskey) {
        let accesskeyName = `context_${itemSpec.id}_accesskey`;
        item.setAttribute("accesskey",
                          this.bundle.GetStringFromName(accesskeyName))
      }
      menu.appendChild(item);
    });

    this.updateContextMenu();
  },

  /**
   * Update state of checkbox-style context menu items.
   */
  updateContextMenu() {
    let doc = content.document;
    this.contextMenuItems.forEach(itemSpec => {
      if (!("checked" in itemSpec)) {
        return;
      }
      let item = doc.getElementById(itemSpec.id);
      if (itemSpec.checked) {
        item.setAttribute("checked", true);
      } else {
        item.removeAttribute("checked");
      }
    });
  },
};
ViewSourceContent.init();
PK
!<LL,chrome/toolkit/content/global/viewSource.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

toolbar[printpreview="true"] {
  -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
}
PK
!<Vqrr+chrome/toolkit/content/global/viewSource.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* import-globals-from ../../../content/globalOverlay.js */
/* import-globals-from ../../printing/content/printUtils.js */
/* import-globals-from ../../../content/viewZoomOverlay.js */
/* import-globals-from ../../../content/contentAreaUtils.js */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var { utils: Cu, interfaces: Ci, classes: Cc } = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ViewSourceBrowser.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
  "resource://gre/modules/CharsetMenu.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");

/* global gBrowser, gViewSourceBundle, gContextMenu */
[
  ["gBrowser",          "content"],
  ["gViewSourceBundle", "viewSourceBundle"],
  ["gContextMenu",      "viewSourceContextMenu"]
].forEach(function([name, id]) {
  Object.defineProperty(window, name, {
    configurable: true,
    enumerable: true,
    get() {
      var element = document.getElementById(id);
      if (!element)
        return null;
      delete window[name];
      return window[name] = element;
    },
  });
});

/**
 * ViewSourceChrome is the primary interface for interacting with
 * the view source browser from a self-contained window.  It extends
 * ViewSourceBrowser with additional things needed inside the special window.
 *
 * It initializes itself on script load.
 */
function ViewSourceChrome() {
  ViewSourceBrowser.call(this);
}

ViewSourceChrome.prototype = {
  __proto__: ViewSourceBrowser.prototype,

  /**
   * The <browser> that will be displaying the view source content.
   */
  get browser() {
    return gBrowser;
  },

  /**
   * The context menu, when opened from the content process, sends
   * up a chunk of serialized data describing the items that the
   * context menu is being opened on. This allows us to avoid using
   * CPOWs.
   */
  contextMenuData: {},

  /**
   * These are the messages that ViewSourceChrome will listen for
   * from the frame script it injects. Any message names added here
   * will automatically have ViewSourceChrome listen for those messages,
   * and remove the listeners on teardown.
   */
  messages: ViewSourceBrowser.prototype.messages.concat([
    "ViewSource:SourceLoaded",
    "ViewSource:SourceUnloaded",
    "ViewSource:Close",
    "ViewSource:OpenURL",
    "ViewSource:UpdateStatus",
    "ViewSource:ContextMenuOpening",
  ]),

  /**
   * This called via ViewSourceBrowser's constructor.  This should be called as
   * soon as the script loads.  When this function executes, we can assume the
   * DOM content has not yet loaded.
   */
  init() {
    this.mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);

    this.shouldWrap = Services.prefs.getBoolPref("view_source.wrap_long_lines");
    this.shouldHighlight =
      Services.prefs.getBoolPref("view_source.syntax_highlight");

    addEventListener("load", this);
    addEventListener("unload", this);
    addEventListener("AppCommand", this, true);
    addEventListener("MozSwipeGesture", this, true);

    ViewSourceBrowser.prototype.init.call(this);
  },

  /**
   * This should be called when the window is closing. This function should
   * clean up event and message listeners.
   */
  uninit() {
    ViewSourceBrowser.prototype.uninit.call(this);

    // "load" event listener is removed in its handler, to
    // ensure we only fire it once.
    removeEventListener("unload", this);
    removeEventListener("AppCommand", this, true);
    removeEventListener("MozSwipeGesture", this, true);
    gContextMenu.removeEventListener("popupshowing", this);
    gContextMenu.removeEventListener("popuphidden", this);
    Services.els.removeSystemEventListener(this.browser, "dragover", this,
                                           true);
    Services.els.removeSystemEventListener(this.browser, "drop", this, true);
  },

  /**
   * Anything added to the messages array will get handled here, and should
   * get dispatched to a specific function for the message name.
   */
  receiveMessage(message) {
    let data = message.data;

    switch (message.name) {
      // Begin messages from super class
      case "ViewSource:PromptAndGoToLine":
        this.promptAndGoToLine();
        break;
      case "ViewSource:GoToLine:Success":
        this.onGoToLineSuccess(data.lineNumber);
        break;
      case "ViewSource:GoToLine:Failed":
        this.onGoToLineFailed();
        break;
      case "ViewSource:StoreWrapping":
        this.storeWrapping(data.state);
        break;
      case "ViewSource:StoreSyntaxHighlighting":
        this.storeSyntaxHighlighting(data.state);
        break;
      // End messages from super class
      case "ViewSource:SourceLoaded":
        this.onSourceLoaded();
        break;
      case "ViewSource:SourceUnloaded":
        this.onSourceUnloaded();
        break;
      case "ViewSource:Close":
        this.close();
        break;
      case "ViewSource:OpenURL":
        this.openURL(data.URL);
        break;
      case "ViewSource:UpdateStatus":
        this.updateStatus(data.label);
        break;
      case "ViewSource:ContextMenuOpening":
        this.onContextMenuOpening(data.isLink, data.isEmail, data.href);
        if (this.browser.isRemoteBrowser) {
          this.openContextMenu(data.screenX, data.screenY);
        }
        break;
    }
  },

  /**
   * Any events should get handled here, and should get dispatched to
   * a specific function for the event type.
   */
  handleEvent(event) {
    switch (event.type) {
      case "unload":
        this.uninit();
        break;
      case "load":
        this.onXULLoaded();
        break;
      case "AppCommand":
        this.onAppCommand(event);
        break;
      case "MozSwipeGesture":
        this.onSwipeGesture(event);
        break;
      case "popupshowing":
        this.onContextMenuShowing(event);
        break;
      case "popuphidden":
        this.onContextMenuHidden(event);
        break;
      case "dragover":
        this.onDragOver(event);
        break;
      case "drop":
        this.onDrop(event);
        break;
    }
  },

  /**
   * Getter that returns whether or not the view source browser
   * has history enabled on it.
   */
  get historyEnabled() {
    return !this.browser.hasAttribute("disablehistory");
  },

  /**
   * Getter for the message manager used to communicate with the view source
   * browser.
   *
   * In this window version of view source, we use the window message manager
   * for loading scripts and listening for messages so that if we switch
   * remoteness of the browser (which we might do if we're attempting to load
   * the document source out of the network cache), we automatically re-load
   * the frame script.
   */
  get mm() {
    return window.messageManager;
  },

  /**
   * Getter for the nsIWebNavigation of the view source browser.
   */
  get webNav() {
    return this.browser.webNavigation;
  },

  /**
   * Send the browser forward in its history.
   */
  goForward() {
    this.browser.goForward();
  },

  /**
   * Send the browser backward in its history.
   */
  goBack() {
    this.browser.goBack();
  },

  /**
   * This should be called once when the DOM has finished loading. Here we
   * set the state of various menu items, and add event listeners to
   * DOM nodes.
   *
   * This is also the place where we handle any arguments that have been
   * passed to viewSource.xul.
   *
   * Modern consumers should pass a single object argument to viewSource.xul:
   *
   *   URL (required):
   *     A string URL for the page we'd like to view the source of.
   *   browser:
   *     The browser containing the document that we would like to view the
   *     source of. This argument is optional if outerWindowID is not passed.
   *   outerWindowID (optional):
   *     The outerWindowID of the content window containing the document that
   *     we want to view the source of. This is the only way of attempting to
   *     load the source out of the network cache.
   *   lineNumber (optional):
   *     The line number to focus on once the source is loaded.
   *
   * The deprecated API has the opener pass in a number of arguments:
   *
   * arg[0] - URL string.
   * arg[1] - Charset value string in the form 'charset=xxx'.
   * arg[2] - Page descriptor from nsIWebPageDescriptor used to load content
   *          from the cache.
   * arg[3] - Line number to go to.
   * arg[4] - Boolean for whether charset was forced by the user
   */
  onXULLoaded() {
    // This handler should only ever run the first time the XUL is loaded.
    removeEventListener("load", this);

    let wrapMenuItem = document.getElementById("menu_wrapLongLines");
    if (this.shouldWrap) {
      wrapMenuItem.setAttribute("checked", "true");
    }

    let highlightMenuItem = document.getElementById("menu_highlightSyntax");
    if (this.shouldHighlight) {
      highlightMenuItem.setAttribute("checked", "true");
    }

    gContextMenu.addEventListener("popupshowing", this);
    gContextMenu.addEventListener("popuphidden", this);

    Services.els.addSystemEventListener(this.browser, "dragover", this, true);
    Services.els.addSystemEventListener(this.browser, "drop", this, true);

    if (!this.historyEnabled) {
      // Disable the BACK and FORWARD commands and hide the related menu items.
      let viewSourceNavigation = document.getElementById("viewSourceNavigation");
      if (viewSourceNavigation) {
        viewSourceNavigation.setAttribute("disabled", "true");
        viewSourceNavigation.setAttribute("hidden", "true");
      }
    }

    // We require the first argument to do any loading of source.
    // otherwise, we're done.
    if (!window.arguments[0]) {
      return undefined;
    }

    if (typeof window.arguments[0] == "string") {
      // We're using the deprecated API
      return this._loadViewSourceDeprecated(window.arguments);
    }

    // We're using the modern API, which allows us to view the
    // source of documents from out of process browsers.
    let args = window.arguments[0];

    // viewPartialSource.js will take care of loading the content in partial mode.
    if (!args.partial) {
      this.loadViewSource(args);
    }

    return undefined;
  },

  /**
   * This is the deprecated API for viewSource.xul, for old-timer consumers.
   * This API might eventually go away.
   */
  _loadViewSourceDeprecated(aArguments) {
    Deprecated.warning("The arguments you're passing to viewSource.xul " +
                       "are using an out-of-date API.",
                       "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
    // Parse the 'arguments' supplied with the dialog.
    //    arg[0] - URL string.
    //    arg[1] - Charset value in the form 'charset=xxx'.
    //    arg[2] - Page descriptor used to load content from the cache.
    //    arg[3] - Line number to go to.
    //    arg[4] - Whether charset was forced by the user

    if (aArguments[2]) {
      let pageDescriptor = aArguments[2];
      if (Cu.isCrossProcessWrapper(pageDescriptor)) {
        throw new Error("Cannot pass a CPOW as the page descriptor to viewSource.xul.");
      }
    }

    if (this.browser.isRemoteBrowser) {
      throw new Error("Deprecated view source API should not use a remote browser.");
    }

    let forcedCharSet;
    if (aArguments[4] && aArguments[1].startsWith("charset=")) {
      forcedCharSet = aArguments[1].split("=")[1];
    }

    this.sendAsyncMessage("ViewSource:LoadSourceDeprecated", {
      URL: aArguments[0],
      lineNumber: aArguments[3],
      forcedCharSet,
    }, {
      pageDescriptor: aArguments[2],
    });
  },

  /**
   * Handler for the AppCommand event.
   *
   * @param event
   *        The AppCommand event being handled.
   */
  onAppCommand(event) {
    event.stopPropagation();
    switch (event.command) {
      case "Back":
        this.goBack();
        break;
      case "Forward":
        this.goForward();
        break;
    }
  },

  /**
   * Handler for the MozSwipeGesture event.
   *
   * @param event
   *        The MozSwipeGesture event being handled.
   */
  onSwipeGesture(event) {
    event.stopPropagation();
    switch (event.direction) {
      case SimpleGestureEvent.DIRECTION_LEFT:
        this.goBack();
        break;
      case SimpleGestureEvent.DIRECTION_RIGHT:
        this.goForward();
        break;
      case SimpleGestureEvent.DIRECTION_UP:
        goDoCommand("cmd_scrollTop");
        break;
      case SimpleGestureEvent.DIRECTION_DOWN:
        goDoCommand("cmd_scrollBottom");
        break;
    }
  },

  /**
   * Called as soon as the frame script reports that some source
   * code has been loaded in the browser.
   */
  onSourceLoaded() {
    document.getElementById("cmd_goToLine").removeAttribute("disabled");

    if (this.historyEnabled) {
      this.updateCommands();
    }

    this.browser.focus();
  },

  /**
   * Called as soon as the frame script reports that some source
   * code has been unloaded from the browser.
   */
  onSourceUnloaded() {
    // Disable "go to line" while reloading due to e.g. change of charset
    // or toggling of syntax highlighting.
    document.getElementById("cmd_goToLine").setAttribute("disabled", "true");
  },

  /**
   * Called by clicks on a menu populated by CharsetMenu.jsm to
   * change the selected character set.
   *
   * @param event
   *        The click event on a character set menuitem.
   */
  onSetCharacterSet(event) {
    if (event.target.hasAttribute("charset")) {
      let charset = event.target.getAttribute("charset");

      // If we don't have history enabled, we have to do a reload in order to
      // show the character set change. See bug 136322.
      this.sendAsyncMessage("ViewSource:SetCharacterSet", {
        charset,
        doPageLoad: this.historyEnabled,
      });

      if (!this.historyEnabled) {
        this.browser
            .reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
      }
    }
  },

  /**
   * Called from the frame script when the context menu is about to
   * open. This tells ViewSourceChrome things about the item that
   * the context menu is being opened on. This should be called before
   * the popupshowing event handler fires.
   */
  onContextMenuOpening(isLink, isEmail, href) {
    this.contextMenuData = { isLink, isEmail, href, isOpen: true };
  },

  /**
   * Event handler for the popupshowing event on the context menu.
   * This handler is responsible for setting the state on various
   * menu items in the context menu, and reads values that were sent
   * up from the frame script and stashed into this.contextMenuData.
   *
   * @param event
   *        The popupshowing event for the context menu.
   */
  onContextMenuShowing(event) {
    let copyLinkMenuItem = document.getElementById("context-copyLink");
    copyLinkMenuItem.hidden = !this.contextMenuData.isLink;

    let copyEmailMenuItem = document.getElementById("context-copyEmail");
    copyEmailMenuItem.hidden = !this.contextMenuData.isEmail;
  },

  /**
   * Called when the user chooses the "Copy Link" or "Copy Email"
   * menu items in the context menu. Copies the relevant selection
   * into the system clipboard.
   */
  onContextMenuCopyLinkOrEmail() {
    // It doesn't make any sense to call this if the context menu
    // isn't open...
    if (!this.contextMenuData.isOpen) {
      return;
    }

    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
                      .getService(Ci.nsIClipboardHelper);
    clipboard.copyString(this.contextMenuData.href);
  },

  /**
   * Called when the context menu closes, and invalidates any data
   * that the frame script might have sent up about what the context
   * menu was opened on.
   */
  onContextMenuHidden(event) {
    this.contextMenuData = {
      isOpen: false,
    };
  },

  /**
   * Called when the user drags something over the content browser.
   */
  onDragOver(event) {
    // For drags that appear to be internal text (for example, tab drags),
    // set the dropEffect to 'none'. This prevents the drop even if some
    // other listener cancelled the event.
    let types = event.dataTransfer.types;
    if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
        event.dataTransfer.dropEffect = "none";
        event.stopPropagation();
        event.preventDefault();
    }

    let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"]
                        .getService(Ci.nsIDroppedLinkHandler);

    if (linkHandler.canDropLink(event, false)) {
      event.preventDefault();
    }
  },

  /**
   * Called twhen the user drops something onto the content browser.
   */
  onDrop(event) {
    if (event.defaultPrevented)
      return;

    let name = { };
    let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"]
                        .getService(Ci.nsIDroppedLinkHandler);
    let uri;
    try {
      // Pass true to prevent the dropping of javascript:/data: URIs
      uri = linkHandler.dropLink(event, name, true);
    } catch (e) {
      return;
    }

    if (uri) {
      this.loadURL(uri);
    }
  },

  /**
   * For remote browsers, the contextmenu event is received in the
   * content process, and a message is sent up from the frame script
   * to ViewSourceChrome, but then it stops. The event does not bubble
   * up to the point that the popup is opened in the parent process.
   * ViewSourceChrome is responsible for opening up the context menu in
   * that case. This is called when we receive the contextmenu message
   * from the child, and we know that the browser is currently remote.
   *
   * @param screenX
   *        The screenX coordinate to open the popup at.
   * @param screenY
   *        The screenY coordinate to open the popup at.
   */
  openContextMenu(screenX, screenY) {
    gContextMenu.openPopupAtScreen(screenX, screenY, true);
  },

  /**
   * Loads the source of a URL. This will probably end up hitting the
   * network.
   *
   * @param URL
   *        A URL string to be opened in the view source browser.
   */
  loadURL(URL) {
    this.sendAsyncMessage("ViewSource:LoadSource", { URL });
  },

  /**
   * Updates any commands that are dependant on command broadcasters.
   */
  updateCommands() {
    let backBroadcaster = document.getElementById("Browser:Back");
    let forwardBroadcaster = document.getElementById("Browser:Forward");

    if (this.webNav.canGoBack) {
      backBroadcaster.removeAttribute("disabled");
    } else {
      backBroadcaster.setAttribute("disabled", "true");
    }
    if (this.webNav.canGoForward) {
      forwardBroadcaster.removeAttribute("disabled");
    } else {
      forwardBroadcaster.setAttribute("disabled", "true");
    }
  },

  /**
   * Updates the status displayed in the status bar of the view source window.
   *
   * @param label
   *        The string to be displayed in the statusbar-lin-col element.
   */
  updateStatus(label) {
    let statusBarField = document.getElementById("statusbar-line-col");
    if (statusBarField) {
      statusBarField.label = label;
    }
  },

  /**
   * Called when the frame script reports that a line was successfully gotten
   * to.
   *
   * @param lineNumber
   *        The line number that we successfully got to.
   */
  onGoToLineSuccess(lineNumber) {
    ViewSourceBrowser.prototype.onGoToLineSuccess.call(this, lineNumber);
    document.getElementById("statusbar-line-col").label =
      gViewSourceBundle.getFormattedString("statusBarLineCol", [lineNumber, 1]);
  },

  /**
   * Reloads the browser, bypassing the network cache.
   */
  reload() {
    this.browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
                                 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
  },

  /**
   * Closes the view source window.
   */
  close() {
    window.close();
  },

  /**
   * Called when the user clicks on the "Wrap Long Lines" menu item.
   */
  toggleWrapping() {
    this.shouldWrap = !this.shouldWrap;
    this.sendAsyncMessage("ViewSource:ToggleWrapping");
  },

  /**
   * Called when the user clicks on the "Syntax Highlighting" menu item.
   */
  toggleSyntaxHighlighting() {
    this.shouldHighlight = !this.shouldHighlight;
    this.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
  },

  /**
   * Updates the "remote" attribute of the view source browser. This
   * will remove the browser from the DOM, and then re-add it in the
   * same place it was taken from.
   *
   * @param shouldBeRemote
   *        True if the browser should be made remote. If the browsers
   *        remoteness already matches this value, this function does
   *        nothing.
   * @param remoteType
   *        The type of remote browser process.
   */
  updateBrowserRemoteness(shouldBeRemote, remoteType) {
    if (this.browser.isRemoteBrowser == shouldBeRemote &&
        this.browser.remoteType == remoteType) {
      return;
    }

    let parentNode = this.browser.parentNode;
    let nextSibling = this.browser.nextSibling;

    // Removing and re-adding the browser from and to the DOM strips its XBL
    // properties. Save and restore sameProcessAsFrameLoader. Note that when we
    // restore sameProcessAsFrameLoader, there won't yet be a binding or
    // setter. This works in conjunction with the hack in <xul:browser>'s
    // constructor to re-get the weak reference to it.
    let sameProcessAsFrameLoader = this.browser.sameProcessAsFrameLoader;

    this.browser.remove();
    if (shouldBeRemote) {
      this.browser.setAttribute("remote", "true");
      this.browser.setAttribute("remoteType", remoteType);
    } else {
      this.browser.removeAttribute("remote");
      this.browser.removeAttribute("remoteType");
    }

    this.browser.sameProcessAsFrameLoader = sameProcessAsFrameLoader;

    // If nextSibling was null, this will put the browser at
    // the end of the list.
    parentNode.insertBefore(this.browser, nextSibling);

    if (shouldBeRemote) {
      // We're going to send a message down to the remote browser
      // to load the source content - however, in order for the
      // contentWindowAsCPOW and contentDocumentAsCPOW values on
      // the remote browser to be set, we must set up the
      // RemoteWebProgress, which is lazily loaded. We only need
      // contentWindowAsCPOW for the printing support, and this
      // should go away once bug 1146454 is fixed, since we can
      // then just pass the outerWindowID of the this.browser to
      // PrintUtils.
      this.browser.webProgress;
    }
  },
};

var viewSourceChrome = new ViewSourceChrome();

/**
 * PrintUtils uses this to make Print Preview work.
 */
var PrintPreviewListener = {
  _ppBrowser: null,

  getPrintPreviewBrowser() {
    if (!this._ppBrowser) {
      this._ppBrowser = document.createElement("browser");
      this._ppBrowser.setAttribute("flex", "1");
      this._ppBrowser.setAttribute("type", "content");
    }

    if (gBrowser.isRemoteBrowser) {
      this._ppBrowser.setAttribute("remote", "true");
    } else {
      this._ppBrowser.removeAttribute("remote");
    }

    let findBar = document.getElementById("FindToolbar");
    document.getElementById("appcontent")
            .insertBefore(this._ppBrowser, findBar);

    return this._ppBrowser;
  },

  getSourceBrowser() {
    return gBrowser;
  },

  getNavToolbox() {
    return document.getElementById("appcontent");
  },

  onEnter() {
    let toolbox = document.getElementById("viewSource-toolbox");
    toolbox.hidden = true;
    gBrowser.collapsed = true;
  },

  onExit() {
    this._ppBrowser.remove();
    gBrowser.collapsed = false;
    document.getElementById("viewSource-toolbox").hidden = false;
  },

  activateBrowser(browser) {
    browser.docShellIsActive = true;
  },
};

// viewZoomOverlay.js uses this
function getBrowser() {
  return gBrowser;
}

Object.defineProperty(this, "gPageLoader", {
  configurable: true,
  enumerable: true,
  get() {
    var webnav = viewSourceChrome.webNav;
    if (!webnav)
      return null;
    delete this.gPageLoader;
    this.gPageLoader = (webnav instanceof Ci.nsIWebPageDescriptor) ? webnav
                                                                   : null;
    return this.gPageLoader;
  },
});

// Strips the |view-source:| for internalSave()
function ViewSourceSavePage() {
  internalSave(gBrowser.currentURI.spec.replace(/^view-source:/i, ""),
               null, null, null, null, null, "SaveLinkTitle",
               null, null, gBrowser.contentDocumentAsCPOW, null,
               gPageLoader);
}

// Below are old deprecated functions and variables left behind for
// compatibility reasons. These will be removed soon via bug 1159293.

Object.defineProperty(this, "gLastLineFound", {
  configurable: true,
  enumerable: true,
  get() {
    Deprecated.warning("gLastLineFound is deprecated - please use " +
                       "viewSourceChrome.lastLineFound instead.",
                       "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
    return viewSourceChrome.lastLineFound;
  },
});

function onLoadViewSource() {
  Deprecated.warning("onLoadViewSource() is deprecated - please use " +
                     "viewSourceChrome.onXULLoaded() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.onXULLoaded();
}

function isHistoryEnabled() {
  Deprecated.warning("isHistoryEnabled() is deprecated - please use " +
                     "viewSourceChrome.historyEnabled instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  return viewSourceChrome.historyEnabled;
}

function ViewSourceClose() {
  Deprecated.warning("ViewSourceClose() is deprecated - please use " +
                     "viewSourceChrome.close() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.close();
}

function ViewSourceReload() {
  Deprecated.warning("ViewSourceReload() is deprecated - please use " +
                     "viewSourceChrome.reload() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.reload();
}

function getWebNavigation() {
  Deprecated.warning("getWebNavigation() is deprecated - please use " +
                     "viewSourceChrome.webNav instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  // The original implementation returned null if anything threw during
  // the getting of the webNavigation.
  try {
    return viewSourceChrome.webNav;
  } catch (e) {
    return null;
  }
}

function viewSource(url) {
  Deprecated.warning("viewSource() is deprecated - please use " +
                     "viewSourceChrome.loadURL() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.loadURL(url);
}

function ViewSourceGoToLine() {
  Deprecated.warning("ViewSourceGoToLine() is deprecated - please use " +
                     "viewSourceChrome.promptAndGoToLine() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.promptAndGoToLine();
}

function goToLine(line) {
  Deprecated.warning("goToLine() is deprecated - please use " +
                     "viewSourceChrome.goToLine() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.goToLine(line);
}

function BrowserForward(aEvent) {
  Deprecated.warning("BrowserForward() is deprecated - please use " +
                     "viewSourceChrome.goForward() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.goForward();
}

function BrowserBack(aEvent) {
  Deprecated.warning("BrowserBack() is deprecated - please use " +
                     "viewSourceChrome.goBack() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.goBack();
}

function UpdateBackForwardCommands() {
  Deprecated.warning("UpdateBackForwardCommands() is deprecated - please use " +
                     "viewSourceChrome.updateCommands() instead.",
                     "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
  viewSourceChrome.updateCommands();
}
PK
!<F++,chrome/toolkit/content/global/viewSource.xul<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/viewSource.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>

<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % sourceDTD SYSTEM "chrome://global/locale/viewSource.dtd" >
%sourceDTD;
<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
%charsetDTD;
]>

<window id="viewSource"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        contenttitlesetting="true"
        title="&mainWindow.title;"
        titlemodifier="&mainWindow.titlemodifier;"
        titlepreface="&mainWindow.preface;"
        titlemenuseparator ="&mainWindow.titlemodifierseparator;"
        windowtype="navigator:view-source"
        width="640" height="480"
        screenX="10" screenY="10"
        persist="screenX screenY width height sizemode">

  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
  <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
  <script type="application/javascript" src="chrome://global/content/viewSource.js"/>
  <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
  <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>

  <stringbundle id="viewSourceBundle" src="chrome://global/locale/viewSource.properties"/>

  <command id="cmd_savePage" oncommand="ViewSourceSavePage();"/>
  <command id="cmd_print" oncommand="PrintUtils.printWindow(gBrowser.outerWindowID, gBrowser);"/>
  <command id="cmd_printpreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
  <command id="cmd_pagesetup" oncommand="PrintUtils.showPageSetup();"/>
  <command id="cmd_close" oncommand="window.close();"/>
  <commandset id="editMenuCommands"/>
  <command id="cmd_find"
           oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
  <command id="cmd_findAgain"
           oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
  <command id="cmd_findPrevious"
           oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
  <command id="cmd_reload" oncommand="viewSourceChrome.reload();"/>
  <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
  <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
  <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
  <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
  <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
  <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>

  <command id="Browser:Back" oncommand="viewSourceChrome.goBack()" observes="viewSourceNavigation"/>
  <command id="Browser:Forward" oncommand="viewSourceChrome.goForward()" observes="viewSourceNavigation"/>

  <broadcaster id="viewSourceNavigation"/>

  <keyset id="editMenuKeys"/>
  <keyset id="viewSourceKeys">
    <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
    <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
    <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel" command="cmd_close"/>
    <key id="key_goToLine"     key="&goToLineCmd.commandkey;"  command="cmd_goToLine"  modifiers="accel"/>

    <key id="key_textZoomEnlarge" key="&textEnlarge.commandkey;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomEnlarge2" key="&textEnlarge.commandkey2;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomEnlarge3" key="&textEnlarge.commandkey3;" command="cmd_textZoomEnlarge" modifiers="accel"/>
    <key id="key_textZoomReduce"  key="&textReduce.commandkey;" command="cmd_textZoomReduce" modifiers="accel"/>
    <key id="key_textZoomReduce2"  key="&textReduce.commandkey2;" command="cmd_textZoomReduce" modifiers="accel"/>
    <key id="key_textZoomReset" key="&textReset.commandkey;" command="cmd_textZoomReset" modifiers="accel"/>
    <key id="key_textZoomReset2" key="&textReset.commandkey2;" command="cmd_textZoomReset" modifiers="accel"/>

    <key id="key_reload" key="&reloadCmd.commandkey;" command="cmd_reload" modifiers="accel"/>
    <key key="&reloadCmd.commandkey;" command="cmd_reload" modifiers="accel,shift"/>
    <key keycode="VK_F5" command="cmd_reload"/>
    <key keycode="VK_F5" command="cmd_reload" modifiers="accel"/>
    <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
    <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
    <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
    <key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
    <key keycode="&findAgainCmd.commandkey2;"  command="cmd_findPrevious" modifiers="shift"/>

    <key keycode="VK_BACK" command="Browser:Back"/>
    <key keycode="VK_BACK" command="Browser:Forward" modifiers="shift"/>
    <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
    <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>

  </keyset>

  <tooltip id="aHTMLTooltip" page="true"/>

  <menupopup id="viewSourceContextMenu">
    <menuitem id="context-back"
              label="&backCmd.label;"
              accesskey="&backCmd.accesskey;"
              command="Browser:Back"
              observes="viewSourceNavigation"/>
    <menuitem id="context-forward"
              label="&forwardCmd.label;"
              accesskey="&forwardCmd.accesskey;"
              command="Browser:Forward"
              observes="viewSourceNavigation"/>
    <menuseparator observes="viewSourceNavigation"/>
    <menuitem id="cMenu_findAgain"/>
    <menuseparator/>
    <menuitem id="cMenu_copy"/>
    <menuitem id="context-copyLink"
              label="&copyLinkCmd.label;"
              accesskey="&copyLinkCmd.accesskey;"
              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
    <menuitem id="context-copyEmail"
              label="&copyEmailCmd.label;"
              accesskey="&copyEmailCmd.accesskey;"
              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
    <menuseparator/>
    <menuitem id="cMenu_selectAll"/>
  </menupopup>

  <!-- Menu -->
  <toolbox id="viewSource-toolbox">
    <menubar id="viewSource-main-menubar">

      <menu id="menu_file" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
        <menupopup id="menu_FilePopup">
          <menuitem key="key_savePage" command="cmd_savePage" id="menu_savePage"
                    label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
          <menuitem command="cmd_pagesetup" id="menu_pageSetup"
                    label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
          <menuitem command="cmd_printpreview" id="menu_printPreview"
                    label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
          <menuitem key="key_print" command="cmd_print" id="menu_print"
                    label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
          <menuseparator/>
          <menuitem key="key_close" command="cmd_close" id="menu_close"
                    label="&closeCmd.label;" accesskey="&closeCmd.accesskey;"/>
        </menupopup>
      </menu>

      <menu id="menu_edit">
        <menupopup id="editmenu-popup">
          <menuitem id="menu_undo"/>
          <menuitem id="menu_redo"/>
          <menuseparator/>
          <menuitem id="menu_cut"/>
          <menuitem id="menu_copy"/>
          <menuitem id="menu_paste"/>
          <menuitem id="menu_delete"/>
          <menuseparator/>
          <menuitem id="menu_selectAll"/>
          <menuseparator/>
          <menuitem id="menu_find"/>
          <menuitem id="menu_findAgain"/>
          <menuseparator/>
          <menuitem id="menu_goToLine" key="key_goToLine" command="cmd_goToLine"
                    label="&goToLineCmd.label;" accesskey="&goToLineCmd.accesskey;"/>
        </menupopup>
      </menu>

      <menu id="menu_view" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
        <menupopup id="viewmenu-popup">
          <menuitem id="menu_reload" command="cmd_reload" accesskey="&reloadCmd.accesskey;"
                    label="&reloadCmd.label;" key="key_reload"/>
          <menuseparator />
          <menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
            <menupopup>
              <menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
                        label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
                        key="key_textZoomEnlarge"/>
              <menuitem id="menu_textReduce" command="cmd_textZoomReduce"
                        label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
                        key="key_textZoomReduce"/>
              <menuseparator/>
              <menuitem id="menu_textReset" command="cmd_textZoomReset"
                        label="&menu_textReset.label;" accesskey="&menu_textReset.accesskey;"
                        key="key_textZoomReset"/>
            </menupopup>
          </menu>

          <!-- Charset Menu -->
          <menu id="charsetMenu"
                label="&charsetMenu2.label;"
                accesskey="&charsetMenu2.accesskey;"
                oncommand="viewSourceChrome.onSetCharacterSet(event);"
                onpopupshowing="CharsetMenu.build(event.target);
                                CharsetMenu.update(event.target, content.document.characterSet);">
            <menupopup/>
          </menu>
          <menuseparator/>
          <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
                    label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
          <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
                    label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
        </menupopup>
      </menu>
    </menubar>
  </toolbox>

  <vbox id="appcontent" flex="1">

    <browser id="content" type="content" name="content" src="about:blank" flex="1"
             primary="true"
             context="viewSourceContextMenu" showcaret="true" tooltip="aHTMLTooltip" />
    <findbar id="FindToolbar" browserid="content"/>
  </vbox>

  <statusbar id="status-bar" class="chromeclass-status">
    <statusbarpanel id="statusbar-line-col" label="" flex="1"/>
  </statusbar>

</window>
PK
!<y0VV0chrome/toolkit/content/global/viewSourceUtils.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/. */

/*
 * To keep the global namespace safe, don't define global variables and
 * functions in this file.
 *
 * This file silently depends on contentAreaUtils.js for
 * getDefaultFileName, getNormalizedLeafName and getDefaultExtension
 */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
  "resource://gre/modules/ViewSourceBrowser.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
  "resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
  "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

var gViewSourceUtils = {

  mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
  mnsIWebProgress: Components.interfaces.nsIWebProgress,
  mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,

  /**
   * Opens the view source window.
   *
   * @param aArgsOrURL (required)
   *        This is either an Object containing parameters, or a string
   *        URL for the page we want to view the source of. In the latter
   *        case we will be paying attention to the other parameters, as
   *        we will be supporting the old API for this method.
   *        If aArgsOrURL is an Object, the other parameters will be ignored.
   *        aArgsOrURL as an Object can include the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   *
   * @param aPageDescriptor (deprecated, optional)
   *        Accepted for compatibility reasons, but is otherwise ignored.
   * @param aDocument (deprecated, optional)
   *        The content document we would like to view the source of. This
   *        function will throw if aDocument is a CPOW.
   * @param aLineNumber (deprecated, optional)
   *        The line number to focus on once the source is loaded.
   */
  viewSource(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber) {
    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
    if (prefs.getBoolPref("view_source.editor.external")) {
      this.openInExternalEditor(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
    } else {
      this._openInInternalViewer(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
    }
  },

  /**
   * Displays view source in the provided <browser>.  This allows for non-window
   * display methods, such as a tab from Firefox.
   *
   * @param aArgs
   *        An object with the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        viewSourceBrowser (required):
   *          The browser to display the view source in.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   */
  viewSourceInBrowser(aArgs) {
    Services.telemetry
            .getHistogramById("VIEW_SOURCE_IN_BROWSER_OPENED_BOOLEAN")
            .add(true);
    let viewSourceBrowser = new ViewSourceBrowser(aArgs.viewSourceBrowser);
    viewSourceBrowser.loadViewSource(aArgs);
  },

  /**
   * Displays view source for a selection from some document in the provided
   * <browser>.  This allows for non-window display methods, such as a tab from
   * Firefox.
   *
   * @param aViewSourceInBrowser
   *        The browser containing the page to view the source of.
   * @param aTarget
   *        Set to the target node for MathML. Null for other types of elements.
   * @param aGetBrowserFn
   *        If set, a function that will return a browser to open the source in.
   *        If null, or this function returns null, opens the source in a new window.
   */
  viewPartialSourceInBrowser(aViewSourceInBrowser, aTarget, aGetBrowserFn) {
    let mm = aViewSourceInBrowser.messageManager;
    mm.addMessageListener("ViewSource:GetSelectionDone", function gotSelection(message) {
      mm.removeMessageListener("ViewSource:GetSelectionDone", gotSelection);

      if (!message.data)
        return;

      let browserToOpenIn = aGetBrowserFn ? aGetBrowserFn() : null;
      if (browserToOpenIn) {
        let viewSourceBrowser = new ViewSourceBrowser(browserToOpenIn);
        viewSourceBrowser.loadViewSourceFromSelection(message.data.uri, message.data.drawSelection,
                                                      message.data.baseURI);
      } else {
        window.openDialog("chrome://global/content/viewPartialSource.xul",
                          "_blank", "all,dialog=no",
                          {
                            URI: message.data.uri,
                            drawSelection: message.data.drawSelection,
                            baseURI: message.data.baseURI,
                            partial: true,
                          });
      }
    });

    mm.sendAsyncMessage("ViewSource:GetSelection", { }, { target: aTarget });
  },

  // Opens the interval view source viewer
  _openInInternalViewer(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber) {
    // try to open a view-source window while inheriting the charset (if any)
    var charset = null;
    var isForcedCharset = false;
    if (aDocument) {
      if (Components.utils.isCrossProcessWrapper(aDocument)) {
        throw new Error("View Source cannot accept a CPOW as a document.");
      }

      charset = "charset=" + aDocument.characterSet;
      try {
        isForcedCharset =
          aDocument.defaultView
                   .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIDOMWindowUtils)
                   .docCharsetIsForced;
      } catch (ex) {
      }
    }
    Services.telemetry
            .getHistogramById("VIEW_SOURCE_IN_WINDOW_OPENED_BOOLEAN")
            .add(true);
    openDialog("chrome://global/content/viewSource.xul",
               "_blank",
               "all,dialog=no",
               aArgsOrURL, charset, aPageDescriptor, aLineNumber, isForcedCharset);
  },

  buildEditorArgs(aPath, aLineNumber) {
    // Determine the command line arguments to pass to the editor.
    // We currently support a %LINE% placeholder which is set to the passed
    // line number (or to 0 if there's none)
    var editorArgs = [];
    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
    var args = prefs.getCharPref("view_source.editor.args");
    if (args) {
      args = args.replace("%LINE%", aLineNumber || "0");
      // add the arguments to the array (keeping quoted strings intact)
      const argumentRE = /"([^"]+)"|(\S+)/g;
      while (argumentRE.test(args))
        editorArgs.push(RegExp.$1 || RegExp.$2);
    }
    editorArgs.push(aPath);
    return editorArgs;
  },

  /**
   * Opens an external editor with the view source content.
   *
   * @param aArgsOrURL (required)
   *        This is either an Object containing parameters, or a string
   *        URL for the page we want to view the source of. In the latter
   *        case we will be paying attention to the other parameters, as
   *        we will be supporting the old API for this method.
   *        If aArgsOrURL is an Object, the other parameters will be ignored.
   *        aArgsOrURL as an Object can include the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   *
   * @param aPageDescriptor (deprecated, optional)
   *        Accepted for compatibility reasons, but is otherwise ignored.
   * @param aDocument (deprecated, optional)
   *        The content document we would like to view the source of. This
   *        function will throw if aDocument is a CPOW.
   * @param aLineNumber (deprecated, optional)
   *        The line number to focus on once the source is loaded.
   * @param aCallBack
   *        A function accepting two arguments:
   *          * result (true = success)
   *          * data object
   *        The function defaults to opening an internal viewer if external
   *        viewing fails.
   */
  openInExternalEditor(aArgsOrURL, aPageDescriptor, aDocument,
                                 aLineNumber, aCallBack) {
    let data;
    if (typeof aArgsOrURL == "string") {
      Deprecated.warning("The arguments you're passing to " +
                         "openInExternalEditor are using an out-of-date API.",
                         "https://developer.mozilla.org/en-US/Add-ons/" +
                         "Code_snippets/View_Source_for_XUL_Applications");
      if (Components.utils.isCrossProcessWrapper(aDocument)) {
        throw new Error("View Source cannot accept a CPOW as a document.");
      }
      data = {
        url: aArgsOrURL,
        pageDescriptor: aPageDescriptor,
        doc: aDocument,
        lineNumber: aLineNumber,
        isPrivate: false,
      };
      if (aDocument) {
          data.isPrivate =
            PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
      }
    } else {
      let { URL, browser, lineNumber } = aArgsOrURL;
      data = {
        url: URL,
        lineNumber,
        isPrivate: false,
      };
      if (browser) {
        data.doc = {
          characterSet: browser.characterSet,
          contentType: browser.documentContentType,
          title: browser.contentTitle,
        };
        data.isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser);
      }
    }

    try {
      var editor = this.getExternalViewSourceEditor();
      if (!editor) {
        this.handleCallBack(aCallBack, false, data);
        return;
      }

      // make a uri
      var ios = Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);
      var charset = data.doc ? data.doc.characterSet : null;
      var uri = ios.newURI(data.url, charset);
      data.uri = uri;

      var path;
      var contentType = data.doc ? data.doc.contentType : null;
      if (uri.scheme == "file") {
        // it's a local file; we can open it directly
        path = uri.QueryInterface(Components.interfaces.nsIFileURL).file.path;

        var editorArgs = this.buildEditorArgs(path, data.lineNumber);
        editor.runw(false, editorArgs, editorArgs.length);
        this.handleCallBack(aCallBack, true, data);
      } else {
        // set up the progress listener with what we know so far
        this.viewSourceProgressListener.contentLoaded = false;
        this.viewSourceProgressListener.editor = editor;
        this.viewSourceProgressListener.callBack = aCallBack;
        this.viewSourceProgressListener.data = data;
        if (!data.pageDescriptor) {
          // without a page descriptor, loadPage has no chance of working. download the file.
          var file = this.getTemporaryFile(uri, data.doc, contentType);
          this.viewSourceProgressListener.file = file;

          var webBrowserPersist = Components
                                  .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
                                  .createInstance(this.mnsIWebBrowserPersist);
          // the default setting is to not decode. we need to decode.
          webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
          webBrowserPersist.progressListener = this.viewSourceProgressListener;
          let referrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER;
          webBrowserPersist.savePrivacyAwareURI(uri, null, null, referrerPolicy, null, null, file, data.isPrivate);

          let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
                                        .getService(Components.interfaces.nsPIExternalAppLauncher);
          if (data.isPrivate) {
            // register the file to be deleted when possible
            helperService.deleteTemporaryPrivateFileWhenPossible(file);
          } else {
            // register the file to be deleted on app exit
            helperService.deleteTemporaryFileOnExit(file);
          }
        } else {
          // we'll use nsIWebPageDescriptor to get the source because it may
          // not have to refetch the file from the server
          // XXXbz this is so broken...  This code doesn't set up this docshell
          // at all correctly; if somehow the view-source stuff managed to
          // execute script we'd be in big trouble here, I suspect.
          var webShell = Components.classes["@mozilla.org/docshell;1"].createInstance();
          webShell.QueryInterface(Components.interfaces.nsIBaseWindow).create();
          this.viewSourceProgressListener.webShell = webShell;
          var progress = webShell.QueryInterface(this.mnsIWebProgress);
          progress.addProgressListener(this.viewSourceProgressListener,
                                       this.mnsIWebProgress.NOTIFY_STATE_DOCUMENT);
          var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
          pageLoader.loadPage(data.pageDescriptor, this.mnsIWebPageDescriptor.DISPLAY_AS_SOURCE);
        }
      }
    } catch (ex) {
      // we failed loading it with the external editor.
      Components.utils.reportError(ex);
      this.handleCallBack(aCallBack, false, data);
    }
  },

  // Default callback - opens the internal viewer if the external editor failed
  internalViewerFallback(result, data) {
    if (!result) {
      this._openInInternalViewer(data.url, data.pageDescriptor, data.doc, data.lineNumber);
    }
  },

  // Calls the callback, keeping in mind undefined or null values.
  handleCallBack(aCallBack, result, data) {
    Services.telemetry
            .getHistogramById("VIEW_SOURCE_EXTERNAL_RESULT_BOOLEAN")
            .add(result);
    // if callback is undefined, default to the internal viewer
    if (aCallBack === undefined) {
      this.internalViewerFallback(result, data);
    } else if (aCallBack) {
      aCallBack(result, data);
    }
  },

  // Returns nsIProcess of the external view source editor or null
  getExternalViewSourceEditor() {
    try {
      let viewSourceAppPath =
          Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch)
                    .getComplexValue("view_source.editor.path",
                                     Components.interfaces.nsIFile);
      let editor = Components.classes["@mozilla.org/process/util;1"]
                             .createInstance(Components.interfaces.nsIProcess);
      editor.init(viewSourceAppPath);

      return editor;
    } catch (ex) {
      Components.utils.reportError(ex);
    }

    return null;
  },

  viewSourceProgressListener: {

    mnsIWebProgressListener: Components.interfaces.nsIWebProgressListener,

    QueryInterface(aIID) {
     if (aIID.equals(this.mnsIWebProgressListener) ||
         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
     throw Components.results.NS_NOINTERFACE;
    },

    destroy() {
      if (this.webShell) {
        this.webShell.QueryInterface(Components.interfaces.nsIBaseWindow).destroy();
      }
      this.webShell = null;
      this.editor = null;
      this.callBack = null;
      this.data = null;
      this.file = null;
    },

    // This listener is used both for tracking the progress of an HTML parse
    // in one case and for tracking the progress of nsIWebBrowserPersist in
    // another case.
    onStateChange(aProgress, aRequest, aFlag, aStatus) {
      // once it's done loading...
      if ((aFlag & this.mnsIWebProgressListener.STATE_STOP) && aStatus == 0) {
        if (!this.webShell) {
          // We aren't waiting for the parser. Instead, we are waiting for
          // an nsIWebBrowserPersist.
          this.onContentLoaded();
          return 0;
        }
        var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
        if (webNavigation.document.readyState == "complete") {
          // This branch is probably never taken. Including it for completeness.
          this.onContentLoaded();
        } else {
          webNavigation.document.addEventListener("DOMContentLoaded",
                                                  this.onContentLoaded.bind(this));
        }
      }
      return 0;
    },

    onContentLoaded() {
      // The progress listener may call this multiple times, so be sure we only
      // run once.
      if (this.contentLoaded) {
        return;
      }
      try {
        if (!this.file) {
          // it's not saved to file yet, it's in the webshell

          // get a temporary filename using the attributes from the data object that
          // openInExternalEditor gave us
          this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
                                                        this.data.doc.contentType);

          // we have to convert from the source charset.
          var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
          var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                                   .createInstance(Components.interfaces.nsIFileOutputStream);
          foStream.init(this.file, 0x02 | 0x08 | 0x20, -1, 0); // write | create | truncate
          var coStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
                                   .createInstance(Components.interfaces.nsIConverterOutputStream);
          coStream.init(foStream, this.data.doc.characterSet);

          // write the source to the file
          coStream.writeString(webNavigation.document.body.textContent);

          // clean up
          coStream.close();
          foStream.close();

          let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
                              .getService(Components.interfaces.nsPIExternalAppLauncher);
          if (this.data.isPrivate) {
            // register the file to be deleted when possible
            helperService.deleteTemporaryPrivateFileWhenPossible(this.file);
          } else {
            // register the file to be deleted on app exit
            helperService.deleteTemporaryFileOnExit(this.file);
          }
        }

        var editorArgs = gViewSourceUtils.buildEditorArgs(this.file.path,
                                                          this.data.lineNumber);
        this.editor.runw(false, editorArgs, editorArgs.length);

        this.contentLoaded = true;
        gViewSourceUtils.handleCallBack(this.callBack, true, this.data);
      } catch (ex) {
        // we failed loading it with the external editor.
        Components.utils.reportError(ex);
        gViewSourceUtils.handleCallBack(this.callBack, false, this.data);
      } finally {
        this.destroy();
      }
    },

    webShell: null,
    editor: null,
    callBack: null,
    data: null,
    file: null
  },

  // returns an nsIFile for the passed document in the system temp directory
  getTemporaryFile(aURI, aDocument, aContentType) {
    // include contentAreaUtils.js in our own context when we first need it
    if (!this._caUtils) {
      var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                                   .getService(Components.interfaces.mozIJSSubScriptLoader);
      this._caUtils = {};
      scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js", this._caUtils);
    }

    var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                .getService(Components.interfaces.nsIProperties);
    var tempFile = fileLocator.get("TmpD", Components.interfaces.nsIFile);
    var fileName = this._caUtils.getDefaultFileName(null, aURI, aDocument, aContentType);
    var extension = this._caUtils.getDefaultExtension(fileName, aURI, aContentType);
    var leafName = this._caUtils.getNormalizedLeafName(fileName, extension);
    tempFile.append(leafName);
    return tempFile;
  }
}
PK
!<0chrome/toolkit/content/global/viewZoomOverlay.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/. */

/** Document Zoom Management Code
 *
 * To use this, you'll need to have a getBrowser() function or use the methods
 * that accept a browser to be modified.
 **/

var ZoomManager = {
  get _prefBranch() {
    delete this._prefBranch;
    return this._prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
                                        .getService(Components.interfaces.nsIPrefBranch);
  },

  get MIN() {
    delete this.MIN;
    return this.MIN = this._prefBranch.getIntPref("zoom.minPercent") / 100;
  },

  get MAX() {
    delete this.MAX;
    return this.MAX = this._prefBranch.getIntPref("zoom.maxPercent") / 100;
  },

  get useFullZoom() {
    return this._prefBranch.getBoolPref("browser.zoom.full");
  },

  set useFullZoom(aVal) {
    this._prefBranch.setBoolPref("browser.zoom.full", aVal);
    return aVal;
  },

  get zoom() {
    return this.getZoomForBrowser(getBrowser());
  },

  getZoomForBrowser: function ZoomManager_getZoomForBrowser(aBrowser) {
    let zoom = (this.useFullZoom || aBrowser.isSyntheticDocument)
               ? aBrowser.fullZoom : aBrowser.textZoom;
    // Round to remove any floating-point error.
    return Number(zoom.toFixed(2));
  },

  set zoom(aVal) {
    this.setZoomForBrowser(getBrowser(), aVal);
    return aVal;
  },

  setZoomForBrowser: function ZoomManager_setZoomForBrowser(aBrowser, aVal) {
    if (aVal < this.MIN || aVal > this.MAX)
      throw Components.results.NS_ERROR_INVALID_ARG;

    if (this.useFullZoom || aBrowser.isSyntheticDocument) {
      aBrowser.textZoom = 1;
      aBrowser.fullZoom = aVal;
    } else {
      aBrowser.textZoom = aVal;
      aBrowser.fullZoom = 1;
    }
  },

  get zoomValues() {
    var zoomValues = this._prefBranch.getCharPref("toolkit.zoomManager.zoomValues")
                                     .split(",").map(parseFloat);
    zoomValues.sort((a, b) => a - b);

    while (zoomValues[0] < this.MIN)
      zoomValues.shift();

    while (zoomValues[zoomValues.length - 1] > this.MAX)
      zoomValues.pop();

    delete this.zoomValues;
    return this.zoomValues = zoomValues;
  },

  enlarge: function ZoomManager_enlarge() {
    var i = this.zoomValues.indexOf(this.snap(this.zoom)) + 1;
    if (i < this.zoomValues.length)
      this.zoom = this.zoomValues[i];
  },

  reduce: function ZoomManager_reduce() {
    var i = this.zoomValues.indexOf(this.snap(this.zoom)) - 1;
    if (i >= 0)
      this.zoom = this.zoomValues[i];
  },

  reset: function ZoomManager_reset() {
    this.zoom = 1;
  },

  toggleZoom: function ZoomManager_toggleZoom() {
    var zoomLevel = this.zoom;

    this.useFullZoom = !this.useFullZoom;
    this.zoom = zoomLevel;
  },

  snap: function ZoomManager_snap(aVal) {
    var values = this.zoomValues;
    for (var i = 0; i < values.length; i++) {
      if (values[i] >= aVal) {
        if (i > 0 && aVal - values[i - 1] < values[i] - aVal)
          i--;
        return values[i];
      }
    }
    return values[i - 1];
  }
};
PK
!<e))%chrome/toolkit/content/global/win.xul<?xml version="1.0"?>
<window id='win'/>
PK
!<jhh2chrome/toolkit/content/global/xml/XMLMonoPrint.css@charset "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/. */

@import url("chrome://global/content/xml/XMLPrettyPrint.css");

#top > .expander-open {
  font-family: monospace;
  white-space: pre;
}
PK
!<r4chrome/toolkit/content/global/xml/XMLPrettyPrint.css@charset "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/. */

@import url("resource://gre-resources/viewsource.css");

#header {
  background-color: #ccc;
  border-bottom: 3px solid black;
  padding: 0.5em;
  margin-bottom: 1em;
}

.expander-content {
  padding-left: 1em;
}

.expander {
  cursor: pointer;
  -moz-user-select: none;
  text-align: center;
  vertical-align: top;
  width: 1em;
  display: inline-block;
  margin-left: -1em;
}

#top > .expander-open, #top > .expander-closed {
  margin-left: 1em;
}

.expander-closed > .expander-content {
  display: none;
}

.comment {
  font-family: monospace;
  white-space: pre;
}

PK
!<Z2ii4chrome/toolkit/content/global/xml/XMLPrettyPrint.xml<?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 xmlns="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml">

  <binding id="prettyprint" bindToUntrustedContent="true">

    <content><html:div id='top' class="highlight"/>
      <html:span style="display: none;"><children/></html:span>
    </content>

    <handlers>
      <handler event="click" button="0">
      <![CDATA[
        try {
          var par = event.originalTarget;
          if (par.nodeName == 'div' && par.className == 'expander') {
            if (par.parentNode.className == 'expander-closed') {
              par.parentNode.className = 'expander-open';
              event.originalTarget.firstChild.data = '\u2212';
            }
            else {
              par.parentNode.className = 'expander-closed';
              event.originalTarget.firstChild.data = '+';
            }
          }
        } catch (e) {
        }
      ]]>
      </handler>
      <handler event="prettyprint-dom-created" allowuntrusted="false">
        <![CDATA[
          document.getAnonymousNodes(this).item(0).appendChild(event.detail);
        ]]>
      </handler>
    </handlers>

  </binding>

</bindings>
PK
!<)\4chrome/toolkit/content/global/xml/XMLPrettyPrint.xsl<?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 % prettyPrintDTD SYSTEM "chrome://global/locale/xml/prettyprint.dtd">
  %prettyPrintDTD;
  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
  %globalDTD;
]>

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns="http://www.w3.org/1999/xhtml">

  <xsl:output method="xml"/>

  <xsl:template match="/">
    <link href="chrome://global/content/xml/XMLPrettyPrint.css" type="text/css" rel="stylesheet"/>
    <link title="Monospace" href="chrome://global/content/xml/XMLMonoPrint.css" type="text/css" rel="alternate stylesheet"/>
    <div id="header" dir="&locale.dir;">
      <p>
        &xml.nostylesheet;
      </p>
    </div>
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="*">
    <div>
      <xsl:text>&lt;</xsl:text>
      <span class="start-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:apply-templates select="@*"/>
      <xsl:text>/&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="*[node()]">
    <div>
      <xsl:text>&lt;</xsl:text>
      <span class="start-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:apply-templates select="@*"/>
      <xsl:text>&gt;</xsl:text>

      <span class="text"><xsl:value-of select="."/></span>

      <xsl:text>&lt;/</xsl:text>
      <span class="end-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:text>&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="*[* or processing-instruction() or comment() or string-length(.) &gt; 50]">
    <div class="expander-open">
      <xsl:call-template name="expander"/>

      <xsl:text>&lt;</xsl:text>
      <span class="start-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:apply-templates select="@*"/>
      <xsl:text>&gt;</xsl:text>

      <div class="expander-content"><xsl:apply-templates/></div>

      <xsl:text>&lt;/</xsl:text>
      <span class="end-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:text>&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:text> </xsl:text>
    <span class="attribute-name"><xsl:value-of select="name(.)"/></span>
    <xsl:text>=</xsl:text>
    <span class="attribute-value">"<xsl:value-of select="."/>"</span>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:if test="normalize-space(.)">
      <xsl:value-of select="."/>
    </xsl:if>
  </xsl:template>

  <xsl:template match="processing-instruction()">
    <div class="pi">
      <xsl:text>&lt;?</xsl:text>
      <xsl:value-of select="name(.)"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>?&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="processing-instruction()[string-length(.) &gt; 50]">
    <div class="expander-open">
      <xsl:call-template name="expander"/>

      <span class="pi">
        <xsl:text> &lt;?</xsl:text>
        <xsl:value-of select="name(.)"/>
      </span>
      <div class="expander-content pi"><xsl:value-of select="."/></div>
      <span class="pi">
        <xsl:text>?&gt;</xsl:text>
      </span>
    </div>
  </xsl:template>

  <xsl:template match="comment()">
    <div class="comment">
      <xsl:text>&lt;!--</xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>--&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="comment()[string-length(.) &gt; 50]">
    <div class="expander-open">
      <xsl:call-template name="expander"/>

      <span class="comment">
        <xsl:text>&lt;!--</xsl:text>
      </span>
      <div class="expander-content comment">
        <xsl:value-of select="."/>
      </div>
      <span class="comment">
        <xsl:text>--&gt;</xsl:text>
      </span> 
    </div>
  </xsl:template>
  
  <xsl:template name="expander">
    <div class="expander">&#x2212;</div>
  </xsl:template>

</xsl:stylesheet>
PK
!<cmm%chrome/toolkit/content/global/xul.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A minimal set of rules for the XUL elements that may be implicitly created
 * as part of HTML/SVG documents (e.g. scrollbars) can be found over in
 * minimal-xul.css.  Rules for everything else related to XUL can be found in
 * this file.  Make sure you choose the correct style sheet when adding new
 * rules.  (This split of the XUL rules is to minimize memory use and improve
 * performance in HTML/SVG documents.)
 *
 * This file should also not contain any app specific styling.  Defaults for
 * widgets of a particular application should be in that application's style
 * sheet.  For example, style definitions for navigator can be found in
 * navigator.css.
 *
 * THIS FILE IS LOCKED DOWN.  YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST
 * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com
 */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
@namespace xbl url("http://www.mozilla.org/xbl"); /* namespace for XBL elements */

:root {
  --animation-easing-function: cubic-bezier(.07, .95, 0, 1);
}

/* ::::::::::
   :: Rules for 'hiding' portions of the chrome for special
   :: kinds of windows (not JUST browser windows) with toolbars
   ::::: */

window[chromehidden~="menubar"] .chromeclass-menubar,
window[chromehidden~="directories"] .chromeclass-directories,
window[chromehidden~="status"] .chromeclass-status,
window[chromehidden~="extrachrome"] .chromeclass-extrachrome,
window[chromehidden~="location"] .chromeclass-location,
window[chromehidden~="location"][chromehidden~="toolbar"] .chromeclass-toolbar,
window[chromehidden~="toolbar"] .chromeclass-toolbar-additional {
  display: none;
}

/* ::::::::::
   :: Rules for forcing direction for entry and display of URIs
   :: or URI elements
   ::::: */

.uri-element {
  direction: ltr !important;
}

/****** elements that have no visual representation ******/

script, data,
commands, commandset, command,
broadcasterset, broadcaster, observes,
keyset, key, toolbarpalette, toolbarset,
template, rule, conditions, action,
bindings, binding, content, member, triple,
treechildren, treeitem, treeseparator, treerow, treecell {
  display: none;
}

xbl|children {
  display: none !important;
}

/********** focus rules **********/

button,
checkbox,
colorpicker[type="button"],
datepicker[type="grid"],
menulist,
radiogroup,
tree,
browser,
editor,
iframe {
  -moz-user-focus: normal;
}

menulist[editable="true"] {
  -moz-user-focus: ignore;
}

/******** window & page ******/

window,
page {
  overflow: -moz-hidden-unscrollable;
  -moz-box-orient: vertical;
}

/******** box *******/

vbox {
  -moz-box-orient: vertical;
}

bbox {
  -moz-box-align: baseline;
}

/********** button **********/

button {
  -moz-binding: url("chrome://global/content/bindings/button.xml#button");
}

button[type="repeat"] {
  -moz-binding: url("chrome://global/content/bindings/button.xml#button-repeat");
}

button[type="menu"], button[type="panel"] {
  -moz-binding: url("chrome://global/content/bindings/button.xml#menu");
}

button[type="menu-button"] {
  -moz-binding: url("chrome://global/content/bindings/button.xml#menu-button");
}

/********** toolbarbutton **********/

toolbarbutton {
  -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton");
}

toolbarbutton.badged-button > toolbarbutton,
toolbarbutton.badged-button {
  -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged");
}

.toolbarbutton-badge:not([value]),
.toolbarbutton-badge[value=""] {
  display: none;
}

toolbarbutton[type="menu"],
toolbarbutton[type="panel"] {
  -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu");
}

toolbarbutton.badged-button[type="menu"],
toolbarbutton.badged-button[type="panel"] {
  -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged-menu");
}

toolbarbutton[type="menu-button"] {
  -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button");
}

toolbar[mode="icons"] .toolbarbutton-text,
toolbar[mode="icons"] .toolbarbutton-multiline-text,
toolbar[mode="text"] .toolbarbutton-icon,
.toolbarbutton-multiline-text:not([wrap="true"]),
.toolbarbutton-text[wrap="true"] {
  display: none;
  -moz-binding: none;
}

/******** browser, editor, iframe ********/

browser,
editor,
iframe {
  display: inline;
}

browser {
  -moz-binding: url("chrome://global/content/bindings/browser.xml#browser");
}

browser[remote=true]:not(.lightweight) {
  -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
}

editor {
  -moz-binding: url("chrome://global/content/bindings/editor.xml#editor");
}

iframe {
  -moz-binding: url("chrome://global/content/bindings/general.xml#iframe");
}

/********** notifications **********/

notificationbox {
  -moz-binding: url("chrome://global/content/bindings/notification.xml#notificationbox");
  -moz-box-orient: vertical;
}

.notificationbox-stack {
  overflow: -moz-hidden-unscrollable;
}

notification {
  -moz-binding: url("chrome://global/content/bindings/notification.xml#notification");
}

notification.animated {
  transition: margin-top 300ms var(--animation-easing-function), opacity 300ms var(--animation-easing-function);
}

/*********** popup notification ************/
popupnotification {
  -moz-binding: url("chrome://global/content/bindings/notification.xml#popup-notification");
}

.popup-notification-menubutton:not([label]) {
  display: none;
}

/********** image **********/

image {
  -moz-binding: url("chrome://global/content/bindings/general.xml#image");
}

/********** checkbox **********/

checkbox {
  -moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox");
}

/********** radio **********/

radiogroup {
  -moz-binding: url("chrome://global/content/bindings/radio.xml#radiogroup");
  -moz-box-orient: vertical;
}

radio {
  -moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
}

/******** groupbox *********/

groupbox {
  -moz-binding: url("chrome://global/content/bindings/groupbox.xml#groupbox");
  display: -moz-groupbox;
}

caption {
  -moz-binding: url("chrome://global/content/bindings/groupbox.xml#caption");
}

.groupbox-body {
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  -moz-box-orient: vertical;
}

/******** draggable elements *********/

windowdragbox {
  -moz-window-dragging: drag;
}

/* The list below is non-comprehensive and will probably need some tweaking. */
toolbaritem,
toolbarbutton,
button,
textbox,
tab,
radio,
splitter,
scale,
menulist {
  -moz-window-dragging: no-drag;
}

/******* toolbar *******/

toolbox {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbox");
  -moz-box-orient: vertical;
}

toolbar {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar");
}

toolbar[customizing="true"][collapsed="true"] {
  /* Some apps, e.g. Firefox, use 'collapsed' to hide toolbars.
     Override it while customizing. */
  visibility: visible;
}

toolbar[customizing="true"][hidden="true"] {
  /* Some apps, e.g. SeaMonkey, use 'hidden' to hide toolbars.
     Override it while customizing. */
  display: -moz-box;
}

toolbar[type="menubar"][autohide="true"] {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-menubar-autohide");
  overflow: hidden;
}

toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true"]) {
  min-height: 0 !important;
  height: 0 !important;
  -moz-appearance: none !important;
  border-style: none !important;
}

toolbarseparator {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
}

toolbarspacer {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
}

toolbarspring {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
  -moz-box-flex: 1000;
}

toolbarpaletteitem {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem");
}

toolbarpaletteitem[place="palette"] {
  -moz-box-orient: vertical;
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem-palette");
}

/********* menubar ***********/

menubar {
  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#menubar");
}

/********* menu ***********/

menubar > menu {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar");
}

menubar > menu.menu-iconic {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar-iconic");
}

menu {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menu");
}

menu.menu-iconic {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-iconic");
}

menubar > menu:empty {
  visibility: collapse;
}

/********* menuitem ***********/

menuitem {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem");
}

menuitem.menuitem-iconic {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
}

menuitem[description] {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-desc-noaccel");
}

menuitem[type="checkbox"],
menuitem[type="radio"] {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
}

menuitem.menuitem-non-iconic {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menubutton-item");
}

menucaption {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption");
}

.menu-text {
  -moz-box-flex: 1;
}

/********* menuseparator ***********/

menuseparator {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuseparator");
}

/********* popup & menupopup ***********/

/* <popup> is deprecated.  Only <menupopup> and <tooltip> are still valid. */

popup,
menupopup {
  -moz-binding: url("chrome://global/content/bindings/popup.xml#popup");
  -moz-box-orient: vertical;
}

panel {
  -moz-binding: url("chrome://global/content/bindings/popup.xml#panel");
  -moz-box-orient: vertical;
}

popup,
menupopup,
panel,
tooltip {
  display: -moz-popup;
  z-index: 2147483647;
  text-shadow: none;
}

tooltip {
  -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip");
  -moz-box-orient: vertical;
  white-space: pre-wrap;
  margin-top: 21px;
}

panel[type="arrow"] {
  -moz-binding: url("chrome://global/content/bindings/popup.xml#arrowpanel");
}


panel[type="arrow"]:not([animate="false"]) {
  transform: scale(.4);
  opacity: 0;
  transition-property: transform, opacity;
  transition-duration: 0.15s;
  transition-timing-function: ease-out;
}

panel[type="arrow"][animate="open"] {
  transform: none;
  opacity: 1.0;
}

panel[type="arrow"][animate="cancel"] {
  transform: none;
}

panel[arrowposition="after_start"]:-moz-locale-dir(ltr),
panel[arrowposition="after_end"]:-moz-locale-dir(rtl) {
  transform-origin: 20px top;
}

panel[arrowposition="after_end"]:-moz-locale-dir(ltr),
panel[arrowposition="after_start"]:-moz-locale-dir(rtl) {
  transform-origin: calc(100% - 20px) top;
}

panel[arrowposition="before_start"]:-moz-locale-dir(ltr),
panel[arrowposition="before_end"]:-moz-locale-dir(rtl) {
  transform-origin: 20px bottom;
}

panel[arrowposition="before_end"]:-moz-locale-dir(ltr),
panel[arrowposition="before_start"]:-moz-locale-dir(rtl) {
  transform-origin: calc(100% - 20px) bottom;
}

panel[arrowposition="start_before"]:-moz-locale-dir(ltr),
panel[arrowposition="end_before"]:-moz-locale-dir(rtl) {
  transform-origin: right 20px;
}

panel[arrowposition="start_after"]:-moz-locale-dir(ltr),
panel[arrowposition="end_after"]:-moz-locale-dir(rtl) {
  transform-origin: right calc(100% - 20px);
}

panel[arrowposition="end_before"]:-moz-locale-dir(ltr),
panel[arrowposition="start_before"]:-moz-locale-dir(rtl) {
  transform-origin: left 20px;
}

panel[arrowposition="end_after"]:-moz-locale-dir(ltr),
panel[arrowposition="start_after"]:-moz-locale-dir(rtl) {
  transform-origin: left calc(100% - 20px);
}


window[sizemode="maximized"] statusbarpanel.statusbar-resizerpanel {
  visibility: collapse;
}

/******** grid **********/

grid {
  display: -moz-grid;
}

rows,
columns {
  display: -moz-grid-group;
}

row,
column {
  display: -moz-grid-line;
}

rows {
  -moz-box-orient: vertical;
}

column {
  -moz-box-orient: vertical;
}

/******** listbox **********/

listbox {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listbox");
}

listhead {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listhead");
}

listrows {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listrows");
}

listitem {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem");
}

listitem[type="checkbox"] {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox");
}

listheader {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listheader");
  -moz-box-ordinal-group: 2147483646;
}

listcell {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell");
}

listcell[type="checkbox"] {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox");
}

.listitem-iconic {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-iconic");
}

listitem[type="checkbox"].listitem-iconic {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox-iconic");
}

.listcell-iconic {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-iconic");
}

listcell[type="checkbox"].listcell-iconic {
  -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox-iconic");
}

listbox {
  display: -moz-grid;
}

listbox[rows] {
  height: auto;
}

listcols, listhead, listrows, listboxbody {
  display: -moz-grid-group;
}

listcol, listitem, listheaditem {
  display: -moz-grid-line;
}

listbox {
  -moz-user-focus: normal;
  -moz-box-orient: vertical;
  min-width: 0px;
  min-height: 0px;
  width: 200px;
  height: 200px;
}

listhead {
  -moz-box-orient: vertical;
}

listrows {
  -moz-box-orient: vertical;
  -moz-box-flex: 1;
}

listboxbody {
  -moz-box-orient: vertical;
  -moz-box-flex: 1;
  /* Don't permit a horizontal scrollbar. See bug 285449 */
  overflow-x: hidden !important;
  overflow-y: auto;
  min-height: 0px;
}

listcol {
  -moz-box-orient: vertical;
  min-width: 16px;
}

listcell {
  -moz-box-align: center;
}

/******** tree ******/

tree {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#tree");
}

treecols {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treecols");
}

treecol {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol");
  -moz-box-ordinal-group: 2147483646;
}

treecol.treecol-image {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol-image");
}

tree > treechildren {
  display: -moz-box;
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treebody");
  -moz-user-select: none;
  -moz-box-flex: 1;
}

treerows {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treerows");
}

treecolpicker {
  -moz-binding: url("chrome://global/content/bindings/tree.xml#columnpicker");
}

tree {
  -moz-box-orient: vertical;
  min-width: 0px;
  min-height: 0px;
  width: 10px;
  height: 10px;
}

tree[hidecolumnpicker="true"] > treecols > treecolpicker {
  display: none;
}

treecol {
  min-width: 16px;
}

treecol[hidden="true"] {
  visibility: collapse;
  display: -moz-box;
}

.tree-scrollable-columns {
  /* Yes, Virginia, this makes it scrollable */
  overflow: hidden;
}

/* ::::: lines connecting cells ::::: */
tree:not([treelines="true"]) > treechildren::-moz-tree-line {
  visibility: hidden;
}

treechildren::-moz-tree-cell(ltr) {
  direction: ltr !important;
}

/********** deck & stack *********/

deck {
  display: -moz-deck;
  -moz-binding: url("chrome://global/content/bindings/general.xml#deck");
}

stack, bulletinboard {
  display: -moz-stack;
}

/********** tabbox *********/

tabbox {
  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabbox");
  -moz-box-orient: vertical;
}

tabs {
  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabs");
  -moz-box-orient: horizontal;
}

tab {
  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tab");
  -moz-box-align: center;
  -moz-box-pack: center;
}

tab[selected="true"]:not([ignorefocus="true"]) {
  -moz-user-focus: normal;
}

tabpanels {
  -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabpanels");
  display: -moz-deck;
}

/********** progressmeter **********/

progressmeter {
  -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter");
}

/********** basic rule for anonymous content that needs to pass box properties through
 ********** to an insertion point parent that holds the real kids **************/

.box-inherit {
  -moz-box-orient: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  -moz-box-direction: inherit;
}

/********** textbox **********/

textbox {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#textbox");
  -moz-user-select: text;
  text-shadow: none;
}

textbox[multiline="true"] {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#textarea");
}

.textbox-input-box {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box");
}

html|textarea.textbox-textarea {
  resize: none;
}

textbox[resizable="true"] > .textbox-input-box > html|textarea.textbox-textarea {
  resize: both;
}

.textbox-input-box[spellcheck="true"] {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell");
}

textbox[type="timed"] {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#timed-textbox");
}

textbox[type="search"] {
  -moz-binding: url("chrome://global/content/bindings/textbox.xml#search-textbox");
}

textbox[type="number"] {
  -moz-binding: url("chrome://global/content/bindings/numberbox.xml#numberbox");
}

.textbox-contextmenu:-moz-locale-dir(rtl) {
  direction: rtl;
}

/********** autocomplete textbox **********/

/* SeaMonkey does not use the new toolkit's autocomplete widget */

textbox[type="autocomplete"] {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete");
}

panel[type="autocomplete"] {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup");
}

panel[type="autocomplete-richlistbox"] {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup");
}

/* FIXME: bug 616258 */

.autocomplete-tree {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-tree");
  -moz-user-focus: ignore;
}

.autocomplete-treebody {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treebody");
}

.autocomplete-richlistbox {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistbox");
  -moz-user-focus: ignore;
}

.autocomplete-richlistbox > scrollbox {
  overflow-x: hidden !important;
}

.autocomplete-richlistitem {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem");
  -moz-box-orient: vertical;
  overflow: -moz-hidden-unscrollable;
}

.autocomplete-treerows {
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treerows");
}

.autocomplete-history-dropmarker {
  display: none;
}

.autocomplete-history-dropmarker[enablehistory="true"] {
  display: -moz-box;
  -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#history-dropmarker");
}


/* the C++ implementation of widgets is too eager to make popups visible.
   this causes problems (bug 120155 and others), thus this workaround: */
popup[type="autocomplete"][hidden="true"] {
  visibility: hidden;
}

/* The following rule is here to fix bug 96899 (and now 117952).
   Somehow trees create a situation
   in which a popupset flows itself as if its popup child is directly within it
   instead of the placeholder child that should actually be inside the popupset.
   This is a stopgap measure, and it does not address the real bug.  */
.autocomplete-result-popupset {
  max-width: 0px;
  width: 0 !important;
  min-width: 0%;
  min-height: 0%;
}

/********** colorpicker **********/

colorpicker {
  -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker");
}

colorpicker[type="button"] {
  -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker-button");
}

.colorpickertile {
  -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpickertile");
}

/********** menulist **********/

menulist {
  -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist");
}

menulist[popuponly="true"] {
  -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-popuponly");
  -moz-appearance: none !important;
  margin: 0 !important;
  height: 0 !important;
  min-height: 0 !important;
  border: 0 !important;
}

menulist[editable="true"] {
  -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-editable");
}

menulist[type="description"] {
  -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-description");
}

menulist > menupopup > menuitem {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-noaccel");
}

menulist > menupopup > menucaption {
  -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption-inmenulist");
}

dropmarker {
  -moz-binding: url("chrome://global/content/bindings/general.xml#dropmarker");
}

/********** splitter **********/

splitter {
  -moz-binding: url("chrome://global/content/bindings/splitter.xml#splitter");
}

grippy {
  -moz-binding: url("chrome://global/content/bindings/splitter.xml#grippy");
}

.tree-splitter {
  width: 0px;
  max-width: 0px;
  min-width: 0% ! important;
  min-height: 0% ! important;
  -moz-box-ordinal-group: 2147483646;
}

/******** scrollbar ********/

slider {
  /* This is a hint to layerization that the scrollbar thumb can never leave
     the scrollbar track. */
  overflow: hidden;
}

/******** scrollbox ********/

scrollbox {
  -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#scrollbox");
  /* This makes it scrollable! */
  overflow: hidden;
}

arrowscrollbox {
  -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox");
}

arrowscrollbox[clicktoscroll="true"] {
  -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll");
}

autorepeatbutton {
  -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#autorepeatbutton");
}

/********** statusbar **********/

statusbar {
  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbar");
}

statusbarpanel {
  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel");
}

.statusbarpanel-iconic {
  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic");
}

.statusbarpanel-iconic-text {
  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic-text");
}

.statusbarpanel-menu-iconic {
  -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-menu-iconic");
}

/********** spinbuttons ***********/

spinbuttons {
  -moz-binding: url("chrome://global/content/bindings/spinbuttons.xml#spinbuttons");
}

.spinbuttons-button {
  -moz-user-focus: ignore;
}

/********** stringbundle **********/

stringbundleset {
  -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundleset");
  visibility: collapse;
}

stringbundle {
  -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundle");
  visibility: collapse;
}

/********** dialog **********/

dialog,
dialog:root /* override :root from above */ {
  -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialog");
  -moz-box-orient: vertical;
}

dialogheader {
  -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialogheader");
}

/********* page ************/

page {
  -moz-box-orient: vertical;
}

/********** wizard **********/

wizard,
wizard:root /* override :root from above */ {
  -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard");
  -moz-box-orient: vertical;
  width: 40em;
  height: 30em;
}

wizardpage {
  -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizardpage");
  -moz-box-orient: vertical;
  overflow: auto;
}

.wizard-header {
  -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-header");
}

.wizard-buttons {
  -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-buttons");
}

/********** preferences ********/

prefwindow,
prefwindow:root /* override :root from above */ {
  -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefwindow");
  -moz-box-orient: vertical;
}

prefpane {
  -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefpane");
  -moz-box-orient: vertical;
}

prefwindow > .paneDeckContainer {
  overflow: hidden;
}

prefpane > .content-box {
  overflow: hidden;
}

prefwindow[type="child"] > .paneDeckContainer {
  overflow: -moz-hidden-unscrollable;
}

prefwindow[type="child"] > prefpane > .content-box {
  -moz-box-flex: 1;
  overflow: -moz-hidden-unscrollable;
}

preferences {
  -moz-binding: url("chrome://global/content/bindings/preferences.xml#preferences");
  visibility: collapse;
}

preference {
  -moz-binding: url("chrome://global/content/bindings/preferences.xml#preference");
  visibility: collapse;
}

radio[pane] {
  -moz-binding: url("chrome://global/content/bindings/preferences.xml#panebutton") !important;
  -moz-box-orient: vertical;
  -moz-box-align: center;
}

prefwindow[chromehidden~="toolbar"] .chromeclass-toolbar {
  display: none;
}

/********** expander ********/

expander {
  -moz-binding: url("chrome://global/content/bindings/expander.xml#expander");
  -moz-box-orient: vertical;
}


/********** Rich Listbox ********/

richlistbox {
  -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistbox');
  -moz-user-focus: normal;
  -moz-box-orient: vertical;
}

richlistitem {
  -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistitem');
}

richlistbox > listheader {
  -moz-box-ordinal-group: 1;
}

/********** datepicker and timepicker ********/

datepicker {
  -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker');
}

datepicker[type="popup"] {
  -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-popup');
}

datepicker[type="grid"] {
  -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-grid');
}

timepicker {
  -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#timepicker');
}


/*********** findbar ************/
findbar {
  -moz-binding: url('chrome://global/content/bindings/findbar.xml#findbar');
}

.findbar-textbox {
  -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox");
}


/*********** filefield ************/
filefield {
  -moz-binding: url("chrome://global/content/bindings/filefield.xml#filefield");
}

/*********** tabmodalprompt ************/
tabmodalprompt {
  -moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt");
  overflow: hidden;
  text-shadow: none;
}

.button-highlightable-text:not([highlightable="true"]),
.button-text[highlightable="true"],
.menulist-highlightable-label:not([highlightable="true"]),
.menulist-label[highlightable="true"],
.menu-iconic-highlightable-text:not([highlightable="true"]),
.menu-iconic-text[highlightable="true"] {
  display: none;
}
PK
!<B%

?chrome/toolkit/content/mozapps/downloads/unknownContentType.xul<?xml version="1.0"?>

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/downloads/unknownContentType.css" type="text/css"?>

<!DOCTYPE dialog [
  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
  %brandDTD;
  <!ENTITY % uctDTD SYSTEM "chrome://mozapps/locale/downloads/unknownContentType.dtd" >
  %uctDTD;
  <!ENTITY % scDTD SYSTEM "chrome://mozapps/locale/downloads/settingsChange.dtd" >
  %scDTD;
]>            

<dialog id="unknownContentType"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
        style="width: 36em;"
        screenX="" screenY=""
        persist="screenX screenY"
        aria-describedby="intro location whichIs type from source unknownPrompt"
        ondialogaccept="return dialog.onOK()"
        ondialogcancel="return dialog.onCancel()">


  <stringbundle id="strings" src="chrome://mozapps/locale/downloads/unknownContentType.properties"/>

  <vbox flex="1" id="container">  
    <description id="intro">&intro2.label;</description>
    <separator class="thin"/>
    <hbox align="start" class="small-indent">
      <image id="contentTypeImage"/>
      <vbox flex="1">
        <description id="location" class="plain" crop="start" flex="1"/>
        <separator class="thin"/>
        <hbox align="center">
          <label id="whichIs" value="&whichIs.label;"/>
          <textbox id="type" class="plain" readonly="true" flex="1" noinitialfocus="true"/>
        </hbox>
        <hbox align="center">
          <label value="&from.label;" id="from"/>
          <description id="source" class="plain" crop="start" flex="1"/>
        </hbox>
      </vbox>
    </hbox>
    
    <separator class="thin"/>

    <hbox align="center" id="basicBox" collapsed="true">
      <label id="unknownPrompt" value="&unknownPromptText.label;" flex="1"/>
    </hbox>

    <groupbox flex="1" id="normalBox">
      <caption label="&actionQuestion.label;"/>
      <separator class="thin"/>
      <radiogroup id="mode" class="small-indent">
        <hbox>
          <radio id="open" label="&openWith.label;" accesskey="&openWith.accesskey;"/>
          <deck id="modeDeck" flex="1">
            <hbox id="openHandlerBox" flex="1" align="center"/>
            <hbox flex="1" align="center">
              <button id="chooseButton" oncommand="dialog.chooseApp();"
                      label="&chooseHandler.label;" accesskey="&chooseHandler.accesskey;"/>
            </hbox>
          </deck>
        </hbox>
        
        <radio id="save" label="&saveFile.label;" accesskey="&saveFile.accesskey;"/>
      </radiogroup> 
      <separator class="thin"/>
      <hbox class="small-indent">
        <checkbox id="rememberChoice" label="&rememberChoice.label;"
                  accesskey="&rememberChoice.accesskey;"
                  oncommand="dialog.toggleRememberChoice(event.target);"/>
      </hbox>
      
      <separator/>
      <description id="settingsChange" hidden="true">&settingsChangeOptions.label;</description>
      <separator class="thin"/>
    </groupbox>
  </vbox>
  
  <menulist id="openHandler" flex="1">
    <menupopup id="openHandlerPopup" oncommand="dialog.openHandlerCommand();">
      <menuitem id="defaultHandler" default="true" crop="right"/>
      <menuitem id="otherHandler" hidden="true" crop="left"/>
      <menuseparator/>
      <menuitem id="choose" label="&other.label;"/>
    </menupopup>
  </menulist>
</dialog>
PK
!<@T??>chrome/toolkit/content/mozapps/extensions/OpenH264-license.txt-------------------------------------------------------
About The Cisco-Provided Binary of OpenH264 Video Codec
-------------------------------------------------------

Cisco provides this program under the terms of the BSD license.  

Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.  

As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk.  Your rights from Cisco under the BSD license are not affected by this choice.  

For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary 

A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org

-----------
BSD License
-----------

Copyright © 2014 Cisco Systems, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-----------------------------------------
AVC/H.264 Patent Portfolio License Notice
-----------------------------------------

The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: 

THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO.  NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE.  ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM

Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla

---------------------------------------------
AVC/H.264 Patent Portfolio License Conditions
---------------------------------------------

In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:

1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;

2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;

3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:

       "OpenH264 Video Codec provided by Cisco Systems, Inc."

4.  Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.  
 


                          v1.0
PK
!<f

2chrome/toolkit/content/mozapps/extensions/about.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";

/* import-globals-from ../../../content/contentAreaUtils.js */

/* exported init, loadHomepage */

var Cu = Components.utils;
Cu.import("resource://gre/modules/AddonManager.jsm");

function init() {
  var addon = window.arguments[0];
  var extensionsStrings = document.getElementById("extensionsStrings");

  document.documentElement.setAttribute("addontype", addon.type);

  var iconURL = AddonManager.getPreferredIconURL(addon, 48, window);
  if (iconURL) {
    var extensionIcon = document.getElementById("extensionIcon");
    extensionIcon.src = iconURL;
  }

  document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]);
  var extensionName = document.getElementById("extensionName");
  extensionName.textContent = addon.name;

  var extensionVersion = document.getElementById("extensionVersion");
  if (addon.version)
    extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version]));
  else
    extensionVersion.hidden = true;

  var extensionDescription = document.getElementById("extensionDescription");
  if (addon.description)
    extensionDescription.textContent = addon.description;
  else
    extensionDescription.hidden = true;

  var numDetails = 0;

  var extensionCreator = document.getElementById("extensionCreator");
  if (addon.creator) {
    extensionCreator.setAttribute("value", addon.creator);
    numDetails++;
  } else {
    extensionCreator.hidden = true;
    var extensionCreatorLabel = document.getElementById("extensionCreatorLabel");
    extensionCreatorLabel.hidden = true;
  }

  var extensionHomepage = document.getElementById("extensionHomepage");
  var homepageURL = addon.homepageURL;
  if (homepageURL) {
    extensionHomepage.setAttribute("homepageURL", homepageURL);
    extensionHomepage.setAttribute("tooltiptext", homepageURL);
    numDetails++;
  } else {
    extensionHomepage.hidden = true;
  }

  numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers);
  numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators);
  numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors);

  if (numDetails == 0) {
    var groove = document.getElementById("groove");
    groove.hidden = true;
    var extensionDetailsBox = document.getElementById("extensionDetailsBox");
    extensionDetailsBox.hidden = true;
  }

  var acceptButton = document.documentElement.getButton("accept");
  acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton");

  setTimeout(sizeToContent, 0);
}

function appendToList(aHeaderId, aNodeId, aItems) {
  var header = document.getElementById(aHeaderId);
  var node = document.getElementById(aNodeId);

  if (!aItems || aItems.length == 0) {
    header.hidden = true;
    return 0;
  }

  for (let currentItem of aItems) {
    var label = document.createElement("label");
    label.textContent = currentItem;
    label.setAttribute("class", "contributor");
    node.appendChild(label);
  }

  return aItems.length;
}

function loadHomepage(aEvent) {
  window.close();
  openURL(aEvent.target.getAttribute("homepageURL"));
}
PK
!<a3chrome/toolkit/content/mozapps/extensions/about.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 
<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css" type="text/css"?> 

<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/extensions/about.dtd">

<dialog id="genericAbout"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="init();"
        buttons="accept"
        buttoniconaccept="close"
        onaccept="close();">

  <script type="application/javascript" src="chrome://mozapps/content/extensions/about.js"/>
  <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>

  <stringbundleset id="aboutSet">
    <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
  </stringbundleset>

  <vbox id="clientBox" flex="1">
    <hbox class="basic-info">
      <vbox pack="center">
        <image id="extensionIcon"/>
      </vbox>
      <vbox flex="1">
        <label id="extensionName"/>
        <label id="extensionVersion" crop="end"/>
      </vbox>
    </hbox>
    <description id="extensionDescription" class="boxIndent"/>

    <separator id="groove" class="groove"/>

    <vbox id="extensionDetailsBox" flex="1">
      <label id="extensionCreatorLabel" class="sectionTitle">&creator.label;</label>
      <hbox id="creatorBox" class="boxIndent">
        <label id="extensionCreator" flex="1" crop="end"/>
        <label id="extensionHomepage" onclick="if (event.button == 0) { loadHomepage(event); }"
               class="text-link" value="&homepage.label;"/>
      </hbox>
  
      <label id="extensionDevelopers" class="sectionTitle">&developers.label;</label>
      <vbox flex="1" id="developersBox" class="boxIndent"/>
      <label id="extensionTranslators" class="sectionTitle">&translators.label;</label>
      <vbox flex="1" id="translatorsBox" class="boxIndent"/>
      <label id="extensionContributors" class="sectionTitle">&contributors.label;</label>
      <vbox flex="1" id="contributorsBox" class="boxIndent"/>
    </vbox>
  </vbox>

</dialog>
PK
!<X7chrome/toolkit/content/mozapps/extensions/blocklist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.hardBlockedAddon {
  -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#hardblockedaddon");
}

.softBlockedAddon {
  -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#softblockedaddon");
}
PK
!<
2		6chrome/toolkit/content/mozapps/extensions/blocklist.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";

/* exported init, finish */

Components.utils.import("resource://gre/modules/Services.jsm");

var gArgs;

function init() {
  var hasHardBlocks = false;
  var hasSoftBlocks = false;
  gArgs = window.arguments[0].wrappedJSObject;

  // NOTE: We use strings from the "updates.properties" bundleset to change the
  // text on the "Cancel" button to "Restart Later". (bug 523784)
  let bundle = Services.strings.
              createBundle("chrome://mozapps/locale/update/updates.properties");
  let cancelButton = document.documentElement.getButton("cancel");
  cancelButton.setAttribute("label", bundle.GetStringFromName("restartLaterButton"));
  cancelButton.setAttribute("accesskey",
                            bundle.GetStringFromName("restartLaterButton.accesskey"));

  var richlist = document.getElementById("addonList");
  var list = gArgs.list;
  list.sort((a, b) => String(a.name).localeCompare(b.name));
  for (let listItem of list) {
    let item = document.createElement("richlistitem");
    item.setAttribute("name", listItem.name);
    item.setAttribute("version", listItem.version);
    item.setAttribute("icon", listItem.icon);
    if (listItem.blocked) {
      item.setAttribute("class", "hardBlockedAddon");
      hasHardBlocks = true;
    } else {
      item.setAttribute("class", "softBlockedAddon");
      hasSoftBlocks = true;
    }
    richlist.appendChild(item);
  }

  if (hasHardBlocks && hasSoftBlocks)
    document.getElementById("bothMessage").hidden = false;
  else if (hasHardBlocks)
    document.getElementById("hardBlockMessage").hidden = false;
  else
    document.getElementById("softBlockMessage").hidden = false;

  var link = document.getElementById("moreInfo");
  if (list.length == 1 && list[0].url) {
    link.setAttribute("href", list[0].url);
  } else {
    var url = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
    link.setAttribute("href", url);
  }
}

function finish(shouldRestartNow) {
  gArgs.restart = shouldRestartNow;
  var list = gArgs.list;
  var items = document.getElementById("addonList").childNodes;
  for (let i = 0; i < list.length; i++) {
    if (!list[i].blocked)
      list[i].disable = items[i].checked;
  }
  return true;
}
PK
!<HJ7chrome/toolkit/content/mozapps/extensions/blocklist.xml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE bindings [
  <!ENTITY % blocklistDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd" >
  %blocklistDTD;
]>

<bindings id="blocklistBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="hardblockedaddon">
    <content align="start">
      <xul:image xbl:inherits="src=icon"/>
      <xul:vbox flex="1">
        <xul:hbox class="addon-name-version">
          <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
          <xul:label class="addonVersion" xbl:inherits="value=version"/>
        </xul:hbox>
        <xul:hbox>
          <xul:spacer flex="1"/>
          <xul:label class="blockedLabel" value="&blocklist.blocked.label;"/>
        </xul:hbox>
      </xul:vbox>
    </content>
  </binding>

  <binding id="softblockedaddon">
    <content align="start">
      <xul:image xbl:inherits="src=icon"/>
      <xul:vbox flex="1">
        <xul:hbox class="addon-name-version">
          <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
          <xul:label class="addonVersion" xbl:inherits="value=version"/>
        </xul:hbox>
        <xul:hbox>
          <xul:spacer flex="1"/>
          <xul:checkbox class="disableCheckbox" checked="true" label="&blocklist.checkbox.label;"/>
        </xul:hbox>
      </xul:vbox>
    </content>
    <implementation>
      <field name="_checkbox">
        document.getAnonymousElementByAttribute(this, "class", "disableCheckbox")
      </field>
      <property name="checked" readonly="true">
        <getter>
          return this._checkbox.checked;
        </getter>
      </property>
    </implementation>
  </binding>
</bindings>
PK
!<7chrome/toolkit/content/mozapps/extensions/blocklist.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/blocklist.css"?>
<?xml-stylesheet href="chrome://mozapps/content/extensions/blocklist.css"?>

<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd">
%extensionsDTD;
]>

<dialog windowtype="Addons:Blocklist" title="&blocklist.title;" align="stretch"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="init();" ondialogaccept="return finish(true)"
                         ondialogcancel="return finish(false)"
        buttons="accept,cancel" style="&blocklist.style;"
        buttonlabelaccept="&blocklist.accept.label;"
        buttonaccesskeyaccept="&blocklist.accept.accesskey;">

  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
  <script type="application/javascript" src="chrome://mozapps/content/extensions/blocklist.js"/>

  <hbox align="stretch" flex="1">
    <vbox pack="start">
      <image class="error-icon"/>
    </vbox>
    <vbox flex="1">
      <label>&blocklist.summary;</label>
      <separator class="thin"/>
      <richlistbox id="addonList" flex="1"/>
      <separator class="thin"/>
      <description id="bothMessage" hidden="true" class="bold">&blocklist.softandhard;</description>
      <description id="hardBlockMessage" hidden="true" class="bold">&blocklist.hardblocked;</description>
      <description id="softBlockMessage" hidden="true" class="bold">&blocklist.softblocked;</description>
      <hbox pack="start">
        <label id="moreInfo" class="text-link" value="&blocklist.moreinfo;"/>
      </hbox>
    </vbox>
  </hbox>
</dialog>
PK
!<1chrome/toolkit/content/mozapps/extensions/eula.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";

/* exported Startup */

var Cu = Components.utils;
Cu.import("resource://gre/modules/AddonManager.jsm");

function Startup() {
  var bundle = document.getElementById("extensionsStrings");
  var addon = window.arguments[0].addon;

  document.documentElement.setAttribute("addontype", addon.type);

  var iconURL = AddonManager.getPreferredIconURL(addon, 48, window);
  if (iconURL)
    document.getElementById("icon").src = iconURL;

  var label = document.createTextNode(bundle.getFormattedString("eulaHeader", [addon.name]));
  document.getElementById("heading").appendChild(label);
  document.getElementById("eula").value = addon.eula;
}
PK
!<Y552chrome/toolkit/content/mozapps/extensions/eula.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/eula.css" type="text/css"?>

<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&eula.title;" width="&eula.width;" height="&eula.height;"
        buttons="accept,cancel" buttonlabelaccept="&eula.accept;"
        ondialogaccept="window.arguments[0].accepted = true"
        onload="Startup();">

  <script type="application/javascript" src="chrome://mozapps/content/extensions/eula.js"/>
  
  <stringbundleset id="extensionsSet">
    <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
  </stringbundleset>

  <hbox id="heading-container">
    <image id="icon"/>
    <label id="heading" flex="1"/>
  </hbox>
  
  <textbox id="eula" multiline="true" readonly="true" flex="1"/>
</dialog>
PK
!<II8chrome/toolkit/content/mozapps/extensions/extensions.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace xhtml "http://www.w3.org/1999/xhtml";

/* HTML link elements do weird things to the layout if they are not hidden */
xhtml|link {
  display: none;
}

#categories {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#categories-list");
}

.category {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category");
}

.sort-controls {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#sorters");
}

.addon[status="installed"] {
  -moz-box-orient: vertical;
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic");
}

.addon[status="installing"] {
  -moz-box-orient: vertical;
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing");
}

.addon[pending="uninstall"] {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled");
}

.creator {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link");
}

.meta-rating {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating");
}

.download-progress, .download-progress[mode="undetermined"] {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress");
}

.install-status {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status");
}

.detail-row {
  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row");
}

.text-list {
  white-space: pre-line;
  -moz-user-select: element;
}

setting, row[unsupported="true"] {
  display: none;
}

setting[type="bool"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-bool");
}

setting[type="bool"][localized="true"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-localized-bool");
}

setting[type="bool"]:not([learnmore]) .preferences-learnmore,
setting[type="boolint"]:not([learnmore]) .preferences-learnmore {
  visibility: collapse;
}

setting[type="boolint"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-boolint");
}

setting[type="integer"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer");
}

setting[type="integer"]:not([size]) textbox {
  -moz-box-flex: 1;
}

setting[type="control"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control");
}

setting[type="string"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string");
}

setting[type="color"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color");
}

setting[type="file"],
setting[type="directory"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path");
}

setting[type="radio"],
setting[type="menulist"] {
  display: -moz-grid-line;
  -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
}

#addonitem-popup > menuitem[disabled="true"] {
  display: none;
}

#addonitem-popup[addontype="theme"] > #menuitem_enableItem,
#addonitem-popup[addontype="theme"] > #menuitem_disableItem,
#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme,
#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme {
  display: none;
}

#show-disabled-unsigned-extensions .button-text {
  margin-inline-start: 3px !important;
  margin-inline-end: 2px !important;
}

#header-searching:not([active]) {
  visibility: hidden;
}

#search-list[local="false"]  > .addon[remote="false"],
#search-list[remote="false"] > .addon[remote="true"] {
  visibility: collapse;
}

#detail-view {
  overflow: auto;
}

.addon:not([notification="warning"]) .warning,
.addon:not([notification="error"]) .error,
.addon:not([notification="info"]) .info,
.addon:not([pending]) .pending,
.addon:not([upgrade="true"]) .update-postfix,
.addon[active="true"] .disabled-postfix,
.addon[pending="install"] .update-postfix,
.addon[pending="install"] .disabled-postfix,
.addon[legacy="false"] .legacy-warning,
#detail-view:not([notification="warning"]) .warning,
#detail-view:not([notification="error"]) .error,
#detail-view:not([notification="info"]) .info,
#detail-view:not([pending]) .pending,
#detail-view:not([upgrade="true"]) .update-postfix,
#detail-view[active="true"] .disabled-postfix,
#detail-view[legacy="false"] .legacy-warning,
#detail-view[loading] .detail-view-container,
#detail-view:not([loading]) .alert-container,
.detail-row:not([value]),
#search-list[remote="false"] #search-allresults-link,
#legacy-list .addon .disabled-postfix {
  display: none;
}

#addons-page:not([warning]) #list-view > .global-warning-container {
  display: none;
}
#addon-list .date-updated,
#legacy-list .date-updated {
  display: none;
}

.view-pane:not(#updates-view) .addon .relnotes-toggle,
.view-pane:not(#updates-view) .addon .include-update,
#updates-view:not([updatetype="available"]) .addon .include-update,
#updates-view[updatetype="available"] .addon .update-available-notice {
  display: none;
}

#addons-page:not([warning]) .global-warning,
#addons-page:not([warning="safemode"]) .global-warning-safemode,
#addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility,
#addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity {
  display: none;
}

/* Plugins aren't yet disabled by safemode (bug 342333),
   so don't show that warning when viewing plugins. */
#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning {
  display: none;
}

#addons-page .view-pane:not([type="theme"]) #getthemes-container,
#addons-page .view-pane:not([type="plugin"]) #plugindeprecation-notice,
#addons-page .view-pane:not([type="experiment"]) .experiment-info-container {
  display: none;
}

.addon .relnotes {
  -moz-user-select: text;
}
#detail-name, #detail-desc, #detail-fulldesc {
  -moz-user-select: text;
}

/* Make sure we're not animating hidden images. See bug 623739. */
#view-port:not([selectedIndex="0"]) #discover-view .loading,
#discover-view:not([selectedIndex="0"]) .loading {
  display: none;
}

/* Elements in unselected richlistitems cannot be focused */
richlistitem:not([selected]) * {
  -moz-user-focus: ignore;
}

#header-search {
  width: 22em;
}

#header-utils-btn {
  -moz-user-focus: normal;
}

.discover-button[disabled="true"] {
  display: none;
}

#experiments-learn-more[disabled="true"] {
  display: none;
}

#experiments-change-telemetry[disabled="true"] {
  display: none;
}

.view-pane:not(#legacy-view) .addon-control.replacement {
  display: none;
}

.view-pane[type="experiment"] .error,
.view-pane[type="experiment"] .warning,
.view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending,
.view-pane[type="experiment"] .disabled-postfix,
.view-pane[type="experiment"] .update-postfix,
.view-pane[type="experiment"] .addon-control.enable,
.view-pane[type="experiment"] .addon-control.disable,
#detail-view[type="experiment"] .alert-container,
#detail-view[type="experiment"] #detail-version,
#detail-view[type="experiment"] #detail-creator,
#detail-view[type="experiment"] #detail-enable-btn,
#detail-view[type="experiment"] #detail-disable-btn {
  display: none;
}

.view-pane:not([type="experiment"]) .experiment-container,
.view-pane:not([type="experiment"]) #detail-experiment-container {
  display: none;
}

.addon[type="experiment"][status="installing"] .experiment-time,
.addon[type="experiment"][status="installing"] .experiment-state {
  display: none;
}
PK
!<WU7chrome/toolkit/content/mozapps/extensions/extensions.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 ../../../content/contentAreaUtils.js */
/* globals XMLStylesheetProcessingInstruction */
/* exported UPDATES_RELEASENOTES_TRANSFORMFILE, XMLURI_PARSE_ERROR, loadView */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DownloadUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
Cu.import("resource://gre/modules/addons/AddonSettings.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils", "resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                  "resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                  "resource://gre/modules/ExtensionParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");


XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                  "resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                  "resource://gre/modules/Preferences.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
  "resource:///modules/experiments/Experiments.jsm");

XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                      "extensions.webextPermissionPrompts", false);
XPCOMUtils.defineLazyPreferenceGetter(this, "ALLOW_NON_MPC",
                                      "extensions.allow-non-mpc-extensions", true);

XPCOMUtils.defineLazyPreferenceGetter(this, "SUPPORT_URL", "app.support.baseURL",
                                      "", null, val => Services.urlFormatter.formatURL(val));

const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
const PREF_LEGACY_EXCEPTIONS = "extensions.legacy.exceptions";
const PREF_LEGACY_ENABLED = "extensions.legacy.enabled";

const LOADING_MSG_DELAY = 100;

const SEARCH_SCORE_MULTIPLIER_NAME = 2;
const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;

// Use integers so search scores are sortable by nsIXULSortService
const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
const SEARCH_SCORE_MATCH_SUBSTRING = 3;

const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";

const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"

var gViewDefault = "addons://discover/";

XPCOMUtils.defineLazyGetter(this, "extensionStylesheets", () => {
  const {ExtensionParent} = Cu.import("resource://gre/modules/ExtensionParent.jsm", {});
  return ExtensionParent.extensionStylesheets;
});

var gStrings = {};
XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
                                   "@mozilla.org/intl/stringbundle;1",
                                   "nsIStringBundleService");

XPCOMUtils.defineLazyGetter(gStrings, "brand", function() {
  return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
});
XPCOMUtils.defineLazyGetter(gStrings, "ext", function() {
  return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
});
XPCOMUtils.defineLazyGetter(gStrings, "dl", function() {
  return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties");
});

XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function() {
  return this.brand.GetStringFromName("brandShortName");
});
XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function() {
  return Services.appinfo.version;
});


XPCOMUtils.defineLazyPreferenceGetter(this, "legacyWarningExceptions",
                                      PREF_LEGACY_EXCEPTIONS, "",
                                      raw => raw.split(","));
XPCOMUtils.defineLazyPreferenceGetter(this, "legacyExtensionsEnabled",
                                      PREF_LEGACY_ENABLED, true,
                                      () => gLegacyView.refresh());

document.addEventListener("load", initialize, true);
window.addEventListener("unload", shutdown);

function promiseEvent(event, target, capture = false) {
  return new Promise(resolve => {
    target.addEventListener(event, resolve, {capture, once: true});
  });
}

var gPendingInitializations = 1;
Object.defineProperty(this, "gIsInitializing", {
  get: () => gPendingInitializations > 0
});

function initialize(event) {
  // XXXbz this listener gets _all_ load events for all nodes in the
  // document... but relies on not being called "too early".
  if (event.target instanceof XMLStylesheetProcessingInstruction) {
    return;
  }
  document.removeEventListener("load", initialize, true);

  let globalCommandSet = document.getElementById("globalCommandSet");
  globalCommandSet.addEventListener("command", function(event) {
    gViewController.doCommand(event.target.id);
  });

  let viewCommandSet = document.getElementById("viewCommandSet");
  viewCommandSet.addEventListener("commandupdate", function(event) {
    gViewController.updateCommands();
  });
  viewCommandSet.addEventListener("command", function(event) {
    gViewController.doCommand(event.target.id);
  });

  let detailScreenshot = document.getElementById("detail-screenshot");
  detailScreenshot.addEventListener("load", function(event) {
    this.removeAttribute("loading");
  });
  detailScreenshot.addEventListener("error", function(event) {
    this.setAttribute("loading", "error");
  });

  let addonPage = document.getElementById("addons-page");
  addonPage.addEventListener("dragenter", function(event) {
    gDragDrop.onDragOver(event);
  });
  addonPage.addEventListener("dragover", function(event) {
    gDragDrop.onDragOver(event);
  });
  addonPage.addEventListener("drop", function(event) {
    gDragDrop.onDrop(event);
  });
  addonPage.addEventListener("keypress", function(event) {
    // If there is an embedded preferences <browser> running in a remote
    // process, we will see the event here first before it gets a chance
    // to bubble up through the embedded page.  To avoid stealing focus,
    // we just ignore events when focus is in an options browser.
    if (event.target.classList.contains("inline-options-browser")) {
      return;
    }
    gHeader.onKeyPress(event);
  });

  if (!isDiscoverEnabled()) {
    gViewDefault = "addons://list/extension";
  }

  gViewController.initialize();
  gCategories.initialize();
  gHeader.initialize();
  gEventManager.initialize();
  Services.obs.addObserver(sendEMPong, "EM-ping");
  Services.obs.notifyObservers(window, "EM-loaded");

  // If the initial view has already been selected (by a call to loadView from
  // the above notifications) then bail out now
  if (gViewController.initialViewSelected)
    return;

  // If there is a history state to restore then use that
  if (window.history.state) {
    gViewController.updateState(window.history.state);
    return;
  }

  // Default to the last selected category
  var view = gCategories.node.value;

  // Allow passing in a view through the window arguments
  if ("arguments" in window && window.arguments.length > 0 &&
      window.arguments[0] !== null && "view" in window.arguments[0]) {
    view = window.arguments[0].view;
  }

  gViewController.loadInitialView(view);
}

function notifyInitialized() {
  if (!gIsInitializing)
    return;

  gPendingInitializations--;
  if (!gIsInitializing) {
    var event = document.createEvent("Events");
    event.initEvent("Initialized", true, true);
    document.dispatchEvent(event);
  }
}

function shutdown() {
  gCategories.shutdown();
  gSearchView.shutdown();
  gEventManager.shutdown();
  gViewController.shutdown();
  Services.obs.removeObserver(sendEMPong, "EM-ping");
}

function sendEMPong(aSubject, aTopic, aData) {
  Services.obs.notifyObservers(window, "EM-pong");
}

// Used by external callers to load a specific view into the manager
function loadView(aViewId) {
  if (!gViewController.initialViewSelected) {
    // The caller opened the window and immediately loaded the view so it
    // should be the initial history entry

    gViewController.loadInitialView(aViewId);
  } else {
    gViewController.loadView(aViewId);
  }
}

function isCorrectlySigned(aAddon) {
  // Add-ons without an "isCorrectlySigned" property are correctly signed as
  // they aren't the correct type for signing.
  return aAddon.isCorrectlySigned !== false;
}

function isDisabledUnsigned(addon) {
  return AddonSettings.REQUIRE_SIGNING && !isCorrectlySigned(addon);
}

function isLegacyExtension(addon) {
  let legacy = false;
  if (addon.type == "extension" && !addon.isWebExtension) {
    legacy = true;
  }
  if (addon.type == "theme") {
    // The logic here is kind of clunky but we want to mark complete
    // themes as legacy.  There's no explicit flag for complete
    // themes so explicitly check for new style themes (for which
    // isWebExtension is true) or lightweight themes (which have
    // ids that end with @personas.mozilla.org)
    legacy = !(addon.isWebExtension || addon.id.endsWith("@personas.mozilla.org"));
  }

  if (legacy && (addon.hidden || addon.signedState == AddonManager.SIGNEDSTATE_PRIVILEGED)) {
    legacy = false;
  }
  // Exceptions that can slip through above: the default theme plus
  // test pilot addons until we get SIGNEDSTATE_PRIVILEGED deployed.
  if (legacy && legacyWarningExceptions.includes(addon.id)) {
    legacy = false;
  }
  return legacy;
}

function isDisabledLegacy(addon) {
  return !legacyExtensionsEnabled && isLegacyExtension(addon);
}

function isDiscoverEnabled() {
  if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
    return false;

  try {
    if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED))
      return false;
  } catch (e) {}

  try {
    if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED))
      return false;
  } catch (e) {}

  return true;
}

function getExperimentEndDate(aAddon) {
  if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
    return 0;
  }

  if (!aAddon.isActive) {
    return aAddon.endDate;
  }

  let experiment = Experiments.instance().getActiveExperiment();
  if (!experiment) {
    return 0;
  }

  return experiment.endDate;
}

/**
 * Obtain the main DOMWindow for the current context.
 */
function getMainWindow() {
  return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIWebNavigation)
               .QueryInterface(Ci.nsIDocShellTreeItem)
               .rootTreeItem
               .QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindow);
}

function getBrowserElement() {
  return window.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDocShell)
               .chromeEventHandler;
}

/**
 * Obtain the DOMWindow that can open a preferences pane.
 *
 * This is essentially "get the browser chrome window" with the added check
 * that the supposed browser chrome window is capable of opening a preferences
 * pane.
 *
 * This may return null if we can't find the browser chrome window.
 */
function getMainWindowWithPreferencesPane() {
  let mainWindow = getMainWindow();
  if (mainWindow && "openAdvancedPreferences" in mainWindow) {
    return mainWindow;
  }
  return null;
}

/**
 * A wrapper around the HTML5 session history service that allows the browser
 * back/forward controls to work within the manager
 */
var HTML5History = {
  get index() {
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .sessionHistory.index;
  },

  get canGoBack() {
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .canGoBack;
  },

  get canGoForward() {
    return window.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .canGoForward;
  },

  back() {
    window.history.back();
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  },

  forward() {
    window.history.forward();
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  },

  pushState(aState) {
    window.history.pushState(aState, document.title);
  },

  replaceState(aState) {
    window.history.replaceState(aState, document.title);
  },

  popState() {
    function onStatePopped(aEvent) {
      window.removeEventListener("popstate", onStatePopped, true);
      // TODO To ensure we can't go forward again we put an additional entry
      // for the current state into the history. Ideally we would just strip
      // the history but there doesn't seem to be a way to do that. Bug 590661
      window.history.pushState(aEvent.state, document.title);
    }
    window.addEventListener("popstate", onStatePopped, true);
    window.history.back();
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  }
};

/**
 * A wrapper around a fake history service
 */
var FakeHistory = {
  pos: 0,
  states: [null],

  get index() {
    return this.pos;
  },

  get canGoBack() {
    return this.pos > 0;
  },

  get canGoForward() {
    return (this.pos + 1) < this.states.length;
  },

  back() {
    if (this.pos == 0)
      throw Components.Exception("Cannot go back from this point");

    this.pos--;
    gViewController.updateState(this.states[this.pos]);
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  },

  forward() {
    if ((this.pos + 1) >= this.states.length)
      throw Components.Exception("Cannot go forward from this point");

    this.pos++;
    gViewController.updateState(this.states[this.pos]);
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  },

  pushState(aState) {
    this.pos++;
    this.states.splice(this.pos, this.states.length);
    this.states.push(aState);
  },

  replaceState(aState) {
    this.states[this.pos] = aState;
  },

  popState() {
    if (this.pos == 0)
      throw Components.Exception("Cannot popState from this view");

    this.states.splice(this.pos, this.states.length);
    this.pos--;

    gViewController.updateState(this.states[this.pos]);
    gViewController.updateCommand("cmd_back");
    gViewController.updateCommand("cmd_forward");
  }
};

// If the window has a session history then use the HTML5 History wrapper
// otherwise use our fake history implementation
if (window.QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIWebNavigation)
          .sessionHistory) {
  var gHistory = HTML5History;
} else {
  gHistory = FakeHistory;
}

var gEventManager = {
  _listeners: {},
  _installListeners: [],

  initialize() {
    const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling",
                          "onDisabled", "onUninstalling", "onUninstalled",
                          "onInstalled", "onOperationCancelled",
                          "onUpdateAvailable", "onUpdateFinished",
                          "onCompatibilityUpdateAvailable",
                          "onPropertyChanged"];
    for (let evt of ADDON_EVENTS) {
      let event = evt;
      this[event] = (...aArgs) => this.delegateAddonEvent(event, aArgs);
    }

    const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted",
                            "onDownloadEnded", "onDownloadFailed",
                            "onDownloadProgress", "onDownloadCancelled",
                            "onInstallStarted", "onInstallEnded",
                            "onInstallFailed", "onInstallCancelled",
                            "onExternalInstall"];
    for (let evt of INSTALL_EVENTS) {
      let event = evt;
      this[event] = (...aArgs) => this.delegateInstallEvent(event, aArgs);
    }

    AddonManager.addManagerListener(this);
    AddonManager.addInstallListener(this);
    AddonManager.addAddonListener(this);

    this.refreshGlobalWarning();
    this.refreshAutoUpdateDefault();

    var contextMenu = document.getElementById("addonitem-popup");
    contextMenu.addEventListener("popupshowing", function() {
      var addon = gViewController.currentViewObj.getSelectedAddon();
      contextMenu.setAttribute("addontype", addon.type);

      var menuSep = document.getElementById("addonitem-menuseparator");
      var countMenuItemsBeforeSep = 0;
      for (let child of contextMenu.children) {
        if (child == menuSep) {
          break;
        }
        if (child.nodeName == "menuitem" &&
          gViewController.isCommandEnabled(child.command)) {
            countMenuItemsBeforeSep++;
        }
      }

      // Hide the separator if there are no visible menu items before it
      menuSep.hidden = (countMenuItemsBeforeSep == 0);

    });

    let addonTooltip = document.getElementById("addonitem-tooltip");
    addonTooltip.addEventListener("popupshowing", function() {
      let addonItem = addonTooltip.triggerNode;
      // The way the test triggers the tooltip the richlistitem is the
      // tooltipNode but in normal use it is the anonymous node. This allows
      // any case
      if (addonItem.localName != "richlistitem")
        addonItem = document.getBindingParent(addonItem);

      let tiptext = addonItem.getAttribute("name");

      if (addonItem.mAddon) {
        if (shouldShowVersionNumber(addonItem.mAddon)) {
          tiptext += " " + (addonItem.hasAttribute("upgrade") ? addonItem.mManualUpdate.version
                                                              : addonItem.mAddon.version);
        }
      } else if (shouldShowVersionNumber(addonItem.mInstall)) {
        tiptext += " " + addonItem.mInstall.version;
      }

      addonTooltip.label = tiptext;
    });
  },

  shutdown() {
    AddonManager.removeManagerListener(this);
    AddonManager.removeInstallListener(this);
    AddonManager.removeAddonListener(this);
  },

  registerAddonListener(aListener, aAddonId) {
    if (!(aAddonId in this._listeners))
      this._listeners[aAddonId] = [];
    else if (this._listeners[aAddonId].indexOf(aListener) != -1)
      return;
    this._listeners[aAddonId].push(aListener);
  },

  unregisterAddonListener(aListener, aAddonId) {
    if (!(aAddonId in this._listeners))
      return;
    var index = this._listeners[aAddonId].indexOf(aListener);
    if (index == -1)
      return;
    this._listeners[aAddonId].splice(index, 1);
  },

  registerInstallListener(aListener) {
    if (this._installListeners.indexOf(aListener) != -1)
      return;
    this._installListeners.push(aListener);
  },

  unregisterInstallListener(aListener) {
    var i = this._installListeners.indexOf(aListener);
    if (i == -1)
      return;
    this._installListeners.splice(i, 1);
  },

  delegateAddonEvent(aEvent, aParams) {
    var addon = aParams.shift();
    if (!(addon.id in this._listeners))
      return;

    var listeners = this._listeners[addon.id];
    for (let listener of listeners) {
      if (!(aEvent in listener))
        continue;
      try {
        listener[aEvent].apply(listener, aParams);
      } catch (e) {
        // this shouldn't be fatal
        Cu.reportError(e);
      }
    }
  },

  delegateInstallEvent(aEvent, aParams) {
    var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon;
    // If the install is an update then send the event to all listeners
    // registered for the existing add-on
    if (existingAddon)
      this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams));

    for (let listener of this._installListeners) {
      if (!(aEvent in listener))
        continue;
      try {
        listener[aEvent].apply(listener, aParams);
      } catch (e) {
        // this shouldn't be fatal
        Cu.reportError(e);
      }
    }
  },

  refreshGlobalWarning() {
    var page = document.getElementById("addons-page");

    if (Services.appinfo.inSafeMode) {
      page.setAttribute("warning", "safemode");
      return;
    }

    if (AddonManager.checkUpdateSecurityDefault &&
        !AddonManager.checkUpdateSecurity) {
      page.setAttribute("warning", "updatesecurity");
      return;
    }

    if (!AddonManager.checkCompatibility) {
      page.setAttribute("warning", "checkcompatibility");
      return;
    }

    page.removeAttribute("warning");
  },

  refreshAutoUpdateDefault() {
    var updateEnabled = AddonManager.updateEnabled;
    var autoUpdateDefault = AddonManager.autoUpdateDefault;

    // The checkbox needs to reflect that both prefs need to be true
    // for updates to be checked for and applied automatically
    document.getElementById("utils-autoUpdateDefault")
            .setAttribute("checked", updateEnabled && autoUpdateDefault);

    document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault;
    document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault;
  },

  onCompatibilityModeChanged() {
    this.refreshGlobalWarning();
  },

  onCheckUpdateSecurityChanged() {
    this.refreshGlobalWarning();
  },

  onUpdateModeChanged() {
    this.refreshAutoUpdateDefault();
  }
};

function attachUpdateHandler(install) {
  if (!WEBEXT_PERMISSION_PROMPTS) {
    return;
  }

  install.promptHandler = (info) => {
    let oldPerms = info.existingAddon.userPermissions;
    if (!oldPerms) {
      // Updating from a legacy add-on, let it proceed
      return Promise.resolve();
    }

    let newPerms = info.addon.userPermissions;

    let difference = Extension.comparePermissions(oldPerms, newPerms);

    // If there are no new permissions, just proceed
    if (difference.origins.length == 0 && difference.permissions.length == 0) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      let subject = {
        wrappedJSObject: {
          target: getBrowserElement(),
          info: {
            type: "update",
            addon: info.addon,
            icon: info.addon.icon,
            permissions: difference,
            resolve,
            reject,
          },
        },
      };
      Services.obs.notifyObservers(subject, "webextension-permission-prompt");
    });
  };
}

var gViewController = {
  viewPort: null,
  currentViewId: "",
  currentViewObj: null,
  currentViewRequest: 0,
  viewObjects: {},
  viewChangeCallback: null,
  initialViewSelected: false,
  lastHistoryIndex: -1,

  initialize() {
    this.viewPort = document.getElementById("view-port");
    this.headeredViews = document.getElementById("headered-views");
    this.headeredViewsDeck = document.getElementById("headered-views-content");

    this.viewObjects["search"] = gSearchView;
    this.viewObjects["discover"] = gDiscoverView;
    this.viewObjects["list"] = gListView;
    this.viewObjects["legacy"] = gLegacyView;
    this.viewObjects["detail"] = gDetailView;
    this.viewObjects["updates"] = gUpdatesView;

    for (let type in this.viewObjects) {
      let view = this.viewObjects[type];
      view.initialize();
    }

    window.controllers.appendController(this);

    window.addEventListener("popstate", function(e) {
                              gViewController.updateState(e.state);
                            });
  },

  shutdown() {
    if (this.currentViewObj)
      this.currentViewObj.hide();
    this.currentViewRequest = 0;

    for (let type in this.viewObjects) {
      let view = this.viewObjects[type];
      if ("shutdown" in view) {
        try {
          view.shutdown();
        } catch (e) {
          // this shouldn't be fatal
          Cu.reportError(e);
        }
      }
    }

    window.controllers.removeController(this);
  },

  updateState(state) {
    try {
      this.loadViewInternal(state.view, state.previousView, state);
      this.lastHistoryIndex = gHistory.index;
    } catch (e) {
      // The attempt to load the view failed, try moving further along history
      if (this.lastHistoryIndex > gHistory.index) {
        if (gHistory.canGoBack)
          gHistory.back();
        else
          gViewController.replaceView(gViewDefault);
      } else if (gHistory.canGoForward) {
        gHistory.forward();
      } else {
        gViewController.replaceView(gViewDefault);
      }
    }
  },

  parseViewId(aViewId) {
    var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
    var [, viewType, viewParam] = aViewId.match(matchRegex) || [];
    return {type: viewType, param: decodeURIComponent(viewParam)};
  },

  get isLoading() {
    return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
  },

  loadView(aViewId) {
    var isRefresh = false;
    if (aViewId == this.currentViewId) {
      if (this.isLoading)
        return;
      if (!("refresh" in this.currentViewObj))
        return;
      if (!this.currentViewObj.canRefresh())
        return;
      isRefresh = true;
    }

    var state = {
      view: aViewId,
      previousView: this.currentViewId
    };
    if (!isRefresh) {
      gHistory.pushState(state);
      this.lastHistoryIndex = gHistory.index;
    }
    this.loadViewInternal(aViewId, this.currentViewId, state);
  },

  // Replaces the existing view with a new one, rewriting the current history
  // entry to match.
  replaceView(aViewId) {
    if (aViewId == this.currentViewId)
      return;

    var state = {
      view: aViewId,
      previousView: null
    };
    gHistory.replaceState(state);
    this.loadViewInternal(aViewId, null, state);
  },

  loadInitialView(aViewId) {
    var state = {
      view: aViewId,
      previousView: null
    };
    gHistory.replaceState(state);

    this.loadViewInternal(aViewId, null, state);
    this.initialViewSelected = true;
    notifyInitialized();
  },

  get displayedView() {
    if (this.viewPort.selectedPanel == this.headeredViews) {
      return this.headeredViewsDeck.selectedPanel;
    }
    return this.viewPort.selectedPanel;
  },

  set displayedView(view) {
    let node = view.node;
    if (node.parentNode == this.headeredViewsDeck) {
      this.headeredViewsDeck.selectedPanel = node;
      this.viewPort.selectedPanel = this.headeredViews;
    } else {
      this.viewPort.selectedPanel = node;
    }
  },

  loadViewInternal(aViewId, aPreviousView, aState) {
    var view = this.parseViewId(aViewId);

    if (!view.type || !(view.type in this.viewObjects))
      throw Components.Exception("Invalid view: " + view.type);

    var viewObj = this.viewObjects[view.type];
    if (!viewObj.node)
      throw Components.Exception("Root node doesn't exist for '" + view.type + "' view");

    if (this.currentViewObj && aViewId != aPreviousView) {
      try {
        let canHide = this.currentViewObj.hide();
        if (canHide === false)
          return;
        this.displayedView.removeAttribute("loading");
      } catch (e) {
        // this shouldn't be fatal
        Cu.reportError(e);
      }
    }

    gCategories.select(aViewId, aPreviousView);

    this.currentViewId = aViewId;
    this.currentViewObj = viewObj;

    this.displayedView = this.currentViewObj;
    this.currentViewObj.node.setAttribute("loading", "true");
    this.currentViewObj.node.focus();

    if (aViewId == aPreviousView)
      this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState);
    else
      this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
  },

  // Moves back in the document history and removes the current history entry
  popState(aCallback) {
    this.viewChangeCallback = aCallback;
    gHistory.popState();
  },

  notifyViewChanged() {
    this.displayedView.removeAttribute("loading");

    if (this.viewChangeCallback) {
      this.viewChangeCallback();
      this.viewChangeCallback = null;
    }

    var event = document.createEvent("Events");
    event.initEvent("ViewChanged", true, true);
    this.currentViewObj.node.dispatchEvent(event);
  },

  commands: {
    cmd_back: {
      isEnabled() {
        return gHistory.canGoBack;
      },
      doCommand() {
        gHistory.back();
      }
    },

    cmd_forward: {
      isEnabled() {
        return gHistory.canGoForward;
      },
      doCommand() {
        gHistory.forward();
      }
    },

    cmd_focusSearch: {
      isEnabled: () => true,
      doCommand() {
        gHeader.focusSearchBox();
      }
    },

    cmd_restartApp: {
      isEnabled() {
        return true;
      },
      doCommand() {
        let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                         createInstance(Ci.nsISupportsPRBool);
        Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                     "restart");
        if (cancelQuit.data)
          return; // somebody canceled our quit request

        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
                         getService(Ci.nsIAppStartup);
        appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
      }
    },

    cmd_enableCheckCompatibility: {
      isEnabled() {
        return true;
      },
      doCommand() {
        AddonManager.checkCompatibility = true;
      }
    },

    cmd_enableUpdateSecurity: {
      isEnabled() {
        return true;
      },
      doCommand() {
        AddonManager.checkUpdateSecurity = true;
      }
    },

    cmd_toggleAutoUpdateDefault: {
      isEnabled() {
        return true;
      },
      doCommand() {
        if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
          // One or both of the prefs is false, i.e. the checkbox is not checked.
          // Now toggle both to true. If the user wants us to auto-update
          // add-ons, we also need to auto-check for updates.
          AddonManager.updateEnabled = true;
          AddonManager.autoUpdateDefault = true;
        } else {
          // Both prefs are true, i.e. the checkbox is checked.
          // Toggle the auto pref to false, but don't touch the enabled check.
          AddonManager.autoUpdateDefault = false;
        }
      }
    },

    cmd_resetAddonAutoUpdate: {
      isEnabled() {
        return true;
      },
      doCommand() {
        AddonManager.getAllAddons(function(aAddonList) {
          for (let addon of aAddonList) {
            if ("applyBackgroundUpdates" in addon)
              addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
          }
        });
      }
    },

    cmd_goToDiscoverPane: {
      isEnabled() {
        return gDiscoverView.enabled;
      },
      doCommand() {
        gViewController.loadView("addons://discover/");
      }
    },

    cmd_goToRecentUpdates: {
      isEnabled() {
        return true;
      },
      doCommand() {
        gViewController.loadView("addons://updates/recent");
      }
    },

    cmd_goToAvailableUpdates: {
      isEnabled() {
        return true;
      },
      doCommand() {
        gViewController.loadView("addons://updates/available");
      }
    },

    cmd_showItemDetails: {
      isEnabled(aAddon) {
        return !!aAddon && (gViewController.currentViewObj != gDetailView);
      },
      doCommand(aAddon, aScrollToPreferences) {
        gViewController.loadView("addons://detail/" +
                                 encodeURIComponent(aAddon.id) +
                                 (aScrollToPreferences ? "/preferences" : ""));
      }
    },

    cmd_findAllUpdates: {
      inProgress: false,
      isEnabled() {
        return !this.inProgress;
      },
      doCommand() {
        this.inProgress = true;
        gViewController.updateCommand("cmd_findAllUpdates");
        document.getElementById("updates-noneFound").hidden = true;
        document.getElementById("updates-progress").hidden = false;
        document.getElementById("updates-manualUpdatesFound-btn").hidden = true;

        var pendingChecks = 0;
        var numUpdated = 0;
        var numManualUpdates = 0;
        var restartNeeded = false;

        let updateStatus = () => {
          if (pendingChecks > 0)
            return;

          this.inProgress = false;
          gViewController.updateCommand("cmd_findAllUpdates");
          document.getElementById("updates-progress").hidden = true;
          gUpdatesView.maybeRefresh();

          Services.obs.notifyObservers(null, "EM-update-check-finished");

          if (numManualUpdates > 0 && numUpdated == 0) {
            document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
            return;
          }

          if (numUpdated == 0) {
            document.getElementById("updates-noneFound").hidden = false;
            return;
          }

          if (restartNeeded) {
            document.getElementById("updates-downloaded").hidden = false;
            document.getElementById("updates-restart-btn").hidden = false;
          } else {
            document.getElementById("updates-installed").hidden = false;
          }
        }

        var updateInstallListener = {
          onDownloadFailed() {
            pendingChecks--;
            updateStatus();
          },
          onInstallCancelled() {
            pendingChecks--;
            updateStatus();
          },
          onInstallFailed() {
            pendingChecks--;
            updateStatus();
          },
          onInstallEnded(aInstall, aAddon) {
            pendingChecks--;
            numUpdated++;
            if (isPending(aInstall.existingAddon, "upgrade"))
              restartNeeded = true;
            updateStatus();
          }
        };

        var updateCheckListener = {
          onUpdateAvailable(aAddon, aInstall) {
            gEventManager.delegateAddonEvent("onUpdateAvailable",
                                             [aAddon, aInstall]);
            attachUpdateHandler(aInstall);
            if (AddonManager.shouldAutoUpdate(aAddon)) {
              aInstall.addListener(updateInstallListener);
              aInstall.install();
            } else {
              pendingChecks--;
              numManualUpdates++;
              updateStatus();
            }
          },
          onNoUpdateAvailable(aAddon) {
            pendingChecks--;
            updateStatus();
          },
          onUpdateFinished(aAddon, aError) {
            gEventManager.delegateAddonEvent("onUpdateFinished",
                                             [aAddon, aError]);
          }
        };

        AddonManager.getAddonsByTypes(null, function(aAddonList) {
          for (let addon of aAddonList) {
            if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
              pendingChecks++;
              addon.findUpdates(updateCheckListener,
                                AddonManager.UPDATE_WHEN_USER_REQUESTED);
            }
          }

          if (pendingChecks == 0)
            updateStatus();
        });
      }
    },

    cmd_findItemUpdates: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return hasPermission(aAddon, "upgrade");
      },
      doCommand(aAddon) {
        var listener = {
          onUpdateAvailable(aAddon, aInstall) {
            gEventManager.delegateAddonEvent("onUpdateAvailable",
                                             [aAddon, aInstall]);
            attachUpdateHandler(aInstall);
            if (AddonManager.shouldAutoUpdate(aAddon))
              aInstall.install();
          },
          onNoUpdateAvailable(aAddon) {
            gEventManager.delegateAddonEvent("onNoUpdateAvailable",
                                             [aAddon]);
          }
        };
        gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
        aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
      }
    },

    cmd_showItemPreferences: {
      isEnabled(aAddon) {
        if (!aAddon ||
            (!aAddon.isActive && aAddon.type !== "plugin") ||
            !aAddon.optionsURL) {
          return false;
        }
        if (gViewController.currentViewObj == gDetailView &&
            (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
             aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER)) {
          return false;
        }
        if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
          return false;
        return true;
      },
      doCommand(aAddon) {
        if (hasInlineOptions(aAddon)) {
          gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
          return;
        }
        var optionsURL = aAddon.optionsURL;
        if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
            openOptionsInTab(optionsURL)) {
          return;
        }
        var windows = Services.wm.getEnumerator(null);
        while (windows.hasMoreElements()) {
          var win = windows.getNext();
          if (win.closed) {
            continue;
          }
          if (win.document.documentURI == optionsURL) {
            win.focus();
            return;
          }
        }
        var features = "chrome,titlebar,toolbar,centerscreen";
        try {
          var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
          features += instantApply ? ",dialog=no" : ",modal";
        } catch (e) {
          features += ",modal";
        }
        openDialog(optionsURL, "", features);
      }
    },

    cmd_showItemAbout: {
      isEnabled(aAddon) {
        // XXXunf This may be applicable to install items too. See bug 561260
        return !!aAddon;
      },
      doCommand(aAddon) {
        var aboutURL = aAddon.aboutURL;
        if (aboutURL)
          openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon);
        else
          openDialog("chrome://mozapps/content/extensions/about.xul",
                     "", "chrome,centerscreen,modal", aAddon);
      }
    },

    cmd_enableItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        let addonType = AddonManager.addonTypes[aAddon.type];
        return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                hasPermission(aAddon, "enable"));
      },
      doCommand(aAddon) {
        if (aAddon.isWebExtension && !aAddon.seen && WEBEXT_PERMISSION_PROMPTS) {
          let perms = aAddon.userPermissions;
          if (perms.origins.length > 0 || perms.permissions.length > 0) {
            let subject = {
              wrappedJSObject: {
                target: getBrowserElement(),
                info: {
                  type: "sideload",
                  addon: aAddon,
                  icon: aAddon.iconURL,
                  permissions: perms,
                  resolve() { aAddon.userDisabled = false },
                  reject() {},
                },
              },
            };
            Services.obs.notifyObservers(subject, "webextension-permission-prompt");
            return;
          }
        }
        aAddon.userDisabled = false;
      },
      getTooltip(aAddon) {
        if (!aAddon)
          return "";
        if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
          return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
        return gStrings.ext.GetStringFromName("enableAddonTooltip");
      }
    },

    cmd_disableItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        let addonType = AddonManager.addonTypes[aAddon.type];
        return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                hasPermission(aAddon, "disable"));
      },
      doCommand(aAddon) {
        aAddon.userDisabled = true;
      },
      getTooltip(aAddon) {
        if (!aAddon)
          return "";
        if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
          return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip");
        return gStrings.ext.GetStringFromName("disableAddonTooltip");
      }
    },

    cmd_installItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE;
      },
      doCommand(aAddon) {
        function doInstall() {
          gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote();
        }

        if (gViewController.currentViewObj == gDetailView)
          gViewController.popState(doInstall);
        else
          doInstall();
      }
    },

    cmd_purchaseItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return !!aAddon.purchaseURL;
      },
      doCommand(aAddon) {
        openURL(aAddon.purchaseURL);
      }
    },

    cmd_uninstallItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return hasPermission(aAddon, "uninstall");
      },
      doCommand(aAddon) {
        if (gViewController.currentViewObj != gDetailView) {
          aAddon.uninstall();
          return;
        }

        gViewController.popState(function() {
          gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
        });
      },
      getTooltip(aAddon) {
        if (!aAddon)
          return "";
        if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL)
          return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip");
        return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
      }
    },

    cmd_cancelUninstallItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return isPending(aAddon, "uninstall");
      },
      doCommand(aAddon) {
        aAddon.cancelUninstall();
      }
    },

    cmd_installFromFile: {
      isEnabled() {
        return true;
      },
      doCommand() {
        const nsIFilePicker = Ci.nsIFilePicker;
        var fp = Cc["@mozilla.org/filepicker;1"]
                   .createInstance(nsIFilePicker);
        fp.init(window,
                gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
                nsIFilePicker.modeOpenMultiple);
        try {
          fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
                          "*.xpi;*.jar;*.zip");
          fp.appendFilters(nsIFilePicker.filterAll);
        } catch (e) { }

        fp.open(result => {
          if (result != nsIFilePicker.returnOK)
            return;

          let browser = getBrowserElement();
          let files = fp.files;
          while (files.hasMoreElements()) {
            let file = files.getNext();
            AddonManager.getInstallForFile(file, install => {
              AddonManager.installAddonFromAOM(browser, document.documentURIObject, install);
            });
          }
        });
      }
    },

    cmd_debugAddons: {
      isEnabled() {
        return true;
      },
      doCommand() {
        let mainWindow = getMainWindow();
        if ("switchToTabHavingURI" in mainWindow) {
          mainWindow.switchToTabHavingURI("about:debugging#addons", true, {
            triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
          });
        }
      },
    },

    cmd_cancelOperation: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return aAddon.pendingOperations != AddonManager.PENDING_NONE;
      },
      doCommand(aAddon) {
        if (isPending(aAddon, "install")) {
          aAddon.install.cancel();
        } else if (isPending(aAddon, "upgrade")) {
          aAddon.pendingUpgrade.install.cancel();
        } else if (isPending(aAddon, "uninstall")) {
          aAddon.cancelUninstall();
        } else if (isPending(aAddon, "enable")) {
          aAddon.userDisabled = true;
        } else if (isPending(aAddon, "disable")) {
          aAddon.userDisabled = false;
        }
      }
    },

    cmd_contribute: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        return ("contributionURL" in aAddon && aAddon.contributionURL);
      },
      doCommand(aAddon) {
        openURL(aAddon.contributionURL);
      }
    },

    cmd_askToActivateItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        let addonType = AddonManager.addonTypes[aAddon.type];
        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                hasPermission(aAddon, "ask_to_activate"));
      },
      doCommand(aAddon) {
        aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
      }
    },

    cmd_alwaysActivateItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        let addonType = AddonManager.addonTypes[aAddon.type];
        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                hasPermission(aAddon, "enable"));
      },
      doCommand(aAddon) {
        aAddon.userDisabled = false;
      }
    },

    cmd_neverActivateItem: {
      isEnabled(aAddon) {
        if (!aAddon)
          return false;
        let addonType = AddonManager.addonTypes[aAddon.type];
        return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
                hasPermission(aAddon, "disable"));
      },
      doCommand(aAddon) {
        aAddon.userDisabled = true;
      }
    },

    cmd_experimentsLearnMore: {
      isEnabled() {
        let mainWindow = getMainWindow();
        return mainWindow && "switchToTabHavingURI" in mainWindow;
      },
      doCommand() {
        let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
        openOptionsInTab(url);
      },
    },

    cmd_experimentsOpenTelemetryPreferences: {
      isEnabled() {
        return !!getMainWindowWithPreferencesPane();
      },
      doCommand() {
        let mainWindow = getMainWindowWithPreferencesPane();
        // The advanced subpanes are only supported in the old organization, which will
        // be removed by bug 1349689.
        if (Preferences.get("browser.preferences.useOldOrganization")) {
          mainWindow.openAdvancedPreferences("dataChoicesTab", {origin: "experimentsOpenPref"});
        } else {
          mainWindow.openPreferences("privacy-reports", {origin: "experimentsOpenPref"});
        }
      },
    },

    cmd_showUnsignedExtensions: {
      isEnabled() {
        return true;
      },
      doCommand() {
        gViewController.loadView("addons://list/extension?unsigned=true");
      },
    },

    cmd_showAllExtensions: {
      isEnabled() {
        return true;
      },
      doCommand() {
        gViewController.loadView("addons://list/extension");
      },
    },
  },

  supportsCommand(aCommand) {
    return (aCommand in this.commands);
  },

  isCommandEnabled(aCommand) {
    if (!this.supportsCommand(aCommand))
      return false;
    var addon = this.currentViewObj.getSelectedAddon();
    return this.commands[aCommand].isEnabled(addon);
  },

  updateCommands() {
    // wait until the view is initialized
    if (!this.currentViewObj)
      return;
    var addon = this.currentViewObj.getSelectedAddon();
    for (let commandId in this.commands)
      this.updateCommand(commandId, addon);
  },

  updateCommand(aCommandId, aAddon) {
    if (typeof aAddon == "undefined")
      aAddon = this.currentViewObj.getSelectedAddon();
    var cmd = this.commands[aCommandId];
    var cmdElt = document.getElementById(aCommandId);
    cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon));
    if ("getTooltip" in cmd) {
      let tooltip = cmd.getTooltip(aAddon);
      if (tooltip)
        cmdElt.setAttribute("tooltiptext", tooltip);
      else
        cmdElt.removeAttribute("tooltiptext");
    }
  },

  doCommand(aCommand, aAddon) {
    if (!this.supportsCommand(aCommand))
      return;
    var cmd = this.commands[aCommand];
    if (!aAddon)
      aAddon = this.currentViewObj.getSelectedAddon();
    if (!cmd.isEnabled(aAddon))
      return;
    cmd.doCommand(aAddon);
  },

  onEvent() {}
};

function hasInlineOptions(aAddon) {
  return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
          aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER ||
          aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO);
}

function openOptionsInTab(optionsURL) {
  let mainWindow = getMainWindow();
  if ("switchToTabHavingURI" in mainWindow) {
    mainWindow.switchToTabHavingURI(optionsURL, true, {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
    return true;
  }
  return false;
}

function formatDate(aDate) {
  const dtOptions = { year: "numeric", month: "long", day: "numeric" };
  return aDate.toLocaleDateString(undefined, dtOptions);
}


function hasPermission(aAddon, aPerm) {
  var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
  return !!(aAddon.permissions & perm);
}


function isPending(aAddon, aAction) {
  var action = AddonManager["PENDING_" + aAction.toUpperCase()];
  return !!(aAddon.pendingOperations & action);
}

function isInState(aInstall, aState) {
  var state = AddonManager["STATE_" + aState.toUpperCase()];
  return aInstall.state == state;
}

function shouldShowVersionNumber(aAddon) {
  if (!aAddon.version)
    return false;

  // The version number is hidden for experiments.
  if (aAddon.type == "experiment")
    return false;

  // The version number is hidden for lightweight themes.
  if (aAddon.type == "theme")
    return !/@personas\.mozilla\.org$/.test(aAddon.id);

  return true;
}

function createItem(aObj, aIsInstall, aIsRemote) {
  let item = document.createElement("richlistitem");

  item.setAttribute("class", "addon addon-view");
  item.setAttribute("name", aObj.name);
  item.setAttribute("type", aObj.type);
  item.setAttribute("remote", !!aIsRemote);

  if (aIsInstall) {
    item.mInstall = aObj;

    if (aObj.state != AddonManager.STATE_INSTALLED) {
      item.setAttribute("status", "installing");
      return item;
    }
    aObj = aObj.addon;
  }

  item.mAddon = aObj;

  item.setAttribute("status", "installed");

  // set only attributes needed for sorting and XBL binding,
  // the binding handles the rest
  item.setAttribute("value", aObj.id);

  if (aObj.type == "experiment") {
    item.endDate = getExperimentEndDate(aObj);
  }

  return item;
}

function sortElements(aElements, aSortBy, aAscending) {
  // aSortBy is an Array of attributes to sort by, in decending
  // order of priority.

  const DATE_FIELDS = ["updateDate"];
  const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"];

  // We're going to group add-ons into the following buckets:
  //
  //  enabledInstalled
  //    * Enabled
  //    * Incompatible but enabled because compatibility checking is off
  //    * Waiting to be installed
  //    * Waiting to be enabled
  //
  //  pendingDisable
  //    * Waiting to be disabled
  //
  //  pendingUninstall
  //    * Waiting to be removed
  //
  //  disabledIncompatibleBlocked
  //    * Disabled
  //    * Incompatible
  //    * Blocklisted

  const UISTATE_ORDER = ["enabled", "askToActivate", "pendingDisable",
                         "pendingUninstall", "disabled"];

  function dateCompare(a, b) {
    var aTime = a.getTime();
    var bTime = b.getTime();
    if (aTime < bTime)
      return -1;
    if (aTime > bTime)
      return 1;
    return 0;
  }

  function numberCompare(a, b) {
    return a - b;
  }

  function stringCompare(a, b) {
    return a.localeCompare(b);
  }

  function uiStateCompare(a, b) {
    // If we're in descending order, swap a and b, because
    // we don't ever want to have descending uiStates
    if (!aAscending)
      [a, b] = [b, a];

    return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b));
  }

  function getValue(aObj, aKey) {
    if (!aObj)
      return null;

    if (aObj.hasAttribute(aKey))
      return aObj.getAttribute(aKey);

    var addon = aObj.mAddon || aObj.mInstall;
    var addonType = aObj.mAddon && AddonManager.addonTypes[aObj.mAddon.type];

    if (!addon)
      return null;

    if (aKey == "uiState") {
      if (addon.pendingOperations == AddonManager.PENDING_DISABLE)
        return "pendingDisable";
      if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL)
        return "pendingUninstall";
      if (!addon.isActive &&
          (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
           addon.pendingOperations != AddonManager.PENDING_INSTALL))
        return "disabled";
      if (addonType && (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
          addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE)
        return "askToActivate";
      return "enabled";
    }

    return addon[aKey];
  }

  // aSortFuncs will hold the sorting functions that we'll
  // use per element, in the correct order.
  var aSortFuncs = [];

  for (let i = 0; i < aSortBy.length; i++) {
    var sortBy = aSortBy[i];

    aSortFuncs[i] = stringCompare;

    if (sortBy == "uiState")
      aSortFuncs[i] = uiStateCompare;
    else if (DATE_FIELDS.indexOf(sortBy) != -1)
      aSortFuncs[i] = dateCompare;
    else if (NUMERIC_FIELDS.indexOf(sortBy) != -1)
      aSortFuncs[i] = numberCompare;
  }


  aElements.sort(function(a, b) {
    if (!aAscending)
      [a, b] = [b, a];

    for (let i = 0; i < aSortFuncs.length; i++) {
      var sortBy = aSortBy[i];
      var aValue = getValue(a, sortBy);
      var bValue = getValue(b, sortBy);

      if (!aValue && !bValue)
        return 0;
      if (!aValue)
        return -1;
      if (!bValue)
        return 1;
      if (aValue != bValue) {
        var result = aSortFuncs[i](aValue, bValue);

        if (result != 0)
          return result;
      }
    }

    // If we got here, then all values of a and b
    // must have been equal.
    return 0;

  });
}

function sortList(aList, aSortBy, aAscending) {
  var elements = Array.slice(aList.childNodes, 0);
  sortElements(elements, [aSortBy], aAscending);

  while (aList.listChild)
    aList.removeChild(aList.lastChild);

  for (let element of elements)
    aList.appendChild(element);
}

function getAddonsAndInstalls(aType, aCallback) {
  let addons = null, installs = null;
  let types = (aType != null) ? [aType] : null;

  AddonManager.getAddonsByTypes(types, function(aAddonsList) {
    addons = aAddonsList.filter(a => !a.hidden);
    if (installs != null)
      aCallback(addons, installs);
  });

  AddonManager.getInstallsByTypes(types, function(aInstallsList) {
    // skip over upgrade installs and non-active installs
    installs = aInstallsList.filter(function(aInstall) {
      return !(aInstall.existingAddon ||
               aInstall.state == AddonManager.STATE_AVAILABLE);
    });

    if (addons != null)
      aCallback(addons, installs)
  });
}

function doPendingUninstalls(aListBox) {
  // Uninstalling add-ons can mutate the list so find the add-ons first then
  // uninstall them
  var items = [];
  var listitem = aListBox.firstChild;
  while (listitem) {
    if (listitem.getAttribute("pending") == "uninstall" &&
        !(listitem.opRequiresRestart("UNINSTALL")))
      items.push(listitem.mAddon);
    listitem = listitem.nextSibling;
  }

  for (let addon of items)
    addon.uninstall();
}

var gCategories = {
  node: null,
  _search: null,

  initialize() {
    this.node = document.getElementById("categories");
    this._search = this.get("addons://search/");

    var types = AddonManager.addonTypes;
    for (var type in types)
      this.onTypeAdded(types[type]);

    AddonManager.addTypeListener(this);

    // eslint-disable-next-line mozilla/use-default-preference-values
    try {
      this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
    } catch (e) { }

    // If there was no last view or no existing category matched the last view
    // then the list will default to selecting the search category and we never
    // want to show that as the first view so switch to the default category
    if (!this.node.selectedItem || this.node.selectedItem == this._search)
      this.node.value = gViewDefault;

    this.node.addEventListener("select", () => {
      this.maybeHideSearch();
      gViewController.loadView(this.node.selectedItem.value);
    });

    this.node.addEventListener("click", (aEvent) => {
      var selectedItem = this.node.selectedItem;
      if (aEvent.target.localName == "richlistitem" &&
          aEvent.target == selectedItem) {
        var viewId = selectedItem.value;

        if (gViewController.parseViewId(viewId).type == "search") {
          viewId += encodeURIComponent(gHeader.searchQuery);
        }

        gViewController.loadView(viewId);
      }
    });
  },

  shutdown() {
    AddonManager.removeTypeListener(this);
  },

  _insertCategory(aId, aName, aView, aPriority, aStartHidden) {
    // If this category already exists then don't re-add it
    if (document.getElementById("category-" + aId))
      return;

    var category = document.createElement("richlistitem");
    category.setAttribute("id", "category-" + aId);
    category.setAttribute("value", aView);
    category.setAttribute("class", "category");
    category.setAttribute("name", aName);
    category.setAttribute("tooltiptext", aName);
    category.setAttribute("priority", aPriority);
    category.setAttribute("hidden", aStartHidden);

    var node;
    for (node of this.node.children) {
      var nodePriority = parseInt(node.getAttribute("priority"));
      // If the new type's priority is higher than this one then this is the
      // insertion point
      if (aPriority < nodePriority)
        break;
      // If the new type's priority is lower than this one then this is isn't
      // the insertion point
      if (aPriority > nodePriority)
        continue;
      // If the priorities are equal and the new type's name is earlier
      // alphabetically then this is the insertion point
      if (String(aName).localeCompare(node.getAttribute("name")) < 0)
        break;
    }

    this.node.insertBefore(category, node);
  },

  _removeCategory(aId) {
    var category = document.getElementById("category-" + aId);
    if (!category)
      return;

    // If this category is currently selected then switch to the default view
    if (this.node.selectedItem == category)
      gViewController.replaceView(gViewDefault);

    this.node.removeChild(category);
  },

  onTypeAdded(aType) {
    // Ignore types that we don't have a view object for
    if (!(aType.viewType in gViewController.viewObjects))
      return;

    var aViewId = "addons://" + aType.viewType + "/" + aType.id;

    var startHidden = false;
    if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
      var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
      startHidden = Services.prefs.getBoolPref(prefName, true);

      gPendingInitializations++;
      getAddonsAndInstalls(aType.id, (aAddonsList, aInstallsList) => {
        var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
        var item = this.get(aViewId);

        // Don't load view that is becoming hidden
        if (hidden && aViewId == gViewController.currentViewId)
          gViewController.loadView(gViewDefault);

        item.hidden = hidden;
        Services.prefs.setBoolPref(prefName, hidden);

        if (aAddonsList.length > 0 || aInstallsList.length > 0) {
          notifyInitialized();
          return;
        }

        gEventManager.registerInstallListener({
          onDownloadStarted(aInstall) {
            this._maybeShowCategory(aInstall);
          },

          onInstallStarted(aInstall) {
            this._maybeShowCategory(aInstall);
          },

          onInstallEnded(aInstall, aAddon) {
            this._maybeShowCategory(aAddon);
          },

          onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
            this._maybeShowCategory(aAddon);
          },

          _maybeShowCategory: aAddon => {
            if (aType.id == aAddon.type) {
              this.get(aViewId).hidden = false;
              Services.prefs.setBoolPref(prefName, false);
              gEventManager.unregisterInstallListener(this);
            }
          }
        });

        notifyInitialized();
      });
    }

    this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
                         startHidden);
  },

  onTypeRemoved(aType) {
    this._removeCategory(aType.id);
  },

  get selected() {
    return this.node.selectedItem ? this.node.selectedItem.value : null;
  },

  select(aId, aPreviousView) {
    var view = gViewController.parseViewId(aId);
    if (view.type == "detail" && aPreviousView) {
      aId = aPreviousView;
      view = gViewController.parseViewId(aPreviousView);
    }
    aId = aId.replace(/\?.*/, "");

    Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId);

    if (this.node.selectedItem &&
        this.node.selectedItem.value == aId) {
      this.node.selectedItem.hidden = false;
      this.node.selectedItem.disabled = false;
      return;
    }

    var item;
    if (view.type == "search")
      item = this._search;
    else
      item = this.get(aId);

    if (item) {
      item.hidden = false;
      item.disabled = false;
      this.node.suppressOnSelect = true;
      this.node.selectedItem = item;
      this.node.suppressOnSelect = false;
      this.node.ensureElementIsVisible(item);

      this.maybeHideSearch();
    }
  },

  get(aId) {
    var items = document.getElementsByAttribute("value", aId);
    if (items.length)
      return items[0];
    return null;
  },

  setBadge(aId, aCount) {
    let item = this.get(aId);
    if (item)
      item.badgeCount = aCount;
  },

  maybeHideSearch() {
    var view = gViewController.parseViewId(this.node.selectedItem.value);
    this._search.disabled = view.type != "search";
  }
};


var gHeader = {
  _search: null,
  _dest: "",

  initialize() {
    this._search = document.getElementById("header-search");

    this._search.addEventListener("command", function(aEvent) {
      var query = aEvent.target.value;
      if (query.length == 0)
        return;

      gViewController.loadView("addons://search/" + encodeURIComponent(query));
    });

    function updateNavButtonVisibility() {
      var shouldShow = gHeader.shouldShowNavButtons;
      document.getElementById("back-btn").hidden = !shouldShow;
      document.getElementById("forward-btn").hidden = !shouldShow;
    }

    window.addEventListener("focus", function(aEvent) {
      if (aEvent.target == window)
        updateNavButtonVisibility();
    });

    updateNavButtonVisibility();
  },

  focusSearchBox() {
    this._search.focus();
  },

  onKeyPress(aEvent) {
    if (String.fromCharCode(aEvent.charCode) == "/") {
      this.focusSearchBox();
    }
  },

  get shouldShowNavButtons() {
    var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIWebNavigation)
                             .QueryInterface(Ci.nsIDocShellTreeItem);

    // If there is no outer frame then make the buttons visible
    if (docshellItem.rootTreeItem == docshellItem)
      return true;

    var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
                                            .getInterface(Ci.nsIDOMWindow);
    var outerDoc = outerWin.document;
    var node = outerDoc.getElementById("back-button");
    // If the outer frame has no back-button then make the buttons visible
    if (!node)
      return true;

    // If the back-button or any of its parents are hidden then make the buttons
    // visible
    while (node != outerDoc) {
      var style = outerWin.getComputedStyle(node);
      if (style.display == "none")
        return true;
      if (style.visibility != "visible")
        return true;
      node = node.parentNode;
    }

    return false;
  },

  get searchQuery() {
    return this._search.value;
  },

  set searchQuery(aQuery) {
    this._search.value = aQuery;
  },
};


var gDiscoverView = {
  node: null,
  enabled: true,
  // Set to true after the view is first shown. If initialization completes
  // after this then it must also load the discover homepage
  loaded: false,
  _browser: null,
  _loading: null,
  _error: null,
  homepageURL: null,
  _loadListeners: [],
  hideHeader: true,

  initialize() {
    this.enabled = isDiscoverEnabled();
    if (!this.enabled) {
      gCategories.get("addons://discover/").hidden = true;
      return;
    }

    this.node = document.getElementById("discover-view");
    this._loading = document.getElementById("discover-loading");
    this._error = document.getElementById("discover-error");
    this._browser = document.getElementById("discover-browser");

    let compatMode = "normal";
    if (!AddonManager.checkCompatibility)
      compatMode = "ignore";
    else if (AddonManager.strictCompatibility)
      compatMode = "strict";

    var url = Services.prefs.getCharPref(PREF_DISCOVERURL);
    url = url.replace("%COMPATIBILITY_MODE%", compatMode);
    url = Services.urlFormatter.formatURL(url);

    let setURL = (aURL) => {
      try {
        this.homepageURL = Services.io.newURI(aURL);
      } catch (e) {
        this.showError();
        notifyInitialized();
        return;
      }

      this._browser.homePage = this.homepageURL.spec;
      this._browser.addProgressListener(this);

      if (this.loaded)
        this._loadURL(this.homepageURL.spec, false, notifyInitialized);
      else
        notifyInitialized();
    }

    if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) {
      setURL(url);
      return;
    }

    gPendingInitializations++;
    AddonManager.getAllAddons(function(aAddons) {
      var list = {};
      for (let addon of aAddons) {
        var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%",
                                                               addon.id);
        try {
          if (!Services.prefs.getBoolPref(prefName))
            continue;
        } catch (e) { }
        list[addon.id] = {
          name: addon.name,
          version: addon.version,
          type: addon.type,
          userDisabled: addon.userDisabled,
          isCompatible: addon.isCompatible,
          isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
        }
      }

      setURL(url + "#" + JSON.stringify(list));
    });
  },

  destroy() {
    try {
      this._browser.removeProgressListener(this);
    } catch (e) {
      // Ignore the case when the listener wasn't already registered
    }
  },

  show(aParam, aRequest, aState, aIsRefresh) {
    gViewController.updateCommands();

    // If we're being told to load a specific URL then just do that
    if (aState && "url" in aState) {
      this.loaded = true;
      this._loadURL(aState.url);
    }

    // If the view has loaded before and still at the homepage (if refreshing),
    // and the error page is not visible then there is nothing else to do
    if (this.loaded && this.node.selectedPanel != this._error &&
        (!aIsRefresh || (this._browser.currentURI &&
         this._browser.currentURI.spec == this._browser.homePage))) {
      gViewController.notifyViewChanged();
      return;
    }

    this.loaded = true;

    // No homepage means initialization isn't complete, the browser will get
    // loaded once initialization is complete
    if (!this.homepageURL) {
      this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
      return;
    }

    this._loadURL(this.homepageURL.spec, aIsRefresh,
                  gViewController.notifyViewChanged.bind(gViewController));
  },

  canRefresh() {
    if (this._browser.currentURI &&
        this._browser.currentURI.spec == this._browser.homePage)
      return false;
    return true;
  },

  refresh(aParam, aRequest, aState) {
    this.show(aParam, aRequest, aState, true);
  },

  hide() { },

  showError() {
    this.node.selectedPanel = this._error;
  },

  _loadURL(aURL, aKeepHistory, aCallback) {
    if (this._browser.currentURI.spec == aURL) {
      if (aCallback)
        aCallback();
      return;
    }

    if (aCallback)
      this._loadListeners.push(aCallback);

    var flags = 0;
    if (!aKeepHistory)
      flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;

    this._browser.loadURIWithFlags(aURL, flags);
  },

  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    // Ignore the about:blank load
    if (aLocation.spec == "about:blank")
      return;

    // When using the real session history the inner-frame will update the
    // session history automatically, if using the fake history though it must
    // be manually updated
    if (gHistory == FakeHistory) {
      var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);

      var state = {
        view: "addons://discover/",
        url: aLocation.spec
      };

      var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
      if (docshell.loadType & replaceHistory)
        gHistory.replaceState(state);
      else
        gHistory.pushState(state);
      gViewController.lastHistoryIndex = gHistory.index;
    }

    gViewController.updateCommands();

    // If the hostname is the same as the new location's host and either the
    // default scheme is insecure or the new location is secure then continue
    // with the load
    if (aLocation.host == this.homepageURL.host &&
        (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
      return;

    // Canceling the request will send an error to onStateChange which will show
    // the error page
    aRequest.cancel(Components.results.NS_BINDING_ABORTED);
  },

  onSecurityChange(aWebProgress, aRequest, aState) {
    // Don't care about security if the page is not https
    if (!this.homepageURL.schemeIs("https"))
      return;

    // If the request was secure then it is ok
    if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE)
      return;

    // Canceling the request will send an error to onStateChange which will show
    // the error page
    aRequest.cancel(Components.results.NS_BINDING_ABORTED);
  },

  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
                        Ci.nsIWebProgressListener.STATE_IS_REQUEST |
                        Ci.nsIWebProgressListener.STATE_TRANSFERRING;
    // Once transferring begins show the content
    if ((aStateFlags & transferStart) === transferStart)
      this.node.selectedPanel = this._browser;

    // Only care about the network events
    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
      return;

    // If this is the start of network activity then show the loading page
    if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
      this.node.selectedPanel = this._loading;

    // Ignore anything except stop events
    if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
      return;

    // Consider the successful load of about:blank as still loading
    if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank")
      return;

    // If there was an error loading the page or the new hostname is not the
    // same as the default hostname or the default scheme is secure and the new
    // scheme is insecure then show the error page
    const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
    if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) ||
        (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
      this.showError();
    } else {
      // Got a successful load, make sure the browser is visible
      this.node.selectedPanel = this._browser;
      gViewController.updateCommands();
    }

    var listeners = this._loadListeners;
    this._loadListeners = [];

    for (let listener of listeners)
      listener();
  },

  onProgressChange() { },
  onStatusChange() { },

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                         Ci.nsISupportsWeakReference]),

  getSelectedAddon() {
    return null;
  }
};


var gCachedAddons = {};

var gSearchView = {
  node: null,
  _filter: null,
  _sorters: null,
  _loading: null,
  _listBox: null,
  _emptyNotice: null,
  _allResultsLink: null,
  _lastQuery: null,
  _lastRemoteTotal: 0,
  _pendingSearches: 0,

  initialize() {
    this.node = document.getElementById("search-view");
    this._filter = document.getElementById("search-filter-radiogroup");
    this._sorters = document.getElementById("search-sorters");
    this._sorters.handler = this;
    this._loading = document.getElementById("search-loading");
    this._listBox = document.getElementById("search-list");
    this._emptyNotice = document.getElementById("search-list-empty");
    this._allResultsLink = document.getElementById("search-allresults-link");

    if (!AddonManager.isInstallEnabled("application/x-xpinstall"))
      this._filter.hidden = true;

    this._listBox.addEventListener("keydown", aEvent => {
      if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
        var item = this._listBox.selectedItem;
        if (item)
          item.showInDetailView();
      }
    });

    this._filter.addEventListener("command", () => this.updateView());
  },

  shutdown() {
    if (AddonRepository.isSearching)
      AddonRepository.cancelSearch();
  },

  get isSearching() {
    return this._pendingSearches > 0;
  },

  show(aQuery, aRequest) {
    gEventManager.registerInstallListener(this);

    this.showEmptyNotice(false);
    this.showAllResultsLink(0);
    this.showLoading(true);
    this._sorters.showprice = false;

    gHeader.searchQuery = aQuery;
    aQuery = aQuery.trim().toLocaleLowerCase();
    if (this._lastQuery == aQuery) {
      this.updateView();
      gViewController.notifyViewChanged();
      return;
    }
    this._lastQuery = aQuery;

    if (AddonRepository.isSearching)
      AddonRepository.cancelSearch();

    while (this._listBox.firstChild.localName == "richlistitem")
      this._listBox.firstChild.remove();

    gCachedAddons = {};
    this._pendingSearches = 2;
    this._sorters.setSort("relevancescore", false);

    var elements = [];

    let createSearchResults = (aObjsList, aIsInstall, aIsRemote) => {
      for (let index in aObjsList) {
        let obj = aObjsList[index];
        let score = aObjsList.length - index;
        if (!aIsRemote && aQuery.length > 0) {
          score = this.getMatchScore(obj, aQuery);
          if (score == 0)
            continue;
        }

        let item = createItem(obj, aIsInstall, aIsRemote);
        item.setAttribute("relevancescore", score);
        if (aIsRemote) {
          gCachedAddons[obj.id] = obj;
          if (obj.purchaseURL)
            this._sorters.showprice = true;
        }

        elements.push(item);
      }
    }

    let finishSearch = (createdCount) => {
      if (elements.length > 0) {
        sortElements(elements, [this._sorters.sortBy], this._sorters.ascending);
        for (let element of elements)
          this._listBox.insertBefore(element, this._listBox.lastChild);
        this.updateListAttributes();
      }

      this._pendingSearches--;
      this.updateView();

      if (!this.isSearching)
        gViewController.notifyViewChanged();
    }

    getAddonsAndInstalls(null, function(aAddons, aInstalls) {
      if (gViewController && aRequest != gViewController.currentViewRequest)
        return;

      createSearchResults(aAddons, false, false);
      createSearchResults(aInstalls, true, false);
      finishSearch();
    });

    var maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS, 0);

    if (maxRemoteResults <= 0) {
      finishSearch(0);
      return;
    }

    AddonRepository.searchAddons(aQuery, maxRemoteResults, {
      searchFailed: () => {
        if (gViewController && aRequest != gViewController.currentViewRequest)
          return;

        this._lastRemoteTotal = 0;

        // XXXunf Better handling of AMO search failure. See bug 579502
        finishSearch(0); // Silently fail
      },

      searchSucceeded: (aAddonsList, aAddonCount, aTotalResults) => {
        if (gViewController && aRequest != gViewController.currentViewRequest)
          return;

        if (aTotalResults > maxRemoteResults)
          this._lastRemoteTotal = aTotalResults;
        else
          this._lastRemoteTotal = 0;

        var createdCount = createSearchResults(aAddonsList, false, true);
        finishSearch(createdCount);
      }
    });
  },

  showLoading(aLoading) {
    this._loading.hidden = !aLoading;
    this._listBox.hidden = aLoading;
  },

  updateView() {
    var showLocal = this._filter.value == "local";

    if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall"))
      showLocal = true;

    this._listBox.setAttribute("local", showLocal);
    this._listBox.setAttribute("remote", !showLocal);

    this.showLoading(this.isSearching && !showLocal);
    if (!this.isSearching) {
      var isEmpty = true;
      var results = this._listBox.getElementsByTagName("richlistitem");
      for (let result of results) {
        var isRemote = (result.getAttribute("remote") == "true");
        if ((isRemote && !showLocal) || (!isRemote && showLocal)) {
          isEmpty = false;
          break;
        }
      }

      this.showEmptyNotice(isEmpty);
      this.showAllResultsLink(this._lastRemoteTotal);
    }

    gViewController.updateCommands();
  },

  hide() {
    gEventManager.unregisterInstallListener(this);
    doPendingUninstalls(this._listBox);
  },

  getMatchScore(aObj, aQuery) {
    var score = 0;
    score += this.calculateMatchScore(aObj.name, aQuery,
                                      SEARCH_SCORE_MULTIPLIER_NAME);
    score += this.calculateMatchScore(aObj.description, aQuery,
                                      SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
    return score;
  },

  calculateMatchScore(aStr, aQuery, aMultiplier) {
    var score = 0;
    if (!aStr || aQuery.length == 0)
      return score;

    aStr = aStr.trim().toLocaleLowerCase();
    var haystack = aStr.split(/\s+/);
    var needles = aQuery.split(/\s+/);

    for (let needle of needles) {
      for (let hay of haystack) {
        if (hay == needle) {
          // matching whole words is best
          score += SEARCH_SCORE_MATCH_WHOLEWORD;
        } else {
          let i = hay.indexOf(needle);
          if (i == 0) // matching on word boundries is also good
            score += SEARCH_SCORE_MATCH_WORDBOUNDRY;
          else if (i > 0) // substring matches not so good
            score += SEARCH_SCORE_MATCH_SUBSTRING;
        }
      }
    }

    // give progressively higher score for longer queries, since longer queries
    // are more likely to be unique and therefore more relevant.
    if (needles.length > 1 && aStr.indexOf(aQuery) != -1)
      score += needles.length;

    return score * aMultiplier;
  },

  showEmptyNotice(aShow) {
    this._emptyNotice.hidden = !aShow;
    this._listBox.hidden = aShow;
  },

  showAllResultsLink(aTotalResults) {
    if (aTotalResults == 0) {
      this._allResultsLink.hidden = true;
      return;
    }

    var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults");
    linkStr = PluralForm.get(aTotalResults, linkStr);
    linkStr = linkStr.replace("#1", aTotalResults);
    this._allResultsLink.setAttribute("value", linkStr);

    this._allResultsLink.setAttribute("href",
                                      AddonRepository.getSearchURL(this._lastQuery));
    this._allResultsLink.hidden = false;
 },

  updateListAttributes() {
    var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
    if (item)
      item.removeAttribute("first");
    item = this._listBox.querySelector("richlistitem[remote='true'][last]");
    if (item)
      item.removeAttribute("last");
    var items = this._listBox.querySelectorAll("richlistitem[remote='true']");
    if (items.length > 0) {
      items[0].setAttribute("first", true);
      items[items.length - 1].setAttribute("last", true);
    }

    item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]");
    if (item)
      item.removeAttribute("first");
    item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]");
    if (item)
      item.removeAttribute("last");
    items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])");
    if (items.length > 0) {
      items[0].setAttribute("first", true);
      items[items.length - 1].setAttribute("last", true);
    }

  },

  onSortChanged(aSortBy, aAscending) {
    var footer = this._listBox.lastChild;
    this._listBox.removeChild(footer);

    sortList(this._listBox, aSortBy, aAscending);
    this.updateListAttributes();

    this._listBox.appendChild(footer);
  },

  onDownloadCancelled(aInstall) {
    this.removeInstall(aInstall);
  },

  onInstallCancelled(aInstall) {
    this.removeInstall(aInstall);
  },

  onInstallEnded(aInstall) {
    // If this is a webextension that was installed from this page,
    // display the post-install notification.
    if (!WEBEXT_PERMISSION_PROMPTS || !aInstall.addon.isWebExtension) {
      return;
    }

    for (let item of this._listBox.childNodes) {
      if (item.mInstall == aInstall) {
        let subject = {
          wrappedJSObject: {
            target: getBrowserElement(),
            addon: aInstall.addon,
          },
        };
        Services.obs.notifyObservers(subject, "webextension-install-notify");
        return;
      }
    }
  },

  removeInstall(aInstall) {
    for (let item of this._listBox.childNodes) {
      if (item.mInstall == aInstall) {
        this._listBox.removeChild(item);
        return;
      }
    }
  },

  getSelectedAddon() {
    var item = this._listBox.selectedItem;
    if (item)
      return item.mAddon;
    return null;
  },

  getListItemForID(aId) {
    var listitem = this._listBox.firstChild;
    while (listitem) {
      if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
        return listitem;
      listitem = listitem.nextSibling;
    }
    return null;
  }
};

var gLegacyView = {
  node: null,
  _listBox: null,
  _categoryItem: null,

  initialize() {
    this.node = document.getElementById("legacy-view");
    this._listBox = document.getElementById("legacy-list");
    this._categoryItem = gCategories.get("addons://legacy/");

    document.getElementById("legacy-learnmore").href = SUPPORT_URL + "webextensions";

    this.refresh();
  },

  async show(type, request) {
    let addons = await AddonManager.getAddonsByTypes(["extension"]);
    addons = addons.filter(a => !a.hidden &&
                              (isDisabledLegacy(a) || isDisabledUnsigned(a)));

    while (this._listBox.itemCount > 0)
      this._listBox.removeItemAt(0);

    let elements = addons.map(a => createItem(a));
    if (elements.length == 0) {
      gViewController.loadView("addons://list/extension");
      return;
    }

    sortElements(elements, ["uiState", "name"], true);
    for (let element of elements) {
      this._listBox.appendChild(element);
    }

    gViewController.notifyViewChanged();
  },

  hide() {
    doPendingUninstalls(this._listBox);
  },

  getSelectedAddon() {
    var item = this._listBox.selectedItem;
    if (item)
      return item.mAddon;
    return null;
  },

  async refresh() {
    if (legacyExtensionsEnabled) {
      this._categoryItem.disabled = true;
      return;
    }

    let extensions = await AddonManager.getAddonsByTypes(["extension"]);

    let haveUnsigned = false;
    let haveLegacy = false;
    for (let extension of extensions) {
      if (isDisabledUnsigned(extension)) {
        haveUnsigned = true;
      }
      if (isLegacyExtension(extension)) {
        haveLegacy = true;
      }
    }

    if (haveLegacy || haveUnsigned) {
      this._categoryItem.disabled = false;
      let name = gStrings.ext.GetStringFromName(`type.${haveUnsigned ? "unsupported" : "legacy"}.name`);
      this._categoryItem.setAttribute("name", name);
      this._categoryItem.tooltiptext = name;
    } else {
      this._categoryItem.disabled = true;
    }
  },
};

var gListView = {
  node: null,
  _listBox: null,
  _emptyNotice: null,
  _type: null,

  initialize() {
    this.node = document.getElementById("list-view");
    this._listBox = document.getElementById("addon-list");
    this._emptyNotice = document.getElementById("addon-list-empty");

    this._listBox.addEventListener("keydown", (aEvent) => {
      if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
        var item = this._listBox.selectedItem;
        if (item)
          item.showInDetailView();
      }
    });

    document.getElementById("signing-learn-more").setAttribute("href", SUPPORT_URL + "unsigned-addons");
    document.getElementById("legacy-extensions-learnmore-link").addEventListener("click", evt => {
      gViewController.loadView("addons://legacy/");
    });

    let findSignedAddonsLink = document.getElementById("find-alternative-addons");
    try {
      findSignedAddonsLink.setAttribute("href",
        Services.urlFormatter.formatURLPref("extensions.getAddons.link.url"));
    } catch (e) {
      findSignedAddonsLink.classList.remove("text-link");
    }

    try {
      document.getElementById("signing-dev-manual-link").setAttribute("href",
        Services.prefs.getCharPref("xpinstall.signatures.devInfoURL"));
    } catch (e) {
      document.getElementById("signing-dev-info").hidden = true;
    }

    if (Preferences.get("plugin.load_flash_only", true)) {
      document.getElementById("plugindeprecation-learnmore-link")
        .setAttribute("href", SUPPORT_URL + "npapi");
    } else {
      document.getElementById("plugindeprecation-notice").hidden = true;
    }

    if (Preferences.get("extensions.getAddons.themes.browseURL", "")) {
      document.getElementById("getthemes-learnmore-link")
        .setAttribute("href", Services.urlFormatter.formatURLPref("extensions.getAddons.themes.browseURL"));
    } else {
      document.getElementById("getthemes-container").hidden = true;
    }
  },

  show(aType, aRequest) {
    let showOnlyDisabledUnsigned = false;
    if (aType.endsWith("?unsigned=true")) {
      aType = aType.replace(/\?.*/, "");
      showOnlyDisabledUnsigned = true;
    }

    if (!(aType in AddonManager.addonTypes))
      throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG);

    this._type = aType;
    this.node.setAttribute("type", aType);
    this.showEmptyNotice(false);

    while (this._listBox.itemCount > 0)
      this._listBox.removeItemAt(0);

    if (aType == "plugin") {
      navigator.plugins.refresh(false);
    }

    getAddonsAndInstalls(aType, (aAddonsList, aInstallsList) => {
      if (gViewController && aRequest != gViewController.currentViewRequest)
        return;

      let showLegacyInfo = false;
      if (!legacyExtensionsEnabled) {
        let preLen = aAddonsList.length;
        aAddonsList = aAddonsList.filter(addon => !isLegacyExtension(addon) &&
                                                  !isDisabledUnsigned(addon));
        if (aAddonsList.length != preLen) {
          showLegacyInfo = true;
        }
      }

      var elements = [];

      for (let addonItem of aAddonsList)
        elements.push(createItem(addonItem));

      for (let installItem of aInstallsList)
        elements.push(createItem(installItem, true));

      this.showEmptyNotice(elements.length == 0);
      if (elements.length > 0) {
        sortElements(elements, ["uiState", "name"], true);
        for (let element of elements)
          this._listBox.appendChild(element);
      }

      this.filterDisabledUnsigned(showOnlyDisabledUnsigned);
      document.getElementById("legacy-extensions-notice").hidden = !showLegacyInfo;

      gEventManager.registerInstallListener(this);
      gViewController.updateCommands();
      gViewController.notifyViewChanged();
    });
  },

  hide() {
    gEventManager.unregisterInstallListener(this);
    doPendingUninstalls(this._listBox);
  },

  filterDisabledUnsigned(aFilter = true) {
    let foundDisabledUnsigned = false;

    if (AddonSettings.REQUIRE_SIGNING) {
      for (let item of this._listBox.childNodes) {
        if (!isCorrectlySigned(item.mAddon))
          foundDisabledUnsigned = true;
        else
          item.hidden = aFilter;
      }
    }

    document.getElementById("show-disabled-unsigned-extensions").hidden =
      aFilter || !foundDisabledUnsigned;

    document.getElementById("show-all-extensions").hidden = !aFilter;
    document.getElementById("disabled-unsigned-addons-info").hidden = !aFilter;
  },

  showEmptyNotice(aShow) {
    this._emptyNotice.hidden = !aShow;
    this._listBox.hidden = aShow;
  },

  onSortChanged(aSortBy, aAscending) {
    sortList(this._listBox, aSortBy, aAscending);
  },

  onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
    // The existing list item will take care of upgrade installs
    if (aExistingAddon)
      return;

    if (aAddon.hidden)
      return;

    this.addItem(aAddon);
  },

  onDownloadStarted(aInstall) {
    this.addItem(aInstall, true);
  },

  onInstallStarted(aInstall) {
    this.addItem(aInstall, true);
  },

  onDownloadCancelled(aInstall) {
    this.removeItem(aInstall, true);
  },

  onInstallCancelled(aInstall) {
    this.removeItem(aInstall, true);
  },

  onInstallEnded(aInstall) {
    // Remove any install entries for upgrades, their status will appear against
    // the existing item
    if (aInstall.existingAddon)
      this.removeItem(aInstall, true);

    if (aInstall.addon.type == "experiment") {
      let item = this.getListItemForID(aInstall.addon.id);
      if (item) {
        item.endDate = getExperimentEndDate(aInstall.addon);
      }
    }
  },

  addItem(aObj, aIsInstall) {
    if (aObj.type != this._type)
      return;

    if (aIsInstall && aObj.existingAddon)
      return;

    let prop = aIsInstall ? "mInstall" : "mAddon";
    for (let item of this._listBox.childNodes) {
      if (item[prop] == aObj)
        return;
    }

    let item = createItem(aObj, aIsInstall);
    this._listBox.insertBefore(item, this._listBox.firstChild);
    this.showEmptyNotice(false);
  },

  removeItem(aObj, aIsInstall) {
    let prop = aIsInstall ? "mInstall" : "mAddon";

    for (let item of this._listBox.childNodes) {
      if (item[prop] == aObj) {
        this._listBox.removeChild(item);
        this.showEmptyNotice(this._listBox.itemCount == 0);
        return;
      }
    }
  },

  getSelectedAddon() {
    var item = this._listBox.selectedItem;
    if (item)
      return item.mAddon;
    return null;
  },

  getListItemForID(aId) {
    var listitem = this._listBox.firstChild;
    while (listitem) {
      if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
        return listitem;
      listitem = listitem.nextSibling;
    }
    return null;
  }
};


var gDetailView = {
  node: null,
  _addon: null,
  _loadingTimer: null,
  _autoUpdate: null,

  initialize() {
    this.node = document.getElementById("detail-view");

    this._autoUpdate = document.getElementById("detail-autoUpdate");

    this._autoUpdate.addEventListener("command", () => {
      this._addon.applyBackgroundUpdates = this._autoUpdate.value;
    }, true);
  },

  shutdown() {
    AddonManager.removeManagerListener(this);
  },

  onUpdateModeChanged() {
    this.onPropertyChanged(["applyBackgroundUpdates"]);
  },

  _updateView(aAddon, aIsRemote, aScrollToPreferences) {
    AddonManager.addManagerListener(this);
    this.clearLoading();

    this._addon = aAddon;
    gEventManager.registerAddonListener(this, aAddon.id);
    gEventManager.registerInstallListener(this);

    this.node.setAttribute("type", aAddon.type);

    let legacy = false;
    if (!aAddon.install) {
      if (aAddon.type == "extension" && !aAddon.isWebExtension) {
        legacy = true;
      }
      if (aAddon.type == "theme") {
        // The logic here is kind of clunky but we want to mark complete
        // themes as legacy.  There's no explicit flag for complete
        // themes so explicitly check for new style themes (for which
        // isWebExtension is true) or lightweight themes (which have
        // ids that end with @personas.mozilla.org)
        legacy = !(aAddon.isWebExtension || aAddon.id.endsWith("@personas.mozilla.org"));
      }

      if (legacy && aAddon.signedState == AddonManager.SIGNEDSTATE_PRIVILEGED) {
        legacy = false;
      }

      // Exceptions that can slip through above: the default theme plus
      // test pilot addons until we get SIGNEDSTATE_PRIVILEGED deployed.
      if (legacy && legacyWarningExceptions.includes(aAddon.id)) {
        legacy = false;
      }
    }
    this.node.setAttribute("legacy", legacy);
    document.getElementById("detail-legacy-warning").href = SUPPORT_URL + "webextensions";

    // If the search category isn't selected then make sure to select the
    // correct category
    if (gCategories.selected != "addons://search/") {
      let category = (isDisabledLegacy(aAddon) || isDisabledUnsigned(aAddon)) ?
                     "addons://legacy" : `addons://list/${aAddon.type}`;
      gCategories.select(category);
    }

    document.getElementById("detail-name").textContent = aAddon.name;
    var icon = AddonManager.getPreferredIconURL(aAddon, 64, window);
    document.getElementById("detail-icon").src = icon ? icon : "";
    document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL);

    var version = document.getElementById("detail-version");
    if (shouldShowVersionNumber(aAddon)) {
      version.hidden = false;
      version.value = aAddon.version;
    } else {
      version.hidden = true;
    }

    var screenshotbox = document.getElementById("detail-screenshot-box");
    var screenshot = document.getElementById("detail-screenshot");
    if (aAddon.screenshots && aAddon.screenshots.length > 0) {
      if (aAddon.screenshots[0].thumbnailURL) {
        screenshot.src = aAddon.screenshots[0].thumbnailURL;
        screenshot.width = aAddon.screenshots[0].thumbnailWidth;
        screenshot.height = aAddon.screenshots[0].thumbnailHeight;
      } else {
        screenshot.src = aAddon.screenshots[0].url;
        screenshot.width = aAddon.screenshots[0].width;
        screenshot.height = aAddon.screenshots[0].height;
      }
      screenshot.setAttribute("loading", "true");
      screenshotbox.hidden = false;
    } else {
      screenshotbox.hidden = true;
    }

    var desc = document.getElementById("detail-desc");
    desc.textContent = aAddon.description;

    var fullDesc = document.getElementById("detail-fulldesc");
    if (aAddon.fullDescription) {
      // The following is part of an awful hack to include the licenses for GMP
      // plugins without having bug 624602 fixed yet, and intentionally ignores
      // localisation.
      if (aAddon.isGMPlugin) {
        // eslint-disable-next-line no-unsanitized/property
        fullDesc.innerHTML = aAddon.fullDescription;
      } else {
        fullDesc.textContent = aAddon.fullDescription;
      }

      fullDesc.hidden = false;
    } else {
      fullDesc.hidden = true;
    }

    var contributions = document.getElementById("detail-contributions");
    if ("contributionURL" in aAddon && aAddon.contributionURL) {
      contributions.hidden = false;
      var amount = document.getElementById("detail-contrib-suggested");
      if (aAddon.contributionAmount) {
        amount.value = gStrings.ext.formatStringFromName("contributionAmount2",
                                                         [aAddon.contributionAmount],
                                                         1);
        amount.hidden = false;
      } else {
        amount.hidden = true;
      }
    } else {
      contributions.hidden = true;
    }

    if ("purchaseURL" in aAddon && aAddon.purchaseURL) {
      var purchase = document.getElementById("detail-purchase-btn");
      purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label",
                                                         [aAddon.purchaseDisplayAmount],
                                                         1);
      purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey");
    }

    var updateDateRow = document.getElementById("detail-dateUpdated");
    if (aAddon.updateDate) {
      var date = formatDate(aAddon.updateDate);
      updateDateRow.value = date;
    } else {
      updateDateRow.value = null;
    }

    // TODO if the add-on was downloaded from releases.mozilla.org link to the
    // AMO profile (bug 590344)
    if (false) {
      document.getElementById("detail-repository-row").hidden = false;
      document.getElementById("detail-homepage-row").hidden = true;
      var repository = document.getElementById("detail-repository");
      repository.value = aAddon.homepageURL;
      repository.href = aAddon.homepageURL;
    } else if (aAddon.homepageURL) {
      document.getElementById("detail-repository-row").hidden = true;
      document.getElementById("detail-homepage-row").hidden = false;
      var homepage = document.getElementById("detail-homepage");
      homepage.value = aAddon.homepageURL;
      homepage.href = aAddon.homepageURL;
    } else {
      document.getElementById("detail-repository-row").hidden = true;
      document.getElementById("detail-homepage-row").hidden = true;
    }

    var rating = document.getElementById("detail-rating");
    if (aAddon.averageRating) {
      rating.averageRating = aAddon.averageRating;
      rating.hidden = false;
    } else {
      rating.hidden = true;
    }

    var reviews = document.getElementById("detail-reviews");
    if (aAddon.reviewURL) {
      var text = gStrings.ext.GetStringFromName("numReviews");
      text = PluralForm.get(aAddon.reviewCount, text)
      text = text.replace("#1", aAddon.reviewCount);
      reviews.value = text;
      reviews.hidden = false;
      reviews.href = aAddon.reviewURL;
    } else {
      reviews.hidden = true;
    }

    document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL;

    var sizeRow = document.getElementById("detail-size");
    if (aAddon.size && aIsRemote) {
      let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size));
      let formatted = gStrings.dl.GetStringFromName("doneSize");
      formatted = formatted.replace("#1", size).replace("#2", unit);
      sizeRow.value = formatted;
    } else {
      sizeRow.value = null;
    }

    var downloadsRow = document.getElementById("detail-downloads");
    if (aAddon.totalDownloads && aIsRemote) {
      var downloads = aAddon.totalDownloads;
      downloadsRow.value = downloads;
    } else {
      downloadsRow.value = null;
    }

    var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID;
    document.getElementById("detail-updates-row").hidden = !canUpdate;

    if ("applyBackgroundUpdates" in aAddon) {
      this._autoUpdate.hidden = false;
      this._autoUpdate.value = aAddon.applyBackgroundUpdates;
      let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
      document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
    } else {
      this._autoUpdate.hidden = true;
      document.getElementById("detail-findUpdates-btn").hidden = false;
    }

    document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
      !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);

    var gridRows = document.querySelectorAll("#detail-grid rows row");
    let first = true;
    for (let gridRow of gridRows) {
      if (first && window.getComputedStyle(gridRow).getPropertyValue("display") != "none") {
        gridRow.setAttribute("first-row", true);
        first = false;
      } else {
        gridRow.removeAttribute("first-row");
      }
    }

    if (this._addon.type == "experiment") {
      let prefix = "details.experiment.";
      let active = this._addon.isActive;

      let stateKey = prefix + "state." + (active ? "active" : "complete");
      let node = document.getElementById("detail-experiment-state");
      node.value = gStrings.ext.GetStringFromName(stateKey);

      let now = Date.now();
      let end = getExperimentEndDate(this._addon);
      let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);

      let timeKey = prefix + "time.";
      let timeMessage;
      if (days < 1) {
        timeKey += (active ? "endsToday" : "endedToday");
        timeMessage = gStrings.ext.GetStringFromName(timeKey);
      } else {
        timeKey += (active ? "daysRemaining" : "daysPassed");
        days = Math.round(days);
        let timeString = gStrings.ext.GetStringFromName(timeKey);
        timeMessage = PluralForm.get(days, timeString)
                                .replace("#1", days);
      }

      document.getElementById("detail-experiment-time").value = timeMessage;
    }

    this.fillSettingsRows(aScrollToPreferences, () => {
      this.updateState();
      gViewController.notifyViewChanged();
    });
  },

  show(aAddonId, aRequest) {
    let index = aAddonId.indexOf("/preferences");
    let scrollToPreferences = false;
    if (index >= 0) {
      aAddonId = aAddonId.substring(0, index);
      scrollToPreferences = true;
    }

    this._loadingTimer = setTimeout(() => {
      this.node.setAttribute("loading-extended", true);
    }, LOADING_MSG_DELAY);

    AddonManager.getAddonByID(aAddonId, (aAddon) => {
      if (gViewController && aRequest != gViewController.currentViewRequest)
        return;

      if (aAddon) {
        this._updateView(aAddon, false, scrollToPreferences);
        return;
      }

      // Look for an add-on pending install
      AddonManager.getAllInstalls(aInstalls => {
        for (let install of aInstalls) {
          if (install.state == AddonManager.STATE_INSTALLED &&
              install.addon.id == aAddonId) {
            this._updateView(install.addon, false);
            return;
          }
        }

        if (aAddonId in gCachedAddons) {
          this._updateView(gCachedAddons[aAddonId], true);
          return;
        }

        // This might happen due to session restore restoring us back to an
        // add-on that doesn't exist but otherwise shouldn't normally happen.
        // Either way just revert to the default view.
        gViewController.replaceView(gViewDefault);
      });
    });
  },

  hide() {
    AddonManager.removeManagerListener(this);
    this.clearLoading();
    if (this._addon) {
      if (hasInlineOptions(this._addon)) {
        Services.obs.notifyObservers(document,
                                     AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
                                     this._addon.id);
      }

      gEventManager.unregisterAddonListener(this, this._addon.id);
      gEventManager.unregisterInstallListener(this);
      this._addon = null;

      // Flush the preferences to disk so they survive any crash
      if (this.node.getElementsByTagName("setting").length)
        Services.prefs.savePrefFile(null);
    }
  },

  updateState() {
    gViewController.updateCommands();

    var pending = this._addon.pendingOperations;
    if (pending != AddonManager.PENDING_NONE) {
      this.node.removeAttribute("notification");

      pending = null;
      const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall",
                                  "upgrade"];
      for (let op of PENDING_OPERATIONS) {
        if (isPending(this._addon, op))
          pending = op;
      }

      this.node.setAttribute("pending", pending);
      document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName(
        "details.notification." + pending,
        [this._addon.name, gStrings.brandShortName], 2
      );
    } else {
      this.node.removeAttribute("pending");

      if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
        this.node.setAttribute("notification", "error");
        document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
          "details.notification.blocked",
          [this._addon.name], 1
        );
        var errorLink = document.getElementById("detail-error-link");
        errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
        errorLink.href = this._addon.blocklistURL;
        errorLink.hidden = false;
      } else if (isDisabledUnsigned(this._addon)) {
        this.node.setAttribute("notification", "error");
        document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
          "details.notification.unsignedAndDisabled", [this._addon.name, gStrings.brandShortName], 2
        );
        let errorLink = document.getElementById("detail-error-link");
        errorLink.value = gStrings.ext.GetStringFromName("details.notification.unsigned.link");
        errorLink.href = SUPPORT_URL + "unsigned-addons";
        errorLink.hidden = false;
      } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility ||
        (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
        this.node.setAttribute("notification", "warning");
        document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
          "details.notification.incompatible",
          [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
        );
        document.getElementById("detail-warning-link").hidden = true;
      } else if (this._addon.appDisabled && !this._addon.multiprocessCompatible && !ALLOW_NON_MPC) {
        this.node.setAttribute("notification", "error");
        document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
          "details.notification.nonMpcDisabled", [this._addon.name], 1
        );
        let errorLink = document.getElementById("detail-error-link");
        errorLink.value = gStrings.ext.GetStringFromName("details.notification.nonMpcDisabled.link");
        errorLink.href = "https://wiki.mozilla.org/Add-ons/ShimsNightly";
      } else if (!isCorrectlySigned(this._addon)) {
        this.node.setAttribute("notification", "warning");
        document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
          "details.notification.unsigned", [this._addon.name, gStrings.brandShortName], 2
        );
        var warningLink = document.getElementById("detail-warning-link");
        warningLink.value = gStrings.ext.GetStringFromName("details.notification.unsigned.link");
        warningLink.href = SUPPORT_URL + "unsigned-addons";
        warningLink.hidden = false;
      } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
        this.node.setAttribute("notification", "warning");
        document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
          "details.notification.softblocked",
          [this._addon.name], 1
        );
        let warningLink = document.getElementById("detail-warning-link");
        warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
        warningLink.href = this._addon.blocklistURL;
        warningLink.hidden = false;
      } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
        this.node.setAttribute("notification", "warning");
        document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
          "details.notification.outdated",
          [this._addon.name], 1
        );
        let warningLink = document.getElementById("detail-warning-link");
        warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link");
        warningLink.href = this._addon.blocklistURL;
        warningLink.hidden = false;
      } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
        this.node.setAttribute("notification", "error");
        document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
          "details.notification.vulnerableUpdatable",
          [this._addon.name], 1
        );
        let errorLink = document.getElementById("detail-error-link");
        errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link");
        errorLink.href = this._addon.blocklistURL;
        errorLink.hidden = false;
      } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
        this.node.setAttribute("notification", "error");
        document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
          "details.notification.vulnerableNoUpdate",
          [this._addon.name], 1
        );
        let errorLink = document.getElementById("detail-error-link");
        errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
        errorLink.href = this._addon.blocklistURL;
        errorLink.hidden = false;
      } else if (this._addon.isGMPlugin && !this._addon.isInstalled &&
                 this._addon.isActive) {
        this.node.setAttribute("notification", "warning");
        let warning = document.getElementById("detail-warning");
        warning.textContent =
          gStrings.ext.formatStringFromName("details.notification.gmpPending",
                                            [this._addon.name], 1);
      } else {
        this.node.removeAttribute("notification");
      }
    }

    let menulist = document.getElementById("detail-state-menulist");
    let addonType = AddonManager.addonTypes[this._addon.type];
    if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) {
      let askItem = document.getElementById("detail-ask-to-activate-menuitem");
      let alwaysItem = document.getElementById("detail-always-activate-menuitem");
      let neverItem = document.getElementById("detail-never-activate-menuitem");
      let hasActivatePermission =
        ["ask_to_activate", "enable", "disable"].some(perm => hasPermission(this._addon, perm));

      if (!this._addon.isActive) {
        menulist.selectedItem = neverItem;
      } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
        menulist.selectedItem = askItem;
      } else {
        menulist.selectedItem = alwaysItem;
      }

      menulist.disabled = !hasActivatePermission;
      menulist.hidden = false;
      menulist.classList.add("no-auto-hide");
    } else {
      menulist.hidden = true;
    }

    this.node.setAttribute("active", this._addon.isActive);
  },

  clearLoading() {
    if (this._loadingTimer) {
      clearTimeout(this._loadingTimer);
      this._loadingTimer = null;
    }

    this.node.removeAttribute("loading-extended");
  },

  emptySettingsRows() {
    var lastRow = document.getElementById("detail-downloads");
    var rows = lastRow.parentNode;
    while (lastRow.nextSibling)
      rows.removeChild(rows.lastChild);
  },

  fillSettingsRows(aScrollToPreferences, aCallback) {
    this.emptySettingsRows();
    if (!hasInlineOptions(this._addon)) {
      if (aCallback)
        aCallback();
      return;
    }

    // We can't use a promise for this, since some code (especially in tests)
    // relies on us finishing before the ViewChanged event bubbles up to its
    // listeners, and promises resolve asynchronously.
    let whenViewLoaded = callback => {
      if (gViewController.displayedView.hasAttribute("loading")) {
        gDetailView.node.addEventListener("ViewChanged", function() {
          callback();
        }, {once: true});
      } else {
        callback();
      }
    };

    let finish = (firstSetting) => {
      // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
      // then notify observers.
      whenViewLoaded(() => {
        if (firstSetting)
          firstSetting.clientTop;
        Services.obs.notifyObservers(document,
                                     AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
                                     this._addon.id);
        if (aScrollToPreferences)
          gDetailView.scrollToPreferencesRows();
      });
    }

    // This function removes and returns the text content of aNode without
    // removing any child elements. Removing the text nodes ensures any XBL
    // bindings apply properly.
    function stripTextNodes(aNode) {
      var text = "";
      for (var i = 0; i < aNode.childNodes.length; i++) {
        if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
          text += aNode.childNodes[i].textContent;
          aNode.removeChild(aNode.childNodes[i--]);
        } else {
          text += stripTextNodes(aNode.childNodes[i]);
        }
      }
      return text;
    }

    var rows = document.getElementById("detail-downloads").parentNode;

    try {
      if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
        whenViewLoaded(() => {
          this.createOptionsBrowser(rows).then(browser => {
            // Make sure the browser is unloaded as soon as we change views,
            // rather than waiting for the next detail view to load.
            document.addEventListener("ViewChanged", function() {
              browser.remove();
            }, {once: true});

            finish(browser);
          });
        });

        if (aCallback)
          aCallback();
      } else {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", this._addon.optionsURL, true);
        xhr.responseType = "xml";
        xhr.onload = (function() {
          var xml = xhr.responseXML;
          var settings = xml.querySelectorAll(":root > setting");

          var firstSetting = null;
          for (var setting of settings) {

            var desc = stripTextNodes(setting).trim();
            if (!setting.hasAttribute("desc"))
              setting.setAttribute("desc", desc);

            var type = setting.getAttribute("type");
            if (type == "file" || type == "directory")
              setting.setAttribute("fullpath", "true");

            setting = document.importNode(setting, true);
            var style = setting.getAttribute("style");
            if (style) {
              setting.removeAttribute("style");
              setting.setAttribute("style", style);
            }

            rows.appendChild(setting);
            var visible = window.getComputedStyle(setting).getPropertyValue("display") != "none";
            if (!firstSetting && visible) {
              setting.setAttribute("first-row", true);
              firstSetting = setting;
            }
          }

          finish(firstSetting);

          if (aCallback)
            aCallback();
        });
        xhr.onerror = function(aEvent) {
          Cu.reportError("Error " + aEvent.target.status +
                         " occurred while receiving " + this._addon.optionsURL);
          if (aCallback)
            aCallback();
        };
        xhr.send();
      }
    } catch (e) {
      Cu.reportError(e);
      if (aCallback)
        aCallback();
    }
  },

  scrollToPreferencesRows() {
    // We find this row, rather than remembering it from above,
    // in case it has been changed by the observers.
    let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
    if (firstRow) {
      let top = firstRow.boxObject.y;
      top -= parseInt(window.getComputedStyle(firstRow).getPropertyValue("margin-top"));

      let detailViewBoxObject = gDetailView.node.boxObject;
      top -= detailViewBoxObject.y;

      detailViewBoxObject.scrollTo(0, top);
    }
  },

  async createOptionsBrowser(parentNode) {
    let browser = document.createElement("browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("disableglobalhistory", "true");
    browser.setAttribute("id", "addon-options");
    browser.setAttribute("class", "inline-options-browser");
    browser.setAttribute("forcemessagemanager", "true");
    browser.setAttribute("selectmenulist", "ContentSelectDropdown");

    let {optionsURL} = this._addon;
    let remote = !E10SUtils.canLoadURIInProcess(optionsURL, Services.appinfo.PROCESS_TYPE_DEFAULT);

    let readyPromise;
    if (remote) {
      browser.setAttribute("remote", "true");
      browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
      readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
    } else {
      readyPromise = promiseEvent("load", browser, true);
    }

    parentNode.appendChild(browser);

    // Force bindings to apply synchronously.
    browser.clientTop;

    await readyPromise;
    ExtensionParent.apiManager.emit("extension-browser-inserted", browser);

    return new Promise(resolve => {
      let messageListener = {
        receiveMessage({name, data}) {
          if (name === "Extension:BrowserResized")
            browser.style.height = `${data.height}px`;
          else if (name === "Extension:BrowserContentLoaded")
            resolve(browser);
        },
      };

      let mm = browser.messageManager;
      mm.loadFrameScript("chrome://extensions/content/ext-browser-content.js",
                         false);
      mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
      mm.addMessageListener("Extension:BrowserResized", messageListener);

      let browserOptions = {
        fixedWidth: true,
        isInline: true,
      };

      if (this._addon.optionsBrowserStyle) {
        browserOptions.stylesheets = extensionStylesheets;
      }

      mm.sendAsyncMessage("Extension:InitBrowser", browserOptions);

      browser.loadURI(optionsURL);
    });
  },

  getSelectedAddon() {
    return this._addon;
  },

  onEnabling() {
    this.updateState();
  },

  onEnabled() {
    this.updateState();
    this.fillSettingsRows();
  },

  onDisabling(aNeedsRestart) {
    this.updateState();
    if (!aNeedsRestart && hasInlineOptions(this._addon)) {
      Services.obs.notifyObservers(document,
                                   AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
                                   this._addon.id);
    }
  },

  onDisabled() {
    this.updateState();
    this.emptySettingsRows();
  },

  onUninstalling() {
    this.updateState();
  },

  onUninstalled() {
    gViewController.popState();
  },

  onOperationCancelled() {
    this.updateState();
  },

  onPropertyChanged(aProperties) {
    if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
      this._autoUpdate.value = this._addon.applyBackgroundUpdates;
      let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
      document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
    }

    if (aProperties.indexOf("appDisabled") != -1 ||
        aProperties.indexOf("signedState") != -1 ||
        aProperties.indexOf("userDisabled") != -1)
      this.updateState();
  },

  onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
    // Only care about upgrades for the currently displayed add-on
    if (!aExistingAddon || aExistingAddon.id != this._addon.id)
      return;

    if (!aNeedsRestart)
      this._updateView(aAddon, false);
    else
      this.updateState();
  },

  onInstallCancelled(aInstall) {
    if (aInstall.addon.id == this._addon.id)
      gViewController.popState();
  }
};


var gUpdatesView = {
  node: null,
  _listBox: null,
  _emptyNotice: null,
  _sorters: null,
  _updateSelected: null,
  _categoryItem: null,

  initialize() {
    this.node = document.getElementById("updates-view");
    this._listBox = document.getElementById("updates-list");
    this._emptyNotice = document.getElementById("updates-list-empty");
    this._sorters = document.getElementById("updates-sorters");
    this._sorters.handler = this;

    this._categoryItem = gCategories.get("addons://updates/available");

    this._updateSelected = document.getElementById("update-selected-btn");
    this._updateSelected.addEventListener("command", function() {
      gUpdatesView.installSelected();
    });

    this.updateAvailableCount(true);

    AddonManager.addAddonListener(this);
    AddonManager.addInstallListener(this);
  },

  shutdown() {
    AddonManager.removeAddonListener(this);
    AddonManager.removeInstallListener(this);
  },

  show(aType, aRequest) {
    document.getElementById("empty-availableUpdates-msg").hidden = aType != "available";
    document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent";
    this.showEmptyNotice(false);

    while (this._listBox.itemCount > 0)
      this._listBox.removeItemAt(0);

    this.node.setAttribute("updatetype", aType);
    if (aType == "recent")
      this._showRecentUpdates(aRequest);
    else
      this._showAvailableUpdates(false, aRequest);
  },

  hide() {
    this._updateSelected.hidden = true;
    this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
    doPendingUninstalls(this._listBox);
  },

  _showRecentUpdates(aRequest) {
    AddonManager.getAllAddons((aAddonsList) => {
      if (gViewController && aRequest != gViewController.currentViewRequest)
        return;

      var elements = [];
      let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
      for (let addon of aAddonsList) {
        if (addon.hidden || !addon.updateDate || addon.updateDate.getTime() < threshold)
          continue;

        elements.push(createItem(addon));
      }

      this.showEmptyNotice(elements.length == 0);
      if (elements.length > 0) {
        sortElements(elements, [this._sorters.sortBy], this._sorters.ascending);
        for (let element of elements)
          this._listBox.appendChild(element);
      }

      gViewController.notifyViewChanged();
    });
  },

  _showAvailableUpdates(aIsRefresh, aRequest) {
    /* Disable the Update Selected button so it can't get clicked
       before everything is initialized asynchronously.
       It will get re-enabled by maybeDisableUpdateSelected(). */
    this._updateSelected.disabled = true;

    AddonManager.getAllInstalls((aInstallsList) => {
      if (!aIsRefresh && gViewController && aRequest &&
          aRequest != gViewController.currentViewRequest)
        return;

      if (aIsRefresh) {
        this.showEmptyNotice(false);
        this._updateSelected.hidden = true;

        while (this._listBox.childNodes.length > 0)
          this._listBox.firstChild.remove();
      }

      var elements = [];

      for (let install of aInstallsList) {
        if (!this.isManualUpdate(install))
          continue;

        let item = createItem(install.existingAddon);
        item.setAttribute("upgrade", true);
        item.addEventListener("IncludeUpdateChanged", () => {
          this.maybeDisableUpdateSelected();
        });
        elements.push(item);
      }

      this.showEmptyNotice(elements.length == 0);
      if (elements.length > 0) {
        this._updateSelected.hidden = false;
        sortElements(elements, [this._sorters.sortBy], this._sorters.ascending);
        for (let element of elements)
          this._listBox.appendChild(element);
      }

      // ensure badge count is in sync
      this._categoryItem.badgeCount = this._listBox.itemCount;

      gViewController.notifyViewChanged();
    });
  },

  showEmptyNotice(aShow) {
    this._emptyNotice.hidden = !aShow;
    this._listBox.hidden = aShow;
  },

  isManualUpdate(aInstall, aOnlyAvailable) {
    var isManual = aInstall.existingAddon &&
                   !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
    if (isManual && aOnlyAvailable)
      return isInState(aInstall, "available");
    return isManual;
  },

  maybeRefresh() {
    if (gViewController.currentViewId == "addons://updates/available")
      this._showAvailableUpdates(true);
    this.updateAvailableCount();
  },

  updateAvailableCount(aInitializing) {
    if (aInitializing)
      gPendingInitializations++;
    AddonManager.getAllInstalls((aInstallsList) => {
      var count = aInstallsList.filter(aInstall => {
        return this.isManualUpdate(aInstall, true);
      }).length;
      this._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
                                    count == 0;
      this._categoryItem.badgeCount = count;
      if (aInitializing)
        notifyInitialized();
    });
  },

  maybeDisableUpdateSelected() {
    for (let item of this._listBox.childNodes) {
      if (item.includeUpdate) {
        this._updateSelected.disabled = false;
        return;
      }
    }
    this._updateSelected.disabled = true;
  },

  installSelected() {
    for (let item of this._listBox.childNodes) {
      if (item.includeUpdate)
        item.upgrade();
    }

    this._updateSelected.disabled = true;
  },

  getSelectedAddon() {
    var item = this._listBox.selectedItem;
    if (item)
      return item.mAddon;
    return null;
  },

  getListItemForID(aId) {
    var listitem = this._listBox.firstChild;
    while (listitem) {
      if (listitem.mAddon.id == aId)
        return listitem;
      listitem = listitem.nextSibling;
    }
    return null;
  },

  onSortChanged(aSortBy, aAscending) {
    sortList(this._listBox, aSortBy, aAscending);
  },

  onNewInstall(aInstall) {
    if (!this.isManualUpdate(aInstall))
      return;
    this.maybeRefresh();
  },

  onInstallStarted(aInstall) {
    this.updateAvailableCount();
  },

  onInstallCancelled(aInstall) {
    if (!this.isManualUpdate(aInstall))
      return;
    this.maybeRefresh();
  },

  onPropertyChanged(aAddon, aProperties) {
    if (aProperties.indexOf("applyBackgroundUpdates") != -1)
      this.updateAvailableCount();
  }
};

var gDragDrop = {
  onDragOver(aEvent) {
    var types = aEvent.dataTransfer.types;
    if (types.includes("text/uri-list") ||
        types.includes("text/x-moz-url") ||
        types.includes("application/x-moz-file"))
      aEvent.preventDefault();
  },

  onDrop(aEvent) {
    let dataTransfer = aEvent.dataTransfer;
    let browser = getBrowserElement();

    // Convert every dropped item into a url and install it
    for (var i = 0; i < dataTransfer.mozItemCount; i++) {
      let url = dataTransfer.mozGetDataAt("text/uri-list", i);
      if (!url) {
        url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
      }
      if (url) {
        url = url.split("\n")[0];
      } else {
        let file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
        if (file) {
          url = Services.io.newFileURI(file).spec;
        }
      }

      if (url) {
        AddonManager.getInstallForURL(url, install => {
          AddonManager.installAddonFromAOM(browser, document.documentURIObject, install);
        }, "application/x-xpinstall");
      }
    }

    aEvent.preventDefault();
  }
};
PK
!<uE:E:8chrome/toolkit/content/mozapps/extensions/extensions.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE page [
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>

<!-- import-globals-from extensions.js -->

<bindings id="addonBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">


  <!-- Rating - displays current/average rating, allows setting user rating -->
  <binding id="rating">
    <content>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(1);"
                 onclick="document.getBindingParent(this).userRating = 1;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(2);"
                 onclick="document.getBindingParent(this).userRating = 2;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(3);"
                 onclick="document.getBindingParent(this).userRating = 3;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(4);"
                 onclick="document.getBindingParent(this).userRating = 4;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(5);"
                 onclick="document.getBindingParent(this).userRating = 5;"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._updateStars();
      ]]></constructor>

      <property name="stars" readonly="true">
        <getter><![CDATA[
          return document.getAnonymousNodes(this);
        ]]></getter>
      </property>

      <property name="averageRating">
        <getter><![CDATA[
          if (this.hasAttribute("averagerating"))
            return this.getAttribute("averagerating");
          return -1;
        ]]></getter>
        <setter><![CDATA[
          this.setAttribute("averagerating", val);
          if (this.showRating == "average")
            this._updateStars();
        ]]></setter>
      </property>

      <property name="userRating">
        <getter><![CDATA[
          if (this.hasAttribute("userrating"))
            return this.getAttribute("userrating");
          return -1;
        ]]></getter>
        <setter><![CDATA[
          if (this.showRating != "user")
            return;
          this.setAttribute("userrating", val);
          if (this.showRating == "user")
            this._updateStars();
        ]]></setter>
      </property>

      <property name="showRating">
        <getter><![CDATA[
          if (this.hasAttribute("showrating"))
            return this.getAttribute("showrating");
          return "average";
        ]]></getter>
        <setter><![CDATA[
          if (val != "average" || val != "user")
            throw Components.Exception("Invalid value", Components.results.NS_ERROR_ILLEGAL_VALUE);
          this.setAttribute("showrating", val);
          this._updateStars();
        ]]></setter>
      </property>

      <method name="_updateStars">
        <body><![CDATA[
          var stars = this.stars;
          var rating = this[this.showRating + "Rating"];
          // average ratings can be non-whole numbers, round them so they
          // match to their closest star
          rating = Math.round(rating);
          for (let i = 0; i < stars.length; i++)
            stars[i].setAttribute("on", rating > i);
        ]]></body>
      </method>

      <method name="_hover">
        <parameter name="aScore"/>
        <body><![CDATA[
          if (this.showRating != "user")
            return;
          var stars = this.stars;
          for (let i = 0; i < stars.length; i++)
            stars[i].setAttribute("on", i <= (aScore - 1));
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <handler event="mouseout">
        this._updateStars();
      </handler>
    </handlers>
  </binding>

  <!-- Download progress - shows graphical progress of download and any
       related status message. -->
  <binding id="download-progress">
    <content>
      <xul:stack flex="1">
        <xul:hbox flex="1">
          <xul:hbox class="start-cap"/>
          <xul:progressmeter anonid="progress" class="progress" flex="1"
                             min="0" max="100"/>
          <xul:hbox class="end-cap"/>
        </xul:hbox>
        <xul:hbox class="status-container">
          <xul:spacer flex="1"/>
          <xul:label anonid="status" class="status"/>
          <xul:spacer flex="1"/>
          <xul:button anonid="cancel-btn" class="cancel"
                      tooltiptext="&progress.cancel.tooltip;"
                      oncommand="document.getBindingParent(this).cancel();"/>
        </xul:hbox>
      </xul:stack>
    </content>

    <implementation>
      <constructor><![CDATA[
        var progress = 0;
        if (this.hasAttribute("progress"))
          progress = parseInt(this.getAttribute("progress"));
        this.progress = progress;
      ]]></constructor>

      <field name="_progress">
        document.getAnonymousElementByAttribute(this, "anonid", "progress");
      </field>
      <field name="_cancel">
        document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn");
      </field>
      <field name="_status">
        document.getAnonymousElementByAttribute(this, "anonid", "status");
      </field>

      <property name="progress">
        <getter><![CDATA[
          return this._progress.value;
        ]]></getter>
        <setter><![CDATA[
          this._progress.value = val;
          if (val == this._progress.max)
            this.setAttribute("complete", true);
          else
            this.removeAttribute("complete");
        ]]></setter>
      </property>

      <property name="maxProgress">
        <getter><![CDATA[
          return this._progress.max;
        ]]></getter>
        <setter><![CDATA[
          if (val == -1) {
            this._progress.mode = "undetermined";
          } else {
            this._progress.mode = "determined";
            this._progress.max = val;
          }
          this.setAttribute("mode", this._progress.mode);
        ]]></setter>
      </property>

      <property name="status">
        <getter><![CDATA[
          return this._status.value;
        ]]></getter>
        <setter><![CDATA[
          this._status.value = val;
        ]]></setter>
      </property>

      <method name="cancel">
        <body><![CDATA[
          this.mInstall.cancel();
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Sorters - displays and controls the sort state of a list. -->
  <binding id="sorters">
    <content orient="horizontal">
      <xul:button anonid="name-btn" class="sorter"
                  label="&sort.name.label;" tooltiptext="&sort.name.tooltip;"
                  oncommand="this.parentNode._handleChange('name');"/>
      <xul:button anonid="date-btn" class="sorter"
                  label="&sort.dateUpdated.label;"
                  tooltiptext="&sort.dateUpdated.tooltip;"
                  oncommand="this.parentNode._handleChange('updateDate');"/>
      <xul:button anonid="price-btn" class="sorter" hidden="true"
                  label="&sort.price.label;"
                  tooltiptext="&sort.price.tooltip;"
                  oncommand="this.parentNode._handleChange('purchaseAmount');"/>
      <xul:button anonid="relevance-btn" class="sorter" hidden="true"
                  label="&sort.relevance.label;"
                  tooltiptext="&sort.relevance.tooltip;"
                  oncommand="this.parentNode._handleChange('relevancescore');"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (!this.hasAttribute("sortby"))
          this.setAttribute("sortby", "name");

        if (this.getAttribute("showrelevance") == "true")
          this._btnRelevance.hidden = false;

        if (this.getAttribute("showprice") == "true")
          this._btnPrice.hidden = false;

        this._refreshState();
      ]]></constructor>

      <field name="handler">null</field>
      <field name="_btnName">
        document.getAnonymousElementByAttribute(this, "anonid", "name-btn");
      </field>
      <field name="_btnDate">
        document.getAnonymousElementByAttribute(this, "anonid", "date-btn");
      </field>
      <field name="_btnPrice">
        document.getAnonymousElementByAttribute(this, "anonid", "price-btn");
      </field>
      <field name="_btnRelevance">
        document.getAnonymousElementByAttribute(this, "anonid", "relevance-btn");
      </field>

      <property name="sortBy">
        <getter><![CDATA[
          return this.getAttribute("sortby");
        ]]></getter>
        <setter><![CDATA[
          if (val != this.sortBy) {
            this.setAttribute("sortBy", val);
            this._refreshState();
          }
        ]]></setter>
      </property>

      <property name="ascending">
        <getter><![CDATA[
          return (this.getAttribute("ascending") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          if (val != this.ascending) {
            this.setAttribute("ascending", val);
            this._refreshState();
          }
        ]]></setter>
      </property>

      <property name="showrelevance">
        <getter><![CDATA[
          return (this.getAttribute("showrelevance") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          this.setAttribute("showrelevance", val);
          this._btnRelevance.hidden = !val;
        ]]></setter>
      </property>

      <property name="showprice">
        <getter><![CDATA[
          return (this.getAttribute("showprice") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          this.setAttribute("showprice", val);
          this._btnPrice.hidden = !val;
        ]]></setter>
      </property>

      <method name="setSort">
        <parameter name="aSort"/>
        <parameter name="aAscending"/>
        <body><![CDATA[
          var sortChanged = false;
          if (aSort != this.sortBy) {
            this.setAttribute("sortby", aSort);
            sortChanged = true;
          }

          aAscending = !!aAscending;
          if (this.ascending != aAscending) {
            this.setAttribute("ascending", aAscending);
            sortChanged = true;
          }

          if (sortChanged)
            this._refreshState();
        ]]></body>
      </method>

      <method name="_handleChange">
        <parameter name="aSort"/>
        <body><![CDATA[
          const ASCENDING_SORT_FIELDS = ["name", "purchaseAmount"];

          // Toggle ascending if sort by is not changing, otherwise
          // name sorting defaults to ascending, others to descending
          if (aSort == this.sortBy)
            this.ascending = !this.ascending;
          else
            this.setSort(aSort, ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0);
        ]]></body>
      </method>

      <method name="_refreshState">
        <body><![CDATA[
          var sortBy = this.sortBy;
          var checkState = this.ascending ? 2 : 1;

          if (sortBy == "name") {
            this._btnName.checkState = checkState;
            this._btnName.checked = true;
          } else {
            this._btnName.checkState = 0;
            this._btnName.checked = false;
          }

          if (sortBy == "updateDate") {
            this._btnDate.checkState = checkState;
            this._btnDate.checked = true;
          } else {
            this._btnDate.checkState = 0;
            this._btnDate.checked = false;
          }

          if (sortBy == "purchaseAmount") {
            this._btnPrice.checkState = checkState;
            this._btnPrice.checked = true;
          } else {
            this._btnPrice.checkState = 0;
            this._btnPrice.checked = false;
          }

          if (sortBy == "relevancescore") {
            this._btnRelevance.checkState = checkState;
            this._btnRelevance.checked = true;
          } else {
            this._btnRelevance.checkState = 0;
            this._btnRelevance.checked = false;
          }

          if (this.handler && "onSortChanged" in this.handler)
            this.handler.onSortChanged(sortBy, this.ascending);
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Categories list - displays the list of categories on the left pane. -->
  <binding id="categories-list"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
    <implementation>
      <!-- This needs to be overridden to allow the fancy animation while not
           allowing that item to be selected when hiding.  -->
      <method name="_canUserSelect">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (aItem.hasAttribute("disabled") &&
              aItem.getAttribute("disabled") == "true")
            return false;
          var style = document.defaultView.getComputedStyle(aItem);
          return style.display != "none" && style.visibility == "visible";
        ]]>
        </body>
      </method>
    </implementation>
  </binding>


  <!-- Category item - an item in the category list. -->
  <binding id="category"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <content align="center">
      <xul:image anonid="icon" class="category-icon"/>
      <xul:label anonid="name" class="category-name" flex="1" xbl:inherits="value=name"/>
      <xul:label anonid="badge" class="category-badge" xbl:inherits="value=count"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (!this.hasAttribute("count"))
          this.setAttribute("count", 0);
      ]]></constructor>

      <property name="badgeCount">
        <getter><![CDATA[
          return this.getAttribute("count");
        ]]></getter>
        <setter><![CDATA[
          if (this.getAttribute("count") == val)
            return;

          this.setAttribute("count", val);
          var event = document.createEvent("Events");
          event.initEvent("CategoryBadgeUpdated", true, true);
          this.dispatchEvent(event);
        ]]></setter>
      </property>
    </implementation>
  </binding>


  <!-- Creator link - Name of a user/developer, providing a link if relevant. -->
  <binding id="creator-link">
    <content>
      <xul:label anonid="label" value="&addon.createdBy.label;"/>
      <xul:label anonid="creator-link" class="creator-link text-link"/>
      <xul:label anonid="creator-name" class="creator-name"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (this.hasAttribute("nameonly") &&
            this.getAttribute("nameonly") == "true") {
          this._label.hidden = true;
        }
      ]]></constructor>

      <field name="_label">
        document.getAnonymousElementByAttribute(this, "anonid", "label");
      </field>
      <field name="_creatorLink">
        document.getAnonymousElementByAttribute(this, "anonid", "creator-link");
      </field>
      <field name="_creatorName">
        document.getAnonymousElementByAttribute(this, "anonid", "creator-name");
      </field>

      <method name="setCreator">
        <parameter name="aCreator"/>
        <parameter name="aHomepageURL"/>
        <body><![CDATA[
          if (!aCreator) {
            this.collapsed = true;
            return;
          }
          this.collapsed = false;
          var url = aCreator.url || aHomepageURL;
          var showLink = !!url;
          if (showLink) {
            this._creatorLink.value = aCreator.name;
            this._creatorLink.href = url;
          } else {
            this._creatorName.value = aCreator.name;
          }
          this._creatorLink.hidden = !showLink;
          this._creatorName.hidden = showLink;
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Install status - Displays the status of an install/upgrade. -->
  <binding id="install-status">
    <content>
      <xul:label anonid="message"/>
      <xul:progressmeter anonid="progress" class="download-progress"/>
      <xul:button anonid="purchase-remote-btn" hidden="true"
                  class="addon-control"
                  oncommand="document.getBindingParent(this).purchaseRemote();"/>
      <xul:button anonid="install-remote-btn" hidden="true"
                  class="addon-control install" label="&addon.install.label;"
                  tooltiptext="&addon.install.tooltip;"
                  oncommand="document.getBindingParent(this).installRemote();"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (this.mInstall)
          this.initWithInstall(this.mInstall);
        else if (this.mControl.mAddon.install)
          this.initWithInstall(this.mControl.mAddon.install);
        else
          this.refreshState();
      ]]></constructor>

      <destructor><![CDATA[
        if (this.mInstall)
          this.mInstall.removeListener(this);
      ]]></destructor>

      <field name="_message">
        document.getAnonymousElementByAttribute(this, "anonid", "message");
      </field>
      <field name="_progress">
        document.getAnonymousElementByAttribute(this, "anonid", "progress");
      </field>
      <field name="_purchaseRemote">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "purchase-remote-btn");
      </field>
      <field name="_installRemote">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-remote-btn");
      </field>
      <field name="_restartNeeded">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "restart-needed");
      </field>
      <field name="_undo">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "undo-btn");
      </field>

      <method name="initWithInstall">
        <parameter name="aInstall"/>
        <body><![CDATA[
          if (this.mInstall) {
            this.mInstall.removeListener(this);
            this.mInstall = null;
          }
          this.mInstall = aInstall;
          this._progress.mInstall = aInstall;
          this.refreshState();
          this.mInstall.addListener(this);
        ]]></body>
      </method>

      <method name="refreshState">
        <body><![CDATA[
          var showInstallRemote = false;
          var showPurchase = false;

          if (this.mInstall) {

            switch (this.mInstall.state) {
              case AddonManager.STATE_AVAILABLE:
                if (this.mControl.getAttribute("remote") != "true")
                  break;

                this._progress.hidden = true;
                showInstallRemote = true;
                break;
              case AddonManager.STATE_DOWNLOADING:
                this.showMessage("installDownloading");
                break;
              case AddonManager.STATE_CHECKING:
                this.showMessage("installVerifying");
                break;
              case AddonManager.STATE_DOWNLOADED:
                this.showMessage("installDownloaded");
                break;
              case AddonManager.STATE_DOWNLOAD_FAILED:
                // XXXunf expose what error occured (bug 553487)
                this.showMessage("installDownloadFailed", true);
                break;
              case AddonManager.STATE_INSTALLING:
                this.showMessage("installInstalling");
                break;
              case AddonManager.STATE_INSTALL_FAILED:
                // XXXunf expose what error occured (bug 553487)
                this.showMessage("installFailed", true);
                break;
              case AddonManager.STATE_CANCELLED:
                this.showMessage("installCancelled", true);
                break;
            }

          } else if (this.mControl.mAddon.purchaseURL) {
            this._progress.hidden = true;
            showPurchase = true;
            this._purchaseRemote.label =
              gStrings.ext.formatStringFromName("addon.purchase.label",
                [this.mControl.mAddon.purchaseDisplayAmount], 1);
            this._purchaseRemote.tooltiptext =
              gStrings.ext.GetStringFromName("addon.purchase.tooltip");
          }

          this._purchaseRemote.hidden = !showPurchase;
          this._installRemote.hidden = !showInstallRemote;

          if ("refreshInfo" in this.mControl)
            this.mControl.refreshInfo();
        ]]></body>
      </method>

      <method name="showMessage">
        <parameter name="aMsgId"/>
        <parameter name="aHideProgress"/>
        <body><![CDATA[
          this._message.setAttribute("hidden", !aHideProgress);
          this._progress.setAttribute("hidden", !!aHideProgress);

          var msg = gStrings.ext.GetStringFromName(aMsgId);
          if (aHideProgress)
            this._message.value = msg;
          else
            this._progress.status = msg;
        ]]></body>
      </method>

      <method name="purchaseRemote">
        <body><![CDATA[
          openURL(this.mControl.mAddon.purchaseURL);
        ]]></body>
      </method>

      <method name="installRemote">
        <body><![CDATA[
          if (this.mControl.getAttribute("remote") != "true")
            return;

          if (this.mControl.mAddon.eula) {
            var data = {
              addon: this.mControl.mAddon,
              accepted: false
            };
            window.openDialog("chrome://mozapps/content/extensions/eula.xul", "_blank",
                              "chrome,dialog,modal,centerscreen,resizable=no", data);
            if (!data.accepted)
              return;
          }

          delete this.mControl.mAddon;
          this.mControl.mInstall = this.mInstall;
          this.mControl.setAttribute("status", "installing");
          let prompt = Services.prefs.getBoolPref("extensions.webextPermissionPrompts", false);
          if (prompt) {
            this.mInstall.promptHandler = info => new Promise((resolve, reject) => {
              // Skip prompts for non-webextensions
              if (!info.addon.userPermissions) {
                resolve();
                return;
              }
              let subject = {
                wrappedJSObject: {
                  target: window.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDocShell).chromeEventHandler,
                  info: {
                    addon: info.addon,
                    source: "AMO",
                    icon: info.addon.iconURL,
                    permissions: info.addon.userPermissions,
                    resolve,
                    reject,
                  },
                },
              };
              Services.obs.notifyObservers(subject, "webextension-permission-prompt");
            });
          }
          this.mInstall.install();
        ]]></body>
      </method>

      <method name="undoAction">
        <body><![CDATA[
          if (!this.mAddon)
            return;
          var pending = this.mAddon.pendingOperations;
          if (pending & AddonManager.PENDING_ENABLE)
            this.mAddon.userDisabled = true;
          else if (pending & AddonManager.PENDING_DISABLE)
            this.mAddon.userDisabled = false;
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadStarted">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadEnded">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadFailed">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadProgress">
        <body><![CDATA[
          this._progress.maxProgress = this.mInstall.maxProgress;
          this._progress.progress = this.mInstall.progress;
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <body><![CDATA[
          this._progress.progress = 0;
          this.refreshState();
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <body><![CDATA[
          this.refreshState();
          if ("onInstallCompleted" in this.mControl)
            this.mControl.onInstallCompleted();
        ]]></body>
      </method>

      <method name="onInstallFailed">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - base - parent binding of any item representing an addon. -->
  <binding id="addon-base"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <implementation>
      <property name="isLegacy" readonly="true">
        <getter><![CDATA[
          if (this.mAddon.install) {
            return false;
          }
          return isLegacyExtension(this.mAddon);
        ]]></getter>
      </property>

      <method name="hasPermission">
        <parameter name="aPerm"/>
        <body><![CDATA[
          var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
          return !!(this.mAddon.permissions & perm);
        ]]></body>
      </method>

      <method name="opRequiresRestart">
        <parameter name="aOperation"/>
        <body><![CDATA[
          var operation = AddonManager["OP_NEEDS_RESTART_" + aOperation.toUpperCase()];
          return !!(this.mAddon.operationsRequiringRestart & operation);
        ]]></body>
      </method>

      <method name="isPending">
        <parameter name="aAction"/>
        <body><![CDATA[
          var action = AddonManager["PENDING_" + aAction.toUpperCase()];
          return !!(this.mAddon.pendingOperations & action);
        ]]></body>
      </method>

      <method name="typeHasFlag">
        <parameter name="aFlag"/>
        <body><![CDATA[
          let flag = AddonManager["TYPE_" + aFlag];
          let type = AddonManager.addonTypes[this.mAddon.type];

          return !!(type.flags & flag);
        ]]></body>
      </method>

      <method name="onUninstalled">
        <body><![CDATA[
          this.remove();
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - generic - A normal addon item, or an update to one -->
  <binding id="addon-generic"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox anonid="warning-container"
                class="warning">
        <xul:image class="warning-icon"/>
        <xul:label anonid="warning" flex="1"/>
        <xul:label anonid="warning-link" class="text-link"/>
        <xul:button anonid="warning-btn" class="button-link" hidden="true"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox anonid="error-container"
                class="error">
        <xul:image class="error-icon"/>
        <xul:label anonid="error" flex="1"/>
        <xul:label anonid="error-link" class="text-link" hidden="true"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox anonid="pending-container"
                class="pending">
        <xul:image class="pending-icon"/>
        <xul:label anonid="pending" flex="1"/>
        <xul:button anonid="restart-btn" class="button-link"
                    label="&addon.restartNow.label;"
                    oncommand="document.getBindingParent(this).restart();"/>
        <xul:button anonid="undo-btn" class="button-link"
                    label="&addon.undoAction.label;"
                    tooltipText="&addon.undoAction.tooltip;"
                    oncommand="document.getBindingParent(this).undo();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>

      <xul:hbox class="content-container" align="center">
        <xul:vbox class="icon-container">
          <xul:image anonid="icon" class="icon"/>
        </xul:vbox>
        <xul:vbox class="content-inner-container" flex="1">
          <xul:hbox class="basicinfo-container">
              <xul:hbox class="name-container">
                <xul:label anonid="name" class="name" crop="end" flex="1"
                           tooltip="addonitem-tooltip" xbl:inherits="value=name"/>
                <xul:label anonid="legacy" class="legacy-warning text-link" value="&addon.legacy.label;"/>
                <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
                <xul:label class="update-postfix" value="&addon.update.postfix;"/>
                <xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
              </xul:hbox>
            <xul:label anonid="date-updated" class="date-updated"
                       unknown="&addon.unknownDate;"/>
          </xul:hbox>
          <xul:hbox class="experiment-container">
            <svg width="6" height="6" viewBox="0 0 6 6" version="1.1"
                 xmlns="http://www.w3.org/2000/svg"
                 class="experiment-bullet-container">
              <circle cx="3" cy="3" r="3" class="experiment-bullet"/>
            </svg>
            <xul:label anonid="experiment-state" class="experiment-state"/>
            <xul:label anonid="experiment-time" class="experiment-time"/>
          </xul:hbox>

          <xul:hbox class="advancedinfo-container" flex="1">
            <xul:vbox class="description-outer-container" flex="1">
              <xul:hbox class="description-container">
                <xul:label anonid="description" class="description" crop="end" flex="1"/>
                <xul:button anonid="details-btn" class="details button-link"
                            label="&addon.details.label;"
                            tooltiptext="&addon.details.tooltip;"
                            oncommand="document.getBindingParent(this).showInDetailView();"/>
                <xul:spacer flex="5000"/> <!-- Necessary to make the description crop -->
              </xul:hbox>
              <xul:vbox anonid="relnotes-container" class="relnotes-container">
                <xul:label class="relnotes-header" value="&addon.releaseNotes.label;"/>
                <xul:label anonid="relnotes-loading" value="&addon.loadingReleaseNotes.label;"/>
                <xul:label anonid="relnotes-error" hidden="true"
                           value="&addon.errorLoadingReleaseNotes.label;"/>
                <xul:vbox anonid="relnotes" class="relnotes"/>
              </xul:vbox>
              <xul:hbox class="relnotes-toggle-container">
                <xul:button anonid="relnotes-toggle-btn" class="relnotes-toggle"
                            hidden="true" label="&cmd.showReleaseNotes.label;"
                            tooltiptext="&cmd.showReleaseNotes.tooltip;"
                            showlabel="&cmd.showReleaseNotes.label;"
                            showtooltip="&cmd.showReleaseNotes.tooltip;"
                            hidelabel="&cmd.hideReleaseNotes.label;"
                            hidetooltip="&cmd.hideReleaseNotes.tooltip;"
                            oncommand="document.getBindingParent(this).toggleReleaseNotes();"/>
              </xul:hbox>
            </xul:vbox>
          </xul:hbox>
        </xul:vbox>
        <xul:vbox class="status-control-wrapper">
          <xul:hbox class="status-container">
            <xul:hbox anonid="checking-update" hidden="true">
              <xul:image class="spinner"/>
              <xul:label value="&addon.checkingForUpdates.label;"/>
            </xul:hbox>
            <xul:vbox anonid="update-available" class="update-available"
                      hidden="true">
              <xul:checkbox anonid="include-update" class="include-update"
                            label="&addon.includeUpdate.label;" checked="true"
                            oncommand="document.getBindingParent(this).onIncludeUpdateChanged();"/>
              <xul:hbox class="update-info-container">
                <xul:label class="update-available-notice"
                           value="&addon.updateAvailable.label;"/>
                <xul:button anonid="update-btn" class="addon-control update"
                            label="&addon.updateNow.label;"
                            tooltiptext="&addon.updateNow.tooltip;"
                            oncommand="document.getBindingParent(this).upgrade();"/>
              </xul:hbox>
            </xul:vbox>
            <xul:hbox anonid="install-status" class="install-status"
                      hidden="true"/>
          </xul:hbox>
          <xul:hbox anonid="control-container" class="control-container">
            <xul:button anonid="preferences-btn"
                        class="addon-control preferences"
                        label="&cmd.showPreferencesWin.label;"
                        tooltiptext="&cmd.showPreferencesWin.tooltip;"
                        oncommand="document.getBindingParent(this).showPreferences();"/>
            <xul:button anonid="enable-btn"  class="addon-control enable"
                        label="&cmd.enableAddon.label;"
                        oncommand="document.getBindingParent(this).userDisabled = false;"/>
            <xul:button anonid="disable-btn" class="addon-control disable"
                        label="&cmd.disableAddon.label;"
                        oncommand="document.getBindingParent(this).userDisabled = true;"/>
            <xul:button anonid="replacement-btn" class="addon-control replacement"
                        label="&cmd.findReplacement.label;"
                        oncommand="document.getBindingParent(this).findReplacement();"/>
            <xul:button anonid="remove-btn" class="addon-control remove"
                        label="&cmd.uninstallAddon.label;"
                        oncommand="document.getBindingParent(this).uninstall();"/>
            <xul:menulist anonid="state-menulist"
                          class="addon-control state"
                          tooltiptext="&cmd.stateMenu.tooltip;">
              <xul:menupopup>
                <xul:menuitem anonid="ask-to-activate-menuitem"
                              class="addon-control"
                              label="&cmd.askToActivate.label;"
                              tooltiptext="&cmd.askToActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
                <xul:menuitem anonid="always-activate-menuitem"
                              class="addon-control"
                              label="&cmd.alwaysActivate.label;"
                              tooltiptext="&cmd.alwaysActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = false;"/>
                <xul:menuitem anonid="never-activate-menuitem"
                              class="addon-control"
                              label="&cmd.neverActivate.label;"
                              tooltiptext="&cmd.neverActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = true;"/>
              </xul:menupopup>
            </xul:menulist>
          </xul:hbox>
        </xul:vbox>
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._installStatus = document.getAnonymousElementByAttribute(this, "anonid", "install-status");
        this._installStatus.mControl = this;

        this.setAttribute("contextmenu", "addonitem-popup");

        this._showStatus("none");

        this._initWithAddon(this.mAddon);

        gEventManager.registerAddonListener(this, this.mAddon.id);
      ]]></constructor>

      <destructor><![CDATA[
        gEventManager.unregisterAddonListener(this, this.mAddon.id);
      ]]></destructor>

      <field name="_warningContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-container");
      </field>
      <field name="_warning">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning");
      </field>
      <field name="_warningLink">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-link");
      </field>
      <field name="_warningBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-btn");
      </field>
      <field name="_errorContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error-container");
      </field>
      <field name="_error">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error");
      </field>
      <field name="_errorLink">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error-link");
      </field>
      <field name="_pendingContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "pending-container");
      </field>
      <field name="_pending">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "pending");
      </field>
      <field name="_infoContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "info-container");
      </field>
      <field name="_info">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "info");
      </field>
      <field name="_experimentState">
        document.getAnonymousElementByAttribute(this, "anonid", "experiment-state");
      </field>
      <field name="_experimentTime">
        document.getAnonymousElementByAttribute(this, "anonid", "experiment-time");
      </field>
      <field name="_icon">
        document.getAnonymousElementByAttribute(this, "anonid", "icon");
      </field>
      <field name="_dateUpdated">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "date-updated");
      </field>
      <field name="_description">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "description");
      </field>
      <field name="_stateMenulist">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "state-menulist");
      </field>
      <field name="_askToActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "ask-to-activate-menuitem");
      </field>
      <field name="_alwaysActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "always-activate-menuitem");
      </field>
      <field name="_neverActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "never-activate-menuitem");
      </field>
      <field name="_preferencesBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "preferences-btn");
      </field>
      <field name="_enableBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "enable-btn");
      </field>
      <field name="_disableBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "disable-btn");
      </field>
      <field name="_removeBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "remove-btn");
      </field>
      <field name="_updateBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "update-btn");
      </field>
      <field name="_controlContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "control-container");
      </field>
      <field name="_installStatus">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-status");
      </field>
      <field name="_checkingUpdate">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "checking-update");
      </field>
      <field name="_updateAvailable">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "update-available");
      </field>
      <field name="_includeUpdate">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "include-update");
      </field>
      <field name="_relNotesLoaded">false</field>
      <field name="_relNotesToggle">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-toggle-btn");
      </field>
      <field name="_relNotesLoading">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-loading");
      </field>
      <field name="_relNotesError">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-error");
      </field>
      <field name="_relNotesContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-container");
      </field>
      <field name="_relNotes">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes");
      </field>

      <property name="userDisabled">
        <getter><![CDATA[
          return this.mAddon.userDisabled;
        ]]></getter>
        <setter><![CDATA[
          if (val === true) {
            gViewController.commands["cmd_disableItem"].doCommand(this.mAddon);
          } else if (val === false) {
            gViewController.commands["cmd_enableItem"].doCommand(this.mAddon);
          } else {
            this.mAddon.userDisabled = val;
          }
        ]]></setter>
      </property>

      <property name="includeUpdate">
        <getter><![CDATA[
          return this._includeUpdate.checked && !!this.mManualUpdate;
        ]]></getter>
        <setter><![CDATA[
          // XXXunf Eventually, we'll want to persist this for individual
          //        updates - see bug 594619.
          this._includeUpdate.checked = !!val;
        ]]></setter>
      </property>

      <method name="_initWithAddon">
        <parameter name="aAddon"/>
        <body><![CDATA[
          this.mAddon = aAddon;

          this._installStatus.mAddon = this.mAddon;
          this._updateDates();
          this._updateState();

          this.setAttribute("name", aAddon.name);

          var iconURL = AddonManager.getPreferredIconURL(aAddon, 48, window);
          if (iconURL)
            this._icon.src = iconURL;
          else
            this._icon.src = "";

          if (this.mAddon.description)
            this._description.value = this.mAddon.description;
          else
            this._description.hidden = true;

          let legacyWarning = legacyExtensionsEnabled && !this.mAddon.install &&
            isLegacyExtension(this.mAddon);
          this.setAttribute("legacy", legacyWarning);
          document.getAnonymousElementByAttribute(this, "anonid", "legacy").href = SUPPORT_URL + "webextensions";

          if (!("applyBackgroundUpdates" in this.mAddon) ||
              (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
                !AddonManager.autoUpdateDefault))) {
            AddonManager.getAllInstalls(aInstallsList => {
              // This can return after the binding has been destroyed,
              // so try to detect that and return early
              if (!("onNewInstall" in this))
                return;
              for (let install of aInstallsList) {
                if (install.existingAddon &&
                    install.existingAddon.id == this.mAddon.id &&
                    install.state == AddonManager.STATE_AVAILABLE) {
                  this.onNewInstall(install);
                  this.onIncludeUpdateChanged();
                }
              }
            });
          }
        ]]></body>
      </method>

      <method name="_showStatus">
        <parameter name="aType"/>
        <body><![CDATA[
          this._controlContainer.hidden = aType != "none" &&
                                          !(aType == "update-available" && !this.hasAttribute("upgrade"));

          this._installStatus.hidden = aType != "progress";
          if (aType == "progress")
            this._installStatus.refreshState();
          this._checkingUpdate.hidden = aType != "checking-update";
          this._updateAvailable.hidden = aType != "update-available";
          this._relNotesToggle.hidden = !(this.mManualUpdate ?
                                          this.mManualUpdate.releaseNotesURI :
                                          this.mAddon.releaseNotesURI);
        ]]></body>
      </method>

      <method name="_updateDates">
        <body><![CDATA[
          function formatDate(aDate) {
            const dtOptions = { year: "numeric", month: "long", day: "numeric" };
            return aDate.toLocaleDateString(undefined, dtOptions);
          }

          if (this.mAddon.updateDate)
            this._dateUpdated.value = formatDate(this.mAddon.updateDate);
          else
            this._dateUpdated.value = this._dateUpdated.getAttribute("unknown");
        ]]></body>
      </method>

      <method name="_updateState">
        <body><![CDATA[
          if (this.parentNode.selectedItem == this)
            gViewController.updateCommands();

          var pending = this.mAddon.pendingOperations;
          if (pending != AddonManager.PENDING_NONE) {
            this.removeAttribute("notification");

            pending = null;
            const PENDING_OPERATIONS = ["enable", "disable", "install",
                                        "uninstall", "upgrade"];
            for (let op of PENDING_OPERATIONS) {
              if (this.isPending(op))
                pending = op;
            }

            this.setAttribute("pending", pending);
            this._pending.textContent = gStrings.ext.formatStringFromName(
              "notification." + pending,
              [this.mAddon.name, gStrings.brandShortName], 2
            );
          } else {
            this.removeAttribute("pending");

            var isUpgrade = this.hasAttribute("upgrade");
            var install = this._installStatus.mInstall;

            if (install && install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.downloadError",
                [this.mAddon.name], 1
              );
              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
              this._warningBtn.hidden = false;
              this._warningLink.hidden = true;
            } else if (install && install.state == AddonManager.STATE_INSTALL_FAILED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.installError",
                [this.mAddon.name], 1
              );
              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.installError.retry");
              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
              this._warningBtn.hidden = false;
              this._warningLink.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.blocked",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (!isUpgrade && !isCorrectlySigned(this.mAddon) &&
                       AddonSettings.REQUIRE_SIGNING) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.unsignedAndDisabled", [this.mAddon.name, gStrings.brandShortName], 2
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
              this._errorLink.href = SUPPORT_URL + "unsigned-addons";
              this._errorLink.hidden = false;
            } else if ((!isUpgrade && !this.mAddon.isCompatible) && (AddonManager.checkCompatibility
            || (this.mAddon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.incompatible",
                [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion], 3
              );
              this._warningLink.hidden = true;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && this.mAddon.appDisabled &&
                       !this.mAddon.multiprocessCompatible &&
                       !Services.prefs.getBoolPref("extensions.allow-non-mpc-extensions", true)) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.nonMpcDisabled", [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.nonMpcDisabled.link");
              this._errorLink.href = "https://wiki.mozilla.org/Add-ons/ShimsNightly";
              this._errorLink.hidden = false;
            } else if (!isUpgrade && !isCorrectlySigned(this.mAddon)) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.unsigned", [this.mAddon.name, gStrings.brandShortName], 2
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
              this._warningLink.href = SUPPORT_URL + "unsigned-addons";
              this._warningLink.hidden = false;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.softblocked",
                [this.mAddon.name], 1
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
              this._warningLink.href = this.mAddon.blocklistURL;
              this._warningLink.hidden = false;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.outdated",
                [this.mAddon.name], 1
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.outdated.link");
              this._warningLink.href = this.mAddon.blocklistURL;
              this._warningLink.hidden = false;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.vulnerableUpdatable",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.vulnerableNoUpdate",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
                       this.mAddon.isActive) {
              this.setAttribute("notification", "warning");
              this._warning.textContent =
                gStrings.ext.formatStringFromName("notification.gmpPending",
                                                  [this.mAddon.name], 1);
            } else {
              this.removeAttribute("notification");
            }
          }

          this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
                                        this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;

          if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) {
            this._enableBtn.disabled = true;
            this._disableBtn.disabled = true;
            this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
            this._alwaysActivateMenuitem.disabled = !this.hasPermission("enable");
            this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
            if (!this.mAddon.isActive) {
              this._stateMenulist.selectedItem = this._neverActivateMenuitem;
            } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
              this._stateMenulist.selectedItem = this._askToActivateMenuitem;
            } else {
              this._stateMenulist.selectedItem = this._alwaysActivateMenuitem;
            }
            let hasActivatePermission =
              ["ask_to_activate", "enable", "disable"].some(perm => this.hasPermission(perm));
            this._stateMenulist.disabled = !hasActivatePermission;
            this._stateMenulist.hidden = false;
            this._stateMenulist.classList.add("no-auto-hide");
          } else {
            this._stateMenulist.hidden = true;

            let enableTooltip = gViewController.commands["cmd_enableItem"]
                                               .getTooltip(this.mAddon);
            this._enableBtn.setAttribute("tooltiptext", enableTooltip);
            if (this.hasPermission("enable")) {
              this._enableBtn.hidden = false;
            } else {
              this._enableBtn.hidden = true;
            }

            let disableTooltip = gViewController.commands["cmd_disableItem"]
                                                .getTooltip(this.mAddon);
            this._disableBtn.setAttribute("tooltiptext", disableTooltip);
            if (this.hasPermission("disable")) {
              this._disableBtn.hidden = false;
            } else {
              this._disableBtn.hidden = true;
            }
          }

          let uninstallTooltip = gViewController.commands["cmd_uninstallItem"]
                                                .getTooltip(this.mAddon);
          this._removeBtn.setAttribute("tooltiptext", uninstallTooltip);
          if (this.hasPermission("uninstall")) {
            this._removeBtn.hidden = false;
          } else {
            this._removeBtn.hidden = true;
          }

          this.setAttribute("active", this.mAddon.isActive);

          var showProgress = this.mAddon.purchaseURL || (this.mAddon.install &&
                             this.mAddon.install.state != AddonManager.STATE_INSTALLED);
          this._showStatus(showProgress ? "progress" : "none");

          if (this.mAddon.type == "experiment") {
            this.removeAttribute("notification");
            let prefix = "experiment.";
            let active = this.mAddon.isActive;

            if (!showProgress) {
              let stateKey = prefix + "state." + (active ? "active" : "complete");
              this._experimentState.value = gStrings.ext.GetStringFromName(stateKey);

              let now = Date.now();
              let end = this.endDate;
              let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);

              let timeKey = prefix + "time.";
              let timeMessage;

              if (days < 1) {
                timeKey += (active ? "endsToday" : "endedToday");
                timeMessage = gStrings.ext.GetStringFromName(timeKey);
              } else {
                timeKey += (active ? "daysRemaining" : "daysPassed");
                days = Math.round(days);
                let timeString = gStrings.ext.GetStringFromName(timeKey);
                timeMessage = PluralForm.get(days, timeString)
                                        .replace("#1", days);
              }

              this._experimentTime.value = timeMessage;
            }
          }
        ]]></body>
      </method>

      <method name="_fetchReleaseNotes">
        <parameter name="aURI"/>
        <body><![CDATA[
          if (!aURI || this._relNotesLoaded) {
            sendToggleEvent();
            return;
          }

          var relNotesData = null, transformData = null;

          this._relNotesLoaded = true;
          this._relNotesLoading.hidden = false;
          this._relNotesError.hidden = true;

          let sendToggleEvent = () => {
            var event = document.createEvent("Events");
            event.initEvent("RelNotesToggle", true, true);
            this.dispatchEvent(event);
          }

          let showRelNotes = () => {
            if (!relNotesData || !transformData)
              return;

            this._relNotesLoading.hidden = true;

            var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
                                      .createInstance(Components.interfaces.nsIXSLTProcessor);
            processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;

            processor.importStylesheet(transformData);
            var fragment = processor.transformToFragment(relNotesData, document);
            this._relNotes.appendChild(fragment);
            if (this.hasAttribute("show-relnotes")) {
              var container = this._relNotesContainer;
              container.style.height = container.scrollHeight + "px";
            }
            sendToggleEvent();
          }

          let handleError = () => {
            dataReq.abort();
            styleReq.abort();
            this._relNotesLoading.hidden = true;
            this._relNotesError.hidden = false;
            this._relNotesLoaded = false; // allow loading to be re-tried
            sendToggleEvent();
          }

          function handleResponse(aEvent) {
            var req = aEvent.target;
            var ct = req.getResponseHeader("content-type");
            if ((!ct || ct.indexOf("text/html") < 0) &&
                req.responseXML &&
                req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) {
              if (req == dataReq)
                relNotesData = req.responseXML;
              else
                transformData = req.responseXML;
              showRelNotes();
            } else {
              handleError();
            }
          }

          var dataReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                              .createInstance(Components.interfaces.nsIXMLHttpRequest);
          dataReq.open("GET", aURI.spec, true);
          dataReq.responseType = "document";
          dataReq.addEventListener("load", handleResponse);
          dataReq.addEventListener("error", handleError);
          dataReq.send(null);

          var styleReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                              .createInstance(Components.interfaces.nsIXMLHttpRequest);
          styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true);
          styleReq.responseType = "document";
          styleReq.addEventListener("load", handleResponse);
          styleReq.addEventListener("error", handleError);
          styleReq.send(null);
        ]]></body>
      </method>

      <method name="toggleReleaseNotes">
        <body><![CDATA[
          if (this.hasAttribute("show-relnotes")) {
            this._relNotesContainer.style.height = "0px";
            this.removeAttribute("show-relnotes");
            this._relNotesToggle.setAttribute(
              "label",
              this._relNotesToggle.getAttribute("showlabel")
            );
            this._relNotesToggle.setAttribute(
              "tooltiptext",
              this._relNotesToggle.getAttribute("showtooltip")
            );
            var event = document.createEvent("Events");
            event.initEvent("RelNotesToggle", true, true);
            this.dispatchEvent(event);
          } else {
            this._relNotesContainer.style.height = this._relNotesContainer.scrollHeight +
                                                   "px";
            this.setAttribute("show-relnotes", true);
            this._relNotesToggle.setAttribute(
              "label",
              this._relNotesToggle.getAttribute("hidelabel")
            );
            this._relNotesToggle.setAttribute(
              "tooltiptext",
              this._relNotesToggle.getAttribute("hidetooltip")
            );
            var uri = this.mManualUpdate ?
                      this.mManualUpdate.releaseNotesURI :
                      this.mAddon.releaseNotesURI;
            this._fetchReleaseNotes(uri);
          }
        ]]></body>
      </method>

      <method name="restart">
        <body><![CDATA[
          gViewController.commands["cmd_restartApp"].doCommand();
        ]]></body>
      </method>

      <method name="undo">
        <body><![CDATA[
          gViewController.commands["cmd_cancelOperation"].doCommand(this.mAddon);
        ]]></body>
      </method>

      <method name="uninstall">
        <body><![CDATA[
          // If uninstalling does not require a restart and the type doesn't
          // support undoing of restartless uninstalls, then we fake it by
          // just disabling it it, and doing the real uninstall later.
          if (!this.opRequiresRestart("uninstall") &&
              !this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
            this.setAttribute("wasDisabled", this.mAddon.userDisabled);

            // We must set userDisabled to true first, this will call
            // _updateState which will clear any pending attribute set.
            this.mAddon.userDisabled = true;

            // This won't update any other add-on manager views (bug 582002)
            this.setAttribute("pending", "uninstall");
          } else {
            this.mAddon.uninstall(true);
          }
        ]]></body>
      </method>

      <method name="showPreferences">
        <body><![CDATA[
          gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
        ]]></body>
      </method>

      <method name="upgrade">
        <body><![CDATA[
          var install = this.mManualUpdate;
          delete this.mManualUpdate;
          install.install();
        ]]></body>
      </method>

      <method name="retryInstall">
        <body><![CDATA[
          var install = this._installStatus.mInstall;
          if (!install)
            return;
          if (install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
              install.state != AddonManager.STATE_INSTALL_FAILED)
            return;
          install.install();
        ]]></body>
      </method>

      <method name="showInDetailView">
        <body><![CDATA[
          gViewController.loadView("addons://detail/" +
                                   encodeURIComponent(this.mAddon.id));
        ]]></body>
      </method>

      <method name="findReplacement">
        <body><![CDATA[
          openURL(`https://addons.mozilla.org/find-replacement/?guid=${this.mAddon.id}`);
        ]]></body>
      </method>

      <method name="onIncludeUpdateChanged">
        <body><![CDATA[
          var event = document.createEvent("Events");
          event.initEvent("IncludeUpdateChanged", true, true);
          this.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="onEnabling">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onEnabled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onDisabling">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onDisabled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onUninstalling">
        <parameter name="aRestartRequired"/>
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onOperationCancelled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onPropertyChanged">
        <parameter name="aProperties"/>
        <body><![CDATA[
          if (aProperties.indexOf("appDisabled") != -1 ||
              aProperties.indexOf("signedState") != -1 ||
              aProperties.indexOf("userDisabled") != -1)
            this._updateState();
        ]]></body>
      </method>

      <method name="onNoUpdateAvailable">
        <body><![CDATA[
          this._showStatus("none");
        ]]></body>
      </method>

      <method name="onCheckingUpdate">
        <body><![CDATA[
          this._showStatus("checking-update");
        ]]></body>
      </method>

      <method name="onCompatibilityUpdateAvailable">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onExternalInstall">
        <parameter name="aAddon"/>
        <parameter name="aExistingAddon"/>
        <parameter name="aNeedsRestart"/>
        <body><![CDATA[
          if (aExistingAddon.id != this.mAddon.id)
            return;

          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!aNeedsRestart)
            this._initWithAddon(aAddon);
          else
            this._updateState();
        ]]></body>
      </method>

      <method name="onNewInstall">
        <parameter name="aInstall"/>
        <body><![CDATA[
          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
            return;
          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
              AddonManager.autoUpdateDefault)
            return;

          this.mManualUpdate = aInstall;
          this._showStatus("update-available");
        ]]></body>
      </method>

      <method name="onDownloadStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          this._updateState();
          this._showStatus("progress");
          this._installStatus.initWithInstall(aInstall);
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          this._updateState();
          this._showStatus("progress");
          this._installStatus.initWithInstall(aInstall);
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <parameter name="aInstall"/>
        <parameter name="aAddon"/>
        <body><![CDATA[
          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
            this._initWithAddon(aAddon);
          else
            this._updateState();
        ]]></body>
      </method>

      <method name="onDownloadFailed">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>

      <method name="onInstallFailed">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>

      <method name="onInstallCancelled">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="click" button="0"><![CDATA[
        switch (event.detail) {
        case 1:
          // Prevent double-click where the UI changes on the first click
          this._lastClickTarget = event.originalTarget;
          break;
        case 2:
          if (event.originalTarget.localName != "button" &&
              !event.originalTarget.classList.contains("text-link") &&
              event.originalTarget == this._lastClickTarget) {
            this.showInDetailView();
          }
          break;
        }
      ]]></handler>
    </handlers>
  </binding>


  <!-- Addon - uninstalled - An uninstalled addon that can be re-installed. -->
  <binding id="addon-uninstalled"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox class="pending">
        <xul:image class="pending-icon"/>
        <xul:label anonid="notice" flex="1"/>
        <xul:button anonid="restart-btn" class="button-link"
                    label="&addon.restartNow.label;"
                    command="cmd_restartApp"/>
        <xul:button anonid="undo-btn" class="button-link"
                    label="&addon.undoRemove.label;"
                    tooltiptext="&addon.undoRemove.tooltip;"
                    oncommand="document.getBindingParent(this).cancelUninstall();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._notice.textContent = gStrings.ext.formatStringFromName("uninstallNotice",
                                                                     [this.mAddon.name],
                                                                     1);

        if (!this.opRequiresRestart("uninstall"))
          this._restartBtn.setAttribute("hidden", true);

        gEventManager.registerAddonListener(this, this.mAddon.id);
      ]]></constructor>

      <destructor><![CDATA[
        gEventManager.unregisterAddonListener(this, this.mAddon.id);
      ]]></destructor>

      <field name="_notice" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "notice");
      </field>
      <field name="_restartBtn" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "restart-btn");
      </field>

      <method name="cancelUninstall">
        <body><![CDATA[
          // This assumes that disabling does not require a restart when
          // uninstalling doesn't. Things will still work if not, the add-on
          // will just still be active until finally getting uninstalled.

          if (this.isPending("uninstall"))
            this.mAddon.cancelUninstall();
          else if (this.getAttribute("wasDisabled") != "true")
            this.mAddon.userDisabled = false;

          this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onOperationCancelled">
        <body><![CDATA[
          if (!this.isPending("uninstall"))
            this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onExternalInstall">
        <parameter name="aAddon"/>
        <parameter name="aExistingAddon"/>
        <parameter name="aNeedsRestart"/>
        <body><![CDATA[
          if (aExistingAddon.id != this.mAddon.id)
            return;

          // Make sure any newly installed add-on has the correct disabled state
          if (this.hasAttribute("wasDisabled"))
            aAddon.userDisabled = this.getAttribute("wasDisabled") == "true";

          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!aNeedsRestart)
            this.mAddon = aAddon;

          this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          // Make sure any newly installed add-on has the correct disabled state
          if (this.hasAttribute("wasDisabled"))
            aInstall.addon.userDisabled = this.getAttribute("wasDisabled") == "true";
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <parameter name="aInstall"/>
        <parameter name="aAddon"/>
        <body><![CDATA[
          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
            this.mAddon = aAddon;

          this.removeAttribute("pending");
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - installing - an addon item that is currently being installed -->
  <binding id="addon-installing"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox anonid="warning-container" class="warning">
        <xul:image class="warning-icon"/>
        <xul:label anonid="warning" flex="1"/>
        <xul:button anonid="warning-link" class="button-link"
                   oncommand="document.getBindingParent(this).retryInstall();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox class="content-container">
        <xul:vbox class="icon-outer-container">
          <xul:vbox class="icon-container">
            <xul:image anonid="icon" class="icon"/>
          </xul:vbox>
        </xul:vbox>
        <xul:vbox class="fade name-outer-container" flex="1">
          <xul:hbox class="name-container">
            <xul:label anonid="name" class="name" crop="end" tooltip="addonitem-tooltip"/>
          </xul:hbox>
        </xul:vbox>
        <xul:vbox class="install-status-container">
          <xul:hbox anonid="install-status" class="install-status"/>
        </xul:vbox>
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._installStatus.mControl = this;
        this._installStatus.mInstall = this.mInstall;
        this.refreshInfo();
      ]]></constructor>

      <field name="_icon">
        document.getAnonymousElementByAttribute(this, "anonid", "icon");
      </field>
      <field name="_name">
        document.getAnonymousElementByAttribute(this, "anonid", "name");
      </field>
      <field name="_warning">
        document.getAnonymousElementByAttribute(this, "anonid", "warning");
      </field>
      <field name="_warningLink">
        document.getAnonymousElementByAttribute(this, "anonid", "warning-link");
      </field>
      <field name="_installStatus">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-status");
      </field>

      <method name="onInstallCompleted">
        <body><![CDATA[
          this.mAddon = this.mInstall.addon;
          this.setAttribute("name", this.mAddon.name);
          this.setAttribute("value", this.mAddon.id);
          this.setAttribute("status", "installed");
        ]]></body>
      </method>

      <method name="refreshInfo">
        <body><![CDATA[
          this.mAddon = this.mAddon || this.mInstall.addon;
          if (this.mAddon) {
            this._icon.src = this.mAddon.iconURL ||
                             (this.mInstall ? this.mInstall.iconURL : "");
            this._name.value = this.mAddon.name;
          } else {
            this._icon.src = this.mInstall.iconURL;
            // AddonInstall.name isn't always available - fallback to filename
            if (this.mInstall.name) {
              this._name.value = this.mInstall.name;
            } else if (this.mInstall.sourceURI) {
              var url = Components.classes["@mozilla.org/network/standard-url;1"]
                                  .createInstance(Components.interfaces.nsIStandardURL);
              url.init(url.URLTYPE_STANDARD, 80, this.mInstall.sourceURI.spec,
                       null, null);
              url.QueryInterface(Components.interfaces.nsIURL);
              this._name.value = url.fileName;
            }
          }

          if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
            this.setAttribute("notification", "warning");
            this._warning.textContent = gStrings.ext.formatStringFromName(
              "notification.downloadError",
              [this._name.value], 1
            );
            this._warningLink.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
          } else if (this.mInstall.state == AddonManager.STATE_INSTALL_FAILED) {
            this.setAttribute("notification", "warning");
            this._warning.textContent = gStrings.ext.formatStringFromName(
              "notification.installError",
              [this._name.value], 1
            );
            this._warningLink.label = gStrings.ext.GetStringFromName("notification.installError.retry");
            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
          } else {
            this.removeAttribute("notification");
          }
        ]]></body>
      </method>

      <method name="retryInstall">
        <body><![CDATA[
          this.mInstall.install();
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="detail-row">
    <content>
      <xul:label class="detail-row-label" xbl:inherits="value=label"/>
      <xul:label class="detail-row-value" xbl:inherits="value"/>
    </content>

    <implementation>
      <property name="value">
        <getter><![CDATA[
          return this.getAttribute("value");
        ]]></getter>
        <setter><![CDATA[
          if (!val)
            this.removeAttribute("value");
          else
            this.setAttribute("value", val);
        ]]></setter>
      </property>
    </implementation>
  </binding>

</bindings>
PK
!<;}T&&8chrome/toolkit/content/mozapps/extensions/extensions.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/content/extensions/extensions.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/extensions.css"?>

<!DOCTYPE page [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>

<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      xmlns:xhtml="http://www.w3.org/1999/xhtml"
      id="addons-page" title="&addons.windowTitle;"
      role="application" windowtype="Addons:Manager"
      disablefastfind="true">

  <xhtml:link rel="shortcut icon"
              href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>

  <script type="application/javascript"
          src="chrome://mozapps/content/extensions/extensions.js"/>
  <script type="application/javascript"
          src="chrome://global/content/contentAreaUtils.js"/>

  <popupset>
    <!-- menu for an addon item -->
    <menupopup id="addonitem-popup">
      <menuitem id="menuitem_showDetails" command="cmd_showItemDetails"
                default="true" label="&cmd.showDetails.label;"
                accesskey="&cmd.showDetails.accesskey;"/>
      <menuitem id="menuitem_enableItem" command="cmd_enableItem"
                label="&cmd.enableAddon.label;"
                accesskey="&cmd.enableAddon.accesskey;"/>
      <menuitem id="menuitem_disableItem" command="cmd_disableItem"
                label="&cmd.disableAddon.label;"
                accesskey="&cmd.disableAddon.accesskey;"/>
      <menuitem id="menuitem_enableTheme" command="cmd_enableItem"
                label="&cmd.enableTheme.label;"
                accesskey="&cmd.enableTheme.accesskey;"/>
      <menuitem id="menuitem_disableTheme" command="cmd_disableItem"
                label="&cmd.disableTheme.label;"
                accesskey="&cmd.disableTheme.accesskey;"/>
      <menuitem id="menuitem_installItem" command="cmd_installItem"
                label="&cmd.installAddon.label;"
                accesskey="&cmd.installAddon.accesskey;"/>
      <menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
                label="&cmd.uninstallAddon.label;"
                accesskey="&cmd.uninstallAddon.accesskey;"/>
      <menuseparator id="addonitem-menuseparator" />
      <menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
                label="&cmd.preferencesWin.label;"
                accesskey="&cmd.preferencesWin.accesskey;"/>
      <menuitem id="menuitem_findUpdates" command="cmd_findItemUpdates"
                label="&cmd.findUpdates.label;"
                accesskey="&cmd.findUpdates.accesskey;"/>
      <menuitem id="menuitem_about" command="cmd_showItemAbout"
                label="&cmd.about.label;"
                accesskey="&cmd.about.accesskey;"/>
    </menupopup>

    <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
      <menupopup rolluponmousewheel="true"
                 activateontab="true" position="after_start"
                 level="parent"
                 consumeoutsideclicks="false" ignorekeys="shortcuts"
        />
    </menulist>

    <tooltip id="addonitem-tooltip"/>
  </popupset>

  <!-- global commands - these act on all addons, or affect the addons manager
       in some other way -->
  <commandset id="globalCommandSet">
    <!-- XXXsw remove useless oncommand attribute once bug 371900 is fixed -->
    <command id="cmd_focusSearch" oncommand=";"/>
    <command id="cmd_findAllUpdates"/>
    <command id="cmd_restartApp"/>
    <command id="cmd_goToDiscoverPane"/>
    <command id="cmd_goToRecentUpdates"/>
    <command id="cmd_goToAvailableUpdates"/>
    <command id="cmd_installFromFile"/>
    <command id="cmd_debugAddons"/>
    <command id="cmd_back"/>
    <command id="cmd_forward"/>
    <command id="cmd_enableCheckCompatibility"/>
    <command id="cmd_enableUpdateSecurity"/>
    <command id="cmd_toggleAutoUpdateDefault"/>
    <command id="cmd_resetAddonAutoUpdate"/>
    <command id="cmd_experimentsLearnMore"/>
    <command id="cmd_experimentsOpenTelemetryPreferences"/>
    <command id="cmd_showUnsignedExtensions"/>
    <command id="cmd_showAllExtensions"/>
  </commandset>

  <!-- view commands - these act on the selected addon -->
  <commandset id="viewCommandSet"
              events="richlistbox-select" commandupdater="true">
    <command id="cmd_showItemDetails"/>
    <command id="cmd_findItemUpdates"/>
    <command id="cmd_showItemPreferences"/>
    <command id="cmd_showItemAbout"/>
    <command id="cmd_enableItem"/>
    <command id="cmd_disableItem"/>
    <command id="cmd_installItem"/>
    <command id="cmd_purchaseItem"/>
    <command id="cmd_uninstallItem"/>
    <command id="cmd_cancelUninstallItem"/>
    <command id="cmd_cancelOperation"/>
    <command id="cmd_contribute"/>
    <command id="cmd_askToActivateItem"/>
    <command id="cmd_alwaysActivateItem"/>
    <command id="cmd_neverActivateItem"/>
  </commandset>

  <keyset>
    <key id="focusSearch" key="&search.commandkey;" modifiers="accel"
         command="cmd_focusSearch"/>
  </keyset>
  <hbox flex="1">
    <vbox>
      <hbox id="nav-header"
            align="center"
            pack="center">
        <toolbarbutton id="back-btn"
                       class="nav-button header-button"
                       command="cmd_back"
                       tooltiptext="&cmd.back.tooltip;"
                       hidden="true"
                       disabled="true"/>
        <toolbarbutton id="forward-btn"
                       class="nav-button header-button"
                       command="cmd_forward"
                       tooltiptext="&cmd.forward.tooltip;"
                       hidden="true"
                       disabled="true"/>
      </hbox>
      <!-- category list -->
      <richlistbox id="categories" flex="1">
        <richlistitem id="category-search" value="addons://search/"
                      class="category"
                      name="&view.search.label;" priority="0"
                      tooltiptext="&view.search.label;" disabled="true"/>
        <richlistitem id="category-discover" value="addons://discover/"
                      class="category"
                      name="&view.discover.label;" priority="1000"
                      tooltiptext="&view.discover.label;"/>
        <richlistitem id="category-legacy" value="addons://legacy/"
                      class="category" priority="20000"
                      disabled="true"/>
        <richlistitem id="category-availableUpdates" value="addons://updates/available"
                      class="category"
                      name="&view.availableUpdates.label;" priority="100000"
                      tooltiptext="&view.availableUpdates.label;"
                      disabled="true"/>
        <richlistitem id="category-recentUpdates" value="addons://updates/recent"
                      class="category"
                      name="&view.recentUpdates.label;" priority="101000"
                      tooltiptext="&view.recentUpdates.label;" disabled="true"/>
      </richlistbox>
    </vbox>
    <vbox class="main-content" flex="1">
      <!-- view port -->
      <deck id="view-port" flex="1" selectedIndex="0">
        <!-- discover view -->
        <deck id="discover-view" flex="1" class="view-pane" selectedIndex="0" tabindex="0">
          <vbox id="discover-loading" align="center" pack="stretch" flex="1" class="alert-container">
            <spacer class="alert-spacer-before"/>
            <hbox class="alert loading" align="center">
              <image/>
              <label value="&loading.label;"/>
            </hbox>
            <spacer class="alert-spacer-after"/>
          </vbox>
          <vbox id="discover-error" align="center" pack="stretch" flex="1" class="alert-container">
            <spacer class="alert-spacer-before"/>
            <hbox>
              <spacer class="discover-spacer-before"/>
              <hbox class="alert" align="center">
                <image class="discover-logo"/>
                <vbox flex="1" align="stretch">
                  <label class="discover-title">&discover.title;</label>
                  <description class="discover-description">&discover.description2;</description>
                  <description class="discover-footer">&discover.footer;</description>
                </vbox>
              </hbox>
              <spacer class="discover-spacer-after"/>
            </hbox>
            <spacer class="alert-spacer-after"/>
          </vbox>
          <browser id="discover-browser" type="content" flex="1"
                     disablehistory="true" homepage="about:blank"/>
        </deck>

        <!-- container for views with the search/tools header -->
        <vbox id="headered-views" flex="1">
          <!-- main header -->
          <hbox id="header" align="center">
            <button id="show-all-extensions" hidden="true"
                    label="&showAllExtensions.button.label;"
                    command="cmd_showAllExtensions"/>
            <spacer flex="1"/>
            <hbox id="updates-container" align="center">
              <image class="spinner"/>
              <label id="updates-noneFound" hidden="true"
                     value="&updates.noneFound.label;"/>
              <button id="updates-manualUpdatesFound-btn" class="button-link"
                      hidden="true" label="&updates.manualUpdatesFound.label;"
                      command="cmd_goToAvailableUpdates"/>
              <label id="updates-progress" hidden="true"
                     value="&updates.updating.label;"/>
              <label id="updates-installed" hidden="true"
                     value="&updates.installed.label;"/>
              <label id="updates-downloaded" hidden="true"
                     value="&updates.downloaded.label;"/>
              <button id="updates-restart-btn" class="button-link" hidden="true"
                      label="&updates.restart.label;"
                      command="cmd_restartApp"/>
            </hbox>
            <button id="show-disabled-unsigned-extensions" hidden="true"
                    class="warning"
                    label="&showUnsignedExtensions.button.label;"
                    command="cmd_showUnsignedExtensions"/>
            <toolbarbutton id="header-utils-btn" class="header-button" type="menu"
                           tooltiptext="&toolsMenu.tooltip;">
              <menupopup id="utils-menu">
                <menuitem id="utils-updateNow"
                          label="&updates.checkForUpdates.label;"
                          accesskey="&updates.checkForUpdates.accesskey;"
                          command="cmd_findAllUpdates"/>
                <menuitem id="utils-viewUpdates"
                          label="&updates.viewUpdates.label;"
                          accesskey="&updates.viewUpdates.accesskey;"
                          command="cmd_goToRecentUpdates"/>
                <menuseparator id="utils-installFromFile-separator"/>
                <menuitem id="utils-installFromFile"
                          label="&installAddonFromFile.label;"
                          accesskey="&installAddonFromFile.accesskey;"
                          command="cmd_installFromFile"/>
                <menuitem id="utils-debugAddons"
                          label="&debugAddons.label;"
                          accesskey="&debugAddons.accesskey;"
                          command="cmd_debugAddons"/>
                <menuseparator/>
                <menuitem id="utils-autoUpdateDefault"
                          label="&updates.updateAddonsAutomatically.label;"
                          accesskey="&updates.updateAddonsAutomatically.accesskey;"
                          type="checkbox" autocheck="false"
                          command="cmd_toggleAutoUpdateDefault"/>
                <menuitem id="utils-resetAddonUpdatesToAutomatic"
                          label="&updates.resetUpdatesToAutomatic.label;"
                          accesskey="&updates.resetUpdatesToAutomatic.accesskey;"
                          command="cmd_resetAddonAutoUpdate"/>
                <menuitem id="utils-resetAddonUpdatesToManual"
                          label="&updates.resetUpdatesToManual.label;"
                          accesskey="&updates.resetUpdatesToManual.accesskey;"
                          command="cmd_resetAddonAutoUpdate"/>
              </menupopup>
            </toolbarbutton>
            <textbox id="header-search" type="search" searchbutton="true"
                     searchbuttonlabel="&search.buttonlabel;"
                     placeholder="&search.placeholder;"/>
          </hbox>

          <deck id="headered-views-content" flex="1" selectedIndex="0">
            <!-- search view -->
            <vbox id="search-view" flex="1" class="view-pane" tabindex="0">
              <hbox class="view-header global-warning-container" align="center">
                <!-- global warnings -->
                <hbox class="global-warning" flex="1">
                  <hbox class="global-warning-safemode" flex="1" align="center"
                        tooltiptext="&warning.safemode.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.safemode.label;"/>
                  </hbox>
                  <hbox class="global-warning-checkcompatibility" flex="1" align="center"
                        tooltiptext="&warning.checkcompatibility.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.checkcompatibility.label;"/>
                  </hbox>
                  <button class="button-link global-warning-checkcompatibility"
                          label="&warning.checkcompatibility.enable.label;"
                          tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                          command="cmd_enableCheckCompatibility"/>
                  <hbox class="global-warning-updatesecurity" flex="1" align="center"
                        tooltiptext="&warning.updatesecurity.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.updatesecurity.label;"/>
                  </hbox>
                  <button class="button-link global-warning-updatesecurity"
                          label="&warning.updatesecurity.enable.label;"
                          tooltiptext="&warning.updatesecurity.enable.tooltip;"
                          command="cmd_enableUpdateSecurity"/>
                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                </hbox>
                <spacer flex="1"/>
                <hbox id="search-sorters" class="sort-controls"
                      showrelevance="true" sortby="relevancescore" ascending="false"/>
              </hbox>
              <hbox id="search-filter" align="center">
                <label id="search-filter-label" value="&search.filter2.label;"/>
                <radiogroup id="search-filter-radiogroup" orient="horizontal"
                            align="center" persist="value" value="remote">
                  <radio id="search-filter-local" class="search-filter-radio"
                         label="&search.filter2.installed.label;" value="local"
                         tooltiptext="&search.filter2.installed.tooltip;"/>
                  <radio id="search-filter-remote" class="search-filter-radio"
                         label="&search.filter2.available.label;" value="remote"
                         tooltiptext="&search.filter2.available.tooltip;"/>
                </radiogroup>
              </hbox>
              <vbox id="search-loading" class="alert-container"
                    flex="1" hidden="true">
                <spacer class="alert-spacer-before"/>
                <hbox class="alert loading" align="center">
                  <image/>
                  <label value="&loading.label;"/>
                </hbox>
                <spacer class="alert-spacer-after"/>
              </vbox>
              <vbox id="search-list-empty" class="alert-container"
                    flex="1" hidden="true">
                <spacer class="alert-spacer-before"/>
                <vbox class="alert">
                  <label value="&listEmpty.search.label;"/>
                  <button class="discover-button"
                          id="discover-button-search"
                          label="&listEmpty.button.label;"
                          command="cmd_goToDiscoverPane"/>
                </vbox>
                <spacer class="alert-spacer-after"/>
              </vbox>
              <richlistbox id="search-list" class="list" flex="1">
                <hbox pack="center">
                  <label id="search-allresults-link" class="text-link"/>
                </hbox>
              </richlistbox>
            </vbox>

            <!-- list view -->
            <vbox id="list-view" flex="1" class="view-pane" align="stretch" tabindex="0">
              <!-- info UI for add-ons that have been disabled for being unsigned -->
              <vbox id="disabled-unsigned-addons-info" hidden="true">
                <label id="disabled-unsigned-addons-heading" value="&disabledUnsigned.heading;"/>
                <description>
                  &disabledUnsigned.description.start;<label class="text-link plain" id="find-alternative-addons">&disabledUnsigned.description.findAddonsLink;</label>&disabledUnsigned.description.end;
                </description>
                <hbox pack="start"><label class="text-link" id="signing-learn-more">&disabledUnsigned.learnMore;</label></hbox>
                <description id="signing-dev-info">
                  &disabledUnsigned.devInfo.start;<label class="text-link plain" id="signing-dev-manual-link">&disabledUnsigned.devInfo.linkToManual;</label>&disabledUnsigned.devInfo.end;
                </description>
              </vbox>
              <vbox id="legacy-extensions-notice" class="alert-container" hidden="true">
                <hbox class="alert">
                  <description>&legacyWarning.description;
                    <label class="text-link plain" id="legacy-extensions-learnmore-link">&legacyWarning.showLegacy;</label>
                  </description>
                </hbox>
              </vbox>
              <vbox id="plugindeprecation-notice" class="alert-container">
                <hbox class="alert">
                  <description>&pluginDeprecation.description; &#160;
                    <label class="text-link plain" id="plugindeprecation-learnmore-link">&pluginDeprecation.learnMore;</label>
                  </description>
                </hbox>
              </vbox>
              <vbox id="getthemes-container" class="alert-container">
                <hbox class="alert">
                  <description>&getThemes.description; &#160;
                    <label class="text-link plain" id="getthemes-learnmore-link">&getThemes.learnMore;</label>
                  </description>
                </hbox>
              </vbox>
              <hbox class="view-header global-warning-container">
                <!-- global warnings -->
                <hbox class="global-warning" flex="1">
                  <hbox class="global-warning-safemode" flex="1" align="center"
                        tooltiptext="&warning.safemode.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.safemode.label;"/>
                  </hbox>
                  <hbox class="global-warning-checkcompatibility" flex="1" align="center"
                        tooltiptext="&warning.checkcompatibility.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.checkcompatibility.label;"/>
                  </hbox>
                  <button class="button-link global-warning-checkcompatibility"
                          label="&warning.checkcompatibility.enable.label;"
                          tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                          command="cmd_enableCheckCompatibility"/>
                  <hbox class="global-warning-updatesecurity" flex="1" align="center"
                        tooltiptext="&warning.updatesecurity.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.updatesecurity.label;"/>
                  </hbox>
                  <button class="button-link global-warning-updatesecurity"
                          label="&warning.updatesecurity.enable.label;"
                          tooltiptext="&warning.updatesecurity.enable.tooltip;"
                          command="cmd_enableUpdateSecurity"/>
                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                </hbox>
              </hbox>
              <hbox class="view-header global-info-container experiment-info-container">
                <hbox class="global-info" flex="1" align="center">
                  <label value="&experiment.info.label;"/>
                  <button id="experiments-learn-more"
                          label="&experiment.info.learnmore;"
                          tooltiptext="&experiment.info.learnmore;"
                          accesskey="&experiment.info.learnmore.accesskey;"
                          command="cmd_experimentsLearnMore"/>
                  <button id="experiments-change-telemetry"
                          label="&experiment.info.changetelemetry;"
                          tooltiptext="&experiment.info.changetelemetry;"
                          accesskey="&experiment.info.changetelemetry.accesskey;"
                          command="cmd_experimentsOpenTelemetryPreferences"/>
                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap. -->
                </hbox>
              </hbox>
              <vbox id="addon-list-empty" class="alert-container"
                    flex="1" hidden="true">
                <spacer class="alert-spacer-before"/>
                <vbox class="alert">
                  <label value="&listEmpty.installed.label;"/>
                  <button class="discover-button"
                          id="discover-button-install"
                          label="&listEmpty.button.label;"
                          command="cmd_goToDiscoverPane"/>
                </vbox>
                <spacer class="alert-spacer-after"/>
              </vbox>
              <richlistbox id="addon-list" class="list" flex="1"/>
            </vbox>

            <!-- legacy extensions view -->
            <vbox id="legacy-view" flex="1" class="view-pane" align="stretch" tabindex="0">
              <vbox id="legacy-extensions-info">
                <label id="legacy-extensions-heading" value="&legacyExtensions.title;"/>
                <description>
                  &legacyExtensions.description;
                  <label class="text-link plain" id="legacy-learnmore">&legacyExtensions.learnMore;</label>
                </description>
              </vbox>
              <richlistbox id="legacy-list" class="list" flex="1"/>
            </vbox>

            <!-- updates view -->
            <vbox id="updates-view" flex="1" class="view-pane" tabindex="0">
              <hbox class="view-header global-warning-container" align="center">
                <!-- global warnings -->
                <hbox class="global-warning" flex="1">
                  <hbox class="global-warning-safemode" flex="1" align="center"
                        tooltiptext="&warning.safemode.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.safemode.label;"/>
                  </hbox>
                  <hbox class="global-warning-checkcompatibility" flex="1" align="center"
                        tooltiptext="&warning.checkcompatibility.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.checkcompatibility.label;"/>
                  </hbox>
                  <button class="button-link global-warning-checkcompatibility"
                          label="&warning.checkcompatibility.enable.label;"
                          tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                          command="cmd_enableCheckCompatibility"/>
                  <hbox class="global-warning-updatesecurity" flex="1" align="center"
                        tooltiptext="&warning.updatesecurity.label;">
                    <image class="warning-icon"/>
                    <label class="global-warning-text" flex="1" crop="end"
                           value="&warning.updatesecurity.label;"/>
                  </hbox>
                  <button class="button-link global-warning-updatesecurity"
                          label="&warning.updatesecurity.enable.label;"
                          tooltiptext="&warning.updatesecurity.enable.tooltip;"
                          command="cmd_enableUpdateSecurity"/>
                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                </hbox>
                <spacer flex="1"/>
                <hbox id="updates-sorters" class="sort-controls" sortby="updateDate"
                      ascending="false"/>
              </hbox>
              <vbox id="updates-list-empty" class="alert-container"
                    flex="1" hidden="true">
                <spacer class="alert-spacer-before"/>
                <vbox class="alert">
                  <label id="empty-availableUpdates-msg" value="&listEmpty.availableUpdates.label;"/>
                  <label id="empty-recentUpdates-msg" value="&listEmpty.recentUpdates.label;"/>
                  <button label="&listEmpty.findUpdates.label;"
                          command="cmd_findAllUpdates"/>
                </vbox>
                <spacer class="alert-spacer-after"/>
              </vbox>
              <hbox id="update-actions" pack="center">
                <button id="update-selected-btn" hidden="true"
                        label="&updates.updateSelected.label;"
                        tooltiptext="&updates.updateSelected.tooltip;"/>
              </hbox>
              <richlistbox id="updates-list" class="list" flex="1"/>
            </vbox>

            <!-- detail view -->
            <scrollbox id="detail-view" flex="1" class="view-pane addon-view" orient="vertical" tabindex="0"
                       role="document">
              <!-- global warnings -->
              <hbox class="global-warning-container global-warning">
                <hbox class="global-warning-safemode" flex="1" align="center"
                      tooltiptext="&warning.safemode.label;">
                  <image class="warning-icon"/>
                  <label class="global-warning-text" flex="1" crop="end"
                         value="&warning.safemode.label;"/>
                </hbox>
                <hbox class="global-warning-checkcompatibility" flex="1" align="center"
                      tooltiptext="&warning.checkcompatibility.label;">
                  <image class="warning-icon"/>
                  <label class="global-warning-text" flex="1" crop="end"
                         value="&warning.checkcompatibility.label;"/>
                </hbox>
                <button class="button-link global-warning-checkcompatibility"
                        label="&warning.checkcompatibility.enable.label;"
                        tooltiptext="&warning.checkcompatibility.enable.tooltip;"
                        command="cmd_enableCheckCompatibility"/>
                <hbox class="global-warning-updatesecurity" flex="1" align="center"
                      tooltiptext="&warning.updatesecurity.label;">
                  <image class="warning-icon"/>
                  <label class="global-warning-text" flex="1" crop="end"
                         value="&warning.updatesecurity.label;"/>
                </hbox>
                <button class="button-link global-warning-updatesecurity"
                        label="&warning.updatesecurity.enable.label;"
                        tooltiptext="&warning.updatesecurity.enable.tooltip;"
                        command="cmd_enableUpdateSecurity"/>
                <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
              </hbox>
              <hbox flex="1">
                <spacer flex="1"/>
                <!-- "loading" splash screen -->
                <vbox class="alert-container">
                  <spacer class="alert-spacer-before"/>
                  <hbox class="alert loading">
                    <image/>
                    <label value="&loading.label;"/>
                  </hbox>
                  <spacer class="alert-spacer-after"/>
                </vbox>
                <!-- actual detail view -->
                <vbox class="detail-view-container" flex="3" contextmenu="addonitem-popup">
                  <vbox id="detail-notifications">
                    <hbox id="warning-container" align="center" class="warning">
                      <image class="warning-icon"/>
                      <label id="detail-warning" flex="1"/>
                      <label id="detail-warning-link" class="text-link"/>
                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                    </hbox>
                    <hbox id="error-container" align="center" class="error">
                      <image class="error-icon"/>
                      <label id="detail-error" flex="1"/>
                      <label id="detail-error-link" class="text-link"/>
                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                    </hbox>
                    <hbox id="pending-container" align="center" class="pending">
                      <image class="pending-icon"/>
                      <label id="detail-pending" flex="1"/>
                      <button id="detail-restart-btn" class="button-link"
                              label="&addon.restartNow.label;"
                              command="cmd_restartApp"/>
                      <button id="detail-undo-btn" class="button-link"
                              label="&addon.undoAction.label;"
                              tooltipText="&addon.undoAction.tooltip;"
                              command="cmd_cancelOperation"/>
                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
                    </hbox>
                  </vbox>
                  <hbox align="start">
                    <vbox id="detail-icon-container" align="end">
                      <image id="detail-icon" class="icon"/>
                    </vbox>
                    <vbox flex="1">
                      <vbox id="detail-summary">
                        <hbox id="detail-name-container" class="name-container"
                              align="start">
                          <label id="detail-name" flex="1"/>
                          <label id="detail-version"/>
                          <label id="detail-legacy-warning" class="legacy-warning text-link" value="&addon.legacy.label;"/>
                          <label class="disabled-postfix" value="&addon.disabled.postfix;"/>
                          <label class="update-postfix" value="&addon.update.postfix;"/>
                          <spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
                        </hbox>
                        <label id="detail-creator" class="creator"/>
                      </vbox>
                      <hbox id="detail-experiment-container">
                        <svg width="8" height="8" viewBox="0 0 8 8" version="1.1"
                             xmlns="http://www.w3.org/2000/svg"
                             id="detail-experiment-bullet-container">
                          <circle cx="4" cy="4" r="4" id="detail-experiment-bullet"/>
                        </svg>
                        <label id="detail-experiment-state"/>
                        <label id="detail-experiment-time"/>
                      </hbox>
                      <hbox id="detail-desc-container" align="start">
                        <vbox id="detail-screenshot-box" pack="center" hidden="true"> <!-- Necessary to work around bug 394738 -->
                          <image id="detail-screenshot"/>
                        </vbox>
                        <vbox flex="1">
                          <description id="detail-desc"/>
                          <description id="detail-fulldesc"/>
                        </vbox>
                      </hbox>
                      <vbox id="detail-contributions">
                        <description id="detail-contrib-description">
                          &detail.contributions.description;
                        </description>
                        <hbox align="center">
                          <label id="detail-contrib-suggested"/>
                          <spacer flex="1"/>
                          <button id="detail-contrib-btn"
                                  label="&cmd.contribute.label;"
                                  accesskey="&cmd.contribute.accesskey;"
                                  tooltiptext="&cmd.contribute.tooltip;"
                                  command="cmd_contribute"/>
                        </hbox>
                      </vbox>
                      <grid id="detail-grid">
                        <columns>
                           <column flex="1"/>
                           <column flex="2"/>
                        </columns>
                        <rows id="detail-rows">
                          <row class="detail-row-complex" id="detail-updates-row">
                            <label class="detail-row-label" value="&detail.updateType;"/>
                            <hbox align="center">
                              <radiogroup id="detail-autoUpdate" orient="horizontal">
                                <!-- The values here need to match the values of
                                     AddonManager.AUTOUPDATE_* -->
                                <radio label="&detail.updateDefault.label;"
                                       tooltiptext="&detail.updateDefault.tooltip;"
                                       value="1"/>
                                <radio label="&detail.updateAutomatic.label;"
                                       tooltiptext="&detail.updateAutomatic.tooltip;"
                                       value="2"/>
                                <radio label="&detail.updateManual.label;"
                                       tooltiptext="&detail.updateManual.tooltip;"
                                       value="0"/>
                              </radiogroup>
                              <button id="detail-findUpdates-btn" class="button-link"
                                      label="&detail.checkForUpdates.label;"
                                      accesskey="&detail.checkForUpdates.accesskey;"
                                      tooltiptext="&detail.checkForUpdates.tooltip;"
                                      command="cmd_findItemUpdates"/>
                            </hbox>
                          </row>
                          <row class="detail-row" id="detail-dateUpdated" label="&detail.lastupdated.label;"/>
                          <row class="detail-row-complex" id="detail-homepage-row" label="&detail.home;">
                            <label class="detail-row-label" value="&detail.home;"/>
                            <label id="detail-homepage" class="detail-row-value text-link" crop="end"/>
                          </row>
                          <row class="detail-row-complex" id="detail-repository-row" label="&detail.repository;">
                            <label class="detail-row-label" value="&detail.repository;"/>
                            <label id="detail-repository" class="detail-row-value text-link"/>
                          </row>
                          <row class="detail-row" id="detail-size" label="&detail.size;"/>
                          <row class="detail-row-complex" id="detail-rating-row">
                            <label class="detail-row-label" value="&rating2.label;"/>
                            <hbox>
                              <label id="detail-rating" class="meta-value meta-rating"
                                     showrating="average"/>
                              <label id="detail-reviews" class="text-link"/>
                            </hbox>
                          </row>
                          <row class="detail-row" id="detail-downloads" label="&detail.numberOfDownloads.label;"/>
                        </rows>
                      </grid>
                      <hbox id="detail-controls">
                        <button id="detail-prefs-btn" class="addon-control preferences"
                                label="&detail.showPreferencesWin.label;"
                                accesskey="&detail.showPreferencesWin.accesskey;"
                                tooltiptext="&detail.showPreferencesWin.tooltip;"
                                command="cmd_showItemPreferences"/>
                        <spacer flex="1"/>
                        <button id="detail-enable-btn" class="addon-control enable"
                                label="&cmd.enableAddon.label;"
                                accesskey="&cmd.enableAddon.accesskey;"
                                command="cmd_enableItem"/>
                        <button id="detail-disable-btn" class="addon-control disable"
                                label="&cmd.disableAddon.label;"
                                accesskey="&cmd.disableAddon.accesskey;"
                                command="cmd_disableItem"/>
                        <button id="detail-uninstall-btn" class="addon-control remove"
                                label="&cmd.uninstallAddon.label;"
                                accesskey="&cmd.uninstallAddon.accesskey;"
                                command="cmd_uninstallItem"/>
                        <button id="detail-purchase-btn" class="addon-control purchase"
                                command="cmd_purchaseItem"/>
                        <button id="detail-install-btn" class="addon-control install"
                                label="&cmd.installAddon.label;"
                                accesskey="&cmd.installAddon.accesskey;"
                                command="cmd_installItem"/>
                        <menulist id="detail-state-menulist"
                                  crop="none" sizetopopup="always"
                                  tooltiptext="&cmd.stateMenu.tooltip;">
                          <menupopup>
                            <menuitem id="detail-ask-to-activate-menuitem"
                                      class="addon-control"
                                      label="&cmd.askToActivate.label;"
                                      tooltiptext="&cmd.askToActivate.tooltip;"
                                      command="cmd_askToActivateItem"/>
                            <menuitem id="detail-always-activate-menuitem"
                                      class="addon-control"
                                      label="&cmd.alwaysActivate.label;"
                                      tooltiptext="&cmd.alwaysActivate.tooltip;"
                                      command="cmd_alwaysActivateItem"/>
                            <menuitem id="detail-never-activate-menuitem"
                                      class="addon-control"
                                      label="&cmd.neverActivate.label;"
                                      tooltiptext="&cmd.neverActivate.tooltip;"
                                      command="cmd_neverActivateItem"/>
                          </menupopup>
                        </menulist>
                      </hbox>
                    </vbox>
                  </hbox>
                </vbox>
                <spacer flex="1"/>
              </hbox>
            </scrollbox>
          </deck>
        </vbox>
      </deck>
    </vbox>
  </hbox>
</page>
PK
!<)__6chrome/toolkit/content/mozapps/extensions/gmpPrefs.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This is intentionally empty and a dummy to let the GMPProvider
     have a preferences button in the list view. -->
PK
!<5^5chrome/toolkit/content/mozapps/extensions/newaddon.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 cancelClicked, continueClicked, initialize, restartClicked, unload */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");

var gAddon = null;

// If the user enables the add-on through some other UI close this window
var EnableListener = {
  onEnabling(aAddon) {
    if (aAddon.id == gAddon.id)
      window.close();
  }
}
AddonManager.addAddonListener(EnableListener);

function initialize() {
  // About URIs don't implement nsIURL so we have to find the query string
  // manually
  let spec = document.location.href;
  let pos = spec.indexOf("?");
  let query = "";
  if (pos >= 0)
    query = spec.substring(pos + 1);

  // Just assume the query is "id=<id>"
  let id = query.substring(3);
  if (!id) {
    window.location = "about:blank";
    return;
  }

  let bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/newaddon.properties");

  AddonManager.getAddonByID(id, function(aAddon) {
    // If the add-on doesn't exist or it is already enabled or it has already
    // been seen or it cannot be enabled then this UI is useless, just close it.
    // This shouldn't normally happen unless session restore restores the tab.
    if (!aAddon || !aAddon.userDisabled || aAddon.seen ||
        !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) {
      window.close();
      return;
    }

    gAddon = aAddon;

    document.getElementById("addon-info").setAttribute("type", aAddon.type);

    let icon = document.getElementById("icon");
    if (aAddon.icon64URL)
      icon.src = aAddon.icon64URL;
    else if (aAddon.iconURL)
      icon.src = aAddon.iconURL;

    let name = bundle.formatStringFromName("name", [aAddon.name, aAddon.version],
                                           2);
    document.getElementById("name").value = name;

    if (aAddon.creator) {
      let creator = bundle.formatStringFromName("author", [aAddon.creator], 1);
      document.getElementById("author").value = creator;
    } else {
      document.getElementById("author").hidden = true;
    }

    let uri = "getResourceURI" in aAddon ? aAddon.getResourceURI() : null;
    let locationLabel = document.getElementById("location");
    if (uri instanceof Ci.nsIFileURL) {
      let location = bundle.formatStringFromName("location", [uri.file.path], 1);
      locationLabel.value = location;
      locationLabel.setAttribute("tooltiptext", location);
    } else {
      document.getElementById("location").hidden = true;
    }

    // Only mark the add-on as seen if the page actually gets focus
    if (document.hasFocus()) {
      aAddon.markAsSeen();
    } else {
      document.addEventListener("focus", () => aAddon.markAsSeen());
    }

    var event = document.createEvent("Events");
    event.initEvent("AddonDisplayed", true, true);
    document.dispatchEvent(event);
  });
}

function unload() {
  AddonManager.removeAddonListener(EnableListener);
}

function continueClicked() {
  AddonManager.removeAddonListener(EnableListener);

  if (document.getElementById("allow").checked) {
    gAddon.userDisabled = false;

    if (gAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
      document.getElementById("allow").disabled = true;
      document.getElementById("buttonDeck").selectedPanel = document.getElementById("restartPanel");
      return;
    }
  }

  window.close();
}

function restartClicked() {
  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                   createInstance(Ci.nsISupportsPRBool);
  Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                               "restart");
  if (cancelQuit.data)
    return; // somebody canceled our quit request

  window.close();

  let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
                   getService(Components.interfaces.nsIAppStartup);
  appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
}

function cancelClicked() {
  gAddon.userDisabled = true;
  AddonManager.addAddonListener(EnableListener);

  document.getElementById("allow").disabled = false;
  document.getElementById("buttonDeck").selectedPanel = document.getElementById("continuePanel");
}
PK
!<yl1	1	6chrome/toolkit/content/mozapps/extensions/newaddon.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/newaddon.css"?>

<!DOCTYPE page [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % newaddonDTD SYSTEM "chrome://mozapps/locale/extensions/newaddon.dtd">
%newaddonDTD;
]>

<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      xmlns:xhtml="http://www.w3.org/1999/xhtml" title="&title;"
      disablefastfind="true" id="addon-page" onload="initialize()"
      onunload="unload()" role="application" align="stretch" pack="stretch">

  <xhtml:link rel="shortcut icon" style="display: none"
              href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>

  <script type="application/javascript"
          src="chrome://mozapps/content/extensions/newaddon.js"/>

  <scrollbox id="addon-scrollbox" align="center">
    <spacer id="spacer-start"/>

    <vbox id="addon-container" class="main-content">
      <description>&intro;</description>

      <hbox id="addon-info">
        <image id="icon"/>
        <vbox flex="1">
          <label id="name"/>
          <label id="author"/>
          <label id="location" crop="end"/>
        </vbox>
      </hbox>

      <hbox id="warning">
        <image id="warning-icon"/>
        <description flex="1">&warning;</description>
      </hbox>

      <checkbox id="allow" label="&allow;"/>
      <description id="later">&later;</description>

      <deck id="buttonDeck">
        <hbox id="continuePanel">
          <button id="continue-button" label="&continue;"
                  oncommand="continueClicked()"/>
        </hbox>
        <vbox id="restartPanel">
          <description id="restartMessage">&restartMessage;</description>
          <hbox id="restartPanelButtons">
            <button id="restart-button" label="&restartButton;" oncommand="restartClicked()"/>
            <button id="cancel-button" label="&cancelButton;" oncommand="cancelClicked()"/>
          </hbox>
        </vbox>
      </deck>
    </vbox>

    <spacer id="spacer-end"/>
  </scrollbox>
</page>
PK
!<j.rbb9chrome/toolkit/content/mozapps/extensions/pluginPrefs.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/.  -->

<!DOCTYPE window SYSTEM "chrome://pluginproblem/locale/pluginproblem.dtd">

<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <setting type="control" title="&plugin.file;">
    <label class="text-list" id="pluginLibraries"/>
  </setting>
  <setting type="control" title="&plugin.mimeTypes;">
    <label class="text-list" id="pluginMimeTypes"/>
  </setting>
  <setting type="bool" pref="plugins.flashBlock.enabled"
           id="pluginFlashBlocking"
           title="&plugin.enableBlocklists.label;"
           learnmore="https://support.mozilla.org/kb/flash-blocklists" />
  <setting type="bool" pref="dom.ipc.plugins.flash.disable-protected-mode"
           inverted="true" title="&plugin.flashProtectedMode.label;"
           id="pluginEnableProtectedMode"
           learnmore="https://support.mozilla.org/kb/flash-protected-mode-settings" />
</vbox>
PK
!<@E??5chrome/toolkit/content/mozapps/extensions/setting.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE page [
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>

<!-- import-globals-from extensions.js -->

<bindings xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="setting-base">
    <implementation>
      <constructor><![CDATA[
        this.preferenceChanged();

        this.addEventListener("keypress", function(event) {
          event.stopPropagation();
        });

        if (this.usePref)
          Services.prefs.addObserver(this.pref, this._observer, true);
      ]]></constructor>

      <field name="_observer"><![CDATA[({
        _self: this,

        QueryInterface(aIID) {
          const Ci = Components.interfaces;
          if (aIID.equals(Ci.nsIObserver) ||
              aIID.equals(Ci.nsISupportsWeakReference) ||
              aIID.equals(Ci.nsISupports))
            return this;

          throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
        },

        observe(aSubject, aTopic, aPrefName) {
          if (aTopic != "nsPref:changed")
            return;

          if (this._self.pref == aPrefName)
            this._self.preferenceChanged();
        }
      })]]>
      </field>

      <method name="fireEvent">
        <parameter name="eventName"/>
        <parameter name="funcStr"/>
        <body>
          <![CDATA[
            let body = funcStr || this.getAttribute(eventName);
            if (!body)
              return;

            try {
              let event = document.createEvent("Events");
              event.initEvent(eventName, true, true);
              let f = new Function("event", body);
              f.call(this, event);
            } catch (e) {
              Cu.reportError(e);
            }
          ]]>
        </body>
      </method>

      <method name="valueFromPreference">
        <body>
        <![CDATA[
          // Should be code to set the from the preference input.value
          throw Components.Exception("No valueFromPreference implementation",
                                     Components.results.NS_ERROR_NOT_IMPLEMENTED);
        ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          // Should be code to set the input.value from the preference
          throw Components.Exception("No valueToPreference implementation",
                                     Components.results.NS_ERROR_NOT_IMPLEMENTED);
        ]]>
        </body>
      </method>

      <method name="inputChanged">
        <body>
        <![CDATA[
          if (this.usePref && !this._updatingInput) {
            this.valueToPreference();
            this.fireEvent("oninputchanged");
          }
        ]]>
        </body>
      </method>

      <method name="preferenceChanged">
        <body>
        <![CDATA[
          if (this.usePref) {
            this._updatingInput = true;
            try {
              this.valueFromPreference();
              this.fireEvent("onpreferencechanged");
            } catch (e) {}
            this._updatingInput = false;
          }
        ]]>
        </body>
      </method>

      <property name="usePref" readonly="true" onget="return this.hasAttribute('pref');"/>
      <property name="pref" readonly="true" onget="return this.getAttribute('pref');"/>
      <property name="type" readonly="true" onget="return this.getAttribute('type');"/>
      <property name="value" onget="return this.input.value;" onset="return this.input.value = val;"/>

      <field name="_updatingInput">false</field>
      <field name="input">document.getAnonymousElementByAttribute(this, "anonid", "input");</field>
      <field name="settings">
        this.parentNode.localName == "settings" ? this.parentNode : null;
      </field>
    </implementation>
  </binding>

  <binding id="setting-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
        <xul:label class="preferences-learnmore text-link"
                   onclick="document.getBindingParent(this).openLearnMore()">&setting.learnmore;</xul:label>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <xul:checkbox anonid="input" xbl:inherits="disabled,onlabel,offlabel,label=checkboxlabel" oncommand="inputChanged();"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          let val = Services.prefs.getBoolPref(this.pref);
          this.value = this.inverted ? !val : val;
         ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          let val = this.value;
          Services.prefs.setBoolPref(this.pref, this.inverted ? !val : val);
        ]]>
        </body>
      </method>

      <property name="value" onget="return this.input.checked;" onset="return this.input.setChecked(val);"/>
      <property name="inverted" readonly="true" onget="return this.getAttribute('inverted');"/>

      <method name="openLearnMore">
        <body>
        <![CDATA[
          window.open(this.getAttribute("learnmore"), "_blank");
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="setting-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          let val = Services.prefs.getIntPref(this.pref);
          this.value = (val == this.getAttribute("on"));
         ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          Services.prefs.setIntPref(this.pref, this.getAttribute(this.value ? "on" : "off"));
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="setting-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          let val = Services.prefs.getComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString).data;
          if (this.inverted) val = !val;
          this.value = (val == "true");
         ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          let val = this.value;
          if (this.inverted) val = !val;
          let pref = Components.classes["@mozilla.org/pref-localizedstring;1"].createInstance(Components.interfaces.nsIPrefLocalizedString);
          pref.data = this.inverted ? (!val).toString() : val.toString();
          Services.prefs.setComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString, pref);
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="setting-integer" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <xul:textbox type="number" anonid="input" oninput="inputChanged();" onchange="inputChanged();"
                     xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound,size"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          let val = Services.prefs.getIntPref(this.pref);
          this.value = val;
         ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          Services.prefs.setIntPref(this.pref, this.value);
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="setting-control" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <children/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="setting-string" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <xul:textbox anonid="input" flex="1" oninput="inputChanged();"
                     xbl:inherits="disabled,emptytext,type=inputtype,min,max,increment,hidespinbuttons,decimalplaces,wraparound"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          this.value = Preferences.get(this.pref, "");
         ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          Preferences.set(this.pref, this.value);
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="setting-color" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <xul:colorpicker type="button" anonid="input" xbl:inherits="disabled" onchange="document.getBindingParent(this).inputChanged();"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="valueFromPreference">
        <body>
        <![CDATA[
          // We must wait for the colorpicker's binding to be applied before setting the value
          if (!this.input.color)
            this.input.initialize();
          this.value = Services.prefs.getCharPref(this.pref);
        ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          Services.prefs.setCharPref(this.pref, this.value);
        ]]>
        </body>
      </method>

      <property name="value" onget="return this.input.color;" onset="return this.input.color = val;"/>
    </implementation>
  </binding>

  <binding id="setting-path" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <xul:button type="button" anonid="button" label="&settings.path.button.label;" xbl:inherits="disabled" oncommand="showPicker();"/>
        <xul:label anonid="input" flex="1" crop="center" xbl:inherits="disabled"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="showPicker">
        <body>
        <![CDATA[
          var filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
          filePicker.init(window, this.getAttribute("title"),
                          this.type == "file" ? Ci.nsIFilePicker.modeOpen : Ci.nsIFilePicker.modeGetFolder);
          if (this.value) {
            try {
              let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
              file.initWithPath(this.value);
              filePicker.displayDirectory = this.type == "file" ? file.parent : file;
              if (this.type == "file") {
                filePicker.defaultString = file.leafName;
              }
            } catch (e) {}
          }
          filePicker.open(rv => {
            if (rv != Ci.nsIFilePicker.returnCancel && filePicker.file) {
              this.value = filePicker.file.path;
              this.inputChanged();
            }
          });
        ]]>
        </body>
      </method>

      <method name="valueFromPreference">
        <body>
        <![CDATA[
          this.value = Preferences.get(this.pref, "");
        ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          Preferences.set(this.pref, this.value);
        ]]>
        </body>
      </method>

      <field name="_value"></field>

      <property name="value">
        <getter>
        <![CDATA[
          return this._value;
        ]]>
        </getter>
        <setter>
        <![CDATA[
          this._value = val;
          let label = "";
          if (val) {
            try {
              let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
              file.initWithPath(val);
              label = this.hasAttribute("fullpath") ? file.path : file.leafName;
            } catch (e) {}
          }
          this.input.tooltipText = val;
          return this.input.value = label;
       ]]>
        </setter>
      </property>
    </implementation>
  </binding>

  <binding id="setting-multi" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
    <content>
      <xul:vbox>
        <xul:hbox class="preferences-alignment">
          <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
        </xul:hbox>
        <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
      </xul:vbox>
      <xul:hbox class="preferences-alignment">
        <children includes="radiogroup|menulist"/>
      </xul:hbox>
    </content>

    <implementation>
      <constructor>
      <![CDATA[
        this.control.addEventListener("command", this.inputChanged.bind(this));
      ]]>
      </constructor>

      <method name="valueFromPreference">
        <body>
        <![CDATA[
          let val = Preferences.get(this.pref, "").toString();

          if ("itemCount" in this.control) {
            for (let i = 0; i < this.control.itemCount; i++) {
              if (this.control.getItemAtIndex(i).value == val) {
                this.control.selectedIndex = i;
                break;
              }
            }
          } else {
            this.control.setAttribute("value", val);
          }
        ]]>
        </body>
      </method>

      <method name="valueToPreference">
        <body>
        <![CDATA[
          // We might not have a pref already set, so we guess the type from the value attribute
          let val = this.control.selectedItem.value;
          if (val == "true" || val == "false") {
            val = val == "true";
          } else if (/^-?\d+$/.test(val)) {
            val = parseInt(val, 10);
          }
          Preferences.set(this.pref, val);
        ]]>
        </body>
      </method>

      <field name="control">this.getElementsByTagName(this.getAttribute("type") == "radio" ? "radiogroup" : "menulist")[0];</field>
    </implementation>
  </binding>
</bindings>
PK
!<F>W>W3chrome/toolkit/content/mozapps/extensions/update.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/. */

// This UI is only opened from the Extension Manager when the app is upgraded.

"use strict";

/* exported gAdminDisabledPage, gFinishedPage, gFoundPage, gInstallErrorsPage,
 *          gNoUpdatesPage, gOfflinePage, gUpdatePage */

const PREF_UPDATE_EXTENSIONS_ENABLED            = "extensions.update.enabled";
const PREF_XPINSTALL_ENABLED                    = "xpinstall.enabled";

// timeout (in milliseconds) to wait for response to the metadata ping
const METADATA_TIMEOUT    = 30000;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
var logger = null;

var gUpdateWizard = {
  // When synchronizing app compatibility info this contains all installed
  // add-ons. When checking for compatible versions this contains only
  // incompatible add-ons.
  addons: [],
  // Contains a Set of IDs for add-on that were disabled by the application update.
  affectedAddonIDs: null,
  // The add-ons that we found updates available for
  addonsToUpdate: [],
  shouldSuggestAutoChecking: false,
  shouldAutoCheck: false,
  xpinstallEnabled: true,
  xpinstallLocked: false,
  // cached AddonInstall entries for add-ons we might want to update,
  // keyed by add-on ID
  addonInstalls: new Map(),
  shuttingDown: false,
  // Count the add-ons disabled by this update, enabled/disabled by
  // metadata checks, and upgraded.
  disabled: 0,
  metadataEnabled: 0,
  metadataDisabled: 0,
  upgraded: 0,
  upgradeFailed: 0,
  upgradeDeclined: 0,

  init() {
    logger = Log.repository.getLogger("addons.update-dialog");
    // XXX could we pass the addons themselves rather than the IDs?
    this.affectedAddonIDs = new Set(window.arguments[0]);

    try {
      this.shouldSuggestAutoChecking =
        !Services.prefs.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED);
    } catch (e) {
    }

    try {
      this.xpinstallEnabled = Services.prefs.getBoolPref(PREF_XPINSTALL_ENABLED);
      this.xpinstallLocked = Services.prefs.prefIsLocked(PREF_XPINSTALL_ENABLED);
    } catch (e) {
    }

    if (Services.io.offline)
      document.documentElement.currentPage = document.getElementById("offline");
    else
      document.documentElement.currentPage = document.getElementById("versioninfo");
  },

  onWizardFinish: function gUpdateWizard_onWizardFinish() {
    if (this.shouldSuggestAutoChecking)
      Services.prefs.setBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED, this.shouldAutoCheck);
  },

  _setUpButton(aButtonID, aButtonKey, aDisabled) {
    var strings = document.getElementById("updateStrings");
    var button = document.documentElement.getButton(aButtonID);
    if (aButtonKey) {
      button.label = strings.getString(aButtonKey);
      try {
        button.setAttribute("accesskey", strings.getString(aButtonKey + "Accesskey"));
      } catch (e) {
      }
    }
    button.disabled = aDisabled;
  },

  setButtonLabels(aBackButton, aBackButtonIsDisabled,
                             aNextButton, aNextButtonIsDisabled,
                             aCancelButton, aCancelButtonIsDisabled) {
    this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
    this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
    this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
  },

  // Update Errors
  errorItems: [],

  checkForErrors(aElementIDToShow) {
    if (this.errorItems.length > 0)
      document.getElementById(aElementIDToShow).hidden = false;
  },

  onWizardClose(aEvent) {
    return this.onWizardCancel();
  },

  onWizardCancel() {
    gUpdateWizard.shuttingDown = true;
    // Allow add-ons to continue downloading and installing
    // in the background, though some may require a later restart
    // Pages that are waiting for user input go into the background
    // on cancel
    if (gMismatchPage.waiting) {
      logger.info("Dialog closed in mismatch page");
      if (gUpdateWizard.addonInstalls.size > 0) {
        gInstallingPage.startInstalls(
          Array.from(gUpdateWizard.addonInstalls.values()));
      }
      return true;
    }

    // Pages that do asynchronous things will just keep running and check
    // gUpdateWizard.shuttingDown to trigger background behaviour
    if (!gInstallingPage.installing) {
      logger.info("Dialog closed while waiting for updated compatibility information");
    } else {
      logger.info("Dialog closed while downloading and installing updates");
    }
    return true;
  }
};

var gOfflinePage = {
  onPageAdvanced() {
    Services.io.offline = false;
    return true;
  },

  toggleOffline() {
    var nextbtn = document.documentElement.getButton("next");
    nextbtn.disabled = !nextbtn.disabled;
  }
}

// Addon listener to count addons enabled/disabled by metadata checks
var listener = {
  onDisabled(aAddon) {
    gUpdateWizard.affectedAddonIDs.add(aAddon.id);
    gUpdateWizard.metadataDisabled++;
  },
  onEnabled(aAddon) {
    gUpdateWizard.affectedAddonIDs.delete(aAddon.id);
    gUpdateWizard.metadataEnabled++;
  }
};

var gVersionInfoPage = {
  _completeCount: 0,
  _totalCount: 0,
  _versionInfoDone: false,
  async onPageShow() {
    gUpdateWizard.setButtonLabels(null, true,
                                  "nextButtonText", true,
                                  "cancelButtonText", false);

    gUpdateWizard.disabled = gUpdateWizard.affectedAddonIDs.size;

    // Ensure compatibility overrides are up to date before checking for
    // individual addon updates.
    AddonManager.addAddonListener(listener);
    if (AddonRepository.isMetadataStale()) {
      // Do the metadata ping, listening for any newly enabled/disabled add-ons.
      await AddonRepository.repopulateCache(METADATA_TIMEOUT);
      if (gUpdateWizard.shuttingDown) {
        logger.debug("repopulateCache completed after dialog closed");
      }
    }
    // Fetch the add-ons that are still affected by this update,
    // excluding the hotfix add-on.
    let idlist = Array.from(gUpdateWizard.affectedAddonIDs).filter(
      a => a.id != AddonManager.hotfixID);
    if (idlist.length < 1) {
      gVersionInfoPage.onAllUpdatesFinished();
      return;
    }

    logger.debug("Fetching affected addons " + idlist.toSource());
    let fetchedAddons = await AddonManager.getAddonsByIDs(idlist);
    // We shouldn't get nulls here, but let's be paranoid...
    gUpdateWizard.addons = fetchedAddons.filter(a => a);
    if (gUpdateWizard.addons.length < 1) {
      gVersionInfoPage.onAllUpdatesFinished();
      return;
    }

    gVersionInfoPage._totalCount = gUpdateWizard.addons.length;

    for (let addon of gUpdateWizard.addons) {
      logger.debug("VersionInfo Finding updates for ${id}", addon);
      addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
    }
  },

  onAllUpdatesFinished() {
    AddonManager.removeAddonListener(listener);
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_disabled",
        gUpdateWizard.disabled);
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_enabled",
        gUpdateWizard.metadataEnabled);
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_disabled",
        gUpdateWizard.metadataDisabled);
    // Record 0 for these here in case we exit early; values will be replaced
    // later if we actually upgrade any.
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded", 0);
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed", 0);
    AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined", 0);
    // Filter out any add-ons that are now enabled.
    let addonList = gUpdateWizard.addons.map(a => a.id + ":" + a.appDisabled);
    logger.debug("VersionInfo updates finished: found " + addonList.toSource());
    let filteredAddons = [];
    for (let a of gUpdateWizard.addons) {
      if (a.appDisabled) {
        logger.debug("Continuing with add-on " + a.id);
        filteredAddons.push(a);
      } else if (gUpdateWizard.addonInstalls.has(a.id)) {
        gUpdateWizard.addonInstalls.get(a.id).cancel();
        gUpdateWizard.addonInstalls.delete(a.id);
      }
    }
    gUpdateWizard.addons = filteredAddons;

    if (gUpdateWizard.shuttingDown) {
      // jump directly to updating auto-update add-ons in the background
      if (gUpdateWizard.addonInstalls.size > 0) {
        let installs = Array.from(gUpdateWizard.addonInstalls.values());
        gInstallingPage.startInstalls(installs);
      }
      return;
    }

    if (filteredAddons.length > 0) {
      if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) {
        document.documentElement.currentPage = document.getElementById("adminDisabled");
        return;
      }
      document.documentElement.currentPage = document.getElementById("mismatch");
    } else {
      logger.info("VersionInfo: No updates require further action");
      // VersionInfo compatibility updates resolved all compatibility problems,
      // close this window and continue starting the application...
      // XXX Bug 314754 - We need to use setTimeout to close the window due to
      // the EM using xmlHttpRequest when checking for updates.
      setTimeout(close, 0);
    }
  },

  // UpdateListener
  onUpdateFinished(aAddon, status) {
    ++this._completeCount;

    if (status != AddonManager.UPDATE_STATUS_NO_ERROR) {
      logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
           " failed for " + aAddon.id + ": " + status);
      gUpdateWizard.errorItems.push(aAddon);
    } else {
      logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
           " finished for " + aAddon.id);
    }

    // If we're not in the background, just make a list of add-ons that have
    // updates available
    if (!gUpdateWizard.shuttingDown) {
      // If we're still in the update check window and the add-on is now active
      // then it won't have been disabled by startup
      if (aAddon.active) {
        AddonManagerPrivate.removeStartupChange(AddonManager.STARTUP_CHANGE_DISABLED, aAddon.id);
        gUpdateWizard.metadataEnabled++;
      }

      // Update the status text and progress bar
      var updateStrings = document.getElementById("updateStrings");
      var statusElt = document.getElementById("versioninfo.status");
      var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
      statusElt.setAttribute("value", statusString);

      // Update the status text and progress bar
      var progress = document.getElementById("versioninfo.progress");
      progress.mode = "normal";
      progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
    }

    if (this._completeCount == this._totalCount)
      this.onAllUpdatesFinished();
  },

  onUpdateAvailable(aAddon, aInstall) {
    logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version);
    gUpdateWizard.addonInstalls.set(aAddon.id, aInstall);
  },
};

var gMismatchPage = {
  waiting: false,

  onPageShow() {
    gMismatchPage.waiting = true;
    gUpdateWizard.setButtonLabels(null, true,
                                  "mismatchCheckNow", false,
                                  "mismatchDontCheck", false);
    document.documentElement.getButton("next").focus();

    var incompatible = document.getElementById("mismatch.incompatible");
    for (let addon of gUpdateWizard.addons) {
      var listitem = document.createElement("listitem");
      listitem.setAttribute("label", addon.name + " " + addon.version);
      incompatible.appendChild(listitem);
    }
  }
};

var gUpdatePage = {
  _totalCount: 0,
  _completeCount: 0,
  onPageShow() {
    gMismatchPage.waiting = false;
    gUpdateWizard.setButtonLabels(null, true,
                                  "nextButtonText", true,
                                  "cancelButtonText", false);
    document.documentElement.getButton("next").focus();

    gUpdateWizard.errorItems = [];

    this._totalCount = gUpdateWizard.addons.length;
    for (let addon of gUpdateWizard.addons) {
      logger.debug("UpdatePage requesting update for " + addon.id);
      // Redundant call to find updates again here when we already got them
      // in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597
      addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
    }
  },

  onAllUpdatesFinished() {
    if (gUpdateWizard.shuttingDown)
      return;

    var nextPage = document.getElementById("noupdates");
    if (gUpdateWizard.addonsToUpdate.length > 0)
      nextPage = document.getElementById("found");
    document.documentElement.currentPage = nextPage;
  },

  // UpdateListener
  onUpdateAvailable(aAddon, aInstall) {
    logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version);
    gUpdateWizard.addonsToUpdate.push(aInstall);
  },

  onUpdateFinished(aAddon, status) {
    if (status != AddonManager.UPDATE_STATUS_NO_ERROR)
      gUpdateWizard.errorItems.push(aAddon);

    ++this._completeCount;

    if (!gUpdateWizard.shuttingDown) {
      // Update the status text and progress bar
      var updateStrings = document.getElementById("updateStrings");
      var statusElt = document.getElementById("checking.status");
      var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
      statusElt.setAttribute("value", statusString);

      var progress = document.getElementById("checking.progress");
      progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
    }

    if (this._completeCount == this._totalCount)
      this.onAllUpdatesFinished()
  },
};

var gFoundPage = {
  onPageShow() {
    gUpdateWizard.setButtonLabels(null, true,
                                  "installButtonText", false,
                                  null, false);

    var foundUpdates = document.getElementById("found.updates");
    for (let install of gUpdateWizard.addonsToUpdate) {
      let listItem = foundUpdates.appendItem(install.name + " " + install.version);
      listItem.setAttribute("type", "checkbox");
      listItem.setAttribute("checked", "true");
      listItem.install = install;
    }

    if (!gUpdateWizard.xpinstallEnabled) {
      document.getElementById("xpinstallDisabledAlert").hidden = false;
      document.getElementById("enableXPInstall").focus();
      document.documentElement.getButton("next").disabled = true;
    } else {
      document.documentElement.getButton("next").focus();
      document.documentElement.getButton("next").disabled = false;
    }
  },

  toggleXPInstallEnable(aEvent) {
    var enabled = aEvent.target.checked;
    gUpdateWizard.xpinstallEnabled = enabled;
    var pref = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
    pref.setBoolPref(PREF_XPINSTALL_ENABLED, enabled);
    this.updateNextButton();
  },

  updateNextButton() {
    if (!gUpdateWizard.xpinstallEnabled) {
      document.documentElement.getButton("next").disabled = true;
      return;
    }

    var oneChecked = false;
    var foundUpdates = document.getElementById("found.updates");
    var updates = foundUpdates.getElementsByTagName("listitem");
    for (let update of updates) {
      if (!update.checked)
        continue;
      oneChecked = true;
      break;
    }

    gUpdateWizard.setButtonLabels(null, true,
                                  "installButtonText", true,
                                  null, false);
    document.getElementById("found").setAttribute("next", "installing");
    document.documentElement.getButton("next").disabled = !oneChecked;
  }
};

var gInstallingPage = {
  _installs: [],
  _errors: [],
  _strings: null,
  _currentInstall: -1,
  _installing: false,

  // Initialize fields we need for installing and tracking progress,
  // and start iterating through the installations
  startInstalls(aInstallList) {
    if (!gUpdateWizard.xpinstallEnabled) {
      return;
    }

    let installs = Array.from(aInstallList).map(a => a.existingAddon.id);
    logger.debug("Start installs for " + installs.toSource());
    this._errors = [];
    this._installs = aInstallList;
    this._installing = true;
    this.startNextInstall();
  },

  onPageShow() {
    gUpdateWizard.setButtonLabels(null, true,
                                  "nextButtonText", true,
                                  null, true);

    var foundUpdates = document.getElementById("found.updates");
    var updates = foundUpdates.getElementsByTagName("listitem");
    let toInstall = [];
    for (let update of updates) {
      if (!update.checked) {
        logger.info("User chose to cancel update of " + update.label);
        gUpdateWizard.upgradeDeclined++;
        update.install.cancel();
        continue;
      }
      toInstall.push(update.install);
    }
    this._strings = document.getElementById("updateStrings");

    this.startInstalls(toInstall);
  },

  startNextInstall() {
    if (this._currentInstall >= 0) {
      this._installs[this._currentInstall].removeListener(this);
    }

    this._currentInstall++;

    if (this._installs.length == this._currentInstall) {
      Services.obs.notifyObservers(null, "TEST:all-updates-done");
      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded",
          gUpdateWizard.upgraded);
      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed",
          gUpdateWizard.upgradeFailed);
      AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined",
          gUpdateWizard.upgradeDeclined);
      this._installing = false;
      if (gUpdateWizard.shuttingDown) {
        return;
      }
      var nextPage = this._errors.length > 0 ? "installerrors" : "finished";
      document.getElementById("installing").setAttribute("next", nextPage);
      document.documentElement.advance();
      return;
    }

    let install = this._installs[this._currentInstall];

    if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) {
      logger.debug("Don't update " + install.existingAddon.id + " in background");
      gUpdateWizard.upgradeDeclined++;
      install.cancel();
      this.startNextInstall();
      return;
    }
    install.addListener(this);
    install.install();
  },

  // InstallListener
  onDownloadStarted(aInstall) {
    if (gUpdateWizard.shuttingDown) {
      return;
    }
    var strings = document.getElementById("updateStrings");
    var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]);
    var actionItem = document.getElementById("actionItem");
    actionItem.value = label;
  },

  onDownloadProgress(aInstall) {
    if (gUpdateWizard.shuttingDown) {
      return;
    }
    var downloadProgress = document.getElementById("downloadProgress");
    downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress);
  },

  onDownloadEnded(aInstall) {
  },

  onDownloadFailed(aInstall) {
    this._errors.push(aInstall);

    gUpdateWizard.upgradeFailed++;
    this.startNextInstall();
  },

  onInstallStarted(aInstall) {
    if (gUpdateWizard.shuttingDown) {
      return;
    }
    var strings = document.getElementById("updateStrings");
    var label = strings.getFormattedString("installingPrefix", [aInstall.name]);
    var actionItem = document.getElementById("actionItem");
    actionItem.value = label;
  },

  onInstallEnded(aInstall, aAddon) {
    if (!gUpdateWizard.shuttingDown) {
      // Remember that this add-on was updated during startup
      AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
                                           aAddon.id);
    }

    gUpdateWizard.upgraded++;
    this.startNextInstall();
  },

  onInstallFailed(aInstall) {
    this._errors.push(aInstall);

    gUpdateWizard.upgradeFailed++;
    this.startNextInstall();
  }
};

var gInstallErrorsPage = {
  onPageShow() {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    document.documentElement.getButton("finish").focus();
  },
};

// Displayed when there are incompatible add-ons and the xpinstall.enabled
// pref is false and locked.
var gAdminDisabledPage = {
  onPageShow() {
    gUpdateWizard.setButtonLabels(null, true, null, true,
                                  "cancelButtonText", true);
    document.documentElement.getButton("finish").focus();
  }
};

// Displayed when selected add-on updates have been installed without error.
// There can still be add-ons that are not compatible and don't have an update.
var gFinishedPage = {
  onPageShow() {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    document.documentElement.getButton("finish").focus();

    if (gUpdateWizard.shouldSuggestAutoChecking) {
      document.getElementById("finishedCheckDisabled").hidden = false;
      gUpdateWizard.shouldAutoCheck = true;
    } else
      document.getElementById("finishedCheckEnabled").hidden = false;

    document.documentElement.getButton("finish").focus();
  }
};

// Displayed when there are incompatible add-ons and there are no available
// updates.
var gNoUpdatesPage = {
  onPageShow(aEvent) {
    gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
    if (gUpdateWizard.shouldSuggestAutoChecking) {
      document.getElementById("noupdatesCheckDisabled").hidden = false;
      gUpdateWizard.shouldAutoCheck = true;
    } else
      document.getElementById("noupdatesCheckEnabled").hidden = false;

    gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
    document.documentElement.getButton("finish").focus();
  }
};
PK
!<'884chrome/toolkit/content/mozapps/extensions/update.xul<?xml version="1.0"?>


<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 
<?xml-stylesheet href="chrome://mozapps/skin/extensions/update.css" type="text/css"?> 

<!DOCTYPE wizard [
<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/update.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%updateDTD;
%brandDTD;
]>

<wizard id="updateWizard"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&updateWizard.title;"
        windowtype="Addons:Compatibility"
        branded="true"
        onload="gUpdateWizard.init();"
        onwizardfinish="gUpdateWizard.onWizardFinish();"
        onwizardcancel="return gUpdateWizard.onWizardCancel();"
        onclose="return gUpdateWizard.onWizardClose(event);"
        buttons="accept,cancel">

  <script type="application/javascript" src="chrome://mozapps/content/extensions/update.js"/>
  
  <stringbundleset id="updateSet">
    <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
    <stringbundle id="updateStrings" src="chrome://mozapps/locale/extensions/update.properties"/>
  </stringbundleset>
  
  <wizardpage id="dummy" pageid="dummy"/>
  
  <wizardpage id="offline" pageid="offline" next="versioninfo"
              label="&offline.title;"
              onpageadvanced="return gOfflinePage.onPageAdvanced();">
    <description>&offline.description;</description>
    <checkbox id="toggleOffline"
              checked="true"
              label="&offline.toggleOffline.label;"
              accesskey="&offline.toggleOffline.accesskey;"
              oncommand="gOfflinePage.toggleOffline();"/>
  </wizardpage>
  
  <wizardpage id="versioninfo" pageid="versioninfo" next="mismatch"
              label="&versioninfo.wizard.title;"
              onpageshow="gVersionInfoPage.onPageShow();">
    <label>&versioninfo.top.label;</label>
    <separator class="thin"/>
    <progressmeter id="versioninfo.progress" mode="undetermined"/>
    <hbox align="center">
      <image id="versioninfo.throbber" class="throbber"/>
      <label flex="1" id="versioninfo.status" crop="right">&versioninfo.waiting;</label>
    </hbox>
    <separator/>
  </wizardpage>

  <wizardpage id="mismatch" pageid="mismatch" next="checking"
              label="&mismatch.win.title;"
              onpageshow="gMismatchPage.onPageShow();">
    <label>&mismatch.top.label;</label>
    <separator class="thin"/>
    <listbox id="mismatch.incompatible" flex="1"/>
    <separator class="thin"/>
    <label>&mismatch.bottom.label;</label>
  </wizardpage>
  
  <wizardpage id="checking" pageid="checking" next="noupdates"
              label="&checking.wizard.title;"
              onpageshow="gUpdatePage.onPageShow();">
    <label>&checking.top.label;</label>
    <separator class="thin"/>
    <progressmeter id="checking.progress"/>
    <hbox align="center">
      <image id="checking.throbber" class="throbber"/>
      <label id="checking.status" flex="1" crop="right">&checking.status;</label>
    </hbox>
  </wizardpage>
    
  <wizardpage id="noupdates" pageid="noupdates"
              label="&noupdates.wizard.title;"
              onpageshow="gNoUpdatesPage.onPageShow();">
    <description>&noupdates.intro.desc;</description>
    <separator class="thin"/>
    <hbox id="updateCheckErrorNotFound" class="alertBox" hidden="true" align="top">
      <description flex="1">&noupdates.error.desc;</description>
    </hbox>
    <separator class="thin"/>
    <description id="noupdatesCheckEnabled" hidden="true">
      &noupdates.checkEnabled.desc;
    </description>
    <vbox id="noupdatesCheckDisabled" hidden="true">
      <description>&finished.checkDisabled.desc;</description>
      <checkbox label="&enableChecking.label;" checked="true"
                oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
    </vbox>
    <separator flex="1"/>
    <label>&clickFinish.label;</label>
    <separator class="thin"/>
  </wizardpage>

  <wizardpage id="found" pageid="found" next="installing"
              label="&found.wizard.title;"
              onpageshow="gFoundPage.onPageShow();">
    <label>&found.top.label;</label>
    <separator class="thin"/>
    <listbox id="found.updates" flex="1" seltype="multiple"
             onclick="gFoundPage.updateNextButton();"/>
    <separator class="thin"/>
    <vbox align="left" id="xpinstallDisabledAlert" hidden="true">
      <description>&found.disabledXPinstall.label;</description>
      <checkbox label="&found.enableXPInstall.label;"
                id="enableXPInstall"
                accesskey="&found.enableXPInstall.accesskey;"
                oncommand="gFoundPage.toggleXPInstallEnable(event);"/>
    </vbox>
  </wizardpage>

  <wizardpage id="installing" pageid="installing" next="finished"
              label="&installing.wizard.title;"
              onpageshow="gInstallingPage.onPageShow();">
    <label>&installing.top.label;</label>
    <progressmeter id="downloadProgress"/>
    <hbox align="center">
      <image id="installing.throbber" class="throbber"/>
      <label id="actionItem" flex="1" crop="right"/>
    </hbox>
    <separator/>
  </wizardpage>
  
  <wizardpage id="installerrors" pageid="installerrors"
              label="&installerrors.wizard.title;"
              onpageshow="gInstallErrorsPage.onPageShow();">
    <hbox align="top" class="alertBox">
      <description flex="1">&installerrors.intro.label;</description>
    </hbox>
    <separator flex="1"/>
    <label>&clickFinish.label;</label>
    <separator class="thin"/>
  </wizardpage>
  
  <wizardpage id="adminDisabled" pageid="adminDisabled"
              label="&adminDisabled.wizard.title;"
              onpageshow="gAdminDisabledPage.onPageShow();">
    <separator/>
    <hbox class="alertBox" align="top">
      <description flex="1">&adminDisabled.warning.label;</description>
    </hbox>
    <separator flex="1"/>
    <label>&clickFinish.label;</label>
    <separator class="thin"/>
  </wizardpage>

  <wizardpage id="finished" pageid="finished"
              label="&finished.wizard.title;"
              onpageshow="gFinishedPage.onPageShow();">

    <label>&finished.top.label;</label>
    <separator/>
    <description id="finishedCheckEnabled" hidden="true">
      &finished.checkEnabled.desc;
    </description>
    <vbox id="finishedCheckDisabled" hidden="true">
      <description>&finished.checkDisabled.desc;</description>
      <checkbox label="&enableChecking.label;" checked="true"
                oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
    </vbox>
    <separator flex="1"/>
    <label>&clickFinish.label;</label>
    <separator class="thin"/>
  </wizardpage>
  
</wizard>

PK
!<rBCC8chrome/toolkit/content/mozapps/extensions/updateinfo.xsl<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<xsl:stylesheet version="1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml"
                              xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- Any elements not otherwise specified will be stripped but the contents
       will be displayed. All attributes are stripped from copied elements. -->

  <!-- Block these elements and their contents -->
  <xsl:template match="xhtml:head|xhtml:script|xhtml:style">
  </xsl:template>

  <!-- Allowable styling elements -->
  <xsl:template match="xhtml:b|xhtml:i|xhtml:em|xhtml:strong|xhtml:u|xhtml:q|xhtml:sub|xhtml:sup|xhtml:code">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <!-- Allowable block formatting elements -->
  <xsl:template match="xhtml:h1|xhtml:h2|xhtml:h3|xhtml:p|xhtml:div|xhtml:blockquote|xhtml:pre">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <!-- Allowable list formatting elements -->
  <xsl:template match="xhtml:ul|xhtml:ol|xhtml:li|xhtml:dl|xhtml:dt|xhtml:dd">
    <xsl:copy><xsl:apply-templates/></xsl:copy>
  </xsl:template>

  <!-- These elements are copied and their contents dropped -->
  <xsl:template match="xhtml:br|xhtml:hr">
    <xsl:copy/>
  </xsl:template>

  <!-- The root document -->
  <xsl:template match="/">
    <xhtml:body><xsl:apply-templates/></xhtml:body>
  </xsl:template>
  
</xsl:stylesheet>
PK
!<:`%`%1chrome/toolkit/content/mozapps/handling/dialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This dialog builds its content based on arguments passed into it.
 * window.arguments[0]:
 *   The title of the dialog.
 * window.arguments[1]:
 *   The url of the image that appears to the left of the description text
 * window.arguments[2]:
 *   The text of the description that will appear above the choices the user
 *   can choose from.
 * window.arguments[3]:
 *   The text of the label directly above the choices the user can choose from.
 * window.arguments[4]:
 *   This is the text to be placed in the label for the checkbox.  If no text is
 *   passed (ie, it's an empty string), the checkbox will be hidden.
 * window.arguments[5]:
 *   The accesskey for the checkbox
 * window.arguments[6]:
 *   This is the text that is displayed below the checkbox when it is checked.
 * window.arguments[7]:
 *   This is the nsIHandlerInfo that gives us all our precious information.
 * window.arguments[8]:
 *   This is the nsIURI that we are being brought up for in the first place.
 * window.arguments[9]:
 *   The nsIInterfaceRequestor of the parent window; may be null
 */

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource://gre/modules/SharedPromptUtils.jsm");


var dialog = {
  // Member Variables

  _handlerInfo: null,
  _URI: null,
  _itemChoose: null,
  _okButton: null,
  _windowCtxt: null,
  _buttonDisabled: true,

  // Methods

 /**
  * This function initializes the content of the dialog.
  */
  initialize: function initialize() {
    this._handlerInfo = window.arguments[7].QueryInterface(Ci.nsIHandlerInfo);
    this._URI         = window.arguments[8].QueryInterface(Ci.nsIURI);
    this._windowCtxt  = window.arguments[9];
    if (this._windowCtxt)
      this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor);
    this._itemChoose  = document.getElementById("item-choose");
    this._okButton    = document.documentElement.getButton("accept");

    var description = {
      image: document.getElementById("description-image"),
      text:  document.getElementById("description-text")
    };
    var options = document.getElementById("item-action-text");
    var checkbox = {
      desc: document.getElementById("remember"),
      text:  document.getElementById("remember-text")
    };

    // Setting values
    document.title               = window.arguments[0];
    description.image.src        = window.arguments[1];
    description.text.textContent = window.arguments[2];
    options.value                = window.arguments[3];
    checkbox.desc.label          = window.arguments[4];
    checkbox.desc.accessKey      = window.arguments[5];
    checkbox.text.textContent    = window.arguments[6];

    // Hide stuff that needs to be hidden
    if (!checkbox.desc.label)
      checkbox.desc.hidden = true;

    // UI is ready, lets populate our list
    this.populateList();

    this._delayHelper = new EnableDelayHelper({
      disableDialog: () => {
        this._buttonDisabled = true;
        this.updateOKButton();
      },
      enableDialog: () => {
        this._buttonDisabled = false;
        this.updateOKButton();
      },
      focusTarget: window
    });
  },

 /**
  * Populates the list that a user can choose from.
  */
  populateList: function populateList() {
    var items = document.getElementById("items");
    var possibleHandlers = this._handlerInfo.possibleApplicationHandlers;
    var preferredHandler = this._handlerInfo.preferredApplicationHandler;
    var ios = Cc["@mozilla.org/network/io-service;1"].
              getService(Ci.nsIIOService);
    for (let i = possibleHandlers.length - 1; i >= 0; --i) {
      let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
      let elm = document.createElement("richlistitem");
      elm.setAttribute("type", "handler");
      elm.setAttribute("name", app.name);
      elm.obj = app;

      if (app instanceof Ci.nsILocalHandlerApp) {
        // See if we have an nsILocalHandlerApp and set the icon
        let uri = ios.newFileURI(app.executable);
        elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
      } else if (app instanceof Ci.nsIWebHandlerApp) {
        let uri = ios.newURI(app.uriTemplate);
        if (/^https?/.test(uri.scheme)) {
          // Unfortunately we can't use the favicon service to get the favicon,
          // because the service looks for a record with the exact URL we give
          // it, and users won't have such records for URLs they don't visit,
          // and users won't visit the handler's URL template, they'll only
          // visit URLs derived from that template (i.e. with %s in the template
          // replaced by the URL of the content being handled).
          elm.setAttribute("image", uri.prePath + "/favicon.ico");
        }
        elm.setAttribute("description", uri.prePath);
      } else if (app instanceof Ci.nsIDBusHandlerApp) {
        elm.setAttribute("description", app.method);
      } else
        throw "unknown handler type";

      items.insertBefore(elm, this._itemChoose);
      if (preferredHandler && app == preferredHandler)
        this.selectedItem = elm;
    }

    if (this._handlerInfo.hasDefaultHandler) {
      let elm = document.createElement("richlistitem");
      elm.setAttribute("type", "handler");
      elm.id = "os-default-handler";
      elm.setAttribute("name", this._handlerInfo.defaultDescription);

      items.insertBefore(elm, items.firstChild);
      if (this._handlerInfo.preferredAction ==
          Ci.nsIHandlerInfo.useSystemDefault)
          this.selectedItem = elm;
    }
    items.ensureSelectedElementIsVisible();
  },

 /**
  * Brings up a filepicker and allows a user to choose an application.
  */
  chooseApplication: function chooseApplication() {
    var bundle = document.getElementById("base-strings");
    var title = bundle.getString("choose.application.title");

    var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    fp.init(window, title, Ci.nsIFilePicker.modeOpen);
    fp.appendFilters(Ci.nsIFilePicker.filterApps);

    fp.open(rv => {
      if (rv == Ci.nsIFilePicker.returnOK && fp.file) {
        let uri = Cc["@mozilla.org/network/util;1"].
                  getService(Ci.nsIIOService).
                  newFileURI(fp.file);

        let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
                         createInstance(Ci.nsILocalHandlerApp);
        handlerApp.executable = fp.file;

        // if this application is already in the list, select it and don't add it again
        let parent = document.getElementById("items");
        for (let i = 0; i < parent.childNodes.length; ++i) {
          let elm = parent.childNodes[i];
          if (elm.obj instanceof Ci.nsILocalHandlerApp && elm.obj.equals(handlerApp)) {
            parent.selectedItem = elm;
            parent.ensureSelectedElementIsVisible();
            return;
          }
        }

        let elm = document.createElement("richlistitem");
        elm.setAttribute("type", "handler");
        elm.setAttribute("name", fp.file.leafName);
        elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
        elm.obj = handlerApp;

        parent.selectedItem = parent.insertBefore(elm, parent.firstChild);
        parent.ensureSelectedElementIsVisible();
      }
    });
  },

 /**
  * Function called when the OK button is pressed.
  */
  onAccept: function onAccept() {
    var checkbox = document.getElementById("remember");
    if (!checkbox.hidden) {
      // We need to make sure that the default is properly set now
      if (this.selectedItem.obj) {
        // default OS handler doesn't have this property
        this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
        this._handlerInfo.preferredApplicationHandler = this.selectedItem.obj;
      } else
        this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
    }
    this._handlerInfo.alwaysAskBeforeHandling = !checkbox.checked;

    var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
             getService(Ci.nsIHandlerService);
    hs.store(this._handlerInfo);

    this._handlerInfo.launchWithURI(this._URI, this._windowCtxt);

    return true;
  },

 /**
  * Determines if the OK button should be disabled or not
  */
  updateOKButton: function updateOKButton() {
    this._okButton.disabled = this._itemChoose.selected ||
                              this._buttonDisabled;
  },

 /**
  * Updates the UI based on the checkbox being checked or not.
  */
  onCheck: function onCheck() {
    if (document.getElementById("remember").checked)
      document.getElementById("remember-text").setAttribute("visible", "true");
    else
      document.getElementById("remember-text").removeAttribute("visible");
  },

  /**
   * Function called when the user double clicks on an item of the list
   */
  onDblClick: function onDblClick() {
    if (this.selectedItem == this._itemChoose)
      this.chooseApplication();
    else
      document.documentElement.acceptDialog();
  },

  // Getters / Setters

 /**
  * Returns/sets the selected element in the richlistbox
  */
  get selectedItem() {
    return document.getElementById("items").selectedItem;
  },
  set selectedItem(aItem) {
    return document.getElementById("items").selectedItem = aItem;
  }

};
PK
!<x7992chrome/toolkit/content/mozapps/handling/dialog.xul<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://mozapps/content/handling/handler.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/handling/handling.css"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/handling/handling.dtd">

<dialog id="handling"
        ondialogaccept="return dialog.onAccept();"
        onload="dialog.initialize();"
        style="min-width: &window.emWidth;; min-height: &window.emHeight;;"
        persist="width height screenX screenY"
        aria-describedby="description-text"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <script src="chrome://mozapps/content/handling/dialog.js" type="application/javascript"/>

  <stringbundleset id="strings">
    <stringbundle id="base-strings"
                  src="chrome://mozapps/locale/handling/handling.properties"/>
  </stringbundleset>

  <hbox>
    <image id="description-image"/>
    <description id="description-text"/>
  </hbox>

  <vbox flex="1">
    <label id="item-action-text" control="items"/>
    <richlistbox id="items" flex="1"
                 ondblclick="dialog.onDblClick();"
                 onselect="dialog.updateOKButton();">
      <richlistitem id="item-choose" orient="horizontal" selected="true">
        <label value="&ChooseOtherApp.description;" flex="1"/>
        <button oncommand="dialog.chooseApplication();"
                label="&ChooseApp.label;" accesskey="&ChooseApp.accessKey;"/>
      </richlistitem>
    </richlistbox>
  </vbox>

  <checkbox id="remember" aria-describedby="remember-text" oncommand="dialog.onCheck();"/>
  <description id="remember-text"/>

  <hbox class="dialog-button-box" pack="end">
    <button dlgtype="cancel" icon="cancel" class="dialog-button"/>
    <button dlgtype="accept" label="&accept;" icon="open" class="dialog-button"/>
  </hbox>

</dialog>
PK
!<`evv3chrome/toolkit/content/mozapps/handling/handler.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

richlistitem[type="handler"] {
  -moz-binding: url('chrome://mozapps/content/handling/handler.xml#handler');
}

#remember-text:not([visible]) {
  visibility: hidden;
}
PK
!<ώ((3chrome/toolkit/content/mozapps/handling/handler.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="hanlder-bindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="handler"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">

    <content>
      <xul:vbox pack="center">
        <xul:image xbl:inherits="src=image" height="32" width="32"/>
      </xul:vbox>
      <xul:vbox flex="1">
        <xul:label class="name" xbl:inherits="value=name"/>
        <xul:label class="description" xbl:inherits="value=description"/>
      </xul:vbox>
    </content>
    <implementation>
      <property name="label" onget="return this.getAttribute('name') + ' ' + this.getAttribute('description');"/>
    </implementation>
  </binding>

</bindings>
PK
!<VE6chrome/toolkit/content/mozapps/preferences/changemp.js// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB;
const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB;
const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot;
const nsIPK11Token = Components.interfaces.nsIPK11Token;


var params;
var pw1;

function init() {
  pw1 = document.getElementById("pw1");

  process();
}


function process() {
  let bundle = document.getElementById("bundlePreferences");

  // If the token is unitialized, don't use the old password box.
  // Otherwise, do.

  let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                  .getService(Ci.nsIPK11TokenDB);
  let token = tokenDB.getInternalKeyToken();
  if (token) {
    let oldpwbox = document.getElementById("oldpw");
    let msgBox = document.getElementById("message");
    if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
      oldpwbox.setAttribute("hidden", "true");
      msgBox.setAttribute("value", bundle.getString("password_not_set"));
      msgBox.setAttribute("hidden", "false");

      if (!token.needsLogin()) {
        oldpwbox.setAttribute("inited", "empty");
      } else {
        oldpwbox.setAttribute("inited", "true");
      }

      // Select first password field
      document.getElementById("pw1").focus();
    } else {
      // Select old password field
      oldpwbox.setAttribute("hidden", "false");
      msgBox.setAttribute("hidden", "true");
      oldpwbox.setAttribute("inited", "false");
      oldpwbox.focus();
    }
  }

  if (params) {
    // Return value 0 means "canceled"
    params.SetInt(1, 0);
  }

  checkPasswords();
}

function setPassword() {
  var pk11db = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
  var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                .getService(Components.interfaces.nsIPromptService);
  var token = pk11db.getInternalKeyToken();

  var oldpwbox = document.getElementById("oldpw");
  var initpw = oldpwbox.getAttribute("inited");
  var bundle = document.getElementById("bundlePreferences");

  var success = false;

  if (initpw == "false" || initpw == "empty") {
    try {
      var oldpw = "";
      var passok = 0;

      if (initpw == "empty") {
        passok = 1;
      } else {
        oldpw = oldpwbox.value;
        passok = token.checkPassword(oldpw);
      }

      if (passok) {
        if (initpw == "empty" && pw1.value == "") {
          // This makes no sense that we arrive here,
          // we reached a case that should have been prevented by checkPasswords.
        } else {
          if (pw1.value == "") {
            var secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
            if (secmoddb.isFIPSEnabled) {
              // empty passwords are not allowed in FIPS mode
              promptService.alert(window,
                                  bundle.getString("pw_change_failed_title"),
                                  bundle.getString("pw_change2empty_in_fips_mode"));
              passok = 0;
            }
          }
          if (passok) {
            token.changePassword(oldpw, pw1.value);
            if (pw1.value == "") {
              promptService.alert(window,
                                  bundle.getString("pw_change_success_title"),
                                  bundle.getString("pw_erased_ok")
                                  + " " + bundle.getString("pw_empty_warning"));
            } else {
              promptService.alert(window,
                                  bundle.getString("pw_change_success_title"),
                                  bundle.getString("pw_change_ok"));
            }
            success = true;
          }
        }
      } else {
        oldpwbox.focus();
        oldpwbox.setAttribute("value", "");
        promptService.alert(window,
                            bundle.getString("pw_change_failed_title"),
                            bundle.getString("incorrect_pw"));
      }
    } catch (e) {
      promptService.alert(window,
                          bundle.getString("pw_change_failed_title"),
                          bundle.getString("failed_pw_change"));
    }
  } else {
    token.initPassword(pw1.value);
    if (pw1.value == "") {
      promptService.alert(window,
                          bundle.getString("pw_change_success_title"),
                          bundle.getString("pw_not_wanted")
                          + " " + bundle.getString("pw_empty_warning"));
    }
    success = true;
  }

  // Terminate dialog
  if (success)
    window.close();
}

function setPasswordStrength() {
// Here is how we weigh the quality of the password
// number of characters
// numbers
// non-alpha-numeric chars
// upper and lower case characters

  var pw = document.getElementById("pw1").value;

// length of the password
  var pwlength = (pw.length);
  if (pwlength > 5)
    pwlength = 5;


// use of numbers in the password
  var numnumeric = pw.replace(/[0-9]/g, "");
  var numeric = (pw.length - numnumeric.length);
  if (numeric > 3)
    numeric = 3;

// use of symbols in the password
  var symbols = pw.replace(/\W/g, "");
  var numsymbols = (pw.length - symbols.length);
  if (numsymbols > 3)
    numsymbols = 3;

// use of uppercase in the password
  var numupper = pw.replace(/[A-Z]/g, "");
  var upper = (pw.length - numupper.length);
  if (upper > 3)
    upper = 3;


  var pwstrength = ((pwlength * 10) - 20) + (numeric * 10) + (numsymbols * 15) + (upper * 10);

  // make sure we're give a value between 0 and 100
  if ( pwstrength < 0 ) {
    pwstrength = 0;
  }

  if ( pwstrength > 100 ) {
    pwstrength = 100;
  }

  var mymeter = document.getElementById("pwmeter");
  mymeter.value = pwstrength;
}

function checkPasswords() {
  var pw1 = document.getElementById("pw1").value;
  var pw2 = document.getElementById("pw2").value;
  var ok = document.documentElement.getButton("accept");

  var oldpwbox = document.getElementById("oldpw");
  if (oldpwbox) {
    var initpw = oldpwbox.getAttribute("inited");

    if (initpw == "empty" && pw1 == "") {
      // The token has already been initialized, therefore this dialog
      // was called with the intention to change the password.
      // The token currently uses an empty password.
      // We will not allow changing the password from empty to empty.
      ok.setAttribute("disabled", "true");
      return;
    }
  }

  if (pw1 == pw2) {
    ok.setAttribute("disabled", "false");
  } else {
    ok.setAttribute("disabled", "true");
  }

}
PK
!<k=		7chrome/toolkit/content/mozapps/preferences/changemp.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
<!ENTITY % changempDTD SYSTEM "chrome://mozapps/locale/preferences/changemp.dtd" >
%brandDTD;
%changempDTD;
]>

<dialog id="changemp" title="&setPassword.title;"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        style="width: 40em;" 
        ondialogaccept="setPassword();"
        onload="init()">

  <script type="application/javascript" src="chrome://mozapps/content/preferences/changemp.js"/>

  <stringbundle id="bundlePreferences" src="chrome://mozapps/locale/preferences/preferences.properties"/>

  <description control="pw1">&masterPasswordDescription.label;</description>

  <groupbox>
    <grid>
      <columns>
        <column flex="1"/>
        <column/>
      </columns>
      <rows>
        <row>
          <label control="oldpw">&setPassword.oldPassword.label;</label>
          <textbox id="oldpw" type="password"/>
          <!-- This textbox is inserted as a workaround to the fact that making the 'type'
                & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
                'false'] and ['text' & 'true'] - as would be necessary if the menu has more
                than one tokens, some initialized and some not - does not work properly. So,
                either the textbox 'oldpw' or the textbox 'message' would be displayed,
                depending on the state of the token selected
          -->
          <textbox id="message" disabled="true" />
        </row>
        <row>
          <label control="pw1">&setPassword.newPassword.label;</label>
          <textbox id="pw1" type="password"
                   oninput="setPasswordStrength(); checkPasswords();"/>
        </row>
        <row>
          <label control="pw2">&setPassword.reenterPassword.label;</label>
          <textbox id="pw2" type="password" oninput="checkPasswords();"/>
        </row>
      </rows>
    </grid>
  </groupbox>

  <groupbox>
    <caption label="&setPassword.meter.label;"/>
    <progressmeter id="pwmeter" mode="determined" value="0"/>
  </groupbox>

  <description control="pw2" class="header">&masterPasswordWarning.label;</description>

</dialog>
PK
!<09chrome/toolkit/content/mozapps/preferences/fontbuilder.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/. */

var FontBuilder = {
  _enumerator: null,
  get enumerator() {
    if (!this._enumerator) {
      this._enumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
                                   .createInstance(Components.interfaces.nsIFontEnumerator);
    }
    return this._enumerator;
  },

  _allFonts: null,
  _langGroupSupported: false,
  buildFontList(aLanguage, aFontType, aMenuList) {
    // Reset the list
    while (aMenuList.hasChildNodes())
      aMenuList.firstChild.remove();

    var defaultFont = null;
    // Load Font Lists
    var fonts = this.enumerator.EnumerateFonts(aLanguage, aFontType, { } );
    if (fonts.length > 0)
      defaultFont = this.enumerator.getDefaultFont(aLanguage, aFontType);
    else {
      fonts = this.enumerator.EnumerateFonts(aLanguage, "", { });
      if (fonts.length > 0)
        defaultFont = this.enumerator.getDefaultFont(aLanguage, "");
    }

    if (!this._allFonts)
      this._allFonts = this.enumerator.EnumerateAllFonts({});

    // Build the UI for the Default Font and Fonts for this CSS type.
    var popup = document.createElement("menupopup");
    var separator;
    if (fonts.length > 0) {
      if (defaultFont) {
        var bundlePreferences = document.getElementById("bundlePreferences");
        var label = bundlePreferences.getFormattedString("labelDefaultFont", [defaultFont]);
        var menuitem = document.createElement("menuitem");
        menuitem.setAttribute("label", label);
        menuitem.setAttribute("value", ""); // Default Font has a blank value
        popup.appendChild(menuitem);

        separator = document.createElement("menuseparator");
        popup.appendChild(separator);
      }

      for (var i = 0; i < fonts.length; ++i) {
        menuitem = document.createElement("menuitem");
        menuitem.setAttribute("value", fonts[i]);
        menuitem.setAttribute("label", fonts[i]);
        popup.appendChild(menuitem);
      }
    }

    // Build the UI for the remaining fonts.
    if (this._allFonts.length > fonts.length) {
      this._langGroupSupported = true;
      // Both lists are sorted, and the Fonts-By-Type list is a subset of the
      // All-Fonts list, so walk both lists side-by-side, skipping values we've
      // already created menu items for.
      var builtItem = separator ? separator.nextSibling : popup.firstChild;
      var builtItemValue = builtItem ? builtItem.getAttribute("value") : null;

      separator = document.createElement("menuseparator");
      popup.appendChild(separator);

      for (i = 0; i < this._allFonts.length; ++i) {
        if (this._allFonts[i] != builtItemValue) {
          menuitem = document.createElement("menuitem");
          menuitem.setAttribute("value", this._allFonts[i]);
          menuitem.setAttribute("label", this._allFonts[i]);
          popup.appendChild(menuitem);
        } else {
          builtItem = builtItem.nextSibling;
          builtItemValue = builtItem ? builtItem.getAttribute("value") : null;
        }
      }
    }
    aMenuList.appendChild(popup);
  },

  readFontSelection(aElement) {
    // Determine the appropriate value to select, for the following cases:
    // - there is no setting
    // - the font selected by the user is no longer present (e.g. deleted from
    //   fonts folder)
    let preference = document.getElementById(aElement.getAttribute("preference"));
    if (preference.value) {
      let fontItems = aElement.getElementsByAttribute("value", preference.value);

      // There is a setting that actually is in the list. Respect it.
      if (fontItems.length)
        return undefined;
    }

    // Otherwise, use "default" font of current system which is computed
    // with "font.name-list.*".  If "font.name.*" is empty string, it means
    // "default".  So, return empty string in this case.
    return "";
  }
};
PK
!<Š$_6chrome/toolkit/content/mozapps/preferences/removemp.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/. */

var gRemovePasswordDialog = {
  _token: null,
  _bundle: null,
  _prompt: null,
  _okButton: null,
  _password: null,
  init() {
    this._prompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                             .getService(Components.interfaces.nsIPromptService);
    this._bundle = document.getElementById("bundlePreferences");

    this._okButton = document.documentElement.getButton("accept");
    this._okButton.label = this._bundle.getString("pw_remove_button");

    this._password = document.getElementById("password");

    var pk11db = Components.classes["@mozilla.org/security/pk11tokendb;1"]
                           .getService(Components.interfaces.nsIPK11TokenDB);
    this._token = pk11db.getInternalKeyToken();

    // Initialize the enabled state of the Remove button by checking the
    // initial value of the password ("" should be incorrect).
    this.validateInput();
  },

  validateInput() {
    this._okButton.disabled = !this._token.checkPassword(this._password.value);
  },

  removePassword() {
    if (this._token.checkPassword(this._password.value)) {
      this._token.changePassword(this._password.value, "");
      this._prompt.alert(window,
                         this._bundle.getString("pw_change_success_title"),
                         this._bundle.getString("pw_erased_ok")
                         + " " + this._bundle.getString("pw_empty_warning"));
    } else {
      this._password.value = "";
      this._password.focus();
      this._prompt.alert(window,
                         this._bundle.getString("pw_change_failed_title"),
                         this._bundle.getString("incorrect_pw"));
    }
  },
};

PK
!<wK7chrome/toolkit/content/mozapps/preferences/removemp.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
<!ENTITY % removempDTD SYSTEM "chrome://mozapps/locale/preferences/removemp.dtd" >
%brandDTD;
%removempDTD;
]>

<dialog id="removemp" title="&removePassword.title;"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
        style="width: 35em;" 
        ondialogaccept="gRemovePasswordDialog.removePassword();" 
        onload="gRemovePasswordDialog.init()">

  <script type="application/javascript" src="chrome://mozapps/content/preferences/removemp.js"/>

  <stringbundle id="bundlePreferences" src="chrome://mozapps/locale/preferences/preferences.properties"/>

  <vbox id="warnings">
    <description>&removeWarning1.label;</description>
    <description class="header">&removeWarning2.label;</description>
  </vbox>
  
  <separator class="thin"/>
    
  <groupbox>
    <caption label="&removeInfo.label;"/>

    <hbox align="center">
      <label control="password" value="&setPassword.oldPassword.label;"/> 
      <textbox id="password" type="password"
               oninput="gRemovePasswordDialog.validateInput();"
               aria-describedby="warnings"/>
    </hbox>
  </groupbox>
  
  <separator/>

</dialog>
PK
!<_=chrome/toolkit/content/mozapps/profile/createProfileWizard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const C = Components.classes;
const I = Components.interfaces;

Components.utils.import("resource://gre/modules/AppConstants.jsm");

const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";

var gProfileService;
var gProfileManagerBundle;

var gDefaultProfileParent;

// The directory where the profile will be created.
var gProfileRoot;

// Text node to display the location and name of the profile to create.
var gProfileDisplay;

// Called once when the wizard is opened.
function initWizard() {
  try {
    gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService);
    gProfileManagerBundle = document.getElementById("bundle_profileManager");

    var dirService = C["@mozilla.org/file/directory_service;1"].getService(I.nsIProperties);
    gDefaultProfileParent = dirService.get("DefProfRt", I.nsIFile);

    // Initialize the profile location display.
    gProfileDisplay = document.getElementById("profileDisplay").firstChild;
    setDisplayToDefaultFolder();
  } catch (e) {
    window.close();
    throw (e);
  }
}

// Called every time the second wizard page is displayed.
function initSecondWizardPage() {
  var profileName = document.getElementById("profileName");
  profileName.select();
  profileName.focus();

  // Initialize profile name validation.
  checkCurrentInput(profileName.value);
}

const kSaltTable = [
  "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
  "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
  "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" ];

var kSaltString = "";
for (var i = 0; i < 8; ++i) {
  kSaltString += kSaltTable[Math.floor(Math.random() * kSaltTable.length)];
}


function saltName(aName) {
  return kSaltString + "." + aName;
}

function setDisplayToDefaultFolder() {
  var defaultProfileDir = gDefaultProfileParent.clone();
  defaultProfileDir.append(saltName(document.getElementById("profileName").value));
  gProfileRoot = defaultProfileDir;
  document.getElementById("useDefault").disabled = true;
}

function updateProfileDisplay() {
  gProfileDisplay.data = gProfileRoot.path;
}

// Invoke a folder selection dialog for choosing the directory of profile storage.
function chooseProfileFolder() {
  var newProfileRoot;

  var dirChooser = C["@mozilla.org/filepicker;1"].createInstance(I.nsIFilePicker);
  dirChooser.init(window, gProfileManagerBundle.getString("chooseFolder"),
                  I.nsIFilePicker.modeGetFolder);
  dirChooser.appendFilters(I.nsIFilePicker.filterAll);

  // default to the Profiles folder
  dirChooser.displayDirectory = gDefaultProfileParent;

  dirChooser.show();
  newProfileRoot = dirChooser.file;

  // Disable the "Default Folder..." button when the default profile folder
  // was selected manually in the File Picker.
  document.getElementById("useDefault").disabled =
    (newProfileRoot.parent.equals(gDefaultProfileParent));

  gProfileRoot = newProfileRoot;
  updateProfileDisplay();
}

// Checks the current user input for validity and triggers an error message accordingly.
function checkCurrentInput(currentInput) {
  var finishButton = document.documentElement.getButton("finish");
  var finishText = document.getElementById("finishText");
  var canAdvance;

  var errorMessage = checkProfileName(currentInput);

  if (!errorMessage) {
    finishText.className = "";
    if (AppConstants.platform == "macosx") {
      finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishTextMac");
    } else {
      finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishText");
    }
    canAdvance = true;
  } else {
    finishText.className = "error";
    finishText.firstChild.data = errorMessage;
    canAdvance = false;
  }

  document.documentElement.canAdvance = canAdvance;
  finishButton.disabled = !canAdvance;

  updateProfileDisplay();

  return canAdvance;
}

function updateProfileName(aNewName) {
  if (checkCurrentInput(aNewName)) {
    gProfileRoot.leafName = saltName(aNewName);
    updateProfileDisplay();
  }
}

// Checks whether the given string is a valid profile name.
// Returns an error message describing the error in the name or "" when it's valid.
function checkProfileName(profileNameToCheck) {
  // Check for emtpy profile name.
  if (!/\S/.test(profileNameToCheck))
    return gProfileManagerBundle.getString("profileNameEmpty");

  // Check whether all characters in the profile name are allowed.
  if (/([\\*:?<>|\/\"])/.test(profileNameToCheck))
    return gProfileManagerBundle.getFormattedString("invalidChar", [RegExp.$1]);

  // Check whether a profile with the same name already exists.
  if (profileExists(profileNameToCheck))
    return gProfileManagerBundle.getString("profileExists");

  // profileNameToCheck is valid.
  return "";
}

function profileExists(aName) {
  var profiles = gProfileService.profiles;
  while (profiles.hasMoreElements()) {
    var profile = profiles.getNext().QueryInterface(I.nsIToolkitProfile);
    if (profile.name.toLowerCase() == aName.toLowerCase())
      return true;
  }

  return false;
}

// Called when the first wizard page is shown.
function enableNextButton() {
  document.documentElement.canAdvance = true;
}

function onFinish() {
  var profileName = document.getElementById("profileName").value;
  var profile;

  // Create profile named profileName in profileRoot.
  try {
    profile = gProfileService.createProfile(gProfileRoot, profileName);
  } catch (e) {
    var profileCreationFailed =
      gProfileManagerBundle.getString("profileCreationFailed");
    var profileCreationFailedTitle =
      gProfileManagerBundle.getString("profileCreationFailedTitle");
    var promptService = C["@mozilla.org/embedcomp/prompt-service;1"].
      getService(I.nsIPromptService);
    promptService.alert(window, profileCreationFailedTitle,
                        profileCreationFailed + "\n" + e);

    return false;
  }

  // window.opener is false if the Create Profile Wizard was opened from the
  // command line.
  if (window.opener) {
    // Add new profile to the list in the Profile Manager.
    window.opener.CreateProfile(profile);
  } else {
    // Use the newly created Profile.
    var profileLock = profile.lock(null);

    var dialogParams = window.arguments[0].QueryInterface(I.nsIDialogParamBlock);
    dialogParams.objects.insertElementAt(profileLock, 0, false);
  }

  // Exit the wizard.
  return true;
}
PK
!<gb		>chrome/toolkit/content/mozapps/profile/createProfileWizard.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE wizard [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/createProfileWizard.dtd">
%profileDTD;
]>

<wizard id="createProfileWizard"
        title="&newprofile.title;"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onwizardfinish="return onFinish();"
        onload="initWizard();"
        style="&window.size;">
  
  <stringbundle id="bundle_profileManager"
                src="chrome://mozapps/locale/profile/profileSelection.properties"/>

  <script type="application/javascript" src="chrome://mozapps/content/profile/createProfileWizard.js"/>

  <wizardpage id="explanation" onpageshow="enableNextButton();">
    <description>&profileCreationExplanation_1.text;</description>
    <description>&profileCreationExplanation_2.text;</description>
    <description>&profileCreationExplanation_3.text;</description>
    <spacer flex="1"/>
    <description>&profileCreationExplanation_4.text;</description>
  </wizardpage>

  <wizardpage id="createProfile" onpageshow="initSecondWizardPage();">
    <description>&profileCreationIntro.text;</description> 

    <label accesskey="&profilePrompt.accesskey;" control="ProfileName">&profilePrompt.label;</label> 
    <textbox id="profileName" value="&profileDefaultName;"
             oninput="updateProfileName(this.value);"/>
    
    <separator/>

    <description>&profileDirectoryExplanation.text;</description>

    <vbox class="indent" flex="1" style="overflow: auto;">
      <description id="profileDisplay">*</description>
    </vbox>

    <hbox>
      <button label="&button.choosefolder.label;" oncommand="chooseProfileFolder();" 
              accesskey="&button.choosefolder.accesskey;"/>

      <button id="useDefault" label="&button.usedefault.label;"
              oncommand="setDisplayToDefaultFolder(); updateProfileDisplay();"
              accesskey="&button.usedefault.accesskey;" disabled="true"/>
    </hbox>

    <separator/>

    <description id="finishText">*</description>
  </wizardpage>

</wizard>
PK
!<S:chrome/toolkit/content/mozapps/profile/profileSelection.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/. */

Components.utils.import("resource://gre/modules/AppConstants.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");

const C = Components.classes;
const I = Components.interfaces;

const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";

var gDialogParams;
var gProfileManagerBundle;
var gBrandBundle;
var gProfileService;

function startup() {
  try {
    gDialogParams = window.arguments[0].
      QueryInterface(I.nsIDialogParamBlock);

    gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService);

    gProfileManagerBundle = document.getElementById("bundle_profileManager");
    gBrandBundle = document.getElementById("bundle_brand");

    document.documentElement.centerWindowOnScreen();

    var profilesElement = document.getElementById("profiles");

    var profileList = gProfileService.profiles;
    while (profileList.hasMoreElements()) {
      var profile = profileList.getNext().QueryInterface(I.nsIToolkitProfile);

      var listitem = profilesElement.appendItem(profile.name, "");

      var tooltiptext =
        gProfileManagerBundle.getFormattedString("profileTooltip", [profile.name, profile.rootDir.path]);
      listitem.setAttribute("tooltiptext", tooltiptext);
      listitem.setAttribute("class", "listitem-iconic");
      listitem.profile = profile;
      try {
        if (profile === gProfileService.selectedProfile) {
          setTimeout(function(a) {
            profilesElement.ensureElementIsVisible(a);
            profilesElement.selectItem(a);
          }, 0, listitem);
        }
      } catch (e) { }
    }

    var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
    autoSelectLastProfile.checked = gProfileService.startWithLastProfile;
    profilesElement.focus();
  } catch (e) {
    window.close();
    throw (e);
  }
}

function acceptDialog() {
  var appName = gBrandBundle.getString("brandShortName");

  var profilesElement = document.getElementById("profiles");
  var selectedProfile = profilesElement.selectedItem;
  if (!selectedProfile) {
    var pleaseSelectTitle = gProfileManagerBundle.getString("pleaseSelectTitle");
    var pleaseSelect =
      gProfileManagerBundle.getFormattedString("pleaseSelect", [appName]);
    Services.prompt.alert(window, pleaseSelectTitle, pleaseSelect);

    return false;
  }

  var profileLock;

  try {
    profileLock = selectedProfile.profile.lock({ value: null });
  } catch (e) {
    if (!selectedProfile.profile.rootDir.exists()) {
      var missingTitle = gProfileManagerBundle.getString("profileMissingTitle");
      var missing =
        gProfileManagerBundle.getFormattedString("profileMissing", [appName]);
      Services.prompt.alert(window, missingTitle, missing);
      return false;
    }

    var lockedTitle = gProfileManagerBundle.getString("profileLockedTitle");
    var locked =
      gProfileManagerBundle.getFormattedString("profileLocked2", [appName, selectedProfile.profile.name, appName]);
    Services.prompt.alert(window, lockedTitle, locked);

    return false;
  }
  gDialogParams.objects.insertElementAt(profileLock.nsIProfileLock, 0, false);

  gProfileService.selectedProfile = selectedProfile.profile;
  gProfileService.defaultProfile = selectedProfile.profile;
  updateStartupPrefs();

  gDialogParams.SetInt(0, 1);

  gDialogParams.SetString(0, selectedProfile.profile.name);

  return true;
}

function exitDialog() {
  updateStartupPrefs();

  return true;
}

function updateStartupPrefs() {
  var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
  gProfileService.startWithLastProfile = autoSelectLastProfile.checked;

  /* Bug 257777 */
  gProfileService.startOffline = document.getElementById("offlineState").checked;
}

// handle key event on listboxes
function onProfilesKey(aEvent) {
  switch ( aEvent.keyCode ) {
  case KeyEvent.DOM_VK_BACK_SPACE:
    if (AppConstants.platform != "macosx")
      break;
  case KeyEvent.DOM_VK_DELETE:
    ConfirmDelete();
    break;
  case KeyEvent.DOM_VK_F2:
    RenameProfile();
    break;
  }
}

function onProfilesDblClick(aEvent) {
  if (aEvent.target.localName == "listitem")
    document.documentElement.acceptDialog();
}

// invoke the createProfile Wizard
function CreateProfileWizard() {
  window.openDialog("chrome://mozapps/content/profile/createProfileWizard.xul",
                    "", "centerscreen,chrome,modal,titlebar", gProfileService);
}

/**
 * Called from createProfileWizard to update the display.
 */
function CreateProfile(aProfile) {
  var profilesElement = document.getElementById("profiles");

  var listitem = profilesElement.appendItem(aProfile.name, "");

  var tooltiptext =
    gProfileManagerBundle.getFormattedString("profileTooltip", [aProfile.name, aProfile.rootDir.path]);
  listitem.setAttribute("tooltiptext", tooltiptext);
  listitem.setAttribute("class", "listitem-iconic");
  listitem.profile = aProfile;

  profilesElement.ensureElementIsVisible(listitem);
  profilesElement.selectItem(listitem);
}

// rename the selected profile
function RenameProfile() {
  var profilesElement = document.getElementById("profiles");
  var selectedItem = profilesElement.selectedItem;
  if (!selectedItem) {
    return false;
  }

  var selectedProfile = selectedItem.profile;

  var oldName = selectedProfile.name;
  var newName = {value: oldName};

  var dialogTitle = gProfileManagerBundle.getString("renameProfileTitle");
  var msg =
    gProfileManagerBundle.getFormattedString("renameProfilePrompt", [oldName]);

  if (Services.prompt.prompt(window, dialogTitle, msg, newName, null, {value: 0})) {
    newName = newName.value;

    // User hasn't changed the profile name. Treat as if cancel was pressed.
    if (newName == oldName)
      return false;

    try {
      selectedProfile.name = newName;
    } catch (e) {
      var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle");
      var alMsg = gProfileManagerBundle.getFormattedString("profileNameInvalid", [newName]);
      Services.prompt.alert(window, alTitle, alMsg);
      return false;
    }

    selectedItem.label = newName;
    var tiptext = gProfileManagerBundle.
                  getFormattedString("profileTooltip",
                                     [newName, selectedProfile.rootDir.path]);
    selectedItem.setAttribute("tooltiptext", tiptext);

    return true;
  }

  return false;
}

function ConfirmDelete() {
  var profileList = document.getElementById( "profiles" );

  var selectedItem = profileList.selectedItem;
  if (!selectedItem) {
    return false;
  }

  var selectedProfile = selectedItem.profile;
  var deleteFiles = false;

  if (selectedProfile.rootDir.exists()) {
    var dialogTitle = gProfileManagerBundle.getString("deleteTitle");
    var dialogText =
      gProfileManagerBundle.getFormattedString("deleteProfileConfirm",
                                               [selectedProfile.rootDir.path]);

    var buttonPressed = Services.prompt.confirmEx(window, dialogTitle, dialogText,
                          (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
                          (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
                          (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
                          gProfileManagerBundle.getString("dontDeleteFiles"),
                          null,
                          gProfileManagerBundle.getString("deleteFiles"),
                          null, {value: 0});
    if (buttonPressed == 1)
      return false;

    if (buttonPressed == 2)
      deleteFiles = true;
  }

  selectedProfile.remove(deleteFiles);
  profileList.removeChild(selectedItem);
  if (profileList.firstChild != undefined) {
    profileList.selectItem(profileList.firstChild);
  }

  return true;
}
PK
!<Zy	y	;chrome/toolkit/content/mozapps/profile/profileSelection.xul<?xml version="1.0"?>
<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
<!--

 This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://mozapps/skin/profile/profileSelection.css" type="text/css"?>

<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/profileSelection.dtd">
%profileDTD;
]>

<dialog
  id="profileWindow"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  class="non-resizable"
  title="&windowtitle.label;"
  orient="vertical"
  buttons="accept,cancel"
  style="width: 30em;"
  onload="startup();"
  ondialogaccept="return acceptDialog()"
  ondialogcancel="return exitDialog()"
  buttonlabelaccept="&start.label;"
  buttonlabelcancel="&exit.label;">

  <stringbundle id="bundle_profileManager"
                src="chrome://mozapps/locale/profile/profileSelection.properties"/>
  <stringbundle id="bundle_brand"
                src="chrome://branding/locale/brand.properties"/>

  <script type="application/javascript" src="chrome://mozapps/content/profile/profileSelection.js"/>

  <description class="label">&pmDescription.label;</description>

  <separator class="thin"/>

  <hbox class="profile-box indent" flex="1">

    <vbox id="managebuttons">
      <button id="newbutton" label="&newButton.label;"
              accesskey="&newButton.accesskey;" oncommand="CreateProfileWizard();"/>
      <button id="renbutton" label="&renameButton.label;"
              accesskey="&renameButton.accesskey;" oncommand="RenameProfile();"/>
      <button id="delbutton" label="&deleteButton.label;"
              accesskey="&deleteButton.accesskey;" oncommand="ConfirmDelete();"/>
    </vbox>

    <separator flex="1"/>

    <vbox flex="1">
      <listbox id="profiles" rows="5" seltype="single"
               ondblclick="onProfilesDblClick(event)"
               onkeypress="onProfilesKey(event);">
      </listbox>

      <!-- Bug 257777 -->
      <checkbox id="offlineState" label="&offlineState.label;" accesskey="&offlineState.accesskey;"/>

      <checkbox id="autoSelectLastProfile" label="&useSelected.label;"
                accesskey="&useSelected.accesskey;"/>
    </vbox>

  </hbox>
</dialog>
PK
!<*P0chrome/toolkit/content/mozapps/update/history.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/. */

const NS_XUL  = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

var gUpdateHistory = {
  _view: null,

  /**
   * Initialize the User Interface
   */
  onLoad() {
    this._view = document.getElementById("historyItems");

    var um =
        Components.classes["@mozilla.org/updates/update-manager;1"].
        getService(Components.interfaces.nsIUpdateManager);
    var uc = um.updateCount;
    if (uc) {
      while (this._view.hasChildNodes())
        this._view.firstChild.remove();

      var bundle = document.getElementById("updateBundle");

      for (var i = 0; i < uc; ++i) {
        var update = um.getUpdateAt(i);
        if (!update || !update.name)
          continue;

        // Don't display updates that are downloading since they don't have
        // valid statusText for the UI (bug 485493).
        if (!update.statusText)
          continue;

        var element = document.createElementNS(NS_XUL, "update");
        this._view.appendChild(element);
        element.name = bundle.getFormattedString("updateFullName",
          [update.name, update.buildID]);
        element.type = bundle.getString("updateType_" + update.type);
        element.installDate = this._formatDate(update.installDate);
        if (update.detailsURL)
          element.detailsURL = update.detailsURL;
        else
          element.hideDetailsURL = true;
        element.status = update.statusText;
      }
    }
    var cancelbutton = document.documentElement.getButton("cancel");
    cancelbutton.focus();
  },

  /**
   * Formats a date into human readable form
   * @param   seconds
   *          A date in seconds since 1970 epoch
   * @returns A human readable date string
   */
  _formatDate(seconds) {
    var date = new Date(seconds);
    const dtOptions = { year: "numeric", month: "long", day: "numeric",
                        hour: "numeric", minute: "numeric", second: "numeric" };
    return date.toLocaleString(undefined, dtOptions);
  }
};

PK
!<ܬHH1chrome/toolkit/content/mozapps/update/history.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE dialog [
<!ENTITY % historyDTD SYSTEM "chrome://mozapps/locale/update/history.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%historyDTD;
%brandDTD;
]>

<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://mozapps/content/update/updates.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/update/updates.css"?>

<dialog id="history" windowtype="Update:History"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        style="width: 35em;"
        buttons="cancel"
        defaultButton="cancel"
        buttonlabelcancel="&closebutton.label;"
        title="&history.title;"
        onload="gUpdateHistory.onLoad();">

  <script type="application/javascript"
          src="chrome://mozapps/content/update/history.js"/>

  <stringbundle id="updateBundle"
                src="chrome://mozapps/locale/update/updates.properties"/>

  <label>&history.intro;</label>
  <separator class="thin"/>
  <richlistbox id="historyItems" flex="1">
    <label>&noupdates.label;</label>
  </richlistbox>
  <separator class="thin"/>
</dialog>
PK
!<YH+N1chrome/toolkit/content/mozapps/update/updates.css/* Stop animations on pages that aren't on the current page (bug 341749). */
#updates:not([currentpageid="checking"]) #checkingProgress,
#updates:not([currentpageid="downloading"]) #downloadProgress {
  display: none;
}

/* Hide the wizard's header so the size of the billboard can size the window 
   on creation. A custom header will be used in its place when a header is
   needed. */
.wizard-header {
  display: none;
}

/* Display the custom header */
.update-header {
  display: -moz-box !important;
}

/* Custom header implementation based on the Wizard's header. This allows the
   size of the billboard's remotecontent to size the window since it does not
   have an updateheader on the billboard page. */
updateheader {
  -moz-binding: url("chrome://mozapps/content/update/updates.xml#updateheader");
  display: -moz-box;
  -moz-box-orient: horizontal;
}

/* Update History Window */
update {
  -moz-binding: url("chrome://mozapps/content/update/updates.xml#update");
  display: -moz-box;
  -moz-box-orient: vertical;
}
PK
!<@jqq0chrome/toolkit/content/mozapps/update/updates.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";

/* import-globals-from ../../../content/contentAreaUtils.js */

// Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
// so we have to use different names.
const {classes: CoC, interfaces: CoI, results: CoR, utils: CoU} = Components;

/* globals DownloadUtils, Services, AUSTLMY */
CoU.import("resource://gre/modules/DownloadUtils.jsm", this);
CoU.import("resource://gre/modules/Services.jsm", this);
CoU.import("resource://gre/modules/UpdateTelemetry.jsm", this);

const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

const PREF_APP_UPDATE_BACKGROUNDERRORS    = "app.update.backgroundErrors";
const PREF_APP_UPDATE_CERT_ERRORS         = "app.update.cert.errors";
const PREF_APP_UPDATE_ELEVATE_NEVER       = "app.update.elevate.never";
const PREF_APP_UPDATE_ENABLED             = "app.update.enabled";
const PREF_APP_UPDATE_LOG                 = "app.update.log";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_TEST_LOOP           = "app.update.test.loop";
const PREF_APP_UPDATE_URL_MANUAL          = "app.update.url.manual";

const UPDATE_TEST_LOOP_INTERVAL = 2000;

const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";

const STATE_DOWNLOADING       = "downloading";
const STATE_PENDING           = "pending";
const STATE_PENDING_SERVICE   = "pending-service";
const STATE_PENDING_ELEVATE   = "pending-elevate";
const STATE_APPLYING          = "applying";
const STATE_APPLIED           = "applied";
const STATE_APPLIED_SERVICE   = "applied-service";
const STATE_SUCCEEDED         = "succeeded";
const STATE_DOWNLOAD_FAILED   = "download-failed";
const STATE_FAILED            = "failed";

const SRCEVT_FOREGROUND       = 1;
const SRCEVT_BACKGROUND       = 2;

const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;

var gLogEnabled = false;
var gUpdatesFoundPageId;

// Notes:
// 1. use the wizard's goTo method whenever possible to change the wizard
//    page since it is simpler than most other methods and behaves nicely with
//    mochitests.
// 2. using a page's onPageShow method to then change to a different page will
//    of course call that page's onPageShow method which can make mochitests
//    overly complicated and fragile so avoid doing this if at all possible.
//    This is why a page's next attribute is set prior to the page being shown
//    whenever possible.

/**
 * Logs a string to the error console.
 * @param   string
 *          The string to write to the error console..
 */
function LOG(module, string) {
  if (gLogEnabled) {
    dump("*** AUS:UI " + module + ":" + string + "\n");
    Services.console.logStringMessage("AUS:UI " + module + ":" + string);
  }
}

/**
 * Opens a URL using the event target's url attribute for the URL. This is a
 * workaround for Bug 263433 which prevents respecting tab browser preferences
 * for where to open a URL.
 */
function openUpdateURL(event) {
  if (event.button == 0)
    openURL(event.target.getAttribute("url"));
}

/**
 * Gets a preference value, handling the case where there is no default.
 * @param   func
 *          The name of the preference function to call, on nsIPrefBranch
 * @param   preference
 *          The name of the preference
 * @param   defaultValue
 *          The default value to return in the event the preference has
 *          no setting
 * @returns The value of the preference, or undefined if there was no
 *          user or default value.
 */
function getPref(func, preference, defaultValue) {
  try {
    return Services.prefs[func](preference);
  } catch (e) {
    LOG("General", "getPref - failed to get preference: " + preference);
  }
  return defaultValue;
}

/**
 * A set of shared data and control functions for the wizard as a whole.
 */
var gUpdates = {
  /**
   * The nsIUpdate object being used by this window (either for downloading,
   * notification or both).
   */
  update: null,

  /**
   * The updates.properties <stringbundle> element.
   */
  strings: null,

  /**
   * The Application brandShortName (e.g. "Firefox")
   */
  brandName: null,

  /**
   * The <wizard> element
   */
  wiz: null,

  /**
   * Whether to run the unload handler. This will be set to false when the user
   * exits the wizard via onWizardCancel or onWizardFinish.
   */
  _runUnload: true,

  /**
   * Submit on close telemtry values for the update wizard.
   * @param  pageID
   *         The page id for the last page displayed.
   */
  _submitTelemetry(aPageID) {
    AUSTLMY.pingWizLastPageCode(aPageID);
  },

  /**
   * Helper function for setButtons
   * Resets button to original label & accesskey if string is null.
   */
  _setButton(button, string) {
    if (string) {
      var label = this.getAUSString(string);
      if (label.indexOf("%S") != -1)
        label = label.replace(/%S/, this.brandName);
      button.label = label;
      button.setAttribute("accesskey",
                          this.getAUSString(string + ".accesskey"));
    } else {
      button.label = button.defaultLabel;
      button.setAttribute("accesskey", button.defaultAccesskey);
    }
  },

  /**
   * Sets the attributes needed for this Wizard's control buttons (labels,
   * disabled, hidden, etc.)
   * @param   extra1ButtonString
   *          The property in the stringbundle containing the label to put on
   *          the first extra button, or null to hide the first extra button.
   * @param   extra2ButtonString
   *          The property in the stringbundle containing the label to put on
   *          the second extra button, or null to hide the second extra button.
   * @param   nextFinishButtonString
   *          The property in the stringbundle containing the label to put on
   *          the Next / Finish button, or null to hide the button. The Next and
   *          Finish buttons are never displayed at the same time in a wizard
   *          with the the Finish button only being displayed when there are no
   *          additional pages to display in the wizard.
   * @param   canAdvance
   *          true if the wizard can be advanced (e.g. the next / finish button
   *          should be enabled), false otherwise.
   * @param   showCancel
   *          true if the wizard's cancel button should be shown, false
   *          otherwise. If not specified this will default to false.
   *
   * Note:
   * Per Bug 324121 the wizard should not look like a wizard and to accomplish
   * this the back button is never displayed. This causes the wizard buttons to
   * be arranged as follows on Windows with the next and finish buttons never
   * being displayed at the same time.
   * +--------------------------------------------------------------+
   * | [ extra1 ] [ extra2 ]                     [ next or finish ] |
   * +--------------------------------------------------------------+
   */
  setButtons(extra1ButtonString, extra2ButtonString,
                       nextFinishButtonString, canAdvance, showCancel) {
    this.wiz.canAdvance = canAdvance;

    var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next");
    var be1 = this.wiz.getButton("extra1");
    var be2 = this.wiz.getButton("extra2");
    var bc = this.wiz.getButton("cancel");

    // Set the labels for the next / finish, extra1, and extra2 buttons
    this._setButton(bnf, nextFinishButtonString);
    this._setButton(be1, extra1ButtonString);
    this._setButton(be2, extra2ButtonString);

    bnf.hidden = bnf.disabled = !nextFinishButtonString;
    be1.hidden = be1.disabled = !extra1ButtonString;
    be2.hidden = be2.disabled = !extra2ButtonString;
    bc.hidden = bc.disabled = !showCancel;

    // Hide and disable the back button each time setButtons is called
    // (see bug 464765).
    var btn = this.wiz.getButton("back");
    btn.hidden = btn.disabled = true;

    // Hide and disable the finish button if not on the last page or the next
    // button if on the last page each time setButtons is called.
    btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish");
    btn.hidden = btn.disabled = true;
  },

  getAUSString(key, strings) {
    if (strings)
      return this.strings.getFormattedString(key, strings);
    return this.strings.getString(key);
  },

  never() {
    // If the user clicks "No Thanks", we should not prompt them to update to
    // this version again unless they manually select "Check for Updates..."
    // which will clear app.update.elevate.never preference.
    let aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    if (aus.elevationRequired) {
      Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_NEVER,
                                 this.update.appVersion);
    }
  },

  /**
   * A hash of |pageid| attribute to page object. Can be used to dispatch
   * function calls to the appropriate page.
   */
  _pages: { },

  /**
   * Called when the user presses the "Finish" button on the wizard, dispatches
   * the function call to the selected page.
   */
  onWizardFinish() {
    this._runUnload = false;
    var pageid = document.documentElement.currentPage.pageid;
    if ("onWizardFinish" in this._pages[pageid])
      this._pages[pageid].onWizardFinish();
    this._submitTelemetry(pageid);
  },

  /**
   * Called when the user presses the "Cancel" button on the wizard, dispatches
   * the function call to the selected page.
   */
  onWizardCancel() {
    this._runUnload = false;
    var pageid = document.documentElement.currentPage.pageid;
    if ("onWizardCancel" in this._pages[pageid])
      this._pages[pageid].onWizardCancel();
    this._submitTelemetry(pageid);
  },

  /**
   * Called when the user presses the "Next" button on the wizard, dispatches
   * the function call to the selected page.
   */
  onWizardNext() {
    var cp = document.documentElement.currentPage;
    if (!cp)
      return;
    var pageid = cp.pageid;
    if ("onWizardNext" in this._pages[pageid])
      this._pages[pageid].onWizardNext();
  },

  /**
   * The checking process that spawned this update UI. There are two types:
   * SRCEVT_FOREGROUND:
   *   Some user-generated event caused this UI to appear, e.g. the Help
   *   menu item or the button in preferences. When in this mode, the UI
   *   should remain active for the duration of the download.
   * SRCEVT_BACKGROUND:
   *   A background update check caused this UI to appear, probably because
   *   the user has the app.update.auto preference set to false.
   */
  sourceEvent: SRCEVT_FOREGROUND,

  /**
   * Helper function for onLoad
   * Saves default button label & accesskey for use by _setButton
   */
  _cacheButtonStrings(buttonName) {
    var button = this.wiz.getButton(buttonName);
    button.defaultLabel = button.label;
    button.defaultAccesskey = button.getAttribute("accesskey");
  },

  /**
   * Called when the wizard UI is loaded.
   */
  onLoad() {
    this.wiz = document.documentElement;

    gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);

    this.strings = document.getElementById("updateStrings");
    var brandStrings = document.getElementById("brandStrings");
    this.brandName = brandStrings.getString("brandShortName");

    var pages = this.wiz.childNodes;
    for (var i = 0; i < pages.length; ++i) {
      var page = pages[i];
      if (page.localName == "wizardpage")
        // eslint-disable-next-line no-eval
        this._pages[page.pageid] = eval(page.getAttribute("object"));
    }

    // Cache the standard button labels in case we need to restore them
    this._cacheButtonStrings("next");
    this._cacheButtonStrings("finish");
    this._cacheButtonStrings("extra1");
    this._cacheButtonStrings("extra2");

    // Advance to the Start page.
    this.getStartPageID(function(startPageID) {
      LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID);
      gUpdates.wiz.currentPage = document.getElementById(startPageID);
    });
  },

  /**
   * Called when the wizard UI is unloaded.
   */
  onUnload() {
    if (this._runUnload) {
      var cp = this.wiz.currentPage;
      if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
        this.onWizardCancel();
    }
  },

  /**
   * Gets the ID of the <wizardpage> object that should be displayed first. This
   * is an asynchronous method that passes the resulting object to a callback
   * function.
   *
   * This is determined by how we were called by the update prompt:
   *
   * Prompt Method:       Arg0:         Update State: Src Event:  Failed:   Result:
   * showUpdateAvailable  nsIUpdate obj --            background  --        updatesfoundbasic
   * showUpdateDownloaded nsIUpdate obj pending       background  --        finishedBackground
   * showUpdateError      nsIUpdate obj failed        either      partial   errorpatching
   * showUpdateError      nsIUpdate obj failed        either      complete  errors
   * checkForUpdates      null          --            foreground  --        checking
   * checkForUpdates      null          downloading   foreground  --        downloading
   *
   * @param   aCallback
   *          A callback to pass the <wizardpage> object to be displayed first to.
   */
  getStartPageID(aCallback) {
    if ("arguments" in window && window.arguments[0]) {
      var arg0 = window.arguments[0];
      if (arg0 instanceof CoI.nsIUpdate) {
        // If the first argument is a nsIUpdate object, we are notifying the
        // user that the background checking found an update that requires
        // their permission to install, and it's ready for download.
        this.setUpdate(arg0);
        if (this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
          aCallback("errorextra");
          return;
        }

        if (this.update.unsupported) {
          aCallback("unsupported");
          return;
        }

        var p = this.update.selectedPatch;
        if (p) {
          let state = p.state;
          let patchFailed = this.update.getProperty("patchingFailed");
          if (patchFailed) {
            if (patchFailed != "partial" || this.update.patchCount != 2) {
              // If the complete patch failed, which is far less likely, show
              // the error text held by the update object in the generic errors
              // page, triggered by the |STATE_DOWNLOAD_FAILED| state. This also
              // handles the case when an elevation was cancelled on Mac OS X.
              state = STATE_DOWNLOAD_FAILED;
            } else {
              // If the system failed to apply the partial patch, show the
              // screen which best describes this condition, which is triggered
              // by the |STATE_FAILED| state.
              state = STATE_FAILED;
            }
          }

          // Now select the best page to start with, given the current state of
          // the Update.
          switch (state) {
            case STATE_PENDING:
            case STATE_PENDING_SERVICE:
            case STATE_PENDING_ELEVATE:
            case STATE_APPLIED:
            case STATE_APPLIED_SERVICE:
              this.sourceEvent = SRCEVT_BACKGROUND;
              aCallback("finishedBackground");
              return;
            case STATE_DOWNLOADING:
              aCallback("downloading");
              return;
            case STATE_FAILED:
              window.getAttention();
              aCallback("errorpatching");
              return;
            case STATE_DOWNLOAD_FAILED:
            case STATE_APPLYING:
              aCallback("errors");
              return;
          }
        }

        let aus = CoC["@mozilla.org/updates/update-service;1"].
                  getService(CoI.nsIApplicationUpdateService);
        if (!aus.canApplyUpdates) {
          aCallback("manualUpdate");
          return;
        }

        aCallback(this.updatesFoundPageId);
        return;
      }
    } else {
      var um = CoC["@mozilla.org/updates/update-manager;1"].
               getService(CoI.nsIUpdateManager);
      if (um.activeUpdate) {
        this.setUpdate(um.activeUpdate);
        aCallback("downloading");
        return;
      }
    }
    aCallback("checking");
  },

  /**
   * Returns the string page ID for the appropriate updates found page based
   * on the update's metadata.
   */
  get updatesFoundPageId() {
    if (gUpdatesFoundPageId)
      return gUpdatesFoundPageId;
    return gUpdatesFoundPageId = "updatesfoundbasic";
  },

  /**
   * Sets the Update object for this wizard
   * @param   update
   *          The update object
   */
  setUpdate(update) {
    this.update = update;
    if (this.update)
      this.update.QueryInterface(CoI.nsIWritablePropertyBag);
  }
};

/**
 * The "Checking for Updates" page. Provides feedback on the update checking
 * process.
 */
var gCheckingPage = {
  /**
   * The nsIUpdateChecker that is currently checking for updates. We hold onto
   * this so we can cancel the update check if the user closes the window.
   */
  _checker: null,

  /**
   * Initialize
   */
  onPageShow() {
    gUpdates.setButtons(null, null, null, false, true);
    gUpdates.wiz.getButton("cancel").focus();

    // Clear elevation never prefs to handle the scenario where the user clicked
    // "never" for an update and then canceled a manual update check.  If the
    // preference isn't cleared then future notifications will never happen.
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
    }

    // The user will be notified if there is an error so clear the background
    // check error count.
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
    }

    // The preference will be set back to true if the system is still
    // unsupported.
    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
    }

    this._checker = CoC["@mozilla.org/updates/update-checker;1"].
                    createInstance(CoI.nsIUpdateChecker);
    this._checker.checkForUpdates(this.updateListener, true);
  },

  /**
   * The user has closed the window, either by pressing cancel or using a Window
   * Manager control, so stop checking for updates.
   */
  onWizardCancel() {
    this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK);
  },

  /**
   * An object implementing nsIUpdateCheckListener that is notified as the
   * update check commences.
   */
  updateListener: {
    /**
     * See nsIUpdateCheckListener
     */
    onCheckComplete(request, updates, updateCount) {
      var aus = CoC["@mozilla.org/updates/update-service;1"].
                getService(CoI.nsIApplicationUpdateService);
      gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
      if (gUpdates.update) {
        LOG("gCheckingPage", "onCheckComplete - update found");
        if (gUpdates.update.unsupported) {
          gUpdates.wiz.goTo("unsupported");
          return;
        }

        if (gUpdates.update.elevationFailure) {
          // Prevent multiple notifications for the same update when the client
          // has had an elevation failure.
          gUpdates.never();
          gUpdates.wiz.goTo("manualUpdate");
          return;
        }

        if (!aus.canApplyUpdates) {
          gUpdates.wiz.goTo("manualUpdate");
          return;
        }

        gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
        return;
      }

      LOG("gCheckingPage", "onCheckComplete - no update found");
      gUpdates.wiz.goTo("noupdatesfound");
    },

    /**
     * See nsIUpdateCheckListener
     */
    onError(request, update) {
      LOG("gCheckingPage", "onError - proceeding to error page");
      gUpdates.setUpdate(update);
      gUpdates.wiz.goTo("errors");
    },

    /**
     * See nsISupports.idl
     */
    QueryInterface(aIID) {
      if (!aIID.equals(CoI.nsIUpdateCheckListener) &&
          !aIID.equals(CoI.nsISupports))
        throw CoR.NS_ERROR_NO_INTERFACE;
      return this;
    }
  }
};

/**
 * The "No Updates Are Available" page
 */
var gNoUpdatesPage = {
  /**
   * Initialize
   */
  onPageShow() {
    LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
        "update. Either there were no updates or |selectUpdate| failed");

    if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true))
      document.getElementById("noUpdatesAutoEnabled").hidden = false;
    else
      document.getElementById("noUpdatesAutoDisabled").hidden = false;

    gUpdates.setButtons(null, null, "okButton", true);
    gUpdates.wiz.getButton("finish").focus();
  }
};

/**
 * The "Unable to Update" page. Provides the user information about why they
 * were unable to update and a manual download url.
 */
var gManualUpdatePage = {
  onPageShow() {
    var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
    var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
    manualUpdateLinkLabel.value = manualURL;
    manualUpdateLinkLabel.setAttribute("url", manualURL);

    gUpdates.setButtons(null, null, "okButton", true);
    gUpdates.wiz.getButton("finish").focus();
  }
};

/**
 * The "System Unsupported" page. Provides the user with information about their
 * system no longer being supported and an url for more information.
 */
var gUnsupportedPage = {
  onPageShow() {
    Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
    if (gUpdates.update.detailsURL) {
      let unsupportedLinkLabel = document.getElementById("unsupportedLinkLabel");
      unsupportedLinkLabel.setAttribute("url", gUpdates.update.detailsURL);
    }

    gUpdates.setButtons(null, null, "okButton", true);
    gUpdates.wiz.getButton("finish").focus();
  }
};

/**
 * The "Updates Are Available" page. Provides the user information about the
 * available update.
 */
var gUpdatesFoundBasicPage = {
  /**
   * Initialize
   */
  onPageShow() {
    gUpdates.wiz.canRewind = false;
    var update = gUpdates.update;
    gUpdates.setButtons("askLaterButton", null, "updateButton_" + update.type,
                        true);
    var btn = gUpdates.wiz.getButton("next");
    btn.focus();

    var updateName = update.name;
    if (update.channel == "nightly") {
      updateName = gUpdates.getAUSString("updateNightlyName",
                                         [gUpdates.brandName,
                                          update.displayVersion,
                                          update.buildID]);
    }
    var updateNameElement = document.getElementById("updateName");
    updateNameElement.value = updateName;

    var introText = gUpdates.getAUSString("intro_" + update.type,
                                          [gUpdates.brandName, update.displayVersion]);
    var introElem = document.getElementById("updatesFoundInto");
    introElem.setAttribute("severity", update.type);
    introElem.textContent = introText;

    var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
    if (update.detailsURL)
      updateMoreInfoURL.setAttribute("url", update.detailsURL);
    else
      updateMoreInfoURL.hidden = true;

    var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type +
                                            ".title");
    document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle);
  },

  onExtra1() {
    gUpdates.wiz.cancel();
  }
};

/**
 * The "Update is Downloading" page - provides feedback for the download
 * process plus a pause/resume UI
 */
var gDownloadingPage = {
  /**
   * DOM Elements
   */
  _downloadStatus: null,
  _downloadProgress: null,
  _pauseButton: null,

  /**
   * Whether or not we are currently paused
   */
  _paused: false,

  /**
   * Label cache to hold the 'Connecting' string
   */
  _label_downloadStatus: null,

  /**
   * Member variables for updating download status
   */
  _lastSec: Infinity,
  _startTime: null,
  _pausedStatus: "",

  _hiding: false,

  /**
   * Have we registered an observer for a background update being staged
   */
  _updateApplyingObserver: false,

  /**
   * Initialize
   */
  onPageShow() {
    this._downloadStatus = document.getElementById("downloadStatus");
    this._downloadProgress = document.getElementById("downloadProgress");
    this._pauseButton = document.getElementById("pauseButton");
    this._label_downloadStatus = this._downloadStatus.textContent;

    this._pauseButton.setAttribute("tooltiptext",
                                   gUpdates.getAUSString("pauseButtonPause"));

    // move focus to the pause/resume button and then disable it (bug #353177)
    this._pauseButton.focus();
    this._pauseButton.disabled = true;

    var aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);

    var um = CoC["@mozilla.org/updates/update-manager;1"].
             getService(CoI.nsIUpdateManager);
    var activeUpdate = um.activeUpdate;
    if (activeUpdate) {
      gUpdates.setUpdate(activeUpdate);

      // It's possible the update has already been downloaded and is being
      // applied by the time this page is shown, depending on how fast the
      // download goes and how quickly the 'next' button is clicked to get here.
      if (activeUpdate.state == STATE_PENDING ||
          activeUpdate.state == STATE_PENDING_ELEVATE ||
          activeUpdate.state == STATE_PENDING_SERVICE) {
        if (!activeUpdate.getProperty("stagingFailed")) {
          gUpdates.setButtons("hideButton", null, null, false);
          gUpdates.wiz.getButton("extra1").focus();

          this._setUpdateApplying();
          return;
        }

        gUpdates.wiz.goTo("finished");
        return;
      }
    }

    if (!gUpdates.update) {
      LOG("gDownloadingPage", "onPageShow - no valid update to download?!");
      return;
    }

    this._startTime = Date.now();

    try {
      // Say that this was a foreground download, not a background download,
      // since the user cared enough to look in on this process.
      gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag);
      gUpdates.update.setProperty("foregroundDownload", "true");

      // Pause any active background download and restart it as a foreground
      // download.
      aus.pauseDownload();
      var state = aus.downloadUpdate(gUpdates.update, false);
      if (state == "failed") {
        // We've tried as hard as we could to download a valid update -
        // we fell back from a partial patch to a complete patch and even
        // then we couldn't validate. Show a validation error with instructions
        // on how to manually update.
        this.cleanUp();
        gUpdates.wiz.goTo("errors");
        return;
      }
      // Add this UI as a listener for active downloads
      aus.addDownloadListener(this);

      if (activeUpdate)
        this._setUIState(!aus.isDownloading);
    } catch (e) {
      LOG("gDownloadingPage", "onPageShow - error: " + e);
    }

    gUpdates.setButtons("hideButton", null, null, false);
    gUpdates.wiz.getButton("extra1").focus();
  },

  /**
   * Updates the text status message
   */
  _setStatus(status) {
    // Don't bother setting the same text more than once. This can happen
    // due to the asynchronous behavior of the downloader.
    if (this._downloadStatus.textContent == status)
      return;
    while (this._downloadStatus.hasChildNodes())
      this._downloadStatus.firstChild.remove();
    this._downloadStatus.appendChild(document.createTextNode(status));
  },

  /**
   * Update download progress status to show time left, speed, and progress.
   * Also updates the status needed for pausing the download.
   *
   * @param aCurr
   *        Current number of bytes transferred
   * @param aMax
   *        Total file size of the download
   * @return Current active download status
   */
  _updateDownloadStatus(aCurr, aMax) {
    let status;

    // Get the download time left and progress
    let rate = aCurr / (Date.now() - this._startTime) * 1000;
    [status, this._lastSec] =
      DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec);

    // Get the download progress for pausing
    this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax);

    return status;
  },

  /**
   * Adjust UI to suit a certain state of paused-ness
   * @param   paused
   *          Whether or not the download is paused
   */
  _setUIState(paused) {
    var u = gUpdates.update;
    if (paused) {
      if (this._downloadProgress.mode != "normal")
        this._downloadProgress.mode = "normal";
      this._pauseButton.setAttribute("tooltiptext",
                                     gUpdates.getAUSString("pauseButtonResume"));
      this._pauseButton.setAttribute("paused", "true");
      var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag);
      var status = p.getProperty("status");
      if (status) {
        let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]);
        this._setStatus(pausedStatus);
      }
    } else {
      if (this._downloadProgress.mode != "undetermined")
        this._downloadProgress.mode = "undetermined";
      this._pauseButton.setAttribute("paused", "false");
      this._pauseButton.setAttribute("tooltiptext",
                                     gUpdates.getAUSString("pauseButtonPause"));
      this._setStatus(this._label_downloadStatus);
    }
  },

  /**
   * Wait for an update being staged in the background.
   */
  _setUpdateApplying() {
    this._downloadProgress.mode = "undetermined";
    this._pauseButton.hidden = true;
    let applyingStatus = gUpdates.getAUSString("applyingUpdate");
    this._setStatus(applyingStatus);

    Services.obs.addObserver(this, "update-staged");
    this._updateApplyingObserver = true;
  },

  /**
   * Clean up the listener and observer registered for the wizard.
   */
  cleanUp() {
    var aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    aus.removeDownloadListener(this);

    if (this._updateApplyingObserver) {
      Services.obs.removeObserver(this, "update-staged");
      this._updateApplyingObserver = false;
    }
  },

  /**
   * When the user clicks the Pause/Resume button
   */
  onPause() {
    var aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    if (this._paused)
      aus.downloadUpdate(gUpdates.update, false);
    else {
      var patch = gUpdates.update.selectedPatch;
      patch.QueryInterface(CoI.nsIWritablePropertyBag);
      patch.setProperty("status", this._pausedStatus);
      aus.pauseDownload();
    }
    this._paused = !this._paused;

    // Update the UI
    this._setUIState(this._paused);
  },

  /**
   * When the user has closed the window using a Window Manager control (this
   * page doesn't have a cancel button) cancel the update in progress.
   */
  onWizardCancel() {
    if (this._hiding)
      return;

    this.cleanUp();
  },

  /**
   * When the user closes the Wizard UI by clicking the Hide button
   */
  onHide() {
    // Set _hiding to true to prevent onWizardCancel from cancelling the update
    // that is in progress.
    this._hiding = true;

    // Remove ourself as a download listener so that we don't continue to be
    // fed progress and state notifications after the UI we're updating has
    // gone away.
    this.cleanUp();

    var aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    var um = CoC["@mozilla.org/updates/update-manager;1"].
             getService(CoI.nsIUpdateManager);
    um.activeUpdate = gUpdates.update;

    // If the download was paused by the user, ask the user if they want to
    // have the update resume in the background.
    var downloadInBackground = true;
    if (this._paused) {
      var title = gUpdates.getAUSString("resumePausedAfterCloseTitle");
      var message = gUpdates.getAUSString("resumePausedAfterCloseMsg",
                                          [gUpdates.brandName]);
      var ps = Services.prompt;
      var flags = ps.STD_YES_NO_BUTTONS;
      // Focus the software update wizard before prompting. This will raise
      // the software update wizard if it is minimized making it more obvious
      // what the prompt is for and will solve the problem of windows
      // obscuring the prompt. See bug #350299 for more details.
      window.focus();
      var rv = ps.confirmEx(window, title, message, flags, null, null, null,
                            null, { });
      if (rv == CoI.nsIPromptService.BUTTON_POS_0)
        downloadInBackground = false;
    }
    if (downloadInBackground) {
      // Continue download in the background at full speed.
      LOG("gDownloadingPage", "onHide - continuing download in background " +
          "at full speed");
      aus.downloadUpdate(gUpdates.update, false);
    }
    gUpdates.wiz.cancel();
  },

  /**
   * When the data transfer begins
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   */
  onStartRequest(request, context) {
    // This !paused test is necessary because onStartRequest may fire after
    // the download was paused (for those speedy clickers...)
    if (this._paused)
      return;

    if (this._downloadProgress.mode != "undetermined")
      this._downloadProgress.mode = "undetermined";
    this._setStatus(this._label_downloadStatus);
  },

  /**
   * When new data has been downloaded
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   progress
   *          The current number of bytes transferred
   * @param   maxProgress
   *          The total number of bytes that must be transferred
   */
  onProgress(request, context, progress, maxProgress) {
    let status = this._updateDownloadStatus(progress, maxProgress);
    var currentProgress = Math.round(100 * (progress / maxProgress));

    var p = gUpdates.update.selectedPatch;
    p.QueryInterface(CoI.nsIWritablePropertyBag);
    p.setProperty("progress", currentProgress);
    p.setProperty("status", status);

    // This !paused test is necessary because onProgress may fire after
    // the download was paused (for those speedy clickers...)
    if (this._paused)
      return;

    if (this._downloadProgress.mode != "normal")
      this._downloadProgress.mode = "normal";
    if (this._downloadProgress.value != currentProgress)
      this._downloadProgress.value = currentProgress;
    if (this._pauseButton.disabled)
      this._pauseButton.disabled = false;

    // If the update has completed downloading and the download status contains
    // the original text return early to avoid an assertion in debug builds.
    // Since the page will advance immmediately due to the update completing the
    // download updating the status is not important.
    // nsTextFrame::GetTrimmedOffsets 'Can only call this on frames that have
    // been reflowed'.
    if (progress == maxProgress &&
        this._downloadStatus.textContent == this._label_downloadStatus)
      return;

    this._setStatus(status);
  },

  /**
   * When we have new status text
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   status
   *          A status code
   * @param   statusText
   *          Human readable version of |status|
   */
  onStatus(request, context, status, statusText) {
    this._setStatus(statusText);
  },

  /**
   * When data transfer ceases
   * @param   request
   *          The nsIRequest object for the transfer
   * @param   context
   *          Additional data
   * @param   status
   *          Status code containing the reason for the cessation.
   */
  onStopRequest(request, context, status) {
    if (this._downloadProgress.mode != "normal")
      this._downloadProgress.mode = "normal";

    var u = gUpdates.update;
    switch (status) {
      case CoR.NS_ERROR_CORRUPTED_CONTENT:
      case CoR.NS_ERROR_UNEXPECTED:
        if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
            (u.isCompleteUpdate || u.patchCount != 2)) {
          // Verification error of complete patch, informational text is held in
          // the update object.
          this.cleanUp();
          gUpdates.wiz.goTo("errors");
          break;
        }
        // Verification failed for a partial patch, complete patch is now
        // downloading so return early and do NOT remove the download listener!

        // Reset the progress meter to "undertermined" mode so that we don't
        // show old progress for the new download of the "complete" patch.
        this._downloadProgress.mode = "undetermined";
        this._pauseButton.disabled = true;
        document.getElementById("verificationFailed").hidden = false;
        break;
      case CoR.NS_BINDING_ABORTED:
        LOG("gDownloadingPage", "onStopRequest - pausing download");
        // Do not remove UI listener since the user may resume downloading again.
        break;
      case CoR.NS_OK:
        LOG("gDownloadingPage", "onStopRequest - patch verification succeeded");
        // If the background update pref is set, we should wait until the update
        // is actually staged in the background.
        let aus = CoC["@mozilla.org/updates/update-service;1"].
                  getService(CoI.nsIApplicationUpdateService);
        if (aus.canStageUpdates) {
          this._setUpdateApplying();
        } else {
          this.cleanUp();
          gUpdates.wiz.goTo("finished");
        }
        break;
      default:
        LOG("gDownloadingPage", "onStopRequest - transfer failed");
        // Some kind of transfer error, die.
        this.cleanUp();
        gUpdates.wiz.goTo("errors");
        break;
    }
  },

  /**
   * See nsIObserver.idl
   */
  observe(aSubject, aTopic, aData) {
    if (aTopic == "update-staged") {
      if (aData == STATE_DOWNLOADING) {
        // We've fallen back to downloding the full update because the
        // partial update failed to get staged in the background.
        this._setStatus("downloading");
        return;
      }
      this.cleanUp();
      if (aData == STATE_APPLIED ||
          aData == STATE_APPLIED_SERVICE ||
          aData == STATE_PENDING ||
          aData == STATE_PENDING_SERVICE ||
          aData == STATE_PENDING_ELEVATE) {
        // If the update is successfully applied, or if the updater has
        // fallen back to non-staged updates, go to the finish page.
        gUpdates.wiz.goTo("finished");
      } else {
        gUpdates.wiz.goTo("errors");
      }
    }
  },

  /**
   * See nsISupports.idl
   */
  QueryInterface(iid) {
    if (!iid.equals(CoI.nsIRequestObserver) &&
        !iid.equals(CoI.nsIProgressEventSink) &&
        !iid.equals(CoI.nsIObserver) &&
        !iid.equals(CoI.nsISupports))
      throw CoR.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

/**
 * The "There was an error during the update" page.
 */
var gErrorsPage = {
  /**
   * Initialize
   */
  onPageShow() {
    gUpdates.setButtons(null, null, "okButton", true);
    gUpdates.wiz.getButton("finish").focus();

    var statusText = gUpdates.update.statusText;
    LOG("gErrorsPage", "onPageShow - update.statusText: " + statusText);

    var errorReason = document.getElementById("errorReason");
    errorReason.value = statusText;
    var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
    var errorLinkLabel = document.getElementById("errorLinkLabel");
    errorLinkLabel.value = manualURL;
    errorLinkLabel.setAttribute("url", manualURL);
  }
};

/**
 * The page shown when there is a background check or a certificate attribute
 * error.
 */
var gErrorExtraPage = {
  /**
   * Initialize
   */
  onPageShow() {
    gUpdates.setButtons(null, null, "okButton", true);
    gUpdates.wiz.getButton("finish").focus();

    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
      Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
    }

    document.getElementById("genericBackgroundErrorLabel").hidden = false;
    let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
    let errorLinkLabel = document.getElementById("errorExtraLinkLabel");
    errorLinkLabel.value = manualURL;
    errorLinkLabel.setAttribute("url", manualURL);
  }
};

/**
 * The "There was an error applying a partial patch" page.
 */
var gErrorPatchingPage = {
  /**
   * Initialize
   */
  onPageShow() {
    gUpdates.setButtons(null, null, "okButton", true);
  },

  onWizardNext() {
    switch (gUpdates.update.selectedPatch.state) {
      case STATE_APPLIED:
      case STATE_APPLIED_SERVICE:
        gUpdates.wiz.goTo("finished");
        break;
      case STATE_PENDING:
      case STATE_PENDING_SERVICE:
        let aus = CoC["@mozilla.org/updates/update-service;1"].
                  getService(CoI.nsIApplicationUpdateService);
        if (!aus.canStageUpdates) {
          gUpdates.wiz.goTo("finished");
          break;
        }
      // intentional fallthrough
      case STATE_DOWNLOADING:
        gUpdates.wiz.goTo("downloading");
        break;
      case STATE_DOWNLOAD_FAILED:
        gUpdates.wiz.goTo("errors");
        break;
    }
  }
};

/**
 * The "Update has been downloaded" page. Shows information about what
 * was downloaded.
 */
var gFinishedPage = {
  /**
   * Initialize
   */
  onPageShow() {
    let aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    if (aus.elevationRequired) {
      LOG("gFinishedPage", "elevationRequired");
      gUpdates.setButtons("restartLaterButton", "noThanksButton",
                          "restartNowButton", true);
    } else {
      LOG("gFinishedPage", "not elevationRequired");
      gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
                          true);
    }
    gUpdates.wiz.getButton("finish").focus();
  },

  /**
   * Initialize the Wizard Page for a Background Source Event
   */
  onPageShowBackground() {
    this.onPageShow();
    let updateFinishedName = document.getElementById("updateFinishedName");
    updateFinishedName.value = gUpdates.update.name;

    let link = document.getElementById("finishedBackgroundLink");
    if (gUpdates.update.detailsURL) {
      link.setAttribute("url", gUpdates.update.detailsURL);
      // The details link is stealing focus so it is disabled by default and
      // should only be enabled after onPageShow has been called.
      link.disabled = false;
    } else {
      link.hidden = true;
    }
    let aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    if (aus.elevationRequired) {
      let more = document.getElementById("finishedBackgroundMore");
      more.setAttribute("hidden", "true");
      let moreElevated =
        document.getElementById("finishedBackgroundMoreElevated");
      moreElevated.setAttribute("hidden", "false");
      let moreElevatedLink =
        document.getElementById("finishedBackgroundMoreElevatedLink");
      moreElevatedLink.setAttribute("hidden", "false");
      let moreElevatedLinkLabel =
        document.getElementById("finishedBackgroundMoreElevatedLinkLabel");
      let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
      moreElevatedLinkLabel.value = manualURL;
      moreElevatedLinkLabel.setAttribute("url", manualURL);
      moreElevatedLinkLabel.setAttribute("hidden", "false");
    }

    if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) {
      setTimeout(function() { gUpdates.wiz.getButton("finish").click(); },
                 UPDATE_TEST_LOOP_INTERVAL);
    }
  },

  /**
   * Called when the wizard finishes, i.e. the "Restart Now" button is
   * clicked.
   */
  onWizardFinish() {
    // Do the restart
    LOG("gFinishedPage", "onWizardFinish - restarting the application");

    let aus = CoC["@mozilla.org/updates/update-service;1"].
              getService(CoI.nsIApplicationUpdateService);
    if (aus.elevationRequired) {
      let um = CoC["@mozilla.org/updates/update-manager;1"].
               getService(CoI.nsIUpdateManager);
      if (um) {
        um.elevationOptedIn();
      }
    }

    // disable the "finish" (Restart) and "extra1" (Later) buttons
    // because the Software Update wizard is still up at the point,
    // and will remain up until we return and we close the
    // window with a |window.close()| in wizard.xml
    // (it was the firing the "wizardfinish" event that got us here.)
    // This prevents the user from switching back
    // to the Software Update dialog and clicking "Restart" or "Later"
    // when dealing with the "confirm close" prompts.
    // See bug #350299 for more details.
    gUpdates.wiz.getButton("finish").disabled = true;
    gUpdates.wiz.getButton("extra1").disabled = true;

    // Notify all windows that an application quit has been requested.
    var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"].
                     createInstance(CoI.nsISupportsPRBool);
    Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                 "restart");

    // Something aborted the quit process.
    if (cancelQuit.data)
      return;

    // If already in safe mode restart in safe mode (bug 327119)
    if (Services.appinfo.inSafeMode) {
      let env = CoC["@mozilla.org/process/environment;1"].
                getService(CoI.nsIEnvironment);
      env.set("MOZ_SAFE_MODE_RESTART", "1");
    }

    // Restart the application
    CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup).
    quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart);
  },

  /**
   * When the user clicks the "Restart Later" instead of the Restart Now" button
   * in the wizard after an update has been downloaded.
   */
  onExtra1() {
    gUpdates.wiz.cancel();
  },

  /**
   * When elevation is required and the user clicks "No Thanks" in the wizard.
   */
  async onExtra2() {
    Services.obs.notifyObservers(null, "update-canceled");
    let um = CoC["@mozilla.org/updates/update-manager;1"].
               getService(CoI.nsIUpdateManager);
    um.cleanupActiveUpdate();
    gUpdates.never();
    gUpdates.wiz.cancel();
  },
};

/**
 * Callback for the Update Prompt to set the current page if an Update Wizard
 * window is already found to be open.
 * @param   pageid
 *          The ID of the page to switch to
 */
function setCurrentPage(pageid) {
  gUpdates.wiz.currentPage = document.getElementById(pageid);
}
PK
!<


1chrome/toolkit/content/mozapps/update/updates.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings SYSTEM "chrome://mozapps/locale/update/updates.dtd">

<bindings id="updatesBindings"
          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="updateheader" extends="chrome://global/content/bindings/wizard.xml#wizard-header">
    <resources>
      <stylesheet src="chrome://global/skin/wizard.css"/>
    </resources>
    <content>
      <xul:hbox class="wizard-header update-header" flex="1">
        <xul:vbox class="wizard-header-box-1">
          <xul:vbox class="wizard-header-box-text">
            <xul:label class="wizard-header-label" xbl:inherits="value=label"/>
          </xul:vbox>
        </xul:vbox>
      </xul:hbox>
    </content>
  </binding>

  <binding id="update" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <content>
      <xul:hbox>
        <xul:label class="update-name" xbl:inherits="value=name" flex="1" crop="right"/>
        <xul:label xbl:inherits="href=detailsURL,hidden=hideDetailsURL" class="text-link"
                   value="&update.details.label;"/>
      </xul:hbox>
      <xul:label class="update-type" xbl:inherits="value=type"/>
      <xul:grid>
        <xul:columns>
          <xul:column class="update-label-column"/>
          <xul:column flex="1"/>
        </xul:columns>
        <xul:rows>
          <xul:row>
            <xul:label class="update-installedOn-label">&update.installedOn.label;</xul:label>
            <xul:label class="update-installedOn-value" xbl:inherits="value=installDate" flex="1" crop="right"/>
          </xul:row>
          <xul:row>
            <xul:label class="update-status-label">&update.status.label;</xul:label>
            <xul:description class="update-status-value" flex="1"/>
          </xul:row>
        </xul:rows>
      </xul:grid>
    </content>
    <implementation>
      <property name="name"
                onget="return this.getAttribute('name');"
                onset="this.setAttribute('name', val); return val;"/>
      <property name="detailsURL"
                onget="return this.getAttribute('detailsURL');"
                onset="this.setAttribute('detailsURL', val); return val;"/>
      <property name="installDate"
                onget="return this.getAttribute('installDate');"
                onset="this.setAttribute('installDate', val); return val;"/>
      <property name="type"
                onget="return this.getAttribute('type');"
                onset="this.setAttribute('type', val); return val;"/>
      <property name="hideDetailsURL"
                onget="return this.getAttribute('hideDetailsURL');"
                onset="this.setAttribute('hideDetailsURL', val); return val;"/>
      <property name="status"
                onget="return this.getAttribute('status');">
        <setter><![CDATA[
          this.setAttribute("status", val);
          var field = document.getAnonymousElementByAttribute(this, "class", "update-status-value");
          while (field.hasChildNodes())
            field.firstChild.remove();
          field.appendChild(document.createTextNode(val));
          return val;
        ]]></setter>
      </property>
    </implementation>
  </binding>
</bindings>
PK
!<
  1chrome/toolkit/content/mozapps/update/updates.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/content/update/updates.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/update/updates.css"?>

<!DOCTYPE wizard [
<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/update/updates.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%updateDTD;
%brandDTD;
]>

<wizard id="updates"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&updateWizard.title;"
        windowtype="Update:Wizard"
        style="width: auto; height: auto"
        onwizardfinish="gUpdates.onWizardFinish();"
        onwizardcancel="gUpdates.onWizardCancel();"
        onwizardnext="gUpdates.onWizardNext();"
        onload="gUpdates.onLoad();"
        onunload="gUpdates.onUnload();">

  <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
  <script type="application/javascript" src="chrome://mozapps/content/update/updates.js"/>

  <stringbundleset id="updateSet">
    <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
    <stringbundle id="updateStrings" src="chrome://mozapps/locale/update/updates.properties"/>
  </stringbundleset>

  <wizardpage id="dummy" pageid="dummy" firstpage="true"/>

  <wizardpage id="checking" pageid="checking" next="noupdatesfound"
              object="gCheckingPage" onpageshow="gCheckingPage.onPageShow();">
    <updateheader label="&checking.title;"/>
    <vbox class="update-content" flex="1">
      <label>&updateCheck.label;</label>
      <separator class="thin"/>
      <progressmeter id="checkingProgress" mode="undetermined"/>
    </vbox>
  </wizardpage>

  <wizardpage id="noupdatesfound" pageid="noupdatesfound"
              object="gNoUpdatesPage" onpageshow="gNoUpdatesPage.onPageShow();">
    <updateheader label="&noupdatesfound.title;"/>
    <vbox class="update-content" flex="1">
      <label id="noUpdatesAutoEnabled" hidden="true">&noupdatesautoenabled.intro;</label>
      <label id="noUpdatesAutoDisabled" hidden="true">&noupdatesautodisabled.intro;</label>
    </vbox>
  </wizardpage>

  <wizardpage id="manualUpdate" pageid="manualUpdate" object="gManualUpdatePage"
              onpageshow="gManualUpdatePage.onPageShow();">
    <updateheader label="&manualUpdate.title;"/>
    <vbox class="update-content" flex="1">
      <label id="manualUpdateDesc">&manualUpdate.desc;</label>
      <label id="manualUpdateSpaceDesc"
             hidden="true">&manualUpdate.space.desc;</label>
      <separator class="thin"/>
      <label>&manualUpdateGetMsg.label;</label>
      <hbox>
        <label class="text-link" id="manualUpdateLinkLabel" value=""
               onclick="openUpdateURL(event);"/>
      </hbox>
    </vbox>
  </wizardpage>

  <wizardpage id="unsupported" pageid="unsupported"
              object="gUnsupportedPage"
              onpageshow="gUnsupportedPage.onPageShow();">
    <updateheader label="&unsupported.title;"/>
    <vbox class="update-content" flex="1">
      <description flex="1">&unsupported.label;
        <label id="unsupportedLinkLabel" class="text-link inline-link" onclick="openUpdateURL(event);">
          &unsupportedLink.label;
        </label>
      </description>
    </vbox>
  </wizardpage>

  <wizardpage id="updatesfoundbasic" pageid="updatesfoundbasic"
              object="gUpdatesFoundBasicPage" next="downloading"
              onpageshow="gUpdatesFoundBasicPage.onPageShow();"
              onextra1="gUpdatesFoundBasicPage.onExtra1();"
              onextra2="gUpdatesFoundBasicPage.onExtra2();">
    <updateheader id="updatesFoundBasicHeader" label=""/>
    <vbox class="update-content" flex="1">
      <label id="updatesFoundInto"/>
      <separator class="thin"/>
      <label id="updateName" crop="right" value=""/>
      <separator id="updateNameSep" class="thin"/>
      <label id="upgradeEvangelism">&evangelism.desc;</label>
      <separator id="upgradeEvangelismSep" flex="1"/>
      <vbox flex="1">
        <hbox id="moreInfoURL">
          <label class="text-link" id="updateMoreInfoURL"
                 value="&clickHere.label;" onclick="openUpdateURL(event);"/>
        </hbox>
      </vbox>
    </vbox>
  </wizardpage>

  <wizardpage id="downloading" pageid="downloading"
              object="gDownloadingPage" onextra1="gDownloadingPage.onHide();"
              onpageshow="gDownloadingPage.onPageShow();">
    <updateheader label="&downloadPage.title;"/>
    <vbox class="update-content" flex="1">
      <hbox id="downloadStatusProgress">
        <progressmeter id="downloadProgress" mode="undetermined" flex="1"/>
        <button id="pauseButton" oncommand="gDownloadingPage.onPause();"
                paused="false"/>
      </hbox>
      <separator class="thin"/>
      <hbox id="downloadStatusLine">
        <label id="downloadStatus" flex="1">&connecting.label;</label>
      </hbox>
      <separator/>
      <hbox id="verificationFailed" align="start" hidden="true">
        <image id="verificationFailedIcon"/>
        <label flex="1">&verificationFailedText.label;</label>
      </hbox>
    </vbox>
  </wizardpage>

  <wizardpage id="errors" pageid="errors" object="gErrorsPage"
              onpageshow="gErrorsPage.onPageShow();">
    <updateheader label="&error.title;"/>
    <vbox class="update-content" flex="1">
      <label id="errorIntro">&error.label;</label>
      <separator/>
      <textbox class="plain" readonly="true" id="errorReason" multiline="true"
               rows="3"/>
      <separator/>
      <label id="errorManual">&errorManual.label;</label>
      <hbox>
        <label class="text-link" id="errorLinkLabel" value=""
               onclick="openUpdateURL(event);"/>
      </hbox>
    </vbox>
  </wizardpage>

  <wizardpage id="errorextra" pageid="errorextra"
              object="gErrorExtraPage"
              onpageshow="gErrorExtraPage.onPageShow();">
    <updateheader label="&error.title;"/>
    <vbox class="update-content" flex="1">
      <label id="bgErrorLabel">&genericBackgroundError.label;</label>
      <hbox>
        <label id="errorExtraLinkLabel" class="text-link"
               value="" onclick="openUpdateURL(event);"/>
      </hbox>
    </vbox>
  </wizardpage>

  <wizardpage id="errorpatching" pageid="errorpatching" next="downloading"
              object="gErrorPatchingPage"
              onpageshow="gErrorPatchingPage.onPageShow();">
    <updateheader label="&error.title;"/>
    <vbox class="update-content" flex="1">
      <label>&errorpatching.intro;</label>
    </vbox>
  </wizardpage>

  <wizardpage id="finished" pageid="finished" object="gFinishedPage"
              onpageshow="gFinishedPage.onPageShow();"
              onextra1="gFinishedPage.onExtra1()">
    <updateheader label="&finishedPage.title;"/>
    <vbox class="update-content" flex="1">
      <label>&finishedPage.text;</label>
    </vbox>
  </wizardpage>

  <wizardpage id="finishedBackground" pageid="finishedBackground"
              object="gFinishedPage" onextra1="gFinishedPage.onExtra1()"
              onextra2="gFinishedPage.onExtra2()"
              onpageshow="gFinishedPage.onPageShowBackground();">
    <updateheader label="&finishedPage.title;"/>
    <vbox class="update-content" flex="1">
      <label>&finishedBackgroundPage.text;</label>
      <separator/>
      <hbox align="center">
        <label>&finishedBackground.name;</label>
        <label id="updateFinishedName" flex="1" crop="right" value=""/>
        <label id="finishedBackgroundLink" class="text-link" disabled="true"
               value="&details.link;" onclick="openUpdateURL(event);"/>
      </hbox>
      <spacer flex="1"/>
      <label id="finishedBackgroundMore">&finishedBackground.more;</label>
      <label id="finishedBackgroundMoreElevated"
             hidden="true">&finishedBackground.moreElevated;</label>
      <label id="finishedBackgroundMoreElevatedLink"
             hidden="true">&errorManual.label;</label>
      <hbox>
        <label class="text-link" id="finishedBackgroundMoreElevatedLinkLabel"
               value="" onclick="openUpdateURL(event);" hidden="true"/>
      </hbox>
    </vbox>
  </wizardpage>

</wizard>
PK
!<|LL=chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

installitem {
  -moz-binding: url("chrome://mozapps/content/xpinstall/xpinstallItem.xml#installitem");
  display: -moz-box;
}
PK
!<#<chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.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/. */

var XPInstallConfirm = {};

XPInstallConfirm.init = function() {
  Components.utils.import("resource://gre/modules/AddonManager.jsm");

  var _installCountdown;
  var _installCountdownInterval;
  var _focused;
  var _timeout;

  // Default to cancelling the install when the window unloads
  XPInstallConfirm._installOK = false;

  var bundle = document.getElementById("xpinstallConfirmStrings");

  let args = window.arguments[0].wrappedJSObject;

  // If all installs have already been cancelled in some way then just close
  // the window
  if (args.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
    window.close();
    return;
  }

  var _installCountdownLength = 5;
  try {
    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
    var delay_in_milliseconds = prefs.getIntPref("security.dialog_enable_delay");
    _installCountdownLength = Math.round(delay_in_milliseconds / 500);
  } catch (ex) { }

  var itemList = document.getElementById("itemList");

  let installMap = new WeakMap();
  let installListener = {
    onDownloadCancelled(install) {
      itemList.removeChild(installMap.get(install));
      if (--numItemsToInstall == 0)
        window.close();
    }
  };

  var numItemsToInstall = args.installs.length;
  for (let install of args.installs) {
    var installItem = document.createElement("installitem");
    itemList.appendChild(installItem);

    installItem.name = install.addon.name;
    installItem.url = install.sourceURI.spec;
    var icon = install.iconURL;
    if (icon)
      installItem.icon = icon;
    var type = install.type;
    if (type)
      installItem.type = type;
    if (install.certName) {
      installItem.cert = bundle.getFormattedString("signed", [install.certName]);
    } else {
      installItem.cert = bundle.getString("unverified");
    }
    installItem.signed = install.certName ? "true" : "false";

    installMap.set(install.wrapped, installItem);
    install.addListener(installListener);
  }

  var introString = bundle.getString("itemWarnIntroSingle");
  if (numItemsToInstall > 4)
    introString = bundle.getFormattedString("itemWarnIntroMultiple", [numItemsToInstall]);
  var textNode = document.createTextNode(introString);
  var introNode = document.getElementById("itemWarningIntro");
  while (introNode.hasChildNodes())
    introNode.firstChild.remove();
  introNode.appendChild(textNode);

  var okButton = document.documentElement.getButton("accept");
  okButton.focus();

  function okButtonCountdown() {
    _installCountdown -= 1;

    if (_installCountdown < 1) {
      okButton.label = bundle.getString("installButtonLabel");
      okButton.disabled = false;
      clearInterval(_installCountdownInterval);
    } else
      okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
  }

  function myfocus() {
    // Clear the timeout if it exists so only the last one will be used.
    if (_timeout)
      clearTimeout(_timeout);

    // Use setTimeout since the last focus or blur event to fire is the one we
    // want
    _timeout = setTimeout(setWidgetsAfterFocus, 0);
  }

  function setWidgetsAfterFocus() {
    if (_focused)
      return;

    _installCountdown = _installCountdownLength;
    _installCountdownInterval = setInterval(okButtonCountdown, 500);
    okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
    _focused = true;
  }

  function myblur() {
    // Clear the timeout if it exists so only the last one will be used.
    if (_timeout)
      clearTimeout(_timeout);

    // Use setTimeout since the last focus or blur event to fire is the one we
    // want
    _timeout = setTimeout(setWidgetsAfterBlur, 0);
  }

  function setWidgetsAfterBlur() {
    if (!_focused)
      return;

    // Set _installCountdown to the inital value set in setWidgetsAfterFocus
    // plus 1 so when the window is focused there is immediate ui feedback.
    _installCountdown = _installCountdownLength + 1;
    okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
    okButton.disabled = true;
    clearInterval(_installCountdownInterval);
    _focused = false;
  }

  function myUnload() {
    if (_installCountdownLength > 0) {
      document.removeEventListener("focus", myfocus, true);
      document.removeEventListener("blur", myblur, true);
    }
    window.removeEventListener("unload", myUnload);

    for (let install of args.installs)
      install.removeListener(installListener);

    // Now perform the desired action - either install the
    // addons or cancel the installations
    if (XPInstallConfirm._installOK) {
      for (let install of args.installs)
        install.install();
    } else {
      for (let install of args.installs) {
        if (install.state != AddonManager.STATE_CANCELLED)
          install.cancel();
      }
    }
  }

  window.addEventListener("unload", myUnload);

  if (_installCountdownLength > 0) {
    document.addEventListener("focus", myfocus, true);
    document.addEventListener("blur", myblur, true);

    okButton.disabled = true;
    setWidgetsAfterFocus();
  } else
    okButton.label = bundle.getString("installButtonLabel");
}

XPInstallConfirm.onOK = function() {
  Components.classes["@mozilla.org/base/telemetry;1"].
    getService(Components.interfaces.nsITelemetry).
    getHistogramById("SECURITY_UI").
    add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
  // Perform the install or cancel after the window has unloaded
  XPInstallConfirm._installOK = true;
  return true;
}

XPInstallConfirm.onCancel = function() {
  // Perform the install or cancel after the window has unloaded
  XPInstallConfirm._installOK = false;
  return true;
}
PK
!<Xt@@=chrome/toolkit/content/mozapps/xpinstall/xpinstallConfirm.xul<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://mozapps/content/xpinstall/xpinstallConfirm.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css" type="text/css"?>

<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">

<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="xpinstallConfirm" title="&dialog.title;" style="&dialog.style;" 
        windowtype="Addons:Install"
        onload="XPInstallConfirm.init()" 
        ondialogaccept="return XPInstallConfirm.onOK();"
        ondialogcancel="return XPInstallConfirm.onCancel();">

  <script src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js" type="application/javascript"/>

  <stringbundle id="xpinstallConfirmStrings" 
                src="chrome://mozapps/locale/xpinstall/xpinstallConfirm.properties"/>

  <vbox flex="1" id="dialogContentBox">
    <hbox id="xpinstallheader" align="start">
      <image class="alert-icon"/>
      <vbox flex="1">
        <description class="warning">&warningPrimary.label;</description>
        <description>&warningSecondary.label;</description>
      </vbox>
    </hbox>
    <label id="itemWarningIntro"/>
    <vbox id="itemList" class="listbox" flex="1" style="overflow: auto;"/>
  </vbox>

</dialog>
PK
!<\		:chrome/toolkit/content/mozapps/xpinstall/xpinstallItem.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE bindings SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">

<bindings id="xpinstallItemBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="installitem">
    <resources>
      <stylesheet src="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css"/>
    </resources>
    <content>
      <xul:hbox flex="1">
        <xul:vbox align="center" pack="center" class="xpinstallIconContainer">
          <xul:image class="xpinstallItemIcon" xbl:inherits="src=icon"/>
        </xul:vbox>
        <xul:vbox flex="1" pack="center">
          <xul:hbox class="xpinstallItemNameRow" align="center">
            <xul:label class="xpinstallItemName" xbl:inherits="value=name" crop="right"/>
            <xul:label class="xpinstallItemSigned" xbl:inherits="value=cert,signed"/>
          </xul:hbox>
          <xul:hbox class="xpinstallItemDetailsRow" align="center">
            <xul:textbox class="xpinstallItemURL" xbl:inherits="value=url" flex="1" readonly="true" crop="right"/>
          </xul:hbox>
        </xul:vbox> 
      </xul:hbox>
    </content>
    <implementation>
      <property name="name"   onset="this.setAttribute('name', val); return val;"
                              onget="return this.getAttribute('name');"/>
      <property name="cert"   onset="this.setAttribute('cert', val); return val;"
                              onget="return this.getAttribute('cert');"/>
      <property name="signed" onset="this.setAttribute('signed', val); return val;"
                              onget="return this.getAttribute('signed');"/>
      <property name="url"    onset="this.setAttribute('url', val); return val;"
                              onget="return this.getAttribute('url');"/>
      <property name="icon"   onset="this.setAttribute('icon', val); return val;"
                              onget="return this.getAttribute('icon');"/>
      <property name="type"   onset="this.setAttribute('type', val); return val;"
                              onget="return this.getAttribute('type');"/>
    </implementation>
  </binding>
         
</bindings>

PK
!<8^^5chrome/toolkit/content/passwordmgr/passwordManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** * =================== SAVED SIGNONS CODE =================== ***/
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                  "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                  "resource://gre/modules/PlacesUtils.jsm");

let kSignonBundle;

// Default value for signon table sorting
let lastSignonSortColumn = "hostname";
let lastSignonSortAscending = true;

let showingPasswords = false;

// password-manager lists
let signons = [];
let deletedSignons = [];

// Elements that would be used frequently
let filterField;
let togglePasswordsButton;
let signonsIntro;
let removeButton;
let removeAllButton;
let signonsTree;

let signonReloadDisplay = {
  observe(subject, topic, data) {
    if (topic == "passwordmgr-storage-changed") {
      switch (data) {
        case "addLogin":
        case "modifyLogin":
        case "removeLogin":
        case "removeAllLogins":
          if (!signonsTree) {
            return;
          }
          signons.length = 0;
          LoadSignons();
          // apply the filter if needed
          if (filterField && filterField.value != "") {
            FilterPasswords();
          }
          signonsTree.treeBoxObject.ensureRowIsVisible(signonsTree.view.selection.currentIndex);
          break;
      }
      Services.obs.notifyObservers(null, "passwordmgr-dialog-updated");
    }
  }
};

// Formatter for localization.
let dateFormatter = Services.intl.createDateTimeFormat(undefined,
                      { dateStyle: "medium" });
let dateAndTimeFormatter = Services.intl.createDateTimeFormat(undefined,
                             { dateStyle: "medium",
                               timeStyle: "short" });

function Startup() {
  // be prepared to reload the display if anything changes
  Services.obs.addObserver(signonReloadDisplay, "passwordmgr-storage-changed");

  signonsTree = document.getElementById("signonsTree");
  kSignonBundle = document.getElementById("signonBundle");
  filterField = document.getElementById("filter");
  togglePasswordsButton = document.getElementById("togglePasswords");
  signonsIntro = document.getElementById("signonsIntro");
  removeButton = document.getElementById("removeSignon");
  removeAllButton = document.getElementById("removeAllSignons");

  togglePasswordsButton.label = kSignonBundle.getString("showPasswords");
  togglePasswordsButton.accessKey = kSignonBundle.getString("showPasswordsAccessKey");
  signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
  removeAllButton.setAttribute("label", kSignonBundle.getString("removeAll.label"));
  removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAll.accesskey"));
  document.getElementsByTagName("treecols")[0].addEventListener("click", (event) => {
    let { target, button } = event;
    let sortField = target.getAttribute("data-field-name");

    if (target.nodeName != "treecol" || button != 0 || !sortField) {
      return;
    }

    SignonColumnSort(sortField);
    Services.telemetry.getKeyedHistogramById("PWMGR_MANAGE_SORTED").add(sortField);
  });

  LoadSignons();

  // filter the table if requested by caller
  if (window.arguments &&
      window.arguments[0] &&
      window.arguments[0].filterString) {
    setFilter(window.arguments[0].filterString);
    Services.telemetry.getHistogramById("PWMGR_MANAGE_OPENED").add(1);
  } else {
    Services.telemetry.getHistogramById("PWMGR_MANAGE_OPENED").add(0);
  }

  FocusFilterBox();
}

function Shutdown() {
  Services.obs.removeObserver(signonReloadDisplay, "passwordmgr-storage-changed");
}

function setFilter(aFilterString) {
  filterField.value = aFilterString;
  FilterPasswords();
}

let signonsTreeView = {
  // Keep track of which favicons we've fetched or started fetching.
  // Maps a login origin to a favicon URL.
  _faviconMap: new Map(),
  _filterSet: [],
  // Coalesce invalidations to avoid repeated flickering.
  _invalidateTask: new DeferredTask(() => {
    signonsTree.treeBoxObject.invalidateColumn(signonsTree.columns.siteCol);
  }, 10),
  _lastSelectedRanges: [],
  selection: null,

  rowCount: 0,
  setTree(tree) {},
  getImageSrc(row, column) {
    if (column.element.getAttribute("id") !== "siteCol") {
      return "";
    }

    const signon = GetVisibleLogins()[row];

    // We already have the favicon URL or we started to fetch (value is null).
    if (this._faviconMap.has(signon.hostname)) {
      return this._faviconMap.get(signon.hostname);
    }

    // Record the fact that we already starting fetching a favicon for this
    // origin in order to avoid multiple requests for the same origin.
    this._faviconMap.set(signon.hostname, null);

    PlacesUtils.promiseFaviconLinkUrl(signon.hostname)
      .then(faviconURI => {
        this._faviconMap.set(signon.hostname, faviconURI.spec);
        this._invalidateTask.arm();
      }).catch(Cu.reportError);

    return "";
  },
  getProgressMode(row, column) {},
  getCellValue(row, column) {},
  getCellText(row, column) {
    let time;
    let signon = GetVisibleLogins()[row];
    switch (column.id) {
      case "siteCol":
        return signon.httpRealm ?
               (signon.hostname + " (" + signon.httpRealm + ")") :
               signon.hostname;
      case "userCol":
        return signon.username || "";
      case "passwordCol":
        return signon.password || "";
      case "timeCreatedCol":
        time = new Date(signon.timeCreated);
        return dateFormatter.format(time);
      case "timeLastUsedCol":
        time = new Date(signon.timeLastUsed);
        return dateAndTimeFormatter.format(time);
      case "timePasswordChangedCol":
        time = new Date(signon.timePasswordChanged);
        return dateFormatter.format(time);
      case "timesUsedCol":
        return signon.timesUsed;
      default:
        return "";
    }
  },
  isEditable(row, col) {
    if (col.id == "userCol" || col.id == "passwordCol") {
      return true;
    }
    return false;
  },
  isSeparator(index) { return false; },
  isSorted() { return false; },
  isContainer(index) { return false; },
  cycleHeader(column) {},
  getRowProperties(row) { return ""; },
  getColumnProperties(column) { return ""; },
  getCellProperties(row, column) {
    if (column.element.getAttribute("id") == "siteCol")
      return "ltr";

    return "";
  },
  setCellText(row, col, value) {
    let table = GetVisibleLogins();
    function _editLogin(field) {
      if (value == table[row][field]) {
        return;
      }
      let existingLogin = table[row].clone();
      table[row][field] = value;
      table[row].timePasswordChanged = Date.now();
      Services.logins.modifyLogin(existingLogin, table[row]);
      signonsTree.treeBoxObject.invalidateRow(row);
    }

    if (col.id == "userCol") {
     _editLogin("username");

    } else if (col.id == "passwordCol") {
      if (!value) {
        return;
      }
      _editLogin("password");
    }
  },
};

function SortTree(column, ascending) {
  let table = GetVisibleLogins();
  // remember which item was selected so we can restore it after the sort
  let selections = GetTreeSelections();
  let selectedNumber = selections.length ? table[selections[0]].number : -1;

  function compareFunc(a, b) {
    let valA, valB;
    switch (column) {
      case "hostname":
        let realmA = a.httpRealm;
        let realmB = b.httpRealm;
        realmA = realmA == null ? "" : realmA.toLowerCase();
        realmB = realmB == null ? "" : realmB.toLowerCase();

        valA = a[column].toLowerCase() + realmA;
        valB = b[column].toLowerCase() + realmB;
        break;
      case "username":
      case "password":
        valA = a[column].toLowerCase();
        valB = b[column].toLowerCase();
        break;

      default:
        valA = a[column];
        valB = b[column];
    }

    if (valA < valB)
      return -1;
    if (valA > valB)
      return 1;
    return 0;
  }

  // do the sort
  table.sort(compareFunc);
  if (!ascending) {
    table.reverse();
  }

  // restore the selection
  let selectedRow = -1;
  if (selectedNumber >= 0 && false) {
    for (let s = 0; s < table.length; s++) {
      if (table[s].number == selectedNumber) {
        // update selection
        // note: we need to deselect before reselecting in order to trigger ...Selected()
        signonsTree.view.selection.select(-1);
        signonsTree.view.selection.select(s);
        selectedRow = s;
        break;
      }
    }
  }

  // display the results
  signonsTree.treeBoxObject.invalidate();
  if (selectedRow >= 0) {
    signonsTree.treeBoxObject.ensureRowIsVisible(selectedRow);
  }
}

function LoadSignons() {
  // loads signons into table
  try {
    signons = Services.logins.getAllLogins();
  } catch (e) {
    signons = [];
  }
  signons.forEach(login => login.QueryInterface(Ci.nsILoginMetaInfo));
  signonsTreeView.rowCount = signons.length;

  // sort and display the table
  signonsTree.view = signonsTreeView;
  // The sort column didn't change. SortTree (called by
  // SignonColumnSort) assumes we want to toggle the sort
  // direction but here we don't so we have to trick it
  lastSignonSortAscending = !lastSignonSortAscending;
  SignonColumnSort(lastSignonSortColumn);

  // disable "remove all signons" button if there are no signons
  if (signons.length == 0) {
    removeAllButton.setAttribute("disabled", "true");
    togglePasswordsButton.setAttribute("disabled", "true");
  } else {
    removeAllButton.removeAttribute("disabled");
    togglePasswordsButton.removeAttribute("disabled");
  }

  return true;
}

function GetVisibleLogins() {
  return signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons;
}

function GetTreeSelections() {
  let selections = [];
  let select = signonsTree.view.selection;
  if (select) {
    let count = select.getRangeCount();
    let min = {};
    let max = {};
    for (let i = 0; i < count; i++) {
      select.getRangeAt(i, min, max);
      for (let k = min.value; k <= max.value; k++) {
        if (k != -1) {
          selections[selections.length] = k;
        }
      }
    }
  }
  return selections;
}

function SignonSelected() {
  let selections = GetTreeSelections();
  if (selections.length) {
    removeButton.removeAttribute("disabled");
  } else {
    removeButton.setAttribute("disabled", true);
  }
}

function DeleteSignon() {
  let syncNeeded = (signonsTreeView._filterSet.length != 0);
  let tree = signonsTree;
  let view = signonsTreeView;
  let table = GetVisibleLogins();

  // Turn off tree selection notifications during the deletion
  tree.view.selection.selectEventsSuppressed = true;

  // remove selected items from list (by setting them to null) and place in deleted list
  let selections = GetTreeSelections();
  for (let s = selections.length - 1; s >= 0; s--) {
    let i = selections[s];
    deletedSignons.push(table[i]);
    table[i] = null;
  }

  // collapse list by removing all the null entries
  for (let j = 0; j < table.length; j++) {
    if (table[j] == null) {
      let k = j;
      while ((k < table.length) && (table[k] == null)) {
        k++;
      }
      table.splice(j, k - j);
      view.rowCount -= k - j;
      tree.treeBoxObject.rowCountChanged(j, j - k);
    }
  }

  // update selection and/or buttons
  if (table.length) {
    // update selection
    let nextSelection = (selections[0] < table.length) ? selections[0] : table.length - 1;
    tree.view.selection.select(nextSelection);
  } else {
    // disable buttons
    removeButton.setAttribute("disabled", "true");
    removeAllButton.setAttribute("disabled", "true");
  }
  tree.view.selection.selectEventsSuppressed = false;
  FinalizeSignonDeletions(syncNeeded);
}

function DeleteAllSignons() {
  let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                           .getService(Ci.nsIPromptService);

  // Confirm the user wants to remove all passwords
  let dummy = { value: false };
  if (prompter.confirmEx(window,
                         kSignonBundle.getString("removeAllPasswordsTitle"),
                         kSignonBundle.getString("removeAllPasswordsPrompt"),
                         prompter.STD_YES_NO_BUTTONS + prompter.BUTTON_POS_1_DEFAULT,
                         null, null, null, null, dummy) == 1) // 1 == "No" button
    return;

  let syncNeeded = signonsTreeView._filterSet.length != 0;
  let view = signonsTreeView;
  let table = GetVisibleLogins();

  // remove all items from table and place in deleted table
  for (let i = 0; i < table.length; i++) {
    deletedSignons.push(table[i]);
  }
  table.length = 0;

  // clear out selections
  view.selection.select(-1);

  // update the tree view and notify the tree
  view.rowCount = 0;

  let box = signonsTree.treeBoxObject;
  box.rowCountChanged(0, -deletedSignons.length);
  box.invalidate();

  // disable buttons
  removeButton.setAttribute("disabled", "true");
  removeAllButton.setAttribute("disabled", "true");
  FinalizeSignonDeletions(syncNeeded);
  Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED_ALL").add(1);
}

function TogglePasswordVisible() {
  if (showingPasswords || masterPasswordLogin(AskUserShowPasswords)) {
    showingPasswords = !showingPasswords;
    togglePasswordsButton.label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
    togglePasswordsButton.accessKey = kSignonBundle.getString(showingPasswords ? "hidePasswordsAccessKey" : "showPasswordsAccessKey");
    document.getElementById("passwordCol").hidden = !showingPasswords;
    FilterPasswords();
  }

  // Notify observers that the password visibility toggling is
  // completed.  (Mostly useful for tests)
  Services.obs.notifyObservers(null, "passwordmgr-password-toggle-complete");
  Services.telemetry.getHistogramById("PWMGR_MANAGE_VISIBILITY_TOGGLED").add(showingPasswords);
}

function AskUserShowPasswords() {
  let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
  let dummy = { value: false };

  // Confirm the user wants to display passwords
  return prompter.confirmEx(window,
          null,
          kSignonBundle.getString("noMasterPasswordPrompt"), prompter.STD_YES_NO_BUTTONS,
          null, null, null, null, dummy) == 0;    // 0=="Yes" button
}

function FinalizeSignonDeletions(syncNeeded) {
  for (let s = 0; s < deletedSignons.length; s++) {
    Services.logins.removeLogin(deletedSignons[s]);
    Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED").add(1);
  }
  // If the deletion has been performed in a filtered view, reflect the deletion in the unfiltered table.
  // See bug 405389.
  if (syncNeeded) {
    try {
      signons = Services.logins.getAllLogins();
    } catch (e) {
      signons = [];
    }
  }
  deletedSignons.length = 0;
}

function HandleSignonKeyPress(e) {
  // If editing is currently performed, don't do anything.
  if (signonsTree.getAttribute("editing")) {
    return;
  }
  if (e.keyCode == KeyboardEvent.DOM_VK_DELETE ||
      (AppConstants.platform == "macosx" &&
       e.keyCode == KeyboardEvent.DOM_VK_BACK_SPACE)) {
    DeleteSignon();
    e.preventDefault();
  }
}

function getColumnByName(column) {
  switch (column) {
    case "hostname":
      return document.getElementById("siteCol");
    case "username":
      return document.getElementById("userCol");
    case "password":
      return document.getElementById("passwordCol");
    case "timeCreated":
      return document.getElementById("timeCreatedCol");
    case "timeLastUsed":
      return document.getElementById("timeLastUsedCol");
    case "timePasswordChanged":
      return document.getElementById("timePasswordChangedCol");
    case "timesUsed":
      return document.getElementById("timesUsedCol");
  }
  return undefined;
}

function SignonColumnSort(column) {
  let sortedCol = getColumnByName(column);
  let lastSortedCol = getColumnByName(lastSignonSortColumn);

  // clear out the sortDirection attribute on the old column
  lastSortedCol.removeAttribute("sortDirection");

  // determine if sort is to be ascending or descending
  lastSignonSortAscending = (column == lastSignonSortColumn) ? !lastSignonSortAscending : true;

  // sort
  lastSignonSortColumn = column;
  SortTree(lastSignonSortColumn, lastSignonSortAscending);

  // set the sortDirection attribute to get the styling going
  // first we need to get the right element
  sortedCol.setAttribute("sortDirection", lastSignonSortAscending ?
                                          "ascending" : "descending");
}

function SignonClearFilter() {
  let singleSelection = (signonsTreeView.selection.count == 1);

  // Clear the Tree Display
  signonsTreeView.rowCount = 0;
  signonsTree.treeBoxObject.rowCountChanged(0, -signonsTreeView._filterSet.length);
  signonsTreeView._filterSet = [];

  // Just reload the list to make sure deletions are respected
  LoadSignons();

  // Restore selection
  if (singleSelection) {
    signonsTreeView.selection.clearSelection();
    for (let i = 0; i < signonsTreeView._lastSelectedRanges.length; ++i) {
      let range = signonsTreeView._lastSelectedRanges[i];
      signonsTreeView.selection.rangedSelect(range.min, range.max, true);
    }
  } else {
    signonsTreeView.selection.select(0);
  }
  signonsTreeView._lastSelectedRanges = [];

  signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
  removeAllButton.setAttribute("label", kSignonBundle.getString("removeAll.label"));
  removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAll.accesskey"));
}

function FocusFilterBox() {
  if (filterField.getAttribute("focused") != "true") {
    filterField.focus();
  }
}

function SignonMatchesFilter(aSignon, aFilterValue) {
  if (aSignon.hostname.toLowerCase().indexOf(aFilterValue) != -1)
    return true;
  if (aSignon.username &&
      aSignon.username.toLowerCase().indexOf(aFilterValue) != -1)
    return true;
  if (aSignon.httpRealm &&
      aSignon.httpRealm.toLowerCase().indexOf(aFilterValue) != -1)
    return true;
  if (showingPasswords && aSignon.password &&
      aSignon.password.toLowerCase().indexOf(aFilterValue) != -1)
    return true;

  return false;
}

function _filterPasswords(aFilterValue, view) {
  aFilterValue = aFilterValue.toLowerCase();
  return signons.filter(s => SignonMatchesFilter(s, aFilterValue));
}

function SignonSaveState() {
  // Save selection
  let seln = signonsTreeView.selection;
  signonsTreeView._lastSelectedRanges = [];
  let rangeCount = seln.getRangeCount();
  for (let i = 0; i < rangeCount; ++i) {
    let min = {}; let max = {};
    seln.getRangeAt(i, min, max);
    signonsTreeView._lastSelectedRanges.push({ min: min.value, max: max.value });
  }
}

function FilterPasswords() {
  if (filterField.value == "") {
    SignonClearFilter();
    return;
  }

  let newFilterSet = _filterPasswords(filterField.value, signonsTreeView);
  if (!signonsTreeView._filterSet.length) {
    // Save Display Info for the Non-Filtered mode when we first
    // enter Filtered mode.
    SignonSaveState();
  }
  signonsTreeView._filterSet = newFilterSet;

  // Clear the display
  let oldRowCount = signonsTreeView.rowCount;
  signonsTreeView.rowCount = 0;
  signonsTree.treeBoxObject.rowCountChanged(0, -oldRowCount);
  // Set up the filtered display
  signonsTreeView.rowCount = signonsTreeView._filterSet.length;
  signonsTree.treeBoxObject.rowCountChanged(0, signonsTreeView.rowCount);

  // if the view is not empty then select the first item
  if (signonsTreeView.rowCount > 0)
    signonsTreeView.selection.select(0);

  signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionFiltered");
  removeAllButton.setAttribute("label", kSignonBundle.getString("removeAllShown.label"));
  removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAllShown.accesskey"));
}

function CopyPassword() {
  // Don't copy passwords if we aren't already showing the passwords & a master
  // password hasn't been entered.
  if (!showingPasswords && !masterPasswordLogin())
    return;
  // Copy selected signon's password to clipboard
  let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                  getService(Ci.nsIClipboardHelper);
  let row = signonsTree.currentIndex;
  let password = signonsTreeView.getCellText(row, {id: "passwordCol" });
  clipboard.copyString(password);
  Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_PASSWORD").add(1);
}

function CopyUsername() {
  // Copy selected signon's username to clipboard
  let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                  getService(Ci.nsIClipboardHelper);
  let row = signonsTree.currentIndex;
  let username = signonsTreeView.getCellText(row, {id: "userCol" });
  clipboard.copyString(username);
  Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_USERNAME").add(1);
}

function EditCellInSelectedRow(columnName) {
  let row = signonsTree.currentIndex;
  let columnElement = getColumnByName(columnName);
  signonsTree.startEditing(row, signonsTree.columns.getColumnFor(columnElement));
}

function UpdateContextMenu() {
  let singleSelection = (signonsTreeView.selection.count == 1);
  let menuItems = new Map();
  let menupopup = document.getElementById("signonsTreeContextMenu");
  for (let menuItem of menupopup.querySelectorAll("menuitem")) {
    menuItems.set(menuItem.id, menuItem);
  }

  if (!singleSelection) {
    for (let menuItem of menuItems.values()) {
      menuItem.setAttribute("disabled", "true");
    }
    return;
  }

  let selectedRow = signonsTree.currentIndex;

  // Disable "Copy Username" if the username is empty.
  if (signonsTreeView.getCellText(selectedRow, { id: "userCol" }) != "") {
    menuItems.get("context-copyusername").removeAttribute("disabled");
  } else {
    menuItems.get("context-copyusername").setAttribute("disabled", "true");
  }

  menuItems.get("context-editusername").removeAttribute("disabled");
  menuItems.get("context-copypassword").removeAttribute("disabled");

  // Disable "Edit Password" if the password column isn't showing.
  if (!document.getElementById("passwordCol").hidden) {
    menuItems.get("context-editpassword").removeAttribute("disabled");
  } else {
    menuItems.get("context-editpassword").setAttribute("disabled", "true");
  }
}

function masterPasswordLogin(noPasswordCallback) {
  // This doesn't harm if passwords are not encrypted
  let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"]
                    .createInstance(Ci.nsIPK11TokenDB);
  let token = tokendb.getInternalKeyToken();

  // If there is no master password, still give the user a chance to opt-out of displaying passwords
  if (token.checkPassword(""))
    return noPasswordCallback ? noPasswordCallback() : true;

  // So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
  try {
    // Relogin and ask for the master password.
    token.login(true);  // 'true' means always prompt for token password. User will be prompted until
                        // clicking 'Cancel' or entering the correct password.
  } catch (e) {
    // An exception will be thrown if the user cancels the login prompt dialog.
    // User is also logged out of Software Security Device.
  }

  return token.isLoggedIn();
}

function escapeKeyHandler() {
  // If editing is currently performed, don't do anything.
  if (signonsTree.getAttribute("editing")) {
    return;
  }
  window.close();
}

function OpenMigrator() {
  const { MigrationUtils } = Cu.import("resource:///modules/MigrationUtils.jsm", {});
  // We pass in the type of source we're using for use in telemetry:
  MigrationUtils.showMigrationWizard(window, [MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS]);
}
PK
!<KBB6chrome/toolkit/content/passwordmgr/passwordManager.xul<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil -*- -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/passwordmgr.css" type="text/css"?>

<!DOCTYPE dialog SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd" >

<window id="SignonViewerDialog"
        windowtype="Toolkit:PasswordManager"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        onload="Startup();"
        onunload="Shutdown();"
        title="&savedLogins.title;"
        style="width: 45em;"
        persist="width height screenX screenY">

  <script type="application/javascript" src="chrome://passwordmgr/content/passwordManager.js"/>

  <stringbundle id="signonBundle"
                src="chrome://passwordmgr/locale/passwordmgr.properties"/>

  <keyset>
    <key keycode="VK_ESCAPE" oncommand="escapeKeyHandler();"/>
    <key key="&windowClose.key;" modifiers="accel" oncommand="escapeKeyHandler();"/>
    <key key="&focusSearch1.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
    <key key="&focusSearch2.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
  </keyset>

  <popupset id="signonsTreeContextSet">
    <menupopup id="signonsTreeContextMenu"
               onpopupshowing="UpdateContextMenu()">
      <menuitem id="context-copyusername"
                label="&copyUsernameCmd.label;"
                accesskey="&copyUsernameCmd.accesskey;"
                oncommand="CopyUsername()"/>
      <menuitem id="context-editusername"
                label="&editUsernameCmd.label;"
                accesskey="&editUsernameCmd.accesskey;"
                oncommand="EditCellInSelectedRow('username')"/>
      <menuseparator/>
      <menuitem id="context-copypassword"
                label="&copyPasswordCmd.label;"
                accesskey="&copyPasswordCmd.accesskey;"
                oncommand="CopyPassword()"/>
      <menuitem id="context-editpassword"
                label="&editPasswordCmd.label;"
                accesskey="&editPasswordCmd.accesskey;"
                oncommand="EditCellInSelectedRow('password')"/>
    </menupopup>
  </popupset>

  <!-- saved signons -->
  <vbox id="savedsignons" class="contentPane" flex="1">
    <!-- filter -->
    <hbox align="center">
      <textbox id="filter" flex="1" type="search"
               aria-controls="signonsTree"
               oncommand="FilterPasswords();"
               placeholder="&searchFilter.label;"
               accesskey="&searchFilter.accesskey;"/>
    </hbox>

    <label control="signonsTree" id="signonsIntro"/>
    <separator class="thin"/>
    <tree id="signonsTree" flex="1"
          width="750"
          style="height: 20em;"
          onkeypress="HandleSignonKeyPress(event)"
          onselect="SignonSelected();"
          editable="true"
          context="signonsTreeContextMenu">
      <treecols>
        <treecol id="siteCol" label="&treehead.site.label;" flex="40"
                 data-field-name="hostname" persist="width"
                 ignoreincolumnpicker="true"
                 sortDirection="ascending"/>
        <splitter class="tree-splitter"/>
        <treecol id="userCol" label="&treehead.username.label;" flex="25"
                 ignoreincolumnpicker="true"
                 data-field-name="username" persist="width"/>
        <splitter class="tree-splitter"/>
        <treecol id="passwordCol" label="&treehead.password.label;" flex="15"
                 ignoreincolumnpicker="true"
                 data-field-name="password" persist="width"
                 hidden="true"/>
        <splitter class="tree-splitter"/>
        <treecol id="timeCreatedCol" label="&treehead.timeCreated.label;" flex="10"
                 data-field-name="timeCreated" persist="width hidden"
                 hidden="true"/>
        <splitter class="tree-splitter"/>
        <treecol id="timeLastUsedCol" label="&treehead.timeLastUsed.label;" flex="20"
                 data-field-name="timeLastUsed" persist="width hidden"
                 hidden="true"/>
        <splitter class="tree-splitter"/>
        <treecol id="timePasswordChangedCol" label="&treehead.timePasswordChanged.label;" flex="10"
                 data-field-name="timePasswordChanged" persist="width hidden"/>
        <splitter class="tree-splitter"/>
        <treecol id="timesUsedCol" label="&treehead.timesUsed.label;" flex="1"
                 data-field-name="timesUsed" persist="width hidden"
                 hidden="true"/>
        <splitter class="tree-splitter"/>
      </treecols>
      <treechildren/>
    </tree>
    <separator class="thin"/>
    <hbox id="SignonViewerButtons">
      <button id="removeSignon" disabled="true" icon="remove"
              label="&remove.label;" accesskey="&remove.accesskey;"
              oncommand="DeleteSignon();"/>
      <button id="removeAllSignons" icon="clear"
              oncommand="DeleteAllSignons();"/>
      <spacer flex="1"/>
      <button accesskey="&import.accesskey;"
              label="&import.label;"
              oncommand="OpenMigrator();"/>
      <button id="togglePasswords"
              oncommand="TogglePasswordVisible();"/>
    </hbox>
  </vbox>
  <hbox align="end">
    <hbox class="actionButtons" flex="1">
      <spacer flex="1"/>
      <button oncommand="close();" icon="close"
              label="&closebutton.label;" accesskey="&closebutton.accesskey;"/>
    </hbox>
  </hbox>
</window>
PK
!<Ď/chrome/toolkit/content/passwordmgr/recipes.json{
  "siteRecipes": [
    {
      "description": "okta uses a hidden password field to disable filling",
      "hosts": ["mozilla.okta.com"],
      "passwordSelector": "#pass-signin"
    },
    {
      "description": "anthem uses a hidden password and username field to disable filling",
      "hosts": ["www.anthem.com"],
      "passwordSelector": "#LoginContent_txtLoginPass"
    },
    {
      "description": "An ephemeral password-shim field is incorrectly selected as the username field.",
      "hosts": ["www.discover.com"],
      "usernameSelector": "#login-account"
    },
    {
      "description": "Tibia uses type=password for its username field and puts the email address before the password field during registration",
      "hosts": ["secure.tibia.com"],
      "usernameSelector": "#accountname, input[name='loginname']",
      "passwordSelector": "#password1, input[name='loginpassword']",
      "pathRegex": "^\/account\/"
    },
    {
      "description": "Username field will be incorrectly captured in the change password form (bug 1243722)",
      "hosts": ["www.facebook.com"],
      "notUsernameSelector": "#password_strength"
    },
    {
      "description": "United uses a useless password field plus one per frequent flyer number during checkin. Don't save any of them (Bug 1330810)",
      "hosts": ["www.united.com"],
      "notPasswordSelector": "input[type='password']",
      "pathRegex": "^\/travel\/checkin\/changefqtv.aspx"
    },
    {
      "description": "Gogo In-Flight uses a password field for credit card numbers on the same page as login",
      "hosts": ["buy.gogoinflight.com"],
      "notPasswordSelector": "#cardNumber"
    }
  ]
}
PK
!<Q-chrome/toolkit/content/preferences.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

#### Cloud Storage Downloads

# NOTE (saveFilesToCloudStorage): %S is replaced by a service name (Dropbox, Google Drive)
# String below is hardcoded for en-US and not localized
saveFilesToCloudStorage=Save files to %S
PK
!<w!%chrome/toolkit/content/providers.json{
  "Dropbox": {
    "displayName": "Dropbox",
    "relativeDownloadPath": ["homeDir", "Dropbox"],
    "relativeDiscoveryPath": {
      "linux": ["homeDir", ".dropbox", "info.json"],
      "macosx": ["homeDir", ".dropbox", "info.json"],
      "win": ["LocalAppData", "Dropbox", "info.json"]
    },
    "typeSpecificData": {
      "default": "Downloads",
      "screenshot": "Screenshots"
    }
  },

  "GDrive": {
    "displayName": "Google Drive",
    "relativeDownloadPath": ["homeDir", "Google Drive"],
    "relativeDiscoveryPath": {
      "macosx": ["homeDir", "Library", "Application Support", "Google", "Drive"],
      "win": ["LocalAppData", "Google", "Drive"]
    },
    "typeSpecificData": {
      "default": "Downloads"
    }
  }
}
PK
!<;FǛ

4chrome/toolkit/content/satchel/formSubmitListener.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

(function() {
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/PrivateBrowsingUtils.jsm");

let satchelFormListener = {
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIFormSubmitObserver,
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference,
  ]),

  debug: true,
  enabled: true,

  init() {
    Services.obs.addObserver(this, "earlyformsubmit");
    Services.obs.addObserver(this, "xpcom-shutdown");
    Services.prefs.addObserver("browser.formfill.", this);
    this.updatePrefs();
  },

  updatePrefs() {
    this.debug          = Services.prefs.getBoolPref("browser.formfill.debug");
    this.enabled        = Services.prefs.getBoolPref("browser.formfill.enable");
  },

  // Implements the Luhn checksum algorithm as described at
  // http://wikipedia.org/wiki/Luhn_algorithm
  isValidCCNumber(ccNumber) {
    // Remove dashes and whitespace
    ccNumber = ccNumber.replace(/[\-\s]/g, "");

    let len = ccNumber.length;
    if (len != 9 && len != 15 && len != 16) {
      return false;
    }

    if (!/^\d+$/.test(ccNumber)) {
      return false;
    }

    let total = 0;
    for (let i = 0; i < len; i++) {
      let ch = parseInt(ccNumber[len - i - 1], 10);
      if (i % 2 == 1) {
        // Double it, add digits together if > 10
        ch *= 2;
        if (ch > 9) {
          ch -= 9;
        }
      }
      total += ch;
    }
    return total % 10 == 0;
  },

  log(message) {
    if (!this.debug) {
      return;
    }
    dump("satchelFormListener: " + message + "\n");
    Services.console.logStringMessage("satchelFormListener: " + message);
  },

  /* ---- nsIObserver interface ---- */

  observe(subject, topic, data) {
    if (topic == "nsPref:changed") {
      this.updatePrefs();
    } else if (topic == "xpcom-shutdown") {
      Services.obs.removeObserver(this, "earlyformsubmit");
      Services.obs.removeObserver(this, "xpcom-shutdown");
      Services.prefs.removeObserver("browser.formfill.", this);
    } else {
      this.log("Oops! Unexpected notification: " + topic);
    }
  },

  /* ---- nsIFormSubmitObserver interfaces ---- */

  notify(form, domWin, actionURI, cancelSubmit) {
    try {
      if (!this.enabled || PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
        return;
      }

      this.log("Form submit observer notified.");

      if (form.hasAttribute("autocomplete") &&
        form.getAttribute("autocomplete").toLowerCase() == "off") {
        return;
      }

      let entries = [];
      for (let i = 0; i < form.elements.length; i++) {
        let input = form.elements[i];
        if (!(input instanceof Ci.nsIDOMHTMLInputElement)) {
          continue;
        }

        // Only use inputs that hold text values (not including type="password")
        if (!input.mozIsTextField(true)) {
          continue;
        }

        // Bug 394612: If Login Manager marked this input, don't save it.
        // The login manager will deal with remembering it.

        // Don't save values when autocomplete=off is present.
        if (input.hasAttribute("autocomplete") &&
          input.getAttribute("autocomplete").toLowerCase() == "off") {
          continue;
        }

        let value = input.value.trim();

        // Don't save empty or unchanged values.
        if (!value || value == input.defaultValue.trim()) {
          continue;
        }

        // Don't save credit card numbers.
        if (this.isValidCCNumber(value)) {
          this.log("skipping saving a credit card number");
          continue;
        }

        let name = input.name || input.id;
        if (!name) {
          continue;
        }

        if (name == "searchbar-history") {
          this.log('addEntry for input name "' + name + '" is denied');
          continue;
        }

        // Limit stored data to 200 characters.
        if (name.length > 200 || value.length > 200) {
          this.log("skipping input that has a name/value too large");
          continue;
        }

        // Limit number of fields stored per form.
        if (entries.length >= 100) {
          this.log("not saving any more entries for this form.");
          break;
        }

        entries.push({ name, value });
      }

      if (entries.length) {
        this.log("sending entries to parent process for form " + form.id);
        sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
      }
    } catch (e) {
      this.log("notify failed: " + e);
    }
  },
};

satchelFormListener.init();
})();
PK
!<||2chrome/toolkit/content/xbl-marquee/xbl-marquee.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* PRINT ONLY rules */
@media print {

  marquee > * > * { 
    margin: 0 !important; 
    padding: 0 !important;
  } /* This hack is needed until bug 119078 gets fixed */
}
PK
!<^^2chrome/toolkit/content/xbl-marquee/xbl-marquee.xml<?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="marqueeBindings"
          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="marquee" bindToUntrustedContent="true">

    <resources>
      <stylesheet src="chrome://xbl-marquee/content/xbl-marquee.css"/>
    </resources>
    <implementation>

      <property name="scrollAmount" exposeToUntrustedContent="true">
        <getter>
          <![CDATA[
          this._mutationActor(this._mutationObserver.takeRecords());
          return this._scrollAmount;
          ]]>
        </getter>
        <setter>
          <![CDATA[
          var val = parseInt(val);
          if (val < 0) {
            return;
          }
          if (isNaN(val)) {
            val = 0;
          }
          this.setAttribute("scrollamount", val);
          ]]>
        </setter>
      </property>

      <property name="scrollDelay" exposeToUntrustedContent="true">
        <getter>
          <![CDATA[
          this._mutationActor(this._mutationObserver.takeRecords());
          var val = parseInt(this.getAttribute("scrolldelay"));

          if (val <= 0 || isNaN(val)) {
            return this._scrollDelay;
          }

          return val;
          ]]>
        </getter>
        <setter>
          var val = parseInt(val);
          if (val > 0 ) {
            this.setAttribute("scrolldelay", val);
          }
        </setter>
      </property>

      <property name="trueSpeed" exposeToUntrustedContent="true">
        <getter>
          <![CDATA[
          if (!this.hasAttribute("truespeed")) {
            return false;
          }

          return true;
          ]]>
        </getter>
        <setter>
          <![CDATA[
          if (val) {
            this.setAttribute("truespeed", "");
          } else {
            this.removeAttribute('truespeed');
          }
          ]]>
        </setter>
      </property>

      <property name="direction" exposeToUntrustedContent="true">
        <getter>
          this._mutationActor(this._mutationObserver.takeRecords());
          return this._direction;
        </getter>
        <setter>
          <![CDATA[
          if (typeof val == 'string') {
            val = val.toLowerCase();
          } else {
            return;
          }
          if (val != 'left' && val != 'right' && val != 'up' && val != 'down') {
            val = 'left';
          }

          this.setAttribute("direction", val);
          ]]>
        </setter>
      </property>

      <property name="behavior" exposeToUntrustedContent="true">
        <getter>
          this._mutationActor(this._mutationObserver.takeRecords());
          return this._behavior;
        </getter>
        <setter>
          if (typeof val == 'string') {
            val = val.toLowerCase();
          }
          if (val == "alternate" || val == "slide" || val == 'scroll') {
            this.setAttribute("behavior", val);
          }
        </setter>
      </property>


      <property name="loop" exposeToUntrustedContent="true">
        <getter>
          <![CDATA[
          this._mutationActor(this._mutationObserver.takeRecords());
          return this._loop;
          ]]>
        </getter>
        <setter>
          <![CDATA[
          var val = parseInt(val);
          if (val == -1 || val > 0) {
            this.setAttribute("loop", val);
          }
          ]]>
        </setter>
      </property>


      <property name="onstart" exposeToUntrustedContent="true">
        <getter>
          return this.getAttribute("onstart");
        </getter>
        <setter>
          this._setEventListener("start", val, true);
          this.setAttribute("onstart", val);
        </setter>
      </property>

      <property name="onfinish" exposeToUntrustedContent="true">
        <getter>
          return this.getAttribute("onfinish");
        </getter>
        <setter>
          this._setEventListener("finish", val, true);
          this.setAttribute("onfinish", val);
        </setter>
      </property>

      <property name="onbounce" exposeToUntrustedContent="true">
        <getter>
          return this.getAttribute("onbounce");
        </getter>
        <setter>
          this._setEventListener("bounce", val, true);
          this.setAttribute("onbounce", val);
        </setter>
      </property>

      <property name="outerDiv"
        onget="return document.getAnonymousNodes(this)[0]"
      />

      <property name="innerDiv"
        onget="return document.getAnonymousElementByAttribute(this, 'class', 'innerDiv');"
      />

      <property name="height" exposeToUntrustedContent="true"
        onget="return this.getAttribute('height');"
        onset="this.setAttribute('height', val);"
      />

      <property name="width" exposeToUntrustedContent="true"
        onget="return this.getAttribute('width');"
        onset="this.setAttribute('width', val);"
      />

      <method name="_set_scrollDelay">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          aValue = parseInt(aValue);
          if (aValue <= 0) {
            return;
          } else if (isNaN(aValue)) {
            this._scrollDelay = 85;
          } else if (aValue < 60) {
            if (this.trueSpeed == true) {
              this._scrollDelay = aValue;
            } else {
              this._scrollDelay = 60;
            }
          } else {
            this._scrollDelay = aValue;
          }
        ]]>
        </body>
      </method>

      <method name="_set_scrollAmount">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          aValue = parseInt(aValue);
          if (isNaN(aValue)) {
            this._scrollAmount = 6;
          } else if (aValue < 0) {
            return;
          } else {
            this._scrollAmount = aValue;
          }
        ]]>
        </body>
      </method>

      <method name="_set_behavior">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (typeof aValue == 'string') {
            aValue = aValue.toLowerCase();
          }
          if (aValue != 'alternate' && aValue != 'slide' && aValue != 'scroll') {
            this._behavior = 'scroll';
          } else {
            this._behavior = aValue;
          }
        ]]>
        </body>
      </method>

      <method name="_set_direction">
        <parameter name="aValue"/>
        <body>
        <![CDATA[
          if (typeof aValue == 'string') {
            aValue = aValue.toLowerCase();
          }
          if (aValue != 'left' && aValue != 'right' && aValue != 'up' && aValue != 'down') {
            aValue = 'left';
          }

          if (aValue != this._direction) {
            this.startNewDirection = true;
          }
          this._direction = aValue;
        ]]>
        </body>
      </method>

      <method name="_set_loop">
        <parameter name="aValue"/>
        <body>
          <![CDATA[
          var aValue = parseInt(aValue);
          if (aValue == 0) {
            return;
          }
          if (isNaN(aValue) || aValue <= -1) {
            aValue = -1;
          }
          this._loop = aValue;
          ]]>
        </body>
      </method>

      <method name="_setEventListener">
        <parameter name="aName"/>
        <parameter name="aValue"/>
        <parameter name="aIgnoreNextCall"/>
        <body>
          <![CDATA[
          // _setEventListener is only used for setting the attribute event
          // handlers, which we want to ignore if our document is sandboxed
          // without the allow-scripts keyword.
          if (document.hasScriptsBlockedBySandbox) {
            return true;
          }

          // attribute event handlers should only be added if the
          // document's CSP allows it.
          if (!document.inlineScriptAllowedByCSP) {
            return true;
          }

          if (this._ignoreNextCall) {
            return this._ignoreNextCall = false;
          }

          if (aIgnoreNextCall) {
            this._ignoreNextCall = true;
          }

          if (typeof this["_on" + aName] == 'function') {
            this.removeEventListener(aName, this["_on" + aName]);
          }

          switch (typeof aValue)
          {
            case "function":
              this["_on" + aName] = aValue;
              this.addEventListener(aName, this["_on" + aName]);
            break;

            case "string":
              if (!aIgnoreNextCall) {
                try {
                  // Function Xrays make this simple and safe. \o/
                  this["_on" + aName] = new window.Function("event", aValue);
                }
                catch(e) {
                  return false;
                }
                this.addEventListener(aName, this["_on" + aName]);
              }
              else {
                this["_on" + aName] = aValue;
              }
            break;

            case "object":
              this["_on" + aName] = aValue;
            break;

            default:
              this._ignoreNextCall = false;
              throw new Error("Invalid argument for Marquee::on" + aName);
          }
          return true;
          ]]>
        </body>
      </method>

      <method name="_fireEvent">
        <parameter name="aName"/>
        <parameter name="aBubbles"/>
        <parameter name="aCancelable"/>
        <body>
        <![CDATA[
          var e = document.createEvent("Events");
          e.initEvent(aName, aBubbles, aCancelable);
          this.dispatchEvent(e);
        ]]>
        </body>
      </method>

      <method name="start" exposeToUntrustedContent="true">
        <body>
        <![CDATA[
          if (this.runId == 0) {
            var myThis = this;
            var lambda = function myTimeOutFunction(){myThis._doMove(false);}
            this.runId = window.setTimeout(lambda, this._scrollDelay - this._deltaStartStop);
            this._deltaStartStop = 0;
          }
        ]]>
        </body>
      </method>

      <method name="stop" exposeToUntrustedContent="true">
        <body>
        <![CDATA[
          if (this.runId != 0) {
            this._deltaStartStop = Date.now()- this._lastMoveDate;
            clearTimeout(this.runId);
          }

          this.runId = 0;
        ]]>
        </body>
      </method>

      <method name="_doMove">
        <parameter name="aResetPosition"/>
        <body>
        <![CDATA[
          this._lastMoveDate = Date.now();

          //startNewDirection is true at first load and whenever the direction is changed
          if (this.startNewDirection) {
            this.startNewDirection = false; //we only want this to run once every scroll direction change

            var corrvalue = 0;

            switch (this._direction)
            {
              case "up":
                var height = document.defaultView.getComputedStyle(this).height;
                this.outerDiv.style.height = height;
                if (this.originalHeight > this.outerDiv.offsetHeight) {
                    corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
                }
                this.innerDiv.style.padding = height + " 0";
                this.dirsign = 1;
                this.startAt = (this._behavior == 'alternate') ? (this.originalHeight - corrvalue) : 0;
                this.stopAt  = (this._behavior == 'alternate' || this._behavior == 'slide') ? 
                                (parseInt(height) + corrvalue) : (this.originalHeight + parseInt(height));
              break;

              case "down":
                var height = document.defaultView.getComputedStyle(this).height;
                this.outerDiv.style.height = height;
                if (this.originalHeight > this.outerDiv.offsetHeight) {
                    corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
                }
                this.innerDiv.style.padding = height + " 0";
                this.dirsign = -1;
                this.startAt  = (this._behavior == 'alternate') ?
                                (parseInt(height) + corrvalue) : (this.originalHeight + parseInt(height));
                this.stopAt = (this._behavior == 'alternate' || this._behavior == 'slide') ? 
                              (this.originalHeight - corrvalue) : 0;
              break;

              case "right":
                if (this.innerDiv.offsetWidth > this.outerDiv.offsetWidth) {
                    corrvalue = this.innerDiv.offsetWidth - this.outerDiv.offsetWidth;
                }
                this.dirsign = -1;
                this.stopAt  = (this._behavior == 'alternate' || this._behavior == 'slide') ? 
                               (this.innerDiv.offsetWidth - corrvalue) : 0;
                this.startAt = this.outerDiv.offsetWidth + ((this._behavior == 'alternate') ? 
                               corrvalue : (this.innerDiv.offsetWidth + this.stopAt));   
              break;

              case "left":
              default:
                if (this.innerDiv.offsetWidth > this.outerDiv.offsetWidth) {
                    corrvalue = this.innerDiv.offsetWidth - this.outerDiv.offsetWidth;
                }
                this.dirsign = 1;
                this.startAt = (this._behavior == 'alternate') ? (this.innerDiv.offsetWidth - corrvalue) : 0;
                this.stopAt  = this.outerDiv.offsetWidth + 
                               ((this._behavior == 'alternate' || this._behavior == 'slide') ? 
                               corrvalue : (this.innerDiv.offsetWidth + this.startAt));
            }

            if (aResetPosition) {
              this.newPosition = this.startAt;
              this._fireEvent("start", false, false);
            }
          } //end if

          this.newPosition = this.newPosition + (this.dirsign * this._scrollAmount);

          if ((this.dirsign == 1 && this.newPosition > this.stopAt) ||
              (this.dirsign == -1 && this.newPosition < this.stopAt))
          {
            switch (this._behavior) 
            {
              case 'alternate':
                // lets start afresh
                this.startNewDirection = true;

                // swap direction
                const swap = {left: "right", down: "up", up: "down", right: "left"};
                this._direction = swap[this._direction];
                this.newPosition = this.stopAt;

                if ((this._direction == "up") || (this._direction == "down")) {
                  this.outerDiv.scrollTop = this.newPosition;
                } else {
                  this.outerDiv.scrollLeft = this.newPosition;
                }

                if (this._loop != 1) {
                  this._fireEvent("bounce", false, true);
                }
              break;

              case 'slide':
                if (this._loop > 1) {
                  this.newPosition = this.startAt;
                }
              break;

              default:
                this.newPosition = this.startAt;

                if ((this._direction == "up") || (this._direction == "down")) {
                  this.outerDiv.scrollTop = this.newPosition;
                } else {
                  this.outerDiv.scrollLeft = this.newPosition;
                }

                //dispatch start event, even when this._loop == 1, comp. with IE6
                this._fireEvent("start", false, false);
            }

            if (this._loop > 1) {
              this._loop--;
            } else if (this._loop == 1) {
              if ((this._direction == "up") || (this._direction == "down")) {
                this.outerDiv.scrollTop = this.stopAt;
              } else {
                this.outerDiv.scrollLeft = this.stopAt;
              }
              this.stop();
              this._fireEvent("finish", false, true);
              return;
            }
          }
          else {
            if ((this._direction == "up") || (this._direction == "down")) {
              this.outerDiv.scrollTop = this.newPosition;
            } else {
              this.outerDiv.scrollLeft = this.newPosition;
            }
          }

          var myThis = this;
          var lambda = function myTimeOutFunction(){myThis._doMove(false);}
          this.runId = window.setTimeout(lambda, this._scrollDelay);
        ]]>
        </body>
      </method>

      <method name="init">
        <body>
        <![CDATA[
          this.stop();

          if ((this._direction != "up") && (this._direction != "down")) {
            var width = window.getComputedStyle(this).width;
            this.innerDiv.parentNode.style.margin = '0 ' + width;

            //XXX Adding the margin sometimes causes the marquee to widen, 
            // see testcase from bug bug 364434: 
            // https://bugzilla.mozilla.org/attachment.cgi?id=249233
            // Just add a fixed width with current marquee's width for now
            if (width != window.getComputedStyle(this).width) {
              var width = window.getComputedStyle(this).width;
              this.outerDiv.style.width = width;
              this.innerDiv.parentNode.style.margin = '0 ' + width;
            }
          }
          else {
            // store the original height before we add padding
            this.innerDiv.style.padding = 0;
            this.originalHeight = this.innerDiv.offsetHeight;
          }

          this._doMove(true);
        ]]>
        </body>
      </method>

      <method name="_mutationActor">
        <parameter name="aMutations"/>
        <body>
        <![CDATA[
          while (aMutations.length > 0) {
            var mutation = aMutations.shift();
            var attrName = mutation.attributeName.toLowerCase();
            var oldValue = mutation.oldValue;
            var target = mutation.target;
            var newValue = target.getAttribute(attrName);

            if (oldValue != newValue) {
              switch (attrName) {
                case "loop":
                  target._set_loop(newValue);
                  if (target.rundId == 0) {
                    target.start();
                  }
                  break;
                case "scrollamount":
                  target._set_scrollAmount(newValue);
                  break;
                case "scrolldelay":
                  target._set_scrollDelay(newValue);
                  target.stop();
                  target.start();
                  break;
                case "truespeed":
                  //needed to update target._scrollDelay
                  var myThis = target;
                  var lambda = function() {myThis._set_scrollDelay(myThis.getAttribute('scrolldelay'));}
                  window.setTimeout(lambda, 0);
                  break;
                case "behavior":
                  target._set_behavior(newValue);
                  target.startNewDirection = true;
                  if ((oldValue == "slide" && target.newPosition == target.stopAt) ||
                      newValue == "alternate" || newValue == "slide") {
                    target.stop();
                    target._doMove(true);
                  }
                  break;
                case "direction":
                  if (!newValue) {
                    newValue = "left";
                  }
                  target._set_direction(newValue);
                  break;
                case "width":
                case "height":
                  target.startNewDirection = true;
                  break;
                case "onstart":
                  target._setEventListener("start", newValue);
                  break;
                case "onfinish":
                  target._setEventListener("finish", newValue);
                  break;
                case "onbounce":
                  target._setEventListener("bounce", newValue);
                  break;
              }
            }
          }
        ]]>
        </body>
      </method>

      <constructor>
        <![CDATA[
          // Set up state.
          this._scrollAmount = 6;
          this._scrollDelay = 85;
          this._direction = "left";
          this._behavior = "scroll";
          this._loop = -1;
          this.dirsign = 1;
          this.startAt = 0;
          this.stopAt = 0;
          this.newPosition = 0;
          this.runId = 0;
          this.originalHeight = 0;
          this.startNewDirection = true;

          // hack needed to fix js error, see bug 386470
          var myThis = this;
          var lambda = function myScopeFunction() { if (myThis.init) myThis.init(); }

          this._set_direction(this.getAttribute('direction'));
          this._set_behavior(this.getAttribute('behavior'));
          this._set_scrollDelay(this.getAttribute('scrolldelay'));
          this._set_scrollAmount(this.getAttribute('scrollamount'));
          this._set_loop(this.getAttribute('loop'));
          this._setEventListener("start", this.getAttribute("onstart"));
          this._setEventListener("finish", this.getAttribute("onfinish"));
          this._setEventListener("bounce", this.getAttribute("onbounce"));
          this.startNewDirection = true;

          this._mutationObserver = new MutationObserver(this._mutationActor);
          this._mutationObserver.observe(this, { attributes: true,
            attributeOldValue: true,
            attributeFilter: ['loop', 'scrollamount', 'scrolldelay', '', 'truespeed', 'behavior',
              'direction', 'width', 'height', 'onstart', 'onfinish', 'onbounce'] });

          // init needs to be run after the page has loaded in order to calculate
          // the correct height/width
          if (document.readyState == "complete") {
            lambda();
          } else {
            window.addEventListener("load", lambda);
          }
        ]]>
      </constructor>
    </implementation>

  </binding>

  <binding id="marquee-horizontal" bindToUntrustedContent="true"
           extends="chrome://xbl-marquee/content/xbl-marquee.xml#marquee"
           inheritstyle="false">

    <!-- White-space isn't allowed because a marquee could be 
         inside 'white-space: pre' -->
    <content>
      <html:div style="display: -moz-box; overflow: hidden; width: -moz-available;"
        ><html:div style="display: -moz-box;"
          ><html:div class="innerDiv" style="display: table; border-spacing: 0;"
            ><html:div
              ><children
            /></html:div
          ></html:div
        ></html:div
      ></html:div>
    </content>

  </binding>

  <binding id="marquee-vertical" bindToUntrustedContent="true"
           extends="chrome://xbl-marquee/content/xbl-marquee.xml#marquee"
           inheritstyle="false">

    <!-- White-space isn't allowed because a marquee could be 
         inside 'white-space: pre' -->
    <content>
      <html:div style="overflow: hidden; width: -moz-available;"
        ><html:div class="innerDiv"
          ><children
        /></html:div
      ></html:div>
    </content>

  </binding>

  <binding id="marquee-horizontal-editable" bindToUntrustedContent="true"
           inheritstyle="false">

    <!-- White-space isn't allowed because a marquee could be 
         inside 'white-space: pre' -->
    <content>
      <html:div style="display: inline-block; overflow: auto; width: -moz-available;"
        ><children
      /></html:div>
    </content>

  </binding>

  <binding id="marquee-vertical-editable" bindToUntrustedContent="true"
           inheritstyle="false">

    <!-- White-space isn't allowed because a marquee could be 
         inside 'white-space: pre' -->
    <content>
      <html:div style="overflow: auto; height: inherit; width: -moz-available;"
        ><children/></html:div>
    </content>

  </binding>

</bindings>
PK
!<;f$dd.chrome/toolkit/pluginproblem/pluginProblem.xml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings [
  <!ENTITY % pluginproblemDTD SYSTEM "chrome://pluginproblem/locale/pluginproblem.dtd">
  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
  %pluginproblemDTD;
  %globalDTD;
  %brandDTD;
]>

<bindings id="pluginBindings"
              xmlns="http://www.mozilla.org/xbl"
              xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
              xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="pluginProblem" inheritstyle="false" chromeOnlyContent="true" bindToUntrustedContent="true">
    <resources>
        <stylesheet src="chrome://pluginproblem/content/pluginProblemContent.css"/>
        <stylesheet src="chrome://mozapps/skin/plugins/pluginProblem.css"/>
    </resources>

    <content>
        <html:div class="mainBox" anonid="main" chromedir="&locale.dir;">
            <html:div class="hoverBox">
                <html:label>
                    <html:button class="icon" anonid="icon"/>
                    <html:div class="msg msgVulnerabilityStatus" anonid="vulnerabilityStatus"><!-- set at runtime --></html:div>
                    <html:div class="msg msgActivationWarning">&pluginActivationWarning;</html:div>
                    <html:div class="msg msgTapToPlay">&tapToPlayPlugin;</html:div>
                    <html:div class="msg msgClickToPlay" anonid="clickToPlay">&clickToActivatePlugin;</html:div>
                </html:label>

                <html:div class="msg msgBlocked">&blockedPlugin.label;</html:div>
                <html:div class="msg msgCrashed">
                    <html:div class="msgCrashedText" anonid="crashedText"><!-- set at runtime --></html:div>
                    <!-- link href set at runtime -->
                    <html:div class="msgReload">&reloadPlugin.pre;<html:a class="reloadLink" anonid="reloadLink" href="">&reloadPlugin.middle;</html:a>&reloadPlugin.post;</html:div>
                </html:div>

                <html:div class="msg msgManagePlugins"><html:a class="action-link" anonid="managePluginsLink" href="">&managePlugins;</html:a></html:div>
                <html:div class="submitStatus" anonid="submitStatus">
                    <html:div class="msg msgPleaseSubmit" anonid="pleaseSubmit">
                        <html:textarea class="submitComment"
                                       anonid="submitComment"
                                       placeholder="&report.comment;"/>
                        <html:div class="submitURLOptInBox">
                            <html:label><html:input class="submitURLOptIn" anonid="submitURLOptIn" type="checkbox"/> &report.pageURL;</html:label>
                        </html:div>
                        <html:div class="submitButtonBox">
                            <html:span class="helpIcon" anonid="helpIcon" role="link"/>
                            <html:input class="submitButton" type="button"
                                        anonid="submitButton"
                                        value="&report.please;"/>
                        </html:div>
                    </html:div>
                    <html:div class="msg msgSubmitting">&report.submitting;<html:span class="throbber"> </html:span></html:div>
                    <html:div class="msg msgSubmitted">&report.submitted;</html:div>
                    <html:div class="msg msgNotSubmitted">&report.disabled;</html:div>
                    <html:div class="msg msgSubmitFailed">&report.failed;</html:div>
                    <html:div class="msg msgNoCrashReport">&report.unavailable;</html:div>
                </html:div>
                <html:div class="msg msgCheckForUpdates"><html:a class="action-link" anonid="checkForUpdatesLink" href="">&checkForUpdates;</html:a></html:div>
            </html:div>
            <html:button class="closeIcon" anonid="closeIcon" title="&hidePluginBtn.label;"/>
        </html:div>
        <html:div style="display:none;"><children/></html:div>
    </content>
    <implementation>
      <constructor>
        // Notify browser-plugins.js that we were attached, on a delay because
        // this binding doesn't complete layout until the constructor
        // completes.
        this.dispatchEvent(new CustomEvent("PluginBindingAttached"));
      </constructor>
    </implementation>
</binding>

<binding id="replacement" extends="chrome://pluginproblem/content/pluginProblem.xml#pluginProblem" inheritstyle="false" chromeOnlyContent="true" bindToUntrustedContent="true">
  <implementation>
    <constructor>
      this.dispatchEvent(new CustomEvent("PluginPlaceholderReplaced"));
    </constructor>
    </implementation>
</binding>

</bindings>
PK
!<rݸN5chrome/toolkit/pluginproblem/pluginProblemBinding.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

embed:-moz-handler-blocked,
embed:-moz-handler-crashed,
embed:-moz-handler-clicktoplay,
embed:-moz-handler-vulnerable-updatable,
embed:-moz-handler-vulnerable-no-update,
applet:-moz-handler-blocked,
applet:-moz-handler-crashed,
applet:-moz-handler-clicktoplay,
applet:-moz-handler-vulnerable-updatable,
applet:-moz-handler-vulnerable-no-update,
object:-moz-handler-blocked,
object:-moz-handler-crashed,
object:-moz-handler-clicktoplay,
object:-moz-handler-vulnerable-updatable,
object:-moz-handler-vulnerable-no-update {
    display: inline-block;
    overflow: hidden;
    opacity: 1 !important;
    -moz-binding: url('chrome://pluginproblem/content/pluginProblem.xml#pluginProblem') !important;
}
PK
!</&&5chrome/toolkit/pluginproblem/pluginProblemContent.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace html url(http://www.w3.org/1999/xhtml);

/* Do not change this without also changing the appropriate line in
 * browser-plugins.js (near where this file is mentioned). */
html|object:not([width]), html|object[width=""],
html|embed:not([width]), html|embed[width=""],
html|applet:not([width]), html|applet[width=""] {
  width: 240px;
}

/* Do not change this without also changing the appropriate line in
 * browser-plugins.js (near where this file is mentioned). */
html|object:not([height]), html|object[height=""],
html|embed:not([height]), html|embed[height=""],
html|applet:not([height]), html|applet[height=""] {
  height: 200px;
}

a .mainBox,
:-moz-handler-clicktoplay .mainBox,
:-moz-handler-vulnerable-updatable .mainBox,
:-moz-handler-vulnerable-no-update .mainBox,
:-moz-handler-blocked .mainBox {
  -moz-user-focus: normal;
}
a .mainBox:focus,
:-moz-handler-clicktoplay .mainBox:focus,
:-moz-handler-vulnerable-updatable .mainBox:focus,
:-moz-handler-vulnerable-no-update .mainBox:focus,
:-moz-handler-blocked .mainBox:focus {
  outline: 1px dotted;
}

.mainBox {
  width: inherit;
  height: inherit;
  overflow: hidden;
  direction: ltr;
  unicode-bidi: embed;
  /* used to block inherited properties */
  text-transform: none;
  text-indent: 0;
  cursor: initial;
  white-space: initial;
  word-spacing: initial;
  letter-spacing: initial;
  line-height: initial;
}

/* Initialize the overlay with visibility:hidden to prevent flickering if
* the plugin is too small to show the overlay */
.mainBox > .hoverBox,
.mainBox > .closeIcon {
  visibility: hidden;
}

.visible > .hoverBox,
.visible > .closeIcon {
  visibility: visible;
}

.mainBox[chromedir="rtl"] {
  direction: rtl;
}

a .hoverBox,
:-moz-handler-clicktoplay .hoverBox,
:-moz-handler-vulnerable-updatable .hoverBox,
:-moz-handler-vulnerable-no-update .hoverBox {
  cursor: pointer;
}

.hoverBox > label {
  cursor: inherit;
}
.icon {
  cursor: inherit;
}

.msg {
  display: none;
}

a .msgClickToPlay,
:-moz-handler-clicktoplay .msgClickToPlay,
:-moz-handler-clicktoplay .msgActivationWarning,
:-moz-handler-vulnerable-updatable .msgVulnerabilityStatus,
:-moz-handler-vulnerable-updatable .msgCheckForUpdates,
:-moz-handler-vulnerable-updatable .msgClickToPlay,
:-moz-handler-vulnerable-no-update .msgVulnerabilityStatus,
:-moz-handler-vulnerable-no-update .msgClickToPlay,
:-moz-handler-blocked .msgBlocked,
:-moz-handler-crashed .msgCrashed {
  display: block;
}

.submitStatus[status] {
  display: -moz-box;
  -moz-box-align: center;
  -moz-box-pack: center;
  height: 160px;
}

.submitStatus[status="noReport"]   .msgNoCrashReport,
.submitStatus[status="please"]     .msgPleaseSubmit,
.submitStatus[status="noSubmit"]   .msgNotSubmitted,
.submitStatus[status="submitting"] .msgSubmitting,
.submitStatus[status="success"]    .msgSubmitted,
.submitStatus[status="failed"]     .msgSubmitFailed {
  display: block;
}
PK
!<Lދ5chrome/toolkit/pluginproblem/pluginReplaceBinding.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

a[href='https://get.adobe.com/flashplayer/'],
a[href='http://get.adobe.com/flashplayer/'],
a[href='https://get.adobe.com/flashplayer'],
a[href='http://get.adobe.com/flashplayer'],
a[href='https://www.adobe.com/go/getflash/'],
a[href='http://www.adobe.com/go/getflash/'] {
  display: inline-block;
  overflow: hidden;
  opacity: 1 !important;
  -moz-binding: url('chrome://pluginproblem/content/pluginProblem.xml#replacement') !important;
}
PK
!<^]@]@2chrome/toolkit/res/accessiblecaret-normal@1.5x.pngPNG


IHDR36jl	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:8A120CC26A0311E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:e8001ebd-0d83-432b-945b-3a54d0f8917b</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:6F9BA90A6A0311E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:6F9BA90B6A0311E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:8A120CC26A0311E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:e8001ebd-0d83-432b-945b-3a54d0f8917b</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:37+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:43:12+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:37+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:37+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>51</exif:PixelXDimension>
         <exif:PixelYDimension>54</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>k cHRMz%u0`:o_F5IDATxilTUe͛i3]YR)l

h-_0,$DD
Rٔn0-3-aƂJ;I^&y7ws=oAD:nkґYkz3c<|_ܱ`,3YVsHD6<&.,q0BO&)9g
r0ܩ+6%ELC WU-w҂JnϮ#g!i6n}ŗ	0cp/7}Vpg3s0d[ a:櫘3oƽkgY&ߴ`]S[kڀapYؚ5	=Y "~`zqۡFĥFk6Y"l]ۡFy{ݻYו#J7c:WxZaZ4;gy][QOGܿt0RYī/9z>#"2(컦냮$$gq	Vs#7YgJzd:O dѲg;fFQKͩ=ڋ1zm=.h	u&Ő9 #v,w]:΋5N[rC%F;+`I7󾷴~c$~2a^i[P,Z"@K'/V͙<ىfwsM"N9H\%DڲY<0
sͼW)dDSҒ+ٛ{{Gg
ѫԟ3"IQq+:Ð?)+f(iT#ne,C@-7+0jA_{F
ȌY|0}XO{_Ŝ2[melM(\W鈡\yJ`rVN)$I%dk,*a\w1ّ$ded91m1\[&e2+$lXBX)5'6mIˌHl#XPS3$9s,VCȈ6le,saȉ_~8J
+(Im\uL9EdTP`ώd
YyG>`?w{zVeu{Q㿵aUSXi>yJҽQooА6w&Vs䛚WVnbyRD\ADvͮU3:RH×o^Wf5=5@$57AU[}nu{+ȈvJp.ɨyeSn.]yj%+3L1e0L[|6Z+͵S&`Y<pR+t;Y lstn\WTa_|]{me{Cza^wLVJSЊˮREtLV8O,Hi?Q}dۇ4ŭ]aZ~=%e/)
ۡ@d77Phw\]D9R|]f}80"!M̎@r?uttttkRǤIENDB`PK
!<e]>]>0chrome/toolkit/res/accessiblecaret-normal@1x.pngPNG


IHDR"$	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:6F9BA9076A0311E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:ba933709-bea0-46bb-a598-d90adbee99f3</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:343E890DA9F911E4AAC4F4FB74504A4A</stRef:instanceID>
            <stRef:documentID>xmp.did:343E890EA9F911E4AAC4F4FB74504A4A</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:6F9BA9076A0311E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:ba933709-bea0-46bb-a598-d90adbee99f3</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:29+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:42:57+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:29+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:29+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>34</exif:PixelXDimension>
         <exif:PixelYDimension>36</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>?o cHRMz%u0`:o_F5IDATx[hUgn{&Ԛ(ЄxZ)R>B "[HB?ZYb
blU$n9gIۍMA?7ws87\HޠspdrKOu^PY?xnyG֬"GWK.oGr2[݃m%َ*ٖ=#=}rQy9!{6x:Jd5%*(Deay/<	h0CWcDos􎧳{fî[)QuYb"v:95S'EaaKs'Pڽ7Y7$W"߮f!B~@X4vtp%cErh+[;L~@<]J_?Jcb-(+\O<}csj9J![_yV"Zc%cC,E_8:k͟IExqfXxbǺ"˲~^Er{Fe
DmxCN ;3R8ܘ[5RXO5B)m"JkEd|W0ҟepbj~]Tip1NM%ba" e&WnAD4fMc͑AV_dp8U?;
 jp0pnf¹
ReE!&9/Y#44{#QQusL;rg/g}=>|hD9 (ZFjd05Ga؉\OLJj{pAPmiSf@t1{7t..ڝO{;{}Thx\@d
͚OL~Xf=
Tkp9mJD	,+?=?{ߩMP:I4\wQ` 21g/%S2$HP$Dė-
Y	Mtjow	IENDB`PK
!<V;<)C)C3chrome/toolkit/res/accessiblecaret-normal@2.25x.pngPNG


IHDRMQ+P	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:8A120CBE6A0311E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:2262f2df-b03c-4df8-ab13-c99474177631</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:343E890DA9F911E4AAC4F4FB74504A4A</stRef:instanceID>
            <stRef:documentID>xmp.did:343E890EA9F911E4AAC4F4FB74504A4A</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:8A120CBE6A0311E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:2262f2df-b03c-4df8-ab13-c99474177631</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:50+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:43:41+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:50+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:50+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>77</exif:PixelXDimension>
         <exif:PixelYDimension>81</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>& cHRMz%u0`:o_F	IDATx{pTǿ{nvl$$$@T&22"a@8*Ȁ0N,be|ᠥ8Ra(T@v?[!lw~gvvfw=wopah4$
?JӃe޲׺U&_KKsи\-27L_:pϫHpd(J6WUAr}=!+˷{*]HMcXƨz:!p[3bO0MI@LNz܀։$W;cÏDיIS
h)gbșz)ѝUྷfbn9rV΁N532]i@ e {ڂE+O:ϩT<&{-\lQ`+94uY{Vve}i}=Fڮ:E`̛'A|df!\ëSN8==g|`hgK	4;]1ʩ>hR.?ަY+JY\޼j?s'4kQW~|]*y􌞞	bz&d51X`(X𛕒
FA˚2wݛ:4{@pѻLY,vScrS?Zlkm$)gij8sNQfkEgF֚x	œ[2Qre?]=uRz8+G-yeǒ;F	ZhD\`1bf*@8*~Ah׊
Pkϸ9^抒H9hbZ;ۊ!.v鮪Ҝ?L	KcN> Ø9&W̹=hk,hz+hgκڥZޯt
KݔEmyV\1En-v`@ܙ7JG%F
smQR<5[sgZZy{Eiő#0>{pLBAȼo
%l QK6dg=UJtMВ^|{x|jM4[ɝECުR{Ƿ{}<;DШنi&$z4T@]$wv[0zp1tg
͡yf
b=+ȓ<S5f+cD
wUr&6`]"A
v ZTQ	4B.sH
jJ*YFZ\O9Pa
54f[	#E9 ys}4b e :ҥ
4BDB)&TPݪ
4u:k
2Mqp5!3]לdF.1g19I!jD =ҔDD	4剘Fk
Ih,	"")E*qO~q.pI:JrKH6I4ղ`(ړF`u |t߉_k
_}cQFjE(8%4ěEŎX7qS$bpݛ6Iҳg{϶~?sc!JMfě;	=@C܁~E;A榄P=|.rE9l\wDW
{%ݩKW'q"JWՆ+*
_̮% ͗;jjSMKb !͍M+jݳm#*^mæWׁ`mr[_ްe*M+6aM-WF-D_p8NH&l鵨!?Xו[@'bhƶw_\UkCo:Οi{AYJ
' 	b٣
^}
	
hovJ޼a5~т5*+x p[>\q>a/oeP`/8)s򓓝w&p{GUIj ׁ?'[8@RD4[
GTuVlw#YKgMLDk}}_޲W>}xD{z^оBj|E΁X

E'[t3l!T8S,
؅/~Cs:S\I+оky\4b18r@Pp.>Sdz :N鰨th*h4̀fȀf@3hh4Af3uIENDB`PK
!<i4&C&C0chrome/toolkit/res/accessiblecaret-normal@2x.pngPNG


IHDRDG	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:6F9BA90F6A0311E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:a802619f-23f5-447b-b2bc-68319975a910</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:343E890DA9F911E4AAC4F4FB74504A4A</stRef:instanceID>
            <stRef:documentID>xmp.did:343E890EA9F911E4AAC4F4FB74504A4A</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:6F9BA90F6A0311E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:a802619f-23f5-447b-b2bc-68319975a910</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:43+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:43:28+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:43+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:43+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>68</exif:PixelXDimension>
         <exif:PixelYDimension>71</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>|
 cHRMz%u0`:o_FIDATxylTǿ3ݵz}cVpIL
q -&PJJ	4iV OPۨRۨQQZP$5*
)&mzL[gH[k9	ŠEE b*_&dg@2?#ѺBB%ݎ6LLAgvjo:CMrfV|k5T/-<}򙵠VlY%B(0Lj`hr~a&@p3fgg` ʗmxTt
_4עuZm"Z(=s[g{ Z 3Mh4A#
+v=Ѐj3PvD%Sݔ+%^gppS)/qfMeɹɘg[kL`*7Q\Ս|TUAIWBM݂ݵkH`Z%'4W~J&m|eyEFj`َ͂lG#mݷ9u7BzB^ؚtv`TVlIFI%.=n?RC>iB(Zxؕ7DLUgj[uۮZd<p;6
Pl!xŪլʳX4<vI~EC
9fNX m.T?6ͯi#7w @Mf=w|5\voݿ.2Hb0Jwۯ}-R(<1Y@]v(~p%0w\%WӾ]]UܺOy(WeitZI/ ݻ=~ak*uǫ%_fPCu0uF>/X4Sͤ5;Pd\K-8iđ`kZx`hXy:j0oWLKDkukҭy9'.ar#1k[i9XZ+2${T^`:2 RyDT8D8hy5g'O-Tba`w54k'zT@0
Kj8	+9hAHչ
5Dl(XI,g@ 6Bp`A-aYBPdG@1/eAOyYӐL^Wј`prND/09㪢5l'f\"x,-<!T"iBH]j2e_Z$0*r/z"(4,:=#\ȲCb'HP!T,%xE.>@NĬCGڥ>b0v"]NaWɦ$N;2l H:שz{uZN]1сoQ}Df;"?lEk~!"z.B$#hH~mߣ)cI"T?⏡SG?,%bT2G?8tT߰Ɯ1-zpx_whqTJ,{zmA~}m)X$M@4~.wlz]{|ͅ
*}o\ԁ
)D?^{eZZ9@H:Įv\RS:@:c2pOXTgϪ҆橚\S295 :K?}Cr@G$U*T5EsqO$dTEt뚑PCp2s߱t9!cw([A( \YӏN^#@u(JR6W ɚb`5
Bs,[ă
L
%0_0:UP@ܙ8:y3֧kO'InXHq|dYQ'9l*ATqS|O'pN\;͠U!			P,wh/wJ:5Y<{/xH0B'"H(w|qe`D]x'1MI'=U=ZDo1=H)- `h7Iڑx&>trBrIŭW<P'yr8bIPݚN,32E E E E QRRRlIENDB`PK
!<Us\??5chrome/toolkit/res/accessiblecaret-tilt-left@1.5x.pngPNG


IHDR36jl	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:18BC63C66A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:d6f80577-f41c-4d2b-8e42-29e0bb8683a3</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:09AE9E646A0411E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:09AE9E656A0411E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:18BC63C66A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:d6f80577-f41c-4d2b-8e42-29e0bb8683a3</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:26:58+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:59:59+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:26:58+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:26:58+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>51</exif:PixelXDimension>
         <exif:PixelYDimension>54</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>|xQ cHRMz%u0`:o_FIDATx{Uǿ眙ܻ׽ꮻ&jXQJXRJ!Q L-
|JL7w5tMWҽ{ݻ=scXw"r00wf9p=5<{@6҄yŒ?1~
?Y1kAJqȒ ) Dp
e0|
0ͅߖ^B2Su7]z+	`KC@d]bW$CnFMIQ&b\ܧX0!`%ab`.SUS	.i`8@yRs
DA&r+]Srw"wz(tCASF
:nEo5tS19E+O]?{/$AʥVʦ30C:is_`t$UqJ[DF4Ro*wy0zC`R6 y`a7;.0ͫ?lmvFN3r0Qt݊Ϭ14J8tF.ynɬWY^yuu4;<+0٥U&YDR:kS\^-Mv`ȓp23{.])~SSGKgXtdph˪fs_x̼H2U<pZp~yQy+& %0ǫB<U&+#7kOF2tD}0aBMF>WʂTpoܾa΋C@sS4p箼`p1}0D
ʲȒY0?ơ,iR6" iH	.2R=FB2̄7"6ZlQTjmI49?ьFf$f{cL\v44V:-3<<Yis&A$7Oi:'&H~Q:Ž@t7愄Ax;qDtU[wl6x4}E67Iaj?ڨK0zؽ3u w{qѤE Ӵ"nH>@iѱ-_\@ %_s-o;3!O,xrɺ7dOtP\@E\w7۵@䲿q<-|gq̅s2{>cLh Ӱ~aOe#wyY9>&|=,ԩzpX<-
R eAe~$}QR5yy5w&θ?i=X:}>PtRv{*}Q0.0oOs5@@qÚRTJJLsb9VKu0w{v0LS)`
0[ÑC"IENDB`PK
!<S>>3chrome/toolkit/res/accessiblecaret-tilt-left@1x.pngPNG


IHDR"$	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:8A120CC66A0311E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:adc41794-f1fe-4e23-bb57-b336800fa125</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:343E8911A9F911E4AAC4F4FB74504A4A</stRef:instanceID>
            <stRef:documentID>xmp.did:343E8912A9F911E4AAC4F4FB74504A4A</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:8A120CC66A0311E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:adc41794-f1fe-4e23-bb57-b336800fa125</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:26:49+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:51:05+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:26:49+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:26:49+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>34</exif:PixelXDimension>
         <exif:PixelYDimension>36</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>.~ cHRMz%u0`:o_FIDATx]lUlwnvۭԒx# c|Dk $
(Ƥ&f"$J"'!Ą6ڤdhݝs
Xfw{2fw9ÈwCp%.5YGX1?ԛB6@"	-;:1a_DF->blso׻ϞpGDM T )=TTcj4j6rAHz Q	eGo#L$e9522YFmq-֒WMIXWO'FdT!wӍ#uzF|
Cd.cPQ0E	b~hB~SNz
b6';d.[L9,@ҫ|Me,sI zk:޺tLP-8t|}^~%#sH:ֲgIʼ/>T{mGhWi rY%_ wHM
Hݯ7m+䖏AMOvwwHn7B-ՔXZ#\Q;{Аu#@`J3c<ذR'dɱsmjUw@UpWDu@AQ%u8a)rLT8>X5tGi`Sa\t
}Z܉`ķ$<EɓNzj\0S7-L-ϋW>?1Ioz9=]<j#/`ƚàZ{lh,}lAʥE=+*y3mvn%@Tc``j'?to`HZ FV~~_wk5L(3$=H,'G/ř_~-N,DNn6lP1@L]yTL^3
`
S=}MB/BEbhRnspj_rgA7IENDB`PK
!<
yBB6chrome/toolkit/res/accessiblecaret-tilt-left@2.25x.pngPNG


IHDRMQ+P	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:18BC63C26A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:88277c75-457a-4852-ae83-b90c0afe678b</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:343E8911A9F911E4AAC4F4FB74504A4A</stRef:instanceID>
            <stRef:documentID>xmp.did:343E8912A9F911E4AAC4F4FB74504A4A</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:18BC63C26A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:88277c75-457a-4852-ae83-b90c0afe678b</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:10+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T16:00:28+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:10+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:10+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>77</exif:PixelXDimension>
         <exif:PixelYDimension>81</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>ޝ cHRMz%u0`:o_FIDATxylTsO{fmljrD۔!MEԄB=P[UhCՊ6?HIK$$ФC 
^>{3aC8Vy?i~38h}C1<UCK!wBKSTt5A4NxLVY$q-s@$>|
mI2uhOE?ihOv׌|0IyD2+w	#?<h1eЪ3`|8@U)5l-ͶP3	I&iZl!{L $ľ7$[`q( P @P(K $8
+yWOMTCc&Bn=MyK\fnH}eHg$}|qKc	cF^mWc]I!PDY4<yb*\Ct_*s޸ӊumFc`ƬTtǟS%M&$ni
L=9nJtKG/$l<ٳ`;0eeaևݳ\g@ϣ
^C{KDsnDEMό?ԉVCs7LVmRQvb)*m'/&mك~aCLj:%PԌ8p`t;ʳȰԯZlF{{Tq av]7	V[XlF;l;C0
mm9f4=C-4<G)sl2<Y )Zˏ}]ߴٱZ2aXVKC޻gk,U+vSf!-F|Gs22kk"E-b;g-2'|M8U}[gÎ&L3v,`>l9V:;ISCt?cL)*SyG@+#HԢyOD(IVUI-4)4J!HȞ2w`ޒw 
R,WC4PAILrCVLUىSkiLcn0Jk@H@ (NK#sir#eP!$
#B	4RA3,O<fZ"d@&탇fLdNHs|Vm'̃Ϸ
ueeǭDwZR@c0",^T˜TM6|6>g@`lFKww`C2Zp5,do9{6obxqb(n){#C;3Y_B {ld+$-׼o;!$	?ֵ3nCtkVȖ_"!}6_{";oHܱSr{\>XdkO?c-}!>W|.Y,X\0USdt?'oy=2i{qfW{JYLۇHY}p׹̩=V:E\yd_k_^_8~n5`sq?JiVP.B}ʟV
D'֗p]
UO.H,M<eH./Dw}uѝp?vCS_xUK?Հ1ay'7IY0:=Vϝ{;,
45ZeeK>WJ.D!2='yI`F;SGw5w[$0]AvEjԊ{=ӳ=FIzRERu0U(S1p5=ީľۓv
@@C][+P0]7iZYۨkFc&d%Bf#]hkYh֚:JFO$,LWTnWtŅQ[+Q"AuSjhՅ>صjG`dG{bZTыۏS8-As4AsAs4AsAs4AsAs4AsA&IENDB`PK
!<C]tAtA3chrome/toolkit/res/accessiblecaret-tilt-left@2x.pngPNG


IHDRDG	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:18BC63CA6A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:bc7be596-1a62-4758-ac0d-ed0522dc5157</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:09AE9E606A0411E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:09AE9E616A0411E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:18BC63CA6A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:bc7be596-1a62-4758-ac0d-ed0522dc5157</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:04+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T16:00:12+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:04+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:04+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>68</exif:PixelXDimension>
         <exif:PixelYDimension>71</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>L8a cHRMz%u0`:o_FLIDATx}l]eǿ{{{muk׭ua\qÈF|AE
ܐl8a#0C0Hka}[s<q1rO~/<]c_:!vBWlFxMH҆ջ_B/C
.[
 E)*@RHnA$!)0_0@v+0'b5]d
Sc-8WQUKˉB܆56;9O5p3w9ڦwfO+!y
]	vjT hdCb@
>!LH1}AZ5Xٰ?\Su_$vr<3	Pt
6$!<XdoeF`i
( 
PER>C)rRHov"̻U
|w"H1@]SCU[v0cyϥ@SIr@ߛ%s^	~dDZ?:Re>(HU,on44I?*
YLr&z}.]+*tvKHeu넕gZ{r,gGc:d&}w<a!9uΤ:^kɁ,/Wo)s|04]$R~KJ{etӭP)El"8
Hd㗖D۾WRH^:Ef[[vZEWm56K,`D!.
jy;=	z(:9Tы֛ٴ<y]Y(؉X$@[6V_ʳ)}Rrb/D.Cd-d2\j"
0Q+VW}jK9Ɩ62W/wQp0@4^Q(|~2i,	~v@
=a]6ISTrC.|	HD Er8RF" BPHY B.ogY!l+?üUd|z\.";!<L{0pY0H%$v%E/Ϝ&(e\?3q>MϽNpGk|(hq ӯ\S}%f0qǁؓC~#-?0C}&>,Tz|ߩɓGY='M(1L{r#R/^ؑdN|J)(5#/$;ڞX <=3rh;5a1#,	)"FR~GvXV~䑧G/*+,r)`ehyYY)fk1 )0]0p[q}CS0ƪoHp3 j>|0|hckuo֒M"7z
=cÿ?ӌ,)i/\l[^vڋ\FPð)_;voUL0dSjU]mk/][OCZ&>hZO
}P$2ǻ=o;xS R+7][%\Ej1݀(n[4Td!):ںo|ev6 P_1"`k%a|B`
&ݯ[#V|[o8 ;JV 燒o[T*?VRpSɩ,.dB	"<}$~޶8ɼx@< x@< x@<x@fo!mQIENDB`PK
!<9??6chrome/toolkit/res/accessiblecaret-tilt-right@1.5x.pngPNG


IHDR36jl	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:6F7A609E6A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:251e0ba4-9b31-4052-acef-e8b8660443c5</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:18BC63C56A0411E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:18BC63C66A0411E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:6F7A609E6A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:251e0ba4-9b31-4052-acef-e8b8660443c5</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:26:29+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:46:22+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:26:29+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:26:29+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>51</exif:PixelXDimension>
         <exif:PixelYDimension>54</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>uL cHRMz%u0`:o_FIDATxilUޙZZZ
T1ąo5Z1j~p!S#$&҂,}]_YCE+o^Լ$Ϲ!fGV6I7gg]0].cu܆n4>o[&@$uN	
܂IHzD|USpI2UrKw${2Zx^VmP1Kuu0U*(22h<X3k\S4XۉVD|DRN5 *@H!'d';>WŤh2<5u^f
ۖ	`h˿oN#iz,ݺC-V	`e;3$~Jz'HF@F2E,/XV6aĻ$xɼcND3"[7
`;9;a)K>O<Pɸ9}g+MsJ	]ItFYkick9Fѥ7=]4)!s(i.V:Vc^}}z]Dz:reA3/~18C	]iSf_|^ɕ07<5AfW:㮝aC%Qdۄsr#L:?i/30J4\Nx}0zyU@?<PF
3`ٙxs#J,W0MLR"!Jr#dz)C
Be$|Dn\gσJJYʶLPk"mel*w0Fr HE2̶3s+>)H
V|(R~6x`ƒ6/o0_x붽`OJ'92]SIE*BHbxdž]lf2[9EUc_āےwoq*n|-,N3oڸΌ+;@H1b-_ذ#욜I!g09tz_Vl WD0}n
ZM9Hq!^nr=ilwV,*i(
_wֿx{u易PtɍkwM]ǸjЊav
C߬y@}xq+qWuCŭUzדl[b0'H{Wꀲ1mWC7H>`OB{Ͼw^嫻h~fu%	ߓ/
6ʌE#W5%ۚm<[t .ow]4|B~L2iFooWQ(u$1"0MPESIJx91:j!z0U+m.`
0LS)`~뭰BIENDB`PK
!<#>>4chrome/toolkit/res/accessiblecaret-tilt-right@1x.pngPNG


IHDR"$	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:57F315D76A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:66f8446c-dd99-4c3a-a997-8d2b88661df0</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:8A120CC56A0311E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:8A120CC66A0311E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:57F315D76A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:66f8446c-dd99-4c3a-a997-8d2b88661df0</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:27:23+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:46:10+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:27:23+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:27:23+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>34</exif:PixelXDimension>
         <exif:PixelYDimension>36</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>\_ cHRMz%u0`:o_FIDATx]hU~dMMӘXQ`})}GTDB+}0`
R*XL*	IfIf|&ƒdwB.a;?9nq,Kg3g͝zk 'K١Dc{}`O#dBZ{zď=
\:Ab;JݭOxy=Tsc1r
߂h+0yU6#ĸSswĴ}L<ؽHJl+)koD$jzjIΒk!	dvmc|ߏ>2@$@1"IiAx%0ApwgPzcK:bȅLʀ$D LR l+B!q.Gj(3c|ւ( R<nV~Y/?ab_n~权~|!Z/#PD{<p\[¶hhnlU^.p}{mwJL¹'	MmR*UH	j.m<R*&jfqLb`j!6*CAm
2qg”=$ȕ
R(̌
)AcDŽ똥'	+g\*C@y@Y<\rH#_5=We	onя̉I6Zׂ1{^7kfuwzɜ1_)BxF67{Hnf9@CR{=: -g0p	\	>Z]F@k<'V;s-HsLd^/hx@;vμLr6F&0|IrXk{/
l^97A6A6A YT7IENDB`PK
!<{BB7chrome/toolkit/res/accessiblecaret-tilt-right@2.25x.pngPNG


IHDRMQ+P	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:57F315DF6A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:c7ff959f-66b1-464f-8a4a-4846262a3eac</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:09AE9E686A0411E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:18BC63C26A0411E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:57F315DF6A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:c7ff959f-66b1-464f-8a4a-4846262a3eac</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:26:42+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:46:48+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:26:42+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:26:42+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>77</exif:PixelXDimension>
         <exif:PixelYDimension>81</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>Q cHRMz%u0`:o_FIDATxylǿka0.0%VRR*WU4i&RI[JڒTmZFD
R*@pB@k
`}{s_صw4jgf3}=FDlx-4͂6T&>Ʋc#kSKxgG]#<݀LQ@2s1$$0Il``$`h㸄_߉>d[3"M[Qzۃ ]C2W4uH7;R%=Mr M!Ø`6>U)e^#cmY}%7Gz4&Kfg$"[U3dHDZ;Iuhwؾ9G"~z#NHK;Zӯ:1A(rM Ɉ{C$%5gοI6
$(h}cدvj6{!`&aTl*`
sr;`(	UlW&Nݞ8k'a$r90?.cPJ+fVoo:gO6:w/윯ƃ{39Ha$"ko%6.Η{|SmC?l8Ol$rf CʁZ!G%6n5=_IbL?Tzƴ(sӎLL7BH'y[3?;)hP:srN> q͵Q	=Cu@.*ڔ_{U[_d:hɶHS
SwV-sV44yHF8#RZ)2
4@"I2۾@#-I	0.Y?g\4j̃iW-%"D:Ϝ/3Z LI5s=WοpK$`[GcLfk~_j~k2;Ʃ-D68J.Npk2
VTNw9
a*JPisgZѢ;\>'	B0Sw]RZ	n}Ҵ)l
BJbW1YP8f 04qك2s1'iD
%4pc 	hD)$hqd:(7Ob̡ijfN2cI/lP{O Ql1=ɼxZi[/ZANhU5А>c{N@miyS9XX
W葾|}H|H胝y}وM	!޲cH'Cy;"mtӍ̩X
ra7Bh$cInwt2`{G	n^VL~sz7i%{-1ٺ-6ѰI	/OnPs'ǣi0.D5@lԮs]30YQeӘ }@{Ǘ>22uӃl2V<ZoG?hHA>K{7*y$e**lt
7K6 5<l0V;'/H'Gu${ۏ//#%k"o=8ߕ%E"#2&Gx熷;V?|;U߄rY˿Wݴ+C2L)tY+]G2B8?lo9pݷ_iAIgƲ+lvp"D/ָ֫}:]BB6`T|-]S'ՔIb -BdC8 ]x:ɶR{׭Za	em|e_;yh)pš0IBF*Μl
kΠі:-Dr>KB6o`s1Ɂ	L%f2F*0bҹ|N?A;_9\ȉXBV:+Y}ɨֲFY,Y,h4e4͂fÂfAY,hY,h4ͲIENDB`PK
!<B{A{A4chrome/toolkit/res/accessiblecaret-tilt-right@2x.pngPNG


IHDRDG	pHYs9iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmpMM:DocumentID>xmp.did:57F315DB6A0411E5B317EE7FADA1A96E</xmpMM:DocumentID>
         <xmpMM:InstanceID>xmp.iid:c2d9f59f-4cee-4c72-bb06-f54da9576e32</xmpMM:InstanceID>
         <xmpMM:DerivedFrom rdf:parseType="Resource">
            <stRef:instanceID>xmp.iid:18BC63C96A0411E5B317EE7FADA1A96E</stRef:instanceID>
            <stRef:documentID>xmp.did:18BC63CA6A0411E5B317EE7FADA1A96E</stRef:documentID>
         </xmpMM:DerivedFrom>
         <xmpMM:OriginalDocumentID>xmp.did:57F315DB6A0411E5B317EE7FADA1A96E</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:c2d9f59f-4cee-4c72-bb06-f54da9576e32</stEvt:instanceID>
                  <stEvt:when>2015-10-15T17:26:36+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2014-06-13T15:46:35+08:00</xmp:CreateDate>
         <xmp:ModifyDate>2015-10-15T17:26:36+08:00</xmp:ModifyDate>
         <xmp:MetadataDate>2015-10-15T17:26:36+08:00</xmp:MetadataDate>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>65535</exif:ColorSpace>
         <exif:PixelXDimension>68</exif:PixelXDimension>
         <exif:PixelYDimension>71</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>pz cHRMz%u0`:o_FSIDATxmp\ef7{v Xځ(*ӗqDV-:ZFNJNuv:5XŶXlUʴ-iiI6{_㇤Ў޽Lܻ{o9Ybfx	S3_}[|zÏmHWZ?؈y=V=RO= EH@BL\'Xmla%0Mx6]ؾ?~Z$^ڃѝ]gbBDEeD6s`˜pRʛVokx#)2G.
 b\X$t\ayһ/V/q5TͶi2)G$fe=\@
0GT,i%A^ ]/TMaQh*SuwC`/U:&HATR":+ Ts^17KCNQ 4]!Rᆦ[wX|ˤc'KtN)@3Oz_}h}uWB>/2OH9՛L8}vv&1Ws
~Yg[ktHXѴP|-ΕY`{
IB|~g+3e>YlXOeƌNfA3@mM+΃ m@Ř1	Wxǖگfl$ۖE`lCf7|s}۫
D蒹o?(-KרuCROYPjkfX8­oyՒbirXXΤm_`yBBm4av&W6.bRT?\c+oQwHaskPB
O%U`).fZ\8`"K_0W+lvIhK0-%m_ i!Tnً6`Ӈ)>GVg@P	
9DUC2@J@~g);) (N!i:Xe!hljM-tb-3hkc,m&rNڙD{^y:B~mK:
$[&2Hٱ>Ǖ41c ee@)#}c1]`<;`p X|vK8v=y/Lt[A>@oH

9o@؉ܵW-ct#]g<CwPO֙.utfd/٩y7:vzkضXCpj$@B*"[_?k@bqѻK_ࢯD0<60ą)J{%
~i%/'~~z83T/ݻ_T!s:gDGsC0czL9؝3
+~uʢ[`V*~O
(
JQ	i ʁȖ_><6m gjQ	e/+_λ2N>Y1
HA	R=gGް-sfM@Θ@1iy⢖]JsYeD(*?i`FO%L{Pvڙq	-9L@mA}Z٬fme*J?6uӌŲ=G#ݧx+}:}˪)*h@QVe	H{@j>]jLUkt~FPG\j=x@< x@< rٵI`c#IENDB`PK
!≮99!chrome/toolkit/res/arrow-left.gifGIF89a!,
s `4;PK
!<]ZI99"chrome/toolkit/res/arrow-right.gifGIF89a!,
&R;PK
!<i988chrome/toolkit/res/arrow.gifGIF89a!,	~!ܓ;PK
!<6R<<"chrome/toolkit/res/arrowd-left.gifGIF89a	!,	
i瞈rjS(;PK
!<хEN;;#chrome/toolkit/res/arrowd-right.gifGIF89a	!,		{t9s;PK
!<,4;;chrome/toolkit/res/arrowd.gifGIF89a	!,	q[*+
;PK
!<#chrome/toolkit/res/broken-image.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤS
 TsA{MIRjS񈖈lR^RJv
pr$njB01ƥR_O>DMuZ/>gfoaylK`ԠFI>kDAϾ:mfYM|0NmxIENDB`PK
!<!E-E-$chrome/toolkit/res/counterstyles.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Defined in CSS Counter Styles Level 3 */

/* 6 Simple Predefined Counter Styles */

/* 6.1 Numeric */

@counter-style decimal-leading-zero {
  system: extends decimal;
  pad: 2 '0';
}

@counter-style arabic-indic {
  system: numeric;
  symbols: \660  \661  \662  \663  \664  \665  \666  \667  \668  \669;
}

@counter-style armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 \554, 8000 \553, 7000 \552, 6000 \551, 5000 \550, 4000 \54F, 3000 \54E, 2000 \54D, 1000 \54C, 900 \54B, 800 \54A, 700 \549, 600 \548, 500 \547, 400 \546, 300 \545, 200 \544, 100 \543, 90 \542, 80 \541, 70 \540, 60 \53F, 50 \53E, 40 \53D, 30 \53C, 20 \53B, 10 \53A, 9 \539, 8 \538, 7 \537, 6 \536, 5 \535, 4 \534, 3 \533, 2 \532, 1 \531;
}

@counter-style upper-armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 \554, 8000 \553, 7000 \552, 6000 \551, 5000 \550, 4000 \54F, 3000 \54E, 2000 \54D, 1000 \54C, 900 \54B, 800 \54A, 700 \549, 600 \548, 500 \547, 400 \546, 300 \545, 200 \544, 100 \543, 90 \542, 80 \541, 70 \540, 60 \53F, 50 \53E, 40 \53D, 30 \53C, 20 \53B, 10 \53A, 9 \539, 8 \538, 7 \537, 6 \536, 5 \535, 4 \534, 3 \533, 2 \532, 1 \531;
}

@counter-style lower-armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 \584, 8000 \583, 7000 \582, 6000 \581, 5000 \580, 4000 \57F, 3000 \57E, 2000 \57D, 1000 \57C, 900 \57B, 800 \57A, 700 \579, 600 \578, 500 \577, 400 \576, 300 \575, 200 \574, 100 \573, 90 \572, 80 \571, 70 \570, 60 \56F, 50 \56E, 40 \56D, 30 \56C, 20 \56B, 10 \56A, 9 \569, 8 \568, 7 \567, 6 \566, 5 \565, 4 \564, 3 \563, 2 \562, 1 \561;
}

@counter-style bengali {
  system: numeric;
  symbols: \9E6  \9E7  \9E8  \9E9  \9EA  \9EB  \9EC  \9ED  \9EE  \9EF;
}

@counter-style cambodian {
  system: extends khmer;
}

@counter-style khmer {
  system: numeric;
  symbols: \17E0  \17E1  \17E2  \17E3  \17E4  \17E5  \17E6  \17E7  \17E8  \17E9;
}

@counter-style cjk-decimal {
  system: numeric;
  range: 0 infinite;
  symbols: \3007  \4E00  \4E8C  \4E09  \56DB  \4E94  \516D  \4E03  \516B  \4E5D;
  suffix: '\3001';
}

@counter-style devanagari {
  system: numeric;
  symbols: \966  \967  \968  \969  \96A  \96B  \96C  \96D  \96E  \96F;
}

@counter-style georgian {
  system: additive;
  range: 1 19999;
  additive-symbols: 10000 \10F5, 9000 \10F0, 8000 \10EF, 7000 \10F4, 6000 \10EE, 5000 \10ED, 4000 \10EC, 3000 \10EB, 2000 \10EA, 1000 \10E9, 900 \10E8, 800 \10E7, 700 \10E6, 600 \10E5, 500 \10E4, 400 \10F3, 300 \10E2, 200 \10E1, 100 \10E0, 90 \10DF, 80 \10DE, 70 \10DD, 60 \10F2, 50 \10DC, 40 \10DB, 30 \10DA, 20 \10D9, 10 \10D8, 9 \10D7, 8 \10F1, 7 \10D6, 6 \10D5, 5 \10D4, 4 \10D3, 3 \10D2, 2 \10D1, 1 \10D0;
}

@counter-style gujarati {
  system: numeric;
  symbols: \AE6  \AE7  \AE8  \AE9  \AEA  \AEB  \AEC  \AED  \AEE  \AEF;
}

@counter-style gurmukhi {
  system: numeric;
  symbols: \A66  \A67  \A68  \A69  \A6A  \A6B  \A6C  \A6D  \A6E  \A6F;
}

/* hebrew is not included because our builtin algorithm can generate a wider
 * range of number in this style than what the spec defines. */

@counter-style kannada {
  system: numeric;
  symbols: \CE6  \CE7  \CE8  \CE9  \CEA  \CEB  \CEC  \CED  \CEE  \CEF;
}

@counter-style lao {
  system: numeric;
  symbols: \ED0  \ED1  \ED2  \ED3  \ED4  \ED5  \ED6  \ED7  \ED8  \ED9;
}

@counter-style malayalam {
  system: numeric;
  symbols: \D66  \D67  \D68  \D69  \D6A  \D6B  \D6C  \D6D  \D6E  \D6F;
}

@counter-style mongolian {
  system: numeric;
  symbols: \1810  \1811  \1812  \1813  \1814  \1815  \1816  \1817  \1818  \1819;
}

@counter-style myanmar {
  system: numeric;
  symbols: \1040  \1041  \1042  \1043  \1044  \1045  \1046  \1047  \1048  \1049;
}

@counter-style oriya {
  system: numeric;
  symbols: \B66  \B67  \B68  \B69  \B6A  \B6B  \B6C  \B6D  \B6E  \B6F;
}

@counter-style persian {
  system: numeric;
  symbols: \6F0  \6F1  \6F2  \6F3  \6F4  \6F5  \6F6  \6F7  \6F8  \6F9;
}

@counter-style lower-roman {
  system: additive;
  range: 1 3999;
  additive-symbols: 1000 m, 900 cm, 500 d, 400 cd, 100 c, 90 xc, 50 l, 40 xl, 10 x, 9 ix, 5 v, 4 iv, 1 i;
}

@counter-style upper-roman {
  system: additive;
  range: 1 3999;
  additive-symbols: 1000 M, 900 CM, 500 D, 400 CD, 100 C, 90 XC, 50 L, 40 XL, 10 X, 9 IX, 5 V, 4 IV, 1 I;
}

@counter-style tamil {
  system: numeric;
  symbols: \BE6  \BE7  \BE8  \BE9  \BEA  \BEB  \BEC  \BED  \BEE  \BEF;
}

@counter-style telugu {
  system: numeric;
  symbols: \C66  \C67  \C68  \C69  \C6A  \C6B  \C6C  \C6D  \C6E  \C6F;
}

@counter-style thai {
  system: numeric;
  symbols: \E50  \E51  \E52  \E53  \E54  \E55  \E56  \E57  \E58  \E59;
}

@counter-style tibetan {
  system: numeric;
  symbols: \F20  \F21  \F22  \F23  \F24  \F25  \F26  \F27  \F28  \F29;
}

/* 6.2 Alphabetic */

@counter-style lower-alpha {
  system: alphabetic;
  symbols: 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;
}

@counter-style lower-latin {
  system: extends lower-alpha;
}

@counter-style upper-alpha {
  system: alphabetic;
  symbols: 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;
}

@counter-style upper-latin {
  system: extends upper-alpha;
}

@counter-style cjk-heavenly-stem {
  system: alphabetic;
  symbols: \7532  \4E59  \4E19  \4E01  \620A  \5DF1  \5E9A  \8F9B  \58EC  \7678;
  fallback: cjk-decimal;
  suffix: '\3001';
}

@counter-style cjk-earthly-branch {
  system: alphabetic;
  symbols: \5B50  \4E11  \5BC5  \536F  \8FB0  \5DF3  \5348  \672A  \7533  \9149  \620C  \4EA5;
  fallback: cjk-decimal;
  suffix: '\3001';
}

@counter-style lower-greek {
  system: alphabetic;
  symbols: \3B1  \3B2  \3B3  \3B4  \3B5  \3B6  \3B7  \3B8  \3B9  \3BA  \3BB  \3BC  \3BD  \3BE  \3BF  \3C0  \3C1  \3C3  \3C4  \3C5  \3C6  \3C7  \3C8  \3C9;
}

@counter-style hiragana {
  system: alphabetic;
  symbols: \3042  \3044  \3046  \3048  \304A  \304B  \304D  \304F  \3051  \3053  \3055  \3057  \3059  \305B  \305D  \305F  \3061  \3064  \3066  \3068  \306A  \306B  \306C  \306D  \306E  \306F  \3072  \3075  \3078  \307B  \307E  \307F  \3080  \3081  \3082  \3084  \3086  \3088  \3089  \308A  \308B  \308C  \308D  \308F  \3090  \3091  \3092  \3093;
  suffix: '\3001';
}

@counter-style hiragana-iroha {
  system: alphabetic;
  symbols: \3044  \308D  \306F  \306B  \307B  \3078  \3068  \3061  \308A  \306C  \308B  \3092  \308F  \304B  \3088  \305F  \308C  \305D  \3064  \306D  \306A  \3089  \3080  \3046  \3090  \306E  \304A  \304F  \3084  \307E  \3051  \3075  \3053  \3048  \3066  \3042  \3055  \304D  \3086  \3081  \307F  \3057  \3091  \3072  \3082  \305B  \3059;
  suffix: '\3001';
}

@counter-style katakana {
  system: alphabetic;
  symbols: \30A2  \30A4  \30A6  \30A8  \30AA  \30AB  \30AD  \30AF  \30B1  \30B3  \30B5  \30B7  \30B9  \30BB  \30BD  \30BF  \30C1  \30C4  \30C6  \30C8  \30CA  \30CB  \30CC  \30CD  \30CE  \30CF  \30D2  \30D5  \30D8  \30DB  \30DE  \30DF  \30E0  \30E1  \30E2  \30E4  \30E6  \30E8  \30E9  \30EA  \30EB  \30EC  \30ED  \30EF  \30F0  \30F1  \30F2  \30F3;
  suffix: '\3001';
}

@counter-style katakana-iroha {
  system: alphabetic;
  symbols: \30A4  \30ED  \30CF  \30CB  \30DB  \30D8  \30C8  \30C1  \30EA  \30CC  \30EB  \30F2  \30EF  \30AB  \30E8  \30BF  \30EC  \30BD  \30C4  \30CD  \30CA  \30E9  \30E0  \30A6  \30F0  \30CE  \30AA  \30AF  \30E4  \30DE  \30B1  \30D5  \30B3  \30A8  \30C6  \30A2  \30B5  \30AD  \30E6  \30E1  \30DF  \30B7  \30F1  \30D2  \30E2  \30BB  \30B9;
  suffix: '\3001';
}

/* 6.3 Symbolic */

/* symbolic counter styles are not included because they will be drew directly
 * by the program instead of use alternative symbols defined in the spec */

/* 7 Complex Predefined Counter Styles */

/* only alias is included as other complex counter styles will be generated by
 * specific algorithms to support the extended range. */

@counter-style cjk-ideographic {
  system: extends trad-chinese-informal;
}

/* Mozilla-specific counter styles */

/* Numeric */

@counter-style -moz-arabic-indic {
  system: extends arabic-indic;
}

@counter-style -moz-persian {
  system: extends persian;
}

@counter-style -moz-urdu {
  system: extends persian;
}

@counter-style -moz-devanagari {
  system: extends devanagari;
}

@counter-style -moz-bengali {
  system: extends bengali;
}

@counter-style -moz-gurmukhi {
  system: extends gurmukhi;
}

@counter-style -moz-gujarati {
  system: extends gujarati;
}

@counter-style -moz-oriya {
  system: extends oriya;
}

@counter-style -moz-tamil {
  system: extends tamil;
}

@counter-style -moz-telugu {
  system: extends telugu;
}

@counter-style -moz-kannada {
  system: extends kannada;
}

@counter-style -moz-malayalam {
  system: extends malayalam;
}

@counter-style -moz-thai {
  system: extends thai;
}

@counter-style -moz-lao {
  system: extends lao;
}

@counter-style -moz-myanmar {
  system: extends myanmar;
}

@counter-style -moz-khmer {
  system: extends khmer;
}

/* Alphabetic */

@counter-style -moz-cjk-heavenly-stem {
  system: extends cjk-heavenly-stem;
}
@counter-style -moz-cjk-earthly-branch {
  system: extends cjk-earthly-branch;
}

@counter-style -moz-hangul {
  system: alphabetic;
  symbols: \AC00  \B098  \B2E4  \B77C  \B9C8  \BC14  \C0AC  \C544  \C790  \CC28  \CE74  \D0C0  \D30C  \D558;
  suffix: ',';
}
@counter-style -moz-hangul-consonant {
  system: alphabetic;
  symbols: \3131  \3134  \3137  \3139  \3141  \3142  \3145  \3147  \3148  \314A  \314B  \314C  \314D  \314E;
  suffix: ',';
}

/* Ge'ez set of Ethiopic ordered list. There are other locale-dependent sets.
 * For the time being, let's implement two Ge'ez sets only
 * per Momoi san's suggestion in bug 102252. 
 * For details, refer to http://www.ethiopic.org/Collation/OrderedLists.html. */
@counter-style -moz-ethiopic-halehame {
  system: alphabetic;
  symbols: \1200  \1208  \1210  \1218  \1220  \1228  \1230  \1240  \1260  \1270  \1280  \1290  \12A0  \12A8  \12C8  \12D0  \12D8  \12E8  \12F0  \1308  \1320  \1330  \1338  \1340  \1348  \1350;
}
@counter-style -moz-ethiopic-halehame-am {
  system: alphabetic;
  symbols: \1200  \1208  \1210  \1218  \1220  \1228  \1230  \1238  \1240  \1260  \1270  \1278  \1280  \1290  \1298  \12A0  \12A8  \12B8  \12C8  \12D0  \12D8  \12E0  \12E8  \12F0  \1300  \1308  \1320  \1328  \1330  \1338  \1340  \1348  \1350;
}
@counter-style -moz-ethiopic-halehame-ti-er {
  system: alphabetic;
  symbols: \1200  \1208  \1210  \1218  \1228  \1230  \1238  \1240  \1250  \1260  \1270  \1278  \1290  \1298  \12A0  \12A8  \12B8  \12C8  \12D0  \12D8  \12E0  \12E8  \12F0  \1300  \1308  \1320  \1328  \1330  \1338  \1348  \1350;
}
@counter-style -moz-ethiopic-halehame-ti-et {
  system: alphabetic;
  symbols: \1200  \1208  \1210  \1218  \1220  \1228  \1230  \1238  \1240  \1250  \1260  \1270  \1278  \1280  \1290  \1298  \12A0  \12A8  \12B8  \12C8  \12D0  \12D8  \12E0  \12E8  \12F0  \1300  \1308  \1320  \1328  \1330  \1338  \1340  \1348  \1350;
}

/* Alias */

@counter-style -moz-trad-chinese-informal {
  system: extends trad-chinese-informal;
}

@counter-style -moz-trad-chinese-formal {
  system: extends trad-chinese-formal;
}

@counter-style -moz-simp-chinese-informal {
  system: extends simp-chinese-informal;
}

@counter-style -moz-simp-chinese-formal {
  system: extends simp-chinese-formal;
}

@counter-style -moz-japanese-informal {
  system: extends japanese-informal;
}

@counter-style -moz-japanese-formal {
  system: extends japanese-formal;
}

@counter-style -moz-ethiopic-numeric {
  system: extends ethiopic-numeric;
}
PK
!<C0{{chrome/toolkit/res/forms.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
  Styles for old GFX form widgets
 **/


@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

*|*::-moz-fieldset-content {
  display: block; /* nsRuleNode::ComputeDisplayData overrules this in some cases */
  unicode-bidi: inherit;
  text-overflow: inherit;
  overflow: inherit;
  overflow-clip-box: inherit;
  /* Need to inherit border-radius too, so when the fieldset has rounded
     borders we don't leak out the corners for hit-testing purposes. */
  border-radius: inherit;
  padding: inherit;
  block-size: 100%; /* Need this so percentage block-sizes of kids work right */
  /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
     ::-moz-scrolled-content in ua.css and ::-moz-button-content below. */
  /* Multicol container */
  -moz-column-count: inherit;
  -moz-column-width: inherit;
  -moz-column-gap: inherit;
  -moz-column-rule: inherit;
  -moz-column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
}

/* miscellaneous form elements */

fieldset > legend {
  padding-inline-start: 2px;
  padding-inline-end: 2px;
  inline-size: -moz-fit-content;
}

legend {
  display: block;
}

fieldset {
  display: block;
  margin-inline-start: 2px;
  margin-inline-end: 2px;
  padding-block-start: 0.35em;
  padding-block-end: 0.75em;
  padding-inline-start: 0.625em;
  padding-inline-end: 0.625em;
  border: 2px groove ThreeDLightShadow;
  min-width: -moz-min-content;
}

label {
  cursor: default;
}

/* default inputs, text inputs, and selects */

/* Note: Values in nsNativeTheme IsWidgetStyled function
   need to match textfield background/border values here */

input {
  -moz-appearance: textfield;
  /* The sum of border and padding on block-start and block-end
     must be the same here, for buttons, and for <select> (including its
     internal padding magic) */
  padding: 1px;
  border: 2px inset ThreeDLightShadow;
  background-color: -moz-Field;
  color: -moz-FieldText;
  font: -moz-field;
  text-rendering: optimizeLegibility;
  line-height: normal;
  text-align: start;
  text-transform: none;
  word-spacing: normal;
  letter-spacing: normal;
  cursor: text;
  -moz-binding: url("chrome://global/content/platformHTMLBindings.xml#inputFields");
  text-indent: 0;
  -moz-user-select: text;
  text-shadow: none;
  overflow-clip-box: content-box;
}

input > .anonymous-div,
input::placeholder {
  word-wrap: normal !important;
  /* Make the line-height equal to the available height */
  line-height: -moz-block-height;
}

@-moz-document url-prefix(chrome://) {
  input.uri-element-right-align:-moz-locale-dir(rtl) {
    direction: ltr !important;
    text-align: right !important;
  }

  /* Make sure that the location bar's alignment in RTL mode changes according
     to the input box direction if the user switches the text direction using
     cmd_switchTextDirection (which applies a dir attribute to the <input>). */
  input.uri-element-right-align[dir=ltr]:-moz-locale-dir(rtl) {
    text-align: left !important;
  }
}

textarea {
  margin-block-start: 1px;
  margin-block-end: 1px;
  border: 2px inset ThreeDLightShadow;
  /* The 1px inline padding is for parity with Win/IE */
  padding-inline-start: 1px;
  padding-inline-end: 1px;
  background-color: -moz-Field;
  color: -moz-FieldText;
  font: medium -moz-fixed;
  text-rendering: optimizeLegibility;
  text-align: start;
  text-transform: none;
  word-spacing: normal;
  letter-spacing: normal;
  vertical-align: text-bottom;
  cursor: text;
  resize: both;
  -moz-binding: url("chrome://global/content/platformHTMLBindings.xml#textAreas");
  -moz-appearance: textfield-multiline;
  text-indent: 0;
  -moz-user-select: text;
  text-shadow: none;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-clip-box: content-box;
}

textarea > scrollbar {
  cursor: default;
}

textarea > .anonymous-div,
input > .anonymous-div,
input::placeholder,
textarea::placeholder,
input > .preview-div
textarea > .preview-div {
  overflow: auto;
  border: 0px !important;
  padding: inherit !important;
  margin: 0px;
  text-decoration: inherit;
  text-decoration-color: inherit;
  text-decoration-style: inherit;
  display: inline-block;
  ime-mode: inherit;
  resize: inherit;
  -moz-control-character-visibility: visible;
  overflow-clip-box: inherit;
}

input > .anonymous-div,
input::placeholder,
input > .preview-div {
  white-space: pre;
}

input > .anonymous-div.wrap {
  white-space: pre-wrap;
}
textarea > .anonymous-div.inherit-overflow,
input > .anonymous-div.inherit-overflow {
  overflow: inherit;
}
textarea > .anonymous-div.inherit-scroll-behavior,
input > .anonymous-div.inherit-scroll-behavior {
  scroll-behavior: inherit;
}

input::placeholder,
textarea::placeholder,
input > .preview-div,
textarea > .preview-div {
  /*
   * Changing display to inline can leads to broken behaviour and will assert.
   */
  display: inline-block !important;

  /*
   * Changing resize would display a broken behaviour and will assert.
   */
  resize: none !important;

  overflow: hidden !important;

  /*
   * The placeholder or preview should be ignored by pointer otherwise, we might have some
   * unexpected behavior like the resize handle not being selectable.
   */
  pointer-events: none !important;
}

input::placeholder,
textarea::placeholder {
  opacity: 0.54;
}

textarea::placeholder,
textarea > .preview-div {
  white-space: pre-wrap !important;
}

input:-moz-read-write,
textarea:-moz-read-write {
  -moz-user-modify: read-write !important;
}

select {
  margin: 0;
  border-color: ThreeDLightShadow;
  background-color: -moz-Combobox;
  color: -moz-ComboboxText;
  font: -moz-list;
  /*
   * Note that the "UA !important" tests in
   * layout/style/test/test_animations.html depend on this rule, because
   * they need some UA !important rule to test.  If this changes, use a
   * different one there.
   */
  line-height: normal !important;
  white-space: nowrap !important;
  word-wrap: normal !important;
  text-align: start;
  cursor: default;
  box-sizing: border-box;
  -moz-user-select: none;
  -moz-appearance: menulist;
  border-width: 2px;
  border-style: inset;
  text-indent: 0;
  overflow: -moz-hidden-unscrollable;
  text-shadow: none;
  /* No text-decoration reaching inside, by default */
  display: inline-block;
  page-break-inside: avoid;
  overflow-clip-box: padding-box !important; /* bug 992447 */
}

/* Need the "select[size][multiple]" selector to override the settings on
   'select[size="1"]', eg if one has <select size="1" multiple> */

select[size],
select[multiple],
select[size][multiple] {
  /* Different alignment and padding for listbox vs combobox */
  background-color: -moz-Field;
  color: -moz-FieldText;
  vertical-align: text-bottom;
  padding-block-start: 1px;
  padding-block-end: 1px;
  padding-inline-start: 0;
  padding-inline-end: 0;
  -moz-appearance: listbox;
}

select[size="0"],
select[size="1"] {
  /* Except this is not a listbox */
  background-color: -moz-Combobox;
  color: -moz-ComboboxText;
  vertical-align: baseline;
  padding: 0;
  -moz-appearance: menulist;
}

select > button {
  inline-size: 12px;
  white-space: nowrap;
  position: static !important;
  background-image: url("arrow.gif") !important;
  background-repeat: no-repeat !important;
  background-position: center !important;
  -moz-appearance: menulist-button;

  /* Make sure to size correctly if the combobox has a non-auto height. */
  block-size: 100% ! important;
  box-sizing: border-box ! important;

  /*
    Make sure to align properly with the display frame.  Note that we
    want the baseline of the combobox to match the baseline of the
    display frame, so the dropmarker is what gets the vertical-align.
  */
  vertical-align: top !important;
}

select > button:active {
  background-image: url("arrowd.gif") !important;
}

select > button[orientation="left"] {
  background-image: url("arrow-left.gif") !important;
}

select > button[orientation="right"] {
  background-image: url("arrow-right.gif") !important;
}

select > button[orientation="left"]:active {
  background-image: url("arrowd-left.gif") !important;
}

select > button[orientation="right"]:active {
  background-image: url("arrowd-right.gif") !important;
}

select:empty {
  inline-size: 2.5em;
}

*|*::-moz-display-comboboxcontrol-frame {
  overflow: -moz-hidden-unscrollable;
  /* This block-start/end padding plus the combobox block-start/end border need to
     add up to the block-start/end borderpadding of text inputs and buttons */
  padding-block-start: 1px;
  padding-block-end: 1px;
  padding-inline-start: 4px;
  padding-inline-end: 0;
  color: inherit;
  white-space: nowrap;
  text-align: inherit;
  -moz-user-select: none;
  /* Make sure to size correctly if the combobox has a non-auto block-size. */
  block-size: 100% ! important;
  box-sizing: border-box ! important;
  line-height: -moz-block-height;
}

option {
  display: block;
  float: none !important;
  position: static !important;
  min-block-size: 1em;
  line-height: normal !important;
  -moz-user-select: none;
  text-indent: 0;
  white-space: nowrap !important;
  word-wrap: normal !important;
  text-align: match-parent;
}

select > option {
  padding-block-start : 0;
  padding-block-end: 0;
  padding-inline-start: 3px;
  padding-inline-end: 5px;
}

option:checked {
  background-color: -moz-html-cellhighlight !important;
  color: -moz-html-cellhighlighttext !important;
}

select:focus > option:checked,
select:focus > optgroup > option:checked {
  background-color: Highlight ! important;
  color: HighlightText ! important;
}

optgroup {
  display: block;
  float: none !important;
  position: static !important;
  font: -moz-list;
  line-height: normal !important;
  font-style: italic;
  font-weight: bold;
  font-size: inherit;
  -moz-user-select: none;
  text-indent: 0;
  white-space: nowrap !important;
  word-wrap: normal !important;
}

optgroup > option {
  padding-inline-start: 20px;
  font-style: normal;
  font-weight: normal;
}

optgroup:before {
  display: block;
  content: attr(label);
}

*|*::-moz-dropdown-list {
  z-index: 2147483647;
  background-color: inherit;
  -moz-user-select: none;
  position: static !important;
  float: none !important;

  /*
   * We can't change the padding here, because that would affect our
   * intrinsic inline-size, since we scroll.  But at the same time, we want
   * to make sure that our inline-start border+padding matches the inline-start
   * border+padding of a combobox so that our scrollbar will line up
   * with the dropmarker.  So set our inline-start border to 2px.
   */
  border: 1px outset black !important;
  border-inline-start-width: 2px ! important;
}

input:disabled,
textarea:disabled,
option:disabled,
optgroup:disabled,
select:disabled:disabled /* Need the pseudo-class twice to have the specificity
                            be at least the same as select[size][multiple] above */
{
  -moz-user-input: disabled;
  color: GrayText;
  background-color: ThreeDLightShadow;
  cursor: inherit;
}

input:disabled,
textarea:disabled {
  cursor: default;
}

option:disabled,
optgroup:disabled {
  background-color: transparent;
}

/* hidden inputs */
input[type="hidden"] {
  -moz-appearance: none;
  display: none !important;
  padding: 0;
  border: 0;
  cursor: auto;
  -moz-user-focus: ignore;
  -moz-binding: none;
}

/* image buttons */
input[type="image"] {
  -moz-appearance: none;
  padding: 0;
  border: none;
  background-color: transparent;
  font-family: sans-serif;
  font-size: small;
  cursor: pointer;
  -moz-binding: none;
}

input[type="image"]:disabled {
  cursor: inherit;
}

input[type="image"]:-moz-focusring {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

/* file selector */
input[type="file"] {
  display: inline-block;
  white-space: nowrap;
  overflow: hidden;
  overflow-clip-box: padding-box;
  color: inherit;

  /* Revert rules which apply on all inputs. */
  -moz-appearance: none;
  -moz-binding: none;
  cursor: default;

  border: none;
  background-color: transparent;
  padding: 0;
}

input[type="file"] > xul|label {
  min-inline-size: 12em;
  padding-inline-start: 5px;
  text-align: match-parent;

  color: inherit;
  font-size: inherit;
  letter-spacing: inherit;

  /*
   * Force the text to have LTR directionality. Otherwise filenames containing
   * RTL characters will be reordered with chaotic results.
   */
  direction: ltr !important;
}

/* button part of file selector */
input[type="file"] > button[type="button"] {
  block-size: inherit;
  font-size: inherit;
  letter-spacing: inherit;
  cursor: inherit;
}

/* colored part of the color selector button */
input[type="color"]:-moz-system-metric(color-picker-available)::-moz-color-swatch {
  width: 100%;
  height: 100%;
  min-width: 3px;
  min-height: 3px;
  margin-inline-start: auto;
  margin-inline-end: auto;
  box-sizing: border-box;
  border: 1px solid grey;
  display: block;
}

/* Try to make RTL <input type='file'> look nicer. */
/* TODO: find a better solution than forcing direction: ltr on all file
   input labels and remove this override -- bug 1161482 */
input[type="file"]:dir(rtl) > xul|label {
  padding-inline-start: 0px;
  padding-inline-end: 5px;
}

/* radio buttons */
input[type="radio"] {
  display: inline-block;
  -moz-appearance: radio;
  margin-block-start: 3px;
  margin-block-end: 0px;
  margin-inline-start: 5px;
  margin-inline-end: 3px;
}

/* check boxes */
input[type="checkbox"] {
  display: inline-block;
  -moz-appearance: checkbox;
  margin-block-start: 3px;
  margin-block-end: 3px;
  margin-inline-start: 4px;
  margin-inline-end: 3px;
}

/* common features of radio buttons and check boxes */

input[type="radio"],
input[type="checkbox"] {
  box-sizing: border-box;
  cursor: default;
  /* unset some values from the general 'input' rule above: */
  padding: unset;
  -moz-binding: unset;
  border: unset;
  background-color: unset;
  color: unset;
}

input[type="radio"]:disabled,
input[type="radio"]:disabled:active,
input[type="radio"]:disabled:hover,
input[type="radio"]:disabled:hover:active,
input[type="checkbox"]:disabled,
input[type="checkbox"]:disabled:active,
input[type="checkbox"]:disabled:hover,
input[type="checkbox"]:disabled:hover:active {
  cursor: inherit;
}


input[type="checkbox"]:-moz-focusring,
input[type="radio"]:-moz-focusring {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

input[type="search"] {
  box-sizing: border-box;
}

/* buttons */

/* Note: Values in nsNativeTheme IsWidgetStyled function
   need to match button background/border values here */

/* Non text-related properties for buttons: these ones are shared with
   input[type="color"] */
button,
input[type="color"]:-moz-system-metric(color-picker-available),
input[type="reset"],
input[type="button"],
input[type="submit"] {
  -moz-appearance: button;
  /* The sum of border and padding on block-start and block-end
     must be the same here, for text inputs, and for <select>.
     Note -moz-focus-inner padding does not affect button size. */
  padding-block-start: 0px;
  padding-inline-end: 8px;
  padding-block-end: 0px;
  padding-inline-start: 8px;
  border: 2px outset ThreeDLightShadow;
  background-color: ButtonFace;
  cursor: default;
  box-sizing: border-box;
  -moz-user-select: none;
  -moz-binding: none;
}

/* Text-related properties for buttons: these ones are not shared with
   input[type="color"] */
button,
input[type="reset"],
input[type="button"],
input[type="submit"] {
  color: ButtonText;
  font: -moz-button;
  line-height: normal;
  white-space: pre;
  text-align: center;
  text-shadow: none;
  overflow-clip-box: padding-box;
}

input[type="color"]:-moz-system-metric(color-picker-available) {
  inline-size: 64px;
  block-size: 23px;
}

button {
  /* Buttons should lay out like "normal" html, mostly */
  white-space: inherit;
  text-indent: 0;
  /* But no text-decoration reaching inside, by default */
  display: inline-block;
}

*|*::-moz-button-content {
  display: block;
  /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
     ::-moz-scrolled-content in ua.css and ::-moz-fieldset-content above. */
  /* Multicol container */
  -moz-column-count: inherit;
  -moz-column-width: inherit;
  -moz-column-gap: inherit;
  -moz-column-rule: inherit;
  -moz-column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
}

button:hover,
input[type="color"]:-moz-system-metric(color-picker-available):hover,
input[type="reset"]:hover,
input[type="button"]:hover,
input[type="submit"]:hover {
  background-color: -moz-buttonhoverface;
}

button:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
input[type="submit"]:hover {
  color: -moz-buttonhovertext;
}

button:active:hover,
input[type="color"]:-moz-system-metric(color-picker-available):active:hover,
input[type="reset"]:active:hover,
input[type="button"]:active:hover,
input[type="submit"]:active:hover {
  padding-block-start: 0px;
  padding-inline-end: 7px;
  padding-block-end: 0px;
  padding-inline-start: 9px;
  border-style: inset;
  background-color: ButtonFace;
}

button:active:hover,
input[type="reset"]:active:hover,
input[type="button"]:active:hover,
input[type="submit"]:active:hover {
  color: ButtonText;
}

button::-moz-focus-inner,
input[type="color"]:-moz-system-metric(color-picker-available)::-moz-focus-inner,
input[type="reset"]::-moz-focus-inner,
input[type="button"]::-moz-focus-inner,
input[type="submit"]::-moz-focus-inner,
input[type="file"] > button[type="button"]::-moz-focus-inner {
  /* Note this padding only affects the -moz-focus-inner ring, not the button itself */
  padding-block-start: 0px;
  padding-inline-end: 2px;
  padding-block-end: 0px;
  padding-inline-start: 2px;
  border: 1px dotted transparent;
}

button:-moz-focusring::-moz-focus-inner,
input[type="color"]:-moz-system-metric(color-picker-available):-moz-focusring::-moz-focus-inner,
input[type="reset"]:-moz-focusring::-moz-focus-inner,
input[type="button"]:-moz-focusring::-moz-focus-inner,
input[type="submit"]:-moz-focusring::-moz-focus-inner,
input[type="file"] > button[type="button"]:-moz-focusring::-moz-focus-inner {
  border-color: ButtonText;
}

button:disabled:active, button:disabled,
input[type="color"]:-moz-system-metric(color-picker-available):disabled:active,
input[type="color"]:-moz-system-metric(color-picker-available):disabled,
input[type="reset"]:disabled:active,
input[type="reset"]:disabled,
input[type="button"]:disabled:active,
input[type="button"]:disabled,
select:disabled > button,
select:disabled > button,
input[type="submit"]:disabled:active,
input[type="submit"]:disabled {
  /* The sum of border and padding on block-start and block-end
     must be the same here and for text inputs */
  padding-block-start: 0px;
  padding-inline-end: 8px;
  padding-block-end: 0px;
  padding-inline-start: 8px;
  border: 2px outset ThreeDLightShadow;
  cursor: inherit;
}

button:disabled:active, button:disabled,
input[type="reset"]:disabled:active,
input[type="reset"]:disabled,
input[type="button"]:disabled:active,
input[type="button"]:disabled,
select:disabled > button,
select:disabled > button,
input[type="submit"]:disabled:active,
input[type="submit"]:disabled {
  color: GrayText;
}

 /*
  * Make form controls inherit 'unicode-bidi' transparently as required by
  *  their various anonymous descendants and pseudo-elements:
  *
  * <textarea> and <input type="text">:
  *  inherit into the XULScroll frame with class 'anonymous-div' which is a
  *  child of the text control.
  *
  * Buttons (either <button>, <input type="submit">, <input type="button">
  *          or <input type="reset">)
  *  inherit into the ':-moz-button-content' pseudo-element.
  *
  * <select>:
  *  inherit into the ':-moz-display-comboboxcontrol-frame' pseudo-element and
  *  the <optgroup>'s ':before' pseudo-element, which is where the label of
  *  the <optgroup> gets displayed. The <option>s don't use anonymous boxes,
  *  so they need no special rules.
  */
textarea > .anonymous-div,
input > .anonymous-div,
input::placeholder,
textarea::placeholder,
*|*::-moz-button-content,
*|*::-moz-display-comboboxcontrol-frame,
optgroup:before {
  unicode-bidi: inherit;
  text-overflow: inherit;
}

/**
 * Set default style for invalid elements.
 */
:not(output):-moz-ui-invalid {
  box-shadow: 0 0 1.5px 1px red;
}

:not(output):-moz-ui-invalid:-moz-focusring {
  box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
}

output:-moz-ui-invalid {
  color: red;
}

@media print {
  input, textarea, select, button {
    -moz-user-input: none !important;
  }

  input[type="file"] { height: 2em; }
}

progress {
  -moz-appearance: progressbar;
  display: inline-block;
  vertical-align: -0.2em;

  /* Default style in case of there is -moz-appearance: none; */
  border: 2px solid;
  /* #e6e6e6 is a light gray. */
  -moz-border-top-colors: ThreeDShadow #e6e6e6;
  -moz-border-right-colors: ThreeDHighlight #e6e6e6;
  -moz-border-bottom-colors: ThreeDHighlight #e6e6e6;
  -moz-border-left-colors: ThreeDShadow #e6e6e6;
  background-color: #e6e6e6;
}

::-moz-progress-bar {
  /* Prevent styling that would change the type of frame we construct. */
  display: inline-block ! important;
  float: none ! important;
  position: static ! important;
  overflow: visible ! important;
  box-sizing: border-box ! important;

  -moz-appearance: progresschunk;
  height: 100%;
  width: 100%;

  /* Default style in case of there is -moz-appearance: none; */
  background-color: #0064b4; /* blue */
}

meter {
  -moz-appearance: meterbar;
  display: inline-block;
  vertical-align: -0.2em;

  background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%);
}

::-moz-meter-bar {
  /* Block styles that would change the type of frame we construct. */
  display: inline-block ! important;
  float: none ! important;
  position: static ! important;
  overflow: visible ! important;

  -moz-appearance: meterchunk;
  height: 100%;
  width: 100%;
}

:-moz-meter-optimum::-moz-meter-bar {
  /* green. */
  background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%);
}
:-moz-meter-sub-optimum::-moz-meter-bar {
  /* orange. */
  background: linear-gradient(#fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%);
}
:-moz-meter-sub-sub-optimum::-moz-meter-bar {
  /* red. */
  background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
}

input[type=range] {
  -moz-appearance: range;
  display: inline-block;
  inline-size: 12em;
  block-size: 1.3em;
  margin-inline-start: 0.7em;
  margin-inline-end: 0.7em;
  margin-block-start: 0;
  margin-block-end: 0;
  /* Override some rules that apply on all input types: */
  cursor: default;
  background: none;
  border: none;
  -moz-binding: none; /* we don't want any of platformHTMLBindings.xml#inputFields */
  /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
  -moz-user-select: none ! important;
}

input[type=range][orient=block] {
  inline-size: 1.3em;
  block-size: 12em;
  margin-inline-start: 0;
  margin-inline-end: 0;
  margin-block-start: 0.7em;
  margin-block-end: 0.7em;
}

input[type=range][orient=horizontal] {
  width: 12em;
  height: 1.3em;
  margin: 0 0.7em;
}

input[type=range][orient=vertical] {
  width: 1.3em;
  height: 12em;
  margin: 0.7em 0;
}

/**
 * Ideally we'd also require :-moz-focusring here, but that doesn't currently
 * work. Instead we only use the -moz-focus-outer border style if
 * NS_EVENT_STATE_FOCUSRING is set (the check is in
 * nsRangeFrame::BuildDisplayList).
 */
input[type=range]::-moz-focus-outer {
  border: 1px dotted black;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling the thumb without worrying about the
 * logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored.
 *
 * If content authors want to have a vertical range, they will also need to
 * set the width/height of this pseudo-element.
 */
input[type=range]::-moz-range-track {
  /* Prevent styling that would change the type of frame we construct. */
  display: inline-block !important;
  float: none !important;
  position: static !important;
  border: none;
  background-color: #999;
  inline-size: 100%;
  block-size: 0.2em;
  /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
  -moz-user-select: none ! important;
}

input[type=range][orient=block]::-moz-range-track {
  inline-size: 0.2em;
  block-size: 100%;
}

input[type=range][orient=horizontal]::-moz-range-track {
  width: 100%;
  height: 0.2em;
}

input[type=range][orient=vertical]::-moz-range-track {
  width: 0.2em;
  height: 100%;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling this pseudo-element without worrying
 * about the logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored. Additionally, if the range is horizontal, the width
 * property is ignored, and if the range range is vertical, the height property
 * is ignored.
 */
input[type=range]::-moz-range-progress {
  /* Prevent styling that would change the type of frame we construct. */
  display: inline-block !important;
  float: none !important;
  position: static !important;
  /* Since one of width/height will be ignored, this just sets the "other"
     dimension.
   */
  width: 0.2em;
  height: 0.2em;
  /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
  -moz-user-select: none ! important;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling the thumb without worrying about the
 * logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored.
 */
input[type=range]::-moz-range-thumb {
  /* Native theming is atomic for range. Set -moz-appearance on the range
   * to get rid of it. The thumb's -moz-appearance is fixed.
   */
  -moz-appearance: range-thumb !important;
  /* Prevent styling that would change the type of frame we construct. */
  display: inline-block !important;
  float: none !important;
  position: static !important;
  width: 1em;
  height: 1em;
  border: 0.1em solid #999;
  border-radius: 0.5em;
  background-color: #F0F0F0;
  /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
  -moz-user-select: none ! important;
}

/* As a temporary workaround until bug 677302 the rule for input[type=number]
 * has moved to number-control.css
 */

input[type=number]::-moz-number-wrapper {
  /* Prevent styling that would change the type of frame we construct. */
  display: flex;
  float: none !important;
  position: static !important;
  block-size: 100%;
}

input[type=number]::-moz-number-text {
  display: block; /* Flex items must be block-level. Normally we do fixup in
                     the style system to ensure this, but that fixup is disabled
                     inside of form controls. So, we hardcode display here. */
  -moz-appearance: none;
  /* work around autofocus bug 939248 on initial load */
  -moz-user-modify: read-write;
  /* This pseudo-element is also an 'input' element (nested inside and
   * distinct from the <input type=number> element) so we need to prevent the
   * explicit setting of 'text-align' by the general CSS rule for 'input'
   * above. We want to inherit its value from its <input type=number>
   * ancestor, not have that general CSS rule reset it.
   */
  text-align: inherit;
  flex: 1;
  min-inline-size: 0;
  padding: 0;
  border: 0;
  margin: 0;
}

input[type=number]::-moz-number-spin-box {
  writing-mode: horizontal-tb;
  display: flex;
  flex-direction: column;
  /* The Window's Theme's spin buttons have a very narrow minimum width, so
   * make it something reasonable:
   */
  width: 16px;
  /* If the spin-box has auto height, it ends up enlarging the default height
   * of the control, so we limit it to 1em here. The height doesn't affect
   * the rendering of the spinner-buttons; it's only for layout purposes.
   *
   * This is a temporary hack until we implement better positioning for the
   * spin-box in vertical mode; it works OK at default size but less well
   * if the font-size is made substantially larger or smaller. (Bug 1175074.)
   */
  max-height: 1em;
  align-self: center;
  justify-content: center;
}

input[type=number]::-moz-number-spin-up {
  writing-mode: horizontal-tb;
  -moz-appearance: spinner-upbutton;
  display: block; /* bug 926670 */
  flex: none;
  cursor: default;
  /* Style for when native theming is off: */
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,4 L3,0 5,4" fill="dimgrey"/></svg>');
  background-repeat: no-repeat;
  background-position: center bottom;
  border: 1px solid darkgray;
  border-bottom: none;
  /* [JK] I think the border-*-*-radius properties here can remain physical,
     as we probably don't want to turn the spinner sideways in vertical writing mode */
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}

input[type=number]::-moz-number-spin-down {
  writing-mode: horizontal-tb;
  -moz-appearance: spinner-downbutton;
  display: block; /* bug 926670 */
  flex: none;
  cursor: default;
  /* Style for when native theming is off: */
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,1 L3,5 5,1" fill="dimgrey"/></svg>');
  background-repeat: no-repeat;
  background-position: center top;
  border: 1px solid darkgray;
  border-top: none;
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
}

input[type="number"] > div > div > div:hover {
  /* give some indication of hover state for the up/down buttons */
  background-color: lightblue;
}

input[type="date"],
input[type="time"] {
  overflow: hidden !important;
}

:-moz-autofill, :-moz-autofill-preview {
  filter: grayscale(21%) brightness(88%) contrast(161%) invert(10%) sepia(40%) saturate(206%);
}
:-moz-autofill-preview {
  color: GrayText;
}
PK
!<"%-``$chrome/toolkit/res/hiddenWindow.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 html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta charset="utf-8"><title></title></head><body></body></html>PK
!<'/GGchrome/toolkit/res/html.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

/* bidi */

:-moz-has-dir-attr {
  unicode-bidi: isolate;
}
:-moz-dir-attr-rtl {
  direction: rtl;
}
:-moz-dir-attr-ltr {
  direction: ltr;
}

:-moz-dir-attr-like-auto:dir(ltr) { direction: ltr; }
:-moz-dir-attr-like-auto:dir(rtl) { direction: rtl; }

/* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi:
 *
 * "When a block element that does not have a dir attribute is transformed to
 * the style of an inline element by a style sheet, the resulting presentation
 * should be equivalent, in terms of bidirectional formatting, to the
 * formatting obtained by explicitly adding a dir attribute (assigned the
 * inherited value) to the transformed element."
 *
 * and the rules in http://dev.w3.org/html5/spec/rendering.html#rendering
 */

address,
article,
aside,
blockquote,
body,
caption,
center,
col,
colgroup,
dd,
dir,
div,
dl,
dt,
fieldset,
figcaption,
figure,
footer,
form,
h1,
h2,
h3,
h4,
h5,
h6,
header,
hgroup,
hr,
html,
legend,
li,
listing,
main,
marquee,
menu,
nav,
noframes,
ol,
p,
plaintext,
pre,
section,
summary,
table,
tbody,
td,
tfoot,
th,
thead,
tr,
ul,
xmp {
  unicode-bidi: isolate;
}

bdi, output {
  unicode-bidi: isolate;
}
/* We need the "bdo:-moz-has-dir-attr" bit because "bdo" has lower
   specificity than the ":-moz-has-dir-attr" selector above. */
bdo, bdo:-moz-has-dir-attr {
  unicode-bidi: isolate-override;
}
textarea:-moz-dir-attr-like-auto,
pre:-moz-dir-attr-like-auto {
    unicode-bidi: plaintext;
}

/* blocks */

article,
aside,
details,
div,
dt,
figcaption,
footer,
form,
header,
hgroup,
html,
main,
nav,
section,
summary {
  display: block;
}

body {
  display: block;
  margin: 8px;
}

p, dl, multicol {
  display: block;
  margin-block-start: 1em;
  margin-block-end: 1em;
}

dd {
  display: block;
  margin-inline-start: 40px;
}

blockquote, figure {
  display: block;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 40px;
  margin-inline-end: 40px;
}

address {
  display: block;
  font-style: italic;
}

center {
  display: block;
  text-align: -moz-center;
}

blockquote[type=cite] {
  display: block;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 0;
  margin-inline-end: 0;
  padding-inline-start: 1em;
  border-inline-start: solid;
  border-color: blue;
  border-width: thin;
}

span[_moz_quote=true] {
  color: blue;
}

pre[_moz_quote=true] {
  color: blue;
}

h1 {
  display: block;
  font-size: 2em;
  font-weight: bold;
  margin-block-start: .67em;
  margin-block-end: .67em;
}

h2,
:-moz-any(article, aside, nav, section)
h1 {
  display: block;
  font-size: 1.5em;
  font-weight: bold;
  margin-block-start: .83em;
  margin-block-end: .83em;
}

h3,
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
h1 {
  display: block;
  font-size: 1.17em;
  font-weight: bold;
  margin-block-start: 1em;
  margin-block-end: 1em;
}

h4,
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
h1 {
  display: block;
  font-size: 1.00em;
  font-weight: bold;
  margin-block-start: 1.33em;
  margin-block-end: 1.33em;
}

h5,
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
h1 {
  display: block;
  font-size: 0.83em;
  font-weight: bold;
  margin-block-start: 1.67em;
  margin-block-end: 1.67em;
}

h6,
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
:-moz-any(article, aside, nav, section)
h1 {
  display: block;
  font-size: 0.67em;
  font-weight: bold;
  margin-block-start: 2.33em;
  margin-block-end: 2.33em;
}

listing {
  display: block;
  font-family: -moz-fixed;
  font-size: medium;
  white-space: pre;
  margin-block-start: 1em;
  margin-block-end: 1em;
}

xmp, pre, plaintext {
  display: block;
  font-family: -moz-fixed;
  white-space: pre;
  margin-block-start: 1em;
  margin-block-end: 1em;
}

/* tables */

table {
  display: table;
  border-spacing: 2px;
  border-collapse: separate;
  /* XXXldb do we want this if we're border-collapse:collapse ? */
  box-sizing: border-box;
  text-indent: 0;
}

table[align="left"] {
  float: left;
}

table[align="right"] {
  float: right;
  text-align: start;
}


/* border collapse rules */

  /* Set hidden if we have 'frame' or 'rules' attribute.
     Set it on all sides when we do so there's more consistency
     in what authors should expect */

  /* Put this first so 'border' and 'frame' rules can override it. */
table[rules] { 
  border-width: thin;
  border-style: hidden;
}

  /* 'border' before 'frame' so 'frame' overrides
      A border with a given value should, of course, pass that value
      as the border-width in pixels -> attr mapping */

  /* :-moz-table-border-nonzero is like [border]:not([border="0"]) except it
     also checks for other zero-like values according to HTML attribute
     parsing rules */
table:-moz-table-border-nonzero { 
  border-width: thin;
  border-style: outset;
}

table[frame] {
  border: thin hidden;
}

/* specificity must beat table:-moz-table-border-nonzero rule above */
table[frame="void"]   { border-style: hidden; }
table[frame="above"]  { border-style: outset hidden hidden hidden; }
table[frame="below"]  { border-style: hidden hidden outset hidden; }
table[frame="lhs"]    { border-style: hidden hidden hidden outset; }
table[frame="rhs"]    { border-style: hidden outset hidden hidden; }
table[frame="hsides"] { border-style: outset hidden; }
table[frame="vsides"] { border-style: hidden outset; }
table[frame="box"],
table[frame="border"] { border-style: outset; }

 
/* Internal Table Borders */

  /* 'border' cell borders first */

table:-moz-table-border-nonzero > * > tr > td,
table:-moz-table-border-nonzero > * > tr > th,
table:-moz-table-border-nonzero > * > td,
table:-moz-table-border-nonzero > * > th,
table:-moz-table-border-nonzero > td,
table:-moz-table-border-nonzero > th
{
  border-width: thin;
  border-style: inset;
}

/* collapse only if rules are really specified */
table[rules]:not([rules="none"]):not([rules=""]) {
  border-collapse: collapse;
}

/* only specified rules override 'border' settings  
  (increased specificity to achieve this) */
table[rules]:not([rules=""])> tr > td,
table[rules]:not([rules=""])> * > tr > td,
table[rules]:not([rules=""])> tr > th,
table[rules]:not([rules=""])> * > tr > th,
table[rules]:not([rules=""])> td,
table[rules]:not([rules=""])> th
{
  border-width: thin;
  border-style: none;
}


table[rules][rules="none"]  > tr > td,
table[rules][rules="none"] > * > tr > td,
table[rules][rules="none"] > tr > th,
table[rules][rules="none"] > * > tr > th,
table[rules][rules="none"] > td,
table[rules][rules="none"] > th
{
  border-width: thin;
  border-style: none;
}

table[rules][rules="all"] > tr > td,
table[rules][rules="all"] > * > tr > td,
table[rules][rules="all"] > tr > th,
table[rules][rules="all"] > * > tr > th,
table[rules][rules="all"] > td,
table[rules][rules="all"] > th 
{
  border-width: thin;
  border-style: solid;
}

table[rules][rules="rows"] > tr,
table[rules][rules="rows"] > * > tr {
  border-block-start-width: thin;
  border-block-end-width: thin;
  border-block-start-style: solid;
  border-block-end-style: solid;
}


table[rules][rules="cols"] > tr > td,
table[rules][rules="cols"] > * > tr > td,
table[rules][rules="cols"] > tr > th,
table[rules][rules="cols"] > * > tr > th {
  border-inline-start-width: thin;
  border-inline-end-width: thin;
  border-inline-start-style: solid;
  border-inline-end-style: solid;
}

table[rules][rules="groups"] > colgroup {
  border-inline-start-width: thin;
  border-inline-end-width: thin;
  border-inline-start-style: solid;
  border-inline-end-style: solid;
}
table[rules][rules="groups"] > tfoot,
table[rules][rules="groups"] > thead,
table[rules][rules="groups"] > tbody {
  border-block-start-width: thin;
  border-block-end-width: thin;
  border-block-start-style: solid;
  border-block-start-style: solid;
}
  
  
/* caption inherits from table not table-outer */  
caption {
  display: table-caption;
  text-align: center;
}

table[align="center"] > caption {
  margin-inline-start: auto;
  margin-inline-end: auto;
}

table[align="center"] > caption[align="left"]:dir(ltr) {
  margin-inline-end: 0;
}
table[align="center"] > caption[align="left"]:dir(rtl) {
  margin-inline-start: 0;
}

table[align="center"] > caption[align="right"]:dir(ltr) {
  margin-inline-start: 0;
}
table[align="center"] > caption[align="right"]:dir(rtl) {
  margin-inline-end: 0;
}

tr {
  display: table-row;
  vertical-align: inherit;
}

col {
  display: table-column;
}

colgroup {
  display: table-column-group;
}

tbody {
  display: table-row-group;
  vertical-align: middle;
}

thead {
  display: table-header-group;
  vertical-align: middle;
}

tfoot {
  display: table-footer-group;
  vertical-align: middle;
}

/* for XHTML tables without tbody */
table > tr {
  vertical-align: middle;
}

td { 
  display: table-cell;
  vertical-align: inherit;
  text-align: inherit; 
  padding: 1px;
}

th {
  display: table-cell;
  vertical-align: inherit;
  font-weight: bold;
  padding: 1px;
}

tr > form:-moz-is-html, tbody > form:-moz-is-html,
thead > form:-moz-is-html, tfoot > form:-moz-is-html,
table > form:-moz-is-html {
  /* Important: don't show these forms in HTML */
  display: none !important;
}

table[bordercolor] > tbody,
table[bordercolor] > thead,
table[bordercolor] > tfoot,
table[bordercolor] > col,
table[bordercolor] > colgroup,
table[bordercolor] > tr,
table[bordercolor] > * > tr,
table[bordercolor]  > tr > td,
table[bordercolor] > * > tr > td,
table[bordercolor]  > tr > th,
table[bordercolor] > * > tr > th {
  border-color: inherit;
}

/* inlines */

q:before {
  content: open-quote;
}

q:after {
  content: close-quote;
}

b, strong {
  font-weight: bolder;
}

i, cite, em, var, dfn {
  font-style: italic;
}

tt, code, kbd, samp {
  font-family: -moz-fixed;
}

u, ins {
  text-decoration: underline;
}

s, strike, del {
  text-decoration: line-through;
}

big {
  font-size: larger;
}

small {
  font-size: smaller;
}

sub {
  vertical-align: sub;
  font-size: smaller;
  line-height: normal;
}

sup {
  vertical-align: super;
  font-size: smaller;
  line-height: normal;
}

nobr {
  white-space: nowrap;
}

mark {
  background: yellow;
  color: black;
}

/* titles */
abbr[title], acronym[title] {
  text-decoration: dotted underline;
}

/* lists */

ul, menu, dir {
  display: block;
  list-style-type: disc;
  margin-block-start: 1em;
  margin-block-end: 1em;
  padding-inline-start: 40px;
}

menu[type="context"] {
  display: none !important;
}

ol {
  display: block;
  list-style-type: decimal;
  margin-block-start: 1em;
  margin-block-end: 1em;
  padding-inline-start: 40px;
}

li {
  display: list-item;
  text-align: match-parent;
}

/* nested lists have no top/bottom margins */
:-moz-any(ul, ol, dir, menu, dl) ul,
:-moz-any(ul, ol, dir, menu, dl) ol,
:-moz-any(ul, ol, dir, menu, dl) dir,
:-moz-any(ul, ol, dir, menu, dl) menu,
:-moz-any(ul, ol, dir, menu, dl) dl {
  margin-block-start: 0;
  margin-block-end: 0;
}

/* 2 deep unordered lists use a circle */
:-moz-any(ol, ul, menu, dir) ul,
:-moz-any(ol, ul, menu, dir) menu,
:-moz-any(ol, ul, menu, dir) dir {
  list-style-type: circle;
}

/* 3 deep (or more) unordered lists use a square */
:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) ul,
:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) menu,
:-moz-any(ol, ul, menu, dir) :-moz-any(ol, ul, menu, dir) dir {
  list-style-type: square;
}


/* leafs */

/* <hr> noshade and color attributes are handled completely by
 * the nsHTMLHRElement attribute mapping code
 */
hr {
  display: block;
  border: 1px inset;
  margin-block-start: 0.5em;
  margin-block-end: 0.5em;
  margin-inline-start: auto;
  margin-inline-end: auto;
  color: gray;
  -moz-float-edge: margin-box;
  box-sizing: border-box;
}

hr[size="1"] {
  border-style: solid none none none;
}

img:-moz-broken::before, input:-moz-broken::before,
img:-moz-user-disabled::before, input:-moz-user-disabled::before,
img:-moz-loading::before, input:-moz-loading::before,
applet:-moz-empty-except-children-with-localname(param):-moz-broken::before,
applet:-moz-empty-except-children-with-localname(param):-moz-user-disabled::before {
  content: -moz-alt-content !important;
  unicode-bidi: isolate;
}

:-moz-any(object,applet):-moz-any(:-moz-broken,:-moz-user-disabled) > *|* {
  /*
    Inherit in the object's alignment so that if we aren't aligned explicitly
    we'll end up in the right place vertically.  See bug 36997.  Note that this
    is not !important because we _might_ be aligned explicitly.
  */
  vertical-align: inherit;
}

img:-moz-suppressed, input:-moz-suppressed, object:-moz-suppressed,
embed:-moz-suppressed, applet:-moz-suppressed {
  /*
    Set visibility too in case the page changes display.  Note that we _may_
    want to just set visibility and not display, in general, if we find that
    display:none breaks too many layouts.  And if we decide we really do want
    people to be able to right-click blocked images, etc, we need to set
    neither one, and hack the painting code.... :(
   */
  display: none !important;
  visibility: hidden !important;
}

img[usemap], object[usemap] {
  color: blue;
}

frameset {
  display: block ! important;
  overflow: -moz-hidden-unscrollable;
  position: static ! important;
  float: none ! important;
  border: none ! important;
}

link { 
  display: none;
}

frame {
  border-radius: 0 ! important;
}

iframe {
  border: 2px inset;
}

noframes {
  display: none;
}

spacer {
  position: static ! important;
  float: none ! important;
}

canvas {
  -moz-user-select: none;
}

/* focusable content: anything w/ tabindex >=0 is focusable, but we
   skip drawing a focus outline on a few things that handle it
   themselves. */
:-moz-focusring:not(input):not(button):not(select):not(textarea):not(iframe):not(frame):not(body):not(html) {
  /* Don't specify the outline-color, we should always use initial value. */
   outline: 1px dotted;
}

/* hidden elements */
base, basefont, datalist, head, meta, script, style, title,
noembed, param, template {
   display: none;
}

area {
  /* Don't give it frames other than its imageframe */
  display: none ! important;
}

iframe:fullscreen {
  /* iframes in full-screen mode don't show a border. */
  border: none !important;
  padding: 0 !important;
}

/* media elements */
video > xul|videocontrols, audio > xul|videocontrols {
  display: -moz-box;
  -moz-box-orient: vertical;
  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#videoControls");
}

video:not([controls]) > xul|videocontrols,
audio:not([controls]) > xul|videocontrols {
  visibility: hidden;
  -moz-binding: none;
}

video {
  object-fit: contain;
}

video > img:-moz-native-anonymous {
  /* Video poster images should render with the video element's "object-fit" &
     "object-position" properties */
  object-fit: inherit !important;
  object-position: inherit !important;
}

audio:not([controls]) {
  display: none;
}

audio[controls] {
  /* This ensures that intrinsic sizing can reliably shrinkwrap our
      controls (which are also always horizontal) and produce a
      reasonable intrinsic size from them. */
  writing-mode: horizontal-tb !important;
}

*|*::-moz-html-canvas-content {
  display: block !important;
  /* we want to be an absolute and fixed container */
  transform: translate(0) !important;
}

video > .caption-box {
  width: 100%;
  height: 100%;
  position: relative;
}

/* ::cue default settings */
::cue {
  color: rgba(255, 255, 255, 1);
  white-space: pre-line;
  background-color: rgba(0, 0, 0, 0.8);
  font: var(--cue-font-size) sans-serif;
}

/* datetime elements */

input[type="time"] > xul|datetimebox {
  display: flex;
  -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
}

input[type="date"] > xul|datetimebox {
  display: flex;
  -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#date-input");
}

/* details & summary */
details > summary:first-of-type,
details > summary:-moz-native-anonymous {
  display: list-item;
  list-style: disclosure-closed inside;
}

details[open] > summary:first-of-type,
details[open] > summary:-moz-native-anonymous {
  list-style-type: disclosure-open;
}

details > summary:first-of-type > *|* {
  /* Cancel "list-style-position: inside" inherited from summary. */
  list-style-position: initial;
}

/* <dialog> element styles */

dialog {
  position: absolute;
  offset-inline-start: 0;
  offset-inline-end: 0;
  color: black;
  margin: auto;
  border-width: initial;
  border-style: solid;
  border-color: initial;
  border-image: initial;
  padding: 1em;
  background: white;
  width: -moz-fit-content;
}

dialog:not([open]) {
  display: none;
}

/* emulation of non-standard HTML <marquee> tag */
marquee {
  inline-size: -moz-available;
  display: inline-block;
  vertical-align: text-bottom;
  text-align: start;
  -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-horizontal');
}

marquee[direction="up"], marquee[direction="down"] {
  -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-vertical');
  block-size: 200px;
}

/* PRINT ONLY rules follow */
@media print {

  marquee { -moz-binding: none; }

}

/* Ruby */

ruby {
  display: ruby;
}
rb {
  display: ruby-base;
  white-space: nowrap;
}
rp {
  display: none;
}
rt {
  display: ruby-text;
}
rtc {
  display: ruby-text-container;
}
rtc, rt {
  white-space: nowrap;
  font-size: 50%;
  -moz-min-font-size-ratio: 50%;
  line-height: 1;
}
rtc, rt {
  text-emphasis: none;
}
rtc:lang(zh), rt:lang(zh) {
  ruby-align: center;
}
rtc:lang(zh-TW), rt:lang(zh-TW) {
  font-size: 30%; /* bopomofo */
  -moz-min-font-size-ratio: 30%;
}
rtc > rt {
  font-size: inherit;
}
ruby, rb, rt, rtc {
  unicode-bidi: isolate;
}
PK
!<UC$chrome/toolkit/res/loading-image.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤ
 9`?APѸZd%qlBv@aKrIJ)c>J)a%sFәwH)}Z}:Fq0Ƽ-PwY6'By<uby}Z+`\]{	X5oeaSQ{xޱIENDB`PK
!<k**chrome/toolkit/res/mathml.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/**************************************************************************/
/* namespace for MathML elements                                          */
/**************************************************************************/

@namespace url(http://www.w3.org/1998/Math/MathML);

/**************************************************************************/
/* <math> - outermost math element                                        */
/**************************************************************************/

math {
  writing-mode: horizontal-tb !important;
  direction: ltr;
  unicode-bidi: embed;
  display: inline;
  font-size: inherit;
  font-style: normal;
  font-family: serif;
  line-height: normal;
  word-spacing: normal;
  letter-spacing: normal;
  text-rendering: optimizeLegibility;
  -moz-float-edge: margin-box;
  -moz-math-display: inline;
}
math[mode="display"], math[display="block"] {
  display: block;
  text-align: -moz-center;
  -moz-math-display: block;
}
math[display="inline"] {
  display: inline;
  -moz-math-display: inline;
}
math[displaystyle="false"] {
  -moz-math-display: inline;
}
math[displaystyle="true"] {
  -moz-math-display: block;
}

/**************************************************************************/
/* Token elements                                                         */
/**************************************************************************/

ms {
  display: inline;
}
ms:before, ms:after {
  content: "\0022"
}
ms[lquote]:before {
  content: attr(lquote)
}
ms[rquote]:after {
  content: attr(rquote)
 }

/**************************************************************************/
/* Links                                                                  */
/**************************************************************************/
:any-link {
  text-decoration: none !important;
}

/**************************************************************************/
/* attributes common to all tags                                          */
/**************************************************************************/

/* These attributes are mapped to style in nsMathMLElement.cpp:

   - background -> background                             (deprecated)
   - color -> color                                       (deprecated)
   - fontfamily -> font-family                            (deprecated)
   - fontsize -> font-size                                (deprecated)
   - fontstyle -> font-style                              (deprecated)
   - fontweight -> font-weight                            (deprecated)
   - mathvariant -> -moz-math-variant
   - scriptsizemultiplier -> -moz-script-size-multiplier
   - scriptminsize -> -moz-script-min-size
   - scriptlevel -> -moz-script-level
   - mathsize -> font-size
   - mathcolor -> color
   - mathbackground -> background

*/


/**************************************************************************/
/* merror                                                                 */
/**************************************************************************/

merror {
  display: block;
  font-family: sans-serif;
  font-weight: bold;
  white-space: pre;
  margin: 1em;
  padding: 1em;
  border-width: thin;
  border-style: inset;
  border-color: red;
  font-size: 14pt;
  background-color: lightyellow;
}

/**************************************************************************/
/* mtable and its related tags                                            */
/**************************************************************************/

mtable {
  display: inline-table;
  border-collapse: separate;
  border-spacing: 0;
  text-indent: 0;
}
mtable[frame="none"] {
  border: none;
}
mtable[frame="solid"] {
  border: solid thin;
}
mtable[frame="dashed"] {
  border: dashed thin;
}

mtr, mlabeledtr {
  display: table-row;
  vertical-align: baseline;
}

mtd {
  display: table-cell;
  vertical-align: inherit;
  text-align: -moz-center;
  white-space: nowrap;
}

/* Don't support m(labeled)tr without mtable, nor mtd without m(labeled)tr */
:not(mtable) > mtr,
:not(mtable) > mlabeledtr,
:not(mtr):not(mlabeledtr) > mtd {
  display: none !important;
}

/* Hide the label because mlabeledtr is not supported yet (bug 356870). This
   rule can be overriden by users. */
mlabeledtr > mtd:first-child {
    display: none;
}

/**********************************************************************/
/* rules to achieve the default spacing between cells. When rowspacing,
   columnspacing and framespacing aren't set on mtable.  The back-end code
   will set the internal attributes depending on the cell's position.
   When they are set, the spacing behaviour is handled outside of CSS */
mtd {
  padding-right: 0.4em;  /* half of columnspacing[colindex] */
  padding-left: 0.4em;   /* half of columnspacing[colindex-1] */
  padding-bottom: 0.5ex; /* half of rowspacing[rowindex] */
  padding-top: 0.5ex;    /* half of rowspacing[rowindex-1] */
}
/* turn off the spacing at the periphery of boundary cells */
mtr:first-child > mtd {
  padding-top: 0ex;
}
mtr:last-child > mtd {
  padding-bottom: 0ex;
}
mtd:first-child {
  padding-inline-start: 0em;
}
mtd:last-child {
  padding-inline-end: 0em;
}
/* re-instate the spacing if the table has a surrounding frame */
mtable[frame="solid"] > mtr:first-child > mtd,
mtable[frame="dashed"] > mtr:first-child > mtd {
  padding-top: 0.5ex; /* framespacing.top */
}
mtable[frame="solid"] > mtr:last-child > mtd,
mtable[frame="dashed"] > mtr:last-child > mtd {
  padding-bottom: 0.5ex; /* framespacing.bottom */
}
mtable[frame="solid"] > mtr > mtd:first-child,
mtable[frame="dashed"] > mtr > mtd:first-child {
  padding-inline-start: 0.4em; /* framespacing.left (or right in rtl)*/
}
mtable[frame="solid"] > mtr > mtd:last-child,
mtable[frame="dashed"] > mtr > mtd:last-child {
  padding-inline-end: 0.4em; /* framespacing.right (or left in rtl)*/
}

mtable[rowspacing] > mtr > mtd,
mtable[columnspacing] > mtr > mtd,
mtable[framespacing] > mtr > mtd {
  /* Spacing handled outside of CSS */
  padding: 0px;
}

/**************************************************************************/
/* This rule is used to give a style context suitable for nsMathMLChars.
   We don't actually style -moz-math-anonymous by default. */
/*
::-moz-math-anonymous {
}
*/

/**********************************************************************/
/* This is used when wrapping non-MathML inline elements inside math. */
*|*::-moz-mathml-anonymous-block {
  display: inline-block !important;
  position: static !important;
  text-indent: 0;
}

/**************************************************************************/
/* Controlling Displaystyle and Scriptlevel                               */
/**************************************************************************/

/*
  http://www.w3.org/Math/draft-spec/chapter3.html#presm.scriptlevel

  The determination of -moz-math-display for <math> involves the displaystyle
  and display attributes. See the <math> section above.
*/

/*
  Map mstyle@displaystyle to -moz-math-display.
*/
mstyle[displaystyle="false"] {
  -moz-math-display: inline;
}
mstyle[displaystyle="true"] {
  -moz-math-display: block;
}

/*  munder, mover and munderover change the scriptlevels of their children
   using -moz-math-increment-script-level because regular CSS rules are
   insufficient to control when the scriptlevel should be incremented. All other
   cases can be described using regular CSS, so we do it this way because it's
   more efficient and less code. */
:-moz-math-increment-script-level { -moz-script-level: +1; }

/*
   The mfrac element sets displaystyle to "false", or if it was already false
   increments scriptlevel by 1, within numerator and denominator.
*/   
mfrac > * {
    -moz-script-level: auto;
    -moz-math-display: inline;
}

/*
   The mroot element increments scriptlevel by 2, and sets displaystyle to
   "false", within index, but leaves both attributes unchanged within base.
   The msqrt element leaves both attributes unchanged within its argument.
*/
mroot > :not(:first-child) {
    -moz-script-level: +2;
    -moz-math-display: inline;
}

/*
   The msub element [...] increments scriptlevel by 1, and sets displaystyle to
   "false", within subscript, but leaves both attributes unchanged within base.

   The msup element [...] increments scriptlevel by 1, and sets displaystyle to
   "false", within superscript, but leaves both attributes unchanged within
   base.

   The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
   to "false", within subscript and superscript, but leaves both attributes
   unchanged within base.

   The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
   to "false", within each of its arguments except base, but leaves both
   attributes unchanged within base.
 */
msub > :not(:first-child),
msup > :not(:first-child),
msubsup > :not(:first-child),
mmultiscripts > :not(:first-child) {
    -moz-script-level: +1;
    -moz-math-display: inline;
}

/*
   The munder element [...] always sets displaystyle to "false" within the
   underscript, but increments scriptlevel by 1 only when accentunder is
   "false". Within base, it always leaves both attributes unchanged.

   The mover element [...] always sets displaystyle to "false" within
   overscript, but increments scriptlevel by 1 only when accent is "false".
   Within base, it always leaves both attributes unchanged.

   The munderover [..] always sets displaystyle to "false" within underscript
   and overscript, but increments scriptlevel by 1 only when accentunder or
   accent, respectively, are "false". Within base, it always leaves both
   attributes unchanged.
*/
munder > :not(:first-child),
mover > :not(:first-child),
munderover > :not(:first-child) {
    -moz-math-display: inline;
}

/*
   The displaystyle attribute is allowed on the mtable element to set the
   inherited value of the attribute. If the attribute is not present, the
   mtable element sets displaystyle to "false" within the table elements.
*/
mtable { -moz-math-display: inline; }
mtable[displaystyle="true"] { -moz-math-display: block; }

/*
   The mscarries element sets displaystyle to "false", and increments
   scriptlevel by 1, so the children are typically displayed in a smaller font.
   XXXfredw: This element is not implemented yet. See bug 534967.
mscarries {
  -moz-script-level: +1;
  -moz-math-display: inline;
}
*/

/* "The mphantom element renders invisibly, but with the same size and other
   dimensions, including baseline position, that its contents would have if
   they were rendered normally.".
   Also, we do not expose the <mphantom> element to the accessible tree
   (see bug 1108378). */
mphantom {
    visibility: hidden;
}
PK
!<%uuchrome/toolkit/res/noframes.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This sheet is added to the style set for documents with frames disabled */

noframes {
  display: block;
}

frame, frameset, iframe {
  display: none !important;
}
PK
!<C>FFchrome/toolkit/res/noscript.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This sheet is added to the style set for documents with script disabled */

noscript {
  display: none !important;
}
PK
!<tN%chrome/toolkit/res/number-control.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This file exists purely because we need the styling for input[type=number]
 * to apply only if the pref dom.forms.number is true. Once bug 677302 is
 * fixed this rule can move back to forms.css.
 */

input[type="number"] {
  -moz-appearance: number-input;
  /* Has to revert some properties applied by the generic input rule. */
  -moz-binding: none;
  inline-size: 20ch; /* It'd be nice if this matched the default inline-size
                        of <input type=text>, but that's not easy to achieve
                        due to platform differences. */
}

PK
!<]Y chrome/toolkit/res/plaintext.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  -moz-control-character-visibility: visible;
}

/* Make text go with the rules of dir=auto, but allow it to be overriden if 'Switch Text Direction' is triggered */
html:not([dir]) pre { /* Not a UA sheet, so doesn't use :-moz-has-dir-attr */
  unicode-bidi: plaintext;
}
PK
!<Lw@$@$chrome/toolkit/res/quirk.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */


/* Quirk: make orphaned LIs have inside bullet (b=1049) */

/* force inside position for orphaned lis */
li {
  list-style-position: inside;
}

/* restore outside position for lists inside LIs */
li ul, li ol, li dir, li menu {
  list-style-position: outside;
}

/* undo previous two rules for properly nested lists */
  ul ul,   ul ol,   ul dir,   ul menu,   ul li,
  ol ul,   ol ol,   ol dir,   ol menu,   ol li,
 dir ul,  dir ol,  dir dir,  dir menu,  dir li,
menu ul, menu ol, menu dir, menu menu, menu li {
  list-style-position: inherit;
}


/* Quirk: ensure that we get proper padding if the very first
 * node in an LI is another UL or OL. This is an ugly way to
 * fix the problem, because it extends the LI up into what
 * would otherwise appear to be the ULs space. (b=38832) */

/* Note: this fix will fail once we implement marker box
 * alignment correctly. */
li > ul:-moz-first-node,
li > ol:-moz-first-node {
  padding-block-start: 1em;
}


table {
  text-align: start;
  white-space: normal; /* compatible with IE & spec */
  line-height: normal;

  /* Quirk: cut off all font inheritance in tables except for family. */
  font-size: initial;
  font-weight: initial;
  font-style: initial;
  font-variant: initial;
}

/*
 * Make table borders gray for compatibility with what other browsers do
 * in all modes, rather than using the foreground color.
 */
table, td, th, tr, thead, tbody, tfoot, colgroup, col {
  border-color: gray;
}


/* Quirk: collapse top margin of BODY and TD and bottom margin of TD */

/*
 * While it may seem simpler to use :-moz-first-node and :-moz-last-node without
 * tags, it's slower, since we have to do the :-moz-first-node or :-moz-last-node
 * check on every single element in the document.  If we list all the
 * element names for which the UA stylesheet specifies a margin, the
 * selectors will be hashed in the RuleHash and things will be much more
 * efficient.
 */
body > p:-moz-first-node, td > p:-moz-first-node, th > p:-moz-first-node,
body > dl:-moz-first-node, td > dl:-moz-first-node, th > dl:-moz-first-node,
body > multicol:-moz-first-node, td > multicol:-moz-first-node, th > multicol:-moz-first-node,
body > blockquote:-moz-first-node, td > blockquote:-moz-first-node, th > blockquote:-moz-first-node,
body > h1:-moz-first-node, td > h1:-moz-first-node, th > h1:-moz-first-node,
body > h2:-moz-first-node, td > h2:-moz-first-node, th > h2:-moz-first-node,
body > h3:-moz-first-node, td > h3:-moz-first-node, th > h3:-moz-first-node,
body > h4:-moz-first-node, td > h4:-moz-first-node, th > h4:-moz-first-node,
body > h5:-moz-first-node, td > h5:-moz-first-node, th > h5:-moz-first-node,
body > h6:-moz-first-node, td > h6:-moz-first-node, th > h6:-moz-first-node,
body > listing:-moz-first-node, td > listing:-moz-first-node, th > listing:-moz-first-node,
body > plaintext:-moz-first-node, td > plaintext:-moz-first-node, th > plaintext:-moz-first-node,
body > xmp:-moz-first-node, td > xmp:-moz-first-node, th > xmp:-moz-first-node,
body > pre:-moz-first-node, td > pre:-moz-first-node, th > pre:-moz-first-node,
body > ul:-moz-first-node, td > ul:-moz-first-node, th > ul:-moz-first-node,
body > menu:-moz-first-node, td > menu:-moz-first-node, th > menu:-moz-first-node,
body > dir:-moz-first-node, td > dir:-moz-first-node, th > dir:-moz-first-node,
body > ol:-moz-first-node, td > ol:-moz-first-node, th > ol:-moz-first-node {
  margin-block-start: 0;
}

td > p:-moz-last-node, th > p:-moz-last-node {
  margin-block-end: 0;
}

/* Similar as above, but for empty elements
 *  collapse the bottom or top margins of empty elements
 *  - see bug 97361
 */
body > p:-moz-only-whitespace:-moz-first-node,
td > p:-moz-only-whitespace:-moz-first-node, th > p:-moz-only-whitespace:-moz-first-node,
body > dl:-moz-only-whitespace:-moz-first-node, td > dl:-moz-only-whitespace:-moz-first-node,
th > dl:-moz-only-whitespace:-moz-first-node, body > multicol:-moz-only-whitespace:-moz-first-node,
td > multicol:-moz-only-whitespace:-moz-first-node, th > multicol:-moz-only-whitespace:-moz-first-node,
body > blockquote:-moz-only-whitespace:-moz-first-node, td > blockquote:-moz-only-whitespace:-moz-first-node,
th > blockquote:-moz-only-whitespace:-moz-first-node, body > h1:-moz-only-whitespace:-moz-first-node,
td > h1:-moz-only-whitespace:-moz-first-node, th > h1:-moz-only-whitespace:-moz-first-node,
body > h2:-moz-only-whitespace:-moz-first-node, td > h2:-moz-only-whitespace:-moz-first-node,
th > h2:-moz-only-whitespace:-moz-first-node, body > h3:-moz-only-whitespace:-moz-first-node,
td > h3:-moz-only-whitespace:-moz-first-node, th > h3:-moz-only-whitespace:-moz-first-node,
body > h4:-moz-only-whitespace:-moz-first-node, td > h4:-moz-only-whitespace:-moz-first-node,
th > h4:-moz-only-whitespace:-moz-first-node, body > h5:-moz-only-whitespace:-moz-first-node,
td > h5:-moz-only-whitespace:-moz-first-node, th > h5:-moz-only-whitespace:-moz-first-node,
body > h6:-moz-only-whitespace:-moz-first-node, td > h6:-moz-only-whitespace:-moz-first-node,
th > h6:-moz-only-whitespace:-moz-first-node, body > listing:-moz-only-whitespace:-moz-first-node,
td > listing:-moz-only-whitespace:-moz-first-node, th > listing:-moz-only-whitespace:-moz-first-node,
body > plaintext:-moz-only-whitespace:-moz-first-node, td > plaintext:-moz-only-whitespace:-moz-first-node,
th > plaintext:-moz-only-whitespace:-moz-first-node, body > xmp:-moz-only-whitespace:-moz-first-node,
td > xmp:-moz-only-whitespace:-moz-first-node, th > xmp:-moz-only-whitespace:-moz-first-node,
body > pre:-moz-only-whitespace:-moz-first-node, td > pre:-moz-only-whitespace:-moz-first-node,
th > pre:-moz-only-whitespace:-moz-first-node, body > ul:-moz-only-whitespace:-moz-first-node,
td > ul:-moz-only-whitespace:-moz-first-node, th > ul:-moz-only-whitespace:-moz-first-node,
body > menu:-moz-only-whitespace:-moz-first-node, td > menu:-moz-only-whitespace:-moz-first-node,
th > menu:-moz-only-whitespace:-moz-first-node, body > dir:-moz-only-whitespace:-moz-first-node,
td > dir:-moz-only-whitespace:-moz-first-node, th > dir:-moz-only-whitespace:-moz-first-node,
body > ol:-moz-only-whitespace:-moz-first-node, td > ol:-moz-only-whitespace:-moz-first-node,
th > ol:-moz-only-whitespace:-moz-first-node {
  margin-block-end: 0;
}

td > p:-moz-only-whitespace:-moz-last-node, th > p:-moz-only-whitespace:-moz-last-node,
td > dl:-moz-only-whitespace:-moz-last-node, th > dl:-moz-only-whitespace:-moz-last-node,
td > multicol:-moz-only-whitespace:-moz-last-node, th > multicol:-moz-only-whitespace:-moz-last-node,
td > blockquote:-moz-only-whitespace:-moz-last-node, th > blockquote:-moz-only-whitespace:-moz-last-node,
td > h1:-moz-only-whitespace:-moz-last-node, th > h1:-moz-only-whitespace:-moz-last-node,
td > h2:-moz-only-whitespace:-moz-last-node, th > h2:-moz-only-whitespace:-moz-last-node,
td > h3:-moz-only-whitespace:-moz-last-node, th > h3:-moz-only-whitespace:-moz-last-node,
td > h4:-moz-only-whitespace:-moz-last-node, th > h4:-moz-only-whitespace:-moz-last-node,
td > h5:-moz-only-whitespace:-moz-last-node, th > h5:-moz-only-whitespace:-moz-last-node,
td > h6:-moz-only-whitespace:-moz-last-node, th > h6:-moz-only-whitespace:-moz-last-node,
td > listing:-moz-only-whitespace:-moz-last-node, th > listing:-moz-only-whitespace:-moz-last-node,
td > plaintext:-moz-only-whitespace:-moz-last-node, th > plaintext:-moz-only-whitespace:-moz-last-node,
td > xmp:-moz-only-whitespace:-moz-last-node, th > xmp:-moz-only-whitespace:-moz-last-node,
td > pre:-moz-only-whitespace:-moz-last-node, th > pre:-moz-only-whitespace:-moz-last-node,
td > ul:-moz-only-whitespace:-moz-last-node, th > ul:-moz-only-whitespace:-moz-last-node,
td > menu:-moz-only-whitespace:-moz-last-node, th > menu:-moz-only-whitespace:-moz-last-node,
td > dir:-moz-only-whitespace:-moz-last-node, th > dir:-moz-only-whitespace:-moz-last-node,
td > ol:-moz-only-whitespace:-moz-last-node, th > ol:-moz-only-whitespace:-moz-last-node {
  margin-block-start: 0;
}


/* Quirk: DD not in DL has text-indent instead of margin (b=5119) */

:not(dl) > dd {
  display: inline;
  margin: 0;
}

:not(dl) > dd:before {
  display: inline;
  white-space: pre;
  font-size: 1px;
  line-height: 0;
  content: "\A  ";
  margin-inline-end: 40px;
}


/* quirk to indent nested DL elements (b=8749) */

dl > dl {
  display: block;
  margin-inline-start: 40px;
}


/* Quirk: Make floated images have a margin  (b=58899) */
img[align=left]:dir(ltr), img[align=right]:dir(rtl) {
  margin-inline-end: 3px;
}

img[align=right]:dir(ltr), img[align=left]:dir(rtl) {
  margin-inline-start: 3px;
}

/*
 * Quirk: Use border-box box sizing for text inputs, password inputs, and
 * textareas.  (b=184478 on why we use content-box sizing in standards mode)
 */

/* Note that all other <input>s already use border-box
   sizing, so we're ok with this selector */
input:not([type=image]), textarea {
  box-sizing: border-box;
}

/* Quirk: give form margin for compat (b=41806) */
form {
  margin-block-end: 1em;
}
PK
!<aOg11chrome/toolkit/res/ua.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace parsererror url(http://www.mozilla.org/newlayout/xml/parsererror.xml);
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

/* magic -- some of these rules are important to keep pages from overriding
            them
*/

/* Tables */

*|*::-moz-table {
  display: table !important;
  box-sizing: border-box; /* XXX do we really want this? */
}

*|*::-moz-inline-table {
  display: inline-table !important;
  box-sizing: border-box; /* XXX do we really want this? */
}

*|*::-moz-table-wrapper {
  /* The inherited properties here need to be safe to have on both the
   * table and the table wrapper, generally because code ignores them
   * for the table. */
  display: inherit !important; /* table or inline-table */
  -moz-top-layer: inherit !important;
  margin: inherit ! important;
  padding: 0 ! important;
  border: none ! important;
  float: inherit;
  clear: inherit;
  position: inherit;
  top: inherit;
  right: inherit;
  bottom: inherit;
  left: inherit;
  z-index: inherit;
  page-break-before: inherit;
  page-break-after: inherit;
  page-break-inside: inherit;
  vertical-align: inherit; /* needed for inline-table */
  line-height: inherit; /* needed for vertical-align on inline-table */
  /* Bug 722777 */
  transform: inherit;
  transform-origin: inherit;
  /* Bug 724750 */
  backface-visibility: inherit;
  clip: inherit;
  /* When the table wrapper is a Flex/Grid item we need these: */
  align-self: inherit;
  justify-self: inherit;
  grid-column-start: inherit;
  grid-column-end: inherit;
  grid-row-start: inherit;
  grid-row-end: inherit;
  order: inherit;
}

*|*::-moz-table-row {
  display: table-row !important;
}

/* The ::-moz-table-column pseudo-element is for extra columns at the end
   of a table. */
*|*::-moz-table-column {
  display: table-column !important;
}

*|*::-moz-table-column-group {
  display: table-column-group !important;
}

*|*::-moz-table-row-group {
  display: table-row-group !important;
}

*|*::-moz-table-cell {
  display: table-cell !important;
  white-space: inherit;
}

/* Ruby */
*|*::-moz-ruby {
  display: ruby;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-base {
  display: ruby-base;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-text {
  display: ruby-text;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-base-container {
  display: ruby-base-container;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-text-container {
  display: ruby-text-container;
  unicode-bidi: isolate;
}

/* Lists */

*|*::-moz-list-bullet, *|*::-moz-list-number {
  display: inline;
  vertical-align: baseline;
  font-variant-numeric: tabular-nums;
  /* Prevent the element from being selected when clicking on the marker. */
  -moz-user-select: none;
}

/* SVG documents don't always load this file but they do have links.
 * If you change the link rules, consider carefully whether to make
 * the same changes to svg.css.
 */

/* Links */

*|*:any-link {
  cursor: pointer;
}

*|*:any-link:-moz-focusring {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

/* Miscellaneous */

*|*::-moz-cell-content {
  display: block !important;
  position: static !important;
  unicode-bidi: inherit;
  text-overflow: inherit;
  overflow-clip-box: inherit;
}

*|*::-moz-block-inside-inline-wrapper {
  display: block !important;
  /* we currently inherit from the inline that is split */
  position: inherit; /* static or relative or sticky */
  outline: inherit;
  outline-offset: inherit;
  clip-path: inherit;
  filter: inherit;
  mask: inherit;
  opacity: inherit;
  text-decoration: inherit;
  -moz-box-ordinal-group: inherit !important;
  overflow-clip-box: inherit;
  unicode-bidi: inherit;
  text-overflow: inherit;
  /* The properties below here don't apply if our position is static,
     and we do want them to have an effect if it's not, so it's fine
     to always inherit them. */
  top: inherit;
  left: inherit;
  bottom: inherit;
  right: inherit;
  z-index: inherit;
}

*|*::-moz-xul-anonymous-block {
  display: block ! important;
  position: static ! important;
  float: none ! important;
  -moz-box-ordinal-group: inherit !important;
  text-overflow: inherit;
  overflow-clip-box: inherit;
}

*|*::-moz-scrolled-content, *|*::-moz-scrolled-canvas,
*|*::-moz-scrolled-page-sequence {
  /* e.g., text inputs, select boxes */
  padding: inherit;
  /* The display doesn't affect the kind of frame constructed here.  This just
     affects auto-width sizing of the block we create. */
  display: block;
  /* make unicode-bidi inherit, otherwise it has no effect on text inputs and
     blocks with overflow: scroll; */
  unicode-bidi: inherit;
  text-overflow: inherit;
  /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
     ::-moz-fieldset-content/::-moz-button-content in forms.css */
  /* Multicol container */
  -moz-column-count: inherit;
  -moz-column-width: inherit;
  -moz-column-gap: inherit;
  -moz-column-rule: inherit;
  -moz-column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
  /* Do not change these. nsCSSFrameConstructor depends on them to create a good
     frame tree. */
  position: static !important;
  float: none !important;
  overflow-clip-box: inherit;
}

*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
  display: block !important;
  background-color: inherit;
}

*|*::-moz-viewport-scroll {
  overflow: auto;
  resize: both;
}

*|*::-moz-column-content {
  /* the column boxes inside a column-flowed block */
  /* make unicode-bidi inherit, otherwise it has no effect on column boxes */
  unicode-bidi: inherit;
  text-overflow: inherit;
  /* inherit the outer frame's display, otherwise we turn into an inline */
  display: inherit !important;
  /* Carry through our parent's height so that %-height children get
  their heights set */
  height: 100%;
}

*|*::-moz-anonymous-flex-item,
*|*::-moz-anonymous-grid-item {
  /* Anonymous blocks that wrap contiguous runs of text
   * inside of a flex or grid container. */
  display: block;
}

*|*::-moz-page-sequence, *|*::-moz-scrolled-page-sequence {
  /* Collection of pages in print/print preview. Visual styles may only appear
   * in print preview. */
  display: block !important;
  background: linear-gradient(#606060, #8a8a8a) fixed;
  height: 100%;
}

*|*::-moz-page {
  /* Individual page in print/print preview. Visual styles may only appear
   * in print preview. */
  display: block !important;
  background: white;
  box-shadow: 5px 5px 8px #202020;
  box-decoration-break: clone;
  margin: 0.125in 0.25in;
}

*|*::-moz-pagecontent {
  display: block !important;
  margin: auto;
}

*|*::-moz-pagebreak {
  display: block !important;
}

/* Printing */

@media print {

  * {
    cursor: default !important;
  }

}

*|*:fullscreen:not(:root) {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100% !important;
  height: 100% !important;
  margin: 0 !important;
  min-width: 0 !important;
  max-width: none !important;
  min-height: 0 !important;
  max-height: none !important;
  box-sizing: border-box !important;
  object-fit: contain;
  transform: none !important;
}

/* Selectors here should match the check in
 * nsViewportFrame.cpp:ShouldInTopLayerForFullscreen() */
*|*:fullscreen:not(:root):not(:-moz-browser-frame) {
  -moz-top-layer: top !important;
}

*|*::backdrop {
  -moz-top-layer: top !important;
  display: block;
  position: fixed;
  top: 0; left: 0;
  right: 0; bottom: 0;
}

*|*:-moz-full-screen:not(:root)::backdrop {
  background: black;
}

/* XML parse error reporting */

parsererror|parsererror {
  display: block;
  font-family: sans-serif;
  font-weight: bold;
  white-space: pre;
  margin: 1em;
  padding: 1em;
  border-width: thin;
  border-style: inset;
  border-color: red;
  font-size: 14pt;
  background-color: lightyellow;
  color: black;
}

parsererror|sourcetext {
  display: block;
  white-space: pre;
  font-family: -moz-fixed;
  margin-top: 2em;
  margin-bottom: 1em;
  color: red;
  font-weight: bold;
  font-size: 12pt;
}

div:-moz-native-anonymous.moz-accessiblecaret {
  /* Add transition effect to make caret size changing smoother. */
  transition-duration: 250ms;
  transition-property: width, height, margin-left;
}

div:-moz-native-anonymous.moz-accessiblecaret,
div:-moz-native-anonymous.moz-accessiblecaret > #text-overlay,
div:-moz-native-anonymous.moz-accessiblecaret > #image,
div:-moz-native-anonymous.moz-accessiblecaret > #bar {
  position: absolute;
  z-index: 2147483647;
}

div:-moz-native-anonymous.moz-accessiblecaret > #text-overlay,
div:-moz-native-anonymous.moz-accessiblecaret > #image {
  top: 0;
  width: 100%;

  /* Override this property in moz-custom-content-container to make dummy touch
   * listener work. */
  pointer-events: auto;
}

div:-moz-native-anonymous.moz-accessiblecaret > #image {
  background-position: center top;
  background-size: 100%;
  background-repeat: no-repeat;
  background-origin: content-box;
  height: 100%;
}

div:-moz-native-anonymous.moz-accessiblecaret > #bar {
  margin-left: 49%;
  background-color: #008aa0;
}

div:-moz-native-anonymous.moz-accessiblecaret.no-bar > #bar {
  display: none;
}

div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
  background-image: url("resource://gre-resources/accessiblecaret-normal@1x.png");
}

div:-moz-native-anonymous.moz-accessiblecaret.left > #text-overlay,
div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
  margin-left: -39%;
}

div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
  background-image: url("resource://gre-resources/accessiblecaret-tilt-left@1x.png");
}

div:-moz-native-anonymous.moz-accessiblecaret.right > #text-overlay,
div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
  margin-left: 41%;
}

div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
  background-image: url("resource://gre-resources/accessiblecaret-tilt-right@1x.png");
}

div:-moz-native-anonymous.moz-accessiblecaret.none {
  display: none;
}

@media (min-resolution: 1.5dppx) {
  div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
    background-image: url("resource://gre-resources/accessiblecaret-normal@1.5x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-left@1.5x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-right@1.5x.png");
  }
}

@media (min-resolution: 2dppx) {
  div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
    background-image: url("resource://gre-resources/accessiblecaret-normal@2x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-left@2x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-right@2x.png");
  }
}

@media (min-resolution: 2.25dppx) {
  div:-moz-native-anonymous.moz-accessiblecaret.normal > #image {
    background-image: url("resource://gre-resources/accessiblecaret-normal@2.25x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.left > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-left@2.25x.png");
  }

  div:-moz-native-anonymous.moz-accessiblecaret.right > #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-right@2.25x.png");
  }
}

/* Custom content container in the CanvasFrame, positioned on top of everything
   everything else, not reacting to pointer events. */
div:-moz-native-anonymous.moz-custom-content-container {
  pointer-events: none;
  -moz-top-layer: top;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
PK
!<!chrome/toolkit/res/viewsource.css@charset "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/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

*|*:root {
  background-color: white;
  color: black;
  direction: ltr;
  -moz-control-character-visibility: visible;
  height: 100%;
}
#viewsource {
  font-family: -moz-fixed;
  font-weight: normal;
  white-space: pre;
  counter-reset: line;
  height: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 8px;
}
#viewsource.wrap {
  white-space: pre-wrap;
  word-wrap: break-word;
}
pre {
  font: inherit;
  color: inherit;
  white-space: inherit;
  margin: 0 0 0 5ch;
}
pre[id]:before,
span[id]:before {
  content: counter(line) " ";
  counter-increment: line;
  -moz-user-select: none;
  display: inline-block;
  width: 5ch;
  margin: 0 0 0 -5ch;
  text-align: right;
  color: #ccc;
  font-weight: normal;
  font-style: normal;
}
.highlight .start-tag {
 color: purple;
 font-weight: bold;
}
.highlight .end-tag {
 color: purple;
 font-weight: bold;
}
.highlight .comment {
 color: green;
 font-style: italic;
}
.highlight .cdata {
 color: #CC0066;
}
.highlight .doctype {
 color: steelblue;
 font-style: italic;
}
.highlight .pi {
 color: orchid;
 font-style: italic;
}
.highlight .entity {
 color: #FF4500;
 font-weight: normal;
}
.highlight .text {
  font-weight: normal;
}
.highlight .attribute-name {
 color: black;
 font-weight: bold;
}
.highlight .attribute-value {
 color: blue;
 font-weight: normal;
}
.highlight .markupdeclaration {
 color: steelblue;
 font-style: italic;
}
span:not(.error), a:not(.error) {
 unicode-bidi: embed;
}
span[id] {
 unicode-bidi: isolate;
}
.highlight .error,
.highlight .error > :-moz-any(.start-tag, .end-tag, .comment, .cdata, .doctype,
                              .pi, .entity, .attribute-name, .attribute-value) {
  color: red;
  font-weight: bold;
}
PK
!<٧Z,chrome/toolkit/skin/classic/global/about.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  background: -moz-Dialog;
  line-height: 1.6em;
  padding: 0 1em;
  font: message-box;
}

body {
  position: relative;
  min-width: 330px;
  max-width: 50em;
  margin: 4em auto;
  padding-inline-start: 30px;
}

.aboutPageWideContainer {
  max-width: 80%;
}

#aboutLogoContainer {
  border: 1px solid ThreeDLightShadow;
  width: 300px;
  margin-bottom: 2em;
}

h1 {
  font-weight: lighter;
  font-size: 2.4em;
}

img {
  border: 0;
}

#version {
  font-weight: bold;
  color: #909090;
  margin: -24px 0 9px 17px;
  text-align: left; /* Override direction alignment on RTL to make sure that the version will fit well on the background. bug 1325232 */
}

ul {
  margin: 0;
  margin-inline-start: 1.5em;
  padding: 0;
}

ul > li {
  margin-top: .5em;
}

th, td {
  padding: 0 5px;
}
PK
!<NN1chrome/toolkit/skin/classic/global/aboutCache.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

h2 {
  margin-top: 2em;
}

table {
  table-layout: fixed;
  width: 100%;
  margin-bottom: 1em;
  padding: 0.5em 0;
  border-radius: 10px;
}

#disk,
#memory,
#offline {
  background-color: rgba(0, 0, 0, .1);
}

th {
  width: 14em;
  white-space: nowrap;
  text-align: end;
}

td {
  font-family: -moz-fixed;
  word-wrap: break-word;
}

#col-key {
  width: 60%;
}

#col-dataSize,
#col-fetchCount,
#col-lastModified,
#col-expires {
  width: 13%;
}

#col-pinned {
  width: 5em;
}

#entries > tbody > tr:nth-child(odd) {
  background-color: rgba(0, 0, 0, .03);
}

#entries > tbody > tr:nth-child(even) {
  background-color: rgba(0, 0, 0, .06);
}

#entries > tbody > tr > td {
  padding: .5em 0;
  text-align: center;
}

#entries > thead > tr > th {
  text-align: center;
  white-space: normal;
}

#entries > thead > tr > th:first-child,
#entries > tbody > tr > td:first-child {
  text-align: start;
}
PK
!<6chrome/toolkit/skin/classic/global/aboutCacheEntry.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  display: table;
}

table {
  table-layout: fixed;
  width: 100%;
}

th {
  width: 12em;
  word-wrap: break-word;
  vertical-align: top;
  text-align: end;
}

td {
  display: block;
  font-family: -moz-fixed;
  white-space: pre-wrap;
}

#td-key {
  word-wrap: break-word;
}
PK
!<:A2chrome/toolkit/skin/classic/global/aboutMemory.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/content/aboutMemory.css");
PK
!<z
z
2chrome/toolkit/skin/classic/global/aboutReader.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  padding: 64px 51px;
}

body.loaded {
  transition: color 0.4s, background-color 0.4s;
}

body.light {
  color: #333333;
  background-color: #ffffff;
}

body.dark {
  color: #eeeeee;
  background-color: #333333;
}

body.dark *::-moz-selection {
  background-color: #FFFFFF;
  color: #0095DD;
}
body.dark a::-moz-selection {
  color: #DD4800;
}

body.sepia {
  color: #5b4636;
  background-color: #f4ecd8;
}

body.sans-serif,
body.sans-serif .remove-button {
  font-family: Helvetica, Arial, sans-serif;
}

body.serif,
body.serif .remove-button  {
  font-family: Georgia, "Times New Roman", serif;
}

#container {
  max-width: 30em;
  margin: 0 auto;
}

#container.font-size1 {
  font-size: 12px;
}

#container.font-size2 {
  font-size: 14px;
}

#container.font-size3 {
  font-size: 16px;
}

#container.font-size4  {
  font-size: 18px;
}

#container.font-size5 {
  font-size: 20px;
}

#container.font-size6 {
  font-size: 22px;
}

#container.font-size7 {
  font-size: 24px;
}

#container.font-size8 {
  font-size: 26px;
}

#container.font-size9 {
  font-size: 28px;
}

#container.content-width1 {
  max-width: 20em;
}

#container.content-width2 {
  max-width: 25em;
}

#container.content-width3 {
  max-width: 30em;
}

#container.content-width4  {
  max-width: 35em;
}

#container.content-width5 {
  max-width: 40em;
}

#container.content-width6 {
  max-width: 45em;
}

#container.content-width7 {
  max-width: 50em;
}

#container.content-width8 {
  max-width: 55em;
}

#container.content-width9 {
  max-width: 60em;
}

/* Override some controls and content styles based on color scheme */

body.light > .container > .header > .domain {
  border-bottom-color: #333333 !important;
}

body.sepia > .container > .header > .domain {
  border-bottom-color: #5b4636 !important;
}

body.dark > .container > .header > .domain {
  border-bottom-color: #eeeeee !important;
}

body.sepia > .container > .footer {
  background-color: #dedad4 !important;
}

body.light blockquote {
  border-inline-start: 2px solid #333333 !important;
}

body.sepia blockquote {
  border-inline-start: 2px solid #5b4636 !important;
}

body.dark blockquote {
  border-inline-start: 2px solid #eeeeee !important;
}

/* Add toolbar transition base on loaded class  */

body.loaded .toolbar {
  transition: transform 0.3s ease-out;
}

body:not(.loaded) .toolbar:-moz-locale-dir(ltr) {
  transform: translateX(-100%);
}

body:not(.loaded) .toolbar:-moz-locale-dir(rtl) {
  transform: translateX(100%);
}
PK
!<Gw9chrome/toolkit/skin/classic/global/aboutReaderContent.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#moz-reader-content {
  display: none;
  font-size: 1em;
  line-height: 1.6em;
}

#moz-reader-content.line-height1 {
  line-height: 1em;
}

#moz-reader-content.line-height2 {
  line-height: 1.2em;
}

#moz-reader-content.line-height3 {
  line-height: 1.4em;
}

#moz-reader-content.line-height4 {
  line-height: 1.6em;
}

#moz-reader-content.line-height5 {
  line-height: 1.8em;
}

#moz-reader-content.line-height6 {
  line-height: 2.0em;
}

#moz-reader-content.line-height7 {
  line-height: 2.2em;
}

#moz-reader-content.line-height8 {
  line-height: 2.4em;
}

#moz-reader-content.line-height9 {
  line-height: 2.6em;
}

@media print {
  p,
  code,
  pre,
  blockquote,
  ul,
  ol,
  li,
  figure,
  .wp-caption {
    margin: 0 0 10px 0 !important;
    padding: 0 !important;
  }
}

h1,
h2,
h3 {
  font-weight: bold;
}

h1 {
  font-size: 1.6em;
  line-height: 1.25em;
}

h2 {
  font-size: 1.2em;
  line-height: 1.51em;
}

h3 {
  font-size: 1em;
  line-height: 1.66em;
}

a:link {
  text-decoration: underline;
  font-weight: normal;
}

a:link,
a:link:hover,
a:link:active {
  color: #0095dd;
}

a:visited {
  color: #c2e;
}

* {
  max-width: 100%;
  height: auto;
}

p,
code,
pre,
blockquote,
ul,
ol,
li,
figure,
.wp-caption {
  margin: -10px -10px 20px -10px;
  padding: 10px;
  border-radius: 5px;
}

li {
  margin-bottom: 0;
}

li > ul,
li > ol {
  margin-bottom: -10px;
}

p > img:only-child,
p > a:only-child > img:only-child,
.wp-caption img,
figure img {
  display: block;
}

img[moz-reader-center] {
  margin-left: auto;
  margin-right: auto;
}

.caption,
.wp-caption-text,
figcaption {
  font-size: 0.9em;
  line-height: 1.48em;
  font-style: italic;
}

code,
pre {
  white-space: pre-wrap;
}

blockquote {
  padding: 0;
  padding-inline-start: 16px;
}

ul,
ol {
  padding: 0;
}

ul {
  padding-inline-start: 30px;
  list-style: disc;
}

ol {
  padding-inline-start: 30px;
  list-style: decimal;
}

/* Hide elements with common "hidden" class names */
.visually-hidden,
.visuallyhidden,
.hidden,
.invisible,
.sr-only {
  display: none;
}
PK
!<L4":chrome/toolkit/skin/classic/global/aboutReaderControls.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

.light-button {
  color: #333333;
  background-color: #ffffff;
}

.dark-button {
  color: #eeeeee;
  background-color: #333333;
}

.sepia-button {
  color: #5b4636;
  background-color: #f4ecd8;
}

.sans-serif-button {
  font-family: Helvetica, Arial, sans-serif;
}

.serif-button {
  font-family: Georgia, "Times New Roman", serif;
}

/* Loading/error message */

#reader-message {
  margin-top: 40px;
  display: none;
  text-align: center;
  width: 100%;
  font-size: 0.9em;
}

/* Header */

.header {
  text-align: start;
  display: none;
}

.domain {
  font-size: 0.9em;
  line-height: 1.48em;
  padding-bottom: 4px;
  font-family: Helvetica, Arial, sans-serif;
  text-decoration: none;
  border-bottom: 1px solid;
  color: #0095dd;
}

.header > h1 {
  font-size: 1.6em;
  line-height: 1.25em;
  width: 100%;
  margin: 30px 0;
  padding: 0;
}

.header > .credits {
  font-size: 0.9em;
  line-height: 1.48em;
  margin: 0 0 10px 0;
  padding: 0;
  font-style: italic;
}

.header > .meta-data {
  font-size: 0.65em;
  margin: 0 0 15px 0;
}

/*======= Controls toolbar =======*/

.toolbar {
  font-family: Helvetica, Arial, sans-serif;
  position: fixed;
  height: 100%;
  top: 0;
  left: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  background-color: #fbfbfb;
  -moz-user-select: none;
  border-right: 1px solid #b5b5b5;
  z-index: 1;
}

.button {
  display: block;
  background-size: 24px 24px;
  background-repeat: no-repeat;
  color: #333;
  background-color: #fbfbfb;
  height: 40px;
  padding: 0;
}

.toolbar .button {
  width: 40px;
  background-position: center;
  margin-right: -1px;
  border-top: 0;
  border-left: 0;
  border-right: 1px solid #b5b5b5;
  border-bottom: 1px solid #c1c1c1;
}

.button[hidden] {
  display: none;
}

.dropdown {
  text-align: center;
  list-style: none;
  margin: 0;
  padding: 0;
}

.dropdown li {
  margin: 0;
  padding: 0;
}

/*======= Popup =======*/

.dropdown-popup {
  min-width: 300px;
  text-align: start;
  position: absolute;
  left: 48px; /* offset to account for toolbar width */
  z-index: 1000;
  background-color: #fbfbfb;
  visibility: hidden;
  border-radius: 4px;
  border: 1px solid #b5b5b5;
  border-bottom-width: 0;
  box-shadow: 0 1px 3px #c1c1c1;
}

.keep-open .dropdown-popup {
  z-index: initial;
}

.dropdown-popup > hr {
  display: none;
}

.open > .dropdown-popup {
  visibility: visible;
}

.dropdown-arrow {
  position: absolute;
  top: 30px; /* offset arrow from top of popup */
  left: -16px;
  width: 16px;
  height: 24px;
  background-image: url("chrome://global/skin/reader/RM-Type-Controls-Arrow.svg");
  display: block;
}

/*======= Font style popup =======*/

#font-type-buttons,
#font-size-buttons,
#color-scheme-buttons,
#content-width-buttons,
#line-height-buttons {
  display: flex;
  flex-direction: row;
}

#font-type-buttons > button:first-child {
  border-top-left-radius: 3px;
}
#font-type-buttons > button:last-child {
  border-top-right-radius: 3px;
}
#color-scheme-buttons > button:first-child {
  border-bottom-left-radius: 3px;
}
#color-scheme-buttons > button:last-child {
  border-bottom-right-radius: 3px;
}

#font-type-buttons > button,
#font-size-buttons > button,
#color-scheme-buttons > button,
#content-width-buttons > button,
#line-height-buttons > button {
  text-align: center;
  border: 0;
}

#font-type-buttons > button,
#font-size-buttons > button,
#content-width-buttons > button,
#line-height-buttons > button {
  width: 50%;
  background-color: transparent;
  border-left: 1px solid #B5B5B5;
  border-bottom: 1px solid #B5B5B5;
}

#color-scheme-buttons > button {
  width: 33.33%;
  font-size: 14px;
}

#color-scheme-buttons > .dark-button {
  margin-top: -1px;
  height: 61px;
}

#font-type-buttons > button:first-child,
#font-size-buttons > button:first-child,
#content-width-buttons > button:first-child,
#line-height-buttons > button:first-child {
  border-left: 0;
}

#font-type-buttons > button {
  display: inline-block;
  font-size: 62px;
  height: 100px;
}

#font-size-buttons > button,
#color-scheme-buttons > button,
#content-width-buttons > button,
#line-height-buttons > button {
  height: 60px;
}

#font-type-buttons > button:active:hover,
#font-type-buttons > button.selected,
#color-scheme-buttons > button:active:hover,
#color-scheme-buttons > button.selected {
  box-shadow: inset 0 -3px 0 0 #fc6420;
}

#font-type-buttons > button:active:hover,
#font-type-buttons > button.selected {
  border-bottom: 1px solid #FC6420;
}

/* Make the serif button content the same size as the sans-serif button content. */
#font-type-buttons > button > .description {
  color: #666;
  font-size: 12px;
  margin-top: -5px;
}

/* Font sizes are different per-platform, so we need custom CSS to line them up. */
#font-type-buttons > .sans-serif-button > .name {
  margin-top: 2px;
}

#font-type-buttons > .sans-serif-button > .description {
  margin-top: -4px;
}

#font-type-buttons > .serif-button > .name {
  font-size: 63px;
}

.button:hover,
#font-size-buttons > button:hover,
#font-type-buttons > button:hover,
#content-width-buttons > button:hover,
#line-height-buttons > button:hover {
  background-color: #ebebeb;
}

.dropdown.open,
.button:active,
#font-size-buttons > button:active,
#font-size-buttons > button.selected,
#content-width-buttons > button:active,
#content-width-buttons > button.selected,
#line-height-buttons > button:active,
#line-height-buttons > button.selected {
  background-color: #dadada;
}

/* Only used on Android */
#font-size-sample {
  display: none;
}

.minus-button,
.plus-button,
.content-width-minus-button,
.content-width-plus-button,
.line-height-minus-button,
.line-height-plus-button {
  background-color: transparent;
  border: 0;
  background-size: 18px 18px;
  background-repeat: no-repeat;
  background-position: center;
}

/*======= Toolbar icons =======*/

.close-button {
  background-image: url("chrome://global/skin/reader/RM-Close-24x24.svg");
  -moz-context-properties: fill;
  fill: #808080;
  height: 68px;
  background-position: center 8px;
}

.close-button:hover {
  fill: #fff;
  background-color: #d94141;
  border-bottom: 1px solid #d94141;
  border-right: 1px solid #d94141;
}

.close-button:hover:active {
  background-color: #AE2325;
  border-bottom: 1px solid #AE2325;
  border-right: 1px solid #AE2325;
}

.style-button {
  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
}

.minus-button {
  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
}

.plus-button {
  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
}

.content-width-minus-button {
  background-size: 42px 16px;
  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
}

.content-width-plus-button {
  background-size: 44px 16px;
  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
}

.line-height-minus-button {
  background-size: 34px 14px;
  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
}

.line-height-plus-button {
  background-size: 34px 24px;
  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
}

@media print {
  .toolbar {
    display: none !important;
  }
}
PK
!<<Vss3chrome/toolkit/skin/classic/global/aboutSupport.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  --aboutSupport-table-background: #ebebeb;
  background-color: var(--in-content-page-background);
}

body {
  margin: 40px 48px;
}

.page-subtitle {
  margin-bottom: 3em;
}

.major-section {
  margin-top: 2em;
  margin-bottom: 1em;
  font-size: large;
  text-align: start;
  font-weight: bold;
}

button {
  margin-inline-start: 0;
  margin-inline-end: 8px;
}

table {
  background-color: var(--aboutSupport-table-background);
  color: var(--in-content-text-color);
  font: message-box;
  text-align: start;
  width: 100%;
  border: 1px solid var(--in-content-border-color);
  border-spacing: 0px;
}

th, td {
  border: 1px solid var(--in-content-border-color);
  padding: 4px;
}

thead th {
  text-align: center;
}

th {
  text-align: start;
  background-color: var(--in-content-table-header-background);
  color: var(--in-content-selected-text);
}

th.title-column {
  white-space: nowrap;
  width: 0px;
  font-size: medium;
}

th.column {
  white-space: nowrap;
  width: 0px;
}

td {
  text-align: start;
  border-color: var(--in-content-table-border-dark-color);
}

td.integer {
  text-align: end;
  font-family: monospace;
}

.prefs-table {
  width: 100%;
  table-layout: fixed;
}

.pref-name {
  width: 70%;
  white-space: nowrap;
  overflow: hidden;
}

.pref-value {
  width: 30%;
  white-space: nowrap;
  overflow: hidden;
}

#action-box {
  background-color: var(--aboutSupport-table-background);
  border: 1px solid var(--in-content-border-color);
  color: var(--in-content-text-color);
  float: right;
  margin-top: 2em;
  margin-bottom: 20px;
  margin-inline-start: 20px;
  margin-inline-end: 0;
  padding: 16px;
  width: 30%;
}

#action-box,
#reset-box,
#safe-mode-box {
  display: none;
}

#action-box:dir(rtl) {
  float: left;
}

#reset-box > h3 {
  margin-top: 0;
}

#action-box button {
  display: block;
}

#verify-place-result {
  max-height: 200px;
  overflow: auto;
}

.block {
  display: block;
}

.hidden {
  display: none;
}
PK
!<*#
#
:chrome/toolkit/skin/classic/global/alerts/alert-common.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== alert.css =====================================================
  == Shared styles specific to the alerts dialog.
  ======================================================================= */

@import url("chrome://global/skin/");

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

#alertBox[animate] {
  animation-timing-function: cubic-bezier(.12,1.23,.48,1.09);
}

#alertBox[animate][clicked] {
  animation-duration: .6s;
  animation-name: alert-clicked-animation;
}

/* This is used if the close button is clicked
   before the animation has finished. */
#alertBox[animate][closing] {
  animation-duration: .6s;
  animation-name: alert-closing-animation;
}

#alertBox[animate]:not([clicked]):not([closing]):hover {
  /* !important is necessary because CSS animations have highest
     importance in the cascade with exception to !important rules. */
  opacity: 1 !important;
}

@keyframes alert-animation {
  from {
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  95% {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes alert-clicked-animation {
  to {
    opacity: 0;
  }
}

@keyframes alert-closing-animation {
  to {
    opacity: 0;
  }
}

#alertIcon {
  margin-top: 6px;
  margin-inline-start: 8px;
  margin-inline-end: 0;
  margin-bottom: 0;
  width: 16px;
  min-height: 16px;
  max-height: 16px;
}

@media (resolution: 2dppx) {
  #alertIcon {
    image-rendering: -moz-crisp-edges;
  }
}

#alertImage {
  width: 80px;
  height: 80px;
  max-width: 80px;
  max-height: 80px;
  object-fit: scale-down;
  margin: 0 7px 7px;
}

.alertTextBox {
  padding-top: 4px;
  /* The text box width is increased to make up for the lack of image when one
     is not provided. 349px is the text box width when a picture is present,
     255px, plus the width of the image, 80px, and the margins, 7px each. */
  width: 349px;
}

#alertBox[hasImage] > box > #alertTextBox {
  width: 255px;
}

#alertBox:not([hasImage]) > box > #alertTextBox {
  padding-inline-start: 8px;
}

#alertTextLabel {
  padding-inline-end: 8px;
}

.alertTitle {
  -moz-box-flex: 1;
  font-weight: bold;
  padding: 6px 8px 0;
  width: 255px;
}

#alertFooter {
  -moz-box-align: start;
}

#alertBox:not([hasOrigin]) > box > #alertTextBox,
#alertFooter {
  padding-bottom: 5px;
}

#alertSourceLabel {
  -moz-box-flex: 1;
  font-size: 83.334%;
  color: GrayText;
}

#alertSettings {
  -moz-appearance: none;
  background-color: transparent;
  border-width: 0;
  border-radius: 20px;
  min-width: 0;
  list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg");
  -moz-context-properties: fill;
  fill: #4d4d4d;
  margin-inline-end: 0;
  margin-bottom: 0;
}

#alertSettings > .button-box {
  padding: 0;
}

#alertSettings:hover,
#alertSettings[open] {
  fill: #ddd;
}

#alertSettings:hover {
  background-color: rgb(128,128,128);
}

#alertSettings[open],
#alertSettings:hover:active {
  background-color: rgb(102,102,102);
}

#alertSettings[focusedViaMouse]:-moz-focusring {
  outline: none;
}

#alertSettings > .button-box > .button-menu-dropmarker,
#alertSettings > .button-box > .box-inherit > .button-text {
  display: none;
}
PK
!<G3chrome/toolkit/skin/classic/global/alerts/alert.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== alert.css =====================================================
  == Styles specific to the alerts dialog.
  ======================================================================= */

@import url("chrome://global/skin/alerts/alert-common.css");

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

#alertNotification {
  -moz-appearance: none;
  background: transparent;
  padding: 10px;
}

#alertBox {
  border: 1px solid ThreeDShadow;
  border-radius: 1px;
  background-color: -moz-Dialog;
  color: -moz-DialogText;
  box-shadow: 0 2px 10px rgba(0,0,0,0.59);
}

.alertCloseButton {
  -moz-appearance: none;
  padding: 4px 2px;
  border: none !important;
}

.alertCloseBox {
  /* The close button is larger on Windows and has a large
     circle around it, so we add more space between the close
     button and the edge of the window. */
  margin-inline-end: 2px;
}

#alertSettings {
  /* The close button is larger on Windows, so the
     gear button is moved over to accomodate it and
     keep the two buttons horizontally aligned together. */
  margin-inline-end: 5px;
}

@media (-moz-windows-default-theme) {
  #alertBox {
    border-color: rgba(107,107,107,.3);
    background-color: rgba(255,255,255,.9);
    color: rgba(0,0,0,.9);
  }
}
PK
!<0chrome/toolkit/skin/classic/global/appPicker.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#app-picker {
  width: 320px;
}

#content-description {
  font-weight: bold;
}

#content-icon,
.listcell-icon {
  margin: 5px;
  width: 32px;
  height: 32px;
}

.listcell-label {
  padding: 5px;
}
PK
!<gp559chrome/toolkit/skin/classic/global/arrow/arrow-dn-dis.gifGIF89a̙!,Db);PK
!<	b555chrome/toolkit/skin/classic/global/arrow/arrow-dn.gifGIF89a!,b);PK
!<//:chrome/toolkit/skin/classic/global/arrow/arrow-lft-dis.gifGIF89a!,Lp;PK
!<+<M666chrome/toolkit/skin/classic/global/arrow/arrow-lft.gifGIF89a!,piP;PK
!<wH66:chrome/toolkit/skin/classic/global/arrow/arrow-rit-dis.gifGIF89a̙!,bR;PK
!<t8666chrome/toolkit/skin/classic/global/arrow/arrow-rit.gifGIF89a!,bR;PK
!<qY669chrome/toolkit/skin/classic/global/arrow/arrow-up-dis.gifGIF89a̙!,`P;PK
!<z"#665chrome/toolkit/skin/classic/global/arrow/arrow-up.gifGIF89a!,bQ;PK
!<>Ichrome/toolkit/skin/classic/global/arrow/panelarrow-horizontal-themed.svg<?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="10" height="20">
  <path d="M 10,0 L 0,10 10,20 z" fill="ThreeDShadow"/>
  <path d="M 10,1 L 1,10 10,19 z" fill="-moz-field"/>
</svg>
PK
!<bpjBchrome/toolkit/skin/classic/global/arrow/panelarrow-horizontal.svg<?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="10" height="20">
  <path d="M 10,0 L 0,10 10,20 z" fill="hsla(210,4%,10%,.2)"/>
  <path d="M 10,1 L 1,10 10,19 z" fill="-moz-field"/>
</svg>
PK
!<q'*Gchrome/toolkit/skin/classic/global/arrow/panelarrow-vertical-themed.svg<?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="20" height="10">
  <path d="M 0,10 L 10,0 20,10 z" fill="ThreeDShadow"/>
  <path d="M 1,10 L 10,1 19,10 z" fill="-moz-field"/>
</svg>
PK
!<ڷ@chrome/toolkit/skin/classic/global/arrow/panelarrow-vertical.svg<?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="20" height="10">
  <path d="M 0,10 L 10,0 20,10 z" fill="hsla(210,4%,10%,.2)"/>
  <path d="M 1,10 L 10,1 19,10 z" fill="-moz-field"/>
</svg>
PK
!<r1KK3chrome/toolkit/skin/classic/global/autocomplete.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== autocomplete.css =================================================
  == Styles used by the autocomplete widget.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* ::::: autocomplete ::::: */

/* .padded is used by autocomplete widgets that don't have an icon. Gross. -dwh */
textbox:not(.padded) {
  cursor: default;
  padding: 0;
}

textbox[nomatch="true"][highlightnonmatches="true"] {
  color: red;
}

.autocomplete-textbox-container {
  -moz-box-align: center;
}

textbox:not(.padded) .textbox-input-box {
  margin: 0 3px;
}

.textbox-input-box {
  -moz-box-align: center;
}

/* ::::: autocomplete popups ::::: */

panel[type="autocomplete"],
panel[type="autocomplete-richlistbox"],
.autocomplete-history-popup {
  -moz-appearance: none;
  border-width: 1px;
  -moz-border-top-colors: ThreeDShadow;
  -moz-border-right-colors: ThreeDShadow;
  -moz-border-bottom-colors: ThreeDShadow;
  -moz-border-left-colors: ThreeDShadow;
  padding: 0;
  color: -moz-FieldText;
  background-color: -moz-Field;
}

.autocomplete-history-popup {
  max-height: 180px;
}

/* ::::: tree ::::: */

.autocomplete-tree {
  -moz-appearance: none !important;
  border: none !important;
  background-color: transparent !important;
}

.autocomplete-treecol {
  -moz-appearance: none !important;
  margin: 0 !important;
  border: none !important;
  padding: 0 !important;
}

/* GTK calculates space for a sort arrow */
.autocomplete-treecol > .treecol-sortdirection {
  -moz-appearance: none !important;
}

.autocomplete-treebody::-moz-tree-cell-text {
  padding-inline-start: 8px;
}

treechildren.autocomplete-treebody::-moz-tree-row(selected) {
 background-color: Highlight;
}

treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
  color: HighlightText !important;
}

.autocomplete-treebody::-moz-tree-image(treecolAutoCompleteValue) {
  max-width: 16px;
  height: 16px;
}

/* ::::: richlistbox autocomplete ::::: */

.autocomplete-richlistbox {
  -moz-appearance: none;
  margin: 0;
}

.autocomplete-richlistitem[selected] {
  background-color: Highlight;
  color: HighlightText;
}

.ac-type-icon {
  width: 16px;
  height: 16px;
  max-width: 16px;
  max-height: 16px;
  margin-inline-start: 14px;
  margin-inline-end: 6px;
}

.ac-site-icon {
  width: 16px;
  height: 16px;
  max-width: 16px;
  max-height: 16px;
  margin-inline-start: 0;
  margin-inline-end: 11px;
  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
}

.ac-title {
  margin-inline-start: 0;
  margin-inline-end: 6px;
}

html|span.ac-tag {
  margin-inline-start: 0;
  margin-inline-end: 2px;
}

.ac-tags {
  margin-inline-start: 0;
  margin-inline-end: 4px;
}

.ac-separator {
  margin-inline-start: 0;
  margin-inline-end: 6px;
}

/* Better align the URL/action with the title. */
.ac-tags,
.ac-separator,
.ac-url,
.ac-action {
  margin-bottom: -2px;
}

.ac-title-text,
.ac-tags-text,
.ac-separator-text,
.ac-url-text,
.ac-action-text,
.ac-text-overflow-container {
  padding: 0 !important;
  margin: 0 !important;
}

/* ::::: textboxes inside toolbarpaletteitems ::::: */

toolbarpaletteitem > toolbaritem > textbox > hbox > hbox > html|*.textbox-input {
  visibility: hidden;
}

toolbarpaletteitem > toolbaritem > * > textbox > hbox > hbox > html|*.textbox-input {
  visibility: hidden;
}

PK
!<s-chrome/toolkit/skin/classic/global/button.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== button.css =====================================================
  == Styles used by the XUL button element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* :::::::::: button :::::::::: */

button {
  -moz-appearance: button;
  margin: 1px 5px 2px;
  min-width: 6.3em;
  color: ButtonText;
  text-shadow: none;
}

.button-box {
  padding-top: 2px;
  padding-bottom: 3px;
  padding-inline-start: 4px;
  padding-inline-end: 5px;
}

.button-text {
  margin: 0 !important;
  text-align: center;
}

/* .......... focused state .......... */

button:-moz-focusring {
  outline: 1px dotted;
  outline-offset: -3px;
}

/* .......... default/hover/focused state .......... */

@media (-moz-windows-default-theme: 0) {
  @media (-moz-windows-compositor) {
    /* This is for high-contrast black and white themes on Windows 8 and later,
       where the native appearance renders a different background (which
       appears to be equivalent to the Highlight color) if the button is in the
       default, hovered or focused state. However, if these states overlap with
       the active, disabled, open or checked state, the appearance reverts back
       to the default background. */
    button:-moz-any([default="true"],:hover,:focus):not(:-moz-any(:active,[disabled="true"],[open="true"],[checked="true"])) {
      color: HighlightText;
    }
  }
}

/* .......... active/open/checked state .......... */

@media (-moz-windows-classic) {
  button:-moz-any(:hover:active,[open="true"],[checked="true"]):not([disabled="true"]) > .button-box {
    padding-top: 3px;
    padding-bottom: 2px;
    padding-inline-start: 5px;
    padding-inline-end: 4px;
  }
}

/* .......... disabled state .......... */

button[disabled="true"] {
  color: GrayText;
}

@media (-moz-windows-classic) {
  button[disabled="true"] {
    color: ThreeDShadow;
    text-shadow: 1px 1px ThreeDHighlight;
  }
}

/* ::::: menu/menu-button buttons ::::: */

button[type="menu-button"] {
  margin: 0;
}

.button-menu-dropmarker,
.button-menubutton-dropmarker {
  -moz-appearance: none !important;
  margin: 1px;
  width: 11px;
  height: 11px;
}

.button-menubutton-dropmarker[open="true"] {
  margin-top: 2px;
  margin-bottom: 0px;
  margin-inline-start: 2px;
  margin-inline-end: 0px;
}

/* ::::: plain buttons ::::: */

button.plain {
  margin: 0 !important;
  padding: 0 !important;
}

button[type="disclosure"] {
  margin: 0;
  -moz-appearance: none;
  list-style-image: url("chrome://global/skin/tree/twisty.svg#clsd");
  min-width: 0;
}

button[type="disclosure"][open="true"] {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#open");
}
PK
!<ut_/chrome/toolkit/skin/classic/global/checkbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== checkbox.css ===================================================
  == Styles used by the XUL checkbox element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: checkbox ::::: */

checkbox {
  -moz-appearance: checkbox-container;
  -moz-box-align: center;
  margin: 2px 4px;
  padding-top: 1px;
  padding-bottom: 1px;
  padding-inline-start: 4px;
  padding-inline-end: 2px;
}

.checkbox-icon {
  margin-inline-end: 2px;
}

.checkbox-label {
  margin: 0 !important;
}

/* ..... focused state ..... */

checkbox:-moz-focusring > .checkbox-label-box {
  outline: 1px dotted;
}

/* ..... disabled state ..... */

checkbox[disabled="true"] {
  color: GrayText;
}

checkbox[disabled="true"]:-moz-system-metric(windows-classic) {
  color: ThreeDShadow;
  text-shadow: 1px 1px ThreeDHighlight;
}

/* ::::: checkmark image ::::: */

.checkbox-check {
  -moz-appearance: checkbox;
  -moz-box-align: center;
  min-width: 13px;
  min-height: 13px;
  margin-inline-end: 3px;
}
PK
!<ͬG2chrome/toolkit/skin/classic/global/colorpicker.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== colorpicker.css ================================================
  == Styles used by the XUL colorpicker element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: colorpicker button ::::: */
 
/* colorpicker button */

colorpicker[type="button"] {
  -moz-appearance: button;
  width: 38px;
  height: 24px;
  padding: 3px;
}

.colorpicker-button-colorbox {
  border: 1px solid #000000;
}

/* ::::: colorpicker tiles ::::: */

.colorpickertile {
  width: 20px;
  height: 20px;
  margin: 1px;
  border-left: 1px solid ThreeDShadow;
  border-top: 1px solid ThreeDShadow;
  border-right: 1px solid ThreeDHighlight;
  border-bottom: 1px solid ThreeDHighlight;
}

.colorpickertile[selected="true"] {
  border: 2px outset #C0C0C0;
}

.colorpickertile[hover="true"] {
  border: 2px dotted #FFFFFF;
}

.cp-light[hover="true"] {
  border: 2px dotted #909090;
}
PK
!<b>3chrome/toolkit/skin/classic/global/commonDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#filler {
  margin-inline-start: -8px;
  margin-inline-end: -10px;
}
PK
!<;vv-chrome/toolkit/skin/classic/global/config.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#warningScreen {
  font-size: 15px;
  padding-top: 0;
  padding-bottom: 0;
  padding-inline-start: calc(48px + 4.6em);
  padding-inline-end: 48px;
}

.title {
  background-image: url("chrome://global/skin/icons/warning.svg");
}

#warningTitle {
  font-weight: lighter;
  line-height: 1.2;
  color: #333;
  margin: 0;
  margin-bottom: .5em;
}

#warningText {
  margin: 1em 0;
}

#warningButton {
  margin-top: 0.6em;
}

#filterRow {
  margin-top: 4px;
  margin-inline-start: 4px;
}

#configTree {
  margin-top: 4px;
  margin-bottom: 4px;
}

#configTreeBody::-moz-tree-cell-text(user) {
  font-weight: bold;
}

#configTreeBody::-moz-tree-cell-text(locked) {
  font-style: italic;
}
PK
!<c7chrome/toolkit/skin/classic/global/customizeToolbar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

window,
dialog {
  padding: 6px;
}

#instructions {
  font-weight: bold;
  font-size: larger;
}

#palette-box {
  -moz-appearance: listbox;
  margin: 0 0 10px;
}

#palette-box > toolbarpaletteitem {
  padding: 8px 2px;
  margin: 0 8px;
}
PK
!<X)  ;chrome/toolkit/skin/classic/global/datetimeinputpickers.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --font-size-default: 1.1rem;
  --spinner-width: 3rem;
  --spinner-margin-top-bottom: 0.4rem;
  --spinner-item-height: 2.4rem;
  --spinner-item-margin-bottom: 0.1rem;
  --spinner-button-height: 1.2rem;
  --colon-width: 2rem;
  --day-period-spacing-width: 1rem;
  --calendar-width: 23.1rem;
  --date-picker-item-height: 2.5rem;
  --date-picker-item-width: 3.3rem;

  --border: 0.1rem solid #D6D6D6;
  --border-radius: 0.3rem;
  --border-active-color: #B1B1B1;

  --font-color: #191919;
  --fill-color: #EBEBEB;

  --today-fill-color: rgb(212, 212, 212);

  --selected-font-color: #FFFFFF;
  --selected-fill-color: #0996F8;

  --button-font-color: #858585;
  --button-font-color-hover: #4D4D4D;
  --button-font-color-active: #191919;
  --button-fill-color-active: #D4D4D4;

  --weekday-header-font-color: #6C6C6C;
  --weekend-header-font-color: rgb(218, 78, 68);

  --weekend-font-color: rgb(218, 78, 68);
  --weekday-outside-font-color: rgb(153, 153, 153);
  --weekend-outside-font-color: rgb(255, 152, 143);

  --weekday-disabled-font-color: rgba(25, 25, 25, 0.2);
  --weekend-disabled-font-color: rgba(218, 78, 68, 0.2);
  --disabled-fill-color: rgba(235, 235, 235, 0.8);

  --disabled-opacity: 0.2;
}

html {
  font-size: 10px;
}

body {
  margin: 0;
  color: var(--font-color);
  font: message-box;
  font-size: var(--font-size-default);
}

button {
  -moz-appearance: none;
  background: none;
  border: none;
}

.nav {
  display: flex;
  width: var(--calendar-width);
  height: 2.4rem;
  margin-bottom: 0.8rem;
  justify-content: space-between;
}

.nav > button {
  width: 3rem;
  height: var(--date-picker-item-height);
  -moz-context-properties: fill;
  fill: var(--button-font-color);
}

.nav > button:hover {
  fill: var(--button-font-color-hover);
}

.nav > button.active {
  fill: var(--button-font-color-active);
}

.nav > button.prev,
.nav > button.next:dir(rtl) {
  background: url("chrome://global/skin/icons/calendar-arrow-left.svg") no-repeat 50% 50%;
}

.nav > button.next,
.nav > button.prev:dir(rtl) {
  background: url("chrome://global/skin/icons/calendar-arrow-right.svg") no-repeat 50% 50%;
}

.month-year-container {
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  left: 3rem;
  right: 3rem;
  width: 17.1rem;
  height: var(--date-picker-item-height);
  z-index: 10;
}

button.month-year {
  font-size: 1.3rem;
  border: var(--border);
  border-radius: 0.3rem;
  padding-top: 0.2rem;
  padding-bottom: 0.2rem;
  padding-inline-start: 1.2rem;
  padding-inline-end: 2.6rem;
}

button.month-year:hover {
  background: var(--fill-color);
}

button.month-year.active {
  border-color: var(--border-active-color);
  background: var(--button-fill-color-active);
}

button.month-year::after {
  position: absolute;
  content: "";
  width: 2.6rem;
  height: 1.6rem;
  background: url("chrome://global/skin/icons/spinner-arrow-down.svg") no-repeat 50% 50%;
  -moz-context-properties: fill;
  fill: var(--button-font-color);
}

button.month-year.active::after {
  background: url("chrome://global/skin/icons/spinner-arrow-up.svg") no-repeat 50% 50%;
}

.month-year-view {
  position: absolute;
  z-index: 5;
  padding-top: 3.2rem;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--calendar-width);
  background: window;
  opacity: 1;
  transition: opacity 0.15s;
}

.month-year-view.hidden {
  visibility: hidden;
  opacity: 0;
}

.month-year-view > .spinner-container {
  width: 5.5rem;
  margin: 0 0.5rem;
}

.month-year-view .spinner {
  transform: scaleY(1);
  transform-origin: top;
  transition: transform 0.15s;
}

.month-year-view.hidden .spinner {
  transform: scaleY(0);
  transition: none;
}

.month-year-view .spinner > div {
  transform: scaleY(1);
  transition: transform 0.15s;
}

.month-year-view.hidden .spinner > div {
  transform: scaleY(2.5);
  transition: none;
}

.order-month-year > #spinner-month,
.order-year-month > #spinner-year {
  order: 1;
}

.order-month-year > #spinner-year,
.order-year-month > #spinner-month {
  order: 2;
}

.calendar-container {
  cursor: default;
  display: flex;
  flex-direction: column;
  width: var(--calendar-width);
}

.week-header {
  display: flex;
}

.week-header > div {
  color: var(--weekday-header-font-color);
}

.week-header > div.weekend {
  color: var(--weekend-header-font-color);
}

.days-viewport {
  height: 15rem;
  overflow: hidden;
  position: relative;
}

.days-view {
  position: absolute;
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
}

.week-header > div,
.days-view > div {
  align-items: center;
  display: flex;
  height: var(--date-picker-item-height);
  position: relative;
  justify-content: center;
  width: var(--date-picker-item-width);
}

.days-view > .outside {
  color: var(--weekday-outside-font-color);
}

.days-view > .weekend {
  color: var(--weekend-font-color);
}

.days-view > .weekend.outside {
  color: var(--weekend-outside-font-color);
}

.days-view > .out-of-range,
.days-view > .off-step {
  color: var(--weekday-disabled-font-color);
  background: var(--disabled-fill-color);
}

.days-view > .out-of-range.weekend,
.days-view > .off-step.weekend {
  color: var(--weekend-disabled-font-color);
}

.days-view > .today {
  font-weight: bold;
}

.days-view > .out-of-range::before,
.days-view > .off-step::before {
  display: none;
}

.days-view > div:hover::before,
.days-view > .select::before,
.days-view > .today::before {
  top: 5%;
  bottom: 5%;
  left: 5%;
  right: 5%;
}

#time-picker,
.month-year-view {
  display: flex;
  flex-direction: row;
  justify-content: center;
}

.spinner-container {
  display: flex;
  flex-direction: column;
  width: var(--spinner-width);
}

.spinner-container > button {
  height: var(--spinner-button-height);
  -moz-context-properties: fill;
  fill: var(--button-font-color);
}

.spinner-container > button:hover {
  fill: var(--button-font-color-hover);
}

.spinner-container > button.active {
  fill: var(--button-font-color-active);
}

.spinner-container > button.up {
  background: url("chrome://global/skin/icons/spinner-arrow-up.svg") no-repeat 50% 50%;
}

.spinner-container > button.down {
  background: url("chrome://global/skin/icons/spinner-arrow-down.svg") no-repeat 50% 50%;
}

.spinner-container.hide-buttons > button {
  visibility: hidden;
}

.spinner-container > .spinner {
  position: relative;
  width: 100%;
  margin: var(--spinner-margin-top-bottom) 0;
  cursor: default;
  overflow-y: scroll;
  scroll-snap-type: mandatory;
  scroll-snap-points-y: repeat(100%);
}

.spinner-container > .spinner > div {
  box-sizing: border-box;
  position: relative;
  text-align: center;
  padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0;
  margin-bottom: var(--spinner-item-margin-bottom);
  height: var(--spinner-item-height);
  -moz-user-select: none;
  scroll-snap-coordinate: 0 0;
}

.spinner-container > .spinner > div::before,
.calendar-container .days-view > div::before {
  position: absolute;
  top: 5%;
  bottom: 5%;
  left: 5%;
  right: 5%;
  z-index: -10;
  border-radius: var(--border-radius);
}

.spinner-container > .spinner > div:hover::before,
.calendar-container .days-view > div:hover::before {
  background: var(--fill-color);
  border: var(--border);
  content: "";
}

.calendar-container .days-view > div.today::before {
  background: var(--today-fill-color);
  content: "";
}

.spinner-container > .spinner:not(.scrolling) > div.selection,
.calendar-container .days-view > div.selection {
  color: var(--selected-font-color);
}

.spinner-container > .spinner > div.selection::before,
.calendar-container .days-view > div.selection::before {
  background: var(--selected-fill-color);
  border: none;
  content: "";
}

.spinner-container > .spinner > div.disabled::before,
.spinner-container > .spinner.scrolling > div.selection::before,
.spinner-container > .spinner.scrolling > div:hover::before {
  display: none;
}

.spinner-container > .spinner > div.disabled {
  opacity: var(--disabled-opacity);
}

.colon {
  display: flex;
  justify-content: center;
  align-items: center;
  width: var(--colon-width);
  margin-bottom: 0.3rem;
}

.spacer {
  width: var(--day-period-spacing-width);
}PK
!<x%5chrome/toolkit/skin/classic/global/datetimepicker.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== datetimepicker.css =============================================
  == Styles used by the XUL datepicker and timepicker elements.
  ======================================================================= */
  
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

datepicker, timepicker {
  margin: 2px 4px;
  padding: 0;
  border: none;
  background: none;
  cursor: default;
}

.datetimepicker-input-box {
  -moz-appearance: textfield;
  cursor: text;
  margin-inline-end: 2px;
  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 0 3px 0;
  padding-inline-start: 4px;
  padding-inline-end: 2px;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

.datetimepicker-input-subbox {
  width: 1.6em;
}

html|*.datetimepicker-input {
  text-align: end;
}

.datetimepicker-separator {
  margin: 0 !important;
}

.datetimepicker-year {
  width: 3.2em;
}

datepicker[readonly="true"],
timepicker[readonly="true"] {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}

datepicker[disabled="true"],
timepicker[disabled="true"] {
  cursor: default;
  background-color: -moz-Dialog;
  color: GrayText;
} 

.datepicker-mainbox {
  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;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

.datepicker-popupgrid > .datepicker-mainbox {
  margin: 0;
  border: none;
}

.datepicker-gridlabel, .datepicker-weeklabel {
  text-align: center;
}

.datepicker-gridlabel[today="true"] {
  background-color: darkgrey;
  color: white;
}

.datepicker-gridlabel[selected="true"] {
  background-color: Highlight;
  color: HighlightText;
}

.datepicker-button {
  -moz-appearance: none;
  min-width: 8px;
  padding: 0px;
}

.datepicker-previous {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
}

.datepicker-next {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
}

.datepicker-previous:hover {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft-hov.gif");
}

.datepicker-next:hover {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit-hov.gif");
}

.datepicker-previous[disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
}

.datepicker-next[disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
}

.datepicker-previous:-moz-locale-dir(rtl) {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
}

.datepicker-next:-moz-locale-dir(rtl) {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
}

.datepicker-previous:-moz-locale-dir(rtl) {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit-hov.gif");
}

.datepicker-next:-moz-locale-dir(rtl):hover {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft-hov.gif");
}

.datepicker-previous[disabled="true"]:-moz-locale-dir(rtl) {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
}

.datepicker-next[disabled="true"]:-moz-locale-dir(rtl) {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
}
PK
!<Dҙō4chrome/toolkit/skin/classic/global/datetimepopup.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

panel[type="arrow"][side="top"],
panel[type="arrow"][side="bottom"] {
  margin-left: 0;
  margin-right: 0;
}
PK
!<Cp00-chrome/toolkit/skin/classic/global/dialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== dialog.css =====================================================
  == Styles used by the XUL dialog element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: dialog ::::: */

dialog { 
  padding-top: 8px;
  padding-bottom: 10px;
  padding-inline-start: 8px;
  padding-inline-end: 10px;
}

/* ::::: dialog buttons ::::: */

.dialog-button {
  font: menu;
  margin-top: 6px;
}

/* ::::: dialog header ::::: */

dialogheader {
  margin: 0px 5px 5px 5px;
  border: 2px solid;
  -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
  -moz-border-right-colors: ThreeDHighlight ThreeDDarkShadow;
  -moz-border-bottom-colors: ThreeDHighlight ThreeDDarkShadow;
  -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
  padding: 5px 8px;
  background-color: Highlight;
  color: HighlightText;
}

.dialogheader-title {
  margin: 0px !important;
  font-size: larger;
  font-weight: bold;
}

/* ::::: large dialog header ::::: */

.header-large {
  -moz-box-orient: vertical;
  margin-top: -8px;
  margin-bottom: 0;
  margin-inline-start: -8px;
  margin-inline-end: -10px;
  border-left: none;
  border-right: none;
  border-top: none;
  -moz-border-bottom-colors: ThreeDHighlight ThreeDShadow;
  padding-top: 12px;
  padding-bottom: 12px;
  padding-inline-start: 25px;
  padding-inline-end: 5px;
  background-color: Window;
  color: WindowText;
}

.header-large > .dialogheader-title {
  font: inherit;
  font-weight: bold;
}

.header-large > .dialogheader-description {
  margin-inline-start: 12px !important;
}
PK
!<<W1><chrome/toolkit/skin/classic/global/dirListing/dirListing.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  background-color: -moz-dialog;
  color: -moz-dialogtext;
  font: message-box;
  padding-left: 2em;
  padding-right: 2em;
}

body {
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  padding: 3em;
  min-width: 30em;
  max-width: 65em;
  margin: 4em auto;
  background-color: -moz-field;
  color: -moz-fieldtext;
}

h1 {
  font-size: 160%;
  margin: 0 0 .6em;
  border-bottom: 1px solid ThreeDLightShadow;
  font-weight: normal;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

p {
  font-size: 110%;
}

#UI_goUp {
  margin-top: 0;
  float: left;
}

#UI_goUp:dir(rtl) {
  float: right;
}

#UI_showHidden {
  margin-top: 0;
  float: right;
}

#UI_showHidden:dir(rtl) {
  float: left;
}

table {
  clear: both;
  width: 90%;
  margin: 0 auto;
}

thead {
  font-size: 130%;
}

/* last modified */
th:last-child {
  text-align: center;
}

th:hover > a {
  text-decoration: underline;
}

body > table > tbody > tr:hover {
  outline: 1px solid ThreeDLightShadow;
  -moz-outline-radius: .3em;
}

/* let 'Size' and 'Last Modified' take only as much space as they need and 'Name' all the rest */
td:not(:first-child) {
  width: 0;
}

.up {
  padding: 0 .5em;
  margin-inline-start: 20px;
}

.up::before {
  margin-inline-end: 4px;
  margin-inline-start: -20px;
  vertical-align: middle;
  content: url(chrome://global/skin/dirListing/up.png);
}

.dir::before {
  content: url(chrome://global/skin/dirListing/folder.png);
}

PK
!<(s<<8chrome/toolkit/skin/classic/global/dirListing/folder.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڌSkADL"`iF%v؛bek/	AS$ȃ !F1޽1q@vofoǔ_/va"0`J]v(5׿?1k?<gi cpp7x?	_Ơ]PzLՅ\2ܙ%_ə;Gt	)'o2 ze'0
 )$3&TSDL*DbyGKI
DU'{6	Q=M~cGߞp믘׆)vE_R(,շ𗣭Y
+R΁-ʂAbu¶´҃;סn2j%Q8`=hS_8W&QW.xVD2PXUVo(R,n47A!ùZrʅOhp:<z9si=y+ï>f|}+@IENDB`PK
!<
X\4chrome/toolkit/skin/classic/global/dirListing/up.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<iIDATxlSKhSA=OԂZwQD̢"(qօ2BEuTY P"fbfzg'xa237s=+ŏ82X~ju{}%[0Yxߡ/hsLf"|7J#'?v ,L)'_*`mf8:]мG G8,_p5?')p3{
NbRCYP
`%_mߩ
=C(9Hr!%!DYvVF6l?hWdZIk1btZU
|ƠۯvfF<'_=dpi9DE"E>ȮѲ>jSK1Tat&6e)!:ş1||}e,553}2pyO3Ȟ8j5lu!`[0$΁ey7C	Ĵ:JP}@kjխ
!ek@<@a+8);D
Hז>wvfz5'5ڻ{`Rz5IENDB`PK
!<w21chrome/toolkit/skin/classic/global/dropmarker.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

dropmarker {
  -moz-appearance: menulist-button;
  width: 16px;
  height: 16px;
  -moz-box-align: center;
  -moz-box-pack: center;
  padding: 1px;
  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
  -moz-image-region: auto;
}

dropmarker:hover:active:not([disabled="true"]) {
  padding-top: 2px;
  padding-bottom: 0px;
  padding-inline-start: 2px;
  padding-inline-end: 0px;
}

dropmarker[disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
}
PK
!<}}/chrome/toolkit/skin/classic/global/expander.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
.expanderButton {
  cursor: pointer !important;
}

.settingsContainer {
  padding-top: 3px;
  padding-bottom: 5px;
  padding-inline-start: 20px;
  padding-inline-end: 5px;
}
PK
!<`XsM0chrome/toolkit/skin/classic/global/filefield.css/*
# -*- 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/.
*/

/* File Field Widget */
filefield {
  margin: 2px 4px;
  -moz-appearance: textfield;
}

.fileFieldContentBox {
  background-color: -moz-Dialog;
}

.fileFieldIcon[disabled="true"] {
  opacity: 0.4;
}

.fileFieldIcon {
  width: 16px;
  height: 16px;
  margin-top: 1px;
  margin-bottom: 1px;
  margin-inline-start: 1px;
  margin-inline-end: 4px;
}

.fileFieldLabel {
  -moz-appearance: none;
  background-color: transparent;
  border: none;
  margin: 0px;
}
PK
!<P.chrome/toolkit/skin/classic/global/filters.svg<?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>
  <filter id="iconPressed" color-interpolation-filters="sRGB">
    <!-- Multiply all components with 0.55. -->
    <feComponentTransfer>
      <feFuncR type="linear" slope=".55"/>
      <feFuncG type="linear" slope=".55"/>
      <feFuncB type="linear" slope=".55"/>
    </feComponentTransfer>
  </filter>
</svg>
PK
!<:)).chrome/toolkit/skin/classic/global/findBar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

findbar {
  box-shadow: 0 1px 1px rgba(0,0,0,.1) inset;
  background-image: linear-gradient(rgba(0,0,0,.15) 1px, rgba(255,255,255,.15) 1px);
  background-size: 100% 2px;
  background-repeat: no-repeat;
  min-width: 1px;
  transition-property: margin-bottom, opacity, visibility;
  transition-duration: 150ms, 150ms, 0s;
  transition-timing-function: ease-in-out, ease-in-out, linear;
}

findbar[hidden] {
  /* Override display:none to make the transition work. */
  display: -moz-box;
  visibility: collapse;
  margin-bottom: -1em;
  opacity: 0;
  transition-delay: 0s, 0s, 150ms;
}

findbar[noanim] {
  transition-duration: 0s !important;
  transition-delay: 0s !important;
}

.findbar-container {
  padding-inline-start: 8px;
  padding-top: 4px;
  padding-bottom: 4px;
}

.findbar-closebutton {
  margin-inline-start: 4px;
  padding-inline-start: 0;
  padding-inline-end: 8px;
  border: none;
  -moz-appearance: none;
}


/* Search field */

.findbar-textbox {
  -moz-appearance: none;
  border: 1px solid ThreeDShadow;
  border-radius: 2px 0 0 2px;
  margin: 0;
  padding: 1px 5px;
  width: 14em;
}

.findbar-textbox:-moz-locale-dir(rtl) {
  border-radius: 0 2px 2px 0;
}

.findbar-textbox[focused="true"] {
  border-color: Highlight;
}

.findbar-textbox[status="notfound"] {
  background-color: #f66;
  color: white;
}

.findbar-textbox[flash="true"] {
  background-color: yellow;
  color: black;
}

.findbar-textbox.minimal {
  border-radius: 2px;
}

/* Buttons */

.findbar-find-previous,
.findbar-find-next {
  margin-inline-start: 0;
  -moz-appearance: none;
  background: linear-gradient(rgba(255,255,255,.8) 1px, rgba(255,255,255,.4) 1px, rgba(255,255,255,.1));
  border: 1px solid ThreeDShadow;
  padding: 1px 5px;
  line-height: 1em;
}

.findbar-find-previous:not([disabled]):active,
.findbar-find-next:not([disabled]):active {
  background: rgba(23,50,76,.2);
  box-shadow: 0 1px 2px rgba(10,31,51,.2) inset;
}

.findbar-find-previous {
  list-style-image: url(chrome://global/skin/icons/find-previous-arrow.svg);
  -moz-context-properties: fill;
  fill: -moz-dialogtext;
}

.findbar-find-next {
  list-style-image: url(chrome://global/skin/icons/find-next-arrow.svg);
  -moz-context-properties: fill;
  fill: -moz-dialogtext;
}

.findbar-find-previous[disabled] {
  fill: GrayText;
}

.findbar-find-next[disabled] {
  fill: GrayText;
}

.findbar-find-previous,
.findbar-find-previous:not([disabled]):active {
  border-right: none;
  border-left: none;
}

.findbar-find-previous > .toolbarbutton-icon,
.findbar-find-next > .toolbarbutton-icon {
  margin: 0;
}

.findbar-find-previous[disabled="true"] > .toolbarbutton-icon,
.findbar-find-next[disabled="true"] > .toolbarbutton-icon {
  opacity: .5;
}

.findbar-find-next:-moz-locale-dir(ltr) {
  border-top-right-radius: 2px;
  border-bottom-right-radius: 2px;
}

.findbar-find-next:-moz-locale-dir(rtl) {
  border-top-left-radius: 2px;
  border-bottom-left-radius: 2px;
}

.findbar-highlight,
.findbar-case-sensitive,
.findbar-entire-word {
  margin-inline-start: 5px;
}

.findbar-highlight > .toolbarbutton-icon,
.findbar-case-sensitive > .toolbarbutton-icon,
.findbar-entire-word > .toolbarbutton-icon {
  display: none;
}

.findbar-find-status,
.found-matches {
  color: GrayText;
  margin: 0 !important;
  margin-inline-start: 12px !important;
}

.find-status-icon[status="pending"] {
  list-style-image: url("chrome://global/skin/icons/loading.png");
}

@media (min-resolution: 1.1dppx) {
  .find-status-icon[status="pending"] {
    width: 16px;
    list-style-image: url("chrome://global/skin/icons/loading@2x.png");
  }
}
PK
!<8ۘ% % -chrome/toolkit/skin/classic/global/global.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== global.css =====================================================
  == Styles that apply everywhere.
  ======================================================================= */

/* all localizable skin settings shall live here */
@import url("chrome://global/locale/intl.css");

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: XBL bindings ::::: */

radio {
  -moz-binding: url("chrome://global/skin/globalBindings.xml#radio");
}

menulist > menupopup {
  -moz-binding: url("chrome://global/content/bindings/popup.xml#popup-scrollbars");
}

/* ::::: Variables ::::: */
:root {
  --arrowpanel-padding: 10px;
  --arrowpanel-background: -moz-field;
  --arrowpanel-color: -moz-FieldText;
  --arrowpanel-border-color: ThreeDShadow;
}

/* ::::: root elements ::::: */

window,
page,
dialog,
wizard,
prefwindow {
  -moz-appearance: window;
  background-color: -moz-Dialog;
  color: -moz-DialogText;
  font: message-box;
}

/* deprecated */
window.dialog {
  padding-top: 8px;
  padding-bottom: 10px;
  padding-inline-start: 8px;
  padding-inline-end: 10px;
}

/* ::::: alert icons :::::*/

.message-icon,
.alert-icon,
.error-icon,
.question-icon {
  width: 32px;
  height: 32px;
}

.message-icon {
  list-style-image: url("chrome://global/skin/icons/info.svg");
}

.alert-dialog #info\.icon,
.alert-icon {
  list-style-image: url("chrome://global/skin/icons/Warning.png");
}

.error-icon {
  list-style-image: url("chrome://global/skin/icons/Error.png");
}

.question-icon {
  list-style-image: url("chrome://global/skin/icons/Question.png");
}

/* ::::: iframe ::::: */

iframe {
  border: none;
  width: 100px;
  height: 100px;
  min-width: 10px;
  min-height: 10px;
}

/* ::::: statusbar ::::: */

statusbar {
  -moz-appearance: statusbar;
  border-top: 1px solid ThreeDLightShadow;
  border-left: 1px solid ThreeDShadow;
  border-right: 1px solid ThreeDHighlight;
  border-bottom: 1px solid ThreeDHighlight;
  background-color: -moz-Dialog;
  min-height: 22px;
}

statusbarpanel {
  -moz-appearance: statusbarpanel;
  -moz-box-align: center;
  -moz-box-pack: center;
  border-left: 1px solid ThreeDHighlight;
  border-top: 1px solid ThreeDHighlight;
  border-right: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDShadow;
  padding: 0 4px;
}

statusbarpanel:not(.statusbar-resizerpanel):-moz-lwtheme {
  -moz-appearance: none;
  border-top-style: none;
  border-bottom-style: none;
  border-inline-start-style: none;
}

.statusbar-resizerpanel {
  -moz-box-align: end;
  -moz-box-pack: end;
  -moz-appearance: resizerpanel;
  padding: 0;
  border: none;
}

.statusbarpanel-iconic,
.statusbarpanel-iconic-text,
.statusbarpanel-menu-iconic {
  padding: 0 1px;
}

/* XXXBlake yeah, shoot me -- these don't belong here.  I'll move them later. */

sidebarheader {
  min-height: 25px;
  background-color: -moz-Dialog;
  color: -moz-dialogText;
  text-shadow: none;
  -moz-appearance: toolbox;
  border-bottom: 1px solid ThreeDShadow;
  border-top: 1px solid ThreeDHighlight;
}

sidebarheader > label {
  padding-inline-start: 4px;
}

.toolbar-focustarget {
  -moz-user-focus: ignore !important;
}

toolbar[mode="text"] .toolbarbutton-text {
  padding: 0 !important;
  margin: 3px 5px !important;
}

/* ::::: miscellaneous formatting ::::: */

:root:-moz-lwtheme {
  -moz-appearance: none;
}

:root:-moz-lwtheme-darktext {
  text-shadow: 0 -0.5px 1.5px white;
}

:root:-moz-lwtheme-brighttext {
  text-shadow: 1px 1px 1.5px black;
}

statusbar:-moz-lwtheme {
  -moz-appearance: none;
  background: none;
  border-style: none;
}

.inset {
  border: 1px solid ThreeDShadow;
  border-right-color: ThreeDHighlight;
  border-bottom-color: ThreeDHighlight;
  margin: 0 5px 5px;
}

.outset {
  border: 1px solid ThreeDShadow;
  border-left-color: ThreeDHighlight;
  border-top-color: ThreeDHighlight;
}

/* separators */
separator:not([orient="vertical"]) {
  height: 1.5em;
}
separator[orient="vertical"] {
  width: 1.5em;
}

separator.thin:not([orient="vertical"]) {
  height: 0.5em;
}
separator.thin[orient="vertical"] {
  width: 0.5em;
}

separator.groove:not([orient="vertical"]) {
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDHighlight;
  height: 0;
  margin-top: 0.4em;
  margin-bottom: 0.4em;
}
separator.groove[orient="vertical"] {
  border-left: 1px solid ThreeDShadow;
  border-right: 1px solid ThreeDHighlight;
  width: 0;
  margin-left: 0.4em;
  margin-right: 0.4em;
}

.small-margin {
  margin: 1px 2px;
}

.plain {
  -moz-appearance: none;
  margin: 0 !important;
  border: none;
  padding: 0;
}

description,
label {
  cursor: default;
  margin-top: 1px;
  margin-bottom: 2px;
  margin-inline-start: 6px;
  margin-inline-end: 5px;
}

description {
  margin-bottom: 4px;
}

label[disabled="true"] {
  color: GrayText;
}

label[disabled="true"]:-moz-system-metric(windows-classic) {
  color: ThreeDShadow;
  text-shadow: 1px 1px ThreeDHighlight;
}

.tooltip-label {
  margin: 0;
}

.header {
  font-weight: bold;
}

.monospace {
  font-family: monospace;
}

.indent {
  margin-inline-start: 23px;
}

.box-padded {
  padding: 5px;
}

.spaced {
  margin: 3px 5px 4px;
}

.wizard-box {
  padding: 20px 44px 10px;
}

.text-link {
  color: -moz-nativehyperlinktext;
  cursor: pointer;
}

.text-link:hover {
  text-decoration: underline;
}

.text-link:-moz-focusring {
  outline: 1px dotted;
}

popupnotificationcontent {
  margin-top: .5em;
}

.popup-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
  /* In order to display the action buttons near the edge of the arrow panel we
   * have to reset its default padding and specify the padding in the individual
   * "popupnotification" elements instead. To keep the rounded borders of the
   * panel, we also have to ensure the contents are clipped to the border box
   * by hiding the overflow, and we have to override the "display" property so
   * that the height of the contents is computed correctly in that case. */
  padding: 0;
  overflow: hidden;
  display: flex;
  /* Make multiple popupnotifications stack vertically. */
  flex-direction: column;
}

/* :::::: autoscroll popup ::::: */

.autoscroller {
  height: 28px;
  width: 28px;
  border: none;
  margin: -14px;
  padding: 0;
  background-image: url("chrome://global/skin/icons/autoscroll.png");
  background-color: transparent;
  background-position: right top;
  -moz-appearance: none;
}

.autoscroller[scrolldir="NS"] {
  background-position: right center;
}

.autoscroller[scrolldir="EW"] {
  background-position: right bottom;
}

/* :::::: Close button icons ::::: */

.close-icon {
  list-style-image: url("chrome://global/skin/icons/close.png");
  -moz-image-region: rect(0, 20px, 20px, 0);
}

.close-icon:hover {
  -moz-image-region: rect(0, 40px, 20px, 20px);
}

.close-icon:hover:active {
  -moz-image-region: rect(0, 60px, 20px, 40px);
}

.close-icon > .button-icon,
.close-icon > .button-box > .button-icon,
.close-icon > .toolbarbutton-icon {
  width: 20px;
}

@media (-moz-os-version: windows-win7) {
  .close-icon {
    -moz-image-region: rect(0, 16px, 16px, 0);
  }

  .close-icon:hover {
    -moz-image-region: rect(0, 32px, 16px, 16px);
  }

  .close-icon:hover:active {
    -moz-image-region: rect(0, 48px, 16px, 32px);
  }

  .close-icon > .button-icon,
  .close-icon > .button-box > .button-icon,
  .close-icon > .toolbarbutton-icon {
    width: 16px;
  }
}

@media (min-resolution: 1.1dppx) {
  .close-icon {
    list-style-image: url("chrome://global/skin/icons/close@2x.png");
    -moz-image-region: rect(0, 40px, 40px, 0);
  }

  .close-icon:hover {
    -moz-image-region: rect(0, 80px, 40px, 40px);
  }

  .close-icon:hover:active {
    -moz-image-region: rect(0, 120px, 40px, 80px);
  }

  @media (-moz-os-version: windows-win7) {
    .close-icon {
      -moz-image-region: rect(0, 32px, 32px, 0);
    }

    .close-icon:hover {
      -moz-image-region: rect(0, 64px, 32px, 32px);
    }

    .close-icon:hover:active {
      -moz-image-region: rect(0, 96px, 32px, 64px);
    }
  }
}
PK
!<REE5chrome/toolkit/skin/classic/global/globalBindings.xml<?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="globalBindings"
   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="radio"
           extends="chrome://global/content/bindings/radio.xml#radio">
    <content>
      <xul:hbox class="radio-check-box1" xbl:inherits="selected,checked,disabled">
        <xul:hbox class="radio-check-box2" flex="1">
          <xul:image class="radio-check" xbl:inherits="selected,checked,disabled"/>
        </xul:hbox>
      </xul:hbox>
      <xul:hbox class="radio-label-box" align="center" flex="1">
        <xul:image class="radio-icon" xbl:inherits="src"/>
        <xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="radio-with-spacing"
           extends="chrome://global/skin/globalBindings.xml#radio">
    <content>
      <xul:hbox class="radio-spacer-box">
        <xul:hbox class="radio-check-box1" xbl:inherits="selected,checked,disabled">
          <xul:hbox class="radio-check-box2" flex="1">
            <xul:image class="radio-check" xbl:inherits="selected,checked,disabled"/>
          </xul:hbox>
        </xul:hbox>
      </xul:hbox>
      <xul:hbox class="radio-label-center-box" flex="1">
        <xul:hbox class="radio-label-box" flex="1">
          <xul:image class="radio-icon" xbl:inherits="src"/>
          <xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
        </xul:hbox>
      </xul:hbox>
    </content>
  </binding>

  <binding id="toolbarpaletteitem-spacer"
           extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
    <content>
      <xul:spacer class="spacer-left"/>
      <children/>
      <xul:spacer class="spacer-right"/>
    </content>
  </binding>

</bindings>
PK
!<ћ/chrome/toolkit/skin/classic/global/groupbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== groupbox.css ==================================================
  == Styles used by the XUL groupbox and related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: groupbox ::::: */

groupbox {
  -moz-appearance: groupbox;
  border: 2px groove ThreeDFace;
  border-radius: 3px;
  margin: 3px;
  padding: 3px 3px 6px;
}

.groupbox-body {
  padding: inherit;
}

caption {
  margin: 0 6px;
  background-color: -moz-Dialog;
}

tabpanels caption {
  -moz-appearance: tabpanel;
}

/* !important is needed to override label in global.css */
.caption-text {
  margin: 0 2px !important;
}
PK
!<jdd2chrome/toolkit/skin/classic/global/icons/Error.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<IDATXGW	P`&T
BҦ6U̴IDf2`&18Mc-@9BeA=DpCbĨFѧId&>{~jƍբ={hhR-00=---RUUeill9|:>iڣE[ff24VXJ&͆><y8{,pE8v0SS߇/
jmmEww7?j9|ޟQ};,ii8ߏ7oΝ;BmEOR_?®OGTOyOCd{{#c*,	@łw)1V
x'~---{4鴑SJ!CquXu]o'ژa^DSS7l~,o탘b|CJ	p/,,bo݆TOO'q7 }]8}

R`%744 5g=61C|_^|pjű$։zH̙ˮ)m…f?LWYY9HNo>܀Ca/A\zUt3BOGl~$'QRRRRaHr޽{h\}b;Aقpy;w>6>b޼7F	XzLعsUT[Hoݺ7nnL4\N?pBP2d^T&$ 4Ag6mAGF
HEMM
QJ	Kp奠5$]]EOO=
va'>:"H$R@[Q;^&%!~g~aXgΜQ^8qByE5/x1GF"qĹbCDQDK/ve~FNȗSaClE>C5Q#
͐vS°:WW$pSȬM-b,!8X}}U0M	P[ji(..F.QyL>"مg!kd%c!7@JNҘ~,TX`"//0(XnH#RD{DBi	YYETe$g}乓&#Y(Ih0'zu/Lf #$ۣW2t}SUI.%
:hJ|1kLI($RI$swMbJ+,[f7IT?C*oy֌ȵ{<BömH3EHA
Vh!ȴ0K ʕ!縑4z	chb,+d%ǹKɽygnn(!]R(!bGZ5sI,шyi3ś35	ׯ_/𕃈+yQ-(**R'^+W8,K$A^KKq(mU
*C	t1`ҷ61R32J8dIKrW/a;H3i$BDl.X,)%	]ngbD9>\֎j>[|٣P[.ck8&ch!!!b-wV'Ÿݨ[un`G=a!"RZ~}?uxU2r9Š}D}s!B$?v!"6}|ۚڭO2W46P#G–1dQ{x7_=Q)f/h4x:Q=/I9h+W%M~|\~]},FKD,܌Ktj>e	:)E^\\-5 |}b2qm߹o.'_ʕ௤A`[ЍM׿w]1qw+VRQQ0I7#nΜ/B}4
Pv?	r?V~7?@IENDB`PK
!<.6chrome/toolkit/skin/classic/global/icons/Landscape.pngPNG


IHDRw=tEXtSoftwareAdobe ImageReadyqe<IDATxUR0L9eCOd|1O5#cc$Xؖ!M{d@=]ғm@DMu=9(<&I^n[bol6e#MwM~e>,KC4D99W+u^?0r)S!r8I&`Ս<澁.`7Mڶ5d7Аxq#x<$D[7wHڕDž[$q?LRd:78LCǺ\2b8MpBR@b	&̶:9-F)ZUE6탮;8P	1w#W
wOLD`jQ=,y;.j/_0gr@]$IENDB`PK
!<>B5chrome/toolkit/skin/classic/global/icons/Portrait.pngPNG


IHDRw=tEXtSoftwareAdobe ImageReadyqe<IDATxڤV;R@]%b3Cqp$tP\å'G%%e(II\BZG8M4^VzZfi(<iCg_dz]kŏ
,{%;9AleYjK$q4},bcNkF%HHy4|\|>Hx⓼:OΗs`RTzUUUJvO?&		Ԑq=I&:"]@qUbO.[tie@E-h W08|t<Ndt#z(
Į6ë4@d—}P5&b"bڝrAi,ɧ#(Q'-Eֿnw#4ݢ,.#@WJBBd@Q)2M--Xw"K:0
aIp01;'Ë̙IENDB`PK
!<w:chrome/toolkit/skin/classic/global/icons/Print-preview.pngPNG


IHDR @ލk	pHYsHHFk>	vpAg XnPLTE󷷷yyy1tRNS_iybKGDIDAT(S}б
0?!:	";lur18$bҊgK=!uX&	e}gc" !xf@v9V5Ӏ8m\aЂ~\u^;p%og0W3 L`dZyS26+`QqjãĿ結LR("zTXtSoftwarexsLOJUMLO
JML/ԮMIENDB`PK
!<5chrome/toolkit/skin/classic/global/icons/Question.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<sIDATXW{lv|/Cp	16WPɨDTФ)rҀ(@TUH)R«$L`c7ۀ;mr&oo5;3{Ό*~f`۷ӦM,0Aii)1c9rQ^^^=!"۽^ooOOϩ֓'O*@uu5n:߿Uz9NNN$d#XQQŋܾ}ƿI2PV@l6s+
!E|ӑP9W\.צ;H@…9B2/Z<c
F{|H
"GGEuX0wFҥK_9uT޽{WҰ;,'eee{SRR֮>4}އOf>y4ż7?IB{{{GccCJǰ'#fLi@sX\66peW*87?Ԍ4\x>ޕq^óA2u-!Lg`vwa<ƻ/?
5e{7nx(L&.t{;#dO\BÈD"FP.'$}ިPry=jU}yĄHϯ&TN2q@gre[b~FqݟOfc^EEEL^"vMQu`mRaW۰[}*~fFlaVRQQ5'|e[-U:SEɢ$~MoQbȀ.t;h^$F˕"55bVӇZ\f+SFb,VD
FJ-`-4bZYpv^gȧGzL_002mq79YfMJ'_u{SR*(u%&W<	c** ӍTM
 ZoH D6Pc#L
v=1*!ֳ>1D	E]VS`"d.,NX<qs`0
F8Xs!@	AQsfd#r",4Vlk0@)*s&=HQ~fKRTe3 .ȲQ(Bwb~
ĨO7%ʼnWs{rL5rrZ1wҢ)ӂ谏-#p"N@PSx=2}tӘI޴tN`ll4g537m$*|Cpp+F:ΣhʿJt|pO(Ñ!qCCClS(<t\kW5dBp1.`L'ݬmJZP=\עg箌"f5w%wb|Br)73ۗ.'zQbK5}}}hnnL;plٲg<9HGRLAɣq3oO8xnkK.L]vMBŧ\tjTRە^oT]S +ww7or9|@9VZ-Y*sA	2Ђ'dERRY1+߽`elnl{oRǎ}YGZ
9syrVהarVӆG[,]T3E? 
ez덭=<c^\U=6m.A)c0nfJ~ڜLO{h3FckivRHؿs555M999@XE@Dcj!2JfD)=x?*DT;%TIop?gIkv}};tz+\vJTA5	y3mI{-@gûvUZW}?]Lڗ-[h۶mҍ.X#ҊI8'[_>ۤEZs3b-D1>Wamm풒Ngڣoc,gϞ=G~rd͚5R|nb}}%{;~V\,IENDB`PK
!<؇9chrome/toolkit/skin/classic/global/icons/Search-close.pngPNG


IHDR0gp	pHYsHHFk>	vpAg0[jPLTE>>hhɂӂ;if9n;<efgÀЁ9he;o<u<|>fggh€Áȁ̂AGjb`M#J)s:eA^i t*5<FMTZbg0a1h2o3u4|5 6tRNS
.67<JRX8{@bKGD6GIDAT(SG0P#z-;"Ld=R0&A.s_J<
;O]JWL\kgk`Eӹڙ]ː8>rֵ?mөumi3Uw>!wl69Z]'ԭXd`!Z$9*#W	*- 89ȓj>?` />"zTXtSoftwarexsLOJUMLO
JML/ԮMIENDB`PK
!<\WW4chrome/toolkit/skin/classic/global/icons/Warning.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<IDATXWYLTWf32KY8,hQ"Z0H&6ѧ5!>CRHLPZE%jkJB4iL+V#FŊZfdYϙA@j}I9̽s߿Be̡Kjq="O\~f[rnlڴuRsskS`ZreSLL㿷T"&d0LwC.wg
A Yjfi`;eEA^孫:"H훕J%nB%Ba4QVVV7S3"4͟?A2_m+ {cرE)Py`RmRQ/vFoZ-k鷺g&5ɘFp1
AA
P(D>4k]]]
%sU`=
Ga4)q]~>~*\<J*Zcy^L;1lXPyvOĉ4cE\Uaڵ=MYN"`X&؊+jRRRL=pCQe: q1t	,Orrr*7nܸ*Nc3up Kf
u@ @$Hwxz{N)--}bYN"&^kNadֱ%؃Ǽg$Ԫ6C;eeel߾Y(OOX&I{wfB+8CA.w c7CehѢǖ$C23j5~;(3d1@DK	G<F
uHMMT-6@r7"P$s
JArl.A^ʪ˗[X.=
u> y	}^1/07x<!#+bh*
k֬,y<&!!v&M+
9LRPQ|h4@Bɚ:7 )y/
6ԓ
mїGHXfo$}#7MoIkfRhzKӊKQFB%tIe	h:ns4Sflw]ADh~ăOܨv"
&(	b	A0
m֭[+ˁrs>s+m40o*=}d@g$7K`YM(KNejZW. ;+<aVa_}oǽIpTu׮]5Tx}l	WSˑѲ0eA	L8abNМoa,nӒHLLzedcsR.YrHN;e pS,;
#iI$ތ*X5:zsq{:[hfůQ;+F\7DE{VD!#zzO*e&i+#gaʙmڰ('=Ƥ̈́m>Cm:F38; C&LxbiuN?ںɓ?L 2L3%p9Yr$9<IENDB`PK
!<1..@chrome/toolkit/skin/classic/global/icons/autocomplete-search.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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">
  <path fill="context-fill" fill-opacity="context-fill-opacity" fill-rule="evenodd" d="M9.356,1.178c-3.014,0-5.458,2.45-5.458,5.472c0,1.086,0.32,2.096,0.864,2.947 l-3.279,3.287c-0.396,0.397-0.396,1.041,0,1.438l0.202,0.202c0.396,0.397,1.039,0.397,1.435,0l3.275-3.283 c0.854,0.554,1.869,0.88,2.962,0.88c3.014,0,5.458-2.45,5.458-5.471C14.814,3.627,12.371,1.178,9.356,1.178z M9.356,10.001 c-1.847,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352c1.846,0,3.344,1.501,3.344,3.352 C12.7,8.501,11.203,10.001,9.356,10.001z"/>
</svg>

PK
!<7chrome/toolkit/skin/classic/global/icons/autoscroll.pngPNG


IHDRTTk	pHYsHHFk>	vpAgTT\obKGDC(IDATx\yhYng]IeaweEDPAEAĠ⭈2_}}t:chw1Gw:I9<~M[tu	z#eW{]_}Y:N"lP֬Yݻ]="Xɝ;wx[jwݫWt߿utt|pB@h2Xff8ihoCߺu3mn?-++"cÇM<Ϸ]vZ>{…pC˗``<YUUb/^T߾}{͛7OǏ7T466577?ikksV1}VT4\zvƍC֭[=F@7>xgXZZZl%#R%(߽{gsed>sbʔ˗/'⡌5(@śTfk><Ǐ`Uaj	v7X-f  Of̘͛G:x">yP_>$h@o.PP}0۳gOgQQqGז}UٿU2220Čŋ3Eyl…C&o~y{vNQ*wsP.`+1N]t+W47/53sxצYG	hŀtsJAYv9͛2
:hРϟu


ofKLq2A?	6ե\l\ٳgfyn:1c;s(`XO1<GOF݌ߟ:Gi~LЃzʠpѣGc?uԾ2zC$xR0/kU8lA_&<,HLL?}>K`K#IMJJRm<,'OQU[{{CԘYf1o0ĉ+V/_رc.!~SNeHpvxBܾ
Hc4زk⇩6է~#@>qEqqql̛̙ǏwϞ=͟?-X#G$&90<y2?~<CCRO}4V7@$%|}GZSQk<
%ij,H;f8.I&1O
eK,aK.(9K-Ɉ8q"2d5j\.JCi.*ɤ~GT5^40ޏ`^^	
j*̙æMF	X\CvP9@N07Ł%?#F0bmP̢`Whl!vN2QL$!{{b$gȱc٘1c<Y\+1Sl,Cz}B@wXs[1#&''7O48X㐴
3_"~´|if&2;7zhX܌k(E?7k}5)޽(䚏D4A
@@UTTpZr{ZZ'XLL 7\5c
.{=3)ŲiƍK4(:@&I/Et: 	ބ0gL=6o޼TlDaQ?$.}LL?wE+yyyV3 %$^՗CH@`x2X[G˱(1z
S
6tzRlgcvVRa63P	XW4	H2@kpxxu`BGPlZq+-[$P
byذalС,N6x`Jpf9+6To߾=屮	d-Tn<*rsse`1Nc`Z*i2Eɟ8zl>kjj2!'TI`ba4W;;5ö[`mʕq`YS%W!`j*yȑtM@<BL*wS_gP̽u-ԏ-!iɛ6mJ8p@
ĖlxY(l
m#6sV
;[k3쵛tXR}NXիW~ׂn,񕠿BVeet5Sm۶鱏]+?M:ik62LhY&,?Žo׮]{j>sbD7:ʝ~`	
+\_RI^:
?
?K3y ?X6iڦ	SP5&.m&:~5jMRVmAm6Sq.zMBSUkڦ6J״MѪmRP_ڦp4&̓6!6
&]8[O&ةVۄ~0bmS$n#6ab0PLTmۻih
1Xޣ)}iRm_4<YmS
oRƓ6;TJ,`m}h>zkhI`k*͕6U@Emfcyt%`MP{&h_mS_ty	U
>Cd6\HSmkI[D!m$M}lm`M_IM@}i4mmҴMI	@7m}id6L&=&ISmj%Sm{uX#mQCXM6ɸ&bto>h4)mҴMI66AR@1m
6jji/	 2Xum
-{j64X1	iiTԯIiI"1$ɿa6III/>x>&6)mI{o&I{oS~o/m&M{j6Iۄ{"&E&MT{6im
n<MڦH.B6+x1xʰkZ6B6	OKXtmzUMjOs&IKs$3@d'U6-pk[-`m'%"&6MܤSm#$5mmҴM?fZ1"6"zTXtSoftwarexsLOJUMLO
JML/ԮMIENDB`PK
!<5__>chrome/toolkit/skin/classic/global/icons/blacklist_favicon.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڜS=hQ_KYXX؈ A	ha
Q#b+4vHa+X{ f4p?}~R}<+")^[	oޘ8RB94GH<Ej<콂WAtwqА)H!̿2
2`mp@t FQwaΉSȾ|¿m@!d%˷WgKǎ!d]S
-סHrsGCz5M<
ז1wa*ZDs, >K`Ȳ;H7H,vX[cfv
jCh,$=U]c_c`y;Fxf;trizq Bڰx]RZEX}3@_̅p'ǘf=͆9£$C5`>ϥ3{h8ju}5`\sJIENDB`PK
!<?XA<chrome/toolkit/skin/classic/global/icons/blacklist_large.pngPNG


IHDR@@iqtEXtSoftwareAdobe ImageReadyqe<&IDATx[Tՙ33`P
!DD-
6X!UjY B0>&XYb$ĈL+U6Q?}Ν;w1UY:s}\L˲Hlq:~Nt_t;vl={BTd!r>s~[[[w^wu?޻wo3-{F"X }oz}!QYY^{իW0Hs;Y;wRkVدZw4iּyo~yv*`*Þ-?sxDO?UdefI$0N-`*:k)ww6}Bqi4RQ_Xxi{xJ<"
Dshs3reh4sSYM< `?uC]uuUV򧟢ٔN-i$LeeB0Ll
oU_O5_SiҔEkS˳'}nZT\903H~jxF-	~ƻhUWp+O#oBSRÏaCzGozB&!B	`<КSq~߰kX)Lpy>4!P0hJwO3v2暼w*N+@@
')~xP^`W^6,5>r2:RLtd*`ҳ`d"Px/TcN𔦁ScRafb4n󅻾Im>q.De4000}/!>WRRBq!Ml"ʪ~HT0b5koSK9sfª}K&RkRu"-w'$A\cbO2L("{I0a'?%JF5tT^[+dkkuу՞U@z6?N`LV7I
%Yxװ`i\wP1*ch;Ql	G4-NTbXlƍ^(˷$Ij߿_rSM%ejCcNDW|OHy"@8BAǑޞfHru`60Z	^P	ASgЮiR 	Jp
}ikg0xc*b`(^1'mWt726wjAhԃ`2B<(zowwK I›ߧ?lF2
6\4ԽBm[x|ഖD۷SrW^)-m
zMj~\HWlg%4Q>'=Y\BV!=.&cq:m:FC
/<R#H}(X pB:Q	J8Ip
wwӂk%3Dna$+/с_<IA[X[3L]##ڍVhW޵_|R11
(Y8T@	DA^%؎?~;4KEiyĠS
Mq=}M!81<|`pbDOg%iEM%j,cdtS:Ϗ=9[6
={uRߨ9k8G(gwAe8^qU CbZ&Hs_@gtoaXfޗ8`$ 1¸i+)gh.}:;گ)YLz3@~*<|_vP)q&hp:_H^. \PHMYB݇RLJWg[\?xލ7P
pb2O	9]6DSPL~췿瞣X7DVCSL[GMP縀[pI'ќ[nׯ#dق<>eR/Ν7rUkF6lA(-w\hK+%ɡa@_R	Ů9u_GJB뺁
nq'u#m?_"f
,*}7{6US‡<عsiߖg߲S[
}2qf
"j*|̙
d4 E)	L~^P.f
wѴcpN^>>6@<CDE&,J	|N+#A&><9쵠ǘ8"hX?Q7A+t")Nb<~Ch3ĕJK.m/Z__y
ÏPdgoUމ{'tي
S'BiSzypWKԊ x]w{9|V1,-
2;{6[	&bJ0ډnHO.A0mJ§U/>槞Xxf-d+I.WrX+VPWQHO	
ƐH[>}F`LP7	B͈Me̩ODT*+O+]L)%C]ZB=:
Sj|	J!SV@):i[~Dc-`s3h8#Q8}l

w
/2_(P[hSUӀ%(1׍Ad|8RᨼC;ȍ(	m5Jq<`H5يbc0ONx@*2V+YC*-{/64)%TiNQ/RbkD|T
Un>6޻7ҙRƮV
ANI{
U	~="t!j2cv.
0T=`u]n09U@
O`Ka(+~#-\JvhnjZA:+M%]Ju9!8űHAy@4:&{ZJJ06_ipB'DhER+"v?$3$M i}|iXmDDX@!l2c82~暼PJĨGxxb'dL_e)RZL~㣏R2"%esK&"cА9Dݛ#@
yb6 L<
?IeSwKP1q}a:νf:}
=B_Im7\$b'qbғ>P
I꜡P cXY8ȸSxK'&~-{@cM^0=&e7ʰ8ҍnS
JIUJP'^RɑPD'V=b>.]
M2z;ߑrX㛙ߦBzRe`>>6eŗhȑLdPJvWh8l}£ݍRHH5JHƢDHYZ|]'oERT44)ri13|P
а1Vf@'ˣqm<@EEf#[P$'H6lgREo/U`H8\	,@ #.7;
5s\o{=ԇ:WC/IM25fs4a{3
x	nd<+8FS‘Y4°"R3@Fܙ %(%R{"zՍ<	&@?~V*!;p8kk<nb^`sI/C\Q c	M
~=7܃ɐyddPK`JPO1G$k>lBἽueo|
Fv.ӳ39RJzl+P#
K%0vZT\ckk$#&XN~OPU
g;''x!#N)C=":|w eߔFsD
‹
?P33)|7Mc0T&'0K%|Q"|3EKR\0U‰~!Jl9qn݂r:Ci~ȅ/s)!
AO($2XBx	_蹴+>/J1$oRTN9wU'@?׋g:[)"
VW„I9rl	'rˍ3VXW'«W~+ow܆Bb'^Y/#rGOO,Wd9y8&*׏&Zn^ƗSmׯr׮]Ν?[P']|>lyꫯ~sޭ[XSSs%h77x:\k֬v&7ӊdK/mtz~XN!DqP}z~bJxV0>T֊?IENDB`PK
!<XV{{4chrome/toolkit/skin/classic/global/icons/blocked.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 45 45">
  <defs>
    <circle id="stop-sign" cx="22.5" cy="22.5" r="20.5"/>
    <mask id="stop-symbol">
      <rect width="45" height="45" fill="#fff"/>
      <line x1="10" y1="23" x2="35" y2="23" stroke="#000" stroke-width="6"/>
    </mask>
  </defs>
  <use xlink:href="#stop-sign" mask="url(#stop-symbol)" fill="#fff"/>
</svg>PK
!<΀}}@chrome/toolkit/skin/classic/global/icons/calendar-arrow-left.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="14" height="14" viewBox="0 0 14 14">
  <path fill="context-fill" d="M9.2 0L11 1.7 5.5 7 11 12.3 9.2 14 2 7"/>
</svg>
PK
!<K&||Achrome/toolkit/skin/classic/global/icons/calendar-arrow-right.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="14" height="14" viewBox="0 0 14 14">
  <path fill="context-fill" d="M4.8 14L3 12.3 8.5 7 3 1.7 4.8 0 12 7"/>
</svg>
PK
!<"}WW@chrome/toolkit/skin/classic/global/icons/close-inverted-win7.pngPNG


IHDR0PbKGD	pHYstIME
3|IDATHAKQ5*{, HA=ēI'0B=dH0 z.A<Yz[iE^z=P]ozfYkA23of73;av_6by=,I9"w|W*--W@WWHﯴ>JՕWq9Nҍ7
È"//'O[45隦102PC!F@4MMˊܷw56\#"<O@:p_8Ld},HCiDl0?d8aDu!;LLy11&bDֈDGN#J!J1<;-i\C܀'SBa!%6h|<N>}`\\xgIjtr[sUR78-T6{nM,螳e@ok7ЛˡB9ʨi;˦zwe]J$(%7H4ꁪ~pfe(ӤRfmyJ 1B]%Qݖ_drrR2	+GRa\^b\^;7(VCnjtu/o՚/g'e7_GG+~ǹgnmdhҬ1D?i).M\6v%\&oӼ?._۷E)7\IENDB`PK
!<%IICchrome/toolkit/skin/classic/global/icons/close-inverted-win7@2x.pngPNG


IHDR` }TbKGD	pHYstIME
 	_IDAThZOSgR>DĵX.
dW^`d]xJ1K6̏dKL`wKW"R>9.i9m9u!Y
9y~{@ҖjloGd@AGQs\ZVȟ
7Υ8.1*/SH¼<
Xj
':fnENa0@ױqvPWҘ:Ɛ
h˖d)A2-M%dnvGVHvd(Xv[#H\gs~n":s
@j{^JڝUU{

NM#ZOIm-aPTQ2ŵq~kDد2_AV+Ds+DX?Ganlt:: "̴!(EqM
0I@Jjj"TW\@DKe$R?dZ&`va쬬A(uѯj5nQ,`s:a*/aHR]ܢ-(φ&"^9+ذf(=6',"	ƪ*nqk1$FZ[PrTQSh6$Hk+V&'߈M;Z=YsI	N:0X2᧵ߋn@2Q@:zcZTuvÇ	r^t"$6ZAD_:\g|tye*A	Zkv$m;`Sh@_P	Jd	V7qFZ[;\*h)01Bs 5F#I{XTc$s3}}x^x҂>My㖡!cdJ</ mu}
,|Zi	/ΌWpӝ9^-Q5 %_Y	K}}DD^$_>L9m'J]=L
%)vBG#$JRL>!ȘCIaʰn+gFLw"AJEIxS$JHLXj]؜}I`s8bHD+@L]SQ;hDDR3X	zYƛa- amlccIܢ"dV~6= ݎթ)LMzg#6?QRGs?$RkxsV75a|o/͛FL
_'8cf``Sc<Dž{z<9e-	09Vۇ<}6&'5$?yM\vL75a0%'1w7 Y!EG@+v\_<&꾰6}wuTNjM]6^8k}|"Jb-^)]9t @̫ݻ:=_g-|>44 !au"cR%	wO	HdR`bh2.&6kFy-W=xmqYAGF>&r5Ѭ~ԻdDyl7aNtt.QКSKH[ҖMkK}cIIENDB`PK
!<ƉwVV;chrome/toolkit/skin/classic/global/icons/close-inverted.pngPNG


IHDR<{
IDATxփ#Q;~;Z#Ly`9F^amgm3Xۓұ)=b	~3h9jGTC:MxG5	K`bi/]uROyh6Mʢ4i*K%,,XM*l|,ly"3[y>z[{%jBF~fGxVv9v:¸/4o5uӱ̌F1-Ӛn dihS',Y>nl)de@UIE쌃AxX?Y'oX/
w2jDw
⮆OKb&lTÚDswpKOiM}Ѓ.4vd-\Mcdt8)V$YI/w/<Fe:K@_ M#TkQ冣,w3qkWD,cTi	K=,3@)?"]x_|z£_atOx4M՞ܰlc.IENDB`PK
!<t >chrome/toolkit/skin/classic/global/icons/close-inverted@2x.pngPNG


IHDRx(~IDATx~W5Cʨ<@U3)Pf%og6CɯSҌ|t4pܣ{B6$'	&d*2",Og!IxˣRwx9C|ȃ%a[dla﹂+WF/ll2j61qr_mJmR*rU&R6ϨTŽwʽ]TDܞQnetp"Q$(oy4E/%jFi(
j;"4'hώh[b9˨yHd%g),MyBx1^ǯ;02;asH;]I,Wy8m=яbLrHMVm@3WÇ%*|%ȇyBГE][x@cgd:ڽjͅTH٨=Oܰ$2Lۭ!"\#y+YaJsv4x+fq5d|vk6I{{j-yZ43o#tbFs5J>2:x]\OWhVa
3:J>x_쏯H%w?<ެ_wvAq.>d'Wk	
h+8;s}dgeϲR%g/(?9FJ3jrmi%'jiw\L
=͊OmBǕ<PF:mLL'fαJ6{7\W/'n1`M&󬎵w??\>
3&6w&klrFQ&QK65N.ڀ'ʝkBz2zrOFQv[<=Tcw5"YkbXN,nIqW&䂿"]U\&'iV?9i][|ӿ0$j)W'jNg2mr2hN}S+=H'T!Ue4f
4!^T҄AגMGN^KR7Z>VQɋZːQU˖t2IuI:&ZԮ˖dOXl?slP]:IENDB`PK
!<٬5ݣ7chrome/toolkit/skin/classic/global/icons/close-win7.pngPNG


IHDR0PbKGD	pHYstIME
6,0IDATHVky|3M1۞O6msM xQ 

Q*CĀA{<(z[HHq'}=d6/}o_{ԗ
>	Lz͛BPk*Ev;91aD$N,z5?xѝ0bjt!޿Il.,d"abvod$Ad{{Y7PccR
S4,,,moc*R
1)9R@J

'˅VKB E,D,Djwt$2ļm#n&m!9GT
?ע
j
jR;;>:@ @Q,"K%̗J>(}PVx&DFxv뇡K6۾]X#4|;?-8GGPJG.YRk@$}b@/.Rdb󗮘Vkڅv1c.ZKW̨a^J`^aY&.р.=:/ZC^}6E|̼x'\_Gs}3BöѰmx2}F|[u[uX-߯Q42Q"hnl頹x|	mۅm;@@{I*SJ)4^uY}n7CE,2Kkfs<lԟUJѓ9#?&N}!lǟvzQI>׿݀-IENDB`PK
!<J:chrome/toolkit/skin/classic/global/icons/close-win7@2x.pngPNG


IHDR` }TbKGD	pHYstIME
M|IDAThZoL[]C06BئB[*m_&K,ҦZMi6i5%ȷ]
R)J%J(f*NҒAw>TH=>wϹD%*QJJl-'K5E[P/߁A
,GuIwNލ**VD&֞TG(x܎w@5hEbhDY!RѪE3>ʠUA) M sOUSWbTTRs2ɔvZɪ<{Ӕ'ǻJ?<7w?ZHΞEÑIviup:=ouv;0({!#DĈxגBDNh޼*rk=Ѩ*Jr8T*z<=lJY7vw
NfKlh:q14HJguC=u'	`ɹP%B\C=uyx nhL&\.@UWS/.պИLIr_Y_ñc\0{<l2fYʺ1Tg՟_~ᱺ$Rē{;6#dxz D7Ƣ󂀻^/6JL3nn#_5%q(g_ %|_蟺=Ӽ1
-KFΗ1Ƅ\!(V&&D("[.OXhxfLz0&8G7<5Rn pҹ借8'8g҈<ҹ \
"B^/(Xءp!'&Wr  fBL&2ăUz&A@ptˁbaշϟ
-D83' d}l[:#cebx8А-F!X
ŋA 
Wq#"Up$<HY%?C	N8O"ŹY=2}rMm vkvͱvKo.n"I?:t6nwvj|-})nČO[3	r A<3"4>miN	KQ?;;t]lՊF+p@˅5`8T:'>e);;B0 D
61Kͱ1ƒJTۍ6EB;Kvk?sowRoXfb!x1`Gq|ކ~@w`Ν𾲾ȏ|~aO0.W.`a!K|3{sRqHڭ-+ׂV^lcOv2'F}8	
7d'':s&}M+0z5.ccXVG|qzk_EQY0p=`{].5#%vkȨ{?\HM\}
rFϊ.XRPh|q $E~gf'{!4>Tß_T&U?M?v:cl=:z44F#6C̐ЀͯN;k	)wa}1sR3bΏ8!_#Gj)ܜYo#ND[fm%+x/鞂20(++ѰtmJNnVbB/}Tslw;aJ|utpVEOiD%*QJ$ _2	IENDB`PK
!<ã<<2chrome/toolkit/skin/classic/global/icons/close.pngPNG


IHDR<{
IDATxօnPVo*sa#nSae)aj,W41>H4@i~z)Ҳ9C,ॴ~zl7%6yK=[[Mh\1qJr4b.HÖ5t[-g)5#h닙ߧۓ%_Fo|7(Eu0$x<b'_m
Erv=[L{E"{I3L9sv	*XsWڰx*mXs{V/
}2g̅B7{cz±A]ڱ#e%1#e%<|FSL瘘3Ozس}ڰ}+xmXJ	g
lh~^yV[v$ib}DI|־:+u?q%"a=QN&*ى<Kcq)9عZtJ=e-Y5}-Y0w-)<݃IENDB`PK
!<X5chrome/toolkit/skin/classic/global/icons/close@2x.pngPNG


IHDRx(OIDATx78/ Ec{{Ly9Xt}9qS9n"dEkۿHзɷX;c.xpB^s:t[|[- 	poa.l$½zXRBzXB
A	z@	A	b2-\KB2-XHs&wD;͙Ɋ^CJkLGp
(EG̘+@)s%Y2CS2Cʋ!9fc
*RW&V(2lD-gp63K2L\Pf&y":6U#wjٗ5`6Y}>T]䫷49یq9.C՛,_N|Do5ي[Ӡ+Cx<Hgr2|"T%Tg's*Dy7K<Rn.ZRR>G1ٖđɶ\}&kA-yW\N K2sr|}2o#<&p9<=/lOHd3r@rT̷)A)J;6el崡
dCr6`O=^AӃ
yP	pdw5X}qʅ8zܭ5Wtݹ5wP/]Ky#AWCwI-Ջ&~Y?Y*/-%f6F*m%fͫ19Zd츖"2eM&%L	SDnM3هsFnkT/d/q2u{?.vd,<(F<ܚF	d<ALޒ@kޮ(^\@1YD!{s	#`i\Mn[xP|u3W_[E&Fo
BOp9>;7>yEs:jyo
Q{LŽ.LŽz(2+(2>ׁFx>ׁ`Ly<Z]y!Z&bk=mezm)-uFזkKAtm}NC`?vyhMIENDB`PK
!<<<t5chrome/toolkit/skin/classic/global/icons/collapse.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?%B0bq(%! k@ĆaP,a 5k{4 1z	W!`sv48lpH-L#RRVb{hh?HQ	A ~`;G}vIENDB`PK
!<s5chrome/toolkit/skin/classic/global/icons/error-16.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<@IDATx\S]Ha~}9̶Z-~rA`n?‹"PQ,XD]EEяARD 

uQA?Zcn[~^8y9y'"^/BII6ݮs8j\P Jq_$!DaA2rN3eggyBH[[۝>$ -s~~~]WW׀sqVr8^Y#{rΝbOO)>4y~㈦R/1J+\ŝ/vvPF`k}}}Er@@BB|FoZm	ѹb4r>zf(4ȕJibb̼y
+454c"ϞINt6{%}D
7n`vxX'a(f!ʱ)@qOF>\3HGL4߽v;+.P/,'π"q-B*
='XZ-fi:A"LsNtԻ*V8JPHBW/,g(>U|o/rˠڱ.K0SUŔN!VCndXV@gZwvv.h#hDxb&jEA>-h!E1	oWGv8E"ߓ''Or SXE92ri>yq\3|l56d~$$/¢>l4؀	A|"+|[:ޡd^CĖueeeE166:I9#{y_V33yY/R`xEIENDB`PK
!<qU"3chrome/toolkit/skin/classic/global/icons/expand.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb?%qx@Ox/ʁĜX*ve@@7³fz1` 0}(
Xaӂ0j>j.͸uqia|uPK(I,`("շIENDB`PK
!<F|<chrome/toolkit/skin/classic/global/icons/find-next-arrow.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="12" height="12" viewBox="0 0 12 12">
  <path fill="context-fill" d="M5.547,8.255L0.538,3.53l1.239-1.265l3.77,3.641l3.719-3.641l1.264,1.188L5.547,8.255z"/>
</svg>

PK
!<~ʥ@chrome/toolkit/skin/classic/global/icons/find-previous-arrow.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="12" height="12" viewBox="0 0 12 12">
  <path fill="context-fill" d="M5.407,1.5l-5,4.599L1.65,7.283l3.757-3.387l3.705,3.385l1.296-1.158L5.407,1.5z"/>
</svg>

PK
!<xr8chrome/toolkit/skin/classic/global/icons/folder-item.pngPNG


IHDR 0pxoIDATXíXk\UgBEbIVB6nD*.Bh`L?e,"B$˶Z0XBELpe%Mh33ɻżd{;g|{U,--0G_MOOmQ}4אE_yw@UE*677_Vo-..>?77#kW^,@
T
~P']"B^R<88xcaazG[`TPC:TSJZ
3<<|ZQ=WMD/2T	R{,^@n222fyw"00A&t2@D'qJ%\.9.AXA%iϞ	1}g+ҊX´AB
(hnۣ;L "{ rAlctXStxKY^v&vXs
cmhLRJ 8Ӥ#Pj9	"9^\60t`4l[-n@NeqetB*Aa/@le]Y!bhm<e=7"zq88De%r-xoI
@Ѹ=??~vy;of<:~&z1%J!}U=LbhfPpMs)<|I>|S| >E=-An>	s\yw| w'(4&}{Z<| .+zkzzz͛7BYdjjώHDfffW#,ޗ/mV*ֽSTʃW.W`xxdFR3 |S|xxnR	
u!"^z`ee@DnFe *Vįv
tl6m<yzZ
p֭fQ	:D_v ?S| d꜈~_bBQth3nE1Zr9֡ 2tZe[fwϟ?K|]grr2FS^]])Bbqt`5Aq1}m8|DU?4b`HIENDB`PK
!<qt1chrome/toolkit/skin/classic/global/icons/info.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 100 100" fill="#424e5a">
  <circle cx="50" cy="50" r="44" stroke="#424e5a" stroke-width="11" fill="none"/>
  <circle cx="50" cy="24.6" r="6.4"/>
  <rect x="45" y="39.9" width="10.1" height="41.8"/>
</svg>
PK
!<n1;chrome/toolkit/skin/classic/global/icons/information-16.pngPNG


IHDR(-StEXtSoftwareAdobe ImageReadyqe<)PLTEfffFGHIJKKLLMMNOOOPPP Q!R"Q"R"S#T$U%S&W&Y(W)W)Z*\*`*a+a+a+b+b-c-d.e0e0f3h7j?sBxFwFyFyFzOzQ}SVVXXXY\m|⛹䜼柽\tRNS-69Qc:IDATWm;N@>3 QCX,hX
_
H5g	n-@j:~aVlJn^Οr͙BuoP2]HDߝBrݛ2m[ܭ+O_/?bT}{}I1!4sccjz}ooP2oo^XDIENDB`PK
!<Q-
mm8chrome/toolkit/skin/classic/global/icons/input-clear.svg<?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 12 12">
  <style>
    .st0 {
      fill: #858585;
    }
    .st1 {
      fill: #FFFFFF;
    }
  </style>
  <circle id="Combined-Shape" class="st0" cx="6" cy="6" r="6"/>
  <polygon id="Close_Button_-_Normal-path" class="st1" points="9,8.1 8.1,9 6,6.9 3.9,9 3,8.1 5.1,6 3,3.9 3.9,3 6,5.1 8.1,3 9,3.9 6.9,6"/>
</svg>
PK
!<h)1)14chrome/toolkit/skin/classic/global/icons/loading.pngPNG


IHDRaacTLmfcTL(D)ptEXtSoftwareAdobe ImageReadyqe<IDAT8ˍKHTqƿsw5⣇#؃	Q$Q	 ضDhբpR{l]!|e*:kǽEi.4qw
=؀q!@hH=ǧ`ւ_dLnlZhZf-}Sn1s+ANK5K	ĺag8
c͘Y8b VsH%ӴD$
HeKM&--C~$jpPkP(cI"cI4b=ٚW7KoSp!Cl;sV,.:B
kJfL)-vI{I/wRJs%%L.		"66ݘcpWls)u&#*ޡ2<y&iDƙ)aSlc5ԑgNpz}#Vά0]vA˲LL_Gé5hSL=UUv1p@atr|zi%z44),>vX5H[ff^:/L&T07ʹ,v{Y[aTf(0
w 3I[2.d;]-^ظ
>Dam=Yb@-畷fcTL(7äfdAT8˅mHQ^3'~ڇ~!GPA+@B1}jeZJ+&ilQ24qDTͽ1Nl8s/@A7BGR()NJiAAakP"a4&+ sqf6zY='&Da@X-x&27JqJMRz4ez<ϳW&	Uo>Q[=chgj@n	04O$+uu	ѕ}.[竎oY HS
c۱vJTn<\$ۻCTL(<X*gE@t^f{گD󠲳Qވɑ!vuJjӊ"PX騂2;BO2,zg~J|=H*(}'<Iigӛ}e3DPF2M<Iyc1IYyV_avqqO7:	Uؓ
h3dɭ-=ɗFUi$9E?/|1&,Bwur!iH^LQ(Ȏ1|%!u'?rSUfcTL(aM|fdAT8˅SMHTQ>;ost$GD"RC  
AEhA"kʅ6-j6PZ:h#8μ}Γm>ss=p%гs{GQgWH#_ЙqAh9%"H@E$DMr(3s
/<::`(m	&*"Xⴰ"-LK=4]''WZw钫]Dp٫4'@z)eу*ږ+fU}U5B7`!eMԷvҿǪJ8)kXP
p2ifj)RRK*(do!`'꺅RC@8rHB`FbIaV1.CqY-P\}!G0mK
i`HeV\ِ^W[93[r霫+U}b;19VS''HҜ;-|.fmqs06=_0?r0:c`g\_OiNέd:3ݚeߗDwJOޣ;
nF>d61MM,1'=6oXGo9_Q4WfcTL(kb7}fdAT8c`[8؋9iS-sƅLW륐ՂҌlزwqXtGǛ=l6=>R~2cڮ@Cj6ŝv7_ǹ\vv9Sۭ1w!sŵ	1$lAY`~ήoZߨ13ah?d@7LO>0$m3Nؤ/
<	wn\Oz?}c!aSNY)_s'gl{cK3ܐkXfCd9A9MwƷ,05Q3Uhg+
91?st 9勎ڜ_S㚰);x)3HM*5O_0IzkoKE
slGӛ6o`Nc`(`2:~wU-x@W0iaF cj=_	w?C.8H%' VbIk؁}>Al(@
D	j/,2U1adbQ(ƙdU2ODfcTL(ayfdAT8c`[89X9yS<K.MS5։ X3`oݍsxz>,Nvl4h=; LIZL&}+KǻN3X;ޞe.t!qx!`7II򵂯c
C[ +h}Zmo;;ޟd|MU0::M#4sP?m6SǧW1$lTc`H4gqpia.s7L3`z&>w7	P{ߜ+gP]^gř?	i}	_3`$
"ʀ̽_qN%y53`=vbW f$dcò~Z?C0"`{uW/8w;0 fFјˉ5sf)o_}şL=Zr @ ͊dt>TgLPx1TbC1j#;o[@.w
@oPYi¿Ub[G]feYFhfcTL	(sfdAT
8c`[8فX)q'k, .cNޔb^d`04#|Z*US?ߧ<mqV^^̒s>PM̞KFEo⓿~`|HO֊63ĭcWqY?SE'}ej{F9Z(qkǻMl?Ɛ!fOsG҈Os[3w;SRv0-뮽K;\ᆴ&s;{j5\Oo?u[
{`.sgLWb_MH3؀3||$mec^B̽_~L% Į@BЀ#y}9?WS`(A]m킽/Kc?@ׂVPCP4FKڮCRk_b_'A@M@HaLZS5[CmN`Ίua2~dBtUwPX?9'{C9t#YV\5RfcTL(aSknfdATMlqivmjPx5"n I$KvFv	'E܄x0[l #s$o8@[<ڣJڠ~^N4+"rF|ٚ[Dj
o5xHOW͡Usu4#}E<<]C.Ϸ\ݟ_@5%4d[%(Oybsەk^aky1Zi~1;Tu³5黢4RC{̠1ßw`@
::~oSꚶ±HQTJwbڕ5@fWJu6b$HyT.ɿ0OD*dYto71]?0?ā=YbTf"
c@ɬs
A
hVT'3{%ZpIϭ/84ۮ2:9Y<ݾ{F?NN41xXg?\A:OR^~3_l464ӳ&CŃTϥWZyHh#;Ip$ltO2J_jz=cogtfcTL
(!fdAT8ˍKHyKߩsLf:D-f(nrQ@ff] (JȖtF!dzwmLtyv//?iv ɭM5jmƯVjq~Ҿ.%$kϾ0?u6H&RI]U7,{arW}]@>7'찪Fe?0Y#ڈ4Y571Dmu	~	{?_DJLٜI9L~Yx,`f/ 3ӝ06N:il<`'CA?)'DbY'd7(@KOXc'ki,46oZ*.fgw	RHNLW[j-{E[ͯ6~-+n;1j$?d&FM_}`̟a~qĵR oMyw=j_k׀Ӎvi]Ƒze\8zR+ݽ=
+012:Iaj
.ih0JՆơz~s ^ۋNbco]-qWJR*bl+7}J4UC]=n"-W.UY:'g3ԾfcTL(aDfdATOHi;3;ӌc,vAQ bյCNA ǍRCAQdIuF<ϯP?s[xvt
.)Kd0V_KC(`s=Wݨlk[R?,	}X^n	u!"nRռ,Vpɨ1'ަ2[_
;:`ǔQEnDdCtO4Hvt<WSs[=;Jek4cje`ȹJmXq
||V!sS#՚
E$ɴEyUXIcm\OvdszUUTڂ+Qu^=~7ZVÁ̱kj"0졷o'/u\)ZQ1^k?::@wN;i|TCD.7GN(u︠b
'@?!rFkGɊ\zXsV?f@)?Kq&?]UF𐙏ɕ?B]᧯O5XP
r՘ZUj5#%ܤ8s^.) |UZPcrЈl1Byid[&FX8)lOQfcTL(EEfdAT8c0ZLb1֘!n-3c@6fb vb_6\Oo@e:3ljfb+F' biEd51R+@yDiTad43%T3H#SZ߰wSv\I[
9W?=ldQSV߉a`Hcdj{k4Zh§F"uk)NxiO3ܐb\J?M`-q&C (v8I)/
/1]
H񮋧ֶWMb3>ͦ4 ~
X1S;p$Cz&u]wP0(uUy
hc{
s}@͇XPzQ!S)wS`HǐY!a8߮	0_"Kmo!	)yC]6	?jhs95{/=Nbi~}u2^bgl=?O_~яR/X'~%sׇ+cPŐ՟ߴ/Xz>ftc-C;EpS7fcTL(`ӖfdAT8˅MHTawQ'rp1RLQ$.]`QBhk\,h(ZDZXFFAD/DijC4;;E"<y#{|6|Nn0tև0Z
¬ϾG
@37Lx-ڮ=)grV}&^X6f~
x
71!mMmzKk{E#^2zekO6A->]uJW%Ӊ0XҜ-(Rv_YvdP2f2QED`5X^0rKJ$HȦLʦ5GY7TkQ!!"b”dqdP,/vMEYD:yc1T$zD=T"N՝`&?OArܯ4Fbns3D|{,0DٯWDLY6t8W5x\CfȬaRp2 ="NM"8ťXgJk:?mR(	S00Kqb5~&8<ڵv{v0G'_<{<X/hr'04$fcTL({fdAT8˅KlaNچ6ƥ:--bA\v+6h\"DlH颱!ae!`.-iVu3XA9'ϛ("Wv3Se!!/lѯl_}őARd}"ѼA(y@F`!&gg6һ(x7^M,_u1H6,Ndkģ'~Pl_ٹ9I3EQOgסVhkMOOMZq_1 t{;^
h,M4ME{`r㗘E8a:۰ ԑDɒ/g3`r8,O[8ymDba)*TqXWguMvԟaVRmI}hl~~"D=|q]DThK_4婢v 	wlo%Bj\r~H:61A$	pھ꾵h[ǖyKj5wfsšкV7'oGDU\)xSkIZlp;(KFAbZtxYQu	I@!4&~m?f W޳t:d1'/g˩O;(:4cfcTL(`7~fdATOhgy&MjkR3NEG9C6a0ZCw̓&ŋnCHNjڦMy
"|>‘{y[zF`R6Cw?|
lٹZz`<GRyz|K{{֮0U"tQ`f3TPO~ғ\3h+_!]@Ng6`kqnM	sd3h6_}`!UH5{S~Ljɩv&Oz&ҶϪĬb_Pʾ}Xm,VB)Mj{֭&2ԱXV@Rouzc-KPZTガ	/60~8R8ED5uzFQ(@in+G=/[cwJ0B.'|y
:w`<=.E0 ((EHkHKG)3ʯdnJf9Cѝf:$ЫsOSωȼ&J6b	X-Uۚ! ^K:K֋P'P;nD:M
>
Ϸ

$!K8۪S˼e%m/fcTL(fdAT8c`H8fH)1u	}զ4ZY
zHw4]_3?/|h/4IA1\?_mjT2E@10hTŦ?U:f?N651k fUWKg.B
҆1 mdYK&|0#;
`}S`֏=!@]ǔoz}2\`?I_f
`_Ϩ8΂!s~30v~:S[̉0]vw&}f`hzsǷ29qJ~FÌc]_0޻
!qk^]#:C%6`4a`x_sLe0of[
(7%|5,$o``(~&ЀKγ5><’{x=sʶ,ɛ{8N=Cp§\f}Lpm
;ez.{>,88?2ë́QV[ߴ2H51F4XBfcTL(`j'fdAT8c`H8fH.ǐ=syѲkaLݽ!e:z@!i[x/?gمnk+c5٘Rw_{kW[EfD|%}$nm2oϳR~?cϏ~51+jf-xg|5od&E{'22?/[iĮ@6EϿ5!@]
S/?2d 	1w|9C&
`l{wsƎw6Io?u% ^h|Zs[	sWk110]Qcj;G=nsG)Prh@nw,u?RrnPa+C542%n0m8G⬟,oy! n=Ce6ͬwpt`{i[뀙lXiߊ8,vhJ[q^]671@
Yq	_Y&
O.:Krwyj\
/fv\@1m FnpTLfcTL(]pfdAT?lq{w׾wZ-HUh$%"`,XHL:JDE"XDta!JҤպ\{}5%a@}>‘}$9;"Lv9:E[W{>LݢzjU$cB~isꮛ dHqLN7LD?P!f
k
#^?\c`79bz5ӹ=Mt!cK<sN"dLk?dznkY2[zzNO{]}k
!ϚTڗP=_O{7@hHjQf5S_/(0Tw4zBʨ35Db3A)~'ke%DY$FgAF?,0le@	2&B1q&9ܿ?цtKBF- |ñΡHrQ̤W0)U}
|f^qۺqG+bZPKb09roXn6fDj[m7cuM~qiVoExHB{7rvJ9zlI7RS$#%HQE&3Ƨ/Bn
ژcA{V}9?|ڣI#UFdZ`	=C=?}:jfcTL(`6tfdAT 8c`H8fHܬΐ9u9]yrwbu!yGC-d`	!Ml@ɔwW\}oRvN`Hņb5o^X};S?쓿?fG}͛\3f70?+c3t}ub&V˕k
<FCߠXq>s7L_3t|C'ܹg@i;gbK̸aIU7Zlc,o2uP{fjVb_ e`jxEwG2i8qWغޟ
<D d	]N-P0& ^ː/͌@.X(nwχ썏&0ĭ5%`*vI[_ruqwf
@ (n-2XW
qk
N33	%8sPzK@M^UlzZi嫿_P5"06sK2D&~Bf|?TӜ;@su)_Ps#@k>7\]-^Lhgy3	S:J+fcTL!(<fdAT"8c`8fHؤ
ĭ)[O3nǚGSIK#g_ոgo	L_zS<!yl(1C~kϙ?gOo&~7[n_Y{2d2`CS0u}|iCoB=z~:1Z۬!udLeWNtC,thݿמCn-ԝN
8ffwVϐsDGY:aOZ}g?kãp8an!c/!@X~wUY?``n}5fI@R@1u9wχUlӁA/ĭe2\Oj/CCwy{?`G@ΐh,(zOSm"`v);X}VSF-A<@PH@.& W0159_b
	o2[nI6^7P}7&PT0D^fT\gdl{[f$`4O@M@@kV7@UFUqOeyq	AO֌ZtEXtSoftwareJapng r119'aIENDB`PK
!<1Ù7chrome/toolkit/skin/classic/global/icons/loading@2x.pngPNG


IHDR  szzacTLmfcTL  (%ſtEXtSoftwareAdobe ImageReadyqe<IDATx|WKW֫OwO3g<;'Lb#@`H  [EH81$HQ Kv2Lӿ8UuOǔ\5U{c/^?;PϳtJ~OG&zW$c6uY3<D9+ldlǰRM0]OTʵ>1{<!x<J0*_1Du^(4K6	qaogf0*S]Ӿ^E?}惡`ptJNSụRzW_4v`2ˆ*{j6w*OюogoLE}(,bY@$B`*{gk[&d8ָ>AkqҰfJ΍[6{˅۔gƠ]&UɩLraw܆΍2#K6.f뙧MF
RVIYl399x!LBBdUU![ݪd>ZDcz?F5~ϦV#qT!luuK0Nײo!KN"8eYr$o
FHL;xh.x$HXɔ)t'w?YQ2cu"#OI%Ÿ$TPHB/E7n#bUr;6&^4g"JFk[wm4
m2yB2,JkN؊pT	ZN6f'917?#OqT۠lDyڟ[_5MHe'LEMR4
5Qd^"ƙ41J9ҋס:%r%V]9혠 {=u!4FJѷذ|FYaҔŊ΅3kܚ׋n3Hns)t@e3Yp:LY/E)3ntQyUF``ke"˔5eF@8£l|RHiJm,*ͮؠKp`
^mN?µ3\VN@"C
;`UgNl}@<uvw7 Hֽs.(e$̂aTCPNn	DLk%5+q 5	=$@P93@cf@"|zQ!eVTKp=p`w(_jNc7pǓcc
"NOKMS
x"gz;_/ʡG/M5KE\Sv#=4!6vMOQo)^-;gŲ"
<"^bw1v"J>գi5Qi*ԁQ;#B pC׽ x2O|=ψ he*P]h=_{w<0dpOߓhNUF`㛗_+MIa2 Qצ:֐~py+~g1oO	[(M:'[^oSnw*FM`ٳ(`|M<;>21L_.[^wpt:˞`@,JK~omdlYYϲpĝapl: PЬXJm¨nK&OyD@7~F_bA-e,F#%2`kmh'@l,AmvhFªQ݋*"̂rCR	FJl{X;Y`";F?rd2H	cֈt&}7ABFh1{
LBv,9unkܒNk n/j()K_W`@ضuy77n
M	mxf7Tmg`ĺ"/}C4t=u9al>w㿖N1CtbGyU|R,u^xxJQ2Mn0\6V-s{o^Tz=W,
	1&8
G@):,
&
VIL/~gJZ.fcTL  (uV/kfdATxڄWݏG|gcr
$/(HyH<%
E7@%8 ,A"a
XȀll!>{qw;3]Tܞ#wgg_ըK9(s
Z҉~,=fP#
P".*.o;D3fy@37ǧ֚S
RP
`$⁠/1ia2+8k('lq}`B\
d$H	$=LNDX` <40PG;O%OIv>i`l&M(/,bMPPo*ۏFkfKdp<BdA	)_qbDiW ֡
#rVSX,	2n`"DW*-4C#*-,>M }]ԗXP!4";5o
$k%L'/F~mHs`-OKōtAlWdesUv~f<p4	R6^"E
JXTnu%wo\|f;w*6Dbٗb
Lj^;ѯ7ﴷ|=u)ϯ׫&gc(|U)8lD]$z&Q6|i\۲ GM#vGoWw%G߱͠e
E*r_`N1Aҳb7Aؾ}۾uE|Er֧{ا%k#{?޾}t9ņ>ռ.&Џ#{gw! MJ^Z-4sI5fU_]/R8w\1gqLP@cڳLdq
N'mSP"EϨg7~҂oGPHwÂP)v1!t"@ˁ+tnC!qR"}2@Vm]l8I)〳L?tm3hs,(B`NR'8VF{b	\ySNL'j)	W!hPh=,ʨ)S4ͻ7uE&K0S=ƌz <J;,a2nmEw\,0=|pZ
h[֘c\O&677F@(=aQofݙtJ(ris"ij'4kcWiiT,MﰑGarn;{ìj~~u=| {bIQJHo^|o	ɦlZ9uℙCƒ`C) -n{	J^rZ4	 0"?q'Y >zk]
r..PN
zʀrh'@qҨQA„Yd{K3).M,
cmjb[j~tlgbbOl#gs[4,Ń+v^.,n<Kkm|Mt.X4EϺȉx`tϧg7=YsQR1-${&:
\y\6n'>zBU~`OZiFc`_r4ő˳xeuF>VE ,bUO}zÏ0&YFjra2v<^9-ΕwŇ>tꋘ/#Ud&Rpqsŋ+NTކKxCοvڎB_s;~Y2<x~?p2wO܃湨<`iRTwp-K;gO4moɿ՘A:W/mB2xFo.8~`*#:;`)*9.܍[>!Ń|;e]}{`WLԽ`a
)4{_H`{$н'p|q'.:p|2n]UtyF9h3:L7=o>Թ|{]0ÈnbЇ$~/dxf~hHRW8i}:
.t\ky
'$a׏FfcTL  (wfdATxtW[\Gsegfw]ko8		8p l)B\"@$x  ^@D
oGA	E$'Q9!keWΥ>ٙY֜s'g`+(桹194og,#Xȼl
!mgu|Eg#4f1ʿJ:PK#f"Q$&#a Ĕ=rѡT;И=.A5<
j
3*d, sF$I^`zօ~[Y}=0+UIE8q6*|Y5kAwM{|mOwWW>u||nQ'9{Ӗ5KNR	+B!(Q>rNP?D17߳Li{|Cj|B	 qaTbj
@*ld!($r}хǦOetSA5Rw.p[RIL&ٖdY*i'SfIQn:#Sov77zb3ST3+m3`Z(BK: Hxeku%O!
>ѬۈbGkX2Evz"
IMLp^u?}BKYT`Yꏨ$Rx8%['-]nD$@	v罒Gϐ	njUo(vaCA
O(V^ۭMqC-UkTkrr%m?Vr+<8vwkN؏{boLwx&Z?<Ǐ(Cr㐘-CIɚ^\xBlI#6.2Ne̟ʓ<(
t aBgU*wO
NID˾㔇0E.n6'Uo{KfnTı6!S$Jh
C
Ľ^ߤ;5`EY9D.d͕EӲNjPTT
Ӥ=)wI`
S<Y2)}.\B%^Q۲e%vSBQ\q~.&Zz[Bi?s`Z\Q&\fP
ЀwX
:ž=Y[|^1HQF
^Y)8!.{8R19{cp4;\fݤj=V"t[g&^ua]S#@@:<Z8fŰ4njDȠcSߐYȣjէĚ3b*-Y
d(w@r;xd:85E
fIQ^%){Kk]jg3`/\вo稟oᴬOz=Szu+J+IOi0{U0~NByV
k(ېa2'dOR>tBY=WAPr(\=?s׹;063^8#bJ4ɑo/74UhHIiJM<gB&kW߲nݳk<ƫY8kB^YjLSFYn7N}|dWDFIQ1Q{9;O~#ڃϓ":ln80]?6Ha®p=*u)qz$<&#翶)#B3N-GJQ_ڍ[`!ׄ7<'ݴ^ٴɩYTLȍ!gHLWR^C]ŀN*j94ݸ;oT7n,Kה1še'#YtSgZT)(9gF#Hm3cӃcRt/Ĥr_A
%azmX?v=k{
!(k0b8
kFD:xs/v<n($t[z-)ΈwmqZy ̴cU&"E^̥` mpନtUKZN{3*c8q绬{i9& N 5dizzHC!W.u; ϓpTS'%Cĸ!COFxG"R[[FIU+DӋfcTL  (u
fdATxڄWMl\W>ͯ؎Q*J,
`BT$,+!$[$袛,*h@J*JP~Hq{~|7NO̼w9;wy%/q1M/qY")byD6Ƙ
i0m2m6Cf}:O܁`4s|N U.{<ϰ'4dN0I$(a:VoO|?
cj/q_L,7kLFHd1lB
8I${s~GUk|ɹ85C\blʐ5i͒J5{ïwof,s',:֢R7$m
Z7o?;`)Ll,&xt>Z̄??6^թzSc0e8cGv`
a6ȑzhJ?l/1$5slYi2Wꂫ5_qRE`W#V0H*DX&	csfw3?XQka;d$VGTklӣ$b6D/\޸7|4iדOdKFՎ윰NJNao;peW|C~j+1
<
)wc"²NW˒6PJ8`"2ғik{a$%#dGL1PsΝWG:PoL
w %zG߉R:u$}21E{WwU{+_іL#`;wn^]w\dsiB{wY
Y}xUir	"O^	(RHSzHe;=Xc3UDg]StRKKV+iEIh,R826w[7-xx	3B{z{oh36yUJ9<KeFѧ	M1pE>xqkxQ'	 D;a?/QW{H<"Dz8Ndt@x1v|训JN
nԢ4@E21#NRhf	3і24_Yl(DB*/:ر但&~:6gEaM)m#%@C]C|'ܒg0zY=FRՄ	H98 "䙒#V$#`hQs=d;et$".06(l'G0&c)zx#"*ڪK1[:Fر?C!!‰s4
KAD5U
"Z^&xWS)|>eǜ/^z<cX4U}ɍ"G%sZ|\w74ȒUzv3,^=ZKFg($;fmAŋc7H/nO,\RFLX'qͶZ;rӞHޠ4"9KnN	yEwNdh`*`&g`xyD<zzS
Z<10>Z٤9R9pk-m!IA_Dhr>@`jJi婣>X(['Ժ֎ByUaQle}lJ-E"8bm1ǴR|#SRQ+L@A/Bܛ3M[vzKoj)8T'fInU%LTCu!+fm^{x4M1~le.%&Qm6Ve-	Yp[{{k]GX߶t
H`t9#v e/GYvяbrk;}SbN6os{k䤏i.9u7G;N
w#p͌_/̣xj{oc-A]/6Vםqu\͋]:Ї,R:~up?019u$L~uO\`*",r3܊p	"WHV9ysXB:R41vls޸rlq'qiWgyQfcTL  (]fdATxڄW[W:\v.;Yﮝ51QA$CqpP 7	x!HHH䇀( !, ;61&;{Υu+ztӧ5ݗ^ B}P!2?&
fИ>u
`͍!?K!׆{^ȟw#B,<AB""c/q
$ZyDMwu&Ã1I{<E;F'EiS~	JI(7!y$IX8TfH<螪/v۷J#RwL3Ln.Ӏ_G|F]	߾gB.H|"A^d~0~m~G[/^nȏB-q`FU$c)wCjWFHq*<Bcq!j7Ev|zȱQ(~U5fʲ\#a&dGSŒͼ$9n[6U6jXFr}o,Q/?Qeiʠָ:8L8h%1}	
!Ԙ9AXnp)Icq9A*PZS*oa3d$2qf4\hf޹H;,,r㤊OWY;24Xjhg9S2da3LȥMCCkoݸM8.;W&ݺW|4=ϒy%[5L^| {xeZNoW!+‡eɦ<f
BAﷷo^;}{NC6ЮРCر(XEeNi~
aKC`eZY6C\ q!at$8n]Ę{jh9^UTbPOCP$)U屩VUHxllڑpA;c;l|dپ}զR/°ΈI] ɯScS`@kXNDsg/_0|oLU3"jr
go^{Expϑ)QeɥvRR1fG1E;Giؙ(2.ef4ഐ%H#<`k\u	iͳAֵB;%ҕ!`(䲺Y]8\eJzGBP}e&Z;5\
l'
r5bIL)?"UQC)ÞڈЄ}T$)Uģ̆'JH(bXDCbۅwAvv
#dm'
ZAC@4B#.1j

.,te48dUr%Sv'{m\_4R0Vh
HJQB|cZv?]B)^މO< !_퍻,YiΨPi2Gΰ-EW >+x, l"
!j#>.ʆ߼?"vfWD5+êcZH
'~>7%㾣4womSZ!Q(ձ2Zlj%gamRP<8q"]nz~Bhj^*a
?dax"9#l_,܄x;e(F
D=?yg#1Gժ7]]Rƛ܉\םӽˠ#Bவ@A&k+t%S'>v6WqHN(6X}f3n{/s."oԹy\E9N+H	iҲLBtsEzee}_>Ai飺v3e0<yɀj|hEL6уzUbFv;p9u,՘o?:.2E`Q,RaN:8E۽)~ezKpH6#<̉ds)IqDT'7SٖoSΓ[g!.ȉ"oN\qUW:#eӚ52;"+qM׶\S_+;9\5vlݰ);hhmhd9,!7fR3_;CrRhr&	cF b	Z>JF-~>c	hKr\Xy7q۾G/
wNvBʽ?WfcTL	  (ulMfdAT
x|WK\G[Ug'X8	!!B$KEXς	ĂE2`$ŀXDBOLڞ>UŹ3=3xFկ{hG	ľ[$&([>e8A$g|
ΕsI.
nK{ݦ8[L>~jwtDWBj)"'HaHgr6&;CItHqp ;:!V>qƳ|Ux_/P)ąR
ZKAIXCI{٣T$~y=3'8r$SC#2J8X<G˖A)Pҫm7[kGX]8Q!3
'ȡY闝TG@bh\ %)rx63KIQ,WfqY
FU"թaOϮXO.fX#rO˜!IVƤ͌Ģ٣Te(7>yi_A}Ҩ)FEi-
kdm`((w$UDyhtz(!淋%!)6ISM$V6;"I.o}X(1vx*H}ѩIg.ia"*HDGLc5ç?;F^p[D.sO+\9D.(ՏnZuK;M
mbQy#ORE9U\I#n,?ք4AXDm_QowZ]_utZF0 /X#VoBߎLϮZ|TQL>QuL'Qdz%Y?)E^8o6M
C$n\61JdbҙDl1L#yCi3b(U*9cKԱe\mn"
]X|SiپĔe@QN%clxVjٸ! ˶ĂUF 5+FEoҐKYm:8&aGn6lj
{KPQaӥ(NHK
*w\JdSX~%'2LD80 6w
˖Va<ieiJc'*}.+^Τvyޅl+{2j'\jHgelJ3^ 	3GH@vzZ(!q9vEܑ}d+}wy}*&"!Q!YAN2+9OUG2:ux8j>UĢ
 :jnd*²,`X;B92vbgePEyr}$Dq$-2GA*=y챧!>OLWC賹eX
1V}?(@ՄSDr8{b]ҋ?vLȀ"iĘ\NZ<hiu#_TW
2>
jd͈2#+
miyDDyТy=Sŗ+*c+kqF
0+ň(JH}?b#ИO
-&1{fqb$84G)'e,;YzV8A$)ɫ5C+#WJƝ'o_C'b‚`'Ku樂mí_q'1m8+WNfT#^fբ|
ʶR?m`8A|TM=b(]ZiUN[۾k}q&NU
s_61F:?1bed_T^UI|4hk;pSXR8iĝXM8&6,C	<
ށEbC{vE4ZY|lmv%Sȥ͸~ᄸտ%=TU"+;]0Ak>\}Vڢ{5+Ļ}~𷾱G1&]msѓ5"Gv.zҼZ_EM/vw2?\^쮟<;;~[mC9[IIm(H>(J^mo3=A!=?fcTL  (yufdATx|WKGyqı$9dDH 8$8r+\pÊd	)"B"{0&y?^ڞy{vfW3SUWg.o4{̻-qZjX#kSkM^&kck7=`cZ2k9NR@*X
%XH,6hat&lH$CY ~oDj
s>qG1$*JRbိd"mH2Y{S 8Rla ^>ABӲ>ŲR'wr=_¸E'Xs)+Ҳ<KAV7b/|u^)h6U}ڈ=cdqɀ9j<ڝXTBVoǦ9PVkVXg :[	qd1Ӛ2Ŏ'jyD&‘WX
}ZDoق3MM&$n4͌uPSnOhDY !{TgBGj3tfS쑎Jm2Q:x
2kxͤ%1@}hT4gO5(׭,Np%D$Duu,ԟaE3@{OJKz^d5EϮ.wWjq6>FcQH(ЅaH0MvW9됐/0BJcc;c?n,]
ɐ=1S?kMƻ=O?&LnRU hB{y"^ؓa?zFBڧW>ng=',Hi8ުTs;#IKV{k<N	ĭΫ
Cl1>[wXHYّ)Rһ~1. Ȱ'F`z<
..]vD*Bvak(˹kz{|.JJA
%̺^$݀SxP?9s!"y%cùVCK}V |dHXAQT/ӌyrRt0~ؒ'!6Mo
<sij)ݐH-]
'm>~,cY$^>FiՊG_C.ȏVVT`J(rnYJQu+<VbpW7$\v>	X؞[+UGi,x"v;@U8̘5CUЉ2l؇>uQr/OtoFbd_#UfL5$.~JB'=&]2-TY,YB!I#Bp?po|MRsrMt5Y<s1A}_.,7p	@u'0N=oisNŪc4d~rSd4"/}Wv	݇/z>s?/fw(tͿș>٧}aodg,*Q?Z=z*Ƀ?YFMY6g_WW;ermn54njßd^jC
Sv}Nclhäs
b\7J%+|V'~*,Y
;#y3Or$6
UfJQ:#af({]Gx?E}#w5=O۠+PT݁qFSK8HTv'><{~t֐tąsˎ0L8UuSQ֪QӚN2z,qjF*U^|quUMg
y[^f4fnQ*{n&;ؗtt:Y'r@Q!Vu%/Ss(K_8>n옕QÌ{2vKS@łWޭs;K4ċ+mD6/!:7F
81

ʿo' փaEA(0Dd9=TРJVUs
X͔,Bq-	pJʖ%%WEh{3
+Kl8`;fcTL
  (ufdATx|WIWc߳t<6$-'(82IXf\"![6HX
,	"2Ye~Uyn[WuzusoDdhcG>p.0Ɯ6NY\*X2ZdLCZWnup}ySoC}r
gOg`!	Xh,uqFzwؘ+;{gyܸso{	f:|Z'/?gٛېkq:,Ncy7U~NL9гUW,Naf0|Cf?O+Hn\`:8;s/vmh?YrUa[0=MΖp!i!sZߟ>q\*"=J9EE|I,gXo!8I\/)<?EB3>,>	AZ!@62PNj7VuFΕILJsORr~B\I[s}HxRSr9$MH}0VɻFF+I38\{+!ϷO8!s//`ӯ->Y%:N dCHRy^W蕝՛[u:maTԦݨ߫N\Mo8eö̜HS2q|T.se1 {p!ό};i7	Niƕ\vWԡ^@zyꡇwW(()
n""3wl%<ˀZwoLZu 8AJ)lw
BP[d2`-J+ -`G>
0d?(nX牢i6p%"ծCd:l܇SҥK[G/)(χ b2S3g1Zu+^doƗMl޾(jUWXt	[bfE>jdEAIkHȠQ(+c&i$(^Hs%"6ŴZ> OӀ+Zb,ۊz"{А2[2ݨzCQľ0ҟ4hv춣Ɣ\q:
|LJڤۆfHH!j,%[3>zE^o1(?+	uh:`P[1nhóNGB>FyDvmoa"	E"	z`Zza?`tmć

Kf:OtI-Z}<̣́׌X'&fe̹KhN@
4Ѓ86Y]RP7L^FݫNAW3F;5o`8˼<eR6tB5A@Ox۪W_a=.~:g"*m?W.[q^D?4CiqWynbO=swJUD?z'n㇥kx'8.ch,LfkI:t>iW"
{J;ﴔ/ZS1{7kF)L5ɜfe:?ue:·!:ogf&F=`92Y?DQIDE_ hBo޻c;7w[ސοXPLP1t 'Utǜ4UV
Vx0@q!xy0iKܑ:	u;R"I:M\U
dQ;teuy,QkCQ}

Q1N]zN=X*uۮayCl[-/]l/tm"/mI[o%6	Nt:ZýJI茏LYЮm7>٬Rö	%`xφQ-9Z^5oEݖ뒣/&"	^\YSdW'#cg/8{39M$d:uㅵu`
EtN韻@e8tډƗ+%2"4%wF;{iqmJgN"qw߆b/<v<\PbSs6/ON+%>Ymϳk!8trKg_Ϡlsʘ|O2&P2IfcTL  (%7fdATx|W]lW;3;wv`nڦM+B
*P!%-H<% /B ^xG	!S@m"*jҪTI46q\'^{8gfvwu:Μ3z@?
_ƃGK ăB Ji4&@cVPG7v1hTW_F(`Nt)}@֘Zl`=qh	< pm.qwġ/TdƘςh)B%/nh+0v>g:cÏG^Xpuaz
;s5W+AiCFr-Dx/vmoi~w?(Ñ&OgT%-Y䃖9v,{E
^ HMJ!Og[*)ǥ+&GNqƹ%i?x0m
(GBPjt<=ߘ{hmS_&tr,m#FY-AJ4a
7Mgwt:t{ @nH?8*JV~2#(Ż*uAZ*3AY$'W,[e
HQ;8cܧS: (i`^Er|l3QU@v
cė%`$TIsyk.AR!;QUO[eU
B))0atc7mTRCJ;q37_^;-aHD*2CιB6YmtzvY)bW$AUw3O;b1e/;Q=c"!uo\PgN(U
鹲T7IYXGaȲ|KոC~`E#5=cA8U
<Rѧɵ3~	Y_?qkEy(H:Ƶ:Rpl
i2JDɛ}F\ӏ:moPT}WBpwaܕ҂a $bciqtVɭPnodl7?mh"<I!}㰲rv!eBQI==jʵЀobLe-_vp]XOjR
T2R~ry'腬m1v{{gË
1kkluօgz)N;2X`MNN,,.ʨmgQ7H(QM7_'
szQՇJ㛐ImvrS(QDbЯ[[9dC<_7.)`
*޷g8`JThA˭ڤ4>XCc176#?!SBkQR!/j)15BWb]UAPkgϟ߻1CYFT<d;OI$!n_"p9D;<M`MV
cju-_<V?t)Y7Tu5?`#s"
w&?IW0mL))Q)q4Y?
kq$[g'z@RߎIW۷_qW%"#gK-S}7&si!4oӫ@Դ6$	X9~JZZЮ!q
w:kZnP(h"Jx,vVz΄[z#<*DTJ1Llxβѽ4깄DCwڏ,Tv[-m?9ӄs5Rgۑ`Zb>65H!IsYB&c7,	qΏW^ʁdhoU3:ڼ		iUM@jL_ y|QƊg\]]nq:+eRG6>}_a腇'n_ ǷnE$C^c*b1qR'*@TY!%HO,n^HS;i>
F
;>DF(Q!q0^W\\ugs)6:N./?;8`fcTL  (t$'fdATx|WYG>̾xc#YBbK@b~W <+D$2b
Bm<NlLƳ3sު^\inw߮wy{0zd%>'pă%dmbٲFXc煔߼ڶߵݭmr&2_aqI1Cc[MI䑧ɘw.oX``'ByOQ1VJTYS`H^Iش$M>ۭL{֝~ufq\
0nVϑsXprpecVGj[nL̞R}NAxW`l`mĚB(BVLXktG:;;OuP܎A]˜l\V"	90R#&GŊc|)	RvS׸04&騽h:I%6hN`Y:v1?sXW8
q[(r3Ǽ+P7]q0}pr: ϲWG
((WYԒ	~w+d慗0p3?;au۵*|eʻ6BBV77ʗWGǹT}X\D@P:S~
B4CtЅ#̋>ro%(­.BLnmZd@of?pan]ua	?zݹI@3#ՕočM	粰C̘IڭrP)$T
"+I2R*tȒIO2HTmդM96
tϻ@$x@H[Mv:09Ju$*k8,.F3tA$a3k_6nYDZSvBL![<_^]q2Mұr5|C* Zf"]
M0`A䘈C4y[헥WVC*4-ΜyOC9r4}fmѩ߽pwfQ \j;.	<88@YfrKizD}xg\:0S,g[w.Z~]C_2nhcT^:RX
1cOy5\SܞEa!ֆ"1ؔ5OYR]bX]"imU.C?0FĪOɩ^@PTE8Jm5_0	&ѡi
; @_12'&˚@i`50kl{Вl.y&?P}?vgIBGQZe׸S:Y0ل./abB*}{pj=n(@Ff42jh

Ătz	vmeiݒ_tFDyux]mQA	'@7	3T)#WX__?"g !aoV&N}vXd`jApyIUNZ-i8?'ҟ k60~exD=ݣk9{Q}ܥNo4aD-=2%QZTUE	-!=l姭NNjfe{A%?0YQGOXu"PƮx$B޸tĕ$|6_te|3"K:"o'қ]MA"rpe@ݰ5ɤ3fRkcA[ݼu#:FGIfTF.9q'9Avuje?F1?G043yYj(b#RVS%_\)
@`hu憎}rsksʸt2v@[n.޹EJݔx]H ެd6mS'O8ҕ|/Pƒ^t=5]vׇTJ5<QV^h3EZ!~lp}moiZ-h??aM݁^+uO}'V>׌K(m	L+NFgV2I>EDI7ipUZp
)+

MNbB;f?dD `
J&ؼ^w)j!
_fcTL  (zvfdATxڄWMG~U33;ޱwc,GHDBH-B—Bp!' $EBaC0!(6a_z<Szfvf!*uTwUߺBL'hRy/3<%1븮{~vݟ'~ =wq{P9)1U`WF(42[';^qΚ5|K
=ۣ`Kd6If=Y HDB4&Iz7?IVn6HX;0^d?#|RH%3~9Ylx$>\H*,)/Jh*,1q_`aQ8G,:@{M>^Xbgjvm~h_,“gL'9[9GI<!PaHGOU=LY@z~AߐM[0$uk->K /eX*ċs'N{O71!2ϏAB_*/@
_??Xz;<½б7QFDH!	.n՟D':m<JD+ܣ[ꍕ%tڤ{.57Vt+M*L*vGN3ۭϭ^J'a>
OY.,D@m/^_N0Gdl
Ih?ʗިH<yp%$]4	EE˫joln?i{rw9x
۫[?R1͵׳x	u4L{Ȗr~(DQ.Su8?˥̅*pM wiƃm
#cZ[7e/#l ,=	RVayM3y9,K#4Sfsy/e$us%[7z@@ԣRS*GGȽsUIml;"io, R(3TR`KfO-s_Hq	'62YP^chfLfEFJfGhb&KOّ)1
N6aHN/K:m1cX\G17a\eC<V@4HePDmtrp4n:m:\A'j95EcgSy\.DŨq1#WyvHhHar(M_]J,ʖ\#U F@q}~ڜ!ȷ=Q*h)Ɇ?dEQI8bVR1{J<8]A%,sE욬AB`t||
h'x'MK+ϳ
~haސGitd5<LTțzivq(H^xɕS{Ty;]i5>PTԌR!!?<=Uy%־[zt&2+ڟLŪ*](ʼwY7Ed;O
ЁAi(ʋbJe9jz;IPeB<ur9S$q~$(<w_KHR[	fFHeDl4Do'q|m2Y	&•c2˰B9Ɩp%mn(N 
 G27W:yr3%!96/i
+ӄ`/`8GTzA"
MFI\?XG^%ly7Aree_Ex*Qk}vy^}O)?SƂbba&ҍ{IfgL]9'!{
oxky>ǽL>bθM͵{oԼK"'ftxȹ㝞jFG=ɝJZl]zâ	/픓єNaX\^Y8}#Edr픳5ltt؈eT*wv푎yhS/Mu m#?Uv":&eI7%=Znq=eZ|eW~rd|J6B5?Bүya;tm%
PjQ,46
fcTL  (txpfdATxڄW[]W^}s̜$&֦I-4AC1^
GyQQDA}((66mb!mL2̍L̹ZZ{sL&3+kvoaQ߾_vt5Έ#ҜbG&ƚ))-eA<rC1&	sa@ JQ˷9iötp߉ӣ1xX$Pih,=0+Ze^:G
[m;̂_~`f^πw\&= 'o_̢tϼ|y{Ҽ}3=~e02v=̴yJ6Q9Gٯ>5O*UǞ{m@1A 	<KxrE:MSo,]BG_ɍT[=}.GU8($txcxcoq&#}kcFx("{r0 BY#WkzHB.NY8W
pc0nw6:͍UZxӌXny3Iɦ}0r„؀\)Y9SodcN-1}Ultjps6N9q
/XsqwqdRl^x>KtLTf#SarNkU'ha1)Ǩy[x,ɰ̡70D4)D}O{!gIN'b1Ekڄ2!,^:EhYN *s@g)zlWuܻa}9Gs3&U<&B> &)yiӜ~ؕVֆ=IM\u1d*/$ $d	aEFd\/0T=e{e)ֶX;Qi7T+kt0\@($Z;$&(Ⱥʓud.@յHP:a)7΄;ܽ%"k{Bp@ D%>t$(L-E6BftAIRRW[$K4B/,[Q~rx_i(Bjʤ*\O.\0'Vrϔ'EvhmJT9xXKm{)O8gk>mk68:ΆBSK?/bv	ɲ[6O?eDA11`%>m\{M=B{@9HbfkPJ}R͏l:f,<0n$tUH)T:aWܯEmuN`H}Rsx0m<rAi*9-aAPBC
3FPF0p*

,;V@HJbetu,S]f	ߩL_J@ܯ:(5>X[F<D̐6B#ӕIgթQo*zW&\r|UPtQ,c@eLo(
p)t{Le_
*aVo%n<6^jES6̈́?bj
2/<wx<%=&5IĉuÄG<Ui|-M?s
pX*YTDX8J.E_`dP^{qf;"4:}/깯(B"#5e†1pc0C]~يW,Vf$ίWo|+&u{G
q
oX8>
09nSe\..#ZE,rejՓiw)!឴Z+w.zUٻwg\DÆ,bt?Z4WEku1%P҇h1Y{ՐuFkbq9.):,mфSM^FY.3*)*'Q6:;{3<y(V;(RVȘ,E}uͥYbZm	@Vt:eƼ66SG*?OB>MK5?[f4˻o_Yj=Kr+TJ+&IfcTL  (]zfdATxڌWIW}C
=nm$&A2`dXbo"!$`łذb+$K)
,RDbQxݴ?}U Jzկ=w(UFe{'_:'10S1oѹv̬ļl_T
^7q!%}43kqn?Z[McU`!:VdO8,@\^>[~-f57Z{6p~$ڽc1{#zVRn#ޑ7{.D&_B0!0I9c[nmK4g}bgMlƋbHу绫6#@`Gɑ{{ǶBTD\H[mԳ֏pOZ!%D`L^ͼ~E,85嘲ԹתDijymϐ94ao0aѰN0c$9s bř?5:1U'm<>-EVaBx,[<ԯꭋ+/j1/šf}%,|u״\c0z+U뢙:߰ޅiZt`bI;hNT$uA\s>w>taZƙ
(fq{Lo最ωvecvU>xy~[9)'T^RɘV+LJi]pXs`huZh}m,05;xcO7	l+L3 '^}}"rxi\"D..9U)?߼ڿ-\M׬Z8U#-,e`ckҺEɊ(@gf)/Pnhw$
guA2+`2=RZO3
0lA9GieVsw4@Iڥk) 8owdLΰErp J9[#O:6#uA*KkrTW+9E1FX	.k	ᲀP82«
7Np&jo2\
V{)ojc#C^Mpz[]<e"'iFZ;e)%˄djC0=с|p9y|ok}P|:?;KW#z Rj"ى%8;AL"&Be!O{UJk}o1TDM17Rg'w8/tE-O@SVMy5$	^&!Z6~j^tY6{`3y@jRV
|=C%%!
oN<b7յ퀆QdA2_E=a)m\]hæW/1 = [SqË 'EGUk{Oˠ=uEϋGG,wƯdx,gF\d+i"ɟZnI[\V4髼9s\fl#^*/ùHíK7d	&1ay6%ޠhpLpY2(Gl}Cgï7矸X^H'+㎕Qm*􁖨rk!6l4ذy[]	\[sY3mO  \qhOQv"VWܓ
}.6:/l\
_<QV+)KanRh
"e<
fwf.à)<d&nQJeӄ&8.$W=f%T? Ps[u~VJ_7VtL
6&[SנY8rN^ʘT3m{.[,KG'[oί=rєZk'@%rb`4{o<Š]gmռLc&Hl2Y޺4\v
_cՎWVէ0fh 
OLAoBxGkkFwPYe7{fcTL  (tfdATxtW]$W>ުٙM?q,!+HBAy#$ >b|QX|'!"u!͏f{g{߽3սo{wsޠf'|mtO%ą^U
^dccc0G{Xi\Nz.VⳆÚ![L:ZG>8uQ-oQGlp0l\6Q;I6$#N=k~}P~I|w1}Ykmvvո;16/Msj|v,
X!F`mPhԜ}NM3Z굮$ou>GgnR|<5pV­!:xG{vUGZC8Tiuk
ϝk=B|:ABT1LfB~>mݤaIj~tY/> ^puGP!a!>?}>DyֆU--6G nH~1M'{Cx}e"	|Yѱ^U,~BL/sJw0lɵ]?nߝbX`E|?o0N S##ts8AAw:޻X+ѩReA2qh/5o6fx[އ6,"k8Lss+Ul6޽΋cMVMwŒVœMvBhn:$l,x\$#NIdKNޓz'&.=,bdpb?3,2G1?#p;@G"6Unš^9׎<*Ĵ_yY(RG<g)
5¥{'4J"QS™, &?;$1殮ӑeXg'.|I@ a0e'3O
C;8*պpbYYN+B
Cb/V(2 t
Dn4AJ>LEbS{pd!PS}Eq
J۴@wr~h+TrfX!\ȝ3;g-lSwx0%~K؝B[zJw%G{|Fqd	/
̳c(꿃%Yk+nKI?BrN'kq+3gZ>B`~'R)B 3Rjt@i
TvҦ7)c28fZ\26Nݽ$Gl	)Q(u_κd$GM0>cu(5$S@iQmX%*nu߬5rסޝz벻9+/RP6I8ZՠݖO?Cz8¦	\`)4zbkڝ<.ޱjQx%XaVذ"*Vn
nxͻLC}θH#M
_en%N3(ŠatUj/u$=F,>Ty
%$^D[z<>B(Ek
8Xoçʑ@شY2_ThwDܯ px+PV&\B9)HTVTu%oM\nثgF_l9]ߐKz Y;7"# hT|^T6zq?7zH*hk}=A-uAB=hooNd^r
G"WW.Tv5s)+:>߄N~4zHзRN跫A̧@G]>Zʬڼ4>)2mfsS6o#Zj鵚~	haTg!oyc<Xq˪m0/.8z_fcTL  (9mfdATx|WoW?ܙYïԎc'i&
GDJPR*U A%|BP$*!>THHHj	HHU%/iDHhhZ&mi'{ޙ7#]=wȷ0<x02>ִl4|F3iC({}O/ЃLM_8NxMm	[#TV*d BcfTk03Sn^e]'www<aDMLY1[-qDd09r! h	J]Ca>PIF7ؽ7fQ-m@!rXmCak<@	t)xcʿ9X4,PY"-5[,;
sGl;Aܶ?ʋ\Cu1Hc
lNҕܞmd%9
kO{ncV
827<:SV'VqY?"ҋ3?LL^(V;e+IO@3;2,5v$ֽB	:?'0Z[i5Rrk\m6qd3TAax@G>mSu{;:@P)*.^C-W6}}HD𩧾/?dr)'Vxmϗϼ
6!g{ ȥ^f=Cg $/wv?1hۧ15]7ȣ,9Y̗rzgW_d0M+O谙µO֜rPMr}!Đ];}6$c	%%o\rfUoe!O m8-{1Hzj- )NxhG	pѳd)xXd|Ԙ[hzlۿXvE2QM$Cw7/K{R-}^3	6sGt=ڃ}ܸ@n-:l
ڢ@Ai{=ek@_:/>qbU Y\}[^l6r'ܱmc_k#LY?RaLa6X@rПlz}֢4bn	iRnCGH5V*Er!%h}%R	<6AXf[ly_T3T0Ãx
;Ŧ{}%6*-,WdH"yFڽcztfM6pP;y%9sn̒&t>
:'vc9VK
tpVȵ1pWYPZ*ǒxt\
RCuh@9$v
o
'D
'$=tĕZ (EIU);=|RM
Ӕ~3yߡׯM*DMcS3G{<ԉ>(ЏTٺ!6t4BًY>aI`eI~6>]]6wK}
9ƨBG'G6M8*:5%JTLpWD8x'AhU߫j::v%i
Fw+CA(_OU`Ko;gz]grA1)Xפb^/Mk~ )"&U)بJժ(">hXtWd 3`swr7cpTH]5b"h
M*42j9
iN]R7(pؤ˥^i|)19q
BЕܵq
KJSX+V"wO`;LJOa\҅7^'^ڰzeRvYZ.bc‹!RB]7N@LeĎn$@#][_z>riPC-oJLƵIq.w抨8g-o|jq]ݵ3"t/ݸxea/ng7]E5hLo!cW#RX&
1	
M^c$ZvYoyj{˶[3wz,tW((T1M>".Ay]RRjۢEq-qveʴ
Jbtcf&fcTL  (tKfdATx|W[G>{zfwfv^X{`,H,;`#E"3(?ɖ$#@20!B.&+cc;7zsKUNUgf
.Lwu}NoVYMqKX{G$\[owhZ]0{\og6
j1*IdAF#A'yWHB^!;}53p5:*-e``"xH8YdA`c@cn~%-^\cO/xՌNq=}0,TFB8d5/l?;f򡠽>SKqp[X3O&6<X`bPYrVD`jܛt{{:T}&8*b0}X?7PqgO#rJ¸q6"^UGPs^	>aiTھktOAoHj=`x[(`2dŜB0CO=%1BBd4ߵbb+`Z9x^@2]P{&q]z,И7s&:a	W{>+c3arJ刋^=s}5L
(ч
Ll0FVn&Q<dO}PA/p>/JW]o*`Myh	
+=}uw^s7Jseq3]^3di (F
2ј٨4T&=O8rNl%]?[[yeHE=tڅ_κ[I7vt.߮KRiK'VEe|&"{KQ"[xZKt%/7Wt
]oحḺ1cWny/ʁ&5t_zˀɊq{q^~n\t,p8B+
}B_í_y!ɡB 	D2Q9+l"C &׼7~ʖ'A}Cy,54GK|SuBT#ߨZzQqf|@i51	)}	xp4C8qmGlL]5%9$v'1*}@'neXcxȖ=vb` $_%|um}}yHiH3=Ճ<*so=c!{r2PEF\q_N|LIBRq♢jKaD*b[0~Ns%Ah&sIl_愈*Te)j
6C;\="HOk
࢕JY+{^W'@>*XS9ĵ;*dҐJ:(`ʓ~otru,$ò>BYmX4s{?CCGǰctޗÁeo_[Y26I&˶耸
#jfsll?_l`zm :*"xoKdXk.,ㆄ[nެϫPdRQ
lLe\gE*<7h.,R]ܸq?=qL3DGU>%a5-zƚ4yk7;LC6qEt8J5AyS"؈S%iJ:Ϛx62	[Uj*{M6M6j>=By
>qZ&zeBr*lKzWjlZ@3ؠٲjd,Qyf%yL;?]Jk%żQdr	:1N>9%){}nu
X 3h=y׳i;߇eU%A}*?3!p$M^s;u	büY`C|;ۤm,ǃy2L?)tguꠙzV9*'50G28	aPp#:q*uhaS)]%~j;9hQˮu~о_z>3Sasc"w1#6T6}V{.W؈w۷g<yѸ7>h4fcTL  (W{fdAT xڔWYWުgw1='!( R^Xb$Bby@B Hby
V$EQC2Edglϸ=յ{Q$jtu|տ1:X{ ќoe~Vkf:gs̲iO¬{dK|uqMyɃ&ص*n/H3KOW	_BGB(	#$MIY$EL{ڣk/}*1Ek@5Nw
?'ubJJPBJ$!-IgQPRķ
^㱖?JxU[pFaٳ5>S
II$=)nVB|+'lL994Q rStg~ˍ!!ALkKSuzc<H{OT(_hEn-֩6/#҉.^{Ϸr?„ՏzYvbx>TN	72_|"gXvL%i=JI[:IL6wý)]e>S_]ldϴ264?"
oys`Ny)јn6sD՗BᢗG2_uxv
?Exx$ߑ^rBςl_Rq@]Ai-wiQ?YTM<(6Q^mt?pzԃ"ceY6J D=ȃm!X9DC"OW$o3xO7J~^G&%yؙ?箉eZkHSeVI/U/o ]aCe!nj˒`gn_X*}t){~C>Az/I[,SA c6A0`%`򩃯X7jš25 P-=0~ַk҉sqT=49IͿ^Og$*Fa|ST8k.ka]Q([W<Fhߙp2ӾPF08g`cõLN1<d1#l<v-MͿ9l!j(Ñᚇw|[އ9
1NHL7{3	D4LЧvZZSk7nCAV ɢ;5"11ypA3b+1LM}a'3b2.#R{X
dp`WiV`Fh5Y\pOD'=;8ZW#n1g6ֈTp~uS;4=Ϣaۘ@xUBaZŖ~\$Y
3g掝T?}7qϡR^q!.0h.t`JԽmFMlS>LDauAQgqȇ8,nΝm
^: MC)l>=ux<=#Xy̹PyFHNr{~w%t,d/eqIqʁ:C~h%Rt+~Y	~]2LygXgvZ1)ty^j-ᕏI.d3!eZi5}/zG)g\4O{QV=8[ vSWdȒM	+S9x=TTae?_MIRUPzKF?JIYЎG9MuA#`DnVm4V@*WNò$;#2~IU\yJPUj{FҀr̎Gd⼽܅Pfwk5|VzL(T>V	+`T	מLjG#ƿ['PP9wax8:F-D4V$Z,ʖ
x4W	\ C&~fPkzT0c<n\	/jؖvEcg9%YleI/ յ+o5v;]f(!Ke!ú[z)̋"
#tv@ʶbM,Yuw{>O0j8ҕ	TR8ֺ&<\7#\#_:ZwaB\w$fcTL!  (w#fdAT"x|WmW~GNbMB$	
mRT""Zh"(O?!Bҟ1JPABZIT&)3~{}3d;9<9gķοE1=P|VvFk~h&L
N*'vY:KK̘yA_}jCJo)r2kʍ9qh#NIX<SIk%	|X{Ap.SԘp?&H2ű56[RT*?፳R{kkEwn!30)<k-*؎q]@$ }bN7!Uz@\5>6}XLޜ;ț4Ua@|c.=cxϦFzdq29JwS5y60<hP"y_LʷQi t)B!#|2)R@{/ۓYqk#>1[&,yi
,יg"-Tr0b&ea=2Û	p1ŕ6[/^^rE2ߠԁ8dy3-`w6W7F"84OLd93FLIi;?gK\bShϻ;6esZq݋*
.WKBd]LTI5)S0jÂSdРlݛxouU$6玧 0<P緗_6a!0$:!bloZD
^Pg
ʤ{?_z$kJ}Pe1E&M1t_-}[0gwnX30+XQyMs8`9ֈQ0nA_9-9I:RUNE[u	a`OY.6E,f\ϊ{d婰D5³Ms|8ɽ],@

֖Kc
1v?6+{?`m`vIΤ7GK'c3j*eUd`nA<{ϏߘʷUū?_qɅԥd4a+2DC4¹"#QOP~?˵Ϳt׶|vpoJC%R튝$L
'*'^IQT7Q{z|ɮ=O8sC}|͖pk5+}ieh{kZɨ!
/T(g@vmO8mJU\[8AT{SVCS_ii}DNxٚ]Տ>eGƁi<+qQd5&5[l!
IY"%<?4zQh^O"渕aa8 LWGSQgBĊHUh!L_,$zW%=P-Dd^R,xiTbdhYS:V3T fd%~
ǐfaQa\puLc֙PH#M۝i7<QV'tN`jlAAC)ǞfIzÅ@
r6i*5ۺ2^	QW
׃$NVŷ.QaȤt0:R6MV%]_6?hӟ:vj@FcbL8.6v+`y⪲
&J{	[n>t9ωj'Vu˜1tѪD[N|V,rBZpj
b:M#_}9I-ϑifcE_7H?=Hز^qcUbDIjGʃL9k;@'NזVY|:쳍[l/cHqp>'<ԥ<ߦ+6I	_YFyxRzjc7Ybq
5R&6=,3A-ŷ{8*ݵU3fbbY_j#n=Lid*Tm=&MÇ7v7y
aQ;אq~hi#(^\?Qy%1åTΘ.׽}#VYK8wo0wDe*ЭtEXtSoftwareJapng r119'aIENDB`PK
!<1k&~Bchrome/toolkit/skin/classic/global/icons/menubutton-dropmarker.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="context-fill" d="m 2,6 6,6 6,-6 -1.5,-1.5 -4.5,4.5 -4.5,-4.5 z" />
</svg>

PK
!<y8chrome/toolkit/skin/classic/global/icons/question-16.pngPNG


IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTEfffFGHIJKKLLMMNOOOPPP Q!R"Q"R"S#Q#T$U$W%S&W&Y'X(W)W)Z*\*`*a+a+a+b+b-c-d.Z.e0e0f2^3h7j?sBxDtEuFwFyFyFzOzQ}SVVXXXY\bcemr|⒭ڛ䜼柽䠦汵Ǿ{`tRNS-69Qc:IDATWc`FfV66VfF(`bdgY8=<"R8X~I%b@56u
Myv̜6ֵ>u\6NN.	5*\l®NF1ql@7'SXyYis+WNzB(P3gj.PFv %-|30n@ZTUXYFp0AWXq:P
&?IENDB`PK
!<=	Y==8chrome/toolkit/skin/classic/global/icons/question-64.pngPNG


IHDR@@iqtEXtSoftwareAdobe ImageReadyqe<IDATx[xU_yvȋ$!1(ˎYevdQF| 8>[U <$H	IHIGNuwu'"i]ussέfxǏ0a裏bĈkflذmmmXb֮]KbժU…/ 33h4;MIGi|yy9сz\eeee˰i&a-֭3<#\to}eYEYn{յV0Ș@F1È)pXjr~h s-BXaÆ@VCZ.}Zb`ܹ/^O2ꯅ2TWY[>쳧!8t,i4\Ņl*U 'GtXѣG1a„KKKսH"r>w222nqu{*hI@Dl
X&<?Gdtp aowC~$a0y9Bi^Aۇ0>@|"<x"wȟ9hՉh4x`?o;1X<L:u:	Gs0U,0iҤϲJgGG,a*1ΈǓf
kپ}}$2%L6mڗiiiq#^
]hhcӉ:Tf$XLL80::0	sVx
s]IIɗqT㉷a]G](3yw~ oa
|rrr?nݟ@ɵu늊$$$}>6"E">LԄ|067F]gpq&w۷o_lsQ$\4v=qU`wCOx0,-cUN+7`˞ǪЊ j5I&fΜf{{{믿vpbH5k־=kQ:jh1&-1GE<Z%Z!%H&Ƚ7Sܳ~!T@%SLy@ѩl9b.rĠ[RQ):7VbIi$%Egy8-T\>dpi3Fp
dA_9jԨ_ڜnUUL^xSS5x"hUOPFB1jl^6#J6k{,4;\)@!""U=b(:x$jvp(-$uN(~փ|TTna)S
)Y~RRR27!)(XduA
}%[{p	>1qt&FyQ YaYfԨ옹s+j+#G>RFYׇRb	Tѹvjuh%cSKa(({j
oRfhr^I֯3&桰	&n:ʇB"`͚5%%%՜GRx9%#VݣxVtN*rpl4_='/tJRq;CܝqO߅Oc<awy/¡ T$?Nk/Ou)9)BNA9߅8i#Қ],V}pF~J B
h
g$+,{)`ɒ%&+zrW	>c4Ѿa	sBx]K缫-T4]bG&ϙsOI(Y>dHzEp2;M.4uяvw9p;}e: 3!iO2pRuY4wmT2h
 G_J95͔k"kS~҂U&񻌯e?N-:e$Z0=6M5Zmu%y?TY%7dyu?ͻ94ړ$CQ*22r=kE
¤%xOhȎ3׈̛e
t'v6
-}8,LMpA:NlীŋC6-pi/`<3!agO3JR_e"3^\=Q5<Vo[j_زeK(N>p,;4 &,
(vd]6]^vOܕ'IC:?Uׁ'z29JUQ*tZM|aJ@`{$Gهdu~6Y-5B~P&qVY^, Q7h>#b
RsDܘS1iicǠ
ObvrGc"1`iYpىZ68	!Ubb<?d`BGp@%EP$r$LD?ˆz<L8z0{HVz*R-Hi>K>NWawK8n*592M1(-`t4'uYi#n9΅CSQ*":nH. pX[:
Ko>tb"DҌ_he!z$ŎT0,^f^J/¥dC.<+wVkR<&b2y\jmVl<h"*R>ny۔2Xϐ{X	\*A%縁+BOmi.B&j\AU:~SzrAb9|O& H,&^ObsX\%/<dC	
Mӂsa?֧׎#F4c,/;Jl}%̓	755)!ܹsʹL'\ 0	`,aD~JbX\ (zEo@`=fLU&%~׮]&ɜ9r"/YN.&>Hx%0_rB	WrH ?Rk^d{z<v
=-%g9F';#&2dCKt7	$İ–9RZ]P0ð"/,qL
Oz2WKKaLS,3ƞJfާ\+p<̝ zl}/TYO~:t.ƌL
#!—^R
!]A~(&dބҒldAUՉ
r IyyyE+))mN/3"?"囚7o{[F"1
?
J@)j1~xaW7tUUR
[jjj672pC+E$#{‹GUd"9&#fd%''B>%Psv,E,/ԵOSڵo(CNS#|(Cq
#U!
l SD#
@r}߾ePmPѲfffjEx
b|FQK|/JA&Aiz}eYÏ1MY!esf_F2	U6:i]ΐmʕk
2pg1!/Z%{@/(^09ہ%c}#]k=
f 3	}Wg[ɟ=9Nݻw5xk&EG>SRDiQ<G/=A_iv1
/bЏ.F{{;v`o6nwkI^`1Ը,IA S_H ;+\pz.Hq>F%kʈsGf5cOܹC˃=74h,0;}tCI~[8ܳG_ǔ۟"R1EFNkx'Ƣ#w|< <M6-oll6%XJo.*AA~$mh	L)I2C<(3OD3̞Zgyg.ePSbVB_y'K~3<v{Y[+"ԘGIxo:i>V1y9
`PWWeէvG0fПƒ&Κ5ks=>;;;Hu#xTڻE{9Y#{
Ų-x{<aBU߀5Q[[[7oܹs[d_wTbo~G{׿ycqaΣ2GnGlu%b(ʋ,@XxdF7%=u֯&C{`/@gdd\z7MEmI^&uF![3rr#>s<MX8%w8Սo絤o+vsh2g}Gbsݲ
QDx#KJ[0bp[0wb$i<\;c۶m;7o~o#kHN~*?S6}IIISuo"(
3P;oawWPq0&i.]jVӏ߯yk*N9s̞7o޽SN&tZ6wۋvzrBPicHylOԫ	䑓A8f"!NH;:8~~my{Z(@gi%CM/,,0{[Kɫ >j']'=Ј>LIwl'N?YVNAoE_\+A9om2nܸ$PNJIFOZQ	oDF߅ZHTYYYM|^]$6v\k,)*$V<FC%/
/Fqؿ_8=VX^/S|WK!+I?)G`xuk)IENDB`PK
!<XR8chrome/toolkit/skin/classic/global/icons/resizer-rtl.pngPNG


IHDR;֕JtEXtSoftwareAdobe ImageReadyqe<lIDATxb?0}AɶAXMM
&	;L,PP7on&@
)3ag ۑl?/Aa%ćz[6+IENDB`PK
!<J4chrome/toolkit/skin/classic/global/icons/resizer.pngPNG


IHDR;֕JtEXtSoftwareAdobe ImageReadyqe<gIDATxb?0406g!C7o1I*4bd#3̩y3F#̟xϏPϸ
S舏`[<l/īIENDB`PK
!<B55;chrome/toolkit/skin/classic/global/icons/search-textbox.svg<?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="12" height="12" viewBox="0 0 12 12">
  <path fill="#939393" fill-rule="evenodd" d="M11.354,10.646l-0.707.707L7.295,8A4.483,4.483,0,1,1,9,4.5,4.458,4.458,0,0,1,8,7.295ZM4.5,1A3.5,3.5,0,1,0,8,4.5,3.5,3.5,0,0,0,4.5,1Z" transform="scale(-1, 1) translate(-12, 0)"/>
</svg>PK
!<K:@mm?chrome/toolkit/skin/classic/global/icons/spinner-arrow-down.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="10" height="6" viewBox="0 0 10 6">
  <path fill="context-fill" d="M0 1l1-1 4 4 4-4 1 1-5 5"/>
</svg>
PK
!<+{$mm=chrome/toolkit/skin/classic/global/icons/spinner-arrow-up.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="10" height="6" viewBox="0 0 10 6">
  <path fill="context-fill" d="M0 5l1 1 4-4 4 4 1-1-5-5"/>
</svg>
PK
!<887chrome/toolkit/skin/classic/global/icons/sslWarning.pngPNG


IHDR@@iqtEXtSoftwareAdobe ImageReadyqe<IDATxݛy\u?μyF	`HۆI8TA?"5T]JJR[mҔPC6 @l	QKb	YqgwΛy&*4;wY~g<UHj~ދ۱g@U¯ܺus_>9	r/?tнz?߿e "ŋ.謽{V,ϩVJ(y|~<gܹ_[0}~X,Genh&	%-?P<MyYǓ!SrͬZ{]wM6}>	TK31
G`^0dSNBg乤=fll׼WfO	:{SI6~;B%~	({=w}'$uќY@=`OZ $.iZyzNz˘*/KtBw2d7@:k6O%Aո[_!"K)k?`\Y) 26Sݗ%U?4HL~)?鸟5"v:gm0Vj*zv^
- `
DQHߑ#G<{JZP'*9Qem2=W]0b@$uS9FDz]obŤwn_"-b!`z5Dn
P9lBxA(<\\pn>F׶h޿gEv;"rA/)qr0K'i
)%\=9x*gCj?=a|rE8^{}:ABX?H	@^$D`߽G*1S7Xtab WhEN;&ǕzrۼћW>o;./p!Jn\]W{{3?
{ G(b	ΘeywH&B.g~pykp0D1KeVccA
+jJ<`,Y$"%6nyb_Z
lʘ	|&P1xEahUI#lɒ.'l$ &Y=+s˾nUWs̞
fKXkxs88@q]i%ҥf}4`HV׽/<F
lȓ8:Eڌs:D$?%}֍vݿMᝏ5B`)
kVVyҙ(Tgaɰ,O.FUJi?mNfQɎĮFrΫrO5W"G2Qg畂u5ք{q&˲~Er?ݹؖt*><T<uO"=8(*9vΩf2-lWHI[X>"AK5ۆ[*io+QjuMj5jZز95A\U\JԨC
?/sT{q*l<R*H{R +hDZo<$!Fv=Hr
l
_g֛Xha.@D
RQgip*5V	_@>JK1/X#m{B5QD
@Su=+K|2_w;.~GoEK.Ah 5S:<Ǯd5ӆȱwDx62VoH*LI*/Hr7x*3IH++@\JTEv}ccƘGXYkCoX*u'5غE?=̒$؜yi{v7k3'[*J1\мA\]bǞ`R"=DO{=uq$(Pq"{k*Vbʃj
k-k'ytƮЌʫ$wb>kW֮2l!=UaAÐzS`!M')y#|3I?Ma"_hԸhߦL{r9D{np͇jd-@]8r<kUxqYVL4ع9>"ǎX'𥧚{W Rb9MvRBk
X{/	Y3eO({xy1qty=ki>5.!	hO?[A5+߮rPO^ikcL~~@c8
xZQG讈398ܸ|Nx/P<w(<P)Út:jBs4V8Or6+9do^uܱ\y7m;WA&jW(-.r]d'زPr>oώ^;QM&:4	M`jR:QC$N[=O)pG]Wk
x8v8̺e9JDT҈0tpy0ZGxIrfY;ŕ6k,dG1NѴnK<<F	,."OokCg8A	EJ^g3\q~<ɷ	Nz5l1=a}g<7A$=Վ9 	]YI1dw&kU,ģ?ůDڸ!+gmKb9ʍFuSǹ58Ր_QŁX5Q-ԎNU|azbjpp+bũb&ql.@DM0	NpÕlIrZA-w٧
9qG6zK\_ƮCyhFfae|'L6N]s!+yíc1hDxfqoƊB,t-Y+P߱|g %iKڪ+Ya5 	JXY$,϶hQ}eՎSxdwNL VybD$ND[pehYIYF7pVX1fFؽWy	</h\;+X*8]FCkaulL(
ͬl</gkse!+-94N@l#Gʽfk:e,t֎>;wdz=V1?ۯo#;f8Laj΃'&
Xkӣ3|U
XFr&/.✣mu6!^=sOjXC`LL|ݫxM6Hwt}z@j֩ʹRXt0^§soQz>9uΖb`3qLN"vk.
9C퀷O]}jw%h3hp@GL\YI&Ea
ª/.>NQ'4"vr`S
W}h/#1y~¾r$ؽUph^f@OrђKY}:VE5SJ\CXB>x"WK
=x	 ׉E(iT`Cz[lqAwY޷o^hX`viK^9wT"oڪ,et
<Чee|Ѱr".ʝR^{K
Zm=rc1ɮq.nOX:,g37'f/	/ZHlOP#n\$ZvʰbL{MGe%vZ{S3!M@Ox| \GkRZk5iB+)\f8 N0+0 ?RȌ_u ~CacDx@Y'HBJUɏ(J״=®Nd` ycࠩf*Mf&%TߑF’$eQ.
Hrvw+4t5 &;l5eڴvۚ}TIB'a\n72:SF&j;tz"Dw&U^81}sweѳ¿Bܻ.Äs-.(!`^ >YdVOZƖmM{l
d$O,	Frз#_R' VR	GlL8)Q;'}Կs.0\dl^//q-LQx/ppvI>xʳ8p`p<	gܽ{+7@c\0Y$׽صkם$9"]v7~XP馛_|[PC"2cǎׁ潹怅Ȍ<GPm
}aU`yo	v/}'IENDB`PK
!<-	1Achrome/toolkit/skin/classic/global/icons/tabprompts-bgtexture.pngPNG


IHDRrP6tEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.0-c060 61.134777, 2010/02/12-17:32:00        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CS5 Macintosh" xmpMM:InstanceID="xmp.iid:7ACC34E2C35911DF8519F58CBF5D9BBF" xmpMM:DocumentID="xmp.did:7ACC34E3C35911DF8519F58CBF5D9BBF"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:7ACC34E0C35911DF8519F58CBF5D9BBF" stRef:documentID="xmp.did:7ACC34E1C35911DF8519F58CBF5D9BBF"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>KIDATxlTYOSA=ЅeiPDQ5}17/<jH4J	K!YV]t9ߥ$Lf;s|Xf#rݪæk(Vr,2o`kk	47jhsds9F?qxx(XåarbRI*#Q2[FPC8QVs9EQ*UeGAMgrp80:Of(ϩ,"ҢQzl])tι3b.9B[+66~NuP,[)Q	w00+D;;;^R)s/HFӀ*K,ՄŌ;rVg"4'Qo:_FKKRhG"mJe鴘Fӓ)ź21ƪX`^@8zm;'fw
B4j*s&|>z;\*zzzyK7]\\zirYt//xPME4PPShhl]g*ë½@lz	z%$*9j{ĸ(42gzD$
PTƧ:e#@ nrg#h)rU`PJģLH~F/G4xF9.9,6ѱd6_e}HԘ[xtV1XZ-%=
qT|dx좈DN\L,cRTZٌs,/X.`vsdf,fԮ6_T?#êMJf33biK>jH1xTIENDB`PK
!<~_7chrome/toolkit/skin/classic/global/icons/warning-16.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڌ_HSQn^dn+)jS)t4qDCTbSӉTJL'={|qdA=QI.A?D:PHWt؇۹|ϏsBdØq*GvKfFGG,"~shhETGGG#ݩ#&)pȈO<ZC	?Nd/xFtjCrꫫB!xOooOݽvbT^` `ppah4.AE$i-C^kh+1	X+<+efz|.g-<d2[m2|[|謅:B?=m~N
/@dQȾAOw8􇃶6_Q]]5^3?ȁd֓pR`chmm8QDEvxtg)ʬH+75ha-9-zE*}6_O?%uMKx
PN&`sFŢTd,5[*xjoSþ|7mv?`^/r6AP
jИSM+sPuB
oQ
ڗ,t4ϣboq&ZtkkL(:mHQXeX155sKS&:IENDB`PK
!<4.7chrome/toolkit/skin/classic/global/icons/warning-64.pngPNG


IHDR@@iqtEXtSoftwareAdobe ImageReadyqe<IDATxkpTetwҝt@HLV ʈ#Q$$3U:)RJ?.~ؚҙ]㰫(:h^E̕[H I'쇾'"S9}ia᧼Yķ@ r0sYolGv]!`V[mm^{Ǯ{]wuw1 Mຮ}ѕ+.V\$C#<N:hi?XfM忸}wR{2j;>3ׯo_Q0qIJoo_+ǘ^ehq~B9ʕ_xW(hnntyD3Up6JJʝo>۶KT̘1cVc㺶~TI&Mx

/yhђy+VhQpw=PYun?05 ݤz}[OKKƒȏ%K,}OY`+ϽaummmC87r"IH!(ܲj溺e{q%/Q*dai졫Ejj{ePQQQvwܯű2%EʬhE!J	ƟR{GXuӜ/|gosY#%]EqwZZZΨ,6B3gκm}}}ĆrH%r
_C,]\h+V4/`w}TWGB{laJnZ88&[JDoE?MMp$3:ȷ-_|֭:;?n̏ˤ Da@'-plAq("F	PSttܹx
;; p]]wm/̚كAR.QkoqLL<iq&9Hs&:mcpnyM+y[GGǻ~ŋNJ8TVdx	`&;DmOF$QKw#ӋB5lƍkms!&G<;St"%c1D8>'ߗȈL-R"=ŵ^W\Mրowy;8᯸N!F>Cw0ZM)
U|YF%]Wv%m7€YlfTN%y{cc.XFJR*aaL€PT<:]⊺y˗/k0m|&L(޽Ȥ]O#0,[`À`P	L-&YL2Կ
![״1GX|#G cCD#=d~Z&O7% e&OIgۜ57pÆ{@ `a"PqE!0&4m;NJ7,v[b߿PEsiJlH(,иFkF:Q5h91VOwTG755}iI&K%8+̃T%,K"J8〉
@KQBvAGkYYK/]
׷΀_n3G79%@۴ǑF)f` ITL%Fe5)L>kG;_KOhwcSY.Ht%

>0Ftjq,Df̌*cAG<]ݻXy˖-mV{}STOse/UP>ģ
$Q2SM7dKhv-Iy6gcXvu-_K.zJ	#%uVƋ=	#r	Cd@#Q|Ύ1kVZຮWj>{=(P_!1$&,LH_1	ٌ2Y<<E1̉ljFGXzukee3
ڵk.PZ"BN:$$mNNZ1(R"¶
A1L&O
ts,2==,

ADی*ӣ%2c%`pr&4 z᪫;wgu6^*ǽkA
=dIMkEAMSx$`9&͘ـQ>Ѯm㝦$ifr˚cLj=AE7ȡ^ЉDk'A0:ulq}A


tb苂ףSA5Z.|…-]zxJNmC'#>x@XYư-8A.1()/1TГ+Lccp8|mbѲ]CWg8+dEIFQEVo‹<`DPJ%Vc)>rF5GIw[̞lZ)8́}s'0ߏ҅.gԨQy(
8[q8o#N.EyAC1n:%YB44Լ`IyU|2XVΘ5KHX7
j"Õxܺ| (@)/2uMō
!n5Ŀ2֮]w	*|XLqKr5مL'Ͽ.#:<{36tzx_*'ছVo6mvS~3%5>ը UdGOKX,Fq]OXüŒ5CzTTȭ
2Rg).)wP0eʹVjh;ޏ5|'b"oa>ɍ8> 9s
8m86JX9Rj49V:#5/_4oɒ%M_	[(PnHx'*'4*N)ٯ0
v\lN7Ƕq|.׌2Bbl*<T6U}D{`n֓`e-ZpJFmP*u͡-AK8Xs(XAlŲllrL*L%>Ku&19/,JM;bj7|)Yv?n%n'sHIR)4XV/>e8 KzJ>b| 94ёanb….h `\:b+Lze'epRsFZs@
9ΏO%P̙*NaFO$M#/3eƍūV]&x/[̺m-A`^2G<f#鳏M.U[$#$hk^gfwϾhoPi(u0C̿tRu#y`YWZzVk/f$7\KЌ_dvc[s<`I؆@`ٖe@k-;;;_x<
>n92/M7ﵜ1CޛfܩuF=^H;PvXd]q&g)ySNY[\[{UKH,FXKe%B`Yaa+ymr:/`HxdBa֙ NzOڀv!Btvg^JٕCv!DpҤʕ+WַΞ=gF;eaYm%²[v\e[V|>F'Hi9cJI"mrc5:
FJ=~Ͽ>|b9mGl.;ǖyt#:oN>Z)5,<tDz( 2HcI?t'ķ;>JIENDB`PK
!<+
+
:chrome/toolkit/skin/classic/global/icons/warning-large.pngPNG


IHDR00WtEXtSoftwareAdobe ImageReadyqe<	IDATxZklfvvomxF(1	

H)%MQTj8U[*4P
ccfzX-u4޽3s8I4_<qcJYY***X]*Þq˓䁼g6:Ǐ1BI)))V(,,zJrrrN!-tq3jkN@[yb:thٳX<߾y+Ab7"44˗/<9s[,i	`dfڎI:k֬]v
yh#(tJv;b,zH?B?;46I1RI@hVV֫fucnfUfD	@۷50%I7ZHgQQQQIO}4Xi^p~%SݻwϹ|re*<O/yWCA2>	`Nc`6mT
.Gk׈	wSdXXjÆ
V/˴23̪򺰠$ s8Gȴ:m4,]
rǐrBbF%~d+rbf'H9ATUUm9z	"NFIΝ;Zg rfI~ZVENJt"5`
'E$$.ŋizBaT߼j4q_ oH,dh!+rh|*6RsLT>Yc)rϜ9ÃlY,l$eJy"$إT'n7뤂̝;7a:i|2`0PDJxkfK> *4(mOh5dhu8ӭaeMKV^jYwRU"UV'X,zZ)2ӊ+jBLAC!!ܿwldɒ7&ZNw(  _}'$֫(.Q0BEn8:["%wޕXmm~4ߺuC'+]VZœ..XӟLyXH	5RRno'Z]hQ7juHX??D^UrY
G;B;r 2+0b(+{QzժqBuwbcc0sI8~!4,~1:O<șэkM )ezҥKSȗE
)m#;$`<ﱼ.td!{hu͚5>
]6o_Gd
mWCR"vSr w\p'|
#vϜ'd֢W^jxk%Y_͖dH
zv[:Nl=g Q	geISϐ>{}HnAL#$$

B\K[%79<ǡY>Cjjj;JZo޼뇬ㆇsrr,c0N6i%qd>(Juw'guRz5RZuEnr=#.d y8b%+d^-"Y{1wgϞc/eXWNTT*]h \^t㍷MZb?<yૼj}oYUTōy))X7]:Ibvd߷lŇg|Ӕb$f|)*@cڵkF8*˭iVOap:8v"L,0J XydXyydL'ZPj}޽+)qìZA5ʋa#Wl引FFr٪XXylA#kt0>+0Llwm	#(x]ɡ:+eZbrƍW轠/oLc6ڪ$*nV.}=BՊtˋ]MvZxq b$mwuO
lĝ֫DBBB1
SVcVݾA
(yIGv
k7mz҈W&2o{Rpɫ#1uxeԻ"(c<眢$e6kkD֍fVS$RЪވs!sg_8>>!/O Ra<z*}=1VSutt…竪Nu2lbqS
7Ҫ27xyDުovtא~M$6Mo6`SoZ$5dl%Q<J<?("	IENDB`PK
!<2~NN4chrome/toolkit/skin/classic/global/icons/warning.svg<?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 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>
PK
!<9Mܹ;chrome/toolkit/skin/classic/global/icons/windowControls.pngPNG


IHDR10tEXtSoftwareAdobe ImageReadyqe<[IDATx
pTv&Y $!"Zq*8BAPZ
G(R# _!U+~Ֆ@@l6d?/$7i7{s߹Fˏ;<{K/SKH>{IOd@y/?Fp|JaE2]D"G$$˿>qT>L|$Bá8hQ}
cМv9p"Q\"X8慗P4 BA(
o9?F8GMKk!f2p/o"C9U#/v/dx
FB9iX3{濺Mܟ[eP($lL1/qp^N4:zdqX	@@OCVJ2s6n>?]Yp|ls;m)#T摊optK/y~*ۢ2οCse
xbL)4fc1/q.;=x_%_FKi|[>-ּcm̗y^S|=ļ?㶵aʵp0Ҏmsś.ukp$ҜEo<8/y=5(3렑8C8wQ'e
sۇiI\(&Zt:p3_Z~̗<_sϻsp?fBgsZk6B2UQ
eT7ﳽuNmIdQ8͡bwxa<$O7&AGUF];g(HD&AGx3f[[IN:$rG]Z"oLf`Hsat{n/+Z_tؒRMf@f5-ٗ^u8wx4wց?758<rbb~Όj{䫾Pm}zJ!'S=1'U_v~R0l%=٩/R''c/d(|P	
H7T&Q*Z$s#B%Դ=ОΜ,EhJ>5X8澧W2M >T79ݿS'@f=r/ǼAa>\Ot4*U'^7[2Rݎ!F	k'_y}~LOp?j1/) [Ntټ87(moB!=AobF'&Ü
ե']<*ُcI@.0=uoO90kzRm\.|nʬ[:Gse!߭hJO&;RhӴ	Jߏ'҉wsRܰV	sbVi[!R&N5᝗͏c^!F8NTy^bqnzbd*(|V g(A$	-@͏Mn<ϤJ4='
UO)Ad;aě IE&ըu nz|uLcZz '2htLV<`Dkk5qmn2k'E6ʁaILOR
λ
iZ3ARņC9*%{<FX}[gSf̞x~9RT9V=reIƙ\EOhu4epz&RrHh12mLo-ˀ[6Bv5\ﭷ
A$گS20f/6LS_\_[煿Ozv _WI
gʃ+4z;7zիW|'q=Η6	bT{lןP^[,)@a.j8OkKcΰ6xʋm
ͨwv醜L+
g%3	=ݓ2c.l<cf.x];!tCtu8{>	T=&s=	!w?LIAI|c=&x1 ŀ%S;^O2Qcz̩zNM0ד9ؾ7o`ӊU_欤.rzZ,r5P[>TkeL@I?sp4r)řM [M)@\O^K+Y/Smk=Aӗ4J	o{
2Oɒᙷ33& ;(|S{ek4)?fPĿP t&NǮ0i|kkY
U!"GAU5?=AE-Zk ^թ'R}z#=-@;:m7^6*3R.+DAEp*C9GO`d>pFY8n'y$an
jW>z牔^B7=43j=X}:An+Y)dʂ+;ronSsu^':Һᣭ SބvY_c<vhѽ;v2,)Q<<Xuhvݟf
N)18>+|&0*Jhq[Gk3lNF6ٲ{zZhjJ1=awEF#${G0H}'1~xQ!)mpiV }\=A}-@Y6+Y^Y?oAQ"$;IENDB`PK
!<٠?chrome/toolkit/skin/classic/global/in-content/check-partial.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="21" height="21" viewBox="0 0 21 21">
  <rect fill="context-fill" stroke="context-stroke" stroke-width="0.5" x="3" y="9" width="15" height="3"/>
</svg>

PK
!<3ؾ7chrome/toolkit/skin/classic/global/in-content/check.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="21" height="21" viewBox="0 0 21 21">
  <path fill="context-fill" stroke="context-stroke" stroke-width="0.5" d="M 9.39,16.5 16.28,6 14.77,4.5 9.37,12.7 6.28,9.2 4.7,10.7 z"/>
</svg>

PK
!<7,d[d[8chrome/toolkit/skin/classic/global/in-content/common.css/* - This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace html "http://www.w3.org/1999/xhtml";
@namespace xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

*|*:root {
  --in-content-page-color: #424e5a;
  --in-content-page-background: #fbfbfb;
  --in-content-text-color: #333;
  --in-content-selected-text: #fff;
  --in-content-header-border-color: #c8c8c8;
  --in-content-box-background: #fff;
  --in-content-box-background-odd: #f3f6fa;
  --in-content-box-background-hover: #ebebeb;
  --in-content-box-background-active: #dadada;
  --in-content-box-border-color: #c1c1c1;
  --in-content-item-hover: rgba(0,149,221,0.25);
  --in-content-item-selected: #0a84ff;
  --in-content-border-highlight: #ff9500;
  --in-content-border-focus: #0a84ff;
  --in-content-border-color: #c1c1c1;
  --in-content-category-text: #c1c1c1;
  --in-content-category-border-focus: 1px dotted #fff;
  --in-content-category-text-selected: #f2f2f2;
  --in-content-category-background: #424f5a;
  --in-content-category-background-hover: #5e6972;
  --in-content-category-background-active: #343f48;
  --in-content-tab-color: #424f5a;
  --in-content-link-color: #0a84ff;
  --in-content-link-color-hover: #0060df;
  --in-content-link-color-active: #ff9500;
  --in-content-link-color-visited: #551a8b;
  --in-content-primary-button-background: #0a84ff;
  --in-content-primary-button-background-hover: #0060df;
  --in-content-primary-button-background-active: #003EAA;
  --in-content-table-border-dark-color: #d1d1d1;
  --in-content-table-header-background: #0a84ff;
}

html|html,
xul|page,
xul|window {
  font: message-box;
  -moz-appearance: none;
  background-color: var(--in-content-page-background);
  color: var(--in-content-page-color);
}

html|body {
  font-size: 15px;
  font-weight: normal;
  margin: 0;
}

html|h1 {
  font-size: 2.5em;
  font-weight: lighter;
  line-height: 1.2;
  color: var(--in-content-text-color);
  margin: 0;
  margin-bottom: .5em;
}

html|hr {
  border-style: solid none none none;
  border-color: var(--in-content-border-color);
}

xul|caption {
  -moz-appearance: none;
  margin: 0;
}

html|h2,
xul|caption > xul|checkbox,
xul|caption > xul|label {
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 22px;
}

xul|caption > xul|checkbox,
xul|caption > xul|label {
  margin: 0 !important;
}

*|*.main-content {
  padding-top: 40px;
  padding-inline-end: 44px; /* compensate the 4px margin of child elements */
  padding-bottom: 48px;
  padding-inline-start: 48px;
  overflow: auto;
}

xul|prefpane > xul|*.content-box {
  overflow: visible;
}

/* groupboxes */

xul|groupbox {
  -moz-appearance: none;
  border: none;
  margin: 15px 0 0;
  padding-inline-start: 0;
  padding-inline-end: 0;
  font-size: 1.25rem;
}

xul|groupbox xul|label:not(.menu-accel):not(.menu-text):not(.indent):not(.learnMore),
xul|groupbox xul|description {
  /* !important needed to override toolkit !important rule */
  margin-inline-start: 0 !important;
  margin-inline-end: 0 !important;
}

/* tabpanels and tabs */

xul|tabpanels {
  -moz-appearance: none;
  font-size: 1.25rem;
  line-height: 22px;
  border: none;
  padding: 0;
  background-color: transparent;
  color: inherit;
}

xul|tabs {
  margin-bottom: 15px;
  border-top: 1px solid var(--in-content-box-border-color);
  border-bottom: 1px solid var(--in-content-box-border-color);
  background-color: var(--in-content-page-background);
}

xul|*.tabs-left,
xul|*.tabs-right {
  border-bottom: none;
}

xul|tab {
  -moz-appearance: none;
  margin-top: 0;
  padding: 4px 20px;
  min-height: 44px;
  color: var(--in-content-tab-color);
  background-color: var(--in-content-page-background);
  border-width: 0;
  /* !important overrides tabbox.css RTL and visuallyselected rules */
  border-radius: 0 !important;
  transition: background-color 50ms ease 0s;
}

xul|tab:hover {
  background-color: var(--in-content-box-background-hover);
}

xul|tab[selected] {
  background-color: var(--in-content-box-background-hover);
  padding-bottom: 0; /* compensate the 4px border */
  border-bottom: 4px solid var(--in-content-border-highlight);
}

xul|*.tab-text {
  font-size: 1.3rem;
  line-height: 22px;
}

/* html buttons */

html|button {
  padding: 3px;
  /* override forms.css */
  font: inherit;
}

/* xul buttons and menulists */

*|button,
html|select,
xul|colorpicker[type="button"],
xul|menulist {
  -moz-appearance: none;
  min-height: 30px;
  color: var(--in-content-text-color);
  border: 1px solid var(--in-content-box-border-color);
  -moz-border-top-colors: none !important;
  -moz-border-right-colors: none !important;
  -moz-border-bottom-colors: none !important;
  -moz-border-left-colors: none !important;
  border-radius: 2px;
  background-color: var(--in-content-page-background);
}

html|select:not([size]):not([multiple]) {
  background-image: url("chrome://global/skin/in-content/dropdown.svg#dropdown");
  background-position: right 3px center;
  background-repeat: no-repeat;
  background-size: auto 18px;
  font-size: inherit;
  padding-inline-start: 5px;
  padding-inline-end: 24px;
  text-overflow: ellipsis;
}

html|button:enabled:hover,
html|select:not([size]):not([multiple]):enabled:hover,
xul|button:not([disabled="true"]):hover,
xul|colorpicker[type="button"]:not([disabled="true"]):hover,
xul|menulist:not([disabled="true"]):hover {
  background-color: var(--in-content-box-background-hover);
}

html|button:enabled:hover:active,
html|select:not([size]):not([multiple]):enabled:hover:active,
xul|button:not([disabled="true"]):hover:active,
xul|colorpicker[type="button"]:not([disabled="true"]):hover:active,
xul|menulist[open="true"]:not([disabled="true"]) {
  background-color: var(--in-content-box-background-active);
}

html|button:disabled,
html|select:disabled,
xul|button[disabled="true"],
xul|colorpicker[type="button"][disabled="true"],
xul|menulist[disabled="true"] {
  opacity: 0.5;
}

*|button.primary {
  background-color: var(--in-content-primary-button-background);
  border-color: transparent;
  color: var(--in-content-selected-text);
}

html|button.primary:enabled:hover,
xul|button.primary:not([disabled="true"]):hover {
  background-color: var(--in-content-primary-button-background-hover);
}

html|button.primary:enabled:hover:active,
xul|button.primary:not([disabled="true"]):hover:active {
  background-color: var(--in-content-primary-button-background-active);
}

xul|colorpicker[type="button"] {
  padding: 6px;
  width: 50px;
}

xul|button > xul|*.button-box,
xul|menulist > xul|*.menulist-label-box {
  padding-right: 10px !important;
  padding-left: 10px !important;
}

xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon[src] {
  margin-inline-end: 5px;
}

xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
  -moz-appearance: none;
  margin: 1px 0;
  margin-inline-start: 10px;
  padding: 0;
  width: 10px;
  height: 16px;
  border: none;
  background-color: transparent;
  list-style-image: url("chrome://global/skin/in-content/dropdown.svg");
  -moz-context-properties: fill;
  fill: -moz-DialogText;
}

xul|*.help-button {
  min-width: 16px;
  margin-inline-end: 0;
  border-width: 0;
  background-image: none;
  box-shadow: none;
  list-style-image: url("chrome://global/skin/in-content/help-glyph.svg");
  -moz-context-properties: fill, stroke, stroke-opacity;
  fill: #999;
  stroke: #999;
  stroke-opacity: 0;
}

xul|*.help-button:not([disabled="true"]):hover {
  background-image: none;
  /* Override default button background */
  background-color: transparent;
  fill: white;
  stroke: #808080;
  stroke-opacity: 1;
}

xul|*.help-button:not([disabled="true"]):hover:active {
  background-image: none;
  /* Override default button background */
  background-color: transparent;
  stroke: #666;
}

xul|*.close-icon > xul|*.button-box,
xul|*.help-button > xul|*.button-box {
  padding-top: 0;
  padding-bottom: 0;
  padding-right: 0 !important;
  padding-left: 0 !important;
}

xul|*.help-button > xul|*.button-box > xul|*.button-icon {
  width: 16px;
  height: 16px;
}

xul|*.help-button > xul|*.button-box > xul|*.button-text {
  display: none;
}

html|*.help-button {
  width: 16px;
  height: 16px;
  border: 0;
  padding: 0;
  display: inline-block;
  background-image: url("chrome://global/skin/in-content/help-glyph.svg");
  -moz-context-properties: fill, stroke, stroke-opacity;
  fill: #999;
  stroke: #999;
  stroke-opacity: 0;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: contain;
}

html|*.help-button:hover {
  fill: white;
  stroke: #808080;
  stroke-opacity: 1;
}

html|*.help-button:hover:active {
  stroke: #666;
}

xul|*.spinbuttons-button {
  min-height: initial;
  margin-inline-start: 10px !important;
  margin-inline-end: 2px !important;
}

xul|*.spinbuttons-up {
  margin-top: 2px !important;
  border-radius: 1px 1px 0 0;
}

xul|*.spinbuttons-down  {
  margin-bottom: 2px !important;
  border-radius: 0 0 1px 1px;
}

xul|*.spinbuttons-button > xul|*.button-box {
  padding: 1px 5px 2px !important;
}

xul|*.spinbuttons-up > xul|*.button-box > xul|*.button-icon {
  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}

xul|*.spinbuttons-up[disabled="true"] > xul|*.button-box > xul|*.button-icon {
  list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
}

xul|*.spinbuttons-down > xul|*.button-box > xul|*.button-icon {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}

xul|*.spinbuttons-down[disabled="true"] > xul|*.button-box > xul|*.button-icon {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
}

xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
  -moz-appearance: none;
  margin-inline-end: 4px;
  padding: 0;
  border: none;
  background-color: transparent;
  list-style-image: url("chrome://global/skin/in-content/dropdown.svg");
  -moz-context-properties: fill;
  fill: -moz-DialogText;
}

xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker > xul|*.dropmarker-icon {
  width: 18px;
  height: 18px;
}

xul|menulist[disabled="true"]:not([editable="true"]) > xul|*.menulist-dropmarker {
  fill: GrayText;
}

xul|menulist > xul|menupopup,
xul|button[type="menu"] > xul|menupopup {
  -moz-appearance: none;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 2px;
  background-color: var(--in-content-box-background);
}

xul|menulist > xul|menupopup xul|menu,
xul|menulist > xul|menupopup xul|menuitem,
xul|button[type="menu"] > xul|menupopup xul|menu,
xul|button[type="menu"] > xul|menupopup xul|menuitem {
  -moz-appearance: none;
  font-size: 1em;
  color: var(--in-content-text-color);
  padding-top: 0.2em;
  padding-bottom: 0.2em;
  padding-inline-start: 10px;
  padding-inline-end: 30px;
}

xul|menulist > xul|menupopup > xul|menu:not([disabled="true"])[_moz-menuactive="true"],
xul|menulist > xul|menupopup > xul|menuitem:not([disabled="true"])[_moz-menuactive="true"],
xul|button[type="menu"] > xul|menupopup > xul|menu:not([disabled="true"])[_moz-menuactive="true"],
xul|button[type="menu"] > xul|menupopup > xul|menuitem:not([disabled="true"])[_moz-menuactive="true"] {
  color: var(--in-content-text-color);
  background-color: var(--in-content-item-hover);
}

xul|menulist > xul|menupopup > xul|menu:not([disabled="true"])[selected="true"],
xul|menulist > xul|menupopup > xul|menuitem:not([disabled="true"])[selected="true"],
xul|button[type="menu"] > xul|menupopup > xul|menu:not([disabled="true"])[selected="true"],
xul|button[type="menu"] > xul|menupopup > xul|menuitem:not([disabled="true"])[selected="true"] {
  color: var(--in-content-selected-text);
  background-color: var(--in-content-item-selected);
}

xul|menulist > xul|menupopup > xul|menu[disabled="true"],
xul|menulist > xul|menupopup > xul|menuitem[disabled="true"],
xul|button[type="menu"] > xul|menupopup > xul|menu[disabled="true"],
xul|button[type="menu"] > xul|menupopup > xul|menuitem[disabled="true"] {
  color: #999;
  /* override the [_moz-menuactive="true"] background color from
     global/menu.css */
  background-color: transparent;
}

xul|menulist > xul|menupopup xul|menuseparator,
xul|button[type="menu"] > xul|menupopup xul|menuseparator {
  -moz-appearance: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--in-content-box-border-color);
  border-bottom: none;
}

/* textboxes */

html|input[type="email"],
html|input[type="tel"],
html|input[type="text"],
html|textarea,
xul|textbox {
  -moz-appearance: none;
  color: var(--in-content-text-color);
  border: 1px solid var(--in-content-box-border-color);
  -moz-border-top-colors: none !important;
  -moz-border-right-colors: none !important;
  -moz-border-bottom-colors: none !important;
  -moz-border-left-colors: none !important;
  border-radius: 2px;
  background-color: var(--in-content-box-background);
}

xul|textbox {
  min-height: 30px;
  padding-right: 10px;
  padding-left: 10px;
}

/* Create a separate rule to unset these styles on .tree-input instead of
   using :not(.tree-input) so the selector specifity doesn't change. */
xul|textbox.tree-input {
  min-height: unset;
  padding-right: unset;
  padding-left: unset;
}

html|input[type="email"],
html|input[type="tel"],
html|input[type="text"],
html|textarea {
  font-family: inherit;
  font-size: inherit;
  padding: 5px 10px;
}

html|input[type="email"]:focus,
html|input[type="tel"]:focus,
html|input[type="text"]:focus,
html|textarea:focus,
xul|textbox[focused] {
  border-color: var(--in-content-border-focus);
}

html|input[type="email"]:disabled,
html|input[type="tel"]:disabled,
html|input[type="text"]:disabled,
html|textarea:disabled,
xul|textbox[disabled="true"] {
  opacity: 0.5;
}

/* Links */

html|a,
.text-link {
  color: var(--in-content-link-color);
  text-decoration: none;
}

html|a:hover,
.text-link:hover {
  color: var(--in-content-link-color-hover);
  text-decoration: underline;
}

html|a:visited {
  color: var(--in-content-link-color-visited);
}

html|a:hover:active,
.text-link:hover:active {
  color: var(--in-content-link-color-active);
  text-decoration: none;
}

/* Checkboxes and radio buttons */

xul|checkbox {
  margin-inline-start: 0;
  -moz-appearance: none;
}

xul|*.checkbox-check,
html|input[type="checkbox"] {
  -moz-appearance: none;
  width: 23px;
  height: 23px;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 2px;
  margin: 0;
  margin-inline-end: 10px;
  background-color: #f1f1f1;
  background-image: linear-gradient(#fff, rgba(255,255,255,0.8));
  background-position: center center;
  background-repeat: no-repeat;
  box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}

xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check,
html|input[type="checkbox"]:not(:disabled):hover {
  border-color: var(--in-content-border-focus);
}

xul|*.checkbox-check[checked] {
  list-style-image: url("chrome://global/skin/in-content/check.svg");
  -moz-context-properties: fill, stroke;
  fill: #2292d0;
  stroke: none;
}

html|input[type="checkbox"]:checked {
  background-image: url("chrome://global/skin/in-content/check.svg"), linear-gradient(#fff, rgba(255,255,255,0.8));
  -moz-context-properties: fill, stroke;
  fill: #2292d0;
  stroke: none;
}

xul|checkbox[disabled="true"] > xul|*.checkbox-check,
html|input[type="checkbox"]:disabled {
  opacity: 0.5;
}

xul|richlistitem > xul|*.checkbox-check {
  margin: 3px 6px;
}

html|*.toggle-container-with-text {
  display: flex;
  align-items: center;
}

xul|radio {
  margin-inline-start: 0;
  -moz-appearance: none;
}

xul|*.radio-check {
  -moz-appearance: none;
  width: 23px;
  height: 23px;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 50%;
  margin-inline-end: 10px;
  background-color: #f1f1f1;
  background-image: linear-gradient(#fff, rgba(255,255,255,0.80));
  box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}

xul|radio:not([disabled="true"]):hover > xul|*.radio-check {
  border-color: var(--in-content-border-focus);
}

xul|*.radio-check[selected] {
  list-style-image: url("chrome://global/skin/in-content/radio.svg");
  -moz-context-properties: fill;
  fill: #2292d0;
}

xul|radio[disabled="true"] > xul|*.radio-check {
  opacity: 0.5;
}

xul|*.radio-label-box {
  margin-inline-start: -1px; /* negative margin for the transparent border */
  margin-inline-end: 10px;
  padding-inline-start: 0;
}

/* Category List */

*|*#categories {
  -moz-appearance: none;
  background-color: var(--in-content-category-background);
  padding-top: 39px;
  margin: 0;
  border-width: 0;
}

*|*.category {
  -moz-appearance: none;
  color: var(--in-content-category-text);
  border-inline-end-width: 0;
  padding-inline-start: 15px;
  padding-inline-end: 21px;
  min-height: 40px;
  transition: background-color 150ms;
}

*|*.category:hover {
  background-color: var(--in-content-category-background-hover);
}

*|*.category[selected],
*|*.category.selected {
  background-color: var(--in-content-category-background-active);
  color: var(--in-content-category-text-selected);
  padding-inline-start: 11px; /* compensate the 4px border */
  border-inline-start: solid 4px var(--in-content-border-highlight);
}

*|*#categories[keyboard-navigation="true"]:-moz-focusring > *|*.category[current] {
  border-top: var(--in-content-category-border-focus);
  border-bottom: var(--in-content-category-border-focus);
}

*|*.category-name {
  line-height: 22px;
  font-size: 1.25rem;
  padding-bottom: 2px;
  padding-inline-start: 9px;
  margin: 0;
  -moz-user-select: none;
}

*|*.category-icon {
  width: 24px;
  height: 24px;
}

/* header */

*|*.header {
  border-bottom: 1px solid var(--in-content-header-border-color);
  margin-inline-end: 4px; /* add the 4px end-margin of other elements */
  margin-bottom: 15px;
  padding-bottom: 15px;
  -moz-box-align: baseline;
}

*|*.header-name {
  font-size: 2.5rem;
  font-weight: normal;
  line-height: 40px;
  margin: 0;
  -moz-user-select: none;
}

/* File fields */

xul|filefield {
  -moz-appearance: none;
  background-color: transparent;
  border: none;
  padding: 0;
}

xul|*.fileFieldContentBox {
  background-color: transparent;
}

xul|*.fileFieldIcon {
  margin-inline-start: 10px;
  margin-inline-end: 0;
}

xul|*.fileFieldLabel {
  margin-inline-start: -26px;
  padding-inline-start: 36px;
}

xul|textbox:-moz-locale-dir(rtl),
xul|*.fileFieldLabel:-moz-locale-dir(rtl),
xul|textbox + xul|button:-moz-locale-dir(ltr),
xul|filefield + xul|button:-moz-locale-dir(ltr) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

xul|textbox:-moz-locale-dir(ltr),
xul|*.fileFieldLabel:-moz-locale-dir(ltr),
xul|textbox + xul|button:-moz-locale-dir(rtl),
xul|filefield + xul|button:-moz-locale-dir(rtl) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

xul|textbox + xul|button,
xul|filefield + xul|button {
  border-inline-start: none;
}

/* List boxes */

html|select[size][multiple],
xul|richlistbox,
xul|listbox {
  -moz-appearance: none;
  margin-inline-start: 0;
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
  color: var(--in-content-text-color);
}

html|select[size][multiple] > html|option,
xul|treechildren::-moz-tree-row,
xul|listbox xul|listitem {
  padding: 0.3em;
  margin: 0;
  border: none;
  border-radius: 0;
  background-image: none;
}

html|select[size][multiple] > html|option:hover,
xul|treechildren::-moz-tree-row(hover),
xul|listbox xul|listitem:hover {
  background-color: var(--in-content-item-hover);
}

xul|treechildren::-moz-tree-row(selected),
xul|listbox xul|listitem[selected="true"] {
  background-color: var(--in-content-item-selected);
  color: var(--in-content-selected-text);
}

/* Trees */

xul|tree {
  -moz-appearance: none;
  font-size: 1em;
  border: 1px solid var(--in-content-box-border-color);
  background-color: var(--in-content-box-background);
  margin: 0;
}

xul|tree:-moz-focusring,
xul|richlistbox:-moz-focusring {
  border: 1px dotted var(--in-content-border-focus);
}

xul|listheader,
xul|treecols {
  -moz-appearance: none;
  border: none;
  border-bottom: 1px solid var(--in-content-border-color);
  padding: 0;
}

.autocomplete-tree > xul|treecols {
  border-bottom: none !important;
}

xul|treecol:not([hideheader="true"]),
xul|treecolpicker {
  -moz-appearance: none;
  border: none;
  background-color: var(--in-content-box-background-hover);
  color: #808080;
  padding: 5px 10px;
}

xul|treecol:not([hideheader="true"]):not([sortable="false"]):hover,
xul|treecolpicker:hover {
  background-color: var(--in-content-box-background-active);
  color: var(--in-content-text-color);
}

xul|treecol:not([hideheader="true"]):not(:first-child),
xul|treecolpicker {
  border-inline-start-width: 1px;
  border-inline-start-style: solid;
  border-image: linear-gradient(transparent 0%, transparent 20%, #c1c1c1 20%, #c1c1c1 80%, transparent 80%, transparent 100%) 1 1;
}

xul|treecol:not([hideheader="true"]) > xul|*.treecol-sortdirection[sortDirection] {
  list-style-image: url("chrome://global/skin/in-content/dropdown.svg");
  -moz-context-properties: fill;
  fill: -moz-DialogText;
  width: 18px;
  height: 18px;
}

xul|treecol:not([hideheader="true"]) > xul|*.treecol-sortdirection[sortDirection="ascending"] {
  transform: scaleY(-1);
}

/* This is the only way to increase the height of a tree row unfortunately */
xul|treechildren::-moz-tree-row {
  min-height: 2em;
}

/* Color needs to be set on tree cell in order to be applied */
xul|treechildren::-moz-tree-cell-text {
  color: var(--in-content-text-color);
}

xul|treechildren::-moz-tree-cell-text(selected) {
  color: var(--in-content-selected-text);
}

xul|caption {
  background-color: transparent;
}

xul|button,
html|button,
xul|colorpicker[type="button"],
xul|menulist {
  margin: 2px 4px;
}

xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
  margin-top: 1px;
  margin-bottom: 1px;
}

xul|checkbox {
  padding-inline-start: 0;
}

@media (-moz-windows-default-theme: 0) {
  xul|*.checkbox-check {
    background-image: none;
  }

  xul|*.checkbox-check[checked] {
    fill: -moz-dialogText;
    stroke: none;
    background-color: -moz-dialog;
  }
}

xul|radio {
  -moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
  padding-inline-start: 0;
}

@media (-moz-windows-default-theme: 0) {
  xul|*.radio-check {
    background-image: none;
  }

  xul|*.radio-check[selected] {
    background-color: -moz-dialog;
    fill: -moz-dialogText;
  }
}

xul|*.radio-icon,
xul|*.checkbox-icon {
  margin-inline-end: 0;
}

/* Never draw a border for the focusring, use outline instead */
xul|*.menulist-label-box,
xul|*.radio-label-box {
  border-style: none;
}

xul|menulist:-moz-focusring > xul|*.menulist-label-box,
xul|radio[focused="true"] > xul|*.radio-label-box,
html|input[type="checkbox"]:-moz-focusring + html|label:before {
  outline: 1px dotted;
}

/* Use a 2px border so that selected row highlight is still visible behind
    an existing high-contrast border that uses the background color */
@media (-moz-windows-default-theme: 0) {
  xul|treechildren::-moz-tree-row(selected),
  xul|listbox xul|listitem[selected="true"] {
     border: 2px dotted Highlight;
  }
}
PK
!<:7!YY:chrome/toolkit/skin/classic/global/in-content/dropdown.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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">
  <path fill="context-fill" d="M12,6l-4.016,4L4,6H12z"/>
</svg>

PK
!<<chrome/toolkit/skin/classic/global/in-content/help-glyph.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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">
  <!-- The context fill is applied to the question mark path and the context stroke is applied to the circle. -->
  <circle fill="context-stroke" fill-opacity="context-stroke-opacity" stroke="context-stroke" stroke-width="2" cx="12" cy="12" r="11"/>
  <path fill="context-fill" d="M12.2,4.9c-1.6,0-2.9,0.4-3.8,0.8L9.2,8c0.6-0.4,1.5-0.6,2.2-0.6c1.1,0,1.6,0.5,1.6,1.2 c0,0.7-0.6,1.3-1.3,2.1c-1,1.1-1.4,2.1-1.3,3.2l0,0.5h3V14c0-0.9,0.3-1.7,1.2-2.5c0.9-0.9,1.9-1.9,1.9-3.4 C16.6,6.4,15.2,4.9,12.2,4.9z M12,16.1c-1.1,0-1.9,0.8-1.9,1.9c0,1.1,0.8,1.9,1.9,1.9c1.2,0,1.9-0.8,1.9-1.9 C13.9,16.9,13.1,16.1,12,16.1z"/>
</svg>

PK
!<?!

<chrome/toolkit/skin/classic/global/in-content/info-pages.css
@import url("chrome://global/skin/in-content/common.css");

:root {
  --in-content-container-min-width: 13em;
  --in-content-container-max-width: 52em;
}

/* Body and container */
body {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  min-height: 100vh;
  padding: 40px 48px;
  align-items: center;
  justify-content: center;
}

.container {
  min-width: var(--in-content-container-min-width);
  max-width: var(--in-content-container-max-width);
}

.container.restore-chosen {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  margin: 10vh 0;
}

/* Typography */
.title {
  background-image: url("chrome://global/skin/icons/info.svg");
  background-position: left 0;
  background-repeat: no-repeat;
  background-size: 1.6em;
  margin-inline-start: -2.3em;
  padding-inline-start: 2.3em;
  font-size: 2.2em;
}

.title:-moz-locale-dir(rtl),
.title:dir(rtl) {
  background-position: right 0;
}

.title-text {
  font-size: inherit;
  padding-bottom: 0.4em;
}

@media (max-width: 970px) {
  .title {
    background-image: none !important;
    padding-inline-start: 0;
    margin-inline-start: 0;
  }

  .title-text {
    padding-top: 0;
  }
}

ul, ol {
  margin: 0;
  padding: 0;
  margin-inline-start: 1em;
}

ul > li, ol > li {
  margin-bottom: .5em;
}

ul {
  list-style: disc;
}

dt {
  font-weight: bold;
}

ul.columns {
  column-count: 2;
  column-gap: 5em;
}

@media (max-width: 35em) {
  ul.columns {
    column-count: 1;
  }
}

/* Buttons */
.button-container {
  margin-top: 1.2em;
}

button {
  padding: 0 1.5em;
  border-radius: 0;
}

.button-container > button:first-child {
  margin-inline-start: 0;
}

/* Trees */
.tree-container {
  margin-top: 1.2em;
  flex-grow: 1;
  min-height: 12em;
}

.tree-container > tree {
  height: 100%;
}

tree {
  width: 100%;
}
PK
!<K$SS7chrome/toolkit/skin/classic/global/in-content/radio.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 21 21">
  <circle fill="context-fill" cx="10.5" cy="10.5" r="6"/>
</svg>

PK
!<Gll.chrome/toolkit/skin/classic/global/listbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== listbox.css =======================================================
  == Styles used by XUL listbox-related elements.
  ======================================================================= */


@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: listbox ::::: */

listbox {
  -moz-appearance: listbox;
  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;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

listbox[disabled="true"] {
  color: GrayText;
}

/* ::::: listitem ::::: */

listitem {
  border: 1px solid transparent;
}

listbox:focus > listitem[selected="true"][current="true"] {
  outline: 1px dotted #F3D982;
}

listbox:focus > listitem[current="true"] {
  outline: 1px dotted Highlight;
  outline-offset: -1px;
}

listitem[selected="true"] {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

listbox:focus > listitem[selected="true"] {
  background-color: Highlight;
  color: HighlightText;
}

/* ::::: listheader ::::: */

listheader {
  -moz-appearance: treeheadercell;
  -moz-box-align: center;
  border: 2px solid;
  -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
  -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
  background-color: -moz-Dialog;
  color: -moz-DialogText;
  padding: 0 4px;
}

listheader[sortable="true"]:hover:active {
  border-top: 2px solid;
  border-right: 1px solid;
  border-bottom: 1px solid;
  border-left: 2px solid;
  -moz-border-top-colors: ThreeDShadow -moz-Dialog;
  -moz-border-right-colors: ThreeDShadow;
  -moz-border-bottom-colors: ThreeDShadow;
  -moz-border-left-colors: ThreeDShadow -moz-Dialog;
  padding-top: 1px;
  padding-bottom: 0px;
  padding-inline-start: 5px;
  padding-inline-end: 4px;
}

.listheader-icon {
  margin-inline-end: 2px;
}

.listheader-label {
  margin: 0px !important;
}

/* ..... sort direction icon ..... */

.listheader-sortdirection {
  list-style-image: none;
}

.listheader-sortdirection[sortDirection="ascending"] {
  list-style-image: url("chrome://global/skin/tree/sort-asc.png");
}

.listheader-sortdirection[sortDirection="ascending"]:-moz-system-metric(windows-classic) {
  list-style-image: url("chrome://global/skin/tree/sort-asc-classic.png");
}

.listheader-sortdirection[sortDirection="descending"] {
  list-style-image: url("chrome://global/skin/tree/sort-dsc.png");
}

.listheader-sortdirection[sortDirection="descending"]:-moz-system-metric(windows-classic) {
  list-style-image: url("chrome://global/skin/tree/sort-dsc-classic.png");
}

/* ::::: listcell ::::: */

.listcell-label {
  margin: 0px !important;
  padding-top: 0px;
  padding-bottom: 1px;
  padding-inline-start: 4px;
  padding-inline-end: 0px;
  white-space: nowrap;
}

.listcell-icon {
  margin-inline-end: 2px;
}

.listcell-label[disabled="true"] {
  color: GrayText;
}

/* ::::: listcell checkbox ::::: */

.listcell-check {
  -moz-appearance: checkbox;
  -moz-box-align: center;
  margin: 0px 2px;
  border: 1px solid -moz-DialogText;
  min-width: 13px;
  min-height: 13px;
  background: -moz-Field no-repeat 50% 50%;
}

@media (-moz-windows-default-theme) {
  listitem {
    --listitem-selectedColor: rgb(217,217,217);
    --listitem-selectedBorder: var(--listitem-selectedColor);
    --listitem-selectedBottomBorder: rgb(204,204,204);
    --listitem-selectedBackground: var(--listitem-selectedColor);
    --listitem-selectedImage: none;
    --listitem-selectedCurrentBorder: rgb(123,195,255);
    --listitem-selectedFocusColor: rgb(205,232,255);
    --listitem-selectedFocusBorder: var(--listitem-selectedFocusColor);
    --listitem-selectedFocusBottomBorder: rgb(165,214,255);
    --listitem-selectedFocusBackground: var(--listitem-selectedFocusColor);
    --listitem-selectedFocusImage: none;
    --listitem-selectedFocusCurrentBorder: var(--listitem-selectedFocusColor);
    --listitem-selectedFocusCurrentBottomBorder: var(--listitem-selectedFocusBottomBorder);
    --listitem-selectedFocusCurrentBackground: var(--listitem-selectedFocusColor);

    color: -moz-FieldText;
    margin-inline-start: 1px;
    margin-inline-end: 1px;
    padding-top: 1px;
    padding-bottom: 1px;
    border-width: 1px;
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }

  listitem[selected="true"] {
    border-top-color: var(--listitem-selectedBorder);
    border-right-color: var(--listitem-selectedBorder);
    border-left-color: var(--listitem-selectedBorder);
    border-bottom-color: var(--listitem-selectedBottomBorder);
    background-image: var(--listitem-selectedImage);
    background-color: var(--listitem-selectedBackground);
    color: -moz-DialogText;
  }

  listbox:focus > listitem[selected="true"] {
    border-top-color: var(--listitem-selectedFocusBorder);
    border-right-color: var(--listitem-selectedFocusBorder);
    border-left-color: var(--listitem-selectedFocusBorder);
    border-bottom-color: var(--listitem-selectedFocusBottomBorder);
    background-image: var(--listitem-selectedFocusImage);
    background-color: var(--listitem-selectedFocusBackground);
    color: -moz-DialogText;
  }

  listbox:focus > listitem[current="true"] {
    border-color: var(--listitem-selectedCurrentBorder);
    outline: none;
  }

  listbox:focus > listitem[selected="true"][current="true"] {
    border-top-color: var(--listitem-selectedFocusCurrentBorder);
    border-right-color: var(--listitem-selectedFocusCurrentBorder);
    border-left-color: var(--listitem-selectedFocusCurrentBorder);
    border-bottom-color: var(--listitem-selectedFocusCurrentBottomBorder);
    background-color: var(--listitem-selectedFocusCurrentBackground);
    outline: none;
  }

  @media (-moz-os-version: windows-win7) {
    listitem {
      --listitem-selectedBottomBorder: var(--listitem-selectedColor);
      --listitem-selectedBackground: rgba(190,190,190,.15);
      --listitem-selectedImage: linear-gradient(rgba(190,190,190,.1), rgba(190,190,190,.4));
      --listitem-selectedCurrentBorder: rgb(125,162,206);
      --listitem-selectedFocusColor: rgb(132,172,221);
      --listitem-selectedFocusBottomBorder: var(--listitem-selectedFocusColor);
      --listitem-selectedFocusBackground: rgba(131,183,249,.02);
      --listitem-selectedFocusImage: linear-gradient(rgba(131,183,249,.16), rgba(131,183,249,.375));
      --listitem-selectedFocusCurrentBackground: rgba(131,183,249,.15);

      border-radius: 3px;
      box-shadow: inset 0 0 0 1px rgba(255,255,255,.4), inset 0 -1px 0 1px rgba(255,255,255,.2);
    }
  }

  @media (-moz-os-version: windows-win8) {
    listitem {
      --listitem-selectedBottomBorder: var(--listitem-selectedColor);
      --listitem-selectedBackground: rgba(190,190,190,.15);
      --listitem-selectedImage: linear-gradient(rgba(190,190,190,.4), rgba(190,190,190,.4));
      --listitem-selectedCurrentBorder: rgb(125,162,206);
      --listitem-selectedFocusColor: rgb(132,172,221);
      --listitem-selectedFocusBottomBorder: var(--listitem-selectedFocusColor);
      --listitem-selectedFocusBackground: rgba(131,183,249,.02);
      --listitem-selectedFocusImage: linear-gradient(rgba(131,183,249,.375), rgba(131,183,249,.375));
      --listitem-selectedFocusCurrentBackground: rgba(131,183,249,.15);
    }
  }
}
PK
!<D.**Bchrome/toolkit/skin/classic/global/media/TopLevelImageDocument.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

@media not print {
  /* N.B.: Remember to update ImageDocument.css in the tree or reftests may fail! */

  body {
    color: #eee;
    background-image: url("chrome://global/skin/media/imagedoc-darknoise.png");
  }

  img.transparent {
    background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png");
    color: #222;
  }
}
PK
!<4MBchrome/toolkit/skin/classic/global/media/TopLevelVideoDocument.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  background-image: url("chrome://global/skin/media/imagedoc-darknoise.png");
  background-color: rgb(33,33,33); /* Average color of that ^ image. */
}

video {
  box-shadow: 0 0 5px rgba(0,0,0,0.6);
}
PK
!<-`=chrome/toolkit/skin/classic/global/media/audioMutedButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill" d="M3.52,5.367c-1.332,0-2.422,1.09-2.422,2.422v2.422c0,1.332,1.09,2.422,2.422,2.422h1.516l4.102,3.633V1.735L5.035,5.367H3.52z"/>
  <path fill="context-fill" fill-rule="evenodd" d="M12.155,12.066l-1.138-1.138l4.872-4.872l1.138,1.138 L12.155,12.066z"/>
  <path fill="context-fill" fill-rule="evenodd" d="M10.998,7.204l1.138-1.138l4.872,4.872l-1.138,1.138L10.998,7.204z"/>
</svg>

PK
!<Ut2RR?chrome/toolkit/skin/classic/global/media/audioNoAudioButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M14.901,3.571l-4.412,3.422V1.919L6.286,5.46H4.869c-1.298,0-2.36,1.062-2.36,2.36v2.36
           c0,1.062,0.708,1.888,1.652,2.242l-2.242,1.77l1.18,1.416L16.081,4.987L14.901,3.571z M10.489,16.081V11.36l-2.669,2.36
           L10.489,16.081z"/>
</svg>

PK
!<Nq?chrome/toolkit/skin/classic/global/media/audioUnmutedButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M3.52,5.367c-1.332,0-2.422,1.09-2.422,2.422v2.422c0,1.332,1.09,2.422,2.422,2.422h1.516l4.102,3.633
           V1.735L5.035,5.367H3.52z M12.059,9c0-0.727-0.484-1.211-1.211-1.211v2.422C11.574,10.211,12.059,9.727,12.059,9z M14.48,9
           c0-1.695-1.211-3.148-2.785-3.512l-0.363,1.09C12.422,6.82,13.27,7.789,13.27,9c0,1.211-0.848,2.18-1.938,2.422l0.484,1.09
           C13.27,12.148,14.48,10.695,14.48,9z M12.543,3.188l-0.484,1.09C14.238,4.883,15.691,6.82,15.691,9c0,2.18-1.453,4.117-3.512,4.601
           l0.484,1.09c2.422-0.605,4.238-2.906,4.238-5.691C16.902,6.215,15.086,3.914,12.543,3.188z"/>
</svg>

PK
!<'[Gchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-off.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill" fill-rule="evenodd"
        d="M16.531,16.107H5.267l1.982-2H15c0.6,0,1-0.4,1-1V5.274
           l1.946-1.964C17.963,3.399,18,3.483,18,3.576v11.031C18,15.407,17.331,16.107,16.531,16.107z M14.016,8.506h-1.218l1.005-1.014
           C13.913,7.789,13.984,8.128,14.016,8.506z M11.786,12.361c-0.828,0-1.476-0.326-1.913-0.902l1.09-1.101
           c0.136,0.323,0.374,0.541,0.796,0.541c0.514,0,0.695-0.44,0.756-1.014h1.535C13.908,11.43,13.071,12.361,11.786,12.361z
           M1.496,16.106C0.697,16.104,0,15.406,0,14.607V3.576c0-0.8,0.7-1.5,1.5-1.5h12.846L16.299,0l1.316,1.283L2.615,17.13L1.496,16.106
           z M3,4.107c-0.6,0-1,0.4-1,1v8c0,0.6,0.4,1,1,1h0.029l2.031-2.16c-0.757-0.503-1.191-1.457-1.191-2.744
           c0-1.936,1.069-3.14,2.428-3.14c1.357,0,2.136,0.76,2.361,2.059l3.777-4.016H3z M8.298,8.506H7.355
           c-0.047-0.623-0.49-1.23-0.99-1.23c-0.561,0-1.337,0.84-1.337,1.995c0,0.674,0.381,1.427,0.95,1.702L8.298,8.506z"/>
</svg>

PK
!<[YFchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-on.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M16.531,1.984H1.5c-0.8,0-1.5,0.7-1.5,1.5v11.031c0,0.8,0.7,1.5,1.5,1.5h15.031
           c0.8,0,1.469-0.7,1.469-1.5V3.484C18,2.684,17.331,1.984,16.531,1.984z
           M16,13.016c0,0.6-0.4,1-1,1H3c-0.6,0-1-0.4-1-1v-8c0-0.6,0.4-1,1-1h12c0.6,0,1,0.4,1,1V13.016z
           M6.426,10.807c-0.811,0-0.96-0.789-0.96-1.628c0-1.155,0.338-1.745,0.899-1.745c0.5,0,0.818,0.357,0.866,0.98
           h1.484C8.585,6.877,7.785,5.972,6.297,5.972c-1.359,0-2.428,1.205-2.428,3.14c0,1.944,0.974,3.157,2.583,3.157
           c1.285,0,2.153-0.93,2.295-2.476H7.244C7.183,10.367,6.94,10.807,6.426,10.807z
           M11.759,10.807c-0.811,0-0.96-0.789-0.96-1.628c0-1.155,0.338-1.745,0.899-1.745c0.5,0,0.756,0.357,0.803,0.98h1.515
           c-0.129-1.537-0.898-2.443-2.385-2.443c-1.359,0-2.396,1.205-2.396,3.14c0,1.944,0.943,3.157,2.552,3.157
           c1.285,0,2.122-0.93,2.264-2.476h-1.535C12.454,10.367,12.273,10.807,11.759,10.807z"/>
</svg>

PK
!<Vm?yOyO2chrome/toolkit/skin/classic/global/media/error.pngPNG


IHDRx 1	pHYs
MiCCPPhotoshop ICC profilexڝSwX>eVBl"#Ya@Ņ
VHUĂ
H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W "R.TSd
ly|B"
I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f	"#HL8?flŢko">!N_puk[Vh]3	Z
zy8@P<
%b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2	w
ONl~Xv@~-g42y@+͗\LD*AaD@$<B
AT:18
\p`	Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W	1'"O%zxb:XF&!!%^'_H$ɒN
!%2IIkHH-S>iL&m O:ňL	$RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t	݃EЗkw

Hb(k{/LӗT02goUX**|:V~TUsU?yTU^V}FUP	թU6RwRPQ__c
FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ!
{--?-jf~7zھbrup@,:m:u	6Qu>cy	Gm7046l18c̐ckihhI'&g5x>fob4ek<abi2ۤĤ)͔kfѴt,ܬج9՜kaټEJ6ǖږ|MV>VyVV׬I\,mWlPW:˶vm))Sn1
9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/>B	
Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$	=sl3Ttcܢ˞w<Y5Y|8? BP/OnM򄛅OEQJ<V8;}ChOFu3	OR+y#MVDެq-9R
i+0(Of++
ym#slLѣRPL/+x[[xHHZ3f#|PظxY"E#Sw.1]Rdxi}h˲PXRUjyRҥC+W4nZcadUj[V*_pFWN_|ymJHnYJjAІ
_mJtzjʹ5a5[̶6z]V&ֿw{;켵+xWkE}nݏb~ݸGwOŞ{{Ejtolܯ	mR6H:p囀oڛwpZ*A'ߦ|{PߙHy+:u-m=茣^G~1cu5W(=䂓dN?=ԙyLk]Q]gCϞ?tL_]p"b%K==G~pH[oeW<tM;js.]yn&%vw
L]zxem``Y	ӇGG#F#
dΓ᧲~VysKXϿyr﫩:#y=}ǽ(@PcǧO>|/%ҟ3:8iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c067 79.157747, 2015/03/30-23:40:42        ">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
            xmlns:dc="http://purl.org/dc/elements/1.1/"
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
            xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
            xmlns:exif="http://ns.adobe.com/exif/1.0/">
         <xmp:CreatorTool>Adobe Photoshop CC 2015 (Macintosh)</xmp:CreatorTool>
         <xmp:CreateDate>2016-08-08T16:58:26+08:00</xmp:CreateDate>
         <xmp:MetadataDate>2016-08-08T16:58:26+08:00</xmp:MetadataDate>
         <xmp:ModifyDate>2016-08-08T16:58:26+08:00</xmp:ModifyDate>
         <xmpMM:InstanceID>xmp.iid:a163ab08-1324-43b5-934c-1e9bfe8831b9</xmpMM:InstanceID>
         <xmpMM:DocumentID>adobe:docid:photoshop:9854d936-9a94-1179-88ef-aa7acfb1f497</xmpMM:DocumentID>
         <xmpMM:OriginalDocumentID>xmp.did:c7837620-bc58-4b2c-a95a-e544a5cb8507</xmpMM:OriginalDocumentID>
         <xmpMM:History>
            <rdf:Seq>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>created</stEvt:action>
                  <stEvt:instanceID>xmp.iid:c7837620-bc58-4b2c-a95a-e544a5cb8507</stEvt:instanceID>
                  <stEvt:when>2016-08-08T16:58:26+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
               </rdf:li>
               <rdf:li rdf:parseType="Resource">
                  <stEvt:action>saved</stEvt:action>
                  <stEvt:instanceID>xmp.iid:a163ab08-1324-43b5-934c-1e9bfe8831b9</stEvt:instanceID>
                  <stEvt:when>2016-08-08T16:58:26+08:00</stEvt:when>
                  <stEvt:softwareAgent>Adobe Photoshop CC 2015 (Macintosh)</stEvt:softwareAgent>
                  <stEvt:changed>/</stEvt:changed>
               </rdf:li>
            </rdf:Seq>
         </xmpMM:History>
         <dc:format>image/png</dc:format>
         <photoshop:ColorMode>3</photoshop:ColorMode>
         <photoshop:ICCProfile>sRGB IEC61966-2.1</photoshop:ICCProfile>
         <tiff:Orientation>1</tiff:Orientation>
         <tiff:XResolution>720000/10000</tiff:XResolution>
         <tiff:YResolution>720000/10000</tiff:YResolution>
         <tiff:ResolutionUnit>2</tiff:ResolutionUnit>
         <exif:ColorSpace>1</exif:ColorSpace>
         <exif:PixelXDimension>140</exif:PixelXDimension>
         <exif:PixelYDimension>120</exif:PixelYDimension>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                                                                                                    
                            
<?xpacket end="w"?>p cHRMz%u0`:o_F
bIDATx{l[W?'86i7.cmvhkA6mLTD%`::kӆ11k14mHNUZ)-k&q7ɒnSۉ{,NIW{/}5i dJADae`^:~-[fcJrQBܺΟ.xZ8bX HaS]BX,5L#O_[-))-]4NJ0R4=ҚfOӇarSuG2NǟJL")_+MT*t:D,0[js*IL&4W*aٯi!`#d*}60BܩiaL*naLUӴ_5"JEKH$n
Bc"".k##A[rfJJJ2>&J	B
S)`Z0HD!sN_aFaFaFDADADAFaFaVq9 H,y###D"eL4Hgg'(++㎽{1sif3{v寮|Y/9${{F-Y===>W]¹z괵@%	wtl/!_,뀢Y2zrcD#^"ʦ<ryl"
eÆ
Ӟ\YU]$vl6皍EBjv(--:knzx<v 7mFuUO^n7v;%%vP0L.KUUR$0 " &$	L&TTT0kQ~spR48xP(DVVs&\

",|>W̻dd2H4R]]YW'¬&.v8MK&DQB`ˁxn\K8!aΜ93
\.κyG-ACCD"?)O03lr:EB,Fm64662|>k۝ښjkjxGՍ&FE6L w#0#0#" " " #0#0#0	Ӧhvm2G7ސF"i!"ȓS"$'faO&I֕^悈mKKK>p@1p{TeS^oknmml9rYex'@kk+g~&<VuMHǹx";ɓ'kǀ8pwr	S8JU썩JgxVqZyJ:?~%L1~
lz(p	8*osAeFY|ʥ08ܤ}Swmp}VTlZI{AY
4O,Q%|xrUC.Be]~=49_cT(LBٹs'w}7gbf/OgIۼ`4Zp2jtt:btww=no.eIۼ1d$|V͖Rv}hjjra2ʙVuÌssdz"===tvv<6y7Eෙ‚O)#9LM>f}vnjuTS= i5'r}%H.עr'"p[L823gT">4𴔷|.zoO{,@%7	166v'L&p>);SB!pwV֣jYҙg<QTT{sbO:$ɬbtsss)F߫0zk
	2$GW(C#t|w="}zJoooֲ={6TBx% zu>v*>Ju<>ZIMSj
fk!544k.vQzj5tsvLXfb1srU3)Rwihh0	7/###tttA*5?sd١f`P>|1mT`ժ]a{ͯZ.W;Z,J'*--e͸\.(//7t!tO__.]|Ch*||*g%T+8*J^#'b;ws`Yn͆ljNP*HFʕ+BY_4IQ
.a"̳$DVuE0.Y̼t|/+2~yJ-0)k+л s @ݿ8*#t+G3{ѧWuFGQÒl+OTt%gF[}_ܬS'|}6-yquJ/%
K	ש<
Gm79'}S8VX_S/M}
8nTUXY0-b ^Aa<IENDB`PK
!<kkBchrome/toolkit/skin/classic/global/media/fullscreenEnterButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M6.728,10.188l-3.235,3.094l0.017-2.267l-1.513-0.016l0,5l4.987-0.008l0.011-1.537l-2.281-0.022
           l3.097-3.158L6.728,10.188z M14.453,11.004l-0.022,2.281l-3.158-3.097l-1.086,1.083l3.094,3.235l-2.267-0.017l-0.016,1.514l5,0
           l-0.008-4.988L14.453,11.004z M11.015,2.01l-0.011,1.537l2.281,0.022l-3.097,3.158l1.083,1.086l3.235-3.094L14.49,6.986
           l1.513,0.016v-5L11.015,2.01z M6.986,3.511l0.016-1.514l-5,0L2.01,6.985l1.537,0.011l0.022-2.281l3.158,3.097l1.086-1.083
           L4.718,3.494L6.986,3.511z"/>
</svg>

PK
!<PWWAchrome/toolkit/skin/classic/global/media/fullscreenExitButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M2.047,11.135l-0.011,1.537l2.281,0.022L1.22,15.851l1.083,1.086l3.235-3.094l-0.017,2.268l1.513,0.016
           l0-5L2.047,11.135z M13.781,12.587l2.267,0.017l0.016-1.514l-5,0l0.008,4.988l1.537,0.011l0.022-2.281l3.158,3.097l1.086-1.083
           L13.781,12.587z M16.058,5.578l-2.281-0.021l3.097-3.158l-1.083-1.086l-3.235,3.094l0.017-2.267L11.06,2.123v5l4.988-0.008
           L16.058,5.578z M5.516,2.098L5.494,4.379L2.336,1.283L1.25,2.365L4.344,5.6L2.077,5.583L2.06,7.097l5,0L7.053,2.109L5.516,2.098z"/>
</svg>

PK
!<c?chrome/toolkit/skin/classic/global/media/imagedoc-darknoise.pngPNG


IHDRtEXtSoftwareAdobe ImageReadyqe<PLTE"""!!!###   HAȫqIDATx[	8yEY8f#=,gzO3WqS}Uy"psz~[fX21S
5>}OEVZ}
{jygHj}Dǯ{r̎fp֙}ǚӢvk~gM}1ưhIJ>fZF/8+ɲՆ[c:E:dTGIk~t܅ĸ6°)DuF]t8R!vhЦ
5Pp;QW깚ʅ}Mi
DžJ:-:kWӌu.]*FŚjXJA7ѢkfCTLT:l,ĦսI5!LU^kͭܢ pYwSRAd<}Bhup_iQջ3	+F[ց~Rd+5£lnBB .2ҀFO>u*pV	6p>)9Z}R	xڍq2hl	ಆB	;cex$\gJ1X
1VJRqj	Y:q͚"`\#M3B[[KS6O'W&-v IGMuٔ;jb`crs}θM'cgʞj9"ty::TtL)CѩF] EʒORJcA	M"u%Jj!MiDjS
-iғ$q\(9rscp
8Fn3)tAܲ<ABh+tThk\KLj#G%Cj8f,a_+qœ4#v!8Q9ɐo;~Io{g2V:MjC#Q>)r9|/H0@h!w@!ЕܥtO|')q<	t4tO4yP(fw4N(0D	z1Ҭ1@3+\I1Ajq$	4ZoMA:c<ʽ+e+`,UkIg^|ls1$Ҥ:0"?LO&	UdS\?23aG85-IVhF'<`k>uR=N<i8I8:e4C>sG>]ȯ9)\.CI0ŜRĒA\d ɚ5<v2|'F0LMcUF	~b&M	y@_źR+MۍVu6g߶Ӄ@ cHgH&IW4w^C5}RX|'€?B #FC.[z]6˲szAWd
>&´9ڑId>H釼$1j6’v07~w?YF2㧗*cEl*u@|[|=iL(0ĵ=n.]@(pCArDt
㪜9+npGL,w^T
KdRWOprb-9z:{OLԋwW+-;?
	6 Gv6u)KSWxvf0FX-P 9=\*J~rVp~O79SF̈<%rjVcۃLMt+tw%?.itl!3IÍO10EY߉72Umd$TYa4&FӨ~'9ߛ6vwEZ( $V9/ 1q?$ڠTVȴ)A0DFDŽpI	VT%\R9d<+h9egw`z­[J
"!it.VÔ$퐯0~~#LF<# }([eb#]/<~^o0MKcΊNS2&S4
swΝ?aKOB	+(Sޙ^~pZ/	4ۣBݴj\E8G8:zomK8l|;kW`֋W+od~Nv\>&E{J~rMZJVR>0\DtCHTm4rV6]e'sJ%`dcIo<I{CB!O$necDž{U*3\ɱsa)jx(E
9:y0hC&#BMLN
|@bAu̔a?MyLv~N݌S-xWIЙʑ~XiN]EnprQ-uC=42pzy9>4^VJήL^|!9DT`ߌ0=]Jgo}hc]n}fQ9㷂I,L'Wo	;?v0~Jρ<to͋rKmx_Û{[GNyߊscbT钖b#ށID{tl+|hpsOX2K5Za[#2w9-LҞ~C<FYy=r&VThgI_W6;iC9Sd6*tgdÕZR{Ӆ~ %b16jIպ|m#g{^ݾ3}fs";S;y񪎗_d})ʝ[#3B'M(D+C!g|2_@:m`J,v; 'tnChwΖ#:]m?SH9IENDB`PK
!<J)@chrome/toolkit/skin/classic/global/media/imagedoc-lightnoise.pngPNG


IHDRtEXtSoftwareAdobe ImageReadyqe<9PLTE   555IIIYYYggguuulbtRNSlAMIDATx[v<,na"_Ė)esrdQ]D;0Q+	΄$W>G4^h>cG)'L1RZƙbj	0-Guc8N {xт\wPe)ZkhvEn3c


Е##J)E2ʇ)r&EL8VT$)zA3[\ڇ"j<CU[M)55x\)pFӥ*Pq)iYLV%՛ZSTpi!ԇjؒ9qzi[=ɅbU?MijQӖ3I!1-l[8;q#DdxUX|n721wvԐ\Z6$sck,'N9$#Ʌ褜!@v:q،B#!abhu.E `p4y=1d0OҤS3ZJsp,਻޹<t52L뺭]VBzNˑ}J5MZFnmEŴ=Fڃc	FmTRC T-Fy |XmjK&J5{3UTv$z@sh=.vdaLbtlC rBhQrʊDX&&aqoCF%:fI
gkeD^s ة;ؓX-Bӭ,Mò*0
:EsWSBj
Q]p؜gX$U%9,\r?ޑfXD"YM;v
CB{vbO)S(Vk6*Q0,ݹs
X\J=
R)ڣ1ɐ2WO(5jy5B	CWv?B:R|DAf:Zߡ09"<٦VxZs1ainٳH<,a|IJsxI6"#X;箕6۬rpY<>OSCP7ԩ}Rfo_Nb)	ٹypǹՊnwTv$	Yr=G X7$VzVz@ABSVxfCi-B@Un>R2VC2e:d"\k8,ty
X|(;+ǵ	k>ȋdH52|nBK'9峒)4F02O/𓾢vQD
bPu4&q46cvHHʒ9F1
Ly}t{.IJ3A7tEI>2])pYN^a
0W:Sg-q`=ːSBoa)DLbpAP7rbq,]	j}Q3FJ^[/4%LF5,CM{Ԯr4l|ߝ0D EYUf=М6
qRi̷f^&Hd֭#Cxԉ<[ec<*Q8ĹQQ P~ָ ;X63\x
7hh4|Zx[ȟKV~|~<#rO z=E
B•AcѐH!42A/j"Kz2"kK P(Xd2)> :5V5lvЏ,E$nt9w%qثlb]lvZy_ȗ=Ϣ&Ŕ4G
D

<3:dd	ZCNdڏIR֢@&B2/T\*cwUֹ<fr6?v٣Wh;|TLj>hSY1ByZi:@
\='zSb`&qO'ɻ{{W_-𽂲;ց4l+"SzTֹbPF	9PBs%~.zᆯ/s%S`f&`AJJA^w­О/O~۷VD ^;w\K?FY`HMwr ҆p,bxZj!77~^5gbޛnb%Qf)
zMxqKs„hL!XZip={leyd+
UGZg XhCV?@;y]-SA/SϾUOζm7u
Ƽq}-uXy'A
OvFwYC#LXUG\#eE*nѭy#"QuB{/&.F8$xFt_{]uY6-0CgQOt8
h&]&WI!~Yto
?Lm5FLpAi&PhjBكUYS	PGw4(CS
gLԃ_/H<//@=I]pHYt֘hsB%I3w;5W7eqp5D
nMdseXsĩ	7u:7
j$/5K
QAnoō?i?<?.<+rq=;4.p&޳si\ ߑYU;3.36=UǷ)i[-u&Xn|p3B>z|0.Mv@;{["۝Km˰Z@t	x:Z-.I%m(tؖzzra#ZvbM?n?c?H~XOIy9x#%$r\I;7ݐ$c7d_ݐHJ^Vy?6v;^Čmf.V0S0j2K5(@^Wc7BSra$]#оZ-3Zfyɽf.;Sz	U3p=-VKʺDVpM"48+$9	ƹteGy"@2C?#GXgD|Yg8~W
ϞsfB#0,҉ȴREŮrBsGA/CW
֍3f::ֿ&	uӖ@ 7;]_jxsyճy!}QC9R&
噴Kݖܫ/1<O`h@>br?ΦQƑaxr|hk!1Eě4櫁c8ч]ѯ]ƄevMK߉qmw;-j~dO4AP_9!sl59U{oFZBk$4hȿ%6*/I{/<EyBM;9݁j*>m8Gl~.K̸?9֒rA~>W{_f7ǧu_j؎ˡ.7y]Dv/ބWWҫ/T:_r+\ʱSBw<%NIP~#|dK=]:{v	ܟv0FKrxۊע~MZv-ꢔyg|fIENDB`PK
!<8chrome/toolkit/skin/classic/global/media/pauseButton.svg<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill" d="M6.002,1.953C5.172,1.953,4.5,2.626,4.5,3.455v11.08
        c0,0.83,0.672,1.502,1.502,1.502c0.829,0,1.502-0.672,1.502-1.502V3.455C7.504,2.626,6.831,1.953,6.002,1.953z M12,1.953
        c-0.828,0-1.5,0.672-1.5,1.5v11.094c0,0.828,0.672,1.5,1.5,1.5s1.5-0.672,1.5-1.5V3.453C13.5,2.625,12.828,1.953,12,1.953z"/>
</svg>

PK
!<z7chrome/toolkit/skin/classic/global/media/playButton.svg<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill" d="M3.243,15.155c0,0.845,0.593,1.157,1.317,0.707l9.659-6.041c0.727-0.453,0.722-1.193,0-1.645L4.556,2.137
    C3.827,1.682,3.237,2.014,3.237,2.844v12.312H3.243z"/>
</svg>

PK
!<QQ4chrome/toolkit/skin/classic/global/media/stalled.pngPNG


IHDR$$acTLSfcTL$$x"bIDATX͗KHTQgJ ڢ=ha{lVI(RBQ"QKb@V5DGGp0LAQg>9n8>\߹^`0xmmmb[SG(JЯC`};xpp,@pK$ >h<d.&|ggg\Zg8O[ZZ@VL0,0T{{{
x@P5K }qqѺ~93T)+8[6l=gcy)	^BE(SJb_Gyqϗpˁ(TѶ@L8 S>\+++(r_XX8+PE4cf@(c5Tj0R͢OJQ1Speڄ]ubb!蔛bn~	Dg+]Gtcffb)S?L.CGD9dÍcI_ ^4܀sq-nоZj gff:03kK7Z2E1ڥW0&62}Etփ0D|ښʄ#P
Ǝwp%KP'Oc<nYcc7ױ{ǍeNz%aB	tF]UV%̘ؖ3{[dWWWYwwmI03ɭab3?H#jdǤ@3P1>1yh¹N.Hq51"3™:է4]pA	$%'q:Q;ˁIKDb_I4GL/ы؇D,P=vtDO	_
&
fcTL#xa{fdATX͕KhSQDQd#.D\VRpi
ҥ-t@qF(]`+pa!$JC^n&͋svѓ<s;_LIv}j}hF9~@fƳC	Θz^ #ǘzizB5vCtD\}ChYǘ^ѸV堼Pm)@J""cbjR!iaX[jE>5^,'~i+f+ZÊ\hkc^T*?s¾Y*N#9]h~ԖDU`qzRgh{­0zP)3h #\.wzWCVvh`[6.
='BG2|hKORPE$'D݂?@rjl+uˏ~NX&Ob3P@&&S}1	VD"x0L\.9J?`0hY
twv/|^	E>5ΡAnotN١gBSġ:UlZDߌ̡@o6	DׇyhJ<^؉DCjǮ
߬k*{̘;=W^M_-d	fcTL#
x R:fdATX͔KhQGJE. Nwjn-fWr'DZmA]DJ"䍛57
yAF qhC^;0L']LϽ9g9IRfyhlP<K`08t܆vc0[ՄdƘcz}v0kRtZޖeypꕰBn3\W'j*)7ϊiTG!1tS(ST+;P1%_.O0@?`JT>Pų:9ThKL

9J3*s͆Kn	ߢ--
 `VN[9gbqBBG9*z_,u*Nd2+ؗll:\8[i}h]
ap>?
4uVGfȆ7m/H(O$MC*cu$y݊5h
==d!h<?J	F_ܳUiÀ}n|Cp8|y<c*}-Xserzw=c1TS
`Zkجr=e/ڀ?[E0cVrPV[G:`,Om[*KP(3F?(S.gnifcTL xafdATX͗]HQgڇEyUuW^vٕF] veR7!E~04oB(YWB6l:Sq:y;k~4<wws:]mnn+Ϻ@,'.ܖ^!H3VW1m2~?m[YYWY333Vpqk].%ձ԰॥+6l Uy]|sʺC
?[۝UHhpD	WDywcST"8q@TEQ(>sL pLA1??-9G:%12 ;A#_YNעE^h25"zsw"=5\Q:}XհA1vw&:)`
T4T)7z)O@n;4Efsk2l###郃Г2|YPTztfB@ۇ^o3T@xbM9GhVɂX[ZZ?06@
@̈́Ip3]hՂQ%n>.nllܳQnNlk*Z[C<߁c
)fGGGbbñǬjǭ
QSH/V&cK<%G}L9r:I
ugeSmEDiaZqס,G~N.RP"T655E*$XXg*0w!n0%eW#<,'!YgbɊ+`\~BlT@0q,WYǪ9WGj3a LGGcE	bq1ǴZ3bߘX ʻfcTL xf:fdATX͖KHTQR2hpQk[m 
Z3jj"eEXc|7FPeX6$OΉ;w^g9Ϲy-..^,f_F(DZ}0+؞^zacc6\PAXh[8G91>>~krriN3jGZw`]hG)"/j88&xPP
͈	Њ'dDs-Ͷc!8f
uKa1h5AJ+++r.@IPvmH@Z1^pH5/rXJE@>B=^ր:i̱r}L faE:躄k"
<w.,zD
,Br@&Ɉ`80t@5`.냈6J"z(0y!~544dAuuu"l졡~s*rc
|C5Af,>F
L8cww1J
M7?=(j0%~PhV;CFy!YYY|477뛚UX,Pl"BWwc
ikk@%pmp	f@\5Tr(WGm&@IΈP6X),Ugg%%EDԶ ia)Fjp:$-HA.PέA	eOV---^pt/y&%ѧs@d]+ިLcG7qbtXR=dՔ#qb")*ƟB
8s+**Y\ކ5#H1fcTL	 x|fdAT
X͖KQ'VH?
 ҫ(*@` FyͤLo0&s: x!AcBƞGggIk9y;mH$RGon~4mbAs9wpj
Yhc;T8@=|{ommAD+TPh!Y~ר`b½p8\yk{{;6!4]$Ժvۋc~T	`Wy>Kyss*B>wJR"P[,7]Tk+|`bcj~~~0uHNLqxs\_?88RJP(Q8wpAov^,́*xa1$$)H̍C|g1A%6# WC+w#wD*̶(b 7lGKp#T-9,ĥ)dtTww@chh^
pC}ҿC̷
G{ާh<Ϲ0N<Wai?B.ȖBE.9d{~t_X6h6iŁ@8 >7'K=+6bLԓSf>+ZZi̓VIYt$lfXY(Z{`9
Qmz~
.r	300PB=5pHX>NV,H`t3KjDH: +0qUtJB&B3^])BFd
qkttX9htڠ-ڴoj!fcTL#xTX
fdATX͖KhQ'TPB+-nF}Z]qb.ZV\7)ZnJLhȃdJ)w2MB8νÜ/PE9fRt]Hs|{}AL&B?8p0H`ȡƽUrZ(
T"Lk3Ph0ءX,:*U*<+J.%
0p{UUdڦp!4i@61~1L`¸/zeW̹6f$y?mB4"z,ˍHz),(ιfGp82rY8OXݑw:|&ŹH$Fu3@Xv03Riv3`yD2!/H&ClVKD´3(ɦHh/lj4/
{DYv!L%cyʺzF#hKn1qC]>~mGk4=Zm\.
;xBO0w|I7]:_`\ל)jc.0ZW
{ë0~Is"SdѶG2kUfcTL
#xP*wfdATX͖=hSQ
U
RP(f
Z[pԵP$CGq
fI,iA
! @I|r\Bm\xsz~;!r+L`V9l6_H$z`c;UL&0J0vqTePt:M#r87d|^W.
X,vIusHlʈ1#pxC<Jy)ARJrCSbv{pqN56AlA+G%b17F ǿ^9YA$CI{V[jhE6kx|@%1|$B
$mnY?
_]?vvQ2bY`!\.wuɚI?|-6t:Q{DĺIalyS	ѸdClh4zxj ``pkX:zLlY#֕~oy<I4M=ԛ(gy4vZ`s+nZOvvv|jX-[;-lZ}0jvKU՗x3#lG&U'd'F"x-*x`;"yK+"\V͌JpL!+TaU+#X/ڦj:YCfcTL#x="vfdATX͕KhQG)ReEܸT؝MW]AZnJ*75>6
yAP`^y
h2ylޑK,&ia77U-h4:(Vk_^7slGab	z9WBNgQH$2.	Đg-i68N/jY^߃,p40bd|x>z/"n@|BqP(Jc?>4ˆi`0x|U5
cij-u$ͮ'C؏B(BW"WyftQ9$=#Pq4Ba=L|G/)n{cYD%v#k֙`8<~o"8J13OQC`FM:o1zWa!/H&}4+=Ѱ#FIdEthB>
z J
!=Ѱ#kKx#[D4"Awlw0M֗(xR|6r8lg6?ryN
sRBAvq܀C]_QP<@7!rQU-Cm!%iI!b@,.vLMW7jՃ9kfcTL  xL7ffdATXW]HQKYTPWQAyUˮLKFȟ4ƫn-V3KĦ:s=O/߾Mmx8y}7uб{uk5L&恁l[VqxXEB@1`p8b,uHG~+{@ !mWxF9t:vvvlooqeoXrۭ{={FGG?`~y@[s q~[[[mkkSnW'BFNrDsOPD688H
rkSUVj0Wj:AAQEA0b>Y
HF*)2d@D@	#HsNn$&=೼1ZքtsN6GrpD֨&XZ5͂c-{0_^i7!
D':<ݦd9mdz
"rNG	Xe3H撊uk=X333Y^qrr5P=55?df9t	ܘ]'&&V
LxN;Fo
F1/CV?~-KDZ&A&ѸwJ6Mặᆸ\PR1o@߀Dogaa|-~#^PeiZcBۀ^Jقъ/B썠mVb;#aqż`u3]x$
k9J0


yr&[yMpV_ZIRrdኸ'\3syt_~;C7Cبemrl!Bd!p4l# V7h(Sp
N0]7
f]\fcTL  xfdATXWKHTQ-K$LZeڦE,AC4V*ƘR\NXr1nlPGM|S:NWUׁ{<}ah}}}'{{{	5;5^opMy9׸@B#!8]D.a^CCCq###Le+Tt*t{{ -zC򱱱4i>
|sT:\Tp-W:gX" }LBR֍ |UO:)\o@s>"T"j9𴌏HgQI ,Fįι&755EDOʖT9-W
p脲m6[,l?2PT\Us ]MHqbb"_>$&X>/9Ps.lLK݄/h+1ݨr
՗X -bi;#[ˏ3d&X<88x9G~WVk(kbNV쵹@3P155uj؆-+b!!=xjrɔ( 3 
|K0![lx"B333SǨA
Jh>0]O᮷ីkkk@2DEPD7Bʊ~iiiv{:.?n >c1lnn>~Z^^ċޥ	/DWϳ@Z,*-XV0e|Q7(lg2?`+BIU@'~ż`e3]~6#6+pL...Kgkkky\[yUpuQ~G. FRrd
'>L\A^yzj@2	Ӊy^QZt,!Lda	:E)	S7(Qp9N$3]OBJ|V+fcTL  xLkUfdATXWKSa,cPʲ.?\ xU0fJ/.6
9e:/LS%Ŝ{y|ڜ:}=3)kdd]sS"6s K=@Gjs.D r)\˚ ^933s9S7#߭b^{Gx<g|󽽽$%{0A]sssG?ȚM^m``:|*rC Zd DO%
=Uϙy=H;G
(	"J.bNMM*.T3B~	c0pCla8
CUh||\IwCsPNV7OXpƨ, ꒜$+&]DЍNb+6vۙsȁʠcA~N0	y:|)sp ۾t&X*6#HE"n1\(Vi#bBx09"696Y}EDXA6J[8%L	 n	pG@ڪ6(x

^?E#Z^^>Nkb^dN"T*r'/#|a?H!b
	 1?4s8QYLE	,_H}D!t&D6wvo___Pȵ^JAT0~RZrkUHRRVZN0	[tmb$nf)0fcTL#xxfdATX͕KhAcDQ(^<衈71<ړ<XkN^V<OVǩaH-4
yCb<i$!ɮ'npcfg&f3~EfESe>oZZL^^F	8&g%
Xf8K&x<~Dh0G+p17==TF,{
Gx~5O	[)Ì	
ϟϢX3c%9bٮhM"8Ep;f}Jq9C1@[WvAg)ţ=2_B_1(}b*鷏2cZLKG%I:n7úԏP6urE+蝅B%(7wM
m}U*l_Ԃ%\?CeUKdXR('-jju}6M>?RW.%(qJ2m6ۀR<yaф6?0K矽nt:PChsF1H6tբZk+>fgП[v+AZ_{	h<9c
m|ԕekfAd2+6y:fcTL#x	fdATX͔KhQSDPɢpх;!viWҍ*)P*JV!b&H*"oN^$?܁!Li99w3܉s`N`ZAtz/	c渦[m@u"
iCxlTFVFg)G|'LR,OUт[UBxX,KOEŋ|m1s
e8N:9{+`5jMR)%x[REZ}Ga̜jS~x;GDfT$Q@l/ӚQ:>ARV#iUqžYi8^0Lc(-;y3
M+rc_35KM>C>dٯxk&1vPJ{T*W/Z1MlMwYpc0;aպl6 Z~6e]oB o-kCb$_VJ<}}^1S-ȰKt0>L4F̶D9Pn{a̜#?@F)f+yNgr,*G&f<nAu[4E0ǵ-/2GA35ͻ7{ \,FVH
Yk[kmfcTL#xϝZfdATX͔KhSAV"EBEAĥ;5viMʝP!>[.D䉛6h4AB$q64M\\;	8sgߜtnO@7&t&iCJ̧&w\glQb>5)8 V9CEīkFaΜ+8
Q/#E"*-gnkCo%EvVq@59s\%X{F霅
2kz93}
z?ovG(V]G;F
>
Vb4Sey8hq/Vb1}<_H$/oɾTJd;qД<N#`ЊJg8|hpE+bC&9j@:Ч8(
'bpߠ\nT*=-~ZR1H*h;O7Z54yZhz>Iq
w"MVjF<|u~4lN{UW(ΙZ.FnjuTU+-v~|J:PW:&lquDԓפ^w6i&s\?/
`jfcTL x fdATXV[HaM;Hit2/,ꮼ*+!*AnB<2	,obQ 
T<ԚoySI
:;;\{t׍FcP"qfkkk|l>钤pnp!%(j4p.WT^-=dAV蟣&%p3p`8ÈIʸ&.yUDNvF"'>r*(䜲!Th9t	`AԨ44Rtj&;Bt'Dr!0%#^`knnWJJ
:4009o`$gZ`%i
dSDЍZRYb!&s<ex#g@E'yf1盱=\:X,uKH:223<<\>88(ߐܵ .:+!BdFupT~x
$	0
 0._̵(*9j
ԊH	 } >LD~
(T;299cii4u7OLLt5 -ccuuW~_T m;;Y *
h4yA@r|[I(t\
kZn3Z:t&T...?(HEuk aCXٻ5;;177s4
@V
5>D#5J!3^y̌s\S44 flLE\M?@M&!dvgyyjLk9::[|`85BD<~J^2BGu_I[ 9mJﴝs\>V'fcTL x$fdAT XVKHTQfI-|.j-b!DAc|\-l@eL/RTJٹpsGс3sι?(^FKKKXkkkpbyyyo63B Oeg;::Lx'744$v	y@1/-}$ (Oե>{E /JT5n?rbDOa=
kZ(B׉$	
CճUUU1xQ+5uBB(s[P!#tPݑWtK=s e"<
.8l;,Kep%QxQWһtb0Wf_Lkx;v|ǜ*8s~Awfec~9k`` zDI$]@-𶻻;vQѢ7*===@Vr#Tb
0r6+17\F-jF0>8z5/188xpރfDxxllhpo~ÑG].O"uZ1	_4_!vxX\Ľ>11qQ'!C0MMM`{|XNQz@'քvv6gyQ&䗈|qyz
0O3fQ̹"i@zx@ j"###@fol_@RI253tN7w(B8At0y;3 +333v\zVG3]$Dl[@t`M0vqHV׸'_Y8|B{ܗ sppo߻$>v39ɽ\`뇸ۍߚ
5ZonfcTL! x#gfdAT"XVKQ$a)QvTH7u'5Sn0\?M+ޤdH8uPcKCƞ'Ngg.=4w|pp@M0D7#
5gŘml5By>]
chsC_0169l3r='ȢG<a00턐x'^%rXVBnY/1|Jj
h3y>S)r=Y	۰v777pZ}mƪUR?]zm}x WS4=nv\_6P&tn4YdqE/v}rV_&rS&c4>>^
4GFFN|C\$
r
!OM>o9c@̋R{Z%Pг	n	 mU?LP+(x"PH$*4V:.fUzx~~D<Ά0y
\L&I;DxWVVNZ: bu `=揘]
feo@qU`x"XX,v4KTrn2[]+`OoRApYv\lZE,*fOLOOg.ޤR(5m>$Xv+o[@9D9~W'	iـ
'	+-d9"1:3 Yb2ϱprlz-H*QؘhCB~`J@'u1DYv h$jܓIڸGd1S8YaTfcTL##
x꭛|fdAT$XՖKhSAo-RATZrE7-uFpࢠh[hɣ%&!y@!<1qhDc?s239g.W~=Za=]t8vjWRiZnW*ċpG&l6,̷	
Ƙ`nr|槆2:1A6sm,K@F>
ػ.Zv0*SW5p]z@`(F"5N&xPnA;W|>+ݖ'9s
B?-x4*`P4|"O00
Codr\IӗE֒n3bCyқJQ4"#?0[3a2%JffGАy6HLbh4zD1Դ0
_B{
{60V'?R"ژټgD1bq&E !jM[fA
=o
=DFt@M5}`0Ȓͱ'_WL?0\@|32`k:9`:
s{29c!~fcTL%#
xc fdAT&X͖IhSQ
u)(](\ FpƅCUҺK^A
!f&HZ7	d0!>I4<'pwOWכZ7l6Cݮzt.+VnзN7(]ź;k뷛fv.cX,+% 6U7SS:RԀ~iD76e3{rh4Q"0u=30P{4S=[/aW,[D"Zwocuhዐ.
mH'zJ.J?!}V<x4@֝`0#M
S.	O:B.0u?@ ,HfYPX,e!F~mԈA`ءm׫|:BzDpXv#c{3
 N1s̒Qc60C:ĩTjd2>%b{ywvѝCly{Љ(>=>6#손ZC=DSGE0"g_DNp4cudEp0ǽ}r;bLcN0]8hώuwfcTL'#x#|fdAT(X͖KhQ+)RUe;]JUABuc-XU^X1AyA"1kI46~͙I7w`5>yv@VhpR*Jt]}hև{_w\Ǻ
RVGRrgZmAx<GQ:s󆐻thW@`u$eэyYnLM0`~LOkG{^w("|~_.;	tEV߿Z|=bqFӴ7B!C|d[~9b/
3|^,b_ѝψ_GΖN8
=%C` ht?;"@>B;&DM`V82g4T*u2zaG0]kՙsFl6cX!KJ$zV9Ks1DBBkBܫfO1G䏴5
!0-dC)3AYt=+Y"7+iVz!O;đ	#k2|a55h4dGVG:Yoam}z'be[a
Y瞹"_kfcTL)xAfdAT*X͖]HQ_SCʼ*+JJȤJn"
i}x5X``0u6Rd:sqxw3M9wE^g2TպCP$kWVV}>_-]^^Ft?RN{.6g2,
8	@O3ݹt9̌Xƒ(kjj:=nqό׀l|A6q?:XF:ñcht_(1:~qBlPj.2+bS,PpR&g~RGVPQ]]er>&+~7825V7)d_0[4T13e[W3p6@
v7GbuM_\\Lc*[a8)\YY݈1;;vfff,sXE<\E7xzz:/Ϲ	N? -H̓ۻאhAccczBI_	tak7Mmrp̪ڦ>dfŵ'MNNR;l7:::CO][[[_,Q 3vȊv$@|s
6R="t]P7z922hgg\7@"),Bw`u`bDJXXX%Å72%`	յ}}}PP%cwʑ-
j%8{t4CF!3c6-5f>`~';b
:@()PT+Pױ\1F=2=&KAOPW21^ˡl.֘V8P\Μ
}4!,6jFP8_f21o3H ^fcTL+xofdAT,X͗KHTQƯSVIѶZ"hcQ*ØJɂV.̨(-*pcX6JRTqt|}u.3c8sΝ=(ƓVBSSSlNs8az
4XD3F9z),sb^Xnmm-Юu\---|A`F,ڨ@.Dpdcn%99'W
7䀰:TD%Ok등|X(cM_^n)P4
q'gt9DohQg
Pm¾8*QذYsnDy̱/XqQ	eO]ѠX抂3b֭4^qaa!aY\q8Vop 
JTCrR\곺mzz:unnBgggo`#:E.e"I;
|1.kXQ-qÁ;hs$ MgfffX6ȇ
';;	b+QCFn---16-P}:h}vddvqo/mkk++y𞱱GGG
BLT*п**lrpr~Dߍo\.W})"<Q[OMME3bvh	Wn:u6UتC>)m2PpX'ښA˵p5"Tx1u_*q"=!LEg?3_բJ	2
899"
685ё.6@XSAYc:V563Z!DB:R~|3B8'?ÇY\1q8
"ڬ4ш313y7sne*6`uZ]DZ$=,X1fcTL-x4fdAT.X͗kQ'JARjvպԊnn\Đ'YKX$mE^nwL'Ƅǽ9N~<Ό pX,=U5p8.>D	ݞj+}H#kn2Or:sfNI:ɦųzKRoՀc˽zaئhcϒhL6cUhD쉚p<?VkV5@OY7 )7P\*3#||>_/9 y%8Հa8zMLa
Q@
pp7S9k@69!6wjǸ1t]}z!ERFLt4r|Pk|^M5@E(VI>tڎd.r96hP(h.y6!k/C#Հiab =TlV=H`2*kީw:wuiNO$	M*k;X,qccÍ~*LYG*:F%a
Z@$z2DN$WFƮ~scW64$hr}
p?ෛzudW̶AyB99>ЇBwb^\GU

<vQX}.GpN6ޖ(cT\ޣP+z|'9rz42!^6sqpM<o!dIENDB`PK
!<Qww5chrome/toolkit/skin/classic/global/media/throbber.pngPNG


IHDR$$acTLSfcTL$$,sIDATX͗_hSWon+'+
AαA/O	(lp{|hQDv `-Ç@/wrv{{4!,3wKB=\֦K~_~sWQHZV3B#k1!3ɶtZӯkxw ⷈxX,!:>!D!4mWUc86S,"bu?]#bj&_PIO3(B.B>Hf_JWtJWz&\j(SLZiD\ݾoZ=*x XZ6Cu@W`T]\hJ}Br\._SӴFsI]ɲ8[(D*
uB!W4Mu}A2D9Ae	S@ijCr"PCpMU3ssso66".!cDu]ڥܨBgMӂprB]*w7:B.(|Q7-!D[6眏s'9\./wW=|jD!qq9rGD3̧470((J@8ά8ݷ\.795P8~rV#ϷUU4MDv)lgqja=,//j!P'F9LfmmI(lkSlk)LnO=LfKѡ(bv'clR:L&D"qǶ/_Jަ0~ei:cl1ֿQÌ{13=HYXXxQ\c')ӫd2Z`0fY˲v4RsjF1h
4o1F-/..O)K)7ؠ?xeYMӜ0MsLڄeY2i(JQJLM3QJGl}b)0nyd)߯bmc'cm~;`˲d?<idnلiZ>`@dyj`dہW$h3eY,v0B#$b݆a7|,lryfcTL$$k_"fdATX͗OhGEڂVb	EHA=x
zG`ۋ
zB]$ewBf#V4̓ p&z؄cw7{4_2~դ$8o'Ix-c8{I:MSN85Ƭ\r8VUhάRVM(Mu[5Mӑpvv])uH)u|vvvԪSJ:](M1t]w}cpp`{J)!RJ9??!Zg%P֟!yI'dOԔr`ϥ?vui>*s,\uNg
v!tLee50JRaEs$Iviief$ٕ;N3~G=@_DJ8Ec̊JRl1Ƭ(zluRJ)̼hLB'Bݵ@]}Ecޞ)|RRJuru.566F]A7(RR81!t<N!:}_h,+Qm54rVzq4dmss+9s=q>;8g/a)={{BBEb7,>֦8,*Y/^&MqG1uτ:8
76r	p]yݷnj8}BEp@X(!-?eWe7-kMZ-UX[.i6koeYcGcҾ}:99c4caRJoPJ=JF>~aK)hҾגc1 cÇۋm


Zgv[Mv?–=YyꟘXo&'ޜz5J(`	!݄^Bȥz	!ݾ7AY>Ϡ<Jscc/`Bb,Zv kXT\ðѣGoKc+@Y1+\Ho-Pv\LA_%\*j;ȶ B.geB鿤
`Sfx	fcTL$$BufdATX͗QhGLjjC	Z["l	"آOڦyl|Q!)(Lb.}(ViH8؝onbhM{,j*BCeg@>M:+5!wk>o|3iMJeRx[V&i`B\B &2LApN+.`ʸBApULh -yq{wqee)᜔2&xTB@J3Lai4O̼(g󇌱c916[H;3Ms/Bh_;3=?e=
s^<FA!qe!ij zTag`Flc|Lqϫ<z˖僑,B	!*u}H
UHy]1c,W.DmjZvK)ۢsЉ.WZc[QJ&1Ɨ0ƣ@q4?4c99`
	Ba\} ˝2rVWRS%P1gMh,\.,GVWWFdr7t꺾+҉bSP6w_uDH)cRiR'tT*ƘRڳI솅R)cpM4MiG)QJg)"G)eˮa11Mkbʽn:
Jm_7eY\eY{C4-'ٶ}rr=u~~u۶/B:3imۿB	!7
öL9suu߬%4um{zIB˲E1];t]-iKm!HeYo*:1l0J[rw]qn:Bf١0#
Qvu]?5>vZӎLB.rrWЯhl>z$E}G0ΡZk0nt:}Ɖt:}lہ.C2q_<ߵ@B]ou5\U_	fcTL$$k8fdATX͖]hWwmVl,ZEG}(mZ>i"$T}V}b_$IjP?^bg;%vLTRj殂PҐܳ60wvٚenkҐ?0w9wcy*|OAc^ZD~A wQӢ
ZZqQk
W#ۘ)N(<~3"3U
&+$avNOO,V.Rd=Z_HD.Tٳg"RJ+dwJ ֏JRK0
]ѻ5r!D
R."fRo
6OO|,
ml
PDӧj)N j DÈ8DT,ŭNDQ8PLC#J|>fHSSSD4jx ]D”EDpW"z$kRID Rj@)Ur@ˈ"&0&!zD܆o4,JѦF_l؅ųn)Y~=f_3c=p4OADq)Q)堔r_,4cl3@\1){YRJ.R~OD
0&1X7-iT*N,vE@Up)
6, 4͎J(8_.=%W%wc%Zq.,0<<rz{{W:0V7r:^dr8?
!:lks9?K@;eYy᫶m-&mǶ˶m_u]F`$9QWuy4R3PmW?Umۗwt`u[A$B}#$1((n0T1s$	8N۶V	!zǹnsyo1QR~nh
dY@t-!q.W]B)͒vXiѴv0Gi\g_\um_T:~޲`KQ}e`?9.J)744Zє{3r4[`eYv:JZ YA:JZ(H,ΨL]zo"X@e163ƎDy9LcfcTL$$P%fdATX͗_hGm#Ċ B/Z_%(i'
)?c'J*"݇sggTn%	w;gCnܹdY'wg4_so4mb휚255u1s^[RX;c!|s>{kR6,:PPheQ=clLiPh]t %y6@ec|l6|>cccoϤ1ιT49p~.[!h-J ̼_̣G>&)TA)׆scsZ2z#JY977'RX,sRp.|rrr<qg&''N
!.+HkrVJ&R{bbb]8s9
ϧ7
!
!e@RJ@|*Q,nPJeRʆB\(ˏϷ	!B̔I!DG-MJɓw&vG/bX=7R:tCܢ&T?(7TQ5%k}ڔkRRu(תx<,KҟBA<{lCf7\WIJZJUJwva%yE&9yL&slttt`a|b;H)cLtu\Ν;.4Bh@4-ݽ{u=p]r
+N/_(]0GB=a.47j+W.
d2߆/ PdFgHE7ۥJ6-c_Ac|L\L&קoue۶W9s.BB)BgsJ)cO0'Bqh,keM	t]rrq	!>۶?>ƸKN8cơ۷oQ5vq)hBN麾VF̋@XjBWr ۶:H&/5J)eL&]כK
BW4
t]@|i	6c`Ul뺾Z@eTq&BH*Qԡ_9zIB0Nm{MŘaLkU4MR6BH8}>qٶm۫"اlT-Z1>`jw8ӔJvR/_xlwKFal=eL=*:
|j2Ms$'HiXtx<"Txo<_@BBߩS4RfcTL	$$kafdAT
X͖AhWgVA6Bx <5ZK@(ڵIt׋BksS/=,!f
ad3^^/od3I|c~Z*#Ãa4ypzZ.)9TEJ9+5sZuRJ
I)JRgRgf]UJ
;5\5pm.<;%8VT>iSJJ&@JZTnD<(:EQG0}xWB<B<}xOY)e5	$a---FD\Fo޼	ם8C|Cq!By{ORsJ&礔O0;7-"ʆ}HQL<w!{xA)eYJYVJY\\|z}"@12O^+ʶZWl^8!"f@x%aݮT*1--E <933&!yw]8(޸8*8-j5-`B{<Vn7֩(~@Ċ)ݝjYZmg/_Aāz{;vaCk*JGKҘ9磏?zmۻ$BW:9T*K-uq]˵@rp	.q˲RV\ukq浂m1vR8	!B9so1ysӧO{h;\$L4;6\6ܡ
M۶{7wV+w@m=8V0)cȇ~\cGcccÎlmK\.o!qؤ9x;3F
!d\.oi)8@-c(X'ɤ48@;o4hX!`fEVJuʶmoOz)Сf@t㡜gflt|4E:.}qC-1Λ(p2l`fIR%LzKByL~&OMMmk	dYgrfca[3'sQ3<cl_[0N
B'if&]7i[,7B8vJ]5`(
 Af+
(lISKf}<lZw8(}LV_C~
3fcTL$$pdfdATX͖_hGb`SЊ^ԗO%OZ)R"CK"p&z'
V@9;ۂm/{D#K Rw9-5ݙh,7M$J}Rjb^9+AAP49*Z|@	`,{A5y/1q )尔AnwKGޟ8,8.8X*DcZ뤔<@٣xO>{lK[09緅?9OJh2kRJI)S<D܈0+aVqᖝ((9!F;WJmQJ=0s<;J푓CO(0[rs~ظ
@yy\.o*I)RLfRJ	'q:drO6BH2ziWeVf˲^:Zm">F'k<@͇T:D
o
ðͯɷ8i!Bf6e<*LZ4"'8mz*?==vK"RSqY*>VCk4"M uDخV\.oN#*ʻZmG}m^5t\k<u/]u<|:[׋z׹>h<Y(>+
]׽ypPy޷|0/^DkT7D^N&\]׽X^+
gSBk^6`<nb<%XkB4sss/?eWrkjC}cBaY:f]<v@eY~ٶ^cm(~Ji
cmmIX,&e!v`?dyW,W7m{4m^'cCvʗf;CzҴmۻZQq%BȠ+>_k,Ӳƒ@4El3kP|@xSlcb}cȶh@mw7rcbeBHo\em4M&:LBzM?Ƶ.2rMD1@e C)M3'-d_3c;[;eC8Ϊ83"۶[vpiKpbl|~d'<g7JV!J)E|(J-GڃRRɭfcTL
$$kfdATX͖OhTGw״JAԃ9D/=b*!=j	JCqM4FhN"‚KlEY	%;{YCf$lޒe<k?v}3oDc֪aY\.#x*04X
N!s!!D!B8|Y&-Hgul6{1y^˗/UQ!StUN!h333MRRgggg5fb}cccs~є9be.)%R
RNJ)O팁yPcr\.wPp0Č\.w⤔7)eAJ9]IԌR꯺f'3a$cZ!D6|s>.-h=J)O)O@	ٚ@.Zދ/:z|/|zSxX,Rz
Ѕ@{P|>YMo޼\J9z]
$JojLOOdz&y^)cm6nVJ2+=ZZc1U7QTr”SJjT+2553d2ubgZ-Vu\k}ї\~7>>NR*ڂ r)Jg748η[SJo{q/faa :rf:y~uq]z%]%D\A,r1㑱)70ܠ^}[ `*Rp%l6h),k;&nH$ղ
lv3!11BH/!Ļ>^q	!]m7%d"eB x@pFѼd2[k
ض}c<d>/	!FʗH$z!۶s[cykx:jY˲vTZ11@٣W2V2J #l\*:P7Rj ۶k%BH\D"QnCm.ӏa+d2ٲ:I(!
T!BHwfǗn7
G9\L)St:%<Wp|xm:ނ:
mL#g
@W̺oX+FGG Afm6@'xДwbPbc7zǶjtfcTL$$,fdATX͗OhTG?T
%ExPIE[֣9$%ŋecb6T"J(!f~}^ۋ˾7Xpf!y3.LNly]cD~0}[Zqۂ UyJ㽴ٶ.A/JRi"Am{k9d|<;&9#059^
}ucr]jsZDT^*&Lko~9)0o10x7X(p/A2.U*V!ĈbJ1%|籕<yin՜1v7޼X,#gS;fbqOM0J)!o5RXJ{̨ro{#˵DTjs~s~ό=ry"/=
d|JJy:B\}uی\ngtֺizzzuSt\.B
\y7Џ+￳lcb]!)z )0O,drrrcwMn;)&=OM!ȳgގP(la{1v1v%ϟ带NH)!!
B|̴6r-Bag.&csssryo\޾X^:A@tضUT*R?(~]\\,..\A3ZmϔR*BE)U5aud2PJ!!D^O	FC!".RUO-Dʨum-o>TJQWRf6kؿ1kٶZnN7@mۛ8͈i(6WD<)ICNqc}!#)#H&0!+8~JPxMF݁x!qyvs?1Zm-mԧZ);x
(tx) #u_(v#b7"~c{lllwmZ9,VD]B:-JͶ"\2
 tzDѭYe!>i#]ѓ/P2śL"X0QL	!輹џkYOtm6]O9L#f%.Up9d2 =Afd2_;P2lA3>L&\~m|ş+m&fcTL$$j-fdATX͗]hWgc6h
R5ji
'>	}6VKMb~ B9wWRΝIC++67](5$vcf/wʰ&]5sϞa,rvy'm_j6B*!D;<9=%h'z@B=BB*T,:CЇt:q݌ílvu1r ]sR}RcB-o	u7]=kec'tC#8S
!0njjjRꊔRjP(*nʚN1:ocG7hQC󁉉P^ԯJBRVJݬJ)qv۶}"cq6;״K)_QJe@)
mcǹTdvcXs48͜fD))FR?/4*<VhxxUL9P?ckdddm$ڊbRjHJ-RޑRdlllm۟m϶~۶/^0I)?jdRtO]ս\:nd28sqc]m~"ƤI)t2RZs62ƚk)bzIƥzb3M-8G !䉽{]ko~1	eY[ffffyy߅1#LjYVi]eYҎ<J}{ݍӥRmRV3+BHr͵xye@w}ߗ333ɕJf6?3yB`իW_XRzJ!GqJIJu9" b)su6]mf;.褔vF1NbTjeYRzJFD\q~~I`V㑍Ҳ8l(S Fi"4@`1 zQ蝅tNB+488RA)R{ QDEFJ3i2#fA٨4.`YoD|"au
#T>(6![S|UDkL&\t]e}h4" ?VL5L@=pxXɒd+$}H&O(~˲uNx
J˲XuD+mfcTL$$w(fdATX͖OhTGߦ
b VC5X{E-HIٸ&QcBb¼,Ń
vf7X6o(;d}dzuu7&|;~cYsZXcsvthre cls~9Z7r Vι9g18s1q9(By_RڜN/6?~A4qλU}U99ZǢ}V,P(L:H餔Bz)g<%QM6=sj9lPTW,!	!n!nLݻw1[)̈́NBiB(=R;zVcQI0<<)966BBd*"ՐRW\B9[	C9I)=y^ueQm.[KdKlc,E+We7}Y!SҞT*RYn-|
5B߄j(


m2#֝ă	!l}[IBoBBRJOUzy0Q!v{!nntt&<o	ty	B	BH;!ʣhhcBo}X,.\RTK&iM^|~QPXs:U\kmC
!RxѣG륔I)寥R]p<uuaA
@A0AAJ)Z*qm(@1_0ZRJRI)GNJ,F4?#T*R S	dF?~<vYۦ6ձb֭!V\xqέCkTkAkjN&1ƻ>c`?{Ǜ0Q)R/0<yؽl'tbf:[0JP)5*kuW߭;A2\8Nww	c}ZeFi]._a`Q8qXQh!ԌjӧRedTlDOj"F10&'&&V)*@ƶuTm{eY1˲,Pݶm3
vH=TZWM)ONN~PȲ,c@@i#ڞdEuZSSSW̌6pr[_om2̛LS6[955p&L{}zf詼%u7rx<;Ixk:9_WJk'	0jfcTL$$jqtOfdATX͗Ah\Ew`KC
ͩx ^z&BV!Eo&Md{Q
J\ovŨФG߼If$/K/y]6[y7HSeBBܰ,~gi;WlRBduǁ	!۶b^qļ;@m	!j`Xm{_J>.RJXYYy>zNZ5iJU,kRkRǹnwK09@Ҍ9p>?B<dƆyR8RRRq0U(DX)ۼ39L)@)}8)Sf
`Y9˲&wwIRPdbqKMb# J4c,]STGP*
]TnRRjв8W8p2ѦIRoR?64J02eމIJ9Z6PK&H)oc0Kq8nZ')xT:j!Z)^|uލ̴Z$M
0Diߴ<ϛT*U*ۃ
9	fΚ?ĴY?g[y޺|u>Z'\16C)]BkHR@<_<ukk?9֠MLIBvxX:t%KEmնr+jg} 8\0XF;;;jz*Vv?0#vOqIkb<i;0,.v l#:I9N9^_RD3e!"Nq(!?rG8矛f N#(M^!fQJs;rB'u0"a￱8E4E)D\BS&u x?LS{]
hlFkj" "Nax=3ȴ@nr~Hמs:8P*،)M<P=P10>4_ō?9fcTL$$֝nfdATX͗AhWg؊ņZ!hj-҃m
6
^ZTjEID<4!ef-$;olImr(Ͳom.߄]-LYwh]{o{iT4=hYU˲FуemmcZu;^:P,;fY=(cGejKb]a`6NMM?&2H$0Ʈٶ=VƮi.N$!!-..^XXx%JBȈiÔҋpxgu
cJ 5vs>,x(BLsO2[a~y,mJѨq۶obnM4D!9	!H8p4)B !䦮]յHeY;;eG"CT*s>9MsO7"Z0A4`ö=m	kҁT*e!h-|Zq}`p-tR7117}!Dx=Bo$o3"!JtHEBIѫ9"Er·M9	;)g	!7MRJ("bkD"E2J?Hj՗z!do+iL&C4Ս8"!|E6$˽'J)'clߐT~#~m7L&qH.{*CJ98S)e"P(4!0\ot]]0x|r"9g;3+<QJ^M(t]o}7~)xRcl6r)ӶZQkL-v|>VX<nw{fOX{\IQ,/YX@(h蠝.U0u<|IGXD,JKaD_bxDiVz3jyID|{w:Ud]j5\. 3jƟ.	|uu@C MkT5Y] nuݿUdxPh|T4"*\=5^(z:\j:2R<zУRvUuV*QD|y:{<x7'6_jsEqfcTL$$jfdATX͗_HGiӀ
6!))	)̓Pl iShN1&P_
"!gTH0!fzrӗ98i|?Mۥٶ}ڶmlxƶkGmx;ٶ}!41~G!۶/;B`F,p0󳳳Ec[*U@4-V輴8̈́d2yq@?yBaYV]B:x(
ڮB	!!뺦&!d$L~Q2
g@W?0e5`{0ƃ1=e5hI)ct\}RRitwu]?V;==}c|!ԋߜ>Sx|2J=q]L&WJ.odFiq5SSS'NDB#!v@ZBy?
!;R2T*uuaB(88%$Æat4uA{j_S0u[T1?ug\!VVV-+eYuV]4;¯_o5)elqqR걪NBH}4]׏NV,HM&wklGť10>|{B)Ry#AJiKVE۫>U?Ly3罠ΧR?={)ii]<v뇪H$SJyly5a6DId>f#>G)>x@.eA+jM;xjnRʺt:}.N7Tq7Wycl1sp·777OǏl698o88co섔y@d238oc+A*=J)r\r˚Nϩ4)nPه .	!L|Tͼ$P 0"'ȩMJc*ƒ@VEQ1bWm~818cl1V
Uj2s!DBa>/^"VWWlll\朷L%"
P>ÙX|oS}m߁
!0EB?(-a,?}Yw_Ҹ7-fcTL$$4(yfdATX͘_hG4jC	Z[lڢOڦy4(M)Xڇ
RC"`wssմKfgՂw7B.;&s	g4?fO6i4M7[#H^4`Fuo"ر@t($
ATt:}tˁF0O8==v&Na.kŕUa:c|qSBB;H@41L&S@I諏{	!l۞$LBlq#+`o?nMbi˦i1dƐicpY}iR\r۶PPR6Cj=TBh!ʁT*u~߲5۶onB0Ƨ	RƲlG6RƂ,:@cxqQ@IBh!tqbb⍆ET8&!
VȶI(EvqƕMaڧQaABsK)urc|ѣGE*dBgԢM .">[%!\Q]#fkifl(,ڝXձZ,6T\J3#WK^E
bmTR:^(z
9jю
1OˏnJ)Y.RJgu\Ji6v1VH)(?QJ	#BA-…w)) Нr|T*]~@2m-jE~[4݊Z:VVV+J[\9{<clu:?91V??+1&w7,ZPx1vs1W09O\iX%;kZWVR>gU*Ì<a1Vt]B"=;!B,<Dh?clN\CAw!
!B*x~R/Uy)d?>p]@C`][[[dMrΟ0ƊJ~	&FRʘbuU!!|}yyKu/pEV}?,
U}O|@ge<Å[|B<S6
!?dKjuZxw|0|fcTL$$jFRfdATX͗]hWgh+hi}>YEIJ` džidcV+~bg]
;w&
U]!w/wenML칿cmm>clgE˶@~;P*I˲zs2Jmw 8j08|tcaYVW.[RtDDsҡ͔ݮnfoCy
$ iv!P:^ڣ끒f;(ǹ8eq.{w[vFԿi˲30Ms5mϲ˲zli6@ 
|-\׽H)=ӒSM޼GOQ@9LV၁
qV{Rz!
D)=8eu5qXGX&d2QJPJ9 u/QJw{!	Bȉk׮Ҵ2Ji88tihhZuN6uBڵ-y'z1_N]^qRt:W/8!!dk}KZ"N)q]Gur/lG_4XNfnV̶ӌ#b̲upΒBqdd?0ƺŽ(\vE{JI~HAo0n2c1fRlNDoma4".a}! 4cEKR~gf\1v1FBw-mQk7
l'B&ɓGĵaS"_#b۟79)B8x5ßN9LqsBBB&:::i}3 "MMM|[JBhprw6-a߿A4W*Je#">wf	!66ݮҮmš3҄BurUJ9zz,,[k7~XF?g2k[Zv)APRJ)jڥk&&&W!ĭѵM0>})"x<D|V@)uN)5.&Rjy+W9w=!ĭR%*ʦca&q/">Rf @Ap)e#ޣG^-[v)+J
*)T.E`ƔRry߼!b)eBJ9׭R&*-Rʔ2AÿfcTL$$^fdAT X͗]hWghU1P`_~!؇BmQD҂OURvU	j'	3s:wPWA*3C|awŐdӗel]mpߜsML`66++.ݦi8Wht,k+$:aY7{Se=w/n2؜dT:}/\xpppK:*NpguK0	!@i K&˫:~-P:*~mmGqPJ:sRK5S{'
أiZD4M68f	0
6MuM4
#|koSUK)5)Uzwޭ.N9@RvoY*q㜮:E)=wʕ
`W= (WYX8jDTR6qm:K)mRDB\xqI J/U%8Ι-
ܾ}{a*SWYYlLeWu$儐=i#OkKҌ1B)\emq+Z麾ֶRR&YJ,Z5XS30>V tTΒס{mp]=}vnմ=@L׉;w|y癞
o"F4˲֛S‮
&,</yy^ݻw?!
W][/P(56y^w%[Sk{Oq3"lr
/f>|^~0ϤI)a| b1
c` 5M{'ëRʢrJJeVc{c uPGGGb9==RR~2Ƙ`25v3
H)crД/^Z@ąBP(#3+nTeV(oaUZb;Ba^z[At6z_JDJYR+&!9	!!;>>Ie/墌T6]H4X,vH) "eRt
9B0Hz+~|~
c<c&c얲Ǐh
:Sa.JQ)eg.[ZCĈ")4*HV###oA3̴"%!DPs~i/HBsTT`sYPhܬs-D\9?Re?w>oTmm]̃^fcTL!$$i.3fdAT"X͗OhWgSA6BB xS5@OZbio5ċB׶J
^=XYy3І0yX^^ii?x~f-kws?3ֽZ|>r"\4|~*`16
OoN&eD8N3
&UYp:=\.o`~q{,L|ٜ0ϳ@0sg3"qxeؠeY9˲,۶w|165ضӸ1r6xH4Qؖdt"BoI۶F̻8}M% <tksRi[Tڦe|uq铘Im+gBDCBB	q,s>8ξNFؐhcvtUDpr5jqZnlɉ9hf)iEZ"BaDLؖe4JBatZZc4zIu=r]|RT*h/8a~J
]7O9X,9
s]m{}`uϹy)j'W$
y۶u
]}yФWkhͥZkEmYkjh&}JUǏaO^KKKDEq8u/W?}G!OBުh&D=%"v|BBB0_ڍF8-8kZW5%9c
!4uBD腲@""z+϶z^wg{V=&M3+8?"hN...*nH)RʪRboѣG;~irZ!='kIaGQGJyO)P)C)(X֋B|-	 XFgBGD4+EѸrNJ[(Oo۾ `n	&)sDt\kZR7RY ԬRfEjG|?#ӎ&M@&;`3)I)RR0o͋ZRQJդRY3>'Kۊ7zҙfcTL#$$,fdAT$X͗]hWgZib`(NjIP"EJD}(}(
kD(AÂfܝBӆΔB%+Ids&0w.,gbӇޕuLYHp½sϹHٶݶotl_lV2\Gl۾WI&9P&eeLf;SpSԒoN7:ef#@t@W\l6ڲ8N{:cl7c@m}.qQ~8Q[8zXm}|Kݱ0_,0aiǶ>˲z-՛YKX	ף.2.rJi6`a}ڼiG?7Ih_ODV"JDrٶvqh+T*aT*1gv8{&k,uSԍ\ؕJoFnڤ>hI∈Cn]Xg-ݔm͔i!RuV)bm\XߋVuU?jR/^8\lv]z"JT՛|erm\׽nrHDtx""V#Dv`%˝w]7y?177B@D/}T@}ތ<oVЊ+يkjMr}TN,+-JeWTp;OOOmbqS#fDG0<6QP(|911qX,Z|Ph}D̄ai>"T*	!!s755^x,"Sjdff&w5DW!8@q&
OIRU4͖r\.o5M~N]y
s>$8@ӈ8Y**H)SR?uR^bq<9hCϟoCD"\kl J A@JC!sG>nd1;;{9"΄aX=A-CJTJAw}(ƥںB9=3?55v4"^=ND>c(!'|R>RޫE
BBGc;ӌRyFKa#,q?5AQ; :FD82I);QJ_l=fcTL%$$i揠fdAT&X͗OhWgZ
QDт ڃZr1
=xHZB4Dkmz ,.+32DpVdfJ([z؝h<ߛ5BhB;{>+N7@BnKM+488x,pY8!L$ea6bvZE>_:[g|~eY_@m-lv˒`00B۶7ENq>8ٶ	c܁
a/`$vFwͲv0Rai.PeYݖeuvCKY.b/$rJiwmss^O:ru47@;NڶZ9JyJTV3B(=ֶ&q蠶L&aL&c|cm`"BaeY!ё0[cGPX>zhzG-I"T
c|c|^c=y)mz2hD]JZv^;Rʿwj>u|cR*%uaaAI)u~0CCC;yMuo8Mq1#eRK)礔auUGR45y^?yu~wV=2
cNJ-Ѓ{qQ 7~0jCVЪk٪jXe>U`|j_)NLBacX=>>m?.%99B}!wBBH'!$wR黜MOOw,(A rg	! ribbⓆ9s>
!+ׯ?K
S.7jgrA
-
!	!"@/8ϫhRRRK)?֧OmƁ!!=	Bjug;M)3n3<.Ƿy(T,5-[1pT*c9?c#vJ>^`B|oo-1vqΩ0Rz11J0rbFbU"S禦NLNNUJcEFcGijgBi/;QJ8P\~L)_6RRi-0ƞT*2MsMTd=me=*JiYqPC[:}lH҇wfcTL'$$p\IfdAT(X͗OhGEm`PZ-Rb4-/zPB3I@{iJH$v{}xB_vϝ|YYWC61
|oYzCQJRJ6M-XlRJh6mYr|>	!p.`>߻@	#]Epttt8P(LuT%;P($vqn"D0Drgmy:q"bt qg!ee,˲l=BH/!lcF.Cd|H4fuڱmx1vrlll[ܶuc}%_d"zuf||u||UkIjf714eBDC3\nNrjD<}IqvU.Ɯ@ZHprkUa6kNy04Zg "1)AĎM-SmIlr-~)Ri!QXti[fffN0-G~7ݾ@h3fs(OfY7㿞?bPJ4hE3>jYVj4aw*CFo-m+dח?ƭ0IH8l6?\DVvFُ_-K啲eWԖ̮}Rˢ1ΦZVx;	5ryKXlmA|',I)RʻJ΅~\K%Or>'ORK)}#Cc)RqsGcc\׽9;7111
RO)q3Pw+ʻia<{s~1vuݟ6ۗ贔r5@Z#^k255yjjj?bŤZ16yޱ9R'fz$,jMZ!Ğ77xO1vuWLA<PJ vR.@!}!-cXTeJR7F(7'\)Djp^!8~AKh]\^9?0|s>b`>IRD>}:SVT;IjzZ^V1yw1v(ud#!Đ^
-)!~52sȶ}IoʶKI˾___fp fcTL)$$imfdAT*X͗]hWgcmVAG}Zj`}'Vk/b_$l6_~ TE%O!vfݑT3iVsoP,Q{vi5YIwWc?{a<l^m>wgV:m7@34tfց&h,+Ш۲YÔD'&&MNN~EQ&	TIf8PeY`8;::T㏉HO'%@m
691VovkceSMD$ai+V˲2ee
iӥ,
ǒ.2ƎV`4P@D5ot\ 3l.EQt@Dѻ	GSKmKOb*q?::Zloo_;kJٶ8ѫQ8
БJabz#P8fYV}&!Q}ODD7ND_QR-#m1qEeѪGm$׫sB`iHz"	xw7tۥ^˲b0̄aG0UAؖm{Mm5Ӣw20 Ñ Ap/0apΝg
4y?i7eٷv^"AZ
N(c=ѹU9
gcԜhݻ+q"s-_0cxxxR
{O)եY#uyyw\.W;mhhh!"^Bۈx+Rj|us],,_UtRەR7P]bk.Sus3oJx+	וQVQ/qk9秵3Oyws,PP؃L
DQJlRR^qb```C<N2%yֲ@CCC+R푑xcBtH)LGcpppv/99px.{,aFXCkB78GJyHJyUJ	*<rŜîכ}"R
A|>J<FD)!)!ĕ$Tiۻs;S\I3`@u<f4yRz!tEH)MӜ7@z!D%ϗ-γ<`fcTL+$$&fdAT,X͗OhWgFZ
b *jkH-hAZ!eϨɃĂ1af~o4<{ڰFQZuf
v7&淿j<efi?ho7V<=iv@t=xnс,=at@;˲-:LYt?pzz0
ebbbyjU玌mfm盥"`PkˀP4l^A9`Irfg)T)W1iz#y00:ԡ7r1059QuT$RʧSSSJ	!!m*
/uhJNڶ*X,VR-s]u݆{%471U|L% )|Q'#tFL<i'ULNN
նr&,/”_A.Au)0ܿYME^x6~)BAF!KB14Jkq"nF`sEX}m{={V}Pj5m0Og@Ęm 2AA  HBØ٤vSV{4Mi\ 
Ob}@nB
u-B!gWeӞ?>xfLsj^(!?.=%ײ%7ԚĮ}TKb1N>(lHϷHPeY5ZU6]y^B\BB=DQJruYYSz.!ywKqBBsr+J9Ji/g()Gt]jv	!!FCAO<}/WRJOgV-fVBB\d2.N7FD|P=㜉)׾š՞CRn*L&eXg*{cY*53_vmGUL&V1$711/
hc\cFGG{15G`z)6T4M{NuW!ąl6L&0LؕHdH)qYJ9uQJĖʧSdك{cn`AywxᭉDD"fg"	բWTO,978e0s1sс.J_U_MRQ9kcq7η޿~UfcTL-$$i_̆gfdAT.X͘MhGWmuq }JZB!mJ.9؅@NN1fM_%Bq+!J!qYԠ`B#l4kR$s.l.6-;7?ޛ};Ѥ!=ϵ4oJ&m0@id2v@lReM
ǩl6@03Lpð7/:"*c@Wt@pwiiU˲mYXXx{O0jm".V*5@C(CmG(#Y=^pfGĿQDA)00M	!eMZ57MKKXunƳH)Pt0#qV'#L&K)~m4Fiq)mN@"_)r\#^$$$kXiVzāaXPJD+NSJ(Sq BȬm}u(@0_21/AMM˲F_k(HR9FgL0)ҏ)c$SJ/ڶ}djz hL)N@i{,\Jr|\.,-JW6eYLR*!ZJ H)WJ[
Cѽ)?33z<`FRc)A\.jMh@iB)e]~*vRi^JF=
u.rRzMm쵏P4ݴv7OaFX<:.B{[BGBEm9C#[M?>nPxQ<B<hQqZd2ٖ;|gڒ#yKq yNAWVVGӎ\gf|Kwygykhc3ƺ166up_08xgqΗtxwX,Έ?2Ԛ~65}Qe\Px=SJ%Ǚ`@8-t#r]wL+4X'Y]9)c\^.ǗR*8cWcW9llIENDB`PK
!<7%7%:chrome/toolkit/skin/classic/global/media/videocontrols.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace url("http://www.w3.org/1999/xhtml");

video > xul|videocontrols,
audio > xul|videocontrols {
  writing-mode: horizontal-tb;
  width: 100%;
  height: 100%;
  display: inline-block;
  overflow: hidden;
}

.controlBar {
  --clickToPlay-width: 48px;
  --clickToPlay-height: var(--clickToPlay-width);
  --playButton-width: 30px;
  --playButton-height: var(--playButton-width);
  --scrubberStack-width: 64px;
  --muteButton-width: 30px;
  --volumeStack-width: 48px;
  --closedCaptionButton-width: 30px;
  --fullscreenButton-width: 30px;

  --positionDurationBox-width: 40px;
  --durationSpan-width: 40px;
  --positionDurationBox-width-long: 60px;
  --durationSpan-width-long: 60px;
}

.controlsContainer [hidden="true"],
.controlBar[hidden] {
  display: none;
}

.controlBar[size="hidden"] {
  display: none;
}

.controlsSpacer[hideCursor] {
  cursor: none;
}

.controlsContainer,
.progressContainer {
  position: relative;
  height: 100%;
}

.stackItem {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
}

.statusOverlay {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgb(80,80,80, .85);
}

.controlsOverlay {
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
}

.controlsSpacerStack {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  justify-content: center;
  align-items: center;
}

.controlsSpacer {
  background-color: rgba(255,255,255,.4);
}

.controlBar {
  position: relative;
  display: flex;
  box-sizing: border-box;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  height: 40px;
  padding: 0 9px;
  background-color: rgba(26,26,26,.8);
}

.playButton,
.muteButton,
.closedCaptionButton,
.fullscreenButton {
  height: 100%;
  min-width: var(--playButton-width);
  min-height: var(--playButton-height);
  padding: 6px;
  border: 0;
  margin: 0;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-origin: content-box;
  background-clip: content-box;
  -moz-context-properties: fill;
  fill: #ffffff;
}

.playButton:hover,
.muteButton:hover,
.closedCaptionButton:hover,
.fullscreenButton:hover {
  fill: #48a0f7;
}

.playButton:hover:active,
.muteButton:hover:active,
.closedCaptionButton:hover:active,
.fullscreenButton:hover:active {
  fill: #2d89e6;
}

.playButton {
  background-image: url(chrome://global/skin/media/pauseButton.svg);
}
.playButton[paused] {
  background-image: url(chrome://global/skin/media/playButton.svg);
}

.muteButton {
  background-image: url(chrome://global/skin/media/audioUnmutedButton.svg);
}
.muteButton[muted] {
  background-image: url(chrome://global/skin/media/audioMutedButton.svg);
}
.muteButton[noAudio],
.muteButton[noAudio]:hover,
.muteButton[noAudio]:hover:active {
  background-image: url(chrome://global/skin/media/audioNoAudioButton.svg);
  fill: white;
}
.muteButton[noAudio] + .volumeStack {
  display: none;
}

.closedCaptionButton {
  background-image: url(chrome://global/skin/media/closedCaptionButton-cc-off.svg);
}
.closedCaptionButton[enabled] {
  background-image: url(chrome://global/skin/media/closedCaptionButton-cc-on.svg);
}

.fullscreenButton {
  background-image: url(chrome://global/skin/media/fullscreenEnterButton.svg);
}
.fullscreenButton[fullscreened] {
  background-image: url(chrome://global/skin/media/fullscreenExitButton.svg);
}

.controlBarSpacer {
  flex-grow: 1;
}

.volumeControl::-moz-range-thumb,
.scrubber::-moz-range-thumb {
  height: 13px;
  width: 13px;
  border: none;
  border-radius: 50%;
  background-color: #ffffff;
  filter: drop-shadow(0px 0px 2px rgba(0,0,0,0.65));
}

.volumeControl::-moz-focus-outer,
.scrubber::-moz-focus-outer {
  border: 0;
}

.progressBackgroundBar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.progressStack {
  position: relative;
  width: 100%;
  height: 5px;
}

.scrubberStack {
  /* minus margin to get basis of required width */
  min-width: calc(var(--scrubberStack-width) - 18px);
  flex-basis: calc(var(--scrubberStack-width) - 18px);
  flex-grow: 2;
  flex-shrink: 0;
  margin: 0 9px;
}

.volumeStack {
  max-width: 60px;
  min-width: var(--volumeStack-width);
  flex-grow: 1;
  flex-shrink: 0;
  margin-right: 6px;
  margin-left: 4px;
}

.bufferBar,
.progressBar,
.scrubber,
.volumeBackground,
.volumeControl {
  bottom: 0;
  left: 0;
  position: absolute;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: 2.5px;
  margin: 0;
  background: none;
  background-color: transparent;
}

.bufferBar,
.volumeBackground {
  background-color: rgba(0,0,0,0.7);
}

.bufferBar::-moz-progress-bar,
.progressBar::-moz-progress-bar,
.volumeBackground::-moz-meter-bar {
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 2.5px;
  background: none;
}

.scrubber:hover::-moz-range-thumb,
.volumeControl:hover::-moz-range-thumb {
  background-color: #48a0f7;
}

.scrubber:active::-moz-range-thumb,
.volumeControl:active::-moz-range-thumb {
  background-color: #2d89e6;
}

.scrubber::-moz-range-track,
.scrubber::-moz-range-progress {
  background-color: transparent;
}

.volumeControl::-moz-range-progress,
.volumeControl::-moz-range-track {
  height: 5px;
  border-radius: 2.5px;
}

.volumeControl::-moz-range-progress {
  background-color: #ffffff;
}

.volumeControl::-moz-range-track {
  background-color: rgba(0,0,0,0.7);
}


.bufferBar::-moz-progress-bar {
  background-color: rgba(255,255,255,0.3);
  border-radius: 2.5px;
}

.progressBar::-moz-progress-bar {
  background-color: #00b6f0;
}

.textTrackList {
  position: absolute;
  right: 5px;
  bottom: 45px;
  max-width: 80%;
  border: 1px solid #000000;
  border-radius: 2.5px;
  padding: 5px 0;
  vertical-align: middle;
  font-size: 12px;
  background-color: #000000;
  opacity: 0.7;
}

.textTrackList > .textTrackItem {
  display: block;
  width: 100%;
  height: 30px;
  padding: 2px 10px;
  border: none;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-align: left;
  text-overflow: ellipsis;
  color: #ffffff;
  background-color: transparent;
}

.textTrackList > .textTrackItem:hover {
  background-color: #444444;
}

.textTrackList > .textTrackItem[on] {
  color: #48a0f7;
}

.positionLabel,
.durationLabel {
  display: none;
}

.positionDurationBox {
  text-align: center;
  padding-inline-start: 1px;
  padding-inline-end: 9px;
  white-space: nowrap;
  font: message-box;
  font-size: 13px;
  font-size-adjust: 0.55;
  color: #ffffff;
}


.duration {
  display: inline-block;
  white-space: pre;
  color: #929292;
}

.statusIcon {
  width: 36px;
  height: 36px;
  margin-bottom: 20px;
}

.statusIcon[type="throbber"] {
  background: url(chrome://global/skin/media/throbber.png) no-repeat center;
}

.statusIcon[type="throbber"][stalled] {
  background: url(chrome://global/skin/media/stalled.png) no-repeat center;
}

.statusIcon[type="error"] {
  min-width: 70px;
  min-height: 60px;
  background: url(chrome://global/skin/media/error.png) no-repeat center;
  background-size: contain;
}

/* Overlay Play button */
.clickToPlay {
  min-width: var(--clickToPlay-width);
  min-height: var(--clickToPlay-height);
  border-radius: 50%;
  background-image: url(chrome://global/skin/media/playButton.svg);
  background-repeat: no-repeat;
  background-position: 54% 50%;
  background-size: 40% 40%;
  background-color: #1a1a1a;
  -moz-context-properties: fill;
  fill: #ffffff;
  opacity: 0.8;
  position: relative;
  top: 20px;
}

.controlsSpacerStack:hover > .clickToPlay,
.clickToPlay:hover {
  opacity: 0.55;
}

.controlsSpacerStack:hover > .clickToPlay[fadeout] {
  opacity: 0;
}

.controlBar[fullscreen-unavailable] .fullscreenButton {
  display: none;
}

/* CSS Transitions */
.clickToPlay {
  transition-property: transform, opacity;
  transition-duration: 400ms, 400ms;
}

.controlsSpacer[fadeout] {
  opacity: 0;
}

.clickToPlay[fadeout] {
  transform: scale(3);
  opacity: 0;
}

.clickToPlay[fadeout][immediate] {
  transition-property: opacity, background-size;
  transition-duration: 0s, 0s;
}
.controlBar:not([immediate]) {
  transition-property: opacity;
  transition-duration: 200ms;
}
.controlBar[fadeout] {
  opacity: 0;
}
.volumeStack:not([immediate]) {
  transition-property: opacity, margin-top;
  transition-duration: 200ms, 200ms;
}
.statusOverlay:not([immediate]) {
  transition-property: opacity;
  transition-duration: 300ms;
  transition-delay: 750ms;
}
.statusOverlay[fadeout] {
  opacity: 0;
}

/* Error description formatting */
.errorLabel {
  padding: 0 10px;
  text-align: center;
  font: message-box;
  font-size: 14px;
  color: #ffffff;
}

.errorLabel {
  display: none;
}

[error="errorAborted"]         > [anonid="errorAborted"],
[error="errorNetwork"]         > [anonid="errorNetwork"],
[error="errorDecode"]          > [anonid="errorDecode"],
[error="errorSrcNotSupported"] > [anonid="errorSrcNotSupported"],
[error="errorNoSource"]        > [anonid="errorNoSource"],
[error="errorGeneric"]         > [anonid="errorGeneric"] {
  display: inline;
}

@media (-moz-windows-default-theme: 0) {
  .controlsSpacer,
  .clickToPlay {
    background-color: transparent;
  }
}
PK
!<$&Dchrome/toolkit/skin/classic/global/menu/shared-menu-check-active.svg<?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">
  <polygon fill="#4a90e2" points="7.1,15.5 0.5,10.8 2.2,8.3 6.3,11.2 13,0.5 15.5,2.2 "/>
</svg>
PK
!<16"}}Cchrome/toolkit/skin/classic/global/menu/shared-menu-check-black.svg<?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 9 9">
  <polygon points="4,9 0,6.2 1,4.7 3.5,6.4 7.5,0 9,1 "/>
</svg>
PK
!<$Cchrome/toolkit/skin/classic/global/menu/shared-menu-check-hover.svg<?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 9 9">
  <polygon fill="#ccc" points="4,9 0,6.2 1,4.7 3.5,6.4 7.5,0 9,1 "/>
</svg>
PK
!<)=chrome/toolkit/skin/classic/global/menu/shared-menu-check.pngPNG


IHDRaIDAT8c`R  4219ŀ5CHy5.E,@E\
B5 ˀ> h	:(a;A:ɐ^$ſ?t:!]H'+ԁ4&'[_t4`m(	IENDB`PK
!<r-yy@chrome/toolkit/skin/classic/global/menu/shared-menu-check@2x.pngPNG


IHDR  szz@IDATXc`F2j@|rf >3GPˑd f&@1-kX,,zX)巀֖#,b+z~	&;8IPrPb \Xb&j%-7#r?fAqTvo%r] ŰC@,E}&/19(a ,>Ԫp,4$5qs 6Bhq=5T3G|81+uG`?AiڅK
	U	WA'A邖l˿&=b(g3=BӠ<#@@5@hgPrL']IENDB`PK
!<+chrome/toolkit/skin/classic/global/menu.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== menu.css =======================================================
  == Styles used by XUL menu-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: menu/menuitem ::::: */

menu,
menuitem,
menucaption {
  -moz-appearance: menuitem;
  -moz-box-align: center;
  color: MenuText;
  font: menu;
  list-style-image: none;
  -moz-image-region: auto;
}

menuitem[default="true"] {
  font-weight: bold;
}

menu[disabled="true"],
menuitem[disabled="true"],
menu[_moz-menuactive="true"][disabled="true"],
menuitem[_moz-menuactive="true"][disabled="true"] {
  color: GrayText;
  text-shadow: none;
}

@media (-moz-windows-classic) {
  menu[disabled="true"],
  menubar > menu[disabled="true"][_moz-menuactive="true"],
  menuitem[disabled="true"] {
    color: ThreeDShadow;
    text-shadow: 1px 1px ThreeDHighlight;
  }
}

menuitem.spell-suggestion {
  font-weight: bold;
}

/* ..... internal content .... */

.menu-accel,
.menu-iconic-accel,
.menu-text,
.menu-iconic-text {
  margin: 0px !important;
  padding: 1px 0px;
  color: inherit;
}

.menu-text {
  padding-inline-start: 1.45em !important;
  -moz-appearance: menuitemtext;
}

.menu-text,
.menu-iconic-text {
  font-weight: inherit;
  margin-inline-start: 2px !important;
  padding-inline-end: 2px;
}

menucaption > .menu-text,
menucaption > .menu-iconic-text {
  font-weight: bold;
  padding-inline-start: 0 !important;
}

.menu-description {
  font-style: italic;
  color: GrayText;
  margin-inline-start: 1ex !important;
}

.menu-accel,
.menu-iconic-accel {
  color: inherit;
  margin-inline-start: 0.74em !important;
  margin-inline-end: 1.35em !important;
}

.menu-iconic-left {
  min-width: 1.45em;
}

.menu-iconic-icon {
  width: 16px;
  height: 16px;
}

menu.menu-iconic > .menu-iconic-left,
menuitem.menuitem-iconic > .menu-iconic-left {
  -moz-appearance: menuimage;
  padding-top: 2px;
}

/* ..... menu arrow box ..... */

.menu-right {
  -moz-appearance: menuarrow;
  margin-inline-end: -2px;
  list-style-image: none;
  min-width: 1.28em;
  padding-top: 1px;
}

/* ::::: menu/menuitems in menubar ::::: */

menubar > menu {
  border: 2px solid transparent;
}

menubar > menu[_moz-menuactive="true"]:not([disabled="true"]) {
  color: -moz-menubarhovertext;
}

menubar > menu[_moz-menuactive="true"][open="true"] {
  border-width: 3px 1px 1px 3px;
}

menubar > menu:-moz-lwtheme {
  -moz-appearance: none;
  border-color: transparent;
}

menubar > menu:-moz-lwtheme:not([disabled="true"]) {
  color: inherit !important;
}

menubar > menu:-moz-lwtheme[_moz-menuactive="true"]:not([disabled="true"]) {
  background-color: Highlight;
  color: HighlightText !important;
  text-shadow: none;
}

@media (-moz-windows-default-theme) {
  menubar > menu:-moz-lwtheme {
    -moz-appearance: menuitem;
  }

  menubar > menu:-moz-lwtheme[_moz-menuactive="true"]:not([disabled="true"]) {
    color: inherit !important;
    text-shadow: inherit;
  }
}

menubar > menu:-moz-window-inactive {
  color: ThreeDShadow;
}

/* ..... internal content .... */

.menubar-left {
  color: inherit;
}

.menubar-text {
  margin: 1px 6px 2px 6px !important;
  color: inherit;
}

/* ::::: menu/menuitems in popups ::::: */

menupopup > menu,
menupopup > menuitem,
menupopup > menucaption {
  max-width: 42em;
}

menu[_moz-menuactive="true"],
menuitem[_moz-menuactive="true"] {
  background-color: -moz-menuhover;
  color: -moz-menuhovertext;
}

/* ::::: menu/menuitems in menulist popups ::::: */

menulist > menupopup > menuitem,
menulist > menupopup > menucaption,
menulist > menupopup > menu {
  -moz-appearance: none !important;
  border: 1px solid transparent;
  padding-inline-start: 5px;
  padding-inline-end: 5px;
  max-width: none;
  font: message-box;
  color: -moz-FieldText;
}

menulist > menupopup > menuitem[_moz-menuactive="true"],
menulist > menupopup > menu[_moz-menuactive="true"] {
  background-color: highlight;
  color: highlighttext;
}

menulist > menupopup > menuitem > .menu-iconic-left,
menulist > menupopup > menucaption > .menu-iconic-left,
menulist > menupopup > menu > .menu-iconic-left {
  display: none;
}

menulist > menupopup > menuitem > label,
menulist > menupopup > menucaption > label,
menulist > menupopup > menu > label {
  padding-top: 0px;
  padding-bottom: 0px;
}

menulist:-moz-focusring > menupopup > menuitem[_moz-menuactive="true"] {
  border: 1px dotted #F5DB95;
}

menulist > menupopup > menuitem[_moz-menuactive="true"][disabled="true"] {
  color: GrayText;
}

menulist > menupopup > menuitem[disabled="true"]:not([_moz-menuactive="true"]):-moz-system-metric(windows-classic) {
  color: GrayText;
  text-shadow: none;
}

menulist > menupopup > :-moz-any(menuitem, menucaption):not(.menuitem-iconic) > .menu-iconic-text {
  margin: 0 !important;
}

/* ::::: checkbox and radio menuitems ::::: */

menuitem[type="checkbox"],
menuitem[checked="true"] {
  -moz-appearance: checkmenuitem;
}
menuitem[type="checkbox"] > .menu-iconic-left,
menuitem[checked="true"] > .menu-iconic-left {
  -moz-appearance: menucheckbox;
  padding-top: 0px;
}

menuitem[type="radio"] {
  -moz-appearance: radiomenuitem;
}
menuitem[type="radio"] > .menu-iconic-left {
  -moz-appearance: menuradio;
  padding-top: 0px;
}

menuitem[type="checkbox"] > .menu-iconic-left > .menu-iconic-icon,
menuitem[checked="true"] > .menu-iconic-left > .menu-iconic-icon,
menuitem[type="radio"] > .menu-iconic-left > .menu-iconic-icon {
  /* prevent .menu-iconic-icon from enforcing a minimal height of 16px (see bug 411064)
     XXXzeniko apply that .menu-iconic-icon rule only for children of .menu-iconic */
  display: none;
}

/* ::::: menuseparator ::::: */

menuseparator {
  -moz-appearance: menuseparator;
  padding: 3px 1px 4px 1px;
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDHighlight;
}

menulist > menupopup > menuseparator {
  padding: 6px 0 5px 0;
  border-top: 1px solid #000000;
  border-bottom: none;
}

/* ::::: autocomplete ::::: */

.autocomplete-history-popup > menuitem {
  max-width: none !important;
  font: message-box;
}
PK
!<aN_t
t
/chrome/toolkit/skin/classic/global/menulist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== menulist.css ===================================================
  == Styles used by the XUL menulist element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* :::::::::: menulist :::::::::: */

menulist {
  -moz-appearance: menulist;
  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;
  background-color: -moz-Field;
  color: -moz-FieldText;
  text-shadow: none;
}

.menulist-label-box {
  -moz-box-align: center;
  -moz-box-pack: center;
  margin: 1px;
  border: 1px solid transparent;
  background-color: transparent;
  color: inherit;
}

.menulist-icon[src] {
  margin: 0px 2px 0px 2px;
}

.menulist-label {
  margin-top: 0 !important;
  margin-inline-end: 0 !important;
  margin-bottom: 0 !important;
  margin-inline-start: 1px !important;
}

.menulist-description {
  font-style: italic;
  color: GrayText;
  margin-inline-start: 1ex !important;
}

/* ..... dropmarker ..... */

menulist[disabled="true"]:hover:active > .menulist-dropmarker {
  -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight;
  -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-left-colors: ThreeDLightShadow ThreeDHighlight;
  padding: 1px;
}

menulist:hover:active > .menulist-dropmarker {
  -moz-border-top-colors: ThreeDShadow ThreeDFace;
  -moz-border-right-colors: ThreeDShadow ThreeDFace;
  -moz-border-bottom-colors: ThreeDShadow ThreeDFace;
  -moz-border-left-colors: ThreeDShadow ThreeDFace;
  padding-top: 2px;
  padding-bottom: 0px;
  padding-inline-start: 2px;
  padding-inline-end: 0px;
}

/* ..... focused state ..... */

menulist:focus:not([open="true"]) > .menulist-label-box {
  background-color: Highlight;
  color: HighlightText;
}

menulist:-moz-focusring:not([open="true"]) > .menulist-label-box {
  border: 1px dotted ThreeDDarkShadow;
}

/* ..... disabled state ..... */

menulist[disabled="true"] {
  background-color: -moz-Dialog;
  color: GrayText;
}

/* ::::: editable menulists ::::: */

.menulist-editable-box {
  padding-top: 3px;
  padding-bottom: 3px;
  padding-inline-start: 2px;
  padding-inline-end: 0px;
}

html|*.menulist-editable-input {
  margin: 0px !important;
  border: none !important;
  padding: 0px !important;
  background: inherit;
  font: inherit;
}

@media (-moz-windows-default-theme) {
  .menulist-label-box {
    background-color: transparent !important;
    color: inherit !important;
  }

  .menulist-label {
    margin-top: -1px !important;
    margin-bottom: -1px !important;
    margin-inline-start: 0 !important;
  }

  .menulist-description {
    margin-inline-start: 1ex !important;
  }

  menulist:not([editable="true"]) > .menulist-dropmarker {
    margin-top: -2px;
    margin-inline-start: 3px;
    margin-inline-end: -3px;
  }

  .menulist-icon {
    margin-top: -1px;
    margin-bottom: -1px;
  }
}
PK
!<Mߡ4chrome/toolkit/skin/classic/global/narrate/arrow.svg<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
  <path d="M6 9L1 4l1-1 4 4 4-4 1 1z" fill="#4C4C4C"/>
</svg>
PK
!<;cb3chrome/toolkit/skin/classic/global/narrate/back.svg<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="M 5 0 C 4.446 0 4 0.446 4 1 L 4 23 C 4 23.554 4.446 24 5 24 L 7 24 C 7.554 24 8 23.554 8 23 L 8 12.404297 C 8.04108 12.509297 8.109944 12.610125 8.203125 12.703125 L 19.296875 23.775391 C 19.495259 23.972391 19.661613 24.039562 19.796875 23.976562 C 19.932137 23.915564 20 23.748516 20 23.478516 L 20 0.52148438 C 20 0.25248437 19.93214 0.084484365 19.796875 0.021484375 C 19.661613 -0.040515625 19.495259 0.02856248 19.296875 0.2265625 L 8.203125 11.298828 C 8.1099445 11.381828 8.04108 11.481703 8 11.595703 L 8 1 C 8 0.446 7.554 0 7 0 L 5 0 z"/>
</svg>

PK
!<3chrome/toolkit/skin/classic/global/narrate/fast.svg<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20.4">
    <path fill="gray" d="M14.42 16.68a.77.77 0 0 0 .54.7l2.51.68a1.58 1.58 0 0 1 1.06 1.22l.05.39-3.89-.53a4.34 4.34 0 0 1-1.74-.72L7.2 14.03a5.79 5.79 0 0 1-5.34-4.88h-.82a1 1 0 0 1-1-1l2.9-3.24a6.16 6.16 0 0 1 4.7-2.39 5.88 5.88 0 0 1 .77.05 5 5 0 0 1 .87.15c3.75 1 6.5 5.84 6.5 5.84a2.27 2.27 0 0 0 1.14.85h.17a1.27 1.27 0 0 0 1.22-.4l.78-1-2.47-1.2c-3.38-1.46-2.46-5.71-2.46-5.71 0-.26.23-.32.42-.14l5.32 5-4.31-4.81a1.39 1.39 0 0 1 .81-1.22l4.17 6.65.33.31 2.19 1.54a2.44 2.44 0 0 1 .92 1.75v2.77l-.16.13a1.66 1.66 0 0 1-1.63.19l-.75-.36a2.57 2.57 0 0 0-2.55.32l-2.18 1.82a4.28 4.28 0 0 1-.89.55 10.18 10.18 0 0 0-4.62-8.46c-.27-.16-.66.31-.47.48a10.52 10.52 0 0 1 3.68 8.5v.48zm8.38-5.42a.49.49 0 1 0-.49-.49.49.49 0 0 0 .49.49zm-18 9.14v-.52a1.39 1.39 0 0 1 .93-1.25s2.7-.66 3.43-1.84l2.06 1.63a25.62 25.62 0 0 1-6.43 2z"/>
</svg>
PK
!</ree6chrome/toolkit/skin/classic/global/narrate/forward.svg<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="m 19,0 c 0.554,0 1,0.446 1,1 l 0,22 c 0,0.554 -0.446,1 -1,1 l -2,0 c -0.554,0 -1,-0.446 -1,-1 l 0,-10.595703 c -0.04108,0.105 -0.109944,0.205828 -0.203125,0.298828 L 4.703125,23.775391 c -0.198384,0.197 -0.364738,0.264171 -0.5,0.201171 C 4.067863,23.915564 4,23.748516 4,23.478516 L 4,0.52148438 c 0,-0.26900001 0.06786,-0.43700001 0.203125,-0.5 0.135262,-0.062 0.301616,0.0070781 0.5,0.20507812 l 11.09375,11.0722655 c 0.09318,0.083 0.162045,0.182875 0.203125,0.296875 L 16,1 c 0,-0.554 0.446,-1 1,-1 l 2,0 z"/>
</svg>

PK
!<CEE3chrome/toolkit/skin/classic/global/narrate/slow.svg<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <g fill="gray">
        <path d="M1.684,13.486c-0.209,0-0.404-0.132-0.474-0.341c-0.528-1.58-0.23-5.767,4.097-7.921 c1.315-0.656,2.589-0.988,3.787-0.988c3.237,0,5.096,2.341,5.99,3.465c0.158,0.199,0.181,0.533,0,0.713 c-0.793,0.794-1.852,1.542-3.231,2.286c-2.46,1.327-5.045,1.775-7.121,2.134c-1.123,0.194-2.093,0.361-2.89,0.627 C1.789,13.479,1.735,13.486,1.684,13.486L1.684,13.486z"/>
        <path d="M23.185,5.465c-0.86-1.121-2.074-1.819-3.168-1.819c-0.641,0-1.556,0.23-2.273,1.328 c-0.374,0.571-0.577,1.161-0.773,1.73c-0.512,1.482-1.041,3.016-4.662,4.969c-2.316,1.249-4.707,1.664-6.815,2.03 c-2.524,0.438-4.704,0.814-5.455,2.622c-0.069,0.165-0.045,0.354,0.062,0.495c0.107,0.143,0.281,0.217,0.46,0.193 c0.667-0.081,1.533,0.041,2.434,0.217c-0.122,0.146-0.261,0.286-0.391,0.418c-0.38,0.385-0.774,0.783-0.657,1.292 c0.108,0.474,0.604,0.699,0.966,0.828c0.399,0.142,0.843,0.217,1.283,0.217c1.241,0,2.216-0.579,2.649-1.539 c1.704,0.287,3.487,0.313,5.043,0.313l1.639-0.006c0.066,0.056,0.178,0.166,0.264,0.25c0.504,0.506,1.348,1.351,2.721,1.351 c0.129,0,0.264-0.008,0.416-0.026c0.687-0.102,1.351-0.267,1.574-0.787c0.227-0.528-0.123-1.023-0.526-1.597 c-0.481-0.685-1.08-1.532-0.998-2.652c0.196-0.397,0.368-0.824,0.546-1.267c0.479-1.19,0.975-2.421,2.12-3.513 c0.431,0.343,1.022,0.549,1.63,0.549l0,0c0.439,0,0.876-0.102,1.295-0.3c0.624-0.293,1.104-0.967,1.316-1.847 C24.175,7.707,23.914,6.418,23.185,5.465L23.185,5.465z M20.397,7.757c-0.276,0-0.5-0.224-0.5-0.5s0.224-0.5,0.5-0.5 c0.275,0,0.5,0.224,0.5,0.5S20.674,7.757,20.397,7.757z"/>
    </g>
</svg>
PK
!<
!""4chrome/toolkit/skin/classic/global/narrate/start.svg<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path d="M21.64 12.44L2.827 22.895c-.217.123-.403.137-.56.042-.155-.094-.233-.264-.233-.51V1.572c0-.244.08-.414.233-.51.157-.093.343-.08.56.044L21.642 11.56c.217.124.326.27.326.44 0 .17-.11.316-.327.44z" fill="gray"/>
</svg>
PK
!<^93chrome/toolkit/skin/classic/global/narrate/stop.svg<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <rect ry="1" rx="1" y="2" x="2" height="20" width="20" fill="gray"/>
</svg>
PK
!<vK}.chrome/toolkit/skin/classic/global/narrate.css.narrating {
  position: relative;
  z-index: 1;
}

body.light .narrating {
  background-color: #ffc;
}

body.sepia .narrating {
  background-color: #e0d7c5;
}

body.dark .narrating {
  background-color: #242424;
}

.narrate-word-highlight {
  position: absolute;
  display: none;
  transform: translate(-50%, calc(-50% - 2px));
  z-index: -1;
  border-bottom-style: solid;
  border-bottom-width: 7px;
  transition: left 0.1s ease, width 0.1s ease;
}

.narrating > .narrate-word-highlight {
  display: inline-block;
}

.narrate-word-highlight.newline {
  transition: none;
}

body.light .narrate-word-highlight {
  border-bottom-color: #ffe087;
}

body.sepia .narrate-word-highlight {
  border-bottom-color: #bdb5a5;
}

body.dark .narrate-word-highlight {
  border-bottom-color: #6f6f6f;
}
PK
!<ޛh))6chrome/toolkit/skin/classic/global/narrateControls.css:scope {
  --border-color: #e5e5e5;
}

#narrate-toggle > svg {
  display: block;
  margin: 0 8px;
}

.dropdown-popup button {
  background-color: transparent;
}

.dropdown-popup button:hover:not(:disabled) {
  background-color: #eaeaea;
}

.narrate-row {
  display: flex;
  align-items: center;
  min-height: 40px;
  box-sizing: border-box;
}

.narrate-row:not(:first-child) {
  border-top: 1px solid var(--border-color);
}

/* Control buttons */

#narrate-control > button {
  background-size: 24px 24px;
  background-repeat: no-repeat;
  background-position: center center;
  height: 64px;
  width: 100px;
  border: none;
  color: #666;
  box-sizing: border-box;
}

#narrate-control > button:not(:first-child) {
  border-left: 1px solid var(--border-color);
}

#narrate-skip-previous {
  border-top-left-radius: 3px;
  background-image: url("chrome://global/skin/narrate/back.svg");
  -moz-context-properties: fill;
  fill: rgb(128 128 128);
}

#narrate-skip-next {
  border-top-right-radius: 3px;
  background-image: url("chrome://global/skin/narrate/forward.svg");
  -moz-context-properties: fill;
  fill: rgb(128 128 128);
}

#narrate-skip-previous:disabled,
#narrate-skip-next:disabled {
  fill: rgb(128 128 128 / 50%);
}

#narrate-start-stop {
  background-image: url("chrome://global/skin/narrate/start.svg");
}

#narrate-dropdown.speaking #narrate-start-stop {
  background-image: url("chrome://global/skin/narrate/stop.svg");
}

/* Rate control */

#narrate-rate::before, #narrate-rate::after {
  content: '';
  width: 48px;
  height: 40px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 24px auto;
}

#narrate-rate::before {
  background-image: url("chrome://global/skin/narrate/slow.svg");
}

#narrate-rate::after {
  background-image: url("chrome://global/skin/narrate/fast.svg");
}

#narrate-rate-input {
  margin: 0 1px;
  flex-grow: 1;
}

#narrate-rate-input::-moz-range-track {
  background-color: #979797;
  height: 2px;
}

#narrate-rate-input::-moz-range-progress {
  background-color: #2EA3FF;
  height: 2px;
}

#narrate-rate-input::-moz-range-thumb {
  background-color: #808080;
  height: 16px;
  width: 16px;
  border-radius: 8px;
  border-width: 0;
}

#narrate-rate-input:active::-moz-range-thumb {
  background-color: #2EA3FF;
}

/* Voice selection */

.voiceselect {
  width: 100%;
}

.voiceselect > button.select-toggle,
.voiceselect > .options > button.option {
  -moz-appearance: none;
  border: none;
  width: 100%;
  min-height: 40px;
}

.voiceselect.open > button.select-toggle {
  border-bottom: 1px solid var(--border-color);
}

.voiceselect > button.select-toggle::after {
  content: '';
  background-image: url("chrome://global/skin/narrate/arrow.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: 12px 12px;
  display: inline-block;
  width: 1.5em;
  height: 1em;
  vertical-align: middle;
}

.voiceselect > .options > button.option:not(:first-child) {
  border-top: 1px solid var(--border-color);
}

.voiceselect > .options > button.option  {
  box-sizing: border-box;
}

.voiceselect > .options:not(.hovering) > button.option:focus {
  background-color: #eaeaea;
}

.voiceselect > .options:not(.hovering) > button.option:hover:not(:focus) {
  background-color: transparent;
}

.voiceselect > .options > button.option::-moz-focus-inner {
  outline: none;
  border: 0;
}

.voiceselect > .options {
  display: none;
  overflow-y: auto;
}

.voiceselect.open > .options {
  display: block;
}

.current-voice {
  color: #7f7f7f;
}

.voiceselect:not(.open) > button,
.option:last-child {
  border-radius: 0 0 3px 3px;
}
PK
!<tɟW
W
/chrome/toolkit/skin/classic/global/netError.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 *  This defines the look-and-feel styling of the error pages.
 *  (see: netError.xhtml)
 *
 *  Original styling by William Price <bugzilla@mob.rice.edu>
 *  Updated by: Steven Garrity <steven@silverorange.com>
 *              Henrik Skupin  <mozilla@hskupin.info>
 */

html {
  background: -moz-Dialog;
}

body {
  margin: 0;
  padding: 0 1em;
  color: -moz-FieldText;
  font: message-box;
}

h1 {
  margin: 0 0 .6em 0;
  border-bottom: 1px solid ThreeDLightShadow;
  font-size: 160%;
}

ul, ol {
  margin: 0;
  margin-inline-start: 1.5em;
  padding: 0;
}

ul > li, ol > li {
  margin-bottom: .5em;
}

ul {
  list-style: square;
}

#errorPageContainer {
  position: relative;
  min-width: 13em;
  max-width: 52em;
  margin: 4em auto;
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  padding: 3em;
  padding-inline-start: 30px;
  background: url("chrome://global/skin/icons/warning-large.png") left 0 no-repeat -moz-Field;
  background-origin: content-box;
}

#errorPageContainer.certerror {
  background-image: url("chrome://global/skin/icons/sslWarning.png");
}

#errorPageContainer:dir(rtl) {
  background-position: right 0;
}

#errorTitle {
  margin-inline-start: 80px;
}

#errorLongContent {
  margin-inline-start: 80px;
}

#errorShortDesc > p {
  overflow: auto;
  border-bottom: 1px solid ThreeDLightShadow;
  padding-bottom: 1em;
  font-size: 130%;
  white-space: pre-wrap;
}

#errorLongDesc {
  padding-inline-end: 3em;
  font-size: 110%;
}

#errorLongDesc > p {
}

#errorTryAgain {
  margin-top: 2em;
  margin-inline-start: 80px;
}

#brand {
  position: absolute;
  right: 0;
  bottom: -1.5em;
  margin-inline-end: 10px;
  opacity: .4;
}

#brand:dir(rtl) {
  right: auto;
  left: 0;
}

#brand > p {
  margin: 0;
}

#errorContainer {
  display: none;
}

#securityOverrideDiv {
  padding-top: 10px;
}

#securityOverrideContent {
  background-color: InfoBackground;
  color: InfoText;
  padding: 10px;
  border-radius: 10px;
}

/* Custom styling for 'blacklist' error class */
:root.blacklist #errorTitle, :root.blacklist #errorLongContent,
:root.blacklist #errorShortDesc, :root.blacklist #errorLongDesc,
:root.blacklist a {
  background-color: #722; /* Dark red */
  color: white;
}

:root.blacklist #errorPageContainer {
  background-image: url("chrome://global/skin/icons/blacklist_large.png");
  background-color: #722;
}

:root.blacklist {
  background: #333;
}

:root.blacklist #errorTryAgain {
  display: none;
}
PK
!<|yy3chrome/toolkit/skin/classic/global/notification.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

notification {
  color: InfoText;
  background-color: InfoBackground;
  text-shadow: none;
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDShadow;
}

notificationbox[notificationside="top"] > notification {
  border-top-style: none;
}

notificationbox[notificationside="bottom"] > notification {
  border-bottom-style: none;
}

notification[type="info"] {
  color: -moz-DialogText;
  background-color: -moz-Dialog;
}

notification[type="critical"] {
  color: white;
  background-image: linear-gradient(rgb(212,0,0), rgb(152,0,0));
}

.messageText > .text-link {
  color: inherit !important;
  text-decoration: underline;
}

.messageImage {
  width: 16px;
  height: 16px;
  margin-inline-start: 6px;
  margin-inline-end: 1px;
}

/* Default icons for notifications */

.messageImage[type="info"] {
  list-style-image: url("chrome://global/skin/icons/information-16.png");
}

.messageImage[type="warning"] {
  list-style-image: url("chrome://global/skin/icons/warning-16.png");
}

.messageImage[type="critical"] {
  list-style-image: url("chrome://global/skin/icons/error-16.png");
}

.messageCloseButton {
  -moz-appearance: none;
  padding: 4px 2px;
  border: none !important;
}

.messageCloseButton > .toolbarbutton-icon {
  margin-inline-end: 5px;
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.popup-notification-body-container {
  padding: var(--arrowpanel-padding);
}

.popup-notification-icon {
  width: 32px;
  height: 32px;
  margin-inline-end: var(--arrowpanel-padding);
}

.popup-notification-body {
  width: 25em;
}

.popup-notification-closebutton {
  margin-inline-end: -8px;
  margin-top: -8px;
}

.popup-notification-origin:not([value]),
.popup-notification-learnmore-link:not([href]) {
  display: none;
}

.popup-notification-origin {
  margin-bottom: .3em !important;
}

.popup-notification-learnmore-link {
  margin-top: .5em !important;
}

.popup-notification-button-container {
  background-color: var(--arrowpanel-dimmed);
  display: flex;
}

.popup-notification-button-container > toolbarseparator {
  -moz-appearance: none;
  border: 0;
  border-left: 1px solid var(--panel-separator-color);
  margin: 7px 0 7px;
  min-width: 0;
}

.popup-notification-button-container:hover > toolbarseparator {
  margin: 0;
}

.popup-notification-button {
  flex: 1;
  -moz-appearance: none;
  background-color: transparent;
  color: inherit;
  margin: 0;
  padding: 0;
  min-width: 0;
  min-height: 41px;
  border: none;
  border-top: 1px solid var(--panel-separator-color);
}

.popup-notification-button:hover:not([disabled]) {
  background-color: var(--arrowpanel-dimmed);
}

.popup-notification-button:hover:active:not([disabled]) {
  background-color: var(--arrowpanel-dimmed-further);
  box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}

.popup-notification-button[disabled] {
  background-color: var(--arrowpanel-dimmed-further);
  color: graytext;
}

.popup-notification-button[default] {
  flex: 0 50%;
}

.popup-notification-button[default][highlight="true"]:not([disabled]) {
  background-color: #0996f8;
  color: white;
}

.popup-notification-button[default][highlight="true"]:hover:not([disabled]) {
  background-color: #0675d3;
}

.popup-notification-button[default][highlight="true"]:hover:active:not([disabled]) {
  background-color: #0568ba;
}

.popup-notification-button[anonid="secondarybutton"][hidden="true"] ~ .popup-notification-button[default] {
  flex: 1;
}

.popup-notification-button > .button-box {
  padding: 0;
}

.popup-notification-dropmarker {
  flex: none;
  padding: 0 15px;
}

.popup-notification-dropmarker > .button-box > hbox {
  display: none;
}

.popup-notification-dropmarker > .button-box > .button-menu-dropmarker {
  /* This is to override the linux !important */
  -moz-appearance: none !important;
  display: -moz-box;
  padding: 0;
  margin: 0;
}

.popup-notification-dropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
  width: 16px;
  height: 16px;
  list-style-image: url(chrome://global/skin/icons/menubutton-dropmarker.svg);
  -moz-context-properties: fill;
  fill: currentColor;
}

/* Override default icon size which is too small for this dropdown */
.popup-notification-dropmarker > .button-box > .button-menu-dropmarker {
  width: 16px;
  height: 16px;
}

@media (-moz-windows-default-theme) {
  .popup-notification-warning {
    color: #aa1b08;
  }
}

/* Swap the default and secondary action, because Windows
 * platform conventions put the default action on the left. */
.popup-notification-button[default] {
  order: -1;
}
PK
!<b@70chrome/toolkit/skin/classic/global/numberbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== numberbox.css ==================================================
  == Styles used by the XUL textbox type="number" element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

textbox[type="number"] {
  padding: 0 !important;
  cursor: default;
}

html|*.numberbox-input {
  text-align: right;
}

.numberbox-input-box {
  -moz-box-align: center;
}

PK
!<g552chrome/toolkit/skin/classic/global/passwordmgr.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

.contentPane {
  margin: 9px 8px 5px 8px;
}

.actionButtons {
  margin: 0px 3px 6px 3px !important;
}

treechildren::-moz-tree-image(siteCol) {
  list-style-image: url(chrome://mozapps/skin/places/defaultFavicon.svg);
  width: 16px;
  height: 16px;
  margin-inline-end: 5px;
}
PK
!<5u

,chrome/toolkit/skin/classic/global/popup.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: menupopup ::::: */

menupopup,
panel {
  border: 3px solid transparent;
  -moz-border-top-colors   : ThreeDLightShadow ThreeDHighlight ThreeDFace;
  -moz-border-left-colors  : ThreeDLightShadow ThreeDHighlight ThreeDFace;
  -moz-border-right-colors : ThreeDDarkShadow  ThreeDShadow    ThreeDFace;
  -moz-border-bottom-colors: ThreeDDarkShadow  ThreeDShadow    ThreeDFace;
  padding: 0px;
  min-width: 1px;
  background: Menu;
  color: MenuText;
}

menupopup {
  -moz-appearance: menupopup;
}

menupopup > menu > menupopup {
 /* align submenus */
  margin-inline-start: -3px;
  margin-top: -3px;
}

panel[type="arrow"] {
  -moz-appearance: none;
  background: transparent;
  border: none;
}

panel[type="arrow"][side="top"],
panel[type="arrow"][side="bottom"] {
  margin-left: -20px;
  margin-right: -20px;
}

panel[type="arrow"][side="left"],
panel[type="arrow"][side="right"] {
  margin-top: -20px;
  margin-bottom: -20px;
}

.panel-arrowcontent {
  padding: var(--arrowpanel-padding);
  color: var(--arrowpanel-color);
  background: var(--arrowpanel-background);
  background-clip: padding-box;
  border: 1px solid var(--arrowpanel-border-color);
  box-shadow: 0 0 4px hsla(0,0%,0%,.2);
  margin: 4px;
}

@media (-moz-os-version: windows-win7) {
.panel-arrowcontent {
  border-radius: 4px;
}
}

.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
  list-style-image: var(--panel-arrow-image-vertical,
                        url("chrome://global/skin/arrow/panelarrow-vertical-themed.svg"));
  position: relative;
  margin-left: 10px;
  margin-right: 10px;
}

.panel-arrow[side="top"] {
  margin-bottom: -5px;
}

.panel-arrow[side="bottom"] {
  transform: scaleY(-1);
  margin-top: -5px;
}

.panel-arrow[side="left"],
.panel-arrow[side="right"] {
  list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal-themed.svg");
  position: relative;
  margin-top: 10px;
  margin-bottom: 10px;
}

.panel-arrow[side="left"] {
  margin-right: -5px;
}

.panel-arrow[side="right"] {
  transform: scaleX(-1);
  margin-left: -5px;
}

@media (-moz-windows-default-theme) {
  .panel-arrowcontent {
    --arrowpanel-border-color: hsla(210,4%,10%,.2);
    box-shadow: 0 0 4px hsla(210,4%,10%,.2);
  }

  .panel-arrow[side="top"],
  .panel-arrow[side="bottom"] {
    list-style-image: var(--panel-arrow-image-vertical,
                          url("chrome://global/skin/arrow/panelarrow-vertical.svg"));
  }

  .panel-arrow[side="left"],
  .panel-arrow[side="right"] {
    list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.svg");
  }
}

/* ::::: tooltip ::::: */

tooltip {
  -moz-appearance: tooltip;
  margin-top: 21px;
  border: 1px solid InfoText;
  padding: 2px 3px;
  max-width: 40em;
  background-color: InfoBackground;
  color: InfoText;
  font: message-box;
}

tooltip[titletip="true"] {
 /* See bug 32157 comment 128
  * margin: -2px 0px 0px -3px;
  */
  max-width: none;
}

/* rules for popups associated with menulists */

menulist > menupopup {
  -moz-appearance: none;
  border-width: 1px;
  -moz-border-top-colors: -moz-FieldText;
  -moz-border-right-colors: -moz-FieldText;
  -moz-border-bottom-colors: -moz-FieldText;
  -moz-border-left-colors: -moz-FieldText;
  padding: 0px;
  min-width: 0px;
  background-color: -moz-Field;
}
PK
!<r12chrome/toolkit/skin/classic/global/preferences.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== preferences.css =====================================================
  == Styles used by the XUL prefwindow element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: dialog ::::: */

prefwindow {
  padding: 0px;
}

prefpane {
  padding-top: 8px;
  padding-bottom: 10px;
  padding-inline-start: 8px;
  padding-inline-end: 10px;
}

prefwindow[type="child"] {
  padding-top: 8px;
  padding-bottom: 10px;
  padding-inline-start: 8px;
  padding-inline-end: 10px;
}

prefwindow[type="child"] > prefpane {
  padding: 0px;
}

.prefWindow-dlgbuttons {
  padding-bottom: 10px;
  padding-inline-start: 8px;
  padding-inline-end: 10px;
}

prefwindow[type="child"] .prefWindow-dlgbuttons {
  padding: 0px;
}

radio[pane] {
  -moz-appearance: none;
  margin: 0px 1px 0px 1px;
  padding: 1px 3px 1px 3px;
  min-width: 4.5em;
}

.paneSelector {
  border-bottom: 2px groove ThreeDFace;
  margin: 0px;
  padding-inline-start: 10px;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

.paneButtonIcon {
  width: 32px;
  height: 32px;
}

radio[pane]:hover {
  background-color: #E0E8F6;
  color: black;
  -moz-appearance: none;
}

radio[pane][selected="true"] {
  background-color: #C1D2EE;
  color: black; 
  -moz-appearance: none;
}

PK
!<_5chrome/toolkit/skin/classic/global/printPageSetup.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.portrait-page {
  list-style-image: url("chrome://global/skin/icons/Portrait.png");
}

.landscape-page {
  list-style-image: url("chrome://global/skin/icons/Landscape.png");
}
PK
!<'-\\3chrome/toolkit/skin/classic/global/printPreview.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.navigate-button {
  min-width: 1.9em;
}

.navigate-button > .toolbarbutton-icon {
  display: none;
}

.toolbar-portrait-page {
  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
  -moz-image-region: rect(0px 16px 16px 0px);
}

.toolbar-landscape-page {
  list-style-image: url("chrome://global/skin/icons/Print-preview.png");
  -moz-image-region: rect(0px 32px 16px 16px);
}
PK
!<TǴII4chrome/toolkit/skin/classic/global/progressmeter.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== progressmeter.css ==============================================
  == Styles used by the XUL progressmeter element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: progressmeter ::::: */

progressmeter {
  -moz-appearance: progressbar;
  margin: 2px 4px;
  border: 2px solid;
  -moz-border-top-colors: ThreeDShadow -moz-Dialog;
  -moz-border-right-colors: ThreeDHighlight -moz-Dialog;
  -moz-border-bottom-colors: ThreeDHighlight -moz-Dialog;
  -moz-border-left-colors: ThreeDShadow -moz-Dialog;
  background-color: -moz-Dialog;
  min-width: 128px;
  min-height: 15px;
}

.progress-bar 
{
  -moz-appearance: progresschunk;
  min-width: 0px;
  background-color: ThreeDShadow;
}

/* ::::: statusbar progressmeter ::::: */

.progressmeter-statusbar {
  margin: 0;
  border: 0px;
  -moz-border-top-colors: ThreeDHighlight -moz-Dialog;
  -moz-border-right-colors: ThreeDShadow -moz-Dialog;
  -moz-border-bottom-colors: ThreeDShadow -moz-Dialog;
  -moz-border-left-colors: ThreeDHighlight -moz-Dialog;
  min-width: 96px;
}
PK
!<J..<chrome/toolkit/skin/classic/global/radio/radio-check-dis.gifGIF89a!,`Q;PK
!<N..8chrome/toolkit/skin/classic/global/radio/radio-check.gifGIF89a!,`Q;PK
!<v`		,chrome/toolkit/skin/classic/global/radio.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== radio.css ===================================================
  == Styles used by the XUL radio element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: radio ::::: */

radio {
  -moz-appearance: radio-container;
  -moz-box-align: center;
  margin: 2px 4px;
  padding-top: 1px;
  padding-bottom: 1px;
  padding-inline-start: 4px;
  padding-inline-end: 2px;
}

.radio-label-box {
  margin-inline-start: 2px;
  border: 1px solid transparent;
  padding-top: 0px;
  padding-bottom: 1px;
  padding-inline-start: 1px;
  padding-inline-end: 0px;
}

.radio-icon {
  margin-inline-end: 2px;
}

.radio-label {
  margin: 0 !important;
}

/* ..... focused state ..... */

radio[focused="true"] > .radio-label-box {
  border: 1px dotted ThreeDDarkShadow;
}

/* ..... disabled state ..... */

radio[disabled="true"] > .radio-check-box1 {
  background-color: -moz-Dialog;
}

radio[disabled="true"] {
  color: GrayText;
}

radio[disabled="true"]:-moz-system-metric(windows-classic) {
  color: ThreeDShadow;
  text-shadow: 1px 1px ThreeDHighlight;
}

/* ::::: checkmark image ::::: */

.radio-check-box1 {
  -moz-appearance: radio;
  margin: 1px 0px;
  border-top: 1px solid ThreeDShadow;
  border-right: 1px solid ThreeDHighlight;
  border-bottom: 1px solid ThreeDHighlight;
  border-left: 1px solid ThreeDShadow;
  border-radius: 50%;
  width: 12px;
  height: 12px;
  background-color: -moz-Field;
}

.radio-check-box2 {
  border-top: 1px solid ThreeDDarkShadow;
  border-right: 1px solid ThreeDLightShadow;
  border-bottom: 1px solid ThreeDLightShadow;
  border-left: 1px solid ThreeDDarkShadow;
  border-radius: 50%;
  padding: 2px;
  width: 4px;
  height: 4px;
  list-style-image: none;
}

radio:hover:active > .radio-check-box1 {
  background-color: -moz-Dialog;
}

/* ..... selected state ..... */

radio[selected="true"] > .radio-check-box1 > .radio-check-box2 {
  list-style-image: url("chrome://global/skin/radio/radio-check.gif");
}

radio[selected="true"][disabled="true"] > .radio-check-box1 > .radio-check-box2 {
  list-style-image: url("chrome://global/skin/radio/radio-check-dis.gif") !important
}
PK
!<`<chrome/toolkit/skin/classic/global/reader/RM-Close-24x24.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="context-fill" d="M19,22H6a2,2,0,0,1-2-2V18l2,2H18a1,1,0,0,0,1-1V5a1,1,0,0,0-1-1H6L4,6V4A2,2,0,0,1,6,2H19a2,2,0,0,1,2,2V20A2,2,0,0,1,19,22Zm-6-9H5.4l4.2,4.154L8.186,18.631,1.567,12.017,8.021,5.411,9.5,6.95,5.424,11H13v2Z"/>
</svg>

PK
!<&Jchrome/toolkit/skin/classic/global/reader/RM-Content-Width-Minus-42x16.svg<?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="42"
     height="16"
     viewBox="0 0 42 16"
     fill="#808080">

  <path d="M14.5,7 L8.75,1.25 L10,-1.91791433e-15 L18,8 L17.375,8.625 L10,16 L8.75,14.75 L14.5,9 L1.13686838e-13,9 L1.13686838e-13,7 L14.5,7 Z"/>
  <path d="M38.5,7 L32.75,1.25 L34,6.58831647e-15 L42,8 L41.375,8.625 L34,16 L32.75,14.75 L38.5,9 L24,9 L24,7 L38.5,7 Z" transform="translate(33.000000, 8.000000) scale(-1, 1) translate(-33.000000, -8.000000)"/>

</svg>
PK
!<?Ichrome/toolkit/skin/classic/global/reader/RM-Content-Width-Plus-44x16.svg<?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="44"
     height="16"
     viewBox="0 0 44 16"
     fill="#808080">

  <path d="M14.5,7 L8.75,1.25 L10,-1.91791433e-15 L18,8 L17.375,8.625 L10,16 L8.75,14.75 L14.5,9 L1.13686838e-13,9 L1.13686838e-13,7 L14.5,7 Z" transform="translate(9.000000, 8.000000) scale(-1, 1) translate(-9.000000, -8.000000)"/>
  <path d="M40.5,7 L34.75,1.25 L36,-5.17110888e-16 L44,8 L43.375,8.625 L36,16 L34.75,14.75 L40.5,9 L26,9 L26,7 L40.5,7 Z"/>

</svg>
PK
!<MHchrome/toolkit/skin/classic/global/reader/RM-Line-Height-Minus-38x14.svg<?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="38"
     height="14"
     viewBox="0 0 38 14"
     fill="#808080">

  <rect x="0" y="0" width="28" height="2"/>
  <rect x="0" y="6" width="38" height="2"/>
  <rect x="0" y="12" width="18" height="2"/>

</svg>
PK
!<teGchrome/toolkit/skin/classic/global/reader/RM-Line-Height-Plus-38x24.svg<?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="38"
     height="24"
     viewBox="0 0 38 24"
     fill="#808080">

  <rect x="0" y="0" width="28" height="2"/>
  <rect x="0" y="11" width="38" height="2"/>
  <rect x="0" y="22" width="18" height="2"/>

</svg>
PK
!<~<chrome/toolkit/skin/classic/global/reader/RM-Minus-24x24.svg<?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 24 24">
  <path fill-rule="evenodd" fill="#808080" d="M0,13.5v-3h24v3H0z"/>
</svg>
PK
!<;chrome/toolkit/skin/classic/global/reader/RM-Plus-24x24.svg<?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 24 24">
  <path fill-rule="evenodd" fill="#808080" d="M24,13.5H13.5V24h-3V13.5H0v-3h10.5V0h3v10.5H24V13.5z"/>
</svg>
PK
!<&@Dchrome/toolkit/skin/classic/global/reader/RM-Type-Controls-24x24.svg<?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="#737373" d="M10.87,18.989h2.144L8.3,3.991H5.724l-4.739,15H3.044l1.115-4.171h5.6ZM4.652,12.91L6.968,5.69l2.294,7.22H4.652ZM22.1,16.515v-5.06c0-2.31-.984-3.713-3.65-3.713a10.236,10.236,0,0,0-3.7.756L15.116,9.9A9.9,9.9,0,0,1,18.1,9.317c1.533,0,1.958.627,1.958,2.223v0.975h-1.35c-3.086,0-4.871,1.125-4.871,3.5a3.217,3.217,0,0,0,3.527,3.338,3.205,3.205,0,0,0,2.945-1.659,2.573,2.573,0,0,0,2.436,1.659l0.441-1.344A1.408,1.408,0,0,1,22.1,16.515ZM17.8,17.9a1.744,1.744,0,0,1-1.911-1.995c0-1.512,1.029-2.111,3.065-2.111h1.1V16.18C19.426,17.334,18.938,17.9,17.8,17.9Z"/>
</svg>
PK
!<Dchrome/toolkit/skin/classic/global/reader/RM-Type-Controls-Arrow.svg<?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 24">
  <polygon points="16.58 0.01 16.57 0 4.58 12 16.57 24 16.58 23.98 16.58 0.01" fill="#b5b5b5"/>
  <polyline points="16.63 1.51 6.08 12.01 16.63 22.5" fill="#fbfbfb"/>
</svg>
PK
!<sGkk.chrome/toolkit/skin/classic/global/resizer.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

resizer {
  -moz-appearance: resizer;
  background: url("chrome://global/skin/icons/resizer.png") no-repeat;
  background-size: 100% 100%;
  cursor: se-resize;
  width: 15px;
  height: 15px;
}

resizer[rtl="true"],
resizer[dir="bottomend"]:-moz-locale-dir(rtl) {
  background: url("chrome://global/skin/icons/resizer-rtl.png") no-repeat;
}

resizer[dir="left"],
resizer[dir="bottomleft"],
resizer[dir="bottomstart"] {
  transform: scaleX(-1);
}

resizer[dir="bottomleft"],
resizer[dir="bottomstart"]:not([rtl="true"]):not(:-moz-locale-dir(rtl)),
resizer[dir="bottomend"][rtl="true"] {
  cursor: sw-resize;
}

resizer[dir="top"],
resizer[dir="bottom"] {
  cursor: ns-resize;
}

resizer[dir="left"],
resizer[dir="right"] {
  cursor: ew-resize;
}

resizer[dir="topleft"] {
  cursor: nw-resize;
}

resizer[dir="topright"] {
  cursor: ne-resize;
}
PK
!<2chrome/toolkit/skin/classic/global/richlistbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

richlistbox {
  -moz-appearance: listbox;
  margin: 2px 4px;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

richlistbox[disabled="true"] {
  color: GrayText;
}

richlistitem[selected="true"] {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

richlistbox:focus > richlistitem[selected="true"] {
  background-color: Highlight;
  color: HighlightText;
}

richlistbox[seltype="multiple"]:focus > richlistitem[current="true"] {
  outline: 1px dotted Highlight;
  outline-offset: -1px;
}

richlistbox[seltype="multiple"]:focus > richlistitem[current="true"][selected="true"] {
  outline: 1px dotted #F3D982; /* TODO: find a suitable system color */
}

PK
!<ki,chrome/toolkit/skin/classic/global/scale.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

.scale-slider {
  -moz-appearance: scale-horizontal;
  margin: 2px 4px;
  width: 100px;
}

.scale-slider[orient="vertical"] {
  -moz-appearance: scale-vertical;
  margin: 4px 2px;
  width: auto;
  height: 100px;
}

.scale-thumb {
  -moz-appearance: scalethumb-horizontal;
  min-width: 30px;
  min-height: 15px;
}

.scale-thumb[orient="vertical"] {
  -moz-appearance: scalethumb-vertical;
  min-width: 15px;
  min-height: 30px;
}

PK
!<fA		1chrome/toolkit/skin/classic/global/scrollbars.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== xulscrollbars.css ==============================================
  == Styles used by XUL scrollbar-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

/* ::::: scrollbar ::::: */

scrollbar {
  -moz-appearance: scrollbar-horizontal;
  -moz-binding: url("chrome://global/content/bindings/scrollbar.xml#scrollbar");
  cursor: default;
}

@media all and (-moz-overlay-scrollbars) {
  scrollbar[root="true"] {
    position: relative;
    z-index: 2147483647; /* largest positive value of a signed 32-bit integer */
  }

  scrollbar:not([active="true"]),
  scrollbar[disabled="true"] {
    visibility: hidden;
  }
}

scrollbar[orient="vertical"] {
  -moz-appearance: scrollbar-vertical;
}

/* ::::: slider - a thumb is inside  ::::: */
slider {
  -moz-appearance: scrollbartrack-horizontal;
}

slider[orient="vertical"] {
  -moz-appearance: scrollbartrack-vertical;
}

/* ::::: thumb (horizontal) ::::: */

thumb {
  -moz-appearance: scrollbarthumb-vertical;
  min-height: 8px;
}

thumb[orient="horizontal"] {
  -moz-appearance: scrollbarthumb-horizontal;
  min-width: 8px;
}

/* ::::: scrollbar button ::::: */

scrollbarbutton {
  min-width: 16px;
  min-height: 16px;
}

/* ::::: square at the corner of two scrollbars ::::: */

scrollcorner { 
  /* XXX -moz-appearance: scrollcorner; */
  -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar-base);
  width: 16px;
  cursor: default;
  background-color: -moz-Dialog;
}

/* ..... increment .... */

scrollbarbutton[type="increment"] {
  -moz-appearance: scrollbarbutton-right;
}

scrollbar[orient="vertical"] > scrollbarbutton[type="increment"] {
  -moz-appearance: scrollbarbutton-down;
}

/* ..... decrement .... */

scrollbarbutton[type="decrement"] {
  -moz-appearance: scrollbarbutton-left;
}

scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"] {
  -moz-appearance: scrollbarbutton-up;
}

PK
!<
0:0chrome/toolkit/skin/classic/global/scrollbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/*
 * Scroll arrows
 */

/* Horizontal enabled */
.autorepeatbutton-up[orient="horizontal"],
.autorepeatbutton-down:-moz-locale-dir(rtl)[orient="horizontal"],
.scrollbutton-up[orient="horizontal"],
.scrollbutton-down:-moz-locale-dir(rtl)[orient="horizontal"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

.autorepeatbutton-down[orient="horizontal"],
.autorepeatbutton-up:-moz-locale-dir(rtl)[orient="horizontal"],
.scrollbutton-down[orient="horizontal"],
.scrollbutton-up:-moz-locale-dir(rtl)[orient="horizontal"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

 /* Horizontal disabled */
.autorepeatbutton-up[orient="horizontal"][disabled="true"],
.autorepeatbutton-down:-moz-locale-dir(rtl)[orient="horizontal"][disabled="true"],
.scrollbutton-up[orient="horizontal"][disabled="true"],
.scrollbutton-down:-moz-locale-dir(rtl)[orient="horizontal"][disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

.autorepeatbutton-down[orient="horizontal"][disabled="true"],
.autorepeatbutton-up:-moz-locale-dir(rtl)[orient="horizontal"][disabled="true"],
.scrollbutton-down[orient="horizontal"][disabled="true"],
.scrollbutton-up:-moz-locale-dir(rtl)[orient="horizontal"][disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

/* Vertical enabled */
.autorepeatbutton-up,
.scrollbutton-up {
  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

.autorepeatbutton-down,
.scrollbutton-down {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

/* Vertical disabled */
.autorepeatbutton-up[disabled="true"],
.scrollbutton-up[disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

.autorepeatbutton-down[disabled="true"],
.scrollbutton-down[disabled="true"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
  -moz-image-region: auto; /* cut off inheritance */
}

.scrollbutton-up > .toolbarbutton-text,
.scrollbutton-down > .toolbarbutton-text {
  display: none;
}

autorepeatbutton,
.scrollbutton-up,
.scrollbutton-down {
  -moz-box-align: center;
  -moz-box-pack: center;
  margin-top: 1px;
  margin-bottom: 2px;
  margin-inline-start: 1px;
  margin-inline-end: 2px;
}

autorepeatbutton {
  border: 1px solid transparent;
  padding: 1px;
}

autorepeatbutton:not([disabled="true"]):hover,
autorepeatbutton:not([disabled="true"]):hover:active {
  margin: 1px;
  border: 1px inset ThreeDFace;
  padding-top: 2px;
  padding-bottom: 1px;
  padding-inline-start: 2px;
  padding-inline-end: 1px;
}
PK
!<2//2chrome/toolkit/skin/classic/global/spinbuttons.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

spinbuttons {
  -moz-appearance: spinner;
  cursor: default;
}

.spinbuttons-button {
  min-width: 13px;
  min-height: 11px;
  margin: 0 !important;
}

.spinbuttons-up {
  -moz-appearance: spinner-upbutton;
}

.spinbuttons-down {
  -moz-appearance: spinner-downbutton;
}
PK
!<+|;chrome/toolkit/skin/classic/global/splitter/grip-bottom.gifGIF89as11c!,s@>0j8ͻ`YרJprMsngXbI00,
D1\:fɜXDجv";PK
!<^e9chrome/toolkit/skin/classic/global/splitter/grip-left.gifGIF89as11c!,s@J0	8pܝz$EUYyv<8JYbfVവv/6=ɍ;PK
!<B:chrome/toolkit/skin/classic/global/splitter/grip-right.gifGIF89as11c!,s@J0f֛%a_R)Ųבbלk7gٙ%&|uVONqBO;f]/+ss%;PK
!<[8chrome/toolkit/skin/classic/global/splitter/grip-top.gifGIF89as11c!,s@=0	8ͻ_$ik,lYY	`@H"rl&LsI1جvˍ;PK
!<Sl/chrome/toolkit/skin/classic/global/splitter.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== splitter.css ===================================================
  == Styles used by the XUL splitter element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: splitter (vertical) ::::: */

splitter {
  -moz-box-align: center;
  -moz-box-pack: center;
  cursor: ew-resize;
  border-width: 0 2px;
  border-style: solid;
  -moz-border-left-colors: ThreeDShadow ThreeDHighlight;
  -moz-border-right-colors: ThreeDDarkShadow ThreeDFace;
  min-width: 6px;
  background-color: ThreeDFace;
}

splitter[state="collapsed"][collapse="before"],
splitter[state="collapsed"][substate="before"],
splitter[state="collapsed"][collapse="after"]:-moz-locale-dir(rtl),
splitter[state="collapsed"][substate="after"]:-moz-locale-dir(rtl) {
  cursor: e-resize;
}

splitter[state="collapsed"][collapse="after"],
splitter[state="collapsed"][substate="after"],
splitter[state="collapsed"][collapse="before"]:-moz-locale-dir(rtl),
splitter[state="collapsed"][substate="before"]:-moz-locale-dir(rtl) {
  cursor: w-resize;
}

splitter:-moz-lwtheme {
  background: none;
}

/* ::::: splitter (horizontal) ::::: */

splitter[orient="vertical"] {
  cursor: ns-resize;
  border-width: 2px 0;
  -moz-border-top-colors: ThreeDShadow ThreeDHighlight;
  -moz-border-bottom-colors: ThreeDDarkShadow ThreeDFace;
  min-height: 6px;
}

splitter[orient="vertical"][state="collapsed"][collapse="before"],
splitter[orient="vertical"][state="collapsed"][substate="before"] {
  cursor: s-resize;
}

splitter[orient="vertical"][state="collapsed"][collapse="after"],
splitter[orient="vertical"][state="collapsed"][substate="after"] {
  cursor: n-resize;
}

splitter[disabled="true"] {
  cursor: default !important;
}

/* ::::: splitter grippy ::::: */

grippy {
  cursor: pointer;
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDShadow;
  min-width: 4px;
  min-height: 115px;
  background-color: transparent;
  background-repeat: no-repeat;
}

grippy:hover {
  background-color: ThreeDHighlight;
}

splitter[orient="vertical"] > grippy {
  border-top: none;
  border-right: 1px solid ThreeDShadow;
  border-bottom: none;
  border-left: 1px solid ThreeDShadow;;
  min-width: 115px;
  min-height: 4px;
}
  
/* ..... normal state ..... */

/* vertical grippies */
splitter[collapse="before"] > grippy,
splitter[collapse="after"] > grippy:-moz-locale-dir(rtl) {
  background-image: url("chrome://global/skin/splitter/grip-left.gif");
}

splitter[collapse="after"] > grippy,
splitter[collapse="before"] > grippy:-moz-locale-dir(rtl) {
  background-image: url("chrome://global/skin/splitter/grip-right.gif");
}

/* horizontal grippies */
splitter[collapse="before"][orient="vertical"] > grippy {
  background-image: url("chrome://global/skin/splitter/grip-top.gif");
}  

splitter[collapse="after"][orient="vertical"] > grippy {
  background-image: url("chrome://global/skin/splitter/grip-bottom.gif");
}  
  
/* ..... collapsed state ..... */

/* vertical grippies */
splitter[collapse="before"][state="collapsed"] > grippy,
splitter[collapse="after"][state="collapsed"] > grippy:-moz-locale-dir(rtl) {
  background-image: url("chrome://global/skin/splitter/grip-right.gif");
}

splitter[collapse="after"][state="collapsed"] > grippy,
splitter[collapse="before"][state="collapsed"] > grippy:-moz-locale-dir(rtl) {
  background-image: url("chrome://global/skin/splitter/grip-left.gif");
}

/* horizontal grippies */
splitter[collapse="before"][state="collapsed"][orient="vertical"] > grippy {
  background-image: url("chrome://global/skin/splitter/grip-bottom.gif");
}  

splitter[collapse="after"][state="collapsed"][orient="vertical"] > grippy {
  background-image: url("chrome://global/skin/splitter/grip-top.gif");
}  

PK
!<Gđ-chrome/toolkit/skin/classic/global/tabbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== tabbox.css =================================================
  == Styles used by XUL tab-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: tabs ::::: */

.tabs-left,
.tabs-right {
  border-bottom: 2px solid;
  -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
}

/* ::::: tabpanels ::::: */

tabpanels {
  -moz-appearance: tabpanels;
  padding: 8px;
  color: -moz-DialogText;
}

/* ::::: tab ::::: */

tab {
  -moz-appearance: tab;
  margin-top: 2px;
  padding: 1px 4px 2px 4px;
  color: -moz-DialogText;
}

.tab-text {
  margin: 0 !important;
}

tab[visuallyselected="true"] {
  margin-top: 0;
  padding: 1px 6px 4px 6px;
}

tab:-moz-focusring > .tab-middle {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

tab:first-of-type[visuallyselected="true"] {
  padding-right: 5px;
  padding-left: 5px;
}

/* ::::: tab-bottom ::::::::::
   :: Tabs that are attached to the bottom of a panel, but not necessarily
   :: a tabpanels.
   ::::: */

.tab-bottom {
  margin-top: 0;
  margin-bottom: 2px;
  padding: 2px 4px 1px 4px;
}

.tab-bottom[visuallyselected="true"] {
  margin-bottom: 0;
  padding: 4px 6px 1px 6px;
}

/* ::::: tabs-bottom ::::: */

.tabs-bottom > .tabs-left,
.tabs-bottom > .tabs-right {
  border-top: 1px solid ThreeDShadow;
  border-bottom: none;
}
PK
!<z>1chrome/toolkit/skin/classic/global/tabprompts.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Tab Modal Prompt boxes */
tabmodalprompt {
  background-image: url(chrome://global/skin/icons/tabprompts-bgtexture.png);
  background-color: hsla(0,0%,10%,.5);
  font-family: sans-serif; /* use content font not system UI font */
}

.mainContainer {
  color: -moz-fieldText;
  background-color: -moz-field;
  border-radius: 2px;
  border: 1px solid threeDDarkShadow;
}

.topContainer {
  padding: 20px;
}

.buttonContainer {
  padding: 12px 20px 15px;
  background-color: hsla(0,0%,0%,.05);
  border-top: 1px solid hsla(0,0%,0%,.05);
}
PK
!<f

.chrome/toolkit/skin/classic/global/textbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== textbox.css ==================================================
  == Styles used by the XUL textbox element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* ::::: textbox ::::: */

textbox {
  -moz-appearance: textfield;
  cursor: text;
  margin: 2px 4px;
  padding: 2px 2px 3px;
  padding-inline-start: 4px;
  background-color: -moz-Field;
  color: -moz-FieldText;
}

html|*.textbox-input, 
html|*.textbox-textarea {
  margin: 0px !important;
  border: none !important;
  padding: 0px 1px !important;
  background-color: inherit;
  color: inherit;
  font: inherit;
}

@media (-moz-windows-default-theme) {
  textbox html|*.textbox-input::placeholder {
    font-style: italic;
  }
}

.textbox-contextmenu {
  cursor: default;
}

/* ..... readonly state ..... */

textbox[readonly="true"] {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}

/* ..... disabled state ..... */

textbox[disabled="true"] {
  cursor: default;
  background-color: -moz-Dialog;
  color: GrayText;
}

/* ::::: plain textbox ::::: */

textbox.plain {
  -moz-appearance: none !important;
  background-color: transparent;
  padding: 0px !important;
  margin: 0px !important;
  border: none !important;
}

textbox.plain html|*.textbox-input,
textbox.plain html|*.textbox-textarea {
  padding: 0px !important;
}

/* ::::: search textbox ::::: */

textbox:not([searchbutton]) > .textbox-input-box > .textbox-search-sign {
  list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
  margin-inline-end: 5px;
}

.textbox-search-icon[searchbutton] {
  list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
}

.textbox-search-icon:-moz-locale-dir(rtl) {
  transform: scaleX(-1);
}

.textbox-search-icon[searchbutton]:not([disabled]) {
  cursor: pointer;
}

.textbox-search-clear {
  list-style-image: url(chrome://global/skin/icons/Search-close.png);
  -moz-image-region: rect(0, 16px, 16px, 0);
}

.textbox-search-clear:not([disabled]) {
  cursor: default;
}

.textbox-search-clear:not([disabled]):hover {
  -moz-image-region: rect(0, 32px, 16px, 16px);
}

.textbox-search-clear:not([disabled]):hover:active {
  -moz-image-region: rect(0, 48px, 16px, 32px);
}

/* ::::: textboxes inside toolbarpaletteitems ::::: */

toolbarpaletteitem > toolbaritem > textbox > .textbox-input-box > html|*.textbox-input {
  visibility: hidden;
}

PK
!<UU?chrome/toolkit/skin/classic/global/toolbar/chevron-inverted.pngPNG


IHDRxUIDATcL |+"_)I,IENDB`PK
!<	996chrome/toolkit/skin/classic/global/toolbar/chevron.gifGIF89a!,
D$yꠒ;PK
!<Q5chrome/toolkit/skin/classic/global/toolbar/spring.pngPNG


IHDR  stEXtSoftwareAdobe ImageReadyqe<VIDATH1n0tu!qZ	6)\
*R)Hz1E
H!Y>lp4(
p3-szaS/Lz?f	+ϊ>
\2_䋠sRm{	ψulڦmbX9%B|`Ъap8I$(
IA2_h:|Lo#QjeVU~E *Ɣ؅R:d	C۫{&q*$'a8@eks={0MIENDB`PK
!<cl.chrome/toolkit/skin/classic/global/toolbar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== toolbar.css ====================================================
  == Styles used by XUL toolbar-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: toolbox ::::: */

toolbox {
  -moz-appearance: toolbox;
}

/* ::::: toolbar & menubar ::::: */

toolbar, menubar {
  -moz-appearance: toolbar;
}

toolbar {
  min-width: 1px;
  min-height: 19px;
}

toolbar:first-child, menubar {
  min-width: 1px;
}

/* ::::: lightweight theme ::::: */
 
menubar:-moz-lwtheme,
toolbox:-moz-lwtheme,
toolbar:-moz-lwtheme {
  -moz-appearance: none;
}

/* ::::: toolbar decorations ::::: */

toolbarseparator {
  -moz-appearance: separator;
}

toolbarspacer {
  width: 15px;
}

/* ::::: toolbarpaletteitem ::::: */

toolbarpaletteitem {
  cursor: grab;
}

.toolbarpaletteitem-box[type="spacer"],
.toolbarpaletteitem-box[type="spring"] {
  border: 1px solid #808080;
  background-color: #FFF !important;
}

toolbarpaletteitem[place="toolbar"] > toolbarspacer {
  width: 11px;
}

.toolbarpaletteitem-box[type="spacer"][place="toolbar"],
.toolbarpaletteitem-box[type="spring"][place="toolbar"] {
  margin-top: 2px;
  margin-bottom: 2px;
  margin-inline-start: 0px;
  margin-inline-end: 2px;
}

.toolbarpaletteitem-box[type="separator"][place="palette"] {
  width: 2px;
  height: 50px;
}

.toolbarpaletteitem-box[type="spacer"][place="palette"],
.toolbarpaletteitem-box[type="spring"][place="palette"] {
  margin-bottom: 2px;
  width: 50px;
  height: 50px;
}

.toolbarpaletteitem-box[type="spring"][place="palette"] {
  background: url("chrome://global/skin/toolbar/spring.png") no-repeat center;
}

/* ..... drag and drop feedback ..... */

toolbarpaletteitem[place="toolbar"] {
  margin-left: -2px;
  margin-right: -2px;
  border-left: 2px solid transparent;
  border-right: 2px solid transparent;
}

toolbarpaletteitem[dragover="left"] {
  border-left-color: #000000;
}

toolbarpaletteitem[dragover="right"] {
  border-right-color: #000000;
}
PK
!<f4chrome/toolkit/skin/classic/global/toolbarbutton.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== toolbarbutton.css =====================================================
  == Styles used by the XUL button element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: toolbarbutton ::::: */

toolbarbutton {
  -moz-appearance: toolbarbutton;
  -moz-box-align: center;
  -moz-box-pack: center;
  margin: 0;
  padding: 3px;
}

.toolbarbutton-icon[label]:not([label=""]),
.toolbarbutton-icon[type="menu"] {
  margin-inline-end: 5px;
}

.toolbarbutton-text {
  margin: 0 !important; /* !important for overriding global.css */
  text-align: center;
}

toolbarbutton.tabbable {
  -moz-user-focus: normal !important;
}

toolbarbutton:-moz-focusring {
  /* -moz-appearance looks redundant here but is necessary.
      Without it, the outline won't appear. */
  -moz-appearance: toolbarbutton;
  outline: 1px dotted -moz-DialogText;
  outline-offset: -2px;
}

toolbarbutton:hover:active:not([disabled="true"]),
toolbarbutton[open="true"]:hover,
toolbarbutton[open="true"] {
  padding-top: 4px;
  padding-bottom: 2px;
  padding-inline-start: 4px;
  padding-inline-end: 2px;
}

toolbarbutton[disabled="true"] {
  color: GrayText;
  text-shadow: none;
}

@media (-moz-windows-classic) {
  toolbarbutton[disabled="true"] {
    color: ThreeDShadow;
    text-shadow: 1px 1px ThreeDHighlight;
  }
}

toolbarbutton[checked="true"]:not([disabled="true"]) {
  padding-top: 4px;
  padding-bottom: 2px;
  padding-inline-start: 4px;
  padding-inline-end: 2px;
  color: ButtonText;
}

@media (-moz-windows-default-theme) {
  toolbarbutton:-moz-lwtheme {
    text-shadow: none;
  }

  toolbarbutton:-moz-lwtheme:not([disabled="true"]) {
    text-shadow: inherit;
  }
}

@media (-moz-windows-default-theme: 0) {
  toolbarbutton:-moz-lwtheme {
    -moz-appearance: none;
  }

  toolbarbutton:-moz-lwtheme:not([disabled="true"]) {
    text-shadow: inherit;
  }
}

/* ::::: toolbarbutton menu ::::: */

.toolbarbutton-menu-dropmarker {
  -moz-appearance: none !important;
  padding: 0;
  width: auto;
  height: auto;
  margin-top: 1px;
}

/* ::::: toolbarbutton menu-button ::::: */

toolbarbutton[type="menu-button"] {
  -moz-box-align: stretch;
  -moz-box-orient: horizontal !important;
}

toolbarbutton[type="menu-button"],
toolbarbutton[type="menu-button"]:hover,
toolbarbutton[type="menu-button"]:hover:active,
toolbarbutton[type="menu-button"][open="true"],
toolbarbutton[type="menu-button"][disabled="true"],
toolbarbutton[type="menu-button"][disabled="true"]:hover,
toolbarbutton[type="menu-button"][disabled="true"]:hover:active {
  padding: 0 !important;
}

.toolbarbutton-menubutton-button {
  -moz-box-align: center;
  -moz-box-pack: center;
  -moz-box-orient: vertical;
}

/* ::::: toolbarbutton badged ::::: */

.toolbarbutton-badge-stack > .toolbarbutton-icon[label]:not([label=""]) {
  margin-inline-end: 0;
}

.toolbarbutton-badge {
  background-color: #d90000;
  font-size: 10px;
  font-weight: bold;
  padding: 0 2px 1px;
  color: #fff;
  border-radius: 2px;
  box-shadow: 0 1px 0 hsla(0, 100%, 100%, .2) inset,
              0 -1px 0 hsla(0, 0%, 0%, .1) inset,
              0 1px 0 hsla(206, 50%, 10%, .2);
  margin: -6px 0 0 !important;
  margin-inline-end: -8px !important;
  min-width: 14px;
  max-width: 28px;
  line-height: 10px;
  text-align: center;
  -moz-stack-sizing: ignore;
}

/* .......... dropmarker .......... */

.toolbarbutton-menubutton-dropmarker {
  -moz-appearance: none;
  padding: 3px 7px;
  width: auto;
}
PK
!<JJ8chrome/toolkit/skin/classic/global/tree/columnpicker.gifGIF89a3!,kR8I%ݨ
YLeT9#*;PK
!<VMٶ<chrome/toolkit/skin/classic/global/tree/sort-asc-classic.pngPNG


IHDR	SktEXtSoftwareAdobe ImageReadyqe<XIDATxb?Ջ'``ݦ}7g`b@ gXqf+ZyXB.B&	C6d"͈l,.`3+1x\IENDB`PK
!<74chrome/toolkit/skin/classic/global/tree/sort-asc.pngPNG


IHDR	SktEXtSoftwareAdobe ImageReadyqe<sIDATxb?Tgb@,}'.,RPARU[/U#SnfF7<e8\
13
?i.F"B 8}7>qIENDB`PK
!<+s<chrome/toolkit/skin/classic/global/tree/sort-dsc-classic.pngPNG


IHDR	SktEXtSoftwareAdobe ImageReadyqe<OIDATxb?Css3`L F#`(s321!L`i"()@6&B ?EF|IENDB`PK
!< M4chrome/toolkit/skin/classic/global/tree/sort-dsc.pngPNG


IHDR	SktEXtSoftwareAdobe ImageReadyqe<yIDATxb?m|5w>~^&&A~~!vVFTyFF'?ˠ&'   /(sB
W+o/?(u@ήIENDB`PK
!<FII;chrome/toolkit/skin/classic/global/tree/twisty-preWin10.svg<?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="9" height="9">
  <style>
    use:not(:target) {
      display: none;
    }
    use {
      stroke: #74747b;
      stroke-opacity: 0.85;
      fill: none;
    }
    use[id^="open"] {
      stroke: #636363;
      stroke-opacity: 1;
    }
    use[id*="-hover"] {
      stroke: #1cc4f7;
      stroke-opacity: 1;
      fill: #c0e8f9;
    }
  </style>
  <defs>
    <path id="clsd-shape" d="m 2.5,0.5 4,4 -4,4 z"/>
    <path id="open-shape" d="M 7.5,3 7.5,7.5 3,7.5 3,6.5 6.5,3 Z"/>
    <path id="clsd-rtl-shape" d="m 6.5,0.5 -4,4 4,4 z"/>
    <path id="open-rtl-shape" d="m 1.5,3 0,4.5 4.5,0 0,-1 L 2.5,3 Z"/>
  </defs>
  <use id="clsd" xlink:href="#clsd-shape"/>
  <use id="clsd-hover" xlink:href="#clsd-shape"/>
  <use id="open" xlink:href="#open-shape"/>
  <use id="open-hover" xlink:href="#open-shape"/>
  <use id="clsd-rtl" xlink:href="#clsd-rtl-shape"/>
  <use id="clsd-hover-rtl" xlink:href="#clsd-rtl-shape"/>
  <use id="open-rtl" xlink:href="#open-rtl-shape"/>
  <use id="open-hover-rtl" xlink:href="#open-rtl-shape"/>
</svg>
PK
!<ew¯2chrome/toolkit/skin/classic/global/tree/twisty.svg<?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="9" height="9">
  <style>
    use:not(:target) {
      display: none;
    }
    use {
      stroke: #b6b6b6;
      stroke-width: 1.6;
      fill: none;
    }
    use[id^="open"] {
      stroke: #636363;
    }
    use[id*="-hover"] {
      stroke: #4ed0f9;
    }
  </style>
  <defs>
    <path id="clsd-shape" d="m 2.5,0.5 4,4 -4,4"/>
    <path id="open-shape" d="m 8.5,2.5 -4,4 -4,-4"/>
    <path id="clsd-rtl-shape" d="m 6.5,0.5 -4,4 4,4"/>
  </defs>
  <use id="clsd" xlink:href="#clsd-shape"/>
  <use id="clsd-hover" xlink:href="#clsd-shape"/>
  <use id="open" xlink:href="#open-shape"/>
  <use id="open-hover" xlink:href="#open-shape"/>
  <use id="clsd-rtl" xlink:href="#clsd-rtl-shape"/>
  <use id="clsd-hover-rtl" xlink:href="#clsd-rtl-shape"/>
  <use id="open-rtl" xlink:href="#open-shape"/>
  <use id="open-hover-rtl" xlink:href="#open-shape"/>
</svg>
PK
!<a$RR+chrome/toolkit/skin/classic/global/tree.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== tree.css ===================================================
  == Styles used by the XUL outline element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: tree ::::: */

tree {
  margin: 0px 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;
  background-color: -moz-Field;
  color: -moz-FieldText;
  -moz-appearance: listbox;
}

/* ::::: tree focusring ::::: */

.focusring > .tree-stack > .tree-rows > .tree-bodybox {
  border: 1px solid transparent;
}

.focusring:-moz-focusring > .tree-stack > .tree-rows > .tree-bodybox {
  border: 1px solid #000000;
}


/* ::::: tree rows ::::: */

treechildren::-moz-tree-row {
  border: 1px solid transparent;
  min-height: 18px;
  height: 1.3em;
}

treechildren::-moz-tree-row(selected) {
  background-color: -moz-cellhighlight;
}

treechildren::-moz-tree-row(selected, focus) {
  background-color: Highlight;
}

treechildren::-moz-tree-row(current, focus) {
  border: 1px dotted Highlight;
}

treechildren::-moz-tree-row(selected, current, focus) {
  border: 1px dotted #F3D982;
}

tree[seltype="cell"] > treechildren::-moz-tree-row,
tree[seltype="text"] > treechildren::-moz-tree-row {
  border: none;
  background-color: transparent;
  background-image: none;
}

/* ::::: tree cells ::::: */

treechildren::-moz-tree-cell {
  padding: 0px 2px 0px 2px;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell-text,
tree[seltype="text"] > treechildren::-moz-tree-cell-text,
treechildren::-moz-tree-cell-text {
  color: inherit;
}

treechildren::-moz-tree-cell-text(selected) {
  color: -moz-cellhighlighttext;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell {
  border: 1px solid transparent;
  padding: 0px 1px 0px 1px;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text {
  border: 1px solid transparent;
  padding: 0px 1px 1px 1px;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected) {
  background-color: -moz-cellhighlight;
}
tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected) {
  color: -moz-cellhighlighttext;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected) {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

treechildren::-moz-tree-cell-text(selected, focus) {
  color: HighlightText;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus) {
  background-color: Highlight;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
  background-color: Highlight;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
  color: HighlightText;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
  color: HighlightText;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell(active, current, focus) {
  border: 1px dotted #000000;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, current, focus) {
  border: 1px dotted #000000;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, current, focus) {
  border: 1px dotted #C0C0C0;
}

tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, current, focus) {
  border: 1px dotted #C0C0C0;
}

/* ::::: lines connecting cells ::::: */

tree[seltype="cell"] > treechildren::-moz-tree-line,
tree[seltype="text"] > treechildren::-moz-tree-line,
treechildren::-moz-tree-line {
  border: 1px dotted ThreeDShadow;
}

tree[seltype="cell"] > treechildren::-moz-tree-line(active, selected, focus),
treechildren::-moz-tree-line(selected, focus) {
  border: 1px dotted HighlightText;
}

/* ::::: tree separator ::::: */

treechildren::-moz-tree-separator {
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDHighlight;
}


/* ::::: drop feedback ::::: */

treechildren::-moz-tree-row(dropOn) {
  background-color: Highlight;
}

tree[seltype="cell"] > treechildren::-moz-tree-cell-text(primary, dropOn),
tree[seltype="text"] > treechildren::-moz-tree-cell-text(primary, dropOn),
treechildren::-moz-tree-cell-text(primary, dropOn) {
  color: HighlightText;
}

treechildren::-moz-tree-drop-feedback {
  background-color: Highlight;
  width: 50px;
  height: 2px;
  margin-inline-start: 5px;
}

/* ::::: tree progress meter ::::: */

treechildren::-moz-tree-progressmeter {
  margin: 2px 4px;
  padding: 1px;
  border: 1px solid;
  border-top-color: ThreeDShadow;
  border-right-color: ThreeDHighlight;
  border-bottom-color: ThreeDHighlight;
  border-left-color: ThreeDShadow;
  background-color: -moz-Dialog;
  color: ThreeDShadow;
}

treechildren::-moz-tree-cell-text(progressmeter) {
  margin: 2px 4px;
}

/* ::::: tree columns ::::: */

treecol,
treecolpicker {
  -moz-appearance: treeheadercell;
  -moz-box-align: center;
  -moz-box-pack: center;
  border: 2px solid;
  -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
  -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
  -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
  background-color: -moz-Dialog;
  color: -moz-DialogText;
  padding: 0px 4px;
}

.treecol-image {
  padding: 0px 1px;
}

.treecol-text {
  margin: 0px !important;
}

treecol[hideheader="true"] {
  -moz-appearance: none;
  border: none;
  padding: 0;
}

/* ..... internal box ..... */

treecol:hover:active,
treecolpicker:hover:active {
  border-top: 2px solid;
  border-bottom: 1px solid;
  -moz-border-top-colors: ThreeDShadow -moz-Dialog;
  -moz-border-right-colors: ThreeDShadow;
  -moz-border-bottom-colors: ThreeDShadow;
  -moz-border-left-colors: ThreeDShadow -moz-Dialog;
  padding-top: 1px;
  padding-bottom: 0px;
  padding-inline-start: 5px;
  padding-inline-end: 3px;
}

.treecol-image:hover:active {
  padding-top: 1px;
  padding-bottom: 0px;
  padding-inline-start: 2px;
  padding-inline-end: 0px;
}

/* ::::: column drag and drop styles ::::: */

treecol[dragging="true"] {
  -moz-border-top-colors: ThreeDDarkShadow transparent !important;
  -moz-border-right-colors: ThreeDDarkShadow transparent!important;
  -moz-border-bottom-colors: ThreeDDarkShadow transparent !important;
  -moz-border-left-colors: ThreeDDarkShadow transparent !important;
  background-color: ThreeDShadow !important;
  color: ThreeDHighlight !important;
}

treecol[insertafter="true"]:-moz-locale-dir(ltr),
treecol[insertbefore="true"]:-moz-locale-dir(rtl) {
  -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
}

treecol[insertafter="true"]:-moz-locale-dir(rtl),
treecol[insertbefore="true"]:-moz-locale-dir(ltr) {
  -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow;
}

treechildren::-moz-tree-column(insertbefore) {
  border-inline-start: 1px solid ThreeDShadow;
}

treechildren::-moz-tree-column(insertafter) {
  border-inline-end: 1px solid ThreeDShadow;
}

/* ::::: sort direction indicator :::::  */

.treecol-sortdirection {
  list-style-image: none;
}

treecol:not([hideheader="true"]) > .treecol-sortdirection[sortDirection="ascending"] {
  list-style-image: url("chrome://global/skin/tree/sort-asc.png");
}

treecol:not([hideheader="true"]) > .treecol-sortdirection[sortDirection="ascending"]:-moz-system-metric(windows-classic) {
  list-style-image: url("chrome://global/skin/tree/sort-asc-classic.png");
}

treecol:not([hideheader="true"]) > .treecol-sortdirection[sortDirection="descending"] {
  list-style-image: url("chrome://global/skin/tree/sort-dsc.png");
}

treecol:not([hideheader="true"]) > .treecol-sortdirection[sortDirection="descending"]:-moz-system-metric(windows-classic) {
  list-style-image: url("chrome://global/skin/tree/sort-dsc-classic.png");
}

/* ::::: column picker :::::  */

.tree-columnpicker-icon {
  list-style-image: url("chrome://global/skin/tree/columnpicker.gif");
}

/* ::::: twisty :::::  */

treechildren::-moz-tree-twisty {
  padding-inline-end: 1px;
  padding-top: 1px;
  width: 9px; /* The image's width is 9 pixels */
  list-style-image: url("chrome://global/skin/tree/twisty.svg#clsd");
}

treechildren::-moz-tree-twisty(open) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#open");
}

treechildren::-moz-tree-twisty(hover) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover");
}

treechildren::-moz-tree-twisty(hover, open) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#open-hover");
}

treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#clsd-rtl");
}

treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#open-rtl");
}

treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover-rtl");
}

treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(hover, open) {
  list-style-image: url("chrome://global/skin/tree/twisty.svg#open-hover-rtl");
}

treechildren::-moz-tree-indentation {
  width: 12px;
}

/* ::::: gridline style ::::: */

treechildren.gridlines::-moz-tree-cell {
  border-right: 1px solid transparent !important;
  border-bottom: 1px solid transparent !important;
}

treechildren.gridlines::-moz-tree-row {
  border: none;
}

/* ::::: editable tree ::::: */

treechildren::-moz-tree-row(selected, editing) {
  background-color: transparent;
  border: none;
}

treechildren::-moz-tree-cell-text(selected, editing) {
  color: inherit;
}

treechildren::-moz-tree-cell(active, selected, focus, editing),
tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus, editing),
tree[seltype="text"] > treechildren::-moz-tree-cell(active, selected, focus, editing) {
  background-color: transparent;
  border: none;
}

treechildren::-moz-tree-cell-text(active, selected, editing) {
  opacity: 0;
}

.tree-input {
  -moz-appearance: none;
  border: 1px solid Highlight;
  -moz-border-top-colors: Highlight;
  -moz-border-bottom-colors: Highlight;
  -moz-border-left-colors: Highlight;
  -moz-border-right-colors: Highlight;
  margin: 0;
  margin-inline-start: -4px;
  padding: 1px;
}

@media (-moz-windows-default-theme) {
  treechildren {
    --treechildren-outline: none;
    --treechildren-2ndBorderColor: rgba(255,255,255,.4);
    --treechildren-selectedColor: rgb(217,217,217);
    --treechildren-focusColor: rgb(123,195,255);
    --treechildren-selectedFocusColor: rgb(205,232,255);
    --treechildren-currentColor: rgb(125,162,206);
    --treechildren-hoverColor: rgb(229,243,255);
    --treechildren-selectedBorder: var(--treechildren-selectedColor);
    --treechildren-selectedBottomBorder: rgb(204,204,204);
    --treechildren-selectedImage: linear-gradient(rgb(217,217,217), rgb(217,217,217));
    --treechildren-selectedBackground: transparent;
    --treechildren-currentFocusBorder: var(--treechildren-focusColor);
    --treechildren-currentFocusBottomBorder: var(--treechildren-focusColor);
    --treechildren-selectedFocusBorder: var(--treechildren-selectedFocusColor);
    --treechildren-selectedFocusBottomBorder: rgb(165,214,255);
    --treechildren-selectedFocusImage: none;
    --treechildren-selectedFocusBackground: var(--treechildren-selectedFocusColor);
    --treechildren-selectedFocusCurrentBorder: var(--treechildren-focusColor);
    --treechildren-selectedFocusCurrentBottomBorder: var(--treechildren-focusColor);
    --treechildren-selectedFocusCurrentImage: linear-gradient(rgb(205,232,255), rgb(205,232,255));
    --treechildren-hoverBorder: var(--treechildren-hoverColor);
    --treechildren-hoverBottomBorder: var(--treechildren-hoverColor);
    --treechildren-hoverImage: linear-gradient(rgb(229,243,255), rgb(229,243,255));
    --treechildren-hoverCurrentBorder: var(--treechildren-currentColor);
    --treechildren-hoverCurrentBottomBorder: var(--treechildren-currentColor);
    --treechildren-hoverCurrentImage: linear-gradient(rgba(131,183,249,.16), rgba(131,183,249,.16));
    --treechildren-hoverSelectedBorder: var(--treechildren-focusColor);
    --treechildren-hoverSelectedBottomBorder: var(--treechildren-focusColor);
    --treechildren-hoverSelectedImage: linear-gradient(rgb(205,232,255), rgb(205,232,255));
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row {
    height: 1.8em;
    color: -moz-FieldText;
    margin-inline-start: 1px;
    margin-inline-end: 1px;
    border-width: 1px;
    border-color: transparent;
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected) {
    -moz-border-top-colors: var(--treechildren-selectedBorder);
    -moz-border-right-colors: var(--treechildren-selectedBorder);
    -moz-border-left-colors: var(--treechildren-selectedBorder);
    -moz-border-bottom-colors: var(--treechildren-selectedBottomBorder);
    background-image: var(--treechildren-selectedImage);
    background-color: var(--treechildren-selectedBackground);
    outline: var(--treechildren-outline);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(current, focus) {
    border-style: solid;
    -moz-border-top-colors: var(--treechildren-currentFocusBorder);
    -moz-border-right-colors: var(--treechildren-currentFocusBorder);
    -moz-border-left-colors: var(--treechildren-currentFocusBorder);
    -moz-border-bottom-colors: var(--treechildren-currentFocusBottomBorder);
    outline: var(--treechildren-outline);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, focus),
  treechildren::-moz-tree-row(dropOn) {
    -moz-border-top-colors: var(--treechildren-selectedFocusBorder);
    -moz-border-right-colors: var(--treechildren-selectedFocusBorder);
    -moz-border-left-colors: var(--treechildren-selectedFocusBorder);
    -moz-border-bottom-colors: var(--treechildren-selectedFocusBottomBorder);
    background-image: var(--treechildren-selectedFocusImage);
    background-color: var(--treechildren-selectedFocusBackground);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, current, focus) {
    border-style: solid;
    -moz-border-top-colors: var(--treechildren-selectedFocusCurrentBorder);
    -moz-border-right-colors: var(--treechildren-selectedFocusCurrentBorder);
    -moz-border-left-colors: var(--treechildren-selectedFocusCurrentBorder);
    -moz-border-bottom-colors: var(--treechildren-selectedFocusCurrentBottomBorder);
    background-image: var(--treechildren-selectedFocusCurrentImage);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover) {
    -moz-border-top-colors: var(--treechildren-hoverBorder);
    -moz-border-right-colors: var(--treechildren-hoverBorder);
    -moz-border-left-colors: var(--treechildren-hoverBorder);
    -moz-border-bottom-colors: var(--treechildren-hoverBottomBorder);
    background-image: var(--treechildren-hoverImage);
    outline: var(--treechildren-outline);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, current) {
    -moz-border-top-colors: var(--treechildren-hoverCurrentBorder);
    -moz-border-right-colors: var(--treechildren-hoverCurrentBorder);
    -moz-border-left-colors: var(--treechildren-hoverCurrentBorder);
    -moz-border-bottom-colors: var(--treechildren-hoverCurrentBottomBorder);
    background-image: var(--treechildren-hoverCurrentImage);
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-row(hover, selected) {
    -moz-border-top-colors: var(--treechildren-hoverSelectedBorder);
    -moz-border-right-colors: var(--treechildren-hoverSelectedBorder);
    -moz-border-left-colors: var(--treechildren-hoverSelectedBorder);
    -moz-border-bottom-colors: var(--treechildren-hoverSelectedBottomBorder);
    background-image: var(--treechildren-hoverSelectedImage);
  }

  tree[disabled="true"] > treechildren::-moz-tree-row {
    background: none;
    -moz-border-top-colors: transparent;
    -moz-border-right-colors: transparent;
    -moz-border-left-colors: transparent;
    -moz-border-bottom-colors: transparent;
  }

  treechildren::-moz-tree-cell(dropOn) {
    background-image: none;
    background-color: transparent;
    border-radius: 0;
  }

  treechildren::-moz-tree-cell-text(primary, dropOn) {
    color: -moz-FieldText;
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-cell-text {
    padding-bottom: initial;
    border-color: transparent;
    background-color: transparent;
  }

  treechildren:not(.autocomplete-treebody)::-moz-tree-cell-text(selected, focus) {
    color: -moz-DialogText;
  }

  @media (-moz-os-version: windows-win7) {
    treechildren {
      --treechildren-outline: 1px solid var(--treechildren-2ndBorderColor);
      --treechildren-2ndBottomBorderColor: rgba(255,255,255,.6);
      --treechildren-selectedBorder: var(--treechildren-selectedColor) var(--treechildren-2ndBorderColor);
      --treechildren-selectedBottomBorder: var(--treechildren-selectedColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-selectedImage: linear-gradient(rgba(190,190,190,.1), rgba(190,190,190,.4));
      --treechildren-currentFocusBorder: var(--treechildren-currentColor) var(--treechildren-2ndBorderColor);
      --treechildren-currentFocusBottomBorder: var(--treechildren-currentColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-selectedFocusBorder: rgb(132,172,221) var(--treechildren-2ndBorderColor);
      --treechildren-selectedFocusBottomBorder: var(--treechildren-currentColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-selectedFocusImage: linear-gradient(rgba(131,183,249,.16), rgba(131,183,249,.375));
      --treechildren-selectedFocusBackground: transparent;
      --treechildren-selectedFocusCurrentBorder: var(--treechildren-currentColor) var(--treechildren-2ndBorderColor);
      --treechildren-selectedFocusCurrentBottomBorder: var(--treechildren-currentColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-selectedFocusCurrentImage: linear-gradient(rgba(131,183,249,.28), rgba(131,183,249,.5));
      --treechildren-hoverBorder: rgb(184,214,251) var(--treechildren-2ndBorderColor);
      --treechildren-hoverBottomBorder: rgb(184,214,251) var(--treechildren-2ndBottomBorderColor);
      --treechildren-hoverImage: linear-gradient(rgba(131,183,249,.05), rgba(131,183,249,.16));
      --treechildren-hoverCurrentBorder: var(--treechildren-currentColor) var(--treechildren-2ndBorderColor);
      --treechildren-hoverCurrentBottomBorder: var(--treechildren-currentColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-hoverCurrentImage: linear-gradient(rgba(131,183,249,.05), rgba(131,183,249,.16));
      --treechildren-hoverSelectedBorder: var(--treechildren-currentColor) var(--treechildren-2ndBorderColor);
      --treechildren-hoverSelectedBottomBorder: var(--treechildren-currentColor) var(--treechildren-2ndBottomBorderColor);
      --treechildren-hoverSelectedImage: linear-gradient(rgba(131,183,249,.28), rgba(131,183,249,.5));
    }

    treechildren:not(.autocomplete-treebody)::-moz-tree-row {
      border-width: 2px;
      border-radius: 3px;
      -moz-outline-radius: 3px;
    }
  }

  @media (-moz-os-version: windows-win8) {
    treechildren {
      --treechildren-outline: 1px solid var(--treechildren-2ndBorderColor);
      --treechildren-selectedBorder: var(--treechildren-selectedColor);
      --treechildren-selectedBottomBorder: var(--treechildren-selectedColor);
      --treechildren-selectedImage: linear-gradient(rgba(190,190,190,.4), rgba(190,190,190,.4));
      --treechildren-currentFocusBorder: var(--treechildren-currentColor);
      --treechildren-currentFocusBottomBorder: var(--treechildren-currentColor);
      --treechildren-selectedFocusBorder: rgb(132,172,221) var(--treechildren-2ndBorderColor);
      --treechildren-selectedFocusBottomBorder: var(--treechildren-currentColor);
      --treechildren-selectedFocusImage: linear-gradient(rgba(131,183,249,.375), rgba(131,183,249,.375));
      --treechildren-selectedFocusBackground: transparent;
      --treechildren-selectedFocusCurrentBorder: var(--treechildren-currentColor);
      --treechildren-selectedFocusCurrentBottomBorder: var(--treechildren-currentColor);
      --treechildren-selectedFocusCurrentImage: linear-gradient(rgba(131,183,249,.5), rgba(131,183,249,.5));
      --treechildren-hoverBorder: rgb(184,214,251);
      --treechildren-hoverBottomBorder: rgb(184,214,251);
      --treechildren-hoverImage: linear-gradient(rgba(131,183,249,.16), rgba(131,183,249,.16));
      --treechildren-hoverSelectedBorder: var(--treechildren-currentColor);
      --treechildren-hoverSelectedBottomBorder: var(--treechildren-currentColor);
      --treechildren-hoverSelectedImage: linear-gradient(rgba(131,183,249,.5), rgba(131,183,249,.5));
    }
  }
}
PK
!<!!jj-chrome/toolkit/skin/classic/global/wizard.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

.wizard-header {
  border-bottom: 2px groove ThreeDFace;
  background-color: Window;
  color: WindowText;
}

.wizard-header-box-1 {
  padding: 5px 0px 5px 0px;
}

wizard[description=""] .wizard-header-description {
  display: none;
}

.wizard-header-label {
  margin-inline-start: 23px;
  font-weight: bold;
}

.wizard-header-description {
  margin-inline-start: 44px;
}

wizard[branded="true"] .wizard-header-icon {
  list-style-image: url("chrome://branding/content/icon48.png");
  margin-inline-end: 5px;
}

.wizard-page-box {
  margin: 10px 44px;
}

.wizard-buttons-separator {
  margin-bottom: 0px !important;
}

.wizard-buttons-box-2 {
  margin: 10px;
}

.wizard-button[dlgtype="finish"],
.wizard-button[dlgtype="next"] {
  margin-inline-start: 0px !important;
}

.wizard-button[dlgtype="back"] {
  margin-inline-end: 0px !important;
}
PK
!<Fnn7chrome/toolkit/skin/classic/mozapps/aboutNetworking.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

html {
  height: 100%;
}

body {
  display: flex;
  align-items: stretch;
  height: 100%;
}

#sectionTitle {
  float: left;
}

#sectionTitle:dir(rtl) {
  float: right;
}

#refreshDiv {
  justify-content: flex-end;
  margin-bottom: 0.5em;
}

#refreshButton {
  margin-top: 0;
}

/** Categories **/

.category {
  cursor: pointer;
  /* Center category names */
  display: flex;
  align-items: center;
}

.category .category-name {
  pointer-events: none;
}

#categories hr {
  border-top-color: rgba(255,255,255,0.15);
}

/** Warning container **/

/* XXX: a lot of this is duplicated from info-pages.css since that stylesheet
   is incompatible with this type of layout */
.warningBackground:not([hidden]) {
  display: flex;
}

.warningBackground {
  flex-direction: column;
  box-sizing: border-box;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  z-index: 10;
  top: 0;
  left: 0;
  position: fixed;
  background: var(--in-content-page-background);
}

.title {
  position: relative;
  border-bottom: 1px solid var(--in-content-box-border-color);
  margin-bottom: 1em;
  padding-bottom: 0.5em;
}

.title::before {
  content: "";
  left: -2.3em;
  top: 0;
  position: absolute;
  display: block;
  width: 1.6em;
  height: 1.6em;
  background: url("chrome://global/skin/icons/warning.svg") no-repeat left center;
  background-size: 1.6em;
}

.title:dir(rtl)::before {
  left: auto;
  right: -2.3em;
}

.warningBackground button {
  margin-top: 1em;
  margin-left: 0;
  min-width: 100px;
}

/** Content area **/

.main-content {
  flex: 1;
}

.tab {
  padding: 0.5em 0;
}

.tab table {
  width: 100%;
}

th, td, table {
  border-collapse: collapse;
  border: none;
  text-align: start;
}

th {
  padding-bottom: 0.5em;
  font-size: larger;
}

td {
  padding-bottom: 0.25em;
  border-bottom: 1px solid var(--in-content-box-border-color);
}
PK
!<L5chrome/toolkit/skin/classic/mozapps/aboutProfiles.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  --aboutProfiles-table-background: #ebebeb;
  background-color: var(--in-content-page-background);
}

body {
  margin: 40px 48px;
}

.page-subtitle {
  margin-bottom: 3em;
}

button {
  margin-inline-start: 0;
  margin-inline-end: 8px;
}

table {
  background-color: var(--aboutProfiles-table-background);
  color: var(--in-content-text-color);
  font: message-box;
  text-align: start;
  width: 100%;
  border: 1px solid var(--in-content-border-color);
  border-spacing: 0px;
}

th, td {
  border: 1px solid var(--in-content-border-color);
  padding: 4px;
  text-align: start;
}

th {
  background-color: var(--in-content-table-header-background);
  color: var(--in-content-selected-text);
}

th.column {
  white-space: nowrap;
  width: 0px;
}

td {
  border-color: var(--in-content-table-border-dark-color);
  unicode-bidi: plaintext; /* Make sure file paths will be LTR */
}

#action-box {
  background-color: var(--aboutProfiles-table-background);
  border: 1px solid var(--in-content-border-color);
  color: var(--in-content-text-color);
  float: right;
  margin-top: 2em;
  margin-bottom: 20px;
  margin-inline-start: 20px;
  margin-inline-end: 0;
  padding: 16px;
  width: 30%;
}

#action-box:dir(rtl) {
  float: left;
}
PK
!<0Z%%;chrome/toolkit/skin/classic/mozapps/aboutServiceWorkers.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  min-width: 330px;
  max-width: 100%;
  min-height: 330px;
  max-height: 100%;
}

.warningBackground {
  display: none;
  background: -moz-Dialog;
  width:100%;
  height:100%;
  z-index:10;
  top:0;
  left:0;
  position:fixed;
}

.warningMessage {
  color: -moz-FieldText;
  position: relative;
  min-width: 330px;
  max-width: 50em;
  margin: 4em auto;
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  padding: 3em;
  padding-inline-start: 30px;
  background: -moz-Field;
  margin-left: auto;
  text-align: center;
}

.active {
  display: block;
}

.inactive {
  display: none;
}
PK
!<(Achrome/toolkit/skin/classic/mozapps/downloads/downloadButtons.pngPNG


IHDR@@iq	pHYsHHFk>	vpAg@@`bKGDCyIDATxyTW;>bL2,RN3$h2Iz41IIeZQA\N\p	(,""hQqdbW!w}R_?sw߻w{﫯J~GӉߠ@Q2/@U\=>K3-]ﹲasyG;4&5b;kZIH9xiFe^菜׮uY.AರWJpxofEA~cyx(΢~F9<"d|Cx8`yCw]2^_-o'_u'pfL!9nђ9cZ."f2~rhC~NaNqM#/PҶgcAndw`7Y}
('pn-
D~ٿ
XҳUw_	z6(:?@|=k5.|XQ+[{[#Hy![Ӣܶj&{:fbs0ue=`6阐jYpKtN& ӱDMOrKzaFX=Wb,Bi_7:wW<׀{pgg{_t
e;&v?`;0Γ}53~ߵAЯ	s7K(sy70^aBi
RЀ
jm7ܨpf/LY7@t.@XN;}dǫǜ2s>Mc.I<,ifa=7.A̬oM֢ggwsèU;ow18bS~f[pkzm
ǫAod6G0uu#ڝfF٨,J8>(k_;<=kn\èS~GfG=U}WnOπ8p6!vg#l
+88cO=vYDI'x
r̿6iNX7|T:^gc	ww{T{rXRu{p8
^N ̓2<XђHc쇜>c:Hd-97~8齱Z>zYp‡ԓ|mFQ#M_:w-x5HM?lOG~F%82c9"1l͌w#fu:'>=?5TO۞Wӥ)S
PկА

1gY~w/?2===|e?#!F>ւ=P</Uw}v~Wvv>џvJWB-iryHH9xsd(ҕիLԂ2TqjEX(64߾K9+4ZP5im:oȯyg|CBǺMx,m-]'@
fP6|u=d5#,][*byEsJzלy^%3&peݖsyoG؜z,@ww{.ԱSt]\eū67j-PjagSnfEs*
TQPPTm@>g,]u*#9}"ÏQȦ b~{̿,Plʭ-F<~w*5w&!L'(ԃbE-(BaΎ~bUN[[lVX3hzYRJu;1/	\*w@VesI!y:x׸T<+%C kkjC|^4@AI`6>}MCđp:1qsssۙ.\I(7Aʳs'k蜲mbT<k+kq1HP7XYǤ]XdǫWh9sENG11c.WqAZAX4Ǎ7:=+^٨}t 9/	s܁O7)W"
wrՠM\s-r+Wsx(
(k1?SNMU44A)_W,@=["X*;XR*
x 岎#,mAa
~L:cv
؋x'1q
rh$Ӝ)/|>wV<m"Tk'b)wǨx?\յs4Z#T(xe:]0O>+-8G׍<;mhsH(ZP_d~_{q?\
>ޣX/<>b'JtYK?qI(G5Ϻ
nx5UBc'[PrmyqXr=$/#qJw`Vg&`OSߤCVV
>Vs;|UTVvfm6*tU_і=RC̫?W^"l7
wKCB:͑|}y_7@2HHHxדܱcG	4G6QEeΝGC6$Ov~GףN<-dmB<ir#6#	4G_/v>˒0`;U)#NnF>}|4<|,/15FrXlW'>$ϱ'z4<:9i=9H("Y\_2qH|Ȇx02Og
9ߋ1hO>;.n	FY&}GG"vɐs^앱`/ǝr]
?j#FX}X1pI&1*ޜ)bXC׌(x#`Y9f+9ɌIΜ9OQor"ˆgo]v)`?.IStr
/;6\Q/l FX_ûPZeee^p!9柁ELH9Q\A'`;{ʕ+0,lf,ݻ?B갽g؏2=p&@)#'o@d"'u1k@ThLS`r	g29Q1y<0i;&(*qv.oe!OaQ=G4!ƻsέMa̳Bưl^	di#*kSMp_\7c#,ۆ1$dT˲阐.S؀hxZa&PKiiLH9f'*Az> Hc:q|Q~ --Mp΁aX\-0Q }K	n1 '%_9LHٻE藒8wc#Ә\63'XDoH	
Q:
cv梼_Ä\A^*,pPA[,Fv#,7HoD`ڲRpPdB:͑M6&"!-hӜ-e:&o/Z/J5ga\68gbAp,^u(b$/!9ĂppKHa0w&,]E~M}/22J%g? OX/| ^Mw@vN4**<DDE{mٌr#CyY.?͔_%SSS=asB^UUtzf&3y^a㦘C)rٹ3<~Erɳp|~#1, ?Vs-FA+YYY<pGIH9I;o*"OIOIF2{?jkk=WY6l]~/~shzuu'EѣƬ7NsdJKKիWWZ+WTXBl256…^|_t^VRR"PA/ >/oIF9~
9xB';Rش%K/^󩪪䷓xR>kS~b'c5w
@NJ_t:$$D^hMR5"?؜0&_Kp'˜|oڴ$Btp
D
 C⃂.Toݺu`yY$+Kw1tNx&c$)իWȲ/2/p


n{iHn۶MH<_:&SH#vt|-񂝊LRbzS˗O`|]BW<RbHJxGh>IxN
I9HyqEwm"-_#YWͶ`DD,:$p
:;f[`|mB^a,܌)'fh$R!"Oە<xPLJ
!f!N
"

ĦNs)6y'6&_k<夝LLH9a=Rc>YBR𩨨09-aU|lRRF//^‡Pp)lEϼyԦ2w\z-|,t)HcLyWIz͝N=}Ӷ;gU3''g͛ga}~
O$_ѿ9,s"zTXtSoftwarexsLOJUMLO
JML/ԮMIENDB`PK
!<#.hh>chrome/toolkit/skin/classic/mozapps/downloads/downloadIcon.pngPNG


IHDRw=tEXtSoftwareAdobe ImageReadyqe<
IDATHǵUoTU|t:Ni+肤$$6lL5iҌ_1P5

411/`	Aa'.Cigi}9Ww;s}VxГH
($w'|Aj<=6|<|*g]|ΞT*hj-
`06]z8<<[fعuk׮)
||֭O5Vm>R	P	JPp؁P?Zkr>z̙rmF 7w/\W.ˋbV"9028%"P(N}<269,H&f{h7{$bc$	Y/..
M?m;h׮?|?@7b8X
P7R`#rn	]kkk 'o]y@`a0Ξo}),xspX#}@oZQ.[,Bq	$dMyu+a.
N:y	prZ&0K_q1?z
w!5<
Z8^Nrp?~!ISn7+BDz6ovx&i-wqh4WuE	!GII&{A6,Wg*TubN;
dI[f\yͽARYy9bࣽM㣵ڠd^/U$TU1s]*iq+ր}‚Y}npy.vٕ3!ù٥l6Jw,tC&廈>%$|uxCҗ.]qG%-wƵI`Y!HtU*F~<O3A
F$8
+m)%)~dU8<¼3?Е.4M7'em%CњQ)EFrD)6o`/矅gL7*ʖfƱM1{K6μ1<|L{3^qlʩ=g)k_
	 B֚t^m}ӷBK<'0?B@?<*Y9iܸu_JUd|؜V")Y%Lx-R͛ohOJRǸT؊nC^jF@
p%mo$t*}s_?!X0$0Nh+84֏+IENDB`PK
!<-#m))Dchrome/toolkit/skin/classic/mozapps/downloads/unknownContentType.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#from {
  margin-top: 1px; 
}

#location {
  font-weight: bold;
}

#contentTypeImage {
  height: 16px;
  width: 16px;
  margin-top: 0px;
  margin-bottom: 0px;
  margin-inline-start: 0px;
  margin-inline-end: 5px;
}

.small-indent {
  margin-inline-start: 15px; 
  margin-inline-end: 15px;
}

.small-indent label {
  margin-inline-start: 0px; 
}

PK
!<óg8chrome/toolkit/skin/classic/mozapps/extensions/about.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#genericAbout {
  padding: 0px;
  min-height: 200px;
  max-height: 400px;
  width: 30em;
}

#clientBox {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}

@media (-moz-windows-compositor) {
  #genericAbout {
    -moz-appearance: -moz-win-glass;
    background: transparent;
  }

  #clientBox {
    -moz-appearance: -moz-win-exclude-glass;
  }
}


.basic-info {
  padding: 10px;
}

#extensionIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg");
  max-width: 64px;
  max-height: 64px;
  margin-inline-end: 6px;
}

#genericAbout[addontype="theme"] #extensionIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
}

#genericAbout[addontype="locale"] #extensionIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
}

#genericAbout[addontype="plugin"] #extensionIcon {
  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
}

#genericAbout[addontype="dictionary"] #extensionIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
}

#extensionName {
  font-size: 200%;
  font-weight: bolder;
}

#extensionVersion {
  font-weight: bold;
}

#extensionDescription {
  margin-top: 4px;
}

#groove {
  margin-top: 8px;
}

#extensionDetailsBox {
  overflow: auto;
  min-height: 100px;
}

.boxIndent {
  margin-inline-start: 18px;
}

#extensionCreator, .contributor {
  margin: 0px;
} 

.sectionTitle {
  padding: 2px 0px 3px 0px;
  margin-top: 3px;
  font-weight: bold;
}

PK
!<f^__Bchrome/toolkit/skin/classic/mozapps/extensions/alerticon-error.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 18 18">
	<path fill="#e62117" d="M10.124,1.324l7.705,14.127c0.234,0.421,0.228,0.843-0.019,1.264c-0.114,0.193-0.271,0.347-0.467,0.461c-0.198,0.114-0.41,0.171-0.638,0.171H1.294c-0.228,0-0.44-0.057-0.636-0.171c-0.198-0.114-0.353-0.268-0.467-0.461c-0.247-0.421-0.254-0.843-0.02-1.264L7.876,1.324C7.99,1.117,8.147,0.953,8.348,0.833C8.548,0.712,8.766,0.652,9,0.652c0.234,0,0.451,0.06,0.652,0.181C9.853,0.953,10.009,1.117,10.124,1.324z M10.264,10.695l0.181-4.605c0-0.08-0.034-0.143-0.1-0.191c-0.087-0.073-0.168-0.11-0.241-0.11H7.896c-0.073,0-0.154,0.037-0.241,0.11c-0.067,0.048-0.1,0.118-0.1,0.211l0.17,4.586c0,0.067,0.034,0.122,0.1,0.165c0.067,0.044,0.147,0.065,0.241,0.065h1.856c0.094,0,0.172-0.021,0.236-0.065C10.222,10.818,10.258,10.762,10.264,10.695z M10.284,14.448v-1.907c0-0.094-0.031-0.172-0.095-0.236c-0.064-0.064-0.139-0.095-0.225-0.095H8.036c-0.087,0-0.162,0.031-0.225,0.095c-0.064,0.064-0.095,0.142-0.095,0.236v1.907c0,0.094,0.031,0.173,0.095,0.236c0.064,0.064,0.138,0.095,0.225,0.095h1.927c0.086,0,0.162-0.031,0.225-0.095C10.252,14.621,10.284,14.542,10.284,14.448z"/>
</svg>
PK
!<BX2__Jchrome/toolkit/skin/classic/mozapps/extensions/alerticon-info-negative.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 18 18">
	<path fill="#828282" d="M10.124,1.324l7.705,14.127c0.234,0.421,0.228,0.843-0.019,1.264c-0.114,0.193-0.271,0.347-0.467,0.461c-0.198,0.114-0.41,0.171-0.638,0.171H1.294c-0.228,0-0.44-0.057-0.636-0.171c-0.198-0.114-0.353-0.268-0.467-0.461c-0.247-0.421-0.254-0.843-0.02-1.264L7.876,1.324C7.99,1.117,8.147,0.953,8.348,0.833C8.548,0.712,8.766,0.652,9,0.652c0.234,0,0.451,0.06,0.652,0.181C9.853,0.953,10.009,1.117,10.124,1.324z M10.264,10.695l0.181-4.605c0-0.08-0.034-0.143-0.1-0.191c-0.087-0.073-0.168-0.11-0.241-0.11H7.896c-0.073,0-0.154,0.037-0.241,0.11c-0.067,0.048-0.1,0.118-0.1,0.211l0.17,4.586c0,0.067,0.034,0.122,0.1,0.165c0.067,0.044,0.147,0.065,0.241,0.065h1.856c0.094,0,0.172-0.021,0.236-0.065C10.222,10.818,10.258,10.762,10.264,10.695z M10.284,14.448v-1.907c0-0.094-0.031-0.172-0.095-0.236c-0.064-0.064-0.139-0.095-0.225-0.095H8.036c-0.087,0-0.162,0.031-0.225,0.095c-0.064,0.064-0.095,0.142-0.095,0.236v1.907c0,0.094,0.031,0.173,0.095,0.236c0.064,0.064,0.138,0.095,0.225,0.095h1.927c0.086,0,0.162-0.031,0.225-0.095C10.252,14.621,10.284,14.542,10.284,14.448z"/>
</svg>
PK
!<MRuJchrome/toolkit/skin/classic/mozapps/extensions/alerticon-info-positive.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 18 18">
	<path fill="#62c44e" d="M18,4.796c0,0.31-0.109,0.573-0.325,0.79l-8.408,8.406l-1.579,1.58c-0.217,0.217-0.48,0.325-0.789,0.325c-0.31,0-0.573-0.108-0.79-0.325l-1.579-1.58L0.325,9.79C0.108,9.573,0,9.31,0,9s0.108-0.573,0.325-0.79l1.58-1.579c0.216-0.217,0.479-0.325,0.789-0.325s0.573,0.108,0.79,0.325l3.414,3.426l7.617-7.63c0.217-0.216,0.48-0.325,0.79-0.325c0.309,0,0.572,0.109,0.789,0.325l1.58,1.58C17.891,4.224,18,4.487,18,4.796z"/>
</svg>
PK
!<o__Dchrome/toolkit/skin/classic/mozapps/extensions/alerticon-warning.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 18 18">
	<path fill="#f0cd2f" d="M10.124,1.324l7.705,14.127c0.234,0.421,0.228,0.843-0.019,1.264c-0.114,0.193-0.271,0.347-0.467,0.461c-0.198,0.114-0.41,0.171-0.638,0.171H1.294c-0.228,0-0.44-0.057-0.636-0.171c-0.198-0.114-0.353-0.268-0.467-0.461c-0.247-0.421-0.254-0.843-0.02-1.264L7.876,1.324C7.99,1.117,8.147,0.953,8.348,0.833C8.548,0.712,8.766,0.652,9,0.652c0.234,0,0.451,0.06,0.652,0.181C9.853,0.953,10.009,1.117,10.124,1.324z M10.264,10.695l0.181-4.605c0-0.08-0.034-0.143-0.1-0.191c-0.087-0.073-0.168-0.11-0.241-0.11H7.896c-0.073,0-0.154,0.037-0.241,0.11c-0.067,0.048-0.1,0.118-0.1,0.211l0.17,4.586c0,0.067,0.034,0.122,0.1,0.165c0.067,0.044,0.147,0.065,0.241,0.065h1.856c0.094,0,0.172-0.021,0.236-0.065C10.222,10.818,10.258,10.762,10.264,10.695z M10.284,14.448v-1.907c0-0.094-0.031-0.172-0.095-0.236c-0.064-0.064-0.139-0.095-0.225-0.095H8.036c-0.087,0-0.162,0.031-0.225,0.095c-0.064,0.064-0.095,0.142-0.095,0.236v1.907c0,0.094,0.031,0.173,0.095,0.236c0.064,0.064,0.138,0.095,0.225,0.095h1.927c0.086,0,0.162-0.031,0.225-0.095C10.252,14.621,10.284,14.542,10.284,14.448z"/>
</svg>
PK
!<U<chrome/toolkit/skin/classic/mozapps/extensions/blocklist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

richlistitem {
  padding-top: 6px;
  padding-bottom: 6px;
  padding-inline-start: 7px;
  padding-inline-end: 7px;
  border-bottom: 1px solid #C0C0C0;
}

.addonName {
  font-weight: bold;
}

.blockedLabel {
  font-weight: bold;
  font-style: italic;
}
PK
!<EF[ss9chrome/toolkit/skin/classic/mozapps/extensions/cancel.pngPNG


IHDRH:IDATcNO=Pd@	P^ 
E;O@\dl?vz9XiIENDB`PK
!<
bEchrome/toolkit/skin/classic/mozapps/extensions/category-available.pngPNG


IHDR  szzIDATXŗyp|ͽIH d(P1.-E\qa:EӡVZv:KAP:uXiAAEeA~[N2Eo99{s#R|syglhߗ]x\M4I޲xSl\иX!4ũ.5hb3o8y{KKKW'\VqݗLwW̚5?+~WuќeeekD444(!X^Kgn*VsS/=kgߴ6uy7dQEsqf/;tbR*Kwl}E׭>f}>7ÕoHjs.emtМ\thcQY_ۅb`|nhT^Q>
A<V#[eP[/NԀAfAɜw45p.u	MGxco!Kaa\Kyx~
	@D8@CkݱF^m	_]A^hW\.H2"(f-ɷ
w&GZt0N@/]a[p$H
ǃVxJG(P @(kLp2qR s<skrn:
UZA]H'%
s+LP;I%:fGbbwA0jM}
#Z
D0=t@
:B@Sɖ>4g"	,Ϋw~yyv)2
ڠ.h.(wdqS
|O>%5f3<Fr#׾pm@"
P)?X?&íu
-=
5oD~:Ѥ@HweW#fLskPuk
+xXғ(t%DDw?![1),lSCDР5J<qïf秷02; =xɤR i0C&VnZB&Pی`R[jtlWJ%[Ċ_X9K7~H[	'+P*s2eOH0$5"@a̽qMyclm&Jzss3~g,Y9$S
ìʆ甌`nդTmO;EAnɬ^q#KDsC#GKmOC*Tǯ=EcJx޹7⏪~:<>Y'wpD
ChNs#Khclgןrsru'R&	ONc4v.fBv?96r\3=z]՜ !Kgo܏9#n!њJQaɄD½I \m.jje._}r̽Q&hF84Tet
kc0Vݝlj<]i&mR`<1$iS9);daX>LۆT/IT	!r:K)K_).3~0kˢók̛2on`TLFhOcۉJT@qc>W"c*idG„l[{9T
#Egvv^|b&0t9aՇfTbDŅ}^}>C!M1X'v
Ӿ6}"Xb`f
+|wnY֙4bN>PՎ4m4穃Uy<sx%UedHa`XjNF\Eµ28[&,G7y<9>u?B`l+%KDpI:%
B0{O|Շ
tW"6uc2tG{wm;)a<Jӥ:^ՖQYiF2lu>:t/#M>kI"a4]񗂣}{tVsjW׆o1o#i{W|ݍЀ [+/ė,
(
Vm5;&&5FlDgY`ܱte`Ƚg3k_6M?<CmIENDB`PK
!<v)KKDchrome/toolkit/skin/classic/mozapps/extensions/category-discover.pngPNG


IHDR  szzIDATXýKW[U陱#cO1KEdH$2Ɋ@d
y.fÒ[Vl@,(+)B,BBL&=tM?<XtW{eE{
QƩ_% ău?p8
_ .9#wBTsB 1g_<
0<L3N>1oIJZlѶ8Q]?>v=:<s׾e{*/ÁI^Wz03~[>JWVI!1Y]xGJO$ƍX,_gɅ頓_{X|+W~233p8q9i[ݕgn_܃~kMRʞe'O|*qRv<ɋ</xkϯ~v\"v8^___8P$_xMcO^wkqxv\ed0݁@!`f7%gŻ&YV߾SoOR}m'4M13(T*y>tJ_SB^w~~8~p`޻wh)LH&W>2;<Be_ܹt	f6eLEĢ(zX__?uMX
fPff:WU)gUQ-TEMD4T03TPաwD<ѪI"w8[wgpI!t^7EY"-0U峸;i3=qGmY5Ӵi/Mv=ZD	jzxbfn7n4FiZJnRU5R/)dV3s lcðQ!e*!ׯ):4";DJ!rwKݻ$qBfvv$Iv5'g"RG}6+Ϭ07;DZcm}geWmD Thۨ
LaNv\UUEEXXZv@:q5en~ al^a2>JnP3@:A`(7QΜa:3҇,[
xrtly )Ӑn͛ETJpDb>TQ٢1N%`q>\4qw?Cz#IENDB`PK
!<pBchrome/toolkit/skin/classic/mozapps/extensions/category-legacy.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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">
  <title>legacy</title>
  <path d="M12,6h0V5a1,1,0,0,0-1-1H7.78A.77.77,0,0,1,7,3.25C7,2.5,8,2.47,8,1.5S7.13,0,6,0,4,.64,4,1.5s1,1,1,1.75A.77.77,0,0,1,4.28,4H1A1,1,0,0,0,0,5V7.25A.77.77,0,0,0,.78,8c.75,0,.78-1,1.75-1S4,7.9,4,9s-.64,2-1.5,2-1-1-1.75-1a.77.77,0,0,0-.75.75V15a1,1,0,0,0,1,1H4.28A.77.77,0,0,0,5,15.25c0-.75-1-.78-1-1.75S4.92,12,6,12a2.58,2.58,0,0,1,1,.18l2.2-4.4A3.13,3.13,0,0,1,12,6Z" fill="#f9f9f9"/>
  <path d="M15.88,14.38,13,8.68a1.15,1.15,0,0,0-2.06,0l-2.85,5.7a1.15,1.15,0,0,0,1,1.66h5.7A1.15,1.15,0,0,0,15.88,14.38Zm-4.45-4.09a.58.58,0,0,1,1.15,0v2.3a.58.58,0,0,1-1.15,0ZM12,15a.72.72,0,1,1,.72-.72A.72.72,0,0,1,12,15Z" fill="#f9f9f9"/>
</svg>
PK
!<&Cchrome/toolkit/skin/classic/mozapps/extensions/category-plugins.pngPNG


IHDR  szzIDATXkE?&yMޘھ6H*xBZPxEA*ޅGxk<[owwo6kҋ{ٝ/|<3<')?:-ϯuMEܜjD?s}{z7$ȯi΍c0꿞ڻ)[^RQ`o05hbD`}+amgF~v+fPI{-W7r k2"3
n̰	

ǐF1CX"hkEjx_j
t{vG1=._:ɮ

u#:qkqKۘ85i0!pwi[.">JcgϿUہ/~X?bܻ?B Y#4$3"K-]gk"bg*!Ghk#K0FނչK[ekGk"Lӝ&ds\J͉ggv#Zmksf3M50yOL_nzH^|P\m9I|4{WU4>}4]BqQ:{9|mg9P~:AI
Ғ<}R:BHġRZ\*kHE{
>@O+AI} `}; +i""h-lEi}%Jzi/@$
øPD@uWW J";*YC>6txy~)N8`[wL`5@R)%|=	[~1W@'$+IENDB`PK
!<砦Bchrome/toolkit/skin/classic/mozapps/extensions/category-recent.pngPNG


IHDR  szzIDATXŗypVs,$@IdahdJP[TZ-2BkQ)uZetlqȈVZQtʢ(U	D |@~r=#`RΜw>y}{֚~eC9K_xߋf{8K/R9kx6H?vϝev+xߖ1rHkϻ}ܵ`uUU?^<ѣkWoLB!-;Ҝ‰W^7tO<:H4Q}+)2~>ERk6Ԍ|Ch/cCNKouȾUSE#k+܀OVX%=wg#@a8`{UpEWUIu~9n's\(XV=a֏QƝ[>"`Ԅ55zdǁHGeyxaҜr'jjn }2J8`DAQ<_^TٶLZɀLQhoTLe>R٤)L(	;;MB^xpgtY*'^77thWN%ifsj=WкM ]дt9aɊ@QQQ@}}=@?
St{Ǚ)<A{|\s>sy4n!3Oz0uk]z?u` ٩G&dR1H!'5Q=u4$?XOvjBJaXƓTszeYxxZPyYU7i=q'
N
217,9*šGjp-TeE 'qt[}ps'b1O=8fԻVKs{7'&	k<Y_Fp`n?w-/?zX\H
$vg%FLx $36οAf4DÂX%(6_x{l˭,<00MElfgZ,**"++]mut{Mw'"ΰ`梗)yYOAs?oi!-?K槵]g"~~q7~iCӇu qcS<:fG]6HGg3ݵmO&jjjb'޶`EƊv#H\H{yVc&</6ᤣ!CQVm!%Ys6.ü	ءjwsf	ҙ͇譣uؽOn00 w61i)N#
6PCMMWa1񼊼Lp4M!p\ʔ()Jb
eJ2ZW>aד@Hk̮fU;ph])4!ҔXfw{!IJ-l²M|>.QoPn:_ߗ#FV]3z)%
0M&q`4$TJzlR̘qOo>S5ps/J e3qA2,|}I-㵕S3bV?ۚ]mwz@P_dM2W>eM,UӔdbhמPkvmޫ6g6$1qjW뿩IZQ"`)Lۏ	)20´$9yAcWZk:"旔oލz4		w5cS00PC`=%!EGFŀ{&8m,'ɮSmi\.RUô%&Le)R(e m8n۾h]fiqvve&bu
Lel{%sss<,i`*6B*I*.glv$>٢ofu1=)e:BGgF2O
l@4gMep>ٮpléC@ֺEkupyZef${h|Ee1j}5EWR/-FӲ˷x}jk }t{Ood=+1?-d'2{#{ڷFk	cp)0/JׇIENDB`PK
!<`(
(
Bchrome/toolkit/skin/classic/mozapps/extensions/category-search.pngPNG


IHDR Vό	IDATHǥWyPU?޷/ <V	 D
Th@fmiL:#NgE8idb'6&h*(BTBX}<x}jB|3߻n=q<d穩Y111E###A0?OMM5&Іg,GgGܧP(Yc I[ ͭ㎔Ԥ$R)2 Iaj}]PMQ{umEYZ1uLgA٠Up.;5W?liiy
<X^^~Xi9R)ưF-HD#.>/*6QaN:uޤO~n81?VimiII}
}ӥnz㓛:eW1Sn>]yT-˥>\p3㒌s5o_9HOx3|;MwtxN%9}]#α3mѱ⧪6,s-o`L|Omݺ&S,mL'#G !$ (˭֠uTrOnTr|4`]ׂ^|E*{Wuޱ35gC[
&WfFga@#~ȒL"
b0$s|md`??h2[m++mٳ;'bilMò/Ƈ*iR@YkIA"Pm6f8Q2-KLL]xzKvO9r\^*.9&&pA"~GXz.[sWwu?WtI2"a}#@͎'z֡"swd0/!	DƂIJ/O_:"yܴS{O\Vѧz{{KPzCXcۣm՝.H\{!@ @!^jr:g 3jo[KWn.Nm[-=77?Li޴"9^Y
<
u2P((8̑QMOЦ_ji"gmhh^(
v֗T(7|zSEf"5N֪QNHw-D!,dw~ei1Q::yd`aiQQ?׬%w)N'-jq4(p& JB),Fc֙*_s{
):qě(Oς"vɷ:'Җ;Pi\qS87s^"9YKa2b#6#;\ӭEEf:UwpB%@\lH鿵E}7V.7?J
7홙ODnIa\%!c]MW5fu;ixtꎾ?>>Bs'$$Pww7|>R*^zpBNG޻L|i	*;aOQz/Vn+NC{~1+g}Ɗ+%,++m3
TjjBC=TBÔÕ6Vǥ=5:-MN#ؐ>Bgb=fw\oL.[i`6o (IK꫊өdr}䘞&N!B;VxŨkݻE.*l4L/_<+wR24!?DjL:2QeΜ>-r	A4:sYѵ`<߲e(eggS\\!v`=)))ًh4
Hn&l4888Q?66	طV\w܁ΐ^/c_Q
`ndffRrrrź:jllLKPʈbWWPlZK'Xnݺ7֬Yg펀Ͻ0w`+MLĤ92SRYM7"*++gnj9Nb|~9#9))&ୀ8s<}
72HUUU#{9xVK/pGg5#ơl6jkk$E
oT*P?~|7c^vV9/"1gB:rYfX11RR| Gds }H.^xaqhܙa '''"rH/;S(o}(7aÆ
`#!0>

@vdG\b/<!ΪիW?yKJJ5&0ѣGoZV,p
Bbyg"hHv	%'vڥbqUoP5W_g5ϞsQtV*m뮻@+
6U&O><scXFd@	ХK߹~'MIENDB`PK
!<N'Cchrome/toolkit/skin/classic/mozapps/extensions/category-service.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<IDATX͗ߏ]U?ks;?:ձTBP(bPAB}R$5%A@Z0ҡLkBaLog=콖^;-J}q'+s=kZb i0@o1aY(I pp)b)}&<)P?!.p@_O$U9xx~ϾnRLj%luTGoۺBC#3,+T<]<ūVȋOxkPOut8AqjBe}vެϡoѳ1z*fwm޴~ڑV®
QOB-'.H"m~'Jy0;yMZ$OZتx:2
'ΞJ	
JOAPPF|pY=-=-{%8K~.I+ψ@I-^worZ<H$+T<4}=	Վ1Ss<I~h^js;M:͢@a-	4ZI\O^n7|=AtLAW8QΊ"M{vJ7V>|3
*2cmXM~+>ȣabdQ[ZqEQpX
+CYhв۽~dǨ3!qon`ɢ.fF=3d^
VV{I%!DscpB^8'p@~+>GQQ9?nz|:<#ǟ.	l=zb`euQ0:*l]g 1lcƣcᗮ(ʣO
"ny9QN1B	ây)v~B4*Rh)_k5lX5#k$\UTv˕e~%ss@Fvvgy|7vP2DH|IDE4@ZNT @*3ZUS3z{JՈYOE,?3DB#bfQhYE	7A؏ai)A"9jB"*ֵ2\P-St"I`aNԻwq4AE*!HX*k8HHJT%	pߙgDwnLW:opp5+	87DJ6lHq%*IYl1~3۽@֛SL׈Y+Xj(F^8r`}k'{`q~WϟI7yg_߈(e8"{"k,MmgO	[BޒQ6;;"ƾh6/Ԅs͓˃j5IDD!vΞ~OI#ϮY2$IjP5c|k*oܾ_9%,ɪU,~ګ7nTRޞ<;')D}ΣƋ,4c(BfNf-dv|k%ycyEyws7V,[b7f`QӃy:EhbcFl̋flyc$[>cw's @\vɆͫ҉[CKNԏ:MT++_h5j1XY!1`sfΜ
E.gMb_ta:l&F.ylO"3QiaY,2!W-r͛KvY|-,J>Ѣ!j*fj M$9hD7v?Z5^9
ֿC~3IENDB`PK
!<l],Dchrome/toolkit/skin/classic/mozapps/extensions/dictionaryGeneric.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<#IDATXŗ͏\G=_'X 1ز,!JXe"`Ɋ-,@V%@FD$ ƞ=~ˢz,axOoSԩ{ιϗ<x@?u2o+Nǝg׏X<6ҕEQ=CBG0/y}8=>|j|S_,.l͛\H7
Sl͏צ?r'Np	ŗon핧1О7vugl?f
xva6C"8,p)bSD?!OlLtg
 u-6cmÄ̝x^!&	{pbFCO(5å/7/
+gV=aus>#>(y@y+1G1,?J
ijC4L(Vy=3@i03L\ߙ+C*W!<
MR@1@^TcGe@UJ)l$!*@PJFl@5ձ2[)q+UT3Q
ITq+OeԂ/ADQ!J&2Y2{tӾ!q3$z 0LK!(ؾMnOs"iO/5$'\3/3ܹ{ӧU}(!@TK\VpwDza+]̥:oS6>

xfr`m4v΀x1泳vԝ&Wg?
]gWhE^3-
//vJdL7٣֖6AJ-Ǟ^6AXfr}6zCZ^1:AL'x	qĩ%
Ue̅Gw6|Ǭ/(a'Iu8|Zݔ0*cS+C~K&n[lmaui[FDx7|=H	T@$bn;F,l<'9RQAm'x"`mt09
[ʢr|ܢp$ªsJ_n
 U!+h+JQP@
@2CN:X1H R/@KFvB:bh{M¨vݤZB^S8Vj`*'fs@FG}iVrٵzQZeXAdѬ/Ceq6)4eSIaA}MϺ{+[x8@}-5´p4u(%ڨH
"HiL 
s_PX2w˹=ЁCZ%g $	̼PYNf
,pQ]oTUdxyD.}
Li^kR<IENDB`PK
!<mڎ..@chrome/toolkit/skin/classic/mozapps/extensions/discover-logo.pngPNG


IHDRWR!HtEXtSoftwareAdobe ImageReadyqe<.IDATxwdgu}{oŮy{rVI	dkEXbב׀KFlx1fi4Y{:U7=jFGHߵsVMWsy9ݬikmcws%lGG\#Vہn7}z_]w~JIHGv3t1]=k.˦:?-?k:m{9
Qu0e?ekW~u#}cD3DTW>%ms;(lH^*]3=pc[CI1߼w_;^-[mpg]:~_R;V0rc)FF/VNJ_w>/[x=DJS.*BJ	FF(Y0-(HgL_1t%<T>B)83ZFsõ~3zͿ6mvGt
зb!2,*Wq|@xzg=3k)#mT~w_#~$7Hl()X)eݺڟ$"D1@bUjVl#_Wnz#^ںiimhEaa~<o
[>OnǷXq"C&C6ۍ_U5sn2EKG7\RHʋs,L876
8~u?SOOӿ>RAP^+R^b2V՚_?~`$29<SgVc-چ|⧚[qEVQ@X+W
Q09raӆM?8Q?b,xVa~f3,Na0k1M,ĝ?aA`>_!X~ؙ2FTQQf
XGaMh>?-aߧpv}瘙< Dk0g^T?0pYUA#2nU[g'NߊKtgwM{Zk/5`hc:5[bfv;^Hl岿koY22똛^N8?KXBi2
(~*)^G`KR)/'~HּZ5kƀk%gOq[QF]B;AxDknnaշeۯc͆_!JbeZ:u>}wx^pSO2s_OV8+E;}C[1_Z	1$Ql/1<w	jTjEJijQ_5!7]źM}!4qlYO~2/:=6y7I'6!]w5b}>]{냧y'i@5e,J,09=ͪ{~钾^8XadAji)VH!r -	{I];GϨ_vb\TZЄ
cb⠎֚n2nV|Xw͢1۰cfE)CK:ų֬Z劫v0J79@:^YX,SıKzGvWlhݑt.f`jvyŲ}p JGhk!ޯ~g|l{lwoi:	e HTldRfڕXM}N&RA:&b
*Dtvn]*pbߗ~QH-[7X'Gǩ>Ry+)\0UZ:tO̍'1!XZk~sIP@X4kwͪ[V&t#*,c:*UL4j?ڠaɃc˞stϧ>$
2Gƕͫ!"bH!'+ԽkWԉ֘?q]Vk4F[1X+0lSƕAKu⤻@zX1QZy0PAHA3&<7=l`Tg&X[:zhd{N>gZ؍pb0$sf:ذzNAns	2^
Tn:ZOY!1߼\HD+8I'qiʕbZ= lZi~<1AZR,R,ժ:V[!s{T{y>k{.ZkTSs3v+kR
<(n
V
췞-#_WH/yKsLצpfUC!$Zl+-hPIA-O4IVfnv|BsѩS~HՐ^ï׈2—7ԮEkȊLA
sk#?F#߹xDL&Ti/?hXauEi')ej~@s0~}z7T.F+~NX"z~p{MH!$mjts*y6նak
 Iٿ5#i_86ȑ̭2;QPh(Zn\,\ktmBziP*PZ7ke<O?wiި|{۲nCH8P""B? 
rG[ɯ^ߞm͌ҹ3O14hbFEk?kF}8|[u5-8np:w{BcƠc1*U
Vc&p-/嬍|v~b1ZSc:Ck"	-ƀ#c2G͕/{	l8XH"6ڧئj%V,==$x^l@z|Og.X۰FUTb'=0]OkK@M?AGa{\|fhf<,?K?k=v9i\x-Y:i6l2)-e@J&{,XVM̂fe9Śaz†
bo;_p:dLdۤU>6#jEdOmF=dU	MƧ@X1
U/eꡏ.	oG$9Ÿُ.RxisC}EVNК-b׫sx׏
.zի6
R8
/i-Ut1Fo{)M30063MOҮlI,"&,b(kew[lKV
ӕ#'-	.uß{w{H^n;?kun),@J)=f''䑢yoK9E$\E6r1z$zrI#grb36^[	TȪ:͠kSXI
G+GBkK.2iWZ\iIw`͟}/}[XьVG?	xR&q\=2@9'
``x\wߊ:BnR^#%Z֔Zd!9LI("t)u6!5>G\9C\'LOHoAcbkpAJ)pDJ:;Zɤ8\,^[>]'!1Zc7޽!Z&p0=AhG>7>ew񁡍{ⷥ8z	HIZY&&'h&)}VBqT:ĥ1Ty4JT@uQ.F.F5G8hnk͒Ny8JCg7CLOcA[trHHaFi]mՆ+$B)̄9 \	JJBK*AnX)
ڟ'.",U0*Mgkw;Wt=4Um\ܟ+@Jqnྵ%9Haqzc^Z'{/Ø#Rm~fraYyުd2ی]#HN&IP
"Rk&fKiTXz.F,wV2ؒ-)Z.҄ʐp$m)=gdS;$Goabt#SRtC	j{Y x]?
O.-j+hڂpq^D*Eb**TR=fx&޹V=ݘ(e0JvNJP)bc&҆@iJ5UxGNzM@d7OMMLKE7x8GavR~)$ʼ6;p(*SV{z<-Y<~6ji2I(V
G>1ڌo2t+Xb4\HhBd3ɜJ&qdS{-yCn<b=?xS.p=jWK85CT;ԍ7/_vUFx	sI$\<w@ʒ
P+6mϪ)F%Iz2ib7q֢D#XjB8 ulxAt9Ṅq\Eq[:h-8T;M1?<'"<]	TG5]
upsp:YVԴ!}r+M'mR"8(@FS(}b4r;Rs<$
Ť1p,9[:	=t66z~ynyXt?-ΤJEwL$Hx.眍LRXf3uݸҢ5×8@[PpPp
th=F܄qq@aa7fX^t?Zxgf(!Der!o(|./w9.=/~v=3㸮l(u$	/Au\t:&B[gn4r.2"U1
EᡅFZh)Hd `x/4rFǟy=Z=51hYl{~~d"9dRtv~w.g{.'4yJm/W\zKnW-iWRLLp$s|3B@\$m\
X~+}C-xlXX.Zxhi"@S~
p8fkhHpp^؏q-8#]
s3MXnKnOD]GeElǾi'sxϙeDC3(Mo}ɵ~uU3--6r,oLj!`6]b)tܦX$<{?.S;@ُq$OKoNqӆJDb`1.L$I" hQhBMjH㇆ 4#h՘b%wȲmHX<7بȐLdOq?k^2s9+hkkCcJGq2= Ӻ@5K7<վw)o|\PWeN22u9}p-+8aqQzWl53bl(W29*~Ъ߷[E._I2H*yy놘6mC-Xk0"nIp}p_w;.I,+Tl&N2-tڵaT̆7|xM>fyxl]}]d3xVj6/d2ǖu൬@zm#jsyȗ}뮧%R`BT><~VёXs9K>pD4a4IE:B:ز*:{ZH
a2}v;t1՝?v
"
{S)H-e졽\kb:{Sy7"	g*++)NC0"|+/暋wifhkR3rə^El(qAM_jECˆd
-+ZQFk7M)QdIfGQQU=P'V|яKJ~9?>_Ϗ(2`
衧\T:0zE,Ûo`rcSR|8Vt.WrJ°ByqqZG!Aim;c_C><87V\yJ.X(zty^
H*B\8(Tc=vgqڎ$ʟ_F2sa:Z[X$:n?_rih?{z-̓ݴu dE-l'!btekT!Us=fO3rjZҰg"
\<Pb1@T*U;89_|@mT$乒:B6T":qFR3b}
&($,*I=0!0VuZaLR@ҹe_m\Lҡ;i<~?"e]j~聗xwOTJ+983_Np'ΌU_hVђ*jZ1;78_)un?o!tQq1MQ^q<ڻ);P^(k~VEYP"cXbM*H[^aD2Չ' -gNQ(IڨUkt0aCOal W^vm;E+[E>69๦5-XǴ^83vk#2y_
Y%~o2G8/.bY,:du}}]hu38NZkJ*dbr')A\y8T+%zX(u6
c@KFENZ!ig&_3*VDPZ79QFQF!Q!a3a 
hI)@P&/VI*J޿^4Ɏa2ec-)Nyn?ldt:|Y#ǞT!O/Bh-K%2}B@{gvn5
#<z嗾hQ&IgsdZқ#{ؼqg0'ylԐL\E]N1f-RF@IElJk &#B_0hW^"uҙ	"A=T}tjNWwz)FPMXW"RC9P$SA(7F{.0qS7NO_|W=orlݻ\,rPBAEł(˶P*URn57֢T1ZE9~W.?gγ"x||g|(aj.ls@:	Ti?9ʥIFIŋT!Nu׷'LR
$~`I{k7Z8Meq
?T}KbSA_(W괶d2)<tFcD[GnGiIZ\[(ueZpˮ~g?ߊÅ|~X(bbJU~)aUjLeIDqR1Z
ZQQv?u&pA`15ј=cuT+>2LKMsە}l'?t
C_^&:{:%GMӻR%JcF?Tk/q3ICfgU1咵֐t` j:NmP%Y&g:{Nm
[n}Q.V,r*Tu?#BWW?*J>5Z,JqvaS|`dКA^bc|b$w\XEl7ݬp-nqgHY<E5{|"k	bJ`;u/\ޚE	q2h\T	P~,PF3Ξ3nup(~|CűT'&m7LT#1&8xJEH$'0}~8t=9o-͎n
c]ҙB)>oq-WM3>2MTgdbˤfǐQ9ʙSٱƗbUZUָZCIf)$]M^ș<G5t
kIbMou
i4ADJF0$Jόqz>xw$l+
5~XT(2,xN1CxQ
jāoӻ?9ztNqͷޓutusN(B)+´"Xs0dvLa^[ ל[U#.&ɴޖECUXcfbB%82N~.OTu5̕'9֭oLBMO.v8"}|U(:cgȗ?d,ahƽ@2y.Zz}>_YDBI:dZ`*
Ai8}ELK8blR)QiZv
3%aLTn%:*9VW
#":CG[JH+xh<C藰csӎ岴.>+$qcҡB
$5R'0>9CoO+Z)u3A(
IEV=,V*ycOf,YRt[Z<[sJxO'S^T|?
1d\'qrx4LNAŠUCDRB&4uH:.an|R27v-YIgGQ9tP@?<eh%Ǚz7#l?thۓx^5t)U5QFiOXmj~HfqvNSjurqVԾ=];p|q~Imm<X)Yhtvս;0A5ja:==C$C["Ia&W#V1t7bmxB󖛯`4BH)j=@S\c#Q3FT$VX:i6s	qK6 5F57?͊$iX2W
Al>b!O-,"O4GFIVbJ#Zxy/#J\YKa˝[W>ݑi gXA*׎hen~j/ھ386WZ&f)+DQLTsU~ٌGgz67̏nS8%tfҍRh8 GrE13F0B4BJ?C:VfP)|ǣr/T+E;?}oogNmqǏ'RLOaqa|~4G[0S#Jy~(	J-ļmju֬߂gSxp2+_eíll&D5qkQǿIoOK7J}Qs6z1Һ^edettBJ\XO;wKr%v{P/'OΎ-k˦v!&aqq123Z3;3t˥z&(A!IVugC^G+V0u3+Z	%6X 5A(ڒ*#&cB~mh$
ШɼS+ٹwԛG&˩|BvZ.wKN櫣w]m2g-+I,,'_(XKWq>I̹*cǔ!U}2iRd9Voa,-NؐrBCHf3u#DvuG(֬[.gJNJ_?5W=Pk`w
 9;'O=ԖK]1[ӷ'qG65E&xŕyb1|?l5ZS#OVt^Ə}kQi)	ő\IƁ( %=czUJ()Gע%?><uTs9|R%*_O&ϤSLx\7v;1|	!>>"(&­lt_Ch'DE'.!6Dq\b!-Z2/efIv:Y/=>	
pҗ=ǏjO/'y,LkYc"k&:p^ٴGuyO
J☍[6{M>?<J8ЬƸl55P
(RDaL4f}҆t)BM(]&B0p2j}&A#_(d[މ"]eQժc㔋R.$HJp1ګef4NX$s6S6=Ƒb@;BR~DhPz[xn&&89--<qxl)c.Va?+,c3|:7ӫԢ6Z+>sb|e.'ȸ$&bvby:̤)+t6bWLrB<DsY#jzC}Ve|LRMrj޹D,>K36Ĺzɦ'6kK{rU]]Ճ',$H $	<I=fVnz( C
)6vW#$RХ166NRE<'怷gwl%zO_CyPNc{46cVLf~g;3M\C
A*@Se~b+  bJTj!JjNZ'Xd1&f)U#|9'f>e,X,i.|.75LćV[0֢u]q?x!#J>C+z	ߏ8=:CyvJBM!QVş=HP(֩Chq~lI >G\h(^P/r(Qeg*tekk(G&G^Nk099AOYݍS>LHmի15=b'_d2j]
<GJtL}o\7pБ]+pBb,h5X(V	kB԰VRT"ʵr5RFEbα+3YdT~qWՓѴ'.BB%B>4O<'JQ,)*kzH͏O>=v^439?{.04
w$o7]f~b58,ޕE`	}b͏G'12.,@?v!<g@r)}IAIgN,Y}<ܽh]jmŹmnς}s/=%#j{)pwɃ{,ruv
#@urߟ6L&-0l_rsa>?ed2@YWD%쉸UIENDB`PK
!<UC<<7chrome/toolkit/skin/classic/mozapps/extensions/eula.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#icon {
  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg");
  max-width: 48px;
  max-height: 48px;
  margin-inline-end: 6px;
}

#eula-dialog[addontype="theme"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
}

#eula-dialog[addontype="locale"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
}

#eula-dialog[addontype="plugin"] #icon {
  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
}

#eula-dialog[addontype="dictionary"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
}

#heading-container {
  -moz-box-align: center;
}

#heading {
  font-size: 120%;
}

#eula {
  -moz-appearance: none;
  color: -moz-FieldText;
  background-color: -moz-Field;
  margin: 1em;
  border: 1px solid ActiveBorder;
}

PK
!<!]66Dchrome/toolkit/skin/classic/mozapps/extensions/experimentGeneric.pngPNG


IHDR  szzIDATxڭOuf97#d2+C0J,BV"7Ahbf2'SejDrFJ2$e.dױfNŹrx_29rZ_L4vU
ṭ`%C_O/Rangyhө<`6G&S^lqԏS0ݻ'f=`F+Ft
鹓=m1qC)|'X7Am+T攣Y ==| ߠ(07N	ðg,*P2)XcCzCUC8gQ9ExMoVڭ`ʨ,脂
P0Ga(a]8Y& ӂcZ񓣰(GG,AwZ05Ǟ
˸%x(\W,cr(rxÂD7`!g2.rV_e4/zwlJL[ïU\NӊCo4`9nb{
!gwYz?ڶV:W]hJ̢hKw
CM@jc	6VK*'7j㝶]gc HVPey15^ݸ+K tg3%K~Cmsh_ԇ0{ѻ<-lx"uv{]ʣy,]2d*؀}?oSRBt38%IENDB`PK
!<CFchrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric-16.pngPNG


IHDRaiIDAT8˥[KA!)TUW酰`%k-e%"
bA) fբ ;l鮮b+v
71hh/_OXKM	"eNJhnB`ٗo7sV'Z('q c8y7>'fDtD>΃	`Haʢכ~#
`0DA,iS^yE碀>L0@wVt-<AI6ԭг􈶩$R?W@rw2?!i-)vW<P{R;:(DA
u՜KԢ-
T]6NCfZIENDB`PK
!<h8kCchrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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 64 64">
  <defs>
    <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 fill="url('#gradient-linear-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>
PK
!<y0B V V=chrome/toolkit/skin/classic/mozapps/extensions/extensions.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

.main-content {
  padding: 0;
}

#nav-header {
  min-height: 39px;
  background-color: #424f5a;
}

.view-pane > .list > scrollbox {
  padding-right: 48px;
  padding-left: 48px;
}


/*** global warnings ***/

.global-warning-container {
  overflow-x: hidden;
}

.global-warning {
  -moz-box-align: center;
  padding: 0 8px;
  color: #c8a91e;
  font-weight: bold;
}

#addons-page[warning] .global-warning-container {
  background-image: linear-gradient(transparent, rgba(255, 255, 0, 0.1));
}

#detail-view .global-warning {
  padding: 4px 12px;
  border-bottom: 1px solid #c1c1c1;
}

@media (max-width: 600px) {
  .global-warning-text {
    display: none;
  }

  .global-warning .warning-icon {
    background-color: #fff;
    box-shadow: 0 0 2px 5px #fff;
    border-radius: 10px;
  }
}

/*** global informations ***/

/* Plugins aren't yet disabled by safemode (bug 342333),
   so don't show that warning when viewing plugins. */
#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning-container {
  background-color: inherit;
  background-image: none;
}


/*** notification icons ***/

.warning-icon,
.error-icon,
.pending-icon,
.info-icon {
  width: 16px;
  height: 16px;
  margin: 3px 0;
}

.warning-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg");
}

.error-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-error.svg");
}

.pending-icon,
.info-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-positive.svg");
}

.addon-view[pending="disable"] .pending-icon,
.addon-view[pending="uninstall"] .pending-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.svg");
}

/*** view alert boxes ***/

.alert-container {
  -moz-box-align: center;
  margin-right: 48px;
  margin-left: 48px;
}

.alert-spacer-before {
  -moz-box-flex: 1;
}

.alert-spacer-after {
  -moz-box-flex: 3;
}

.alert {
  -moz-box-align: center;
  padding: 10px;
  color: #333;
  border: 1px solid #c1c1c1;
  border-radius: 2px;
  background-color: #ebebeb;
}

.alert .alert-title {
  font-weight: bold;
  font-size: 200%;
  margin-bottom: 15px;
}

.alert button {
  margin: 1em 2em;
}

.loading {
  list-style-image: url("chrome://global/skin/icons/loading.png");
  padding-left: 20px;
  padding-right: 20px;
}

@media (min-resolution: 1.1dppx) {
  .loading > image {
    width: 16px;
    list-style-image: url("chrome://global/skin/icons/loading@2x.png");
  }
}

button.warning {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg");
}


/*** category selector ***/

#categories {
  padding-top: 0;
}

.category[selected="true"]:hover {
  background-color:#1A2533;
}

.category[disabled] {
  overflow: hidden;
  height: 0;
  min-height: 0;
  opacity: 0;
  transition-property: min-height, opacity;
  transition-duration: 1s, 0.8s;
}

.category:not([disabled]) {
  min-height: 40px;
  transition-property: min-height, opacity;
  transition-duration: 1s, 0.8s;
}

/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
  .category-name {
    display: none;
  }
}

.category-badge {
  background-color: #55D4FF;
  padding: 2px 8px;
  margin: 6px 0;
  margin-inline-start: 6px;
  border-radius: 100%;
  color: #FFF;
  font-weight: bold;
  text-align: center;
}

.category-badge[value="0"] {
  display: none;
}

#category-search > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-search.png");
}
#category-discover > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png");
}
#category-locale > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png");
}
#category-extension > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.svg");
}
#category-service > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-service.png");
}
#category-theme > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png");
}
#category-plugin > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png");
}
#category-dictionary > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-dictionaries.png");
}
#category-experiment > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-experiments.png");
}
#category-legacy > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-legacy.svg");
}
#category-availableUpdates > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-available.png");
}
#category-recentUpdates > .category-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/category-recent.png");
}


/*** header ***/

#header {
  margin-top: 20px;
  margin-bottom: 20px;
  margin-right: 48px;
  margin-left: 48px;
}

@media (max-width: 600px) {
  #header-search {
    width: 12em;
  }
}

.view-header {
  margin: 0 48px;
  border-bottom: 1px solid #c1c1c1;
}

#header-utils-btn {
  height: 30px;
  line-height: 20px;
  border-color: #c1c1c1;
  background-color: #fbfbfb;
  padding-right: 10px;
  padding-left: 10px;
}

#header-utils-btn:not([disabled="true"]):active:hover,
#header-utils-btn[open="true"] {
  padding-bottom: 0px;
  padding-top: 0px;
  background-color: #dadada;
}

.header-button {
  -moz-appearance: none;
  border: 1px solid;
  border-radius: 2px;
}

.header-button[disabled="true"] > .toolbarbutton-icon {
  opacity: 0.4;
}

.header-button:not([disabled="true"]):hover,
#header-utils-btn:not([disabled="true"]):hover {
  background-color: #ebebeb;
  cursor: pointer;
}

.header-button > .toolbarbutton-text {
  display: none;
}

.nav-button {
  list-style-image: url(chrome://mozapps/skin/extensions/navigation.png);
  margin-top: 15px;
  margin-bottom: 15px;
  border-color: transparent;
}

.nav-button:not([disabled="true"]):hover {
  border-color: #ebebeb;
}

#back-btn:-moz-locale-dir(ltr),
#forward-btn:-moz-locale-dir(rtl) {
  -moz-image-region: rect(0, 18px, 18px, 0);
}

#back-btn:-moz-locale-dir(rtl),
#forward-btn:-moz-locale-dir(ltr) {
  -moz-image-region: rect(0, 36px, 18px, 18px);
}


/*** sorters ***/

.sort-controls {
  -moz-appearance: none;
}

.sorter {
  height: 35px;
  border: none;
  border-radius: 0;
  background-color: transparent;
  color: #536680;
  margin: 0;
  min-width: 12px !important;
  -moz-box-direction: reverse;
}

.sorter .button-box {
  padding-top: 0;
  padding-bottom: 0;
}

.sorter[checkState="1"],
.sorter[checkState="2"] {
  background-color: #ebebeb;
  box-shadow: 0 -4px 0 0 #ff9500 inset;
}

.sorter .button-icon {
  margin-inline-start: 6px;
}


/*** discover view ***/

.discover-spacer-before,
.discover-spacer-after {
  -moz-box-flex: 1;
}

#discover-error .alert {
  max-width: 45em;
  -moz-box-flex: 1;
}

.discover-logo {
  list-style-image: url("chrome://mozapps/skin/extensions/discover-logo.png");
  margin-inline-end: 15px;
}

.discover-title {
  font-weight: bold;
  font-size: 24px;
  font-family: MetaWebPro-Book, "Trebuchet MS", sans-serif;
  margin: 0 0 15px 0;
}

.discover-description {
  text-align: justify;
  margin: 0 0 15px 0;
}

.discover-footer {
  text-align: justify;
}


/*** list ***/

.list {
  -moz-appearance: none;
  margin: 0;
  border-width: 0 !important;
  background-color: transparent;
}

.list > scrollbox > .scrollbox-innerbox {
  border: 1px dotted transparent;
}

.list:-moz-focusring > scrollbox > .scrollbox-innerbox {
  border-color: #0095dd;
}

.addon {
  color: #444;
  border-bottom: 1px solid #c1c1c1;
  padding: 5px;
  background-origin: border-box;
}

.addon:not(:only-child):last-child {
  border-bottom-width: 0;
}

.details {
  cursor: pointer;
  margin: 0;
  margin-inline-start: 10px;
}

.icon-container {
  width: 48px;
  height: 48px;
  margin: 3px 7px;
  -moz-box-align: center;
  -moz-box-pack: center;
}

.icon {
  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg");
  max-width: 32px;
  max-height: 32px;
}

.content-inner-container {
  margin-inline-end: 5px;
}

.addon[active="false"] .icon {
  filter: grayscale(1);
}

.addon-view[type="theme"] .icon {
  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
}

.addon-view[type="locale"] .icon {
  list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
}

.addon-view[type="plugin"] .icon {
  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
}

.addon-view[type="dictionary"] .icon {
  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
}

.addon-view[type="experiment"] .icon {
  list-style-image: url("chrome://mozapps/skin/extensions/experimentGeneric.png");
}

.name-container {
  font-size: 1.3rem;
  font-weight: bold;
  -moz-box-align: end;
  -moz-box-flex: 1;
}

.legacy-warning {
  background-color: #FFE900;
  color: #3E2800;
  padding: 4px 5px 3px;
  font-size: 0.9rem;
  font-weight: 600;
  -moz-user-focus: ignore;
  transition-property: color, background-color;
  transition-timing-function: cubic-bezier(.07,.95,0,1);
  transition-duration: 150ms;
}

.legacy-warning:hover {
  background-color: #D7B600;
  color: #3E2800;
  text-decoration: none;
}

.legacy-warning:hover:active {
  background-color: #a47f00;
  color: #FFF;
}

#detail-view .legacy-warning {
  margin-top: 0.78rem;
}

.creator {
  font-weight: bold;
}

.description-container {
  margin-inline-start: 6px;
  -moz-box-align: center;
  font-size: 1.25rem;
}

.description {
  margin: 0;
}

.warning,
.pending,
.error {
  margin-inline-start: 48px;
  font-weight: bold;
  -moz-box-align: center;
}

.content-container,
.basicinfo-container {
  -moz-box-align: start;
}

.addon[status="installing"] > .content-container {
  -moz-box-align: stretch;
}

.update-info-container {
  -moz-box-align: center;
}

.update-available {
  -moz-box-align: end;
}

.install-status-container {
  -moz-box-pack: end;
  -moz-box-align: end;
}

.name-outer-container {
  -moz-box-pack: center;
}

.relnotes-toggle-container,
.icon-outer-container {
  -moz-box-pack: start;
}

.status-container,
.control-container {
  -moz-box-pack: end;
}

.addon-view .warning {
  color: #d8b826;
}

.addon-view .error {
  color: #e62117;
}

.addon-view .pending {
  color: #62c44e;
}

.addon-view[pending="disable"] .pending,
.addon-view[pending="uninstall"] .pending {
  color: #898989;
}

.addon .relnotes-container {
  -moz-box-align: start;
  margin-inline-start: 6px;
  height: 0;
  overflow: hidden;
  opacity: 0;
  transition-property: height, opacity;
  transition-duration: 0.5s, 0.5s;
}

.addon[show-relnotes] .relnotes-container {
  opacity: 1;
  transition-property: height, opacity;
  transition-duration: 0.5s, 0.5s;
}

.addon .relnotes-header {
  font-weight: bold;
  margin: 10px 0;
}

.addon .relnotes-toggle {
  -moz-appearance: none;
  border: none;
  background: transparent;
  font-weight: bold;
  cursor: pointer;
}

.addon .relnotes-toggle > .button-box > .button-icon {
  padding-inline-start: 4px;
}

.addon-view[notification],
.addon-view[pending] {
  --view-highlight-color: transparent;
  background-image: radial-gradient(at 50% 0%,
                                    var(--view-highlight-color) 0%,
                                    transparent 75%);
}
.addon-view[notification="warning"] {
  --view-highlight-color: #F9F5E5;
}

.addon-view[notification="error"] {
  --view-highlight-color: #FFE8E9;
}

.addon-view[pending="enable"],
.addon-view[pending="upgrade"],
.addon-view[pending="install"] {
  --view-highlight-color: #EFFAF2;
}

.addon-view[pending="disable"],
.addon-view[pending="uninstall"] {
  --view-highlight-color: #F2F2F2;
}

.addon[selected] {
  background-color: #fafafa;
  color: #333;
  padding-inline-start: 1px; /* compensate the 4px border */
  border-inline-start: solid 4px #ff9500;
}

#addon-list .addon[active="false"] > .content-container > .content-inner-container {
  color: #999;
}

#addon-list .addon[active="false"][selected] > .content-container > .content-inner-container {
  color: #777;
}


/*** item - uninstalled ***/

.addon[status="uninstalled"] {
  border: none;
}

.addon[status="uninstalled"] > .container {
  -moz-box-align: center;
  padding: 4px 20px;
  background-color: #FDFFA8;
  border-radius: 8px;
  font-size: 120%;
}

.addon[status="uninstalled"][selected] {
  background-color: transparent;
}


/*** search view ***/

#search-filter {
  padding: 5px 20px;
  margin-right: 48px;
  margin-left: 48px;
  font-size: 120%;
  border-bottom: 1px solid #c1c1c1;
  overflow-x: hidden;
}

#search-filter-label {
  font-weight: bold;
  color: grey;
  margin-inline-end: 10px;
}

#search-allresults-link {
  margin-top: 1em;
  margin-bottom: 2em;
}


/*** detail view ***/

#detail-view > .box-inherit {
  margin-right: 48px;
  margin-left: 48px;
}

#detail-view .loading {
  opacity: 0;
}

#detail-view[loading-extended] .loading {
  opacity: 1;
  transition-property: opacity;
  transition-duration: 1s;
}

.detail-view-container {
  padding-inline-end: 2em;
  padding-bottom: 2em;
  font-size: 1.25rem;
  color: #333;
}

#detail-notifications {
  margin-top: 1em;
  margin-bottom: 2em;
}

#detail-notifications .warning,
#detail-notifications .pending,
#detail-notifications .error {
  margin-inline-start: 0;
}

#detail-icon-container {
  width: 64px;
  margin-inline-end: 10px;
  margin-top: 6px;
}

#detail-icon {
  max-width: 64px;
  max-height: 64px;
}

#detail-summary {
  margin-bottom: 2em;
}

#detail-name-container {
  font-size: 2.5rem;
  font-weight: normal;
}

#detail-screenshot-box {
  margin-inline-end: 2em;
  background-image: linear-gradient(rgba(255,255,255,.5), transparent);
  background-color: white;
  box-shadow: 0 1px 2px #666;
  border-radius: 2px;
}

#detail-screenshot {
  max-width: 300px;
  max-height: 300px;
}

#detail-screenshot[loading] {
  background-image: url("chrome://global/skin/icons/loading.png");
  background-position: 50% 50%;
  background-repeat: no-repeat;
}

@media (min-resolution: 1.1dppx) {
  #detail-screenshot[loading] {
    background-image: url("chrome://global/skin/icons/loading@2x.png");
    background-size: 16px;
  }
}

#detail-screenshot[loading="error"] {
  background-image: url("chrome://global/skin/media/error.png");
}

#detail-desc-container {
  margin-bottom: 2em;
}

#detail-desc, #detail-fulldesc {
  margin-inline-start: 6px;
  /* This is necessary to fix layout issues with multi-line descriptions, see
     bug 592712*/
  outline: solid transparent;
  white-space: pre-wrap;
  min-width: 10em;
}

#detail-fulldesc {
  margin-top: 1em;
}

#detail-contributions {
  border-radius: 2px;
  border: 1px solid #D2DBE8;
  margin-bottom: 2em;
  padding: 1em;
  background-color: #F3F7FB;
}

#detail-contrib-description {
  font-style: italic;
  margin-bottom: 1em;
  color: #373D48;
}

#detail-contrib-suggested {
  color: grey;
  font-weight: bold;
}

#detail-contrib-btn {
  color: #FFF;
  text-shadow: none;
  border: 1px solid #0095dd;
  list-style-image: url("chrome://mozapps/skin/extensions/heart.png");
  background-color: #0095dd;
}

#detail-contrib-btn .button-icon {
  margin-inline-end: 5px;
}

#detail-contrib-btn:not(:active):hover {
  border-color: #008acb;
  background-color: #008acb;
}

#detail-contrib-btn:active:hover {
  background-color: #006b9d;
  border-color: #006b9d;
}

#detail-grid {
  margin-bottom: 2em;
}

#detail-grid > columns > column:first-child {
  min-width: 15em;
  max-width: 25em;
}

.detail-row[first-row="true"],
.detail-row-complex[first-row="true"],
setting[first-row="true"] {
  border-top: none;
}

.detail-row,
.detail-row-complex,
setting {
  border-top: 1px solid #c1c1c1;
  -moz-box-align: center;
  min-height: 35px;
  line-height: 20px;
  text-shadow: 0 1px 1px #fefffe;
}

#detail-controls {
  margin-bottom: 1em;
}

.inline-options-browser,
setting[first-row="true"] {
  margin-top: 2em;
}

setting {
  -moz-box-align: start;
}

.preferences-alignment {
  min-height: 30px;
  -moz-box-align: center;
}

.preferences-description {
  font-size: 90.9%;
  color: graytext;
  margin-top: -2px;
  margin-inline-start: 2em;
  white-space: pre-wrap;
}

.preferences-description:empty {
  display: none;
}

setting[type="radio"] > radiogroup {
  -moz-box-orient: horizontal;
}


/*** creator ***/

.creator > label {
  margin-inline-start: 0;
  margin-inline-end: 0;
}

.creator > .text-link {
  margin-top: 1px;
  margin-bottom: 1px;
}


/*** rating ***/

.meta-rating {
  margin-inline-end: 0;
  padding-top: 2px;
}


/*** download progress ***/

.download-progress {
  border: 1px solid #c1c1c1;
  border-radius: 2px;
  background-color: #fbfbfb;
  width: 200px;
  height: 30px;
  margin: 2px 4px;
}

.download-progress[mode="undetermined"] {
  border-color: #0095dd;
}

.download-progress .start-cap,
.download-progress[complete] .end-cap,
.download-progress[mode="undetermined"] .end-cap,
.download-progress .progress .progress-bar {
  -moz-appearance: none;
  background-color: #0095dd;
}

.download-progress .progress .progress-bar  {
  min-height: 28px;
}

.download-progress .progress {
  -moz-appearance: none;
  background-color: transparent;
  padding: 0;
  margin: 0;
  border: none;
}

.download-progress .start-cap,
.download-progress .end-cap {
  width: 4px;
}

.download-progress .start-cap:-moz-locale-dir(ltr),
.download-progress .end-cap:-moz-locale-dir(rtl) {
  border-radius: 1px 0 0 1px;
}

.download-progress .end-cap:-moz-locale-dir(ltr),
.download-progress .start-cap:-moz-locale-dir(rtl) {
  border-radius: 0 1px 1px 0;
}

.download-progress .cancel {
  -moz-appearance: none;
  padding: 3px;
  min-width: 0;
  width: 20px;
  height: 20px;
  margin: 3px;
}

.download-progress .cancel .button-box {
  /* override in-content/common.css !important rule */
  padding: 0 !important;
}

.download-progress .cancel .button-text {
  display: none;
}

.download-progress .cancel .button-icon {
  margin: 0;
}

.download-progress .cancel {
  list-style-image: url('chrome://mozapps/skin/extensions/cancel.png');
}

.download-progress .status-container {
  -moz-box-align: center;
}

.download-progress .status {
  color: #333;
  text-shadow: #fff 0 0 2px;
}


/*** install status ***/

.install-status {
  -moz-box-align: center;
}


/*** check for updates ***/

#updates-container {
  -moz-box-align: center;
}

#updates-container .button-link {
  font-weight: bold;
}

#updates-installed,
#updates-downloaded {
  color: #00BB00;
  font-weight: bold;
}

#update-selected {
  margin: 12px;
}


/*** buttons ***/

.addon-control[disabled="true"]:not(.no-auto-hide) {
  display: none;
}

.no-auto-hide .addon-control {
  display: block !important;
}

button.button-link {
  -moz-appearance: none;
  background: transparent;
  border: none;
  box-shadow: none;
  color: #0095dd;
  cursor: pointer;
  min-width: 0;
  min-height: 20px;
  margin: 0 6px;
}

button.button-link:hover {
  background-color: transparent;
  color: #178ce5;
  text-decoration: underline;
}

/* Needed to override normal button style from inContent.css */
button.button-link:not([disabled="true"]):active:hover {
  background-color: transparent;
  color: #ff9500;
  text-decoration: none;
}

.addon-control.replacement {
  background-color: #0a84ff;
  border-color: #0a84ff;
  color: #fff;
}

.addon-control.replacement:active,
.addon-control.replacement:hover {
  background-color: #0060df;
  border-color: #0060df;
}

.addon-control.replacement:active:hover {
  background-color: #003eaa;
  border-color: #003eaa;
}

/*** telemetry experiments ***/

#detail-experiment-container {
  font-size: 80%;
  margin-bottom: 1em;
}

#detail-experiment-bullet-container,
#detail-experiment-state,
#detail-experiment-time,
.experiment-bullet-container,
.experiment-state,
.experiment-time {
  vertical-align: middle;
  display: inline-block;
}

.addon .experiment-bullet,
#detail-experiment-bullet {
  fill: rgb(158, 158, 158);
}

.addon[active="true"] .experiment-bullet,
#detail-view[active="true"] #detail-experiment-bullet {
  fill: rgb(106, 201, 20);
}

/*** info UI for add-ons that have been disabled for being unsigned ***/

#show-disabled-unsigned-extensions:not(:hover) {
  background-color: #fcf8ed;
}

#disabled-unsigned-addons-info,
#legacy-extensions-info {
  margin-bottom: 2em;
  margin-right: 48px;
  margin-left: 48px;
}

#disabled-unsigned-addons-heading,
#legacy-extensions-heading {
  font-size: 1.4em;
  font-weight: bold;
  margin-bottom: .5em;
}

#signing-dev-info {
  font-style: italic;
}

#detail-findUpdates-btn[hidden] {
  display: -moz-box;
  visibility: hidden;
}

#header-utils-btn {
  list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg");
  -moz-context-properties: fill;
  fill: #424f5a;
  margin-inline-end: 16px;
}

@media (-moz-windows-default-theme: 0) {
  #header-utils-btn {
    fill: GrayText;
  }
}

.sorter[checkState="1"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}

.sorter[checkState="2"] {
  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}

.addon .relnotes-toggle {
  -moz-box-direction: reverse;
  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}

.addon[show-relnotes] .relnotes-toggle {
  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}

.meta-rating > .star {
  list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png");
  padding: 0 1px;
}

.meta-rating > .star[on="true"] {
  list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png");
}

#detail-view .legacy-warning {
  margin-top: 1rem;
}
PK
!<S8chrome/toolkit/skin/classic/mozapps/extensions/heart.pngPNG


IHDR
7	pHYs
OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*!	J!QEEȠQ,
!{kּ>H3Q5B.@
$pd!s#~<<+"xM0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^uf@Wp~<<EJB[aW}g_Wl~<$2]GLϒ	bG"IbX*QqD2"B)%d,>5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A,`6B$BB
dr`)B(Ͱ*`/@4Qhp.U=pa(	Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q=C7Fdt1r=6Ыhڏ>C03l0.B8,	c˱"VcϱwE	6wB aAHXLXNH $4	7	Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![
b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw
Ljg(gwLӋT071oUX**|
J&*/TުUUT^S}FU3S	ԖUPSSg;goT?~YYLOCQ_ cx,!k
u5&|v*=9C3J3WRf?qtN	(~))4L1e\kXHQG6EYAJ'\'GgSSݧ
M=:.kDwn^Loy}/TmGX$<5qo</QC]@Caaᄑ<FFi\$mmƣ&&!&KMMRM);L;L֙͢5=12כ߷`ZxZ,eIZYnZ9YXUZ]F%ֻNNgðɶۮm}agbgŮ}}=
Z~sr:V:ޚΜ?}/gX3)iSGggs󈋉K.>.ȽJtq]zۯ6iܟ4)Y3sCQ?0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl{/]py.,:@LN8A*%w%
yg"/6шC\*NH*Mz쑼5y$3,幄'L
Lݛ:v m2=:1qB!Mggfvˬen/kY-
BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9<qy
+V<*mOW~&zMk^ʂkU
}]OX/Yߵa>(xoʿܔĹdff-[n
ڴ
VE/(ۻC<e;?TTTT6ݵan{4[>ɾUUMfeI?m]Nmq#׹=TR+Gw-
6
U#pDy	
:v{vg/jBFS[b[O>zG4<YyJTiӓgό}~.`ۢ{cjotE;;\tWW:_mt<Oǻ\kz{f7y՞9=ݽzo~r'˻w'O_@AC݇?[jwGCˆ
8>99?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڔ1
@DD@
9`6`+GVv6@ ,eu``2W1:ӟΙ*``&j&ThBYRLR8^fw+;t¥n_M~s8yؓ=Л+7dmԱ}$`ڽhNZIENDB`PK
!<		@chrome/toolkit/skin/classic/mozapps/extensions/localeGeneric.pngPNG


IHDR  szz	IDATXŗk]U};?xJԥQ &MRE(T

KHJ( hRRT	!Jq4DGb;v<lΝ8*i
8rZ_k_9#~oJ{b8GU
վNYYg./~3?wzX1_z:?*=;"w&w,WO잤׈e2]ʅOoAl#O]7a̿fj[NvO}	R	+t	b?F7I.5Gg?ЯrzrbeꅘJj[[GiYp[`%tLjifnV?v㙏~oO>-_zvb8@DunBKWi+X"\G
L7O[K2?{~/?bjh1dⳭc(dk)dk%H(}|GO;֗ܿ"}$aɜemVVFz}幋Ax˱#_~'yD c($dP+Hb(HA`aj8=NJH_R|#}I2|
Ϗ=o_	G =	B#<|Omr"`1&a<Cg+vw_o_UDnsXʁvd-k=C,v3GkISNᅫB }~~~/!xkNlOnuXkXLZVbR,wTSoE>~Z-"3sI<W?$@c@━N$OaXx32L^zƱַt2j>:mXNZ;n}gP;i{"x}[-Teh1ۄ<KqCG7C-G>*J:8,g^z^Y	ކs8eP5Y^,Q[,EV9ȣwkkIϧ#|gչqu$&S^ƫhI#_*5y{$nE_dEVbR8y=f-^Xi[n^FmPXgqyHl! ǟ;~x[(vzeAOI<Xj0TL2s!,qUcpZrppG?0sJx̜'Y#yt5'D7'1ݼ@Rrm2WwcqF8}:s{ҕT2K:Ӗ8BBη!JGimD&Xgij!ӷ	!cw/kqJ)ǩS8=h]jhǷK3yw)`{w+8СF[MX	;\ȶm*z4WvXmpZ86`":NZwaA=8{i0훯Wwƪ>=g1=C۵n _	eH腴6][,i05N7J`DdR|߼j&l_EzGD2""5Ӣ~W{]`ܠ`y9
8y"ds`@1GΑ=Vog(27}2}VVVwlš`,0Z0V~_Op޸^ԣS38@2hQBLeh#w453y|-;˒D?@s3lI);3u!R|B>F)Lqb;lnE75>zm{8Hf}ݒ#ǥ?t7+"7פ9Fvx#[lRk`8Hˌo$z]';}
,,`֛+|&UHQ E"Q"e"JĔ\ȕI\$>')!IRH[iD=pOX#ɿ^^}j>ʥX),O4L
IQ+Po2lg|]Z!P=gkmGr殱ڼ2}8a׆	2EAL1NNs_{OwP&M=rȁ{,.̱
G{y'OK s''>0j
?j7ꇏ>65u'kijX[2@.4}O}c~Tc#Ff9}e.֭J/)ȶS3ڐ''O۱`$ܰ`CЊMclfފlU?uIENDB`PK
!<7o=chrome/toolkit/skin/classic/mozapps/extensions/navigation.pngPNG


IHDR$3#|(^IDATxc`jWXKX38)uLve+wEO0!+hެJ)6:&{<_wP\3Lf7Ȍ.^o	9#@9_ɝ6cd3Μ=(Kb
Uly]d_TfP0[ZQ}s#uĩPGtj7 gWBr@
cKwhj7]xG? Hg|}r u9HH#?yRXs	}AhjOXv>zڻ}b
fpв/b-Nr*ZkqN:W]|1f&0JG(ڴes>@ydOވX <q[d3wRP\T8iQ8rYh %Bs-dIk-beoNBXvv!!P4Bƅ:^AMg땫?5FeUu{vG_I+[%y?LʻaƔSͺt(yRZ1('dQ`F=b!ثIIENDB`PK
!<s#;chrome/toolkit/skin/classic/mozapps/extensions/newaddon.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

#addon-page {
  font-size: 1.1em;
}

#addon-scrollbox {
  overflow: auto;
  -moz-box-orient: vertical;
  -moz-box-flex: 1;
}

#spacer-start {
  -moz-box-flex: 1;
}

#spacer-end {
  -moz-box-flex: 3;
}

#addon-container {
  overflow: visible;
  max-width: 800px;
  margin: 20px;
  padding: 30px 90px;
}

#addon-info {
  -moz-box-align: start;
  margin: 25px 10px;
}

#icon {
  margin-top: 8px;
  margin-inline-end: 10px;
  max-width: 64px;
  max-height: 64px;
  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg");
}

.addon-info[type="theme"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
}

.addon-info[type="locale"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
}

.addon-info[type="plugin"] #icon {
  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
}

.addon-info[type="dictionary"] #icon {
  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
}

#name {
  font-size: 130%;
}

#author {
  color: GrayText;
}

#location {
  color: GrayText;
}

#warning {
  margin-bottom: 25px;
  -moz-box-align: start;
}

#warning-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.svg");
  width: 16px;
  height: 16px;
  margin-top: 5px;
  margin-inline-end: 5px;
}

#allow {
  margin-inline-start: 84px;
  margin-bottom: 20px;
}

#buttonDeck {
  margin-top: 25px;
  -moz-box-align: stretch;
}

#continuePanel {
  -moz-box-pack: end;
  -moz-box-align: end;
}

#restartPanel {
  -moz-box-pack: end;
  -moz-box-align: stretch;
}

#restartPanelButtons {
  margin-top: 25px;
  -moz-box-pack: end;
}

#later {
  color: GrayText;
}
PK
!<'5΍Achrome/toolkit/skin/classic/mozapps/extensions/rating-not-won.pngPNG


IHDR1_tEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.0-c060 61.134777, 2010/02/12-17:32:00        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CS5 Macintosh" xmpMM:InstanceID="xmp.iid:E2E24CAEAB3D11DF92EACBC46CFBBD70" xmpMM:DocumentID="xmp.did:E2E24CAFAB3D11DF92EACBC46CFBBD70"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:E2E24CACAB3D11DF92EACBC46CFBBD70" stRef:documentID="xmp.did:E2E24CADAB3D11DF92EACBC46CFBBD70"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>J!ɌIDATxڌRMLAm
R$ `Fт	A^%!F4$ixYqЃƟ9z."PJ37K(M͗}|}{Ea(D7ag>}xherse~_ȣE&Vr>J˲QO	yK1NSD?pF~\4NesYBZVrh*}J)(ntK<!;M3~RFs *E
(lRe~<6"g_p)K@(yHdkPU	+/lT*ۇ޹ݍoЄ>hrCpyMW[Bˉ3 )SL"9}U*őYJ#("@0d3@trPpN:ϝv܏bt|fb\m9CS5uڍ{AwSΓ(m^%̀Z9z܄Z݁m0uP-Տ2G(bݙ@v$tȜ{0gN/hJ™;nݸ:}X@Hg/GO۪IHx:/St>F$D'/IENDB`PK
!<pB~~=chrome/toolkit/skin/classic/mozapps/extensions/rating-won.pngPNG


IHDR1_tEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.0-c060 61.134777, 2010/02/12-17:32:00        "> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmp:CreatorTool="Adobe Photoshop CS5 Macintosh" xmpMM:InstanceID="xmp.iid:E2E24CAAAB3D11DF92EACBC46CFBBD70" xmpMM:DocumentID="xmp.did:E2E24CABAB3D11DF92EACBC46CFBBD70"> <xmpMM:DerivedFrom stRef:instanceID="xmp.iid:E2E24CA8AB3D11DF92EACBC46CFBBD70" stRef:documentID="xmp.did:E2E24CA9AB3D11DF92EACBC46CFBBD70"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="r"?>/&mIDATxt]HQugvUr#M-}!|I$KE(EJL2RLpfgN箩(:93;Clۆͮlyf*ضٰ꣛xEy7R|ʀ2%&&Gʗ$!^ȞE:3Z8w@Q4`8<dlCƒdKD;Rz|k}T.09–oN1BfJ=~Y6 }E}n'9b,`{6AYJ(#$k'RUFXE,Ba&;8\7QeEACHB{A˄0'9~Nw.7`E7-90=<iB0@mM>	ɧ
[Zݗ}o	]ۘ  s|@gCvxvV[տҝ3?u˿Yࡊ@VĄ83@K~DKǵJ8cyLdha\78qkwMEҰRp(d-3.cc$H"QCi2_iV8M
d[09ńD[AHnJ	5<&k!ZPE5/	FӉ|CM20̃cY_TPX6ϼS
jwKXKol+(%@uQ{Pq?Щ?ǚIENDB`PK
!<PIq..?chrome/toolkit/skin/classic/mozapps/extensions/themeGeneric.pngPNG


IHDR  szzIDATXV	P6SRΤ5N4WR/0)55iԌ@x \PEC@)+r-.,r,fae#1tڙ3~|~-%-U}s+QiFPi\222|~Miii
|~qE]bcc`py𡢵^`4011$:C)/~{w&HUU9q&''aZt]7(9


@KKx<oE ::zYzzznoo/ߌ)}a}c4P|@YY022EGѐLCl%µ,;;c`8gL?ځqV	9CFHϰ 1.B$(0z)4N7?,
3Fڡoj;nxPw3t7F~h lx˗!hǴO<	EpAJ<zn|c\3I0Pn
o_|aRQQn!$"|vF0_{^ vo7eh%`1G`rR&/,|
n0D~IS܁Z7\);DIx@>tc:Nw@>ҋj{?.S3؆G@d|NqAII1B*r*wl)NDϠZKL@`u͠$@5D=hc6Pcw
*NY!C&V|ι6pH[tuA3͛vI[B/Pwp5*
rwN%ELw&Ȏ#%jPK8jv088Y)q<L:
 4\l!b4z韠z_rBnn.Wi<6JDyBBB=~O>-,A=ܸx$́S@EH}L:nAzD@VrNB<׮]{066iMi**/
02A)Ģ+DkG
_)GסL2;(&P


бAoW2C[|y+ 55#rT*\$>L	K?)l|EHȕQ

\Z%&&s*J퇥5,\ĈR"rE;Sq,	p4qwB!Eq@@n,dXolZYYp
Q@Ԃ(^#c_@-
.>E<uH
Lp-t:&6A7u}D"455yJ	8QRPyLF
u%_@igwXb%ӧ~
PFb#//{Ԉ!c!gMM
(}Q;Xf-u-%=Sߛ|YYY$E"--rzҘ씠sA9eX@j,gbSzDUЂ:0wd'3"\E٭)7mxژOŊLHHPSds4&;79'ilnK	\߿M%W7:kD7S2؅@{xx̋N())AxxW,z%bg>@u8k
+?Zȹ
%8GJf;4&;I꾄॔^cX:bkK߃B077]0H[vpO==kk|ϿسzEWWNǾbptt9Gdd$X_ٳgU;w<%B$8W9*ᦟE^W9'o9tPk+l;;;lmml۶m6f%`0zYؿuQIENDB`PK
!<r 9chrome/toolkit/skin/classic/mozapps/extensions/update.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.throbber {
  list-style-image: url("chrome://global/skin/icons/loading.png");
  width: 16px;
  height: 16px;
  margin-top: 5px;
  margin-bottom: 5px;
  margin-inline-start: 5px;
  margin-inline-end: 2px;
}

@media (min-resolution: 1.1dppx) {
  .throbber {
    list-style-image: url("chrome://global/skin/icons/loading@2x.png");
  }
}

.alertBox {
  background-color: InfoBackground;
  color: InfoText;
  border: 1px outset InfoBackground;
  margin-left: 3px;
  margin-right: 3px;
  padding: 5px;
}
PK
!<Ӡ2UU<chrome/toolkit/skin/classic/mozapps/extensions/utilities.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.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="context-fill" d="m11.5,13.9l-.6-1.5c.3-.2 .5-.4 .8-.6 .2-.2 .4-.5 .6-.7l1.5,.6c.3,.1 .6,0 .7-.3l.4-1c.1-.3 0-.6-.3-.7l-1.5-.6c.1-.6 .1-1.3 0-2l1.5-.6c.3-.1 .4-.4 .3-.7l-.4-1c-.1-.3-.4-.4-.7-.3l-1.5,.6c-.2-.3-.4-.5-.6-.8-.2-.1-.5-.3-.7-.5l.6-1.5c.1-.3 0-.6-.3-.7l-.9-.4c-.3-.1-.6,0-.7,.3l-.6,1.5c-.6-.1-1.3-.1-2,0l-.6-1.5c-.1-.3-.4-.4-.7-.3l-1,.4c-.2,.1-.3,.4-.2,.6l.6,1.5c-.3,.3-.5,.5-.8,.7-.2,.3-.4,.5-.6,.8l-1.5-.7c-.3-.1-.6,0-.7,.3l-.4,.9c-.1,.3 0,.6 .3,.7l1.5,.7c-.1,.6-.1,1.3 0,1.9l-1.5,.6c-.3,.1-.4,.4-.3,.7l.4,1c.1,.3 .4,.4 .7,.3l1.5-.6c.2,.3 .4,.5 .6,.8 .2,.2 .5,.4 .7,.6l-.6,1.5c-.1,.3 0,.6 .3,.7l1,.4c.3,.1 .6,0 .7-.3l.6-1.5c.6,.1 1.3,.1 2,0l.6,1.5c.1,.3 .4,.4 .7,.3l1-.4c.1-.1 .3-.4 .1-.7zm-5.1-4.2c-.9-.9-.9-2.4 0-3.3 .9-.9 2.4-.9 3.3,0 .9,.9 .9,2.4 0,3.3-.9,.9-2.4,.9-3.3,0z"/>
</svg>

PK
!<X<Iuu9chrome/toolkit/skin/classic/mozapps/handling/handling.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#description-image:not([src]) {
  height: 32px;
  width: 32px;
}

richlistitem[type] {
  min-height: 36px; /* Don't forget to update the richlistbox height! */
  padding-inline-start: 2px;
 }
 
richlistitem {
  -moz-box-align: center;
}

richlistbox {
  /* 3 items high, plus 4px for top and bottom margins, less 2px for border */
  min-height: 110px;
}

.name {
  font-weight: bold;
}

.description {
  color: GrayText;
}
PK
!<l=chrome/toolkit/skin/classic/mozapps/places/defaultFavicon.svg<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
  <circle cx="8.028" cy="8" r="8" fill="#fff"/>
  <circle cx="8.028" cy="8" r="7" fill="#737373"/>
  <g fill="#fff">
    <path d="M3.12 6.664c.163-.13.743.134 1.188-.013.213-.07.018-.32.325-.37.152-.02.158.27.34.15.057-.03.036-.15.055-.16.047-.03 0-.31.114-.38.4-.24.335.6.764.22.25-.31-.24-.57-.127-.88.16-.44.44-.09.73-.2.21-.08-.04-.59.14-.68.34-.17.26.39.53.42.17.02.53-.28.51-.45-.03-.2-.45-.27-.53-.45-.1-.21.15-.41.06-.56-.11-.19-.47-.36-.67-.18-.31.27.18.3.05.47-.15.2-.18-.04-.35.06-.2.11-.18.44-.46.314-.69-.285.71-.85.38-1.27-.05-.06-.28-.034-.3-.105-.03-.1.17-.19.23-.224a5.986 5.986 0 0 0-4.07 5.09s.12.2.41.01.58.05.68-.15c.17-.34-.34-.35-.02-.61z"/>
    <path d="M6.44 2.44c0 .173-.008.658.38.164a.78.78 0 0 1 .79-.134c-.075-.026-.538.31-.385.4s.46-.115.55-.208a1.343 1.343 0 0 0 .335-.514c.024-.134.024-.15.024-.15a6.03 6.03 0 0 0-1.852.248s.158.092.158.193zm2.304.05c.13.06-.44.217-.4.375s.557.187.758.235.177.15.355.33c.3.3.35-.572.646-.617.237-.036.12-.2.043-.245s.124-.124.124-.124a5.966 5.966 0 0 0-1.735-.42s-.3-.036-.168.17a.806.806 0 0 0 .377.297zm4.803 3.22c-.01-.027 0 .1-.053.13s-.12-.21-.233.024a.2.2 0 0 1-.1.16c-.08 0 .06-.235.06-.258 0-.04-.04-.064-.05-.1-.03-.11-.06-.222-.07-.247-.02-.05.04-.1.05-.17-.1-.13.01-.33-.03-.4a6.03 6.03 0 0 0-1-1.22l-.01.01a6.1 6.1 0 0 0 .38.63c.04.06-.25-.06-.18 0-.08-.07-.17-.27-.25-.34-.1-.1-.14-.2-.22-.27-.12-.12-.29-.15-.3-.12-.02.06.12.34.15.39.18.3.28.19.42.24.25.09.44.96.74.94-.09 0-.36-.1-.44 0s.12.1.28.59c.15.45.12.22.29.26a.48.48 0 0 1 .24.41c0 .08-.1.09-.13.18-.02.11.09.34.07.43-.02.11-.13.14-.21.29-.11 0 .02-.12-.21-.08-.17.02-.15.33-.35.38-.1.03-.21.06-.26.03-.1-.07-.04-.26-.11-.23-.12.05-.18.29-.17.47s.23.13.07.36c-.18.27-.24-.09-.33-.05s-.12.04-.15.17c-.01.04.06.02.05.09-.01.09-.12.13-.19.25s.12.06.147.07c0-.02.33.22.12.31a.57.57 0 0 0-.325.573c0 .13.19-.158.36-.11s.058.68.167.63c.07-.04.08-.09.13-.13s.13-.05.17-.1.223-.55.46-.4c.1.06.01.46.094.44.06-.014.23-.25.31-.2.14.09.05.056.32.23s.008.28.275.183c.43-.157.49-1.486.515-1.82a5.932 5.932 0 0 0-.43-2.69zM9.34 12.26c.143-.135.264-.293.122-.476-.088-.113-.34-.144-.51-.335a.31.31 0 0 0-.276-.14c-.06.02.128.24.128.27 0 .07.094.2.08.27-.035.15-.213.22-.352.23a.2.2 0 0 1-.18-.02c.015.12.033-.43.006-.28.012-.07.13-.19.136-.24.01-.07-.572-.18-.155-.23s.19-.35.15-.52c-.1-.37.01-.3-.27-.44-.09-.04-.33.18-.42.14-.05-.02.05-.2.02-.25s-.17 0-.22-.03c-.1-.06 0-.47-.13-.47-.02 0-.25.21-.26.14-.07-.73-.2.09-.32.06s-.07-.36.03-.34c.17-.1-.53-.55.13-.47.17.02.48.1.3-.02s-.38-.05-.4-.1c-.16-.44-.29.1-.53.06-.05-.01.03-.29-.02-.28-.09 0-.18.03-.26.27a1.107 1.107 0 0 1-.36.12c-.14.06.19.342-.16.2s-.24-.35-.27-.36c-.5-.13-.88-.26-1.17.44-.12.27.24.423-.03.643-.08.066-.7.055-.7.174 0 .09.08.08-.05.17s-.27-.07-.15.18.44.64.7.48a6.02 6.02 0 0 1 .27.67.25.25 0 0 1-.2.02c-.02 0 .77.865.79.934.03.18-.19.22-.19.22a5.966 5.966 0 0 0 2.13.887s-.04-.12.46-.27a.832.832 0 0 0 .6-.085 1.53 1.53 0 0 1 .6-.253c.92-.21.42-.6.87-1.01zm3.22-.452a2.138 2.138 0 0 0-.386.285 1.374 1.374 0 0 0-.322.374c-.187.122.12-.272.01-.21-.034.02-.05.076-.16.126-.02-.02-.086 0-.124.01-.18.066-.217.23-.345.19a.46.46 0 0 0-.43.13c-.135-.072-.2.2-.257.17 0-.045.076-.122 0-.133s-.276-.017-.347.116c-.02.032.13.026.18.075.02.03.22.3.06.19-.07-.04-.16.08-.39.14a.567.567 0 0 0-.22 0 .436.436 0 0 0-.2.08c-.15.05-.23.08-.13.09.18.02.07.1.15.09 0 0-.44.1-.29.08a1.117 1.117 0 0 1 .13-.01.3.3 0 0 0 .11.01c.19-.03.24-.05.34-.07-.05.02-.23.07-.28.14-.02.03-.11.04-.2.07a.972.972 0 0 1-.17.08 1.086 1.086 0 0 1-.32.09.422.422 0 0 0-.23.07 5.992 5.992 0 0 0 3.97-2.26.192.192 0 0 1-.05.04 1.262 1.262 0 0 1-.13.08z"/>
    <path d="M8.088 10.067c.18-.007.81.013.9-.173.128-.278-.247-.616-.535-.594-.353.027-.6.777-.365.767zm4.667.325c-.027.04-.685 0-.37.284.03.028.346-.05.332 0-.046.15-.126.123-.2.206a1.3 1.3 0 0 0-.237.417c-.027-.06-.053.02-.025.05 0 .01-.064.07-.06.08-.05.11-.154.2-.07.17.245-.07.355-.26.675-.61s.254-1.06-.045-.61zm-.815 1.258c-.043-.065-.148 0-.183.052q.027.1.16-.008c.02-.02.036-.02.022-.043z"/>
  </g>
</svg>
PK
!<n5Echrome/toolkit/skin/classic/mozapps/plugins/contentPluginActivate.pngPNG


IHDR00W
OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*!	J!QEEȠQ,
!{kּ>H3Q5B.@
$pd!s#~<<+"xM0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^uf@Wp~<<EJB[aW}g_Wl~<$2]GLϒ	bG"IbX*QqD2"B)%d,>5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A,`6B$BB
dr`)B(Ͱ*`/@4Qhp.U=pa(	Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q=C7Fdt1r=6Ыhڏ>C03l0.B8,	c˱"VcϱwE	6wB aAHXLXNH $4	7	Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![
b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw
Ljg(gwLӋT071oUX**|
J&*/TުUUT^S}FU3S	ԖUPSSg;goT?~YYLOCQ_ cx,!k
u5&|v*=9C3J3WRf?qtN	(~))4L1e\kXHQG6EYAJ'\'GgSSݧ
M=:.kDwn^Loy}/TmGX$<5qo</QC]@Caaᄑ<FFi\$mmƣ&&!&KMMRM);L;L֙͢5=12כ߷`ZxZ,eIZYnZ9YXUZ]F%ֻNNgðɶۮm}agbgŮ}}=
Z~sr:V:ޚΜ?}/gX3)iSGggs󈋉K.>.ȽJtq]zۯ6iܟ4)Y3sCQ?0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl{/]py.,:@LN8A*%w%
yg"/6шC\*NH*Mz쑼5y$3,幄'L
Lݛ:v m2=:1qB!Mggfvˬen/kY-
BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9<qy
+V<*mOW~&zMk^ʂkU
}]OX/Yߵa>(xoʿܔĹdff-[n
ڴ
VE/(ۻC<e;?TTTT6ݵan{4[>ɾUUMfeI?m]Nmq#׹=TR+Gw-
6
U#pDy	
:v{vg/jBFS[b[O>zG4<YyJTiӓgό}~.`ۢ{cjotE;;\tWW:_mt<Oǻ\kz{f7y՞9=ݽzo~r'˻w'O_@AC݇?[jwGCˆ
8>99?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-bKGD	pHYstIME%IDAThڽJ@_Z4bcp>`kkke![X(~̂`4ɸ4a3s&J))^0d-ws=ylgG=t[
MOUD}eɻy,zúbū@4o^Fv:?Ԍb8KCP-B@d`-ԔvHD8MB+e
B8`mUq]}7IENDB`PK
!<PrDchrome/toolkit/skin/classic/mozapps/plugins/contentPluginBlocked.pngPNG


IHDR00WIDATx՚[leǿ+j[D<<c!WFVo&`Ӻ[
[;`ñ0QAgꓥ
eqK}ku:ƍw4\L'>jnaHyK왡^j	LghY.a HHv,z4&5~N%H81֘a=tzGaᕩGF+k%zVbik0^\X hW
|SzMg	{؋Zh=0xSż;C@MnCc[!Wzߜ\&x|{ɒm&-g-O^x)_c]Ex/&y[(`T
תM':nV!D͆G:D(#&UWmavIWwˊ#Vn,&!f6oP!!M_UT`ri*z;Y(0d T
T?pv_4gl,d2:<tzqlE7O,d"Jf]`isfywʦfrYXz-~dh@\y&:F>2Di?Xo34o/<u2MUaжtqHcurFX<u2M̈́uAa9#dND65sX3pWxd"N2^6@&a]`,-rE٠-@<u2M$uN\8˛xd"*QpZz3xkju;x&Ն@&J]`YrͭMW]V2:^U`.g	zAgv˧17,$x)
{|:gd!Șz.;Yyʼq6@2-MU˗p	-0{]L
8m譖2*+\.`(1E:QnB7Ȣ
y*a3J&+r*`3x=UnˆgzB	oB[Xj6=ʡkJ;py[ߘFUber9+;Š*OG[oT
Ty9}'r0%h^*
°a2۾h~wir^x!?1\b/?|+00'Hh\tmZ[tQxJl6w@XcY
X4K'.Rm'j_MжGܑ-pc1,{2]l%BEmeR8.TadfP.
1Xc\_$n=jIENDB`PK
!<qZZBchrome/toolkit/skin/classic/mozapps/plugins/contentPluginClose.pngPNG


IHDR04:tEXtSoftwareAdobe ImageReadyqe<IDATUiluߧ׳hh&[76ǡBH(1D^'1/QCA"F0 cBaeO<Oc
1+1NFЅn<A7CF?ػóɠty]L"LPelѫۂ7bg>oY(d>{2ؕV{
ůä#2jdf	ɏ݃::1˥{*PEL+zD`x?[ǯ1}Y_'D~7WC%44z=e7c7ŧWfLj#[#!G <
P4S{$|NUޏ325MUwSᇧ^ԔL:ԢS_I+'{Mm@ʎ75&jz67,_cwƓ1{
/XK%sKFPͶ,%@_
s9;\>	,4k~/omE}ufbU71Gn6۳#?PMl\*$6UO9\v@`qt'G4^zIkV,yFo/슦1/d7h)Hh
6xh| 3
`@ aٲd"@0ȀA,L4AZ46`C0@iff[bCK$A(`K'zK_a#>p&d9'J8\{L@O?B*+mp๮!c.@.WJs-nl%|nbiU/r{voy{21dH*L,pzhP(J$|uJe9t׸E26zsAWTS:wa‰bbJ";*ەnT"ϰ$ȚicDA`S59L7A јxkyK|<`BZ8Іƨ:fj32S4dK맗-*1  =z25hWƥՋ<"*?x9{qG)U3Q[ArET_k	IOVLm?ZuLJi쿡s̫
&NNJVYJ%azSPpAniKOP
6/X='K<B{I#USrIENDB`PK
!<T0Dchrome/toolkit/skin/classic/mozapps/plugins/contentPluginCrashed.pngPNG


IHDR00WtEXtSoftwareAdobe ImageReadyqe<`IDATxZMH$GǟQWwWC!kxP011BBPWV0ēM䖐5 A\LdOkQguӝWj(Ag|S]{UﯪUUل,oYh/r0j*gٻ#C`gmS
,jL8M6:AKʘ2Qj4]Q-42!^N-LȎyv!}n2x㷱OaxWLkl:
-w%\HogL1U^6-40dd\ؗL#2 (F<*IQljǨٱ2=q>@"#D*NЙc(|<*IQNR7)_TvpBHE~tR%8DW^+5n,AO|Dg]:⫸;!u/\hR>*@HYܨ>$J\@O
Lp&LL;#O|<u\booouccg`RUkkkNOOF~W7772AhPLy AQAal=»DIlkka	:Hyg@OrCJdvUVVV0fE㹟`}}e/COp@;zL-3^,yxI<FFF~LDK0<<ѿ.}L5 Ʌ10{hIF6Ӗ#};0;;w2v`ttVOVwwZZZ05AnWWF
444ӓ>D* b"S9`?(766x\TTdllf(looߛ"6/[__}BG5p؎AOX&$dh&6B1Y$3Zo
c
H0CdeaaP(3B7u7G{V|!AeOyU _힞;5ܰx"kF2;<<|RVV0s*-ED|8`7,(0*MarrOlUUUݎ`0xj/็AQQQ!2?Y3>P\\lvePbt
qB^^[·KKK
ŝkvne@ (P^^^
nR+LSYYYL_Tބ{Lng"uF@72+@V&J=rzjN|/٠N`0]t*0?y\-2m~+;AW	EYH!27!U/>T?"'Տ̹x@j?nUzBXIENDB`PK
!<+i$Cchrome/toolkit/skin/classic/mozapps/plugins/contentPluginStripe.pngPNG


IHDR<<:rsRGBbKGDtIDAThMK1AQ7Ж=͗ѢW|鶻ټLrgHH:"G؈Slq=n*`W"<g%du$h('Ap{{W~_Vq5)X-$w!ph{h	[
oZ+yр͝:lNJl.ZljljzlJ	l*l
)\9Il,,6l;l;;;
{;vvv	nۃu%yi,D,<t-aEk	[5``5axXnYfkfk&k&[&[&[}ǻpzטOV7~wIENDB`PK
!<#jvc
c
@chrome/toolkit/skin/classic/mozapps/plugins/pluginBlocked-64.pngPNG


IHDR@@iq
*IDATx^
\u7;;;klQchma$T1*6	M)M(*U?J
R
#':Řx^;ޝwO_ӛ쬅Zz==fÛ[߽TIfT5N}iEaA~?HO=_֔YqLMW(7T#8"c"<Sb`e:A9#\9|C2 ߸&5]$O("
[DWTxVw/`ZQm74`VQ=/_1+
%vH_upyri<ڍ+eY?.8=v\ǩnʆ”hJZM Ƥ*H)6_!/|CHd1W|_?먞Pg7)_K\[8~uڧ_y5G{`訉OUNnQhG2Bm4iߏ)l 'ԇ~ԏc7"*;k#>UITZ.5\g/H|iK.gH
JJXߨЦ4	qN9sCжJ9Ckf@x>uҥMA$YÊtx
V_|$FWQ1s՘f~#LZtxd986J>yNx\vwUi[-,@JL|bc00JbMcA}j6B& gIJ,e#p~h^IF\i-֏E%@轴K+.j-VIn%e.a^1Y7Fٯ3	x|K@1DQ5K0mm|=#tH,CiT+ə:bDW\,";?x4R1D}%5g&T9+qu\Hců[>E!@b6F"A
,hO.Rxz8"/,_ؼE!@*"?{6]3hZ?qA/_YLFPE!?FT.1 a7x퉿exŌlBet% miOiLj`E~6ߩ~}kk_H;ߵE@۞ċ;=kqS_:ӳ"&o'vErP:O H_<wg^=@W)w`UwD/޷M>}/1Kwi{6"0rfpQL'p='O_Q8ZQْbشc~ E8oἫWS1cfRjxum}%%\f_f
7JlD4I#['V,[mx+!TT;awa ;BP0:v9})H9%oeJ#&	X&Z A@޷`\Y*>(hqkG.:@999'B^AJLA
 NqPAZ>únmI޺Su3XvNeAP2B#&mTQ<qVoPXT%[+VdEDI,DZ@` XBc* 

*9<G$ϵX<
$`dF@B@`"<"TsCn,2@NH
$t_-;
C&~ў3ȣ̺׎EK ^dp/H4}(CL>,';Z!y<`5WP$tόu7p
USB6P}լs'/М8*X1P
Y0@
H{.yz38KoZ:eG92z|> [HWoUyCo*#cúXXɢ
,yVIXBD0}4w\$BX~x錽Q_ 
^B/m7Ҙ/6_&(@Ogp諂߲?TQY{~+K%" ^/{UD(H!+VRFywLc=hg$\yxLAگuw>]}=һ2Pđ,4XBKx'm"S$0yW
a˱P>B#RS(G	uo)񺭅p@xxfA`α@B~{DgZ)V=xdhK){y	QI.Sps,1[jvǴJZ-> @BHm!EU5`
bVB<FWv'acsԉ4DL7=@kEe.M(GFFGkK/6ACt[]r">4[+'"g^wj^k[.^.Bks_C_z]"Y=xgYT<y1f<|"~҃|#RZA$4<xܧ'f)J'!pdԏR>/Kd(mB#dHൿJZWU*`‡:YIK_$k?Az)+@A|sl] 2UO?^5A
%B>@(GiDKYAL}	'r!,jpœz^C}1{*<U^N5P$5?pBR,c>>!F&dvxU?}}4\k:WGb{8 k){}b}*F:n^'<qۻ?eJ>➈	[	a@ @~M=0PaҼ@J?`@o@ldKu$ !DǮ9/7={bb0CPHonh璙G@-c;Od
뢄?@~)&%#ۀ-!`%$wZ쒀@bxUy>IENDB`PK
!<Z|=chrome/toolkit/skin/classic/mozapps/plugins/pluginBlocked.pngPNG


IHDR  szztEXtSoftwareAdobe ImageReadyqe<RIDATxWMhY/LgaCl.ɲ?B0S@׽yXX!xPV<	1+Vdd಺LL23mؙJ
^W݊8JRŴEuv6PgDz[L>;11!cccpTt7=A6#hC?TZP5m:B"h/F*:ۆHhgXqAؘ?ϝ_gϐu;vU(PciqoaD?~2@iν5ip^m1d0N'0E83A)n	R4QM`E!(>y"Ndd	X̷oE
#CwCe`f||@TP{`K6C4D<j(
1YFmu$^P%j	W0O:t2ݽmC@cCQ߿ͧt*dYe2Ũlu345}:oN&fGٖ`̲}
)1;!ٔk@Y`Y1yN>yӧmhH)s޻TS۶̓=pa8u`>/\DZqdsmr].!{KR̀F>9{veC^[\1L{L-Y}^w"vCop>
@Qgid18(t,ʙ<zȑ#0N;CZ9A㺷mcGВy1|JvrD\;uő}0WIʋ<zATz<0!g!@\˾f6kGmKTxɺ{ycu+7Q}	Ȁpz_a5sdÇ2}MNbƤh2Ood4@6xKj4%$}8M߄"^g" rj6́J>0r~gU^ٗ/<Aй7xo(V;+G}i!IENDB`PK
!<	eW@chrome/toolkit/skin/classic/mozapps/plugins/pluginGeneric-16.pngPNG


IHDRaIDAT8nA3sDat@,D[ !HA!6
("
--$ra	8	椑fvv֨*Yb;轸<yy>}ۍ+G[
>
qQۧk3j5ةooZzXm2+PRSZyQ̏#F~<&	ף*0ĽsԫfB;YC3ӌ8ktGЌBsy^8uB9\ȬݖY»P@D/ 2^h` 35A*"Q0'N)NR$)R$[(qm5L'2X8'sKr',8WZ<A&#O2\w(2|
ΗbB@<e	baDA
e9?RPY^4mUPlqޘC߄AIENDB`PK
!<:zN:=chrome/toolkit/skin/classic/mozapps/plugins/pluginGeneric.pngPNG


IHDR  szzIDATXk]E?g~$/y/y1
RHIVT(Q\f ..Km\]ɻw游$7&tlfޝ|?=3g*ϲg?]nafo=0?J'~ʯi[G`5|=>l[8Ъ^fcʀ#GxڭXYOY^ڙU>JkzTayFڞyzWƭ맾:r?d|DTU!`ɌcpҲS;v9yWᗛu"ndA(ˏs+O";{3g7ܙ[6FNZlܟ7$8;3j;+}|yz@̫ɍRoy[YCmCx+$y\S逵6دMO2_7FNL48=<$.hFH
&X+zmg%%Tg8ggYO^i"dABT"tm;I6SUVUŷ1q>X ƞv
Jې;|mB%wXPq-6u;/9ч)#ƊZ|@}`Y(&z ۻߙr*jT|Ă/bqң5>3PZ$D%,(!*!쿳 "So^B$
JO#("y`Sk$!tS%",l	nVWSlrm^f8q8y:< /\W@}RE80US`,iH,APhZ@l 8B2K'$[+IENDB`PK
!<bll=chrome/toolkit/skin/classic/mozapps/plugins/pluginHelp-16.pngPNG


IHDRasRGBbKGD	pHYstIME
(>IDAT8u?Q]+IlsP$i
EQp"́!* B0V E`uĤ$9Żl?;6cY^~f
O! Dp\(JU|Gౢ(6Ip&?Dw>:#8x+_ftDS(`Znl6LwgryDL&Owbخ'"Unr8,˺H$>x}e=`<b2L	`0j}dr-"Wy @:>DdcZ~4qss{EjrjEDRP(l6-50
~`ooh4ߑiھaV4m4VȦRimw;mNRu =
$<k,'"IENDB`PK
!<=~W2=chrome/toolkit/skin/classic/mozapps/plugins/pluginProblem.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace html url(http://www.w3.org/1999/xhtml);

/* These styles affect only the bound element, not other page content. */
/* Keep any changes to these styles in sync with plugin-doorhanger.inc.css */
.mainBox {
  font: message-box;
  font-size: 12px;
  text-align: center;
  display: table;
  width: 100%;
  height: 100%;
  background-color: rgb(72,72,72);
  color: white;
  -moz-user-select: none;
}

.hoverBox {
  display: table-cell;
  box-sizing: border-box;
  padding: 5px;
  vertical-align: middle;
  width: 100%;
  height: 100%;
}
:-moz-handler-vulnerable-updatable .hoverBox:active,
:-moz-handler-vulnerable-no-update .hoverBox:active,
:-moz-handler-clicktoplay .hoverBox:active {
  background-color: rgb(65, 65, 65);
}

:-moz-handler-clicktoplay .hoverBox:active .msgClickToPlay,
:-moz-handler-vulnerable-updatable .hoverBox:active .msgClickToPlay,
:-moz-handler-vulnerable-no-update .hoverBox:active .msgClickToPlay {
  color: red;
}

:-moz-handler-vulnerable-updatable .hoverBox,
:-moz-handler-vulnerable-no-update .hoverBox,
:-moz-handler-blocked .hoverBox,
:-moz-handler-crashed .hoverBox {
  background-image: url(chrome://mozapps/skin/plugins/contentPluginStripe.png);
}

html|a {
  color: white;
}

.icon {
  width: 48px;
  height: 48px;
  background-position: center;
  background-repeat: no-repeat;
  border: none;
  background-color: transparent;
  -moz-user-focus: ignore;
  margin-bottom: 6px;
}

:-moz-handler-vulnerable-updatable .icon,
:-moz-handler-vulnerable-no-update .icon {
  background-image: url(chrome://mozapps/skin/plugins/contentPluginBlocked.png);
  -moz-user-focus: normal;
}
:-moz-handler-blocked .icon {
  background-image: url(chrome://mozapps/skin/plugins/contentPluginBlocked.png);
}
a .icon,
:-moz-handler-clicktoplay .icon {
  background-image: url(chrome://mozapps/skin/plugins/contentPluginActivate.png);
  -moz-user-focus: normal;
}
:-moz-handler-crashed .icon {
  background-image: url(chrome://mozapps/skin/plugins/contentPluginCrashed.png);
}

.throbber {
  padding-left: 16px; /* width of the background image */
  background: url(chrome://global/skin/icons/loading.png) no-repeat;
  margin-left: 5px;
}

@media (min-resolution: 1.1dppx) {
  .throbber {
    background-image: url(chrome://global/skin/icons/loading@2x.png);
    background-size: 16px;
  }
}

.msgClickToPlay {
  text-decoration: underline;
}

/* on desktop, don't ever show the tap-to-play UI: that is for mobile only */
:-moz-handler-clicktoplay .msgTapToPlay,
a .msgTapToPlay  {
  display: none;
}

.submitStatus div {
  min-height: 19px; /* height of biggest line (with throbber) */
}

.submitComment {
  width: 340px;
  height: 70px;
  padding: 5px;
  border: none;
  border-radius: 5px;
  resize: none;
  font-family: inherit;
  font-size: inherit;
}

.submitURLOptInBox {
  text-align: start;
}

.submitURLOptIn {
  margin-left: -1px;
}

.mainBox[chromedir="rtl"] .submitURLOptIn {
  margin-left: 0;
  margin-right: -1px;
}

.submitButtonBox {
  margin-top: 7px;
}

.submitButton {
  float: right;
}

.mainBox[chromedir="rtl"] .submitButton {
  float: left;
}

.helpIcon {
  display: inline-block;
  min-width: 16px;
  min-height: 16px;
  background: url(chrome://mozapps/skin/plugins/pluginHelp-16.png) no-repeat;
  cursor: pointer;
  float: left;
}

.mainBox[chromedir="rtl"] .helpIcon {
  float: right;
}

.closeIcon {
  display: block;
  width: 16px;
  height: 16px;
  margin-top: 4px;
  margin-inline-start: -20px;
  margin-inline-end: 4px;
  border: none;
  background-color: transparent;
  background-image: url("chrome://mozapps/skin/plugins/contentPluginClose.png");
  background-repeat: no-repeat;
}

.closeIcon:hover {
  background-position: -16px 0;
}

.closeIcon:hover:active {
  background-position: -32px 0;
}

.action-link {
  display: inline-block;
  border-radius: 10px;
  background-color: rgb(35, 35, 35);
  padding: 2px 8px;
  margin-top: 7px;
  text-decoration: none;
}
.action-link:active {
  background-color: rgb(20, 20, 20);
}

:-moz-handler-vulnerable-updatable .action-link {
  background-color: #a81b0c;
}
:-moz-handler-vulnerable-updatable .action-link:active {
  background-color: #801409;
}
PK
!<Fl[[@chrome/toolkit/skin/classic/mozapps/profile/profileSelection.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


@import url("chrome://global/skin/global.css");

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

#profiles > listitem {
  list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
}

#profiles > listitem > listcell > image {
  width: 16px;
  height: 16px;
}

box#managebuttons > button {
  min-width: 8em;
}

#managebuttons {
  padding-top: 1em;
}
PK
!<[/,NN;chrome/toolkit/skin/classic/mozapps/profile/profileicon.pngPNG


IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxlkqǿ23Y6MFWĖ"ZjEDћ/ ⡗P-(EZiZLff$3(-}#֭A<SS'2EQ>_lMDvn62iFG2
K_gg%w<poeXrPPy^|	c
IRzb߃DYƏL;9&=*8oBTݰ:eDQ(q0x-<èz*%A+8|.v080p͑Ag825;]yfT]uٜbrȷ.߼
1uXYsA	VDgPu9y>o#CDd2l!Kx{ 85v'#hubٕ[
LA<1ܻyMQqu}Go'4B_hO rWdbN/yǰchzJ"
}~u_s
	c;}	W 0pm;{y5w@GzKj
%Nre*,w5owðUWTlFF!|r|<
\RY#'+_Gذs1zrûrw&4
69GX,-R.)ZSL1-"|&XOIENDB`PK
!<(>chrome/toolkit/skin/classic/mozapps/update/downloadButtons.pngPNG


IHDR@@iq	pHYsHHFk>	vpAg@@`bKGDCyIDATxyTW;>bL2,RN3$h2Iz41IIeZQA\N\p	(,""hQqdbW!w}R_?sw߻w{﫯J~GӉߠ@Q2/@U\=>K3-]ﹲasyG;4&5b;kZIH9xiFe^菜׮uY.AರWJpxofEA~cyx(΢~F9<"d|Cx8`yCw]2^_-o'_u'pfL!9nђ9cZ."f2~rhC~NaNqM#/PҶgcAndw`7Y}
('pn-
D~ٿ
XҳUw_	z6(:?@|=k5.|XQ+[{[#Hy![Ӣܶj&{:fbs0ue=`6阐jYpKtN& ӱDMOrKzaFX=Wb,Bi_7:wW<׀{pgg{_t
e;&v?`;0Γ}53~ߵAЯ	s7K(sy70^aBi
RЀ
jm7ܨpf/LY7@t.@XN;}dǫǜ2s>Mc.I<,ifa=7.A̬oM֢ggwsèU;ow18bS~f[pkzm
ǫAod6G0uu#ڝfF٨,J8>(k_;<=kn\èS~GfG=U}WnOπ8p6!vg#l
+88cO=vYDI'x
r̿6iNX7|T:^gc	ww{T{rXRu{p8
^N ̓2<XђHc쇜>c:Hd-97~8齱Z>zYp‡ԓ|mFQ#M_:w-x5HM?lOG~F%82c9"1l͌w#fu:'>=?5TO۞Wӥ)S
PկА

1gY~w/?2===|e?#!F>ւ=P</Uw}v~Wvv>џvJWB-iryHH9xsd(ҕիLԂ2TqjEX(64߾K9+4ZP5im:oȯyg|CBǺMx,m-]'@
fP6|u=d5#,][*byEsJzלy^%3&peݖsyoG؜z,@ww{.ԱSt]\eū67j-PjagSnfEs*
TQPPTm@>g,]u*#9}"ÏQȦ b~{̿,Plʭ-F<~w*5w&!L'(ԃbE-(BaΎ~bUN[[lVX3hzYRJu;1/	\*w@VesI!y:x׸T<+%C kkjC|^4@AI`6>}MCđp:1qsssۙ.\I(7Aʳs'k蜲mbT<k+kq1HP7XYǤ]XdǫWh9sENG11c.WqAZAX4Ǎ7:=+^٨}t 9/	s܁O7)W"
wrՠM\s-r+Wsx(
(k1?SNMU44A)_W,@=["X*;XR*
x 岎#,mAa
~L:cv
؋x'1q
rh$Ӝ)/|>wV<m"Tk'b)wǨx?\յs4Z#T(xe:]0O>+-8G׍<;mhsH(ZP_d~_{q?\
>ޣX/<>b'JtYK?qI(G5Ϻ
nx5UBc'[PrmyqXr=$/#qJw`Vg&`OSߤCVV
>Vs;|UTVvfm6*tU_і=RC̫?W^"l7
wKCB:͑|}y_7@2HHHxדܱcG	4G6QEeΝGC6$Ov~GףN<-dmB<ir#6#	4G_/v>˒0`;U)#NnF>}|4<|,/15FrXlW'>$ϱ'z4<:9i=9H("Y\_2qH|Ȇx02Og
9ߋ1hO>;.n	FY&}GG"vɐs^앱`/ǝr]
?j#FX}X1pI&1*ޜ)bXC׌(x#`Y9f+9ɌIΜ9OQor"ˆgo]v)`?.IStr
/;6\Q/l FX_ûPZeee^p!9柁ELH9Q\A'`;{ʕ+0,lf,ݻ?B갽g؏2=p&@)#'o@d"'u1k@ThLS`r	g29Q1y<0i;&(*qv.oe!OaQ=G4!ƻsέMa̳Bưl^	di#*kSMp_\7c#,ۆ1$dT˲阐.S؀hxZa&PKiiLH9f'*Az> Hc:q|Q~ --Mp΁aX\-0Q }K	n1 '%_9LHٻE藒8wc#Ә\63'XDoH	
Q:
cv梼_Ä\A^*,pPA[,Fv#,7HoD`ڲRpPdB:͑M6&"!-hӜ-e:&o/Z/J5ga\68gbAp,^u(b$/!9ĂppKHa0w&,]E~M}/22J%g? OX/| ^Mw@vN4**<DDE{mٌr#CyY.?͔_%SSS=asB^UUtzf&3y^a㦘C)rٹ3<~Erɳp|~#1, ?Vs-FA+YYY<pGIH9I;o*"OIOIF2{?jkk=WY6l]~/~shzuu'EѣƬ7NsdJKKիWWZ+WTXBl256…^|_t^VRR"PA/ >/oIF9~
9xB';Rش%K/^󩪪䷓xR>kS~b'c5w
@NJ_t:$$D^hMR5"?؜0&_Kp'˜|oڴ$Btp
D
 C⃂.Toݺu`yY$+Kw1tNx&c$)իWȲ/2/p


n{iHn۶MH<_:&SH#vt|-񂝊LRbzS˗O`|]BW<RbHJxGh>IxN
I9HyqEwm"-_#YWͶ`DD,:$p
:;f[`|mB^a,܌)'fh$R!"Oە<xPLJ
!f!N
"

ĦNs)6y'6&_k<夝LLH9a=Rc>YBR𩨨09-aU|lRRF//^‡Pp)lEϼyԦ2w\z-|,t)HcLyWIz͝N=}Ӷ;gU3''g͛ga}~
O$_ѿ9,s"zTXtSoftwarexsLOJUMLO
JML/ԮMIENDB`PK
!<ЖKn

6chrome/toolkit/skin/classic/mozapps/update/updates.css/* General */
/* Specify the size for the wizardpage so the billboard has a fixed size. 3rd
   party themes should typically specify the same values. */
wizardpage {
  height: 360px;
  width: 700px;
}

/* Remove margin and padding so the billboard will extend to the edge of the
   window. 3rd party themes should typically specify the same values. */
#updates, .wizard-page-box {
  margin: 0;
  padding: 0;
}

.update-header {
  padding: 0px 10px;
}

.update-content {
  padding: 10px;
}

.wizard-buttons-separator {
  margin-top: 0 !important;
}

.inline-link {
  color: -moz-nativehyperlinktext;
  text-decoration: none;
}

.inline-link:hover {
  text-decoration: underline;
}

/* Unsupported Page */
#unsupportedLabel, #unsupportedLinkLabel {
  margin-inline-start: 0;
  padding-inline-start: 0;
}

/* Update Found Basic Page */
#updateName, #updateFinishedName {
  font-weight: bold;
  font-size: larger;
}

/* Downloading Page */
#downloadStatusLine {
  -moz-box-align: center;
}

#downloadStatus {
  height: 3em !important;
}

#downloadStatusProgress {
  padding-right: 5px;
}

#pauseButton {
  list-style-image: url(chrome://mozapps/skin/update/downloadButtons.png);
  -moz-image-region: rect(0px, 48px, 16px, 32px);
  -moz-appearance: none;
  background-color: transparent;
  border: none;
  min-width: 0;
  min-height: 0;
  margin: 0;
  padding: 0;
}

/* !Important must be used otherwise this won't immediately take affect */
#pauseButton > .button-box {
  padding: 0 !important;
}

#pauseButton:hover {
  -moz-image-region: rect(16px, 48px, 32px, 32px);
}

#pauseButton:active {
  -moz-image-region: rect(32px, 48px, 48px, 32px);
}

#pauseButton[disabled="true"] {
  -moz-image-region: rect(48px, 48px, 64px, 32px);
}

#pauseButton[paused="true"] {
  -moz-image-region: rect(0px, 16px, 16px, 0px);
}

#pauseButton[paused="true"]:hover {
  -moz-image-region: rect(16px, 16px, 32px, 0px);
}

#pauseButton[paused="true"]:active {
  -moz-image-region: rect(32px, 16px, 48px, 0px);
}

#pauseButton[paused="true"][disabled="true"] {
  -moz-image-region: rect(48px, 16px, 64px, 0px);
}

#verificationFailedIcon {
  margin-left: 5px;
  list-style-image: url("chrome://global/skin/icons/information-16.png");
}

/* Error Page */
#errorReason {
  margin-top: 1px;
  margin-bottom: 2px;
  margin-inline-start: 6px !important;
  margin-inline-end: 5px;
  font-weight: bold;
}

/* Update History Window */
update {
  border-bottom: 1px dotted #C0C0C0;
}

.update-name {
  font-weight: bold;
}

.update-label-column {
  -moz-box-align: end;
}

.update-type {
  font-weight: bold;
  color: #990000;
}

#historyItems {
  -moz-appearance: listbox;
  height: 200px;
  margin: 1px 5px 4px 5px;
}

#historyItems > scrollbox {
  margin-bottom: 1px;
}
PK
!<Jv=chrome/toolkit/skin/classic/mozapps/viewsource/viewsource.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This is for styling the menus of the viewsource window */
PK
!<'TBchrome/toolkit/skin/classic/mozapps/xpinstall/xpinstallConfirm.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#xpinstallheader {
  margin-bottom: 2em;
}

#itemList {
  -moz-appearance: listbox;
  margin: 3px 4px 10px 4px;
  height: 14em;
  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;
}

#itemWarningIntro {
  margin-inline-start: 8px;
}

#dialogContentBox {
  padding: 5px;
}

installitem {
  padding-top: 5px;
  padding-bottom: 5px;
  padding-inline-start: 5px;
  padding-inline-end: 0;
  border-bottom: 1px dotted #C0C0C0;
  margin-bottom: 5px;
}

.alert-icon {
  list-style-image: url("chrome://global/skin/icons/warning-large.png");
  width: 48px;
  height: 48px;
  margin-inline-end: 20px;
}

.warning {
  font-weight: bold;
  font-size: 1.25em;
  margin-bottom: 1em;
}

.xpinstallIconContainer {
  width: 32px;
  height: 32px;
  margin-inline-end: 5px;
}

.xpinstallItemName {
  font-weight: bold;
}

.xpinstallItemSigned {
  font-style: italic;
  font-size: 0.9em;
}

.xpinstallItemURL {
  -moz-appearance: none;
  border: none;
  padding: 0;
  background-color: -moz-Field;
  color: -moz-FieldText;
  margin-top: 1px;
  margin-bottom: 1px;
  margin-inline-start: 6px;
  margin-inline-end: 5px;
}

.xpinstallItemIcon {
  max-width: 32px;
  max-height: 32px;
  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.svg");
}

installitem[type="theme"] .xpinstallItemIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
}

installitem[type="locale"] .xpinstallItemIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
}

installitem[type="plugin"] .xpinstallItemIcon {
  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
}

installitem[type="dictionary"] .xpinstallItemIcon {
  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
}
PK
!<3d
%chrome/recording/content/recording.js/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- /
/* vim: set shiftwidth=4 tabstop=8 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/. */

var CC = Components.classes;
const CI = Components.interfaces;

const NS_GFXINFO_CONTRACTID = "@mozilla.org/gfx/info;1";

var gContainingWindow = null;

var gBrowser;

function OnDocumentLoad(evt) {
    if (evt.target != gBrowser.contentDocument || evt.target.location == "about:blank")
        return;
    gBrowser.removeEventListener("load", OnDocumentLoad, true);
    gContainingWindow.close();
}

this.OnRecordingLoad = function OnRecordingLoad(win) {
    if (win === undefined || win == null) {
        win = window;
    }
    if (gContainingWindow == null && win != null) {
        gContainingWindow = win;
    }

    gBrowser = gContainingWindow.document.getElementById("browser");

    var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo);
    var info = gfxInfo.getInfo();
    dump(info.AzureContentBackend + "\n");
    if (info.AzureContentBackend == "none") {
        alert("Page recordings may only be made with Azure content enabled.");
        gContainingWindow.close();
        return;
    }

    gBrowser.addEventListener("load", OnDocumentLoad, true);

    var args = window.arguments[0].wrappedJSObject;

    gBrowser.loadURI(args.uri);
};
PK
!<X&chrome/recording/content/recording.xul<!-- vim: set shiftwidth=4 tabstop=8 autoindent 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/. -->
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="recording-window"
        hidechrome="true"
        onload="OnRecordingLoad();"
        style="background:white; overflow:hidden; width:800px; height:600px;"
        >
    <script type="application/ecmascript" src="recording.js" />
  <browser id="browser" type="content" primary="true" style="min-width: 1024px; min-height: 768px; max-width: 1024px; max-height: 768px"/>
</window>
PK
!<dPPgreprefs.js//@line 1 "z:\build\build\src\security\manager\ssl\security-prefs.js"
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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("security.tls.version.min", 1);
pref("security.tls.version.max", 3);
pref("security.tls.version.fallback-limit", 3);
pref("security.tls.insecure_fallback_hosts", "");
pref("security.tls.enable_0rtt_data", false);

pref("security.ssl.treat_unsafe_negotiation_as_broken", false);
pref("security.ssl.require_safe_negotiation",  false);
pref("security.ssl.enable_ocsp_stapling", true);
pref("security.ssl.enable_false_start", true);
pref("security.ssl.enable_alpn", true);

pref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
pref("security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", true);
pref("security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", true);
pref("security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", true);
pref("security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", true);
pref("security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", true);
pref("security.ssl3.ecdhe_rsa_aes_128_sha", true);
pref("security.ssl3.ecdhe_ecdsa_aes_128_sha", true);
pref("security.ssl3.ecdhe_rsa_aes_256_sha", true);
pref("security.ssl3.ecdhe_ecdsa_aes_256_sha", true);
pref("security.ssl3.dhe_rsa_aes_128_sha", true);
pref("security.ssl3.dhe_rsa_aes_256_sha", true);
pref("security.ssl3.rsa_aes_128_sha", true);
pref("security.ssl3.rsa_aes_256_sha", true);
pref("security.ssl3.rsa_des_ede3_sha", true);

pref("security.content.signature.root_hash",
     "97:E8:BA:9C:F1:2F:B3:DE:53:CC:42:A4:E6:57:7E:D6:4D:F4:93:C2:47:B4:14:FE:A0:36:81:8D:38:23:56:0E");

pref("security.default_personal_cert",   "Ask Every Time");
pref("security.remember_cert_checkbox_default_setting", true);
pref("security.ask_for_password",        0);
pref("security.password_lifetime",       30);

// The supported values of this pref are:
// 0: disable detecting Family Safety mode and importing the root
// 1: only attempt to detect Family Safety mode (don't import the root)
// 2: detect Family Safety mode and import the root
// (This is only relevant to Windows 8.1)
pref("security.family_safety.mode", 2);

pref("security.enterprise_roots.enabled", false);

// The supported values of this pref are:
// 0: do not fetch OCSP
// 1: fetch OCSP for DV and EV certificates
// 2: fetch OCSP only for EV certificates
//@line 55 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.OCSP.enabled", 1);
//@line 59 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.OCSP.require", false);
pref("security.OCSP.GET.enabled", false);
//@line 62 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.OCSP.timeoutMilliseconds.soft", 2000);
//@line 66 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.OCSP.timeoutMilliseconds.hard", 10000);

pref("security.pki.cert_short_lifetime_in_days", 10);
// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
// See the comment in CertVerifier.cpp.
// 3 = only allow SHA-1 for certificates issued by an imported root.
pref("security.pki.sha1_enforcement_level", 3);

// security.pki.name_matching_mode controls how the platform matches hostnames
// to name information in TLS certificates. The possible values are:
// 0: always fall back to the subject common name if necessary (as in, if the
//    subject alternative name extension is either not present or does not
//    contain any DNS names or IP addresses)
// 1: fall back to the subject common name for certificates valid before 23
//    August 2016 if necessary
// 2: fall back to the subject common name for certificates valid before 23
//    August 2015 if necessary
// 3: only use name information from the subject alternative name extension
//@line 85 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.pki.name_matching_mode", 1);
//@line 89 "z:\build\build\src\security\manager\ssl\security-prefs.js"

// security.pki.netscape_step_up_policy controls how the platform handles the
// id-Netscape-stepUp OID in extended key usage extensions of CA certificates.
// 0: id-Netscape-stepUp is always considered equivalent to id-kp-serverAuth
// 1: it is considered equivalent when the notBefore is before 23 August 2016
// 2: similarly, but for 23 August 2015
// 3: it is never considered equivalent
//@line 97 "z:\build\build\src\security\manager\ssl\security-prefs.js"
pref("security.pki.netscape_step_up_policy", 1);
//@line 101 "z:\build\build\src\security\manager\ssl\security-prefs.js"

// Configures Certificate Transparency support mode:
// 0: Fully disabled.
// 1: Only collect telemetry. CT qualification checks are not performed.
pref("security.pki.certificate_transparency.mode", 0);

pref("security.webauth.u2f", false);
pref("security.webauth.u2f_enable_softtoken", false);
pref("security.webauth.u2f_enable_usbtoken", false);

pref("security.webauth.webauthn", false);
pref("security.webauth.webauthn_enable_softtoken", false);
pref("security.webauth.webauthn_enable_usbtoken", false);

pref("security.ssl.errorReporting.enabled", true);
pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
pref("security.ssl.errorReporting.automatic", false);

// Impose a maximum age on HPKP headers, to avoid sites getting permanently
// blacking themselves out by setting a bad pin.  (60 days by default)
// https://tools.ietf.org/html/rfc7469#section-4.1
pref("security.cert_pinning.max_max_age_seconds", 5184000);

// HSTS Priming
// If a request is mixed-content, send an HSTS priming request to attempt to
// see if it is available over HTTPS.
// Don't change the order of evaluation of mixed-content and HSTS upgrades in
// order to be most compatible with current standards in Release
pref("security.mixed_content.send_hsts_priming", false);
pref("security.mixed_content.use_hsts", false);
//@line 137 "z:\build\build\src\security\manager\ssl\security-prefs.js"
// Approximately 1 week default cache for HSTS priming failures, in seconds
pref ("security.mixed_content.hsts_priming_cache_timeout", 604800);
// Force the channel to timeout in 2 seconds if we have not received
// expects a time in milliseconds
pref ("security.mixed_content.hsts_priming_request_timeout", 2000);
//@line 1 "z:\build\build\src\modules\libpref\init\all.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/. */

/* The prefs in this file are shipped with the GRE and should apply to all
 * embedding situations. Application-specific preferences belong somewhere else,
 * for example xpfe/bootstrap/browser-prefs.js
 *
 * Platform-specific #ifdefs at the end of this file override the generic
 * entries at the top.
 */

/*
 * 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.
 */

pref("preferences.allow.omt-write", false);

pref("keyword.enabled", false);
pref("general.useragent.locale", "chrome://global/locale/intl.properties");
pref("general.useragent.compatMode.firefox", false);

// This pref exists only for testing purposes. In order to disable all
// overrides by default, don't initialize UserAgentOverrides.jsm.
pref("general.useragent.site_specific_overrides", true);

pref("general.config.obscure_value", 13); // for MCD .cfg files

pref("general.warnOnAboutConfig", true);

// maximum number of dated backups to keep at any time
pref("browser.bookmarks.max_backups",       5);

// Delete HTTP cache v1 data
pref("browser.cache.auto_delete_cache_version", 0);
// Preference for switching the cache backend, can be changed freely at runtime
// 0 - use the old (Darin's) cache
// 1 - use the new cache back-end (cache v2)
pref("browser.cache.use_new_backend",       0);
pref("browser.cache.use_new_backend_temp",  true);

pref("browser.cache.disk.enable",           true);
// Is this the first-time smartsizing has been introduced?
pref("browser.cache.disk.smart_size.first_run", true);
// Does the user want smart-sizing?
pref("browser.cache.disk.smart_size.enabled", true);
// Which max value should we use for smart-sizing?
pref("browser.cache.disk.smart_size.use_old_max", true);
// Size (in KB) explicitly set by the user. Used when smart_size.enabled == false
pref("browser.cache.disk.capacity",         256000);
// When smartsizing is disabled we could potentially fill all disk space by
// cache data when the disk capacity is not set correctly. To avoid that we
// check the free space every time we write some data to the cache. The free
// space is checked against two limits. Once the soft limit is reached we start
// evicting the least useful entries, when we reach the hard limit writing to
// the entry fails.
pref("browser.cache.disk.free_space_soft_limit", 5120); // 5MB
pref("browser.cache.disk.free_space_hard_limit", 1024); // 1MB
// Max-size (in KB) for entries in disk cache. Set to -1 for no limit.
// (Note: entries bigger than 1/8 of disk-cache are never cached)
pref("browser.cache.disk.max_entry_size",    51200);  // 50 MB
pref("browser.cache.memory.enable",         true);
// -1 = determine dynamically, 0 = none, n = memory capacity in kilobytes
//pref("browser.cache.memory.capacity",     -1);
// Max-size (in KB) for entries in memory cache. Set to -1 for no limit.
// (Note: entries bigger than than 90% of the mem-cache are never cached)
pref("browser.cache.memory.max_entry_size",  5120);
// Memory limit (in kB) for new cache data not yet written to disk. Writes to
// the cache are buffered and written to disk on background with low priority.
// With a slow persistent storage these buffers may grow when data is coming
// fast from the network. When the amount of unwritten data is exceeded, new
// writes will simply fail. We have two buckets, one for important data
// (priority) like html, css, fonts and js, and one for other data like images,
// video, etc.
// Note: 0 means no limit.
pref("browser.cache.disk.max_chunks_memory_usage", 10240);
pref("browser.cache.disk.max_priority_chunks_memory_usage", 10240);

pref("browser.cache.disk_cache_ssl",        true);
// 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically
pref("browser.cache.check_doc_frequency",   3);
// Limit of recent metadata we keep in memory for faster access, in Kb
pref("browser.cache.disk.metadata_memory_limit", 250); // 0.25 MB
// The number of chunks we preload ahead of read.  One chunk has currently 256kB.
pref("browser.cache.disk.preload_chunk_count", 4); // 1 MB of read ahead
// The half life used to re-compute cache entries frecency in hours.
pref("browser.cache.frecency_half_life_hours", 6);

// Number of seconds the cache spends writting pending data and closing files
// after the shutdown has been signalled.  Past that time data are never written
// and files are left open given up to the OS to do the cleanup.
pref("browser.cache.max_shutdown_io_lag", 2);

pref("browser.cache.offline.enable",           true);
// enable offline apps by default, disable prompt
pref("offline-apps.allow_by_default",          true);

// offline cache capacity in kilobytes
pref("browser.cache.offline.capacity",         512000);

// the user should be warned if offline app disk usage exceeds this amount
// (in kilobytes)
pref("offline-apps.quota.warn",        51200);

// zlib compression level used for cache compression:
// 0 => disable compression
// 1 => best speed
// 9 => best compression
// cache compression turned off for now - see bug #715198
pref("browser.cache.compression_level", 0);

// Don't show "Open with" option on download dialog if true.
pref("browser.download.forbid_open_with", false);

// Whether or not testing features are enabled.
pref("dom.quotaManager.testing", false);

// Whether or not indexedDB is enabled.
pref("dom.indexedDB.enabled", true);
// Whether or not indexedDB experimental features are enabled.
pref("dom.indexedDB.experimental", false);
// Enable indexedDB logging.
pref("dom.indexedDB.logging.enabled", true);
// Detailed output in log messages.
pref("dom.indexedDB.logging.details", true);
// Enable profiler marks for indexedDB events.
pref("dom.indexedDB.logging.profiler-marks", false);

// Whether or not File Handle is enabled.
pref("dom.fileHandle.enabled", true);

// Whether window.onappinstalled from "W3C Web Manifest" is enabled
pref("dom.manifest.onappinstalled", false);

// Whether or not selection events are enabled
pref("dom.select_events.enabled", true);

// Whether or not selection events on text controls are enabled
//@line 147 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.select_events.textcontrols.enabled", false);
//@line 149 "z:\build\build\src\modules\libpref\init\all.js"

// Whether or not Web Workers are enabled.
pref("dom.workers.enabled", true);

// The number of workers per domain allowed to run concurrently.
// We're going for effectively infinite, while preventing abuse.
pref("dom.workers.maxPerDomain", 512);

pref("dom.serviceWorkers.enabled", false);

// The amount of time (milliseconds) service workers keep running after each event.
pref("dom.serviceWorkers.idle_timeout", 30000);

// The amount of time (milliseconds) service workers can be kept running using waitUntil promises.
pref("dom.serviceWorkers.idle_extended_timeout", 300000);

// Enable test for 24 hours update, service workers will always treat last update check time is over 24 hours
pref("dom.serviceWorkers.testUpdateOverOneDay", false);

// Whether nonzero values can be returned from performance.timing.*
pref("dom.enable_performance", true);

// Whether resource timing will be gathered and returned by performance.GetEntries*
pref("dom.enable_resource_timing", true);

// Enable printing performance marks/measures to log
pref("dom.performance.enable_user_timing_logging", false);

// Enable notification of performance timing
pref("dom.performance.enable_notify_performance_timing", false);

// Enable Permission API's .revoke() method
pref("dom.permissions.revoke.enable", false);

// Enable exposing timeToNonBlankPaint
pref("dom.performance.time_to_non_blank_paint.enabled", false);

// Enable Performance Observer API
//@line 190 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.enable_performance_observer", false);
//@line 192 "z:\build\build\src\modules\libpref\init\all.js"

// Enable requestIdleCallback API
pref("dom.requestIdleCallback.enabled", true);

// Whether the Gamepad API is enabled
pref("dom.gamepad.enabled", true);
pref("dom.gamepad.test.enabled", false);
//@line 200 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.gamepad.non_standard_events.enabled", false);
//@line 204 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.gamepad.extensions.enabled", true);
pref("dom.gamepad.haptic_feedback.enabled", true);

// If this is true, TextEventDispatcher dispatches keydown and keyup events
// even during composition (keypress events are never fired during composition
// even if this is true).
pref("dom.keyboardevent.dispatch_during_composition", false);

// Whether to run add-on code in different compartments from browser code. This
// causes a separate compartment for each (addon, global) combination, which may
// significantly increase the number of compartments in the system.
pref("dom.compartment_per_addon", true);

// Whether to enable the JavaScript start-up cache. This causes one of the first
// execution to record the bytecode of the JavaScript function used, and save it
// in the existing cache entry. On the following loads of the same script, the
// bytecode would be loaded from the cache instead of being generated once more.
pref("dom.script_loader.bytecode_cache.enabled", false);

// Ignore the heuristics of the bytecode cache, and always record on the first
// visit. (used for testing purposes).

// Choose one strategy to use to decide when the bytecode should be encoded and
// saved. The following strategies are available right now:
//   * -2 : (reader mode) The bytecode cache would be read, but it would never
//          be saved.
//   * -1 : (eager mode) The bytecode would be saved as soon as the script is
//          seen for the first time, independently of the size or last access
//          time.
//   *  0 : (default) The bytecode would be saved in order to minimize the
//          page-load time.
//
// Other values might lead to experimental strategies. For more details, have a
// look at: ScriptLoader::ShouldCacheBytecode function.
pref("dom.script_loader.bytecode_cache.strategy", 0);

// Fastback caching - if this pref is negative, then we calculate the number
// of content viewers to cache based on the amount of available memory.
pref("browser.sessionhistory.max_total_viewers", -1);

pref("ui.use_native_colors", true);
pref("ui.click_hold_context_menus", false);
// Duration of timeout of incremental search in menus (ms).  0 means infinite.
pref("ui.menu.incremental_search.timeout", 1000);
// If true, all popups won't hide automatically on blur
pref("ui.popup.disable_autohide", false);

pref("browser.display.use_document_fonts",  1);  // 0 = never, 1 = quick, 2 = always
// 0 = default: always, except in high contrast mode
// 1 = always
// 2 = never
pref("browser.display.document_color_use", 0);
pref("browser.display.use_system_colors",   false);
pref("browser.display.foreground_color",    "#000000");
pref("browser.display.background_color",    "#FFFFFF");
pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline
// 0 = no external leading,
// 1 = use external leading only when font provides,
// 2 = add extra leading both internal leading and external leading are zero
pref("browser.display.normal_lineheight_calc_control", 2);
// enable showing image placeholders while image is loading or when image is broken
pref("browser.display.show_image_placeholders", true);
// if browser.display.show_image_placeholders is true then this controls whether the loading image placeholder and border is shown or not
pref("browser.display.show_loading_image_placeholder", false);
// min font device pixel size at which to turn on high quality
pref("browser.display.auto_quality_min_font_size", 20);
pref("browser.anchor_color",                "#0000EE");
pref("browser.active_color",                "#EE0000");
pref("browser.visited_color",               "#551A8B");
pref("browser.underline_anchors",           true);
pref("browser.enable_automatic_image_resizing", false);
pref("browser.enable_click_image_resizing", true);

// See http://dev.w3.org/html5/spec/forms.html#attr-fe-autofocus
pref("browser.autofocus", true);

// See http://whatwg.org/specs/web-apps/current-work/#ping
pref("browser.send_pings", false);
pref("browser.send_pings.max_per_link", 1);           // limit the number of pings that are sent per link click
pref("browser.send_pings.require_same_host", false);  // only send pings to the same host if this is true

pref("browser.display.use_focus_colors",    false);
pref("browser.display.focus_background_color", "#117722");
pref("browser.display.focus_text_color",     "#ffffff");
pref("browser.display.focus_ring_width",     1);
pref("browser.display.focus_ring_on_anything", false);
// focus ring border style.
// 0 = solid border, 1 = dotted border
pref("browser.display.focus_ring_style", 1);

pref("browser.helperApps.alwaysAsk.force",  false);
pref("browser.helperApps.neverAsk.saveToDisk", "");
pref("browser.helperApps.neverAsk.openFile", "");
pref("browser.helperApps.deleteTempFileOnExit", false);

// xxxbsmedberg: where should prefs for the toolkit go?
pref("browser.chrome.toolbar_tips",         true);
// 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
pref("browser.chrome.toolbar_style",        2);
// max image size for which it is placed in the tab icon for tabbrowser.
// if 0, no images are used for tab icons for image documents.
pref("browser.chrome.image_icons.max_size", 1024);

pref("browser.triple_click_selects_paragraph", true);

// Print/Preview Shrink-To-Fit won't shrink below 20% for text-ish documents.
pref("print.shrink-to-fit.scale-limit-percent", 20);

// Whether we should display simplify page checkbox on print preview UI
pref("print.use_simplify_page", false);

// Disable support for MathML
pref("mathml.disabled",    false);

// Enable scale transform for stretchy MathML operators. See bug 414277.
pref("mathml.scale_stretchy_operators.enabled", true);

pref("media.dormant-on-pause-timeout-ms", 5000);

// File-backed MediaCache size in kilobytes
pref("media.cache_size", 512000);
// When a network connection is suspended, don't resume it until the
// amount of buffered data falls below this threshold (in seconds).
pref("media.cache_resume_threshold", 30);
// Stop reading ahead when our buffered data is this many seconds ahead
// of the current playback position. This limit can stop us from using arbitrary
// amounts of network bandwidth prefetching huge videos.
pref("media.cache_readahead_limit", 60);
// If a resource is known to be smaller than this size (in kilobytes), a
// memory-backed MediaCache may be used; otherwise the (single shared
// global) file-backed MediaCache is used.
pref("media.memory_cache_max_size", 8192);
// Don't create more memory-backed MediaCaches if their combined size would go
// above the lowest limit (in kilobytes or in percent of physical memory size).
pref("media.memory_caches_combined_limit_kb", 524288);
pref("media.memory_caches_combined_limit_pc_sysmem", 5);

// Cache size hint (in bytes) for each MediaResourceIndex.
// 0 -> no cache. Will use next power of 2, clamped to 32B-128KB.
pref("media.cache.resource-index", 8192);

// We'll throttle the download if the download rate is throttle-factor times
// the estimated playback rate, AND we satisfy the cache readahead_limit
// above. The estimated playback rate is time_duration/length_in_bytes.
// This means we'll only throttle the download if there's no concern that
// throttling would cause us to stop and buffer.
pref("media.throttle-factor", 2);
// By default, we'll throttle media download once we've reached the the
// readahead_limit if the download is fast. This pref toggles the "and the
// download is fast" check off, so that we can always throttle the download
// once the readaheadd limit is reached even on a slow network.
pref("media.throttle-regardless-of-download-rate", false);

// Master HTML5 media volume scale.
pref("media.volume_scale", "1.0");

// Timeout for wakelock release
pref("media.wakelock_timeout", 2000);

// Whether we should play videos opened in a "video document", i.e. videos
// opened as top-level documents, as opposed to inside a media element.
pref("media.play-stand-alone", true);

pref("media.hardware-video-decoding.enabled", true);
pref("media.hardware-video-decoding.force-enabled", false);

//@line 371 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.mp4.enabled", true);
// Specifies whether the PDMFactory can create a test decoder that
//@line 374 "z:\build\build\src\modules\libpref\init\all.js"
// just outputs blank frames/audio instead of actually decoding. The blank
// decoder works on all platforms.
pref("media.use-blank-decoder", false);
//@line 378 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.wmf.enabled", true);
pref("media.wmf.decoder.thread-count", -1);
pref("media.wmf.low-latency.enabled", false);
pref("media.wmf.skip-blacklist", false);
pref("media.wmf.vp9.enabled", true);
pref("media.wmf.allow-unsupported-resolutions", false);
pref("media.windows-media-foundation.allow-d3d11-dxva", true);
pref("media.windows-media-foundation.use-nv12-format", false);
pref("media.wmf.disable-d3d11-for-dlls", "igd11dxva64.dll: 20.19.15.4463, 20.19.15.4454, 20.19.15.4444, 20.19.15.4416, 20.19.15.4404, 20.19.15.4390, 20.19.15.4380, 20.19.15.4377, 20.19.15.4364, 20.19.15.4360, 20.19.15.4352, 20.19.15.4331, 20.19.15.4326, 20.19.15.4300; igd10iumd32.dll: 20.19.15.4444, 20.19.15.4424, 20.19.15.4409, 20.19.15.4390, 20.19.15.4380, 20.19.15.4360, 10.18.10.4358, 20.19.15.4331, 20.19.15.4312, 20.19.15.4300, 10.18.15.4281, 10.18.15.4279, 10.18.10.4276, 10.18.15.4268, 10.18.15.4256, 10.18.10.4252, 10.18.15.4248, 10.18.14.4112, 10.18.10.3958, 10.18.10.3496, 10.18.10.3431, 10.18.10.3412, 10.18.10.3355, 9.18.10.3234, 9.18.10.3071, 9.18.10.3055, 9.18.10.3006; igd10umd32.dll: 9.17.10.4229, 9.17.10.3040, 9.17.10.2857, 8.15.10.2274, 8.15.10.2272, 8.15.10.2246, 8.15.10.1840, 8.15.10.1808; igd10umd64.dll: 9.17.10.4229, 9.17.10.2857, 10.18.10.3496; isonyvideoprocessor.dll: 4.1.2247.8090, 4.1.2153.6200; tosqep.dll: 1.2.15.526, 1.1.12.201, 1.0.11.318, 1.0.11.215, 1.0.10.1224; tosqep64.dll: 1.1.12.201, 1.0.11.215; nvwgf2um.dll: 22.21.13.8253, 22.21.13.8233, 22.21.13.8205, 22.21.13.8189, 22.21.13.8178, 22.21.13.8165, 21.21.13.7892, 21.21.13.7878, 21.21.13.7866, 21.21.13.7849, 21.21.13.7654, 21.21.13.7653, 21.21.13.7633, 21.21.13.7619, 21.21.13.7563, 21.21.13.7306, 21.21.13.7290, 21.21.13.7270, 21.21.13.7254, 21.21.13.6939, 21.21.13.6926, 21.21.13.6909, 21.21.13.4201, 21.21.13.4200, 10.18.13.6881, 10.18.13.6839, 10.18.13.6510, 10.18.13.6472, 10.18.13.6143, 10.18.13.5946, 10.18.13.5923, 10.18.13.5921, 10.18.13.5891, 10.18.13.5887, 10.18.13.5582, 10.18.13.5445, 10.18.13.5382, 10.18.13.5362, 9.18.13.4788, 9.18.13.4752, 9.18.13.4725, 9.18.13.4709, 9.18.13.4195, 9.18.13.4192, 9.18.13.4144, 9.18.13.4052, 9.18.13.3788, 9.18.13.3523, 9.18.13.3235, 9.18.13.3165, 9.18.13.2723, 9.18.13.2702, 9.18.13.1422, 9.18.13.1407, 9.18.13.1106, 9.18.13.546; atidxx32.dll: 21.19.151.3, 21.19.142.257, 21.19.137.514, 21.19.137.1, 21.19.134.1, 21.19.128.7, 21.19.128.4, 20.19.0.32837, 20.19.0.32832, 8.17.10.682, 8.17.10.671, 8.17.10.661, 8.17.10.648, 8.17.10.644, 8.17.10.625, 8.17.10.605, 8.17.10.581, 8.17.10.569, 8.17.10.560, 8.17.10.545, 8.17.10.539, 8.17.10.531, 8.17.10.525, 8.17.10.520, 8.17.10.519, 8.17.10.514, 8.17.10.511, 8.17.10.494, 8.17.10.489, 8.17.10.483, 8.17.10.453, 8.17.10.451, 8.17.10.441, 8.17.10.436, 8.17.10.432, 8.17.10.425, 8.17.10.418, 8.17.10.414, 8.17.10.401, 8.17.10.395, 8.17.10.385, 8.17.10.378, 8.17.10.362, 8.17.10.355, 8.17.10.342, 8.17.10.331, 8.17.10.318, 8.17.10.310, 8.17.10.286, 8.17.10.269, 8.17.10.261, 8.17.10.247, 8.17.10.240, 8.15.10.212; atidxx64.dll: 21.19.151.3, 21.19.142.257, 21.19.137.514, 21.19.137.1, 21.19.134.1, 21.19.128.7, 21.19.128.4, 20.19.0.32832, 8.17.10.682, 8.17.10.661, 8.17.10.644, 8.17.10.625; nvumdshim.dll: 10.18.13.6822");
pref("media.wmf.disable-d3d9-for-dlls", "igdumd64.dll: 8.15.10.2189, 8.15.10.2119, 8.15.10.2104, 8.15.10.2102, 8.771.1.0; atiumd64.dll: 7.14.10.833, 7.14.10.867, 7.14.10.885, 7.14.10.903, 7.14.10.911, 8.14.10.768, 9.14.10.1001, 9.14.10.1017, 9.14.10.1080, 9.14.10.1128, 9.14.10.1162, 9.14.10.1171, 9.14.10.1183, 9.14.10.1197, 9.14.10.945, 9.14.10.972, 9.14.10.984, 9.14.10.996");
//@line 398 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.ffvpx.enabled", true);
//@line 401 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.ffmpeg.low-latency.enabled", false);
//@line 403 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.gmp.decoder.enabled", false);
pref("media.gmp.decoder.aac", 0);
pref("media.gmp.decoder.h264", 0);
//@line 407 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.raw.enabled", true);
//@line 409 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.ogg.enabled", true);
pref("media.opus.enabled", true);
pref("media.wave.enabled", true);
pref("media.webm.enabled", true);

pref("media.eme.chromium-api.enabled", true);
pref("media.eme.chromium-api.video-shmems", 6);

//@line 424 "z:\build\build\src\modules\libpref\init\all.js"

// GMP storage version number. At startup we check the version against
// media.gmp.storage.version.observed, and if the versions don't match,
// we clear storage and set media.gmp.storage.version.observed=expected.
// This provides a mechanism to clear GMP storage when non-compatible
// changes are made.
pref("media.gmp.storage.version.expected", 1);

// Filter what triggers user notifications.
// See DecoderDoctorDocumentWatcher::ReportAnalysis for details.
//@line 437 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMF,MediaCannotInitializePulseAudio,MediaCannotPlayNoDecoders,MediaUnsupportedLibavcodec");
//@line 439 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.decoder-doctor.decode-errors-allowed", "");
pref("media.decoder-doctor.decode-warnings-allowed", "");
// Whether we report partial failures.
pref("media.decoder-doctor.verbose", false);
// Whether DD should consider WMF-disabled a WMF failure, useful for testing.
pref("media.decoder-doctor.wmf-disabled-is-failure", false);
// URL to report decode issues
pref("media.decoder-doctor.new-issue-endpoint", "https://webcompat.com/issues/new");

// Whether to suspend decoding of videos in background tabs.
//@line 450 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.suspend-bkgnd-video.enabled", false);
//@line 454 "z:\build\build\src\modules\libpref\init\all.js"
// Delay, in ms, from time window goes to background to suspending
// video decoders. Defaults to 10 seconds.
pref("media.suspend-bkgnd-video.delay-ms", 10000);
// Resume video decoding when the cursor is hovering on a background tab to
// reduce the resume latency and improve the user experience.
pref("media.resume-bkgnd-video-on-tabhover", true);;

//@line 462 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.navigator.enabled", true);
pref("media.navigator.video.enabled", true);
pref("media.navigator.load_adapt", true);
pref("media.navigator.load_adapt.encoder_only", true);
pref("media.navigator.load_adapt.measure_interval",1000);
pref("media.navigator.load_adapt.avg_seconds",3);
pref("media.navigator.load_adapt.high_load","0.90");
pref("media.navigator.load_adapt.low_load","0.40");
pref("media.navigator.video.default_fps",30);
pref("media.navigator.video.default_minfps",10);
pref("media.navigator.video.use_remb", true);
pref("media.navigator.video.use_tmmbr", false);
pref("media.navigator.audio.use_fec", true);
pref("media.navigator.video.red_ulpfec_enabled", false);

pref("media.peerconnection.dtmf.enabled", true);

pref("media.webrtc.debug.trace_mask", 0);
pref("media.webrtc.debug.multi_log", false);
pref("media.webrtc.debug.aec_log_dir", "");
pref("media.webrtc.debug.log_file", "");
pref("media.webrtc.debug.aec_dump_max_size", 4194304); // 4MB

pref("media.navigator.video.default_width",0);  // adaptive default
pref("media.navigator.video.default_height",0); // adaptive default
pref("media.peerconnection.enabled", true);
pref("media.peerconnection.video.enabled", true);
pref("media.navigator.video.max_fs", 12288); // Enough for 2048x1536
pref("media.navigator.video.max_fr", 60);
pref("media.navigator.video.h264.level", 31); // 0x42E01f - level 3.1
pref("media.navigator.video.h264.max_br", 0);
pref("media.navigator.video.h264.max_mbps", 0);
pref("media.peerconnection.video.h264_enabled", false);
pref("media.peerconnection.video.vp9_enabled", true);
pref("media.getusermedia.aec", 1);
pref("media.getusermedia.browser.enabled", false);
pref("media.getusermedia.channels", 0);
// Desktop is typically VGA capture or more; and qm_select will not drop resolution
// below 1/2 in each dimension (or so), so QVGA (320x200) is the lowest here usually.
pref("media.peerconnection.video.min_bitrate", 0);
pref("media.peerconnection.video.start_bitrate", 0);
pref("media.peerconnection.video.max_bitrate", 0);
pref("media.peerconnection.video.min_bitrate_estimate", 0);
pref("media.peerconnection.video.denoising", false);
pref("media.navigator.audio.fake_frequency", 1000);
pref("media.navigator.permission.disabled", false);
pref("media.peerconnection.simulcast", true);
pref("media.peerconnection.default_iceservers", "[]");
pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments.
pref("media.peerconnection.ice.tcp", true);
pref("media.peerconnection.ice.tcp_so_sock_count", 0); // Disable SO gathering
pref("media.peerconnection.ice.link_local", false); // Set only for testing IPV6 in networks that don't assign IPV6 addresses
pref("media.peerconnection.ice.force_interface", ""); // Limit to only a single interface
pref("media.peerconnection.ice.relay_only", false); // Limit candidates to TURN
pref("media.peerconnection.use_document_iceservers", true);
pref("media.peerconnection.identity.enabled", true);
pref("media.peerconnection.identity.timeout", 10000);
pref("media.peerconnection.ice.stun_client_maximum_transmits", 7);
pref("media.peerconnection.ice.trickle_grace_period", 5000);
pref("media.peerconnection.ice.no_host", false);
pref("media.peerconnection.ice.default_address_only", false);
pref("media.peerconnection.ice.proxy_only", false);

// These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h
// kXxxUnchanged = 0, kXxxDefault = 1, and higher values are specific to each
// setting (for Xxx = Ec, Agc, or Ns).  Defaults are all set to kXxxDefault here.
pref("media.peerconnection.turn.disable", false);
//@line 533 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.getusermedia.aec_enabled", true);
pref("media.getusermedia.noise_enabled", true);
//@line 536 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.getusermedia.aec_extended_filter", true);
pref("media.getusermedia.aec_delay_agnostic", true);
pref("media.getusermedia.noise", 1);
pref("media.getusermedia.agc_enabled", false);
pref("media.getusermedia.agc", 1);
// capture_delay: Adjustments for OS-specific input delay (lower bound)
// playout_delay: Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound)
// full_duplex: enable cubeb full-duplex capture/playback
//@line 549 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.peerconnection.capture_delay", 50);
pref("media.getusermedia.playout_delay", 40);
pref("media.navigator.audio.full_duplex", true);
//@line 569 "z:\build\build\src\modules\libpref\init\all.js"
// Use MediaDataDecoder API for WebRTC, this includes hardware acceleration for
// decoding.
pref("media.navigator.mediadatadecoder_enabled", false);
//@line 573 "z:\build\build\src\modules\libpref\init\all.js"

pref("dom.webaudio.enabled", true);

//@line 577 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.getusermedia.screensharing.enabled", true);
//@line 579 "z:\build\build\src\modules\libpref\init\all.js"

//@line 581 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.getusermedia.screensharing.allowed_domains", "webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com,air.mozilla.org,*.circuit.com,*.yourcircuit.com,circuit.siemens.com,yourcircuit.siemens.com,circuitsandbox.net,*.unify.com,tandi.circuitsandbox.net,*.ericsson.net,*.cct.ericsson.net,*.opentok.com,*.conf.meetecho.com,meet.jit.si,*.meet.jit.si,web.stage.speakeasyapp.net,web.speakeasyapp.net,*.hipchat.me,*.beta-wspbx.com,*.wspbx.com,*.unifiedcloudit.com,*.smartboxuc.com,*.smartbox-uc.com,*.panterranetworks.com,pexipdemo.com,*.pexipdemo.com,pex.me,*.pex.me,*.rd.pexip.com,1click.io,*.1click.io,*.fuze.com,*.fuzemeeting.com,*.thinkingphones.com,gotomeeting.com,*.gotomeeting.com,gotowebinar.com,*.gotowebinar.com,gototraining.com,*.gototraining.com,citrix.com,*.citrix.com,expertcity.com,*.expertcity.com,citrixonline.com,*.citrixonline.com,g2m.me,*.g2m.me,gotomeet.me,*.gotomeet.me,gotomeet.at,*.gotomeet.at,miriadaxdes.miriadax.net,certificacion.miriadax.net,miriadax.net,*.wire.com,sylaps.com,*.sylaps.com,bluejeans.com,*.bluejeans.com,*.a.bluejeans.com,*.bbcollab.com");
//@line 586 "z:\build\build\src\modules\libpref\init\all.js"

pref("media.getusermedia.audiocapture.enabled", false);

// TextTrack WebVTT Region extension support.
pref("media.webvtt.regions.enabled", false);

// WebVTT pseudo element and class support.
pref("media.webvtt.pseudo.enabled", true);

// AudioTrack and VideoTrack support
pref("media.track.enabled", false);

// Whether to enable MediaSource support.
pref("media.mediasource.enabled", true);

pref("media.mediasource.mp4.enabled", true);

//@line 604 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.mediasource.webm.enabled", false);
//@line 608 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.mediasource.webm.audio.enabled", true);

// Use new MediaFormatReader architecture for plain ogg.
pref("media.flac.enabled", true);
pref("media.ogg.flac.enabled", true);

pref("media.benchmark.vp9.threshold", 150);
pref("media.benchmark.frames", 300);
pref("media.benchmark.timeout", 1000);

//@line 619 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.webspeech.recognition.enable", false);
pref("media.webspeech.synth.enabled", false);
//@line 623 "z:\build\build\src\modules\libpref\init\all.js"
pref("media.encoder.webm.enabled", true);
//@line 625 "z:\build\build\src\modules\libpref\init\all.js"

// Whether to autostart a media element with an |autoplay| attribute
pref("media.autoplay.enabled", true);

// The default number of decoded video frames that are enqueued in
// MediaDecoderReader's mVideoQueue.
pref("media.video-queue.default-size", 10);

// The maximum number of queued frames to send to the compositor.
// By default, send all of them.
pref("media.video-queue.send-to-compositor-size", 9999);

// Whether to disable the video stats to prevent fingerprinting
pref("media.video_stats.enabled", true);

// Whether to check the decoder supports recycling.
pref("media.decoder.recycle.enabled", false);

//Weather MFR should try to skip to next key frame or not.
pref("media.decoder.skip-to-next-key-frame.enabled", true);

// Log level for cubeb, the audio input/output system. Valid values are
// "verbose", "normal" and "" (log disabled).
pref("media.cubeb.logging_level", "");

// Set to true to force demux/decode warnings to be treated as errors.
pref("media.playback.warnings-as-errors", false);

// Weather we allow AMD switchable graphics
pref("layers.amd-switchable-gfx.enabled", true);

// Whether to use async panning and zooming
pref("layers.async-pan-zoom.enabled", true);

// Whether to enable event region building during painting
pref("layout.event-regions.enabled", false);

// Whether to enable arbitrary layer geometry for OpenGL compositor
pref("layers.geometry.opengl.enabled", true);

// Whether to enable arbitrary layer geometry for Basic compositor
pref("layers.geometry.basic.enabled", true);

// Whether to enable arbitrary layer geometry for DirectX compositor
pref("layers.geometry.d3d11.enabled", true);

// APZ preferences. For documentation/details on what these prefs do, check
// gfx/layers/apz/src/AsyncPanZoomController.cpp.
pref("apz.allow_checkerboarding", true);
pref("apz.allow_immediate_handoff", true);
pref("apz.allow_zooming", false);
pref("apz.autoscroll.enabled", false);

// Whether to lock touch scrolling to one axis at a time
// 0 = FREE (No locking at all)
// 1 = STANDARD (Once locked, remain locked until scrolling ends)
// 2 = STICKY (Allow lock to be broken, with hysteresis)
pref("apz.axis_lock.mode", 0);
pref("apz.axis_lock.lock_angle", "0.5235987");        // PI / 6 (30 degrees)
pref("apz.axis_lock.breakout_threshold", "0.03125");  // 1/32 inches
pref("apz.axis_lock.breakout_angle", "0.3926991");    // PI / 8 (22.5 degrees)
pref("apz.axis_lock.direct_pan_angle", "1.047197");   // PI / 3 (60 degrees)
pref("apz.content_response_timeout", 400);
pref("apz.drag.enabled", true);
pref("apz.drag.initial.enabled", true);
pref("apz.danger_zone_x", 50);
pref("apz.danger_zone_y", 100);
pref("apz.disable_for_scroll_linked_effects", false);
pref("apz.displayport_expiry_ms", 15000);
pref("apz.enlarge_displayport_when_clipped", false);
pref("apz.fling_accel_base_mult", "1.0");
pref("apz.fling_accel_interval_ms", 500);
pref("apz.fling_accel_min_velocity", "1.5");
pref("apz.fling_accel_supplemental_mult", "1.0");
pref("apz.fling_curve_function_x1", "0.0");
pref("apz.fling_curve_function_y1", "0.0");
pref("apz.fling_curve_function_x2", "1.0");
pref("apz.fling_curve_function_y2", "1.0");
pref("apz.fling_curve_threshold_inches_per_ms", "-1.0");
pref("apz.fling_friction", "0.002");
pref("apz.fling_min_velocity_threshold", "0.5");
pref("apz.fling_stop_on_tap_threshold", "0.05");
pref("apz.fling_stopped_threshold", "0.01");
//@line 711 "z:\build\build\src\modules\libpref\init\all.js"
pref("apz.frame_delay.enabled", false);
//@line 716 "z:\build\build\src\modules\libpref\init\all.js"
pref("apz.keyboard.enabled", false);
//@line 718 "z:\build\build\src\modules\libpref\init\all.js"
pref("apz.max_velocity_inches_per_ms", "-1.0");
pref("apz.max_velocity_queue_size", 5);
pref("apz.min_skate_speed", "1.0");
pref("apz.minimap.enabled", false);
pref("apz.minimap.visibility.enabled", false);
pref("apz.one_touch_pinch.enabled", true);
pref("apz.overscroll.enabled", false);
pref("apz.overscroll.min_pan_distance_ratio", "1.0");
pref("apz.overscroll.spring_friction", "0.015");
pref("apz.overscroll.spring_stiffness", "0.0018");
pref("apz.overscroll.stop_distance_threshold", "5.0");
pref("apz.overscroll.stop_velocity_threshold", "0.01");
pref("apz.overscroll.stretch_factor", "0.35");
pref("apz.paint_skipping.enabled", true);
// Fetch displayport updates early from the message queue
pref("apz.peek_messages.enabled", true);
pref("apz.popups.enabled", false);

// Whether to print the APZC tree for debugging
pref("apz.printtree", false);

//@line 742 "z:\build\build\src\modules\libpref\init\all.js"
pref("apz.record_checkerboarding", false);
//@line 744 "z:\build\build\src\modules\libpref\init\all.js"
pref("apz.second_tap_tolerance", "0.5");
pref("apz.test.logging_enabled", false);
pref("apz.touch_start_tolerance", "0.1");
pref("apz.touch_move_tolerance", "0.1");
pref("apz.velocity_bias", "0.0");
pref("apz.velocity_relevance_time_ms", 150);
pref("apz.x_skate_highmem_adjust", "0.0");
pref("apz.y_skate_highmem_adjust", "0.0");
pref("apz.x_skate_size_multiplier", "1.25");
pref("apz.y_skate_size_multiplier", "3.5");
pref("apz.x_stationary_size_multiplier", "1.5");
pref("apz.y_stationary_size_multiplier", "3.5");
pref("apz.zoom_animation_duration_ms", 250);
pref("apz.scale_repaint_delay_ms", 500);

//@line 766 "z:\build\build\src\modules\libpref\init\all.js"

//@line 774 "z:\build\build\src\modules\libpref\init\all.js"

//@line 776 "z:\build\build\src\modules\libpref\init\all.js"
// Use containerless scrolling for now on desktop.
pref("layout.scroll.root-frame-containers", false);
//@line 779 "z:\build\build\src\modules\libpref\init\all.js"

pref("layout.scrollbars.always-layerize-track", false);

// Whether to enable LayerScope tool and default listening port
pref("gfx.layerscope.enabled", false);
pref("gfx.layerscope.port", 23456);

// Log severe performance warnings to the error console and profiles.
// This should be use to quickly find which slow paths are used by test cases.
pref("gfx.perf-warnings.enabled", false);

// 0 = Off, 1 = Full, 2 = Tagged Images Only.
// See eCMSMode in gfx/thebes/gfxPlatform.h
pref("gfx.color_management.mode", 2);
pref("gfx.color_management.display_profile", "");
pref("gfx.color_management.rendering_intent", 0);
pref("gfx.color_management.enablev4", false);

pref("gfx.downloadable_fonts.enabled", true);
pref("gfx.downloadable_fonts.fallback_delay", 3000);
pref("gfx.downloadable_fonts.fallback_delay_short", 100);

// disable downloadable font cache so that behavior is consistently
// the uncached load behavior across pages (useful for testing reflow problems)
pref("gfx.downloadable_fonts.disable_cache", false);

pref("gfx.downloadable_fonts.woff2.enabled", true);

// Whether OTS validation should be applied to OpenType Layout (OTL) tables
//@line 809 "z:\build\build\src\modules\libpref\init\all.js"
pref("gfx.downloadable_fonts.otl_validation", false);
//@line 813 "z:\build\build\src\modules\libpref\init\all.js"

// Whether to preserve OpenType variation tables in fonts (bypassing OTS)
pref("gfx.downloadable_fonts.keep_variation_tables", false);

//@line 821 "z:\build\build\src\modules\libpref\init\all.js"

// Do we fire a notification about missing fonts, so the front-end can decide
// whether to try and do something about it (e.g. download additional fonts)?
pref("gfx.missing_fonts.notify", false);

// prefs controlling the font (name/cmap) loader that runs shortly after startup
pref("gfx.font_loader.families_per_slice", 3); // read in info 3 families at a time
//@line 829 "z:\build\build\src\modules\libpref\init\all.js"
pref("gfx.font_loader.delay", 120000);         // 2 minutes after startup
pref("gfx.font_loader.interval", 1000);        // every 1 second until complete
//@line 835 "z:\build\build\src\modules\libpref\init\all.js"

// whether to always search all font cmaps during system font fallback
pref("gfx.font_rendering.fallback.always_use_cmaps", false);

// cache shaped word results
pref("gfx.font_rendering.wordcache.charlimit", 32);

// cache shaped word results
pref("gfx.font_rendering.wordcache.maxentries", 10000);

pref("gfx.font_rendering.graphite.enabled", true);

//@line 848 "z:\build\build\src\modules\libpref\init\all.js"
pref("gfx.font_rendering.directwrite.force-enabled", false);
pref("gfx.font_rendering.directwrite.use_gdi_table_loading", true);
//@line 851 "z:\build\build\src\modules\libpref\init\all.js"

pref("gfx.font_rendering.opentype_svg.enabled", true);

//@line 855 "z:\build\build\src\modules\libpref\init\all.js"
// comma separated list of backends to use in order of preference
// e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo");
pref("gfx.canvas.azure.backends", "direct2d1.1,skia,cairo");
pref("gfx.content.azure.backends", "direct2d1.1,skia,cairo");
//@line 870 "z:\build\build\src\modules\libpref\init\all.js"

pref("gfx.canvas.skiagl.dynamic-cache", true);

pref("gfx.text.disable-aa", false);

pref("gfx.work-around-driver-bugs", true);

pref("gfx.draw-color-bars", false);

pref("gfx.logging.painted-pixel-count.enabled", false);
pref("gfx.logging.texture-usage.enabled", false);
pref("gfx.logging.peak-texture-usage.enabled", false);

pref("gfx.ycbcr.accurate-conversion", false);

//@line 888 "z:\build\build\src\modules\libpref\init\all.js"
pref("gfx.webrender.enabled", false);
//@line 891 "z:\build\build\src\modules\libpref\init\all.js"
pref("gfx.webrender.force-angle", true);
//@line 893 "z:\build\build\src\modules\libpref\init\all.js"

pref("gfx.webrender.highlight-painted-layers", false);
pref("gfx.webrender.layers-free", false);
pref("gfx.webrender.profiler.enabled", false);

// Whether webrender should be used as much as possible.
pref("gfx.webrendest.enabled", false);

pref("accessibility.browsewithcaret", false);
pref("accessibility.warn_on_browsewithcaret", true);

pref("accessibility.browsewithcaret_shortcut.enabled", true);

//@line 907 "z:\build\build\src\modules\libpref\init\all.js"
// Tab focus model bit field:
// 1 focuses text controls, 2 focuses other form elements, 4 adds links.
// Most users will want 1, 3, or 7.
// On OS X, we use Full Keyboard Access system preference,
// unless accessibility.tabfocus is set by the user.
pref("accessibility.tabfocus", 7);
pref("accessibility.tabfocus_applies_to_xul", false);
//@line 918 "z:\build\build\src\modules\libpref\init\all.js"

// We follow the "Click in the scrollbar to:" system preference on OS X and
// "gtk-primary-button-warps-slider" property with GTK (since 2.24 / 3.6),
// unless this preference is explicitly set.
//@line 923 "z:\build\build\src\modules\libpref\init\all.js"
pref("ui.scrollToClick", 0);
//@line 925 "z:\build\build\src\modules\libpref\init\all.js"

// provide ability to turn on support for canvas focus rings
pref("canvas.focusring.enabled", true);
pref("canvas.customfocusring.enabled", false);
pref("canvas.hitregions.enabled", false);
pref("canvas.filters.enabled", true);
// Add support for canvas path objects
pref("canvas.path.enabled", true);
pref("canvas.capturestream.enabled", true);

// Disable the ImageBitmap-extensions for now.
pref("canvas.imagebitmap_extensions.enabled", false);

// We want the ability to forcibly disable platform a11y, because
// some non-a11y-related components attempt to bring it up.  See bug
// 538530 for details about Windows; we have a pref here that allows it
// to be disabled for performance and testing resons.
// See bug 761589 for the crossplatform aspect.
//
// This pref is checked only once, and the browser needs a restart to
// pick up any changes.
//
// Values are -1 always on. 1 always off, 0 is auto as some platform perform
// further checks.
pref("accessibility.force_disabled", 0);

pref("accessibility.AOM.enabled", false);

//@line 954 "z:\build\build\src\modules\libpref\init\all.js"
// Some accessibility tools poke at windows in the plugin process during setup
// which can cause hangs.  To hack around this set accessibility.delay_plugins
// to true, you can also try increasing accessibility.delay_plugin_time if your
// machine is slow and you still experience hangs.
// See bug 781791.
pref("accessibility.delay_plugins", false);
pref("accessibility.delay_plugin_time", 10000);

// The COM handler used for Windows e10s performance and live regions
pref("accessibility.handler.enabled", true);
//@line 965 "z:\build\build\src\modules\libpref\init\all.js"

pref("focusmanager.testmode", false);

pref("accessibility.usetexttospeech", "");
pref("accessibility.usebrailledisplay", "");
pref("accessibility.accesskeycausesactivation", true);
pref("accessibility.mouse_focuses_formcontrol", false);

// Type Ahead Find
pref("accessibility.typeaheadfind", true);
pref("accessibility.typeaheadfind.autostart", true);
// casesensitive: controls the find bar's case-sensitivity
//     0 - "never"  (case-insensitive)
//     1 - "always" (case-sensitive)
// other - "auto"   (case-sensitive for mixed-case input, insensitive otherwise)
pref("accessibility.typeaheadfind.casesensitive", 0);
pref("accessibility.typeaheadfind.linksonly", true);
pref("accessibility.typeaheadfind.startlinksonly", false);
pref("accessibility.typeaheadfind.timeout", 4000);
pref("accessibility.typeaheadfind.enabletimeout", true);
pref("accessibility.typeaheadfind.soundURL", "beep");
pref("accessibility.typeaheadfind.enablesound", true);
//@line 990 "z:\build\build\src\modules\libpref\init\all.js"
pref("accessibility.typeaheadfind.prefillwithselection", true);
//@line 992 "z:\build\build\src\modules\libpref\init\all.js"
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.modalHighlight", false);
pref("findbar.entireword", false);
pref("findbar.iteratorTimeout", 100);

// use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
pref("gfx.use_text_smoothing_setting", false);

// Number of characters to consider emphasizing for rich autocomplete results
pref("toolkit.autocomplete.richBoundaryCutoff", 200);

// Variable controlling logging for osfile.
pref("toolkit.osfile.log", false);

pref("toolkit.cosmeticAnimations.enabled", true);

pref("toolkit.scrollbox.smoothScroll", true);
pref("toolkit.scrollbox.scrollIncrement", 20);
pref("toolkit.scrollbox.verticalScrollDistance", 3);
pref("toolkit.scrollbox.horizontalScrollDistance", 5);
pref("toolkit.scrollbox.clickToScroll.scrollDelay", 150);

// Telemetry settings.
// Server to submit telemetry pings to.
pref("toolkit.telemetry.server", "https://incoming.telemetry.mozilla.org");
// Telemetry server owner. Please change if you set toolkit.telemetry.server to a different server
pref("toolkit.telemetry.server_owner", "Mozilla");
// Information page about telemetry (temporary ; will be about:telemetry in the end)
pref("toolkit.telemetry.infoURL", "https://www.mozilla.org/legal/privacy/firefox.html#telemetry");
// Determines whether full SQL strings are returned when they might contain sensitive info
// i.e. dynamically constructed SQL strings or SQL executed by addons against addon DBs
pref("toolkit.telemetry.debugSlowSql", false);
// Whether to use the unified telemetry behavior, requires a restart.
pref("toolkit.telemetry.unified", true);
// AsyncShutdown delay before crashing in case of shutdown freeze
pref("toolkit.asyncshutdown.crash_timeout", 60000);
// Extra logging for AsyncShutdown barriers and phases
pref("toolkit.asyncshutdown.log", false);

// Enable deprecation warnings.
pref("devtools.errorconsole.deprecation_warnings", true);

//@line 1039 "z:\build\build\src\modules\libpref\init\all.js"
sticky_pref("devtools.debugger.prompt-connection", true);
//@line 1041 "z:\build\build\src\modules\libpref\init\all.js"

//@line 1043 "z:\build\build\src\modules\libpref\init\all.js"
// Disable debugging chrome
sticky_pref("devtools.chrome.enabled", false);
// Disable remote debugging connections
sticky_pref("devtools.debugger.remote-enabled", false);
//@line 1052 "z:\build\build\src\modules\libpref\init\all.js"


// Disable remote debugging protocol logging
pref("devtools.debugger.log", false);
pref("devtools.debugger.log.verbose", false);

pref("devtools.debugger.remote-port", 6000);
pref("devtools.debugger.remote-websocket", false);
// Force debugger server binding on the loopback interface
pref("devtools.debugger.force-local", true);
// Block tools from seeing / interacting with certified apps
pref("devtools.debugger.forbid-certified-apps", true);

// DevTools default color unit
pref("devtools.defaultColorUnit", "authored");

// Used for devtools debugging
pref("devtools.dump.emit", false);

// Controls whether EventEmitter module throws dump message on each emit
pref("toolkit.dump.emit", false);

// Disable device discovery logging
pref("devtools.discovery.log", false);
// Whether to scan for DevTools devices via WiFi
pref("devtools.remote.wifi.scan", true);
// Whether UI options for controlling device visibility over WiFi are shown
// N.B.: This does not set whether the device can be discovered via WiFi, only
// whether the UI control to make such a choice is shown to the user
pref("devtools.remote.wifi.visible", true);
// Client must complete TLS handshake within this window (ms)
pref("devtools.remote.tls-handshake-timeout", 10000);

// URL of the remote JSON catalog used for device simulation
pref("devtools.devices.url", "https://code.cdn.mozilla.net/devices/devices.json");

// Display the introductory text
pref("devtools.gcli.hideIntro", false);

// How eager are we to show help: never=1, sometimes=2, always=3
pref("devtools.gcli.eagerHelper", 2);

// Alias to the script URLs for inject command.
pref("devtools.gcli.jquerySrc", "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js");
pref("devtools.gcli.lodashSrc", "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js");
pref("devtools.gcli.underscoreSrc", "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js");

// Set imgur upload client ID
pref("devtools.gcli.imgurClientID", '0df414e888d7240');
// Imgur's upload URL
pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");

// GCLI commands directory
pref("devtools.commands.dir", "");

// Allows setting the performance marks for which telemetry metrics will be recorded.
pref("devtools.telemetry.supported_performance_marks", "contentInteractive,navigationInteractive,navigationLoaded,visuallyLoaded,fullyLoaded,mediaEnumerated,scanEnd");

// Deprecation warnings after DevTools file migration.
pref("devtools.migration.warnings", true);

// view source
pref("view_source.syntax_highlight", true);
pref("view_source.wrap_long_lines", false);
pref("view_source.editor.external", false);
pref("view_source.editor.path", "");
// allows to add further arguments to the editor; use the %LINE% placeholder
// for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
pref("view_source.editor.args", "");

// When true this will word-wrap plain text documents.
pref("plain_text.wrap_long_lines", false);

// whether or not to draw images while dragging
pref("nglayout.enable_drag_images", true);

// enable/disable paint flashing --- useful for debugging
// the first one applies to everything, the second one only to chrome
pref("nglayout.debug.paint_flashing", false);
pref("nglayout.debug.paint_flashing_chrome", false);

// enable/disable widget update area flashing --- only supported with
// BasicLayers (other layer managers always update the entire widget area)
pref("nglayout.debug.widget_update_flashing", false);

// Enable/disable display list invalidation logging --- useful for debugging.
pref("nglayout.debug.invalidation", false);

// Whether frame visibility tracking is enabled globally.
pref("layout.framevisibility.enabled", true);

pref("layout.framevisibility.numscrollportwidths", 0);
pref("layout.framevisibility.numscrollportheights", 1);

// scrollbar snapping region
// 0 - off
// 1 and higher - slider thickness multiple
pref("slider.snapMultiplier", 0);

// option to choose plug-in finder
pref("application.use_ns_plugin_finder", false);

// URI fixup prefs
pref("browser.fixup.alternate.enabled", true);
pref("browser.fixup.alternate.prefix", "www.");
pref("browser.fixup.alternate.suffix", ".com");
pref("browser.fixup.dns_first_for_single_words", false);
pref("browser.fixup.hide_user_pass", true);

// Location Bar AutoComplete
pref("browser.urlbar.autocomplete.enabled", true);
//@line 1166 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.urlbar.usepreloadedtopurls.enabled", false);
//@line 1168 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.urlbar.usepreloadedtopurls.expire_days", 14);

// Print header customization
// Use the following codes:
// &T - Title
// &U - Document URL
// &D - Date/Time
// &P - Page Number
// &PT - Page Number "of" Page total
// Set each header to a string containing zero or one of these codes
// and the code will be replaced in that string by the corresponding data
pref("print.print_headerleft", "&T");
pref("print.print_headercenter", "");
pref("print.print_headerright", "&U");
pref("print.print_footerleft", "&PT");
pref("print.print_footercenter", "");
pref("print.print_footerright", "&D");
pref("print.show_print_progress", true);

// xxxbsmedberg: more toolkit prefs

// When this is set to false each window has its own PrintSettings
// and a change in one window does not affect the others
pref("print.use_global_printsettings", true);

// Save the Printings after each print job
pref("print.save_print_settings", true);

// Cache old Presentation when going into Print Preview
pref("print.always_cache_old_pres", false);

// Enables you to specify the amount of the paper that is to be treated
// as unwriteable.  The print_edge_XXX and print_margin_XXX preferences
// are treated as offsets that are added to this pref.
// Default is "-1", which means "use the system default".  (If there is
// no system default, then the -1 is treated as if it were 0.)
// This is used by both Printing and Print Preview.
// Units are in 1/100ths of an inch.
pref("print.print_unwriteable_margin_top",    -1);
pref("print.print_unwriteable_margin_left",   -1);
pref("print.print_unwriteable_margin_right",  -1);
pref("print.print_unwriteable_margin_bottom", -1);

// Enables you to specify the gap from the edge of the paper's
// unwriteable area to the margin.
// This is used by both Printing and Print Preview
// Units are in 1/100ths of an inch.
pref("print.print_edge_top", 0);
pref("print.print_edge_left", 0);
pref("print.print_edge_right", 0);
pref("print.print_edge_bottom", 0);

// Print via the parent process. This is only used when e10s is enabled.
//@line 1222 "z:\build\build\src\modules\libpref\init\all.js"
pref("print.print_via_parent", true);
//@line 1226 "z:\build\build\src\modules\libpref\init\all.js"

// Pref used by the spellchecker extension to control the
// maximum number of misspelled words that will be underlined
// in a document.
pref("extensions.spellcheck.inline.max-misspellings", 500);

// Prefs used by libeditor. Prefs specific to seamonkey composer
// belong in comm-central/editor/ui/composer.js

pref("editor.use_custom_colors", false);
pref("editor.singleLine.pasteNewlines",      2);
pref("editor.use_css",                       false);
pref("editor.css.default_length_unit",       "px");
pref("editor.resizing.preserve_ratio",       true);
pref("editor.positioning.offset",            0);
//@line 1244 "z:\build\build\src\modules\libpref\init\all.js"
pref("editor.use_div_for_default_newlines",  false);
//@line 1246 "z:\build\build\src\modules\libpref\init\all.js"

// Scripts & Windows prefs
pref("dom.disable_beforeunload",            false);
pref("dom.disable_window_flip",             false);
pref("dom.disable_window_move_resize",      false);
pref("dom.disable_window_status_change",    false);

pref("dom.disable_window_open_feature.titlebar",    false);
pref("dom.disable_window_open_feature.close",       false);
pref("dom.disable_window_open_feature.toolbar",     false);
pref("dom.disable_window_open_feature.location",    false);
pref("dom.disable_window_open_feature.personalbar", false);
pref("dom.disable_window_open_feature.menubar",     false);
pref("dom.disable_window_open_feature.resizable",   true);
pref("dom.disable_window_open_feature.minimizable", false);
pref("dom.disable_window_open_feature.status",      true);
pref("dom.disable_window_showModalDialog",          true);

pref("dom.allow_scripts_to_close_windows",          false);

pref("dom.require_user_interaction_for_beforeunload", true);

pref("dom.disable_open_during_load",                false);
pref("dom.popup_maximum",                           20);
pref("dom.popup_allowed_events", "change click dblclick mouseup pointerup notificationclick reset submit touchend");
pref("dom.disable_open_click_delay", 1000);

pref("dom.storage.enabled", true);
pref("dom.storage.default_quota",      5120);
pref("dom.storage.testing", false);

pref("dom.send_after_paint_to_content", false);

// Timeout clamp in ms for timeouts we clamp
pref("dom.min_timeout_value", 4);
// And for background windows
pref("dom.min_background_timeout_value", 1000);
// Timeout clamp in ms for tracking timeouts we clamp
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
//@line 1288 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.min_tracking_timeout_value", 4);
//@line 1290 "z:\build\build\src\modules\libpref\init\all.js"
// And for background windows
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
pref("dom.min_tracking_background_timeout_value", 10000);
// Delay in ms from document load until we start throttling background timeouts.
pref("dom.timeout.throttling_delay", 30000);

// Time (in ms) that it takes to regenerate 1ms.
pref("dom.timeout.background_budget_regeneration_rate", 100);
// Maximum value (in ms) for the background budget. Only valid for
// values greater than 0.
pref("dom.timeout.background_throttling_max_budget", 50);
// Time (in ms) that it takes to regenerate 1ms.
pref("dom.timeout.foreground_budget_regeneration_rate", 1);
// Maximum value (in ms) for the background budget. Only valid for
// values greater than 0.
pref("dom.timeout.foreground_throttling_max_budget", -1);
// The maximum amount a timeout can be delayed by budget throttling
pref("dom.timeout.budget_throttling_max_delay", 15000);
// Turn off budget throttling by default
pref("dom.timeout.enable_budget_timer_throttling", false);

// Don't use new input types
pref("dom.experimental_forms", false);

// Enable <input type=number>:
pref("dom.forms.number", true);

// Enable <input type=color> by default. It will be turned off for remaining
// platforms which don't have a color picker implemented yet.
pref("dom.forms.color", true);

// Support for input type=date and type=time. Enabled by default on Nightly.
//@line 1325 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.forms.datetime", false);
//@line 1327 "z:\build\build\src\modules\libpref\init\all.js"

// Support for input type=month, type=week and type=datetime-local. By default,
// disabled.
pref("dom.forms.datetime.others", false);

// Enable time picker UI. By default, disabled.
pref("dom.forms.datetime.timepicker", false);

// Support @autocomplete values for form autofill feature.
pref("dom.forms.autocomplete.formautofill", false);

// Enable search in <select> dropdowns (more than 40 options)
pref("dom.forms.selectSearch", false);
// Allow for webpages to provide custom styling for <select>
// popups. Disabled on Linux due to bug 1338283.
//@line 1345 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.forms.select.customstyling", true);
//@line 1347 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.select_popup_in_parent.enabled", false);

// Enable Directory API. By default, disabled.
pref("dom.input.dirpicker", false);

// Enable not moving the cursor to end when a text input or textarea has .value
// set to the value it already has.  By default, enabled.
pref("dom.input.skip_cursor_move_for_same_value_set", true);

// Enables system messages and activities
pref("dom.sysmsg.enabled", false);

// Enable pre-installed applications.
pref("dom.webapps.useCurrentProfile", false);

pref("dom.cycle_collector.incremental", true);

// Whether Xrays expose properties from the named properties object (aka global
// scope polluter).  Values are:
//   0 = properties exposed on Xrays
//   1 = properties exposed on Xrays, except in web extension content scripts.
//   2 = properties not exposed on xrays
pref("dom.allow_named_properties_object_for_xrays", 1);

// Parsing perf prefs. For now just mimic what the old code did.
//@line 1375 "z:\build\build\src\modules\libpref\init\all.js"

// Disable popups from plugins by default
//   0 = openAllowed
//   1 = openControlled
//   2 = openAbused
pref("privacy.popups.disable_from_plugins", 2);

// send "do not track" HTTP header, disabled by default
pref("privacy.donottrackheader.enabled",    false);
// If true, close buton will be shown on permission prompts
// and for all PopupNotifications, the secondary action of
// the popup will be called when the popup is dismissed.
pref("privacy.permissionPrompts.showCloseButton", false);
// Enforce tracking protection in all modes
pref("privacy.trackingprotection.enabled",  false);
// Enforce tracking protection in Private Browsing mode
pref("privacy.trackingprotection.pbmode.enabled",  true);
// Annotate channels based on the tracking protection list in all modes
pref("privacy.trackingprotection.annotate_channels",  true);
// First Party Isolation (double keying), disabled by default
pref("privacy.firstparty.isolate",                        false);
// If false, two windows in the same domain with different first party domains
// (top level URLs) can access resources through window.opener.
// This pref is effective only when "privacy.firstparty.isolate" is true.
pref("privacy.firstparty.isolate.restrict_opener_access", true);
// Anti-fingerprinting, disabled by default
pref("privacy.resistFingerprinting", false);
// Lower the priority of network loads for resources on the tracking protection list.
// Note that this requires the privacy.trackingprotection.annotate_channels pref to be on in order to have any effect.
//@line 1407 "z:\build\build\src\modules\libpref\init\all.js"
pref("privacy.trackingprotection.lower_network_priority", false);
//@line 1409 "z:\build\build\src\modules\libpref\init\all.js"

pref("dom.event.contextmenu.enabled",       true);
pref("dom.event.clipboardevents.enabled",   true);
pref("dom.event.highrestimestamp.enabled",  true);

pref("dom.webcomponents.enabled",           false);
pref("dom.webcomponents.customelements.enabled", false);

pref("javascript.enabled",                  true);
pref("javascript.options.strict",           false);
//@line 1422 "z:\build\build\src\modules\libpref\init\all.js"
pref("javascript.options.baselinejit",      true);
pref("javascript.options.ion",              true);
pref("javascript.options.asmjs",            true);
pref("javascript.options.wasm",             true);
pref("javascript.options.wasm_baselinejit", false);
pref("javascript.options.native_regexp",    true);
pref("javascript.options.parallel_parsing", true);
//@line 1432 "z:\build\build\src\modules\libpref\init\all.js"
pref("javascript.options.asyncstack",       false);
//@line 1434 "z:\build\build\src\modules\libpref\init\all.js"
pref("javascript.options.throw_on_asmjs_validation_failure", false);
pref("javascript.options.ion.offthread_compilation", true);
//@line 1439 "z:\build\build\src\modules\libpref\init\all.js"
// This preference instructs the JS engine to discard the
// source of any privileged JS after compilation. This saves
// memory, but makes things like Function.prototype.toSource()
// fail.
pref("javascript.options.discardSystemSource", false);
// This preference limits the memory usage of javascript.
// If you want to change these values for your device,
// please find Bug 417052 comment 17 and Bug 456721
// Comment 32 and Bug 613551.
pref("javascript.options.mem.high_water_mark", 128);
pref("javascript.options.mem.max", -1);
pref("javascript.options.mem.nursery.max_kb", -1);
pref("javascript.options.mem.gc_per_zone", true);
pref("javascript.options.mem.gc_incremental", true);
pref("javascript.options.mem.gc_incremental_slice_ms", 5);
pref("javascript.options.mem.gc_compacting", true);
pref("javascript.options.mem.log", false);
pref("javascript.options.mem.notify", false);
pref("javascript.options.gc_on_memory_pressure", true);
pref("javascript.options.compact_on_user_inactive", true);
//@line 1462 "z:\build\build\src\modules\libpref\init\all.js"
pref("javascript.options.compact_on_user_inactive_delay", 300000); // ms
//@line 1464 "z:\build\build\src\modules\libpref\init\all.js"

pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1000);
pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 100);
pref("javascript.options.mem.gc_high_frequency_high_limit_mb", 500);
pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 300);
pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 150);
pref("javascript.options.mem.gc_low_frequency_heap_growth", 150);
pref("javascript.options.mem.gc_dynamic_heap_growth", true);
pref("javascript.options.mem.gc_dynamic_mark_slice", true);
pref("javascript.options.mem.gc_refresh_frame_slices_enabled", true);
pref("javascript.options.mem.gc_allocation_threshold_mb", 30);
pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
pref("javascript.options.mem.gc_max_empty_chunk_count", 30);

pref("javascript.options.showInConsole", false);

pref("javascript.options.shared_memory", true);

pref("javascript.options.throw_on_debuggee_would_run", false);
pref("javascript.options.dump_stack_on_debuggee_would_run", false);

// Streams API
pref("javascript.options.streams", false);

// advanced prefs
pref("advanced.mailftp",                    false);
pref("image.animation_mode",                "normal");

// Same-origin policy for file URIs, "false" is traditional
pref("security.fileuri.strict_origin_policy", true);

// If this pref is true, prefs in the logging.config branch will be cleared on
// startup. This is done so that setting a log-file and log-modules at runtime
// doesn't persist across restarts leading to huge logfile and low disk space.
pref("logging.config.clear_on_startup", true);

// If there is ever a security firedrill that requires
// us to block certian ports global, this is the pref
// to use.  Is is a comma delimited list of port numbers
// for example:
//   pref("network.security.ports.banned", "1,2,3,4,5");
// prevents necko connecting to ports 1-5 unless the protocol
// overrides.

// Allow necko to do A/B testing. Will generally only happen if
// telemetry is also enabled as otherwise there is no way to report
// the results
pref("network.allow-experiments", true);

// Allow the network changed event to get sent when a network topology or
// setup change is noticed while running.
pref("network.notify.changed", true);

// Allow network detection of IPv6 related changes (bug 1245059)
//@line 1519 "z:\build\build\src\modules\libpref\init\all.js"
pref("network.notify.IPv6", false);
//@line 1523 "z:\build\build\src\modules\libpref\init\all.js"

// Transmit UDP busy-work to the LAN when anticipating low latency
// network reads and on wifi to mitigate 802.11 Power Save Polling delays
pref("network.tickle-wifi.enabled", false);
pref("network.tickle-wifi.duration", 400);
pref("network.tickle-wifi.delay", 16);

// Turn off interprocess security checks. Needed to run xpcshell tests.
pref("network.disable.ipc.security", false);

// Default action for unlisted external protocol handlers
pref("network.protocol-handler.external-default", true);      // OK to load
pref("network.protocol-handler.warn-external-default", true); // warn before load

// Prevent using external protocol handlers for these schemes
pref("network.protocol-handler.external.hcp", false);
pref("network.protocol-handler.external.vbscript", false);
pref("network.protocol-handler.external.javascript", false);
pref("network.protocol-handler.external.data", false);
pref("network.protocol-handler.external.ms-help", false);
pref("network.protocol-handler.external.shell", false);
pref("network.protocol-handler.external.vnd.ms.radio", false);
//@line 1548 "z:\build\build\src\modules\libpref\init\all.js"
pref("network.protocol-handler.external.disk", false);
pref("network.protocol-handler.external.disks", false);
pref("network.protocol-handler.external.afp", false);
pref("network.protocol-handler.external.moz-icon", false);

// Don't allow  external protocol handlers for common typos
pref("network.protocol-handler.external.ttp", false);  // http
pref("network.protocol-handler.external.ttps", false); // https
pref("network.protocol-handler.external.tps", false);  // https
pref("network.protocol-handler.external.ps", false);   // https
pref("network.protocol-handler.external.ile", false);  // file
pref("network.protocol-handler.external.le", false);   // file

// An exposed protocol handler is one that can be used in all contexts.  A
// non-exposed protocol handler is one that can only be used internally by the
// application.  For example, a non-exposed protocol would not be loaded by the
// application in response to a link click or a X-remote openURL command.
// Instead, it would be deferred to the system's external protocol handler.
// Only internal/built-in protocol handlers can be marked as exposed.

// This pref controls the default settings.  Per protocol settings can be used
// to override this value.
pref("network.protocol-handler.expose-all", true);

// Warning for about:networking page
pref("network.warnOnAboutNetworking", true);

// Example: make IMAP an exposed protocol
// pref("network.protocol-handler.expose.imap", true);

// Whether IOService.connectivity and NS_IsOffline depends on connectivity status
pref("network.manage-offline-status", true);
// If set to true, IOService.offline depends on IOService.connectivity
pref("network.offline-mirrors-connectivity", false);

// <http>
pref("network.http.version", "1.1");      // default
// pref("network.http.version", "1.0");   // uncomment this out in case of problems
// pref("network.http.version", "0.9");   // it'll work too if you're crazy
// keep-alive option is effectively obsolete. Nevertheless it'll work with
// some older 1.0 servers:

pref("network.http.proxy.version", "1.1");    // default
// pref("network.http.proxy.version", "1.0"); // uncomment this out in case of problems
                                              // (required if using junkbuster proxy)

// this preference can be set to override the socket type used for normal
// HTTP traffic.  an empty value indicates the normal TCP/IP socket type.
pref("network.http.default-socket-type", "");

// There is a problem with some IIS7 servers that don't close the connection
// properly after it times out (bug #491541). Default timeout on IIS7 is
// 120 seconds. We need to reuse or drop the connection within this time.
// We set the timeout a little shorter to keep a reserve for cases when
// the packet is lost or delayed on the route.
pref("network.http.keep-alive.timeout", 115);

// Timeout connections if an initial response is not received after 5 mins.
pref("network.http.response.timeout", 300);

// Limit the absolute number of http connections.
// Note: the socket transport service will clamp the number below this if the OS
// cannot allocate that many FDs
//@line 1614 "z:\build\build\src\modules\libpref\init\all.js"
pref("network.http.max-connections", 900);
//@line 1616 "z:\build\build\src\modules\libpref\init\all.js"

// If NOT connecting via a proxy, then
// a new connection will only be attempted if the number of active persistent
// connections to the server is less then max-persistent-connections-per-server.
pref("network.http.max-persistent-connections-per-server", 6);

// Number of connections that we can open beyond the standard parallelism limit defined
// by max-persistent-connections-per-server/-proxy to handle urgent-start marked requests
pref("network.http.max-urgent-start-excessive-connections-per-host", 3);

// If connecting via a proxy, then a
// new connection will only be attempted if the number of active persistent
// connections to the proxy is less then max-persistent-connections-per-proxy.
pref("network.http.max-persistent-connections-per-proxy", 32);

// amount of time (in seconds) to suspend pending requests, before spawning a
// new connection, once the limit on the number of persistent connections per
// host has been reached.  however, a new connection will not be created if
// max-connections or max-connections-per-server has also been reached.
pref("network.http.request.max-start-delay", 10);

// If a connection is reset, we will retry it max-attempts times.
pref("network.http.request.max-attempts", 10);

// Headers
pref("network.http.accept.default", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

// Prefs allowing granular control of referers
// 0=don't send any, 1=send only on clicks, 2=send on image requests as well
pref("network.http.sendRefererHeader",      2);
// Set the default Referrer Policy to be used unless overriden by the site
// 0=no-referrer, 1=same-origin, 2=strict-origin-when-cross-origin,
// 3=no-referrer-when-downgrade
pref("network.http.referer.userControlPolicy", 3);
// false=real referer, true=spoof referer (use target URI as referer)
pref("network.http.referer.spoofSource", false);
// false=allow onion referer, true=hide onion referer (use empty referer)
pref("network.http.referer.hideOnionSource", false);
// 0=full URI, 1=scheme+host+port+path, 2=scheme+host+port
pref("network.http.referer.trimmingPolicy", 0);
// 0=full URI, 1=scheme+host+port+path, 2=scheme+host+port
pref("network.http.referer.XOriginTrimmingPolicy", 0);
// 0=always send, 1=send iff base domains match, 2=send iff hosts match
pref("network.http.referer.XOriginPolicy", 0);

// Maximum number of consecutive redirects before aborting.
pref("network.http.redirection-limit", 20);

// Enable http compression: comment this out in case of problems with 1.1
// NOTE: support for "compress" has been disabled per bug 196406.
// NOTE: separate values with comma+space (", "): see bug 576033
pref("network.http.accept-encoding", "gzip, deflate");
pref("network.http.accept-encoding.secure", "gzip, deflate, br");

// Prompt for redirects resulting in unsafe HTTP requests
pref("network.http.prompt-temp-redirect", false);

// If true generate CORRUPTED_CONTENT errors for entities that
// contain an invalid Assoc-Req response header
pref("network.http.assoc-req.enforce", false);

// On networks deploying QoS, it is recommended that these be lockpref()'d,
// since inappropriate marking can easily overwhelm bandwidth reservations
// for certain services (i.e. EF for VoIP, AF4x for interactive video,
// AF3x for broadcast/streaming video, etc)

// default value for HTTP
// in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
// Section 4.8 "High-Throughput Data Service Class"
pref("network.http.qos", 0);

// The number of milliseconds after sending a SYN for an HTTP connection,
// to wait before trying a different connection. 0 means do not use a second
// connection.
pref("network.http.connection-retry-timeout", 250);

// The number of seconds after sending initial SYN for an HTTP connection
// to give up if the OS does not give up first
pref("network.http.connection-timeout", 90);

// The number of seconds to allow active connections to prove that they have
// traffic before considered stalled, after a network change has been detected
// and signalled.
pref("network.http.network-changed.timeout", 5);

// The maximum number of current global half open sockets allowable
// when starting a new speculative connection.
pref("network.http.speculative-parallel-limit", 6);

// Whether or not to block requests for non head js/css items (e.g. media)
// while those elements load.
pref("network.http.rendering-critical-requests-prioritization", true);

// Disable IPv6 for backup connections to workaround problems about broken
// IPv6 connectivity.
pref("network.http.fast-fallback-to-IPv4", true);

// Try and use SPDY when using SSL
pref("network.http.spdy.enabled", true);
pref("network.http.spdy.enabled.http2", true);
pref("network.http.spdy.enabled.deps", true);
pref("network.http.spdy.enforce-tls-profile", true);
pref("network.http.spdy.chunk-size", 16000);
pref("network.http.spdy.timeout", 170);
pref("network.http.spdy.coalesce-hostnames", true);
pref("network.http.spdy.persistent-settings", false);
pref("network.http.spdy.ping-threshold", 58);
pref("network.http.spdy.ping-timeout", 8);
pref("network.http.spdy.send-buffer-size", 131072);
pref("network.http.spdy.allow-push", true);
pref("network.http.spdy.push-allowance", 131072);   // 128KB
pref("network.http.spdy.pull-allowance", 12582912); // 12MB
pref("network.http.spdy.default-concurrent", 100);
pref("network.http.spdy.default-hpack-buffer", 65536); // 64k

// alt-svc allows separation of transport routing from
// the origin host without using a proxy.
pref("network.http.altsvc.enabled", true);
pref("network.http.altsvc.oe", true);

// Turn on 0RTT data for TLS 1.3
pref("security.tls.enable_0rtt_data", true);

// the origin extension impacts h2 coalescing
pref("network.http.originextension", true);

pref("network.http.diagnostics", false);

pref("network.http.pacing.requests.enabled", true);
pref("network.http.pacing.requests.min-parallelism", 6);
pref("network.http.pacing.requests.hz", 80);
pref("network.http.pacing.requests.burst", 10);

// TCP Keepalive config for HTTP connections.
pref("network.http.tcp_keepalive.short_lived_connections", true);
// Max time from initial request during which conns are considered short-lived.
pref("network.http.tcp_keepalive.short_lived_time", 60);
// Idle time of TCP connection until first keepalive probe sent.
pref("network.http.tcp_keepalive.short_lived_idle_time", 10);

pref("network.http.tcp_keepalive.long_lived_connections", true);
pref("network.http.tcp_keepalive.long_lived_idle_time", 600);

pref("network.http.enforce-framing.http1", false); // should be named "strict"
pref("network.http.enforce-framing.soft", true);

// If it is set to false, headers with empty value will not appear in the header
// array - behavior as it used to be. If it is true: empty headers coming from
// the network will exist in header array as empty string. Call SetHeader with
// an empty value will still delete the header.(Bug 6699259)
pref("network.http.keep_empty_response_headers_as_empty_string", true);

// Max size, in bytes, for received HTTP response header.
pref("network.http.max_response_header_size", 393216);

// If we should attempt to race the cache and network
pref("network.http.rcwn.enabled", false);
pref("network.http.rcwn.cache_queue_normal_threshold", 8);
pref("network.http.rcwn.cache_queue_priority_threshold", 2);
// We might attempt to race the cache with the network only if a resource
// is smaller than this size.
pref("network.http.rcwn.small_resource_size_kb", 256);

pref("network.http.rcwn.max_wait_before_racing_ms", 500);

// The ratio of the transaction count for the focused window and the count of
// all available active connections.
pref("network.http.focused_window_transaction_ratio", "0.9");

// Whether or not we give more priority to active tab.
// Note that this requires restart for changes to take effect.
pref("network.http.active_tab_priority", true);

// default values for FTP
// in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
// Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22)
// per Section 4.7 "Low-Latency Data Service Class".
pref("network.ftp.data.qos", 0);
pref("network.ftp.control.qos", 0);

// The max time to spend on xpcom events between two polls in ms.
pref("network.sts.max_time_for_events_between_two_polls", 100);

// During shutdown we limit PR_Close calls. If time exceeds this pref (in ms)
// let sockets just leak.
pref("network.sts.max_time_for_pr_close_during_shutdown", 5000);
// </http>

// 2147483647 == PR_INT32_MAX == ~2 GB
pref("network.websocket.max-message-size", 2147483647);

// Should we automatically follow http 3xx redirects during handshake
pref("network.websocket.auto-follow-http-redirects", false);

// the number of seconds to wait for websocket connection to be opened
pref("network.websocket.timeout.open", 20);

// the number of seconds to wait for a clean close after sending the client
// close message
pref("network.websocket.timeout.close", 20);

// the number of seconds of idle read activity to sustain before sending a
// ping probe. 0 to disable.
pref("network.websocket.timeout.ping.request", 0);

// the deadline, expressed in seconds, for some read activity to occur after
// generating a ping. If no activity happens then an error and unclean close
// event is sent to the javascript websockets application
pref("network.websocket.timeout.ping.response", 10);

// Defines whether or not to try to negotiate the permessage compression
// extension with the websocket server.
pref("network.websocket.extensions.permessage-deflate", true);

// the maximum number of concurrent websocket sessions. By specification there
// is never more than one handshake oustanding to an individual host at
// one time.
pref("network.websocket.max-connections", 200);

// by default scripts loaded from a https:// origin can only open secure
// (i.e. wss://) websockets.
pref("network.websocket.allowInsecureFromHTTPS", false);

// by default we delay websocket reconnects to same host/port if previous
// connection failed, per RFC 6455 section 7.2.3
pref("network.websocket.delay-failed-reconnects", true);

// </ws>

// Server-Sent Events
// Equal to the DEFAULT_RECONNECTION_TIME_VALUE value in nsEventSource.cpp
pref("dom.server-events.default-reconnection-time", 5000); // in milliseconds

// If false, remote JAR files that are served with a content type other than
// application/java-archive or application/x-jar will not be opened
// by the jar channel.
pref("network.jar.open-unsafe-types", false);
// If true, loading remote JAR files using the jar: protocol will be prevented.
pref("network.jar.block-remote-files", true);

// This preference, if true, causes all UTF-8 domain names to be normalized to
// punycode.  The intention is to allow UTF-8 domain names as input, but never
// generate them from punycode.
pref("network.IDN_show_punycode", false);

// If "network.IDN.use_whitelist" is set to true, TLDs with
// "network.IDN.whitelist.tld" explicitly set to true are treated as
// IDN-safe. Otherwise, they're treated as unsafe and punycode will be used
// for displaying them in the UI (e.g. URL bar), unless they conform to one of
// the profiles specified in
// http://www.unicode.org/reports/tr36/proposed.html#Security_Levels_and_Alerts
// If "network.IDN.restriction_profile" is "high", the Highly Restrictive
// profile is used.
// If "network.IDN.restriction_profile" is "moderate", the Moderately
// Restrictive profile is used.
// In all other cases, the ASCII-Only profile is used.
// Note that these preferences are referred to ONLY when
// "network.IDN_show_punycode" is false. In other words, all IDNs will be shown
// in punycode if "network.IDN_show_punycode" is true.
pref("network.IDN.restriction_profile", "moderate");
pref("network.IDN.use_whitelist", false);

// ccTLDs
pref("network.IDN.whitelist.ac", true);
pref("network.IDN.whitelist.ar", true);
pref("network.IDN.whitelist.at", true);
pref("network.IDN.whitelist.br", true);
pref("network.IDN.whitelist.ca", true);
pref("network.IDN.whitelist.ch", true);
pref("network.IDN.whitelist.cl", true);
pref("network.IDN.whitelist.cn", true);
pref("network.IDN.whitelist.de", true);
pref("network.IDN.whitelist.dk", true);
pref("network.IDN.whitelist.ee", true);
pref("network.IDN.whitelist.es", true);
pref("network.IDN.whitelist.fi", true);
pref("network.IDN.whitelist.fr", true);
pref("network.IDN.whitelist.gr", true);
pref("network.IDN.whitelist.gt", true);
pref("network.IDN.whitelist.hu", true);
pref("network.IDN.whitelist.il", true);
pref("network.IDN.whitelist.io", true);
pref("network.IDN.whitelist.ir", true);
pref("network.IDN.whitelist.is", true);
pref("network.IDN.whitelist.jp", true);
pref("network.IDN.whitelist.kr", true);
pref("network.IDN.whitelist.li", true);
pref("network.IDN.whitelist.lt", true);
pref("network.IDN.whitelist.lu", true);
pref("network.IDN.whitelist.lv", true);
pref("network.IDN.whitelist.no", true);
pref("network.IDN.whitelist.nu", true);
pref("network.IDN.whitelist.nz", true);
pref("network.IDN.whitelist.pl", true);
pref("network.IDN.whitelist.pm", true);
pref("network.IDN.whitelist.pr", true);
pref("network.IDN.whitelist.re", true);
pref("network.IDN.whitelist.se", true);
pref("network.IDN.whitelist.sh", true);
pref("network.IDN.whitelist.si", true);
pref("network.IDN.whitelist.tf", true);
pref("network.IDN.whitelist.th", true);
pref("network.IDN.whitelist.tm", true);
pref("network.IDN.whitelist.tw", true);
pref("network.IDN.whitelist.ua", true);
pref("network.IDN.whitelist.vn", true);
pref("network.IDN.whitelist.wf", true);
pref("network.IDN.whitelist.yt", true);

// IDN ccTLDs
// ae, UAE, .<Emarat>
pref("network.IDN.whitelist.xn--mgbaam7a8h", true);
// cn, China, .<China> with variants
pref("network.IDN.whitelist.xn--fiqz9s", true); // Traditional
pref("network.IDN.whitelist.xn--fiqs8s", true); // Simplified
// eg, Egypt, .<Masr>
pref("network.IDN.whitelist.xn--wgbh1c", true);
// hk, Hong Kong, .<Hong Kong>
pref("network.IDN.whitelist.xn--j6w193g", true);
// ir, Iran, <.Iran> with variants
pref("network.IDN.whitelist.xn--mgba3a4f16a", true);
pref("network.IDN.whitelist.xn--mgba3a4fra", true);
// jo, Jordan, .<Al-Ordon>
pref("network.IDN.whitelist.xn--mgbayh7gpa", true);
// lk, Sri Lanka, .<Lanka> and .<Ilangai>
pref("network.IDN.whitelist.xn--fzc2c9e2c", true);
pref("network.IDN.whitelist.xn--xkc2al3hye2a", true);
// qa, Qatar, .<Qatar>
pref("network.IDN.whitelist.xn--wgbl6a", true);
// rs, Serbia, .<Srb>
pref("network.IDN.whitelist.xn--90a3ac", true);
// ru, Russian Federation, .<RF>
pref("network.IDN.whitelist.xn--p1ai", true);
// sa, Saudi Arabia, .<al-Saudiah> with variants
pref("network.IDN.whitelist.xn--mgberp4a5d4ar", true);
pref("network.IDN.whitelist.xn--mgberp4a5d4a87g", true);
pref("network.IDN.whitelist.xn--mgbqly7c0a67fbc", true);
pref("network.IDN.whitelist.xn--mgbqly7cvafr", true);
// sy, Syria, .<Souria>
pref("network.IDN.whitelist.xn--ogbpf8fl", true);
// th, Thailand, .<Thai>
pref("network.IDN.whitelist.xn--o3cw4h", true);
// tw, Taiwan, <.Taiwan> with variants
pref("network.IDN.whitelist.xn--kpry57d", true);  // Traditional
pref("network.IDN.whitelist.xn--kprw13d", true);  // Simplified

// gTLDs
pref("network.IDN.whitelist.asia", true);
pref("network.IDN.whitelist.biz", true);
pref("network.IDN.whitelist.cat", true);
pref("network.IDN.whitelist.info", true);
pref("network.IDN.whitelist.museum", true);
pref("network.IDN.whitelist.org", true);
pref("network.IDN.whitelist.tel", true);

// NOTE: Before these can be removed, one of bug 414812's tests must be updated
//       or it will likely fail!  Please CC jwalden+bmo on the bug associated
//       with removing these so he can provide a patch to make the necessary
//       changes to avoid bustage.
// ".test" localised TLDs for ICANN's top-level IDN trial
pref("network.IDN.whitelist.xn--0zwm56d", true);
pref("network.IDN.whitelist.xn--11b5bs3a9aj6g", true);
pref("network.IDN.whitelist.xn--80akhbyknj4f", true);
pref("network.IDN.whitelist.xn--9t4b11yi5a", true);
pref("network.IDN.whitelist.xn--deba0ad", true);
pref("network.IDN.whitelist.xn--g6w251d", true);
pref("network.IDN.whitelist.xn--hgbk6aj7f53bba", true);
pref("network.IDN.whitelist.xn--hlcj6aya9esc7a", true);
pref("network.IDN.whitelist.xn--jxalpdlp", true);
pref("network.IDN.whitelist.xn--kgbechtv", true);
pref("network.IDN.whitelist.xn--zckzah", true);

// If a domain includes any of the following characters, it may be a spoof
// attempt and so we always display the domain name as punycode. This would
// override the settings "network.IDN_show_punycode" and
// "network.IDN.whitelist.*".  (please keep this value in sync with the
// built-in fallback in intl/uconv/nsTextToSubURI.cpp)
pref("network.IDN.blacklist_chars", "\u0020\u00A0\u00BC\u00BD\u00BE\u01C3\u02D0\u0337\u0338\u0589\u058A\u05C3\u05F4\u0609\u060A\u066A\u06D4\u0701\u0702\u0703\u0704\u115F\u1160\u1735\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u200E\u200F\u2010\u2019\u2024\u2027\u2028\u2029\u202A\u202B\u202C\u202D\u202E\u202F\u2039\u203A\u2041\u2044\u2052\u205F\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215A\u215B\u215C\u215D\u215E\u215F\u2215\u2236\u23AE\u2571\u29F6\u29F8\u2AFB\u2AFD\u2FF0\u2FF1\u2FF2\u2FF3\u2FF4\u2FF5\u2FF6\u2FF7\u2FF8\u2FF9\u2FFA\u2FFB\u3000\u3002\u3014\u3015\u3033\u30A0\u3164\u321D\u321E\u33AE\u33AF\u33C6\u33DF\uA789\uFE14\uFE15\uFE3F\uFE5D\uFE5E\uFEFF\uFF0E\uFF0F\uFF61\uFFA0\uFFF9\uFFFA\uFFFB\uFFFC\uFFFD");

// This preference specifies a list of domains for which DNS lookups will be
// IPv4 only. Works around broken DNS servers which can't handle IPv6 lookups
// and/or allows the user to disable IPv6 on a per-domain basis. See bug 68796.
pref("network.dns.ipv4OnlyDomains", "");

// This preference can be used to turn off IPv6 name lookups. See bug 68796.
pref("network.dns.disableIPv6", false);

// This is the number of dns cache entries allowed
pref("network.dnsCacheEntries", 400);

// In the absence of OS TTLs, the DNS cache TTL value
pref("network.dnsCacheExpiration", 60);

// Get TTL; not supported on all platforms; nop on the unsupported ones.
pref("network.dns.get-ttl", true);

// The grace period allows the DNS cache to use expired entries, while kicking off
// a revalidation in the background.
pref("network.dnsCacheExpirationGracePeriod", 60);

// This preference can be used to turn off DNS prefetch.
pref("network.dns.disablePrefetch", false);

// This preference controls whether .onion hostnames are
// rejected before being given to DNS. RFC 7686
pref("network.dns.blockDotOnion", true);

// These domains are treated as localhost equivalent
pref("network.dns.localDomains", "");

// When non empty all non-localhost DNS queries (including IP addresses)
// resolve to this value. The value can be a name or an IP address.
// domains mapped to localhost with localDomains stay localhost.
pref("network.dns.forceResolve", "");

// Contols whether or not "localhost" should resolve when offline
pref("network.dns.offline-localhost", true);

// The maximum allowed length for a URL - 1MB default
pref("network.standard-url.max-length", 1048576);

// The preference controls if the rust URL parser is run in parallel with the
// C++ implementation. Requires restart for changes to take effect.
pref("network.standard-url.enable-rust", false);

// Whether nsIURI.host/.hostname/.spec should return a punycode string
// If set to false we will revert to previous behaviour and return a unicode string.
pref("network.standard-url.punycode-host", false);

// Idle timeout for ftp control connections - 5 minute default
pref("network.ftp.idleConnectionTimeout", 300);

// directory listing format
// 2: HTML
// 3: XUL directory viewer
// all other values are treated like 2
pref("network.dir.format", 2);

// enables the prefetch service (i.e., prefetching of <link rel="next"> and
// <link rel="prefetch"> URLs).
pref("network.prefetch-next", true);
// enables the preloading (i.e., preloading of <link rel="preload"> URLs).
pref("network.preload", true);

// enables the predictive service
pref("network.predictor.enabled", true);
pref("network.predictor.enable-hover-on-ssl", false);
pref("network.predictor.enable-prefetch", false);
pref("network.predictor.page-degradation.day", 0);
pref("network.predictor.page-degradation.week", 5);
pref("network.predictor.page-degradation.month", 10);
pref("network.predictor.page-degradation.year", 25);
pref("network.predictor.page-degradation.max", 50);
pref("network.predictor.subresource-degradation.day", 1);
pref("network.predictor.subresource-degradation.week", 10);
pref("network.predictor.subresource-degradation.month", 25);
pref("network.predictor.subresource-degradation.year", 50);
pref("network.predictor.subresource-degradation.max", 100);
pref("network.predictor.prefetch-rolling-load-count", 10);
pref("network.predictor.prefetch-min-confidence", 100);
pref("network.predictor.preconnect-min-confidence", 90);
pref("network.predictor.preresolve-min-confidence", 60);
pref("network.predictor.redirect-likely-confidence", 75);
pref("network.predictor.prefetch-force-valid-for", 10);
pref("network.predictor.max-resources-per-entry", 100);
pref("network.predictor.max-uri-length", 500);
pref("network.predictor.cleaned-up", false);

// The following prefs pertain to the negotiate-auth extension (see bug 17578),
// which provides transparent Kerberos or NTLM authentication using the SPNEGO
// protocol.  Each pref is a comma-separated list of keys, where each key has
// the format:
//   [scheme "://"] [host [":" port]]
// For example, "foo.com" would match "http://www.foo.com/bar", etc.

// Force less-secure NTLMv1 when needed (NTLMv2 is the default).
pref("network.auth.force-generic-ntlm-v1", false);

// This list controls which URIs can use the negotiate-auth protocol.  This
// list should be limited to the servers you know you'll need to login to.
pref("network.negotiate-auth.trusted-uris", "");
// This list controls which URIs can support delegation.
pref("network.negotiate-auth.delegation-uris", "");

// Do not allow SPNEGO by default when challenged by a local server.
pref("network.negotiate-auth.allow-non-fqdn", false);

// Allow SPNEGO by default when challenged by a proxy server.
pref("network.negotiate-auth.allow-proxies", true);

// Path to a specific gssapi library
pref("network.negotiate-auth.gsslib", "");

// Specify if the gss lib comes standard with the OS
pref("network.negotiate-auth.using-native-gsslib", true);

//@line 2113 "z:\build\build\src\modules\libpref\init\all.js"

// Default to using the SSPI intead of GSSAPI on windows
pref("network.auth.use-sspi", true);

//@line 2118 "z:\build\build\src\modules\libpref\init\all.js"

// Controls which NTLM authentication implementation we default to. True forces
// the use of our generic (internal) NTLM authentication implementation vs. any
// native implementation provided by the os. This pref is for diagnosing issues
// with native NTLM. (See bug 520607 for details.) Using generic NTLM authentication
// can expose the user to reflection attack vulnerabilities. Do not change this
// unless you know what you're doing!
// This pref should be removed 6 months after the release of firefox 3.6.
pref("network.auth.force-generic-ntlm", false);

// The following prefs are used to enable automatic use of the operating
// system's NTLM implementation to silently authenticate the user with their
// Window's domain logon.  The trusted-uris pref follows the format of the
// trusted-uris pref for negotiate authentication.
pref("network.automatic-ntlm-auth.allow-proxies", true);
pref("network.automatic-ntlm-auth.allow-non-fqdn", false);
pref("network.automatic-ntlm-auth.trusted-uris", "");

// The string to return to the server as the 'workstation' that the
// user is using.  Bug 1046421 notes that the previous default, of the
// system hostname, could be used for user fingerprinting.
//
// However, in some network environments where allowedWorkstations is in use
// to provide a level of host-based access control, it must be set to a string
// that is listed in allowedWorkstations for the user's account in their
// AD Domain.
pref("network.generic-ntlm-auth.workstation", "WORKSTATION");

// Sub-resources HTTP-authentication:
//   0 - don't allow sub-resources to open HTTP authentication credentials
//       dialogs
//   1 - allow sub-resources to open HTTP authentication credentials dialogs,
//       but don't allow it for cross-origin sub-resources
//   2 - allow the cross-origin authentication as well.
pref("network.auth.subresource-http-auth-allow", 2);

// Sub-resources HTTP-authentication for cross-origin images:
// true - it is allowed to present http auth. dialog for cross-origin images.
// false - it is not allowed.
// If network.auth.subresource-http-auth-allow has values 0 or 1 this pref does not
// have any effect.
pref("network.auth.subresource-img-cross-origin-http-auth-allow", true);

// This preference controls whether to allow sending default credentials (SSO) to
// NTLM/Negotiate servers allowed in the "trusted uri" list when navigating them
// in a Private Browsing window.
// If set to false, Private Browsing windows will not use default credentials and ask
// for credentials from the user explicitly.
// If set to true, and a server URL conforms other conditions for sending default
// credentials, those will be sent automatically in Private Browsing windows.
//
// This preference has no effect when the browser is set to "Never Remember History",
// in that case default credentials will always be used.
pref("network.auth.private-browsing-sso", false);

// Control how throttling of http responses works - number of ms that each
// suspend and resume period lasts (prefs named appropriately)
pref("network.http.throttle.enable", true);
pref("network.http.throttle.suspend-for", 900);
pref("network.http.throttle.resume-for", 100);
// Delay we resume throttled background responses after the last unthrottled
// response has finished.  Prevents resuming too soon during an active page load
// at which sub-resource reqeusts quickly come and go.
pref("network.http.throttle.resume-background-in", 1000);
// After the last transaction activation or last data chunk response we only
// throttle for this period of time.  This prevents comet and unresponsive
// http requests to engage long-standing throttling.
pref("network.http.throttle.time-window", 3000);

// Give higher priority to requests resulting from a user interaction event
// like click-to-play, image fancy-box zoom, navigation.
pref("network.http.on_click_priority", true);

pref("permissions.default.image",           1); // 1-Accept, 2-Deny, 3-dontAcceptForeign

pref("network.proxy.type",                  5);
pref("network.proxy.ftp",                   "");
pref("network.proxy.ftp_port",              0);
pref("network.proxy.http",                  "");
pref("network.proxy.http_port",             0);
pref("network.proxy.ssl",                   "");
pref("network.proxy.ssl_port",              0);
pref("network.proxy.socks",                 "");
pref("network.proxy.socks_port",            0);
pref("network.proxy.socks_version",         5);
pref("network.proxy.socks_remote_dns",      false);
pref("network.proxy.proxy_over_tls",        true);
pref("network.proxy.no_proxies_on",         "localhost, 127.0.0.1");
pref("network.proxy.failover_timeout",      1800); // 30 minutes
pref("network.online",                      true); //online/offline
pref("network.cookie.cookieBehavior",       0); // 0-Accept, 1-dontAcceptForeign, 2-dontAcceptAny, 3-limitForeign
//@line 2212 "z:\build\build\src\modules\libpref\init\all.js"
pref("network.cookie.thirdparty.sessionOnly", false);
pref("network.cookie.leave-secure-alone",   true);
pref("network.cookie.lifetimePolicy",       0); // 0-accept, 1-dontUse 2-acceptForSession, 3-acceptForNDays
pref("network.cookie.prefsMigrated",        false);
pref("network.cookie.lifetime.days",        90); // Ignored unless network.cookie.lifetimePolicy is 3.

// The PAC file to load.  Ignored unless network.proxy.type is 2.
pref("network.proxy.autoconfig_url", "");
// Strip off paths when sending URLs to PAC scripts
pref("network.proxy.autoconfig_url.include_path", false);

// If we cannot load the PAC file, then try again (doubling from interval_min
// until we reach interval_max or the PAC file is successfully loaded).
pref("network.proxy.autoconfig_retry_interval_min", 5);    // 5 seconds
pref("network.proxy.autoconfig_retry_interval_max", 300);  // 5 minutes

// Use the HSTS preload list by default
pref("network.stricttransportsecurity.preloadlist", true);

// Use JS mDNS as a fallback
pref("network.mdns.use_js_fallback", false);

pref("converter.html2txt.structs",          true); // Output structured phrases (strong, em, code, sub, sup, b, i, u)
pref("converter.html2txt.header_strategy",  1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention
// Whether we include ruby annotation in the text despite whether it
// is requested. This was true because we didn't explicitly strip out
// annotations. Set false by default to provide a better behavior, but
// we want to be able to pref-off it if user doesn't like it.
pref("converter.html2txt.always_include_ruby", false);

pref("intl.accept_languages",               "chrome://global/locale/intl.properties");
pref("intl.menuitems.alwaysappendaccesskeys","chrome://global/locale/intl.properties");
pref("intl.menuitems.insertseparatorbeforeaccesskeys","chrome://global/locale/intl.properties");
pref("intl.charset.detector",               "chrome://global/locale/intl.properties");
pref("intl.charset.fallback.override",      "");
pref("intl.charset.fallback.tld",           true);
pref("intl.ellipsis",                       "chrome://global-platform/locale/intl.properties");
pref("intl.locale.matchOS",                 false);
// this pref allows user to request that all internationalization formatters
// like date/time formatting, unit formatting, calendars etc. should use
// OS locale set instead of the app locale set.
pref("intl.regional_prefs.use_os_locales",  false);
// fallback charset list for Unicode conversion (converting from Unicode)
// currently used for mail send only to handle symbol characters (e.g Euro, trademark, smartquotes)
// for ISO-8859-1
pref("intl.fallbackCharsetList.ISO-8859-1", "windows-1252");
pref("font.language.group",                 "chrome://global/locale/intl.properties");

// Android-specific pref to use key-events-only mode for IME-unaware webapps.
//@line 2264 "z:\build\build\src\modules\libpref\init\all.js"
pref("intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition", false);
//@line 2266 "z:\build\build\src\modules\libpref\init\all.js"

// If you use legacy Chinese IME which puts an ideographic space to composition
// string as placeholder, this pref might be useful.  If this is true and when
// web contents forcibly commits composition (e.g., moving focus), the
// ideographic space will be ignored (i.e., commits with empty string).
pref("intl.ime.remove_placeholder_character_at_commit", false);

//@line 2274 "z:\build\build\src\modules\libpref\init\all.js"
pref("intl.uidirection", -1); // -1 to set from locale; 0 for LTR; 1 for RTL
//@line 2283 "z:\build\build\src\modules\libpref\init\all.js"

// use en-US hyphenation by default for content tagged with plain lang="en"
pref("intl.hyphenation-alias.en", "en-us");
// and for other subtags of en-*, if no specific patterns are available
pref("intl.hyphenation-alias.en-*", "en-us");

pref("intl.hyphenation-alias.af-*", "af");
pref("intl.hyphenation-alias.bg-*", "bg");
pref("intl.hyphenation-alias.ca-*", "ca");
pref("intl.hyphenation-alias.cy-*", "cy");
pref("intl.hyphenation-alias.da-*", "da");
pref("intl.hyphenation-alias.eo-*", "eo");
pref("intl.hyphenation-alias.es-*", "es");
pref("intl.hyphenation-alias.et-*", "et");
pref("intl.hyphenation-alias.fi-*", "fi");
pref("intl.hyphenation-alias.fr-*", "fr");
pref("intl.hyphenation-alias.gl-*", "gl");
pref("intl.hyphenation-alias.hr-*", "hr");
pref("intl.hyphenation-alias.hsb-*", "hsb");
pref("intl.hyphenation-alias.hu-*", "hu");
pref("intl.hyphenation-alias.ia-*", "ia");
pref("intl.hyphenation-alias.is-*", "is");
pref("intl.hyphenation-alias.it-*", "it");
pref("intl.hyphenation-alias.kmr-*", "kmr");
pref("intl.hyphenation-alias.la-*", "la");
pref("intl.hyphenation-alias.lt-*", "lt");
pref("intl.hyphenation-alias.mn-*", "mn");
pref("intl.hyphenation-alias.nl-*", "nl");
pref("intl.hyphenation-alias.pl-*", "pl");
pref("intl.hyphenation-alias.pt-*", "pt");
pref("intl.hyphenation-alias.ru-*", "ru");
pref("intl.hyphenation-alias.sl-*", "sl");
pref("intl.hyphenation-alias.sv-*", "sv");
pref("intl.hyphenation-alias.tr-*", "tr");
pref("intl.hyphenation-alias.uk-*", "uk");

// use reformed (1996) German patterns by default unless specifically tagged as de-1901
// (these prefs may soon be obsoleted by better BCP47-based tag matching, but for now...)
pref("intl.hyphenation-alias.de", "de-1996");
pref("intl.hyphenation-alias.de-*", "de-1996");
pref("intl.hyphenation-alias.de-AT-1901", "de-1901");
pref("intl.hyphenation-alias.de-DE-1901", "de-1901");
pref("intl.hyphenation-alias.de-CH-*", "de-CH");

// patterns from TeX are tagged as "sh" (Serbo-Croatian) macrolanguage;
// alias "sr" (Serbian) and "bs" (Bosnian) to those patterns
// (Croatian has its own separate patterns).
pref("intl.hyphenation-alias.sr", "sh");
pref("intl.hyphenation-alias.bs", "sh");
pref("intl.hyphenation-alias.sh-*", "sh");
pref("intl.hyphenation-alias.sr-*", "sh");
pref("intl.hyphenation-alias.bs-*", "sh");

// Norwegian has two forms, Bokmål and Nynorsk, with "no" as a macrolanguage encompassing both.
// For "no", we'll alias to "nb" (Bokmål) as that is the more widely used written form.
pref("intl.hyphenation-alias.no", "nb");
pref("intl.hyphenation-alias.no-*", "nb");
pref("intl.hyphenation-alias.nb-*", "nb");
pref("intl.hyphenation-alias.nn-*", "nn");

// All prefs of default font should be "auto".
pref("font.name.serif.ar", "");;
pref("font.name.sans-serif.ar", "");
pref("font.name.monospace.ar", "");
pref("font.name.cursive.ar", "");

pref("font.name.serif.el", "");
pref("font.name.sans-serif.el", "");
pref("font.name.monospace.el", "");
pref("font.name.cursive.el", "");

pref("font.name.serif.he", "");
pref("font.name.sans-serif.he", "");
pref("font.name.monospace.he", "");
pref("font.name.cursive.he", "");

pref("font.name.serif.ja", "");
pref("font.name.sans-serif.ja", "");
pref("font.name.monospace.ja", "");
pref("font.name.cursive.ja", "");

pref("font.name.serif.ko", "");
pref("font.name.sans-serif.ko", "");
pref("font.name.monospace.ko", "");
pref("font.name.cursive.ko", "");

pref("font.name.serif.th", "");
pref("font.name.sans-serif.th", "");
pref("font.name.monospace.th", "");
pref("font.name.cursive.th", "");

pref("font.name.serif.x-cyrillic", "");
pref("font.name.sans-serif.x-cyrillic", "");
pref("font.name.monospace.x-cyrillic", "");
pref("font.name.cursive.x-cyrillic", "");

pref("font.name.serif.x-unicode", "");
pref("font.name.sans-serif.x-unicode", "");
pref("font.name.monospace.x-unicode", "");
pref("font.name.cursive.x-unicode", "");

pref("font.name.serif.x-western", "");
pref("font.name.sans-serif.x-western", "");
pref("font.name.monospace.x-western", "");
pref("font.name.cursive.x-western", "");

pref("font.name.serif.zh-CN", "");
pref("font.name.sans-serif.zh-CN", "");
pref("font.name.monospace.zh-CN", "");
pref("font.name.cursive.zh-CN", "");

pref("font.name.serif.zh-TW", "");
pref("font.name.sans-serif.zh-TW", "");
pref("font.name.monospace.zh-TW", "");
pref("font.name.cursive.zh-TW", "");

pref("font.name.serif.zh-HK", "");
pref("font.name.sans-serif.zh-HK", "");
pref("font.name.monospace.zh-HK", "");
pref("font.name.cursive.zh-HK", "");

pref("font.name.serif.x-devanagari", "");
pref("font.name.sans-serif.x-devanagari", "");
pref("font.name.monospace.x-devanagari", "");
pref("font.name.cursive.x-devanagari", "");

pref("font.name.serif.x-tamil", "");
pref("font.name.sans-serif.x-tamil", "");
pref("font.name.monospace.x-tamil", "");
pref("font.name.cursive.x-tamil", "");

pref("font.name.serif.x-armn", "");
pref("font.name.sans-serif.x-armn", "");
pref("font.name.monospace.x-armn", "");
pref("font.name.cursive.x-armn", "");

pref("font.name.serif.x-beng", "");
pref("font.name.sans-serif.x-beng", "");
pref("font.name.monospace.x-beng", "");
pref("font.name.cursive.x-beng", "");

pref("font.name.serif.x-cans", "");
pref("font.name.sans-serif.x-cans", "");
pref("font.name.monospace.x-cans", "");
pref("font.name.cursive.x-cans", "");

pref("font.name.serif.x-ethi", "");
pref("font.name.sans-serif.x-ethi", "");
pref("font.name.monospace.x-ethi", "");
pref("font.name.cursive.x-ethi", "");

pref("font.name.serif.x-geor", "");
pref("font.name.sans-serif.x-geor", "");
pref("font.name.monospace.x-geor", "");
pref("font.name.cursive.x-geor", "");

pref("font.name.serif.x-gujr", "");
pref("font.name.sans-serif.x-gujr", "");
pref("font.name.monospace.x-gujr", "");
pref("font.name.cursive.x-gujr", "");

pref("font.name.serif.x-guru", "");
pref("font.name.sans-serif.x-guru", "");
pref("font.name.monospace.x-guru", "");
pref("font.name.cursive.x-guru", "");

pref("font.name.serif.x-khmr", "");
pref("font.name.sans-serif.x-khmr", "");
pref("font.name.monospace.x-khmr", "");
pref("font.name.cursive.x-khmr", "");

pref("font.name.serif.x-mlym", "");
pref("font.name.sans-serif.x-mlym", "");
pref("font.name.monospace.x-mlym", "");
pref("font.name.cursive.x-mlym", "");

pref("font.name.serif.x-orya", "");
pref("font.name.sans-serif.x-orya", "");
pref("font.name.monospace.x-orya", "");
pref("font.name.cursive.x-orya", "");

pref("font.name.serif.x-telu", "");
pref("font.name.sans-serif.x-telu", "");
pref("font.name.monospace.x-telu", "");
pref("font.name.cursive.x-telu", "");

pref("font.name.serif.x-knda", "");
pref("font.name.sans-serif.x-knda", "");
pref("font.name.monospace.x-knda", "");
pref("font.name.cursive.x-knda", "");

pref("font.name.serif.x-sinh", "");
pref("font.name.sans-serif.x-sinh", "");
pref("font.name.monospace.x-sinh", "");
pref("font.name.cursive.x-sinh", "");

pref("font.name.serif.x-tibt", "");
pref("font.name.sans-serif.x-tibt", "");
pref("font.name.monospace.x-tibt", "");
pref("font.name.cursive.x-tibt", "");

pref("font.name.serif.x-math", "");
pref("font.name.sans-serif.x-math", "");
pref("font.name.monospace.x-math", "");
pref("font.name.cursive.x-math", "");

pref("font.name-list.serif.x-math", "Latin Modern Math, STIX Two Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, serif");
pref("font.name-list.sans-serif.x-math", "sans-serif");
pref("font.name-list.monospace.x-math", "monospace");

// Some CJK fonts have bad underline offset, their CJK character glyphs are overlapped (or adjoined)  to its underline.
// These fonts are ignored the underline offset, instead of it, the underline is lowered to bottom of its em descent.
pref("font.blacklist.underline_offset", "FangSong,Gulim,GulimChe,MingLiU,MingLiU-ExtB,MingLiU_HKSCS,MingLiU-HKSCS-ExtB,MS Gothic,MS Mincho,MS PGothic,MS PMincho,MS UI Gothic,PMingLiU,PMingLiU-ExtB,SimHei,SimSun,SimSun-ExtB,Hei,Kai,Apple LiGothic,Apple LiSung,Osaka");

//@line 2504 "z:\build\build\src\modules\libpref\init\all.js"

pref("images.dither", "auto");
pref("security.directory",              "");

pref("signed.applets.codebase_principal_support", false);
pref("security.checkloaduri", true);
pref("security.xpconnect.plugin.unrestricted", true);
// security-sensitive dialogs should delay button enabling. In milliseconds.
pref("security.dialog_enable_delay", 1000);
pref("security.notification_enable_delay", 500);

pref("security.csp.enable", true);
pref("security.csp.experimentalEnabled", false);
pref("security.csp.enableStrictDynamic", true);

// Default Content Security Policy to apply to signed contents.
pref("security.signed_content.CSP.default", "script-src 'self'; style-src 'self'");

// Mixed content blocking
pref("security.mixed_content.block_active_content", false);
pref("security.mixed_content.block_display_content", false);

// Sub-resource integrity
pref("security.sri.enable", true);

// Block scripts with wrong MIME type such as image/ or video/.
pref("security.block_script_with_wrong_mime", true);

// Block images of wrong MIME for XCTO: nosniff.
pref("security.xcto_nosniff_block_images", false);

// OCSP must-staple
pref("security.ssl.enable_ocsp_must_staple", true);

// Insecure Form Field Warning
pref("security.insecure_field_warning.contextual.enabled", false);
pref("security.insecure_field_warning.ignore_local_ip_address", true);

// Disable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 0);
// Do not process hpkp headers rooted by not built in roots by default.
// This is to prevent accidental pinning from MITM devices and is used
// for tests.
pref("security.cert_pinning.process_headers_from_non_builtin_roots", false);

// If set to true, allow view-source URIs to be opened from URIs that share
// their protocol with the inner URI of the view-source URI
pref("security.view-source.reachable-from-inner-protocol", false);

// If set to true, in some limited circumstances it may be possible to load
// privileged content in frames inside unprivileged content.
pref("security.allow_chrome_frames_inside_content", false);

// Services security settings
pref("services.settings.server", "https://firefox.settings.services.mozilla.com/v1");

// Blocklist preferences
pref("extensions.blocklist.enabled", true);
// OneCRL freshness checking depends on this value, so if you change it,
// please also update security.onecrl.maximum_staleness_in_seconds.
pref("extensions.blocklist.interval", 86400);
// Required blocklist freshness for OneCRL OCSP bypass
// (default is 1.25x extensions.blocklist.interval, or 30 hours)
pref("security.onecrl.maximum_staleness_in_seconds", 108000);
pref("extensions.blocklist.url", "https://blocklists.settings.services.mozilla.com/v1/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
pref("extensions.blocklist.detailsURL", "https://blocked.cdn.mozilla.net/");
pref("extensions.blocklist.itemURL", "https://blocked.cdn.mozilla.net/%blockID%.html");
// Controls what level the blocklist switches from warning about items to forcibly
// blocking them.
pref("extensions.blocklist.level", 2);
// Blocklist via settings server (Kinto)
pref("services.blocklist.changes.path", "/buckets/monitor/collections/changes/records");
pref("services.blocklist.bucket", "blocklists");
pref("services.blocklist.onecrl.collection", "certificates");
pref("services.blocklist.onecrl.checked", 0);
pref("services.blocklist.addons.collection", "addons");
pref("services.blocklist.addons.checked", 0);
pref("services.blocklist.plugins.collection", "plugins");
pref("services.blocklist.plugins.checked", 0);
pref("services.blocklist.pinning.enabled", true);
pref("services.blocklist.pinning.bucket", "pinning");
pref("services.blocklist.pinning.collection", "pins");
pref("services.blocklist.pinning.checked", 0);
pref("services.blocklist.gfx.collection", "gfx");
pref("services.blocklist.gfx.checked", 0);

// Controls whether signing should be enforced on signature-capable blocklist
// collections.
pref("services.blocklist.signing.enforced", true);

// Enable blocklists via the services settings mechanism
pref("services.blocklist.update_enabled", true);

// Enable certificate blocklist updates via services settings
pref("security.onecrl.via.amo", false);


// Modifier key prefs: default to Windows settings,
// menu access key = alt, accelerator key = control.
// Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js
pref("ui.key.accelKey", 17);
pref("ui.key.menuAccessKey", 18);
pref("ui.key.generalAccessKey", -1);

// If generalAccessKey is -1, use the following two prefs instead.
// Use 0 for disabled, 1 for Shift, 2 for Ctrl, 4 for Alt, 8 for Meta, 16 for Win
// (values can be combined, e.g. 5 for Alt+Shift)
pref("ui.key.chromeAccess", 4);
pref("ui.key.contentAccess", 5);

pref("ui.key.menuAccessKeyFocuses", false); // overridden below
pref("ui.key.saveLink.shift", true); // true = shift, false = meta

// Disable page loading activity cursor by default.
pref("ui.use_activity_cursor", false);

// Middle-mouse handling
pref("middlemouse.paste", false);
pref("middlemouse.openNewWindow", true);
pref("middlemouse.contentLoadURL", false);
pref("middlemouse.scrollbarPosition", false);

// Clipboard behavior
pref("clipboard.autocopy", false);

// Clipboard only supports text/plain
pref("clipboard.plainTextOnly", false);

//@line 2633 "z:\build\build\src\modules\libpref\init\all.js"
// Setting false you can disable 4th button and/or 5th button of your mouse.
// 4th button is typically mapped to "Back" and 5th button is typically mapped
// to "Forward" button.
pref("mousebutton.4th.enabled", true);
pref("mousebutton.5th.enabled", true);
//@line 2639 "z:\build\build\src\modules\libpref\init\all.js"

// mouse wheel scroll transaction period of time (in milliseconds)
pref("mousewheel.transaction.timeout", 1500);
// mouse wheel scroll transaction is held even if the mouse cursor is moved.
pref("mousewheel.transaction.ignoremovedelay", 100);

// prefs for app level mouse wheel scrolling acceleration.
// number of mousewheel clicks when acceleration starts
// acceleration can be turned off if pref is set to -1
pref("mousewheel.acceleration.start", -1);
// factor to be multiplied for constant acceleration
pref("mousewheel.acceleration.factor", 10);

// Prefs for override the system mouse wheel scrolling speed on
// content of the web pages.  When
// "mousewheel.system_scroll_override_on_root_content.enabled" is true and the system
// scrolling speed isn't customized by the user, the content scrolling
// speed is multiplied by the following factors.  The value will be used as
// 1/100.  E.g., 200 means 2.00.
// NOTE: Even if "mousewheel.system_scroll_override_on_root_content.enabled" is
// true, when Gecko detects the user customized the system scrolling speed
// settings, the override isn't executed.
pref("mousewheel.system_scroll_override_on_root_content.vertical.factor", 200);
pref("mousewheel.system_scroll_override_on_root_content.horizontal.factor", 200);

// mousewheel.*.action can specify the action when you use mosue wheel.
// When no modifier keys are pressed or two or more modifires are pressed,
// .default is used.
// 0: Nothing happens
// 1: Scrolling contents
// 2: Go back or go forward, in your history
// 3: Zoom in or out.
pref("mousewheel.default.action", 1);
pref("mousewheel.with_alt.action", 2);
pref("mousewheel.with_control.action", 3);
pref("mousewheel.with_meta.action", 1);  // command key on Mac
pref("mousewheel.with_shift.action", 1);
pref("mousewheel.with_win.action", 1);

// mousewheel.*.action.override_x will override the action
// when the mouse wheel is rotated along the x direction.
// -1: Don't override the action.
// 0 to 3: Override the action with the specified value.
pref("mousewheel.default.action.override_x", -1);
pref("mousewheel.with_alt.action.override_x", -1);
pref("mousewheel.with_control.action.override_x", -1);
pref("mousewheel.with_meta.action.override_x", -1);  // command key on Mac
pref("mousewheel.with_shift.action.override_x", -1);
pref("mousewheel.with_win.action.override_x", -1);

// mousewheel.*.delta_multiplier_* can specify the value muliplied by the delta
// value.  The values will be used after divided by 100.  I.e., 100 means 1.0,
// -100 means -1.0.  If the values were negative, the direction would be
// reverted.  The absolue value must be 100 or larger.
pref("mousewheel.default.delta_multiplier_x", 100);
pref("mousewheel.default.delta_multiplier_y", 100);
pref("mousewheel.default.delta_multiplier_z", 100);
pref("mousewheel.with_alt.delta_multiplier_x", 100);
pref("mousewheel.with_alt.delta_multiplier_y", 100);
pref("mousewheel.with_alt.delta_multiplier_z", 100);
pref("mousewheel.with_control.delta_multiplier_x", 100);
pref("mousewheel.with_control.delta_multiplier_y", 100);
pref("mousewheel.with_control.delta_multiplier_z", 100);
pref("mousewheel.with_meta.delta_multiplier_x", 100);  // command key on Mac
pref("mousewheel.with_meta.delta_multiplier_y", 100);  // command key on Mac
pref("mousewheel.with_meta.delta_multiplier_z", 100);  // command key on Mac
pref("mousewheel.with_shift.delta_multiplier_x", 100);
pref("mousewheel.with_shift.delta_multiplier_y", 100);
pref("mousewheel.with_shift.delta_multiplier_z", 100);
pref("mousewheel.with_win.delta_multiplier_x", 100);
pref("mousewheel.with_win.delta_multiplier_y", 100);
pref("mousewheel.with_win.delta_multiplier_z", 100);

// If line-height is lower than this value (in device pixels), 1 line scroll
// scrolls this height.
pref("mousewheel.min_line_scroll_amount", 5);

// These define the smooth scroll behavior (min ms, max ms) for different triggers
// Some triggers:
// mouseWheel: Discrete mouse wheel events, Synaptics touchpads on windows (generate wheel events)
// Lines:  Up/Down/Left/Right KB arrows
// Pages:  Page up/down, Space
// Scrollbars: Clicking scrollbars arrows, clicking scrollbars tracks
// Note: Currently OS X trackpad and magic mouse don't use our smooth scrolling
// Note: These are relevant only when "general.smoothScroll" is enabled
pref("general.smoothScroll.mouseWheel.durationMinMS", 200);
pref("general.smoothScroll.mouseWheel.durationMaxMS", 400);
pref("general.smoothScroll.pixels.durationMinMS", 150);
pref("general.smoothScroll.pixels.durationMaxMS", 150);
pref("general.smoothScroll.lines.durationMinMS", 150);
pref("general.smoothScroll.lines.durationMaxMS", 150);
pref("general.smoothScroll.pages.durationMinMS", 150);
pref("general.smoothScroll.pages.durationMaxMS", 150);
pref("general.smoothScroll.scrollbars.durationMinMS", 150);
pref("general.smoothScroll.scrollbars.durationMaxMS", 150);
pref("general.smoothScroll.other.durationMinMS", 150);
pref("general.smoothScroll.other.durationMaxMS", 150);
// Enable disable smooth scrolling for different triggers (when "general.smoothScroll" is enabled)
pref("general.smoothScroll.mouseWheel", true);
pref("general.smoothScroll.pixels", true);
pref("general.smoothScroll.lines", true);
pref("general.smoothScroll.pages", true);
pref("general.smoothScroll.scrollbars", true);
pref("general.smoothScroll.other", true);
// To connect consecutive scroll events into a continuous flow, the animation's duration
// should be longer than scroll events intervals (or else the scroll will stop
// before the next event arrives - we're guessing next interval by averaging recent
// intervals).
// This defines how longer is the duration compared to events interval (percentage)
pref("general.smoothScroll.durationToIntervalRatio", 200);
// These two prefs determine the timing function.
pref("general.smoothScroll.currentVelocityWeighting", "0.25");
pref("general.smoothScroll.stopDecelerationWeighting", "0.4");

pref("profile.confirm_automigration",true);
// profile.migration_behavior determines how the profiles root is set
// 0 - use NS_APP_USER_PROFILES_ROOT_DIR
// 1 - create one based on the NS4.x profile root
// 2 - use, if not empty, profile.migration_directory otherwise same as 0
pref("profile.migration_behavior",0);
pref("profile.migration_directory", "");

// the amount of time (in seconds) that must elapse
// before we think your mozilla profile is defunct
// and you'd benefit from re-migrating from 4.x
// see bug #137886 for more details
//
// if -1, we never think your profile is defunct
// and users will never see the remigrate UI.
pref("profile.seconds_until_defunct", -1);
// We can show it anytime from menus
pref("profile.manage_only_at_launch", false);

pref("prefs.converted-to-utf8",false);

// ------------------
//  Text Direction
// ------------------
// 1 = directionLTRBidi *
// 2 = directionRTLBidi
pref("bidi.direction", 1);
// ------------------
//  Text Type
// ------------------
// 1 = charsettexttypeBidi *
// 2 = logicaltexttypeBidi
// 3 = visualtexttypeBidi
pref("bidi.texttype", 1);
// ------------------
//  Numeral Style
// ------------------
// 0 = nominalnumeralBidi *
// 1 = regularcontextnumeralBidi
// 2 = hindicontextnumeralBidi
// 3 = arabicnumeralBidi
// 4 = hindinumeralBidi
// 5 = persiancontextnumeralBidi
// 6 = persiannumeralBidi
pref("bidi.numeral", 0);
// Whether delete and backspace should immediately delete characters not
// visually adjacent to the caret, or adjust the visual position of the caret
// on the first keypress and delete the character on a second keypress
pref("bidi.edit.delete_immediately", true);

// Bidi caret movement style:
// 0 = logical
// 1 = visual
// 2 = visual, but logical during selection
pref("bidi.edit.caret_movement_style", 2);

// Setting this pref to |true| forces Bidi UI menu items and keyboard shortcuts
// to be exposed, and enables the directional caret hook. By default, only
// expose it for bidi-associated system locales.
pref("bidi.browser.ui", false);

// used for double-click word selection behavior. Win will override.
pref("layout.word_select.eat_space_to_next_word", false);
pref("layout.word_select.stop_at_punctuation", true);

// controls caret style and word-delete during text selection
// 0 = use platform default
// 1 = caret moves and blinks as when there is no selection; word
//     delete deselects the selection and then deletes word
// 2 = caret moves to selection edge and is not visible during selection;
//     word delete deletes the selection (Mac and Linux default)
// 3 = caret moves and blinks as when there is no selection; word delete
//     deletes the selection
// Windows default is 1 for word delete behavior, the rest as for 2.
pref("layout.selection.caret_style", 0);

// pref to report CSS errors to the error console
pref("layout.css.report_errors", true);

// Should the :visited selector ever match (otherwise :link matches instead)?
pref("layout.css.visited_links_enabled", true);

// Override DPI. A value of -1 means use the maximum of 96 and the system DPI.
// A value of 0 means use the system DPI. A positive value is used as the DPI.
// This sets the physical size of a device pixel and thus controls the
// interpretation of physical units such as "pt".
pref("layout.css.dpi", -1);

// Set the number of device pixels per CSS pixel. A value <= 0 means choose
// automatically based on user settings for the platform (e.g., "UI scale factor"
// on Mac). A positive value is used as-is. This effectively controls the size
// of a CSS "px". This is only used for windows on the screen, not for printing.
pref("layout.css.devPixelsPerPx", "-1.0");

// Is support for CSS initial-letter property enabled?
pref("layout.css.initial-letter.enabled", false);

// Is support for mix-blend-mode enabled?
pref("layout.css.mix-blend-mode.enabled", true);

// Is support for isolation enabled?
pref("layout.css.isolation.enabled", true);

// Is support for CSS Filters enabled?
pref("layout.css.filters.enabled", true);

// Set the threshold distance in CSS pixels below which scrolling will snap to
// an edge, when scroll snapping is set to "proximity".
pref("layout.css.scroll-snap.proximity-threshold", 200);

// When selecting the snap point for CSS scroll snapping, the velocity of the
// scroll frame is clamped to this speed, in CSS pixels / s.
pref("layout.css.scroll-snap.prediction-max-velocity", 2000);

// When selecting the snap point for CSS scroll snapping, the velocity of the
// scroll frame is integrated over this duration, in seconds.  The snap point
// best suited for this position is selected, enabling the user to perform fling
// gestures.
pref("layout.css.scroll-snap.prediction-sensitivity", "0.750");

// Is support for basic shapes in clip-path enabled?
pref("layout.css.clip-path-shapes.enabled", true);

// Is support for DOMPoint enabled?
pref("layout.css.DOMPoint.enabled", true);

// Is support for DOMQuad enabled?
pref("layout.css.DOMQuad.enabled", true);

// Is support for DOMMatrix enabled?
pref("layout.css.DOMMatrix.enabled", true);

// Is support for GeometryUtils.getBoxQuads enabled?
//@line 2887 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.getBoxQuads.enabled", false);
//@line 2891 "z:\build\build\src\modules\libpref\init\all.js"

// Is support for GeometryUtils.convert*FromNode enabled?
//@line 2894 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.convertFromNode.enabled", false);
//@line 2898 "z:\build\build\src\modules\libpref\init\all.js"

// Is support for CSS "text-align: unsafe X" enabled?
pref("layout.css.text-align-unsafe-value.enabled", false);

// Is support for CSS text-justify property enabled?
pref("layout.css.text-justify.enabled", true);

// Is support for CSS "float: inline-{start,end}" and
// "clear: inline-{start,end}" enabled?
pref("layout.css.float-logical-values.enabled", true);

// Is support for the CSS4 image-orientation property enabled?
pref("layout.css.image-orientation.enabled", true);

// Is support for the font-display @font-face descriptor enabled?
pref("layout.css.font-display.enabled", false);

// Is support for variation fonts enabled?
pref("layout.css.font-variations.enabled", false);

// Is support for the frames() timing function enabled?
//@line 2920 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.frames-timing.enabled", false);
//@line 2924 "z:\build\build\src\modules\libpref\init\all.js"

// Are sets of prefixed properties supported?
pref("layout.css.prefixes.border-image", true);
pref("layout.css.prefixes.transforms", true);
pref("layout.css.prefixes.transitions", true);
pref("layout.css.prefixes.animations", true);
pref("layout.css.prefixes.box-sizing", true);
pref("layout.css.prefixes.font-features", true);

// Is -moz-prefixed gradient functions enabled?
pref("layout.css.prefixes.gradients", true);

// Are webkit-prefixed properties & property-values supported?
pref("layout.css.prefixes.webkit", true);

// Are "-webkit-{min|max}-device-pixel-ratio" media queries supported?
// (Note: this pref has no effect if the master 'layout.css.prefixes.webkit'
// pref is set to false.)
pref("layout.css.prefixes.device-pixel-ratio-webkit", false);

// Is support for <style scoped> enabled in content documents?
//
// If disabled, this will also disable the DOM API (HTMLStyleElement.scoped)
// in chrome documents.
pref("layout.css.scoped-style.enabled", false);

// Is support for the :scope selector enabled?
pref("layout.css.scope-pseudo.enabled", true);

// Is support for background-blend-mode enabled?
pref("layout.css.background-blend-mode.enabled", true);

// Is support for CSS text-combine-upright (tate-chu-yoko) enabled?
pref("layout.css.text-combine-upright.enabled", true);
// Is support for CSS text-combine-upright: digits 2-4 enabled?
pref("layout.css.text-combine-upright-digits.enabled", false);

// Is -moz-osx-font-smoothing enabled?
// Only supported in OSX builds
//@line 2966 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.osx-font-smoothing.enabled", false);
//@line 2968 "z:\build\build\src\modules\libpref\init\all.js"

// Is support for the CSS-wide "unset" value enabled?
pref("layout.css.unset-value.enabled", true);

// Is support for the "all" shorthand enabled?
pref("layout.css.all-shorthand.enabled", true);

// Is support for CSS overflow-clip-box enabled for non-UA sheets?
pref("layout.css.overflow-clip-box.enabled", false);

// Is support for CSS grid enabled?
pref("layout.css.grid.enabled", true);

// Is support for CSS "grid-template-{columns,rows}: subgrid X" enabled?
pref("layout.css.grid-template-subgrid-value.enabled", false);

// Is support for CSS contain enabled?
pref("layout.css.contain.enabled", false);

// Is support for CSS box-decoration-break enabled?
pref("layout.css.box-decoration-break.enabled", true);

// Is layout of CSS outline-style:auto enabled?
pref("layout.css.outline-style-auto.enabled", false);

// Is CSSOM-View scroll-behavior and its MSD smooth scrolling enabled?
pref("layout.css.scroll-behavior.enabled", true);

// Is the CSSOM-View scroll-behavior CSS property enabled?
pref("layout.css.scroll-behavior.property-enabled", true);

// Tuning of the smooth scroll motion used by CSSOM-View scroll-behavior.
// Spring-constant controls the strength of the simulated MSD
// (Mass-Spring-Damper)
pref("layout.css.scroll-behavior.spring-constant", "250.0");

// Tuning of the smooth scroll motion used by CSSOM-View scroll-behavior.
// Damping-ratio controls the dampening force of the simulated MSD
// (Mass-Spring-Damper).
// When below 1.0, the system is under-damped; it may overshoot the target and
// oscillate.
// When greater than 1.0, the system is over-damped; it will reach the target at
// reduced speed without overshooting.
// When equal to 1.0, the system is critically-damped; it will reach the target
// at the greatest speed without overshooting.
pref("layout.css.scroll-behavior.damping-ratio", "1.0");

// Is support for scroll-snap enabled?
pref("layout.css.scroll-snap.enabled", true);

// Is support for CSS shape-outside enabled?
pref("layout.css.shape-outside.enabled", false);

// Is support for document.fonts enabled?
pref("layout.css.font-loading-api.enabled", true);

// Should stray control characters be rendered visibly?
//@line 3026 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.control-characters.visible", false);
//@line 3030 "z:\build\build\src\modules\libpref\init\all.js"

// Is support for column-span enabled?
pref("layout.css.column-span.enabled", false);

// Is effect of xml:base disabled for style attribute?
pref("layout.css.style-attr-with-xml-base.disabled", true);

// pref for which side vertical scrollbars should be on
// 0 = end-side in UI direction
// 1 = end-side in document/content direction
// 2 = right
// 3 = left
pref("layout.scrollbar.side", 0);

// pref to stop overlay scrollbars from fading out, for testing purposes
pref("layout.testing.overlay-scrollbars.always-visible", false);

// Enable/disable interruptible reflow, which allows reflows to stop
// before completion (and display the partial results) when user events
// are pending.
pref("layout.interruptible-reflow.enabled", true);

// pref to control browser frame rate, in Hz. A value <= 0 means choose
// automatically based on knowledge of the platform (or 60Hz if no platform-
// specific information is available).
pref("layout.frame_rate", -1);

// pref to dump the display list to the log. Useful for debugging drawing.
pref("layout.display-list.dump", false);
pref("layout.display-list.dump-content", false);

// pref to control whether layout warnings that are hit quite often are enabled
pref("layout.spammy_warnings.enabled", false);

// Should we fragment floats inside CSS column layout?
pref("layout.float-fragments-inside-column.enabled", true);

// The number of frames times the frame rate is the time required to
// pass without painting used to guess that we'll not paint again soon
pref("layout.idle_period.required_quiescent_frames", 2);

// The amount of time (milliseconds) needed between an idle period's
// end and the start of the next tick to avoid jank.
pref("layout.idle_period.time_limit", 1);

// Is support for the Web Animations API enabled?
// Before enabling this by default, make sure also CSSPseudoElement interface
// has been spec'ed properly, or we should add a separate pref for
// CSSPseudoElement interface. See Bug 1174575 for further details.
//@line 3080 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.animations-api.core.enabled", false);
//@line 3084 "z:\build\build\src\modules\libpref\init\all.js"

// Is support for the Element.animate() function (a subset of the Web Animations
// API) enabled?
// Note that if dom.animations-api.core.enabled is true, this preference is
// ignored.
pref("dom.animations-api.element-animate.enabled", true);

// Pref to throttle offsreen animations
pref("dom.animations.offscreen-throttling", true);

// Prefs to control the maximum area to pre-render when animating a large
// element on the compositor.
pref("layout.animation.prerender.partial", false);
pref("layout.animation.prerender.viewport-ratio-limit-x", "1.125");
pref("layout.animation.prerender.viewport-ratio-limit-y", "1.125");
pref("layout.animation.prerender.absolute-limit-x", 4096);
pref("layout.animation.prerender.absolute-limit-y", 4096);

// pref to permit users to make verified SOAP calls by default
pref("capability.policy.default.SOAPCall.invokeVerifySourceHeader", "allAccess");

// if true, allow plug-ins to override internal imglib decoder mime types in full-page mode
pref("plugin.override_internal_types", false);

// See bug 136985.  Gives embedders a pref to hook into to show
// a popup blocker if they choose.
pref("browser.popups.showPopupBlocker", true);

// Pref to control whether the viewmanager code does double-buffering or not
// See http://bugzilla.mozilla.org/show_bug.cgi?id=169483 for further details...
pref("viewmanager.do_doublebuffering", true);

// enable single finger gesture input (win7+ tablets)
pref("gestures.enable_single_finger_input", true);

pref("editor.resizing.preserve_ratio",       true);
pref("editor.positioning.offset",            0);

pref("dom.use_watchdog", true);
pref("dom.max_chrome_script_run_time", 20);
pref("dom.max_script_run_time", 10);

// Stop all scripts in a compartment when the "stop script" dialog is used.
pref("dom.global_stop_script", true);

// Time (milliseconds) between throttled idle callbacks.
pref("dom.idle_period.throttled_length", 10000);

// The amount of idle time (milliseconds) reserved for a long idle period
pref("idle_queue.long_period", 50);

// The minimum amount of time (milliseconds) required for an idle
// period to be scheduled on the main thread. N.B. that
// layout.idle_period.time_limit adds padding at the end of the idle
// period, which makes the point in time that we expect to become busy
// again be:
// now + idle_queue.min_period + layout.idle_period.time_limit
pref("idle_queue.min_period", 3);

// Hang monitor timeout after which we kill the browser, in seconds
// (0 is disabled)
// Disabled on all platforms per bug 705748 until the found issues are
// resolved.
pref("hangmonitor.timeout", 0);

pref("plugins.load_appdir_plugins", false);
// If true, plugins will be click to play
pref("plugins.click_to_play", false);

// This only supports one hidden ctp plugin, edit nsPluginArray.cpp if adding a second
pref("plugins.navigator.hidden_ctp_plugin", "");

// The default value for nsIPluginTag.enabledState (STATE_ENABLED = 2)
pref("plugin.default.state", 2);

// The MIME type that should bind to legacy java-specific invocations like
// <applet> and <object data="java:foo">. Setting this to a non-java MIME type
// is undefined behavior.
pref("plugin.java.mime", "application/x-java-vm");

// How long in minutes we will allow a plugin to work after the user has chosen
// to allow it "now"
pref("plugin.sessionPermissionNow.intervalInMinutes", 60);
// How long in days we will allow a plugin to work after the user has chosen
// to allow it persistently.
pref("plugin.persistentPermissionAlways.intervalInDays", 90);

// This pref can take 3 possible string values:
// "always"     - always use favor fallback mode
// "follow-ctp" - activate if ctp is active for the given
//                plugin object (could be due to a plugin-wide
//                setting or a site-specific setting)
// "never"      - never use favor fallback mode
pref("plugins.favorfallback.mode", "never");

// A comma-separated list of rules to follow when deciding
// whether an object has been provided with good fallback content.
// The valid values can be found at nsObjectLoadingContent::HasGoodFallback.
pref("plugins.favorfallback.rules", "");


// Set IPC timeouts for plugins and tabs, except in leak-checking and
// dynamic analysis builds.  (NS_FREE_PERMANENT_DATA is C++ only, so
// approximate its definition here.)
//@line 3189 "z:\build\build\src\modules\libpref\init\all.js"
// How long a plugin is allowed to process a synchronous IPC message
// before we consider it "hung".
pref("dom.ipc.plugins.timeoutSecs", 45);
// How long a plugin process will wait for a response from the parent
// to a synchronous request before terminating itself. After this
// point the child assumes the parent is hung. Currently disabled.
pref("dom.ipc.plugins.parentTimeoutSecs", 0);
// How long a plugin in e10s is allowed to process a synchronous IPC
// message before we notify the chrome process of a hang.
pref("dom.ipc.plugins.contentTimeoutSecs", 10);
// How long a plugin launch is allowed to take before
// we consider it failed.
pref("dom.ipc.plugins.processLaunchTimeoutSecs", 45);
//@line 3203 "z:\build\build\src\modules\libpref\init\all.js"
// How long a plugin is allowed to process a synchronous IPC message
// before we display the plugin hang UI
pref("dom.ipc.plugins.hangUITimeoutSecs", 11);
// Minimum time that the plugin hang UI will be displayed
pref("dom.ipc.plugins.hangUIMinDisplaySecs", 10);
//@line 3209 "z:\build\build\src\modules\libpref\init\all.js"
// How long a content process can take before closing its IPC channel
// after shutdown is initiated.  If the process exceeds the timeout,
// we fear the worst and kill it.
pref("dom.ipc.tabs.shutdownTimeoutSecs", 5);
//@line 3225 "z:\build\build\src\modules\libpref\init\all.js"

pref("dom.ipc.plugins.flash.disable-protected-mode", false);

pref("dom.ipc.plugins.flash.subprocess.crashreporter.enabled", true);
pref("dom.ipc.plugins.reportCrashURL", true);

// How long we wait before unloading an idle plugin process.
// Defaults to 30 seconds.
pref("dom.ipc.plugins.unloadTimeoutSecs", 30);

// Allow Flash async drawing mode in 64-bit release builds
pref("dom.ipc.plugins.asyncdrawing.enabled", true);
// Force the accelerated direct path for a subset of Flash wmode values
pref("dom.ipc.plugins.forcedirect.enabled", true);

// Enable multi by default for Nightly and DevEdition only.
// For Beta and Release builds, multi is controlled by the e10srollout addon.
//@line 3243 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.ipc.processCount", 1);
//@line 3247 "z:\build\build\src\modules\libpref\init\all.js"

// Default to allow only one file:// URL content process.
pref("dom.ipc.processCount.file", 1);

// WebExtensions only support a single extension process.
pref("dom.ipc.processCount.extension", 1);

// Disable support for SVG
pref("svg.disabled", false);

// Override default dom.ipc.processCount for some remote content process types.
pref("dom.ipc.processCount.webLargeAllocation", 10);

// Enable the Large-Allocation header
pref("dom.largeAllocationHeader.enabled", true);

// Pref to control whether we use separate content processes for top-level load
// of file:// URIs.
pref("browser.tabs.remote.separateFileUriProcess", true);

// Pref that enables top level web content pages that are opened from file://
// URI pages to run in the file content process.
// This has been added in case breaking any window references between these
// sorts of pages, which we have to do when we run them in the normal web
// content process, causes compatibility issues.
pref("browser.tabs.remote.allowLinkedWebInFileUriProcess", true);

// Enable caching of Moz2D Path objects for SVG geometry elements
pref("svg.path-caching.enabled", true);

// Enable the use of display-lists for SVG hit-testing and painting.
pref("svg.display-lists.hit-testing.enabled", true);
pref("svg.display-lists.painting.enabled", true);

// Is support for the SVG 2 paint-order property enabled?
pref("svg.paint-order.enabled", true);

// Is support for the <marker orient="auto-start-reverse"> feature enabled?
pref("svg.marker-improvements.enabled", true);

// Is support for the new getBBox method from SVG 2 enabled?
// See https://svgwg.org/svg2-draft/single-page.html#types-SVGBoundingBoxOptions
pref("svg.new-getBBox.enabled", false);

pref("svg.transform-box.enabled", true);

//@line 3299 "z:\build\build\src\modules\libpref\init\all.js"
pref("svg.context-properties.content.enabled", false);

// Default font types and sizes by locale
pref("font.default.ar", "sans-serif");
pref("font.minimum-size.ar", 0);
pref("font.size.variable.ar", 16);
pref("font.size.fixed.ar", 13);

pref("font.default.el", "serif");
pref("font.minimum-size.el", 0);
pref("font.size.variable.el", 16);
pref("font.size.fixed.el", 13);

pref("font.default.he", "sans-serif");
pref("font.minimum-size.he", 0);
pref("font.size.variable.he", 16);
pref("font.size.fixed.he", 13);

pref("font.default.ja", "sans-serif");
pref("font.minimum-size.ja", 0);
pref("font.size.variable.ja", 16);
pref("font.size.fixed.ja", 16);

pref("font.default.ko", "sans-serif");
pref("font.minimum-size.ko", 0);
pref("font.size.variable.ko", 16);
pref("font.size.fixed.ko", 16);

pref("font.default.th", "sans-serif");
pref("font.minimum-size.th", 0);
pref("font.size.variable.th", 16);
pref("font.size.fixed.th", 13);

pref("font.default.x-cyrillic", "serif");
pref("font.minimum-size.x-cyrillic", 0);
pref("font.size.variable.x-cyrillic", 16);
pref("font.size.fixed.x-cyrillic", 13);

pref("font.default.x-devanagari", "serif");
pref("font.minimum-size.x-devanagari", 0);
pref("font.size.variable.x-devanagari", 16);
pref("font.size.fixed.x-devanagari", 13);

pref("font.default.x-tamil", "serif");
pref("font.minimum-size.x-tamil", 0);
pref("font.size.variable.x-tamil", 16);
pref("font.size.fixed.x-tamil", 13);

pref("font.default.x-armn", "serif");
pref("font.minimum-size.x-armn", 0);
pref("font.size.variable.x-armn", 16);
pref("font.size.fixed.x-armn", 13);

pref("font.default.x-beng", "serif");
pref("font.minimum-size.x-beng", 0);
pref("font.size.variable.x-beng", 16);
pref("font.size.fixed.x-beng", 13);

pref("font.default.x-cans", "serif");
pref("font.minimum-size.x-cans", 0);
pref("font.size.variable.x-cans", 16);
pref("font.size.fixed.x-cans", 13);

pref("font.default.x-ethi", "serif");
pref("font.minimum-size.x-ethi", 0);
pref("font.size.variable.x-ethi", 16);
pref("font.size.fixed.x-ethi", 13);

pref("font.default.x-geor", "serif");
pref("font.minimum-size.x-geor", 0);
pref("font.size.variable.x-geor", 16);
pref("font.size.fixed.x-geor", 13);

pref("font.default.x-gujr", "serif");
pref("font.minimum-size.x-gujr", 0);
pref("font.size.variable.x-gujr", 16);
pref("font.size.fixed.x-gujr", 13);

pref("font.default.x-guru", "serif");
pref("font.minimum-size.x-guru", 0);
pref("font.size.variable.x-guru", 16);
pref("font.size.fixed.x-guru", 13);

pref("font.default.x-khmr", "serif");
pref("font.minimum-size.x-khmr", 0);
pref("font.size.variable.x-khmr", 16);
pref("font.size.fixed.x-khmr", 13);

pref("font.default.x-mlym", "serif");
pref("font.minimum-size.x-mlym", 0);
pref("font.size.variable.x-mlym", 16);
pref("font.size.fixed.x-mlym", 13);

pref("font.default.x-orya", "serif");
pref("font.minimum-size.x-orya", 0);
pref("font.size.variable.x-orya", 16);
pref("font.size.fixed.x-orya", 13);

pref("font.default.x-telu", "serif");
pref("font.minimum-size.x-telu", 0);
pref("font.size.variable.x-telu", 16);
pref("font.size.fixed.x-telu", 13);

pref("font.default.x-knda", "serif");
pref("font.minimum-size.x-knda", 0);
pref("font.size.variable.x-knda", 16);
pref("font.size.fixed.x-knda", 13);

pref("font.default.x-sinh", "serif");
pref("font.minimum-size.x-sinh", 0);
pref("font.size.variable.x-sinh", 16);
pref("font.size.fixed.x-sinh", 13);

pref("font.default.x-tibt", "serif");
pref("font.minimum-size.x-tibt", 0);
pref("font.size.variable.x-tibt", 16);
pref("font.size.fixed.x-tibt", 13);

pref("font.default.x-unicode", "serif");
pref("font.minimum-size.x-unicode", 0);
pref("font.size.variable.x-unicode", 16);
pref("font.size.fixed.x-unicode", 13);

pref("font.default.x-western", "serif");
pref("font.minimum-size.x-western", 0);
pref("font.size.variable.x-western", 16);
pref("font.size.fixed.x-western", 13);

pref("font.default.zh-CN", "sans-serif");
pref("font.minimum-size.zh-CN", 0);
pref("font.size.variable.zh-CN", 16);
pref("font.size.fixed.zh-CN", 16);

pref("font.default.zh-HK", "sans-serif");
pref("font.minimum-size.zh-HK", 0);
pref("font.size.variable.zh-HK", 16);
pref("font.size.fixed.zh-HK", 16);

pref("font.default.zh-TW", "sans-serif");
pref("font.minimum-size.zh-TW", 0);
pref("font.size.variable.zh-TW", 16);
pref("font.size.fixed.zh-TW", 16);

// mathml.css sets font-size to "inherit" and font-family to "serif" so only
// font.name.*.x-math and font.minimum-size.x-math are really relevant.
pref("font.default.x-math", "serif");
pref("font.minimum-size.x-math", 0);
pref("font.size.variable.x-math", 16);
pref("font.size.fixed.x-math", 13);

/*
 * A value greater than zero enables font size inflation for
 * pan-and-zoom UIs, so that the fonts in a block are at least the size
 * that, if a block's width is scaled to match the device's width, the
 * fonts in the block are big enough that at most the pref value ems of
 * text fit in *the width of the device*.
 *
 * When both this pref and the next are set, the larger inflation is
 * used.
 */
pref("font.size.inflation.emPerLine", 0);
/*
 * A value greater than zero enables font size inflation for
 * pan-and-zoom UIs, so that if a block's width is scaled to match the
 * device's width, the fonts in a block are at least the font size
 * given.  The value given is in twips, i.e., 1/20 of a point, or 1/1440
 * of an inch.
 *
 * When both this pref and the previous are set, the larger inflation is
 * used.
 */
pref("font.size.inflation.minTwips", 0);
/*
 * In products with multi-mode pan-and-zoom and non-pan-and-zoom UIs,
 * this pref forces font inflation to always be enabled in all modes.
 * That is, any heuristics used to detect pan-and-zoom
 * vs. non-pan-and-zoom modes are disabled and all content is treated
 * as pan-and-zoom mode wrt font inflation.
 *
 * This pref has no effect if font inflation is not enabled through
 * either of the prefs above.  It has no meaning in single-mode UIs.
 */
pref("font.size.inflation.forceEnabled", false);
/*
 * In products with multi-mode pan-and-zoom and non-pan-and-zoom UIs,
 * this pref disables font inflation in master-process contexts where
 * existing heuristics can't be used determine enabled-ness.
 *
 * This pref has no effect if font inflation is not enabled through
 * either of the prefs above.  The "forceEnabled" pref above overrides
 * this pref.
 */
pref("font.size.inflation.disabledInMasterProcess", false);
/*
 * Since the goal of font size inflation is to avoid having to
 * repeatedly scroll side to side to read a block of text, and there are
 * a number of page layouts where a relatively small chunk of text is
 * better of not being inflated according to the same algorithm we use
 * for larger chunks of text, we want a threshold for an amount of text
 * that triggers font size inflation.  This preference controls that
 * threshold.
 *
 * It controls the threshold used within an *approximation* of the
 * number of lines of text we use.  In particular, if we assume that
 * each character (collapsing collapsible whitespace) has a width the
 * same as the em-size of the font (when, normally, it's actually quite
 * a bit smaller on average), this preference gives the percentage of a
 * number of lines of text we'd need to trigger inflation.  This means
 * that a percentage of 100 means that we'd need a number of characters
 * (we know the font size and the width) equivalent to one line of
 * square text (which is actually a lot less than a real line of text).
 *
 * A value of 0 means there's no character length threshold.
 */
pref("font.size.inflation.lineThreshold", 400);

/*
 * Defines the font size inflation mapping intercept parameter.
 *
 * Font size inflation computes a minimum font size, m, based on
 * other preferences (see font.size.inflation.minTwips and
 * font.size.inflation.emPerLine, above) and the width of the
 * frame in which the text resides. Using this minimum, a specified
 * font size, s, is mapped to an inflated font size, i, using an
 * equation that varies depending on the value of the font size
 * inflation mapping intercept parameter, P:
 *
 * If the intercept parameter is negative, then the following mapping
 * function is used:
 *
 * i = m + s
 *
 * If the intercept parameter is non-negative, then the mapping function
 * is a function such that its graph meets the graph of i = s at the
 * point where both i and s are (1 + P/2) * m for values of s that are
 * large enough. This means that when s=0, i is always equal to m.
 */
pref("font.size.inflation.mappingIntercept", 1);

/*
 * This controls the percentage that fonts will be inflated, if font
 * size inflation is enabled. Essentially, if we have a specified font
 * size, s, and an inflated font size, i, this specifies that the ratio
 * i/s * 100 should never exceed the value of this preference.
 *
 * In order for this preference to have any effect, its value must be
 * greater than 100, since font inflation can never decrease the ratio
 * i/s.
 */
pref("font.size.inflation.maxRatio", 0);

/**
 * This setting corresponds to a global text zoom setting affecting
 * all content that is not already subject to font size inflation.
 * It is interpreted as a percentage value that is applied on top
 * of the document's current text zoom setting.
 *
 * The resulting total zoom factor (text zoom * system font scale)
 * will be limited by zoom.minPercent and maxPercent.
 */
pref("font.size.systemFontScale", 100);

/*
 * When enabled, the touch.radius and mouse.radius prefs allow events to be dispatched
 * to nearby elements that are sensitive to the event. See PositionedEventTargeting.cpp.
 * The 'mm' prefs define a rectangle around the nominal event target point within which
 * we will search for suitable elements. 'visitedWeight' is a percentage weight;
 * a value > 100 makes a visited link be treated as further away from the event target
 * than it really is, while a value < 100 makes a visited link be treated as closer
 * to the event target than it really is.
 */
pref("ui.touch.radius.enabled", false);
pref("ui.touch.radius.leftmm", 8);
pref("ui.touch.radius.topmm", 12);
pref("ui.touch.radius.rightmm", 8);
pref("ui.touch.radius.bottommm", 4);
pref("ui.touch.radius.visitedWeight", 120);

pref("ui.mouse.radius.enabled", false);
pref("ui.mouse.radius.leftmm", 8);
pref("ui.mouse.radius.topmm", 12);
pref("ui.mouse.radius.rightmm", 8);
pref("ui.mouse.radius.bottommm", 4);
pref("ui.mouse.radius.visitedWeight", 120);

// When true, the ui.mouse.radius.* prefs will only affect simulated mouse events generated by touch input.
// When false, the prefs will be used for all mouse events.
pref("ui.mouse.radius.inputSource.touchOnly", true);

//@line 3589 "z:\build\build\src\modules\libpref\init\all.js"

pref("font.name-list.serif.ar", "Times New Roman");
pref("font.name-list.sans-serif.ar", "Segoe UI, Tahoma, Arial");
pref("font.name-list.monospace.ar", "Courier New");
pref("font.name-list.cursive.ar", "Comic Sans MS");

pref("font.name-list.serif.el", "Times New Roman");
pref("font.name-list.sans-serif.el", "Arial");
pref("font.name-list.monospace.el", "Courier New");
pref("font.name-list.cursive.el", "Comic Sans MS");

pref("font.name-list.serif.he", "Narkisim, David");
pref("font.name-list.sans-serif.he", "Arial");
pref("font.name-list.monospace.he", "Fixed Miriam Transparent, Miriam Fixed, Rod, Courier New");
pref("font.name-list.cursive.he", "Guttman Yad, Ktav, Arial");

//@line 3610 "z:\build\build\src\modules\libpref\init\all.js"
pref("font.name-list.serif.ja", "MS PMincho, MS Mincho, MS PGothic, MS Gothic,Meiryo");
pref("font.name-list.sans-serif.ja", "MS PGothic, MS Gothic, MS PMincho, MS Mincho,Meiryo");
pref("font.name-list.monospace.ja", "MS Gothic, MS Mincho, MS PGothic, MS PMincho,Meiryo");
//@line 3614 "z:\build\build\src\modules\libpref\init\all.js"

pref("font.name-list.serif.ko", "Batang, Gulim");
pref("font.name-list.sans-serif.ko", "Gulim");
pref("font.name-list.monospace.ko", "GulimChe");
pref("font.name-list.cursive.ko", "Gungsuh");

pref("font.name-list.serif.th", "Tahoma");
pref("font.name-list.sans-serif.th", "Tahoma");
pref("font.name-list.monospace.th", "Tahoma");
pref("font.name-list.cursive.th", "Tahoma");

pref("font.name-list.serif.x-cyrillic", "Times New Roman");
pref("font.name-list.sans-serif.x-cyrillic", "Arial");
pref("font.name-list.monospace.x-cyrillic", "Courier New");
pref("font.name-list.cursive.x-cyrillic", "Comic Sans MS");

pref("font.name-list.serif.x-unicode", "Times New Roman");
pref("font.name-list.sans-serif.x-unicode", "Arial");
pref("font.name-list.monospace.x-unicode", "Courier New");
pref("font.name-list.cursive.x-unicode", "Comic Sans MS");

pref("font.name-list.serif.x-western", "Times New Roman");
pref("font.name-list.sans-serif.x-western", "Arial");
pref("font.name-list.monospace.x-western", "Courier New");
pref("font.name-list.cursive.x-western", "Comic Sans MS");

pref("font.name-list.serif.zh-CN", "SimSun, MS Song, SimSun-ExtB");
pref("font.name-list.sans-serif.zh-CN", "Microsoft YaHei, SimHei");
pref("font.name-list.monospace.zh-CN", "SimSun, MS Song, SimSun-ExtB");
pref("font.name-list.cursive.zh-CN", "KaiTi, KaiTi_GB2312");

// Per Taiwanese users' demand. They don't want to use TC fonts for
// rendering Latin letters. (bug 88579)
pref("font.name-list.serif.zh-TW", "Times New Roman, PMingLiu, MingLiU, MingLiU-ExtB");
pref("font.name-list.sans-serif.zh-TW", "Arial, PMingLiU, MingLiU, MingLiU-ExtB");
pref("font.name-list.monospace.zh-TW", "MingLiU, MingLiU-ExtB");
pref("font.name-list.cursive.zh-TW", "DFKai-SB");

// hkscsm3u.ttf (HKSCS-2001) :  http://www.microsoft.com/hk/hkscs
// Hong Kong users have the same demand about glyphs for Latin letters (bug 88579)
pref("font.name-list.serif.zh-HK", "Times New Roman, MingLiu_HKSCS, Ming(for ISO10646), MingLiU, MingLiU_HKSCS-ExtB");
pref("font.name-list.sans-serif.zh-HK", "Arial, MingLiU_HKSCS, Ming(for ISO10646), MingLiU, MingLiU_HKSCS-ExtB");
pref("font.name-list.monospace.zh-HK", "MingLiU_HKSCS, Ming(for ISO10646), MingLiU, MingLiU_HKSCS-ExtB");
pref("font.name-list.cursive.zh-HK", "DFKai-SB");

pref("font.name-list.serif.x-devanagari", "Kokila, Raghindi");
pref("font.name-list.sans-serif.x-devanagari", "Nirmala UI, Mangal");
pref("font.name-list.monospace.x-devanagari", "Mangal, Nirmala UI");

pref("font.name-list.serif.x-tamil", "Latha");
pref("font.name-list.monospace.x-tamil", "Latha");

//@line 3667 "z:\build\build\src\modules\libpref\init\all.js"

pref("font.name-list.serif.x-armn", "Sylfaen");
pref("font.name-list.sans-serif.x-armn", "Arial AMU");
pref("font.name-list.monospace.x-armn", "Arial AMU");

pref("font.name-list.serif.x-beng", "Vrinda, Akaash, Likhan, Ekushey Punarbhaba");
pref("font.name-list.sans-serif.x-beng", "Vrinda, Akaash, Likhan, Ekushey Punarbhaba");
pref("font.name-list.monospace.x-beng", "Mitra Mono, Likhan, Mukti Narrow");

pref("font.name-list.serif.x-cans", "Aboriginal Serif, BJCree Uni");
pref("font.name-list.sans-serif.x-cans", "Aboriginal Sans");
pref("font.name-list.monospace.x-cans", "Aboriginal Sans, OskiDakelh, Pigiarniq, Uqammaq");

pref("font.name-list.serif.x-ethi", "Visual Geez Unicode, Visual Geez Unicode Agazian");
pref("font.name-list.sans-serif.x-ethi", "GF Zemen Unicode");
pref("font.name-list.monospace.x-ethi", "Ethiopia Jiret");
pref("font.name-list.cursive.x-ethi", "Visual Geez Unicode Title");

pref("font.name-list.serif.x-geor", "Sylfaen, BPG Paata Khutsuri U, TITUS Cyberbit Basic");
pref("font.name-list.sans-serif.x-geor", "BPG Classic 99U");
pref("font.name-list.monospace.x-geor", "BPG Classic 99U");

pref("font.name-list.serif.x-gujr", "Shruti");
pref("font.name-list.sans-serif.x-gujr", "Shruti");
pref("font.name-list.monospace.x-gujr", "Shruti");

pref("font.name-list.serif.x-guru", "Raavi, Saab");
pref("font.name-list.sans-serif.x-guru", "");
pref("font.name-list.monospace.x-guru", "Raavi, Saab");

pref("font.name-list.serif.x-khmr", "PhnomPenh OT,.Mondulkiri U GR 1.5, Khmer OS");
pref("font.name-list.sans-serif.x-khmr", "Khmer OS");
pref("font.name-list.monospace.x-khmr", "Khmer OS, Khmer OS System");

pref("font.name-list.serif.x-mlym", "Rachana_w01, AnjaliOldLipi, Kartika, ThoolikaUnicode");
pref("font.name-list.sans-serif.x-mlym", "Rachana_w01, AnjaliOldLipi, Kartika, ThoolikaUnicode");
pref("font.name-list.monospace.x-mlym", "Rachana_w01, AnjaliOldLipi, Kartika, ThoolikaUnicode");

pref("font.name-list.serif.x-orya", "ori1Uni, Kalinga");
pref("font.name-list.sans-serif.x-orya", "ori1Uni, Kalinga");
pref("font.name-list.monospace.x-orya", "ori1Uni, Kalinga");

pref("font.name-list.serif.x-telu", "Gautami, Akshar Unicode");
pref("font.name-list.sans-serif.x-telu", "Gautami, Akshar Unicode");
pref("font.name-list.monospace.x-telu", "Gautami, Akshar Unicode");

pref("font.name-list.serif.x-knda", "Tunga, AksharUnicode");
pref("font.name-list.sans-serif.x-knda", "Tunga, AksharUnicode");
pref("font.name-list.monospace.x-knda", "Tunga, AksharUnicode");

pref("font.name-list.serif.x-sinh", "Iskoola Pota, AksharUnicode");
pref("font.name-list.sans-serif.x-sinh", "Iskoola Pota, AksharUnicode");
pref("font.name-list.monospace.x-sinh", "Iskoola Pota, AksharUnicode");

pref("font.name-list.serif.x-tibt", "Tibetan Machine Uni, Jomolhari, Microsoft Himalaya");
pref("font.name-list.sans-serif.x-tibt", "Tibetan Machine Uni, Jomolhari, Microsoft Himalaya");
pref("font.name-list.monospace.x-tibt", "Tibetan Machine Uni, Jomolhari, Microsoft Himalaya");

pref("font.minimum-size.th", 10);

pref("font.default.x-devanagari", "sans-serif");

pref("font.name-list.serif.x-math", "Latin Modern Math, STIX Two Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, Times New Roman");
pref("font.name-list.sans-serif.x-math", "Arial");
pref("font.name-list.monospace.x-math", "Courier New");
pref("font.name-list.cursive.x-math", "Comic Sans MS");

// ClearType tuning parameters for directwrite/d2d.
//
// Allows overriding of underlying registry values in:
//   HKCU/Software/Microsoft/Avalon.Graphics/<display> (contrast and level)
//   HKLM/Software/Microsoft/Avalon.Graphics/<display> (gamma, pixel structure)
// and selection of the ClearType/antialiasing mode.
//
// A value of -1 implies use the default value, otherwise value ranges
// follow registry settings:
//   gamma [1000, 2200]  default: based on screen, typically 2200 (== 2.2)
//   enhanced contrast [0, 1000] default: 50
//   cleartype level [0, 100] default: 100
//   pixel structure [0, 2] default: 0 (flat/RGB/BGR)
//   rendering mode [0, 5] default: 0
//     0 = use default for font & size;
//     1 = aliased;
//     2 = GDI Classic;
//     3 = GDI Natural Widths;
//     4 = Natural;
//     5 = Natural Symmetric
//
// See:
//   http://msdn.microsoft.com/en-us/library/aa970267.aspx
//   http://msdn.microsoft.com/en-us/library/dd368190%28v=VS.85%29.aspx
// Note: DirectWrite uses the "Enhanced Contrast Level" value rather than the
// "Text Contrast Level" value

pref("gfx.font_rendering.cleartype_params.gamma", -1);
pref("gfx.font_rendering.cleartype_params.enhanced_contrast", -1);
pref("gfx.font_rendering.cleartype_params.cleartype_level", -1);
pref("gfx.font_rendering.cleartype_params.pixel_structure", -1);
pref("gfx.font_rendering.cleartype_params.rendering_mode", -1);

// A comma-separated list of font family names. Fonts in these families will
// be forced to use "GDI Classic" ClearType mode, provided the value
// of gfx.font_rendering.cleartype_params.rendering_mode is -1
// (i.e. a specific rendering_mode has not been explicitly set).
// Currently we apply this setting to the sans-serif Microsoft "core Web fonts".
pref("gfx.font_rendering.cleartype_params.force_gdi_classic_for_families",
     "Arial,Consolas,Courier New,Microsoft Sans Serif,Segoe UI,Tahoma,Trebuchet MS,Verdana");
// The maximum size at which we will force GDI classic mode using
// force_gdi_classic_for_families.
pref("gfx.font_rendering.cleartype_params.force_gdi_classic_max_size", 15);

pref("ui.key.menuAccessKeyFocuses", true);

// override double-click word selection behavior.
pref("layout.word_select.eat_space_to_next_word", true);

// scrollbar snapping region
pref("slider.snapMultiplier", 6);

// print_extra_margin enables platforms to specify an extra gap or margin
// around the content of the page for Print Preview only
pref("print.print_extra_margin", 90); // twips (90 twips is an eigth of an inch)

// Whether to extend the native dialog with information on printing frames.
pref("print.extend_native_print_dialog", true);

// Locate plugins by the directories specified in the Windows registry for PLIDs
// Which is currently HKLM\Software\MozillaPlugins\xxxPLIDxxx\Path
pref("plugin.scan.plid.all", true);

// Whether sending WM_MOUSEWHEEL and WM_MOUSEHWHEEL to plugins on Windows.
pref("plugin.mousewheel.enabled", true);

// Switch the keyboard layout per window
pref("intl.keyboard.per_window_layout", false);

//@line 3804 "z:\build\build\src\modules\libpref\init\all.js"
// Enable/Disable TSF support on Vista or later.
pref("intl.tsf.enable", true);

// Support IMEs implemented with IMM in TSF mode.
pref("intl.tsf.support_imm", true);

// This is referred only when both "intl.tsf.enable" and "intl.tsf.support_imm"
// are true.  When this is true, default IMC is associated with focused window
// only when active keyboard layout is a legacy IMM-IME.
pref("intl.tsf.associate_imc_only_when_imm_ime_is_active", false);

// Enables/Disables hack for specific TIP.

// Whether creates native caret for ATOK or not.
pref("intl.tsf.hack.atok.create_native_caret", true);
// Whether use available composition string rect for result of
// ITfContextView::GetTextExt() even if the specified range is same as the
// range of composition string but some character rects of them are not
// available.  Note that this is ignored if active ATOK is or older than 2016
// and create_native_caret is true.
pref("intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string", true);
// Whether use composition start position for the result of
// ITfContextView::GetTextExt() if the specified range is larger than
// composition start offset.
// For Free ChangJie 2010
pref("intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error", true);
// For Microsoft Pinyin and Microsoft Wubi
pref("intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error", true);
// For Microsoft ChangJie and Microsoft Quick
pref("intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error", true);
// Whether use previous character rect for the result of
// ITfContextView::GetTextExt() if the specified range is the first character
// of selected clause of composition string.
pref("intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_first_char", true);
// Whether use previous character rect for the result of
// ITfContextView::GetTextExt() if the specified range is the caret of
// composition string.
pref("intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret", true);
// Whether hack ITextStoreACP::QueryInsert() or not.  The method should return
// new selection after specified length text is inserted at specified range.
// However, Microsoft's some Chinese TIPs expect that the result is same as
// specified range.  If following prefs are true, ITextStoreACP::QueryInsert()
// returns specified range only when one of the TIPs is active.
// For Microsoft Pinyin and Microsoft Wubi
pref("intl.tsf.hack.ms_simplified_chinese.query_insert_result", true);
// For Microsoft ChangJie and Microsoft Quick
pref("intl.tsf.hack.ms_traditional_chinese.query_insert_result", true);
//@line 3852 "z:\build\build\src\modules\libpref\init\all.js"

// If composition_font is set, Gecko sets the font to IME.  IME may use
// the fonts on their window like candidate window.  If they are empty,
// Gecko uses the system default font which is set to the IM context.
// The font name must not start with '@'.  When the writing mode is vertical,
// Gecko inserts '@' to the start of the font name automatically.
// FYI: Changing these prefs requires to restart.
pref("intl.imm.composition_font", "");

// Japanist 2003's candidate window is broken if the font is "@System" which
// is default composition font for vertical writing mode.
// You can specify font to use on candidate window of Japanist 2003.
// This value must not start with '@'.
// FYI: Changing this pref requires to restart.
pref("intl.imm.composition_font.japanist_2003", "MS PGothic");

// Even if IME claims that they support vertical writing mode but it may not
// support vertical writing mode for its candidate window.  This pref allows
// to ignore the claim.
// FYI: Changing this pref requires to restart.
pref("intl.imm.vertical_writing.always_assume_not_supported", false);

// We cannot retrieve active IME name with IMM32 API if a TIP of TSF is active.
// This pref can specify active IME name when Japanese TIP is active.
// For example:
//   Google Japanese Input: "Google 日本語入力 IMM32 モジュール"
//   ATOK 2011: "ATOK 2011" (similarly, e.g., ATOK 2013 is "ATOK 2013")
pref("intl.imm.japanese.assume_active_tip_name_as", "");

// See bug 448927, on topmost panel, some IMEs are not usable on Windows.
pref("ui.panel.default_level_parent", false);

pref("mousewheel.system_scroll_override_on_root_content.enabled", true);

// Enable system settings cache for mouse wheel message handling.
// Note that even if this pref is set to true, Gecko may not cache the system
// settings if Gecko detects that the cache won't be refreshed properly when
// the settings are changed.
pref("mousewheel.system_settings_cache.enabled", true);

// This is a pref to test system settings cache for mouse wheel message
// handling.  If this is set to true, Gecko forcibly use the cache.
pref("mousewheel.system_settings_cache.force_enabled", false);

// High resolution scrolling with supported mouse drivers on Vista or later.
pref("mousewheel.enable_pixel_scrolling", true);

// If your mouse drive sends WM_*SCROLL messages when you turn your mouse wheel,
// set this to true.  Then, gecko processes them as mouse wheel messages.
pref("mousewheel.emulate_at_wm_scroll", false);

// Enables or disabled the TrackPoint hack, -1 is autodetect, 0 is off,
// and 1 is on.  Set this to 1 if TrackPoint scrolling is not working.
pref("ui.trackpoint_hack.enabled", -1);

// Setting this to a non-empty string overrides the Win32 "window class" used
// for "normal" windows. Setting this to MozillaUIWindowClass might make
// some trackpad drivers behave better.
pref("ui.window_class_override", "");

// Enables or disables the Elantech gesture hacks.  -1 is autodetect, 0 is off,
// and 1 is on.  Set this to 1 if three-finger swipe gestures do not cause
// page back/forward actions, or if pinch-to-zoom does not work.
pref("ui.elantech_gesture_hacks.enabled", -1);

// Show the Windows on-screen keyboard (osk.exe) when a text field is focused.
pref("ui.osk.enabled", true);
// Only show the on-screen keyboard if there are no physical keyboards attached
// to the device.
pref("ui.osk.detect_physical_keyboard", true);
// Path to TabTip.exe on local machine. Cached for performance reasons.
pref("ui.osk.on_screen_keyboard_path", "");
// Only try to show the on-screen keyboard on Windows 10 and later. Setting
// this pref to false will allow the OSK to show on Windows 8 and 8.1.
pref("ui.osk.require_win10", false);
// This pref stores the "reason" that the on-screen keyboard was either
// shown or not shown when focus is moved to an editable text field. It is
// used to help debug why the keyboard is either not appearing when expected
// or appearing when it is not expected.
pref("ui.osk.debug.keyboardDisplayReason", "");

//@line 3935 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4157 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4218 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4416 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4437 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4549 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4569 "z:\build\build\src\modules\libpref\init\all.js"

// Login Manager prefs
pref("signon.rememberSignons",              true);
pref("signon.rememberSignons.visibilityToggle", true);
pref("signon.autofillForms",                true);
pref("signon.autofillForms.http",           false);
pref("signon.autologin.proxy",              false);
pref("signon.formlessCapture.enabled",      true);
pref("signon.storeWhenAutocompleteOff",     true);
pref("signon.debug",                        false);
pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
pref("signon.schemeUpgrades",               false);
// This temporarily prevents the master password to reprompt for autocomplete.
pref("signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes

// Satchel (Form Manager) prefs
pref("browser.formfill.debug",            false);
pref("browser.formfill.enable",           true);
pref("browser.formfill.expire_days",      180);
pref("browser.formfill.agedWeight",       2);
pref("browser.formfill.bucketSize",       1);
pref("browser.formfill.maxTimeGroupings", 25);
pref("browser.formfill.timeGroupingSize", 604800);
pref("browser.formfill.boundaryWeight",   25);
pref("browser.formfill.prefixWeight",     5);

// Zoom prefs
pref("browser.zoom.full", false);
pref("zoom.minPercent", 30);
pref("zoom.maxPercent", 300);
pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3");

//
// Image-related prefs
//

// The maximum size, in bytes, of the decoded images we cache
pref("image.cache.size", 5242880);

// A weight, from 0-1000, to place on time when comparing to size.
// Size is given a weight of 1000 - timeweight.
pref("image.cache.timeweight", 500);

// Decode all images automatically on load, ignoring our normal heuristics.
pref("image.decode-immediately.enabled", false);

// Whether we attempt to downscale images during decoding.
pref("image.downscale-during-decode.enabled", true);

// The default Accept header sent for images loaded over HTTP(S)
pref("image.http.accept", "*/*");

// The threshold for inferring that changes to an <img> element's |src|
// attribute by JavaScript represent an animation, in milliseconds. If the |src|
// attribute is changing more frequently than this value, then we enter a
// special "animation mode" which is designed to eliminate flicker. Set to 0 to
// disable.
pref("image.infer-src-animation.threshold-ms", 2000);

// Whether the network request priority should be adjusted according
// the layout and view frame position of each particular image.
pref("image.layout_network_priority", true);

//
// Image memory management prefs
//

// Discards inactive image frames and re-decodes them on demand from
// compressed data.
pref("image.mem.discardable", true);

// Discards inactive image frames of _animated_ images and re-decodes them on
// demand from compressed data. Has no effect if image.mem.discardable is false.
pref("image.mem.animated.discardable", true);

// Decodes images into shared memory to allow direct use in separate
// rendering processes.
pref("image.mem.shared", false);

// Allows image locking of decoded image data in content processes.
pref("image.mem.allow_locking_in_content_processes", true);

// Chunk size for calls to the image decoders
pref("image.mem.decode_bytes_at_a_time", 16384);

// Minimum timeout for expiring unused images from the surface cache, in
// milliseconds. This controls how long we store cached temporary surfaces.
pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60s

// Maximum size for the surface cache, in kilobytes.
pref("image.mem.surfacecache.max_size_kb", 1048576); // 1GB

// The surface cache's size, within the constraints of the maximum size set
// above, is determined as a fraction of main memory size. The size factor is
// interpreted as a reciprocal, so a size factor of 4 means to use no more than
// 1/4 of main memory.  The default should be a good balance for most systems.
pref("image.mem.surfacecache.size_factor", 4);

// How much of the data in the surface cache is discarded when we get a memory
// pressure notification, as a fraction. The discard factor is interpreted as a
// reciprocal, so a discard factor of 1 means to discard everything in the
// surface cache on memory pressure, a discard factor of 2 means to discard half
// of the data, and so forth. The default should be a good balance for desktop
// and laptop systems, where we never discard visible images.
pref("image.mem.surfacecache.discard_factor", 1);

// How many threads we'll use for multithreaded decoding. If < 0, will be
// automatically determined based on the system's number of cores.
pref("image.multithreaded_decoding.limit", -1);

// Limit for the canvas image cache. 0 means we don't limit the size of the
// cache.
pref("canvas.image.cache.limit", 0);

// WebGL prefs
//@line 4688 "z:\build\build\src\modules\libpref\init\all.js"
pref("gl.msaa-level", 2);
//@line 4690 "z:\build\build\src\modules\libpref\init\all.js"
pref("gl.require-hardware", false);
//@line 4694 "z:\build\build\src\modules\libpref\init\all.js"
pref("gl.ignore-dx-interop2-blacklist", false);
pref("gl.use-tls-is-current", 0);

pref("webgl.force-enabled", false);
pref("webgl.disabled", false);
pref("webgl.disable-angle", false);
pref("webgl.disable-wgl", false);
pref("webgl.min_capability_mode", false);
pref("webgl.disable-extensions", false);
pref("webgl.msaa-force", false);
pref("webgl.prefer-16bpp", false);
pref("webgl.default-no-alpha", false);
pref("webgl.force-layers-readback", false);
pref("webgl.force-index-validation", false);
pref("webgl.lose-context-on-memory-pressure", false);
pref("webgl.can-lose-context-in-foreground", true);
pref("webgl.restore-context-when-visible", true);
pref("webgl.max-warnings-per-context", 32);
pref("webgl.enable-draft-extensions", false);
pref("webgl.enable-privileged-extensions", false);
pref("webgl.bypass-shader-validation", false);
pref("webgl.disable-fail-if-major-performance-caveat", false);
pref("webgl.disable-DOM-blit-uploads", false);
pref("webgl.allow-fb-invalidation", false);
pref("webgl.webgl2-compat-mode", false);

pref("webgl.perf.max-warnings", 0);
pref("webgl.perf.max-acceptable-fb-status-invals", 0);
pref("webgl.perf.spew-frame-allocs", true);

pref("webgl.enable-webgl2", true);

pref("webgl.enable-debug-renderer-info", true);
pref("webgl.renderer-string-override", "");
pref("webgl.vendor-string-override", "");

//@line 4731 "z:\build\build\src\modules\libpref\init\all.js"
pref("webgl.angle.try-d3d11", true);
pref("webgl.angle.force-d3d11", false);
pref("webgl.angle.force-warp", false);
pref("webgl.dxgl.enabled", true);
pref("webgl.dxgl.needs-finish", false);
//@line 4737 "z:\build\build\src\modules\libpref\init\all.js"

pref("gfx.offscreencanvas.enabled", false);

// Stagefright prefs
pref("stagefright.force-enabled", false);
pref("stagefright.disabled", false);

// sendbuffer of 0 means use OS default, sendbuffer unset means use
// gecko default which varies depending on windows version and is OS
// default on non windows
// pref("network.tcp.sendbuffer", 0);

// TCP Keepalive
pref("network.tcp.keepalive.enabled", true);
// Default idle time before first TCP keepalive probe; same time for interval
// between successful probes. Can be overridden in socket transport API.
// Win, Linux and Mac.
pref("network.tcp.keepalive.idle_time", 600); // seconds; 10 mins
// Default timeout for retransmission of unack'd keepalive probes.
// Win and Linux only; not configurable on Mac.
//@line 4758 "z:\build\build\src\modules\libpref\init\all.js"
pref("network.tcp.keepalive.retry_interval", 1); // seconds
//@line 4760 "z:\build\build\src\modules\libpref\init\all.js"
// Default maximum probe retransmissions.
// Linux only; not configurable on Win and Mac; fixed at 10 and 8 respectively.
//@line 4765 "z:\build\build\src\modules\libpref\init\all.js"

pref("network.tcp.tcp_fastopen_enable", false);
pref("network.tcp.tcp_fastopen_consecutive_failure_limit", 5);

// Whether to disable acceleration for all widgets.
pref("layers.acceleration.disabled", false);
// Preference that when switched at runtime will run a series of benchmarks
// and output the result to stderr.
pref("layers.bench.enabled", false);

//@line 4776 "z:\build\build\src\modules\libpref\init\all.js"
pref("layers.gpu-process.enabled", true);
pref("media.gpu-process-decoder", true);
pref("layers.gpu-process.allow-software", true);
//@line 4783 "z:\build\build\src\modules\libpref\init\all.js"

// Whether to force acceleration on, ignoring blacklists.
//@line 4793 "z:\build\build\src\modules\libpref\init\all.js"
pref("layers.acceleration.force-enabled", false);
//@line 4795 "z:\build\build\src\modules\libpref\init\all.js"

pref("layers.acceleration.draw-fps", false);

// Enable DEAA antialiasing for transformed layers in the compositor
//@line 4800 "z:\build\build\src\modules\libpref\init\all.js"
// Desktop prefs
pref("layers.deaa.enabled", true);
//@line 4806 "z:\build\build\src\modules\libpref\init\all.js"

pref("layers.dump", false);
//@line 4815 "z:\build\build\src\modules\libpref\init\all.js"
pref("layers.draw-borders", false);
pref("layers.draw-tile-borders", false);
pref("layers.draw-bigimage-borders", false);
pref("layers.enable-tiles", false);
pref("layers.single-tile.enabled", true);
pref("layers.low-precision-buffer", false);
pref("layers.progressive-paint", false);
pref("layers.tile-width", 256);
pref("layers.tile-height", 256);
pref("layers.child-process-shutdown", true);
// Max number of layers per container. See Overwrite in mobile prefs.
pref("layers.max-active", -1);
// If this is set the tile size will only be treated as a suggestion.
// On B2G we will round this to the stride of the underlying allocation.
// On any platform we may later use the screen size and ignore
// tile-width/tile-height entirely. Its recommended to turn this off
// if you change the tile size.
pref("layers.tiles.adjust", true);

// Compositor target frame rate. NOTE: If vsync is enabled the compositor
// frame rate will still be capped.
// -1 -> default (match layout.frame_rate or 60 FPS)
// 0  -> full-tilt mode: Recomposite even if not transaction occured.
pref("layers.offmainthreadcomposition.frame-rate", -1);

//@line 4846 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4850 "z:\build\build\src\modules\libpref\init\all.js"

// Whether to animate simple opacity and transforms on the compositor
pref("layers.offmainthreadcomposition.async-animations", true);

// Whether to log information about off main thread animations to stderr
pref("layers.offmainthreadcomposition.log-animations", false);

pref("layers.bufferrotation.enabled", true);

pref("layers.componentalpha.enabled", true);
pref("layers.draw-mask-debug", false);

pref("gfx.content.always-paint", false);

//@line 4867 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4875 "z:\build\build\src\modules\libpref\init\all.js"

pref("widget.window-transforms.disabled", false);

//@line 4879 "z:\build\build\src\modules\libpref\init\all.js"
// Whether to disable the automatic detection and use of direct2d.
pref("gfx.direct2d.disabled", false);

// Whether to attempt to enable Direct2D regardless of automatic detection or
// blacklisting
pref("gfx.direct2d.force-enabled", false);

pref("gfx.direct3d11.enable-debug-layer", false);
pref("gfx.direct3d11.break-on-error", false);

pref("layers.prefer-opengl", false);
//@line 4891 "z:\build\build\src\modules\libpref\init\all.js"

// Copy-on-write canvas
pref("layers.shared-buffer-provider.enabled", true);

//@line 4896 "z:\build\build\src\modules\libpref\init\all.js"
pref("layers.shared-buffer-provider.enabled", false);
//@line 4898 "z:\build\build\src\modules\libpref\init\all.js"

//@line 4903 "z:\build\build\src\modules\libpref\init\all.js"

// Force all possible layers to be always active layers
pref("layers.force-active", false);

// Never use gralloc surfaces, even when they're available on this
// platform and are the optimal surface type.
pref("layers.gralloc.disable", false);

// Enable/Disable the geolocation API for content
pref("geo.enabled", true);

// Timeout for outbound network geolocation provider XHR
pref("geo.wifi.xhr.timeout", 60000);

// Enable/Disable the orientation API for content
pref("device.sensors.enabled", true);

// Enable/Disable the device storage API for content
pref("device.storage.enabled", false);

// Toggle which thread the HTML5 parser uses for stream parsing
pref("html5.offmainthread", true);
// Time in milliseconds between the time a network buffer is seen and the
// timer firing when the timer hasn't fired previously in this parse in the
// off-the-main-thread HTML5 parser.
pref("html5.flushtimer.initialdelay", 120);
// Time in milliseconds between the time a network buffer is seen and the
// timer firing when the timer has already fired previously in this parse.
pref("html5.flushtimer.subsequentdelay", 120);

// Push/Pop/Replace State prefs
pref("browser.history.maxStateObjectSize", 655360);

pref("browser.meta_refresh_when_inactive.disabled", false);

// XPInstall prefs
pref("xpinstall.whitelist.required", true);
// Only Firefox requires add-on signatures
pref("xpinstall.signatures.required", false);
pref("extensions.alwaysUnpack", false);
pref("extensions.minCompatiblePlatformVersion", "2.0");
pref("extensions.webExtensionsMinPlatformVersion", "42.0a1");
pref("extensions.legacy.enabled", true);
pref("extensions.allow-non-mpc-extensions", true);

// Other webextensions prefs
pref("extensions.webextensions.keepStorageOnUninstall", false);
pref("extensions.webextensions.keepUuidOnUninstall", false);
// Redirect basedomain used by identity api
pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
// Whether or not webextension themes are supported.
pref("extensions.webextensions.themes.enabled", false);
pref("extensions.webextensions.themes.icons.enabled", false);
pref("extensions.webextensions.remote", false);
// Whether or not the moz-extension resource loads are remoted. For debugging
// purposes only. Setting this to false will break moz-extension URI loading
// unless other process sandboxing and extension remoting prefs are changed.
pref("extensions.webextensions.protocol.remote", true);

// Report Site Issue button
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
//@line 4967 "z:\build\build\src\modules\libpref\init\all.js"
pref("extensions.webcompat-reporter.enabled", false);
//@line 4969 "z:\build\build\src\modules\libpref\init\all.js"

pref("network.buffer.cache.count", 24);
pref("network.buffer.cache.size",  32768);

// Desktop Notification
pref("notification.feature.enabled", false);

// Web Notification
pref("dom.webnotifications.enabled", true);
pref("dom.webnotifications.serviceworker.enabled", true);
pref("dom.webnotifications.requireinteraction.count", 3);
//@line 4983 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.webnotifications.requireinteraction.enabled", false);
//@line 4985 "z:\build\build\src\modules\libpref\init\all.js"

// Show favicons in web notifications.
pref("alerts.showFavicons", false);

// DOM full-screen API.
pref("full-screen-api.enabled", false);
//@line 4992 "z:\build\build\src\modules\libpref\init\all.js"
pref("full-screen-api.unprefix.enabled", false);
//@line 4996 "z:\build\build\src\modules\libpref\init\all.js"
pref("full-screen-api.allow-trusted-requests-only", true);
pref("full-screen-api.pointer-lock.enabled", true);
// transition duration of fade-to-black and fade-from-black, unit: ms
//@line 5000 "z:\build\build\src\modules\libpref\init\all.js"
pref("full-screen-api.transition-duration.enter", "200 200");
pref("full-screen-api.transition-duration.leave", "200 200");
//@line 5006 "z:\build\build\src\modules\libpref\init\all.js"
// timeout for black screen in fullscreen transition, unit: ms
pref("full-screen-api.transition.timeout", 1000);
// time for the warning box stays on the screen before sliding out, unit: ms
pref("full-screen-api.warning.timeout", 3000);
// delay for the warning box to show when pointer stays on the top, unit: ms
pref("full-screen-api.warning.delay", 500);

// DOM pointerlock API
// time for the warning box stays on the screen before sliding out, unit: ms
pref("pointer-lock-api.warning.timeout", 3000);

// DOM idle observers API
pref("dom.idle-observers-api.enabled", true);

// Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
// Used to detect long running handlers of user-generated events.
pref("dom.event.handling-user-input-time-limit", 1000);

// Whether we should layerize all animated images (if otherwise possible).
pref("layout.animated-image-layers.enabled", false);

pref("dom.vibrator.enabled", true);
pref("dom.vibrator.max_vibrate_ms", 10000);
pref("dom.vibrator.max_vibrate_list_len", 128);

// Battery API
pref("dom.battery.enabled", true);

// Push

pref("dom.push.enabled", false);

pref("dom.push.loglevel", "error");

pref("dom.push.serverURL", "wss://push.services.mozilla.com/");
pref("dom.push.userAgentID", "");

// The maximum number of push messages that a service worker can receive
// without user interaction.
pref("dom.push.maxQuotaPerSubscription", 16);

// The maximum number of recent message IDs to store for each push
// subscription, to avoid duplicates for unacknowledged messages.
pref("dom.push.maxRecentMessageIDsPerSubscription", 10);

// The delay between receiving a push message and updating the quota for a
// subscription.
pref("dom.push.quotaUpdateDelay", 3000); // 3 seconds

// Is the network connection allowed to be up?
// This preference should be used in UX to enable/disable push.
pref("dom.push.connection.enabled", true);

// Exponential back-off start is 5 seconds like in HTTP/1.1.
// Maximum back-off is pingInterval.
pref("dom.push.retryBaseInterval", 5000);

// Interval at which to ping PushServer to check connection status. In
// milliseconds. If no reply is received within requestTimeout, the connection
// is considered closed.
pref("dom.push.pingInterval", 1800000); // 30 minutes

// How long before we timeout
pref("dom.push.requestTimeout", 10000);

// WebPush prefs:
pref("dom.push.http2.reset_retry_count_after_ms", 60000);
pref("dom.push.http2.maxRetries", 2);
pref("dom.push.http2.retryInterval", 5000);

// W3C touch events
// 0 - disabled, 1 - enabled, 2 - autodetect
// Autodetection is currently only supported on Windows and GTK3
//@line 5082 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.w3c_touch_events.enabled", 2);
//@line 5084 "z:\build\build\src\modules\libpref\init\all.js"

// W3C draft pointer events
//@line 5089 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.w3c_pointer_events.enabled", false);
//@line 5091 "z:\build\build\src\modules\libpref\init\all.js"

// Control firing WidgetMouseEvent by handling Windows pointer messages or mouse
// messages.
//@line 5095 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.w3c_pointer_events.dispatch_by_pointer_messages", false);
//@line 5097 "z:\build\build\src\modules\libpref\init\all.js"

// W3C pointer events draft
pref("dom.w3c_pointer_events.implicit_capture", false);

// WHATWG promise rejection events. See
// https://html.spec.whatwg.org/multipage/webappapis.html#promiserejectionevent
// TODO: Enable the event interface once actually firing it (bug 1362272).
pref("dom.promise_rejection_events.enabled", false);

// W3C draft ImageCapture API
pref("dom.imagecapture.enabled", false);

// W3C MediaDevices devicechange event
pref("media.ondevicechange.enabled", true);

// W3C MediaDevices devicechange fake event
pref("media.ondevicechange.fakeDeviceChangeEvent.enabled", false);

// W3C touch-action css property (related to touch and pointer events)
// Note that we turn this on even on platforms/configurations where touch
// events are not supported (e.g. OS X, or Windows with e10s disabled). For
// those platforms we don't handle touch events anyway so it's conceptually
// a no-op.
pref("layout.css.touch_action.enabled", true);

// Enables some assertions in nsStyleContext that are too expensive
// for general use, but might be useful to enable for specific tests.
// This only has an effect in DEBUG-builds.
pref("layout.css.expensive-style-struct-assertions.enabled", false);

// enable JS dump() function.
pref("browser.dom.window.dump.enabled", false);

//@line 5134 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.netinfo.enabled", false);
//@line 5136 "z:\build\build\src\modules\libpref\init\all.js"

//@line 5138 "z:\build\build\src\modules\libpref\init\all.js"
// On 32-bit Windows, fire a low-memory notification if we have less than this
// many mb of virtual address space available.
pref("memory.low_virtual_memory_threshold_mb", 128);

// On Windows 32-bit, fire a low-memory notification if we have less
// than this many mb of commit space (physical memory plus page file) left.
pref("memory.low_commit_space_threshold_mb", 128);

// On Windows 32-bit, fire a low-memory notification if we have less
// than this many mb of physical memory available on the whole machine.
pref("memory.low_physical_memory_threshold_mb", 0);

// On Windows 32-bit, don't fire a low-memory notification because of
// low available physical memory or low commit space more than once every
// low_memory_notification_interval_ms.
pref("memory.low_memory_notification_interval_ms", 10000);
//@line 5155 "z:\build\build\src\modules\libpref\init\all.js"

// How long must we wait before declaring that a window is a "ghost" (i.e., a
// likely leak)?  This should be longer than it usually takes for an eligible
// window to be collected via the GC/CC.
pref("memory.ghost_window_timeout_seconds", 60);

// Disable freeing dirty pages when minimizing memory.
pref("memory.free_dirty_pages", false);

// Disable the Linux-specific, system-wide memory reporter.
//@line 5168 "z:\build\build\src\modules\libpref\init\all.js"

// Don't dump memory reports on OOM, by default.
pref("memory.dump_reports_on_oom", false);

// Number of stack frames to capture in createObjectURL for about:memory.
pref("memory.blob_report.stack_frames", 0);

// comma separated list of domain origins (e.g. https://domain.com) that still
// need localStorage in the frameworker
pref("social.whitelist", "https://mozsocial.cliqz.com");
// comma separated list of domain origins (e.g. https://domain.com) for
// directory websites (e.g. AMO) that can install providers for other sites
pref("social.directories", "https://activations.cdn.mozilla.net");
// remote-install allows any website to activate a provider, with extended UI
// notifying user of installation. we can later pref off remote install if
// necessary. This does not affect whitelisted and directory installs.
pref("social.remote-install.enabled", true);
pref("social.toast-notifications.enabled", true);

// Disable idle observer fuzz, because only privileged content can access idle
// observers (bug 780507).
pref("dom.idle-observers-api.fuzz_time.disabled", true);

// Minimum delay in milliseconds between network activity notifications (0 means
// no notifications). The delay is the same for both download and upload, though
// they are handled separately. This pref is only read once at startup:
// a restart is required to enable a new value.
pref("network.activity.blipIntervalMilliseconds", 0);

// When we're asked to take a screenshot, don't wait more than 2000ms for the
// event loop to become idle before actually taking the screenshot.
pref("dom.browserElement.maxScreenshotDelayMS", 2000);

// Whether we should show the placeholder when the element is focused but empty.
pref("dom.placeholder.show_on_focus", true);

// WebVR is enabled by default in beta and release for Windows and for all
// platforms in nightly and aurora.
//@line 5207 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.vr.enabled", true);
//@line 5211 "z:\build\build\src\modules\libpref\init\all.js"
// It is often desirable to automatically start vr presentation when
// a user puts on the VR headset.  This is done by emitting the
// Window.vrdisplayactivate event when the headset's sensors detect it
// being worn.  This can result in WebVR content taking over the headset
// when the user is using it outside the browser or inadvertent start of
// presentation due to the high sensitivity of the proximity sensor in some
// headsets, so it is off by default.
pref("dom.vr.autoactivate.enabled", false);
// The threshold value of trigger inputs for VR controllers
pref("dom.vr.controller_trigger_threshold", "0.1");
// Maximum number of milliseconds the browser will wait for content to call
// VRDisplay.requestPresent after emitting vrdisplayactivate during VR
// link traversal.  This prevents a long running event handler for
// vrdisplayactivate from later calling VRDisplay.requestPresent, which would
// result in a non-responsive browser in the VR headset.
pref("dom.vr.navigation.timeout", 5000);
// Oculus device
//@line 5232 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.vr.oculus.enabled", false);
//@line 5234 "z:\build\build\src\modules\libpref\init\all.js"
// Minimum number of milliseconds after content has stopped VR presentation
// before the Oculus session is re-initialized to an invisible / tracking
// only mode.  If this value is too high, users will need to wait longer
// after stopping WebVR presentation before automatically returning to the
// Oculus home interface.  (They can immediately return to the Oculus Home
// interface through the Oculus HUD without waiting this duration)
// If this value is too low, the Oculus Home interface may be visible
// momentarily during VR link navigation.
pref("dom.vr.oculus.present.timeout", 10000);
// Minimum number of milliseconds that the browser will wait before
// reloading the Oculus OVR library after seeing a "ShouldQuit" flag set.
// Oculus requests that we shut down and unload the OVR library, by setting
// a "ShouldQuit" flag.  To ensure that we don't interfere with
// Oculus software auto-updates, we will not attempt to re-load the
// OVR library until this timeout has elapsed.
pref("dom.vr.oculus.quit.timeout", 30000);
// OSVR device
pref("dom.vr.osvr.enabled", false);
// OpenVR device
//@line 5257 "z:\build\build\src\modules\libpref\init\all.js"
// See Bug 1310663 (Linux) and Bug 1310665 (macOS)
pref("dom.vr.openvr.enabled", false);
//@line 5260 "z:\build\build\src\modules\libpref\init\all.js"
// Pose prediction reduces latency effects by returning future predicted HMD
// poses to callers of the WebVR API.  This currently only has an effect for
// Oculus Rift on SDK 0.8 or greater.
pref("dom.vr.poseprediction.enabled", true);
// Starting VR presentation is only allowed within a user gesture or event such
// as VRDisplayActivate triggered by the system.  dom.vr.require-gesture allows
// this requirement to be disabled for special cases such as during automated
// tests or in a headless kiosk system.
pref("dom.vr.require-gesture", true);
// path to OSVR DLLs
pref("gfx.vr.osvr.utilLibPath", "");
pref("gfx.vr.osvr.commonLibPath", "");
pref("gfx.vr.osvr.clientLibPath", "");
pref("gfx.vr.osvr.clientKitLibPath", "");
// Puppet device, used for simulating VR hardware within tests and dev tools
pref("dom.vr.puppet.enabled", false);
// Allow displaying the result of vr submitframe (0: disable, 1: store the
// result as a base64 image, 2: show it on the screen).
pref("dom.vr.puppet.submitframe", 0);
// VR test system.
pref("dom.vr.test.enabled", false);

// If the user puts a finger down on an element and we think the user
// might be executing a pan gesture, how long do we wait before
// tentatively deciding the gesture is actually a tap and activating
// the target element?
pref("ui.touch_activation.delay_ms", 100);

// If the user has clicked an element, how long do we keep the
// :active state before it is cleared by the mouse sequences
// fired after a touchstart/touchend.
pref("ui.touch_activation.duration_ms", 10);

// nsMemoryInfoDumper can watch a fifo in the temp directory and take various
// actions when the fifo is written to.  Disable this in general.
pref("memory_info_dumper.watch_fifo.enabled", false);

// If minInterval is 0, the check will only happen
// when the service has a strong suspicion we are in a captive portal
pref("network.captive-portal-service.minInterval", 60000); // 60 seconds
pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes
// Every 10 checks, the delay is increased by a factor of 5
pref("network.captive-portal-service.backoffFactor", "5.0");
pref("network.captive-portal-service.enabled", false);

pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt");
pref("captivedetect.canonicalContent", "success\n");
pref("captivedetect.maxWaitingTime", 5000);
pref("captivedetect.pollingTime", 3000);
pref("captivedetect.maxRetryCount", 5);

//@line 5312 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.forms.inputmode", false);
//@line 5316 "z:\build\build\src\modules\libpref\init\all.js"

pref("dom.flyweb.enabled", false);

// Enable mapped array buffer by default.
pref("dom.mapped_arraybuffer.enabled", true);

// The tables used for Safebrowsing phishing and malware checks.
pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,test-malware-simple,test-unwanted-simple");

//@line 5326 "z:\build\build\src\modules\libpref\init\all.js"
// In the official build, we are allowed to use google's private
// phishing list "goog-phish-shavar". See Bug 1288840.
pref("urlclassifier.phishTable", "goog-phish-shavar,test-phish-simple");
//@line 5332 "z:\build\build\src\modules\libpref\init\all.js"

// Tables for application reputation.
pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256");
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");

pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,except-flashinfobar-digest256");

// The table and update/gethash URLs for Safebrowsing phishing and malware
// checks.
pref("urlclassifier.trackingTable", "test-track-simple,base-track-digest256");
pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");

// The number of random entries to send with a gethash request.
pref("urlclassifier.gethashnoise", 4);

// Gethash timeout for Safebrowsing.
pref("urlclassifier.gethash.timeout_ms", 5000);
// Update server response timeout for Safebrowsing.
pref("urlclassifier.update.response_timeout_ms", 30000);
// Download update timeout for Safebrowsing.
pref("urlclassifier.update.timeout_ms", 90000);

// Name of the about: page contributed by safebrowsing to handle display of error
// pages on phishing/malware hits.  (bug 399233)
pref("urlclassifier.alternate_error_page", "blocked");

// Enable phishing protection
pref("browser.safebrowsing.phishing.enabled", true);

// Enable malware protection
pref("browser.safebrowsing.malware.enabled", true);

//@line 5365 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.safebrowsing.downloads.enabled", true);
//@line 5369 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.safebrowsing.downloads.remote.enabled", true);
pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
pref("browser.safebrowsing.debug", false);

// The protocol version we communicate with google server.
pref("browser.safebrowsing.provider.google.pver", "2.2");
pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,googpub-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
pref("browser.safebrowsing.provider.google.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
pref("browser.safebrowsing.provider.google.advisoryName", "Google Safe Browsing.");

// Prefs for v4.
pref("browser.safebrowsing.provider.google4.pver", "4");
pref("browser.safebrowsing.provider.google4.lists", "goog-badbinurl-proto,goog-downloadwhite-proto,goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto");
pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%&$httpMethod=POST");
pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
pref("browser.safebrowsing.provider.google4.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing.");

pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");

// The table and global pref for blocking plugin content
pref("browser.safebrowsing.blockedURIs.enabled", true);
pref("urlclassifier.blockedTable", "test-block-simple,mozplugin-block-digest256");

// The protocol version we communicate with mozilla server.
pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,except-flashinfobar-digest256");
pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
// Set to a date in the past to force immediate download in new profiles.
pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
// Block lists for tracking protection. The name values will be used as the keys
// to lookup the localized name in preferences.properties.
pref("browser.safebrowsing.provider.mozilla.lists.base.name", "mozstdName");
pref("browser.safebrowsing.provider.mozilla.lists.base.description", "mozstdDesc");
pref("browser.safebrowsing.provider.mozilla.lists.content.name", "mozfullName");
pref("browser.safebrowsing.provider.mozilla.lists.content.description", "mozfullDesc2");

pref("urlclassifier.flashAllowTable", "allow-flashallow-digest256");
pref("urlclassifier.flashAllowExceptTable", "except-flashallow-digest256");
pref("urlclassifier.flashTable", "block-flash-digest256");
pref("urlclassifier.flashExceptTable", "except-flash-digest256");
pref("urlclassifier.flashSubDocTable", "block-flashsubdoc-digest256");
pref("urlclassifier.flashSubDocExceptTable", "except-flashsubdoc-digest256");
pref("urlclassifier.flashInfobarTable", "except-flashinfobar-digest256");

pref("plugins.http_https_only", true);
pref("plugins.flashBlock.enabled", false);

// Allow users to ignore Safe Browsing warnings.
pref("browser.safebrowsing.allowOverride", true);

//@line 5435 "z:\build\build\src\modules\libpref\init\all.js"
// Normally the "client ID" sent in updates is appinfo.name, but for
// official Firefox releases from Mozilla we use a special identifier.
pref("browser.safebrowsing.id", "navclient-auto-ffox");
//@line 5441 "z:\build\build\src\modules\libpref\init\all.js"

// Turn off Spatial navigation by default.
pref("snav.enabled", false);

// Debug-only pref to force enable the AccessibleCaret. If you want to
// control AccessibleCaret by mouse, you'll need to set
// "layout.accessiblecaret.hide_carets_for_mouse_input" to false.
pref("layout.accessiblecaret.enabled", false);

// Enable the accessible caret on platforms/devices
// that we detect have touch support. Note that this pref is an
// additional way to enable the accessible carets, rather than
// overriding the layout.accessiblecaret.enabled pref.
pref("layout.accessiblecaret.enabled_on_touch", true);

// CSS attributes of the AccessibleCaret in CSS pixels.
pref("layout.accessiblecaret.width", "34.0");
pref("layout.accessiblecaret.height", "36.0");
pref("layout.accessiblecaret.margin-left", "-18.5");
pref("layout.accessiblecaret.bar.width", "2.0");

// Show no selection bars at the two ends of the selection highlight.
pref("layout.accessiblecaret.bar.enabled", false);

// Show the caret when long tapping on an empty content.
pref("layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content", false);

// Simulate long tap to select words on the platforms where APZ is not enabled
// or long tap events does not fired by APZ.
pref("layout.accessiblecaret.use_long_tap_injector", false);

// By default, carets become tilt only when they are overlapping.
pref("layout.accessiblecaret.always_tilt", false);

// By default, carets always show when scrolling (either panning for zooming)
// the page.
pref("layout.accessiblecaret.always_show_when_scrolling", true);

// Selection change notifications generated by Javascript hide
// AccessibleCarets and close UI interaction by default.
pref("layout.accessiblecaret.allow_script_change_updates", false);

// Allow one caret to be dragged across the other caret without any limitation.
// This matches the built-in convention for all desktop platforms.
pref("layout.accessiblecaret.allow_dragging_across_other_caret", true);

// Optionally provide haptic feedback on longPress selection events.
pref("layout.accessiblecaret.hapticfeedback", false);

// Smart phone-number selection on long-press is not enabled by default.
pref("layout.accessiblecaret.extend_selection_for_phone_number", false);

// Keep the accessible carets hidden when the user is using mouse input.
pref("layout.accessiblecaret.hide_carets_for_mouse_input", true);

// Wakelock is disabled by default.
pref("dom.wakelock.enabled", false);

// The URL of the Firefox Accounts auth server backend
pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");

pref("beacon.enabled", true);

// Camera prefs
pref("camera.control.face_detection.enabled", true);


// SW Cache API
pref("dom.caches.enabled", true);

// UDPSocket API
pref("dom.udpsocket.enabled", false);

// Presentation API
pref("dom.presentation.enabled", false);
pref("dom.presentation.controller.enabled", false);
pref("dom.presentation.receiver.enabled", false);

// Presentation Device
pref("dom.presentation.tcp_server.debug", false);
pref("dom.presentation.discovery.enabled", false);
pref("dom.presentation.discovery.timeout_ms", 10000);
pref("dom.presentation.discoverable", false);
pref("dom.presentation.discoverable.encrypted", true);
pref("dom.presentation.discoverable.retry_ms", 5000);
pref("dom.presentation.session_transport.data_channel.enable", false);

//@line 5536 "z:\build\build\src\modules\libpref\init\all.js"

// Enable meta-viewport support in remote APZ-enabled frames.
pref("dom.meta-viewport.enabled", false);

// Search service settings
pref("browser.search.log", false);
pref("browser.search.update", true);
pref("browser.search.update.log", false);
pref("browser.search.update.interval", 21600);
pref("browser.search.suggest.enabled", true);
pref("browser.search.reset.enabled", false);
pref("browser.search.reset.whitelist", "");
pref("browser.search.geoSpecificDefaults", false);
pref("browser.search.geoip.url", "https://location.services.mozilla.com/v1/country?key=%MOZILLA_API_KEY%");
pref("browser.search.geoip.timeout", 3000);

//@line 5553 "z:\build\build\src\modules\libpref\init\all.js"
// {moz:official} expands to "official"
pref("browser.search.official", true);
//@line 5556 "z:\build\build\src\modules\libpref\init\all.js"

// GMPInstallManager prefs

// User-settable override to media.gmp-manager.url for testing purposes.
//pref("media.gmp-manager.url.override", "");

// Update service URL for GMP install/updates:
pref("media.gmp-manager.url", "https://aus5.mozilla.org/update/3/GMP/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");

// When |media.gmp-manager.cert.requireBuiltIn| is true or not specified the
// final certificate and all certificates the connection is redirected to before
// the final certificate for the url specified in the |media.gmp-manager.url|
// preference must be built-in.
pref("media.gmp-manager.cert.requireBuiltIn", true);

// The |media.gmp-manager.certs.| preference branch contains branches that are
// sequentially numbered starting at 1 that contain attribute name / value
// pairs for the certificate used by the server that hosts the update xml file
// as specified in the |media.gmp-manager.url| preference. When these preferences are
// present the following conditions apply for a successful update check:
// 1. the uri scheme must be https
// 2. the preference name must exist as an attribute name on the certificate and
//    the value for the name must be the same as the value for the attribute name
//    on the certificate.
// If these conditions aren't met it will be treated the same as when there is
// no update available. This validation will not be performed when the
// |media.gmp-manager.url.override| user preference has been set for testing updates or
// when the |media.gmp-manager.cert.checkAttributes| preference is set to false. Also,
// the |media.gmp-manager.url.override| preference should ONLY be used for testing.
// IMPORTANT! app.update.certs.* prefs should also be updated if these
// are updated.
pref("media.gmp-manager.cert.checkAttributes", true);
pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US");
pref("media.gmp-manager.certs.1.commonName", "aus5.mozilla.org");
pref("media.gmp-manager.certs.2.issuerName", "CN=thawte SSL CA - G2,O=\"thawte, Inc.\",C=US");
pref("media.gmp-manager.certs.2.commonName", "aus5.mozilla.org");

// Whether or not to perform reader mode article parsing on page load.
// If this pref is disabled, we will never show a reader mode icon in the toolbar.
pref("reader.parse-on-load.enabled", true);

// After what size document we don't bother running Readability on it
// because it'd slow things down too much
pref("reader.parse-node-limit", 3000);

// Force-enables reader mode parsing, even on low-memory platforms, where it
// is disabled by default.
pref("reader.parse-on-load.force-enabled", false);

// Whether we include full URLs in browser console errors. This is disabled
// by default because some platforms will persist these, leading to privacy issues.
pref("reader.errors.includeURLs", false);

// The default relative font size in reader mode (1-9)
pref("reader.font_size", 5);

// The default relative content width in reader mode (1-9)
pref("reader.content_width", 3);

// The default relative line height in reader mode (1-9)
pref("reader.line_height", 4);

// The default color scheme in reader mode (light, dark, sepia, auto)
// auto = color automatically adjusts according to ambient light level
// (auto only works on platforms where the 'devicelight' event is enabled)
pref("reader.color_scheme", "light");

// Color scheme values available in reader mode UI.
pref("reader.color_scheme.values", "[\"light\",\"dark\",\"sepia\"]");

// The font type in reader (sans-serif, serif)
pref("reader.font_type", "sans-serif");

// Whether or not the user has interacted with the reader mode toolbar.
// This is used to show a first-launch tip in reader mode.
pref("reader.has_used_toolbar", false);

// Whether to use a vertical or horizontal toolbar.
pref("reader.toolbar.vertical", true);

//@line 5637 "z:\build\build\src\modules\libpref\init\all.js"
pref("narrate.enabled", true);
//@line 5641 "z:\build\build\src\modules\libpref\init\all.js"

pref("narrate.test", false);
pref("narrate.rate", 0);
pref("narrate.voice", " { \"default\": \"automatic\" }");
// Only make voices that match content language available.
pref("narrate.filter-voices", true);

//@line 5654 "z:\build\build\src\modules\libpref\init\all.js"

// HTML <dialog> element
pref("dom.dialog_element.enabled", false);

// Secure Element API
//@line 5660 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.secureelement.enabled", false);
//@line 5662 "z:\build\build\src\modules\libpref\init\all.js"

// Allow control characters appear in composition string.
// When this is false, control characters except
// CHARACTER TABULATION (horizontal tab) are removed from
// both composition string and data attribute of compositionupdate
// and compositionend events.
pref("dom.compositionevent.allow_control_characters", false);

pref("memory.report_concurrency", 10);

// Add Mozilla AudioChannel APIs.
pref("media.useAudioChannelAPI", false);

// Expose Request.context. Currently disabled since the spec is in flux.
pref("dom.requestcontext.enabled", false);

pref("toolkit.pageThumbs.screenSizeDivisor", 7);
pref("toolkit.pageThumbs.minWidth", 0);
pref("toolkit.pageThumbs.minHeight", 0);

pref("webextensions.tests", false);

// 16MB default non-parseable upload limit for requestBody.raw.bytes
pref("webextensions.webRequest.requestBodyMaxRawBytes", 16777216);

pref("webextensions.storage.sync.enabled", true);
pref("webextensions.storage.sync.serverURL", "https://webextensions.settings.services.mozilla.com/v1");

// Allow customization of the fallback directory for file uploads
pref("dom.input.fallbackUploadDir", "");

// Turn rewriting of youtube embeds on/off
pref("plugins.rewrite_youtube_embeds", true);

// Disable browser frames by default
pref("dom.mozBrowserFramesEnabled", false);

// Is support for 'color-adjust' CSS property enabled?
pref("layout.css.color-adjust.enabled", true);

pref("dom.audiochannel.audioCompeting", false);
pref("dom.audiochannel.audioCompeting.allAgents", false);

// Default media volume
pref("media.default_volume", "1.0");

pref("media.seekToNextFrame.enabled", true);

// return the maximum number of cores that navigator.hardwareCurrency returns
pref("dom.maxHardwareConcurrency", 16);

// Shutdown the osfile worker if its no longer needed.
//@line 5717 "z:\build\build\src\modules\libpref\init\all.js"

//@line 5719 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.webkitBlink.dirPicker.enabled", true);
pref("dom.webkitBlink.filesystem.enabled", true);
//@line 5722 "z:\build\build\src\modules\libpref\init\all.js"

pref("media.block-autoplay-until-in-foreground", true);

// Is Stylo CSS support built and enabled?
// Only define this pref if Stylo support is actually built in.
//@line 5731 "z:\build\build\src\modules\libpref\init\all.js"
pref("layout.css.servo.enabled", false);
//@line 5734 "z:\build\build\src\modules\libpref\init\all.js"

// HSTS Priming
// If a request is mixed-content, send an HSTS priming request to attempt to
// see if it is available over HTTPS.
// Don't change the order of evaluation of mixed-content and HSTS upgrades in
// order to be most compatible with current standards in Release
pref("security.mixed_content.send_hsts_priming", false);
pref("security.mixed_content.use_hsts", false);
//@line 5748 "z:\build\build\src\modules\libpref\init\all.js"
// Approximately 1 week default cache for HSTS priming failures, in seconds
pref("security.mixed_content.hsts_priming_cache_timeout", 604800);
// Force the channel to timeout in 2 seconds if we have not received
// expects a time in milliseconds
pref("security.mixed_content.hsts_priming_request_timeout", 2000);

// TODO: Bug 1324406: Treat 'data:' documents as unique, opaque origins
// If true, data: URIs will be treated as unique opaque origins, hence will use
// a NullPrincipal as the security context.
// Otherwise it will inherit the origin from parent node, this is the legacy
// behavior of Firefox.
pref("security.data_uri.unique_opaque_origin", false);

// TODO: Bug 1380959: Block toplevel data: URI navigations
// If true, all toplevel data: URI navigations will be blocked.
// Please note that manually entering a data: URI in the
// URL-Bar will not be blocked when flipping this pref.
pref("security.data_uri.block_toplevel_data_uri_navigations", false);

// Disable Storage api in release builds.
//@line 5771 "z:\build\build\src\modules\libpref\init\all.js"
pref("dom.storageManager.enabled", false);
//@line 5773 "z:\build\build\src\modules\libpref\init\all.js"

pref("dom.storageManager.prompt.testing", false);
pref("dom.storageManager.prompt.testing.allow", false);

// Enable the Storage management in about:preferences and persistent-storage permission request
// To enable the DOM implementation, turn on "dom.storageManager.enabled"
//@line 5782 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.storageManager.enabled", false);
//@line 5784 "z:\build\build\src\modules\libpref\init\all.js"
pref("browser.storageManager.pressureNotification.minIntervalMS", 1200000);
pref("browser.storageManager.pressureNotification.usageThresholdGB", 5);

// When a user cancels this number of authentication dialogs coming from
// a single web page in a row, all following authentication dialogs will
// be blocked (automatically canceled) for that page. The counter resets
// when the page is reloaded. To turn this feature off, just set the limit to 0.
pref("prompts.authentication_dialog_abuse_limit", 3);

pref("dom.IntersectionObserver.enabled", true);

// Whether module scripts (<script type="module">) are enabled for content.
pref("dom.moduleScripts.enabled", false);

// Maximum amount of time in milliseconds consecutive setTimeout()/setInterval()
// callback are allowed to run before yielding the event loop.
pref("dom.timeout.max_consecutive_callbacks_ms", 4);

// Use this preference to house "Payment Request API" during development
pref("dom.payments.request.enabled", false);

//@line 5808 "z:\build\build\src\modules\libpref\init\all.js"

//@line 5817 "z:\build\build\src\modules\libpref\init\all.js"
pref("layers.mlgpu.dev-enabled", false);
pref("layers.mlgpu.enable-on-windows7", false);
//@line 5821 "z:\build\build\src\modules\libpref\init\all.js"

// Set advanced layers preferences here to have them show up in about:config or
// to be overridable in reftest.list files. They should pretty much all be set
// to a value of 2, and the conditional-pref code in gfxPrefs.h will convert
// it to a boolean as appropriate. In particular, do NOT add ifdefs here to
// turn these on and off, instead use the conditional-pref code in gfxPrefs.h
// to do that.
pref("layers.advanced.background-color", 2);
pref("layers.advanced.background-image", 2);
pref("layers.advanced.border-layers", 2);
pref("layers.advanced.boxshadow-inset-layers", 2);
pref("layers.advanced.boxshadow-outer-layers", 2);
pref("layers.advanced.bullet-layers", 2);
pref("layers.advanced.button-foreground-layers", 2);
pref("layers.advanced.canvas-background-color", 2);
pref("layers.advanced.caret-layers", 2);
pref("layers.advanced.columnRule-layers", 2);
pref("layers.advanced.displaybuttonborder-layers", 2);
pref("layers.advanced.image-layers", 2);
pref("layers.advanced.outline-layers", 2);
pref("layers.advanced.solid-color", 2);
pref("layers.advanced.table", 2);
pref("layers.advanced.text-layers", 2);
pref("layers.advanced.filter-layers", 2);

// Enable lowercased response header name
pref("dom.xhr.lowercase_header.enabled", false);

// When a crash happens, whether to include heap regions of the crash context
// in the minidump. Enabled by default on nightly and aurora.
//@line 5852 "z:\build\build\src\modules\libpref\init\all.js"
pref("toolkit.crashreporter.include_context_heap", false);
//@line 5856 "z:\build\build\src\modules\libpref\init\all.js"

// Open noopener links in a new process
pref("dom.noopener.newprocess.enabled", true);
//@line 1 "z:\build\build\src\toolkit\components\telemetry\datareporting-prefs.js"
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pref */

pref("datareporting.policy.dataSubmissionEnabled", true);
pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "0");
pref("datareporting.policy.dataSubmissionPolicyAcceptedVersion", 0);
pref("datareporting.policy.dataSubmissionPolicyBypassNotification", false);
pref("datareporting.policy.currentPolicyVersion", 2);
pref("datareporting.policy.minimumPolicyVersion", 1);
pref("datareporting.policy.minimumPolicyVersion.channel-beta", 2);
pref("datareporting.policy.firstRunURL", "https://www.mozilla.org/privacy/firefox/");
//@line 1 "z:\build\build\src\toolkit\components\telemetry\healthreport-prefs.js"
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pref */

pref("datareporting.healthreport.infoURL", "https://www.mozilla.org/legal/privacy/firefox.html#health-report");

// Health Report is enabled by default on all channels.
pref("datareporting.healthreport.uploadEnabled", true);

pref("datareporting.healthreport.about.reportUrl", "https://fhr.cdn.mozilla.net/%LOCALE%/v4/");
PK
!<M} defaults/autoconfig/prefcalls.js/* global processLDAPValues */
/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const nsILDAPURL = Components.interfaces.nsILDAPURL;
const LDAPURLContractID = "@mozilla.org/network/ldap-url;1";
const nsILDAPSyncQuery = Components.interfaces.nsILDAPSyncQuery;
const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1";
const nsIPrefService = Components.interfaces.nsIPrefService;
const PrefServiceContractID = "@mozilla.org/preferences-service;1";

var gVersion;
var gIsUTF8;

function getPrefBranch() {

    var prefService = Components.classes[PrefServiceContractID]
                                .getService(nsIPrefService);
    return prefService.getBranch(null);
}

function pref(prefName, value) {

    try {
        var prefBranch = getPrefBranch();

        if (typeof value == "string") {
            if (gIsUTF8) {
                prefBranch.setStringPref(prefName, value);
                return;
            }
            prefBranch.setCharPref(prefName, value);
        } else if (typeof value == "number") {
            prefBranch.setIntPref(prefName, value);
        } else if (typeof value == "boolean") {
            prefBranch.setBoolPref(prefName, value);
        }
    } catch (e) {
        displayError("pref", e);
    }
}

function defaultPref(prefName, value) {

    try {
        var prefService = Components.classes[PrefServiceContractID]
                                    .getService(nsIPrefService);
        var prefBranch = prefService.getDefaultBranch(null);
        if (typeof value == "string") {
            if (gIsUTF8) {
                prefBranch.setStringPref(prefName, value);
                return;
            }
            prefBranch.setCharPref(prefName, value);
        } else if (typeof value == "number") {
            prefBranch.setIntPref(prefName, value);
        } else if (typeof value == "boolean") {
            prefBranch.setBoolPref(prefName, value);
        }
    } catch (e) {
        displayError("defaultPref", e);
    }
}

function lockPref(prefName, value) {

    try {
        var prefBranch = getPrefBranch();

        if (prefBranch.prefIsLocked(prefName))
            prefBranch.unlockPref(prefName);

        defaultPref(prefName, value);

        prefBranch.lockPref(prefName);
    } catch (e) {
        displayError("lockPref", e);
    }
}

function unlockPref(prefName) {

    try {

        var prefBranch = getPrefBranch();
        prefBranch.unlockPref(prefName);
    } catch (e) {
        displayError("unlockPref", e);
    }
}

function getPref(prefName) {

    try {
        var prefBranch = getPrefBranch();

        switch (prefBranch.getPrefType(prefName)) {

        case prefBranch.PREF_STRING:
            if (gIsUTF8) {
                return prefBranch.getStringPref(prefName);
            }
            return prefBranch.getCharPref(prefName);

        case prefBranch.PREF_INT:
            return prefBranch.getIntPref(prefName);

        case prefBranch.PREF_BOOL:
            return prefBranch.getBoolPref(prefName);
        default:
            return null;
        }
    } catch (e) {
        displayError("getPref", e);
    }
    return undefined;
}

function clearPref(prefName) {

    try {
        var prefBranch = getPrefBranch();
            prefBranch.clearUserPref(prefName);
    } catch (e) {
    }

}

function setLDAPVersion(version) {
    gVersion = version;
}


function getLDAPAttributes(host, base, filter, attribs, isSecure) {

    try {
        var urlSpec = "ldap" + (isSecure ? "s" : "") + "://" + host + (isSecure ? ":636" : "") + "/" + base + "?" + attribs + "?sub?" +
                      filter;

        var url = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(Components.interfaces.nsIIOService)
                            .newURI(urlSpec)
                            .QueryInterface(Components.interfaces.nsILDAPURL);

        var ldapquery = Components.classes[LDAPSyncQueryContractID]
                                  .createInstance(nsILDAPSyncQuery);
        // default to LDAP v3
        if (!gVersion)
          gVersion = Components.interfaces.nsILDAPConnection.VERSION3
        // user supplied method
        processLDAPValues(ldapquery.getQueryResults(url, gVersion));
    } catch (e) {
        displayError("getLDAPAttibutes", e);
    }
}

function getLDAPValue(str, key) {

    try {
        if (str == null || key == null)
            return null;

        var search_key = "\n" + key + "=";

        var start_pos = str.indexOf(search_key);
        if (start_pos == -1)
            return null;

        start_pos += search_key.length;

        var end_pos = str.indexOf("\n", start_pos);
        if (end_pos == -1)
            end_pos = str.length;

        return str.substring(start_pos, end_pos);
    } catch (e) {
        displayError("getLDAPValue", e);
    }
    return undefined;
}

function displayError(funcname, message) {

    try {
        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                      .getService(Components.interfaces.nsIPromptService);
        var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                               .getService(Components.interfaces.nsIStringBundleService)
                               .createBundle("chrome://autoconfig/locale/autoconfig.properties");

         var title = bundle.GetStringFromName("autoConfigTitle");
         var msg = bundle.formatStringFromName("autoConfigMsg", [funcname], 1);
         promptService.alert(null, title, msg + " " + message);
    } catch (e) { }
}

function getenv(name) {
    try {
        var environment = Components.classes["@mozilla.org/process/environment;1"].
            getService(Components.interfaces.nsIEnvironment);
        return environment.get(name);
    } catch (e) {
        displayError("getEnvironment", e);
    }
    return undefined;
}
PK
!<JYj!!defaults/pref/marionette.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pref */

// Marionette is the remote protocol that lets OOP programs communicate
// with, instrument, and control Gecko.
//
// It is included in Firefox, but not enabled by default unless the
// -marionette flag is passed.

// Port to start Marionette server on.
pref("marionette.port", 2828);

// Marionette logging verbosity.  Allowed values are "fatal", "error",
// "warn", "info", "config", "debug", and "trace".
pref("marionette.log.level", "info");

// Sets preferences recommended when using Firefox in automation with
// Marionette.
pref("marionette.prefs.recommended", true);
PK
!<w7>defaults/pref/services-sync.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pref */

pref("services.sync.lastversion", "firstrun");
pref("services.sync.sendVersionInfo", true);

pref("services.sync.scheduler.eolInterval", 604800); // 1 week
pref("services.sync.scheduler.idleInterval", 3600);  // 1 hour
pref("services.sync.scheduler.activeInterval", 600);   // 10 minutes
pref("services.sync.scheduler.immediateInterval", 90);    // 1.5 minutes
pref("services.sync.scheduler.idleTime", 300);   // 5 minutes

pref("services.sync.scheduler.fxa.singleDeviceInterval", 3600); // 1 hour

pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks

// Note that new engines are typically added with a default of disabled, so
// when an existing sync user gets the Firefox upgrade that supports the engine
// it starts as disabled until the user has explicitly opted in.
// The sync "create account" process typically *will* offer these engines, so
// they may be flipped to enabled at that time.
pref("services.sync.engine.addons", true);
pref("services.sync.engine.addresses", false);
pref("services.sync.engine.bookmarks", true);
pref("services.sync.engine.creditcards", false);
pref("services.sync.engine.history", true);
pref("services.sync.engine.passwords", true);
pref("services.sync.engine.prefs", true);
pref("services.sync.engine.tabs", true);
pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|resource:.*|chrome:.*|wyciwyg:.*|file:.*|blob:.*)$");

// The addresses and CC engines might not actually be available at all.
pref("services.sync.engine.addresses.available", false);
pref("services.sync.engine.creditcards.available", false);

// If true, add-on sync ignores changes to the user-enabled flag. This
// allows people to have the same set of add-ons installed across all
// profiles while maintaining different enabled states.
pref("services.sync.addons.ignoreUserEnabledChanges", false);

// Comma-delimited list of hostnames to trust for add-on install.
pref("services.sync.addons.trustedSourceHostnames", "addons.mozilla.org");

pref("services.sync.log.appender.console", "Fatal");
pref("services.sync.log.appender.dump", "Error");
pref("services.sync.log.appender.file.level", "Trace");
pref("services.sync.log.appender.file.logOnError", true);
//@line 54 "z:\build\build\src\services\sync\services-sync.js"
pref("services.sync.log.appender.file.logOnSuccess", false);
//@line 56 "z:\build\build\src\services\sync\services-sync.js"
pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
pref("services.sync.log.rootLogger", "Debug");
pref("services.sync.log.logger.addonutils", "Debug");
pref("services.sync.log.logger.declined", "Debug");
pref("services.sync.log.logger.service.main", "Debug");
pref("services.sync.log.logger.status", "Debug");
pref("services.sync.log.logger.authenticator", "Debug");
pref("services.sync.log.logger.network.resources", "Debug");
pref("services.sync.log.logger.engine.bookmarks", "Debug");
pref("services.sync.log.logger.engine.clients", "Debug");
pref("services.sync.log.logger.engine.forms", "Debug");
pref("services.sync.log.logger.engine.history", "Debug");
pref("services.sync.log.logger.engine.passwords", "Debug");
pref("services.sync.log.logger.engine.prefs", "Debug");
pref("services.sync.log.logger.engine.tabs", "Debug");
pref("services.sync.log.logger.engine.addons", "Debug");
pref("services.sync.log.logger.engine.addresses", "Debug");
pref("services.sync.log.logger.engine.creditcards", "Debug");
pref("services.sync.log.logger.engine.extension-storage", "Debug");
pref("services.sync.log.logger.engine.apps", "Debug");
pref("services.sync.log.logger.identity", "Debug");
pref("services.sync.log.cryptoDebug", false);

pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");

pref("services.sync.telemetry.submissionInterval", 43200); // 12 hours in seconds
pref("services.sync.telemetry.maxPayloadCount", 500);

//@line 90 "z:\build\build\src\services\sync\services-sync.js"

//@line 96 "z:\build\build\src\services\sync\services-sync.js"

// We consider validation this frequently. After considering validation, even
// if we don't end up validating, we won't try again unless this much time has passed.
pref("services.sync.engine.bookmarks.validation.interval", 86400); // 24 hours in seconds

// We only run validation `services.sync.validation.percentageChance` percent of
// the time, even if it's been the right amount of time since the last validation,
// and you meet the maxRecord checks.
pref("services.sync.engine.bookmarks.validation.percentageChance", 10);

// We won't validate an engine if it has more than this many records on the server.
pref("services.sync.engine.bookmarks.validation.maxRecords", 1000);

// The maximum number of immediate resyncs to trigger for changes made during
// a sync.
pref("services.sync.maxResyncs", 5);
PK
!<sC*#*#res/EditorOverride.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

*|* {
  -moz-user-modify: read-write;
}

/* Styles to alter look of things in the Editor content window 
 *  that should NOT be removed when we display in completely WYSIWYG 
 *  "Browser Preview" mode.
 *  Anything that should change, like appearance of table borders
 *  and Named Anchors, should be placed in EditorContent.css instead of here.
*/

/* Primary cursor is text I-beam */

::-moz-canvas, a:link {
  cursor: text;
}

/* Use default arrow over objects with size that 
   are selected when clicked on.
   Override the browser's pointer cursor over links
*/

img, img[usemap], area,
object, object[usemap], 
applet, hr, button, input, textarea, select,
a:link img, a:visited img, a:active img,
a[name]:-moz-only-whitespace {
  cursor: default;
}

a:visited, a:active {
  cursor: text;
}

/* Prevent clicking on links from going to link */
a:link img, a:visited img {
  -moz-user-input: none;
}

/* We suppress user/author's prefs for link underline, 
   so we must set explicitly. This isn't good!
*/
a:link {
  color: -moz-hyperlinktext;
}

/* Allow double-clicks on these widgets to open properties dialogs
   XXX except when the widget has disabled attribute */
input, button, textarea {
  -moz-user-select: all !important;
  -moz-user-input: auto !important;
  -moz-user-focus: none !important;
}

/* XXX Still need a better way of blocking other events to these widgets */
select, input[disabled], input[type="checkbox"], input[type="radio"], input[type="file"] {
  -moz-user-select: all !important;
  -moz-user-input: none !important;
  -moz-user-focus: none !important;
}

input[type="hidden"] {
  border: 1px solid black !important;
  visibility: visible !important;
}

label {
    -moz-user-select: all !important;
}

::-moz-display-comboboxcontrol-frame {
  -moz-user-select: text !important;
}

option {
  -moz-user-select: text !important;
}

#mozToc.readonly {
  -moz-user-select: all !important;
  -moz-user-input: none !important;
}

/* the following rules are for Image Resizing */

span[\_moz_anonclass="mozResizer"] {
  width: 5px;
  height: 5px;
  position: absolute;
  border: 1px black solid;
  background-color: white;
  -moz-user-select: none;
  z-index: 2147483646; /* max value -1 for this property */
}

/* we can't use :active below */
span[\_moz_anonclass="mozResizer"][\_moz_activated],
span[\_moz_anonclass="mozResizer"]:hover {
  background-color: black;
}

span[\_moz_anonclass="mozResizer"].hidden,
span[\_moz_anonclass="mozResizingShadow"].hidden,
img[\_moz_anonclass="mozResizingShadow"].hidden,
span[\_moz_anonclass="mozGrabber"].hidden,
span[\_moz_anonclass="mozResizingInfo"].hidden,
a[\_moz_anonclass="mozTableRemoveRow"].hidden,
a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
  display: none !important;
}

span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
  cursor: nw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
  cursor: n-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
  cursor: ne-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
  cursor: w-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
  cursor: e-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
  cursor: sw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
  cursor: s-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
  cursor: se-resize;
}

span[\_moz_anonclass="mozResizingShadow"],
img[\_moz_anonclass="mozResizingShadow"] {
  outline: thin dashed black;
  -moz-user-select: none;
  opacity: 0.5;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

span[\_moz_anonclass="mozResizingInfo"] {
  font-family: sans-serif;
  font-size: x-small;
  color: black;
  background-color: #d0d0d0;
  border: ridge 2px #d0d0d0;
  padding: 2px;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

img[\_moz_resizing] {
  outline: thin solid black;
}

*[\_moz_abspos] {
  outline: silver ridge 2px;
  z-index: 2147483645 !important; /* max value -2 for this property */
}
*[\_moz_abspos="white"] {
  background-color: white !important;
}
*[\_moz_abspos="black"] {
  background-color: black !important;
}

span[\_moz_anonclass="mozGrabber"] {
  outline: ridge 2px silver;
  padding: 2px;
  position: absolute;
  width: 12px;
  height: 12px;
  background-image: url("resource://gre/res/grabber.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  cursor: move;
  z-index: 2147483647; /* max value for this property */
}

/* INLINE TABLE EDITING */

a[\_moz_anonclass="mozTableAddColumnBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
  background-image: url("resource://gre/res/table-add-column-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
  background-image: url("resource://gre/res/table-add-column-before-active.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
  background-image: url("resource://gre/res/table-add-column-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
  background-image: url("resource://gre/res/table-add-column-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-column.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
  background-image: url("resource://gre/res/table-remove-column-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"]:active {
  background-image: url("resource://gre/res/table-remove-column-active.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
  background-image: url("resource://gre/res/table-add-row-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"]:active {
  background-image: url("resource://gre/res/table-add-row-before-active.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
  background-image: url("resource://gre/res/table-add-row-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"]:active {
  background-image: url("resource://gre/res/table-add-row-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-row.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none !important;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableRemoveRow"]:hover {
  background-image: url("resource://gre/res/table-remove-row-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"]:active {
  background-image: url("resource://gre/res/table-remove-row-active.gif");
}
PK
!<=lv&&res/contenteditable.css/* -*- 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/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

*|*::-moz-canvas {
  cursor: text;
}

*|*:-moz-read-write :-moz-read-only {
  -moz-user-select: all;
}

*|*:-moz-read-only > :-moz-read-write {
  /* override the above -moz-user-select: all rule. */
  -moz-user-select: -moz-text;
}

input:-moz-read-write > .anonymous-div:-moz-read-only,
textarea:-moz-read-write > .anonymous-div:-moz-read-only {
  -moz-user-select: text;
}

/* Use default arrow over objects with size that 
   are selected when clicked on.
   Override the browser's pointer cursor over links
*/

img:-moz-read-write, img:-moz-read-write[usemap], area:-moz-read-write,
object:-moz-read-write, object:-moz-read-write[usemap], 
applet:-moz-read-write, hr:-moz-read-write, button:-moz-read-write,
select:-moz-read-write,
a:-moz-read-write:link img, a:-moz-read-write:visited img,
a:-moz-read-write:active img, a:-moz-read-write:-moz-only-whitespace[name] {
  cursor: default;
}

*|*:any-link:-moz-read-write {
  cursor: text;
}

/* Prevent clicking on links from going to link */
a:link:-moz-read-write img, a:visited:-moz-read-write img,
a:active:-moz-read-write img {
  -moz-user-input: none;
}

/* We suppress user/author's prefs for link underline, 
   so we must set explicitly. This isn't good!
*/
a:link:-moz-read-write {
  color: -moz-hyperlinktext;
}

/* Allow double-clicks on these widgets to open properties dialogs
   XXX except when the widget has disabled attribute */
*|*:-moz-read-write > input:-moz-read-only,
*|*:-moz-read-write > button:-moz-read-only,
*|*:-moz-read-write > textarea:-moz-read-only {
  -moz-user-select: all;
  -moz-user-input: auto !important;
  -moz-user-focus: none !important;
}

/* XXX Still need a better way of blocking other events to these widgets */
select:-moz-read-write,
*|*:-moz-read-write > input:disabled,
*|*:-moz-read-write > input[type="checkbox"],
*|*:-moz-read-write > input[type="radio"],
*|*:-moz-read-write > input[type="file"],
input[contenteditable="true"]:disabled,
input[contenteditable="true"][type="checkbox"],
input[contenteditable="true"][type="radio"],
input[contenteditable="true"][type="file"] {
  -moz-user-select: all;
  -moz-user-input: none !important;
  -moz-user-focus: none !important;
}

/* emulation of non-standard HTML <marquee> tag */
marquee:-moz-read-write {
  -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-horizontal-editable');
}

marquee[direction="up"]:-moz-read-write, marquee[direction="down"]:-moz-read-write {
  -moz-binding: url('chrome://xbl-marquee/content/xbl-marquee.xml#marquee-vertical-editable');
}

*|*:-moz-read-write > input[type="hidden"],
input[contenteditable="true"][type="hidden"] {
  border: 1px solid black !important;
  visibility: visible !important;
}

label:-moz-read-write {
    -moz-user-select: all;
}

*|*::-moz-display-comboboxcontrol-frame {
  -moz-user-select: text;
}

option:-moz-read-write {
  -moz-user-select: text;
}

/* the following rules are for Image Resizing */

span[\_moz_anonclass="mozResizer"] {
  width: 5px;
  height: 5px;
  position: absolute;
  border: 1px black solid;
  background-color: white;
  -moz-user-select: none;
  z-index: 2147483646; /* max value -1 for this property */
}

/* we can't use :active below */
span[\_moz_anonclass="mozResizer"][\_moz_activated],
span[\_moz_anonclass="mozResizer"]:hover {
  background-color: black;
}

span[\_moz_anonclass="mozResizer"].hidden,
span[\_moz_anonclass="mozResizingShadow"].hidden,
img[\_moz_anonclass="mozResizingShadow"].hidden,
span[\_moz_anonclass="mozGrabber"].hidden,
span[\_moz_anonclass="mozResizingInfo"].hidden,
a[\_moz_anonclass="mozTableRemoveRow"].hidden,
a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
  display: none !important;
}

span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
  cursor: nw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
  cursor: n-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
  cursor: ne-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
  cursor: w-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
  cursor: e-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
  cursor: sw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
  cursor: s-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
  cursor: se-resize;
}

span[\_moz_anonclass="mozResizingShadow"],
img[\_moz_anonclass="mozResizingShadow"] {
  outline: thin dashed black;
  -moz-user-select: none;
  opacity: 0.5;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

span[\_moz_anonclass="mozResizingInfo"] {
  font-family: sans-serif;
  font-size: x-small;
  color: black;
  background-color: #d0d0d0;
  border: ridge 2px #d0d0d0;
  padding: 2px;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

img[\_moz_resizing] {
  outline: thin solid black;
}

*[\_moz_abspos] {
  outline: silver ridge 2px;
  z-index: 2147483645 !important; /* max value -2 for this property */
}
*[\_moz_abspos="white"] {
  background-color: white !important;
}
*[\_moz_abspos="black"] {
  background-color: black !important;
}

span[\_moz_anonclass="mozGrabber"] {
  outline: ridge 2px silver;
  padding: 2px;
  position: absolute;
  width: 12px;
  height: 12px;
  background-image: url("resource://gre/res/grabber.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  cursor: move;
}

/* INLINE TABLE EDITING */

a[\_moz_anonclass="mozTableAddColumnBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
  background-image: url("resource://gre/res/table-add-column-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
  background-image: url("resource://gre/res/table-add-column-before-active.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
  background-image: url("resource://gre/res/table-add-column-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
  background-image: url("resource://gre/res/table-add-column-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-column.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
  background-image: url("resource://gre/res/table-remove-column-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"]:active {
  background-image: url("resource://gre/res/table-remove-column-active.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
  background-image: url("resource://gre/res/table-add-row-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"]:active {
  background-image: url("resource://gre/res/table-add-row-before-active.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
  background-image: url("resource://gre/res/table-add-row-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"]:active {
  background-image: url("resource://gre/res/table-add-row-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-row.gif");
  background-repeat: no-repeat;
  background-position: center center;
  -moz-user-select: none;
  -moz-user-focus: none !important;
}

a[\_moz_anonclass="mozTableRemoveRow"]:hover {
  background-image: url("resource://gre/res/table-remove-row-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"]:active {
  background-image: url("resource://gre/res/table-remove-row-active.gif");
}
PK
!<`FFres/designmode.css/* -*- 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/. */

*|* {
  -moz-user-modify: read-write;
}
PK
!<3!res/ImageDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This CSS stylesheet defines the rules to be applied to any ImageDocuments,
 * including those in frames.
*/

@media not print {
  .overflowingVertical, .overflowingHorizontalOnly {
    cursor: zoom-out;
  }

  .shrinkToFit {
    cursor: zoom-in;
  }
}

@media print {
  /* We must declare the image as a block element. If we stay as
  an inline element, our parent LineBox will be inline too and
  ignore the available height during reflow.
  This is bad during printing, it means tall image frames won't know
  the size of the paper and cannot break into continuations along
  multiple pages. */
  img {
    display: block;
  }
}
PK
!<ÃEۭres/TopLevelImageDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
  This CSS stylesheet defines the rules to be applied to ImageDocuments that
  are top level (e.g. not iframes).
*/

@media not print {
  body {
    margin: 0;
  }

  img {
    text-align: center;
    position: absolute;
    margin: auto;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
  }

  img.overflowingVertical {
    /* If we're overflowing vertically, we need to set margin-top to
       0.  Otherwise we'll end up trying to vertically center, and end
       up cutting off the top part of the image. */
    margin-top: 0;
  }

  .completeRotation {
    transition: transform 0.3s ease 0s;
  }
}

img {
  image-orientation: from-image;
}
PK
!<Зres/TopLevelVideoDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
  This CSS stylesheet defines the rules to be applied to VideoDocuments that
  are top level (e.g. not iframes).
*/

body {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}

video {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  -moz-user-select: none;
}

video:focus {
  outline-width: 0;
}
PK
!<Ұ
i::%res/table-add-column-after-active.gifGIF89a!,&[J;PK
!<v7::$res/table-add-column-after-hover.gifGIF89a1!,@`"L`ƒ
;PK
!<x
::res/table-add-column-after.gifGIF89a!,@`"L`ƒ
;PK
!<Z(99&res/table-add-column-before-active.gifGIF89a!,
s Kf;PK
!<M99%res/table-add-column-before-hover.gifGIF89a1!,0 "ThaA;PK
!</99res/table-add-column-before.gifGIF89a!,0 "ThaA;PK
!<<Ed99"res/table-add-row-after-active.gifGIF89a!,
!܋;PK
!<K::!res/table-add-row-after-hover.gifGIF89a1!,(0B2;PK
!<ؿ::res/table-add-row-after.gifGIF89a!,(0B2;PK
!<ބw99#res/table-add-row-before-active.gifGIF89a!,
ZbQ;PK
!<99"res/table-add-row-before-hover.gifGIF89a1!,@A0aA!;PK
!<?499res/table-add-row-before.gifGIF89a!,@A0aA!;PK
!<	$CC"res/table-remove-column-active.gifGIF89a1!, @	"@aFlH`B
;PK
!<&II!res/table-remove-column-hover.gifGIF89a1!,&@
0`a†6\81"E*\x;PK
!<V)IIres/table-remove-column.gifGIF89a!,&@
0`a†6\81"E*\x;PK
!<	$CCres/table-remove-row-active.gifGIF89a1!, @	"@aFlH`B
;PK
!<&IIres/table-remove-row-hover.gifGIF89a1!,&@
0`a†6\81"E*\x;PK
!<V)IIres/table-remove-row.gifGIF89a!,&@
0`a†6\81"E*\x;PK
!<!rZZres/grabber.gifGIF89asss,?	HPD	 D	5rqȅ'H"D!j)`@;PK
!<8EhEhres/fonts/mathfont.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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
#  Do not translate anything in this file

# The ordered list of fonts with which to attempt to stretch MathML
# characters is controlled by setting pref("font.mathfont-family",
# "CMSY10, CMEX10, ...") for example, or by setting the font-family list in
# :-moz-math-stretchy in mathml.css.
#
# Note: setting base fonts for non-stretchy characters only works
# for operators that are ultimately handled by nsMathMLChar.
# @see how |useMathMLChar| is set in nsMathMLmoFrame::Stretch() & Paint().

# Operator Dictionary indexed on the "form" (i.e., infix, prefix, or suffix).
# Each entry lists the attributes of the operator, using its Unicode format.

operator.\u0021.postfix = lspace:1 rspace:0 # !
operator.\u0021\u0021.postfix = lspace:1 rspace:0 # !!
operator.\u0021\u003D.infix = lspace:4 rspace:4 # !=
operator.\u0025.infix = lspace:3 rspace:3 # percent sign
operator.\u0026.postfix = lspace:0 rspace:0 # &amp;
operator.\u0026\u0026.infix = lspace:4 rspace:4 # &amp;&amp;
operator.\u0027.postfix = lspace:0 rspace:0 accent # '
operator.\u0028.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # (
operator.\u0029.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # )
operator.\u002A.infix = lspace:3 rspace:3 # *
operator.\u002A\u002A.infix = lspace:1 rspace:1 # **
operator.\u002A\u003D.infix = lspace:4 rspace:4 # *=
operator.\u002B.infix = lspace:4 rspace:4 # +
operator.\u002B.prefix = lspace:0 rspace:1 # +
operator.\u002B\u002B.postfix = lspace:0 rspace:0 # ++
operator.\u002B\u003D.infix = lspace:4 rspace:4 # +=
operator.\u002C.infix = lspace:0 rspace:3 separator # ,
operator.\u002D.infix = lspace:4 rspace:4 # -
operator.\u002D.prefix = lspace:0 rspace:1 # -
operator.\u002D\u002D.postfix = lspace:0 rspace:0 # --
operator.\u002D\u003D.infix = lspace:4 rspace:4 # -=
operator.\u002D\u003E.infix = lspace:5 rspace:5 # ->
operator.\u002E.infix = lspace:3 rspace:3 # .
operator.\u002E\u002E.postfix = lspace:0 rspace:0 # ..
operator.\u002E\u002E\u002E.postfix = lspace:0 rspace:0 # ...
operator.\u002F.infix = lspace:1 rspace:1 direction:vertical # solidus
operator.\u002F\u002F.infix = lspace:1 rspace:1 # //
operator.\u002F\u003D.infix = lspace:4 rspace:4 # /=
operator.\u003A.infix = lspace:1 rspace:2 # :
operator.\u003A\u003D.infix = lspace:4 rspace:4 # :=
operator.\u003B.infix = lspace:0 rspace:3 separator # ;
operator.\u003C.infix = lspace:5 rspace:5 # &lt;
operator.\u003C\u003D.infix = lspace:5 rspace:5 # &lt;=
operator.\u003C\u003E.infix = lspace:1 rspace:1 # &lt;>
operator.\u003D.infix = lspace:5 rspace:5 direction:horizontal # =
operator.\u003D\u003D.infix = lspace:4 rspace:4 # ==
operator.\u003E.infix = lspace:5 rspace:5 # >
operator.\u003E\u003D.infix = lspace:5 rspace:5 # >=
operator.\u003F.infix = lspace:1 rspace:1 # ?
operator.\u0040.infix = lspace:1 rspace:1 # @
operator.\u005B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # [
operator.\u005C.infix = lspace:0 rspace:0 # reverse solidus
operator.\u005D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # ]
operator.\u005E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hat; circumflex accent
operator.\u005E.infix = lspace:1 rspace:1 direction:horizontal # ^
operator.\u005F.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # _ low line
operator.\u005F.infix = lspace:1 rspace:1 stretchy direction:horizontal # _ low line
operator.\u0060.postfix = lspace:0 rspace:0 accent # &DiacriticalGrave;
operator.\u007B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # {
operator.\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # &VerticalLine; |
operator.\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
operator.\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
operator.\u007C\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # ||
operator.\u007C\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: ||
operator.\u007C\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: ||
operator.\u007C\u007C\u007C.infix = lspace:2 rspace:2 stretchy fence symmetric direction:vertical # multiple character operator: |||
operator.\u007C\u007C\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: |||
operator.\u007C\u007C\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # multiple character operator: |||
operator.\u007D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical mirrorable # }
operator.\u007E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ~ tilde
operator.\u00A8.postfix = lspace:0 rspace:0 accent # &DoubleDot;
operator.\u00AC.prefix = lspace:2 rspace:1 # not sign
operator.\u00AF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBar;
operator.\u00B0.postfix = lspace:0 rspace:0 # degree sign
operator.\u00B1.infix = lspace:4 rspace:4 # &PlusMinus;
operator.\u00B1.prefix = lspace:0 rspace:1 # &PlusMinus;
operator.\u00B4.postfix = lspace:0 rspace:0 accent # &DiacriticalAcute;
operator.\u00B7.infix = lspace:4 rspace:4 # &CenterDot;
operator.\u00B8.postfix = lspace:0 rspace:0 accent # &Cedilla;
operator.\u00D7.infix = lspace:4 rspace:4 # multiplication sign
operator.\u00F7.infix = lspace:4 rspace:4 # division sign
operator.\u02C6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter circumflex accent
operator.\u02C7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hacek; caron
operator.\u02C9.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter macron
operator.\u02CA.postfix = lspace:0 rspace:0 accent # modifier letter acute accent
operator.\u02CB.postfix = lspace:0 rspace:0 accent # modifier letter grave accent
operator.\u02CD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low macron
operator.\u02D8.postfix = lspace:0 rspace:0 accent # &Breve;
operator.\u02D9.postfix = lspace:0 rspace:0 accent # &DiacriticalDot;
operator.\u02DA.postfix = lspace:0 rspace:0 accent # ring above
operator.\u02DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &DiacriticalTilde; small tilde
operator.\u02DD.postfix = lspace:0 rspace:0 accent # &DiacriticalDoubleAcute;
operator.\u02F7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low tilde
operator.\u0302.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # combining circumflex accent
operator.\u0311.postfix = lspace:0 rspace:0 accent # &DownBreve;
operator.\u03F6.infix = lspace:5 rspace:5 # greek reversed lunate epsilon symbol
operator.\u2016.prefix = lspace:0 rspace:0 stretchy fence direction:vertical # &Vert; &Verbar;
operator.\u2016.postfix = lspace:0 rspace:0 stretchy fence direction:vertical # &Vert; &Verbar;
operator.\u2018.prefix = lspace:0 rspace:0 fence mirrorable # &OpenCurlyQuote;
operator.\u2019.postfix = lspace:0 rspace:0 fence mirrorable # &CloseCurlyQuote;
operator.\u201C.prefix = lspace:0 rspace:0 fence mirrorable # &OpenCurlyDoubleQuote;
operator.\u201D.postfix = lspace:0 rspace:0 fence mirrorable # &CloseCurlyDoubleQuote;
operator.\u2022.infix = lspace:4 rspace:4 # bullet
operator.\u2026.infix = lspace:0 rspace:0 # horizontal ellipsis
operator.\u2032.postfix = lspace:0 rspace:2 # prime
operator.\u203E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # overline
operator.\u2044.infix = lspace:4 rspace:4 stretchy direction:vertical # fraction slash
operator.\u2061.infix = lspace:0 rspace:0 # &ApplyFunction;
operator.\u2062.infix = lspace:0 rspace:0 # &InvisibleTimes;
operator.\u2063.infix = lspace:0 rspace:0 separator # &InvisibleComma;
operator.\u2064.infix = lspace:0 rspace:0 # invisible plus
operator.\u20DB.postfix = lspace:0 rspace:0 accent # &TripleDot;
operator.\u20DC.postfix = lspace:0 rspace:0 accent # combining four dots above
operator.\u2145.prefix = lspace:2 rspace:1 # &CapitalDifferentialD;
operator.\u2146.prefix = lspace:2 rspace:0 # &DifferentialD;
operator.\u2190.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrow;
operator.\u2191.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrow;
operator.\u2192.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrow;
operator.\u2193.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrow;
operator.\u2194.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightArrow;
operator.\u2195.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpDownArrow;
operator.\u2196.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpperLeftArrow;
operator.\u2197.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpperRightArrow;
operator.\u2198.infix = lspace:5 rspace:5 stretchy direction:horizontal # &LowerRightArrow;
operator.\u2199.infix = lspace:5 rspace:5 stretchy direction:horizontal # &LowerLeftArrow;
operator.\u219A.infix = lspace:5 rspace:5 accent # leftwards arrow with stroke
operator.\u219B.infix = lspace:5 rspace:5 accent # rightwards arrow with stroke
operator.\u219C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards wave arrow
operator.\u219D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards wave arrow
operator.\u219E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards two headed arrow
operator.\u219F.infix = lspace:5 rspace:5 stretchy accent direction:vertical # upwards two headed arrow
operator.\u21A0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two headed arrow
operator.\u21A1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards two headed arrow
operator.\u21A2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with tail
operator.\u21A3.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with tail
operator.\u21A4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeArrow;
operator.\u21A5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpTeeArrow;
operator.\u21A6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeArrow;
operator.\u21A7.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownTeeArrow;
operator.\u21A8.infix = lspace:5 rspace:5 stretchy direction:vertical # up down arrow with base
operator.\u21A9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookleftarrow; &larrhk;
operator.\u21AA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookrightarrow; &rarrhk;
operator.\u21AB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with loop
operator.\u21AC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with loop
operator.\u21AD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right wave arrow
operator.\u21AE.infix = lspace:5 rspace:5 accent # left right arrow with stroke
operator.\u21AF.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards zigzag arrow
operator.\u21B0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip leftwards
operator.\u21B1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip rightwards
operator.\u21B2.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip leftwards
operator.\u21B3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip rightwards
operator.\u21B4.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards arrow with corner downwards
operator.\u21B5.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with corner leftwards
operator.\u21B6.infix = lspace:5 rspace:5 accent # anticlockwise top semicircle arrow
operator.\u21B7.infix = lspace:5 rspace:5 accent # clockwise top semicircle arrow
operator.\u21B8.infix = lspace:5 rspace:5 # north west arrow to long bar
operator.\u21B9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow to bar over rightwards arrow to bar
operator.\u21BA.infix = lspace:5 rspace:5 # anticlockwise open circle arrow
operator.\u21BB.infix = lspace:5 rspace:5 # clockwise open circle arrow
operator.\u21BC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVector;
operator.\u21BD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftVector;
operator.\u21BE.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVector;
operator.\u21BF.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVector;
operator.\u21C0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVector;
operator.\u21C1.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightVector;
operator.\u21C2.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVector;
operator.\u21C3.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVector;
operator.\u21C4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowLeftArrow;
operator.\u21C5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowDownArrow;
operator.\u21C6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowRightArrow;
operator.\u21C7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards paired arrows
operator.\u21C8.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards paired arrows
operator.\u21C9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards paired arrows
operator.\u21CA.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards paired arrows
operator.\u21CB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &ReverseEquilibrium;
operator.\u21CC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Equilibrium;
operator.\u21CD.infix = lspace:5 rspace:5 accent # leftwards double arrow with stroke
operator.\u21CE.infix = lspace:5 rspace:5 accent # left right double arrow with stroke
operator.\u21CF.infix = lspace:5 rspace:5 accent # rightwards double arrow with stroke
operator.\u21D0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftArrow;
operator.\u21D1.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpArrow;
operator.\u21D2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Implies; &DoubleRightArrow;
operator.\u21D3.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleDownArrow;
operator.\u21D4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftRightArrow;
operator.\u21D5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpDownArrow;
operator.\u21DA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple arrow
operator.\u21DB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple arrow
operator.\u21DC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards squiggle arrow
operator.\u21DD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards squiggle arrow
operator.\u21DE.infix = lspace:5 rspace:5 # upwards arrow with double stroke
operator.\u21DF.infix = lspace:5 rspace:5 # downwards arrow with double stroke
operator.\u21E0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards dashed arrow
operator.\u21E1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards dashed arrow
operator.\u21E2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards dashed arrow
operator.\u21E3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards dashed arrow
operator.\u21E4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowBar;
operator.\u21E5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowBar;
operator.\u21E6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards white arrow
operator.\u21E7.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow
operator.\u21E8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow
operator.\u21E9.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards white arrow
operator.\u21EA.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow from bar
operator.\u21EB.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal
operator.\u21EC.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with horizontal bar
operator.\u21ED.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with vertical bar
operator.\u21EE.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow
operator.\u21EF.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow on pedestal
operator.\u21F0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow from wall
operator.\u21F1.infix = lspace:5 rspace:5 # north west arrow to corner
operator.\u21F2.infix = lspace:5 rspace:5 # south east arrow to corner
operator.\u21F3.infix = lspace:5 rspace:5 stretchy direction:vertical # up down white arrow
operator.\u21F4.infix = lspace:5 rspace:5 accent # right arrow with small circle
operator.\u21F5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowUpArrow;
operator.\u21F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # three rightwards arrows
operator.\u21F7.infix = lspace:5 rspace:5 accent # leftwards arrow with vertical stroke
operator.\u21F8.infix = lspace:5 rspace:5 accent # rightwards arrow with vertical stroke
operator.\u21F9.infix = lspace:5 rspace:5 accent # left right arrow with vertical stroke
operator.\u21FA.infix = lspace:5 rspace:5 accent # leftwards arrow with double vertical stroke
operator.\u21FB.infix = lspace:5 rspace:5 accent # rightwards arrow with double vertical stroke
operator.\u21FC.infix = lspace:5 rspace:5 accent # left right arrow with double vertical stroke
operator.\u21FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards open-headed arrow
operator.\u21FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards open-headed arrow
operator.\u21FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right open-headed arrow
operator.\u2200.prefix = lspace:2 rspace:1 # &ForAll;
operator.\u2201.infix = lspace:1 rspace:2 # complement
operator.\u2202.prefix = lspace:2 rspace:1 # &PartialD;
operator.\u2203.prefix = lspace:2 rspace:1 # &Exists;
operator.\u2204.prefix = lspace:2 rspace:1 # &NotExists;
operator.\u2206.infix = lspace:3 rspace:3 # increment
operator.\u2207.prefix = lspace:2 rspace:1 # &Del;
operator.\u2208.infix = lspace:5 rspace:5 # &Element;
operator.\u2209.infix = lspace:5 rspace:5 # &NotElement;
operator.\u220A.infix = lspace:5 rspace:5 # small element of
operator.\u220B.infix = lspace:5 rspace:5 # &SuchThat; &ReverseElement;
operator.\u220C.infix = lspace:5 rspace:5 # &NotReverseElement;
operator.\u220D.infix = lspace:5 rspace:5 # small contains as member
operator.\u220E.infix = lspace:3 rspace:3 # end of proof
operator.\u220F.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Product;
operator.\u2210.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Coproduct;
operator.\u2211.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical mirrorable # &Sum;
operator.\u2212.infix = lspace:4 rspace:4 # official Unicode minus sign
operator.\u2212.prefix = lspace:0 rspace:1 # official Unicode minus sign
operator.\u2213.infix = lspace:4 rspace:4 # &MinusPlus;
operator.\u2213.prefix = lspace:0 rspace:1 # &MinusPlus;
operator.\u2214.infix = lspace:4 rspace:4 # dot plus
operator.\u2215.infix = lspace:4 rspace:4 stretchy mirrorable direction:vertical # division slash
operator.\u2216.infix = lspace:4 rspace:4 direction:vertical # set minus
operator.\u2217.infix = lspace:4 rspace:4 # asterisk operator
operator.\u2218.infix = lspace:4 rspace:4 # &SmallCircle;
operator.\u2219.infix = lspace:4 rspace:4 # bullet operator
operator.\u221A.prefix = lspace:1 rspace:1 stretchy direction:vertical mirrorable # &Sqrt;
operator.\u221B.prefix = lspace:1 rspace:1 # cube root
operator.\u221C.prefix = lspace:1 rspace:1 # fourth root
operator.\u221D.infix = lspace:5 rspace:5 # &Proportional;
operator.\u221F.infix = lspace:5 rspace:5 # right angle
operator.\u2220.prefix = lspace:0 rspace:0 # angle
operator.\u2221.prefix = lspace:0 rspace:0 # measured angle
operator.\u2222.prefix = lspace:0 rspace:0 # spherical angle
operator.\u2223.infix = lspace:5 rspace:5 direction:vertical # divides
operator.\u2224.infix = lspace:5 rspace:5 # &NotVerticalBar;
operator.\u2225.infix = lspace:5 rspace:5 direction:vertical # parallel to
operator.\u2226.infix = lspace:5 rspace:5 # &NotDoubleVerticalBar;
operator.\u2227.infix = lspace:4 rspace:4 # &wedge;
operator.\u2228.infix = lspace:4 rspace:4 # &vee;
operator.\u2229.infix = lspace:4 rspace:4 # &cap;
operator.\u222A.infix = lspace:4 rspace:4 # &cup;
operator.\u222B.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &Integral;
operator.\u222C.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # double integral
operator.\u222D.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # triple integral
operator.\u222E.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &ContourIntegral;
operator.\u222F.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # &DoubleContourIntegral;
operator.\u2230.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # volume integral
operator.\u2231.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # clockwise integral
operator.\u2232.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # &ClockwiseContourIntegral;
operator.\u2233.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral # &CounterClockwiseContourIntegral;
operator.\u2234.infix = lspace:5 rspace:5 # &Therefore;
operator.\u2235.infix = lspace:5 rspace:5 # &Because;
operator.\u2236.infix = lspace:5 rspace:5 # ratio
operator.\u2237.infix = lspace:5 rspace:5 # &Colon; &Proportion;
operator.\u2238.infix = lspace:4 rspace:4 # dot minus
operator.\u2239.infix = lspace:5 rspace:5 # excess
operator.\u223A.infix = lspace:4 rspace:4 # geometric proportion
operator.\u223B.infix = lspace:5 rspace:5 # homothetic
operator.\u223C.infix = lspace:5 rspace:5 # &Tilde;
operator.\u223D.infix = lspace:5 rspace:5 # reversed tilde
operator.\u223D\u0331.infix = lspace:3 rspace:3 # reversed tilde with underline
operator.\u223E.infix = lspace:5 rspace:5 # inverted lazy s
operator.\u223F.infix = lspace:3 rspace:3 # sine wave
operator.\u2240.infix = lspace:4 rspace:4 # &VerticalTilde;
operator.\u2241.infix = lspace:5 rspace:5 # &NotTilde;
operator.\u2242.infix = lspace:5 rspace:5 # &EqualTilde;
operator.\u2242\u0338.infix = lspace:5 rspace:5 # &NotEqualTilde;
operator.\u2243.infix = lspace:5 rspace:5 # &TildeEqual;
operator.\u2244.infix = lspace:5 rspace:5 # &NotTildeEqual;
operator.\u2245.infix = lspace:5 rspace:5 # &TildeFullEqual;
operator.\u2246.infix = lspace:5 rspace:5 # approximately but not actually equal to
operator.\u2247.infix = lspace:5 rspace:5 # &NotTildeFullEqual;
operator.\u2248.infix = lspace:5 rspace:5 # &TildeTilde;
operator.\u2249.infix = lspace:5 rspace:5 # &NotTildeTilde;
operator.\u224A.infix = lspace:5 rspace:5 # almost equal or equal to
operator.\u224B.infix = lspace:5 rspace:5 # triple tilde
operator.\u224C.infix = lspace:5 rspace:5 # all equal to
operator.\u224D.infix = lspace:5 rspace:5 # &CupCap;
operator.\u224E.infix = lspace:5 rspace:5 # &HumpDownHump;
operator.\u224E\u0338.infix = lspace:5 rspace:5 # &NotHumpDownHump;
operator.\u224F.infix = lspace:5 rspace:5 # &HumpEqual;
operator.\u224F\u0338.infix = lspace:5 rspace:5 # &NotHumpEqual;
operator.\u2250.infix = lspace:5 rspace:5 # &DotEqual;
operator.\u2251.infix = lspace:5 rspace:5 # geometrically equal to
operator.\u2252.infix = lspace:5 rspace:5 # approximately equal to or the image of
operator.\u2253.infix = lspace:5 rspace:5 # image of or approximately equal to
operator.\u2254.infix = lspace:5 rspace:5 # &Assign;
operator.\u2255.infix = lspace:5 rspace:5 # equals colon
operator.\u2256.infix = lspace:5 rspace:5 # ring in equal to
operator.\u2257.infix = lspace:5 rspace:5 # ring equal to
operator.\u2258.infix = lspace:5 rspace:5 # corresponds to
operator.\u2259.infix = lspace:5 rspace:5 # estimates
operator.\u225A.infix = lspace:5 rspace:5 # equiangular to
operator.\u225C.infix = lspace:5 rspace:5 # delta equal to
operator.\u225D.infix = lspace:5 rspace:5 # equal to by definition
operator.\u225E.infix = lspace:5 rspace:5 # measured by
operator.\u225F.infix = lspace:5 rspace:5 # questioned equal to
operator.\u2260.infix = lspace:5 rspace:5 # &NotEqual;
operator.\u2261.infix = lspace:5 rspace:5 # &Congruent;
operator.\u2262.infix = lspace:5 rspace:5 # &NotCongruent;
operator.\u2263.infix = lspace:5 rspace:5 # strictly equivalent to
operator.\u2264.infix = lspace:5 rspace:5 # &le;
operator.\u2265.infix = lspace:5 rspace:5 # &GreaterEqual;
operator.\u2266.infix = lspace:5 rspace:5 # &LessFullEqual;
operator.\u2266\u0338.infix = lspace:5 rspace:5 # &NotGreaterFullEqual;
operator.\u2267.infix = lspace:5 rspace:5 # &GreaterFullEqual;
operator.\u2268.infix = lspace:5 rspace:5 # less-than but not equal to
operator.\u2269.infix = lspace:5 rspace:5 # greater-than but not equal to
operator.\u226A.infix = lspace:5 rspace:5 # &NestedLessLess;
operator.\u226A\u0338.infix = lspace:5 rspace:5 # &NotLessLess;
operator.\u226B.infix = lspace:5 rspace:5 # &NestedGreaterGreater;
operator.\u226B\u0338.infix = lspace:5 rspace:5 # &NotGreaterGreater;
operator.\u226C.infix = lspace:5 rspace:5 # between
operator.\u226D.infix = lspace:5 rspace:5 # &NotCupCap;
operator.\u226E.infix = lspace:5 rspace:5 # &NotLess;
operator.\u226F.infix = lspace:5 rspace:5 # &NotGreater;
operator.\u2270.infix = lspace:5 rspace:5 # &NotLessEqual;
operator.\u2271.infix = lspace:5 rspace:5 # &NotGreaterEqual;
operator.\u2272.infix = lspace:5 rspace:5 # &LessTilde;
operator.\u2273.infix = lspace:5 rspace:5 # &GreaterTilde;
operator.\u2274.infix = lspace:5 rspace:5 # &NotLessTilde;
operator.\u2275.infix = lspace:5 rspace:5 # &NotGreaterTilde;
operator.\u2276.infix = lspace:5 rspace:5 # &LessGreater;
operator.\u2277.infix = lspace:5 rspace:5 # &GreaterLess;
operator.\u2278.infix = lspace:5 rspace:5 # &NotLessGreater;
operator.\u2279.infix = lspace:5 rspace:5 # &NotGreaterLess;
operator.\u227A.infix = lspace:5 rspace:5 # &Precedes;
operator.\u227B.infix = lspace:5 rspace:5 # &Succeeds;
operator.\u227C.infix = lspace:5 rspace:5 # &PrecedesSlantEqual;
operator.\u227D.infix = lspace:5 rspace:5 # &SucceedsSlantEqual;
operator.\u227E.infix = lspace:5 rspace:5 # &PrecedesTilde;
operator.\u227F.infix = lspace:5 rspace:5 # &SucceedsTilde;
operator.\u227F\u0338.infix = lspace:5 rspace:5 # &NotSucceedsTilde;
operator.\u2280.infix = lspace:5 rspace:5 # &NotPrecedes;
operator.\u2281.infix = lspace:5 rspace:5 # &NotSucceeds;
operator.\u2282.infix = lspace:5 rspace:5 # &subset;
operator.\u2282\u20D2.infix = lspace:5 rspace:5 # subset of with vertical line
operator.\u2283.infix = lspace:5 rspace:5 # &Superset;
operator.\u2283\u20D2.infix = lspace:5 rspace:5 # superset of with vertical line
operator.\u2284.infix = lspace:5 rspace:5 # &nsub;
operator.\u2285.infix = lspace:5 rspace:5 # &nsup;
operator.\u2286.infix = lspace:5 rspace:5 # &SubsetEqual;
operator.\u2287.infix = lspace:5 rspace:5 # &SupersetEqual;
operator.\u2288.infix = lspace:5 rspace:5 # &NotSubsetEqual;
operator.\u2289.infix = lspace:5 rspace:5 # &NotSupersetEqual;
operator.\u228A.infix = lspace:5 rspace:5 # &subsetneq; &subne;
operator.\u228B.infix = lspace:5 rspace:5 # superset of with not equal to
operator.\u228C.infix = lspace:4 rspace:4 # multiset
operator.\u228D.infix = lspace:4 rspace:4 # multiset multiplication
operator.\u228E.infix = lspace:4 rspace:4 direction:vertical # &UnionPlus;
operator.\u228F.infix = lspace:5 rspace:5 # &SquareSubset;
operator.\u228F\u0338.infix = lspace:5 rspace:5 # &NotSquareSubset;
operator.\u2290.infix = lspace:5 rspace:5 # &SquareSuperset;
operator.\u2290\u0338.infix = lspace:5 rspace:5 # &NotSquareSuperset;
operator.\u2291.infix = lspace:5 rspace:5 # &SquareSubsetEqual;
operator.\u2292.infix = lspace:5 rspace:5 # &SquareSupersetEqual;
operator.\u2293.infix = lspace:4 rspace:4 direction:vertical # &SquareIntersection;
operator.\u2294.infix = lspace:4 rspace:4 direction:vertical # &SquareUnion;
operator.\u2295.infix = lspace:4 rspace:4 # &CirclePlus;
operator.\u2296.infix = lspace:4 rspace:4 # &CircleMinus;
operator.\u2297.infix = lspace:4 rspace:4 # &CircleTimes;
operator.\u2298.infix = lspace:4 rspace:4 # circled division slash
operator.\u2299.infix = lspace:4 rspace:4 # &CircleDot;
operator.\u229A.infix = lspace:4 rspace:4 # circled ring operator
operator.\u229B.infix = lspace:4 rspace:4 # circled asterisk operator
operator.\u229C.infix = lspace:4 rspace:4 # circled equals
operator.\u229D.infix = lspace:4 rspace:4 # circled dash
operator.\u229E.infix = lspace:4 rspace:4 # squared plus
operator.\u229F.infix = lspace:4 rspace:4 # squared minus
operator.\u22A0.infix = lspace:4 rspace:4 # squared times
operator.\u22A1.infix = lspace:4 rspace:4 # squared dot operator
operator.\u22A2.infix = lspace:5 rspace:5 # &RightTee;
operator.\u22A3.infix = lspace:5 rspace:5 # &LeftTee;
operator.\u22A4.infix = lspace:5 rspace:5 # &DownTee;
operator.\u22A5.infix = lspace:5 rspace:5 # &UpTee;
operator.\u22A6.infix = lspace:5 rspace:5 # assertion
operator.\u22A7.infix = lspace:5 rspace:5 # models
operator.\u22A8.infix = lspace:5 rspace:5 # &DoubleRightTee;
operator.\u22A9.infix = lspace:5 rspace:5 # forces
operator.\u22AA.infix = lspace:5 rspace:5 # triple vertical bar right turnstile
operator.\u22AB.infix = lspace:5 rspace:5 # double vertical bar double right turnstile
operator.\u22AC.infix = lspace:5 rspace:5 # does not prove
operator.\u22AD.infix = lspace:5 rspace:5 # not true
operator.\u22AE.infix = lspace:5 rspace:5 # does not force
operator.\u22AF.infix = lspace:5 rspace:5 # negated double vertical bar double right turnstile
operator.\u22B0.infix = lspace:5 rspace:5 # precedes under relation
operator.\u22B1.infix = lspace:5 rspace:5 # succeeds under relation
operator.\u22B2.infix = lspace:5 rspace:5 # &LeftTriangle;
operator.\u22B3.infix = lspace:5 rspace:5 # &RightTriangle;
operator.\u22B4.infix = lspace:5 rspace:5 # &LeftTriangleEqual;
operator.\u22B5.infix = lspace:5 rspace:5 # &RightTriangleEqual;
operator.\u22B6.infix = lspace:5 rspace:5 # original of
operator.\u22B7.infix = lspace:5 rspace:5 # image of
operator.\u22B8.infix = lspace:5 rspace:5 # multimap
operator.\u22B9.infix = lspace:5 rspace:5 # hermitian conjugate matrix
operator.\u22BA.infix = lspace:4 rspace:4 # intercalate
operator.\u22BB.infix = lspace:4 rspace:4 # xor
operator.\u22BC.infix = lspace:4 rspace:4 # nand
operator.\u22BD.infix = lspace:4 rspace:4 # nor
operator.\u22BE.infix = lspace:3 rspace:3 # right angle with arc
operator.\u22BF.infix = lspace:3 rspace:3 # right triangle
operator.\u22C0.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Wedge;
operator.\u22C1.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Vee;
operator.\u22C2.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Intersection;
operator.\u22C3.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &Union;
operator.\u22C4.infix = lspace:4 rspace:4 # &Diamond;
operator.\u22C5.infix = lspace:4 rspace:4 # &cdot;
operator.\u22C6.infix = lspace:4 rspace:4 # &Star;
operator.\u22C7.infix = lspace:4 rspace:4 # division times
operator.\u22C8.infix = lspace:5 rspace:5 # bowtie
operator.\u22C9.infix = lspace:4 rspace:4 # left normal factor semidirect product
operator.\u22CA.infix = lspace:4 rspace:4 # right normal factor semidirect product
operator.\u22CB.infix = lspace:4 rspace:4 # left semidirect product
operator.\u22CC.infix = lspace:4 rspace:4 # right semidirect product
operator.\u22CD.infix = lspace:5 rspace:5 # reversed tilde equals
operator.\u22CE.infix = lspace:4 rspace:4 # curly logical or
operator.\u22CF.infix = lspace:4 rspace:4 # curly logical and
operator.\u22D0.infix = lspace:5 rspace:5 # &Subset;
operator.\u22D1.infix = lspace:5 rspace:5 # double superset
operator.\u22D2.infix = lspace:4 rspace:4 # &Cap;
operator.\u22D3.infix = lspace:4 rspace:4 # &Cup;
operator.\u22D4.infix = lspace:5 rspace:5 # pitchfork
operator.\u22D5.infix = lspace:5 rspace:5 # equal and parallel to
operator.\u22D6.infix = lspace:5 rspace:5 # less-than with dot
operator.\u22D7.infix = lspace:5 rspace:5 # greater-than with dot
operator.\u22D8.infix = lspace:5 rspace:5 # very much less-than
operator.\u22D9.infix = lspace:5 rspace:5 # very much greater-than
operator.\u22DA.infix = lspace:5 rspace:5 # &LessEqualGreater;
operator.\u22DB.infix = lspace:5 rspace:5 # &GreaterEqualLess;
operator.\u22DC.infix = lspace:5 rspace:5 # equal to or less-than
operator.\u22DD.infix = lspace:5 rspace:5 # equal to or greater-than
operator.\u22DE.infix = lspace:5 rspace:5 # equal to or precedes
operator.\u22DF.infix = lspace:5 rspace:5 # equal to or succeeds
operator.\u22E0.infix = lspace:5 rspace:5 # &NotPrecedesSlantEqual;
operator.\u22E1.infix = lspace:5 rspace:5 # &NotSucceedsSlantEqual;
operator.\u22E2.infix = lspace:5 rspace:5 # &NotSquareSubsetEqual;
operator.\u22E3.infix = lspace:5 rspace:5 # &NotSquareSupersetEqual;
operator.\u22E4.infix = lspace:5 rspace:5 # square image of or not equal to
operator.\u22E5.infix = lspace:5 rspace:5 # square original of or not equal to
operator.\u22E6.infix = lspace:5 rspace:5 # less-than but not equivalent to
operator.\u22E7.infix = lspace:5 rspace:5 # greater-than but not equivalent to
operator.\u22E8.infix = lspace:5 rspace:5 # precedes but not equivalent to
operator.\u22E9.infix = lspace:5 rspace:5 # succeeds but not equivalent to
operator.\u22EA.infix = lspace:5 rspace:5 # &NotLeftTriangle;
operator.\u22EB.infix = lspace:5 rspace:5 # &NotRightTriangle;
operator.\u22EC.infix = lspace:5 rspace:5 # &NotLeftTriangleEqual;
operator.\u22ED.infix = lspace:5 rspace:5 # &NotRightTriangleEqual;
operator.\u22EE.infix = lspace:5 rspace:5 # vertical ellipsis
operator.\u22EF.infix = lspace:0 rspace:0 # midline horizontal ellipsis
operator.\u22F0.infix = lspace:5 rspace:5 # up right diagonal ellipsis
operator.\u22F1.infix = lspace:5 rspace:5 # down right diagonal ellipsis
operator.\u22F2.infix = lspace:5 rspace:5 # element of with long horizontal stroke
operator.\u22F3.infix = lspace:5 rspace:5 # element of with vertical bar at end of horizontal stroke
operator.\u22F4.infix = lspace:5 rspace:5 # small element of with vertical bar at end of horizontal stroke
operator.\u22F5.infix = lspace:5 rspace:5 # element of with dot above
operator.\u22F6.infix = lspace:5 rspace:5 # element of with overbar
operator.\u22F7.infix = lspace:5 rspace:5 # small element of with overbar
operator.\u22F8.infix = lspace:5 rspace:5 # element of with underbar
operator.\u22F9.infix = lspace:5 rspace:5 # element of with two horizontal strokes
operator.\u22FA.infix = lspace:5 rspace:5 # contains with long horizontal stroke
operator.\u22FB.infix = lspace:5 rspace:5 # contains with vertical bar at end of horizontal stroke
operator.\u22FC.infix = lspace:5 rspace:5 # small contains with vertical bar at end of horizontal stroke
operator.\u22FD.infix = lspace:5 rspace:5 # contains with overbar
operator.\u22FE.infix = lspace:5 rspace:5 # small contains with overbar
operator.\u22FF.infix = lspace:5 rspace:5 # z notation bag membership
operator.\u2308.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftCeiling;
operator.\u2309.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightCeiling;
operator.\u230A.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftFloor;
operator.\u230B.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightFloor;
operator.\u23B4.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBracket;
operator.\u23B5.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBracket;
operator.\u23DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (Unicode)
operator.\u23DD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (Unicode)
operator.\u23DE.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (Unicode)
operator.\u23DF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (Unicode)
operator.\u23E0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # top tortoise shell bracket
operator.\u23E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # bottom tortoise shell bracket
operator.\u25A0.infix = lspace:3 rspace:3 # black square
operator.\u25A1.infix = lspace:3 rspace:3 # white square
operator.\u25AA.infix = lspace:3 rspace:3 # black small square
operator.\u25AB.infix = lspace:3 rspace:3 # white small square
operator.\u25AD.infix = lspace:3 rspace:3 # white rectangle
operator.\u25AE.infix = lspace:3 rspace:3 # black vertical rectangle
operator.\u25AF.infix = lspace:3 rspace:3 # white vertical rectangle
operator.\u25B0.infix = lspace:3 rspace:3 # black parallelogram
operator.\u25B1.infix = lspace:3 rspace:3 # white parallelogram
operator.\u25B2.infix = lspace:4 rspace:4 # black up-pointing triangle
operator.\u25B3.infix = lspace:4 rspace:4 # white up-pointing triangle
operator.\u25B4.infix = lspace:4 rspace:4 # black up-pointing small triangle
operator.\u25B5.infix = lspace:4 rspace:4 # white up-pointing small triangle
operator.\u25B6.infix = lspace:4 rspace:4 # black right-pointing triangle
operator.\u25B7.infix = lspace:4 rspace:4 # white right-pointing triangle
operator.\u25B8.infix = lspace:4 rspace:4 # black right-pointing small triangle
operator.\u25B9.infix = lspace:4 rspace:4 # white right-pointing small triangle
operator.\u25BC.infix = lspace:4 rspace:4 # black down-pointing triangle
operator.\u25BD.infix = lspace:4 rspace:4 # white down-pointing triangle
operator.\u25BE.infix = lspace:4 rspace:4 # black down-pointing small triangle
operator.\u25BF.infix = lspace:4 rspace:4 # white down-pointing small triangle
operator.\u25C0.infix = lspace:4 rspace:4 # black left-pointing triangle
operator.\u25C1.infix = lspace:4 rspace:4 # white left-pointing triangle
operator.\u25C2.infix = lspace:4 rspace:4 # black left-pointing small triangle
operator.\u25C3.infix = lspace:4 rspace:4 # white left-pointing small triangle
operator.\u25C4.infix = lspace:4 rspace:4 # black left-pointing pointer
operator.\u25C5.infix = lspace:4 rspace:4 # white left-pointing pointer
operator.\u25C6.infix = lspace:4 rspace:4 # black diamond
operator.\u25C7.infix = lspace:4 rspace:4 # white diamond
operator.\u25C8.infix = lspace:4 rspace:4 # white diamond containing black small diamond
operator.\u25C9.infix = lspace:4 rspace:4 # fisheye
operator.\u25CC.infix = lspace:4 rspace:4 # dotted circle
operator.\u25CD.infix = lspace:4 rspace:4 # circle with vertical fill
operator.\u25CE.infix = lspace:4 rspace:4 # bullseye
operator.\u25CF.infix = lspace:4 rspace:4 # black circle
operator.\u25D6.infix = lspace:4 rspace:4 # left half black circle
operator.\u25D7.infix = lspace:4 rspace:4 # right half black circle
operator.\u25E6.infix = lspace:4 rspace:4 # white bullet
operator.\u266D.postfix = lspace:0 rspace:2 # music flat sign
operator.\u266E.postfix = lspace:0 rspace:2 # music natural sign
operator.\u266F.postfix = lspace:0 rspace:2 # music sharp sign
operator.\u2758.infix = lspace:5 rspace:5 direction:vertical # light vertical bar 
operator.\u2772.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # light left tortoise shell bracket ornament
operator.\u2773.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # light right tortoise shell bracket ornament
operator.\u27E6.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftDoubleBracket;
operator.\u27E7.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightDoubleBracket;
operator.\u27E8.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &LeftAngleBracket;
operator.\u27E9.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # &RightAngleBracket;
operator.\u27EA.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left double angle bracket
operator.\u27EB.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right double angle bracket
operator.\u27EC.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left white tortoise shell bracket
operator.\u27ED.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right white tortoise shell bracket
operator.\u27EE.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical left flattened parenthesis
operator.\u27EF.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # mathematical right flattened parenthesis
operator.\u27F0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards quadruple arrow
operator.\u27F1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards quadruple arrow
operator.\u27F5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftArrow;
operator.\u27F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongRightArrow;
operator.\u27F7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftRightArrow;
operator.\u27F8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftArrow;
operator.\u27F9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongRightArrow;
operator.\u27FA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftRightArrow;
operator.\u27FB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards arrow from bar
operator.\u27FC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards arrow from bar
operator.\u27FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards double arrow from bar
operator.\u27FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards double arrow from bar
operator.\u27FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards squiggle arrow
operator.\u2900.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with vertical stroke
operator.\u2901.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with double vertical stroke
operator.\u2902.infix = lspace:5 rspace:5 accent # leftwards double arrow with vertical stroke
operator.\u2903.infix = lspace:5 rspace:5 accent # rightwards double arrow with vertical stroke
operator.\u2904.infix = lspace:5 rspace:5 accent # left right double arrow with vertical stroke
operator.\u2905.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow from bar
operator.\u2906.infix = lspace:5 rspace:5 accent # leftwards double arrow from bar
operator.\u2907.infix = lspace:5 rspace:5 accent # rightwards double arrow from bar
operator.\u2908.infix = lspace:5 rspace:5 # downwards arrow with horizontal stroke
operator.\u2909.infix = lspace:5 rspace:5 # upwards arrow with horizontal stroke
operator.\u290A.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards triple arrow
operator.\u290B.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards triple arrow
operator.\u290C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards double dash arrow
operator.\u290D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards double dash arrow
operator.\u290E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple dash arrow
operator.\u290F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple dash arrow
operator.\u2910.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two-headed triple dash arrow
operator.\u2911.infix = lspace:5 rspace:5 accent # rightwards arrow with dotted stem
operator.\u2912.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowBar;
operator.\u2913.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowBar;
operator.\u2914.infix = lspace:5 rspace:5 accent # rightwards arrow with tail with vertical stroke
operator.\u2915.infix = lspace:5 rspace:5 accent # rightwards arrow with tail with double vertical stroke
operator.\u2916.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail
operator.\u2917.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail with vertical stroke
operator.\u2918.infix = lspace:5 rspace:5 accent # rightwards two-headed arrow with tail with double vertical stroke
operator.\u2919.infix = lspace:5 rspace:5 accent # leftwards arrow-tail
operator.\u291A.infix = lspace:5 rspace:5 accent # rightwards arrow-tail
operator.\u291B.infix = lspace:5 rspace:5 accent # leftwards double arrow-tail
operator.\u291C.infix = lspace:5 rspace:5 accent # rightwards double arrow-tail
operator.\u291D.infix = lspace:5 rspace:5 accent # leftwards arrow to black diamond
operator.\u291E.infix = lspace:5 rspace:5 accent # rightwards arrow to black diamond
operator.\u291F.infix = lspace:5 rspace:5 accent # leftwards arrow from bar to black diamond
operator.\u2920.infix = lspace:5 rspace:5 accent # rightwards arrow from bar to black diamond
operator.\u2923.infix = lspace:5 rspace:5 # north west arrow with hook
operator.\u2924.infix = lspace:5 rspace:5 # north east arrow with hook
operator.\u2925.infix = lspace:5 rspace:5 # south east arrow with hook
operator.\u2926.infix = lspace:5 rspace:5 # south west arrow with hook
operator.\u2927.infix = lspace:5 rspace:5 # north west arrow and north east arrow
operator.\u2928.infix = lspace:5 rspace:5 # north east arrow and south east arrow
operator.\u2929.infix = lspace:5 rspace:5 # south east arrow and south west arrow
operator.\u292A.infix = lspace:5 rspace:5 # south west arrow and north west arrow
operator.\u292B.infix = lspace:5 rspace:5 # rising diagonal crossing falling diagonal
operator.\u292C.infix = lspace:5 rspace:5 # falling diagonal crossing rising diagonal
operator.\u292D.infix = lspace:5 rspace:5 # south east arrow crossing north east arrow
operator.\u292E.infix = lspace:5 rspace:5 # north east arrow crossing south east arrow
operator.\u292F.infix = lspace:5 rspace:5 # falling diagonal crossing north east arrow
operator.\u2930.infix = lspace:5 rspace:5 # rising diagonal crossing south east arrow
operator.\u2931.infix = lspace:5 rspace:5 # north east arrow crossing north west arrow
operator.\u2932.infix = lspace:5 rspace:5 # north west arrow crossing north east arrow
operator.\u2933.infix = lspace:5 rspace:5 accent # wave arrow pointing directly right
operator.\u2934.infix = lspace:5 rspace:5 # arrow pointing rightwards then curving upwards
operator.\u2935.infix = lspace:5 rspace:5 # arrow pointing rightwards then curving downwards
operator.\u2936.infix = lspace:5 rspace:5 # arrow pointing downwards then curving leftwards
operator.\u2937.infix = lspace:5 rspace:5 # arrow pointing downwards then curving rightwards
operator.\u2938.infix = lspace:5 rspace:5 # right-side arc clockwise arrow
operator.\u2939.infix = lspace:5 rspace:5 # left-side arc anticlockwise arrow
operator.\u293A.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow
operator.\u293B.infix = lspace:5 rspace:5 accent # bottom arc anticlockwise arrow
operator.\u293C.infix = lspace:5 rspace:5 accent # top arc clockwise arrow with minus
operator.\u293D.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow with plus
operator.\u293E.infix = lspace:5 rspace:5 # lower right semicircular clockwise arrow
operator.\u293F.infix = lspace:5 rspace:5 # lower left semicircular anticlockwise arrow
operator.\u2940.infix = lspace:5 rspace:5 # anticlockwise closed circle arrow
operator.\u2941.infix = lspace:5 rspace:5 # clockwise closed circle arrow
operator.\u2942.infix = lspace:5 rspace:5 accent # rightwards arrow above short leftwards arrow
operator.\u2943.infix = lspace:5 rspace:5 accent # leftwards arrow above short rightwards arrow
operator.\u2944.infix = lspace:5 rspace:5 accent # short rightwards arrow above leftwards arrow
operator.\u2945.infix = lspace:5 rspace:5 accent # rightwards arrow with plus below
operator.\u2946.infix = lspace:5 rspace:5 accent # leftwards arrow with plus below
operator.\u2947.infix = lspace:5 rspace:5 accent # rightwards arrow through x
operator.\u2948.infix = lspace:5 rspace:5 accent # left right arrow through small circle
operator.\u2949.infix = lspace:5 rspace:5 # upwards two-headed arrow from small circle
operator.\u294A.infix = lspace:5 rspace:5 accent # left barb up right barb down harpoon
operator.\u294B.infix = lspace:5 rspace:5 accent # left barb down right barb up harpoon
operator.\u294C.infix = lspace:5 rspace:5 # up barb right down barb left harpoon
operator.\u294D.infix = lspace:5 rspace:5 # up barb left down barb right harpoon
operator.\u294E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightVector;
operator.\u294F.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpDownVector;
operator.\u2950.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftRightVector;
operator.\u2951.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpDownVector;
operator.\u2952.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVectorBar;
operator.\u2953.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVectorBar;
operator.\u2954.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVectorBar;
operator.\u2955.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVectorBar;
operator.\u2956.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownLeftVectorBar;
operator.\u2957.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownRightVectorBar;
operator.\u2958.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVectorBar;
operator.\u2959.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVectorBar;
operator.\u295A.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeVector;
operator.\u295B.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeVector;
operator.\u295C.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpTeeVector;
operator.\u295D.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownTeeVector;
operator.\u295E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftTeeVector;
operator.\u295F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightTeeVector;
operator.\u2960.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpTeeVector;
operator.\u2961.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownTeeVector;
operator.\u2962.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above leftwards harpoon with barb down
operator.\u2963.infix = lspace:5 rspace:5 # upwards harpoon with barb left beside upwards harpoon with barb right
operator.\u2964.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above rightwards harpoon with barb down
operator.\u2965.infix = lspace:5 rspace:5 # downwards harpoon with barb left beside downwards harpoon with barb right
operator.\u2966.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above rightwards harpoon with barb up
operator.\u2967.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb down above rightwards harpoon with barb down
operator.\u2968.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above leftwards harpoon with barb up
operator.\u2969.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb down above leftwards harpoon with barb down
operator.\u296A.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb up above long dash
operator.\u296B.infix = lspace:5 rspace:5 accent # leftwards harpoon with barb down below long dash
operator.\u296C.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb up above long dash
operator.\u296D.infix = lspace:5 rspace:5 accent # rightwards harpoon with barb down below long dash
operator.\u296E.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpEquilibrium;
operator.\u296F.infix = lspace:5 rspace:5 stretchy direction:vertical # &ReverseUpEquilibrium;
operator.\u2970.infix = lspace:5 rspace:5 accent # &RoundImplies;
operator.\u2971.infix = lspace:5 rspace:5 accent # equals sign above rightwards arrow
operator.\u2972.infix = lspace:5 rspace:5 accent # tilde operator above rightwards arrow
operator.\u2973.infix = lspace:5 rspace:5 accent # leftwards arrow above tilde operator
operator.\u2974.infix = lspace:5 rspace:5 accent # rightwards arrow above tilde operator
operator.\u2975.infix = lspace:5 rspace:5 accent # rightwards arrow above almost equal to
operator.\u2976.infix = lspace:5 rspace:5 accent # less-than above leftwards arrow
operator.\u2977.infix = lspace:5 rspace:5 accent # leftwards arrow through less-than
operator.\u2978.infix = lspace:5 rspace:5 accent # greater-than above rightwards arrow
operator.\u2979.infix = lspace:5 rspace:5 accent # subset above rightwards arrow
operator.\u297A.infix = lspace:5 rspace:5 accent # leftwards arrow through subset
operator.\u297B.infix = lspace:5 rspace:5 accent # superset above leftwards arrow
operator.\u297C.infix = lspace:5 rspace:5 accent # left fish tail
operator.\u297D.infix = lspace:5 rspace:5 accent # right fish tail
operator.\u297E.infix = lspace:5 rspace:5 # up fish tail
operator.\u297F.infix = lspace:5 rspace:5 # down fish tail
operator.\u2980.prefix = lspace:0 rspace:0 stretchy fence direction:vertical # triple direction:vertical bar delimiter
operator.\u2980.postfix = lspace:0 rspace:0 stretchy fence direction:vertical # triple direction:vertical bar delimiter
operator.\u2981.infix = lspace:3 rspace:3 # z notation spot
operator.\u2982.infix = lspace:3 rspace:3 # z notation type colon
operator.\u2983.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left white curly bracket
operator.\u2984.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right white curly bracket
operator.\u2985.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left white parenthesis
operator.\u2986.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right white parenthesis
operator.\u2987.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation left image bracket
operator.\u2988.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation right image bracket
operator.\u2989.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation left binding bracket
operator.\u298A.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # z notation right binding bracket
operator.\u298B.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with underbar
operator.\u298C.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with underbar
operator.\u298D.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with tick in top corner
operator.\u298E.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with tick in bottom corner
operator.\u298F.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left square bracket with tick in bottom corner
operator.\u2990.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right square bracket with tick in top corner
operator.\u2991.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left angle bracket with dot
operator.\u2992.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right angle bracket with dot
operator.\u2993.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left arc less-than bracket
operator.\u2994.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right arc greater-than bracket
operator.\u2995.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # double left arc greater-than bracket
operator.\u2996.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # double right arc less-than bracket
operator.\u2997.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left black tortoise shell bracket
operator.\u2998.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right black tortoise shell bracket
operator.\u2999.infix = lspace:3 rspace:3 # dotted fence
operator.\u299A.infix = lspace:3 rspace:3 # vertical zigzag line
operator.\u299B.infix = lspace:3 rspace:3 # measured angle opening left
operator.\u299C.infix = lspace:3 rspace:3 # right angle variant with square
operator.\u299D.infix = lspace:3 rspace:3 # measured right angle with dot
operator.\u299E.infix = lspace:3 rspace:3 # angle with s inside
operator.\u299F.infix = lspace:3 rspace:3 # acute angle
operator.\u29A0.infix = lspace:3 rspace:3 # spherical angle opening left
operator.\u29A1.infix = lspace:3 rspace:3 # spherical angle opening up
operator.\u29A2.infix = lspace:3 rspace:3 # turned angle
operator.\u29A3.infix = lspace:3 rspace:3 # reversed angle
operator.\u29A4.infix = lspace:3 rspace:3 # angle with underbar
operator.\u29A5.infix = lspace:3 rspace:3 # reversed angle with underbar
operator.\u29A6.infix = lspace:3 rspace:3 # oblique angle opening up
operator.\u29A7.infix = lspace:3 rspace:3 # oblique angle opening down
operator.\u29A8.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and right
operator.\u29A9.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and left
operator.\u29AA.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and right
operator.\u29AB.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and left
operator.\u29AC.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and up
operator.\u29AD.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and up
operator.\u29AE.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and down
operator.\u29AF.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and down
operator.\u29B0.infix = lspace:3 rspace:3 # reversed empty set
operator.\u29B1.infix = lspace:3 rspace:3 # empty set with overbar
operator.\u29B2.infix = lspace:3 rspace:3 # empty set with small circle above
operator.\u29B3.infix = lspace:3 rspace:3 # empty set with right arrow above
operator.\u29B4.infix = lspace:3 rspace:3 # empty set with left arrow above
operator.\u29B5.infix = lspace:3 rspace:3 # circle with horizontal bar
operator.\u29B6.infix = lspace:4 rspace:4 # circled vertical bar
operator.\u29B7.infix = lspace:4 rspace:4 # circled parallel
operator.\u29B8.infix = lspace:4 rspace:4 # circled reverse solidus
operator.\u29B9.infix = lspace:4 rspace:4 # circled perpendicular
operator.\u29BA.infix = lspace:4 rspace:4 # circle divided by horizontal bar and top half divided by vertical bar
operator.\u29BB.infix = lspace:4 rspace:4 # circle with superimposed x
operator.\u29BC.infix = lspace:4 rspace:4 # circled anticlockwise-rotated division sign
operator.\u29BD.infix = lspace:4 rspace:4 # up arrow through circle
operator.\u29BE.infix = lspace:4 rspace:4 # circled white bullet
operator.\u29BF.infix = lspace:4 rspace:4 # circled bullet
operator.\u29C0.infix = lspace:5 rspace:5 # circled less-than
operator.\u29C1.infix = lspace:5 rspace:5 # circled greater-than
operator.\u29C2.infix = lspace:3 rspace:3 # circle with small circle to the right
operator.\u29C3.infix = lspace:3 rspace:3 # circle with two horizontal strokes to the right
operator.\u29C4.infix = lspace:4 rspace:4 # squared rising diagonal slash
operator.\u29C5.infix = lspace:4 rspace:4 # squared falling diagonal slash
operator.\u29C6.infix = lspace:4 rspace:4 # squared asterisk
operator.\u29C7.infix = lspace:4 rspace:4 # squared small circle
operator.\u29C8.infix = lspace:4 rspace:4 # squared square
operator.\u29C9.infix = lspace:3 rspace:3 # two joined squares
operator.\u29CA.infix = lspace:3 rspace:3 # triangle with dot above
operator.\u29CB.infix = lspace:3 rspace:3 # triangle with underbar
operator.\u29CC.infix = lspace:3 rspace:3 # s in triangle
operator.\u29CD.infix = lspace:3 rspace:3 # triangle with serifs at bottom
operator.\u29CE.infix = lspace:5 rspace:5 # right triangle above left triangle
operator.\u29CF.infix = lspace:5 rspace:5 # &LeftTriangleBar;
operator.\u29CF\u0338.infix = lspace:5 rspace:5 # &NotLeftTriangleBar;
operator.\u29D0.infix = lspace:5 rspace:5 # &RightTriangleBar;
operator.\u29D0\u0338.infix = lspace:5 rspace:5 # &NotRightTriangleBar;
operator.\u29D1.infix = lspace:5 rspace:5 # bowtie with left half black
operator.\u29D2.infix = lspace:5 rspace:5 # bowtie with right half black
operator.\u29D3.infix = lspace:5 rspace:5 # black bowtie
operator.\u29D4.infix = lspace:5 rspace:5 # times with left half black
operator.\u29D5.infix = lspace:5 rspace:5 # times with right half black
operator.\u29D6.infix = lspace:4 rspace:4 # white hourglass
operator.\u29D7.infix = lspace:4 rspace:4 # black hourglass
operator.\u29D8.infix = lspace:3 rspace:3 # left wiggly fence
operator.\u29D9.infix = lspace:3 rspace:3 # right wiggly fence
operator.\u29DB.infix = lspace:3 rspace:3 # right double wiggly fence
operator.\u29DC.infix = lspace:3 rspace:3 # incomplete infinity
operator.\u29DD.infix = lspace:3 rspace:3 # tie over infinity
operator.\u29DE.infix = lspace:5 rspace:5 # infinity negated with vertical bar
operator.\u29DF.infix = lspace:3 rspace:3 # double-ended multimap
operator.\u29E0.infix = lspace:3 rspace:3 # square with contoured outline
operator.\u29E1.infix = lspace:5 rspace:5 # increases as
operator.\u29E2.infix = lspace:4 rspace:4 # shuffle product
operator.\u29E3.infix = lspace:5 rspace:5 # equals sign and slanted parallel
operator.\u29E4.infix = lspace:5 rspace:5 # equals sign and slanted parallel with tilde above
operator.\u29E5.infix = lspace:5 rspace:5 # identical to and slanted parallel
operator.\u29E6.infix = lspace:5 rspace:5 # gleich stark
operator.\u29E7.infix = lspace:3 rspace:3 # thermodynamic
operator.\u29E8.infix = lspace:3 rspace:3 # down-pointing triangle with left half black
operator.\u29E9.infix = lspace:3 rspace:3 # down-pointing triangle with right half black
operator.\u29EA.infix = lspace:3 rspace:3 # black diamond with down arrow
operator.\u29EB.infix = lspace:3 rspace:3 # black lozenge
operator.\u29EC.infix = lspace:3 rspace:3 # white circle with down arrow
operator.\u29ED.infix = lspace:3 rspace:3 # black circle with down arrow
operator.\u29EE.infix = lspace:3 rspace:3 # error-barred white square
operator.\u29EF.infix = lspace:3 rspace:3 # error-barred black square
operator.\u29F0.infix = lspace:3 rspace:3 # error-barred white diamond
operator.\u29F1.infix = lspace:3 rspace:3 # error-barred black diamond
operator.\u29F2.infix = lspace:3 rspace:3 # error-barred white circle
operator.\u29F3.infix = lspace:3 rspace:3 # error-barred black circle
operator.\u29F4.infix = lspace:5 rspace:5 # rule-delayed
operator.\u29F5.infix = lspace:4 rspace:4 # reverse solidus operator
operator.\u29F6.infix = lspace:4 rspace:4 # solidus with overbar
operator.\u29F7.infix = lspace:4 rspace:4 # reverse solidus with horizontal stroke
operator.\u29F8.infix = lspace:3 rspace:3 # big solidus
operator.\u29F9.infix = lspace:3 rspace:3 # big reverse solidus
operator.\u29FA.infix = lspace:3 rspace:3 # double plus
operator.\u29FB.infix = lspace:3 rspace:3 # triple plus
operator.\u29FC.prefix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # left-pointing curved angle bracket
operator.\u29FD.postfix = lspace:0 rspace:0 stretchy fence symmetric mirrorable direction:vertical # right-pointing curved angle bracket
operator.\u29FE.infix = lspace:4 rspace:4 # tiny
operator.\u29FF.infix = lspace:4 rspace:4 # miny
operator.\u2A00.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigodot;
operator.\u2A01.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigoplus;
operator.\u2A02.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigotimes;
operator.\u2A03.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary union operator with dot
operator.\u2A04.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &biguplus;
operator.\u2A05.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary square intersection operator
operator.\u2A06.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &bigsqcup;
operator.\u2A07.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # two logical and operator
operator.\u2A08.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # two logical or operator
operator.\u2A09.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary times operator
operator.\u2A0A.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical mirrorable # modulo two sum
operator.\u2A0B.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # summation with integral
operator.\u2A0C.prefix = lspace:0 rspace:1 largeop symmetric direction:vertical integral mirrorable # quadruple integral operator
operator.\u2A0D.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # finite part integral
operator.\u2A0E.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with double stroke
operator.\u2A0F.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral average with slash
operator.\u2A10.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # circulation function
operator.\u2A11.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral # anticlockwise integration
operator.\u2A12.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration with rectangular path around pole
operator.\u2A13.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration with semicircular path around pole
operator.\u2A14.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical integral mirrorable # line integration not including the pole
operator.\u2A15.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral around a point operator
operator.\u2A16.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # quaternion integral operator
operator.\u2A17.prefix = lspace:1 rspace:2 largeop symmetric integral mirrorable direction:vertical # integral with leftwards arrow with hook
operator.\u2A18.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with times sign
operator.\u2A19.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with intersection
operator.\u2A1A.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with union
operator.\u2A1B.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with overbar
operator.\u2A1C.prefix = lspace:1 rspace:2 largeop symmetric direction:vertical integral mirrorable # integral with underbar
operator.\u2A1D.infix = lspace:3 rspace:3 # join
operator.\u2A1E.infix = lspace:3 rspace:3 # large left triangle operator
operator.\u2A1F.infix = lspace:3 rspace:3 # z notation schema composition
operator.\u2A20.infix = lspace:3 rspace:3 # z notation schema piping
operator.\u2A21.infix = lspace:3 rspace:3 # z notation schema projection
operator.\u2A22.infix = lspace:4 rspace:4 # plus sign with small circle above
operator.\u2A23.infix = lspace:4 rspace:4 # plus sign with circumflex accent above
operator.\u2A24.infix = lspace:4 rspace:4 # plus sign with tilde above
operator.\u2A25.infix = lspace:4 rspace:4 # plus sign with dot below
operator.\u2A26.infix = lspace:4 rspace:4 # plus sign with tilde below
operator.\u2A27.infix = lspace:4 rspace:4 # plus sign with subscript two
operator.\u2A28.infix = lspace:4 rspace:4 # plus sign with black triangle
operator.\u2A29.infix = lspace:4 rspace:4 # minus sign with comma above
operator.\u2A2A.infix = lspace:4 rspace:4 # minus sign with dot below
operator.\u2A2B.infix = lspace:4 rspace:4 # minus sign with falling dots
operator.\u2A2C.infix = lspace:4 rspace:4 # minus sign with rising dots
operator.\u2A2D.infix = lspace:4 rspace:4 # plus sign in left half circle
operator.\u2A2E.infix = lspace:4 rspace:4 # plus sign in right half circle
operator.\u2A2F.infix = lspace:4 rspace:4 # &Cross;
operator.\u2A30.infix = lspace:4 rspace:4 # multiplication sign with dot above
operator.\u2A31.infix = lspace:4 rspace:4 # multiplication sign with underbar
operator.\u2A32.infix = lspace:4 rspace:4 # semidirect product with bottom closed
operator.\u2A33.infix = lspace:4 rspace:4 # smash product
operator.\u2A34.infix = lspace:4 rspace:4 # multiplication sign in left half circle
operator.\u2A35.infix = lspace:4 rspace:4 # multiplication sign in right half circle
operator.\u2A36.infix = lspace:4 rspace:4 # circled multiplication sign with circumflex accent
operator.\u2A37.infix = lspace:4 rspace:4 # multiplication sign in double circle
operator.\u2A38.infix = lspace:4 rspace:4 # circled division sign
operator.\u2A39.infix = lspace:4 rspace:4 # plus sign in triangle
operator.\u2A3A.infix = lspace:4 rspace:4 # minus sign in triangle
operator.\u2A3B.infix = lspace:4 rspace:4 # multiplication sign in triangle
operator.\u2A3C.infix = lspace:4 rspace:4 # interior product
operator.\u2A3D.infix = lspace:4 rspace:4 # righthand interior product
operator.\u2A3E.infix = lspace:4 rspace:4 # z notation relational composition
operator.\u2A3F.infix = lspace:4 rspace:4 # amalgamation or coproduct
operator.\u2A40.infix = lspace:4 rspace:4 # intersection with dot
operator.\u2A41.infix = lspace:4 rspace:4 # union with minus sign
operator.\u2A42.infix = lspace:4 rspace:4 # union with overbar
operator.\u2A43.infix = lspace:4 rspace:4 # intersection with overbar
operator.\u2A44.infix = lspace:4 rspace:4 # intersection with logical and
operator.\u2A45.infix = lspace:4 rspace:4 # union with logical or
operator.\u2A46.infix = lspace:4 rspace:4 # union above intersection
operator.\u2A47.infix = lspace:4 rspace:4 # intersection above union
operator.\u2A48.infix = lspace:4 rspace:4 # union above bar above intersection
operator.\u2A49.infix = lspace:4 rspace:4 # intersection above bar above union
operator.\u2A4A.infix = lspace:4 rspace:4 # union beside and joined with union
operator.\u2A4B.infix = lspace:4 rspace:4 # intersection beside and joined with intersection
operator.\u2A4C.infix = lspace:4 rspace:4 # closed union with serifs
operator.\u2A4D.infix = lspace:4 rspace:4 # closed intersection with serifs
operator.\u2A4E.infix = lspace:4 rspace:4 # double square intersection
operator.\u2A4F.infix = lspace:4 rspace:4 # double square union
operator.\u2A50.infix = lspace:4 rspace:4 # closed union with serifs and smash product
operator.\u2A51.infix = lspace:4 rspace:4 # logical and with dot above
operator.\u2A52.infix = lspace:4 rspace:4 # logical or with dot above
operator.\u2A53.infix = lspace:4 rspace:4 direction:vertical # &And;
operator.\u2A54.infix = lspace:4 rspace:4 direction:vertical # &Or;
operator.\u2A55.infix = lspace:4 rspace:4 # two intersecting logical and
operator.\u2A56.infix = lspace:4 rspace:4 # two intersecting logical or
operator.\u2A57.infix = lspace:4 rspace:4 # sloping large or
operator.\u2A58.infix = lspace:4 rspace:4 # sloping large and
operator.\u2A59.infix = lspace:5 rspace:5 # logical or overlapping logical and
operator.\u2A5A.infix = lspace:4 rspace:4 # logical and with middle stem
operator.\u2A5B.infix = lspace:4 rspace:4 # logical or with middle stem
operator.\u2A5C.infix = lspace:4 rspace:4 # logical and with horizontal dash
operator.\u2A5D.infix = lspace:4 rspace:4 # logical or with horizontal dash
operator.\u2A5E.infix = lspace:4 rspace:4 # logical and with double overbar
operator.\u2A5F.infix = lspace:4 rspace:4 # logical and with underbar
operator.\u2A60.infix = lspace:4 rspace:4 # logical and with double underbar
operator.\u2A61.infix = lspace:4 rspace:4 # small vee with underbar
operator.\u2A62.infix = lspace:4 rspace:4 # logical or with double overbar
operator.\u2A63.infix = lspace:4 rspace:4 # logical or with double underbar
operator.\u2A64.infix = lspace:4 rspace:4 # z notation domain antirestriction
operator.\u2A65.infix = lspace:4 rspace:4 # z notation range antirestriction
operator.\u2A66.infix = lspace:5 rspace:5 # equals sign with dot below
operator.\u2A67.infix = lspace:5 rspace:5 # identical with dot above
operator.\u2A68.infix = lspace:5 rspace:5 # triple horizontal bar with double vertical stroke
operator.\u2A69.infix = lspace:5 rspace:5 # triple horizontal bar with triple vertical stroke
operator.\u2A6A.infix = lspace:5 rspace:5 # tilde operator with dot above
operator.\u2A6B.infix = lspace:5 rspace:5 # tilde operator with rising dots
operator.\u2A6C.infix = lspace:5 rspace:5 # similar minus similar
operator.\u2A6D.infix = lspace:5 rspace:5 # congruent with dot above
operator.\u2A6E.infix = lspace:5 rspace:5 # equals with asterisk
operator.\u2A6F.infix = lspace:5 rspace:5 # almost equal to with circumflex accent
operator.\u2A70.infix = lspace:5 rspace:5 # approximately equal or equal to
operator.\u2A71.infix = lspace:4 rspace:4 # equals sign above plus sign
operator.\u2A72.infix = lspace:4 rspace:4 # plus sign above equals sign
operator.\u2A73.infix = lspace:5 rspace:5 # equals sign above tilde operator
operator.\u2A74.infix = lspace:5 rspace:5 # double colon equal
operator.\u2A75.infix = lspace:5 rspace:5 # &Equal;
operator.\u2A76.infix = lspace:5 rspace:5 # three consecutive equals signs
operator.\u2A77.infix = lspace:5 rspace:5 # equals sign with two dots above and two dots below
operator.\u2A78.infix = lspace:5 rspace:5 # equivalent with four dots above
operator.\u2A79.infix = lspace:5 rspace:5 # less-than with circle inside
operator.\u2A7A.infix = lspace:5 rspace:5 # greater-than with circle inside
operator.\u2A7B.infix = lspace:5 rspace:5 # less-than with question mark above
operator.\u2A7C.infix = lspace:5 rspace:5 # greater-than with question mark above
operator.\u2A7D.infix = lspace:5 rspace:5 # &LessSlantEqual;
operator.\u2A7D\u0338.infix = lspace:5 rspace:5 # &NotLessSlantEqual;
operator.\u2A7E.infix = lspace:5 rspace:5 # &GreaterSlantEqual;
operator.\u2A7E\u0338.infix = lspace:5 rspace:5 # &NotGreaterSlantEqual;
operator.\u2A7F.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot inside
operator.\u2A80.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot inside
operator.\u2A81.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above
operator.\u2A82.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above
operator.\u2A83.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above right
operator.\u2A84.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above left
operator.\u2A85.infix = lspace:5 rspace:5 # &lessapprox;
operator.\u2A86.infix = lspace:5 rspace:5 # &gtrapprox;
operator.\u2A87.infix = lspace:5 rspace:5 # less-than and single-line not equal to
operator.\u2A88.infix = lspace:5 rspace:5 # greater-than and single-line not equal to
operator.\u2A89.infix = lspace:5 rspace:5 # less-than and not approximate
operator.\u2A8A.infix = lspace:5 rspace:5 # greater-than and not approximate
operator.\u2A8B.infix = lspace:5 rspace:5 # &lesseqqgtr;
operator.\u2A8C.infix = lspace:5 rspace:5 # &gtreqqless;
operator.\u2A8D.infix = lspace:5 rspace:5 # less-than above similar or equal
operator.\u2A8E.infix = lspace:5 rspace:5 # greater-than above similar or equal
operator.\u2A8F.infix = lspace:5 rspace:5 # less-than above similar above greater-than
operator.\u2A90.infix = lspace:5 rspace:5 # greater-than above similar above less-than
operator.\u2A91.infix = lspace:5 rspace:5 # less-than above greater-than above double-line equal
operator.\u2A92.infix = lspace:5 rspace:5 # greater-than above less-than above double-line equal
operator.\u2A93.infix = lspace:5 rspace:5 # less-than above slanted equal above greater-than above slanted equal
operator.\u2A94.infix = lspace:5 rspace:5 # greater-than above slanted equal above less-than above slanted equal
operator.\u2A95.infix = lspace:5 rspace:5 # slanted equal to or less-than
operator.\u2A96.infix = lspace:5 rspace:5 # slanted equal to or greater-than
operator.\u2A97.infix = lspace:5 rspace:5 # slanted equal to or less-than with dot inside
operator.\u2A98.infix = lspace:5 rspace:5 # slanted equal to or greater-than with dot inside
operator.\u2A99.infix = lspace:5 rspace:5 # double-line equal to or less-than
operator.\u2A9A.infix = lspace:5 rspace:5 # double-line equal to or greater-than
operator.\u2A9B.infix = lspace:5 rspace:5 # double-line slanted equal to or less-than
operator.\u2A9C.infix = lspace:5 rspace:5 # double-line slanted equal to or greater-than
operator.\u2A9D.infix = lspace:5 rspace:5 # similar or less-than
operator.\u2A9E.infix = lspace:5 rspace:5 # similar or greater-than
operator.\u2A9F.infix = lspace:5 rspace:5 # similar above less-than above equals sign
operator.\u2AA0.infix = lspace:5 rspace:5 # similar above greater-than above equals sign
operator.\u2AA1.infix = lspace:5 rspace:5 # &LessLess;
operator.\u2AA1\u0338.infix = lspace:5 rspace:5 # &NotNestedLessLess;
operator.\u2AA2.infix = lspace:5 rspace:5 # &GreaterGreater;
operator.\u2AA2\u0338.infix = lspace:5 rspace:5 # &NotNestedGreaterGreater;
operator.\u2AA3.infix = lspace:5 rspace:5 # double nested less-than with underbar
operator.\u2AA4.infix = lspace:5 rspace:5 # greater-than overlapping less-than
operator.\u2AA5.infix = lspace:5 rspace:5 # greater-than beside less-than
operator.\u2AA6.infix = lspace:5 rspace:5 # less-than closed by curve
operator.\u2AA7.infix = lspace:5 rspace:5 # greater-than closed by curve
operator.\u2AA8.infix = lspace:5 rspace:5 # less-than closed by curve above slanted equal
operator.\u2AA9.infix = lspace:5 rspace:5 # greater-than closed by curve above slanted equal
operator.\u2AAA.infix = lspace:5 rspace:5 # smaller than
operator.\u2AAB.infix = lspace:5 rspace:5 # larger than
operator.\u2AAC.infix = lspace:5 rspace:5 # smaller than or equal to
operator.\u2AAD.infix = lspace:5 rspace:5 # larger than or equal to
operator.\u2AAE.infix = lspace:5 rspace:5 # equals sign with bumpy above
operator.\u2AAF.infix = lspace:5 rspace:5 # &PrecedesEqual;
operator.\u2AAF\u0338.infix = lspace:5 rspace:5 # &NotPrecedesEqual;
operator.\u2AB0.infix = lspace:5 rspace:5 # &SucceedsEqual;
operator.\u2AB0\u0338.infix = lspace:5 rspace:5 # &NotSucceedsEqual;
operator.\u2AB1.infix = lspace:5 rspace:5 # precedes above single-line not equal to
operator.\u2AB2.infix = lspace:5 rspace:5 # succeeds above single-line not equal to
operator.\u2AB3.infix = lspace:5 rspace:5 # &prE;
operator.\u2AB4.infix = lspace:5 rspace:5 # &scE;
operator.\u2AB5.infix = lspace:5 rspace:5 # precedes above not equal to
operator.\u2AB6.infix = lspace:5 rspace:5 # succeeds above not equal to
operator.\u2AB7.infix = lspace:5 rspace:5 # &precapprox;
operator.\u2AB8.infix = lspace:5 rspace:5 # &succapprox;
operator.\u2AB9.infix = lspace:5 rspace:5 # precedes above not almost equal to
operator.\u2ABA.infix = lspace:5 rspace:5 # succeeds above not almost equal to
operator.\u2ABB.infix = lspace:5 rspace:5 # double precedes
operator.\u2ABC.infix = lspace:5 rspace:5 # double succeeds
operator.\u2ABD.infix = lspace:5 rspace:5 # subset with dot
operator.\u2ABE.infix = lspace:5 rspace:5 # superset with dot
operator.\u2ABF.infix = lspace:5 rspace:5 # subset with plus sign below
operator.\u2AC0.infix = lspace:5 rspace:5 # superset with plus sign below
operator.\u2AC1.infix = lspace:5 rspace:5 # subset with multiplication sign below
operator.\u2AC2.infix = lspace:5 rspace:5 # superset with multiplication sign below
operator.\u2AC3.infix = lspace:5 rspace:5 # subset of or equal to with dot above
operator.\u2AC4.infix = lspace:5 rspace:5 # superset of or equal to with dot above
operator.\u2AC5.infix = lspace:5 rspace:5 # &subseteqq;
operator.\u2AC6.infix = lspace:5 rspace:5 # &supseteqq;
operator.\u2AC7.infix = lspace:5 rspace:5 # subset of above tilde operator
operator.\u2AC8.infix = lspace:5 rspace:5 # superset of above tilde operator
operator.\u2AC9.infix = lspace:5 rspace:5 # subset of above almost equal to
operator.\u2ACA.infix = lspace:5 rspace:5 # superset of above almost equal to
operator.\u2ACB.infix = lspace:5 rspace:5 # subset of above not equal to
operator.\u2ACC.infix = lspace:5 rspace:5 # superset of above not equal to
operator.\u2ACD.infix = lspace:5 rspace:5 # square left open box operator
operator.\u2ACE.infix = lspace:5 rspace:5 # square right open box operator
operator.\u2ACF.infix = lspace:5 rspace:5 # closed subset
operator.\u2AD0.infix = lspace:5 rspace:5 # closed superset
operator.\u2AD1.infix = lspace:5 rspace:5 # closed subset or equal to
operator.\u2AD2.infix = lspace:5 rspace:5 # closed superset or equal to
operator.\u2AD3.infix = lspace:5 rspace:5 # subset above superset
operator.\u2AD4.infix = lspace:5 rspace:5 # superset above subset
operator.\u2AD5.infix = lspace:5 rspace:5 # subset above subset
operator.\u2AD6.infix = lspace:5 rspace:5 # superset above superset
operator.\u2AD7.infix = lspace:5 rspace:5 # superset beside subset
operator.\u2AD8.infix = lspace:5 rspace:5 # superset beside and joined by dash with subset
operator.\u2AD9.infix = lspace:5 rspace:5 # element of opening downwards
operator.\u2ADA.infix = lspace:5 rspace:5 # pitchfork with tee top
operator.\u2ADB.infix = lspace:5 rspace:5 # transversal intersection
operator.\u2ADC.infix = lspace:5 rspace:5 # forking
operator.\u2ADD.infix = lspace:5 rspace:5 # nonforking
operator.\u2ADD\u0338.infix = lspace:5 rspace:5 # nonforking with slash
operator.\u2ADE.infix = lspace:5 rspace:5 # short left tack
operator.\u2ADF.infix = lspace:5 rspace:5 # short down tack
operator.\u2AE0.infix = lspace:5 rspace:5 # short up tack
operator.\u2AE1.infix = lspace:5 rspace:5 # perpendicular with s
operator.\u2AE2.infix = lspace:5 rspace:5 # vertical bar triple right turnstile
operator.\u2AE3.infix = lspace:5 rspace:5 # double vertical bar left turnstile
operator.\u2AE4.infix = lspace:5 rspace:5 # &DoubleLeftTee;
operator.\u2AE5.infix = lspace:5 rspace:5 # double vertical bar double left turnstile
operator.\u2AE6.infix = lspace:5 rspace:5 # long dash from left member of double vertical
operator.\u2AE7.infix = lspace:5 rspace:5 # short down tack with overbar
operator.\u2AE8.infix = lspace:5 rspace:5 # short up tack with underbar
operator.\u2AE9.infix = lspace:5 rspace:5 # short up tack above short down tack
operator.\u2AEA.infix = lspace:5 rspace:5 # double down tack
operator.\u2AEB.infix = lspace:5 rspace:5 # double up tack
operator.\u2AEC.infix = lspace:5 rspace:5 # double stroke not sign
operator.\u2AED.infix = lspace:5 rspace:5 # reversed double stroke not sign
operator.\u2AEE.infix = lspace:5 rspace:5 # does not divide with reversed negation slash
operator.\u2AEF.infix = lspace:5 rspace:5 # vertical line with circle above
operator.\u2AF0.infix = lspace:5 rspace:5 # vertical line with circle below
operator.\u2AF1.infix = lspace:5 rspace:5 # down tack with circle below
operator.\u2AF2.infix = lspace:5 rspace:5 # parallel with horizontal stroke
operator.\u2AF3.infix = lspace:5 rspace:5 # parallel with tilde operator
operator.\u2AF4.infix = lspace:4 rspace:4 # triple vertical bar binary relation
operator.\u2AF5.infix = lspace:4 rspace:4 # triple vertical bar with horizontal stroke
operator.\u2AF6.infix = lspace:4 rspace:4 # triple colon operator
operator.\u2AF7.infix = lspace:5 rspace:5 # triple nested less-than
operator.\u2AF8.infix = lspace:5 rspace:5 # triple nested greater-than
operator.\u2AF9.infix = lspace:5 rspace:5 # double-line slanted less-than or equal to
operator.\u2AFA.infix = lspace:5 rspace:5 # double-line slanted greater-than or equal to
operator.\u2AFB.infix = lspace:4 rspace:4 # triple solidus binary relation
operator.\u2AFC.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # large triple vertical bar operator
operator.\u2AFD.infix = lspace:4 rspace:4 # double solidus operator
operator.\u2AFE.infix = lspace:3 rspace:3 # white vertical bar
operator.\u2AFF.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # n-ary white vertical bar
operator.\u2B45.infix = lspace:5 rspace:5 stretchy direction:horizontal # leftwards quadruple arrow
operator.\u2B46.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards quadruple arrow

# Entries below are not part of the official MathML dictionary

operator.\u0021.prefix = lspace:0 rspace:5 # !
operator.\u0026.infix = lspace:5 rspace:5 # &amp;
operator.\u0026.prefix = lspace:0 rspace:5 # &amp;
operator.\u002B\u002B.prefix = lspace:0 rspace:2 # ++
operator.\u002D\u002D.prefix = lspace:0 rspace:2 # --
operator.\u003B.postfix = lspace:0 rspace:0 separator # ;
operator.\u003C\u20D2.infix = lspace:5 rspace:5 # &nvlt;
operator.\u003E\u20D2.infix = lspace:5 rspace:5 # &nvgt;
operator.\u006C\u0069\u006D.prefix = lspace:0 rspace:3 movablelimits # lim
operator.\u006D\u0061\u0078.prefix = lspace:0 rspace:3 movablelimits # max
operator.\u006D\u0069\u006E.prefix = lspace:0 rspace:3 movablelimits # min
operator.\u007E.infix = lspace:2 rspace:2 stretchy direction:horizontal # ~
operator.\u0332.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBar;
operator.\u2016.infix = lspace:5 rspace:5 stretchy direction:vertical # &Vert; &Verbar;
operator.\u20D0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D0;
operator.\u20D1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D1;
operator.\u20D6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D6;
operator.\u20D7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D7;
operator.\u20E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20E1;
operator.\u2190\u200B.infix = lspace:5 rspace:5 # &ShortLeftArrow;
operator.\u2191\u200B.infix = lspace:2 rspace:2 # &ShortUpArrow;
operator.\u2192\u200B.infix = lspace:5 rspace:5 # &ShortRightArrow;
operator.\u2193\u200B.infix = lspace:2 rspace:2 # &ShortDownArrow;
operator.\u2223.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
operator.\u2223.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
operator.\u2225.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
operator.\u2225.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
operator.\u2282\u020D2.infix = lspace:5 rspace:5 # &NotSubset;
operator.\u2283\u020D2.infix = lspace:5 rspace:5 # &NotSuperset;
operator.\u228E.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &UnionPlus;
operator.\u2295.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CirclePlus;
operator.\u2296.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleMinus;
operator.\u2297.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleTimes;
operator.\u2299.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleDot;
operator.\u23B0.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &lmoustache; &lmoust;
operator.\u23B1.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &rmoustache; &rmoust;
operator.\u2500.infix = lspace:0 rspace:0 stretchy direction:horizontal # &HorizontalLine;
operator.\u25A1.prefix = lspace:0 rspace:2 # &Square;
operator.\u2606.infix = lspace:3 rspace:3 # &star;
operator.\u2AC5\u0338.infix = lspace:5 rspace:5 # &nsubseteqq;
operator.\u2AC6\u0338.infix = lspace:5 rspace:5 # &nsubseteqq;
operator.\u2AEC.prefix = lspace:0 rspace:5 # &Not;
operator.\uFE35.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (MathML 2.0)
operator.\uFE36.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (MathML 2.0)
operator.\uFE37.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (MathML 2.0)
operator.\uFE38.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (MathML 2.0)


##################################################################################
# DOCUMENTATION ON HOW TO SETUP THE PROPERTY FILE ASSOCIATED TO EACH FONT
# More fonts can be supported for stretchy characters by setting up mathfont
# property files as described below.
#
# Each font should have its set of glyph data. For example, the glyph data for
# the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties"
# and "mathfontMTExtra.properties", respectively. The font property file is a
# set of all the stretchy MathML characters that can be rendered with that font
# using larger and/or partial glyphs. Each stretchy character is associated to
# a list in the font property file which gives, in that order, the 4 partial
# glyphs: top (or left), middle, bottom (or right), glue; and the variants of
# bigger sizes (if any). A position that is not relevant to a particular character
# is indicated there with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
PK
!<7(res/fonts/mathfontSTIXGeneral.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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
#  Do not translate anything in this file

# This file contains the list of some stretchy MathML chars that can be
# rendered with the STIXGeneral set.

external.1 = STIXSizeOneSym
external.2 = STIXSizeTwoSym
external.3 = STIXSizeThreeSym
external.4 = STIXSizeFourSym
external.5 = STIXSizeFiveSym
external.6 = STIXIntegralsD
external.7 = STIXNonUnicode

############
# 1) Constructions from mathfontSTIXSizeOneSym.properties (bug 1007093) #

#        [ T/L |  M  | B/R |  G  | size0 ... size{N-1} ]
\u0028 = \u239B@1\uFFFD\u239D@1\u239C@1\uFFFD(@1(@2(@3(@4 # (
\u0029 = \u239E@1\uFFFD\u23A0@1\u239F@1\uFFFD)@1)@2)@3)@4 # )
\u005B = \u23A1@1\uFFFD\u23A3@1\u23A2@1\u005B@1[@1[@2[@3[@4 # [
\u005D = \u23A4@1\uFFFD\u23A6@1\u23A5@1\u005D@1]@1]@2]@3]@4 # ]
\u007B = \u23A7@1\u23A8@1\u23A9@1\u23AA@1\u007B@1{@1{@2{@3{@4 # {
\u007D = \u23AB@1\u23AC@1\u23AD@1\u23AA@1\u007D@1}@1}@2}@3}@4 # }

# N-ARY operators
\u2140 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2140@1 # DOUBLE-STRUCK N-ARY SUMMATION
\u220F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u220F@1 # N-ARY PRODUCT
\u2210 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2210@1 # N-ARY COPRODUCT
\u2211 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2211@1 # N-ARY SUMMATION
\u22C0 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C0@1 # N-ARY LOGICAL AND
\u22C1 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C1@1 # N-ARY LOGICAL OR
\u22C2 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C2@1 # N-ARY INTERSECTION
\u22C3 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C3@1 # N-ARY UNION
\u2A00 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A00@1 # N-ARY CIRCLED DOT OPERATOR
\u2A01 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A01@1 # N-ARY CIRCLED PLUS OPERATOR
\u2A02 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A02@1 # N-ARY CIRCLED TIMES OPERATOR
\u2A03 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A03@1 # N-ARY UNION OPERATOR WITH DOT
\u2A04 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A04@1 # N-ARY UNION OPERATOR WITH PLUS
\u2A05 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A05@1 # N-ARY SQUARE INTERSECTION OPERATOR
\u2A06 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A06@1 # N-ARY SQUARE UNION OPERATOR
\u2A09 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A09@1 # N-ARY TIMES OPERATOR
\u2AFF = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2AFF@1 # N-ARY WHITE VERTICAL BAR

# E000 stix-radical symbol vertical extender
# E001 stix-radical symbol top corner
\u221A = \uE001@7\uFFFD\u221A@4\uE000@7\uFFFD\u221A@1\u221A@2\u221A@3 # Sqrt, radic

# Integrals
\u222B = \u2320@1\uFFFD\u2321@1\u23AE@1\uFFFD@1\u222B@6
\u222C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222C@6
\u222D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222D@6
\u222E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222E@6
\u222F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222F@6
\u2230 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2230@6
\u2231 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2231@6
\u2232 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2232@6
\u2233 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2233@6
\u2A0B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0B@6
\u2A0C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0C@6
\u2A0D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0D@6
\u2A0E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0E@6
\u2A0F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0F@6
\u2A10 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A10@6
\u2A11 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A11@6
\u2A12 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A12@6
\u2A13 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A13@6
\u2A14 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A14@6
\u2A15 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A15@6
\u2A16 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A16@6
\u2A17 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A17@6
\u2A18 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A18@6
\u2A19 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A19@6
\u2A1A = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1A@6
\u2A1B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1B@6
\u2A1C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1C@6

\u27E8 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E8@1\u27E8@2\u27E8@3\u27E8@4 # LeftAngleBracket
\u27E9 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E9@1\u27E9@2\u27E9@3\u27E9@4 # RightAngleBracket

\u23DE = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (Unicode)
\uFE37 = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (MathML 2.0)
\u23B4 = \uE146@7\uFFFD\uE147@7\uE14A@7\uFFFD\u23B4@1\u23B4@2\u23B4@3\u23B4@4\u23B4@5 # &OverBracket;
\u23DC = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (Unicode)
\uFE35 = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (MathML 2.0)
\u23DF = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (Unicode)
\uFE38 = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (MathML 2.0)
\u23B5 = \uE148@7\uFFFD\uE149@7\uE14B@7\uFFFD\u23B5@1\u23B5@2\u23B5@3\u23B5@4\u23B5@5 # &UnderBracket;
\u23DD = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (Unicode)
\uFE36 = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (MathML 2.0)

\u005E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # circumflex accent, COMBINING CIRCUMFLEX ACCENT
\u02C6 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # modifier letter circumflex accent, COMBINING CIRCUMFLEX ACCENT
\u007E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # ~ tilde, COMBINING TILDE
\u02DC = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # small tilde, COMBINING TILDE
\u02C7 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u030C@1\u030C@2\u030C@3\u030C@4\u030C@5 # caron, COMBINING CARON

############
# 2) Constructions from mathfontSTIXNonUnicode.properties (bug 1007093) #

#        [ T/L |  M  | B/R |  G  | size0 ... size{N-1} ]
# E0B4 stix-arrow hookleft
# E0B5 stix-arrow hookright
\u21A9 = \u2190\uFFFD\uE0B5@7\u23AF # hookleftarrow, larrhk
\u21AA = \uE0B4@7\uFFFD\u2192\u23AF # hookrightarrow, rarrhk

# 0E10E stix-stix-extender for vertical double arrow
# 0E10F stix-extender for horizontal double arrow
\u21D0 = \u21D0\uFFFD\uFFFD\uE10F@7\uFFFD\u27F8 # DoubleLeftArrow, Leftarrow, lArr
\u21D1 = \u21D1\uFFFD\uFFFD\uE10E@7 # DoubleUpArrow, Uparrow, uArr
\u21D2 = \uFFFD\uFFFD\u21D2\uE10F@7\uFFFD\u27F9 # DoubleRightArrow, Implies, Rightarrow, rArr
\u21D3 = \uFFFD\uFFFD\u21D3\uE10E@7 # DoubleDownArrow, Downarrow, dArr
\u21D4 = \u21D0\uFFFD\u21D2\uE10F@7\uFFFD\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff
\u21D5 = \u21D1\uFFFD\u21D3\uE10E@7 # DoubleUpDownArrow, Updownarrow, vArr

# STIXGeneral U+22A2/U+22A3 RIGHT/LEFT TACK are different heights to U+23AF.
# Could use LONG RIGHT/LEFT TACK instead, but STIXNonUnicode provides
# E0B6 stix-maps-to-relation tail
\u21A4 = \u2190\uFFFD\uE0B6@7\u23AF\uFFFD\u27FB # LeftTeeArrow, mapstoleft
\u21A6 = \uE0B6@7\uFFFD\u2192\u23AF\uFFFD\u27FC # RightTeeArrow, map, mapsto
\u295A = \u21BC\uFFFD\uE0B6@7\u23AF # LeftTeeVector
\u295B = \uE0B6@7\uFFFD\u21C0\u23AF # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
\u295E = \u21BD\uFFFD\uE0B6@7\u23AF # DownLeftTeeVector
\u295F = \uE0B6@7\uFFFD\u21C1\u23AF # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
PK
!<܉$res/fonts/mathfontUnicode.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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
#  Do not translate anything in this file

# This file contains the list of all stretchy MathML chars that
# can be rendered using only Unicode code points.

#        [ T/L |  M  | B/R |  G  | size0 ... size{N-1} ]
\u0028 = \u239B\uFFFD\u239D\u239C\u0028 # (
\u0029 = \u239E\uFFFD\u23A0\u239F\u0029 # )
\u005B = \u23A1\uFFFD\u23A3\u23A2\u005B # [
\u005D = \u23A4\uFFFD\u23A6\u23A5\u005D # ]
\u007B = \u23A7\u23A8\u23A9\u23AA\u007B # {
\u007C = \uFFFD\uFFFD\uFFFD\u007C\u007C # |
\u007D = \u23AB\u23AC\u23AD\u23AA\u007D # }

# OverBar is stretched with U+0305 COMBINING OVERLINE which "connects on left and right"
\u00AF = \uFFFD\uFFFD\uFFFD\u0305\u00AF # OverBar
#\u0305 doesn't appear to be referenced by the MathML spec
\u203E = \uFFFD\uFFFD\uFFFD\u0305\u00AF # overline
\u0332 = \uFFFD\uFFFD\uFFFD\u0332\u0332 # COMBINING LOW LINE, UnderBar
\u005F = \uFFFD\uFFFD\uFFFD\u0332\u0332 # _ low line
\u003D = \uFFFD\uFFFD\uFFFD\u003D\u003D # = equal sign

\u2016 = \uFFFD\uFFFD\uFFFD\u2016\u2016 # DOUBLE VERTICAL LINE, Vert, Verbar

\u2190 = \u2190\uFFFD\uFFFD\u23AF\u2190\u27F5 # LeftArrow, larr, leftarrow
\u2191 = \u2191\uFFFD\uFFFD\u23D0\u2191 # UpArrow, uarr, uparrow
\u2192 = \uFFFD\uFFFD\u2192\u23AF\u2192\u27F6 # RightArrow, rarr, rightarrow
\u2193 = \uFFFD\uFFFD\u2193\u23D0\u2193 # DownArrow, darr, downarrow
\u2194 = \u2190\uFFFD\u2192\u23AF\u2194\u27F7 # LeftRightArrow, harr, leftrightarrow
\u2195 = \u2191\uFFFD\u2193\u23D0\u2195 # UpDownArrow, updownarrow, varr

# For STIXGeneral U+22A2/U+22A3 RIGHT/LEFT TACK are different heights to U+23AF.
# Could use LONG RIGHT/LEFT TACK instead, but STIXNonUnicode provides
# E0B6 stix-maps-to-relation tail
#\u21A4 = \u2190\uFFFD\u27DE\u23AF\u21A6\u27FB # LeftTeeArrow, mapstoleft
#\u21A6 = \u27DD\uFFFD\u2192\u23AF\u21A6\u27FC # RightTeeArrow, map, mapsto
#\u295A = \u21BC\uFFFD\u27DE\u23AF\u295A # LeftTeeVector
#\u295B = \u27DD\uFFFD\u21C0\u23AF\u295B # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
#\u295E = \u21BD\uFFFD\u27DE\u23AF\u295E # DownLeftTeeVector
#\u295F = \u27DD\uFFFD\u21C1\u23AF\u295F # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
# Cambria Math does not have U+27DD/U+27DE
\u21A4 = \u2190\uFFFD\u22A3\u23AF\u21A6\u27FB # LeftTeeArrow, mapstoleft
\u21A6 = \u22A2\uFFFD\u2192\u23AF\u21A6\u27FC # RightTeeArrow, map, mapsto
\u295A = \u21BC\uFFFD\u22A3\u23AF\u295A # LeftTeeVector
\u295B = \u22A2\uFFFD\u21C0\u23AF\u295B # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
\u295E = \u21BD\uFFFD\u22A3\u23AF\u295E # DownLeftTeeVector
\u295F = \u22A2\uFFFD\u21C1\u23AF\u295F # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector

\u21C0 = \uFFFD\uFFFD\u21C0\u23AF\u21C0 # RightVector, rharu, rightharpoonup
\u21C1 = \uFFFD\uFFFD\u21C1\u23AF\u21C1 # DownRightVector, rhard, rightharpoon down
\u21BC = \u21BC\uFFFD\uFFFD\u23AF\u21BC # LeftVector, leftharpoonup, lharu
\u21BD = \u21BD\uFFFD\uFFFD\u23AF\u21BD # DownLeftVector, leftharpoondown, lhard
\u21D0 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D0\u27F8 # DoubleLeftArrow, Leftarrow, lArr
\u21D2 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D2\u27F9 # DoubleRightArrow, Implies, Rightarro
\u21D4 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D4\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff

# \u221A radical may be made from RADICAL SYMBOL BOTTOM U+23B7 but few fonts
# support this character and it is not clear what the appropriate vertical
# glue whould be.

\u2223 = \uFFFD\uFFFD\uFFFD\u2223\u2223 # VerticalBar, mid
\u2225 = \uFFFD\uFFFD\uFFFD\u2225\u2225 # DoubleVerticalBar, par, parallel

# If fonts have U+23AE INTEGRAL EXTENSION:
# (STIXSize1, Cambria Math, DejaVu Sans/Serif, Apple's Symbol) 
\u222B = \u2320\uFFFD\u2321\u23AE\u222B # Integral, int
# Many fonts don't have U+23AE.  For these fonts, a rule can be used as glue:
# \u222B = \u2320\uFFFD\u2321\uFFFD\u222B # Integral, int

# Using parts of [ and ] (could use box drawings instead)
\u2308 = \u23A1\uFFFD\uFFFD\u23A2\u2308 # LeftCeiling, lceil
\u2309 = \u23A4\uFFFD\uFFFD\u23A5\u2309 # RightCeiling, rceil
\u230A = \uFFFD\uFFFD\u23A3\u23A2\u230A # LeftFloor, lfloor
\u230B = \uFFFD\uFFFD\u23A6\u23A5\u230B # RightFloor, rfloor

# Support for l/r moustache from the parts of lbrace { and rbrace }
\u23B0 = \u23A7\uFFFD\u23AD\u23AA\u23B0 # lmoustache, lmoust
\u23B1 = \u23AB\uFFFD\u23A9\u23AA\u23B1 # rmoustache, rmoust

# Using normal arrows as heads instead of long arrows for the sake of
# Apple's Symbol font.
\u27F5 = \u2190\uFFFD\uFFFD\u23AF\u27F5 # LongLeftArrow
\u27F6 = \uFFFD\uFFFD\u2192\u23AF\u27F6 # LongRightArrow
\u27F7 = \u2190\uFFFD\u2192\u23AF\u27F7 # LongLeftRightArrow

\u294E = \u21BC\uFFFD\u21C0\u23AF\u294E #LEFT BARB UP RIGHT BARB UP HARPOON, LeftRightVector
\u2950 = \u21BD\uFFFD\u21C1\u23AF\u2950 #LEFT BARB DOWN RIGHT BARB DOWN HARPOON , DownLeftRightVector
PK
!<K*mmres/dtd/htmlmathml-f.ent
<!-- 
     Copyright 1998 - 2011 W3C.

     Use and distribution of this code are permitted under the terms of
     either of the following two licences:

     1) W3C Software Notice and License.
        http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html


     2) The license used for the WHATWG HTML specification,
        which states, in full:
            You are granted a license to use, reproduce and create derivative
            works of this document.


     Please report any errors to David Carlisle
     via the public W3C list www-math@w3.org.

 
       Public identifier: -//W3C//ENTITIES HTML MathML Set//EN//XML
       System identifier: http://www.w3.org/2003/entities/2007/htmlmathml-f.ent

     The public identifier should always be used verbatim.
     The system identifier may be changed to suit local requirements.

     Typical invocation:

       <!ENTITY % htmlmathml-f PUBLIC
         "-//W3C//ENTITIES HTML MathML Set//EN//XML"
         "http://www.w3.org/2003/entities/2007/htmlmathml-f.ent"
       >
       %htmlmathml-f;



-->

<!ENTITY AElig            "&#x000C6;" ><!--LATIN CAPITAL LETTER AE -->
<!ENTITY AMP              "&#38;#38;" ><!--AMPERSAND -->
<!ENTITY Aacute           "&#x000C1;" ><!--LATIN CAPITAL LETTER A WITH ACUTE -->
<!ENTITY Abreve           "&#x00102;" ><!--LATIN CAPITAL LETTER A WITH BREVE -->
<!ENTITY Acirc            "&#x000C2;" ><!--LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
<!ENTITY Acy              "&#x00410;" ><!--CYRILLIC CAPITAL LETTER A -->
<!ENTITY Afr              "&#x1D504;" ><!--MATHEMATICAL FRAKTUR CAPITAL A -->
<!ENTITY Agrave           "&#x000C0;" ><!--LATIN CAPITAL LETTER A WITH GRAVE -->
<!ENTITY Alpha            "&#x00391;" ><!--GREEK CAPITAL LETTER ALPHA -->
<!ENTITY Amacr            "&#x00100;" ><!--LATIN CAPITAL LETTER A WITH MACRON -->
<!ENTITY And              "&#x02A53;" ><!--DOUBLE LOGICAL AND -->
<!ENTITY Aogon            "&#x00104;" ><!--LATIN CAPITAL LETTER A WITH OGONEK -->
<!ENTITY Aopf             "&#x1D538;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL A -->
<!ENTITY ApplyFunction    "&#x02061;" ><!--FUNCTION APPLICATION -->
<!ENTITY Aring            "&#x000C5;" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
<!ENTITY Ascr             "&#x1D49C;" ><!--MATHEMATICAL SCRIPT CAPITAL A -->
<!ENTITY Assign           "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY Atilde           "&#x000C3;" ><!--LATIN CAPITAL LETTER A WITH TILDE -->
<!ENTITY Auml             "&#x000C4;" ><!--LATIN CAPITAL LETTER A WITH DIAERESIS -->
<!ENTITY Backslash        "&#x02216;" ><!--SET MINUS -->
<!ENTITY Barv             "&#x02AE7;" ><!--SHORT DOWN TACK WITH OVERBAR -->
<!ENTITY Barwed           "&#x02306;" ><!--PERSPECTIVE -->
<!ENTITY Bcy              "&#x00411;" ><!--CYRILLIC CAPITAL LETTER BE -->
<!ENTITY Because          "&#x02235;" ><!--BECAUSE -->
<!ENTITY Bernoullis       "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY Beta             "&#x00392;" ><!--GREEK CAPITAL LETTER BETA -->
<!ENTITY Bfr              "&#x1D505;" ><!--MATHEMATICAL FRAKTUR CAPITAL B -->
<!ENTITY Bopf             "&#x1D539;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL B -->
<!ENTITY Breve            "&#x002D8;" ><!--BREVE -->
<!ENTITY Bscr             "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY Bumpeq           "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY CHcy             "&#x00427;" ><!--CYRILLIC CAPITAL LETTER CHE -->
<!ENTITY COPY             "&#x000A9;" ><!--COPYRIGHT SIGN -->
<!ENTITY Cacute           "&#x00106;" ><!--LATIN CAPITAL LETTER C WITH ACUTE -->
<!ENTITY Cap              "&#x022D2;" ><!--DOUBLE INTERSECTION -->
<!ENTITY CapitalDifferentialD "&#x02145;" ><!--DOUBLE-STRUCK ITALIC CAPITAL D -->
<!ENTITY Cayleys          "&#x0212D;" ><!--BLACK-LETTER CAPITAL C -->
<!ENTITY Ccaron           "&#x0010C;" ><!--LATIN CAPITAL LETTER C WITH CARON -->
<!ENTITY Ccedil           "&#x000C7;" ><!--LATIN CAPITAL LETTER C WITH CEDILLA -->
<!ENTITY Ccirc            "&#x00108;" ><!--LATIN CAPITAL LETTER C WITH CIRCUMFLEX -->
<!ENTITY Cconint          "&#x02230;" ><!--VOLUME INTEGRAL -->
<!ENTITY Cdot             "&#x0010A;" ><!--LATIN CAPITAL LETTER C WITH DOT ABOVE -->
<!ENTITY Cedilla          "&#x000B8;" ><!--CEDILLA -->
<!ENTITY CenterDot        "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY Cfr              "&#x0212D;" ><!--BLACK-LETTER CAPITAL C -->
<!ENTITY Chi              "&#x003A7;" ><!--GREEK CAPITAL LETTER CHI -->
<!ENTITY CircleDot        "&#x02299;" ><!--CIRCLED DOT OPERATOR -->
<!ENTITY CircleMinus      "&#x02296;" ><!--CIRCLED MINUS -->
<!ENTITY CirclePlus       "&#x02295;" ><!--CIRCLED PLUS -->
<!ENTITY CircleTimes      "&#x02297;" ><!--CIRCLED TIMES -->
<!ENTITY ClockwiseContourIntegral "&#x02232;" ><!--CLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY CloseCurlyDoubleQuote "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY CloseCurlyQuote  "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY Colon            "&#x02237;" ><!--PROPORTION -->
<!ENTITY Colone           "&#x02A74;" ><!--DOUBLE COLON EQUAL -->
<!ENTITY Congruent        "&#x02261;" ><!--IDENTICAL TO -->
<!ENTITY Conint           "&#x0222F;" ><!--SURFACE INTEGRAL -->
<!ENTITY ContourIntegral  "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY Copf             "&#x02102;" ><!--DOUBLE-STRUCK CAPITAL C -->
<!ENTITY Coproduct        "&#x02210;" ><!--N-ARY COPRODUCT -->
<!ENTITY CounterClockwiseContourIntegral "&#x02233;" ><!--ANTICLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY Cross            "&#x02A2F;" ><!--VECTOR OR CROSS PRODUCT -->
<!ENTITY Cscr             "&#x1D49E;" ><!--MATHEMATICAL SCRIPT CAPITAL C -->
<!ENTITY Cup              "&#x022D3;" ><!--DOUBLE UNION -->
<!ENTITY CupCap           "&#x0224D;" ><!--EQUIVALENT TO -->
<!ENTITY DD               "&#x02145;" ><!--DOUBLE-STRUCK ITALIC CAPITAL D -->
<!ENTITY DDotrahd         "&#x02911;" ><!--RIGHTWARDS ARROW WITH DOTTED STEM -->
<!ENTITY DJcy             "&#x00402;" ><!--CYRILLIC CAPITAL LETTER DJE -->
<!ENTITY DScy             "&#x00405;" ><!--CYRILLIC CAPITAL LETTER DZE -->
<!ENTITY DZcy             "&#x0040F;" ><!--CYRILLIC CAPITAL LETTER DZHE -->
<!ENTITY Dagger           "&#x02021;" ><!--DOUBLE DAGGER -->
<!ENTITY Darr             "&#x021A1;" ><!--DOWNWARDS TWO HEADED ARROW -->
<!ENTITY Dashv            "&#x02AE4;" ><!--VERTICAL BAR DOUBLE LEFT TURNSTILE -->
<!ENTITY Dcaron           "&#x0010E;" ><!--LATIN CAPITAL LETTER D WITH CARON -->
<!ENTITY Dcy              "&#x00414;" ><!--CYRILLIC CAPITAL LETTER DE -->
<!ENTITY Del              "&#x02207;" ><!--NABLA -->
<!ENTITY Delta            "&#x00394;" ><!--GREEK CAPITAL LETTER DELTA -->
<!ENTITY Dfr              "&#x1D507;" ><!--MATHEMATICAL FRAKTUR CAPITAL D -->
<!ENTITY DiacriticalAcute "&#x000B4;" ><!--ACUTE ACCENT -->
<!ENTITY DiacriticalDot   "&#x002D9;" ><!--DOT ABOVE -->
<!ENTITY DiacriticalDoubleAcute "&#x002DD;" ><!--DOUBLE ACUTE ACCENT -->
<!ENTITY DiacriticalGrave "&#x00060;" ><!--GRAVE ACCENT -->
<!ENTITY DiacriticalTilde "&#x002DC;" ><!--SMALL TILDE -->
<!ENTITY Diamond          "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY DifferentialD    "&#x02146;" ><!--DOUBLE-STRUCK ITALIC SMALL D -->
<!ENTITY Dopf             "&#x1D53B;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL D -->
<!ENTITY Dot              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY DotDot           " &#x020DC;" ><!--COMBINING FOUR DOTS ABOVE -->
<!ENTITY DotEqual         "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY DoubleContourIntegral "&#x0222F;" ><!--SURFACE INTEGRAL -->
<!ENTITY DoubleDot        "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY DoubleDownArrow  "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY DoubleLeftArrow  "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY DoubleLeftRightArrow "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY DoubleLeftTee    "&#x02AE4;" ><!--VERTICAL BAR DOUBLE LEFT TURNSTILE -->
<!ENTITY DoubleLongLeftArrow "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY DoubleLongLeftRightArrow "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY DoubleLongRightArrow "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY DoubleRightArrow "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY DoubleRightTee   "&#x022A8;" ><!--TRUE -->
<!ENTITY DoubleUpArrow    "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY DoubleUpDownArrow "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY DoubleVerticalBar "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY DownArrow        "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY DownArrowBar     "&#x02913;" ><!--DOWNWARDS ARROW TO BAR -->
<!ENTITY DownArrowUpArrow "&#x021F5;" ><!--DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW -->
<!ENTITY DownBreve        " &#x00311;" ><!--COMBINING INVERTED BREVE -->
<!ENTITY DownLeftRightVector "&#x02950;" ><!--LEFT BARB DOWN RIGHT BARB DOWN HARPOON -->
<!ENTITY DownLeftTeeVector "&#x0295E;" ><!--LEFTWARDS HARPOON WITH BARB DOWN FROM BAR -->
<!ENTITY DownLeftVector   "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY DownLeftVectorBar "&#x02956;" ><!--LEFTWARDS HARPOON WITH BARB DOWN TO BAR -->
<!ENTITY DownRightTeeVector "&#x0295F;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR -->
<!ENTITY DownRightVector  "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY DownRightVectorBar "&#x02957;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN TO BAR -->
<!ENTITY DownTee          "&#x022A4;" ><!--DOWN TACK -->
<!ENTITY DownTeeArrow     "&#x021A7;" ><!--DOWNWARDS ARROW FROM BAR -->
<!ENTITY Downarrow        "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY Dscr             "&#x1D49F;" ><!--MATHEMATICAL SCRIPT CAPITAL D -->
<!ENTITY Dstrok           "&#x00110;" ><!--LATIN CAPITAL LETTER D WITH STROKE -->
<!ENTITY ENG              "&#x0014A;" ><!--LATIN CAPITAL LETTER ENG -->
<!ENTITY ETH              "&#x000D0;" ><!--LATIN CAPITAL LETTER ETH -->
<!ENTITY Eacute           "&#x000C9;" ><!--LATIN CAPITAL LETTER E WITH ACUTE -->
<!ENTITY Ecaron           "&#x0011A;" ><!--LATIN CAPITAL LETTER E WITH CARON -->
<!ENTITY Ecirc            "&#x000CA;" ><!--LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
<!ENTITY Ecy              "&#x0042D;" ><!--CYRILLIC CAPITAL LETTER E -->
<!ENTITY Edot             "&#x00116;" ><!--LATIN CAPITAL LETTER E WITH DOT ABOVE -->
<!ENTITY Efr              "&#x1D508;" ><!--MATHEMATICAL FRAKTUR CAPITAL E -->
<!ENTITY Egrave           "&#x000C8;" ><!--LATIN CAPITAL LETTER E WITH GRAVE -->
<!ENTITY Element          "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY Emacr            "&#x00112;" ><!--LATIN CAPITAL LETTER E WITH MACRON -->
<!ENTITY EmptySmallSquare "&#x025FB;" ><!--WHITE MEDIUM SQUARE -->
<!ENTITY EmptyVerySmallSquare "&#x025AB;" ><!--WHITE SMALL SQUARE -->
<!ENTITY Eogon            "&#x00118;" ><!--LATIN CAPITAL LETTER E WITH OGONEK -->
<!ENTITY Eopf             "&#x1D53C;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL E -->
<!ENTITY Epsilon          "&#x00395;" ><!--GREEK CAPITAL LETTER EPSILON -->
<!ENTITY Equal            "&#x02A75;" ><!--TWO CONSECUTIVE EQUALS SIGNS -->
<!ENTITY EqualTilde       "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY Equilibrium      "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY Escr             "&#x02130;" ><!--SCRIPT CAPITAL E -->
<!ENTITY Esim             "&#x02A73;" ><!--EQUALS SIGN ABOVE TILDE OPERATOR -->
<!ENTITY Eta              "&#x00397;" ><!--GREEK CAPITAL LETTER ETA -->
<!ENTITY Euml             "&#x000CB;" ><!--LATIN CAPITAL LETTER E WITH DIAERESIS -->
<!ENTITY Exists           "&#x02203;" ><!--THERE EXISTS -->
<!ENTITY ExponentialE     "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY Fcy              "&#x00424;" ><!--CYRILLIC CAPITAL LETTER EF -->
<!ENTITY Ffr              "&#x1D509;" ><!--MATHEMATICAL FRAKTUR CAPITAL F -->
<!ENTITY FilledSmallSquare "&#x025FC;" ><!--BLACK MEDIUM SQUARE -->
<!ENTITY FilledVerySmallSquare "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY Fopf             "&#x1D53D;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL F -->
<!ENTITY ForAll           "&#x02200;" ><!--FOR ALL -->
<!ENTITY Fouriertrf       "&#x02131;" ><!--SCRIPT CAPITAL F -->
<!ENTITY Fscr             "&#x02131;" ><!--SCRIPT CAPITAL F -->
<!ENTITY GJcy             "&#x00403;" ><!--CYRILLIC CAPITAL LETTER GJE -->
<!ENTITY GT               "&#x0003E;" ><!--GREATER-THAN SIGN -->
<!ENTITY Gamma            "&#x00393;" ><!--GREEK CAPITAL LETTER GAMMA -->
<!ENTITY Gammad           "&#x003DC;" ><!--GREEK LETTER DIGAMMA -->
<!ENTITY Gbreve           "&#x0011E;" ><!--LATIN CAPITAL LETTER G WITH BREVE -->
<!ENTITY Gcedil           "&#x00122;" ><!--LATIN CAPITAL LETTER G WITH CEDILLA -->
<!ENTITY Gcirc            "&#x0011C;" ><!--LATIN CAPITAL LETTER G WITH CIRCUMFLEX -->
<!ENTITY Gcy              "&#x00413;" ><!--CYRILLIC CAPITAL LETTER GHE -->
<!ENTITY Gdot             "&#x00120;" ><!--LATIN CAPITAL LETTER G WITH DOT ABOVE -->
<!ENTITY Gfr              "&#x1D50A;" ><!--MATHEMATICAL FRAKTUR CAPITAL G -->
<!ENTITY Gg               "&#x022D9;" ><!--VERY MUCH GREATER-THAN -->
<!ENTITY Gopf             "&#x1D53E;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL G -->
<!ENTITY GreaterEqual     "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY GreaterEqualLess "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY GreaterFullEqual "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY GreaterGreater   "&#x02AA2;" ><!--DOUBLE NESTED GREATER-THAN -->
<!ENTITY GreaterLess      "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY GreaterSlantEqual "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY GreaterTilde     "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY Gscr             "&#x1D4A2;" ><!--MATHEMATICAL SCRIPT CAPITAL G -->
<!ENTITY Gt               "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY HARDcy           "&#x0042A;" ><!--CYRILLIC CAPITAL LETTER HARD SIGN -->
<!ENTITY Hacek            "&#x002C7;" ><!--CARON -->
<!ENTITY Hat              "&#x0005E;" ><!--CIRCUMFLEX ACCENT -->
<!ENTITY Hcirc            "&#x00124;" ><!--LATIN CAPITAL LETTER H WITH CIRCUMFLEX -->
<!ENTITY Hfr              "&#x0210C;" ><!--BLACK-LETTER CAPITAL H -->
<!ENTITY HilbertSpace     "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY Hopf             "&#x0210D;" ><!--DOUBLE-STRUCK CAPITAL H -->
<!ENTITY HorizontalLine   "&#x02500;" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
<!ENTITY Hscr             "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY Hstrok           "&#x00126;" ><!--LATIN CAPITAL LETTER H WITH STROKE -->
<!ENTITY HumpDownHump     "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY HumpEqual        "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY IEcy             "&#x00415;" ><!--CYRILLIC CAPITAL LETTER IE -->
<!ENTITY IJlig            "&#x00132;" ><!--LATIN CAPITAL LIGATURE IJ -->
<!ENTITY IOcy             "&#x00401;" ><!--CYRILLIC CAPITAL LETTER IO -->
<!ENTITY Iacute           "&#x000CD;" ><!--LATIN CAPITAL LETTER I WITH ACUTE -->
<!ENTITY Icirc            "&#x000CE;" ><!--LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
<!ENTITY Icy              "&#x00418;" ><!--CYRILLIC CAPITAL LETTER I -->
<!ENTITY Idot             "&#x00130;" ><!--LATIN CAPITAL LETTER I WITH DOT ABOVE -->
<!ENTITY Ifr              "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY Igrave           "&#x000CC;" ><!--LATIN CAPITAL LETTER I WITH GRAVE -->
<!ENTITY Im               "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY Imacr            "&#x0012A;" ><!--LATIN CAPITAL LETTER I WITH MACRON -->
<!ENTITY ImaginaryI       "&#x02148;" ><!--DOUBLE-STRUCK ITALIC SMALL I -->
<!ENTITY Implies          "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Int              "&#x0222C;" ><!--DOUBLE INTEGRAL -->
<!ENTITY Integral         "&#x0222B;" ><!--INTEGRAL -->
<!ENTITY Intersection     "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY InvisibleComma   "&#x02063;" ><!--INVISIBLE SEPARATOR -->
<!ENTITY InvisibleTimes   "&#x02062;" ><!--INVISIBLE TIMES -->
<!ENTITY Iogon            "&#x0012E;" ><!--LATIN CAPITAL LETTER I WITH OGONEK -->
<!ENTITY Iopf             "&#x1D540;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL I -->
<!ENTITY Iota             "&#x00399;" ><!--GREEK CAPITAL LETTER IOTA -->
<!ENTITY Iscr             "&#x02110;" ><!--SCRIPT CAPITAL I -->
<!ENTITY Itilde           "&#x00128;" ><!--LATIN CAPITAL LETTER I WITH TILDE -->
<!ENTITY Iukcy            "&#x00406;" ><!--CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I -->
<!ENTITY Iuml             "&#x000CF;" ><!--LATIN CAPITAL LETTER I WITH DIAERESIS -->
<!ENTITY Jcirc            "&#x00134;" ><!--LATIN CAPITAL LETTER J WITH CIRCUMFLEX -->
<!ENTITY Jcy              "&#x00419;" ><!--CYRILLIC CAPITAL LETTER SHORT I -->
<!ENTITY Jfr              "&#x1D50D;" ><!--MATHEMATICAL FRAKTUR CAPITAL J -->
<!ENTITY Jopf             "&#x1D541;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL J -->
<!ENTITY Jscr             "&#x1D4A5;" ><!--MATHEMATICAL SCRIPT CAPITAL J -->
<!ENTITY Jsercy           "&#x00408;" ><!--CYRILLIC CAPITAL LETTER JE -->
<!ENTITY Jukcy            "&#x00404;" ><!--CYRILLIC CAPITAL LETTER UKRAINIAN IE -->
<!ENTITY KHcy             "&#x00425;" ><!--CYRILLIC CAPITAL LETTER HA -->
<!ENTITY KJcy             "&#x0040C;" ><!--CYRILLIC CAPITAL LETTER KJE -->
<!ENTITY Kappa            "&#x0039A;" ><!--GREEK CAPITAL LETTER KAPPA -->
<!ENTITY Kcedil           "&#x00136;" ><!--LATIN CAPITAL LETTER K WITH CEDILLA -->
<!ENTITY Kcy              "&#x0041A;" ><!--CYRILLIC CAPITAL LETTER KA -->
<!ENTITY Kfr              "&#x1D50E;" ><!--MATHEMATICAL FRAKTUR CAPITAL K -->
<!ENTITY Kopf             "&#x1D542;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL K -->
<!ENTITY Kscr             "&#x1D4A6;" ><!--MATHEMATICAL SCRIPT CAPITAL K -->
<!ENTITY LJcy             "&#x00409;" ><!--CYRILLIC CAPITAL LETTER LJE -->
<!ENTITY LT               "&#38;#60;" ><!--LESS-THAN SIGN -->
<!ENTITY Lacute           "&#x00139;" ><!--LATIN CAPITAL LETTER L WITH ACUTE -->
<!ENTITY Lambda           "&#x0039B;" ><!--GREEK CAPITAL LETTER LAMDA -->
<!ENTITY Lang             "&#x027EA;" ><!--MATHEMATICAL LEFT DOUBLE ANGLE BRACKET -->
<!ENTITY Laplacetrf       "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY Larr             "&#x0219E;" ><!--LEFTWARDS TWO HEADED ARROW -->
<!ENTITY Lcaron           "&#x0013D;" ><!--LATIN CAPITAL LETTER L WITH CARON -->
<!ENTITY Lcedil           "&#x0013B;" ><!--LATIN CAPITAL LETTER L WITH CEDILLA -->
<!ENTITY Lcy              "&#x0041B;" ><!--CYRILLIC CAPITAL LETTER EL -->
<!ENTITY LeftAngleBracket "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY LeftArrow        "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY LeftArrowBar     "&#x021E4;" ><!--LEFTWARDS ARROW TO BAR -->
<!ENTITY LeftArrowRightArrow "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY LeftCeiling      "&#x02308;" ><!--LEFT CEILING -->
<!ENTITY LeftDoubleBracket "&#x027E6;" ><!--MATHEMATICAL LEFT WHITE SQUARE BRACKET -->
<!ENTITY LeftDownTeeVector "&#x02961;" ><!--DOWNWARDS HARPOON WITH BARB LEFT FROM BAR -->
<!ENTITY LeftDownVector   "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY LeftDownVectorBar "&#x02959;" ><!--DOWNWARDS HARPOON WITH BARB LEFT TO BAR -->
<!ENTITY LeftFloor        "&#x0230A;" ><!--LEFT FLOOR -->
<!ENTITY LeftRightArrow   "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY LeftRightVector  "&#x0294E;" ><!--LEFT BARB UP RIGHT BARB UP HARPOON -->
<!ENTITY LeftTee          "&#x022A3;" ><!--LEFT TACK -->
<!ENTITY LeftTeeArrow     "&#x021A4;" ><!--LEFTWARDS ARROW FROM BAR -->
<!ENTITY LeftTeeVector    "&#x0295A;" ><!--LEFTWARDS HARPOON WITH BARB UP FROM BAR -->
<!ENTITY LeftTriangle     "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY LeftTriangleBar  "&#x029CF;" ><!--LEFT TRIANGLE BESIDE VERTICAL BAR -->
<!ENTITY LeftTriangleEqual "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY LeftUpDownVector "&#x02951;" ><!--UP BARB LEFT DOWN BARB LEFT HARPOON -->
<!ENTITY LeftUpTeeVector  "&#x02960;" ><!--UPWARDS HARPOON WITH BARB LEFT FROM BAR -->
<!ENTITY LeftUpVector     "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY LeftUpVectorBar  "&#x02958;" ><!--UPWARDS HARPOON WITH BARB LEFT TO BAR -->
<!ENTITY LeftVector       "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY LeftVectorBar    "&#x02952;" ><!--LEFTWARDS HARPOON WITH BARB UP TO BAR -->
<!ENTITY Leftarrow        "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY Leftrightarrow   "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY LessEqualGreater "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY LessFullEqual    "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY LessGreater      "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY LessLess         "&#x02AA1;" ><!--DOUBLE NESTED LESS-THAN -->
<!ENTITY LessSlantEqual   "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY LessTilde        "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY Lfr              "&#x1D50F;" ><!--MATHEMATICAL FRAKTUR CAPITAL L -->
<!ENTITY Ll               "&#x022D8;" ><!--VERY MUCH LESS-THAN -->
<!ENTITY Lleftarrow       "&#x021DA;" ><!--LEFTWARDS TRIPLE ARROW -->
<!ENTITY Lmidot           "&#x0013F;" ><!--LATIN CAPITAL LETTER L WITH MIDDLE DOT -->
<!ENTITY LongLeftArrow    "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY LongLeftRightArrow "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY LongRightArrow   "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY Longleftarrow    "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY Longleftrightarrow "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY Longrightarrow   "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Lopf             "&#x1D543;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL L -->
<!ENTITY LowerLeftArrow   "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY LowerRightArrow  "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY Lscr             "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY Lsh              "&#x021B0;" ><!--UPWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY Lstrok           "&#x00141;" ><!--LATIN CAPITAL LETTER L WITH STROKE -->
<!ENTITY Lt               "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY Map              "&#x02905;" ><!--RIGHTWARDS TWO-HEADED ARROW FROM BAR -->
<!ENTITY Mcy              "&#x0041C;" ><!--CYRILLIC CAPITAL LETTER EM -->
<!ENTITY MediumSpace      "&#x0205F;" ><!--MEDIUM MATHEMATICAL SPACE -->
<!ENTITY Mellintrf        "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY Mfr              "&#x1D510;" ><!--MATHEMATICAL FRAKTUR CAPITAL M -->
<!ENTITY MinusPlus        "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY Mopf             "&#x1D544;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL M -->
<!ENTITY Mscr             "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY Mu               "&#x0039C;" ><!--GREEK CAPITAL LETTER MU -->
<!ENTITY NJcy             "&#x0040A;" ><!--CYRILLIC CAPITAL LETTER NJE -->
<!ENTITY Nacute           "&#x00143;" ><!--LATIN CAPITAL LETTER N WITH ACUTE -->
<!ENTITY Ncaron           "&#x00147;" ><!--LATIN CAPITAL LETTER N WITH CARON -->
<!ENTITY Ncedil           "&#x00145;" ><!--LATIN CAPITAL LETTER N WITH CEDILLA -->
<!ENTITY Ncy              "&#x0041D;" ><!--CYRILLIC CAPITAL LETTER EN -->
<!ENTITY NegativeMediumSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeThickSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeThinSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeVeryThinSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NestedGreaterGreater "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY NestedLessLess   "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY NewLine          "&#x0000A;" ><!--LINE FEED (LF) -->
<!ENTITY Nfr              "&#x1D511;" ><!--MATHEMATICAL FRAKTUR CAPITAL N -->
<!ENTITY NoBreak          "&#x02060;" ><!--WORD JOINER -->
<!ENTITY NonBreakingSpace "&#x000A0;" ><!--NO-BREAK SPACE -->
<!ENTITY Nopf             "&#x02115;" ><!--DOUBLE-STRUCK CAPITAL N -->
<!ENTITY Not              "&#x02AEC;" ><!--DOUBLE STROKE NOT SIGN -->
<!ENTITY NotCongruent     "&#x02262;" ><!--NOT IDENTICAL TO -->
<!ENTITY NotCupCap        "&#x0226D;" ><!--NOT EQUIVALENT TO -->
<!ENTITY NotDoubleVerticalBar "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY NotElement       "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY NotEqual         "&#x02260;" ><!--NOT EQUAL TO -->
<!ENTITY NotEqualTilde    "&#x02242;&#x00338;" ><!--MINUS TILDE with slash -->
<!ENTITY NotExists        "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY NotGreater       "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY NotGreaterEqual  "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY NotGreaterFullEqual "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY NotGreaterGreater "&#x0226B;&#x00338;" ><!--MUCH GREATER THAN with slash -->
<!ENTITY NotGreaterLess   "&#x02279;" ><!--NEITHER GREATER-THAN NOR LESS-THAN -->
<!ENTITY NotGreaterSlantEqual "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY NotGreaterTilde  "&#x02275;" ><!--NEITHER GREATER-THAN NOR EQUIVALENT TO -->
<!ENTITY NotHumpDownHump  "&#x0224E;&#x00338;" ><!--GEOMETRICALLY EQUIVALENT TO with slash -->
<!ENTITY NotHumpEqual     "&#x0224F;&#x00338;" ><!--DIFFERENCE BETWEEN with slash -->
<!ENTITY NotLeftTriangle  "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY NotLeftTriangleBar "&#x029CF;&#x00338;" ><!--LEFT TRIANGLE BESIDE VERTICAL BAR with slash -->
<!ENTITY NotLeftTriangleEqual "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY NotLess          "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY NotLessEqual     "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY NotLessGreater   "&#x02278;" ><!--NEITHER LESS-THAN NOR GREATER-THAN -->
<!ENTITY NotLessLess      "&#x0226A;&#x00338;" ><!--MUCH LESS THAN with slash -->
<!ENTITY NotLessSlantEqual "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY NotLessTilde     "&#x02274;" ><!--NEITHER LESS-THAN NOR EQUIVALENT TO -->
<!ENTITY NotNestedGreaterGreater "&#x02AA2;&#x00338;" ><!--DOUBLE NESTED GREATER-THAN with slash -->
<!ENTITY NotNestedLessLess "&#x02AA1;&#x00338;" ><!--DOUBLE NESTED LESS-THAN with slash -->
<!ENTITY NotPrecedes      "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY NotPrecedesEqual "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY NotPrecedesSlantEqual "&#x022E0;" ><!--DOES NOT PRECEDE OR EQUAL -->
<!ENTITY NotReverseElement "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY NotRightTriangle "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY NotRightTriangleBar "&#x029D0;&#x00338;" ><!--VERTICAL BAR BESIDE RIGHT TRIANGLE with slash -->
<!ENTITY NotRightTriangleEqual "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY NotSquareSubset  "&#x0228F;&#x00338;" ><!--SQUARE IMAGE OF with slash -->
<!ENTITY NotSquareSubsetEqual "&#x022E2;" ><!--NOT SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY NotSquareSuperset "&#x02290;&#x00338;" ><!--SQUARE ORIGINAL OF with slash -->
<!ENTITY NotSquareSupersetEqual "&#x022E3;" ><!--NOT SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY NotSubset        "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY NotSubsetEqual   "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY NotSucceeds      "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY NotSucceedsEqual "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY NotSucceedsSlantEqual "&#x022E1;" ><!--DOES NOT SUCCEED OR EQUAL -->
<!ENTITY NotSucceedsTilde "&#x0227F;&#x00338;" ><!--SUCCEEDS OR EQUIVALENT TO with slash -->
<!ENTITY NotSuperset      "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY NotSupersetEqual "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY NotTilde         "&#x02241;" ><!--NOT TILDE -->
<!ENTITY NotTildeEqual    "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY NotTildeFullEqual "&#x02247;" ><!--NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO -->
<!ENTITY NotTildeTilde    "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY NotVerticalBar   "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY Nscr             "&#x1D4A9;" ><!--MATHEMATICAL SCRIPT CAPITAL N -->
<!ENTITY Ntilde           "&#x000D1;" ><!--LATIN CAPITAL LETTER N WITH TILDE -->
<!ENTITY Nu               "&#x0039D;" ><!--GREEK CAPITAL LETTER NU -->
<!ENTITY OElig            "&#x00152;" ><!--LATIN CAPITAL LIGATURE OE -->
<!ENTITY Oacute           "&#x000D3;" ><!--LATIN CAPITAL LETTER O WITH ACUTE -->
<!ENTITY Ocirc            "&#x000D4;" ><!--LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
<!ENTITY Ocy              "&#x0041E;" ><!--CYRILLIC CAPITAL LETTER O -->
<!ENTITY Odblac           "&#x00150;" ><!--LATIN CAPITAL LETTER O WITH DOUBLE ACUTE -->
<!ENTITY Ofr              "&#x1D512;" ><!--MATHEMATICAL FRAKTUR CAPITAL O -->
<!ENTITY Ograve           "&#x000D2;" ><!--LATIN CAPITAL LETTER O WITH GRAVE -->
<!ENTITY Omacr            "&#x0014C;" ><!--LATIN CAPITAL LETTER O WITH MACRON -->
<!ENTITY Omega            "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
<!ENTITY Omicron          "&#x0039F;" ><!--GREEK CAPITAL LETTER OMICRON -->
<!ENTITY Oopf             "&#x1D546;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL O -->
<!ENTITY OpenCurlyDoubleQuote "&#x0201C;" ><!--LEFT DOUBLE QUOTATION MARK -->
<!ENTITY OpenCurlyQuote   "&#x02018;" ><!--LEFT SINGLE QUOTATION MARK -->
<!ENTITY Or               "&#x02A54;" ><!--DOUBLE LOGICAL OR -->
<!ENTITY Oscr             "&#x1D4AA;" ><!--MATHEMATICAL SCRIPT CAPITAL O -->
<!ENTITY Oslash           "&#x000D8;" ><!--LATIN CAPITAL LETTER O WITH STROKE -->
<!ENTITY Otilde           "&#x000D5;" ><!--LATIN CAPITAL LETTER O WITH TILDE -->
<!ENTITY Otimes           "&#x02A37;" ><!--MULTIPLICATION SIGN IN DOUBLE CIRCLE -->
<!ENTITY Ouml             "&#x000D6;" ><!--LATIN CAPITAL LETTER O WITH DIAERESIS -->
<!ENTITY OverBar          "&#x0203E;" ><!--OVERLINE -->
<!ENTITY OverBrace        "&#x023DE;" ><!--TOP CURLY BRACKET -->
<!ENTITY OverBracket      "&#x023B4;" ><!--TOP SQUARE BRACKET -->
<!ENTITY OverParenthesis  "&#x023DC;" ><!--TOP PARENTHESIS -->
<!ENTITY PartialD         "&#x02202;" ><!--PARTIAL DIFFERENTIAL -->
<!ENTITY Pcy              "&#x0041F;" ><!--CYRILLIC CAPITAL LETTER PE -->
<!ENTITY Pfr              "&#x1D513;" ><!--MATHEMATICAL FRAKTUR CAPITAL P -->
<!ENTITY Phi              "&#x003A6;" ><!--GREEK CAPITAL LETTER PHI -->
<!ENTITY Pi               "&#x003A0;" ><!--GREEK CAPITAL LETTER PI -->
<!ENTITY PlusMinus        "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY Poincareplane    "&#x0210C;" ><!--BLACK-LETTER CAPITAL H -->
<!ENTITY Popf             "&#x02119;" ><!--DOUBLE-STRUCK CAPITAL P -->
<!ENTITY Pr               "&#x02ABB;" ><!--DOUBLE PRECEDES -->
<!ENTITY Precedes         "&#x0227A;" ><!--PRECEDES -->
<!ENTITY PrecedesEqual    "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY PrecedesSlantEqual "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY PrecedesTilde    "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY Prime            "&#x02033;" ><!--DOUBLE PRIME -->
<!ENTITY Product          "&#x0220F;" ><!--N-ARY PRODUCT -->
<!ENTITY Proportion       "&#x02237;" ><!--PROPORTION -->
<!ENTITY Proportional     "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY Pscr             "&#x1D4AB;" ><!--MATHEMATICAL SCRIPT CAPITAL P -->
<!ENTITY Psi              "&#x003A8;" ><!--GREEK CAPITAL LETTER PSI -->
<!ENTITY QUOT             "&#x00022;" ><!--QUOTATION MARK -->
<!ENTITY Qfr              "&#x1D514;" ><!--MATHEMATICAL FRAKTUR CAPITAL Q -->
<!ENTITY Qopf             "&#x0211A;" ><!--DOUBLE-STRUCK CAPITAL Q -->
<!ENTITY Qscr             "&#x1D4AC;" ><!--MATHEMATICAL SCRIPT CAPITAL Q -->
<!ENTITY RBarr            "&#x02910;" ><!--RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW -->
<!ENTITY REG              "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY Racute           "&#x00154;" ><!--LATIN CAPITAL LETTER R WITH ACUTE -->
<!ENTITY Rang             "&#x027EB;" ><!--MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET -->
<!ENTITY Rarr             "&#x021A0;" ><!--RIGHTWARDS TWO HEADED ARROW -->
<!ENTITY Rarrtl           "&#x02916;" ><!--RIGHTWARDS TWO-HEADED ARROW WITH TAIL -->
<!ENTITY Rcaron           "&#x00158;" ><!--LATIN CAPITAL LETTER R WITH CARON -->
<!ENTITY Rcedil           "&#x00156;" ><!--LATIN CAPITAL LETTER R WITH CEDILLA -->
<!ENTITY Rcy              "&#x00420;" ><!--CYRILLIC CAPITAL LETTER ER -->
<!ENTITY Re               "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY ReverseElement   "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY ReverseEquilibrium "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY ReverseUpEquilibrium "&#x0296F;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY Rfr              "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY Rho              "&#x003A1;" ><!--GREEK CAPITAL LETTER RHO -->
<!ENTITY RightAngleBracket "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY RightArrow       "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY RightArrowBar    "&#x021E5;" ><!--RIGHTWARDS ARROW TO BAR -->
<!ENTITY RightArrowLeftArrow "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY RightCeiling     "&#x02309;" ><!--RIGHT CEILING -->
<!ENTITY RightDoubleBracket "&#x027E7;" ><!--MATHEMATICAL RIGHT WHITE SQUARE BRACKET -->
<!ENTITY RightDownTeeVector "&#x0295D;" ><!--DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR -->
<!ENTITY RightDownVector  "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY RightDownVectorBar "&#x02955;" ><!--DOWNWARDS HARPOON WITH BARB RIGHT TO BAR -->
<!ENTITY RightFloor       "&#x0230B;" ><!--RIGHT FLOOR -->
<!ENTITY RightTee         "&#x022A2;" ><!--RIGHT TACK -->
<!ENTITY RightTeeArrow    "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY RightTeeVector   "&#x0295B;" ><!--RIGHTWARDS HARPOON WITH BARB UP FROM BAR -->
<!ENTITY RightTriangle    "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY RightTriangleBar "&#x029D0;" ><!--VERTICAL BAR BESIDE RIGHT TRIANGLE -->
<!ENTITY RightTriangleEqual "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY RightUpDownVector "&#x0294F;" ><!--UP BARB RIGHT DOWN BARB RIGHT HARPOON -->
<!ENTITY RightUpTeeVector "&#x0295C;" ><!--UPWARDS HARPOON WITH BARB RIGHT FROM BAR -->
<!ENTITY RightUpVector    "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY RightUpVectorBar "&#x02954;" ><!--UPWARDS HARPOON WITH BARB RIGHT TO BAR -->
<!ENTITY RightVector      "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY RightVectorBar   "&#x02953;" ><!--RIGHTWARDS HARPOON WITH BARB UP TO BAR -->
<!ENTITY Rightarrow       "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Ropf             "&#x0211D;" ><!--DOUBLE-STRUCK CAPITAL R -->
<!ENTITY RoundImplies     "&#x02970;" ><!--RIGHT DOUBLE ARROW WITH ROUNDED HEAD -->
<!ENTITY Rrightarrow      "&#x021DB;" ><!--RIGHTWARDS TRIPLE ARROW -->
<!ENTITY Rscr             "&#x0211B;" ><!--SCRIPT CAPITAL R -->
<!ENTITY Rsh              "&#x021B1;" ><!--UPWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY RuleDelayed      "&#x029F4;" ><!--RULE-DELAYED -->
<!ENTITY SHCHcy           "&#x00429;" ><!--CYRILLIC CAPITAL LETTER SHCHA -->
<!ENTITY SHcy             "&#x00428;" ><!--CYRILLIC CAPITAL LETTER SHA -->
<!ENTITY SOFTcy           "&#x0042C;" ><!--CYRILLIC CAPITAL LETTER SOFT SIGN -->
<!ENTITY Sacute           "&#x0015A;" ><!--LATIN CAPITAL LETTER S WITH ACUTE -->
<!ENTITY Sc               "&#x02ABC;" ><!--DOUBLE SUCCEEDS -->
<!ENTITY Scaron           "&#x00160;" ><!--LATIN CAPITAL LETTER S WITH CARON -->
<!ENTITY Scedil           "&#x0015E;" ><!--LATIN CAPITAL LETTER S WITH CEDILLA -->
<!ENTITY Scirc            "&#x0015C;" ><!--LATIN CAPITAL LETTER S WITH CIRCUMFLEX -->
<!ENTITY Scy              "&#x00421;" ><!--CYRILLIC CAPITAL LETTER ES -->
<!ENTITY Sfr              "&#x1D516;" ><!--MATHEMATICAL FRAKTUR CAPITAL S -->
<!ENTITY ShortDownArrow   "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY ShortLeftArrow   "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY ShortRightArrow  "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY ShortUpArrow     "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY Sigma            "&#x003A3;" ><!--GREEK CAPITAL LETTER SIGMA -->
<!ENTITY SmallCircle      "&#x02218;" ><!--RING OPERATOR -->
<!ENTITY Sopf             "&#x1D54A;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL S -->
<!ENTITY Sqrt             "&#x0221A;" ><!--SQUARE ROOT -->
<!ENTITY Square           "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY SquareIntersection "&#x02293;" ><!--SQUARE CAP -->
<!ENTITY SquareSubset     "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY SquareSubsetEqual "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY SquareSuperset   "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY SquareSupersetEqual "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY SquareUnion      "&#x02294;" ><!--SQUARE CUP -->
<!ENTITY Sscr             "&#x1D4AE;" ><!--MATHEMATICAL SCRIPT CAPITAL S -->
<!ENTITY Star             "&#x022C6;" ><!--STAR OPERATOR -->
<!ENTITY Sub              "&#x022D0;" ><!--DOUBLE SUBSET -->
<!ENTITY Subset           "&#x022D0;" ><!--DOUBLE SUBSET -->
<!ENTITY SubsetEqual      "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY Succeeds         "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY SucceedsEqual    "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY SucceedsSlantEqual "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY SucceedsTilde    "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY SuchThat         "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY Sum              "&#x02211;" ><!--N-ARY SUMMATION -->
<!ENTITY Sup              "&#x022D1;" ><!--DOUBLE SUPERSET -->
<!ENTITY Superset         "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY SupersetEqual    "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY Supset           "&#x022D1;" ><!--DOUBLE SUPERSET -->
<!ENTITY THORN            "&#x000DE;" ><!--LATIN CAPITAL LETTER THORN -->
<!ENTITY TRADE            "&#x02122;" ><!--TRADE MARK SIGN -->
<!ENTITY TSHcy            "&#x0040B;" ><!--CYRILLIC CAPITAL LETTER TSHE -->
<!ENTITY TScy             "&#x00426;" ><!--CYRILLIC CAPITAL LETTER TSE -->
<!ENTITY Tab              "&#x00009;" ><!--CHARACTER TABULATION -->
<!ENTITY Tau              "&#x003A4;" ><!--GREEK CAPITAL LETTER TAU -->
<!ENTITY Tcaron           "&#x00164;" ><!--LATIN CAPITAL LETTER T WITH CARON -->
<!ENTITY Tcedil           "&#x00162;" ><!--LATIN CAPITAL LETTER T WITH CEDILLA -->
<!ENTITY Tcy              "&#x00422;" ><!--CYRILLIC CAPITAL LETTER TE -->
<!ENTITY Tfr              "&#x1D517;" ><!--MATHEMATICAL FRAKTUR CAPITAL T -->
<!ENTITY Therefore        "&#x02234;" ><!--THEREFORE -->
<!ENTITY Theta            "&#x00398;" ><!--GREEK CAPITAL LETTER THETA -->
<!ENTITY ThickSpace       "&#x0205F;&#x0200A;" ><!--space of width 5/18 em -->
<!ENTITY ThinSpace        "&#x02009;" ><!--THIN SPACE -->
<!ENTITY Tilde            "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY TildeEqual       "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY TildeFullEqual   "&#x02245;" ><!--APPROXIMATELY EQUAL TO -->
<!ENTITY TildeTilde       "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY Topf             "&#x1D54B;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL T -->
<!ENTITY TripleDot        " &#x020DB;" ><!--COMBINING THREE DOTS ABOVE -->
<!ENTITY Tscr             "&#x1D4AF;" ><!--MATHEMATICAL SCRIPT CAPITAL T -->
<!ENTITY Tstrok           "&#x00166;" ><!--LATIN CAPITAL LETTER T WITH STROKE -->
<!ENTITY Uacute           "&#x000DA;" ><!--LATIN CAPITAL LETTER U WITH ACUTE -->
<!ENTITY Uarr             "&#x0219F;" ><!--UPWARDS TWO HEADED ARROW -->
<!ENTITY Uarrocir         "&#x02949;" ><!--UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE -->
<!ENTITY Ubrcy            "&#x0040E;" ><!--CYRILLIC CAPITAL LETTER SHORT U -->
<!ENTITY Ubreve           "&#x0016C;" ><!--LATIN CAPITAL LETTER U WITH BREVE -->
<!ENTITY Ucirc            "&#x000DB;" ><!--LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
<!ENTITY Ucy              "&#x00423;" ><!--CYRILLIC CAPITAL LETTER U -->
<!ENTITY Udblac           "&#x00170;" ><!--LATIN CAPITAL LETTER U WITH DOUBLE ACUTE -->
<!ENTITY Ufr              "&#x1D518;" ><!--MATHEMATICAL FRAKTUR CAPITAL U -->
<!ENTITY Ugrave           "&#x000D9;" ><!--LATIN CAPITAL LETTER U WITH GRAVE -->
<!ENTITY Umacr            "&#x0016A;" ><!--LATIN CAPITAL LETTER U WITH MACRON -->
<!ENTITY UnderBar         "&#x0005F;" ><!--LOW LINE -->
<!ENTITY UnderBrace       "&#x023DF;" ><!--BOTTOM CURLY BRACKET -->
<!ENTITY UnderBracket     "&#x023B5;" ><!--BOTTOM SQUARE BRACKET -->
<!ENTITY UnderParenthesis "&#x023DD;" ><!--BOTTOM PARENTHESIS -->
<!ENTITY Union            "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY UnionPlus        "&#x0228E;" ><!--MULTISET UNION -->
<!ENTITY Uogon            "&#x00172;" ><!--LATIN CAPITAL LETTER U WITH OGONEK -->
<!ENTITY Uopf             "&#x1D54C;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL U -->
<!ENTITY UpArrow          "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY UpArrowBar       "&#x02912;" ><!--UPWARDS ARROW TO BAR -->
<!ENTITY UpArrowDownArrow "&#x021C5;" ><!--UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW -->
<!ENTITY UpDownArrow      "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY UpEquilibrium    "&#x0296E;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY UpTee            "&#x022A5;" ><!--UP TACK -->
<!ENTITY UpTeeArrow       "&#x021A5;" ><!--UPWARDS ARROW FROM BAR -->
<!ENTITY Uparrow          "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY Updownarrow      "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY UpperLeftArrow   "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY UpperRightArrow  "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY Upsi             "&#x003D2;" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
<!ENTITY Upsilon          "&#x003A5;" ><!--GREEK CAPITAL LETTER UPSILON -->
<!ENTITY Uring            "&#x0016E;" ><!--LATIN CAPITAL LETTER U WITH RING ABOVE -->
<!ENTITY Uscr             "&#x1D4B0;" ><!--MATHEMATICAL SCRIPT CAPITAL U -->
<!ENTITY Utilde           "&#x00168;" ><!--LATIN CAPITAL LETTER U WITH TILDE -->
<!ENTITY Uuml             "&#x000DC;" ><!--LATIN CAPITAL LETTER U WITH DIAERESIS -->
<!ENTITY VDash            "&#x022AB;" ><!--DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -->
<!ENTITY Vbar             "&#x02AEB;" ><!--DOUBLE UP TACK -->
<!ENTITY Vcy              "&#x00412;" ><!--CYRILLIC CAPITAL LETTER VE -->
<!ENTITY Vdash            "&#x022A9;" ><!--FORCES -->
<!ENTITY Vdashl           "&#x02AE6;" ><!--LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL -->
<!ENTITY Vee              "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY Verbar           "&#x02016;" ><!--DOUBLE VERTICAL LINE -->
<!ENTITY Vert             "&#x02016;" ><!--DOUBLE VERTICAL LINE -->
<!ENTITY VerticalBar      "&#x02223;" ><!--DIVIDES -->
<!ENTITY VerticalLine     "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY VerticalSeparator "&#x02758;" ><!--LIGHT VERTICAL BAR -->
<!ENTITY VerticalTilde    "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY VeryThinSpace    "&#x0200A;" ><!--HAIR SPACE -->
<!ENTITY Vfr              "&#x1D519;" ><!--MATHEMATICAL FRAKTUR CAPITAL V -->
<!ENTITY Vopf             "&#x1D54D;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL V -->
<!ENTITY Vscr             "&#x1D4B1;" ><!--MATHEMATICAL SCRIPT CAPITAL V -->
<!ENTITY Vvdash           "&#x022AA;" ><!--TRIPLE VERTICAL BAR RIGHT TURNSTILE -->
<!ENTITY Wcirc            "&#x00174;" ><!--LATIN CAPITAL LETTER W WITH CIRCUMFLEX -->
<!ENTITY Wedge            "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY Wfr              "&#x1D51A;" ><!--MATHEMATICAL FRAKTUR CAPITAL W -->
<!ENTITY Wopf             "&#x1D54E;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL W -->
<!ENTITY Wscr             "&#x1D4B2;" ><!--MATHEMATICAL SCRIPT CAPITAL W -->
<!ENTITY Xfr              "&#x1D51B;" ><!--MATHEMATICAL FRAKTUR CAPITAL X -->
<!ENTITY Xi               "&#x0039E;" ><!--GREEK CAPITAL LETTER XI -->
<!ENTITY Xopf             "&#x1D54F;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL X -->
<!ENTITY Xscr             "&#x1D4B3;" ><!--MATHEMATICAL SCRIPT CAPITAL X -->
<!ENTITY YAcy             "&#x0042F;" ><!--CYRILLIC CAPITAL LETTER YA -->
<!ENTITY YIcy             "&#x00407;" ><!--CYRILLIC CAPITAL LETTER YI -->
<!ENTITY YUcy             "&#x0042E;" ><!--CYRILLIC CAPITAL LETTER YU -->
<!ENTITY Yacute           "&#x000DD;" ><!--LATIN CAPITAL LETTER Y WITH ACUTE -->
<!ENTITY Ycirc            "&#x00176;" ><!--LATIN CAPITAL LETTER Y WITH CIRCUMFLEX -->
<!ENTITY Ycy              "&#x0042B;" ><!--CYRILLIC CAPITAL LETTER YERU -->
<!ENTITY Yfr              "&#x1D51C;" ><!--MATHEMATICAL FRAKTUR CAPITAL Y -->
<!ENTITY Yopf             "&#x1D550;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL Y -->
<!ENTITY Yscr             "&#x1D4B4;" ><!--MATHEMATICAL SCRIPT CAPITAL Y -->
<!ENTITY Yuml             "&#x00178;" ><!--LATIN CAPITAL LETTER Y WITH DIAERESIS -->
<!ENTITY ZHcy             "&#x00416;" ><!--CYRILLIC CAPITAL LETTER ZHE -->
<!ENTITY Zacute           "&#x00179;" ><!--LATIN CAPITAL LETTER Z WITH ACUTE -->
<!ENTITY Zcaron           "&#x0017D;" ><!--LATIN CAPITAL LETTER Z WITH CARON -->
<!ENTITY Zcy              "&#x00417;" ><!--CYRILLIC CAPITAL LETTER ZE -->
<!ENTITY Zdot             "&#x0017B;" ><!--LATIN CAPITAL LETTER Z WITH DOT ABOVE -->
<!ENTITY ZeroWidthSpace   "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY Zeta             "&#x00396;" ><!--GREEK CAPITAL LETTER ZETA -->
<!ENTITY Zfr              "&#x02128;" ><!--BLACK-LETTER CAPITAL Z -->
<!ENTITY Zopf             "&#x02124;" ><!--DOUBLE-STRUCK CAPITAL Z -->
<!ENTITY Zscr             "&#x1D4B5;" ><!--MATHEMATICAL SCRIPT CAPITAL Z -->
<!ENTITY aacute           "&#x000E1;" ><!--LATIN SMALL LETTER A WITH ACUTE -->
<!ENTITY abreve           "&#x00103;" ><!--LATIN SMALL LETTER A WITH BREVE -->
<!ENTITY ac               "&#x0223E;" ><!--INVERTED LAZY S -->
<!ENTITY acE              "&#x0223E;&#x00333;" ><!--INVERTED LAZY S with double underline -->
<!ENTITY acd              "&#x0223F;" ><!--SINE WAVE -->
<!ENTITY acirc            "&#x000E2;" ><!--LATIN SMALL LETTER A WITH CIRCUMFLEX -->
<!ENTITY acute            "&#x000B4;" ><!--ACUTE ACCENT -->
<!ENTITY acy              "&#x00430;" ><!--CYRILLIC SMALL LETTER A -->
<!ENTITY aelig            "&#x000E6;" ><!--LATIN SMALL LETTER AE -->
<!ENTITY af               "&#x02061;" ><!--FUNCTION APPLICATION -->
<!ENTITY afr              "&#x1D51E;" ><!--MATHEMATICAL FRAKTUR SMALL A -->
<!ENTITY agrave           "&#x000E0;" ><!--LATIN SMALL LETTER A WITH GRAVE -->
<!ENTITY alefsym          "&#x02135;" ><!--ALEF SYMBOL -->
<!ENTITY aleph            "&#x02135;" ><!--ALEF SYMBOL -->
<!ENTITY alpha            "&#x003B1;" ><!--GREEK SMALL LETTER ALPHA -->
<!ENTITY amacr            "&#x00101;" ><!--LATIN SMALL LETTER A WITH MACRON -->
<!ENTITY amalg            "&#x02A3F;" ><!--AMALGAMATION OR COPRODUCT -->
<!ENTITY amp              "&#38;#38;" ><!--AMPERSAND -->
<!ENTITY and              "&#x02227;" ><!--LOGICAL AND -->
<!ENTITY andand           "&#x02A55;" ><!--TWO INTERSECTING LOGICAL AND -->
<!ENTITY andd             "&#x02A5C;" ><!--LOGICAL AND WITH HORIZONTAL DASH -->
<!ENTITY andslope         "&#x02A58;" ><!--SLOPING LARGE AND -->
<!ENTITY andv             "&#x02A5A;" ><!--LOGICAL AND WITH MIDDLE STEM -->
<!ENTITY ang              "&#x02220;" ><!--ANGLE -->
<!ENTITY ange             "&#x029A4;" ><!--ANGLE WITH UNDERBAR -->
<!ENTITY angle            "&#x02220;" ><!--ANGLE -->
<!ENTITY angmsd           "&#x02221;" ><!--MEASURED ANGLE -->
<!ENTITY angmsdaa         "&#x029A8;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT -->
<!ENTITY angmsdab         "&#x029A9;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT -->
<!ENTITY angmsdac         "&#x029AA;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT -->
<!ENTITY angmsdad         "&#x029AB;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT -->
<!ENTITY angmsdae         "&#x029AC;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP -->
<!ENTITY angmsdaf         "&#x029AD;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP -->
<!ENTITY angmsdag         "&#x029AE;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN -->
<!ENTITY angmsdah         "&#x029AF;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN -->
<!ENTITY angrt            "&#x0221F;" ><!--RIGHT ANGLE -->
<!ENTITY angrtvb          "&#x022BE;" ><!--RIGHT ANGLE WITH ARC -->
<!ENTITY angrtvbd         "&#x0299D;" ><!--MEASURED RIGHT ANGLE WITH DOT -->
<!ENTITY angsph           "&#x02222;" ><!--SPHERICAL ANGLE -->
<!ENTITY angst            "&#x000C5;" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
<!ENTITY angzarr          "&#x0237C;" ><!--RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW -->
<!ENTITY aogon            "&#x00105;" ><!--LATIN SMALL LETTER A WITH OGONEK -->
<!ENTITY aopf             "&#x1D552;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL A -->
<!ENTITY ap               "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY apE              "&#x02A70;" ><!--APPROXIMATELY EQUAL OR EQUAL TO -->
<!ENTITY apacir           "&#x02A6F;" ><!--ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT -->
<!ENTITY ape              "&#x0224A;" ><!--ALMOST EQUAL OR EQUAL TO -->
<!ENTITY apid             "&#x0224B;" ><!--TRIPLE TILDE -->
<!ENTITY apos             "&#x00027;" ><!--APOSTROPHE -->
<!ENTITY approx           "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY approxeq         "&#x0224A;" ><!--ALMOST EQUAL OR EQUAL TO -->
<!ENTITY aring            "&#x000E5;" ><!--LATIN SMALL LETTER A WITH RING ABOVE -->
<!ENTITY ascr             "&#x1D4B6;" ><!--MATHEMATICAL SCRIPT SMALL A -->
<!ENTITY ast              "&#x0002A;" ><!--ASTERISK -->
<!ENTITY asymp            "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY asympeq          "&#x0224D;" ><!--EQUIVALENT TO -->
<!ENTITY atilde           "&#x000E3;" ><!--LATIN SMALL LETTER A WITH TILDE -->
<!ENTITY auml             "&#x000E4;" ><!--LATIN SMALL LETTER A WITH DIAERESIS -->
<!ENTITY awconint         "&#x02233;" ><!--ANTICLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY awint            "&#x02A11;" ><!--ANTICLOCKWISE INTEGRATION -->
<!ENTITY bNot             "&#x02AED;" ><!--REVERSED DOUBLE STROKE NOT SIGN -->
<!ENTITY backcong         "&#x0224C;" ><!--ALL EQUAL TO -->
<!ENTITY backepsilon      "&#x003F6;" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
<!ENTITY backprime        "&#x02035;" ><!--REVERSED PRIME -->
<!ENTITY backsim          "&#x0223D;" ><!--REVERSED TILDE -->
<!ENTITY backsimeq        "&#x022CD;" ><!--REVERSED TILDE EQUALS -->
<!ENTITY barvee           "&#x022BD;" ><!--NOR -->
<!ENTITY barwed           "&#x02305;" ><!--PROJECTIVE -->
<!ENTITY barwedge         "&#x02305;" ><!--PROJECTIVE -->
<!ENTITY bbrk             "&#x023B5;" ><!--BOTTOM SQUARE BRACKET -->
<!ENTITY bbrktbrk         "&#x023B6;" ><!--BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET -->
<!ENTITY bcong            "&#x0224C;" ><!--ALL EQUAL TO -->
<!ENTITY bcy              "&#x00431;" ><!--CYRILLIC SMALL LETTER BE -->
<!ENTITY bdquo            "&#x0201E;" ><!--DOUBLE LOW-9 QUOTATION MARK -->
<!ENTITY becaus           "&#x02235;" ><!--BECAUSE -->
<!ENTITY because          "&#x02235;" ><!--BECAUSE -->
<!ENTITY bemptyv          "&#x029B0;" ><!--REVERSED EMPTY SET -->
<!ENTITY bepsi            "&#x003F6;" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
<!ENTITY bernou           "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY beta             "&#x003B2;" ><!--GREEK SMALL LETTER BETA -->
<!ENTITY beth             "&#x02136;" ><!--BET SYMBOL -->
<!ENTITY between          "&#x0226C;" ><!--BETWEEN -->
<!ENTITY bfr              "&#x1D51F;" ><!--MATHEMATICAL FRAKTUR SMALL B -->
<!ENTITY bigcap           "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY bigcirc          "&#x025EF;" ><!--LARGE CIRCLE -->
<!ENTITY bigcup           "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY bigodot          "&#x02A00;" ><!--N-ARY CIRCLED DOT OPERATOR -->
<!ENTITY bigoplus         "&#x02A01;" ><!--N-ARY CIRCLED PLUS OPERATOR -->
<!ENTITY bigotimes        "&#x02A02;" ><!--N-ARY CIRCLED TIMES OPERATOR -->
<!ENTITY bigsqcup         "&#x02A06;" ><!--N-ARY SQUARE UNION OPERATOR -->
<!ENTITY bigstar          "&#x02605;" ><!--BLACK STAR -->
<!ENTITY bigtriangledown  "&#x025BD;" ><!--WHITE DOWN-POINTING TRIANGLE -->
<!ENTITY bigtriangleup    "&#x025B3;" ><!--WHITE UP-POINTING TRIANGLE -->
<!ENTITY biguplus         "&#x02A04;" ><!--N-ARY UNION OPERATOR WITH PLUS -->
<!ENTITY bigvee           "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY bigwedge         "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY bkarow           "&#x0290D;" ><!--RIGHTWARDS DOUBLE DASH ARROW -->
<!ENTITY blacklozenge     "&#x029EB;" ><!--BLACK LOZENGE -->
<!ENTITY blacksquare      "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY blacktriangle    "&#x025B4;" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangledown "&#x025BE;" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangleleft "&#x025C2;" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangleright "&#x025B8;" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY blank            "&#x02423;" ><!--OPEN BOX -->
<!ENTITY blk12            "&#x02592;" ><!--MEDIUM SHADE -->
<!ENTITY blk14            "&#x02591;" ><!--LIGHT SHADE -->
<!ENTITY blk34            "&#x02593;" ><!--DARK SHADE -->
<!ENTITY block            "&#x02588;" ><!--FULL BLOCK -->
<!ENTITY bne              "&#x0003D;&#x020E5;" ><!--EQUALS SIGN with reverse slash -->
<!ENTITY bnequiv          "&#x02261;&#x020E5;" ><!--IDENTICAL TO with reverse slash -->
<!ENTITY bnot             "&#x02310;" ><!--REVERSED NOT SIGN -->
<!ENTITY bopf             "&#x1D553;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL B -->
<!ENTITY bot              "&#x022A5;" ><!--UP TACK -->
<!ENTITY bottom           "&#x022A5;" ><!--UP TACK -->
<!ENTITY bowtie           "&#x022C8;" ><!--BOWTIE -->
<!ENTITY boxDL            "&#x02557;" ><!--BOX DRAWINGS DOUBLE DOWN AND LEFT -->
<!ENTITY boxDR            "&#x02554;" ><!--BOX DRAWINGS DOUBLE DOWN AND RIGHT -->
<!ENTITY boxDl            "&#x02556;" ><!--BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE -->
<!ENTITY boxDr            "&#x02553;" ><!--BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxH             "&#x02550;" ><!--BOX DRAWINGS DOUBLE HORIZONTAL -->
<!ENTITY boxHD            "&#x02566;" ><!--BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL -->
<!ENTITY boxHU            "&#x02569;" ><!--BOX DRAWINGS DOUBLE UP AND HORIZONTAL -->
<!ENTITY boxHd            "&#x02564;" ><!--BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxHu            "&#x02567;" ><!--BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxUL            "&#x0255D;" ><!--BOX DRAWINGS DOUBLE UP AND LEFT -->
<!ENTITY boxUR            "&#x0255A;" ><!--BOX DRAWINGS DOUBLE UP AND RIGHT -->
<!ENTITY boxUl            "&#x0255C;" ><!--BOX DRAWINGS UP DOUBLE AND LEFT SINGLE -->
<!ENTITY boxUr            "&#x02559;" ><!--BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxV             "&#x02551;" ><!--BOX DRAWINGS DOUBLE VERTICAL -->
<!ENTITY boxVH            "&#x0256C;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL -->
<!ENTITY boxVL            "&#x02563;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND LEFT -->
<!ENTITY boxVR            "&#x02560;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND RIGHT -->
<!ENTITY boxVh            "&#x0256B;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxVl            "&#x02562;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE -->
<!ENTITY boxVr            "&#x0255F;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxbox           "&#x029C9;" ><!--TWO JOINED SQUARES -->
<!ENTITY boxdL            "&#x02555;" ><!--BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE -->
<!ENTITY boxdR            "&#x02552;" ><!--BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxdl            "&#x02510;" ><!--BOX DRAWINGS LIGHT DOWN AND LEFT -->
<!ENTITY boxdr            "&#x0250C;" ><!--BOX DRAWINGS LIGHT DOWN AND RIGHT -->
<!ENTITY boxh             "&#x02500;" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
<!ENTITY boxhD            "&#x02565;" ><!--BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxhU            "&#x02568;" ><!--BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxhd            "&#x0252C;" ><!--BOX DRAWINGS LIGHT DOWN AND HORIZONTAL -->
<!ENTITY boxhu            "&#x02534;" ><!--BOX DRAWINGS LIGHT UP AND HORIZONTAL -->
<!ENTITY boxminus         "&#x0229F;" ><!--SQUARED MINUS -->
<!ENTITY boxplus          "&#x0229E;" ><!--SQUARED PLUS -->
<!ENTITY boxtimes         "&#x022A0;" ><!--SQUARED TIMES -->
<!ENTITY boxuL            "&#x0255B;" ><!--BOX DRAWINGS UP SINGLE AND LEFT DOUBLE -->
<!ENTITY boxuR            "&#x02558;" ><!--BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxul            "&#x02518;" ><!--BOX DRAWINGS LIGHT UP AND LEFT -->
<!ENTITY boxur            "&#x02514;" ><!--BOX DRAWINGS LIGHT UP AND RIGHT -->
<!ENTITY boxv             "&#x02502;" ><!--BOX DRAWINGS LIGHT VERTICAL -->
<!ENTITY boxvH            "&#x0256A;" ><!--BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxvL            "&#x02561;" ><!--BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE -->
<!ENTITY boxvR            "&#x0255E;" ><!--BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxvh            "&#x0253C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL -->
<!ENTITY boxvl            "&#x02524;" ><!--BOX DRAWINGS LIGHT VERTICAL AND LEFT -->
<!ENTITY boxvr            "&#x0251C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND RIGHT -->
<!ENTITY bprime           "&#x02035;" ><!--REVERSED PRIME -->
<!ENTITY breve            "&#x002D8;" ><!--BREVE -->
<!ENTITY brvbar           "&#x000A6;" ><!--BROKEN BAR -->
<!ENTITY bscr             "&#x1D4B7;" ><!--MATHEMATICAL SCRIPT SMALL B -->
<!ENTITY bsemi            "&#x0204F;" ><!--REVERSED SEMICOLON -->
<!ENTITY bsim             "&#x0223D;" ><!--REVERSED TILDE -->
<!ENTITY bsime            "&#x022CD;" ><!--REVERSED TILDE EQUALS -->
<!ENTITY bsol             "&#x0005C;" ><!--REVERSE SOLIDUS -->
<!ENTITY bsolb            "&#x029C5;" ><!--SQUARED FALLING DIAGONAL SLASH -->
<!ENTITY bsolhsub         "&#x027C8;" ><!--REVERSE SOLIDUS PRECEDING SUBSET -->
<!ENTITY bull             "&#x02022;" ><!--BULLET -->
<!ENTITY bullet           "&#x02022;" ><!--BULLET -->
<!ENTITY bump             "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY bumpE            "&#x02AAE;" ><!--EQUALS SIGN WITH BUMPY ABOVE -->
<!ENTITY bumpe            "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY bumpeq           "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY cacute           "&#x00107;" ><!--LATIN SMALL LETTER C WITH ACUTE -->
<!ENTITY cap              "&#x02229;" ><!--INTERSECTION -->
<!ENTITY capand           "&#x02A44;" ><!--INTERSECTION WITH LOGICAL AND -->
<!ENTITY capbrcup         "&#x02A49;" ><!--INTERSECTION ABOVE BAR ABOVE UNION -->
<!ENTITY capcap           "&#x02A4B;" ><!--INTERSECTION BESIDE AND JOINED WITH INTERSECTION -->
<!ENTITY capcup           "&#x02A47;" ><!--INTERSECTION ABOVE UNION -->
<!ENTITY capdot           "&#x02A40;" ><!--INTERSECTION WITH DOT -->
<!ENTITY caps             "&#x02229;&#x0FE00;" ><!--INTERSECTION with serifs -->
<!ENTITY caret            "&#x02041;" ><!--CARET INSERTION POINT -->
<!ENTITY caron            "&#x002C7;" ><!--CARON -->
<!ENTITY ccaps            "&#x02A4D;" ><!--CLOSED INTERSECTION WITH SERIFS -->
<!ENTITY ccaron           "&#x0010D;" ><!--LATIN SMALL LETTER C WITH CARON -->
<!ENTITY ccedil           "&#x000E7;" ><!--LATIN SMALL LETTER C WITH CEDILLA -->
<!ENTITY ccirc            "&#x00109;" ><!--LATIN SMALL LETTER C WITH CIRCUMFLEX -->
<!ENTITY ccups            "&#x02A4C;" ><!--CLOSED UNION WITH SERIFS -->
<!ENTITY ccupssm          "&#x02A50;" ><!--CLOSED UNION WITH SERIFS AND SMASH PRODUCT -->
<!ENTITY cdot             "&#x0010B;" ><!--LATIN SMALL LETTER C WITH DOT ABOVE -->
<!ENTITY cedil            "&#x000B8;" ><!--CEDILLA -->
<!ENTITY cemptyv          "&#x029B2;" ><!--EMPTY SET WITH SMALL CIRCLE ABOVE -->
<!ENTITY cent             "&#x000A2;" ><!--CENT SIGN -->
<!ENTITY centerdot        "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY cfr              "&#x1D520;" ><!--MATHEMATICAL FRAKTUR SMALL C -->
<!ENTITY chcy             "&#x00447;" ><!--CYRILLIC SMALL LETTER CHE -->
<!ENTITY check            "&#x02713;" ><!--CHECK MARK -->
<!ENTITY checkmark        "&#x02713;" ><!--CHECK MARK -->
<!ENTITY chi              "&#x003C7;" ><!--GREEK SMALL LETTER CHI -->
<!ENTITY cir              "&#x025CB;" ><!--WHITE CIRCLE -->
<!ENTITY cirE             "&#x029C3;" ><!--CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT -->
<!ENTITY circ             "&#x002C6;" ><!--MODIFIER LETTER CIRCUMFLEX ACCENT -->
<!ENTITY circeq           "&#x02257;" ><!--RING EQUAL TO -->
<!ENTITY circlearrowleft  "&#x021BA;" ><!--ANTICLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY circlearrowright "&#x021BB;" ><!--CLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY circledR         "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY circledS         "&#x024C8;" ><!--CIRCLED LATIN CAPITAL LETTER S -->
<!ENTITY circledast       "&#x0229B;" ><!--CIRCLED ASTERISK OPERATOR -->
<!ENTITY circledcirc      "&#x0229A;" ><!--CIRCLED RING OPERATOR -->
<!ENTITY circleddash      "&#x0229D;" ><!--CIRCLED DASH -->
<!ENTITY cire             "&#x02257;" ><!--RING EQUAL TO -->
<!ENTITY cirfnint         "&#x02A10;" ><!--CIRCULATION FUNCTION -->
<!ENTITY cirmid           "&#x02AEF;" ><!--VERTICAL LINE WITH CIRCLE ABOVE -->
<!ENTITY cirscir          "&#x029C2;" ><!--CIRCLE WITH SMALL CIRCLE TO THE RIGHT -->
<!ENTITY clubs            "&#x02663;" ><!--BLACK CLUB SUIT -->
<!ENTITY clubsuit         "&#x02663;" ><!--BLACK CLUB SUIT -->
<!ENTITY colon            "&#x0003A;" ><!--COLON -->
<!ENTITY colone           "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY coloneq          "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY comma            "&#x0002C;" ><!--COMMA -->
<!ENTITY commat           "&#x00040;" ><!--COMMERCIAL AT -->
<!ENTITY comp             "&#x02201;" ><!--COMPLEMENT -->
<!ENTITY compfn           "&#x02218;" ><!--RING OPERATOR -->
<!ENTITY complement       "&#x02201;" ><!--COMPLEMENT -->
<!ENTITY complexes        "&#x02102;" ><!--DOUBLE-STRUCK CAPITAL C -->
<!ENTITY cong             "&#x02245;" ><!--APPROXIMATELY EQUAL TO -->
<!ENTITY congdot          "&#x02A6D;" ><!--CONGRUENT WITH DOT ABOVE -->
<!ENTITY conint           "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY copf             "&#x1D554;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL C -->
<!ENTITY coprod           "&#x02210;" ><!--N-ARY COPRODUCT -->
<!ENTITY copy             "&#x000A9;" ><!--COPYRIGHT SIGN -->
<!ENTITY copysr           "&#x02117;" ><!--SOUND RECORDING COPYRIGHT -->
<!ENTITY crarr            "&#x021B5;" ><!--DOWNWARDS ARROW WITH CORNER LEFTWARDS -->
<!ENTITY cross            "&#x02717;" ><!--BALLOT X -->
<!ENTITY cscr             "&#x1D4B8;" ><!--MATHEMATICAL SCRIPT SMALL C -->
<!ENTITY csub             "&#x02ACF;" ><!--CLOSED SUBSET -->
<!ENTITY csube            "&#x02AD1;" ><!--CLOSED SUBSET OR EQUAL TO -->
<!ENTITY csup             "&#x02AD0;" ><!--CLOSED SUPERSET -->
<!ENTITY csupe            "&#x02AD2;" ><!--CLOSED SUPERSET OR EQUAL TO -->
<!ENTITY ctdot            "&#x022EF;" ><!--MIDLINE HORIZONTAL ELLIPSIS -->
<!ENTITY cudarrl          "&#x02938;" ><!--RIGHT-SIDE ARC CLOCKWISE ARROW -->
<!ENTITY cudarrr          "&#x02935;" ><!--ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS -->
<!ENTITY cuepr            "&#x022DE;" ><!--EQUAL TO OR PRECEDES -->
<!ENTITY cuesc            "&#x022DF;" ><!--EQUAL TO OR SUCCEEDS -->
<!ENTITY cularr           "&#x021B6;" ><!--ANTICLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY cularrp          "&#x0293D;" ><!--TOP ARC ANTICLOCKWISE ARROW WITH PLUS -->
<!ENTITY cup              "&#x0222A;" ><!--UNION -->
<!ENTITY cupbrcap         "&#x02A48;" ><!--UNION ABOVE BAR ABOVE INTERSECTION -->
<!ENTITY cupcap           "&#x02A46;" ><!--UNION ABOVE INTERSECTION -->
<!ENTITY cupcup           "&#x02A4A;" ><!--UNION BESIDE AND JOINED WITH UNION -->
<!ENTITY cupdot           "&#x0228D;" ><!--MULTISET MULTIPLICATION -->
<!ENTITY cupor            "&#x02A45;" ><!--UNION WITH LOGICAL OR -->
<!ENTITY cups             "&#x0222A;&#x0FE00;" ><!--UNION with serifs -->
<!ENTITY curarr           "&#x021B7;" ><!--CLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY curarrm          "&#x0293C;" ><!--TOP ARC CLOCKWISE ARROW WITH MINUS -->
<!ENTITY curlyeqprec      "&#x022DE;" ><!--EQUAL TO OR PRECEDES -->
<!ENTITY curlyeqsucc      "&#x022DF;" ><!--EQUAL TO OR SUCCEEDS -->
<!ENTITY curlyvee         "&#x022CE;" ><!--CURLY LOGICAL OR -->
<!ENTITY curlywedge       "&#x022CF;" ><!--CURLY LOGICAL AND -->
<!ENTITY curren           "&#x000A4;" ><!--CURRENCY SIGN -->
<!ENTITY curvearrowleft   "&#x021B6;" ><!--ANTICLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY curvearrowright  "&#x021B7;" ><!--CLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY cuvee            "&#x022CE;" ><!--CURLY LOGICAL OR -->
<!ENTITY cuwed            "&#x022CF;" ><!--CURLY LOGICAL AND -->
<!ENTITY cwconint         "&#x02232;" ><!--CLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY cwint            "&#x02231;" ><!--CLOCKWISE INTEGRAL -->
<!ENTITY cylcty           "&#x0232D;" ><!--CYLINDRICITY -->
<!ENTITY dArr             "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY dHar             "&#x02965;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY dagger           "&#x02020;" ><!--DAGGER -->
<!ENTITY daleth           "&#x02138;" ><!--DALET SYMBOL -->
<!ENTITY darr             "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY dash             "&#x02010;" ><!--HYPHEN -->
<!ENTITY dashv            "&#x022A3;" ><!--LEFT TACK -->
<!ENTITY dbkarow          "&#x0290F;" ><!--RIGHTWARDS TRIPLE DASH ARROW -->
<!ENTITY dblac            "&#x002DD;" ><!--DOUBLE ACUTE ACCENT -->
<!ENTITY dcaron           "&#x0010F;" ><!--LATIN SMALL LETTER D WITH CARON -->
<!ENTITY dcy              "&#x00434;" ><!--CYRILLIC SMALL LETTER DE -->
<!ENTITY dd               "&#x02146;" ><!--DOUBLE-STRUCK ITALIC SMALL D -->
<!ENTITY ddagger          "&#x02021;" ><!--DOUBLE DAGGER -->
<!ENTITY ddarr            "&#x021CA;" ><!--DOWNWARDS PAIRED ARROWS -->
<!ENTITY ddotseq          "&#x02A77;" ><!--EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW -->
<!ENTITY deg              "&#x000B0;" ><!--DEGREE SIGN -->
<!ENTITY delta            "&#x003B4;" ><!--GREEK SMALL LETTER DELTA -->
<!ENTITY demptyv          "&#x029B1;" ><!--EMPTY SET WITH OVERBAR -->
<!ENTITY dfisht           "&#x0297F;" ><!--DOWN FISH TAIL -->
<!ENTITY dfr              "&#x1D521;" ><!--MATHEMATICAL FRAKTUR SMALL D -->
<!ENTITY dharl            "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY dharr            "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY diam             "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY diamond          "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY diamondsuit      "&#x02666;" ><!--BLACK DIAMOND SUIT -->
<!ENTITY diams            "&#x02666;" ><!--BLACK DIAMOND SUIT -->
<!ENTITY die              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY digamma          "&#x003DD;" ><!--GREEK SMALL LETTER DIGAMMA -->
<!ENTITY disin            "&#x022F2;" ><!--ELEMENT OF WITH LONG HORIZONTAL STROKE -->
<!ENTITY div              "&#x000F7;" ><!--DIVISION SIGN -->
<!ENTITY divide           "&#x000F7;" ><!--DIVISION SIGN -->
<!ENTITY divideontimes    "&#x022C7;" ><!--DIVISION TIMES -->
<!ENTITY divonx           "&#x022C7;" ><!--DIVISION TIMES -->
<!ENTITY djcy             "&#x00452;" ><!--CYRILLIC SMALL LETTER DJE -->
<!ENTITY dlcorn           "&#x0231E;" ><!--BOTTOM LEFT CORNER -->
<!ENTITY dlcrop           "&#x0230D;" ><!--BOTTOM LEFT CROP -->
<!ENTITY dollar           "&#x00024;" ><!--DOLLAR SIGN -->
<!ENTITY dopf             "&#x1D555;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL D -->
<!ENTITY dot              "&#x002D9;" ><!--DOT ABOVE -->
<!ENTITY doteq            "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY doteqdot         "&#x02251;" ><!--GEOMETRICALLY EQUAL TO -->
<!ENTITY dotminus         "&#x02238;" ><!--DOT MINUS -->
<!ENTITY dotplus          "&#x02214;" ><!--DOT PLUS -->
<!ENTITY dotsquare        "&#x022A1;" ><!--SQUARED DOT OPERATOR -->
<!ENTITY doublebarwedge   "&#x02306;" ><!--PERSPECTIVE -->
<!ENTITY downarrow        "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY downdownarrows   "&#x021CA;" ><!--DOWNWARDS PAIRED ARROWS -->
<!ENTITY downharpoonleft  "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY downharpoonright "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY drbkarow         "&#x02910;" ><!--RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW -->
<!ENTITY drcorn           "&#x0231F;" ><!--BOTTOM RIGHT CORNER -->
<!ENTITY drcrop           "&#x0230C;" ><!--BOTTOM RIGHT CROP -->
<!ENTITY dscr             "&#x1D4B9;" ><!--MATHEMATICAL SCRIPT SMALL D -->
<!ENTITY dscy             "&#x00455;" ><!--CYRILLIC SMALL LETTER DZE -->
<!ENTITY dsol             "&#x029F6;" ><!--SOLIDUS WITH OVERBAR -->
<!ENTITY dstrok           "&#x00111;" ><!--LATIN SMALL LETTER D WITH STROKE -->
<!ENTITY dtdot            "&#x022F1;" ><!--DOWN RIGHT DIAGONAL ELLIPSIS -->
<!ENTITY dtri             "&#x025BF;" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY dtrif            "&#x025BE;" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY duarr            "&#x021F5;" ><!--DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW -->
<!ENTITY duhar            "&#x0296F;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY dwangle          "&#x029A6;" ><!--OBLIQUE ANGLE OPENING UP -->
<!ENTITY dzcy             "&#x0045F;" ><!--CYRILLIC SMALL LETTER DZHE -->
<!ENTITY dzigrarr         "&#x027FF;" ><!--LONG RIGHTWARDS SQUIGGLE ARROW -->
<!ENTITY eDDot            "&#x02A77;" ><!--EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW -->
<!ENTITY eDot             "&#x02251;" ><!--GEOMETRICALLY EQUAL TO -->
<!ENTITY eacute           "&#x000E9;" ><!--LATIN SMALL LETTER E WITH ACUTE -->
<!ENTITY easter           "&#x02A6E;" ><!--EQUALS WITH ASTERISK -->
<!ENTITY ecaron           "&#x0011B;" ><!--LATIN SMALL LETTER E WITH CARON -->
<!ENTITY ecir             "&#x02256;" ><!--RING IN EQUAL TO -->
<!ENTITY ecirc            "&#x000EA;" ><!--LATIN SMALL LETTER E WITH CIRCUMFLEX -->
<!ENTITY ecolon           "&#x02255;" ><!--EQUALS COLON -->
<!ENTITY ecy              "&#x0044D;" ><!--CYRILLIC SMALL LETTER E -->
<!ENTITY edot             "&#x00117;" ><!--LATIN SMALL LETTER E WITH DOT ABOVE -->
<!ENTITY ee               "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY efDot            "&#x02252;" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
<!ENTITY efr              "&#x1D522;" ><!--MATHEMATICAL FRAKTUR SMALL E -->
<!ENTITY eg               "&#x02A9A;" ><!--DOUBLE-LINE EQUAL TO OR GREATER-THAN -->
<!ENTITY egrave           "&#x000E8;" ><!--LATIN SMALL LETTER E WITH GRAVE -->
<!ENTITY egs              "&#x02A96;" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
<!ENTITY egsdot           "&#x02A98;" ><!--SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE -->
<!ENTITY el               "&#x02A99;" ><!--DOUBLE-LINE EQUAL TO OR LESS-THAN -->
<!ENTITY elinters         "&#x023E7;" ><!--ELECTRICAL INTERSECTION -->
<!ENTITY ell              "&#x02113;" ><!--SCRIPT SMALL L -->
<!ENTITY els              "&#x02A95;" ><!--SLANTED EQUAL TO OR LESS-THAN -->
<!ENTITY elsdot           "&#x02A97;" ><!--SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE -->
<!ENTITY emacr            "&#x00113;" ><!--LATIN SMALL LETTER E WITH MACRON -->
<!ENTITY empty            "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emptyset         "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emptyv           "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emsp             "&#x02003;" ><!--EM SPACE -->
<!ENTITY emsp13           "&#x02004;" ><!--THREE-PER-EM SPACE -->
<!ENTITY emsp14           "&#x02005;" ><!--FOUR-PER-EM SPACE -->
<!ENTITY eng              "&#x0014B;" ><!--LATIN SMALL LETTER ENG -->
<!ENTITY ensp             "&#x02002;" ><!--EN SPACE -->
<!ENTITY eogon            "&#x00119;" ><!--LATIN SMALL LETTER E WITH OGONEK -->
<!ENTITY eopf             "&#x1D556;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL E -->
<!ENTITY epar             "&#x022D5;" ><!--EQUAL AND PARALLEL TO -->
<!ENTITY eparsl           "&#x029E3;" ><!--EQUALS SIGN AND SLANTED PARALLEL -->
<!ENTITY eplus            "&#x02A71;" ><!--EQUALS SIGN ABOVE PLUS SIGN -->
<!ENTITY epsi             "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
<!ENTITY epsilon          "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
<!ENTITY epsiv            "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY eqcirc           "&#x02256;" ><!--RING IN EQUAL TO -->
<!ENTITY eqcolon          "&#x02255;" ><!--EQUALS COLON -->
<!ENTITY eqsim            "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY eqslantgtr       "&#x02A96;" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
<!ENTITY eqslantless      "&#x02A95;" ><!--SLANTED EQUAL TO OR LESS-THAN -->
<!ENTITY equals           "&#x0003D;" ><!--EQUALS SIGN -->
<!ENTITY equest           "&#x0225F;" ><!--QUESTIONED EQUAL TO -->
<!ENTITY equiv            "&#x02261;" ><!--IDENTICAL TO -->
<!ENTITY equivDD          "&#x02A78;" ><!--EQUIVALENT WITH FOUR DOTS ABOVE -->
<!ENTITY eqvparsl         "&#x029E5;" ><!--IDENTICAL TO AND SLANTED PARALLEL -->
<!ENTITY erDot            "&#x02253;" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
<!ENTITY erarr            "&#x02971;" ><!--EQUALS SIGN ABOVE RIGHTWARDS ARROW -->
<!ENTITY escr             "&#x0212F;" ><!--SCRIPT SMALL E -->
<!ENTITY esdot            "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY esim             "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY eta              "&#x003B7;" ><!--GREEK SMALL LETTER ETA -->
<!ENTITY eth              "&#x000F0;" ><!--LATIN SMALL LETTER ETH -->
<!ENTITY euml             "&#x000EB;" ><!--LATIN SMALL LETTER E WITH DIAERESIS -->
<!ENTITY euro             "&#x020AC;" ><!--EURO SIGN -->
<!ENTITY excl             "&#x00021;" ><!--EXCLAMATION MARK -->
<!ENTITY exist            "&#x02203;" ><!--THERE EXISTS -->
<!ENTITY expectation      "&#x02130;" ><!--SCRIPT CAPITAL E -->
<!ENTITY exponentiale     "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY fallingdotseq    "&#x02252;" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
<!ENTITY fcy              "&#x00444;" ><!--CYRILLIC SMALL LETTER EF -->
<!ENTITY female           "&#x02640;" ><!--FEMALE SIGN -->
<!ENTITY ffilig           "&#x0FB03;" ><!--LATIN SMALL LIGATURE FFI -->
<!ENTITY fflig            "&#x0FB00;" ><!--LATIN SMALL LIGATURE FF -->
<!ENTITY ffllig           "&#x0FB04;" ><!--LATIN SMALL LIGATURE FFL -->
<!ENTITY ffr              "&#x1D523;" ><!--MATHEMATICAL FRAKTUR SMALL F -->
<!ENTITY filig            "&#x0FB01;" ><!--LATIN SMALL LIGATURE FI -->
<!ENTITY fjlig            "&#x00066;&#x0006A;" ><!--fj ligature -->
<!ENTITY flat             "&#x0266D;" ><!--MUSIC FLAT SIGN -->
<!ENTITY fllig            "&#x0FB02;" ><!--LATIN SMALL LIGATURE FL -->
<!ENTITY fltns            "&#x025B1;" ><!--WHITE PARALLELOGRAM -->
<!ENTITY fnof             "&#x00192;" ><!--LATIN SMALL LETTER F WITH HOOK -->
<!ENTITY fopf             "&#x1D557;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL F -->
<!ENTITY forall           "&#x02200;" ><!--FOR ALL -->
<!ENTITY fork             "&#x022D4;" ><!--PITCHFORK -->
<!ENTITY forkv            "&#x02AD9;" ><!--ELEMENT OF OPENING DOWNWARDS -->
<!ENTITY fpartint         "&#x02A0D;" ><!--FINITE PART INTEGRAL -->
<!ENTITY frac12           "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
<!ENTITY frac13           "&#x02153;" ><!--VULGAR FRACTION ONE THIRD -->
<!ENTITY frac14           "&#x000BC;" ><!--VULGAR FRACTION ONE QUARTER -->
<!ENTITY frac15           "&#x02155;" ><!--VULGAR FRACTION ONE FIFTH -->
<!ENTITY frac16           "&#x02159;" ><!--VULGAR FRACTION ONE SIXTH -->
<!ENTITY frac18           "&#x0215B;" ><!--VULGAR FRACTION ONE EIGHTH -->
<!ENTITY frac23           "&#x02154;" ><!--VULGAR FRACTION TWO THIRDS -->
<!ENTITY frac25           "&#x02156;" ><!--VULGAR FRACTION TWO FIFTHS -->
<!ENTITY frac34           "&#x000BE;" ><!--VULGAR FRACTION THREE QUARTERS -->
<!ENTITY frac35           "&#x02157;" ><!--VULGAR FRACTION THREE FIFTHS -->
<!ENTITY frac38           "&#x0215C;" ><!--VULGAR FRACTION THREE EIGHTHS -->
<!ENTITY frac45           "&#x02158;" ><!--VULGAR FRACTION FOUR FIFTHS -->
<!ENTITY frac56           "&#x0215A;" ><!--VULGAR FRACTION FIVE SIXTHS -->
<!ENTITY frac58           "&#x0215D;" ><!--VULGAR FRACTION FIVE EIGHTHS -->
<!ENTITY frac78           "&#x0215E;" ><!--VULGAR FRACTION SEVEN EIGHTHS -->
<!ENTITY frasl            "&#x02044;" ><!--FRACTION SLASH -->
<!ENTITY frown            "&#x02322;" ><!--FROWN -->
<!ENTITY fscr             "&#x1D4BB;" ><!--MATHEMATICAL SCRIPT SMALL F -->
<!ENTITY gE               "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY gEl              "&#x02A8C;" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
<!ENTITY gacute           "&#x001F5;" ><!--LATIN SMALL LETTER G WITH ACUTE -->
<!ENTITY gamma            "&#x003B3;" ><!--GREEK SMALL LETTER GAMMA -->
<!ENTITY gammad           "&#x003DD;" ><!--GREEK SMALL LETTER DIGAMMA -->
<!ENTITY gap              "&#x02A86;" ><!--GREATER-THAN OR APPROXIMATE -->
<!ENTITY gbreve           "&#x0011F;" ><!--LATIN SMALL LETTER G WITH BREVE -->
<!ENTITY gcirc            "&#x0011D;" ><!--LATIN SMALL LETTER G WITH CIRCUMFLEX -->
<!ENTITY gcy              "&#x00433;" ><!--CYRILLIC SMALL LETTER GHE -->
<!ENTITY gdot             "&#x00121;" ><!--LATIN SMALL LETTER G WITH DOT ABOVE -->
<!ENTITY ge               "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY gel              "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY geq              "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY geqq             "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY geqslant         "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY ges              "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY gescc            "&#x02AA9;" ><!--GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL -->
<!ENTITY gesdot           "&#x02A80;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE -->
<!ENTITY gesdoto          "&#x02A82;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE -->
<!ENTITY gesdotol         "&#x02A84;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT -->
<!ENTITY gesl             "&#x022DB;&#x0FE00;" ><!--GREATER-THAN slanted EQUAL TO OR LESS-THAN -->
<!ENTITY gesles           "&#x02A94;" ><!--GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL -->
<!ENTITY gfr              "&#x1D524;" ><!--MATHEMATICAL FRAKTUR SMALL G -->
<!ENTITY gg               "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY ggg              "&#x022D9;" ><!--VERY MUCH GREATER-THAN -->
<!ENTITY gimel            "&#x02137;" ><!--GIMEL SYMBOL -->
<!ENTITY gjcy             "&#x00453;" ><!--CYRILLIC SMALL LETTER GJE -->
<!ENTITY gl               "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY glE              "&#x02A92;" ><!--GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL -->
<!ENTITY gla              "&#x02AA5;" ><!--GREATER-THAN BESIDE LESS-THAN -->
<!ENTITY glj              "&#x02AA4;" ><!--GREATER-THAN OVERLAPPING LESS-THAN -->
<!ENTITY gnE              "&#x02269;" ><!--GREATER-THAN BUT NOT EQUAL TO -->
<!ENTITY gnap             "&#x02A8A;" ><!--GREATER-THAN AND NOT APPROXIMATE -->
<!ENTITY gnapprox         "&#x02A8A;" ><!--GREATER-THAN AND NOT APPROXIMATE -->
<!ENTITY gne              "&#x02A88;" ><!--GREATER-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY gneq             "&#x02A88;" ><!--GREATER-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY gneqq            "&#x02269;" ><!--GREATER-THAN BUT NOT EQUAL TO -->
<!ENTITY gnsim            "&#x022E7;" ><!--GREATER-THAN BUT NOT EQUIVALENT TO -->
<!ENTITY gopf             "&#x1D558;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL G -->
<!ENTITY grave            "&#x00060;" ><!--GRAVE ACCENT -->
<!ENTITY gscr             "&#x0210A;" ><!--SCRIPT SMALL G -->
<!ENTITY gsim             "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY gsime            "&#x02A8E;" ><!--GREATER-THAN ABOVE SIMILAR OR EQUAL -->
<!ENTITY gsiml            "&#x02A90;" ><!--GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN -->
<!ENTITY gt               "&#x0003E;" ><!--GREATER-THAN SIGN -->
<!ENTITY gtcc             "&#x02AA7;" ><!--GREATER-THAN CLOSED BY CURVE -->
<!ENTITY gtcir            "&#x02A7A;" ><!--GREATER-THAN WITH CIRCLE INSIDE -->
<!ENTITY gtdot            "&#x022D7;" ><!--GREATER-THAN WITH DOT -->
<!ENTITY gtlPar           "&#x02995;" ><!--DOUBLE LEFT ARC GREATER-THAN BRACKET -->
<!ENTITY gtquest          "&#x02A7C;" ><!--GREATER-THAN WITH QUESTION MARK ABOVE -->
<!ENTITY gtrapprox        "&#x02A86;" ><!--GREATER-THAN OR APPROXIMATE -->
<!ENTITY gtrarr           "&#x02978;" ><!--GREATER-THAN ABOVE RIGHTWARDS ARROW -->
<!ENTITY gtrdot           "&#x022D7;" ><!--GREATER-THAN WITH DOT -->
<!ENTITY gtreqless        "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY gtreqqless       "&#x02A8C;" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
<!ENTITY gtrless          "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY gtrsim           "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY gvertneqq        "&#x02269;&#x0FE00;" ><!--GREATER-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY gvnE             "&#x02269;&#x0FE00;" ><!--GREATER-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY hArr             "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY hairsp           "&#x0200A;" ><!--HAIR SPACE -->
<!ENTITY half             "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
<!ENTITY hamilt           "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY hardcy           "&#x0044A;" ><!--CYRILLIC SMALL LETTER HARD SIGN -->
<!ENTITY harr             "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY harrcir          "&#x02948;" ><!--LEFT RIGHT ARROW THROUGH SMALL CIRCLE -->
<!ENTITY harrw            "&#x021AD;" ><!--LEFT RIGHT WAVE ARROW -->
<!ENTITY hbar             "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY hcirc            "&#x00125;" ><!--LATIN SMALL LETTER H WITH CIRCUMFLEX -->
<!ENTITY hearts           "&#x02665;" ><!--BLACK HEART SUIT -->
<!ENTITY heartsuit        "&#x02665;" ><!--BLACK HEART SUIT -->
<!ENTITY hellip           "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
<!ENTITY hercon           "&#x022B9;" ><!--HERMITIAN CONJUGATE MATRIX -->
<!ENTITY hfr              "&#x1D525;" ><!--MATHEMATICAL FRAKTUR SMALL H -->
<!ENTITY hksearow         "&#x02925;" ><!--SOUTH EAST ARROW WITH HOOK -->
<!ENTITY hkswarow         "&#x02926;" ><!--SOUTH WEST ARROW WITH HOOK -->
<!ENTITY hoarr            "&#x021FF;" ><!--LEFT RIGHT OPEN-HEADED ARROW -->
<!ENTITY homtht           "&#x0223B;" ><!--HOMOTHETIC -->
<!ENTITY hookleftarrow    "&#x021A9;" ><!--LEFTWARDS ARROW WITH HOOK -->
<!ENTITY hookrightarrow   "&#x021AA;" ><!--RIGHTWARDS ARROW WITH HOOK -->
<!ENTITY hopf             "&#x1D559;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL H -->
<!ENTITY horbar           "&#x02015;" ><!--HORIZONTAL BAR -->
<!ENTITY hscr             "&#x1D4BD;" ><!--MATHEMATICAL SCRIPT SMALL H -->
<!ENTITY hslash           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY hstrok           "&#x00127;" ><!--LATIN SMALL LETTER H WITH STROKE -->
<!ENTITY hybull           "&#x02043;" ><!--HYPHEN BULLET -->
<!ENTITY hyphen           "&#x02010;" ><!--HYPHEN -->
<!ENTITY iacute           "&#x000ED;" ><!--LATIN SMALL LETTER I WITH ACUTE -->
<!ENTITY ic               "&#x02063;" ><!--INVISIBLE SEPARATOR -->
<!ENTITY icirc            "&#x000EE;" ><!--LATIN SMALL LETTER I WITH CIRCUMFLEX -->
<!ENTITY icy              "&#x00438;" ><!--CYRILLIC SMALL LETTER I -->
<!ENTITY iecy             "&#x00435;" ><!--CYRILLIC SMALL LETTER IE -->
<!ENTITY iexcl            "&#x000A1;" ><!--INVERTED EXCLAMATION MARK -->
<!ENTITY iff              "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY ifr              "&#x1D526;" ><!--MATHEMATICAL FRAKTUR SMALL I -->
<!ENTITY igrave           "&#x000EC;" ><!--LATIN SMALL LETTER I WITH GRAVE -->
<!ENTITY ii               "&#x02148;" ><!--DOUBLE-STRUCK ITALIC SMALL I -->
<!ENTITY iiiint           "&#x02A0C;" ><!--QUADRUPLE INTEGRAL OPERATOR -->
<!ENTITY iiint            "&#x0222D;" ><!--TRIPLE INTEGRAL -->
<!ENTITY iinfin           "&#x029DC;" ><!--INCOMPLETE INFINITY -->
<!ENTITY iiota            "&#x02129;" ><!--TURNED GREEK SMALL LETTER IOTA -->
<!ENTITY ijlig            "&#x00133;" ><!--LATIN SMALL LIGATURE IJ -->
<!ENTITY imacr            "&#x0012B;" ><!--LATIN SMALL LETTER I WITH MACRON -->
<!ENTITY image            "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY imagline         "&#x02110;" ><!--SCRIPT CAPITAL I -->
<!ENTITY imagpart         "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY imath            "&#x00131;" ><!--LATIN SMALL LETTER DOTLESS I -->
<!ENTITY imof             "&#x022B7;" ><!--IMAGE OF -->
<!ENTITY imped            "&#x001B5;" ><!--LATIN CAPITAL LETTER Z WITH STROKE -->
<!ENTITY in               "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY incare           "&#x02105;" ><!--CARE OF -->
<!ENTITY infin            "&#x0221E;" ><!--INFINITY -->
<!ENTITY infintie         "&#x029DD;" ><!--TIE OVER INFINITY -->
<!ENTITY inodot           "&#x00131;" ><!--LATIN SMALL LETTER DOTLESS I -->
<!ENTITY int              "&#x0222B;" ><!--INTEGRAL -->
<!ENTITY intcal           "&#x022BA;" ><!--INTERCALATE -->
<!ENTITY integers         "&#x02124;" ><!--DOUBLE-STRUCK CAPITAL Z -->
<!ENTITY intercal         "&#x022BA;" ><!--INTERCALATE -->
<!ENTITY intlarhk         "&#x02A17;" ><!--INTEGRAL WITH LEFTWARDS ARROW WITH HOOK -->
<!ENTITY intprod          "&#x02A3C;" ><!--INTERIOR PRODUCT -->
<!ENTITY iocy             "&#x00451;" ><!--CYRILLIC SMALL LETTER IO -->
<!ENTITY iogon            "&#x0012F;" ><!--LATIN SMALL LETTER I WITH OGONEK -->
<!ENTITY iopf             "&#x1D55A;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL I -->
<!ENTITY iota             "&#x003B9;" ><!--GREEK SMALL LETTER IOTA -->
<!ENTITY iprod            "&#x02A3C;" ><!--INTERIOR PRODUCT -->
<!ENTITY iquest           "&#x000BF;" ><!--INVERTED QUESTION MARK -->
<!ENTITY iscr             "&#x1D4BE;" ><!--MATHEMATICAL SCRIPT SMALL I -->
<!ENTITY isin             "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY isinE            "&#x022F9;" ><!--ELEMENT OF WITH TWO HORIZONTAL STROKES -->
<!ENTITY isindot          "&#x022F5;" ><!--ELEMENT OF WITH DOT ABOVE -->
<!ENTITY isins            "&#x022F4;" ><!--SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY isinsv           "&#x022F3;" ><!--ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY isinv            "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY it               "&#x02062;" ><!--INVISIBLE TIMES -->
<!ENTITY itilde           "&#x00129;" ><!--LATIN SMALL LETTER I WITH TILDE -->
<!ENTITY iukcy            "&#x00456;" ><!--CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
<!ENTITY iuml             "&#x000EF;" ><!--LATIN SMALL LETTER I WITH DIAERESIS -->
<!ENTITY jcirc            "&#x00135;" ><!--LATIN SMALL LETTER J WITH CIRCUMFLEX -->
<!ENTITY jcy              "&#x00439;" ><!--CYRILLIC SMALL LETTER SHORT I -->
<!ENTITY jfr              "&#x1D527;" ><!--MATHEMATICAL FRAKTUR SMALL J -->
<!ENTITY jmath            "&#x00237;" ><!--LATIN SMALL LETTER DOTLESS J -->
<!ENTITY jopf             "&#x1D55B;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL J -->
<!ENTITY jscr             "&#x1D4BF;" ><!--MATHEMATICAL SCRIPT SMALL J -->
<!ENTITY jsercy           "&#x00458;" ><!--CYRILLIC SMALL LETTER JE -->
<!ENTITY jukcy            "&#x00454;" ><!--CYRILLIC SMALL LETTER UKRAINIAN IE -->
<!ENTITY kappa            "&#x003BA;" ><!--GREEK SMALL LETTER KAPPA -->
<!ENTITY kappav           "&#x003F0;" ><!--GREEK KAPPA SYMBOL -->
<!ENTITY kcedil           "&#x00137;" ><!--LATIN SMALL LETTER K WITH CEDILLA -->
<!ENTITY kcy              "&#x0043A;" ><!--CYRILLIC SMALL LETTER KA -->
<!ENTITY kfr              "&#x1D528;" ><!--MATHEMATICAL FRAKTUR SMALL K -->
<!ENTITY kgreen           "&#x00138;" ><!--LATIN SMALL LETTER KRA -->
<!ENTITY khcy             "&#x00445;" ><!--CYRILLIC SMALL LETTER HA -->
<!ENTITY kjcy             "&#x0045C;" ><!--CYRILLIC SMALL LETTER KJE -->
<!ENTITY kopf             "&#x1D55C;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL K -->
<!ENTITY kscr             "&#x1D4C0;" ><!--MATHEMATICAL SCRIPT SMALL K -->
<!ENTITY lAarr            "&#x021DA;" ><!--LEFTWARDS TRIPLE ARROW -->
<!ENTITY lArr             "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY lAtail           "&#x0291B;" ><!--LEFTWARDS DOUBLE ARROW-TAIL -->
<!ENTITY lBarr            "&#x0290E;" ><!--LEFTWARDS TRIPLE DASH ARROW -->
<!ENTITY lE               "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY lEg              "&#x02A8B;" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
<!ENTITY lHar             "&#x02962;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY lacute           "&#x0013A;" ><!--LATIN SMALL LETTER L WITH ACUTE -->
<!ENTITY laemptyv         "&#x029B4;" ><!--EMPTY SET WITH LEFT ARROW ABOVE -->
<!ENTITY lagran           "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY lambda           "&#x003BB;" ><!--GREEK SMALL LETTER LAMDA -->
<!ENTITY lang             "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY langd            "&#x02991;" ><!--LEFT ANGLE BRACKET WITH DOT -->
<!ENTITY langle           "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY lap              "&#x02A85;" ><!--LESS-THAN OR APPROXIMATE -->
<!ENTITY laquo            "&#x000AB;" ><!--LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
<!ENTITY larr             "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY larrb            "&#x021E4;" ><!--LEFTWARDS ARROW TO BAR -->
<!ENTITY larrbfs          "&#x0291F;" ><!--LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND -->
<!ENTITY larrfs           "&#x0291D;" ><!--LEFTWARDS ARROW TO BLACK DIAMOND -->
<!ENTITY larrhk           "&#x021A9;" ><!--LEFTWARDS ARROW WITH HOOK -->
<!ENTITY larrlp           "&#x021AB;" ><!--LEFTWARDS ARROW WITH LOOP -->
<!ENTITY larrpl           "&#x02939;" ><!--LEFT-SIDE ARC ANTICLOCKWISE ARROW -->
<!ENTITY larrsim          "&#x02973;" ><!--LEFTWARDS ARROW ABOVE TILDE OPERATOR -->
<!ENTITY larrtl           "&#x021A2;" ><!--LEFTWARDS ARROW WITH TAIL -->
<!ENTITY lat              "&#x02AAB;" ><!--LARGER THAN -->
<!ENTITY latail           "&#x02919;" ><!--LEFTWARDS ARROW-TAIL -->
<!ENTITY late             "&#x02AAD;" ><!--LARGER THAN OR EQUAL TO -->
<!ENTITY lates            "&#x02AAD;&#x0FE00;" ><!--LARGER THAN OR slanted EQUAL -->
<!ENTITY lbarr            "&#x0290C;" ><!--LEFTWARDS DOUBLE DASH ARROW -->
<!ENTITY lbbrk            "&#x02772;" ><!--LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT -->
<!ENTITY lbrace           "&#x0007B;" ><!--LEFT CURLY BRACKET -->
<!ENTITY lbrack           "&#x0005B;" ><!--LEFT SQUARE BRACKET -->
<!ENTITY lbrke            "&#x0298B;" ><!--LEFT SQUARE BRACKET WITH UNDERBAR -->
<!ENTITY lbrksld          "&#x0298F;" ><!--LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER -->
<!ENTITY lbrkslu          "&#x0298D;" ><!--LEFT SQUARE BRACKET WITH TICK IN TOP CORNER -->
<!ENTITY lcaron           "&#x0013E;" ><!--LATIN SMALL LETTER L WITH CARON -->
<!ENTITY lcedil           "&#x0013C;" ><!--LATIN SMALL LETTER L WITH CEDILLA -->
<!ENTITY lceil            "&#x02308;" ><!--LEFT CEILING -->
<!ENTITY lcub             "&#x0007B;" ><!--LEFT CURLY BRACKET -->
<!ENTITY lcy              "&#x0043B;" ><!--CYRILLIC SMALL LETTER EL -->
<!ENTITY ldca             "&#x02936;" ><!--ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS -->
<!ENTITY ldquo            "&#x0201C;" ><!--LEFT DOUBLE QUOTATION MARK -->
<!ENTITY ldquor           "&#x0201E;" ><!--DOUBLE LOW-9 QUOTATION MARK -->
<!ENTITY ldrdhar          "&#x02967;" ><!--LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY ldrushar         "&#x0294B;" ><!--LEFT BARB DOWN RIGHT BARB UP HARPOON -->
<!ENTITY ldsh             "&#x021B2;" ><!--DOWNWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY le               "&#x02264;" ><!--LESS-THAN OR EQUAL TO -->
<!ENTITY leftarrow        "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY leftarrowtail    "&#x021A2;" ><!--LEFTWARDS ARROW WITH TAIL -->
<!ENTITY leftharpoondown  "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY leftharpoonup    "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY leftleftarrows   "&#x021C7;" ><!--LEFTWARDS PAIRED ARROWS -->
<!ENTITY leftrightarrow   "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY leftrightarrows  "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY leftrightharpoons "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY leftrightsquigarrow "&#x021AD;" ><!--LEFT RIGHT WAVE ARROW -->
<!ENTITY leftthreetimes   "&#x022CB;" ><!--LEFT SEMIDIRECT PRODUCT -->
<!ENTITY leg              "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY leq              "&#x02264;" ><!--LESS-THAN OR EQUAL TO -->
<!ENTITY leqq             "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY leqslant         "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY les              "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY lescc            "&#x02AA8;" ><!--LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL -->
<!ENTITY lesdot           "&#x02A7F;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE -->
<!ENTITY lesdoto          "&#x02A81;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE -->
<!ENTITY lesdotor         "&#x02A83;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT -->
<!ENTITY lesg             "&#x022DA;&#x0FE00;" ><!--LESS-THAN slanted EQUAL TO OR GREATER-THAN -->
<!ENTITY lesges           "&#x02A93;" ><!--LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL -->
<!ENTITY lessapprox       "&#x02A85;" ><!--LESS-THAN OR APPROXIMATE -->
<!ENTITY lessdot          "&#x022D6;" ><!--LESS-THAN WITH DOT -->
<!ENTITY lesseqgtr        "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY lesseqqgtr       "&#x02A8B;" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
<!ENTITY lessgtr          "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY lesssim          "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY lfisht           "&#x0297C;" ><!--LEFT FISH TAIL -->
<!ENTITY lfloor           "&#x0230A;" ><!--LEFT FLOOR -->
<!ENTITY lfr              "&#x1D529;" ><!--MATHEMATICAL FRAKTUR SMALL L -->
<!ENTITY lg               "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY lgE              "&#x02A91;" ><!--LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL -->
<!ENTITY lhard            "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY lharu            "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY lharul           "&#x0296A;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH -->
<!ENTITY lhblk            "&#x02584;" ><!--LOWER HALF BLOCK -->
<!ENTITY ljcy             "&#x00459;" ><!--CYRILLIC SMALL LETTER LJE -->
<!ENTITY ll               "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY llarr            "&#x021C7;" ><!--LEFTWARDS PAIRED ARROWS -->
<!ENTITY llcorner         "&#x0231E;" ><!--BOTTOM LEFT CORNER -->
<!ENTITY llhard           "&#x0296B;" ><!--LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH -->
<!ENTITY lltri            "&#x025FA;" ><!--LOWER LEFT TRIANGLE -->
<!ENTITY lmidot           "&#x00140;" ><!--LATIN SMALL LETTER L WITH MIDDLE DOT -->
<!ENTITY lmoust           "&#x023B0;" ><!--UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION -->
<!ENTITY lmoustache       "&#x023B0;" ><!--UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION -->
<!ENTITY lnE              "&#x02268;" ><!--LESS-THAN BUT NOT EQUAL TO -->
<!ENTITY lnap             "&#x02A89;" ><!--LESS-THAN AND NOT APPROXIMATE -->
<!ENTITY lnapprox         "&#x02A89;" ><!--LESS-THAN AND NOT APPROXIMATE -->
<!ENTITY lne              "&#x02A87;" ><!--LESS-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY lneq             "&#x02A87;" ><!--LESS-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY lneqq            "&#x02268;" ><!--LESS-THAN BUT NOT EQUAL TO -->
<!ENTITY lnsim            "&#x022E6;" ><!--LESS-THAN BUT NOT EQUIVALENT TO -->
<!ENTITY loang            "&#x027EC;" ><!--MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET -->
<!ENTITY loarr            "&#x021FD;" ><!--LEFTWARDS OPEN-HEADED ARROW -->
<!ENTITY lobrk            "&#x027E6;" ><!--MATHEMATICAL LEFT WHITE SQUARE BRACKET -->
<!ENTITY longleftarrow    "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY longleftrightarrow "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY longmapsto       "&#x027FC;" ><!--LONG RIGHTWARDS ARROW FROM BAR -->
<!ENTITY longrightarrow   "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY looparrowleft    "&#x021AB;" ><!--LEFTWARDS ARROW WITH LOOP -->
<!ENTITY looparrowright   "&#x021AC;" ><!--RIGHTWARDS ARROW WITH LOOP -->
<!ENTITY lopar            "&#x02985;" ><!--LEFT WHITE PARENTHESIS -->
<!ENTITY lopf             "&#x1D55D;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL L -->
<!ENTITY loplus           "&#x02A2D;" ><!--PLUS SIGN IN LEFT HALF CIRCLE -->
<!ENTITY lotimes          "&#x02A34;" ><!--MULTIPLICATION SIGN IN LEFT HALF CIRCLE -->
<!ENTITY lowast           "&#x02217;" ><!--ASTERISK OPERATOR -->
<!ENTITY lowbar           "&#x0005F;" ><!--LOW LINE -->
<!ENTITY loz              "&#x025CA;" ><!--LOZENGE -->
<!ENTITY lozenge          "&#x025CA;" ><!--LOZENGE -->
<!ENTITY lozf             "&#x029EB;" ><!--BLACK LOZENGE -->
<!ENTITY lpar             "&#x00028;" ><!--LEFT PARENTHESIS -->
<!ENTITY lparlt           "&#x02993;" ><!--LEFT ARC LESS-THAN BRACKET -->
<!ENTITY lrarr            "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY lrcorner         "&#x0231F;" ><!--BOTTOM RIGHT CORNER -->
<!ENTITY lrhar            "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY lrhard           "&#x0296D;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH -->
<!ENTITY lrm              "&#x0200E;" ><!--LEFT-TO-RIGHT MARK -->
<!ENTITY lrtri            "&#x022BF;" ><!--RIGHT TRIANGLE -->
<!ENTITY lsaquo           "&#x02039;" ><!--SINGLE LEFT-POINTING ANGLE QUOTATION MARK -->
<!ENTITY lscr             "&#x1D4C1;" ><!--MATHEMATICAL SCRIPT SMALL L -->
<!ENTITY lsh              "&#x021B0;" ><!--UPWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY lsim             "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY lsime            "&#x02A8D;" ><!--LESS-THAN ABOVE SIMILAR OR EQUAL -->
<!ENTITY lsimg            "&#x02A8F;" ><!--LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN -->
<!ENTITY lsqb             "&#x0005B;" ><!--LEFT SQUARE BRACKET -->
<!ENTITY lsquo            "&#x02018;" ><!--LEFT SINGLE QUOTATION MARK -->
<!ENTITY lsquor           "&#x0201A;" ><!--SINGLE LOW-9 QUOTATION MARK -->
<!ENTITY lstrok           "&#x00142;" ><!--LATIN SMALL LETTER L WITH STROKE -->
<!ENTITY lt               "&#38;#60;" ><!--LESS-THAN SIGN -->
<!ENTITY ltcc             "&#x02AA6;" ><!--LESS-THAN CLOSED BY CURVE -->
<!ENTITY ltcir            "&#x02A79;" ><!--LESS-THAN WITH CIRCLE INSIDE -->
<!ENTITY ltdot            "&#x022D6;" ><!--LESS-THAN WITH DOT -->
<!ENTITY lthree           "&#x022CB;" ><!--LEFT SEMIDIRECT PRODUCT -->
<!ENTITY ltimes           "&#x022C9;" ><!--LEFT NORMAL FACTOR SEMIDIRECT PRODUCT -->
<!ENTITY ltlarr           "&#x02976;" ><!--LESS-THAN ABOVE LEFTWARDS ARROW -->
<!ENTITY ltquest          "&#x02A7B;" ><!--LESS-THAN WITH QUESTION MARK ABOVE -->
<!ENTITY ltrPar           "&#x02996;" ><!--DOUBLE RIGHT ARC LESS-THAN BRACKET -->
<!ENTITY ltri             "&#x025C3;" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY ltrie            "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY ltrif            "&#x025C2;" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY lurdshar         "&#x0294A;" ><!--LEFT BARB UP RIGHT BARB DOWN HARPOON -->
<!ENTITY luruhar          "&#x02966;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP -->
<!ENTITY lvertneqq        "&#x02268;&#x0FE00;" ><!--LESS-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY lvnE             "&#x02268;&#x0FE00;" ><!--LESS-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY mDDot            "&#x0223A;" ><!--GEOMETRIC PROPORTION -->
<!ENTITY macr             "&#x000AF;" ><!--MACRON -->
<!ENTITY male             "&#x02642;" ><!--MALE SIGN -->
<!ENTITY malt             "&#x02720;" ><!--MALTESE CROSS -->
<!ENTITY maltese          "&#x02720;" ><!--MALTESE CROSS -->
<!ENTITY map              "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY mapsto           "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY mapstodown       "&#x021A7;" ><!--DOWNWARDS ARROW FROM BAR -->
<!ENTITY mapstoleft       "&#x021A4;" ><!--LEFTWARDS ARROW FROM BAR -->
<!ENTITY mapstoup         "&#x021A5;" ><!--UPWARDS ARROW FROM BAR -->
<!ENTITY marker           "&#x025AE;" ><!--BLACK VERTICAL RECTANGLE -->
<!ENTITY mcomma           "&#x02A29;" ><!--MINUS SIGN WITH COMMA ABOVE -->
<!ENTITY mcy              "&#x0043C;" ><!--CYRILLIC SMALL LETTER EM -->
<!ENTITY mdash            "&#x02014;" ><!--EM DASH -->
<!ENTITY measuredangle    "&#x02221;" ><!--MEASURED ANGLE -->
<!ENTITY mfr              "&#x1D52A;" ><!--MATHEMATICAL FRAKTUR SMALL M -->
<!ENTITY mho              "&#x02127;" ><!--INVERTED OHM SIGN -->
<!ENTITY micro            "&#x000B5;" ><!--MICRO SIGN -->
<!ENTITY mid              "&#x02223;" ><!--DIVIDES -->
<!ENTITY midast           "&#x0002A;" ><!--ASTERISK -->
<!ENTITY midcir           "&#x02AF0;" ><!--VERTICAL LINE WITH CIRCLE BELOW -->
<!ENTITY middot           "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY minus            "&#x02212;" ><!--MINUS SIGN -->
<!ENTITY minusb           "&#x0229F;" ><!--SQUARED MINUS -->
<!ENTITY minusd           "&#x02238;" ><!--DOT MINUS -->
<!ENTITY minusdu          "&#x02A2A;" ><!--MINUS SIGN WITH DOT BELOW -->
<!ENTITY mlcp             "&#x02ADB;" ><!--TRANSVERSAL INTERSECTION -->
<!ENTITY mldr             "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
<!ENTITY mnplus           "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY models           "&#x022A7;" ><!--MODELS -->
<!ENTITY mopf             "&#x1D55E;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL M -->
<!ENTITY mp               "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY mscr             "&#x1D4C2;" ><!--MATHEMATICAL SCRIPT SMALL M -->
<!ENTITY mstpos           "&#x0223E;" ><!--INVERTED LAZY S -->
<!ENTITY mu               "&#x003BC;" ><!--GREEK SMALL LETTER MU -->
<!ENTITY multimap         "&#x022B8;" ><!--MULTIMAP -->
<!ENTITY mumap            "&#x022B8;" ><!--MULTIMAP -->
<!ENTITY nGg              "&#x022D9;&#x00338;" ><!--VERY MUCH GREATER-THAN with slash -->
<!ENTITY nGt              "&#x0226B;&#x020D2;" ><!--MUCH GREATER THAN with vertical line -->
<!ENTITY nGtv             "&#x0226B;&#x00338;" ><!--MUCH GREATER THAN with slash -->
<!ENTITY nLeftarrow       "&#x021CD;" ><!--LEFTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nLeftrightarrow  "&#x021CE;" ><!--LEFT RIGHT DOUBLE ARROW WITH STROKE -->
<!ENTITY nLl              "&#x022D8;&#x00338;" ><!--VERY MUCH LESS-THAN with slash -->
<!ENTITY nLt              "&#x0226A;&#x020D2;" ><!--MUCH LESS THAN with vertical line -->
<!ENTITY nLtv             "&#x0226A;&#x00338;" ><!--MUCH LESS THAN with slash -->
<!ENTITY nRightarrow      "&#x021CF;" ><!--RIGHTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nVDash           "&#x022AF;" ><!--NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -->
<!ENTITY nVdash           "&#x022AE;" ><!--DOES NOT FORCE -->
<!ENTITY nabla            "&#x02207;" ><!--NABLA -->
<!ENTITY nacute           "&#x00144;" ><!--LATIN SMALL LETTER N WITH ACUTE -->
<!ENTITY nang             "&#x02220;&#x020D2;" ><!--ANGLE with vertical line -->
<!ENTITY nap              "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY napE             "&#x02A70;&#x00338;" ><!--APPROXIMATELY EQUAL OR EQUAL TO with slash -->
<!ENTITY napid            "&#x0224B;&#x00338;" ><!--TRIPLE TILDE with slash -->
<!ENTITY napos            "&#x00149;" ><!--LATIN SMALL LETTER N PRECEDED BY APOSTROPHE -->
<!ENTITY napprox          "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY natur            "&#x0266E;" ><!--MUSIC NATURAL SIGN -->
<!ENTITY natural          "&#x0266E;" ><!--MUSIC NATURAL SIGN -->
<!ENTITY naturals         "&#x02115;" ><!--DOUBLE-STRUCK CAPITAL N -->
<!ENTITY nbsp             "&#x000A0;" ><!--NO-BREAK SPACE -->
<!ENTITY nbump            "&#x0224E;&#x00338;" ><!--GEOMETRICALLY EQUIVALENT TO with slash -->
<!ENTITY nbumpe           "&#x0224F;&#x00338;" ><!--DIFFERENCE BETWEEN with slash -->
<!ENTITY ncap             "&#x02A43;" ><!--INTERSECTION WITH OVERBAR -->
<!ENTITY ncaron           "&#x00148;" ><!--LATIN SMALL LETTER N WITH CARON -->
<!ENTITY ncedil           "&#x00146;" ><!--LATIN SMALL LETTER N WITH CEDILLA -->
<!ENTITY ncong            "&#x02247;" ><!--NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO -->
<!ENTITY ncongdot         "&#x02A6D;&#x00338;" ><!--CONGRUENT WITH DOT ABOVE with slash -->
<!ENTITY ncup             "&#x02A42;" ><!--UNION WITH OVERBAR -->
<!ENTITY ncy              "&#x0043D;" ><!--CYRILLIC SMALL LETTER EN -->
<!ENTITY ndash            "&#x02013;" ><!--EN DASH -->
<!ENTITY ne               "&#x02260;" ><!--NOT EQUAL TO -->
<!ENTITY neArr            "&#x021D7;" ><!--NORTH EAST DOUBLE ARROW -->
<!ENTITY nearhk           "&#x02924;" ><!--NORTH EAST ARROW WITH HOOK -->
<!ENTITY nearr            "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY nearrow          "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY nedot            "&#x02250;&#x00338;" ><!--APPROACHES THE LIMIT with slash -->
<!ENTITY nequiv           "&#x02262;" ><!--NOT IDENTICAL TO -->
<!ENTITY nesear           "&#x02928;" ><!--NORTH EAST ARROW AND SOUTH EAST ARROW -->
<!ENTITY nesim            "&#x02242;&#x00338;" ><!--MINUS TILDE with slash -->
<!ENTITY nexist           "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY nexists          "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY nfr              "&#x1D52B;" ><!--MATHEMATICAL FRAKTUR SMALL N -->
<!ENTITY ngE              "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY nge              "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY ngeq             "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY ngeqq            "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY ngeqslant        "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nges             "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY ngsim            "&#x02275;" ><!--NEITHER GREATER-THAN NOR EQUIVALENT TO -->
<!ENTITY ngt              "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY ngtr             "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY nhArr            "&#x021CE;" ><!--LEFT RIGHT DOUBLE ARROW WITH STROKE -->
<!ENTITY nharr            "&#x021AE;" ><!--LEFT RIGHT ARROW WITH STROKE -->
<!ENTITY nhpar            "&#x02AF2;" ><!--PARALLEL WITH HORIZONTAL STROKE -->
<!ENTITY ni               "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY nis              "&#x022FC;" ><!--SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY nisd             "&#x022FA;" ><!--CONTAINS WITH LONG HORIZONTAL STROKE -->
<!ENTITY niv              "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY njcy             "&#x0045A;" ><!--CYRILLIC SMALL LETTER NJE -->
<!ENTITY nlArr            "&#x021CD;" ><!--LEFTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nlE              "&#x02266;&#x00338;" ><!--LESS-THAN OVER EQUAL TO with slash -->
<!ENTITY nlarr            "&#x0219A;" ><!--LEFTWARDS ARROW WITH STROKE -->
<!ENTITY nldr             "&#x02025;" ><!--TWO DOT LEADER -->
<!ENTITY nle              "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY nleftarrow       "&#x0219A;" ><!--LEFTWARDS ARROW WITH STROKE -->
<!ENTITY nleftrightarrow  "&#x021AE;" ><!--LEFT RIGHT ARROW WITH STROKE -->
<!ENTITY nleq             "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY nleqq            "&#x02266;&#x00338;" ><!--LESS-THAN OVER EQUAL TO with slash -->
<!ENTITY nleqslant        "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nles             "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nless            "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY nlsim            "&#x02274;" ><!--NEITHER LESS-THAN NOR EQUIVALENT TO -->
<!ENTITY nlt              "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY nltri            "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY nltrie           "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY nmid             "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nopf             "&#x1D55F;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL N -->
<!ENTITY not              "&#x000AC;" ><!--NOT SIGN -->
<!ENTITY notin            "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY notinE           "&#x022F9;&#x00338;" ><!--ELEMENT OF WITH TWO HORIZONTAL STROKES with slash -->
<!ENTITY notindot         "&#x022F5;&#x00338;" ><!--ELEMENT OF WITH DOT ABOVE with slash -->
<!ENTITY notinva          "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY notinvb          "&#x022F7;" ><!--SMALL ELEMENT OF WITH OVERBAR -->
<!ENTITY notinvc          "&#x022F6;" ><!--ELEMENT OF WITH OVERBAR -->
<!ENTITY notni            "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY notniva          "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY notnivb          "&#x022FE;" ><!--SMALL CONTAINS WITH OVERBAR -->
<!ENTITY notnivc          "&#x022FD;" ><!--CONTAINS WITH OVERBAR -->
<!ENTITY npar             "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nparallel        "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nparsl           "&#x02AFD;&#x020E5;" ><!--DOUBLE SOLIDUS OPERATOR with reverse slash -->
<!ENTITY npart            "&#x02202;&#x00338;" ><!--PARTIAL DIFFERENTIAL with slash -->
<!ENTITY npolint          "&#x02A14;" ><!--LINE INTEGRATION NOT INCLUDING THE POLE -->
<!ENTITY npr              "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY nprcue           "&#x022E0;" ><!--DOES NOT PRECEDE OR EQUAL -->
<!ENTITY npre             "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nprec            "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY npreceq          "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nrArr            "&#x021CF;" ><!--RIGHTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nrarr            "&#x0219B;" ><!--RIGHTWARDS ARROW WITH STROKE -->
<!ENTITY nrarrc           "&#x02933;&#x00338;" ><!--WAVE ARROW POINTING DIRECTLY RIGHT with slash -->
<!ENTITY nrarrw           "&#x0219D;&#x00338;" ><!--RIGHTWARDS WAVE ARROW with slash -->
<!ENTITY nrightarrow      "&#x0219B;" ><!--RIGHTWARDS ARROW WITH STROKE -->
<!ENTITY nrtri            "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY nrtrie           "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY nsc              "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY nsccue           "&#x022E1;" ><!--DOES NOT SUCCEED OR EQUAL -->
<!ENTITY nsce             "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nscr             "&#x1D4C3;" ><!--MATHEMATICAL SCRIPT SMALL N -->
<!ENTITY nshortmid        "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nshortparallel   "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nsim             "&#x02241;" ><!--NOT TILDE -->
<!ENTITY nsime            "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY nsimeq           "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY nsmid            "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nspar            "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nsqsube          "&#x022E2;" ><!--NOT SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY nsqsupe          "&#x022E3;" ><!--NOT SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY nsub             "&#x02284;" ><!--NOT A SUBSET OF -->
<!ENTITY nsubE            "&#x02AC5;&#x00338;" ><!--SUBSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsube            "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY nsubset          "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY nsubseteq        "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY nsubseteqq       "&#x02AC5;&#x00338;" ><!--SUBSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsucc            "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY nsucceq          "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nsup             "&#x02285;" ><!--NOT A SUPERSET OF -->
<!ENTITY nsupE            "&#x02AC6;&#x00338;" ><!--SUPERSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsupe            "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY nsupset          "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY nsupseteq        "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY nsupseteqq       "&#x02AC6;&#x00338;" ><!--SUPERSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY ntgl             "&#x02279;" ><!--NEITHER GREATER-THAN NOR LESS-THAN -->
<!ENTITY ntilde           "&#x000F1;" ><!--LATIN SMALL LETTER N WITH TILDE -->
<!ENTITY ntlg             "&#x02278;" ><!--NEITHER LESS-THAN NOR GREATER-THAN -->
<!ENTITY ntriangleleft    "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY ntrianglelefteq  "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY ntriangleright   "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY ntrianglerighteq "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY nu               "&#x003BD;" ><!--GREEK SMALL LETTER NU -->
<!ENTITY num              "&#x00023;" ><!--NUMBER SIGN -->
<!ENTITY numero           "&#x02116;" ><!--NUMERO SIGN -->
<!ENTITY numsp            "&#x02007;" ><!--FIGURE SPACE -->
<!ENTITY nvDash           "&#x022AD;" ><!--NOT TRUE -->
<!ENTITY nvHarr           "&#x02904;" ><!--LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvap             "&#x0224D;&#x020D2;" ><!--EQUIVALENT TO with vertical line -->
<!ENTITY nvdash           "&#x022AC;" ><!--DOES NOT PROVE -->
<!ENTITY nvge             "&#x02265;&#x020D2;" ><!--GREATER-THAN OR EQUAL TO with vertical line -->
<!ENTITY nvgt             "&#x0003E;&#x020D2;" ><!--GREATER-THAN SIGN with vertical line -->
<!ENTITY nvinfin          "&#x029DE;" ><!--INFINITY NEGATED WITH VERTICAL BAR -->
<!ENTITY nvlArr           "&#x02902;" ><!--LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvle             "&#x02264;&#x020D2;" ><!--LESS-THAN OR EQUAL TO with vertical line -->
<!ENTITY nvlt             "&#38;#x0003C;&#x020D2;" ><!--LESS-THAN SIGN with vertical line -->
<!ENTITY nvltrie          "&#x022B4;&#x020D2;" ><!--NORMAL SUBGROUP OF OR EQUAL TO with vertical line -->
<!ENTITY nvrArr           "&#x02903;" ><!--RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvrtrie          "&#x022B5;&#x020D2;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line -->
<!ENTITY nvsim            "&#x0223C;&#x020D2;" ><!--TILDE OPERATOR with vertical line -->
<!ENTITY nwArr            "&#x021D6;" ><!--NORTH WEST DOUBLE ARROW -->
<!ENTITY nwarhk           "&#x02923;" ><!--NORTH WEST ARROW WITH HOOK -->
<!ENTITY nwarr            "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY nwarrow          "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY nwnear           "&#x02927;" ><!--NORTH WEST ARROW AND NORTH EAST ARROW -->
<!ENTITY oS               "&#x024C8;" ><!--CIRCLED LATIN CAPITAL LETTER S -->
<!ENTITY oacute           "&#x000F3;" ><!--LATIN SMALL LETTER O WITH ACUTE -->
<!ENTITY oast             "&#x0229B;" ><!--CIRCLED ASTERISK OPERATOR -->
<!ENTITY ocir             "&#x0229A;" ><!--CIRCLED RING OPERATOR -->
<!ENTITY ocirc            "&#x000F4;" ><!--LATIN SMALL LETTER O WITH CIRCUMFLEX -->
<!ENTITY ocy              "&#x0043E;" ><!--CYRILLIC SMALL LETTER O -->
<!ENTITY odash            "&#x0229D;" ><!--CIRCLED DASH -->
<!ENTITY odblac           "&#x00151;" ><!--LATIN SMALL LETTER O WITH DOUBLE ACUTE -->
<!ENTITY odiv             "&#x02A38;" ><!--CIRCLED DIVISION SIGN -->
<!ENTITY odot             "&#x02299;" ><!--CIRCLED DOT OPERATOR -->
<!ENTITY odsold           "&#x029BC;" ><!--CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN -->
<!ENTITY oelig            "&#x00153;" ><!--LATIN SMALL LIGATURE OE -->
<!ENTITY ofcir            "&#x029BF;" ><!--CIRCLED BULLET -->
<!ENTITY ofr              "&#x1D52C;" ><!--MATHEMATICAL FRAKTUR SMALL O -->
<!ENTITY ogon             "&#x002DB;" ><!--OGONEK -->
<!ENTITY ograve           "&#x000F2;" ><!--LATIN SMALL LETTER O WITH GRAVE -->
<!ENTITY ogt              "&#x029C1;" ><!--CIRCLED GREATER-THAN -->
<!ENTITY ohbar            "&#x029B5;" ><!--CIRCLE WITH HORIZONTAL BAR -->
<!ENTITY ohm              "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
<!ENTITY oint             "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY olarr            "&#x021BA;" ><!--ANTICLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY olcir            "&#x029BE;" ><!--CIRCLED WHITE BULLET -->
<!ENTITY olcross          "&#x029BB;" ><!--CIRCLE WITH SUPERIMPOSED X -->
<!ENTITY oline            "&#x0203E;" ><!--OVERLINE -->
<!ENTITY olt              "&#x029C0;" ><!--CIRCLED LESS-THAN -->
<!ENTITY omacr            "&#x0014D;" ><!--LATIN SMALL LETTER O WITH MACRON -->
<!ENTITY omega            "&#x003C9;" ><!--GREEK SMALL LETTER OMEGA -->
<!ENTITY omicron          "&#x003BF;" ><!--GREEK SMALL LETTER OMICRON -->
<!ENTITY omid             "&#x029B6;" ><!--CIRCLED VERTICAL BAR -->
<!ENTITY ominus           "&#x02296;" ><!--CIRCLED MINUS -->
<!ENTITY oopf             "&#x1D560;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL O -->
<!ENTITY opar             "&#x029B7;" ><!--CIRCLED PARALLEL -->
<!ENTITY operp            "&#x029B9;" ><!--CIRCLED PERPENDICULAR -->
<!ENTITY oplus            "&#x02295;" ><!--CIRCLED PLUS -->
<!ENTITY or               "&#x02228;" ><!--LOGICAL OR -->
<!ENTITY orarr            "&#x021BB;" ><!--CLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY ord              "&#x02A5D;" ><!--LOGICAL OR WITH HORIZONTAL DASH -->
<!ENTITY order            "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY orderof          "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY ordf             "&#x000AA;" ><!--FEMININE ORDINAL INDICATOR -->
<!ENTITY ordm             "&#x000BA;" ><!--MASCULINE ORDINAL INDICATOR -->
<!ENTITY origof           "&#x022B6;" ><!--ORIGINAL OF -->
<!ENTITY oror             "&#x02A56;" ><!--TWO INTERSECTING LOGICAL OR -->
<!ENTITY orslope          "&#x02A57;" ><!--SLOPING LARGE OR -->
<!ENTITY orv              "&#x02A5B;" ><!--LOGICAL OR WITH MIDDLE STEM -->
<!ENTITY oscr             "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY oslash           "&#x000F8;" ><!--LATIN SMALL LETTER O WITH STROKE -->
<!ENTITY osol             "&#x02298;" ><!--CIRCLED DIVISION SLASH -->
<!ENTITY otilde           "&#x000F5;" ><!--LATIN SMALL LETTER O WITH TILDE -->
<!ENTITY otimes           "&#x02297;" ><!--CIRCLED TIMES -->
<!ENTITY otimesas         "&#x02A36;" ><!--CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT -->
<!ENTITY ouml             "&#x000F6;" ><!--LATIN SMALL LETTER O WITH DIAERESIS -->
<!ENTITY ovbar            "&#x0233D;" ><!--APL FUNCTIONAL SYMBOL CIRCLE STILE -->
<!ENTITY par              "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY para             "&#x000B6;" ><!--PILCROW SIGN -->
<!ENTITY parallel         "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY parsim           "&#x02AF3;" ><!--PARALLEL WITH TILDE OPERATOR -->
<!ENTITY parsl            "&#x02AFD;" ><!--DOUBLE SOLIDUS OPERATOR -->
<!ENTITY part             "&#x02202;" ><!--PARTIAL DIFFERENTIAL -->
<!ENTITY pcy              "&#x0043F;" ><!--CYRILLIC SMALL LETTER PE -->
<!ENTITY percnt           "&#x00025;" ><!--PERCENT SIGN -->
<!ENTITY period           "&#x0002E;" ><!--FULL STOP -->
<!ENTITY permil           "&#x02030;" ><!--PER MILLE SIGN -->
<!ENTITY perp             "&#x022A5;" ><!--UP TACK -->
<!ENTITY pertenk          "&#x02031;" ><!--PER TEN THOUSAND SIGN -->
<!ENTITY pfr              "&#x1D52D;" ><!--MATHEMATICAL FRAKTUR SMALL P -->
<!ENTITY phi              "&#x003C6;" ><!--GREEK SMALL LETTER PHI -->
<!ENTITY phiv             "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY phmmat           "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY phone            "&#x0260E;" ><!--BLACK TELEPHONE -->
<!ENTITY pi               "&#x003C0;" ><!--GREEK SMALL LETTER PI -->
<!ENTITY pitchfork        "&#x022D4;" ><!--PITCHFORK -->
<!ENTITY piv              "&#x003D6;" ><!--GREEK PI SYMBOL -->
<!ENTITY planck           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY planckh          "&#x0210E;" ><!--PLANCK CONSTANT -->
<!ENTITY plankv           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY plus             "&#x0002B;" ><!--PLUS SIGN -->
<!ENTITY plusacir         "&#x02A23;" ><!--PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE -->
<!ENTITY plusb            "&#x0229E;" ><!--SQUARED PLUS -->
<!ENTITY pluscir          "&#x02A22;" ><!--PLUS SIGN WITH SMALL CIRCLE ABOVE -->
<!ENTITY plusdo           "&#x02214;" ><!--DOT PLUS -->
<!ENTITY plusdu           "&#x02A25;" ><!--PLUS SIGN WITH DOT BELOW -->
<!ENTITY pluse            "&#x02A72;" ><!--PLUS SIGN ABOVE EQUALS SIGN -->
<!ENTITY plusmn           "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY plussim          "&#x02A26;" ><!--PLUS SIGN WITH TILDE BELOW -->
<!ENTITY plustwo          "&#x02A27;" ><!--PLUS SIGN WITH SUBSCRIPT TWO -->
<!ENTITY pm               "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY pointint         "&#x02A15;" ><!--INTEGRAL AROUND A POINT OPERATOR -->
<!ENTITY popf             "&#x1D561;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL P -->
<!ENTITY pound            "&#x000A3;" ><!--POUND SIGN -->
<!ENTITY pr               "&#x0227A;" ><!--PRECEDES -->
<!ENTITY prE              "&#x02AB3;" ><!--PRECEDES ABOVE EQUALS SIGN -->
<!ENTITY prap             "&#x02AB7;" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
<!ENTITY prcue            "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY pre              "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY prec             "&#x0227A;" ><!--PRECEDES -->
<!ENTITY precapprox       "&#x02AB7;" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
<!ENTITY preccurlyeq      "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY preceq           "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY precnapprox      "&#x02AB9;" ><!--PRECEDES ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY precneqq         "&#x02AB5;" ><!--PRECEDES ABOVE NOT EQUAL TO -->
<!ENTITY precnsim         "&#x022E8;" ><!--PRECEDES BUT NOT EQUIVALENT TO -->
<!ENTITY precsim          "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY prime            "&#x02032;" ><!--PRIME -->
<!ENTITY primes           "&#x02119;" ><!--DOUBLE-STRUCK CAPITAL P -->
<!ENTITY prnE             "&#x02AB5;" ><!--PRECEDES ABOVE NOT EQUAL TO -->
<!ENTITY prnap            "&#x02AB9;" ><!--PRECEDES ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY prnsim           "&#x022E8;" ><!--PRECEDES BUT NOT EQUIVALENT TO -->
<!ENTITY prod             "&#x0220F;" ><!--N-ARY PRODUCT -->
<!ENTITY profalar         "&#x0232E;" ><!--ALL AROUND-PROFILE -->
<!ENTITY profline         "&#x02312;" ><!--ARC -->
<!ENTITY profsurf         "&#x02313;" ><!--SEGMENT -->
<!ENTITY prop             "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY propto           "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY prsim            "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY prurel           "&#x022B0;" ><!--PRECEDES UNDER RELATION -->
<!ENTITY pscr             "&#x1D4C5;" ><!--MATHEMATICAL SCRIPT SMALL P -->
<!ENTITY psi              "&#x003C8;" ><!--GREEK SMALL LETTER PSI -->
<!ENTITY puncsp           "&#x02008;" ><!--PUNCTUATION SPACE -->
<!ENTITY qfr              "&#x1D52E;" ><!--MATHEMATICAL FRAKTUR SMALL Q -->
<!ENTITY qint             "&#x02A0C;" ><!--QUADRUPLE INTEGRAL OPERATOR -->
<!ENTITY qopf             "&#x1D562;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Q -->
<!ENTITY qprime           "&#x02057;" ><!--QUADRUPLE PRIME -->
<!ENTITY qscr             "&#x1D4C6;" ><!--MATHEMATICAL SCRIPT SMALL Q -->
<!ENTITY quaternions      "&#x0210D;" ><!--DOUBLE-STRUCK CAPITAL H -->
<!ENTITY quatint          "&#x02A16;" ><!--QUATERNION INTEGRAL OPERATOR -->
<!ENTITY quest            "&#x0003F;" ><!--QUESTION MARK -->
<!ENTITY questeq          "&#x0225F;" ><!--QUESTIONED EQUAL TO -->
<!ENTITY quot             "&#x00022;" ><!--QUOTATION MARK -->
<!ENTITY rAarr            "&#x021DB;" ><!--RIGHTWARDS TRIPLE ARROW -->
<!ENTITY rArr             "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY rAtail           "&#x0291C;" ><!--RIGHTWARDS DOUBLE ARROW-TAIL -->
<!ENTITY rBarr            "&#x0290F;" ><!--RIGHTWARDS TRIPLE DASH ARROW -->
<!ENTITY rHar             "&#x02964;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY race             "&#x0223D;&#x00331;" ><!--REVERSED TILDE with underline -->
<!ENTITY racute           "&#x00155;" ><!--LATIN SMALL LETTER R WITH ACUTE -->
<!ENTITY radic            "&#x0221A;" ><!--SQUARE ROOT -->
<!ENTITY raemptyv         "&#x029B3;" ><!--EMPTY SET WITH RIGHT ARROW ABOVE -->
<!ENTITY rang             "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY rangd            "&#x02992;" ><!--RIGHT ANGLE BRACKET WITH DOT -->
<!ENTITY range            "&#x029A5;" ><!--REVERSED ANGLE WITH UNDERBAR -->
<!ENTITY rangle           "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY raquo            "&#x000BB;" ><!--RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
<!ENTITY rarr             "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY rarrap           "&#x02975;" ><!--RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO -->
<!ENTITY rarrb            "&#x021E5;" ><!--RIGHTWARDS ARROW TO BAR -->
<!ENTITY rarrbfs          "&#x02920;" ><!--RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND -->
<!ENTITY rarrc            "&#x02933;" ><!--WAVE ARROW POINTING DIRECTLY RIGHT -->
<!ENTITY rarrfs           "&#x0291E;" ><!--RIGHTWARDS ARROW TO BLACK DIAMOND -->
<!ENTITY rarrhk           "&#x021AA;" ><!--RIGHTWARDS ARROW WITH HOOK -->
<!ENTITY rarrlp           "&#x021AC;" ><!--RIGHTWARDS ARROW WITH LOOP -->
<!ENTITY rarrpl           "&#x02945;" ><!--RIGHTWARDS ARROW WITH PLUS BELOW -->
<!ENTITY rarrsim          "&#x02974;" ><!--RIGHTWARDS ARROW ABOVE TILDE OPERATOR -->
<!ENTITY rarrtl           "&#x021A3;" ><!--RIGHTWARDS ARROW WITH TAIL -->
<!ENTITY rarrw            "&#x0219D;" ><!--RIGHTWARDS WAVE ARROW -->
<!ENTITY ratail           "&#x0291A;" ><!--RIGHTWARDS ARROW-TAIL -->
<!ENTITY ratio            "&#x02236;" ><!--RATIO -->
<!ENTITY rationals        "&#x0211A;" ><!--DOUBLE-STRUCK CAPITAL Q -->
<!ENTITY rbarr            "&#x0290D;" ><!--RIGHTWARDS DOUBLE DASH ARROW -->
<!ENTITY rbbrk            "&#x02773;" ><!--LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT -->
<!ENTITY rbrace           "&#x0007D;" ><!--RIGHT CURLY BRACKET -->
<!ENTITY rbrack           "&#x0005D;" ><!--RIGHT SQUARE BRACKET -->
<!ENTITY rbrke            "&#x0298C;" ><!--RIGHT SQUARE BRACKET WITH UNDERBAR -->
<!ENTITY rbrksld          "&#x0298E;" ><!--RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER -->
<!ENTITY rbrkslu          "&#x02990;" ><!--RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER -->
<!ENTITY rcaron           "&#x00159;" ><!--LATIN SMALL LETTER R WITH CARON -->
<!ENTITY rcedil           "&#x00157;" ><!--LATIN SMALL LETTER R WITH CEDILLA -->
<!ENTITY rceil            "&#x02309;" ><!--RIGHT CEILING -->
<!ENTITY rcub             "&#x0007D;" ><!--RIGHT CURLY BRACKET -->
<!ENTITY rcy              "&#x00440;" ><!--CYRILLIC SMALL LETTER ER -->
<!ENTITY rdca             "&#x02937;" ><!--ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS -->
<!ENTITY rdldhar          "&#x02969;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY rdquo            "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY rdquor           "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY rdsh             "&#x021B3;" ><!--DOWNWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY real             "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY realine          "&#x0211B;" ><!--SCRIPT CAPITAL R -->
<!ENTITY realpart         "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY reals            "&#x0211D;" ><!--DOUBLE-STRUCK CAPITAL R -->
<!ENTITY rect             "&#x025AD;" ><!--WHITE RECTANGLE -->
<!ENTITY reg              "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY rfisht           "&#x0297D;" ><!--RIGHT FISH TAIL -->
<!ENTITY rfloor           "&#x0230B;" ><!--RIGHT FLOOR -->
<!ENTITY rfr              "&#x1D52F;" ><!--MATHEMATICAL FRAKTUR SMALL R -->
<!ENTITY rhard            "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY rharu            "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY rharul           "&#x0296C;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH -->
<!ENTITY rho              "&#x003C1;" ><!--GREEK SMALL LETTER RHO -->
<!ENTITY rhov             "&#x003F1;" ><!--GREEK RHO SYMBOL -->
<!ENTITY rightarrow       "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY rightarrowtail   "&#x021A3;" ><!--RIGHTWARDS ARROW WITH TAIL -->
<!ENTITY rightharpoondown "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY rightharpoonup   "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY rightleftarrows  "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY rightleftharpoons "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY rightrightarrows "&#x021C9;" ><!--RIGHTWARDS PAIRED ARROWS -->
<!ENTITY rightsquigarrow  "&#x0219D;" ><!--RIGHTWARDS WAVE ARROW -->
<!ENTITY rightthreetimes  "&#x022CC;" ><!--RIGHT SEMIDIRECT PRODUCT -->
<!ENTITY ring             "&#x002DA;" ><!--RING ABOVE -->
<!ENTITY risingdotseq     "&#x02253;" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
<!ENTITY rlarr            "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY rlhar            "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY rlm              "&#x0200F;" ><!--RIGHT-TO-LEFT MARK -->
<!ENTITY rmoust           "&#x023B1;" ><!--UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION -->
<!ENTITY rmoustache       "&#x023B1;" ><!--UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION -->
<!ENTITY rnmid            "&#x02AEE;" ><!--DOES NOT DIVIDE WITH REVERSED NEGATION SLASH -->
<!ENTITY roang            "&#x027ED;" ><!--MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET -->
<!ENTITY roarr            "&#x021FE;" ><!--RIGHTWARDS OPEN-HEADED ARROW -->
<!ENTITY robrk            "&#x027E7;" ><!--MATHEMATICAL RIGHT WHITE SQUARE BRACKET -->
<!ENTITY ropar            "&#x02986;" ><!--RIGHT WHITE PARENTHESIS -->
<!ENTITY ropf             "&#x1D563;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL R -->
<!ENTITY roplus           "&#x02A2E;" ><!--PLUS SIGN IN RIGHT HALF CIRCLE -->
<!ENTITY rotimes          "&#x02A35;" ><!--MULTIPLICATION SIGN IN RIGHT HALF CIRCLE -->
<!ENTITY rpar             "&#x00029;" ><!--RIGHT PARENTHESIS -->
<!ENTITY rpargt           "&#x02994;" ><!--RIGHT ARC GREATER-THAN BRACKET -->
<!ENTITY rppolint         "&#x02A12;" ><!--LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE -->
<!ENTITY rrarr            "&#x021C9;" ><!--RIGHTWARDS PAIRED ARROWS -->
<!ENTITY rsaquo           "&#x0203A;" ><!--SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
<!ENTITY rscr             "&#x1D4C7;" ><!--MATHEMATICAL SCRIPT SMALL R -->
<!ENTITY rsh              "&#x021B1;" ><!--UPWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY rsqb             "&#x0005D;" ><!--RIGHT SQUARE BRACKET -->
<!ENTITY rsquo            "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY rsquor           "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY rthree           "&#x022CC;" ><!--RIGHT SEMIDIRECT PRODUCT -->
<!ENTITY rtimes           "&#x022CA;" ><!--RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT -->
<!ENTITY rtri             "&#x025B9;" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY rtrie            "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY rtrif            "&#x025B8;" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY rtriltri         "&#x029CE;" ><!--RIGHT TRIANGLE ABOVE LEFT TRIANGLE -->
<!ENTITY ruluhar          "&#x02968;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP -->
<!ENTITY rx               "&#x0211E;" ><!--PRESCRIPTION TAKE -->
<!ENTITY sacute           "&#x0015B;" ><!--LATIN SMALL LETTER S WITH ACUTE -->
<!ENTITY sbquo            "&#x0201A;" ><!--SINGLE LOW-9 QUOTATION MARK -->
<!ENTITY sc               "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY scE              "&#x02AB4;" ><!--SUCCEEDS ABOVE EQUALS SIGN -->
<!ENTITY scap             "&#x02AB8;" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
<!ENTITY scaron           "&#x00161;" ><!--LATIN SMALL LETTER S WITH CARON -->
<!ENTITY sccue            "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY sce              "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY scedil           "&#x0015F;" ><!--LATIN SMALL LETTER S WITH CEDILLA -->
<!ENTITY scirc            "&#x0015D;" ><!--LATIN SMALL LETTER S WITH CIRCUMFLEX -->
<!ENTITY scnE             "&#x02AB6;" ><!--SUCCEEDS ABOVE NOT EQUAL TO -->
<!ENTITY scnap            "&#x02ABA;" ><!--SUCCEEDS ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY scnsim           "&#x022E9;" ><!--SUCCEEDS BUT NOT EQUIVALENT TO -->
<!ENTITY scpolint         "&#x02A13;" ><!--LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE -->
<!ENTITY scsim            "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY scy              "&#x00441;" ><!--CYRILLIC SMALL LETTER ES -->
<!ENTITY sdot             "&#x022C5;" ><!--DOT OPERATOR -->
<!ENTITY sdotb            "&#x022A1;" ><!--SQUARED DOT OPERATOR -->
<!ENTITY sdote            "&#x02A66;" ><!--EQUALS SIGN WITH DOT BELOW -->
<!ENTITY seArr            "&#x021D8;" ><!--SOUTH EAST DOUBLE ARROW -->
<!ENTITY searhk           "&#x02925;" ><!--SOUTH EAST ARROW WITH HOOK -->
<!ENTITY searr            "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY searrow          "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY sect             "&#x000A7;" ><!--SECTION SIGN -->
<!ENTITY semi             "&#x0003B;" ><!--SEMICOLON -->
<!ENTITY seswar           "&#x02929;" ><!--SOUTH EAST ARROW AND SOUTH WEST ARROW -->
<!ENTITY setminus         "&#x02216;" ><!--SET MINUS -->
<!ENTITY setmn            "&#x02216;" ><!--SET MINUS -->
<!ENTITY sext             "&#x02736;" ><!--SIX POINTED BLACK STAR -->
<!ENTITY sfr              "&#x1D530;" ><!--MATHEMATICAL FRAKTUR SMALL S -->
<!ENTITY sfrown           "&#x02322;" ><!--FROWN -->
<!ENTITY sharp            "&#x0266F;" ><!--MUSIC SHARP SIGN -->
<!ENTITY shchcy           "&#x00449;" ><!--CYRILLIC SMALL LETTER SHCHA -->
<!ENTITY shcy             "&#x00448;" ><!--CYRILLIC SMALL LETTER SHA -->
<!ENTITY shortmid         "&#x02223;" ><!--DIVIDES -->
<!ENTITY shortparallel    "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY shy              "&#x000AD;" ><!--SOFT HYPHEN -->
<!ENTITY sigma            "&#x003C3;" ><!--GREEK SMALL LETTER SIGMA -->
<!ENTITY sigmaf           "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY sigmav           "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY sim              "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY simdot           "&#x02A6A;" ><!--TILDE OPERATOR WITH DOT ABOVE -->
<!ENTITY sime             "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY simeq            "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY simg             "&#x02A9E;" ><!--SIMILAR OR GREATER-THAN -->
<!ENTITY simgE            "&#x02AA0;" ><!--SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN -->
<!ENTITY siml             "&#x02A9D;" ><!--SIMILAR OR LESS-THAN -->
<!ENTITY simlE            "&#x02A9F;" ><!--SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN -->
<!ENTITY simne            "&#x02246;" ><!--APPROXIMATELY BUT NOT ACTUALLY EQUAL TO -->
<!ENTITY simplus          "&#x02A24;" ><!--PLUS SIGN WITH TILDE ABOVE -->
<!ENTITY simrarr          "&#x02972;" ><!--TILDE OPERATOR ABOVE RIGHTWARDS ARROW -->
<!ENTITY slarr            "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY smallsetminus    "&#x02216;" ><!--SET MINUS -->
<!ENTITY smashp           "&#x02A33;" ><!--SMASH PRODUCT -->
<!ENTITY smeparsl         "&#x029E4;" ><!--EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE -->
<!ENTITY smid             "&#x02223;" ><!--DIVIDES -->
<!ENTITY smile            "&#x02323;" ><!--SMILE -->
<!ENTITY smt              "&#x02AAA;" ><!--SMALLER THAN -->
<!ENTITY smte             "&#x02AAC;" ><!--SMALLER THAN OR EQUAL TO -->
<!ENTITY smtes            "&#x02AAC;&#x0FE00;" ><!--SMALLER THAN OR slanted EQUAL -->
<!ENTITY softcy           "&#x0044C;" ><!--CYRILLIC SMALL LETTER SOFT SIGN -->
<!ENTITY sol              "&#x0002F;" ><!--SOLIDUS -->
<!ENTITY solb             "&#x029C4;" ><!--SQUARED RISING DIAGONAL SLASH -->
<!ENTITY solbar           "&#x0233F;" ><!--APL FUNCTIONAL SYMBOL SLASH BAR -->
<!ENTITY sopf             "&#x1D564;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL S -->
<!ENTITY spades           "&#x02660;" ><!--BLACK SPADE SUIT -->
<!ENTITY spadesuit        "&#x02660;" ><!--BLACK SPADE SUIT -->
<!ENTITY spar             "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY sqcap            "&#x02293;" ><!--SQUARE CAP -->
<!ENTITY sqcaps           "&#x02293;&#x0FE00;" ><!--SQUARE CAP with serifs -->
<!ENTITY sqcup            "&#x02294;" ><!--SQUARE CUP -->
<!ENTITY sqcups           "&#x02294;&#x0FE00;" ><!--SQUARE CUP with serifs -->
<!ENTITY sqsub            "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY sqsube           "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY sqsubset         "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY sqsubseteq       "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY sqsup            "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY sqsupe           "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY sqsupset         "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY sqsupseteq       "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY squ              "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY square           "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY squarf           "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY squf             "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY srarr            "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY sscr             "&#x1D4C8;" ><!--MATHEMATICAL SCRIPT SMALL S -->
<!ENTITY ssetmn           "&#x02216;" ><!--SET MINUS -->
<!ENTITY ssmile           "&#x02323;" ><!--SMILE -->
<!ENTITY sstarf           "&#x022C6;" ><!--STAR OPERATOR -->
<!ENTITY star             "&#x02606;" ><!--WHITE STAR -->
<!ENTITY starf            "&#x02605;" ><!--BLACK STAR -->
<!ENTITY straightepsilon  "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY straightphi      "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY strns            "&#x000AF;" ><!--MACRON -->
<!ENTITY sub              "&#x02282;" ><!--SUBSET OF -->
<!ENTITY subE             "&#x02AC5;" ><!--SUBSET OF ABOVE EQUALS SIGN -->
<!ENTITY subdot           "&#x02ABD;" ><!--SUBSET WITH DOT -->
<!ENTITY sube             "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY subedot          "&#x02AC3;" ><!--SUBSET OF OR EQUAL TO WITH DOT ABOVE -->
<!ENTITY submult          "&#x02AC1;" ><!--SUBSET WITH MULTIPLICATION SIGN BELOW -->
<!ENTITY subnE            "&#x02ACB;" ><!--SUBSET OF ABOVE NOT EQUAL TO -->
<!ENTITY subne            "&#x0228A;" ><!--SUBSET OF WITH NOT EQUAL TO -->
<!ENTITY subplus          "&#x02ABF;" ><!--SUBSET WITH PLUS SIGN BELOW -->
<!ENTITY subrarr          "&#x02979;" ><!--SUBSET ABOVE RIGHTWARDS ARROW -->
<!ENTITY subset           "&#x02282;" ><!--SUBSET OF -->
<!ENTITY subseteq         "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY subseteqq        "&#x02AC5;" ><!--SUBSET OF ABOVE EQUALS SIGN -->
<!ENTITY subsetneq        "&#x0228A;" ><!--SUBSET OF WITH NOT EQUAL TO -->
<!ENTITY subsetneqq       "&#x02ACB;" ><!--SUBSET OF ABOVE NOT EQUAL TO -->
<!ENTITY subsim           "&#x02AC7;" ><!--SUBSET OF ABOVE TILDE OPERATOR -->
<!ENTITY subsub           "&#x02AD5;" ><!--SUBSET ABOVE SUBSET -->
<!ENTITY subsup           "&#x02AD3;" ><!--SUBSET ABOVE SUPERSET -->
<!ENTITY succ             "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY succapprox       "&#x02AB8;" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
<!ENTITY succcurlyeq      "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY succeq           "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY succnapprox      "&#x02ABA;" ><!--SUCCEEDS ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY succneqq         "&#x02AB6;" ><!--SUCCEEDS ABOVE NOT EQUAL TO -->
<!ENTITY succnsim         "&#x022E9;" ><!--SUCCEEDS BUT NOT EQUIVALENT TO -->
<!ENTITY succsim          "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY sum              "&#x02211;" ><!--N-ARY SUMMATION -->
<!ENTITY sung             "&#x0266A;" ><!--EIGHTH NOTE -->
<!ENTITY sup              "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY sup1             "&#x000B9;" ><!--SUPERSCRIPT ONE -->
<!ENTITY sup2             "&#x000B2;" ><!--SUPERSCRIPT TWO -->
<!ENTITY sup3             "&#x000B3;" ><!--SUPERSCRIPT THREE -->
<!ENTITY supE             "&#x02AC6;" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
<!ENTITY supdot           "&#x02ABE;" ><!--SUPERSET WITH DOT -->
<!ENTITY supdsub          "&#x02AD8;" ><!--SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET -->
<!ENTITY supe             "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY supedot          "&#x02AC4;" ><!--SUPERSET OF OR EQUAL TO WITH DOT ABOVE -->
<!ENTITY suphsol          "&#x027C9;" ><!--SUPERSET PRECEDING SOLIDUS -->
<!ENTITY suphsub          "&#x02AD7;" ><!--SUPERSET BESIDE SUBSET -->
<!ENTITY suplarr          "&#x0297B;" ><!--SUPERSET ABOVE LEFTWARDS ARROW -->
<!ENTITY supmult          "&#x02AC2;" ><!--SUPERSET WITH MULTIPLICATION SIGN BELOW -->
<!ENTITY supnE            "&#x02ACC;" ><!--SUPERSET OF ABOVE NOT EQUAL TO -->
<!ENTITY supne            "&#x0228B;" ><!--SUPERSET OF WITH NOT EQUAL TO -->
<!ENTITY supplus          "&#x02AC0;" ><!--SUPERSET WITH PLUS SIGN BELOW -->
<!ENTITY supset           "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY supseteq         "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY supseteqq        "&#x02AC6;" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
<!ENTITY supsetneq        "&#x0228B;" ><!--SUPERSET OF WITH NOT EQUAL TO -->
<!ENTITY supsetneqq       "&#x02ACC;" ><!--SUPERSET OF ABOVE NOT EQUAL TO -->
<!ENTITY supsim           "&#x02AC8;" ><!--SUPERSET OF ABOVE TILDE OPERATOR -->
<!ENTITY supsub           "&#x02AD4;" ><!--SUPERSET ABOVE SUBSET -->
<!ENTITY supsup           "&#x02AD6;" ><!--SUPERSET ABOVE SUPERSET -->
<!ENTITY swArr            "&#x021D9;" ><!--SOUTH WEST DOUBLE ARROW -->
<!ENTITY swarhk           "&#x02926;" ><!--SOUTH WEST ARROW WITH HOOK -->
<!ENTITY swarr            "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY swarrow          "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY swnwar           "&#x0292A;" ><!--SOUTH WEST ARROW AND NORTH WEST ARROW -->
<!ENTITY szlig            "&#x000DF;" ><!--LATIN SMALL LETTER SHARP S -->
<!ENTITY target           "&#x02316;" ><!--POSITION INDICATOR -->
<!ENTITY tau              "&#x003C4;" ><!--GREEK SMALL LETTER TAU -->
<!ENTITY tbrk             "&#x023B4;" ><!--TOP SQUARE BRACKET -->
<!ENTITY tcaron           "&#x00165;" ><!--LATIN SMALL LETTER T WITH CARON -->
<!ENTITY tcedil           "&#x00163;" ><!--LATIN SMALL LETTER T WITH CEDILLA -->
<!ENTITY tcy              "&#x00442;" ><!--CYRILLIC SMALL LETTER TE -->
<!ENTITY tdot             " &#x020DB;" ><!--COMBINING THREE DOTS ABOVE -->
<!ENTITY telrec           "&#x02315;" ><!--TELEPHONE RECORDER -->
<!ENTITY tfr              "&#x1D531;" ><!--MATHEMATICAL FRAKTUR SMALL T -->
<!ENTITY there4           "&#x02234;" ><!--THEREFORE -->
<!ENTITY therefore        "&#x02234;" ><!--THEREFORE -->
<!ENTITY theta            "&#x003B8;" ><!--GREEK SMALL LETTER THETA -->
<!ENTITY thetasym         "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY thetav           "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY thickapprox      "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY thicksim         "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY thinsp           "&#x02009;" ><!--THIN SPACE -->
<!ENTITY thkap            "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY thksim           "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY thorn            "&#x000FE;" ><!--LATIN SMALL LETTER THORN -->
<!ENTITY tilde            "&#x002DC;" ><!--SMALL TILDE -->
<!ENTITY times            "&#x000D7;" ><!--MULTIPLICATION SIGN -->
<!ENTITY timesb           "&#x022A0;" ><!--SQUARED TIMES -->
<!ENTITY timesbar         "&#x02A31;" ><!--MULTIPLICATION SIGN WITH UNDERBAR -->
<!ENTITY timesd           "&#x02A30;" ><!--MULTIPLICATION SIGN WITH DOT ABOVE -->
<!ENTITY tint             "&#x0222D;" ><!--TRIPLE INTEGRAL -->
<!ENTITY toea             "&#x02928;" ><!--NORTH EAST ARROW AND SOUTH EAST ARROW -->
<!ENTITY top              "&#x022A4;" ><!--DOWN TACK -->
<!ENTITY topbot           "&#x02336;" ><!--APL FUNCTIONAL SYMBOL I-BEAM -->
<!ENTITY topcir           "&#x02AF1;" ><!--DOWN TACK WITH CIRCLE BELOW -->
<!ENTITY topf             "&#x1D565;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL T -->
<!ENTITY topfork          "&#x02ADA;" ><!--PITCHFORK WITH TEE TOP -->
<!ENTITY tosa             "&#x02929;" ><!--SOUTH EAST ARROW AND SOUTH WEST ARROW -->
<!ENTITY tprime           "&#x02034;" ><!--TRIPLE PRIME -->
<!ENTITY trade            "&#x02122;" ><!--TRADE MARK SIGN -->
<!ENTITY triangle         "&#x025B5;" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
<!ENTITY triangledown     "&#x025BF;" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY triangleleft     "&#x025C3;" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY trianglelefteq   "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY triangleq        "&#x0225C;" ><!--DELTA EQUAL TO -->
<!ENTITY triangleright    "&#x025B9;" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY trianglerighteq  "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY tridot           "&#x025EC;" ><!--WHITE UP-POINTING TRIANGLE WITH DOT -->
<!ENTITY trie             "&#x0225C;" ><!--DELTA EQUAL TO -->
<!ENTITY triminus         "&#x02A3A;" ><!--MINUS SIGN IN TRIANGLE -->
<!ENTITY triplus          "&#x02A39;" ><!--PLUS SIGN IN TRIANGLE -->
<!ENTITY trisb            "&#x029CD;" ><!--TRIANGLE WITH SERIFS AT BOTTOM -->
<!ENTITY tritime          "&#x02A3B;" ><!--MULTIPLICATION SIGN IN TRIANGLE -->
<!ENTITY trpezium         "&#x023E2;" ><!--WHITE TRAPEZIUM -->
<!ENTITY tscr             "&#x1D4C9;" ><!--MATHEMATICAL SCRIPT SMALL T -->
<!ENTITY tscy             "&#x00446;" ><!--CYRILLIC SMALL LETTER TSE -->
<!ENTITY tshcy            "&#x0045B;" ><!--CYRILLIC SMALL LETTER TSHE -->
<!ENTITY tstrok           "&#x00167;" ><!--LATIN SMALL LETTER T WITH STROKE -->
<!ENTITY twixt            "&#x0226C;" ><!--BETWEEN -->
<!ENTITY twoheadleftarrow "&#x0219E;" ><!--LEFTWARDS TWO HEADED ARROW -->
<!ENTITY twoheadrightarrow "&#x021A0;" ><!--RIGHTWARDS TWO HEADED ARROW -->
<!ENTITY uArr             "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY uHar             "&#x02963;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY uacute           "&#x000FA;" ><!--LATIN SMALL LETTER U WITH ACUTE -->
<!ENTITY uarr             "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY ubrcy            "&#x0045E;" ><!--CYRILLIC SMALL LETTER SHORT U -->
<!ENTITY ubreve           "&#x0016D;" ><!--LATIN SMALL LETTER U WITH BREVE -->
<!ENTITY ucirc            "&#x000FB;" ><!--LATIN SMALL LETTER U WITH CIRCUMFLEX -->
<!ENTITY ucy              "&#x00443;" ><!--CYRILLIC SMALL LETTER U -->
<!ENTITY udarr            "&#x021C5;" ><!--UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW -->
<!ENTITY udblac           "&#x00171;" ><!--LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
<!ENTITY udhar            "&#x0296E;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY ufisht           "&#x0297E;" ><!--UP FISH TAIL -->
<!ENTITY ufr              "&#x1D532;" ><!--MATHEMATICAL FRAKTUR SMALL U -->
<!ENTITY ugrave           "&#x000F9;" ><!--LATIN SMALL LETTER U WITH GRAVE -->
<!ENTITY uharl            "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY uharr            "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY uhblk            "&#x02580;" ><!--UPPER HALF BLOCK -->
<!ENTITY ulcorn           "&#x0231C;" ><!--TOP LEFT CORNER -->
<!ENTITY ulcorner         "&#x0231C;" ><!--TOP LEFT CORNER -->
<!ENTITY ulcrop           "&#x0230F;" ><!--TOP LEFT CROP -->
<!ENTITY ultri            "&#x025F8;" ><!--UPPER LEFT TRIANGLE -->
<!ENTITY umacr            "&#x0016B;" ><!--LATIN SMALL LETTER U WITH MACRON -->
<!ENTITY uml              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY uogon            "&#x00173;" ><!--LATIN SMALL LETTER U WITH OGONEK -->
<!ENTITY uopf             "&#x1D566;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL U -->
<!ENTITY uparrow          "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY updownarrow      "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY upharpoonleft    "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY upharpoonright   "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY uplus            "&#x0228E;" ><!--MULTISET UNION -->
<!ENTITY upsi             "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
<!ENTITY upsih            "&#x003D2;" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
<!ENTITY upsilon          "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
<!ENTITY upuparrows       "&#x021C8;" ><!--UPWARDS PAIRED ARROWS -->
<!ENTITY urcorn           "&#x0231D;" ><!--TOP RIGHT CORNER -->
<!ENTITY urcorner         "&#x0231D;" ><!--TOP RIGHT CORNER -->
<!ENTITY urcrop           "&#x0230E;" ><!--TOP RIGHT CROP -->
<!ENTITY uring            "&#x0016F;" ><!--LATIN SMALL LETTER U WITH RING ABOVE -->
<!ENTITY urtri            "&#x025F9;" ><!--UPPER RIGHT TRIANGLE -->
<!ENTITY uscr             "&#x1D4CA;" ><!--MATHEMATICAL SCRIPT SMALL U -->
<!ENTITY utdot            "&#x022F0;" ><!--UP RIGHT DIAGONAL ELLIPSIS -->
<!ENTITY utilde           "&#x00169;" ><!--LATIN SMALL LETTER U WITH TILDE -->
<!ENTITY utri             "&#x025B5;" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
<!ENTITY utrif            "&#x025B4;" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
<!ENTITY uuarr            "&#x021C8;" ><!--UPWARDS PAIRED ARROWS -->
<!ENTITY uuml             "&#x000FC;" ><!--LATIN SMALL LETTER U WITH DIAERESIS -->
<!ENTITY uwangle          "&#x029A7;" ><!--OBLIQUE ANGLE OPENING DOWN -->
<!ENTITY vArr             "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY vBar             "&#x02AE8;" ><!--SHORT UP TACK WITH UNDERBAR -->
<!ENTITY vBarv            "&#x02AE9;" ><!--SHORT UP TACK ABOVE SHORT DOWN TACK -->
<!ENTITY vDash            "&#x022A8;" ><!--TRUE -->
<!ENTITY vangrt           "&#x0299C;" ><!--RIGHT ANGLE VARIANT WITH SQUARE -->
<!ENTITY varepsilon       "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY varkappa         "&#x003F0;" ><!--GREEK KAPPA SYMBOL -->
<!ENTITY varnothing       "&#x02205;" ><!--EMPTY SET -->
<!ENTITY varphi           "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY varpi            "&#x003D6;" ><!--GREEK PI SYMBOL -->
<!ENTITY varpropto        "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY varr             "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY varrho           "&#x003F1;" ><!--GREEK RHO SYMBOL -->
<!ENTITY varsigma         "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY varsubsetneq     "&#x0228A;&#x0FE00;" ><!--SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsubsetneqq    "&#x02ACB;&#x0FE00;" ><!--SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsupsetneq     "&#x0228B;&#x0FE00;" ><!--SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsupsetneqq    "&#x02ACC;&#x0FE00;" ><!--SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vartheta         "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY vartriangleleft  "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY vartriangleright "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY vcy              "&#x00432;" ><!--CYRILLIC SMALL LETTER VE -->
<!ENTITY vdash            "&#x022A2;" ><!--RIGHT TACK -->
<!ENTITY vee              "&#x02228;" ><!--LOGICAL OR -->
<!ENTITY veebar           "&#x022BB;" ><!--XOR -->
<!ENTITY veeeq            "&#x0225A;" ><!--EQUIANGULAR TO -->
<!ENTITY vellip           "&#x022EE;" ><!--VERTICAL ELLIPSIS -->
<!ENTITY verbar           "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY vert             "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY vfr              "&#x1D533;" ><!--MATHEMATICAL FRAKTUR SMALL V -->
<!ENTITY vltri            "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY vnsub            "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY vnsup            "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY vopf             "&#x1D567;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL V -->
<!ENTITY vprop            "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY vrtri            "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY vscr             "&#x1D4CB;" ><!--MATHEMATICAL SCRIPT SMALL V -->
<!ENTITY vsubnE           "&#x02ACB;&#x0FE00;" ><!--SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsubne           "&#x0228A;&#x0FE00;" ><!--SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsupnE           "&#x02ACC;&#x0FE00;" ><!--SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsupne           "&#x0228B;&#x0FE00;" ><!--SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vzigzag          "&#x0299A;" ><!--VERTICAL ZIGZAG LINE -->
<!ENTITY wcirc            "&#x00175;" ><!--LATIN SMALL LETTER W WITH CIRCUMFLEX -->
<!ENTITY wedbar           "&#x02A5F;" ><!--LOGICAL AND WITH UNDERBAR -->
<!ENTITY wedge            "&#x02227;" ><!--LOGICAL AND -->
<!ENTITY wedgeq           "&#x02259;" ><!--ESTIMATES -->
<!ENTITY weierp           "&#x02118;" ><!--SCRIPT CAPITAL P -->
<!ENTITY wfr              "&#x1D534;" ><!--MATHEMATICAL FRAKTUR SMALL W -->
<!ENTITY wopf             "&#x1D568;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL W -->
<!ENTITY wp               "&#x02118;" ><!--SCRIPT CAPITAL P -->
<!ENTITY wr               "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY wreath           "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY wscr             "&#x1D4CC;" ><!--MATHEMATICAL SCRIPT SMALL W -->
<!ENTITY xcap             "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY xcirc            "&#x025EF;" ><!--LARGE CIRCLE -->
<!ENTITY xcup             "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY xdtri            "&#x025BD;" ><!--WHITE DOWN-POINTING TRIANGLE -->
<!ENTITY xfr              "&#x1D535;" ><!--MATHEMATICAL FRAKTUR SMALL X -->
<!ENTITY xhArr            "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY xharr            "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY xi               "&#x003BE;" ><!--GREEK SMALL LETTER XI -->
<!ENTITY xlArr            "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY xlarr            "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY xmap             "&#x027FC;" ><!--LONG RIGHTWARDS ARROW FROM BAR -->
<!ENTITY xnis             "&#x022FB;" ><!--CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY xodot            "&#x02A00;" ><!--N-ARY CIRCLED DOT OPERATOR -->
<!ENTITY xopf             "&#x1D569;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL X -->
<!ENTITY xoplus           "&#x02A01;" ><!--N-ARY CIRCLED PLUS OPERATOR -->
<!ENTITY xotime           "&#x02A02;" ><!--N-ARY CIRCLED TIMES OPERATOR -->
<!ENTITY xrArr            "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY xrarr            "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY xscr             "&#x1D4CD;" ><!--MATHEMATICAL SCRIPT SMALL X -->
<!ENTITY xsqcup           "&#x02A06;" ><!--N-ARY SQUARE UNION OPERATOR -->
<!ENTITY xuplus           "&#x02A04;" ><!--N-ARY UNION OPERATOR WITH PLUS -->
<!ENTITY xutri            "&#x025B3;" ><!--WHITE UP-POINTING TRIANGLE -->
<!ENTITY xvee             "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY xwedge           "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY yacute           "&#x000FD;" ><!--LATIN SMALL LETTER Y WITH ACUTE -->
<!ENTITY yacy             "&#x0044F;" ><!--CYRILLIC SMALL LETTER YA -->
<!ENTITY ycirc            "&#x00177;" ><!--LATIN SMALL LETTER Y WITH CIRCUMFLEX -->
<!ENTITY ycy              "&#x0044B;" ><!--CYRILLIC SMALL LETTER YERU -->
<!ENTITY yen              "&#x000A5;" ><!--YEN SIGN -->
<!ENTITY yfr              "&#x1D536;" ><!--MATHEMATICAL FRAKTUR SMALL Y -->
<!ENTITY yicy             "&#x00457;" ><!--CYRILLIC SMALL LETTER YI -->
<!ENTITY yopf             "&#x1D56A;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Y -->
<!ENTITY yscr             "&#x1D4CE;" ><!--MATHEMATICAL SCRIPT SMALL Y -->
<!ENTITY yucy             "&#x0044E;" ><!--CYRILLIC SMALL LETTER YU -->
<!ENTITY yuml             "&#x000FF;" ><!--LATIN SMALL LETTER Y WITH DIAERESIS -->
<!ENTITY zacute           "&#x0017A;" ><!--LATIN SMALL LETTER Z WITH ACUTE -->
<!ENTITY zcaron           "&#x0017E;" ><!--LATIN SMALL LETTER Z WITH CARON -->
<!ENTITY zcy              "&#x00437;" ><!--CYRILLIC SMALL LETTER ZE -->
<!ENTITY zdot             "&#x0017C;" ><!--LATIN SMALL LETTER Z WITH DOT ABOVE -->
<!ENTITY zeetrf           "&#x02128;" ><!--BLACK-LETTER CAPITAL Z -->
<!ENTITY zeta             "&#x003B6;" ><!--GREEK SMALL LETTER ZETA -->
<!ENTITY zfr              "&#x1D537;" ><!--MATHEMATICAL FRAKTUR SMALL Z -->
<!ENTITY zhcy             "&#x00436;" ><!--CYRILLIC SMALL LETTER ZHE -->
<!ENTITY zigrarr          "&#x021DD;" ><!--RIGHTWARDS SQUIGGLE ARROW -->
<!ENTITY zopf             "&#x1D56B;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Z -->
<!ENTITY zscr             "&#x1D4CF;" ><!--MATHEMATICAL SCRIPT SMALL Z -->
<!ENTITY zwj              "&#x0200D;" ><!--ZERO WIDTH JOINER -->
<!ENTITY zwnj             "&#x0200C;" ><!--ZERO WIDTH NON-JOINER -->
PK
!<Ukkres/html/folder.pngPNG


IHDRatIME 4	pHYsiTSgAMAaIDATxSAkA&iӦhkxQ,_<Hłɛ`҂ׂoB)AXlb͒%IQѓ|3ޛ73%PkL+i`1/“+ⅵVF,C+>7CNfO3mqDҭH93kE^͕Ѫ =5loYw7V&\g}@2>/)x7\}2ˉ"YX.2v؎!S8 e+@`ϛHO
Tw6(IiXAs+,\!Qc7ޣ)0D-!5yNOb] ٣V1{
0TǹHak	ķVTA	LI 4	f
d84	q]Gݨ`c;PV5/m1y]f).pH۫oȾ6 <,=ʳIENDB`PK
!<N
res/language.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

aa.accept     = true
ab.accept     = true
ae.accept     = true
af.accept     = true
ak.accept     = true
am.accept     = true
an.accept     = true
ar.accept     = true
ar-ae.accept  = true
ar-bh.accept  = true
ar-dz.accept  = true
ar-eg.accept  = true
ar-iq.accept  = true
ar-jo.accept  = true
ar-kw.accept  = true
ar-lb.accept  = true
ar-ly.accept  = true
ar-ma.accept  = true
ar-om.accept  = true
ar-qa.accept  = true
ar-sa.accept  = true
ar-sy.accept  = true
ar-tn.accept  = true
ar-ye.accept  = true
as.accept     = true
ast.accept    = true
av.accept     = true
ay.accept     = true
az.accept     = true
ba.accept     = true
be.accept     = true
bg.accept     = true
bh.accept     = true
bi.accept     = true
bm.accept     = true
bn.accept     = true
bo.accept     = true
br.accept     = true
bs.accept     = true
ca.accept     = true
ce.accept     = true
ch.accept     = true
co.accept     = true
cr.accept     = true
cs.accept     = true
csb.accept    = true
cu.accept     = true
cv.accept     = true
cy.accept     = true
da.accept     = true
de.accept     = true
de-at.accept  = true
de-ch.accept  = true
de-de.accept  = true
de-li.accept  = true
de-lu.accept  = true
dsb.accept    = true
dv.accept     = true
dz.accept     = true
ee.accept     = true
el.accept     = true
en.accept     = true
en-au.accept  = true
en-bz.accept  = true
en-ca.accept  = true
en-gb.accept  = true
en-ie.accept  = true
en-jm.accept  = true
en-nz.accept  = true
en-ph.accept  = true
en-tt.accept  = true
en-us.accept  = true
en-za.accept  = true
en-zw.accept  = true
eo.accept     = true
es.accept     = true
es-ar.accept  = true
es-bo.accept  = true
es-cl.accept  = true
es-co.accept  = true
es-cr.accept  = true
es-do.accept  = true
es-ec.accept  = true
es-es.accept  = true
es-gt.accept  = true
es-hn.accept  = true
es-mx.accept  = true
es-ni.accept  = true
es-pa.accept  = true
es-pe.accept  = true
es-pr.accept  = true
es-py.accept  = true
es-sv.accept  = true
es-uy.accept  = true
es-ve.accept  = true
et.accept     = true
eu.accept     = true
fa.accept     = true
fa-ir.accept  = true
ff.accept     = true
fi.accept     = true
fj.accept     = true
fo.accept     = true
fr.accept     = true
fr-be.accept  = true
fr-ca.accept  = true
fr-ch.accept  = true
fr-fr.accept  = true
fr-lu.accept  = true
fr-mc.accept  = true
fur.accept    = true
fy.accept     = true
ga.accept     = true
gd.accept     = true
gl.accept     = true
gn.accept     = true
gu.accept     = true
gv.accept     = true
ha.accept     = true
haw.accept    = true
he.accept     = true
hi.accept     = true
hil.accept    = true
ho.accept     = true
hsb.accept    = true
hr.accept     = true
ht.accept     = true
hu.accept     = true
hy.accept     = true
hz.accept     = true
ia.accept     = true
id.accept     = true
ie.accept     = true
ig.accept     = true
ii.accept     = true
ik.accept     = true
io.accept     = true
is.accept     = true
it.accept     = true
it-ch.accept  = true
iu.accept     = true
ja.accept     = true
jv.accept     = true
ka.accept     = true
kg.accept     = true
ki.accept     = true
kk.accept     = true
kl.accept     = true
km.accept     = true
kn.accept     = true
ko.accept     = true
ko-kp.accept  = true
ko-kr.accept  = true
kok.accept    = true
kr.accept     = true
ks.accept     = true
ku.accept     = true
kv.accept     = true
kw.accept     = true
ky.accept     = true
la.accept     = true
lb.accept     = true
lg.accept     = true
li.accept     = true
ln.accept     = true
lo.accept     = true
lt.accept     = true
lu.accept     = true
lv.accept     = true
mg.accept     = true
mh.accept     = true
mi.accept     = true
mk.accept     = true
mk-mk.accept  = true
ml.accept     = true
mn.accept     = true
mr.accept     = true
ms.accept     = true
mt.accept     = true
my.accept     = true
na.accept     = true
nb.accept     = true
nd.accept     = true
ne.accept     = true
ng.accept     = true
nl.accept     = true
nl-be.accept  = true
nn.accept     = true
no.accept     = true
nr.accept     = true
nso.accept    = true
nv.accept     = true
ny.accept     = true
oc.accept     = true
oj.accept     = true
om.accept     = true
or.accept     = true
os.accept     = true
pa.accept     = true
pa-in.accept  = true
pa-pk.accept  = true
pi.accept     = true
pl.accept     = true
ps.accept     = true
pt.accept     = true
pt-br.accept  = true
qu.accept     = true
rm.accept     = true
rn.accept     = true
ro.accept     = true
ro-md.accept  = true
ro-ro.accept  = true
ru.accept     = true
ru-md.accept  = true
rw.accept     = true
sa.accept     = true
sc.accept     = true
sd.accept     = true
sg.accept     = true
si.accept     = true
sk.accept     = true
sl.accept     = true
sm.accept     = true
so.accept     = true
son-ml.accept = true
sq.accept     = true
sr.accept     = true
ss.accept     = true
st.accept     = true
su.accept     = true
sv.accept     = true
sv-fi.accept  = true
sv-se.accept  = true
sw.accept     = true
ta.accept     = true
te.accept     = true
tg.accept     = true
th.accept     = true
ti.accept     = true
tig.accept    = true
tk.accept     = true
tl.accept     = true
tlh.accept    = true
tn.accept     = true
to.accept     = true
tr.accept     = true
ts.accept     = true
tt.accept     = true
tw.accept     = true
ty.accept     = true
ug.accept     = true
uk.accept     = true
ur.accept     = true
uz.accept     = true
ve.accept     = true
vi.accept     = true
vo.accept     = true
wa.accept     = true
wo.accept     = true
xh.accept     = true
yi.accept     = true
yo.accept     = true
za.accept     = true
zh.accept     = true
zh-cn.accept  = true
zh-hk.accept  = true
zh-sg.accept  = true
zh-tw.accept  = true
zu.accept     = true
PK
!<dQ(res/entityTables/html40Latin1.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 names that assign html elements to
#  ascii codepoints. Do not translate any of the "entities" in
#  this file.

entity.list.name=html40Latin1
entity.160=&nbsp;
entity.161=&iexcl;
entity.162=&cent;
entity.163=&pound;
entity.164=&curren;
entity.165=&yen;
entity.166=&brvbar;
entity.167=&sect;
entity.168=&uml;
entity.169=&copy;
entity.170=&ordf;
entity.171=&laquo;
entity.172=&not;
entity.173=&shy;
entity.174=&reg;
entity.175=&macr;
entity.176=&deg;
entity.177=&plusmn;
entity.178=&sup2;
entity.179=&sup3;
entity.180=&acute;
entity.181=&micro;
entity.182=&para;
entity.183=&middot;
entity.184=&cedil;
entity.185=&sup1;
entity.186=&ordm;
entity.187=&raquo;
entity.188=&frac14;
entity.189=&frac12;
entity.190=&frac34;
entity.191=&iquest;
entity.192=&Agrave;
entity.193=&Aacute;
entity.194=&Acirc;
entity.195=&Atilde;
entity.196=&Auml;
entity.197=&Aring;
entity.198=&AElig;
entity.199=&Ccedil;
entity.200=&Egrave;
entity.201=&Eacute;
entity.202=&Ecirc;
entity.203=&Euml;
entity.204=&Igrave;
entity.205=&Iacute;
entity.206=&Icirc;
entity.207=&Iuml;
entity.208=&ETH;
entity.209=&Ntilde;
entity.210=&Ograve;
entity.211=&Oacute;
entity.212=&Ocirc;
entity.213=&Otilde;
entity.214=&Ouml;
entity.215=&times;
entity.216=&Oslash;
entity.217=&Ugrave;
entity.218=&Uacute;
entity.219=&Ucirc;
entity.220=&Uuml;
entity.221=&Yacute;
entity.222=&THORN;
entity.223=&szlig;
entity.224=&agrave;
entity.225=&aacute;
entity.226=&acirc;
entity.227=&atilde;
entity.228=&auml;
entity.229=&aring;
entity.230=&aelig;
entity.231=&ccedil;
entity.232=&egrave;
entity.233=&eacute;
entity.234=&ecirc;
entity.235=&euml;
entity.236=&igrave;
entity.237=&iacute;
entity.238=&icirc;
entity.239=&iuml;
entity.240=&eth;
entity.241=&ntilde;
entity.242=&ograve;
entity.243=&oacute;
entity.244=&ocirc;
entity.245=&otilde;
entity.246=&ouml;
entity.247=&divide;
entity.248=&oslash;
entity.249=&ugrave;
entity.250=&uacute;
entity.251=&ucirc;
entity.252=&uuml;
entity.253=&yacute;
entity.254=&thorn;
entity.255=&yuml;








PK
!<aa)res/entityTables/html40Special.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 names that assign entity references to
#	numbers in an entity.list object. Do not translate this file.

entity.list.name=html40Special
entity.338=&OElig;
entity.339=&oelig;
entity.352=&Scaron;
entity.353=&scaron;
entity.376=&Yuml;
entity.710=&circ;
entity.732=&tilde;
entity.8194=&ensp;
entity.8195=&emsp;
entity.8201=&thinsp;
entity.8204=&zwnj;
entity.8205=&zwj;
entity.8206=&lrm;
entity.8207=&rlm;
entity.8211=&ndash;
entity.8212=&mdash;
entity.8216=&lsquo;
entity.8217=&rsquo;
entity.8218=&sbquo;
entity.8220=&ldquo;
entity.8221=&rdquo;
entity.8222=&bdquo;
entity.8224=&dagger;
entity.8225=&Dagger;
entity.8240=&permil;
entity.8249=&lsaquo;
entity.8250=&rsaquo;
entity.8364=&euro;
PK
!<ID
D
)res/entityTables/html40Symbols.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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
# Do not translate this file.

entity.list.name=html40Symbols
entity.402=&fnof;
entity.913=&Alpha;
entity.914=&Beta;
entity.915=&Gamma;
entity.916=&Delta;
entity.917=&Epsilon;
entity.918=&Zeta;
entity.919=&Eta;
entity.920=&Theta;
entity.921=&Iota;
entity.922=&Kappa;
entity.923=&Lambda;
entity.924=&Mu;
entity.925=&Nu;
entity.926=&Xi;
entity.927=&Omicron;
entity.928=&Pi;
entity.929=&Rho;
entity.931=&Sigma;
entity.932=&Tau;
entity.933=&Upsilon;
entity.934=&Phi;
entity.935=&Chi;
entity.936=&Psi;
entity.937=&Omega;
entity.945=&alpha;
entity.946=&beta;
entity.947=&gamma;
entity.948=&delta;
entity.949=&epsilon;
entity.950=&zeta;
entity.951=&eta;
entity.952=&theta;
entity.953=&iota;
entity.954=&kappa;
entity.955=&lambda;
entity.956=&mu;
entity.957=&nu;
entity.958=&xi;
entity.959=&omicron;
entity.960=&pi;
entity.961=&rho;
entity.962=&sigmaf;
entity.963=&sigma;
entity.964=&tau;
entity.965=&upsilon;
entity.966=&phi;
entity.967=&chi;
entity.968=&psi;
entity.969=&omega;
entity.977=&thetasym;
entity.978=&upsih;
entity.982=&piv;
entity.8226=&bull;
entity.8230=&hellip;
entity.8242=&prime;
entity.8243=&Prime;
entity.8254=&oline;
entity.8260=&frasl;
entity.8472=&weierp;
entity.8465=&image;
entity.8476=&real;
entity.8482=&trade;
entity.8501=&alefsym;
entity.8592=&larr;
entity.8593=&uarr;
entity.8594=&rarr;
entity.8595=&darr;
entity.8596=&harr;
entity.8629=&crarr;
entity.8656=&lArr;
entity.8657=&uArr;
entity.8658=&rArr;
entity.8659=&dArr;
entity.8660=&hArr;
entity.8704=&forall;
entity.8706=&part;
entity.8707=&exist;
entity.8709=&empty;
entity.8711=&nabla;
entity.8712=&isin;
entity.8713=&notin;
entity.8715=&ni;
entity.8719=&prod;
entity.8721=&sum;
entity.8722=&minus;
entity.8727=&lowast;
entity.8730=&radic;
entity.8733=&prop;
entity.8734=&infin;
entity.8736=&ang;
entity.8743=&and;
entity.8744=&or;
entity.8745=&cap;
entity.8746=&cup;
entity.8747=&int;
entity.8756=&there4;
entity.8764=&sim;
entity.8773=&cong;
entity.8776=&asymp;
entity.8800=&ne;
entity.8801=&equiv;
entity.8804=&le;
entity.8805=&ge;
entity.8834=&sub;
entity.8835=&sup;
entity.8836=&nsub;
entity.8838=&sube;
entity.8839=&supe;
entity.8853=&oplus;
entity.8855=&otimes;
entity.8869=&perp;
entity.8901=&sdot;
entity.8968=&lceil;
entity.8969=&rceil;
entity.8970=&lfloor;
entity.8971=&rfloor;
entity.9001=&lang;
entity.9002=&rang;
entity.9674=&loz;
entity.9824=&spades;
entity.9827=&clubs;
entity.9829=&hearts;
entity.9830=&diams;
PK
!<
WVoVo$res/entityTables/mathml20.properties# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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
#  Do not translate this file.
#  The file contains a mapping of Unicode values to MathML entity names.
#  Entities already listed in html40 were deliberately excluded. So you must use
#  nsIEntityConverter.html40 + nsIEntityConverter.mathml20 to access everything.

entity.list.name=mathml20
entity.168=&DoubleDot;
entity.175=&OverBar;
entity.177=&PlusMinus;
entity.184=&Cedilla;
entity.256=&Amacr;
entity.257=&amacr;
entity.258=&Abreve;
entity.259=&abreve;
entity.260=&Aogon;
entity.261=&aogon;
entity.262=&Cacute;
entity.263=&cacute;
entity.264=&Ccirc;
entity.265=&ccirc;
entity.266=&Cdot;
entity.267=&cdot;
entity.268=&Ccaron;
entity.269=&ccaron;
entity.270=&Dcaron;
entity.271=&dcaron;
entity.272=&Dstrok;
entity.273=&dstrok;
entity.274=&Emacr;
entity.275=&emacr;
entity.278=&Edot;
entity.279=&edot;
entity.280=&Eogon;
entity.281=&eogon;
entity.282=&Ecaron;
entity.283=&ecaron;
entity.284=&Gcirc;
entity.285=&gcirc;
entity.286=&Gbreve;
entity.287=&gbreve;
entity.288=&Gdot;
entity.289=&gdot;
entity.290=&Gcedil;
entity.292=&Hcirc;
entity.293=&hcirc;
entity.294=&Hstrok;
entity.295=&hstrok;
entity.296=&Itilde;
entity.297=&itilde;
entity.298=&Imacr;
entity.299=&imacr;
entity.302=&Iogon;
entity.303=&iogon;
entity.304=&Idot;
entity.306=&IJlig;
entity.307=&ijlig;
entity.308=&Jcirc;
entity.309=&jcirc;
entity.310=&Kcedil;
entity.311=&kcedil;
entity.312=&kgreen;
entity.313=&Lacute;
entity.314=&lacute;
entity.315=&Lcedil;
entity.316=&lcedil;
entity.317=&Lcaron;
entity.318=&lcaron;
entity.319=&Lmidot;
entity.320=&lmidot;
entity.321=&Lstrok;
entity.322=&lstrok;
entity.323=&Nacute;
entity.324=&nacute;
entity.325=&Ncedil;
entity.326=&ncedil;
entity.327=&Ncaron;
entity.328=&ncaron;
entity.329=&napos;
entity.330=&ENG;
entity.331=&eng;
entity.332=&Omacr;
entity.333=&omacr;
entity.336=&Odblac;
entity.337=&odblac;
entity.340=&Racute;
entity.341=&racute;
entity.342=&Rcedil;
entity.343=&rcedil;
entity.344=&Rcaron;
entity.345=&rcaron;
entity.346=&Sacute;
entity.347=&sacute;
entity.348=&Scirc;
entity.349=&scirc;
entity.350=&Scedil;
entity.351=&scedil;
entity.354=&Tcedil;
entity.355=&tcedil;
entity.356=&Tcaron;
entity.357=&tcaron;
entity.358=&Tstrok;
entity.359=&tstrok;
entity.360=&Utilde;
entity.361=&utilde;
entity.362=&Umacr;
entity.363=&umacr;
entity.364=&Ubreve;
entity.365=&ubreve;
entity.366=&Uring;
entity.367=&uring;
entity.368=&Udblac;
entity.369=&udblac;
entity.370=&Uogon;
entity.371=&uogon;
entity.372=&Wcirc;
entity.373=&wcirc;
entity.374=&Ycirc;
entity.375=&ycirc;
entity.377=&Zacute;
entity.378=&zacute;
entity.379=&Zdot;
entity.380=&zdot;
entity.381=&Zcaron;
entity.382=&zcaron;
entity.437=&imped;
entity.501=&gacute;
entity.711=&Hacek;
entity.728=&Breve;
entity.729=&DiacriticalDot;
entity.730=&ring;
entity.731=&ogon;
entity.732=&DiacriticalTilde;
entity.733=&DiacriticalDoubleAcute;
entity.785=&DownBreve;
entity.818=&UnderBar;
entity.949=&varepsilon;
entity.962=&varsigma;
entity.966=&varphi;
entity.977=&vartheta;
entity.978=&Upsi;
entity.981=&straightphi;
entity.982=&varpi;
entity.988=&Gammad;
entity.989=&digamma;
entity.1008=&varkappa;
entity.1009=&varrho;
entity.1013=&straightepsilon;
entity.1014=&backepsilon;
entity.1025=&IOcy;
entity.1026=&DJcy;
entity.1027=&GJcy;
entity.1028=&Jukcy;
entity.1029=&DScy;
entity.1030=&Iukcy;
entity.1031=&YIcy;
entity.1032=&Jsercy;
entity.1033=&LJcy;
entity.1034=&NJcy;
entity.1035=&TSHcy;
entity.1036=&KJcy;
entity.1038=&Ubrcy;
entity.1039=&DZcy;
entity.1040=&Acy;
entity.1041=&Bcy;
entity.1042=&Vcy;
entity.1043=&Gcy;
entity.1044=&Dcy;
entity.1045=&IEcy;
entity.1046=&ZHcy;
entity.1047=&Zcy;
entity.1048=&Icy;
entity.1049=&Jcy;
entity.1050=&Kcy;
entity.1051=&Lcy;
entity.1052=&Mcy;
entity.1053=&Ncy;
entity.1054=&Ocy;
entity.1055=&Pcy;
entity.1056=&Rcy;
entity.1057=&Scy;
entity.1058=&Tcy;
entity.1059=&Ucy;
entity.1060=&Fcy;
entity.1061=&KHcy;
entity.1062=&TScy;
entity.1063=&CHcy;
entity.1064=&SHcy;
entity.1065=&SHCHcy;
entity.1066=&HARDcy;
entity.1067=&Ycy;
entity.1068=&SOFTcy;
entity.1069=&Ecy;
entity.1070=&YUcy;
entity.1071=&YAcy;
entity.1072=&acy;
entity.1073=&bcy;
entity.1074=&vcy;
entity.1075=&gcy;
entity.1076=&dcy;
entity.1077=&iecy;
entity.1078=&zhcy;
entity.1079=&zcy;
entity.1080=&icy;
entity.1081=&jcy;
entity.1082=&kcy;
entity.1083=&lcy;
entity.1084=&mcy;
entity.1085=&ncy;
entity.1086=&ocy;
entity.1087=&pcy;
entity.1088=&rcy;
entity.1089=&scy;
entity.1090=&tcy;
entity.1091=&ucy;
entity.1092=&fcy;
entity.1093=&khcy;
entity.1094=&tscy;
entity.1095=&chcy;
entity.1096=&shcy;
entity.1097=&shchcy;
entity.1098=&hardcy;
entity.1099=&ycy;
entity.1100=&softcy;
entity.1101=&ecy;
entity.1102=&yucy;
entity.1103=&yacy;
entity.1105=&iocy;
entity.1106=&djcy;
entity.1107=&gjcy;
entity.1108=&jukcy;
entity.1109=&dscy;
entity.1110=&iukcy;
entity.1111=&yicy;
entity.1112=&jsercy;
entity.1113=&ljcy;
entity.1114=&njcy;
entity.1115=&tshcy;
entity.1116=&kjcy;
entity.1118=&ubrcy;
entity.1119=&dzcy;
entity.8196=&emsp13;
entity.8197=&emsp14;
entity.8199=&numsp;
entity.8200=&puncsp;
entity.8201=&ThinSpace;
entity.8202=&VeryThinSpace;
entity.8203=&ZeroWidthSpace;
entity.8208=&hyphen;
entity.8213=&horbar;
entity.8214=&Verbar;
entity.8216=&OpenCurlyQuote;
entity.8217=&CloseCurlyQuote;
entity.8218=&lsquor;
entity.8220=&OpenCurlyDoubleQuote;
entity.8221=&CloseCurlyDoubleQuote;
entity.8222=&ldquor;
entity.8226=&bullet;
entity.8229=&nldr;
entity.8241=&pertenk;
entity.8244=&tprime;
entity.8245=&backprime;
entity.8257=&caret;
entity.8259=&hybull;
entity.8271=&bsemi;
entity.8279=&qprime;
entity.8287=&MediumSpace;
entity.8288=&NoBreak;
entity.8289=&ApplyFunction;
entity.8290=&InvisibleTimes;
entity.8291=&InvisibleComma;
entity.8411=&TripleDot;
entity.8412=&DotDot;
entity.8450=&Copf;
entity.8453=&incare;
entity.8458=&gscr;
entity.8459=&HilbertSpace;
entity.8460=&Poincareplane;
entity.8461=&Hopf;
entity.8462=&planckh;
entity.8463=&hslash;
entity.8464=&Iscr;
entity.8465=&Ifr;
entity.8466=&Laplacetrf;
entity.8467=&ell;
entity.8469=&Nopf;
entity.8470=&numero;
entity.8471=&copysr;
entity.8473=&Popf;
entity.8474=&Qopf;
entity.8475=&Rscr;
entity.8476=&Rfr;
entity.8477=&Ropf;
entity.8478=&rx;
entity.8484=&Zopf;
entity.8486=&ohm;
entity.8487=&mho;
entity.8488=&Zfr;
entity.8489=&iiota;
entity.8491=&angst;
entity.8492=&Bernoullis;
entity.8493=&Cayleys;
entity.8495=&escr;
entity.8496=&Escr;
entity.8497=&Fouriertrf;
entity.8499=&Mellintrf;
entity.8500=&orderof;
entity.8501=&aleph;
entity.8502=&beth;
entity.8503=&gimel;
entity.8504=&daleth;
entity.8517=&CapitalDifferentialD;
entity.8518=&DifferentialD;
entity.8519=&ExponentialE;
entity.8520=&ImaginaryI;
entity.8531=&frac13;
entity.8532=&frac23;
entity.8533=&frac15;
entity.8534=&frac25;
entity.8535=&frac35;
entity.8536=&frac45;
entity.8537=&frac16;
entity.8538=&frac56;
entity.8539=&frac18;
entity.8540=&frac38;
entity.8541=&frac58;
entity.8542=&frac78;
entity.8592=&LeftArrow;
entity.8593=&UpArrow;
entity.8594=&RightArrow;
entity.8595=&DownArrow;
entity.8596=&LeftRightArrow;
entity.8597=&UpDownArrow;
entity.8598=&UpperLeftArrow;
entity.8599=&UpperRightArrow;
entity.8600=&LowerRightArrow;
entity.8601=&LowerLeftArrow;
entity.8602=&nleftarrow;
entity.8603=&nrightarrow;
entity.8605=&rightsquigarrow;
entity.8606=&Larr;
entity.8607=&Uarr;
entity.8608=&Rarr;
entity.8609=&Darr;
entity.8610=&leftarrowtail;
entity.8611=&rightarrowtail;
entity.8612=&LeftTeeArrow;
entity.8613=&UpTeeArrow;
entity.8614=&RightTeeArrow;
entity.8615=&DownTeeArrow;
entity.8617=&hookleftarrow;
entity.8618=&hookrightarrow;
entity.8619=&looparrowleft;
entity.8620=&looparrowright;
entity.8621=&leftrightsquigarrow;
entity.8622=&nleftrightarrow;
entity.8624=&Lsh;
entity.8625=&Rsh;
entity.8626=&ldsh;
entity.8627=&rdsh;
entity.8630=&curvearrowleft;
entity.8631=&curvearrowright;
entity.8634=&circlearrowleft;
entity.8635=&circlearrowright;
entity.8636=&LeftVector;
entity.8637=&DownLeftVector;
entity.8638=&RightUpVector;
entity.8639=&LeftUpVector;
entity.8640=&RightVector;
entity.8641=&DownRightVector;
entity.8642=&RightDownVector;
entity.8643=&LeftDownVector;
entity.8644=&RightArrowLeftArrow;
entity.8645=&UpArrowDownArrow;
entity.8646=&LeftArrowRightArrow;
entity.8647=&leftleftarrows;
entity.8648=&upuparrows;
entity.8649=&rightrightarrows;
entity.8650=&downdownarrows;
entity.8651=&ReverseEquilibrium;
entity.8652=&Equilibrium;
entity.8653=&nLeftarrow;
entity.8654=&nLeftrightarrow;
entity.8655=&nRightarrow;
entity.8656=&DoubleLeftArrow;
entity.8657=&DoubleUpArrow;
entity.8658=&DoubleRightArrow;
entity.8659=&DoubleDownArrow;
entity.8660=&DoubleLeftRightArrow;
entity.8661=&DoubleUpDownArrow;
entity.8662=&nwArr;
entity.8663=&neArr;
entity.8664=&seArr;
entity.8665=&swArr;
entity.8666=&Lleftarrow;
entity.8667=&Rrightarrow;
entity.8669=&zigrarr;
entity.8676=&LeftArrowBar;
entity.8677=&RightArrowBar;
entity.8693=&DownArrowUpArrow;
entity.8701=&loarr;
entity.8702=&roarr;
entity.8703=&hoarr;
entity.8704=&ForAll;
entity.8705=&complement;
entity.8706=&PartialD;
entity.8707=&Exists;
entity.8708=&NotExists;
entity.8709=&emptyset;
entity.8711=&Del;
entity.8712=&Element;
entity.8713=&NotElement;
entity.8715=&ReverseElement;
entity.8716=&NotReverseElement;
entity.8719=&Product;
entity.8720=&Coproduct;
entity.8721=&Sum;
entity.8723=&MinusPlus;
entity.8724=&dotplus;
entity.8726=&Backslash;
entity.8728=&SmallCircle;
entity.8730=&Sqrt;
entity.8733=&Proportional;
entity.8735=&angrt;
entity.8736=&angle;
entity.8737=&measuredangle;
entity.8738=&angsph;
entity.8739=&VerticalBar;
entity.8740=&NotVerticalBar;
entity.8741=&DoubleVerticalBar;
entity.8742=&NotDoubleVerticalBar;
entity.8743=&wedge;
entity.8744=&vee;
entity.8747=&Integral;
entity.8748=&Int;
entity.8749=&iiint;
entity.8750=&ContourIntegral;
entity.8751=&DoubleContourIntegral;
entity.8752=&Cconint;
entity.8753=&cwint;
entity.8754=&ClockwiseContourIntegral;
entity.8755=&CounterClockwiseContourIntegral;
entity.8756=&Therefore;
entity.8757=&Because;
entity.8758=&ratio;
entity.8759=&Proportion;
entity.8760=&dotminus;
entity.8762=&mDDot;
entity.8763=&homtht;
entity.8764=&Tilde;
entity.8765=&backsim;
entity.8766=&mstpos;
entity.8767=&acd;
entity.8768=&VerticalTilde;
entity.8769=&NotTilde;
entity.8770=&EqualTilde;
entity.8771=&TildeEqual;
entity.8772=&NotTildeEqual;
entity.8773=&TildeFullEqual;
entity.8774=&simne;
entity.8775=&NotTildeFullEqual;
entity.8776=&TildeTilde;
entity.8777=&NotTildeTilde;
entity.8778=&approxeq;
entity.8779=&apid;
entity.8780=&backcong;
entity.8781=&CupCap;
entity.8782=&HumpDownHump;
entity.8783=&HumpEqual;
entity.8784=&DotEqual;
entity.8785=&doteqdot;
entity.8786=&fallingdotseq;
entity.8787=&risingdotseq;
entity.8788=&Assign;
entity.8789=&eqcolon;
entity.8790=&eqcirc;
entity.8791=&circeq;
entity.8793=&wedgeq;
entity.8794=&veeeq;
entity.8796=&triangleq;
entity.8799=&questeq;
entity.8800=&NotEqual;
entity.8801=&Congruent;
entity.8802=&NotCongruent;
entity.8804=&leq;
entity.8805=&GreaterEqual;
entity.8806=&LessFullEqual;
entity.8807=&GreaterFullEqual;
entity.8808=&lneqq;
entity.8809=&gneqq;
entity.8810=&NestedLessLess;
entity.8811=&NestedGreaterGreater;
entity.8812=&between;
entity.8813=&NotCupCap;
entity.8814=&NotLess;
entity.8815=&NotGreater;
entity.8816=&NotLessEqual;
entity.8817=&NotGreaterEqual;
entity.8818=&LessTilde;
entity.8819=&GreaterTilde;
entity.8820=&NotLessTilde;
entity.8821=&NotGreaterTilde;
entity.8822=&LessGreater;
entity.8823=&GreaterLess;
entity.8824=&NotLessGreater;
entity.8825=&NotGreaterLess;
entity.8826=&Precedes;
entity.8827=&Succeeds;
entity.8828=&PrecedesSlantEqual;
entity.8829=&SucceedsSlantEqual;
entity.8830=&PrecedesTilde;
entity.8831=&SucceedsTilde;
entity.8832=&NotPrecedes;
entity.8833=&NotSucceeds;
entity.8834=&subset;
entity.8835=&Superset;
entity.8837=&nsup;
entity.8838=&SubsetEqual;
entity.8839=&SupersetEqual;
entity.8840=&NotSubsetEqual;
entity.8841=&NotSupersetEqual;
entity.8842=&subsetneq;
entity.8843=&supsetneq;
entity.8845=&cupdot;
entity.8846=&UnionPlus;
entity.8847=&SquareSubset;
entity.8848=&SquareSuperset;
entity.8849=&SquareSubsetEqual;
entity.8850=&SquareSupersetEqual;
entity.8851=&SquareIntersection;
entity.8852=&SquareUnion;
entity.8853=&CirclePlus;
entity.8854=&CircleMinus;
entity.8855=&CircleTimes;
entity.8856=&osol;
entity.8857=&CircleDot;
entity.8858=&circledcirc;
entity.8859=&circledast;
entity.8861=&circleddash;
entity.8862=&boxplus;
entity.8863=&boxminus;
entity.8864=&boxtimes;
entity.8865=&dotsquare;
entity.8866=&RightTee;
entity.8867=&LeftTee;
entity.8868=&DownTee;
entity.8869=&UpTee;
entity.8871=&models;
entity.8872=&DoubleRightTee;
entity.8873=&Vdash;
entity.8874=&Vvdash;
entity.8875=&VDash;
entity.8876=&nvdash;
entity.8877=&nvDash;
entity.8878=&nVdash;
entity.8879=&nVDash;
entity.8880=&prurel;
entity.8882=&LeftTriangle;
entity.8883=&RightTriangle;
entity.8884=&LeftTriangleEqual;
entity.8885=&RightTriangleEqual;
entity.8886=&origof;
entity.8887=&imof;
entity.8888=&multimap;
entity.8889=&hercon;
entity.8890=&intercal;
entity.8891=&veebar;
entity.8893=&barvee;
entity.8894=&angrtvb;
entity.8895=&lrtri;
entity.8896=&Wedge;
entity.8897=&Vee;
entity.8898=&Intersection;
entity.8899=&Union;
entity.8900=&Diamond;
entity.8902=&Star;
entity.8903=&divideontimes;
entity.8904=&bowtie;
entity.8905=&ltimes;
entity.8906=&rtimes;
entity.8907=&leftthreetimes;
entity.8908=&rightthreetimes;
entity.8909=&backsimeq;
entity.8910=&curlyvee;
entity.8911=&curlywedge;
entity.8912=&Subset;
entity.8913=&Supset;
entity.8914=&Cap;
entity.8915=&Cup;
entity.8916=&pitchfork;
entity.8917=&epar;
entity.8918=&lessdot;
entity.8919=&gtrdot;
entity.8920=&Ll;
entity.8921=&Gg;
entity.8922=&LessEqualGreater;
entity.8923=&GreaterEqualLess;
entity.8926=&curlyeqprec;
entity.8927=&curlyeqsucc;
entity.8928=&NotPrecedesSlantEqual;
entity.8929=&NotSucceedsSlantEqual;
entity.8930=&NotSquareSubsetEqual;
entity.8931=&NotSquareSupersetEqual;
entity.8934=&lnsim;
entity.8935=&gnsim;
entity.8936=&precnsim;
entity.8937=&succnsim;
entity.8938=&NotLeftTriangle;
entity.8939=&NotRightTriangle;
entity.8940=&NotLeftTriangleEqual;
entity.8941=&NotRightTriangleEqual;
entity.8942=&vellip;
entity.8943=&ctdot;
entity.8944=&utdot;
entity.8945=&dtdot;
entity.8946=&disin;
entity.8947=&isinsv;
entity.8948=&isins;
entity.8949=&isindot;
entity.8950=&notinvc;
entity.8951=&notinvb;
entity.8953=&isinE;
entity.8954=&nisd;
entity.8955=&xnis;
entity.8956=&nis;
entity.8957=&notnivc;
entity.8958=&notnivb;
entity.8965=&barwedge;
entity.8966=&Barwed;
entity.8968=&LeftCeiling;
entity.8969=&RightCeiling;
entity.8970=&LeftFloor;
entity.8971=&RightFloor;
entity.8972=&drcrop;
entity.8973=&dlcrop;
entity.8974=&urcrop;
entity.8975=&ulcrop;
entity.8976=&bnot;
entity.8978=&profline;
entity.8979=&profsurf;
entity.8981=&telrec;
entity.8982=&target;
entity.8988=&ulcorner;
entity.8989=&urcorner;
entity.8990=&llcorner;
entity.8991=&lrcorner;
entity.8994=&frown;
entity.8995=&smile;
entity.9005=&cylcty;
entity.9006=&profalar;
entity.9014=&topbot;
entity.9021=&ovbar;
entity.9023=&solbar;
entity.9084=&angzarr;
entity.9136=&lmoustache;
entity.9137=&rmoustache;
entity.9140=&OverBracket;
entity.9141=&UnderBracket;
entity.9142=&bbrktbrk;
entity.9180=&OverParenthesis;
entity.9181=&UnderParenthesis;
entity.9182=&OverBrace;
entity.9183=&UnderBrace;
entity.9186=&trpezium;
entity.9191=&elinters;
entity.9251=&blank;
entity.9416=&circledS;
entity.9472=&HorizontalLine;
entity.9474=&boxv;
entity.9484=&boxdr;
entity.9488=&boxdl;
entity.9492=&boxur;
entity.9496=&boxul;
entity.9500=&boxvr;
entity.9508=&boxvl;
entity.9516=&boxhd;
entity.9524=&boxhu;
entity.9532=&boxvh;
entity.9552=&boxH;
entity.9553=&boxV;
entity.9554=&boxdR;
entity.9555=&boxDr;
entity.9556=&boxDR;
entity.9557=&boxdL;
entity.9558=&boxDl;
entity.9559=&boxDL;
entity.9560=&boxuR;
entity.9561=&boxUr;
entity.9562=&boxUR;
entity.9563=&boxuL;
entity.9564=&boxUl;
entity.9565=&boxUL;
entity.9566=&boxvR;
entity.9567=&boxVr;
entity.9568=&boxVR;
entity.9569=&boxvL;
entity.9570=&boxVl;
entity.9571=&boxVL;
entity.9572=&boxHd;
entity.9573=&boxhD;
entity.9574=&boxHD;
entity.9575=&boxHu;
entity.9576=&boxhU;
entity.9577=&boxHU;
entity.9578=&boxvH;
entity.9579=&boxVh;
entity.9580=&boxVH;
entity.9600=&uhblk;
entity.9604=&lhblk;
entity.9608=&block;
entity.9617=&blk14;
entity.9618=&blk12;
entity.9619=&blk34;
entity.9633=&Square;
entity.9642=&FilledVerySmallSquare;
entity.9643=&EmptyVerySmallSquare;
entity.9645=&rect;
entity.9646=&marker;
entity.9649=&fltns;
entity.9651=&bigtriangleup;
entity.9652=&blacktriangle;
entity.9653=&triangle;
entity.9656=&blacktriangleright;
entity.9657=&triangleright;
entity.9661=&bigtriangledown;
entity.9662=&blacktriangledown;
entity.9663=&triangledown;
entity.9666=&blacktriangleleft;
entity.9667=&triangleleft;
entity.9674=&lozenge;
entity.9675=&cir;
entity.9708=&tridot;
entity.9711=&bigcirc;
entity.9720=&ultri;
entity.9721=&urtri;
entity.9722=&lltri;
entity.9723=&EmptySmallSquare;
entity.9724=&FilledSmallSquare;
entity.9733=&bigstar;
entity.9734=&star;
entity.9742=&phone;
entity.9792=&female;
entity.9794=&male;
entity.9824=&spadesuit;
entity.9827=&clubsuit;
entity.9829=&heartsuit;
entity.9830=&diamondsuit;
entity.9834=&sung;
entity.9837=&flat;
entity.9838=&natural;
entity.9839=&sharp;
entity.10003=&checkmark;
entity.10007=&cross;
entity.10016=&maltese;
entity.10038=&sext;
entity.10072=&VerticalSeparator;
entity.10098=&lbbrk;
entity.10099=&rbbrk;
entity.10214=&LeftDoubleBracket;
entity.10215=&RightDoubleBracket;
entity.10216=&LeftAngleBracket;
entity.10217=&RightAngleBracket;
entity.10218=&Lang;
entity.10219=&Rang;
entity.10220=&loang;
entity.10221=&roang;
entity.10229=&LongLeftArrow;
entity.10230=&LongRightArrow;
entity.10231=&LongLeftRightArrow;
entity.10232=&DoubleLongLeftArrow;
entity.10233=&DoubleLongRightArrow;
entity.10234=&DoubleLongLeftRightArrow;
entity.10236=&longmapsto;
entity.10239=&dzigrarr;
entity.10498=&nvlArr;
entity.10499=&nvrArr;
entity.10500=&nvHarr;
entity.10501=&Map;
entity.10508=&lbarr;
entity.10509=&bkarow;
entity.10510=&lBarr;
entity.10511=&dbkarow;
entity.10512=&RBarr;
entity.10513=&DDotrahd;
entity.10514=&UpArrowBar;
entity.10515=&DownArrowBar;
entity.10518=&Rarrtl;
entity.10521=&latail;
entity.10522=&ratail;
entity.10523=&lAtail;
entity.10524=&rAtail;
entity.10525=&larrfs;
entity.10526=&rarrfs;
entity.10527=&larrbfs;
entity.10528=&rarrbfs;
entity.10531=&nwarhk;
entity.10532=&nearhk;
entity.10533=&hksearow;
entity.10534=&hkswarow;
entity.10535=&nwnear;
entity.10536=&nesear;
entity.10537=&seswar;
entity.10538=&swnwar;
entity.10547=&rarrc;
entity.10549=&cudarrr;
entity.10550=&ldca;
entity.10551=&rdca;
entity.10552=&cudarrl;
entity.10553=&larrpl;
entity.10556=&curarrm;
entity.10557=&cularrp;
entity.10565=&rarrpl;
entity.10568=&harrcir;
entity.10569=&Uarrocir;
entity.10570=&lurdshar;
entity.10571=&ldrushar;
entity.10574=&LeftRightVector;
entity.10575=&RightUpDownVector;
entity.10576=&DownLeftRightVector;
entity.10577=&LeftUpDownVector;
entity.10578=&LeftVectorBar;
entity.10579=&RightVectorBar;
entity.10580=&RightUpVectorBar;
entity.10581=&RightDownVectorBar;
entity.10582=&DownLeftVectorBar;
entity.10583=&DownRightVectorBar;
entity.10584=&LeftUpVectorBar;
entity.10585=&LeftDownVectorBar;
entity.10586=&LeftTeeVector;
entity.10587=&RightTeeVector;
entity.10588=&RightUpTeeVector;
entity.10589=&RightDownTeeVector;
entity.10590=&DownLeftTeeVector;
entity.10591=&DownRightTeeVector;
entity.10592=&LeftUpTeeVector;
entity.10593=&LeftDownTeeVector;
entity.10594=&lHar;
entity.10595=&uHar;
entity.10596=&rHar;
entity.10597=&dHar;
entity.10598=&luruhar;
entity.10599=&ldrdhar;
entity.10600=&ruluhar;
entity.10601=&rdldhar;
entity.10602=&lharul;
entity.10603=&llhard;
entity.10604=&rharul;
entity.10605=&lrhard;
entity.10606=&UpEquilibrium;
entity.10607=&ReverseUpEquilibrium;
entity.10608=&RoundImplies;
entity.10609=&erarr;
entity.10610=&simrarr;
entity.10611=&larrsim;
entity.10612=&rarrsim;
entity.10613=&rarrap;
entity.10614=&ltlarr;
entity.10616=&gtrarr;
entity.10617=&subrarr;
entity.10619=&suplarr;
entity.10620=&lfisht;
entity.10621=&rfisht;
entity.10622=&ufisht;
entity.10623=&dfisht;
entity.10629=&lopar;
entity.10630=&ropar;
entity.10635=&lbrke;
entity.10636=&rbrke;
entity.10637=&lbrkslu;
entity.10638=&rbrksld;
entity.10639=&lbrksld;
entity.10640=&rbrkslu;
entity.10641=&langd;
entity.10642=&rangd;
entity.10643=&lparlt;
entity.10644=&rpargt;
entity.10645=&gtlPar;
entity.10646=&ltrPar;
entity.10650=&vzigzag;
entity.10652=&vangrt;
entity.10653=&angrtvbd;
entity.10660=&ange;
entity.10661=&range;
entity.10662=&dwangle;
entity.10663=&uwangle;
entity.10664=&angmsdaa;
entity.10665=&angmsdab;
entity.10666=&angmsdac;
entity.10667=&angmsdad;
entity.10668=&angmsdae;
entity.10669=&angmsdaf;
entity.10670=&angmsdag;
entity.10671=&angmsdah;
entity.10672=&bemptyv;
entity.10673=&demptyv;
entity.10674=&cemptyv;
entity.10675=&raemptyv;
entity.10676=&laemptyv;
entity.10677=&ohbar;
entity.10678=&omid;
entity.10679=&opar;
entity.10681=&operp;
entity.10683=&olcross;
entity.10684=&odsold;
entity.10686=&olcir;
entity.10687=&ofcir;
entity.10688=&olt;
entity.10689=&ogt;
entity.10690=&cirscir;
entity.10691=&cirE;
entity.10692=&solb;
entity.10693=&bsolb;
entity.10697=&boxbox;
entity.10701=&trisb;
entity.10702=&rtriltri;
entity.10703=&LeftTriangleBar;
entity.10704=&RightTriangleBar;
entity.10714=&race;
entity.10716=&iinfin;
entity.10717=&infintie;
entity.10718=&nvinfin;
entity.10723=&eparsl;
entity.10724=&smeparsl;
entity.10725=&eqvparsl;
entity.10731=&blacklozenge;
entity.10740=&RuleDelayed;
entity.10742=&dsol;
entity.10752=&bigodot;
entity.10753=&bigoplus;
entity.10754=&bigotimes;
entity.10756=&biguplus;
entity.10758=&bigsqcup;
entity.10764=&iiiint;
entity.10765=&fpartint;
entity.10768=&cirfnint;
entity.10769=&awint;
entity.10770=&rppolint;
entity.10771=&scpolint;
entity.10772=&npolint;
entity.10773=&pointint;
entity.10774=&quatint;
entity.10775=&intlarhk;
entity.10786=&pluscir;
entity.10787=&plusacir;
entity.10788=&simplus;
entity.10789=&plusdu;
entity.10790=&plussim;
entity.10791=&plustwo;
entity.10793=&mcomma;
entity.10794=&minusdu;
entity.10797=&loplus;
entity.10798=&roplus;
entity.10799=&Cross;
entity.10800=&timesd;
entity.10801=&timesbar;
entity.10803=&smashp;
entity.10804=&lotimes;
entity.10805=&rotimes;
entity.10806=&otimesas;
entity.10807=&Otimes;
entity.10808=&odiv;
entity.10809=&triplus;
entity.10810=&triminus;
entity.10811=&tritime;
entity.10812=&intprod;
entity.10815=&amalg;
entity.10816=&capdot;
entity.10818=&ncup;
entity.10819=&ncap;
entity.10820=&capand;
entity.10821=&cupor;
entity.10822=&cupcap;
entity.10823=&capcup;
entity.10824=&cupbrcap;
entity.10825=&capbrcup;
entity.10826=&cupcup;
entity.10827=&capcap;
entity.10828=&ccups;
entity.10829=&ccaps;
entity.10832=&ccupssm;
entity.10835=&And;
entity.10836=&Or;
entity.10837=&andand;
entity.10838=&oror;
entity.10839=&orslope;
entity.10840=&andslope;
entity.10842=&andv;
entity.10843=&orv;
entity.10844=&andd;
entity.10845=&ord;
entity.10847=&wedbar;
entity.10854=&sdote;
entity.10858=&simdot;
entity.10861=&congdot;
entity.10862=&easter;
entity.10863=&apacir;
entity.10864=&apE;
entity.10865=&eplus;
entity.10866=&pluse;
entity.10867=&Esim;
entity.10868=&Colone;
entity.10869=&Equal;
entity.10871=&ddotseq;
entity.10872=&equivDD;
entity.10873=&ltcir;
entity.10874=&gtcir;
entity.10875=&ltquest;
entity.10876=&gtquest;
entity.10877=&LessSlantEqual;
entity.10878=&GreaterSlantEqual;
entity.10879=&lesdot;
entity.10880=&gesdot;
entity.10881=&lesdoto;
entity.10882=&gesdoto;
entity.10883=&lesdotor;
entity.10884=&gesdotol;
entity.10885=&lessapprox;
entity.10886=&gtrapprox;
entity.10887=&lneq;
entity.10888=&gneq;
entity.10889=&lnapprox;
entity.10890=&gnapprox;
entity.10891=&lesseqqgtr;
entity.10892=&gtreqqless;
entity.10893=&lsime;
entity.10894=&gsime;
entity.10895=&lsimg;
entity.10896=&gsiml;
entity.10897=&lgE;
entity.10898=&glE;
entity.10899=&lesges;
entity.10900=&gesles;
entity.10901=&eqslantless;
entity.10902=&eqslantgtr;
entity.10903=&elsdot;
entity.10904=&egsdot;
entity.10905=&el;
entity.10906=&eg;
entity.10909=&siml;
entity.10910=&simg;
entity.10911=&simlE;
entity.10912=&simgE;
entity.10913=&LessLess;
entity.10914=&GreaterGreater;
entity.10916=&glj;
entity.10917=&gla;
entity.10918=&ltcc;
entity.10919=&gtcc;
entity.10920=&lescc;
entity.10921=&gescc;
entity.10922=&smt;
entity.10923=&lat;
entity.10924=&smte;
entity.10925=&late;
entity.10926=&bumpE;
entity.10927=&PrecedesEqual;
entity.10928=&SucceedsEqual;
entity.10931=&prE;
entity.10932=&scE;
entity.10933=&precneqq;
entity.10934=&succneqq;
entity.10935=&precapprox;
entity.10936=&succapprox;
entity.10937=&precnapprox;
entity.10938=&succnapprox;
entity.10939=&Pr;
entity.10940=&Sc;
entity.10941=&subdot;
entity.10942=&supdot;
entity.10943=&subplus;
entity.10944=&supplus;
entity.10945=&submult;
entity.10946=&supmult;
entity.10947=&subedot;
entity.10948=&supedot;
entity.10949=&subseteqq;
entity.10950=&supseteqq;
entity.10951=&subsim;
entity.10952=&supsim;
entity.10955=&subsetneqq;
entity.10956=&supsetneqq;
entity.10959=&csub;
entity.10960=&csup;
entity.10961=&csube;
entity.10962=&csupe;
entity.10963=&subsup;
entity.10964=&supsub;
entity.10965=&subsub;
entity.10966=&supsup;
entity.10967=&suphsub;
entity.10968=&supdsub;
entity.10969=&forkv;
entity.10970=&topfork;
entity.10971=&mlcp;
entity.10980=&DoubleLeftTee;
entity.10982=&Vdashl;
entity.10983=&Barv;
entity.10984=&vBar;
entity.10985=&vBarv;
entity.10987=&Vbar;
entity.10988=&Not;
entity.10989=&bNot;
entity.10990=&rnmid;
entity.10991=&cirmid;
entity.10992=&midcir;
entity.10993=&topcir;
entity.10994=&nhpar;
entity.10995=&parsim;
entity.11005=&parsl;
entity.64256=&fflig;
entity.64257=&filig;
entity.64258=&fllig;
entity.64259=&ffilig;
entity.64260=&ffllig;
entity.119964=&Ascr;
entity.119966=&Cscr;
entity.119967=&Dscr;
entity.119970=&Gscr;
entity.119973=&Jscr;
entity.119974=&Kscr;
entity.119977=&Nscr;
entity.119978=&Oscr;
entity.119979=&Pscr;
entity.119980=&Qscr;
entity.119982=&Sscr;
entity.119983=&Tscr;
entity.119984=&Uscr;
entity.119985=&Vscr;
entity.119986=&Wscr;
entity.119987=&Xscr;
entity.119988=&Yscr;
entity.119989=&Zscr;
entity.119990=&ascr;
entity.119991=&bscr;
entity.119992=&cscr;
entity.119993=&dscr;
entity.119995=&fscr;
entity.119997=&hscr;
entity.119998=&iscr;
entity.119999=&jscr;
entity.120000=&kscr;
entity.120001=&lscr;
entity.120002=&mscr;
entity.120003=&nscr;
entity.120005=&pscr;
entity.120006=&qscr;
entity.120007=&rscr;
entity.120008=&sscr;
entity.120009=&tscr;
entity.120010=&uscr;
entity.120011=&vscr;
entity.120012=&wscr;
entity.120013=&xscr;
entity.120014=&yscr;
entity.120015=&zscr;
entity.120068=&Afr;
entity.120069=&Bfr;
entity.120071=&Dfr;
entity.120072=&Efr;
entity.120073=&Ffr;
entity.120074=&Gfr;
entity.120077=&Jfr;
entity.120078=&Kfr;
entity.120079=&Lfr;
entity.120080=&Mfr;
entity.120081=&Nfr;
entity.120082=&Ofr;
entity.120083=&Pfr;
entity.120084=&Qfr;
entity.120086=&Sfr;
entity.120087=&Tfr;
entity.120088=&Ufr;
entity.120089=&Vfr;
entity.120090=&Wfr;
entity.120091=&Xfr;
entity.120092=&Yfr;
entity.120094=&afr;
entity.120095=&bfr;
entity.120096=&cfr;
entity.120097=&dfr;
entity.120098=&efr;
entity.120099=&ffr;
entity.120100=&gfr;
entity.120101=&hfr;
entity.120102=&ifr;
entity.120103=&jfr;
entity.120104=&kfr;
entity.120105=&lfr;
entity.120106=&mfr;
entity.120107=&nfr;
entity.120108=&ofr;
entity.120109=&pfr;
entity.120110=&qfr;
entity.120111=&rfr;
entity.120112=&sfr;
entity.120113=&tfr;
entity.120114=&ufr;
entity.120115=&vfr;
entity.120116=&wfr;
entity.120117=&xfr;
entity.120118=&yfr;
entity.120119=&zfr;
entity.120120=&Aopf;
entity.120121=&Bopf;
entity.120123=&Dopf;
entity.120124=&Eopf;
entity.120125=&Fopf;
entity.120126=&Gopf;
entity.120128=&Iopf;
entity.120129=&Jopf;
entity.120130=&Kopf;
entity.120131=&Lopf;
entity.120132=&Mopf;
entity.120134=&Oopf;
entity.120138=&Sopf;
entity.120139=&Topf;
entity.120140=&Uopf;
entity.120141=&Vopf;
entity.120142=&Wopf;
entity.120143=&Xopf;
entity.120144=&Yopf;
entity.120146=&aopf;
entity.120147=&bopf;
entity.120148=&copf;
entity.120149=&dopf;
entity.120150=&eopf;
entity.120151=&fopf;
entity.120152=&gopf;
entity.120153=&hopf;
entity.120154=&iopf;
entity.120155=&jopf;
entity.120156=&kopf;
entity.120157=&lopf;
entity.120158=&mopf;
entity.120159=&nopf;
entity.120160=&oopf;
entity.120161=&popf;
entity.120162=&qopf;
entity.120163=&ropf;
entity.120164=&sopf;
entity.120165=&topf;
entity.120166=&uopf;
entity.120167=&vopf;
entity.120168=&wopf;
entity.120169=&xopf;
entity.120170=&yopf;
entity.120171=&zopf;
entity.120484=&imath;
entity.120485=&jmath;
PK
!<n{		res/svg.css/* -*- 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/. */

@namespace url(http://www.w3.org/2000/svg);
@namespace xml url(http://www.w3.org/XML/1998/namespace);

style, script, symbol {
 display: none;
}

symbol:-moz-use-shadow-tree-root {
  display: inline !important;
}

switch {
 -moz-binding: none !important;
}

svg:not(:root), symbol, image, marker, pattern, foreignObject {
 overflow: hidden;
}

@media all and (-moz-is-glyph) {
 :root {
   fill: context-fill;
   fill-opacity: context-fill-opacity;
   stroke: context-stroke;
   stroke-opacity: context-stroke-opacity;
   stroke-width: context-value;
   stroke-dasharray: context-value;
   stroke-dashoffset: context-value;
 }
}

foreignObject {
  -moz-appearance: none ! important;
  margin: 0 ! important;
  padding: 0 ! important;
  border-width: 0 ! important;
  white-space: normal;
}

@media all and (-moz-is-resource-document) {
 foreignObject *|* {
   -moz-appearance: none !important;
 }
}

*|*::-moz-svg-foreign-content {
  display: block !important;
  /* We need to be an absolute and fixed container */
  transform: translate(0) !important;
  text-indent: 0;
}

/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
   noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
*/
*:not(svg),
*:not(foreignObject) > svg {
  transform-origin:0 0;
}

*|*::-moz-svg-text {
  unicode-bidi: inherit;
  vector-effect: inherit;
}

*[xml|space=preserve] {
  white-space: -moz-pre-space;
}

*|*::-moz-svg-marker-anon-child {
  clip-path: inherit;
  filter: inherit;
  mask: inherit;
  opacity: inherit;
}

*:-moz-focusring {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

/* nsDocumentViewer::CreateStyleSet doesn't load ua.css.
 * A few styles are common to html and SVG though
 * so we copy the rules below from that file.
 */

/* Links */

*|*:any-link {
  cursor: pointer;
}

*|*:any-link:-moz-focusring {
  /* Don't specify the outline-color, we should always use initial value. */
  outline: 1px dotted;
}

/*
 * SVG-as-an-image needs this rule
 */
*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
  display: block !important;
  background-color: inherit;
}
PK
!<d*chrome/pippki/content/pippki/CAOverlay.xul<?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 overlay SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="CAOverlay" 
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="CACerts">
    <description>&certmgr.cas;</description>
    <separator class="thin"/>
    <tree id="ca-tree" flex="1" enableColumnDrag="true"
              onselect="ca_enableButtons()">
      <treecols>
        <treecol id="certcol" label="&certmgr.certname;" primary="true" 
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="tokencol" label="&certmgr.tokenname;"
                     persist="hidden width ordinal" flex="1"/>
      </treecols>
      <treechildren ondblclick="viewCerts();"/>
    </tree>

    <separator class="thin"/>

    <hbox>
      <button id="ca_viewButton"
              label="&certmgr.view2.label;"
              accesskey="&certmgr.view2.accesskey;"
              disabled="true" oncommand="viewCerts();"/>
      <button id="ca_editButton"
              label="&certmgr.edit3.label;"
              accesskey="&certmgr.edit3.accesskey;"
              disabled="true" oncommand="editCerts();"/>
      <button id="ca_addButton"
              label="&certmgr.restore2.label;"
              accesskey="&certmgr.restore2.accesskey;"
              oncommand="addCACerts();"/>
      <button id="ca_exportButton"
              label="&certmgr.export.label;"
              accesskey="&certmgr.export.accesskey;"
              disabled="true" oncommand="exportCerts();"/>
      <button id="ca_deleteButton"
              label="&certmgr.delete_builtin.label;"
              accesskey="&certmgr.delete_builtin.accesskey;"
              disabled="true" oncommand="deleteCerts();"/>
    </hbox>
  </vbox>
</overlay>
PK
!<\,chrome/pippki/content/pippki/MineOverlay.xul<?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 overlay SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="MineOverlay" 
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="myCerts">
    <description>&certmgr.mine;</description>
    <separator class="thin"/>
    <tree id="user-tree" flex="1" enableColumnDrag="true"
              onselect="mine_enableButtons()">
      <treecols>
        <treecol id="certcol" label="&certmgr.certname;" primary="true" 
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="tokencol" label="&certmgr.tokenname;"
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="serialnumcol" label="&certmgr.serial;"
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="issuedcol" label="&certmgr.begins;"
                     hidden="true" persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="expiredcol" label="&certmgr.expires;"
                     persist="hidden width ordinal" flex="1"/>
      </treecols>
      <treechildren ondblclick="viewCerts();"/>
    </tree>
    
    <separator class="thin"/>

    <hbox>
       <button id="mine_viewButton" class="normal" 
               label="&certmgr.view2.label;"
               accesskey="&certmgr.view2.accesskey;"
               disabled="true" oncommand="viewCerts();"/>
       <button id="mine_backupButton" class="normal" 
               label="&certmgr.backup2.label;"
               accesskey="&certmgr.backup2.accesskey;"
               disabled="true" oncommand="backupCerts();"/>
       <button id="mine_backupAllButton" class="normal" 
               label="&certmgr.backupall2.label;"
               accesskey="&certmgr.backupall2.accesskey;"
               oncommand="backupAllCerts();"/>
       <button id="mine_restoreButton" class="normal" 
               label="&certmgr.restore2.label;"
               accesskey="&certmgr.restore2.accesskey;"
               oncommand="restoreCerts();"/>
       <button id="mine_deleteButton" class="normal" 
               label="&certmgr.delete2.label;"
               accesskey="&certmgr.delete2.accesskey;"
               disabled="true" oncommand="deleteCerts();"/>
    </hbox>
  </vbox>
</overlay>
PK
!<ٽ(.chrome/pippki/content/pippki/OrphanOverlay.xul<?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 overlay SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="OrphanOverlay" 
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="OrphanCerts">
    <description>&certmgr.orphans;</description>
    <separator class="thin"/>
    <tree id="orphan-tree" flex="1" enableColumnDrag="true"
              onselect="orphan_enableButtons()">
      <treecols>
        <treecol id="certcol" label="&certmgr.certname;" primary="true" 
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="tokencol" label="&certmgr.tokenname;"
                     persist="hidden width ordinal" flex="1"/>
      </treecols>
      <treechildren ondblclick="viewCerts();"/>
    </tree>
    
    <separator class="thin"/>

    <hbox>
       <button id="orphan_viewButton" class="normal" 
               label="&certmgr.view2.label;"
               accesskey="&certmgr.view2.accesskey;"
               disabled="true" oncommand="viewCerts();"/>
       <button id="orphan_exportButton" class="normal" 
               label="&certmgr.export.label;"
               accesskey="&certmgr.export.accesskey;"
               disabled="true" oncommand="exportCerts();"/>
       <button id="orphan_deleteButton" class="normal" 
               label="&certmgr.delete2.label;"
               accesskey="&certmgr.delete2.accesskey;"
               disabled="true" oncommand="deleteCerts();"/>
    </hbox>
  </vbox>
</overlay>
PK
!<f}((.chrome/pippki/content/pippki/OthersOverlay.xul<?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 overlay SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="OthersOverlay"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="othersCerts">
    <description>&certmgr.others;</description>
    <separator class="thin"/>
    <tree id="email-tree" flex="1"
              onselect="email_enableButtons()">
      <treecols>
        <treecol id="certcol" label="&certmgr.certname;" primary="true" 
                     flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="emailcol" label="&certmgr.email;"
                     flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="expiredcol" label="&certmgr.expires;"
                 flex="1"/>
      </treecols>
      <treechildren flex="1" ondblclick="viewCerts();"/>
    </tree>

    <separator class="thin"/>

    <hbox>
      <button id="email_viewButton"
              label="&certmgr.view2.label;"
              accesskey="&certmgr.view2.accesskey;"
              disabled="true" oncommand="viewCerts();"/>
      <button id="email_addButton"
              label="&certmgr.restore2.label;"
              accesskey="&certmgr.restore2.accesskey;"
              oncommand="addEmailCert();"/>
      <button id="email_exportButton"
              label="&certmgr.export.label;"
              accesskey="&certmgr.export.accesskey;"
              disabled="true" oncommand="exportCerts();"/>
      <button id="email_deleteButton"
              label="&certmgr.delete2.label;"
              accesskey="&certmgr.delete2.accesskey;"
              disabled="true" oncommand="deleteCerts();"/>
    </hbox>
  </vbox>
</overlay>
PK
!<x0b	b	0chrome/pippki/content/pippki/WebSitesOverlay.xul<?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 overlay SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="WebSitesOverlay" 
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <vbox id="webCerts">
    <description>&certmgr.websites2;</description>
    <separator class="thin"/>
    <tree id="server-tree" flex="1" enableColumnDrag="true"
              onselect="websites_enableButtons()">
      <treecols>
        <treecol id="certcol" label="&certmgr.certname;" primary="true" 
                     persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="sitecol" label="&certmgr.certserver;" 
                 persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="lifetimecol" label="&certmgr.override_lifetime;" 
                 persist="hidden width ordinal" flex="1"/>
        <splitter class="tree-splitter"/>
        <treecol id="expiredcol" label="&certmgr.expires;"
                 persist="hidden width ordinal" flex="1"/>
      </treecols>
      <treechildren ondblclick="viewCerts();"/>
    </tree>

    <separator class="thin"/>

    <hbox>
      <button id="websites_viewButton"
              label="&certmgr.view2.label;"
              accesskey="&certmgr.view2.accesskey;"
              disabled="true" oncommand="viewCerts();"/>
      <button id="websites_exportButton"
              label="&certmgr.export.label;"
              accesskey="&certmgr.export.accesskey;"
              disabled="true" oncommand="exportCerts();"/>
      <button id="websites_deleteButton"
              label="&certmgr.delete2.label;"
              accesskey="&certmgr.delete2.accesskey;"
              disabled="true" oncommand="deleteCerts();"/>
      <button id="websites_exceptionButton"
              label="&certmgr.addException.label;"
              accesskey="&certmgr.addException.accesskey;"
              oncommand="addException();"/>
    </hbox>
  </vbox>
</overlay>
PK
!<-pLWW)chrome/pippki/content/pippki/certDump.xul<?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 SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="certDumpOverlay"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:cert="http://netscape.com/rdf-cert#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox class="box-padded" id="certPrettyPrint" flex="1">
  <label class="header" value="&certmgr.hierarchy.label;"
         control="treesetDump" accesskey="&certmgr.hierarchy.accesskey2;"/>
  <tree id="treesetDump" onselect="updateCertDump();" flex="1"
        hidecolumnpicker="true" style="height: 8em;">
    <treecols>
      <treecol id="dumpCol" flex="1" primary="true" hideheader="true"/>
    </treecols>
  </tree>

  <label class="header" value="&certmgr.details.label;"
         control="prettyDumpTree" accesskey="&certmgr.details.accesskey;"/>
  <tree id="prettyDumpTree" style="height: 15em" treelines="true" flex="1"
            onselect="displaySelected();" hidecolumnpicker="true">
    <treecols>
      <treecol flex="1" id="certDataCol" primary="true" hideheader="true"/>
    </treecols>
    <treechildren/>
  </tree>

  <label class="header" value="&certmgr.fields.label;"
         control="certDumpVal" accesskey="&certmgr.fields.accesskey;"/>
  <textbox id="certDumpVal" multiline="true" flex="1"
           readonly="true" style="height: 11em; font-family: -moz-fixed;"/>

  <separator class="thin"/>
  <hbox>
    <button id="export_cert" class="normal" label="&certmgr.export.label;"
            accesskey="&certmgr.export.accesskey;"
            oncommand="exportToFile(window, getCurrentCert());"/>
  </hbox>
</vbox>
</overlay>
PK
!<>>+chrome/pippki/content/pippki/certManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

const nsIFilePicker = Components.interfaces.nsIFilePicker;
const nsFilePicker = "@mozilla.org/filepicker;1";
const nsIX509CertDB = Components.interfaces.nsIX509CertDB;
const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
const nsIX509Cert = Components.interfaces.nsIX509Cert;
const nsICertTree = Components.interfaces.nsICertTree;
const nsCertTree = "@mozilla.org/security/nsCertTree;1";

const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";

var { NetUtil } = Components.utils.import("resource://gre/modules/NetUtil.jsm", {});
var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});

var key;

/**
 * List of certs currently selected in the active tab.
 * @type nsIX509Cert[]
 */
var selected_certs = [];
var selected_tree_items = [];
var selected_index = [];
var certdb;

/**
 * Cert tree for the "Authorities" tab.
 * @type nsICertTree
 */
var caTreeView;
/**
 * Cert tree for the "Servers" tab.
 * @type nsICertTree
 */
var serverTreeView;
/**
 * Cert tree for the "People" tab.
 * @type nsICertTree
 */
var emailTreeView;
/**
 * Cert tree for the "Your Certificates" tab.
 * @type nsICertTree
 */
var userTreeView;
/**
 * Cert tree for the "Other" tab.
 * @type nsICertTree
 */
var orphanTreeView;

var smartCardObserver = {
  observe() {
    onSmartCardChange();
  }
};

function DeregisterSmartCardObservers() {
  Services.obs.removeObserver(smartCardObserver, "smartcard-insert");
  Services.obs.removeObserver(smartCardObserver, "smartcard-remove");
}

function LoadCerts() {
  Services.obs.addObserver(smartCardObserver, "smartcard-insert");
  Services.obs.addObserver(smartCardObserver, "smartcard-remove");

  certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
  var certcache = certdb.getCerts();

  caTreeView = Components.classes[nsCertTree]
                    .createInstance(nsICertTree);
  caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
  document.getElementById("ca-tree").view = caTreeView;

  serverTreeView = Components.classes[nsCertTree]
                        .createInstance(nsICertTree);
  serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT);
  document.getElementById("server-tree").view = serverTreeView;

  emailTreeView = Components.classes[nsCertTree]
                       .createInstance(nsICertTree);
  emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT);
  document.getElementById("email-tree").view = emailTreeView;

  userTreeView = Components.classes[nsCertTree]
                      .createInstance(nsICertTree);
  userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT);
  document.getElementById("user-tree").view = userTreeView;

  orphanTreeView = Components.classes[nsCertTree]
                      .createInstance(nsICertTree);
  orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT);
  document.getElementById("orphan-tree").view = orphanTreeView;

  enableBackupAllButton();
}

function enableBackupAllButton() {
  let backupAllButton = document.getElementById("mine_backupAllButton");
  backupAllButton.disabled = userTreeView.rowCount < 1;
}

function getSelectedCerts() {
  var ca_tab = document.getElementById("ca_tab");
  var mine_tab = document.getElementById("mine_tab");
  var others_tab = document.getElementById("others_tab");
  var websites_tab = document.getElementById("websites_tab");
  var orphan_tab = document.getElementById("orphan_tab");
  var items = null;
  if (ca_tab.selected) {
    items = caTreeView.selection;
  } else if (mine_tab.selected) {
    items = userTreeView.selection;
  } else if (others_tab.selected) {
    items = emailTreeView.selection;
  } else if (websites_tab.selected) {
    items = serverTreeView.selection;
  } else if (orphan_tab.selected) {
    items = orphanTreeView.selection;
  }
  selected_certs = [];
  var cert = null;
  var nr = 0;
  if (items != null) nr = items.getRangeCount();
  if (nr > 0) {
    for (let i = 0; i < nr; i++) {
      var o1 = {};
      var o2 = {};
      items.getRangeAt(i, o1, o2);
      var min = o1.value;
      var max = o2.value;
      for (let j = min; j <= max; j++) {
        if (ca_tab.selected) {
          cert = caTreeView.getCert(j);
        } else if (mine_tab.selected) {
          cert = userTreeView.getCert(j);
        } else if (others_tab.selected) {
          cert = emailTreeView.getCert(j);
        } else if (websites_tab.selected) {
          cert = serverTreeView.getCert(j);
        } else if (orphan_tab.selected) {
          cert = orphanTreeView.getCert(j);
        }
        if (cert) {
          var sc = selected_certs.length;
          selected_certs[sc] = cert;
          selected_index[sc] = j;
        }
      }
    }
  }
}

function getSelectedTreeItems() {
  var ca_tab = document.getElementById("ca_tab");
  var mine_tab = document.getElementById("mine_tab");
  var others_tab = document.getElementById("others_tab");
  var websites_tab = document.getElementById("websites_tab");
  var orphan_tab = document.getElementById("orphan_tab");
  var items = null;
  if (ca_tab.selected) {
    items = caTreeView.selection;
  } else if (mine_tab.selected) {
    items = userTreeView.selection;
  } else if (others_tab.selected) {
    items = emailTreeView.selection;
  } else if (websites_tab.selected) {
    items = serverTreeView.selection;
  } else if (orphan_tab.selected) {
    items = orphanTreeView.selection;
  }
  selected_certs = [];
  selected_tree_items = [];
  selected_index = [];
  var tree_item = null;
  var nr = 0;
  if (items != null) nr = items.getRangeCount();
  if (nr > 0) {
    for (let i = 0; i < nr; i++) {
      var o1 = {};
      var o2 = {};
      items.getRangeAt(i, o1, o2);
      var min = o1.value;
      var max = o2.value;
      for (let j = min; j <= max; j++) {
        if (ca_tab.selected) {
          tree_item = caTreeView.getTreeItem(j);
        } else if (mine_tab.selected) {
          tree_item = userTreeView.getTreeItem(j);
        } else if (others_tab.selected) {
          tree_item = emailTreeView.getTreeItem(j);
        } else if (websites_tab.selected) {
          tree_item = serverTreeView.getTreeItem(j);
        } else if (orphan_tab.selected) {
          tree_item = orphanTreeView.getTreeItem(j);
        }
        if (tree_item) {
          var sc = selected_tree_items.length;
          selected_tree_items[sc] = tree_item;
          selected_index[sc] = j;
        }
      }
    }
  }
}

/**
 * Returns true if nothing in the given cert tree is selected or if the
 * selection includes a container. Returns false otherwise.
 *
 * @param {nsICertTree} certTree
 * @returns {Boolean}
 */
function nothingOrContainerSelected(certTree) {
  var certTreeSelection = certTree.selection;
  var numSelectionRanges = certTreeSelection.getRangeCount();

  if (numSelectionRanges == 0) {
    return true;
  }

  for (var i = 0; i < numSelectionRanges; i++) {
    var o1 = {};
    var o2 = {};
    certTreeSelection.getRangeAt(i, o1, o2);
    var minIndex = o1.value;
    var maxIndex = o2.value;
    for (var j = minIndex; j <= maxIndex; j++) {
      if (certTree.isContainer(j)) {
        return true;
      }
    }
  }

  return false;
}

/**
 * Enables or disables buttons corresponding to a cert tree depending on what
 * is selected in the cert tree.
 *
 * @param {nsICertTree} certTree
 * @param {Array} idList A list of string identifiers for button elements to
 *    enable or disable.
 */
function enableButtonsForCertTree(certTree, idList) {
  let disableButtons = nothingOrContainerSelected(certTree);

  for (let id of idList) {
    document.getElementById(id).setAttribute("disabled", disableButtons);
  }
}

function ca_enableButtons() {
  let idList = [
    "ca_viewButton",
    "ca_editButton",
    "ca_exportButton",
    "ca_deleteButton",
  ];
  enableButtonsForCertTree(caTreeView, idList);
}

function mine_enableButtons() {
  let idList = [
    "mine_viewButton",
    "mine_backupButton",
    "mine_deleteButton",
  ];
  enableButtonsForCertTree(userTreeView, idList);
}

function websites_enableButtons() {
  let idList = [
    "websites_viewButton",
    "websites_exportButton",
    "websites_deleteButton",
  ];
  enableButtonsForCertTree(serverTreeView, idList);
}

function email_enableButtons() {
  let idList = [
    "email_viewButton",
    "email_exportButton",
    "email_deleteButton",
  ];
  enableButtonsForCertTree(emailTreeView, idList);
}

function orphan_enableButtons() {
  let idList = [
    "orphan_viewButton",
    "orphan_exportButton",
    "orphan_deleteButton",
  ];
  enableButtonsForCertTree(orphanTreeView, idList);
}

function backupCerts() {
  getSelectedCerts();
  var numcerts = selected_certs.length;
  if (numcerts == 0) {
    return;
  }

  var bundle = document.getElementById("pippki_bundle");
  var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
  fp.init(window,
          bundle.getString("chooseP12BackupFileDialog"),
          nsIFilePicker.modeSave);
  fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"),
                  "*.p12");
  fp.appendFilters(nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
      certdb.exportPKCS12File(fp.file, selected_certs.length, selected_certs);
    }
  });
}

function backupAllCerts() {
  // Select all rows, then call doBackup()
  userTreeView.selection.selectAll();
  backupCerts();
}

function editCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    window.openDialog("chrome://pippki/content/editcacert.xul", "",
                      "chrome,centerscreen,modal", cert);
  }
}

function restoreCerts() {
  var bundle = document.getElementById("pippki_bundle");
  var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
  fp.init(window,
          bundle.getString("chooseP12RestoreFileDialog2"),
          nsIFilePicker.modeOpen);
  fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"),
                  "*.p12; *.pfx");
  fp.appendFilter(bundle.getString("file_browse_Certificate_spec"),
                  gCertFileTypes);
  fp.appendFilters(nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv != nsIFilePicker.returnOK) {
      return;
    }

    // If this is an X509 user certificate, import it as one.

    var isX509FileType = false;
    var fileTypesList = gCertFileTypes.slice(1).split("; *");
    for (var type of fileTypesList) {
      if (fp.file.path.endsWith(type)) {
        isX509FileType = true;
        break;
      }
    }

    if (isX509FileType) {
      let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                      .createInstance(Components.interfaces.nsIFileInputStream);
      fstream.init(fp.file, -1, 0, 0);
      let dataString = NetUtil.readInputStreamToString(fstream, fstream.available());
      let dataArray = [];
      for (let i = 0; i < dataString.length; i++) {
        dataArray.push(dataString.charCodeAt(i));
      }
      fstream.close();
      let prompter = Services.ww.getNewPrompter(window);
      let interfaceRequestor = {
        getInterface() {
          return prompter;
        }
      };
      certdb.importUserCertificate(dataArray, dataArray.length, interfaceRequestor);
    } else {
      // Otherwise, assume it's a PKCS12 file and import it that way.
      certdb.importPKCS12File(fp.file);
    }

    var certcache = certdb.getCerts();
    userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT);
    userTreeView.selection.clearSelection();
    caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
    caTreeView.selection.clearSelection();
    enableBackupAllButton();
  });
}

function exportCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    exportToFile(window, cert);
  }
}

/**
 * Deletes the selected certs in the active tab.
 */
function deleteCerts() {
  getSelectedTreeItems();
  let numcerts = selected_tree_items.length;
  if (numcerts == 0) {
    return;
  }

  const treeViewMap = {
    "mine_tab": userTreeView,
    "websites_tab": serverTreeView,
    "ca_tab": caTreeView,
    "others_tab": emailTreeView,
    "orphan_tab": orphanTreeView,
  };
  let selTab = document.getElementById("certMgrTabbox").selectedItem;
  let selTabID = selTab.getAttribute("id");

  if (!(selTabID in treeViewMap)) {
    return;
  }

  let retVals = {
    deleteConfirmed: false,
  };
  window.openDialog("chrome://pippki/content/deletecert.xul", "",
                    "chrome,centerscreen,modal", selTabID, selected_tree_items,
                    retVals);

  if (retVals.deleteConfirmed) {
    let treeView = treeViewMap[selTabID];

    for (let t = numcerts - 1; t >= 0; t--) {
      treeView.deleteEntryObject(selected_index[t]);
    }

    selected_tree_items = [];
    selected_index = [];
    treeView.selection.clearSelection();
    if (selTabID == "mine_tab") {
      enableBackupAllButton();
    }
  }
}

function viewCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    viewCertHelper(window, cert);
  }
}

function addCACerts() {
  var bundle = document.getElementById("pippki_bundle");
  var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
  fp.init(window,
          bundle.getString("importCACertsPrompt"),
          nsIFilePicker.modeOpen);
  fp.appendFilter(bundle.getString("file_browse_Certificate_spec"),
                  gCertFileTypes);
  fp.appendFilters(nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == nsIFilePicker.returnOK) {
      certdb.importCertsFromFile(fp.file, nsIX509Cert.CA_CERT);
      caTreeView.loadCerts(nsIX509Cert.CA_CERT);
      caTreeView.selection.clearSelection();
    }
  });
}

function onSmartCardChange() {
  var certcache = certdb.getCerts();
  // We've change the state of the smart cards inserted or removed
  // that means the available certs may have changed. Update the display
  userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT);
  userTreeView.selection.clearSelection();
  caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
  caTreeView.selection.clearSelection();
  serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT);
  serverTreeView.selection.clearSelection();
  emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT);
  emailTreeView.selection.clearSelection();
  orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT);
  orphanTreeView.selection.clearSelection();
}

function addEmailCert() {
  var bundle = document.getElementById("pippki_bundle");
  var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
  fp.init(window,
          bundle.getString("importEmailCertPrompt"),
          nsIFilePicker.modeOpen);
  fp.appendFilter(bundle.getString("file_browse_Certificate_spec"),
                  gCertFileTypes);
  fp.appendFilters(nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == nsIFilePicker.returnOK) {
      certdb.importCertsFromFile(fp.file, nsIX509Cert.EMAIL_CERT);
      var certcache = certdb.getCerts();
      emailTreeView.loadCertsFromCache(certcache, nsIX509Cert.EMAIL_CERT);
      emailTreeView.selection.clearSelection();
      caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
      caTreeView.selection.clearSelection();
    }
  });
}

function addException() {
  window.openDialog("chrome://pippki/content/exceptionDialog.xul", "",
                    "chrome,centerscreen,modal");
  var certcache = certdb.getCerts();
  serverTreeView.loadCertsFromCache(certcache, nsIX509Cert.SERVER_CERT);
  serverTreeView.selection.clearSelection();
  orphanTreeView.loadCertsFromCache(certcache, nsIX509Cert.UNKNOWN_CERT);
  orphanTreeView.selection.clearSelection();
}
PK
!<b,chrome/pippki/content/pippki/certManager.xul<?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"?>

<?xul-overlay href="chrome://pippki/content/MineOverlay.xul"?>
<?xul-overlay href="chrome://pippki/content/OthersOverlay.xul"?>
<?xul-overlay href="chrome://pippki/content/WebSitesOverlay.xul"?>
<?xul-overlay href="chrome://pippki/content/CAOverlay.xul"?>
<?xul-overlay href="chrome://pippki/content/OrphanOverlay.xul"?>

<!DOCTYPE dialog SYSTEM "chrome://pippki/locale/certManager.dtd">

<dialog id="certmanager"
	windowtype="mozilla:certmanager"
	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&certmgr.title;"
        onload="LoadCerts();"
        onunload="DeregisterSmartCardObservers();"
        buttons="accept"
        style="width: 63em; height: 32em;"
        persist="screenX screenY width height">

  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

  <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
  <script type="application/javascript" src="chrome://pippki/content/certManager.js"/>

  <vbox flex="1">
    <tabbox id="certmanagertabs" flex="1" style="margin:5px" persist="selectedIndex">
      <tabs id="certMgrTabbox">
        <tab id="mine_tab" label="&certmgr.tab.mine;"/>
        <tab id="others_tab" label="&certmgr.tab.others2;"/>
        <tab id="websites_tab" label="&certmgr.tab.websites3;"/>
        <tab id="ca_tab" label="&certmgr.tab.ca;" selected="true"/>
        <tab id="orphan_tab" label="&certmgr.tab.orphan2;"/>
      </tabs>
      <tabpanels flex="1">
        <vbox id="myCerts" flex="1"/>
        <vbox id="othersCerts" flex="1"/>
        <vbox id="webCerts" flex="1"/>
        <vbox id="CACerts" flex="1"/>
        <vbox id="OrphanCerts" flex="1"/>
      </tabpanels>
    </tabbox>

  </vbox>

</dialog>
PK
!<ʴ9G1G1*chrome/pippki/content/pippki/certViewer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/**
 * @file Implements functionality for certViewer.xul and its tabs certDump.xul
 *       and viewCertDetails.xul: a dialog that allows various attributes of a
 *       certificate to be viewed.
 * @argument {nsISupports} window.arguments[0]
 *           The cert to view, queryable to nsIX509Cert.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const nsIX509Cert = Ci.nsIX509Cert;
const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
const nsIX509CertDB = Ci.nsIX509CertDB;
const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
const nsIPK11TokenDB = Ci.nsIPK11TokenDB;
const nsIASN1Object = Ci.nsIASN1Object;
const nsIASN1Sequence = Ci.nsIASN1Sequence;
const nsIASN1PrintableItem = Ci.nsIASN1PrintableItem;
const nsIASN1Tree = Ci.nsIASN1Tree;
const nsASN1Tree = "@mozilla.org/security/nsASN1Tree;1";

var bundle;

function doPrompt(msg) {
  let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Components.interfaces.nsIPromptService);
  prompts.alert(window, null, msg);
}

/**
 * Fills out the "Certificate Hierarchy" tree of the cert viewer "Details" tab.
 *
 * @param {tree} node
 *        Parent tree node to append to.
 * @param {nsIArray<nsIX509Cert>} chain
 *        Chain where cert element n is issued by cert element n + 1.
 */
function AddCertChain(node, chain) {
  let child = document.getElementById(node);
  for (let i = chain.length - 1; i >= 0; i--) {
    let currCert = chain.queryElementAt(i, nsIX509Cert);
    let displayValue = currCert.displayName;
    let addTwistie = i != 0;
    child = addChildrenToTree(child, displayValue, currCert.dbKey, addTwistie);
  }
}

/**
 * Adds a "verified usage" of a cert to the "General" tab of the cert viewer.
 *
 * @param {String} usage
 *        Verified usage to add.
 */
function AddUsage(usage) {
  let verifyInfoBox = document.getElementById("verify_info_box");
  let text = document.createElement("textbox");
  text.setAttribute("value", usage);
  text.setAttribute("style", "margin: 2px 5px");
  text.setAttribute("readonly", "true");
  text.setAttribute("class", "scrollfield");
  verifyInfoBox.appendChild(text);
}

function setWindowName() {
  bundle = document.getElementById("pippki_bundle");

  let cert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);
  document.title = bundle.getFormattedString("certViewerTitle",
                                             [cert.displayName]);

  //
  //  Set the cert attributes for viewing
  //

  //  The chain of trust
  AddCertChain("treesetDump", cert.getChain());
  DisplayGeneralDataFromCert(cert);
  BuildPrettyPrint(cert);

  asyncDetermineUsages(cert);
}

// Certificate usages we care about in the certificate viewer.
const certificateUsageSSLClient              = 0x0001;
const certificateUsageSSLServer              = 0x0002;
const certificateUsageSSLCA                  = 0x0008;
const certificateUsageEmailSigner            = 0x0010;
const certificateUsageEmailRecipient         = 0x0020;
const certificateUsageObjectSigner           = 0x0040;

// A map from the name of a certificate usage to the value of the usage.
// Useful for printing debugging information and for enumerating all supported
// usages.
const certificateUsages = {
  certificateUsageSSLClient,
  certificateUsageSSLServer,
  certificateUsageSSLCA,
  certificateUsageEmailSigner,
  certificateUsageEmailRecipient,
  certificateUsageObjectSigner,
};

// Map of certificate usage name to localization identifier.
const certificateUsageToStringBundleName = {
  certificateUsageSSLClient: "VerifySSLClient",
  certificateUsageSSLServer: "VerifySSLServer",
  certificateUsageSSLCA: "VerifySSLCA",
  certificateUsageEmailSigner: "VerifyEmailSigner",
  certificateUsageEmailRecipient: "VerifyEmailRecip",
  certificateUsageObjectSigner: "VerifyObjSign",
};

const PRErrorCodeSuccess = 0;

const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
const SEC_ERROR_EXPIRED_CERTIFICATE                     = SEC_ERROR_BASE + 11;
const SEC_ERROR_REVOKED_CERTIFICATE                     = SEC_ERROR_BASE + 12;
const SEC_ERROR_UNKNOWN_ISSUER                          = SEC_ERROR_BASE + 13;
const SEC_ERROR_UNTRUSTED_ISSUER                        = SEC_ERROR_BASE + 20;
const SEC_ERROR_UNTRUSTED_CERT                          = SEC_ERROR_BASE + 21;
const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE              = SEC_ERROR_BASE + 30;
const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED       = SEC_ERROR_BASE + 176;

/**
 * Kicks off asynchronous verifications of the given certificate to determine
 * what usages it is currently valid for. Updates the usage display area when
 * complete.
 *
 * @param {nsIX509Cert} cert
 *        The certificate to determine valid usages for.
 */
function asyncDetermineUsages(cert) {
  let promises = [];
  let now = Date.now() / 1000;
  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
                 .getService(Ci.nsIX509CertDB);
  Object.keys(certificateUsages).forEach(usageString => {
    promises.push(new Promise((resolve, reject) => {
      let usage = certificateUsages[usageString];
      certdb.asyncVerifyCertAtTime(cert, usage, 0, null, now,
        (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
          resolve({ usageString, errorCode: aPRErrorCode });
        });
    }));
  });
  Promise.all(promises).then(displayUsages);
}

/**
 * Updates the usage display area given the results from asyncDetermineUsages.
 *
 * @param {Array} results
 *        An array of objects with the properties "usageString" and "errorCode".
 *        usageString is a string that is a key in the certificateUsages map.
 *        errorCode is either an NSPR error code or PRErrorCodeSuccess (which is
 *        a pseudo-NSPR error code with the value 0 that indicates success).
 */
function displayUsages(results) {
  document.getElementById("verify_pending").setAttribute("hidden", "true");
  let verified = document.getElementById("verified");
  let someSuccess = results.some(result =>
    result.errorCode == PRErrorCodeSuccess
  );
  if (someSuccess) {
    let verifystr = bundle.getString("certVerified");
    verified.textContent = verifystr;
    let pipnssBundle = Services.strings.createBundle(
      "chrome://pipnss/locale/pipnss.properties");
    results.forEach(result => {
      if (result.errorCode != PRErrorCodeSuccess) {
        return;
      }
      let bundleName = certificateUsageToStringBundleName[result.usageString];
      let usage = pipnssBundle.GetStringFromName(bundleName);
      AddUsage(usage);
    });
  } else {
    const errorRankings = [
      { error: SEC_ERROR_REVOKED_CERTIFICATE,
        bundleString: "certNotVerified_CertRevoked" },
      { error: SEC_ERROR_UNTRUSTED_CERT,
        bundleString: "certNotVerified_CertNotTrusted" },
      { error: SEC_ERROR_UNTRUSTED_ISSUER,
        bundleString: "certNotVerified_IssuerNotTrusted" },
      { error: SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
        bundleString: "certNotVerified_AlgorithmDisabled" },
      { error: SEC_ERROR_EXPIRED_CERTIFICATE,
        bundleString: "certNotVerified_CertExpired" },
      { error: SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
        bundleString: "certNotVerified_CAInvalid" },
      { error: SEC_ERROR_UNKNOWN_ISSUER,
        bundleString: "certNotVerified_IssuerUnknown" },
    ];
    let verifystr;
    for (let errorRanking of errorRankings) {
      let errorPresent = results.some(result =>
        result.errorCode == errorRanking.error
      );
      if (errorPresent) {
        verifystr = bundle.getString(errorRanking.bundleString);
        break;
      }
    }
    if (!verifystr) {
      verifystr = bundle.getString("certNotVerified_Unknown");
    }
    verified.textContent = verifystr;
  }
  // Notify that we are done determining the certificate's valid usages (this
  // should be treated as an implementation detail that enables tests to run
  // efficiently - other code in the browser probably shouldn't rely on this).
  Services.obs.notifyObservers(window, "ViewCertDetails:CertUsagesDone");
}

function addChildrenToTree(parentTree, label, value, addTwistie) {
  let treeChild1 = document.createElement("treechildren");
  let treeElement = addTreeItemToTreeChild(treeChild1, label, value,
                                           addTwistie);
  parentTree.appendChild(treeChild1);
  return treeElement;
}

function addTreeItemToTreeChild(treeChild, label, value, addTwistie) {
  let treeElem1 = document.createElement("treeitem");
  if (addTwistie) {
    treeElem1.setAttribute("container", "true");
    treeElem1.setAttribute("open", "true");
  }
  let treeRow = document.createElement("treerow");
  let treeCell = document.createElement("treecell");
  treeCell.setAttribute("label", label);
  if (value) {
    treeCell.setAttribute("display", value);
  }
  treeRow.appendChild(treeCell);
  treeElem1.appendChild(treeRow);
  treeChild.appendChild(treeElem1);
  return treeElem1;
}

function displaySelected() {
  var asn1Tree = document.getElementById("prettyDumpTree")
          .view.QueryInterface(nsIASN1Tree);
  var items = asn1Tree.selection;
  var certDumpVal = document.getElementById("certDumpVal");
  if (items.currentIndex != -1) {
    var value = asn1Tree.getDisplayData(items.currentIndex);
    certDumpVal.value = value;
  } else {
    certDumpVal.value = "";
  }
}

function BuildPrettyPrint(cert) {
  var certDumpTree = Components.classes[nsASN1Tree].
                          createInstance(nsIASN1Tree);
  certDumpTree.loadASN1Structure(cert.ASN1Structure);
  document.getElementById("prettyDumpTree").view = certDumpTree;
}

function addAttributeFromCert(nodeName, value) {
  var node = document.getElementById(nodeName);
  if (!value) {
    value = bundle.getString("notPresent");
  }
  node.setAttribute("value", value);
}

/**
 * Displays information about a cert in the "General" tab of the cert viewer.
 *
 * @param {nsIX509Cert} cert
 *        Cert to display information about.
 */
function DisplayGeneralDataFromCert(cert) {
  addAttributeFromCert("commonname", cert.commonName);
  addAttributeFromCert("organization", cert.organization);
  addAttributeFromCert("orgunit", cert.organizationalUnit);
  addAttributeFromCert("serialnumber", cert.serialNumber);
  addAttributeFromCert("sha256fingerprint", cert.sha256Fingerprint);
  addAttributeFromCert("sha1fingerprint", cert.sha1Fingerprint);
  addAttributeFromCert("validitystart", cert.validity.notBeforeLocalDay);
  addAttributeFromCert("validityend", cert.validity.notAfterLocalDay);

  addAttributeFromCert("issuercommonname", cert.issuerCommonName);
  addAttributeFromCert("issuerorganization", cert.issuerOrganization);
  addAttributeFromCert("issuerorgunit", cert.issuerOrganizationUnit);
}

function updateCertDump() {
  var asn1Tree = document.getElementById("prettyDumpTree")
          .view.QueryInterface(nsIASN1Tree);

  var tree = document.getElementById("treesetDump");
  if (tree.currentIndex < 0) {
    doPrompt("No items are selected."); // This should never happen.
  } else {
    var item = tree.contentView.getItemAtIndex(tree.currentIndex);
    var dbKey = item.firstChild.firstChild.getAttribute("display");
    //  Get the cert from the cert database
    var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
    var cert = certdb.findCertByDBKey(dbKey);
    asn1Tree.loadASN1Structure(cert.ASN1Structure);
  }
  displaySelected();
}

function getCurrentCert() {
  var realIndex;
  var tree = document.getElementById("treesetDump");
  if (tree.view.selection.isSelected(tree.currentIndex)
      && document.getElementById("prettyprint_tab").selected) {
    /* if the user manually selected a cert on the Details tab,
       then take that one  */
    realIndex = tree.currentIndex;
  } else {
    /* otherwise, take the one at the bottom of the chain
       (i.e. the one of the end-entity, unless we're displaying
       CA certs) */
    realIndex = tree.view.rowCount - 1;
  }
  if (realIndex >= 0) {
    var item = tree.contentView.getItemAtIndex(realIndex);
    var dbKey = item.firstChild.firstChild.getAttribute("display");
    var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
    var cert = certdb.findCertByDBKey(dbKey);
    return cert;
  }
  /* shouldn't really happen */
  return null;
}
PK
!<	;+chrome/pippki/content/pippki/certViewer.xul<?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://pippki/locale/certManager.dtd">

<?xul-overlay href="chrome://pippki/content/viewCertDetails.xul"?>
<?xul-overlay href="chrome://pippki/content/certDump.xul"?>

<dialog id="certDetails"
  title="&certmgr.certdetail.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  buttons="accept"
  buttonlabelaccept="&certmgr.close.label;"
  buttonaccesskeyaccept="&certmgr.close.accesskey;"
  onload="setWindowName();">

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript"
        src="chrome://pippki/content/certViewer.js"/>
<script type="application/javascript" src="chrome://pippki/content/pippki.js"/>

  <tabbox flex="1">
    <tabs>
      <tab id="general_tab" label="&certmgr.detail.general_tab.title;"
           accesskey="&certmgr.detail.general_tab.accesskey;"/>
      <tab id="prettyprint_tab" label="&certmgr.detail.prettyprint_tab.title;"
           accesskey="&certmgr.detail.prettyprint_tab.accesskey;"/>
    </tabs>
    <tabpanels flex="1">
      <vbox id="general_info"/>
      <vbox id="certPrettyPrint"/>
    </tabpanels>
  </tabbox>

</dialog>
PK
!<h{C.chrome/pippki/content/pippki/changepassword.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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;

const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB;
const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB;
const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot;
const nsIPK11Token = Components.interfaces.nsIPK11Token;

var params;
var tokenName = "";
var pw1;

function doPrompt(msg) {
  let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Components.interfaces.nsIPromptService);
  prompts.alert(window, null, msg);
}

function onLoad() {
  document.documentElement.getButton("accept").disabled = true;

  pw1 = document.getElementById("pw1");
  params = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
  tokenName = params.GetString(1);

  document.getElementById("tokenName").setAttribute("value", tokenName);

  process();
}

function process() {
  let bundle = document.getElementById("pippki_bundle");

  // If the token is unitialized, don't use the old password box.
  // Otherwise, do.

  let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                  .getService(Ci.nsIPK11TokenDB);
  let token;
  if (tokenName.length > 0) {
    token = tokenDB.findTokenByName(tokenName);
  } else {
    token = tokenDB.getInternalKeyToken();
  }
  if (token) {
    let oldpwbox = document.getElementById("oldpw");
    let msgBox = document.getElementById("message");
    if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
      oldpwbox.setAttribute("hidden", "true");
      msgBox.setAttribute("value", bundle.getString("password_not_set"));
      msgBox.setAttribute("hidden", "false");

      if (!token.needsLogin()) {
        oldpwbox.setAttribute("inited", "empty");
      } else {
        oldpwbox.setAttribute("inited", "true");
      }

      // Select first password field
      document.getElementById("pw1").focus();
    } else {
      // Select old password field
      oldpwbox.setAttribute("hidden", "false");
      msgBox.setAttribute("hidden", "true");
      oldpwbox.setAttribute("inited", "false");
      oldpwbox.focus();
    }
  }

  if (params) {
    // Return value 0 means "canceled"
    params.SetInt(1, 0);
  }

  checkPasswords();
}

function setPassword() {
  let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                  .getService(Ci.nsIPK11TokenDB);
  let token;
  if (tokenName.length > 0) {
    token = tokenDB.findTokenByName(tokenName);
  } else {
    token = tokenDB.getInternalKeyToken();
  }

  var oldpwbox = document.getElementById("oldpw");
  var initpw = oldpwbox.getAttribute("inited");
  var bundle = document.getElementById("pippki_bundle");

  var success = false;

  if (initpw == "false" || initpw == "empty") {
    try {
      var oldpw = "";
      var passok = 0;

      if (initpw == "empty") {
        passok = 1;
      } else {
        oldpw = oldpwbox.value;
        passok = token.checkPassword(oldpw);
      }

      if (passok) {
        if (initpw == "empty" && pw1.value == "") {
          // checkPasswords() should have prevented this path from being reached.
        } else {
          if (pw1.value == "") {
            var secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
            if (secmoddb.isFIPSEnabled) {
              // empty passwords are not allowed in FIPS mode
              doPrompt(bundle.getString("pw_change2empty_in_fips_mode"));
              passok = 0;
            }
          }
          if (passok) {
            token.changePassword(oldpw, pw1.value);
            if (pw1.value == "") {
              doPrompt(bundle.getString("pw_erased_ok")
                    + " "
                    + bundle.getString("pw_empty_warning"));
            } else {
              doPrompt(bundle.getString("pw_change_ok"));
            }
            success = true;
          }
        }
      } else {
        oldpwbox.focus();
        oldpwbox.setAttribute("value", "");
        doPrompt(bundle.getString("incorrect_pw"));
      }
    } catch (e) {
      doPrompt(bundle.getString("failed_pw_change"));
    }
  } else {
    token.initPassword(pw1.value);
    if (pw1.value == "") {
      doPrompt(bundle.getString("pw_not_wanted") + " " +
               bundle.getString("pw_empty_warning"));
    }
    success = true;
  }

  if (success && params) {
    // Return value 1 means "successfully executed ok"
    params.SetInt(1, 1);
  }

  // Terminate dialog
  return success;
}

function setPasswordStrength() {
  // We weigh the quality of the password by checking the number of:
  //  - Characters
  //  - Numbers
  //  - Non-alphanumeric chars
  //  - Upper and lower case characters

  let pw = document.getElementById("pw1").value;

  let pwlength = pw.length;
  if (pwlength > 5) {
    pwlength = 5;
  }

  let numnumeric = pw.replace(/[0-9]/g, "");
  let numeric = pw.length - numnumeric.length;
  if (numeric > 3) {
    numeric = 3;
  }

  let symbols = pw.replace(/\W/g, "");
  let numsymbols = pw.length - symbols.length;
  if (numsymbols > 3) {
    numsymbols = 3;
  }

  let numupper = pw.replace(/[A-Z]/g, "");
  let upper = pw.length - numupper.length;
  if (upper > 3) {
    upper = 3;
  }

  let pwstrength = (pwlength * 10) - 20 + (numeric * 10) + (numsymbols * 15) +
                   (upper * 10);

  // Clamp strength to [0, 100].
  if (pwstrength < 0) {
    pwstrength = 0;
  }
  if (pwstrength > 100) {
    pwstrength = 100;
  }

  let meter = document.getElementById("pwmeter");
  meter.setAttribute("value", pwstrength);
}

function checkPasswords() {
  let pw1 = document.getElementById("pw1").value;
  let pw2 = document.getElementById("pw2").value;

  var oldpwbox = document.getElementById("oldpw");
  if (oldpwbox) {
    var initpw = oldpwbox.getAttribute("inited");

    if (initpw == "empty" && pw1 == "") {
      // The token has already been initialized, therefore this dialog
      // was called with the intention to change the password.
      // The token currently uses an empty password.
      // We will not allow changing the password from empty to empty.
      document.documentElement.getButton("accept").disabled = true;
      return;
    }
  }

  document.documentElement.getButton("accept").disabled = (pw1 != pw2);
}
PK
!<ƯUU/chrome/pippki/content/pippki/changepassword.xul<?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://pippki/locale/pippki.dtd">

<dialog id="set_password" title="&setPassword.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
  buttons="accept,cancel"
  ondialogaccept="return setPassword();"
  onload="onLoad();">

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript"
        src="chrome://pippki/content/changepassword.js"/>

<hbox align="center">
  <label value="&setPassword.tokenName.label;: "/>
  <label id="tokenName" />
</hbox>

<separator/>

<groupbox>
<grid>
 <columns>
   <column/>
   <column/> 
 </columns>
 <rows>
   <row>
     <label value="&setPassword.oldPassword.label;"/> 
     <textbox id="oldpw" type="password"/>
     <!-- This textbox is inserted as a workaround to the fact that making the 'type' 
          & 'disabled' property of the 'oldpw' textbox toggle between ['password' & 
          'false'] and ['text' & 'true'] - as would be necessary if the menu has more 
          than one tokens, some initialized and some not - does not work properly. So, 
          either the textbox 'oldpw' or the textbox 'message' would be displayed, 
          depending on the state of the token selected 
     -->
     <textbox id="message" disabled="true" />
   </row>
   <row>
     <label value="&setPassword.newPassword.label;"/> 
     <textbox id="pw1" type="password" 
       oninput="setPasswordStrength(); checkPasswords();"/> 
   </row>
   <row>
     <label value="&setPassword.reenterPassword.label;"/> 
     <textbox id="pw2" type="password" oninput="checkPasswords();"/>  
   </row>
 </rows>
</grid>
</groupbox>

<groupbox>
 <caption label="&setPassword.meter.label;"/>
 <progressmeter id="pwmeter" mode="determined" 
                  value="0"/>
</groupbox>

</dialog>
PK
!<b\8!!+chrome/pippki/content/pippki/choosetoken.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/. */
/* import-globals-from pippki.js */
"use strict";

const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;

var dialogParams;

function onLoad() {
  dialogParams = window.arguments[0].QueryInterface(nsIDialogParamBlock);
  let selectElement = document.getElementById("tokens");
  let count = dialogParams.GetInt(0);
  for (let i = 0; i < count; i++) {
    let menuItemNode = document.createElement("menuitem");
    let token = dialogParams.GetString(i);
    menuItemNode.setAttribute("value", token);
    menuItemNode.setAttribute("label", token);
    selectElement.firstChild.appendChild(menuItemNode);
    if (i == 0) {
      selectElement.selectedItem = menuItemNode;
    }
  }
}

function doOK() {
  let tokenList = document.getElementById("tokens");
  // Signal that the user accepted.
  dialogParams.SetInt(0, 1);
  // Signal the name of the token the user chose.
  dialogParams.SetString(0, tokenList.value);
  return true;
}

function doCancel() {
  dialogParams.SetInt(0, 0); // Signal that the user cancelled.
  return true;
}
PK
!<J88,chrome/pippki/content/pippki/choosetoken.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" >
%pippkiDTD;
]>


<dialog id="ssl_warning" title="&chooseToken.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
  style="width: 40em;"
  buttons="accept,cancel"
  ondialogaccept="return doOK();"
  ondialogcancel="return doCancel();"
  onload="onLoad();">

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
<script type="application/javascript" src="chrome://pippki/content/choosetoken.js"/>

  <groupbox>
    <description>&chooseToken.message1;</description>
    <menulist id="tokens">
      <menupopup/>
    </menulist>
  </groupbox>

</dialog>
PK
!<ѣ-chrome/pippki/content/pippki/clientauthask.js/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

/**
 * @file Implements the functionality of clientauthask.xul: a dialog that allows
 *       a user pick a client certificate for TLS client authentication.
 * @argument {String} window.arguments[0]
 *           The hostname of the server requesting client authentication.
 * @argument {String} window.arguments[1]
 *           The Organization of the server cert.
 * @argument {String} window.arguments[2]
 *           The Organization of the issuer of the server cert.
 * @argument {Number} window.arguments[3]
 *           The port of the server.
 * @argument {nsISupports} window.arguments[4]
 *           List of certificates the user can choose from, queryable to
 *           nsIArray<nsIX509Cert>.
 * @argument {nsISupports} window.arguments[5]
 *           Object to set the return values of calling the dialog on, queryable
 *           to the underlying type of ClientAuthAskReturnValues.
 */

/**
 * @typedef ClientAuthAskReturnValues
 * @type nsIWritablePropertyBag2
 * @property {Boolean} certChosen
 *           Set to true if the user chose a cert and accepted the dialog, false
 *           otherwise.
 * @property {Boolean} rememberSelection
 *           Set to true if the user wanted their cert selection to be
 *           remembered, false otherwise.
 * @property {Number} selectedIndex
 *           The index the chosen cert is at for the given cert list. Undefined
 *           value if |certChosen| is not true.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

/**
 * The pippki <stringbundle> element.
 * @type <stringbundle>
 */
var bundle;
/**
 * The array of certs the user can choose from.
 * @type nsIArray<nsIX509Cert>
 */
var certArray;
/**
 * The checkbox storing whether the user wants to remember the selected cert.
 * @type nsIDOMXULCheckboxElement
 */
var rememberBox;

function onLoad() {
  bundle = document.getElementById("pippki_bundle");
  let rememberSetting =
    Services.prefs.getBoolPref("security.remember_cert_checkbox_default_setting");

  rememberBox = document.getElementById("rememberBox");
  rememberBox.label = bundle.getString("clientAuthRemember");
  rememberBox.checked = rememberSetting;

  let hostname = window.arguments[0];
  let org = window.arguments[1];
  let issuerOrg = window.arguments[2];
  let port = window.arguments[3];
  let formattedOrg = bundle.getFormattedString("clientAuthMessage1", [org]);
  let formattedIssuerOrg = bundle.getFormattedString("clientAuthMessage2",
                                                     [issuerOrg]);
  let formattedHostnameAndPort =
    bundle.getFormattedString("clientAuthHostnameAndPort",
                              [hostname, port.toString()]);
  setText("hostname", formattedHostnameAndPort);
  setText("organization", formattedOrg);
  setText("issuer", formattedIssuerOrg);

  let selectElement = document.getElementById("nicknames");
  certArray = window.arguments[4].QueryInterface(Ci.nsIArray);
  for (let i = 0; i < certArray.length; i++) {
    let menuItemNode = document.createElement("menuitem");
    let cert = certArray.queryElementAt(i, Ci.nsIX509Cert);
    let nickAndSerial =
      bundle.getFormattedString("clientAuthNickAndSerial",
                                [cert.displayName, cert.serialNumber]);
    menuItemNode.setAttribute("value", i);
    menuItemNode.setAttribute("label", nickAndSerial); // This is displayed.
    selectElement.firstChild.appendChild(menuItemNode);
    if (i == 0) {
      selectElement.selectedItem = menuItemNode;
    }
  }

  setDetails();

  Services.obs.notifyObservers(document.getElementById("certAuthAsk"),
                               "cert-dialog-loaded");
}

/**
 * Populates the details section with information concerning the selected cert.
 */
function setDetails() {
  let index = parseInt(document.getElementById("nicknames").value);
  let cert = certArray.queryElementAt(index, Ci.nsIX509Cert);

  let detailLines = [
    bundle.getFormattedString("clientAuthIssuedTo", [cert.subjectName]),
    bundle.getFormattedString("clientAuthSerial", [cert.serialNumber]),
    bundle.getFormattedString("clientAuthValidityPeriod",
                              [cert.validity.notBeforeLocalTime,
                               cert.validity.notAfterLocalTime]),
  ];
  let keyUsages = cert.keyUsages;
  if (keyUsages) {
    detailLines.push(bundle.getFormattedString("clientAuthKeyUsages",
                                               [keyUsages]));
  }
  let emailAddresses = cert.getEmailAddresses({});
  if (emailAddresses.length > 0) {
    let joinedAddresses = emailAddresses.join(", ");
    detailLines.push(bundle.getFormattedString("clientAuthEmailAddresses",
                                               [joinedAddresses]));
  }
  detailLines.push(bundle.getFormattedString("clientAuthIssuedBy",
                                             [cert.issuerName]));
  detailLines.push(bundle.getFormattedString("clientAuthStoredOn",
                                             [cert.tokenName]));

  document.getElementById("details").value = detailLines.join("\n");
}

function onCertSelected() {
  setDetails();
}

function doOK() {
  let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("certChosen", true);
  let index = parseInt(document.getElementById("nicknames").value);
  retVals.setPropertyAsUint32("selectedIndex", index);
  retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
  return true;
}

function doCancel() {
  let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("certChosen", false);
  retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
  return true;
}
PK
!<5.chrome/pippki/content/pippki/clientauthask.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" >
%pippkiDTD;
]>


<dialog id="certAuthAsk" title="&clientAuthAsk.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
  buttons="accept,cancel"
  ondialogaccept="return doOK();"
  ondialogcancel="return doCancel();"
  onload="onLoad();">

<stringbundleset id="stringbundleset">
  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
</stringbundleset>

<script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
<script type="application/javascript" src="chrome://pippki/content/clientauthask.js"/>

  <groupbox>
    <description style="font-weight: bold;">&clientAuthAsk.message1;</description>
    <description id="hostname"/>
    <description id="organization"/>
    <description id="issuer"/>
    </groupbox>
    <groupbox>
    <description style="font-weight: bold;">&clientAuthAsk.message2;</description>
    <broadcaster id="certSelected" oncommand="onCertSelected();"/>
    <!-- The items in this menulist must never be sorted,
         but remain in the order filled by the application
    -->
    <menulist id="nicknames" observes="certSelected">
        <menupopup/>
    </menulist>
    <description>&clientAuthAsk.message3;</description>
    <textbox readonly="true" id="details" multiline="true"
      style="height: 11em;"/>
    <checkbox id="rememberBox" checked="true"/>
  </groupbox>

</dialog>
PK
!<R``.chrome/pippki/content/pippki/createCertInfo.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/. */
/* import-globals-from pippki.js */
"use strict";

var keygenThread;

function onLoad() {
  keygenThread = window.arguments[0].QueryInterface(Components.interfaces.nsIKeygenThread);

  if (!keygenThread) {
    window.close();
    return;
  }

  window.setCursor("wait");

  var obs = {
    observe: function keygenListenerObserve(subject, topic, data) {
      if (topic == "keygen-finished") {
        window.close();
      }
    }
  };

  keygenThread.startKeyGeneration(obs);
}

function onClose() {
  window.setCursor("auto");

  var alreadyClosed = {};
  keygenThread.userCanceled(alreadyClosed);
}
PK
!<"/chrome/pippki/content/pippki/createCertInfo.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://pippki/locale/pippki.dtd">

<window
  id="domainMismatch" title="&createCertInfo.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  onload="onLoad();"
  onclose="onClose();"
>

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript" src="pippki.js" />
<script type="application/javascript" src="createCertInfo.js" />

<vbox style="margin: 5px; max-width: 50em;">

  <description>&createCertInfo.msg1;</description>
  <separator/>
  <description style="font-weight: bold; text-align: center;">&createCertInfo.msg2;</description>
  <separator/>

</vbox>
</window>
PK
!<ŀQQ*chrome/pippki/content/pippki/deletecert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

/**
 * @file Implements the functionality of deletecert.xul: a dialog that allows a
 *       user to confirm whether to delete certain certificates.
 * @argument {String} window.arguments[0]
 *           One of the tab IDs listed in certManager.xul.
 * @argument {nsICertTreeItem[]} window.arguments[1]
 *           An array of cert tree items representing the certs to delete.
 * @argument {DeleteCertReturnValues} window.arguments[2]
 *           Object holding the return values of calling the dialog.
 */

/**
 * @typedef DeleteCertReturnValues
 * @type Object
 * @property {Boolean} deleteConfirmed
 *           Set to true if the user confirmed deletion of the given certs,
 *           false otherwise.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

/**
 * Returns the most appropriate string to represent the given nsICertTreeItem.
 * @param {nsICertTreeItem} certTreeItem
 *        The item to represent.
 * @returns {String}
 *          A representative string.
 */
function certTreeItemToString(certTreeItem) {
  let cert = certTreeItem.cert;
  if (!cert) {
    return certTreeItem.hostPort;
  }

  const attributes = [
    cert.commonName,
    cert.organizationalUnit,
    cert.organization,
    cert.subjectName,
  ];
  for (let attribute of attributes) {
    if (attribute) {
      return attribute;
    }
  }

  let bundle = document.getElementById("pippki_bundle");
  return bundle.getFormattedString("certWithSerial", [cert.serialNumber]);
}

/**
 * onload() handler.
 */
function onLoad() {
  let typeFlag = window.arguments[0];
  let bundle = document.getElementById("pippki_bundle");
  let title;
  let confirm;
  let impact;

  switch (typeFlag) {
    case "mine_tab":
      title = bundle.getString("deleteUserCertTitle");
      confirm = bundle.getString("deleteUserCertConfirm");
      impact = bundle.getString("deleteUserCertImpact");
      break;
    case "websites_tab":
      title = bundle.getString("deleteSslCertTitle3");
      confirm = bundle.getString("deleteSslCertConfirm3");
      impact = bundle.getString("deleteSslCertImpact3");
      break;
    case "ca_tab":
      title = bundle.getString("deleteCaCertTitle2");
      confirm = bundle.getString("deleteCaCertConfirm2");
      impact = bundle.getString("deleteCaCertImpactX2");
      break;
    case "others_tab":
      title = bundle.getString("deleteEmailCertTitle");
      confirm = bundle.getString("deleteEmailCertConfirm");
      impact = bundle.getString("deleteEmailCertImpactDesc");
      break;
    case "orphan_tab":
      title = bundle.getString("deleteOrphanCertTitle");
      confirm = bundle.getString("deleteOrphanCertConfirm");
      impact = "";
      break;
    default:
      return;
  }

  document.title = title;

  setText("confirm", confirm);

  let box = document.getElementById("certlist");
  let certTreeItems = window.arguments[1];
  for (let certTreeItem of certTreeItems) {
    let listItem = document.createElement("richlistitem");
    let label = document.createElement("label");
    label.setAttribute("value", certTreeItemToString(certTreeItem));
    listItem.appendChild(label);
    box.appendChild(listItem);
  }

  setText("impact", impact);
}

/**
 * ondialogaccept() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogAccept() {
  let retVals = window.arguments[2];
  retVals.deleteConfirmed = true;
  return true;
}

/**
 * ondialogcancel() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogCancel() {
  let retVals = window.arguments[2];
  retVals.deleteConfirmed = false;
  return true;
}
PK
!< *=^^+chrome/pippki/content/pippki/deletecert.xul<?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://pippki/locale/certManager.dtd">

<dialog id="deleteCertificate"
  title="&certmgr.deletecert.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  onload="onLoad();"
  buttons="accept,cancel"
  ondialogaccept="return onDialogAccept();"
  ondialogcancel="return onDialogCancel();">

  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

  <script type="application/javascript" src="chrome://pippki/content/deletecert.js"/>
  <script type="application/javascript" src="pippki.js" />

  <description id="confirm" style="width: 400px;"/>
  <richlistbox id="certlist" class="box-padded" flex="1"
               style="min-height: 8em; height: 8em; min-width: 35em;"/>
  <description id="impact" style="width: 400px;"/>

</dialog>
PK
!<*>377.chrome/pippki/content/pippki/device_manager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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 nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot;
const nsIPKCS11Module = Components.interfaces.nsIPKCS11Module;
const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB;
const nsIPK11Token = Components.interfaces.nsIPK11Token;
const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB;
const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
const nsDialogParamBlock = "@mozilla.org/embedcomp/dialogparam;1";
const nsIPKCS11 = Components.interfaces.nsIPKCS11;
const nsPKCS11ContractID = "@mozilla.org/security/pkcs11;1";

var { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});

var bundle;
var secmoddb;
var skip_enable_buttons = false;

var smartCardObserver = {
  observe() {
    onSmartCardChange();
  }
};

function DeregisterSmartCardObservers() {
  Services.obs.removeObserver(smartCardObserver, "smartcard-insert");
  Services.obs.removeObserver(smartCardObserver, "smartcard-remove");
}

/* Do the initial load of all PKCS# modules and list them. */
function LoadModules() {
  bundle = document.getElementById("pippki_bundle");
  secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
  Services.obs.addObserver(smartCardObserver, "smartcard-insert");
  Services.obs.addObserver(smartCardObserver, "smartcard-remove");

  RefreshDeviceList();
}

function getPKCS11() {
  return Components.classes[nsPKCS11ContractID].getService(nsIPKCS11);
}

function getNSSString(name) {
  return document.getElementById("pipnss_bundle").getString(name);
}

function doPrompt(msg) {
  let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Components.interfaces.nsIPromptService);
  prompts.alert(window, null, msg);
}

function doConfirm(msg) {
  let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
    getService(Components.interfaces.nsIPromptService);
  return prompts.confirm(window, null, msg);
}

function RefreshDeviceList() {
  let modules = secmoddb.listModules();
  while (modules.hasMoreElements()) {
    let module = modules.getNext().QueryInterface(nsIPKCS11Module);
    let slotnames = [];
    let slots = module.listSlots();
    while (slots.hasMoreElements()) {
      let slot = slots.getNext().QueryInterface(nsIPKCS11Slot);
      // Token names are preferred because NSS prefers lookup by token name.
      slotnames.push(slot.tokenName ? slot.tokenName : slot.name);
    }
    AddModule(module.name, slotnames);
  }

  // Set the text on the FIPS button.
  SetFIPSButton();
}

function SetFIPSButton() {
  var fipsButton = document.getElementById("fipsbutton");
  var label;
  if (secmoddb.isFIPSEnabled) {
   label = bundle.getString("disable_fips");
  } else {
   label = bundle.getString("enable_fips");
  }
  fipsButton.setAttribute("label", label);

  var can_toggle = secmoddb.canToggleFIPS;
  if (can_toggle) {
    fipsButton.removeAttribute("disabled");
  } else {
    fipsButton.setAttribute("disabled", "true");
  }
}

/* Add a module to the tree.  slots is the array of slots in the module,
 * to be represented as children.
 */
function AddModule(module, slots) {
  var tree = document.getElementById("device_list");
  var item  = document.createElement("treeitem");
  var row  = document.createElement("treerow");
  var cell = document.createElement("treecell");
  cell.setAttribute("label", module);
  row.appendChild(cell);
  item.appendChild(row);
  var parent = document.createElement("treechildren");
  for (let slot of slots) {
    var child_item = document.createElement("treeitem");
    var child_row = document.createElement("treerow");
    var child_cell = document.createElement("treecell");
    child_cell.setAttribute("label", slot);
    child_row.appendChild(child_cell);
    child_item.appendChild(child_row);
    child_item.setAttribute("pk11kind", "slot");
    parent.appendChild(child_item);
  }
  item.appendChild(parent);
  item.setAttribute("pk11kind", "module");
  item.setAttribute("open", "true");
  item.setAttribute("container", "true");
  tree.appendChild(item);
}

var selected_slot;
var selected_module;

/* get the slot selected by the user (can only be one-at-a-time) */
function getSelectedItem() {
  var tree = document.getElementById("device_tree");
  if (tree.currentIndex < 0) return;
  var item = tree.contentView.getItemAtIndex(tree.currentIndex);
  selected_slot = null;
  selected_module = null;
  if (item) {
    var kind = item.getAttribute("pk11kind");
    var module_name;
    if (kind == "slot") {
      // get the module cell for this slot cell
      var cell = item.parentNode.parentNode.firstChild.firstChild;
      module_name = cell.getAttribute("label");
      var module = secmoddb.findModuleByName(module_name);
      // get the cell for the selected row (the slot to display)
      cell = item.firstChild.firstChild;
      var slot_name = cell.getAttribute("label");
      selected_slot = module.findSlotByName(slot_name);
    } else { // (kind == "module")
      // get the cell for the selected row (the module to display)
      cell = item.firstChild.firstChild;
      module_name = cell.getAttribute("label");
      selected_module = secmoddb.findModuleByName(module_name);
    }
  }
}

function enableButtons() {
  if (skip_enable_buttons) {
    return;
  }

  var login_toggle = "true";
  var logout_toggle = "true";
  var pw_toggle = "true";
  var unload_toggle = "true";
  getSelectedItem();
  if (selected_module) {
    unload_toggle = "false";
    showModuleInfo();
  } else if (selected_slot) {
    // here's the workaround - login functions are all with token,
    // so grab the token type
    var selected_token = selected_slot.getToken();
    if (selected_token != null) {
      if (selected_token.needsLogin() || !(selected_token.needsUserInit)) {
        pw_toggle = "false";
        if (selected_token.needsLogin()) {
          if (selected_token.isLoggedIn()) {
            logout_toggle = "false";
          } else {
            login_toggle = "false";
          }
        }
      }
    }
    showSlotInfo();
  }
  document.getElementById("login_button")
          .setAttribute("disabled", login_toggle);
  document.getElementById("logout_button")
          .setAttribute("disabled", logout_toggle);
  document.getElementById("change_pw_button")
          .setAttribute("disabled", pw_toggle);
  document.getElementById("unload_button")
          .setAttribute("disabled", unload_toggle);
}

// clear the display of information for the slot
function ClearInfoList() {
  let infoList = document.getElementById("info_list");
  while (infoList.hasChildNodes()) {
    infoList.firstChild.remove();
  }
}

function ClearDeviceList() {
  ClearInfoList();

  skip_enable_buttons = true;
  var tree = document.getElementById("device_tree");
  tree.view.selection.clearSelection();
  skip_enable_buttons = false;

  // Remove the existing listed modules so that a refresh doesn't display the
  // module that just changed.
  let deviceList = document.getElementById("device_list");
  while (deviceList.hasChildNodes()) {
    deviceList.firstChild.remove();
  }
}


// show a list of info about a slot
function showSlotInfo() {
  var present = true;
  ClearInfoList();
  switch (selected_slot.status) {
   case nsIPKCS11Slot.SLOT_DISABLED:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_disabled"),
                "tok_status");
     present = false;
     break;
   case nsIPKCS11Slot.SLOT_NOT_PRESENT:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_notpresent"),
                "tok_status");
     present = false;
     break;
   case nsIPKCS11Slot.SLOT_UNINITIALIZED:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_uninitialized"),
                "tok_status");
     break;
   case nsIPKCS11Slot.SLOT_NOT_LOGGED_IN:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_notloggedin"),
                "tok_status");
     break;
   case nsIPKCS11Slot.SLOT_LOGGED_IN:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_loggedin"),
                "tok_status");
     break;
   case nsIPKCS11Slot.SLOT_READY:
     AddInfoRow(bundle.getString("devinfo_status"),
                bundle.getString("devinfo_stat_ready"),
                "tok_status");
     break;
   default:
     return;
  }
  AddInfoRow(bundle.getString("devinfo_desc"),
             selected_slot.desc, "slot_desc");
  AddInfoRow(bundle.getString("devinfo_manID"),
             selected_slot.manID, "slot_manID");
  AddInfoRow(bundle.getString("devinfo_hwversion"),
             selected_slot.HWVersion, "slot_hwv");
  AddInfoRow(bundle.getString("devinfo_fwversion"),
             selected_slot.FWVersion, "slot_fwv");
  if (present) {
     showTokenInfo();
  }
}

function showModuleInfo() {
  ClearInfoList();
  AddInfoRow(bundle.getString("devinfo_modname"),
             selected_module.name, "module_name");
  AddInfoRow(bundle.getString("devinfo_modpath"),
             selected_module.libName, "module_path");
}

// add a row to the info list, as [col1 col2] (ex.: ["status" "logged in"])
function AddInfoRow(col1, col2, cell_id) {
  var tree = document.getElementById("info_list");
  var item  = document.createElement("treeitem");
  var row  = document.createElement("treerow");
  var cell1 = document.createElement("treecell");
  cell1.setAttribute("label", col1);
  cell1.setAttribute("crop", "never");
  row.appendChild(cell1);
  var cell2 = document.createElement("treecell");
  cell2.setAttribute("label", col2);
  cell2.setAttribute("crop", "never");
  cell2.setAttribute("id", cell_id);
  row.appendChild(cell2);
  item.appendChild(row);
  tree.appendChild(item);
}

// log in to a slot
function doLogin() {
  getSelectedItem();
  // here's the workaround - login functions are with token
  var selected_token = selected_slot.getToken();
  try {
    selected_token.login(false);
    var tok_status = document.getElementById("tok_status");
    if (selected_token.isLoggedIn()) {
      tok_status.setAttribute("label",
                              bundle.getString("devinfo_stat_loggedin"));
    } else {
      tok_status.setAttribute("label",
                              bundle.getString("devinfo_stat_notloggedin"));
    }
  } catch (e) {
    doPrompt(bundle.getString("login_failed"));
  }
  enableButtons();
}

// log out of a slot
function doLogout() {
  getSelectedItem();
  // here's the workaround - login functions are with token
  var selected_token = selected_slot.getToken();
  try {
    selected_token.logoutAndDropAuthenticatedResources();
    var tok_status = document.getElementById("tok_status");
    if (selected_token.isLoggedIn()) {
      tok_status.setAttribute("label",
                              bundle.getString("devinfo_stat_loggedin"));
    } else {
      tok_status.setAttribute("label",
                              bundle.getString("devinfo_stat_notloggedin"));
    }
  } catch (e) {
  }
  enableButtons();
}

// load a new device
function doLoad() {
  window.open("load_device.xul", "loaddevice", "chrome,centerscreen,modal");
  ClearDeviceList();
  RefreshDeviceList();
}

function deleteSelected() {
  getSelectedItem();
  if (selected_module &&
      doConfirm(getNSSString("DelModuleWarning"))) {
    try {
      getPKCS11().deleteModule(selected_module.name);
    } catch (e) {
      doPrompt(getNSSString("DelModuleError"));
      return false;
    }
    selected_module = null;
    return true;
  }
  return false;
}

function doUnload() {
  if (deleteSelected()) {
    ClearDeviceList();
    RefreshDeviceList();
  }
}

// handle card insertion and removal
function onSmartCardChange() {
  var tree = document.getElementById("device_tree");
  var index = tree.currentIndex;
  tree.currentIndex = 0;
  ClearDeviceList();
  RefreshDeviceList();
  tree.currentIndex = index;
  enableButtons();
}

function changePassword() {
  getSelectedItem();
  let params = Components.classes[nsDialogParamBlock]
                         .createInstance(nsIDialogParamBlock);
  params.SetString(1, selected_slot.tokenName);
  window.openDialog("changepassword.xul", "", "chrome,centerscreen,modal",
                    params);
  showSlotInfo();
  enableButtons();
}

// -------------------------------------   Old code

function showTokenInfo() {
  var selected_token = selected_slot.getToken();
  AddInfoRow(bundle.getString("devinfo_label"),
             selected_token.tokenLabel, "tok_label");
  AddInfoRow(bundle.getString("devinfo_manID"),
             selected_token.tokenManID, "tok_manID");
  AddInfoRow(bundle.getString("devinfo_serialnum"),
             selected_token.tokenSerialNumber, "tok_sNum");
  AddInfoRow(bundle.getString("devinfo_hwversion"),
             selected_token.tokenHWVersion, "tok_hwv");
  AddInfoRow(bundle.getString("devinfo_fwversion"),
             selected_token.tokenFWVersion, "tok_fwv");
}

function toggleFIPS() {
  if (!secmoddb.isFIPSEnabled) {
    // A restriction of FIPS mode is, the password must be set
    // In FIPS mode the password must be non-empty.
    // This is different from what we allow in NON-Fips mode.

    var tokendb = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
    var internal_token = tokendb.getInternalKeyToken(); // nsIPK11Token
    if (!internal_token.hasPassword) {
      // Token has either no or an empty password.
      doPrompt(bundle.getString("fips_nonempty_password_required"));
      return;
    }
  }

  try {
    secmoddb.toggleFIPSMode();
  } catch (e) {
    doPrompt(bundle.getString("unable_to_toggle_fips"));
    return;
  }

  // Remove the existing listed modules so that a refresh doesn't display the
  // module that just changed.
  ClearDeviceList();

  RefreshDeviceList();
}
PK
!<Fyw

/chrome/pippki/content/pippki/device_manager.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % deviceManangerDTD SYSTEM "chrome://pippki/locale/deviceManager.dtd">
%deviceManangerDTD;
<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" >
%pippkiDTD;
]>

<dialog id="devicemanager"
	windowtype="mozilla:devicemanager"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&devmgr.title;"
        style="&devmgr.style2;"
        persist="screenX screenY width height"
        onload="LoadModules();"
        onunload="DeregisterSmartCardObservers();"
        buttons="accept">

<stringbundleset id="stringbundleset">
  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
  <stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/>
</stringbundleset>

<script type="application/javascript" src="chrome://pippki/content/device_manager.js"/>

<grid flex="1" style="margin:5px">
  <columns>
    <column flex="1"/>
    <column flex="3"/>
    <column/>
  </columns>
  <rows>
    <row flex="1">
      <vbox> <!-- List of devices -->
        <tree id="device_tree" seltype="single"
              onselect="enableButtons();" hidecolumnpicker="true"
              flex="1" style="min-width: 15em">
          <treecols>
            <treecol id="deviceCol" flex="1" primary="true" label="&devmgr.devlist.label;"/>
          </treecols>
          <treechildren id="device_list"/>
        </tree>
      </vbox> <!-- / List of devices -->
      <vbox> <!-- Device status -->
        <tree id="info_tree" seltype="single" hidecolumnpicker="true"
              flex="1" style="min-width: 10em">
          <treecols>
            <treecol id="title1Col" flex="5" primary="true" label="&devmgr.details.title;"/>
            <treecol id="title2Col" flex="7" label="&devmgr.details.title2;"/>
          </treecols>
          <treechildren id="info_list"/>
        </tree>
      </vbox> <!-- / Device status -->
      <vbox> <!-- Buttons for manipulating devices -->
        <button id="login_button"
                label="&devmgr.button.login.label;"
                accesskey="&devmgr.button.login.accesskey;"
                oncommand="doLogin();" disabled="true"/>
        <button id="logout_button"
                label="&devmgr.button.logout.label;"
                accesskey="&devmgr.button.logout.accesskey;"
                oncommand="doLogout();" disabled="true"/>
        <button id="change_pw_button"
                label="&devmgr.button.changepw.label;"
                accesskey="&devmgr.button.changepw.accesskey;"
                oncommand="changePassword();" disabled="true"/>
        <button id="load_button"
                label="&devmgr.button.load.label;"
                accesskey="&devmgr.button.load.accesskey;"
                oncommand="doLoad();"/>
        <button id="unload_button"
                label="&devmgr.button.unload.label;"
                accesskey="&devmgr.button.unload.accesskey;"
                oncommand="doUnload();" disabled="true"/>
        <button id="fipsbutton"
                label=""
                accesskey="&devmgr.button.fips.accesskey;"
                oncommand="toggleFIPS();"/>
      </vbox> <!-- / Buttons for manipulating devices -->
    </row>
  </rows>
</grid>

</dialog>
PK
!<0g99,chrome/pippki/content/pippki/downloadcert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

/**
 * @file Implements the functionality of downloadcert.xul: a dialog that allows
 *       a user to confirm whether to import a certificate, and if so what trust
 *       to give it.
 * @argument {nsISupports} window.arguments[0]
 *           Certificate to confirm import of, queryable to nsIX509Cert.
 * @argument {nsISupports} window.arguments[1]
 *           Object to set the return values of calling the dialog on, queryable
 *           to the underlying type of DownloadCertReturnValues.
 */

/**
 * @typedef DownloadCertReturnValues
 * @type nsIWritablePropertyBag2
 * @property {Boolean} importConfirmed
 *           Set to true if the user confirmed import of the cert and accepted
 *           the dialog, false otherwise.
 * @property {Boolean} trustForSSL
 *           Set to true if the cert should be trusted for SSL, false otherwise.
 *           Undefined value if |importConfirmed| is not true.
 * @property {Boolean} trustForEmail
 *           Set to true if the cert should be trusted for e-mail, false
 *           otherwise. Undefined value if |importConfirmed| is not true.
 * @property {Boolean} trustForObjSign
 *           Set to true if the cert should be trusted for object signing, false
 *           otherwise. Undefined value if |importConfirmed| is not true.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

/**
 * The cert to potentially import.
 * @type nsIX509Cert
 */
var gCert;

/**
 * onload() handler.
 */
function onLoad() {
  gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);

  let bundle = document.getElementById("pippki_bundle");
  let caName = gCert.commonName;
  if (caName.length == 0) {
    caName = bundle.getString("unnamedCA");
  }

  setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName]));
}

/**
 * Handler for the "View Cert" button.
 */
function viewCert() {
  viewCertHelper(window, gCert);
}

/**
 * ondialogaccept() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogAccept() {
  let checkSSL = document.getElementById("trustSSL");
  let checkEmail = document.getElementById("trustEmail");
  let checkObjSign = document.getElementById("trustObjSign");

  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("importConfirmed", true);
  retVals.setPropertyAsBool("trustForSSL", checkSSL.checked);
  retVals.setPropertyAsBool("trustForEmail", checkEmail.checked);
  retVals.setPropertyAsBool("trustForObjSign", checkObjSign.checked);
  return true;
}

/**
 * ondialogcancel() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogCancel() {
  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("importConfirmed", false);
  return true;
}
PK
!<9Z-chrome/pippki/content/pippki/downloadcert.xul<?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://pippki/locale/pippki.dtd">

<dialog id="download_cert"
        title="&downloadCert.title;"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        style="width: 46em;"
        buttons="accept,cancel"
        ondialogaccept="return onDialogAccept();"
        ondialogcancel="return onDialogCancel();"
        onload="onLoad();">

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript" src="chrome://pippki/content/downloadcert.js"/>
<script type="application/javascript" src="chrome://pippki/content/pippki.js"/>


  <!--  Let 'em know what they're doing -->
  <vbox>
    <description>&downloadCert.message1;</description>
  </vbox>

  <separator/>

  <!--  checkboxes for trust bits
     -  "do you want to?"
     -  * trust for SSL
     -  * trust for email
     -  * trust for object signing
    -->
  <vbox>
    <description id="trustHeader"/>
    <checkbox label="&downloadCert.trustSSL;"
              id="trustSSL"/>
    <checkbox label="&downloadCert.trustEmail;"
              id="trustEmail"/>
    <checkbox label="&downloadCert.trustObjSign;"
              id="trustObjSign"/>
  </vbox>

  <separator/>

  <vbox>
    <description>&downloadCert.message3;</description>
    <separator/>
    <grid>
      <columns>
        <column/>
        <column/>
      </columns>
      <rows>
        <row>
          <button id="viewC-button"
                  label="&downloadCert.viewCert.label;"
                  oncommand="viewCert();"/>
          <description style="margin: 4px;">&downloadCert.viewCert.text;</description>
        </row>
      </rows>
    </grid>
  </vbox>

</dialog>
PK
!<tiLL*chrome/pippki/content/pippki/editcacert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                .getService(Ci.nsIX509CertDB);
/**
 * Cert to edit the trust of.
 * @type nsIX509Cert
 */
var gCert;

/**
 * onload() handler.
 */
function onLoad() {
  gCert = window.arguments[0];

  let bundle = document.getElementById("pippki_bundle");
  setText("certmsg",
          bundle.getFormattedString("editTrustCA", [gCert.commonName]));

  let sslCheckbox = document.getElementById("trustSSL");
  sslCheckbox.checked = gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
                                              Ci.nsIX509CertDB.TRUSTED_SSL);

  let emailCheckbox = document.getElementById("trustEmail");
  emailCheckbox.checked = gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
                                                Ci.nsIX509CertDB.TRUSTED_EMAIL);

  let objSignCheckbox = document.getElementById("trustObjSign");
  objSignCheckbox.checked =
    gCertDB.isCertTrusted(gCert, Ci.nsIX509Cert.CA_CERT,
                          Ci.nsIX509CertDB.TRUSTED_OBJSIGN);
}

/**
 * ondialogaccept() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogAccept() {
  let sslCheckbox = document.getElementById("trustSSL");
  let emailCheckbox = document.getElementById("trustEmail");
  let objSignCheckbox = document.getElementById("trustObjSign");
  let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0;
  let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0;
  let trustObjSign = objSignCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_OBJSIGN
                                             : 0;

  gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT,
                       trustSSL | trustEmail | trustObjSign);
  return true;
}
PK
!<IP+chrome/pippki/content/pippki/editcacert.xul<?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://pippki/locale/certManager.dtd">

<dialog id="editCaCert"
        title="&certmgr.editcacert.title;"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        buttons="accept,cancel"
        ondialogaccept="return onDialogAccept();"
        onload="onLoad();"
>

  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

  <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
  <script type="application/javascript"
          src="chrome://pippki/content/editcacert.js"/>

  <description id="certmsg"/>
  <separator/>
  <description>&certmgr.editcert.edittrust;</description>
  <vbox align="start">
    <checkbox label="&certmgr.editcert.trustssl;"
              id="trustSSL"/>
    <checkbox label="&certmgr.editcert.trustemail;"
              id="trustEmail"/>
    <checkbox label="&certmgr.editcert.trustobjsign;"
              id="trustObjSign"/>
  </vbox>

</dialog>
PK
!<uB0B0/chrome/pippki/content/pippki/exceptionDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

var gDialog;
var gBundleBrand;
var gPKIBundle;
var gSSLStatus;
var gCert;
var gChecking;
var gBroken;
var gNeedReset;
var gSecHistogram;
var gNsISecTel;

Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");

function badCertListener() {}
badCertListener.prototype = {
  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },
  QueryInterface(aIID) {
    if (aIID.equals(Components.interfaces.nsIBadCertListener2) ||
        aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
        aIID.equals(Components.interfaces.nsISupports)) {
      return this;
    }

    throw new Error(Components.results.NS_ERROR_NO_INTERFACE);
  },
  handle_test_result() {
    if (gSSLStatus) {
      gCert = gSSLStatus.QueryInterface(Components.interfaces.nsISSLStatus).serverCert;
    }
  },
  notifyCertProblem: function MSR_notifyCertProblem(socketInfo, sslStatus, targetHost) {
    gBroken = true;
    gSSLStatus = sslStatus;
    this.handle_test_result();
    return true; // suppress error UI
  }
};

function initExceptionDialog() {
  gNeedReset = false;
  gDialog = document.documentElement;
  gBundleBrand = document.getElementById("brand_bundle");
  gPKIBundle = document.getElementById("pippki_bundle");
  gSecHistogram = Components.classes["@mozilla.org/base/telemetry;1"].
                    getService(Components.interfaces.nsITelemetry).
                    getHistogramById("SECURITY_UI");
  gNsISecTel = Components.interfaces.nsISecurityUITelemetry;

  var brandName = gBundleBrand.getString("brandShortName");
  setText("warningText", gPKIBundle.getFormattedString("addExceptionBrandedWarning2", [brandName]));
  gDialog.getButton("extra1").disabled = true;

  var args = window.arguments;
  if (args && args[0]) {
    if (args[0].location) {
      // We were pre-seeded with a location.
      document.getElementById("locationTextBox").value = args[0].location;
      document.getElementById("checkCertButton").disabled = false;

      if (args[0].sslStatus) {
        gSSLStatus = args[0].sslStatus;
        gCert = gSSLStatus.serverCert;
        gBroken = true;
        updateCertStatus();
      } else if (args[0].prefetchCert) {
        // We can optionally pre-fetch the certificate too.  Don't do this
        // synchronously, since it would prevent the window from appearing
        // until the fetch is completed, which could be multiple seconds.
        // Instead, let's use a timer to spawn the actual fetch, but update
        // the dialog to "checking..." state right away, so that the UI
        // is appropriately responsive.  Bug 453855
        document.getElementById("checkCertButton").disabled = true;
        gChecking = true;
        updateCertStatus();

        window.setTimeout(checkCert, 0);
      }
    }

    // Set out parameter to false by default
    args[0].exceptionAdded = false;
  }
}

/**
 * Attempt to download the certificate for the location specified, and populate
 * the Certificate Status section with the result.
 */
function checkCert() {
  gCert = null;
  gSSLStatus = null;
  gChecking = true;
  gBroken = false;
  updateCertStatus();

  var uri = getURI();

  var req = new XMLHttpRequest();
  try {
    if (uri) {
      req.open("GET", uri.prePath, false);
      req.channel.notificationCallbacks = new badCertListener();
      req.send(null);
    }
  } catch (e) {
    // We *expect* exceptions if there are problems with the certificate
    // presented by the site.  Log it, just in case, but we can proceed here,
    // with appropriate sanity checks
    Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " +
                                 "This results in a (mostly harmless) exception being thrown. " +
                                 "Logged for information purposes only: " + e);
  } finally {
    gChecking = false;
  }

  if (req.channel && req.channel.securityInfo) {
    const Ci = Components.interfaces;
    gSSLStatus = req.channel.securityInfo
                    .QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
    gCert = gSSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
  }

  updateCertStatus();
}

/**
 * Build and return a URI, based on the information supplied in the
 * Certificate Location fields
 *
 * @returns {nsIURI}
 *          URI constructed from the information supplied on success, null
 *          otherwise.
 */
function getURI() {
  // Use fixup service instead of just ioservice's newURI since it's quite
  // likely that the host will be supplied without a protocol prefix, resulting
  // in malformed uri exceptions being thrown.
  let fus = Components.classes["@mozilla.org/docshell/urifixup;1"]
                      .getService(Components.interfaces.nsIURIFixup);
  let locationTextBox = document.getElementById("locationTextBox");
  let uri = fus.createFixupURI(locationTextBox.value, 0);

  if (!uri) {
    return null;
  }

  if (uri.scheme == "http") {
    uri.scheme = "https";
  }

  if (uri.port == -1) {
    uri.port = 443;
  }

  return uri;
}

function resetDialog() {
  document.getElementById("viewCertButton").disabled = true;
  document.getElementById("permanent").disabled = true;
  gDialog.getButton("extra1").disabled = true;
  setText("headerDescription", "");
  setText("statusDescription", "");
  setText("statusLongDescription", "");
  setText("status2Description", "");
  setText("status2LongDescription", "");
  setText("status3Description", "");
  setText("status3LongDescription", "");
}

/**
 * Called by input textboxes to manage UI state
 */
function handleTextChange() {
  var checkCertButton = document.getElementById("checkCertButton");
  checkCertButton.disabled = !(document.getElementById("locationTextBox").value);
  if (gNeedReset) {
    gNeedReset = false;
    resetDialog();
  }
}

function updateCertStatus() {
  var shortDesc, longDesc;
  var shortDesc2, longDesc2;
  var shortDesc3, longDesc3;
  var use2 = false;
  var use3 = false;
  let bucketId = gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASE;
  if (gCert) {
    if (gBroken) {
      var mms = "addExceptionDomainMismatchShort";
      var mml = "addExceptionDomainMismatchLong2";
      var exs = "addExceptionExpiredShort";
      var exl = "addExceptionExpiredLong2";
      var uts = "addExceptionUnverifiedOrBadSignatureShort";
      var utl = "addExceptionUnverifiedOrBadSignatureLong2";
      var use1 = false;
      if (gSSLStatus.isDomainMismatch) {
        bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN;
        use1 = true;
        shortDesc = mms;
        longDesc  = mml;
      }
      if (gSSLStatus.isNotValidAtThisTime) {
        bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME;
        if (!use1) {
          use1 = true;
          shortDesc = exs;
          longDesc  = exl;
        } else {
          use2 = true;
          shortDesc2 = exs;
          longDesc2  = exl;
        }
      }
      if (gSSLStatus.isUntrusted) {
        bucketId += gNsISecTel.WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED;
        if (!use1) {
          use1 = true;
          shortDesc = uts;
          longDesc  = utl;
        } else if (!use2) {
          use2 = true;
          shortDesc2 = uts;
          longDesc2  = utl;
        } else {
          use3 = true;
          shortDesc3 = uts;
          longDesc3  = utl;
        }
      }
      gSecHistogram.add(bucketId);

      // In these cases, we do want to enable the "Add Exception" button
      gDialog.getButton("extra1").disabled = false;

      // If the Private Browsing service is available and the mode is active,
      // don't store permanent exceptions, since they would persist after
      // private browsing mode was disabled.
      var inPrivateBrowsing = inPrivateBrowsingMode();
      var pe = document.getElementById("permanent");
      pe.disabled = inPrivateBrowsing;
      pe.checked = !inPrivateBrowsing;

      setText("headerDescription", gPKIBundle.getString("addExceptionInvalidHeader"));
    } else {
      shortDesc = "addExceptionValidShort";
      longDesc  = "addExceptionValidLong";
      gDialog.getButton("extra1").disabled = true;
      document.getElementById("permanent").disabled = true;
    }

    // We're done checking the certificate, so allow the user to check it again.
    document.getElementById("checkCertButton").disabled = false;
    document.getElementById("viewCertButton").disabled = false;

    // Notify observers about the availability of the certificate
    Components.classes["@mozilla.org/observer-service;1"]
              .getService(Components.interfaces.nsIObserverService)
              .notifyObservers(null, "cert-exception-ui-ready");
  } else if (gChecking) {
    shortDesc = "addExceptionCheckingShort";
    longDesc  = "addExceptionCheckingLong2";
    // We're checking the certificate, so we disable the Get Certificate
    // button to make sure that the user can't interrupt the process and
    // trigger another certificate fetch.
    document.getElementById("checkCertButton").disabled = true;
    document.getElementById("viewCertButton").disabled = true;
    gDialog.getButton("extra1").disabled = true;
    document.getElementById("permanent").disabled = true;
  } else {
    shortDesc = "addExceptionNoCertShort";
    longDesc  = "addExceptionNoCertLong2";
    // We're done checking the certificate, so allow the user to check it again.
    document.getElementById("checkCertButton").disabled = false;
    document.getElementById("viewCertButton").disabled = true;
    gDialog.getButton("extra1").disabled = true;
    document.getElementById("permanent").disabled = true;
  }

  setText("statusDescription", gPKIBundle.getString(shortDesc));
  setText("statusLongDescription", gPKIBundle.getString(longDesc));

  if (use2) {
    setText("status2Description", gPKIBundle.getString(shortDesc2));
    setText("status2LongDescription", gPKIBundle.getString(longDesc2));
  }

  if (use3) {
    setText("status3Description", gPKIBundle.getString(shortDesc3));
    setText("status3LongDescription", gPKIBundle.getString(longDesc3));
  }

  window.sizeToContent();

  gNeedReset = true;
}

/**
 * Handle user request to display certificate details
 */
function viewCertButtonClick() {
  gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_CLICK_VIEW_CERT);
  if (gCert) {
    viewCertHelper(this, gCert);
  }
}

/**
 * Handle user request to add an exception for the specified cert
 */
function addException() {
  if (!gCert || !gSSLStatus) {
    return;
  }

  var overrideService = Components.classes["@mozilla.org/security/certoverride;1"]
                                  .getService(Components.interfaces.nsICertOverrideService);
  var flags = 0;
  let confirmBucketId = gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE;
  if (gSSLStatus.isUntrusted) {
    flags |= overrideService.ERROR_UNTRUSTED;
    confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_UNTRUSTED;
  }
  if (gSSLStatus.isDomainMismatch) {
    flags |= overrideService.ERROR_MISMATCH;
    confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_DOMAIN;
  }
  if (gSSLStatus.isNotValidAtThisTime) {
    flags |= overrideService.ERROR_TIME;
    confirmBucketId += gNsISecTel.WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_FLAG_TIME;
  }

  var permanentCheckbox = document.getElementById("permanent");
  var shouldStorePermanently = permanentCheckbox.checked && !inPrivateBrowsingMode();
  if (!permanentCheckbox.checked) {
    gSecHistogram.add(gNsISecTel.WARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTION);
  }

  gSecHistogram.add(confirmBucketId);
  var uri = getURI();
  overrideService.rememberValidityOverride(
    uri.asciiHost, uri.port,
    gCert,
    flags,
    !shouldStorePermanently);

  let args = window.arguments;
  if (args && args[0]) {
    args[0].exceptionAdded = true;
  }

  gDialog.acceptDialog();
}

/**
 * @returns {Boolean} Whether this dialog is in private browsing mode.
 */
function inPrivateBrowsingMode() {
  return PrivateBrowsingUtils.isWindowPrivate(window);
}
PK
!<>0chrome/pippki/content/pippki/exceptionDialog.xul<?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://pippki/locale/certManager.dtd">

<dialog id="exceptiondialog"
        windowtype="mozilla:exceptiondialog"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&exceptionMgr.title;"
        buttons="cancel,extra1,extra2"
        buttonlabelextra1="&exceptionMgr.exceptionButton.label;"
        buttonaccesskeyextra1="&exceptionMgr.exceptionButton.accesskey;"
        style="width: 46em; min-height: 38em;"
        onload="initExceptionDialog();"
        ondialogextra1="addException();"
        ondialogextra2="checkCert();"
        persist="screenX screenY width height"
        defaultButton="extra2">

  <stringbundleset id="stringbundleset">
    <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
    <stringbundle id="brand_bundle" src="chrome://branding/locale/brand.properties"/>
  </stringbundleset>

  <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
  <script type="application/javascript" src="chrome://pippki/content/exceptionDialog.js"/>

  <hbox>
    <vbox>
      <image src="chrome://global/skin/icons/warning-large.png"/>
      <spacer flex="1"/>
    </vbox>
    <vbox flex="1">
      <!-- Note that because of the styling, there must be no whitespace within
      the description tags -->
      <description id="warningText"
                   style="white-space: pre-wrap"/>
      <description id="warningSupplemental"
                   style="font-weight: bold; white-space: pre-wrap;"
                   >&exceptionMgr.supplementalWarning;</description>
    </vbox>
  </hbox>

  <groupbox id="locationGroupBox">
    <caption label="&exceptionMgr.certlocation.caption2;"/>
    <hbox align="center">
      <label control="locationTextBox" value="&exceptionMgr.certlocation.url;"/>
      <textbox id="locationTextBox" flex="1" oninput="handleTextChange();"
               value="https://" class="uri-element"/>
      <button id="checkCertButton" disabled="true" dlgtype="extra2"
              accesskey="&exceptionMgr.certlocation.accesskey;"
              label="&exceptionMgr.certlocation.download;"/>
    </hbox>
  </groupbox>

  <groupbox id="certStatusGroupBox" flex="1">
    <caption label="&exceptionMgr.certstatus.caption;"/>
    <hbox>
      <description id="headerDescription" style="white-space: pre-wrap;"
                   flex="1"/>
      <vbox>
        <button id="viewCertButton" label="&exceptionMgr.certstatus.viewCert;"
                accesskey="&exceptionMgr.certstatus.accesskey;"
                disabled="true" oncommand="viewCertButtonClick();"/>
      </vbox>
    </hbox>
    <description id="statusDescription"
                 style="font-weight: bold; padding-bottom: 1em;"/>
    <description id="statusLongDescription" style="white-space: pre-wrap;"/>
    <description id="status2Description"
                 style="font-weight: bold; padding-bottom: 1em;"/>
    <description id="status2LongDescription" style="white-space: pre-wrap;"/>
    <description id="status3Description"
                 style="font-weight: bold; padding-bottom: 1em;"/>
    <description id="status3LongDescription" style="white-space: pre-wrap;"/>
    <spacer flex="1"/>
    <checkbox id="permanent" disabled="true"
              label="&exceptionMgr.permanent.label;"
              accesskey="&exceptionMgr.permanent.accesskey;"/>
  </groupbox>
</dialog>
PK
!<p!+chrome/pippki/content/pippki/load_device.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

/**
 * @file Implements the functionality of load_device.xul: a dialog that allows
 *       a PKCS #11 module to be loaded into Firefox.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

function onBrowseBtnPress() {
  let bundle = document.getElementById("pippki_bundle");
  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  fp.init(window, bundle.getString("loadPK11ModuleFilePickerTitle"),
          Ci.nsIFilePicker.modeOpen);
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == Ci.nsIFilePicker.returnOK) {
      document.getElementById("device_path").value = fp.file.path;
    }

    // This notification gets sent solely for test purposes. It should not be
    // used by production code.
    Services.obs.notifyObservers(window, "LoadPKCS11Module:FilePickHandled");
  });
}

/**
 * ondialogaccept() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogAccept() {
  let bundle = document.getElementById("pipnss_bundle");
  let nameBox = document.getElementById("device_name");
  let pathBox = document.getElementById("device_path");
  let pkcs11 = Cc["@mozilla.org/security/pkcs11;1"].getService(Ci.nsIPKCS11);

  try {
    pkcs11.addModule(nameBox.value, pathBox.value, 0, 0);
  } catch (e) {
    alertPromptService(null, bundle.getString("AddModuleFailure"));
    return false;
  }

  return true;
}
PK
!<$ܗԠ,chrome/pippki/content/pippki/load_device.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE dialog [
<!ENTITY % deviceManangerDTD SYSTEM "chrome://pippki/locale/deviceManager.dtd">
%deviceManangerDTD;
]>

<dialog id="loaddevice"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        title="&loaddevice.title2;"
        buttons="accept,cancel"
        ondialogaccept="return onDialogAccept();">

<stringbundleset id="stringbundleset">
  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
  <stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/>
</stringbundleset>

  <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
  <script type="application/javascript"
          src="chrome://pippki/content/load_device.js"/>

  <description>&loaddevice.info;</description>
  <hbox align="center">
    <label value="&loaddevice.modname;" accesskey="&loaddevice.modname.accesskey;"
           control="device_name"/>
    <textbox id="device_name" flex="1" value="&loaddevice.modname.default;"/>
  </hbox>
  <hbox align="center">
    <label value="&loaddevice.filename;" accesskey="&loaddevice.filename.accesskey;"
           control="device_path"/>
    <textbox id="device_path" flex="1"/>
    <button id="browse" label="&loaddevice.browse;" flex="1"
            accesskey="&loaddevice.browse.accesskey;"
            oncommand="onBrowseBtnPress();"/>
  </hbox>

</dialog>
PK
!<4&chrome/pippki/content/pippki/pippki.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";

/*
 * These are helper functions to be included
 * pippki UI js files.
 */

function setText(id, value) {
  let element = document.getElementById(id);
  if (!element) {
    return;
  }
  if (element.hasChildNodes()) {
    element.firstChild.remove();
  }
  element.appendChild(document.createTextNode(value));
}

const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
const nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1";

function viewCertHelper(parent, cert) {
  if (!cert) {
    return;
  }

  var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs);
  cd.viewCert(parent, cert);
}

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 getPKCS7String(cert, chainMode) {
  var length = {};
  var pkcs7Array = cert.exportAsCMS(chainMode, length);
  var pkcs7String = "";
  for (var i = 0; i < pkcs7Array.length; i++) {
    pkcs7String += String.fromCharCode(pkcs7Array[i]);
  }
  return pkcs7String;
}

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";
}

function alertPromptService(title, message) {
  var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
           getService(Components.interfaces.nsIPromptService);
  ps.alert(window, title, message);
}

const DEFAULT_CERT_EXTENSION = "crt";

/**
 * Generates a filename for a cert suitable to set as the |defaultString|
 * attribute on an nsIFilePicker.
 *
 * @param {nsIX509Cert} cert
 *        The cert to generate a filename for.
 * @returns {String}
 *          Generated filename.
 */
function certToFilename(cert) {
  let filename = cert.displayName;

  // Remove unneeded and/or unsafe characters.
  filename = filename.replace(/\s/g, "")
                     .replace(/\./g, "")
                     .replace(/\\/g, "")
                     .replace(/\//g, "");

  // nsIFilePicker.defaultExtension is more of a suggestion to some
  // implementations, so we include the extension in the file name as well. This
  // is what the documentation for nsIFilePicker.defaultString says we should do
  // anyways.
  return `${filename}.${DEFAULT_CERT_EXTENSION}`;
}

function exportToFile(parent, cert) {
  var bundle = document.getElementById("pippki_bundle");
  if (!cert) {
    return;
  }

  var nsIFilePicker = Components.interfaces.nsIFilePicker;
  var fp = Components.classes["@mozilla.org/filepicker;1"].
           createInstance(nsIFilePicker);
  fp.init(parent, bundle.getString("SaveCertAs"),
          nsIFilePicker.modeSave);
  fp.defaultString = certToFilename(cert);
  fp.defaultExtension = DEFAULT_CERT_EXTENSION;
  fp.appendFilter(bundle.getString("CertFormatBase64"), "*.crt; *.pem");
  fp.appendFilter(bundle.getString("CertFormatBase64Chain"), "*.crt; *.pem");
  fp.appendFilter(bundle.getString("CertFormatDER"), "*.der");
  fp.appendFilter(bundle.getString("CertFormatPKCS7"), "*.p7c");
  fp.appendFilter(bundle.getString("CertFormatPKCS7Chain"), "*.p7c");
  fp.appendFilters(nsIFilePicker.filterAll);
  var res = fp.show();
  if (res != nsIFilePicker.returnOK && res != nsIFilePicker.returnReplace) {
    return;
  }

  var content = "";
  switch (fp.filterIndex) {
    case 1:
      content = getPEMString(cert);
      var chain = cert.getChain();
      for (let i = 1; i < chain.length; i++) {
        content += getPEMString(chain.queryElementAt(i, Components.interfaces.nsIX509Cert));
      }
      break;
    case 2:
      content = getDERString(cert);
      break;
    case 3:
      content = getPKCS7String(cert, Components.interfaces.nsIX509Cert.CMS_CHAIN_MODE_CertOnly);
      break;
    case 4:
      content = getPKCS7String(cert, Components.interfaces.nsIX509Cert.CMS_CHAIN_MODE_CertChainWithRoot);
      break;
    case 0:
    default:
      content = getPEMString(cert);
      break;
  }
  var msg;
  var written = 0;
  try {
    var file = Components.classes["@mozilla.org/file/local;1"].
               createInstance(Components.interfaces.nsILocalFile);
    file.initWithPath(fp.file.path);
    var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
              createInstance(Components.interfaces.nsIFileOutputStream);
    // flags: PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
    fos.init(file, 0x02 | 0x08 | 0x20, 0o0644, 0);
    written = fos.write(content, content.length);
    fos.close();
  } catch (e) {
    switch (e.result) {
      case Components.results.NS_ERROR_FILE_ACCESS_DENIED:
        msg = bundle.getString("writeFileAccessDenied");
        break;
      case Components.results.NS_ERROR_FILE_IS_LOCKED:
        msg = bundle.getString("writeFileIsLocked");
        break;
      case Components.results.NS_ERROR_FILE_NO_DEVICE_SPACE:
      case Components.results.NS_ERROR_FILE_DISK_FULL:
        msg = bundle.getString("writeFileNoDeviceSpace");
        break;
      default:
        msg = e.message;
        break;
    }
  }
  if (written != content.length) {
    if (msg.length == 0) {
      msg = bundle.getString("writeFileUnknownError");
    }
    alertPromptService(bundle.getString("writeFileFailure"),
                       bundle.getFormattedString("writeFileFailed",
                       [fp.file.path, msg]));
  }
}
PK
!<Q-chrome/pippki/content/pippki/protectedAuth.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

function onLoad() {
  let protectedAuthThread =
    window.arguments[0].QueryInterface(Components.interfaces.nsIProtectedAuthThread);

  if (!protectedAuthThread) {
    window.close();
    return;
  }

  try {
    let tokenName = protectedAuthThread.getTokenName();

    let tag = document.getElementById("tokenName");
    tag.setAttribute("value", tokenName);

    window.setCursor("wait");

    let obs = {
      observe: function protectedAuthListenerObserve(subject, topic, data) {
        if (topic == "operation-completed") {
          window.close();
        }
      }
    };

    protectedAuthThread.login(obs);
  } catch (exception) {
    window.close();
  }
}

function onClose() {
  window.setCursor("auto");
}
PK
!<F/::.chrome/pippki/content/pippki/protectedAuth.xul<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<!DOCTYPE window SYSTEM "chrome://pippki/locale/pippki.dtd">

<window
  id="protectedAuth" title="&protectedAuth.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"      
  onload="onLoad();"
  onclose="onClose();"
>

<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

<script type="application/javascript" src="pippki.js" />
<script type="application/javascript" src="protectedAuth.js" />
<script type="application/javascript" src="chrome://help/content/help.js" />

<vbox style="margin: 5px; max-width: 50em;">

  <description>&protectedAuth.msg;</description>

  <hbox>
         <description>&protectedAuth.tokenName.label;</description>
         <description id="tokenName"></description>
  </hbox>

</vbox>
</window>
PK
!<Y#-chrome/pippki/content/pippki/resetpassword.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.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 pippki.js */
"use strict";

function resetPassword() {
  var pk11db = Components.classes["@mozilla.org/security/pk11tokendb;1"]
                                 .getService(Components.interfaces.nsIPK11TokenDB);
  var token = pk11db.getInternalKeyToken();
  token.reset();

  try {
    var loginManager = Components.classes["@mozilla.org/login-manager;1"].
                       getService(Components.interfaces.nsILoginManager);
    loginManager.removeAllLogins();
  } catch (e) {
  }

  var bundle = document.getElementById("pippki_bundle");
  var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  if (promptService && bundle) {
    promptService.alert(window,
                        bundle.getString("resetPasswordConfirmationTitle"),
                        bundle.getString("resetPasswordConfirmationMessage"));
  }

  return true;
}
PK
!<?>.chrome/pippki/content/pippki/resetpassword.xul<?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://pippki/locale/pippki.dtd">

<dialog id="reset_password" title="&resetPassword.title;"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  buttons="accept,cancel"
  buttonlabelaccept="&resetPasswordButtonLabel;"
  defaultButton="cancel"
  ondialogaccept="return resetPassword();"
  style="width: 40em;">

  <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>

  <script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
  <script type="application/javascript" src="chrome://pippki/content/resetpassword.js"/>

  <hbox flex="1">
    <vbox>
      <image class="alert-icon" style="margin: 5px;"/>
    </vbox>
    <vbox style="margin: 5px;" flex="1">
      <hbox flex="1">
        <vbox flex="1">
          <description>&resetPassword.text;</description>
        </vbox>
      </hbox>
    </vbox>
  </hbox>
</dialog>
PK
!<sWKK.chrome/pippki/content/pippki/setp12password.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/**
 * @file Implements the functionality of setp12password.xul: a dialog that lets
 *       the user confirm the password to set on a PKCS #12 file.
 * @argument {nsISupports} window.arguments[0]
 *           Object to set the return values of calling the dialog on, queryable
 *           to the underlying type of SetP12PasswordReturnValues.
 */

/**
 * @typedef SetP12PasswordReturnValues
 * @type nsIWritablePropertyBag2
 * @property {Boolean} confirmedPassword
 *           Set to true if the user entered two matching passwords and
 *           confirmed the dialog.
 * @property {String} password
 *           The password the user entered. Undefined value if
 *           |confirmedPassword| is not true.
 */

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;

/**
 * onload() handler.
 */
function onLoad() {
  // Ensure the first password textbox has focus.
  document.getElementById("pw1").focus();
}

/**
 * ondialogaccept() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogAccept() {
  let password = document.getElementById("pw1").value;

  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("confirmedPassword", true);
  retVals.setPropertyAsAString("password", password);
  return true;
}

/**
 * ondialogcancel() handler.
 *
 * @returns {Boolean} true to make the dialog close, false otherwise.
 */
function onDialogCancel() {
  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("confirmedPassword", false);
  return true;
}

/**
 * Calculates the strength of the given password, suitable for use in updating
 * a progress bar that represents said strength.
 *
 * The strength of the password is calculated by checking the number of:
 *   - Characters
 *   - Numbers
 *   - Non-alphanumeric chars
 *   - Upper case characters
 *
 * @param {String} password
 *        The password to calculate the strength of.
 * @returns {Number}
 *          The strength of the password in the range [0, 100].
 */
function getPasswordStrength(password) {
  let lengthStrength = password.length;
  if (lengthStrength > 5) {
    lengthStrength = 5;
  }

  let nonNumericChars = password.replace(/[0-9]/g, "");
  let numericStrength = password.length - nonNumericChars.length;
  if (numericStrength > 3) {
    numericStrength = 3;
  }

  let nonSymbolChars = password.replace(/\W/g, "");
  let symbolStrength = password.length - nonSymbolChars.length;
  if (symbolStrength > 3) {
    symbolStrength = 3;
  }

  let nonUpperAlphaChars = password.replace(/[A-Z]/g, "");
  let upperAlphaStrength = password.length - nonUpperAlphaChars.length;
  if (upperAlphaStrength > 3) {
    upperAlphaStrength = 3;
  }

  let strength = (lengthStrength * 10) - 20 + (numericStrength * 10) +
                 (symbolStrength * 15) + (upperAlphaStrength * 10);
  if (strength < 0) {
    strength = 0;
  }
  if (strength > 100) {
    strength = 100;
  }

  return strength;
}

/**
 * oninput() handler for both password textboxes.
 *
 * @param {Boolean} recalculatePasswordStrength
 *                  Whether to recalculate the strength of the first password.
 */
function onPasswordInput(recalculatePasswordStrength) {
  let pw1 = document.getElementById("pw1").value;

  if (recalculatePasswordStrength) {
    document.getElementById("pwmeter").value = getPasswordStrength(pw1);
  }

  // Disable the accept button if the two passwords don't match, and enable it
  // if the passwords do match.
  let pw2 = document.getElementById("pw2").value;
  document.documentElement.getButton("accept").disabled = (pw1 != pw2);
}
PK
!<*/chrome/pippki/content/pippki/setp12password.xul<?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://pippki/locale/pippki.dtd">

<dialog id="setp12password"
        title="&pkcs12.setpassword.title;"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        style="width: 48em;"
        buttons="accept,cancel"
        ondialogaccept="return onDialogAccept();"
        ondialogcancel="return onDialogCancel();"
        onload="onLoad();">

  <script type="application/javascript"
          src="chrome://pippki/content/setp12password.js"/>

  <description>&pkcs12.setpassword.message;</description>
  <separator />
  <grid>
    <columns> <column/> <column/> </columns>
    <rows>
      <row>
        <label value="&pkcs12.setpassword.label1;"/>
        <textbox id="pw1" type="password" oninput="onPasswordInput(true);"/>
      </row>
      <row>
        <label value="&pkcs12.setpassword.label2;"/>
        <textbox id="pw2" type="password" oninput="onPasswordInput(false);"/>
      </row>
    </rows>
  </grid>
  <separator/>
  <description>&pkcs12.setpassword.reminder;</description>
  <separator/>
  <label value="&setPassword.meter.label;"/>
  <grid style="margin: 4px;">
    <rows> <row/> </rows>
    <columns>
      <column style="margin: 5px;">
        <progressmeter flex="1" id="pwmeter" mode="determined" value="0"
                       orient="horizontal"
                       style="width: 20em; foreground-color: red"/>
      </column>
    </columns>
  </grid>
</dialog>
PK
!<9A

0chrome/pippki/content/pippki/viewCertDetails.xul<?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 SYSTEM "chrome://pippki/locale/certManager.dtd">

<overlay id="certViewerOverlay" 
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
         xmlns:cert="http://netscape.com/rdf-cert#" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox class="box-padded" id="general_info">
  <vbox id="verify_info_box">
    <label id="verify_pending" value="&certmgr.pending.label;"/>
    <label class="header" id="verified"/>
  </vbox>
  <separator class="groove"/>
  <vbox flex="1">
    <grid> 
      <columns>
        <column/>
        <column flex="1"/>
      </columns>
      <rows>
        <row>
          <label class="header" value="&certmgr.subjectinfo.label;"/>
          <spacer/>
          <spacer/>
        </row>
        <row>
          <label value="&certmgr.certdetail.cn;"/>
          <textbox id="commonname" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.certdetail.o;"/>
          <textbox id="organization" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.certdetail.ou;"/>
          <textbox id="orgunit" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.certdetail.serialnumber;"/>
          <textbox id="serialnumber" class="plain" readonly="true"/>
        </row>
        <row>
          <separator class="thin"/>
          <spacer/>
        </row>
        <row>
          <label class="header" value="&certmgr.issuerinfo.label;"/>
          <spacer/>
        </row>
        <row>
          <label value="&certmgr.certdetail.cn;"/>
          <textbox id="issuercommonname" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.certdetail.o;"/>
          <textbox id="issuerorganization" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.certdetail.ou;"/>
          <textbox id="issuerorgunit" class="plain" readonly="true"/>
        </row>
        <row>
          <separator class="thin"/>
          <spacer/>
        </row>
        <row>
          <label class ="header" value="&certmgr.periodofvalidity.label;"/>
          <spacer/>
        </row>
        <row>
          <label value="&certmgr.begins;"/>
          <textbox id="validitystart" class="plain" readonly="true"/>
        </row>
        <row>
          <label value="&certmgr.expires;"/>
          <textbox id="validityend" class="plain" readonly="true"/>
        </row>
        <row>
          <separator class="thin"/>
          <spacer/>
        </row>
        <row>
          <label class="header" value="&certmgr.fingerprints.label;"/>
          <spacer/>
        </row>
        <row>
          <label value="&certmgr.certdetail.sha256fingerprint;"/>
          <hbox>
            <textbox id="sha256fingerprint" class="plain" readonly="true" multiline="true"
                     style="height: 6ex; width: 48ch; font-family: monospace;"/>
          </hbox>
        </row>
        <row>
          <label value="&certmgr.certdetail.sha1fingerprint;"/>
          <textbox id="sha1fingerprint" class="plain" readonly="true" style="min-width:34em;"/>
        </row>
      </rows>
    </grid>
  </vbox>
</vbox>
</overlay>
PK
!<gUUcomponents/CrashService.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If 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/AppConstants.jsm", this);
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
Cu.import("resource://gre/modules/KeyValueParser.jsm");
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

/**
 * Run the minidump analyzer tool to gather stack traces from the minidump. The
 * stack traces will be stored in the .extra file under the StackTraces= entry.
 *
 * @param minidumpPath {string} The path to the minidump file
 *
 * @returns {Promise} A promise that gets resolved once minidump analysis has
 *          finished.
 */
function runMinidumpAnalyzer(minidumpPath) {
  return new Promise((resolve, reject) => {
    const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
    const exeName = "minidump-analyzer" + binSuffix;

    let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);

    if (AppConstants.platform === "macosx") {
        exe.append("crashreporter.app");
        exe.append("Contents");
        exe.append("MacOS");
    }

    exe.append(exeName);

    let args = [ minidumpPath ];
    let process = Cc["@mozilla.org/process/util;1"]
                    .createInstance(Ci.nsIProcess);
    process.init(exe);
    process.startHidden = true;
    process.runAsync(args, args.length, (subject, topic, data) => {
      switch (topic) {
        case "process-finished":
          resolve();
          break;
        default:
          reject(topic);
          break;
      }
    });
  });
}

/**
 * Computes the SHA256 hash of a minidump file
 *
 * @param minidumpPath {string} The path to the minidump file
 *
 * @returns {Promise} A promise that resolves to the hash value of the
 *          minidump.
 */
function computeMinidumpHash(minidumpPath) {
  return (async function() {
    let minidumpData = await OS.File.read(minidumpPath);
    let hasher = Cc["@mozilla.org/security/hash;1"]
                   .createInstance(Ci.nsICryptoHash);
    hasher.init(hasher.SHA256);
    hasher.update(minidumpData, minidumpData.length);

    let hashBin = hasher.finish(false);
    let hash = "";

    for (let i = 0; i < hashBin.length; i++) {
      // Every character in the hash string contains a byte of the hash data
      hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
    }

    return hash;
  })();
}

/**
 * Process the given .extra file and return the annotations it contains in an
 * object.
 *
 * @param extraPath {string} The path to the .extra file
 *
 * @return {Promise} A promise that resolves to an object holding the crash
 *         annotations.
 */
function processExtraFile(extraPath) {
  return (async function() {
    let decoder = new TextDecoder();
    let extraData = await OS.File.read(extraPath);

    return parseKeyValuePairs(decoder.decode(extraData));
  })();
}

/**
 * This component makes crash data available throughout the application.
 *
 * It is a service because some background activity will eventually occur.
 */
this.CrashService = function() {};

CrashService.prototype = Object.freeze({
  classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsICrashService,
    Ci.nsIObserver,
  ]),

  addCrash(processType, crashType, id) {
    switch (processType) {
    case Ci.nsICrashService.PROCESS_TYPE_MAIN:
      processType = Services.crashmanager.PROCESS_TYPE_MAIN;
      break;
    case Ci.nsICrashService.PROCESS_TYPE_CONTENT:
      processType = Services.crashmanager.PROCESS_TYPE_CONTENT;
      break;
    case Ci.nsICrashService.PROCESS_TYPE_PLUGIN:
      processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
      break;
    case Ci.nsICrashService.PROCESS_TYPE_GMPLUGIN:
      processType = Services.crashmanager.PROCESS_TYPE_GMPLUGIN;
      break;
    case Ci.nsICrashService.PROCESS_TYPE_GPU:
      processType = Services.crashmanager.PROCESS_TYPE_GPU;
      break;
    default:
      throw new Error("Unrecognized PROCESS_TYPE: " + processType);
    }

    switch (crashType) {
    case Ci.nsICrashService.CRASH_TYPE_CRASH:
      crashType = Services.crashmanager.CRASH_TYPE_CRASH;
      break;
    case Ci.nsICrashService.CRASH_TYPE_HANG:
      crashType = Services.crashmanager.CRASH_TYPE_HANG;
      break;
    default:
      throw new Error("Unrecognized CRASH_TYPE: " + crashType);
    }

    let blocker = (async function() {
      let metadata = {};
      let hash = null;

      try {
        let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
                   .getService(Components.interfaces.nsICrashReporter);
        let minidumpPath = cr.getMinidumpForID(id).path;
        let extraPath = cr.getExtraFileForID(id).path;

        await runMinidumpAnalyzer(minidumpPath);
        metadata = await processExtraFile(extraPath);
        hash = await computeMinidumpHash(minidumpPath);
      } catch (e) {
        Cu.reportError(e);
      }

      if (hash) {
        metadata.MinidumpSha256Hash = hash;
      }

      await Services.crashmanager.addCrash(processType, crashType, id,
                                           new Date(), metadata);
    })();

    AsyncShutdown.profileBeforeChange.addBlocker(
      "CrashService waiting for content crash ping to be sent", blocker
    );

    blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));

    return blocker;
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "profile-after-change":
        // Side-effect is the singleton is instantiated.
        Services.crashmanager;
        break;
    }
  },
});

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashService]);
PK
!<HE_#components/nsTerminatorTelemetry.js/* -*- 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";

/**
 * Read the data saved by nsTerminator during shutdown and feed it to the
 * relevant telemetry histograms.
 */

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "OS",
  "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
  "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
  "resource://gre/modules/Services.jsm");

function nsTerminatorTelemetry() {}

var HISTOGRAMS = {
  "quit-application": "SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION",
  "profile-change-teardown": "SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN",
  "profile-before-change":  "SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE",
  "xpcom-will-shutdown": "SHUTDOWN_PHASE_DURATION_TICKS_XPCOM_WILL_SHUTDOWN",
};

nsTerminatorTelemetry.prototype = {
  classID: Components.ID("{3f78ada1-cba2-442a-82dd-d5fb300ddea7}"),

  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsTerminatorTelemetry),

  // nsISupports

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),

  // nsIObserver

  observe: function DS_observe(aSubject, aTopic, aData) {
    (async function() {
      //
      // This data is hardly critical, reading it can wait for a few seconds.
      //
      await new Promise(resolve => setTimeout(resolve, 3000));

      let PATH = OS.Path.join(OS.Constants.Path.localProfileDir,
        "ShutdownDuration.json");
      let raw;
      try {
        raw = await OS.File.read(PATH, { encoding: "utf-8" });
      } catch (ex) {
        if (!ex.becauseNoSuchFile) {
          throw ex;
        }
        return;
      }
      // Let other errors be reported by Promise's error-reporting.

      // Clean up
      OS.File.remove(PATH);
      OS.File.remove(PATH + ".tmp");

      let data = JSON.parse(raw);
      for (let k of Object.keys(data)) {
        let id = HISTOGRAMS[k];
        try {
          let histogram = Services.telemetry.getHistogramById(id);
          if (!histogram) {
            throw new Error("Unknown histogram " + id);
          }

          histogram.add(Number.parseInt(data[k]));
        } catch (ex) {
          // Make sure that the error is reported and causes test failures,
          // but otherwise, ignore it.
          Promise.reject(ex);
          continue;
        }
      }

      // Inform observers that we are done.
      Services.obs.notifyObservers(null,
        "shutdown-terminator-telemetry-updated");
    })();
  },
};

// Module

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsTerminatorTelemetry]);
PK
!<B/,/,components/SanityTest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { utils: Cu, interfaces: Ci, classes: Cc, results: Cr } = Components;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const FRAME_SCRIPT_URL = "chrome://gfxsanity/content/gfxFrameScript.js";

const PAGE_WIDTH = 92;
const PAGE_HEIGHT = 166;
const DRIVER_PREF = "sanity-test.driver-version";
const DEVICE_PREF = "sanity-test.device-id";
const VERSION_PREF = "sanity-test.version";
const ADVANCED_LAYERS_PREF = "sanity-test.advanced-layers";
const DISABLE_VIDEO_PREF = "media.hardware-video-decoding.failed";
const RUNNING_PREF = "sanity-test.running";
const TIMEOUT_SEC = 20;

const AL_ENABLED_PREF = "layers.mlgpu.dev-enabled";
const AL_TEST_FAILED_PREF = "layers.mlgpu.sanity-test-failed";

// GRAPHICS_SANITY_TEST histogram enumeration values
const TEST_PASSED = 0;
const TEST_FAILED_RENDER = 1;
const TEST_FAILED_VIDEO = 2;
const TEST_CRASHED = 3;
const TEST_TIMEOUT = 4;

// GRAPHICS_SANITY_TEST_REASON enumeration values.
const REASON_FIRST_RUN = 0;
const REASON_FIREFOX_CHANGED = 1;
const REASON_DEVICE_CHANGED = 2;
const REASON_DRIVER_CHANGED = 3;
const REASON_AL_CONFIG_CHANGED = 4;

// GRAPHICS_SANITY_TEST_OS_SNAPSHOT histogram enumeration values
const SNAPSHOT_VIDEO_OK = 0;
const SNAPSHOT_VIDEO_FAIL = 1;
const SNAPSHOT_ERROR = 2;
const SNAPSHOT_TIMEOUT = 3;
const SNAPSHOT_LAYERS_OK = 4;
const SNAPSHOT_LAYERS_FAIL = 5;

function testPixel(ctx, x, y, r, g, b, a, fuzz) {
  var data = ctx.getImageData(x, y, 1, 1);

  if (Math.abs(data.data[0] - r) <= fuzz &&
      Math.abs(data.data[1] - g) <= fuzz &&
      Math.abs(data.data[2] - b) <= fuzz &&
      Math.abs(data.data[3] - a) <= fuzz) {
    return true;
  }
  return false;
}

function reportResult(val) {
  try {
    let histogram = Services.telemetry.getHistogramById("GRAPHICS_SANITY_TEST");
    histogram.add(val);
  } catch (e) {}

  Services.prefs.setBoolPref(RUNNING_PREF, false);
  Services.prefs.savePrefFile(null);
}

function reportTestReason(val) {
  let histogram = Services.telemetry.getHistogramById("GRAPHICS_SANITY_TEST_REASON");
  histogram.add(val);
}

function annotateCrashReport(value) {
  try {
    // "1" if we're annotating the crash report, "" to remove the annotation.
    var crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].
                          getService(Ci.nsICrashReporter);
    crashReporter.annotateCrashReport("GraphicsSanityTest", value ? "1" : "");
  } catch (e) {
  }
}

function setTimeout(aMs, aCallback) {
  var timer = Cc["@mozilla.org/timer;1"].
                createInstance(Ci.nsITimer);
  timer.initWithCallback(aCallback, aMs, Ci.nsITimer.TYPE_ONE_SHOT);
}

function takeWindowSnapshot(win, ctx) {
  // TODO: drawWindow reads back from the gpu's backbuffer, which won't catch issues with presenting
  // the front buffer via the window manager. Ideally we'd use an OS level API for reading back
  // from the desktop itself to get a more accurate test.
  var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
  ctx.drawWindow(win.ownerGlobal, 0, 0, PAGE_WIDTH, PAGE_HEIGHT, "rgb(255,255,255)", flags);
}

// Verify that all the 4 coloured squares of the video
// render as expected (with a tolerance of 64 to allow for
// yuv->rgb differences between platforms).
//
// The video is 64x64, and is split into quadrants of
// different colours. The top left of the video is 8,72
// and we test a pixel 10,10 into each quadrant to avoid
// blending differences at the edges.
//
// We allow massive amounts of fuzz for the colours since
// it can depend hugely on the yuv -> rgb conversion, and
// we don't want to fail unnecessarily.
function verifyVideoRendering(ctx) {
  return testPixel(ctx, 18, 82, 255, 255, 255, 255, 64) &&
    testPixel(ctx, 50, 82, 0, 255, 0, 255, 64) &&
    testPixel(ctx, 18, 114, 0, 0, 255, 255, 64) &&
    testPixel(ctx, 50, 114, 255, 0, 0, 255, 64);
}

// Verify that the middle of the layers test is the color we expect.
// It's a red 64x64 square, test a pixel deep into the 64x64 square
// to prevent fuzzing.
function verifyLayersRendering(ctx) {
  return testPixel(ctx, 18, 18, 255, 0, 0, 255, 64);
}

function testCompositor(test, win, ctx) {
  takeWindowSnapshot(win, ctx);
  var testPassed = true;

  if (!verifyVideoRendering(ctx)) {
    reportResult(TEST_FAILED_VIDEO);
    Services.prefs.setBoolPref(DISABLE_VIDEO_PREF, true);
    testPassed = false;
  }

  if (!verifyLayersRendering(ctx)) {
    // Try disabling advanced layers if it was enabled. Also trgiger
    // a device reset so the screen redraws.
    if (Services.prefs.getBoolPref(AL_ENABLED_PREF, false)) {
      Services.prefs.setBoolPref(AL_TEST_FAILED_PREF, true);
      test.utils.triggerDeviceReset();
    }
    reportResult(TEST_FAILED_RENDER);
    testPassed = false;
  } else {
    Services.prefs.setBoolPref(AL_TEST_FAILED_PREF, false);
  }

  if (testPassed) {
    reportResult(TEST_PASSED);
  }

  return testPassed;
}

var listener = {
  win: null,
  utils: null,
  canvas: null,
  ctx: null,
  mm: null,

  messages: [
    "gfxSanity:ContentLoaded",
  ],

  scheduleTest(win) {
    this.win = win;
    this.win.onload = this.onWindowLoaded.bind(this);
    this.utils = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindowUtils);
    setTimeout(TIMEOUT_SEC * 1000, () => {
      if (this.win) {
        reportResult(TEST_TIMEOUT);
        this.endTest();
      }
    });
  },

  runSanityTest() {
    this.canvas = this.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    this.canvas.setAttribute("width", PAGE_WIDTH);
    this.canvas.setAttribute("height", PAGE_HEIGHT);
    this.ctx = this.canvas.getContext("2d");

    // Perform the compositor backbuffer test, which currently we use for
    // actually deciding whether to enable hardware media decoding.
    testCompositor(this, this.win, this.ctx);

    this.endTest();
  },

  receiveMessage(message) {
    switch (message.name) {
      case "gfxSanity:ContentLoaded":
        this.runSanityTest();
        break;
    }
  },

  onWindowLoaded() {
    let browser = this.win.document.createElementNS(XUL_NS, "browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("disableglobalhistory", "true");

    let remoteBrowser = Services.appinfo.browserTabsRemoteAutostart;
    browser.setAttribute("remote", remoteBrowser);

    browser.style.width = PAGE_WIDTH + "px";
    browser.style.height = PAGE_HEIGHT + "px";

    this.win.document.documentElement.appendChild(browser);
    // Have to set the mm after we append the child
    this.mm = browser.messageManager;

    this.messages.forEach((msgName) => {
      this.mm.addMessageListener(msgName, this);
    });

    this.mm.loadFrameScript(FRAME_SCRIPT_URL, false);
  },

  endTest() {
    if (!this.win) {
      return;
    }

    this.win.ownerGlobal.close();
    this.win = null;
    this.utils = null;
    this.canvas = null;
    this.ctx = null;

    if (this.mm) {
      // We don't have a MessageManager if onWindowLoaded never fired.
      this.messages.forEach((msgName) => {
        this.mm.removeMessageListener(msgName, this);
      });

      this.mm = null;
    }

    // Remove the annotation after we've cleaned everything up, to catch any
    // incidental crashes from having performed the sanity test.
    annotateCrashReport(false);
  }
};

function SanityTest() {}
SanityTest.prototype = {
  classID: Components.ID("{f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd}"),
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                         Ci.nsISupportsWeakReference]),

  shouldRunTest() {
    // Only test gfx features if firefox has updated, or if the user has a new
    // gpu or drivers.
    var buildId = Services.appinfo.platformBuildID;
    var gfxinfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
    var hasAL = Services.prefs.getBoolPref(AL_ENABLED_PREF, false);

    if (Services.prefs.getBoolPref(RUNNING_PREF, false)) {
      Services.prefs.setBoolPref(DISABLE_VIDEO_PREF, true);
      reportResult(TEST_CRASHED);
      return false;
    }

    function checkPref(pref, value, reason) {
      let prefValue;
      let prefType = Services.prefs.getPrefType(pref);

      switch (prefType) {
          case Ci.nsIPrefBranch.PREF_INVALID:
              reportTestReason(REASON_FIRST_RUN);
              return false;

          case Ci.nsIPrefBranch.PREF_STRING:
              prefValue = Services.prefs.getStringPref(pref);
              break;

          case Ci.nsIPrefBranch.PREF_BOOL:
              prefValue = Services.prefs.getBoolPref(pref);
              break;

          case Ci.nsIPrefBranch.PREF_INT:
              prefValue = Services.prefs.getIntPref(pref);
              break;

          default:
              throw new Error("Unexpected preference type.");
      }

      if (prefValue != value) {
        reportTestReason(reason);
        return false;
      }

      return true;
    }

    // TODO: Handle dual GPU setups
    if (checkPref(DRIVER_PREF, gfxinfo.adapterDriverVersion, REASON_DRIVER_CHANGED) &&
        checkPref(DEVICE_PREF, gfxinfo.adapterDeviceID, REASON_DEVICE_CHANGED) &&
        checkPref(VERSION_PREF, buildId, REASON_FIREFOX_CHANGED) &&
        checkPref(ADVANCED_LAYERS_PREF, hasAL, REASON_AL_CONFIG_CHANGED)) {
      return false;
    }

    // Enable hardware decoding so we can test again
    // and record the driver version to detect if the driver changes.
    Services.prefs.setBoolPref(DISABLE_VIDEO_PREF, false);
    Services.prefs.setStringPref(DRIVER_PREF, gfxinfo.adapterDriverVersion);
    Services.prefs.setStringPref(DEVICE_PREF, gfxinfo.adapterDeviceID);
    Services.prefs.setStringPref(VERSION_PREF, buildId);
    Services.prefs.setBoolPref(ADVANCED_LAYERS_PREF, hasAL);

    // Update the prefs so that this test doesn't run again until the next update.
    Services.prefs.setBoolPref(RUNNING_PREF, true);
    Services.prefs.savePrefFile(null);
    return true;
  },

  observe(subject, topic, data) {
    if (topic != "profile-after-change") return;

    // profile-after-change fires only at startup, so we won't need
    // to use the listener again.
    let tester = listener;
    listener = null;

    if (!this.shouldRunTest()) return;

    annotateCrashReport(true);

    // Open a tiny window to render our test page, and notify us when it's loaded
    var sanityTest = Services.ww.openWindow(null,
        "chrome://gfxsanity/content/sanityparent.html",
        "Test Page",
        "width=" + PAGE_WIDTH + ",height=" + PAGE_HEIGHT + ",chrome,titlebar=0,scrollbars=0",
        null);

    // There's no clean way to have an invisible window and ensure it's always painted.
    // Instead, move the window far offscreen so it doesn't show up during launch.
    sanityTest.moveTo(100000000, 1000000000);
    tester.scheduleTest(sanityTest);
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SanityTest]);
PK--

Anon7 - 2022
AnonSec Team